| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266 |
- <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>
|