zhangmeng
2024-04-19 e3ba120cb766a17e098e58d11c39ffc600a3070c
commit | author | age
e3ba12 1 <template>
Z 2     <view
3         class="u-notice"
4         @tap="clickHandler"
5     >
6         <slot name="icon">
7             <view
8                 class="u-notice__left-icon"
9                 v-if="icon"
10             >
11                 <u-icon
12                     :name="icon"
13                     :color="color"
14                     size="19"
15                 ></u-icon>
16             </view>
17         </slot>
18         <view
19             class="u-notice__content"
20             ref="u-notice__content"
21         >
22             <text
23                 ref="u-notice__content__text"
24                 class="u-notice__content__text"
25                 :style="[textStyle]"
26             >{{text}}</text>
27         </view>
28         <view
29             class="u-notice__right-icon"
30             v-if="['link', 'closable'].includes(mode)"
31         >
32             <u-icon
33                 v-if="mode === 'link'"
34                 name="arrow-right"
35                 :size="17"
36                 :color="color"
37             ></u-icon>
38             <u-icon
39                 v-if="mode === 'closable'"
40                 @click="close"
41                 name="close"
42                 :size="16"
43                 :color="color"
44             ></u-icon>
45         </view>
46     </view>
47 </template>
48 <script>
49     import props from './props.js';
50     // #ifdef APP-NVUE
51     const animation = uni.requireNativePlugin('animation')
52     const dom = uni.requireNativePlugin('dom')
53     // #endif
54     /**
55      * RowNotice 滚动通知中的水平滚动模式
56      * @description 水平滚动
57      * @tutorial https://www.uviewui.com/components/noticeBar.html
58      * @property {String | Number}    text            显示的内容,字符串
59      * @property {String}            icon            是否显示左侧的音量图标 (默认 'volume' )
60      * @property {String}            mode            通告模式,link-显示右箭头,closable-显示右侧关闭图标
61      * @property {String}            color            文字颜色,各图标也会使用文字颜色 (默认 '#f9ae3d' )
62      * @property {String}            bgColor            背景颜色 (默认 ''#fdf6ec' )
63      * @property {String | Number}    fontSize        字体大小,单位px (默认 14 )
64      * @property {String | Number}    speed            水平滚动时的滚动速度,即每秒滚动多少px(rpx),这有利于控制文字无论多少时,都能有一个恒定的速度  (默认 80 )
65      * 
66      * @event {Function} click 点击通告文字触发
67      * @event {Function} close 点击右侧关闭图标触发
68      * @example 
69      */
70     export default {
71         name: 'u-row-notice',
72         mixins: [uni.$u.mpMixin, uni.$u.mixin,props],
73         data() {
74             return {
75                 animationDuration: '0', // 动画执行时间
76                 animationPlayState: 'paused', // 动画的开始和结束执行
77                 // nvue下,内容发生变化,导致滚动宽度也变化,需要标志为是否需要重新计算宽度
78                 // 不能在内容变化时直接重新计算,因为nvue的animation模块上一次的滚动不是刚好结束,会有影响
79                 nvueInit: true,
80                 show: true
81             };
82         },
83         watch: {
84             text: {
85                 immediate: true,
86                 handler(newValue, oldValue) {
87                     // #ifdef APP-NVUE
88                     this.nvueInit = true
89                     // #endif
90                     // #ifndef APP-NVUE
91                     this.vue()
92                     // #endif
93                     
94                     if(!uni.$u.test.string(newValue)) {
95                         uni.$u.error('noticebar组件direction为row时,要求text参数为字符串形式')
96                     }
97                 }
98             },
99             fontSize() {
100                 // #ifdef APP-NVUE
101                 this.nvueInit = true
102                 // #endif
103                 // #ifndef APP-NVUE
104                 this.vue()
105                 // #endif
106             },
107             speed() {
108                 // #ifdef APP-NVUE
109                 this.nvueInit = true
110                 // #endif
111                 // #ifndef APP-NVUE
112                 this.vue()
113                 // #endif
114             }
115         },
116         computed: {
117             // 文字内容的样式
118             textStyle() {
119                 let style = {}
120                 style.color = this.color
121                 style.animationDuration = this.animationDuration
122                 style.animationPlayState = this.animationPlayState
123                 style.fontSize = uni.$u.addUnit(this.fontSize)
124                 return style
125             },
126         },
127         mounted() {
128             // #ifdef APP-PLUS
129             // 在APP上(含nvue),监听当前webview是否处于隐藏状态(进入下一页时即为hide状态)
130             // 如果webivew隐藏了,为了节省性能的损耗,应停止动画的执行,同时也是为了保持进入下一页返回后,滚动位置保持不变
131             var pages = getCurrentPages()
132             var page = pages[pages.length - 1]
133             var currentWebview = page.$getAppWebview()
134             currentWebview.addEventListener('hide', () => {
135                 this.webviewHide = true
136             })
137             currentWebview.addEventListener('show', () => {
138                 this.webviewHide = false
139             })
140             // #endif
141
142             this.init()
143         },
144         methods: {
145             init() {
146                 // #ifdef APP-NVUE
147                 this.nvue()
148                 // #endif
149
150                 // #ifndef APP-NVUE
151                 this.vue()
152                 // #endif
153                 
154                 if(!uni.$u.test.string(this.text)) {
155                     uni.$u.error('noticebar组件direction为row时,要求text参数为字符串形式')
156                 }
157             },
158             // vue版处理
159             async vue() {
160                 // #ifndef APP-NVUE
161                 let boxWidth = 0,
162                     textWidth = 0
163                 // 进行一定的延时
164                 await uni.$u.sleep()
165                 // 查询盒子和文字的宽度
166                 textWidth = (await this.$uGetRect('.u-notice__content__text')).width
167                 boxWidth = (await this.$uGetRect('.u-notice__content')).width
168                 // 根据t=s/v(时间=路程/速度),这里为何不需要加上#u-notice-box的宽度,因为中设置了.u-notice-content样式中设置了padding-left: 100%
169                 // 恰巧计算出来的结果中已经包含了#u-notice-box的宽度
170                 this.animationDuration = `${textWidth / uni.$u.getPx(this.speed)}s`
171                 // 这里必须这样开始动画,否则在APP上动画速度不会改变
172                 this.animationPlayState = 'paused'
173                 setTimeout(() => {
174                     this.animationPlayState = 'running'
175                 }, 10)
176                 // #endif
177             },
178             // nvue版处理
179             async nvue() {
180                 // #ifdef APP-NVUE
181                 this.nvueInit = false
182                 let boxWidth = 0,
183                     textWidth = 0
184                 // 进行一定的延时
185                 await uni.$u.sleep()
186                 // 查询盒子和文字的宽度
187                 textWidth = (await this.getNvueRect('u-notice__content__text')).width
188                 boxWidth = (await this.getNvueRect('u-notice__content')).width
189                 // 将文字移动到盒子的右边沿,之所以需要这么做,是因为nvue不支持100%单位,否则可以通过css设置
190                 animation.transition(this.$refs['u-notice__content__text'], {
191                     styles: {
192                         transform: `translateX(${boxWidth}px)`
193                     },
194                 }, () => {
195                     // 如果非禁止动画,则开始滚动
196                     !this.stopAnimation && this.loopAnimation(textWidth, boxWidth)
197                 });
198                 // #endif
199             },
200             loopAnimation(textWidth, boxWidth) {
201                 // #ifdef APP-NVUE
202                 animation.transition(this.$refs['u-notice__content__text'], {
203                     styles: {
204                         // 目标移动终点为-textWidth,也即当文字的最右边贴到盒子的左边框的位置
205                         transform: `translateX(-${textWidth}px)`
206                     },
207                     // 滚动时间的计算为,时间 = 路程(boxWidth + textWidth) / 速度,最后转为毫秒
208                     duration: (boxWidth + textWidth) / uni.$u.getPx(this.speed) * 1000,
209                     delay: 10
210                 }, () => {
211                     animation.transition(this.$refs['u-notice__content__text'], {
212                         styles: {
213                             // 重新将文字移动到盒子的右边沿
214                             transform: `translateX(${this.stopAnimation ? 0 : boxWidth}px)`
215                         },
216                     }, () => {
217                         // 如果非禁止动画,则继续下一轮滚动
218                         if (!this.stopAnimation) {
219                             // 判断是否需要初始化计算尺寸
220                             if (this.nvueInit) {
221                                 this.nvue()
222                             } else {
223                                 this.loopAnimation(textWidth, boxWidth)
224                             }
225                         }
226                     });
227                 })
228                 // #endif
229             },
230             getNvueRect(el) {
231                 // #ifdef APP-NVUE
232                 // 返回一个promise
233                 return new Promise(resolve => {
234                     dom.getComponentRect(this.$refs[el], (res) => {
235                         resolve(res.size)
236                     })
237                 })
238                 // #endif
239             },
240             // 点击通告栏
241             clickHandler(index) {
242                 this.$emit('click')
243             },
244             // 点击右侧按钮,需要判断点击的是关闭图标还是箭头图标
245             close() {
246                 this.$emit('close')
247             }
248         },
249         // #ifdef APP-NVUE
250         beforeDestroy() {
251             this.stopAnimation = true
252         },
253         // #endif
254     };
255 </script>
256
257 <style lang="scss" scoped>
258     @import "../../libs/css/components.scss";
259
260     .u-notice {
261         @include flex;
262         align-items: center;
263         justify-content: space-between;
264
265         &__left-icon {
266             align-items: center;
267             margin-right: 5px;
268         }
269
270         &__right-icon {
271             margin-left: 5px;
272             align-items: center;
273         }
274
275         &__content {
276             text-align: right;
277             flex: 1;
278             @include flex;
279             flex-wrap: nowrap;
280             overflow: hidden;
281
282             &__text {
283                 font-size: 14px;
284                 color: $u-warning;
285                 /* #ifndef APP-NVUE */
286                 // 这一句很重要,为了能让滚动左右连接起来
287                 padding-left: 100%;
288                 word-break: keep-all;
289                 white-space: nowrap;
290                 animation: u-loop-animation 10s linear infinite both;
291                 /* #endif */
292             }
293         }
294
295     }
296
297     @keyframes u-loop-animation {
298         0% {
299             transform: translate3d(0, 0, 0);
300         }
301
302         100% {
303             transform: translate3d(-100%, 0, 0);
304         }
305     }
306 </style>