吉安感知网项目-前端
chenyao
2026-02-04 d1bcde4242b59e3c102b5b61c378af1756aeadfe
Merge remote-tracking branch 'origin/master'
30 files modified
19 files deleted
14632 ■■■■■ changed files
applications/drone-command/env/.env 10 ●●●●● patch | view | raw | blame | history
applications/drone-command/env/.env.development 7 ●●●● patch | view | raw | blame | history
applications/drone-command/env/.env.production 4 ●●●● patch | view | raw | blame | history
applications/drone-command/env/.env.test 13 ●●●● patch | view | raw | blame | history
applications/drone-command/src/api/dataCockpit/index.js 20 ●●●●● patch | view | raw | blame | history
applications/drone-command/src/api/home/aggregation.js 34 ●●●●● patch | view | raw | blame | history
applications/drone-command/src/api/home/index.js 149 ●●●●● patch | view | raw | blame | history
applications/drone-command/src/api/home/territory.js 9 ●●●●● patch | view | raw | blame | history
applications/drone-command/src/api/job/task.js 8 ●●●●● patch | view | raw | blame | history
applications/drone-command/src/api/patchManagement/index.js 11 ●●●●● patch | view | raw | blame | history
applications/drone-command/src/api/tickets/orderLog.js 108 ●●●●● patch | view | raw | blame | history
applications/drone-command/src/api/tickets/ticket.js 121 ●●●●● patch | view | raw | blame | history
applications/drone-command/src/axios.js 174 ●●●● patch | view | raw | blame | history
applications/drone-command/src/components/map-container/device-map-container.vue 7 ●●●●● patch | view | raw | blame | history
applications/drone-command/src/page/index/top/index.vue 6 ●●●● patch | view | raw | blame | history
applications/drone-command/src/permission.js 4 ●●●● patch | view | raw | blame | history
applications/drone-command/src/store/modules/user.js 2 ●●●●● patch | view | raw | blame | history
applications/drone-command/src/utils/cesium/useBoundary.js 2 ●●● patch | view | raw | blame | history
applications/drone-command/src/utils/stateToImageMap/drone.js 10 ●●●●● patch | view | raw | blame | history
applications/drone-command/src/views/resource/components/DrawPolygon.vue 2 ●●● patch | view | raw | blame | history
applications/drone-command/src/views/tickets/component/AddEditDetails.vue 622 ●●●●● patch | view | raw | blame | history
applications/drone-command/src/views/tickets/component/SearchBox.vue 313 ●●●●● patch | view | raw | blame | history
applications/drone-command/src/views/tickets/orderLog.vue 528 ●●●●● patch | view | raw | blame | history
applications/drone-command/src/views/tickets/orderLogCopy.vue 1666 ●●●●● patch | view | raw | blame | history
applications/drone-command/src/views/tickets/ticket-copy.vue 4072 ●●●●● patch | view | raw | blame | history
applications/drone-command/src/views/tickets/ticket.vue 2358 ●●●●● patch | view | raw | blame | history
applications/drone-command/src/views/tickets/ticketComponent/CreateTicketDialog.vue 713 ●●●●● patch | view | raw | blame | history
applications/drone-command/src/views/tickets/ticketComponent/DispatchDialog.vue 244 ●●●●● patch | view | raw | blame | history
applications/drone-command/src/views/tickets/ticketComponent/RecheckDialog.vue 117 ●●●●● patch | view | raw | blame | history
applications/drone-command/src/views/tickets/ticketComponent/TicketDetailDialog.vue 1441 ●●●●● patch | view | raw | blame | history
applications/mobile-web-view/env/.env 2 ●●●●● patch | view | raw | blame | history
applications/mobile-web-view/env/.env.development 20 ●●●●● patch | view | raw | blame | history
applications/mobile-web-view/env/.env.localhost 7 ●●●● patch | view | raw | blame | history
applications/mobile-web-view/env/.env.production 16 ●●●● patch | view | raw | blame | history
applications/mobile-web-view/env/.env.test 11 ●●●●● patch | view | raw | blame | history
applications/mobile-web-view/index.html 2 ●●● patch | view | raw | blame | history
applications/mobile-web-view/src/appPages/work/workDetail/index.vue 1023 ●●●●● patch | view | raw | blame | history
applications/mobile-web-view/src/layout/Header.vue 251 ●●●●● patch | view | raw | blame | history
applications/mobile-web-view/src/layout/showTips.vue 189 ●●●●● patch | view | raw | blame | history
applications/mobile-web-view/src/layout/useGlobalWS.js 80 ●●●●● patch | view | raw | blame | history
applications/mobile-web-view/src/page/GatewayPage/GatewayPage.vue 171 ●●●●● patch | view | raw | blame | history
applications/mobile-web-view/src/permission.js 2 ●●● patch | view | raw | blame | history
applications/task-work-order/env/.env 6 ●●●● patch | view | raw | blame | history
applications/task-work-order/env/.env.development 2 ●●● patch | view | raw | blame | history
applications/task-work-order/env/.env.production 5 ●●●● patch | view | raw | blame | history
applications/task-work-order/env/.env.test 25 ●●●●● patch | view | raw | blame | history
applications/task-work-order/src/views/orderView/appConfiguration/process/index.vue 10 ●●●● patch | view | raw | blame | history
applications/task-work-order/src/views/orderView/orderManage/inspectionRequest/index.vue 17 ●●●●● patch | view | raw | blame | history
applications/task-work-order/src/views/orderView/orderManage/orderManage/index.vue 18 ●●●●● patch | view | raw | blame | history
applications/drone-command/env/.env
@@ -1,13 +1,3 @@
/*
 * @Author       : yuan
 * @Date         : 2026-01-07 14:58:30
 * @LastEditors  : yuan
 * @LastEditTime : 2026-02-03 15:43:33
 * @FilePath     : \applications\drone-command\env\.env
 * @Description  :
 * Copyright 2026 OBKoro1, All Rights Reserved.
 * 2026-01-07 14:58:30
 */
#接口地址
VITE_APP_API=/api
applications/drone-command/env/.env.development
@@ -2,7 +2,7 @@
 # @Author       : yuan
 # @Date         : 2026-01-31 14:34:31
 # @LastEditors  : yuan
 # @LastEditTime : 2026-02-03 15:52:27
 # @LastEditTime : 2026-02-03 17:22:33
 # @FilePath     : \applications\drone-command\env\.env.development
 # @Description  : 
 # Copyright 2026 OBKoro1, All Rights Reserved. 
@@ -16,10 +16,7 @@
#开发环境代理地址(推荐本地新建文件 .env.development.local 来进行覆盖)
# VITE_APP_URL=https://wrj.shuixiongit.com/api
VITE_APP_URL=http://192.168.1.168
#新大屏地址
VITE_APP_DASHBOARD_URL='https://wrj.shuixiongit.com/command-center-dashboard/'
VITE_APP_URL=http://192.168.1.33
# 域名
VITE_APP_AREA_NAME=https://wrj.shuixiongit.com
applications/drone-command/env/.env.production
@@ -1,4 +1,3 @@
NODE_ENV='production'
#生产环境配置
@@ -9,9 +8,6 @@
# 域名
VITE_APP_AREA_NAME=https://aisky.org.cn
#新大屏地址
VITE_APP_DASHBOARD_URL='https://aisky.org.cn/command-center-dashboard/'
# ws地址
VITE_APP_WS_API_URL=wss://aisky.org.cn/drone-wss/api/v1/ws
applications/drone-command/env/.env.test
@@ -1,3 +1,13 @@
###
 # @Author       : yuan
 # @Date         : 2026-01-07 14:58:30
 # @LastEditors  : yuan
 # @LastEditTime : 2026-02-04 09:12:33
 # @FilePath     : \applications\drone-command\env\.env.test
 # @Description  :
 # Copyright 2026 OBKoro1, All Rights Reserved.
 # 2026-01-07 14:58:30
###
NODE_ENV = 'test'
#开发环境配置
@@ -5,9 +15,6 @@
# 服务地址
VITE_APP_URL = https://wrj.shuixiongit.com/api
#新大屏地址
VITE_APP_DASHBOARD_URL = 'https://wrj.shuixiongit.com/command-center-dashboard/'
# 域名
VITE_APP_AREA_NAME = https://wrj.shuixiongit.com
applications/drone-command/src/api/dataCockpit/index.js
@@ -2,7 +2,7 @@
 * @Author       : yuan
 * @Date         : 2026-01-09 11:14:04
 * @LastEditors  : yuan
 * @LastEditTime : 2026-02-02 09:45:21
 * @LastEditTime : 2026-02-03 17:21:56
 * @FilePath     : \applications\drone-command\src\api\dataCockpit\index.js
 * @Description  : 
 * Copyright 2026 OBKoro1, All Rights Reserved. 
@@ -100,3 +100,21 @@
        params
    })
}
// 查list --- 区域
export const newAreaDivideList = params => {
    return request({
        url: `/drone-fw/cockpit/dataCockpit/areaDivideList`,
        method: 'get',
        params,
    })
}
// 查list
export const newDefenseSceneManageList = params => {
    return request({
        url: `/drone-fw/cockpit/dataCockpit/defenseSceneManageList`,
        method: 'get',
        params,
    })
}
applications/drone-command/src/api/home/aggregation.js
File was deleted
applications/drone-command/src/api/home/index.js
File was deleted
applications/drone-command/src/api/home/territory.js
File was deleted
applications/drone-command/src/api/job/task.js
@@ -198,6 +198,14 @@
    })
}
export const getDeviceRegionCount = params => {
    return request({
        url: '/drone-device-core/manage/api/v1/devices/getDeviceRegionCount',
        method: 'get',
        params,
    })
}
// 获取区域全部
export const getRegionTreeAll = params => {
    return request({
applications/drone-command/src/api/patchManagement/index.js
@@ -111,4 +111,13 @@
        method: 'get',
        params,
    })
}
}
// 更新图斑数据
export const sdfwUpdate = params => {
    return request({
        url: `/drone-device-core/patches/api/v1/Patches/sdfwUpdate`,
        method: 'put',
        data: params,
    })
}
applications/drone-command/src/api/tickets/orderLog.js
File was deleted
applications/drone-command/src/api/tickets/ticket.js
File was deleted
applications/drone-command/src/axios.js
@@ -6,63 +6,62 @@
 * isToken是否需要token
 */
import axios from 'axios';
import store from '@/store/';
import router from '@/router/';
import { serialize } from '@/utils/util';
import { getToken, removeToken, removeRefreshToken } from '@/utils/auth';
import { isURL, validatenull } from '@/utils/validate';
import { ElMessage } from 'element-plus';
import website from '@/config/website';
import { Base64 } from 'js-base64';
import { baseUrl } from '@/config/env';
import crypto from '@/utils/crypto';
const adminUrl = import.meta.env.VITE_APP_DASHBOARD_URL;
import axios from 'axios'
import store from '@/store/'
import router from '@/router/'
import { serialize } from '@/utils/util'
import { getToken, removeToken, removeRefreshToken } from '@/utils/auth'
import { isURL, validatenull } from '@/utils/validate'
import { ElMessage } from 'element-plus'
import website from '@/config/website'
import { Base64 } from 'js-base64'
import { baseUrl } from '@/config/env'
import crypto from '@/utils/crypto'
// 全局未授权错误提示状态,只提示一次
let isErrorShown = false;
let isErrorShown = false
let _retry = false;
let _retry = false
// 超时时间设置为10分钟,部分接口上传比较慢,如固件上传
axios.defaults.timeout = 600000;
axios.defaults.timeout = 600000
//返回其他状态码
axios.defaults.validateStatus = function (status) {
  return status >= 200 && status <= 500; // 默认的
};
  return status >= 200 && status <= 500 // 默认的
}
//跨域请求,允许保存cookie
axios.defaults.withCredentials = true;
axios.defaults.withCredentials = true
//http request拦截
axios.interceptors.request.use(
  config => {
    // 初始化错误提示状态
    isErrorShown = false;
    isErrorShown = false
    //地址为已经配置状态则不添加前缀
    if (!isURL(config.url) && !config.url.startsWith(baseUrl)) {
      config.url = baseUrl + config.url;
      config.url = baseUrl + config.url
    }
    //安全请求header
    config.headers['areaCode'] = config?.params?.areaCode || store?.state?.user?.userInfo?.detail?.areaCode;
    config.headers['Blade-Requested-With'] = 'BladeHttpRequest';
    config.headers['areaCode'] = config?.params?.areaCode || store?.state?.user?.userInfo?.detail?.areaCode
    config.headers['Blade-Requested-With'] = 'BladeHttpRequest'
    //headers判断是否需要
    const authorization = config.authorization === false;
    const authorization = config.authorization === false
    if (!authorization) {
      config.headers['Authorization'] = `Basic ${Base64.encode(
        `${website.clientId}:${website.clientSecret}`
      )}`;
      )}`
    }
    //headers判断请求是否携带token
    const meta = config.meta || {};
    const isToken = meta.isToken === false;
    const meta = config.meta || {}
    const isToken = meta.isToken === false
    //headers传递token是否加密
    const cryptoToken = config.cryptoToken === true;
    const cryptoToken = config.cryptoToken === true
    //判断传递数据是否加密
    const cryptoData = config.cryptoData === true;
    const token = getToken();
    const cryptoData = config.cryptoData === true
    const token = getToken()
    if (token && !isToken) {
      config.headers[website.tokenHeader] = cryptoToken
        ? 'crypto ' + crypto.encryptAES(token, crypto.cryptoKey)
        : 'bearer ' + token;
        : 'bearer ' + token
    }
    // 开启报文加密
    // if (cryptoData) {
@@ -77,131 +76,118 @@
    // }
    //headers中配置text请求
    if (config.text === true) {
      config.headers['Content-Type'] = 'text/plain';
      config.headers['Content-Type'] = 'text/plain'
    }
    //headers中配置serialize为true开启序列化
    if (config.method === 'post' && meta.isSerialize === true) {
      config.data = serialize(config.data);
      config.data = serialize(config.data)
    }
    return config;
    return config
  },
  error => {
    return Promise.reject(error);
    return Promise.reject(error)
  }
);
)
//http response拦截
axios.interceptors.response.use(
  res => {
    const status = res.data.error_code || res.data.code || res.status;
    const statusWhiteList = website.statusWhiteList || [];
    const message = res.data.msg || res.data.error_description || res.data.message || '系统错误';
    const config = res.config;
    const cryptoData = config.cryptoData === true;
    const status = res.data.error_code || res.data.code || res.status
    const statusWhiteList = website.statusWhiteList || []
    const message = res.data.msg || res.data.error_description || res.data.message || '系统错误'
    const config = res.config
    const cryptoData = config.cryptoData === true
    if (status === 511) {
      ElMessage({
        message: '请联系运维人员上传密钥证书!',
        type: 'error',
      });
      })
      // 获取当前路由名称
      const currentRouteName = router.currentRoute.value.name;
      const currentRouteName = router.currentRoute.value.name
      // 如果当前路由不是登录页,则跳转到登录页
      if (currentRouteName !== '登录页') {
        store.dispatch('FedLogOut').then(() =>
          router.push({
            path: '/login',
          })
        );
        )
      }
      return Promise.reject(new Error(message));
      return Promise.reject(new Error(message))
    }
    //如果在白名单里则自行catch逻辑处理
    if (statusWhiteList.includes(status)) return Promise.reject(res);
    if (statusWhiteList.includes(status)) return Promise.reject(res)
    // 如果是401并且没有重试过,尝试刷新token
    if (status === 401 && !_retry) {
      // 标记此请求已尝试刷新token
      _retry = true;
      _retry = true
      // 调用RefreshToken action来刷新token
      return store
        .dispatch('RefreshToken')
        .then(() => {
          const meta = config.meta || {};
          const isToken = meta.isToken === false;
          const cryptoToken = config.cryptoToken === true;
          const meta = config.meta || {}
          const isToken = meta.isToken === false
          const cryptoToken = config.cryptoToken === true
          // 获取刷新后的token
          const token = getToken();
          const token = getToken()
          if (token && !isToken) {
            config.headers[website.tokenHeader] = cryptoToken
              ? 'crypto ' + crypto.encryptAES(token, crypto.cryptoKey)
              : 'bearer ' + token;
              : 'bearer ' + token
          }
          // 重新发送原来的请求
          return axios(config);
          return axios(config)
        })
        .catch(() => {
          // 首次报错时提示
          if (!isErrorShown) {
            isErrorShown = true;
            isErrorShown = true
            ElMessage({
              message: message,
              type: 'error',
            });
            })
          }
          // 清除token信息
          removeToken();
          removeRefreshToken();
          const env = import.meta.env.VITE_APP_ENV;
          removeToken()
          removeRefreshToken()
          const env = import.meta.env.VITE_APP_ENV
          // 重定向到登录页
          store.dispatch('FedLogOut').then(() => router.push({
              path: '/login'
          }));
          // env === 'development'
          //   ? store.dispatch('FedLogOut').then(() =>
          //       router.push({
          //         path: '/login',
          //       })
          //     )
          //   : store.dispatch('FedLogOut').then(() => window.location.replace(`${adminUrl}#/login`));
          return Promise.reject(new Error(message));
        });
            path: '/login'
          }))
          return Promise.reject(new Error(message))
        })
    }
    // 如果是401并且已经重试过,直接跳转到登录页面
    if (status === 401 && _retry) {
      // 首次报错时提示
      if (!isErrorShown) {
        isErrorShown = true;
        isErrorShown = true
        ElMessage({
          message: '用户令牌过期,请重新登录',
          type: 'error',
        });
        })
      }
      // 清除token信息
      removeToken();
      removeRefreshToken();
      removeToken()
      removeRefreshToken()
      // 重定向到登录页
      //   store.dispatch('FedLogOut').then(() => router.push({
      //       path: '/login'
      //   }));
      const env = import.meta.env.VITE_APP_ENV;
      env === 'development'
        ? store.dispatch('FedLogOut').then(() =>
            router.push({
              path: '/login',
            })
          )
        : store.dispatch('FedLogOut').then(() => window.location.replace(`${adminUrl}#/login`));
      return Promise.reject(new Error(message));
      store.dispatch('FedLogOut').then(() => router.push({
        path: '/login'
      }))
      return Promise.reject(new Error(message))
    }
    // 如果请求为oauth2错误码则首次报错时提示
    if (status > 2000 && !validatenull(res.data.error_description)) {
      // 首次报错时提示
      if (!isErrorShown) {
        isErrorShown = true;
        isErrorShown = true
        ElMessage({
          message: message,
          type: 'error',
        });
        })
      }
      return Promise.reject(new Error(message));
      return Promise.reject(new Error(message))
    }
    // 如果请求为非200默认统一处理
    if (status !== 200 && status !== 0) {
@@ -209,19 +195,19 @@
        ElMessage({
          message: message,
          type: 'error',
        });
        })
      }
      return Promise.reject(new Error(message));
      return Promise.reject(new Error(message))
    }
    // 解析加密报文
    if (cryptoData) {
      res.data = JSON.parse(crypto.decryptAES(res.data, crypto.aesKey));
      res.data = JSON.parse(crypto.decryptAES(res.data, crypto.aesKey))
    }
    return res;
    return res
  },
  error => {
    return Promise.reject(new Error(error));
    return Promise.reject(new Error(error))
  }
);
)
window._request = axios
export default axios;
export default axios
applications/drone-command/src/components/map-container/device-map-container.vue
@@ -49,9 +49,8 @@
import CommonCesiumMap from '@/components/map-container/common-cesium-map.vue'
import { buildEllipsePositions } from '@/utils/cesium/shapeTools'
import { AREA_TYPE_STYLE_MAP, BUFFER_LEVEL_STYLES, DEFAULT_AREA_STYLE } from '@ztzf/constants'
import { fwAreaDivideListApi } from '@/views/areaManage/partition/partitionApi'
import { fwDefenseSceneManageListApi } from '@/views/areaManage/sceneManage/sceneManageApi'
import { newAreaDivideList, newDefenseSceneManageList } from '@/api/dataCockpit/index'
import { newCockpitAggregationApi } from '@/api/dataCockpit'
import layerControlIcon from '@/assets/images/dataCockpit/layerControl.png'
@@ -1142,7 +1141,7 @@
const loadPartitions = async () => {
    if (!viewer) return
    try {
        const res = await fwAreaDivideListApi({
        const res = await newAreaDivideList({
            isSetSceneManage: 1,
            flyTime: dayjs().format('YYYY-MM-DD HH:mm:ss'),
        })
@@ -1306,7 +1305,7 @@
const loadCommandPosts = async () => {
    try {
        const res = await fwDefenseSceneManageListApi({
        const res = await newDefenseSceneManageList({
            time: dayjs().format('YYYY-MM-DD HH:mm:ss')
        })
        renderCommandPosts(res?.data?.data ?? [])
applications/drone-command/src/page/index/top/index.vue
@@ -93,16 +93,12 @@
        cancelButtonClass: 'command-message-box-cancel',
      }).then(() => {
        this.$store.dispatch('LogOut').then(() => {
          const env = import.meta.env.VITE_APP_ENV
          const adminUrl = import.meta.env.VITE_APP_DASHBOARD_URL
                    this.$router.push({ path: '/login' })
          // env === 'development' ? this.$router.push({ path: '/login' }) : window.location.replace(`${adminUrl}#/login`)
        })
      })
    },
    jumpMH () {
      // const adminUrl = import.meta.env.VITE_APP_DASHBOARD_URL
      // window.location.href = `${adminUrl}#/gatewayPage`
    },
  },
}
applications/drone-command/src/permission.js
@@ -18,8 +18,6 @@
NProgress.configure({ showSpinner: false })
const lockPage = '/lock' //锁屏页
const urlParams = getUrlParams(window.location.href)
const adminUrl = import.meta.env.VITE_APP_DASHBOARD_URL
const env = import.meta.env.VITE_APP_ENV
function findRouteByPath (routes, targetPath) {
  // 遍历数组中的每个路由对象
@@ -96,7 +94,6 @@
        store.dispatch('FedLogOut').then(() => {
          next({ path: '/login' })
        })
        // store.dispatch('FedLogOut').then(() =>  window.location.replace(`${adminUrl}#/login`));
      } else {
        const meta = to.meta || {}
        const query = to.query || {}
@@ -122,7 +119,6 @@
      next()
    } else {
            next('/login')
      // env === 'development' ? next('/login') : window.location.replace(`${adminUrl}#/login`)
    }
  }
})
applications/drone-command/src/store/modules/user.js
@@ -272,8 +272,6 @@
    GetMenu({ commit, dispatch }, tenantId) {
      const errLogOut = () => {
        dispatch('LogOut').then(()=>{
          const env = import.meta.env.VITE_APP_ENV
          const adminUrl = import.meta.env.VITE_APP_DASHBOARD_URL
                    window.location.href = "/drone-command/login"
          // env === 'development'
          //   ? window.location.href = "/drone-command/login"
applications/drone-command/src/utils/cesium/useBoundary.js
@@ -4,7 +4,7 @@
import * as Cesium from 'cesium'
import { PolyGradientMaterial } from '@/utils/cesium/Material'
import { useStore } from 'vuex'
import { getDeviceRegion, getDeviceRegionCount } from '@/api/home/aggregation'
import { getDeviceRegion, getDeviceRegionCount } from '@/api/job/task'
import userStore from '@/store/modules/user'
import { areaCodeToArr, getContourByCode, getDockPolyLine, getLnglatAltitude } from '@/utils/cesium/mapUtil'
import getBaseConfig from '@/buildConfig/config'
applications/drone-command/src/utils/stateToImageMap/drone.js
@@ -1,13 +1,3 @@
/*
 * @Author: shuishen 1109946754@qq.com
 * @Date: 2025-04-17 20:17:12
 * @LastEditors: shuishen 1109946754@qq.com
 * @LastEditTime: 2025-04-17 20:28:08
 * @FilePath: \command-center-dashboard\src\utils\stateToImageMap\drone.js
 * @Description:
 *
 * Copyright (c) 2025 by shuishen, All Rights Reserved.
 */
import endingImg from '@/assets/images/aiNowFly/ending-offline.png'
import endingHighImg from '@/assets/images/aiNowFly/ending-high.png'
applications/drone-command/src/views/resource/components/DrawPolygon.vue
@@ -6,7 +6,7 @@
import * as Cesium from 'cesium';
import { ElMessage } from 'element-plus';
import { sdfwUpdate } from '@/api/home/territory';
import { sdfwUpdate } from '@/api/patchManagement';
let curPolygonPosition = [];
let savePolygonPosition = [];
applications/drone-command/src/views/tickets/component/AddEditDetails.vue
File was deleted
applications/drone-command/src/views/tickets/component/SearchBox.vue
File was deleted
applications/drone-command/src/views/tickets/orderLog.vue
File was deleted
applications/drone-command/src/views/tickets/orderLogCopy.vue
File was deleted
applications/drone-command/src/views/tickets/ticket-copy.vue
File was deleted
applications/drone-command/src/views/tickets/ticket.vue
File was deleted
applications/drone-command/src/views/tickets/ticketComponent/CreateTicketDialog.vue
File was deleted
applications/drone-command/src/views/tickets/ticketComponent/DispatchDialog.vue
File was deleted
applications/drone-command/src/views/tickets/ticketComponent/RecheckDialog.vue
File was deleted
applications/drone-command/src/views/tickets/ticketComponent/TicketDetailDialog.vue
File was deleted
applications/mobile-web-view/env/.env
@@ -23,5 +23,3 @@
#地址搜索服务
VITE_APP_SEARCH_URL = https://wrj.shuixiongit.com/gd-restapi-amap
#算法平台地址
VITE_APP_AI_PLAT_URL = http://192.168.1.54:30800
applications/mobile-web-view/env/.env.development
@@ -1,13 +1,3 @@
###
 # @Author       : yuan
 # @Date         : 2025-06-18 11:40:10
 # @LastEditors  : yuan
 # @LastEditTime : 2025-09-27 15:06:24
 # @FilePath     : \.env.development
 # @Description  :
 # Copyright 2025 OBKoro1, All Rights Reserved.
 # 2025-06-18 11:40:10
###
#开发环境配置
VITE_APP_ENV = 'development'
@@ -27,13 +17,10 @@
# 航线文件地址
VITE_APP_AIRLINE_URL = https://wrj.shuixiongit.com/minio/cloud-bucket
VITE_APP_PICTURE_URL = https://wrj.shuixiongit.com/aiskyminio/cloud-bucket
#管理后台地址
VITE_APP_ADMIN_URL = 'https://wrj.shuixiongit.com/manage/'
#内嵌航线规划iframe地址
VITE_APP_WAY_LINE_PLAN = 'https://wrj.shuixiongit.com/bigScreen/#/layout/iframeWayline'
# 地形存放地址
VITE_APP_TERRAIN_URL = https://wrj.shuixiongit.com/aiskyminio/cloud-bucket/ztzf_terrain/
# 行政区划存放地址
VITE_APP_REGION_URL = https://wrj.shuixiongit.com/aiskyminio/cloud-bucket/ztzf_region
@@ -41,14 +28,9 @@
# 国土举证 图片和视频
VITE_APP_MEDIAPANEL_API_URL = https://wrj.shuixiongit.com/minio/cloud-bucket
#域名
VITE_APP_AREA_NAME = https://wrj.shuixiongit.com
# arcgis服务图层地址
VITE_APP_ARCGIS_URL = https://wrj.shuixiongit.com/arcgis116
#地址搜索服务
VITE_APP_SEARCH_URL = https://wrj.shuixiongit.com/gd-restapi-amap
# 3Dtiles地址
VITE_APP_3DTILE_URL = https://wrj.shuixiongit.com/aiskyminio/cloud-bucket/ztzf_tiles
applications/mobile-web-view/env/.env.localhost
@@ -17,13 +17,10 @@
VITE_APP_AIRLINE_URL = http://192.168.1.227:80/minio/cloud-bucket
VITE_APP_PICTURE_URL = https://wrj.shuixiongit.com/aiskyminio/cloud-bucket
#管理后台地址
VITE_APP_ADMIN_URL = 'http://192.168.1.227:5174/manage/'
#内嵌航线规划iframe地址
VITE_APP_WAY_LINE_PLAN = 'http://192.168.1.227:80/bigScreen/#/layout/iframeWayline'
# 地形存放地址
VITE_APP_TERRAIN_URL = https://wrj.shuixiongit.com/aiskyminio/cloud-bucket/ztzf_terrain/
# 行政区划存放地址
VITE_APP_REGION_URL = https://wrj.shuixiongit.com/aiskyminio/cloud-bucket/ztzf_region
@@ -31,8 +28,6 @@
# 国土举证 图片和视频
VITE_APP_MEDIAPANEL_API_URL = http://192.168.1.227:80/minio/cloud-bucket
#域名
VITE_APP_AREA_NAME = http://192.168.1.227:80/dji_in_rectangle
# arcgis服务图层地址
VITE_APP_ARCGIS_URL = http://192.168.1.227:80/arcgis116
applications/mobile-web-view/env/.env.production
@@ -4,10 +4,10 @@
 # @LastEditors  : yuan
 # @LastEditTime : 2025-09-27 15:09:17
 # @FilePath     : \.env.production
 # @Description  :
 # Copyright 2025 OBKoro1, All Rights Reserved.
 # @Description  :
 # Copyright 2025 OBKoro1, All Rights Reserved.
 # 2025-09-26 16:06:57
###
###
#生产环境配置
VITE_APP_ENV = 'production'
@@ -27,14 +27,10 @@
VITE_APP_PICTURE_URL = https://wrj.shuixiongit.com/aiskyminio/cloud-bucket
# 管理后台地址
VITE_APP_ADMIN_URL = 'https://aisky.org.cn/manage/'
#内嵌航线规划iframe地址
VITE_APP_WAY_LINE_PLAN = 'https://aisky.org.cn/bigScreen/#/layout/iframeWayline'
# 地形存放地址
VITE_APP_TERRAIN_URL = https://wrj.shuixiongit.com/aiskyminio/cloud-bucket/ztzf_terrain/
# 行政区划存放地址
VITE_APP_REGION_URL = https://wrj.shuixiongit.com/aiskyminio/cloud-bucket/ztzf_region
@@ -42,14 +38,8 @@
# 国土举证 图片和视频
VITE_APP_MEDIAPANEL_API_URL =  https://aisky.org.cn/minio/cloud-bucket
#域名
VITE_APP_AREA_NAME = https://aisky.org.cn
# arcgis服务图层地址
VITE_APP_ARCGIS_URL =  https://aisky.org.cn/arcgis116
#地址搜索服务
VITE_APP_SEARCH_URL = https://aisky.org.cn/gd-restapi-amap
# 3Dtiles地址
VITE_APP_3DTILE_URL = https://wrj.shuixiongit.com/aiskyminio/cloud-bucket/ztzf_tiles
applications/mobile-web-view/env/.env.test
@@ -24,14 +24,9 @@
VITE_APP_AIRLINE_URL = https://wrj.shuixiongit.com/minio/cloud-bucket
VITE_APP_PICTURE_URL = https://wrj.shuixiongit.com/aiskyminio/cloud-bucket
#管理后台地址
VITE_APP_ADMIN_URL = 'https://wrj.shuixiongit.com/manage/'
#内嵌航线规划iframe地址
VITE_APP_WAY_LINE_PLAN = 'https://wrj.shuixiongit.com/bigScreen/#/layout/iframeWayline'
# 地形存放地址
VITE_APP_TERRAIN_URL = https://wrj.shuixiongit.com/aiskyminio/cloud-bucket/ztzf_terrain/
# 行政区划存放地址
VITE_APP_REGION_URL = https://wrj.shuixiongit.com/aiskyminio/cloud-bucket/ztzf_region
@@ -39,11 +34,5 @@
# 国土举证 图片和视频
VITE_APP_MEDIAPANEL_API_URL = https://wrj.shuixiongit.com/minio/cloud-bucket
#域名
VITE_APP_AREA_NAME = https://wrj.shuixiongit.com
# arcgis服务图层地址
VITE_APP_ARCGIS_URL = https://wrj.shuixiongit.com/arcgis116
# 3Dtiles地址
VITE_APP_3DTILE_URL = https://wrj.shuixiongit.com/aiskyminio/cloud-bucket/ztzf_tiles
applications/mobile-web-view/index.html
@@ -28,7 +28,7 @@
        <link rel="stylesheet" href="/iconfont/common/iconfont.css" />
        <link rel="stylesheet" href="/css/loading.css" />
        <link rel="stylesheet" href="/css/saber.css" />
        <title>掌控智飞</title>
        <title></title>
    </head>
    <body>
applications/mobile-web-view/src/appPages/work/workDetail/index.vue
@@ -10,641 +10,82 @@
                </van-swipe>
                <van-image-preview v-model:show="previewShow" :images="getImageList" :initial-index="previewIndex" />
                <div class="detailTitle">
                    <div class="titleText">
                        <div class="itemStatus">
                            <span v-if="currentDetail.status === 0" style="background-color: #ff7411"></span>
                            <span v-else-if="currentDetail.status === 2" style="background-color: #ff472f"></span>
                            <span v-else-if="currentDetail.status === 3" style="background-color: #ffc300"></span>
                            <span v-else-if="currentDetail.status === 4" style="background-color: #06d957"></span>
                            <div>{{ currentDetail.event_name }}</div>
                        </div>
                        <div class="timeNavigation">
                            <p>{{ formatDate(currentDetail.create_time) }}</p>
                            <img @click="jumpMap(currentDetail)" src="/src/appDataSource/appwork/navigation.svg"
                                alt="" />
                        </div>
                    </div>
                </div>
            </div>
        </div>
        <!-- 步骤条 -->
        <div class="stepBox">
            <div class="stepContainer">
                <van-steps direction="vertical" :active="currentStep">
                    <van-step v-for="(step, index) in displayedSteps" :key="step.status">
                        <div class="horizontal-step">
                            <span class="step-title" :class="getStatusClass(index)">{{ step.title }}</span>
    <!-- 工单内容 -->
    <div class="worderContainer">
      <div class="workOrderContent">
        <div class="workOrderTitle">工单内容</div>
        <div class="workOrderContainer">
          <div class="orderRow">
            <div class="rowTitle">工单编号</div>
            <div>{{ workDetailData.eventNum }}</div>
          </div>
          <div class="orderRow">
            <div class="rowTitle">工单处置人</div>
            <div>{{ workDetailData.disposeUserName }}</div>
          </div>
          <div class="orderRow">
            <div class="rowTitle">处置部门</div>
            <div>{{ workDetailData.disposeDeptName }}</div>
          </div>
          <div class="orderRow">
            <div class="rowTitle">拍摄时间</div>
            <div>{{ workDetailData.shootTime }}</div>
          </div>
          <div class="orderRow">
            <div class="rowTitle">分发人员</div>
            <div>{{ workDetailData.distributeUserName }}</div>
          </div>
          <div class="orderRow">
            <div class="rowTitle">分发部门</div>
            <div>{{ workDetailData.distributeDeptName }}</div>
          </div>
                            <div class="step-desc" v-if="stepResponse[index]">
                                <span>{{ stepResponse[index].name || '' }}</span>
                                <span>{{ stepResponse[index].create_time || '' }}</span>
                            </div>
                        </div>
                    </van-step>
                </van-steps>
            </div>
        </div>
        <!-- 工单内容 -->
        <div class="worderContainer">
            <div class="workOrderContent">
                <div class="workOrderTitle">工单内容</div>
                <div class="workOrderContainer">
                    <div class="orderRow">
                        <span class="required-mark" v-if="isEditable">*</span>
                        <div class="rowTitle">工单名称</div>
                        <div v-if="!isEditable">{{ currentDetail.event_name }}</div>
                        <van-field v-else v-model="currentDetail.event_name" name="event_name" required
                            input-align="right" placeholder="请输入" class="custom-field" />
                    </div>
                    <div class="orderRow">
                        <div class="rowTitle">工单编号</div>
                        <div>{{ currentDetail.event_num }}</div>
                    </div>
                    <div class="orderRow">
                        <div class="rowTitle">工单类型</div>
                        <div>{{ workOrderTypeName }}</div>
                    </div>
                    <div class="orderRow" v-if="currentDetail.work_type == '0'">
                        <div class="rowTitle">关联任务</div>
                        <div>{{ currentDetail.job_name }}</div>
                    </div>
                    <div class="orderRow">
                        <div class="rowTitle">工单创建人</div>
                        <div>{{ currentDetail.event_num?.slice(0, 2) === 'AI' ? '智飞agent' : currentDetail.create_user }}
                        </div>
                    </div>
                    <div class="orderRow">
                        <div class="rowTitle">事件地址</div>
                        <div class="addressBox" @click="jumpMap(currentDetail)">
                            <div class="rowAddress">{{ currentDetail.address }}</div>
                            <img class="pointing" src="/src/appDataSource/appwork/pointing.svg" alt="" />
                        </div>
                    </div>
                    <div class="orderRow">
                        <div class="guanlian">
                            <span class="required-mark" v-if="isEditable">*</span>
                            <div class="rowTitle">关联算法</div>
                        </div>
                        <div v-if="!isEditable">{{ currentDetail.ai_types }}</div>
                        <div v-else>
                            <span @click="openselect" class="selectTrigger">
                                {{ currentDetail.ai_types }}
                                <img class="downpointing" src="/src/appDataSource/appwork/downpointing.svg" alt="" />
                            </span>
                            <van-popup v-model:show="showPicker" destroy-on-close position="bottom"
                                :close-on-click-overlay="true">
                                <van-picker v-model="selectedValues" title="选择关联算法" :columns="columns"
                                    @confirm="onConfirm" @cancel="onCancel" />
                            </van-popup>
                        </div>
                    </div>
                    <div class="orderRow">
                        <div class="rowTitle">发起部门</div>
                        <div>{{ currentDetail.dept_name }}</div>
                    </div>
                    <div class="orderRow">
                        <div class="rowTitle">发起任务时间</div>
                        <div>{{ currentDetail.create_time }}</div>
                    </div>
                    <div class="orderRow">
                        <div class="rowTitle">所属机巢</div>
                        <div>{{ currentDetail.device_names }}</div>
                    </div>
                    <div class="orderRow">
                        <span class="required-mark" v-if="isEditable">*</span>
                        <div class="rowTitle">工单内容</div>
                        <div v-if="!isEditable">{{ currentDetail.content }}</div>
                        <van-field v-else class="custom-field" input-align="right" v-model="currentDetail.content"
                            name="remark" required placeholder="请输入" />
                    </div>
                    <div class="rowCard" v-if="isEditableProcess">
                        <div class="guanlian">
                            <span class="required-mark">*</span>
                            <div class="rowTitle">
                                上传图片
                                <span>(只能上传jpg、jpeg、png照片,且不超过5M)</span>
                            </div>
                        </div>
                        <div>
                            <van-field name="uploader">
                                <template #input>
                                    <van-uploader :disabled="!isCurrentUserAssigned" v-model="fileList"
                                        :after-read="afterRead" :max-count="1" :max-size="5 * 1024 * 1024"
                                        @oversize="onOversize" :before-read="beforeRead" @delete="handleDelete"
                                        accept="image/jpeg,image/jpg,image/png" />
                                </template>
                            </van-field>
                        </div>
                    </div>
                    <div class="orderRow" v-if="isEditableProcess">
                        <span class="required-mark">*</span>
                        <div class="rowTitle">事件处理详情</div>
                        <van-field class="custom-field" input-align="right" v-model="currentDetail.processingDetail"
                            name="event_name" required placeholder="请输入" :disabled="!isCurrentUserAssigned" />
                    </div>
                    <div class="orderRow" v-if="isComplent">
                        <div class="rowTitle">事件处理详情</div>
                        <div>{{ currentDetail.processingDetail }}</div>
                    </div>
                </div>
            </div>
        </div>
        <!-- 操作按钮 -->
        <div class="actionButton">
            <div class="floatingBtn leftFloatingBtn" @click="leftClick">
                <img src="/src/appDataSource/appwork/leftBtn1.svg" alt="" />
            </div>
            <div class="paginationInfo">
                <span>{{ currentPage }} / {{ totalRecords }}</span>
            </div>
            <div class="btngroups" v-if="currentDetail.status === 2">
                <van-button type="primary" color="#FF452C" round text="不通过" @click="rejectTicket"></van-button>
                <van-button type="primary" color="#1D6FE9" round text="通过" @click="approveTicket"></van-button>
            </div>
            <div class="btngroups" v-else-if="currentDetail.status === 0">
                <van-button type="primary" color="#FF452C" round text="不受理" @click="rejectTicket"></van-button>
                <van-button type="primary" color="#1D6FE9" round text="受理" @click="approveAndDispatch"></van-button>
            </div>
            <div class="btngroups" v-else-if="currentDetail.status === 3">
                <van-button type="primary" color="#FF452C" round text="取消" @click="cancellation"></van-button>
                <van-button :disabled="!isCurrentUserAssigned" type="primary" color="#1D6FE9" round text="完成工单"
                    @click="completeTicket"></van-button>
            </div>
            <div class="btngroups back-btn" v-else>
                <van-button type="primary" round color="#FF730F" text="返回" @click="cancellation"></van-button>
            </div>
            <div class="floatingBtn rightFloatingBtn" @click="rightClick">
                <img src="/src/appDataSource/appwork/rightBtn1.svg" alt="" />
            </div>
        </div>
        <!--派发工单对话框-->
        <van-dialog v-model:show="isshowDispatch" title="派发工单" show-cancel-button @confirm="submitDispatch"
            @cancel="dispatchCancel">
            <van-field v-model="dispatchForm.departmentName" is-link readonly label="选择部门" placeholder="请选择部门"
                @click="openPicker('department')" />
            <van-field v-model="dispatchForm.handlerName" is-link readonly label="选择处理人" placeholder="请选择处理人"
                @click="openPicker('processor')" :disabled="!dispatchForm.department" />
        </van-dialog>
        <teleport to="body">
            <van-popup v-model:show="showGlobalPicker" position="bottom">
                <!-- 部门选择器 -->
                <van-picker v-if="currentPickerType === 'department'" v-model="selectedDepartment" title="选择部门"
                    :columns="departments" @confirm="onDepartmentConfirm" @cancel="onPickerCancel" />
                <!-- 处理人选择器 -->
                <van-picker v-if="currentPickerType === 'processor'" v-model="selectedProcessor" title="选择处理人"
                    :columns="availableDispatchHandlers" @confirm="onProcessorConfirm" @cancel="onPickerCancel" />
            </van-popup>
        </teleport>
          <div class="orderRow">
            <div class="rowTitle">分发时间</div>
            <div>{{ workDetailData.distributeTime }}</div>
          </div>
          <div class="orderRow">
            <div class="rowTitle">工单位置</div>
            <div class="rowAddress" @click="jumpMap(workDetailData)">{{ workDetailData.eventLocation }}</div>
          </div>
        </div>
      </div>
    </div>
    </div>
</template>
<script setup>
import {ALL_WORK_ORDER_TYPE_OPTIONS} from '@ztzf/constants'
import { getDeviceRegion } from '@/api/home/aggregation'
import { usePermission } from '@/appPages/work/usePermission'
import { showToast, showNotify, showImagePreview } from 'vant'
import { getShowImg, getSmallImg } from '@/utils/util'
import { useRoute } from 'vue-router'
import { getStepInfo, getList, flowEvent, getTicketInfo } from '/src/api/work/index.js'
import { showToast, showNotify, showImagePreview } from 'vant'
import dayjs from 'dayjs'
import _ from 'lodash'
import { useStore } from 'vuex'
const store = useStore()
const userInfo = computed(() => store?.state?.user?.userInfo)
const { permission, getButtonsApi } = usePermission()
const showPicker = ref(false)
const selectedValues = ref([])
const columns = ref([])
const allAlgorithms = ref([])
const algorithms = ref([])
const types = ref([]) //工单类型
const eventNum = ref('')
const currentStatus = ref(0)
const stepResponse = ref([])
const currentStep = ref(0)
const route = useRoute()
const currentDetail = ref({ status: 0 })
const currentIndex = ref(null) //当前显示数据索引
const departments = ref([]) //部门
const departmentUsers = ref({})
const fileList = ref([])
// 机巢数据
let machineData = ref([])
// 处理人选择相关
const processorColumns = ref([])
// 可编辑状态
const isEditable = computed(() => currentDetail.value.status === 0)
const isEditableProcess = computed(() => currentDetail.value.status === 3)
const isComplent = computed(() => currentDetail.value.status === 4)
// 工单内容
const workDetailData = ref({})
// 分页相关
const currentPage = ref(1)
const pageSize = ref(1)
const totalRecords = ref(0)
const formatDate = dateString => {
    return dayjs(dateString).format('MM/DD HH:mm')
}
const allStepConfigs = ref([
    { title: '待审核', status: 2 },
    { title: '待处理', status: 0 },
    { title: '处理中', status: 3 },
    { title: '已完成', status: 4 },
])
const hasProcessingBtnPermission = () => {
    return permission.value && permission.value.tickets_processing_btn === true
}
// 完成工单
const hasProcessedAndOverBtnPermission = () => {
    return permission.value && permission.value.tickets_view_processedAndOver === true
}
// 通过不通过
const hasReviewBtnPermission = () => {
    return permission.value && permission.value.tickets_review_btn === true
}
// 是否有权限完成工单
const isCurrentUserAssigned = computed(() => {
    const handleUserId = currentDetail.value?.handle_user_id
    const currentUserId = userInfo.value?.user_id
    return !!handleUserId && !!currentUserId && String(handleUserId) === String(currentUserId)
// 预览图片
const previewShow = ref(false)
const previewIndex = ref(0)
const getImageList = computed(() => {
    const imageArr = []
    const detail = workDetailData.value
    if (detail.eventImageUrl) {
        const smallUrl = getSmallImg(detail.eventImageUrl)
        imageArr.push(smallUrl)
    }
    return imageArr
})
const getStatusClass = index => {
    if (index <= currentStep.value) {
        const status = Number(stepResponse.value[index]?.status) || 0
        const statusClasses = {
            2: 'status-pending', // 待审核
            0: 'status-waiting', // 待处理
            3: 'status-processing', // 处理中
            4: 'status-completed', // 已完成
        }
        return statusClasses[status] || ''
    }
    return 'status-default'
}
// 关联算法
const getTicketInfoData = async () => {
    const response = await getTicketInfo()
    const { dept_data, event_type, ai_type, info } = response.data.data
    allAlgorithms.value = info
    types.value = Object.entries(event_type).map(([key, value]) => ({
        label: value,
        value: key,
    }))
    departments.value = dept_data.map(item => ({
        text: item.dept_name,
        value: item.id,
    }))
    departmentUsers.value = dept_data.reduce((acc, dept) => {
        acc[dept.id] = dept.user_data || []
        return acc
    }, {})
}
// 处理人
const availableDispatchHandlers = computed(() => {
    if (!dispatchForm.value.department) return []
    const users = departmentUsers.value[dispatchForm.value.department]
    if (!users) return []
    return users.map(user => ({
        text: user.name,
        value: user.id,
    }))
})
// 关联算法选择
const handleTypeChange = typeValue => {
    const matchedCategory = allAlgorithms.value.find(category => category.dict_key === typeValue)
    if (!matchedCategory || !matchedCategory.algorithms || matchedCategory.algorithms.length === 0) {
        // 无匹配的算法时清空
        algorithms.value = []
        return
    }
    algorithms.value = matchedCategory.algorithms.map(algo => ({
        label: algo.dict_value,
        value: algo.dict_key,
        dict_key: algo.dict_key,
        dict_value: algo.dict_value,
    }))
    columns.value = algorithms.value.map(item => ({
        text: item.label,
        value: item.value,
    }))
}
// 机巢数据
const handleNodeClick = async data => {
    const droneList = await getDeviceRegion({ areaCode: userInfo.value.detail.areaCode })
    machineData.value = droneList?.data?.data
}
const tableData = ref([])
const fetchWorkData = async (val) => {
    const params = {
        current: Number(currentPage.value) || 1,
        size: pageSize.value,
        source: 1,
        // event_name: val.keyword || val.eventNum,
        ai_types: val.aiType,
        way_line_job_info_id: val.wLJobInfoId,
        work_types: ALL_WORK_ORDER_TYPE_OPTIONS
    }
    if (val.status !== 'undefined') {
        params.status = val.status
    }
    const res = await getList(params)
    const response = res.data.data.records
    totalRecords.value = res.data.data.total || 0
  console.log(totalRecords.value,res.data.data.total,2222)
    if (response && response.length > 0) {
        const item = response[0]
        const matchedMachine = machineData.value.find(m => m.device_sn === item.device_sn)
        const deviceNickname = matchedMachine?.nickname || ''
        currentDetail.value = {
            ...item,
            processingDetail: item.processing_details,
            update_photo_url: item.update_photo_url,
            photos: [],
            aiType: item.ai_type_key_list?.join(',') || '',
            status: Number(item.status) || 0,
            device_names: deviceNickname ? deviceNickname : ''
        }
        currentStatus.value = currentDetail.value.status
        handleTypeChange(currentDetail.value.work_order_type_dict_key)
        await getStepInfoData(currentDetail.value.event_num)
    }
}
// 计算要显示的步骤
const displayedSteps = computed(() => {
    return allStepConfigs.value.filter(stepConfig => {
        return stepResponse.value.some(stepData => Number(stepData?.status) === stepConfig.status)
const openPreview = index => {
    const detail = workDetailData.value
    const showUrl = getShowImg(detail.eventImageUrl)
    showImagePreview({
        images: [showUrl],
        startPosition: 0,
    })
})
// 计算工单类型显示名称
const workOrderTypeName = computed(() => {
    const type = types.value.find(item => item.value === currentDetail.value.work_order_type_dict_key)
    return type ? type.label : currentDetail.value.work_order_type_dict_key
})
// 步骤条
const calculateCurrentStep = status => {
    const stepIndex = displayedSteps.value.findIndex(step => step.status === status)
    return stepIndex !== -1 ? stepIndex : 0
}
const getStepInfoData = async val => {
    const res = await getStepInfo(val)
    stepResponse.value = res.data.data.map(item => ({
        ...item,
        status: Number(item.status) || 0,
    }))
    currentStep.value = calculateCurrentStep(currentStatus.value)
}
// 通过
const approveTicket = async () => {
    const data = {
        id: currentDetail.value.id,
        status: currentDetail.value.status,
        isPass: 0, // 0 表示通过
        eventNum: currentDetail.value.event_num,
    }
    const file = currentDetail.value.file || null
    const response = await flowEvent(data, file)
    if (response.data.code === 0) {
        showToast('工单已通过')
    }
    const transmitData = { data: { type: 'workback', fun: 'add' } }
    wx.miniProgram.switchTab({ url: `/pages/work/index?addLog=111` })
    wx.miniProgram.postMessage(transmitData)
    uni.postMessage(transmitData)
}
// 不通过/不受理
const rejectTicket = async () => {
    const data = {
        id: currentDetail.value.id,
        status: currentDetail.value.status,
        isPass: 1,
    }
    const response = await flowEvent(data)
    if (response.data.code === 0) {
        showToast('工单未通过')
    }
    const transmitData = { data: { type: 'workback', fun: 'add' } }
    wx.miniProgram.switchTab({ url: `/pages/work/index?addLog=111` })
    wx.miniProgram.postMessage(transmitData)
    uni.postMessage(transmitData)
}
const isshowDispatch = ref(false)
// 受理
const approveAndDispatch = () => {
    if (!currentDetail.value.event_name) {
        showToast('请填写工单名称')
        return
    }
    if (!currentDetail.value.ai_types) {
        showToast('请选择关联算法')
        return
    }
    if (!currentDetail.value.content) {
        showToast('请填写工单内容')
        return
    }
    isshowDispatch.value = true
}
const dispatchCancel = () => {
    // 重置表单
    dispatchForm.value = {
        department: '',
        departmentName: '',
        handler: '',
        handlerName: '',
    }
    isshowDispatch.value = false
}
const dispatchForm = ref({
    department: '', // 存储部门ID (value)
    departmentName: '', // 显示部门名称 (text)
    handler: '', // 存储处理人ID (value)
    handlerName: '', // 显示处理人名称 (text)
})
const selectedDepartment = ref([])
const selectedProcessor = ref([])
const showGlobalPicker = ref(false)
const currentPickerType = ref('')
const openPicker = type => {
    currentPickerType.value = type
    showGlobalPicker.value = true
}
// 部门选择确认
const onDepartmentConfirm = value => {
    dispatchForm.value.department = value.selectedValues[0]
    dispatchForm.value.departmentName = value.selectedOptions[0].text
    showGlobalPicker.value = false
}
// 处理人选择确认
const onProcessorConfirm = value => {
    if (!dispatchForm.value.department) {
        showToast('请先选择部门')
        return
    }
    dispatchForm.value.handler = value.selectedValues[0]
    dispatchForm.value.handlerName = value.selectedOptions[0].text
    showGlobalPicker.value = false
}
// 取消选择
const onPickerCancel = () => {
    showGlobalPicker.value = false
}
// 派发工单
const submitDispatch = async () => {
    if (!dispatchForm.value.department) {
        showToast('请选择部门')
        return
    }
    if (!dispatchForm.value.handler) {
        showToast('请选择处理人')
        return
    }
    try {
        const data = {
            id: currentDetail.value.id,
            status: currentDetail.value.status,
            isPass: 0,
            eventName: currentDetail.value.event_name,
            eventNum: currentDetail.value.event_num,
            workOrderTypeDictKey: currentDetail.value.work_order_type_dict_key,
            content: currentDetail.value.content,
            createDept: dispatchForm.value.department,
            updateUser: dispatchForm.value.handler,
            aiType: currentDetail.value.aiType,
        }
        const file = currentDetail.value.file || null
        const response = await flowEvent(data, file)
        if (response.data.code === 0) {
            showToast('工单派发成功')
            const transmitData = { data: { type: 'workback', fun: 'add' } }
            wx.miniProgram.switchTab({ url: `/pages/work/index?addLog=111` })
            wx.miniProgram.postMessage(transmitData)
            uni.postMessage(transmitData)
            isshowDispatch.value = false
        }
    } catch (error) {
        showToast('工单派发失败')
    }
}
// 完成工单
const completeTicket = async () => {
    if (!currentDetail.value.processingDetail) {
        showToast('请先填写事件处理详情')
        return
    }
    if (!currentDetail.value.photos || currentDetail.value.photos.length === 0) {
        showToast('请选择上传图片')
        return
    }
    const data = {
        id: currentDetail.value.id,
        status: currentDetail.value.status,
        processingDetails: currentDetail.value.processingDetail,
        eventNum: currentDetail.value.event_num,
    }
    const file = currentDetail.value.photos?.[0] || null
    const response = await flowEvent(data, file)
    if (response.data.code === 0) {
        showToast('工单已完成')
    }
    const transmitData = { data: { type: 'workback', fun: 'add' } }
    wx.miniProgram.switchTab({ url: `/pages/work/index?addLog=111` })
    wx.miniProgram.postMessage(transmitData)
    uni.postMessage(transmitData)
}
// 取消
const cancellation = () => {
    const transmitData = { data: { type: 'workback', fun: 'add' } }
    wx.miniProgram.switchTab({ url: `/pages/work/index?addLog=111` })
    wx.miniProgram.postMessage(transmitData)
    uni.postMessage(transmitData)
}
// 下拉选择
const openselect = () => {
    showPicker.value = true
}
// 选择器确认方法
const onConfirm = val => {
    currentDetail.value.ai_types = val.selectedOptions.map(option => option.text).join(',')
    currentDetail.value.aiType = val.selectedValues.join(',')
    showPicker.value = false
}
// 选择器取消方法
const onCancel = () => {
    showPicker.value = false
}
// 文件上传前的校验
const beforeRead = file => {
    const ext = file.name.split('.').pop().toLowerCase()
    const allowedExts = ['jpg', 'jpeg', 'png']
    if (!allowedExts.includes(ext)) {
        showToast('请上传 jpg/jpeg/png 格式图片')
        return false
    }
    return true
}
// 文件大小超过限制时的回调
const onOversize = file => {
    showToast('文件大小不能超过 5MB')
}
// 文件读取完成后的回调
const afterRead = file => {
    currentDetail.value.photos = [file.file]
}
// 处理图片删除
const handleDelete = () => {
    currentDetail.value.photos = []
}
// 更新当前显示的工单详情
const updateCurrentDetail = () => {
    currentDetail.value = tableData.value[currentIndex.value]
    if (currentDetail.value.status !== undefined) {
        currentDetail.value.status = Number(currentDetail.value.status) || 0
    }
    currentStatus.value = currentDetail.value.status
    getStepInfoData(currentDetail.value.event_num)
}
// 上一页
const leftClick = () => {
    if (currentPage.value > 1) {
        currentPage.value--
        fetchWorkData(route.query)
    } else {
        showToast('已经是第一页')
    }
}
// 下一页
const rightClick = () => {
    if (currentPage.value < totalRecords.value) {
        currentPage.value++
        fetchWorkData(route.query)
    } else {
        showToast('已经是最后一页')
    }
    previewIndex.value = index
    previewShow.value = true
}
// 跳转地图
const jumpMap = item => {
@@ -653,57 +94,15 @@
    wx.miniProgram.postMessage(transmitData)
    uni.postMessage(transmitData)
}
// 预览图片
const previewShow = ref(false)
const previewIndex = ref(0)
const getImageList = computed(() => {
    const imageArr = []
    const detail = currentDetail.value
    if (detail.photo_url) {
        const smallUrl = getSmallImg(detail.photo_url)
        imageArr.push(smallUrl)
    }
    if (detail.update_photo_url) {
        const smallUrl = getSmallImg(detail.update_photo_url)
        imageArr.push(smallUrl)
    }
    return imageArr
})
const openPreview = index => {
    const detail = currentDetail.value
    const showUrl = getShowImg(detail.photo_url || detail.update_photo_url)
    showImagePreview({
        images: [showUrl],
        startPosition: 0,
    })
    previewIndex.value = index
    previewShow.value = true
}
onMounted(async () => {
    handleNodeClick()
    eventNum.value = route.query.eventNum || ''
    await getTicketInfoData()
  currentPage.value = route.query.current
    await fetchWorkData(route.query)
    await getStepInfoData(eventNum.value)
    currentIndex.value = tableData.value.findIndex(item => item.event_num === currentDetail.value.event_num)
workDetailData.value = JSON.parse(route.query.workDetailData)
console.log('route.query',workDetailData.value);
})
</script>
<style lang="scss" scoped>
.workDetailContainer {
    position: relative;
    .required-mark {
        color: #ff4d4f; // 红色
        margin-left: 4px;
    }
  padding-top: 44px;
    .detailTop {
        .image-container {
            position: relative;
@@ -716,255 +115,6 @@
                display: block;
                object-fit: cover;
            }
            .detailTitle {
                position: absolute;
                left: 0;
                top: 0;
                width: 100%;
                padding: 5px;
                height: 30px;
                background: rgba(7, 7, 7, 0.4);
            }
            .titleText {
                display: flex;
                width: 100%;
                justify-content: space-between;
                align-items: center;
                font-family: Source Han Sans CN, Source Han Sans CN;
                font-weight: 400;
                font-size: 13px;
                color: #ffffff;
                .itemStatus {
                    display: flex;
                    align-items: center;
                    font-family: Source Han Sans CN, Source Han Sans CN;
                    font-weight: 400;
                    font-size: 13px;
                    color: #ffffff;
                    span {
                        display: inline-block;
                        width: 10px;
                        height: 10px;
                        border-radius: 50%;
                        margin-right: 7px;
                    }
                }
            }
            .timeNavigation {
                display: flex;
                align-items: center;
                font-family: Source Han Sans CN, Source Han Sans CN;
                font-weight: 400;
                font-size: 13px;
                color: #ffffff;
                img {
                    width: 20px;
                    height: 20px;
                    margin-left: 5px;
                }
            }
        }
    }
    .stepBox {
        padding: 0 12px;
    }
    .stepContainer {
        margin-top: 10px;
        display: flex;
        border-radius: 5px;
        padding: 10px;
        background: #fff;
        :deep() {
            .van-step__circle-container {
                font-size: 20px;
            }
            .van-step__circle {
                width: 15px;
                height: 15px;
            }
            .van-step--vertical {
                padding: 20px 20px 20px 0;
            }
            .van-step__circle-container,
            .van-step__line {
                top: 50%;
            }
            .van-step--vertical:not(:last-child):after {
                border-bottom-width: 0;
            }
        }
        .horizontal-step {
            display: flex;
            align-items: center;
            gap: 12px;
            .step-title {
                min-width: 60px;
                background: #e8e8e8;
                padding: 5px;
                border-radius: 5px;
                text-align: center;
                &.status-pending {
                    background-color: #ffebeb; // 待审核
                    color: #ff1414;
                }
                &.status-waiting {
                    background-color: #fff1e6; // 待处理
                    color: #ff7411;
                }
                &.status-processing {
                    background-color: #fff6d8; // 处理中
                    color: #ffbb00;
                }
                &.status-completed {
                    background-color: #e5fcff; // 已完成
                    color: #0291a1;
                }
                &.status-default {
                    background-color: #e8e8e8; // 默认背景色
                    color: #191919;
                }
            }
            .step-desc {
                display: flex;
                justify-content: space-between;
                gap: 28px;
                color: #222324;
                font-size: 14px;
            }
            .step-desc span:first-child {
                width: 60px;
                white-space: nowrap;
                overflow: hidden;
                text-overflow: ellipsis;
            }
            span:last-child {
                margin-left: auto;
                white-space: nowrap;
                overflow: hidden;
                text-overflow: ellipsis;
            }
        }
    }
    :deep(.van-step__circle) {
        width: 8px;
        height: 8px;
    }
    /* 悬浮左右按钮样式 */
    .floatingBtn {
        position: fixed;
        top: 50%;
        width: 46px;
        height: 46px;
        z-index: 9;
        transform: translateY(-50%);
        background: rgba(17, 17, 17, 0.28);
        backdrop-filter: blur(3px);
        border-radius: 50%;
        display: flex;
        align-items: center;
        justify-content: center;
    }
    .leftFloatingBtn {
        left: 5px;
        /* 左边距离 */
    }
    .rightFloatingBtn {
        right: 5px;
        /* 右边距离 */
    }
    .floatingBtn img {
        width: 10px;
        height: 18px;
    }
    .paginationInfo {
        position: absolute;
        left: 50%;
        transform: translateX(-50%);
        font-size: 14px;
        color: #666;
        background: rgba(255, 255, 255, 0.9);
        padding: 4px 12px;
        border-radius: 12px;
    }
    .actionButton {
        display: flex;
        justify-content: center;
        align-items: center;
        width: 100%;
        height: 61px;
        background: #ffffff;
        border-radius: 6px 6px 6px 6px;
        padding: 0 18px;
        .btngroups {
            display: flex;
            justify-content: space-between;
            align-items: center;
            .van-button {
                padding: 9px 20px;
                height: 38px;
                width: 156px;
                &:last-child {
                    margin-left: 12px;
                }
            }
        }
        .back-btn :deep(.van-button) {
            width: 100%;
            width: 324px;
            margin-left: 0px !important;
        }
        .leftBtn {
            width: 27px;
            height: 27px;
            cursor: pointer;
            img {
                width: 27px;
                height: 27px;
            }
        }
        .disableds {
            background: #999 !important;
            cursor: not-allowed !important;
            pointer-events: none;
            opacity: 0.3 !important;
        }
    }
@@ -986,12 +136,10 @@
            font-weight: bold;
            font-size: 16px;
            color: #222324;
            // margin-bottom: 16px;
        }
        .workOrderContainer {
            .orderRow {
                // margin-bottom: 10px;
                display: flex;
                justify-content: space-between;
                align-items: center;
@@ -1034,35 +182,16 @@
            }
            .rowAddress {
                font-family: Source Han Sans CN, Source Han Sans CN;
                font-weight: 400;
                font-size: 14px;
                color: #1d6fe9;
                white-space: nowrap;
                /* 禁止换行 */
                overflow: hidden;
                text-overflow: ellipsis;
                // max-width: 74%;
                text-decoration: underline;
                text-align: right;
                padding-top: 1px;
                padding-left: 5px;
                padding-right: 2px;
            }
            .selectTrigger {
                font-family: Source Han Sans CN, Source Han Sans CN;
                font-weight: 400;
                font-size: 14px;
                color: #222324;
            }
            .pointing {
                width: 8px;
                height: 12px;
            }
            .downpointing {
                width: 12px;
                height: 7px;
            }
            :deep(.custom-field) {
                padding: 0 !important;
                padding-left: 10px !important;
@@ -1072,20 +201,8 @@
                padding: 0 !important;
            }
            .titketName {
                display: flex;
                align-items: center;
            }
        }
    }
    .upload-link {
        font-family: Source Han Sans CN, Source Han Sans CN;
        font-weight: 400;
        font-size: 12px;
        color: #1d6fe9;
        cursor: pointer;
        text-decoration: underline;
    }
}
</style>
applications/mobile-web-view/src/layout/Header.vue
File was deleted
applications/mobile-web-view/src/layout/showTips.vue
File was deleted
applications/mobile-web-view/src/layout/useGlobalWS.js
File was deleted
applications/mobile-web-view/src/page/GatewayPage/GatewayPage.vue
File was deleted
applications/mobile-web-view/src/permission.js
@@ -78,6 +78,6 @@
    const isLoginPage = to.path === '/login'
    // 根据是否登录页设置标题
    // const pageTitle = isLoginPage ? 'appWebview' : 'appWebview'
    document.title = to.meta.title || '掌控智飞'
    document.title = to.meta.title || ''
    store.commit('SET_IS_SEARCH', false)
})
applications/task-work-order/env/.env
@@ -1,6 +1,9 @@
#接口地址
VITE_APP_API=/api
# 开发环境代理接口地址
VITE_APP_URL=http://192.168.1.168
#开发环境代理接口地址-xt
VITE_APP_API_XT=/apiXT
@@ -22,4 +25,5 @@
# 预览地址 previewURL
VITE_APP_PREVIEW_URL=http://192.168.1.36:8012
VITE_APP_TERRAIN_URL=https://wrj.shuixiongit.com/aiskyminio/cloud-bucket/ztzf_c_uas/
#地形服务地址
VITE_APP_TERRAIN_URL=https://wrj.shuixiongit.com/aiskyminio/cloud-bucket/ztzf_c_uas/
applications/task-work-order/env/.env.development
@@ -7,7 +7,7 @@
VITE_APP_URL=http://192.168.1.227
#新大屏地址
VITE_APP_DASHBOARD_URL='https://wrj.shuixiongit.com/command-center-dashboard/'
VITE_APP_DASHBOARD_URL=''
# ws地址
VITE_APP_WS_API_URL=wss://wrj.shuixiongit.com/drone-wss/api/v1/ws
applications/task-work-order/env/.env.production
@@ -4,11 +4,8 @@
#生产环境配置
VITE_APP_ENV='production'
# 服务地址
VITE_APP_URL=https://aisky.org.cn/api
#新大屏地址
VITE_APP_DASHBOARD_URL='https://aisky.org.cn/command-center-dashboard/'
VITE_APP_DASHBOARD_URL=''
# ws地址
VITE_APP_WS_API_URL=wss://aisky.org.cn/drone-wss/api/v1/ws
applications/task-work-order/env/.env.test
@@ -1,33 +1,10 @@
###
 # @Author       : yuan
 # @Date         : 2026-02-03 13:46:08
 # @LastEditors  : yuan
 # @LastEditTime : 2026-02-03 15:44:11
 # @FilePath     : \applications\task-work-order\env\.env.test
 # @Description  :
 # Copyright 2026 OBKoro1, All Rights Reserved.
 # 2026-02-03 13:46:08
###
###
 # @Author       : yuan
 # @Date         : 2026-02-03 13:46:08
 # @LastEditors  : yuan
 # @LastEditTime : 2026-02-03 15:44:01
 # @FilePath     : \applications\task-work-order\env\.env.test
 # @Description  :
 # Copyright 2026 OBKoro1, All Rights Reserved.
 # 2026-02-03 13:46:08
###
NODE_ENV='test'
#开发环境配置
VITE_APP_ENV='test'
# 服务地址
VITE_APP_URL=https://wrj.shuixiongit.com/api
#新大屏地址
VITE_APP_DASHBOARD_URL='https://wrj.shuixiongit.com/command-center-dashboard/'
VITE_APP_DASHBOARD_URL=''
# ws地址
VITE_APP_WS_API_URL=wss://wrj.shuixiongit.com/drone-wss/api/v1/ws
applications/task-work-order/src/views/orderView/appConfiguration/process/index.vue
@@ -28,12 +28,12 @@
        </el-form>
        <div class="gd-table-toolbar">
            <el-button :icon="Plus" color="#4C34FF" type="primary" v-if="permission.flow_model_create" @click="handleCreate">
            <el-button :icon="Plus" color="#4C34FF" type="primary"  @click="handleCreate">
                创建
            </el-button>
            <el-button
                :icon="Delete"
                v-if="permission.flow_model_delete"
                @click="handleBatchDelete"
            >
                删除
@@ -61,9 +61,9 @@
                    <el-table-column prop="lastUpdated" show-overflow-tooltip label="更新时间" />
                    <el-table-column label="操作" class-name="operation-btns" width="200">
                        <template v-slot="{ row }">
                            <el-link type="primary" v-if="permission.flow_model_update" @click="handleUpdate(row)">配置</el-link>
                            <el-link type="primary" v-if="permission.flow_model_deploy" @click="handleDeploy(row)">部署</el-link>
                            <el-link type="primary" v-if="permission.flow_model_delete" @click="handleDelete(row)">删除</el-link>
                            <el-link type="primary"  @click="handleUpdate(row)">配置</el-link>
                            <el-link type="primary" @click="handleDeploy(row)">部署</el-link>
                            <el-link type="primary"  @click="handleDelete(row)">删除</el-link>
                        </template>
                    </el-table-column>
                </el-table>
applications/task-work-order/src/views/orderView/orderManage/inspectionRequest/index.vue
@@ -91,7 +91,9 @@
                    </el-table-column>
                    <el-table-column prop="taskStatus" show-overflow-tooltip label="巡查任务状态">
                        <template v-slot="{ row }">
                            {{ getDictLabel(row.taskStatus, dictObj.taskStatus) }}
                            <span :style="{color: colors?.[row?.taskStatus]}">
                                {{ getDictLabel(row.taskStatus, dictObj.taskStatus) }}
                            </span>
                        </template>
                    </el-table-column>
                    <el-table-column prop="executeTime" show-overflow-tooltip label="任务执行时间" />
@@ -174,6 +176,19 @@
const activeName = ref('all')
provide('dictObj', dictObj)
const colors = {
    0: '#F6A000',//0待签收
    1: '#FF0202',//1拒绝签收
    2: '#FF0202',//2已撤回
    3: '#212BF4',//3待审核
    4: '#FF0000',//4审核驳回
    5: '#0068F0',//5审核通过
    6: '#FD6716',//6待验收
    7: '#FF0000',//7拒绝验收
    8: '#019612',//8验收通过
}
// 获取列表
async function getList() {
    const range = dateRangeFormat(dateRange.value)
applications/task-work-order/src/views/orderView/orderManage/orderManage/index.vue
@@ -90,7 +90,9 @@
                    </el-table-column>
                    <el-table-column prop="status" show-overflow-tooltip label="阶段状态">
                        <template v-slot="{ row }">
                            {{ getDictLabel(row.workOrderStatus, dictObj.workOrderStatus).split('_')[0] }}
                            <span :style="{color: colors?.[row?.workOrderStatus]}">
                                {{ getDictLabel(row.workOrderStatus, dictObj.workOrderStatus).split('_')[0] }}
                            </span>
                        </template>
                    </el-table-column>
                    <el-table-column prop="workOrderStatus" show-overflow-tooltip label="工单状态">
@@ -236,6 +238,20 @@
    })
}
const colors = {
    10: '#AE00FF',
    11: '#AE00FF',
    20: '#002BFF',
    21: '#002BFF',
    22: '#002BFF',
    23: '#002BFF',
    30: '#FF5106',
    31: '#FF5106',
    40: '#FF0000',
    50: '#008CFF',
    60: '#019612',
}
// 删除
async function handleDelete(row) {
    const tips = row ? '该条' : '选中的项'