unknown
2023-10-23 5672f352d0ba114e2ae96c8cefad6c74ae6d2934
苏州-web:智能安全帽提交
已修改11个文件
已添加2个文件
已删除2个文件
5492 ■■■■■ 文件已修改
web/package-lock.json 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
web/package.json 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
web/src/api/modules/safety.js 517 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
web/src/assets/markerIcon.png 补丁 | 查看 | 原始文档 | blame | 历史
web/src/components/element/Form.vue 64 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
web/src/components/element/Table.vue 300 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
web/src/components/table/Pagination.vue 78 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
web/src/components/table/Table.vue 126 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
web/src/plugins/public.js 363 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
web/src/style/layout-main.scss 959 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
web/src/views/ProjectManage/SectionManage.vue 1219 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
web/src/views/SecureManage/SmartHelmet/AlarmRecord.vue 728 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
web/src/views/SecureManage/SmartHelmet/PhoneManage.vue 253 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
web/src/views/SecureManage/SmartHelmet/SafeHat.vue 132 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
web/src/views/SecureManage/SmartHelmet/TrackBack.vue 741 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
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",
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",
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 }),
    }
  }
}
web/src/assets/markerIcon.png
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>
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>
web/src/components/table/Pagination.vue
文件已删除
web/src/components/table/Table.vue
文件已删除
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')
    })
}
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
}
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);
  }
}
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>
web/src/views/SecureManage/SmartHelmet/AlarmRecord.vue
@@ -1,3 +1,725 @@
<template>
    <div>报警记录</div>
</template>
<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>
web/src/views/SecureManage/SmartHelmet/PhoneManage.vue
@@ -1,3 +1,250 @@
<template>
    <div>照片管理</div>
</template>
<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>
web/src/views/SecureManage/SmartHelmet/SafeHat.vue
@@ -1,3 +1,129 @@
<template>
    <div>安全帽设备</div>
</template>
<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>
web/src/views/SecureManage/SmartHelmet/TrackBack.vue
@@ -1,3 +1,738 @@
<template>
    <div>轨迹回放</div>
</template>
<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>