123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432 |
- <template>
- <!-- 添加一个类似鼠标hover事件 -->
- <div class="marquee-box">
- <div class="scroll-area">
- <audio
- :ref="`audioPlayer${config.code}`"
- muted
- autoplay
- crossorigin="anonymous"
- />
- <!-- 设置margin,使内容 有从无到有的出现效果 -->
- <div
- class="marquee-container"
- @mouseenter.stop="mouseenter"
- @mouseleave.stop="mouseleave"
- >
- <div class="icon">
- <icon-svg
- v-if="config.customize.icon.name && config.customize.icon.position === 'left'"
- :name="config.customize.icon.name"
- :style="{ color: config.customize.icon.color, width: config.customize.fontSize + 'px',height: config.customize.fontSize + 'px' }"
- />
- </div>
- <svg class="svg-container">
- <defs>
- <linearGradient
- :id="'backgroundGradient-' + config.code"
- :x1="0"
- :y1="['to top right'].includes(config.customize.bgGradientDirection) ? '100%' : '0'"
- :x2="['to right', 'to bottom right', 'to top right'].includes(config.customize.bgGradientDirection) ? '100%' : '0'"
- :y2="['to bottom', 'to bottom right'].includes(config.customize.bgGradientDirection) ? '100%' : '0'"
- >
- <stop
- offset="0%"
- :stop-color="config.customize.backgroundColorType === 'pure' ? config.customize.backgroundColor : config.customize.bgGradientColor0"
- />
- <stop
- offset="100%"
- :stop-color="config.customize.backgroundColorType === 'pure' ? config.customize.backgroundColor : config.customize.bgGradientColor1"
- />
- </linearGradient>
- <linearGradient
- :id="'textGradient-' + config.code"
- :x1="0"
- :y1="['to top right'].includes(config.customize.textGradientDirection) ? '100%' : '0'"
- :x2="['to right', 'to bottom right', 'to top right'].includes(config.customize.textGradientDirection) ? '100%' : '0'"
- :y2="['to bottom', 'to bottom right'].includes(config.customize.textGradientDirection) ? '100%' : '0'"
- >
- <stop
- offset="0%"
- :stop-color="config.customize.textColorType === 'pure' ? config.customize.textColor : config.customize.textGradientColor0"
- />
- <stop
- offset="100%"
- :stop-color="config.customize.textColorType === 'pure' ? config.customize.textColor : config.customize.textGradientColor1"
- />
- </linearGradient>
- </defs>
- <rect
- v-if="config.customize.backgroundColorType !== 'transparent'"
- width="100%"
- height="100%"
- :fill="`url(#backgroundGradient-${config.code})`"
- />
- <text
- :x="10"
- :y="config.customize.fontSize"
- :style="{ fontSize: config.customize.fontSize + 'px', fontWeight: config.customize.fontWeight }"
- :fill="`url(#textGradient-${config.code})`"
- >
- <animate
- v-if="isAnimate"
- :attributeName="attributeName[config.customize.direction]"
- :from="from[config.customize.direction]"
- :to="to[config.customize.direction]"
- :dur="config.customize.dur + 's'"
- repeatCount="indefinite"
- />
- {{ config.customize.title }}
- </text>
- </svg>
- <div class="icon">
- <icon-svg
- v-if="config.customize.icon.name && config.customize.icon.position === 'right'"
- :name="config.customize.icon.name"
- :style="{ color: config.customize.icon.color, width: config.customize.fontSize + 'px',height: config.customize.fontSize + 'px' }"
- />
- </div>
- </div>
- </div>
- <div
- v-show="config.customize.voiceBroadcast && showVoiceSwitch"
- class="voice-switch"
- :style="{fontSize:config.customize.fontSize + 'px',right:config.customize.fontSize + 5 + 'px',}"
- @mouseenter.stop="mouseenter"
- >
- <i
- :class="voiceSwitchValue ? 'el-icon-microphone' : 'el-icon-turn-off-microphone'"
- @click="voiceSwitch"
- />
- </div>
- </div>
- </template>
- <script>
- import Speech from 'speak-tts'
- import { EventBus } from 'data-room-ui/js/utils/eventBus'
- import commonMixins from 'data-room-ui/js/mixins/commonMixins'
- import paramsMixins from 'data-room-ui/js/mixins/paramsMixins'
- import linkageMixins from 'data-room-ui/js/mixins/linkageMixins'
- import { settingToTheme } from 'data-room-ui/js/utils/themeFormatting'
- import cloneDeep from 'lodash/cloneDeep'
- import IconSvg from 'data-room-ui/SvgIcon'
- export default {
- name: 'Marquee',
- props: {
- // 卡片的属性
- config: {
- type: Object,
- default: () => ({})
- }
- },
- components: {
- IconSvg
- },
- data () {
- return {
- showVoiceSwitch: false,
- voiceSwitchValue: true,
- customClass: {},
- attributeName: {
- right: 'x',
- left: 'x',
- top: 'y',
- bottom: 'y'
- },
- // 动画开始
- from: {
- left: '-100%',
- right: '100%',
- top: '-100%',
- bottom: '100%'
- },
- // 动画结束
- to: {
- left: '100%',
- right: '-100%',
- top: '100%',
- bottom: '-100%'
- },
- isAnimate: true,
- // 组件内部数据
- innerData: null,
- // 音频播放
- audio: null,
- // 音频地址
- isPlayAudio: null,
- // 语音播报
- speech: null,
- isInit: false,
- numberBroadcasts: 0
- }
- },
- computed: {
- // speechText
- speechText () {
- return this.config.customize.title || ''
- },
- isPreview () {
- return (this.$route.path === window?.BS_CONFIG?.routers?.previewUrl) || (this.$route.path === '/big-screen/preview')
- },
- audioSrc () {
- return this.config?.option?.data?.[this.config?.dataSource?.metricField] || ''
- }
- },
- watch: {
- speechText (val) {
- if (!this.isPreview && this.config.customize.voiceBroadcast && !this.isInit) {
- this.speechBroadcast(val)
- } else {
- if (this.speech) {
- this.speech = null
- }
- }
- },
- deep: true,
- audioSrc (val) {
- if (this.config.customize.voiceBroadcast) {
- if (this.audio) {
- this.audio.src = val
- this.audio.play()
- }
- } else {
- if (this.aduio) {
- this.aduio.pause()
- this.aduio = null
- }
- }
- }
- },
- mixins: [paramsMixins, commonMixins, linkageMixins],
- mounted () {
- this.chartInit()
- // 如果点击了生成图片,则先关闭动画
- EventBus.$on('stopMarquee', () => {
- this.isAnimate = false
- })
- // 图片生成完成后,再开启动画
- EventBus.$on('startMarquee', () => {
- this.isAnimate = true
- })
- // 如果删除了组件
- EventBus.$on('deleteComponent', (codes) => {
- if (codes.includes(this.config.code)) {
- if (this.audio) {
- this.audio.pause()
- this.audio = null
- }
- if (this.speech) {
- this.speech = null
- }
- }
- })
- this.speech = null
- this.isInit = true
- // 如果是预览模式的话,则弹出对话框,当前大屏存在语音播报,是否开启语音播报
- if (this.isPreview && this.config.customize.voiceBroadcast) {
- this.$confirm('当前大屏存在语音播报,是否开启语音播报?若开启请点击确认或者回车', '提示', {
- confirmButtonText: '确定',
- cancelButtonText: '取消',
- type: 'warning',
- customClass: 'bs-el-message-box'
- }).then(() => {
- if (this.audioSrc) {
- this.audio.play()
- } else {
- this.speech = null
- this.speechBroadcast(this.config.customize.title)
- this.isInit = false
- }
- }).catch(() => { })
- }
- document.addEventListener('visibilitychange', this.handleVisibilityChange)
- },
- beforeDestroy () {
- EventBus.$off('stopMarquee')
- EventBus.$off('startMarquee')
- EventBus.$off('deleteComponent')
- },
- methods: {
- dataFormatting (config, data) {
- // 数据返回成功则赋值
- if (data.success) {
- data = data.data
- // 获取到后端返回的数据,有则赋值
- if (config.dataHandler) {
- try {
- // 此处函数处理data
- eval(config.dataHandler)
- } catch (e) {
- console.info(e)
- }
- }
- config.option.data = data
- config.customize.title = config.option.data[config.dataSource.dimensionField] || config.customize.title
- this.innerData = config
- // 语音播报
- } else {
- // 数据返回失败则赋前端的模拟数据
- config.option.data = []
- }
- // 清除上一个visibilitychange监听,重新开始监听
- if (this.voiceSwitchValue) {
- this.voiceBroadcast(config)
- }
- return config
- },
- // 语音播报
- voiceBroadcast (config) {
- const innerData = this.innerData || config
- if (innerData) {
- if (config.customize.voiceBroadcast) {
- if (innerData?.dataSource?.businessKey && innerData?.option?.data[this.innerData.dataSource.metricField]) {
- // 如果aduio存在,先销毁这个实例,或者替换它的URL
- if (this.aduio) {
- this.aduio.pause()
- this.aduio = null
- }
- // 获取音频元素
- this.audio = this.$refs[`audioPlayer${config.code}`]
- this.audio.src = innerData.option.data[this.innerData.dataSource.metricField]
- this.audio.play()
- } else if (config.customize.title) {
- // 页面初始化不执行
- if (!this.isInit) {
- this.speechBroadcast(config.customize.title)
- }
- }
- } else {
- if (this.aduio) {
- this.aduio.pause()
- this.aduio = null
- }
- }
- }
- },
- // 语音播报
- speechBroadcast (text) {
- this.numberBroadcasts = 0
- this.speech = new Speech()
- this.speech.setLanguage('zh-CN')
- this.speech.pitch = 1
- this.speech.init()
- if (this.speech.hasBrowserSupport()) {
- if (this.numberBroadcasts < 1) {
- this.speech.speak({ text: text })
- this.numberBroadcasts += 1
- }
- } else {
- this.$message({
- message: '您的浏览器不支持语音播报',
- type: 'warning'
- })
- }
- },
- changeStyle (config) {
- config = { ...this.config, ...config }
- if (config.customize.voiceBroadcast && this.isInit && !this.audioSrc) {
- this.isInit = false
- this.speechBroadcast(config.customize.title)
- }
- // 样式改变时更新主题配置
- config.theme = settingToTheme(cloneDeep(config), this.customTheme)
- this.changeChartConfig(config)
- if (config.code === this.activeCode) {
- this.changeActiveItemConfig(config)
- }
- },
- // 监听页面是否可见
- handleVisibilityChange () {
- if (document.visibilityState === 'hidden') {
- if (this.audio) {
- this.audio.pause()
- }
- if (this.speech) {
- this.speech = null
- }
- } else {
- if (this.audio) {
- this.audio.play()
- }
- if (this.speech) {
- this.speech.resume()
- }
- }
- },
- voiceSwitch () {
- this.voiceSwitchValue = !this.voiceSwitchValue
- if (this.voiceSwitchValue) {
- if (this.audio) {
- try {
- this.audio.play()
- } catch (e) {
- console.info(e)
- }
- }
- if (this.speech) {
- this.speech.resume()
- }
- } else {
- if (this.audio) {
- try {
- this.audio.pause()
- } catch (e) {
- console.info(e)
- }
- }
- if (this.speech) {
- this.speech.pause()
- }
- }
- },
- mouseenter () {
- this.showVoiceSwitch = true
- },
- mouseleave () {
- this.showVoiceSwitch = false
- }
- }
- }
- </script>
- <style lang="scss" scoped>
- .marquee-box {
- width: 100%;
- height: 100%;
- user-select: none;
- white-space: nowrap;
- overflow: hidden;
- position: relative;
- .scroll-area {
- width: 100%;
- height: 100%;
- .marquee-container {
- width: 100%;
- height: 100%;
- display: flex;
- .svg-container {
- width: 100%;
- height: 100%;
- }
- }
- }
- .icon {
- position: relative;
- top: 0;
- // 清除浮动
- }
- }
- .voice-switch{
- position: absolute;
- cursor: pointer;
- bottom: 5px;
- color: #fff;
- }
- </style>
|