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.

423 lines
10 KiB

3 weeks ago
  1. <template>
  2. <view v-if="show" class="uni-noticebar" :style="{ backgroundColor }" @click="onClick">
  3. <slot v-if="showIcon === true || showIcon === 'true'" name="noticebarIcon">
  4. <uni-icons class="uni-noticebar-icon" type="sound" :color="color" :size="fontSize * 1.5" />
  5. </slot>
  6. <view ref="textBox" class="uni-noticebar__content-wrapper" :class="{
  7. 'uni-noticebar__content-wrapper--scrollable': scrollable,
  8. 'uni-noticebar__content-wrapper--single': !scrollable && (single || moreText)
  9. }" :style="{ height: scrollable ? fontSize * 1.5 + 'px' : 'auto' }">
  10. <view :id="elIdBox" class="uni-noticebar__content" :class="{
  11. 'uni-noticebar__content--scrollable': scrollable,
  12. 'uni-noticebar__content--single': !scrollable && (single || moreText)
  13. }">
  14. <text :id="elId" ref="animationEle" class="uni-noticebar__content-text" :class="{
  15. 'uni-noticebar__content-text--scrollable': scrollable,
  16. 'uni-noticebar__content-text--single': !scrollable && (single || showGetMore)
  17. }" :style="{
  18. color: color,
  19. fontSize: fontSize + 'px',
  20. lineHeight: fontSize * 1.5 + 'px',
  21. width: wrapWidth + 'px',
  22. 'animationDuration': animationDuration,
  23. '-webkit-animationDuration': animationDuration,
  24. animationPlayState: webviewHide ? 'paused' : animationPlayState,
  25. '-webkit-animationPlayState': webviewHide ? 'paused' : animationPlayState,
  26. animationDelay: animationDelay,
  27. '-webkit-animationDelay': animationDelay
  28. }">{{text}}</text>
  29. </view>
  30. </view>
  31. <view v-if="isShowGetMore" class="uni-noticebar__more uni-cursor-point" @click="clickMore">
  32. <text v-if="moreText.length > 0" :style="{ color: moreColor, fontSize: fontSize + 'px' }">{{ moreText }}</text>
  33. <uni-icons v-else type="right" :color="moreColor" :size="fontSize * 1.1" />
  34. </view>
  35. <view class="uni-noticebar-close uni-cursor-point" v-if="isShowClose">
  36. <uni-icons type="closeempty" :color="color" :size="fontSize * 1.1" @click="close" />
  37. </view>
  38. </view>
  39. </template>
  40. <script>
  41. // #ifdef APP-NVUE
  42. const dom = weex.requireModule('dom');
  43. const animation = weex.requireModule('animation');
  44. // #endif
  45. /**
  46. * NoticeBar 自定义导航栏
  47. * @description 通告栏组件
  48. * @tutorial https://ext.dcloud.net.cn/plugin?id=30
  49. * @property {Number} speed 文字滚动的速度默认100px/
  50. * @property {String} text 显示文字
  51. * @property {String} backgroundColor 背景颜色
  52. * @property {String} color 文字颜色
  53. * @property {String} moreColor 查看更多文字的颜色
  54. * @property {String} moreText 设置查看更多的文本
  55. * @property {Boolean} single = [true|false] 是否单行
  56. * @property {Boolean} scrollable = [true|false] 是否滚动为true时NoticeBar为单行
  57. * @property {Boolean} showIcon = [true|false] 是否显示左侧喇叭图标
  58. * @property {Boolean} showClose = [true|false] 是否显示左侧关闭按钮
  59. * @property {Boolean} showGetMore = [true|false] 是否显示右侧查看更多图标为true时NoticeBar为单行
  60. * @event {Function} click 点击 NoticeBar 触发事件
  61. * @event {Function} close 关闭 NoticeBar 触发事件
  62. * @event {Function} getmore 点击查看更多时触发事件
  63. */
  64. export default {
  65. name: 'UniNoticeBar',
  66. emits: ['click', 'getmore', 'close'],
  67. props: {
  68. text: {
  69. type: String,
  70. default: ''
  71. },
  72. moreText: {
  73. type: String,
  74. default: ''
  75. },
  76. backgroundColor: {
  77. type: String,
  78. default: '#FFF9EA'
  79. },
  80. speed: {
  81. // 默认1s滚动100px
  82. type: Number,
  83. default: 100
  84. },
  85. color: {
  86. type: String,
  87. default: '#FF9A43'
  88. },
  89. fontSize: {
  90. type: Number,
  91. default: 14
  92. },
  93. moreColor: {
  94. type: String,
  95. default: '#FF9A43'
  96. },
  97. single: {
  98. // 是否单行
  99. type: [Boolean, String],
  100. default: false
  101. },
  102. scrollable: {
  103. // 是否滚动,添加后控制单行效果取消
  104. type: [Boolean, String],
  105. default: false
  106. },
  107. showIcon: {
  108. // 是否显示左侧icon
  109. type: [Boolean, String],
  110. default: false
  111. },
  112. showGetMore: {
  113. // 是否显示右侧查看更多
  114. type: [Boolean, String],
  115. default: false
  116. },
  117. showClose: {
  118. // 是否显示左侧关闭按钮
  119. type: [Boolean, String],
  120. default: false
  121. }
  122. },
  123. data() {
  124. const elId = `Uni_${Math.ceil(Math.random() * 10e5).toString(36)}`
  125. const elIdBox = `Uni_${Math.ceil(Math.random() * 10e5).toString(36)}`
  126. return {
  127. textWidth: 0,
  128. boxWidth: 0,
  129. wrapWidth: '',
  130. webviewHide: false,
  131. // #ifdef APP-NVUE
  132. stopAnimation: false,
  133. // #endif
  134. elId: elId,
  135. elIdBox: elIdBox,
  136. show: true,
  137. animationDuration: 'none',
  138. animationPlayState: 'paused',
  139. animationDelay: '0s'
  140. }
  141. },
  142. watch: {
  143. text(newValue, oldValue) {
  144. this.initSize();
  145. }
  146. },
  147. computed: {
  148. isShowGetMore() {
  149. return this.showGetMore === true || this.showGetMore === 'true'
  150. },
  151. isShowClose() {
  152. return (this.showClose === true || this.showClose === 'true') &&
  153. (this.showGetMore === false || this.showGetMore === 'false')
  154. }
  155. },
  156. mounted() {
  157. // #ifdef APP-PLUS
  158. var pages = getCurrentPages();
  159. var page = pages[pages.length - 1];
  160. var currentWebview = page.$getAppWebview();
  161. currentWebview.addEventListener('hide', () => {
  162. this.webviewHide = true
  163. })
  164. currentWebview.addEventListener('show', () => {
  165. this.webviewHide = false
  166. })
  167. // #endif
  168. this.$nextTick(() => {
  169. this.initSize()
  170. })
  171. },
  172. // #ifdef APP-NVUE
  173. beforeDestroy() {
  174. this.stopAnimation = true
  175. },
  176. // #endif
  177. methods: {
  178. initSize() {
  179. if (this.scrollable) {
  180. // #ifndef APP-NVUE
  181. let query = [],
  182. boxWidth = 0,
  183. textWidth = 0;
  184. let textQuery = new Promise((resolve, reject) => {
  185. uni.createSelectorQuery()
  186. // #ifndef MP-ALIPAY
  187. .in(this)
  188. // #endif
  189. .select(`#${this.elId}`)
  190. .boundingClientRect()
  191. .exec(ret => {
  192. this.textWidth = ret[0].width
  193. resolve()
  194. })
  195. })
  196. let boxQuery = new Promise((resolve, reject) => {
  197. uni.createSelectorQuery()
  198. // #ifndef MP-ALIPAY
  199. .in(this)
  200. // #endif
  201. .select(`#${this.elIdBox}`)
  202. .boundingClientRect()
  203. .exec(ret => {
  204. this.boxWidth = ret[0].width
  205. resolve()
  206. })
  207. })
  208. query.push(textQuery)
  209. query.push(boxQuery)
  210. Promise.all(query).then(() => {
  211. this.animationDuration = `${this.textWidth / this.speed}s`
  212. this.animationDelay = `-${this.boxWidth / this.speed}s`
  213. setTimeout(() => {
  214. this.animationPlayState = 'running'
  215. }, 1000)
  216. })
  217. // #endif
  218. // #ifdef APP-NVUE
  219. dom.getComponentRect(this.$refs['animationEle'], (res) => {
  220. let winWidth = uni.getSystemInfoSync().windowWidth
  221. this.textWidth = res.size.width
  222. animation.transition(this.$refs['animationEle'], {
  223. styles: {
  224. transform: `translateX(-${winWidth}px)`
  225. },
  226. duration: 0,
  227. timingFunction: 'linear',
  228. delay: 0
  229. }, () => {
  230. if (!this.stopAnimation) {
  231. animation.transition(this.$refs['animationEle'], {
  232. styles: {
  233. transform: `translateX(-${this.textWidth}px)`
  234. },
  235. timingFunction: 'linear',
  236. duration: (this.textWidth - winWidth) / this.speed * 1000,
  237. delay: 1000
  238. }, () => {
  239. if (!this.stopAnimation) {
  240. this.loopAnimation()
  241. }
  242. });
  243. }
  244. });
  245. })
  246. // #endif
  247. }
  248. // #ifdef APP-NVUE
  249. if (!this.scrollable && (this.single || this.moreText)) {
  250. dom.getComponentRect(this.$refs['textBox'], (res) => {
  251. this.wrapWidth = res.size.width
  252. })
  253. }
  254. // #endif
  255. },
  256. loopAnimation() {
  257. // #ifdef APP-NVUE
  258. animation.transition(this.$refs['animationEle'], {
  259. styles: {
  260. transform: `translateX(0px)`
  261. },
  262. duration: 0
  263. }, () => {
  264. if (!this.stopAnimation) {
  265. animation.transition(this.$refs['animationEle'], {
  266. styles: {
  267. transform: `translateX(-${this.textWidth}px)`
  268. },
  269. duration: this.textWidth / this.speed * 1000,
  270. timingFunction: 'linear',
  271. delay: 0
  272. }, () => {
  273. if (!this.stopAnimation) {
  274. this.loopAnimation()
  275. }
  276. });
  277. }
  278. });
  279. // #endif
  280. },
  281. clickMore() {
  282. this.$emit('getmore')
  283. },
  284. close() {
  285. this.show = false;
  286. this.$emit('close')
  287. },
  288. onClick() {
  289. this.$emit('click')
  290. }
  291. }
  292. }
  293. </script>
  294. <style lang="scss" scoped>
  295. .uni-noticebar {
  296. /* #ifndef APP-NVUE */
  297. display: flex;
  298. width: 100%;
  299. box-sizing: border-box;
  300. /* #endif */
  301. flex-direction: row;
  302. align-items: center;
  303. padding: 10px 12px;
  304. margin-bottom: 10px;
  305. }
  306. .uni-cursor-point {
  307. /* #ifdef H5 */
  308. cursor: pointer;
  309. /* #endif */
  310. }
  311. .uni-noticebar-close {
  312. margin-left: 8px;
  313. margin-right: 5px;
  314. }
  315. .uni-noticebar-icon {
  316. margin-right: 5px;
  317. }
  318. .uni-noticebar__content-wrapper {
  319. flex: 1;
  320. flex-direction: column;
  321. overflow: hidden;
  322. }
  323. .uni-noticebar__content-wrapper--single {
  324. /* #ifndef APP-NVUE */
  325. line-height: 18px;
  326. /* #endif */
  327. }
  328. .uni-noticebar__content-wrapper--single,
  329. .uni-noticebar__content-wrapper--scrollable {
  330. flex-direction: row;
  331. }
  332. /* #ifndef APP-NVUE */
  333. .uni-noticebar__content-wrapper--scrollable {
  334. position: relative;
  335. }
  336. /* #endif */
  337. .uni-noticebar__content--scrollable {
  338. /* #ifdef APP-NVUE */
  339. flex: 0;
  340. /* #endif */
  341. /* #ifndef APP-NVUE */
  342. flex: 1;
  343. display: block;
  344. overflow: hidden;
  345. /* #endif */
  346. }
  347. .uni-noticebar__content--single {
  348. /* #ifndef APP-NVUE */
  349. display: flex;
  350. flex: none;
  351. width: 100%;
  352. justify-content: center;
  353. /* #endif */
  354. }
  355. .uni-noticebar__content-text {
  356. font-size: 14px;
  357. line-height: 18px;
  358. /* #ifndef APP-NVUE */
  359. word-break: break-all;
  360. /* #endif */
  361. }
  362. .uni-noticebar__content-text--single {
  363. /* #ifdef APP-NVUE */
  364. lines: 1;
  365. /* #endif */
  366. /* #ifndef APP-NVUE */
  367. display: block;
  368. width: 100%;
  369. white-space: nowrap;
  370. /* #endif */
  371. overflow: hidden;
  372. text-overflow: ellipsis;
  373. }
  374. .uni-noticebar__content-text--scrollable {
  375. /* #ifdef APP-NVUE */
  376. lines: 1;
  377. padding-left: 750rpx;
  378. /* #endif */
  379. /* #ifndef APP-NVUE */
  380. position: absolute;
  381. display: block;
  382. height: 18px;
  383. line-height: 18px;
  384. white-space: nowrap;
  385. padding-left: 100%;
  386. animation: notice 10s 0s linear infinite both;
  387. animation-play-state: paused;
  388. /* #endif */
  389. }
  390. .uni-noticebar__more {
  391. /* #ifndef APP-NVUE */
  392. display: inline-flex;
  393. /* #endif */
  394. flex-direction: row;
  395. flex-wrap: nowrap;
  396. align-items: center;
  397. padding-left: 5px;
  398. }
  399. @keyframes notice {
  400. 100% {
  401. transform: translate3d(-100%, 0, 0);
  402. }
  403. }
  404. </style>