442 lines
13 KiB
Vue

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>
<!-- 添加一个类似鼠标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'
import { get } from 'sortablejs'
export default {
name: 'Marquee',
props: {
// 卡片的属性
config: {
type: Object,
default: () => ({})
}
},
components: {
IconSvg
},
data () {
return {
showVoiceSwitch: false,
visibilityState: 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,
firstSpeech: true,
numberBroadcasts: 0
}
},
computed: {
// speechText
speechText () {
return this.config.customize.title || ''
},
audioSrc: {
get () {
return this.config?.option?.data?.[this.config?.dataSource?.metricField] || ''
},
set (val) {
this.config.option.data[this.config.dataSource.metricField] = val
}
}
},
watch: {
speechText (val) {
if (!this.isPreview && this.config.customize.voiceBroadcast && !this.isInit && !this.firstSpeech) {
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.visibilityState && this.isInit) {
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 && !config?.option?.data?.[this.config?.dataSource?.metricField]) {
this.isInit = false
this.speechBroadcast(config.customize.title)
this.$nextTick(() => {
this.firstSpeech = false
})
}
// 样式改变时更新主题配置
config.theme = settingToTheme(cloneDeep(config), this.customTheme)
this.changeChartConfig(config)
if (config.code === this.activeCode) {
this.changeActiveItemConfig(config)
}
},
// 监听页面是否可见
handleVisibilityChange () {
if (document.visibilityState === 'hidden') {
this.visibilityState = true
if (this.audio) {
this.audio.pause()
}
if (this.speech) {
this.speech = null
}
} else {
this.visibilityState = false
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>