fuliqi
2024-04-19 fed41b2fd390ae729c05f63fcbc9f5e93cfd8f71
Merge remote-tracking branch 'origin/master'

# Conflicts:
# src/views/system/rule/index.vue
# src/views/system/template/index.vue
31个文件已修改
20个文件已添加
2个文件已删除
142592 ■■■■■ 已修改文件
package.json 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
public/index.html 52 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/platform/threshold.js 55 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/platform/unit.js 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/platform/work-order.js 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/map/texture/circle-point.png 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/map/texture/gz-map-fx.jpg 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/map/texture/gz-map.jpg 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/map/texture/map_texture.jpg 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/map/texture/rotating-point2.png 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/map/texture/rotatingAperture.png 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/map/texture/scene-bg2.png 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/map/zigong.json 60219 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/map/zigong1.json 73667 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/map/zigong2.json 6776 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/FileUpload/index.vue 9 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/permission.js 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/home/data-view/components/data-map.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/screen/components/screen-data/index.vue 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/screen/components/screen-map-cover/index.vue 36 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/screen/components/screen-map-three/experience/camera.js 66 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/screen/components/screen-map-three/experience/cssRenderer.js 35 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/screen/components/screen-map-three/experience/index.js 95 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/screen/components/screen-map-three/experience/renderer.js 37 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/screen/components/screen-map-three/experience/utils/eventEmitter.js 197 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/screen/components/screen-map-three/experience/utils/sizes.js 35 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/screen/components/screen-map-three/experience/utils/time.js 31 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/screen/components/screen-map-three/experience/world/enviroment.js 177 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/screen/components/screen-map-three/experience/world/map.js 315 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/screen/components/screen-map-three/experience/world/world.js 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/screen/components/screen-map-three/index.vue 51 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/screen/components/screen-map/index.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/screen/components/screen-wrapper/index.vue 32 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/screen/index.vue 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/calculate/order/index.vue 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/calculate/rule/index.vue 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/car/index.vue 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/contract/index.vue 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/face/index.vue 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/point/index.vue 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/region/index.vue 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/report/index.vue 5 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/result/index.vue 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/rule/default/index.vue 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/score/default/index.vue 5 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/template/default/index.vue 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/threshold/index.vue 276 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/unit/index.vue 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/unit/people/index.vue 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/video/index.vue 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/work-order/distribute/index.vue 127 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/work-order/index.vue 162 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
vue.config.js 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
package.json
@@ -41,6 +41,7 @@
    "axios": "0.24.0",
    "clipboard": "2.0.8",
    "core-js": "3.25.3",
    "d3": "^7.9.0",
    "echarts": "5.4.0",
    "echarts-gl": "^2.0.9",
    "element-ui": "2.15.14",
@@ -55,6 +56,7 @@
    "quill": "1.3.7",
    "screenfull": "5.0.2",
    "sortablejs": "1.10.2",
    "three": "^0.163.0",
    "v-scale-screen": "1.0.0",
    "vue": "2.6.12",
    "vue-count-to": "1.0.13",
public/index.html
@@ -1,21 +1,27 @@
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
    <meta name="renderer" content="webkit">
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
    <link rel="icon" href="<%= BASE_URL %>favicon.ico">
    <title><%= webpackConfig.name %></title>
    <!--[if lt IE 11]><script>window.location.href='/html/ie.html';</script><![endif]-->
      <style>
<head>
  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
  <meta name="renderer" content="webkit">
  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
  <link rel="icon" href="<%= BASE_URL %>favicon.ico">
  <title>
    <%= webpackConfig.name %>
  </title>
  <!--[if lt IE 11]><script>window.location.href='/html/ie.html';</script><![endif]-->
  <style>
    html,
    body,
    #app {
      height: 100%;
      margin: 0px;
      padding: 0px;
      overflow: hidden;
    }
    .chromeframe {
      margin: 0.2em 0;
      background: #ccc;
@@ -92,6 +98,7 @@
        -ms-transform: rotate(0deg);
        transform: rotate(0deg);
      }
      100% {
        -webkit-transform: rotate(360deg);
        -ms-transform: rotate(360deg);
@@ -105,6 +112,7 @@
        -ms-transform: rotate(0deg);
        transform: rotate(0deg);
      }
      100% {
        -webkit-transform: rotate(360deg);
        -ms-transform: rotate(360deg);
@@ -194,15 +202,17 @@
      opacity: 0.5;
    }
  </style>
  </head>
  <body>
    <div id="app">
        <div id="loader-wrapper">
            <div id="loader"></div>
            <div class="loader-section section-left"></div>
            <div class="loader-section section-right"></div>
            <div class="load_title">正在加载系统资源,请耐心等待</div>
        </div>
    </div>
  </body>
</html>
</head>
<body>
  <div id="app">
    <div id="loader-wrapper">
      <div id="loader"></div>
      <div class="loader-section section-left"></div>
      <div class="loader-section section-right"></div>
      <div class="load_title">正在加载系统资源,请耐心等待</div>
    </div>
  </div>
</body>
</html>
src/api/platform/threshold.js
@@ -3,16 +3,32 @@
// 查询运维阈值列表
export function listThreshold(query) {
  return request({
    url: '/ycl/threshold/list',
    url: '/threshold/list',
    method: 'get',
    params: query
  })
}
// 查询运维阈值详细
export function getThreshold(id) {
export function getVideo(id) {
  return request({
    url: '/ycl/threshold/' + id,
    url: '/threshold/video/' + id,
    method: 'get'
  })
}
// 查询运维阈值详细
export function getCar(id) {
  return request({
    url: '/threshold/car/' + id,
    method: 'get'
  })
}
// 查询运维阈值详细
export function getFace(id) {
  return request({
    url: '/threshold/face/' + id,
    method: 'get'
  })
}
@@ -20,7 +36,7 @@
// 新增运维阈值
export function addThreshold(data) {
  return request({
    url: '/ycl/threshold',
    url: '/threshold',
    method: 'post',
    data: data
  })
@@ -29,7 +45,7 @@
// 修改运维阈值
export function updateThreshold(data) {
  return request({
    url: '/ycl/threshold',
    url: '/threshold',
    method: 'put',
    data: data
  })
@@ -38,7 +54,34 @@
// 删除运维阈值
export function delThreshold(id) {
  return request({
    url: '/ycl/threshold/' + id,
    url: '/threshold/' + id,
    method: 'delete'
  })
}
// 修改视频阈值
export function editVideo(data) {
  return request({
    url: '/threshold/video',
    method: 'put',
    data: data
  })
}
// 修改车辆阈值
export function editCar(data) {
  return request({
    url: '/threshold/car',
    method: 'put',
    data: data
  })
}
// 修改人脸阈值
export function editFace(data) {
  return request({
    url: '/threshold/face',
    method: 'put',
    data: data
  })
}
src/api/platform/unit.js
@@ -50,3 +50,11 @@
    method: 'get'
  })
}
// 获取运维单位工单统计列表
export function workList() {
  return request({
    url: '/yw-unit/work/list',
    method: 'get'
  })
}
src/api/platform/work-order.js
@@ -27,6 +27,15 @@
  })
}
// 下发选择工单
export function selectedIdsDistribute(data) {
  return request({
    url: '/work-order/distribute/ids',
    method: 'post',
    data: data
  })
}
// 查询运维工单详细
export function getWorkOrder(id) {
  return request({
@@ -78,3 +87,27 @@
    data: data
  })
}
// 获取工单运维情况
export function getYwCondition(data) {
  return request({
    url: '/work-order/yw-condition/' + data,
    method: 'get'
  })
}
// 获取工单运维审核记录
export function getYwAuditingList(data) {
  return request({
    url: '/work-order/yw-auditing-list/' + data,
    method: 'get'
  })
}
// 获取工单运维情况记录
export function getYwConditionList(data) {
  return request({
    url: '/work-order/yw-condition-list/' + data,
    method: 'get'
  })
}
src/assets/map/texture/circle-point.png
src/assets/map/texture/gz-map-fx.jpg
src/assets/map/texture/gz-map.jpg
src/assets/map/texture/map_texture.jpg
src/assets/map/texture/rotating-point2.png
src/assets/map/texture/rotatingAperture.png
src/assets/map/texture/scene-bg2.png
src/assets/map/zigong.json
File was deleted
src/assets/map/zigong1.json
File was deleted
src/assets/map/zigong2.json
New file
Diff too large
src/components/FileUpload/index.vue
@@ -28,9 +28,12 @@
    <!-- 文件列表 -->
    <transition-group class="upload-file-list el-upload-list el-upload-list--text" name="el-fade-in-linear" tag="ul">
      <li :key="file.url" class="el-upload-list__item ele-upload-list__item-content" v-for="(file, index) in fileList">
        <el-link :href="`${baseUrl}${file.url}`" :underline="false" target="_blank">
        <el-link @click="handleDownload(file.url)" :underline="false" target="_blank">
          <span class="el-icon-document"> {{ getFileName(file.name) }} </span>
        </el-link>
        <!-- <el-link :href="`${baseUrl}${file.url}`" :underline="false" target="_blank">
          <span class="el-icon-document"> {{ getFileName(file.name) }} </span>
        </el-link> -->
        <div class="ele-upload-list__item-content-action">
          <el-link :underline="false" @click="handleDelete(index)" type="danger">删除</el-link>
        </div>
@@ -189,6 +192,10 @@
        strs += list[i].url + separator;
      }
      return strs != '' ? strs.substr(0, strs.length - 1) : '';
    },
    /** 下载按钮操作 */
    handleDownload (data) {
      this.$download.resource(data);
    }
  }
};
src/permission.js
@@ -8,7 +8,7 @@
NProgress.configure({ showSpinner: false })
const whiteList = ['/login', '/register',]
const whiteList = ['/login', '/register','/screen']
router.beforeEach((to, from, next) => {
  NProgress.start()
src/views/home/data-view/components/data-map.vue
@@ -8,7 +8,7 @@
<script>
import * as echarts from 'echarts';
import 'echarts-gl';
import mapData from '@/assets/map/zigong.json';
import mapData from '@/assets/map/zigong2.json';
echarts.registerMap('zigong', mapData);
console.log(mapData);
let mapChart = null;
src/views/screen/components/screen-data/index.vue
@@ -67,7 +67,7 @@
        grid: {
          top: '10%',
          right: 0,
          bottom: '15%',
          bottom: '17%',
        },
        legend: {
          right: 0,
@@ -170,8 +170,8 @@
  margin: 10px 0;
  .panel-item {
    width: 120px;
    height: 120px;
    width: 110px;
    height: 110px;
  }
}
</style>
src/views/screen/components/screen-map-cover/index.vue
New file
@@ -0,0 +1,36 @@
<template>
  <div class="map-container">
    <wrapper-title :title="'区域地图'"></wrapper-title>
    <div class="map-content">
    </div>
  </div>
</template>
<script>
import WrapperTitle from '../wrapper-title/index';
export default {
  name: 'ScreenMapCover',
  components: {
    WrapperTitle
  }
}
</script>
<style lang="scss" scoped>
.map-container {
  width: 100%;
  flex: 1;
  margin-bottom: 20px;
  display: flex;
  flex-direction: column;
  .map-content {
    flex: 1;
    pointer-events: none;
  }
}
</style>
src/views/screen/components/screen-map-three/experience/camera.js
New file
@@ -0,0 +1,66 @@
import { MathUtils } from 'three';
import { PerspectiveCamera, CameraHelper } from 'three';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';
export default class Camera {
    constructor(experience) {
        this.experience = experience;
        this.scene = experience.scene;
        this.canvas = experience.canvas;
        this.sizes = experience.sizes;
        this.setInstance();
        this.setOrbitControls();
    }
    // 设置透视相机
    setInstance() {
        this.instance = new PerspectiveCamera(45, this.sizes.width / this.sizes.height, 0.1, 200);
        this.instance.position.set(0, 45, 45);
        this.scene.add(this.instance);
        // const help = new CameraHelper(this.instance);
        // this.scene.add(help);
    }
    setOrbitControls() {
        this.controls = new OrbitControls(this.instance, this.canvas);
        this.controls.target.set(0, 0, 5);
        this.controls.enableDamping = true;
        this.controls.minDistance = 20;
        this.controls.maxDistance = 80;
        this.controls.maxPolarAngle = MathUtils.degToRad(80);
        // this.controls.maxPolarAngle = (-Math.PI / 2);
    }
    resize() {
        // 重新计算比例
        this.cameraAspect = this.sizes.width / this.sizes.height;
        this.instance.updateProjectionMatrix();
    }
    update() {
        this.controls.update();
    }
    destroy() {
        this.disposeObject();
        this.removeObject();
        this.resetObject();
    }
    disposeObject() {
        this.controls.dispose();
    }
    removeObject() {
        this.scene.remove(this.instance);
    }
    resetObject() {
        this.controls = null;
        this.instance = null;
        this.scene = null;
        this.canvas = null;
        this.sizes = null;
    }
}
src/views/screen/components/screen-map-three/experience/cssRenderer.js
New file
@@ -0,0 +1,35 @@
import * as THREE from 'three';
import { CSS2DRenderer } from 'three/examples/jsm/renderers/CSS2DRenderer';
export default class CSSRenderer {
  constructor(experience) {
    this.experience = experience;
    this.container = experience.container;
    this.canvas = experience.canvas;
    this.sizes = experience.sizes;
    this.scene = experience.scene;
    this.camera = experience.camera;
    this.setInstance();
  }
  setInstance() {
    this.instance = new CSS2DRenderer();
    this.instance.setSize(this.sizes.width, this.sizes.height);
    this.instance.domElement.style.position = 'absolute';
    this.instance.domElement.style.top = '0px';
    this.instance.domElement.style.left = '0px';
    this.instance.domElement.style.pointerEvents = 'none';
    this.container.appendChild(this.instance.domElement);
  }
  resize() {
    this.instance.setSize(this.sizes.width, this.sizes.height);
    this.instance.setPixelRatio(this.sizes.pixelRatio);
  }
  update() {
    this.instance.render(this.scene, this.camera.instance);
  }
  destroy() {
    this.instance.domElement.remove();
  }
}
src/views/screen/components/screen-map-three/experience/index.js
New file
@@ -0,0 +1,95 @@
import { Scene, GridHelper, AxesHelper } from 'three';
import Stats from "three/examples/jsm/libs/stats.module";
import World from "./world/world";
import Camera from "./camera";
import Renderer from "./renderer";
import CSSRenderer from './cssRenderer';
// 工具类
import Sizes from "./utils/sizes";
import Time from "./utils/time";
export default class Experience {
  constructor(canvas) {
    this.canvas = canvas;
    this.container = canvas.parentElement;
    this.sizes = new Sizes(this.canvas);
    this.time = new Time();
    this.scene = new Scene();
    this.camera = new Camera(this);
    this.renderer = new Renderer(this);
    this.cssRenderer = new CSSRenderer(this);
    this.world = new World(this);
    // const size = 100;
    // const divisions = 100;
    // const gridHelper = new GridHelper(size, divisions);
    // this.scene.add(gridHelper);
    // this.stats = new Stats();
    // document.querySelector('.map-container').appendChild(this.stats.dom);
    // 帧
    this.time.on('tick', () => {
      this.update();
    });
  }
  update() {
    this.camera.update();
    this.world.update();
    this.renderer.update();
    this.cssRenderer.update();
    // this.stats.update();
  }
  /**
   * 销毁场景
   */
  destroy() {
    this.disposeObject();
    this.resetObject();
  }
  disposeObject() {
    this.time.destroy();
    this.world.destroy();
    this.camera.destroy();
    this.renderer.destroy();
    this.cssRenderer.destroy();
    this.scene.traverse((child) => {
      if (child.material) {
        // 可能存在材质为数组的情况
        if (child.material instanceof Array) {
          child.material.forEach((item) => item.dispose());
        } else {
          child.material.dispose();
          if (child.material.map) {
            child.material.map.dispose();
          }
        }
      }
      if (child.geometry) {
        child.geometry.dispose();
        child.geometry.attributes = null; // 这些属性包括position, normal, uv等等
      }
      child = null;
    });
  }
  resetObject() {
    this.world = null;
    this.camera = null;
    this.renderer = null;
    this.cssRenderer = null;
    this.scene = null;
    this.canvas = null;
    this.container = null;
    this.time = null;
    this.sizes = null;
  }
}
src/views/screen/components/screen-map-three/experience/renderer.js
New file
@@ -0,0 +1,37 @@
import * as THREE from 'three';
export default class Renderer {
    constructor(experience) {
        this.experience = experience;
        this.canvas = this.experience.canvas;
        this.sizes = this.experience.sizes;
        this.scene = this.experience.scene;
        this.camera = this.experience.camera;
        this.setInstance();
    }
    setInstance() {
        this.instance = new THREE.WebGLRenderer({
            canvas: this.canvas,
            antialias: true,
            alpha: true,
            logarithmicDepthBuffer: true
        });
        // this.instance.toneMapping = THREE.CineonToneMapping;
        // this.instance.toneMappingExposure = 1.75;
        // this.instance.shadowMap.enabled = true;
        // this.instance.shadowMap.type = THREE.PCFSoftShadowMap;
        this.instance.setSize(this.sizes.width, this.sizes.height);
        this.instance.setPixelRatio(this.sizes.pixelRatio);
    }
    resize() {
        this.instance.setSize(this.sizes.width, this.sizes.height);
        this.instance.setPixelRatio(this.sizes.pixelRatio);
    }
    update() {
        this.instance.render(this.scene, this.camera.instance);
    }
    destroy() {
        this.instance.dispose();
        this.instance.domElement.remove();
    }
}
src/views/screen/components/screen-map-three/experience/utils/eventEmitter.js
New file
@@ -0,0 +1,197 @@
export default class EventEmitter
{
    constructor()
    {
        this.callbacks = {}
        this.callbacks.base = {}
    }
    on(_names, callback)
    {
        // Errors
        if(typeof _names === 'undefined' || _names === '')
        {
            console.warn('wrong names')
            return false
        }
        if(typeof callback === 'undefined')
        {
            console.warn('wrong callback')
            return false
        }
        // Resolve names
        const names = this.resolveNames(_names)
        // Each name
        names.forEach((_name) =>
        {
            // Resolve name
            const name = this.resolveName(_name)
            // Create namespace if not exist
            if(!(this.callbacks[ name.namespace ] instanceof Object))
                this.callbacks[ name.namespace ] = {}
            // Create callback if not exist
            if(!(this.callbacks[ name.namespace ][ name.value ] instanceof Array))
                this.callbacks[ name.namespace ][ name.value ] = []
            // Add callback
            this.callbacks[ name.namespace ][ name.value ].push(callback)
        })
        return this
    }
    off(_names)
    {
        // Errors
        if(typeof _names === 'undefined' || _names === '')
        {
            console.warn('wrong name')
            return false
        }
        // Resolve names
        const names = this.resolveNames(_names)
        // Each name
        names.forEach((_name) =>
        {
            // Resolve name
            const name = this.resolveName(_name)
            // Remove namespace
            if(name.namespace !== 'base' && name.value === '')
            {
                delete this.callbacks[ name.namespace ]
            }
            // Remove specific callback in namespace
            else
            {
                // Default
                if(name.namespace === 'base')
                {
                    // Try to remove from each namespace
                    for(const namespace in this.callbacks)
                    {
                        if(this.callbacks[ namespace ] instanceof Object && this.callbacks[ namespace ][ name.value ] instanceof Array)
                        {
                            delete this.callbacks[ namespace ][ name.value ]
                            // Remove namespace if empty
                            if(Object.keys(this.callbacks[ namespace ]).length === 0)
                                delete this.callbacks[ namespace ]
                        }
                    }
                }
                // Specified namespace
                else if(this.callbacks[ name.namespace ] instanceof Object && this.callbacks[ name.namespace ][ name.value ] instanceof Array)
                {
                    delete this.callbacks[ name.namespace ][ name.value ]
                    // Remove namespace if empty
                    if(Object.keys(this.callbacks[ name.namespace ]).length === 0)
                        delete this.callbacks[ name.namespace ]
                }
            }
        })
        return this
    }
    trigger(_name, _args)
    {
        // Errors
        if(typeof _name === 'undefined' || _name === '')
        {
            console.warn('wrong name')
            return false
        }
        let finalResult = null
        let result = null
        // Default args
        const args = !(_args instanceof Array) ? [] : _args
        // Resolve names (should on have one event)
        let name = this.resolveNames(_name)
        // Resolve name
        name = this.resolveName(name[ 0 ])
        // Default namespace
        if(name.namespace === 'base')
        {
            // Try to find callback in each namespace
            for(const namespace in this.callbacks)
            {
                if(this.callbacks[ namespace ] instanceof Object && this.callbacks[ namespace ][ name.value ] instanceof Array)
                {
                    this.callbacks[ namespace ][ name.value ].forEach(function(callback)
                    {
                        result = callback.apply(this, args)
                        if(typeof finalResult === 'undefined')
                        {
                            finalResult = result
                        }
                    })
                }
            }
        }
        // Specified namespace
        else if(this.callbacks[ name.namespace ] instanceof Object)
        {
            if(name.value === '')
            {
                console.warn('wrong name')
                return this
            }
            this.callbacks[ name.namespace ][ name.value ].forEach(function(callback)
            {
                result = callback.apply(this, args)
                if(typeof finalResult === 'undefined')
                    finalResult = result
            })
        }
        return finalResult
    }
    resolveNames(_names)
    {
        let names = _names
        names = names.replace(/[^a-zA-Z0-9 ,/.]/g, '')
        names = names.replace(/[,/]+/g, ' ')
        names = names.split(' ')
        return names
    }
    resolveName(name)
    {
        const newName = {}
        const parts = name.split('.')
        newName.original  = name
        newName.value     = parts[ 0 ]
        newName.namespace = 'base' // Base namespace
        // Specified namespace
        if(parts.length > 1 && parts[ 1 ] !== '')
        {
            newName.namespace = parts[ 1 ]
        }
        return newName
    }
}
src/views/screen/components/screen-map-three/experience/utils/sizes.js
New file
@@ -0,0 +1,35 @@
/**
 * 计算大小
 */
import EventEmitter from './eventEmitter';
export default class Sizes extends EventEmitter {
    constructor(canvas) {
        super();
        this.container = document.querySelector('.map-container');
        this.pixelRatio = Math.min(window.devicePixelRatio, 2);
        this.width = this.container.offsetWidth;
        this.height = this.container.offsetHeight;
        this.device = document.body.clientWidth <= 968 ? 'mobile' : 'pc';
        // this.resizeObserver = new ResizeObserver(entries => {
        //     let rect = canvas.getBoundingClientRect();
        //     this.scaleX = rect.width / this.width;
        //     this.scaleY = rect.height / this.height;
        //     console.log(this.scaleX, this.scaleY);
        // })
        // this.resizeObserver.observe(this.container);
        // 宽高变化
        // window.addEventListener('resize', () => {
        //     this.pixelRatio = Math.min(window.devicePixelRatio, 2);
        //     this.trigger('resize');
        //     if (this.width < 968 && this.device !== 'mobile') {
        //         this.device = 'mobile';
        //         this.trigger('devicechange');
        //     } else if (this.width >= 968 && this.device !== 'pc') {
        //         this.device = 'pc';
        //         this.trigger('devicechange');
        //     }
        // });
    }
}
src/views/screen/components/screen-map-three/experience/utils/time.js
New file
@@ -0,0 +1,31 @@
import EventEmitter from "./eventEmitter";
export default class Time extends EventEmitter {
    constructor() {
        super();
        this.start = Date.now();
        this.current = this.start;
        this.elapsed = 0;
        this.delta = 16;
        // 合适的时机执行loop循环
        window.requestAnimationFrame(() => {
            this.tick();
        });
    }
    tick() {
        const currentTime = Date.now();
        this.delta = currentTime - this.current;
        this.current = currentTime;
        this.elapsed = this.current - this.start;
        this.trigger('tick');
        this.loopId = window.requestAnimationFrame(() => {
            this.tick();
        });
    }
    destroy() {
        window.cancelAnimationFrame(this.loopId);
        this.off('tick');
    }
}
src/views/screen/components/screen-map-three/experience/world/enviroment.js
New file
@@ -0,0 +1,177 @@
import * as THREE from 'three';
import rotatingAperture from '@/assets/map/texture/rotatingAperture.png';
import rotatingPoint from '@/assets/map/texture/rotating-point2.png';
import circlePoint from '@/assets/map/texture/circle-point.png';
import sceneBg from '@/assets/map/texture/scene-bg2.png';
import { GUI } from "three/examples/jsm/libs/lil-gui.module.min.js";
export default class Enviroment {
  constructor(experience) {
    this.experience = experience;
    this.scene = this.experience.scene;
    this.textureLoader = new THREE.TextureLoader();
    this.setSunLight();
    this.setRotateHola();
    this.setBackground();
    this.setCirclePoint();
    // this.debuger();
  }
  setSunLight() {
    //   平行光1
    this.directionalLight1 = new THREE.DirectionalLight(0xffffff, 0.9);
    this.directionalLight1.position.set(0, 57, 33);
    //   平行光2
    this.directionalLight2 = new THREE.DirectionalLight(0xffffff, 0.6);
    this.directionalLight2.position.set(-95, 28, -33);
    // 环境光
    this.ambientLight = new THREE.AmbientLight(0xffffff, 0.8);
    this.scene.add(this.directionalLight1);
    this.scene.add(this.directionalLight2);
    this.scene.add(this.ambientLight);
  }
  setRotateHola() {
    const rotatingApertureTexture = this.textureLoader.load(rotatingAperture);
    const rotatingPointTexture = this.textureLoader.load(rotatingPoint);
    const meshConfig1 = {
      width: 48,
      height: 48,
      texture: rotatingApertureTexture,
      positionList: [0, 0.4, 0],
      scaleList: [1, 1, 1],
      rotateList: [-Math.PI / 2, 0, 0]
    };
    const meshConfig2 = {
      width: 40,
      height: 40,
      texture: rotatingPointTexture,
      positionList: [0, 0.3, 0],
      scaleList: [1, 1, 1],
      rotateList: [-Math.PI / 2, 0, 0]
    };
    this.hola1 = this.createMesh(meshConfig1);
    this.hola2 = this.createMesh(meshConfig2);
    this.scene.add(this.hola1);
    this.scene.add(this.hola2);
  }
  setBackground() {
    const sceneBgTexture = this.textureLoader.load(sceneBg);
    const plane = new THREE.PlaneGeometry(120, 120);
    const material = new THREE.MeshPhongMaterial({
      // color: 0x061920,
      color: 0xffffff,
      map: sceneBgTexture,
      transparent: true,
      opacity: 1,
      // depthTest: true
    });
    this.background = new THREE.Mesh(plane, material);
    this.background.rotation.set(-Math.PI / 2, 0, 0);
    this.background.position.set(0, 0.1, 0);
    this.scene.add(this.background);
  }
  setCirclePoint() {
    const circleTexture = this.textureLoader.load(circlePoint);
    const plane = new THREE.PlaneGeometry(45, 45);
    const material = new THREE.MeshPhongMaterial({
      color: 0x00ffff,
      map: circleTexture,
      transparent: true,
      opacity: 1,
      depthTest: false,
    });
    this.circle = new THREE.Mesh(plane, material);
    this.circle.rotation.set(-Math.PI / 2, 0, 0);
    this.circle.position.set(0, 0.2, 0);
    this.scene.add(this.circle);
  }
  createMesh(config) {
    let { width, height, texture, positionList, rotateList, scaleList } = config;
    let plane = new THREE.PlaneGeometry(width, height);
    let material = new THREE.MeshBasicMaterial({
      map: texture,
      transparent: true,
      opacity: 1,
      depthTest: false,
    });
    let mesh = new THREE.Mesh(plane, material);
    mesh.position.set(...positionList);
    mesh.scale.set(...scaleList);
    mesh.rotation.set(...rotateList);
    return mesh;
  }
  update() {
    if (this.hola1) {
      this.hola1.rotation.z += 0.001;
    }
    if (this.hola2) {
      this.hola2.rotation.z -= 0.001;
    }
  }
  destroy() {
    this.disposeObject();
    this.removeObject();
    this.resetObject();
  }
  disposeObject() {
    this.hola1.geometry.dispose();
    this.hola1.material.dispose();
    this.hola2.geometry.dispose();
    this.hola2.material.dispose();
    this.background.geometry.dispose();
    this.background.material.dispose();
    this.circle.geometry.dispose();
    this.circle.material.dispose();
    this.directionalLight1.dispose();
    this.directionalLight2.dispose();
    this.ambientLight.dispose();
  }
  removeObject() {
    this.scene.remove(this.hola1);
    this.scene.remove(this.hola2);
    this.scene.remove(this.background);
    this.scene.remove(this.circle);
  }
  resetObject() {
    this.hola1 = null;
    this.hola2 = null;
    this.background = null;
    this.circle = null;
    this.directionalLight1 = null;
    this.directionalLight2 = null;
    this.ambientLight = null;
  }
  debuger() {
    const gui = new GUI();
    const folder1 = gui.addFolder('平行光1');
    const folder2 = gui.addFolder('平行光2');
    const folder3 = gui.addFolder('环境光');
    folder1.add(this.directionalLight1.position, 'x').min(-200).max(200).step(1).name("x轴的位置");
    folder1.add(this.directionalLight1.position, 'y').min(-200).max(200).step(1).name("y轴的位置");
    folder1.add(this.directionalLight1.position, 'z').min(-200).max(200).step(1).name("z轴的位置");
    folder1.add(this.directionalLight1, 'intensity').min(0).max(1).step(0.1).name("强度");
    folder2.add(this.directionalLight2.position, 'x').min(-200).max(200).step(1).name("x轴的位置");
    folder2.add(this.directionalLight2.position, 'y').min(-200).max(200).step(1).name("y轴的位置");
    folder2.add(this.directionalLight2.position, 'z').min(-200).max(200).step(1).name("z轴的位置");
    folder2.add(this.directionalLight2, 'intensity').min(0).max(1).step(0.1).name("强度");
    folder3.add(this.ambientLight, 'intensity').min(0).max(1).step(0.1).name("强度");
  }
}
src/views/screen/components/screen-map-three/experience/world/map.js
New file
@@ -0,0 +1,315 @@
import * as THREE from 'three';
import * as d3 from 'd3';
import mapData from '@/assets/map/zigong2.json';
import { CSS2DObject } from 'three/examples/jsm/renderers/CSS2DRenderer';
import textureMapImage from '@/assets/map/texture/gz-map.jpg';
import textureMapFxImage from '@/assets/map/texture/gz-map-fx.jpg';
import gsap from 'gsap';
// 地图深度
const MAP_DEPTH = 0.2;
const projection = d3.geoMercator().center([104.779307, 29.33924]).translate([0, 0, 0]);
const raycaster = new THREE.Raycaster();
export default class Map {
    constructor(experience) {
        this.experience = experience;
        this.scene = experience.scene;
        this.camera = experience.camera;
        this.provinceMeshList = [];
        this.labelList = [];
        this.textureLoader = new THREE.TextureLoader();
        this.setTexture();
        this.operationData(mapData);
        setTimeout(() => {
            this.enterAnimation();
        }, 500);
    }
    setTexture() {
        const textureMap = this.textureLoader.load(textureMapImage);
        const textureMapFx = this.textureLoader.load(textureMapFxImage);
        textureMap.wrapS = textureMapFx.wrapS = THREE.RepeatWrapping;
        textureMap.wrapT = textureMapFx.wrapT = THREE.RepeatWrapping;
        textureMap.flipY = textureMapFx.flipY = false;
        textureMap.rotation = textureMapFx.rotation = THREE.MathUtils.degToRad(45);
        const scale = 0.128;
        textureMap.repeat.set(scale, scale);
        textureMapFx.repeat.set(scale, scale);
        this.topFaceMaterial = new THREE.MeshPhongMaterial({
            map: textureMap,
            // color: 0xb4eeea,
            color: 0xb3fffa,
            combine: THREE.MultiplyOperation,
            transparent: true,
            opacity: 1,
        });
        this.sideMaterial = new THREE.MeshLambertMaterial({
            color: 0x123024,
            transparent: true,
            opacity: 0.9,
        });
    }
    /**
     * 解析json数据,并绘制地图多边形
     * @param {*} jsondata 地图数据
     */
    operationData(jsondata) {
        this.map = new THREE.Group();
        // geo信息
        const features = jsondata.features;
        features.forEach((feature) => {
            // 单个省份 对象
            const province = new THREE.Object3D();
            // 地址
            province.properties = feature.properties.name;
            province.isHover = false;
            // 多个情况
            // console.log(feature.geometry.type);
            if (feature.geometry.type === "MultiPolygon") {
                feature.geometry.coordinates.forEach((coordinate) => {
                    coordinate.forEach((rows) => {
                        const line = this.drawBoundary(rows);
                        const mesh = this.drawExtrudeMesh(rows);
                        province.add(line);
                        province.add(mesh);
                        this.provinceMeshList.push(mesh);
                    });
                });
            }
            // 单个情况
            if (feature.geometry.type === "Polygon") {
                feature.geometry.coordinates.forEach((coordinate) => {
                    const line = this.drawBoundary(coordinate);
                    const mesh = this.drawExtrudeMesh(coordinate);
                    province.add(line);
                    province.add(mesh);
                    this.provinceMeshList.push(mesh);
                });
            }
            const label = this.drawLabelText(feature);
            this.labelList.push({ name: feature.properties.name, label });
            province.add(label);
            this.map.add(province);
        });
        this.map.position.set(1, 1, -1.5);
        this.map.scale.set(10, 10, 1);
        this.map.rotation.set(THREE.MathUtils.degToRad(-90), 0, THREE.MathUtils.degToRad(20));
        this.scene.add(this.map);
        this.setMouseEvent();
    }
    /**
     * 画区域分界线
     * @param {*} polygon 区域坐标点数组
     * @returns 区域分界线
     */
    drawBoundary(polygon) {
        const points = [];
        for (let i = 0; i < polygon.length; i++) {
            const [x, y] = projection(polygon[i]);
            points.push(new THREE.Vector3(x, -y, 0));
        }
        const lineGeometry = new THREE.BufferGeometry().setFromPoints(points);
        const lineMaterial = new THREE.LineBasicMaterial({
            color: 0xffffff,
            linewidth: 2,
            transparent: true,
            depthTest: false,
        });
        const line = new THREE.Line(lineGeometry, lineMaterial);
        line.translateZ(MAP_DEPTH + 0.001);
        return line;
    }
    /**
     * 绘制区域多边形
     * @param {*} polygon 区域坐标点数组
     * @returns 区域多边形
     */
    drawExtrudeMesh(polygon) {
        const shape = new THREE.Shape();
        for (let i = 0; i < polygon.length; i++) {
            const [x, y] = projection(polygon[i]);
            if (i === 0) {
                shape.moveTo(x, -y);
            }
            shape.lineTo(x, -y);
        }
        const geometry = new THREE.ExtrudeGeometry(shape, {
            depth: MAP_DEPTH,
            bevelEnabled: false,
            bevelSegments: 1,
            bevelThickness: 0.1,
        });
        return new THREE.Mesh(geometry, [
            this.topFaceMaterial,
            this.sideMaterial,
        ]);
    }
    drawLabelText(province) {
        const [x, y] = projection(province.properties.center);
        const div = document.createElement('div');
        div.innerHTML = province.properties.name;
        div.style.padding = '4px 10px';
        div.style.color = '#fff';
        div.style.fontSize = '16px';
        div.style.position = 'absolute';
        div.style.backgroundColor = 'rgba(25,25,25,0.5)';
        div.style.borderRadius = '5px';
        const label = new CSS2DObject(div);
        div.style.pointerEvents = 'none';
        label.position.set(x, y, MAP_DEPTH + 0.05);
        return label;
    }
    setMouseEvent() {
        this.mouseEvent = this.handleEvent.bind(this);
        this.experience.canvas.addEventListener("mousemove", this.mouseEvent);
    }
    removeMouseEvent() {
        this.experience.canvas.removeEventListener("mousemove", this.mouseEvent);
    }
    handleEvent(e) {
        // console.log(this);
        // return;
        if (this.map) {
            let mouse = new THREE.Vector2();
            let getBoundingClientRect = this.experience.canvas.getBoundingClientRect();
            let x = ((e.clientX - getBoundingClientRect.left) / getBoundingClientRect.width) * 2 - 1;
            let y = -((e.clientY - getBoundingClientRect.top) / getBoundingClientRect.height) * 2 + 1;
            mouse.x = x;
            mouse.y = y;
            raycaster.setFromCamera(mouse, this.camera.instance);
            let intersects = raycaster.intersectObjects(this.provinceMeshList, false);
            if (intersects.length) {
                let temp = intersects[0].object;
                this.animation(temp.parent);
                this.showLabel(temp.parent);
            } else {
                this.animation();
            }
        }
    }
    throttle(callback, delay) {
        let lastCall = 0;
        return function () {
            let now = new Date().getTime();
            if (now - lastCall >= delay) {
                lastCall = now;
                callback.apply(null, arguments);
            }
        };
    }
    animation(province) {
        if (province) {
            if (!province.isHover) {
                province.isHover = true;
                this.map.children.forEach((item) => {
                    if (item.properties === province.properties) {
                        gsap.to(province.position, {
                            z: 0.12,
                            duration: 0.6
                        })
                    } else {
                        this.resetAnimation(item);
                    }
                })
            }
        } else {
            this.resetAllAnimation();
        }
    }
    resetAnimation(province) {
        gsap.to(province.position, {
            z: 0,
            duration: 0.6,
            onComplete: () => {
                province.isHover = false;
            }
        })
    }
    resetAllAnimation() {
        this.map.children.forEach((item) => {
            this.resetAnimation(item);
        })
    }
    showLabel(province) {
        this.labelList.forEach((item) => {
            // if (item.name === province.properties) {
            //     item.label.element.style.visibility = 'visible';
            // } else {
            //     item.label.element.style.visibility = 'hidden';
            // }
        })
    }
    enterAnimation() {
        gsap.to(this.map.scale, {
            z: 10,
            duration: 0.6,
            ease: 'power1.out'
        })
    }
    destroy() {
        this.removeMouseEvent();
        this.disposeObject();
        this.removeObject();
        this.resetObject();
    }
    disposeObject() {
        this.map.traverse((child) => {
            if (child.material) {
                // 可能存在材质为数组的情况
                if (child.material instanceof Array) {
                    child.material.forEach((item) => item.dispose());
                } else {
                    child.material.dispose();
                    if (child.material.map) {
                        child.material.map.dispose();
                    }
                }
            }
            if (child.geometry) {
                child.geometry.dispose();
                child.geometry.attributes = null; // 这些属性包括position, normal, uv等等
            }
            child = null;
        });
        this.topFaceMaterial.dispose();
        this.sideMaterial.dispose();
    }
    removeObject() {
        this.scene.remove(this.map);
    }
    resetObject() {
        this.map = null;
        this.provinceMeshList = null;
        this.labelList = null;
        this.textureLoader = null;
    }
}
src/views/screen/components/screen-map-three/experience/world/world.js
New file
@@ -0,0 +1,21 @@
import EventEmitter from '../utils/eventEmitter';
import Enviroment from './enviroment';
import Map from './map';
export default class World extends EventEmitter  {
  constructor(experience) {
    super();
    this.experience = experience;
    this.scene = this.experience.scene;
    this.enviroment = new Enviroment(this.experience);
    this.map = new Map(this.experience);
  }
  update() {
    this.enviroment.update();
  }
  destroy() {
    this.enviroment.destroy();
    this.map.destroy();
  }
}
src/views/screen/components/screen-map-three/index.vue
New file
@@ -0,0 +1,51 @@
<template>
  <div class="map-container">
    <canvas class="world" ref="worldContainer"></canvas>
  </div>
</template>
<script>
import Experience from './experience/index';
let world = null;
export default {
  name: 'ScreenMapThree',
  props: {
    loadEnd: {
      type: Boolean,
      default: false
    },
  },
  watch: {
    loadEnd: {
      handler(newVal) {
        if (newVal) {
          world = new Experience(this.$refs.worldContainer);
        }
      }
    }
  },
  mounted() {
  },
  beforeDestroy() {
    world.destroy();
    world = null;
  },
}
</script>
<style lang="scss" scoped>
.map-container {
  width: 100%;
  height: 100%;
  position: absolute;
  left: 0;
  top: 0;
  z-index: 0;
  .world {
    width: 100%;
    height: 100%;
  }
}
</style>
src/views/screen/components/screen-map/index.vue
@@ -12,7 +12,7 @@
<script>
import * as echarts from 'echarts';
import 'echarts-gl';
import mapData from '@/assets/map/zigong1.json';
import mapData from '@/assets/map/zigong2.json';
import WrapperTitle from '../wrapper-title/index';
import ScreenData from '../screen-data/index';
src/views/screen/components/screen-wrapper/index.vue
@@ -5,13 +5,14 @@
      返回
    </div>
    <div class="wrapper-content">
      <screen-map-three :loadEnd="isEnd"></screen-map-three>
      <div class="left-container wrapper">
        <screen-face class="enter-left" :class="{ 'animate-enter-x': isEnd }"></screen-face>
        <screen-car class="enter-left animate-delay-1" :class="{ 'animate-enter-x': isEnd }"></screen-car>
        <screen-video class="enter-left animate-delay-2" :class="{ 'animate-enter-x': isEnd }"></screen-video>
      </div>
      <div class="center-container center-wrapper">
        <screen-map></screen-map>
        <screen-map-cover></screen-map-cover>
        <screen-table class="enter-top" :class="{ 'animate-enter-y': isEnd }"></screen-table>
        <!-- <screen-detection></screen-detection> -->
@@ -30,16 +31,19 @@
import ScreenFace from '../screen-face/index';
import ScreenVideo from '../screen-video/index';
import ScreenCar from '../screen-car/index';
import ScreenMap from '../screen-map/index';
import ScreenMapCover from '../screen-map-cover/index';
import ScreenTable from '../screen-table/index';
import ScreenMapThree from '../screen-map-three/index';
import ScreenData from '../screen-data/index';
export default {
  name: 'ScreenWrapper',
  components: {
    SelectItem,
    ScreenExamine,
    ScreenMap,
    ScreenTable,
    ScreenMapCover,
    ScreenMapThree,
    ScreenFace,
    ScreenVideo,
    ScreenCar,
@@ -47,7 +51,7 @@
  },
  data() {
    return {
      isEnd: false
      isEnd: false,
    }
  },
  methods: {
@@ -65,7 +69,7 @@
    const container = document.querySelector('.screen-wrapper');
    container.addEventListener('transitionend', this.checkAnimationEnd);
  },
  destroyed() {
  beforDestroy() {
    const container = document.querySelector('.screen-wrapper');
    container.removeEventListener('transitionend', this.checkAnimationEnd);
  }
@@ -133,24 +137,30 @@
  flex-direction: column;
  justify-content: space-between;
}
.animate-enter-x {
  animation: enter-x 0.4s ease forwards;
}
.animate-enter-y {
  animation: enter-y 0.4s ease forwards;
}
.enter-left {
  transform: translateX(-100px);
  opacity: 0;
}
.enter-right {
  transform: translateX(100px);
  opacity: 0;
}
.enter-top {
  transform: translateY(100px);
  opacity: 0;
}
.animate-delay-1 {
  animation-delay: 0.1s;
}
@@ -164,14 +174,22 @@
}
@keyframes enter-x {
  100% {
  from {
    opacity: 0;
  }
  to {
    opacity: 1;
    transform: translateX(0);
  }
}
@keyframes enter-y {
  100% {
  from {
    opacity: 0;
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
src/views/screen/index.vue
@@ -1,6 +1,5 @@
<template>
  <div class="screen-container">
    <screen-title></screen-title>
    <v-scale-screen width="1920" height="1080" :autoScale="true" :delay="0" class="screen">
      <screen-wrapper></screen-wrapper>
@@ -12,6 +11,7 @@
<script>
import ScreenTitle from './components/screen-title/index.vue';
import ScreenWrapper from './components/screen-wrapper/index.vue';
export default {
  name: 'App',
@@ -30,6 +30,10 @@
</script>
<style lang="scss" scoped>
.screen-container {
  user-select: none;
  -webkit-user-select: none;
}
.screen {
  background: url('../../assets/images/screen/pageBg1.jpg') !important;
  background-size: cover !important;
src/views/system/calculate/order/index.vue
@@ -57,6 +57,7 @@
          icon="el-icon-delete"
          size="mini"
          :disabled="multiple"
          v-hasPermi="['system:result:remove']"
          @click="handleDelete"
        >删除</el-button>
      </el-col>
@@ -66,6 +67,7 @@
          plain
          icon="el-icon-download"
          size="mini"
          v-hasPermi="['system:result:export']"
          @click="handleExport"
        >导出</el-button>
      </el-col>
src/views/system/calculate/rule/index.vue
@@ -45,18 +45,21 @@
            type="text"
            icon="el-icon-edit"
            @click="handleUpdate(scope.row)"
            v-hasPermi="['calculate:rule:edit']"
          >修改</el-button>
          <el-button
            size="mini"
            type="text"
            icon="el-icon-plus"
            @click="handleAdd(scope.row)"
            v-hasPermi="['calculate:rule:add']"
          >新增</el-button>
          <el-button
            size="mini"
            type="text"
            icon="el-icon-delete"
            @click="handleDelete(scope.row)"
            v-hasPermi="['calculate:rule:remove']"
          >删除</el-button>
        </template>
      </el-table-column>
src/views/system/car/index.vue
@@ -108,6 +108,7 @@
          icon="el-icon-download"
          size="mini"
          @click="handleExport"
          v-hasPermi="['system:monitor:export']"
        >导出</el-button>
      </el-col>
      <right-toolbar :showSearch.sync="showSearch" @queryTable="getList" :columns="columns"></right-toolbar>
src/views/system/contract/index.vue
@@ -24,6 +24,7 @@
          plain
          icon="el-icon-plus"
          size="mini"
          v-hasPermi="['system:contract:add']"
          @click="handleAdd"
        >新增</el-button>
      </el-col>
@@ -33,6 +34,7 @@
          plain
          icon="el-icon-top"
          size="mini"
          v-hasPermi="['system:contract:import']"
          @click="handleImportPoint"
        >导入</el-button>
      </el-col>
src/views/system/face/index.vue
@@ -107,6 +107,7 @@
          icon="el-icon-download"
          size="mini"
          @click="handleExport"
          v-hasPermi="['system:monitor:export']"
        >导出</el-button>
      </el-col>
      <right-toolbar :showSearch.sync="showSearch" @queryTable="getList" :columns="columns"></right-toolbar>
src/views/system/point/index.vue
@@ -49,6 +49,7 @@
          size="mini"
          :disabled="single"
          @click="handleUpdate"
          v-hasPermi="['point:edit']"
        >修改</el-button>
      </el-col>
      <el-col :span="1.5">
@@ -59,6 +60,7 @@
          size="mini"
          :disabled="multiple"
          @click="handleDelete"
          v-hasPermi="['point:remove']"
        >删除</el-button>
      </el-col>
      <el-col :span="1.5">
@@ -68,6 +70,7 @@
          icon="el-icon-top"
          size="mini"
          @click="handleImportPoint"
          v-hasPermi="['point:import']"
        >导入点位</el-button>
      </el-col>
      <el-col :span="1.5">
@@ -77,6 +80,7 @@
          icon="el-icon-receiving"
          size="mini"
          @click="handleEditBatch"
          v-hasPermi="['point:edit']"
        >批量修改</el-button>
      </el-col>
      <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
@@ -116,12 +120,14 @@
            type="text"
            icon="el-icon-edit"
            @click="handleUpdate(scope.row)"
            v-hasPermi="['point:edit']"
          >修改</el-button>
          <el-button
            size="mini"
            type="text"
            icon="el-icon-delete"
            @click="handleDelete(scope.row)"
            v-hasPermi="['point:remove']"
          >删除</el-button>
        </template>
      </el-table-column>
src/views/system/region/index.vue
@@ -45,6 +45,7 @@
          icon="el-icon-plus"
          size="mini"
          @click="handleAdd"
          v-hasPermi="['system:report:add']"
        >新增</el-button>
      </el-col>
      <el-col :span="1.5">
@@ -55,6 +56,7 @@
          size="mini"
          :disabled="single"
          @click="handleUpdate"
          v-hasPermi="['system:report:edit']"
        >修改</el-button>
      </el-col>
      <el-col :span="1.5">
@@ -65,6 +67,7 @@
          size="mini"
          :disabled="multiple"
          @click="handleDelete"
          v-hasPermi="['system:report:remove']"
        >删除</el-button>
      </el-col>
      <el-col :span="1.5">
@@ -74,6 +77,7 @@
          icon="el-icon-download"
          size="mini"
          @click="handleExport"
          v-hasPermi="['system:report:export']"
        >导出</el-button>
      </el-col>
      <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
@@ -103,12 +107,14 @@
            type="text"
            icon="el-icon-edit"
            @click="handleUpdate(scope.row)"
            v-hasPermi="['system:report:edit']"
          >修改</el-button>
          <el-button
            size="mini"
            type="text"
            icon="el-icon-delete"
            @click="handleDelete(scope.row)"
            v-hasPermi="['system:report:remove']"
          >删除</el-button>
        </template>
      </el-table-column>
src/views/system/report/index.vue
@@ -52,6 +52,7 @@
          icon="el-icon-plus"
          size="mini"
          @click="handleAdd"
          v-hasPermi="['system:report:add']"
        >新增</el-button>
      </el-col>
<!--      <el-col :span="1.5">-->
@@ -72,6 +73,7 @@
          size="mini"
          :disabled="multiple"
          @click="handleDelete"
          v-hasPermi="['system:report:remove']"
        >删除</el-button>
      </el-col>
      <el-col :span="1.5">
@@ -81,6 +83,7 @@
          icon="el-icon-download"
          size="mini"
          @click="handleExport"
          v-hasPermi="['system:report:export']"
        >导出</el-button>
      </el-col>
      <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
@@ -118,7 +121,7 @@
            type="text"
            icon="el-icon-edit"
            @click="handleAuditing(scope.row)"
            v-hasPermi="['system:report:audit']"
            v-hasPermi="['system:report:edit']"
            v-if="scope.row.status === 0"
          >审核</el-button>
          <el-button
src/views/system/result/index.vue
@@ -73,8 +73,8 @@
            </div>
            <div class="bottom-publish">
              <el-button size="medium" type="success">发布</el-button>
              <el-button size="medium" @click="jumpDetail" type="info">详情</el-button>
              <el-button size="medium" v-hasPermi="['calculate:rule:add']" type="success">发布</el-button>
              <el-button size="medium" v-hasPermi="['calculate:rule:query']" @click="jumpDetail" type="info">详情</el-button>
            </div>
          </div>
        </div>
src/views/system/rule/default/index.vue
@@ -85,18 +85,21 @@
            type="text"
            icon="el-icon-edit"
            @click="handleUpdate(scope.row)"
            v-hasPermi="['system:rule:edit']"
          >修改</el-button>
          <el-button
            size="mini"
            type="text"
            icon="el-icon-plus"
            @click="handleAdd(scope.row)"
            v-hasPermi="['system:rule:add']"
          >新增</el-button>
          <el-button
            size="mini"
            type="text"
            icon="el-icon-delete"
            @click="handleDelete(scope.row)"
            v-hasPermi="['system:rule:remove']"
          >删除</el-button>
        </template>
      </el-table-column>
src/views/system/score/default/index.vue
@@ -48,6 +48,7 @@
          icon="el-icon-plus"
          size="mini"
          @click="handleAdd"
           v-hasPermi="['system:template:add']"
        >新增</el-button>
      </el-col>
      <el-col :span="1.5">
@@ -58,6 +59,7 @@
          size="mini"
          :disabled="single"
          @click="handleUpdate"
           v-hasPermi="['system:template:edit']"
        >修改</el-button>
      </el-col>
      <el-col :span="1.5">
@@ -68,6 +70,7 @@
          size="mini"
          :disabled="multiple"
          @click="handleDelete"
           v-hasPermi="['system:template:remove']"
        >删除</el-button>
      </el-col>
      <el-col :span="1.5">
@@ -77,7 +80,7 @@
          icon="el-icon-download"
          size="mini"
          @click="handleExport"
          v-hasPermi="['system:auditing:export']"
          v-hasPermi="['system:template:export']"
        >导出</el-button>
      </el-col>
      <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
src/views/system/template/default/index.vue
@@ -42,6 +42,7 @@
          icon="el-icon-plus"
          size="mini"
          @click="handleAdd"
          v-hasPermi="['platform:template:add']"
        >新增</el-button>
      </el-col>
      <el-col :span="1.5">
@@ -51,6 +52,7 @@
          icon="el-icon-edit"
          size="mini"
          :disabled="single"
          v-hasPermi="['platform:template:edit']"
          @click="handleUpdate"
        >修改</el-button>
      </el-col>
@@ -61,6 +63,7 @@
          icon="el-icon-delete"
          size="mini"
          :disabled="multiple"
          v-hasPermi="['platform:template:remove']"
          @click="handleDelete"
        >删除</el-button>
      </el-col>
@@ -70,6 +73,7 @@
          plain
          icon="el-icon-download"
          size="mini"
          v-hasPermi="['platform:template:export']"
          @click="handleExport"
        >导出</el-button>
      </el-col>
@@ -94,12 +98,14 @@
            type="text"
            icon="el-icon-edit"
            @click="handleUpdate(scope.row)"
            v-hasPermi="['platform:template:edit']"
          >修改</el-button>
          <el-button
            size="mini"
            type="text"
            icon="el-icon-delete"
            @click="handleDelete(scope.row)"
            v-hasPermi="['platform:template:remove']"
          >删除</el-button>
        </template>
      </el-table-column>
src/views/system/threshold/index.vue
@@ -18,25 +18,40 @@
      <el-table-column type="selection" width="55" align="center"/>
      <el-table-column label="设备类型" align="center" prop="monitorType">
        <template slot-scope="scope">
          <span v-show="scope.row['monitorType'] === '1'">人脸</span>
          <span v-show="scope.row['monitorType'] === '2'">车辆</span>
          <span v-show="scope.row['monitorType'] === '3'">视频</span>
          <span v-show="scope.row['monitorType'] === 'face'">人脸</span>
          <span v-show="scope.row['monitorType'] === 'car'">车辆</span>
          <span v-show="scope.row['monitorType'] === 'video'">视频</span>
        </template>
      </el-table-column>
      <el-table-column label="超时天数" align="center" prop="timeout"/>
      <el-table-column label="工单阈值" align="center" prop="indicator">
        <template slot-scope="scope">
          <div v-for="item in JSON.parse(scope.row.indicator)" :key="item" style="display: flex;flex-direction: row">
            <div style="width: 120px;text-align: right">{{ item.label }}</div>
            <div style="width: 60px;text-align: right">{{ item.value}}</div>
          <div class="table-row" style="display: flex;flex-direction: row">
            <div class="table-row-item">图像质量</div>
            <div class="table-row-item">{{ scope.row.imageQuality }}</div>
          </div>
          <div class="table-row" style="display: flex;flex-direction: row">
            <div class="table-row-item">视频质量</div>
            <div class="table-row-item">{{ scope.row.videoQuality }}</div>
          </div>
          <div class="table-row" style="display: flex;flex-direction: row">
            <div class="table-row-item">标注准确率</div>
            <div class="table-row-item">{{ scope.row.annotationAccuracy }}</div>
          </div>
        </template>
      </el-table-column>
      <el-table-column label="下发阈值" align="center" prop="indicator">
        <template slot-scope="scope">
          <div v-for="item in JSON.parse(scope.row.indicator)" :key="item" style="display: flex;flex-direction: row">
            <div style="width: 120px;text-align: right">{{ item.label }}</div>
            <div style="width: 60px;text-align: right">{{ item.value2}}</div>
          <div class="table-row" style="display: flex;flex-direction: row">
            <div class="table-row-item">图像质量</div>
            <div class="table-row-item">{{ scope.row.imageQualityAuto }}</div>
          </div>
          <div class="table-row" style="display: flex;flex-direction: row">
            <div class="table-row-item">视频质量</div>
            <div class="table-row-item">{{ scope.row.videoQualityAuto }}</div>
          </div>
          <div class="table-row" style="display: flex;flex-direction: row">
            <div class="table-row-item">标注准确率</div>
            <div class="table-row-item">{{ scope.row.annotationAccuracyAuto }}</div>
          </div>
        </template>
      </el-table-column>
@@ -46,8 +61,8 @@
            size="mini"
            type="text"
            icon="el-icon-edit"
            @click="handleUpdate(scope.row)"
            v-hasPermi="['ycl:threshold:edit']"
            @click="updateWho(scope.row)"
          >修改
          </el-button>
<!--          <el-button-->
@@ -70,36 +85,87 @@
      @pagination="getList"
    />
    <!-- 添加或修改运维阈值对话框 -->
    <el-dialog :title="title" :visible.sync="open" width="400px" append-to-body>
      <el-form ref="form" :model="form" :rules="rules" label-width="150px">
    <!-- 人脸阈值 -->
    <el-dialog title="修改人脸工单阈值" :visible.sync="faceOpen" width="400px" append-to-body>
      <el-form ref="faceForm" :model="faceForm" :rules="rules" label-width="150px">
        <el-form-item label="设备类型" prop="monitorType">
          <el-select v-model="form.monitorType" placeholder="请选择设备类型" @change="handleModeNameChange">
            <el-option label="人脸" value="1"/>
            <el-option label="车辆" value="2"/>
            <el-option label="视频" value="3"/>
          <el-select v-model="faceForm.monitorType" placeholder="请选择设备类型" @change="handleModeNameChange">
            <el-option label="人脸" value="face"/>
            <el-option label="车辆" value="car"/>
            <el-option label="视频" value="video"/>
          </el-select>
        </el-form-item>
        <el-form-item label="超时天数" prop="timeout" label-width="150px">
          <el-input type="number" min="0" max="1000" v-model="form.timeout" placeholder="请输入超时天数"/>
        </el-form-item>
        <el-form-item :label="indicator.label" prop="indexOneValue" v-for="indicator in indicators" label-width="150px">
          <el-input class="el-input-half-width" v-model="indicator.value" placeholder="工单阈值"/>
          <el-input style="float: right;" class="el-input-half-width" v-model="indicator.value2" placeholder="下发阈值"/>
      </el-form>
      <div slot="footer" class="dialog-footer">
        <el-button type="primary" @click="editFace">确 定</el-button>
        <el-button @click="cancelFace">取 消</el-button>
      </div>
    </el-dialog>
    <!-- 车辆阈值 -->
    <el-dialog title="修改车辆工单阈值" :visible.sync="faceOpen" width="400px" append-to-body>
      <el-form ref="faceForm" :model="faceForm" :rules="rules" label-width="150px">
        <el-form-item label="设备类型" prop="monitorType">
          <el-select v-model="faceForm.monitorType" placeholder="请选择设备类型" @change="handleModeNameChange">
            <el-option label="人脸" value="face"/>
            <el-option label="车辆" value="car"/>
            <el-option label="视频" value="video"/>
          </el-select>
        </el-form-item>
      </el-form>
      <div slot="footer" class="dialog-footer">
        <el-button type="primary" @click="submitForm">确 定</el-button>
        <el-button @click="cancel">取 消</el-button>
        <el-button type="primary" @click="editCar">确 定</el-button>
        <el-button @click="cancelCar">取 消</el-button>
      </div>
    </el-dialog>
    <!-- 视频阈值 -->
    <el-dialog title="修改视频工单阈值" :visible.sync="videoOpen" width="500px" append-to-body>
      <el-form ref="videoForm" :model="videoForm" :rules="rules" label-width="100px">
        <el-form-item label="设备类型" prop="monitorType">
          <el-select v-model="videoForm.monitorType" disabled placeholder="请选择设备类型" @change="handleModeNameChange">
            <el-option label="人脸" value="face"/>
            <el-option label="车辆" value="car"/>
            <el-option label="视频" value="video"/>
          </el-select>
        </el-form-item>
        <el-form-item label="图像质量" prop="imageQuality" label-width="100px">
          <el-select v-model="videoForm.imageQuality" placeholder="工单阈值" @change="handleModeNameChange">
            <el-option :key="dict.value" :label="dict.value" v-for="dict in dict.type.image_qualify"/>
          </el-select>
          <el-select v-model="videoForm.imageQualityAuto" placeholder="下发阈值" @change="handleModeNameChange">
            <el-option :key="dict.value" :label="dict.value" v-for="dict in dict.type.image_qualify"/>
          </el-select>
        </el-form-item>
        <el-form-item label="视频质量" prop="videoQuality" label-width="100px">
          <el-select v-model="videoForm.videoQuality" placeholder="工单阈值" @change="handleModeNameChange">
            <el-option :key="dict.value" :label="dict.value" v-for="dict in dict.type.video_qualify"/>
          </el-select>
          <el-select v-model="videoForm.videoQualityAuto" placeholder="下发阈值" @change="handleModeNameChange">
            <el-option :key="dict.value" :label="dict.value" v-for="dict in dict.type.video_qualify"/>
          </el-select>
        </el-form-item>
        <el-form-item label="标注准确率" prop="annotationAccuracy" label-width="100px">
          <el-input v-model="videoForm.annotationAccuracy" type="number" size="small" placeholder="工单阈值"></el-input>
          <el-input v-model="videoForm.annotationAccuracy" type="number" size="small" placeholder="下发阈值"></el-input>
        </el-form-item>
      </el-form>
      <div slot="footer" class="dialog-footer">
        <el-button type="primary" @click="editVideo">确 定</el-button>
        <el-button @click="cancelVideo">取 消</el-button>
      </div>
    </el-dialog>
  </div>
</template>
<script>
import { listThreshold, getThreshold, delThreshold, addThreshold, updateThreshold } from '@/api/platform/threshold'
import { listThreshold, getThreshold, editVideo } from '@/api/platform/threshold'
import { editCar, editFace, getCar, getFace, getVideo } from '../../../api/platform/threshold'
export default {
  dicts: ['image_qualify', 'video_qualify'],
  name: 'Threshold',
  data() {
    return {
@@ -121,7 +187,8 @@
      // 弹出层标题
      title: '',
      // 是否显示弹出层
      open: false,
      videoOpen: false,
      faceOpen: false,
      // 查询参数
      queryParams: {
        pageNum: 1,
@@ -129,15 +196,23 @@
        monitorType: null
      },
      // 表单参数
      form: {},
      videoForm: {},
      carForm: {},
      faceForm: {},
      // 表单校验
      rules: {
        monitorType: [
          { required: true, message: '设备类型:1人脸 2车辆 3视频不能为空', trigger: 'change' }
        ],
        timeout: [
          { required: true, message: '超时天数不能为空', trigger: 'blur' }
        ]
        videoQuality: [
          { required: true, message: '请选择视频质量阈值', trigger: 'change' }
        ],
        imageQuality: [
          { required: true, message: '请选择图像质量阈值', trigger: 'change' }
        ],
        annotationAccuracy: [
          { required: true, message: '请填写标注准确率阈值', trigger: 'blur' }
        ],
      }
    }
  },
@@ -145,6 +220,48 @@
    this.getList()
  },
  methods: {
    editVideo() {
      this.$refs['videoForm'].validate(validate => {
        if (validate) {
          editVideo(this.videoForm).then(res => {
            if (res.code === 200) {
              this.$message.success("修改成功")
              this.getList();
            } else {
              this.$message.success("修改失败")
            }
          })
        }
      })
    },
    editFace() {
      this.$refs['faceForm'].validate(validate => {
        if (validate) {
          editFace(this.videoForm).then(res => {
            if (res.code === 200) {
              this.$message.success("修改成功")
              this.getList();
            } else {
              this.$message.success("修改失败")
            }
          })
        }
      })
    },
    editCar() {
      this.$refs['carForm'].validate(validate => {
        if (validate) {
          editCar(this.carForm).then(res => {
            if (res.code === 200) {
              this.$message.success("修改成功")
              this.getList();
            } else {
              this.$message.success("修改失败")
            }
          })
        }
      })
    },
    /** 查询运维阈值列表 */
    getList() {
      this.loading = true
@@ -155,9 +272,17 @@
      })
    },
    // 取消按钮
    cancel() {
      this.open = false
      this.reset()
    cancelFace() {
      this.faceOpen = false
      this.resetFace()
    },
    cancelCar() {
      this.carOpen = false
      this.resetCar()
    },
    cancelVideo() {
      this.videoOpen = false
      this.resetVideo()
    },
    // 表单重置
    reset() {
@@ -171,6 +296,45 @@
        deleted: null
      }
      this.resetForm('form')
    },
    resetVideo() {
      this.videoForm = {
        id: null,
        monitorType: "",
        imageQuality: "",
        imageQualityAuto: "",
        videoQuality: "",
        videoQualityAuto: "",
        annotationAccuracy: null,
        annotationAccuracyAuto: null,
      }
      this.resetForm('videoForm')
    },
    resetFace() {
      this.faceForm = {
        id: null,
        monitorType: "",
        imageQuality: "",
        imageQualityAuto: "",
        videoQuality: "",
        videoQualityAuto: "",
        annotationAccuracy: null,
        annotationAccuracyAuto: null,
      }
      this.resetForm('faceForm')
    },
    resetCar() {
      this.carForm = {
        id: null,
        monitorType: "",
        imageQuality: "",
        imageQualityAuto: "",
        videoQuality: "",
        videoQualityAuto: "",
        annotationAccuracy: null,
        annotationAccuracyAuto: null,
      }
      this.resetForm('carForm')
    },
    /** 搜索按钮操作 */
    handleQuery() {
@@ -196,13 +360,31 @@
      this.handleModeNameChange()
    },
    /** 修改按钮操作 */
    updateWho(row) {
      if (row.monitorType === 'face') {
        getFace(row.id).then(response => {
          this.faceForm = response.data
          this.faceOpen = true;
        })
      } else if (row.monitorType === 'car') {
        getCar(row.id).then(response => {
          this.carForm = response.data
          this.carOpen = true;
        })
      } else {
        getVideo(row.id).then(response => {
          this.videoForm = response.data
          this.videoOpen = true;
        })
      }
    },
    handleUpdate(row) {
      this.reset()
      const id = row.id || this.ids
      getThreshold(id).then(response => {
        this.form = response.data
        this.indicators = JSON.parse(this.form.indicator)
        this.open = true
        this.videoOpen = true
        this.title = '修改运维阈值'
      })
    },
@@ -276,7 +458,7 @@
            value: null
          }
        ]
      } else if (this.form.monitorType === '2') {
      } else if (this.form.monitorType === 'car') {
        this.indicators = [
          {
            label: '过车数据量',
@@ -311,24 +493,20 @@
            value: null
          }
        ]
      } else if (this.form.monitorType === '3') {
      } else if (this.form.monitorType === 'video') {
        this.indicators = [
          {
            label: '采集设备总数',
            label: '录像质量',
            value: null
          },
          {
            label: '监测正常设备数',
            label: '标注准确率',
            value: null
          },
          {
            label: '编码异常设备数',
            label: '图像质量',
            value: null
          },
          {
            label: '经纬度异常设备数',
            value: null
          }
        ]
      }
    }
@@ -339,4 +517,12 @@
.el-input-half-width {
  width: calc(50% - 6px); /* 减去一些间隔 */
}
.table-row {
  display: flex;
  flex-direction: row;
}
.table-row-item {
  width: 120px;
  text-align: center;
}
</style>
src/views/system/unit/index.vue
@@ -44,6 +44,7 @@
          icon="el-icon-plus"
          size="mini"
          @click="handleAdd"
          v-hasPermi="['unit:add']"
        >新增</el-button>
      </el-col>
      <el-col :span="1.5">
@@ -54,6 +55,7 @@
          size="mini"
          :disabled="single"
          @click="handleUpdate"
          v-hasPermi="['unit:edit']"
        >修改</el-button>
      </el-col>
      <el-col :span="1.5">
@@ -64,6 +66,7 @@
          size="mini"
          :disabled="multiple"
          @click="handleDelete"
          v-hasPermi="['unit:remove']"
        >删除</el-button>
      </el-col>
      <el-col :span="1.5">
@@ -73,6 +76,7 @@
          icon="el-icon-download"
          size="mini"
          @click="handleExport"
          v-hasPermi="['unit:export']"
        >导出</el-button>
      </el-col>
      <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
@@ -94,12 +98,14 @@
            type="text"
            icon="el-icon-edit"
            @click="handleUpdate(scope.row)"
            v-hasPermi="['unit:edit']"
          >修改</el-button>
          <el-button
            size="mini"
            type="text"
            icon="el-icon-delete"
            @click="handleDelete(scope.row)"
            v-hasPermi="['unit:remove']"
          >删除</el-button>
        </template>
      </el-table-column>
src/views/system/unit/people/index.vue
@@ -43,6 +43,7 @@
          icon="el-icon-plus"
          size="mini"
          @click="handleAdd"
          v-hasPermi="['unit:people:add']"
        >新增</el-button>
      </el-col>
      <el-col :span="1.5">
@@ -53,6 +54,7 @@
          size="mini"
          :disabled="single"
          @click="handleUpdate"
          v-hasPermi="['unit:people:edit']"
        >修改</el-button>
      </el-col>
      <el-col :span="1.5">
@@ -63,6 +65,7 @@
          size="mini"
          :disabled="multiple"
          @click="handleDelete"
          v-hasPermi="['unit:people:remove']"
        >删除</el-button>
      </el-col>
      <el-col :span="1.5">
@@ -72,6 +75,7 @@
          icon="el-icon-download"
          size="mini"
          @click="handleExport"
          v-hasPermi="['unit:people:export']"
        >导出</el-button>
      </el-col>
      <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
@@ -92,12 +96,14 @@
            type="text"
            icon="el-icon-edit"
            @click="handleUpdate(scope.row)"
            v-hasPermi="['unit:people:edit']"
          >修改</el-button>
          <el-button
            size="mini"
            type="text"
            icon="el-icon-delete"
            @click="handleDelete(scope.row)"
            v-hasPermi="['unit:people:remove']"
          >删除</el-button>
        </template>
      </el-table-column>
src/views/system/video/index.vue
@@ -114,6 +114,7 @@
          icon="el-icon-download"
          size="mini"
          @click="handleExport"
          v-hasPermi="['system:monitor:export']"
        >导出</el-button>
      </el-col>
      <right-toolbar :showSearch.sync="showSearch" @queryTable="getList" :columns="columns"></right-toolbar>
src/views/system/work-order/distribute/index.vue
@@ -4,48 +4,12 @@
      <el-col :span="24" style="position: relative">
        <el-menu :default-active="activeIndex" class="el-menu" mode="horizontal" @select="handleSelect">
          <el-menu-item index="0" @click="changeUnit(null, '全部')">
            全部单位(430)
            全部单位({{ totalWorkOrders }})
          </el-menu-item>
          <el-menu-item index="1">
            自流井运维单位(70)
          <el-menu-item :index="index + 1" v-for="(item, index) in unitList" :key="index" @click="changeUnit(item.id, item.value)">
            {{ item.unitName }}({{ item.workOrderCount }})
          </el-menu-item>
          <el-menu-item index="2">
            大安运维(50)
          </el-menu-item>
          <el-menu-item index="3">富顺运维单位(70)</el-menu-item>
          <el-menu-item index="4">高新运维单位(15)</el-menu-item>
          <el-menu-item index="5">荣县运维单位(90)</el-menu-item>
          <el-menu-item index="6">贡井运维单位(45)</el-menu-item>
          <el-menu-item index="7">沿滩运维单位(70)</el-menu-item>
        </el-menu>
        <el-popover
          placement="right"
          width="400"
          trigger="click">
          <el-form :model="settingForm" :rules="settingRules" ref="settingForm" label-width="120px">
            <el-form-item label="录像质量" prop="videoQuality">
              <el-select style="width: 100%" v-model="settingForm.videoQuality" placeholder="最低录像质量">
                <el-option label="全录像" value="全录像"/>
                <el-option label="部分录像" value="部分录像"/>
                <el-option label="无录像" value="无录像"/>
              </el-select>
            </el-form-item>
            <el-form-item label="点位离线时长" prop="outLine">
              <el-input type="number" v-model="settingForm.outLine" placeholder="不能超过多久">
                <template slot="append">分钟</template>
              </el-input>
            </el-form-item>
            <el-form-item label="视频标注准确率" prop="videoLabel">
              <el-input type="number" v-model="settingForm.videoLabel" placeholder="最低准确率">
                <template slot="append">%</template>
              </el-input>
            </el-form-item>
            <el-form-item>
              <el-button type="primary" @click="submitSetting">保存</el-button>
            </el-form-item>
          </el-form>
          <el-button title="自动生成工单设置" slot="reference" class="setting" style="" type="success" icon="el-icon-s-tools" circle></el-button>
        </el-popover>
      </el-col>
    </el-row>
@@ -67,19 +31,20 @@
          <span style="font-weight: bold;font-size: 16px">快捷下发</span>
          <el-form ref="fastDistributeForm" :model="fastDistributeForm" :rules="fastDistributeRules" label-width="80px">
            <el-form-item label="快捷方式" prop="fastWay">
              <el-radio v-model="fastDistributeForm.fastWay" label="0">最近30分钟</el-radio>
              <el-radio v-model="fastDistributeForm.fastWay" label="01">最近1小时</el-radio>
              <el-radio v-model="fastDistributeForm.fastWay" label="10">最近2小时</el-radio>
              <el-radio v-model="fastDistributeForm.fastWay" label="11">最近1天</el-radio>
              <el-radio v-model="fastDistributeForm.fastWay" label="101">自定义</el-radio>
              <el-radio v-model="fastDistributeForm.fastWay" label="LAST_HALF_HOUR">最近30分钟</el-radio>
              <el-radio v-model="fastDistributeForm.fastWay" label="LAST_HOUR">最近1小时</el-radio>
              <el-radio v-model="fastDistributeForm.fastWay" label="LAST_TWO_HOUR">最近2小时</el-radio>
              <el-radio v-model="fastDistributeForm.fastWay" label="LAST_DAY">最近1天</el-radio>
              <el-radio v-model="fastDistributeForm.fastWay" label="CUSTOM">自定义</el-radio>
            </el-form-item>
            <el-form-item v-if="fastDistributeForm.fastWay === '101'" label="时间范围">
            <el-form-item v-if="fastDistributeForm.fastWay === 'CUSTOM'" label="时间范围">
              <el-date-picker
                style="width: 100%"
                v-model="fastTimeRange"
                type="datetimerange"
                range-separator="至"
                start-placeholder="开始日期"
                value-format="yyyy-MM-dd HH:mm:ss"
                end-placeholder="结束日期">
              </el-date-picker>
            </el-form-item>
@@ -134,7 +99,7 @@
            <el-option
              v-for="item in unitList"
              :key="item.id"
              :label="item.value"
              :label="item.unitName"
              :value="item.id">
            </el-option>
          </el-select>
@@ -147,6 +112,7 @@
            reserve-keyword
            placeholder="请选择来源"
            :remote-method="remoteGetPoints"
            @change="setPointId"
            :loading="selectLoading">
            <el-option
              v-for="item in pointList"
@@ -174,13 +140,18 @@
</template>
<script>
import {distributeWorkOrder, fastDistribute, addWorkOrder, updateWorkOrder} from '@/api/platform/work-order'
import {unitSelect} from "@/api/platform/unit";
import {distributeWorkOrder, fastDistribute, addWorkOrder, updateWorkOrder, selectedIdsDistribute} from '@/api/platform/work-order'
import {workList} from "@/api/platform/unit";
import { pointSelectData } from "@/api/platform/point";
export default {
  name: 'index',
  data() {
    return {
      selectedIdsDistributeForm: {
        ids: [],
        unitId: null
      },
      unitList: [],
      settingForm: {
        // 离线
        outLine: null,
@@ -219,13 +190,13 @@
      // 下发表单验证
      fastDistributeRules: {
        fastWay: [
          { required: true, message: "请选择快速分发方式", trigger: "change" }
          { required: true, message: "请选择快速分发方式", trigger: "blur" }
        ],
        fastNumLimit: [
          { required: true, message: "请输入快速分发数量限制", trigger: "change" }
          { required: true, message: "请输入快速分发数量限制", trigger: "blur" }
        ],
        errorType: [
          { required: true, message: "请故障类型", trigger: "change" }
          { required: true, message: "请故障类型", trigger: "blur" }
        ],
      },
      queryParams: {
@@ -270,9 +241,17 @@
  },
  mounted() {
    this.page();
    this.selectUnit();
  },
  computed: {
    totalWorkOrders() {
      return this.unitList.reduce((total, item) => total + item.workOrderCount, 0);
    },
  },
  methods: {
    setPointId(selectedValue) {
      const selectedItem = this.pointList.find(item => item.value === selectedValue);
      this.form.pointId = selectedItem.id
    },
    submitSetting() {
      this.$refs['settingForm'].validate((valid) => {
        if (valid) {
@@ -285,6 +264,7 @@
    },
    page() {
      this.loading = true;
      this.selectUnit();
      distributeWorkOrder(this.queryParams).then(res => {
        this.workOrderList = res.data;
        this.total = res.total;
@@ -303,41 +283,44 @@
      this.fastDistributeForm.end = null
      this.fastDistributeForm.fastNumLimit = null
      this.fastTimeRange = []
      this.fastDistributeForm.errorType = null
    },
    // 全部下发
    allDistribute() {
      this.fastDistribute();
      this.selectedIdsDistributeForm.unitId = this.unitId
      selectedIdsDistribute(this.selectedIdsDistributeForm).then(res => {
        this.$message.success("工单下发成功")
        this.page();
      })
    },
    // 快速下发
    fastDistribute() {
      // this.$refs['fastDistributeForm'].validate((valid) => {
      //   if (valid) {
          // 如果是自定义方式,那么时间段必填
          if (this.fastDistributeForm.fastWay === '101' && !this.fastTimeRange.length > 0) {
            this.$message.warning("请选择时间范围")
            return false
          }
          if (this.fastTimeRange.length > 0) {
            this.fastDistributeForm.start = this.fastTimeRange[0]
            this.fastDistributeForm.end = this.fastTimeRange[1]
          }
      this.$refs['fastDistributeForm'].validate((valid) => {
        if (valid) {
          this.fastDistributeForm.start = this.fastTimeRange[0]
          this.fastDistributeForm.end = this.fastTimeRange[1]
          this.fastDistributeForm.unitId = this.unitId
          fastDistribute(this.fastDistributeForm).then(res => {
            // this.clearFastDistributeForm();
            res.code == 200 ? this.$message.success(res.msg) : this.$message.warning(res.msg)
            this.clearFastDistributeForm();
            this.page();
          })
        // } else {
        //   return false
        // }
      // })
        }
      })
    },
    // 选中工单下发
    selectedDistribute() {
      if (this.multipleSelection.length < 1) {
        this.$message.warning("请先选择要下发的工单")
        return
      }
      this.fastDistribute();
      }
      this.selectedIdsDistributeForm.unitId = this.unitId;
      this.selectedIdsDistributeForm.ids = this.multipleSelection;
      selectedIdsDistribute(this.selectedIdsDistributeForm).then(res => {
        this.$message.success("工单下发成功")
        this.selectedIdsDistributeForm.ids = [];
        this.page();
      })
    },
    handleSelect(key, keyPath) {
      console.log(key, keyPath);
@@ -387,7 +370,7 @@
    },
    // 运维公司下拉数据
    selectUnit() {
      unitSelect().then(res => {
      workList().then(res => {
        this.unitList = res.data;
      })
    },
src/views/system/work-order/index.vue
@@ -16,6 +16,14 @@
          <el-option label="设备遗失" value="设备遗失"></el-option>
        </el-select>
      </el-form-item>
      <el-form-item label="审核状态" prop="status">
        <el-select v-model="queryParams.status" placeholder="审核状态" clearable @clear="handleQuery">
          <el-option label="已下发" value="DISTRIBUTED"></el-option>
          <el-option label="运维已处理" value="YW_HANDLE"></el-option>
          <el-option label="审核通过" value="AUDITING_SUCCESS"></el-option>
          <el-option label="审核不通过" value="AUDITING_FAIL"></el-option>
        </el-select>
      </el-form-item>
      <el-form-item label="运维处理时间">
        <el-date-picker
          clearable
@@ -76,6 +84,7 @@
                size="mini"
                type="text"
                @click="handleReport(item)"
                v-hasPermi="['system:report:add']"
              >事后报备</el-button>
            </el-row>
          </div>
@@ -94,8 +103,8 @@
    <!-- 事后报备 -->
    <el-dialog title="事后报备" :visible.sync="reportOpen" width="600px" append-to-body>
      <el-form ref="reportForm" :model="reportForm" :rules="reportFormRules" label-width="80px">
        <el-form-item label="点位" prop="pointId">
          <el-input v-model="reportForm.pointId" disabled />
        <el-form-item label="点位" prop="source">
          <el-input v-model="reportForm.source" disabled />
        </el-form-item>
        <el-form-item label="报备类型">
          <el-input v-model="reportForm.reportType" disabled />
@@ -131,15 +140,15 @@
        <el-form-item label="运维人员" prop="ywPeopleName">
          <el-input v-model="auditingForm.ywPeopleName" disabled />
        </el-form-item>
        <el-form-item label="现场情况">
        <el-form-item label="现场情况" v-if="auditingForm.condition">
          <el-input type="textarea" v-html="auditingForm.condition" disabled />
        </el-form-item>
        <el-form-item label="佐证材料">
        <el-form-item label="佐证材料" v-if="auditingForm.proofMaterials">
          <el-link v-for="item in auditingForm.proofMaterials != null ? auditingForm.proofMaterials.split(',') : auditingForm.proofMaterials" :underline="false" :key="item" @click="handleDownload(item)">{{ item.substring(item.lastIndexOf("/") + 1) }}</el-link>
        </el-form-item>
        <el-form-item label="审核结果" prop="auditingResult">
          <el-radio v-model="auditingForm.auditingResult" label="pass">通过</el-radio>
          <el-radio v-model="auditingForm.auditingResult" label="return">驳回</el-radio>
          <el-radio v-model="auditingForm.auditingResult" label="AUDITING_SUCCESS">通过</el-radio>
          <el-radio v-model="auditingForm.auditingResult" label="AUDITING_FAIL">驳回</el-radio>
        </el-form-item>
        <el-form-item label="审核备注" prop="auditingRemark">
          <el-input v-model="auditingForm.auditingRemark" type="textarea" maxlength="30" show-word-limit/>
@@ -152,7 +161,7 @@
    </el-dialog>
    <el-dialog title="运维情况记录" :visible.sync="ywConditionOpen" width="500px" append-to-body>
      <el-form ref="ywConditionForm" :model="ywConditionForm" :rules="ywConditionRules" label-width="80px">
      <el-form ref="form" :model="form" :rules="ywConditionRules" label-width="80px">
        <el-form-item label="工单号" prop="workOrderNo">
          <el-input v-model="ywConditionForm.workOrderNo" disabled  />
        </el-form-item>
@@ -160,13 +169,13 @@
          <el-input v-model="ywConditionForm.unitName" disabled  />
        </el-form-item>
        <el-form-item label="运维人员" prop="ywPeopleName">
          <el-input v-model="ywConditionForm.ywPeopleName" />
          <el-input v-model="ywConditionForm.ywPeopleName" disabled />
        </el-form-item>
        <el-form-item label="现场情况" prop="reportContent">
          <editor v-model="form.condition" :min-height="192"/>
        <el-form-item label="现场情况" prop="ywCondition">
          <editor v-model="form.ywCondition" :min-height="192"/>
        </el-form-item>
        <el-form-item label="佐证材料" prop="reportMaterials">
          <file-upload v-model="form.proofMaterials"/>
        <el-form-item label="佐证材料" prop="ywProofMaterials">
          <file-upload v-model="form.ywProofMaterials"/>
        </el-form-item>
      </el-form>
      <div slot="footer" class="dialog-footer">
@@ -179,7 +188,7 @@
    <!-- 过程图 -->
    <el-dialog title="过程图" :visible.sync="flowOpen" width="1200px" :close-on-click-modal="false" append-to-body>
      <div>
        <el-steps :active="2" :space="500">
        <el-steps :active="ywAuditingList.length != 0 ? 3 : ywHandleList.length != 0 ? 2 : 1" :space="500">
          <el-step title="产生工单">
            <template slot="description">
              <div>
@@ -195,88 +204,54 @@
                <div class="flow-item-x">
                  创建时间:{{flowForm.createTime}}
                </div>
                <div class="flow-item-x">
                  处理期限:<el-tag type="danger" size="small">12小时</el-tag>
                <div class="flow-item-x" v-if="flowForm.processingPeriod">
                  处理期限:<el-tag type="danger" size="small">{{ flowForm.processingPeriod }}</el-tag>
                </div>
              </div>
            </template>
          </el-step>
          <el-step title="运维处理">
            <template slot="description">
              <div class="y-item">
              <div class="y-item" v-for="(item, index) in ywHandleList">
                <div class="y-item-1">
                  <div class="flow-item-x">
                    运维状态:<el-tag type="danger" size="small">结果上报</el-tag>
                    现场情况:
                    <el-tooltip class="item" effect="dark" :content="item.ywCondition" placement="top">
                      <el-tag size="small" class="line-limit-length">{{ item.ywCondition }}</el-tag>
                    </el-tooltip>
                  </div>
                  <div class="flow-item-x">
                    运维人员:<el-tag type="danger" size="small">{{flowForm.ywPeopleName}}</el-tag>
                  </div>
                  <!-- <div class="flow-item-x">
                    <el-tag size="small" v-for="file in item.ywProofMaterials != null ? item.ywProofMaterials.split(',') : item.ywProofMaterials" :key="file" @click="handleDownload(file)">{{ file.substring(file.lastIndexOf("/") + 1) }}</el-tag>
                  </div> -->
                </div>
                <div class="y-item-2">
                  <div >
                    {{flowForm.ywHandleTime}}
                    {{ parseTime(item.createTime) }}
                  </div>
                  <div >
                    第几次:1
                    第几次:{{ index + 1 }}
                  </div>
                </div>
              </div>
              <div class="y-item">
                <div class="y-item-1">
                  <div class="flow-item-x">
                    运维状态:<el-tag type="danger" size="small">结果上报</el-tag>
                  </div>
                  <div class="flow-item-x">
                    运维人员:<el-tag type="danger" size="small">{{flowForm.ywPeopleName}}</el-tag>
                  </div>
                </div>
                <div class="y-item-2">
                  <div >
                    {{flowForm.ywHandleTime}}
                  </div>
                  <div >
                    第几次:2
                  </div>
                </div>
              </div>
            </template>
          </el-step>
          <el-step title="结果审核">
            <template slot="description">
              <div class="y-item">
              <div class="y-item" v-for="(item, index) in ywAuditingList">
                <div class="y-item-1">
                  <div class="flow-item-x">
                    审核结果:<el-tag type="danger" size="small">驳回</el-tag>
                    审核结果:<el-tag :type="item.result == '审核通过' ? 'success' : 'danger'" size="small">{{ item.result }}</el-tag>
                  </div>
                  <div class="flow-item-x">
                    审核说明:<el-tag type="danger" size="small">未成功处理</el-tag>
                    审核说明:<el-tag size="small" v-if="item.remark">{{ item.remark }}</el-tag>
                  </div>
                </div>
                <div class="y-item-2">
                  <div >
                    {{flowForm.ywHandleTime}}
                    {{ parseTime(item.createTime) }}
                  </div>
                  <div >
                    第几次:1
                  </div>
                </div>
              </div>
              <div class="y-item">
                <div class="y-item-1">
                  <div class="flow-item-x">
                    审核状态:<el-tag type="info" size="small">审核中</el-tag>
                  </div>
                  <div class="flow-item-x">
                    审核说明:
                  </div>
                </div>
                <div class="y-item-2">
                  <div >
                    {{flowForm.ywHandleTime}}
                  </div>
                  <div >
                    第几次:2
                    第几次:{{ index + 1 }}
                  </div>
                </div>
              </div>
@@ -308,13 +283,16 @@
}
</style>
<script>
import { listWorkOrder, getWorkOrder, delWorkOrder, addWorkOrder, updateWorkOrder,auditing, ywCondition } from "@/api/platform/work-order";
import { listWorkOrder, getWorkOrder, delWorkOrder, addWorkOrder, updateWorkOrder,auditing, ywCondition, getYwCondition, getYwConditionList, getYwAuditingList } from "@/api/platform/work-order";
import { addReport } from "@/api/platform/report";
export default {
  name: "Work-order",
  components: {
  },
  data() {
    return {
      ywHandleList: [],
      ywAuditingList: [],
      reportFormRules: {
        reportContent: [
          { required: true, message: "报备内容不能为空", trigger: "blur" }
@@ -326,7 +304,7 @@
      reportOpen: false,
      reportForm: {
        reportType: "事后报备",
        pointId: "琼于镇3街1号摄像头",
        pointId: "",
        errorType: "",
        reportContent: "",
        reportMaterials: ""
@@ -366,6 +344,7 @@
        ywHandleTime: null,
        ywResult: null,
        ywCondition: null,
        status: ''
      },
      // 表单参数
      form: {},
@@ -423,9 +402,9 @@
    submitReportForm() {
      this.$refs["reportForm"].validate(valid => {
        if (valid) {
          addReport(this.form).then(response => {
          addReport(this.reportForm).then(response => {
            this.$modal.msgSuccess("成功提交报备");
            this.open = false;
            this.reportOpen = false;
            this.getList();
          });
        }
@@ -433,20 +412,27 @@
    },
    // 事后报备按钮
    handleReport(row) {
      this.reportForm.pointId = row.source;
      this.reportForm.pointId = row.pointId;
      this.reportForm.source = row.source;
      this.reportOpen = true;
    },
    // 审核按钮
    handleAuditing(row) {
      this.auditingForm = row;
      this.auditingForm.condition = '设备异常遮挡,现场修复完成'
      this.auditingForm.proofMaterials = '30路由配置_20240411101816A008.png'
      this.auditingOpen = true;
      getYwCondition(row.id).then(response => {
        if (response.data) {
          this.auditingForm.condition = response.data.ywCondition;
          this.auditingForm.proofMaterials = response.data.ywProofMaterials;
        }
        this.auditingOpen = true;
      });
    },
    // 运维情况按钮
    handleYwCondition(row) {
      this.form = {};
      this.ywConditionForm = row;
      this.ywConditionOpen = true;
      this.form.id = row.id;
    },
    // 运维结果按钮
    handleYwResult(row) {
@@ -460,8 +446,18 @@
    },
    // 过程图查看
    handleFlow(row) {
      this.flowOpen = true;
      this.flowForm = row;
      getYwAuditingList(row.id).then(response => {
        if (response.data) {
          this.ywAuditingList = response.data;
        }
      });
      getYwConditionList(row.id).then(response => {
        if (response.data) {
          this.ywHandleList = response.data;
        }
        this.flowOpen = true;
      });
    },
    // 提交审核
    submitAuditing() {
@@ -470,6 +466,7 @@
          auditing(this.auditingForm).then(res => {
            this.auditingOpen = false;
            this.$modal.msgSuccess("操作成功");
            this.getList();
          })
        }
      })
@@ -507,11 +504,12 @@
    },
    // 提交运维情况
    submitYwCondition() {
      this.$refs["ywConditionForm"].validate(valid => {
      this.$refs["form"].validate(valid => {
        if (valid) {
          ywCondition(this.ywConditionForm).then(res => {
          ywCondition(this.form).then(res => {
            this.ywConditionOpen = false;
            this.$modal.msgSuccess("操作成功");
            this.getList();
          })
        }
      })
@@ -540,10 +538,9 @@
    getList() {
      this.loading = true;
      this.queryParams.params = {};
      if (null != this.daterangeYwHandleTime && '' != this.daterangeYwHandleTime) {
        this.queryParams["start"] = this.daterangeYwHandleTime[0];
        this.queryParams["end"] = this.daterangeYwHandleTime[1];
      }
      this.queryParams["start"] = this.daterangeYwHandleTime[0];
      this.queryParams["end"] = this.daterangeYwHandleTime[1];
      if (this.queryParams["status"] == "") { this.queryParams["status"] = "DISTRIBUTED"; }
      listWorkOrder(this.queryParams).then(response => {
        this.workOrderList = response.data;
        this.total = response.total;
@@ -673,4 +670,11 @@
  height: 150px;
  padding: 10px;
}
.line-limit-length {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  width: 200px;
}
</style>
vue.config.js
@@ -35,7 +35,8 @@
    proxy: {
      // detail: https://cli.vuejs.org/config/#devserver-proxy
      [process.env.VUE_APP_BASE_API]: {
        target: `http://localhost:8080`,
        // target: `http://localhost:8080`,
        target: `http://192.168.3.87:8080`,
        changeOrigin: true,
        pathRewrite: {
          ['^' + process.env.VUE_APP_BASE_API]: ''