init
This commit is contained in:
156
uni_modules/nutui-uni/components/noticebar/index.scss
Normal file
156
uni_modules/nutui-uni/components/noticebar/index.scss
Normal file
@@ -0,0 +1,156 @@
|
||||
/* stylelint-disable keyframes-name-pattern */
|
||||
.nut-theme-dark {
|
||||
.nut-noticebar__page {
|
||||
color: $dark-color;
|
||||
background: $dark-background2;
|
||||
}
|
||||
|
||||
.nut-noticebar__vertical {
|
||||
color: $dark-color;
|
||||
}
|
||||
}
|
||||
|
||||
.nut-noticebar__page {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: $noticebar-across-height;
|
||||
padding: $noticebar-box-padding;
|
||||
font-size: $noticebar-font-size;
|
||||
color: $noticebar-color;
|
||||
background: $noticebar-background;
|
||||
|
||||
&--wrapable {
|
||||
height: auto;
|
||||
padding: $noticebar-wrapable-padding;
|
||||
|
||||
.nut-noticebar__page-wrap {
|
||||
height: auto !important;
|
||||
|
||||
.nut-noticebar__page-wrap-content {
|
||||
position: relative;
|
||||
word-wrap: break-word;
|
||||
white-space: normal;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.nut-noticebar__page--withicon {
|
||||
position: relative;
|
||||
padding-right: 40px;
|
||||
}
|
||||
|
||||
.nut-noticebar__page-lefticon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin: $noticebar-lefticon-margin;
|
||||
background-size: 100% 100%;
|
||||
}
|
||||
|
||||
.nut-noticebar__page-righticon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin: $noticebar-righticon-margin;
|
||||
}
|
||||
|
||||
.nut-noticebar__page-wrap {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
height: $noticebar-across-line-height;
|
||||
overflow: hidden;
|
||||
line-height: $noticebar-across-line-height;
|
||||
}
|
||||
|
||||
.nut-noticebar__page-wrap-content {
|
||||
position: absolute;
|
||||
white-space: nowrap;
|
||||
|
||||
&.nut-ellipsis {
|
||||
display: inline-block;
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
// 只跑一次
|
||||
.play {
|
||||
animation: nut-notice-bar-play linear both running;
|
||||
}
|
||||
|
||||
.play-infinite {
|
||||
animation: nut-notice-bar-play-infinite linear infinite both running;
|
||||
}
|
||||
|
||||
.play-vertical {
|
||||
animation: nut-notice-bar-play-vertical linear infinite both running;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes nut-notice-bar-play {
|
||||
to {
|
||||
transform: translate3d(-100%, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes nut-notice-bar-play-infinite {
|
||||
to {
|
||||
transform: translateX(-100%);
|
||||
}
|
||||
}
|
||||
|
||||
// 垂直方向的滚动
|
||||
@keyframes nut-notice-bar-play-vertical {
|
||||
to {
|
||||
transform: translateY($noticebar-across-height);
|
||||
}
|
||||
}
|
||||
|
||||
// 纵向
|
||||
.nut-noticebar__vertical {
|
||||
position: relative;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
height: $noticebar-across-height;
|
||||
padding: $noticebar-box-padding;
|
||||
overflow: hidden;
|
||||
font-size: $noticebar-font-size;
|
||||
color: $noticebar-color;
|
||||
background: $noticebar-background;
|
||||
|
||||
.nut-noticebar__vertical-list {
|
||||
display: block;
|
||||
flex: 1;
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
|
||||
.nut-noticebar__vertical-item {
|
||||
width: 100%;
|
||||
|
||||
// display: flex;
|
||||
// align-items: center;
|
||||
|
||||
|
||||
height: $noticebar-across-height;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
.nut-noticebar-custom-item {
|
||||
position: absolute;
|
||||
top: 999999px;
|
||||
}
|
||||
|
||||
.go {
|
||||
display: flex;
|
||||
align-self: center;
|
||||
margin: $noticebar-righticon-margin;
|
||||
}
|
||||
}
|
||||
1
uni_modules/nutui-uni/components/noticebar/index.ts
Normal file
1
uni_modules/nutui-uni/components/noticebar/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export type * from './noticebar'
|
||||
95
uni_modules/nutui-uni/components/noticebar/noticebar.ts
Normal file
95
uni_modules/nutui-uni/components/noticebar/noticebar.ts
Normal file
@@ -0,0 +1,95 @@
|
||||
import type { ExtractPropTypes } from 'vue'
|
||||
import { CLICK_EVENT, CLOSE_EVENT } from '../_constants'
|
||||
import { commonProps, isString, makeArrayProp, makeNumberProp, makeNumericProp, makeStringProp, truthProp } from '../_utils'
|
||||
|
||||
export interface stateProps {
|
||||
wrapWidth: number
|
||||
firstRound: boolean
|
||||
duration: number
|
||||
offsetWidth: number
|
||||
showNoticebar: boolean
|
||||
animationClass: string
|
||||
animate: boolean
|
||||
scrollList: any[]
|
||||
distance: number
|
||||
timer: null
|
||||
keepAlive: boolean
|
||||
isCanScroll: null | boolean
|
||||
showNotica: boolean
|
||||
id: number
|
||||
}
|
||||
|
||||
export const noticebarProps = {
|
||||
...commonProps,
|
||||
/**
|
||||
* @description 滚动的方向,可选 `across`、`vertical`
|
||||
*/
|
||||
direction: makeStringProp<'across' | 'vertical'>('across'),
|
||||
/**
|
||||
* @description 纵向滚动数据列表, `vertical`方向
|
||||
*/
|
||||
list: makeArrayProp<string | object>([]),
|
||||
/**
|
||||
* @description 停留时间(毫秒),`vertical`方向
|
||||
*/
|
||||
standTime: makeNumberProp(1000),
|
||||
/**
|
||||
* @description 稍复杂的动画,耗能会高,`vertical`方向
|
||||
*/
|
||||
complexAm: Boolean,
|
||||
/**
|
||||
* @description 每一个滚动列的高度(px),注意:在使用 slot 插槽定义滚动单元时,按照实际高度修改此值
|
||||
*/
|
||||
height: makeNumberProp(40),
|
||||
/**
|
||||
* @description 提示的信息
|
||||
*/
|
||||
text: makeStringProp(''),
|
||||
/**
|
||||
* @description 是否启用关闭模式
|
||||
*/
|
||||
closeMode: Boolean,
|
||||
/**
|
||||
* @description 是否展示左侧图标, 滚动方向为 `across` 生效
|
||||
*/
|
||||
leftIcon: truthProp,
|
||||
/**
|
||||
* @description 导航栏的文字颜色
|
||||
*/
|
||||
customColor: makeStringProp(''),
|
||||
/**
|
||||
* @description 导航栏的背景颜色
|
||||
*/
|
||||
background: makeStringProp(''),
|
||||
/**
|
||||
* @description 延时多少秒
|
||||
*/
|
||||
delay: makeNumericProp(1),
|
||||
/**
|
||||
* @description 是否可以滚动
|
||||
*/
|
||||
scrollable: truthProp,
|
||||
/**
|
||||
* @description 滚动速率 (px/s)
|
||||
*/
|
||||
speed: makeNumberProp(50),
|
||||
/**
|
||||
* @description 是否开启文本换行,`scrollable` 会设置为 `false`
|
||||
*/
|
||||
wrapable: Boolean,
|
||||
/**
|
||||
* @description `vertical`方向时`list`属性如果传入数组对象,显示文本的字段名
|
||||
*/
|
||||
fieldName: String,
|
||||
}
|
||||
|
||||
export type NoticeBarProps = ExtractPropTypes<typeof noticebarProps>
|
||||
|
||||
export const noticebarEmits = {
|
||||
[CLICK_EVENT]: (value: Event | string) => value instanceof Object || isString(value),
|
||||
[CLOSE_EVENT]: (evt: Event | string) => evt instanceof Object || isString(evt),
|
||||
acrossEnd: (evt: Event) => evt instanceof Object,
|
||||
|
||||
}
|
||||
|
||||
export type NoticeBarEmits = typeof noticebarEmits
|
||||
323
uni_modules/nutui-uni/components/noticebar/noticebar.vue
Normal file
323
uni_modules/nutui-uni/components/noticebar/noticebar.vue
Normal file
@@ -0,0 +1,323 @@
|
||||
<script setup lang="ts">
|
||||
import type { ComponentInternalInstance } from 'vue'
|
||||
import { computed, defineComponent, getCurrentInstance, onActivated, onDeactivated, onMounted, onUnmounted, reactive, ref, watch } from 'vue'
|
||||
import { CLICK_EVENT, CLOSE_EVENT, PREFIX } from '../_constants'
|
||||
import { useSelectorQuery } from '../_hooks'
|
||||
import { getMainClass, isObject, pxCheck } from '../_utils'
|
||||
import NutIcon from '../icon/icon.vue'
|
||||
import type { stateProps } from './noticebar'
|
||||
import { noticebarEmits, noticebarProps } from './noticebar'
|
||||
|
||||
const props = defineProps(noticebarProps)
|
||||
|
||||
const emit = defineEmits(noticebarEmits)
|
||||
const instance = getCurrentInstance() as ComponentInternalInstance
|
||||
const { getSelectorNodeInfo } = useSelectorQuery(instance)
|
||||
|
||||
const wrap = ref<null | HTMLElement>(null)
|
||||
const content = ref<null | HTMLElement>(null)
|
||||
|
||||
const state = reactive<stateProps>({
|
||||
wrapWidth: 0,
|
||||
firstRound: true,
|
||||
duration: 0,
|
||||
offsetWidth: 0,
|
||||
showNoticebar: true,
|
||||
animationClass: '',
|
||||
|
||||
animate: false,
|
||||
scrollList: [],
|
||||
distance: 0,
|
||||
timer: null,
|
||||
keepAlive: false,
|
||||
isCanScroll: null,
|
||||
showNotica: true,
|
||||
id: Math.round(Math.random() * 100000),
|
||||
})
|
||||
|
||||
const classes = computed(() => {
|
||||
return getMainClass(props, componentName)
|
||||
})
|
||||
|
||||
const isEllipsis = computed(() => {
|
||||
if (state.isCanScroll == null)
|
||||
return false && !props.wrapable
|
||||
|
||||
else
|
||||
return !state.isCanScroll && !props.wrapable
|
||||
})
|
||||
|
||||
const wrapContentClass = computed(() => {
|
||||
return {
|
||||
'nut-noticebar__page-wrap-content': true,
|
||||
'nut-ellipsis': isEllipsis.value,
|
||||
[`content${state.id}`]: true,
|
||||
[state.animationClass]: true,
|
||||
}
|
||||
})
|
||||
|
||||
const barStyle = computed(() => {
|
||||
const style: {
|
||||
[props: string]: any
|
||||
} = {}
|
||||
|
||||
props.customColor && (style.color = props.customColor)
|
||||
props.background && (style.background = props.background)
|
||||
|
||||
if (props.direction === 'vertical')
|
||||
style.height = `${props.height}px`
|
||||
|
||||
return style
|
||||
})
|
||||
|
||||
const contentStyle = computed(() => {
|
||||
return {
|
||||
animationDelay: `${state.firstRound ? props.delay : 0}s`,
|
||||
animationDuration: `${state.duration}s`,
|
||||
transform: `translateX(${state.firstRound ? 0 : `${state.wrapWidth}px`})`,
|
||||
}
|
||||
})
|
||||
const horseLampStyle = computed(() => {
|
||||
let styles = {}
|
||||
if (props.complexAm) {
|
||||
styles = {
|
||||
transform: `translateY(${state.distance}px)`,
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (state.animate) {
|
||||
styles = {
|
||||
'transition': `all ${~~(props.height / props.speed / 4)}s`,
|
||||
'margin-top': `-${props.height}px`,
|
||||
}
|
||||
}
|
||||
}
|
||||
return styles
|
||||
})
|
||||
|
||||
watch(
|
||||
() => props.text,
|
||||
() => {
|
||||
initScrollWrap()
|
||||
},
|
||||
)
|
||||
|
||||
watch(
|
||||
() => props.list,
|
||||
(value) => {
|
||||
state.scrollList = [].concat(value as any)
|
||||
},
|
||||
)
|
||||
|
||||
function initScrollWrap() {
|
||||
if (state.showNoticebar === false)
|
||||
return
|
||||
|
||||
setTimeout(() => {
|
||||
if (state.showNoticebar === false)
|
||||
return
|
||||
|
||||
let wrapWidth = 0
|
||||
let offsetWidth = 0
|
||||
|
||||
getSelectorNodeInfo(`.wrap${state.id}`).then((rect) => {
|
||||
if (rect.width! > 0)
|
||||
wrapWidth = rect.width!
|
||||
getSelectorNodeInfo(`.content${state.id}`).then((rect) => {
|
||||
if (rect.width! > 0)
|
||||
offsetWidth = rect.width!
|
||||
state.isCanScroll = props.scrollable == null ? offsetWidth > wrapWidth : props.scrollable
|
||||
if (state.isCanScroll) {
|
||||
state.wrapWidth = wrapWidth
|
||||
state.offsetWidth = offsetWidth
|
||||
|
||||
state.duration = offsetWidth / props.speed
|
||||
state.animationClass = 'play'
|
||||
}
|
||||
else {
|
||||
state.animationClass = ''
|
||||
}
|
||||
})
|
||||
})
|
||||
}, 100)
|
||||
}
|
||||
function handleClick(event: Event) {
|
||||
emit('click', event)
|
||||
}
|
||||
|
||||
function onClickIcon(event: Event) {
|
||||
if (props.closeMode)
|
||||
state.showNoticebar = !props.closeMode
|
||||
|
||||
emit('close', event)
|
||||
}
|
||||
|
||||
function onAnimationEnd(event: Event) {
|
||||
state.firstRound = false
|
||||
emit('acrossEnd', event)
|
||||
setTimeout(() => {
|
||||
state.duration = (state.offsetWidth + state.wrapWidth) / props.speed
|
||||
state.animationClass = 'play-infinite'
|
||||
}, 0)
|
||||
}
|
||||
|
||||
/**
|
||||
* 利益点滚动方式一
|
||||
*/
|
||||
function startRollEasy() {
|
||||
showhorseLamp();
|
||||
(state.timer as any) = setInterval(showhorseLamp, ~~(props.height / props.speed / 4) * 1000 + props.standTime)
|
||||
}
|
||||
function showhorseLamp() {
|
||||
state.animate = true
|
||||
setTimeout(() => {
|
||||
state.scrollList.push(state.scrollList[0])
|
||||
state.scrollList.shift()
|
||||
state.animate = false
|
||||
}, ~~(props.height / props.speed / 4) * 1000)
|
||||
}
|
||||
|
||||
function startRoll() {
|
||||
(state.timer as any) = setInterval(() => {
|
||||
const chunk = 100
|
||||
for (let i = 0; i < chunk; i++)
|
||||
scroll(i, !(i < chunk - 1))
|
||||
}, props.standTime + 100 * props.speed)
|
||||
}
|
||||
function scroll(n: number, last: boolean) {
|
||||
setTimeout(() => {
|
||||
state.distance -= props.height / 100
|
||||
if (last) {
|
||||
state.scrollList.push(state.scrollList[0])
|
||||
state.scrollList.shift()
|
||||
state.distance = 0
|
||||
}
|
||||
}, n * props.speed)
|
||||
}
|
||||
/**
|
||||
* 点击滚动单元
|
||||
*/
|
||||
function go(item: any) {
|
||||
emit(CLICK_EVENT, item)
|
||||
}
|
||||
|
||||
function handleClickIcon() {
|
||||
if (props.closeMode)
|
||||
state.showNoticebar = !props.closeMode
|
||||
|
||||
emit(CLOSE_EVENT, state.scrollList[0] as any)
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
if (props.direction === 'vertical') {
|
||||
state.scrollList = [].concat(props.list as any)
|
||||
|
||||
setTimeout(() => {
|
||||
props.complexAm ? startRoll() : startRollEasy()
|
||||
}, props.standTime)
|
||||
}
|
||||
else {
|
||||
initScrollWrap()
|
||||
}
|
||||
})
|
||||
|
||||
onActivated(() => {
|
||||
if (state.keepAlive)
|
||||
state.keepAlive = false
|
||||
})
|
||||
|
||||
onDeactivated(() => {
|
||||
state.keepAlive = true
|
||||
clearInterval(state.timer as any)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
clearInterval(state.timer as any)
|
||||
})
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
const componentName = `${PREFIX}-noticebar`
|
||||
|
||||
export default defineComponent({
|
||||
name: componentName,
|
||||
options: {
|
||||
virtualHost: true,
|
||||
addGlobalClass: true,
|
||||
styleIsolation: 'shared',
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view :class="classes" :style="customStyle">
|
||||
<view
|
||||
v-if="direction === 'across'"
|
||||
class="nut-noticebar__page"
|
||||
:class="{
|
||||
'nut-noticebar__page--withicon': closeMode,
|
||||
'nut-noticebar__page--close': closeMode,
|
||||
'nut-noticebar__page--wrapable': wrapable,
|
||||
'nut-hidden': !state.showNoticebar,
|
||||
}"
|
||||
:style="barStyle"
|
||||
@click="(handleClick as any)"
|
||||
>
|
||||
<view v-if="leftIcon" class="nut-noticebar__page-lefticon">
|
||||
<slot name="leftIcon">
|
||||
<NutIcon name="notice" size="16px" />
|
||||
</slot>
|
||||
</view>
|
||||
<view ref="wrap" :class="`nut-noticebar__page-wrap wrap${state.id}`">
|
||||
<view
|
||||
ref="content"
|
||||
:class="wrapContentClass"
|
||||
:style="contentStyle"
|
||||
@animationend="(onAnimationEnd as any)"
|
||||
@webkit-animation-end="(onAnimationEnd as any)"
|
||||
>
|
||||
<slot>{{ text }}</slot>
|
||||
</view>
|
||||
</view>
|
||||
<view v-if="closeMode || $slots.rightIcon" class="nut-noticebar__page-righticon" @click.stop="(onClickIcon as any)">
|
||||
<slot name="rightIcon">
|
||||
<NutIcon name="circle-close" />
|
||||
</slot>
|
||||
</view>
|
||||
</view>
|
||||
<!-- TODO uniapp拿不到 slots -->
|
||||
|
||||
<view
|
||||
v-if="state.scrollList.length > 0 && direction === 'vertical' && state.showNoticebar"
|
||||
class="nut-noticebar__vertical"
|
||||
:style="barStyle"
|
||||
>
|
||||
<view class="nut-noticebar__vertical-list" :style="horseLampStyle">
|
||||
<view
|
||||
v-for="(item, index) in state.scrollList"
|
||||
:key="index"
|
||||
class="nut-noticebar__vertical-item"
|
||||
:style="{ height: pxCheck(height), lineHeight: pxCheck(height) }"
|
||||
@click="go(item)"
|
||||
>
|
||||
{{ props.fieldName && isObject(item) ? item[props.fieldName] : item }}
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="go" @click="handleClickIcon()">
|
||||
<slot name="rightIcon">
|
||||
<NutIcon
|
||||
v-if="closeMode"
|
||||
name="circle-close"
|
||||
:custom-color="customColor"
|
||||
size="11px"
|
||||
/>
|
||||
</slot>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
@import './index';
|
||||
</style>
|
||||
Reference in New Issue
Block a user