Quellcode durchsuchen

feat: ai regular audit

Casper Dai vor 3 Jahren
Ursprung
Commit
6f983d8bbe

+ 8 - 1
src/main.js

@@ -4,7 +4,14 @@ import Vue from 'vue'
 import 'element-ui/lib/theme-chalk/index.css'
 import './scss/index.scss'
 
-import Element from 'element-ui'
+import Element, { InputNumber } from 'element-ui'
+InputNumber.methods.handleInputChange = function (value) {
+  const newVal = value === '' ? this.min === -Infinity ? void 0 : this.min : Number(value)
+  if (!isNaN(newVal) || value === '') {
+    this.setCurrentValue(newVal)
+  }
+  this.userInput = null
+}
 
 import App from './App'
 import store from './store'

+ 11 - 4
src/router/index.js

@@ -268,11 +268,11 @@ export const asyncRoutes = [
         meta: { title: '组织管理' }
       },
       {
-        name: 'user',
-        path: 'user',
+        name: 'account',
+        path: 'account',
         component: () => import('@/views/realm/user/index'),
         access: [Access.MANAGE_TENANTS, Access.MANAGE_TENANT],
-        meta: { title: '用户管理' }
+        meta: { title: '账号管理' }
       },
       {
         name: 'device',
@@ -283,7 +283,7 @@ export const asyncRoutes = [
       },
       {
         name: 'device-assign',
-        path: 'assign',
+        path: 'device/assign',
         component: () => import('@/views/realm/assign/index'),
         access: [Access.MANAGE_TENANTS, Access.MANAGE_TENANT],
         meta: { title: '设备分配' }
@@ -294,6 +294,13 @@ export const asyncRoutes = [
         component: () => import('@/views/realm/ai-stock/index'),
         access: [Access.MANAGE_TENANTS, Access.MANAGE_TENANT],
         meta: { title: 'AI审核库存' }
+      },
+      {
+        name: 'ai-timing',
+        path: 'ai/timing',
+        component: () => import('@/views/realm/ai-timing/index'),
+        access: [Access.MANAGE_TENANTS, Access.MANAGE_TENANT],
+        meta: { title: 'AI抽帧检测' }
       }
     ]
   },

+ 25 - 0
src/views/realm/ai-timing/Group.vue

@@ -0,0 +1,25 @@
+<template>
+  <wrapper
+    fill
+    margin
+    padding
+    background
+  >
+    <group-timing-table :group="tenant" />
+  </wrapper>
+</template>
+
+<script>
+import { mapGetters } from 'vuex'
+import GroupTimingTable from './components/GroupTimingTable'
+
+export default {
+  name: 'AITimingGroup',
+  components: {
+    GroupTimingTable
+  },
+  computed: {
+    ...mapGetters(['tenant'])
+  }
+}
+</script>

+ 95 - 0
src/views/realm/ai-timing/Tenant.vue

@@ -0,0 +1,95 @@
+<template>
+  <wrapper
+    :vertical="false"
+    fill
+    margin
+    padding
+    background
+  >
+    <template v-if="groups">
+      <div class="c-tree-sidebar u-overflow-y--auto">
+        <el-tree
+          ref="groupTree"
+          :data="groups"
+          class="c-tree-sidebar__main"
+          node-key="path"
+          highlight-current
+          @node-click="onGroupTreeClick"
+        />
+      </div>
+      <group-timing-table
+        :group="group.path"
+        admin
+      />
+    </template>
+    <template v-else>
+      <div
+        v-loading="loading"
+        class="l-flex__auto l-flex--row center"
+      >
+        <template v-if="!loading">
+          <warning
+            v-if="error"
+            @click="getTreeData"
+          />
+          <div
+            v-else
+            class="u-bold"
+          >
+            暂无租户,请先添加租户
+          </div>
+        </template>
+      </div>
+    </template>
+  </wrapper>
+</template>
+
+<script>
+import { getTopGroups } from '@/api/user'
+import GroupTimingTable from './components/GroupTimingTable'
+
+export default {
+  name: 'AITimingTenant',
+  components: {
+    GroupTimingTable
+  },
+  data () {
+    return {
+      loading: true,
+      error: false,
+      groups: null,
+      group: null
+    }
+  },
+  created () {
+    this.getTreeData()
+  },
+  methods: {
+    getTreeData () {
+      this.loading = true
+      this.error = false
+      getTopGroups().then(
+        ({ data }) => {
+          if (data.length) {
+            this.groups = data
+            this.group = this.groups[0]
+            this.$nextTick(() => {
+              this.$refs.groupTree.setCurrentKey(this.group.path)
+            })
+          }
+        },
+        () => {
+          this.error = true
+        }
+      ).finally(() => {
+        this.loading = false
+      })
+    },
+    onGroupTreeClick (group) {
+      if (!this.group || this.group.id !== group.id) {
+        this.group = group
+      }
+    }
+  }
+}
+</script>

+ 90 - 0
src/views/realm/ai-timing/api.js

@@ -0,0 +1,90 @@
+import request from '@/utils/request'
+import {
+  add,
+  update,
+  send
+} from '@/api/base'
+
+export function getTenantConfig (tenant) {
+  return send({
+    url: '/device/deviceAuditConfig/tenantServiceConfig',
+    method: 'GET',
+    params: { tenant }
+  })
+}
+
+export function addTenantConfig (data) {
+  return add({
+    url: '/device/deviceAuditConfig/tenantServiceConfig',
+    method: 'POST',
+    data
+  })
+}
+
+export function updateTenantConfig (data) {
+  return update({
+    url: '/device/deviceAuditConfig/tenantServiceConfig',
+    method: 'PUT',
+    data
+  })
+}
+
+export function getTenantStratConfig (tenant) {
+  return send({
+    url: '/device/deviceAuditConfig/deviceAuditConfig',
+    method: 'GET',
+    params: { tenant }
+  })
+}
+
+export function addTenantStratConfig (data) {
+  return add({
+    url: '/device/deviceAuditConfig/deviceAuditConfig',
+    method: 'POST',
+    data
+  })
+}
+
+export function updateTenantStratConfig (data) {
+  return update({
+    url: '/device/deviceAuditConfig/deviceAuditConfig',
+    method: 'PUT',
+    data
+  })
+}
+
+export function getDeviceServiceConfig (deviceId) {
+  return send({
+    url: '/device/deviceAuditConfig/deviceServiceConfig',
+    method: 'GET',
+    params: { deviceId, service: 1 }
+  })
+}
+
+export function addDeviceServiceConfig (data) {
+  return add({
+    url: '/device/deviceAuditConfig/deviceServiceConfig',
+    method: 'POST',
+    data
+  })
+}
+
+export function updateDeviceServiceConfig (data) {
+  return update({
+    url: '/device/deviceAuditConfig/deviceServiceConfig',
+    method: 'PUT',
+    data
+  })
+}
+
+export function getHistory (query) {
+  const { pageNum: pageIndex, pageSize, ...params } = query
+  return request({
+    url: '/deviceAudit/picAudit/listByPage',
+    method: 'GET',
+    params: {
+      pageIndex, pageSize,
+      ...params
+    }
+  })
+}

+ 50 - 0
src/views/realm/ai-timing/components/DeviceServiceConfigDialog.vue

@@ -0,0 +1,50 @@
+<template>
+  <confirm-dialog
+    ref="configDialog"
+    title="设备服务配置"
+    @confirm="onSave"
+  >
+    <div class="c-grid-form mini u-align-self--center">
+      <span class="c-grid-form__label u-bold">开启:</span>
+      <div class="l-flex--row c-grid-form__option">
+        <el-switch
+          v-model="deviceServiceConfig.enabled"
+          active-color="#13ce66"
+          inactive-color="#ff4949"
+        />
+      </div>
+    </div>
+  </confirm-dialog>
+</template>
+
+<script>
+import {
+  getDeviceServiceConfig,
+  addDeviceServiceConfig,
+  updateDeviceServiceConfig
+} from '../api'
+
+export default {
+  name: 'DeviceServiceConfigDialog',
+  data () {
+    return {
+      deviceServiceConfig: {}
+    }
+  },
+  methods: {
+    show (deviceId) {
+      getDeviceServiceConfig(deviceId).then(({ data }) => {
+        this.deviceServiceConfig = data || {
+          deviceId,
+          service: 1,
+          enabled: false
+        }
+        this.$refs.configDialog.show()
+      })
+    },
+    onSave (done) {
+      (this.deviceServiceConfig.id ? updateDeviceServiceConfig : addDeviceServiceConfig)(this.deviceServiceConfig).then(done)
+    }
+  }
+}
+</script>

+ 187 - 0
src/views/realm/ai-timing/components/GroupTimingTable.vue

@@ -0,0 +1,187 @@
+<template>
+  <schema-table
+    ref="table"
+    :schema="schema"
+  >
+    <service-config-dialog ref="serviceConfigDialog" />
+    <strat-config-dialog ref="stratConfigDialog" />
+    <table-dialog
+      ref="historyDialog"
+      :title="historyTitle"
+      :schema="historySchema"
+    />
+    <device-service-config-dialog ref="deviceServiceConfigDialog" />
+  </schema-table>
+</template>
+
+<script>
+import { getDevicesByAdmin } from '@/api/device'
+import { getHistory } from '../api'
+import ServiceConfigDialog from './ServiceConfigDialog'
+import StratConfigDialog from './StratConfigDialog'
+import DeviceServiceConfigDialog from './DeviceServiceConfigDialog'
+
+export default {
+  name: 'AITimingTable',
+  components: {
+    ServiceConfigDialog,
+    StratConfigDialog,
+    DeviceServiceConfigDialog
+  },
+  props: {
+    group: {
+      type: String,
+      required: true
+    },
+    admin: {
+      type: [Boolean, String],
+      default: false
+    }
+  },
+  data () {
+    return {
+      schema: {
+        condition: { name: '' },
+        list: this.getDevices,
+        filters: [
+          { key: 'name', type: 'search', placeholder: '设备名称' }
+        ],
+        buttons: [
+          this.admin ? { label: '服务配置', on: this.onServiceConfig } : null,
+          { label: '策略配置', on: this.onStratConfig },
+          { label: '全部回采记录', on: this.onAllHistory }
+        ].filter(Boolean),
+        cols: [
+          { prop: 'name', label: '设备名称', 'min-width': 120 },
+          { prop: 'serialNumber', label: '序列号' },
+          { prop: 'mac', label: 'MAC' },
+          { type: 'invoke', width: 180, render: [
+            { label: '服务配置', on: this.onDeviceServiceConfig },
+            { label: '回采记录', on: this.onDeviceHistory }
+          ] }
+        ]
+      },
+      device: null
+    }
+  },
+  computed: {
+    historyTitle () {
+      return this.device ? `${this.device.name}的回采记录` : '全部回采记录'
+    },
+    historySchema () {
+      return {
+        condition: { auditState: void 0 },
+        list: this.getHistory,
+        transform: this.transform,
+        filters: [
+          { key: 'auditState', type: 'select', placeholder: '全部状态', options: [
+            { value: 7, label: '合规' },
+            { value: 2, label: '疑似' },
+            { value: 1, label: '不合规' }
+          ] }
+        ],
+        cols: [
+          { prop: 'file', label: '截图', type: 'asset' },
+          { prop: 'ai', label: 'AI审核', type: 'tag', width: 180 },
+          this.device ? null : { prop: 'deviceName', label: '设备名称' },
+          { prop: 'screenshotTime', label: '回采时间' }
+        ]
+      }
+    }
+  },
+  watch: {
+    group () {
+      this.$refs.table.pageTo(1)
+    }
+  },
+  methods: {
+    getDevices (params) {
+      return getDevicesByAdmin({
+        tenant: this.group,
+        ...params
+      })
+    },
+    getHistory (params) {
+      return getHistory({
+        tenant: this.group,
+        deviceId: this.device ? this.device.id : void 0,
+        ...params
+      })
+    },
+    transform ({ deviceName, screenshotTime, screenshotUrl, screenshotDeleted, auditState, auditMsg }) {
+      return {
+        file: screenshotUrl && !screenshotDeleted
+          ? { thumbnail: screenshotUrl }
+          : null,
+        ai: this.getAIState(auditState, auditMsg),
+        deviceName,
+        screenshotTime
+      }
+    },
+    getAIState (status, msg) {
+      switch (status) {
+        case 1:
+          return {
+            type: 'danger',
+            label: '不合规'
+          }
+        case 2:
+          return {
+            type: 'warning',
+            label: '疑似',
+            msg
+          }
+        case 3:
+          return {
+            type: 'info',
+            label: '审核失败',
+            msg
+          }
+        case 4:
+        case 5:
+        case 6:
+          return {
+            type: 'primmary',
+            label: '审核中'
+          }
+        case 7:
+          return {
+            type: 'success',
+            label: '通过'
+          }
+        case 8:
+          return {
+            type: 'info',
+            label: '无法审核',
+            msg
+          }
+        case 9:
+          return {
+            type: 'info',
+            label: '未开启',
+            msg
+          }
+        default:
+          return null
+      }
+    },
+    onServiceConfig () {
+      this.$refs.serviceConfigDialog.show(this.group)
+    },
+    onStratConfig () {
+      this.$refs.stratConfigDialog.show(this.group)
+    },
+    onAllHistory () {
+      this.device = null
+      this.$refs.historyDialog.show()
+    },
+    onDeviceServiceConfig ({ id }) {
+      this.$refs.deviceServiceConfigDialog.show(id)
+    },
+    onDeviceHistory ({ id, name }) {
+      this.device = { id, name }
+      this.$refs.historyDialog.show()
+    }
+  }
+}
+</script>

+ 50 - 0
src/views/realm/ai-timing/components/ServiceConfigDialog.vue

@@ -0,0 +1,50 @@
+<template>
+  <confirm-dialog
+    ref="configDialog"
+    title="服务配置"
+    @confirm="onSave"
+  >
+    <div class="c-grid-form mini u-align-self--center">
+      <span class="c-grid-form__label u-bold">开启:</span>
+      <div class="l-flex--row c-grid-form__option">
+        <el-switch
+          v-model="tenantConfig.enabled"
+          active-color="#13ce66"
+          inactive-color="#ff4949"
+        />
+      </div>
+    </div>
+  </confirm-dialog>
+</template>
+
+<script>
+import {
+  getTenantConfig,
+  addTenantConfig,
+  updateTenantConfig
+} from '../api'
+
+export default {
+  name: 'ServiceConfigDialog',
+  data () {
+    return {
+      tenantConfig: {}
+    }
+  },
+  methods: {
+    show (tenant) {
+      getTenantConfig(tenant).then(({ data }) => {
+        this.tenantConfig = data || {
+          tenant,
+          enabled: false,
+          collectDevice: 1
+        }
+        this.$refs.configDialog.show()
+      })
+    },
+    onSave (done) {
+      (this.tenantConfig.id ? updateTenantConfig : addTenantConfig)(this.tenantConfig).then(done)
+    }
+  }
+}
+</script>

+ 116 - 0
src/views/realm/ai-timing/components/StratConfigDialog.vue

@@ -0,0 +1,116 @@
+<template>
+  <confirm-dialog
+    ref="configDialog"
+    title="策略配置"
+    @confirm="onSave"
+  >
+    <div class="c-grid-form mini u-align-self--center">
+      <span class="c-grid-form__label u-bold">开启:</span>
+      <div class="l-flex--row c-grid-form__option">
+        <el-switch
+          v-model="tenantStratConfig.enabled"
+          active-color="#13ce66"
+          inactive-color="#ff4949"
+        />
+      </div>
+      <span class="c-grid-form__label">抽帧间隔:</span>
+      <div class="c-grid-form__option">
+        <el-input-number
+          v-model="tenantStratConfig.offset"
+          :min="10"
+          :max="3600"
+          step-strictly
+        />
+        秒(10~3600)
+      </div>
+      <span class="c-grid-form__label required">开始时间:</span>
+      <el-time-picker
+        v-model="tenantStratConfig.startTime"
+        value-format="HH:mm:ss"
+        :clearable="false"
+      />
+      <span class="c-grid-form__label required">结束时间:</span>
+      <el-time-picker
+        v-model="tenantStratConfig.endTime"
+        value-format="HH:mm:ss"
+        :clearable="false"
+      />
+      <span class="c-grid-form__label">图片保存:</span>
+      <div
+        class="l-flex--row c-grid-form__option c-grid-form__info"
+        data-info="开启将保存至多1000张合格图片"
+      >
+        <el-switch
+          v-model="tenantStratConfig.storePic"
+          active-color="#13ce66"
+          inactive-color="#ff4949"
+        />
+      </div>
+      <span class="c-grid-form__label">库存阈值:</span>
+      <div
+        class="l-flex--row c-grid-form__option c-grid-form__info"
+        data-info="库存低于阈值后将关闭抽帧检测"
+      >
+        <el-input-number
+          v-model="tenantStratConfig.creditThreshold"
+          :min="0"
+          :max="999999"
+          step-strictly
+        />
+      </div>
+    </div>
+  </confirm-dialog>
+</template>
+
+<script>
+import {
+  getTenantStratConfig,
+  addTenantStratConfig,
+  updateTenantStratConfig
+} from '../api'
+
+export default {
+  name: 'StratConfigDialog',
+  data () {
+    return {
+      tenantStratConfig: {}
+    }
+  },
+  methods: {
+    show (tenant) {
+      getTenantStratConfig(tenant).then(({ data }) => {
+        this.tenantStratConfig = data || {
+          tenant,
+          enabled: false,
+          offset: 10,
+          startTime: '08:00:00',
+          endTime: '23:59:59',
+          storePic: true,
+          suspectedStrategy: 0,
+          nonComplianceStrategy: 2,
+          creditThreshold: 0
+        }
+        this.$refs.configDialog.show()
+      })
+    },
+    onSave (done) {
+      const { startTime, endTime } = this.tenantStratConfig
+      if (!startTime) {
+        this.$message({
+          type: 'warning',
+          message: '请选择开始时间'
+        })
+        return
+      }
+      if (!endTime) {
+        this.$message({
+          type: 'warning',
+          message: '请选择结束时间'
+        })
+        return
+      }
+      (this.tenantStratConfig.id ? updateTenantStratConfig : addTenantStratConfig)(this.tenantStratConfig).then(done)
+    }
+  }
+}
+</script>

+ 19 - 0
src/views/realm/ai-timing/index.vue

@@ -0,0 +1,19 @@
+<script>
+import { mapGetters } from 'vuex'
+import Tenant from './Tenant'
+import Group from './Group'
+
+export default {
+  name: 'AITiming',
+  components: {
+    Tenant,
+    Group
+  },
+  computed: {
+    ...mapGetters(['isSuperAdmin'])
+  },
+  render (h) {
+    return h(this.isSuperAdmin ? 'Tenant' : 'Group')
+  }
+}
+</script>