Casper Dai пре 3 година
родитељ
комит
e1b00b7c07

+ 100 - 0
src/api/external.js

@@ -198,3 +198,103 @@ export function getCameras (query) {
     params
   })
 }
+
+export function getGateways (query) {
+  const { pageNum: pageIndex, pageSize, ...params } = query
+  return request({
+    url: '/device/thirdPartyGateway/list',
+    method: 'GET',
+    params: {
+      pageIndex, pageSize,
+      ...params
+    }
+  })
+}
+
+export function addGateway (data) {
+  return add({
+    url: '/device/thirdPartyGateway',
+    method: 'POST',
+    data
+  })
+}
+
+export function updateGateway (data) {
+  return update({
+    url: '/device/thirdPartyGateway',
+    method: 'PUT',
+    data
+  })
+}
+
+export function deleteGateway ({ id, name }) {
+  return del({
+    url: `/device/thirdPartyGateway/${id}`,
+    method: 'DELETE'
+  }, name)
+}
+
+export function getGateway (deviceId) {
+  return request({
+    url: `/device/bind/thirdPartyGateway/${deviceId}`,
+    method: 'GET'
+  }).then(({ data }) => data?.[0])
+}
+
+export function bindGateway (deviceId, thirdPartyDeviceId) {
+  return bind(deviceId, ThirdPartyDevice.GATEWAY, thirdPartyDeviceId)
+}
+
+export function plcCommand (deviceId, status) {
+  return messageSend({
+    url: '/device/thirdplc/command',
+    method: 'POST',
+    data: { deviceId, status }
+  }, '触发')
+}
+
+export function getPLCs (query) {
+  const { pageNum: pageIndex, pageSize, ...params } = query
+  return request({
+    url: '/device/thirdplc/pagequery',
+    method: 'POST',
+    data: {
+      pageIndex, pageSize,
+      ...params
+    }
+  })
+}
+
+export function addPLC (data) {
+  return add({
+    url: '/device/thirdplc/add',
+    method: 'POST',
+    data
+  })
+}
+
+export function updatePLC (data) {
+  return update({
+    url: '/device/thirdplc/modify',
+    method: 'PUT',
+    data
+  })
+}
+
+export function deletePLC ({ id, name }) {
+  return del({
+    url: `/device/thirdplc/delete/${id}`,
+    method: 'DELETE'
+  }, name)
+}
+
+export function bindPLC (deviceId, thirdPartyDeviceId) {
+  return bind(deviceId, ThirdPartyDevice.PLC, thirdPartyDeviceId)
+}
+
+export function getBoundPLCs (deviceId) {
+  return request({
+    url: `/device/bind/thirdPartyPlc/${deviceId}`,
+    method: 'GET'
+  }).then(({ data }) => data)
+}

+ 2 - 1
src/constant.js

@@ -64,7 +64,8 @@ export const ThirdPartyDevice = {
   SENDING_CARD: 2,
   SCREEN: 3,
   LED_CAMERA: 4,
-  TRAFFIC_CAMERA: 5
+  TRAFFIC_CAMERA: 5,
+  PLC: 6
 }
 
 export const Transmitter = {

+ 7 - 0
src/router/index.js

@@ -217,6 +217,13 @@ export const asyncRoutes = [
         component: () => import('@/views/external/transmitter/index'),
         meta: { title: '发送控制设备' }
       },
+      {
+        path: 'gateway',
+        name: 'gateway',
+        access: Access.MANAGE_TENANTS,
+        component: () => import('@/views/external/gateway/index'),
+        meta: { title: '网关' }
+      },
       {
         path: 'camera',
         name: 'camera',

+ 56 - 42
src/views/device/detail/components/DeviceInvoke/ScreenSwitch.vue

@@ -2,65 +2,79 @@
   <div class="l-flex--col center has-border radius has-padding">
     <i
       class="o-icon has-bg u-pointer"
-      @click="invoke"
+      @click="onInvoke"
     />
     <div class="has-padding u-color--black u-bold">开关大屏</div>
     <el-dialog
       :visible.sync="show"
       custom-class="c-dialog"
       title="开关大屏"
-      :before-close="onCloseDialog"
     >
-      <!-- <tabs
-        :items="tabs"
-        :active.sync="active"
-      /> -->
-      <template v-if="show">
-        <template v-if="isImmediate">
-          <div class="l-flex__fill l-flex--col jcenter center has-bottom-padding">
-            <div>
-              <button
-                class="o-button c-sibling-item"
-                @click="onSwitch(true)"
-              >
-                即刻开机
-              </button>
-              <button
-                class="o-button c-sibling-item far"
-                @click="onSwitch(false)"
-              >
-                即刻关机
-              </button>
-            </div>
-            <div class="has-padding u-color--info">【实时控制】会立即触发</div>
-          </div>
-        </template>
-        <schema-table
-          v-else
-          ref="table"
-          :schema="schema"
-          :proxy.sync="currOptions"
-        />
-      </template>
+      <div
+        v-if="show"
+        class="l-flex__fill l-flex--col jcenter center has-bottom-padding"
+      >
+        <div>
+          <button
+            class="o-button c-sibling-item"
+            @click="onSwitch(true)"
+          >
+            即刻开机
+          </button>
+          <button
+            class="o-button c-sibling-item far"
+            @click="onSwitch(false)"
+          >
+            即刻关机
+          </button>
+        </div>
+      </div>
     </el-dialog>
-    <task-dialog
-      ref="editDialog"
-      :title="dialogTitle"
-      @confirm="onSave"
-    />
   </div>
 </template>
 
 <script>
-import switchTaskMixin from './mixins/switch-task'
+import {
+  getBoundPLCs,
+  plcCommand
+} from '@/api/external'
 
 export default {
   name: 'ScreenSwitch',
-  mixins: [switchTaskMixin],
+  props: {
+    device: {
+      type: Object,
+      required: true
+    }
+  },
   data () {
     return {
-      openFunctionKey: 'bootScreen',
-      closeFunctionKey: 'shutdownScreen'
+      show: false
+    }
+  },
+  methods: {
+    onInvoke () {
+      const loading = this.$showLoading()
+      getBoundPLCs(this.device.id).finally(() => {
+        this.$closeLoading(loading)
+      }).then(data => {
+        if (data.length) {
+          this.show = true
+        } else {
+          this.$message({
+            type: 'warning',
+            message: '暂未绑定PLC,请联系管理员'
+          })
+        }
+      })
+    },
+    onSwitch (open) {
+      this.$confirm(
+        `立即${open ? '开机' : '关机'}?`,
+        { type: 'warning' }
+      ).then(() => {
+        plcCommand(this.device.id, open ? 1 : 0)
+      })
     }
   }
 }

+ 268 - 0
src/views/device/detail/components/external/Gateway/index.vue

@@ -0,0 +1,268 @@
+<template>
+  <div
+    v-loading="loading"
+    class="l-flex--col"
+  >
+    <template v-if="!loading">
+      <warning
+        v-if="error"
+        @click="getGateway"
+      />
+      <template v-else>
+        <template v-if="gateway">
+          <div class="c-sibling-item--v c-info">
+            <div class="l-flex--row has-bottom-padding u-bold">
+              <span class="c-sibling-item">网关信息</span>
+              <span
+                v-if="isSuperAdmin"
+                class="c-sibling-item c-info__edit u-pointer"
+                @click="onUnbindGateway"
+              >
+                <i class="el-icon-edit" />
+                解绑
+              </span>
+            </div>
+            <div class="l-flex--row c-info__block">
+              <div class="l-flex--row l-flex__fill c-info__item">
+                <div class="l-flex__none c-info__title">名称</div>
+                <auto-text
+                  class="l-flex__fill c-info__value"
+                  :text="gateway.name"
+                />
+              </div>
+              <div class="l-flex--row l-flex__fill c-info__item">
+                <div class="l-flex__none c-info__title">地址</div>
+                <div class="l-flex__fill c-info__value">{{ gateway.ip }}</div>
+              </div>
+            </div>
+          </div>
+          <div class="c-sibling-item--v far c-info">
+            <div class="l-flex--row has-bottom-padding u-bold">
+              <span class="c-sibling-item">PLC信息</span>
+              <span
+                v-if="isSuperAdmin"
+                class="c-sibling-item c-info__edit u-pointer"
+                @click="onBindPLC"
+              >
+                <i class="el-icon-edit" />
+                绑定
+              </span>
+            </div>
+            <div
+              v-if="plcs.length === 0"
+              class="u-color--info"
+            >暂未绑定</div>
+            <div
+              v-for="plc in plcs"
+              :key="plc.id"
+              class="l-flex--row c-info__block"
+            >
+              <div class="l-flex--row l-flex__fill c-info__item">
+                <div class="l-flex__none c-info__title">
+                  <span
+                    class="c-info__edit u-pointer"
+                    @click="onUnbindPLC(plc)"
+                  >
+                    解绑
+                  </span>
+                  名称
+                </div>
+                <auto-text
+                  class="l-flex__fill c-info__value"
+                  :text="plc.thirdPartyDevice.name"
+                />
+              </div>
+              <div class="l-flex--row l-flex__fill c-info__item">
+                <div class="l-flex__none c-info__title">地址</div>
+                <div class="l-flex__fill c-info__value">{{ plc.thirdPartyDevice.identifier }}</div>
+              </div>
+            </div>
+          </div>
+        </template>
+        <template v-else>
+          <div class="l-flex__fill l-flex--row center u-color--info">
+            <div class="l-flex--col center">
+              <div class="has-padding">未绑定网关,请联系管理员</div>
+              <button
+                v-if="isSuperAdmin"
+                class="o-button"
+                @click="onBindGateway"
+              >
+                绑定网关
+              </button>
+            </div>
+          </div>
+        </template>
+      </template>
+    </template>
+    <table-dialog
+      ref="chooseDialog"
+      :title="dialogTitle"
+      :schema="schema"
+      @choosen="onChoose"
+    />
+  </div>
+</template>
+
+<script>
+import { mapGetters } from 'vuex'
+import {
+  getGateway,
+  getGateways,
+  bindGateway,
+  getBoundPLCs,
+  getPLCs,
+  bindPLC,
+  unbind
+} from '@/api/external'
+
+export default {
+  name: 'Gateway',
+  props: {
+    device: {
+      type: Object,
+      required: true
+    }
+  },
+  data () {
+    return {
+      loading: false,
+      error: false,
+      info: null,
+      deviceType: 'GATEWAY'
+    }
+  },
+  computed: {
+    ...mapGetters(['isSuperAdmin']),
+    dialogTitle () {
+      switch (this.deviceType) {
+        case 'PLC':
+          return 'PLC选择'
+        default:
+          return '网关选择'
+      }
+    },
+    schema () {
+      switch (this.deviceType) {
+        case 'PLC':
+          return {
+            list: this.getPLCs,
+            cols: [
+              { prop: 'name', label: '名称' },
+              { prop: 'identifier', label: '地址' },
+              { prop: 'remark', label: '备注' }
+            ]
+          }
+        default:
+          return {
+            list: getGateways,
+            cols: [
+              { prop: 'name', label: '名称' },
+              { prop: 'ip', label: '地址' }
+            ]
+          }
+      }
+    },
+    gateway () {
+      return this.info?.gateway?.thirdPartyDevice
+    },
+    plcs () {
+      return this.info?.plcs || []
+    }
+  },
+  activated () {
+    !this.info && this.getInfo()
+  },
+  methods: {
+    getInfo (type) {
+      if (this.loading) {
+        return
+      }
+      this.loading = true
+      this.error = false
+
+      let arr
+      if (this.info && type) {
+        switch (type) {
+          case 'PLC':
+            arr = [Promise.resolve(this.info.gateway), this.getBoundPLCs()]
+            break
+          default:
+            arr = [this.getGateway(), Promise.resolve(this.info.plcs)]
+            break
+        }
+      } else {
+        arr = [this.getGateway(), this.getBoundPLCs()]
+      }
+
+      this.info = null
+      Promise.all(arr).then(
+        data => {
+          this.info = {
+            gateway: data[0],
+            plcs: data[1]
+          }
+        },
+        () => {
+          this.error = false
+        }
+      ).finally(() => {
+        this.loading = false
+      })
+    },
+    getGateway () {
+      return getGateway(this.device.id)
+    },
+    getBoundPLCs () {
+      return getBoundPLCs(this.device.id)
+    },
+    onChoose ({ value, done }) {
+      (this.deviceType === 'PLC' ? bindPLC : bindGateway)(this.device.id, value.id).then(() => {
+        done()
+        this.getInfo(this.deviceType)
+      })
+    },
+    onBindGateway () {
+      this.deviceType = 'GATEWAY'
+      this.$refs.chooseDialog.show()
+    },
+    onUnbindGateway () {
+      if (this.plcs.length) {
+        this.$message({
+          type: 'warning',
+          message: '请先解绑所有PLC'
+        })
+        return
+      }
+      this.$confirm(
+        '解绑网关?',
+        { type: 'warning' }
+      ).then(() => {
+        unbind(this.info.gateway.id).then(() => {
+          this.info = null
+        })
+      })
+    },
+    getPLCs (params) {
+      return getPLCs({
+        gatewayId: this.gateway.id,
+        ...params
+      })
+    },
+    onBindPLC () {
+      this.deviceType = 'PLC'
+      this.$refs.chooseDialog.show()
+    },
+    onUnbindPLC (plc) {
+      this.$confirm(
+        `解绑PLC ${plc.thirdPartyDevice.name}?`,
+        { type: 'warning' }
+      ).then(() => {
+        unbind(plc.id).then(() => {
+          this.plcs.splice(this.plcs.indexOf(plc), 1)
+        })
+      })
+    }
+  }
+}
+</script>

+ 7 - 4
src/views/device/detail/index.vue

@@ -75,6 +75,7 @@ import Sensors from './components/external/Sensors'
 import Transmitter from './components/external/Transmitter'
 import ReceivingCard from './components/external/ReceivingCard'
 import Camera from './components/external/Camera'
+import Gateway from './components/external/Gateway'
 
 export default {
   name: 'DeviceDetail',
@@ -87,7 +88,8 @@ export default {
     Sensors,
     Transmitter,
     ReceivingCard,
-    Camera
+    Camera,
+    Gateway
   },
   data () {
     return {
@@ -103,11 +105,12 @@ export default {
         { key: 'DeviceAlarm', label: '设备告警' },
         this.accessSet.has(Access.MANAGE_DEVICE) ? { key: 'DeviceInvoke', label: '设备操控' } : null,
         __SENSOR__ ? { key: 'Sensors', label: '传感器' } : null,
+        __STAGING__ ? { key: 'LinkState', label: '全链路监测' } : null,
         __STAGING__ ? { key: 'Transmitter', label: '发送控制设备' } : null,
         __STAGING__ ? { key: 'ReceivingCard', label: '接收卡' } : null,
-        __STAGING__ ? { key: 'LinkState', label: '全链路监测状态' } : null,
-        __STAGING__ ? { key: 'Camera', label: '摄像头' } : null
-      ].filter(val => val)
+        __STAGING__ ? { key: 'Camera', label: '摄像头' } : null,
+        __STAGING__ ? { key: 'Gateway', label: '网关' } : null
+      ].filter(Boolean)
     }
   },
   computed: {

+ 143 - 0
src/views/external/gateway/PLC.vue

@@ -0,0 +1,143 @@
+<template>
+  <schema-table
+    ref="table"
+    :schema="schema"
+  >
+    <confirm-dialog
+      ref="editDialog"
+      :title="dialogTitle"
+      :append-to-body="true"
+      @confirm="onConfirm"
+    >
+      <div class="c-grid-form u-align-self--center">
+        <span class="c-grid-form__label required">名称:</span>
+        <el-input
+          v-model.trim="plc.name"
+          placeholder="最多50个字符"
+          maxlength="50"
+          clearable
+        />
+        <span class="c-grid-form__label required">地址:</span>
+        <el-input
+          v-model.trim="plc.identifier"
+          placeholder="最多50个字符"
+          maxlength="50"
+          clearable
+        />
+        <span class="c-grid-form__label">备注:</span>
+        <el-input
+          v-model.trim="plc.remark"
+          type="textarea"
+          maxlength="100"
+          :rows="3"
+          show-word-limit
+        />
+      </div>
+    </confirm-dialog>
+  </schema-table>
+</template>
+
+<script>
+import {
+  getPLCs,
+  addPLC,
+  updatePLC,
+  deletePLC
+} from '@/api/external'
+
+export default {
+  name: 'PLCList',
+  props: {
+    gateway: {
+      type: Object,
+      required: true
+    }
+  },
+  data () {
+    return {
+      plc: {},
+      schema: {
+        list: this.getPLCs,
+        buttons: [
+          { type: 'add', on: this.onAdd }
+        ],
+        cols: [
+          { prop: 'name', label: '名称' },
+          { prop: 'identifier', label: '地址' },
+          { prop: 'remark', label: '备注' },
+          { type: 'invoke', render: [
+            { label: '编辑', on: this.onEdit },
+            { label: '删除', on: this.onDel }
+          ] }
+        ]
+      }
+    }
+  },
+  computed: {
+    dialogTitle () {
+      return this.plc.id ? '编辑PLC' : '新增PLC'
+    }
+  },
+  methods: {
+    getPLCs (params) {
+      return getPLCs({
+        gatewayId: this.gateway.id,
+        ...params
+      })
+    },
+    onAdd () {
+      this.plc = {
+        name: '',
+        identifier: '',
+        remark: ''
+      }
+      this.$refs.editDialog.show()
+    },
+    onEdit ({ id, name, identifier, remark }) {
+      this.plc = { id, name, identifier, remark }
+      this.$refs.editDialog.show()
+    },
+    onConfirm (done) {
+      if (!this.plc.name) {
+        this.$message({
+          type: 'warning',
+          message: '请填写PLC名称'
+        })
+        return
+      }
+      if (!this.plc.identifier) {
+        this.$message({
+          type: 'warning',
+          message: '请填写PLC标识'
+        })
+        return
+      }
+      if (this.plc.id) {
+        this.onConfirmEdit(this.plc, done)
+      } else {
+        this.onConfirmAdd(this.plc, done)
+      }
+    },
+    onConfirmAdd (plc, done) {
+      addPLC({
+        gatewayId: this.gateway.id,
+        ...plc
+      }).then(() => {
+        done()
+        this.$refs.table.resetCondition()
+      })
+    },
+    onConfirmEdit (plc, done) {
+      updatePLC(plc).then(() => {
+        done()
+        this.$refs.table.pageTo()
+      })
+    },
+    onDel (plc) {
+      deletePLC(plc).then(() => {
+        this.$refs.table.decrease(1)
+      })
+    }
+  }
+}
+</script>

+ 157 - 0
src/views/external/gateway/index.vue

@@ -0,0 +1,157 @@
+<template>
+  <wrapper
+    fill
+    margin
+    padding
+    background
+  >
+    <schema-table
+      ref="table"
+      :schema="schema"
+    />
+    <confirm-dialog
+      ref="editDialog"
+      :title="dialogTitle"
+      @confirm="onConfirm"
+    >
+      <div class="c-grid-form u-align-self--center">
+        <span class="c-grid-form__label required">名称:</span>
+        <el-input
+          v-model.trim="gateway.name"
+          placeholder="最多50个字符"
+          maxlength="50"
+          clearable
+        />
+        <span class="c-grid-form__label required">地址:</span>
+        <el-input
+          v-model.trim="gateway.ip"
+          placeholder="最多50个字符"
+          maxlength="50"
+          clearable
+        />
+        <span class="c-grid-form__label">备注:</span>
+        <el-input
+          v-model.trim="gateway.remark"
+          type="textarea"
+          maxlength="100"
+          :rows="3"
+          show-word-limit
+        />
+      </div>
+    </confirm-dialog>
+    <el-dialog
+      :visible.sync="showPlc"
+      title="PLC"
+      custom-class="c-dialog"
+      :close-on-click-modal="false"
+    >
+      <plc
+        v-if="showPlc"
+        :gateway="gateway"
+      />
+    </el-dialog>
+  </wrapper>
+</template>
+
+<script>
+import {
+  getGateways,
+  addGateway,
+  updateGateway,
+  deleteGateway
+} from '@/api/external'
+import PLC from './PLC'
+
+export default {
+  name: 'GatewayList',
+  components: {
+    plc: PLC
+  },
+  data () {
+    return {
+      gateway: {},
+      schema: {
+        list: getGateways,
+        buttons: [
+          { type: 'add', on: this.onAdd }
+        ],
+        cols: [
+          { prop: 'name', label: '名称' },
+          { prop: 'ip', label: '地址' },
+          { prop: 'remark', label: '备注' },
+          { type: 'invoke', width: 160, render: [
+            { label: '编辑', on: this.onEdit },
+            { label: 'PLC', on: this.onEditPLC },
+            { label: '删除', on: this.onDel }
+          ] }
+        ]
+      },
+      showPlc: false
+    }
+  },
+  computed: {
+    dialogTitle () {
+      return this.gateway.id ? '编辑网关' : '新增网关'
+    }
+  },
+  methods: {
+    onAdd () {
+      this.gateway = {
+        name: '',
+        ip: '',
+        mac: '',
+        account: '',
+        password: '',
+        remark: ''
+      }
+      this.$refs.editDialog.show()
+    },
+    onEdit ({ id, name, ip, remark }) {
+      this.gateway = { id, name, ip, remark }
+      this.$refs.editDialog.show()
+    },
+    onConfirm (done) {
+      if (!this.gateway.name) {
+        this.$message({
+          type: 'warning',
+          message: '请填写网关名称'
+        })
+        return
+      }
+      if (!this.gateway.ip) {
+        this.$message({
+          type: 'warning',
+          message: '请填写网关地址'
+        })
+        return
+      }
+      if (this.gateway.id) {
+        this.onConfirmEdit(this.gateway, done)
+      } else {
+        this.onConfirmAdd(this.gateway, done)
+      }
+    },
+    onConfirmAdd (gateway, done) {
+      addGateway(gateway).then(() => {
+        done()
+        this.$refs.table.resetCondition()
+      })
+    },
+    onConfirmEdit (gateway, done) {
+      updateGateway(gateway).then(() => {
+        done()
+        this.$refs.table.pageTo()
+      })
+    },
+    onDel (gateway) {
+      deleteGateway(gateway).then(() => {
+        this.$refs.table.decrease(1)
+      })
+    },
+    onEditPLC (gateway) {
+      this.gateway = gateway
+      this.showPlc = true
+    }
+  }
+}
+</script>