From 5672f352d0ba114e2ae96c8cefad6c74ae6d2934 Mon Sep 17 00:00:00 2001
From: unknown <280848880@qq.com>
Date: 星期一, 23 十月 2023 10:38:28 +0800
Subject: [PATCH] 苏州-web:智能安全帽提交

---
 web/src/components/element/Table.vue                   |  300 +-
 web/src/views/ProjectManage/SectionManage.vue          | 1219 ++++++------
 web/src/views/SecureManage/SmartHelmet/AlarmRecord.vue |  728 +++++++
 web/src/assets/markerIcon.png                          |    0 
 web/src/plugins/public.js                              |  363 ++-
 web/src/style/layout-main.scss                         |  959 +++++-----
 /dev/null                                              |  126 -
 web/src/api/modules/safety.js                          |  517 +++--
 web/src/components/element/Form.vue                    |   64 
 web/src/views/SecureManage/SmartHelmet/TrackBack.vue   |  741 ++++++++
 web/package.json                                       |    2 
 web/src/views/SecureManage/SmartHelmet/SafeHat.vue     |  132 +
 web/src/views/SecureManage/SmartHelmet/PhoneManage.vue |  253 ++
 web/package-lock.json                                  |   10 
 14 files changed, 3,676 insertions(+), 1,738 deletions(-)

diff --git a/web/package-lock.json b/web/package-lock.json
index c1c1899..a2b3711 100644
--- a/web/package-lock.json
+++ b/web/package-lock.json
@@ -4,6 +4,11 @@
   "lockfileVersion": 1,
   "requires": true,
   "dependencies": {
+    "@amap/amap-jsapi-loader": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/@amap/amap-jsapi-loader/-/amap-jsapi-loader-1.0.1.tgz",
+      "integrity": "sha512-nPyLKt7Ow/ThHLkSvn2etQlUzqxmTVgK7bIgwdBRTg2HK5668oN7xVxkaiRe3YZEzGzfV2XgH5Jmu2T73ljejw=="
+    },
     "@ampproject/remapping": {
       "version": "2.1.1",
       "resolved": "https://registry.npmmirror.com/@ampproject/remapping/-/remapping-2.1.1.tgz",
@@ -4977,6 +4982,11 @@
         }
       }
     },
+    "echarts-liquidfill": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/echarts-liquidfill/-/echarts-liquidfill-3.1.0.tgz",
+      "integrity": "sha512-5Dlqs/jTsdTUAsd+K5LPLLTgrbbNORUSBQyk8PSy1Mg2zgHDWm83FmvA4s0ooNepCJojFYRITTQ4GU1UUSKYLw=="
+    },
     "ee-first": {
       "version": "1.1.1",
       "resolved": "https://registry.npmmirror.com/ee-first/-/ee-first-1.1.1.tgz",
diff --git a/web/package.json b/web/package.json
index 472926c..1f87330 100644
--- a/web/package.json
+++ b/web/package.json
@@ -8,12 +8,14 @@
     "lint": "vue-cli-service lint"
   },
   "dependencies": {
+    "@amap/amap-jsapi-loader": "^1.0.1",
     "@vue/composition-api": "^1.4.9",
     "@wangeditor/editor": "^5.1.23",
     "@wangeditor/editor-for-vue": "^1.0.2",
     "axios": "^0.26.0",
     "core-js": "^3.6.5",
     "echarts": "^5.4.2",
+    "echarts-liquidfill": "^3.1.0",
     "element-ui": "^2.15.6",
     "ezuikit-js": "^7.7.5",
     "node-sass": "^4.14.1",
diff --git a/web/src/api/modules/safety.js b/web/src/api/modules/safety.js
index 637f94b..963b9c4 100644
--- a/web/src/api/modules/safety.js
+++ b/web/src/api/modules/safety.js
@@ -1,238 +1,281 @@
-/**
- * 安全管理模块
- */
- import axios from '../request';
- export default{
-  /**
-   * 安全公告模块
-   */
-  // 查询安全公告信息列表
-  searchSafetyAfficheList: (params) =>
-    axios({
-      method: 'post',
-      url: '/secure/secureNotice/findList',
-      headers: {
-        pageNum: params.pageNum,
-        pageSize: params.pageSize
-      },
-      data: {
-        noticeName: params.noticeName,
-        startTime: params.startTime,
-        endTime: params.endTime
-      }
-    }),
-  // 添加 修改 安全公告信息
-  insertSafetyAfficheInfo: (params) =>
-    axios.post('/secure/secureNotice/addSecureNotice', params),
-  // 删除安全公告信息
-  deleteSafetyAfficheInfo: (params) =>
-    axios.post('secure/secureNotice/delete', params),
-  // 上架 下架安全公告信息
-  upAndDownSafetyAfficheInfo: (params) =>
-    axios.post('/secure/secureNotice/updown', params),
-
-
-  /**
-   * 劳务档案
-   */
-  //奖惩记录列表
-	recordAwardsLists: (params) =>
-    axios.post('/secure/encourage/encourageRecordWebList', params),
-  //安全码列表
-  codeSafeLists: (params) =>
-    axios.post('/secure/encourage/encourageSecurityCode', params),
-
-  /**
-   * 安全考核模块
-  */
-  // 根据类型获取类型名称(安全培训和日常培训制定名称)
-  getAllSecureTrainPull: (params) =>
-    axios.post('/secure/secureTrain/secureTrainPull', params),
-  // 查询安全考核信息
-  searchSafetyExamineList: (params) =>
-    axios({
-      method: 'post',
-      url: '/secure/exam/findList',
-      headers: {
-        pageNum: params.pageNum,
-        pageSize: params.pageSize
-      },
-      data: {
-        examName: params.examName,
-        startDay: params.startDay,
-        endDay: params.endDay
-      }
-    }),
-  // 添加 修改 安全考核信息
-  insertSafetyExamineInfo: (params) =>
-    axios.post('/secure/exam/addExam', params),
-  // 删除安全考核信息
-  deleteSafetyExamineInfo: (params) =>
-    axios.post('/secure/exam/delete', params),
-  // 获取安全考核统计信息
-  getExamRecordStatistical: (params) =>
-    axios.post('/secure/examRecord/recordStat', params),
-  // 查询安全考核记录
-  searchSafetyCheckRecord: (params) =>
-    axios({
-      method: 'post',
-      url: '/secure/examRecord/recordList',
-      headers: {
-        pageNum: params.pageNum,
-        pageSize: params.pageSize
-      },
-      data: {
-        examId: params.examId,
-        examResult: params.examResult,
-        departId: params.departId,
-        groupId: params.groupId,
-        startTime: params.startTime,
-        endTime: params.endTime
-      }
-    }),
-  
-  
-  // 安全考题信息列表
-  searchSafetyTopicList: (params) =>
-  axios({
-    method: 'post',
-    url: '/secure/question/findList',
-    headers: {
-      pageNum: params.pageNum,
-      pageSize: params.pageSize
-    },
-    data: {
-      trainName: params.trainName
-    }
-  }),
-  // 新增 修改安全考题信息
-  insertSafetyTopicInfo: (params) =>
-    axios.post('/secure/question/addQuestion', params),
-  // 删除安全考题信息
-  deleteSafetyTopicInfo: (params) =>
-    axios.post('/secure/question/delete', params),
-  // 获取答案列表信息
-  getAnswerlIstInfo: (params) =>
-    axios.post('secure/question/answerList', params),
-
-  /**
-   * 消防器材模块
-   */
-  // 查询消防器材信息列表
-  searchFireequipmentList: (params) =>
-    axios({
-      method: 'post',
-      url: '/secure/secureGood/findList',
-      headers: {
-        pageNum: params.pageNum,
-        pageSize: params.pageSize
-      },
-      data: {
-        goodName: params.goodName,
-        startTime: params.startTime,
-        endTime: params.endTime
-      }
-    }),
-  // 添加 修改 消防器材信息
-  insertFireequipmentInfo: (params) =>
-    axios.post('/secure/secureGood/addSecureGood', params),
-  // 删除消防器材信息
-  deleteFireequipmentInfo: (params) =>
-    axios.post('secure/secureGood/delete', params),
-  // 导出二维码
-  getFireequipmentCode: (params) =>
-    axios({
-      method: 'get',
-      url: '/secure/secureGood/viewcode',
-      params: params,
-      responseType: 'blob'
-    }),
-  // 获取消防器材图片列表
-  getFireequipmentImageInfo: (params) =>
-    axios.post('/secure/secureGood/fileList', params),
-
-  /**
-   * 安全培训和日常培训制定
-   */
-  // 列表信息
-  searchEnactLists: params =>
-    axios.post('/secure/secureTrain/secureTrainList', params),
-  // 添加信息
-  insertEnactInfo: params =>
-    axios.post('/secure/secureTrain/secureTrainInsert', params),
-  // 修改信息
-  updateEnactInfo: params =>
-    axios.post('/secure/secureTrain/secureTrainUpdate', params),
-  // 信息详情
-  detailsEnactInfo: params =>
-    axios.post('/secure/secureTrain/secureTrainInfo', params),
-  // 删除信息
-  deleteEnactInfo: params =>
-    axios.post('/secure/secureTrain/secureTrainDel', params),
-  //查询记录
-  getEnactRecords: params =>
-    axios.post('/secure/secureTrain/secureTrainRecord', params),
-
-  /**
-   * 安全资料模块
-   */
-  // 列表信息
-  searchDatumLists: params =>
-    axios.post('/secure/material/materialList', params),
-  // 添加信息
-  insertDatumInfo: params =>
-    axios.post('/secure/material/materialInsert', params),
-  // 修改信息
-  updateDatumInfo: params =>
-    axios.post('/secure/material/materialUpdate', params),
-    // 信息详情
-  detailsDatumInfo: params =>
-    axios.post('/secure/material/materialInfo', params),
-  
-  /**
-   * 奖惩标准模块
-   */
-  // 列表信息
-  searchPunishLists: params =>
-    axios.post('/secure/encourage/encourageList', params),
-  // 添加信息
-  insertPunishInfo: params =>
-    axios.post('/secure/encourage/encourageInsert', params),
-  // 修改信息
-  updatePunishInfo: params =>
-    axios.post('/secure/encourage/encourageUpdate', params),
-  // 信息详情
-  detailsPunishInfo: params =>
-    axios.post('/secure/encourage/encourageInfo', params),
-  // 信息删除
-  deletePunishInfo: params =>
-    axios.post('/secure/encourage/encourageDel', params),
-
-  /**
-  * 风险分级管控
-  */
-  RiskGrad: {
-    // 危险源告知
-    warning : {
-      getLists: params =>
-        axios.post('/secure/regionHazardInform/findAll', params),
-      insert: params =>
-        axios.post('/secure/regionHazardInform/insert', params),
-      update: params =>
-        axios.post('/secure/regionHazardInform/update', params),
-      delete: params =>
-        axios.get('/secure/regionHazardInform/delete', { params }),
-    },
-    // 区域包保
-    allocation : {
-      getLists: params =>
-        axios.post('/secure/tRegionWarranty/findAll', params),
-      insert: params =>
-        axios.post('/secure/tRegionWarranty/insert', params),
-      update: params =>
-        axios.post('/secure/tRegionWarranty/update', params),
-      delete: params =>
-        axios.get('/secure/tRegionWarranty/delete', { params }),
-    }
-  }
+/**
+ * 安全管理模块
+ */
+ import axios from '../request';
+ export default{
+  /**
+   * 安全公告模块
+   */
+  // 查询安全公告信息列表
+  searchSafetyAfficheList: (params) =>
+    axios({
+      method: 'post',
+      url: '/secure/secureNotice/findList',
+      headers: {
+        pageNum: params.pageNum,
+        pageSize: params.pageSize
+      },
+      data: {
+        noticeName: params.noticeName,
+        startTime: params.startTime,
+        endTime: params.endTime
+      }
+    }),
+  // 添加 修改 安全公告信息
+  insertSafetyAfficheInfo: (params) =>
+    axios.post('/secure/secureNotice/addSecureNotice', params),
+  // 删除安全公告信息
+  deleteSafetyAfficheInfo: (params) =>
+    axios.post('secure/secureNotice/delete', params),
+  // 上架 下架安全公告信息
+  upAndDownSafetyAfficheInfo: (params) =>
+    axios.post('/secure/secureNotice/updown', params),
+
+
+  /**
+   * 劳务档案
+   */
+  //奖惩记录列表
+	recordAwardsLists: (params) =>
+    axios.post('/secure/encourage/encourageRecordWebList', params),
+  //安全码列表
+  codeSafeLists: (params) =>
+    axios.post('/secure/encourage/encourageSecurityCode', params),
+
+  /**
+   * 安全考核模块
+  */
+  // 根据类型获取类型名称(安全培训和日常培训制定名称)
+  getAllSecureTrainPull: (params) =>
+    axios.post('/secure/secureTrain/secureTrainPull', params),
+  // 查询安全考核信息
+  searchSafetyExamineList: (params) =>
+    axios({
+      method: 'post',
+      url: '/secure/exam/findList',
+      headers: {
+        pageNum: params.pageNum,
+        pageSize: params.pageSize
+      },
+      data: {
+        examName: params.examName,
+        startDay: params.startDay,
+        endDay: params.endDay
+      }
+    }),
+  // 添加 修改 安全考核信息
+  insertSafetyExamineInfo: (params) =>
+    axios.post('/secure/exam/addExam', params),
+  // 删除安全考核信息
+  deleteSafetyExamineInfo: (params) =>
+    axios.post('/secure/exam/delete', params),
+  // 获取安全考核统计信息
+  getExamRecordStatistical: (params) =>
+    axios.post('/secure/examRecord/recordStat', params),
+  // 查询安全考核记录
+  searchSafetyCheckRecord: (params) =>
+    axios({
+      method: 'post',
+      url: '/secure/examRecord/recordList',
+      headers: {
+        pageNum: params.pageNum,
+        pageSize: params.pageSize
+      },
+      data: {
+        examId: params.examId,
+        examResult: params.examResult,
+        departId: params.departId,
+        groupId: params.groupId,
+        startTime: params.startTime,
+        endTime: params.endTime
+      }
+    }),
+  
+  
+  // 安全考题信息列表
+  searchSafetyTopicList: (params) =>
+  axios({
+    method: 'post',
+    url: '/secure/question/findList',
+    headers: {
+      pageNum: params.pageNum,
+      pageSize: params.pageSize
+    },
+    data: {
+      trainName: params.trainName
+    }
+  }),
+  // 新增 修改安全考题信息
+  insertSafetyTopicInfo: (params) =>
+    axios.post('/secure/question/addQuestion', params),
+  // 删除安全考题信息
+  deleteSafetyTopicInfo: (params) =>
+    axios.post('/secure/question/delete', params),
+  // 获取答案列表信息
+  getAnswerlIstInfo: (params) =>
+    axios.post('secure/question/answerList', params),
+
+  /**
+   * 消防器材模块
+   */
+  // 查询消防器材信息列表
+  searchFireequipmentList: (params) =>
+    axios({
+      method: 'post',
+      url: '/secure/secureGood/findList',
+      headers: {
+        pageNum: params.pageNum,
+        pageSize: params.pageSize
+      },
+      data: {
+        goodName: params.goodName,
+        startTime: params.startTime,
+        endTime: params.endTime
+      }
+    }),
+  // 添加 修改 消防器材信息
+  insertFireequipmentInfo: (params) =>
+    axios.post('/secure/secureGood/addSecureGood', params),
+  // 删除消防器材信息
+  deleteFireequipmentInfo: (params) =>
+    axios.post('secure/secureGood/delete', params),
+  // 导出二维码
+  getFireequipmentCode: (params) =>
+    axios({
+      method: 'get',
+      url: '/secure/secureGood/viewcode',
+      params: params,
+      responseType: 'blob'
+    }),
+  // 获取消防器材图片列表
+  getFireequipmentImageInfo: (params) =>
+    axios.post('/secure/secureGood/fileList', params),
+
+  /**
+   * 安全培训和日常培训制定
+   */
+  // 列表信息
+  searchEnactLists: params =>
+    axios.post('/secure/secureTrain/secureTrainList', params),
+  // 添加信息
+  insertEnactInfo: params =>
+    axios.post('/secure/secureTrain/secureTrainInsert', params),
+  // 修改信息
+  updateEnactInfo: params =>
+    axios.post('/secure/secureTrain/secureTrainUpdate', params),
+  // 信息详情
+  detailsEnactInfo: params =>
+    axios.post('/secure/secureTrain/secureTrainInfo', params),
+  // 删除信息
+  deleteEnactInfo: params =>
+    axios.post('/secure/secureTrain/secureTrainDel', params),
+  //查询记录
+  getEnactRecords: params =>
+    axios.post('/secure/secureTrain/secureTrainRecord', params),
+
+  /**
+   * 安全资料模块
+   */
+  // 列表信息
+  searchDatumLists: params =>
+    axios.post('/secure/material/materialList', params),
+  // 添加信息
+  insertDatumInfo: params =>
+    axios.post('/secure/material/materialInsert', params),
+  // 修改信息
+  updateDatumInfo: params =>
+    axios.post('/secure/material/materialUpdate', params),
+    // 信息详情
+  detailsDatumInfo: params =>
+    axios.post('/secure/material/materialInfo', params),
+  
+  /**
+   * 奖惩标准模块
+   */
+  // 列表信息
+  searchPunishLists: params =>
+    axios.post('/secure/encourage/encourageList', params),
+  // 添加信息
+  insertPunishInfo: params =>
+    axios.post('/secure/encourage/encourageInsert', params),
+  // 修改信息
+  updatePunishInfo: params =>
+    axios.post('/secure/encourage/encourageUpdate', params),
+  // 信息详情
+  detailsPunishInfo: params =>
+    axios.post('/secure/encourage/encourageInfo', params),
+  // 信息删除
+  deletePunishInfo: params =>
+    axios.post('/secure/encourage/encourageDel', params),
+
+  /**
+  * 风险分级管控
+  */
+  RiskGrad: {
+    // 危险源告知
+    warning : {
+      getLists: params =>
+        axios.post('/secure/regionHazardInform/findAll', params),
+      insert: params =>
+        axios.post('/secure/regionHazardInform/insert', params),
+      update: params =>
+        axios.post('/secure/regionHazardInform/update', params),
+      delete: params =>
+        axios.get('/secure/regionHazardInform/delete', { params }),
+    },
+    // 区域包保
+    allocation : {
+      getLists: params =>
+        axios.post('/secure/tRegionWarranty/findAll', params),
+      insert: params =>
+        axios.post('/secure/tRegionWarranty/insert', params),
+      update: params =>
+        axios.post('/secure/tRegionWarranty/update', params),
+      delete: params =>
+        axios.get('/secure/tRegionWarranty/delete', { params }),
+    }
+  },
+  /**
+  * 智能安全帽
+  */
+  SmartHelmet: {
+    // 人员列表(智能安全帽下其他页面共用此接口)
+    getLists: params =>
+      axios.post('/materials/helmet/helmetList', params),
+    // ---轨迹回放---
+    trackBack: {
+      // 获取用户在线时长
+      getOnlineTime: params =>
+        axios.post('/materials/helmet/helmetTrajectoryList', params),
+      // 轨迹数据
+      getTrackLists: params =>
+        axios.post('/materials/helmet/helmetMotionList', params),
+    },
+    // ---照片管理---
+    pic: {
+      // 图片
+      getPics: params => 
+        axios.post('/materials/helmet/helmetPictureList', params),
+    },
+    // ---报警记录---
+    warning: {
+      // 报警数
+      getWarning: params => 
+        axios.post('/materials/helmet/helmetReportTotal', params),
+      // 报警详情
+      getDetailWarning: params => 
+        axios.post('/materials/helmet/helmetReportUser', params),
+    },
+    // 区域包保
+    allocation : {
+      getLists: params =>
+        axios.post('/secure/tRegionWarranty/findAll', params),
+      insert: params =>
+        axios.post('/secure/tRegionWarranty/insert', params),
+      update: params =>
+        axios.post('/secure/tRegionWarranty/update', params),
+      delete: params =>
+        axios.get('/secure/tRegionWarranty/delete', { params }),
+    }
+  }
 }
\ No newline at end of file
diff --git a/web/src/assets/markerIcon.png b/web/src/assets/markerIcon.png
new file mode 100644
index 0000000..39347ed
--- /dev/null
+++ b/web/src/assets/markerIcon.png
Binary files differ
diff --git a/web/src/components/element/Form.vue b/web/src/components/element/Form.vue
new file mode 100644
index 0000000..a955305
--- /dev/null
+++ b/web/src/components/element/Form.vue
@@ -0,0 +1,64 @@
+<template>
+  <div class="role">
+    <!-- https://juejin.cn/post/7042597964618924069?searchId=202310071305486BA29870D97939288832#heading-8 -->
+    <!-- https://juejin.cn/post/7139110255748710436?searchId=2023092815420483B1983D44854DBE6608#comment -->
+    <el-form 
+      ref="ruleForm" 
+      class="rule-form" 
+      :model="formConfig.formModels"
+      :rules="formConfig.formRules"
+      :label-width="formConfig.labelWidth || 'auto'">
+      <el-row>
+        <el-col v-for="item in formConfig.formItems" :key="item.label" :span="item.span || 24">
+          <el-form-item :label="item.label" :style="formConfig.itemStyle || null">
+
+            <template v-if="item.type === 'input' || item.type === 'password'">
+              <el-input :placeholder="item.placeholder || `请输入${item.label}`"
+                :show-password="item.type === 'password'"></el-input>
+            </template>
+
+            <template v-else-if="item.type === 'select'">
+              <el-select :placeholder="item.placeholder || `请选择${item.label}`">
+                <el-option v-for="option in item.options" :key="option.label" :label="option.label"
+                  :value="option.value"></el-option>
+              </el-select>
+            </template>
+
+            <template v-else>
+              <el-date-picker v-bind="item.otherOptions"></el-date-picker>
+            </template>
+
+          </el-form-item>
+        </el-col>
+      </el-row>
+    </el-form>
+  </div>
+</template>
+<script>
+export default {
+  data() {
+    return {}
+  },
+  props: {
+    formConfig: {
+      type: Object,
+      default: () => ({})
+    }
+  }
+}
+</script>
+<style scoped lang="scss">
+.el-form {
+  padding-right: 15px;
+  max-height: 550px;
+  overflow-y: auto;
+}
+
+::v-deep .el-form-item__label {
+  color: #fff
+}
+
+.el-select {
+  width: 100%;
+}
+</style> 
\ No newline at end of file
diff --git a/web/src/components/element/Table.vue b/web/src/components/element/Table.vue
index b266c54..e5d56bd 100644
--- a/web/src/components/element/Table.vue
+++ b/web/src/components/element/Table.vue
@@ -1,151 +1,151 @@
-<template>
-  <div>
-    <!-- elTable -->
-    <el-table v-loading="tableLoading" :data="tableData" :ref="tableRef" :size="tableSize" @row-click="rowClick"
-      @row-dblclick="rowDblClick" @selection-change="handleSelectionChange" border stripe>
-      <template v-for="(col, index) in tableColumns">
-        <!-- 选择框 -->
-        <el-table-column v-if="col.selection" :key="`selection${index}`" width="50" type="selection" align="center">
-        </el-table-column>
-
-        <!-- 序号 -->
-        <el-table-column v-else-if="col.index" :key="`index${index}`" width="50" :label="col.name || '序号'" type="index"
-          align="center">
-        </el-table-column>
-
-        <!-- 操作 -->
-        <el-table-column v-else-if="col.operation" :key="`operation${col.name}`" :width="col.width" :label="col.name"
-          align="center">
-          <template #default="{ row }">
-            <el-button v-for="subCol in col.value" :key="subCol.name" :class="subCol.class || ''" :icon="subCol.icon || ''"
-              v-permission="subCol.permission || ''" @click="subCol.handleRow ? subCol.handleRow(row) : emptyFn">
-              {{ subCol.name }}
-            </el-button>
-          </template>
-        </el-table-column>
-
-        <!-- 常规col -->
-        <el-table-column v-else :key="col.name" :width="col.width" :label="col.name" :prop="col.key"
-          :show-overflow-tooltip="col.showOverflowTip || false" :align="col.align || 'center'">
-          <template #default="{ row }">
-            <!-- slotのcol -->
-            <template v-if="col.slot">
-              <slot :name="col.slot" :row="row"></slot>
-            </template>
-            <!-- 需处理数据のcol -->
-            <template v-else-if="col.formatter">
-              {{ col.formatter(row) }}
-            </template>
-            <!-- 普通のcol -->
-            <template v-else>
-              {{ row[col.key] }}
-            </template>
-          </template>
-        </el-table-column>
-      </template>
-    </el-table>
-    <!-- 分页组件 -->
-    <cPagination :total="pageTotal" :page-num.sync="pageNum" :page-size.sync="pageSize" @change-page-num="changePageNum"
-      @change-page-size="changePageSize" />
-  </div>
-</template>
-<script>
-import cPagination from "./Pagination"
-export default {
-  name: "cTable",
-  data() {
-    return {
-      time: null,
-      emptyFn: () => { }
-    };
-  },
-  props: {
-    tableData: {
-      type: Array,
-      default() {
-        return [];
-      }
-    },
-    tableHeight: {
-      type: Number,
-      default: null
-    },
-    tableLoading: {
-      type: Boolean,
-      default: false
-    },
-    tableRef: {
-      type: String,
-      default: "multipleTable"
-    },
-    tableSize: {
-      type: String,
-      default: "mini"
-    },
-    tableColumns: {
-      type: Array,
-      default() {
-        return [];
-      }
-    },
-    pageTotal: {
-      type: Number
-    },
-    pageNum: {
-      type: Number,
-      default: 1
-    },
-    pageSize: {
-      type: Number,
-      default: 10
-    },
-    pageChange: {
-      type: Function,
-      default: () => { }
-    },
-    handleSelection: {
-      type: Function,
-      default: () => { }
-    }
-  },
-  components: {
-    cPagination
-  },
-  methods: {
-    changePageNum(val) {
-      // console.log(val)
-      // this.pageNum = val
-      this.$emit("update:pageNum", val);
-      this.pageChange();
-    },
-    changePageSize(val) {
-      this.$emit("update:pageSize", val);
-      if (val * (this.pageNum - 1) <= this.pageTotal) {
-        this.pageChange();
-      }
-    },
-    // 单击
-    rowClick() {
-      // this.time && clearTimeout(this.time);
-      // this.time = setTimeout(() => {
-      //   this.$refs[this.tableRef].toggleRowSelection(row);
-      // }, 200);
-    },
-    // 双击
-    rowDblClick() {
-      // this.time && clearTimeout(this.time);
-    },
-    clearSelection() {
-      this.$refs[this.tableRef].clearSelection();
-    },
-    handleSelectionChange(val) {
-      this.handleSelection(val);
-    }
-  }
-};
-</script>
-<style lang="scss" scoped>
-.el-table {
-  border: none;
-}
+<template>
+  <div class="box">
+    <!-- elTable -->
+    <el-table v-loading="tableLoading" :data="tableData" :ref="tableRef" :size="tableSize" @row-click="handleRowClick"
+      @row-dblclick="handleRowDblClick" @selection-change="handleSelectionChange" border stripe>
+      <template v-for="(col, index) in tableColumns">
+        <!-- 选择框 -->
+        <el-table-column v-if="col.selection" :key="`selection${index}`" width="50" type="selection" align="center">
+        </el-table-column>
+
+        <!-- 序号 -->
+        <el-table-column v-else-if="col.index" :key="`index${index}`" width="50" :label="col.name || '序号'" type="index"
+          align="center">
+        </el-table-column>
+
+        <!-- 操作 -->
+        <el-table-column v-else-if="col.operation" :key="`operation${col.name}`" :width="col.width" :label="col.name"
+          align="center">
+          <template #default="{ row }">
+            <el-button v-for="subCol in col.value" :key="subCol.name" :class="subCol.class || ''"
+              :icon="subCol.icon || ''" v-permission="subCol.permission || ''"
+              @click="subCol.handleRow ? subCol.handleRow(row) : emptyFn">
+              {{ subCol.name }}
+            </el-button>
+          </template>
+        </el-table-column>
+
+        <!-- 常规col -->
+        <el-table-column v-else :key="col.name" :width="col.width" :label="col.name" :prop="col.key"
+          :show-overflow-tooltip="col.showOverflowTip || false" :align="col.align || 'center'">
+          <template #default="{ row }">
+            <!-- slotのcol -->
+            <template v-if="col.slot">
+              <slot :name="col.slot" :row="row"></slot>
+            </template>
+            <!-- 需处理数据のcol -->
+            <template v-else-if="col.formatter">
+              {{ col.formatter(row) }}
+            </template>
+            <!-- 普通のcol -->
+            <template v-else>
+              {{ row[col.key] }}
+            </template>
+          </template>
+        </el-table-column>
+      </template>
+    </el-table>
+    <!-- 分页组件 -->
+    <cPagination v-if="pageTotal" :total="pageTotal" :page-num.sync="pageNum" :page-size.sync="pageSize"
+      @change-page-num="changePageNum" @change-page-size="changePageSize" />
+  </div>
+</template>
+<script>
+import cPagination from "./Pagination"
+export default {
+  name: "cTable",
+  data() {
+    return {
+      time: null,
+      emptyFn: () => { }
+    };
+  },
+  props: {
+    tableData: {
+      type: Array,
+      default() {
+        return [];
+      }
+    },
+    tableHeight: {
+      type: Number,
+      default: null
+    },
+    tableLoading: {
+      type: Boolean,
+      default: false
+    },
+    tableRef: {
+      type: String,
+      default: "multipleTable"
+    },
+    tableSize: {
+      type: String,
+      default: "mini"
+    },
+    tableColumns: {
+      type: Array,
+      default() {
+        return [];
+      }
+    },
+    pageTotal: {
+      type: Number
+    },
+    pageNum: {
+      type: Number,
+      default: 1
+    },
+    pageSize: {
+      type: Number,
+      default: 10
+    },
+    pageChange: {
+      type: Function,
+      default: () => { }
+    },
+    handleSelection: {
+      type: Function,
+      default: () => { }
+    },
+    rowClick: {
+      type: Function,
+      default: () => { }
+    },
+  },
+  components: {
+    cPagination
+  },
+  methods: {
+    changePageNum(val) {
+      this.$emit("update:pageNum", val)
+      this.pageChange()
+    },
+    changePageSize(val) {
+      this.$emit("update:pageSize", val)
+      if (val * (this.pageNum - 1) <= this.pageTotal) {
+        this.pageChange()
+      }
+    },
+    // 单击
+    handleRowClick(row) {
+      this.rowClick(row)
+    },
+    // 双击
+    handleRowDblClick() { },
+    handleSelectionChange(val) {
+      this.handleSelection(val)
+    }
+  }
+};
+</script>
+<style lang="scss" scoped>
+.box {
+  height: 100%;
+}
+
+.el-table {
+  border: none;
+  height: 100%;
+}
 </style>
\ No newline at end of file
diff --git a/web/src/components/table/Pagination.vue b/web/src/components/table/Pagination.vue
deleted file mode 100644
index 46cfa39..0000000
--- a/web/src/components/table/Pagination.vue
+++ /dev/null
@@ -1,78 +0,0 @@
-<template>
-  <div class="pagination-container">
-    <el-pagination @current-change="handleCurrentChange" @size-change="handleSizeChange" :page-sizes="[10, 20, 30]"
-      :total="total" :current-page="pageNum" :page-size="pageSize"
-      layout="total, sizes, prev, pager, next, jumper"></el-pagination>
-  </div>
-</template>
-<script>
-export default {
-  name: "pagination",
-  data() {
-    return {};
-  },
-  props: {
-    // 总页数
-    total: {
-      type: Number,
-    },
-    // 当前页
-    pageNum: {
-      type: Number,
-    },
-    // 每页显示条数
-    pageSize: {
-      type: Number,
-    },
-  },
-  methods: {
-    // 当前页码变化
-    handleCurrentChange(val) {
-      this.$emit("change-page-num", val);
-    },
-    //  每页查看条数变化
-    handleSizeChange(val) {
-      this.$emit("change-page-size", val);
-    },
-  },
-  // watch: {
-  //   pageSize: {
-  //     // immediate: true,
-  //     handler(newValue, oldValue) {
-  //       this.page._pageSize = this.newValue;
-  //       console.log("pageSize", newValue, oldValue);
-  //     },
-  //   },
-  //   pageNum: {
-  //     // immediate: true,
-  //     handler(newValue, oldValue) {
-  //       this.page._currentPage = newValue;
-  //       console.log("pageNum", newValue, oldValue);
-  //     },
-  //   },
-  // },
-};
-</script>
-<style lang="scss" scoped>
-// 主体底部样式
-::v-deep.el-pagination .btn-prev,
-::v-deep.el-pagination .btn-next,
-::v-deep.el-pagination .el-pager li {
-  background-color: rgba(20, 25, 58, 0.4);
-  border: 1px solid rgba(255, 255, 255, 0.12);
-  font-weight: 400;
-  color: #E2E4E9;
-  border-radius: 4px;
-}
-
-::v-deep.el-pagination .el-pager li:not(.disabled).active {
-  color: #fff;
-  border: 1px solid #39B5FE;
-  background-color: #0B3562;
-  font-weight: 400;
-}
-
-.pagination-container {
-  margin-top: 30px;
-}
-</style>
\ No newline at end of file
diff --git a/web/src/components/table/Table.vue b/web/src/components/table/Table.vue
deleted file mode 100644
index ce3e9a8..0000000
--- a/web/src/components/table/Table.vue
+++ /dev/null
@@ -1,126 +0,0 @@
-<template>
-  <div>
-    <el-table v-loading="tableLoading" :data="tableData" :ref="tableRef" :size="tableSize" @row-click="rowClick"
-      @row-dblclick="rowDblClick" @selection-change="handleSelectionChange" border stripe>
-      <template v-for="(col, index) in tableColumnsConfig">
-        <!-- 选择框 -->
-        <el-table-column v-if="col.selection" width="50" :key="`selection_${index}`" type="selection" align="center">
-        </el-table-column>
-        <!-- 序号 -->
-        <el-table-column v-else-if="col.index" width="50" label="序号" :key="`index_${index}`" type="index" align="center">
-        </el-table-column>
-        <!-- 自定义 -->
-        <slot v-else-if="col.slot" :name="col.slot" show-overflow-tooltip></slot>
-        <!-- 常规col -->
-        <el-table-column v-else :key="`col_${index}`" :width="col.width" :label="col.name" :prop="col.key"
-          :show-overflow-tooltip="col.showOverflowTip || false" :align="col.align || 'center'">
-          <template #default="{ row }">
-            {{ row[col.key] }}
-          </template>
-        </el-table-column>
-      </template>
-    </el-table>
-    <!-- 分页组件 -->
-    <cPagination :total="pageTotal" :page-num.sync="pageNum" :page-size.sync="pageSize" @change-page-num="changePageNum"
-      @change-page-size="changePageSize" />
-  </div>
-</template>
-<script>
-import cPagination from "@/components/table/Pagination"
-export default {
-  name: "cTable",
-  data() {
-    return {
-      time: null
-    };
-  },
-  props: {
-    tableData: {
-      type: Array,
-      default() {
-        return [];
-      }
-    },
-    tableHeight: {
-      type: Number,
-      default: null
-    },
-    tableLoading: {
-      type: Boolean,
-      default: false
-    },
-    tableRef: {
-      type: String,
-      default: "multipleTable"
-    },
-    tableSize: {
-      type: String,
-      default: "mini"
-    },
-    tableColumnsConfig: {
-      type: Array,
-      default() {
-        return [];
-      }
-    },
-    pageTotal: {
-      type: Number
-    },
-    pageNum: {
-      type: Number,
-      default: 1
-    },
-    pageSize: {
-      type: Number,
-      default: 10
-    },
-    pageChange: {
-      type: Function,
-      default: () => { }
-    },
-    handleSelection: {
-      type: Function,
-      default: () => { }
-    }
-  },
-  components: {
-    cPagination
-  },
-  methods: {
-    changePageNum(val) {
-      // console.log(val)
-      // this.pageNum = val
-      this.$emit("update:pageNum", val);
-      this.pageChange();
-    },
-    changePageSize(val) {
-      this.$emit("update:pageSize", val);
-      if (val * (this.pageNum - 1) <= this.pageTotal) {
-        this.pageChange();
-      }
-    },
-    // 单击
-    rowClick() {
-      // this.time && clearTimeout(this.time);
-      // this.time = setTimeout(() => {
-      //   this.$refs[this.tableRef].toggleRowSelection(row);
-      // }, 200);
-    },
-    // 双击
-    rowDblClick() {
-      // this.time && clearTimeout(this.time);
-    },
-    clearSelection() {
-      this.$refs[this.tableRef].clearSelection();
-    },
-    handleSelectionChange(val) {
-      this.handleSelection(val);
-    }
-  }
-};
-</script>
-<style lang="scss" scoped>
-.el-table {
-  border: none;
-}
-</style>
\ No newline at end of file
diff --git a/web/src/plugins/public.js b/web/src/plugins/public.js
index 10fb130..cd06262 100644
--- a/web/src/plugins/public.js
+++ b/web/src/plugins/public.js
@@ -1,167 +1,196 @@
-import axios from 'axios'
-import Vue from 'vue'
-/**
- * 防抖函数
- */
- export const debounce = (fun, time) => {
-	let timer = null
-	return function() {
-		if(timer) {
-			clearTimeout(timer)
-		}
-		let args = arguments
-		timer = setTimeout(() => {
-			fun.apply(this, args)
-		}, time)
-	}
-}
-
-/**
- * 节流函数
- */
-export const throttle = (fun, time) => {
-	let strTime = 0
-	return function() {
-		let endTime = new Date()
-		let args = arguments
-		if(endTime - strTime > time) {
-			strTime = endTime
-			fun.apply(this, args)
-		}
-	}
-}
-
-/**
- * 动态切换组件尺寸
-*/
-export const changeSize = () => {
-  // let size = ""
-  // let viewWidth = document.documentElement.clientWidth
-  // switch(true) {
-  //   case viewWidth <= 1024:
-  //     size = "mini"
-  //     break;
-  //   case viewWidth <= 1280:
-  //     size = "small"
-  //     break;
-  //   case viewWidth <= 1600:
-  //     size = "medium"
-  //     break;
-  //   default:
-  //     size = ""
-  // }
-  // return size
-	return 'small'
-}
-
-/**
- * 存入/更新cookie信息
- * name: cookie 名称
- * values: cookie 值
- * times: 过期时间
-*/
-export const setCookie = (name, values, times) => {
-	let date = new Date();
-	let params = JSON.stringify(values)
-	date.setTime(date.getTime() + (times * 24 * 60 * 60 * 1000));
-	document.cookie = `${name}=${params};expires=${date.toGMTString()}`;
-}
-
-/**
- * 获取cookie信息
- * name: 存入的cookie名
-*/
-export const getCookie = (name) => {
-	let data = "";
-	let list = document.cookie.split(';');
-	list.forEach(item => {
-		item = item.split('=')
-		if(item[0].replace(/^\s+|\s+$/g,"") === name) {
-			data = JSON.parse(item[item.length - 1])
-		}
-	})
-	return data
-}
-
-/**
- * 处理时间戳
-*/
-export const changeTime = (time) => {
-	const date = new Date(time);
-	console.log(date,'---');
-	let year = date.getFullYear();
-	let month = date.getMonth() + 1;
-	month = month > 9 ? month : `0${month}`;
-	let day = date.getDate() > 9 ? date.getDate() : `0${date.getDate()}`;
-	let hours = date.getHours() > 9 ? date.getHours() : `0${date.getHours()}`;
-	let min = date.getMinutes() > 9 ? date.getMinutes() : `0${date.getMinutes()}`;
-	let sec = date.getSeconds() > 9 ? date.getSeconds() : `0${date.getSeconds()}`;
-	return `${year}-${month}-${day} ${hours}:${min}:${sec}`
-}
-
-/**
- * 下载文件公共方法
-*/
-
-export const downLoadFile = (data, url) => {
-	let link = document.createElement("a")
-	link.style.display = "none";
-	link.href = `${process.env.VUE_APP_BASE_URL}${url}?authcode=${data}`;
-	document.body.appendChild(link)
-	link.click();
-	URL.revokeObjectURL(link.href) // 释放URL 对象
-}
-
-export const downFiles = (data, fileName = 'downLoad', fileType) => {
-	let url = URL.createObjectURL(new Blob([data]))
-	let link = document.createElement("a")
-	link.style.display = "none";
-	link.href = url;
-	link.setAttribute("download", `${fileName}.${fileType}`)
-	document.body.appendChild(link)
-	link.click();
-	URL.revokeObjectURL(link.href) // 释放URL 对象
-}
-
-/**
- * 转二维码(blob流转地址)
- */
-export const tranQr = (val) => {
-	let imageUrl = '';
-	Vue.prototype.$api.Print.getPrintPhone({num:val}).then(res=>{
-		imageUrl = URL.createObjectURL(new Blob([res.data]));
-	})
-	return imageUrl
-}
-
-
-export const downLoadQR = (data, url) => {
-	axios({
-		method: 'get',
-		url: `${process.env.VUE_APP_BASE_URL}${url}?num=${data}`,
-		responseType: 'blob'
-	}).then((res) => {
-		downFiles(res.data, '二维码图片','png')
-	})
-	// let link = document.createElement("a")
-	// link.style.display = "none";
-	// link.href = ;
-	// document.body.appendChild(link)
-	// link.click();
-	// URL.revokeObjectURL(link.href) // 释放URL 对象
-
-	// link.href = `${process.env.VUE_APP_BASE_URL}${url}?num=${data}`;
-	// link.setAttribute("download", '二维码图片.png')
-	// document.body.appendChild(link)
-	// link.click();
-}
-//通过扫描二维码跳转详情页面
-export const downLoadQRDetails = (data,id,typePage, url) => {
-	axios({
-		method: 'get',
-		url: `${process.env.VUE_APP_BASE_URL}${url}?num=${data}&bigDeviceId=${id}&type=${typePage}`,
-		responseType: 'blob'
-	}).then((res) => {
-		downFiles(res.data, '二维码图片','png')
-	})
-}
\ No newline at end of file
+import axios from 'axios'
+import Vue from 'vue'
+/**
+ * 防抖函数
+ */
+ export const debounce = (fun, time) => {
+	let timer = null
+	return function() {
+		if(timer) {
+			clearTimeout(timer)
+		}
+		let args = arguments
+		timer = setTimeout(() => {
+			fun.apply(this, args)
+		}, time)
+	}
+}
+
+/**
+ * 节流函数
+ */
+export const throttle = (fun, time) => {
+	let strTime = 0
+	return function() {
+		let endTime = new Date()
+		let args = arguments
+		if(endTime - strTime > time) {
+			strTime = endTime
+			fun.apply(this, args)
+		}
+	}
+}
+
+/**
+ * 动态切换组件尺寸
+*/
+export const changeSize = () => {
+  // let size = ""
+  // let viewWidth = document.documentElement.clientWidth
+  // switch(true) {
+  //   case viewWidth <= 1024:
+  //     size = "mini"
+  //     break;
+  //   case viewWidth <= 1280:
+  //     size = "small"
+  //     break;
+  //   case viewWidth <= 1600:
+  //     size = "medium"
+  //     break;
+  //   default:
+  //     size = ""
+  // }
+  // return size
+	return 'small'
+}
+
+/**
+ * 存入/更新cookie信息
+ * name: cookie 名称
+ * values: cookie 值
+ * times: 过期时间
+*/
+export const setCookie = (name, values, times) => {
+	let date = new Date();
+	let params = JSON.stringify(values)
+	date.setTime(date.getTime() + (times * 24 * 60 * 60 * 1000));
+	document.cookie = `${name}=${params};expires=${date.toGMTString()}`;
+}
+
+/**
+ * 获取cookie信息
+ * name: 存入的cookie名
+*/
+export const getCookie = (name) => {
+	let data = "";
+	let list = document.cookie.split(';');
+	list.forEach(item => {
+		item = item.split('=')
+		if(item[0].replace(/^\s+|\s+$/g,"") === name) {
+			data = JSON.parse(item[item.length - 1])
+		}
+	})
+	return data
+}
+
+/**
+ * 处理时间戳
+*/
+export const changeTime = (time) => {
+	const date = new Date(time);
+	console.log(date,'---');
+	let year = date.getFullYear();
+	let month = date.getMonth() + 1;
+	month = month > 9 ? month : `0${month}`;
+	let day = date.getDate() > 9 ? date.getDate() : `0${date.getDate()}`;
+	let hours = date.getHours() > 9 ? date.getHours() : `0${date.getHours()}`;
+	let min = date.getMinutes() > 9 ? date.getMinutes() : `0${date.getMinutes()}`;
+	let sec = date.getSeconds() > 9 ? date.getSeconds() : `0${date.getSeconds()}`;
+	return `${year}-${month}-${day} ${hours}:${min}:${sec}`
+}
+
+/**
+ * 下载文件公共方法
+*/
+
+export const downLoadFile = (data, url) => {
+	let link = document.createElement("a")
+	link.style.display = "none";
+	link.href = `${process.env.VUE_APP_BASE_URL}${url}?authcode=${data}`;
+	document.body.appendChild(link)
+	link.click();
+	URL.revokeObjectURL(link.href) // 释放URL 对象
+}
+
+export const downFiles = (data, fileName = 'downLoad', fileType) => {
+	let url = URL.createObjectURL(new Blob([data]))
+	let link = document.createElement("a")
+	link.style.display = "none";
+	link.href = url;
+	link.setAttribute("download", `${fileName}.${fileType}`)
+	document.body.appendChild(link)
+	link.click();
+	URL.revokeObjectURL(link.href) // 释放URL 对象
+}
+
+/**
+ * 转二维码(blob流转地址)
+ */
+export const tranQr = (val) => {
+	let imageUrl = '';
+	Vue.prototype.$api.Print.getPrintPhone({num:val}).then(res=>{
+		imageUrl = URL.createObjectURL(new Blob([res.data]));
+	})
+	return imageUrl
+}
+
+
+export const downLoadQR = (data, url) => {
+	axios({
+		method: 'get',
+		url: `${process.env.VUE_APP_BASE_URL}${url}?num=${data}`,
+		responseType: 'blob'
+	}).then((res) => {
+		downFiles(res.data, '二维码图片','png')
+	})
+	// let link = document.createElement("a")
+	// link.style.display = "none";
+	// link.href = ;
+	// document.body.appendChild(link)
+	// link.click();
+	// URL.revokeObjectURL(link.href) // 释放URL 对象
+
+	// link.href = `${process.env.VUE_APP_BASE_URL}${url}?num=${data}`;
+	// link.setAttribute("download", '二维码图片.png')
+	// document.body.appendChild(link)
+	// link.click();
+}
+//通过扫描二维码跳转详情页面
+export const downLoadQRDetails = (data,id,typePage, url) => {
+	axios({
+		method: 'get',
+		url: `${process.env.VUE_APP_BASE_URL}${url}?num=${data}&bigDeviceId=${id}&type=${typePage}`,
+		responseType: 'blob'
+	}).then((res) => {
+		downFiles(res.data, '二维码图片','png')
+	})
+}
+
+/* Date原型添加format方法 */
+Date.prototype.format = function (fmt) {
+  fmt = fmt || 'yyyy-MM-dd'
+  var o = {
+    'M+': this.getMonth() + 1, //月份
+    'd+': this.getDate(), //日
+    'h+': this.getHours(), //小时
+    'm+': this.getMinutes(), //分
+    's+': this.getSeconds(), //秒
+    'q+': Math.floor((this.getMonth() + 3) / 3), //季度
+    S: this.getMilliseconds() //毫秒
+  }
+  if (/(y+)/.test(fmt)) {
+    fmt = fmt.replace(
+      RegExp.$1,
+      (this.getFullYear() + '').substr(4 - RegExp.$1.length)
+    )
+  }
+  for (var k in o) {
+    if (new RegExp('(' + k + ')').test(fmt)) {
+      fmt = fmt.replace(
+        RegExp.$1,
+        RegExp.$1.length == 1 ? o[k] : ('00' + o[k]).substr(('' + o[k]).length)
+      )
+    }
+  }
+  return fmt
+}    
\ No newline at end of file
diff --git a/web/src/style/layout-main.scss b/web/src/style/layout-main.scss
index 3eb82ea..e6479f6 100644
--- a/web/src/style/layout-main.scss
+++ b/web/src/style/layout-main.scss
@@ -1,464 +1,497 @@
-// 页面主体样式
-.main {
-  position: absolute;
-  display: flex;
-  flex-direction: column;
-  width: calc(100% - 30px);
-  height: calc(100% - 30px);
-  border-radius: 4px;
-  background: url('~@/assets/main-bg.png') no-repeat;
-  background-size: 100% 100%;
-  // background: linear-gradient(170deg, rgba(14, 54, 120, .3) 20%, #0E3678 35%, rgba(14, 54, 120, .3) 55%);
-  box-shadow: 0 2px 12px 0 rgba(57, 181, 254, 0.6);
-
-  //
-  .main_tabs {
-    padding: 20px 0 10px;
-
-    /deep/ .el-tabs__nav-wrap {
-      padding: 0 20px;
-      font-size: 20px;
-
-      &::after {
-        height: 1px;
-        background-color: #1B428F;
-      }
-    }
-
-    /deep/ .el-tabs__active-bar {
-      background-color: #39B5FE;
-    }
-  }
-  
-  // 主体头部样式
-  .main_header {
-    display: flex;
-    margin: 9px 24px 24px;
-    font-size: 1rem;
-    flex-wrap: wrap;
-
-    .header_item {
-      display: flex;
-      align-items: center;
-      margin-right: 24px;
-      margin-top: 15px;
-    }
-
-    .header_label {
-      color: #EAEAEA;
-      white-space: nowrap;
-      text-align: right;
-      // min-width: 100px;
-    }
-  }
-  
-  //
-  .main_functional {
-    display: flex;
-    margin: 10px 10px 20px;
-  }
-
-  // 主体内容样式
-  .main_content {
-    position: relative;
-    flex: 1;
-    margin: 0 24px;
-    overflow: hidden;
-  }
-
-  // 主体底部样式
-  .main_footer {
-    height: 50px;
-    margin: 10px;
-    text-align: right;
-    margin-top: 35px;
-    ::v-deep.el-input__inner{
-      border:1px solid #294366;
-      background-color: #102448;
-    }
-    ::v-deep .el-pagination__jump{
-      color: #bfbfbf;
-    }
-  }
-
-  ::v-deep .search_btn {
-    border: 0 none;
-    color: #fff;
-    background: url('~@/assets/search_bg.png') no-repeat;
-    background-size: 100% 100%;
-    &:hover {
-      color: #F69C42;
-    }
-  }
-
-  ::v-deep .table_btn {
-    border: 0 none;
-    color: #39B5FE;
-    background: url('~@/assets/table_btn.png') no-repeat;
-    background-size: 100% 100%;
-    &:hover {
-      color: rgba($color: #39B5FE, $alpha: .7);
-    }
-  }
-
-  ::v-deep .delete_btn {
-    border: 0 none;
-    color: #F94550;
-    background: url('~@/assets/error_btn.png') no-repeat;
-    background-size: 100% 100%;
-    &:hover {
-      color: rgba($color: #F94550, $alpha: .7);
-    }
-  }
-
-  ::v-deep .el-tabs__item {
-    color: #fff;
-  }
-  ::v-deep .el-loading-mask {
-    background-color: rgba($color: #132a5c, $alpha: .8);
-  }
-  ::v-deep .el-tabs__item.is-active {
-    color: #39B5FE;
-  }
-  ::v-deep .el-tabs__active-bar {
-    background-color: #39B5FE;
-  }
-  ::v-deep .el-tabs__nav-wrap::after {
-    background-color: #1c50ae;
-  }
-  ::v-deep .el-table {
-    background: transparent
-  }
-  ::v-deep .el-input__inner {
-    border: 1px solid #39B5FE;
-    background-color: transparent;
-    color: #fff;
-  }
-  ::v-deep .el-textarea__inner {
-    color: #fff;
-    background-color: transparent;
-    border: 1px solid #39B5FE;
-  }
-  ::v-deep .el-dialog__body .el-input__inner {
-    background-color: transparent;
-    border: 1px solid rgba(57, 181, 254, 1);
-    color: #fff;
-  }
-  ::v-deep .el-table tr {
-    background: transparent;
-  }
-  ::v-deep .el-table__expand-icon {
-    color: #22fffe;
-  }
-  ::v-deep .el-table--enable-row-hover .el-table__body tr:hover > td.el-table__cell {
-    background: rgba($color: #34AFED, $alpha: .5);
-  }
-  ::v-deep .el-table .warning-row {
-    background: transparent;
-  }
-  ::v-deep .el-table .success-row {
-    background: #031a46;
-  }
-  ::v-deep .el-table__body .el-table__row.hover-row td {
-    background-color: #0d3271;
-  }
-  ::v-deep .el-date-editor .el-range-input {
-    background-color: #0b2f66;
-    color: #fff;
-  }
-  ::v-deep .el-table tr {
-    &:nth-of-type(even) {
-      background: linear-gradient(
-        90deg,
-        transparent 3%, 
-        rgba(57, 166, 254, 0.4) 55%, 
-        transparent 95%);
-    }
-  }
-  ::v-deep .el-date-editor .el-range-separator {
-    // color: #606266;
-    color: #fff;
-  }
-  ::v-deep .el-upload--picture-card {
-    background-color: #0b2c64;
-    border-color: #39B5FE;
-  }
-  ::v-deep .el-radio {
-    padding-bottom: 16px;
-    color: #fff;
-  }
-  ::v-deep .el-radio__inner {
-    border-color: #39B5FE;
-    background-color: #061c48;
-  }
-  ::v-deep .el-input-group__append,
-  .el-input-group__prepend {
-    background-color: #0B2559;
-    border-color: #39B5FE;
-    color: #fff;
-    padding: 0 10px;
-  }
-  /deep/.el-date-editor .el-range-input{
-    background-color: transparent;
-  }
-}
-
-// 列表按钮侧边竖线
-.line {
-  position: relative;
-
-  &::before {
-    content: "";
-    position: absolute;
-    top: 50%;
-    right: -6px;
-    transform: translateY(-50%);
-    width: 1px;
-    height: 40%;
-    background-color: #eaeaea;
-  }
-}
-.preview_dialog{
-  ::v-deep .el-dialog__headerbtn{
-    font-size: 24px!important;
-  }
-}
-
-.scan_dialog{
-  ::v-deep .el-dialog{
-    min-width: 200px!important;
-    margin-top: 30vh!important;
-  }
-  ::v-deep .el-dialog__header{
-    padding-bottom: 10px!important;
-  }
-  ::v-deep .el-dialog__body{
-    padding: 10px 10px 26px!important;
-  }
-}
-// 弹出框标题蓝线
-.prop_dialog {
-  ::v-deep .el-dialog__header {
-    position: relative;
-    padding: 20px 20px 20px 24px;
-  }
-
-  ::v-deep .el-dialog__headerbtn .el-dialog__close {
-    color: #BFBFBF;
-  }
-
-  ::v-deep .el-dialog__body {
-    padding: 30px 10px 10px 24px;
-  }
-  ::v-deep .el-dialog__title {
-    color: #39B5FE;
-    font-weight: 600;
-  }
-  ::v-deep .el-dialog__footer {
-    padding: 20px 20px 20px;
-  }
-  .submit_btn {
-    border: 0 none;
-    color: #39B5FE;
-    background: url('~@/assets/submit_bg.png') no-repeat;
-    background-size: 100% 100%;
-    &:hover {
-      color: #39B5FE;
-    }
-  }
-}
-
-// form表单样式
-.rule_form {
-  padding-right: 15px;
-  max-height: 550px;
-  overflow-y: auto;
-
-  ::v-deep .el-select {
-    width: 100%;
-  }
-  ::v-deep .el-date-editor.el-input,
-  .el-date-editor.el-input__inner {
-    width: 100%;
-    color: #fff;
-    background-color: #102351;
-  }
-  ::v-deep.el-form-item__label-wrap .el-form-item__label {
-    color: #fff;
-  }
-  .text_ranage {
-    width: 210px;
-    padding: 0px 15px;
-    margin-left: 15px;
-    background-color: #102351;
-    color: #fff;
-  }
-  ::v-deep .el-form-item__label {
-    color: #fff;
-  }
-  ::v-deep .el-form-item__content {
-    display: flex;
-    flex-direction: row;
-    .marks_text {
-      margin-left: 20px;
-      padding: 0px 10px;
-      width: 18rem;
-      border: 1px solid #deeaff;
-      background-color: #deeaff;
-
-      .el-icon-time:before {
-        color: #2c77ff;
-        padding-right: 10px;
-      }
-    }
-    .el-switch {
-      padding-top: 15px;
-    }
-  }
-  ::v-deep .el-textarea__inner {
-    font-family: Avenir, Helvetica, Arial, sans-serif;
-    background-color: #102351;
-  }
-
-  ::v-deep .el-cascader {
-    width: 100%;
-  }
-  ::v-deep .el-input-number {
-    width: 100%;
-  }
-  ::v-deep .el-input-number .el-input__inner {
-    text-align: left;
-    background-color: #102351;
-  }
-}
-
-.tabs_main {
-  margin: 0;
-  width: 100%;
-  height: 100%;
-  box-shadow: none;
-}
-
-/deep/ .el-textarea__inner{
-  background-color: transparent;
-  border: 1px solid #39B5FE;
-}
-.inspection_content {
-  display: flex;
-  flex-direction: column;
-  padding: 0 20px;
-  height: 500px;
-  overflow: auto;
-
-  .inspection_content_item {
-    display: flex;
-    flex-direction: column;
-    margin-bottom: 20px;
-    width: 100%;
-    color: #E1E3E9;
-
-    .inspection_content_item--header {
-      position: relative;
-      display: flex;
-      justify-content: space-between;
-      padding: 0 0 5px 15px;
-      border-bottom: 1px solid #39B5FE;
-
-      &::after {
-        content: "";
-        position: absolute;
-        top: 50%;
-        left: 0;
-        transform: translateY(-50%);
-        width: 3px;
-        height: 70%;
-        background: #19F7F7;
-      }
-
-      .label {
-        color: #19F7F7;
-      }
-    }
-
-    .inspection_content_item--content {
-      display: flex;
-      flex-direction: column;
-      padding: 10px 0;
-
-      .info_image {
-        margin: 10px 0;
-        width: 150px;
-        height: 140px;
-        border: 1px solid #39B5FE;
-        border-radius: 6px;
-        cursor: pointer;
-      }
-    }
-  }
-}
-
-.prop_image {
-  padding: 0 15px 20px 0;
-  width: 100%;
-  height: 450px;
-  background: transparent;
-}
-//预览按钮样式
-.preview_content{
-  padding: 24px 20px;
-  box-shadow: 2px 2px 12px 0 rgba(0, 0, 0, 0.06);
-  .preview_titles{
-  text-align: center;
-  font-size: 28px;
-  font-weight: 600;
-  color: #39B5FE;
-  }
-  .preview_times{
-      text-align: center;
-      font-size: 14px;
-      color: #999999;
-      margin-top: 15px;
-      padding-bottom: 15px;
-      border-bottom: 1px dashed #eaeaea;
-  }
-  .preview_main{
-    padding: 24px 20px 0px;
-    overflow: auto;
-  }
-}
-
-/deep/.el-dialog.is-fullscreen{
-  overflow-x: hidden;
-  overflow-y: auto;
-  width: 64%;
-  height: 92%;
-  margin-top: 40px;
-  background: #f6f6f6!important;
-}
-
-/deep/.el-input-number__decrease{
-  border-right-color: #39B5FE;
-  .el-icon-minus:before{
-    color: #19F6F8;
-  }
-}
-
-/deep/.el-input-number__increase{
-  border-left-color: #39B5FE;
-  .el-icon-plus:before{
-    color: #19F6F8;
-  }
-}
-
-/deep/.el-input-number__increase{
-  background-color: transparent;
-}
-
-/deep/.el-input-number__decrease{
-  background-color: transparent;
-}
-
-/deep/.el-textarea.is-disabled .el-textarea__inner{
-  background-color: #0D2657;
-  border-color: #39B5FE;
+// 页面主体样式
+.main {
+  position: absolute;
+  display: flex;
+  flex-direction: column;
+  width: calc(100% - 30px);
+  height: calc(100% - 30px);
+  border-radius: 4px;
+  background: url('~@/assets/main-bg.png') no-repeat;
+  background-size: 100% 100%;
+  // background: linear-gradient(170deg, rgba(14, 54, 120, .3) 20%, #0E3678 35%, rgba(14, 54, 120, .3) 55%);
+  box-shadow: 0 2px 12px 0 rgba(57, 181, 254, 0.6);
+
+  //
+  .main_tabs {
+    padding: 20px 0 10px;
+
+    /deep/ .el-tabs__nav-wrap {
+      padding: 0 20px;
+      font-size: 20px;
+
+      &::after {
+        height: 1px;
+        background-color: #1B428F;
+      }
+    }
+
+    /deep/ .el-tabs__active-bar {
+      background-color: #39B5FE;
+    }
+  }
+  
+  // 主体头部样式
+  .main_header {
+    display: flex;
+    margin: 9px 24px 24px;
+    font-size: 1rem;
+    flex-wrap: wrap;
+
+    .header_item {
+      display: flex;
+      align-items: center;
+      margin-right: 24px;
+      margin-top: 15px;
+    }
+
+    .header_label {
+      color: #EAEAEA;
+      white-space: nowrap;
+      text-align: right;
+      // min-width: 100px;
+    }
+  }
+  
+  //
+  .main_functional {
+    display: flex;
+    margin: 10px 10px 20px;
+  }
+
+  // 主体内容样式
+  .main_content {
+    position: relative;
+    flex: 1;
+    margin: 0 24px;
+    overflow: hidden;
+  }
+
+  // 主体底部样式
+  .main_footer {
+    height: 50px;
+    margin: 10px;
+    text-align: right;
+    margin-top: 35px;
+    ::v-deep.el-input__inner{
+      border:1px solid #294366;
+      background-color: #102448;
+    }
+    ::v-deep .el-pagination__jump{
+      color: #bfbfbf;
+    }
+  }
+
+  ::v-deep .search_btn {
+    border: 0 none;
+    color: #fff;
+    background: url('~@/assets/search_bg.png') no-repeat;
+    background-size: 100% 100%;
+    &:hover {
+      color: #F69C42;
+    }
+  }
+
+  ::v-deep .table_btn {
+    border: 0 none;
+    color: #39B5FE;
+    background: url('~@/assets/table_btn.png') no-repeat;
+    background-size: 100% 100%;
+    &:hover {
+      color: rgba($color: #39B5FE, $alpha: .7);
+    }
+  }
+
+  ::v-deep .delete_btn {
+    border: 0 none;
+    color: #F94550;
+    background: url('~@/assets/error_btn.png') no-repeat;
+    background-size: 100% 100%;
+    &:hover {
+      color: rgba($color: #F94550, $alpha: .7);
+    }
+  }
+
+  ::v-deep .el-checkbox__inner {
+    border: 1px solid #3DC8FF;
+    background-color: transparent;
+    &::after{
+      border: 1px solid #3DC8FF;
+      border-left: 0;
+      border-top: 0;
+    }
+  }
+  ::v-deep .el-tabs__item {
+    color: #fff;
+  }
+  ::v-deep .el-loading-mask {
+    background-color: rgba($color: #132a5c, $alpha: .8);
+  }
+  ::v-deep .el-tabs__item.is-active {
+    color: #39B5FE;
+  }
+  ::v-deep .el-tabs__active-bar {
+    background-color: #39B5FE;
+  }
+  ::v-deep .el-tabs__nav-wrap::after {
+    background-color: #1c50ae;
+  }
+  ::v-deep .el-table {
+    background: transparent
+  }
+  ::v-deep .el-input__inner {
+    border: 1px solid #39B5FE;
+    background-color: transparent;
+    color: #fff;
+  }
+  ::v-deep .el-textarea__inner {
+    color: #fff;
+    background-color: transparent;
+    border: 1px solid #39B5FE;
+  }
+  ::v-deep .el-dialog__body .el-input__inner {
+    background-color: transparent;
+    border: 1px solid rgba(57, 181, 254, 1);
+    color: #fff;
+  }
+  ::v-deep .el-table tr {
+    background: transparent;
+  }
+  ::v-deep .el-table__expand-icon {
+    color: #22fffe;
+  }
+  ::v-deep .el-table--enable-row-hover .el-table__body tr:hover > td.el-table__cell {
+    background: rgba($color: #34AFED, $alpha: .5);
+  }
+  ::v-deep .el-table .warning-row {
+    background: transparent;
+  }
+  ::v-deep .el-table .success-row {
+    background: #031a46;
+  }
+  ::v-deep .el-table__body .el-table__row.hover-row td {
+    background-color: #0d3271;
+  }
+  ::v-deep .el-date-editor .el-range-input {
+    background-color: #0b2f66;
+    color: #fff;
+  }
+  ::v-deep .el-table tr {
+    &:nth-of-type(even) {
+      background: linear-gradient(
+        90deg,
+        transparent 3%, 
+        rgba(57, 166, 254, 0.4) 55%, 
+        transparent 95%);
+    }
+  }
+  ::v-deep .el-date-editor .el-range-separator {
+    // color: #606266;
+    color: #fff;
+  }
+  ::v-deep .el-upload--picture-card {
+    background-color: #0b2c64;
+    border-color: #39B5FE;
+  }
+  ::v-deep .el-radio {
+    padding-bottom: 16px;
+    color: #fff;
+  }
+  ::v-deep .el-radio__inner {
+    border-color: #39B5FE;
+    background-color: #061c48;
+  }
+  ::v-deep .el-input-group__append,
+  .el-input-group__prepend {
+    background-color: #0B2559;
+    border-color: #39B5FE;
+    color: #fff;
+    padding: 0 10px;
+  }
+  /deep/.el-date-editor .el-range-input{
+    background-color: transparent;
+  }
+}
+
+// 列表按钮侧边竖线
+.line {
+  position: relative;
+
+  &::before {
+    content: "";
+    position: absolute;
+    top: 50%;
+    right: -6px;
+    transform: translateY(-50%);
+    width: 1px;
+    height: 40%;
+    background-color: #eaeaea;
+  }
+}
+.preview_dialog{
+  ::v-deep .el-dialog__headerbtn{
+    font-size: 24px!important;
+  }
+}
+
+.scan_dialog{
+  ::v-deep .el-dialog{
+    min-width: 200px!important;
+    margin-top: 30vh!important;
+  }
+  ::v-deep .el-dialog__header{
+    padding-bottom: 10px!important;
+  }
+  ::v-deep .el-dialog__body{
+    padding: 10px 10px 26px!important;
+  }
+}
+// 弹出框标题蓝线
+.prop_dialog {
+  ::v-deep .el-dialog__header {
+    position: relative;
+    padding: 20px 20px 20px 24px;
+  }
+
+  ::v-deep .el-dialog__headerbtn .el-dialog__close {
+    color: #BFBFBF;
+  }
+
+  ::v-deep .el-dialog__body {
+    padding: 30px 10px 10px 24px;
+  }
+  ::v-deep .el-dialog__title {
+    color: #39B5FE;
+    font-weight: 600;
+  }
+  ::v-deep .el-dialog__footer {
+    padding: 20px 20px 20px;
+  }
+  .submit_btn {
+    border: 0 none;
+    color: #39B5FE;
+    background: url('~@/assets/submit_bg.png') no-repeat;
+    background-size: 100% 100%;
+    &:hover {
+      color: #39B5FE;
+    }
+  }
+}
+
+// form表单样式
+.rule_form {
+  padding-right: 15px;
+  max-height: 550px;
+  overflow-y: auto;
+
+  ::v-deep .el-select {
+    width: 100%;
+  }
+  ::v-deep .el-date-editor.el-input,
+  .el-date-editor.el-input__inner {
+    width: 100%;
+    color: #fff;
+    background-color: #102351;
+  }
+  ::v-deep.el-form-item__label-wrap .el-form-item__label {
+    color: #fff;
+  }
+  .text_ranage {
+    width: 210px;
+    padding: 0px 15px;
+    margin-left: 15px;
+    background-color: #102351;
+    color: #fff;
+  }
+  ::v-deep .el-form-item__label {
+    color: #fff;
+  }
+  ::v-deep .el-form-item__content {
+    display: flex;
+    flex-direction: row;
+    .marks_text {
+      margin-left: 20px;
+      padding: 0px 10px;
+      width: 18rem;
+      border: 1px solid #deeaff;
+      background-color: #deeaff;
+
+      .el-icon-time:before {
+        color: #2c77ff;
+        padding-right: 10px;
+      }
+    }
+    .el-switch {
+      padding-top: 15px;
+    }
+  }
+  ::v-deep .el-textarea__inner {
+    font-family: Avenir, Helvetica, Arial, sans-serif;
+    background-color: #102351;
+  }
+
+  ::v-deep .el-cascader {
+    width: 100%;
+  }
+  ::v-deep .el-input-number {
+    width: 100%;
+  }
+  ::v-deep .el-input-number .el-input__inner {
+    text-align: left;
+    background-color: #102351;
+  }
+}
+
+.tabs_main {
+  margin: 0;
+  width: 100%;
+  height: 100%;
+  box-shadow: none;
+}
+
+/deep/ .el-textarea__inner{
+  background-color: transparent;
+  border: 1px solid #39B5FE;
+}
+.inspection_content {
+  display: flex;
+  flex-direction: column;
+  padding: 0 20px;
+  height: 500px;
+  overflow: auto;
+
+  .inspection_content_item {
+    display: flex;
+    flex-direction: column;
+    margin-bottom: 20px;
+    width: 100%;
+    color: #E1E3E9;
+
+    .inspection_content_item--header {
+      position: relative;
+      display: flex;
+      justify-content: space-between;
+      padding: 0 0 5px 15px;
+      border-bottom: 1px solid #39B5FE;
+
+      &::after {
+        content: "";
+        position: absolute;
+        top: 50%;
+        left: 0;
+        transform: translateY(-50%);
+        width: 3px;
+        height: 70%;
+        background: #19F7F7;
+      }
+
+      .label {
+        color: #19F7F7;
+      }
+    }
+
+    .inspection_content_item--content {
+      display: flex;
+      flex-direction: column;
+      padding: 10px 0;
+
+      .info_image {
+        margin: 10px 0;
+        width: 150px;
+        height: 140px;
+        border: 1px solid #39B5FE;
+        border-radius: 6px;
+        cursor: pointer;
+      }
+    }
+  }
+}
+
+.prop_image {
+  padding: 0 15px 20px 0;
+  width: 100%;
+  height: 450px;
+  background: transparent;
+}
+//预览按钮样式
+.preview_content{
+  padding: 24px 20px;
+  box-shadow: 2px 2px 12px 0 rgba(0, 0, 0, 0.06);
+  .preview_titles{
+  text-align: center;
+  font-size: 28px;
+  font-weight: 600;
+  color: #39B5FE;
+  }
+  .preview_times{
+      text-align: center;
+      font-size: 14px;
+      color: #999999;
+      margin-top: 15px;
+      padding-bottom: 15px;
+      border-bottom: 1px dashed #eaeaea;
+  }
+  .preview_main{
+    padding: 24px 20px 0px;
+    overflow: auto;
+  }
+}
+
+/deep/.el-dialog.is-fullscreen{
+  overflow-x: hidden;
+  overflow-y: auto;
+  width: 64%;
+  height: 92%;
+  margin-top: 40px;
+  background: #f6f6f6!important;
+}
+
+/deep/.el-input-number__decrease{
+  border-right-color: #39B5FE;
+  .el-icon-minus:before{
+    color: #19F6F8;
+  }
+}
+
+/deep/.el-input-number__increase{
+  border-left-color: #39B5FE;
+  .el-icon-plus:before{
+    color: #19F6F8;
+  }
+}
+
+/deep/.el-input-number__increase{
+  background-color: transparent;
+}
+
+/deep/.el-input-number__decrease{
+  background-color: transparent;
+}
+
+/deep/.el-textarea.is-disabled .el-textarea__inner{
+  background-color: #0D2657;
+  border-color: #39B5FE;
+}
+
+/* 滚动条统一样式修改 */
+/deep/ {
+  &::-webkit-scrollbar {
+    width: 8px;
+    height: 8px;
+  }
+
+  &::-webkit-scrollbar-corner {
+    background-color: transparent;
+  }
+
+  &::-webkit-scrollbar-thumb {
+    border-radius: 10px;
+    box-shadow: inset 0 0 5px transparent;
+    background: #39B5FE;
+  }
+
+  &::-webkit-scrollbar-track {
+    box-shadow: inset 0 0 5px transparent;
+    border-radius: 10px;
+    background: rgba(76, 188, 254, .3);
+  }
 }
\ No newline at end of file
diff --git a/web/src/views/ProjectManage/SectionManage.vue b/web/src/views/ProjectManage/SectionManage.vue
index 09e277f..dbae05b 100644
--- a/web/src/views/ProjectManage/SectionManage.vue
+++ b/web/src/views/ProjectManage/SectionManage.vue
@@ -1,584 +1,637 @@
-<template>
-  <!-- 工程项目管理 ==> 单位工程管理-->
-  <div class="main">
-    <div class="main_header">
-      <div class="header_item">
-        <span class="header_label">标段名称:</span>
-        <el-input v-model="queryInfo.segmentName" clearable placeholder="请输入标段名称"></el-input>
-      </div>
-      <div class="header_item">
-        <span class="header_label">项目名称:</span>
-        <el-select v-model="queryInfo.proId" placeholder="请选择项目名称" clearable>
-          <el-option v-for="item in optionsProject" :key="item.proId" :label="item.proName" :value="item.proId">
-          </el-option>
-        </el-select>
-      </div>
-      <div class="header_item">
-        <el-button icon="el-icon-search" v-permission="'search'" @click="queryReset">查询</el-button>
-        <el-button class="search_btn" icon="el-icon-plus" v-permission="'insert'" @click="addRow">新增</el-button>
-      </div>
-    </div>
-    <div class="main_content">
-      <cpnTable :table-index="true" :table-data="dataList" :table-columns="tableColumns" :page-total="total"
-        :page-num.sync="queryInfo.pageNum" :page-size.sync="queryInfo.pageSize" :page-change="pageChange">
-
-        <template #finished="{ row }">
-          <div style="cursor: pointer;" @click="showDetail(row)">{{ row.completedQuantity }}</div>
-        </template>
-      </cpnTable>
-    </div>
-    <!-- dialog -->
-    <el-dialog class="prop_dialog" v-if="isRender" :title="dialogTitle" :visible.sync="asyncVisible" width="660px"
-      @close="closeForm">
-      <el-form ref="ruleForm" :model="ruleForm" :rules="rules" label-width="auto" class="rule_form">
-        <el-form-item label="项目名称:" prop="proId">
-          <el-select v-model="ruleForm.proId" placeholder="请选择" @change="changeNeed">
-            <el-option v-for="item in optionsProject" :key="item.proId" :label="item.proName" :value="item.proId">
-            </el-option>
-          </el-select>
-        </el-form-item>
-        <el-form-item label="起讫里程:" prop="mileage">
-          <el-input v-model="ruleForm.mileage" clearable placeholder="请输入起讫里程"></el-input>
-        </el-form-item>
-        <el-form-item label="单位工程名称:" prop="unitProjectName">
-          <el-input v-model="ruleForm.unitProjectName" clearable placeholder="请输入单位工程名称"></el-input>
-        </el-form-item>
-        <el-form-item label="需求负责人:" prop="segmentAdmin">
-          <el-select v-model="ruleForm.segmentAdmin" placeholder="请选择求负责人">
-            <el-option v-for="item in optionsUser" :key="item.userId" :label="item.realName" :value="item.userId">
-            </el-option>
-          </el-select>
-        </el-form-item>
-        <el-form-item label="盾构单位:" prop="shieldEnp">
-          <el-input v-model="ruleForm.shieldEnp" clearable placeholder="请输入盾构单位"></el-input>
-        </el-form-item>
-        <el-form-item label="开始时间:" prop="startTime">
-          <el-date-picker v-model="ruleForm.startTime" value-format="yyyy-MM-dd" placeholder="请选择开始日期"></el-date-picker>
-        </el-form-item>
-        <el-form-item label="结束时间:" prop="endTime">
-          <el-date-picker v-model="ruleForm.endTime" value-format="yyyy-MM-dd" placeholder="请选择结束日期"></el-date-picker>
-        </el-form-item>
-        <el-form-item label="工期:" prop="duration">
-          <el-input v-model="ruleForm.duration" clearable placeholder="请输入工期"></el-input>
-        </el-form-item>
-        <el-form-item label="站点:" prop="station">
-          <el-input v-model="ruleForm.station" clearable placeholder="请输入站点"></el-input>
-        </el-form-item>
-        <div class="section_needs">标段需求</div>
-        <div class="section_needs_content">
-          <div v-for="item in ruleForm.segmentList" :key="item.dictId" class="needs_items">
-            <div class="needs_text">{{ item.dictName }}</div>
-            <el-input placeholder="请输入数量" type="number" v-model="item.needNum" class="needs_num">
-              <template slot="append">块</template>
-            </el-input>
-          </div>
-        </div>
-      </el-form>
-      <div slot="footer">
-        <el-button @click="asyncVisible = false">取 消</el-button>
-        <el-button class="submit_btn" @click="onSubmit('ruleForm')">提 交</el-button>
-      </div>
-    </el-dialog>
-    <!-- detail dialog -->
-    <el-dialog class="prop_dialog" v-if="detail.isRenderDetail" title="管片数量" :visible.sync="detail.asyncVisible"
-      width="1000px">
-      <el-card>
-        <div class="titles" v-for="item in detail.infoMap" :key="item.key">
-          {{ item.name }}&nbsp;:
-          <template v-if="item.key === 'waterproofType'">
-            <span>{{ detail.infos[item.key] === 0 ? '有' : '无' }} </span>
-          </template>
-          <template v-else-if="item.key === 'proHas'">
-            <span v-for="val in detail.infos[item.key]" :key="val.hasSteel">
-              {{ val.dictName }}
-            </span>
-          </template>
-          <template v-else>
-            <span>{{ detail.infos[item.key] }}</span>
-          </template>
-        </div>
-      </el-card>
-      <cpnTable :table-index="true" :table-data="detail.tableData" :table-columns="detail.tableColumns"
-        :page-total="detail.total" :page-num.sync="detail.pageNum" :page-size.sync="detail.pageSize"
-        :page-change="detailPageChange">
-      </cpnTable>
-    </el-dialog>
-  </div>
-</template>
-
-<script>
-import {throttle} from '../../plugins/public'; // 导入节流、动态切换组件尺寸方法
-import cpnTable from '@/components/element/Table'
-export default {
-  data() {
-    return {
-      isRender: false,
-      loading: false,
-      asyncVisible: false,
-      submitMode: '', // add update
-      total: 0,
-      queryInfo: {
-        pageNum: 1,
-        pageSize: 10,
-        segmentName: '',
-        proId: '',
-      },
-      dataList: [],
-      tableColumns: [],
-      optionsUser: [], //需求负责人
-      optionsProject: [], // 项目名称
-      ruleForm: {}, // 按钮表单
-      rules: {
-        proId: [{required: true, message: '请选择', trigger: 'change'}],
-        mileage: [{required: true, message: '请输入', trigger: 'blur'}],
-        unitProjectName: [{required: true, message: '请输入', trigger: 'blur'}],
-        segmentAdmin: [{required: true, message: '请选择', trigger: 'change'}],
-        shieldEnp: [{required: true, message: '请输入', trigger: 'blur'}],
-        startTime: [{required: true, message: '请选择', trigger: 'change'}],
-        endTime: [{required: true, message: '请选择', trigger: 'change'}],
-        duration: [{required: true, message: '请输入', trigger: 'blur'}],
-        station: [{required: true, message: '请输入', trigger: 'blur'}],
-      },
-      detail: {
-        rowId: '',
-        isRenderDetail: false,
-        asyncVisible: false,
-        pageNum: 1,
-        pageSize: 10,
-        total: 0,
-        tableData: [],
-        tableColumns: [],
-        infos: [],
-        infoMap: [
-          {
-            name: '项目名称',
-            key: 'proName',
-          },
-          {
-            name: '单位工程名称',
-            key: 'createUnit',
-          },
-          {
-            name: '外径',
-            key: 'outsideDiameter',
-          },
-          {
-            name: '內径',
-            key: 'innerDiameter',
-          },
-          {
-            name: '厚度',
-            key: 'thickness',
-          },
-          {
-            name: '环宽',
-            key: 'ringWidth',
-          },
-          {
-            name: '混凝土强度等级',
-            key: 'concreteStrengthGrade',
-          },
-          {
-            name: '抗渗等级',
-            key: 'impermeabilityLevel',
-          },
-          {
-            name: '配筋型号',
-            key: 'proHas',
-          },
-          {
-            name: '有无外弧面防水',
-            key: 'waterproofType',
-          }
-        ]
-      }
-    }
-  },
-  components: {
-    cpnTable
-  },
-  computed: {
-    isUpdate() {
-      return this.submitMode === 'update'
-    },
-    dialogTitle() {
-      return this.isUpdate ? '修改新增单位工程' : '新增单位工程'
-    },
-  },
-  created() {
-    this.setFormProps()
-    this.setTableColumn()
-    this.getLists()
-    this.getAllProjects()
-    this.getAllPersons()
-    // this.getAllBlocks() // 暂时没用,先屏蔽
-  },
-  methods: {
-    // 获取table列表数据
-    getLists() {
-      this.loading = true
-      let params = this.queryInfo
-      this.$api.Engineer.searchSegment(params).then(res => {
-        if (res.statusMsg === 'ok') {
-          this.total = res.data.total
-          this.dataList = res.data.list
-        }
-        this.loading = false
-      })
-    },
-    //获得所有人员
-    getAllPersons() {
-      this.$api.Engineer.getPersonsList({}).then(res => {
-        if (res.statusMsg === 'ok') {
-          this.optionsUser = res.data
-        } else {
-          this.$message.warning('人员名称获取失败')
-        }
-      })
-    },
-    //获得所有项目名称
-    getAllProjects() {
-      let obj = {
-        pageNum: 1,
-        pageSize: 100000000
-      }
-      this.$api.Engineer.searchProjects(obj).then(res => {
-        if (res.statusMsg === 'ok') {
-          this.optionsProject = res.data.list
-        } else {
-          this.$message.warning('项目名称获取失败')
-        }
-      })
-    },
-    //字典里获取所有标段块号
-    getAllBlocks() {
-      let params = {
-        pageNum: 1,
-        pageSize: 100000000
-      }
-      this.$api.Dictionary.searchDictionary(params).then(res => {
-        if (res.statusMsg === 'ok') {
-          const segmentList = []
-          res.data.list.forEach(item => {
-            if (item.dictType === '5') {
-              segmentList.push({
-                dictName: item.dictName,
-                needType: item.dictId,
-                needNum: 0,
-                segmentId: ''
-              })
-            }
-          })
-          this.ruleForm.segmentList = segmentList
-        } else {
-          this.$message.warning('标段获取失败')
-        }
-      })
-    },
-    // 已完成 table信息
-    getDetailLists() {
-      let detailData = this.detail
-      this.$api.DuctpiecePLM.searchDuctpiecePLMList({
-        segmentId: detailData.rowId,
-        pageNum: detailData.pageNum,
-        pageSize: detailData.pageSize
-      }).then(res => {
-        if (res.success) {
-          detailData.total = res.data.total
-          detailData.tableData = res.data.list
-        }
-      })
-    },
-    // 已完成 title信息
-    getDetailInfos(id) {
-      this.$api.Engineer.detailsProjects({proId: id}).then(res => {
-        if (res.success) {
-          this.detail.infos = res.data
-        }
-      })
-    },
-    // 初始化 ruleform
-    setFormProps(options = {}) {
-      let _form = {
-        proId: '', // 项目名称id
-        mileage: '', // 起讫里程
-        unitProjectName: '',  // 单位工程名称
-        segmentAdmin: '', // 需求负责人
-        shieldEnp: '', // 盾构单位
-        startTime: null, // 开始时间
-        endTime: null, // 结束时间
-        duration: '', // 工期
-        station: '', // 站点
-        segmentList: [], // 标段需求
-      }
-      this.ruleForm = Object.keys(options).length ? options : _form
-    },
-    // 初始化 table 配置
-    setTableColumn() {
-      this.tableColumns = [
-        {index: true},
-        {slot: "finished", name: "已完成(块)"},
-        {name: "项目名称", key: "proName", width: 160},
-        {name: "起讫里程", key: "mileage"},
-        {name: "单位工程名称", key: "unitProjectName"},
-        {name: "盾构单位", key: "shieldEnp"},
-        {name: "开始时间", key: "startTime", width: 100},
-        {name: "结束时间", key: "endTime", width: 100},
-        {name: "工期", key: "duration"},
-        {name: "站点", key: "station"},
-        {name: "负责人", key: "realName"},
-        {
-          operation: true, name: "操作", width: 140, value: [
-            {name: "修改", class: "table_btn", permission: "update", handleRow: this.updateRow},
-            {name: "删除", class: "delete_btn", permission: "delete", handleRow: this.deleteRow},
-          ]
-        },
-      ]
-      this.detail.tableColumns = [
-        {index: true},
-        {name: "环号", key: "ringNum"},
-        {name: "管片编号", key: "pipeNum", width: 140},
-        {name: "转向", key: "turnName", width: 106},
-        {name: "配筋", key: "reinforcementName"},
-        {name: "注浆孔", key: "groutingHolesName"},
-        {name: "块号", key: "blockNumName"},
-        {name: "模具", key: "mouldNum"},
-        {name: "入模时间", key: "intoModTime", width: 136},
-        {name: "浇筑时间", key: "pouringTime"},
-        {name: "质检时间", key: "checkTime", width: 136},
-        {name: "生产班组", key: "groupName"},
-        {name: "项目", key: "proName", width: 240},
-        {name: "质量标注", key: "checkResultStr"},
-      ]
-    },
-    // 重置表单
-    resetForm(formName) {
-      this.$refs[formName].resetFields()
-    },
-    // 显示表单
-    showForm() {
-      !this.isRender && (this.isRender = true)
-      this.asyncVisible = true
-    },
-    // 隐藏表单
-    closeForm() {
-      this.asyncVisible = false
-      this.resetForm('ruleForm')
-      this.setFormProps()
-    },
-    // 查询按钮列表信息
-    queryReset() {
-      this.queryInfo.pageNum = 1
-      this.queryInfo.pageSize = 10
-      this.getLists()
-    },
-    // 添加数据
-    addRow() {
-      this.submitMode = 'add'
-      this.showForm()
-    },
-    // 更新数据
-    async updateRow(row) {
-      this.submitMode = 'update'
-      this.showForm()
-      const segmentList = await this.getProjectBlocks(row.segmentId)
-      Object.keys(this.ruleForm).forEach(item => {
-        if (row.hasOwnProperty.call(row, item)) {
-          this.ruleForm[item] = row[item]
-        }
-      })
-      this.ruleForm.segmentId = row.segmentId
-      this.ruleForm.segmentList = segmentList
-    },
-    // 删除数据
-    deleteRow(row) {
-      this.$confirm("该操作将删除该信息,是否继续删除?", "提示", {
-        confirmButtonText: "确定",
-        cancelButtonText: "取消",
-        type: "warning"
-      }).then(() => {
-        this.$api.Engineer.deleteSegment({segmentId: row.segmentId}).then(res => {
-          if (res.statusMsg === 'ok') {
-            this.queryReset();
-            this.$message.success("删除成功!")
-          } else {
-            this.$message.warning(res.statusMsg)
-          }
-        })
-      }).catch(() => {
-        this.$message.warning("您已取消")
-      })
-    },
-    //获取项目标段
-    getProjectBlocks(id) {
-      return new Promise(resolve => {
-        this.$api.Engineer.detailsSegment({segmentId: id}).then(res => {
-          let outData = []
-          if (res.statusMsg === 'ok') {
-            outData.push(...res.data.segmentNeeds)
-          }
-          resolve(outData)
-        })
-      })
-    },
-    //通过不同的项目展示不同的标段需求
-    changeNeed(val) {
-      this.$api.Reinforce.searchProjectBears({proId: val}).then(res => {
-        if (res.statusMsg === 'ok') {
-          const segmentList = []
-          res.data.blokDtos.forEach(item => {
-            segmentList.push({
-              dictName: item.blockName,
-              needType: item.blockNum,
-              needNum: 0,
-              segmentId: ''
-            })
-          })
-          this.ruleForm.segmentList = segmentList
-        } else {
-          this.$message.warning('标段获取失败')
-        }
-      })
-    },
-    // 提交表单
-    onSubmit: throttle(function () {
-      this.$refs.ruleForm.validate(valid => {
-        if (!valid) return
-        const params = this.ruleForm
-        if (this.isUpdate) {
-          // 更新
-          this.$api.Engineer.updateSegment(params).then((res) => {
-            if (res.statusMsg === 'ok') {
-              this.closeForm()
-              this.getLists()
-              this.$message.success('更新成功!')
-            } else {
-              this.$message.warning(res.statusMsg)
-            }
-          })
-        } else {
-          // 添加
-          this.$api.Engineer.insertSegment(params).then((res) => {
-            if (res.statusMsg === 'ok') {
-              this.closeForm()
-              this.getLists()
-              this.$message.success('添加成功!')
-            } else {
-              this.$message.warning(res.statusMsg)
-            }
-          })
-        }
-      })
-    }, 1000),
-    showDetail(row) {
-      let detailData = this.detail
-      !detailData.isRenderDetail && (detailData.isRenderDetail = true)
-      detailData.asyncVisible = true
-      detailData.rowId = row.segmentId
-      detailData.total = 0
-      detailData.tableData = []
-      detailData.infos = []
-      this.getDetailInfos(row.proId)
-      this.getDetailLists()
-    },
-    // 分页改变
-    pageChange() {
-      this.getLists();
-    },
-    // 已完成の分页改变
-    detailPageChange() {
-      this.getDetailLists();
-    },
-  }
-}
-</script>
-
-<style lang="scss" scoped>
-@import '../../style/layout-main.scss';
-
-/deep/ {
-  &::-webkit-scrollbar {
-    width: 8px;
-    height: 8px;
-  }
-
-  &::-webkit-scrollbar-corner {
-    background-color: transparent;
-  }
-
-  &::-webkit-scrollbar-thumb {
-    border-radius: 10px;
-    box-shadow: inset 0 0 5px transparent;
-    background: #39B5FE;
-  }
-
-  &::-webkit-scrollbar-track {
-    box-shadow: inset 0 0 5px transparent;
-    border-radius: 10px;
-    background: rgba(76, 188, 254, .3);
-  }
-}
-
-.el-card {
-  margin-bottom: 20px;
-  border: none;
-  color: #fff;
-  background: rgba(9, 64, 101, 1);
-
-  .titles {
-    float: left;
-    width: 25%;
-    min-height: 36px;
-    padding-right: 6px;
-    margin-bottom: 6px;
-    line-height: 18px;
-    box-sizing: border-box;
-  }
-
-  span {
-    color: #39B5FE;
-  }
-}
-
-.section_needs {
-  position: relative;
-  color: #18F5F7;
-  padding: 20px 20px 10px 15px;
-  border-bottom: 1px solid #1949A3;
-
-  &::before {
-    position: absolute;
-    content: "";
-    width: 2px;
-    height: 20px;
-    background-color: #18F5F7;
-    top: 20px;
-    left: 5px;
-  }
-}
-
-.section_needs_content {
-  display: flex;
-  flex-wrap: wrap;
-
-  .needs_items {
-    max-width: 190px;
-    min-width: 142px;
-    padding: 15px;
-    display: flex;
-
-    .needs_text {
-      // width: 50px;
-      flex: none;
-      align-self: center;
-      text-align: center;
-      padding-right: 15px;
-      color: #E1E3E9;
-    }
-
-    .needs_num {
-      align-self: center;
-    }
-  }
-}
+<template>
+  <!-- 工程项目管理 ==> 单位工程管理-->
+  <div class="main">
+    <div class="main_header">
+      <div class="header_item">
+        <span class="header_label">标段名称:</span>
+        <el-input v-model="queryInfo.segmentName" clearable placeholder="请输入标段名称"></el-input>
+      </div>
+      <div class="header_item">
+        <span class="header_label">项目名称:</span>
+        <el-select v-model="queryInfo.proId" placeholder="请选择项目名称" clearable>
+          <el-option v-for="item in optionsProject" :key="item.proId" :label="item.proName" :value="item.proId">
+          </el-option>
+        </el-select>
+      </div>
+      <div class="header_item">
+        <el-button icon="el-icon-search" v-permission="'search'" @click="queryReset">查询</el-button>
+        <el-button class="search_btn" icon="el-icon-plus" v-permission="'insert'" @click="addRow">新增</el-button>
+      </div>
+    </div>
+    <div class="main_content">
+      <cpnTable :table-index="true" :table-loading="loading" :table-data="dataList" :table-columns="tableColumns"
+        :page-total="total" :page-num.sync="queryInfo.pageNum" :page-size.sync="queryInfo.pageSize"
+        :page-change="pageChange">
+
+        <template #finished="{ row }">
+          <div style="cursor: pointer;" @click="showDetail(row)">{{ row.completedQuantity }}</div>
+        </template>
+      </cpnTable>
+    </div>
+    <!-- dialog -->
+    <el-dialog class="prop_dialog" v-if="isRender" :title="dialogTitle" :visible.sync="asyncVisible" width="660px"
+      @close="closeForm">
+
+      <cpnForm :form-config="formConfig"></cpnForm>
+
+      <el-form ref="ruleForm" :model="ruleForm" :rules="rules" label-width="auto" class="rule_form">
+        <el-form-item label="项目名称:" prop="proId">
+          <el-select v-model="ruleForm.proId" placeholder="请选择" @change="changeNeed">
+            <el-option v-for="item in optionsProject" :key="item.proId" :label="item.proName" :value="item.proId">
+            </el-option>
+          </el-select>
+        </el-form-item>
+        <el-form-item label="起讫里程:" prop="mileage">
+          <el-input v-model="ruleForm.mileage" clearable placeholder="请输入起讫里程"></el-input>
+        </el-form-item>
+        <el-form-item label="单位工程名称:" prop="unitProjectName">
+          <el-input v-model="ruleForm.unitProjectName" clearable placeholder="请输入单位工程名称"></el-input>
+        </el-form-item>
+        <el-form-item label="需求负责人:" prop="segmentAdmin">
+          <el-select v-model="ruleForm.segmentAdmin" placeholder="请选择求负责人">
+            <el-option v-for="item in optionsUser" :key="item.userId" :label="item.realName" :value="item.userId">
+            </el-option>
+          </el-select>
+        </el-form-item>
+        <el-form-item label="盾构单位:" prop="shieldEnp">
+          <el-input v-model="ruleForm.shieldEnp" clearable placeholder="请输入盾构单位"></el-input>
+        </el-form-item>
+        <el-form-item label="开始时间:" prop="startTime">
+          <el-date-picker v-model="ruleForm.startTime" value-format="yyyy-MM-dd" placeholder="请选择开始日期"></el-date-picker>
+        </el-form-item>
+        <el-form-item label="结束时间:" prop="endTime">
+          <el-date-picker v-model="ruleForm.endTime" value-format="yyyy-MM-dd" placeholder="请选择结束日期"></el-date-picker>
+        </el-form-item>
+        <el-form-item label="工期:" prop="duration">
+          <el-input v-model="ruleForm.duration" clearable placeholder="请输入工期"></el-input>
+        </el-form-item>
+        <el-form-item label="站点:" prop="station">
+          <el-input v-model="ruleForm.station" clearable placeholder="请输入站点"></el-input>
+        </el-form-item>
+        <div class="section_needs">标段需求</div>
+        <div class="section_needs_content">
+          <div v-for="item in ruleForm.segmentList" :key="item.dictId" class="needs_items">
+            <div class="needs_text">{{ item.dictName }}</div>
+            <el-input placeholder="请输入数量" type="number" v-model="item.needNum" class="needs_num">
+              <template slot="append">块</template>
+            </el-input>
+          </div>
+        </div>
+      </el-form>
+      <div slot="footer">
+        <el-button @click="asyncVisible = false">取 消</el-button>
+        <el-button class="submit_btn" @click="onSubmit('ruleForm')">提 交</el-button>
+      </div>
+    </el-dialog>
+    <!-- detail dialog -->
+    <el-dialog class="prop_dialog" v-if="detail.isRenderDetail" title="管片数量" :visible.sync="detail.asyncVisible"
+      width="1000px">
+      <el-card>
+        <div class="titles" v-for="item in detail.infoMap" :key="item.key">
+          {{ item.name }}&nbsp;:
+          <template v-if="item.key === 'waterproofType'">
+            <span>{{ detail.infos[item.key] === 0 ? '有' : '无' }} </span>
+          </template>
+          <template v-else-if="item.key === 'proHas'">
+            <span v-for="val in detail.infos[item.key]" :key="val.hasSteel">
+              {{ val.dictName }}
+            </span>
+          </template>
+          <template v-else>
+            <span>{{ detail.infos[item.key] }}</span>
+          </template>
+        </div>
+      </el-card>
+      <cpnTable :table-index="true" :table-data="detail.tableData" :table-columns="detail.tableColumns"
+        :page-total="detail.total" :page-num.sync="detail.pageNum" :page-size.sync="detail.pageSize"
+        :page-change="detailPageChange">
+      </cpnTable>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import {throttle} from '../../plugins/public'; // 导入节流、动态切换组件尺寸方法
+import cpnTable from '@/components/element/Table'
+import cpnForm from '@/components/element/Form'
+export default {
+  data() {
+    return {
+      isRender: false,
+      loading: false,
+      asyncVisible: false,
+      submitMode: '', // add update
+      total: 0,
+      queryInfo: {
+        pageNum: 1,
+        pageSize: 10,
+        segmentName: '',
+        proId: '',
+      },
+      dataList: [],
+      tableColumns: [],
+      optionsUser: [], //需求负责人
+      optionsProject: [], // 项目名称
+      ruleForm: {}, // 按钮表单
+      rules: {
+        proId: [{required: true, message: '请选择', trigger: 'change'}],
+        mileage: [{required: true, message: '请输入', trigger: 'blur'}],
+        unitProjectName: [{required: true, message: '请输入', trigger: 'blur'}],
+        segmentAdmin: [{required: true, message: '请选择', trigger: 'change'}],
+        shieldEnp: [{required: true, message: '请输入', trigger: 'blur'}],
+        startTime: [{required: true, message: '请选择', trigger: 'change'}],
+        endTime: [{required: true, message: '请选择', trigger: 'change'}],
+        duration: [{required: true, message: '请输入', trigger: 'blur'}],
+        station: [{required: true, message: '请输入', trigger: 'blur'}],
+      },
+      detail: {
+        rowId: '',
+        isRenderDetail: false,
+        asyncVisible: false,
+        pageNum: 1,
+        pageSize: 10,
+        total: 0,
+        tableData: [],
+        tableColumns: [],
+        infos: [],
+        infoMap: [
+          {
+            name: '项目名称',
+            key: 'proName',
+          },
+          {
+            name: '单位工程名称',
+            key: 'createUnit',
+          },
+          {
+            name: '外径',
+            key: 'outsideDiameter',
+          },
+          {
+            name: '內径',
+            key: 'innerDiameter',
+          },
+          {
+            name: '厚度',
+            key: 'thickness',
+          },
+          {
+            name: '环宽',
+            key: 'ringWidth',
+          },
+          {
+            name: '混凝土强度等级',
+            key: 'concreteStrengthGrade',
+          },
+          {
+            name: '抗渗等级',
+            key: 'impermeabilityLevel',
+          },
+          {
+            name: '配筋型号',
+            key: 'proHas',
+          },
+          {
+            name: '有无外弧面防水',
+            key: 'waterproofType',
+          }
+        ]
+      },
+
+      formConfig: {
+        formModels: {
+          proId: '', // 项目名称id
+          mileage: '', // 起讫里程
+          unitProjectName: '',  // 单位工程名称
+          segmentAdmin: '', // 需求负责人
+          shieldEnp: '', // 盾构单位
+          startTime: null, // 开始时间
+          endTime: null, // 结束时间
+          duration: '', // 工期
+          station: '', // 站点
+          segmentList: [], // 标段需求
+        },
+        formItems: [
+          {
+            type: 'input', label: '用户id'
+          },
+          {
+            type: 'input', label: '用户名'
+          },
+
+          {
+            type: 'input', label: '真实姓名'
+          },
+          {
+            type: 'input', label: '电话号码'
+          },
+          {
+            type: 'select', label: '用户状态', options: [
+              {label: '禁用', value: 0},
+              {label: '启用', value: 1}
+            ]
+          },
+          {
+            type: 'datepicker', label: '创建时间', otherOptions: {
+              startPlaceholder: '开始时间',
+              endPlaceholder: '结束时间',
+              type: 'daterange',
+              'unlink-panels': true
+            }
+          }
+        ],
+        itemColLayout: {
+          span: 8
+        }
+      }
+    }
+  },
+  components: {
+    cpnTable,
+    cpnForm
+  },
+  computed: {
+    isUpdate() {
+      return this.submitMode === 'update'
+    },
+    dialogTitle() {
+      return this.isUpdate ? '修改新增单位工程' : '新增单位工程'
+    },
+  },
+  created() {
+    this.setFormProps()
+    this.setTableColumn()
+    this.getLists()
+    this.getAllProjects()
+    this.getAllPersons()
+    // this.getAllBlocks() // 暂时没用,先屏蔽
+  },
+  methods: {
+    // 获取table列表数据
+    getLists() {
+      this.loading = true
+      let params = this.queryInfo
+      this.$api.Engineer.searchSegment(params).then(res => {
+        if (res.statusMsg === 'ok') {
+          this.total = res.data.total
+          this.dataList = res.data.list
+        }
+        this.loading = false
+      })
+    },
+    //获得所有人员
+    getAllPersons() {
+      this.$api.Engineer.getPersonsList({}).then(res => {
+        if (res.statusMsg === 'ok') {
+          this.optionsUser = res.data
+        } else {
+          this.$message.warning('人员名称获取失败')
+        }
+      })
+    },
+    //获得所有项目名称
+    getAllProjects() {
+      let obj = {
+        pageNum: 1,
+        pageSize: 100000000
+      }
+      this.$api.Engineer.searchProjects(obj).then(res => {
+        if (res.statusMsg === 'ok') {
+          this.optionsProject = res.data.list
+        } else {
+          this.$message.warning('项目名称获取失败')
+        }
+      })
+    },
+    //字典里获取所有标段块号
+    getAllBlocks() {
+      let params = {
+        pageNum: 1,
+        pageSize: 100000000
+      }
+      this.$api.Dictionary.searchDictionary(params).then(res => {
+        if (res.statusMsg === 'ok') {
+          const segmentList = []
+          res.data.list.forEach(item => {
+            if (item.dictType === '5') {
+              segmentList.push({
+                dictName: item.dictName,
+                needType: item.dictId,
+                needNum: 0,
+                segmentId: ''
+              })
+            }
+          })
+          this.ruleForm.segmentList = segmentList
+        } else {
+          this.$message.warning('标段获取失败')
+        }
+      })
+    },
+    // 已完成 table信息
+    getDetailLists() {
+      let detailData = this.detail
+      this.$api.DuctpiecePLM.searchDuctpiecePLMList({
+        segmentId: detailData.rowId,
+        pageNum: detailData.pageNum,
+        pageSize: detailData.pageSize
+      }).then(res => {
+        if (res.success) {
+          detailData.total = res.data.total
+          detailData.tableData = res.data.list
+        }
+      })
+    },
+    // 已完成 title信息
+    getDetailInfos(id) {
+      this.$api.Engineer.detailsProjects({proId: id}).then(res => {
+        if (res.success) {
+          this.detail.infos = res.data
+        }
+      })
+    },
+    // 初始化 ruleform
+    setFormProps(options = {}) {
+      let _form = {
+        proId: '', // 项目名称id
+        mileage: '', // 起讫里程
+        unitProjectName: '',  // 单位工程名称
+        segmentAdmin: '', // 需求负责人
+        shieldEnp: '', // 盾构单位
+        startTime: null, // 开始时间
+        endTime: null, // 结束时间
+        duration: '', // 工期
+        station: '', // 站点
+        segmentList: [], // 标段需求
+      }
+      this.ruleForm = Object.keys(options).length ? options : _form
+    },
+    // 初始化 table 配置
+    setTableColumn() {
+      this.tableColumns = [
+        {index: true},
+        {slot: "finished", name: "已完成(块)"},
+        {name: "项目名称", key: "proName", width: 160},
+        {name: "起讫里程", key: "mileage"},
+        {name: "单位工程名称", key: "unitProjectName"},
+        {name: "盾构单位", key: "shieldEnp"},
+        {name: "开始时间", key: "startTime", width: 100},
+        {name: "结束时间", key: "endTime", width: 100},
+        {name: "工期", key: "duration"},
+        {name: "站点", key: "station"},
+        {name: "负责人", key: "realName"},
+        {
+          operation: true, name: "操作", width: 140, value: [
+            {name: "修改", class: "table_btn", permission: "update", handleRow: this.updateRow},
+            {name: "删除", class: "delete_btn", permission: "delete", handleRow: this.deleteRow},
+          ]
+        },
+      ]
+      this.detail.tableColumns = [
+        {index: true},
+        {name: "环号", key: "ringNum"},
+        {name: "管片编号", key: "pipeNum", width: 140},
+        {name: "转向", key: "turnName", width: 106},
+        {name: "配筋", key: "reinforcementName"},
+        {name: "注浆孔", key: "groutingHolesName"},
+        {name: "块号", key: "blockNumName"},
+        {name: "模具", key: "mouldNum"},
+        {name: "入模时间", key: "intoModTime", width: 136},
+        {name: "浇筑时间", key: "pouringTime"},
+        {name: "质检时间", key: "checkTime", width: 136},
+        {name: "生产班组", key: "groupName"},
+        {name: "项目", key: "proName", width: 240},
+        {name: "质量标注", key: "checkResultStr"},
+      ]
+    },
+    // 重置表单
+    resetForm(formName) {
+      this.$refs[formName].resetFields()
+    },
+    // 显示表单
+    showForm() {
+      !this.isRender && (this.isRender = true)
+      this.asyncVisible = true
+    },
+    // 隐藏表单
+    closeForm() {
+      this.asyncVisible = false
+      this.resetForm('ruleForm')
+      this.setFormProps()
+    },
+    // 查询按钮列表信息
+    queryReset() {
+      this.queryInfo.pageNum = 1
+      this.queryInfo.pageSize = 10
+      this.getLists()
+    },
+    // 添加数据
+    addRow() {
+      this.submitMode = 'add'
+      this.showForm()
+    },
+    // 更新数据
+    async updateRow(row) {
+      this.submitMode = 'update'
+      this.showForm()
+      const segmentList = await this.getProjectBlocks(row.segmentId)
+      Object.keys(this.ruleForm).forEach(item => {
+        if (row.hasOwnProperty.call(row, item)) {
+          this.ruleForm[item] = row[item]
+        }
+      })
+      this.ruleForm.segmentId = row.segmentId
+      this.ruleForm.segmentList = segmentList
+    },
+    // 删除数据
+    deleteRow(row) {
+      this.$confirm("该操作将删除该信息,是否继续删除?", "提示", {
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "warning"
+      }).then(() => {
+        this.$api.Engineer.deleteSegment({segmentId: row.segmentId}).then(res => {
+          if (res.statusMsg === 'ok') {
+            this.queryReset();
+            this.$message.success("删除成功!")
+          } else {
+            this.$message.warning(res.statusMsg)
+          }
+        })
+      }).catch(() => {
+        this.$message.warning("您已取消")
+      })
+    },
+    //获取项目标段
+    getProjectBlocks(id) {
+      return new Promise(resolve => {
+        this.$api.Engineer.detailsSegment({segmentId: id}).then(res => {
+          let outData = []
+          if (res.statusMsg === 'ok') {
+            outData.push(...res.data.segmentNeeds)
+          }
+          resolve(outData)
+        })
+      })
+    },
+    //通过不同的项目展示不同的标段需求
+    changeNeed(val) {
+      this.$api.Reinforce.searchProjectBears({proId: val}).then(res => {
+        if (res.statusMsg === 'ok') {
+          const segmentList = []
+          res.data.blokDtos.forEach(item => {
+            segmentList.push({
+              dictName: item.blockName,
+              needType: item.blockNum,
+              needNum: 0,
+              segmentId: ''
+            })
+          })
+          this.ruleForm.segmentList = segmentList
+        } else {
+          this.$message.warning('标段获取失败')
+        }
+      })
+    },
+    // 提交表单
+    onSubmit: throttle(function () {
+      this.$refs.ruleForm.validate(valid => {
+        if (!valid) return
+        const params = this.ruleForm
+        if (this.isUpdate) {
+          // 更新
+          this.$api.Engineer.updateSegment(params).then((res) => {
+            if (res.statusMsg === 'ok') {
+              this.closeForm()
+              this.getLists()
+              this.$message.success('更新成功!')
+            } else {
+              this.$message.warning(res.statusMsg)
+            }
+          })
+        } else {
+          // 添加
+          this.$api.Engineer.insertSegment(params).then((res) => {
+            if (res.statusMsg === 'ok') {
+              this.closeForm()
+              this.getLists()
+              this.$message.success('添加成功!')
+            } else {
+              this.$message.warning(res.statusMsg)
+            }
+          })
+        }
+      })
+    }, 1000),
+    showDetail(row) {
+      let detailData = this.detail
+      !detailData.isRenderDetail && (detailData.isRenderDetail = true)
+      detailData.asyncVisible = true
+      detailData.rowId = row.segmentId
+      detailData.total = 0
+      detailData.tableData = []
+      detailData.infos = []
+      this.getDetailInfos(row.proId)
+      this.getDetailLists()
+    },
+    // 分页改变
+    pageChange() {
+      this.getLists();
+    },
+    // 已完成の分页改变
+    detailPageChange() {
+      this.getDetailLists();
+    },
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+@import '../../style/layout-main.scss';
+
+/deep/ {
+  &::-webkit-scrollbar {
+    width: 8px;
+    height: 8px;
+  }
+
+  &::-webkit-scrollbar-corner {
+    background-color: transparent;
+  }
+
+  &::-webkit-scrollbar-thumb {
+    border-radius: 10px;
+    box-shadow: inset 0 0 5px transparent;
+    background: #39B5FE;
+  }
+
+  &::-webkit-scrollbar-track {
+    box-shadow: inset 0 0 5px transparent;
+    border-radius: 10px;
+    background: rgba(76, 188, 254, .3);
+  }
+}
+
+.el-card {
+  margin-bottom: 20px;
+  border: none;
+  color: #fff;
+  background: rgba(9, 64, 101, 1);
+
+  .titles {
+    float: left;
+    width: 25%;
+    min-height: 36px;
+    padding-right: 6px;
+    margin-bottom: 6px;
+    line-height: 18px;
+    box-sizing: border-box;
+  }
+
+  span {
+    color: #39B5FE;
+  }
+}
+
+.section_needs {
+  position: relative;
+  color: #18F5F7;
+  padding: 20px 20px 10px 15px;
+  border-bottom: 1px solid #1949A3;
+
+  &::before {
+    position: absolute;
+    content: "";
+    width: 2px;
+    height: 20px;
+    background-color: #18F5F7;
+    top: 20px;
+    left: 5px;
+  }
+}
+
+.section_needs_content {
+  display: flex;
+  flex-wrap: wrap;
+
+  .needs_items {
+    max-width: 190px;
+    min-width: 142px;
+    padding: 15px;
+    display: flex;
+
+    .needs_text {
+      // width: 50px;
+      flex: none;
+      align-self: center;
+      text-align: center;
+      padding-right: 15px;
+      color: #E1E3E9;
+    }
+
+    .needs_num {
+      align-self: center;
+    }
+  }
+}
 </style>
\ No newline at end of file
diff --git a/web/src/views/SecureManage/SmartHelmet/AlarmRecord.vue b/web/src/views/SecureManage/SmartHelmet/AlarmRecord.vue
index 668cc49..9c82ec0 100644
--- a/web/src/views/SecureManage/SmartHelmet/AlarmRecord.vue
+++ b/web/src/views/SecureManage/SmartHelmet/AlarmRecord.vue
@@ -1,3 +1,725 @@
-<template>
-    <div>报警记录</div>
-</template>
\ No newline at end of file
+<template>
+    <!-- 安全管理 ==> 智能安全帽 => 报警记录 -->
+    <div class="main">
+        <!-- main_left -->
+        <div class="main_left" v-show="!isRenderDetail">
+            <div class="main_left_search">
+                <div class="search_item">
+                    <span>姓名:</span>
+                    <el-input size="mini" v-model="queryInfo.userName" placeholder="请输入姓名" clearable></el-input>
+                </div>
+                <div class="search_item">
+                    <span>设备编号:</span>
+                    <el-input size="mini" v-model="queryInfo.deviceNum" placeholder="请输入设备编号" clearable></el-input>
+                    <el-button icon="el-icon-search" @click="queryTable">查询</el-button>
+                </div>
+            </div>
+            <div class="main_left_table">
+                <cpnTable :table-loading="loading" :table-data="dataList" :table-columns="tableColumns">
+                </cpnTable>
+            </div>
+        </div>
+        <!-- main_right -->
+        <div class="main_right" v-show="!isRenderDetail">
+            <div class="echart_options">
+                <el-radio-group v-model="chart.radioGroupVal" @change="loadChart">
+                    <el-radio-button label="7">近7天报警数</el-radio-button>
+                    <el-radio-button label="31">近31天报警数</el-radio-button>
+                </el-radio-group>
+            </div>
+            <div ref="echart_bar" class="echart_bar"></div>
+            <div class="echart_drop">
+                <div ref="echart_drop_1" :class="{ show: chart.isShowDrop1, hide: !chart.isShowDrop1 }"></div>
+                <div ref="echart_drop_2" :class="{ show: chart.isShowDrop2, hide: !chart.isShowDrop2 }"></div>
+            </div>
+        </div>
+        <!-- detail -->
+        <div class="detail" v-if="isRenderDetail">
+            <div class="detail_option">
+                <div class="search_item">
+                    <span>日期:</span>
+                    <el-date-picker class="elDatePicker" type="daterange" v-model="detail.date" value-format="yyyy-MM-dd"
+                        start-placeholder="开始日期" end-placeholder="结束日期" clearable></el-date-picker>
+                </div>
+                <el-button icon="el-icon-search" @click="queryDetail">查询</el-button>
+                <el-button style="float:right" @click="hideDetail">返回</el-button>
+            </div>
+            <div class="detail_botbox">
+                <div class="detail_table">
+                    <cpnTable :table-data="detail.dataList" :table-columns="detail.tableColumns">
+                    </cpnTable>
+                </div>
+                <div class="detail_map">
+                    <div id="container"></div>
+                    <div class="detail_map_info_l" v-show="detail.isShowInfo">
+                        <h2>静默报警</h2>
+                        <p>原因: <span>{{ detail.info.cause }}</span></p>
+                        <p>姓名: <span>{{ detail.info.name }}</span></p>
+                        <p>设备: <span>{{ detail.info.device }}</span></p>
+                        <p>位置: <span>{{ detail.info.position }}</span></p>
+                        <p>时间: <span>{{ detail.info.time }}</span></p>
+                        <el-button style="float: right;" @click="() => { detail.isShowInfo = false }">确定</el-button>
+                    </div>
+                    <div class="detail_map_info_r" v-show="detail.isShowInfo">
+                        <h2>报警设备信息</h2>
+                        <p>姓名: <span>{{ detail.info.name }}</span></p>
+                        <p>时间: <span>{{ detail.info.time }}</span></p>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+</template>
+  
+<script>
+import AMapLoader from '@amap/amap-jsapi-loader';
+import cpnTable from '@/components/element/Table'
+import 'echarts-liquidfill' // 水滴图形
+export default {
+    data() {
+        return {
+            loading: false,
+            isRenderDetail: false, // 是否查看轨迹地图
+            queryInfo: {
+                pageNum: 1,
+                pageSize: 9999,
+                userName: '',
+                deviceNum: '',
+            },
+            dataList: [],
+            tableColumns: [],
+            userId: '', // 用户id
+            userName: '', // 用户名称
+            deviceNum: '', // 设备编号
+            // 图表数据
+            chart: {
+                date: '',
+                radioGroupVal: '7', // (7 31)默认近7天
+                isShowDrop1: false,
+                isShowDrop2: false,
+                chartInstanceLine: null, // 柱状图实例
+                chartInstanceDrop1: null, // 水滴图实例
+                chartInstanceDrop2: null,
+            },
+            detail: { // 详情
+                date: [], // 日期查询
+                dataList: [],
+                tableColumns: [],
+                isShowInfo: false,
+                // 报警信息
+                info: {
+                    cause: '',
+                    name: '',
+                    device: '',
+                    position: '',
+                    time: '',
+                },
+                // 报警类型原因
+                sosTypes: {
+                    1: '长时间静止'
+                },
+            },
+            $http: '', // 接口api路径
+        }
+    },
+    components: {
+        cpnTable
+    },
+    created() {
+        // 添加密钥,获取高德地址使用
+        window._AMapSecurityConfig = {
+            securityJsCode: "aa90b00e27025d1f57286675cc8b232e",
+        }
+        this.$http = this.$api.Safety.SmartHelmet.warning
+        this.setTableColumn()
+        this.getLists()
+    },
+    mounted() {
+        this.initChart()
+        window.addEventListener("resize", this.screenAdapter)
+    },
+    beforeDestroy() {
+        this.clearMap()
+        this.clearChart()
+        window.removeEventListener("resize", this.screenAdapter)
+    },
+    methods: {
+        setTableColumn() {
+            this.tableColumns = [
+                {name: "用户ID", key: "userId"},
+                {name: "姓名", key: "userName"},
+                {name: "设备编号", key: "deviceNum"},
+                {
+                    operation: true, name: "操作", width: 140, value: [
+                        {name: "查看", class: "table_btn", handleRow: this.warchRow},
+                        {name: "详情", class: "table_btn", handleRow: this.detailRow},
+                    ]
+                },
+            ]
+        },
+        // 获取报警信息
+        async getWarning() {
+            let out = {}
+            const params = {
+                userId: this.userId,
+                strTime: this.chart.date?.[0],
+                endTime: this.chart.date?.[1],
+            }
+            const {data, statusMsg} = await this.$http.getWarning(params)
+            if (statusMsg === 'ok') {
+                out = data
+            }
+            return out
+        },
+        // 获取table数据
+        getLists() {
+            this.loading = true
+            this.$api.Safety.SmartHelmet.getLists(this.queryInfo).then(res => {
+                if (res.statusMsg === 'ok') {
+                    this.dataList = res.data.list
+                }
+                this.loading = false
+            })
+        },
+        // table-详情按钮
+        detailRow(row) {
+            this.userId = row.userId
+            this.deviceNum = row.deviceNum
+            this.renderDetail()
+        },
+        // table-查看按钮
+        warchRow(row) {
+            if (this.userId === row.userId) return
+            this.userId = row.userId
+            this.userName = row.userName
+            console.log(row)
+            this.chart.isShowDrop1 = false
+            this.chart.isShowDrop2 = false
+            this.loadChart()
+        },
+        // 加载图表
+        loadChart() {
+            this.changeDay()
+            this.showDrop()
+            this.renderChart()
+        },
+        // 水滴图表根据  7/31 切换显示
+        showDrop() {
+            if (this.chart.radioGroupVal === '7') {
+                this.chart.isShowDrop1 = true
+            } else if (this.chart.radioGroupVal === '31') {
+                this.chart.isShowDrop2 = true
+            }
+        },
+        // 近 7/31 天切换
+        changeDay() {
+            const nowDate = new Date().format()
+            const beforeDate7 = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).format()
+            const beforeDate31 = new Date(Date.now() - 31 * 24 * 60 * 60 * 1000).format()
+            const beforeDate = this.chart.radioGroupVal === '7' ? beforeDate7 : (this.chart.radioGroupVal === '31' ? beforeDate31 : '')
+
+            this.chart.date = [beforeDate, nowDate]
+        },
+        // 初始化图表
+        initChart() {
+            this.chart.chartInstanceLine = this.$echarts.init(this.$refs.echart_bar)
+            this.chart.chartInstanceDrop1 = this.$echarts.init(this.$refs.echart_drop_1)
+            this.chart.chartInstanceDrop2 = this.$echarts.init(this.$refs.echart_drop_2)
+            this.renderBaseChart()
+        },
+        // 渲染chart基础配置
+        renderBaseChart() {
+            const baseBarOptions = {
+                animationDuration: 1500,
+                tooltip: {
+                    trigger: "axis",
+                    formatter: "日期: {b} <div></div> 在线时长: {c}分",
+                },
+                grid: {
+                    top: "10%",
+                    right: "0",
+                    left: "2%",
+                    bottom: "14",
+                    containLabel: true,
+                },
+                xAxis: [{
+                    type: 'category',
+                    axisLine: {
+                        lineStyle: {
+                            width: 2,
+                            color: "#B7E4F7",
+                        },
+                    },
+                    axisTick: {
+                        show: false,
+                    },
+                }],
+                yAxis: [{
+                    type: 'value',
+                    axisLabel: {
+                        formatter: "{value}",
+                        color: "#CAD3E0",
+                    },
+                    splitLine: {
+                        lineStyle: {
+                            width: 2,
+                            type: "dashed",
+                            color: "#5A7E9D",
+                        },
+                    },
+                }],
+                series: [{
+                    type: 'line',
+                    smooth: true,
+                    barWidth: 14,
+                    color: new this.$echarts.graphic.LinearGradient(0, 0, 0, 1, [
+                        {offset: 0, color: "#C15B3C"},
+                        {offset: 1, color: "#FACD91"},
+                    ]),
+                }],
+            }
+            const baseDropOptions1 = {
+                title: {
+                    text: '近一周报警天数',
+                    left: '32%',
+                    top: 30,
+                    textStyle: {
+                        color: "#fff",
+                    },
+                },
+                series: [{
+                    type: 'liquidFill',
+                    data: [0],
+                    radius: '60%', //图表的大小 值是圆的直径
+                    shape: 'circle', //水填充图的形状 circle默认圆形  rect圆角矩形  triangle三角形  diamond菱形  pin水滴状 arrow箭头状  还可以是svg的path
+                    center: ['50%', '50%'], //图表相对于盒子的位置
+                    outline: {
+                        show: false
+                    },
+                    backgroundStyle: { // 背景配置
+                        color: 'transparent', //背景颜色
+                        borderWidth: '2',//边框大小
+                        borderColor: 'rgba(17, 94, 176, 0.4)', // 边框颜色
+                    },
+                    itemStyle: {
+                        color: new this.$echarts.graphic.LinearGradient(
+                            0, 0, 0, 1, //4个参数用于配置渐变色的起止位置, 这4个参数依次对应右/下/左/上四个方位. 而0 0 0 1则代表渐变色从正上方开始
+                            [
+                                {offset: 0, color: '#2790F4'},
+                                {offset: 1, color: '#165190'}
+                            ] //数组, 用于配置颜色的渐变过程. 每一项为一个对象, 包含offset和color两个参数. offset的范围是0 ~ 1, 用于表示位置
+                        )
+                    },
+                }]
+            }
+            const baseDropOptions2 = {
+                title: {
+                    text: '近一个月(31天)报警天数',
+                    right: '24%',
+                    top: 30,
+                    textStyle: {
+                        color: "#fff",
+                    },
+                },
+                series: [{
+                    type: 'liquidFill',
+                    data: [0],
+                    radius: '60%', //图表的大小 值是圆的直径
+                    shape: 'circle', //水填充图的形状 circle默认圆形  rect圆角矩形  triangle三角形  diamond菱形  pin水滴状 arrow箭头状  还可以是svg的path
+                    center: ['50%', '50%'], //图表相对于盒子的位置
+                    outline: {
+                        show: false
+                    },
+                    backgroundStyle: { // 背景配置
+                        color: 'transparent', //背景颜色
+                        borderWidth: '2',//边框大小
+                        borderColor: 'rgba(17, 94, 176, 0.4)', // 边框颜色
+                    },
+                    itemStyle: {
+                        color: new this.$echarts.graphic.LinearGradient(
+                            0, 0, 0, 1, //4个参数用于配置渐变色的起止位置, 这4个参数依次对应右/下/左/上四个方位. 而0 0 0 1则代表渐变色从正上方开始
+                            [
+                                {offset: 0, color: '#2790F4'},
+                                {offset: 1, color: '#165190'}
+                            ] //数组, 用于配置颜色的渐变过程. 每一项为一个对象, 包含offset和color两个参数. offset的范围是0 ~ 1, 用于表示位置
+                        )
+                    },
+                }]
+            }
+            this.chart.chartInstanceLine.setOption(baseBarOptions)
+            this.chart.chartInstanceDrop1.setOption(baseDropOptions1)
+            this.chart.chartInstanceDrop2.setOption(baseDropOptions2)
+        },
+        // 渲染chart内容数据(x,y,series)
+        async renderChart() {
+            const obj = await this.getWarning()
+            const day = obj.day // 报警天数
+            const percentDay = (day / this.chart.radioGroupVal).toFixed(2) //百分比
+            const x = [] // x轴数据
+            const y = [] // y轴数据
+            obj.reportNumDtos.forEach(item => {
+                x.push(item.smTime)
+                y.push(item.num)
+            })
+            const lineOptions = {
+                title: {
+                    text: `${this.userName} 近${this.chart.radioGroupVal}天报警天数`,
+                    left: '42%',
+                    textStyle: {
+                        color: "#E2E5EA",
+                        fontSize: 12,
+                    },
+                },
+                xAxis: {
+                    index: 0,
+                    data: x
+                },
+                series: {
+                    index: 0,
+                    data: y
+                }
+            }
+            const dropOptions = {
+                series: {
+                    index: 0,
+                    data: [percentDay],
+                    label: {
+                        color: '#fff',
+                        formatter: () => day,
+                    }
+                }
+            }
+            this.chart.chartInstanceLine.setOption(lineOptions)
+            if (this.chart.radioGroupVal === '7') {
+                this.chart.chartInstanceDrop1.setOption(dropOptions)
+
+            } else if (this.chart.radioGroupVal === '31') {
+                this.chart.chartInstanceDrop2.setOption(dropOptions)
+            }
+        },
+        screenAdapter() {
+            this.chart.chartInstanceLine?.resize()
+            this.chart.chartInstanceDrop1?.resize()
+            this.chart.chartInstanceDrop2?.resize()
+        },
+        queryTable() {
+            this.getLists()
+        },
+        /* --------详情页面-s */
+        renderDetail() {
+            this.isRenderDetail = true
+            this.setTableDetailColumn()
+            this.getDetailLists()
+            this.renderMap()
+        },
+        setTableDetailColumn() {
+            if (this.detail.tableColumns.length) return
+            this.detail.tableColumns = [
+                {name: "时间", key: "smTime"},
+                {name: "姓名", key: "userName", width: 70},
+                {name: "原因", key: "sosType", width: 80, formatter: (row) => this.detail.sosTypes[row.sosType] || ''},
+                {
+                    operation: true, name: "操作", width: 100, value: [
+                        {name: "查看", class: "table_btn", handleRow: this.warchDetailRow},
+                    ]
+                },
+            ]
+        },
+        getDetailLists() {
+            const params = {
+                userId: this.userId,
+                strTime: this.detail.date?.[0],
+                endTime: this.detail.date?.[1],
+            }
+            this.detail.dataList = []
+            this.$http.getDetailWarning(params).then(res => {
+                if (res.statusMsg === 'ok') {
+                    this.detail.dataList = res.data
+                }
+            })
+        },
+        // 查看具体报警信息
+        async warchDetailRow(row) {
+            const _position = await this.getAdress(row.xpoint, row.ypoint)
+            const info = {
+                cause: this.detail.sosTypes[row.sosType],
+                name: row.userName,
+                device: this.deviceNum,
+                position: `${_position}(经度:${row.ypoint}, 纬度${row.xpoint})`,
+                time: row.smTime,
+            }
+            this.detail.info = info
+            this.detail.isShowInfo = true
+        },
+        renderMap() {
+            AMapLoader.load({
+                key: "1dc3895771d98745fa27bbcc3280464f", // 申请好的Web端开发者Key,首次调用 load 时必填
+                version: "2.0", // 指定要加载的 JSAPI 的版本,缺省时默认为 1.4.15
+            }).then(AMap => {
+                this.detail.AMap = AMap
+                this.detail.map = new AMap.Map("container", {
+                    resizeEnable: true,
+                    viewMode: "2D", // 是否为3D地图模式
+                    zoom: 13, // 初始化地图级别
+                })
+            })
+        },
+        createMapMarker(lnglatXY) {
+            if (!this.detail.marker) {
+                const AMap = this.detail.AMap
+                this.detail.marker = new AMap.Marker()
+            }
+            const marker = this.detail.marker
+            marker.setMap(null)
+            marker.setPosition(lnglatXY)
+            this.detail.map.add(marker)
+            this.detail.map.setFitView()
+        },
+        // 获取地址
+        getAdress(x, y) {
+            return new Promise(resolve => {
+                let address = ''
+                const AMap = this.detail.AMap
+                const lnglatXY = [y, x] // 位置的经纬度
+                this.createMapMarker(lnglatXY)
+                AMap.plugin('AMap.Geocoder', () => {
+                    const geocoder = new AMap.Geocoder({
+                        radius: 1000,
+                        extensions: "all"
+                    })
+                    geocoder.getAddress(lnglatXY, function (status, result) {
+                        if (status === 'complete' && result.info === 'OK') {
+                            address = result.regeocode.formattedAddress
+                        }
+                        resolve(address)
+                    })
+                })
+            })
+        },
+        hideDetail() {
+            this.clearMap()
+            this.detail.date = []
+            this.detail.isShowInfo = false
+            this.isRenderDetail = !this.isRenderDetail
+        },
+        queryDetail() {
+            this.getDetailLists()
+        },
+        /*  --------详情页面-e */
+        clearMap() {
+            this.detail.map?.destroy()
+        },
+        clearChart() {
+            this.chart.chartInstanceLine?.clear()
+            this.chart.chartInstanceDrop1?.clear()
+            this.chart.chartInstanceDrop2?.clear()
+        },
+    }
+}
+</script>
+  
+<style lang="scss" scoped>
+@import '@/style/layout-main.scss';
+
+::v-deep .el-radio-button__inner {
+    border: 1px solid #39B5FE;
+    color: #fff;
+    background: transparent;
+}
+
+.hide {
+    opacity: 0;
+}
+
+.show {
+    opacity: 1;
+}
+
+#container {
+    width: 100%;
+    height: 100%;
+}
+
+.main {
+    display: flex;
+    flex-direction: row;
+    color: #EBEBEB;
+
+    .main_left {
+        display: flex;
+        flex-direction: column;
+        width: 380px;
+        padding: 14px;
+        margin-right: 18px;
+        border: 1px solid #39B5FE;
+        border-radius: 10px;
+        box-shadow: rgba(0, 145, 255, 0.7) 0 0 18px inset;
+        box-sizing: content-box;
+
+        .search_item {
+            span {
+                display: inline-block;
+                min-width: 54px;
+                margin-right: 6px;
+                text-align: right;
+            }
+
+            .el-input {
+                width: 220px;
+                margin: 0 28px 16px 0;
+            }
+        }
+
+        ::v-deep.main_left_table {
+            overflow: hidden;
+
+            .el-table {
+                display: flex;
+                flex-direction: column;
+
+                .el-table__body-wrapper {
+                    overflow-y: auto;
+                    flex: 1;
+                }
+            }
+        }
+    }
+
+    .main_right {
+        overflow: hidden;
+        display: flex;
+        flex-direction: column;
+        flex: 1;
+        padding: 20px;
+        border: 1px solid #39B5FE;
+        background-color: rgba(0, 52, 121, 0.3);
+        border-radius: 10px;
+        box-shadow: rgba(0, 145, 255, 0.7) 0 0 18px inset;
+        box-sizing: content-box;
+
+        .echart_options {
+            margin-bottom: 10px;
+
+
+        }
+
+        .echart_bar {
+            flex: 1;
+        }
+
+        .echart_drop {
+            flex: 1.1;
+
+            > div {
+                float: left;
+                width: 50%;
+                height: 100%;
+            }
+        }
+    }
+
+    .detail {
+        display: flex;
+        flex-direction: column;
+        position: absolute;
+        width: 100%;
+        height: 100%;
+        padding: 20px;
+
+        .detail_option {
+            .search_item {
+                float: left;
+
+                span {
+                    display: inline-block;
+                    margin-right: 6px;
+                }
+
+                .el-input {
+                    width: 220px;
+                    margin: 0 28px 16px 0;
+                }
+
+                .elDatePicker {
+                    margin: 0 10px 14px 0;
+                }
+            }
+        }
+
+        .detail_botbox {
+            overflow: hidden;
+            flex: 1;
+        }
+
+        ::v-deep.detail_table {
+            float: left;
+            width: 400px;
+            height: 100%;
+            margin-right: 20px;
+
+            .el-table {
+                display: flex;
+                flex-direction: column;
+
+                .el-table__body-wrapper {
+                    overflow-y: auto;
+                    flex: 1;
+                }
+            }
+        }
+
+        .detail_map {
+            position: relative;
+            overflow: hidden;
+            min-height: 200px;
+            height: 100%;
+
+            p {
+                display: flex;
+                width: 300px;
+                padding-left: 10px;
+                line-height: 32px;
+                word-break: break-all;
+                border-radius: 3px;
+                background: #143164;
+
+                span {
+                    padding: 0 6px;
+                    color: #39B5FE;
+                    flex: 1;
+                }
+
+            }
+
+            .detail_map_info_l {
+                position: absolute;
+                left: 44px;
+                top: 160px;
+                padding: 0 20px 10px 20px;
+                border-radius: 5px;
+                background: #0D1846;
+
+                h2 {
+                    margin-bottom: 30px;
+                    color: #F94550;
+                }
+            }
+
+            .detail_map_info_r {
+                position: absolute;
+                right: 36px;
+                top: 260px;
+                padding: 0 20px;
+                border-radius: 5px;
+                background: #0D1846;
+
+                h2 {
+                    margin-bottom: 30px;
+                    color: #39B5FE;
+                }
+            }
+        }
+
+    }
+}
+</style>
\ No newline at end of file
diff --git a/web/src/views/SecureManage/SmartHelmet/PhoneManage.vue b/web/src/views/SecureManage/SmartHelmet/PhoneManage.vue
index f377ec1..300e799 100644
--- a/web/src/views/SecureManage/SmartHelmet/PhoneManage.vue
+++ b/web/src/views/SecureManage/SmartHelmet/PhoneManage.vue
@@ -1,3 +1,250 @@
-<template>
-    <div>照片管理</div>
-</template>
\ No newline at end of file
+<template>
+    <!-- 安全管理 ==> 智能安全帽 => 照片管理 -->
+    <div class="main">
+        <!-- main_left -->
+        <div class="main_left">
+            <div class="main_left_search">
+                <div class="search_item">
+                    <span>姓名:</span>
+                    <el-input size="mini" v-model="queryInfo.userName" placeholder="请输入姓名" clearable></el-input>
+                </div>
+                <div class="search_item">
+                    <span>设备编号:</span>
+                    <el-input size="mini" v-model="queryInfo.deviceNum" placeholder="请输入设备编号" clearable></el-input>
+                    <el-button icon="el-icon-search" @click="queryTable">查询</el-button>
+                </div>
+            </div>
+            <div class="main_left_table">
+                <cpnTable :table-loading="loading" :table-data="dataList" :table-columns="tableColumns"
+                    :row-click="rowClick">
+                </cpnTable>
+            </div>
+        </div>
+        <!-- main_right -->
+        <div class="main_right">
+            <div class="search_item">
+                <span>日期:</span>
+                <el-date-picker type="daterange" v-model="pics.date" value-format="yyyy-MM-dd" start-placeholder="开始日期"
+                    end-placeholder="结束日期" clearable></el-date-picker>
+                <!-- <el-select v-model="pics.type">
+                    <el-option :value="1">所有照片</el-option>
+                    <el-option :value="2">安全带取证</el-option>
+                </el-select> -->
+                <el-button icon="el-icon-search" @click="queryPic">查询</el-button>
+            </div>
+            <div class="pic_wrap">
+                <div class="pic_item" v-for="item in pics.data" :key="item.iid">
+                    <el-image :src="item.imageUrl" :preview-src-list="[item.imageUrl]" lazy></el-image>
+                    <div class="pic_item_info">
+                        <div class="pic_item_info_time">{{ item.smTime }}</div>
+                        <div class="pic_item_info_des"></div>
+                        <div class="pic_item_info_btn">
+                        </div>
+                    </div>
+                </div>
+                <div class="nopic" v-if="!pics.data.length">暂无图片</div>
+            </div>
+        </div>
+    </div>
+</template>
+  
+<script>
+import cpnTable from '@/components/element/Table'
+export default {
+    data() {
+        return {
+            loading: false,
+            queryInfo: {
+                pageNum: 1,
+                pageSize: 9999,
+                userName: '',
+                deviceNum: '',
+            },
+            dataList: [],
+            tableColumns: [],
+            userId: '', // 用户id
+            pics: {
+                date: [],
+                type: '',
+                data: [],
+            },
+            $http: '', // 接口api路径
+        }
+    },
+    components: {
+        cpnTable
+    },
+    created() {
+        this.$http = this.$api.Safety.SmartHelmet.pic
+        this.setTableColumn()
+        this.getLists()
+    },
+    methods: {
+        setTableColumn() {
+            this.tableColumns = [
+                {selection: true},
+                {name: "用户ID", key: "userId"},
+                {name: "姓名", key: "userName"},
+                {name: "设备编号", key: "deviceNum"},
+            ]
+        },
+        getLists() {
+            this.loading = true
+            this.$api.Safety.SmartHelmet.getLists(this.queryInfo).then(res => {
+                if (res.statusMsg === 'ok') {
+                    this.dataList = res.data.list
+                }
+            }).finally(() => this.loading = false)
+        },
+        getPics() {
+            const params = {
+                userId: this.userId,
+                pageNum: 1,
+                pageSize: 9999,
+                strTime: this.pics.date?.[0],
+                endTime: this.pics.date?.[1],
+            }
+            this.$http.getPics(params).then(res => {
+                if (res.statusMsg === 'ok') {
+                    this.pics.data = res.data.list
+                }
+            })
+        },
+        rowClick(row) {
+            this.userId = row.userId
+            this.getPics()
+        },
+        queryTable() {
+            this.getLists()
+        },
+        queryPic() {
+            this.getPics()
+        },
+    }
+}
+</script>
+  
+<style lang="scss" scoped>
+@import '@/style/layout-main.scss';
+
+.main {
+    display: flex;
+    flex-direction: row;
+    color: #EBEBEB;
+
+    .main_left {
+        display: flex;
+        flex-direction: column;
+        width: 380px;
+        padding: 14px;
+        margin-right: 18px;
+        border: 1px solid #39B5FE;
+        border-radius: 10px;
+        box-shadow: rgba(0, 145, 255, 0.7) 0 0 18px inset;
+        box-sizing: content-box;
+
+        .search_item {
+            span {
+                display: inline-block;
+                min-width: 54px;
+                margin-right: 6px;
+                text-align: right;
+            }
+
+            .el-input {
+                width: 220px;
+                margin: 0 28px 16px 0;
+            }
+        }
+
+        ::v-deep.main_left_table {
+            overflow: hidden;
+
+            .el-table__row {
+                cursor: pointer
+            }
+
+            .el-table {
+                display: flex;
+                flex-direction: column;
+
+                .el-table__body-wrapper {
+                    overflow-y: auto;
+                    flex: 1;
+                }
+            }
+        }
+    }
+
+    .main_right {
+        overflow: hidden;
+        display: flex;
+        flex-direction: column;
+        flex: 1;
+        padding: 20px;
+        border: 1px solid #39B5FE;
+        background-color: rgba(0, 52, 121, 0.3);
+        border-radius: 10px;
+        box-shadow: rgba(0, 145, 255, 0.7) 0 0 18px inset;
+        box-sizing: content-box;
+
+        .search_item {
+
+            .el-button,
+            .el-input,
+            .el-select {
+                margin: 0 12px 14px 6px;
+            }
+        }
+
+        .pic_wrap {
+            flex: 1;
+            display: flex;
+            flex-wrap: wrap;
+            justify-content: space-between;
+            align-items: center;
+            overflow-y: auto;
+
+            .nopic {
+                width: 100%;
+                text-align: center;
+                font-size: 28px;
+            }
+
+            .pic_item {
+                display: flex;
+                flex-direction: column;
+                width: 380px;
+                height: 300px;
+                border: 1px solid green;
+                margin: 10px;
+
+                .el-image {
+                    border: 1px solid #4EA7DD;
+                }
+
+                .pic_item_info {
+                    position: relative;
+                    flex: 1;
+                    margin-top: 3px;
+                    border: 1px solid #3DC8FF;
+                    background: rgba(21, 66, 107, .6);
+
+                    .pic_item_info_time {
+                        position: absolute;
+                        top: 40%;
+                        font-size: 13px;
+                        text-indent: 20px;
+                    }
+
+                    .pic_item_info_btn {
+                        position: absolute;
+                        top: 34px;
+                        right: 20px;
+                    }
+                }
+            }
+        }
+    }
+}
+</style>
\ No newline at end of file
diff --git a/web/src/views/SecureManage/SmartHelmet/SafeHat.vue b/web/src/views/SecureManage/SmartHelmet/SafeHat.vue
index ac93bb0..06f4ff5 100644
--- a/web/src/views/SecureManage/SmartHelmet/SafeHat.vue
+++ b/web/src/views/SecureManage/SmartHelmet/SafeHat.vue
@@ -1,3 +1,129 @@
-<template>
-    <div>安全帽设备</div>
-</template>
\ No newline at end of file
+<template>
+    <!-- 安全管理 ==> 智能安全帽 => 安全帽设备 -->
+    <div class="main">
+        <!-- main_left -->
+        <div class="main_left">
+            <div class="main_left_search">
+                <div class="search_item">
+                    <span>姓名:</span>
+                    <el-input size="mini" v-model="queryInfo.userName" placeholder="请输入姓名" clearable></el-input>
+                </div>
+                <div class="search_item">
+                    <span>设备编号:</span>
+                    <el-input size="mini" v-model="queryInfo.deviceNum" placeholder="请输入设备编号" clearable></el-input>
+                    <el-button icon="el-icon-search" v-permission="'search'" @click="queryTable">查询</el-button>
+                </div>
+            </div>
+            <div class="main_left_table">
+                <cpnTable :table-loading="loading" :table-data="dataList" :table-columns="tableColumns"
+                    :row-click="rowClick">
+                </cpnTable>
+            </div>
+        </div>
+    </div>
+</template>
+  
+<script>
+import cpnTable from '@/components/element/Table'
+export default {
+    data() {
+        return {
+            loading: false,
+            queryInfo: {
+                pageNum: 1,
+                pageSize: 9999,
+                userName: '',
+                deviceNum: '',
+            },
+            dataList: [],
+            tableColumns: [],
+            userId: '', // 用户id
+        }
+    },
+    components: {
+        cpnTable
+    },
+    created() {
+        this.setTableColumn()
+        this.getLists()
+    },
+    methods: {
+        setTableColumn() {
+            this.tableColumns = [
+                {selection: true},
+                {name: "用户ID", key: "userId"},
+                {name: "姓名", key: "userName"},
+                {name: "设备编号", key: "deviceNum"},
+            ]
+        },
+        getLists() {
+            this.loading = true
+            this.$api.Safety.SmartHelmet.getLists(this.queryInfo).then(res => {
+                if (res.statusMsg === 'ok') {
+                    this.dataList = res.data.list
+                }
+            }).finally(() => this.loading = false)
+        },
+        rowClick(row) {
+            this.userId = row.userId
+        },
+        queryTable() {
+            this.getLists()
+        },
+    }
+}
+</script>
+  
+<style lang="scss" scoped>
+@import '@/style/layout-main.scss';
+
+.main {
+    display: flex;
+    flex-direction: row;
+    color: #EBEBEB;
+
+    .main_left {
+        display: flex;
+        flex-direction: column;
+        width: 380px;
+        padding: 14px;
+        margin-right: 18px;
+        border: 1px solid #39B5FE;
+        border-radius: 10px;
+        box-shadow: rgba(0, 145, 255, 0.7) 0 0 18px inset;
+        box-sizing: content-box;
+
+        .search_item {
+            span {
+                display: inline-block;
+                min-width: 54px;
+                margin-right: 6px;
+                text-align: right;
+            }
+
+            .el-input {
+                width: 220px;
+                margin: 0 28px 16px 0;
+            }
+        }
+
+        ::v-deep.main_left_table {
+            overflow: hidden;
+
+            .el-table__row {
+                cursor: pointer
+            }
+
+            .el-table {
+                display: flex;
+                flex-direction: column;
+
+                .el-table__body-wrapper {
+                    overflow-y: auto;
+                    flex: 1;
+                }
+            }
+        }
+    }
+}
+</style>
\ No newline at end of file
diff --git a/web/src/views/SecureManage/SmartHelmet/TrackBack.vue b/web/src/views/SecureManage/SmartHelmet/TrackBack.vue
index 1939462..a943fa9 100644
--- a/web/src/views/SecureManage/SmartHelmet/TrackBack.vue
+++ b/web/src/views/SecureManage/SmartHelmet/TrackBack.vue
@@ -1,3 +1,738 @@
-<template>
-    <div>轨迹回放</div>
-</template>
\ No newline at end of file
+<template>
+    <!-- 安全管理 ==> 智能安全帽 => 轨迹回放 -->
+    <div class="main">
+        <!-- table -->
+        <div class="main_left">
+            <div class="main_left_search">
+                <div class="search_item">
+                    <span>姓名:</span>
+                    <el-input size="mini" v-model="queryInfo.userName" placeholder="请输入姓名" clearable></el-input>
+                </div>
+                <div class="search_item">
+                    <span>设备编号:</span>
+                    <el-input size="mini" v-model="queryInfo.deviceNum" placeholder="请输入设备编号" clearable></el-input>
+                    <el-button icon="el-icon-search" @click="queryTable">查询</el-button>
+                </div>
+            </div>
+            <div class="main_left_table">
+                <cpnTable :table-loading="loading" :table-data="dataList" :table-columns="tableColumns"
+                    :row-click="rowClick">
+                </cpnTable>
+            </div>
+        </div>
+        <!-- chart -->
+        <div class="main_right_echart" v-show="!isShowMap">
+            <div class="echart_options">
+                <span>日期:</span>
+                <el-date-picker class="elDatePicker" type="daterange" v-model="chart.date" value-format="yyyy-MM-dd"
+                    start-placeholder="开始日期" end-placeholder="结束日期" clearable></el-date-picker>
+                <el-button icon="el-icon-search" @click="queryEchart">查询</el-button>
+                <el-button style="float:right" @click="watchMap">查看</el-button>
+            </div>
+            <div ref="echart_bar" class="echart_bar"></div>
+            <div ref="echart_drop" class="echart_drop"></div>
+        </div>
+        <!-- map -->
+        <div class="main_right_map" v-show="isShowMap">
+            <div class="map_option">
+                <span>日期:</span>
+                <el-date-picker class="elDatePicker" type="daterange" v-model="chart.date" value-format="yyyy-MM-dd"
+                    start-placeholder="开始日期" end-placeholder="结束日期" clearable></el-date-picker>
+                <el-button icon="el-icon-search" @click="queryEchart">查询</el-button>
+                <el-button style="float:right" @click="hideMap">返回</el-button>
+            </div>
+            <div class="map_view">
+                <div class="map_view_main">
+                    <div id="container"></div>
+                    <div id="trackDetail" v-show="isShowTrackDetail">
+                        <h2>位置信息统计</h2>
+                        <div class="ul_box">
+                            <ul>
+                                <li :class="{ active: index === maps.trackListIndex }"
+                                    v-for="(val, index) in   maps.trackLists  " :key="index" @click="trackListClick(index)">
+                                    {{ val }}
+                                </li>
+                            </ul>
+                        </div>
+                    </div>
+                </div>
+                <div class="map_view_btn">
+                    <el-button @click="startAnimation">开始动画</el-button>
+                    <el-button @click="pauseAnimation">停止动画</el-button>
+                    <el-button @click="resumeAnimation">继续动画</el-button>
+                    <el-button @click="stopAnimation">停止动画</el-button>
+                    <el-button @click="showTrackDetail">轨迹详情</el-button>
+                </div>
+            </div>
+        </div>
+    </div>
+</template>
+  
+<script>
+import AMapLoader from '@amap/amap-jsapi-loader';
+import cpnTable from '@/components/element/Table'
+import icon from '@/assets/markerIcon.png'
+import 'echarts-liquidfill' // 水滴图形
+export default {
+    data() {
+        return {
+            loading: false,
+            isShowMap: false, // 是否查看轨迹地图
+            isShowTrackDetail: false, // 是否渲染轨迹详情
+            queryInfo: {
+                pageNum: 1,
+                pageSize: 9999,
+                userName: '',
+                deviceNum: '',
+            },
+            dataList: [],
+            tableColumns: [],
+            userId: '', // 用户id
+            // 图表数据
+            chart: {
+                date: [new Date(Date.now() - 30 * 24 * 60 * 60 * 1000).format(), new Date().format()], // 轨迹图表日期.默认近一个月
+                totaltime: '', // 安全帽在线总时长
+            },
+            maps: { // 地图数据
+                date: '',
+                trackLists: [], // 轨迹详情
+                trackListIndex: '',
+            },
+        }
+    },
+    components: {
+        cpnTable
+    },
+    created() {
+        // 添加密钥,获取高德地址使用
+        window._AMapSecurityConfig = {
+            securityJsCode: "aa90b00e27025d1f57286675cc8b232e",
+        }
+        // 存储全局变量.(不响应式)
+        this.maps.times = []
+        this.maps.lineArr = []
+        this.maps.AMap = null
+        this.maps.map = null
+        this.maps.mapMarker = null
+        this.chart.chartInstanceBar = null
+        this.chart.chartInstanceDrop = null
+        this.$http = this.$api.Safety.SmartHelmet.trackBack // 接口api路径
+
+        this.setTableColumn()
+        this.getLists()
+    },
+    mounted() {
+        window.addEventListener("resize", this.screenAdapter)
+    },
+    beforeDestroy() {
+        this.clearMap()
+        this.clearChart()
+        window.removeEventListener("resize", this.screenAdapter)
+    },
+    methods: {
+        setTableColumn() {
+            this.tableColumns = [
+                {selection: true},
+                {name: "用户ID", key: "userId"},
+                {name: "姓名", key: "userName"},
+                {name: "设备编号", key: "deviceNum"},
+            ]
+        },
+        getLists() {
+            this.loading = true
+            this.$api.Safety.SmartHelmet.getLists(this.queryInfo).then(res => {
+                if (res.statusMsg === 'ok') {
+                    this.dataList = res.data.list
+                }
+                this.loading = false
+            })
+        },
+        // 获取用户在线时长
+        async getOnlineTime() {
+            let out = {}
+            const params = {
+                userId: this.userId,
+                // strTime: this.chart.date?.[0],
+                // endTime: this.chart.date?.[1],
+                strTime: '2023-06-01',
+                endTime: '2023-06-31',
+            }
+            const {data, statusMsg} = await this.$http.getOnlineTime(params)
+            if (statusMsg === 'ok') {
+                out = {
+                    list: data.tHelmetTrajectory,
+                    total: data.total,
+                }
+            }
+            return out
+        },
+        // 获取用户轨迹
+        async getTrackLists() {
+            let out = {
+                positions: [], // 经纬度
+                times: [], // 时间
+            }
+            const params = {
+                userId: this.userId,
+                // strTime: this.chart.date?.[0],
+                // endTime: this.chart.date?.[1],
+                strTime: '2023-06-01',
+                endTime: '2023-06-31',
+            }
+            const {data, statusMsg} = await this.$http.getTrackLists(params)
+            if (statusMsg === 'ok') {
+                data.forEach(item => {
+                    out.positions.push([+item.ypoint, +item.xpoint])
+                    out.times.push(item.smTime)
+                })
+            }
+            return out
+        },
+        // 初始化图表
+        initChart() {
+            this.chart.chartInstanceBar = this.$echarts.init(this.$refs.echart_bar)
+            this.chart.chartInstanceDrop = this.$echarts.init(this.$refs.echart_drop)
+            this.renderBaseChart()
+        },
+        // 渲染chart基础配置
+        renderBaseChart() {
+            const baseBarOptions = {
+                animationDuration: 1500,
+                tooltip: {
+                    trigger: "axis",
+                },
+                legend: {
+                    itemWidth: 35,
+                    itemHeight: 12,
+                    x: 'right',
+                    y: 'top',
+                    textStyle: {
+                        color: "#fff",
+                    },
+                },
+                grid: {
+                    top: "10%",
+                    right: "0",
+                    left: "2%",
+                    bottom: "14",
+                    containLabel: true,
+                },
+                xAxis: [{
+                    type: 'category',
+                    axisLine: {
+                        lineStyle: {
+                            width: 2,
+                            color: "#B7E4F7",
+                        },
+                    },
+                    axisTick: {
+                        show: false,
+                    },
+                }],
+                yAxis: [{
+                    type: 'value',
+                    axisLabel: {
+                        formatter: "{value}",
+                        color: "#CAD3E0",
+                    },
+                    splitLine: {
+                        lineStyle: {
+                            width: 2,
+                            type: "dashed",
+                            color: "#5A7E9D",
+                        },
+                    },
+                }],
+                series: [{
+                    type: 'bar',
+                    name: "在线时长",
+                    barWidth: 14,
+                    color: new this.$echarts.graphic.LinearGradient(0, 0, 0, 1, [
+                        {offset: 0, color: "#C15B3C"},
+                        {offset: 1, color: "#FACD91"},
+                    ]),
+                }],
+            }
+            const baseDropOptions = {
+                series: [{
+                    type: 'liquidFill',
+                    data: [0],
+                    radius: '70%', //图表的大小 值是圆的直径
+                    shape: 'circle', //水填充图的形状 circle默认圆形  rect圆角矩形  triangle三角形  diamond菱形  pin水滴状 arrow箭头状  还可以是svg的path
+                    center: ['50%', '50%'], //图表相对于盒子的位置
+                    outline: {
+                        show: false
+                    },
+                    backgroundStyle: { // 背景配置
+                        color: 'transparent', //背景颜色
+                        borderWidth: '2',//边框大小
+                        borderColor: 'rgba(17, 94, 176, 0.4)', // 边框颜色
+                    },
+                    itemStyle: {
+                        color: new this.$echarts.graphic.LinearGradient(
+                            0, 0, 0, 1, //4个参数用于配置渐变色的起止位置, 这4个参数依次对应右/下/左/上四个方位. 而0 0 0 1则代表渐变色从正上方开始
+                            [
+                                {offset: 0, color: '#2790F4'},
+                                {offset: 1, color: '#165190'}
+                            ] //数组, 用于配置颜色的渐变过程. 每一项为一个对象, 包含offset和color两个参数. offset的范围是0 ~ 1, 用于表示位置
+                        )
+                    },
+                }]
+            }
+            this.chart.chartInstanceBar.setOption(baseBarOptions)
+            this.chart.chartInstanceDrop.setOption(baseDropOptions)
+        },
+        // 渲染chart内容数据(x,y,series)
+        async renderChart() {
+            const {list, total} = await this.getOnlineTime()
+            const x = [] // x轴数据
+            const y = [] // y轴数据
+            list.forEach(item => {
+                x.push(item.ctime)
+                y.push(item.ltime)
+            })
+            const baseBarOptions = {
+                xAxis: {
+                    index: 0,
+                    data: x
+                },
+                series: {
+                    index: 0,
+                    data: y
+                }
+            }
+            const baseDropOptions = {
+                series: {
+                    index: 0,
+                    data: [0.3],
+                    label: {
+                        color: '#fff',
+                        fontSize: 28,
+                        formatter: () => this.handleTime(total),
+                    }
+                }
+            }
+            this.chart.chartInstanceBar.setOption(baseBarOptions)
+            this.chart.chartInstanceDrop.setOption(baseDropOptions)
+        },
+        // 处理秒=>天时分
+        handleTime(num) {
+            let second = num * 60
+            let day = 0
+            let hour = 0
+            let minutes = 0
+            if (second < 86400) {
+                day = 0;
+                if (second < 3600) {
+                    hour = 0;
+                    if (second < 60) {
+                        minutes = 0;
+                    } else {
+                        minutes = Math.floor(second / 60);
+                    }
+                } else {
+                    hour = Math.floor(second / 3600);
+                    if (second - hour * 3600 < 60) {
+                        minutes = 0;
+                    } else {
+                        minutes = Math.floor((second - hour * 3600) / 60);
+                    }
+                }
+            } else {
+                day = Math.floor(second / 86400);
+                if (second - 86400 * day < 3600) {
+                    hour = 0;
+                    minutes = Math.floor((second - 86400 * day) / 60);
+                } else {
+                    hour = Math.floor((second - 86400 * day) / 3600);
+                    if (second - 86400 * day - 3600 * hour < 60) {
+                        minutes = 0;
+                    } else {
+                        minutes = Math.floor((second - 86400 * day - 3600 * hour) / 60);
+                    }
+                }
+            }
+            day = day ? day + "天" : ''
+            hour = hour ? hour + "小时" : ''
+            minutes = minutes && minutes + "分"
+            return day + hour + minutes
+        },
+        rowClick(row) {
+            this.userId = row.userId
+            if (!this.chart.chartInstanceBar) {
+                this.initChart()
+            }
+            this.hideMap()
+            this.renderChart()
+        },
+        queryTable() {
+            this.getLists()
+        },
+        queryEchart() {
+            this.renderChart()
+        },
+        queryMap() {
+
+        },
+        // chart 查看按钮
+        watchMap() {
+            if (!this.userId) {
+                this.$message.warning('请先选择用户');
+                return
+            }
+            this.renderMap()
+        },
+        async renderMap() {
+            this.isShowMap = !this.isShowMap
+            let {positions, times} = await this.getTrackLists()
+            this.maps.times = times
+            this.maps.lineArr = positions
+            AMapLoader.load({
+                key: "1dc3895771d98745fa27bbcc3280464f", // 申请好的Web端开发者Key,首次调用 load 时必填
+                version: "2.0", // 指定要加载的 JSAPI 的版本,缺省时默认为 1.4.15
+            }).then(AMap => {
+                this.maps.AMap = AMap
+                AMap.plugin('AMap.MoveAnimation', () => {
+                    // 创建地图
+                    this.maps.map = new AMap.Map("container", {
+                        resizeEnable: true,
+                        viewMode: "2D", // 是否为3D地图模式
+                        zoom: 17, // 初始化地图级别
+                    })
+                    if (positions.length) {
+                        // 创建一个 (小人) Icon
+                        var personIcon = new AMap.Icon({
+                            image: icon, // 图标的取图地址
+                            imageSize: new AMap.Size(40, 40), // 图标所用图片大小
+                        })
+                        // 创建marker实例
+                        let mapMarker = this.maps.mapMarker = new AMap.Marker({
+                            map: this.maps.map,
+                            position: positions[0],
+                            icon: personIcon,
+                            zIndex: 10,
+                            offset: new AMap.Pixel(-20, -36),
+                        })
+                        // marker提示框
+                        let infoWindow = new AMap.InfoWindow({offset: new AMap.Pixel(0, -30)});
+                        let markerMouseover = async (e) => {
+                            let index = e.target.content
+                            let adress = await this.getAdress(positions[index])
+                            let time = times[index]
+                            let el = `
+                            <p><t>地址:</t><span>${adress}</span></p>
+                            <p><t>时间:</t><span>${time}</span></p>
+                            <p><t>经纬度:</t><span>${positions[index][0]}, ${positions[index][1]}</span></p>
+                            `
+                            infoWindow.setContent(el)
+                            infoWindow.open(this.maps.map, e.target.getPosition())
+                        }
+                        // 给折线图添加拐点和绑定事件
+                        for (var i = 0; i < positions.length; i += 1) {
+                            let center = positions[i]
+                            let icon = new AMap.Icon({
+                                size: new AMap.Size(6, 11),    // 图标尺寸
+                                image: 'https://webapi.amap.com/theme/v1.3/markers/n/mark_b.png',  // Icon的图像
+                                imageSize: new AMap.Size(6, 11)
+                            })
+                            let marker = new AMap.Marker({
+                                map: this.maps.map,
+                                position: center,
+                                icon: icon,
+                                offset: new AMap.Pixel(-3, -10)
+                            })
+                            marker.content = i
+                            marker.on("mouseover", markerMouseover)
+                        }
+                        // 绘制轨迹
+                        new AMap.Polyline({
+                            map: this.maps.map,
+                            path: positions,
+                            // showDir: true,
+                            strokeColor: "#28F",  //线颜色
+                            strokeWeight: 3,      //线宽
+                        })
+                        // 已通过轨迹
+                        let passedPolyline = new AMap.Polyline({
+                            map: this.maps.map,
+                            strokeColor: "red",  //线颜色
+                            strokeWeight: 3,      //线宽
+                        })
+                        mapMarker.on('moving', (e) => {
+                            passedPolyline.setPath(e.passedPath);
+                            this.maps.map.setCenter(e.target.getPosition(), true)
+                        })
+                        this.maps.map.setFitView()
+                    }
+                })
+            })
+        },
+        // 获取地址
+        getAdress(lnglats) {
+            return new Promise(resolve => {
+                const AMap = this.maps.AMap
+                AMap.plugin('AMap.Geocoder', () => {
+                    const geocoder = new AMap.Geocoder({
+                        radius: 1000,
+                        extensions: "all"
+                    })
+                    geocoder.getAddress(lnglats, (status, result) => {
+                        let address = ''
+                        if (status === 'complete' && result.info === 'OK') {
+                            address = result.regeocodes ? result.regeocodes : result.regeocode.formattedAddress
+                        }
+                        resolve(address)
+                    })
+                })
+            })
+        },
+        hideMap() {
+            this.clearMap()
+            this.isShowMap = false
+            this.maps.trackLists = []
+            this.maps.trackListIndex = ''
+        },
+        startAnimation() {
+            this.maps.mapMarker?.moveAlong(this.maps.lineArr, {
+                // 每一段的时长
+                duration: 500,//可根据实际采集时间间隔设置
+                // JSAPI2.0 是否延道路自动设置角度在 moveAlong 里设置
+                autoRotation: true,
+            })
+        },
+        pauseAnimation() {
+            this.maps.mapMarker?.pauseMove()
+        },
+        resumeAnimation() {
+            this.maps.mapMarker?.resumeMove()
+        },
+        stopAnimation() {
+            this.maps.mapMarker?.stopMove()
+        },
+        // 显示轨迹详情
+        async showTrackDetail() {
+            this.isShowTrackDetail = !this.isShowTrackDetail
+            if (this.maps.trackLists.length) {
+                return
+            }
+            if (this.maps.times.length && this.maps.lineArr.length) {
+                let tracks = []
+                let adress = await this.getAdress([...this.maps.lineArr])
+                adress.forEach((item, index) => {
+                    tracks.push(`位置: ${this.maps.times[index]}(${item.formattedAddress})`)
+                })
+                this.maps.trackLists = tracks
+            }
+        },
+        trackListClick(index) {
+            this.maps.trackListIndex = index
+        },
+        screenAdapter() {
+            this.chart.chartInstanceBar?.resize()
+            this.chart.chartInstanceDrop?.resize()
+        },
+        clearMap() {
+            this.maps.mapMarker?.stopMove()
+            this.maps.map?.destroy()
+        },
+        clearChart() {
+            this.chart.chartInstanceBar?.clear()
+            this.chart.chartInstanceDrop?.clear()
+        },
+    }
+}
+</script>
+  
+<style lang="scss" scoped>
+@import '@/style/layout-main.scss';
+
+::v-deep .amap-info-content {
+    flex-direction: row;
+    width: 220px;
+    padding: 14px;
+    line-height: 20px;
+    border-radius: 5px;
+    background: #0D1846;
+
+    p {
+        display: flex;
+        width: 100%;
+    }
+
+    t {
+        width: 40px;
+    }
+
+    span {
+        flex: 1;
+        color: #39B5FE;
+    }
+}
+
+::v-deep .bottom-center .amap-info-sharp {
+    border-top: 8px solid #0D1846;
+}
+
+.active {
+    color: #39B5FE;
+}
+
+#container {
+    min-height: 400px;
+    flex: 1.6;
+}
+
+#trackDetail {
+    overflow: hidden;
+    display: flex;
+    flex-direction: column;
+    flex: 1;
+
+    h2 {
+        text-indent: 20px;
+    }
+
+    .ul_box {
+        overflow-y: auto;
+        flex: 1;
+
+        ul {
+            margin: 0;
+            padding: 0 16px;
+        }
+
+        li {
+            padding: 10px 0;
+            font-size: 15px;
+            line-height: 24px;
+            list-style: none;
+            border-bottom: 1px dashed #fff;
+            cursor: pointer;
+        }
+    }
+}
+
+.main {
+    display: flex;
+    flex-direction: row;
+    color: #EBEBEB;
+
+    .main_left {
+        display: flex;
+        flex-direction: column;
+        width: 380px;
+        padding: 14px;
+        margin-right: 18px;
+        border: 1px solid #39B5FE;
+        border-radius: 10px;
+        box-shadow: rgba(0, 145, 255, 0.7) 0 0 18px inset;
+        box-sizing: content-box;
+
+        .search_item {
+            span {
+                display: inline-block;
+                min-width: 54px;
+                margin-right: 6px;
+                text-align: right;
+            }
+
+            .el-input {
+                width: 220px;
+                margin: 0 28px 16px 0;
+            }
+        }
+
+        ::v-deep.main_left_table {
+            overflow: hidden;
+
+            .el-table__row {
+                cursor: pointer
+            }
+
+            .el-table {
+                display: flex;
+                flex-direction: column;
+
+                .el-table__body-wrapper {
+                    overflow-y: auto;
+                    flex: 1;
+                }
+            }
+        }
+    }
+
+    .main_right_echart {
+        overflow: hidden;
+        display: flex;
+        flex-direction: column;
+        flex: 1;
+        padding: 20px;
+        border: 1px solid #39B5FE;
+        background-color: rgba(0, 52, 121, 0.3);
+        border-radius: 10px;
+        box-shadow: rgba(0, 145, 255, 0.7) 0 0 18px inset;
+        box-sizing: content-box;
+
+        .echart_options {
+            span {
+                display: inline-block;
+                margin-right: 6px;
+                text-align: right;
+            }
+
+            .elDatePicker {
+                width: 260px;
+                margin: 0 10px 14px 0;
+            }
+        }
+
+        .echart_bar {
+            flex: 1;
+        }
+
+        .echart_drop {
+            flex: 1.1;
+        }
+    }
+
+    .main_right_map {
+        display: flex;
+        flex-direction: column;
+        position: absolute;
+        right: 0;
+        left: 430px;
+        top: 0;
+        bottom: 0;
+        padding: 20px;
+
+        .map_option {
+            span {
+                display: inline-block;
+                margin-right: 6px;
+            }
+
+            .elDatePicker {
+                width: 260px;
+                margin: 0 10px 14px 0;
+            }
+        }
+
+        .map_view {
+            overflow: hidden;
+            display: flex;
+            flex-direction: column;
+            flex: 1;
+
+            .map_view_main {
+                overflow: hidden;
+                display: flex;
+                flex: 1;
+            }
+
+            .map_view_btn {
+                margin-top: 10px;
+            }
+        }
+    }
+}
+</style>
\ No newline at end of file

--
Gitblit v1.9.3