index.vue 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297
  1. <template>
  2. <div class="marquee-box">
  3. <div class="scroll-area">
  4. <!-- 设置margin,使内容 有从无到有的出现效果 -->
  5. <div class="marquee-container">
  6. <div class="icon">
  7. <i
  8. v-if="config.customize.icon.position === 'left'"
  9. :class="config.customize.icon.name"
  10. :style="{ color: config.customize.icon.color, fontSize: config.customize.fontSize + 'px' }"
  11. />
  12. </div>
  13. <svg class="svg-container">
  14. <defs>
  15. <linearGradient
  16. :id="'backgroundGradient-'+config.code"
  17. :x1="0"
  18. :y1="['to top right'].includes(config.customize.bgGradientDirection) ? '100%' : '0'"
  19. :x2="['to right','to bottom right','to top right'].includes(config.customize.bgGradientDirection) ? '100%' : '0'"
  20. :y2="['to bottom','to bottom right'].includes(config.customize.bgGradientDirection) ? '100%' : '0'"
  21. >
  22. <stop
  23. offset="0%"
  24. :stop-color="config.customize.backgroundColorType === 'pure' ? config.customize.backgroundColor : config.customize.bgGradientColor0"
  25. />
  26. <stop
  27. offset="100%"
  28. :stop-color="config.customize.backgroundColorType === 'pure' ? config.customize.backgroundColor : config.customize.bgGradientColor1"
  29. />
  30. </linearGradient>
  31. <linearGradient
  32. :id="'textGradient-'+config.code"
  33. :x1="0"
  34. :y1="['to top right'].includes(config.customize.textGradientDirection) ? '100%' : '0'"
  35. :x2="['to right','to bottom right','to top right'].includes(config.customize.textGradientDirection) ? '100%' : '0'"
  36. :y2="['to bottom','to bottom right'].includes(config.customize.textGradientDirection) ? '100%' : '0'"
  37. >
  38. <stop
  39. offset="0%"
  40. :stop-color="config.customize.textColorType === 'pure' ? config.customize.textColor : config.customize.textGradientColor0"
  41. />
  42. <stop
  43. offset="100%"
  44. :stop-color="config.customize.textColorType === 'pure' ? config.customize.textColor : config.customize.textGradientColor1"
  45. />
  46. </linearGradient>
  47. </defs>
  48. <rect
  49. v-if="config.customize.backgroundColorType !== 'transparent'"
  50. width="100%"
  51. height="100%"
  52. :fill="`url(#backgroundGradient-${config.code})`"
  53. />
  54. <text
  55. :x="10"
  56. :y="config.customize.fontSize"
  57. :style="{ fontSize: config.customize.fontSize + 'px', fontWeight: config.customize.fontWeight }"
  58. :fill="`url(#textGradient-${config.code})`"
  59. >
  60. <animate
  61. v-if="isAnimate"
  62. :attributeName="attributeName[config.customize.direction]"
  63. :from="from[config.customize.direction]"
  64. :to="to[config.customize.direction]"
  65. :dur="config.customize.dur + 's'"
  66. repeatCount="indefinite"
  67. />
  68. {{ config.customize.title }}
  69. </text>
  70. </svg>
  71. <div class="icon">
  72. <i
  73. v-if="config.customize.icon.position === 'right'"
  74. :class="config.customize.icon.name"
  75. :style="{ color: config.customize.icon.color, fontSize: config.customize.fontSize + 'px' }"
  76. />
  77. </div>
  78. </div>
  79. </div>
  80. </div>
  81. </template>
  82. <script>
  83. import Speech from 'speak-tts'
  84. import { EventBus } from 'data-room-ui/js/utils/eventBus'
  85. import commonMixins from 'data-room-ui/js/mixins/commonMixins'
  86. import paramsMixins from 'data-room-ui/js/mixins/paramsMixins'
  87. import { settingToTheme } from 'data-room-ui/js/utils/themeFormatting'
  88. import cloneDeep from 'lodash/cloneDeep'
  89. export default {
  90. props: {
  91. // 卡片的属性
  92. config: {
  93. type: Object,
  94. default: () => ({})
  95. }
  96. },
  97. data () {
  98. return {
  99. customClass: {},
  100. attributeName: {
  101. right: 'x',
  102. left: 'x',
  103. top: 'y',
  104. bottom: 'y'
  105. },
  106. // 动画开始
  107. from: {
  108. left: '-100%',
  109. right: '100%',
  110. top: '-100%',
  111. bottom: '100%'
  112. },
  113. // 动画结束
  114. to: {
  115. left: '100%',
  116. right: '-100%',
  117. top: '100%',
  118. bottom: '-100%'
  119. },
  120. isAnimate: true,
  121. // 组件内部数据
  122. innerData: null,
  123. // 音频播放
  124. aduio: null,
  125. // 语音播报
  126. speech: null,
  127. // 语音播报定时器
  128. speechTimer: null
  129. }
  130. },
  131. computed: {
  132. },
  133. mixins: [paramsMixins, commonMixins],
  134. mounted () {
  135. this.chartInit()
  136. // 如果点击了生成图片,则先关闭动画
  137. EventBus.$on('stopMarquee', () => {
  138. this.isAnimate = false
  139. })
  140. // 图片生成完成后,再开启动画
  141. EventBus.$on('startMarquee', () => {
  142. this.isAnimate = true
  143. })
  144. document.addEventListener('visibilitychange', this.handleVisibilityChange)
  145. },
  146. beforeDestroy () {
  147. EventBus.$off('stopMarquee')
  148. EventBus.$off('startMarquee')
  149. // 销毁语音播报定时器
  150. if (this.speechTimer) {
  151. clearInterval(this.speechTimer)
  152. }
  153. },
  154. methods: {
  155. dataFormatting (config, data) {
  156. // 数据返回成功则赋值
  157. if (data.success) {
  158. data = data.data
  159. // 获取到后端返回的数据,有则赋值
  160. if (config.dataHandler) {
  161. try {
  162. // 此处函数处理data
  163. eval(config.dataHandler)
  164. } catch (e) {
  165. console.error(e)
  166. }
  167. }
  168. config.option.data = data
  169. config.customize.title = config.option.data[config.dataSource.dimensionField] || config.customize.title
  170. this.innerData = config
  171. // 语音播报
  172. } else {
  173. // 数据返回失败则赋前端的模拟数据
  174. config.option.data = []
  175. }
  176. return config
  177. },
  178. // 语音播报
  179. voiceBroadcast (config) {
  180. if (this.innerData) {
  181. if (config.customize.voiceBroadcast) {
  182. if (this.innerData.dataSource.businessKey && this.innerData.option.data[this.innerData.dataSource.metricField]) {
  183. // 如果aduio存在,先销毁这个实例,或者替换它的URL
  184. if (this.aduio) {
  185. this.aduio.pause()
  186. this.aduio = null
  187. }
  188. this.aduio = new Audio()
  189. this.aduio.src = this.innerData.option.data[this.innerData.dataSource.metricField]
  190. this.aduio.play()
  191. } else if (config.customize.title) {
  192. this.speechBroadcast(config.customize.title)
  193. // 根据配置的时间,定时播报,第一次播报后,再定时播报
  194. this.speechBroadcast(config.customize.title)
  195. if (config.customize.dur) {
  196. this.speechTimer = setInterval(() => {
  197. this.speechBroadcast(config.customize.title)
  198. }, config.customize.dur * 1000)
  199. }
  200. }
  201. } else {
  202. if (this.aduio) {
  203. this.aduio.pause()
  204. this.aduio = null
  205. }
  206. }
  207. } else {
  208. if (config.customize.voiceBroadcast) {
  209. this.speech = new Speech()
  210. if (config.customize.dur) {
  211. this.speechBroadcast(config.customize.title)
  212. this.speechTimer = setInterval(() => {
  213. this.speechBroadcast(config.customize.title)
  214. }, config.customize.dur * 1000)
  215. }
  216. }
  217. }
  218. },
  219. // 语音播报
  220. speechBroadcast (text) {
  221. if (this.speech.hasBrowserSupport()) {
  222. this.speech.setLanguage('zh-CN')
  223. this.speech.pitch = 1
  224. this.speech.init()
  225. this.speech.speak({ text: text })
  226. } else {
  227. this.$message({
  228. message: '您的浏览器不支持语音播报',
  229. type: 'warning'
  230. })
  231. }
  232. },
  233. changeStyle (config) {
  234. config = { ...this.config, ...config }
  235. this.voiceBroadcast(config)
  236. // 样式改变时更新主题配置
  237. config.theme = settingToTheme(cloneDeep(config), this.customTheme)
  238. this.changeChartConfig(config)
  239. if (config.code === this.activeCode) {
  240. this.changeActiveItemConfig(config)
  241. }
  242. },
  243. // 监听页面是否可见
  244. handleVisibilityChange () {
  245. if (document.visibilityState === 'hidden') {
  246. if (this.aduio) {
  247. this.aduio.pause()
  248. }
  249. if (this.speech) {
  250. this.speech.pause()
  251. }
  252. } else {
  253. if (this.aduio) {
  254. this.aduio.play()
  255. }
  256. if (this.speech) {
  257. this.speech.resume()
  258. }
  259. }
  260. }
  261. }
  262. }
  263. </script>
  264. <style lang="scss" scoped>
  265. .marquee-box {
  266. width: 100%;
  267. height: 100%;
  268. white-space: nowrap;
  269. overflow: hidden;
  270. .scroll-area {
  271. width: 100%;
  272. height: 100%;
  273. .marquee-container {
  274. width: 100%;
  275. height: 100%;
  276. display: flex;
  277. .svg-container {
  278. width: 100%;
  279. height: 100%;
  280. }
  281. }
  282. }
  283. .icon {
  284. position: relative;
  285. top: 0;
  286. // 清除浮动
  287. }
  288. }
  289. </style>