.env.development
@@ -16,3 +16,8 @@ # 航线文件地址 VITE_APP_AIRLINE_URL = https://wrj.shuixiongit.com/minio/cloud-bucket # 图片存放地址 VITE_APP_TERRAIN_URL = https://wrj.shuixiongit.com/aiskyminio/cloud-bucket # 是否在打包时开启压缩,支持 gzip 和 brotli VITE_BUILD_COMPRESS = gzip index.html
@@ -8,7 +8,7 @@ <meta http-equiv="X-UA-Compatible" content="chrome=1" /> <meta name="renderer" content="webkit" /> <meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=0" /> <meta name="apple-mobile-web-app-capable" content="yes" /> <meta name="mobile-web-app-capable" content="yes" /> <meta name="apple-mobile-web-app-status-bar-style" content="black" /> <meta name="format-detection" content="telephone=no" /> <link rel="icon" href="/img/bg/index_logo2.png" /> src/api/algorithm.js
New file @@ -0,0 +1,19 @@ // 算法仓库 import request from '@/axios' // 图片列表 export const getalgorithmList = (data, params) => { return request({ url: `/drone-device-core/aiTmp/list`, method: 'post', data, params }) } //机巢查询 export const selectDeviceList = data => { return request({ url: `/drone-device-core/aiTmp/devices`, method: 'get', data: data, }) } src/api/tickets/ticket.js
@@ -1,30 +1,30 @@ import request from '@/axios'; import request from '@/axios' export const getList = (data) => { return request({ url: '/drone-device-core/jobEvent/eventPage', method: 'post', data, }); }; }) } // 修改创建工单的方法 export const createTicket = (data, file) => { const formData = new FormData(); const formData = new FormData() // 创建 eventDto 对象,不显式设置 file 字段 const eventDto = { ...data // 直接使用传入的 data,不添加 file: null }; } // 添加所有字段到 FormData Object.entries(eventDto).forEach(([key, value]) => { formData.append(key, value); }); formData.append(key, value) }) // 只有当 file 存在时才添加文件 if (file) { formData.append("file", file); formData.append("file", file) } return request({ @@ -34,8 +34,8 @@ headers: { 'Content-Type': 'multipart/form-data' } }); }; }) } // 新增接口:获取工单详细信息 export const getTicketInfo = (id) => { @@ -43,23 +43,23 @@ url: '/drone-device-core/jobEvent/getTicketInfo', method: 'get', params: { id }, // 使用工单 ID 查询 }); }; }) } // 修改接口:处理待审核状态,动态构建 FormData 提交 export const flowEvent = (data, file) => { const formData = new FormData(); const formData = new FormData() // 动态添加非空字段到 FormData Object.entries(data).forEach(([key, value]) => { if (value !== undefined && value !== null) { formData.append(key, value); formData.append(key, value) } }); }) // 如果 file 存在,则添加到 FormData if (file) { formData.append('file', file); formData.append('file', file) } return request({ @@ -69,8 +69,8 @@ headers: { 'Content-Type': 'multipart/form-data', // 设置为表单数据格式 }, }); }; }) } // 新增接口:获取状态统计数据 export const getstatusCount = (params) => { @@ -78,13 +78,29 @@ url: '/drone-device-core/jobEvent/getstatusCount', method: 'get', params, }); }; }) } export const getStepInfo = (eventNum) => { return request({ url: '/drone-device-core/jobEvent/getStepInfo', method: 'get', params: { eventNum } }); }; }) } // 人工复核接口 export const getReviewById = (id) => { return request({ url: `/drone-device-core/jobEvent/reviewById/${id}`, method: 'get', }) } // 下发任务 export const getCreateEventJob = (id) => { return request({ url: `/drone-device-core/wayline/waylineJobInfo/createEventJob/${id}`, method: 'post', }) } src/assets/images/ht-sfbg.png
src/assets/map_images/EndPointicon.png
src/assets/map_images/Startingpointicon.png
src/components/map-container/mapContainer.vue
@@ -24,10 +24,14 @@ import { nextTick, onMounted, onUnmounted } from 'vue' import { read } from 'xlsx' import startPng from '@/assets/map_images/Startingpointicon.png' import endPng from '@/assets/map_images/EndPointicon.png' window.$viewer = null window.$Cesium = null let pointLayer = null let polylineLayer = null let pointHtmlLayer = null const { VITE_APP_BASE } = import.meta.env // import * as Cesium from 'cesium' @@ -82,6 +86,8 @@ window.$viewer?.addLayer(pointLayer) polylineLayer = new DC.VectorLayer('polylineLayer') window.$viewer?.addLayer(polylineLayer) pointHtmlLayer = new DC.HtmlLayer('pointHtmlLayer') window.$viewer?.addLayer(pointHtmlLayer) isViewerReady.value = true } @@ -113,9 +119,7 @@ if (!lng || !lat) return let [newLng, newLat] = DC.CoordTransform.GCJ02ToWGS84(lng, lat) let point = new DC.Point(new DC.Position(newLng, newLat)) let point = new DC.Point(new DC.Position(lng, lat)) pointLayer.addOverlay(point) window.$viewer?.zoomTo(pointLayer) @@ -127,15 +131,14 @@ */ function addPolyline (data) { if (polylineLayer) polylineLayer.clear() if (pointHtmlLayer) pointHtmlLayer.clear() if (data.length === 0) return const positionStr = data.map(item => { const [lng, lat] = item let [newLng, newLat] = DC.CoordTransform.GCJ02ToWGS84(lng, lat) return `${newLng}, ${newLat}` return `${lng}, ${lat}` }).join(';') let polyline = new DC.Polyline(positionStr) @@ -149,6 +152,38 @@ }) polylineLayer.addOverlay(polyline) data.forEach((item, index) => { const [lng, lat] = item let position = new DC.Position(lng, lat) console.log(lng, lat) let billboard = null if (index === 0) { billboard = new DC.Billboard(position, startPng) } if (index === data.length - 1) { billboard = new DC.Billboard(position, endPng) } billboard && (billboard.size = [20, 20]) billboard && (billboard.setStyle({ "pixelOffset": { "x": 0, "y": -8 } })) billboard && polylineLayer.addOverlay(billboard) if (index !== 0 && index !== data.length - 1) { let divIcon = new DC.DivIcon( position, `<div class="point-icon-box">${index}</div>` ) pointHtmlLayer.addOverlay(divIcon) } }) const line = turf.lineString(positionStr.split(';').map(i => i.split(','))) const bbox = turf.bbox(line) const bboxPolygon = turf.bboxPolygon(bbox) src/config/website.js
@@ -32,8 +32,10 @@ menu: true, }, fistPage: { name: '首页', path: '/wel/index', name: '事件工单', // path: '/wel/index', path: '/tickets/ticket', }, //配置菜单的属性 menu: { src/router/page/index.js
@@ -64,9 +64,15 @@ isAuth: false, }, }, // { // path: '/', // name: '主页', // // redirect: '/wel', // redirect:'/tickets/ticket', // }, { path: '/', name: '主页', redirect: '/wel', }, path: '/', name: '主页', redirect: '/tickets/ticket', }, ]; src/router/views/index.js
@@ -27,6 +27,24 @@ }, ], }, // 事件工单 { path: '/tickets', component: () => Store.getters.isMacOs ? import('@/mac/index.vue') : import('@/page/index/index.vue'), redirect: '/tickets/ticket', children: [ { path: 'ticket', name: '事件工单', meta: { i18n: 'dashboard', }, component: () => import(/* webpackChunkName: "views" */ '@/views/tickets/ticket.vue'), }, ], }, { path: '/test', component: Layout, src/styles/common.scss
@@ -13,8 +13,7 @@ .avue-sidebar, .avue-top, .avue-logo, .avue-layout .login-logo, .avue-layout .login-logo, .avue-main { transition: all .3s; } @@ -32,7 +31,9 @@ height: $top_height; display: flex; .avue-menu, .el-menu-item, .el-sub-menu__title { .avue-menu, .el-menu-item, .el-sub-menu__title { height: $top_height; line-height: $top_height; } @@ -57,6 +58,7 @@ } .avue--collapse { .avue-sidebar, .avue-logo { width: $sidebar_collapse; @@ -130,3 +132,23 @@ @import './media.scss'; //滚动条样式 @include scrollBar; // 地图区域html标签样式调整 .div-icon { padding: 0px !important; border: none !important; background: transparent !important; border-radius: 50% !important; .point-icon-box { width: 28px; height: 28px; line-height: 28px; text-align: center; font-size: 16px; color: #fff; background: rgb(255, 186, 0); border-radius: 50%; } } src/utils/coordinateTransformation.js
New file @@ -0,0 +1,103 @@ /* * @Author: GuLiMmo 2820890765@qq.com * @Date: 2024-06-12 13:51:57 * @LastEditors: GuLiMmo 2820890765@qq.com * @LastEditTime: 2024-06-12 13:54:00 * @FilePath: /bigScreen/src/utils/coordinateTransformation.js * @Description: * Copyright (c) 2024 by GuLiMmo, All Rights Reserved. */ const PI = Math.PI; const a = 6378245.0; const ee = 0.00669342162296594323; export function wgs84ToGcj02(lng, lat) { if (out_of_china(lng, lat)) { return [lng, lat]; } else { var dlat = transformlat(lng - 105.0, lat - 35.0); var dlng = transformlng(lng - 105.0, lat - 35.0); var radlat = (lat / 180.0) * PI; var magic = Math.sin(radlat); magic = 1 - ee * magic * magic; var sqrtmagic = Math.sqrt(magic); dlat = (dlat * 180.0) / (((a * (1 - ee)) / (magic * sqrtmagic)) * PI); dlng = (dlng * 180.0) / ((a / sqrtmagic) * Math.cos(radlat) * PI); var mglat = lat + dlat; var mglng = lng + dlng; return [mglng, mglat]; } } export function gcj02ToWgs84(lng, lat) { if (out_of_china(lng, lat)) { return [lng, lat]; } else { var dlat = transformlat(lng - 105.0, lat - 35.0); var dlng = transformlng(lng - 105.0, lat - 35.0); var radlat = (lat / 180.0) * PI; var magic = Math.sin(radlat); magic = 1 - ee * magic * magic; var sqrtmagic = Math.sqrt(magic); dlat = (dlat * 180.0) / (((a * (1 - ee)) / (magic * sqrtmagic)) * PI); dlng = (dlng * 180.0) / ((a / sqrtmagic) * Math.cos(radlat) * PI); var mglat = lat + dlat; var mglng = lng + dlng; return [lng * 2 - mglng, lat * 2 - mglat]; } } function transformlat(lng, lat) { var ret = -100.0 + 2.0 * lng + 3.0 * lat + 0.2 * lat * lat + 0.1 * lng * lat + 0.2 * Math.sqrt(Math.abs(lng)); ret += ((20.0 * Math.sin(6.0 * lng * PI) + 20.0 * Math.sin(2.0 * lng * PI)) * 2.0) / 3.0; ret += ((20.0 * Math.sin(lat * PI) + 40.0 * Math.sin((lat / 3.0) * PI)) * 2.0) / 3.0; ret += ((160.0 * Math.sin((lat / 12.0) * PI) + 320 * Math.sin((lat * PI) / 30.0)) * 2.0) / 3.0; return ret; } function transformlng(lng, lat) { var ret = 300.0 + lng + 2.0 * lat + 0.1 * lng * lng + 0.1 * lng * lat + 0.1 * Math.sqrt(Math.abs(lng)); ret += ((20.0 * Math.sin(6.0 * lng * PI) + 20.0 * Math.sin(2.0 * lng * PI)) * 2.0) / 3.0; ret += ((20.0 * Math.sin(lng * PI) + 40.0 * Math.sin((lng / 3.0) * PI)) * 2.0) / 3.0; ret += ((150.0 * Math.sin((lng / 12.0) * PI) + 300.0 * Math.sin((lng / 30.0) * PI)) * 2.0) / 3.0; return ret; } // 判断是否在国内,不在国内则不做偏移 function out_of_china(lng, lat) { return ( lng < 72.004 || lng > 137.8347 || lat < 0.8293 || lat > 55.8271 || false ); } src/utils/util.js
@@ -1,16 +1,16 @@ import { validatenull } from './validate'; import sha256 from 'crypto-js/sha256'; import Base64 from 'crypto-js/enc-base64'; import { validatenull } from './validate' import sha256 from 'crypto-js/sha256' import Base64 from 'crypto-js/enc-base64' //表单序列化 export const serialize = data => { let list = []; let list = [] Object.keys(data).forEach(ele => { list.push(`${ele}=${data[ele]}`); }); return list.join('&'); }; list.push(`${ele}=${data[ele]}`) }) return list.join('&') } export const getObjType = obj => { var toString = Object.prototype.toString; var toString = Object.prototype.toString var map = { '[object Boolean]': 'boolean', '[object Number]': 'number', @@ -22,136 +22,136 @@ '[object Undefined]': 'undefined', '[object Null]': 'null', '[object Object]': 'object', }; if (obj instanceof Element) { return 'element'; } return map[toString.call(obj)]; }; if (obj instanceof Element) { return 'element' } return map[toString.call(obj)] } /** * 对象深拷贝 */ export const deepClone = data => { var type = getObjType(data); var obj; var type = getObjType(data) var obj if (type === 'array') { obj = []; obj = [] } else if (type === 'object') { obj = {}; obj = {} } else { //不再具有下一层次 return data; return data } if (type === 'array') { for (var i = 0, len = data.length; i < len; i++) { obj.push(deepClone(data[i])); obj.push(deepClone(data[i])) } } else if (type === 'object') { for (var key in data) { obj[key] = deepClone(data[key]); obj[key] = deepClone(data[key]) } } return obj; }; return obj } /** * 设置灰度模式 */ export const toggleGrayMode = status => { if (status) { document.body.className = document.body.className + ' grayMode'; document.body.className = document.body.className + ' grayMode' } else { document.body.className = document.body.className.replace(' grayMode', ''); document.body.className = document.body.className.replace(' grayMode', '') } }; } /** * 设置主题 */ export const setTheme = name => { document.body.className = name; }; document.body.className = name } /** * 加密处理 */ export const encryption = params => { let { data = {}, type, param, key } = params; let result = JSON.parse(JSON.stringify(data)); let { data = {}, type, param, key } = params let result = JSON.parse(JSON.stringify(data)) if (type == 'Base64') { param.forEach(ele => { result[ele] = Base64.stringify(result[ele]); }); result[ele] = Base64.stringify(result[ele]) }) } else if (type == 'Aes') { param.forEach(ele => { result[ele] = sha256(result[ele], key); }); result[ele] = sha256(result[ele], key) }) } return result; }; return result } /** * 浏览器判断是否全屏 */ export const fullscreenToggel = () => { if (fullscreenEnable()) { exitFullScreen(); exitFullScreen() } else { reqFullScreen(); reqFullScreen() } }; } /** * esc监听全屏 */ export const listenfullscreen = callback => { function listen() { callback(); function listen () { callback() } document.addEventListener('fullscreenchange', function () { listen(); }); listen() }) document.addEventListener('mozfullscreenchange', function () { listen(); }); listen() }) document.addEventListener('webkitfullscreenchange', function () { listen(); }); listen() }) document.addEventListener('msfullscreenchange', function () { listen(); }); }; listen() }) } /** * 浏览器判断是否全屏 */ export const fullscreenEnable = () => { var isFullscreen = document.isFullScreen || document.mozIsFullScreen || document.webkitIsFullScreen; return isFullscreen; }; document.isFullScreen || document.mozIsFullScreen || document.webkitIsFullScreen return isFullscreen } /** * 浏览器全屏 */ export const reqFullScreen = () => { if (document.documentElement.requestFullScreen) { document.documentElement.requestFullScreen(); document.documentElement.requestFullScreen() } else if (document.documentElement.webkitRequestFullScreen) { document.documentElement.webkitRequestFullScreen(); document.documentElement.webkitRequestFullScreen() } else if (document.documentElement.mozRequestFullScreen) { document.documentElement.mozRequestFullScreen(); document.documentElement.mozRequestFullScreen() } }; } /** * 浏览器退出全屏 */ export const exitFullScreen = () => { if (document.documentElement.requestFullScreen) { document.exitFullScreen(); document.exitFullScreen() } else if (document.documentElement.webkitRequestFullScreen) { document.webkitCancelFullScreen(); document.webkitCancelFullScreen() } else if (document.documentElement.mozRequestFullScreen) { document.mozCancelFullScreen(); document.mozCancelFullScreen() } }; } /** * 递归寻找子类的父类 */ @@ -161,16 +161,16 @@ if (menu[i].children.length != 0) { for (let j = 0; j < menu[i].children.length; j++) { if (menu[i].children[j].id == id) { return menu[i]; return menu[i] } else { if (menu[i].children[j].children.length != 0) { return findParent(menu[i].children[j].children, id); return findParent(menu[i].children[j].children, id) } } } } } }; } /** * 判断2个对象属性和值是否相等 */ @@ -180,157 +180,157 @@ */ export const loadStyle = url => { const link = document.createElement('link'); link.type = 'text/css'; link.rel = 'stylesheet'; link.href = url; const head = document.getElementsByTagName('head')[0]; head.appendChild(link); }; const link = document.createElement('link') link.type = 'text/css' link.rel = 'stylesheet' link.href = url const head = document.getElementsByTagName('head')[0] head.appendChild(link) } /** * 判断路由是否相等 */ export const diff = (obj1, obj2) => { delete obj1.close; var o1 = obj1 instanceof Object; var o2 = obj2 instanceof Object; delete obj1.close var o1 = obj1 instanceof Object var o2 = obj2 instanceof Object if (!o1 || !o2) { /* 判断不是对象 */ return obj1 === obj2; return obj1 === obj2 } if (Object.keys(obj1).length !== Object.keys(obj2).length) { return false; return false //Object.keys() 返回一个由对象的自身可枚举属性(key值)组成的数组,例如:数组返回下表:let arr = ["a", "b", "c"];console.log(Object.keys(arr))->0,1,2; } for (var attr in obj1) { var t1 = obj1[attr] instanceof Object; var t2 = obj2[attr] instanceof Object; var t1 = obj1[attr] instanceof Object var t2 = obj2[attr] instanceof Object if (t1 && t2) { return diff(obj1[attr], obj2[attr]); return diff(obj1[attr], obj2[attr]) } else if (obj1[attr] !== obj2[attr]) { return false; return false } } return true; }; return true } /** * 根据字典的value显示label */ export const findByvalue = (dic, value) => { let result = ''; if (validatenull(dic)) return value; let result = '' if (validatenull(dic)) return value if (typeof value == 'string' || typeof value == 'number' || typeof value == 'boolean') { let index = 0; index = findArray(dic, value); let index = 0 index = findArray(dic, value) if (index != -1) { result = dic[index].label; result = dic[index].label } else { result = value; result = value } } else if (value instanceof Array) { result = []; let index = 0; result = [] let index = 0 value.forEach(ele => { index = findArray(dic, ele); index = findArray(dic, ele) if (index != -1) { result.push(dic[index].label); result.push(dic[index].label) } else { result.push(value); result.push(value) } }); result = result.toString(); }) result = result.toString() } return result; }; return result } /** * 根据字典的value查找对应的index */ export const findArray = (dic, value) => { for (let i = 0; i < dic.length; i++) { if (dic[i].value == value) { return i; return i } } return -1; }; return -1 } /** * 生成随机len位数字 */ export const randomLenNum = (len, date) => { let random = ''; let random = '' random = Math.ceil(Math.random() * 100000000000000) .toString() .substr(0, len ? len : 4); if (date) random = random + Date.now(); return random; }; .substr(0, len ? len : 4) if (date) random = random + Date.now() return random } /** * 打开小窗口 */ export const openWindow = (url, title, w, h) => { // Fixes dual-screen position Most browsers Firefox const dualScreenLeft = window.screenLeft !== undefined ? window.screenLeft : screen.left; const dualScreenTop = window.screenTop !== undefined ? window.screenTop : screen.top; const dualScreenLeft = window.screenLeft !== undefined ? window.screenLeft : screen.left const dualScreenTop = window.screenTop !== undefined ? window.screenTop : screen.top const width = window.innerWidth ? window.innerWidth : document.documentElement.clientWidth ? document.documentElement.clientWidth : screen.width; ? document.documentElement.clientWidth : screen.width const height = window.innerHeight ? window.innerHeight : document.documentElement.clientHeight ? document.documentElement.clientHeight : screen.height; ? document.documentElement.clientHeight : screen.height const left = width / 2 - w / 2 + dualScreenLeft; const top = height / 2 - h / 2 + dualScreenTop; const left = width / 2 - w / 2 + dualScreenLeft const top = height / 2 - h / 2 + dualScreenTop const newWindow = window.open( url, title, 'toolbar=no, location=no, directories=no, status=no, menubar=no, scrollbars=no, resizable=yes, copyhistory=no, width=' + w + ', height=' + h + ', top=' + top + ', left=' + left ); w + ', height=' + h + ', top=' + top + ', left=' + left ) // Puts focus on the newWindow if (window.focus) { newWindow.focus(); newWindow.focus() } }; } export const getScreen = isCollapse => { if (document.body.clientWidth <= 768) { return !isCollapse; return !isCollapse } else { return isCollapse; return isCollapse } }; } /** * 获取顶部地址栏地址 */ export const getTopUrl = () => { return window.location.href.split('/#/')[0]; }; return window.location.href.split('/#/')[0] } /** * 获取url参数 * @param name 参数名 */ export const getQueryString = name => { let reg = new RegExp('(^|&)' + name + '=([^&]*)(&|$)', 'i'); let r = window.location.search.substr(1).match(reg); if (r != null) return unescape(decodeURI(r[2])); return null; }; let reg = new RegExp('(^|&)' + name + '=([^&]*)(&|$)', 'i') let r = window.location.search.substr(1).match(reg) if (r != null) return unescape(decodeURI(r[2])) return null } /** * 下载文件 @@ -338,29 +338,29 @@ * @param {String} name - 文件名,eg: test.png */ export const downloadFileBlob = (path, name) => { const xhr = new XMLHttpRequest(); xhr.open('get', path); xhr.responseType = 'blob'; xhr.send(); const xhr = new XMLHttpRequest() xhr.open('get', path) xhr.responseType = 'blob' xhr.send() xhr.onload = function () { if (this.status === 200 || this.status === 304) { // 如果是IE10及以上,不支持download属性,采用msSaveOrOpenBlob方法,但是IE10以下也不支持msSaveOrOpenBlob if ('msSaveOrOpenBlob' in navigator) { navigator.msSaveOrOpenBlob(this.response, name); return; navigator.msSaveOrOpenBlob(this.response, name) return } const url = URL.createObjectURL(this.response); const a = document.createElement('a'); a.style.display = 'none'; a.href = url; a.download = name; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); const url = URL.createObjectURL(this.response) const a = document.createElement('a') a.style.display = 'none' a.href = url a.download = name document.body.appendChild(a) a.click() document.body.removeChild(a) URL.revokeObjectURL(url) } }; }; } } /** * 下载文件 @@ -368,46 +368,63 @@ * @param {String} name - 文件名,eg: test.png */ export const downloadFileBase64 = (path, name) => { const xhr = new XMLHttpRequest(); xhr.open('get', path); xhr.responseType = 'blob'; xhr.send(); const xhr = new XMLHttpRequest() xhr.open('get', path) xhr.responseType = 'blob' xhr.send() xhr.onload = function () { if (this.status === 200 || this.status === 304) { const fileReader = new FileReader(); fileReader.readAsDataURL(this.response); const fileReader = new FileReader() fileReader.readAsDataURL(this.response) fileReader.onload = function () { const a = document.createElement('a'); a.style.display = 'none'; a.href = this.result; a.download = name; document.body.appendChild(a); a.click(); document.body.removeChild(a); }; const a = document.createElement('a') a.style.display = 'none' a.href = this.result a.download = name document.body.appendChild(a) a.click() document.body.removeChild(a) } } }; }; } } /** * 下载excel * @param {blob} fileArrayBuffer 文件流 * @param {String} filename 文件名称 */ export const downloadXls = (fileArrayBuffer, filename) => { let data = new Blob([fileArrayBuffer], { type: 'application/vnd.ms-excel,charset=utf-8' }); let data = new Blob([fileArrayBuffer], { type: 'application/vnd.ms-excel,charset=utf-8' }) if (typeof window.chrome !== 'undefined') { // Chrome var link = document.createElement('a'); link.href = window.URL.createObjectURL(data); link.download = filename; link.click(); var link = document.createElement('a') link.href = window.URL.createObjectURL(data) link.download = filename link.click() } else if (typeof window.navigator.msSaveBlob !== 'undefined') { // IE var blob = new Blob([data], { type: 'application/force-download' }); window.navigator.msSaveBlob(blob, filename); var blob = new Blob([data], { type: 'application/force-download' }) window.navigator.msSaveBlob(blob, filename) } else { // Firefox var file = new File([data], filename, { type: 'application/force-download' }); window.open(URL.createObjectURL(file)); var file = new File([data], filename, { type: 'application/force-download' }) window.open(URL.createObjectURL(file)) } }; } export const calculateDefaultRange = () => { const now = new Date() const currentYear = now.getFullYear() const currentMonth = now.getMonth() // 0-11 // 计算上个月 let lastMonthYear = currentYear let lastMonth = currentMonth - 1 if (lastMonth < 0) { lastMonth = 11 lastMonthYear = currentYear - 1 } // 设置默认范围 [上个月, 当前月] return [new Date(lastMonthYear, lastMonth, 1), new Date(currentYear, currentMonth, 1)] } src/views/algorithmRepository/algorithmRepository.vue
New file @@ -0,0 +1,336 @@ <template> <basic-container> <div class="algorithContainer"> <div class="algorithItem" v-if="!showDetail"> <div class="item" v-for="(item, index) in AlgorithmData" :key="index" :class="{ 'active-bg': activeItem === item.dictValue }" @click="jumpDatail(item)" > <img class="imgicon" :src="`${baseUrl}/后台-算法仓库/${item.dictValue}.png`" alt="" /> <div>{{ item.dictValue }}</div> <div :class="!statusSign && item.dictValue === nameSign ? 'stopStatus' : 'normalStatus'" @click.stop="changeStatus(item)" > {{ !statusSign && item.dictValue === nameSign ? '停用状态' : '正常状态' }} </div> </div> </div> <!-- 详情页 --> <div class="algorithItemDetail" v-else> <div class="search"> <div class="searchBox"> <div class="item"> <div class="itemchild">模糊查询:</div> <el-input v-model="params.name" class="filter-item" placeholder="请输入事件名称" clearable ></el-input> </div> <div class="item"> <el-date-picker type="daterange" range-separator="至" start-placeholder="开始日期" end-placeholder="结束日期" value-format="YYYY-MM-DD" v-model="taskData" placeholder="请选择日期" @change="changeselect" /> </div> <div class="item"> <div class="itemchild">机巢查询:</div> <el-select v-model="params.device_name" placeholder="请选择" class="filter-item"> <el-option v-for="item in jcoptions" :key="item" :label="item" :value="item" /> </el-select> </div> </div> <div class="search-btn"> <el-button type="primary" icon="el-icon-back" @click="goback">返回</el-button> <el-button type="primary" icon="el-icon-search" @click="handleSearch">搜索</el-button> <el-button icon="el-icon-delete" @click="handleReset">清空</el-button> </div> </div> <div class="pictureitem" v-if="detailData.length > 0" v-loading="loading" element-loading-text="加载中" > <div class="imgitem" v-for="(item, index) in detailData" :key="index"> <img :src="item.url" alt="" /> <div class="info"> <div class="name">{{ item.name }}</div> <div class="time">{{ item.create_time.slice(5, 16).replace('-', '/', 1) }}</div> </div> </div> </div> <el-empty class="custom-empty" v-else> <template #description> <span class="custom-text">暂无数据</span> </template> </el-empty> <!-- 分页 --> <el-pagination class="pageStyle" background :page-sizes="[10, 20, 30, 50]" v-model:current-page="params.current" v-model:page-size="params.size" layout="total, prev, pager, next,sizes, jumper" :total="total" @size-change="handleSizeChange" @current-change="handleCurrentChange" /> </div> </div> </basic-container> </template> <script setup> defineOptions({ name: 'algorithmRepository', }); import { getDictionaryByCode } from '@/api/system/dictbiz'; import { getalgorithmList, selectDeviceList } from '@/api/algorithm'; import { useRouter } from 'vue-router'; const router = useRouter(); const baseUrl = import.meta.env.VITE_APP_TERRAIN_URL; const showDetail = ref(false); const taskData = ref(''); const jcvalue = ref(''); const jcoptions = ref([]); const total = ref(0); const loading = ref(true); const params = ref({ ai_type_key: '', start_date: null, end_date: null, device_name: '', name: '', current: 1, size: 10, }); // 请求字典字段 let AlgorithmData = ref([]); const detailData = ref([]); const requestDictionary = () => { getDictionaryByCode('SF').then(res => { if (res.code !== 0) { // 处理数据 AlgorithmData.value = res.data.data['SF']; } }); }; const activeItem = ref(null); const jumpDatail = val => { showDetail.value = true; activeItem.value = val.dictValue; params.value.ai_type_key = val.dictKey; getList(); }; const statusSign = ref(true); const nameSign = ref(''); const changeStatus = val => { nameSign.value = val.dictValue; statusSign.value = !statusSign.value; }; // 详情 const getList = () => { getalgorithmList(params.value).then(res => { loading.value = true; detailData.value = res.data.data.records; total.value = res.data.data.total; setTimeout(() => { loading.value = false; }, 1000); }); }; // 机巢查询 const getDeviceList = () => { selectDeviceList().then(res => { jcoptions.value = res.data.data; }); }; // 日期选择 const changeselect = () => { params.value.start_date = taskData.value.length ? `${taskData.value[0]} 00:00:00` : null; params.value.end_date = taskData.value.length ? `${taskData.value[1]} 23:59:59` : null; }; const handleSearch = () => { getList(); }; const handleReset = () => { params.value.start_date = null; params.value.end_date = null; params.value.device_name = ''; params.value.name = ''; getList(); }; const goback = () => { showDetail.value = false; activeItem.value = null; }; // 分页大小改变 const handleSizeChange = val => { params.size = val; getList(); }; // 页码改变 const handleCurrentChange = val => { params.current = val; getList(); }; onMounted(() => { requestDictionary(); getDeviceList(); }); </script> <style scoped lang="scss"> .algorithItem { padding: 20px; display: grid; grid-template-columns: repeat(5, 1fr); gap: 53px; text-align: center; vertical-align: middle; .item { height: 300px; display: flex; flex-direction: column; justify-content: center; align-items: center; background: url('/src/assets/images/ht-sfbg.png') no-repeat center; background-size: 100% 100%; .imgicon { width: 100px; height: 100px; margin-bottom: 47px; } &:hover { background: url('/src/assets/images/ht-sfbg-hover.png') no-repeat center; background-size: 100% 100%; } &.active-bg { background: url('/src/assets/images/ht-sfbg-click.png') no-repeat center; background-size: 100% 100%; } .normalStatus { width: 116px; height: 43px; background: #ebfbee; border-radius: 50px; text-align: center; line-height: 43px; font-weight: 400; font-size: 16px; color: #029d36; margin-top: 23px; cursor: pointer; } .stopStatus { width: 116px; height: 43px; background: #e8e8e8; border-radius: 50px 50px 50px 50px; text-align: center; line-height: 43px; font-weight: 400; font-size: 16px; color: #464747; margin-top: 23px; cursor: pointer; } .normalStatus:hover { background: rgba(6, 217, 87, 0.2); } } } .algorithItemDetail { padding: 20px; .pictureitem { display: grid; grid-template-columns: repeat(5, 1fr); gap: 24px; .imgitem { border-radius: 12px 12px 0 0; overflow: hidden; img { width: 100%; height: 200px; display: block; margin: 0; padding: 0; } .info { display: flex; justify-content: space-between; align-items: center; height: 42px; border-radius: 0 0 12px 12px; overflow: hidden; background: linear-gradient(180deg, #ffffff 0%, #e5edff 100%); border: 1px solid #1c5cff; border-top: none !important; .name { margin-left: 12px; font-weight: 500; font-size: 16px; color: #363636; } .time { margin-right: 12px; font-weight: 500; font-size: 14px; color: #595959; } } } } .search { display: flex; justify-content: space-between; } .searchBox { display: flex; align-items: center; margin-bottom: 27px; .itemchild { white-space: nowrap; margin-right: 5px; font-weight: 400; font-size: 14px; color: #363636; } .item { display: flex; align-items: center; margin-right: 49px; } } .search-btn { display: flex; } .filter-item { width: 218px; } } .pageStyle { margin-top: 32px; display: flex; justify-content: right; } </style> src/views/tickets/orderLog.vue
@@ -11,233 +11,120 @@ <template> <basic-container> <el-tabs v-model="activeTab" @tab-click="handleTabChange"> <el-tab-pane v-for="tab in filteredTabs" :key="tab.name" :label="`${tab.label} (${tab.count})`" :name="tab.name" > <el-tab-pane v-for="tab in filteredTabs" :key="tab.name" :label="`${tab.label} (${tab.count})`" :name="tab.name"> <div class="tab-content"> <!-- 查询条件筛选栏 --> <div class="filter-bar"> <el-input style="width: 250px" v-model="filters.key_word" placeholder="输入工单编号/名称/内容/姓名" class="filter-item" clearable @keyup.enter="handleSearch" /> <div class="search-bar-box"> <div class="search-bar-box-item"> <el-input v-model="filters.key_word" placeholder="输入工单编号/名称/内容/姓名" clearable @keyup.enter="handleSearch" /> </div> <el-select placeholder="请选择所属单位" v-model="filters.create_dept" @change="handleDepartmentChange" class="filter-item" clearable > <el-option v-for="dept in departments" :key="dept.value" :label="dept.label" :value="dept.value" /> </el-select> <div class="search-bar-box-item"> <el-select placeholder="请选择所属单位" v-model="filters.create_dept" @change="handleDepartmentChange" clearable> <el-option v-for="dept in departments" :key="dept.value" :label="dept.label" :value="dept.value" /> </el-select> </div> <el-date-picker v-model="filters.dateRange" type="datetimerange" range-separator="至" start-placeholder="开始日期" end-placeholder="结束日期" class="filter-item" style="width: 100px" :default-value="datePickerDefaultVal" /> <el-select v-model="filters.file_id" placeholder="请选择关联航线" class="filter-item frequency" clearable > <el-option v-for="item in wayLineList" :key="item.wayline_id" :label="item.name" :value="item.wayline_id" /> </el-select> <div class="search-bar-box-item"> <el-date-picker v-model="filters.dateRange" type="daterange" range-separator="至" start-placeholder="开始日期" end-placeholder="结束日期" :default-value="datePickerDefaultVal" /> </div> <el-select v-model="filters.ai_types" placeholder="关联算法" class="filter-item" clearable > <el-option v-for="item in ai_types" :key="item.dictKey" :label="item.dictValue" :value="item.dictKey" /> </el-select> <div class="search-bar-box-item"> <el-select v-model="filters.file_id" placeholder="请选择关联航线" clearable> <el-option v-for="item in wayLineList" :key="item.wayline_id" :label="item.name" :value="item.wayline_id" /> </el-select> </div> <el-select v-model="filters.type" placeholder="请选择工单类型" class="filter-item" clearable > <el-option v-for="item in types" :key="item.dictValue" :label="item.dictValue" :value="item.dictKey" /> </el-select> <div class="search-bar-box-item"> <el-select v-model="filters.ai_types" placeholder="关联算法" clearable> <el-option v-for="item in ai_types" :key="item.dictKey" :label="item.dictValue" :value="item.dictKey" /> </el-select> </div> <el-select v-model="filters.status" placeholder="请选择工单状态" class="filter-item" clearable > <el-option v-for="item in statuses" :key="item.value" :label="item.label" :value="item.value" /> </el-select> <el-date-picker v-model="filters.cycleDateRange" type="datetimerange" range-separator="至" start-placeholder="工单周期开始日期" end-placeholder="工单周期结束时间日期" class="filter-item" :default-value="datePickerDefaultVal" style="width: 220px !important" /> <el-select v-model="filters.rep_fre_type" placeholder="请选择频次" class="filter-item frequency" > <el-option v-for="item in cycles" :key="item" :label="item" :value="item" /> </el-select> <el-time-picker style="width: 150px" v-model="filters.deal_time" placeholder="请选择执行时间" prop="deal_time" value-format="HH:mm" :picker-options="{ selectableRange: '00:00 - 23:59', }" /> <el-col :span="8"> </el-col> <el-button type="primary" icon="el-icon-search" @click="handleSearch">查询</el-button> <el-button icon="el-icon-refresh" @click="handleReset">重置</el-button> <div class="search-bar-box-item"> <el-select v-model="filters.type" placeholder="请选择工单类型" clearable> <el-option v-for="item in types" :key="item.dictValue" :label="item.dictValue" :value="item.dictKey" /> </el-select> </div> <div class="search-bar-box-item"> <el-select v-model="filters.status" placeholder="请选择工单状态" clearable> <el-option v-for="item in statuses" :key="item.value" :label="item.label" :value="item.value" /> </el-select> </div> </div> <div class="search-bar-box"> <div class="search-bar-box-item flex-2"> <el-date-picker v-model="filters.cycleDateRange" type="daterange" range-separator="至" start-placeholder="工单周期开始日期" end-placeholder="工单周期结束日期" :default-value="datePickerDefaultVal" /> </div> <div class="search-bar-box-item"> <el-select v-model="filters.rep_fre_type" placeholder="请选择频次"> <el-option v-for="item in cycles" :key="item" :label="item" :value="item" /> </el-select> </div> <div class="search-bar-box-item"> <el-time-picker v-model="filters.deal_time" placeholder="请选择执行时间" prop="deal_time" value-format="HH:mm" :picker-options="{ selectableRange: '00:00 - 23:59', }" /> </div> <div class="search-bar-box-item"></div> <div class="search-bar-box-item"></div> <div class="search-bar-box-item search-btn"> <el-button type="primary" icon="el-icon-search" @click="handleSearch">搜索</el-button> <el-button icon="el-icon-refresh" @click="handleReset">清空</el-button> </div> </div> </div> <!-- 表格部分 --> <avue-crud :data="tableData" :option="option" v-model:page="page" ref="crud" :table-loading="loading" @current-change="currentChange" @refresh-change="refreshChange" @on-load="onLoad" @search-change="searchChange" @size-change="sizeChange" > <avue-crud :data="tableData" :option="option" v-model:page="page" ref="crud" :table-loading="loading" @current-change="currentChange" @refresh-change="refreshChange" @on-load="onLoad" @search-change="searchChange" @size-change="sizeChange"> <template #menu-left> <el-button v-if="hasAddBtnPermission() && activeTab != 'WAIT_AUDIT'" type="primary" icon="el-icon-plus" @click="handleAdd" >新建工单</el-button > <el-button type="success" plain icon="el-icon-download" @click="exportData" >导出</el-button > <el-button v-if="hasAddBtnPermission() && activeTab != 'WAIT_AUDIT'" type="primary" icon="el-icon-plus" @click="handleAdd">新建工单</el-button> <el-button type="success" plain icon="el-icon-download" @click="exportData">导出</el-button> </template> <template #menu="{ row }"> <div class="menu-custom-box"> <div v-if="row.status == 1"> <el-button class="audit-btn" v-if="hasPaddingBtnPermission()" type="text" icon="el-icon-view" @click="handleCheckDetail(row)" >审核</el-button > </div> <template v-if="row.status == 1"> <el-button v-if="hasPaddingBtnPermission()" type="text" icon="el-icon-view" @click="handleCheckDetail(row)">审核</el-button> </template> <div v-if=" (userInfo.user_id == row.create_user || hasRecallPaddingBtnPermission()) && row.status == 1 " > <template v-if=" (userInfo.user_id == row.create_user || hasRecallPaddingBtnPermission()) && row.status == 1 "> <!--待审核状态--> <el-button class="withdraw-btn" type="text" icon="el-icon-warning" @click="orderLogRecall(row.id)" >撤回</el-button > </div> <el-button type="text" icon="el-icon-warning" @click="orderLogRecall(row.id)">撤回</el-button> </template> <!--已驳回--> <div v-if="row.status == 2"> <el-button class="reject-reason-btn" type="text" icon="el-icon-warning" @click="rejectDetail(row.id)" >驳回原因</el-button > </div> <template v-if="row.status == 2"> <el-button type="text" icon="el-icon-warning" @click="rejectDetail(row.id)">驳回原因</el-button> </template> <!--草稿--> <div v-if="row.status == 0 && userInfo.user_id == row.create_user"> <el-button class="edit-btn" type="text" icon="el-icon-edit-outline" @click="handleViewDetail(row)" >编辑</el-button > <el-button class="publish-btn" type="text" icon="el-icon-position" @click="userPublishPush(row.id)" >发布</el-button > <el-button class="delete-btn" type="text" icon="el-icon-delete" @click="deleteOrderLog(row.id)" >删除</el-button > </div> <div v-if="row.status == 3 || row.status == 1 || row.status == 2"> <el-button class="detail-btn" type="text" icon="el-icon-view" @click="handleViewDetail(row)" >详情</el-button > </div> <el-button type="text" icon="el-icon-edit" @click="handleViewDetail(row)">编辑</el-button> <el-button type="text" icon="el-icon-position" @click="userPublishPush(row.id)">发起</el-button> <el-button type="text" icon="el-icon-delete" @click="deleteOrderLog(row.id)">删除</el-button> <template v-if="row.status == 3 || row.status == 1 || row.status == 2"> <el-button type="text" icon="el-icon-view" @click="handleViewDetail(row)">详情</el-button> </template> </div> </template> <template #status="{ row }"> @@ -254,34 +141,19 @@ </el-tabs> <!-- 新建工单对话框 --> <el-dialog v-model="dialogVisible" title="新建工单" width="70%" :close-on-click-modal="false" @close="resetForm" > <el-dialog v-model="dialogVisible" title="新建工单" width="70%" :close-on-click-modal="false" @close="resetForm"> <el-form :model="form" :rules="rules" ref="testform" label-width="100px"> <el-row :gutter="20"> <el-col :span="12"> <el-form-item label="工单名称" prop="name"> <el-input v-model="form.name" placeholder="请输入工单名称" maxlength="100" show-word-limit ></el-input> <el-input v-model="form.name" placeholder="请输入工单名称" maxlength="100" show-word-limit></el-input> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="关联航线" prop="file_id"> <el-select v-model="form.file_id" placeholder="请选择航线" @change="getFlyingNestBy"> <el-option v-for="item in wayLineList" :key="item.wayline_id" :label="item.name" :value="item.wayline_id" /> <el-option v-for="item in wayLineList" :key="item.wayline_id" :label="item.name" :value="item.wayline_id" /> </el-select> </el-form-item> </el-col> @@ -290,24 +162,15 @@ <el-col :span="12"> <el-form-item label="关联机巢" prop="device_sns"> <el-select v-model="form.device_sns" placeholder="请选择机巢" multiple> <el-option v-for="item in device_sns" :key="item.device_sn" :label="item.nickname" :value="item.device_sn" /> <el-option v-for="item in device_sns" :key="item.device_sn" :label="item.nickname" :value="item.device_sn" /> </el-select> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="关联算法" prop="ai_types"> <el-select v-model="form.ai_types" placeholder="请选择关联算法" multiple> <el-option v-for="item in ai_types" :key="item.dictKey" :label="item.dictValue" :value="item.dictKey" /> <el-option v-for="item in ai_types" :key="item.dictKey" :label="item.dictValue" :value="item.dictKey" /> </el-select> </el-form-item> </el-col> @@ -316,45 +179,31 @@ <el-row :gutter="20"> <el-col :span="12"> <el-form-item label="工单内容" prop="content"> <el-input type="textarea" v-model="form.content" rows="4" placeholder="请输入工单内容" maxlength="255" show-word-limit ></el-input> <el-input type="textarea" v-model="form.content" rows="4" placeholder="请输入工单内容" maxlength="255" show-word-limit></el-input> </el-form-item> </el-col> <el-col :span="6"> <el-col :span="8"> <el-form-item label="周期频次" prop="date_range"> <el-date-picker v-model="form.date_range" type="daterange" range-separator="至" start-placeholder="开始日期" end-placeholder="结束日期" class="date-picker" /> <el-date-picker v-model="form.date_range" type="daterange" range-separator="至" start-placeholder="开始日期" end-placeholder="结束日期" /> </el-form-item> </el-col> <el-col :span="3"> <el-select v-model="form.rep_fre_type" placeholder="请选择频次"> <el-option v-for="item in cycles" :key="item" :label="item" :value="item" /> </el-select> </el-col> <el-col :span="3"> <el-time-picker style="width: 100px" v-model="form.deal_time" prop="deal_time" value-format="HH:mm" :picker-options="{ selectableRange: '00:00 - 23:59', }" /> <el-col :span="4"> <div class="flex"> <div class="flex-1"> <el-select v-model="form.rep_fre_type" placeholder="请选择频次"> <el-option v-for="item in cycles" :key="item" :label="item" :value="item" /> </el-select> </div> <div class="flex-1"> <el-time-picker style="width: 100px" v-model="form.deal_time" prop="deal_time" value-format="HH:mm" :picker-options="{ selectableRange: '00:00 - 23:59', }" /> </div> </div> </el-col> </el-row> @@ -366,7 +215,7 @@ <el-row> <div class="add-box-btns"> <el-button type="danger" @click="submitForm(1)">发布</el-button> <el-button type="danger" @click="submitForm(1)">发起</el-button> <el-button type="primary" @click="submitForm(0)">存草稿</el-button> </div> </el-row> @@ -374,24 +223,15 @@ </el-dialog> <!-- 工单详情对话框 --> <el-dialog v-model="detailVisible" :title="detailTitle" width="70%" :close-on-click-modal="false" @close="resetForm" > <el-dialog v-model="detailVisible" :title="detailTitle" width="70%" :close-on-click-modal="false" @close="resetForm"> <div class="event-title-center">{{ form.name }}</div> <el-form :model="form" ref="testform" label-width="100px"> <div class="custom-steps-container"> <!-- 标题行 --> <div class="steps-titles"> <div v-for="(record, index) in form.record_list" :class="{ active: record.user_id >= 0 }" :key="index" class="step-title" > <div v-for="(record, index) in form.record_list" :class="{ active: record.user_id >= 0 }" :key="index" class="step-title"> {{ record.status_str }} </div> </div> @@ -403,8 +243,7 @@ <span class="step-description" style="position: relative; display: inline-block"> {{ record.user_name }} </span> <span style=" <span style=" position: absolute; left: 80%; top: 50%; @@ -413,8 +252,7 @@ margin-left: 4px; color: #666; font-size: 12px; " > "> {{ record.interval_time_str }} </span> <div class="step-description"> @@ -434,12 +272,8 @@ <el-col :span="12"> <el-form-item label="关联航线" prop="file_id"> <el-select v-model="form.file_id" placeholder="请选择航线" @change="getFlyingNestBy"> <el-option v-for="item in wayLineList" :key="item.wayline_id" :label="item.name" :value="item.wayline_id" /> <el-option v-for="item in wayLineList" :key="item.wayline_id" :label="item.name" :value="item.wayline_id" /> </el-select> </el-form-item> </el-col> @@ -448,24 +282,15 @@ <el-col :span="12"> <el-form-item label="关联机巢" prop="device_sns"> <el-select v-model="form.device_sns" placeholder="请选择机巢" multiple> <el-option v-for="item in device_sns" :key="item.device_sn" :label="item.nickname" :value="item.device_sn" /> <el-option v-for="item in device_sns" :key="item.device_sn" :label="item.nickname" :value="item.device_sn" /> </el-select> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="关联算法" prop="ai_types"> <el-select v-model="form.ai_types" placeholder="请选择关联算法" multiple> <el-option v-for="item in ai_types" :key="item.dictKey" :label="item.dictValue" :value="item.dictKey" /> <el-option v-for="item in ai_types" :key="item.dictKey" :label="item.dictValue" :value="item.dictKey" /> </el-select> </el-form-item> </el-col> @@ -480,14 +305,8 @@ <el-col :span="6"> <el-form-item label="周期频次" prop="date_range"> <el-date-picker v-model="form.date_range" type="daterange" range-separator="至" start-placeholder="开始日期" end-placeholder="结束日期" class="date-picker" /> <el-date-picker v-model="form.date_range" type="daterange" range-separator="至" start-placeholder="开始日期" end-placeholder="结束日期" /> </el-form-item> </el-col> @@ -498,29 +317,18 @@ </el-col> <el-col :span="3"> <el-time-picker style="width: 100px" v-model="form.deal_time" prop="deal_time" value-format="HH:mm" <el-time-picker style="width: 100px" v-model="form.deal_time" prop="deal_time" value-format="HH:mm" :picker-options="{ selectableRange: '00:00 - 23:59', }" /> }" /> </el-col> </el-row> <el-row :gutter="20"> <el-col :span="12"> <el-form-item label="工单内容" prop="content"> <el-input type="textarea" v-model="form.content" rows="4" placeholder="请输入工单内容" maxlength="255" show-word-limit ></el-input> <el-input type="textarea" v-model="form.content" rows="4" placeholder="请输入工单内容" maxlength="255" show-word-limit></el-input> </el-form-item> </el-col> </el-row> @@ -533,51 +341,30 @@ <el-row> <div class="add-box-btns"> <el-button type="danger" <el-button type="danger" v-if="form.status == 0 || (form.status == 2 && userInfo.user_id == form.create_user)" @click="submitForm(1)" >发布</el-button > @click="submitForm(1)">发布</el-button> <!-- <el-button type="primary" v-if="form.status == 0 || userInfo.user_id == form.create_user" @click="submitForm(0)">保存</el-button> --> <el-button type="primary" v-if="form.status == 1 && this.permission.orderLogpass" @click="orderLogPass(form.id)" >通过</el-button > <el-button type="danger" v-if="form.status == 1 && hasRejectionBtnPermission()" @click="orderLogReject(form.id)" >驳回</el-button > <el-button type="primary" v-if="form.status == 1 && this.permission.orderLogpass" @click="orderLogPass(form.id)">通过</el-button> <el-button type="danger" v-if="form.status == 1 && hasRejectionBtnPermission()" @click="orderLogReject(form.id)">驳回</el-button> </div> </el-row> </el-form> </el-dialog> <!-- 工单详情 --> <el-dialog v-model="detailVisibleCopy" title="工单详情" width="70%" :close-on-click-modal="false" @close="resetForm" > <el-dialog v-model="detailVisibleCopy" title="工单详情" width="70%" :close-on-click-modal="false" @close="resetForm"> <div class="event-title-center">{{ form.name }}</div> <el-form :model="form" ref="testform" label-width="100px"> <div class="custom-steps-container"> <!-- 标题行 --> <div class="steps-titles"> <div v-for="(record, index) in form.record_list" :class="{ active: record.user_id >= 0 }" :key="index" class="step-title" > <div v-for="(record, index) in form.record_list" :class="{ active: record.user_id >= 0 }" :key="index" class="step-title"> {{ record.status_str }} </div> </div> @@ -589,8 +376,7 @@ <span class="step-description" style="position: relative; display: inline-block"> {{ record.user_name }} </span> <span style=" <span style=" position: absolute; left: 80%; top: 50%; @@ -599,8 +385,7 @@ margin-left: 4px; color: #666; font-size: 12px; " > "> {{ record.interval_time_str }} </span> <div class="step-description"> @@ -614,22 +399,14 @@ <el-row :gutter="20"> <el-col :span="12"> <el-form-item label="工单名称" prop="name"> <el-input v-model="form.name" placeholder="请输入工单名称" :disabled="true" ></el-input> <el-input v-model="form.name" placeholder="请输入工单名称" :disabled="true"></el-input> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="关联航线" prop="file_id"> <el-select v-model="form.file_id" placeholder="请选择航线" :disabled="true"> <el-option v-for="item in wayLineList" :key="item.wayline_id" :label="item.name" :value="item.wayline_id" /> <el-option v-for="item in wayLineList" :key="item.wayline_id" :label="item.name" :value="item.wayline_id" /> </el-select> </el-form-item> </el-col> @@ -638,35 +415,16 @@ <el-row :gutter="20"> <el-col :span="12"> <el-form-item label="关联机巢" prop="device_sns"> <el-select v-model="form.device_sns" placeholder="请选择机巢" multiple :disabled="true" > <el-option v-for="item in device_sns" :key="item.device_sn" :label="item.nickname" :value="item.device_sn" /> <el-select v-model="form.device_sns" placeholder="请选择机巢" multiple :disabled="true"> <el-option v-for="item in device_sns" :key="item.device_sn" :label="item.nickname" :value="item.device_sn" /> </el-select> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="关联算法" prop="ai_types"> <el-select v-model="form.ai_types" placeholder="请选择关联算法" multiple :disabled="true" > <el-option v-for="item in ai_types" :key="item.dictKey" :label="item.dictValue" :value="item.dictKey" /> <el-select v-model="form.ai_types" placeholder="请选择关联算法" multiple :disabled="true"> <el-option v-for="item in ai_types" :key="item.dictKey" :label="item.dictValue" :value="item.dictKey" /> </el-select> </el-form-item> </el-col> @@ -681,15 +439,8 @@ <el-col :span="6"> <el-form-item label="周期频次" prop="date_range"> <el-date-picker v-model="form.date_range" type="daterange" range-separator="至" start-placeholder="开始日期" end-placeholder="结束日期" class="date-picker" :disabled="true" /> <el-date-picker v-model="form.date_range" type="daterange" range-separator="至" start-placeholder="开始日期" end-placeholder="结束日期" :disabled="true" /> </el-form-item> </el-col> <el-col :span="3"> @@ -699,32 +450,18 @@ </el-col> <el-col :span="3"> <el-time-picker style="width: 100px" v-model="form.deal_time" prop="deal_time" :disabled="true" value-format="HH:mm" :picker-options="{ <el-time-picker style="width: 100px" v-model="form.deal_time" prop="deal_time" :disabled="true" value-format="HH:mm" :picker-options="{ selectableRange: '00:00 - 23:59', }" /> }" /> </el-col> </el-row> <el-row :gutter="20"> <el-col :span="12"> <el-form-item label="工单内容" prop="content"> <el-input type="textarea" v-model="form.content" rows="2" placeholder="请输入工单内容" maxlength="255" show-word-limit :readonly="true" :disabled="true" ></el-input> <el-input type="textarea" v-model="form.content" rows="2" placeholder="请输入工单内容" maxlength="255" show-word-limit :readonly="true" :disabled="true"></el-input> </el-form-item> </el-col> </el-row> @@ -737,27 +474,16 @@ <el-row> <div class="add-box-btns"> <el-button type="danger" <el-button type="danger" v-if="form.status == 0 || (form.status == 2 && userInfo.user_id == form.create_user)" @click="submitForm(1)" >发布</el-button > @click="submitForm(1)">发布</el-button> <!-- <el-button type="primary" v-if="form.status == 0 || userInfo.user_id == form.create_user" @click="submitForm(0)">保存</el-button> --> <el-button type="primary" v-if="form.status == 1 && this.permission.orderLogpass" @click="orderLogPass(form.id)" >通过</el-button > <el-button type="danger" v-if="form.status == 1 && hasRejectionBtnPermission()" @click="orderLogReject(form.id)" >驳回</el-button > <el-button type="primary" v-if="form.status == 1 && this.permission.orderLogpass" @click="orderLogPass(form.id)">通过</el-button> <el-button type="danger" v-if="form.status == 1 && hasRejectionBtnPermission()" @click="orderLogReject(form.id)">驳回</el-button> <el-button @click="detailVisibleCopy = false">取消</el-button> </div> </el-row> @@ -767,6 +493,7 @@ </template> <script> import { calculateDefaultRange } from '@/utils/util' import { getList, saveUpdateOrderLog, @@ -778,38 +505,21 @@ jobStatusNum, userPublish, deleteOrderLog, } from '@/api/tickets/orderLog'; import { getTicketInfo } from '@/api/tickets/ticket'; import { getDictionaryByCode } from '@/api/system/dictbiz'; import { getWaylineFileListByArea } from '@/api/resource/wayline'; import { export_json_to_excel } from '@/utils/exportExcel'; import { getFlyingNestBy } from '@/api/device/device'; import { mapGetters } from 'vuex'; import NProgress from 'nprogress'; import { downloadXls } from '@/utils/util'; import 'nprogress/nprogress.css'; import { analyzeKmzFile, removeTextKey, XMLToJSON } from '@/utils/cesium/kmz'; } from '@/api/tickets/orderLog' import { getTicketInfo } from '@/api/tickets/ticket' import { getDictionaryByCode } from '@/api/system/dictbiz' import { getWaylineFileListByArea } from '@/api/resource/wayline' import { export_json_to_excel } from '@/utils/exportExcel' import { getFlyingNestBy } from '@/api/device/device' import { mapGetters } from 'vuex' import NProgress from 'nprogress' import { downloadXls } from '@/utils/util' import 'nprogress/nprogress.css' import { analyzeKmzFile, removeTextKey, XMLToJSON } from '@/utils/cesium/kmz' export default { name: 'TicketPage', data() { const calculateDefaultRange = () => { const now = new Date(); const currentYear = now.getFullYear(); const currentMonth = now.getMonth(); // 0-11 // 计算上个月 let lastMonthYear = currentYear; let lastMonth = currentMonth - 1; if (lastMonth < 0) { lastMonth = 11; lastMonthYear = currentYear - 1; } // 设置默认范围 [上个月, 当前月] return [new Date(lastMonthYear, lastMonth, 1), new Date(currentYear, currentMonth, 1)]; }; data () { return { activeTab: 'all', @@ -849,6 +559,9 @@ tableData: [], option: { index: true, indexWidth: 66, indexLabel: '序号', indexFixed: true, align: 'center', @@ -869,12 +582,13 @@ menuWidth: 210, menuClassName: 'cur-menu', column: [ { label: '序号', prop: 'id', width: 72, ellipsis: true }, { label: '工单编号', prop: 'job_info_num', width: 100, ellipsis: true, overHidden: true }, { label: '工单名称', prop: 'name', width: 100, ellipsis: true, overHidden: true }, { label: '工单状态', prop: 'status', width: 88 }, { label: '所属单位', prop: 'dept_name', width: 100, ellipsis: true }, { label: '发起时间', prop: 'create_time', width: 144, ellipsis: true }, { label: '工单内容', prop: 'content', width: 160, ellipsis: true, overHidden: true }, { label: '已执行次数', prop: 'job_num', width: 96, ellipsis: true }, { label: '工单内容', prop: 'content', ellipsis: true, overHidden: true }, { label: '关联航线', prop: 'wayline_name', width: 100, ellipsis: true, overHidden: true }, { label: '关联算法', prop: 'ai_type_str', width: 100, ellipsis: true, overHidden: true }, { label: '关联机巢', prop: 'device_names', width: 100, ellipsis: true, overHidden: true }, @@ -890,7 +604,6 @@ ellipsis: true, // overHidden: true }, { label: '工单状态', prop: 'status', width: 96 }, ], }, @@ -925,119 +638,119 @@ // 配置时间选择器默认配置 datePickerDefaultVal: calculateDefaultRange(), }; } }, async created() { var response = await getDictionaryByCode('SF'); var word_order_typeResponse = await getDictionaryByCode('WORK_ORDER_TYPE'); this.ai_types = response.data.data['SF']; this.types = word_order_typeResponse.data.data['WORK_ORDER_TYPE']; async created () { var response = await getDictionaryByCode('SF') var word_order_typeResponse = await getDictionaryByCode('WORK_ORDER_TYPE') this.ai_types = response.data.data['SF'] this.types = word_order_typeResponse.data.data['WORK_ORDER_TYPE'] //获取航线 this.asyncgetWaylineFileListByArea(); const response2 = await getTicketInfo(); const { dept_data, event_type, ai_type } = response2.data.data; this.asyncgetWaylineFileListByArea() const response2 = await getTicketInfo() const { dept_data, event_type, ai_type } = response2.data.data this.departments = dept_data.map(item => ({ label: item.dept_name, value: item.id, })); })) }, mounted() { this.fetchTableData(); mounted () { this.fetchTableData() }, computed: { ...mapGetters(['userInfo', 'permission']), filteredTabs() { filteredTabs () { // rejection_and_draft 权限控制“已驳回”和“草稿”tab const canShowRejectAndDraft = this.permission?.rejection_and_draft === true; const canShowRejectAndDraft = this.permission?.rejection_and_draft === true return this.tabs .map(tab => { if (tab.name === 'DRAFT') { return { ...tab, isShow: canShowRejectAndDraft }; return { ...tab, isShow: canShowRejectAndDraft } } if (tab.name === 'REJECTED') { return { ...tab, isShow: canShowRejectAndDraft }; return { ...tab, isShow: canShowRejectAndDraft } } return { ...tab, isShow: true }; return { ...tab, isShow: true } }) .filter(tab => tab.isShow); .filter(tab => tab.isShow) }, }, methods: { searchChange(params, done) { console.log('searchChange'); this.query = params; this.parentId = ''; this.page.currentPage = 1; this.onLoad(this.page, params); done(); searchChange (params, done) { console.log('searchChange') this.query = params this.parentId = '' this.page.currentPage = 1 this.onLoad(this.page, params) done() }, async onLoad(page, params = {}) { this.loading = true; async onLoad (page, params = {}) { this.loading = true getList( null, this.page.currentPage, this.page.pageSize, Object.assign(params, this.query) ).then(res => { this.tableData = res.data.data; this.loading = false; this.selectionClear(); }); this.tableData = res.data.data this.loading = false this.selectionClear() }) }, selectionClear() { this.selectionList = []; this.$refs.crud.toggleSelection(); selectionClear () { this.selectionList = [] this.$refs.crud.toggleSelection() }, async loadAMapScripts() { async loadAMapScripts () { try { // await loadAMap(); // await loadAMapUI(); this.mapLoaded = true; this.mapLoaded = true } catch (error) { console.error('Failed to load AMap scripts:', error); this.$message.error('地图加载失败,请检查网络或API Key配置'); console.error('Failed to load AMap scripts:', error) this.$message.error('地图加载失败,请检查网络或API Key配置') } }, formatCycleTime(row) { return `${row.cycle_time_value}`; formatCycleTime (row) { return `${row.cycle_time_value}` }, async fetchTableData() { this.loading = true; async fetchTableData () { this.loading = true try { let params = this.getQueryParam(); console.log('发送的参数:', params); const response = await getList(params, this.page.currentPage, this.page.pageSize); let params = this.getQueryParam() console.log('发送的参数:', params) const response = await getList(params, this.page.currentPage, this.page.pageSize) if (!response?.data?.data?.records) { throw new Error('接口返回数据格式不正确'); throw new Error('接口返回数据格式不正确') } const { total, records } = response.data.data; const { total, records } = response.data.data this.tableData = records.map(item => { return item; }); return item }) console.log('权限检查:', this.permission); this.page.total = total || 0; this.updateGlobalCounts(); console.log('权限检查:', this.permission) this.page.total = total || 0 this.updateGlobalCounts() } catch (error) { console.error('获取数据失败:', error); this.$message.error(error.message || '获取数据失败'); this.tableData = []; this.page.total = 0; console.error('获取数据失败:', error) this.$message.error(error.message || '获取数据失败') this.tableData = [] this.page.total = 0 } finally { this.loading = false; this.loading = false } }, getQueryParam() { const currentTab = this.tabs.find(tab => tab.name === this.activeTab); getQueryParam () { const currentTab = this.tabs.find(tab => tab.name === this.activeTab) if (this.filters.dateRange) { console.log( 'this.formatDate(this.filters.dateRange[0])', this.formatDate(this.filters.dateRange[0]) ); ) } const params = { @@ -1050,7 +763,7 @@ file_id: this.filters.file_id || undefined, status: this.filters.status !== '' ? Number(this.filters.status) : currentTab?.value, dept_id: this.filters.department || undefined, creat_start_date: this.filters.dateRange?.[0] create_start_date: this.filters.dateRange?.[0] ? this.formatDate(this.filters.dateRange[0]) : undefined, create_end_date: this.filters.dateRange?.[1] @@ -1060,50 +773,50 @@ start_date: this.filters.cycleDateRange?.[0] ? this.formatDate(this.filters.cycleDateRange[0]) : undefined, end_date: this.filters.dateRacycleDateRangenge?.[1] end_date: this.filters.cycleDateRange?.[1] ? this.formatDate(this.filters.cycleDateRange[1]).replace('00:00:00', '23:59:59') : undefined, rep_fre_type: this.filters.rep_fre_type || undefined, deal_time: this.filters.deal_time || undefined, current: this.page.currentPage, size: this.page.pageSize, }; return params; } return params }, sizeChange(pageSize) { this.page.pageSize = pageSize; sizeChange (pageSize) { this.page.pageSize = pageSize }, async submitForm(status) { async submitForm (status) { this.$refs.testform.validate(async valid => { if (valid) { let dateRange = this.form.date_range; console.log('dateRange' + dateRange); let dateRange = this.form.date_range console.log('dateRange' + dateRange) this.form.begin_time = this.formatDate(dateRange[0]); this.form.end_time = this.formatDate(dateRange[1]); this.form.begin_time = this.formatDate(dateRange[0]) this.form.end_time = this.formatDate(dateRange[1]) const submitData = { ...this.form, status: status, }; await saveUpdateOrderLog(submitData); let id = this.form.id; if (id) { this.$message.success('工单发布成功'); } else { this.$message.success('工单创建成功'); } this.dialogVisible = false; await saveUpdateOrderLog(submitData) let id = this.form.id if (id) { this.$message.success('工单发布成功') } else { this.$message.success('工单创建成功') } this.dialogVisible = false this.detailVisible = false; (this.device_sns = []), (this.wayLineList = []), this.fetchTableData(); (this.device_sns = []), (this.wayLineList = []), this.fetchTableData() } }); }) }, //驳回原因显示 async rejectDetail(id) { const response = await orderLogDetails(id); let data = response.data.data; async rejectDetail (id) { const response = await orderLogDetails(id) let data = response.data.data this.$confirm(data.remark, '驳回原因', { confirmButtonText: '确定', cancelButtonText: '取消', @@ -1111,93 +824,93 @@ }).then(() => { this.form = { ...response.data.data, }; this.detailVisible = true; }); } this.detailVisible = true }) }, formatDate(date) { if (!date) return undefined; const d = new Date(date); formatDate (date) { if (!date) return undefined const d = new Date(date) return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String( d.getDate() ).padStart(2, '0')} 00:00:00`; ).padStart(2, '0')} 00:00:00` }, mapStatus(status) { mapStatus (status) { const statusTextMap = { 0: '草稿', 1: '待审核', 2: '已驳回', 3: '已通过', }; return statusTextMap[status] || '未知状态'; } return statusTextMap[status] || '未知状态' }, getStatusTagType(status) { getStatusTagType (status) { const statusMap = { 1: 'warning', 2: 'info', 3: 'primary', 4: 'success', 5: 'danger', }; return statusMap[status] || 'info'; } return statusMap[status] || 'info' }, handleTabChange(tab) { this.activeTab = tab.props?.name || tab.name; this.filters.status = ''; this.page.currentPage = 1; this.fetchTableData(); handleTabChange (tab) { this.activeTab = tab.props?.name || tab.name this.filters.status = '' this.page.currentPage = 1 this.fetchTableData() }, handleSearch() { this.page.currentPage = 1; this.fetchTableData(); handleSearch () { this.page.currentPage = 1 this.fetchTableData() }, handleReset() { handleReset () { this.filters = { keyword: '', department: '', type: '', dateRange: [], status: '', }; this.page.currentPage = 1; this.fetchTableData(); } this.page.currentPage = 1 this.fetchTableData() }, currentChange(currentPage) { this.page.currentPage = currentPage; currentChange (currentPage) { this.page.currentPage = currentPage }, async updateGlobalCounts() { async updateGlobalCounts () { const counts = { all: 0, DRAFT: 0, WAIT_AUDIT: 0, REJECTED: 0, PASS: 0, }; var reponse = await jobStatusNum(); console.log('统计' + reponse.data.data); } var reponse = await jobStatusNum() console.log('统计' + reponse.data.data) reponse.data.data.forEach(item => { const tab = this.tabs.find(t => t.name === item.dict_key); const tab = this.tabs.find(t => t.name === item.dict_key) if (tab) { tab.count = item.num; tab.count = item.num } }); }) }, handleAdd() { this.form = {}; this.dialogVisible = true; handleAdd () { this.form = {} this.dialogVisible = true //航线列表 this.asyncgetWaylineFileListByArea(); this.asyncgetWaylineFileListByArea() }, resetForm() { resetForm () { this.form = { name: '', type: '', @@ -1207,222 +920,227 @@ address: '', content: '', photos: [], }; } if (this.$refs.testform) { this.$refs.testform.resetFields(); this.$refs.testform.resetFields() } }, formatLocation(location) { formatLocation (location) { if (!Array.isArray(location)) { return '未知位置'; return '未知位置' } return `${location[0].toFixed(6)}, ${location[1].toFixed(6)}`; return `${location[0].toFixed(6)}, ${location[1].toFixed(6)}` }, async handleViewDetail(row) { const response = await orderLogDetails(row.id); const data = response.data.data; async handleViewDetail (row) { const response = await orderLogDetails(row.id) const data = response.data.data this.form = { ...data, }; } // 更新机巢列表 this.device_sns = data.device_list; this.device_sns = data.device_list this.permission && (this.permission.order_log_review || this.permission.order_log_recall) && (data.status == 1 || data.status == 3 || data.status == 2) (this.permission.order_log_review || this.permission.order_log_recall) && (data.status == 1 || data.status == 3 || data.status == 2) ? (this.detailTitle = '工单详情') : (this.detailTitle = '编辑工单'); : (this.detailTitle = '编辑工单') this.detailVisible = true; this.detailVisible = true this.initMapLine(); this.initMapLine(data.device_map_infos) }, async handleCheckDetail(row) { const response = await orderLogDetails(row.id); const data = response.data.data; async handleCheckDetail (row) { const response = await orderLogDetails(row.id) const data = response.data.data this.form = { ...data, }; } // 更新机巢列表 this.device_sns = data.device_list; this.detailVisibleCopy = true; this.initMapLine(); this.device_sns = data.device_list this.detailVisibleCopy = true this.initMapLine(data.device_map_infos) }, //导出 async exportData() { async exportData () { this.$confirm('是否智飞工单数据?', '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning', }).then(() => { NProgress.start(); let params = this.getQueryParam(); NProgress.start() let params = this.getQueryParam() orderLogExport(params).then(res => { downloadXls(res.data, `智飞工单${this.$dayjs().format('YYYY-MM-DD')}.xlsx`); NProgress.done(); }); }); downloadXls(res.data, `智飞工单${this.$dayjs().format('YYYY-MM-DD')}.xlsx`) NProgress.done() }) }) }, hasAddBtnPermission() { hasAddBtnPermission () { // undefined 或 false 都返回 false,只有 true 返回 true console.log('this.permission.order_log_add :', this.permission.order_log_add); return this.permission && this.permission.order_log_add === true; console.log('this.permission.order_log_add :', this.permission.order_log_add) return this.permission && this.permission.order_log_add === true }, hasPaddingBtnPermission() { hasPaddingBtnPermission () { // undefined 或 false 都返回 false,只有 true 返回 true console.log('权限检查:', this.permission); return this.permission && this.permission.order_log_review === true; console.log('权限检查:', this.permission) return this.permission && this.permission.order_log_review === true }, hasRecallPaddingBtnPermission() { hasRecallPaddingBtnPermission () { // undefined 或 false 都返回 false,只有 true 返回 true console.log('权限检查:', this.permission); return this.permission && this.permission.order_log_recall === true; console.log('权限检查:', this.permission) return this.permission && this.permission.order_log_recall === true }, //驳回按钮权限 hasRejectionBtnPermission() { hasRejectionBtnPermission () { // undefined 或 false 都返回 false,只有 true 返回 true console.log('权限检查:', this.permission); return this.permission && this.permission.rejection_btn === true; console.log('权限检查:', this.permission) return this.permission && this.permission.rejection_btn === true }, //自己点发布 userPublishPush(id) { userPublishPush (id) { this.$confirm('确定发布吗?', '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning', }).then(() => { let response = userPublish(id); this.$message.success('发布成功'); this.fetchTableData(); }); let response = userPublish(id) this.$message.success('发布成功') this.fetchTableData() }) }, //删除 deleteOrderLog(id) { deleteOrderLog (id) { this.$confirm('确定删除吗?', '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning', }).then(() => { let response = deleteOrderLog(id); this.$message.success('删除'); this.fetchTableData(); }); let response = deleteOrderLog(id) this.$message.success('删除') this.fetchTableData() }) }, refreshChange() { this.fetchTableData(); refreshChange () { this.fetchTableData() }, //获取航线列表 async asyncgetWaylineFileListByArea(name) { var wayLineListResponse = await getWaylineFileListByArea(this.userInfo.detail.areaCode); this.wayLineList = wayLineListResponse.data.data; async asyncgetWaylineFileListByArea (name) { var wayLineListResponse = await getWaylineFileListByArea(this.userInfo.detail.areaCode) this.wayLineList = wayLineListResponse.data.data this.initMapLine(); this.initMapLine() }, initMapLine() { let currentLine = this.wayLineList.find(item => item.wayline_id == this.form.file_id); initMapLine (infos = {}) { let currentLine = this.wayLineList.find(item => item.wayline_id == this.form.file_id) if (!currentLine) return; if (!currentLine) return // 异步解析kmz文件 const analysis = async url => { return new Promise(async resolve => { const res = await analyzeKmzFile(`${url}?_t=${new Date().getTime()}`); const templateXML = await res.fileInfoObj['wpmz/template.kml']; const templateXMLJSON = XMLToJSON(templateXML)?.['Document']; const templateXMLObj = removeTextKey(templateXMLJSON.Folder); resolve(templateXMLObj); }); }; const res = await analyzeKmzFile(`${url}?_t=${new Date().getTime()}`) const templateXML = await res.fileInfoObj['wpmz/template.kml'] const templateXMLJSON = XMLToJSON(templateXML)?.['Document'] const templateXMLObj = removeTextKey(templateXMLJSON.Folder) resolve(templateXMLObj) }) } const drawLine = async () => { let prexUrl = ref(import.meta.env.VITE_APP_AIRLINE_URL + currentLine.object_key); const res = await analysis(prexUrl.value); if (!res.Placemark.length) return; renderingLine(res); }; let prexUrl = ref(import.meta.env.VITE_APP_AIRLINE_URL + currentLine.object_key) const res = await analysis(prexUrl.value) if (!res.Placemark.length) return renderingLine(res) } const renderingLine = lineObj => { const positions = lineObj.Placemark.map(item => { return item.Point.coordinates.split(','); }); return item.Point.coordinates.split(',') }) if (JSON.stringify(infos) != '{}') positions.unshift([ infos[0].longitude, infos[0].latitude, ]) this.$nextTick(() => { if (this.$refs.MapContainer && this.$refs.MapContainer.initAddEntity) { this.$refs.MapContainer.initAddEntity('polyline', positions); this.$refs.MapContainer.initAddEntity('polyline', positions) } }); }; }) } drawLine(); drawLine() }, //可飞行机巢列表 async getFlyingNestBy(waylineId) { this.initMapLine(); async getFlyingNestBy (waylineId) { this.initMapLine() //按照航线来 const params = { type: 0, waylineId: waylineId, }; var wayLineListResponse = await getFlyingNestBy(params); this.device_sns = wayLineListResponse.data.data; } var wayLineListResponse = await getFlyingNestBy(params) this.device_sns = wayLineListResponse.data.data }, //撤回 async orderLogRecall(id) { async orderLogRecall (id) { this.$confirm('确定撤回则到草稿箱。', '是否撤回?', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning', }).then(async () => { let reposne = await orderLogRecall(id); this.handleSearch(); }); let reposne = await orderLogRecall(id) this.handleSearch() }) }, onLoad() { this.fetchTableData(); onLoad () { this.fetchTableData() }, /** * 通过 */ async orderLogPass(id) { let response = await orderLogPass(id); let data = response.data.data; this.$message.success('审核通过'); this.detailVisibleCopy = false; async orderLogPass (id) { let response = await orderLogPass(id) let data = response.data.data this.$message.success('审核通过') this.detailVisibleCopy = false }, /** * 驳回 */ async orderLogReject(id) { async orderLogReject (id) { this.$prompt('', '驳回原因', { confirmButtonText: '确定', cancelButtonText: '取消', }).then(async ({ value }) => { let response = await orderLogReject(id, value); let data = response.data.data; this.$message.success('驳回成果'); this.detailVisibleCopy = false; }); let response = await orderLogReject(id, value) let data = response.data.data this.$message.success('驳回成果') this.detailVisibleCopy = false }) }, }, watch: { tableData: { handler() { handler () { // this.updateTabCounts() }, deep: true, }, }, }; } </script> <style></style> <style lang="scss" scoped> @@ -1441,23 +1159,32 @@ } .filter-bar { display: flex; // align-items: center; margin-bottom: 15px; flex-wrap: wrap; .filter-item { margin-right: 20px; margin-bottom: 10px; width: 200px; } .search-bar-box { margin-bottom: 15px; display: flex; justify-content: space-between; gap: 8px; .frequency { margin-left: 20px; } &-item { flex: 1; .date-picker { width: 240px; &> ::v-deep(.el-date-editor) { width: 100%; box-sizing: border-box; } } .search-btn { flex: 1; display: flex; justify-content: flex-end; } .flex-2 { min-width: 454px; } } } @@ -1574,37 +1301,15 @@ .menu-custom-box { display: flex; justify-content: center; flex-wrap: wrap; & > div { &> ::v-deep(.el-button) { flex: 1; } max-width: 44px; .audit-btn { color: #1ba0ff; } .withdraw-btn { color: #ff5d9e; } .reject-reason-btn { color: #ff6a00; } .edit-btn { color: #00b187; } .publish-btn { color: #5e00ff; } .delete-btn { color: #fe0202; } .detail-btn { color: #1f4aff; &:nth-child(4n) { margin-left: 0; } } } } @@ -1614,5 +1319,16 @@ display: flex; justify-content: center; } .flex { display: flex; } .flex-1 { flex: 1; &> ::v-deep(div) { width: 100% !important; } } </style> ` src/views/tickets/ticket.vue
@@ -14,7 +14,7 @@ <el-option v-for="item in types" :key="item.value" :label="item.label" :value="item.value" /> </el-select> <el-date-picker v-model="filters.dateRange" type="daterange" class="filter-item" range-separator="至" start-placeholder="开始日期" end-placeholder="结束日期"> start-placeholder="开始日期" end-placeholder="结束日期" :default-value="datePickerDefaultVal"> </el-date-picker> <el-select v-model="filters.status" placeholder="请选择状态" class="filter-item" clearable> <el-option v-for="item in statuses" :key="item.value" :label="item.label" :value="item.value" /> @@ -23,8 +23,11 @@ <el-option v-for="item in algorithms" :key="item.dict_key" :label="item.dict_value" :value="item.dict_key" /> </el-select> <el-button type="primary" icon="el-icon-search" @click="handleSearch">查询</el-button> <el-button icon="el-icon-refresh" @click="handleReset">重置</el-button> <el-select v-model="filters.isReview" placeholder="请选择复核状态" class="filter-item" clearable> <el-option v-for="item in reviewStatuses" :key="item.value" :label="item.label" :value="item.value" /> </el-select> <el-button type="primary" icon="el-icon-search" @click="handleSearch">搜索</el-button> <el-button icon="el-icon-refresh" @click="handleReset">清空</el-button> </div> <!-- 表格部分 --> @@ -45,9 +48,13 @@ <el-button type="text" icon="el-icon-delete" class="danger-button" @click="handleDelete(row)">删除</el-button> </template> <template v-else> <el-button type="text" icon="el-icon-view" @click="handleViewDetail(row)">详情</el-button> </template> <el-button v-if="row.status === 4 && row.isReview !== 1" type="text" icon="el-icon-check" @click="reCheck(row)">复核</el-button> </template> <template #status="{ row }"> <span :style="getStatusTagType(row.status) ? 'color:' + getStatusTagType(row.status) : ''"> @@ -56,6 +63,10 @@ </template> <template #keyData="{ row }"> <span>{{ row.address }}</span> </template> <template #isReview="{ row }"> <span>{{ showIsReviewText(row) }}</span> </template> </avue-crud> </div> @@ -269,10 +280,10 @@ </div> <!-- 上传图片 --> <div v-if="[3, 4].includes(currentDetail.status)" class="form-section" style="margin-bottom: 32px;"> <div v-if="[3].includes(currentDetail.status)" class="form-section" style="margin-bottom: 32px;"> <div class="section-title" v-if="hasProcessedAndOverBtnPermission()"> <!-- 已完成状态显示必填星号 --> <template v-if="currentDetail.status === 4"> <template v-if="currentDetail.status === 3"> <span class="required-label"> <span class="required-star">*</span>上传图片 </span> @@ -324,7 +335,7 @@ <el-col :span="12"> <div class="media-box"> <!-- 根据状态显示不同的标题和内容 --> <template v-if="currentDetail.status === 5"> <template v-if="currentDetail.status === 4"> <div class="media-title">工单处理图片</div> <div class="media-content"> <el-image v-if="currentDetail.updatePhotoUrl" :src="getThumbUrl(currentDetail.updatePhotoUrl)" @@ -368,9 +379,9 @@ </template> <template v-else-if="currentDetail.status === 0"> <el-button v-if="hasProcessingBtnPermission()" type="primary" :loading="dispatchLoading" @click="approveAndDispatch">通过并派发</el-button> @click="approveAndDispatch">受理</el-button> <el-button v-if="hasProcessingBtnPermission()" type="danger" :loading="rejectLoading" @click="rejectTicket">不通过</el-button> @click="rejectTicket">不受理</el-button> <el-button @click="detailVisible = false">取消</el-button> </template> <template v-if="currentDetail.status === 3"> @@ -381,8 +392,8 @@ </template> <template v-else-if="currentDetail.status === 4"> <!-- 已完成 --> <el-button v-if="hasProcessedAndOverBtnPermission()" type="primary" :loading="finalizeLoading" @click="finalizeTicket">完结工单</el-button> <!-- <el-button v-if="hasProcessedAndOverBtnPermission()" type="primary" :loading="finalizeLoading" @click="finalizeTicket">完结工单</el-button> --> <el-button @click="detailVisible = false">取消</el-button> </template> <template v-else-if="currentDetail.status === 5"> @@ -459,11 +470,33 @@ </div> </template> </el-dialog> <!-- 复核弹出层 --> <el-dialog v-model="reCheckDialog" title="工单复核" width="30%" append-to-body custom-class="re-check-dialog" @close="reCheckDialog = false"> <div class="dialog-footer"> <el-button type="primary" @click="reCheckConfirm(1)">人工复核</el-button> <el-button type="primary" @click="reCheckConfirm(2)">机器复核</el-button> </div> </el-dialog> </basic-container> </template> <script> import { getList, createTicket, getTicketInfo, flowEvent, getstatusCount, getStepInfo } from '@/api/tickets/ticket' import { ElMessageBox, ElLoading } from 'element-plus' import { calculateDefaultRange } from '@/utils/util' import { gcj02ToWgs84, wgs84ToGcj02 } from '@/utils/coordinateTransformation' import { getList, createTicket, getTicketInfo, flowEvent, getstatusCount, getStepInfo, getReviewById, getCreateEventJob } from '@/api/tickets/ticket' import { export_json_to_excel } from '@/utils/exportExcel' import geoJson from '@/assets/geoJson.json' @@ -488,7 +521,7 @@ { label: "待处理", name: "processing", value: 0, count: 0 }, { label: "处理中", name: "inProgress", value: 3, count: 0 }, { label: "已完成", name: "completed", value: 4, count: 0 }, { label: "已完结", name: "closed", value: 5, count: 0 }, // { label: "已完结", name: "closed", value: 5, count: 0 }, { label: "我发起的工单", name: "myTickets", value: null, count: 0 }, ], filters: { @@ -498,6 +531,7 @@ dateRange: [], status: "", algorithm: "", // 新增算法筛选字段 isReview: "", // 添加复核状态筛选字段 }, departments: [], types: [], @@ -513,6 +547,10 @@ { label: "处理中", value: "3" }, { label: "已完成", value: "4" }, { label: "已完结", value: "5" }, ], reviewStatuses: [ { label: "否", value: 0 }, { label: "是", value: 1 } ], tableData: [], option: { @@ -532,22 +570,30 @@ page: true, column: [ // { label: "序号", prop: "id", width: 70 }, { label: "工单编号", prop: "orderNumber", width: 150, overHidden: true, tooltip: true }, { label: "工单名称", prop: "orderName", width: 170, overHidden: true, tooltip: true }, { label: "所属单位", prop: "department", width: 100, overHidden: true, tooltip: true }, { label: "工单编号", prop: "orderNumber", width: 120, overHidden: true, tooltip: true }, { label: "工单名称", prop: "orderName", width: 150, overHidden: true, tooltip: true }, { label: "所属单位", prop: "department", overHidden: true, tooltip: true }, { label: "发起时间", prop: "startTime", width: 160 }, { label: "关联算法", prop: "aiType", width: 160, overHidden: true, tooltip: true }, { label: "工单类型", prop: "type", width: 120, overHidden: true, tooltip: true }, { label: "关联算法", prop: "aiType", width: 150, overHidden: true, tooltip: true }, { label: "工单类型", prop: "type", width: 130, overHidden: true, tooltip: true }, { label: "工单内容", prop: "content", slot: true, width: 190, width: 152, overHidden: true }, { label: "创建人", prop: "creator", width: 100 }, { label: "处理人", prop: "handler", width: 100 }, { label: "工单状态", prop: "status", slot: true, width: 90 } { slot: true, hide: false, label: "复核状态", prop: "isReview", width: 90, }, { label: "工单状态", prop: "status", slot: true, width: 90 }, ], }, page: { @@ -628,6 +674,12 @@ currentImageIndex: 1, // 新增:当前图片索引 totalTime: '', isShowInfo: false, // 配置时间选择器默认配置 datePickerDefaultVal: calculateDefaultRange(), // 复核弹窗 reCheckDialog: false, } }, created () { @@ -800,6 +852,14 @@ // 其它tab直接用接口返回的stepInfos return this.stepInfos.map(step => String(step.status)) }, showIsReviewText () { return (row) => { if (['4', '5'].includes(String(row.status))) return row.isReview === 1 ? '是' : '否' return '/' } } }, methods: { async loadAMapScripts () { @@ -937,7 +997,7 @@ try { const currentTab = this.tabs.find(tab => tab.name === this.activeTab) const params = { work_order_type_dict_key: this.filters.type || undefined, word_order_type: this.filters.type || undefined, status: currentTab?.name === 'myTickets' ? undefined : this.filters.status !== "" ? Number(this.filters.status) : currentTab?.value, @@ -952,6 +1012,7 @@ is_draft: currentTab?.name === 'myTickets' ? 1 : undefined, user_id: currentTab?.name === 'myTickets' ? this.userInfo.user_id : undefined, is_review: this.filters.isReview === '' ? undefined : this.filters.isReview, // 添加复核状态查询参数 } const response = await getList(params) @@ -997,6 +1058,7 @@ work_type: item.work_type !== undefined ? Number(item.work_type) : 0, // 保留work_type字段并转为数字 job_name: item.job_name || '', job_create_time: item.job_create_time || '', isReview: item.is_review, // 添加复核状态字段映射 } }) @@ -1038,12 +1100,14 @@ return } let [lng, lat] = this.disposeLocation(true, this.form) const submitData = { eventName: this.form.name, content: this.form.content, workType: "1", longitude: String(this.form.location[0]), latitude: String(this.form.location[1]), longitude: lng, latitude: lat, address: this.form.address, workOrderTypeDictKey: this.form.type, aiType: Array.isArray(this.form.algorithm) ? this.form.algorithm : [this.form.algorithm], // 传数组 @@ -1099,13 +1163,16 @@ if (!handlerValue || handlerValue === '未分配') { handlerValue = undefined } let [lng, lat] = this.disposeLocation(true, this.form) const submitData = { id: this.form.id, eventName: this.form.name || undefined, content: this.form.content || undefined, workType: "1", longitude: this.form.location?.[0] ? String(this.form.location[0]) : undefined, latitude: this.form.location?.[1] ? String(this.form.location[1]) : undefined, longitude: lng, latitude: lat, address: this.form.address || undefined, workOrderTypeDictKey: this.form.type || undefined, aiType: this.form.algorithm && this.form.algorithm.length > 0 @@ -1222,6 +1289,11 @@ handleTabChange (tab) { this.activeTab = tab.props?.name || tab.name const isReview = this.findObject(this.option.column, 'isReview') // 根据条件切换显隐 isReview.hide = !['all', 'completed', 'myTickets'].includes(this.activeTab) this.handleReset() this.page.currentPage = 1 this.fetchTableData() @@ -1242,6 +1314,7 @@ dateRange: [], status: "", algorithm: "", // 重置时清空算法筛选 isReview: "", // 重置时清空复核状态 } this.page.currentPage = 1 this.fetchTableData() @@ -1318,7 +1391,7 @@ const steps = Array.isArray(stepResponse.data.data) ? stepResponse.data.data : stepResponse.data.data?.steps || [] const finishedStep = steps.find(s => String(s.status) === '5') const finishedStep = steps.find(s => String(s.status) === '4') this.totalTime = finishedStep && finishedStep.total_time ? finishedStep.total_time : '' if (this.activeTab !== 'myTickets') { this.stepInfos = steps.map(step => ({ @@ -1641,6 +1714,13 @@ this.$message.warning('请先填写事件处理详情') return } // 检查图片上传 if (!this.currentDetail.photos || this.currentDetail.photos.length === 0) { this.$message.warning('请选择上传图片') this.completeLoading = false return } const data = { id: this.currentDetail.id, @@ -1735,11 +1815,12 @@ isPass: 0, // 0 表示通过 eventName: this.currentDetail.orderName, // 工单名称 eventNum: this.currentDetail.orderNumber, workOrderTypeDictKey: this.currentDetail.type, // 工单类型 workOrderTypeDictKey: this.currentDetail.type, // 直接使用原始的 dict_key content: this.currentDetail.content, // 使用 content 替代原来的 remark createDept: this.dispatchForm.department, // 派发部门 ID updateUser: this.dispatchForm.handler, // 处理人 ID } const file = this.currentDetail.file || null // 如果没有文件,则为 null @@ -1873,13 +1954,19 @@ photos: [], } this.form.location = Array.isArray(row.location) && row.location.length >= 2 ? [ Number(row.location[0]), Number(row.location[1]), let curLocation = [] if (Array.isArray(row.location) && row.location.length >= 2) { let [lng, lat] = this.disposeLocation(false, row) curLocation = [ lng, lat, row.location[2] || row.address || '' ] : [] } this.form.location = curLocation this.form.address = this.form.location[2] || '' // 设置地图中心点 if (Array.isArray(this.form.location) && this.form.location.length >= 2) { @@ -1907,16 +1994,6 @@ }) this.dialogVisible = true }, // 添加数据监听以确保算法数据的响应性 watch: { 'form.algorithm': { handler (newVal) { console.log('算法值变化:', newVal) }, deep: true } }, // 添加删除方法 @@ -2195,6 +2272,73 @@ if (lastDot === -1) return url return url.slice(0, lastDot) + '_show' + url.slice(lastDot) }, /** * 坐标转换方法处理 * @param goWgs84 是否由国测转换到84,默认false----由84转换到国测 */ disposeLocation (goWgs84 = false, data) { let lng, lat if (goWgs84) { lng = data.location?.[0] ? String(data.location[0]) : undefined lat = data.location?.[1] ? String(data.location[1]) : undefined if (lng && lat) { [lng, lat] = gcj02ToWgs84(Number(lng), Number(lat)) } } else { lng = Number(data.location[0]) lat = Number(data.location[1]) if (lng && lat) { [lng, lat] = wgs84ToGcj02(Number(lng), Number(lat)) } } return [String(lng), String(lat)] }, // 复核按钮 reCheck (row) { this.reCheckData = row this.reCheckDialog = true }, // 复核确认框按钮事件 reCheckConfirm (key) { const that = this if (key == 1) { getReviewById(this.reCheckData.id).then(res => { this.reCheckDialog = false this.page.currentPage = 1 this.fetchTableData() this.fetchTabCounts() }) } else { const loading = ElLoading.service({ lock: true, text: '复核任务创建中……', background: 'rgba(0, 0, 0, 0.7)', }) // 获取时间的接口 getCreateEventJob(that.reCheckData.id).then(res => { loading.close() ElMessageBox.confirm(`预计复核执行时间为${res.data.data}`, '提示', { confirmButtonText: '确定', showCancelButton: false, // 关键配置 type: 'warning' }).then(() => { this.reCheckDialog = false this.page.currentPage = 1 this.fetchTableData() this.fetchTabCounts() }) }) } } }, } </script> @@ -2722,6 +2866,13 @@ } } .re-check-dialog { :deep(.el-dialog__body) { padding: 0; background-color: #f5f7fa; } } .review-container { position: relative;