324 lines
7.8 KiB
Vue
324 lines
7.8 KiB
Vue
<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>
|