吉安感知网项目-前端
shuishen
2026-01-19 16e9746e5da1ac0485fde18e71fb7bf8ee376d42
Merge branch 'master' of http://139.196.74.78:10010/r/jagzwxm/ja_web
20 files modified
7 files added
4 files deleted
3563 ■■■■■ changed files
applications/mobile-web-view/src/appComponents/LeafletMap/index.vue 88 ●●●● patch | view | raw | blame | history
applications/mobile-web-view/src/appDataSource/leafletMapIcon/search-normal.svg 14 ●●●●● patch | view | raw | blame | history
applications/task-work-order/src/config/website.js 139 ●●●● patch | view | raw | blame | history
applications/task-work-order/src/permission copy 2.js 71 ●●●●● patch | view | raw | blame | history
applications/task-work-order/src/permission copy.js 133 ●●●●● patch | view | raw | blame | history
applications/task-work-order/src/permission.js 3 ●●●● patch | view | raw | blame | history
applications/task-work-order/src/router/page/index.js 133 ●●●● patch | view | raw | blame | history
applications/task-work-order/src/router/views/index.js 18 ●●●●● patch | view | raw | blame | history
applications/task-work-order/src/store/modules/user.js 769 ●●●● patch | view | raw | blame | history
applications/task-work-order/src/styles/common/cockpit.scss 63 ●●●● patch | view | raw | blame | history
applications/task-work-order/src/views/basicManage/maintainRecord/FormDiaLog.vue 135 ●●●● patch | view | raw | blame | history
applications/task-work-order/src/views/basicManage/maintainRecord/MaintenanceDiaLog.vue 177 ●●●●● patch | view | raw | blame | history
applications/task-work-order/src/views/basicManage/maintainRecord/fwDeviceMaintainRecord.js 19 ●●●●● patch | view | raw | blame | history
applications/task-work-order/src/views/basicManage/maintainRecord/index.vue 30 ●●●●● patch | view | raw | blame | history
applications/task-work-order/src/views/orderView/orderDataManage/evaluate/FormDiaLog.vue 220 ●●●●● patch | view | raw | blame | history
applications/task-work-order/src/views/orderView/orderDataManage/evaluate/evaluateApi.js 37 ●●●●● patch | view | raw | blame | history
applications/task-work-order/src/views/orderView/orderDataManage/evaluate/index.vue 178 ●●●●● patch | view | raw | blame | history
applications/task-work-order/src/views/orderView/orderDataManage/supplyAdd/FormDiaLog.vue 291 ●●●●● patch | view | raw | blame | history
applications/task-work-order/src/views/orderView/orderDataManage/supplyAdd/index.vue 218 ●●●●● patch | view | raw | blame | history
applications/task-work-order/src/views/orderView/orderDataManage/supplyAdd/supplyAddApi.js 55 ●●●●● patch | view | raw | blame | history
applications/task-work-order/src/views/orderView/orderManage/inspectionRequest/index.vue 2 ●●● patch | view | raw | blame | history
applications/task-work-order/src/views/wel/index.vue 4 ●●●● patch | view | raw | blame | history
uniapps/work-app/src/App.vue 17 ●●●● patch | view | raw | blame | history
uniapps/work-app/src/hooks/useGlobalWS.js 104 ●●●● patch | view | raw | blame | history
uniapps/work-app/src/pages/login/index.vue 22 ●●●● patch | view | raw | blame | history
uniapps/work-app/src/store/index.js 3 ●●●● patch | view | raw | blame | history
uniapps/work-app/src/store/modules/location.js 137 ●●●●● patch | view | raw | blame | history
uniapps/work-app/src/subPackages/userDetail/infos/index.vue 37 ●●●●● patch | view | raw | blame | history
uniapps/work-app/src/utils/location.js 260 ●●●●● patch | view | raw | blame | history
uniapps/work-app/src/utils/websocket.js 30 ●●●● patch | view | raw | blame | history
uniapps/work-wx/src/subPackages/userDetail/infos/index.vue 156 ●●●● patch | view | raw | blame | history
applications/mobile-web-view/src/appComponents/LeafletMap/index.vue
@@ -156,9 +156,9 @@
let locationFlag = false
let watchId = null
let userLocationMarker = null
const setMapLocation = () => {
    locationFlag = true
// 初始化实时位置监听
const initLocationWatch = () => {
    // 在WebView加载的网页中
    // 检查浏览器是否支持 geolocation
    if (navigator.geolocation) {
@@ -166,7 +166,6 @@
            // 停止监听位置
            navigator.geolocation.clearWatch(watchId)
        }
        // 开始持续获取用户的位置
        watchId = navigator.geolocation.watchPosition(
            function (position) {
@@ -174,31 +173,26 @@
                const lat = position.coords.latitude // 纬度
                const lng = position.coords.longitude // 经度
                if (locationFlag) {
                    if (userLocationMarker) {
                        userLocationMarker.setLatLng([lat, lng])
                    } else {
                        userLocationMarker = L.marker([lat, lng], {
                            icon: L.icon({
                // 确保位置标记存在并更新位置
                if (userLocationMarker) {
                    userLocationMarker.setLatLng([lat, lng])
                } else {
                    userLocationMarker = L.marker([lat, lng], {
                        icon: L.icon({
                                iconUrl: userLocationIcon, // 图片路径
                                iconSize: [24, 24], // 图标尺寸
                                iconAnchor: [12, 12], // 锚点位置
                            }),
                        }).addTo(map)
                    }
                                    iconSize: [24, 24], // 图标尺寸
                                    iconAnchor: [12, 12], // 锚点位置
                                }),
                    }).addTo(map)
                }
                // 如果是点击定位按钮触发的,将地图视图定位到当前位置
                if (locationFlag) {
                    mapSetView({
                        lat,
                        lng,
                    })
                    locationFlag = false
                    return
                }
                if (userLocationMarker) {
                    userLocationMarker.setLatLng([lat, lng])
                }
            },
            function (error) {
@@ -213,6 +207,24 @@
        )
    } else {
        console.log('该浏览器不支持地理定位功能。')
    }
}
const setMapLocation = () => {
    locationFlag = true
    // 如果还没有开始监听位置,先初始化监听
    if (!watchId) {
        initLocationWatch()
    } else {
        // 已经在监听位置,会自动定位到当前位置
        // 可以添加一个视觉反馈,比如位置标记闪烁效果
        if (userLocationMarker) {
            // 添加闪烁效果
            userLocationMarker.getElement().classList.add('location-blink')
            setTimeout(() => {
                userLocationMarker.getElement().classList.remove('location-blink')
            }, 1000)
        }
    }
}
let lastLocationMarker = null
@@ -450,6 +462,9 @@
    initMap()
    map.addLayer(layers[0].map)
    // 初始化实时位置监听
    initLocationWatch()
    EventBus.on('mapSetView', mapSetView)
    EventBus.on('mapAddMarker', mapAddMarker)
    EventBus.on('mapAddAirMarker', mapAddAirMarker)
@@ -466,6 +481,17 @@
})
onUnmounted(() => {
    // 清理位置监听
    if (watchId) {
        navigator.geolocation.clearWatch(watchId)
        watchId = null
    }
    // 移除位置标记
    if (userLocationMarker) {
        map.removeLayer(userLocationMarker)
        userLocationMarker = null
    }
    EventBus.off('mapSetView', mapSetView)
    EventBus.off('mapAddMarker', mapAddMarker)
    EventBus.off('mapAddAirMarker', mapAddAirMarker)
@@ -606,4 +632,24 @@
        }
    }
}
// 位置标记闪烁效果
.location-blink {
    animation: blink 1s ease-in-out;
}
@keyframes blink {
    0% {
        transform: scale(1);
        opacity: 1;
    }
    50% {
        transform: scale(1.2);
        opacity: 0.8;
    }
    100% {
        transform: scale(1);
        opacity: 1;
    }
}
</style>
applications/mobile-web-view/src/appDataSource/leafletMapIcon/search-normal.svg
New file
@@ -0,0 +1,14 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="search-normal">
<g id="search-normal_2">
<g id="search-normal_3">
<g id="vuesax/linear/search-normal">
<g id="search-normal_4">
<path id="Vector" d="M7.66659 14C11.1644 14 13.9999 11.1644 13.9999 7.66665C13.9999 4.16884 11.1644 1.33331 7.66659 1.33331C4.16878 1.33331 1.33325 4.16884 1.33325 7.66665C1.33325 11.1644 4.16878 14 7.66659 14Z" stroke="#191919" stroke-linecap="round" stroke-linejoin="round"/>
<path id="Vector_2" d="M14.6666 14.6666L13.3333 13.3333" stroke="#191919" stroke-linecap="round" stroke-linejoin="round"/>
</g>
</g>
</g>
</g>
</g>
</svg>
applications/task-work-order/src/config/website.js
@@ -4,81 +4,78 @@
 * @LastEditors  : yuan
 * @LastEditTime : 2026-01-16 11:12:30
 * @FilePath     : \applications\task-work-order\src\config\website.js
 * @Description  :
 * Copyright 2026 OBKoro1, All Rights Reserved.
 * @Description  :
 * Copyright 2026 OBKoro1, All Rights Reserved.
 * 2026-01-06 09:47:27
 */
/**
 * 全局配置文件
 */
export default {
  title: 'saber',
  logo: 'S',
  key: 'work', //配置主键,目前用于存储
  indexTitle: '',
  clientId: 'saber', // 客户端id
  clientSecret: 'saber_secret', // 客户端密钥
  tenantMode: true, // 是否开启租户模式
  tenantId: '000000', // 管理组租户编号
  captchaMode: true, // 是否开启验证码模式
  switchMode: false, // 是否开启部门切换模式
  lockPage: '/lock',
  tokenTime: 3000,
  tokenHeader: 'Blade-Auth',
  //http的status默认放行不才用统一处理的,
  statusWhiteList: [],
  //配置首页不可关闭
  setting: {
    sidebar: 'vertical',
    tag: true,
    debug: true,
    collapse: true,
    search: true,
    color: true,
    lock: true,
    screenshot: true,
    fullscreen: true,
    theme: true,
    menu: true,
  },
  fistPage: {
    name: '数据驾驶舱',
    path: '/dataCockpit/index',
    // path: '/tickets/ticket',
  },
  //配置菜单的属性
  menu: {
    iconDefault: 'icon-caidan',
    label: 'name',
    path: 'path',
    icon: 'source',
    children: 'children',
    query: 'query',
    href: 'path',
    meta: 'meta',
  },
  //oauth2配置
  oauth2: {
    // 是否开启注册功能
    registerMode: true,
    // 使用后端工程 @org.springblade.test.Sm2KeyGenerator 获取
    publicKey: '请配置国密sm2公钥',
    // 第三方系统授权地址
    authUrl: 'http://localhost/blade-auth/oauth/render',
    // 单点登录系统认证(cloud端口为8100,boot端口为80)
    ssoUrl:
      'http://localhost:8100/oauth/authorize?client_id=saber3&response_type=code&redirect_uri=',
    // 单点登录回调地址(Saber服务的登录界面地址)
    redirectUri: 'http://localhost:2888/login',
  },
  //设计器配置
  design: {
    // 流程设计器类型(true->nutflow,false->flowable)
    designMode: true,
    // 流程设计器地址(flowable模式)
    designUrl: 'http://localhost:9999',
    // 报表设计器地址(cloud端口为8108,boot端口为80)
    reportUrl: 'http://localhost:8108/ureport',
  },
    title: 'saber',
    logo: 'S',
    key: 'work', //配置主键,目前用于存储
    indexTitle: '',
    clientId: 'saber', // 客户端id
    clientSecret: 'saber_secret', // 客户端密钥
    tenantMode: true, // 是否开启租户模式
    tenantId: '000000', // 管理组租户编号
    captchaMode: true, // 是否开启验证码模式
    switchMode: false, // 是否开启部门切换模式
    lockPage: '/lock',
    tokenTime: 3000,
    tokenHeader: 'Blade-Auth',
    //http的status默认放行不才用统一处理的,
    statusWhiteList: [],
    //配置首页不可关闭
    setting: {
        sidebar: 'vertical',
        tag: true,
        debug: true,
        collapse: true,
        search: true,
        color: true,
        lock: true,
        screenshot: true,
        fullscreen: true,
        theme: true,
        menu: true,
    },
    fistPage: {
        name: '测试页',
        path: '/test',
    },
    //配置菜单的属性
    menu: {
        iconDefault: 'icon-caidan',
        label: 'name',
        path: 'path',
        icon: 'source',
        children: 'children',
        query: 'query',
        href: 'path',
        meta: 'meta',
    },
    //oauth2配置
    oauth2: {
        // 是否开启注册功能
        registerMode: true,
        // 使用后端工程 @org.springblade.test.Sm2KeyGenerator 获取
        publicKey: '请配置国密sm2公钥',
        // 第三方系统授权地址
        authUrl: 'http://localhost/blade-auth/oauth/render',
        // 单点登录系统认证(cloud端口为8100,boot端口为80)
        ssoUrl: 'http://localhost:8100/oauth/authorize?client_id=saber3&response_type=code&redirect_uri=',
        // 单点登录回调地址(Saber服务的登录界面地址)
        redirectUri: 'http://localhost:2888/login',
    },
    //设计器配置
    design: {
        // 流程设计器类型(true->nutflow,false->flowable)
        designMode: true,
        // 流程设计器地址(flowable模式)
        designUrl: 'http://localhost:9999',
        // 报表设计器地址(cloud端口为8108,boot端口为80)
        reportUrl: 'http://localhost:8108/ureport',
    },
}
applications/task-work-order/src/permission copy 2.js
File was deleted
applications/task-work-order/src/permission copy.js
File was deleted
applications/task-work-order/src/permission.js
@@ -67,7 +67,6 @@
            query: firstMenu.query || {},
            meta: firstMenu.meta || {},
          })
          store.commit('ADD_TAG', {
            name: toMenu.name,
            path: toMenu.path,
@@ -139,4 +138,4 @@
    const pageTitle = isLoginPage ? '中图智飞低空感知网平台' : '综合管理平台'
    document.title = pageTitle
    store.commit('SET_IS_SEARCH', false)
})
})
applications/task-work-order/src/router/page/index.js
@@ -1,77 +1,64 @@
import Store from '@/store/'
export default [
  {
    path: '/login',
    name: '登录页',
    component: () =>
      Store.getters.isMacOs ? import('@/mac/login.vue') : import('@/page/login/index.vue'),
    meta: {
      keepAlive: true,
      isTab: false,
      isAuth: false,
    },
  },
  {
    path: '/oauth/redirect/:source',
    name: '第三方登录',
    component: () =>
      Store.getters.isMacOs ? import('@/mac/login.vue') : import('@/page/login/index.vue'),
    meta: {
      keepAlive: true,
      isTab: false,
      isAuth: false,
    },
  },
  {
    path: '/lock',
    name: '锁屏页',
    component: () =>
      Store.getters.isMacOs ? import('@/mac/lock.vue') : import('@/page/lock/index.vue'),
    meta: {
      keepAlive: true,
      isTab: false,
      isAuth: false,
    },
  },
  {
    path: '/404',
    component: () => import(/* webpackChunkName: "page" */ '@/components/error-page/404.vue'),
    name: '404',
    meta: {
      keepAlive: true,
      isTab: false,
      isAuth: false,
    },
  },
  {
    path: '/403',
    component: () => import(/* webpackChunkName: "page" */ '@/components/error-page/403.vue'),
    name: '403',
    meta: {
      keepAlive: true,
      isTab: false,
      isAuth: false,
    },
  },
  {
    path: '/500',
    component: () => import(/* webpackChunkName: "page" */ '@/components/error-page/500.vue'),
    name: '500',
    meta: {
      keepAlive: true,
      isTab: false,
      isAuth: false,
    },
  },
  {
    path: '/',
    name: '主页',
    redirect: '/wel',
  },
  // {
  //    path: '/',
  //    name: '主页',
  //    redirect: '/tickets/ticket',
  //    },
    {
        path: '/login',
        name: '登录页',
        component: () => (Store.getters.isMacOs ? import('@/mac/login.vue') : import('@/page/login/index.vue')),
        meta: {
            keepAlive: true,
            isTab: false,
            isAuth: false,
        },
    },
    {
        path: '/oauth/redirect/:source',
        name: '第三方登录',
        component: () => (Store.getters.isMacOs ? import('@/mac/login.vue') : import('@/page/login/index.vue')),
        meta: {
            keepAlive: true,
            isTab: false,
            isAuth: false,
        },
    },
    {
        path: '/lock',
        name: '锁屏页',
        component: () => (Store.getters.isMacOs ? import('@/mac/lock.vue') : import('@/page/lock/index.vue')),
        meta: {
            keepAlive: true,
            isTab: false,
            isAuth: false,
        },
    },
    {
        path: '/404',
        component: () => import(/* webpackChunkName: "page" */ '@/components/error-page/404.vue'),
        name: '404',
        meta: {
            keepAlive: true,
            isTab: false,
            isAuth: false,
        },
    },
    {
        path: '/403',
        component: () => import(/* webpackChunkName: "page" */ '@/components/error-page/403.vue'),
        name: '403',
        meta: {
            keepAlive: true,
            isTab: false,
            isAuth: false,
        },
    },
    {
        path: '/500',
        component: () => import(/* webpackChunkName: "page" */ '@/components/error-page/500.vue'),
        name: '500',
        meta: {
            keepAlive: true,
            isTab: false,
            isAuth: false,
        },
    },
]
applications/task-work-order/src/router/views/index.js
@@ -18,24 +18,6 @@
      }
    ],
  },
  // 事件工单
  // {
  //    path: '/tickets',
  //   component: () =>
  //    Store.getters.isMacOs ? import('@/mac/index.vue') : import('@/page/index/index.vue'),
  //    redirect: '/tickets/ticket',
  //    children: [
  //    {
  //    path: 'ticket',
  //  name: '事件工单',
  //    meta: {
  //    i18n: 'dashboard',
  //   },
  //   component: () => import(/* webpackChunkName: "views" */ '@/views/tickets/ticket.vue'),
  //    },
  //   ],
  //    },
  {
    path: '/test',
    component: Layout,
applications/task-work-order/src/store/modules/user.js
@@ -1,396 +1,385 @@
import {
  setToken,
  setRefreshToken,
  removeToken,
  removeRefreshToken,
  getRefreshToken,
} from '@/utils/auth';
import { setStore, getStore } from '@/utils/store';
import { validatenull } from '@/utils/validate';
import { deepClone } from '@/utils/util';
import { setToken, setRefreshToken, removeToken, removeRefreshToken, getRefreshToken } from '@/utils/auth'
import { setStore, getStore } from '@/utils/store'
import { validatenull } from '@/utils/validate'
import { deepClone } from '@/utils/util'
import defaultAva from '@/assets/images/defaultava.png'
import {
  loginByUsername,
  loginBySocial,
  loginBySso,
  loginByPhone,
  getUserInfo,
  logout,
  refreshToken,
  getButtons,
  registerUser,
  getParentDeptInfo
} from '@/api/user';
import { getRoutes, getTopMenu } from '@/api/system/menu';
import { formatPath } from '@/router/avue-router';
import { ElMessage } from 'element-plus';
import { encrypt } from '@/utils/sm2';
import md5 from 'js-md5';
    loginByUsername,
    loginBySocial,
    loginBySso,
    loginByPhone,
    getUserInfo,
    logout,
    refreshToken,
    getButtons,
    registerUser,
    getParentDeptInfo,
} from '@/api/user'
import { getRoutes, getTopMenu } from '@/api/system/menu'
import { formatPath } from '@/router/avue-router'
import { ElMessage } from 'element-plus'
import { encrypt } from '@/utils/sm2'
import md5 from 'js-md5'
const user = {
  state: {
    tenantId: getStore({ name: 'tenantId' }) || '',
    userInfo: getStore({ name: 'userInfo' }) || [],
    permission: getStore({ name: 'permission' }) || {},
    roles: [],
    menuId: {},
    menu: getStore({ name: 'menu' }) || [],
    menuAll: getStore({ name: 'menuAll' }) || [],
    token: getStore({ name: 'token' }) || '',
    refreshToken: getStore({ name: 'refreshToken' }) || '',
    parentDeptInfo: getStore({ name: 'parentDeptInfo' }) || '',
    state: {
        tenantId: getStore({ name: 'tenantId' }) || '',
        userInfo: getStore({ name: 'userInfo' }) || [],
        permission: getStore({ name: 'permission' }) || {},
        roles: [],
        menuId: {},
        menu: getStore({ name: 'menu' }) || [],
        menuAll: getStore({ name: 'menuAll' }) || [],
        token: getStore({ name: 'token' }) || '',
        refreshToken: getStore({ name: 'refreshToken' }) || '',
        parentDeptInfo: getStore({ name: 'parentDeptInfo' }) || '',
    },
    actions: {
        //根据用户名登录
        LoginByUsername({ commit }, userInfo = {}) {
            return new Promise((resolve, reject) => {
                loginByUsername(
                    userInfo.tenantId,
                    userInfo.deptId,
                    userInfo.roleId,
                    userInfo.username,
                    md5(userInfo.password),
                    userInfo.type,
                    userInfo.key,
                    userInfo.code
                )
                    .then(res => {
                        const data = res.data
                        if (data.error_description) {
                            ElMessage({
                                message: data.error_description,
                                type: 'error',
                            })
                        } else {
                            commit('SET_TOKEN', data.access_token)
                            commit('SET_REFRESH_TOKEN', data.refresh_token)
                            commit('SET_TENANT_ID', data.tenant_id)
                            commit('SET_USER_INFO', data)
                            commit('SET_PARENT_DEPT_INFO', data.dept_id)
                            commit('DEL_ALL_TAG')
                            commit('CLEAR_LOCK')
                        }
                        resolve()
                    })
                    .catch(err => {
                        reject(err)
                    })
            })
        },
        //根据第三方信息登录
        LoginBySocial({ commit }, userInfo) {
            return new Promise((resolve, reject) => {
                loginBySocial(userInfo.tenantId, userInfo.source, userInfo.code, userInfo.state)
                    .then(res => {
                        const data = res.data
                        if (data.error_description) {
                            ElMessage({
                                message: data.error_description,
                                type: 'error',
                            })
                        } else {
                            commit('SET_TOKEN', data.access_token)
                            commit('SET_REFRESH_TOKEN', data.refresh_token)
                            commit('SET_USER_INFO', data)
                            commit('SET_TENANT_ID', data.tenant_id)
                            commit('DEL_ALL_TAG')
                            commit('CLEAR_LOCK')
                        }
                        resolve()
                    })
                    .catch(err => {
                        reject(err)
                    })
            })
        },
        //根据单点信息登录
        LoginBySso({ commit }, userInfo) {
            return new Promise((resolve, reject) => {
                loginBySso(userInfo.state, userInfo.code)
                    .then(res => {
                        const data = res.data
                        if (data.error_description) {
                            ElMessage({
                                message: data.error_description,
                                type: 'error',
                            })
                        } else {
                            commit('SET_TOKEN', data.access_token)
                            commit('SET_REFRESH_TOKEN', data.refresh_token)
                            commit('SET_USER_INFO', data)
                            commit('SET_TENANT_ID', data.tenant_id)
                            commit('DEL_ALL_TAG')
                            commit('CLEAR_LOCK')
                        }
                        resolve()
                    })
                    .catch(err => {
                        reject(err)
                    })
            })
        },
        //根据手机信息登录
        LoginByPhone({ commit }, userInfo) {
            return new Promise((resolve, reject) => {
                loginByPhone(userInfo.tenantId, encrypt(userInfo.phone), userInfo.codeId, userInfo.codeValue)
                    .then(res => {
                        const data = res.data
                        if (data.error_description) {
                            ElMessage({
                                message: data.error_description,
                                type: 'error',
                            })
                        } else {
                            commit('SET_TOKEN', data.access_token)
                            commit('SET_REFRESH_TOKEN', data.refresh_token)
                            commit('SET_USER_INFO', data)
                            commit('SET_TENANT_ID', data.tenant_id)
                            commit('DEL_ALL_TAG')
                            commit('CLEAR_LOCK')
                        }
                        resolve()
                    })
                    .catch(err => {
                        reject(err)
                    })
            })
        },
        //用户注册
        RegisterUser({ commit }, userInfo = {}) {
            return new Promise((resolve, reject) => {
                registerUser(
                    userInfo.tenantId,
                    userInfo.name,
                    userInfo.account,
                    encrypt(userInfo.password),
                    userInfo.phone,
                    userInfo.email
                ).then(res => {
                    const data = res.data
                    if (data.error_description) {
                        ElMessage({
                            message: data.error_description,
                            type: 'error',
                        })
                        reject(data.error_description)
                    } else {
                        commit('SET_TOKEN', data.access_token)
                        commit('SET_REFRESH_TOKEN', data.refresh_token)
                        commit('SET_USER_INFO', data)
                        commit('SET_TENANT_ID', data.tenant_id)
                        commit('DEL_ALL_TAG')
                        commit('CLEAR_LOCK')
                    }
                    resolve()
                })
            })
        },
        GetUserInfo({ commit }) {
            return new Promise((resolve, reject) => {
                getUserInfo()
                    .then(res => {
                        const data = res.data.data
                        commit('SET_ROLES', data.roles)
                        resolve(data)
                    })
                    .catch(err => {
                        reject(err)
                    })
            })
        },
        //刷新token
        RefreshToken({ state, commit }, userInfo) {
            return new Promise((resolve, reject) => {
                refreshToken(
                    getRefreshToken(),
                    state.tenantId,
                    !validatenull(userInfo) ? userInfo.deptId : state.userInfo.dept_id,
                    !validatenull(userInfo) ? userInfo.roleId : state.userInfo.role_id
                )
                    .then(res => {
                        const data = res.data
                        commit('SET_TOKEN', data.access_token)
                        commit('SET_REFRESH_TOKEN', data.refresh_token)
                        commit('SET_USER_INFO', data)
                        resolve()
                    })
                    .catch(error => {
                        reject(error)
                    })
            })
        },
        // 登出
        LogOut({ commit }) {
            return new Promise((resolve, reject) => {
                logout()
                    .then(() => {
                        commit('SET_TOKEN', '')
                        commit('SET_MENU_ALL_NULL', [])
                        commit('SET_MENU', [])
                        commit('SET_ROLES', [])
                        commit('DEL_ALL_TAG', [])
                        commit('CLEAR_LOCK')
                        removeToken()
                        removeRefreshToken()
                        resolve()
                    })
                    .catch(error => {
                        reject(error)
                    })
            })
        },
        //注销session
        FedLogOut({ commit }) {
            return new Promise(resolve => {
                commit('SET_TOKEN', '')
                commit('SET_MENU_ALL_NULL', [])
                commit('SET_MENU', [])
                commit('SET_ROLES', [])
                commit('DEL_ALL_TAG', [])
                commit('CLEAR_LOCK')
                removeToken()
                removeRefreshToken()
                removeToken()
                resolve()
            })
        },
        GetTopMenu() {
            return new Promise(resolve => {
                getTopMenu().then(res => {
                    const data = res.data.data || []
                    resolve(data)
                })
            })
        },
        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
                    env === 'development'
                        ? (window.location.href = '/task-work-order/login')
                        : window.location.replace(`${adminUrl}#/login`)
                })
            }
            return new Promise(resolve => {
                getRoutes({ tenantId, sysType: 6 })
                    .then(res => {
                        const data = res.data.data
                        let menu = deepClone(data)
                        if (!res.data?.data?.length) {
                            errLogOut()
                            return
                        }
                        menu.forEach(ele => formatPath(ele, true))
                        commit('SET_MENU', menu)
                        commit('SET_MENU_ALL', menu)
                        dispatch('GetButtons')
                        resolve(menu)
                    })
                    .catch(err => {
                        errLogOut()
                    })
            })
        },
        GetButtons({ commit }) {
            return new Promise(resolve => {
                getButtons({ sysType: 6 }).then(res => {
                    const data = res.data.data
                    commit('SET_PERMISSION', data)
                    resolve()
                })
            })
        },
    },
    mutations: {
        SET_PARENT_DEPT_INFO(state, deptId) {
            getParentDeptInfo({ deptId: deptId }).then(res => {
                const data = res.data.data
                state.parentDeptInfo = data
                setStore({ name: 'parentDeptInfo', content: data })
            })
        },
        SET_TOKEN: (state, token) => {
            setToken(token)
            state.token = token
            setStore({ name: 'token', content: state.token })
        },
        SET_REFRESH_TOKEN: (state, refreshToken) => {
            setRefreshToken(refreshToken)
            state.refreshToken = refreshToken
            setStore({ name: 'refreshToken', content: state.refreshToken })
        },
        SET_MENU_ID(state, menuId) {
            state.menuId = menuId
        },
        SET_TENANT_ID: (state, tenantId) => {
            state.tenantId = tenantId
            setStore({ name: 'tenantId', content: state.tenantId })
        },
        SET_USER_INFO: (state, userInfo) => {
            if (validatenull(userInfo.user_id) && validatenull(userInfo.account)) {
                state.userInfo = { user_name: 'unauth', role_name: 'unauth' }
            } else {
                if (validatenull(userInfo.avatar)) {
                    userInfo.avatar = defaultAva
                }
                state.userInfo = userInfo
            }
            setStore({ name: 'userInfo', content: state.userInfo })
        },
        SET_MENU_ALL: (state, menuAll) => {
            let menu = state.menuAll
            menuAll.forEach(ele => {
                let index = menu.findIndex(item => item.path === ele.path)
                if (index === -1) {
                    menu.push(ele)
                } else {
                    menu[index] = ele
                }
            })
            state.menuAll = menu
            setStore({ name: 'menuAll', content: state.menuAll })
        },
        SET_MENU_ALL_NULL: state => {
            state.menuAll = []
            setStore({ name: 'menuAll', content: state.menuAll })
        },
        SET_MENU: (state, menu) => {
            state.menu = menu
            setStore({ name: 'menu', content: state.menu })
        },
        SET_ROLES: (state, roles) => {
            state.roles = roles
        },
        SET_PERMISSION: (state, permission) => {
            let result = []
  },
  actions: {
    //根据用户名登录
    LoginByUsername({ commit }, userInfo = {}) {
      return new Promise((resolve, reject) => {
        loginByUsername(
          userInfo.tenantId,
          userInfo.deptId,
          userInfo.roleId,
          userInfo.username,
          md5(userInfo.password),
          userInfo.type,
          userInfo.key,
          userInfo.code
        )
          .then(res => {
            const data = res.data;
            if (data.error_description) {
              ElMessage({
                message: data.error_description,
                type: 'error',
              });
            } else {
              commit('SET_TOKEN', data.access_token);
              commit('SET_REFRESH_TOKEN', data.refresh_token);
              commit('SET_TENANT_ID', data.tenant_id);
              commit('SET_USER_INFO', data);
              commit('SET_PARENT_DEPT_INFO', data.dept_id);
              commit('DEL_ALL_TAG');
              commit('CLEAR_LOCK');
            }
            resolve();
          })
          .catch(err => {
            reject(err);
          });
      });
    },
    //根据第三方信息登录
    LoginBySocial({ commit }, userInfo) {
      return new Promise((resolve, reject) => {
        loginBySocial(userInfo.tenantId, userInfo.source, userInfo.code, userInfo.state)
          .then(res => {
            const data = res.data;
            if (data.error_description) {
              ElMessage({
                message: data.error_description,
                type: 'error',
              });
            } else {
              commit('SET_TOKEN', data.access_token);
              commit('SET_REFRESH_TOKEN', data.refresh_token);
              commit('SET_USER_INFO', data);
              commit('SET_TENANT_ID', data.tenant_id);
              commit('DEL_ALL_TAG');
              commit('CLEAR_LOCK');
            }
            resolve();
          })
          .catch(err => {
            reject(err);
          });
      });
    },
    //根据单点信息登录
    LoginBySso({ commit }, userInfo) {
      return new Promise((resolve, reject) => {
        loginBySso(userInfo.state, userInfo.code)
          .then(res => {
            const data = res.data;
            if (data.error_description) {
              ElMessage({
                message: data.error_description,
                type: 'error',
              });
            } else {
              commit('SET_TOKEN', data.access_token);
              commit('SET_REFRESH_TOKEN', data.refresh_token);
              commit('SET_USER_INFO', data);
              commit('SET_TENANT_ID', data.tenant_id);
              commit('DEL_ALL_TAG');
              commit('CLEAR_LOCK');
            }
            resolve();
          })
          .catch(err => {
            reject(err);
          });
      });
    },
    //根据手机信息登录
    LoginByPhone({ commit }, userInfo) {
      return new Promise((resolve, reject) => {
        loginByPhone(
          userInfo.tenantId,
          encrypt(userInfo.phone),
          userInfo.codeId,
          userInfo.codeValue
        )
          .then(res => {
            const data = res.data;
            if (data.error_description) {
              ElMessage({
                message: data.error_description,
                type: 'error',
              });
            } else {
              commit('SET_TOKEN', data.access_token);
              commit('SET_REFRESH_TOKEN', data.refresh_token);
              commit('SET_USER_INFO', data);
              commit('SET_TENANT_ID', data.tenant_id);
              commit('DEL_ALL_TAG');
              commit('CLEAR_LOCK');
            }
            resolve();
          })
          .catch(err => {
            reject(err);
          });
      });
    },
    //用户注册
    RegisterUser({ commit }, userInfo = {}) {
      return new Promise((resolve, reject) => {
        registerUser(
          userInfo.tenantId,
          userInfo.name,
          userInfo.account,
          encrypt(userInfo.password),
          userInfo.phone,
          userInfo.email
        ).then(res => {
          const data = res.data;
          if (data.error_description) {
            ElMessage({
              message: data.error_description,
              type: 'error',
            });
            reject(data.error_description);
          } else {
            commit('SET_TOKEN', data.access_token);
            commit('SET_REFRESH_TOKEN', data.refresh_token);
            commit('SET_USER_INFO', data);
            commit('SET_TENANT_ID', data.tenant_id);
            commit('DEL_ALL_TAG');
            commit('CLEAR_LOCK');
          }
          resolve();
        });
      });
    },
    GetUserInfo({ commit }) {
      return new Promise((resolve, reject) => {
        getUserInfo()
          .then(res => {
            const data = res.data.data;
            commit('SET_ROLES', data.roles);
            resolve(data);
          })
          .catch(err => {
            reject(err);
          });
      });
    },
    //刷新token
    RefreshToken({ state, commit }, userInfo) {
      return new Promise((resolve, reject) => {
        refreshToken(
          getRefreshToken(),
          state.tenantId,
          !validatenull(userInfo) ? userInfo.deptId : state.userInfo.dept_id,
          !validatenull(userInfo) ? userInfo.roleId : state.userInfo.role_id
        )
          .then(res => {
            const data = res.data;
            commit('SET_TOKEN', data.access_token);
            commit('SET_REFRESH_TOKEN', data.refresh_token);
            commit('SET_USER_INFO', data);
            resolve();
          })
          .catch(error => {
            reject(error);
          });
      });
    },
    // 登出
    LogOut({ commit }) {
      return new Promise((resolve, reject) => {
        logout()
          .then(() => {
            commit('SET_TOKEN', '');
            commit('SET_MENU_ALL_NULL', []);
            commit('SET_MENU', []);
            commit('SET_ROLES', []);
            commit('DEL_ALL_TAG', []);
            commit('CLEAR_LOCK');
            removeToken();
            removeRefreshToken();
            resolve();
          })
          .catch(error => {
            reject(error);
          });
      });
    },
    //注销session
    FedLogOut({ commit }) {
      return new Promise(resolve => {
        commit('SET_TOKEN', '');
        commit('SET_MENU_ALL_NULL', []);
        commit('SET_MENU', []);
        commit('SET_ROLES', []);
        commit('DEL_ALL_TAG', []);
        commit('CLEAR_LOCK');
        removeToken();
        removeRefreshToken();
        removeToken();
        resolve();
      });
    },
    GetTopMenu() {
      return new Promise(resolve => {
        getTopMenu().then(res => {
          const data = res.data.data || [];
          resolve(data);
        });
      });
    },
    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
          env === 'development'
            ? window.location.href = "/manage/login"
            : window.location.replace(`${adminUrl}#/login`)
        })
      }
      return new Promise(resolve => {
        getRoutes({tenantId,sysType: 6}).then(res => {
          const data = res.data.data;
          let menu = deepClone(data);
          if (!res.data?.data?.length){
            errLogOut()
            return
          }
          menu.forEach(ele => formatPath(ele, true));
          commit('SET_MENU', menu);
          commit('SET_MENU_ALL', menu);
          dispatch('GetButtons');
          resolve(menu);
        }).catch(err =>{
          errLogOut()
        })
      });
    },
    GetButtons({ commit }) {
      return new Promise(resolve => {
        getButtons({sysType: 6}).then(res => {
          const data = res.data.data;
          commit('SET_PERMISSION', data);
          resolve();
        });
      });
    },
  },
  mutations: {
    SET_PARENT_DEPT_INFO(state, deptId){
      getParentDeptInfo({deptId:deptId}).then(res=>{
        const data = res.data.data;
        state.parentDeptInfo = data;
        setStore({ name: 'parentDeptInfo', content: data });
      })
    },
    SET_TOKEN: (state, token) => {
      setToken(token);
      state.token = token;
      setStore({ name: 'token', content: state.token });
    },
    SET_REFRESH_TOKEN: (state, refreshToken) => {
      setRefreshToken(refreshToken);
      state.refreshToken = refreshToken;
      setStore({ name: 'refreshToken', content: state.refreshToken });
    },
    SET_MENU_ID(state, menuId) {
      state.menuId = menuId;
    },
    SET_TENANT_ID: (state, tenantId) => {
      state.tenantId = tenantId;
      setStore({ name: 'tenantId', content: state.tenantId });
    },
    SET_USER_INFO: (state, userInfo) => {
      if (validatenull(userInfo.user_id) && validatenull(userInfo.account)) {
        state.userInfo = { user_name: 'unauth', role_name: 'unauth' };
      } else {
        if (validatenull(userInfo.avatar)) {
          userInfo.avatar = defaultAva;
        }
        state.userInfo = userInfo;
      }
      setStore({ name: 'userInfo', content: state.userInfo });
    },
    SET_MENU_ALL: (state, menuAll) => {
      let menu = state.menuAll;
      menuAll.forEach(ele => {
        let index = menu.findIndex(item => item.path === ele.path);
        if (index === -1) {
          menu.push(ele);
        } else {
          menu[index] = ele;
        }
      });
      state.menuAll = menu;
      setStore({ name: 'menuAll', content: state.menuAll });
    },
    SET_MENU_ALL_NULL: state => {
      state.menuAll = [];
      setStore({ name: 'menuAll', content: state.menuAll });
    },
    SET_MENU: (state, menu) => {
      state.menu = menu;
      setStore({ name: 'menu', content: state.menu });
    },
    SET_ROLES: (state, roles) => {
      state.roles = roles;
    },
    SET_PERMISSION: (state, permission) => {
      let result = [];
            function getCode(list) {
                list.forEach(ele => {
                    if (typeof ele === 'object') {
                        const children = ele.children
                        const code = ele.code
                        if (children) {
                            getCode(children)
                        } else {
                            result.push(code)
                        }
                    }
                })
            }
      function getCode(list) {
        list.forEach(ele => {
          if (typeof ele === 'object') {
            const children = ele.children;
            const code = ele.code;
            if (children) {
              getCode(children);
            } else {
              result.push(code);
            }
          }
        });
      }
      getCode(permission);
      state.permission = {};
      result.forEach(ele => {
        state.permission[ele] = true;
      });
      setStore({ name: 'permission', content: state.permission, type: 'session' });
    },
  },
};
export default user;
            getCode(permission)
            state.permission = {}
            result.forEach(ele => {
                state.permission[ele] = true
            })
            setStore({ name: 'permission', content: state.permission, type: 'session' })
        },
    },
}
export default user
applications/task-work-order/src/styles/common/cockpit.scss
@@ -1,3 +1,5 @@
//变量
$gd-placeholder: #86909C;
.gd-table {
  height: 0;
@@ -20,9 +22,7 @@
    border-bottom: 1px solid #DBDFF1 !important;
    .cell {
      padding: 0;
      padding-left: 16px;
      padding: 0 0 0 16px;
      font-family: Source Han Sans CN, Source Han Sans CN;
      font-weight: 400;
      font-size: 14px;
@@ -38,9 +38,7 @@
    height: 56px;
    .cell {
      padding: 0;
      padding-left: 16px;
      padding: 0 0 0 16px;
      font-family: Source Han Sans CN, Source Han Sans CN;
      font-weight: 400;
      font-size: 14px;
@@ -79,25 +77,25 @@
  }
}
.gd-input {
  .el-input__wrapper {
  .el-input__wrapper,.el-textarea__inner {
    padding: 0 8px !important;
    background: #DDE2ED;
    background: #F2F3F5;
    box-shadow: none !important;
    border: none;
  }
    .el-input__inner {
      color: #383874;
      &::placeholder {
        color: #A1A3D4;
      }
  .el-input__inner,.el-textarea__inner {
    &::placeholder {
      color: $gd-placeholder;
    }
  }
  &.is-disabled {
    .el-input__wrapper {
      background-color: #DDE2ED;
      background: #DDE2ED;
    }
  }
}
@@ -117,7 +115,7 @@
  .el-select__placeholder {
    &.is-transparent {
      color: #A1A3D4 !important;
      color: $gd-placeholder !important;
    }
    &.el-select__selected-item {
@@ -240,7 +238,7 @@
    color: #fff;
    &::placeholder {
      color: #A1A3D4;
      color: $gd-placeholder;
    }
  }
@@ -583,16 +581,6 @@
            }
          }
          .el-textarea {
            .el-textarea__inner {
              color: #fff;
              background: #DDE2ED !important;
              border: none;
              box-shadow: none;
            }
          }
          .el-button {
            color: #fff;
            background: #DDE2ED !important;
@@ -661,28 +649,7 @@
          }
        }
        .el-input {
          .el-input__wrapper {
            padding: 0 8px !important;
            background: #F2F3F5;
            box-shadow: none !important;
            border: none;
            .el-input__inner {
              color: #383874;
              &::placeholder {
                color: #86909C;
              }
            }
          }
          &.is-disabled {
            .el-input__wrapper {
              background-color: #F2F3F5;
            }
          }
        }
      }
    }
  }
applications/task-work-order/src/views/basicManage/maintainRecord/FormDiaLog.vue
@@ -1,7 +1,12 @@
<template>
    <el-dialog class="gd-dialog" v-model="visible" :title="titleEnum[dialogMode]" @closed="handleClosed" destroy-on-close>
    <el-dialog
        class="gd-dialog"
        v-model="visible"
        :title="titleEnum[dialogMode]"
        @closed="visible = false"
        destroy-on-close
    >
        <div class="detail-container" v-if="dialogReadonly">
            <div class="detail-title">设备详情</div>
            <el-row>
                <el-col :span="12">
                    <div class="label">设备名称</div>
@@ -44,29 +49,6 @@
                    <div class="val">{{ formData.manufacturer }}</div>
                </el-col>
            </el-row>
            <div class="detail-title">维护详情</div>
            <div class="gd-table-container" v-loading="loading">
                <div class="gd-table-content">
                    <el-table class="gd-table" :data="list">
                        <el-table-column type="index" width="60" label="序号" />
                        <el-table-column prop="maintainTime" label="维护时间" />
                        <el-table-column prop="maintainContent" label="维护内容" />
                        <el-table-column prop="replacePart" label="跟换部件" />
                    </el-table>
                </div>
                <div class="gd-pagination-parent">
                    <el-pagination
                        popper-class="gd-select-popper"
                        v-model:current-page="searchParams.current"
                        v-model:page-size="searchParams.size"
                        layout="total, prev, pager, next, sizes"
                        :total="total"
                        @change="getList"
                    />
                </div>
            </div>
        </div>
        <el-form
@@ -107,30 +89,10 @@
                        </el-select>
                    </el-form-item>
                </el-col>
                <el-col :span="12">
                    <el-form-item label="维护计划时间" prop="planCycleValue">
                        <el-select
                            class="gd-select"
                            popper-class="gd-select-popper"
                            v-model="formData.planCycleValue"
                            placeholder="请选择"
                            clearable
                            multiple
                            :disabled="!formData.planCycleType"
                        >
                            <el-option
                                v-for="item in planCycleValueOptions"
                                :key="item.value"
                                :label="item.value"
                                :value="item.value"
                            />
                        </el-select>
                    </el-form-item>
                </el-col>
            </el-row>
        </el-form>
        <template #footer>
            <el-button color="#F2F3F5" @click="handleCancel">{{ dialogReadonly ? '关闭' : '取消' }}</el-button>
            <el-button color="#F2F3F5" @click="visible = false">{{ dialogReadonly ? '关闭' : '取消' }}</el-button>
            <el-button
                class="save-btn"
                color="#4C34FF"
@@ -149,11 +111,9 @@
import { computed, ref } from 'vue'
import { ElMessage } from 'element-plus'
import { fieldRules, getDictLabel } from '@ztzf/utils'
import { fwDeviceMaintainRecordPageApi } from '@/views/basicManage/maintainRecord/fwDeviceMaintainRecord'
import {
    fwDeviceMaintainPlanDetailApi,
    fwDeviceMaintainPlanSubmitApi,
    fwDevicePageApi,
} from '@/views/basicManage/maintainRecord/maintainRecordApi'
const initForm = () => ({
@@ -163,41 +123,23 @@
})
const planCycleOptions = inject('planCycleOptions')
const planCycleValueOptions = ref([])
const emit = defineEmits(['success'])
const formRef = ref(null) // 表单实例
const formData = ref(initForm()) // 表单数据
const visible = ref(false) // 弹框显隐
const visible = defineModel() // 弹框显隐
const dialogMode = ref('add') // 弹框模式
const submitting = ref(false) // 提交中
const deviceList = ref([]) // 设备列表
const getPlanCycleLabel = inject('getPlanCycleLabel')
const dictObj = inject('dictObj')
const dialogReadonly = computed(() => dialogMode.value === 'view')
const titleEnum = ref({ edit: '编辑', view: '查看', add: '维护计划' })
const planCycleLabel = computed(() => {
    const item = planCycleOptions.find(option => option.value === formData.value.planCycleType)
    return item?.label || formData.value.planCycleType || '-'
})
const titleEnum = ref({ edit: '编辑', view: '查看', add: '新增' })
const rules = {
    deviceId: fieldRules(true),
    planCycleType: fieldRules(true),
    planCycleValue: [{ required: true, message: '请选择', trigger: ['blur', 'change'] }],
}
// 获取设备列表
function getDeviceList() {
    return fwDevicePageApi({ current: 1, size: 999 }).then(res => {
        deviceList.value = res?.data?.data?.records ?? []
    })
}
// 关闭弹框
function handleCancel() {
    visible.value = false
}
// 提交新增/编辑
@@ -220,70 +162,21 @@
    if (!formData.value.id) return
    const res = await fwDeviceMaintainPlanDetailApi({ id: formData.value.id })
    formData.value = res?.data?.data ?? {}
    console.log(formData.value, 66)
}
// 关闭后重置
function handleClosed() {
    formData.value = initForm()
}
const initSearchParams = () => ({
    current: 1, // 当前页
    size: 10, // 每页大小
})
const searchParams = ref(initSearchParams())
const total = ref(0) // 总条数
const loading = ref(true) // 列表加载中
const list = ref([]) // 列表数据
async function getList() {
    loading.value = true
    try {
        const params = { ...searchParams.value, planId: formData.value.id }
        const res = await fwDeviceMaintainRecordPageApi(params)
        list.value = res?.data?.data?.records ?? []
        total.value = res?.data?.data?.total ?? 0
    } finally {
        loading.value = false
    }
}
// 打开弹框
async function open({ mode, row } = {}) {
    dialogMode.value = mode || 'add'
    visible.value = true
    await getDeviceList()
    if (dialogMode.value === 'add') {
        formData.value = initForm()
    } else {
        formData.value = row
async function open({ mode = 'add', row } = {}) {
    dialogMode.value = mode
    formData.value = dialogMode.value === 'add' ? initForm() : row
    if (dialogMode.value !== 'add') {
        await loadDetail()
        getList()
    }
}
defineExpose({ open })
watch(
    () => formData.value?.planCycleType,
    type => {
        planCycleValueOptions.value = []
        if (!type) return
        if (type === '1') {
            ;[...Array(12)].forEach((item, index) => {
                ;[...Array(31)].forEach((item1, index1) => {
                    planCycleValueOptions.value.push({ value: `${index + 1}月${index1 + 1}号` })
                })
            })
        } else if (type === '2') {
            ;[...Array(31)].forEach((item, index) => {
                planCycleValueOptions.value.push({ value: `${index + 1}号` })
            })
        } else if (type === '3') {
            ;[...Array(7)].forEach((item, index) => {
                planCycleValueOptions.value.push({ value: `星期${index + 1}` })
            })
        }
    }
)
</script>
applications/task-work-order/src/views/basicManage/maintainRecord/MaintenanceDiaLog.vue
File was deleted
applications/task-work-order/src/views/basicManage/maintainRecord/fwDeviceMaintainRecord.js
File was deleted
applications/task-work-order/src/views/basicManage/maintainRecord/index.vue
@@ -50,7 +50,7 @@
        </el-form>
        <div class="gd-table-toolbar">
            <el-button :icon="Plus" color="#4C34FF" type="primary" @click="handleAdd">维护计划</el-button>
            <el-button :icon="Plus" color="#4C34FF" type="primary" @click="openForm('add')">新增</el-button>
            <el-button :icon="Delete" color="#4C34FF" :disabled="!selectedIds.length" @click="handleDelete()">删除</el-button>
        </div>
@@ -76,8 +76,8 @@
                    </el-table-column>
                    <el-table-column label="操作" class-name="operation-btns">
                        <template v-slot="{ row }">
                            <el-link @click="handleView(row)">查看</el-link>
                            <el-link @click="maintenance(row)">维护</el-link>
                            <el-link @click="openForm('view', row)">查看</el-link>
                            <el-link @click="openForm('edit', row)">编辑</el-link>
                            <el-link @click="handleDelete(row)">删除</el-link>
                        </template>
                    </el-table-column>
@@ -96,8 +96,7 @@
            </div>
        </div>
        <FormDiaLog ref="dialogRef" @success="getList" />
        <MaintenanceDiaLog ref="maintenanceDialogRef" @success="getList" />
        <FormDiaLog ref="dialogRef" @success="getList" v-if="dialogVisible" v-model="dialogVisible" />
    </basic-container>
</template>
<script setup>
@@ -108,7 +107,6 @@
import { getDeptTree } from '@/api/system/dept'
import { getDictLabel } from '@ztzf/utils'
import FormDiaLog from './FormDiaLog.vue'
import MaintenanceDiaLog from '@/views/basicManage/maintainRecord/MaintenanceDiaLog.vue'
import {
    fwDeviceMaintainPlanPageApi,
    fwDeviceMaintainPlanRemoveApi,
@@ -128,7 +126,7 @@
const selectedIds = ref([]) // 勾选的ID列表
const queryParamsRef = ref(null) // 查询表单实例
const dialogRef = ref(null) // 弹框实例
const maintenanceDialogRef = ref(null) // 弹框实例
const dialogVisible = ref(false)
const dictObj = ref({
    deviceType: [], // 设备类型
    deviceAtt: [], // 设备属性
@@ -171,14 +169,13 @@
    getList()
}
// 查看
function handleView(row) {
    dialogRef.value?.open({ mode: 'view', row: { ...row } })
}
// 维护
function maintenance(row) {
    maintenanceDialogRef.value?.open({ mode: 'edit', row: { ...row } })
// 新增/编辑/查看 弹框
function openForm(mode, row) {
    dialogVisible.value = true
    nextTick(() => {
        dialogRef.value?.open({ mode, row })
    })
}
// 删除
@@ -214,11 +211,6 @@
    getDeptTree().then(res => {
        deptTree.value = res.data.data
    })
}
// 新增
function handleAdd() {
    dialogRef.value?.open({ mode: 'add' })
}
function getPlanCycleLabel(row) {
applications/task-work-order/src/views/orderView/orderDataManage/evaluate/FormDiaLog.vue
New file
@@ -0,0 +1,220 @@
<template>
    <el-dialog
        class="gd-dialog"
        v-model="visible"
        :title="titleEnum[dialogMode]"
        @closed="visible = false"
        destroy-on-close
    >
        <div class="detail-container" v-if="dialogReadonly">
            <el-row>
                <el-col :span="12">
                    <div class="label">标题</div>
                    <div class="val">{{ formData.title }}</div>
                </el-col>
                <el-col :span="12">
                    <div class="label">数据名称</div>
                    <div class="val">{{ formData.dataName }}</div>
                </el-col>
                <el-col :span="12">
                    <div class="label">提出部门</div>
                    <div class="val">{{ formData.proposeDeptName }}</div>
                </el-col>
                <el-col :span="12">
                    <div class="label">数据提供部门</div>
                    <div class="val">{{ formData.dataProvideDeptName }}</div>
                </el-col>
                <el-col :span="12">
                    <div class="label">评分</div>
                    <div class="val">{{ formData.score }}</div>
                </el-col>
                <el-col :span="12">
                    <div class="label">是否解决</div>
                    <div class="val">{{ formData.isResolved === '1' ? '是' : '否' }}</div>
                </el-col>
                <el-col :span="24">
                    <div class="label">评价内容</div>
                    <div class="val">{{ formData.evaluationContent }}</div>
                </el-col>
            </el-row>
        </div>
        <el-form
            class="dialog-form"
            v-else
            ref="formRef"
            :model="formData"
            :rules="rules"
            :disabled="dialogReadonly"
            label-width="80px"
        >
            <el-row>
                <el-col :span="12">
                    <el-form-item label="标题" prop="title">
                        <el-input class="gd-input" v-model="formData.title" placeholder="请输入" clearable />
                    </el-form-item>
                </el-col>
                <el-col :span="12">
                    <el-form-item label="数据名称" prop="dataName">
                        <el-input class="gd-input" v-model="formData.dataName" placeholder="请输入" clearable />
                    </el-form-item>
                </el-col>
                <el-col :span="12">
                    <el-form-item label="提出部门" prop="proposeDeptId">
                        <el-tree-select
                            class="gd-select"
                            popper-class="gd-tree-select-popper"
                            v-model="formData.proposeDeptId"
                            node-key="id"
                            :data="deptTree"
                            :props="treeProps"
                            check-strictly
                            clearable
                        />
                    </el-form-item>
                </el-col>
                <el-col :span="12">
                    <el-form-item label="数据提供部门" prop="dataProvideDeptId">
                        <el-tree-select
                            class="gd-select"
                            popper-class="gd-tree-select-popper"
                            v-model="formData.dataProvideDeptId"
                            node-key="id"
                            :data="deptTree"
                            :props="treeProps"
                            check-strictly
                            clearable
                        />
                    </el-form-item>
                </el-col>
                <el-col :span="12">
                    <el-form-item label="评分" prop="score">
                        <el-input-number
                            class="gd-input"
                            v-model="formData.score"
                            :min="1"
                            :max="10"
                            placeholder="请输入"
                            controls-position="right"
                        />
                    </el-form-item>
                </el-col>
                <el-col :span="12">
                    <el-form-item label="是否解决" prop="isResolved">
                        <el-select
                            class="gd-select"
                            popper-class="gd-select-popper"
                            v-model="formData.isResolved"
                            placeholder="请选择"
                            clearable
                        >
                            <el-option v-for="item in isResolvedOptions" :key="item.value" :label="item.label" :value="item.value" />
                        </el-select>
                    </el-form-item>
                </el-col>
                <el-col :span="24">
                    <el-form-item label="评价内容" prop="evaluationContent">
                        <el-input
                            class="gd-input"
                            v-model="formData.evaluationContent"
                            type="textarea"
                            :rows="4"
                            placeholder="请输入"
                            clearable
                        />
                    </el-form-item>
                </el-col>
            </el-row>
        </el-form>
        <template #footer>
            <el-button color="#F2F3F5" @click="visible = false">{{ dialogReadonly ? '关闭' : '取消' }}</el-button>
            <el-button
                class="save-btn"
                color="#4C34FF"
                v-if="!dialogReadonly"
                :loading="submitting"
                :disabled="submitting"
                @click="handleSubmit"
            >
                保存
            </el-button>
        </template>
    </el-dialog>
</template>
<script setup>
import { computed, ref } from 'vue'
import { ElMessage } from 'element-plus'
import { fieldRules } from '@ztzf/utils'
import {
    gdDataEvaluationDetailApi,
    gdDataEvaluationSubmitApi,
} from '@/views/orderView/orderDataManage/evaluate/evaluateApi'
// 初始化表单数据
const initForm = () => ({
    title: '',
    dataName: '',
    proposeDeptId: '',
    dataProvideDeptId: '',
    score: null,
    isResolved: '',
    evaluationContent: '',
})
const deptTree = inject('deptTree')
const treeProps = inject('treeProps')
const isResolvedOptions = inject('isResolvedOptions')
const emit = defineEmits(['success'])
const formRef = ref(null) // 表单实例
const formData = ref(initForm()) // 表单数据
const visible = defineModel() // 弹框显隐
const dialogMode = ref('add') // 弹框模式
const submitting = ref(false) // 提交中
const dialogReadonly = computed(() => dialogMode.value === 'view')
const titleEnum = ref({ edit: '编辑', view: '查看', add: '新增' })
const rules = {
    title: fieldRules(true),
    dataName: fieldRules(true),
    proposeDeptId: fieldRules(true),
    dataProvideDeptId: fieldRules(true),
    score: fieldRules(true),
    isResolved: fieldRules(true),
    evaluationContent: fieldRules(true),
}
// 提交新增/编辑
async function handleSubmit() {
    const isValid = await formRef.value?.validate().catch(() => false)
    if (!isValid) return
    submitting.value = true
    try {
        await gdDataEvaluationSubmitApi(formData.value)
        ElMessage.success(dialogMode.value === 'add' ? '新增成功' : '更新成功')
        visible.value = false
        emit('success')
    } finally {
        submitting.value = false
    }
}
// 加载详情
async function loadDetail() {
    if (!formData.value.id) return
    const res = await gdDataEvaluationDetailApi({ id: formData.value.id })
    formData.value = res?.data?.data ?? {}
}
// 打开弹框
async function open({ mode = 'add', row } = {}) {
    dialogMode.value = mode
    formData.value = dialogMode.value === 'add' ? initForm() : row
    if (dialogMode.value !== 'add') {
        await loadDetail()
    }
}
defineExpose({ open })
</script>
applications/task-work-order/src/views/orderView/orderDataManage/evaluate/evaluateApi.js
New file
@@ -0,0 +1,37 @@
import request from '@/axios'
// 列表
export const gdDataEvaluationPageApi = params => {
    return request({
        url: `/drone-gd/orderdata/gdDataEvaluation/page`,
        method: 'get',
        params: { descs: 'update_time', ...params },
    })
}
// 新增或编辑
export const gdDataEvaluationSubmitApi = data => {
    return request({
        url: `/drone-gd/orderdata/gdDataEvaluation/submit`,
        method: 'post',
        data,
    })
}
// 删除
export const gdDataEvaluationRemoveApi = params => {
    return request({
        url: `/drone-gd/orderdata/gdDataEvaluation/remove`,
        method: 'post',
        params,
    })
}
// 详情
export const gdDataEvaluationDetailApi = params => {
    return request({
        url: `/drone-gd/orderdata/gdDataEvaluation/detail`,
        method: 'get',
        params,
    })
}
applications/task-work-order/src/views/orderView/orderDataManage/evaluate/index.vue
@@ -1,9 +1,177 @@
<template>
  <basic-container>
    评价管理
  </basic-container>
    <basic-container>
        <el-form ref="queryParamsRef" :model="searchParams" class="gd-search-form">
            <el-form-item label="标题" prop="title">
                <el-input class="gd-input" v-model="searchParams.title" placeholder="请输入" clearable @clear="handleSearch" />
            </el-form-item>
            <el-form-item label="是否解决" prop="isResolved">
                <el-select
                    class="gd-select"
                    popper-class="gd-select-popper"
                    v-model="searchParams.isResolved"
                    placeholder="请选择"
                    clearable
                    @change="handleSearch"
                >
                    <el-option v-for="item in isResolvedOptions" :key="item.value" :label="item.label" :value="item.value" />
                </el-select>
            </el-form-item>
            <el-form-item class="gd-search-actions">
                <el-button :icon="RefreshRight" @click="resetForm"></el-button>
                <el-button class="search-btn" :icon="Search" @click="handleSearch"></el-button>
            </el-form-item>
        </el-form>
        <div class="gd-table-toolbar">
            <el-button :icon="Plus" color="#4C34FF" type="primary" @click="openForm('add')">新增</el-button>
            <el-button :icon="Delete" color="#4C34FF" :disabled="!selectedIds.length" @click="handleDelete()">删除</el-button>
        </div>
        <div class="gd-table-container" v-loading="loading">
            <div class="gd-table-content gd-table-content-bg">
                <el-table class="gd-table" :data="list" @selection-change="handleSelectionChange">
                    <el-table-column type="selection" width="46" />
                    <el-table-column prop="title" show-overflow-tooltip label="标题" />
                    <el-table-column prop="dataName" show-overflow-tooltip label="数据名称" />
                    <el-table-column prop="proposeDeptName" show-overflow-tooltip label="提出部门" />
                    <el-table-column prop="dataProvideDeptName" show-overflow-tooltip label="数据提供部门" />
                    <el-table-column prop="score" show-overflow-tooltip label="评分" />
                    <el-table-column prop="isResolved" show-overflow-tooltip label="是否解决">
                        <template v-slot="{ row }">
                            {{ row.isResolved === '1' ? '是' : '否' }}
                        </template>
                    </el-table-column>
                    <el-table-column prop="evaluationContent" show-overflow-tooltip label="评价内容" />
                    <el-table-column label="操作" class-name="operation-btns">
                        <template v-slot="{ row }">
                            <el-link @click="openForm('view', row)">查看</el-link>
                            <el-link @click="openForm('edit', row)">编辑</el-link>
                            <el-link @click="handleDelete(row)">删除</el-link>
                        </template>
                    </el-table-column>
                </el-table>
            </div>
            <div class="gd-pagination-parent">
                <el-pagination
                    popper-class="gd-select-popper"
                    v-model:current-page="searchParams.current"
                    v-model:page-size="searchParams.size"
                    layout="total, prev, pager, next, sizes"
                    :total="total"
                    @change="getList"
                />
            </div>
        </div>
        <FormDiaLog ref="dialogRef" @success="getList" v-if="dialogVisible" v-model="dialogVisible" />
    </basic-container>
</template>
<script setup>
import { Search, RefreshRight, Plus, Delete } from '@element-plus/icons-vue'
import { onMounted, ref } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { getDeptTree } from '@/api/system/dept'
import FormDiaLog from './FormDiaLog.vue'
import {
    gdDataEvaluationPageApi,
    gdDataEvaluationRemoveApi,
} from '@/views/orderView/orderDataManage/evaluate/evaluateApi'
// 初始化查询参数
const initSearchParams = () => ({
    title: '', // 标题
    isResolved: '', // 是否解决
    current: 1, // 当前页
    size: 10, // 每页大小
})
const searchParams = ref(initSearchParams()) // 查询参数
const total = ref(0) // 总条数
const loading = ref(true) // 列表加载中
const list = ref([]) // 列表数据
const selectedIds = ref([]) // 勾选的ID列表
const queryParamsRef = ref(null) // 查询表单实例
const dialogRef = ref(null) // 弹框实例
const dialogVisible = ref(false)
const deptTree = ref([]) // 部门树
const treeProps = {
    label: 'name',
    children: 'children',
}
const isResolvedOptions = [
    { label: '否', value: '0' },
    { label: '是', value: '1' },
]
provide('deptTree', deptTree)
provide('treeProps', treeProps)
provide('isResolvedOptions', isResolvedOptions)
// 获取列表
async function getList() {
    loading.value = true
    try {
        const res = await gdDataEvaluationPageApi(searchParams.value)
        list.value = res?.data?.data?.records ?? []
        total.value = res?.data?.data?.total ?? 0
    } finally {
        loading.value = false
    }
}
// 查询
function handleSearch() {
    searchParams.value.current = 1
    getList()
}
// 重置查询
function resetForm() {
    queryParamsRef.value?.resetFields()
    searchParams.value.current = 1
    getList()
}
// 新增/编辑/查看 弹框
function openForm(mode, row) {
    dialogVisible.value = true
    nextTick(() => {
        dialogRef.value?.open({ mode, row })
    })
}
// 删除
async function handleDelete(row) {
    const tips = row ? '该条' : '选中的项'
    await ElMessageBox.confirm(`确认删除${tips}吗?`, '提示', {
        type: 'warning',
        customClass: 'gd-confirm-custom',
        confirmButtonClass: 'gd-confirm-button',
        cancelButtonClass: 'gd-confirm-cancel-button',
    })
    const ids = row ? row.id : selectedIds.value.join(',')
    await gdDataEvaluationRemoveApi({ ids })
    ElMessage.success('删除成功')
    selectedIds.value = []
    getList()
}
// 勾选值设置
function handleSelectionChange(rows) {
    selectedIds.value = rows.map(item => item.id)
}
// 获取部门树
function getDeptTreeFun() {
    getDeptTree().then(res => {
        deptTree.value = res.data.data
    })
}
onMounted(() => {
    getList()
    getDeptTreeFun()
})
</script>
<style scoped lang="scss">
</style>
<style scoped lang="scss"></style>
applications/task-work-order/src/views/orderView/orderDataManage/supplyAdd/FormDiaLog.vue
New file
@@ -0,0 +1,291 @@
<template>
    <el-dialog
        class="gd-dialog"
        v-model="visible"
        :title="titleEnum[dialogMode]"
        @closed="visible = false"
        destroy-on-close
    >
        <div class="detail-container" v-if="dialogReadonly">
            <el-row>
                <el-col :span="12">
                    <div class="label">需求名称</div>
                    <div class="val">{{ formData.demandName }}</div>
                </el-col>
                <el-col :span="12">
                    <div class="label">需求类型</div>
                    <div class="val">{{ getDictLabel(formData.demandType, dictObj.requirementType) }}</div>
                </el-col>
                <el-col :span="12">
                    <div class="label">需求联系人</div>
                    <div class="val">{{ formData.contactPerson }}</div>
                </el-col>
                <el-col :span="12">
                    <div class="label">需求联系人电话</div>
                    <div class="val">{{ formData.contactPhone }}</div>
                </el-col>
                <el-col :span="12">
                    <div class="label">需求邮箱</div>
                    <div class="val">{{ formData.contactEmail }}</div>
                </el-col>
                <el-col :span="12">
                    <div class="label">更新周期</div>
                    <div class="val">{{ formData.updateCycle }}</div>
                </el-col>
                <el-col :span="12">
                    <div class="label">申请依据</div>
                    <div class="val">{{ formData.applyBasis }}</div>
                </el-col>
                <el-col :span="12">
                    <div class="label">共享类型</div>
                    <div class="val">{{ getDictLabel(formData.shareType, dictObj.sharedType) }}</div>
                </el-col>
                <el-col :span="12">
                    <div class="label">应用场景类型</div>
                    <div class="val">{{ getDictLabel(formData.applicationScene, dictObj.appSceneType) }}</div>
                </el-col>
                <el-col :span="12">
                    <div class="label">责任部门</div>
                    <div class="val">{{ formData.responsibleDeptName }}</div>
                </el-col>
                <el-col :span="12">
                    <div class="label">数据来源依据</div>
                    <div class="val">{{ formData.dataSource }}</div>
                </el-col>
                <el-col :span="24">
                    <div class="label">需求信息项</div>
                    <div class="val">{{ formData.demandInfo }}</div>
                </el-col>
            </el-row>
        </div>
        <el-form
            class="dialog-form"
            v-else
            ref="formRef"
            :model="formData"
            :rules="rules"
            :disabled="dialogReadonly"
            label-width="120px"
        >
            <el-row>
                <el-col :span="12">
                    <el-form-item label="需求名称" prop="demandName">
                        <el-input class="gd-input" v-model="formData.demandName" placeholder="请输入" clearable />
                    </el-form-item>
                </el-col>
                <el-col :span="12">
                    <el-form-item label="需求类型" prop="demandType">
                        <el-select
                            class="gd-select"
                            popper-class="gd-select-popper"
                            v-model="formData.demandType"
                            placeholder="请选择"
                            clearable
                        >
                            <el-option
                                v-for="item in dictObj.requirementType"
                                :key="item.dictKey"
                                :label="item.dictValue"
                                :value="item.dictKey"
                            />
                        </el-select>
                    </el-form-item>
                </el-col>
                <el-col :span="12">
                    <el-form-item label="需求联系人" prop="contactPerson">
                        <el-input class="gd-input" v-model="formData.contactPerson" placeholder="请输入" clearable />
                    </el-form-item>
                </el-col>
                <el-col :span="12">
                    <el-form-item label="需求联系人电话" prop="contactPhone">
                        <el-input class="gd-input" v-model="formData.contactPhone" placeholder="请输入" clearable />
                    </el-form-item>
                </el-col>
                <el-col :span="12">
                    <el-form-item label="需求邮箱" prop="contactEmail">
                        <el-input class="gd-input" v-model="formData.contactEmail" placeholder="请输入" clearable />
                    </el-form-item>
                </el-col>
                <el-col :span="12">
                    <el-form-item label="更新周期" prop="updateCycle">
                        <el-input class="gd-input" v-model="formData.updateCycle" placeholder="请输入" clearable />
                    </el-form-item>
                </el-col>
                <el-col :span="12">
                    <el-form-item label="申请依据" prop="applyBasis">
                        <el-input class="gd-input" v-model="formData.applyBasis" placeholder="请输入" clearable />
                    </el-form-item>
                </el-col>
                <el-col :span="12">
                    <el-form-item label="共享类型" prop="shareType">
                        <el-select
                            class="gd-select"
                            popper-class="gd-select-popper"
                            v-model="formData.shareType"
                            placeholder="请选择"
                            clearable
                        >
                            <el-option
                                v-for="item in dictObj.sharedType"
                                :key="item.dictKey"
                                :label="item.dictValue"
                                :value="item.dictKey"
                            />
                        </el-select>
                    </el-form-item>
                </el-col>
                <el-col :span="12">
                    <el-form-item label="应用场景类型" prop="applicationScene">
                        <el-select
                            class="gd-select"
                            popper-class="gd-select-popper"
                            v-model="formData.applicationScene"
                            placeholder="请选择"
                            clearable
                        >
                            <el-option
                                v-for="item in dictObj.appSceneType"
                                :key="item.dictKey"
                                :label="item.dictValue"
                                :value="item.dictKey"
                            />
                        </el-select>
                    </el-form-item>
                </el-col>
                <el-col :span="12">
                    <el-form-item label="责任部门" prop="responsibleDeptId">
                        <el-tree-select
                            class="gd-select"
                            popper-class="gd-tree-select-popper"
                            v-model="formData.responsibleDeptId"
                            node-key="id"
                            :data="deptTree"
                            :props="treeProps"
                            check-strictly
                            clearable
                        />
                    </el-form-item>
                </el-col>
                <el-col :span="12">
                    <el-form-item label="数据来源依据" prop="dataSource">
                        <el-input class="gd-input" v-model="formData.dataSource" placeholder="请输入" clearable />
                    </el-form-item>
                </el-col>
                <el-col :span="24">
                    <el-form-item label="需求信息项" prop="demandInfo">
                        <el-input
                            class="gd-input"
                            v-model="formData.demandInfo"
                            type="textarea"
                            :rows="4"
                            placeholder="请输入"
                            clearable
                        />
                    </el-form-item>
                </el-col>
            </el-row>
        </el-form>
        <template #footer>
            <el-button color="#F2F3F5" @click="visible = false">{{ dialogReadonly ? '关闭' : '取消' }}</el-button>
            <el-button
                class="save-btn"
                color="#4C34FF"
                v-if="!dialogReadonly"
                :loading="submitting"
                :disabled="submitting"
                @click="handleSubmit"
            >
                保存
            </el-button>
        </template>
    </el-dialog>
</template>
<script setup>
import { computed, ref } from 'vue'
import { ElMessage } from 'element-plus'
import { fieldRules } from '@ztzf/utils'
import { getDictLabel } from '@ztzf/utils'
import {
    gdSupplyDemandDetailApi,
    gdSupplyDemandSubmitApi,
} from '@/views/orderView/orderDataManage/supplyAdd/supplyAddApi'
// 初始化表单数据
const initForm = () => ({
    demandName: '',
    demandType: '',
    contactPerson: '',
    contactPhone: '',
    contactEmail: '',
    updateCycle: '',
    applyBasis: '',
    shareType: '',
    applicationScene: '',
    responsibleDeptId: '',
    dataSource: '',
    demandInfo: '',
})
const dictObj = inject('dictObj')
const deptTree = inject('deptTree')
const treeProps = inject('treeProps')
const emit = defineEmits(['success'])
const formRef = ref(null) // 表单实例
const formData = ref(initForm()) // 表单数据
const visible = defineModel() // 弹框显隐
const dialogMode = ref('add') // 弹框模式
const submitting = ref(false) // 提交中
const dialogReadonly = computed(() => dialogMode.value === 'view')
const titleEnum = ref({ edit: '编辑', view: '查看', add: '新增' })
const rules = {
    demandName: fieldRules(true),
    demandType: fieldRules(true),
    contactPerson: fieldRules(true),
    contactPhone: fieldRules(true),
    contactEmail: fieldRules(true),
    updateCycle: fieldRules(true),
    applyBasis: fieldRules(true),
    shareType: fieldRules(true),
    applicationScene: fieldRules(true),
    responsibleDeptId: fieldRules(true),
    dataSource: fieldRules(true),
    demandInfo: fieldRules(true),
}
// 提交新增/编辑
async function handleSubmit() {
    const isValid = await formRef.value?.validate().catch(() => false)
    if (!isValid) return
    submitting.value = true
    try {
        await gdSupplyDemandSubmitApi(formData.value)
        ElMessage.success(dialogMode.value === 'add' ? '新增成功' : '更新成功')
        visible.value = false
        emit('success')
    } finally {
        submitting.value = false
    }
}
// 加载详情
async function loadDetail() {
    if (!formData.value.id) return
    const res = await gdSupplyDemandDetailApi({ id: formData.value.id })
    formData.value = res?.data?.data ?? {}
}
// 打开弹框
async function open({ mode = 'add', row } = {}) {
    dialogMode.value = mode
    formData.value = dialogMode.value === 'add' ? initForm() : row
    if (dialogMode.value !== 'add') {
        await loadDetail()
    }
}
defineExpose({ open })
</script>
applications/task-work-order/src/views/orderView/orderDataManage/supplyAdd/index.vue
@@ -1,9 +1,217 @@
<template>
  <basic-container>
    供需填报管理
  </basic-container>
    <basic-container>
        <el-form ref="queryParamsRef" :model="searchParams" class="gd-search-form">
            <el-form-item label="需求名称" prop="demandName">
                <el-input
                    class="gd-input"
                    v-model="searchParams.demandName"
                    placeholder="请输入"
                    clearable
                    @clear="handleSearch"
                />
            </el-form-item>
            <el-form-item label="需求状态" prop="demandStatus">
                <el-select
                    class="gd-select"
                    popper-class="gd-select-popper"
                    v-model="searchParams.demandStatus"
                    placeholder="请选择"
                    clearable
                    @change="handleSearch"
                >
                    <el-option
                        v-for="item in dictObj.demandStatus"
                        :key="item.dictKey"
                        :label="item.dictValue"
                        :value="item.dictKey"
                    />
                </el-select>
            </el-form-item>
            <el-form-item class="gd-search-actions">
                <el-button :icon="RefreshRight" @click="resetForm"></el-button>
                <el-button class="search-btn" :icon="Search" @click="handleSearch"></el-button>
            </el-form-item>
        </el-form>
        <div class="gd-table-toolbar">
            <el-button :icon="Plus" color="#4C34FF" type="primary" @click="openForm('add')">新增</el-button>
            <el-button :icon="Delete" color="#4C34FF" :disabled="!selectedIds.length" @click="handleDelete()">删除</el-button>
        </div>
        <div class="gd-table-container" v-loading="loading">
            <div class="gd-table-content gd-table-content-bg">
                <el-table class="gd-table" :data="list" @selection-change="handleSelectionChange">
                    <el-table-column type="selection" width="46" />
                    <el-table-column prop="demandName" show-overflow-tooltip label="需求名称" />
                    <el-table-column prop="demandType" show-overflow-tooltip label="需求类型">
                        <template v-slot="{ row }">
                            {{ getDictLabel(row.demandType, dictObj.requirementType) }}
                        </template>
                    </el-table-column>
                    <el-table-column prop="contactPerson" show-overflow-tooltip label="需求联系人" />
                    <el-table-column prop="contactPhone" show-overflow-tooltip label="需求联系人电话" />
                    <el-table-column prop="contactEmail" show-overflow-tooltip label="需求邮箱" />
                    <el-table-column prop="demandStatus" show-overflow-tooltip label="需求状态">
                        <!-- <template v-slot="{ row }">
                            {{ getDictLabel(row.demandStatus, dictObj.demandStatus) }}
                        </template> -->
                    </el-table-column>
                    <el-table-column prop="updateCycle" show-overflow-tooltip label="更新周期" />
                    <el-table-column prop="applyBasis" show-overflow-tooltip label="申请依据" />
                    <el-table-column prop="shareType" show-overflow-tooltip label="共享类型">
                        <template v-slot="{ row }">
                            {{ getDictLabel(row.shareType, dictObj.sharedType) }}
                        </template>
                    </el-table-column>
                    <el-table-column prop="applicationScene" show-overflow-tooltip label="应用场景类型">
                        <template v-slot="{ row }">
                            {{ getDictLabel(row.applicationScene, dictObj.appSceneType) }}
                        </template>
                    </el-table-column>
                    <el-table-column prop="responsibleDeptName" show-overflow-tooltip label="责任部门" />
                    <el-table-column prop="dataSource" show-overflow-tooltip label="数据来源依据" />
                    <el-table-column prop="demandInfo" show-overflow-tooltip label="需求信息项" />
                    <el-table-column label="操作" class-name="operation-btns">
                        <template v-slot="{ row }">
                            <el-link @click="openForm('view', row)">查看</el-link>
                            <el-link @click="openForm('edit', row)">编辑</el-link>
                            <el-link @click="handleDelete(row)">删除</el-link>
                        </template>
                    </el-table-column>
                </el-table>
            </div>
            <div class="gd-pagination-parent">
                <el-pagination
                    popper-class="gd-select-popper"
                    v-model:current-page="searchParams.current"
                    v-model:page-size="searchParams.size"
                    layout="total, prev, pager, next, sizes"
                    :total="total"
                    @change="getList"
                />
            </div>
        </div>
        <FormDiaLog ref="dialogRef" @success="getList" v-if="dialogVisible" v-model="dialogVisible" />
    </basic-container>
</template>
<script setup>
import { Search, RefreshRight, Plus, Delete } from '@element-plus/icons-vue'
import { onMounted, ref } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { getDictionaryByCode } from '@/api/system/dictbiz'
import { getDeptTree } from '@/api/system/dept'
import { getDictLabel } from '@ztzf/utils'
import FormDiaLog from './FormDiaLog.vue'
import {
    gdSupplyDemandPageApi,
    gdSupplyDemandRemoveApi,
} from '@/views/orderView/orderDataManage/supplyAdd/supplyAddApi'
// 初始化查询参数
const initSearchParams = () => ({
    demandName: '', // 需求名称
    demandStatus: '', // 需求状态
    current: 1, // 当前页
    size: 10, // 每页大小
})
const searchParams = ref(initSearchParams()) // 查询参数
const total = ref(0) // 总条数
const loading = ref(true) // 列表加载中
const list = ref([]) // 列表数据
const selectedIds = ref([]) // 勾选的ID列表
const queryParamsRef = ref(null) // 查询表单实例
const dialogRef = ref(null) // 弹框实例
const dialogVisible = ref(false)
const dictObj = ref({
    requirementType: [], // 需求类型
    sharedType: [], // 共享类型
    appSceneType: [], // 应用场景类型
})
provide('dictObj', dictObj)
const deptTree = ref([]) // 部门树
const treeProps = {
    label: 'name',
    children: 'children',
}
provide('deptTree', deptTree)
provide('treeProps', treeProps)
// 获取列表
async function getList() {
    loading.value = true
    try {
        const res = await gdSupplyDemandPageApi(searchParams.value)
        list.value = res?.data?.data?.records ?? []
        total.value = res?.data?.data?.total ?? 0
    } finally {
        loading.value = false
    }
}
// 查询
function handleSearch() {
    searchParams.value.current = 1
    getList()
}
// 重置查询
function resetForm() {
    queryParamsRef.value?.resetFields()
    searchParams.value.current = 1
    getList()
}
// 新增/编辑/查看 弹框
function openForm(mode, row) {
    dialogVisible.value = true
    nextTick(() => {
        dialogRef.value?.open({ mode, row })
    })
}
// 删除
async function handleDelete(row) {
    const tips = row ? '该条' : '选中的项'
    await ElMessageBox.confirm(`确认删除${tips}吗?`, '提示', {
        type: 'warning',
        customClass: 'gd-confirm-custom',
        confirmButtonClass: 'gd-confirm-button',
        cancelButtonClass: 'gd-confirm-cancel-button',
    })
    const ids = row ? row.id : selectedIds.value.join(',')
    await gdSupplyDemandRemoveApi({ ids })
    ElMessage.success('删除成功')
    selectedIds.value = []
    getList()
}
// 勾选值设置
function handleSelectionChange(rows) {
    selectedIds.value = rows.map(item => item.id)
}
// 获取字典
function getDictList() {
    getDictionaryByCode('requirementType,sharedType,appSceneType').then(res => {
        dictObj.value = res.data.data
    })
}
// 获取部门树
function getDeptTreeFun() {
    getDeptTree().then(res => {
        deptTree.value = res.data.data
    })
}
onMounted(() => {
    getList()
    getDictList()
    getDeptTreeFun()
})
</script>
<style scoped lang="scss">
</style>
<style scoped lang="scss"></style>
applications/task-work-order/src/views/orderView/orderDataManage/supplyAdd/supplyAddApi.js
New file
@@ -0,0 +1,55 @@
import request from '@/axios'
// 获取供需需求列表
export const gdSupplyDemandPageApi = params => {
    return request({
        url: `/drone-gd/orderdata/gdSupplyDemand/page`,
        method: 'get',
        params: { descs: 'update_time', ...params },
    })
}
// 新增或编辑供需需求
export const gdSupplyDemandSubmitApi = data => {
    return request({
        url: `/drone-gd/orderdata/gdSupplyDemand/submit`,
        method: 'post',
        data,
    })
}
// 删除供需需求
export const gdSupplyDemandRemoveApi = params => {
    return request({
        url: `/drone-gd/orderdata/gdSupplyDemand/remove`,
        method: 'post',
        params,
    })
}
// 获取供需需求详情
export const gdSupplyDemandDetailApi = params => {
    return request({
        url: `/drone-gd/orderdata/gdSupplyDemand/detail`,
        method: 'get',
        params,
    })
}
// 审核通过
export const gdSupplyDemandAuditPassApi = data => {
    return request({
        url: `/drone-gd/orderdata/gdSupplyDemand/audit-pass`,
        method: 'post',
        data,
    })
}
// 拒绝申请
export const gdSupplyDemandAuditRejectApi = data => {
    return request({
        url: `/drone-gd/orderdata/gdSupplyDemand/audit-reject`,
        method: 'post',
        data,
    })
}
applications/task-work-order/src/views/orderView/orderManage/inspectionRequest/index.vue
@@ -1,6 +1,6 @@
<template>
  <basic-container>
    基础管理
    基础3管理
  </basic-container>
</template>
<script setup>
applications/task-work-order/src/views/wel/index.vue
@@ -1,6 +1,6 @@
<template>
    <div>
     测试
    </div>
</template>
@@ -10,4 +10,4 @@
<style lang="scss" scoped>
</style>
</style>
uniapps/work-app/src/App.vue
@@ -1,19 +1,32 @@
<script setup>
import { onHide, onLaunch, onShow } from "@dcloudio/uni-app";
import { useAppStore, useUserStore } from "@/store";
import { useAppStore, useUserStore, useLocationStore } from "@/store";
import { useGlobalWS } from "@/hooks/useGlobalWS.js";
const appStore = useAppStore();
const userStore = useUserStore();
const locationStore = useLocationStore();
useGlobalWS();
onShow(() => {
onShow(async () => {
  console.log("App Show");
  // 应用从后台返回前台时,重新初始化位置服务
  if (userStore.userInfo && locationStore) {
    try {
      await locationStore.initLocationService();
    } catch (error) {
      console.error('重新初始化位置服务失败:', error);
    }
  }
});
onHide(() => {
  console.log("App Hide");
  // 应用进入后台时,停止位置监听以节省资源
  if (locationStore) {
    locationStore.stopLocationWatch();
  }
});
onLaunch(() => {
uniapps/work-app/src/hooks/useGlobalWS.js
@@ -5,6 +5,7 @@
export function useGlobalWS() {
    const userStore = useUserStore();
  const appStore = useAppStore();
  const callStatus = ref(null)
  // 设置默认用户ID为3
  const defaultUserId = '3';
@@ -16,10 +17,9 @@
    console.log('🌐 全局WebSocket收到消息111111111111111111111:', payload)
    // 先尝试直接处理消息(适用于mobile-web-view的voiceCallDetail页面的消息格式)
    const t = (payload.type || '').toString()
    const bizCode = payload.biz_code || ''
    console.log('📋 消息分析:type=' + t + ', biz_code=' + bizCode)
    // const bizCode = payload.biz_code || ''
    callStatus.value = t
    // console.log('📋 消息分析:type=' + t )
    // 处理语音通话请求
    if (t === 'call') {
      console.log('📞 全局收到来电 call,来自', payload.from)
@@ -36,15 +36,8 @@
      try {
        // 优先使用uni-app的导航API(适用于同应用内跳转)
        if (typeof uni !== 'undefined' && uni.navigateTo) {
          console.log('使用uni-app导航API跳转到语音通话页面');
          uni.navigateTo({
            url: `/subPackages/voiceCallDetail/index?voiceparams=${encodedParams}`,
            success: () => {
              console.log('✅ 应用内导航成功!');
            },
            fail: (err) => {
              console.error('❌ 应用内导航失败:', err);
            }
          });
        } else {
          console.error('无法跳转到语音通话页面:当前环境不支持导航');
@@ -56,64 +49,63 @@
    }
    // 然后处理biz_code格式的消息
    switch (bizCode) {
      case 'JOB_ISREFRESH':
        appStore.setJobUpdateKeyAdd()
        break
      case 'DEVICE_ISREFRESH':
        appStore.setDeviceUpdateKeyAdd()
        break
      case 'DOWNLOAD_PROGRESS':
        break
      case 'LOGOUT_USER':
        userStore.setUserInfo(null)
        uni.reLaunch({
          url: '/pages/login/index'
        })
        break
      case 'VoiceCall':
        // enterRoom(payload, userId.value)
        break
      default:
        // 记录未处理的消息
        console.log('未处理的WebSocket消息:', payload)
        break;
    }
    // switch (bizCode) {
    //   case 'JOB_ISREFRESH':
    //     appStore.setJobUpdateKeyAdd()
    //     break
    //   case 'DEVICE_ISREFRESH':
    //     appStore.setDeviceUpdateKeyAdd()
    //     break
    //   case 'DOWNLOAD_PROGRESS':
    //     break
    //   case 'LOGOUT_USER':
    //     userStore.setUserInfo(null)
    //     uni.reLaunch({
    //       url: '/pages/login/index'
    //     })
    //     break
    //   case 'VoiceCall':
    //     // enterRoom(payload, userId.value)
    //     break
    //   default:
    //     // 记录未处理的消息
    //     console.log('未处理的WebSocket消息:', payload)
    //     break;
    // }
  }
  // 初始化WebSocket连接
  function initWS() {
    try {
      // 检查是否已经有活跃的WebSocket连接
      if (!websocketService.getConnected()) {
        // 使用默认用户ID初始化WebSocket连接
        websocketService.init(defaultUserId, WS_BASE);
        console.log('🌐 全局WebSocket初始化成功,默认用户ID:', defaultUserId);
      } else {
        console.log('🌐 WebSocket已存在活跃连接,无需重新初始化');
      }
      // 只设置一次全局消息处理回调
      // 检查是否已经设置了回调
      if (typeof websocketService.getOnMessageCallback() !== 'function') {
        // 设置全局消息处理回调
        websocketService.setOnMessageCallback(messageHandler);
        console.log('✅ 全局WebSocket消息处理器已设置');
      websocketService.setOnMessageCallback(messageHandler);
      // 获取当前用户ID,优先使用store中的用户信息
      const userId = userStore.userInfo?.id || defaultUserId;
      // 检查是否已经有活跃的WebSocket连接
      if (!websocketService.getConnected() || websocketService.userId !== userId) {
        // 使用用户ID初始化WebSocket连接
        websocketService.init(userId, WS_BASE);
        // console.log('🌐 全局WebSocket初始化成功,用户ID:', userId);
      } else {
        console.log('✅ WebSocket消息处理器已存在,无需重新设置');
        websocketService.connect();
      }
    } catch (error) {
      console.error('❌ 全局WebSocket初始化失败:', error);
    }
  }
  // 监听用户信息变化,初始化WebSocket
  watch(
    () => userStore.userInfo,
    (newUserInfo) => {
      console.log('👤 用户信息变化,尝试初始化WebSocket');
    () => callStatus.value,
    (newValue) => {
      if (newValue === 'accept') {
        console.log('📞 通话中,跳过WebSocket初始化');
        return;
      }
      initWS();
    },
    { immediate: true }
    { immediate: true, deep: true }
  )
}
uniapps/work-app/src/pages/login/index.vue
@@ -44,7 +44,8 @@
import { getAssetsImage } from "@/utils/index.js";
import md5 from "js-md5";
import { loginByUsername } from "@/api/user/index.js";
import { useUserStore } from "@/store/index.js";
import { useUserStore, useLocationStore } from "@/store/index.js";
import locationUtil from "@/utils/location.js";
import websocketService from "@/utils/websocket.js";
import { HOME_PATH, LOGIN_PATH, removeQueryString } from "@/router";
@@ -56,6 +57,7 @@
const agreed = ref(true);
const userStore = useUserStore();
const locationStore = useLocationStore();
const loginForm = ref({
  username: "",
  password: "",
@@ -135,8 +137,22 @@
      userInfo.key,
      userInfo.code
    );
    userStore.setUserInfo(res.data.data);
    console.log('登录成功,WebSocket连接将由全局钩子统一管理');
    userStore.setUserInfo(res.data.data);
    // 请求位置权限并初始化位置服务
    try {
      // 请求位置权限
      const hasPermission = await locationUtil.requestLocationPermission();
      if (hasPermission) {
        console.log('获取位置权限成功');
        // 初始化位置服务
        await locationStore.initLocationService();
      } else {
        console.log('获取位置权限失败');
      }
    } catch (error) {
      console.error('处理位置服务失败:', error);
    }
    uni.reLaunch({
      url: "/pages/work/index",
    });
uniapps/work-app/src/store/index.js
@@ -6,6 +6,7 @@
import useAppStore from "./modules/app"
import useUserStore from "./modules/user"
import useMapStore from "./modules/map"
import useLocationStore from "./modules/location"
// 安装pinia状态管理插件
function setupStore (app) {
@@ -23,5 +24,5 @@
}
// 导出模块
export { useAppStore, useUserStore, useMapStore }
export { useAppStore, useUserStore, useMapStore, useLocationStore }
export default setupStore
uniapps/work-app/src/store/modules/location.js
New file
@@ -0,0 +1,137 @@
import { defineStore } from 'pinia'
import locationUtil from '@/utils/location.js'
const useLocationStore = defineStore('location', {
  state: () => ({
    // 当前位置信息
    currentLocation: null,
    // 位置权限状态
    hasPermission: false,
    // 系统定位服务是否开启
    systemLocationEnabled: true,
    // 位置更新间隔(毫秒)
    updateInterval: 5000,
    // 位置监听器ID
    watcherId: null
  }),
  getters: {
    /**
     * 获取当前位置
     * @returns {Object|null} 当前位置信息
     */
    getCurrentLocation: (state) => state.currentLocation,
    /**
     * 获取位置权限状态
     * @returns {boolean} 是否有位置权限
     */
    getPermissionStatus: (state) => state.hasPermission,
    /**
     * 获取系统定位服务状态
     * @returns {boolean} 系统定位服务是否开启
     */
    getSystemLocationStatus: (state) => state.systemLocationEnabled
  },
  actions: {
    /**
     * 初始化位置服务
     */
    async initLocationService() {
      try {
        // 检查系统定位服务是否开启
        this.systemLocationEnabled = await locationUtil.checkSystemLocationEnabled();
        if (!this.systemLocationEnabled) {
          console.warn('系统定位服务未开启');
        }
        // 请求位置权限
        this.hasPermission = await locationUtil.requestLocationPermission();
        if (this.hasPermission) {
          // 获取当前位置
          await this.updateCurrentLocation();
          // 开始位置监听
          this.startLocationWatch();
        }
      } catch (error) {
        console.error('初始化位置服务失败:', error);
      }
    },
    /**
     * 更新当前位置
     */
    async updateCurrentLocation() {
      try {
        const location = await locationUtil.getCurrentLocation();
        this.currentLocation = location;
        console.log('位置更新:', location);
        return location;
      } catch (error) {
        console.error('更新位置失败:', error);
        return null;
      }
    },
    /**
     * 开始位置监听
     */
    startLocationWatch() {
      if (this.watcherId) {
        this.stopLocationWatch();
      }
      this.watcherId = locationUtil.startLocationWatcher((location) => {
        this.currentLocation = location;
        // 可以在这里添加位置信息上报逻辑
        this.reportLocation(location);
      }, {
        interval: this.updateInterval
      });
    },
    /**
     * 停止位置监听
     */
    stopLocationWatch() {
      if (this.watcherId) {
        locationUtil.stopLocationWatcher();
        this.watcherId = null;
      }
    },
    /**
     * 重新请求位置权限
     */
    async reRequestPermission() {
      this.hasPermission = await locationUtil.requestLocationPermission();
      if (this.hasPermission) {
        await this.updateCurrentLocation();
        this.startLocationWatch();
      }
      return this.hasPermission;
    },
    /**
     * 位置信息上报(可根据实际需求实现)
     * @param {Object} location 位置信息
     */
    reportLocation(location) {
      // 这里可以添加位置信息上报到服务器的逻辑
      // 例如:api.uploadLocation(location)
      // console.log('位置上报:', location);
    },
    /**
     * 清理位置服务
     */
    cleanupLocationService() {
      this.stopLocationWatch();
      this.currentLocation = null;
    }
  }
})
export default useLocationStore
uniapps/work-app/src/subPackages/userDetail/infos/index.vue
@@ -25,6 +25,21 @@
                    <div>{{userInfo.deptName}}</div>
                </div>
                <div class="orderRow">
                    <div class="rowTitle">性别</div>
                    <div>
                        <u-radio-group v-model="userInfo.sex">
                            <up-radio
                                v-for="(item, index) in sexList"
                                :key="index"
                                :label="item.label"
                                :name="item.name"
                                @change="radioChange"
                                >
                            </up-radio>
                        </u-radio-group>
                    </div>
                </div>
                <div class="orderRow">
                    <div class="rowTitle">手机号</div>
                    <div>{{userInfo.phone}}</div>
                    <!-- <u-input input-align="right" v-model="userInfo.phone" type="number" placeholder="请输入手机号"
@@ -33,9 +48,9 @@
                </div>
                <div class="orderRow">
                    <div class="rowTitle">邮箱</div>
                    <div>{{userInfo.email}}</div>
                    <!-- <u-input input-align="right" v-model="userInfo.email" type="email" placeholder="请输入邮箱"
                        class="input-item" /> -->
                    <!-- <div>{{userInfo.email}}</div> -->
                    <u-input input-align="right" v-model="userInfo.email" type="email" placeholder="请输入邮箱"
                        class="input-item" />
                </div>
            </div>
        </view>
@@ -61,7 +76,22 @@
        phone: '',
        email: '',
        deptName: '',
        sex: 2,
    });
    const sexList = [
        {
            label: '男',
            name: '0'
        },
        {
            label: '女',
            name: '1'
        },
        {
            label: '未知',
            name: '2'
        }
    ]
    // 校验手机号
    const validatePhone = () => {
        const phone = userInfo.value.phone
@@ -103,6 +133,7 @@
                phone: user.sysDept.phone,
                email: user.sysDept.email,
                deptName: user.sysDept.deptName,
                sex: user.sex || 2,
            };
        });
uniapps/work-app/src/utils/location.js
New file
@@ -0,0 +1,260 @@
/**
 * 位置管理工具类
 * 用于处理位置权限请求和实时位置获取
 */
import permission from './voiceCallByTX/TrtcCloud/permission.js'
// 平台判断
const isIos = () => {
  // #ifdef APP-PLUS
  return plus.os.name === "iOS";
  // #endif
  return false;
};
// 位置更新回调函数
let locationUpdateCallback = null;
// 位置监听器ID
let locationWatcherId = null;
/**
 * 请求位置权限
 * @returns {Promise<boolean>} 是否获取到权限
 */
export const requestLocationPermission = async () => {
  return new Promise(async (resolve) => {
    // #ifdef APP-PLUS
    if (isIos()) {
      // iOS平台
      const hasPermission = permission.judgeIosPermission('location');
      if (hasPermission) {
        resolve(true);
      } else {
        // iOS需要提示用户手动开启权限
        uni.showModal({
          title: '提示',
          content: '应用需要获取位置信息权限,请在设置中开启',
          confirmText: '去设置',
          cancelText: '取消',
          success: (res) => {
            if (res.confirm) {
              permission.gotoAppPermissionSetting();
              resolve(false);
            } else {
              resolve(false);
            }
          }
        });
      }
    } else {
      // Android平台
      const result = await permission.requestAndroidPermission('android.permission.ACCESS_FINE_LOCATION');
      if (result === 1) {
        resolve(true);
      } else if (result === 0) {
        // 拒绝本次申请
        resolve(false);
      } else if (result === -1) {
        // 永久拒绝
        uni.showModal({
          title: '提示',
          content: '应用需要获取位置信息权限,请在设置中开启',
          confirmText: '去设置',
          cancelText: '取消',
          success: (res) => {
            if (res.confirm) {
              permission.gotoAppPermissionSetting();
              resolve(false);
            } else {
              resolve(false);
            }
          }
        });
      }
    }
    // #endif
    // #ifdef H5
    // H5平台使用浏览器原生API请求权限
    if (navigator.geolocation) {
      navigator.permissions.query({ name: 'geolocation' }).then((result) => {
        if (result.state === 'granted') {
          resolve(true);
        } else if (result.state === 'prompt') {
          // 还未请求过权限,会在getLocation时提示
          resolve(true);
        } else {
          resolve(false);
        }
      }).catch(() => {
        resolve(false);
      });
    } else {
      resolve(false);
    }
    // #endif
    // #ifdef MP
    // 小程序平台
    uni.getSetting({
      success: (res) => {
        if (res.authSetting['scope.userLocation']) {
          resolve(true);
        } else {
          uni.authorize({
            scope: 'scope.userLocation',
            success: () => {
              resolve(true);
            },
            fail: () => {
              uni.showModal({
                title: '提示',
                content: '应用需要获取位置信息权限,请在设置中开启',
                confirmText: '去设置',
                cancelText: '取消',
                success: (res) => {
                  if (res.confirm) {
                    uni.openSetting();
                    resolve(false);
                  } else {
                    resolve(false);
                  }
                }
              });
            }
          });
        }
      }
    });
    // #endif
  });
};
/**
 * 获取当前位置信息
 * @returns {Promise<Object>} 位置信息对象
 */
export const getCurrentLocation = () => {
  return new Promise((resolve, reject) => {
    uni.getLocation({
      type: 'gcj02',
      altitude: true,
      success: (res) => {
        resolve({
          latitude: res.latitude,
          longitude: res.longitude,
          altitude: res.altitude,
          accuracy: res.accuracy,
          speed: res.speed,
          verticalAccuracy: res.verticalAccuracy,
          horizontalAccuracy: res.horizontalAccuracy
        });
      },
      fail: (err) => {
        console.error('获取位置失败:', err);
        reject(err);
      }
    });
  });
};
/**
 * 开始实时位置监听
 * @param {Function} callback 位置更新回调函数
 * @param {Object} options 监听选项
 * @returns {number} 监听器ID
 */
export const startLocationWatcher = (callback, options = {}) => {
  if (locationWatcherId) {
    stopLocationWatcher();
  }
  locationUpdateCallback = callback;
  locationWatcherId = uni.startLocationUpdate({
    success: () => {
      console.log('开始位置监听成功');
    },
    fail: (err) => {
      console.error('开始位置监听失败:', err);
      // 如果是因为权限问题,重新请求权限
      if (err.errCode === 12002) {
        requestLocationPermission();
      }
    }
  });
  // 监听位置更新
  uni.onLocationChange((res) => {
    const locationInfo = {
      latitude: res.latitude,
      longitude: res.longitude,
      altitude: res.altitude,
      accuracy: res.accuracy,
      speed: res.speed,
      verticalAccuracy: res.verticalAccuracy,
      horizontalAccuracy: res.horizontalAccuracy,
      timestamp: res.timestamp
    };
    if (locationUpdateCallback) {
      locationUpdateCallback(locationInfo);
    }
  });
  return locationWatcherId;
};
/**
 * 停止位置监听
 */
export const stopLocationWatcher = () => {
  if (locationWatcherId) {
    uni.stopLocationUpdate({
      success: () => {
        console.log('停止位置监听成功');
      },
      fail: (err) => {
        console.error('停止位置监听失败:', err);
      }
    });
    locationWatcherId = null;
  }
  // 移除位置更新监听
  uni.offLocationChange();
  locationUpdateCallback = null;
};
/**
 * 检查系统定位服务是否开启
 * @returns {Promise<boolean>} 是否开启
 */
export const checkSystemLocationEnabled = async () => {
  return new Promise((resolve) => {
    // #ifdef APP-PLUS
    const enabled = permission.checkSystemEnableLocation();
    resolve(enabled);
    // #endif
    // #ifdef H5
    resolve(navigator.geolocation ? true : false);
    // #endif
    // #ifdef MP
    uni.getSystemInfo({
      success: (res) => {
        // 小程序无法直接检查系统定位服务是否开启
        resolve(true);
      },
      fail: () => {
        resolve(false);
      }
    });
    // #endif
  });
};
export default {
  requestLocationPermission,
  getCurrentLocation,
  startLocationWatcher,
  stopLocationWatcher,
  checkSystemLocationEnabled
};
uniapps/work-app/src/utils/websocket.js
@@ -1,4 +1,4 @@
// WebSocket服务管理
import { useGlobalWS } from "@/hooks/useGlobalWS.js";// WebSocket服务管理
class WebSocketService {
  constructor() {
    this.socketTask = null
@@ -34,10 +34,10 @@
      this.socketTask = uni.connectSocket({
        url: this.url,
        success: () => {
          console.log('WebSocket连接已请求')
          // console.log('WebSocket连接已请求')
        },
        fail: (error) => {
          console.error('WebSocket连接请求失败:', error)
          // console.error('WebSocket连接请求失败:', error)
          if (this.onErrorCallback) {
            this.onErrorCallback(error)
          }
@@ -47,7 +47,7 @@
      // 监听连接打开
      this.socketTask.onOpen(() => {
        this.connected = true
        console.log('WebSocket已连接,userId:', this.userId)
        // console.log('WebSocket已连接,userId:', this.userId)
        this.startPing()
        if (this.onOpenCallback) {
          this.onOpenCallback()
@@ -57,7 +57,7 @@
      // 监听连接关闭
      this.socketTask.onClose(() => {
        this.connected = false
        console.log('WebSocket已关闭')
        // console.log('WebSocket已关闭')
        this.stopPing()
        if (this.onCloseCallback) {
          this.onCloseCallback()
@@ -66,7 +66,7 @@
      // 监听连接错误
      this.socketTask.onError((error) => {
        console.error('WebSocket错误:', error)
        // console.error('WebSocket错误:', error)
        this.connected = false
        this.stopPing()
        if (this.onErrorCallback) {
@@ -78,16 +78,16 @@
      this.socketTask.onMessage((res) => {
        try {
          const message = JSON.parse(res.data)
          console.log('收到WebSocket消息:', message)
          // console.log('收到WebSocket消息:', message)
          if (this.onMessageCallback) {
            this.onMessageCallback(message)
          }
        } catch (error) {
          console.error('解析WebSocket消息失败:', error)
          // console.error('解析WebSocket消息失败:', error)
        }
      })
    } catch (error) {
      console.error('WebSocket连接失败:', error)
      // console.error('WebSocket连接失败:', error)
      if (this.onErrorCallback) {
        this.onErrorCallback(error)
      }
@@ -112,10 +112,11 @@
    this.socketTask.send({
      data: JSON.stringify(message),
      success: () => {
        console.log('WebSocket消息发送成功:', message)
        // console.log('WebSocket消息发送成功:', message)
        useGlobalWS();
      },
      fail: (error) => {
        console.error('WebSocket消息发送失败:', error)
        // console.error('WebSocket消息发送失败:', error)
      }
    })
  }
@@ -146,10 +147,10 @@
        code: 1000,
        reason: '正常关闭',
        success: () => {
          console.log('WebSocket已关闭')
          // console.log('WebSocket已关闭')
        },
        fail: (error) => {
          console.error('WebSocket关闭失败:', error)
          // console.error('WebSocket关闭失败:', error)
        }
      })
      this.socketTask = null
@@ -160,10 +161,9 @@
  // 设置消息回调
  setOnMessageCallback(callback) {
    console.log('🔔 WebSocket消息回调已设置')
    this.onMessageCallback = callback
  }
  // 获取当前消息回调(用于检查是否已设置)
  getOnMessageCallback() {
    return this.onMessageCallback
uniapps/work-wx/src/subPackages/userDetail/infos/index.vue
@@ -8,16 +8,27 @@
        <view class="detailBox">
            <div class="detailCon">
                <div class="orderRow">
                    <div class="rowTitle">姓名</div>
                    <div class="rowTitle">昵称</div>
                    <div>{{userInfo.nickName}}</div>
                </div>
                <!-- <div class="orderRow">
                    <div class="rowTitle">所属单位</div>
                    <div>{{userInfo.deptName}}</div>
                </div> -->
                <div class="orderRow">
                    <div class="rowTitle">认证状态</div>
                    <div>{{userInfo.status === 1 ? '已认证' : '未认证'}}</div>
                </div>
                <div class="orderRow">
                    <div class="rowTitle">性别</div>
                    <div>
                        <u-radio-group v-model="userInfo.sex">
                            <up-radio
                                v-for="(item, index) in sexList"
                                :key="index"
                                :label="item.label"
                                :name="item.name"
                                @change="radioChange"
                                >
                            </up-radio>
                        </u-radio-group>
                    </div>
                </div>
                <div class="orderRow">
                    <div class="rowTitle">手机号</div>
@@ -41,6 +52,7 @@
</template>
<script setup>
    import { uploadFileApi } from '@/api/index'
    import showDefaultHeader from "@/static/images/user/default-header.png";
    import {
        getEnvObj,
@@ -62,7 +74,22 @@
        phone: '',
        email: '',
        deptName: '',
        sex: 2,
    });
    const sexList = [
        {
            label: '男',
            name: '0'
        },
        {
            label: '女',
            name: '1'
        },
        {
            label: '未知',
            name: '2'
        }
    ]
    // 校验手机号
    const validatePhone = () => {
        const phone = userInfo.value.phone
@@ -97,15 +124,14 @@
    const getUserInfoData = () => {
        getUserInfo().then(res => {
            const user = res.data.data;
            console.log('user',user)
            userInfo.value = {
                id: user.userId,
                avatar: user.avatar,
                nickName: user.nickName,
                // realName: user.realName,
                phone: user.phonenumber,
                email: user.sysDept.email,
                deptName: user.sysDept.deptName,
                sex: user.sex || 2,
            };
        });
@@ -113,45 +139,47 @@
    const reset = () => {
        getUserInfoData();
    };
    const {
        VITE_API_BASE_URL
    } = getEnvObj()
    function uploadUtil(options) {
        const {
            formData,
            filePath,
            url
        } = options;
        return new Promise((resolve, reject) => {
            let accessToken = useUserStore()?.$state?.userInfo?.access_token;
            uni.uploadFile({
                url: `${VITE_API_BASE_URL}${url}`,
                name: 'file',
                header: {
                    'Blade-Auth': 'bearer ' + accessToken
                },
                filePath: filePath,
                formData,
                success: (res) => {
                    const resData = JSON.parse(res.data)
                    if (resData.code === 200 || resData.code === 0) {
                        resolve(res)
                    } else {
                        showToast(resData.message)
                        reject(res)
                    }
                },
                fail: (err) => {
                    reject(err)
                }
            });
        })
    }
    // 提交文件
function afterReadFile(event) {
    console.log('上传的文件:', event);
    // 准备上传参数
    const uploadParams = {
        name: event.name, // 服务器端接收文件的字段名
        file: event.file, // 传入文件对象
        filePath: event.filePath, // 文件路径
    };
    // 调用上传文件接口
    uploadFileApi(uploadParams).then(res => {
        uni.hideLoading();
        console.log('上传成功结果:', res);
        // 根据接口返回的数据结构处理结果
        if (res.data && res.data.code === 200) {
            const uploadResult = res.data.data;
            userInfo.value.avatar = uploadResult.fileUrl || '文件上传成功';
            uni.showToast({
                title: '上传成功',
                icon: 'success'
            });
        } else {
            uni.showToast({
                title: res.msg || '上传失败',
                icon: 'none'
            });
        }
    }).catch(err => {
        uni.hideLoading();
        console.error('上传失败:', err);
        uni.showToast({
            title: '上传失败',
            icon: 'none'
        });
    });
}
    const uploadAvatar = () => {
        uni.chooseImage({
            count: 1,
@@ -176,35 +204,11 @@
                    title: '上传中...'
                });
                // 上传文件
                uploadUtil({
                    filePath: filePath,
                    formData: {
                        fileName: fileName,
                        sn: 'avatar_upload'
                    },
                    url: '/blade-resource/oss/endpoint/put-file'
                }).then(res => {
                    const resData = JSON.parse(res.data);
                    if (resData.code === 200 || resData.code === 0) {
                        // 更新头像显示
                        userInfo.value.avatar = resData.data.link || resData.data.url;
                        uni.hideLoading();
                        uni.showToast({
                            title: '头像上传成功',
                            icon: 'success'
                        });
                    } else {
                        throw new Error(resData.message || '上传失败');
                    }
                }).catch(err => {
                    uni.hideLoading();
                    uni.showToast({
                        title: err.message || '上传失败',
                        icon: 'none'
                    });
                });
                afterReadFile({
                    file: tempFile,
                    filePath: filePath,
                    name: fileName,
                })
            }
        });
    }
@@ -329,7 +333,7 @@
    }
    .note {
        position: absolute;
        bottom: 400rpx;
        bottom: 300rpx;
        font-family: Source Han Sans CN, Source Han Sans CN;
        font-weight: 400;
        font-size: 10px;