吉安感知网项目-前端
shuishen
8 days ago 6e88705bd5b443a259b24c17c8a299765d059d96
Merge branch 'master' of http://139.196.74.78:10010/r/jagzwxm/ja_web
79 files modified
6 files added
1328 ■■■■ changed files
AGENTS.md 38 ●●●●● patch | view | raw | blame | history
applications/drone-command/index.html 4 ●●●● patch | view | raw | blame | history
applications/drone-command/src/assets/images/common/point-active.png patch | view | raw | blame | history
applications/drone-command/src/assets/images/common/point.png patch | view | raw | blame | history
applications/drone-command/src/assets/images/dataCenter/positionicon.png patch | view | raw | blame | history
applications/drone-command/src/components/map-container/common-cesium-map.vue 306 ●●●●● patch | view | raw | blame | history
applications/drone-command/src/styles/common/cockpit.scss 14 ●●●●● patch | view | raw | blame | history
applications/drone-command/src/utils/cesium/shapeTools/draw/DrawBufferCircleTool.js 3 ●●●● patch | view | raw | blame | history
applications/drone-command/src/utils/cesium/shapeTools/draw/DrawEllipseTool.js 3 ●●●● patch | view | raw | blame | history
applications/drone-command/src/utils/cesium/shapeTools/draw/DrawPolygonTool.js 3 ●●●● patch | view | raw | blame | history
applications/drone-command/src/utils/cesium/shapeTools/draw/DrawRectangleTool.js 3 ●●●● patch | view | raw | blame | history
applications/drone-command/src/utils/cesium/shapeTools/edit/EditBufferCircleTool.js 3 ●●●● patch | view | raw | blame | history
applications/drone-command/src/utils/cesium/shapeTools/edit/EditEllipseTool.js 3 ●●●● patch | view | raw | blame | history
applications/drone-command/src/utils/cesium/shapeTools/edit/EditPolygonTool.js 3 ●●●● patch | view | raw | blame | history
applications/drone-command/src/utils/cesium/shapeTools/edit/EditRectangleTool.js 3 ●●●● patch | view | raw | blame | history
applications/drone-command/src/views/aICrudTemplate/index.vue 2 ●●● patch | view | raw | blame | history
applications/drone-command/src/views/areaManage/areaStatistics/index.vue 2 ●●● patch | view | raw | blame | history
applications/drone-command/src/views/areaManage/defenseZone/index.vue 2 ●●● patch | view | raw | blame | history
applications/drone-command/src/views/areaManage/partition/FormDiaLog.vue 2 ●●● patch | view | raw | blame | history
applications/drone-command/src/views/areaManage/partition/index.vue 2 ●●● patch | view | raw | blame | history
applications/drone-command/src/views/areaManage/precinctInfo/FormDiaLog.vue 5 ●●●●● patch | view | raw | blame | history
applications/drone-command/src/views/areaManage/precinctInfo/index.vue 2 ●●● patch | view | raw | blame | history
applications/drone-command/src/views/areaManage/sceneConfig/index.vue 2 ●●● patch | view | raw | blame | history
applications/drone-command/src/views/areaManage/sceneManage/FormDiaLog.vue 4 ●●●● patch | view | raw | blame | history
applications/drone-command/src/views/areaManage/sceneManage/index.vue 2 ●●● patch | view | raw | blame | history
applications/drone-command/src/views/audioCall.vue 3 ●●●● patch | view | raw | blame | history
applications/drone-command/src/views/basicManage/deviceScrap/index.vue 2 ●●● patch | view | raw | blame | history
applications/drone-command/src/views/basicManage/deviceStock/FormDiaLog.vue 39 ●●●●● patch | view | raw | blame | history
applications/drone-command/src/views/basicManage/deviceStock/index.vue 22 ●●●● patch | view | raw | blame | history
applications/drone-command/src/views/basicManage/maintainRecord/index.vue 4 ●●●● patch | view | raw | blame | history
applications/drone-command/src/views/detectionCountermeasure/detectionRange/index.vue 2 ●●● patch | view | raw | blame | history
applications/drone-command/src/views/detectionCountermeasure/deviceAppConfig/index.vue 3 ●●●● patch | view | raw | blame | history
applications/drone-command/src/views/permissionManage/permissionDept/index.vue 2 ●●● patch | view | raw | blame | history
applications/drone-command/src/views/permissionManage/permissionRole/index.vue 2 ●●● patch | view | raw | blame | history
applications/drone-command/src/views/permissionManage/permissionUser/index.vue 2 ●●● patch | view | raw | blame | history
applications/drone-command/src/views/recordManage/historyTracks/TrajectoryDiaLog.vue 131 ●●●●● patch | view | raw | blame | history
applications/drone-command/src/views/recordManage/historyTracks/index.vue 2 ●●● patch | view | raw | blame | history
applications/drone-command/src/views/system/helpCenter.vue 2 ●●● patch | view | raw | blame | history
applications/drone-command/src/views/system/user/index.vue 2 ●●● patch | view | raw | blame | history
applications/mobile-web-view/env/.env 7 ●●●● patch | view | raw | blame | history
applications/mobile-web-view/src/api/map/address.js 5 ●●●●● patch | view | raw | blame | history
applications/mobile-web-view/src/appPages/voiceCallDetail/index.vue 3 ●●●● patch | view | raw | blame | history
applications/mobile-web-view/src/axios.js 26 ●●●●● patch | view | raw | blame | history
applications/mobile-web-view/src/axiosKY.js 190 ●●●●● patch | view | raw | blame | history
applications/mobile-web-view/vite.config.mjs 15 ●●●● patch | view | raw | blame | history
applications/task-work-order/env/.env 5 ●●●●● patch | view | raw | blame | history
applications/task-work-order/package.json 1 ●●●● patch | view | raw | blame | history
applications/task-work-order/src/api/documentPreview/index.js 9 ●●●●● patch | view | raw | blame | history
applications/task-work-order/src/permission.js 18 ●●●●● patch | view | raw | blame | history
applications/task-work-order/src/router/views/index.js 12 ●●●●● patch | view | raw | blame | history
applications/task-work-order/src/views/basicManage/maintainRecord/index.vue 2 ●●● patch | view | raw | blame | history
applications/task-work-order/src/views/documentPreview/index.vue 103 ●●●●● patch | view | raw | blame | history
applications/task-work-order/src/views/orderView/appConfiguration/inventory/index.vue 2 ●●● patch | view | raw | blame | history
applications/task-work-order/src/views/orderView/appConfiguration/materials/index.vue 2 ●●● patch | view | raw | blame | history
applications/task-work-order/src/views/orderView/appConfiguration/process/index.vue 2 ●●● patch | view | raw | blame | history
applications/task-work-order/src/views/orderView/deviceInvocation/deviceCallRecord/FormDiaLog.vue 6 ●●●● patch | view | raw | blame | history
applications/task-work-order/src/views/orderView/deviceInvocation/deviceCallRecord/index.vue 2 ●●● patch | view | raw | blame | history
applications/task-work-order/src/views/orderView/deviceInvocation/equipmentResource/index.vue 7 ●●●● patch | view | raw | blame | history
applications/task-work-order/src/views/orderView/flyingHand/index.vue 2 ●●● patch | view | raw | blame | history
applications/task-work-order/src/views/orderView/orderDataManage/appInnovation/index.vue 2 ●●● patch | view | raw | blame | history
applications/task-work-order/src/views/orderView/orderDataManage/dataObjection/FormDiaLog.vue 16 ●●●● patch | view | raw | blame | history
applications/task-work-order/src/views/orderView/orderDataManage/dataObjection/index.vue 2 ●●● patch | view | raw | blame | history
applications/task-work-order/src/views/orderView/orderDataManage/evaluate/index.vue 2 ●●● patch | view | raw | blame | history
applications/task-work-order/src/views/orderView/orderDataManage/supplyAdd/index.vue 2 ●●● patch | view | raw | blame | history
applications/task-work-order/src/views/orderView/orderManage/clueEvents/ViewDiaLog.vue 2 ●●● patch | view | raw | blame | history
applications/task-work-order/src/views/orderView/orderManage/clueEvents/index.vue 2 ●●● patch | view | raw | blame | history
applications/task-work-order/src/views/orderView/orderManage/inspectionReport/index.vue 96 ●●●● patch | view | raw | blame | history
applications/task-work-order/src/views/orderView/orderManage/inspectionRequest/FormDiaLog.vue 2 ●●● 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/orderView/orderManage/operatingIncome/index.vue 2 ●●● patch | view | raw | blame | history
applications/task-work-order/src/views/orderView/orderManage/orderManage/FormDiaLog.vue 31 ●●●●● patch | view | raw | blame | history
applications/task-work-order/src/views/orderView/orderManage/orderManage/index.vue 79 ●●●●● patch | view | raw | blame | history
applications/task-work-order/src/views/orderView/orderManage/orderManage/orderManageApi.js 2 ●●● patch | view | raw | blame | history
applications/task-work-order/src/views/orderView/orderManage/orderManage/outcomeData.vue 2 ●●● patch | view | raw | blame | history
applications/task-work-order/src/views/orderView/organizational/agenciesManagement/index.vue 2 ●●● patch | view | raw | blame | history
applications/task-work-order/src/views/orderView/organizational/roleMange/index.vue 2 ●●● patch | view | raw | blame | history
applications/task-work-order/src/views/orderView/organizational/userMange/index.vue 2 ●●● patch | view | raw | blame | history
applications/task-work-order/src/views/orderView/organizational/zoningManagement/index.vue 2 ●●● patch | view | raw | blame | history
applications/task-work-order/src/views/system/helpCenter.vue 2 ●●● patch | view | raw | blame | history
applications/task-work-order/src/views/system/user/index.vue 2 ●●● patch | view | raw | blame | history
package.json 3 ●●●●● patch | view | raw | blame | history
pnpm-lock.yaml 16 ●●●●● patch | view | raw | blame | history
pnpm-workspace.yaml 1 ●●●● patch | view | raw | blame | history
uniapps/work-app/src/hooks/useGlobalWS.js 3 ●●●● patch | view | raw | blame | history
uniapps/work-wx/uploadCloudFunction.sh 1 ●●●● patch | view | raw | blame | history
AGENTS.md
@@ -12,45 +12,13 @@
  - `uniapps/work-wx/`:使用 UniApp 开发的微信小程序。
- 资源文件通常放在各应用内,具体入口以各应用的 `README.md` 为准。
## 构建、测试与开发命令
在仓库根目录使用 `pnpm`:
- `pnpm install`:安装工作区依赖。
- `pnpm dev`:并行启动所有工作区的开发服务。
- `pnpm dev:drone-command`:启动单个应用。
- `pnpm dev:mobile-web-view`:启动单个应用。
- `pnpm dev:task-work-order`:启动单个应用。
- `pnpm dev:work-app`:启动 UniApp H5 开发服务。
- `pnpm dev:work-wx`:启动 UniApp H5 开发服务。
- `pnpm build:test:<app>` / `pnpm build:prod:<app>`:构建指定应用,例如 `pnpm build:prod:mobile-web-view`。
## 编码风格与命名规范
- 使用 Prettier 统一格式化(`.prettierrc.json`)。
- 使用 Tab 缩进(`tabWidth: 2`, `useTabs: true`),`semi: false`,`singleQuote: true`。
- SCSS 使用 `tabWidth: 4` 且双引号。
- 文件与目录保持 kebabCase,遵循现有应用与包的命名风格。
## 测试指南
- 无需任何测试
## 提交与合并请求规范
- 提交历史使用简短的约定式主题(如 `feat: ...`)。
- 建议使用 `feat:`、`fix:`、`chore:`、`refactor:` 等前缀并保持简洁描述。
- PR 需要包含:目的、影响的应用/包、UI 改动截图。
- 可关联相关 issue 或工单。
## 环境与配置
- Node.js >= 20,pnpm >= 9。
- 优先使用根目录工作区脚本,除非必要不要在子目录直接运行脚本。
# AI Rules / AI行为规范
- 对低风险修改,AI可自主执行,无需批准。
  AI may proceed autonomously for low-risk changes.
- 仅当修改可能影响架构、核心模块、公共接口、数据结构或安全性时,才需要请求批准。
  Ask for approval only for high-impact changes.
- 若问题不影响系统正确性,应自行做出合理决策,而不是提问。
  DO NOT ask questions that do not affect correctness.
- 优先最小化修改范围。
  Prefer minimal changes.
- 未被明确要求时,禁止大规模重构。
  Avoid large refactors unless requested.
- 优先最小化修改范围
- 未被明确要求时,禁止大规模重构
- 每个方法前面加一行简短中文注释
- 修改完不需要走打包编译校验
applications/drone-command/index.html
@@ -23,11 +23,11 @@
    <!-- 高德地图api更新必须配合安全密钥使用 -->
    <script>
        window._AMapSecurityConfig = {
            securityJsCode: '1cd61831ddce188f3129b761f57024fc',
            securityJsCode: 'xxx',
        }
    </script>
    <script type="text/javascript"
                    src='https://webapi.amap.com/maps?v=2.0&key=eb0654440644f710110547d2132b2fbe&plugin=AMap.PlaceSearch,AMap.Geocoder'></script>
                    src='https://webapi.amap.com/maps?v=2.0&key=xxx&plugin=AMap.PlaceSearch,AMap.Geocoder'></script>
    <script src="https://webapi.amap.com/ui/1.1/main.js?v=1.0.11"></script>
    <title>吉安市低空经济服务一体化平台</title>
</head>
applications/drone-command/src/assets/images/common/point-active.png
applications/drone-command/src/assets/images/common/point.png
applications/drone-command/src/assets/images/dataCenter/positionicon.png

applications/drone-command/src/components/map-container/common-cesium-map.vue
@@ -1,5 +1,42 @@
<template>
    <div :id="mapId" class="common-cesium-map" @contextmenu.prevent></div>
    <div :id="mapId" class="common-cesium-map" @contextmenu.prevent>
        <template v-if="props.list && props.list.length">
            <div class="card">
                <div class="timeline-prefix">
                    <slot name="timeline-prefix"></slot>
                </div>
                <div
                    ref="timelineRef"
                    class="card-content"
                    :class="{ 'is-dragging': timelineDragging }"
                    @mousedown="handleTimelineMouseDown"
                    @mousemove="handleTimelineMouseMove"
                    @mouseup="stopTimelineDrag"
                    @mouseleave="stopTimelineDrag"
                >
                    <div class="timeline-track">
                        <div class="time-line"></div>
                        <div
                            class="card-item"
                            v-for="(item, index) in props.list"
                            @click="handleTimelineItemClick(item, index)"
                        >
                            <div class="time-point">
                                <img
                                    class="active"
                                    v-if="index === selectedImgIndex"
                                    src="@/assets/images/common/point-active.png"
                                    alt=""
                                />
                                <img v-else src="@/assets/images/common/point.png" alt="" />
                            </div>
                            <div class="time-bottom">{{ item.createTime.slice(11) }}</div>
                        </div>
                    </div>
                </div>
            </div>
        </template>
    </div>
</template>
<script setup>
@@ -8,6 +45,7 @@
import { onBeforeUnmount, onMounted, watch } from 'vue'
import { PublicCesium } from '@/utils/cesium/publicCesium'
import { loadJaAdminBoundary, removeJaAdminBoundary } from '@/utils/cesium/adminBoundary'
import positionIcon from '@/assets/images/dataCenter/positionicon.png'
const props = defineProps({
    active: {
@@ -58,10 +96,19 @@
        type: Number,
        default: 10000,
    },
    list: {
        type: Array,
        default: () => [],
    },
})
const emit = defineEmits(['ready', 'height-change', 'stage-change'])
const emit = defineEmits(['ready', 'height-change', 'stage-change', 'point-change'])
const mapId = props.domId || `common-cesium-map-${Math.random().toString(36).slice(2, 10)}`
const timelineRef = ref(null)
const timelineDragging = ref(false)
let timelineStartX = 0
let timelineStartScrollLeft = 0
let timelineDidDrag = false
let viewInstance = null
let viewer = null
const ADMIN_BOUNDARY_NAMES = {
@@ -108,7 +155,142 @@
        removeCameraListener = () => viewer?.camera?.changed?.removeEventListener(onCameraChanged)
    }
    emit('ready', { viewer, publicCesium: viewInstance })
    // 地图就绪时,若列表已加载则默认标记选中项(首个)
    showSelectedMarker()
}
const selectedImgIndex = ref(0)
// 航线高度(与 TrajectoryDiaLog 里 MOCK_FLIGHT_HEIGHT 保持一致),让标记悬浮在轨迹线上
const FLIGHT_HEIGHT = 120
const POSITION_MOVE_DURATION = 1000
// 当前经纬度位置标记
let positionEntity = null
let positionAnimationFrame = null
// 停止位置图标移动动画
const stopPositionAnimation = () => {
    if (positionAnimationFrame !== null) {
        cancelAnimationFrame(positionAnimationFrame)
        positionAnimationFrame = null
    }
}
// 平滑移动位置图标
const animatePositionMarker = (targetPosition, duration) => {
    const currentPosition = positionEntity?.position?.getValue(viewer.clock.currentTime, new Cesium.Cartesian3())
    if (!currentPosition) {
        positionEntity.position = targetPosition
        return
    }
    stopPositionAnimation()
    const startTime = performance.now()
    const nextPosition = new Cesium.Cartesian3()
    // 更新位置图标移动进度
    const updatePosition = currentTime => {
        const progress = Math.min((currentTime - startTime) / duration, 1)
        Cesium.Cartesian3.lerp(currentPosition, targetPosition, progress, nextPosition)
        positionEntity.position.setValue(nextPosition)
        viewer.scene.requestRender()
        if (progress < 1) {
            positionAnimationFrame = requestAnimationFrame(updatePosition)
        } else {
            positionAnimationFrame = null
        }
    }
    positionAnimationFrame = requestAnimationFrame(updatePosition)
}
// 点击周期
const clickWeekTime = (item, index, options = {}) => {
    // clickImgSrc.value = item.url
    selectedImgIndex.value = index
    showPositionMarker(item, options)
    // 通知父组件当前选中的轨迹点,用于右侧表格同步高亮
    emit('point-change', { item, index })
}
// 开始拖动时间轴
const handleTimelineMouseDown = event => {
    if (event.button !== 0) return
    timelineDragging.value = true
    timelineDidDrag = false
    timelineStartX = event.clientX
    timelineStartScrollLeft = timelineRef.value?.scrollLeft ?? 0
}
// 横向拖动时间轴
const handleTimelineMouseMove = event => {
    if (!timelineDragging.value || !timelineRef.value) return
    const offsetX = event.clientX - timelineStartX
    if (Math.abs(offsetX) > 3) timelineDidDrag = true
    if (!timelineDidDrag) return
    timelineRef.value.scrollLeft = timelineStartScrollLeft - offsetX
    event.preventDefault()
}
// 停止拖动时间轴
const stopTimelineDrag = () => {
    timelineDragging.value = false
    setTimeout(() => {
        timelineDidDrag = false
    }, 0)
}
// 点击时间轴节点
const handleTimelineItemClick = (item, index) => {
    if (timelineDidDrag) return
    clickWeekTime(item, index)
}
// 在地图上通过经纬度显示位置图标
const showPositionMarker = (item, { animate = false, duration = POSITION_MOVE_DURATION } = {}) => {
    if (!viewer) return
    const longitude = Number(item?.longitude)
    const latitude = Number(item?.latitude)
    if (Number.isNaN(longitude) || Number.isNaN(latitude)) return
    // 与航线相同的高度,让标记悬浮在轨迹线上,而不是贴地
    const position = Cesium.Cartesian3.fromDegrees(longitude, latitude, FLIGHT_HEIGHT)
    if (positionEntity) {
        if (animate) {
            animatePositionMarker(position, duration)
        } else {
            stopPositionAnimation()
            positionEntity.position = position
        }
    } else {
        positionEntity = viewer.entities.add({
            position,
            billboard: {
                image: positionIcon,
                width: 36,
                height: 36,
                verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
                disableDepthTestDistance: Number.POSITIVE_INFINITY,
            },
        })
    }
}
// 显示当前选中项的标记(默认首个),地图就绪和列表加载后都会调用
const showSelectedMarker = () => {
    const item = props.list?.[selectedImgIndex.value]
    if (item) showPositionMarker(item)
}
// 列表数据到位后,默认选中并标记首个点
watch(
    () => props.list,
    list => {
        if (list && list.length) {
            selectedImgIndex.value = 0
            showSelectedMarker()
            // 默认选中首个,同步通知父组件高亮表格首行
            emit('point-change', { item: list[0], index: 0 })
        }
    }
)
watch(
    () => props.active,
@@ -122,9 +304,11 @@
})
onBeforeUnmount(() => {
    stopPositionAnimation()
    if (removeCameraListener) removeCameraListener()
    removeJaAdminBoundary(viewer, adminBoundarySources)
    adminBoundarySources = null
    positionEntity = null
    viewInstance?.viewerDestroy?.()
})
@@ -216,12 +400,128 @@
    })
}
defineExpose({ getMap, getViewer, getPublicCesium, flyToPoint, setAdminBoundaryVisible, zoomToAdminBoundary })
defineExpose({ getMap, getViewer, getPublicCesium, flyToPoint, clickWeekTime, setAdminBoundaryVisible, zoomToAdminBoundary })
</script>
<style scoped lang="scss">
.common-cesium-map {
    width: 100%;
    height: 100%;
    position: relative;
    .card {
        position: absolute;
        bottom: 0px;
        width: 100%;
        height: 67px;
        box-sizing: border-box;
        background: rgba(0, 0, 0, 0.42);
        border-radius: 0;
        color: #d5d5d5;
        font-size: 14px;
        .timeline-prefix {
            position: absolute;
            left: 7.5%;
            top: 50%;
            display: flex;
            align-items: center;
            justify-content: center;
            z-index: 2;
            transform: translate(-50%, -50%);
        }
        .card-content {
            display: flex;
            position: absolute;
            left: 15%;
            width: 70%;
            height: 100%;
            overflow-x: auto;
            overflow-y: hidden;
            white-space: nowrap;
            cursor: grab;
            scrollbar-width: none;
            &::-webkit-scrollbar {
                display: none;
            }
            &.is-dragging {
                cursor: grabbing;
                user-select: none;
            }
            .timeline-track {
                position: relative;
                display: flex;
                flex: 0 0 auto;
                height: 100%;
                margin: 0 auto;
            }
            .card-item {
                position: relative;
                flex: 0 0 82px;
                width: 82px;
                text-align: center;
                cursor: pointer;
            }
            &.is-dragging .card-item {
                cursor: grabbing;
            }
            .time-top {
                margin-top: 10px;
                line-height: 1;
                text-align: center;
                cursor: pointer;
            }
            .active {
                font-weight: 400;
                color: #ffffff;
            }
            .time-point {
                display: flex;
                align-items: center;
                justify-content: center;
                position: absolute;
                top: 22px;
                left: 50%;
                width: 18px;
                height: 18px;
                z-index: 1;
                transform: translate(-50%, 0);
                img {
                    width: 11.2px;
                    height: 11.2px;
                }
                img.active {
                    width: 16.89px;
                    height: 16.89px;
                }
            }
            .time-bottom {
                margin-top: 44px;
            }
            .time-line {
                position: absolute;
                left: 41px;
                right: 41px;
                bottom: 36px;
                height: 1px;
                background: rgba(237, 237, 237, 0.6);
                z-index: 0;
                pointer-events: none;
            }
        }
    }
}
</style>
applications/drone-command/src/styles/common/cockpit.scss
@@ -1139,8 +1139,10 @@
        }
    }
    th.el-table__cell:not(.fixed-column),
    td.el-table__cell:not(.fixed-column) {
    // 注意:Element Plus 2.9.11 固定列类名为 el-table-fixed-column--left/right,
    // 老的 .fixed-column 已不存在,这里需一并排除,否则固定列会被刷成透明
    th.el-table__cell:not(.fixed-column):not(.el-table-fixed-column--left):not(.el-table-fixed-column--right),
    td.el-table__cell:not(.fixed-column):not(.el-table-fixed-column--left):not(.el-table-fixed-column--right) {
        background: transparent !important;
    }
@@ -1182,7 +1184,9 @@
        }
    }
    .fixed-column {
    .fixed-column,
    .el-table-fixed-column--left,
    .el-table-fixed-column--right {
        background-color: #1A1A2A !important;
    }
}
@@ -1781,7 +1785,7 @@
                            align-items: center;
                            .label {
                                width: 96px;
                                min-width: 96px;
                                font-family: Source Han Sans CN, Source Han Sans CN;
                                font-weight: 500;
                                font-size: 14px;
@@ -1965,7 +1969,7 @@
                                justify-content: flex-end;
                                padding-right: 12px;
                                width: 96px;
                                min-width: 96px;
                                font-family: Source Han Sans CN, Source Han Sans CN;
                                font-weight: 500;
applications/drone-command/src/utils/cesium/shapeTools/draw/DrawBufferCircleTool.js
@@ -242,7 +242,8 @@
    getPositionFromScreen(screenPosition) {
        const scene = this.viewer.scene
        const cartesian = scene.pickPosition(screenPosition)
        const ray = scene.camera.getPickRay(screenPosition)
        const cartesian = ray ? scene.globe.pick(ray, scene) : null
        if (cartesian) return cartesian
        return scene.camera.pickEllipsoid(screenPosition, scene.globe.ellipsoid)
    }
applications/drone-command/src/utils/cesium/shapeTools/draw/DrawEllipseTool.js
@@ -144,7 +144,8 @@
    getPositionFromScreen(screenPosition) {
        const scene = this.viewer.scene
        const cartesian = scene.pickPosition(screenPosition)
        const ray = scene.camera.getPickRay(screenPosition)
        const cartesian = ray ? scene.globe.pick(ray, scene) : null
        if (cartesian) return cartesian
        return scene.camera.pickEllipsoid(screenPosition, scene.globe.ellipsoid)
    }
applications/drone-command/src/utils/cesium/shapeTools/draw/DrawPolygonTool.js
@@ -313,7 +313,8 @@
    getPositionFromScreen(screenPosition) {
        const scene = this.viewer.scene
        const cartesian = scene.pickPosition(screenPosition)
        const ray = scene.camera.getPickRay(screenPosition)
        const cartesian = ray ? scene.globe.pick(ray, scene) : null
        if (cartesian) return cartesian
        return scene.camera.pickEllipsoid(screenPosition, scene.globe.ellipsoid)
    }
applications/drone-command/src/utils/cesium/shapeTools/draw/DrawRectangleTool.js
@@ -117,7 +117,8 @@
    getPositionFromScreen(screenPosition) {
        const scene = this.viewer.scene
        const cartesian = scene.pickPosition(screenPosition)
        const ray = scene.camera.getPickRay(screenPosition)
        const cartesian = ray ? scene.globe.pick(ray, scene) : null
        if (cartesian) return cartesian
        return scene.camera.pickEllipsoid(screenPosition, scene.globe.ellipsoid)
    }
applications/drone-command/src/utils/cesium/shapeTools/edit/EditBufferCircleTool.js
@@ -334,7 +334,8 @@
    getPositionFromScreen(screenPosition) {
        const scene = this.viewer.scene
        const cartesian = scene.pickPosition(screenPosition)
        const ray = scene.camera.getPickRay(screenPosition)
        const cartesian = ray ? scene.globe.pick(ray, scene) : null
        if (cartesian) return cartesian
        return scene.camera.pickEllipsoid(screenPosition, scene.globe.ellipsoid)
    }
applications/drone-command/src/utils/cesium/shapeTools/edit/EditEllipseTool.js
@@ -255,7 +255,8 @@
    getPositionFromScreen(screenPosition) {
        const scene = this.viewer.scene
        const cartesian = scene.pickPosition(screenPosition)
        const ray = scene.camera.getPickRay(screenPosition)
        const cartesian = ray ? scene.globe.pick(ray, scene) : null
        if (cartesian) return cartesian
        return scene.camera.pickEllipsoid(screenPosition, scene.globe.ellipsoid)
    }
applications/drone-command/src/utils/cesium/shapeTools/edit/EditPolygonTool.js
@@ -284,7 +284,8 @@
    getPositionFromScreen(screenPosition) {
        const scene = this.viewer.scene
        const cartesian = scene.pickPosition(screenPosition)
        const ray = scene.camera.getPickRay(screenPosition)
        const cartesian = ray ? scene.globe.pick(ray, scene) : null
        if (cartesian) return cartesian
        return scene.camera.pickEllipsoid(screenPosition, scene.globe.ellipsoid)
    }
applications/drone-command/src/utils/cesium/shapeTools/edit/EditRectangleTool.js
@@ -149,7 +149,8 @@
    getPositionFromScreen(screenPosition) {
        const scene = this.viewer.scene
        const cartesian = scene.pickPosition(screenPosition)
        const ray = scene.camera.getPickRay(screenPosition)
        const cartesian = ray ? scene.globe.pick(ray, scene) : null
        if (cartesian) return cartesian
        return scene.camera.pickEllipsoid(screenPosition, scene.globe.ellipsoid)
    }
applications/drone-command/src/views/aICrudTemplate/index.vue
@@ -52,7 +52,7 @@
            </el-table-column>
            <el-table-column prop="deviceModel" label="设备型号" />
            <el-table-column prop="deviceSpecification" label="规格" />
            <el-table-column label="操作">
            <el-table-column label="操作" fixed="right">
                <template v-slot="{ row }">
                    <el-link @click="openForm('view', row)" type="primary">查看</el-link>
                    <el-link @click="openForm('edit', row)" type="warning">编辑</el-link>
applications/drone-command/src/views/areaManage/areaStatistics/index.vue
@@ -49,7 +49,7 @@
                    <el-table-column prop="counterCount" show-overflow-tooltip label="侦测反制次数" />
                    <el-table-column prop="counterSuccessCount" show-overflow-tooltip label="反制成功次数" />
                    <el-table-column prop="controlEffect" show-overflow-tooltip label="管控效果" />
                    <el-table-column label="操作" class-name="operation-btns">
                    <el-table-column label="操作" class-name="operation-btns" fixed="right">
                        <template v-slot="{ row }">
                            <el-link @click="openForm(row)">查看</el-link>
                        </template>
applications/drone-command/src/views/areaManage/defenseZone/index.vue
@@ -27,7 +27,7 @@
                    <el-table-column prop="sceneCount" show-overflow-tooltip label="场景数量" />
                    <el-table-column prop="areaCount" show-overflow-tooltip label="区域数量" />
                    <el-table-column prop="deviceCount" show-overflow-tooltip label="设备数量" />
                    <el-table-column label="操作" class-name="operation-btns">
                    <el-table-column label="操作" class-name="operation-btns" fixed="right">
                        <template v-slot="{ row }">
                            <el-link @click="openForm('view', row)">查看</el-link>
                            <el-link @click="openForm('edit', row)">编辑</el-link>
applications/drone-command/src/views/areaManage/partition/FormDiaLog.vue
@@ -180,7 +180,7 @@
                                    {{ drawTypeLabelMap[row.drawType] || '-' }}
                                </template>
                            </el-table-column>
                            <el-table-column label="操作" class-name="operation-btns" width="120">
                            <el-table-column label="操作" class-name="operation-btns" fixed="right" width="120">
                                <template v-slot="{ row }">
                                    <el-link @click="handleShapeEdit(row)">编辑</el-link>
                                    <el-link @click="handleShapeDelete(row)">删除</el-link>
applications/drone-command/src/views/areaManage/partition/index.vue
@@ -52,7 +52,7 @@
                            {{ formatFlyDate(row) }}
                        </template>
                    </el-table-column>
                    <el-table-column label="操作" class-name="operation-btns">
                    <el-table-column label="操作" class-name="operation-btns" fixed="right">
                        <template v-slot="{ row }">
                            <el-link @click="openForm('view', row)">查看</el-link>
                            <el-link @click="openForm('edit', row)">编辑</el-link>
applications/drone-command/src/views/areaManage/precinctInfo/FormDiaLog.vue
@@ -264,6 +264,11 @@
        redPointEntity.position = Cesium.Cartesian3.fromDegrees(longitude, latitude)
        redPointEntity.label.text = `${longitude.toFixed(6)}, ${latitude.toFixed(6)}`
    }
    // 打开弹窗回显时,相机定位到该经纬度(2000m 视高,瞬间定位)
    viewer.camera.flyTo({
        destination: Cesium.Cartesian3.fromDegrees(longitude, latitude, 30000),
        duration: 0,
    })
}
function formatCoord(value) {
applications/drone-command/src/views/areaManage/precinctInfo/index.vue
@@ -28,7 +28,7 @@
                    <el-table-column prop="latitude" show-overflow-tooltip label="纬度" />
                    <el-table-column prop="contactPerson" show-overflow-tooltip label="联系人" />
                    <el-table-column prop="contactPhone" show-overflow-tooltip label="联系方式" />
                    <el-table-column label="操作" class-name="operation-btns">
                    <el-table-column label="操作" class-name="operation-btns" fixed="right">
                        <template v-slot="{ row }">
                            <el-link @click="openForm('edit', row)">编辑</el-link>
                            <el-link @click="handleDelete(row)">删除</el-link>
applications/drone-command/src/views/areaManage/sceneConfig/index.vue
@@ -55,7 +55,7 @@
                    <el-table-column prop="areaCount" show-overflow-tooltip label="区域数量" />
                    <el-table-column prop="counterDeviceCount" show-overflow-tooltip label="反制设备数量" />
                    <el-table-column prop="detectDeviceCount" show-overflow-tooltip label="侦测设备数量" />
                    <el-table-column label="操作" class-name="operation-btns">
                    <el-table-column label="操作" class-name="operation-btns" fixed="right">
                        <template v-slot="{ row }">
                            <el-link @click="openForm('view', row)">查看</el-link>
                            <el-link @click="openForm('edit', row)">编辑</el-link>
applications/drone-command/src/views/areaManage/sceneManage/FormDiaLog.vue
@@ -146,7 +146,7 @@
import { ElMessage } from 'element-plus'
import { fwDefenseSceneManageDetailApi, fwDefenseSceneManageSubmitApi } from './sceneManageApi'
import { fwDefenseSceneListApi } from '../sceneConfig/sceneConfigApi'
import { fieldRules } from '@ztzf/utils'
import { fieldRules, contactPhoneRules } from '@ztzf/utils'
import CommonCesiumMap from '@/components/map-container/common-cesium-map.vue'
import * as Cesium from 'cesium'
import commandPost from '@/assets/images/dataCockpit/legend/command-post.png'
@@ -213,7 +213,7 @@
const rules = {
    sceneName: fieldRules(true, 50),
    defenseLeader: fieldRules(true, 50),
    leaderPhone: fieldRules(true, 50),
    leaderPhone: contactPhoneRules(true, 50),
    effectiveDateStart: fieldRules(true),
    longitude: fieldRules(true),
    defenseSceneId: fieldRules(true),
applications/drone-command/src/views/areaManage/sceneManage/index.vue
@@ -42,7 +42,7 @@
                        </template>
                    </el-table-column>
                    <el-table-column prop="defenseSceneName" show-overflow-tooltip label="关联场景配置" />
                    <el-table-column label="操作" class-name="operation-btns">
                    <el-table-column label="操作" class-name="operation-btns" fixed="right">
                        <template v-slot="{ row }">
                            <el-link @click="openForm('view', row)">查看</el-link>
                            <el-link @click="openForm('edit', row)">编辑</el-link>
applications/drone-command/src/views/audioCall.vue
@@ -36,7 +36,8 @@
import { ref, computed, onBeforeUnmount } from 'vue'
/** ✅ 你的 WS 地址前缀(后面拼 userId) */
const WS_BASE = 'ws://218.202.104.82:38201/ws/chat'
// const WS_BASE = 'ws://218.202.104.82:38201/ws/chat'
const WS_BASE = 'ws://220.177.172.27:8100/webrtc/ws/chat'
const uid = ref('2021474568497131521')
const peerUid = ref('2021474815063486465')
applications/drone-command/src/views/basicManage/deviceScrap/index.vue
@@ -76,7 +76,7 @@
                    </el-table-column>
                    <el-table-column prop="scrapReason" show-overflow-tooltip label="报废原因" />
                    <el-table-column prop="disposeWay" show-overflow-tooltip label="处置方式" />
                    <el-table-column label="操作" class-name="operation-btns">
                    <el-table-column label="操作" class-name="operation-btns" fixed="right">
                        <template v-slot="{ row }">
                            <el-link @click="handleView(row)">查看</el-link>
                        </template>
applications/drone-command/src/views/basicManage/deviceStock/FormDiaLog.vue
@@ -45,6 +45,14 @@
                    <div class="val">{{ formData.belongDeptName }}</div>
                </el-col>
                <el-col :span="12">
                    <div class="label">开始使用日期</div>
                    <div class="val">{{ formData.useDate }}</div>
                </el-col>
                <el-col :span="12">
                    <div class="label">使用年限</div>
                    <div class="val">{{ formData.serviceLife }}</div>
                </el-col>
                <el-col :span="12">
                    <div class="label">负责人</div>
                    <div class="val">{{ formData.charger }}</div>
                </el-col>
@@ -80,7 +88,7 @@
            </div>
        </div>
        <el-form class="dialog-form" v-else ref="formRef" :model="formData" :rules="rules" label-width="100px">
        <el-form class="dialog-form" v-else ref="formRef" :model="formData" :rules="rules" label-width="120px">
            <el-row>
                <el-col :span="12">
                    <el-form-item label="设备名称" prop="deviceName">
@@ -147,6 +155,33 @@
                        <el-tree-select class="command-select" popper-class="command-tree-select-popper"
                            v-model="formData.belongDept" node-key="id" :data="deptTree" :props="treeProps"
                            check-strictly @change="belongDeptChange" clearable />
                    </el-form-item>
                </el-col>
                <el-col :span="12">
                    <el-form-item label="开始使用日期" prop="useDate">
                        <el-date-picker class="command-date-picker" popper-class="command-date-picker-popper"
                            v-model="formData.useDate"
                            type="date"
                            placeholder="选择日期"
                            value-format="YYYY-MM-DD"
                            clearable
                        />
                    </el-form-item>
                </el-col>
                <el-col :span="12">
                    <el-form-item label="使用年限" prop="serviceLife">
                         <el-input-number
                            class="command-input"
                            v-model="formData.serviceLife"
                            :min="0"
                            :max="100"
                            :precision="0"
                            :step="1"
                            step-strictly
                            :controls="false"
                            placeholder="请输入"
                            @keydown="e => ['.', 'e', 'E', '+', '-'].includes(e.key) && e.preventDefault()"
                        />
                    </el-form-item>
                </el-col>
                <el-col :span="12">
@@ -246,6 +281,8 @@
    belongDept: fieldRules(true),
    contactPhone: contactPhoneRules(true, 50),
    charger: fieldRules(true),
    serviceLife: fieldRules(true),
    useDate: fieldRules(true),
}
const userList = ref([])
applications/drone-command/src/views/basicManage/deviceStock/index.vue
@@ -1,9 +1,9 @@
<template>
    <basic-container>
        <div class="chart">
            <DeviceChart1 :key="updateKey"/>
            <DeviceChart2 :key="updateKey"/>
            <DeviceChart3 :key="updateKey"/>
            <DeviceChart1 :key="updateKey" />
            <DeviceChart2 :key="updateKey" />
            <DeviceChart3 :key="updateKey" />
        </div>
        <el-form ref="queryParamsRef" :model="searchParams" class="command-page-history-search">
@@ -91,7 +91,14 @@
                            {{ getDictLabel(row.trackStatus, trackStatusOptions) }}
                        </template>
                    </el-table-column>
                    <el-table-column label="操作" class-name="operation-btns" width="196">
                    <el-table-column prop="maintenanceStatus" show-overflow-tooltip width="96" label="报废预警">
                        <template v-slot="{ row }">
                            <span :style="{ color: row.maintenanceStatus === 0 ? 'inherit' : 'red' }">
                                {{ row.maintenanceStatus === 0 ? '正常' : '临近报废' }}
                            </span>
                        </template>
                    </el-table-column>
                    <el-table-column label="操作" class-name="operation-btns" width="196" fixed="right">
                        <template v-slot="{ row }">
                            <el-link @click="handleView(row)">查看</el-link>
                            <el-link @click="handleEdit(row)">编辑</el-link>
@@ -172,7 +179,7 @@
// 出库状态
const trackStatusOptions = ref([
    { dictKey: 0, dictValue: '未出库' },
    { dictKey: 1, dictValue: '已出库' }
    { dictKey: 1, dictValue: '已出库' },
])
const updateKey = ref(0)
@@ -232,7 +239,7 @@
        cancelButtonClass: 'command-message-box-cancel',
    })
    const payload = {
        deviceId: row.id
        deviceId: row.id,
    }
    await fwDeviceUpdateTrackStatusApi(payload)
    ElMessage.success('归还成功')
@@ -341,4 +348,7 @@
        }
    }
}
.danger-text {
    color: red; /* 或者直接用 red */
}
</style>
applications/drone-command/src/views/basicManage/maintainRecord/index.vue
@@ -47,7 +47,7 @@
                    <el-table-column prop="charger" show-overflow-tooltip label="负责人" />
                    <el-table-column prop="maintainStatus" show-overflow-tooltip label="维护提醒">
                        <template v-slot="{ row }">
                            {{ row.maintainStatus === 1 ? '已维修' : '未维修' }}
                            {{ row.maintainStatus === 1 ? '已维护' : '未维护' }}
                        </template>
                    </el-table-column>
                    <el-table-column prop="planCycleType" show-overflow-tooltip label="维护计划">
@@ -55,7 +55,7 @@
                            {{ getPlanCycleLabel(row) }}
                        </template>
                    </el-table-column>
                    <el-table-column label="操作" class-name="operation-btns">
                    <el-table-column label="操作" class-name="operation-btns" fixed="right">
                        <template v-slot="{ row }">
                            <el-link @click="handleView(row)">查看</el-link>
                            <el-link @click="maintenance(row)">维护</el-link>
applications/drone-command/src/views/detectionCountermeasure/detectionRange/index.vue
@@ -65,7 +65,7 @@
                            {{ formatDeviceCode(row) }}
                        </template>
                    </el-table-column>
                    <el-table-column label="操作" class-name="operation-btns">
                    <el-table-column label="操作" class-name="operation-btns" fixed="right">
                        <template v-slot="{ row }">
                            <el-link @click="handleView(row)">查看</el-link>
                            <el-link @click="handleEdit(row)">编辑</el-link>
applications/drone-command/src/views/detectionCountermeasure/deviceAppConfig/index.vue
@@ -78,7 +78,7 @@
                    <el-table-column prop="manufacturer" show-overflow-tooltip label="厂商" />
                    <el-table-column prop="contactPhone" show-overflow-tooltip width="112" label="联系方式" />
                    <el-table-column prop="sceneName" show-overflow-tooltip label="所属场景" />
                    <el-table-column label="操作" class-name="operation-btns">
                    <el-table-column label="操作" class-name="operation-btns" fixed="right">
                        <template v-slot="{ row }">
                            <el-link @click="openForm('edit', row)" type="warning">配置接口</el-link>
                        </template>
@@ -127,6 +127,7 @@
    status: '', // 设备状态
    current: 1, // 当前页
    size: 10, // 每页大小
    trackStatus: 1, // 轨迹状态
})
const searchParams = ref(initSearchParams()) // 查询参数
applications/drone-command/src/views/permissionManage/permissionDept/index.vue
@@ -64,7 +64,7 @@
                    <el-table-column prop="bingId" show-overflow-tooltip width="80" label="组织id" />
                    <el-table-column prop="areaName" show-overflow-tooltip label="行政区划" />
                    <el-table-column label="操作" class-name="operation-btns">
                    <el-table-column label="操作" class-name="operation-btns" fixed="right">
                        <template v-slot="{ row }">
                            <el-link @click="handleView(row)">查看</el-link>
                            <el-link v-if="permission.permissionDept_edit" @click="handleEdit(row)">编辑</el-link>
applications/drone-command/src/views/permissionManage/permissionRole/index.vue
@@ -57,7 +57,7 @@
                    <el-table-column prop="roleName" show-overflow-tooltip label="角色名称" />
                    <el-table-column prop="roleAlias" show-overflow-tooltip label="角色别名" />
                    <el-table-column prop="sort" show-overflow-tooltip label="角色排序" />
                    <el-table-column label="操作" class-name="operation-btns">
                    <el-table-column label="操作" class-name="operation-btns" fixed="right">
                        <template v-slot="{ row }">
                            <el-link @click="handleView(row)">查看</el-link>
                            <el-link @click="handleEdit(row)">编辑</el-link>
applications/drone-command/src/views/permissionManage/permissionUser/index.vue
@@ -67,7 +67,7 @@
                    <el-table-column prop="roleName" show-overflow-tooltip label="所属角色" />
                    <el-table-column prop="deptName" show-overflow-tooltip label="所属部门" />
                    <el-table-column prop="phone" show-overflow-tooltip width="120" label="手机号" />
                    <el-table-column label="操作" class-name="operation-btns" width="180">
                    <el-table-column label="操作" class-name="operation-btns" width="180" fixed="right">
                        <template v-slot="{ row }">
                            <el-link  @click="handleView(row)">查看</el-link>
                            <el-link v-if="permission.permissionUser_edit" @click="handleEdit(row)">编辑</el-link>
applications/drone-command/src/views/recordManage/historyTracks/TrajectoryDiaLog.vue
@@ -17,7 +17,16 @@
                    :terrain="true"
                    :layer-mode="4"
                    :boundary="false"
                />
                    :list="list"
                    @point-change="handlePointChange"
                >
                    <template #timeline-prefix>
                        <el-icon class="track-play-btn" :size="30" @click.stop="handlePlayClick">
                            <VideoPause v-if="isPlaying" />
                            <VideoPlay v-else />
                        </el-icon>
                    </template>
                </CommonCesiumMap>
            </div>
            <div class="right-container">
                <div class="header">
@@ -53,7 +62,7 @@
                    <div class="detail-title">轨迹点</div>
                    <div class="command-table-container">
                        <div class="command-table-content">
                            <el-table class="command-table" :data="list" :row-class-name="getRowClassName">
                            <el-table ref="pointTableRef" class="command-table" :data="list" :row-class-name="getRowClassName">
                                <el-table-column type="index" width="60" label="序号" />
                                <el-table-column label="经纬度">
                                    <template #default="{ row }">{{ row.longitude }},{{ row.latitude }}</template>
@@ -64,7 +73,9 @@
                    </div>
                </div>
                <div class="footer">
                    <el-button v-if="dialogMode != 'view'" color="#2B2B4C" @click="handleCancel">{{ dialogReadonly ? '关闭' : '取消' }}</el-button>
                    <el-button v-if="dialogMode != 'view'" color="#2B2B4C" @click="handleCancel">
                        {{ dialogReadonly ? '关闭' : '取消' }}
                    </el-button>
                </div>
            </div>
        </div>
@@ -72,9 +83,9 @@
</template>
<script setup>
import { Close } from '@element-plus/icons-vue'
import { Close, VideoPause, VideoPlay } from '@element-plus/icons-vue'
import { computed, nextTick, ref } from 'vue'
import { computed, nextTick, onBeforeUnmount, ref } from 'vue'
import CommonCesiumMap from '@/components/map-container/common-cesium-map.vue'
import { fwDroneFlightRecordDetailApi } from '@/views/recordManage/historyTracks/fwDroneFlightRecord'
import { fwDroneFlightRecordDetailPageApi } from '@/views/recordManage/historyTracks/flyTrajectory'
@@ -163,18 +174,87 @@
// 关闭后重置
function handleClosed() {
    stopTrackPlay()
    formData.value = initForm()
    list.value = []
    selectedPointIndex.value = 0
    viewer = null
    geometricSource = null
    droneLineEntity = null
    startDroneEntity = null
}
function getRowClassName({ row }) {
    console.log(row.isAlarm, 11111111111)
// 当前选中的轨迹点索引(与地图时间轴联动)
const selectedPointIndex = ref(0)
const pointTableRef = ref(null)
const isPlaying = ref(false)
const PLAY_INTERVAL = 1000
let playTimer = null
let playIndex = 0
    return row?.isAlarm === 1 ? 'alarm-row' : ''
// 停止轨迹播放
function stopTrackPlay() {
    if (playTimer) {
        clearTimeout(playTimer)
        playTimer = null
    }
    isPlaying.value = false
}
// 播放下一个轨迹点
function playNextPoint() {
    if (!list.value.length || playIndex >= list.value.length) {
        stopTrackPlay()
        return
    }
    mapRef.value?.clickWeekTime?.(list.value[playIndex], playIndex, {
        animate: true,
        duration: PLAY_INTERVAL,
    })
    playIndex += 1
    playTimer = setTimeout(() => {
        playTimer = null
        if (playIndex >= list.value.length) {
            stopTrackPlay()
            return
        }
        playNextPoint()
    }, PLAY_INTERVAL)
}
// 切换轨迹播放状态
function handlePlayClick() {
    if (isPlaying.value) {
        stopTrackPlay()
        return
    }
    if (!list.value.length) return
    mapRef.value?.clickWeekTime?.(list.value[0], 0)
    if (list.value.length === 1) return
    playIndex = 1
    isPlaying.value = true
    playNextPoint()
}
// 地图上点击轨迹点后,同步高亮表格对应行,并滚动到该行
function handlePointChange({ index } = {}) {
    if (typeof index !== 'number') return
    selectedPointIndex.value = index
    scrollToSelectedRow()
}
// 表格行较多时自动滚动到选中行
async function scrollToSelectedRow() {
    await nextTick()
    const rowEl = pointTableRef.value?.$el?.querySelector('.selected-row')
    rowEl?.scrollIntoView({ block: 'nearest', behavior: 'smooth' })
}
function getRowClassName({ row, rowIndex }) {
    const classNames = []
    if (row?.isAlarm === 1) classNames.push('alarm-row')
    if (rowIndex === selectedPointIndex.value) classNames.push('selected-row')
    return classNames.join(' ')
}
let viewer, geometricSource, droneLineEntity, startDroneEntity
@@ -189,6 +269,7 @@
// 打开弹框
async function open({ mode, row } = {}) {
    stopTrackPlay()
    dialogMode.value = mode || 'add'
    visible.value = true
    await nextTick()
@@ -202,14 +283,46 @@
}
defineExpose({ open })
// 卸载时清理播放定时器
onBeforeUnmount(() => {
    stopTrackPlay()
})
</script>
<style scoped lang="scss">
.left-container {
    min-width: 0;
    overflow: hidden;
}
.right-container {
    position: relative;
    flex-shrink: 0;
    z-index: 1;
    background: #1a1a2a;
}
:deep(.alarm-row) {
    background: #2B2B4C !important;
    background: #2b2b4c !important;
    td {
        background-color: transparent !important;
    }
}
// 与地图时间轴联动的选中行高亮
:deep(.selected-row) {
    background: #14335f !important;
    td {
        background-color: transparent !important;
        color: #4faaff !important;
    }
}
.track-play-btn {
    color: #ffffff;
    cursor: pointer;
}
</style>
applications/drone-command/src/views/recordManage/historyTracks/index.vue
@@ -66,7 +66,7 @@
                            {{ formatStayDuration(row.stayDuration) }}
                        </template>
                    </el-table-column>
                    <el-table-column label="操作" class-name="operation-btns">
                    <el-table-column label="操作" class-name="operation-btns" fixed="right">
                        <template v-slot="{ row }">
                            <el-link @click="handleView(row)">历史轨迹</el-link>
                        </template>
applications/drone-command/src/views/system/helpCenter.vue
@@ -17,7 +17,7 @@
        <el-table-column prop="levelTwoType" label="二级类别" />
        <el-table-column prop="uploadUser" label="上传者" />
        <el-table-column prop="createTime" label="上传时间" />
     <el-table-column label="操作" width="200" align="center">
     <el-table-column label="操作" width="200" align="center" fixed="right">
  <template #default="scope">
    <div class="actionBar" >
      <el-button 
applications/drone-command/src/views/system/user/index.vue
@@ -137,7 +137,7 @@
                        </template>
                    </el-table-column>
                    <el-table-column prop="phone" label="手机号码" />
                    <el-table-column label="操作" width="180">
                    <el-table-column label="操作" width="180" fixed="right">
                        <template v-slot="{ row }">
                            <el-link v-if="permission.user_view" @click="openForm('view', row)" type="primary">查看</el-link>
                            <el-link v-if="permission.user_edit && !auditMode" @click="openForm('edit', row)" type="warning">
applications/mobile-web-view/env/.env
@@ -2,12 +2,15 @@
#代理用的
VITE_APP_API = /api
VITE_APP_API = /jals/yisuanzhijian
VITE_APP_API_JALS = /jals/api
#axiosKY 直连航空网关域名(各环境一致)
VITE_APP_KY_URL = https://wrj.shuixiongit.com
#线上用的
VITE_APP_XT_URL=http://220.177.172.27:8100/yisuanzhijian
VITE_APP_XT_URL = http://220.177.172.27:8100/jals/yisuanzhijian
#调试参数
VITE_APP_DEBUG_KEY = saber
applications/mobile-web-view/src/api/map/address.js
@@ -1,4 +1,5 @@
import request from '@/axios'
import requestKY from '@/axiosKY'
/**
 * @description: 获取地址搜索结果
@@ -29,9 +30,9 @@
  })
}
// 我的空域查询
// 我的空域查询(走 axiosKY 实例,直连航空网关)
export const droneFlightTaskApi = data => {
  return request({
  return requestKY({
    url: `/webservice/droneFlightTask/pageInfoOfMy`,
    method: 'post',
    data,
applications/mobile-web-view/src/appPages/voiceCallDetail/index.vue
@@ -43,7 +43,8 @@
const route = useRoute()
/** ✅ 你的 WS 地址前缀(后面拼 userId) */
// const WS_BASE = 'ws://218.202.104.82:38201/ws/chat?userId='
const WS_BASE = 'ws://218.202.104.82:38201/ws/chat'
// const WS_BASE = 'ws://218.202.104.82:38201/ws/chat'
const WS_BASE = 'ws://220.177.172.27:8100/webrtc/ws/chat'
const newUserInfo = ref(null)
const uid = ref('')
const peerUid = ref('')
applications/mobile-web-view/src/axios.js
@@ -25,19 +25,23 @@
// 全局锁机制相关变量
window.isRefreshing = false // 标记当前是否正在刷新token
axios.defaults.timeout = 60000
//返回其他状态码
axios.defaults.validateStatus = function (status) {
    return status >= 200 && status <= 500 // 默认的
}
//跨域请求,允许保存cookie
axios.defaults.withCredentials = false
// 创建作用域的 axios 实例
const service = axios.create({
    // 超时时间设置为10分钟,部分接口上传比较慢,如固件上传
    timeout: 600000,
    //返回其他状态码
    validateStatus: function (status) {
        return status >= 200 && status <= 500 // 默认的
    },
    //跨域请求,允许保存cookie
    withCredentials: true,
})
NProgress.configure({
    showSpinner: false,
})
//http request拦截
axios.interceptors.request.use(
service.interceptors.request.use(
    config => {
        // 初始化错误提示状态
        isErrorShown = false
@@ -112,7 +116,7 @@
)
//http response拦截
axios.interceptors.response.use(
service.interceptors.response.use(
    res => {
        // NProgress.done()
        //获取配置信息
@@ -178,5 +182,5 @@
        return Promise.reject(new Error(error))
    }
)
window._request = axios
export default axios
window._request = service
export default service
applications/mobile-web-view/src/axiosKY.js
New file
@@ -0,0 +1,190 @@
/**
 * 全站http配置
 *
 * axios参数说明
 * isSerialize是否开启form表单提交
 * isToken是否需要token
 */
import axios from 'axios'
import store from '@/store/'
import router from '@/router/'
import { serialize } from '@/utils/util'
import { getToken, removeRefreshToken, removeToken } from '@/utils/auth'
import { isURL, validatenull } from '@/utils/validate'
import website from '@/config/website'
import NProgress from 'nprogress' // progress bar
import 'nprogress/nprogress.css' // progress bar style
import crypto from '@/utils/crypto'
import { getStore } from '@/utils/store'
import { showToast } from 'vant'
import { baseUrl, baseUrlJals } from '@/config/env'
let { VITE_APP_ENV,VITE_APP_XT_URL,VITE_APP_API,VITE_APP_KY_URL } = import.meta.env
// 全局未授权错误提示状态,只提示一次
let isErrorShown = false
// 全局锁机制相关变量
window.isRefreshing = false // 标记当前是否正在刷新token
// 创建作用域的 axios 实例
const service = axios.create({
    // 直连航空网关域名,请求最终为 baseURL + /jals/yisuanzhijian + /webservice/...
    // VITE_APP_KY_URL 各环境一致 = https://wrj.shuixiongit.com
    baseURL: VITE_APP_KY_URL,
    // 超时时间设置为10分钟,部分接口上传比较慢,如固件上传
    timeout: 600000,
    //返回其他状态码
    validateStatus: function (status) {
        return status >= 200 && status <= 500 // 默认的
    },
    //跨域请求,允许保存cookie
    withCredentials: true,
})
NProgress.configure({
    showSpinner: false,
})
//http request拦截
service.interceptors.request.use(
    config => {
        console.log(config,'9090')
        // 初始化错误提示状态
        isErrorShown = false
        // if (VITE_APP_ENV === 'development') {
        //     config.url = VITE_APP_API + config.url
        // } else {
        //     config.url = VITE_APP_XT_URL + config.url
        // }
        if (config.isUseJals) {
            if (!isURL(config.url) && !config.url.startsWith(baseUrlJals)) {
                config.url = baseUrlJals + config.url
            }
        }
        //地址为已经配置状态则不添加前缀
        if (!isURL(config.url) && !config.url.startsWith(baseUrl) && !config.isUseJals) {
            config.url = baseUrl + config.url
        }
        config.headers['areaCode'] = store.state.user.selectedAreaCode
        //安全请求header
        config.headers['Blade-Requested-With'] = 'BladeHttpRequest'
        //headers判断是否需要
        // const authorization = config.authorization === false
        // if (!authorization) {
        //     config.headers['Authorization'] = `Basic ${Base64.encode(`${website.clientId}:${website.clientSecret}`)}`
        // }
        config.headers['Authorization'] = store.state.user.token
        // 根据后端要求,post的data为空的话传{}
        if (config.method === 'post') {
            config.data = config.data || {}
        }
        //headers判断请求是否携带token
        const meta = config.meta || {}
        const isToken = meta.isToken === false
        //headers传递token是否加密
        const cryptoToken = config.cryptoToken === true
        //判断传递数据是否加密
        const cryptoData = config.cryptoData === true
        const token = getToken() || getStore({ name: 'token' })
        if (token && !isToken) {
            config.headers[website.tokenHeader] = cryptoToken
                ? 'crypto ' + crypto.encryptAES(token, crypto.cryptoKey)
                : 'bearer ' + token
        }
        // 开启报文加密
        // if (cryptoData) {
        //   if (config.params) {
        //     const data = crypto.encryptAES(JSON.stringify(config.params), crypto.aesKey);
        //     config.params = { data };
        //   }
        //   if (config.data) {
        //     config.text = true;
        //     config.data = crypto.encryptAES(JSON.stringify(config.data), crypto.aesKey);
        //   }
        // }
        //headers中配置text请求
        if (config.text === true) {
            config.headers['Content-Type'] = 'text/plain'
        }
        //headers中配置serialize为true开启序列化
        if (config.method === 'post' && meta.isSerialize === true) {
            config.data = serialize(config.data)
        }
        return config
    },
    error => {
        return Promise.reject(error)
    }
)
//http response拦截
service.interceptors.response.use(
    res => {
        // NProgress.done()
        //获取配置信息
        const config = res.config
        const cryptoData = config.cryptoData === true
        //解析加密报文
        if (cryptoData) {
            res.data = JSON.parse(crypto.decryptAES(res.data, crypto.aesKey))
        }
        //获取状态信息
        const status = res.data.error_code || res.data.code || res.status
        const statusWhiteList = website.statusWhiteList || []
        const message = res.data.msg || res.data.error_description || res.data.message || '系统错误'
        // 处理没有密钥证书,需登录重新联系运维人员上传证书
        if (status === 511) {
            // 获取当前路由名称
            const currentRoutePath = router.currentRoute.value
            if (currentRoutePath.path !== '/login') {
                store.dispatch('FedLogOut').then(() => router.push({ path: '/login' }))
            }
            return Promise.reject(new Error(message))
        }
        //如果在白名单里则自行catch逻辑处理
        if (statusWhiteList.includes(status)) return Promise.reject(res)
        // 如果是401并且没有重试过,尝试刷新token
        if (status === 401) {
            isErrorShown = true
            showToast('用户令牌不可用,请重新登录');
            const transmitData = { data: { type: 'tokenExpired' } }
            wx.miniProgram.reLaunch({url: 'pages/login/index'})
            wx.miniProgram.postMessage(transmitData)
            uni.postMessage(transmitData)
            removeToken()
            removeRefreshToken()
            // 重定向到登录页
            store.dispatch('FedLogOut').then(() => router.push({ path: '/login' }))
            return Promise.reject(new Error(message))
        }
        // 如果请求为oauth2错误码则首次报错时提示
        if (status > 2000 && !validatenull(res.data.error_description)) {
            if (!isErrorShown) {
                isErrorShown = true
                showToast(message);
            }
            return Promise.reject(new Error(message))
        }
        // 如果请求为非200则默认统一处理
        if (status !== 200) {
            // ElMessage({
            //     message: message,
            //     type: status === -2 ? 'warning' : 'error',
            // })
            showToast(message)
            return Promise.reject(new Error(message))
        }
        return res
    },
    error => {
        // NProgress.done()
        return Promise.reject(new Error(error))
    }
)
window._request = service
export default service
applications/mobile-web-view/vite.config.mjs
@@ -108,11 +108,11 @@
          rewrite: path => path.replace(/^\/dji_in_rectangle/, ''),
        },
        '/webservice': {
          target: VITE_APP_XT_URL,
          changeOrigin: true,
          rewrite: path => path.replace(/^\/webservice/, '/webservice'),
        },
        // '/webservice': {
        //   target: VITE_APP_XT_URL,
        //   changeOrigin: true,
        //   rewrite: path => path.replace(/^\/webservice/, '/webservice'),
        // },
        '/jals/api/drone-gd': {
          target: VITE_APP_XT_URL_GD,
@@ -120,10 +120,9 @@
          rewrite: path => path.replace(/^\/drone-gd/, '/drone-gd'),
        },
        '/api': {
          target: VITE_APP_XT_URL,
        '/jals/yisuanzhijian/webservice': {
          target: 'https://wrj.shuixiongit.com',
          changeOrigin: true,
          rewrite: path => path.replace(/^\/api/, ''),
        }
      },
    },
applications/task-work-order/env/.env
@@ -24,6 +24,11 @@
# 预览地址 previewURL
VITE_APP_PREVIEW_URL=http://220.177.172.27:8100/kkfile
# OnlyOffice 文档服务地址
#VITE_APP_ONLYOFFICE_DOCUMENT_SERVER_URL=http://192.168.1.54:8089/
VITE_APP_ONLYOFFICE_DOCUMENT_SERVER_URL=http://220.177.172.27:8100/onlyoffice
#地形服务地址
VITE_APP_TERRAIN_URL=http://220.177.172.27:8100/ztzf_terrain/
applications/task-work-order/package.json
@@ -13,6 +13,7 @@
        "@amap/amap-jsapi-loader": "catalog:",
        "@dvgis/dc-sdk": "catalog:",
        "@element-plus/icons-vue": "catalog:",
        "@onlyoffice/document-editor-vue": "catalog:",
        "@saber/nf-design-base-elp": "catalog:",
        "@saber/nf-form-design-elp": "catalog:",
        "@saber/nf-form-elp": "catalog:",
applications/task-work-order/src/api/documentPreview/index.js
New file
@@ -0,0 +1,9 @@
import request from '@/axios'
export const getOnlyOfficeConfigApi = params => {
    return request({
        url: '/blade-resource/attach/getOnlyOfficeConfig',
        method: 'get',
        params,
    })
}
applications/task-work-order/src/permission.js
@@ -67,14 +67,16 @@
          //   query: firstMenu.query || {},
          //   meta: firstMenu.meta || {},
          // })
          store.commit('ADD_TAG', {
            name: toMenu.name,
            path: toMenu.path,
            fullPath: toMenu.path,
            params: toMenu.params || {},
            query: toMenu.query || {},
            meta: toMenu.meta || {},
          })
          if (toMenu && to.meta?.isTab !== false) {
            store.commit('ADD_TAG', {
              name: toMenu.name,
              path: toMenu.path,
              fullPath: toMenu.path,
              params: toMenu.params || {},
              query: toMenu.query || {},
              meta: toMenu.meta || {},
            })
          }
          next(to.fullPath)
        }
applications/task-work-order/src/router/views/index.js
@@ -22,6 +22,18 @@
      }
    ],
  },
    {
        path: '/documentPreview/officeEdit',
        name: '巡查报告在线编辑',
        component: () =>
            import(
                /* webpackChunkName: "views" */ '@/views/documentPreview/index.vue'
            ),
        meta: {
            isTab: false,
            menu: false,
        },
    },
  {
    path: '/test',
    component: Layout,
applications/task-work-order/src/views/basicManage/maintainRecord/index.vue
@@ -51,7 +51,7 @@
                            {{ getPlanCycleLabel(row) }}
                        </template>
                    </el-table-column>
                    <el-table-column label="操作" class-name="operation-btns">
                    <el-table-column label="操作" class-name="operation-btns" fixed="right">
                        <template v-slot="{ row }">
                            <el-link @click="openForm('view', row)">查看</el-link>
                            <el-link @click="openForm('edit', row)">编辑</el-link>
applications/task-work-order/src/views/documentPreview/index.vue
New file
@@ -0,0 +1,103 @@
<template>
    <div class="document-preview-page" v-loading="loading">
        <DocumentEditor
            v-if="config"
            id="inspectionReportOfficeEditor"
            :documentServerUrl="documentServerUrl"
            :config="config"
            height="100%"
            width="100%"
            :events_onAppReady="onAppReady"
            :events_onDocumentReady="onDocumentReady"
            :events_onDocumentStateChange="onDocumentStateChange"
            :events_onWarning="onWarning"
            :events_onError="onError"
            :onLoadComponentError="onLoadComponentError"
        />
        <el-empty v-else-if="!loading" :description="errorMessage || '缺少文档配置'" />
    </div>
</template>
<script setup>
import { computed, ref, watch } from 'vue'
import { useRoute } from 'vue-router'
import { ElMessage } from 'element-plus'
import { DocumentEditor } from '@onlyoffice/document-editor-vue'
import { getOnlyOfficeConfigApi } from '@/api/documentPreview'
const route = useRoute()
const documentServerUrl = import.meta.env.VITE_APP_ONLYOFFICE_DOCUMENT_SERVER_URL
const attachId = computed(() => String(route.query.attachId || ''))
const mode = computed(() => String(route.query.mode || ''))
const config = ref(null)
const loading = ref(false)
const errorMessage = ref('')
async function getOnlyOfficeConfig() {
    if (!attachId.value) {
        config.value = null
        errorMessage.value = '缺少附件ID'
        return
    }
    loading.value = true
    errorMessage.value = ''
    try {
        const res = await getOnlyOfficeConfigApi({
            attachId: attachId.value,
            mode: mode.value,
        })
        const data = res?.data?.data || res?.data
        if (!data?.document || !data?.editorConfig) {
            throw new Error('OnlyOffice配置不完整')
        }
        config.value = data
    } catch (error) {
        console.error('获取OnlyOffice配置失败:', error)
        config.value = null
        errorMessage.value = '获取文档配置失败'
        ElMessage.error(error?.message || '获取文档配置失败')
    } finally {
        loading.value = false
    }
}
watch(attachId, getOnlyOfficeConfig, {
    immediate: true,
})
function onAppReady(event) {
    console.log('OnlyOffice app ready:', event)
}
function onDocumentReady(event) {
    console.log('OnlyOffice document ready:', event)
}
function onDocumentStateChange(event) {
    console.log('OnlyOffice document state change:', event)
}
function onWarning(event) {
    console.warn('OnlyOffice warning:', event)
}
function onError(event) {
    console.error('OnlyOffice error:', event)
}
function onLoadComponentError(errorCode, errorDescription) {
    console.error('OnlyOffice load error:', errorCode, errorDescription)
}
</script>
<style scoped lang="scss">
.document-preview-page {
    height: 100vh;
    min-height: 0;
    width: 100vw;
    overflow: hidden;
}
</style>
applications/task-work-order/src/views/orderView/appConfiguration/inventory/index.vue
@@ -62,7 +62,7 @@
                    <el-table-column prop="matterName" show-overflow-tooltip label="事项名称" />
                    <el-table-column prop="catalogCode" show-overflow-tooltip label="目录编码" />
                    <el-table-column prop="orgName" show-overflow-tooltip label="所属机构" />
                    <el-table-column label="操作" class-name="operation-btns" width="250">
                    <el-table-column label="操作" class-name="operation-btns" width="250" fixed="right">
                        <template v-slot="{ row }">
                            <el-link type="primary" @click="openForm('view', row)">查看</el-link>
                            <el-link type="primary" @click="openForm('edit', row)">编辑</el-link>
applications/task-work-order/src/views/orderView/appConfiguration/materials/index.vue
@@ -41,7 +41,7 @@
                            {{ getDictLabel(String(row.isAllScenario), dictObj.isAllScenario) }}
                        </template>
                    </el-table-column>
                    <el-table-column label="操作" class-name="operation-btns" width="200">
                    <el-table-column label="操作" class-name="operation-btns" width="200" fixed="right">
                        <template v-slot="{ row }">
                            <el-link type="primary" @click="openForm('view', row)">查看</el-link>
                            <el-link type="primary" @click="openForm('edit', row)">编辑</el-link>
applications/task-work-order/src/views/orderView/appConfiguration/process/index.vue
@@ -59,7 +59,7 @@
                    </el-table-column>
                    <el-table-column prop="created" show-overflow-tooltip label="创建时间" />
                    <el-table-column prop="lastUpdated" show-overflow-tooltip label="更新时间" />
                    <el-table-column label="操作" class-name="operation-btns" width="200">
                    <el-table-column label="操作" class-name="operation-btns" width="200" fixed="right">
                        <template v-slot="{ row }">
                            <el-link type="primary"  @click="handleUpdate(row)">配置</el-link>
                            <el-link type="primary" @click="handleDeploy(row)">部署</el-link>
applications/task-work-order/src/views/orderView/deviceInvocation/deviceCallRecord/FormDiaLog.vue
@@ -95,8 +95,7 @@
    visible.value = false
}
async function loadDetail(id) {
    formData.value.callId = id || formData.value.callId
async function loadDetail() {
    const res = await getDeviceCallRecordDetails(formData.value)
    detailsList.value = res?.data?.data.records || []
    total.value = res?.data?.data.total || 0
@@ -109,7 +108,8 @@
async function open({ mode, row } = {}) {
    dialogMode.value = mode || 'add'
    visible.value = true
    await loadDetail(row.id)
    formData.value.callId = row.id || ''
    await loadDetail()
}
onMounted(() => {
applications/task-work-order/src/views/orderView/deviceInvocation/deviceCallRecord/index.vue
@@ -84,7 +84,7 @@
                    <el-table-column prop="deviceName" show-overflow-tooltip label="执行设备" />
                    <el-table-column prop="planExecuteTime" show-overflow-tooltip label="计划执行时间" />
                    <el-table-column prop="actualExecuteTime" show-overflow-tooltip label="实际执行时间" />
                    <el-table-column label="操作" class-name="operation-btns" width="180">
                    <el-table-column label="操作" class-name="operation-btns" width="180" fixed="right">
                        <template v-slot="{ row }">
                            <el-link  @click="handleView(row)">查看</el-link>
                        </template>
applications/task-work-order/src/views/orderView/deviceInvocation/equipmentResource/index.vue
@@ -75,6 +75,11 @@
                            {{ getDictLabel(row.modeCode, dictObj.modeCode) }}
                        </template>
                    </el-table-column>
                    <el-table-column prop="maintenanceState" show-overflow-tooltip label="保养状态">
                        <template v-slot="{ row }">
                            {{ row.maintenanceState === 0 ? '无需保养' : row.maintenanceState === 1 ? '待保养' : '正在保养' }}
                        </template>
                    </el-table-column>
                    <el-table-column prop="deviceSn" show-overflow-tooltip label="设备SN" />
                    <el-table-column show-overflow-tooltip label="设备位置" >
                        <template v-slot="{ row }">
@@ -83,7 +88,7 @@
                    </el-table-column>
                    <el-table-column prop="firmwareVersion" show-overflow-tooltip label="固件版本" />
                    <el-table-column prop="deviceDeptName" show-overflow-tooltip label="所属部门" />
                    <el-table-column label="操作" class-name="operation-btns" width="180">
                    <el-table-column label="操作" class-name="operation-btns" width="180" fixed="right">
                        <template v-slot="{ row }">
                            <el-link @click="viewEquipment(row)">查看</el-link>
                            <!-- <el-link type="primary" @click="editEquipment(row)">编辑</el-link>
applications/task-work-order/src/views/orderView/flyingHand/index.vue
@@ -75,7 +75,7 @@
                            {{ getTaskTypeLabel(row.skilledTaskType, workOrderTypeXT) }}
                        </template>
                    </el-table-column>
                    <el-table-column label="操作" class-name="operation-btns" width="180">
                    <el-table-column label="操作" class-name="operation-btns" width="180" fixed="right">
                        <template v-slot="{ row }">
                            <el-link @click="viewFlyingHand(row)">查看</el-link>
                            <el-link type="primary" @click="editFlyingHand(row)">编辑</el-link>
applications/task-work-order/src/views/orderView/orderDataManage/appInnovation/index.vue
@@ -60,7 +60,7 @@
                    <el-table-column prop="resourceName" show-overflow-tooltip label="资源名称" />
                    <el-table-column prop="resourceCode" show-overflow-tooltip label="资源编码" />
                    <el-table-column prop="caseDesc" show-overflow-tooltip label="案例描述" />
                    <el-table-column label="操作" class-name="operation-btns" width="150">
                    <el-table-column label="操作" class-name="operation-btns" width="150" fixed="right">
                        <template v-slot="{ row }">
                            <el-link  type="primary"  @click="openForm('view', row)">查看</el-link>
                            <el-link  type="primary"  @click="openForm('edit', row)" v-if="!['0','1', '2'].includes(row.innovationStatus) && row.isTheSameDepartment">编辑</el-link>
applications/task-work-order/src/views/orderView/orderDataManage/dataObjection/FormDiaLog.vue
@@ -448,7 +448,7 @@
      objectionDesc: formData.value.objectionDesc,
      objectionBasis: formData.value.objectionBasis,
      areaCode: formData.value.areaCode || '',
      handleUnit: formData.value.handleUnit || '',
      handleUnit: applyFormData.value.applyTargetDeptId || formData.value.handleUnit || '',
      // 构建附件列表 - 使用当前已上传的文件(包括回显的)
            ...(uploadedFiles.value.length > 0 && {
                attachmentList: uploadedFiles.value.map(file => ({
@@ -487,6 +487,17 @@
     showApplyDialog.value = true
}
// 根据部门 id 在部门树里递归查找对应的中文名称(title)
function findDeptTitleById(tree, id) {
    if (!Array.isArray(tree) || id === '' || id == null) return ''
    for (const node of tree) {
        if (node.id === id) return node[treeProps.label]
        const found = findDeptTitleById(node[treeProps.children], id)
        if (found) return found
    }
    return ''
}
// 申请提交
async function handleApplySubmit() {
    const isValid = await applyFormRef.value?.validate().catch(() => false)
@@ -512,7 +523,8 @@
            objectionDesc: applyData.objectionDesc,
            objectionBasis: applyData.objectionBasis,
            areaCode: applyData.areaCode || '',
            handleUnit: applyData.handleUnit || '',
            // 处理单位存部门中文名称,而不是 id
            handleUnit: findDeptTitleById(deptTree.value, applyFormData.value.applyTargetDeptId) || applyData.handleUnit || '',
            // 构建附件列表 - 使用当前已上传的文件(包括回显的)
            ...(uploadedFiles.value.length > 0 && {
                attachmentList: uploadedFiles.value.map(file => ({
applications/task-work-order/src/views/orderView/orderDataManage/dataObjection/index.vue
@@ -69,7 +69,7 @@
                    <el-table-column prop="objectionBasis" show-overflow-tooltip label="异议依据" />
                    <el-table-column prop="attachNames" show-overflow-tooltip label="附件名称" />
                    <el-table-column prop="reviewOpinion" show-overflow-tooltip label="审核意见" />
                    <el-table-column label="操作" class-name="operation-btns" width="150">
                    <el-table-column label="操作" class-name="operation-btns" width="150" fixed="right">
                        <template v-slot="{ row }">
                            <el-link  type="primary"  @click="openForm('view', row)">查看</el-link>
                            <el-link  type="primary"  @click="openForm('edit', row)" v-if="!['1', '2'].includes(row.objectionStatus)">编辑</el-link>
applications/task-work-order/src/views/orderView/orderDataManage/evaluate/index.vue
@@ -54,7 +54,7 @@
                        </template>
                    </el-table-column>
                    <el-table-column prop="evaluationContent" show-overflow-tooltip label="评价内容" />
                    <el-table-column label="操作" class-name="operation-btns" width="150">
                    <el-table-column label="操作" class-name="operation-btns" width="150" fixed="right">
                        <template v-slot="{ row }">
                            <el-link  type="primary"  @click="openForm('view', row)">查看</el-link>
                            <el-link  type="primary"  @click="openForm('edit', row)" v-if="row.isTheSameDepartment">编辑</el-link>
applications/task-work-order/src/views/orderView/orderDataManage/supplyAdd/index.vue
@@ -76,7 +76,7 @@
                    <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" width="150">
                    <el-table-column label="操作" class-name="operation-btns" width="150" fixed="right">
                        <template v-slot="{ row }">
                            <el-link  type="primary"  @click="openForm('view', row)">查看</el-link>
                            <el-link  type="primary"  @click="openForm('edit', row)" v-if="!['1', '2', '3'].includes(row.demandStatus) && permissionList.addBtn">编辑</el-link>
applications/task-work-order/src/views/orderView/orderManage/clueEvents/ViewDiaLog.vue
@@ -33,7 +33,7 @@
                </el-table-column>
                <el-table-column prop="distributeDeptName" show-overflow-tooltip label="分发部门" />
                <el-table-column prop="distributeUserName" show-overflow-tooltip label="分发人员" />
                <el-table-column label="操作" class-name="operation-btns" width="140">
                <el-table-column label="操作" class-name="operation-btns" width="140" fixed="right">
                    <template v-slot="{ row }">
                        <el-link
                            type="primary"
applications/task-work-order/src/views/orderView/orderManage/clueEvents/index.vue
@@ -85,7 +85,7 @@
                    <el-table-column prop="resultCount" show-overflow-tooltip label="线索数量" />
                    <el-table-column prop="issueEventCount" show-overflow-tooltip label="转为问题" />
                    <el-table-column prop="taskDesc" show-overflow-tooltip label="巡查任务描述" />
                    <el-table-column label="操作" class-name="operation-btns">
                    <el-table-column label="操作" class-name="operation-btns" fixed="right">
                        <template v-slot="{ row }">
                            <el-link type="primary" @click="viewDiaLogView(row)">查看线索</el-link>
                        </template>
applications/task-work-order/src/views/orderView/orderManage/inspectionReport/index.vue
@@ -41,7 +41,11 @@
                <el-table class="gd-table" :data="list" @selection-change="handleSelectionChange">
                    <el-table-column type="selection" width="46" />
                    <el-table-column type="index" width="64" label="序号" />
                    <el-table-column prop="nickName" show-overflow-tooltip label="文档名称" />
                    <el-table-column prop="nickName" class-name="operation-btns" show-overflow-tooltip label="文档名称">
                        <template v-slot="{ row }">
                            <el-link  type="primary"  @click="openForm(row)">{{ row.nickName }}</el-link>
                        </template>
                    </el-table-column>
                    <el-table-column prop="attachSize" show-overflow-tooltip label="文档大小">
                        <template v-slot="{ row }">
                        {{ formatFileSize(row.attachSize) }}
@@ -54,10 +58,10 @@
                            {{ getTaskTypeLabel(row.resultType, workOrderTypeXT) }}
                        </template>
                    </el-table-column>
                    <el-table-column label="操作" class-name="operation-btns">
                    <el-table-column label="操作" class-name="operation-btns" fixed="right">
                        <template v-slot="{ row }">
                            <el-link  type="primary"  @click="seeOnlineWord(row)">查看</el-link>
                            <el-link  type="primary"  @click="openForm(row)">在线编辑</el-link>
                            <el-link type="primary" :disabled="isZipRow(row)" @click="openOfficeEdit(row)">在线编辑</el-link>
                            <el-link  type="primary"  @click="handleDelete(row)">删除</el-link>
                        </template>
                    </el-table-column>
@@ -135,8 +139,8 @@
<script setup>
import { Search, RefreshRight, Download, Upload } from '@element-plus/icons-vue'
import { fjPageApi, fjSubmitApi, fjRemoveApi, fjUploadApi, fjDownloadByByteApi } from './inspectionRequestApi'
import { useStore } from 'vuex'
import { ref, computed, onMounted } from 'vue'
import { ref, onMounted, onBeforeUnmount } from 'vue'
import { useRouter } from 'vue-router'
import { ElMessage, ElMessageBox } from 'element-plus'
import PreviewFiles from '@/components/PreviewFiles/PreviewFiles.vue'
import { Base64 } from 'js-base64'
@@ -147,8 +151,7 @@
    taskTypeCascaderProps,
} from '../taskTypeOptions'
const store = useStore()
const userInfo = computed(() => store.getters.userInfo)
const router = useRouter()
@@ -269,17 +272,66 @@
];
const { VITE_APP_PREVIEW_URL } = import.meta.env
// kkfileView 中,word/pdf 默认转成只读 pdf 预览,excel 才是可编辑的 html 模式。
// 对这些类型追加 officePreviewType=html,让其以可编辑的 html 方式打开
const editableOfficeTypes = ['doc', 'docx', 'pdf']
// 在线查看文档
function seeOnlineWord(row) {
    // 获取点之后的文件格式名
    const fileType = row?.link?.split('.').pop() || ''
    const fileType = (row?.link?.split('.').pop() || '').toLowerCase()
    if (!row.link) {
        ElMessage.warning('文件链接不存在')
    } else if (allSupportedExtensions.includes(fileType)){
        window.open(`${VITE_APP_PREVIEW_URL}/onlinePreview?url=`+encodeURIComponent(Base64.encode(row.link)));
        // let url = `${VITE_APP_PREVIEW_URL}/onlinePreview?url=` + encodeURIComponent(Base64.encode(row.link))
        // // word/pdf 以可编辑的 html 模式打开(与 excel 一致)
        // if (editableOfficeTypes.includes(fileType)) {
        //     url += '&officePreviewType=html'
        // }
        // window.open(url)
        const route = router.resolve({
            path: '/documentPreview/officeEdit',
            query: {
                attachId: row.id,
                mode: 'view',
            },
        })
        window.open(route.href, '_blank')
    } else {
        ElMessage.warning(`${fileType}文件格式,可以下载再查看`)
    }
}
// 取行的文件扩展名(列表数据没有 extension 字段,回退用 link 解析)
function getRowExtension(row) {
    return (row?.extension || row?.link?.split('.').pop() || '').toLowerCase()
}
// 是否为 zip 文件(zip 不支持在线编辑)
function isZipRow(row) {
    return getRowExtension(row) === 'zip'
}
function openOfficeEdit(row) {
    if (!row?.id) {
        ElMessage.warning('附件ID不存在')
        return
    }
    if (isZipRow(row)) {
        ElMessage.warning('zip文件格式,不能编辑')
        return
    }
    const route = router.resolve({
        path: '/documentPreview/officeEdit',
        query: {
            attachId: row.id,
            mode: 'edit',
        },
    })
    window.open(route.href, '_blank')
    // 标记已跳转去编辑,切回本页时刷新列表
    pendingEditRefresh = true
}
// 删除
@@ -423,9 +475,33 @@
    }
}
// 在线编辑跳转标记:从编辑页切回本页时刷新列表
let pendingEditRefresh = false
function handleVisibilityChange() {
    if (document.visibilityState === 'visible' && pendingEditRefresh) {
        pendingEditRefresh = false
        getList()
    }
}
onMounted(() => {
    getDictList()
    getList()
    document.addEventListener('visibilitychange', handleVisibilityChange)
})
onBeforeUnmount(() => {
    document.removeEventListener('visibilitychange', handleVisibilityChange)
})
</script>
<style scoped lang="scss"></style>
<style scoped lang="scss">
// 操作列里链接的文字(span)被主题强制刷成蓝色 #0075FF,
// 禁用时需在这里把 span 一并置灰,否则「在线编辑」禁用了还是蓝的
:deep(.operation-btns) {
    .el-link.is-disabled,
    .el-link.is-disabled span {
        color: #c0c4cc !important;
        cursor: not-allowed !important;
    }
}
</style>
applications/task-work-order/src/views/orderView/orderManage/inspectionRequest/FormDiaLog.vue
@@ -171,7 +171,7 @@
                                    >
                                        <template #default>
                                        <!-- 匹配区域=1 显示红星号 -->
                                        <span v-if="item.areaMatchFlag === 1" style="color: #f56c6c; margin-right: 4px;">*</span>
                                        <span v-if="item.taskTypeMatchFlag === 1" style="color: #f56c6c; margin-right: 4px;">*</span>
                                        <span>{{ item.flyerName }}</span>
                                        </template>
                                    </el-option>
applications/task-work-order/src/views/orderView/orderManage/inspectionRequest/index.vue
@@ -95,7 +95,7 @@
                    <el-table-column prop="executeTime" show-overflow-tooltip label="任务执行时间" />
                    <el-table-column prop="createTime" show-overflow-tooltip label="巡查任务创建时间" />
                    <el-table-column prop="taskDesc" show-overflow-tooltip label="巡查任务描述" />
                    <el-table-column label="操作" class-name="operation-btns">
                    <el-table-column label="操作" class-name="operation-btns" fixed="right">
                        <template v-slot="{ row }">
                            <el-link type="primary" @click="viewDiaLogView(row)">查看</el-link>
                        </template>
applications/task-work-order/src/views/orderView/orderManage/operatingIncome/index.vue
@@ -19,7 +19,7 @@
          <el-table-column prop="marketSpace" label="市场空间" align="center" show-overflow-tooltip></el-table-column>
          <el-table-column prop="createTime" label="创建时间" align="center"></el-table-column>
          <el-table-column prop="nickName" label="创建人" align="center"></el-table-column>
          <el-table-column label="操作" width="180" align="center" class-name="operation-btns">
          <el-table-column label="操作" width="180" align="center" class-name="operation-btns" fixed="right">
            <template #default="scope">
              <!-- <el-button icon="el-icon-view" type="text" @click="handleDetail(scope.row)">查看</el-button> -->
              <el-link type="primary" @click="handleEdit(scope.row)">编辑</el-link>
applications/task-work-order/src/views/orderView/orderManage/orderManage/FormDiaLog.vue
@@ -299,6 +299,7 @@
    remark: '',
    geom: '',
    recommendDeviceIds: '',
    typePinyinFirst: '',
})
const mapRef = ref(null)
@@ -686,6 +687,36 @@
    }
)
// 取汉字(或英文)首字的拼音首字母,大写。如「占」→ Z
function getFirstPinyinLetter(text) {
    const char = String(text ?? '').trim().charAt(0)
    if (!char) return ''
    // 英文/数字直接取大写
    if (/[a-zA-Z]/.test(char)) return char.toUpperCase()
    // 按拼音排序的各首字母边界字,用 zh-CN 本地化比较定位所属字母
    const boundaries = [
        ['A', '啊'], ['B', '芭'], ['C', '擦'], ['D', '搭'], ['E', '蛾'],
        ['F', '发'], ['G', '噶'], ['H', '哈'], ['J', '击'], ['K', '喀'],
        ['L', '垃'], ['M', '妈'], ['N', '拿'], ['O', '哦'], ['P', '啪'],
        ['Q', '期'], ['R', '然'], ['S', '撒'], ['T', '塌'], ['W', '挖'],
        ['X', '昔'], ['Y', '压'], ['Z', '匝'],
    ]
    for (let i = boundaries.length - 1; i >= 0; i--) {
        if (char.localeCompare(boundaries[i][1], 'zh-CN') >= 0) return boundaries[i][0]
    }
    return ''
}
// 工单类型变化时,用类型名称第一个字的拼音首字母(大写)赋值给 typePinyinFirst
watch(
    () => formData.value.workOrderType,
    value => {
        const label = getTaskTypeLabel(value, workOrderTypeXT.value)
        formData.value.typePinyinFirst = getFirstPinyinLetter(label)
    },
    { deep: true }
)
// 地图加载完毕
function mapReady() {
    dialogMode.value === 'add' && mapRef.value.flyBoundary()
applications/task-work-order/src/views/orderView/orderManage/orderManage/index.vue
@@ -15,6 +15,16 @@
                />
            </el-form-item>
            <el-form-item label="工单编号" prop="workOrderCode">
                <el-input
                    class="gd-input gray"
                    v-model="searchParams.workOrderCode"
                    placeholder="请输入"
                    clearable
                    @clear="handleSearch"
                />
            </el-form-item>
            <el-form-item label="工单类型" prop="workOrderType">
                <el-cascader
                    class="gd-cascader gray"
@@ -60,6 +70,25 @@
                />
            </el-form-item>
            <el-form-item label="需求方名称" prop="createUserName">
                <el-input
                    class="gd-input gray"
                    v-model="searchParams.createUserName"
                    placeholder="请输入"
                    clearable
                    @clear="handleSearch"
                />
            </el-form-item>
            <el-form-item label="服务方名称" prop="serviceParty">
                <el-input
                    class="gd-input gray"
                    v-model="searchParams.serviceParty"
                    placeholder="请输入"
                    clearable
                    @clear="handleSearch"
                />
            </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>
@@ -74,11 +103,11 @@
            </div>
            <div class="gd-table-content gd-table-content-bg">
                <el-table class="gd-table" :data="list" @selection-change="handleSelectionChange">
                <el-table class="gd-table" :data="list" @selection-change="handleSelectionChange" @sort-change="handleSortChange">
                    <el-table-column type="index" width="64" label="序号" />
                    <el-table-column prop="workOrderName" show-overflow-tooltip label="工单名称" />
                    <el-table-column prop="workOrderCode" show-overflow-tooltip label="工单编号" />
                    <el-table-column prop="workOrderType" show-overflow-tooltip label="工单类型">
                    <el-table-column prop="workOrderType" show-overflow-tooltip label="工单类型" sortable="custom">
                        <template v-slot="{ row }">
                            {{ getTaskTypeLabel(row.workOrderType, workOrderTypeXT) }}
                        </template>
@@ -104,7 +133,7 @@
                    <el-table-column prop="createUserName" show-overflow-tooltip label="需求方名称" />
                    <el-table-column prop="serviceParty" show-overflow-tooltip label="服务方名称" />
                    <el-table-column prop="createTime" show-overflow-tooltip label="工单创建时间" />
                    <el-table-column label="操作" class-name="operation-btns">
                    <el-table-column label="操作" class-name="operation-btns" fixed="right">
                        <template v-slot="{ row }">
                            <el-link type="primary" @click="openFormChange(row)">查看</el-link>
                            <el-link type="primary" @click="routeManagement(row)">航线管理</el-link>
@@ -157,10 +186,14 @@
// 初始化查询参数
const initSearchParams = () => ({
    workOrderName: '', // 工单名称
    workOrderCode: '', // 工单编号
    workOrderType: '', // 工单类型
    workOrderStatus: '', // 工单状态
    executeStartTime: '', // 执行开始时间
    executeEndTime: '', // 执行结束时间
    createUserName: '', // 需求方名称
    serviceParty: '', // 服务方名称
    typePinyinFirst: '', // 工单类型拼音首字母(大写)
    current: 1, // 当前页
    size: 10, // 每页大小
})
@@ -196,6 +229,7 @@
            endTime: range[1],
            createUser: activeName.value === 'my' ? store.state.user.userInfo.user_id : '',
            workOrderType: searchParams?.value?.workOrderType?.join?.(','),
            typePinyinFirst: typePinyinFirst.value,
        })
        list.value = res?.data?.data?.records ?? []
        total.value = res?.data?.data?.total ?? 0
@@ -311,10 +345,47 @@
    myTotal.value = myRes?.data?.data?.total ?? 0
}
// 工单类型排序方向:asc(升序)/ desc(降序)/ ''(不排序)
const typePinyinFirst = ref('')
// 点击工单类型排序三角:上三角传 asc,下三角传 desc
function handleSortChange({ prop, order }) {
    if (prop !== 'workOrderType') return
    typePinyinFirst.value = order === 'ascending' ? 'asc' : order === 'descending' ? 'desc' : ''
    searchParams.value.current = 1
    getList()
}
onMounted(() => {
    getTabTotals()
    getList()
    getDictList()
})
</script>
<style scoped lang="scss"></style>
<style scoped lang="scss">
.gd-search-form {
    // 搜索项较多,用栅格分两排整齐排列(4 列 × 2 行)
    display: grid;
    grid-template-columns: repeat(5, max-content);
    gap: 16px 40px;
    align-items: center;
    // 清除公共样式里的行内间距,交给 grid 的 gap 统一控制
    :deep(.el-form-item) {
        margin: 0 !important;
    }
    // 固定 label 宽度,让每一列的输入框起点对齐
    :deep(.el-form-item__label) {
        width: 84px;
        margin-right: 12px;
        flex-shrink: 0;
    }
    // 操作按钮紧跟最后一个搜索项
    :deep(.gd-search-actions) {
        justify-self: start;
        margin-left: 30px !important;
    }
}
</style>
applications/task-work-order/src/views/orderView/orderManage/orderManage/orderManageApi.js
@@ -5,7 +5,7 @@
    return request({
        url: `/drone-gd/workOrder/gdWorkOrder/page`,
        method: 'get',
        params: { descs: 'create_time', ...params },
        params: { ...params },
    })
}
applications/task-work-order/src/views/orderView/orderManage/orderManage/outcomeData.vue
@@ -79,7 +79,7 @@
          </el-table-column>
          
          <el-table-column prop="shootTime" show-overflow-tooltip label="拍摄时间" />
          <el-table-column label="操作" class-name="operation-btns" width="120">
          <el-table-column label="操作" class-name="operation-btns" width="120" fixed="right">
            <template v-slot="{ row }">
              <!-- <el-link type="primary" @click="handleDownload(row)">下载</el-link> -->
              <el-link type="primary" @click="handleDelete(row)">删除</el-link>
applications/task-work-order/src/views/orderView/organizational/agenciesManagement/index.vue
@@ -62,7 +62,7 @@
             {{ getDictLabel(row.status, dictObj.institutionStatus) }}
            </template>
          </el-table-column>
          <el-table-column label="操作" class-name="operation-btns" width="200">
          <el-table-column label="操作" class-name="operation-btns" width="200" fixed="right">
            <template v-slot="{ row }">
              <el-link type="primary" @click="openForm('view', row)">查看</el-link>
              <el-link type="primary"  @click="openForm('edit', row)">编辑</el-link>
applications/task-work-order/src/views/orderView/organizational/roleMange/index.vue
@@ -57,7 +57,7 @@
                    <el-table-column prop="roleName" show-overflow-tooltip label="角色名称" />
                    <el-table-column prop="roleAlias" show-overflow-tooltip label="角色别名" />
                    <el-table-column prop="sort" show-overflow-tooltip label="角色排序" />
                    <el-table-column label="操作" class-name="operation-btns">
                    <el-table-column label="操作" class-name="operation-btns" fixed="right">
                        <template v-slot="{ row }">
                            <el-link @click="handleView(row)">查看</el-link>
                            <el-link @click="handleEdit(row)">编辑</el-link>
applications/task-work-order/src/views/orderView/organizational/userMange/index.vue
@@ -65,7 +65,7 @@
                    <el-table-column prop="roleName" show-overflow-tooltip label="所属角色" />
                    <el-table-column prop="deptName" show-overflow-tooltip label="所属部门" />
                    <el-table-column prop="phone" show-overflow-tooltip width="120" label="手机号" />
                    <el-table-column label="操作" class-name="operation-btns" width="180">
                    <el-table-column label="操作" class-name="operation-btns" width="180" fixed="right">
                        <template v-slot="{ row }">
                            <el-link  @click="handleView(row)">查看</el-link>
                            <el-link v-if="permission.orderUser_edit" @click="handleEdit(row)">编辑</el-link>
applications/task-work-order/src/views/orderView/organizational/zoningManagement/index.vue
@@ -66,7 +66,7 @@
              {{ getDictLabel(row.status, dictObj.zoningStatus) }}
            </template>
          </el-table-column>
          <el-table-column label="操作" class-name="operation-btns" width="200">
          <el-table-column label="操作" class-name="operation-btns" width="200" fixed="right">
            <template v-slot="{ row }">
              <el-link type="primary" @click="openForm('view', row)">查看</el-link>
              <el-link type="primary"  @click="openForm('edit', row)">编辑</el-link>
applications/task-work-order/src/views/system/helpCenter.vue
@@ -17,7 +17,7 @@
        <el-table-column prop="levelTwoType" label="二级类别" />
        <el-table-column prop="uploadUser" label="上传者" />
        <el-table-column prop="createTime" label="上传时间" />
     <el-table-column label="操作" width="200" align="center">
     <el-table-column label="操作" width="200" align="center" fixed="right">
  <template #default="scope">
    <div class="actionBar" >
      <el-button 
applications/task-work-order/src/views/system/user/index.vue
@@ -137,7 +137,7 @@
                        </template>
                    </el-table-column>
                    <el-table-column prop="phone" label="手机号码" />
                    <el-table-column label="操作" width="180">
                    <el-table-column label="操作" width="180" fixed="right">
                        <template v-slot="{ row }">
                            <el-link v-if="permission.user_view" @click="openForm('view', row)" type="primary">查看</el-link>
                            <el-link v-if="permission.user_edit && !auditMode" @click="openForm('edit', row)" type="warning">
package.json
@@ -16,9 +16,6 @@
        "prod": "pnpm -r prod",
        "prod:drone-command": "pnpm -C applications/drone-command prod",
        "prod:mobile-web-view": "pnpm -C applications/mobile-web-view prod",
        "build:test:drone-command": "pnpm -C applications/drone-command build:test",
        "build:test:mobile-web-view": "pnpm -C applications/mobile-web-view build:test",
        "build:test:task-work-order": "pnpm -C applications/task-work-order build:test",
        "build:prod:drone-command": "pnpm -C applications/drone-command build:prod",
        "build:prod:mobile-web-view": "pnpm -C applications/mobile-web-view build:prod",
        "build:prod:task-work-order": "pnpm -C applications/task-work-order build:prod"
pnpm-lock.yaml
@@ -18,6 +18,9 @@
    '@element-plus/icons-vue':
      specifier: ^2.3.1
      version: 2.3.2
    '@onlyoffice/document-editor-vue':
      specifier: ^1.6.1
      version: 1.6.1
    '@saber/nf-design-base-elp':
      specifier: ^1.3.0
      version: 1.3.0
@@ -640,6 +643,9 @@
      '@element-plus/icons-vue':
        specifier: 'catalog:'
        version: 2.3.2(vue@3.5.27(typescript@5.9.3))
      '@onlyoffice/document-editor-vue':
        specifier: 'catalog:'
        version: 1.6.1(vue@3.5.27(typescript@5.9.3))
      '@saber/nf-design-base-elp':
        specifier: 'catalog:'
        version: 1.3.0
@@ -2796,6 +2802,11 @@
  '@nuxt/kit@3.20.2':
    resolution: {integrity: sha512-laqfmMcWWNV1FsVmm1+RQUoGY8NIJvCRl0z0K8ikqPukoEry0LXMqlQ+xaf8xJRvoH2/78OhZmsEEsUBTXipcw==}
    engines: {node: '>=18.12.0'}
  '@onlyoffice/document-editor-vue@1.6.1':
    resolution: {integrity: sha512-sdU7h684ESSdXvGNDcMf73UmToiZGMVO5QRIazTmGfm+bKOnT5ildomeagYFdnQaHQH0J28EJqc4jqXOcQbicA==}
    peerDependencies:
      vue: ^3.0.0
  '@parcel/watcher-android-arm64@2.5.4':
    resolution: {integrity: sha512-hoh0vx4v+b3BNI7Cjoy2/B0ARqcwVNrzN/n7DLq9ZB4I3lrsvhrkCViJyfTj/Qi5xM9YFiH4AmHGK6pgH1ss7g==}
@@ -12004,6 +12015,11 @@
    transitivePeerDependencies:
      - magicast
  '@onlyoffice/document-editor-vue@1.6.1(vue@3.5.27(typescript@5.9.3))':
    dependencies:
      lodash: 4.17.21
      vue: 3.5.27(typescript@5.9.3)
  '@parcel/watcher-android-arm64@2.5.4':
    optional: true
pnpm-workspace.yaml
@@ -8,6 +8,7 @@
  "@dvgis/dc-sdk": "3.4.0"
  "@dvgis/vite-plugin-dc": "2.2.0"
  "@element-plus/icons-vue": "^2.3.1"
  "@onlyoffice/document-editor-vue": "^1.6.1"
  "@saber/nf-design-base-elp": "^1.3.0"
  "@saber/nf-form-design-elp": "^1.4.1"
  "@saber/nf-form-elp": "^1.4.4"
uniapps/work-app/src/hooks/useGlobalWS.js
@@ -31,7 +31,8 @@
  const defaultUserId = '2021474815063486465';
  // WebSocket基础URL
  // const WS_BASE = 'wss://wrj.shuixiongit.com/ws/chat?userId=';
  const WS_BASE = 'ws://218.202.104.82:38201/ws/chat';
  // const WS_BASE = 'ws://218.202.104.82:38201/ws/chat';
  const WS_BASE = 'ws://220.177.172.27:8100/webrtc/ws/chat';
    // 获取通讯录数据
  async function fetchContactList() {
uniapps/work-wx/uploadCloudFunction.sh
New file
@@ -0,0 +1 @@
${installPath} cloud functions deploy --e ${envId} --n quickstartFunctions --r --project ${projectPath}