Files
taimed-international-app/uni_modules/wot-design-uni/components/wd-notice-bar/wd-notice-bar.vue
2025-11-04 12:37:04 +08:00

267 lines
7.0 KiB
Vue

<template>
<view v-if="show" :class="`wd-notice-bar ${customClass} ${noticeBarClass}`" :style="rootStyle">
<wd-icon v-if="prefix" custom-class="wd-notice-bar__prefix" :name="prefix"></wd-icon>
<slot v-else name="prefix"></slot>
<view class="wd-notice-bar__wrap">
<view class="wd-notice-bar__content" :style="animation" @transitionend="animationEnd" @click="handleClick">
<template v-if="isVertical">
<view v-for="item in textArray" :key="item">{{ item }}</view>
<view v-if="textArray.length > 1">{{ textArray[0] }}</view>
</template>
<slot v-else>{{ currentText }}</slot>
</view>
</view>
<wd-icon v-if="closable" custom-class="wd-notice-bar__suffix" name="close-bold" @click="handleClose"></wd-icon>
<slot v-else name="suffix"></slot>
</view>
</template>
<script lang="ts">
export default {
name: 'wd-notice-bar',
options: {
virtualHost: true,
addGlobalClass: true,
styleIsolation: 'shared'
}
}
</script>
<script lang="ts" setup>
import wdIcon from '../wd-icon/wd-icon.vue'
import { ref, watch, nextTick, computed, getCurrentInstance, type CSSProperties, onMounted, onActivated, onDeactivated, reactive } from 'vue'
import { getRect, isArray, isDef, objToStyle } from '../common/util'
import { type NoticeBarExpose, noticeBarProps } from './types'
const $wrap = '.wd-notice-bar__wrap'
const $content = '.wd-notice-bar__content'
const props = defineProps(noticeBarProps)
const emit = defineEmits(['close', 'next', 'click'])
const wrapWidth = ref<number>(0)
const show = ref<boolean>(true)
const currentIndex = ref<number>(0)
const textArray = computed(() => (Array.isArray(props.text) ? props.text : [props.text]))
const currentText = computed(() => textArray.value[currentIndex.value])
const verticalIndex = ref<number>(0)
const wrapRect = ref<UniApp.NodeInfo | null>(null) // 外层容器节点信息
const contentRect = ref<UniApp.NodeInfo | null>(null) // 内容节点信息
const isHorizontal = computed(() => props.direction === 'horizontal')
const isVertical = computed(() => props.direction === 'vertical')
const transitionState = reactive<CSSProperties>({
transitionProperty: 'unset',
transitionDelay: 'unset',
transitionDuration: 'unset',
transform: 'none',
transitionTimingFunction: 'linear'
})
const animation = computed(() => {
return objToStyle(transitionState)
})
const rootStyle = computed(() => {
const style: CSSProperties = {}
if (isDef(props.color)) {
style.color = props.color
}
if (isDef(props.backgroundColor)) {
style.background = props.backgroundColor
}
return `${objToStyle(style)}${props.customStyle}`
})
const noticeBarClass = computed(() => {
const { type, wrapable, scrollable } = props
let noticeBarClasses: string[] = []
type && noticeBarClasses.push(`is-${type}`)
if (isHorizontal.value) {
!wrapable && !scrollable && noticeBarClasses.push('wd-notice-bar--ellipse')
} else {
noticeBarClasses.push('wd-notice-bar--ellipse')
}
wrapable && !scrollable && noticeBarClasses.push('wd-notice-bar--wrap')
return noticeBarClasses.join(' ')
})
const { proxy } = getCurrentInstance() as any
watch(
() => props.text,
() => {
reset()
},
{ deep: true }
)
onMounted(() => {
startTransition()
// #ifdef APP-PLUS
const pages = getCurrentPages()
const currentPage = pages[pages.length - 1]
const currentWebview = currentPage.$getAppWebview!()
currentWebview.addEventListener('hide', () => {
stopTransition()
})
currentWebview.addEventListener('show', () => {
startTransition()
})
// #endif
})
onActivated(() => {
startTransition()
})
onDeactivated(() => {
stopTransition()
})
function reset() {
stopTransition()
startTransition()
}
function startTransition() {
nextTick(() => scroll())
}
function stopTransition() {
transitionState.transitionProperty = 'unset'
transitionState.transitionDelay = 'unset'
transitionState.transitionDuration = 'unset'
transitionState.transform = 'none'
transitionState.transitionTimingFunction = 'linear'
currentIndex.value = 0
verticalIndex.value = 0
}
function handleClose() {
show.value = false
emit('close')
}
function setTransition({ duration, delay, translate }: { duration: number; delay: number; translate: number }) {
transitionState.transitionProperty = 'all'
transitionState.transitionDelay = `${delay}s`
transitionState.transitionDuration = `${duration}s`
transitionState.transform = `${props.direction === 'vertical' ? 'translateY' : 'translateX'}(${translate}px)`
transitionState.transitionTimingFunction = 'linear'
}
function queryRect() {
return Promise.all([getRect($wrap, false, proxy), getRect($content, false, proxy)])
}
async function verticalAnimate(height: number) {
const translate = -(height / (textArray.value.length + 1)) * (currentIndex.value + 1)
setTransition({
duration: height / (textArray.value.length + 1) / props.speed,
delay: props.delay,
translate
})
}
async function scroll() {
const [wRect, cRect] = await queryRect()
if (!wRect.width || !cRect.width || !cRect.height) return
wrapRect.value = wRect
contentRect.value = cRect
wrapWidth.value = wRect.width
if (isHorizontal.value) {
if (props.scrollable) {
setTransition({
duration: cRect.width / props.speed,
delay: props.delay,
translate: -cRect.width
})
}
} else {
if (textArray.value.length > 1) {
verticalAnimate(cRect.height)
}
}
}
function next() {
if (currentIndex.value >= textArray.value.length - 1) {
currentIndex.value = 0
} else {
currentIndex.value++
}
emit('next', currentIndex.value)
}
function animationEnd() {
if (isHorizontal.value) {
setTransition({
duration: 0,
delay: 0,
translate: wrapWidth.value + 1
})
} else {
if (++verticalIndex.value >= textArray.value.length) {
verticalIndex.value = 0
setTransition({
duration: 0,
delay: 0,
translate: 0
})
}
}
const timer = setTimeout(() => {
next() // 更换下一条文本
nextTick(async () => {
try {
const [wRect, cRect] = await queryRect()
wrapRect.value = wRect
contentRect.value = cRect
wrapWidth.value = wRect.width || 0
} catch (error) {
// console.error(error)
}
if (!contentRect.value || !contentRect.value.width || !contentRect.value.height) return
if (isHorizontal.value) {
setTransition({
duration: (wrapWidth.value + contentRect.value.width) / props.speed,
delay: props.delay,
translate: -contentRect.value.width
})
} else {
verticalAnimate(contentRect.value.height)
}
})
clearTimeout(timer)
}, 20)
}
function handleClick() {
const result = isArray(props.text)
? {
index: currentIndex.value,
text: props.text[currentIndex.value]
}
: {
index: 0,
text: props.text
}
emit('click', result)
}
defineExpose<NoticeBarExpose>({ reset })
</script>
<style lang="scss" scoped>
@import './index.scss';
</style>