You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

338 lines
9.3 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<template>
<view
class="u-notice"
@tap="clickHandler"
>
<slot name="icon">
<view
class="u-notice__left-icon"
v-if="icon"
>
<u-icon
:name="icon"
:color="color"
size="19"
></u-icon>
</view>
</slot>
<view
class="u-notice__content"
ref="u-notice__content"
>
<view
ref="u-notice__content__text"
class="u-notice__content__text"
:style="[animationStyle]"
>
<text
v-for="(item, index) in innerText"
:key="index"
:style="[textStyle]"
>{{item}}</text>
</view>
</view>
<view
class="u-notice__right-icon"
v-if="['link', 'closable'].includes(mode)"
>
<u-icon
v-if="mode === 'link'"
name="arrow-right"
:size="17"
:color="color"
></u-icon>
<u-icon
v-if="mode === 'closable'"
@click="close"
name="close"
:size="16"
:color="color"
></u-icon>
</view>
</view>
</template>
<script>
import { props } from './props';
import { mpMixin } from '../../libs/mixin/mpMixin';
import { mixin } from '../../libs/mixin/mixin';
import { addUnit, error, sleep, getPx } from '../../libs/function/index';
import test from '../../libs/function/test';
// #ifdef APP-NVUE
const animation = uni.requireNativePlugin('animation')
const dom = uni.requireNativePlugin('dom')
// #endif
/**
* RowNotice 滚动通知中的水平滚动模式
* @description 水平滚动
* @tutorial https://ijry.github.io/uview-plus/components/noticeBar.html
* @property {String | Number} text 显示的内容,字符串
* @property {String} icon 是否显示左侧的音量图标 (默认 'volume' )
* @property {String} mode 通告模式link-显示右箭头closable-显示右侧关闭图标
* @property {String} color 文字颜色,各图标也会使用文字颜色 (默认 '#f9ae3d' )
* @property {String} bgColor 背景颜色 (默认 ''#fdf6ec' )
* @property {String | Number} fontSize 字体大小单位px (默认 14 )
* @property {String | Number} speed 水平滚动时的滚动速度即每秒滚动多少px(rpx),这有利于控制文字无论多少时,都能有一个恒定的速度 (默认 80 )
*
* @event {Function} click 点击通告文字触发
* @event {Function} close 点击右侧关闭图标触发
* @example
*/
export default {
name: 'u-row-notice',
mixins: [mpMixin, mixin, props],
data() {
return {
animationDuration: '0', // 动画执行时间
animationPlayState: 'paused', // 动画的开始和结束执行
// nvue下内容发生变化导致滚动宽度也变化需要标志为是否需要重新计算宽度
// 不能在内容变化时直接重新计算因为nvue的animation模块上一次的滚动不是刚好结束会有影响
nvueInit: true,
show: true
};
},
watch: {
text: {
immediate: true,
handler(newValue, oldValue) {
// #ifdef APP-NVUE
this.nvueInit = true
// #endif
// #ifndef APP-NVUE
this.vue()
// #endif
if(!test.string(newValue)) {
error('noticebar组件direction为row时要求text参数为字符串形式')
}
}
},
fontSize() {
// #ifdef APP-NVUE
this.nvueInit = true
// #endif
// #ifndef APP-NVUE
this.vue()
// #endif
},
speed() {
// #ifdef APP-NVUE
this.nvueInit = true
// #endif
// #ifndef APP-NVUE
this.vue()
// #endif
}
},
computed: {
// 文字内容的样式
textStyle() {
let style = {}
style.whiteSpace = 'nowrap !important'
style.color = this.color
style.fontSize = addUnit(this.fontSize)
return style
},
animationStyle() {
let style = {}
style.animationDuration = this.animationDuration
style.animationPlayState = this.animationPlayState
return style
},
// 内部对用户传入的数据进一步分割放到多个text标签循环否则如果用户传入的字符串很长100个字符以上
// 放在一个text标签中进行滚动在低端安卓机上动画可能会出现抖动现象需要分割到多个text中可解决此问题
innerText() {
let result = [],
// 每组text标签的字符长度
len = 20
const textArr = this.text.split('')
for (let i = 0; i < textArr.length; i += len) {
// 对拆分的后的text进行slice分割得到的为数组再进行join拼接为字符串
result.push(textArr.slice(i, i + len).join(''))
}
return result
}
},
mounted() {
// #ifdef APP-PLUS
// 在APP上(含nvue)监听当前webview是否处于隐藏状态(进入下一页时即为hide状态)
// 如果webivew隐藏了为了节省性能的损耗应停止动画的执行同时也是为了保持进入下一页返回后滚动位置保持不变
var pages = getCurrentPages()
var page = pages[pages.length - 1]
var currentWebview = page.$getAppWebview()
currentWebview.addEventListener('hide', () => {
this.webviewHide = true
})
currentWebview.addEventListener('show', () => {
this.webviewHide = false
})
// #endif
this.init()
},
emits: ["click", "close"],
methods: {
init() {
// #ifdef APP-NVUE
this.nvue()
// #endif
// #ifndef APP-NVUE
this.vue()
// #endif
if(!test.string(this.text)) {
error('noticebar组件direction为row时要求text参数为字符串形式')
}
},
// vue版处理
async vue() {
// #ifndef APP-NVUE
let boxWidth = 0,
textWidth = 0
// 进行一定的延时
await sleep()
// 查询盒子和文字的宽度
textWidth = (await this.$uGetRect('.u-notice__content__text')).width
boxWidth = (await this.$uGetRect('.u-notice__content')).width
// 根据t=s/v(时间=路程/速度),这里为何不需要加上#u-notice-box的宽度因为中设置了.u-notice-content样式中设置了padding-left: 100%
// 恰巧计算出来的结果中已经包含了#u-notice-box的宽度
this.animationDuration = `${textWidth / getPx(this.speed)}s`
// 这里必须这样开始动画否则在APP上动画速度不会改变
this.animationPlayState = 'paused'
setTimeout(() => {
this.animationPlayState = 'running'
}, 10)
// #endif
},
// nvue版处理
async nvue() {
// #ifdef APP-NVUE
this.nvueInit = false
let boxWidth = 0,
textWidth = 0
// 进行一定的延时
await sleep()
// 查询盒子和文字的宽度
textWidth = (await this.getNvueRect('u-notice__content__text')).width
boxWidth = (await this.getNvueRect('u-notice__content')).width
// 将文字移动到盒子的右边沿之所以需要这么做是因为nvue不支持100%单位否则可以通过css设置
animation.transition(this.$refs['u-notice__content__text'], {
styles: {
transform: `translateX(${boxWidth}px)`
},
}, () => {
// 如果非禁止动画,则开始滚动
!this.stopAnimation && this.loopAnimation(textWidth, boxWidth)
});
// #endif
},
loopAnimation(textWidth, boxWidth) {
// #ifdef APP-NVUE
animation.transition(this.$refs['u-notice__content__text'], {
styles: {
// 目标移动终点为-textWidth也即当文字的最右边贴到盒子的左边框的位置
transform: `translateX(-${textWidth}px)`
},
// 滚动时间的计算为,时间 = 路程(boxWidth + textWidth) / 速度,最后转为毫秒
duration: (boxWidth + textWidth) / getPx(this.speed) * 1000,
delay: 10
}, () => {
animation.transition(this.$refs['u-notice__content__text'], {
styles: {
// 重新将文字移动到盒子的右边沿
transform: `translateX(${this.stopAnimation ? 0 : boxWidth}px)`
},
}, () => {
// 如果非禁止动画,则继续下一轮滚动
if (!this.stopAnimation) {
// 判断是否需要初始化计算尺寸
if (this.nvueInit) {
this.nvue()
} else {
this.loopAnimation(textWidth, boxWidth)
}
}
});
})
// #endif
},
getNvueRect(el) {
// #ifdef APP-NVUE
// 返回一个promise
return new Promise(resolve => {
dom.getComponentRect(this.$refs[el], (res) => {
resolve(res.size)
})
})
// #endif
},
// 点击通告栏
clickHandler(index) {
this.$emit('click')
},
// 点击右侧按钮,需要判断点击的是关闭图标还是箭头图标
close() {
this.$emit('close')
}
},
beforeUnmount() {
this.stopAnimation = true
}
};
</script>
<style lang="scss" scoped>
@import "../../libs/css/components.scss";
.u-notice {
@include flex;
align-items: center;
justify-content: space-between;
&__left-icon {
align-items: center;
margin-right: 5px;
}
&__right-icon {
margin-left: 5px;
align-items: center;
}
&__content {
text-align: right;
flex: 1;
@include flex;
flex-wrap: nowrap;
overflow: hidden;
&__text {
font-size: 14px;
color: $u-warning;
/* #ifndef APP-NVUE */
// 这一句很重要,为了能让滚动左右连接起来
padding-left: 100%;
word-break: keep-all;
white-space: nowrap;
animation: u-loop-animation 10s linear infinite both;
/* #endif */
@include flex(row);
line-height: 100%;
}
}
}
/* #ifndef APP-NVUE */
@keyframes u-loop-animation {
0% {
transform: translate3d(0, 0, 0);
}
100% {
transform: translate3d(-100%, 0, 0);
}
}
/* #endif */
</style>