فهرست منبع

feat: 跑马灯组件添加语音播报功能,包括文字播报和在线mp3语音播放

wu.jian2 1 سال پیش
والد
کامیت
06a593e687

+ 13 - 20
data-room-ui/package-lock.json

@@ -1,6 +1,6 @@
 {
   "name": "@gcpaas/data-room-ui",
-  "version": "1.0.1-2023081002-Alpha",
+  "version": "1.0.1-2023082201-Alpha",
   "lockfileVersion": 1,
   "requires": true,
   "dependencies": {
@@ -5797,7 +5797,6 @@
       "version": "2.0.11",
       "resolved": "https://registry.npmjs.org/clipboard/-/clipboard-2.0.11.tgz",
       "integrity": "sha512-C+0bbOqkezLIsmWSvlsXS0Q0bmkugu7jcfMIACB+RDEntIzQIkdr148we28AfSloQLRdZlYL/QYyrq05j/3Faw==",
-      "dev": true,
       "requires": {
         "good-listener": "^1.2.2",
         "select": "^1.1.2",
@@ -7337,8 +7336,7 @@
     "delegate": {
       "version": "3.2.0",
       "resolved": "https://registry.npmjs.org/delegate/-/delegate-3.2.0.tgz",
-      "integrity": "sha512-IofjkYBZaZivn0V8nnsMJGBr4jVLxHDheKSW88PyxS5QC4Vo9ZbZVvhzlSxY87fVq3STR6r+4cGepyHkcWOQSw==",
-      "dev": true
+      "integrity": "sha512-IofjkYBZaZivn0V8nnsMJGBr4jVLxHDheKSW88PyxS5QC4Vo9ZbZVvhzlSxY87fVq3STR6r+4cGepyHkcWOQSw=="
     },
     "delegates": {
       "version": "1.0.0",
@@ -9609,7 +9607,6 @@
       "version": "1.2.2",
       "resolved": "https://registry.npmjs.org/good-listener/-/good-listener-1.2.2.tgz",
       "integrity": "sha512-goW1b+d9q/HIwbVYZzZ6SsTr4IgE+WA44A0GmPIQstuOrgsFcT7VEJ48nmr9GaRtNu0XTKacFLGnBPAM6Afouw==",
-      "dev": true,
       "requires": {
         "delegate": "^3.1.2"
       }
@@ -17336,8 +17333,7 @@
     "select": {
       "version": "1.1.2",
       "resolved": "https://registry.npmjs.org/select/-/select-1.1.2.tgz",
-      "integrity": "sha512-OwpTSOfy6xSs1+pwcNrv0RBMOzI39Lp3qQKUTPVVPRjCdNa5JH/oPRiqsesIskK8TVgmRiHwO4KXlV2Li9dANA==",
-      "dev": true
+      "integrity": "sha512-OwpTSOfy6xSs1+pwcNrv0RBMOzI39Lp3qQKUTPVVPRjCdNa5JH/oPRiqsesIskK8TVgmRiHwO4KXlV2Li9dANA=="
     },
     "select-hose": {
       "version": "2.0.0",
@@ -17862,6 +17858,11 @@
         }
       }
     },
+    "speak-tts": {
+      "version": "2.0.8",
+      "resolved": "https://registry.npmjs.org/speak-tts/-/speak-tts-2.0.8.tgz",
+      "integrity": "sha512-VY6Q6mRjdou6bF+x0LspvM7GJhBxHx8CLyGPTNQQ7jrztiGutyI4QNZn0cA17c4uk0FnFbA4PaMI3skeZ6PiFg=="
+    },
     "split": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/split/-/split-1.0.1.tgz",
@@ -19015,8 +19016,7 @@
     "tiny-emitter": {
       "version": "2.1.0",
       "resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.1.0.tgz",
-      "integrity": "sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==",
-      "dev": true
+      "integrity": "sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q=="
     },
     "tiny-sass-compiler": {
       "version": "0.12.2",
@@ -19784,8 +19784,7 @@
     "vue": {
       "version": "2.6.10",
       "resolved": "https://registry.npmjs.org/vue/-/vue-2.6.10.tgz",
-      "integrity": "sha512-ImThpeNU9HbdZL3utgMCq0oiMzAkt1mcgy3/E6zWC/G6AaQoeuFdsl9nDhTDU3X1R6FK7nsIUuRACVcjI+A2GQ==",
-      "dev": true
+      "integrity": "sha512-ImThpeNU9HbdZL3utgMCq0oiMzAkt1mcgy3/E6zWC/G6AaQoeuFdsl9nDhTDU3X1R6FK7nsIUuRACVcjI+A2GQ=="
     },
     "vue-codemirror": {
       "version": "4.0.6",
@@ -19799,8 +19798,7 @@
     "vue-contextmenujs": {
       "version": "1.4.9",
       "resolved": "https://registry.npmjs.org/vue-contextmenujs/-/vue-contextmenujs-1.4.9.tgz",
-      "integrity": "sha512-Z3x3VBrwTwz7ow4YhbjMBPl4zz3uiwhyRIffQ/ZJl+A1Vg8B7e9bVOe2FNGqro+Opyhdf84enx4EgZNsCoPWZA==",
-      "dev": true
+      "integrity": "sha512-Z3x3VBrwTwz7ow4YhbjMBPl4zz3uiwhyRIffQ/ZJl+A1Vg8B7e9bVOe2FNGqro+Opyhdf84enx4EgZNsCoPWZA=="
     },
     "vue-draggable-resizable-gorkys": {
       "version": "2.4.8",
@@ -19916,7 +19914,6 @@
       "version": "1.4.3",
       "resolved": "https://registry.npmjs.org/vue-json-editor/-/vue-json-editor-1.4.3.tgz",
       "integrity": "sha512-st9HdXBgCnyEmmfWrZQiKzp4KuYXzmYVUNDn5h6Fa18MrrGS1amnyUFyv7hQFsNBDW27B7BKkdGOqszYT1srAg==",
-      "dev": true,
       "requires": {
         "vue": "^2.2.6"
       }
@@ -19925,7 +19922,6 @@
       "version": "2.2.22",
       "resolved": "https://registry.npmjs.org/vue-json-viewer/-/vue-json-viewer-2.2.22.tgz",
       "integrity": "sha512-3oPH5BxoUWva/qp7wNJj+15FBXyi9Yu5VDW4mCWivjHR1pUpMv34fjqqxML7jh2uOqm1S/3Xks5nQ5JjC5+OWw==",
-      "dev": true,
       "requires": {
         "clipboard": "^2.0.4"
       }
@@ -20000,8 +19996,7 @@
     "vue-sketch-ruler": {
       "version": "1.0.3",
       "resolved": "https://registry.npmjs.org/vue-sketch-ruler/-/vue-sketch-ruler-1.0.3.tgz",
-      "integrity": "sha512-WfCNXJWn7gdST870nkEixR9JiZqGFCuW/Tuld2EQDYHTleDKPz2A45yTVqrF3MuRLelLkoqtz3+8ZfBhQ7sHGw==",
-      "dev": true
+      "integrity": "sha512-WfCNXJWn7gdST870nkEixR9JiZqGFCuW/Tuld2EQDYHTleDKPz2A45yTVqrF3MuRLelLkoqtz3+8ZfBhQ7sHGw=="
     },
     "vue-style-loader": {
       "version": "4.1.3",
@@ -20073,7 +20068,6 @@
       "version": "2.24.3",
       "resolved": "https://registry.npmjs.org/vuedraggable/-/vuedraggable-2.24.3.tgz",
       "integrity": "sha512-6/HDXi92GzB+Hcs9fC6PAAozK1RLt1ewPTLjK0anTYguXLAeySDmcnqE8IC0xa7shvSzRjQXq3/+dsZ7ETGF3g==",
-      "dev": true,
       "requires": {
         "sortablejs": "1.10.2"
       },
@@ -20081,8 +20075,7 @@
         "sortablejs": {
           "version": "1.10.2",
           "resolved": "https://registry.npmjs.org/sortablejs/-/sortablejs-1.10.2.tgz",
-          "integrity": "sha512-YkPGufevysvfwn5rfdlGyrGjt7/CRHwvRPogD/lC+TnvcN29jDpCifKP+rBqf+LRldfXSTh+0CGLcSg0VIxq3A==",
-          "dev": true
+          "integrity": "sha512-YkPGufevysvfwn5rfdlGyrGjt7/CRHwvRPogD/lC+TnvcN29jDpCifKP+rBqf+LRldfXSTh+0CGLcSg0VIxq3A=="
         }
       }
     },

+ 7 - 6
data-room-ui/package.json

@@ -70,18 +70,19 @@
     "qs": "^6.9.6",
     "remote-vue-loader": "1.0.0",
     "sortablejs": "^1.15.0",
+    "speak-tts": "^2.0.8",
     "videojs-contrib-hls": "^5.15.0",
     "vue-codemirror": "^4.0.6",
-    "vue-draggable-resizable-gorkys": "^2.4.8",
-    "vue-router": "3.0.6",
-    "vue-video-player": "^5.0.1",
-    "vuex": "3.1.0",
-    "ztree": "3.5.24",
     "vue-contextmenujs": "^1.4.9",
+    "vue-draggable-resizable-gorkys": "^2.4.8",
     "vue-json-editor": "^1.4.3",
     "vue-json-viewer": "^2.2.22",
+    "vue-router": "3.0.6",
     "vue-sketch-ruler": "^1.0.3",
-    "vuedraggable": "^2.24.3"
+    "vue-video-player": "^5.0.1",
+    "vuedraggable": "^2.24.3",
+    "vuex": "3.1.0",
+    "ztree": "3.5.24"
   },
   "devDependencies": {
     "@babel/core": "^7.12.16",

+ 113 - 9
data-room-ui/packages/BasicComponents/Marquee/index.vue

@@ -83,6 +83,7 @@
 </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'
@@ -117,8 +118,19 @@ export default {
         top: '100%',
         bottom: '-100%'
       },
-      isAnimate: true
+      isAnimate: true,
+      // 组件内部数据
+      innerData: null,
+      // 音频播放
+      aduio: null,
+      // 语音播报
+      speech: null,
+      // 语音播报定时器
+      speechTimer: null
     }
+  },
+  computed: {
+
   },
   mixins: [paramsMixins, commonMixins],
   mounted () {
@@ -131,20 +143,115 @@ export default {
     EventBus.$on('startMarquee', () => {
       this.isAnimate = true
     })
+    document.addEventListener('visibilitychange', this.handleVisibilityChange)
   },
   beforeDestroy () {
     EventBus.$off('stopMarquee')
     EventBus.$off('startMarquee')
+    // 销毁语音播报定时器
+    if (this.speechTimer) {
+      clearInterval(this.speechTimer)
+    }
   },
   methods: {
-    changeStyle () {
-    },
     dataFormatting (config, data) {
-      // 文本数据配置原则:选择数据集则以后端返回的数据为主,否则以设置面板中标题设置为准
-      if (config.dataSource.businessKey) {
-        config.customize.title = data && data.data && data.data.length ? data.data[0][config.dataSource.metricField] : '暂无数据'
+      // 数据返回成功则赋值
+      if (data.success) {
+        data = data.data
+        // 获取到后端返回的数据,有则赋值
+        if (config.dataHandler) {
+          try {
+            // 此处函数处理data
+            eval(config.dataHandler)
+          } catch (e) {
+            console.error(e)
+          }
+        }
+        config.option.data = data
+        config.customize.title = config.option.data[config.dataSource.dimensionField] || config.customize.title
+        this.innerData = config
+        // 语音播报
+      } else {
+        // 数据返回失败则赋前端的模拟数据
+        config.option.data = []
       }
       return config
+    },
+    // 语音播报
+    voiceBroadcast (config) {
+      if (this.innerData) {
+        if (config.customize.voiceBroadcast) {
+          if (this.innerData.dataSource.businessKey && this.innerData.option.data[this.innerData.dataSource.metricField]) {
+            // 如果aduio存在,先销毁这个实例,或者替换它的URL
+            if (this.aduio) {
+              this.aduio.pause()
+              this.aduio = null
+            }
+            this.aduio = new Audio()
+            this.aduio.src = this.innerData.option.data[this.innerData.dataSource.metricField]
+            this.aduio.play()
+          } else if (config.customize.title) {
+            this.speechBroadcast(config.customize.title)
+            // 根据配置的时间,定时播报,第一次播报后,再定时播报
+            this.speechBroadcast(config.customize.title)
+            if (config.customize.dur) {
+              this.speechTimer = setInterval(() => {
+                this.speechBroadcast(config.customize.title)
+              }, config.customize.dur * 1000)
+            }
+          }
+        } else {
+          if (this.aduio) {
+            this.aduio.pause()
+            this.aduio = null
+          }
+        }
+      } else {
+        if (config.customize.voiceBroadcast) {
+          this.speech = new Speech()
+          if (config.customize.dur) {
+            this.speechBroadcast(config.customize.title)
+            this.speechTimer = setInterval(() => {
+              this.speechBroadcast(config.customize.title)
+            }, config.customize.dur * 1000)
+          }
+        }
+      }
+    },
+    // 语音播报
+    speechBroadcast (text) {
+      if (this.speech.hasBrowserSupport()) {
+        this.speech.setLanguage('zh-CN')
+        this.speech.pitch = 1
+        this.speech.init()
+        this.speech.speak({ text: text })
+      } else {
+        this.$message({
+          message: '您的浏览器不支持语音播报',
+          type: 'warning'
+        })
+      }
+    },
+    changeStyle (config) {
+      this.voiceBroadcast(config)
+    },
+    // 监听页面是否可见
+    handleVisibilityChange () {
+      if (document.visibilityState === 'hidden') {
+        if (this.aduio) {
+          this.aduio.pause()
+        }
+        if (this.speech) {
+          this.speech.pause()
+        }
+      } else {
+        if (this.aduio) {
+          this.aduio.play()
+        }
+        if (this.speech) {
+          this.speech.resume()
+        }
+      }
     }
   }
 }
@@ -154,14 +261,12 @@ export default {
 .marquee-box {
   width: 100%;
   height: 100%;
-  display: inline-block;
   white-space: nowrap;
   overflow: hidden;
 
   .scroll-area {
     width: 100%;
     height: 100%;
-    display: inline-block;
 
     .marquee-container {
       width: 100%;
@@ -169,7 +274,6 @@ export default {
       display: flex;
 
       .svg-container {
-        display: inline-block;
         width: 100%;
         height: 100%;
       }

+ 16 - 0
data-room-ui/packages/BasicComponents/Marquee/setting.vue

@@ -254,6 +254,22 @@
           />
         </el-form-item>
       </div>
+      <SettingTitle>其他</SettingTitle>
+      <div class="lc-field-body">
+        <!-- 是否开启语音播报 -->
+        <el-form-item
+          label="语音播报"
+          label-width="100px"
+        >
+          {{ config.customize.voiceBroadcast }}
+          <el-switch
+            v-model="config.customize.voiceBroadcast"
+            :active-value="true"
+            :inactive-value="false"
+            class="bs-el-switch"
+          />
+        </el-form-item>
+      </div>
     </el-form>
   </div>
 </template>

+ 13 - 7
data-room-ui/packages/BasicComponents/Marquee/settingConfig.js

@@ -14,18 +14,19 @@ export const settingConfig = {
     ...displayOption,
     metricField: {
       // 指标
-      label: '指标',
+      label: '音频链接',
       enable: true,
       multiple: false // 是否多选
     },
     dimensionField: {
       // 维度
-      label: '维度', // 维度/查询字段
-      enable: false,
-      multiple: true // 是否多选
+      label: '跑马灯内容', // 维度/查询字段
+      enable: true,
+      multiple: false // 是否多选
     }
   }
 }
+const dataHandler = ''
 const customConfig = {
   type: 'marquee',
   root: {
@@ -63,11 +64,16 @@ const customConfig = {
     // 背景渐变色结束颜色
     bgGradientColor1: '#fff',
     // 背景色渐变方向
-    bgGradientDirection: 'to right'
-
+    bgGradientDirection: 'to right',
+    // 语音播报
+    voiceBroadcast: false
   }
 
 }
+
+// 配置处理脚本
+
 export const dataConfig = {
-  ...commonConfig(customConfig)
+  ...commonConfig(customConfig),
+  dataHandler
 }

+ 1 - 1
data-room-ui/packages/BigScreenDesign/RightSetting/DataSetting.vue

@@ -41,7 +41,7 @@
           </div>
         </div>
         <div
-          v-if="config.type === 'customComponent'"
+          v-if="['customComponent','marquee'].includes(config.type)"
           class="data-setting-data-box"
         >
           <div class="lc-field-head">

+ 10 - 4
data-room-ui/packages/assets/style/bsTheme.scss

@@ -527,10 +527,10 @@
 
 // radio
 .bs-el-radio-group {
-  .el-radio__label{
+  .el-radio__label {
     color: var(--bs-el-text) !important;
   }
-  .el-radio__input.is-checked + .el-radio__label{
+  .el-radio__input.is-checked + .el-radio__label {
     color: var(--bs-el-color-primary) !important;
   }
   .el-radio__inner {
@@ -538,7 +538,7 @@
     background: var(--bs-background-1) !important;
     background-color: var(--bs-background-1) !important;
   }
-  .el-radio__input.is-checked .el-radio__inner::after{
+  .el-radio__input.is-checked .el-radio__inner::after {
     background-color: var(--bs-el-color-primary) !important;
   }
 }
@@ -598,8 +598,14 @@
 
 // switch
 .bs-el-switch {
+
+  .el-switch.is-checked .el-switch__core {
+    background: var(--bs-el-color-primary) !important;
+    background-color: var(--bs-el-color-primary) !important;
+  }
   .el-switch__core {
-    background: var(--bs-el-background-1) !important;
+    background: var(--bs-el-background-1);
+    background-color: var(--bs-el-background-1);
   }
 }
 

+ 1 - 1
data-room-ui/packages/js/utils/getComponentConfig.js

@@ -1,5 +1,5 @@
 import Icon from 'data-room-ui/assets/images/bigScreenIcon/export'
-console.log(Icon)
+
 export default function getComponentConfig (type) {
   switch (type) {
     case 'texts':

+ 36 - 0
data-room-ui/packages/js/utils/voiceBroadcast.js

@@ -0,0 +1,36 @@
+/**
+ * @description 文字转语音方法
+ * @public
+ * @param { text, rate, lang, volume, pitch } object
+ * @param  text 要合成的文字内容,字符串
+ * @param  rate 读取文字的语速 0.1~10  正常1
+ * @param  lang 读取文字时的语言
+ * @param  volume  读取时声音的音量 0~1  正常1
+ * @param  pitch  读取时声音的音高 0~2  正常1
+ * @returns SpeechSynthesisUtterance
+ */
+export default function speak ({ text, speechRate, lang, volume, pitch }, endEvent, startEvent) {
+  if (!window.SpeechSynthesisUtterance) {
+    console.warn('当前浏览器不支持文字转语音服务')
+    return
+  }
+
+  if (!text) {
+    return
+  }
+
+  const speechUtterance = new SpeechSynthesisUtterance()
+  speechUtterance.text = text
+  speechUtterance.rate = speechRate || 1
+  speechUtterance.lang = lang || 'zh-CN'
+  speechUtterance.volume = volume || 1
+  speechUtterance.pitch = pitch || 1
+  speechUtterance.onend = function () {
+    endEvent && endEvent()
+  }
+  speechUtterance.onstart = function () {
+    startEvent && startEvent()
+  }
+  speechSynthesis.speak(speechUtterance)
+  console.log(2)
+}