Forráskód Böngészése

refactor(volume): percentage -> level 16

Casper Dai 2 éve
szülő
commit
850f06b52d

+ 2 - 1
src/utils/adapter/monitor.js

@@ -4,6 +4,7 @@ import {
   createClient,
   decodePayload
 } from '@/utils/mqtt'
+import { transformPercentVolumeToLevelVolume } from '@/utils/control/volume'
 import { getStatusReport } from '@/api/device'
 import { getThirdPartyDevicesByThirdPartyDevice } from '@/api/mesh'
 import {
@@ -84,7 +85,7 @@ export function startMonitor () {
         versionCode,
         externalUsage,
         ramUsage,
-        volume
+        volume: transformPercentVolumeToLevelVolume(volume)
       })
       return
     }

+ 23 - 0
src/utils/control/light.js

@@ -0,0 +1,23 @@
+import { publish } from '@/utils/mqtt.js'
+import { saveBoxLogger } from '@/api/platform.js'
+
+export function sendLightToDevice ({ id, productId, name }, percentLight) {
+  console.log('light', name, '->', `${percentLight}%`)
+  const timestamp = `${Date.now()}`
+  const messageId = `volume_${timestamp}`
+  return publish(`${productId}/${id}/function/invoke`, JSON.stringify({
+    messageId,
+    timestamp,
+    'function': 'volumeControl',
+    inputs: [{
+      name: 'volume',
+      value: `${percentLight}`
+    }]
+  }), true).then(() => {
+    saveBoxLogger({
+      description: `将【${name}】亮度设置为${percentLight}%`,
+      method: '亮度设置',
+      params: `${id} ${name} ${percentLight}%`
+    })
+  })
+}

+ 105 - 0
src/utils/control/volume.js

@@ -0,0 +1,105 @@
+import { publish } from '@/utils/mqtt.js'
+import { saveBoxLogger } from '@/api/platform.js'
+
+export const MAX_VOLUME_LEVEL = 15
+
+export const DEFAULT_VOLUME_LEVEL = 3
+
+export const SLIDER_OPTIONS = {
+  min: 0,
+  max: MAX_VOLUME_LEVEL,
+  'show-stops': true,
+  marks: {
+    0: {
+      style: {
+        'white-space': 'nowrap'
+      },
+      label: 'Lv.0'
+    },
+    [DEFAULT_VOLUME_LEVEL]: {
+      style: {
+        color: '#1989fa',
+        'white-space': 'nowrap'
+      },
+      label: `Lv.${DEFAULT_VOLUME_LEVEL}`
+    },
+    9: {
+      style: {
+        color: '#67c23a',
+        'white-space': 'nowrap'
+      },
+      label: 'Lv.9'
+    },
+    12: {
+      style: {
+        color: '#e6a23c',
+        'white-space': 'nowrap'
+      },
+      label: 'Lv.12'
+    },
+    [MAX_VOLUME_LEVEL]: {
+      style: {
+        color: '#f56c6c',
+        'white-space': 'nowrap'
+      },
+      label: `Lv.${MAX_VOLUME_LEVEL}`
+    }
+  }
+}
+
+const queue = []
+let waiting = false
+
+function run () {
+  const logger = queue.shift()
+  if (logger) {
+    saveBoxLogger(logger).finally(run)
+  } else {
+    waiting = false
+  }
+}
+
+function saveLogger (logger) {
+  queue.push(logger)
+  if (!waiting) {
+    waiting = true
+    setTimeout(run, 0)
+  }
+}
+
+export function sendVolumeToDevice ({ id, productId, name }, levelVolume) {
+  const percentVolume = transformLevelVolumeToPercentVolume(levelVolume)
+  console.log('volume', name, '->', `Lv.${levelVolume}`, `${percentVolume}%`)
+  const timestamp = `${Date.now()}`
+  const messageId = `volume_${timestamp}`
+  return publish(`${productId}/${id}/function/invoke`, JSON.stringify({
+    messageId,
+    timestamp,
+    'function': 'volumeControl',
+    inputs: [{
+      name: 'volume',
+      value: `${percentVolume}`
+    }]
+  }), true).then(() => {
+    saveLogger({
+      description: `将【${name}】音量设置为Lv.${levelVolume}`,
+      method: '音量设置',
+      params: `${id} ${name} Lv.${levelVolume}`
+    })
+  })
+}
+
+export function transformLevelVolumeToPercentVolume (volume) {
+  return Math.ceil(volume * 100 / MAX_VOLUME_LEVEL)
+}
+
+export function transformPercentVolumeToLevelVolume (volume) {
+  return Math.round(volume * MAX_VOLUME_LEVEL / 100)
+}
+
+export function parseVolume (volume) {
+  if (volume === MAX_VOLUME_LEVEL) {
+    return 'MAX Lv'
+  }
+  return `Lv.${volume}`
+}

+ 8 - 1
src/views/dashboard/components/DeviceCard.vue

@@ -58,7 +58,7 @@
             class="c-sibling-item"
             icon-class="volume"
           />
-          <span class="c-sibling-item nearest">{{ volume }}%</span>
+          <span class="c-sibling-item nearest">{{ volumeTip }}</span>
         </template>
       </div>
     </div>
@@ -143,6 +143,7 @@ import {
   getStartDate,
   toDate
 } from '@/utils/event'
+import { parseVolume } from '@/utils/control/volume'
 import { getTimelineByRange } from '@/api/device'
 
 export default {
@@ -228,6 +229,12 @@ export default {
     },
     needCheck () {
       return this.isInView && this.isRealOnline
+    },
+    volumeTip () {
+      if (this.volume > -1) {
+        return parseVolume(this.volume)
+      }
+      return ''
     }
   },
   watch: {

+ 11 - 6
src/views/dashboard/components/DeviceCardSimple.vue

@@ -55,7 +55,7 @@
             class="c-sibling-item"
             icon-class="volume"
           />
-          <span class="c-sibling-item nearest">{{ volume }}%</span>
+          <span class="c-sibling-item nearest">{{ volumeTip }}</span>
         </template>
       </div>
     </div>
@@ -71,6 +71,7 @@ import {
   addListener,
   removeListener
 } from '@/utils/adapter'
+import { parseVolume } from '@/utils/control/volume'
 
 export default {
   name: 'DeviceCardSimple',
@@ -141,6 +142,12 @@ export default {
         : this.isOnline
           ? '播控器正常运行中'
           : '当前播控器离线了'
+    },
+    volumeTip () {
+      if (this.volume > -1) {
+        return parseVolume(this.volume)
+      }
+      return ''
     }
   },
   watch: {
@@ -197,7 +204,7 @@ export default {
 .o-device {
   display: inline-flex;
   flex-direction: column;
-  padding: $padding--md $padding--lg;
+  padding: $padding--2xs $padding--xs;
   color: $black;
   line-height: 1;
   border-radius: $radius;
@@ -232,9 +239,8 @@ export default {
   &__tip {
     display: inline-block;
     position: relative;
-    left: $padding--lg;
+    left: $padding--xs;
     padding: $padding--2xs $padding--sm $padding--2xs $padding;
-    margin-left: -$spacing--xs;
     border-radius: $radius--sm 0 0 $radius--sm;
     background-color: currentColor;
   }
@@ -242,9 +248,8 @@ export default {
   &__volume {
     display: inline-block;
     position: relative;
-    left: $padding--lg;
+    left: $padding--xs;
     padding: $padding--2xs;
-    margin-left: -$spacing--xs;
     border-radius: $radius--sm 0 0 $radius--sm;
     background-color: $gray--dark;
   }

+ 16 - 35
src/views/dashboard/components/DrawerVolumeSettings.vue

@@ -16,18 +16,14 @@
           @click="onHide"
         />
       </div>
-      <div class="l-flex__none l-flex--row center c-sibling-item--v">
-        <span class="c-sibling-item u-font-size--sm u-bold">音量</span>
-        <el-input-number
+      <div class="l-flex__none l-flex--row center c-sibling-item--v near">
+        <el-slider
           v-model="volume"
-          class="c-sibling-item"
-          :min="0"
-          :max="100"
-          step-strictly
+          class="c-sibling-item u-width--md"
+          v-bind="sliderOptions"
         />
-        <span class="c-sibling-item nearer u-font-size--sm">%</span>
         <button
-          class="c-sibling-item farther o-button"
+          class="c-sibling-item farthest o-button"
           @click="onVolume"
         >
           应用
@@ -35,7 +31,7 @@
       </div>
       <device-group-tree
         v-if="groups"
-        class="l-flex__fill c-sibling-item--v"
+        class="l-flex__fill c-sibling-item--v far"
         :groups="groups"
         checkbox
         check-on-click-node
@@ -47,8 +43,11 @@
 </template>
 
 <script>
-import { publish } from '@/utils/mqtt'
-import { saveBoxLogger } from '@/api/platform'
+import {
+  DEFAULT_VOLUME_LEVEL,
+  SLIDER_OPTIONS,
+  sendVolumeToDevice
+} from '@/utils/control/volume.js'
 import DeviceGroupTree from './DeviceGroupTree.vue'
 
 export default {
@@ -59,13 +58,14 @@ export default {
   data () {
     return {
       drawer: false,
-      volume: 50,
+      volume: 0,
+      sliderOptions: SLIDER_OPTIONS,
       groups: null
     }
   },
   methods: {
     show (groups) {
-      this.volume = 50
+      this.volume = DEFAULT_VOLUME_LEVEL
       this.groups = groups
       this.drawer = true
     },
@@ -89,38 +89,19 @@ export default {
       }
       const volume = this.volume
       this.$confirm(
-        `将所选设备的音量均设置为${volume}%?`,
+        `将所选设备的音量均设置为Lv.${volume}?`,
         '操作确认',
         {
           type: 'warning'
         }
       ).then(() => {
-        this.$selectedDevices.forEach(device => this.changeVolume(device, volume))
+        this.$selectedDevices.forEach(device => sendVolumeToDevice(device, volume))
         this.$message({
           type: 'success',
           message: '配置下发中...'
         })
         this.onHide()
       })
-    },
-    changeVolume ({ productId, id, name }, volume) {
-      const timestamp = `${Date.now()}`
-      const messageId = `volume_${timestamp}`
-      publish(`${productId}/${id}/function/invoke`, JSON.stringify({
-        messageId,
-        timestamp,
-        'function': 'volumeControl',
-        inputs: [{
-          name: 'volume',
-          value: `${volume}`
-        }]
-      }), true).then(() => {
-        saveBoxLogger({
-          description: `将【${name}】音量设置为${this.taskValue}%`,
-          method: '音量设置',
-          params: `${id} ${name} ${volume}%`
-        })
-      })
     }
   }
 }

+ 15 - 32
src/views/dashboard/components/VolumeDialog.vue

@@ -8,60 +8,43 @@
   >
     <template #default>
       <div class="l-flex--row center">
-        <el-input-number
-          v-model="editVolume"
-          class="c-sibling-item"
-          :min="0"
-          :max="100"
-          step-strictly
+        <el-slider
+          v-model="volume"
+          class="u-width--md"
+          v-bind="sliderOptions"
         />
-        <span class="c-sibling-item nearer u-font-size--sm">%</span>
       </div>
     </template>
   </confirm-dialog>
 </template>
 
 <script>
-import { publish } from '@/utils/mqtt'
-import { saveBoxLogger } from '@/api/platform'
+import {
+  SLIDER_OPTIONS,
+  sendVolumeToDevice
+} from '@/utils/control/volume.js'
 
 export default {
   name: 'VolumeDialog',
   data () {
     return {
-      editVolume: 0
+      volume: 0,
+      sliderOptions: SLIDER_OPTIONS
     }
   },
   methods: {
     show (volume, { id, productId, name }) {
       this.$device = { id, productId, name }
-      this.$editVolume = volume
-      this.editVolume = volume
+      this.$volume = volume
+      this.volume = volume
       this.$refs.volumeDialog.show()
     },
     onConfirmVolume (done) {
-      const volume = this.editVolume
-      if (volume === this.$editVolume) {
+      const volume = this.volume
+      if (volume === this.$volume) {
         done()
       } else {
-        console.log(volume, this.$device)
-        const { id, productId, name } = this.$device
-        const timestamp = `${Date.now()}`
-        const messageId = `volume_${timestamp}`
-        publish(`${productId}/${id}/function/invoke`, JSON.stringify({
-          messageId,
-          timestamp,
-          'function': 'volumeControl',
-          inputs: [{
-            name: 'volume',
-            value: `${volume}`
-          }]
-        }), true).then(() => {
-          saveBoxLogger({
-            description: `将【${name}】音量设置为${volume}%`,
-            method: '音量设置',
-            params: `${id} ${name} ${volume}%`
-          })
+        sendVolumeToDevice(this.$device, volume).then(() => {
           this.$message({
             type: 'success',
             message: '配置下发中...'

+ 3 - 1
src/views/device/detail/components/DeviceInfo/components/VolumeInfo.vue

@@ -1,4 +1,6 @@
 <script>
+import { parseVolume } from '@/utils/control/volume'
+
 export default {
   name: 'VolumeInfo',
   props: {
@@ -18,7 +20,7 @@ export default {
         class: 'el-icon-loading u-font-size'
       })
     }
-    return h('div', null, `${this.volume}%`)
+    return h('div', null, parseVolume(this.volume))
   }
 }
 </script>

+ 40 - 17
src/views/device/detail/components/DeviceInvoke/ScreenLight.vue

@@ -2,7 +2,7 @@
   <div class="l-flex--col center has-border radius has-padding ">
     <i
       class="o-icon lg u-pointer"
-      @click="invoke"
+      @click="onTrigger"
     />
     <div class="has-padding u-color--black u-bold">{{ type }}</div>
     <c-dialog
@@ -17,16 +17,15 @@
         />
         <template v-if="isImmediate">
           <div class="l-flex__auto l-flex--col jcenter center has-bottom-padding">
-            <div class="c-sibling-item--v c-grid-form auto">
-              <span class="c-grid-form__label u-required">{{ tip }}</span>
-              <div class="l-flex--row">
-                <el-input-number
-                  v-model="taskValue"
-                  :min="0"
-                  :max="100"
-                  step-strictly
-                />&nbsp;%
-              </div>
+            <div class="l-flex--row c-sibling-item--v">
+              <div class="c-sibling-item">亮度(%)</div>
+              <el-input-number
+                v-model="light"
+                class="c-sibling-item u-width"
+                :min="0"
+                :max="100"
+                step-strictly
+              />
             </div>
             <button
               class="c-sibling-item--v o-button"
@@ -50,14 +49,14 @@
       @confirm="onSave"
     >
       <template #default>
-        <span class="c-grid-form__label u-required">{{ tip }}</span>
+        <span class="c-grid-form__label u-required">亮度(%)</span>
         <div class="l-flex--row">
           <el-input-number
-            v-model="task"
+            v-model="task.value"
             :min="0"
             :max="100"
             step-strictly
-          />&nbsp;%
+          />
         </div>
       </template>
     </task-dialog>
@@ -65,7 +64,8 @@
 </template>
 
 <script>
-import simpleTaskMixin from './mixins/simple-task'
+import { sendLightToDevice } from '@/utils/control/light.js'
+import simpleTaskMixin from './mixins/simple-task.js'
 
 export default {
   name: 'ScreenLight',
@@ -76,8 +76,31 @@ export default {
       functionKey: 'brightnessControl',
       valueKey: 'brigthness',
       schema: this.createSchema([
-        { label: '亮度值(%)', render ({ inputs }) { return inputs[0]?.value } }
-      ])
+        { label: '亮度(%)', render ({ inputs }) { return inputs[0]?.value } }
+      ]),
+      light: 0
+    }
+  },
+  methods: {
+    onTrigger () {
+      this.light = 50
+      this.invoke()
+    },
+    onInvoke () {
+      const light = this.light
+      this.$confirm(
+        `将亮度设置为${light}%?`,
+        '操作确认',
+        { type: 'warning' }
+      ).then(() => {
+        sendLightToDevice(this.device, light).then(() => {
+          this.$set(this.device, this.valueKey, light)
+          this.$message({
+            type: 'success',
+            message: '配置下发中...'
+          })
+        })
+      })
     }
   }
 }

+ 75 - 25
src/views/device/detail/components/DeviceInvoke/ScreenVolume.vue

@@ -6,9 +6,15 @@
     >
       <i
         class="o-icon lg u-pointer"
-        @click="invoke"
+        @click="onTrigger"
       />
-      <div class="has-padding u-color--black u-bold">{{ type }}</div>
+      <div class="has-padding u-color--black u-bold">
+        <i
+          v-if="loading"
+          class="el-icon-loading"
+        />
+        {{ type }}
+      </div>
     </slot>
     <c-dialog
       ref="dialog"
@@ -22,19 +28,13 @@
         />
         <template v-if="isImmediate">
           <div class="l-flex__auto l-flex--col jcenter center has-bottom-padding">
-            <div class="c-sibling-item--v c-grid-form auto">
-              <span class="c-grid-form__label u-required">{{ tip }}</span>
-              <div class="l-flex--row">
-                <el-input-number
-                  v-model="taskValue"
-                  :min="0"
-                  :max="100"
-                  step-strictly
-                />&nbsp;%
-              </div>
-            </div>
+            <el-slider
+              v-model="volume"
+              class="c-sibling-item--v u-width--lg"
+              v-bind="sliderOptions"
+            />
             <button
-              class="c-sibling-item--v o-button"
+              class="c-sibling-item--v farther o-button"
               @click="onInvoke"
             >
               应用
@@ -55,14 +55,12 @@
       @confirm="onSave"
     >
       <template #default>
-        <span class="c-grid-form__label u-required">{{ tip }}</span>
-        <div class="l-flex--row">
-          <el-input-number
-            v-model="task"
-            :min="0"
-            :max="100"
-            step-strictly
-          />&nbsp;%
+        <div class="l-flex--row center c-grid-form__row">
+          <el-slider
+            v-model="task.value"
+            class="u-width--md"
+            v-bind="sliderOptions"
+          />
         </div>
       </template>
     </task-dialog>
@@ -70,7 +68,15 @@
 </template>
 
 <script>
-import simpleTaskMixin from './mixins/simple-task'
+import {
+  DEFAULT_VOLUME_LEVEL,
+  SLIDER_OPTIONS,
+  transformLevelVolumeToPercentVolume,
+  transformPercentVolumeToLevelVolume,
+  parseVolume,
+  sendVolumeToDevice
+} from '@/utils/control/volume.js'
+import simpleTaskMixin from './mixins/simple-task.js'
 
 export default {
   name: 'ScreenVolumn',
@@ -81,8 +87,10 @@ export default {
       functionKey: 'volumeControl',
       valueKey: 'volume',
       schema: this.createSchema([
-        { label: '音量值(%)', render ({ inputs }) { return inputs[0]?.value } }
-      ])
+        { label: '音量', render ({ inputs }) { return parseVolume(transformPercentVolumeToLevelVolume(inputs[0]?.value)) } }
+      ]),
+      volume: 0,
+      sliderOptions: SLIDER_OPTIONS
     }
   },
   computed: {
@@ -90,6 +98,48 @@ export default {
       return this.$scopedSlots.trigger
         ? ''
         : 'l-flex--col center has-border radius has-padding'
+    },
+    loading () {
+      return isNaN(this.device.volume)
+    }
+  },
+  methods: {
+    onTrigger () {
+      this.volume = this.loading ? DEFAULT_VOLUME_LEVEL : this.device.volume
+      this.invoke()
+    },
+    onInvoke () {
+      const volume = this.volume
+      this.$confirm(
+        `将音量设置为Lv.${volume}?`,
+        '操作确认',
+        { type: 'warning' }
+      ).then(() => {
+        sendVolumeToDevice(this.device, volume).then(() => {
+          this.$set(this.device, this.valueKey, volume)
+          this.$message({
+            type: 'success',
+            message: '配置下发中...'
+          })
+        })
+      })
+    },
+    createTask (task) {
+      console.log(task)
+      if (task && task.inputs) {
+        const valueKey = this.valueKey
+        const prop = task.inputs.find(item => item.name === valueKey)
+        if (prop) {
+          return { value: transformPercentVolumeToLevelVolume(prop.value) }
+        }
+      }
+      return { value: this.createDefaultValue() }
+    },
+    createDefaultValue () {
+      return DEFAULT_VOLUME_LEVEL
+    },
+    getTaskValue () {
+      return transformLevelVolumeToPercentVolume(this.task.value)
     }
   }
 }

+ 14 - 34
src/views/device/detail/components/DeviceInvoke/mixins/simple-task.js

@@ -1,6 +1,5 @@
 import taskMixin from './task'
 import { createListOptions } from '@/utils'
-import { saveBoxLogger } from '@/api/platform'
 
 export default {
   mixins: [taskMixin],
@@ -9,65 +8,46 @@ export default {
       tabs: [
         { key: 'immediate', name: '实时控制' },
         { key: 'delay', name: '定时控制' }
-      ],
-      taskValue: 50
+      ]
     }
   },
   computed: {
     settings () {
       return `${this.type}设置`
-    },
-    tip () {
-      return `${this.type}值`
     }
   },
   methods: {
     invoke () {
       this.active = 'immediate'
-      this.taskValue = isNaN(this.device[this.valueKey]) ? 50 : this.device[this.valueKey]
       this.options = createListOptions({ functionKey: this.functionKey })
       this.$refs.dialog.show()
     },
-    onInvoke () {
-      this.$confirm(
-        `将${this.type}设置为${this.taskValue}%?`,
-        '操作确认',
-        { type: 'warning' }
-      ).then(() => {
-        this.sendTopic(this.functionKey, this.createProps(this.taskValue)).then(() => {
-          this.$set(this.device, this.valueKey, this.taskValue)
-          saveBoxLogger({
-            description: `将【${this.device.name}】的${this.type}设置为${this.taskValue}%`,
-            method: `${this.type}设置`,
-            params: `${this.deviceId} ${this.device.name} ${this.taskValue}%`
-          })
-        })
-      })
-    },
-    createProps (val) {
-      return [{
-        name: this.valueKey,
-        value: val.toString()
-      }]
-    },
     createTask (task) {
       if (task && task.inputs) {
         const valueKey = this.valueKey
         const prop = task.inputs.find(item => item.name === valueKey)
         if (prop) {
-          this.$taskValue = Number(prop.value)
-          return this.$taskValue
+          return { value: Number(prop.value) }
         }
       }
+      return { value: this.createDefaultValue() }
+    },
+    createDefaultValue () {
       return 50
     },
-    isSameTask () {
-      return this.$taskValue === this.task
+    isSameTask (task, oldTask) {
+      return task.value === oldTask.value
     },
     getTask () {
       return {
-        inputs: this.createProps(this.task)
+        inputs: [{
+          name: this.valueKey,
+          value: this.getTaskValue().toString()
+        }]
       }
+    },
+    getTaskValue () {
+      return this.task.value
     }
   }
 }

+ 6 - 4
src/views/device/detail/components/DeviceInvoke/mixins/task.js

@@ -65,7 +65,8 @@ export default {
     onEdit (task) {
       this.isAdd = false
       this.task = this.createTask(task)
-      this.$task = task
+      this.$task = { ...this.task }
+      this.$taskOptions = { ...task }
       this.$refs.taskDialog.show(task)
     },
     createTask () {
@@ -75,8 +76,8 @@ export default {
       console.log('task', task)
       if (
         !this.isAdd
-        && Object.keys(task).every(key => task[key] === this.$task[key])
-        && this.isSameTask(this.$task)
+        && Object.keys(task).every(key => task[key] === this.$taskOptions[key])
+        && this.isSameTask(task, this.$task)
       ) {
         done()
         return
@@ -84,6 +85,7 @@ export default {
       (this.isAdd ? this.addTask : this.updateTask)(task).then(done)
     },
     isSameTask () {
+      // implement in component
       return true
     },
     addTask (task) {
@@ -100,7 +102,7 @@ export default {
     updateTask (task) {
       return updateTask({
         deviceId: this.deviceId,
-        taskId: this.$task.taskId,
+        taskId: this.$taskOptions.taskId,
         functionKey: this.$refs.table.getCondition().functionKey,
         ...task,
         ...this.getTask()