Browse Source

feat:更新ai任务界面&视频监控界面

xiayu3 4 years ago
parent
commit
e732ae3768

+ 23 - 43
security-protection-platform/.aid/mock/videoSurveillance.js

@ -9,25 +9,18 @@ let totalData = Mock.mock({
9 9
      'org|1':['河东风场', '河西风场', '河南风场', '河北风场', '邵阳风场']// 风场名称
10 10
    }],
11 11
  },
12
  'gateFieldData':{
13
    'success':true,
14
    'data|100-150': [{
15
    'videoId|+1':012013, //视频id
16
	  'url':'/static/images/demo.png', // 图片url 临时
17
    'videoDetail|1':['东门(安全帽识别、人脸识别)','西门(安全帽识别、人脸识别))','配电室(安全帽识别、人脸识别、抽烟识别)','南门(车辆识别)','集控室(人脸识别、玩手机识别)','1#电梯(人员摔倒识别 电梯门闭合识别)'], // 视频内容
18
    'id|+1':'101100', // 风场id
19
    'options1|+1':{ content: '识别记录', placement: 'top'},
20
    'options2|+1':{ content: '视频回放', placement: 'top'},
21
    'options3|+1':{ content: '全屏', placement: 'top'}
22
    }]
23
  },
24
  'centerControlRoomData':{
12
  'videoSurveillanceData':{
25 13
    'success':true,
14
    'departments|2':[{ //风场具体位置信息
15
      'depName|+1':['风场大门','集控室'],
16
      'depId|+1':0001
17
    }],
26 18
    'data|150': [{
27 19
    'videoId|+1':012013, //视频id
28 20
	  'url':'/static/images/demo.png', // 图片url 临时
29 21
    'videoDetail|1':['东门(安全帽识别、人脸识别)','西门(安全帽识别、人脸识别))','配电室(安全帽识别、人脸识别、抽烟识别)','南门(车辆识别)','集控室(人脸识别、玩手机识别)','1#电梯(人员摔倒识别 电梯门闭合识别)'], // 视频内容
30 22
    'id|+1':'101100', // 风场id
23
    'departmentId|1':[1,2], // 所属位置id
31 24
    'options1|+1':{ content: '识别记录', placement: 'top'},
32 25
    'options2|+1':{ content: '视频回放', placement: 'top'},
33 26
    'options3|+1':{ content: '全屏', placement: 'top'}
@ -45,25 +38,31 @@ let totalData = Mock.mock({
45 38
  },
46 39
})
47 40
module.exports = [
48
  {
49
  url: '/videoSurveillance/getOrganizationData',
50
  method: 'get',
51
  type: 'func',
52
  response: req => {
53
    return totalData.organizationData
54
  }
55
},
56 41
{
57
  url: '/videoSurveillance/getGateFieldData',
42
  url: '/videoSurveillance/getVideoSurveillanceData',
58 43
  method: 'get',
59 44
  type: 'func',
60 45
  response: req => {
61 46
    const url = parseQueryString(req.url)
62 47
    const current = parseInt(url.page)
63 48
    const limit = parseInt(url.limit)
64
    const total = totalData.gateFieldData.data.length
49
    let total = 0
50
    const department = totalData.videoSurveillanceData.departments.map(item=>{
51
      total = 0
52
      for(let i=0;i<totalData.videoSurveillanceData.data.length;i++){
53
          if( totalData.videoSurveillanceData.data[i].departmentId===item.depId){
54
            total+=1
55
          }
56
      }
57
      return {
58
      depName:item.depName,
59
      depId:item.depId,
60
      total:total
61
      }
62
    })
65 63
    let data = {
66
        data:totalData.gateFieldData.data.slice((current-1)*limit,(current)*limit),
64
        data:totalData.videoSurveillanceData.data.slice((current-1)*limit,(current)*limit),
65
        departments: department,
67 66
        page:current,
68 67
        success:true,
69 68
        limit:limit,
@ -73,25 +72,6 @@ module.exports = [
73 72
    }
74 73
},
75 74
{
76
  url: '/videoSurveillance/getCenterRoomData',
77
  method: 'get',
78
  type: 'func',
79
  response: req => {
80
    const url = parseQueryString(req.url)
81
    const current = parseInt(url.page)
82
    const limit = parseInt(url.limit)
83
    const total = totalData.gateFieldData.data.length
84
    let data = {
85
        data:totalData.gateFieldData.data.slice((current-1)*limit,(current)*limit),
86
        page:current,
87
        success:true,
88
        limit:limit,
89
        total:total
90
    }
91
  return data
92
  }
93
},
94
{
95 75
  url: '/videoSurveillance/getDistinguishData',
96 76
  method: 'get',
97 77
  type: 'func',

+ 2 - 14
security-protection-platform/src/api/videoSurveillance/index.js

@ -2,25 +2,13 @@ import http from '@/http'
2 2
const { $http } = http
3 3
4 4
const api = {
5
  // 获取视频监控风场列表数据
6
  getOrganizationData() {
7
    return $http.get('/videoSurveillance/getOrganizationData')
8
  },
9 5
  // 获取风场大门表格数据
10
  getGateFieldData(params) {
11
    return $http.get('/videoSurveillance/getGateFieldData', params)
12
  },
13
  // 获取集控室表格数据
14
  getCenterRoomData(params) {
15
    return $http.get('/videoSurveillance/getCenterRoomData', params)
6
  getVideoSurveillanceData(params) {
7
    return $http.get('/videoSurveillance/getVideoSurveillanceData', params)
16 8
  },
17 9
  // 获取识别记录数据
18 10
  getDistinguishData(params) {
19 11
    return $http.get('/videoSurveillance/getDistinguishData', params)
20
  },
21
  // 获取考勤月报详情数据
22
  getAttendanceDetail(params) {
23
    return $http.get('/attendance/getMonthReportDetail', params)
24 12
  }
25 13
}
26 14
export default api

+ 330 - 0
security-protection-platform/src/modules/system/assignment/index.vue

@ -0,0 +1,330 @@
1
<template>
2
  <div class="device-manage-page">
3
    <t-card class="page-card">
4
      <div class="select-confidtion-box row">
5
        <div class="col-3">任务名称:
6
          <t-select v-model="taskName" width="200px">
7
            <t-option v-for="item in taskNameList" :key="item.id" :value="item.id">{{ item.name }}</t-option>
8
          </t-select>
9
        </div>
10
        <div class="col-3">匹配模型:
11
          <t-select v-model="matchingType" width="200px">
12
            <t-option v-for="item in matchingTypeList" :key="item.id" :value="item.id">{{ item.name }}</t-option>
13
          </t-select>
14
        </div>
15
        <div class="col-3">任务状态:
16
          <t-select v-model="taskStatus" width="200px">
17
            <t-option v-for="item in taskStatusList" :key="item.id" :value="item.id">{{ item.name }}</t-option>
18
          </t-select>
19
        </div>
20
        <div class="col-2 offset-1 select-button-box" align="end">
21
          <t-button color="primary" @click="selectHandle">查询</t-button>
22
          <t-button @click="resetData">重置</t-button>
23
        </div>
24
      </div>
25
      <div class="function-button-box">
26
        <t-button color="primary">
27
          <t-icon icon="plus-circle-outline" size="16"></t-icon>新增
28
        </t-button>
29
        <t-button @click="multDeleteDeviceData">
30
          <t-icon icon="trash" size="16"></t-icon>删除
31
        </t-button>
32
      </div>
33
      <div class="table-box">
34
        <t-table :data="taskDataList" line @selection-change="selectChange">
35
          <t-table-column type="selection" width="70"></t-table-column>
36
          <t-table-column prop="taskName" label="任务名称" width="110"></t-table-column>
37
          <t-table-column prop="taskId" label="任务编号" width="120"></t-table-column>
38
          <t-table-column prop="matchingType" label="匹配模型" width="120"></t-table-column>
39
          <t-table-column prop="executionDevice" label="执行设备" width="220"></t-table-column>
40
          <t-table-column prop="configurationTime" label="配置时间" width="180"></t-table-column>
41
          <t-table-column prop="status" style="background-color:#000" label="状态" width="120">
42
            <template v-slot="{row}">
43
              <div :class="getTypecellClass(row.status)" class="tag-cell">
44
                <span class="status-ball"></span>{{ row.status }}
45
              </div>
46
            </template>
47
          </t-table-column>
48
          <t-table-column label="操作">
49
            <template v-slot="{row}">
50
              <a href="javascript:;" style="color:#0089D4;">编辑</a>
51
              <a href="javascript:;" style="color:#0089D4;" @click="deleteDeviceData(row)">删除</a>
52
              <a href="javascript:;" style="color:#0089D4;">执行</a>
53
              <a href="javascript:;" style="color:#0089D4;">停止</a>
54
            </template>
55
          </t-table-column>
56
        </t-table>
57
      </div>
58
      <div class="table-pager">
59
        <t-pager :current.sync="page" :total="total" :page-size="limit" :sizer-range="[10,20,50,100]" show-elevator show-sizer @on-size-change="onSizeChange" @on-change="onChange">>
60
        </t-pager>
61
      </div>
62
    </t-card>
63
    <!-- 指令下发对话框 -->
64
    <!-- <template>
65
      <send-order-modal :data="currentDeviceData"></send-order-modal>
66
    </template> -->
67
  </div>
68
</template>
69
70
<script>
71
import sysapi from '@/api/system'
72
// import sendOrderModal from './components/modal/sendordermodal.vue'
73
export default {
74
  components: {
75
    // sendOrderModal
76
  },
77
  data () {
78
    return {
79
      // 设备类型id
80
      taskName: '',
81
      // 任务名称列表
82
      taskNameList: [],
83
      // 匹配模型列表
84
      matchingTypeList: [],
85
      // 任务状态列表
86
      taskStatusList: [],
87
      // 任务数据列表
88
      taskDataList: [],
89
      // 匹配模型
90
      matchingType: '',
91
      // 任务状态
92
      taskStatus: '',
93
      // 当前页
94
      page: 1,
95
      // 当前页的数据个数
96
      limit: 10,
97
      // 数据总数
98
      total: 100,
99
      // 批量删除的设备id
100
      deletetaskStatus: [],
101
      // 当前设备数据
102
      currentDeviceData: {}
103
    }
104
  },
105
  created () {
106
    this.getDeviceData()
107
    this.getDeviceTypes()
108
  },
109
  methods: {
110
    testStyle(a, b, c) {
111
      console.log(b)
112
      a.className = 'test'
113
    },
114
    getTypecellClass (orderTypeName) {
115
      let typecellClass = ''
116
      switch (orderTypeName) {
117
        case '摄像头':
118
        case '门禁':
119
          typecellClass = 'tag-cell-executing'
120
          break
121
        case '人脸终端':
122
          typecellClass = 'tag-cell-stoped'
123
          break
124
      }
125
      return typecellClass
126
    },
127
    // 获取设备列表数据
128
    async getDeviceData () {
129
      const res = await sysapi.getDeviceData({ params: { page: this.page, limit: this.limit, taskName: this.taskName, matchingType: this.matchingType, taskStatus: this.taskStatus } }
130
      )
131
      if (res.status === 200) {
132
        console.log(res.data.data)
133
        const list = res.data.data.data
134
        let dataList = list.map(item => {
135
          return {
136
            taskName: item.deviceName,
137
            status: item.deviceType
138
          }
139
        })
140
        console.log(dataList)
141
        this.taskDataList = dataList
142
        this.total = res.data.total
143
      } else {
144
        this.$Message.danger('设备数据列表获取失败!')
145
      }
146
    },
147
    // 向服务器发送请求获取设备类型列表数据
148
    async getDeviceTypes () {
149
      const res = await sysapi.getDeviceTypes()
150
      if (res.status === 200) {
151
        this.taskNameList = res.data.data
152
      } else {
153
        this.$Message.danger('设备类型列表数据获取失败!')
154
      }
155
    },
156
    // 查询数据
157
    selectHandle () {
158
      this.page = 1
159
      this.getDeviceData()
160
    },
161
    // 重置查询数据
162
    resetData () {
163
      this.page = 1
164
      this.taskName = ''
165
      this.taskStatus = ''
166
      this.matchingType = ''
167
      this.getDeviceData()
168
    },
169
    // 当前页改变 触发事件
170
    onChange (page) {
171
      this.page = page
172
      this.getDeviceData()
173
    },
174
    // 当前页数据总数改变 触发事件
175
    onSizeChange (pageSize) {
176
      this.limit = pageSize
177
      this.getDeviceData()
178
    },
179
    // 删除数据
180
    deleteDeviceData (row) {
181
      this.$Confirm.confirm({
182
        title: '正在准备删除...',
183
        content: '<p>确定要删除?</p><p>删除后将无法还原!</p>',
184
        ok: async () => {
185
          const res = await sysapi.deleteDeviceData({ taskStatus: row.taskStatus })
186
          if (res.status === 200) {
187
            this.getDeviceData()
188
            this.$Message.success('删除成功!')
189
          } else {
190
            this.$Message.danger('删除失败!')
191
          }
192
        },
193
        cancel: () => {
194
          this.$Message.info('已取消删除!')
195
        }
196
      })
197
    },
198
    // 当复选框发生改变时 触发事件
199
    selectChange (data) {
200
      const arr = []
201
      data.forEach(item => arr.push(item.taskStatus))
202
      this.deletetaskStatus = arr
203
    },
204
    // 批量删除数据
205
    multDeleteDeviceData () {
206
      if (this.deletetaskStatus.length === 0) {
207
        return this.$Message.warning('请选择要删除的数据!')
208
      }
209
      this.$Confirm.confirm({
210
        title: '正在准备删除...',
211
        content: '<p>确定要删除?</p><p>删除后将无法还原!</p>',
212
        ok: async () => {
213
          const res = await sysapi.deleteDeviceData({ taskStatus: this.deletetaskStatus })
214
          if (res.status === 200) {
215
            this.getDeviceData()
216
            this.$Message.success('删除成功!')
217
          } else {
218
            this.$Message.danger('删除失败!')
219
          }
220
        },
221
        cancel: () => {
222
          this.$Message.info('已取消删除!')
223
        }
224
      })
225
    }
226
  }
227
}
228
</script>
229
230
<style lang="scss">
231
.tag-cell {
232
  display: flex;
233
  align-items: center;
234
  justify-content: center;
235
  height: 32px;
236
  border-radius: 4px;
237
  &-executing {
238
      .status-ball{
239
     width:6px;
240
     height:6px;
241
     border-radius:60px;
242
     margin-right:8px;
243
     background-color: rgba(20, 123, 209, 100);
244
    }
245
    border-radius: 16px;
246
    color:rgba(20, 123, 209, 100);
247
    background-color: rgba(20, 123, 209, 0.1);
248
  }
249
250
  &-stoped {
251
    border-radius: 16px;
252
    opacity: 0.25;
253
    color:rgba(0, 0, 0, 1);
254
    background-color: rgba(0, 0, 0, 0.1);
255
    .status-ball{
256
     background-color:rgba(0, 0, 0, 1);
257
     width:6px;
258
     height:6px;
259
     border-radius:60px;
260
     margin-right:8px
261
    }
262
  }
263
}
264
.device-manage-page {
265
    .test{
266
        background-color: #0089d4;
267
    }
268
  .card-block {
269
    padding: 0px 24px !important;
270
  }
271
  .tabs {
272
    overflow: inherit;
273
  }
274
  .tabs-list {
275
    .tabs-list__content {
276
      display: flex;
277
      justify-content: flex-end;
278
      margin-top: -10px;
279
      .tabs-list__nav {
280
        height: 32px;
281
        line-height: 16px;
282
      }
283
    }
284
    .tabs-tab {
285
      margin: 0;
286
      padding: 0 25px;
287
      font-size: 14px;
288
    }
289
    .tabs-tab.checked {
290
      border-bottom: 3px solid #2d98ff;
291
    }
292
  }
293
  .select-button-box {
294
    padding: 0;
295
    button {
296
      margin-left: 5px;
297
    }
298
  }
299
  .select-confidtion-box {
300
    margin: 24px 0;
301
    .col-3 {
302
      padding: 0;
303
    }
304
  }
305
  .function-button-box {
306
    line-height: 32px;
307
    button {
308
      margin-right: 12px;
309
      i {
310
        line-height: 32px;
311
        margin-right: 8px;
312
      }
313
    }
314
    a {
315
      color: #0089d4;
316
      i {
317
        line-height: 32px !important;
318
        margin-left: 4px;
319
      }
320
    }
321
  }
322
  .table-box {
323
    margin: 20px 0;
324
  }
325
  .table-pager {
326
    display: flex;
327
    justify-content: flex-end;
328
  }
329
}
330
</style>

+ 51 - 79
security-protection-platform/src/modules/videoSurveillance/index.vue

@ -1,18 +1,18 @@
1 1
<template>
2 2
  <div class="page-main">
3 3
    <t-button-group class="top-btn">
4
      <t-button :color="tabType === 'gate' ? 'primary' : 'secondary'" @click="tabClick('gate')">风场大门</t-button>
5
      <t-button :color="tabType === 'room' ? 'primary' : 'secondary'" @click="tabClick('room')">集控室</t-button>
4
      <t-button :color="tabId === 1 ? 'primary' : 'secondary'" @click="tabClick(1)">风场大门</t-button>
5
      <t-button :color="tabId === 2 ? 'primary' : 'secondary'" @click="tabClick(2)">集控室</t-button>
6 6
    </t-button-group>
7 7
    <div class="page-top">
8 8
      <span>风场:</span>
9
      <t-select v-model="gateFieldData" @change="getWindFieldData">
9
      <t-select v-model="gateFieldData" clearable @change="getVideoSurveillanceData(true)">
10 10
        <t-option v-for="(item,index) in organizationList" :key="index" :value="item.id">{{ item.org }}</t-option>
11 11
      </t-select>
12 12
    </div>
13
    <div v-if="tabType==='gate'" >
14
      <div class="page-bottom">
15
        <t-card v-for="(item,index) in gateList" :key="index" :class="index%3===0?'card-video-first':'card-video'">
13
    <div class="page-bottom">
14
      <div v-for="(item,index) in videoList" :key="index">
15
        <t-card :class="index%3===0?'card-video-first':'card-video'">
16 16
          <img :src="item.url" class="card-image">
17 17
          <div slot="foot" class="bottom">
18 18
            <span v-tooltip="item.videoDetail">{{ item.videoDetail.content | handleText }}</span>
@ -24,24 +24,8 @@
24 24
          </div>
25 25
        </t-card>
26 26
      </div>
27
      <t-pager :page-size="gatePageSize" :current="gateCurrent" :total="gateTotal" :sizer-range="[ 5, 10, 20, 30 ]" class="pager" show-elevator @on-change="onChangeGate"></t-pager>
28
    </div>
29
    <div v-if="tabType==='room'" >
30
      <div class="page-bottom">
31
        <t-card v-for="(item,index) in roomList" :key="index" :class="index%3===0?'card-video-first':'card-video'">
32
          <img :src="item.url" class="card-image">
33
          <div slot="foot" class="bottom">
34
            <span v-tooltip="item.videoDetail">{{ item.videoDetail.content | handleText }}</span>
35
            <div style="margin-left:auto">
36
              <t-button v-tooltip="item.options1" class="bottom-btn" @click="goDistinguishRecord(item.videoId)"><t-icon size="16" icon="image-outline"></t-icon></t-button>
37
              <t-button v-tooltip="item.options2" class="bottom-btn"><t-icon size="16" icon="piechart-outline"></t-icon></t-button>
38
              <t-button v-tooltip="item.options3" class="bottom-btn"><t-icon size="16" icon="arrow-expand-all-outline"></t-icon></t-button>
39
            </div>
40
          </div>
41
        </t-card>
42
      </div>
43
      <t-pager :page-size="roomPageSize" :current="roomCurrent" :total="roomTotal" :sizer-range="[ 5, 10, 20, 30 ]" class="pager" show-elevator @on-change="onChangeRoom"></t-pager>
44 27
    </div>
28
    <t-pager :page-size="videoPageSize" :current="videoCurrent" :total="videoTotal" :sizer-range="[ 5, 10, 20, 30 ]" class="pager" show-elevator @on-change="onChangeGate"></t-pager>
45 29
  </div>
46 30
</template>
47 31
<script>
@ -59,34 +43,33 @@ export default {
59 43
  },
60 44
  data() {
61 45
    return {
62
      gateCurrent: 1, // 大门分页数据
63
      gatePageSize: 10, // 大门分页数据
64
      gateTotal: 100, // 大门分页总数
65
      roomCurrent: 1, // 集控室分页数据
66
      roomPageSize: 10, // 集控室分页数据
67
      roomTotal: 100, // 集控室分页总数
68
      tabType: 'gate',
46
      videoCurrent: 1, // 大门分页数据
47
      videoPageSize: 10, // 大门分页数据
48
      videoTotal: 100, // 大门分页总数
49
      tabId: 1,
50
      paramsObj: { // 接口请求数据
51
        page: 0,
52
        limit: 10
53
      },
54
      departments: '',
69 55
      organizationList: [], // 风场数据
70 56
      gateFieldData: 101101,
71
      gateList: [],
57
      videoList: [],
58
      totalList: [],
72 59
      roomList: []
73 60
    }
74 61
  },
75 62
  mounted() {
76 63
    this.getWindFiledList() // 获取风场列表
77
    this.getWindFieldData()
78
    this.getCenterRoomData()
64
    this.getVideoSurveillanceData() // 获取视频监控界面数据
79 65
  },
80 66
  methods: {
81
    tabClick(name) {
82
      this.gateFieldData = ''
83
      this.gateCurrent = 1
84
      this.roomCurrent = 1
85
      if (name === 'gate') {
86
        this.tabType = 'gate'
87
      } else {
88
        this.tabType = 'room'
89
      }
67
    async tabClick(id) {
68
      this.paramsObj.page = 0
69
      this.videoList = []
70
      this.tabId = id
71
      this.videoCurrent = 1
72
      this.getVideoSurveillanceData()
90 73
    },
91 74
    getWindFiledList() {
92 75
      commonapi.getDepartments().then(resp => {
@ -104,43 +87,31 @@ export default {
104 87
      })
105 88
    },
106 89
    // 获得风场大门数据
107
    getWindFieldData() {
108
      let obj = {
109
        page: this.gateCurrent,
110
        limit: this.gatePageSize
90
    getVideoSurveillanceData(isWind) {
91
      if (isWind) {
92
        this.videoList = []
111 93
      }
112
      sysapi.getGateFieldData({params: obj}).then(res => {
113
        this.gateList = res.data.data
114
        this.gateTotal = res.data.total
115
        this.gateList.forEach(element => {
116
          const txt = element.videoDetail
117
          element.videoDetail = {}
118
          element.videoDetail.content = txt
119
          element.videoDetail.placement = 'top'
94
      if (this.videoList.length <= 10) {
95
        this.paramsObj.page = this.videoCurrent
96
        sysapi.getVideoSurveillanceData({params: this.paramsObj}).then(res => {
97
          this.departments = res.data.departments
98
          this.videoTotal = this.departments[this.tabId - 1].total
99
          res.data.data.forEach((element, index) => {
100
            if (element.departmentId === this.tabId) {
101
              this.videoList.push(element)
102
            }
103
          })
104
          this.videoList.forEach(element => {
105
            if (typeof (element.videoDetail) === 'object') {
106
              var txt = element.videoDetail.content
107
            } else { txt = element.videoDetail }
108
            element.videoDetail = {}
109
            element.videoDetail.content = txt
110
            element.videoDetail.placement = 'top'
111
          })
112
          this.getVideoSurveillanceData()
120 113
        })
121
      })
122
    },
123
    // 获得集控室数据
124
    getCenterRoomData() {
125
      let obj = {
126
        page: this.roomCurrent,
127
        limit: this.roomPageSize
128 114
      }
129
      sysapi.getCenterRoomData({params: obj}).then(res => {
130
        this.roomList = res.data.data
131
        this.roomTotal = res.data.total
132
        this.roomList.forEach(element => {
133
          const txt = element.videoDetail
134
          element.videoDetail = {}
135
          element.videoDetail.content = txt
136
          element.videoDetail.placement = 'top'
137
        })
138
      })
139
    },
140
    // 集控室分页
141
    onChangeRoom (val) {
142
      this.roomCurrent = val
143
      this.getCenterRoomData()
144 115
    },
145 116
    // 进入识别记录界面
146 117
    goDistinguishRecord(id) {
@ -148,8 +119,9 @@ export default {
148 119
    },
149 120
    // 风场大门分页
150 121
    onChangeGate(val) {
151
      this.gateCurrent = val
152
      this.getWindFieldData()
122
      this.videoList = []
123
      this.videoCurrent = val
124
      this.getVideoSurveillanceData()
153 125
    }
154 126
  }
155 127
}
@ -166,8 +138,8 @@ export default {
166 138
        flex-wrap: wrap;
167 139
        .card-video-first, .card-video{
168 140
          width:397px;
169
          margin-top: 20px;
170 141
          height:245px;
142
          margin-top: 20px;
171 143
            .card-image{
172 144
                width: 100%;
173 145
                height: 100%;

+ 10 - 4
security-protection-platform/src/routes.js

@ -93,16 +93,22 @@ export const constantRoutes = [
93 93
        meta: { title: '设备管理' }
94 94
      },
95 95
      {
96
        name: 'sys_attendance',
97
        path: 'attendance',
98
        component: () => import(/* webpackChunkName: "sys_attendance" */ './modules/system/attendance'),
99
        meta: { title: '考勤配置' }
96
        name: 'sys_task',
97
        path: 'assignment',
98
        component: () => import(/* webpackChunkName: "sys_attendance" */ './modules/system/assignment'),
99
        meta: { title: 'AI任务' }
100 100
      },
101 101
      {
102 102
        name: 'sys_monitor',
103 103
        path: 'monitor',
104 104
        component: () => import(/* webpackChunkName: "sys_monitor" */ './modules/system/monitor'),
105 105
        meta: { title: '监控布局' }
106
      },
107
      {
108
        name: 'sys_attendance',
109
        path: 'attendance',
110
        component: () => import(/* webpackChunkName: "sys_attendance" */ './modules/system/attendance'),
111
        meta: { title: '考勤配置' }
106 112
      }
107 113
    ]
108 114
  },