From cf55e57ac8e892c321cc9eeb79e14b39e7d80e82 Mon Sep 17 00:00:00 2001
From: chenyao <1219716595@qq.com>
Date: Fri, 28 Nov 2025 16:34:19 +0800
Subject: [PATCH] Merge branch 'feature/v8.0/8.0.3' into prod
---
src/views/tickets/ticket.vue | 4325 ++++---------
src/views/tickets/ticketComponent/CreateTicketDialog.vue | 710 ++
src/views/resource/components/spotDetails.vue | 27
src/views/tickets/ticketComponent/DispatchDialog.vue | 244
src/views/wel/components/proportionStatic.vue | 42
src/views/system/user.vue | 12
src/views/layerManagement/components/leftList.vue | 46
src/views/tickets/component/SearchBox.vue | 313 +
src/api/home/index.js | 8
src/views/wel/components/backlog.vue | 2
src/api/tickets/orderLog.js | 8
src/utils/date.js | 27
src/views/job/components/DeviceJobDetails.vue | 1046 +-
src/views/layerManagement/index.vue | 111
src/views/tickets/component/AddEditDetails.vue | 550 +
src/views/layerManagement/components/rightEdit.vue | 80
src/views/tickets/ticketComponent/RecheckDialog.vue | 117
src/views/layerManagement/components/folderFile.vue | 23
src/views/tickets/ticket-copy.vue | 4069 +++++++++++++
src/styles/element-ui.scss | 30
src/views/authority/role.vue | 14
/dev/null | 1402 ----
src/api/tickets/ticket.js | 10
src/views/tickets/orderLog.vue | 2111 +-----
src/views/tickets/ticketComponent/TicketDetailDialog.vue | 1439 ++++
src/views/tickets/orderLogCopy.vue | 1666 +++++
src/views/dataCenter/dataCenter.vue | 23
src/assets/images/layerManagement/closeBtn.svg | 24
28 files changed, 11,720 insertions(+), 6,759 deletions(-)
diff --git a/src/api/home/index.js b/src/api/home/index.js
index 3b7e45f..0715709 100644
--- a/src/api/home/index.js
+++ b/src/api/home/index.js
@@ -6,6 +6,14 @@
method: 'get',
});
};
+// 大屏首页=>事件状态数量
+export const getEventStatusNum = data => {
+ return request({
+ url: '/drone-device-core/jobEvent/eventStatusNum',
+ method: 'post',
+ data,
+ })
+}
// 事件概况分类数
export const getJobEventByStatus = data => {
return request({
diff --git a/src/api/tickets/orderLog.js b/src/api/tickets/orderLog.js
index 037bd05..5e53e5f 100644
--- a/src/api/tickets/orderLog.js
+++ b/src/api/tickets/orderLog.js
@@ -68,6 +68,14 @@
});
};
+// 智飞工单 统计数替换上面接口
+// export const jobStatusNum = (data) => {
+// return request({
+// url: '/drone-device-core/wayline/orderLog/jobStatusNum',
+// method: 'post',
+// data
+// });
+// };
// 草稿转发布
export const userPublish = (id) => {
diff --git a/src/api/tickets/ticket.js b/src/api/tickets/ticket.js
index a8cf0cb..139345a 100644
--- a/src/api/tickets/ticket.js
+++ b/src/api/tickets/ticket.js
@@ -73,14 +73,20 @@
}
// 新增接口:获取状态统计数据
-export const getstatusCount = (params) => {
+export const getstatusCount1 = (params) => {
return request({
url: '/drone-device-core/jobEvent/getstatusCount',
method: 'get',
params,
})
}
-
+export const getstatusCount = (data) => {
+ return request({
+ url: '/drone-device-core/jobEvent/getstatusCount',
+ method: 'post',
+ data,
+ })
+}
export const getStepInfo = (eventNum) => {
return request({
url: '/drone-device-core/jobEvent/getStepInfo',
diff --git a/src/assets/images/layerManagement/closeBtn.svg b/src/assets/images/layerManagement/closeBtn.svg
new file mode 100644
index 0000000..7343b99
--- /dev/null
+++ b/src/assets/images/layerManagement/closeBtn.svg
@@ -0,0 +1,24 @@
+<svg width="135" height="52" viewBox="0 0 135 52" fill="none" xmlns="http://www.w3.org/2000/svg">
+<g id="Group 1321316178">
+<g id="Rectangle 34624972" filter="url(#filter0_d_2725_2773)">
+<rect x="5" width="125" height="39" rx="4" fill="#FF9011"/>
+</g>
+<path id="关闭" d="M76.16 21.406H71.442C72.506 23.044 74.256 24.206 76.664 24.724C76.258 25.102 75.74 25.872 75.488 26.362C73.164 25.718 71.526 24.486 70.35 22.694C69.538 24.108 67.886 25.452 64.68 26.432C64.456 26.054 63.896 25.326 63.448 24.934C66.85 23.954 68.264 22.68 68.796 21.406H63.868V19.754H69.132V19.614V17.85H64.792V16.198H67.116C66.85 15.554 66.346 14.728 65.87 14.112L67.27 13.412C67.886 14.112 68.474 15.064 68.74 15.722L67.886 16.198H71.26C71.778 15.372 72.324 14.238 72.66 13.384L74.48 13.986C74.046 14.742 73.556 15.526 73.108 16.198H75.432V17.85H71.092V19.642V19.754H76.16V21.406ZM81.298 15.54L79.898 16.464C79.604 15.834 78.82 14.896 78.176 14.224L79.478 13.398C80.15 14.014 80.948 14.91 81.298 15.54ZM77.966 26.432V16.73H79.632V26.432H77.966ZM87.892 19.516H86.296V23.478C86.296 24.318 86.128 24.738 85.54 25.004C84.98 25.256 84.154 25.298 83.034 25.298C82.964 24.822 82.726 24.136 82.502 23.688C83.216 23.716 84.014 23.716 84.252 23.716C84.49 23.702 84.574 23.646 84.574 23.436V20.748C83.58 22.022 82.292 23.1 80.864 23.912C80.626 23.562 80.08 22.96 79.73 22.68C81.242 21.91 82.628 20.748 83.552 19.516H80.36V17.934H84.574V16.128H86.296V17.934H87.892V19.516ZM90.104 14.028V24.654C90.104 25.452 89.95 25.844 89.446 26.11C88.956 26.362 88.242 26.404 87.22 26.39C87.164 25.97 86.94 25.298 86.73 24.892C87.304 24.92 87.99 24.92 88.186 24.92C88.396 24.906 88.466 24.836 88.466 24.626V15.568H81.844V14.028H90.104Z" fill="white"/>
+<g id="Frame">
+<path id="Vector" d="M54.4599 25.1179C54.2915 25.1179 54.1232 25.0537 53.9946 24.9251L44.0678 14.9981C43.8107 14.7413 43.8107 14.3244 44.0678 14.0676C44.3249 13.8105 44.7413 13.8105 44.9983 14.0676L54.9252 23.9946C55.1822 24.2514 55.1822 24.6683 54.9252 24.9251C54.7967 25.0537 54.6283 25.1179 54.4599 25.1179Z" fill="white"/>
+<path id="Vector_2" d="M44.5331 25.1179C44.3647 25.1179 44.1963 25.0537 44.0678 24.9251C43.8107 24.6683 43.8107 24.2514 44.0678 23.9946L53.9946 14.0676C54.2517 13.8105 54.6681 13.8105 54.9252 14.0676C55.1822 14.3244 55.1822 14.7413 54.9252 14.9981L44.9983 24.9251C44.8698 25.0537 44.7014 25.1179 44.5331 25.1179Z" fill="white"/>
+</g>
+</g>
+<defs>
+<filter id="filter0_d_2725_2773" x="1" y="0" width="133" height="47" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
+<feFlood flood-opacity="0" result="BackgroundImageFix"/>
+<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
+<feOffset dy="4"/>
+<feGaussianBlur stdDeviation="2"/>
+<feComposite in2="hardAlpha" operator="out"/>
+<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.25 0"/>
+<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_2725_2773"/>
+<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_2725_2773" result="shape"/>
+</filter>
+</defs>
+</svg>
diff --git a/src/styles/element-ui.scss b/src/styles/element-ui.scss
index 15e48ed..ffb58cb 100644
--- a/src/styles/element-ui.scss
+++ b/src/styles/element-ui.scss
@@ -184,25 +184,37 @@
.ztzf-layer-search {
color: #fff !important;
background-color: transparent !important;
+
.el-select__wrapper {
background-color: transparent !important;
}
+
.el-select__popper.el-popper {
background-color: transparent !important;
}
+
.el-popper.is-light {
background-color: transparent !important;
}
- .el-select__placeholder {
- &.is-transparent {
- color: rgba(255,255,255,0.37) !important;
- }
-
- &.el-select__selected-item {
- color: #ffffff;
- }
- }
+ .el-select__placeholder {
+ &.is-transparent {
+ color: rgba(255,255,255,0.37) !important;
+ }
+
+ &.is-disabled {
+ color: rgba(255,255,255,0.37) !important;
+ }
+ }
+
+ .el-select__selected-item {
+ color: #ffffff !important;
+
+ &.is-disabled {
+ color: rgba(255,255,255,0.37) !important;
+ z-index: 99;
+ }
+ }
}
.ztzf-select-popper.el-select__popper {
border:#2D2D2D !important;
diff --git a/src/utils/date.js b/src/utils/date.js
index 35d9eb7..0e22793 100644
--- a/src/utils/date.js
+++ b/src/utils/date.js
@@ -1,3 +1,11 @@
+import dayjs from 'dayjs'
+import 'dayjs/locale/zh-cn'
+import weekday from 'dayjs/plugin/weekday'
+
+// 配置 dayjs
+dayjs.locale('zh-cn')
+dayjs.extend(weekday)
+
export const calcDate = (date1, date2) => {
let date3 = date2 - date1;
@@ -56,3 +64,22 @@
export function dateNow() {
return dateFormat(new Date(), 'yyyyMMddhhmmss');
}
+
+export const getDateRange = (unit, formatStr = 'YYYY-MM-DD HH:mm:ss') => {
+ const todayDate = dayjs()
+ let returnArr = [dayjs(), todayDate]
+ if (unit === 'today') {
+ returnArr = [dayjs(), todayDate]
+ }
+ if (unit === 'week') {
+ returnArr = [dayjs().weekday(0), todayDate]
+ }
+ if (unit === 'month') {
+ returnArr = [dayjs().startOf('month'), todayDate]
+ }
+ if (unit === 'year') {
+ returnArr = [dayjs().startOf('year'), todayDate]
+ }
+ returnArr = [returnArr[0].startOf('day').format(formatStr), returnArr[1].endOf('day').format(formatStr)]
+ return returnArr
+}
diff --git a/src/views/authority/role.vue b/src/views/authority/role.vue
index 9082ed2..85811e3 100644
--- a/src/views/authority/role.vue
+++ b/src/views/authority/role.vue
@@ -24,6 +24,11 @@
:props="props">
</el-tree>
</el-tab-pane>
+ <el-tab-pane label="大屏菜单">
+ <el-tree :data="menuGrantListOld" show-checkbox node-key="id" ref="treeMenuApp" :default-checked-keys="menuTreeObjOld"
+ :props="props">
+ </el-tree>
+ </el-tab-pane>
<!-- <el-tab-pane label="数据权限">
<el-tree
:data="dataScopeGrantList"
@@ -82,11 +87,13 @@
},
menuGrantList: [],
menuGrantListApp: [],
+ menuGrantListOld: [],
dataScopeGrantList: [],
apiScopeGrantList: [],
apiGrantList: [],
menuTreeObj: [],
menuTreeObjApp: [],
+ menuTreeObjOld: [],
dataScopeTreeObj: [],
apiScopeTreeObj: [],
selectionList: [],
@@ -350,6 +357,13 @@
this.box = true
})
})
+ grantTree({sysType: 2}).then(res => {
+ this.menuGrantListOld = res.data.data.menu
+ getRole(this.ids).then(res => {
+ this.menuTreeObjOld = res.data.data.menu
+ this.box = true
+ })
+ })
},
handleDelete () {
if (this.selectionList.length === 0) {
diff --git a/src/views/dataCenter/dataCenter.vue b/src/views/dataCenter/dataCenter.vue
index 157b928..95d19e5 100644
--- a/src/views/dataCenter/dataCenter.vue
+++ b/src/views/dataCenter/dataCenter.vue
@@ -205,7 +205,7 @@
class="videoItem"
ref="videoRefs"
controls
- autoplay
+
:src="dialogDetailList.link"
></video>
<!-- 地图 -->
@@ -308,7 +308,9 @@
class="videoBox"
ref="videoRefs"
controls
- autoplay
+ preload="auto"
+ @play="handleVideoPlay"
+ @ended="handleVideoEnded(index)"
:src="currentVideoUrl"
></video>
</div>
@@ -364,6 +366,23 @@
const currentAreaPosition = ref({ height: 1987280, latitude: 27.636112, longitude: 115.732975 });
let handler = null;
const switchFolders = ref(false)
+// 视频播放事件处理
+const handleVideoPlay = (event) => {
+ if (event.target.playbackRate !== 0.75) {
+ event.target.playbackRate = 0.75
+
+ }
+}
+const handleVideoEnded = index => {
+ // 获取当前视频
+ const video = videoRefs.value[index]
+
+ // 重置视频播放时间为 0
+ video.currentTime = 0
+
+ // 重新加载视频
+ video.load()
+}
function bytesToMB(bytes, decimalPlaces = 2) {
if (typeof bytes !== 'number' || bytes < 0) return '0';
return (bytes / 1048576).toFixed(decimalPlaces) + ' MB';
diff --git a/src/views/job/components/DeviceJobDetails.vue b/src/views/job/components/DeviceJobDetails.vue
index 69e6e52..8ac0999 100644
--- a/src/views/job/components/DeviceJobDetails.vue
+++ b/src/views/job/components/DeviceJobDetails.vue
@@ -1,148 +1,150 @@
<!-- 历史任务详情 -->
<template>
- <el-dialog class="ztzf-dialog" append-to-body modal-class="detailsOfHistoricalTasks" v-model="isShow" title="历史任务详情"
- :width="pxToRem(1800)" :close-on-click-modal="false" :destroy-on-close="true">
- <div class="content">
- <div class="contentLeft">
- <div class="machineTableDetailsTitle"><img src="/src/assets/images/task/sign.svg" alt=""><span>详情</span></div>
- <div class="infoBox">
- <div class="itemBoxLeft">
- <div v-for="(item, index) in infoList" :key="index" class="itemCon">
- <div class="itemBox">
- <div class="itemTitle">{{ item.name }}:</div>
- <div truncated class="itemValue" v-if="item.name !== '飞行事件' && item.name !== '任务执行次数'">{{
- item.value ? item.value : '--' }}</div>
- <div class="itemValue" v-if="item.name === '任务执行次数'">{{ item.value ? item.value : '0'
- }}次</div>
- <!-- 飞行事件 -->
- <div class="flightEvents" v-if="item.name === '飞行事件'">
- <template v-if="flightEvents.length">
- <img v-for="(item, index) in flightEvents" alt="" :src="item.img"
- :title="item.name" :key="index"></img>
- </template>
- <div class="itemValue" v-else>--</div>
- </div>
- </div>
- </div>
- </div>
+ <el-dialog class="ztzf-dialog" append-to-body modal-class="detailsOfHistoricalTasks" v-model="isShow" title="历史任务详情"
+ :width="pxToRem(1800)" :close-on-click-modal="false" :destroy-on-close="true">
+ <div class="content">
+ <div class="contentLeft">
+ <div class="machineTableDetailsTitle"><img src="/src/assets/images/task/sign.svg" alt=""><span>详情</span></div>
+ <div class="infoBox">
+ <div class="itemBoxLeft">
+ <div v-for="(item, index) in infoList" :key="index" class="itemCon">
+ <div class="itemBox">
+ <div class="itemTitle">{{ item.name }}:</div>
+ <div truncated class="itemValue" v-if="item.name !== '飞行事件' && item.name !== '任务执行次数'">{{
+ item.value ? item.value : '--' }}</div>
+ <div class="itemValue" v-if="item.name === '任务执行次数'">{{ item.value ? item.value : '0'
+ }}次</div>
+ <!-- 飞行事件 -->
+ <div class="flightEvents" v-if="item.name === '飞行事件'">
+ <template v-if="flightEvents.length">
+ <img v-for="(item, index) in flightEvents" alt="" :src="item.img"
+ :title="item.name" :key="index"></img>
+ </template>
+ <div class="itemValue" v-else>--</div>
+ </div>
+ </div>
+ </div>
+ </div>
- </div>
- <JobRelatedEvents :jobTimes="jobTimes" :batchNo="batchNo" />
- <div class="devicetitle" v-if="isShow">
- <div>
- <img src="/src/assets/images/task/sign.svg" alt="">
- <p>
- 成果数据
- <span>{{ total }}</span>
- 个
- </p>
- </div>
- <div class="rightBox" v-if="total">
- <div class="downloadBtn" @click="htlsrwxq === 100 && downloadFun()">
- <el-progress v-if="htlsrwxq !== 100" :percentage="htlsrwxq" :show-text="false" striped striped-flow :duration="1" />
- <div class="downloadBtnText">
- <span v-if="htlsrwxq === 100">下载</span>
- <template v-else>
- 处理中<el-icon @click="cancelDownload"><CircleClose /></el-icon>
- </template>
- </div>
- </div>
- <div class="downloadBtn" @click="showAll = !showAll" v-if="total > 5">
- {{ showAll ? '收起' : '更多' }}
- </div>
- </div>
- </div>
- <div class="imgListBox">
- <!-- 图片显示 -->
- <template v-for="(item, index) in achievementList.slice(0, visibleCount)" :key="index">
- <div class="result-item">
- <el-checkbox v-model="item.checked" />
- <div class="itemName">{{ item.createTime }}</div>
- <!-- 正射 -->
- <el-image
- v-if="item.resultType === 4"
- :src="item.smallUrl"
- :preview-src-list="[item.showUrl]"
- fit="cover"
- preview-teleported
- />
- <!-- 全景 -->
- <img
- v-else-if="item.resultType === 5"
- class="quanjing"
- @click="clickpanorama(item)"
- :src="item?.smallUrl"
- alt=""
- />
- <!-- 视频 -->
- <div v-else-if="item.resultType === 1" class="videotime">
- <img
- class="videoDisplay"
- :src="convertVideoUrlToThumbnail(item.link)"
- alt=""
- @click="enterFullScreen(index)"
- />
- <img
- @click="enterFullScreen(index)"
- class="videobutton"
- src="@/assets/images/task/videoshow.png"
- alt=""
- />
- </div>
- <!-- 图片 -->
- <el-image
- v-else
- :src="item.smallUrl"
- :preview-src-list="[item.showUrl]"
- preview-teleported
- :initial-index="index"
- fit="cover"
- />
+ </div>
+ <JobRelatedEvents :jobTimes="jobTimes" :batchNo="batchNo" />
+ <div class="devicetitle" v-if="isShow">
+ <div>
+ <img src="/src/assets/images/task/sign.svg" alt="">
+ <p>
+ 成果数据
+ <span>{{ total }}</span>
+ 个
+ </p>
+ </div>
+ <div class="rightBox" v-if="total">
+ <div class="downloadBtn" @click="htlsrwxq === 100 && downloadFun()">
+ <el-progress v-if="htlsrwxq !== 100" :percentage="htlsrwxq" :show-text="false" striped striped-flow :duration="1" />
+ <div class="downloadBtnText">
+ <span v-if="htlsrwxq === 100">下载</span>
+ <template v-else>
+ 处理中<el-icon @click="cancelDownload"><CircleClose /></el-icon>
+ </template>
+ </div>
+ </div>
+ <div class="downloadBtn" @click="showAll = !showAll" v-if="total > 5">
+ {{ showAll ? '收起' : '更多' }}
+ </div>
+ </div>
+ </div>
+ <div class="imgListBox">
+ <!-- 图片显示 -->
+ <template v-for="(item, index) in achievementList.slice(0, visibleCount)" :key="index">
+ <div class="result-item">
+ <el-checkbox v-model="item.checked" />
+ <div class="itemName">{{ item.createTime }}</div>
+ <!-- 正射 -->
+ <el-image
+ v-if="item.resultType === 4"
+ :src="item.smallUrl"
+ :preview-src-list="[item.showUrl]"
+ fit="cover"
+ preview-teleported
+ />
+ <!-- 全景 -->
+ <img
+ v-else-if="item.resultType === 5"
+ class="quanjing"
+ @click="clickpanorama(item)"
+ :src="item?.smallUrl"
+ alt=""
+ />
+ <!-- 视频 -->
+ <div v-else-if="item.resultType === 1" class="videotime">
+ <img
+ class="videoDisplay"
+ :src="convertVideoUrlToThumbnail(item.link)"
+ alt=""
+ @click="enterFullScreen(index)"
+ />
+ <img
+ @click="enterFullScreen(index)"
+ class="videobutton"
+ src="@/assets/images/task/videoshow.png"
+ alt=""
+ />
+ </div>
+ <!-- 图片 -->
+ <el-image
+ v-else
+ :src="item.smallUrl"
+ :preview-src-list="[item.showUrl]"
+ preview-teleported
+ :initial-index="index"
+ fit="cover"
+ />
- <el-dialog
- class="ztzf-dialog"
- append-to-body
- modal-class="detailsOfHistoricalTasks"
- v-model="VideoShow"
- :width="pxToRem(1600)"
- :close-on-click-modal="false"
- :destroy-on-close="true"
- >
- <div class="fullscreen">
- <video
- ref="fullscreenVideo"
- class="fullscreen-video"
- :src="currentVideoUrl"
- :style="{ width: pxToRem(1600), height: '80vh' }"
- controls
- autoplay
- ></video>
- </div>
- </el-dialog>
- </div>
+ <el-dialog
+ class="ztzf-dialog"
+ append-to-body
+ modal-class="detailsOfHistoricalTasks"
+ v-model="VideoShow"
+ :width="pxToRem(1600)"
+ :close-on-click-modal="false"
+ :destroy-on-close="true"
+ >
+ <div class="fullscreen">
+ <video
+ ref="fullscreenVideo"
+ class="fullscreen-video"
+ :src="currentVideoUrl"
+ :style="{ width: pxToRem(1567), height: '80vh' }"
+ controls
+ preload="auto"
+ @play="handleVideoPlay"
+ @ended="handleVideoEnded"
+ ></video>
+ </div>
+ </el-dialog>
+ </div>
- </template>
+ </template>
- </div>
- <el-image-viewer
- v-if="showViewer"
- :url-list="previewUrls"
- :initial-index="activeIndex"
- @close="showViewer = false"
- />
- </div>
+ </div>
+ <el-image-viewer
+ v-if="showViewer"
+ :url-list="previewUrls"
+ :initial-index="activeIndex"
+ @close="showViewer = false"
+ />
+ </div>
- <div class="content-right" v-if="isShow">
- <DeviceJobDetailsMap
- :detailsData="detailsData"
- :yuanImages="yuanImages"
- @showImageeclick="showImageeclick"
- :jobId="props.jobId"
- />
- <div class="content-map-popups"></div>
- </div>
- </div>
- </el-dialog>
+ <div class="content-right" v-if="isShow">
+ <DeviceJobDetailsMap
+ :detailsData="detailsData"
+ :yuanImages="yuanImages"
+ @showImageeclick="showImageeclick"
+ :jobId="props.jobId"
+ />
+ <div class="content-map-popups"></div>
+ </div>
+ </div>
+ </el-dialog>
<!-- 全景360 -->
<PanoramaPopup
v-if="'全景'"
@@ -169,46 +171,66 @@
const VideoShow = ref(false)
const showAll = ref(false) // 是否展示全部
let loadingData
+const fullscreenVideo = ref(null)
+// 视频播放事件处理
+const handleVideoPlay = (event) => {
+ if (event.target.playbackRate !== 0.75) {
+ event.target.playbackRate = 0.75
+
+ }
+}
+const handleVideoEnded = index => {
+ // 获取当前视频
+ const video = fullscreenVideo.value
+
+ // 重置视频播放时间为 0
+ video.currentTime = 0
+
+ // 重新加载视频
+ video.load()
+}
// 原图
const yuanImages = ref([])
function convertVideoUrlToThumbnail(videoUrl) {
- // 检查是否是有效的视频URL
- if (!videoUrl || typeof videoUrl !== 'string') {
- return videoUrl
- }
- // 替换文件扩展名
- return videoUrl.replace(/\.mp4$/, '_small.jpg')
+ // 检查是否是有效的视频URL
+ if (!videoUrl || typeof videoUrl !== 'string') {
+ return videoUrl
+ }
+ // 替换文件扩展名
+ return videoUrl.replace(/\.mp4$/, '_small.jpg')
}
const infoList = ref([
- { name: '任务编号', value: '', field: 'job_info_num' },
- { name: '任务名称', value: '', field: 'name' },
- { name: '任务类型', value: '', field: 'industry_type_str' },
- { name: '飞行事件', value: '', field: 'event_number' },
- { name: '所属机巢', value: '', field: 'device_names' },
- { name: '所属部门', value: '', field: 'dept_name' },
- { name: '任务时间', value: '', field: 'cycle_time_value' },
- { name: '关联算法', value: '', field: 'ai_type_str' },
- { name: '任务描述', value: '', field: 'remark' },
+ { name: '任务编号', value: '', field: 'job_info_num' },
+ { name: '任务名称', value: '', field: 'name' },
+ { name: '任务类型', value: '', field: 'industry_type_str' },
+ { name: '飞行事件', value: '', field: 'event_number' },
+ { name: '所属机巢', value: '', field: 'device_names' },
+ { name: '所属部门', value: '', field: 'dept_name' },
+ { name: '任务时间', value: '', field: 'cycle_time_value' },
+ { name: '关联算法', value: '', field: 'ai_type_str' },
{ name: '自定义识别区', value: '', field: 'enable_custom_area' },
- // { name: '任务执行次数', value: '', field: 'job_num' },
+ { name: '创建人', value: '', field: 'creator_name' },
+ { name: '任务描述', value: '', field: 'remark' },
+ // { name: '任务执行次数', value: '', field: 'job_num' },
])
// 机巢状态
const flystatus = ref('')
const detailsData = ref({
- id: null,
- remark: null,
- is_monitoring: null,
- industry_type_str: null,
- area_code: null,
- ai_type_str: null,
- begin_time: null,
- end_time: null,
- device_names: null,
- create_time: null,
- name: null,
- event_number: null,
- creator_name: null,
- way_lines: [],
+ id: null,
+ remark: null,
+ is_monitoring: null,
+ industry_type_str: null,
+ area_code: null,
+ ai_type_str: null,
+ enable_custom_area:null,
+ begin_time: null,
+ end_time: null,
+ device_names: null,
+ create_time: null,
+ name: null,
+ event_number: null,
+ creator_name: null,
+ way_lines: [],
})
// 任务成果
const total = ref(0)
@@ -218,10 +240,10 @@
provide('wayLineJodInfoId', wayLineJodInfoId)
// 可见数量
const visibleCount = computed(() =>
- showAll.value ? achievementList.value.length : Math.min(5, achievementList.value.length)
+ showAll.value ? achievementList.value.length : Math.min(5, achievementList.value.length)
)
const getAchievement = () => {
- if (!props.jobId) return
+ if (!props.jobId) return
const attachmentsParams = {
'wayLineJobId': props.jobId,
@@ -230,83 +252,83 @@
current: 1,
size: 2000,
}
- const pageParams = {
- current: 1,
- size: 2000,
- }
+ const pageParams = {
+ current: 1,
+ size: 2000,
+ }
aiImagesPage(attachmentsParams ).then(res => {
- achievementList.value = res.data.data.records.map(i => ({
- ...i,
- checked: false,
- smallUrl: i.resultType === 4 ? getzsSmallImg(i.link) : getSmallImg(i.link),
- showUrl: i.resultType === 4 ? getzsShowImg(i.link) : getShowImg(i.link),
- }))
- total.value = res.data.data.total
- // console.log('成果数据', res.data.data)
- })
+ achievementList.value = res.data.data.records.map(i => ({
+ ...i,
+ checked: false,
+ smallUrl: i.resultType === 4 ? getzsSmallImg(i.link) : getSmallImg(i.link),
+ showUrl: i.resultType === 4 ? getzsShowImg(i.link) : getShowImg(i.link),
+ }))
+ total.value = res.data.data.total
+ // console.log('成果数据', res.data.data)
+ })
}
const jumpMore = () => {
- ElMessage.warning('正在加急开发中...')
+ ElMessage.warning('正在加急开发中...')
}
const flightEvents = ref([])
const jobTimes = ref([])
const getDetails = () => {
- const params = {
- wayLineJobInfoId: props.wayLineJodInfoId,
- waylineJobId: props.waylineJobId,
- batchNo: props.batchNo
- }
- getJobDetails(params).then(res => {
- detailsData.value = res.data.data
- jobTimes.value = res.data.data.job_times
- infoList.value.forEach(item => {
- if (item.name === '任务频次') {
- const { rep_rule_type = '', rep_rule_val = '' } = detailsData?.value || {}
- item.value = rep_rule_type + ' -- ' + rep_rule_val
- } else {
- item.value = detailsData.value?.[item.field] || '--'
- }
- if (item.name === '飞行事件') {
- const { action_modes = [] } = detailsData?.value || {}
- flightEvents.value = action_modes.flatMap(({ actionActuatorFunc }) =>
- droneEventList.filter(({ value }) => actionActuatorFunc === value)
- )
- }
+ const params = {
+ wayLineJobInfoId: props.wayLineJodInfoId,
+ waylineJobId: props.waylineJobId,
+ batchNo: props.batchNo
+ }
+ getJobDetails(params).then(res => {
+ detailsData.value = res.data.data
+ jobTimes.value = res.data.data.job_times
+ infoList.value.forEach(item => {
+ if (item.name === '任务频次') {
+ const { rep_rule_type = '', rep_rule_val = '' } = detailsData?.value || {}
+ item.value = rep_rule_type + ' -- ' + rep_rule_val
+ } else {
+ item.value = detailsData.value?.[item.field] || '--'
+ }
+ if (item.name === '飞行事件') {
+ const { action_modes = [] } = detailsData?.value || {}
+ flightEvents.value = action_modes.flatMap(({ actionActuatorFunc }) =>
+ droneEventList.filter(({ value }) => actionActuatorFunc === value)
+ )
+ }
if (item.name === '自定义识别区') {
item.value = res.data.data.enable_custom_area ? '是': '否'
}
- getJobsAllFiles({
- wayLineJobId: detailsData.value.way_lines.map(item => item.job_id).join(','),
- resultTypes: [0, 2, 4, 5],
- }).then(result => {
- if (result.data.code !== 200) return
- yuanImages.value = result.data.data.records
+ getJobsAllFiles({
+ wayLineJobId: detailsData.value.way_lines.map(item => item.job_id).join(','),
+ resultTypes: [0, 2, 4, 5],
+ }).then(result => {
+ if (result.data.code !== 200) return
+ yuanImages.value = result.data.data.records
- })
- })
- // flystatus.value = res.data.data.ai_type_str
- })
+ })
+ })
+ // flystatus.value = res.data.data.ai_type_str
+ })
}
// 播放
const videoRef = ref(null);
const currentVideoIndex = ref(-1)
-const fullscreenVideo = ref(null)
+
const enterFullScreen = (index) => {
- currentVideoIndex.value = index
- VideoShow.value = true
+ currentVideoIndex.value = index
+ VideoShow.value = true
}
// 计算当前视频URL
const currentVideoUrl = computed(() => {
- return achievementList.value[currentVideoIndex.value]?.link || ''
+ return achievementList.value[currentVideoIndex.value]?.link || ''
})
// 关闭弹窗处理
const handleClose = () => {
- if (fullscreenVideo.value) {
- fullscreenVideo.value.pause()
- fullscreenVideo.value.currentTime = 0
- }
+ if (fullscreenVideo.value) {
+ fullscreenVideo.value.pause()
+ fullscreenVideo.value.currentTime = 0
+ }
}
// 下载
const downloadFun = async () => {
@@ -334,353 +356,353 @@
const panoramaParamsShow = ref(false)
const panoramaParamsUrl = ref(null)
const clickpanorama = val => {
- panoramaParamsShow.value = true
- panoramaParamsUrl.value = val.link
+ panoramaParamsShow.value = true
+ panoramaParamsUrl.value = val.link
}
// 从地图点击图片预览
const showViewer = ref(false)
const activeIndex = ref(0)
const previewUrls = ref([])
const showImageeclick = (list, index, categoriestype) => {
- if (categoriestype === 5) {
- panoramaParamsShow.value = true
- panoramaParamsUrl.value = list[0]
- } else {
- previewUrls.value = list
- activeIndex.value = 0
- showViewer.value = true
- }
+ if (categoriestype === 5) {
+ panoramaParamsShow.value = true
+ panoramaParamsUrl.value = list[0]
+ } else {
+ previewUrls.value = list
+ activeIndex.value = 0
+ showViewer.value = true
+ }
}
// 取消下载
function cancelDownload() {
- cancelDownloadApi({ type:'htlsrwxq' }).then(res =>{
- ElMessage.success('取消成功')
- }).catch(e =>{
+ cancelDownloadApi({ type:'htlsrwxq' }).then(res =>{
+ ElMessage.success('取消成功')
+ }).catch(e =>{
EventBus.emit('useGlobalWS-messageHandler', {biz_code: 'DOWNLOAD_PROGRESS',data:{status: 'CANCELLED',type:'htlsrwxq'}})
})
}
onMounted(() => {
- getDetails()
- getAchievement()
+ getDetails()
+ getAchievement()
})
</script>
<style lang="scss">
.detailsOfHistoricalTasks {
- .el-dialog {
- --el-dialog-margin-top: 5vh;
- }
+ .el-dialog {
+ --el-dialog-margin-top: 5vh;
+ }
- .el-pagination {
- justify-content: center;
- padding: 20px;
- }
+ .el-pagination {
+ justify-content: center;
+ padding: 20px;
+ }
- .el-dialog__body {
- height: 80vh;
- }
+ .el-dialog__body {
+ height: 80vh;
+ }
}
</style>
<style lang="scss" scoped>
.content {
- display: flex;
- height: 100%;
-.contentLeft {
- margin-left: 35px;
- margin-right: 24px;
- width: 1150px;
- overflow: auto;
+ display: flex;
+ height: 100%;
+ .contentLeft {
+ margin-left: 35px;
+ margin-right: 24px;
+ width: 1150px;
+ overflow: auto;
- .machineTableDetailsTitle {
- margin-bottom: 10px;
- // background: url('/src/assets/images/task/detailtitle.png') no-repeat center;
- border-bottom: 2px solid #e4e7ed;
- background-size: 100% 100%;
- img{
- width: 15px;
- height: 15px;}
- span {
- display: inline-block;
- margin-left: 10px;
- font-size: 16px;
- // color: #0282ff;
- color: #303133;
- line-height: 20px;
- text-align: left;
- margin-bottom: 8px;
- }
- }
+ .machineTableDetailsTitle {
+ margin-bottom: 10px;
+ // background: url('/src/assets/images/task/detailtitle.png') no-repeat center;
+ border-bottom: 2px solid #e4e7ed;
+ background-size: 100% 100%;
+ img{
+ width: 15px;
+ height: 15px;}
+ span {
+ display: inline-block;
+ margin-left: 10px;
+ font-size: 16px;
+ // color: #0282ff;
+ color: #303133;
+ line-height: 20px;
+ text-align: left;
+ margin-bottom: 8px;
+ }
+ }
- .devicetitle {
- margin-bottom: 16px;
- // background: url('/src/assets/images/task/detailtitle.png') no-repeat center;
- border-bottom: 2px solid #e4e7ed;
- background-size: 100% 100%;
- display: flex;
- justify-content: space-between;
- align-content: center;
- .rightBox{
- display: flex;
- align-items: center;
- padding-right: 10px;
- gap: 0 10px;
+ .devicetitle {
+ margin-bottom: 16px;
+ // background: url('/src/assets/images/task/detailtitle.png') no-repeat center;
+ border-bottom: 2px solid #e4e7ed;
+ background-size: 100% 100%;
+ display: flex;
+ justify-content: space-between;
+ align-content: center;
+ .rightBox{
+ display: flex;
+ align-items: center;
+ padding-right: 10px;
+ gap: 0 10px;
- .downloadBtn{
- position: relative;
- display: flex;
- align-items: center;
- justify-content: center;
- height: 28px;
- padding: 0 15px;
- background: rgba(28, 92, 255, 0.14);
- border-radius: 4px 4px 4px 4px;
- border: 1px solid #1c5cff;
- cursor: pointer;
- color: #1C5CFF;
+ .downloadBtn{
+ position: relative;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ height: 28px;
+ padding: 0 15px;
+ background: rgba(28, 92, 255, 0.14);
+ border-radius: 4px 4px 4px 4px;
+ border: 1px solid #1c5cff;
+ cursor: pointer;
+ color: #1C5CFF;
- .downloadBtnText{
- display: flex;
- align-items: center;
- gap: 5px;
- z-index: 5;
- }
- .el-progress{
- position: absolute;
- left: 0;
- top: 0;
- width: 100%;
+ .downloadBtnText{
+ display: flex;
+ align-items: center;
+ gap: 5px;
+ z-index: 5;
+ }
+ .el-progress{
+ position: absolute;
+ left: 0;
+ top: 0;
+ width: 100%;
- :deep(){
- .el-progress-bar__outer{
- height: 28px !important;
- border-radius: 0 !important;
- background: transparent !important;
+ :deep(){
+ .el-progress-bar__outer{
+ height: 28px !important;
+ border-radius: 0 !important;
+ background: transparent !important;
- .el-progress-bar__inner{
- border-radius: 0 !important;
- }
- }
- }
- }
+ .el-progress-bar__inner{
+ border-radius: 0 !important;
+ }
+ }
+ }
+ }
- }
- }
- img{
- width: 15px;
- height: 15px;}
- p {
- display: inline-block;
- margin-left: 10px;
- font-size: 16px;
- color: #363636;
- line-height: 20px;
- text-align: left;
- margin-bottom: 8px;
+ }
+ }
+ img{
+ width: 15px;
+ height: 15px;}
+ p {
+ display: inline-block;
+ margin-left: 10px;
+ font-size: 16px;
+ color: #363636;
+ line-height: 20px;
+ text-align: left;
+ margin-bottom: 8px;
- span {
- font-size: 26px;
- color: #0282ff;
- font-weight: bold;
- }
- }
+ span {
+ font-size: 26px;
+ color: #0282ff;
+ font-weight: bold;
+ }
+ }
- .more {
- color: #2f9dff;
- font-size: 14px;
- padding-top: 5px;
- cursor: pointer;
- }
- }
+ .more {
+ color: #2f9dff;
+ font-size: 14px;
+ padding-top: 5px;
+ cursor: pointer;
+ }
+ }
- .infoBox {
- display: flex;
- justify-content: space-between;
+ .infoBox {
+ display: flex;
+ justify-content: space-between;
- .itemBoxLeft {
- flex: 1;
- display: grid;
- grid-template-columns: repeat(2, 1fr);
- column-gap: 10px;
- /* 只设置列间距 */
- row-gap: 0;
- /* 取消行间距 */
- padding: 10px;
- font-size: 14px;
+ .itemBoxLeft {
+ flex: 1;
+ display: grid;
+ grid-template-columns: repeat(2, 1fr);
+ column-gap: 10px;
+ /* 只设置列间距 */
+ row-gap: 0;
+ /* 取消行间距 */
+ padding: 10px;
+ font-size: 14px;
- .itemBox {
- display: flex;
- align-items: center;
- background: #fff;
- height: 43px;
- border-top: 2px solid #EFEFEF;
+ .itemBox {
+ display: flex;
+ align-items: center;
+ background: #fff;
+ height: 43px;
+ border-top: 2px solid #EFEFEF;
- }
+ }
- }
+ }
- .itemCon:nth-last-child(-n+2) {
- border-bottom: 2px solid #EFEFEF;
- }
+ .itemCon:nth-last-child(-n+2) {
+ border-bottom: 2px solid #EFEFEF;
+ }
- .itemBox:nth-last-child(-n+2) .itemTitle {
- border-bottom: 1px solid #EFEFEF;
- }
+ .itemBox:nth-last-child(-n+2) .itemTitle {
+ border-bottom: 1px solid #EFEFEF;
+ }
- .itemTitle {
- color: #0B1D38;
- width: 26%;
- text-align: right;
- background: #F3F6FF;
- height: 43px;
- line-height: 43px;
- padding-right: 10px;
- border-top: 1px solid #EFEFEF;
- }
+ .itemTitle {
+ color: #0B1D38;
+ width: 26%;
+ text-align: right;
+ background: #F3F6FF;
+ height: 43px;
+ line-height: 43px;
+ padding-right: 10px;
+ border-top: 1px solid #EFEFEF;
+ }
- .itemValue {
- font-size: 14px;
- color: #747e91;
- text-align: center;
- width: 74%;
+ .itemValue {
+ font-size: 14px;
+ color: #747e91;
+ text-align: center;
+ width: 74%;
- }
+ }
- .flightEvents {
- display: flex;
- flex-wrap: wrap;
- gap: 10px 10px;
- width: 74%;
- justify-content: center;
+ .flightEvents {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 10px 10px;
+ width: 74%;
+ justify-content: center;
- img {
- width: 30px;
- height: 30px;
- }
- }
+ img {
+ width: 30px;
+ height: 30px;
+ }
+ }
- }
+ }
- .imgListBox {
- display: grid;
- grid-template-columns: repeat(5, 1fr);
- gap: 10px;
- margin-bottom: 49px;
+ .imgListBox {
+ display: grid;
+ grid-template-columns: repeat(5, 1fr);
+ gap: 10px;
+ margin-bottom: 49px;
- > * {
- width: 100%;
- height: 200px;
- position: relative;
- border-radius: 0px 0px 4px 4px;
- overflow: hidden;
- }
- :deep(.el-checkbox__inner) {
- width: 17px !important;
- height: 17px !important;
- background: rgba(37, 36, 36, 0.63) !important;
- border-radius: 4px 4px 4px 4px;
- border: 1px solid #ffffff !important;
- }
- .el-checkbox {
- position: absolute;
- top: 0;
- left: 6px;
- }
- :deep(.el-checkbox__inner:after) {
- position: absolute;
- left: 50% !important;
- top: 40% !important;
- transform: translate(-50%, -50%) rotate(45deg) !important;
- }
- :deep(.el-checkbox.is-checked .el-checkbox__inner) {
- background-color: #09297b !important;
- border-color: #1c5cff !important;
- }
- .el-image {
- width: 100%;
- height: 100%;
- cursor: pointer;
- position: relative;
- }
+ > * {
+ width: 100%;
+ height: 200px;
+ position: relative;
+ border-radius: 0px 0px 4px 4px;
+ overflow: hidden;
+ }
+ :deep(.el-checkbox__inner) {
+ width: 17px !important;
+ height: 17px !important;
+ background: rgba(37, 36, 36, 0.63) !important;
+ border-radius: 4px 4px 4px 4px;
+ border: 1px solid #ffffff !important;
+ }
+ .el-checkbox {
+ position: absolute;
+ top: 0;
+ left: 6px;
+ }
+ :deep(.el-checkbox__inner:after) {
+ position: absolute;
+ left: 50% !important;
+ top: 40% !important;
+ transform: translate(-50%, -50%) rotate(45deg) !important;
+ }
+ :deep(.el-checkbox.is-checked .el-checkbox__inner) {
+ background-color: #09297b !important;
+ border-color: #1c5cff !important;
+ }
+ .el-image {
+ width: 100%;
+ height: 100%;
+ cursor: pointer;
+ position: relative;
+ }
- .quanjing {
- width: 100%;
- height: 100%;
- object-fit: cover;
- cursor: pointer;
- position: relative;
- }
+ .quanjing {
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+ cursor: pointer;
+ position: relative;
+ }
- .videotime {
- width: 100%;
- height: 100%;
- position: relative;
+ .videotime {
+ width: 100%;
+ height: 100%;
+ position: relative;
- .videoDisplay {
- width: 100%;
- height: 100%;
- object-fit: cover;
- }
- }
- .itemName {
- position: absolute;
- bottom: 0;
- left: 0;
- height: 23px;
- width: 100%;
- background: rgba(22, 22, 22, 0.68);
- border-radius: 0px 0px 4px 4px;
- font-family: Source Han Sans CN, Source Han Sans CN;
- font-weight: 400;
- font-size: 14px;
- color: #ffffff;
- line-height: 23px;
- z-index: 9;
- padding: 0 6px;
- }
- }
+ .videoDisplay {
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+ }
+ }
+ .itemName {
+ position: absolute;
+ bottom: 0;
+ left: 0;
+ height: 23px;
+ width: 100%;
+ background: rgba(22, 22, 22, 0.68);
+ border-radius: 0px 0px 4px 4px;
+ font-family: Source Han Sans CN, Source Han Sans CN;
+ font-weight: 400;
+ font-size: 14px;
+ color: #ffffff;
+ line-height: 23px;
+ z-index: 9;
+ padding: 0 6px;
+ }
+ }
-.videobutton {
- position: absolute;
- top: 40%;
- left: 50%;
- transform: translate(-50%);
- cursor: pointer;
- width: 40px;
- height: 40px;
- display: inline-block;
- background: #999;
- border-radius: 50%;
- }
- .videoDisplay {
- display: flex;
- flex-wrap: wrap;
- width: 200px;
- height: 200px;
+ .videobutton {
+ position: absolute;
+ top: 40%;
+ left: 50%;
+ transform: translate(-50%);
+ cursor: pointer;
+ width: 40px;
+ height: 40px;
+ display: inline-block;
+ background: #999;
+ border-radius: 50%;
+ }
+ .videoDisplay {
+ display: flex;
+ flex-wrap: wrap;
+ width: 200px;
+ height: 200px;
- }
+ }
- .videotime {
- position: relative;
- }
+ .videotime {
+ position: relative;
+ }
- }
+ }
- .content-right {
- position: relative;
- width: 0;
- flex-grow: 1;
- background: #19ad8d;
- margin-right: 17px;
- overflow: hidden;
+ .content-right {
+ position: relative;
+ width: 0;
+ flex-grow: 1;
+ background: #19ad8d;
+ margin-right: 17px;
+ overflow: hidden;
- .content-map-popups {
- pointer-events: none;
+ .content-map-popups {
+ pointer-events: none;
- > * {
- pointer-events: auto !important;
- }
- }
- }
+ > * {
+ pointer-events: auto !important;
+ }
+ }
+ }
}
</style>
diff --git a/src/views/layerManagement/components/folderFile.vue b/src/views/layerManagement/components/folderFile.vue
index 3365405..eac8fde 100644
--- a/src/views/layerManagement/components/folderFile.vue
+++ b/src/views/layerManagement/components/folderFile.vue
@@ -49,13 +49,13 @@
<div class="title">{{ layerParams.fileType === 1 ? '识别区' : '禁飞区' }}总数量</div>
<div class="num">
- <span>{{ formData.total_count }}</span> 个
+ <span class="totalQuantity">{{ formData.total_count }}</span> 个
</div>
</div>
<div class="itemData">
<div class="title">{{ layerParams.fileType === 1 ? '识别区' : '禁飞区' }}总面积</div>
<div class="num">
- <span class="areaStyle">{{ formData.total_area }}</span> m²
+ <span class="areaStyle">{{ formData.total_area.toFixed(2) }}</span> m²
</div>
</div>
</div>
@@ -82,7 +82,7 @@
</template>
<script setup>
-const emit = defineEmits(['refreshMethod']);
+const emit = defineEmits(['refreshMethod','callParentMethod']);
import EventBus from '@/utils/eventBus';
import { ElMessage } from 'element-plus';
import dayjs from 'dayjs';
@@ -152,6 +152,7 @@
};
const cancelHandel = () => {
clearAllData();
+ emit('callParentMethod');
};
const submitHandle = () => {
if (!formData.value.name) {
@@ -185,6 +186,7 @@
EventBus.emit('gettreeDataApi');
emit('refreshMethod');
clearAllData();
+ emit('callParentMethod');
});
} else {
//文件夹编辑
@@ -192,6 +194,7 @@
ElMessage.success('编辑成功');
EventBus.emit('gettreeDataApi');
clearAllData();
+ emit('callParentMethod');
});
}
};
@@ -279,11 +282,17 @@
font-size: 14px;
color: #ffffff;
}
- .areaStyle {
+ .totalQuantity {
font-family: Source Han Sans CN, Source Han Sans CN;
- font-weight: bold;
- font-size: 20px;
- color: #FFFFFF;
+ font-weight: bold;
+ font-size: 20px;
+ }
+ .areaStyle {
+ font-family: Source Han Sans CN, Source Han Sans CN;
+ font-weight: bold;
+ font-size: 20px;
+ color: #FFFFFF;
+
}
}
.itemData {
diff --git a/src/views/layerManagement/components/leftList.vue b/src/views/layerManagement/components/leftList.vue
index 25a3eaf..9f320b4 100644
--- a/src/views/layerManagement/components/leftList.vue
+++ b/src/views/layerManagement/components/leftList.vue
@@ -44,7 +44,7 @@
<div
class="tableListBox"
v-loading="loading"
- element-loading-background="rgba(0, 0, 0, 0.5)"
+ element-loading-background="rgba(0, 0, 0, 0.2)"
element-loading-text="加载中..."
>
<el-tree
@@ -167,25 +167,28 @@
);
};
const selectAll = () => {
+ layerParams.value.currentLocationFolderId = null;
layerParams.value.isSingleLocating = false
if (treeRef.value) {
const allFenceKeys = getAllFenceKeys();
const allFenceNodes = treeData.value.flatMap(folder => folder.children);
-
if (checkedKeys.value.length === allFenceKeys.length) {
// 取消全选
treeRef.value.setCheckedKeys([]);
checkedKeys.value = [];
checkedNodes.value = [];
coverData.value = [];
+ emit('update:coverData', coverData.value);
} else {
// 全选
treeRef.value.setCheckedKeys(allFenceKeys);
checkedKeys.value = allFenceKeys;
checkedNodes.value = allFenceNodes;
coverData.value = allFenceNodes;
+ emit('update:coverData', coverData.value);
+
}
- emit('update:coverData', coverData.value);
+
}
};
// 定位按钮事件
@@ -402,7 +405,7 @@
left: 0;
height: calc(100% - 32px);
z-index: 99;
- width: 382px;
+ width: 332px;
overflow: hidden;
background: rgba(0,0,0,0.51);
backdrop-filter: blur(4px);
@@ -417,7 +420,7 @@
margin-top: 10px;
padding-right: 12px;
.inputName {
- width: 184px;
+ width: 140px;
height: 32px;
}
.searchAndReset {
@@ -426,34 +429,7 @@
justify-content: right;
align-items: center;
margin-right: 10px;
- .reset,
- .search {
- display: flex;
- align-items: center;
- justify-content: center;
- width: 46px;
- height: 32px;
- cursor: pointer;
- }
- .reset {
- margin-left: 12px;
- background: #0d2b38;
- border-radius: 4px 4px 4px 4px;
- border: 1px solid #84cbd3;
- font-weight: 400;
- font-size: 14px;
- color: #ffffff;
- }
-
- .search {
- background: #1e2d51;
- border-radius: 4px 4px 4px 4px;
- border: 1px solid #c5d6ff;
- font-weight: 400;
- font-size: 14px;
- color: #ffffff;
- }
}
}
.btnGroups {
@@ -471,8 +447,10 @@
flex-grow: 1;
padding-right: 12px;
:deep(.el-tree-node__content) {
- height: 35px !important;
- line-height: 35px !important;
+ height: 45px !important;
+ line-height: 45px !important;
+ // padding-left: 0 !important;
+ border-bottom: 1px dotted rgba(255,255,255,0.1);
}
.isTheTerritory {
diff --git a/src/views/layerManagement/components/rightEdit.vue b/src/views/layerManagement/components/rightEdit.vue
index 9e9dee6..5692638 100644
--- a/src/views/layerManagement/components/rightEdit.vue
+++ b/src/views/layerManagement/components/rightEdit.vue
@@ -1,5 +1,5 @@
<template>
- <div class="rightContainer">
+ <div class="rightContainer" >
<div class="contenttitle">{{ layerParams.fenceType === 1 ? '识别区' : '禁飞区' }}信息</div>
<div class="centerBox">
<div class="itemRow">
@@ -122,8 +122,10 @@
</div>
</div>
<div class="btnGroups">
- <img src="/src/assets/images/layerManagement/savebtn.svg" alt="" @click="submitHandle" />
- <img src="/src/assets/images/layerManagement/cancelbtn.svg" @click="cancelHandel" alt="" />
+ <img v-if="!layerParams.editingIsProhibited" src="/src/assets/images/layerManagement/savebtn.svg" alt="" @click="submitHandle" />
+ <img v-if="layerParams.editingIsProhibited" src="/src/assets/images/layerManagement/closeBtn.svg" @click="cancelHandel" alt="" />
+ <img v-else src="/src/assets/images/layerManagement/cancelbtn.svg" @click="cancelHandel" alt="" />
+
</div>
</div>
</template>
@@ -133,7 +135,7 @@
import dayjs from 'dayjs';
import { deitFenceApi, addFenceApi } from '@/api/layer/index';
import EventBus from '@/utils/eventBus';
-const emit = defineEmits(['callParentMethod']);
+const emit = defineEmits(['callParentMethod','update:loading']);
const layerParams = inject('layerParams');
const options = ref([]);
const timeValue = ref('');
@@ -150,6 +152,7 @@
geo_data: '',
});
+const loading = ref(false);
const detailData = ref(null);
const fenceArea = ref(0);
detailData.value = layerParams.value.editDetailData;
@@ -237,11 +240,16 @@
layerParams.value.editingIsProhibited = false;
};
const cancelHandel = () => {
+loading.value = false
+ emit('update:loading', false);
clearAllData();
emit('callParentMethod');
EventBus.emit('gettreeDataApi');
};
const submitHandle = () => {
+if(layerParams.value.crossSurface){
+ return ElMessage.warning('测区不支持交叉面')
+}
if (!formData.value.folder_id) {
ElMessage.error('请选择文件夹');
return;
@@ -258,6 +266,8 @@
ElMessage.error('请绘制区域');
return;
}
+ loading.value = true
+ emit('update:loading', true);
const params = {
folder_id: formData.value.folder_id,
name: formData.value.name,
@@ -277,22 +287,45 @@
}
if (layerParams.value.decideWhetherToAddOrEdit === 1) {
//围栏新增
- addFenceApi(params).then(res => {
- ElMessage.success('新增成功');
- EventBus.emit('gettreeDataApi');
- clearAllData();
- emit('callParentMethod');
- });
+ addFenceApi(params)
+ .then(res => {
+ if(res.data.code ===0 ){
+ ElMessage.success('新增成功');
+ EventBus.emit('gettreeDataApi');
+ loading.value = false;
+ emit('update:loading', false);
+ clearAllData();
+ emit('callParentMethod');
+ }
+ })
+ .catch(err => {
+ ElMessage.error('新增失败,请重试');
+ })
+ .finally(() => {
+ loading.value = false;
+ emit('update:loading', false);
+ });
} else if (layerParams.value.decideWhetherToAddOrEdit === 2) {
//围栏编辑
- deitFenceApi(params).then(res => {
- ElMessage.success('编辑成功');
- EventBus.emit('gettreeDataApi');
-
- clearAllData();
- emit('callParentMethod');
- });
- }
+ deitFenceApi(params)
+ .then(res => {
+ if(res.data.code === 0){
+ ElMessage.success('编辑成功');
+ EventBus.emit('gettreeDataApi');
+ clearAllData();
+ emit('callParentMethod');
+ loading.value = false;
+ emit('update:loading', false);
+ }
+ })
+ .catch(err => {
+ ElMessage.error('编辑失败,请重试');
+ })
+ .finally(() => {
+ loading.value = false;
+ emit('update:loading', false);
+ });
+ }
};
onMounted(() => {
// 获取判断状态(默认设为 1)
@@ -344,11 +377,17 @@
backdrop-filter: blur(4px);
border-radius: 8px 8px 8px 8px;
padding: 16px 12px;
-
+:deep(.el-select__wrapper.is-disabled .el-select__selected-item){
+color: rgba(255,255,255,0.37) !important;
+}
:deep(.el-textarea__inner) {
background: transparent !important;
- color: #ffffff !important;
+ color: #ffffff;
}
+ :deep(.el-textarea.is-disabled .el-textarea__inner){
+ color: var(--el-disabled-text-color) !important;
+
+}
.contenttitle {
font-family: Source Han Sans CN, Source Han Sans CN;
font-weight: bold;
@@ -420,6 +459,7 @@
height: 39px;
cursor: pointer;
}
+
}
}
</style>
diff --git a/src/views/layerManagement/index.vue b/src/views/layerManagement/index.vue
index c014986..506055e 100644
--- a/src/views/layerManagement/index.vue
+++ b/src/views/layerManagement/index.vue
@@ -8,8 +8,18 @@
</el-tabs>
</div>
<!-- 地图 -->
- <div class="mapContainer">
- <div class="tool-tip" v-if="layerParams.addNest">点击地图生成测绘区域</div>
+ <div class="mapContainer" v-loading="isPageLoading"
+ element-loading-background="rgba(0, 0, 0, 0.5)"
+ element-loading-text="加载中...">
+ <div class="tool-tip warning" v-show="isShowWaringTip">
+ <span class="icon">
+ <el-icon><WarningFilled /></el-icon>
+ </span>
+ <span>测区不支持交叉面</span>
+ </div>
+ <div class="tool-tip" v-if="!isShowWaringTip && layerParams.addNest">
+ {{ showToolTipText }}
+ </div>
<div id="layMap" class="ztzf-cesium"></div>
<leftList
v-if="
@@ -23,15 +33,17 @@
@update:editFolder="handleFolder"
:activeName="activeName"
@newFencesMethods="newFencesMethods"
- :reset-check="resetCheck"
+ :reset-check="resetCheck"
></leftList>
<rightEdit
v-if="layerParams.addNest || layerParams.editNest"
@callParentMethod="parentMethod"
+ @update:loading="handleLoadingChange"
></rightEdit>
<folderFile
@refreshMethod="refreshMethod"
:activeName="activeName"
+ @callParentMethod="parentMethod"
v-if="layerParams.addFolder || layerParams.editFolder"
></folderFile>
</div>
@@ -52,12 +64,14 @@
import * as Cesium from 'cesium';
import { PublicCesium } from '@/utils/cesium/publicCesium';
import _, { cloneDeep, throttle } from 'lodash';
-const isProd = import.meta.env.VITE_APP_ENV === 'production'
+
import { provide } from 'vue';
import { useStore } from 'vuex';
+const isProd = import.meta.env.VITE_APP_ENV === 'production'
const store = useStore();
const userInfo = computed(() => store.getters.userInfo);
const areaCode = userInfo.value?.detail?.areaCode || '';
+const showToolTipText = ref('点击地图生成测绘区域')
// 红色警告交叉面提示窗
const isShowWaringTip = ref(false);
const activeName = ref('自定义识别区');
@@ -81,6 +95,7 @@
type: '2',
},
];
+
// 如果是生产环境,只返回自定义识别区
if (isProd) {
return allTabs.filter(tab => tab.type === '0');
@@ -107,6 +122,7 @@
isDetailShow: false,
isSingleLocating:false , //是否是单个定位
currentLocationFolderId:null,
+ crossSurface:false,//是否交叉面
});
const resetCheck = ref(false);
const handleClick = tab => {
@@ -121,7 +137,7 @@
layerParams.value.addFolder = false
layerParams.value.editFolder = false
resetCheck.value = !resetCheck.value;
-
+
};
const refreshMethod = () => {
getdataFolderApi();
@@ -150,7 +166,7 @@
viewer.entities.removeAll();
layerParams.value.editDetailData = val;
drawPolygonExample.editThePatch(true);
- drawPolygonExample.drawTheArea(true);
+ drawPolygonExample.drawTheArea(true);
let geoDataArr = JSON.parse(val.geo_data);
if (geoDataArr.length > 0) {
geoDataArr.pop();
@@ -164,6 +180,12 @@
// 编辑文件夹
const handleFolder = val => {
parentMethod()
+ selectDataList.value = val.children;
+ if (selectDataList.value?.length > 0) {
+ loadDataToMap(selectDataList.value);
+ } else {
+ viewer.entities.removeAll();
+ }
layerParams.value.editFolderData = val;
};
// 新增开启绘制
@@ -199,7 +221,7 @@
console.warn(`数据 ${item.name} 坐标点不足,无法绘制`);
return;
}
-
+
tbJwdList.push(...positions);
let degreesArray = [];
positions.forEach(pos => {
@@ -214,20 +236,20 @@
border2: Cesium.Color.fromCssColorString('#ff0000'),
border3: Cesium.Color.fromCssColorString('#00ffea'),
};
- const fillColor = item.category_id === 1
- ? colorMap[1]
- : item.category_id === 2
- ? colorMap[2]
- : item.category_id === 3
- ? colorMap[3]
+ const fillColor = item.category_id === 1
+ ? colorMap[1]
+ : item.category_id === 2
+ ? colorMap[2]
+ : item.category_id === 3
+ ? colorMap[3]
: Cesium.Color.YELLOW.withAlpha(0.5);
-
- const borderColor = item.category_id === 1
- ? colorMap.border1
- : item.category_id === 2
- ? colorMap.border2
- : item.category_id === 3
- ? colorMap.border3
+
+ const borderColor = item.category_id === 1
+ ? colorMap.border1
+ : item.category_id === 2
+ ? colorMap.border2
+ : item.category_id === 3
+ ? colorMap.border3
: Cesium.Color.YELLOW;
viewer.entities.add({
id: `polygon_${item.id}`,
@@ -250,17 +272,17 @@
material:borderColor,
clampToGround: true,
},
- zIndex: 99,
+ zIndex: 99,
});
- });
+ });
if (!layerParams.value.isSingleLocating) {
- focusOnAllFeatures();
+ focusOnAllFeatures();
}
viewInstance.value?.addLeftClickEvent(null, handleFenceClick);
};
const focusOnAllFeatures = () => {
if (tbJwdList.length === 0 || !viewer) return;
-
+
// 判断是否有指定定位的文件夹
if (layerParams.value.currentLocationFolderId) {
// 筛选当前文件夹下的所有子节点坐标
@@ -270,7 +292,7 @@
const positions = parseGeoDataToPositions(item.geo_data, item.altitude);
return positions.length >= 3 ? positions : [];
});
-
+
if (targetFolderPositions.length > 0) {
const positionsData = targetFolderPositions.map(pos => [
pos.lng,
@@ -304,16 +326,16 @@
const handleFenceClick = movement => {
if (!viewer) return;
layerParams.value.editDetailData = null;
-
+
if(activeName.value !== '国土空间规划') {
- const pickedObjects = viewer.scene.drillPick(movement.position, 10);
+ const pickedObjects = viewer.scene.drillPick(movement.position, 10);
// 遍历所有被点击的实体,找到围栏类型(customType: 'fence_polygon')
let selectedEntity = null;
for (let i = 0; i < pickedObjects.length; i++) {
const pick = pickedObjects[i];
if (Cesium.defined(pick.id) && pick.id?.customType === 'fence_polygon') {
selectedEntity = pick.id;
- break;
+ break;
}
}
// viewer.entities.values.forEach(entity => {
@@ -338,9 +360,6 @@
layerParams.value.editingIsProhibited = true;
}
}
-}else {
-
-
}
};
@@ -379,13 +398,12 @@
};
// 编辑/绘制
const loadPlanarRoute = async (positions = null, save = false) => {
- isShowWaringTip.value = false;
if (positions) {
curPolygonPosition = positions.map(item => {
let cartographic = Cesium.Cartographic.fromCartesian(item);
let lng = Cesium.Math.toDegrees(cartographic.longitude); // 经度
let lat = Cesium.Math.toDegrees(cartographic.latitude); // 纬度
- let height = Cesium.Math.toDegrees(cartographic.height); //高度
+ let height = Cesium.Math.toDegrees(cartographic.height); //高度
return {
lng: _.round(lng, 6),
lat: _.round(lat, 6),
@@ -394,6 +412,15 @@
});
}
let polygon = curPolygonPosition.map(item => [item?.lng, item?.lat]);
+ if(polygon.length ===0){
+ isShowWaringTip.value = false
+ }
+ if(polygon.length > 0 && !isShowWaringTip.value){
+ showToolTipText.value = '测绘区域已绘制完成'
+ }else {
+ showToolTipText.value = '点击地图生成测绘区域'
+ }
+
if(polygon.length > 0){
polygon.push([curPolygonPosition[0]?.lng, curPolygonPosition[0]?.lat])
}
@@ -416,6 +443,7 @@
};
drawPolygonExample.subscribe('getShowWaringTip', data => {
isShowWaringTip.value = data;
+ layerParams.value.crossSurface = data
});
const throttleLoadPlanarRoute = throttle(loadPlanarRoute, 200);
drawPolygonExample.subscribe('getPolygonPositions', data => {
@@ -444,6 +472,11 @@
} else {
cesium.removeEventListener('contextmenu', preventDefault);
}
+};
+// 添加loading
+const isPageLoading = ref(false);
+const handleLoadingChange = (status) => {
+ isPageLoading.value = status;
};
provide('layerParams', layerParams);
onMounted(() => {
@@ -517,6 +550,18 @@
position: relative;
width: 100%;
height: 80vh;
+ .warning {
+ position: absolute;
+ top: 234px;
+ color: #fff;
+ background: rgba(140, 0, 0, 0.4) !important;
+
+ .icon {
+ display: flex;
+ align-items: center;
+ color: #ff1f1f;
+ }
+ }
#layMap {
width: 100%;
height: 100%;
diff --git a/src/views/resource/components/spotDetails.vue b/src/views/resource/components/spotDetails.vue
index 28eb3a8..dd0f1eb 100644
--- a/src/views/resource/components/spotDetails.vue
+++ b/src/views/resource/components/spotDetails.vue
@@ -81,14 +81,30 @@
</el-table-column>
<el-table-column label="操作" align="center" v-if="props.title === '图斑编辑'">
<template #default="scope">
- <span class="operationspan" @click="handleDelete(scope.row)">删除</span>
- <span
+ <!-- <span class="operationspan" @click="handleDelete(scope.row)">删除</span> -->
+ <el-button icon="el-icon-delete" link @click="handleDelete(scope.row)"></el-button>
+ <!-- <span
class="operationspan"
v-if="scope.row.is_exception == 2"
@click.stop="handleSelectionChange(scope.row)"
>
{{ isEditing && selectionIds === scope.row.id ? '取消编辑' : '编辑' }}
- </span>
+ </span> -->
+ <template v-if="scope.row.is_exception == 2">
+ <el-button
+ v-if="!isEditing || selectionIds !== scope.row.id"
+ icon="el-icon-edit"
+ link
+ @click.stop="handleSelectionChange(scope.row)"
+ />
+ <el-button
+ v-else
+ icon="el-icon-circle-close"
+ link
+ @click.stop="handleSelectionChange(scope.row)"
+
+ />
+ </template>
</template>
</el-table-column>
</el-table>
@@ -913,4 +929,9 @@
height: 100%;
width: 100%;
}
+.el-button {
+ padding: 0;
+ color: #fff;
+ width: 17px;
+}
</style>
diff --git a/src/views/system/user.vue b/src/views/system/user.vue
index 84b895f..a4782da 100644
--- a/src/views/system/user.vue
+++ b/src/views/system/user.vue
@@ -490,7 +490,7 @@
label: '所属部门',
prop: 'deptId',
type: 'tree',
- multiple: true,
+ multiple: false,
dicData: [],
// props: {
// label: 'title',
@@ -793,7 +793,7 @@
}
}
- row.deptId = func.join(row.deptId)
+ // row.deptId = func.join(row.deptId)
row.roleId = func.join(row.roleId)
row.postId = func.join(row.postId)
row.name = row.realName
@@ -836,7 +836,7 @@
}
}
- row.deptId = func.join(row.deptId)
+ // row.deptId = func.join(row.deptId)
row.roleId = func.join(row.roleId)
row.postId = func.join(row.postId)
row.name = row.realName
@@ -1061,9 +1061,9 @@
if (['edit', 'view'].includes(type)) {
getUser(this.form.id).then(res => {
this.form = res.data.data
- if (this.form.hasOwnProperty('deptId')) {
- this.form.deptId = func.split(this.form.deptId)
- }
+ // if (this.form.hasOwnProperty('deptId')) {
+ // this.form.deptId = func.split(this.form.deptId)
+ // }
if (this.form.hasOwnProperty('roleId')) {
this.form.roleId = func.split(this.form.roleId)
}
diff --git a/src/views/tickets/component/AddEditDetails.vue b/src/views/tickets/component/AddEditDetails.vue
new file mode 100644
index 0000000..e0c0330
--- /dev/null
+++ b/src/views/tickets/component/AddEditDetails.vue
@@ -0,0 +1,550 @@
+<template>
+ <el-dialog
+ class="ztzf-dialog-mange"
+ modal-class="add-edit-details"
+ v-model="isShowAddEditDetails"
+ :title="txtTitle"
+ width="70%"
+ :close-on-click-modal="false"
+ :destroy-on-close="true"
+ @close="cancel"
+ :before-close="handleBeforeClose"
+ >
+ <div class="add-edit-details">
+ <div class="process" v-if="txtTitle !=='新建工单'">
+ <div class="order_title">{{ formParams.name }}</div>
+ <div class="custom-steps-container">
+ <!-- 标题行 -->
+ <div class="steps-titles">
+ <div class="step-title" v-for="(record, index) in formParams.record_list" :class="{ active: record.user_id >= 0 }" :key="index">
+ {{ record.status_str }}
+ </div>
+ </div>
+ <!-- Element Steps 组件 -->
+ <el-steps :active="formParams.active" align-center class="custom-steps">
+ <el-step v-for="(record, index) in formParams.record_list" :key="index">
+ <template #description>
+ <span class="step-description" style="position: relative; display: inline-block">
+ {{ record.user_name }}
+ </span>
+ <span class="step-timer">
+ {{ record.interval_time_str }}
+ </span>
+ <div class="step-description">
+ {{ record.create_time_str }}
+ </div>
+ </template>
+ </el-step>
+ </el-steps>
+ </div>
+ </div>
+ <el-form ref="orderFormRef" :model="formParams" :rules="rules" label-width="100px">
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="工单名称" prop="name">
+ <el-input v-model="formParams.name" placeholder="请输入工单名称" maxlength="100" show-word-limit :disabled="txtTitle === '工单审核' || txtTitle === '工单详情'"></el-input>
+ </el-form-item>
+ </el-col>
+ <el-col :span="6">
+ <el-form-item label="关联航线" prop="file_id">
+ <el-select v-model="formParams.file_id" placeholder="请选择航线" filterable @change="getFlyingNest" :disabled="txtTitle === '工单审核' || txtTitle === '工单详情'">
+ <el-option v-for="item in lineList" :key="item.wayline_id" :label="item.name"
+ :value="item.wayline_id" />
+ </el-select>
+ </el-form-item>
+ </el-col>
+ <el-col :span="6">
+ <el-form-item label="安全返航真高" prop="rth_altitude" class="safe-height">
+ <el-input-number v-model="formParams.rth_altitude" :min="50" :max="500" :disabled="txtTitle === '工单审核' || txtTitle === '工单详情'"></el-input-number>
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="关联机巢" prop="device_sns">
+ <el-select v-model="formParams.device_sns" placeholder="请选择机巢" multiple :disabled="(txtTitle === '工单审核' || txtTitle === '工单详情')">
+ <el-option v-for="item in drone_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="formParams.ai_types" placeholder="请选择关联算法" multiple :disabled="txtTitle === '工单审核' || txtTitle === '工单详情'">
+ <el-option v-for="item in aiTypeList" :key="item.dictKey" :label="item.dictValue" :value="item.dictKey" />
+ </el-select>
+ </el-form-item>
+ </el-col>
+ </el-row>
+
+ <el-row :gutter="20">
+ <el-col :span="12" v-if="txtTitle === '新建工单'">
+ <el-form-item label="工单内容" prop="content">
+ <el-input type="textarea" v-model="formParams.content" rows="4" placeholder="请输入工单内容" maxlength="255" show-word-limit :disabled="txtTitle === '工单审核' || txtTitle === '工单详情'"></el-input>
+ </el-form-item>
+ </el-col>
+
+ <el-col :span="12" prop="statusStr" v-else>
+ <el-form-item label="当前状态">
+ {{ getOrderStatus(formParams.status) }}
+ </el-form-item>
+ </el-col>
+
+ <el-col :span="7">
+ <el-form-item label="周期频次" prop="date_range">
+ <el-date-picker :disabled="txtTitle === '工单审核' || txtTitle === '工单详情'"
+ v-model="formParams.date_range"
+ type="daterange"
+ range-separator="至"
+ start-placeholder="开始日期"
+ end-placeholder="结束日期"
+ :disabled-date="disabledDate" />
+ </el-form-item>
+ </el-col>
+ <el-col :span="2">
+ <el-select v-model="formParams.rep_fre_type" placeholder="请选择频次" :disabled="txtTitle === '工单审核' || txtTitle === '工单详情'">
+ <el-option v-for="item in weeks" :key="item" :label="item" :value="item" />
+ </el-select>
+ </el-col>
+ <el-col :span="3">
+ <el-time-picker :style="{ width: pxToRem(140) }"
+ v-model="formParams.deal_time"
+ :disabled="txtTitle === '工单审核' || txtTitle === '工单详情'"
+ prop="deal_time"
+ value-format="HH:mm"
+ :picker-options="{ selectableRange: '00:00 - 23:59'}" />
+ </el-col>
+ </el-row>
+
+ <el-row :gutter="20" v-if="txtTitle === '工单审核' || txtTitle === '工单详情' || txtTitle === '工单编辑'">
+ <el-col :span="12">
+ <el-form-item label="工单内容" prop="content">
+ <el-input type="textarea" v-model="formParams.content" rows="4" placeholder="请输入工单内容" maxlength="255"
+ show-word-limit :disabled="txtTitle === '工单审核' || txtTitle === '工单详情'"></el-input>
+ </el-form-item>
+ </el-col>
+ </el-row>
+
+ <el-row :gutter="20" style="height: 400px">
+ <el-col :span="24">
+ <map-container v-if="isShowAddEditDetails" ref="mapContainerRef"></map-container>
+ </el-col>
+ </el-row>
+ </el-form>
+ </div>
+ <template #footer v-if="txtTitle === '新建工单'">
+ <div class="dialog-footer">
+ <el-button type="danger" @click="submitForm(1)" icon="el-icon-position">发起</el-button>
+<!-- <el-button type="primary" @click="submitForm(0)" icon="el-icon-document-add">存草稿</el-button>-->
+ <el-button @click="cancel" icon="el-icon-circle-close">取消</el-button>
+ </div>
+ </template>
+ <template #footer v-if="txtTitle === '工单审核'">
+ <div class="dialog-footer">
+ <el-button type="primary"
+ v-if="formParams.status == 1 && permission.orderLogpass"
+ @click="orderPass(formParams.id)">通过
+ </el-button>
+ <el-button type="danger"
+ v-if="formParams.status == 1 && permission.rejection_btn"
+ @click="orderReject(formParams.id)">驳回
+ </el-button>
+ <el-button @click="cancel" icon="el-icon-circle-close">取消</el-button>
+ </div>
+ </template>
+ <template #footer v-if="txtTitle === '工单编辑'">
+ <div class="dialog-footer">
+ <el-button type="danger"
+ v-if="formParams.status == 0 || (formParams.status == 2 && userInfo.user_id === formParams.create_user)"
+ @click="submitForm(1)" icon="el-icon-position">发布
+ </el-button>
+ <el-button @click="cancel" icon="el-icon-circle-close">取消</el-button>
+ </div>
+ </template>
+ </el-dialog>
+</template>
+
+<script setup>
+import { pxToRem, pxToRemNum } from '@/utils/rem'
+import { ref, shallowRef, onMounted, computed } from 'vue';
+import { newGetWorkspacesPage } from '@/api/resource/wayline'
+import { getDictionaryByCode } from '@/api/system/dictbiz'
+import { getFlyingNestBy } from '@/api/device/device'
+import {
+ saveUpdateOrderLog,
+ orderLogDetails,
+ orderLogPass,
+ orderLogReject,
+ orderLogRecall, getWaylineMaxTerrainHeight
+} from '@/api/tickets/orderLog';
+import dayjs from 'dayjs'
+import 'dayjs/locale/zh-cn' // 导入中文语言包
+import _ from 'lodash'
+import { ElMessage, ElMessageBox } from 'element-plus';
+import weekday from 'dayjs/plugin/weekday'
+dayjs.extend(weekday)
+dayjs.locale('zh-cn')
+import { useStore } from 'vuex'
+
+const emit = defineEmits(['refresh']);
+
+const store = useStore()
+const userInfo = computed(() => store.state.user.userInfo)
+const permission = computed(() => store.state.user.permission);
+
+// safeHeight 计算属性
+const safeHeight = computed(() => {
+ const height = store.state.common.safeHeight
+ return Number(height) || 0 // 确保是数字类型
+})
+
+// positionsArr 计算属性
+const positionsArr = computed(() => {
+ const arr = store.state.common.positionsArr
+ return arr || [] // 确保是数组类型
+})
+
+const txtTitle = inject('txtTitle')
+const rowObj = inject('rowObj')
+
+const isShowAddEditDetails = defineModel('show')
+const mapContainerRef = ref(null)
+const orderFormRef = ref(null)
+const formParams = ref({})
+const rules = ref({
+ name: [
+ { required: true, message: '请输入工单名称', trigger: 'blur' },
+ { max: 100, message: '工单名称不能超过100个字', trigger: 'blur' }
+ ],
+ file_id: [{ required: true, message: '需要选择航线', trigger: 'change' }],
+ device_sns: [{ required: true, message: '请选择机巢', trigger: 'change' }],
+ content: [
+ { required: true, message: '请输入工单内容', trigger: 'blur' },
+ { max: 255, message: '工单内容不能超过255个字', trigger: 'blur' }
+ ],
+ date_range: [{ required: true, message: '请选择时间不能为空', trigger: 'blur' }],
+ rep_fre_type: [{ required: true, message: '请选择周期', trigger: 'blur' }],
+ deal_time: [{ required: true, message: '请选择执行时间', trigger: 'blur' }]
+})
+//周期
+const weeks = ref(['每天', '周一', '周二', '周三', '周四', '周五', '周六', '周末', '周天', '工作日'])
+
+let lineList = inject('lineList')
+let aiTypeList = inject('aiTypeList')
+
+// 获取航线
+// let lineList = ref([])
+// function getLines() {
+// const formData = {
+// name: '',
+// waylineType: undefined,
+// current: 1,
+// size: 99999999,
+// descs: 'update_time'
+// }
+// newGetWorkspacesPage(formData).then(res => {
+// lineList.value = res.data.data.records
+// })
+// }
+
+function getOrderStatus (status) {
+ const statusTextMap = {
+ 0: '草稿',
+ 1: '待审核',
+ 2: '已驳回',
+ 3: '已通过'
+ }
+ return statusTextMap[status] || '未知状态'
+}
+
+function disabledDate (time) {
+ return time.getTime() < Date.now() - 8.64e7 // 86400000 = 24 * 60 * 60 * 1000
+}
+
+// 关联机巢
+const drone_sns = ref([])
+function getFlyingNest (waylineId) {
+ console.log('选择航线', lineList.value)
+ // 选中航线对象
+ initMapLine({}, polygon => {
+ const currentLine = lineList.value.find(item => item.wayline_id === waylineId)
+ const params = {
+ type: ['2', '4', '5'].includes(currentLine.wayline_type) ? 2 : 0,
+ wayline_id: waylineId,
+ polygon
+ }
+ // 重置
+ formParams.value.device_sns = []
+ drone_sns.value = []
+ getFlyingNestBy(params).then(res => {
+ drone_sns.value = res.data.data
+ initMapLine()
+ })
+ })
+}
+
+// 获取算法
+// let aiTypeList = ref([])
+// function aitypeList() {
+// getDictionaryByCode('SF').then(res => {
+// aiTypeList.value = res.data.data['SF']
+// })
+// }
+
+// initMapLine 方法
+const initMapLine = async (infos = {}, cb = () => {}) => {
+ const currentLine = lineList.value.find(item => item.wayline_id == formParams.value.file_id)
+
+
+ if (!currentLine) return
+
+ await nextTick()
+ if (mapContainerRef.value?.initAddEntity) {
+ mapContainerRef.value.initAddEntity('polyline', {
+ url: `${import.meta.env.VITE_APP_AIRLINE_URL + currentLine.object_key}?_t=${new Date().getTime()}`,
+ type: currentLine.wayline_type,
+ cb,
+ infos
+ })
+ }
+}
+
+function 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`
+}
+
+async function returnHeight() {
+ let checkedList = drone_sns.value.filter(item => formParams.value.device_sns.includes(item.device_sn));
+ const maxItem = checkedList.reduce((max, item) => {
+ return item.drone_height > max.drone_height ? item : max;
+ });
+ // 返航绝对高度
+ let backHeight = formParams.value.rth_altitude // + maxItem.drone_height
+ console.log(backHeight,'backHeight')
+ // 安全起飞高度
+ let positions = positionsArr.value
+ positions.unshift({latitude: maxItem.latitude, longitude: maxItem.longitude, height:maxItem.drone_height })
+ positions.push({latitude: maxItem.latitude, longitude: maxItem.longitude, height:maxItem.drone_height})
+ let resultHeight = 0
+ await getWaylineMaxTerrainHeight(positions).then(res => {
+ resultHeight = safeHeight.value > (res.data.data + 30) ? safeHeight.value : (res.data.data + 30)
+ })
+ let valueHeight = _.round(backHeight - resultHeight, 2)
+ return _.round(backHeight - resultHeight, 2)
+}
+
+function submitForm(status) {
+ orderFormRef.value?.validate(async (valid, fields) => {
+ if (valid) {
+ console.log('表单验证通过')
+ formParams.value.begin_time = formatDate(formParams.value.date_range[0])
+ formParams.value.end_time = formatDate(formParams.value.date_range[1])
+ // 如果选中日期包含当前天,那么选中的时间点不能小于当前时间
+ if (formParams.value.deal_time) {
+ const selectedDate = dayjs(formParams.value.date_range[0]).format('YYYY-MM-DD')
+ const selectedTime = dayjs(selectedDate + ' ' + formParams.value.deal_time).toDate()
+ const now = new Date()
+
+ if (selectedDate === dayjs().format('YYYY-MM-DD') && selectedTime < now) {
+ return ElMessage({message: '任务时间不能小于当前时间', type: 'warning'})
+ }
+ }
+ let checkedList = drone_sns.value.filter(item => formParams.value.device_sns.includes(item.device_sn));
+ const maxItem = checkedList.reduce((max, item) => {
+ return item.drone_height > max.drone_height ? item : max;
+ });
+ // 返航绝对高度
+ let backHeight = formParams.value.rth_altitude // + maxItem.drone_height
+ console.log(backHeight,'backHeight')
+ // 安全起飞高度
+ let positions = positionsArr.value
+ positions.unshift({latitude: maxItem.latitude, longitude: maxItem.longitude, height:maxItem.drone_height })
+ positions.push({latitude: maxItem.latitude, longitude: maxItem.longitude, height:maxItem.drone_height})
+ let resultHeight = 0
+ let valueHeight = 0
+ await getWaylineMaxTerrainHeight(positions).then(res => {
+ resultHeight = (safeHeight.value + maxItem.drone_height) > (res.data.data + 30) ? (safeHeight.value + maxItem.drone_height) : (res.data.data + 30)
+ console.log(resultHeight,maxItem.drone_height, '999')
+ valueHeight = (resultHeight - maxItem.drone_height) > backHeight ? _.round(resultHeight - maxItem.drone_height) : 0
+ })
+ if (valueHeight > 0) {
+ await ElMessageBox.confirm(`当前返航高度存在安全隐患,建议调整为${valueHeight}米以上后进行发布`, '提示', {
+ confirmButtonText: '确定',
+ cancelButtonText: '取消',
+ type: 'warning'
+ })
+ }
+ const submitData = {
+ ...formParams.value,
+ status: status,
+ ai_types: formParams.value.ai_types?.length ? formParams.value.ai_types : []
+ }
+ await saveUpdateOrderLog(submitData)
+ if (txtTitle === '新建工单') {
+ ElMessage({message: '工单创建成功', type: 'success'})
+ } else {
+ ElMessage({message: '工单发布成功', type: 'success'})
+ }
+ cancel()
+ } else {
+ console.log('表单验证失败', fields)
+ }
+ })
+}
+// 审核通过
+function orderPass(id) {
+ orderLogPass(id).then(res => {
+ ElMessage.success('审核通过')
+ cancel()
+ })
+}
+// 驳回
+const orderReject = async (id) => {
+ try {
+ const { value } = await ElMessageBox.prompt('', '驳回原因', {
+ confirmButtonText: '确定',
+ cancelButtonText: '取消',
+ inputPlaceholder: '请输入驳回原因',
+ inputValidator: (value) => {
+ if (!value) {
+ return '驳回原因不能为空'
+ }
+ }
+ })
+ // 用户输入了内容并点击确定
+ await orderLogReject(id, value)
+ ElMessage.success('驳回成功')
+ cancel()
+ } catch (error) {
+ // 用户点击取消或关闭弹窗
+ if (error !== 'cancel' && error !== 'close') {
+ ElMessage.error('驳回失败')
+ console.error('驳回错误:', error)
+ }
+ }
+}
+
+const initMapPosition = async () => {
+ await nextTick()
+
+ if (mapContainerRef.value?.initAddEntity) {
+ mapContainerRef.value.initAddEntity('initPosition')
+ }
+}
+
+// 监听txtTitle
+watch(isShowAddEditDetails, async (newVal, oldVal) => {
+ console.log('txtTitle 发生变化:', newVal, oldVal)
+ formParams.value = {rth_altitude:120}
+ if (txtTitle.value === '工单审核' || txtTitle.value === '工单详情' || txtTitle.value === '工单编辑') {
+ const response = await orderLogDetails(rowObj.value.id)
+ const data = response.data.data
+ formParams.value = {
+ ...data
+ }
+ console.log(formParams.value, '898789')
+ drone_sns.value = data.device_list
+ initMapLine(data.device_map_infos)
+ } else {
+ initMapPosition()
+ }
+}, {
+ immediate: true // 立即执行一次
+})
+function handleBeforeClose() {
+ cancel()
+}
+function cancel() {
+ formParams.value = {}
+ isShowAddEditDetails.value = false
+ emit('refresh');
+}
+
+// onMounted(async () => {
+// aitypeList()
+// getLines()
+// });
+</script>
+
+<style lang="scss" scoped>
+.add-edit-details {
+ .safe-height {
+ :deep(.el-form-item__label) {
+ width: 120px !important;
+ }
+
+ :deep(.el-input-number) {
+ width: 180px !important;
+ }
+ }
+
+ .week_pc_one {
+ flex: 1;
+ .week-r {
+ margin-right: 4px;
+ }
+
+ &> ::v-deep(div) {
+ width: 100% !important;
+ }
+ }
+ .process {
+ .order_title {
+ height: 30px;
+ line-height: 30px;
+ text-align: center;
+ font-size: 20px;
+ font-weight: bold;
+ color: #333;
+ }
+ .custom-steps-container {
+ width: 100%;
+ margin: 7px 0;
+ }
+
+ .steps-titles {
+ display: flex;
+ justify-content: space-between;
+ margin-bottom: 16px;
+ position: relative;
+ }
+
+ .step-title {
+ text-align: center;
+ flex: 1;
+ font-size: 16px;
+ color: #999;
+ position: relative;
+ padding-bottom: 10px;
+ }
+
+ .step-title.active {
+ color: #409eff;
+ font-weight: bold;
+ }
+
+ .custom-steps {
+ margin-top: -20px;
+ /* 向上移动与标题重叠 */
+ }
+
+ .el-step__head {
+ margin-top: 0;
+ }
+
+ .el-step__description {
+ margin-top: 8px;
+ padding: 0 20px;
+ }
+
+ .step-description {
+ font-size: 14px;
+ color: #666;
+ line-height: 2.2;
+ }
+ }
+}
+</style>
\ No newline at end of file
diff --git a/src/views/tickets/component/SearchBox.vue b/src/views/tickets/component/SearchBox.vue
new file mode 100644
index 0000000..05d7d21
--- /dev/null
+++ b/src/views/tickets/component/SearchBox.vue
@@ -0,0 +1,313 @@
+<template>
+ <div class="search-box-test">
+ <el-form :model="searchForm" inline>
+ <div class="search-first">
+ <el-form-item label="关键字:">
+ <el-input v-model="searchForm.key_word" placeholder="编号/名称/内容/姓名" clearable />
+ </el-form-item>
+ <el-form-item label="选择日期:" :style="{ width: pxToRem(460) }">
+ <el-date-picker
+ v-model="dateRange"
+ type="daterange"
+ range-separator="至" start-placeholder="开始日期"
+ end-placeholder="结束日期"
+ :default-value="datePickerDefaultVal" @change="handleSearch" />
+ </el-form-item>
+ <el-form-item label="关联航线:">
+ <el-select
+ :teleported="false"
+ v-model="searchForm.file_id"
+ placeholder="请选择关联航线"
+ clearable
+ @change="handleSearch"
+ >
+ <el-option
+ v-for="item in lineList"
+ :key="item.wayline_id"
+ :label="item.name"
+ :value="item.wayline_id"
+ />
+ </el-select>
+ </el-form-item>
+ <el-form-item label="关联算法:">
+ <el-select
+ :teleported="false"
+ v-model="searchForm.ai_typesValue"
+ placeholder="请选择关联算法"
+ clearable @change="handleSearch">
+ <el-option
+ v-for="item in aiTypeList"
+ :key="item.dictKey"
+ :label="item.dictValue"
+ :value="item.dictKey"
+ />
+ </el-select>
+ </el-form-item>
+ <el-form-item label="工单状态:">
+ <el-select
+ :teleported="false"
+ :disabled="activeTab === 'WAIT_AUDIT' || activeTab === 'REJECTED' || activeTab === 'PASS'"
+ v-model="searchForm.status"
+ placeholder="请选择单状态"
+ @change="handleSearch"
+ clearable>
+ <el-option
+ v-for="item in orderStatus"
+ :key="item.value"
+ :label="item.label"
+ :value="item.value"
+ />
+ </el-select>
+ </el-form-item>
+ <el-form-item label="工单周期:" :style="{ width: pxToRem(460) }">
+ <el-date-picker
+ v-model="cycleDateRange"
+ type="daterange"
+ range-separator="至" start-placeholder="开始日期"
+ end-placeholder="结束日期"
+ :default-value="datePickerDefaultVal" @change="handleSearch"/>
+ </el-form-item>
+ <el-form-item label="选择频次:">
+ <el-select v-model="searchForm.rep_fre_type" placeholder="请选择频次" clearable @change="handleSearch">
+ <el-option v-for="item in cycles" :key="item" :label="item" :value="item" />
+ </el-select>
+ </el-form-item>
+ <el-form-item label="执行时间:">
+ <el-time-picker @change="handleSearch"
+ v-model="searchForm.deal_time"
+ placeholder="请选择执行时间"
+ prop="deal_time"
+ value-format="HH:mm"
+ :picker-options="{ selectableRange: '00:00 - 23:59'}" />
+ </el-form-item>
+ <el-button type="primary" icon="el-icon-search" @click="handleSearch">搜索</el-button>
+ <el-button icon="el-icon-refresh" @click="handleReset">清空</el-button>
+ </div>
+ </el-form>
+ </div>
+</template>
+<script setup>
+import { pxToRem } from '@/utils/rem';
+import { calculateDefaultRange } from '@/utils/util'
+import { newGetWorkspacesPage } from '@/api/resource/wayline'
+import { getDictionaryByCode } from '@/api/system/dictbiz'
+import dayjs from 'dayjs';
+// 选择日期
+const dateRange = ref('')
+let datePickerDefaultVal = calculateDefaultRange()
+// 选择周期
+const cycleDateRange = ref('')
+const searchForm = reactive({
+ ai_types: [],
+ ai_typesValue: '',
+ deal_time: "",
+ file_id: "",
+ key_word: "",
+ status: '',
+ rep_fre_type: '',
+})
+
+const emit = defineEmits(['search']);
+
+const rowObj = inject('rowObj')
+const activeTab = inject('activeTab')
+const lineList = inject('lineList')
+const aiTypeList = inject('aiTypeList')
+
+watch(activeTab, (newTab, oldTab) => {
+ if (newTab === 'all') {
+ searchForm.status = ''
+ }
+ if(newTab === 'WAIT_AUDIT') {
+ searchForm.status = '1'
+ }
+ if(newTab === 'REJECTED') {
+ searchForm.status = '2'
+ }
+ if(newTab === 'PASS') {
+ searchForm.status = '3'
+ }
+ emit('search', searchForm);
+})
+
+// 周期
+const cycles = ref(['每天', '周一', '周二', '周三', '周四', '周五', '周六', '周末', '周天', '工作日'])
+// 工单状态
+const orderStatus = ref([
+ { label: '待审核', value: '1' },
+ { label: '已驳回', value: '2' },
+ { label: '已通过', value: '3' }
+])
+const timeFormat = 'YYYY-MM-DD HH:mm:ss';
+// 搜索
+const handleSearch = () => {
+ if (!dateRange.value) {
+ dateRange.value = [];
+ }
+ if (!cycleDateRange.value) {
+ cycleDateRange.value = [];
+ }
+ // if (dateRange.value && dateRange.value.length) {
+ // checked.value = '';
+ // searchForm.date_enum = '';
+ // }
+ // 提交至store
+ let params = {
+ ...searchForm,
+ create_start_date: dateRange.value.length
+ ? dayjs(dateRange?.value[0]).startOf('day').format(timeFormat)
+ : null,
+ create_end_date: dateRange?.value.length
+ ? dayjs(dateRange?.value[1]).endOf('day').format(timeFormat)
+ : null,
+ start_date: cycleDateRange?.value.length
+ ? dayjs(cycleDateRange?.value[0]).startOf('day').format(timeFormat)
+ : null,
+ end_date: cycleDateRange?.value.length
+ ? dayjs(cycleDateRange?.value[1]).startOf('day').format(timeFormat)
+ : null,
+ };
+ emit('search', params);
+};
+
+function handleReset () {
+ searchForm.ai_types = []
+ searchForm.ai_typesValue = ''
+ searchForm.deal_time = ""
+ searchForm.file_id = ""
+ searchForm.key_word = ""
+ if (activeTab.value === 'all') {
+ searchForm.status = ''
+ }
+ searchForm.rep_fre_type = ''
+ dateRange.value = ''
+ searchForm.create_start_date = null
+ searchForm.create_end_date = null
+ cycleDateRange.value = ''
+ searchForm.start_date = null
+ searchForm.emd_date = null
+ emit('search', searchForm);
+}
+
+// onMounted(() => {
+// aitypeList()
+// getLines()
+// });
+</script>
+
+<style lang="scss" scoped>
+// .taskAlgorithm {
+// :deep() {
+// .task-algorithm > div {
+// width: 233px !important;
+// }
+// }
+// }
+.search-box-test {
+ margin-bottom: 16px;
+ transition: all 0.3s;
+
+ .search-first {
+ position: relative;
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ gap: 20px 15px; // 设置行间距和列间距
+
+ .more {
+ cursor: pointer;
+ width: 32px;
+ height: 32px;
+ color: #1c5cff;
+ line-height: 32px;
+ font-size: 16px;
+ margin: 0 28px;
+ }
+
+ .search-btn {
+ // position: absolute;
+ // right: 0;
+ display: flex;
+ justify-content: space-between;
+ cursor: pointer;
+ font-size: 14px;
+
+ .btn {
+ width: 80px;
+ height: 32px;
+ border-radius: 4px 4px 4px 4px;
+ line-height: 32px;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ // margin-left: px;
+ img {
+ width: 14px;
+ height: 14px;
+ }
+ }
+ .search {
+ background: #409eff;
+ color: #fff;
+ margin-right: 10px;
+ }
+ .clear {
+ border: 1px solid #d2d1d1;
+ color: #676767;
+ margin-right: 10px;
+ }
+ .add {
+ background: #ff9243;
+ color: #fff;
+ }
+ }
+
+ .time-card {
+ text-align: center;
+ background: #ffffff;
+ border-radius: 4px 0px 0px 4px;
+ border: 1px solid #e5e5e5;
+ font-family: Source Han Sans CN, Source Han Sans CN;
+ font-weight: 400;
+ font-size: 14px;
+ color: #7c8091;
+ display: flex;
+ height: 32px;
+
+ .card-item {
+ width: 60px;
+ height: 100%;
+ line-height: 32px;
+ cursor: pointer;
+ }
+
+ .active {
+ background: #ffffff;
+ border-radius: 0px 0px 0px 0px;
+ border: 1px solid #1c5cff;
+ color: #1441ff;
+ }
+ }
+ }
+
+ :deep(.el-form) {
+ :deep(.el-input__wrapper.is-disabled) {
+ box-shadow: 0 0 0 1px #026ad6;
+ }
+ .el-form-item__label {
+ width: 80px;
+ }
+
+ .el-form-item {
+ margin-bottom: 0;
+ width: 280px;
+ margin-right: 20px;
+
+ .el-form-item__label {
+ color: #363636;
+ line-height: 32px;
+ }
+ }
+ }
+}
+</style>
diff --git a/src/views/tickets/orderLog-copy1.vue b/src/views/tickets/orderLog-copy1.vue
deleted file mode 100644
index 29b5936..0000000
--- a/src/views/tickets/orderLog-copy1.vue
+++ /dev/null
@@ -1,1402 +0,0 @@
-
-<!--
- * @Author: shuishen 1109946754@qq.com
- * @Date: 2025-04-15 20:02:29
- * @LastEditors: shuishen 1109946754@qq.com
- * @LastEditTime: 2025-04-28 11:38:16
- * @FilePath: \drone-web-manage\src\views\tickets\orderLog.vue
- * @Description:
- *
- * Copyright (c) 2025 by shuishen, All Rights Reserved.
--->
-<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">
- <basic-main-content>
- <!-- 查询条件筛选栏 -->
- <div class="filter-bar">
- <div class="search-bar-box">
- <div class="search-bar-box-item">
- <el-input v-model="filters.key_word" placeholder="输入工单编号/名称/内容/姓名" clearable
- @keyup.enter="handleSearch" />
- </div>
-
- <div class="search-bar-box-item">
- <el-select placeholder="请选择所属部门" v-model="filters.create_dept" clearable>
- <el-option v-for="dept in departments" :key="dept.value" :label="dept.label" :value="dept.value" />
- </el-select>
- </div>
-
- <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>
-
- <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>
-
- <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>
-
- <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" v-if="activeTab === tab.name">
- <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>
- </template>
-
- <template #menu="{ row }">
- <div class="menu-custom-box">
- <template v-if="row.status == 1">
- <el-button v-if="hasPaddingBtnPermission()" type="text" icon="el-icon-view"
- @click="handleCheckDetail(row)">审核</el-button>
- </template>
- <!-- v-if="
- (userInfo.user_id == row.create_user || hasRecallPaddingBtnPermission()) &&
- row.status == 1
- " -->
- <template v-if="row.status == 1">
- <!--待审核状态-->
- <el-button type="text" icon="el-icon-warning" v-if="hasRecallPaddingBtnPermission()"
- @click="orderLogRecall(row.id)">撤回</el-button>
- <el-button type="text" icon="el-icon-view" @click="handleViewDetail(row)">详情</el-button>
- </template>
- <!--已驳回-->
- <template v-if="row.status == 2">
- <el-button type="text" icon="el-icon-warning" @click="rejectDetail(row.id)">驳回原因</el-button>
- <el-button type="text" icon="el-icon-view" @click="handleViewDetail(row)">详情</el-button>
- </template>
- <!-- 已通过 -->
- <template v-if="row.status == 3">
- <el-button type="text" icon="el-icon-view" @click="handleViewDetail(row)">详情</el-button>
- </template>
- <!--草稿-->
- <template v-if="row.status == 0">
- <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>
- </div>
- </template>
- <template #status="{ row }">
- <el-tag :type="getStatusTagType(row.status)">{{ mapStatus(row.status) }}</el-tag>
- </template>
- <template #keyData="{ row }">
- <el-tooltip :content="row.address" placement="top" effect="light">
- <span>{{ row.keyData }}</span>
- </el-tooltip>
- </template>
- </avue-crud>
- </basic-main-content>
- </el-tab-pane>
- </el-tabs>
-
- <!-- 新建工单对话框 -->
- <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-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-select>
- </el-form-item>
- </el-col>
- </el-row>
- <el-row :gutter="20">
- <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-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-select>
- </el-form-item>
- </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-form-item>
- </el-col>
-
- <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="结束日期" />
- </el-form-item>
- </el-col>
- <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>
-
- <el-row :gutter="20" style="height: 400px">
- <el-col :span="24">
- <map-container v-if="dialogVisible" ref="MapContainer"></map-container>
- </el-col>
- </el-row>
-
- <el-row>
- <div class="add-box-btns">
- <el-button type="danger" @click="submitForm(1)">发起</el-button>
- <el-button type="primary" @click="submitForm(0)">存草稿</el-button>
- </div>
- </el-row>
- </el-form>
- </el-dialog>
-
- <!-- 工单详情对话框 -->
- <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">
- {{ record.status_str }}
- </div>
- </div>
-
- <!-- Element Steps 组件 -->
- <el-steps :active="form.active" align-center class="custom-steps">
- <el-step v-for="(record, index) in form.record_list" :key="index">
- <template #description>
- <span class="step-description" style="position: relative; display: inline-block">
- {{ record.user_name }}
- </span>
- <span class="step-timer ">
- {{ record.interval_time_str }}
- </span>
- <div class="step-description">
- {{ record.create_time_str }}
- </div>
- </template>
- </el-step>
- </el-steps>
- </div>
-
- <el-row :gutter="20">
- <el-col :span="12">
- <el-form-item label="工单名称" prop="name">
- <el-input v-model="form.name" placeholder="请输入工单名称"></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-select>
- </el-form-item>
- </el-col>
- </el-row>
- <el-row :gutter="20">
- <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-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-select>
- </el-form-item>
- </el-col>
- </el-row>
-
- <el-row :gutter="20">
- <el-col :span="12" prop="statusStr">
- <el-form-item label="当前状态">
- {{ mapStatus(form.status) }}
- </el-form-item>
- </el-col>
-
- <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="结束日期" />
- </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>
- </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-form-item>
- </el-col>
- </el-row>
-
- <el-row :gutter="20" style="height: 400px">
- <el-col :span="24">
- <map-container v-if="detailVisible" ref="MapContainer"></map-container>
- </el-col>
- </el-row>
-
- <el-row>
- <div class="add-box-btns">
- <el-button type="danger"
- v-if="form.status == 0 || (form.status == 2 && userInfo.user_id == form.create_user)"
- @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 && hasPaddingBtnPermission()"
- @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">
- <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">
- {{ record.status_str }}
- </div>
- </div>
-
- <!-- Element Steps 组件 -->
- <el-steps :active="form.active" align-center class="custom-steps">
- <el-step v-for="(record, index) in form.record_list" :key="index">
- <template #description>
- <span class="step-description" style="position: relative; display: inline-block">
- {{ record.user_name }}
- </span>
- <span class="step-timer ">
- {{ record.interval_time_str }}
- </span>
- <div class="step-description">
- {{ record.create_time_str }}
- </div>
- </template>
- </el-step>
- </el-steps>
- </div>
-
- <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-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-select>
- </el-form-item>
- </el-col>
- </el-row>
-
- <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>
- </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>
- </el-form-item>
- </el-col>
- </el-row>
-
- <el-row :gutter="20">
- <el-col :span="12" prop="statusStr">
- <el-form-item label="当前状态">
- {{ mapStatus(form.status) }}
- </el-form-item>
- </el-col>
-
- <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="结束日期" :disabled="true" />
- </el-form-item>
- </el-col>
- <el-col :span="3">
- <el-select v-model="form.rep_fre_type" placeholder="请选择频次" :disabled="true">
- <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" :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-form-item>
- </el-col>
- </el-row>
-
- <el-row :gutter="20" style="height: 400px">
- <el-col :span="24">
- <map-container v-if="detailVisibleCopy" ref="MapContainer"></map-container>
- </el-col>
- </el-row>
-
- <el-row>
- <div class="add-box-btns">
- <el-button type="danger"
- v-if="form.status == 0 || (form.status == 2 && userInfo.user_id == form.create_user)"
- @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 && hasPaddingBtnPermission()"
- @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>
- </el-form>
- </el-dialog>
- </basic-container>
-</template>
-
-<script>
-import { calculateDefaultRange } from '@/utils/util'
-import {
- getList,
- saveUpdateOrderLog,
- orderLogDetails,
- orderLogRecall,
- orderLogReject,
- orderLogPass,
- orderLogExport,
- 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'
-
-export default {
- name: 'TicketPage',
- data () {
- return {
- activeTab: 'all',
-
- tabs: [
- { label: '全部工单', name: 'all', value: null, count: 0 },
- { label: '待审核', name: 'WAIT_AUDIT', value: 1, count: 0 },
- { label: '已驳回', name: 'REJECTED', value: 2, count: 0 },
- { label: '已通过', name: 'PASS', value: 3, count: 0 },
- { label: '草稿', name: 'DRAFT', value: 0, count: 0 },
- ],
- filters: {
- key_word: '',
- create_dept: '',
- start_date: null,
- end_date: null,
- file_id: '',
- ai_types: [],
- status: '',
- dateRange: [],
- },
- departments: [],
- types: [],
- device_sns: [],
- ai_types: [],
- wayLineList: [],
- detailTitle: '编辑工单',
- detailVisibleCopy: false,
-
- statuses: [
- { label: '草稿', value: '0' },
- { label: '待审核', value: '1' },
- { label: '已驳回', value: '2' },
- { label: '已通过', value: '3' },
- ],
- //周期
- cycles: ['周一', '周二', '周三', '周四', '周五', '周六', '周末', '周天', '工作日', '每天'],
-
- tableData: [],
- option: {
- index: true,
- indexWidth: 66,
- indexLabel: '序号',
- indexFixed: true,
-
- align: 'center',
- border: true,
- stripe: true,
- searchShow: true,
- searchBtnText: '搜索',
- searchMenuSpan: 6,
- addBtnText: '新建工单',
- addBtn: false,
- viewBtn: false,
- editBtn: false,
- delBtn: false,
- cancelBtn: true,
- cancelBtnText: '取消',
-
- menu: true,
- menuWidth: 210,
- menuClassName: 'cur-menu',
-
- height: 'auto',
- calcHeight: 196,
-
- column: [
- {
- label: '工单编号', prop: 'job_info_num', width: 100, ellipsis: true, overHidden: true,
- showOverflowTooltip: true,
- },
- {
- label: '工单名称', prop: 'name', width: 100, ellipsis: true, overHidden: true,
- showOverflowTooltip: true,
- },
- {
- label: '工单状态', prop: 'status', width: 88,
- showOverflowTooltip: true,
- },
- {
- label: '所属部门', prop: 'dept_name', width: 100, ellipsis: true,
- showOverflowTooltip: true,
- },
- {
- label: '发起时间', prop: 'create_time', width: 144, ellipsis: true,
- showOverflowTooltip: true,
- },
- {
- label: '已执行次数', prop: 'job_num', width: 96, ellipsis: true,
- showOverflowTooltip: true,
- },
- {
- label: '工单内容', prop: 'content', ellipsis: true, overHidden: true,
- showOverflowTooltip: true,
- },
- {
- label: '关联航线', prop: 'wayline_name', width: 100, ellipsis: true, overHidden: true,
- showOverflowTooltip: true,
- },
- {
- label: '关联算法', prop: 'ai_type_str', width: 100, ellipsis: true, overHidden: true,
- showOverflowTooltip: true,
- },
- {
- label: '关联机巢', prop: 'device_names', width: 100, ellipsis: true, overHidden: true,
- showOverflowTooltip: true,
- },
-
- {
- label: '创建人', prop: 'creator_name', width: 96, ellipsis: true, overHidden: true,
- showOverflowTooltip: true,
- },
- {
- label: '关联机巢', prop: 'device_names', width: 112, ellipsis: true, overHidden: true,
- showOverflowTooltip: true,
- },
- {
- label: '工单周期频次',
- prop: '',
- width: 110,
- formatter: row => this.formatCycleTime(row),
- html: true,
- ellipsis: true,
- showOverflowTooltip: true,
- // overHidden: true
- },
- ],
- },
-
- page: {
- pageSize: 10,
- currentPage: 1,
- total: 0,
- },
- dialogVisible: false,
- detailVisible: false,
- currentDetail: {},
- form: {},
- rules: {
- name: [
- { required: true, message: '请输入工单名称', trigger: 'blur' },
- { max: 100, message: '工单名称不能超过100个字', trigger: 'blur' },
- ],
- file_id: [{ required: true, message: '需要选择航线', trigger: 'change' }],
- device_sns: [{ required: true, message: '请选择机巢', trigger: 'change' }],
- ai_types: [{ required: true, message: '请选择算法', trigger: 'change' }],
- content: [
- { required: true, message: '请输入工单内容', trigger: 'blur' },
- { max: 255, message: '工单内容不能超过255个字', trigger: 'blur' },
- ],
- date_range: [{ required: true, message: '请选择时间不能为空', trigger: 'blur' }],
- rep_fre_type: [{ required: true, message: '请选择周期', trigger: 'blur' }],
- deal_time: [{ required: true, message: '请选择执行时间', trigger: 'blur' }],
- },
- loading: false,
- globalCounts: {},
- mapLoaded: false,
-
- // 配置时间选择器默认配置
- 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']
- //获取航线
- 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()
- const id = this.$route.query.id
- if (id) {
- // 确保 id 存在
- this.handleViewDetail({ id })
- } else {
- console.error('工单ID不存在!')
- }
- },
- computed: {
- ...mapGetters(['userInfo', 'permission']),
- filteredTabs () {
- // rejection_and_draft 权限控制“已驳回”和“草稿”tab
- const canShowRejectAndDraft = this.permission?.rejection_and_draft === true
- return this.tabs
- .map(tab => {
- if (tab.name === 'DRAFT') {
- return { ...tab, isShow: canShowRejectAndDraft }
- }
- if (tab.name === 'REJECTED') {
- return { ...tab, isShow: canShowRejectAndDraft }
- }
- return { ...tab, isShow: true }
- })
- .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()
- },
- 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()
- })
- },
- selectionClear () {
- this.selectionList = []
- this.$refs.crud.toggleSelection()
- },
- async loadAMapScripts () {
- try {
- // await loadAMap();
- // await loadAMapUI();
- this.mapLoaded = true
- } catch (error) {
- console.error('Failed to load AMap scripts:', error)
- this.$message.error('地图加载失败,请检查网络或API Key配置')
- }
- },
- formatCycleTime (row) {
- return `${row.cycle_time_value}`
- },
-
- async fetchTableData () {
- this.loading = true
- try {
- 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('接口返回数据格式不正确')
- }
-
- const { total, records } = response.data.data
- this.tableData = records.map(item => {
- return item
- })
-
- // 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
- } finally {
- this.loading = false
- }
- },
-
- 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 = {
- key_word: this.filters.key_word || undefined,
- create_dept: this.filters.create_dept || undefined,
- ai_types:
- this.filters.ai_types && this.filters.ai_types.length > 0
- ? [this.filters.ai_types]
- : null,
- file_id: this.filters.file_id || undefined,
- status: this.filters.status !== '' ? Number(this.filters.status) : currentTab?.value,
- dept_id: this.filters.department || undefined,
- create_start_date: this.filters.dateRange?.[0]
- ? this.formatDate(this.filters.dateRange[0])
- : undefined,
- create_end_date: this.filters.dateRange?.[1]
- ? this.formatDate(this.filters.dateRange[1]).replace('00:00:00', '23:59:59')
- : undefined,
-
- start_date: this.filters.cycleDateRange?.[0]
- ? this.formatDate(this.filters.cycleDateRange[0])
- : undefined,
- 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
- },
- sizeChange (pageSize) {
- this.page.pageSize = pageSize
- },
- async submitForm (status) {
- this.$refs.testform.validate(async valid => {
- if (valid) {
- 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])
-
- 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
- this.detailVisible = false;
- (this.device_sns = []), (this.wayLineList = []), this.fetchTableData()
- }
- })
- },
- //驳回原因显示
- async rejectDetail (id) {
- const response = await orderLogDetails(id)
- let data = response.data.data
- this.$confirm(data.remark, '驳回原因', {
- confirmButtonText: '确定',
- cancelButtonText: '取消',
- type: 'warning',
- }).then(() => {
- this.form = {
- ...response.data.data,
- }
- this.detailVisible = true
- })
- },
- 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`
- },
-
- mapStatus (status) {
- const statusTextMap = {
- 0: '草稿',
- 1: '待审核',
- 2: '已驳回',
- 3: '已通过',
- }
- return statusTextMap[status] || '未知状态'
- },
-
- getStatusTagType (status) {
- const statusMap = {
- 1: 'warning',
- 2: 'info',
- 3: 'primary',
- 4: 'success',
- 5: 'danger',
- }
- return statusMap[status] || 'info'
- },
-
- 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()
- },
-
- handleReset () {
- this.filters = {
- keyword: '',
- department: '',
- type: '',
- dateRange: [],
- status: '',
- }
- this.page.currentPage = 1
- this.fetchTableData()
- },
-
- currentChange (currentPage) {
- this.page.currentPage = currentPage
- },
-
- async updateGlobalCounts () {
- const counts = {
- all: 0,
- DRAFT: 0,
- WAIT_AUDIT: 0,
- REJECTED: 0,
- PASS: 0,
- }
- 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)
- if (tab) {
- tab.count = item.num
- }
- })
- },
-
- handleAdd () {
- this.form = {}
- this.dialogVisible = true
- //航线列表
- this.asyncgetWaylineFileListByArea()
- },
-
- resetForm () {
- this.form = {
- name: '',
- type: '',
- handler: '',
- algorithm: '',
- location: '',
- address: '',
- content: '',
- photos: [],
- }
- if (this.$refs.testform) {
- this.$refs.testform.resetFields()
- }
- },
-
- formatLocation (location) {
- if (!Array.isArray(location)) {
- return '未知位置'
- }
- return `${location[0].toFixed(6)}, ${location[1].toFixed(6)}`
- },
- async handleViewDetail (row) {
- const response = await orderLogDetails(row.id)
- const data = response.data.data
- this.form = {
- ...data,
- }
-
- // 更新机巢列表
- 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.detailTitle = '工单详情')
- : (this.detailTitle = '编辑工单')
-
- this.detailVisible = true
-
- // 更新航线列表,追加 wayline_file_region_vo 数据
- if (data.wayline_file_region_vo) {
- const newWayline = data.wayline_file_region_vo
-
- // 检查是否已经存在于 this.wayLineList 中
- const isDuplicate = this.wayLineList.some(
- (item) => item.wayline_id === newWayline.wayline_id
- )
-
- if (!isDuplicate) {
- this.wayLineList.push(newWayline)
- }
- }
-
- this.initMapLine(data.device_map_infos)
- },
- async handleCheckDetail (row) {
- const response = await orderLogDetails(row.id)
- const data = response.data.data
- this.form = {
- ...data,
- }
- // 更新航线列表,追加 wayline_file_region_vo 数据
- // 更新航线列表,追加 wayline_file_region_vo 数据
- if (data.wayline_file_region_vo) {
- const newWayline = data.wayline_file_region_vo
-
- // 检查是否已经存在于 this.wayLineList 中
- const isDuplicate = this.wayLineList.some(
- (item) => item.wayline_id === newWayline.wayline_id
- )
-
- if (!isDuplicate) {
- this.wayLineList.push(newWayline)
- }
- }
- // 更新机巢列表
- this.device_sns = data.device_list
- this.detailVisibleCopy = true
- this.initMapLine(data.device_map_infos)
- },
- //导出
- async exportData () {
- this.$confirm('是否智飞工单数据?', '提示', {
- confirmButtonText: '确定',
- cancelButtonText: '取消',
- type: 'warning',
- }).then(() => {
- NProgress.start()
- let params = this.getQueryParam()
- orderLogExport(params).then(res => {
- downloadXls(res.data, `智飞工单${this.$dayjs().format('YYYY-MM-DD')}.xlsx`)
- NProgress.done()
- })
- })
- },
- 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
- },
- hasPaddingBtnPermission () {
- // undefined 或 false 都返回 false,只有 true 返回 true
- // console.log('权限检查:', this.permission)
- return this.permission && this.permission.order_log_review === true
- },
- hasRecallPaddingBtnPermission () {
- // undefined 或 false 都返回 false,只有 true 返回 true
- // console.log('权限检查:', this.permission)
- // 智飞工单撤回
- return this.permission && this.permission.order_log_recall === true
- },
- //驳回按钮权限
- hasRejectionBtnPermission () {
- // undefined 或 false 都返回 false,只有 true 返回 true
- // console.log('权限检查:', this.permission)
- return this.permission && this.permission.rejection_btn === true
- },
- //自己点发布
- userPublishPush (id) {
- this.$confirm('确定发布吗?', '提示', {
- confirmButtonText: '确定',
- cancelButtonText: '取消',
- type: 'warning',
- }).then(() => {
- let response = userPublish(id)
- this.$message.success('发布成功')
- this.fetchTableData()
- })
- },
-
- //删除
- deleteOrderLog (id) {
- this.$confirm('确定删除吗?', '提示', {
- confirmButtonText: '确定',
- cancelButtonText: '取消',
- type: 'warning',
- }).then(() => {
- let response = deleteOrderLog(id)
- this.$message.success('删除')
- this.fetchTableData()
- })
- },
- refreshChange () {
- this.fetchTableData()
- },
- //获取航线列表
- async asyncgetWaylineFileListByArea (name) {
- var wayLineListResponse = await getWaylineFileListByArea(this.userInfo.detail.areaCode)
- this.wayLineList = wayLineListResponse.data.data
-
- this.initMapLine()
- },
-
- initMapLine (infos = {}, cb = () => { }) {
- let currentLine = this.wayLineList.find(item => item.wayline_id == this.form.file_id)
-
- if (!currentLine) return
-
- this.$nextTick(() => {
- if (this.$refs.MapContainer && this.$refs.MapContainer.initAddEntity) {
- this.$refs.MapContainer.initAddEntity('polyline', {
- url: `${import.meta.env.VITE_APP_AIRLINE_URL + currentLine.object_key}?_t=${new Date().getTime()}`,
- type: currentLine.wayline_type,
- cb,
- infos
- })
- }
- })
- },
-
- //可飞行机巢列表
- async getFlyingNestBy (waylineId) {
- const that = this
-
- that.device_sns = []
-
- this.initMapLine({}, async (polygon) => {
- const currentLine = that.wayLineList.find(item => item.wayline_id === waylineId)
-
- //按照航线来
- const params = {
- type: [2, 4, 5].includes(currentLine.wayline_type) ? 2 : 0,
- wayline_id: waylineId,
- polygon
- }
- var wayLineListResponse = await getFlyingNestBy(params)
- that.device_sns = wayLineListResponse.data.data
- })
- },
-
- //撤回
- async orderLogRecall (id) {
- this.$confirm('确定撤回则到草稿箱。', '是否撤回?', {
- confirmButtonText: '确定',
- cancelButtonText: '取消',
- type: 'warning',
- }).then(async () => {
- let reposne = await orderLogRecall(id)
- this.handleSearch()
- })
- },
- onLoad () {
- this.fetchTableData()
- },
- /**
- * 通过
- */
- async orderLogPass (id) {
- let response = await orderLogPass(id)
- let data = response.data.data
- this.$message.success('审核通过')
- this.detailVisibleCopy = false
-
- this.page.currentPage = 1
- this.onLoad(this.page, this.query)
- },
- /**
- * 驳回
- */
- 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
-
- this.page.currentPage = 1
- this.onLoad(this.page, this.query)
- })
- },
- },
-
- watch: {
- tableData: {
- handler () {
- // this.updateTabCounts()
- },
- deep: true,
- },
- },
-}
-</script>
-
-<style lang="scss" scoped>
-::v-deep(.el-tabs) {
- height: 100%;
- display: flex;
- flex-direction: column;
-
- .el-tabs__header {
- order: 1;
- }
-
- .el-tabs__content {
- order: 2;
- }
-
- .el-tabs__content {
- height: 0;
- flex: 1;
- display: flex;
- flex-direction: column;
-
- .el-tab-pane {
- height: 0;
- flex: 1;
- display: flex;
- flex-direction: column;
- }
- }
-}
-
-.filter-bar {
- // align-items: center;
-
- .search-bar-box {
- margin-bottom: 15px;
- display: flex;
- justify-content: space-between;
- gap: 8px;
-
- &-item {
- flex: 1;
-
- &> ::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;
- }
- }
-}
-
-:deep(.el-pagination .el-select) {
- width: 128px !important;
-}
-
-.step-timer {
- position: absolute;
- left: 80%;
- top: 50%;
- transform: translateY(-50%);
- width: 100px;
- margin-left: 4px;
- color: #666;
- font-size: 12px;
-}
-
-:deep(.el-textarea__inner) {
- padding: 8px 12px;
-}
-
-:deep(.el-input__inner),
-:deep(.el-select),
-:deep(.el-select .el-input) {
- width: 100%; // 确保所有输入框和选择框宽度一致
-}
-
-.action-bar {
- margin-bottom: 16px;
-}
-
-.el-tabs {
- :deep(.el-tabs__content) {
- overflow: visible;
- }
-}
-
-.el-dialog {
- .el-form-item {
- margin-bottom: 20px;
- }
-}
-
-.el-upload {
- width: 100%;
- display: flex;
- flex-wrap: wrap;
-
- :deep(.el-upload-list__item) {
- transition: all 0.3s ease;
- }
-
- :deep(.el-upload-list__item:hover) {
- background-color: #f5f7fa;
- }
-}
-
-.el-upload__tip {
- font-size: 12px;
- color: #909399;
- margin-top: 7px;
-}
-
-.custom-steps-container {
- width: 100%;
- margin: 20px 0;
-}
-
-.steps-titles {
- display: flex;
- justify-content: space-between;
- margin-bottom: 16px;
- position: relative;
-}
-
-.step-title {
- text-align: center;
- flex: 1;
- font-size: 16px;
- color: #999;
- position: relative;
- padding-bottom: 10px;
-}
-
-.step-title.active {
- color: #409eff;
- font-weight: bold;
-}
-
-.custom-steps {
- margin-top: -20px;
- /* 向上移动与标题重叠 */
-}
-
-.el-step__head {
- margin-top: 0;
-}
-
-.el-step__description {
- margin-top: 8px;
- padding: 0 20px;
-}
-
-.step-description {
- font-size: 14px;
- color: #666;
- line-height: 1.5;
-}
-
-.event-title-center {
- text-align: center;
- font-size: 20px;
- font-weight: bold;
- margin-bottom: 12px;
- color: #333;
-}
-
-.cur-menu {
- .menu-custom-box {
- display: flex;
- justify-content: center;
- flex-wrap: wrap;
-
- &> ::v-deep(.el-button) {
- flex: 1;
- margin-left: 0;
- margin-right: 10px;
-
- }
- }
-}
-
-.add-box-btns {
- width: 100%;
- display: flex;
- justify-content: center;
-}
-
-.flex {
- display: flex;
-}
-
-.flex-1 {
- flex: 1;
-
- &> ::v-deep(div) {
- width: 100% !important;
- }
-}
-</style>
-
diff --git a/src/views/tickets/orderLog.vue b/src/views/tickets/orderLog.vue
index 825d984..595c86c 100644
--- a/src/views/tickets/orderLog.vue
+++ b/src/views/tickets/orderLog.vue
@@ -1,1708 +1,511 @@
+<!-- 任务统计表格 -->
<template>
- <basic-container>
+ <div class="order-log-update">
<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">
- <basic-main-content>
- <!-- 查询条件筛选栏 -->
- <div class="ztzf-form-search">
- <el-form :model="filters" inline>
- <el-row :gutter="24">
- <el-col :span="4">
- <el-form-item label="关键字:">
- <el-input v-model="filters.key_word" placeholder="编号/名称/内容/姓名" clearable
- @keyup.enter="handleSearch" />
- </el-form-item>
-
- <!-- <div class="search-bar-box-item">-->
- <!-- <el-select placeholder="请选择所属单位" v-model="filters.create_dept" clearable>-->
- <!-- <el-option v-for="dept in departments" :key="dept.value" :label="dept.label" :value="dept.value" />-->
- <!-- </el-select>-->
- <!-- </div>-->
-
- </el-col>
- <el-col :span="8">
- <el-form-item label="选择日期:">
- <el-date-picker @change="handleSearch" v-model="filters.dateRange" type="daterange"
- range-separator="至" start-placeholder="开始日期" end-placeholder="结束日期"
- :default-value="datePickerDefaultVal" />
- </el-form-item>
- </el-col>
- <el-col :span="4">
- <el-form-item label="关联航线:">
- <el-select @change="handleSearch" v-model="filters.file_id" placeholder="请选择关联航线" filterable
- clearable>
- <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>
- <el-col :span="4">
- <el-form-item label="关联算法:">
- <el-select @change="handleSearch" 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>
- </el-form-item>
-
- <!-- <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> -->
-
- </el-col>
- <el-col :span="4">
- <el-form-item label="工单状态:">
- <el-select @change="handleSearch" v-model="filters.status" placeholder="请选择工单状态" clearable
- :disabled="activeTab !== 'all'">
- <el-option v-for="item in statuses" :key="item.value" :label="item.label" :value="item.value" />
- </el-select>
- </el-form-item>
- </el-col>
- </el-row>
- <el-row :gutter="24">
- <el-col :span="8">
- <el-form-item label="工单周期:">
- <el-date-picker @change="handleSearch" v-model="filters.cycleDateRange" type="daterange"
- range-separator="至" start-placeholder="工单周期开始日期" end-placeholder="工单周期结束日期"
- :default-value="datePickerDefaultVal" />
- </el-form-item>
- </el-col>
- <el-col :span="4">
- <el-form-item label="选择频次:">
- <el-select @change="handleSearch" v-model="filters.rep_fre_type" placeholder="请选择频次" clearable>
- <el-option v-for="item in cycles" :key="item" :label="item" :value="item" />
- </el-select>
- </el-form-item>
- </el-col>
- <el-col :span="4">
- <el-form-item label="执行时间:">
- <el-time-picker @change="handleSearch" v-model="filters.deal_time" placeholder="请选择执行时间"
- prop="deal_time" value-format="HH:mm" :picker-options="{
- selectableRange: '00:00 - 23:59',
- }" />
- </el-form-item>
- </el-col>
- <el-col :span="4">
- <el-button type="primary" icon="el-icon-search" @click="handleSearch">搜索
- </el-button>
- <el-button icon="el-icon-refresh" @click="handleReset">清空</el-button>
- </el-col>
- </el-row>
- </el-form>
- </div>
-
- <!-- 表格部分 -->
- <avue-crud class="ztzf-public-general-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" v-if="activeTab === tab.name">
- <template #job_info_num="{ row }">
- <el-tooltip-copy :content="row.job_info_num" :showCopyText="true" textAlign="left">
- {{ row.job_info_num }}
- </el-tooltip-copy>
- </template>
-
- <template #name="{ row }">
- <el-tooltip-copy :content="row.name" :showCopyText="true" textAlign="left">
- {{ row.name }}
- </el-tooltip-copy>
- </template>
-
- <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>
- </template>
-
- <template #menu="{ row }">
- <div class="menu-custom-box">
- <template v-if="row.status == 1">
- <el-button v-if="hasPaddingBtnPermission()" type="text" icon="el-icon-view"
- @click="handleCheckDetail(row)">审核
- </el-button>
- </template>
- <!-- v-if="
- (userInfo.user_id == row.create_user || hasRecallPaddingBtnPermission()) &&
- row.status == 1
- " -->
- <template v-if="row.status == 1">
+ <el-tab-pane v-for="tab in tabsList" :key="tab.name" :label="`${tab.label} (${tab.count})`" :name="tab.name">
+ <SearchBox @search="searchClick"></SearchBox>
+ <el-button v-if="permission?.order_log_add && activeTab != 'WAIT_AUDIT'" type="primary" icon="el-icon-plus" @click="handleAddOrder">新建工单
+ </el-button>
+ <el-button type="success" plain icon="el-icon-download" @click="exportData">导出
+ </el-button>
+ <div class="task-table">
+ <el-table border :data="orderListTable" class="custom-header" @cell-click="handleCellClick">
+ <el-table-column label="序号" type="index" width="60">
+ <template #default="{ $index }">
+ {{ ($index + 1 + (orderListParams.current - 1) * orderListParams.size).toString().padStart(2,
+ '0') }}
+ </template>
+ </el-table-column>
+ <el-table-column prop="job_info_num" label="工单编号" width="150" show-overflow-tooltip>
+ <template #default="scope">
+ <el-tooltip-copy :content="scope.row.job_info_num" :showCopyText="true">
+ {{scope.row.job_info_num}}
+ </el-tooltip-copy>
+ </template>
+ </el-table-column>
+ <el-table-column prop="name" label="工单名称" width="100" show-overflow-tooltip>
+ <template #default="scope">
+ <el-tooltip-copy :content="scope.row.name" :showCopyText="true">
+ {{scope.row.name}}
+ </el-tooltip-copy>
+ </template>
+ </el-table-column>
+ <el-table-column prop="dept_name" label="工单状态" width="88">
+ <template #default="scope">
+ <el-tag :type="getStatusTagType(scope.row.status)">{{ getOrderStatus(scope.row.status) }}</el-tag>
+ </template>
+ </el-table-column>
+ <el-table-column prop="dept_name" label="所属单位" width="88" show-overflow-tooltip/>
+ <el-table-column prop="create_time" label="创建时间" width="120" show-overflow-tooltip/>
+ <el-table-column prop="job_num" label="已执行次数" width="100" align="center" />
+ <el-table-column prop="content" label="工单内容" show-overflow-tooltip align="center"/>
+ <el-table-column prop="wayline_name" label="关联航线" show-overflow-tooltip />
+ <el-table-column prop="ai_type_str" label="关联算法" width="100" align="center" show-overflow-tooltip/>
+ <el-table-column prop="device_names" label="关联机巢" width="100" align="center" show-overflow-tooltip/>
+ <el-table-column prop="creator_name" label="创建人" width="100" align="center" show-overflow-tooltip />
+ <el-table-column prop="cycle_time_value" label="工单周期频次" width="110" align="center" show-overflow-tooltip />
+ <el-table-column label="操作" width="200" fixed="right" align="center">
+ <template #default="scope">
+<!-- {{scope.row.status}}-->
+ <template v-if="scope.row.status === 1">
+ <el-button type="text" icon="el-icon-view" v-if="permission.order_log_review" @click="handleCheckDetail(scope.row, '工单审核')">审核</el-button>
<!--待审核状态-->
- <el-button type="text" icon="el-icon-warning" v-if="hasRecallPaddingBtnPermission()"
- @click="orderLogRecall(row.id)">撤回
- </el-button>
- <!-- <el-button type="text" icon="el-icon-view" @click="handleViewDetail(row)">详情</el-button> -->
+ <el-button type="text" icon="el-icon-warning" v-if="permission.order_log_recall" @click="revokeOrder(scope.row.id)">撤回</el-button>
+ <el-button type="text" icon="el-icon-view" @click="handleViewDetail(scope.row, '工单详情')">详情</el-button>
</template>
<!--已驳回-->
- <template v-if="row.status == 2">
- <el-button type="text" icon="el-icon-warning" @click="rejectDetail(row.id)">驳回原因
- </el-button>
- <el-button v-if="userInfo.user_id == row.create_user" type="text" icon="el-icon-view"
- @click="handleViewDetail(row)">编辑
- </el-button>
- <el-button v-else type="text" icon="el-icon-view" @click="handleViewDetail(row)">详情
- </el-button>
+ <template v-if="scope.row.status === 2">
+ <el-button type="text" icon="el-icon-warning" @click="rejectReason(scope.row.id, scope.row)">驳回原因</el-button>
+ <el-button type="text" icon="el-icon-view" @click="handleViewDetail(scope.row, '工单详情')">详情</el-button>
+<!-- <el-button type="text" icon="el-icon-view" v-if="userInfo.user_id === scope.row.create_user" @click="handleViewDetail(scope.row, '工单编辑')">编辑</el-button>-->
+<!-- <el-button type="text" icon="el-icon-view" v-else @click="handleViewDetail(scope.row, '工单详情')">详情</el-button>-->
</template>
<!-- 已通过 -->
- <template v-if="row.status == 3">
- <el-button type="text" icon="el-icon-view" @click="handleViewDetail(row)">详情
- </el-button>
+ <template v-if="scope.row.status === 3">
+ <el-button type="text" icon="el-icon-view" @click="handleViewDetail(scope.row, '工单详情')">详情</el-button>
</template>
<!--草稿-->
- <template v-if="row.status == 0">
- <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>
- </div>
- </template>
- <template #status="{ row }">
- <el-tag :type="getStatusTagType(row.status)">{{ mapStatus(row.status) }}</el-tag>
- </template>
- <template #keyData="{ row }">
- <el-tooltip :content="row.address" placement="top" effect="light">
- <span>{{ row.keyData }}</span>
- </el-tooltip>
- </template>
- </avue-crud>
- </basic-main-content>
+<!-- <template v-if="scope.row.status === 0">-->
+<!-- <el-button type="text" icon="el-icon-edit" @click="handleViewDetail(scope.row, '工单编辑')">编辑</el-button>-->
+<!-- <el-button type="text" icon="el-icon-position" @click="userPublishPush(scope.row.id)">发布</el-button>-->
+<!-- <el-button type="text" icon="el-icon-delete" @click="deleteOrder(scope.row.id)">删除</el-button>-->
+<!-- </template>-->
+ </template>
+ </el-table-column>
+ </el-table>
+ </div>
+ <div class="pagination">
+ <el-pagination class="ztzf-pagination" popper-class="custom-pagination-dropdown" background
+ :page-sizes="[10, 20, 30, 40, 50, 100]" v-model:current-page="orderListParams.current"
+ v-model:page-size="orderListParams.size" layout="total, sizes, prev, pager, next, jumper" :total="total"
+ @size-change="handleSizeChange" @current-change="handleCurrentChange" />
+ </div>
</el-tab-pane>
</el-tabs>
-
- <!-- 新建工单对话框 -->
- <el-dialog class="ztzf-dialog-mange" 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-form-item>
- </el-col>
- <el-col :span="6">
- <el-form-item label="关联航线" prop="file_id">
- <el-select v-model="form.file_id" placeholder="请选择航线" filterable @change="getFlyingNestBy">
- <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>
- <el-col :span="6">
- <el-form-item label="安全返航真高" prop="rth_altitude" class="safe-height">
- <el-input-number v-model="form.rth_altitude" :min="50" :max="500"></el-input-number>
- </el-form-item>
- </el-col>
- </el-row>
- <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="!device_sns.length">
- <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="6">
- <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-select>
- </el-form-item>
- </el-col>
- <el-col :span="6">
- <el-form-item label="自定义识别区" prop="enable_custom_area" class="safe-height">
- <el-checkbox v-model="form.enable_custom_area"></el-checkbox>
- </el-form-item>
- </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-form-item>
- </el-col>
-
- <el-col :span="7">
- <el-form-item label="周期频次" prop="date_range">
- <el-date-picker v-model="form.date_range" type="daterange" range-separator="至" start-placeholder="开始日期"
- end-placeholder="结束日期" :disabled-date="disabledDate" />
- </el-form-item>
- </el-col>
- <el-col :span="5">
- <div class="flex">
- <div class="flex-1 flex-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>
- </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>
-
- <el-row :gutter="20" style="height: 400px">
- <el-col :span="24">
- <map-container v-if="dialogVisible" ref="MapContainer"></map-container>
- </el-col>
- </el-row>
-
- <!-- <el-row>-->
- <!-- <div class="add-box-btns">-->
- <!-- <el-button type="danger" @click="submitForm(1)">发起</el-button>-->
- <!-- <el-button type="primary" @click="submitForm(0)">存草稿</el-button>-->
- <!-- <el-button @click="dialogVisible = false">取消</el-button>-->
- <!-- </div>-->
- <!-- </el-row>-->
- </el-form>
- <template #footer>
- <div class="dialog-footer">
- <el-button type="danger" @click="submitForm(1)" icon="el-icon-position">发起</el-button>
- <!-- <el-button type="primary" @click="submitForm(0)" icon="el-icon-document-add">存草稿</el-button> -->
- <el-button @click="dialogVisible = false" icon="el-icon-circle-close">取消</el-button>
- </div>
- </template>
- </el-dialog>
-
- <!-- 工单详情对话框 -->
- <el-dialog class="ztzf-dialog-mange" align-center 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" :rules="rules" ref="testform" label-width="100px">
- <div class="custom-steps-container">
- <!-- 标题行 -->
- <div class="steps-titles" v-if="filters.status !== '0'">
- <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>
-
- <!-- Element Steps 组件 -->
- <el-steps :active="form.active" align-center class="custom-steps" v-if="filters.status !== '0'">
- <el-step v-for="(record, index) in form.record_list" :key="index">
- <template #description>
- <span class="step-description" style="position: relative; display: inline-block">
- {{ record.user_name }}
- </span>
- <span class="step-timer">
- {{ record.interval_time_str }}
- </span>
- <div class="step-description">
- {{ record.create_time_str }}
- </div>
- </template>
- </el-step>
- </el-steps>
- </div>
-
- <el-row :gutter="20">
- <el-col :span="12">
- <el-form-item label="工单名称" prop="name">
- <el-input v-model="form.name" placeholder="请输入工单名称" :disabled="detailTitle === '工单详情'"></el-input>
- </el-form-item>
- </el-col>
- <el-col :span="6">
- <el-form-item label="关联航线" prop="file_id">
- <el-select v-model="form.file_id" placeholder="请选择航线" @change="getFlyingNestBy" filterable
- :disabled="detailTitle === '工单详情'">
- <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>
- <el-col :span="6">
- <el-form-item label="安全返航真高" prop="rth_altitude" class="safe-height">
- <el-input-number :disabled="detailTitle === '工单详情'" v-model="form.rth_altitude" :min="50"
- :max="500"></el-input-number>
- </el-form-item>
- </el-col>
- </el-row>
- <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="detailTitle === '工单详情'">
- <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="6">
- <el-form-item label="关联算法" prop="ai_types">
- <el-select v-model="form.ai_types" placeholder="请选择关联算法" multiple :disabled="detailTitle === '工单详情'">
- <el-option v-for="item in ai_types" :key="item.dictKey" :label="item.dictValue" :value="item.dictKey" />
- </el-select>
- </el-form-item>
- </el-col>
- <el-col :span="6">
- <el-form-item label="自定义识别区" prop="enable_custom_area" class="safe-height">
- <el-checkbox v-model="form.enable_custom_area" :disabled="detailTitle === '工单详情'"></el-checkbox>
- </el-form-item>
- </el-col>
- </el-row>
-
- <el-row :gutter="20">
- <el-col :span="12" prop="statusStr">
- <el-form-item label="当前状态">
- {{ mapStatus(form.status) }}
- </el-form-item>
- </el-col>
-
- <el-col :span="7">
- <el-form-item label="周期频次" prop="date_range">
- <el-date-picker v-model="form.date_range" type="daterange" range-separator="至" start-placeholder="开始日期"
- end-placeholder="结束日期" :disabled-date="disabledDate" :disabled="detailTitle === '工单详情'" />
- </el-form-item>
- </el-col>
-
- <el-col :span="5">
- <div class="flex">
- <div class="flex-1 flex-3">
- <el-select v-model="form.rep_fre_type" placeholder="请选择频次" :disabled="detailTitle === '工单详情'">
- <el-option v-for="item in cycles" :key="item" :label="item" :value="item" />
- </el-select>
- </div>
- <div class="flex-1">
- <el-time-picker v-model="form.deal_time" prop="deal_time"
- value-format="HH:mm" :picker-options="{
- selectableRange: '00:00 - 23:59',
- }" :disabled="detailTitle === '工单详情'" />
- </div>
- </div>
- </el-col>
-
- <!-- <el-col :span="3">-->
- <!-- <el-time-picker class="timeStyle" :style="{ width: pxToRem(140) }" v-model="form.deal_time" prop="deal_time"-->
- <!-- value-format="HH:mm" :picker-options="{-->
- <!-- selectableRange: '00:00 - 23:59',-->
- <!-- }" :disabled="detailTitle === '工单详情'" />-->
- <!-- </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 :disabled="detailTitle === '工单详情'"></el-input>
- </el-form-item>
- </el-col>
- </el-row>
-
- <el-row :gutter="20" style="height: 400px">
- <el-col :span="24">
- <map-container v-if="detailVisible" ref="MapContainer"></map-container>
- </el-col>
- </el-row>
- </el-form>
- <template #footer>
- <div class="dialog-footer">
- <el-button type="danger" v-if="form.status == 0 || (form.status == 2 && userInfo.user_id == form.create_user)"
- @click="submitForm(1)" icon="el-icon-position">发布
- </el-button>
- <el-button type="primary"
- v-if="form.status == 0 || (form.status == 2 && userInfo.user_id == form.create_user)" @click="submitForm(0)"
- icon="el-icon-plus">保存
- </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 && hasPaddingBtnPermission()"
- @click="orderLogPass(form.id)">通过
- </el-button>
- <el-button type="danger" v-if="form.status == 1 && hasRejectionBtnPermission()"
- @click="orderLogReject(form.id)">驳回
- </el-button>
- </div>
- </template>
- </el-dialog>
-
- <!-- 工单详情 -->
- <el-dialog class="ztzf-dialog-mange" align-center 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" v-if="filters.status !== '0'">
- <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>
-
- <!-- Element Steps 组件 -->
- <el-steps :active="form.active" align-center class="custom-steps" v-if="filters.status !== '0'">
- <el-step v-for="(record, index) in form.record_list" :key="index">
- <template #description>
- <span class="step-description" style="position: relative; display: inline-block">
- {{ record.user_name }}
- </span>
- <span class="step-timer">
- {{ record.interval_time_str }}
- </span>
- <div class="step-description">
- {{ record.create_time_str }}
- </div>
- </template>
- </el-step>
- </el-steps>
- </div>
-
- <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-form-item>
- </el-col>
- <el-col :span="6">
- <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-select>
- </el-form-item>
- </el-col>
- <el-col :span="6">
- <el-form-item label="安全返航真高" prop="rth_altitude" class="safe-height">
- <el-input-number :disabled="true" v-model="form.rth_altitude" :min="50" :max="500"></el-input-number>
- </el-form-item>
- </el-col>
- </el-row>
-
- <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>
- </el-form-item>
- </el-col>
- <el-col :span="6">
- <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>
- </el-form-item>
- </el-col>
- <el-col :span="6">
- <el-form-item label="自定义识别区" prop="enable_custom_area" class="safe-height">
- <el-checkbox v-model="form.enable_custom_area" :disabled="true"></el-checkbox>
- </el-form-item>
- </el-col>
- </el-row>
-
- <el-row :gutter="20">
- <el-col :span="12" prop="statusStr">
- <el-form-item label="当前状态">
- {{ mapStatus(form.status) }}
- </el-form-item>
- </el-col>
-
- <el-col :span="7">
- <el-form-item label="周期频次" prop="date_range">
- <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="5">
- <div class="flex">
- <div class="flex-1 flex-3">
- <el-select v-model="form.rep_fre_type" placeholder="请选择频次" :disabled="true">
- <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" :disabled="true"
- value-format="HH:mm" :picker-options="{
- selectableRange: '00:00 - 23:59',
- }" />
- </div>
- </div>
- </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="{-->
- <!-- 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-form-item>
- </el-col>
- </el-row>
-
- <el-row :gutter="20" style="height: 400px">
- <el-col :span="24">
- <map-container v-if="detailVisibleCopy" ref="MapContainer"></map-container>
- </el-col>
- </el-row>
-
- </el-form>
- <template #footer>
- <div class="dialog-footer">
- <el-button type="danger" v-if="form.status == 0 || (form.status == 2 && userInfo.user_id == form.create_user)"
- @click="submitForm(1)" icon="el-icon-position">发布
- </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 && hasPaddingBtnPermission()" @click="orderLogPass(form.id)"
- icon="el-icon-check">通过
- </el-button>
- <el-button type="danger" v-if="form.status == 1 && hasRejectionBtnPermission()"
- @click="orderLogReject(form.id)" icon="el-icon-close">驳回
- </el-button>
- <el-button @click="detailVisibleCopy = false" icon="el-icon-circle-close">取消</el-button>
- </div>
- </template>
- </el-dialog>
- </basic-container>
+ </div>
+ <AddEditDetails v-model:show="isShowAddEditDetails" @refresh="refreshGetOrderList" />
</template>
-<script>
-import { pxToRem, pxToRemNum } from '@/utils/rem'
-import { calculateDefaultRange } from '@/utils/util'
+<script setup>
+import SearchBox from './component/SearchBox.vue'
+import AddEditDetails from './component/AddEditDetails.vue'
+import NProgress from 'nprogress'
+import { useStore } from 'vuex'
+import { useRoute } from 'vue-router'
+import { downloadXls } from '@/utils/util'
import {
getList,
- saveUpdateOrderLog,
orderLogDetails,
- orderLogRecall,
- orderLogReject,
- orderLogPass,
orderLogExport,
jobStatusNum,
- userPublish,
deleteOrderLog,
- getWaylineMaxTerrainHeight
} from '@/api/tickets/orderLog'
-import { getTicketInfo } from '@/api/tickets/ticket'
+import { newGetWorkspacesPage } from '@/api/resource/wayline'
import { getDictionaryByCode } from '@/api/system/dictbiz'
-import { getWaylineFileListByArea, newGetWorkspacesPage } 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'
+import { cloneDeep } from 'lodash';
+import { computed, onMounted } from 'vue';
+import { ElMessage, ElMessageBox } from 'element-plus'
import dayjs from 'dayjs'
import 'dayjs/locale/zh-cn' // 导入中文语言包
import weekday from 'dayjs/plugin/weekday'
-import elTooltipCopy from '@/components/ElTooltipCopy.vue'
-import { CircleClose, Promotion, Select } from '@element-plus/icons-vue'
-import { useStore } from 'vuex'
-import _ from 'lodash'
-// const store = useStore()
-dayjs.extend(weekday)
-dayjs.locale('zh-cn')
+import { aiImagesPage } from '@/api/dataCenter/dataCenter';
-// const lastHeight = computed(() => store.state.common.lastHeight)
+const store = useStore()
+const route = useRoute()
-export default {
- components: { elTooltipCopy },
- name: 'TicketPage',
- data () {
- return {
- activeTab: 'all',
- getlastHeight: 0,
- tabs: [
- { label: '全部工单', name: 'all', value: null, count: 0 },
- { label: '待审核', name: 'WAIT_AUDIT', value: 1, count: 0 },
- { label: '已驳回', name: 'REJECTED', value: 2, count: 0 },
- { label: '已通过', name: 'PASS', value: 3, count: 0 },
- // { label: '草稿', name: 'DRAFT', value: 0, count: 0 }
- ],
- filters: {
- key_word: '',
- create_dept: '',
- start_date: null,
- end_date: null,
- file_id: '',
- ai_types: [],
- status: '',
- dateRange: []
- },
- departments: [],
- types: [],
- device_sns: [],
- ai_types: [],
- wayLineList: [],
- detailTitle: '编辑工单',
- detailVisibleCopy: false,
+const userInfo = computed(() => store.state.user.userInfo)
+const permission = computed(() => store.state.user.permission);
- statuses: [
- // { label: '草稿', value: '0' },
- { label: '待审核', value: '1' },
- { label: '已驳回', value: '2' },
- { label: '已通过', value: '3' }
- ],
- //周期
- cycles: ['每天', '周一', '周二', '周三', '周四', '周五', '周六', '周末', '周天', '工作日'],
+const isShowAddEditDetails = ref(false)
- tableData: [],
- option: {
- index: true,
- indexWidth: 66,
- indexLabel: '序号',
- indexFixed: true,
+const activeTab = ref('all')
+provide('activeTab', activeTab)
+let tabsList = ref([
+ // { label: '我的工单', name: 'my_order', value: null, count: 0 },
+ // { label: '全部工单', name: 'all', value: null, count: 0 },
+ // { label: '待审核', name: 'WAIT_AUDIT', value: 1, count: 0 },
+ // { label: '已驳回', name: 'REJECTED', value: 2, count: 0 },
+ // { label: '已通过', name: 'PASS', value: 3, count: 0 },
+ // { label: '草稿', name: 'DRAFT', value: 0, count: 0 }
+])
+function filteredTabs () {
+ if (permission?.value.orderLog_tab_allOrders) {
+ tabsList.value.push({ label: '全部工单', name: 'all', value: null, count: 0 })
+ }
+ if (permission?.value.orderLog_tab_pending) {
+ tabsList.value.push({ label: '待审核', name: 'WAIT_AUDIT', value: 1, count: 0 })
+ }
+ if (permission?.value.orderLog_tab_reject) {
+ tabsList.value.push({ label: '已驳回', name: 'REJECTED', value: 2, count: 0 })
+ }
+ if (permission?.value.orderLog_tab_pass) {
+ tabsList.value.push({ label: '已通过', name: 'PASS', value: 3, count: 0 })
+ }
+ activeTab.value = tabsList.value[0].name
+ // tab值传
+ orderListParams.status = tabsList.value[0].value
+ // tabsList.value
+ // .map(tab => {
+ // if (tab.name === 'all') {
+ // return { ...tab, isShow: permission?.value.orderLog_tab_allOrders }
+ // }
+ // if (tab.name === 'WAIT_AUDIT') {
+ // return { ...tab, isShow: permission?.value.orderLog_tab_pending }
+ // }
+ // if (tab.name === 'REJECTED') {
+ // return { ...tab, isShow: permission?.value.orderLog_tab_reject }
+ // }
+ // if (tab.name === 'PASS') {
+ // return { ...tab, isShow: permission?.value.orderLog_tab_pass }
+ // }
+ // // return { ...tab, isShow: true }
+ // })
+ // .filter(tab => tab.isShow)
+}
- align: 'left',
- border: true,
- stripe: false,
- searchShow: true,
- searchBtnText: '搜索',
- searchMenuSpan: 6,
- addBtnText: '新建工单',
- addBtn: false,
- viewBtn: false,
- editBtn: false,
- delBtn: false,
- cancelBtn: true,
- cancelBtnText: '取消',
+// tab切换
+function handleTabChange (tab) {
+ activeTab.value = tab.paneName
+ // orderListParams.status = ''
+ // if (tab.paneName === 'WAIT_AUDIT') {
+ // orderListParams.status = '1'
+ // } else if (tab.paneName === 'REJECTED') {
+ // orderListParams.status = '2'
+ // } else if (tab.paneName === 'PASS') {
+ // orderListParams.status = '3'
+ // } else if (tab.paneName === 'DRAFT') {
+ // orderListParams.status = '0'
+ // }
+ // refreshGetOrderList()
+}
- menu: true,
- menuWidth: 210,
- menuClassName: 'cur-menu',
-
- height: 'auto',
- calcHeight: 196,
-
- column: [
- {
- label: '工单编号',
- prop: 'job_info_num',
- width: 150
- },
- {
- label: '工单名称',
- prop: 'name',
- width: 100,
- ellipsis: true,
- overHidden: true,
- showOverflowTooltip: true
- },
- {
- label: '工单状态',
- prop: 'status',
- width: 88,
- showOverflowTooltip: true
- },
- {
- label: '所属单位',
- prop: 'dept_name',
- ellipsis: true,
- showOverflowTooltip: true
- },
- {
- label: '创建时间',
- prop: 'create_time',
- width: 144,
- ellipsis: true,
- showOverflowTooltip: true
- },
- {
- label: '已执行次数',
- prop: 'job_num',
- width: 70,
- ellipsis: true,
- showOverflowTooltip: true
- },
- {
- label: '工单内容',
- prop: 'content',
- ellipsis: true,
- overHidden: true,
- showOverflowTooltip: true
- },
- {
- label: '关联航线',
- prop: 'wayline_name',
- ellipsis: true,
- overHidden: true,
- showOverflowTooltip: true
- },
- {
- label: '关联算法',
- prop: 'ai_type_str',
- width: 100,
- ellipsis: true,
- overHidden: true,
- showOverflowTooltip: true
- },
- {
- label: '关联机巢',
- prop: 'device_names',
- width: 100,
- ellipsis: true,
- overHidden: true,
- showOverflowTooltip: true
- },
-
- {
- label: '创建人',
- prop: 'creator_name',
- width: 96,
- ellipsis: true,
- overHidden: true,
- showOverflowTooltip: true
- },
- // {
- // label: '关联机巢', prop: 'device_names', width: 112, ellipsis: true, overHidden: true,
- // showOverflowTooltip: true,
- // },
- {
- label: '工单周期频次',
- prop: '',
- width: 110,
- formatter: row => this.formatCycleTime(row),
- html: true,
- ellipsis: true,
- showOverflowTooltip: true
- // overHidden: true
- }
- ]
- },
-
- page: {
- pageSize: 10,
- currentPage: 1,
- total: 0
- },
- dialogVisible: false,
- detailVisible: false,
- currentDetail: {},
- form: {},
- rules: {
- name: [
- { required: true, message: '请输入工单名称', trigger: 'blur' },
- { max: 100, message: '工单名称不能超过100个字', trigger: 'blur' }
- ],
- file_id: [{ required: true, message: '需要选择航线', trigger: 'change' }],
- device_sns: [{ required: true, message: '请选择机巢', trigger: 'change' }],
- content: [
- { required: true, message: '请输入工单内容', trigger: 'blur' },
- { max: 255, message: '工单内容不能超过255个字', trigger: 'blur' }
- ],
- date_range: [{ required: true, message: '请选择时间不能为空', trigger: 'blur' }],
- rep_fre_type: [{ required: true, message: '请选择周期', trigger: 'blur' }],
- deal_time: [{ required: true, message: '请选择执行时间', trigger: 'blur' }]
- },
- loading: false,
- globalCounts: {},
- mapLoaded: false,
-
- // 配置时间选择器默认配置
- datePickerDefaultVal: calculateDefaultRange()
+// 更新统计数
+async function updateGlobalCounts() {
+ let res = await jobStatusNum()
+ res.data.data.forEach(item => {
+ const tab = tabsList.value.find(t => t.name === item.dict_key)
+ if (tab) {
+ tab.count = item.num
}
- },
- 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.departments = dept_data.map(item => ({
- label: item.dept_name,
- value: item.id
- }))
- },
- mounted () {
- this.fetchTableData()
- const id = this.$route.query.id
- console.log('idddddd', id)
+ })
+}
- if (id) {
- // 确保 id 存在
- this.handleViewDetail({ id })
- const find = this.$store.state.tags.bsTagList.find(i => i.path === '/tickets/orderLog')
- find && (find.query = {})
- } else {
- console.error('工单ID不存在!')
+// 获取航线
+let lineList = ref([])
+provide('lineList', lineList)
+function getLines() {
+ const formData = {
+ name: '',
+ waylineType: undefined,
+ current: 1,
+ size: 99999999,
+ descs: 'update_time'
+ }
+ newGetWorkspacesPage(formData).then(res => {
+ lineList.value = res.data.data.records
+ })
+}
+
+// 获取算法
+let aiTypeList = ref([])
+provide('aiTypeList', aiTypeList)
+function aitypeList() {
+ getDictionaryByCode('SF').then(res => {
+ aiTypeList.value = res.data.data['SF']
+ })
+}
+
+const total = ref(0)
+const orderListParams = reactive({
+ current: 1,
+ size: 10,
+ searchParams: {},
+ status: ''
+})
+
+const orderListTable = ref([])
+function getTableList() {
+ orderListParams.searchParams.ai_types = orderListParams.searchParams.ai_typesValue ? [ orderListParams.searchParams.ai_typesValue] : null
+ const apiParams = {
+ ...cloneDeep(orderListParams),
+ ...cloneDeep(orderListParams.searchParams)
+ }
+ getList(apiParams, orderListParams.current, orderListParams.size).then(res => {
+ orderListTable.value = res.data.data.records
+ total.value = res.data.data.total
+ // updateGlobalCounts()
+ })
+}
+
+// 分页大小改变
+const handleSizeChange = val => {
+ orderListParams.size = val
+ getTableList()
+}
+
+// 页码改变
+const handleCurrentChange = val => {
+ orderListParams.current = val
+ getTableList()
+}
+
+// 传参查询条件
+const searchClick = params => {
+ orderListParams.current = 1
+ orderListParams.size = 10
+ orderListParams.searchParams = params
+ getTableList()
+}
+
+const refreshGetOrderList = () => {
+ orderListParams.current = 1
+ orderListParams.size = 10
+ getTableList()
+}
+
+function getOrderStatus (status) {
+ const statusTextMap = {
+ // 0: '草稿',
+ 1: '待审核',
+ 2: '已驳回',
+ 3: '已通过'
+ }
+ return statusTextMap[status] || '未知状态'
+}
+function getStatusTagType (status) {
+ const statusMap = {
+ 1: 'warning',
+ 2: 'info',
+ 3: 'primary',
+ 4: 'success',
+ 5: 'danger'
+ }
+ return statusMap[status] || 'info'
+}
+// 新增按钮 和 导出按钮
+// function hasAddBtnPermission () {
+// return permission && permission?.value.order_log_add
+// }
+
+// 新建工单、编辑、详情
+const txtTitle = ref('新建工单')
+const rowObj = ref({})
+provide('txtTitle', txtTitle)
+provide('rowObj', rowObj)
+function handleAddOrder() {
+ txtTitle.value = '新建工单'
+ isShowAddEditDetails.value = true
+}
+// 详情、编辑
+function handleViewDetail(row, txt) {
+ rowObj.value = row
+ txtTitle.value = txt
+ isShowAddEditDetails.value = true
+}
+
+// 审核
+function handleCheckDetail(row, txt) {
+ rowObj.value = row
+ txtTitle.value = txt
+ isShowAddEditDetails.value = true
+}
+
+// 导出
+async function exportData() {
+ try {
+ await ElMessageBox.confirm('是否导出智飞工单数据?', '提示', {
+ confirmButtonText: '确定',
+ cancelButtonText: '取消',
+ type: 'warning',
+ })
+
+ NProgress.start()
+
+ orderListParams.searchParams.ai_types = orderListParams.searchParams.ai_typesValue ? [ orderListParams.searchParams.ai_typesValue] : null
+ const apiParams = {
+ ...cloneDeep(orderListParams),
+ ...cloneDeep(orderListParams.searchParams)
}
- },
- computed: {
- CircleClose () {
- return CircleClose
- },
- ...mapGetters(['userInfo', 'permission']),
- safeHeight () {
- const safeHeight = this.$store.state.common.safeHeight
- return Number(safeHeight) || 0 // 确保是数字类型
- },
- positionsArr () {
- const positionsArr = this.$store.state.common.positionsArr
- return positionsArr || [] // 确保是数字类型
- },
- filteredTabs () {
- // rejection_and_draft 权限控制“已驳回”和“草稿”tab
- // console.log(this.permission, '权限信息')
- // console.log(this.userInfo, '权限信息22')
- const canShowRejectAndDraft = this.permission?.rejection_and_draft === true
- return this.tabs
- .map(tab => {
- if (tab.name === 'DRAFT') {
- return { ...tab, isShow: canShowRejectAndDraft }
- }
- if (tab.name === 'REJECTED') {
- return { ...tab, isShow: canShowRejectAndDraft }
- }
- return { ...tab, isShow: true }
- })
- .filter(tab => tab.isShow)
+ const res = await orderLogExport(apiParams)
+ downloadXls(res.data, `智飞工单${dayjs().format('YYYY-MM-DD')}.xlsx`)
+ ElMessage.success('导出成功')
+
+ } catch (error) {
+ if (error !== 'cancel') {
+ ElMessage.error('导出失败')
+ console.error('导出错误:', error)
}
- },
+ } finally {
+ NProgress.done()
+ }
+}
- methods: {
- handleCellClick (row, column) {
- if (column.no === 1) {
- navigator.clipboard.writeText(row.job_info_num).then(() => {
- this.$message.success('复制工单编号成功')
- })
- } else if (column.no === 2) {
- navigator.clipboard.writeText(row.name).then(() => {
- this.$message.success('复制工单名称成功')
- })
- } else if (column.no === 4) {
- navigator.clipboard.writeText(row.dept_name).then(() => {
- this.$message.success('复制所属单位成功')
- })
- } else if (column.no === 8) {
- navigator.clipboard.writeText(row.wayline_name).then(() => {
- this.$message.success('复制关联航线成功')
- })
- } else if (column.no === 9) {
- navigator.clipboard.writeText(row.ai_type_str).then(() => {
- this.$message.success('复制关联算法成功')
- })
- }
- },
- disabledDate (time) {
- return time.getTime() < Date.now() - 8.64e7 // 86400000 = 24 * 60 * 60 * 1000
- },
- 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
- 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()
- })
- },
- selectionClear () {
- this.selectionList = []
- this.$refs.crud.toggleSelection()
- },
- async loadAMapScripts () {
- try {
- // await loadAMap();
- // await loadAMapUI();
- this.mapLoaded = true
- } catch (error) {
- console.error('Failed to load AMap scripts:', error)
- this.$message.error('地图加载失败,请检查网络或API Key配置')
- }
- },
- formatCycleTime (row) {
- return `${row.cycle_time_value}`
- },
-
- async fetchTableData () {
- this.loading = true
- try {
- 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('接口返回数据格式不正确')
- }
-
- const { total, records } = response.data.data
- this.tableData = records.map(item => {
- return item
- })
-
- // 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
- } finally {
- this.loading = false
- }
- },
-
- 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 = {
- key_word: this.filters.key_word || undefined,
- create_dept: this.filters.create_dept || undefined,
- ai_types:
- this.filters.ai_types && this.filters.ai_types.length > 0
- ? [this.filters.ai_types]
- : null,
- file_id: this.filters.file_id || undefined,
- status: this.filters.status !== '' ? Number(this.filters.status) : currentTab?.value,
- dept_id: this.filters.department || undefined,
- create_start_date: this.filters.dateRange?.[0]
- ? this.formatDate(this.filters.dateRange[0])
- : undefined,
- create_end_date: this.filters.dateRange?.[1]
- ? this.formatDate(this.filters.dateRange[1]).replace('00:00:00', '23:59:59')
- : undefined,
-
- start_date: this.filters.cycleDateRange?.[0]
- ? this.formatDate(this.filters.cycleDateRange[0])
- : undefined,
- 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
- },
- sizeChange (pageSize) {
- this.page.pageSize = pageSize
- },
- async submitForm (status) {
- this.$refs.testform.validate(async valid => {
- if (valid) {
- 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])
-
- // 如果选中日期包含当前天,那么选中的时间点不能小于当前时间
- if (this.form.deal_time) {
- const selectedDate = dayjs(this.form.date_range[0]).format('YYYY-MM-DD')
- const selectedTime = dayjs(selectedDate + ' ' + this.form.deal_time).toDate()
- const now = new Date()
-
- if (selectedDate === dayjs().format('YYYY-MM-DD') && selectedTime < now) {
- return this.$message.warning('任务时间不能小于当前时间')
- }
- }
- let checkedList = this.device_sns.filter(item => this.form.device_sns.includes(item.device_sn));
- const maxItem = checkedList.reduce((max, item) => {
- return item.drone_height > max.drone_height ? item : max;
- });
- // 返航绝对高度
- let backHeight = this.form.rth_altitude // + maxItem.drone_height
- console.log(backHeight,'backHeight')
- // 安全起飞高度
- let positions = this.positionsArr
- positions.unshift({latitude: maxItem.latitude, longitude: maxItem.longitude, height:maxItem.drone_height })
- positions.push({latitude: maxItem.latitude, longitude: maxItem.longitude, height:maxItem.drone_height})
- let resultHeight = 0
- let valueHeight = 0
- await getWaylineMaxTerrainHeight(positions).then(res => {
- resultHeight = (this.safeHeight+maxItem.drone_height) > (res.data.data + 30) ? (this.safeHeight+maxItem.drone_height) : (res.data.data + 30)
- valueHeight = (resultHeight - maxItem.drone_height) > backHeight ? _.round(resultHeight - maxItem.drone_height) : 0
- })
-
- console.log('打印比较值', valueHeight)
-
- if (valueHeight > 0) {
- this.$confirm(`当前返航高度存在安全隐患,建议调整为${valueHeight}米以上后进行发布`, '提示', {
- confirmButtonText: '确定',
- cancelButtonText: '取消',
- type: 'warning'
- }).then(async () => {
- const submitData = {
- ...this.form,
- status: status,
- ai_types: this.form.ai_types?.length ? this.form.ai_types : []
- }
- 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()
- }).catch(() => {
-
- })
- } else {
- const submitData = {
- ...this.form,
- status: status,
- ai_types: this.form.ai_types?.length ? this.form.ai_types : []
- }
- 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()
- }
- }
- })
- },
- //驳回原因显示
- async rejectDetail (id) {
- const response = await orderLogDetails(id)
- let data = response.data.data
- this.$confirm(data.remark, '驳回原因', {
- confirmButtonText: '确定',
- cancelButtonText: '取消',
- type: 'warning'
- }).then(() => {
- this.form = {
- ...response.data.data
- }
- // this.detailVisible = true
- this.handleViewDetail(data)
- })
- },
- 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`
- },
-
- mapStatus (status) {
- const statusTextMap = {
- 0: '草稿',
- 1: '待审核',
- 2: '已驳回',
- 3: '已通过'
- }
- return statusTextMap[status] || '未知状态'
- },
-
- getStatusTagType (status) {
- const statusMap = {
- 1: 'warning',
- 2: 'info',
- 3: 'primary',
- 4: 'success',
- 5: 'danger'
- }
- return statusMap[status] || 'info'
- },
-
- handleTabChange (tab) {
- this.activeTab = tab.props?.name || tab.name
- this.filters.status = ''
- if (tab.props?.name === 'WAIT_AUDIT') {
- this.filters.status = '1'
- } else if (tab.props?.name === 'REJECTED') {
- this.filters.status = '2'
- } else if (tab.props?.name === 'PASS') {
- this.filters.status = '3'
- } else if (tab.props?.name === 'DRAFT') {
- this.filters.status = '0'
- }
- this.page.currentPage = 1
- this.fetchTableData()
- },
-
- handleSearch () {
- this.page.currentPage = 1
- this.fetchTableData()
- },
-
- handleReset () {
- this.filters = {
- keyword: '',
- department: '',
- type: '',
- dateRange: [],
- status: ''
- }
- this.page.currentPage = 1
- this.fetchTableData()
- },
-
- currentChange (currentPage) {
- this.page.currentPage = currentPage
- },
-
- async updateGlobalCounts () {
- const counts = {
- all: 0,
- DRAFT: 0,
- WAIT_AUDIT: 0,
- REJECTED: 0,
- PASS: 0
- }
- 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)
- if (tab) {
- tab.count = item.num
- }
- })
- },
-
- handleAdd () {
- this.form = {}
- this.form.rth_altitude = 120
- this.dialogVisible = true
-
- this.$nextTick(() => {
- if (this.$refs.MapContainer && this.$refs.MapContainer.initAddEntity) {
- this.$refs.MapContainer.initAddEntity('initPosition')
- }
- })
-
- //航线列表
- this.asyncgetWaylineFileListByArea()
- },
-
- resetForm () {
- this.form = {
- name: '',
- type: '',
- handler: '',
- algorithm: '',
- location: '',
- address: '',
- content: '',
- photos: []
- }
- if (this.$refs.testform) {
- this.$refs.testform.resetFields()
- }
- },
-
- formatLocation (location) {
- if (!Array.isArray(location)) {
- return '未知位置'
- }
- return `${location[0].toFixed(6)}, ${location[1].toFixed(6)}`
- },
- async handleViewDetail (row) {
- const response = await orderLogDetails(row.id)
- const data = response.data.data
- this.form = {
- ...data
- }
-
- // 更新机巢列表
- this.device_sns = data.device_map_infos;// 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.userInfo.user_id !== this.form.create_user))
- ? (this.detailTitle = '工单详情')
- : (this.detailTitle = '编辑工单')
-
- this.detailVisible = true
- // 更新航线列表,追加 wayline_file_region_vo 数据
- if (data.wayline_file_region_vo) {
- const newWayline = data.wayline_file_region_vo
-
- // 检查是否已经存在于 this.wayLineList 中
- const isDuplicate = this.wayLineList.some(
- item => item.wayline_id === newWayline.wayline_id
- )
-
- if (!isDuplicate) {
- this.wayLineList.push(newWayline)
- }
- }
-
- this.initMapLine(data.device_map_infos)
- const find = this.$store.state.tags.bsTagList.find(i => i.path === '/tickets/orderLog')
- find && (find.query = {})
- },
- async handleCheckDetail (row) {
- const response = await orderLogDetails(row.id)
- const data = response.data.data
- this.form = {
- ...data
- }
- // 更新航线列表,追加 wayline_file_region_vo 数据
- // 更新航线列表,追加 wayline_file_region_vo 数据
- if (data.wayline_file_region_vo) {
- const newWayline = data.wayline_file_region_vo
-
- // 检查是否已经存在于 this.wayLineList 中
- const isDuplicate = this.wayLineList.some(
- item => item.wayline_id === newWayline.wayline_id
- )
-
- if (!isDuplicate) {
- this.wayLineList.push(newWayline)
- }
- }
- // 更新机巢列表
- this.device_sns = data.device_list
- this.detailVisibleCopy = true
- this.initMapLine(data.device_map_infos)
- },
- //导出
- async exportData () {
- this.$confirm('是否智飞工单数据?', '提示', {
- confirmButtonText: '确定',
- cancelButtonText: '取消',
- type: 'warning'
- }).then(() => {
- NProgress.start()
- let params = this.getQueryParam()
- orderLogExport(params).then(res => {
- downloadXls(res.data, `智飞工单${this.$dayjs().format('YYYY-MM-DD')}.xlsx`)
- NProgress.done()
- })
- })
- },
- 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
- },
- hasPaddingBtnPermission () {
- // undefined 或 false 都返回 false,只有 true 返回 true
- // console.log('权限检查:', this.permission)
- return this.permission && this.permission.order_log_review === true
- },
- hasRecallPaddingBtnPermission () {
- // undefined 或 false 都返回 false,只有 true 返回 true
- // console.log('权限检查:', this.permission)
- // 智飞工单撤回
- return this.permission && this.permission.order_log_recall === true
- },
- //驳回按钮权限
- hasRejectionBtnPermission () {
- // undefined 或 false 都返回 false,只有 true 返回 true
- // console.log('权限检查:', this.permission)
- return this.permission && this.permission.rejection_btn === true
- },
- //自己点发布
- userPublishPush (id) {
- this.$confirm('确定发布吗?', '提示', {
- confirmButtonText: '确定',
- cancelButtonText: '取消',
- type: 'warning'
- }).then(() => {
- let response = userPublish(id)
- this.$message.success('发布成功')
- this.fetchTableData()
- })
- },
-
- //删除
- deleteOrderLog (id) {
- this.$confirm('确定删除吗?', '提示', {
- confirmButtonText: '确定',
- cancelButtonText: '取消',
- type: 'warning'
- }).then(() => {
- let response = deleteOrderLog(id)
- this.$message.success('删除')
- this.fetchTableData()
- })
- },
- refreshChange () {
- this.fetchTableData()
- },
- //获取航线列表
- async asyncgetWaylineFileListByArea (name) {
- const formData = {
- name: '',
- waylineType: undefined,
- current: 1,
- size: 99999999,
- descs: 'update_time'
- }
- var wayLineListResponse = await newGetWorkspacesPage(formData)
- this.wayLineList = wayLineListResponse.data.data.records
-
- this.initMapLine()
- },
-
- initMapLine (infos = {}, cb = () => {
- }) {
- let currentLine = this.wayLineList.find(item => item.wayline_id == this.form.file_id)
-
- if (!currentLine) return
-
- this.$nextTick(() => {
- if (this.$refs.MapContainer && this.$refs.MapContainer.initAddEntity) {
- this.$refs.MapContainer.initAddEntity('polyline', {
- url: `${import.meta.env.VITE_APP_AIRLINE_URL + currentLine.object_key
- }?_t=${new Date().getTime()}`,
- type: currentLine.wayline_type,
- cb,
- infos
- })
- }
- })
- },
-
- //可飞行机巢列表
- async getFlyingNestBy (waylineId) {
- const that = this
-
- // 先清空
- that.form.device_sns = []
- that.device_sns = []
-
- this.initMapLine({}, async polygon => {
- const currentLine = that.wayLineList.find(item => item.wayline_id === waylineId)
- //按照航线来
- const params = {
- type: ['2', '4', '5'].includes(currentLine.wayline_type) ? 2 : 0,
- wayline_id: waylineId,
- polygon
- }
- var wayLineListResponse = await getFlyingNestBy(params)
- // 再赋值
- that.device_sns = wayLineListResponse.data.data
- })
- },
-
- //撤回
- async orderLogRecall (id) {
- this.$confirm('确定撤回则到草稿箱。', '是否撤回?', {
- confirmButtonText: '确定',
- cancelButtonText: '取消',
- type: 'warning'
- }).then(async () => {
- let reposne = await orderLogRecall(id)
- this.handleSearch()
- })
- },
- onLoad () {
- this.fetchTableData()
- },
- /**
- * 通过
- */
- async orderLogPass (id) {
- let response = await orderLogPass(id)
- let data = response.data.data
- this.$message.success('审核通过')
- this.detailVisibleCopy = false
-
- this.detailVisible = false
-
- this.page.currentPage = 1
- this.onLoad(this.page, this.query)
- // this.handleViewDetail()
- },
- /**
- * 驳回
- */
- 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
-
- // this.detailVisible = false
-
- this.page.currentPage = 1
- this.onLoad(this.page, this.query)
- })
- }
- },
- watch: {
- // lastHeight(newVal, oldVal) {
- // console.log('lastHeight', newVal, oldVal);
- // this.getlastHeight = newVal;
- // },
- tableData: {
- handler () {
- // this.updateTabCounts()
- },
- deep: true
+// 撤回工单
+async function revokeOrder(id) {
+ try {
+ await ElMessageBox.confirm('撤回会删除本工单,需重新提交?', '是否撤回', {
+ confirmButtonText: '确定',
+ cancelButtonText: '取消',
+ type: 'warning'
+ })
+ // 用户点击确定,执行撤回操作
+ // await orderLogRecall(id)
+ await deleteOrderLog(id)
+ // 显示成功消息
+ ElMessage.success('撤回成功')
+ // 触发搜索刷新
+ refreshGetOrderList()
+ } catch (error) {
+ // 用户点击取消,不执行任何操作
+ if (error !== 'cancel') {
+ ElMessage.error('撤回失败')
+ console.error('撤回错误:', error)
}
}
}
+
+// 驳回原因
+async function rejectReason(id, row) {
+ try {
+ const res = await orderLogDetails(id)
+ const data = res.data.data
+
+ await ElMessageBox.confirm(data.remark, '驳回原因', {
+ confirmButtonText: '确定',
+ cancelButtonText: '取消',
+ type: 'warning'
+ })
+
+ // 用户点击确定后的逻辑 TODO
+ // form.value = { ...res.data.data }
+ handleViewDetail(row, userInfo.value.user_id === row.create_user ? '工单编辑':'工单详情')
+
+ } catch (error) {
+ // 用户点击取消,不执行任何操作
+ if (error !== 'cancel') {
+ ElMessage.error('操作失败')
+ console.error('错误:', error)
+ }
+ }
+}
+
+function handleCellClick (row, column) {
+ if (column.no === 1) {
+ navigator.clipboard.writeText(row.job_info_num).then(() => {
+ ElMessage.success('复制工单编号成功')
+ })
+ } else if (column.no === 2) {
+ navigator.clipboard.writeText(row.name).then(() => {
+ ElMessage.success('复制工单名称成功')
+ })
+ } else if (column.no === 4) {
+ navigator.clipboard.writeText(row.dept_name).then(() => {
+ ElMessage.success('复制所属单位成功')
+ })
+ } else if (column.no === 8) {
+ navigator.clipboard.writeText(row.wayline_name).then(() => {
+ ElMessage.success('复制关联航线成功')
+ })
+ } else if (column.no === 9) {
+ navigator.clipboard.writeText(row.ai_type_str).then(() => {
+ ElMessage.success('复制关联算法成功')
+ })
+ }
+}
+
+// 删除
+async function deleteOrder(id) {
+ try {
+ await ElMessageBox.confirm('确定删除吗?', '提示', {
+ confirmButtonText: '确定',
+ cancelButtonText: '取消',
+ type: 'warning'
+ })
+ deleteOrderLog(id)
+ ElMessage.success('删除成功')
+ // 触发搜索刷新
+ refreshGetOrderList()
+ } catch (error) {
+ if (error !== 'cancel') {
+ ElMessage.error('删除失败')
+ console.error('删除错误:', error)
+ }
+ }
+}
+
+onMounted(() => {
+ getLines()
+ aitypeList()
+ filteredTabs()
+ getTableList()
+ updateGlobalCounts()
+ if (route.query.id) {
+ route.query.status === '1' ? handleCheckDetail({ id: route.query.id}, '工单审核') : handleCheckDetail({ id: route.query.id}, '工单详情')
+ }
+})
</script>
<style lang="scss" scoped>
-//:deep(.el-input-number) {
-// width: 200px;
-//}
-
-//:deep(.el-form-item__label) {
-// width: 100px !important;
-//}
-
-::v-deep(.el-tabs) {
- height: 100%;
+.order-log-update {
+ height: 0;
+ flex: 1;
+ margin: 0 10px 10px 10px;
+ background-color: #ffffff;
+ padding: 10px 20px;
+ border-radius: 5px;
display: flex;
flex-direction: column;
- .el-tabs__header {
- order: 1;
+ // 表格
+ .task-table {
+ //height: 0;
+ //flex: 1;
+ height: calc(100vh - 380px);
+ margin-top: 18px;
+ overflow: auto;
}
- .el-tabs__content {
- order: 2;
+ .btnItem {
+ height: 27px;
+ line-height: 27px;
+ border-radius: 0px 0px 0px 0px;
+ border: 1px solid #51a8ff;
+ font-family: Segoe UI, Segoe UI;
+ font-weight: 400;
+ font-size: 14px;
+ color: #1C5CFF;
+ padding: 0 10px;
+ display: inline-block;
+ margin-right: 10px;
+ cursor: pointer;
+
+ &.turnBack {
+ border: 1px solid #ffa500;
+ color: #ffa500;
+ }
+
+ &.cancelTask {
+ border: 1px solid #00AA2D;
+ color: #00AA2D;
+ }
+ }
+ // 分页
+ :deep(.custom-header th.el-table__cell) {
+ color: rgba(0, 0, 0, 0.85);
}
- .el-tabs__content {
- height: 0;
- flex: 1;
+ :deep(.el-table td.el-table__cell) {
+ color: #606266;
+ }
+
+ :deep(.el-pagination) {
display: flex;
- flex-direction: column;
-
- .el-tab-pane {
- height: 0;
- flex: 1;
- display: flex;
- flex-direction: column;
- }
- }
-}
-
-.filter-bar {
- // align-items: center;
-
- .search-bar-box {
- margin-bottom: 15px;
- display: flex;
- justify-content: space-between;
- gap: 8px;
-
- &-item {
- flex: 1;
-
- &> ::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;
- }
- }
-}
-
-:deep(.el-pagination .el-select) {
- width: 128px !important;
-}
-
-.step-timer {
- position: absolute;
- left: 80%;
- top: 50%;
- transform: translateY(-50%);
- width: 100px;
- margin-left: 4px;
- color: #666;
- font-size: 12px;
-}
-
-:deep(.el-textarea__inner) {
- padding: 8px 12px;
-}
-
-:deep(.el-input__inner),
-:deep(.el-select),
-:deep(.el-select .el-input) {
- width: 100%; // 确保所有输入框和选择框宽度一致
-}
-
-:deep(.el-textarea__inner) {
- resize: none;
-}
-
-.action-bar {
- margin-bottom: 16px;
-}
-
-.el-tabs {
- :deep(.el-tabs__content) {
- overflow: visible;
- }
-}
-
-.el-dialog {
- .el-form-item {
- margin-bottom: 10px;
- }
-}
-
-.el-upload {
- width: 100%;
- display: flex;
- flex-wrap: wrap;
-
- :deep(.el-upload-list__item) {
- transition: all 0.3s ease;
+ justify-content: right;
}
- :deep(.el-upload-list__item:hover) {
- background-color: #f5f7fa;
+ :deep(.el-pagination button) {
+ background: center center no-repeat none !important;
+ color: #8eb8ea !important;
}
-}
-
-.el-upload__tip {
- font-size: 12px;
- color: #909399;
- margin-top: 7px;
-}
-
-.custom-steps-container {
- width: 100%;
- margin: 20px 0;
-}
-
-.steps-titles {
- display: flex;
- justify-content: space-between;
- margin-bottom: 16px;
- position: relative;
-}
-
-.step-title {
- text-align: center;
- flex: 1;
- font-size: 16px;
- color: #999;
- position: relative;
- padding-bottom: 10px;
-}
-
-.step-title.active {
- color: #409eff;
- font-weight: bold;
-}
-
-.custom-steps {
- margin-top: -20px;
- /* 向上移动与标题重叠 */
-}
-
-.el-step__head {
- margin-top: 0;
-}
-
-.el-step__description {
- margin-top: 8px;
- padding: 0 20px;
-}
-
-.step-description {
- font-size: 14px;
- color: #666;
- line-height: 1.5;
-}
-
-.event-title-center {
- text-align: center;
- font-size: 20px;
- font-weight: bold;
- margin-bottom: 12px;
- color: #333;
-}
-
-.cur-menu {
- .menu-custom-box {
- display: flex;
- justify-content: center;
- flex-wrap: wrap;
-
- &> ::v-deep(.el-button) {
- flex: 1;
- margin-left: 0;
- margin-right: 10px;
- }
- }
-}
-
-.add-box-btns {
- width: 100%;
- display: flex;
- justify-content: center;
-}
-
-.safe-height {
- :deep(.el-form-item__label) {
- width: 120px !important;
- }
-
- :deep(.el-input-number) {
- width: 180px !important;
- }
-}
-
-.flex {
- display: flex;
-}
-
-.flex-1 {
- flex: 1;
-
- &> ::v-deep(div) {
- width: 100% !important;
- }
-}
-.flex-3 {
- margin-right: 4px;
}
</style>
diff --git a/src/views/tickets/orderLogCopy.vue b/src/views/tickets/orderLogCopy.vue
new file mode 100644
index 0000000..f3bb788
--- /dev/null
+++ b/src/views/tickets/orderLogCopy.vue
@@ -0,0 +1,1666 @@
+<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">
+ <basic-main-content>
+ <!-- 查询条件筛选栏 -->
+ <div class="ztzf-form-search">
+ <el-form :model="filters" inline>
+ <el-row :gutter="24">
+ <el-col :span="4">
+ <el-form-item label="关键字:">
+ <el-input v-model="filters.key_word" placeholder="编号/名称/内容/姓名" clearable
+ @keyup.enter="handleSearch" />
+ </el-form-item>
+
+ <!-- <div class="search-bar-box-item">-->
+ <!-- <el-select placeholder="请选择所属单位" v-model="filters.create_dept" clearable>-->
+ <!-- <el-option v-for="dept in departments" :key="dept.value" :label="dept.label" :value="dept.value" />-->
+ <!-- </el-select>-->
+ <!-- </div>-->
+
+ </el-col>
+ <el-col :span="8">
+ <el-form-item label="选择日期:">
+ <el-date-picker @change="handleSearch" v-model="filters.dateRange" type="daterange"
+ range-separator="至" start-placeholder="开始日期" end-placeholder="结束日期"
+ :default-value="datePickerDefaultVal" />
+ </el-form-item>
+ </el-col>
+ <el-col :span="4">
+ <el-form-item label="关联航线:">
+ <el-select @change="handleSearch" v-model="filters.file_id" placeholder="请选择关联航线" filterable
+ clearable>
+ <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>
+ <el-col :span="4">
+ <el-form-item label="关联算法:">
+ <el-select @change="handleSearch" 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>
+ </el-form-item>
+
+ <!-- <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> -->
+
+ </el-col>
+ <el-col :span="4">
+ <el-form-item label="工单状态:">
+ <el-select @change="handleSearch" v-model="filters.status" placeholder="请选择工单状态" clearable
+ :disabled="activeTab !== 'all'">
+ <el-option v-for="item in statuses" :key="item.value" :label="item.label" :value="item.value" />
+ </el-select>
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="24">
+ <el-col :span="8">
+ <el-form-item label="工单周期:">
+ <el-date-picker @change="handleSearch" v-model="filters.cycleDateRange" type="daterange"
+ range-separator="至" start-placeholder="工单周期开始日期" end-placeholder="工单周期结束日期"
+ :default-value="datePickerDefaultVal" />
+ </el-form-item>
+ </el-col>
+ <el-col :span="4">
+ <el-form-item label="选择频次:">
+ <el-select @change="handleSearch" v-model="filters.rep_fre_type" placeholder="请选择频次" clearable>
+ <el-option v-for="item in cycles" :key="item" :label="item" :value="item" />
+ </el-select>
+ </el-form-item>
+ </el-col>
+ <el-col :span="4">
+ <el-form-item label="执行时间:">
+ <el-time-picker @change="handleSearch" v-model="filters.deal_time" placeholder="请选择执行时间"
+ prop="deal_time" value-format="HH:mm" :picker-options="{
+ selectableRange: '00:00 - 23:59',
+ }" />
+ </el-form-item>
+ </el-col>
+ <el-col :span="4">
+ <el-button type="primary" icon="el-icon-search" @click="handleSearch">搜索
+ </el-button>
+ <el-button icon="el-icon-refresh" @click="handleReset">清空</el-button>
+ </el-col>
+ </el-row>
+ </el-form>
+ </div>
+
+ <!-- 表格部分 -->
+ <avue-crud class="ztzf-public-general-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" v-if="activeTab === tab.name">
+ <template #job_info_num="{ row }">
+ <el-tooltip-copy :content="row.job_info_num" :showCopyText="true" textAlign="left">
+ {{ row.job_info_num }}
+ </el-tooltip-copy>
+ </template>
+
+ <template #name="{ row }">
+ <el-tooltip-copy :content="row.name" :showCopyText="true" textAlign="left">
+ {{ row.name }}
+ </el-tooltip-copy>
+ </template>
+
+ <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>
+ </template>
+
+ <template #menu="{ row }">
+ <div class="menu-custom-box">
+ <template v-if="row.status == 1">
+ <el-button v-if="hasPaddingBtnPermission()" type="text" icon="el-icon-view"
+ @click="handleCheckDetail(row)">审核
+ </el-button>
+ </template>
+ <!-- v-if="
+ (userInfo.user_id == row.create_user || hasRecallPaddingBtnPermission()) &&
+ row.status == 1
+ " -->
+ <template v-if="row.status == 1">
+ <!--待审核状态-->
+ <el-button type="text" icon="el-icon-warning" v-if="hasRecallPaddingBtnPermission()"
+ @click="orderLogRecall(row.id)">撤回
+ </el-button>
+ <!-- <el-button type="text" icon="el-icon-view" @click="handleViewDetail(row)">详情</el-button> -->
+ </template>
+ <!--已驳回-->
+ <template v-if="row.status == 2">
+ <el-button type="text" icon="el-icon-warning" @click="rejectDetail(row.id)">驳回原因
+ </el-button>
+ <el-button v-if="userInfo.user_id == row.create_user" type="text" icon="el-icon-view"
+ @click="handleViewDetail(row)">编辑
+ </el-button>
+ <el-button v-else type="text" icon="el-icon-view" @click="handleViewDetail(row)">详情
+ </el-button>
+ </template>
+ <!-- 已通过 -->
+ <template v-if="row.status == 3">
+ <el-button type="text" icon="el-icon-view" @click="handleViewDetail(row)">详情
+ </el-button>
+ </template>
+ <!--草稿-->
+ <template v-if="row.status == 0">
+ <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>
+ </div>
+ </template>
+ <template #status="{ row }">
+ <el-tag :type="getStatusTagType(row.status)">{{ mapStatus(row.status) }}</el-tag>
+ </template>
+ <template #keyData="{ row }">
+ <el-tooltip :content="row.address" placement="top" effect="light">
+ <span>{{ row.keyData }}</span>
+ </el-tooltip>
+ </template>
+ </avue-crud>
+ </basic-main-content>
+ </el-tab-pane>
+ </el-tabs>
+
+ <!-- 新建工单对话框 -->
+ <el-dialog class="ztzf-dialog-mange" 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-form-item>
+ </el-col>
+ <el-col :span="6">
+ <el-form-item label="关联航线" prop="file_id">
+ <el-select v-model="form.file_id" placeholder="请选择航线" filterable @change="getFlyingNestBy">
+ <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>
+ <el-col :span="6">
+ <el-form-item label="安全返航真高" prop="rth_altitude" class="safe-height">
+ <el-input-number v-model="form.rth_altitude" :min="50" :max="500"></el-input-number>
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <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="!device_sns.length">
+ <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-select>
+ </el-form-item>
+ </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-form-item>
+ </el-col>
+
+ <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="结束日期" :disabled-date="disabledDate" />
+ </el-form-item>
+ </el-col>
+ <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>
+
+ <el-row :gutter="20" style="height: 400px">
+ <el-col :span="24">
+ <map-container v-if="dialogVisible" ref="MapContainer"></map-container>
+ </el-col>
+ </el-row>
+
+ <!-- <el-row>-->
+ <!-- <div class="add-box-btns">-->
+ <!-- <el-button type="danger" @click="submitForm(1)">发起</el-button>-->
+ <!-- <el-button type="primary" @click="submitForm(0)">存草稿</el-button>-->
+ <!-- <el-button @click="dialogVisible = false">取消</el-button>-->
+ <!-- </div>-->
+ <!-- </el-row>-->
+ </el-form>
+ <template #footer>
+ <div class="dialog-footer">
+ <el-button type="danger" @click="submitForm(1)" icon="el-icon-position">发起</el-button>
+ <el-button type="primary" @click="submitForm(0)" icon="el-icon-document-add">存草稿</el-button>
+ <el-button @click="dialogVisible = false" icon="el-icon-circle-close">取消</el-button>
+ </div>
+ </template>
+ </el-dialog>
+
+ <!-- 工单详情对话框 -->
+ <el-dialog class="ztzf-dialog-mange" align-center 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" :rules="rules" ref="testform" label-width="100px">
+ <div class="custom-steps-container">
+ <!-- 标题行 -->
+ <div class="steps-titles" v-if="filters.status !== '0'">
+ <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>
+
+ <!-- Element Steps 组件 -->
+ <el-steps :active="form.active" align-center class="custom-steps" v-if="filters.status !== '0'">
+ <el-step v-for="(record, index) in form.record_list" :key="index">
+ <template #description>
+ <span class="step-description" style="position: relative; display: inline-block">
+ {{ record.user_name }}
+ </span>
+ <span class="step-timer">
+ {{ record.interval_time_str }}
+ </span>
+ <div class="step-description">
+ {{ record.create_time_str }}
+ </div>
+ </template>
+ </el-step>
+ </el-steps>
+ </div>
+
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="工单名称" prop="name">
+ <el-input v-model="form.name" placeholder="请输入工单名称" :disabled="detailTitle === '工单详情'"></el-input>
+ </el-form-item>
+ </el-col>
+ <el-col :span="6">
+ <el-form-item label="关联航线" prop="file_id">
+ <el-select v-model="form.file_id" placeholder="请选择航线" @change="getFlyingNestBy" filterable
+ :disabled="detailTitle === '工单详情'">
+ <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>
+ <el-col :span="6">
+ <el-form-item label="安全返航真高" prop="rth_altitude" class="safe-height">
+ <el-input-number :disabled="detailTitle === '工单详情'" v-model="form.rth_altitude" :min="50"
+ :max="500"></el-input-number>
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <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="detailTitle === '工单详情'">
+ <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="detailTitle === '工单详情'">
+ <el-option v-for="item in ai_types" :key="item.dictKey" :label="item.dictValue" :value="item.dictKey" />
+ </el-select>
+ </el-form-item>
+ </el-col>
+ </el-row>
+
+ <el-row :gutter="20">
+ <el-col :span="12" prop="statusStr">
+ <el-form-item label="当前状态">
+ {{ mapStatus(form.status) }}
+ </el-form-item>
+ </el-col>
+
+ <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="结束日期" :disabled-date="disabledDate" :disabled="detailTitle === '工单详情'" />
+ </el-form-item>
+ </el-col>
+
+ <el-col :span="3">
+ <el-select v-model="form.rep_fre_type" placeholder="请选择频次" :disabled="detailTitle === '工单详情'">
+ <el-option v-for="item in cycles" :key="item" :label="item" :value="item" />
+ </el-select>
+ </el-col>
+
+ <el-col :span="3">
+ <el-time-picker class="timeStyle" :style="{ width: pxToRem(140) }" v-model="form.deal_time" prop="deal_time"
+ value-format="HH:mm" :picker-options="{
+ selectableRange: '00:00 - 23:59',
+ }" :disabled="detailTitle === '工单详情'" />
+ </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 :disabled="detailTitle === '工单详情'"></el-input>
+ </el-form-item>
+ </el-col>
+ </el-row>
+
+ <el-row :gutter="20" style="height: 400px">
+ <el-col :span="24">
+ <map-container v-if="detailVisible" ref="MapContainer"></map-container>
+ </el-col>
+ </el-row>
+ </el-form>
+ <template #footer>
+ <div class="dialog-footer">
+ <el-button type="danger" v-if="form.status == 0 || (form.status == 2 && userInfo.user_id == form.create_user)"
+ @click="submitForm(1)" icon="el-icon-position">发布
+ </el-button>
+ <el-button type="primary"
+ v-if="form.status == 0 || (form.status == 2 && userInfo.user_id == form.create_user)" @click="submitForm(0)"
+ icon="el-icon-plus">保存
+ </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 && hasPaddingBtnPermission()"
+ @click="orderLogPass(form.id)">通过
+ </el-button>
+ <el-button type="danger" v-if="form.status == 1 && hasRejectionBtnPermission()"
+ @click="orderLogReject(form.id)">驳回
+ </el-button>
+ </div>
+ </template>
+ </el-dialog>
+
+ <!-- 工单详情 -->
+ <el-dialog class="ztzf-dialog-mange" align-center 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" v-if="filters.status !== '0'">
+ <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>
+
+ <!-- Element Steps 组件 -->
+ <el-steps :active="form.active" align-center class="custom-steps" v-if="filters.status !== '0'">
+ <el-step v-for="(record, index) in form.record_list" :key="index">
+ <template #description>
+ <span class="step-description" style="position: relative; display: inline-block">
+ {{ record.user_name }}
+ </span>
+ <span class="step-timer">
+ {{ record.interval_time_str }}
+ </span>
+ <div class="step-description">
+ {{ record.create_time_str }}
+ </div>
+ </template>
+ </el-step>
+ </el-steps>
+ </div>
+
+ <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-form-item>
+ </el-col>
+ <el-col :span="6">
+ <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-select>
+ </el-form-item>
+ </el-col>
+ <el-col :span="6">
+ <el-form-item label="安全返航真高" prop="rth_altitude" class="safe-height">
+ <el-input-number :disabled="true" v-model="form.rth_altitude" :min="50" :max="500"></el-input-number>
+ </el-form-item>
+ </el-col>
+ </el-row>
+
+ <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>
+ </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>
+ </el-form-item>
+ </el-col>
+ </el-row>
+
+ <el-row :gutter="20">
+ <el-col :span="12" prop="statusStr">
+ <el-form-item label="当前状态">
+ {{ mapStatus(form.status) }}
+ </el-form-item>
+ </el-col>
+
+ <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="结束日期" :disabled="true" />
+ </el-form-item>
+ </el-col>
+ <el-col :span="3">
+ <el-select v-model="form.rep_fre_type" placeholder="请选择频次" :disabled="true">
+ <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" :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-form-item>
+ </el-col>
+ </el-row>
+
+ <el-row :gutter="20" style="height: 400px">
+ <el-col :span="24">
+ <map-container v-if="detailVisibleCopy" ref="MapContainer"></map-container>
+ </el-col>
+ </el-row>
+
+ </el-form>
+ <template #footer>
+ <div class="dialog-footer">
+ <el-button type="danger" v-if="form.status == 0 || (form.status == 2 && userInfo.user_id == form.create_user)"
+ @click="submitForm(1)" icon="el-icon-position">发布
+ </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 && hasPaddingBtnPermission()" @click="orderLogPass(form.id)"
+ icon="el-icon-check">通过
+ </el-button>
+ <el-button type="danger" v-if="form.status == 1 && hasRejectionBtnPermission()"
+ @click="orderLogReject(form.id)" icon="el-icon-close">驳回
+ </el-button>
+ <el-button @click="detailVisibleCopy = false" icon="el-icon-circle-close">取消</el-button>
+ </div>
+ </template>
+ </el-dialog>
+ </basic-container>
+</template>
+
+<script>
+import { pxToRem, pxToRemNum } from '@/utils/rem'
+import { calculateDefaultRange } from '@/utils/util'
+import {
+ getList,
+ saveUpdateOrderLog,
+ orderLogDetails,
+ orderLogRecall,
+ orderLogReject,
+ orderLogPass,
+ orderLogExport,
+ jobStatusNum,
+ userPublish,
+ deleteOrderLog,
+ getWaylineMaxTerrainHeight
+} from '@/api/tickets/orderLog'
+import { getTicketInfo } from '@/api/tickets/ticket'
+import { getDictionaryByCode } from '@/api/system/dictbiz'
+import { getWaylineFileListByArea, newGetWorkspacesPage } 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'
+import dayjs from 'dayjs'
+import 'dayjs/locale/zh-cn' // 导入中文语言包
+import weekday from 'dayjs/plugin/weekday'
+import elTooltipCopy from '@/components/ElTooltipCopy.vue'
+import { CircleClose, Promotion, Select } from '@element-plus/icons-vue'
+import { useStore } from 'vuex'
+import _ from 'lodash'
+// const store = useStore()
+dayjs.extend(weekday)
+dayjs.locale('zh-cn')
+
+// const lastHeight = computed(() => store.state.common.lastHeight)
+
+export default {
+ components: { elTooltipCopy },
+ name: 'TicketPage',
+ data () {
+ return {
+ activeTab: 'all',
+ getlastHeight: 0,
+ tabs: [
+ { label: '全部工单', name: 'all', value: null, count: 0 },
+ { label: '待审核', name: 'WAIT_AUDIT', value: 1, count: 0 },
+ { label: '已驳回', name: 'REJECTED', value: 2, count: 0 },
+ { label: '已通过', name: 'PASS', value: 3, count: 0 },
+ { label: '草稿', name: 'DRAFT', value: 0, count: 0 }
+ ],
+ filters: {
+ key_word: '',
+ create_dept: '',
+ start_date: null,
+ end_date: null,
+ file_id: '',
+ ai_types: [],
+ status: '',
+ dateRange: []
+ },
+ departments: [],
+ types: [],
+ device_sns: [],
+ ai_types: [],
+ wayLineList: [],
+ detailTitle: '编辑工单',
+ detailVisibleCopy: false,
+
+ statuses: [
+ { label: '草稿', value: '0' },
+ { label: '待审核', value: '1' },
+ { label: '已驳回', value: '2' },
+ { label: '已通过', value: '3' }
+ ],
+ //周期
+ cycles: ['每天', '周一', '周二', '周三', '周四', '周五', '周六', '周末', '周天', '工作日'],
+
+ tableData: [],
+ option: {
+ index: true,
+ indexWidth: 66,
+ indexLabel: '序号',
+ indexFixed: true,
+
+ align: 'left',
+ border: true,
+ stripe: false,
+ searchShow: true,
+ searchBtnText: '搜索',
+ searchMenuSpan: 6,
+ addBtnText: '新建工单',
+ addBtn: false,
+ viewBtn: false,
+ editBtn: false,
+ delBtn: false,
+ cancelBtn: true,
+ cancelBtnText: '取消',
+
+ menu: true,
+ menuWidth: 210,
+ menuClassName: 'cur-menu',
+
+ height: 'auto',
+ calcHeight: 196,
+
+ column: [
+ {
+ label: '工单编号',
+ prop: 'job_info_num',
+ width: 150
+ },
+ {
+ label: '工单名称',
+ prop: 'name',
+ width: 100,
+ ellipsis: true,
+ overHidden: true,
+ showOverflowTooltip: true
+ },
+ {
+ label: '工单状态',
+ prop: 'status',
+ width: 88,
+ showOverflowTooltip: true
+ },
+ {
+ label: '所属单位',
+ prop: 'dept_name',
+ ellipsis: true,
+ showOverflowTooltip: true
+ },
+ {
+ label: '创建时间',
+ prop: 'create_time',
+ width: 144,
+ ellipsis: true,
+ showOverflowTooltip: true
+ },
+ {
+ label: '已执行次数',
+ prop: 'job_num',
+ width: 70,
+ ellipsis: true,
+ showOverflowTooltip: true
+ },
+ {
+ label: '工单内容',
+ prop: 'content',
+ ellipsis: true,
+ overHidden: true,
+ showOverflowTooltip: true
+ },
+ {
+ label: '关联航线',
+ prop: 'wayline_name',
+ ellipsis: true,
+ overHidden: true,
+ showOverflowTooltip: true
+ },
+ {
+ label: '关联算法',
+ prop: 'ai_type_str',
+ width: 100,
+ ellipsis: true,
+ overHidden: true,
+ showOverflowTooltip: true
+ },
+ {
+ label: '关联机巢',
+ prop: 'device_names',
+ width: 100,
+ ellipsis: true,
+ overHidden: true,
+ showOverflowTooltip: true
+ },
+
+ {
+ label: '创建人',
+ prop: 'creator_name',
+ width: 96,
+ ellipsis: true,
+ overHidden: true,
+ showOverflowTooltip: true
+ },
+ // {
+ // label: '关联机巢', prop: 'device_names', width: 112, ellipsis: true, overHidden: true,
+ // showOverflowTooltip: true,
+ // },
+ {
+ label: '工单周期频次',
+ prop: '',
+ width: 110,
+ formatter: row => this.formatCycleTime(row),
+ html: true,
+ ellipsis: true,
+ showOverflowTooltip: true
+ // overHidden: true
+ }
+ ]
+ },
+
+ page: {
+ pageSize: 10,
+ currentPage: 1,
+ total: 0
+ },
+ dialogVisible: false,
+ detailVisible: false,
+ currentDetail: {},
+ form: {},
+ rules: {
+ name: [
+ { required: true, message: '请输入工单名称', trigger: 'blur' },
+ { max: 100, message: '工单名称不能超过100个字', trigger: 'blur' }
+ ],
+ file_id: [{ required: true, message: '需要选择航线', trigger: 'change' }],
+ device_sns: [{ required: true, message: '请选择机巢', trigger: 'change' }],
+ content: [
+ { required: true, message: '请输入工单内容', trigger: 'blur' },
+ { max: 255, message: '工单内容不能超过255个字', trigger: 'blur' }
+ ],
+ date_range: [{ required: true, message: '请选择时间不能为空', trigger: 'blur' }],
+ rep_fre_type: [{ required: true, message: '请选择周期', trigger: 'blur' }],
+ deal_time: [{ required: true, message: '请选择执行时间', trigger: 'blur' }]
+ },
+ loading: false,
+ globalCounts: {},
+ mapLoaded: false,
+
+ // 配置时间选择器默认配置
+ 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']
+ //获取航线
+ 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()
+ const id = this.$route.query.id
+ console.log('idddddd', id)
+
+ if (id) {
+ // 确保 id 存在
+ this.handleViewDetail({ id })
+ const find = this.$store.state.tags.bsTagList.find(i => i.path === '/tickets/orderLog')
+ find && (find.query = {})
+ } else {
+ console.error('工单ID不存在!')
+ }
+ },
+ computed: {
+ CircleClose () {
+ return CircleClose
+ },
+ ...mapGetters(['userInfo', 'permission']),
+ safeHeight () {
+ const safeHeight = this.$store.state.common.safeHeight
+ return Number(safeHeight) || 0 // 确保是数字类型
+ },
+ positionsArr () {
+ const positionsArr = this.$store.state.common.positionsArr
+ return positionsArr || [] // 确保是数字类型
+ },
+ filteredTabs () {
+ // rejection_and_draft 权限控制“已驳回”和“草稿”tab
+ // console.log(this.permission, '权限信息')
+ // console.log(this.userInfo, '权限信息22')
+ const canShowRejectAndDraft = this.permission?.rejection_and_draft === true
+ return this.tabs
+ .map(tab => {
+ if (tab.name === 'DRAFT') {
+ return { ...tab, isShow: canShowRejectAndDraft }
+ }
+ if (tab.name === 'REJECTED') {
+ return { ...tab, isShow: canShowRejectAndDraft }
+ }
+ return { ...tab, isShow: true }
+ })
+ .filter(tab => tab.isShow)
+ }
+ },
+
+ methods: {
+ handleCellClick (row, column) {
+ if (column.no === 1) {
+ navigator.clipboard.writeText(row.job_info_num).then(() => {
+ this.$message.success('复制工单编号成功')
+ })
+ } else if (column.no === 2) {
+ navigator.clipboard.writeText(row.name).then(() => {
+ this.$message.success('复制工单名称成功')
+ })
+ } else if (column.no === 4) {
+ navigator.clipboard.writeText(row.dept_name).then(() => {
+ this.$message.success('复制所属单位成功')
+ })
+ } else if (column.no === 8) {
+ navigator.clipboard.writeText(row.wayline_name).then(() => {
+ this.$message.success('复制关联航线成功')
+ })
+ } else if (column.no === 9) {
+ navigator.clipboard.writeText(row.ai_type_str).then(() => {
+ this.$message.success('复制关联算法成功')
+ })
+ }
+ },
+ disabledDate (time) {
+ return time.getTime() < Date.now() - 8.64e7 // 86400000 = 24 * 60 * 60 * 1000
+ },
+ 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
+ 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()
+ })
+ },
+ selectionClear () {
+ this.selectionList = []
+ this.$refs.crud.toggleSelection()
+ },
+ async loadAMapScripts () {
+ try {
+ // await loadAMap();
+ // await loadAMapUI();
+ this.mapLoaded = true
+ } catch (error) {
+ console.error('Failed to load AMap scripts:', error)
+ this.$message.error('地图加载失败,请检查网络或API Key配置')
+ }
+ },
+ formatCycleTime (row) {
+ return `${row.cycle_time_value}`
+ },
+
+ async fetchTableData () {
+ this.loading = true
+ try {
+ 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('接口返回数据格式不正确')
+ }
+
+ const { total, records } = response.data.data
+ this.tableData = records.map(item => {
+ return item
+ })
+
+ // 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
+ } finally {
+ this.loading = false
+ }
+ },
+
+ 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 = {
+ key_word: this.filters.key_word || undefined,
+ create_dept: this.filters.create_dept || undefined,
+ ai_types:
+ this.filters.ai_types && this.filters.ai_types.length > 0
+ ? [this.filters.ai_types]
+ : null,
+ file_id: this.filters.file_id || undefined,
+ status: this.filters.status !== '' ? Number(this.filters.status) : currentTab?.value,
+ dept_id: this.filters.department || undefined,
+ create_start_date: this.filters.dateRange?.[0]
+ ? this.formatDate(this.filters.dateRange[0])
+ : undefined,
+ create_end_date: this.filters.dateRange?.[1]
+ ? this.formatDate(this.filters.dateRange[1]).replace('00:00:00', '23:59:59')
+ : undefined,
+
+ start_date: this.filters.cycleDateRange?.[0]
+ ? this.formatDate(this.filters.cycleDateRange[0])
+ : undefined,
+ 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
+ },
+ sizeChange (pageSize) {
+ this.page.pageSize = pageSize
+ },
+ async submitForm (status) {
+ this.$refs.testform.validate(async valid => {
+ if (valid) {
+ 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])
+
+ // 如果选中日期包含当前天,那么选中的时间点不能小于当前时间
+ if (this.form.deal_time) {
+ const selectedDate = dayjs(this.form.date_range[0]).format('YYYY-MM-DD')
+ const selectedTime = dayjs(selectedDate + ' ' + this.form.deal_time).toDate()
+ const now = new Date()
+
+ if (selectedDate === dayjs().format('YYYY-MM-DD') && selectedTime < now) {
+ return this.$message.warning('任务时间不能小于当前时间')
+ }
+ }
+ let checkedList = this.device_sns.filter(item => this.form.device_sns.includes(item.device_sn));
+ const maxItem = checkedList.reduce((max, item) => {
+ return item.drone_height > max.drone_height ? item : max;
+ });
+ // 返航绝对高度
+ let backHeight = this.form.rth_altitude // + maxItem.drone_height
+ console.log(backHeight,'backHeight')
+ // 安全起飞高度
+ let positions = this.positionsArr
+ positions.unshift({latitude: maxItem.latitude, longitude: maxItem.longitude, height:maxItem.drone_height })
+ positions.push({latitude: maxItem.latitude, longitude: maxItem.longitude, height:maxItem.drone_height})
+ let resultHeight = 0
+ await getWaylineMaxTerrainHeight(positions).then(res => {
+ resultHeight = this.safeHeight > (res.data.data + 30) ? this.safeHeight : (res.data.data + 30)
+ })
+ let valueHeight = _.round(backHeight - resultHeight, 2)
+ console.log('打印比较值', valueHeight)
+
+ if (valueHeight < 0) {
+ this.$confirm(`当前返航高度存在安全隐患,建议调整为${resultHeight}米以上后进行发布`, '提示', {
+ confirmButtonText: '确定',
+ cancelButtonText: '取消',
+ type: 'warning'
+ }).then(async () => {
+ const submitData = {
+ ...this.form,
+ status: status,
+ ai_types: this.form.ai_types?.length ? this.form.ai_types : []
+ }
+ 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()
+ }).catch(() => {
+
+ })
+ } else {
+ const submitData = {
+ ...this.form,
+ status: status,
+ ai_types: this.form.ai_types?.length ? this.form.ai_types : []
+ }
+ 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()
+ }
+ }
+ })
+ },
+ //驳回原因显示
+ async rejectDetail (id) {
+ const response = await orderLogDetails(id)
+ let data = response.data.data
+ this.$confirm(data.remark, '驳回原因', {
+ confirmButtonText: '确定',
+ cancelButtonText: '取消',
+ type: 'warning'
+ }).then(() => {
+ this.form = {
+ ...response.data.data
+ }
+ // this.detailVisible = true
+ this.handleViewDetail(data)
+ })
+ },
+ 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`
+ },
+
+ mapStatus (status) {
+ const statusTextMap = {
+ 0: '草稿',
+ 1: '待审核',
+ 2: '已驳回',
+ 3: '已通过'
+ }
+ return statusTextMap[status] || '未知状态'
+ },
+
+ getStatusTagType (status) {
+ const statusMap = {
+ 1: 'warning',
+ 2: 'info',
+ 3: 'primary',
+ 4: 'success',
+ 5: 'danger'
+ }
+ return statusMap[status] || 'info'
+ },
+
+ handleTabChange (tab) {
+ this.activeTab = tab.props?.name || tab.name
+ this.filters.status = ''
+ if (tab.props?.name === 'WAIT_AUDIT') {
+ this.filters.status = '1'
+ } else if (tab.props?.name === 'REJECTED') {
+ this.filters.status = '2'
+ } else if (tab.props?.name === 'PASS') {
+ this.filters.status = '3'
+ } else if (tab.props?.name === 'DRAFT') {
+ this.filters.status = '0'
+ }
+ this.page.currentPage = 1
+ this.fetchTableData()
+ },
+
+ handleSearch () {
+ this.page.currentPage = 1
+ this.fetchTableData()
+ },
+
+ handleReset () {
+ this.filters = {
+ keyword: '',
+ department: '',
+ type: '',
+ dateRange: [],
+ status: ''
+ }
+ this.page.currentPage = 1
+ this.fetchTableData()
+ },
+
+ currentChange (currentPage) {
+ this.page.currentPage = currentPage
+ },
+
+ async updateGlobalCounts () {
+ const counts = {
+ all: 0,
+ DRAFT: 0,
+ WAIT_AUDIT: 0,
+ REJECTED: 0,
+ PASS: 0
+ }
+ 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)
+ if (tab) {
+ tab.count = item.num
+ }
+ })
+ },
+
+ handleAdd () {
+ this.form = {}
+ this.form.rth_altitude = 120
+ this.dialogVisible = true
+
+ this.$nextTick(() => {
+ if (this.$refs.MapContainer && this.$refs.MapContainer.initAddEntity) {
+ this.$refs.MapContainer.initAddEntity('initPosition')
+ }
+ })
+
+ //航线列表
+ this.asyncgetWaylineFileListByArea()
+ },
+
+ resetForm () {
+ this.form = {
+ name: '',
+ type: '',
+ handler: '',
+ algorithm: '',
+ location: '',
+ address: '',
+ content: '',
+ photos: []
+ }
+ if (this.$refs.testform) {
+ this.$refs.testform.resetFields()
+ }
+ },
+
+ formatLocation (location) {
+ if (!Array.isArray(location)) {
+ return '未知位置'
+ }
+ return `${location[0].toFixed(6)}, ${location[1].toFixed(6)}`
+ },
+ async handleViewDetail (row) {
+ const response = await orderLogDetails(row.id)
+ const data = response.data.data
+ this.form = {
+ ...data
+ }
+
+ // 更新机巢列表
+ this.device_sns = data.device_map_infos;// 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.userInfo.user_id !== this.form.create_user))
+ ? (this.detailTitle = '工单详情')
+ : (this.detailTitle = '编辑工单')
+
+ this.detailVisible = true
+ // 更新航线列表,追加 wayline_file_region_vo 数据
+ if (data.wayline_file_region_vo) {
+ const newWayline = data.wayline_file_region_vo
+
+ // 检查是否已经存在于 this.wayLineList 中
+ const isDuplicate = this.wayLineList.some(
+ item => item.wayline_id === newWayline.wayline_id
+ )
+
+ if (!isDuplicate) {
+ this.wayLineList.push(newWayline)
+ }
+ }
+
+ this.initMapLine(data.device_map_infos)
+ const find = this.$store.state.tags.bsTagList.find(i => i.path === '/tickets/orderLog')
+ find && (find.query = {})
+ },
+ async handleCheckDetail (row) {
+ const response = await orderLogDetails(row.id)
+ const data = response.data.data
+ this.form = {
+ ...data
+ }
+ // 更新航线列表,追加 wayline_file_region_vo 数据
+ // 更新航线列表,追加 wayline_file_region_vo 数据
+ if (data.wayline_file_region_vo) {
+ const newWayline = data.wayline_file_region_vo
+
+ // 检查是否已经存在于 this.wayLineList 中
+ const isDuplicate = this.wayLineList.some(
+ item => item.wayline_id === newWayline.wayline_id
+ )
+
+ if (!isDuplicate) {
+ this.wayLineList.push(newWayline)
+ }
+ }
+ // 更新机巢列表
+ this.device_sns = data.device_list
+ this.detailVisibleCopy = true
+ this.initMapLine(data.device_map_infos)
+ },
+ //导出
+ async exportData () {
+ this.$confirm('是否智飞工单数据?', '提示', {
+ confirmButtonText: '确定',
+ cancelButtonText: '取消',
+ type: 'warning'
+ }).then(() => {
+ NProgress.start()
+ let params = this.getQueryParam()
+ orderLogExport(params).then(res => {
+ downloadXls(res.data, `智飞工单${this.$dayjs().format('YYYY-MM-DD')}.xlsx`)
+ NProgress.done()
+ })
+ })
+ },
+ 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
+ },
+ hasPaddingBtnPermission () {
+ // undefined 或 false 都返回 false,只有 true 返回 true
+ // console.log('权限检查:', this.permission)
+ return this.permission && this.permission.order_log_review === true
+ },
+ hasRecallPaddingBtnPermission () {
+ // undefined 或 false 都返回 false,只有 true 返回 true
+ // console.log('权限检查:', this.permission)
+ // 智飞工单撤回
+ return this.permission && this.permission.order_log_recall === true
+ },
+ //驳回按钮权限
+ hasRejectionBtnPermission () {
+ // undefined 或 false 都返回 false,只有 true 返回 true
+ // console.log('权限检查:', this.permission)
+ return this.permission && this.permission.rejection_btn === true
+ },
+ //自己点发布
+ userPublishPush (id) {
+ this.$confirm('确定发布吗?', '提示', {
+ confirmButtonText: '确定',
+ cancelButtonText: '取消',
+ type: 'warning'
+ }).then(() => {
+ let response = userPublish(id)
+ this.$message.success('发布成功')
+ this.fetchTableData()
+ })
+ },
+
+ //删除
+ deleteOrderLog (id) {
+ this.$confirm('确定删除吗?', '提示', {
+ confirmButtonText: '确定',
+ cancelButtonText: '取消',
+ type: 'warning'
+ }).then(() => {
+ let response = deleteOrderLog(id)
+ this.$message.success('删除')
+ this.fetchTableData()
+ })
+ },
+ refreshChange () {
+ this.fetchTableData()
+ },
+ //获取航线列表
+ async asyncgetWaylineFileListByArea (name) {
+ const formData = {
+ name: '',
+ waylineType: undefined,
+ current: 1,
+ size: 99999999,
+ descs: 'update_time'
+ }
+ var wayLineListResponse = await newGetWorkspacesPage(formData)
+ this.wayLineList = wayLineListResponse.data.data.records
+
+ this.initMapLine()
+ },
+
+ initMapLine (infos = {}, cb = () => {
+ }) {
+ let currentLine = this.wayLineList.find(item => item.wayline_id == this.form.file_id)
+
+ if (!currentLine) return
+
+ this.$nextTick(() => {
+ if (this.$refs.MapContainer && this.$refs.MapContainer.initAddEntity) {
+ this.$refs.MapContainer.initAddEntity('polyline', {
+ url: `${import.meta.env.VITE_APP_AIRLINE_URL + currentLine.object_key
+ }?_t=${new Date().getTime()}`,
+ type: currentLine.wayline_type,
+ cb,
+ infos
+ })
+ }
+ })
+ },
+
+ //可飞行机巢列表
+ async getFlyingNestBy (waylineId) {
+ const that = this
+
+ // 先清空
+ that.form.device_sns = []
+ that.device_sns = []
+
+ this.initMapLine({}, async polygon => {
+ const currentLine = that.wayLineList.find(item => item.wayline_id === waylineId)
+ //按照航线来
+ const params = {
+ type: ['2', '4', '5'].includes(currentLine.wayline_type) ? 2 : 0,
+ wayline_id: waylineId,
+ polygon
+ }
+ var wayLineListResponse = await getFlyingNestBy(params)
+ // 再赋值
+ that.device_sns = wayLineListResponse.data.data
+ })
+ },
+
+ //撤回
+ async orderLogRecall (id) {
+ this.$confirm('确定撤回则到草稿箱。', '是否撤回?', {
+ confirmButtonText: '确定',
+ cancelButtonText: '取消',
+ type: 'warning'
+ }).then(async () => {
+ let reposne = await orderLogRecall(id)
+ this.handleSearch()
+ })
+ },
+ onLoad () {
+ this.fetchTableData()
+ },
+ /**
+ * 通过
+ */
+ async orderLogPass (id) {
+ let response = await orderLogPass(id)
+ let data = response.data.data
+ this.$message.success('审核通过')
+ this.detailVisibleCopy = false
+
+ this.detailVisible = false
+
+ this.page.currentPage = 1
+ this.onLoad(this.page, this.query)
+ // this.handleViewDetail()
+ },
+ /**
+ * 驳回
+ */
+ 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
+
+ // this.detailVisible = false
+
+ this.page.currentPage = 1
+ this.onLoad(this.page, this.query)
+ })
+ }
+ },
+ watch: {
+ // lastHeight(newVal, oldVal) {
+ // console.log('lastHeight', newVal, oldVal);
+ // this.getlastHeight = newVal;
+ // },
+ tableData: {
+ handler () {
+ // this.updateTabCounts()
+ },
+ deep: true
+ }
+ }
+}
+</script>
+
+<style lang="scss" scoped>
+//:deep(.el-input-number) {
+// width: 200px;
+//}
+
+//:deep(.el-form-item__label) {
+// width: 100px !important;
+//}
+
+::v-deep(.el-tabs) {
+ height: 100%;
+ display: flex;
+ flex-direction: column;
+
+ .el-tabs__header {
+ order: 1;
+ }
+
+ .el-tabs__content {
+ order: 2;
+ }
+
+ .el-tabs__content {
+ height: 0;
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+
+ .el-tab-pane {
+ height: 0;
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ }
+ }
+}
+
+.filter-bar {
+ // align-items: center;
+
+ .search-bar-box {
+ margin-bottom: 15px;
+ display: flex;
+ justify-content: space-between;
+ gap: 8px;
+
+ &-item {
+ flex: 1;
+
+ &> ::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;
+ }
+ }
+}
+
+:deep(.el-pagination .el-select) {
+ width: 128px !important;
+}
+
+.step-timer {
+ position: absolute;
+ left: 80%;
+ top: 50%;
+ transform: translateY(-50%);
+ width: 100px;
+ margin-left: 4px;
+ color: #666;
+ font-size: 12px;
+}
+
+:deep(.el-textarea__inner) {
+ padding: 8px 12px;
+}
+
+:deep(.el-input__inner),
+:deep(.el-select),
+:deep(.el-select .el-input) {
+ width: 100%; // 确保所有输入框和选择框宽度一致
+}
+
+:deep(.el-textarea__inner) {
+ resize: none;
+}
+
+.action-bar {
+ margin-bottom: 16px;
+}
+
+.el-tabs {
+ :deep(.el-tabs__content) {
+ overflow: visible;
+ }
+}
+
+.el-dialog {
+ .el-form-item {
+ margin-bottom: 10px;
+ }
+}
+
+.el-upload {
+ width: 100%;
+ display: flex;
+ flex-wrap: wrap;
+
+ :deep(.el-upload-list__item) {
+ transition: all 0.3s ease;
+ }
+
+ :deep(.el-upload-list__item:hover) {
+ background-color: #f5f7fa;
+ }
+}
+
+.el-upload__tip {
+ font-size: 12px;
+ color: #909399;
+ margin-top: 7px;
+}
+
+.custom-steps-container {
+ width: 100%;
+ margin: 20px 0;
+}
+
+.steps-titles {
+ display: flex;
+ justify-content: space-between;
+ margin-bottom: 16px;
+ position: relative;
+}
+
+.step-title {
+ text-align: center;
+ flex: 1;
+ font-size: 16px;
+ color: #999;
+ position: relative;
+ padding-bottom: 10px;
+}
+
+.step-title.active {
+ color: #409eff;
+ font-weight: bold;
+}
+
+.custom-steps {
+ margin-top: -20px;
+ /* 向上移动与标题重叠 */
+}
+
+.el-step__head {
+ margin-top: 0;
+}
+
+.el-step__description {
+ margin-top: 8px;
+ padding: 0 20px;
+}
+
+.step-description {
+ font-size: 14px;
+ color: #666;
+ line-height: 1.5;
+}
+
+.event-title-center {
+ text-align: center;
+ font-size: 20px;
+ font-weight: bold;
+ margin-bottom: 12px;
+ color: #333;
+}
+
+.cur-menu {
+ .menu-custom-box {
+ display: flex;
+ justify-content: center;
+ flex-wrap: wrap;
+
+ &> ::v-deep(.el-button) {
+ flex: 1;
+ margin-left: 0;
+ margin-right: 10px;
+ }
+ }
+}
+
+.add-box-btns {
+ width: 100%;
+ display: flex;
+ justify-content: center;
+}
+
+.safe-height {
+ :deep(.el-form-item__label) {
+ width: 120px !important;
+ }
+
+ :deep(.el-input-number) {
+ width: 180px !important;
+ }
+}
+
+.flex {
+ display: flex;
+}
+
+.flex-1 {
+ flex: 1;
+
+ &> ::v-deep(div) {
+ width: 100% !important;
+ }
+}
+</style>
diff --git a/src/views/tickets/ticket-copy.vue b/src/views/tickets/ticket-copy.vue
new file mode 100644
index 0000000..c9b97e4
--- /dev/null
+++ b/src/views/tickets/ticket-copy.vue
@@ -0,0 +1,4069 @@
+<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"
+ >
+ <basic-main-content>
+ <!-- 查询条件筛选栏 -->
+ <!-- 查询条件筛选栏 -->
+ <div class="ztzf-form-search">
+ <el-form :model="filters" inline>
+ <el-row :gutter="24">
+ <el-col :span="4">
+ <el-form-item label="关键字:">
+ <el-input
+ v-model="filters.keyword"
+ placeholder="请输入关键字"
+ class="filter-item"
+ clearable
+ @clear="handleKeyWords"
+ @keyup.enter="handleSearch"
+ />
+ </el-form-item>
+ </el-col>
+
+ <!-- <el-select
+ v-model="filters.department"
+ placeholder="请选择所属部门"
+ class="filter-item"
+ clearable
+ >
+ <el-option
+ v-for="item in departments"
+ :key="item.value"
+ :label="item.label"
+ :value="item.value"
+ />
+ </el-select> -->
+ <!-- <el-select
+ v-model="filters.type"
+ placeholder="请选择工单类型"
+ class="filter-item"
+ clearable
+ @change="handleSearch"
+ >
+ <el-option
+ v-for="item in types"
+ :key="item.value"
+ :label="item.label"
+ :value="item.value"
+ />
+ </el-select> -->
+ <el-col :span="5">
+ <el-form-item label="选择日期:">
+ <el-date-picker
+ v-model="filters.dateRange"
+ type="daterange"
+ class="filter-item"
+ range-separator="至"
+ start-placeholder="开始日期"
+ end-placeholder="结束日期"
+ :default-value="datePickerDefaultVal"
+ >
+ </el-date-picker>
+ </el-form-item>
+ </el-col>
+ <el-col :span="4">
+ <el-form-item label="选择状态:">
+ <el-select
+ :disabled="activeTab !== 'all'"
+ v-model="filters.status"
+ placeholder="请选择状态"
+ class="filter-item"
+ clearable
+ @change="handleSearch"
+ >
+ <el-option
+ v-for="item in statuses"
+ :key="item.value"
+ :label="item.label"
+ :value="item.value"
+ />
+ </el-select>
+ </el-form-item>
+ </el-col>
+
+ <!-- <el-select
+ v-model="filters.algorithm"
+ placeholder="请选择关联算法"
+ class="filter-item"
+ clearable
+ @change="handleSearch"
+ >
+ <el-option
+ v-for="item in algorithms"
+ :key="item.dict_key"
+ :label="item.dict_value"
+ :value="item.dict_key"
+ />
+ </el-select> -->
+ <!-- <el-tree-select
+ popper-class="custom-tree-select"
+ :style="{ width: pxToRem(186) }"
+ placeholder="请选择关联算法"
+ v-model="dictKey"
+ :data="dataList"
+ :default-expanded-keys="[dictKey]"
+ check-strictly
+ node-key="id"
+ :props="treePropsSF"
+ @node-click="handleSFNodeClick"
+ clearable
+ @clear="handleClear"
+ /> -->
+ <el-col :span="4">
+ <el-form-item label="选择算法:">
+ <el-tree-select
+ style="z-index: 1000"
+ :teleported="false"
+ class="custom-tree-select"
+ :style="{ width: pxToRem(186) }"
+ v-model="dictKey"
+ :data="dataList"
+ :default-expanded-keys="[dictKey]"
+ :props="treePropsSF"
+ :render-after-expand="false"
+ :default-checked-keys="checkedKeys"
+ node-key="id"
+ multiple
+ show-checkbox
+ collapse-tags
+ collapse-tags-tooltip
+ clearable
+ @check="handleCheck"
+ @clear="handleClear"
+ />
+ </el-form-item>
+ </el-col>
+ <el-col :span="4">
+ <el-form-item label="复核状态:">
+ <el-select
+ v-model="filters.isReview"
+ placeholder="请选择复核状态"
+ class="filter-item"
+ clearable
+ @change="handleSearch"
+ >
+ <el-option
+ v-for="item in reviewStatuses"
+ :key="item.value"
+ :label="item.label"
+ :value="item.value"
+ />
+ </el-select>
+ </el-form-item>
+ </el-col>
+ <el-col :span="3">
+ <el-button type="primary" icon="el-icon-search" @click="handleSearch"
+ >搜索</el-button
+ >
+ <el-button icon="el-icon-refresh" @click="handleReset">清空</el-button>
+ </el-col>
+ </el-row>
+ </el-form>
+ </div>
+ <!-- 表格部分 -->
+ <avue-crud
+ class="ztzf-public-general-avue-crud"
+ ref="avueCrud"
+ v-model="tableData"
+ :option="option"
+ :data="tableData"
+ v-model:page="page"
+ @size-change="sizeChange"
+ @current-change="handleCurrentChange"
+ @refresh-change="refreshChange"
+ :table-loading="loading"
+ @selection-change="handleSelectionChange"
+ :permission="permissionList"
+ v-if="activeTab === tab.name"
+ >
+ <template #orderNumber="{ row }">
+ <el-tooltip-copy :content="row.orderNumber" :showCopyText="true" textAlign="left">
+ {{ row.orderNumber }}
+ </el-tooltip-copy>
+ </template>
+ <template #orderName="{ row }">
+ <el-tooltip-copy :content="row.orderName" :showCopyText="true" textAlign="left">
+ {{ row.orderName }}
+ </el-tooltip-copy>
+ </template>
+ <template #menu-left>
+ <el-button
+ v-if="(activeTab === 'all' || activeTab === 'myTickets') && permissionList.addBtn"
+ type="primary"
+ icon="el-icon-plus"
+ @click="handleAdd"
+ >新建工单
+ </el-button>
+ <el-button
+ v-if="activeTab === 'pending' && permissionList.reviewBtn"
+ type="success"
+ icon="el-icon-check"
+ @click="openReviewDialog"
+ >批量审核
+ </el-button>
+
+ <el-button
+ v-if="permissionList.exportBtn"
+ type="success"
+ plain
+ icon="el-icon-download"
+ @click="exportData"
+ >导出
+ </el-button>
+ </template>
+ <template #menu="{ row }">
+ <template v-if="row.status === -1">
+ <el-button type="text" icon="el-icon-edit" @click="handleEdit(row)">编辑</el-button>
+ <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>
+
+ <template v-if="row.status === 4 && activeTab === 'completed'">
+ <el-button type="text" icon="el-icon-download" @click="exportTheTick(row)"
+ >导出
+ </el-button>
+ </template>
+ <template v-if="permission.tickets_repeat_review">
+ <el-button
+ v-if="row.status === 4 && row.isReview !== 1"
+ type="text"
+ icon="el-icon-check"
+ @click="reCheck(row)"
+ >复核
+ </el-button>
+ </template>
+ </template>
+ <template #status="{ row }">
+ <span
+ :style="getStatusTagType(row.status) ? 'color:' + getStatusTagType(row.status) : ''"
+ >
+ {{ mapStatus(row.status) }}
+ </span>
+ </template>
+ <template #keyData="{ row }">
+ <span>{{ row.address }}</span>
+ </template>
+
+ <template #isReview="{ row }">
+ <span>{{ showIsReviewText(row) }}</span>
+ </template>
+ </avue-crud>
+ </basic-main-content>
+ </el-tab-pane>
+ </el-tabs>
+
+ <!-- 新建工单对话框 -->
+ <el-dialog
+ v-model="dialogVisible"
+ v-if="dialogVisible"
+ title="新建工单"
+ width="70%"
+ :close-on-click-modal="false"
+ @close="resetForm"
+ >
+ <el-form
+ :model="form"
+ :rules="rules"
+ ref="form"
+ label-width="90px"
+ class="create-ticket-form"
+ >
+ <div class="form-section">
+ <el-row :gutter="16">
+ <el-col :span="12">
+ <el-form-item label="工单名称" prop="name">
+ <el-input v-model="form.name" placeholder="请输入工单名称"></el-input>
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="工单类型" prop="type">
+ <el-select
+ @change="handleTypeChange"
+ v-model="form.type"
+ placeholder="请选择工单类型"
+ class="full-width"
+ >
+ <el-option
+ v-for="item in types"
+ :key="item.value"
+ :label="item.label"
+ :value="item.value"
+ />
+ </el-select>
+ </el-form-item>
+ </el-col>
+ </el-row>
+
+ <el-row :gutter="16">
+ <el-col :span="12">
+ <el-form-item label="处理部门" prop="department">
+ <el-select
+ v-model="form.department"
+ placeholder="请选择处理部门"
+ @change="handleDepartmentChange"
+ class="full-width"
+ >
+ <el-option
+ v-for="dept in departments"
+ :key="dept.value"
+ :label="dept.label"
+ :value="dept.value"
+ />
+ </el-select>
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="处理人员" prop="handler">
+ <el-select
+ v-model="form.handler"
+ placeholder="请先选择处理人员"
+ :disabled="!form.department"
+ class="full-width"
+ >
+ <el-option
+ v-for="user in availableHandlers"
+ :key="user.id"
+ :label="user.name"
+ :value="user.id"
+ />
+ </el-select>
+ </el-form-item>
+ </el-col>
+ </el-row>
+
+ <el-row :gutter="16">
+ <el-col :span="12">
+ <el-form-item label="关联算法" prop="algorithm">
+ <el-select
+ v-model="form.algorithm"
+ multiple
+ placeholder="请选择关联算法"
+ class="full-width"
+ :disabled="!form.type"
+ >
+ <el-option
+ v-for="item in algorithms2"
+ :key="item.value"
+ :label="item.label"
+ :value="item.value"
+ />
+ </el-select>
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="事件位置" prop="location">
+ <div class="location-wrapper">
+ <avue-input-map
+ v-model="form.location"
+ :clearable="false"
+ :params="mapParams"
+ @change="handleLocationChange"
+ type="button"
+ >
+ <el-button type="primary" plain class="map-button">
+ <i class="el-icon-map-location"></i> 地图选点
+ </el-button>
+ </avue-input-map>
+ <!-- <div v-if="form.location?.length >= 3">
+ {{ form.location[2] || '获取地址中...' }}
+ </div> -->
+ </div>
+ </el-form-item>
+ </el-col>
+ </el-row>
+
+ <el-row :gutter="16">
+ <el-col :span="12">
+ <el-form-item label="工单内容" prop="content">
+ <el-input
+ type="textarea"
+ v-model="form.content"
+ :rows="4"
+ placeholder="请输入工单内容描述"
+ ></el-input>
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="事件图片" prop="photos" required class="upload-wrapper">
+ <el-upload
+ v-if="createoredit === 1"
+ ref="upload"
+ :action="'#'"
+ :auto-upload="false"
+ list-type="picture-card"
+ :on-change="handleFileChange"
+ :on-remove="handleUploadRemove"
+ :before-upload="beforeUpload"
+ :file-list="form.photos"
+ :limit="1"
+ accept="image/*"
+ class="create-upload"
+ >
+ <template v-if="form.photos.length < 1">
+ <!-- <i class="el-icon-plus">+</i> -->
+ <div class="el-icon-plus">
+ <span>+</span>
+ </div>
+ </template>
+ </el-upload>
+ <el-upload
+ v-else
+ ref="upload"
+ :action="'#'"
+ :auto-upload="false"
+ list-type="picture-card"
+ :on-change="handleFileChange"
+ :on-remove="handleUploadRemove"
+ :before-upload="beforeUpload"
+ :file-list="popupShowImage(form.photos)"
+ :limit="1"
+ accept="image/*"
+ class="create-upload"
+ >
+ <template v-if="form.photos.length < 1">
+ <div class="el-icon-plus">
+ <span>+</span>
+ </div>
+ </template>
+ </el-upload>
+ <div class="upload-tip">需上传含有地址信息的照片(jpg、jpeg、png),且不超过5M</div>
+ </el-form-item>
+ </el-col>
+ </el-row>
+ </div>
+ </el-form>
+ <template #footer>
+ <div class="dialog-footer-new">
+ <el-button
+ type="danger"
+ :loading="submitLoading"
+ @click="submitForm"
+ icon="el-icon-position"
+ >发布
+ </el-button>
+ <el-button
+ type="infoprimary"
+ plain
+ :loading="draftLoading"
+ @click="saveDraft"
+ icon="el-icon-document-add"
+ >存草稿
+ </el-button>
+
+ <el-button @click="handleCancel" icon="el-icon-circle-close">取 消</el-button>
+ </div>
+ </template>
+ </el-dialog>
+
+ <!-- 工单详情对话框 -->
+ <el-dialog
+ class="custom-dialog"
+ align-center
+ v-model="detailVisible"
+ title="工单详情"
+ width="80%"
+ append-to-body
+ >
+ <div class="detail-container">
+ <div class="detail-top-title">
+ <div class="event-title-center event-orderNumber">
+ {{ currentDetail.orderNumber || '工单编号' }}
+ </div>
+ <div class="event-title-center">{{ currentDetail.orderName || '事件名称' }}</div>
+ </div>
+ <div v-if="totalTime" class="event-total-time">总耗时:{{ totalTime }}</div>
+ <!-- 工单状态流程 -->
+ <div class="custom-steps-container">
+ <!-- 标题行 -->
+ <div class="steps-titles">
+ <div
+ v-for="(status, index) in stepStatusList"
+ :key="index"
+ :class="{
+ 'step-title': true,
+ active: index <= stepStatusList.indexOf(String(currentDetail.status)),
+ }"
+ >
+ {{ mapStatus(status) }}
+ </div>
+ </div>
+
+ <!-- Element Steps 组件 -->
+ <el-steps :active="getActiveStep() - 1" align-center class="custom-steps">
+ <el-step v-for="(status, index) in stepStatusList" :key="index">
+ <template #description>
+ <span class="step-description">
+ {{ getStepHandler(status) }}
+ </span>
+ <div class="step-description" v-if="getStepTime(status)">
+ <span class="step-timer"> 耗时:{{ getStepTime(status) }} </span>
+ </div>
+ <div class="step-description">
+ {{ getStepCreateTime(status) }}
+ </div>
+ </template>
+ </el-step>
+ </el-steps>
+ </div>
+
+ <div class="PopUpTableScrolls">
+ <!-- 基本信息表格 -->
+ <el-table :show-header="false" :data="formattedDetailFields" border class="tableCss">
+ <el-table-column prop="label1" label="基本信息" width="150">
+ <template #default="{ row }">
+ <!-- 添加必填星号的标签 -->
+ <span
+ v-if="
+ currentDetail.status === 0 &&
+ (row.label1 === '关联算法' || row.label1 === '工单名称')
+ "
+ class="required-label"
+ >
+ <span class="required-star">*</span>{{ row.label1 }}
+ </span>
+ <span v-else>{{ row.label1 }}</span>
+ </template>
+ </el-table-column>
+ <el-table-column>
+ <template #default="{ row }">
+ <template
+ v-if="
+ currentDetail.status === 0 &&
+ row.label1 === '工单名称' &&
+ hasProcessingBtnPermission()
+ "
+ >
+ <el-input
+ v-model="currentDetail.orderName"
+ placeholder="请输入工单名称"
+ class="required-input"
+ />
+ </template>
+ <template
+ v-else-if="
+ currentDetail.status === 0 &&
+ row.label1 === '关联算法' &&
+ hasProcessingBtnPermission()
+ "
+ >
+ <el-select
+ v-model="currentDetail.aiType"
+ placeholder="请选择关联算法"
+ class="required-input"
+ >
+ <el-option
+ v-for="item in algorithms2"
+ :key="item.value"
+ :label="item.label"
+ :value="item.value"
+ />
+ </el-select>
+ </template>
+ <template v-else>{{ row.value1 }}</template>
+ </template>
+ </el-table-column>
+ <el-table-column prop="label2" label="基本信息" width="150">
+ <template #default="{ row }">
+ <!-- 添加必填星号的标签 -->
+
+ <span
+ v-if="currentDetail.status === 0 && row.label2 === '工单内容'"
+ class="required-label"
+ >
+ <span class="required-star">*</span>{{ row.label2 }}
+ </span>
+ <span v-else>{{ row.label2 }}</span>
+ </template>
+ </el-table-column>
+ <el-table-column>
+ <template #default="{ row }">
+ <!-- 修改工单类型和工单内容的显示 -->
+
+ <template
+ v-if="
+ currentDetail.status === 0 &&
+ row.label2 === '工单内容' &&
+ hasProcessingBtnPermission()
+ "
+ >
+ <el-input
+ type="textarea"
+ v-model="currentDetail.content"
+ placeholder="请输入工单内容"
+ class="required-input"
+ />
+ </template>
+ <template v-else>{{ row.value2 }}</template>
+ </template>
+ </el-table-column>
+ </el-table>
+
+ <!-- 事件处理详情 -->
+ <div v-if="[3, 4].includes(currentDetail.status)" class="form-section">
+ <div class="section-title">
+ <!-- 处理中状态显示必填星号 -->
+ <template v-if="currentDetail.status === 3">
+ <span class="required-label">
+ <span class="required-star">*</span>事件处理详情
+ </span>
+ </template>
+ <template v-else> 事件处理详情</template>
+ </div>
+ <!-- 处理中状态显示输入框 -->
+ <template v-if="currentDetail.status === 3 && hasProcessedAndOverBtnPermission()">
+ <el-input
+ type="textarea"
+ v-model="currentDetail.processingDetail"
+ placeholder="请输入事件处理详情"
+ :rows="4"
+ style="width: 100%; margin-bottom: 10px"
+ />
+ </template>
+ <!-- 已完成和已完结状态显示只读文本 -->
+ <template v-else>
+ <div class="readonly-processing-detail">
+ {{ currentDetail.processingDetail }}
+ </div>
+ </template>
+ </div>
+
+ <!-- 上传图片 -->
+ <div v-if="[3].includes(currentDetail.status)" class="form-section uploadImg">
+ <div class="section-title" v-if="hasProcessedAndOverBtnPermission()">
+ <!-- 已完成状态显示必填星号 -->
+ <template v-if="currentDetail.status === 3">
+ <span class="required-label"> <span class="required-star">*</span>上传图片 </span>
+ </template>
+ <template v-else> 上传图片</template>
+ </div>
+ <el-upload
+ v-if="hasProcessedAndOverBtnPermission()"
+ ref="upload"
+ :action="'#'"
+ :auto-upload="false"
+ list-type="picture-card"
+ :on-change="handleFileChange"
+ :on-remove="handleUploadRemove"
+ :before-upload="beforeUpload"
+ :file-list="currentDetail.photos || []"
+ :limit="1"
+ accept="image/*"
+ class="detail-upload"
+ >
+ <template v-if="!currentDetail.photos || currentDetail.photos.length < 1">
+ <!-- <i class="el-icon-plus">+</i> -->
+ <div class="el-icon-plus">
+ <span>+</span>
+ </div>
+ </template>
+ </el-upload>
+ <div class="el-upload__tip" v-if="hasProcessedAndOverBtnPermission()">
+ (上传照片即可完结工单,只能上传jpg、jpeg、png照片,且不超过5M)
+ </div>
+ </div>
+
+ <!-- 图片和地图部分 -->
+ <div class="media-section">
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <div class="media-box">
+ <div class="media-title">事件图片</div>
+ <div class="media-content">
+ <el-image
+ v-if="currentDetail.mediaUrl"
+ :src="getThumbUrl(currentDetail.mediaUrl)"
+ :preview-src-list="[getPreviewUrl(currentDetail.mediaUrl)]"
+ fit="contain"
+ style=" cursor: pointer"
+ >
+ <template #placeholder>
+ <div class="image-placeholder">
+ <i class="el-icon-picture-outline"></i>
+ <span>加载中...</span>
+ </div>
+ </template>
+ <template #error>
+ <div class="image-error">
+ <i class="el-icon-picture-outline"></i>
+ <span>加载失败</span>
+ </div>
+ </template>
+ </el-image>
+ <div v-else class="no-media">暂无图片/视频</div>
+ </div>
+ </div>
+ </el-col>
+ <el-col :span="12">
+ <div class="media-box">
+ <!-- 根据状态显示不同的标题和内容 -->
+
+ <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)"
+ :preview-src-list="[getPreviewUrl(currentDetail.updatePhotoUrl)]"
+ fit="fill"
+ >
+ <template #placeholder>
+ <div class="image-placeholder">
+ <i class="el-icon-picture-outline"></i>
+ <span>加载中...</span>
+ </div>
+ </template>
+ <template #error>
+ <div class="image-error">
+ <i class="el-icon-picture-outline"></i>
+ <span>加载失败</span>
+ </div>
+ </template>
+ </el-image>
+ <div v-else class="no-media">暂无处理图片</div>
+ </div>
+ </template>
+ <template v-else>
+ <div class="media-title">
+ 地图标记事件点
+ <el-popover
+ v-if="currentDetail.status === 3 || currentDetail.status === 0"
+ popper-class="custom-qrcode-popover"
+ :width="120"
+ :visible="currentDetail.showQR && detailVisible"
+ placement="top"
+ title=""
+ trigger="click"
+ >
+ <template #reference>
+ <img
+ @click.stop="handleQRCode(currentDetail)"
+ class="QRCodeImg"
+ src="@/assets/images/dataCenter/qrCode.svg"
+ alt=""
+ title="事件导航"
+ />
+ </template>
+ <div class="qrcode-content">
+ <div class="close-btn" @click.stop="currentDetail.showQR = false">×</div>
+ <CreateQRcode
+ v-if="currentDetail.showQR && detailVisible"
+ :latAndLon="currentDetail.location"
+ ></CreateQRcode>
+ </div>
+ </el-popover>
+ </div>
+ <div class="media-content">
+ <map-container v-if="detailVisible" ref="MapContainer"></map-container>
+ </div>
+ </template>
+ </div>
+ </el-col>
+ </el-row>
+ </div>
+ </div>
+
+ <!-- 操作按钮 -->
+ <div class="dialog-footer1-new">
+ <div class="leftBtn" :class="currentIndex === 0 ? 'disableds' : ''" @click="leftClick">
+ 上一页
+ </div>
+ <div class="btngroups">
+ <template v-if="currentDetail.status === 2">
+ <!-- 待审核 -->
+ <el-button
+ v-if="hasReviewBtnPermission()"
+ type="primary"
+ :loading="approveLoading"
+ @click="approveTicket"
+ icon="el-icon-check"
+ >通过
+ </el-button>
+ <el-button
+ v-if="hasReviewBtnPermission()"
+ type="danger"
+ :loading="rejectLoading"
+ @click="rejectTicket"
+ icon="el-icon-close"
+ >不通过
+ </el-button>
+ <el-button @click="detailVisible = false" icon="el-icon-circle-close">取消</el-button>
+ </template>
+ <template v-else-if="currentDetail.status === 0">
+ <el-button
+ v-if="hasProcessingBtnPermission()"
+ type="primary"
+ :loading="dispatchLoading"
+ @click="approveAndDispatch"
+ icon="el-icon-check"
+ >受理
+ </el-button>
+ <el-button
+ v-if="hasProcessingBtnPermission()"
+ type="danger"
+ :loading="rejectLoading"
+ @click="rejectTicket"
+ icon="el-icon-close"
+ >不受理
+ </el-button>
+ <el-button @click="detailVisible = false" icon="el-icon-circle-close">取消</el-button>
+ </template>
+ <template v-if="currentDetail.status === 3">
+ <!-- 处理中 -->
+ <el-button
+ v-if="hasProcessedAndOverBtnPermission()"
+ type="primary"
+ :loading="completeLoading"
+ @click="completeTicket"
+ icon="el-icon-circle-check"
+ >完成工单
+ </el-button>
+ <el-button @click="detailVisible = false" icon="el-icon-circle-close">取消</el-button>
+ </template>
+ <template v-else-if="currentDetail.status === 4">
+ <!-- 已完成 -->
+ <!-- <el-button v-if="hasProcessedAndOverBtnPermission()" type="primary" :loading="finalizeLoading"
+ @click="finalizeTicket">完结工单</el-button> -->
+ <el-button @click="detailVisible = false" icon="el-icon-circle-close">取消</el-button>
+ </template>
+ </div>
+ <div
+ :class="currentIndex === tableData.length - 1 ? 'disableds' : ''"
+ class="leftBtn"
+ @click="rightClick"
+ >
+ 下一页
+ </div>
+ </div>
+ </div>
+ </el-dialog>
+
+ <!-- 派发工单对话框 -->
+ <el-dialog
+ v-model="dispatchDialogVisible"
+ title="派发工单"
+ width="40%"
+ :close-on-click-modal="false"
+ >
+ <el-form :model="dispatchForm" :rules="dispatchRules" ref="dispatchForm" label-width="100px">
+ <el-form-item label="选择部门" prop="department">
+ <el-select
+ v-model="dispatchForm.department"
+ placeholder="请选择部门"
+ @change="handleDispatchDepartmentChange"
+ >
+ <el-option
+ v-for="dept in departments"
+ :key="dept.value"
+ :label="dept.label"
+ :value="dept.value"
+ />
+ </el-select>
+ </el-form-item>
+ <el-form-item label="选择处理人" prop="handler">
+ <el-select
+ filterable
+ v-model="dispatchForm.handler"
+ placeholder="请选择处理人"
+ :disabled="!dispatchForm.department"
+ >
+ <el-option
+ v-for="user in availableDispatchHandlers"
+ :key="user.id"
+ :label="user.name"
+ :value="user.id"
+ />
+ </el-select>
+ </el-form-item>
+ </el-form>
+ <template #footer>
+ <el-button @click="dispatchDialogVisible = false" icon="el-icon-circle-close"
+ >取消
+ </el-button>
+ <el-button type="primary" :loading="dispatchLoading" @click="submitDispatch"
+ >确认派发
+ </el-button>
+ </template>
+ </el-dialog>
+
+ <!-- 添加在其他 dialog 组件之后 -->
+ <el-dialog
+ v-model="reviewDialogVisible"
+ title="批量审核"
+ width="1100"
+ append-to-body
+ class="ztzf-dialog-mange"
+ custom-class="review-dialog"
+ @close="cancleBatchReject"
+ >
+ <div class="review-container">
+ <div class="review-image-wrapper">
+ <!-- 修改左右箭头的显示条件 -->
+ <el-button
+ v-if="selections.length > 1 && currentReviewImage"
+ class="arrow-button left"
+ @click="handlePrevImage"
+ icon="el-icon-arrow-left"
+ >
+ </el-button>
+
+ <div class="review-image-container">
+ <!-- <el-image v-if="currentReviewImage" :src="getThumbUrl(currentReviewImage)" fit="fill"
+ :preview-src-list="getImageList()" :initial-index="currentImageIndex - 1" class="preview-image"
+ style="cursor: pointer"> -->
+ <el-image
+ v-if="currentReviewImage"
+ :src="getPreviewUrl(currentReviewImage)"
+ fit="fill"
+ :initial-index="currentImageIndex - 1"
+ class="preview-image"
+ style="cursor: pointer"
+ >
+ <template #error>
+ <div class="image-error">
+ <i class="el-icon-picture-outline"></i>
+ <span>图片加载失败</span>
+ </div>
+ </template>
+ </el-image>
+ <div v-else class="no-image">暂无图片</div>
+ </div>
+
+ <!-- 修改右箭头的显示条件 -->
+ <el-button
+ v-if="selections.length > 1 && currentReviewImage"
+ class="arrow-button right"
+ @click="handleNextImage"
+ icon="el-icon-arrow-right"
+ >
+ </el-button>
+ </div>
+
+ <!-- 修改分页器的显示条件 -->
+ <div class="review-pagination" v-if="selections.length > 1 && currentReviewImage">
+ <el-pagination
+ small
+ layout="prev, pager, next"
+ hide-on-single-page
+ :total="selections.length"
+ :current-page="currentImageIndex"
+ :page-size="1"
+ @current-change="handleImagePageChange"
+ >
+ </el-pagination>
+ </div>
+ </div>
+
+ <template #footer>
+ <div class="dialog-footer">
+ <el-button type="primary" @click="handleBatchApprove" icon="el-icon-check"
+ >通过
+ </el-button>
+ <el-button type="danger" @click="handleBatchReject" icon="el-icon-close"
+ >不通过
+ </el-button>
+ <el-button @click="cancleBatchReject" icon="el-icon-circle-close">取消</el-button>
+ </div>
+ </template>
+ </el-dialog>
+
+ <!-- 复核弹出层 -->
+
+ <el-dialog
+ v-model="reCheckDialog"
+ title="工单复核"
+ width="30%"
+ append-to-body
+ class="ztzf-dialog-mange"
+ @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 { pxToRem, pxToRemNum } from '@/utils/rem';
+import { getSmallImg, getShowImg } from '@/utils/util';
+import { ElMessageBox, ElLoading } from 'element-plus';
+import { calculateDefaultRange } from '@/utils/util';
+import { gcj02ToWgs84, wgs84ToGcj02 } from '@/utils/coordinateTransformation';
+import _ from 'lodash';
+import {
+ getList,
+ createTicket,
+ getTicketInfo,
+ flowEvent,
+ getstatusCount,
+ getStepInfo,
+ getReviewById,
+ getCreateEventJob,
+ exportTheTicket
+} from '@/api/tickets/ticket';
+import { getSFDictionaryTree } from '@/api/job/task';
+import { export_json_to_excel } from '@/utils/exportExcel';
+import geoJson from '@/assets/geoJson.json';
+import { mapGetters } from 'vuex';
+import { getAdcodeObj } from '@/utils/disposeData';
+import elTooltipCopy from '@/components/ElTooltipCopy.vue';
+import getBaseConfig from '@/buildConfig/config';
+import CreateQRcode from '@/components/CreateQRcode/CreateQRcode.vue';
+import { CircleClose } from '@element-plus/icons-vue';
+
+const { envName } = getBaseConfig();
+
+function regExp(label, name) {
+ var reg = new RegExp(label + '=([^&]*)(&|$)', 'g');
+ return name.match(reg)[0].split('=')[1];
+}
+
+export default {
+ components: { elTooltipCopy, CreateQRcode },
+ name: 'TicketPage',
+ data() {
+ return {
+ currentIndex: null, // 当前显示的数据索引
+ submitLoading: false, // 新增loading状态
+ draftLoading: false,
+ approveLoading: false,
+ rejectLoading: false,
+ dispatchLoading: false,
+ completeLoading: false,
+ finalizeLoading: false,
+ createoredit: '',
+ activeTab: 'all',
+ // tabs 只保留静态结构,不做权限判断
+ tabs: [
+ { label: '全部工单', name: 'all', value: null, count: 0 },
+ { label: '待审核', name: 'pending', value: 2, count: 0 },
+ { 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: 'myTickets', value: null, count: 0 },
+ ],
+ filters: {
+ keyword: '',
+ department: '',
+ type: '',
+ dateRange: [],
+ status: '',
+ algorithm: '', // 新增算法筛选字段
+ isReview: '', // 添加复核状态筛选字段
+ },
+ departments: [],
+ types: [],
+ allAlgorithms: [],
+ handlers: [
+ { label: '处理人A', value: 'handlerA' },
+ { label: '处理人B', value: 'handlerB' },
+ ],
+
+ algorithms: [],
+ algorithms2: [],
+ statuses: [
+ { label: '待审核', value: '2' },
+ { label: '待处理', value: '0' },
+ { label: '处理中', value: '3' },
+ { label: '已完成', value: '4' },
+ ],
+ reviewStatuses: [
+ { label: '否', value: 0 },
+ { label: '是', value: 1 },
+ ],
+ tableData: [],
+ option: {
+ border: true,
+ stripe: false,
+ selection: true, // 添加多选功能
+ index: true, // 保留序号功能
+ indexLabel: '序号',
+ indexWidth: 60,
+ menuWidth: 150,
+ searchMenuSpan: 6,
+ viewBtn: false,
+ editBtn: false,
+ delBtn: false,
+ addBtn: false,
+ menu: true,
+ page: true,
+
+ height: 'auto',
+ calcHeight: 196,
+
+ column: [
+ // { label: "序号", prop: "id", width: 70 },
+ { label: '工单编号', prop: 'orderNumber', width: 170 },
+ { label: '工单名称', prop: 'orderName', width: 150, overHidden: true, tooltip: true },
+ { label: '所属部门', prop: 'department', overHidden: true, tooltip: true },
+ { label: '发起任务时间', prop: 'startTime', width: 160, overHidden: true },
+ { label: '关联算法', prop: 'aiType', overHidden: true, tooltip: true },
+ {
+ label: '工单类型',
+ prop: 'type',
+ width: 110,
+ overHidden: true,
+ tooltip: true,
+ type: 'select',
+ dicData: [],
+ },
+ {
+ label: '工单内容',
+ prop: 'content',
+ slot: true,
+ width: 152,
+ overHidden: true,
+ },
+ { label: '创建人', prop: 'creator', width: 120, overHidden: true },
+ { label: '处理人', prop: 'handler', width: 120, overHidden: true },
+ {
+ slot: true,
+ hide: envName === 'jiangwu' ? true : false,
+ label: '复核状态',
+ prop: 'isReview',
+ width: 90,
+ },
+ { label: '工单状态', prop: 'status', slot: true, width: 90 },
+ ],
+ },
+ page: {
+ pageSize: 10,
+ currentPage: 1,
+ total: 0,
+ },
+ dialogVisible: false,
+ detailVisible: false,
+ currentDetail: {},
+ form: {
+ name: '',
+ type: '',
+ department: '',
+ handler: '',
+ algorithm: [], // 关联算法改为数组
+ location: [], // 将存储为[经度, 纬度, 地址]格式
+ address: '',
+ photos: [],
+ content: '', // 新增字段,用于存储后端返回的 content
+ },
+ rules: {
+ name: [{ required: true, message: '请输入工单名称', trigger: 'blur' }],
+ type: [{ required: true, message: '请选择工单类型', trigger: 'change' }],
+ department: [{ required: true, message: '请选择所属部门', trigger: 'change' }],
+ handler: [{ required: true, message: '请选择处理人员', trigger: 'change' }],
+ content: [{ required: true, message: '请输入工单内容', trigger: 'blur' }],
+ algorithm: [{ required: true, message: '请选择关联算法', trigger: 'change' }],
+ location: [
+ {
+ required: true,
+ validator: (rule, value, callback) => {
+ if (!value || value.length < 2) {
+ callback(new Error('请选择位置信息'));
+ } else if (!value[0] || !value[1]) {
+ callback(new Error('请选择位置信息'));
+ } else {
+ callback();
+ }
+ },
+ trigger: 'change',
+ },
+ ],
+ photos: [
+ {
+ validator: (rule, value, callback) => {
+ if (!this.form.photos || this.form.photos.length === 0) {
+ callback(new Error('请上传工单图片'));
+ } else {
+ callback();
+ }
+ },
+ trigger: 'change',
+ },
+ ],
+ },
+ departmentUsers: {},
+ loading: false,
+ globalCounts: {},
+ mapLoaded: false,
+ isFetching: false,
+ mapParams: {
+ zoom: 15,
+ center: null, // 初始设为 null,等待动态设置
+ },
+ dispatchDepartment: '', // 新增:派发部门
+ dispatchHandler: '', // 新增:派发处理人
+ dispatchDialogVisible: false, // 新增:派发对话框可见性
+ dispatchForm: {
+ department: '',
+ handler: '',
+ }, // 新增:派发表单数据
+ dispatchRules: {
+ department: [{ required: true, message: '请选择部门', trigger: 'change' }],
+ handler: [{ required: true, message: '请选择处理人', trigger: 'change' }],
+ }, // 新增:派发表单验证规则
+ stepInfos: [], // 新增:存储步骤信息
+ fixedStatuses: ['2', '0', '3', '4'], // 固定的五个状态
+ userNameToIdMap: {}, // 新增用户名到ID的映射
+ workType: 0, // 新增:当前工单work_type
+ selections: [], // 添加选中行数据数组
+ reviewDialogVisible: false, // 新增:审核对话框可见性
+ currentReviewImage: '', // 新增:当前审核图片
+ currentImageIndex: 1, // 新增:当前图片索引
+ totalTime: '',
+ isShowInfo: false,
+
+ // 配置时间选择器默认配置
+ datePickerDefaultVal: calculateDefaultRange(),
+
+ // 复核弹窗
+ reCheckDialog: false,
+ treePropsSF: {
+ label: 'dictValue',
+ value: 'id',
+ children: 'children',
+ },
+ dictKey: '',
+ dataList: [],
+ checkedKeys: [],
+ };
+ },
+ created() {
+ this.inputMapShowDefaultCenter = null;
+
+ this.loadAMapScripts();
+ this.fetchDropdownData();
+ },
+
+ mounted() {
+ this.getAlgorithmList();
+
+ const href = this.$route.href;
+ if (this.$route?.query?.status !== undefined && this.$route?.query?.status !== null) {
+ this.filters.status = this.$route?.query?.status + '';
+ this.$router.replace({});
+ }
+
+ let curQueryParams = {};
+ let isTabProcessed = false;
+ if (href.indexOf('?') != -1 && href.split('?').length > 0) {
+ curQueryParams = href
+ .split('?')[1]
+ .split('&')
+ .reduce((pre, cur) => {
+ let newArr = cur.split('=');
+
+ pre[newArr[0]] = newArr[1];
+
+ return pre;
+ }, {});
+
+ const { orderNumber = undefined, day = undefined, tab = undefined } = curQueryParams;
+
+ // 日历传值
+ if (day) {
+ const date = new Date(day + 'T00:00:00+08:00');
+ const dateArray = [date, date];
+ const handler = {
+ get(target, prop) {
+ if (typeof prop === 'string' && /^\d+$/.test(prop)) {
+ const index = parseInt(prop);
+ const dateObj = target[index];
+ return dateObj.toDateString() + ' 00:00:00 GMT+0800 (中国标准时间)';
+ }
+ return Reflect.get(target, prop);
+ },
+ };
+
+ const proxyArray = new Proxy(dateArray, handler);
+ this.filters.dateRange = proxyArray;
+ }
+
+ if (orderNumber) {
+ this.filters.keyword = orderNumber;
+ this.$nextTick(() => {
+ this.isShowInfo = true;
+ const find = this.$store.state.tags.bsTagList.find(i => i.path === '/tickets/ticket');
+ find && (find.query = {});
+ });
+ }
+ if (tab) {
+ const isTabValid = this.filteredTabs.some(t => t.name === tab);
+ if (isTabValid) {
+ this.activeTab = tab;
+ this.handleTabChangeAfterJump();
+ isTabProcessed = true;
+ const find = this.$store.state.tags.bsTagList.find(i => i.path === '/tickets/ticket');
+ find && (find.query = {});
+ }
+ }
+ }
+
+ this.fetchTabCounts(); // 新增:初始化时获取 tab 数据
+ this.fetchTableData();
+ },
+ computed: {
+ CircleClose() {
+ return CircleClose;
+ },
+ firstRowData() {
+ return this.tableData.length > 0 ? this.tableData[0] : null;
+ },
+ availableHandlers() {
+ return this.form.department ? this.departmentUsers[this.form.department] || [] : [];
+ },
+ availableDispatchHandlers() {
+ return this.dispatchForm.department
+ ? this.departmentUsers[this.dispatchForm.department] || []
+ : [];
+ },
+ detailTableData() {
+ return [
+ {
+ label: '工单名称',
+ value: this.currentDetail.orderName,
+ editable: this.currentDetail.status === 0,
+ type: 'input',
+ },
+ {
+ label: '关键任务',
+ value: this.currentDetail.keyData,
+ editable: false,
+ },
+ {
+ label: '工单创建人',
+ value: this.currentDetail.creator,
+ editable: false,
+ },
+ {
+ label: '当前状态',
+ value: this.mapStatus(this.currentDetail.status),
+ editable: false,
+ },
+ {
+ label: '事件地址',
+ value: this.currentDetail.address,
+ editable: false,
+ },
+ {
+ label: '工单类型',
+ value: this.currentDetail.type,
+ editable: this.currentDetail.status === 0,
+ type: 'select',
+ options: this.types,
+ },
+ {
+ label: '关联算法',
+ value: this.currentDetail.aiType,
+ editable: false,
+ },
+ {
+ label: '发起单位',
+ value: this.currentDetail.department,
+ editable: false,
+ },
+ {
+ label: '发起任务时间',
+ value: this.currentDetail.startTime,
+ editable: false,
+ },
+ {
+ label: '工单内容',
+ value: this.currentDetail.content,
+ editable: this.currentDetail.status === 0,
+ type: 'textarea',
+ },
+ ];
+ },
+ detailFields() {
+ return [
+ {
+ label: '工单名称',
+ value: this.currentDetail.orderName,
+ editable: this.currentDetail.status === 0,
+ type: 'input',
+ },
+ { label: '关键任务', value: this.currentDetail.keyData, editable: false },
+ { label: '工单创建人', value: this.currentDetail.creator, editable: false },
+ { label: '当前状态', value: this.mapStatus(this.currentDetail.status), editable: false },
+ { label: '事件地址', value: this.currentDetail.address, editable: false },
+ {
+ label: '工单类型',
+ value: this.currentDetail.type,
+ editable: this.currentDetail.status === 0,
+ type: 'select',
+ options: this.types,
+ },
+ { label: '关联算法', value: this.currentDetail.aiType, editable: false },
+ { label: '发起单位', value: this.currentDetail.department, editable: false },
+ { label: '发起任务时间', value: this.currentDetail.startTime, editable: false },
+ {
+ label: '工单内容',
+ value: this.currentDetail.content,
+ editable: this.currentDetail.status === 0,
+ type: 'textarea',
+ },
+ ];
+ },
+ formattedDetailFields() {
+ const fields = [
+ { label: '工单名称', value: this.currentDetail.orderName },
+ {
+ label: '工单类型',
+ // 修改这里:使用 types 数组查找对应的 label
+ value:
+ this.types.find(t => t.value === this.currentDetail.type)?.label ||
+ this.currentDetail.type,
+ },
+ { label: '关联任务', value: this.currentDetail.job_name || '/' },
+ { label: '工单创建人', value: this.currentDetail.creator },
+ { label: '当前状态', value: this.mapStatus(this.currentDetail.status) },
+ { label: '事件地址', value: this.currentDetail.address || this.currentDetail.latAndLon }, // 包含经纬度信息
+ {
+ label: '关联算法',
+ value:
+ this.algorithms.find(t => t.value === this.currentDetail.aiType)?.label ||
+ this.currentDetail.aiType,
+ },
+ { label: '发起单位', value: this.currentDetail.department },
+ { label: '发起任务时间', value: this.currentDetail.startTime },
+ { label: '工单内容', value: this.currentDetail.content },
+ ];
+
+ // 过滤掉值为 '/' 的列
+ const filteredFields = fields.filter(field => field.value !== '/');
+ // 将字段分成两列
+ const formattedFields = [];
+ for (let i = 0; i < filteredFields.length; i += 2) {
+ formattedFields.push({
+ label1: filteredFields[i]?.label || '',
+ value1: filteredFields[i]?.value || (filteredFields[i]?.label ? '暂无数据' : ''),
+ label2: filteredFields[i + 1]?.label || '',
+ value2: filteredFields[i + 1]?.value || (filteredFields[i + 1]?.label ? '暂无数据' : ''),
+ });
+ }
+
+ return formattedFields;
+ },
+ dynamicFixedStatuses() {
+ // 直接使用接口返回的 stepInfos
+ return this.stepInfos.map(step => String(step.status));
+ },
+ ...mapGetters(['userInfo', 'permission']),
+ // 动态过滤tabs,保证isShow为true/false
+ filteredTabs() {
+ // 统一处理权限,undefined视为false
+ const tabStatus = this.permission?.tickets_tab_status === true;
+ const tabPending = this.permission?.tickets_tab_pending === true;
+ const tabMyTickets = this.permission?.tickets_tab_mytickets === true;
+ return this.tabs
+ .map(tab => {
+ if (tab.name === 'all') {
+ return { ...tab, isShow: true };
+ }
+ if (tab.name === 'pending') {
+ return { ...tab, isShow: tabPending };
+ }
+ if (['processing', 'inProgress', 'completed', 'closed'].includes(tab.name)) {
+ return { ...tab, isShow: tabStatus };
+ }
+ if (tab.name === 'myTickets') {
+ return { ...tab, isShow: tabMyTickets };
+ }
+ return { ...tab, isShow: false };
+ })
+ .filter(tab => tab.isShow);
+ },
+ permissionList() {
+ // 可根据实际后端权限key调整
+ return {
+ addBtn: this.validData(this.permission.tickets_add, false),
+ delBtn: this.validData(this.permission.tickets_delete, false),
+ exportBtn: this.validData(this.permission.tickets_export, false),
+ reviewBtn: this.validData(this.permission.tickets_review, false),
+ };
+ },
+ stepStatusList() {
+ // “我发起的工单”tab用默认流程,其它tab用接口返回的stepInfos
+ if (this.activeTab === 'myTickets') {
+ if (this.workType === 1) {
+ return ['3', '4'];
+ }
+ return this.fixedStatuses;
+ }
+
+ // 其它tab直接用接口返回的stepInfos
+ return this.stepInfos.map(step => String(step.status));
+ },
+
+ showIsReviewText() {
+ return row => {
+ if (['4'].includes(String(row.status))) return row.isReview === 1 ? '是' : '否';
+
+ return '/';
+ };
+ },
+
+ popupShowImage() {
+ return list => {
+ return list.map(item => ({
+ ...item,
+ url: getShowImg(item.url),
+ }));
+ };
+ },
+ },
+
+ methods: {
+ handleCheck(data, { checkedKeys, checkedNodes }) {
+ this.checkedKeys = checkedKeys;
+ // 获取所有选中节点的 dictKey
+ const selectedDictKeys = checkedNodes.map(node => node.dictKey).filter(Boolean);
+ this.filters.type = selectedDictKeys;
+ this.fetchTableData();
+ },
+ // 算法
+ getAlgorithmList() {
+ getSFDictionaryTree({ code: 'SF' }).then(res => {
+ if (res.data.code === 200) {
+ const result = res.data.data[0].children;
+ // 过滤第一层数据
+ const filteredData = result.map(item => {
+ // 过滤第二层数据
+ const children = item.children?.map(child => ({
+ ...child,
+ children: [], // 清空第三层数据
+ }));
+ return {
+ ...item,
+ children: children || [],
+ };
+ });
+ this.dataList = filteredData;
+ }
+ });
+ },
+
+ handleSFNodeClick(data) {
+ this.filters.type = '';
+ this.filters.algorithm = '';
+ this.filters.type = data.dictKey;
+ // 更新列表请求
+ this.fetchTableData();
+ },
+ handleClear() {
+ this.dictKey = '';
+ this.filters.algorithm = '';
+ this.filters.type = '';
+ this.fetchTableData();
+ },
+ handleCellClick(row, column) {
+ console.log(row, column.no);
+ if (column.no === 2) {
+ navigator.clipboard.writeText(row.orderNumber).then(() => {
+ this.$message.success('复制工单编号成功');
+ });
+ } else if (column.no === 3) {
+ navigator.clipboard.writeText(row.orderName).then(() => {
+ this.$message.success('复制工单名称成功');
+ });
+ } else if (column.no === 4) {
+ navigator.clipboard.writeText(row.department).then(() => {
+ this.$message.success('复制所属部门成功');
+ });
+ }
+ },
+ // 左切换
+ leftClick() {
+ if (this.tableData.length === 0) return;
+ this.currentIndex = Math.max(0, this.currentIndex - 1);
+ this.updateCurrentDetail();
+ },
+ // 右切换
+ rightClick() {
+ if (this.tableData.length === 0) return;
+ this.currentIndex = Math.min(this.tableData.length - 1, this.currentIndex + 1);
+ this.updateCurrentDetail();
+ },
+ // 更新当前详情
+ updateCurrentDetail() {
+ this.currentDetail = this.tableData[this.currentIndex];
+ this.currentDetail.mediaUrl = this.currentDetail.photo_url;
+ this.currentDetail.updatePhotoUrl = this.currentDetail.update_photo_url;
+ this.currentDetail.processingDetail = this.currentDetail.content;
+ this.handleViewDetail(this.currentDetail);
+
+ // 如果使用地图组件,需要更新地图标记
+ this.$nextTick(() => {
+ if (this.$refs.MapContainer && this.currentDetail.location) {
+ this.$refs.MapContainer.initAddEntity('point', this.currentDetail.location);
+ }
+ });
+ },
+ async handleQRCode(val) {
+ val.showQR = !val.showQR;
+ },
+ async loadAMapScripts() {
+ try {
+ const areaCode = this.userInfo.detail.areaCode;
+ const subAreaCode = areaCode ? areaCode.substring(0, 6) : '';
+ const adcodeObj = getAdcodeObj(geoJson, 'adcode', subAreaCode);
+
+ // console.log('区域代码:', subAreaCode);
+
+ // 直接从返回对象中获取正确的路径
+ const center = adcodeObj?.payload?.objects?.collection?.geometries?.[0]?.properties?.center;
+
+ if (Array.isArray(center) && center.length === 2) {
+ this.inputMapShowDefaultCenter = center;
+ } else {
+ // 如果找不到中心点,尝试使用 bbox 的中心点
+ const bbox = adcodeObj?.payload?.bbox;
+ if (Array.isArray(bbox) && bbox.length === 4) {
+ const centerX = (bbox[0] + bbox[2]) / 2;
+ const centerY = (bbox[1] + bbox[3]) / 2;
+ this.inputMapShowDefaultCenter = [centerX, centerY];
+ } else {
+ this.inputMapShowDefaultCenter = [115.861365, 28.621311];
+ }
+ }
+ this.mapParams.center = [...this.inputMapShowDefaultCenter];
+
+ this.mapLoaded = true;
+ } catch (error) {
+ this.$message.error('地图加载失败,请检查网络或API Key配置');
+ }
+ },
+ async handleBatchApprove() {
+ try {
+ if (this.selections.length === 0) {
+ this.$message.warning('没有选中的工单');
+ return;
+ }
+
+ const currentItem = this.selections[this.currentImageIndex - 1];
+ if (!currentItem) {
+ this.$message.warning('当前工单数据无效');
+ return;
+ }
+
+ await this.$confirm('确认审核通过当前工单?', '提示', {
+ confirmButtonText: '确定',
+ cancelButtonText: '取消',
+ type: 'warning',
+ });
+
+ const data = {
+ id: currentItem.id,
+ status: currentItem.status,
+ isPass: 0,
+ eventNum: currentItem.orderNumber,
+ eventName: currentItem.orderName,
+ };
+
+ const response = await flowEvent(data);
+ if (response.data.code === 0) {
+ this.$message.success('工单审核通过');
+
+ // 创建新的数组而不是修改原数组
+ const newSelections = [...this.selections];
+ newSelections.splice(this.currentImageIndex - 1, 1);
+ this.selections = newSelections;
+
+ // 修正索引并更新图片
+ if (this.selections.length > 0) {
+ if (this.currentImageIndex > this.selections.length) {
+ this.currentImageIndex = this.selections.length;
+ }
+ this.updateCurrentReviewImage();
+ } else {
+ this.reviewDialogVisible = false;
+ this.currentImageIndex = 1;
+ this.currentReviewImage = '';
+ }
+
+ // 刷新表格数据
+ this.fetchTableData();
+ } else {
+ throw new Error(response.data.msg || '审核失败');
+ }
+ } catch (error) {
+ if (error === 'cancel') return;
+ this.$message.error(error.message || '审核失败,请稍后重试');
+ }
+ },
+ async fetchDropdownData() {
+ try {
+ const response = await getTicketInfo();
+
+ const { dept_data, event_type, ai_type, info } = response.data.data;
+
+ this.departments = dept_data.map(item => ({
+ label: item.dept_name,
+ value: item.id,
+ }));
+
+ this.departmentUsers = dept_data.reduce((acc, dept) => {
+ acc[dept.id] = dept.user_data || [];
+ return acc;
+ }, {});
+
+ this.types = Object.entries(event_type).map(([key, value]) => ({
+ label: value,
+ value: key,
+ }));
+
+ const columnType = this.findObject(this.option.column, 'type');
+ columnType.dicData = this.types;
+ this.allAlgorithms = info;
+
+ console.log('工单类型', this.types);
+ console.log('关联算法', this.allAlgorithms);
+ // 确保算法数据的映射一致
+ this.algorithms =
+ ai_type?.map(item => ({
+ dict_key: item.dict_key,
+ dict_value: item.dict_value,
+ // 同时添加 label 和 value 以兼容两处使用
+ label: item.dict_value,
+ value: item.dict_key,
+ })) || [];
+
+ this.algorithms2 = _.cloneDeep(this.algorithms);
+
+ // 构建用户ID和名称的映射关系
+ this.userNameToIdMap = {};
+ dept_data.forEach(dept => {
+ (dept.user_data || []).forEach(user => {
+ this.userNameToIdMap[user.name] = user.id;
+ });
+ });
+ } catch (error) {
+ this.$message.error('加载下拉框数据失败');
+ }
+ },
+ // 工单类型变化时触发
+ handleTypeChange(typeValue) {
+ this.form.algorithm = [];
+ if (!typeValue) {
+ // 未选择类型时清空算法列表
+ this.algorithms2 = [];
+ return;
+ }
+
+ const matchedCategory = this.allAlgorithms.find(category => category.dict_key === typeValue);
+
+ if (
+ !matchedCategory ||
+ !matchedCategory.algorithms ||
+ matchedCategory.algorithms.length === 0
+ ) {
+ // 无匹配的算法时清空
+ this.algorithms2 = [];
+ this.$message.info('该工单类型暂无关联算法');
+ return;
+ }
+ this.algorithms2 = matchedCategory.algorithms.map(algo => ({
+ label: algo.dict_value,
+ value: algo.dict_key,
+ dict_key: algo.dict_key,
+ dict_value: algo.dict_value,
+ }));
+ },
+
+ async fetchTableData() {
+ if (this.isFetching) return;
+ this.isFetching = true;
+ this.loading = true;
+ try {
+ const currentTab = this.tabs.find(tab => tab.name === this.activeTab);
+ const params = {
+ // word_order_types: this.filters.type || undefined,
+ ai_types: this.filters.type || undefined, // 算法使用该字段筛选
+ status:
+ currentTab?.name === 'myTickets'
+ ? undefined
+ : this.filters.status !== ''
+ ? Number(this.filters.status)
+ : currentTab?.value,
+ event_name: this.filters.keyword || undefined,
+ dept_id: this.filters.department || undefined,
+ start_date: this.filters.dateRange?.[0]
+ ? this.formatDate(this.filters.dateRange[0])
+ : undefined,
+ end_date: this.filters.dateRange?.[1]
+ ? this.formatDate(this.filters.dateRange[1]).replace('00:00:00', '23:59:59')
+ : undefined,
+ current: Number(this.page.currentPage), // 使用当前页码
+ size: Number(this.page.pageSize), // 使用每页条数
+ ai_type: this.filters.algorithm || undefined, // 添加算法参数
+ // 添加 is_draft 参数,仅在"我发起的"标签页时设置为1
+ 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, // 添加复核状态查询参数
+ source:1 ,//数据来源
+ };
+
+ const response = await getList(params);
+ if (!response?.data?.data?.records) {
+ throw new Error('接口返回数据格式不正确');
+ }
+
+ const { total, records } = response.data.data;
+ let filteredRecords = records;
+
+ // 如果是"我发起的"tab,过滤数据
+ // if (currentTab?.name === 'myTickets') {
+ // filteredRecords = records.filter(item =>
+ // String(item.create_user_id) === String(item.user_id)
+ // );
+ // }
+
+ this.tableData = filteredRecords.map(item => {
+ const longitude = Number(item.longitude) || 0;
+ const latitude = Number(item.latitude) || 0;
+ return {
+ id: item.id,
+ orderNumber: item.event_num, // 修改这里:优先使用 event_num
+ orderName: item.event_name,
+ department:
+ this.departments.find(d => d.value === item.dept_id)?.label || item.dept_name,
+ startTime: item.job_create_time || '/',
+ aiType: item.ai_types,
+ content: item.content, // 将后端返回的 content 映射为 content
+ type: item.work_order_type_dict_key,
+ keyData:
+ !isNaN(longitude) && !isNaN(latitude)
+ ? `${longitude.toFixed(6)}, ${latitude.toFixed(6)}`
+ : '未知位置',
+ address: item.address,
+ creator: item.event_num?.slice(0, 2) === 'AI' ? '智飞agent' : item.create_user,
+ handler: item.update_user || '未分配',
+ status: Number(item.status || 0),
+ // 保存原始字段
+ photo_url: item.photo_url || '', // 保存原始 photo_url
+ video_url: item.video_url || '', // 保存原始 video_url
+ location: !isNaN(longitude) && !isNaN(latitude) ? [longitude, latitude] : null,
+ processing_details: item.processing_details || '', // 添加处理详情字段
+ update_photo_url: item.update_photo_url || '', // 添加处理图片字段
+ 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, // 添加复核状态字段映射
+
+
+ };
+ });
+
+ // 更新总数显示
+ this.page.total = total || 0;
+
+ // 是否弹出详情页
+ if (this.isShowInfo) {
+ this.handleViewDetail(this.firstRowData);
+ this.isShowInfo = false; // 生效一次
+ }
+ await this.fetchTabCounts();
+ } catch (error) {
+ this.$message.error(error.message || '获取数据失败');
+ this.tableData = [];
+ this.page.total = 0;
+ } finally {
+ this.loading = false;
+ this.isFetching = false;
+ }
+ },
+
+ async submitForm() {
+ if (this.submitLoading) return; // 防止重复提交
+ this.submitLoading = true;
+ try {
+ // 提交时需要完整验证
+ await this.$refs.form.validate();
+
+ // 验证位置信息
+ if (!this.form.location || this.form.location.length < 2) {
+ this.$message.warning('请在地图上选择位置');
+ return;
+ }
+
+ // 修改图片验证逻辑
+ if (!this.form.photos || this.form.photos.length === 0) {
+ this.$message.warning('请上传工单图片');
+ return;
+ }
+
+ let [lng, lat] = this.disposeLocation(true, this.form);
+
+ const submitData = {
+ eventName: this.form.name,
+ content: this.form.content,
+ workType: '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], // 传数组
+ updateUser: this.form.handler,
+ createDept: this.form.department,
+ isDraft: 0,
+ };
+
+ if (this.form.id) {
+ submitData.id = this.form.id;
+ }
+
+ // 修改获取文件的逻辑
+ let file = null;
+ const photoInfo = this.form.photos[0];
+
+ if (photoInfo.raw) {
+ // 如果有新上传的文件,使用新文件
+ file = photoInfo.raw;
+ } else if (photoInfo.existingUrl) {
+ // 如果是已存在的图片,将URL添加到提交数据中
+ submitData.photoUrl = photoInfo.existingUrl;
+ } else {
+ this.$message.warning('图片文件无效,请重新上传');
+ return;
+ }
+
+ const response = await createTicket(submitData, file);
+ if (response.data.code === 0) {
+ this.$message.success('工单创建成功');
+ this.dialogVisible = false;
+ this.fetchTableData();
+ } else {
+ throw new Error(response.data.msg || '创建失败');
+ }
+ } catch (error) {
+ if (error.message.includes('验证未通过')) {
+ this.$message.warning('请填写完整的工单信息');
+ } else {
+ this.$message.error(error.message || '工单创建失败,请稍后重试');
+ }
+ } finally {
+ this.submitLoading = false;
+ }
+ },
+
+ async saveDraft() {
+ if (this.draftLoading) return; // 防止重复提交
+ this.draftLoading = true;
+ try {
+ let handlerValue = this.form.handler;
+ if (!handlerValue || handlerValue === '未分配') {
+ handlerValue = undefined;
+ }
+
+ // 验证位置信息
+ if (
+ !this.form.location ||
+ this.form.location.length < 2 ||
+ !this.form.location[0] ||
+ !this.form.location[1]
+ ) {
+ this.$message.warning('请在地图上选择位置');
+ return;
+ }
+
+ 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: lng,
+ latitude: lat,
+ address: this.form.address || undefined,
+ workOrderTypeDictKey: this.form.type || undefined,
+ aiType:
+ this.form.algorithm && this.form.algorithm.length > 0 ? this.form.algorithm : undefined, // 传数组
+ updateUser: handlerValue,
+ createDept: this.form.department || undefined,
+ isDraft: 1,
+ };
+
+ // 草稿时也至少需要工单名称
+ if (!submitData.eventName) {
+ this.$message.warning('请输入工单名称');
+ return;
+ }
+
+ // 过滤掉所有 undefined 的字段
+ Object.keys(submitData).forEach(
+ key => submitData[key] === undefined && delete submitData[key]
+ );
+
+ let file = null;
+ if (this.form.photos && this.form.photos.length > 0) {
+ file = this.form.photos[0].raw;
+ }
+
+ const response = await createTicket(submitData, file);
+ if (response.data.code === 0) {
+ this.$message.success('草稿保存成功');
+ this.dialogVisible = false;
+ (this.form = {
+ name: '',
+ type: '',
+ department: '',
+ handler: '',
+ algorithm: [], // 关联算法改为数组
+ location: [], // 将存储为[经度, 纬度, 地址]格式
+ address: '',
+ photos: [],
+ content: '', // 新增字段,用于存储后端返回的 content
+ }),
+ this.fetchTableData();
+ } else {
+ throw new Error(response.data.msg || '保存失败');
+ }
+ } catch (error) {
+ this.$message.error(error.message || '保存草稿失败,请稍后重试');
+ } finally {
+ this.draftLoading = false;
+ }
+ },
+ handleCancel() {
+ this.resetForm();
+ this.dialogVisible = false;
+ },
+ handleLocationChange(val) {
+ let locationValue = val.value;
+ if (locationValue && locationValue.length >= 2) {
+ // 兼容第三项为地址
+ const [lng, lat] = gcj02ToWgs84(locationValue[0], locationValue[1]);
+ this.form.location = [Number(lng), Number(lat), locationValue[2] || ''];
+ this.form.address = locationValue[2] || '';
+ } else {
+ this.form.location = [];
+ this.form.address = '';
+ }
+ },
+
+ 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`;
+ },
+
+ mapStatus(status) {
+ const statusTextMap = {
+ '-1': '草稿', // 添加草稿状态
+ 2: '待审核',
+ 0: '待处理',
+ 3: '处理中',
+ 4: '已完成',
+ };
+ return statusTextMap[status] || '未知状态';
+ },
+
+ getStatusTagType(status) {
+ // 草稿不加颜色
+ if (status === -1 || status === '-1') return '';
+ // 状态颜色映射
+ const colorMap = {
+ 0: '#FF7411', // 待处理-淡红
+ 3: '#FFC300', // 处理中-更淡红
+ 2: '#FF472F', // 待审核-橙色
+ 4: '#0291A1', // 已完成-淡蓝
+ };
+ return colorMap[String(status)] || '';
+ },
+
+ async fetchTabCounts() {
+ try {
+ // 判断是否有部门筛选
+ let params = {};
+ if (this.filters.department) {
+ params.deptId = this.filters.department;
+ }
+ const response = await getstatusCount(params);
+ const { statusCount, totalCount, userCount } = response.data.data;
+ this.tabs.forEach(tab => {
+ if (tab.name === 'all') {
+ tab.count = totalCount || 0; // 总工单数
+ } else if (tab.name === 'myTickets') {
+ tab.count = userCount || 0; // 我发起的工单数
+ } else {
+ tab.count = statusCount[String(tab.value)] || 0; // 根据状态值映射
+ }
+ });
+ } catch (error) {
+ this.$message.error('获取 tab 数据失败');
+ }
+ },
+
+ 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();
+ this.fetchTabCounts(); // 切换 tab 时重新获取数据
+ },
+ //新增 跳转后触发的Tab切换
+ handleTabChangeAfterJump() {
+ const isReview = this.findObject(this.option.column, 'isReview');
+ isReview.hide = !['all', 'completed', 'myTickets'].includes(this.activeTab);
+ this.handleReset();
+ this.page.currentPage = 1;
+ this.fetchTableData();
+ this.fetchTabCounts();
+ },
+ handleSearch() {
+ this.page.currentPage = 1;
+ this.fetchTableData();
+ this.fetchTabCounts();
+ },
+
+ handleReset() {
+ this.dictKey = '';
+ this.filters = {
+ keyword: '',
+ department: '',
+ type: '',
+ dateRange: [],
+ status: '',
+ algorithm: '', // 重置时清空算法筛选
+ isReview: '', // 重置时清空复核状态
+ };
+ this.page.currentPage = 1;
+ this.$router.replace({}); //清除url参数
+ this.fetchTableData();
+ },
+ handleKeyWords() {
+ this.$router.replace({}); //清除url参数
+ },
+
+ async handleCurrentChange(val) {
+ // 先更新页码
+ this.page.currentPage = val;
+ // 等待 DOM 更新后再请求数据
+ await this.$nextTick();
+ await this.fetchTableData();
+ },
+
+ async sizeChange(val) {
+ this.page.pageSize = val;
+ this.page.currentPage = 1; // 重置到第一页
+ await this.$nextTick();
+ await this.fetchTableData();
+ },
+
+ handleAdd() {
+ this.createoredit = 1;
+ this.dialogVisible = true;
+ this.mapParams.center = [...this.inputMapShowDefaultCenter];
+ this.form.location = [];
+ },
+
+ resetForm() {
+ this.form = {
+ name: '',
+ type: '',
+ department: '',
+ handler: '',
+ algorithm: [], // 关联算法改为数组
+ location: [], // 将存储为[经度, 纬度, 地址]格式
+ address: '',
+ content: '',
+ photos: [],
+ content: '', // 新增字段,用于存储后端返回的 content
+ };
+ if (this.$refs.form) {
+ this.$refs.form.resetFields();
+ }
+ },
+
+ formatLocation(location) {
+ if (!Array.isArray(location)) {
+ return '未知位置';
+ }
+ return `${location[0].toFixed(6)}, ${location[1].toFixed(6)}`;
+ },
+
+ async handleViewDetail(row) {
+ // 找到当前行在tableData中的索引
+ this.currentIndex = this.tableData.findIndex(item => item.id === row.id);
+ // 先设置workType,直接从row读取
+ this.workType = row.work_type !== undefined ? Number(row.work_type) : 0;
+
+ // 重置上传组件的文件列表
+ this.$nextTick(() => {
+ if (this.$refs.MapContainer && this.$refs.MapContainer.initAddEntity) {
+ this.$refs.MapContainer.initAddEntity('point', this.currentDetail.location);
+ }
+ });
+
+ const detailData = {
+ ...row,
+ processingDetail: row.processing_details || '',
+ mediaUrl: row.photo_url || row.video_url || '',
+ updatePhotoUrl: row.update_photo_url || '',
+ photos: [],
+ job_name: row.job_name || '', // 新增
+ };
+
+ let stepArr = [];
+ try {
+ const stepResponse = await getStepInfo(row.orderNumber);
+ const steps = Array.isArray(stepResponse.data.data)
+ ? stepResponse.data.data
+ : stepResponse.data.data?.steps || [];
+ 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 => ({
+ status: String(step.status),
+ name: step.name,
+ time: step.time,
+ create_time: step.create_time,
+ }));
+ } else {
+ const statusArr = this.workType === 1 ? ['3', '4'] : this.fixedStatuses;
+ this.stepInfos = statusArr.map(status => {
+ const step = steps.find(s => String(s.status) === String(status));
+ return {
+ status,
+ name: step ? step.name : '',
+ time: step ? step.time : null,
+ create_time: step ? step.create_time : null,
+ };
+ });
+ }
+ this.currentDetail.status = row.status;
+ } catch (error) {
+ if (this.activeTab === 'myTickets') {
+ const statusArr = this.workType === 1 ? ['3', '4'] : this.fixedStatuses;
+ this.stepInfos = statusArr.map(status => ({
+ status,
+ name: status === row.status ? row.handler || '未分配' : '未处理',
+ time: status === row.status ? row.startTime || '未知时间' : null,
+ }));
+ } else {
+ this.stepInfos = [];
+ }
+ }
+ console.log(detailData, 'detailDatadetailDatadetailData');
+ this.currentDetail = {
+ ...detailData,
+ // address: null,
+ showQR: false,
+ latAndLon: _.round(detailData.location[0], 6) + ',' + _.round(detailData.location[1], 6),
+ };
+
+ console.log('this.currentDetail', this.currentDetail);
+
+ this.detailVisible = true;
+ this.handleTypeChange(this.currentDetail.type);
+
+
+ this.$nextTick(() => {
+ if (this.$refs.MapContainer && this.$refs.MapContainer.initAddEntity) {
+ this.$refs.MapContainer.initAddEntity('point', this.currentDetail.location);
+ }
+ });
+ },
+
+ getStepHandler(status) {
+ const step = this.stepInfos.find(step => step.status === status);
+ return step ? step.name : '';
+ },
+
+ getStepTime(status) {
+ const step = this.stepInfos.find(step => step.status === status);
+ // 如果 step 不存在或 step.time 为 0,返回 false
+ return step && step.time && step.time !== '0' ? step.time : false;
+ },
+ getStepCreateTime(status) {
+ const step = this.stepInfos.find(step => step.status === status);
+ return step ? step.create_time : null;
+ },
+ getActiveStep() {
+ // 步骤索引适配
+ const arr = this.stepStatusList;
+ const index = arr.indexOf(String(this.currentDetail.status));
+ return index !== -1 ? index + 2 : 1;
+ },
+
+ openMap() {
+ const areaCode = this.userInfo.detail.areaCode;
+ const subAreaCode = areaCode ? areaCode.substring(0, 6) : '';
+ getAdcodeObj(geoJson, 'adcode', subAreaCode);
+ this.$message.info('地图选址功能暂未实现');
+ },
+
+ handlePreview(file) {
+ this.$message.info(`预览图片:${file.name}`);
+ },
+
+ handleRemove(file) {
+ this.$message.info(`移除图片:${file.name}`);
+ },
+
+ refreshChange() {
+ if (this.isFetching) return;
+ this.fetchTableData();
+ },
+
+ onLoad() {
+ if (this.isFetching) return;
+ this.fetchTableData();
+ },
+
+ async exportData() {
+ try {
+ this.loading = true;
+ let exportData = [];
+
+ // 如果有选中的数据,则导出选中的数据
+ if (this.selections.length > 0) {
+ exportData = this.selections.map(item => this.formatExportItem(item));
+ } else {
+ // 没有选中数据时,导出当前页面的数据
+ exportData = this.tableData.map(item => this.formatExportItem(item));
+ }
+
+ if (exportData.length === 0) {
+ this.$message.warning('没有数据可供导出');
+ return;
+ }
+
+ const headers = [
+ '工单编号',
+ '工单名称',
+ '所属部门',
+ '发起时间',
+ '关联算法',
+ '工单内容',
+ '工单类型',
+ '经纬度',
+ '创建人',
+ '处理人',
+ '工单状态',
+ ];
+
+ export_json_to_excel(headers, exportData, '工单数据');
+ this.$message.success('数据导出成功');
+ } catch (error) {
+ // console.error('导出失败:', error);
+ this.$message.error(error.message || '导出失败,请稍后重试');
+ } finally {
+ this.loading = false;
+ }
+ },
+
+ formatExportItem(item) {
+ const longitude = Number(item.longitude) || Number(item.location?.[0]) || 0;
+ const latitude = Number(item.latitude) || Number(item.location?.[1]) || 0;
+
+ return {
+ 工单编号: item.orderNumber || item.event_num || '',
+ 工单名称: item.orderName || item.event_name || '',
+ 所属部门: item.department || item.dept_name || '',
+ 发起时间: item.startTime || item.create_time || '',
+ 关联算法: item.aiType || item.ai_types || '',
+ 工单内容: item.address || item.content || '',
+ 工单类型:
+ this.types.find(t => t.value === (item.type || item.work_order_type_dict_key))?.label ||
+ '',
+ 经纬度:
+ !isNaN(longitude) && !isNaN(latitude)
+ ? `${longitude.toFixed(6)}, ${latitude.toFixed(6)}`
+ : '',
+ 创建人: item.creator || item.create_user || '',
+ 处理人: item.handler || item.update_user || '',
+ 工单状态: this.mapStatus(Number(item.status || 0)),
+ };
+ },
+
+ handleDepartmentChange(deptId) {
+ this.form.handler = '';
+ },
+
+ handleDispatchDepartmentChange(deptId) {
+ this.dispatchForm.handler = ''; // 清空处理人选择
+ },
+
+ // 文件改变时的钩子
+ handleFileChange(file, fileList) {
+ // 保持最新的文件列表
+ this.form.photos = fileList.map(item => ({
+ ...item,
+ existingUrl: item.url && !item.raw ? item.url : null, // 标记已存在的图片URL
+ }));
+ this.currentDetail.photos = this.form.photos;
+ },
+
+ // 文件移除时的钩子
+ handleUploadRemove(file, fileList) {
+ this.form.photos = fileList;
+ this.currentDetail.photos = fileList;
+ },
+
+ // 上传前的验证
+ beforeUpload(file) {
+ const isImage = file.type.includes('image');
+ const isLt5M = file.size / 1024 / 1024 < 5;
+
+ if (!isImage) {
+ this.$message.error('只能上传图片文件!');
+ return false;
+ }
+ if (!isLt5M) {
+ this.$message.error('图片大小不能超过5MB!');
+ return false;
+ }
+ return true;
+ },
+
+ async approveTicket() {
+ if (this.approveLoading) return;
+ this.approveLoading = true;
+ try {
+ const data = {
+ id: this.currentDetail.id,
+ status: this.currentDetail.status,
+ isPass: 0, // 0 表示通过
+ eventNum: this.currentDetail.orderNumber,
+ };
+
+ const file = this.currentDetail.file || null; // 如果没有文件,则为 null
+
+ const response = await flowEvent(data, file);
+ if (response.data.code === 0) {
+ this.$message.success('工单已通过');
+ this.detailVisible = false;
+ this.fetchTableData();
+ } else {
+ throw new Error(response.data.msg || '操作失败');
+ }
+ } catch (error) {
+ this.$message.error(error.message || '操作失败,请稍后重试');
+ } finally {
+ this.approveLoading = false;
+ }
+ },
+ async rejectTicket() {
+ if (this.rejectLoading) return;
+ this.rejectLoading = true;
+ try {
+ const data = {
+ id: this.currentDetail.id,
+ status: this.currentDetail.status,
+ isPass: 1, // 1 表示不通过
+ };
+
+ const response = await flowEvent(data);
+ if (response.data.code === 0) {
+ this.$message.success('工单未通过');
+ this.detailVisible = false;
+ this.fetchTableData();
+ } else {
+ throw new Error(response.data.msg || '操作失败');
+ }
+ } catch (error) {
+ this.$message.error(error.message || '操作失败,请稍后重试');
+ } finally {
+ this.rejectLoading = false;
+ }
+ },
+ async submitProcessing() {
+ if (this.currentDetail.status !== 3) {
+ this.$message.warning('只有处理中状态的工单可以提交处理详情');
+ return;
+ }
+
+ try {
+ const data = {
+ id: this.currentDetail.id, // 当前工单 ID
+ status: this.currentDetail.status, // 当前工单状态
+ processing_details: this.currentDetail.processingDetail, // 事件处理详情
+ };
+
+ // 如果有图片,添加 file 参数
+ const file = this.currentDetail.photos?.[0]?.raw || null;
+
+ const response = await flowEvent(data, file);
+ if (response.data.code === 0) {
+ this.$message.success('处理详情提交成功');
+ this.detailVisible = false;
+ this.fetchTableData();
+ } else {
+ throw new Error(response.data.msg || '提交失败');
+ }
+ } catch (error) {
+ console.error('处理详情提交失败:', error);
+ this.$message.error(error.message || '提交失败,请稍后重试');
+ }
+ },
+ markAsCompleted() {
+ this.$message.success('工单已标记为完成');
+ },
+ async completeTicket() {
+ if (this.completeLoading) return;
+ this.completeLoading = true;
+ try {
+ if (!this.currentDetail.processingDetail) {
+ 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,
+ status: this.currentDetail.status,
+ processingDetails: this.currentDetail.processingDetail,
+ eventNum: this.currentDetail.orderNumber,
+ };
+
+ // 如果有上传的图片,添加到请求中
+ const file = this.currentDetail.photos?.[0]?.raw || null;
+
+ const response = await flowEvent(data, file);
+
+ if (response.data.code === 0) {
+ this.$message.success('工单已完成');
+ this.detailVisible = false;
+ this.fetchTableData(); // 刷新列表数据
+ } else {
+ throw new Error(response.data.msg || '操作失败');
+ }
+ } catch (error) {
+ console.error('完成工单失败:', error);
+ this.$message.error(error.message || '操作失败,请稍后重试');
+ } finally {
+ this.completeLoading = false;
+ }
+ },
+ async approveAndDispatch() {
+ if (this.dispatchLoading) return;
+
+ // 添加必填项检查
+ if (!this.currentDetail.orderName || !this.currentDetail.orderName.trim()) {
+ this.$message.warning('请填写工单名称');
+ return;
+ }
+ if (!this.currentDetail.aiType) {
+ this.$message.warning('请选择关联算法');
+ return;
+ }
+ if (!this.currentDetail.content || !this.currentDetail.content.trim()) {
+ this.$message.warning('请填写工单内容');
+ return;
+ }
+
+ // 通过验证后,打开派发对话框
+ this.dispatchDialogVisible = true;
+
+ console.log('受理', this.currentDetail);
+ },
+ hasProcessingBtnPermission() {
+ // undefined 或 false 都返回 false,只有 true 返回 true
+ // console.log('权限检查:', this.permission.tickets_processing_btn)
+ return this.permission && this.permission.tickets_processing_btn === true;
+ },
+ hasProcessedAndOverBtnPermission() {
+ // undefined 或 false 都返回 false,只有 true 返回 true
+ //console.log('权限检查:', this.permission)
+ return this.permission && this.permission.tickets_view_processedAndOver === true;
+ },
+ hasReviewBtnPermission() {
+ // undefined 或 false 都返回 false,只有 true 返回 true
+ // console.log('权限检查:', this.permission)
+ return this.permission && this.permission.tickets_review_btn === true;
+ },
+
+ async submitDispatch() {
+ if (this.dispatchLoading) return;
+ if (!this.currentDetail.orderName || !this.currentDetail.orderName.trim()) {
+ this.$message.warning('请填写工单名称');
+ return;
+ }
+ if (!this.currentDetail.type) {
+ this.$message.warning('请选择工单类型');
+ return;
+ }
+ if (!this.currentDetail.content || !this.currentDetail.content.trim()) {
+ this.$message.warning('请填写工单内容');
+ return;
+ }
+ if (!this.dispatchForm.department) {
+ this.$message.warning('请选择部门');
+ return;
+ }
+ if (!this.dispatchForm.handler) {
+ this.$message.warning('请选择处理人');
+ return;
+ }
+ this.dispatchLoading = true;
+ console.log('派发成功', this.currentDetail);
+ const isValue = this.algorithms.some(item => item.value === this.currentDetail.aiType);
+ let resultValue;
+
+ if (isValue) {
+ // 如果已经是value,直接使用
+ resultValue = this.currentDetail.aiType;
+ } else {
+ // 否则查找对应的value
+ const matched = this.algorithms.find(
+ item => item.dict_value === this.currentDetail.aiType ||
+ item.label === this.currentDetail.aiType
+ );
+ resultValue = matched ? matched.value : null;
+ }
+
+// console.log('resultValue',resultValue);
+ this.$refs.dispatchForm.validate(async valid => {
+ if (valid) {
+ try {
+ const data = {
+ id: this.currentDetail.id,
+ status: this.currentDetail.status,
+ isPass: 0, // 0 表示通过
+ eventName: this.currentDetail.orderName, // 工单名称
+ eventNum: this.currentDetail.orderNumber,
+ workOrderTypeDictKey: this.currentDetail.type, // 直接使用原始的 dict_key
+ content: this.currentDetail.content, // 使用 content 替代原来的 remark
+ createDept: this.dispatchForm.department, // 派发部门 ID
+ updateUser: this.dispatchForm.handler, // 处理人 ID
+ aiType:resultValue,
+ };
+ console.log('派发', data);
+
+ const file = this.currentDetail.file || null; // 如果没有文件,则为 null
+
+ const response = await flowEvent(data, file);
+ if (response.data.code === 0) {
+ this.$message.success('工单已成功派发');
+ this.dispatchDialogVisible = false;
+ this.detailVisible = false;
+ this.fetchTableData();
+ } else {
+ throw new Error(response.data.msg || '派发失败');
+ }
+ } catch (error) {
+ console.error('派发失败:', error);
+ this.$message.error(error.message || '派发失败,请稍后重试');
+ } finally {
+ this.dispatchLoading = false;
+ }
+ } else {
+ this.dispatchLoading = false;
+ }
+ });
+ },
+ async finalizeTicket() {
+ if (this.finalizeLoading) return;
+ this.finalizeLoading = true;
+ try {
+ // 检查是否上传了图片
+ if (!this.currentDetail.photos || !this.currentDetail.photos.length) {
+ this.$message.warning('请上传事件处理照片,或飞行任务结束核验工单是否完结');
+ return;
+ }
+
+ const data = {
+ id: this.currentDetail.id,
+ status: this.currentDetail.status,
+ eventNum: this.currentDetail.orderNumber,
+ };
+
+ const file = this.currentDetail.photos[0].raw;
+
+ const response = await flowEvent(data, file);
+
+ if (response.data.code === 0) {
+ this.$message.success('工单已完结');
+ this.detailVisible = false;
+ this.fetchTableData();
+ } else {
+ throw new Error(response.data.msg || '操作失败');
+ }
+ } catch (error) {
+ console.error('完结工单失败:', error);
+ this.$message.error(error.message || '操作失败,请稍后重试');
+ } finally {
+ this.finalizeLoading = false;
+ }
+ },
+
+ // 添加编辑方法
+ handleEdit(row) {
+ this.createoredit = 2;
+ console.log('编辑原始数据:', row);
+
+ // 尝试从row.dept_id或通过部门名称查找对应的部门ID
+ let deptId = row.dept_id; // 优先使用原始数据中的dept_id
+
+ // 如果没有dept_id,则通过部门名称查找
+ if (!deptId) {
+ // 输出所有可用的部门数据用于调试
+ console.log('可用部门列表:', this.departments);
+ console.log('当前部门名称:', row.department);
+
+ const deptInfo = this.departments.find(
+ dept => dept.label && dept.label.trim() === row.department.trim()
+ );
+ deptId = deptInfo?.value;
+ }
+
+ // 获取工单类型值 - 从types中找到匹配的值
+ const typeValue =
+ this.types.find(t => t.value === row.type)?.value || row.work_order_type_dict_key;
+ // 获取处理人ID - 使用userNameToIdMap映射
+ const handlerId = this.userNameToIdMap[row.handler] || row.handler;
+
+ console.log('数据映射:', {
+ 部门名称: row.department,
+ 找到的部门ID: deptId,
+ 原始处理人: row.handler,
+ 处理人ID: handlerId,
+ 原始工单类型: row.type,
+ 映射后类型值: typeValue,
+ });
+
+ // 修改算法数组的处理逻辑
+ let algorithmArr = [];
+ if (Array.isArray(row.aiType)) {
+ algorithmArr = row.aiType;
+ } else if (typeof row.aiType === 'string' && row.aiType) {
+ // 首先尝试将字符串按照逗号或顿号分割
+ algorithmArr = row.aiType.split(/[,、]/).map(item => item.trim());
+ }
+
+ // 确保算法值与选项匹配
+ algorithmArr = algorithmArr
+ .map(item => {
+ // 如果是字符串值,尝试找到对应的 dict_key
+ if (typeof item === 'string') {
+ const matchedAlgorithm = this.algorithms.find(
+ algo => algo.dict_value === item || algo.dict_key === item
+ );
+ return matchedAlgorithm ? matchedAlgorithm.dict_key : item;
+ }
+ return item;
+ })
+ .filter(Boolean); // 过滤掉无效值
+
+ console.log('算法处理:', {
+ 原始值: row.aiType,
+ 分割后: algorithmArr,
+ });
+
+ this.form = {
+ id: row.id,
+ name: row.orderName,
+ type: typeValue,
+ department: deptId,
+ handler: handlerId,
+ algorithm: algorithmArr,
+ location:
+ Array.isArray(row.location) && row.location.length >= 2
+ ? [Number(row.location[0]), Number(row.location[1])]
+ : [],
+ address: row.address || '',
+ content: row.content,
+ photos: [],
+ };
+
+ 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) {
+ this.mapParams.center = [Number(this.form.location[0]), Number(this.form.location[1])];
+ } else {
+ this.mapParams.center = [...this.inputMapShowDefaultCenter];
+ }
+ // 如果有图片,添加到表单中
+ if (row.photo_url) {
+ // 创建一个带有必要信息的文件对象
+ this.form.photos = [
+ {
+ name: 'existing-photo.jpg', // 添加默认扩展名
+ url: row.photo_url, // 用于预览的URL
+ status: 'success', // 标记为已上传成功
+ raw: null, // 初始化为null
+ existingUrl: row.photo_url, // 保存原始URL,用于区分是否为已存在的图片
+ },
+ ];
+ }
+
+ // 调试输出
+ console.log('编辑表单数据:', {
+ 原始算法值: row.aiType,
+ 处理后算法值: algorithmArr,
+ 表单数据: this.form,
+ });
+
+ this.dialogVisible = true;
+ },
+
+ // 添加删除方法
+ handleDelete(row) {
+ this.$confirm('确认删除该工单?', '提示', {
+ confirmButtonText: '确定',
+ cancelButtonText: '取消',
+ type: 'warning',
+ })
+ .then(async () => {
+ try {
+ const response = await flowEvent({
+ id: row.id,
+ status: 0,
+ isDelete: 1,
+ });
+
+ if (response.data.code === 0) {
+ this.$message.success('删除成功');
+ this.fetchTableData();
+ } else {
+ throw new Error(response.data.msg || '删除失败');
+ }
+ } catch (error) {
+ console.error('删除失败:', error);
+ this.$message.error(error.message || '删除失败,请稍后重试');
+ }
+ })
+ .catch(() => {});
+ },
+
+ // 添加选择变化处理方法
+ handleSelectionChange(selection) {
+ this.selections = selection;
+ console.log('已选择的行:', selection);
+ },
+
+ // 添加全选方法
+ handleSelectAll(val) {
+ this.$refs.avueCrud.toggleSelection(val);
+ },
+
+ // 添加单行选择方法
+ handleSelect(selection, row) {
+ console.log('选中行变化:', selection, row);
+ },
+
+ // 如果需要手动选中某些行
+ setSelection(rows) {
+ this.$nextTick(() => {
+ rows.forEach(row => {
+ this.$refs.avueCrud.toggleSelection(row, true);
+ });
+ });
+ },
+
+ // 清空选择
+ clearSelection() {
+ this.$refs.avueCrud.clearSelection();
+ },
+
+ // 打开审核对话框
+ openReviewDialog() {
+ if (this.selections.length === 0) {
+ this.$message.warning('请先选择要审核的工单');
+ return;
+ }
+ this.currentImageIndex = 1;
+ this.updateCurrentReviewImage();
+ this.reviewDialogVisible = true;
+ },
+
+ // 更新当前审核图片
+ updateCurrentReviewImage() {
+ // 修正索引范围
+ if (!this.selections || this.selections.length === 0) {
+ this.currentReviewImage = '';
+ this.currentImageIndex = 1;
+ return;
+ }
+ // 如果当前索引超出范围,自动回退到最后一张
+ if (this.currentImageIndex > this.selections.length) {
+ this.currentImageIndex = this.selections.length;
+ }
+ if (this.currentImageIndex < 1) {
+ this.currentImageIndex = 1;
+ }
+ const index = this.currentImageIndex - 1;
+ if (index >= 0 && index < this.selections.length) {
+ const currentItem = this.selections[index];
+ this.currentReviewImage = currentItem.photo_url || '';
+ } else {
+ this.currentReviewImage = '';
+ }
+ },
+
+ // 处理图片分页变化
+ handleImagePageChange(page) {
+ if (page > 0 && page <= this.selections.length) {
+ this.currentImageIndex = page;
+ this.updateCurrentReviewImage();
+ }
+ },
+
+ // 批量审核通过
+ async handleBatchApprove() {
+ try {
+ if (this.selections.length === 0) {
+ this.$message.warning('没有选中的工单');
+ return;
+ }
+
+ const currentItem = this.selections[this.currentImageIndex - 1];
+ if (!currentItem) {
+ this.$message.warning('当前工单数据无效');
+ return;
+ }
+
+ await this.$confirm('确认审核通过当前工单?', '提示', {
+ confirmButtonText: '确定',
+ cancelButtonText: '取消',
+ type: 'warning',
+ });
+
+ const data = {
+ id: currentItem.id,
+ status: currentItem.status,
+ isPass: 0,
+ eventNum: currentItem.orderNumber,
+ };
+ console.log('删除前:', this.selections);
+ const response = await flowEvent(data);
+ if (response.data.code === 0) {
+ this.$message.success('工单审核通过');
+
+ // 创建新的数组而不是修改原数组
+ const newSelections = [...this.selections];
+ newSelections.splice(this.currentImageIndex - 1, 1);
+
+ // 修正索引并更新图片
+ if (newSelections.length > 0) {
+ if (this.currentImageIndex > this.selections.length) {
+ this.currentImageIndex = this.selections.length;
+ }
+ this.updateCurrentReviewImage();
+ } else {
+ this.reviewDialogVisible = false;
+ this.currentImageIndex = 1;
+ this.currentReviewImage = '';
+ this.fetchTableData();
+ }
+
+ // 刷新表格数据
+ // this.fetchTableData();
+ this.selections = newSelections;
+ } else {
+ throw new Error(response.data.msg || '审核失败');
+ }
+ } catch (error) {
+ if (error === 'cancel') return;
+ this.$message.error(error.message || '审核失败,请稍后重试');
+ }
+ },
+
+ cancleBatchReject() {
+ this.reviewDialogVisible = false;
+ this.selections = [];
+ this.currentImageIndex = 1;
+ this.currentReviewImage = '';
+ this.fetchTableData();
+ },
+ // 批量审核不通过
+ async handleBatchReject() {
+ try {
+ if (this.selections.length === 0) {
+ this.$message.warning('没有选中的工单');
+ return;
+ }
+
+ const currentItem = this.selections[this.currentImageIndex - 1];
+ if (!currentItem) {
+ this.$message.warning('当前工单数据无效');
+ return;
+ }
+
+ await this.$confirm('确认该工单审核不通过?', '提示', {
+ confirmButtonText: '确定',
+ cancelButtonText: '取消',
+ type: 'warning',
+ });
+
+ const data = {
+ id: currentItem.id,
+ status: currentItem.status,
+ isPass: 1,
+ eventNum: currentItem.orderNumber,
+ eventName: currentItem.orderName,
+ };
+
+ const response = await flowEvent(data);
+ if (response.data.code === 0) {
+ this.$message.success('工单审核不通过');
+
+ // 创建新的数组而不是修改原数组
+ const newSelections = [...this.selections];
+ newSelections.splice(this.currentImageIndex - 1, 1);
+ this.selections = newSelections;
+
+ // 修正索引并更新图片
+ if (this.selections.length > 0) {
+ if (this.currentImageIndex > this.selections.length) {
+ this.currentImageIndex = this.selections.length;
+ }
+ this.updateCurrentReviewImage();
+ } else {
+ this.reviewDialogVisible = false;
+ this.currentImageIndex = 1;
+ this.currentReviewImage = '';
+ this.fetchTableData();
+ }
+
+ // 刷新表格数据
+ // this.fetchTableData();
+ } else {
+ throw new Error(response.data.msg || '驳回失败');
+ }
+ } catch (error) {
+ if (error === 'cancel') return;
+ this.$message.error(error.message || '驳回失败,请稍后重试');
+ }
+ },
+
+ // 获取所有图片列表用于预览
+ getImageList() {
+ return this.selections.map(item => this.getPreviewUrl(item.photo_url)).filter(url => url); // 过滤掉空值
+ },
+
+ // 处理图片点击
+ handleImageClick() {
+ // 图片点击事件由 el-image 的预览功能处理
+ },
+
+ // 处理上一张图片
+ handlePrevImage() {
+ if (this.currentImageIndex > 1) {
+ this.currentImageIndex--;
+ this.updateCurrentReviewImage();
+ }
+ },
+
+ // 处理下一张图片
+ handleNextImage() {
+ if (this.currentImageIndex < this.selections.length) {
+ this.currentImageIndex++;
+ this.updateCurrentReviewImage();
+ }
+ },
+
+ /**
+ * 获取缩略图地址
+ * @param {string} url 原图地址
+ * @returns {string} 缩略图地址
+ */
+ getThumbUrl(url) {
+ if (!url) return '';
+ const lastDot = url.lastIndexOf('.');
+ if (lastDot === -1) return url;
+ return url.slice(0, lastDot) + '_small' + url.slice(lastDot);
+ },
+
+ // 添加新方法:获取预览图地址
+ getPreviewUrl(url) {
+ if (!url) return '';
+ const lastDot = url.lastIndexOf('.');
+ 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 (Array.isArray(data.location) && data.location.length > 0) {
+ 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(that.reCheckData.id).then(res => {
+ that.reCheckDialog = false;
+ that.page.currentPage = 1;
+ that.fetchTableData();
+ that.fetchTabCounts();
+ });
+ } else {
+ const loading = ElLoading.service({
+ lock: true,
+ text: '复核任务创建中……',
+ background: 'rgba(0, 0, 0, 0.7)',
+ });
+
+ function closeConfirm() {
+ that.reCheckDialog = false;
+ that.page.currentPage = 1;
+ that.fetchTableData();
+ that.fetchTabCounts();
+ loading.close();
+ }
+
+ // 获取时间的接口
+ getCreateEventJob(that.reCheckData.id)
+ .then(res => {
+ ElMessageBox.confirm(`预计复核执行时间为${res.data.data}`, '提示', {
+ confirmButtonText: '确定',
+ showCancelButton: false, // 关键配置
+ closeOnClickModal: false,
+ closeOnPressEscape: false,
+ type: 'warning',
+ })
+ .then(() => {
+ closeConfirm();
+ })
+ .catch(() => {
+ closeConfirm();
+ });
+ })
+ .catch(() => {
+ closeConfirm();
+ });
+ }
+ },
+ // 导出工单报表
+ exportTheTick(row){
+ const params = {
+ num:row.orderNumber
+ }
+ exportTheTicket(params).then(res=>{
+ const elink = document.createElement('a')
+ elink.download = row.orderName + '.docx'
+ elink.style.display = 'none'
+ const blob = new Blob([res.data])
+ elink.href = URL.createObjectURL(blob)
+ document.body.appendChild(elink)
+ elink.click()
+ document.body.removeChild(elink)
+ })
+ this.$message.success('数据导出成功');
+ }
+ },
+ activated() {
+ this.handleReset();
+ },
+};
+</script>
+<style lang="scss">
+.custom-dialog {
+ max-height: 96vh;
+
+ .el-dialog__body {
+ border-top: 0.1rem solid #f0f0f0;
+ }
+}
+
+.custom-qrcode-popover {
+ min-width: 160px !important;
+ min-height: 140px !important;
+ position: relative;
+
+ .qrcode-content {
+ margin-top: 8px;
+
+ .close-btn {
+ position: absolute;
+ top: 2px;
+ right: 2px;
+ width: 16px;
+ height: 16px;
+ line-height: 16px;
+ text-align: center;
+ cursor: pointer;
+ color: #999;
+ font-size: 16px;
+ z-index: 10;
+ }
+ }
+}
+</style>
+<style lang="scss" scoped>
+::v-deep(.el-tabs) {
+ height: 100%;
+ display: flex;
+ flex-direction: column;
+
+ .el-tabs__header {
+ order: 1;
+ }
+
+ .el-tabs__content {
+ order: 2;
+ }
+
+ .el-tabs__content {
+ height: 0;
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+
+ .el-tab-pane {
+ height: 0;
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ }
+ }
+}
+
+.filter-bar {
+ display: flex;
+ align-items: center;
+ margin-bottom: 15px;
+ flex-wrap: wrap;
+ gap: 8px; // 使用 gap 统一设置间距
+
+ .filter-item {
+ width: 280px; // 减小宽度
+ }
+
+ .date-picker {
+ // width: 240px; // 日期选择器宽度适当调整
+ }
+
+ .el-button {
+ margin-left: 0; // 覆盖 element-ui 默认的按钮间距
+ }
+}
+
+.uploadImg {
+ margin-bottom: 32px;
+}
+
+.tableCss {
+ width: 100%;
+ margin-bottom: 10px;
+}
+
+.step-timer {
+ position: absolute;
+ right: 80%;
+ top: 50%;
+ transform: translateY(-50%);
+ width: 100px;
+ margin-left: 4px;
+ color: #666;
+ font-size: 12px;
+}
+
+.event-total-time {
+ text-align: center;
+ color: #666;
+ font-size: 15px;
+ margin-bottom: 12px;
+}
+
+.action-bar {
+ margin-bottom: 16px;
+}
+
+.el-dialog {
+ .el-form-item {
+ margin-bottom: 20px;
+ }
+}
+
+.el-upload {
+ width: 100%;
+ display: flex;
+ flex-wrap: wrap;
+
+ :deep(.el-upload-list__item) {
+ transition: all 0.3s ease;
+ }
+
+ :deep(.el-upload-list__item:hover) {
+ background-color: #f5f7fa;
+ }
+}
+
+.el-upload__tip {
+ font-size: 12px;
+ color: #909399;
+ margin-top: 12px;
+}
+
+.create-ticket-form {
+ padding: 20px 10px;
+
+ .form-section {
+ background-color: #fff;
+ border-radius: 4px;
+
+ .el-row {
+ margin-bottom: 16px;
+
+ &:last-child {
+ margin-bottom: 0;
+ }
+ }
+ }
+
+ .location-wrapper {
+ width: 100%; // 修改为100%以适应父容器
+ height: auto; // 修改为auto以自适应内容
+
+ .map-button {
+ width: 100%; // 让按钮填满容器宽度
+ height: 36px; // 与其他输入框保持一致的高度
+ display: flex;
+ align-items: center;
+ justify-content: center;
+
+ i {
+ margin-right: 4px;
+ }
+ }
+
+ .location-text {
+ margin-top: 8px;
+ padding: 8px 12px;
+ background-color: #f5f7fa;
+ border-radius: 4px;
+ color: #606266;
+ font-size: 13px;
+ line-height: 1.4;
+ }
+ }
+
+ .upload-wrapper {
+ .uploader {
+ :deep(.el-upload--picture-card) {
+ width: 120px;
+ height: 100px;
+ line-height: 128px;
+ }
+
+ :deep(.el-upload-list__item) {
+ width: 120px;
+ height: 120px;
+ }
+ }
+
+ .upload-tip {
+ font-size: 12px;
+ color: #909399;
+ line-height: 1.4;
+ margin-top: 8px;
+ }
+ }
+
+ .el-form-item {
+ margin-bottom: 18px;
+
+ &:last-child {
+ margin-bottom: 0;
+ }
+ }
+
+ :deep(.el-form-item__label) {
+ font-weight: 500;
+ color: #606266;
+ }
+
+ // :deep(.el-input__inner) {
+ // height: 36px;
+ // line-height: 36px;
+ // }
+
+ :deep(.el-textarea__inner) {
+ padding: 8px 12px;
+ }
+
+ // :deep(.el-input__inner),
+ // :deep(.el-select),
+ // :deep(.el-select .el-input) {
+ // width: 100%; // 确保所有输入框和选择框宽度一致
+ // }
+
+ .full-width {
+ width: 100%;
+ }
+}
+
+:deep(.el-dialog__body) {
+ border-top: 0.1rem solid #f0f0f0;
+}
+
+.dialog-footer-new {
+ text-align: right;
+ padding-top: 16px;
+ border-top: 1px solid #ebeef5;
+
+ .el-button + .el-button {
+ margin-left: 12px;
+ }
+
+ .el-button {
+ padding: 9px 20px;
+
+ &:last-child {
+ margin-left: 12px;
+ }
+ }
+}
+
+.dialog-footer1-new {
+ position: sticky;
+ bottom: 28px;
+ left: 0;
+ right: 0;
+ background: white;
+ z-index: 10;
+ padding: 16px 20px;
+ border-top: 1px solid #ebeef5;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+
+ .el-button + .el-button {
+ margin-left: 12px;
+ }
+
+ .btngroups {
+ margin-left: 12px;
+ }
+
+ .el-button {
+ padding: 9px 20px;
+
+ &:last-child {
+ margin-left: 12px;
+ margin-right: 12px;
+ }
+ }
+}
+
+.map-container {
+ width: 100%;
+ height: 400px;
+ margin-bottom: 15px;
+ border: 1px solid #dcdfe6;
+ border-radius: 4px;
+ overflow: hidden;
+
+ :deep(.el-input-map) {
+ height: 100%;
+ }
+}
+
+.location-info {
+ margin-top: 10px;
+ padding: 10px;
+ background-color: #f5f7fa;
+ border-radius: 4px;
+
+ p {
+ margin: 5px 0;
+ color: #606266;
+ font-size: 14px;
+ }
+}
+
+.map-select {
+ display: flex;
+ align-items: flex-start;
+ gap: 15px;
+
+ .selected-location {
+ flex: 1;
+ padding: 5px 10px;
+ background-color: #f5f7fa;
+ border-radius: 4px;
+
+ p {
+ margin: 5px 0;
+ color: #606266;
+ font-size: 14px;
+ }
+ }
+}
+
+.preview-image {
+ border-radius: 4px;
+ overflow: hidden;
+ background-color: #f5f7fa;
+}
+
+.image-placeholder,
+.image-error,
+.no-media {
+ height: 200px;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ color: #909399;
+ background-color: #f5f7fa;
+ border-radius: 4px;
+
+ i {
+ font-size: 32px;
+ margin-bottom: 8px;
+ }
+}
+
+.no-media {
+ border: 1px dashed #d9d9d9;
+}
+
+.detail-container {
+ padding: 0 20px;
+
+ .detail-top-title {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+
+ .event-orderNumber {
+ margin-right: 10px;
+ }
+ }
+}
+
+.status-flow {
+ margin-bottom: 20px;
+
+ .custom-steps {
+ .el-step__description {
+ position: relative;
+ margin: 0;
+ padding: 0;
+ }
+
+ // 添加发起任务步骤的特殊样式
+ .init-step-info {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ text-align: center;
+ width: 130px;
+
+ .creator-name {
+ font-size: 14px;
+ font-weight: bold;
+ color: #303133;
+ margin-bottom: 4px;
+ }
+
+ .create-time {
+ font-size: 10px;
+ color: #909399;
+ }
+ }
+
+ // 保持其他步骤的原有样式
+ .step-info {
+ display: flex;
+ margin: 0;
+ padding: 0;
+ justify-content: space-between;
+ align-items: center;
+ width: 200px;
+
+ .process-time {
+ font-size: 10px;
+ color: #909399;
+ text-align: left;
+ margin-left: -50%;
+ padding-left: 0%;
+ flex: 1;
+ }
+
+ .handler-name {
+ font-size: 14px;
+ font-weight: bold;
+ color: #303133;
+ text-align: left;
+ flex: 1;
+ }
+ }
+ }
+}
+
+.basic-info {
+ margin-bottom: 20px;
+}
+
+.media-section {
+ // margin-bottom: 20px;
+
+ .el-row {
+ display: flex;
+ align-items: center;
+ }
+}
+
+.leftBtn {
+ width: 70px;
+ height: 32px;
+ background-color: #999;
+ border-radius: 5px;
+ text-align: center;
+ line-height: 32px;
+ color: #fff;
+ cursor: pointer;
+ opacity: 0.8;
+}
+
+.disableds {
+ background: #999 !important;
+ cursor: not-allowed !important;
+ pointer-events: none;
+ opacity: 0.3 !important;
+}
+
+.PopUpTableScrolls {
+ max-height: 600px;
+ overflow-y: scroll;
+ overflow-x: hidden;
+}
+
+.media-box {
+ width: 100%;
+ border: 1px solid #dcdfe6;
+ border-radius: 4px;
+ padding: 10px;
+ background: #fff;
+ box-sizing: border-box;
+
+ .media-title {
+ font-weight: bold;
+ margin-bottom: 5px;
+ display: flex;
+ align-items: center;
+
+ .QRCodeImg {
+ width: 18px;
+ height: 18px;
+ cursor: pointer;
+ padding-bottom: 1px;
+ }
+ }
+
+ .media-content {
+ position: relative;
+ height: 500px;
+
+ :deep(.el-image) {
+ width: 100% !important;
+ max-width: 100%;
+ max-height: 100%;
+
+ .el-image__inner {
+ width: 100%;
+ height: 100%;
+ object-fit: cover !important;
+ }
+ }
+ }
+}
+
+.image-placeholder,
+.image-error,
+.no-media {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ height: 100%;
+ color: #909399;
+ background-color: #f5f7fa;
+ border-radius: 4px;
+}
+
+.image-placeholder i,
+.image-error i {
+ font-size: 32px;
+ margin-bottom: 8px;
+}
+
+.no-media {
+ border: 1px dashed #d9d9d9;
+}
+
+.info-table {
+ margin-bottom: 20px;
+}
+
+.info-item {
+ display: flex;
+ margin-bottom: 10px;
+}
+
+.info-label {
+ font-weight: bold;
+ width: 120px;
+ color: #606266;
+}
+
+.info-value {
+ flex: 1;
+ color: #303133;
+ word-break: break-word;
+}
+
+.readonly-processing-detail {
+ background-color: #f5f7fa;
+ padding: 12px;
+ border-radius: 4px;
+ min-height: 30px;
+ color: #606266;
+ line-height: 1.5;
+ display: block;
+ text-align: left;
+ margin-bottom: 5px;
+
+ &:first-child {
+ font-weight: bold;
+ margin-bottom: 4px;
+ }
+}
+
+// 添加删除按钮样式
+.danger-button {
+ color: #f56c6c;
+}
+
+.danger-button:hover {
+ color: #f78989;
+}
+
+.custom-steps-container {
+ width: 100%;
+ margin: 10px 0;
+}
+
+.steps-titles {
+ display: flex;
+ justify-content: space-between;
+ margin-bottom: 14px;
+ position: relative;
+}
+
+.step-title {
+ text-align: center;
+ flex: 1;
+ font-size: 14px;
+ color: #999;
+ position: relative;
+ padding-bottom: 5px;
+}
+
+.step-title.active {
+ color: #409eff;
+ font-weight: bold;
+}
+
+.event-title-center {
+ text-align: center;
+ font-size: 20px;
+ font-weight: bold;
+ margin-bottom: 5px;
+ color: #333;
+}
+
+.custom-steps {
+ margin-top: -20px;
+
+ :deep(.el-step__description) {
+ margin-top: 8px;
+ padding: 0 20px;
+ }
+}
+
+.step-description {
+ font-size: 14px;
+ color: #666;
+ line-height: 1.5;
+ display: block;
+ text-align: center;
+
+ &:first-child {
+ font-weight: bold;
+ margin-bottom: 4px;
+ }
+}
+
+// 覆盖其他相关样式
+.status-flow {
+ .custom-steps {
+ .el-step__description {
+ position: relative;
+ margin: 0;
+ padding: 0;
+ }
+
+ // 移除之前的样式
+ .init-step-info,
+ .step-info {
+ display: block;
+ width: auto;
+ text-align: center;
+ }
+ }
+}
+
+// 添加新的样式
+.review-dialog {
+ :deep(.el-dialog__body) {
+ padding: 0;
+ background-color: #f5f7fa;
+ border-top: 0.1rem solid #f0f0f0;
+ }
+}
+
+.re-check-dialog {
+ :deep(.el-dialog__body) {
+ padding: 0;
+ background-color: #f5f7fa;
+ border-top: 0.1rem solid #f0f0f0;
+ }
+}
+
+.review-container {
+ position: relative;
+
+ .review-image-wrapper {
+ position: relative;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ height: 600px;
+ background-color: #f5f7fa;
+ }
+
+ .review-image-container {
+ width: 100%;
+ height: 100%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+
+ :deep(.el-image) {
+ width: 100%;
+ height: 100%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+
+ .el-image__inner {
+ max-height: 100%;
+ max-width: 100%;
+ object-fit: contain;
+ }
+ }
+ }
+
+ .arrow-button {
+ position: absolute;
+ top: 50%;
+ transform: translateY(-50%);
+ width: 44px;
+ height: 44px;
+ background: rgba(0, 0, 0, 0.3);
+ border-radius: 50%;
+ border: none;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ cursor: pointer;
+ transition: all 0.3s;
+ z-index: 100;
+
+ &:hover {
+ background: rgba(0, 0, 0, 0.6);
+ }
+
+ i {
+ color: #fff;
+ font-size: 24px;
+ }
+
+ &.left {
+ left: 20px;
+ color: #ffffff;
+ }
+
+ &.right {
+ right: 20px;
+ color: #ffffff;
+ }
+ }
+
+ .preview-image {
+ max-width: 100%;
+ max-height: 100%;
+ }
+
+ .review-pagination {
+ padding: 16px 0;
+ display: flex;
+ justify-content: center;
+ background-color: #fff;
+
+ :deep(.el-pagination) {
+ .btn-prev,
+ .btn-next {
+ display: none;
+ }
+
+ .el-pager {
+ .number {
+ margin: 0 4px;
+ }
+ }
+ }
+ }
+
+ .image-error {
+ height: 100%;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ color: #909399;
+
+ i {
+ font-size: 48px;
+ margin-bottom: 16px;
+ }
+
+ span {
+ font-size: 16px;
+ }
+ }
+}
+
+/* 新建工单和处理工单的上传组件样式 */
+.create-upload,
+.detail-upload {
+ :deep(.el-upload--picture-card) {
+ width: 120px;
+ height: 100px;
+ line-height: 100px;
+ }
+
+ /* 隐藏额外的上传按钮 */
+ :deep(.el-upload.el-upload--picture-card) {
+ display: none;
+ }
+
+ /* 当没有图片时显示上传按钮 */
+ :deep(.el-upload.el-upload--picture-card:first-child) {
+ display: flex;
+ }
+
+ /* 上传组件的预览图片样式 */
+ :deep(.el-upload-list--picture-card .el-upload-list__item) {
+ width: 120px;
+ height: 100px;
+ }
+}
+
+/* 原有图片预览的样式 */
+.el-image-viewer__wrapper {
+ :deep(.el-image-viewer__img) {
+ max-width: 100%;
+ max-height: 100%;
+ object-fit: contain;
+ }
+}
+
+/* 必填项样式 */
+.required-label {
+ position: relative;
+ display: inline-block;
+
+ .required-star {
+ color: #f56c6c;
+ margin-right: 4px;
+ }
+}
+
+/* 必填输入框样式 */
+.required-input {
+ width: 100%;
+
+ :deep(.el-input__inner),
+ :deep(.el-textarea__inner) {
+ &:focus {
+ border-color: #409eff;
+ }
+ }
+}
+</style>
diff --git a/src/views/tickets/ticket.vue b/src/views/tickets/ticket.vue
index e418862..fde578b 100644
--- a/src/views/tickets/ticket.vue
+++ b/src/views/tickets/ticket.vue
@@ -4,12 +4,11 @@
<el-tab-pane
v-for="tab in filteredTabs"
:key="tab.name"
- :label="`${tab.label} (${tab.count})`"
+ :label="tab.name === 'myTickets' ? tab.label : `${tab.label} (${tab.count})`"
:name="tab.name"
>
<basic-main-content>
- <!-- 查询条件筛选栏 -->
- <!-- 查询条件筛选栏 -->
+ <!-- 查询条件筛选栏 -->
<div class="ztzf-form-search">
<el-form :model="filters" inline>
<el-row :gutter="24">
@@ -25,37 +24,12 @@
/>
</el-form-item>
</el-col>
-
- <!-- <el-select
- v-model="filters.department"
- placeholder="请选择所属部门"
- class="filter-item"
- clearable
- >
- <el-option
- v-for="item in departments"
- :key="item.value"
- :label="item.label"
- :value="item.value"
- />
- </el-select> -->
- <!-- <el-select
- v-model="filters.type"
- placeholder="请选择工单类型"
- class="filter-item"
- clearable
- @change="handleSearch"
- >
- <el-option
- v-for="item in types"
- :key="item.value"
- :label="item.label"
- :value="item.value"
- />
- </el-select> -->
<el-col :span="5">
<el-form-item label="选择日期:">
<el-date-picker
+ clearable
+ @clear="handleKeyWords"
+ @change="handleSearch"
v-model="filters.dateRange"
type="daterange"
class="filter-item"
@@ -70,6 +44,7 @@
<el-col :span="4">
<el-form-item label="选择状态:">
<el-select
+ :disabled="activeTab !== 'all'"
v-model="filters.status"
placeholder="请选择状态"
class="filter-item"
@@ -86,34 +61,6 @@
</el-form-item>
</el-col>
- <!-- <el-select
- v-model="filters.algorithm"
- placeholder="请选择关联算法"
- class="filter-item"
- clearable
- @change="handleSearch"
- >
- <el-option
- v-for="item in algorithms"
- :key="item.dict_key"
- :label="item.dict_value"
- :value="item.dict_key"
- />
- </el-select> -->
- <!-- <el-tree-select
- popper-class="custom-tree-select"
- :style="{ width: pxToRem(186) }"
- placeholder="请选择关联算法"
- v-model="dictKey"
- :data="dataList"
- :default-expanded-keys="[dictKey]"
- check-strictly
- node-key="id"
- :props="treePropsSF"
- @node-click="handleSFNodeClick"
- clearable
- @clear="handleClear"
- /> -->
<el-col :span="4">
<el-form-item label="选择算法:">
<el-tree-select
@@ -138,7 +85,7 @@
/>
</el-form-item>
</el-col>
- <el-col :span="4">
+ <el-col :span="4" v-if="activeTab === 'completed'">
<el-form-item label="复核状态:">
<el-select
v-model="filters.isReview"
@@ -166,746 +113,205 @@
</el-form>
</div>
<!-- 表格部分 -->
- <avue-crud
- class="ztzf-public-general-avue-crud"
- ref="avueCrud"
- v-model="tableData"
- :option="option"
- :data="tableData"
- v-model:page="page"
- @size-change="sizeChange"
- @current-change="handleCurrentChange"
- @refresh-change="refreshChange"
- :table-loading="loading"
- @selection-change="handleSelectionChange"
- :permission="permissionList"
- v-if="activeTab === tab.name"
- >
- <template #orderNumber="{ row }">
- <el-tooltip-copy :content="row.orderNumber" :showCopyText="true" textAlign="left">
- {{ row.orderNumber }}
- </el-tooltip-copy>
- </template>
- <template #orderName="{ row }">
- <el-tooltip-copy :content="row.orderName" :showCopyText="true" textAlign="left">
- {{ row.orderName }}
- </el-tooltip-copy>
- </template>
- <template #menu-left>
+ <div class="table-operations">
+ <div class="left-operations">
<el-button
v-if="(activeTab === 'all' || activeTab === 'myTickets') && permissionList.addBtn"
type="primary"
icon="el-icon-plus"
@click="handleAdd"
- >新建工单
+ >
+ 新建工单
</el-button>
<el-button
v-if="activeTab === 'pending' && permissionList.reviewBtn"
type="success"
icon="el-icon-check"
@click="openReviewDialog"
- >批量审核
+ >
+ 批量审核
</el-button>
-
+
<el-button
v-if="permissionList.exportBtn"
type="success"
plain
icon="el-icon-download"
@click="exportData"
- >导出
+ >
+ 导出
</el-button>
+ </div>
+ </div>
+ <div class="titketBox">
+ <div class="dataTable">
+ <el-table
+ class="ztzf-public-general-el-table custom-header"
+ ref="elTable"
+ :data="tableData"
+ v-loading="loading"
+ @selection-change="handleSelectionChange"
+ border
+ style="width: 100%"
+ v-if="activeTab === tab.name"
+ >
+ <el-table-column type="selection" width="55" v-if="permissionList.reviewBtn || permissionList.exportBtn" />
+ <el-table-column type="index" label="序号" width="60" />
+ <el-table-column prop="orderNumber" label="工单编号" width="170">
+ <template #default="{ row }">
+ <el-tooltip-copy :content="row.orderNumber" :showCopyText="true" textAlign="left">
+ {{ row.orderNumber }}
+ </el-tooltip-copy>
</template>
- <template #menu="{ row }">
+ </el-table-column>
+ <el-table-column prop="orderName" label="工单名称" width="150" show-overflow-tooltip>
+ <template #default="{ row }">
+ <el-tooltip-copy :content="row.orderName" :showCopyText="true" textAlign="left">
+ {{ row.orderName }}
+ </el-tooltip-copy>
+ </template>
+ </el-table-column>
+
+ <el-table-column prop="department" label="所属部门" show-overflow-tooltip />
+
+ <el-table-column prop="startTime" label="发起任务时间" width="160" show-overflow-tooltip />
+
+ <el-table-column prop="aiType" label="关联算法" show-overflow-tooltip />
+
+ <el-table-column prop="type" label="工单类型" width="110" show-overflow-tooltip>
+ <template #default="{ row }">
+ {{ getTypeLabel(row.type) }}
+ </template>
+ </el-table-column>
+
+ <el-table-column prop="content" label="工单内容" width="132" show-overflow-tooltip>
+ <template #default="{ row }">
+ {{ row.address || row.content }}
+ </template>
+ </el-table-column>
+
+ <el-table-column prop="creator" label="创建人" width="120" show-overflow-tooltip />
+
+ <el-table-column prop="handler" label="处理人" width="120" show-overflow-tooltip />
+
+ <el-table-column
+ v-if="['all', 'completed', 'myTickets'].includes(activeTab)"
+ label="复核状态"
+ prop="isReview"
+ width="90"
+ >
+ <template #default="{ row }">
+ <span>{{ showIsReviewText(row) }}</span>
+ </template>
+ </el-table-column>
+
+ <el-table-column label="工单状态" prop="status" width="90">
+ <template #default="{ row }">
+ <span :style="getStatusTagType(row.status) ? 'color:' + getStatusTagType(row.status) : ''">
+ {{ mapStatus(row.status) }}
+ </span>
+ </template>
+ </el-table-column>
+
+ <!-- 操作列 -->
+ <el-table-column label="操作" width="200" align="center">
+ <template #default="{ row }">
<template v-if="row.status === -1">
<el-button type="text" icon="el-icon-edit" @click="handleEdit(row)">编辑</el-button>
- <el-button
- type="text"
- icon="el-icon-delete"
- class="danger-button"
- @click="handleDelete(row)"
- >删除
+ <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>
+ <el-button type="text" icon="el-icon-view" @click="handleViewDetail(row)">详情</el-button>
</template>
-
- <template v-if="row.status === 4 && activeTab === 'completed' && !isProd">
- <el-button type="text" icon="el-icon-download" @click="exportTheTick(row)"
- >导出
- </el-button>
+
+ <template v-if="row.status === 4 && !isProd">
+ <el-button type="text" icon="el-icon-download" @click="exportTheTick(row)">导出</el-button>
</template>
+
<template v-if="permission.tickets_repeat_review">
<el-button
v-if="row.status === 4 && row.isReview !== 1"
type="text"
icon="el-icon-check"
@click="reCheck(row)"
- >复核
+ >
+ 复核
</el-button>
</template>
</template>
- <template #status="{ row }">
- <span
- :style="getStatusTagType(row.status) ? 'color:' + getStatusTagType(row.status) : ''"
- >
- {{ mapStatus(row.status) }}
- </span>
- </template>
- <template #keyData="{ row }">
- <span>{{ row.address }}</span>
- </template>
-
- <template #isReview="{ row }">
- <span>{{ showIsReviewText(row) }}</span>
- </template>
- </avue-crud>
+ </el-table-column>
+ </el-table>
+ </div>
+ <div class="pagination">
+ <el-pagination
+ class="table-pagination"
+ v-model:current-page="page.currentPage"
+ v-model:page-size="page.pageSize"
+ :page-sizes="[10, 20, 30, 40]"
+ :total="page.total"
+ background
+ layout="total, sizes, prev, pager, next, jumper"
+ @size-change="sizeChange"
+ @current-change="handleCurrentChange"
+ />
+ </div>
+ </div>
</basic-main-content>
</el-tab-pane>
</el-tabs>
<!-- 新建工单对话框 -->
- <el-dialog
- v-model="dialogVisible"
- v-if="dialogVisible"
- title="新建工单"
- width="70%"
- :close-on-click-modal="false"
- @close="resetForm"
- >
- <el-form
- :model="form"
- :rules="rules"
- ref="form"
- label-width="90px"
- class="create-ticket-form"
- >
- <div class="form-section">
- <el-row :gutter="16">
- <el-col :span="12">
- <el-form-item label="工单名称" prop="name">
- <el-input v-model="form.name" placeholder="请输入工单名称"></el-input>
- </el-form-item>
- </el-col>
- <el-col :span="12">
- <el-form-item label="工单类型" prop="type">
- <el-select
- @change="handleTypeChange"
- v-model="form.type"
- placeholder="请选择工单类型"
- class="full-width"
- >
- <el-option
- v-for="item in types"
- :key="item.value"
- :label="item.label"
- :value="item.value"
- />
- </el-select>
- </el-form-item>
- </el-col>
- </el-row>
-
- <el-row :gutter="16">
- <el-col :span="12">
- <el-form-item label="处理部门" prop="department">
- <el-select
- v-model="form.department"
- placeholder="请选择处理部门"
- @change="handleDepartmentChange"
- class="full-width"
- >
- <el-option
- v-for="dept in departments"
- :key="dept.value"
- :label="dept.label"
- :value="dept.value"
- />
- </el-select>
- </el-form-item>
- </el-col>
- <el-col :span="12">
- <el-form-item label="处理人员" prop="handler">
- <el-select
- v-model="form.handler"
- placeholder="请先选择处理人员"
- :disabled="!form.department"
- class="full-width"
- >
- <el-option
- v-for="user in availableHandlers"
- :key="user.id"
- :label="user.name"
- :value="user.id"
- />
- </el-select>
- </el-form-item>
- </el-col>
- </el-row>
-
- <el-row :gutter="16">
- <el-col :span="12">
- <el-form-item label="关联算法" prop="algorithm">
- <el-select
- v-model="form.algorithm"
- multiple
- placeholder="请选择关联算法"
- class="full-width"
- :disabled="!form.type"
- >
- <el-option
- v-for="item in algorithms2"
- :key="item.value"
- :label="item.label"
- :value="item.value"
- />
- </el-select>
- </el-form-item>
- </el-col>
- <el-col :span="12">
- <el-form-item label="事件位置" prop="location">
- <div class="location-wrapper">
- <avue-input-map
- v-model="form.location"
- :clearable="false"
- :params="mapParams"
- @change="handleLocationChange"
- type="button"
- >
- <el-button type="primary" plain class="map-button">
- <i class="el-icon-map-location"></i> 地图选点
- </el-button>
- </avue-input-map>
- <!-- <div v-if="form.location?.length >= 3">
- {{ form.location[2] || '获取地址中...' }}
- </div> -->
- </div>
- </el-form-item>
- </el-col>
- </el-row>
-
- <el-row :gutter="16">
- <el-col :span="12">
- <el-form-item label="工单内容" prop="content">
- <el-input
- type="textarea"
- v-model="form.content"
- :rows="4"
- placeholder="请输入工单内容描述"
- ></el-input>
- </el-form-item>
- </el-col>
- <el-col :span="12">
- <el-form-item label="事件图片" prop="photos" required class="upload-wrapper">
- <el-upload
- v-if="createoredit === 1"
- ref="upload"
- :action="'#'"
- :auto-upload="false"
- list-type="picture-card"
- :on-change="handleFileChange"
- :on-remove="handleUploadRemove"
- :before-upload="beforeUpload"
- :file-list="form.photos"
- :limit="1"
- accept="image/*"
- class="create-upload"
- >
- <template v-if="form.photos.length < 1">
- <!-- <i class="el-icon-plus">+</i> -->
- <div class="el-icon-plus">
- <span>+</span>
- </div>
- </template>
- </el-upload>
- <el-upload
- v-else
- ref="upload"
- :action="'#'"
- :auto-upload="false"
- list-type="picture-card"
- :on-change="handleFileChange"
- :on-remove="handleUploadRemove"
- :before-upload="beforeUpload"
- :file-list="popupShowImage(form.photos)"
- :limit="1"
- accept="image/*"
- class="create-upload"
- >
- <template v-if="form.photos.length < 1">
- <div class="el-icon-plus">
- <span>+</span>
- </div>
- </template>
- </el-upload>
- <div class="upload-tip">需上传含有地址信息的照片(jpg、jpeg、png),且不超过5M</div>
- </el-form-item>
- </el-col>
- </el-row>
- </div>
- </el-form>
- <template #footer>
- <div class="dialog-footer-new">
- <el-button
- type="danger"
- :loading="submitLoading"
- @click="submitForm"
- icon="el-icon-position"
- >发布
- </el-button>
- <!-- <el-button
- type="infoprimary"
- plain
- :loading="draftLoading"
- @click="saveDraft"
- icon="el-icon-document-add"
- >存草稿
- </el-button> -->
-
- <el-button @click="handleCancel" icon="el-icon-circle-close">取 消</el-button>
- </div>
- </template>
- </el-dialog>
-
+ <create-ticket-dialog
+ v-model="dialogVisible"
+ :departments="departments"
+ :department-users="departmentUsers"
+ :types="types"
+ :all-algorithms="allAlgorithms"
+ :edit-data="editFormData"
+ :map-center="inputMapShowDefaultCenter"
+ @create-success="handleCreateSuccess"
+ @draft-success="handleDraftSuccess"
+ @create-error="handleCreateError"
+ />
<!-- 工单详情对话框 -->
- <el-dialog
- class="custom-dialog"
- align-center
- v-model="detailVisible"
- title="工单详情"
- width="80%"
- append-to-body
- >
- <div class="detail-container">
- <div class="detail-top-title">
- <div class="event-title-center event-orderNumber">
- {{ currentDetail.orderNumber || '工单编号' }}
- </div>
- <div class="event-title-center">{{ currentDetail.orderName || '事件名称' }}</div>
- </div>
- <div v-if="totalTime" class="event-total-time">总耗时:{{ totalTime }}</div>
- <!-- 工单状态流程 -->
- <div class="custom-steps-container">
- <!-- 标题行 -->
- <div class="steps-titles">
- <div
- v-for="(status, index) in stepStatusList"
- :key="index"
- :class="{
- 'step-title': true,
- active: index <= stepStatusList.indexOf(String(currentDetail.status)),
- }"
- >
- {{ mapStatus(status) }}
- </div>
- </div>
-
- <!-- Element Steps 组件 -->
- <el-steps :active="getActiveStep() - 1" align-center class="custom-steps">
- <el-step v-for="(status, index) in stepStatusList" :key="index">
- <template #description>
- <span class="step-description">
- {{ getStepHandler(status) }}
- </span>
- <div class="step-description" v-if="getStepTime(status)">
- <span class="step-timer"> 耗时:{{ getStepTime(status) }} </span>
- </div>
- <div class="step-description">
- {{ getStepCreateTime(status) }}
- </div>
- </template>
- </el-step>
- </el-steps>
- </div>
-
- <div class="PopUpTableScrolls">
- <!-- 基本信息表格 -->
- <el-table :show-header="false" :data="formattedDetailFields" border class="tableCss">
- <el-table-column prop="label1" label="基本信息" width="150">
- <template #default="{ row }">
- <!-- 添加必填星号的标签 -->
- <span
- v-if="
- currentDetail.status === 0 &&
- (row.label1 === '关联算法' || row.label1 === '工单名称')
- "
- class="required-label"
- >
- <span class="required-star">*</span>{{ row.label1 }}
- </span>
- <span v-else>{{ row.label1 }}</span>
- </template>
- </el-table-column>
- <el-table-column>
- <template #default="{ row }">
- <template
- v-if="
- currentDetail.status === 0 &&
- row.label1 === '工单名称' &&
- hasProcessingBtnPermission()
- "
- >
- <el-input
- v-model="currentDetail.orderName"
- placeholder="请输入工单名称"
- class="required-input"
- />
- </template>
- <template
- v-else-if="
- currentDetail.status === 0 &&
- row.label1 === '关联算法' &&
- hasProcessingBtnPermission()
- "
- >
- <el-select
- v-model="currentDetail.aiType"
- placeholder="请选择关联算法"
- class="required-input"
- >
- <el-option
- v-for="item in algorithms2"
- :key="item.value"
- :label="item.label"
- :value="item.value"
- />
- </el-select>
- </template>
- <template v-else>{{ row.value1 }}</template>
- </template>
- </el-table-column>
- <el-table-column prop="label2" label="基本信息" width="150">
- <template #default="{ row }">
- <!-- 添加必填星号的标签 -->
-
- <span
- v-if="currentDetail.status === 0 && row.label2 === '工单内容'"
- class="required-label"
- >
- <span class="required-star">*</span>{{ row.label2 }}
- </span>
- <span v-else>{{ row.label2 }}</span>
- </template>
- </el-table-column>
- <el-table-column>
- <template #default="{ row }">
- <!-- 修改工单类型和工单内容的显示 -->
-
- <template
- v-if="
- currentDetail.status === 0 &&
- row.label2 === '工单内容' &&
- hasProcessingBtnPermission()
- "
- >
- <el-input
- type="textarea"
- v-model="currentDetail.content"
- placeholder="请输入工单内容"
- class="required-input"
- />
- </template>
- <template v-else>{{ row.value2 }}</template>
- </template>
- </el-table-column>
- </el-table>
-
- <!-- 事件处理详情 -->
- <div v-if="[3, 4].includes(currentDetail.status)" class="form-section">
- <div class="section-title">
- <!-- 处理中状态显示必填星号 -->
- <template v-if="currentDetail.status === 3">
- <span class="required-label">
- <span class="required-star">*</span>事件处理详情
- </span>
- </template>
- <template v-else> 事件处理详情</template>
- </div>
- <!-- 处理中状态显示输入框 -->
- <template v-if="currentDetail.status === 3 && hasProcessedAndOverBtnPermission()">
- <el-input
- type="textarea"
- v-model="currentDetail.processingDetail"
- placeholder="请输入事件处理详情"
- :rows="4"
- style="width: 100%; margin-bottom: 10px"
- />
- </template>
- <!-- 已完成和已完结状态显示只读文本 -->
- <template v-else>
- <div class="readonly-processing-detail">
- {{ currentDetail.processingDetail }}
- </div>
- </template>
- </div>
-
- <!-- 上传图片 -->
- <div v-if="[3].includes(currentDetail.status)" class="form-section uploadImg">
- <div class="section-title" v-if="hasProcessedAndOverBtnPermission()">
- <!-- 已完成状态显示必填星号 -->
- <template v-if="currentDetail.status === 3">
- <span class="required-label"> <span class="required-star">*</span>上传图片 </span>
- </template>
- <template v-else> 上传图片</template>
- </div>
- <el-upload
- v-if="hasProcessedAndOverBtnPermission()"
- ref="upload"
- :action="'#'"
- :auto-upload="false"
- list-type="picture-card"
- :on-change="handleFileChange"
- :on-remove="handleUploadRemove"
- :before-upload="beforeUpload"
- :file-list="currentDetail.photos || []"
- :limit="1"
- accept="image/*"
- class="detail-upload"
- >
- <template v-if="!currentDetail.photos || currentDetail.photos.length < 1">
- <!-- <i class="el-icon-plus">+</i> -->
- <div class="el-icon-plus">
- <span>+</span>
- </div>
- </template>
- </el-upload>
- <div class="el-upload__tip" v-if="hasProcessedAndOverBtnPermission()">
- (上传照片即可完结工单,只能上传jpg、jpeg、png照片,且不超过5M)
- </div>
- </div>
-
- <!-- 图片和地图部分 -->
- <div class="media-section">
- <el-row :gutter="20">
- <el-col :span="12">
- <div class="media-box">
- <div class="media-title">事件图片</div>
- <div class="media-content">
- <el-image
- v-if="currentDetail.mediaUrl"
- :src="getThumbUrl(currentDetail.mediaUrl)"
- :preview-src-list="[getPreviewUrl(currentDetail.mediaUrl)]"
- fit="contain"
- style=" cursor: pointer"
- >
- <template #placeholder>
- <div class="image-placeholder">
- <i class="el-icon-picture-outline"></i>
- <span>加载中...</span>
- </div>
- </template>
- <template #error>
- <div class="image-error">
- <i class="el-icon-picture-outline"></i>
- <span>加载失败</span>
- </div>
- </template>
- </el-image>
- <div v-else class="no-media">暂无图片/视频</div>
- </div>
- </div>
- </el-col>
- <el-col :span="12">
- <div class="media-box">
- <!-- 根据状态显示不同的标题和内容 -->
-
- <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)"
- :preview-src-list="[getPreviewUrl(currentDetail.updatePhotoUrl)]"
- fit="fill"
- >
- <template #placeholder>
- <div class="image-placeholder">
- <i class="el-icon-picture-outline"></i>
- <span>加载中...</span>
- </div>
- </template>
- <template #error>
- <div class="image-error">
- <i class="el-icon-picture-outline"></i>
- <span>加载失败</span>
- </div>
- </template>
- </el-image>
- <div v-else class="no-media">暂无处理图片</div>
- </div>
- </template>
- <template v-else>
- <div class="media-title">
- 地图标记事件点
- <el-popover
- v-if="currentDetail.status === 3 || currentDetail.status === 0"
- popper-class="custom-qrcode-popover"
- :width="120"
- :visible="currentDetail.showQR && detailVisible"
- placement="top"
- title=""
- trigger="click"
- >
- <template #reference>
- <img
- @click.stop="handleQRCode(currentDetail)"
- class="QRCodeImg"
- src="@/assets/images/dataCenter/qrCode.svg"
- alt=""
- title="事件导航"
- />
- </template>
- <div class="qrcode-content">
- <div class="close-btn" @click.stop="currentDetail.showQR = false">×</div>
- <CreateQRcode
- v-if="currentDetail.showQR && detailVisible"
- :latAndLon="currentDetail.location"
- ></CreateQRcode>
- </div>
- </el-popover>
- </div>
- <div class="media-content">
- <map-container v-if="detailVisible" ref="MapContainer"></map-container>
- </div>
- </template>
- </div>
- </el-col>
- </el-row>
- </div>
- </div>
-
- <!-- 操作按钮 -->
- <div class="dialog-footer1-new">
- <div class="leftBtn" :class="currentIndex === 0 ? 'disableds' : ''" @click="leftClick">
- 上一页
- </div>
- <div class="btngroups">
- <template v-if="currentDetail.status === 2">
- <!-- 待审核 -->
- <el-button
- v-if="hasReviewBtnPermission()"
- type="primary"
- :loading="approveLoading"
- @click="approveTicket"
- icon="el-icon-check"
- >通过
- </el-button>
- <el-button
- v-if="hasReviewBtnPermission()"
- type="danger"
- :loading="rejectLoading"
- @click="rejectTicket"
- icon="el-icon-close"
- >不通过
- </el-button>
- <el-button @click="detailVisible = false" icon="el-icon-circle-close">取消</el-button>
- </template>
- <template v-else-if="currentDetail.status === 0">
- <el-button
- v-if="hasProcessingBtnPermission()"
- type="primary"
- :loading="dispatchLoading"
- @click="approveAndDispatch"
- icon="el-icon-check"
- >受理
- </el-button>
- <el-button
- v-if="hasProcessingBtnPermission()"
- type="danger"
- :loading="rejectLoading"
- @click="rejectTicket"
- icon="el-icon-close"
- >不受理
- </el-button>
- <el-button @click="detailVisible = false" icon="el-icon-circle-close">取消</el-button>
- </template>
- <template v-if="currentDetail.status === 3">
- <!-- 处理中 -->
- <el-button
- v-if="hasProcessedAndOverBtnPermission()"
- type="primary"
- :loading="completeLoading"
- @click="completeTicket"
- icon="el-icon-circle-check"
- >完成工单
- </el-button>
- <el-button @click="detailVisible = false" icon="el-icon-circle-close">取消</el-button>
- </template>
- <template v-else-if="currentDetail.status === 4">
- <!-- 已完成 -->
- <!-- <el-button v-if="hasProcessedAndOverBtnPermission()" type="primary" :loading="finalizeLoading"
- @click="finalizeTicket">完结工单</el-button> -->
- <el-button @click="detailVisible = false" icon="el-icon-circle-close">取消</el-button>
- </template>
- </div>
- <div
- :class="currentIndex === tableData.length - 1 ? 'disableds' : ''"
- class="leftBtn"
- @click="rightClick"
- >
- 下一页
- </div>
- </div>
- </div>
- </el-dialog>
+ <ticket-detail-dialog
+ v-model="detailVisible"
+ :current-detail="currentDetail"
+ :current-index="currentIndex"
+ :total-items="tableData.length"
+ :algorithms="algorithms2"
+ :types="types"
+ :permission="permission"
+ :step-status-list="stepStatusList"
+ :work-type="workType"
+ @detail-success="handleDetailSuccess"
+ @detail-error="handleDetailError"
+ @prev-item="handlePrevItem"
+ @next-item="handleNextItem"
+ @update:current-detail="handleCurrentDetailUpdate"
+ @approve-and-dispatch="handleApproveAndDispatch"
+ />
<!-- 派发工单对话框 -->
- <el-dialog
- v-model="dispatchDialogVisible"
- title="派发工单"
- width="40%"
- :close-on-click-modal="false"
- >
- <el-form :model="dispatchForm" :rules="dispatchRules" ref="dispatchForm" label-width="100px">
- <el-form-item label="选择部门" prop="department">
- <el-select
- v-model="dispatchForm.department"
- placeholder="请选择部门"
- @change="handleDispatchDepartmentChange"
- >
- <el-option
- v-for="dept in departments"
- :key="dept.value"
- :label="dept.label"
- :value="dept.value"
- />
- </el-select>
- </el-form-item>
- <el-form-item label="选择处理人" prop="handler">
- <el-select
- filterable
- v-model="dispatchForm.handler"
- placeholder="请选择处理人"
- :disabled="!dispatchForm.department"
- >
- <el-option
- v-for="user in availableDispatchHandlers"
- :key="user.id"
- :label="user.name"
- :value="user.id"
- />
- </el-select>
- </el-form-item>
- </el-form>
- <template #footer>
- <el-button @click="dispatchDialogVisible = false" icon="el-icon-circle-close"
- >取消
- </el-button>
- <el-button type="primary" :loading="dispatchLoading" @click="submitDispatch"
- >确认派发
- </el-button>
- </template>
- </el-dialog>
+ <dispatch-dialog
+ v-model="dispatchDialogVisible"
+ :current-detail="currentDetail"
+ :department-users="departmentUsers"
+ :algorithms="algorithms"
+ :types="types"
+ @dispatch-success="handleDispatchSuccess"
+ @dispatch-error="handleDispatchError"
+ />
- <!-- 添加在其他 dialog 组件之后 -->
+ <!-- 批量审核对话框 -->
<el-dialog
v-model="reviewDialogVisible"
title="批量审核"
width="1100"
+ align-center
append-to-body
class="ztzf-dialog-mange"
custom-class="review-dialog"
@@ -981,41 +387,30 @@
</div>
</template>
</el-dialog>
-
<!-- 复核弹出层 -->
-
- <el-dialog
- v-model="reCheckDialog"
- title="工单复核"
- width="30%"
- append-to-body
- class="ztzf-dialog-mange"
- @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>
+ <recheck-dialog
+ v-model="reCheckDialog"
+ :recheck-data="reCheckData"
+ @recheck-success="handleRecheckSuccess"
+/>
</basic-container>
</template>
-<script>
+<script setup>
+import { ElMessage} from 'element-plus';
+import { useRoute, useRouter } from 'vue-router'
+import { useStore } from 'vuex'
import { pxToRem, pxToRemNum } from '@/utils/rem';
import { getSmallImg, getShowImg } from '@/utils/util';
-import { ElMessageBox, ElLoading } from 'element-plus';
import { calculateDefaultRange } from '@/utils/util';
import { gcj02ToWgs84, wgs84ToGcj02 } from '@/utils/coordinateTransformation';
import _ from 'lodash';
import {
getList,
- createTicket,
getTicketInfo,
flowEvent,
getstatusCount,
getStepInfo,
- getReviewById,
- getCreateEventJob,
exportTheTicket
} from '@/api/tickets/ticket';
import { getSFDictionaryTree } from '@/api/job/task';
@@ -1025,2233 +420,1118 @@
import { getAdcodeObj } from '@/utils/disposeData';
import elTooltipCopy from '@/components/ElTooltipCopy.vue';
import getBaseConfig from '@/buildConfig/config';
-import CreateQRcode from '@/components/CreateQRcode/CreateQRcode.vue';
-import { CircleClose } from '@element-plus/icons-vue';
-const isProd = import.meta.env.VITE_APP_ENV === 'production'
+import RecheckDialog from '@/views/tickets/ticketComponent/RecheckDialog.vue';
+import DispatchDialog from '@/views/tickets/ticketComponent/DispatchDialog.vue';
+import CreateTicketDialog from '@/views/tickets/ticketComponent/CreateTicketDialog.vue';
+import TicketDetailDialog from '@/views/tickets/ticketComponent/TicketDetailDialog.vue';
const { envName } = getBaseConfig();
+const isProd = import.meta.env.VITE_APP_ENV === 'production'
+const route = useRoute()
+const router = useRouter()
+const store = useStore()
+const currentIndex = ref(null)
+const finalizeLoading = ref(false)
+const activeTab = ref('all')
+const tabs = ref([
+ { label: '我的工单', name: 'myTickets', value: null, count: 0 },
+ { label: '全部工单', name: 'all', value: null, count: 0 },
+ { label: '待审核', name: 'pending', value: 2, count: 0 },
+ { label: '待处理', name: 'processing', value: 0, count: 0 },
+ { label: '处理中', name: 'inProgress', value: 3, count: 0 },
+ { label: '已完成', name: 'completed', value: 4, count: 0 },
+])
+const filters = reactive({
+ keyword: '',
+ department: '',
+ type: '',
+ dateRange: [],
+ status: '',
+ algorithm: '',
+ isReview: '',
+})
+const departments = ref([])
+const types = ref([])
+const allAlgorithms = ref([])
+const handlers = ref([
+ { label: '处理人A', value: 'handlerA' },
+ { label: '处理人B', value: 'handlerB' },
+])
+const algorithms = ref([])
+const algorithms2 = ref([])
+const statuses = ref([
+ { label: '待审核', value: '2' },
+ { label: '待处理', value: '0' },
+ { label: '处理中', value: '3' },
+ { label: '已完成', value: '4' },
+])
+const reviewStatuses = ref([
+ { label: '否', value: 0 },
+ { label: '是', value: 1 },
+])
+const tableData = ref([])
+const page = reactive({
+ pageSize: 10,
+ currentPage: 1,
+ total: 0,
+})
+const dialogVisible = ref(false)
+const editFormData = ref(null)
+const detailVisible = ref(false)
+const currentDetail = ref({})
+const departmentUsers = ref({})
+const loading = ref(false)
+const globalCounts = ref({})
+const mapLoaded = ref(false)
+const isFetching = ref(false)
+const mapParams = reactive({
+ zoom: 15,
+ center: null,
+})
+const dispatchDialogVisible = ref(false)
+const stepInfos = ref([])
+const userNameToIdMap = ref({})
+const workType = ref(0)
+const selections = ref([])
+const reviewDialogVisible = ref(false)
+const currentReviewImage = ref('')
+const currentImageIndex = ref(1)
+const totalTime = ref('')
+const isShowInfo = ref(false)
+const datePickerDefaultVal = ref(calculateDefaultRange())
+const reCheckDialog = ref(false)
+const reCheckData = ref({})
+const treePropsSF = reactive({
+ label: 'dictValue',
+ value: 'id',
+ children: 'children',
+})
+const dictKey = ref('')
+const dataList = ref([])
+const checkedKeys = ref([])
+const inputMapShowDefaultCenter = ref(null)
+const userInfo = computed(() => store.state.user.userInfo)
+const permission = computed(() => store.state.user.permission);
-function regExp(label, name) {
- var reg = new RegExp(label + '=([^&]*)(&|$)', 'g');
- return name.match(reg)[0].split('=')[1];
+// 计算属性
+const firstRowData = computed(() => {
+ return tableData.value.length > 0 ? tableData.value[0] : null
+})
+ // 动态过滤tabs,保证isShow为true/false
+ const filteredTabs = computed(() => {
+ const tabPending = permission.value?.tickets_tab_pending === true//待审核
+ const tabMyTickets = permission.value?.tickets_tab_mytickets === true //我的工单
+ const tabALL = permission.value?.tickets_allWorkOrders === true //全部工单
+ const tabInProgress= permission.value?.tickets_inProgress === true //处理中
+ const tabProcessing= permission.value?.tickets_pending === true //待处理
+ const tabCompleted= permission.value?.tickets_wasDone === true //已完成
+ return tabs.value
+ .map(tab => {
+ switch (tab.name) {
+ case 'all':
+ return { ...tab, isShow: tabALL } // 全部工单
+ case 'pending':
+ return { ...tab, isShow: tabPending } // 待审核
+ case 'myTickets':
+ return { ...tab, isShow: tabMyTickets } // 我的工单
+ case 'processing':
+ return { ...tab, isShow: tabProcessing } // 待处理
+ case 'inProgress':
+ return { ...tab, isShow: tabInProgress } // 处理中
+ case 'completed':
+ return { ...tab, isShow: tabCompleted } // 已完成
+ default:
+ return { ...tab, isShow: false } // 其他标签默认隐藏
+ }
+ })
+ .filter(tab => tab.isShow)
+})
+ // 可根据实际后端权限key调整
+const permissionList = computed(() => {
+ return {
+ addBtn: validData(permission.value.tickets_add, false),
+ delBtn: validData(permission.value.tickets_delete, false),
+ exportBtn: validData(permission.value.tickets_export, false),
+ reviewBtn: validData(permission.value.tickets_review_btn, false),
+ }
+})
+// 计算默认激活的标签(无全部工单时优先激活待审核)
+const defaultActiveTab = computed(() => {
+ const hasAllTab = filteredTabs.value.some(tab => tab.name === 'all')
+ const hasPendingTab = filteredTabs.value.some(tab => tab.name === 'pending')
+
+ // 如果没有全部工单权限,但有待审核权限,则默认显示待审核
+ if (!hasAllTab && hasPendingTab) {
+ return 'pending'
+ }
+ // 如果有全部工单权限,则默认显示全部工单
+ else if (hasAllTab) {
+ return 'all'
+ }
+ // 否则显示第一个可用的tab
+ else {
+ return filteredTabs.value[0]?.name || 'all'
+ }
+})
+// 提取状态列表计算属性
+const statusList = computed(() => {
+ const visibleTabs = filteredTabs.value;
+ return visibleTabs
+ .filter(tab => tab.name !== 'all' && tab.name !== 'myTickets' && tab.value !== undefined)
+ .map(tab => String(tab.value));
+});
+const stepStatusList = computed(() => {
+ return stepInfos.value.map(step => String(step.status))
+})
+const showIsReviewText = computed(() => (row) => {
+ if (['4'].includes(String(row.status))) return row.isReview === 1 ? '是' : '否'
+ return '/'
+})
+const popupShowImage = computed(() => (list) => {
+ return list.map(item => ({
+ ...item,
+ url: getShowImg(item.url),
+ }))
+})
+// 方法
+function validData(value, defaultValue) {
+ return value !== undefined ? value : defaultValue
}
-
-export default {
- components: { elTooltipCopy, CreateQRcode },
- name: 'TicketPage',
- data() {
- return {
- currentIndex: null, // 当前显示的数据索引
- submitLoading: false, // 新增loading状态
- draftLoading: false,
- approveLoading: false,
- rejectLoading: false,
- dispatchLoading: false,
- completeLoading: false,
- finalizeLoading: false,
- createoredit: '',
- activeTab: 'all',
- // tabs 只保留静态结构,不做权限判断
- tabs: [
- { label: '全部工单', name: 'all', value: null, count: 0 },
- { label: '待审核', name: 'pending', value: 2, count: 0 },
- { 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: 'myTickets', value: null, count: 0 },
- ],
- filters: {
- keyword: '',
- department: '',
- type: '',
- dateRange: [],
- status: '',
- algorithm: '', // 新增算法筛选字段
- isReview: '', // 添加复核状态筛选字段
- },
- departments: [],
- types: [],
- allAlgorithms: [],
- handlers: [
- { label: '处理人A', value: 'handlerA' },
- { label: '处理人B', value: 'handlerB' },
- ],
-
- algorithms: [],
- algorithms2: [],
- statuses: [
- { label: '待审核', value: '2' },
- { label: '待处理', value: '0' },
- { label: '处理中', value: '3' },
- { label: '已完成', value: '4' },
- ],
- reviewStatuses: [
- { label: '否', value: 0 },
- { label: '是', value: 1 },
- ],
- tableData: [],
- option: {
- border: true,
- stripe: false,
- selection: true, // 添加多选功能
- index: true, // 保留序号功能
- indexLabel: '序号',
- indexWidth: 60,
- menuWidth: 150,
- searchMenuSpan: 6,
- viewBtn: false,
- editBtn: false,
- delBtn: false,
- addBtn: false,
- menu: true,
- page: true,
-
- height: 'auto',
- calcHeight: 196,
-
- column: [
- // { label: "序号", prop: "id", width: 70 },
- { label: '工单编号', prop: 'orderNumber', width: 170 },
- { label: '工单名称', prop: 'orderName', width: 150, overHidden: true, tooltip: true },
- { label: '所属部门', prop: 'department', overHidden: true, tooltip: true },
- { label: '发起任务时间', prop: 'startTime', width: 160, overHidden: true },
- { label: '关联算法', prop: 'aiType', overHidden: true, tooltip: true },
- {
- label: '工单类型',
- prop: 'type',
- width: 110,
- overHidden: true,
- tooltip: true,
- type: 'select',
- dicData: [],
- },
- {
- label: '工单内容',
- prop: 'content',
- slot: true,
- width: 152,
- overHidden: true,
- },
- { label: '创建人', prop: 'creator', width: 120, overHidden: true },
- { label: '处理人', prop: 'handler', width: 120, overHidden: true },
- {
- slot: true,
- hide: envName === 'jiangwu' ? true : false,
- label: '复核状态',
- prop: 'isReview',
- width: 90,
- },
- { label: '工单状态', prop: 'status', slot: true, width: 90 },
- ],
- },
- page: {
- pageSize: 10,
- currentPage: 1,
- total: 0,
- },
- dialogVisible: false,
- detailVisible: false,
- currentDetail: {},
- form: {
- name: '',
- type: '',
- department: '',
- handler: '',
- algorithm: [], // 关联算法改为数组
- location: [], // 将存储为[经度, 纬度, 地址]格式
- address: '',
- photos: [],
- content: '', // 新增字段,用于存储后端返回的 content
- },
- rules: {
- name: [{ required: true, message: '请输入工单名称', trigger: 'blur' }],
- type: [{ required: true, message: '请选择工单类型', trigger: 'change' }],
- department: [{ required: true, message: '请选择所属部门', trigger: 'change' }],
- handler: [{ required: true, message: '请选择处理人员', trigger: 'change' }],
- content: [{ required: true, message: '请输入工单内容', trigger: 'blur' }],
- algorithm: [{ required: true, message: '请选择关联算法', trigger: 'change' }],
- location: [
- {
- required: true,
- validator: (rule, value, callback) => {
- if (!value || value.length < 2) {
- callback(new Error('请选择位置信息'));
- } else if (!value[0] || !value[1]) {
- callback(new Error('请选择位置信息'));
- } else {
- callback();
- }
- },
- trigger: 'change',
- },
- ],
- photos: [
- {
- validator: (rule, value, callback) => {
- if (!this.form.photos || this.form.photos.length === 0) {
- callback(new Error('请上传工单图片'));
- } else {
- callback();
- }
- },
- trigger: 'change',
- },
- ],
- },
- departmentUsers: {},
- loading: false,
- globalCounts: {},
- mapLoaded: false,
- isFetching: false,
- mapParams: {
- zoom: 15,
- center: null, // 初始设为 null,等待动态设置
- },
- dispatchDepartment: '', // 新增:派发部门
- dispatchHandler: '', // 新增:派发处理人
- dispatchDialogVisible: false, // 新增:派发对话框可见性
- dispatchForm: {
- department: '',
- handler: '',
- }, // 新增:派发表单数据
- dispatchRules: {
- department: [{ required: true, message: '请选择部门', trigger: 'change' }],
- handler: [{ required: true, message: '请选择处理人', trigger: 'change' }],
- }, // 新增:派发表单验证规则
- stepInfos: [], // 新增:存储步骤信息
- fixedStatuses: ['2', '0', '3', '4'], // 固定的五个状态
- userNameToIdMap: {}, // 新增用户名到ID的映射
- workType: 0, // 新增:当前工单work_type
- selections: [], // 添加选中行数据数组
- reviewDialogVisible: false, // 新增:审核对话框可见性
- currentReviewImage: '', // 新增:当前审核图片
- currentImageIndex: 1, // 新增:当前图片索引
- totalTime: '',
- isShowInfo: false,
-
- // 配置时间选择器默认配置
- datePickerDefaultVal: calculateDefaultRange(),
-
- // 复核弹窗
- reCheckDialog: false,
- treePropsSF: {
- label: 'dictValue',
- value: 'id',
- children: 'children',
- },
- dictKey: '',
- dataList: [],
- checkedKeys: [],
- };
- },
- created() {
- this.inputMapShowDefaultCenter = null;
-
- this.loadAMapScripts();
- this.fetchDropdownData();
- },
-
- mounted() {
- this.getAlgorithmList();
-
- const href = this.$route.href;
- if (this.$route?.query?.status !== undefined && this.$route?.query?.status !== null) {
- this.filters.status = this.$route?.query?.status + '';
- this.$router.replace({});
+// 获取表格数据
+async function fetchTableData() {
+ if (isFetching.value) return
+ isFetching.value = true
+ loading.value = true
+ const statusListVal = statusList.value;
+ try {
+ const currentTab = tabs.value.find(tab => tab.name === activeTab.value)
+ const params = {
+ ai_types: filters.type || undefined,
+ status: currentTab?.name === 'myTickets'
+ ? undefined
+ : filters.status !== ''
+ ? Number(filters.status)
+ : currentTab?.value,
+ event_name: filters.keyword || undefined,
+ dept_id: filters.department || undefined,
+ start_date: filters.dateRange?.[0]
+ ? formatDate(filters.dateRange[0])
+ : undefined,
+ end_date: filters.dateRange?.[1]
+ ? formatDate(filters.dateRange[1]).replace('00:00:00', '23:59:59')
+ : undefined,
+ current: Number(page.currentPage),
+ size: Number(page.pageSize),
+ ai_type: filters.algorithm || undefined,
+ is_draft: currentTab?.name === 'myTickets' ? 1 : undefined,
+ user_id: currentTab?.name === 'myTickets' ? userInfo.value.user_id : undefined,
+ is_review: filters.isReview === '' ? undefined : filters.isReview,
+ source: 1,
+ status_list:currentTab?.name !== 'myTickets'? statusListVal : undefined
}
- let curQueryParams = {};
- let isTabProcessed = false;
- if (href.indexOf('?') != -1 && href.split('?').length > 0) {
- curQueryParams = href
- .split('?')[1]
- .split('&')
- .reduce((pre, cur) => {
- let newArr = cur.split('=');
-
- pre[newArr[0]] = newArr[1];
-
- return pre;
- }, {});
-
- const { orderNumber = undefined, day = undefined, tab = undefined } = curQueryParams;
-
- // 日历传值
- if (day) {
- const date = new Date(day + 'T00:00:00+08:00');
- const dateArray = [date, date];
- const handler = {
- get(target, prop) {
- if (typeof prop === 'string' && /^\d+$/.test(prop)) {
- const index = parseInt(prop);
- const dateObj = target[index];
- return dateObj.toDateString() + ' 00:00:00 GMT+0800 (中国标准时间)';
- }
- return Reflect.get(target, prop);
- },
- };
-
- const proxyArray = new Proxy(dateArray, handler);
- this.filters.dateRange = proxyArray;
- }
-
- if (orderNumber) {
- this.filters.keyword = orderNumber;
- this.$nextTick(() => {
- this.isShowInfo = true;
- const find = this.$store.state.tags.bsTagList.find(i => i.path === '/tickets/ticket');
- find && (find.query = {});
- });
- }
- if (tab) {
- const isTabValid = this.filteredTabs.some(t => t.name === tab);
- if (isTabValid) {
- this.activeTab = tab;
- this.handleTabChangeAfterJump();
- isTabProcessed = true;
- const find = this.$store.state.tags.bsTagList.find(i => i.path === '/tickets/ticket');
- find && (find.query = {});
- }
- }
+ const response = await getList(params)
+ if (!response?.data?.data?.records) {
+ throw new Error('接口返回数据格式不正确')
}
-
- this.fetchTabCounts(); // 新增:初始化时获取 tab 数据
- this.fetchTableData();
- },
- computed: {
- CircleClose() {
- return CircleClose;
- },
- firstRowData() {
- return this.tableData.length > 0 ? this.tableData[0] : null;
- },
- availableHandlers() {
- return this.form.department ? this.departmentUsers[this.form.department] || [] : [];
- },
- availableDispatchHandlers() {
- return this.dispatchForm.department
- ? this.departmentUsers[this.dispatchForm.department] || []
- : [];
- },
- detailTableData() {
- return [
- {
- label: '工单名称',
- value: this.currentDetail.orderName,
- editable: this.currentDetail.status === 0,
- type: 'input',
- },
- {
- label: '关键任务',
- value: this.currentDetail.keyData,
- editable: false,
- },
- {
- label: '工单创建人',
- value: this.currentDetail.creator,
- editable: false,
- },
- {
- label: '当前状态',
- value: this.mapStatus(this.currentDetail.status),
- editable: false,
- },
- {
- label: '事件地址',
- value: this.currentDetail.address,
- editable: false,
- },
- {
- label: '工单类型',
- value: this.currentDetail.type,
- editable: this.currentDetail.status === 0,
- type: 'select',
- options: this.types,
- },
- {
- label: '关联算法',
- value: this.currentDetail.aiType,
- editable: false,
- },
- {
- label: '发起单位',
- value: this.currentDetail.department,
- editable: false,
- },
- {
- label: '发起任务时间',
- value: this.currentDetail.startTime,
- editable: false,
- },
- {
- label: '工单内容',
- value: this.currentDetail.content,
- editable: this.currentDetail.status === 0,
- type: 'textarea',
- },
- ];
- },
- detailFields() {
- return [
- {
- label: '工单名称',
- value: this.currentDetail.orderName,
- editable: this.currentDetail.status === 0,
- type: 'input',
- },
- { label: '关键任务', value: this.currentDetail.keyData, editable: false },
- { label: '工单创建人', value: this.currentDetail.creator, editable: false },
- { label: '当前状态', value: this.mapStatus(this.currentDetail.status), editable: false },
- { label: '事件地址', value: this.currentDetail.address, editable: false },
- {
- label: '工单类型',
- value: this.currentDetail.type,
- editable: this.currentDetail.status === 0,
- type: 'select',
- options: this.types,
- },
- { label: '关联算法', value: this.currentDetail.aiType, editable: false },
- { label: '发起单位', value: this.currentDetail.department, editable: false },
- { label: '发起任务时间', value: this.currentDetail.startTime, editable: false },
- {
- label: '工单内容',
- value: this.currentDetail.content,
- editable: this.currentDetail.status === 0,
- type: 'textarea',
- },
- ];
- },
- formattedDetailFields() {
- const fields = [
- { label: '工单名称', value: this.currentDetail.orderName },
- {
- label: '工单类型',
- // 修改这里:使用 types 数组查找对应的 label
- value:
- this.types.find(t => t.value === this.currentDetail.type)?.label ||
- this.currentDetail.type,
- },
- { label: '关联任务', value: this.currentDetail.job_name || '/' },
- { label: '工单创建人', value: this.currentDetail.creator },
- { label: '当前状态', value: this.mapStatus(this.currentDetail.status) },
- { label: '事件地址', value: this.currentDetail.address || this.currentDetail.latAndLon }, // 包含经纬度信息
- {
- label: '关联算法',
- value:
- this.algorithms.find(t => t.value === this.currentDetail.aiType)?.label ||
- this.currentDetail.aiType,
- },
- { label: '发起单位', value: this.currentDetail.department },
- { label: '发起任务时间', value: this.currentDetail.startTime },
- { label: '工单内容', value: this.currentDetail.content },
- ];
-
- // 过滤掉值为 '/' 的列
- const filteredFields = fields.filter(field => field.value !== '/');
- // 将字段分成两列
- const formattedFields = [];
- for (let i = 0; i < filteredFields.length; i += 2) {
- formattedFields.push({
- label1: filteredFields[i]?.label || '',
- value1: filteredFields[i]?.value || (filteredFields[i]?.label ? '暂无数据' : ''),
- label2: filteredFields[i + 1]?.label || '',
- value2: filteredFields[i + 1]?.value || (filteredFields[i + 1]?.label ? '暂无数据' : ''),
- });
- }
-
- return formattedFields;
- },
- dynamicFixedStatuses() {
- // 直接使用接口返回的 stepInfos
- return this.stepInfos.map(step => String(step.status));
- },
- ...mapGetters(['userInfo', 'permission']),
- // 动态过滤tabs,保证isShow为true/false
- filteredTabs() {
- // 统一处理权限,undefined视为false
- const tabStatus = this.permission?.tickets_tab_status === true;
- const tabPending = this.permission?.tickets_tab_pending === true;
- const tabMyTickets = this.permission?.tickets_tab_mytickets === true;
- return this.tabs
- .map(tab => {
- if (tab.name === 'all') {
- return { ...tab, isShow: true };
- }
- if (tab.name === 'pending') {
- return { ...tab, isShow: tabPending };
- }
- if (['processing', 'inProgress', 'completed', 'closed'].includes(tab.name)) {
- return { ...tab, isShow: tabStatus };
- }
- if (tab.name === 'myTickets') {
- return { ...tab, isShow: tabMyTickets };
- }
- return { ...tab, isShow: false };
- })
- .filter(tab => tab.isShow);
- },
- permissionList() {
- // 可根据实际后端权限key调整
+ const { total, records } = response.data.data
+ let filteredRecords = records
+ tableData.value = filteredRecords.map(item => {
+ const longitude = Number(item.longitude) || 0
+ const latitude = Number(item.latitude) || 0
return {
- addBtn: this.validData(this.permission.tickets_add, false),
- delBtn: this.validData(this.permission.tickets_delete, false),
- exportBtn: this.validData(this.permission.tickets_export, false),
- reviewBtn: this.validData(this.permission.tickets_review, false),
- };
- },
- stepStatusList() {
- // “我发起的工单”tab用默认流程,其它tab用接口返回的stepInfos
- if (this.activeTab === 'myTickets') {
- if (this.workType === 1) {
- return ['3', '4'];
- }
- return this.fixedStatuses;
+ id: item.id,
+ orderNumber: item.event_num,
+ orderName: item.event_name,
+ department: departments.value.find(d => d.value === item.dept_id)?.label || item.dept_name,
+ startTime: item.job_create_time || '/',
+ aiType: item.ai_types,
+ content: item.content,
+ type: item.work_order_type_dict_key,
+ keyData: !isNaN(longitude) && !isNaN(latitude)
+ ? `${longitude.toFixed(6)}, ${latitude.toFixed(6)}`
+ : '未知位置',
+ address: item.address,
+ creator: item.event_num?.slice(0, 2) === 'AI' ? '智飞agent' : item.create_user,
+ handler: item.update_user || '未分配',
+ status: Number(item.status || 0),
+ photo_url: item.photo_url || '',
+ video_url: item.video_url || '',
+ location: !isNaN(longitude) && !isNaN(latitude) ? [longitude, latitude] : null,
+ processing_details: item.processing_details || '',
+ update_photo_url: item.update_photo_url || '',
+ work_type: item.work_type !== undefined ? Number(item.work_type) : 0,
+ job_name: item.job_name || '',
+ job_create_time: item.job_create_time || '',
+ isReview: item.is_review,
+ handle_user_id:item.handle_user_id
+
}
+ })
- // 其它tab直接用接口返回的stepInfos
- return this.stepInfos.map(step => String(step.status));
- },
-
- showIsReviewText() {
- return row => {
- if (['4'].includes(String(row.status))) return row.isReview === 1 ? '是' : '否';
-
- return '/';
- };
- },
-
- popupShowImage() {
- return list => {
- return list.map(item => ({
+ page.total = total || 0
+ if (isShowInfo.value) {
+ handleViewDetail(firstRowData.value)
+ isShowInfo.value = false
+ }
+ await fetchTabCounts()
+ } catch (error) {
+ console.error('获取数据失败:', error)
+ tableData.value = []
+ page.total = 0
+ } finally {
+ loading.value = false
+ isFetching.value = false
+ }
+}
+// 获取工单类型标签
+function getTypeLabel(typeValue) {
+ const typeObj = types.value.find(item => item.value === typeValue)
+ return typeObj ? typeObj.label : typeValue
+}
+function handleCheck(data, { checkedKeys: keys, checkedNodes }) {
+ checkedKeys.value = keys
+ const selectedDictKeys = checkedNodes.map(node => node.dictKey).filter(Boolean)
+ filters.type = selectedDictKeys
+ fetchTableData()
+}
+// 算法
+function getAlgorithmList() {
+ getSFDictionaryTree({ code: 'SF' }).then(res => {
+ if (res.data.code === 200) {
+ const result = res.data.data[0].children
+ const filteredData = result.map(item => {
+ const children = item.children?.map(child => ({
+ ...child,
+ children: [],
+ }))
+ return {
...item,
- url: getShowImg(item.url),
- }));
- };
- },
- },
-
- methods: {
- handleCheck(data, { checkedKeys, checkedNodes }) {
- this.checkedKeys = checkedKeys;
- // 获取所有选中节点的 dictKey
- const selectedDictKeys = checkedNodes.map(node => node.dictKey).filter(Boolean);
- this.filters.type = selectedDictKeys;
- this.fetchTableData();
- },
- // 算法
- getAlgorithmList() {
- getSFDictionaryTree({ code: 'SF' }).then(res => {
- if (res.data.code === 200) {
- const result = res.data.data[0].children;
- // 过滤第一层数据
- const filteredData = result.map(item => {
- // 过滤第二层数据
- const children = item.children?.map(child => ({
- ...child,
- children: [], // 清空第三层数据
- }));
- return {
- ...item,
- children: children || [],
- };
- });
- this.dataList = filteredData;
+ children: children || [],
}
- });
- },
-
- handleSFNodeClick(data) {
- this.filters.type = '';
- this.filters.algorithm = '';
- this.filters.type = data.dictKey;
- // 更新列表请求
- this.fetchTableData();
- },
- handleClear() {
- this.dictKey = '';
- this.filters.algorithm = '';
- this.filters.type = '';
- this.fetchTableData();
- },
- handleCellClick(row, column) {
- console.log(row, column.no);
- if (column.no === 2) {
- navigator.clipboard.writeText(row.orderNumber).then(() => {
- this.$message.success('复制工单编号成功');
- });
- } else if (column.no === 3) {
- navigator.clipboard.writeText(row.orderName).then(() => {
- this.$message.success('复制工单名称成功');
- });
- } else if (column.no === 4) {
- navigator.clipboard.writeText(row.department).then(() => {
- this.$message.success('复制所属部门成功');
- });
- }
- },
- // 左切换
- leftClick() {
- if (this.tableData.length === 0) return;
- this.currentIndex = Math.max(0, this.currentIndex - 1);
- this.updateCurrentDetail();
- },
- // 右切换
- rightClick() {
- if (this.tableData.length === 0) return;
- this.currentIndex = Math.min(this.tableData.length - 1, this.currentIndex + 1);
- this.updateCurrentDetail();
- },
- // 更新当前详情
- updateCurrentDetail() {
- this.currentDetail = this.tableData[this.currentIndex];
- this.currentDetail.mediaUrl = this.currentDetail.photo_url;
- this.currentDetail.updatePhotoUrl = this.currentDetail.update_photo_url;
- this.currentDetail.processingDetail = this.currentDetail.content;
- this.handleViewDetail(this.currentDetail);
-
- // 如果使用地图组件,需要更新地图标记
- this.$nextTick(() => {
- if (this.$refs.MapContainer && this.currentDetail.location) {
- this.$refs.MapContainer.initAddEntity('point', this.currentDetail.location);
- }
- });
- },
- async handleQRCode(val) {
- val.showQR = !val.showQR;
- },
- async loadAMapScripts() {
- try {
- const areaCode = this.userInfo.detail.areaCode;
- const subAreaCode = areaCode ? areaCode.substring(0, 6) : '';
- const adcodeObj = getAdcodeObj(geoJson, 'adcode', subAreaCode);
-
- // console.log('区域代码:', subAreaCode);
-
- // 直接从返回对象中获取正确的路径
- const center = adcodeObj?.payload?.objects?.collection?.geometries?.[0]?.properties?.center;
-
- if (Array.isArray(center) && center.length === 2) {
- this.inputMapShowDefaultCenter = center;
- } else {
- // 如果找不到中心点,尝试使用 bbox 的中心点
- const bbox = adcodeObj?.payload?.bbox;
- if (Array.isArray(bbox) && bbox.length === 4) {
- const centerX = (bbox[0] + bbox[2]) / 2;
- const centerY = (bbox[1] + bbox[3]) / 2;
- this.inputMapShowDefaultCenter = [centerX, centerY];
- } else {
- this.inputMapShowDefaultCenter = [115.861365, 28.621311];
- }
- }
- this.mapParams.center = [...this.inputMapShowDefaultCenter];
-
- this.mapLoaded = true;
- } catch (error) {
- this.$message.error('地图加载失败,请检查网络或API Key配置');
- }
- },
- async handleBatchApprove() {
- try {
- if (this.selections.length === 0) {
- this.$message.warning('没有选中的工单');
- return;
- }
-
- const currentItem = this.selections[this.currentImageIndex - 1];
- if (!currentItem) {
- this.$message.warning('当前工单数据无效');
- return;
- }
-
- await this.$confirm('确认审核通过当前工单?', '提示', {
- confirmButtonText: '确定',
- cancelButtonText: '取消',
- type: 'warning',
- });
-
- const data = {
- id: currentItem.id,
- status: currentItem.status,
- isPass: 0,
- eventNum: currentItem.orderNumber,
- eventName: currentItem.orderName,
- };
-
- const response = await flowEvent(data);
- if (response.data.code === 0) {
- this.$message.success('工单审核通过');
-
- // 创建新的数组而不是修改原数组
- const newSelections = [...this.selections];
- newSelections.splice(this.currentImageIndex - 1, 1);
- this.selections = newSelections;
-
- // 修正索引并更新图片
- if (this.selections.length > 0) {
- if (this.currentImageIndex > this.selections.length) {
- this.currentImageIndex = this.selections.length;
- }
- this.updateCurrentReviewImage();
- } else {
- this.reviewDialogVisible = false;
- this.currentImageIndex = 1;
- this.currentReviewImage = '';
- }
-
- // 刷新表格数据
- this.fetchTableData();
- } else {
- throw new Error(response.data.msg || '审核失败');
- }
- } catch (error) {
- if (error === 'cancel') return;
- this.$message.error(error.message || '审核失败,请稍后重试');
- }
- },
- async fetchDropdownData() {
- try {
- const response = await getTicketInfo();
-
- const { dept_data, event_type, ai_type, info } = response.data.data;
-
- this.departments = dept_data.map(item => ({
- label: item.dept_name,
- value: item.id,
- }));
-
- this.departmentUsers = dept_data.reduce((acc, dept) => {
- acc[dept.id] = dept.user_data || [];
- return acc;
- }, {});
-
- this.types = Object.entries(event_type).map(([key, value]) => ({
- label: value,
- value: key,
- }));
-
- const columnType = this.findObject(this.option.column, 'type');
- columnType.dicData = this.types;
- this.allAlgorithms = info;
-
- console.log('工单类型', this.types);
- console.log('关联算法', this.allAlgorithms);
- // 确保算法数据的映射一致
- this.algorithms =
- ai_type?.map(item => ({
- dict_key: item.dict_key,
- dict_value: item.dict_value,
- // 同时添加 label 和 value 以兼容两处使用
- label: item.dict_value,
- value: item.dict_key,
- })) || [];
-
- this.algorithms2 = _.cloneDeep(this.algorithms);
-
- // 构建用户ID和名称的映射关系
- this.userNameToIdMap = {};
- dept_data.forEach(dept => {
- (dept.user_data || []).forEach(user => {
- this.userNameToIdMap[user.name] = user.id;
- });
- });
- } catch (error) {
- this.$message.error('加载下拉框数据失败');
- }
- },
- // 工单类型变化时触发
- handleTypeChange(typeValue) {
- this.form.algorithm = [];
- if (!typeValue) {
- // 未选择类型时清空算法列表
- this.algorithms2 = [];
- return;
- }
-
- const matchedCategory = this.allAlgorithms.find(category => category.dict_key === typeValue);
-
- if (
- !matchedCategory ||
- !matchedCategory.algorithms ||
- matchedCategory.algorithms.length === 0
- ) {
- // 无匹配的算法时清空
- this.algorithms2 = [];
- this.$message.info('该工单类型暂无关联算法');
- return;
- }
- this.algorithms2 = matchedCategory.algorithms.map(algo => ({
- label: algo.dict_value,
- value: algo.dict_key,
- dict_key: algo.dict_key,
- dict_value: algo.dict_value,
- }));
- },
-
- async fetchTableData() {
- if (this.isFetching) return;
- this.isFetching = true;
- this.loading = true;
- try {
- const currentTab = this.tabs.find(tab => tab.name === this.activeTab);
- const params = {
- // word_order_types: this.filters.type || undefined,
- ai_types: this.filters.type || undefined, // 算法使用该字段筛选
- status:
- currentTab?.name === 'myTickets'
- ? undefined
- : this.filters.status !== ''
- ? Number(this.filters.status)
- : currentTab?.value,
- event_name: this.filters.keyword || undefined,
- dept_id: this.filters.department || undefined,
- start_date: this.filters.dateRange?.[0]
- ? this.formatDate(this.filters.dateRange[0])
- : undefined,
- end_date: this.filters.dateRange?.[1]
- ? this.formatDate(this.filters.dateRange[1]).replace('00:00:00', '23:59:59')
- : undefined,
- current: Number(this.page.currentPage), // 使用当前页码
- size: Number(this.page.pageSize), // 使用每页条数
- ai_type: this.filters.algorithm || undefined, // 添加算法参数
- // 添加 is_draft 参数,仅在"我发起的"标签页时设置为1
- 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, // 添加复核状态查询参数
- source:1 ,//数据来源
- };
-
- const response = await getList(params);
- if (!response?.data?.data?.records) {
- throw new Error('接口返回数据格式不正确');
- }
-
- const { total, records } = response.data.data;
- let filteredRecords = records;
-
- // 如果是"我发起的"tab,过滤数据
- // if (currentTab?.name === 'myTickets') {
- // filteredRecords = records.filter(item =>
- // String(item.create_user_id) === String(item.user_id)
- // );
- // }
-
- this.tableData = filteredRecords.map(item => {
- const longitude = Number(item.longitude) || 0;
- const latitude = Number(item.latitude) || 0;
- return {
- id: item.id,
- orderNumber: item.event_num, // 修改这里:优先使用 event_num
- orderName: item.event_name,
- department:
- this.departments.find(d => d.value === item.dept_id)?.label || item.dept_name,
- startTime: item.job_create_time || '/',
- aiType: item.ai_types,
- content: item.content, // 将后端返回的 content 映射为 content
- type: item.work_order_type_dict_key,
- keyData:
- !isNaN(longitude) && !isNaN(latitude)
- ? `${longitude.toFixed(6)}, ${latitude.toFixed(6)}`
- : '未知位置',
- address: item.address,
- creator: item.event_num?.slice(0, 2) === 'AI' ? '智飞agent' : item.create_user,
- handler: item.update_user || '未分配',
- status: Number(item.status || 0),
- // 保存原始字段
- photo_url: item.photo_url || '', // 保存原始 photo_url
- video_url: item.video_url || '', // 保存原始 video_url
- location: !isNaN(longitude) && !isNaN(latitude) ? [longitude, latitude] : null,
- processing_details: item.processing_details || '', // 添加处理详情字段
- update_photo_url: item.update_photo_url || '', // 添加处理图片字段
- 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, // 添加复核状态字段映射
-
-
- };
- });
-
- // 更新总数显示
- this.page.total = total || 0;
-
- // 是否弹出详情页
- if (this.isShowInfo) {
- this.handleViewDetail(this.firstRowData);
- this.isShowInfo = false; // 生效一次
- }
- await this.fetchTabCounts();
- } catch (error) {
- this.$message.error(error.message || '获取数据失败');
- this.tableData = [];
- this.page.total = 0;
- } finally {
- this.loading = false;
- this.isFetching = false;
- }
- },
-
- async submitForm() {
- if (this.submitLoading) return; // 防止重复提交
- this.submitLoading = true;
- try {
- // 提交时需要完整验证
- await this.$refs.form.validate();
-
- // 验证位置信息
- if (!this.form.location || this.form.location.length < 2) {
- this.$message.warning('请在地图上选择位置');
- return;
- }
-
- // 修改图片验证逻辑
- if (!this.form.photos || this.form.photos.length === 0) {
- this.$message.warning('请上传工单图片');
- return;
- }
-
- let [lng, lat] = this.disposeLocation(true, this.form);
-
- const submitData = {
- eventName: this.form.name,
- content: this.form.content,
- workType: '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], // 传数组
- updateUser: this.form.handler,
- createDept: this.form.department,
- isDraft: 0,
- };
-
- if (this.form.id) {
- submitData.id = this.form.id;
- }
-
- // 修改获取文件的逻辑
- let file = null;
- const photoInfo = this.form.photos[0];
-
- if (photoInfo.raw) {
- // 如果有新上传的文件,使用新文件
- file = photoInfo.raw;
- } else if (photoInfo.existingUrl) {
- // 如果是已存在的图片,将URL添加到提交数据中
- submitData.photoUrl = photoInfo.existingUrl;
- } else {
- this.$message.warning('图片文件无效,请重新上传');
- return;
- }
-
- const response = await createTicket(submitData, file);
- if (response.data.code === 0) {
- this.$message.success('工单创建成功');
- this.dialogVisible = false;
- this.fetchTableData();
- } else {
- throw new Error(response.data.msg || '创建失败');
- }
- } catch (error) {
- if (error.message.includes('验证未通过')) {
- this.$message.warning('请填写完整的工单信息');
- } else {
- this.$message.error(error.message || '工单创建失败,请稍后重试');
- }
- } finally {
- this.submitLoading = false;
- }
- },
-
- async saveDraft() {
- if (this.draftLoading) return; // 防止重复提交
- this.draftLoading = true;
- try {
- let handlerValue = this.form.handler;
- if (!handlerValue || handlerValue === '未分配') {
- handlerValue = undefined;
- }
-
- // 验证位置信息
- if (
- !this.form.location ||
- this.form.location.length < 2 ||
- !this.form.location[0] ||
- !this.form.location[1]
- ) {
- this.$message.warning('请在地图上选择位置');
- return;
- }
-
- 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: lng,
- latitude: lat,
- address: this.form.address || undefined,
- workOrderTypeDictKey: this.form.type || undefined,
- aiType:
- this.form.algorithm && this.form.algorithm.length > 0 ? this.form.algorithm : undefined, // 传数组
- updateUser: handlerValue,
- createDept: this.form.department || undefined,
- isDraft: 1,
- };
-
- // 草稿时也至少需要工单名称
- if (!submitData.eventName) {
- this.$message.warning('请输入工单名称');
- return;
- }
-
- // 过滤掉所有 undefined 的字段
- Object.keys(submitData).forEach(
- key => submitData[key] === undefined && delete submitData[key]
- );
-
- let file = null;
- if (this.form.photos && this.form.photos.length > 0) {
- file = this.form.photos[0].raw;
- }
-
- const response = await createTicket(submitData, file);
- if (response.data.code === 0) {
- this.$message.success('草稿保存成功');
- this.dialogVisible = false;
- (this.form = {
- name: '',
- type: '',
- department: '',
- handler: '',
- algorithm: [], // 关联算法改为数组
- location: [], // 将存储为[经度, 纬度, 地址]格式
- address: '',
- photos: [],
- content: '', // 新增字段,用于存储后端返回的 content
- }),
- this.fetchTableData();
- } else {
- throw new Error(response.data.msg || '保存失败');
- }
- } catch (error) {
- this.$message.error(error.message || '保存草稿失败,请稍后重试');
- } finally {
- this.draftLoading = false;
- }
- },
- handleCancel() {
- this.resetForm();
- this.dialogVisible = false;
- },
- handleLocationChange(val) {
- let locationValue = val.value;
- if (locationValue && locationValue.length >= 2) {
- // 兼容第三项为地址
- const [lng, lat] = gcj02ToWgs84(locationValue[0], locationValue[1]);
- this.form.location = [Number(lng), Number(lat), locationValue[2] || ''];
- this.form.address = locationValue[2] || '';
- } else {
- this.form.location = [];
- this.form.address = '';
- }
- },
-
- 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`;
- },
-
- mapStatus(status) {
- const statusTextMap = {
- '-1': '草稿', // 添加草稿状态
- 2: '待审核',
- 0: '待处理',
- 3: '处理中',
- 4: '已完成',
- };
- return statusTextMap[status] || '未知状态';
- },
-
- getStatusTagType(status) {
- // 草稿不加颜色
- if (status === -1 || status === '-1') return '';
- // 状态颜色映射
- const colorMap = {
- 0: '#FF7411', // 待处理-淡红
- 3: '#FFC300', // 处理中-更淡红
- 2: '#FF472F', // 待审核-橙色
- 4: '#0291A1', // 已完成-淡蓝
- };
- return colorMap[String(status)] || '';
- },
-
- async fetchTabCounts() {
- try {
- // 判断是否有部门筛选
- let params = {};
- if (this.filters.department) {
- params.deptId = this.filters.department;
- }
- const response = await getstatusCount(params);
- const { statusCount, totalCount, userCount } = response.data.data;
- this.tabs.forEach(tab => {
- if (tab.name === 'all') {
- tab.count = totalCount || 0; // 总工单数
- } else if (tab.name === 'myTickets') {
- tab.count = userCount || 0; // 我发起的工单数
- } else {
- tab.count = statusCount[String(tab.value)] || 0; // 根据状态值映射
- }
- });
- } catch (error) {
- this.$message.error('获取 tab 数据失败');
- }
- },
-
- 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();
- this.fetchTabCounts(); // 切换 tab 时重新获取数据
- },
- //新增 跳转后触发的Tab切换
- handleTabChangeAfterJump() {
- const isReview = this.findObject(this.option.column, 'isReview');
- isReview.hide = !['all', 'completed', 'myTickets'].includes(this.activeTab);
- this.handleReset();
- this.page.currentPage = 1;
- this.fetchTableData();
- this.fetchTabCounts();
- },
- handleSearch() {
- this.page.currentPage = 1;
- this.fetchTableData();
- this.fetchTabCounts();
- },
-
- handleReset() {
- this.dictKey = '';
- this.filters = {
- keyword: '',
- department: '',
- type: '',
- dateRange: [],
- status: '',
- algorithm: '', // 重置时清空算法筛选
- isReview: '', // 重置时清空复核状态
- };
- this.page.currentPage = 1;
- this.$router.replace({}); //清除url参数
- this.fetchTableData();
- },
- handleKeyWords() {
- this.$router.replace({}); //清除url参数
- },
-
- async handleCurrentChange(val) {
- // 先更新页码
- this.page.currentPage = val;
- // 等待 DOM 更新后再请求数据
- await this.$nextTick();
- await this.fetchTableData();
- },
-
- async sizeChange(val) {
- this.page.pageSize = val;
- this.page.currentPage = 1; // 重置到第一页
- await this.$nextTick();
- await this.fetchTableData();
- },
-
- handleAdd() {
- this.createoredit = 1;
- this.dialogVisible = true;
- this.mapParams.center = [...this.inputMapShowDefaultCenter];
- this.form.location = [];
- },
-
- resetForm() {
- this.form = {
- name: '',
- type: '',
- department: '',
- handler: '',
- algorithm: [], // 关联算法改为数组
- location: [], // 将存储为[经度, 纬度, 地址]格式
- address: '',
- content: '',
- photos: [],
- content: '', // 新增字段,用于存储后端返回的 content
- };
- if (this.$refs.form) {
- this.$refs.form.resetFields();
- }
- },
-
- formatLocation(location) {
- if (!Array.isArray(location)) {
- return '未知位置';
- }
- return `${location[0].toFixed(6)}, ${location[1].toFixed(6)}`;
- },
-
- async handleViewDetail(row) {
- // 找到当前行在tableData中的索引
- this.currentIndex = this.tableData.findIndex(item => item.id === row.id);
- // 先设置workType,直接从row读取
- this.workType = row.work_type !== undefined ? Number(row.work_type) : 0;
-
- // 重置上传组件的文件列表
- this.$nextTick(() => {
- if (this.$refs.MapContainer && this.$refs.MapContainer.initAddEntity) {
- this.$refs.MapContainer.initAddEntity('point', this.currentDetail.location);
- }
- });
-
- const detailData = {
- ...row,
- processingDetail: row.processing_details || '',
- mediaUrl: row.photo_url || row.video_url || '',
- updatePhotoUrl: row.update_photo_url || '',
- photos: [],
- job_name: row.job_name || '', // 新增
- };
-
- let stepArr = [];
- try {
- const stepResponse = await getStepInfo(row.orderNumber);
- const steps = Array.isArray(stepResponse.data.data)
- ? stepResponse.data.data
- : stepResponse.data.data?.steps || [];
- 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 => ({
- status: String(step.status),
- name: step.name,
- time: step.time,
- create_time: step.create_time,
- }));
- } else {
- const statusArr = this.workType === 1 ? ['3', '4'] : this.fixedStatuses;
- this.stepInfos = statusArr.map(status => {
- const step = steps.find(s => String(s.status) === String(status));
- return {
- status,
- name: step ? step.name : '',
- time: step ? step.time : null,
- create_time: step ? step.create_time : null,
- };
- });
- }
- this.currentDetail.status = row.status;
- } catch (error) {
- if (this.activeTab === 'myTickets') {
- const statusArr = this.workType === 1 ? ['3', '4'] : this.fixedStatuses;
- this.stepInfos = statusArr.map(status => ({
- status,
- name: status === row.status ? row.handler || '未分配' : '未处理',
- time: status === row.status ? row.startTime || '未知时间' : null,
- }));
- } else {
- this.stepInfos = [];
- }
- }
- console.log(detailData, 'detailDatadetailDatadetailData');
- this.currentDetail = {
- ...detailData,
- // address: null,
- showQR: false,
- latAndLon: _.round(detailData.location[0], 6) + ',' + _.round(detailData.location[1], 6),
- };
-
- console.log('this.currentDetail', this.currentDetail);
-
- this.detailVisible = true;
- this.handleTypeChange(this.currentDetail.type);
-
-
- this.$nextTick(() => {
- if (this.$refs.MapContainer && this.$refs.MapContainer.initAddEntity) {
- this.$refs.MapContainer.initAddEntity('point', this.currentDetail.location);
- }
- });
- },
-
- getStepHandler(status) {
- const step = this.stepInfos.find(step => step.status === status);
- return step ? step.name : '';
- },
-
- getStepTime(status) {
- const step = this.stepInfos.find(step => step.status === status);
- // 如果 step 不存在或 step.time 为 0,返回 false
- return step && step.time && step.time !== '0' ? step.time : false;
- },
- getStepCreateTime(status) {
- const step = this.stepInfos.find(step => step.status === status);
- return step ? step.create_time : null;
- },
- getActiveStep() {
- // 步骤索引适配
- const arr = this.stepStatusList;
- const index = arr.indexOf(String(this.currentDetail.status));
- return index !== -1 ? index + 2 : 1;
- },
-
- openMap() {
- const areaCode = this.userInfo.detail.areaCode;
- const subAreaCode = areaCode ? areaCode.substring(0, 6) : '';
- getAdcodeObj(geoJson, 'adcode', subAreaCode);
- this.$message.info('地图选址功能暂未实现');
- },
-
- handlePreview(file) {
- this.$message.info(`预览图片:${file.name}`);
- },
-
- handleRemove(file) {
- this.$message.info(`移除图片:${file.name}`);
- },
-
- refreshChange() {
- if (this.isFetching) return;
- this.fetchTableData();
- },
-
- onLoad() {
- if (this.isFetching) return;
- this.fetchTableData();
- },
-
- async exportData() {
- try {
- this.loading = true;
- let exportData = [];
-
- // 如果有选中的数据,则导出选中的数据
- if (this.selections.length > 0) {
- exportData = this.selections.map(item => this.formatExportItem(item));
- } else {
- // 没有选中数据时,导出当前页面的数据
- exportData = this.tableData.map(item => this.formatExportItem(item));
- }
-
- if (exportData.length === 0) {
- this.$message.warning('没有数据可供导出');
- return;
- }
-
- const headers = [
- '工单编号',
- '工单名称',
- '所属部门',
- '发起时间',
- '关联算法',
- '工单内容',
- '工单类型',
- '经纬度',
- '创建人',
- '处理人',
- '工单状态',
- ];
-
- export_json_to_excel(headers, exportData, '工单数据');
- this.$message.success('数据导出成功');
- } catch (error) {
- // console.error('导出失败:', error);
- this.$message.error(error.message || '导出失败,请稍后重试');
- } finally {
- this.loading = false;
- }
- },
-
- formatExportItem(item) {
- const longitude = Number(item.longitude) || Number(item.location?.[0]) || 0;
- const latitude = Number(item.latitude) || Number(item.location?.[1]) || 0;
-
- return {
- 工单编号: item.orderNumber || item.event_num || '',
- 工单名称: item.orderName || item.event_name || '',
- 所属部门: item.department || item.dept_name || '',
- 发起时间: item.startTime || item.create_time || '',
- 关联算法: item.aiType || item.ai_types || '',
- 工单内容: item.address || item.content || '',
- 工单类型:
- this.types.find(t => t.value === (item.type || item.work_order_type_dict_key))?.label ||
- '',
- 经纬度:
- !isNaN(longitude) && !isNaN(latitude)
- ? `${longitude.toFixed(6)}, ${latitude.toFixed(6)}`
- : '',
- 创建人: item.creator || item.create_user || '',
- 处理人: item.handler || item.update_user || '',
- 工单状态: this.mapStatus(Number(item.status || 0)),
- };
- },
-
- handleDepartmentChange(deptId) {
- this.form.handler = '';
- },
-
- handleDispatchDepartmentChange(deptId) {
- this.dispatchForm.handler = ''; // 清空处理人选择
- },
-
- // 文件改变时的钩子
- handleFileChange(file, fileList) {
- // 保持最新的文件列表
- this.form.photos = fileList.map(item => ({
- ...item,
- existingUrl: item.url && !item.raw ? item.url : null, // 标记已存在的图片URL
- }));
- this.currentDetail.photos = this.form.photos;
- },
-
- // 文件移除时的钩子
- handleUploadRemove(file, fileList) {
- this.form.photos = fileList;
- this.currentDetail.photos = fileList;
- },
-
- // 上传前的验证
- beforeUpload(file) {
- const isImage = file.type.includes('image');
- const isLt5M = file.size / 1024 / 1024 < 5;
-
- if (!isImage) {
- this.$message.error('只能上传图片文件!');
- return false;
- }
- if (!isLt5M) {
- this.$message.error('图片大小不能超过5MB!');
- return false;
- }
- return true;
- },
-
- async approveTicket() {
- if (this.approveLoading) return;
- this.approveLoading = true;
- try {
- const data = {
- id: this.currentDetail.id,
- status: this.currentDetail.status,
- isPass: 0, // 0 表示通过
- eventNum: this.currentDetail.orderNumber,
- };
-
- const file = this.currentDetail.file || null; // 如果没有文件,则为 null
-
- const response = await flowEvent(data, file);
- if (response.data.code === 0) {
- this.$message.success('工单已通过');
- this.detailVisible = false;
- this.fetchTableData();
- } else {
- throw new Error(response.data.msg || '操作失败');
- }
- } catch (error) {
- this.$message.error(error.message || '操作失败,请稍后重试');
- } finally {
- this.approveLoading = false;
- }
- },
- async rejectTicket() {
- if (this.rejectLoading) return;
- this.rejectLoading = true;
- try {
- const data = {
- id: this.currentDetail.id,
- status: this.currentDetail.status,
- isPass: 1, // 1 表示不通过
- };
-
- const response = await flowEvent(data);
- if (response.data.code === 0) {
- this.$message.success('工单未通过');
- this.detailVisible = false;
- this.fetchTableData();
- } else {
- throw new Error(response.data.msg || '操作失败');
- }
- } catch (error) {
- this.$message.error(error.message || '操作失败,请稍后重试');
- } finally {
- this.rejectLoading = false;
- }
- },
- async submitProcessing() {
- if (this.currentDetail.status !== 3) {
- this.$message.warning('只有处理中状态的工单可以提交处理详情');
- return;
- }
-
- try {
- const data = {
- id: this.currentDetail.id, // 当前工单 ID
- status: this.currentDetail.status, // 当前工单状态
- processing_details: this.currentDetail.processingDetail, // 事件处理详情
- };
-
- // 如果有图片,添加 file 参数
- const file = this.currentDetail.photos?.[0]?.raw || null;
-
- const response = await flowEvent(data, file);
- if (response.data.code === 0) {
- this.$message.success('处理详情提交成功');
- this.detailVisible = false;
- this.fetchTableData();
- } else {
- throw new Error(response.data.msg || '提交失败');
- }
- } catch (error) {
- console.error('处理详情提交失败:', error);
- this.$message.error(error.message || '提交失败,请稍后重试');
- }
- },
- markAsCompleted() {
- this.$message.success('工单已标记为完成');
- },
- async completeTicket() {
- if (this.completeLoading) return;
- this.completeLoading = true;
- try {
- if (!this.currentDetail.processingDetail) {
- 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,
- status: this.currentDetail.status,
- processingDetails: this.currentDetail.processingDetail,
- eventNum: this.currentDetail.orderNumber,
- };
-
- // 如果有上传的图片,添加到请求中
- const file = this.currentDetail.photos?.[0]?.raw || null;
-
- const response = await flowEvent(data, file);
-
- if (response.data.code === 0) {
- this.$message.success('工单已完成');
- this.detailVisible = false;
- this.fetchTableData(); // 刷新列表数据
- } else {
- throw new Error(response.data.msg || '操作失败');
- }
- } catch (error) {
- console.error('完成工单失败:', error);
- this.$message.error(error.message || '操作失败,请稍后重试');
- } finally {
- this.completeLoading = false;
- }
- },
- async approveAndDispatch() {
- if (this.dispatchLoading) return;
-
- // 添加必填项检查
- if (!this.currentDetail.orderName || !this.currentDetail.orderName.trim()) {
- this.$message.warning('请填写工单名称');
- return;
- }
- if (!this.currentDetail.aiType) {
- this.$message.warning('请选择关联算法');
- return;
- }
- if (!this.currentDetail.content || !this.currentDetail.content.trim()) {
- this.$message.warning('请填写工单内容');
- return;
- }
-
- // 通过验证后,打开派发对话框
- this.dispatchDialogVisible = true;
-
- console.log('受理', this.currentDetail);
- },
- hasProcessingBtnPermission() {
- // undefined 或 false 都返回 false,只有 true 返回 true
- // console.log('权限检查:', this.permission.tickets_processing_btn)
- return this.permission && this.permission.tickets_processing_btn === true;
- },
- hasProcessedAndOverBtnPermission() {
- // undefined 或 false 都返回 false,只有 true 返回 true
- //console.log('权限检查:', this.permission)
- return this.permission && this.permission.tickets_view_processedAndOver === true;
- },
- hasReviewBtnPermission() {
- // undefined 或 false 都返回 false,只有 true 返回 true
- // console.log('权限检查:', this.permission)
- return this.permission && this.permission.tickets_review_btn === true;
- },
-
- async submitDispatch() {
- if (this.dispatchLoading) return;
- if (!this.currentDetail.orderName || !this.currentDetail.orderName.trim()) {
- this.$message.warning('请填写工单名称');
- return;
- }
- if (!this.currentDetail.type) {
- this.$message.warning('请选择工单类型');
- return;
- }
- if (!this.currentDetail.content || !this.currentDetail.content.trim()) {
- this.$message.warning('请填写工单内容');
- return;
- }
- if (!this.dispatchForm.department) {
- this.$message.warning('请选择部门');
- return;
- }
- if (!this.dispatchForm.handler) {
- this.$message.warning('请选择处理人');
- return;
- }
- this.dispatchLoading = true;
- console.log('派发成功', this.currentDetail);
- const isValue = this.algorithms.some(item => item.value === this.currentDetail.aiType);
- let resultValue;
-
- if (isValue) {
- // 如果已经是value,直接使用
- resultValue = this.currentDetail.aiType;
- } else {
- // 否则查找对应的value
- const matched = this.algorithms.find(
- item => item.dict_value === this.currentDetail.aiType ||
- item.label === this.currentDetail.aiType
- );
- resultValue = matched ? matched.value : null;
- }
-
-// console.log('resultValue',resultValue);
- this.$refs.dispatchForm.validate(async valid => {
- if (valid) {
- try {
- const data = {
- id: this.currentDetail.id,
- status: this.currentDetail.status,
- isPass: 0, // 0 表示通过
- eventName: this.currentDetail.orderName, // 工单名称
- eventNum: this.currentDetail.orderNumber,
- workOrderTypeDictKey: this.currentDetail.type, // 直接使用原始的 dict_key
- content: this.currentDetail.content, // 使用 content 替代原来的 remark
- createDept: this.dispatchForm.department, // 派发部门 ID
- updateUser: this.dispatchForm.handler, // 处理人 ID
- aiType:resultValue,
- };
- console.log('派发', data);
-
- const file = this.currentDetail.file || null; // 如果没有文件,则为 null
-
- const response = await flowEvent(data, file);
- if (response.data.code === 0) {
- this.$message.success('工单已成功派发');
- this.dispatchDialogVisible = false;
- this.detailVisible = false;
- this.fetchTableData();
- } else {
- throw new Error(response.data.msg || '派发失败');
- }
- } catch (error) {
- console.error('派发失败:', error);
- this.$message.error(error.message || '派发失败,请稍后重试');
- } finally {
- this.dispatchLoading = false;
- }
- } else {
- this.dispatchLoading = false;
- }
- });
- },
- async finalizeTicket() {
- if (this.finalizeLoading) return;
- this.finalizeLoading = true;
- try {
- // 检查是否上传了图片
- if (!this.currentDetail.photos || !this.currentDetail.photos.length) {
- this.$message.warning('请上传事件处理照片,或飞行任务结束核验工单是否完结');
- return;
- }
-
- const data = {
- id: this.currentDetail.id,
- status: this.currentDetail.status,
- eventNum: this.currentDetail.orderNumber,
- };
-
- const file = this.currentDetail.photos[0].raw;
-
- const response = await flowEvent(data, file);
-
- if (response.data.code === 0) {
- this.$message.success('工单已完结');
- this.detailVisible = false;
- this.fetchTableData();
- } else {
- throw new Error(response.data.msg || '操作失败');
- }
- } catch (error) {
- console.error('完结工单失败:', error);
- this.$message.error(error.message || '操作失败,请稍后重试');
- } finally {
- this.finalizeLoading = false;
- }
- },
-
- // 添加编辑方法
- handleEdit(row) {
- this.createoredit = 2;
- console.log('编辑原始数据:', row);
-
- // 尝试从row.dept_id或通过部门名称查找对应的部门ID
- let deptId = row.dept_id; // 优先使用原始数据中的dept_id
-
- // 如果没有dept_id,则通过部门名称查找
- if (!deptId) {
- // 输出所有可用的部门数据用于调试
- console.log('可用部门列表:', this.departments);
- console.log('当前部门名称:', row.department);
-
- const deptInfo = this.departments.find(
- dept => dept.label && dept.label.trim() === row.department.trim()
- );
- deptId = deptInfo?.value;
- }
-
- // 获取工单类型值 - 从types中找到匹配的值
- const typeValue =
- this.types.find(t => t.value === row.type)?.value || row.work_order_type_dict_key;
- // 获取处理人ID - 使用userNameToIdMap映射
- const handlerId = this.userNameToIdMap[row.handler] || row.handler;
-
- console.log('数据映射:', {
- 部门名称: row.department,
- 找到的部门ID: deptId,
- 原始处理人: row.handler,
- 处理人ID: handlerId,
- 原始工单类型: row.type,
- 映射后类型值: typeValue,
- });
-
- // 修改算法数组的处理逻辑
- let algorithmArr = [];
- if (Array.isArray(row.aiType)) {
- algorithmArr = row.aiType;
- } else if (typeof row.aiType === 'string' && row.aiType) {
- // 首先尝试将字符串按照逗号或顿号分割
- algorithmArr = row.aiType.split(/[,、]/).map(item => item.trim());
- }
-
- // 确保算法值与选项匹配
- algorithmArr = algorithmArr
- .map(item => {
- // 如果是字符串值,尝试找到对应的 dict_key
- if (typeof item === 'string') {
- const matchedAlgorithm = this.algorithms.find(
- algo => algo.dict_value === item || algo.dict_key === item
- );
- return matchedAlgorithm ? matchedAlgorithm.dict_key : item;
- }
- return item;
- })
- .filter(Boolean); // 过滤掉无效值
-
- console.log('算法处理:', {
- 原始值: row.aiType,
- 分割后: algorithmArr,
- });
-
- this.form = {
- id: row.id,
- name: row.orderName,
- type: typeValue,
- department: deptId,
- handler: handlerId,
- algorithm: algorithmArr,
- location:
- Array.isArray(row.location) && row.location.length >= 2
- ? [Number(row.location[0]), Number(row.location[1])]
- : [],
- address: row.address || '',
- content: row.content,
- photos: [],
- };
-
- 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) {
- this.mapParams.center = [Number(this.form.location[0]), Number(this.form.location[1])];
- } else {
- this.mapParams.center = [...this.inputMapShowDefaultCenter];
- }
- // 如果有图片,添加到表单中
- if (row.photo_url) {
- // 创建一个带有必要信息的文件对象
- this.form.photos = [
- {
- name: 'existing-photo.jpg', // 添加默认扩展名
- url: row.photo_url, // 用于预览的URL
- status: 'success', // 标记为已上传成功
- raw: null, // 初始化为null
- existingUrl: row.photo_url, // 保存原始URL,用于区分是否为已存在的图片
- },
- ];
- }
-
- // 调试输出
- console.log('编辑表单数据:', {
- 原始算法值: row.aiType,
- 处理后算法值: algorithmArr,
- 表单数据: this.form,
- });
-
- this.dialogVisible = true;
- },
-
- // 添加删除方法
- handleDelete(row) {
- this.$confirm('确认删除该工单?', '提示', {
- confirmButtonText: '确定',
- cancelButtonText: '取消',
- type: 'warning',
})
- .then(async () => {
- try {
- const response = await flowEvent({
- id: row.id,
- status: 0,
- isDelete: 1,
- });
+ dataList.value = filteredData
- if (response.data.code === 0) {
- this.$message.success('删除成功');
- this.fetchTableData();
- } else {
- throw new Error(response.data.msg || '删除失败');
- }
- } catch (error) {
- console.error('删除失败:', error);
- this.$message.error(error.message || '删除失败,请稍后重试');
- }
- })
- .catch(() => {});
- },
-
- // 添加选择变化处理方法
- handleSelectionChange(selection) {
- this.selections = selection;
- console.log('已选择的行:', selection);
- },
-
- // 添加全选方法
- handleSelectAll(val) {
- this.$refs.avueCrud.toggleSelection(val);
- },
-
- // 添加单行选择方法
- handleSelect(selection, row) {
- console.log('选中行变化:', selection, row);
- },
-
- // 如果需要手动选中某些行
- setSelection(rows) {
- this.$nextTick(() => {
- rows.forEach(row => {
- this.$refs.avueCrud.toggleSelection(row, true);
- });
- });
- },
-
- // 清空选择
- clearSelection() {
- this.$refs.avueCrud.clearSelection();
- },
-
- // 打开审核对话框
- openReviewDialog() {
- if (this.selections.length === 0) {
- this.$message.warning('请先选择要审核的工单');
- return;
- }
- this.currentImageIndex = 1;
- this.updateCurrentReviewImage();
- this.reviewDialogVisible = true;
- },
-
- // 更新当前审核图片
- updateCurrentReviewImage() {
- // 修正索引范围
- if (!this.selections || this.selections.length === 0) {
- this.currentReviewImage = '';
- this.currentImageIndex = 1;
- return;
- }
- // 如果当前索引超出范围,自动回退到最后一张
- if (this.currentImageIndex > this.selections.length) {
- this.currentImageIndex = this.selections.length;
- }
- if (this.currentImageIndex < 1) {
- this.currentImageIndex = 1;
- }
- const index = this.currentImageIndex - 1;
- if (index >= 0 && index < this.selections.length) {
- const currentItem = this.selections[index];
- this.currentReviewImage = currentItem.photo_url || '';
- } else {
- this.currentReviewImage = '';
- }
- },
-
- // 处理图片分页变化
- handleImagePageChange(page) {
- if (page > 0 && page <= this.selections.length) {
- this.currentImageIndex = page;
- this.updateCurrentReviewImage();
- }
- },
-
- // 批量审核通过
- async handleBatchApprove() {
- try {
- if (this.selections.length === 0) {
- this.$message.warning('没有选中的工单');
- return;
- }
-
- const currentItem = this.selections[this.currentImageIndex - 1];
- if (!currentItem) {
- this.$message.warning('当前工单数据无效');
- return;
- }
-
- await this.$confirm('确认审核通过当前工单?', '提示', {
- confirmButtonText: '确定',
- cancelButtonText: '取消',
- type: 'warning',
- });
-
- const data = {
- id: currentItem.id,
- status: currentItem.status,
- isPass: 0,
- eventNum: currentItem.orderNumber,
- };
- console.log('删除前:', this.selections);
- const response = await flowEvent(data);
- if (response.data.code === 0) {
- this.$message.success('工单审核通过');
-
- // 创建新的数组而不是修改原数组
- const newSelections = [...this.selections];
- newSelections.splice(this.currentImageIndex - 1, 1);
-
- // 修正索引并更新图片
- if (newSelections.length > 0) {
- if (this.currentImageIndex > this.selections.length) {
- this.currentImageIndex = this.selections.length;
- }
- this.updateCurrentReviewImage();
- } else {
- this.reviewDialogVisible = false;
- this.currentImageIndex = 1;
- this.currentReviewImage = '';
- this.fetchTableData();
- }
-
- // 刷新表格数据
- // this.fetchTableData();
- this.selections = newSelections;
- } else {
- throw new Error(response.data.msg || '审核失败');
- }
- } catch (error) {
- if (error === 'cancel') return;
- this.$message.error(error.message || '审核失败,请稍后重试');
- }
- },
-
- cancleBatchReject() {
- this.reviewDialogVisible = false;
- this.selections = [];
- this.currentImageIndex = 1;
- this.currentReviewImage = '';
- this.fetchTableData();
- },
- // 批量审核不通过
- async handleBatchReject() {
- try {
- if (this.selections.length === 0) {
- this.$message.warning('没有选中的工单');
- return;
- }
-
- const currentItem = this.selections[this.currentImageIndex - 1];
- if (!currentItem) {
- this.$message.warning('当前工单数据无效');
- return;
- }
-
- await this.$confirm('确认该工单审核不通过?', '提示', {
- confirmButtonText: '确定',
- cancelButtonText: '取消',
- type: 'warning',
- });
-
- const data = {
- id: currentItem.id,
- status: currentItem.status,
- isPass: 1,
- eventNum: currentItem.orderNumber,
- eventName: currentItem.orderName,
- };
-
- const response = await flowEvent(data);
- if (response.data.code === 0) {
- this.$message.success('工单审核不通过');
-
- // 创建新的数组而不是修改原数组
- const newSelections = [...this.selections];
- newSelections.splice(this.currentImageIndex - 1, 1);
- this.selections = newSelections;
-
- // 修正索引并更新图片
- if (this.selections.length > 0) {
- if (this.currentImageIndex > this.selections.length) {
- this.currentImageIndex = this.selections.length;
- }
- this.updateCurrentReviewImage();
- } else {
- this.reviewDialogVisible = false;
- this.currentImageIndex = 1;
- this.currentReviewImage = '';
- this.fetchTableData();
- }
-
- // 刷新表格数据
- // this.fetchTableData();
- } else {
- throw new Error(response.data.msg || '驳回失败');
- }
- } catch (error) {
- if (error === 'cancel') return;
- this.$message.error(error.message || '驳回失败,请稍后重试');
- }
- },
-
- // 获取所有图片列表用于预览
- getImageList() {
- return this.selections.map(item => this.getPreviewUrl(item.photo_url)).filter(url => url); // 过滤掉空值
- },
-
- // 处理图片点击
- handleImageClick() {
- // 图片点击事件由 el-image 的预览功能处理
- },
-
- // 处理上一张图片
- handlePrevImage() {
- if (this.currentImageIndex > 1) {
- this.currentImageIndex--;
- this.updateCurrentReviewImage();
- }
- },
-
- // 处理下一张图片
- handleNextImage() {
- if (this.currentImageIndex < this.selections.length) {
- this.currentImageIndex++;
- this.updateCurrentReviewImage();
- }
- },
-
- /**
- * 获取缩略图地址
- * @param {string} url 原图地址
- * @returns {string} 缩略图地址
- */
- getThumbUrl(url) {
- if (!url) return '';
- const lastDot = url.lastIndexOf('.');
- if (lastDot === -1) return url;
- return url.slice(0, lastDot) + '_small' + url.slice(lastDot);
- },
-
- // 添加新方法:获取预览图地址
- getPreviewUrl(url) {
- if (!url) return '';
- const lastDot = url.lastIndexOf('.');
- 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 (Array.isArray(data.location) && data.location.length > 0) {
- 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(that.reCheckData.id).then(res => {
- that.reCheckDialog = false;
- that.page.currentPage = 1;
- that.fetchTableData();
- that.fetchTabCounts();
- });
- } else {
- const loading = ElLoading.service({
- lock: true,
- text: '复核任务创建中……',
- background: 'rgba(0, 0, 0, 0.7)',
- });
-
- function closeConfirm() {
- that.reCheckDialog = false;
- that.page.currentPage = 1;
- that.fetchTableData();
- that.fetchTabCounts();
- loading.close();
- }
-
- // 获取时间的接口
- getCreateEventJob(that.reCheckData.id)
- .then(res => {
- ElMessageBox.confirm(`预计复核执行时间为${res.data.data}`, '提示', {
- confirmButtonText: '确定',
- showCancelButton: false, // 关键配置
- closeOnClickModal: false,
- closeOnPressEscape: false,
- type: 'warning',
- })
- .then(() => {
- closeConfirm();
- })
- .catch(() => {
- closeConfirm();
- });
- })
- .catch(() => {
- closeConfirm();
- });
- }
- },
- // 导出工单报表
- exportTheTick(row){
- const params = {
- num:row.orderNumber
- }
- exportTheTicket(params).then(res=>{
- const elink = document.createElement('a')
- elink.download = row.orderName + '.docx'
- elink.style.display = 'none'
- const blob = new Blob([res.data])
- elink.href = URL.createObjectURL(blob)
- document.body.appendChild(elink)
- elink.click()
- document.body.removeChild(elink)
- })
- this.$message.success('数据导出成功');
}
- },
- activated() {
- this.handleReset();
- },
-};
+ })
+}
+function handleSFNodeClick(data) {
+ filters.type = ''
+ filters.algorithm = ''
+ filters.type = data.dictKey
+ fetchTableData()
+}
+function handleClear() {
+ dictKey.value = ''
+ filters.algorithm = ''
+ filters.type = ''
+ fetchTableData()
+}
+// 上一项
+function handlePrevItem() {
+ currentIndex.value = Math.max(0, currentIndex.value - 1)
+ updateCurrentDetail()
+}
+
+// 下一项
+function handleNextItem() {
+ currentIndex.value = Math.min(tableData.value.length - 1, currentIndex.value + 1)
+ updateCurrentDetail()
+}
+
+// 更新当前详情
+function updateCurrentDetail() {
+ currentDetail.value = tableData.value[currentIndex.value]
+ currentDetail.value.mediaUrl = currentDetail.value.photo_url
+ currentDetail.value.updatePhotoUrl = currentDetail.value.update_photo_url
+ currentDetail.value.processingDetail = currentDetail.value.processing_details
+ currentDetail.value.showQR = false
+ currentDetail.value.latAndLon = _.round(currentDetail.value.location[0], 6) + ',' + _.round(currentDetail.value.location[1], 6)
+ getStepInfoData(currentDetail.value.orderNumber)
+}
+
+// 更新当前详情数据
+function handleCurrentDetailUpdate(updatedDetail) {
+ currentDetail.value = updatedDetail
+}
+// 受理并派发
+function handleApproveAndDispatch(detail) {
+if(!detail.orderName){
+ ElMessage.warning('请填写工单名称');
+ return;
+}
+if(!detail.aiType){
+ ElMessage.warning('请选择关联算法');
+ return;
+}
+
+if(!detail.content){
+ ElMessage.warning('请填写工单内容');
+ return;
+}
+ currentDetail.value = detail
+ dispatchDialogVisible.value = true
+}
+async function loadAMapScripts() {
+ try {
+ const areaCode = userInfo.value.detail.areaCode
+ const subAreaCode = areaCode ? areaCode.substring(0, 6) : ''
+ const adcodeObj = getAdcodeObj(geoJson, 'adcode', subAreaCode)
+
+ const center = adcodeObj?.payload?.objects?.collection?.geometries?.[0]?.properties?.center
+
+ if (Array.isArray(center) && center.length === 2) {
+ inputMapShowDefaultCenter.value = center
+ } else {
+ const bbox = adcodeObj?.payload?.bbox
+ if (Array.isArray(bbox) && bbox.length === 4) {
+ const centerX = (bbox[0] + bbox[2]) / 2
+ const centerY = (bbox[1] + bbox[3]) / 2
+ inputMapShowDefaultCenter.value = [centerX, centerY]
+ } else {
+ inputMapShowDefaultCenter.value = [115.861365, 28.621311]
+ }
+ }
+ mapParams.center = [...inputMapShowDefaultCenter.value]
+ mapLoaded.value = true
+ } catch (error) {
+ console.error('地图加载失败:', error)
+ }
+}
+async function fetchDropdownData() {
+ try {
+ const response = await getTicketInfo()
+ const { dept_data, event_type, ai_type, info } = response.data.data
+ departments.value = dept_data.map(item => ({
+ label: item.dept_name,
+ value: item.id,
+ }))
+
+ departmentUsers.value = dept_data.reduce((acc, dept) => {
+ acc[dept.id] = dept.user_data || []
+ return acc
+ }, {})
+
+ types.value = Object.entries(event_type).map(([key, value]) => ({
+ label: value,
+ value: key,
+ }))
+ allAlgorithms.value = info
+
+ algorithms.value = ai_type?.map(item => ({
+ dict_key: item.dict_key,
+ dict_value: item.dict_value,
+ label: item.dict_value,
+ value: item.dict_key,
+ })) || []
+
+ algorithms2.value = _.cloneDeep(algorithms.value)
+
+ userNameToIdMap.value = {}
+ dept_data.forEach(dept => {
+ (dept.user_data || []).forEach(user => {
+ userNameToIdMap.value[user.name] = user.id
+ })
+ })
+ } catch (error) {
+ console.error('加载下拉框数据失败:', error)
+ }
+}
+
+function handleTypeChange(typeValue) {
+ if (!typeValue) {
+ algorithms2.value = []
+ return
+ }
+ const matchedCategory = allAlgorithms.value.find(category => category.dict_key === typeValue)
+ if (!matchedCategory || !matchedCategory.algorithms || matchedCategory.algorithms.length === 0) {
+ algorithms2.value = []
+ return
+ }
+ algorithms2.value = matchedCategory.algorithms.map(algo => ({
+ label: algo.dict_value,
+ value: algo.dict_key,
+ dict_key: algo.dict_key,
+ dict_value: algo.dict_value,
+ }))
+}
+function 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`
+}
+
+function mapStatus(status) {
+ const statusTextMap = {
+
+ 2: '待审核',
+ 0: '待处理',
+ 3: '处理中',
+ 4: '已完成',
+ }
+ return statusTextMap[status] || '未知状态'
+}
+function getStatusTagType(status) {
+ if (status === -1 || status === '-1') return ''
+ const colorMap = {
+ 0: '#FF7411',
+ 3: '#FFC300',
+ 2: '#FF472F',
+ 4: '#0291A1',
+ }
+ return colorMap[String(status)] || ''
+}
+// 获取tab数据
+async function fetchTabCounts() {
+ const statusListVal = statusList.value;
+ const params = {
+ status_list: statusListVal
+ }
+ try {
+ const response = await getstatusCount(params)
+ const statusCount = response.data.data || {}
+ const totalCount = Object.entries(statusCount).reduce((sum, [key, count]) => {
+ if (key === '6') return sum
+ return sum + (count || 0)
+ }, 0)
+ tabs.value.forEach(tab => {
+ if (tab.name === 'all') {
+ tab.count = totalCount
+ } else if (tab.name === 'myTickets') {
+ tab.count = 0
+ } else {
+ tab.count = statusCount[String(tab.value)] || 0
+ }
+ })
+ } catch (error) {
+ console.error('获取标签页计数失败:', error)
+ tabs.value.forEach(tab => {
+ tab.count = 0
+ })
+ }
+}
+
+function handleTabChange(tab) {
+ activeTab.value = tab.props?.name || tab.name
+ fetchTabCounts()
+ handleReset()
+ page.currentPage = 1
+ fetchTableData()
+}
+
+// 跳转后触发的Tab切换
+function handleTabChangeAfterJump() {
+ handleReset()
+ page.currentPage = 1
+ fetchTableData()
+ fetchTabCounts()
+}
+function handleSearch() {
+ page.currentPage = 1
+ fetchTableData()
+ fetchTabCounts()
+}
+function handleReset() {
+ dictKey.value = ''
+ Object.assign(filters, {
+ keyword: '',
+ department: '',
+ type: '',
+ dateRange: [],
+ status: '',
+ algorithm: '',
+ isReview: '',
+ })
+ page.currentPage = 1
+ router.replace({})
+ fetchTableData()
+}
+
+function handleKeyWords() {
+ router.replace({})
+}
+
+// 分页
+async function handleCurrentChange(val) {
+ page.currentPage = val
+ await nextTick()
+ await fetchTableData()
+}
+
+async function sizeChange(val) {
+ page.pageSize = val
+ page.currentPage = 1
+ await nextTick()
+ await fetchTableData()
+}
+
+function handleAdd() {
+ editFormData.value = null
+ dialogVisible.value = true
+}
+
+async function getStepInfoData(val) {
+ const orderNum = val
+ const stepResponse = await getStepInfo(orderNum)
+ const steps = Array.isArray(stepResponse.data.data)
+ ? stepResponse.data.data
+ : stepResponse.data.data?.steps || []
+ const finishedStep = steps.find(s => String(s.status) === '4')
+ totalTime.value = finishedStep && finishedStep.total_time ? finishedStep.total_time : ''
+ stepInfos.value = steps.map(step => ({
+ status: String(step.status),
+ name: step.name,
+ time: step.time,
+ create_time: step.create_time,
+ }))
+}
+// 详情
+async function handleViewDetail(row) {
+console.log('row',row);
+
+ currentIndex.value = tableData.value.findIndex(item => item.id === row.id)
+ workType.value = row.work_type !== undefined ? Number(row.work_type) : 0
+ const detailData = {
+ ...row,
+ processingDetail: row.processing_details || '',
+ mediaUrl: row.photo_url || row.video_url || '',
+ updatePhotoUrl: row.update_photo_url || '',
+ photos: [],
+ job_name: row.job_name || '',
+ }
+
+ await getStepInfoData(row.orderNumber)
+ currentDetail.value.status = row.status
+
+ currentDetail.value = {
+ ...detailData,
+ showQR: false,
+ latAndLon: _.round(detailData.location[0], 6) + ',' + _.round(detailData.location[1], 6),
+ }
+// console.log('currentDetail.value',currentDetail.value);
+
+ detailVisible.value = true
+ handleTypeChange(currentDetail.value.type)
+}
+
+// 详情成功回调
+function handleDetailSuccess() {
+ fetchTableData()
+}
+
+// 详情错误回调
+function handleDetailError(error) {
+ console.error('工单操作失败:', error)
+}
+
+function refreshChange() {
+ if (isFetching.value) return
+ fetchTableData()
+}
+
+function onLoad() {
+ if (isFetching.value) return
+ fetchTableData()
+}
+// 导出数据
+async function exportData() {
+ try {
+ loading.value = true
+ let exportData = []
+
+ if (selections.value.length > 0) {
+ exportData = selections.value.map(item => formatExportItem(item))
+ } else {
+ exportData = tableData.value.map(item => formatExportItem(item))
+ }
+
+ if (exportData.length === 0) {
+ console.warn('没有数据可供导出')
+ ElMessage.warning('没有数据可供导出');
+ return
+ }
+
+ const headers = [
+ '工单编号',
+ '工单名称',
+ '所属部门',
+ '发起时间',
+ '关联算法',
+ '工单内容',
+ '工单类型',
+ '经纬度',
+ '创建人',
+ '处理人',
+ '工单状态',
+ ]
+
+ export_json_to_excel(headers, exportData, '工单数据')
+ } catch (error) {
+ console.error('导出失败:', error)
+ } finally {
+ loading.value = false
+ }
+}
+
+function formatExportItem(item) {
+ const longitude = Number(item.longitude) || Number(item.location?.[0]) || 0
+ const latitude = Number(item.latitude) || Number(item.location?.[1]) || 0
+
+ return {
+ 工单编号: item.orderNumber || item.event_num || '',
+ 工单名称: item.orderName || item.event_name || '',
+ 所属部门: item.department || item.dept_name || '',
+ 发起时间: item.startTime || item.create_time || '',
+ 关联算法: item.aiType || item.ai_types || '',
+ 工单内容: item.address || item.content || '',
+ 工单类型: types.value.find(t => t.value === (item.type || item.work_order_type_dict_key))?.label || '',
+ 经纬度: !isNaN(longitude) && !isNaN(latitude)
+ ? `${longitude.toFixed(6)}, ${latitude.toFixed(6)}`
+ : '',
+ 创建人: item.creator || item.create_user || '',
+ 处理人: item.handler || item.update_user || '',
+ 工单状态: mapStatus(Number(item.status || 0)),
+ }
+}
+// 派发成功回调
+function handleDispatchSuccess() {
+ detailVisible.value = false
+ fetchTableData()
+}
+// 派发错误回调
+function handleDispatchError(error) {
+ console.error('派发失败:', error)
+}
+// 编辑工单
+function handleEdit(row) {
+ editFormData.value = row
+ dialogVisible.value = true
+}
+
+// 创建成功回调
+function handleCreateSuccess() {
+ fetchTableData()
+}
+
+// 草稿保存成功回调
+function handleDraftSuccess() {
+ fetchTableData()
+}
+
+// 创建失败回调
+function handleCreateError(error) {
+ console.error('工单操作失败:', error)
+}
+
+// 删除方法
+function handleDelete(row) {
+
+}
+
+// 添加选择变化处理方法
+function handleSelectionChange(selection) {
+ selections.value = selection
+}
+
+// 打开审核对话框
+function openReviewDialog() {
+ if (selections.value.length === 0) {
+ ElMessage.warning('请先选择要审核的工单');
+ return
+ }
+ currentImageIndex.value = 1
+ updateCurrentReviewImage()
+ reviewDialogVisible.value = true
+}
+
+// 更新当前审核图片
+function updateCurrentReviewImage() {
+ if (!selections.value || selections.value.length === 0) {
+ currentReviewImage.value = ''
+ currentImageIndex.value = 1
+ return
+ }
+ if (currentImageIndex.value > selections.value.length) {
+ currentImageIndex.value = selections.value.length
+ }
+ if (currentImageIndex.value < 1) {
+ currentImageIndex.value = 1
+ }
+ const index = currentImageIndex.value - 1
+ if (index >= 0 && index < selections.value.length) {
+ const currentItem = selections.value[index]
+ currentReviewImage.value = currentItem.photo_url || ''
+ } else {
+ currentReviewImage.value = ''
+ }
+}
+
+// 处理图片分页变化
+function handleImagePageChange(page) {
+ if (page > 0 && page <= selections.value.length) {
+ currentImageIndex.value = page
+ updateCurrentReviewImage()
+ }
+}
+
+// 批量审核通过
+async function handleBatchApprove() {
+ try {
+ if (selections.value.length === 0) {
+ console.warn('没有选中的工单')
+ return
+ }
+
+ const currentItem = selections.value[currentImageIndex.value - 1]
+ if (!currentItem) {
+ console.warn('当前工单数据无效')
+ return
+ }
+
+ // 在Vue 3中需要使用ElMessageBox.confirm
+
+ const data = {
+ id: currentItem.id,
+ status: currentItem.status,
+ isPass: 0,
+ eventNum: currentItem.orderNumber,
+ }
+
+ const response = await flowEvent(data)
+ if (response.data.code === 0) {
+
+
+ const newSelections = [...selections.value]
+ newSelections.splice(currentImageIndex.value - 1, 1)
+
+ if (newSelections.length > 0) {
+ if (currentImageIndex.value > selections.value.length) {
+ currentImageIndex.value = selections.value.length
+ }
+ updateCurrentReviewImage()
+ } else {
+ reviewDialogVisible.value = false
+ currentImageIndex.value = 1
+ currentReviewImage.value = ''
+ fetchTableData()
+ }
+
+ selections.value = newSelections
+ } else {
+ throw new Error(response.data.msg || '审核失败')
+ }
+ } catch (error) {
+ console.error('审核失败:', error)
+ }
+}
+
+function cancleBatchReject() {
+ reviewDialogVisible.value = false
+ selections.value = []
+ currentImageIndex.value = 1
+ currentReviewImage.value = ''
+ fetchTableData()
+}
+
+// 批量审核不通过
+async function handleBatchReject() {
+ try {
+ if (selections.value.length === 0) {
+ console.warn('没有选中的工单')
+ return
+ }
+
+ const currentItem = selections.value[currentImageIndex.value - 1]
+ if (!currentItem) {
+ console.warn('当前工单数据无效')
+ return
+ }
+
+ // 在Vue 3中需要使用ElMessageBox.confirm
+ const data = {
+ id: currentItem.id,
+ status: currentItem.status,
+ isPass: 1,
+ eventNum: currentItem.orderNumber,
+ eventName: currentItem.orderName,
+ }
+
+ const response = await flowEvent(data)
+ if (response.data.code === 0) {
+
+
+ const newSelections = [...selections.value]
+ newSelections.splice(currentImageIndex.value - 1, 1)
+ selections.value = newSelections
+
+ if (selections.value.length > 0) {
+ if (currentImageIndex.value > selections.value.length) {
+ currentImageIndex.value = selections.value.length
+ }
+ updateCurrentReviewImage()
+ } else {
+ reviewDialogVisible.value = false
+ currentImageIndex.value = 1
+ currentReviewImage.value = ''
+ fetchTableData()
+ }
+ } else {
+ throw new Error(response.data.msg || '驳回失败')
+ }
+ } catch (error) {
+ console.error('驳回失败:', error)
+ }
+}
+
+// 获取所有图片列表用于预览
+function getImageList() {
+ return selections.value.map(item => getPreviewUrl(item.photo_url)).filter(url => url)
+}
+
+// 处理上一张图片
+function handlePrevImage() {
+ if (currentImageIndex.value > 1) {
+ currentImageIndex.value--
+ updateCurrentReviewImage()
+ }
+}
+
+// 处理下一张图片
+function handleNextImage() {
+ if (currentImageIndex.value < selections.value.length) {
+ currentImageIndex.value++
+ updateCurrentReviewImage()
+ }
+}
+
+/**
+ * 获取缩略图地址
+ */
+function getThumbUrl(url) {
+ if (!url) return ''
+ const lastDot = url.lastIndexOf('.')
+ if (lastDot === -1) return url
+ return url.slice(0, lastDot) + '_small' + url.slice(lastDot)
+}
+
+// 获取预览图地址
+function getPreviewUrl(url) {
+ if (!url) return ''
+ const lastDot = url.lastIndexOf('.')
+ if (lastDot === -1) return url
+ return url.slice(0, lastDot) + '_show' + url.slice(lastDot)
+}
+
+/**
+ * 坐标转换方法处理
+ */
+function disposeLocation(goWgs84 = false, data) {
+ let lng = ''
+ let lat = ''
+
+ if (Array.isArray(data.location) && data.location.length > 0) {
+ 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)]
+}
+
+// 复核按钮
+function reCheck(row) {
+ reCheckData.value = row
+ reCheckDialog.value = true
+}
+
+// 复核成功回调
+function handleRecheckSuccess() {
+ page.currentPage = 1
+ fetchTableData()
+ fetchTabCounts()
+}
+
+// 导出工单报表
+function exportTheTick(row) {
+ const params = {
+ num: row.orderNumber
+ }
+ exportTheTicket(params).then(res => {
+ const elink = document.createElement('a')
+ elink.download = row.orderName + '.docx'
+ elink.style.display = 'none'
+ const blob = new Blob([res.data])
+ elink.href = URL.createObjectURL(blob)
+ document.body.appendChild(elink)
+ elink.click()
+ document.body.removeChild(elink)
+ })
+
+}
+
+// 生命周期
+onMounted(() => {
+
+ inputMapShowDefaultCenter.value = null
+ loadAMapScripts()
+ fetchDropdownData()
+ getAlgorithmList()
+ // 设置默认activeTab
+ activeTab.value = defaultActiveTab.value
+ const href = window.location.href;
+ if (route?.query?.status !== undefined && route?.query?.status !== null) {
+ filters.status = route?.query?.status + ''
+ router.replace({})
+ }
+
+ let curQueryParams = {}
+
+ if (href?.indexOf('?') != -1 && href?.split('?').length > 0) {
+ curQueryParams = href
+ .split('?')[1]
+ .split('&')
+ .reduce((pre, cur) => {
+ let newArr = cur.split('=')
+ pre[newArr[0]] = newArr[1]
+ return pre
+ }, {})
+
+ const { orderNumber = undefined, day = undefined, tab = undefined } = curQueryParams
+
+ if (day) {
+ const date = new Date(day + 'T00:00:00+08:00')
+ const dateArray = [date, date]
+ const handler = {
+ get(target, prop) {
+ if (typeof prop === 'string' && /^\d+$/.test(prop)) {
+ const index = parseInt(prop)
+ const dateObj = target[index]
+ return dateObj.toDateString() + ' 00:00:00 GMT+0800 (中国标准时间)'
+ }
+ return Reflect.get(target, prop)
+ },
+ }
+
+ const proxyArray = new Proxy(dateArray, handler)
+ filters.dateRange = proxyArray
+ }
+
+ if (orderNumber) {
+ filters.keyword = orderNumber
+ nextTick(() => {
+ isShowInfo.value = true
+ const find = store.state.tags.bsTagList.find(i => i.path === '/tickets/ticket')
+ if (find) {
+ find.query = {}
+ }
+ })
+ }
+
+ if (tab) {
+ const isTabValid = filteredTabs.value.some(t => t.name === tab)
+ if (isTabValid) {
+ activeTab.value = tab
+ handleTabChangeAfterJump()
+ const find = store.state.tags.bsTagList.find(i => i.path === '/tickets/ticket')
+ if (find) {
+ find.query = {}
+ }
+ }
+ }
+ }
+
+ fetchTabCounts()
+ fetchTableData()
+})
+
+onActivated(() => {
+ handleReset()
+})
</script>
-<style lang="scss">
-.custom-dialog {
- max-height: 96vh;
- .el-dialog__body {
- border-top: 0.1rem solid #f0f0f0;
- }
-}
-
-.custom-qrcode-popover {
- min-width: 160px !important;
- min-height: 140px !important;
- position: relative;
-
- .qrcode-content {
- margin-top: 8px;
-
- .close-btn {
- position: absolute;
- top: 2px;
- right: 2px;
- width: 16px;
- height: 16px;
- line-height: 16px;
- text-align: center;
- cursor: pointer;
- color: #999;
- font-size: 16px;
- z-index: 10;
- }
- }
-}
-</style>
<style lang="scss" scoped>
+.el-card__body {
+height: 100%;}
+.titketBox {
+
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+}
+.dataTable {
+ height: 0;
+ flex: 1;
+ overflow: auto;
+}
+.pagination {
+ display: flex;
+ justify-content: flex-end;
+ margin-top: 20px;
+ position: fixed;
+ bottom: 30px;
+ left: 0;
+ right: 0;
+ padding: 0 30px;
+
+ z-index: 1000;
+}
+:deep(.custom-header th.el-table__cell) {
+ color: rgba(0, 0, 0, 0.85);
+ background: #fafafa;
+ height: 55px !important;
+}
+/* 表格操作区域样式 */
+.table-operations {
+ margin-bottom: 16px;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
+
+.left-operations {
+ display: flex;
+ gap: 8px;
+}
+
+/* 表格样式 */
+.ztzf-public-general-el-table {
+ margin-bottom: 16px;
+}
+
+/* 分页样式 */
+.table-pagination {
+ display: flex;
+ justify-content: flex-end;
+ margin-top: 10px;
+}
+.ztzf-public-general-el-table {
+ :deep(.el-table__header-wrapper) {
+ background-color: #f5f7fa;
+ }
+
+ :deep(.el-table th) {
+ background-color: #f5f7fa;
+ color: #606266;
+ font-weight: 600;
+ }
+
+ :deep(.el-table--border) {
+ border: 1px solid #ebeef5;
+ }
+}
::v-deep(.el-tabs) {
height: 100%;
display: flex;
@@ -3304,28 +1584,6 @@
margin-bottom: 32px;
}
-.tableCss {
- width: 100%;
- margin-bottom: 10px;
-}
-
-.step-timer {
- position: absolute;
- right: 80%;
- top: 50%;
- transform: translateY(-50%);
- width: 100px;
- margin-left: 4px;
- color: #666;
- font-size: 12px;
-}
-
-.event-total-time {
- text-align: center;
- color: #666;
- font-size: 15px;
- margin-bottom: 12px;
-}
.action-bar {
margin-bottom: 16px;
@@ -3776,22 +2034,7 @@
word-break: break-word;
}
-.readonly-processing-detail {
- background-color: #f5f7fa;
- padding: 12px;
- border-radius: 4px;
- min-height: 30px;
- color: #606266;
- line-height: 1.5;
- display: block;
- text-align: left;
- margin-bottom: 5px;
- &:first-child {
- font-weight: bold;
- margin-bottom: 4px;
- }
-}
// 添加删除按钮样式
.danger-button {
@@ -4008,31 +2251,6 @@
}
}
-/* 新建工单和处理工单的上传组件样式 */
-.create-upload,
-.detail-upload {
- :deep(.el-upload--picture-card) {
- width: 120px;
- height: 100px;
- line-height: 100px;
- }
-
- /* 隐藏额外的上传按钮 */
- :deep(.el-upload.el-upload--picture-card) {
- display: none;
- }
-
- /* 当没有图片时显示上传按钮 */
- :deep(.el-upload.el-upload--picture-card:first-child) {
- display: flex;
- }
-
- /* 上传组件的预览图片样式 */
- :deep(.el-upload-list--picture-card .el-upload-list__item) {
- width: 120px;
- height: 100px;
- }
-}
/* 原有图片预览的样式 */
.el-image-viewer__wrapper {
@@ -4040,29 +2258,6 @@
max-width: 100%;
max-height: 100%;
object-fit: contain;
- }
-}
-
-/* 必填项样式 */
-.required-label {
- position: relative;
- display: inline-block;
-
- .required-star {
- color: #f56c6c;
- margin-right: 4px;
- }
-}
-
-/* 必填输入框样式 */
-.required-input {
- width: 100%;
-
- :deep(.el-input__inner),
- :deep(.el-textarea__inner) {
- &:focus {
- border-color: #409eff;
- }
}
}
</style>
diff --git a/src/views/tickets/ticketComponent/CreateTicketDialog.vue b/src/views/tickets/ticketComponent/CreateTicketDialog.vue
new file mode 100644
index 0000000..3c9f952
--- /dev/null
+++ b/src/views/tickets/ticketComponent/CreateTicketDialog.vue
@@ -0,0 +1,710 @@
+<template>
+ <el-dialog
+ v-model="dialogVisible"
+ title="新建工单"
+ width="70%"
+ :close-on-click-modal="false"
+ @close="handleClose"
+ >
+ <el-form
+ :model="form"
+ :rules="rules"
+ ref="formRef"
+ label-width="90px"
+ class="create-ticket-form"
+ @submit.prevent
+ >
+ <div class="form-section">
+ <el-row :gutter="16">
+ <el-col :span="12">
+ <el-form-item label="工单名称" prop="name">
+ <el-input
+ v-model="form.name"
+ placeholder="请输入工单名称"
+ clearable
+ />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="工单类型" prop="type">
+ <el-select
+ @change="handleTypeChange"
+ v-model="form.type"
+ placeholder="请选择工单类型"
+ class="full-width"
+ clearable
+ >
+ <el-option
+ v-for="item in types"
+ :key="item.value"
+ :label="item.label"
+ :value="item.value"
+ />
+ </el-select>
+ </el-form-item>
+ </el-col>
+ </el-row>
+
+ <el-row :gutter="16">
+ <el-col :span="12">
+ <el-form-item label="处理部门" prop="department">
+ <el-select
+ v-model="form.department"
+ placeholder="请选择处理部门"
+ @change="handleDepartmentChange"
+ class="full-width"
+ clearable
+ >
+ <el-option
+ v-for="dept in departments"
+ :key="dept.value"
+ :label="dept.label"
+ :value="dept.value"
+ />
+ </el-select>
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="处理人员" prop="handler">
+ <el-select
+ v-model="form.handler"
+ placeholder="请先选择处理人员"
+ :disabled="!form.department"
+ class="full-width"
+ clearable
+ >
+ <el-option
+ v-for="user in availableHandlers"
+ :key="user.id"
+ :label="user.name"
+ :value="user.id"
+ />
+ </el-select>
+ </el-form-item>
+ </el-col>
+ </el-row>
+
+ <el-row :gutter="16">
+ <el-col :span="12">
+ <el-form-item label="关联算法" prop="algorithm">
+ <el-select
+ v-model="form.algorithm"
+ multiple
+ placeholder="请选择关联算法"
+ class="full-width"
+ :disabled="!form.type"
+ clearable
+ >
+ <el-option
+ v-for="item in availableAlgorithms"
+ :key="item.value"
+ :label="item.label"
+ :value="item.value"
+ />
+ </el-select>
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="事件位置" prop="location">
+ <div class="location-wrapper">
+ <avue-input-map
+ v-model="form.location"
+ :clearable="false"
+ :params="mapParams"
+ @change="handleLocationChange"
+ type="button"
+ >
+ <el-button type="primary" plain class="map-button">
+ <i class="el-icon-map-location"></i> 地图选点
+ </el-button>
+ </avue-input-map>
+ </div>
+ </el-form-item>
+ </el-col>
+ </el-row>
+
+ <el-row :gutter="16">
+ <el-col :span="12">
+ <el-form-item label="工单内容" prop="content">
+ <el-input
+ type="textarea"
+ v-model="form.content"
+ :rows="4"
+ placeholder="请输入工单内容描述"
+ />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="事件图片" prop="photos" required class="upload-wrapper">
+ <el-upload
+ ref="uploadRef"
+ :action="'#'"
+ :auto-upload="false"
+ list-type="picture-card"
+ :on-change="handleFileChange"
+ :on-remove="handleUploadRemove"
+ :before-upload="beforeUpload"
+ :file-list="form.photos"
+ :limit="1"
+ accept="image/*"
+ class="create-upload"
+ >
+ <template v-if="form.photos.length < 1">
+ <div class="el-icon-plus">
+ <span>+</span>
+ </div>
+ </template>
+ </el-upload>
+ <div class="upload-tip">需上传含有地址信息的照片(jpg、jpeg、png),且不超过5M</div>
+ </el-form-item>
+ </el-col>
+ </el-row>
+ </div>
+ </el-form>
+
+ <template #footer>
+ <div class="dialog-footer-new">
+ <el-button
+ type="danger"
+ :loading="submitLoading"
+ @click="handleSubmit"
+ icon="el-icon-position"
+ >
+ 发布
+ </el-button>
+
+ <el-button @click="handleClose" icon="el-icon-circle-close">取 消</el-button>
+ </div>
+ </template>
+ </el-dialog>
+</template>
+
+<script>
+import { createTicket } from '@/api/tickets/ticket';
+import { gcj02ToWgs84, wgs84ToGcj02 } from '@/utils/coordinateTransformation';
+
+export default {
+ name: 'CreateTicketDialog',
+ props: {
+ modelValue: {
+ type: Boolean,
+ default: false
+ },
+ departments: {
+ type: Array,
+ default: () => []
+ },
+ departmentUsers: {
+ type: Object,
+ default: () => ({})
+ },
+ types: {
+ type: Array,
+ default: () => []
+ },
+ allAlgorithms: {
+ type: Array,
+ default: () => []
+ },
+ editData: {
+ type: Object,
+ default: null
+ },
+ mapCenter: {
+ type: Array,
+ default: () => [115.861365, 28.621311]
+ }
+ },
+ emits: ['update:modelValue', 'create-success', 'draft-success', 'create-error'],
+ data() {
+ return {
+ form: {
+ name: '',
+ type: '',
+ department: '',
+ handler: '',
+ algorithm: [],
+ location: [],
+ address: '',
+ content: '',
+ photos: []
+ },
+ rules: {
+ name: [{ required: true, message: '请输入工单名称', trigger: 'blur' }],
+ type: [{ required: true, message: '请选择工单类型', trigger: 'change' }],
+ department: [{ required: true, message: '请选择所属部门', trigger: 'change' }],
+ handler: [{ required: true, message: '请选择处理人员', trigger: 'change' }],
+ content: [{ required: true, message: '请输入工单内容', trigger: 'blur' }],
+ algorithm: [{ required: true, message: '请选择关联算法', trigger: 'change' }],
+ location: [
+ {
+ required: true,
+ validator: (rule, value, callback) => {
+ if (!value || value.length < 2) {
+ callback(new Error('请选择位置信息'));
+ } else if (!value[0] || !value[1]) {
+ callback(new Error('请选择位置信息'));
+ } else {
+ callback();
+ }
+ },
+ trigger: 'change',
+ },
+ ],
+ photos: [
+ {
+ validator: (rule, value, callback) => {
+ if (!this.form.photos || this.form.photos.length === 0) {
+ callback(new Error('请上传工单图片'));
+ } else {
+ callback();
+ }
+ },
+ trigger: 'change',
+ },
+ ],
+ },
+ availableAlgorithms: [],
+ mapParams: {
+ zoom: 15,
+ center: null
+ },
+ submitLoading: false,
+ draftLoading: false
+ };
+ },
+ computed: {
+ dialogVisible: {
+ get() {
+ return this.modelValue;
+ },
+ set(value) {
+ this.$emit('update:modelValue', value);
+ }
+ },
+ availableHandlers() {
+ return this.form.department ? this.departmentUsers[this.form.department] || [] : [];
+ },
+ isEditMode() {
+ return !!this.editData;
+ }
+ },
+ watch: {
+ modelValue(newVal) {
+ if (newVal) {
+ this.initForm();
+ this.mapParams.center = [...this.mapCenter];
+ }
+ }
+ },
+ methods: {
+ initForm() {
+ if (this.isEditMode && this.editData) {
+ // 编辑模式:填充表单数据
+ this.loadEditData();
+ } else {
+ // 新建模式:重置表单
+ this.resetForm();
+ }
+ },
+
+ loadEditData() {
+ const data = this.editData;
+
+ // 获取部门ID
+ let deptId = data.dept_id;
+ if (!deptId) {
+ const deptInfo = this.departments.find(
+ dept => dept.label && dept.label.trim() === data.department.trim()
+ );
+ deptId = deptInfo?.value;
+ }
+
+ // 获取工单类型值
+ const typeValue = this.types.find(t => t.value === data.type)?.value || data.work_order_type_dict_key;
+
+ // 处理算法数组
+ let algorithmArr = [];
+ if (Array.isArray(data.aiType)) {
+ algorithmArr = data.aiType;
+ } else if (typeof data.aiType === 'string' && data.aiType) {
+ algorithmArr = data.aiType.split(/[,、]/).map(item => item.trim());
+ }
+
+ // 确保算法值与选项匹配
+ algorithmArr = algorithmArr
+ .map(item => {
+ if (typeof item === 'string') {
+ const matchedAlgorithm = this.allAlgorithms.find(
+ algo => algo.dict_value === item || algo.dict_key === item
+ );
+ return matchedAlgorithm ? matchedAlgorithm.dict_key : item;
+ }
+ return item;
+ })
+ .filter(Boolean);
+
+ // 处理位置数据
+ let curLocation = [];
+ if (Array.isArray(data.location) && data.location.length >= 2) {
+ let [lng, lat] = this.disposeLocation(false, data);
+ curLocation = [lng, lat, data.location[2] || data.address || ''];
+ }
+
+ this.form = {
+ id: data.id,
+ name: data.orderName,
+ type: typeValue,
+ department: deptId,
+ handler: data.handler,
+ algorithm: algorithmArr,
+ location: curLocation,
+ address: curLocation[2] || '',
+ content: data.content,
+ photos: data.photo_url ? [{
+ name: 'existing-photo.jpg',
+ url: data.photo_url,
+ status: 'success',
+ raw: null,
+ existingUrl: data.photo_url,
+ }] : []
+ };
+
+ // 更新算法选项
+ this.handleTypeChange(typeValue);
+ },
+
+ resetForm() {
+ this.form = {
+ name: '',
+ type: '',
+ department: '',
+ handler: '',
+ algorithm: [],
+ location: [],
+ address: '',
+ content: '',
+ photos: []
+ };
+ this.availableAlgorithms = [];
+
+ if (this.$refs.formRef) {
+ this.$refs.formRef.clearValidate();
+ }
+ },
+
+ handleClose() {
+ this.dialogVisible = false;
+ this.resetForm();
+ },
+ handleTypeChange(typeValue) {
+ this.form.algorithm = [];
+ if (!typeValue) {
+ this.availableAlgorithms = [];
+ return;
+ }
+ const matchedCategory = this.allAlgorithms.find(category => category.dict_key === typeValue);
+ if (!matchedCategory || !matchedCategory.algorithms || matchedCategory.algorithms.length === 0) {
+ this.availableAlgorithms = [];
+ this.$message.info('该工单类型暂无关联算法');
+ return;
+ }
+
+ this.availableAlgorithms = matchedCategory.algorithms.map(algo => ({
+ label: algo.dict_value,
+ value: algo.dict_key,
+ dict_key: algo.dict_key,
+ dict_value: algo.dict_value,
+ }));
+ },
+
+ handleDepartmentChange() {
+ this.form.handler = '';
+ },
+
+ handleLocationChange(val) {
+ let locationValue = val.value;
+ if (locationValue && locationValue.length >= 2) {
+ const [lng, lat] = gcj02ToWgs84(locationValue[0], locationValue[1]);
+ this.form.location = [Number(lng), Number(lat), locationValue[2] || ''];
+ this.form.address = locationValue[2] || '';
+ } else {
+ this.form.location = [];
+ this.form.address = '';
+ }
+ },
+
+ handleFileChange(file, fileList) {
+ this.form.photos = fileList.map(item => ({
+ ...item,
+ existingUrl: item.url && !item.raw ? item.url : null,
+ }));
+ },
+
+ handleUploadRemove(file, fileList) {
+ this.form.photos = fileList;
+ },
+
+ beforeUpload(file) {
+ const isImage = file.type.includes('image');
+ const isLt5M = file.size / 1024 / 1024 < 5;
+
+ if (!isImage) {
+ this.$message.error('只能上传图片文件!');
+ return false;
+ }
+ if (!isLt5M) {
+ this.$message.error('图片大小不能超过5MB!');
+ return false;
+ }
+ return true;
+ },
+
+ async handleSubmit() {
+ if (this.submitLoading) return;
+
+ try {
+ await this.$refs.formRef.validate();
+
+ // 验证位置信息
+ if (!this.form.location || this.form.location.length < 2) {
+ this.$message.warning('请在地图上选择位置');
+ return;
+ }
+
+ // 验证图片
+ if (!this.form.photos || this.form.photos.length === 0) {
+ this.$message.warning('请上传工单图片');
+ return;
+ }
+
+ this.submitLoading = true;
+
+ let [lng, lat] = this.disposeLocation(true, this.form);
+
+ const submitData = {
+ eventName: this.form.name,
+ content: this.form.content,
+ workType: '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],
+ updateUser: this.form.handler,
+ createDept: this.form.department,
+ isDraft: 0,
+ };
+
+ if (this.form.id) {
+ submitData.id = this.form.id;
+ }
+
+ // 获取文件
+ let file = null;
+ const photoInfo = this.form.photos[0];
+
+ if (photoInfo.raw) {
+ file = photoInfo.raw;
+ } else if (photoInfo.existingUrl) {
+ submitData.photoUrl = photoInfo.existingUrl;
+ } else {
+ this.$message.warning('图片文件无效,请重新上传');
+ return;
+ }
+
+ const response = await createTicket(submitData, file);
+ if (response.data.code === 0) {
+ this.$message.success(this.isEditMode ? '工单编辑成功' : '工单创建成功');
+ this.$emit('create-success');
+ this.handleClose();
+ } else {
+ throw new Error(response.data.msg || '操作失败');
+ }
+ } catch (error) {
+ if (error.message.includes('验证未通过')) {
+ this.$message.warning('请填写完整的工单信息');
+ } else {
+ this.$message.error(error.message || '操作失败,请稍后重试');
+ this.$emit('create-error', error);
+ }
+ } finally {
+ this.submitLoading = false;
+ }
+ },
+
+
+ disposeLocation(goWgs84 = false, data) {
+ let lng = '', lat = '';
+
+ if (Array.isArray(data.location) && data.location.length > 0) {
+ 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] = this.wgs84ToGcj02(Number(lng), Number(lat));
+ }
+ }
+ }
+
+ return [String(lng), String(lat)];
+ },
+
+
+ }
+};
+</script>
+
+<style lang="scss" scoped>
+.full-width {
+ width: 100%;
+}
+
+.location-wrapper {
+ width: 100%;
+ height: auto;
+}
+
+.map-button {
+ width: 100%;
+ height: 36px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.create-ticket-form {
+ padding: 20px 10px;
+
+ .form-section {
+ background-color: #fff;
+ border-radius: 4px;
+
+ .el-row {
+ margin-bottom: 16px;
+
+ &:last-child {
+ margin-bottom: 0;
+ }
+ }
+ }
+
+ .location-wrapper {
+ width: 100%; // 修改为100%以适应父容器
+ height: auto; // 修改为auto以自适应内容
+
+ .map-button {
+ width: 100%; // 让按钮填满容器宽度
+ height: 36px; // 与其他输入框保持一致的高度
+ display: flex;
+ align-items: center;
+ justify-content: center;
+
+ i {
+ margin-right: 4px;
+ }
+ }
+
+ .location-text {
+ margin-top: 8px;
+ padding: 8px 12px;
+ background-color: #f5f7fa;
+ border-radius: 4px;
+ color: #606266;
+ font-size: 13px;
+ line-height: 1.4;
+ }
+ }
+
+ .upload-wrapper {
+ .uploader {
+ :deep(.el-upload--picture-card) {
+ width: 120px;
+ height: 100px;
+ line-height: 128px;
+ }
+
+ :deep(.el-upload-list__item) {
+ width: 120px;
+ height: 120px;
+ }
+ }
+
+ .upload-tip {
+ font-size: 12px;
+ color: #909399;
+ line-height: 1.4;
+ margin-top: 8px;
+ }
+ }
+
+ .el-form-item {
+ margin-bottom: 18px;
+
+ &:last-child {
+ margin-bottom: 0;
+ }
+ }
+
+ :deep(.el-form-item__label) {
+ font-weight: 500;
+ color: #606266;
+ }
+
+ // :deep(.el-input__inner) {
+ // height: 36px;
+ // line-height: 36px;
+ // }
+
+ :deep(.el-textarea__inner) {
+ padding: 8px 12px;
+ }
+
+ // :deep(.el-input__inner),
+ // :deep(.el-select),
+ // :deep(.el-select .el-input) {
+ // width: 100%; // 确保所有输入框和选择框宽度一致
+ // }
+
+ .full-width {
+ width: 100%;
+ }
+}
+
+:deep(.el-dialog__body) {
+ border-top: 0.1rem solid #f0f0f0;
+}
+/* 新建工单和处理工单的上传组件样式 */
+.create-upload,
+.detail-upload {
+ :deep(.el-upload--picture-card) {
+ width: 120px;
+ height: 100px;
+ line-height: 100px;
+ }
+
+ /* 隐藏额外的上传按钮 */
+ :deep(.el-upload.el-upload--picture-card) {
+ display: none;
+ }
+
+ /* 当没有图片时显示上传按钮 */
+ :deep(.el-upload.el-upload--picture-card:first-child) {
+ display: flex;
+ }
+
+ /* 上传组件的预览图片样式 */
+ :deep(.el-upload-list--picture-card .el-upload-list__item) {
+ width: 120px;
+ height: 100px;
+ }
+}
+</style>
\ No newline at end of file
diff --git a/src/views/tickets/ticketComponent/DispatchDialog.vue b/src/views/tickets/ticketComponent/DispatchDialog.vue
new file mode 100644
index 0000000..11046da
--- /dev/null
+++ b/src/views/tickets/ticketComponent/DispatchDialog.vue
@@ -0,0 +1,244 @@
+<template>
+ <el-dialog
+ v-model="dialogVisible"
+ title="派发工单"
+ width="40%"
+ :close-on-click-modal="false"
+ @close="handleClose"
+ >
+ <el-form
+ :model="form"
+ :rules="rules"
+ ref="formRef"
+ label-width="100px"
+ @submit.prevent
+ >
+ <el-form-item label="选择部门" prop="department">
+ <el-tree-select
+ v-model="form.department"
+ placeholder="请选择部门"
+ :data="deptTreeData"
+ :props="treeProps"
+ :filterable="true"
+ :allow-clear="true"
+ :check-strictly="true"
+ @change="handleDepartmentChange"
+ class="full-width"
+ />
+ </el-form-item>
+ <el-form-item label="选择处理人" prop="handler">
+ <el-select
+ filterable
+ v-model="form.handler"
+ placeholder="请选择处理人"
+ :disabled="!form.department"
+ class="full-width"
+ >
+ <el-option
+ v-for="user in availableHandlers"
+ :key="user.id"
+ :label="user.name"
+ :value="user.id"
+ />
+ </el-select>
+ </el-form-item>
+ </el-form>
+
+ <template #footer>
+ <el-button @click="handleClose" icon="el-icon-circle-close">取消</el-button>
+ <el-button
+ type="primary"
+ :loading="loading"
+ @click="handleSubmit"
+ icon="el-icon-check"
+ >
+ 确认派发
+ </el-button>
+ </template>
+ </el-dialog>
+</template>
+
+<script setup>
+import { getDeptTree, getDeptLazyTree } from '@/api/system/dept'
+import { ElMessage } from 'element-plus';
+import { flowEvent } from '@/api/tickets/ticket';
+import { onMounted } from 'vue';
+const props = defineProps({
+ modelValue: {
+ type: Boolean,
+ default: false
+ },
+ currentDetail: {
+ type: Object,
+ default: () => ({})
+ },
+
+ departmentUsers: {
+ type: Object,
+ default: () => ({})
+ },
+ algorithms: {
+ type: Array,
+ default: () => []
+ },
+ types: {
+ type: Array,
+ default: () => []
+ }
+});
+const emit = defineEmits(['update:modelValue', 'dispatch-success', 'dispatch-error']);
+const formRef = ref(null); // 表单引用
+const loading = ref(false);
+const form = ref({
+ department: '',
+ handler: ''
+});
+const deptTreeData =ref([])
+// 树形选择器配置
+const treeProps = ref({
+ value: 'id', // 对应部门数据的id字段(作为选中值)
+ label: 'name', // 对应部门数据的name字段(作为显示文本)
+ children: 'children' // 对应子部门字段
+});
+// 获取树形数据
+ const getDepartmentTree=() =>{
+ const tenantId = '000000'
+ getDeptTree(tenantId).then(res => {
+ deptTreeData.value = res.data.data
+
+ })
+}
+// 表单校验规则
+const rules = ref({
+ department: [{ required: true, message: '请选择部门', trigger: 'change' }],
+ handler: [{ required: true, message: '请选择处理人', trigger: 'change' }]
+});
+const dialogVisible = computed({
+ get() {
+ return props.modelValue;
+ },
+ set(value) {
+ emit('update:modelValue', value);
+ }
+});
+
+// 处理人列表
+const availableHandlers = computed(() => {
+ return form.value.department ? props.departmentUsers[form.value.department] || [] : [];
+});
+watch(
+ () => props.modelValue,
+ (newVal) => {
+ if (newVal) {
+ resetForm();
+ }
+ },
+ { immediate: true }
+);
+
+// 重置表单
+const resetForm = () => {
+ form.value = {
+ department: '',
+ handler: ''
+ };
+ if (formRef.value) {
+ formRef.value.clearValidate();
+ }
+};
+
+// 关闭对话框
+const handleClose = () => {
+ dialogVisible.value = false;
+ resetForm();
+};
+
+// 部门切换时清空处理人
+const handleDepartmentChange = () => {
+ form.value.handler = '';
+};
+
+// 验证必填字段(currentDetail 中的必填项)
+const validateRequiredFields = () => {
+ if (!props.currentDetail.orderName || !props.currentDetail.orderName.trim()) {
+ ElMessage.warning('请填写工单名称');
+ return false;
+ }
+ if (!props.currentDetail.type) {
+ ElMessage.warning('请选择工单类型');
+ return false;
+ }
+ if (!props.currentDetail.content || !props.currentDetail.content.trim()) {
+ ElMessage.warning('请填写工单内容');
+ return false;
+ }
+ return true;
+};
+
+// 获取算法值
+const getAlgorithmValue = () => {
+ const { aiType } = props.currentDetail;
+ // 检查是否直接匹配 value
+ const isValueMatch = props.algorithms.some(item => item.value === aiType);
+ if (isValueMatch) {
+ return aiType;
+ }
+ // 尝试匹配 dict_value 或 label
+ const matched = props.algorithms.find(
+ item => item.dict_value === aiType || item.label === aiType
+ );
+ return matched ? matched.value : null;
+};
+
+// 提交派发
+const handleSubmit = async () => {
+ try {
+ const valid = await formRef.value.validate();
+ if (!valid) return;
+ if (!validateRequiredFields()) {
+ return;
+ }
+
+ loading.value = true;
+ const algorithmValue = getAlgorithmValue();
+ const data = {
+ id: props.currentDetail.id,
+ status: props.currentDetail.status,
+ isPass: 0, // 0 表示通过
+ eventName: props.currentDetail.orderName,
+ eventNum: props.currentDetail.orderNumber,
+ workOrderTypeDictKey: props.currentDetail.type,
+ content: props.currentDetail.content,
+ createDept: form.value.department,
+ updateUser: form.value.handler,
+ aiType: algorithmValue,
+ };
+
+ // 发送请求
+ const response = await flowEvent(data);
+ if (response.data.code === 0) {
+ ElMessage.success('工单已成功派发');
+ emit('dispatch-success');
+ handleClose();
+ } else {
+ throw new Error(response.data.msg || '派发失败');
+ }
+ } catch (error) {
+ console.error('派发失败:', error);
+ emit('dispatch-error', error);
+ ElMessage.error(error.message || '派发失败,请稍后重试');
+ } finally {
+ loading.value = false;
+ }
+};
+onMounted(()=>{
+ getDepartmentTree()
+})
+</script>
+<style scoped>
+.full-width {
+ width: 100%;
+}
+.el-form {
+margin-top: 20px;}
+</style>
\ No newline at end of file
diff --git a/src/views/tickets/ticketComponent/RecheckDialog.vue b/src/views/tickets/ticketComponent/RecheckDialog.vue
new file mode 100644
index 0000000..30e3673
--- /dev/null
+++ b/src/views/tickets/ticketComponent/RecheckDialog.vue
@@ -0,0 +1,117 @@
+<template>
+ <el-dialog
+ v-model="dialogVisible"
+ title="工单复核"
+ width="30%"
+ append-to-body
+ class="ztzf-dialog-mange"
+ @close="handleClose"
+ >
+ <div class="dialog-footer">
+ <el-button type="primary" @click="handleConfirm(1)">人工复核</el-button>
+ <el-button type="primary" @click="handleConfirm(2)">无人机复核</el-button>
+ </div>
+ </el-dialog>
+</template>
+
+<script>
+import { ElMessageBox, ElLoading } from 'element-plus';
+import { getReviewById, getCreateEventJob } from '@/api/tickets/ticket';
+
+export default {
+ name: 'RecheckDialog',
+ props: {
+ modelValue: {
+ type: Boolean,
+ default: false
+ },
+ recheckData: {
+ type: Object,
+ default: () => ({})
+ }
+ },
+ emits: ['update:modelValue', 'recheck-success'],
+ computed: {
+ dialogVisible: {
+ get() {
+ return this.modelValue;
+ },
+ set(value) {
+ this.$emit('update:modelValue', value);
+ }
+ }
+ },
+ methods: {
+ handleClose() {
+ this.dialogVisible = false;
+ },
+
+ handleConfirm(key) {
+ if (key === 1) {
+ this.handleManualRecheck();
+ } else {
+ this.handleUAVRecheck();
+ }
+ },
+
+ async handleManualRecheck() {
+ try {
+ await getReviewById(this.recheckData.id);
+ this.$message.success('人工复核成功');
+ this.$emit('recheck-success');
+ this.dialogVisible = false;
+ } catch (error) {
+ this.$message.error('人工复核失败');
+ console.error('人工复核失败:', error);
+ }
+ },
+
+ async handleUAVRecheck() {
+ const loading = ElLoading.service({
+ lock: true,
+ text: '复核任务创建中……',
+ background: 'rgba(0, 0, 0, 0.7)',
+ });
+
+ try {
+ const response = await getCreateEventJob(this.recheckData.id);
+ const estimatedTime = response.data.data;
+
+ loading.close();
+
+ await ElMessageBox.confirm(
+ `预计复核执行时间为${estimatedTime}`,
+ '提示',
+ {
+ confirmButtonText: '确定',
+ showCancelButton: false,
+ closeOnClickModal: false,
+ closeOnPressEscape: false,
+ type: 'warning',
+ }
+ );
+
+ this.$message.success('无人机复核任务创建成功');
+ this.$emit('recheck-success');
+ this.dialogVisible = false;
+ } catch (error) {
+ loading.close();
+ if (error !== 'cancel') {
+ this.$message.error('无人机复核任务创建失败');
+ console.error('无人机复核失败:', error);
+ }
+ this.dialogVisible = false;
+ }
+ }
+ }
+};
+</script>
+
+<style scoped>
+.dialog-footer {
+ display: flex;
+ justify-content: center;
+ gap: 20px;
+ padding: 20px 0;
+}
+</style>
\ No newline at end of file
diff --git a/src/views/tickets/ticketComponent/TicketDetailDialog.vue b/src/views/tickets/ticketComponent/TicketDetailDialog.vue
new file mode 100644
index 0000000..3532696
--- /dev/null
+++ b/src/views/tickets/ticketComponent/TicketDetailDialog.vue
@@ -0,0 +1,1439 @@
+<template>
+ <el-dialog
+ class="custom-dialog"
+ align-center
+ v-model="dialogVisible"
+ :title="isEditMode ? '编辑工单' : '工单详情'"
+ width="80%"
+ append-to-body
+ @close="handleClose"
+ >
+ <div class="detail-container">
+ <div class="detail-top-title">
+ <div class="event-title-center event-orderNumber">
+ {{ currentDetail.orderNumber || '工单编号' }}
+ </div>
+ <div class="event-title-center">{{ currentDetail.orderName || '事件名称' }}</div>
+ </div>
+ <div v-if="totalTime" class="event-total-time">总耗时:{{ totalTime }}</div>
+ <!-- 工单状态流程 -->
+ <div class="custom-steps-container">
+ <!-- 标题行 -->
+ <div class="steps-titles">
+ <div
+ v-for="(status, index) in stepStatusList"
+ :key="index"
+ :class="{
+ 'step-title': true,
+ active: index <= stepStatusList.indexOf(String(currentDetail.status)),
+ }"
+ >
+ {{ mapStatus(status) }}
+ </div>
+ </div>
+
+ <!-- Element Steps 组件 -->
+ <el-steps :active="getActiveStep() - 1" align-center class="custom-steps">
+ <el-step v-for="(status, index) in stepStatusList" :key="index">
+ <template #description>
+ <span class="step-description">
+ {{ getStepHandler(status) }}
+ </span>
+ <div class="step-description" v-if="getStepTime(status)">
+ <span class="step-timer"> 耗时:{{ getStepTime(status) }} </span>
+ </div>
+ <div class="step-description">
+ {{ getStepCreateTime(status) }}
+ </div>
+ </template>
+ </el-step>
+ </el-steps>
+ </div>
+
+ <div class="PopUpTableScrolls">
+ <!-- 基本信息表格 -->
+ <el-table :show-header="false" :data="formattedDetailFields" border class="tableCss">
+ <el-table-column prop="label1" label="基本信息" width="150">
+ <template #default="{ row }">
+ <span
+ v-if="
+ currentDetail.status === 0 &&
+ (row.label1 === '关联算法' || row.label1 === '工单名称')
+ "
+ class="required-label"
+ >
+ <span class="required-star">*</span>{{ row.label1 }}
+ </span>
+ <span v-else>{{ row.label1 }}</span>
+ </template>
+ </el-table-column>
+ <el-table-column>
+ <template #default="{ row }">
+ <template
+ v-if="
+ currentDetail.status === 0 &&
+ row.label1 === '工单名称' &&
+ hasProcessingBtnPermission()
+ "
+ >
+ <el-input
+ v-model="currentDetail.orderName"
+ placeholder="请输入工单名称"
+ class="required-input"
+ />
+ </template>
+ <template
+ v-else-if="
+ currentDetail.status === 0 &&
+ row.label1 === '关联算法' &&
+ hasProcessingBtnPermission()
+ "
+ >
+ <el-select
+ v-model="currentDetail.aiType"
+ placeholder="请选择关联算法"
+ class="required-input"
+ >
+ <el-option
+ v-for="item in algorithms"
+ :key="item.value"
+ :label="item.label"
+ :value="item.value"
+ />
+ </el-select>
+ </template>
+ <template v-else>{{ row.value1 }}</template>
+ </template>
+ </el-table-column>
+ <el-table-column prop="label2" label="基本信息" width="150">
+ <template #default="{ row }">
+ <span
+ v-if="currentDetail.status === 0 && row.label2 === '工单内容'"
+ class="required-label"
+ >
+ <span class="required-star">*</span>{{ row.label2 }}
+ </span>
+ <span v-else>{{ row.label2 }}</span>
+ </template>
+ </el-table-column>
+ <el-table-column>
+ <template #default="{ row }">
+ <template
+ v-if="
+ currentDetail.status === 0 &&
+ row.label2 === '工单内容' &&
+ hasProcessingBtnPermission()
+ "
+ >
+ <el-input
+ type="textarea"
+ :maxlength="200"
+ show-word-limit
+ v-model="currentDetail.content"
+ placeholder="请输入工单内容"
+ class="required-input"
+ />
+ </template>
+ <template v-else>{{ row.value2 }}</template>
+ </template>
+ </el-table-column>
+ </el-table>
+
+ <!-- 事件处理详情 -->
+ <div v-if="[3, 4].includes(currentDetail.status)" class="form-section">
+ <div class="section-title">
+ <template v-if="currentDetail.status === 3">
+ <span class="required-label">
+ <span class="required-star">*</span>事件处理详情
+ </span>
+ </template>
+ <template v-else> 事件处理详情</template>
+ </div>
+ <!-- 处理中状态显示输入框 -->
+ <template v-if="currentDetail.status === 3 && hasProcessedAndOverBtnPermission()">
+ <el-input
+ type="textarea"
+ v-model="currentDetail.processingDetail"
+ placeholder="请输入事件处理详情"
+ :rows="4"
+ :maxlength="200"
+ show-word-limit
+ style="width: 100%; margin-bottom: 10px"
+ :disabled="!isCurrentUserAssigned"
+ />
+ </template>
+ <!-- 已完成和已完结状态显示只读文本 -->
+ <template v-else>
+ <div class="readonly-processing-detail">
+ {{ currentDetail.processingDetail }}
+ </div>
+ </template>
+ </div>
+
+ <!-- 上传图片 -->
+ <div v-if="[3].includes(currentDetail.status)" class="form-section uploadImg">
+ <div class="section-title" v-if="hasProcessedAndOverBtnPermission()">
+ <template v-if="currentDetail.status === 3">
+ <span class="required-label"> <span class="required-star">*</span>上传图片 </span>
+ </template>
+ <template v-else> 上传图片</template>
+ </div>
+ <el-upload
+ :disabled="!isCurrentUserAssigned"
+ v-if="hasProcessedAndOverBtnPermission()"
+ ref="uploadRef"
+ :action="'#'"
+ :auto-upload="false"
+ list-type="picture-card"
+ :on-change="handleFileChange"
+ :on-remove="handleUploadRemove"
+ :before-upload="beforeUpload"
+ :file-list="currentDetail.photos || []"
+ :limit="1"
+ accept="image/*"
+ class="detail-upload"
+ >
+ <template v-if="!currentDetail.photos || currentDetail.photos.length < 1">
+ <div class="el-icon-plus">
+ <span>+</span>
+ </div>
+ </template>
+ </el-upload>
+ <div class="el-upload__tip" v-if="hasProcessedAndOverBtnPermission()">
+ (上传照片即可完结工单,只能上传jpg、jpeg、png照片,且不超过5M)
+ </div>
+ </div>
+
+ <!-- 图片和地图部分 -->
+ <div class="media-section">
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <div class="media-box">
+ <div class="media-title">事件图片</div>
+ <div class="media-content">
+ <el-image
+ v-if="currentDetail.mediaUrl"
+ :src="getThumbUrl(currentDetail.mediaUrl)"
+ :preview-src-list="[getPreviewUrl(currentDetail.mediaUrl)]"
+ fit="contain"
+ style="cursor: pointer"
+ >
+ <template #placeholder>
+ <div class="image-placeholder">
+ <i class="el-icon-picture-outline"></i>
+ <span>加载中...</span>
+ </div>
+ </template>
+ <template #error>
+ <div class="image-error">
+ <i class="el-icon-picture-outline"></i>
+ <span>加载失败</span>
+ </div>
+ </template>
+ </el-image>
+ <div v-else class="no-media">暂无图片/视频</div>
+ </div>
+ </div>
+ </el-col>
+ <el-col :span="12">
+ <div class="media-box">
+ <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)"
+ :preview-src-list="[getPreviewUrl(currentDetail.updatePhotoUrl)]"
+ fit="fill"
+ >
+ <template #placeholder>
+ <div class="image-placeholder">
+ <i class="el-icon-picture-outline"></i>
+ <span>加载中...</span>
+ </div>
+ </template>
+ <template #error>
+ <div class="image-error">
+ <i class="el-icon-picture-outline"></i>
+ <span>加载失败</span>
+ </div>
+ </template>
+ </el-image>
+ <div v-else class="no-media">暂无处理图片</div>
+ </div>
+ </template>
+ <template v-else>
+ <div class="media-title">
+ 地图标记事件点
+ <el-popover
+ v-if="currentDetail.status === 3 || currentDetail.status === 0"
+ popper-class="custom-qrcode-popover"
+ :width="120"
+ :visible="currentDetail.showQR && dialogVisible"
+ placement="top"
+ title=""
+ trigger="click"
+ >
+ <template #reference>
+ <img
+ @click.stop="handleQRCode(currentDetail)"
+ class="QRCodeImg"
+ src="@/assets/images/dataCenter/qrCode.svg"
+ alt=""
+ title="事件导航"
+ />
+ </template>
+ <div class="qrcode-content">
+ <div class="close-btn" @click.stop="currentDetail.showQR = false">×</div>
+ <CreateQRcode
+ v-if="currentDetail.showQR && dialogVisible"
+ :lat-and-lon="currentDetail.location"
+ />
+ </div>
+ </el-popover>
+ </div>
+ <div class="media-content">
+ <map-container v-if="dialogVisible" ref="mapContainerRef" />
+ </div>
+ </template>
+ </div>
+ </el-col>
+ </el-row>
+ </div>
+ </div>
+
+ <!-- 操作按钮 -->
+ <div class="dialog-footer1-new">
+ <div
+ :class="currentIndex === 0 ? 'disableds' : ''"
+ class="leftBtn"
+ @click="handlePrev"
+ >
+ 上一页
+ </div>
+ <div class="btngroups">
+ <template v-if="currentDetail.status === 2">
+ <!-- 待审核 -->
+ <el-button
+ v-if="hasReviewBtnPermission()"
+ type="primary"
+ :loading="approveLoading"
+ @click="handleApprove"
+ icon="el-icon-check"
+ >
+ 通过
+ </el-button>
+ <el-button
+ v-if="hasReviewBtnPermission()"
+ type="danger"
+ :loading="rejectLoading"
+ @click="handleReject"
+ icon="el-icon-close"
+ >
+ 不通过
+ </el-button>
+ <el-button @click="handleClose" icon="el-icon-circle-close">取消</el-button>
+ </template>
+ <template v-else-if="currentDetail.status === 0">
+ <el-button
+ v-if="hasProcessingBtnPermission()"
+ type="primary"
+ :loading="dispatchLoading"
+ @click="handleApproveAndDispatch"
+ icon="el-icon-check"
+ >
+ 受理
+ </el-button>
+ <el-button
+ v-if="hasProcessingBtnPermission()"
+ type="danger"
+ :loading="rejectLoading"
+ @click="handleReject"
+ icon="el-icon-close"
+ >
+ 不受理
+ </el-button>
+ <el-button @click="handleClose" icon="el-icon-circle-close">取消</el-button>
+ </template>
+ <template v-if="currentDetail.status === 3">
+ <!-- 处理中 -->
+ <el-button
+ v-if="hasProcessedAndOverBtnPermission()"
+ type="primary"
+ :loading="completeLoading"
+ @click="handleComplete"
+ icon="el-icon-circle-check"
+ :disabled="!isCurrentUserAssigned"
+ >
+ 完成工单
+ </el-button>
+ <el-button @click="handleClose" icon="el-icon-circle-close">取消</el-button>
+ </template>
+ <template v-else-if="currentDetail.status === 4">
+ <!-- 已完成 -->
+ <el-button @click="handleClose" icon="el-icon-circle-close">取消</el-button>
+ </template>
+ </div>
+ <div
+ :class="currentIndex === totalItems - 1 ? 'disableds' : ''"
+ class="leftBtn"
+ @click="handleNext"
+ >
+ 下一页
+ </div>
+ </div>
+ </div>
+ </el-dialog>
+</template>
+
+<script setup>
+import { useStore } from 'vuex'
+import { ref, computed, watch, nextTick } from 'vue';
+import { flowEvent, getStepInfo } from '@/api/tickets/ticket';
+import { getSmallImg, getShowImg } from '@/utils/util';
+import CreateQRcode from '@/components/CreateQRcode/CreateQRcode.vue';
+import { ElMessage} from 'element-plus';
+const store = useStore()
+const userInfo = computed(() => store.state.user.userInfo)
+const isLoading = ref(false);
+// console.log('userInfo',userInfo.value);
+// 定义props
+const props = defineProps({
+ modelValue: {
+ type: Boolean,
+ default: false
+ },
+ currentDetail: {
+ type: Object,
+ default: () => ({})
+ },
+ currentIndex: {
+ type: Number,
+ default: 0
+ },
+ totalItems: {
+ type: Number,
+ default: 0
+ },
+ algorithms: {
+ type: Array,
+ default: () => []
+ },
+ types: {
+ type: Array,
+ default: () => []
+ },
+ permission: {
+ type: Object,
+ default: () => ({})
+ },
+ stepStatusList: {
+ type: Array,
+ default: () => []
+ },
+ workType: {
+ type: Number,
+ default: 0
+ }
+});
+
+// 定义emits
+
+const emit = defineEmits([
+ 'update:modelValue',
+ 'detail-success',
+ 'detail-error',
+ 'prev-item',
+ 'next-item',
+ 'update:currentDetail',
+ 'approve-and-dispatch'
+]);
+
+// 响应式数据
+const stepInfos = ref([]);
+const totalTime = ref('');
+const approveLoading = ref(false);
+const rejectLoading = ref(false);
+const dispatchLoading = ref(false);
+const completeLoading = ref(false);
+const finalizeLoading = ref(false);
+const uploadRef = ref(null);
+const mapContainerRef = ref(null);
+
+// 计算属性
+const dialogVisible = computed({
+ get() {
+ return props.modelValue;
+ },
+ set(value) {
+ emit('update:modelValue', value);
+ }
+});
+
+const isEditMode = computed(() => {
+ return props.currentDetail.status === -1;
+});
+// 是否有权限完成工单
+const isCurrentUserAssigned =computed(()=>{
+ const handleUserId = props.currentDetail?.handle_user_id;
+ const currentUserId = userInfo.value?.user_id;
+ return !!handleUserId && !!currentUserId && String(handleUserId) === String(currentUserId);
+})
+const formattedDetailFields = computed(() => {
+ const fields = [
+ { label: '工单名称', value: props.currentDetail.orderName },
+ {
+ label: '工单类型',
+ value: props.types.find(t => t.value === props.currentDetail.type)?.label || props.currentDetail.type,
+ },
+ { label: '关联任务', value: props.currentDetail.job_name || '/' },
+ { label: '工单创建人', value: props.currentDetail.creator },
+ { label: '当前状态', value: mapStatus(props.currentDetail.status) },
+ { label: '事件地址', value: props.currentDetail.address || props.currentDetail.latAndLon },
+ {
+ label: '关联算法',
+ value: props.algorithms.find(t => t.value === props.currentDetail.aiType)?.label || props.currentDetail.aiType,
+ },
+ { label: '发起单位', value: props.currentDetail.department },
+ { label: '发起任务时间', value: props.currentDetail.startTime },
+ { label: '工单内容', value: props.currentDetail.content },
+ ];
+
+ const filteredFields = fields.filter(field => field.value !== '/');
+ const formattedFields = [];
+ for (let i = 0; i < filteredFields.length; i += 2) {
+ formattedFields.push({
+ label1: filteredFields[i]?.label || '',
+ value1: filteredFields[i]?.value || (filteredFields[i]?.label ? '暂无数据' : ''),
+ label2: filteredFields[i + 1]?.label || '',
+ value2: filteredFields[i + 1]?.value || (filteredFields[i + 1]?.label ? '暂无数据' : ''),
+ });
+ }
+
+ return formattedFields;
+});
+
+// 监听
+watch(
+ () => props.modelValue,
+ (newVal) => {
+ if (newVal) {
+ loadStepInfo();
+ initMap();
+ }
+ }
+);
+
+watch(
+ () => props.currentDetail,
+ (newVal) => {
+ if (newVal && dialogVisible.value) {
+ isLoading.value = true;
+ setTimeout(() => {
+ loadStepInfo();
+ initMap();
+ isLoading.value = false;
+ }, 50);
+
+ }
+ },
+ { deep: true }
+);
+
+// 方法
+const handleClose = () => {
+ dialogVisible.value = false;
+};
+
+const handlePrev = () => {
+ if (props.currentIndex > 0) {
+ emit('prev-item');
+ }
+};
+
+const handleNext = () => {
+ if (props.currentIndex < props.totalItems - 1) {
+ emit('next-item');
+ }
+};
+
+const loadStepInfo = async () => {
+ try {
+ const stepResponse = await getStepInfo(props.currentDetail.orderNumber);
+ const steps = Array.isArray(stepResponse.data.data)
+ ? stepResponse.data.data
+ : stepResponse.data.data?.steps || [];
+
+ const finishedStep = steps.find(s => String(s.status) === '4');
+ totalTime.value = finishedStep && finishedStep.total_time ? finishedStep.total_time : '';
+
+ stepInfos.value = steps.map(step => ({
+ status: String(step.status),
+ name: step.name,
+ time: step.time,
+ create_time: step.create_time,
+ }));
+ } catch (error) {
+ stepInfos.value = [];
+ }
+};
+
+const initMap = () => {
+ nextTick(() => {
+ if (mapContainerRef.value && mapContainerRef.value.initAddEntity && props.currentDetail.location) {
+ mapContainerRef.value.initAddEntity('point', props.currentDetail.location);
+ }
+ });
+};
+
+const mapStatus = (status) => {
+ const statusTextMap = {
+ 2: '待审核',
+ 0: '待处理',
+ 3: '处理中',
+ 4: '已完成',
+ };
+ return statusTextMap[status] || '未知状态';
+};
+
+const getStepHandler = (status) => {
+ const step = stepInfos.value.find(step => step.status === status);
+ return step ? step.name : '';
+};
+
+const getStepTime = (status) => {
+ const step = stepInfos.value.find(step => step.status === status);
+ return step && step.time && step.time !== '0' ? step.time : false;
+};
+
+const getStepCreateTime = (status) => {
+ const step = stepInfos.value.find(step => step.status === status);
+ return step ? step.create_time : null;
+};
+
+const getActiveStep = () => {
+ const arr = props.stepStatusList;
+ const index = arr.indexOf(String(props.currentDetail.status));
+ return index !== -1 ? index + 2 : 1;
+};
+
+const handleQRCode = (val) => {
+ emit('update:currentDetail', {
+ ...props.currentDetail,
+ showQR: !val.showQR
+ });
+};
+
+const getThumbUrl = (url) => {
+ if (!url) return '';
+ return getSmallImg(url);
+};
+
+const getPreviewUrl = (url) => {
+ if (!url) return '';
+ return getShowImg(url);
+};
+
+const hasProcessingBtnPermission = () => {
+ return props.permission && props.permission.tickets_processing_btn === true;
+};
+
+const hasProcessedAndOverBtnPermission = () => {
+ return props.permission && props.permission.tickets_view_processedAndOver === true;
+};
+// 通过不通过
+const hasReviewBtnPermission = () => {
+ return props.permission && props.permission.tickets_review_btn === true;
+};
+
+// 文件操作方法
+const handleFileChange = (file, fileList) => {
+ emit('update:currentDetail', {
+ ...props.currentDetail,
+ photos: fileList
+ });
+};
+
+const handleUploadRemove = (file, fileList) => {
+ emit('update:currentDetail', {
+ ...props.currentDetail,
+ photos: fileList
+ });
+};
+
+const beforeUpload = (file) => {
+ const isImage = file.type.includes('image');
+ const isLt5M = file.size / 1024 / 1024 < 5;
+
+ if (!isImage) {
+ ElMessage.error('只能上传图片文件!');
+ return false;
+ }
+ if (!isLt5M) {
+ ElMessage.error('图片大小不能超过5MB!');
+ return false;
+ }
+ return true;
+};
+
+// 工单操作方法
+const handleApprove = async () => {
+ if (approveLoading.value) return;
+ approveLoading.value = true;
+
+ try {
+ const data = {
+ id: props.currentDetail.id,
+ status: props.currentDetail.status,
+ isPass: 0,
+ eventNum: props.currentDetail.orderNumber,
+ };
+
+ const response = await flowEvent(data);
+ if (response.data.code === 0) {
+ ElMessage.success('工单已通过');
+ emit('detail-success');
+ handleClose();
+ } else {
+ throw new Error(response.data.msg || '操作失败');
+ }
+ } catch (error) {
+ ElMessage.error(error.message || '操作失败,请稍后重试');
+ emit('detail-error', error);
+ } finally {
+ approveLoading.value = false;
+ }
+};
+
+const handleReject = async () => {
+ if (rejectLoading.value) return;
+ rejectLoading.value = true;
+
+ try {
+ const data = {
+ id: props.currentDetail.id,
+ status: props.currentDetail.status,
+ isPass: 1,
+ };
+
+ const response = await flowEvent(data);
+ if (response.data.code === 0) {
+ ElMessage.success('工单未通过');
+ emit('detail-success');
+ handleClose();
+ } else {
+ throw new Error(response.data.msg || '操作失败');
+ }
+ } catch (error) {
+ ElMessage.error(error.message || '操作失败,请稍后重试');
+ emit('detail-error', error);
+ } finally {
+ rejectLoading.value = false;
+ }
+};
+
+const handleApproveAndDispatch = () => {
+ // 触发派发工单操作,由父组件处理
+ emit('approve-and-dispatch', props.currentDetail);
+};
+
+const handleComplete = async () => {
+ if (completeLoading.value) return;
+ completeLoading.value = true;
+ if (!props.currentDetail.processingDetail) {
+ ElMessage.warning('请先填写事件处理详情');
+ completeLoading.value = false;
+ return;
+ }
+
+ if (!props.currentDetail.photos || props.currentDetail.photos.length === 0) {
+ ElMessage.warning('请选择上传图片');
+ completeLoading.value = false;
+ return;
+ }
+ try {
+ const data = {
+ id: props.currentDetail.id,
+ status: props.currentDetail.status,
+ processingDetails: props.currentDetail.processingDetail,
+ eventNum: props.currentDetail.orderNumber,
+ };
+ const file = props.currentDetail.photos?.[0]?.raw || null;
+ const response = await flowEvent(data, file);
+ if (response.data.code === 0) {
+ ElMessage.success('工单已完成');
+ emit('detail-success');
+ handleClose();
+ } else {
+ throw new Error(response.data.msg || '操作失败');
+ }
+ handleClose();
+ } catch (error) {
+ console.error('完成工单失败:', error);
+ ElMessage.error(error.message || '操作失败,请稍后重试');
+ emit('detail-error', error);
+ handleClose();
+ } finally {
+ completeLoading.value = false;
+ handleClose();
+ }
+};
+</script>
+
+<style lang="scss">
+.custom-dialog {
+ height: 96vh;
+// min-height: 50vh;
+ .el-dialog__body {
+ border-top: 0.1rem solid #f0f0f0;
+ }
+}
+
+.custom-qrcode-popover {
+ min-width: 160px !important;
+ min-height: 140px !important;
+ position: relative;
+
+ .qrcode-content {
+ margin-top: 8px;
+
+ .close-btn {
+ position: absolute;
+ top: 2px;
+ right: 2px;
+ width: 16px;
+ height: 16px;
+ line-height: 16px;
+ text-align: center;
+ cursor: pointer;
+ color: #999;
+ font-size: 16px;
+ z-index: 10;
+ }
+ }
+}
+</style>
+<style lang="scss" scoped>
+.uploadImg {
+ margin-bottom: 32px;
+}
+
+.tableCss {
+ width: 100%;
+ margin-bottom: 10px;
+}
+
+.step-timer {
+ position: absolute;
+ right: 92%;
+ top: 50%;
+ transform: translateY(-50%);
+ width: 100px;
+ margin-left: 4px;
+ color: #666;
+ font-size: 12px;
+ white-space: nowrap;
+}
+
+.event-total-time {
+ text-align: center;
+ color: #666;
+ font-size: 15px;
+ margin-bottom: 12px;
+}
+
+.action-bar {
+ margin-bottom: 16px;
+}
+
+.el-dialog {
+ .el-form-item {
+ margin-bottom: 20px;
+ }
+}
+
+.el-upload {
+ width: 100%;
+ display: flex;
+ flex-wrap: wrap;
+
+ :deep(.el-upload-list__item) {
+ transition: all 0.3s ease;
+ }
+
+ :deep(.el-upload-list__item:hover) {
+ background-color: #f5f7fa;
+ }
+}
+
+.el-upload__tip {
+ font-size: 12px;
+ color: #909399;
+ margin-top: 12px;
+}
+
+.create-ticket-form {
+ padding: 20px 10px;
+
+ .form-section {
+ background-color: #fff;
+ border-radius: 4px;
+
+ .el-row {
+ margin-bottom: 16px;
+
+ &:last-child {
+ margin-bottom: 0;
+ }
+ }
+ }
+
+ .location-wrapper {
+ width: 100%; // 修改为100%以适应父容器
+ height: auto; // 修改为auto以自适应内容
+
+ .map-button {
+ width: 100%; // 让按钮填满容器宽度
+ height: 36px; // 与其他输入框保持一致的高度
+ display: flex;
+ align-items: center;
+ justify-content: center;
+
+ i {
+ margin-right: 4px;
+ }
+ }
+
+ .location-text {
+ margin-top: 8px;
+ padding: 8px 12px;
+ background-color: #f5f7fa;
+ border-radius: 4px;
+ color: #606266;
+ font-size: 13px;
+ line-height: 1.4;
+ }
+ }
+
+ .upload-wrapper {
+ .uploader {
+ :deep(.el-upload--picture-card) {
+ width: 120px;
+ height: 100px;
+ line-height: 128px;
+ }
+
+ :deep(.el-upload-list__item) {
+ width: 120px;
+ height: 120px;
+ }
+ }
+
+ .upload-tip {
+ font-size: 12px;
+ color: #909399;
+ line-height: 1.4;
+ margin-top: 8px;
+ }
+ }
+
+ .el-form-item {
+ margin-bottom: 18px;
+
+ &:last-child {
+ margin-bottom: 0;
+ }
+ }
+
+ :deep(.el-form-item__label) {
+ font-weight: 500;
+ color: #606266;
+ }
+ :deep(.el-textarea__inner) {
+ padding: 8px 12px;
+ }
+ .full-width {
+ width: 100%;
+ }
+}
+
+:deep(.el-dialog__body) {
+ border-top: 0.1rem solid #f0f0f0;
+}
+
+.dialog-footer-new {
+ text-align: right;
+ padding-top: 16px;
+ border-top: 1px solid #ebeef5;
+
+ .el-button + .el-button {
+ margin-left: 12px;
+ }
+
+ .el-button {
+ padding: 9px 20px;
+
+ &:last-child {
+ margin-left: 12px;
+ }
+ }
+}
+
+.dialog-footer1-new {
+ position: sticky;
+ bottom: 28px;
+ left: 0;
+ right: 0;
+ background: white;
+ z-index: 10;
+ padding: 16px 20px;
+ border-top: 1px solid #ebeef5;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+
+ .el-button + .el-button {
+ margin-left: 12px;
+ }
+
+ .btngroups {
+ margin-left: 12px;
+ }
+
+ .el-button {
+ padding: 9px 20px;
+
+ &:last-child {
+ margin-left: 12px;
+ margin-right: 12px;
+ }
+ }
+}
+
+.map-container {
+ width: 100%;
+ height: 400px;
+ margin-bottom: 15px;
+ border: 1px solid #dcdfe6;
+ border-radius: 4px;
+ overflow: hidden;
+
+ :deep(.el-input-map) {
+ height: 100%;
+ }
+}
+
+.location-info {
+ margin-top: 10px;
+ padding: 10px;
+ background-color: #f5f7fa;
+ border-radius: 4px;
+
+ p {
+ margin: 5px 0;
+ color: #606266;
+ font-size: 14px;
+ }
+}
+
+.map-select {
+ display: flex;
+ align-items: flex-start;
+ gap: 15px;
+
+ .selected-location {
+ flex: 1;
+ padding: 5px 10px;
+ background-color: #f5f7fa;
+ border-radius: 4px;
+
+ p {
+ margin: 5px 0;
+ color: #606266;
+ font-size: 14px;
+ }
+ }
+}
+
+.preview-image {
+ border-radius: 4px;
+ overflow: hidden;
+ background-color: #f5f7fa;
+}
+
+.image-placeholder,
+.image-error,
+.no-media {
+ height: 200px;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ color: #909399;
+ background-color: #f5f7fa;
+ border-radius: 4px;
+
+ i {
+ font-size: 32px;
+ margin-bottom: 8px;
+ }
+}
+
+.no-media {
+ border: 1px dashed #d9d9d9;
+}
+
+.detail-container {
+ padding: 0 20px;
+
+ .detail-top-title {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+
+ .event-orderNumber {
+ margin-right: 10px;
+ }
+ }
+}
+
+.status-flow {
+ margin-bottom: 20px;
+
+ .custom-steps {
+ .el-step__description {
+ position: relative;
+ margin: 0;
+ padding: 0;
+ }
+
+ // 添加发起任务步骤的特殊样式
+ .init-step-info {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ text-align: center;
+ width: 130px;
+
+ .creator-name {
+ font-size: 14px;
+ font-weight: bold;
+ color: #303133;
+ margin-bottom: 4px;
+ }
+
+ .create-time {
+ font-size: 10px;
+ color: #909399;
+ }
+ }
+
+ // 保持其他步骤的原有样式
+ .step-info {
+ display: flex;
+ margin: 0;
+ padding: 0;
+ justify-content: space-between;
+ align-items: center;
+ width: 200px;
+
+ .process-time {
+ font-size: 10px;
+ color: #909399;
+ text-align: left;
+ margin-left: -50%;
+ padding-left: 0%;
+ flex: 1;
+ }
+
+ .handler-name {
+ font-size: 14px;
+ font-weight: bold;
+ color: #303133;
+ text-align: left;
+ flex: 1;
+ }
+ }
+ }
+}
+
+.basic-info {
+ margin-bottom: 20px;
+}
+
+.media-section {
+ // margin-bottom: 20px;
+
+ .el-row {
+ display: flex;
+ align-items: center;
+ }
+}
+
+.leftBtn {
+ width: 70px;
+ height: 32px;
+ background-color: #999;
+ border-radius: 5px;
+ text-align: center;
+ line-height: 32px;
+ color: #fff;
+ cursor: pointer;
+ opacity: 0.8;
+}
+
+.disableds {
+ background: #999 !important;
+ cursor: not-allowed !important;
+ pointer-events: none;
+ opacity: 0.3 !important;
+}
+
+.PopUpTableScrolls {
+ max-height: 600px;
+ overflow-y: scroll;
+ overflow-x: hidden;
+
+}
+
+.media-box {
+ width: 100%;
+ border: 1px solid #dcdfe6;
+ border-radius: 4px;
+ padding: 10px;
+ background: #fff;
+ box-sizing: border-box;
+
+ .media-title {
+ font-weight: bold;
+ margin-bottom: 5px;
+ display: flex;
+ align-items: center;
+
+ .QRCodeImg {
+ width: 18px;
+ height: 18px;
+ cursor: pointer;
+ padding-bottom: 1px;
+ }
+ }
+
+ .media-content {
+ position: relative;
+ height: 500px;
+
+ :deep(.el-image) {
+ width: 100% !important;
+ max-width: 100%;
+ max-height: 100%;
+
+ .el-image__inner {
+ width: 100%;
+ height: 100%;
+ object-fit: cover !important;
+ }
+ }
+ }
+}
+
+.image-placeholder,
+.image-error,
+.no-media {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ height: 100%;
+ color: #909399;
+ background-color: #f5f7fa;
+ border-radius: 4px;
+}
+
+.image-placeholder i,
+.image-error i {
+ font-size: 32px;
+ margin-bottom: 8px;
+}
+
+.no-media {
+ border: 1px dashed #d9d9d9;
+}
+
+.info-table {
+ margin-bottom: 20px;
+}
+
+.info-item {
+ display: flex;
+ margin-bottom: 10px;
+}
+
+.info-label {
+ font-weight: bold;
+ width: 120px;
+ color: #606266;
+}
+
+.info-value {
+ flex: 1;
+ color: #303133;
+ word-break: break-word;
+}
+
+.readonly-processing-detail {
+ background-color: #f5f7fa;
+ padding: 12px;
+ border-radius: 4px;
+ min-height: 30px;
+ color: #606266;
+ line-height: 1.5;
+ display: block;
+ text-align: left;
+ margin-bottom: 5px;
+
+ &:first-child {
+ font-weight: bold;
+ margin-bottom: 4px;
+ }
+}
+
+// 添加删除按钮样式
+.danger-button {
+ color: #f56c6c;
+}
+
+.danger-button:hover {
+ color: #f78989;
+}
+
+.custom-steps-container {
+ width: 100%;
+ margin: 10px 0;
+}
+
+.steps-titles {
+ display: flex;
+ justify-content: space-between;
+ margin-bottom: 14px;
+ position: relative;
+}
+
+.step-title {
+ text-align: center;
+ flex: 1;
+ font-size: 14px;
+ color: #999;
+ position: relative;
+ padding-bottom: 5px;
+}
+
+.step-title.active {
+ color: #409eff;
+ font-weight: bold;
+}
+
+.event-title-center {
+ text-align: center;
+ font-size: 20px;
+ font-weight: bold;
+ margin-bottom: 5px;
+ color: #333;
+}
+
+.custom-steps {
+ margin-top: -20px;
+
+ :deep(.el-step__description) {
+ margin-top: 8px;
+ padding: 0 20px;
+ }
+}
+
+.step-description {
+ font-size: 14px;
+ color: #666;
+ line-height: 1.5;
+ display: block;
+ text-align: center;
+
+ &:first-child {
+ font-weight: bold;
+ margin-bottom: 4px;
+ }
+}
+
+// 覆盖其他相关样式
+.status-flow {
+ .custom-steps {
+ .el-step__description {
+ position: relative;
+ margin: 0;
+ padding: 0;
+ }
+
+ // 移除之前的样式
+ .init-step-info,
+ .step-info {
+ display: block;
+ width: auto;
+ text-align: center;
+ }
+ }
+}
+/* 新建工单和处理工单的上传组件样式 */
+.create-upload,
+.detail-upload {
+ :deep(.el-upload--picture-card) {
+ width: 120px;
+ height: 100px;
+ line-height: 100px;
+ }
+
+ /* 隐藏额外的上传按钮 */
+ :deep(.el-upload.el-upload--picture-card) {
+ display: none;
+ }
+
+ /* 当没有图片时显示上传按钮 */
+ :deep(.el-upload.el-upload--picture-card:first-child) {
+ display: flex;
+ }
+
+ /* 上传组件的预览图片样式 */
+ :deep(.el-upload-list--picture-card .el-upload-list__item) {
+ width: 120px;
+ height: 100px;
+ }
+}
+
+/* 原有图片预览的样式 */
+.el-image-viewer__wrapper {
+ :deep(.el-image-viewer__img) {
+ max-width: 100%;
+ max-height: 100%;
+ object-fit: contain;
+ }
+}
+
+/* 必填项样式 */
+.required-label {
+ position: relative;
+ display: inline-block;
+
+ .required-star {
+ color: #f56c6c;
+ margin-right: 4px;
+ }
+}
+
+/* 必填输入框样式 */
+.required-input {
+ width: 100%;
+
+ :deep(.el-input__inner),
+ :deep(.el-textarea__inner) {
+ &:focus {
+ border-color: #409eff;
+ }
+ }
+}
+</style>
diff --git a/src/views/wel/components/backlog.vue b/src/views/wel/components/backlog.vue
index c106d5c..d2b2162 100644
--- a/src/views/wel/components/backlog.vue
+++ b/src/views/wel/components/backlog.vue
@@ -155,11 +155,13 @@
const jumporder = val => {
if (checked.value === '智飞工单') {
const id = val.id
+ const status = val.status
getaddOrderRecord(id)
router.replace({
path: `/tickets/orderLog`,
query: {
id,
+ status,
},
})
} else {
diff --git a/src/views/wel/components/proportionStatic.vue b/src/views/wel/components/proportionStatic.vue
index 7601a16..81f6a4c 100644
--- a/src/views/wel/components/proportionStatic.vue
+++ b/src/views/wel/components/proportionStatic.vue
@@ -46,22 +46,32 @@
<script setup>
import useEchartsResize from '@/hooks/useEchartsResize';
-import { getJobEventByStatus } from '@/api/home/index';
+import { getJobEventByStatus, getEventStatusNum } from '@/api/home/index';
+import { getDateRange } from '@/utils/date'
import overviewImg2 from '@/assets/images/workbench/tc2.svg';
import overviewImg3 from '@/assets/images/workbench/tc3.svg';
import overviewImg4 from '@/assets/images/workbench/tc4.svg';
import overviewImg5 from '@/assets/images/workbench/tc5.svg';
import { useRouter } from 'vue-router'
+import _ from 'lodash'
const router = useRouter()
let checked = ref('CURRENT_YEAR');
let timeListStr = ['本周', '本月', '本年'];
let timeListEnum = ['CURRENT_WEEK', 'CURRENT_MONTH', 'CURRENT_YEAR'];
+
+const dateRanges = {
+ today: getDateRange('today'),
+ week: getDateRange('week'),
+ month: getDateRange('month'),
+ year: getDateRange('year'),
+}
+
const params = ref({
- date_enum: 'CURRENT_YEAR',
+ // date_enum: 'CURRENT_YEAR',
device_sn: '',
- end_date: undefined,
- start_date: undefined,
- source:1,//数据来源
+ end_date: dateRanges['year'][1], // undefined,
+ start_date: dateRanges['year'][0], // undefined,
+ source:1,//数据来源
});
const dateSelect = ref('CURRENT_YEAR');
const eventTypeList = ref([
@@ -72,13 +82,14 @@
]);
// 工单统计
const getTypeData = () => {
- getJobEventByStatus(params.value).then(res => {
+ getEventStatusNum(params.value).then(res => {
const resList = res?.data?.data || [];
+ let totalNum = resList.reduce((sum, item) => sum + (item.num || 0), 0) - resList[0].num;
resList.forEach(item => {
eventTypeList.value.forEach(item1 => {
if (item1.name === item.name) {
item1.value = item.num;
- item1.rate = item.rate;
+ item1.rate = _.round((item.num/totalNum)*100, 1);
}
});
});
@@ -94,7 +105,18 @@
let timeClick = (item, index) => {
checked.value = item;
- params.value.date_enum = item;
+ // params.value.date_enum = item;
+ if(item === 'CURRENT_WEEK') {
+ params.value.start_date = dateRanges['week'][0]
+ params.value.end_date = dateRanges['week'][1]
+ } else if(item === 'CURRENT_MONTH') {
+ params.value.start_date = dateRanges['month'][0]
+ params.value.end_date = dateRanges['month'][1]
+ } else {
+ params.value.start_date = dateRanges['year'][0]
+ params.value.end_date = dateRanges['year'][1]
+ }
+
dateSelect.value = item;
getTypeData();
};
@@ -110,7 +132,7 @@
const echartsRef = ref(null);
let { chart } = useEchartsResize(echartsRef);
const initChart = val => {
- const totalNum = val.reduce((sum, item) => sum + item.num, 0);
+ let totalNum = val.reduce((sum, item) => sum + item.num, 0);
const data = {
total: {
title: '总计',
@@ -119,7 +141,7 @@
data: val.map(item => ({
value: item.num,
name: item.name,
- rate: item.rate,
+ rate: _.round((item.num/totalNum)*100, 1),
})),
};
const containerWidth = chart.value.clientWidth;
--
Gitblit v1.9.3