Kaynağa Gözat

feat(power): batch operation of timing tasks

Casper Dai 2 yıl önce
ebeveyn
işleme
3b9330c537

+ 233 - 0
src/views/device/power/components/DevicePowerDecreaseTask.vue

@@ -0,0 +1,233 @@
+<script>
+import {
+  ThirdPartyDevice,
+  PowerAction
+} from '@/constant'
+import { publish } from '@/utils/mqtt'
+import {
+  Status,
+  GET_MULTI_POWER_TIMING,
+  SET_MULTI_POWER_TIMING,
+  addListener,
+  removeListener,
+  addInjectListener,
+  removeInjectListener
+} from '@/utils/adapter'
+import { savePowerLogger } from '@/api/platform'
+
+export default {
+  name: 'DevicePowerDecreaseTask',
+  props: {
+    device: {
+      type: Object,
+      required: true
+    },
+    dateRange: {
+      type: Array,
+      required: true
+    },
+    actions: {
+      type: Array,
+      required: true
+    },
+    invoke: {
+      type: Number,
+      default: 0
+    }
+  },
+  data () {
+    return {
+      status: 0,
+      retryCount: 0,
+      cardInfo: null
+    }
+  },
+  mounted () {
+    this.$timer = -1
+    addListener(this.device.id, this.onMessage)
+    addInjectListener(this.device.id, this.onInjectMessage)
+  },
+  beforeDestroy () {
+    clearTimeout(this.$timer)
+    removeListener(this.device.id, this.onMessage)
+    removeInjectListener(this.device.id, this.onInjectMessage)
+  },
+  methods: {
+    onMessage (value) {
+      const multiCard = value[ThirdPartyDevice.MULTI_FUNCTION_CARD]
+      if (multiCard.status === Status.OK) {
+        if (!this.cardInfo) {
+          const { connectIndex, portIndex } = multiCard.powers[0]
+          const type = multiCard.powers.find(({ type }) => type === '屏体电源' || type === '默认')?.type || multiCard.powers.find(({ type }) => !!type)?.type
+          this.cardInfo = { type, connectIndex, portIndex }
+          if (type) {
+            this.onSync()
+          } else {
+            this.status = 4
+          }
+        }
+      }
+    },
+    onInjectMessage (message) {
+      if (message.messageId === this.$messageId) {
+        this.$messageId = null
+        if (message.code !== 0) {
+          this.status = 3
+          return
+        }
+        const data = message.data ? JSON.parse(message.data.replaceAll("'", '"')) : {}
+        if (data.logined === false) {
+          this.status = 3
+          return
+        }
+        switch (message.function) {
+          case GET_MULTI_POWER_TIMING:
+            clearTimeout(this.$timer)
+            this.sendTasks(data.data)
+            break
+          case SET_MULTI_POWER_TIMING:
+            clearTimeout(this.$timer)
+            this.status = 2
+            break
+          default:
+            break
+        }
+      }
+    },
+    onSync () {
+      this.$timer = setTimeout(this.retry, 5000)
+      this.sendTopic(GET_MULTI_POWER_TIMING, { sn: this.device.serialNumber }).then(messageId => {
+        this.$messageId = messageId
+      }, () => {
+        clearTimeout(this.$timer)
+      })
+    },
+    createTasks () {
+      const type = this.cardInfo.type
+      return this.tasks.map(task => {
+        return {
+          ...task,
+          type
+        }
+      })
+    },
+    sendTasks (tasks) {
+      const data = []
+      const { connectIndex, portIndex } = this.cardInfo
+      const currTasks = tasks.find(item => item.connectIndex === connectIndex && item.portIndex === portIndex)?.conditions
+      if (currTasks) {
+        const { change, data: newTasks } = this.checkAndTransfromTasks(currTasks)
+        if (change) {
+          this.status = 1
+          data.push({
+            connectIndex,
+            portIndex,
+            enable: true,
+            conditions: newTasks.map(({ status, ...task }) => task)
+          })
+          this.$timer = setTimeout(this.retry, 5000)
+          this.sendTopic(SET_MULTI_POWER_TIMING, {
+            sn: this.device.serialNumber,
+            taskInfo: data
+          }).then(messageId => {
+            this.$messageId = messageId
+            const [startDate, endDate] = this.dateRange
+            const date = startDate === endDate ? startDate : `${startDate} - ${endDate}`
+            const action = this.actions.length ? this.actions.map(action => action === PowerAction.OPEN ? '开启任务' : '关闭任务').join('/') : '所有定时任务'
+            savePowerLogger({
+              description: `设备【${this.device.name}】 ${['停用', '启用', '移除'][this.invoke]} ${date} ${action}`,
+              method: '电源定时任务设置',
+              params: JSON.stringify({
+                id: this.device.id,
+                name: this.device.name,
+                dateRange: this.dateRange,
+                actions: this.actions,
+                invoke: this.invoke
+              })
+            })
+          }, () => {
+            clearTimeout(this.$timer)
+          })
+          return
+        }
+      }
+      this.status = 2
+    },
+    checkAndTransfromTasks (tasks) {
+      const data = []
+      let change = false
+      const [startTime, endTime] = this.dateRange
+      const needCheckAction = this.actions.length > 0
+      const isDel = this.invoke === 2
+      const actionEnable = isDel ? false : !!this.invoke
+      for (let i = 0; i < tasks.length; i++) {
+        const task = tasks[i]
+        if (!(task.startTime > endTime || task.endTime < startTime)) {
+          if (!needCheckAction || this.actions.includes(task.action)) {
+            if (isDel) {
+              change = true
+              continue
+            } else if (actionEnable !== task.enable) {
+              change = true
+              data.push({
+                ...task,
+                enable: actionEnable
+              })
+              continue
+            }
+          }
+        }
+        data.push({ ...task })
+      }
+      return { change, data }
+    },
+    retry () {
+      this.retryCount += 1
+      this.status = 1
+      this.onSync()
+    },
+    sendTopic (invoke, inputs) {
+      const timestamp = `${Date.now()}`
+      const messageId = `${invoke}_${timestamp}`
+      return publish(
+        `${this.device.productId}/${this.device.id}/multifunctionCard/invoke`,
+        JSON.stringify({
+          messageId,
+          timestamp,
+          'function': invoke,
+          inputs: JSON.stringify(inputs || [])
+        }),
+        true
+      ).then(() => messageId)
+    }
+  },
+  render (h) {
+    if (this.status === 3 || this.status === 4) {
+      return h('el-tooltip', {
+        props: {
+          content: this.status === 3 ? '连接失败,请联系管理员' : '无可用端口,请联系管理员',
+          placement: 'left',
+          enterable: false
+        }
+      }, [
+        h('el-tag', {
+          staticClass: 'o-tag u-readonly',
+          props: {
+            size: 'medium',
+            'disable-transitions': true,
+            type: 'danger'
+          }
+        }, '失败')
+      ])
+    }
+    return h('el-tag', {
+      staticClass: 'o-tag u-readonly',
+      props: {
+        size: 'medium',
+        'disable-transitions': true,
+        type: ['primary', 'warning', 'success'][this.status]
+      }
+    }, ['等待', this.retryCount === 0 ? '同步中' : `重试(${this.retryCount})`, '成功'][this.status])
+  }
+}
+</script>

+ 1 - 1
src/views/device/power/components/DevicePowerTask.vue

@@ -27,7 +27,7 @@ export default {
   data () {
     return {
       status: 0,
-      retryCount: 1,
+      retryCount: 0,
       cardInfo: null
     }
   },

+ 125 - 5
src/views/device/power/index.vue

@@ -41,13 +41,77 @@
         <div class="has-bottom-padding--sm u-color--error dark u-font-size--sm">同步中请勿关闭弹窗,否则将停止同步</div>
       </template>
     </table-dialog>
+    <confirm-dialog
+      ref="dateRangeDialog"
+      title="定时任务日期范围选择"
+      @confirm="onConfirmDate"
+    >
+      <template #default>
+        <div class="c-grid-form auto u-align-self--center">
+          <div class="c-grid-form__label u-required">日期范围</div>
+          <el-date-picker
+            v-model="dateRange"
+            type="daterange"
+            range-separator="至"
+            value-format="yyyy-MM-dd"
+            :picker-options="pickerOptions"
+            :editable="false"
+            :clearable="false"
+          />
+          <div class="c-grid-form__label u-required">目标任务</div>
+          <el-checkbox-group
+            v-model="actions"
+            class="l-flex--row c-grid-form__option"
+            size="mini"
+            fill="#1c5cb0"
+            :min="1"
+          >
+            <el-checkbox-button
+              v-for="action in actionOptions"
+              :key="action.value"
+              :label="action.value"
+            >
+              {{ action.label }}
+            </el-checkbox-button>
+          </el-checkbox-group>
+          <div class="c-grid-form__label u-required">操作方式</div>
+          <el-radio-group
+            v-model="invoke"
+            class="l-flex--row c-grid-form__auto"
+          >
+            <el-radio
+              v-for="option in invokeOptions"
+              :key="option.value"
+              :label="option.value"
+            >
+              {{ option.label }}
+            </el-radio>
+          </el-radio-group>
+        </div>
+      </template>
+    </confirm-dialog>
+    <table-dialog
+      ref="decreaseTaskDialog"
+      title="定时任务同步"
+      :schema="decreaseSchema"
+    >
+      <template #tip>
+        <div class="has-bottom-padding--sm u-color--error dark u-font-size--sm">同步中请勿关闭弹窗,否则将停止同步</div>
+      </template>
+    </table-dialog>
   </wrapper>
 </template>
 
 <script>
 import { mapGetters } from 'vuex'
-import { PowerAction } from '@/constant'
-import { transformToCron } from '@/utils'
+import {
+  ONE_DAY,
+  PowerAction
+} from '@/constant'
+import {
+  parseTime,
+  transformToCron
+} from '@/utils'
 import {
   subscribe,
   unsubscribe
@@ -61,6 +125,7 @@ import {
 import { getDevicesWithPower } from '@/api/device'
 import DevicePower from './components/DevicePower.vue'
 import DevicePowerTask from './components/DevicePowerTask.vue'
+import DevicePowerDecreaseTask from './components/DevicePowerDecreaseTask.vue'
 
 export default {
   name: 'BatchPowerOperation',
@@ -74,10 +139,11 @@ export default {
           'row-click': this.onRowClick
         },
         buttons: [
-          { label: '批量开启电源', on: this.onOpen },
-          { label: '批量关闭电源', on: this.onClose },
+          { label: '一键开启', on: this.onOpen },
+          { label: '一键关闭', on: this.onClose },
           { label: '历史开关任务', on: this.onViewHistory },
-          { label: '批量定时任务', on: this.onAddTimingTask }
+          { label: '新增定时任务', on: this.onAddTimingTask },
+          { label: '启停定时任务', on: this.onDecreaseTimingTask }
         ],
         filters: [
           { key: 'name', type: 'search', placeholder: '设备名称' },
@@ -132,6 +198,42 @@ export default {
           { label: '状态', render: (device, h) => h(DevicePowerTask, { props: { device, tasks: this.$tasks } }), width: 120, align: 'center' },
           { prop: 'address', label: '地址' }
         ]
+      },
+      dateRange: [],
+      pickerOptions: {
+        disabledDate (date) {
+          return date <= Date.now() - ONE_DAY
+        }
+      },
+      actions: [PowerAction.OPEN],
+      actionOptions: [
+        { value: PowerAction.OPEN, label: '开启' },
+        { value: PowerAction.CLOSE, label: '关闭' }
+      ],
+      invoke: 0,
+      invokeOptions: [
+        { value: 0, label: '停用' },
+        { value: 1, label: '启用' },
+        { value: 2, label: '移除' }
+      ],
+      decreaseSchema: {
+        nonPagination: true,
+        props: {
+          size: 'small'
+        },
+        list: this.getSyncDevices,
+        cols: [
+          { prop: 'name', label: '设备名称', 'min-width': 60 },
+          { label: '状态', render: (device, h) => h(DevicePowerDecreaseTask, {
+            props: {
+              device,
+              'date-range': this.dateRange,
+              actions: this.actions,
+              invoke: this.invoke
+            }
+          }), width: 120, align: 'center' },
+          { prop: 'address', label: '地址' }
+        ]
       }
     }
   },
@@ -317,6 +419,24 @@ export default {
       done()
       this.$refs.syncTaskDialog.show()
     },
+    onDecreaseTimingTask () {
+      if (!this.$selectionItems?.length) {
+        this.$message({
+          type: 'warning',
+          message: '请先选择需要操作的设备'
+        })
+        return
+      }
+      const date = parseTime(new Date(), '{y}-{m}-{d}')
+      this.dateRange = [date, date]
+      this.actions = [PowerAction.OPEN, PowerAction.CLOSE]
+      this.invoke = 0
+      this.$refs.dateRangeDialog.show()
+    },
+    onConfirmDate (done) {
+      done()
+      this.$refs.decreaseTaskDialog.show()
+    },
     getSyncDevices () {
       return Promise.resolve({ data: Object.freeze([...this.$selectionItems]) })
     }