吉安感知网项目-前端
chenyao
2026-01-22 6ed0045e0f379dbd7fdd065a65f8898c482a7cab
Merge remote-tracking branch 'origin/master'
18 files modified
6 files added
743 ■■■■ changed files
applications/drone-command/src/api/logs.js 7 ●●●●● patch | view | raw | blame | history
applications/drone-command/src/page/index/sidebar/sidebarItem.vue 9 ●●●● patch | view | raw | blame | history
applications/drone-command/src/styles/common/cockpit.scss 38 ●●●●● patch | view | raw | blame | history
applications/drone-command/src/views/api.txt 63 ●●●●● patch | view | raw | blame | history
applications/drone-command/src/views/areaManage/partition/FormDiaLog.vue 33 ●●●●● patch | view | raw | blame | history
applications/drone-command/src/views/permissionManage/operationLog/index.vue 145 ●●●●● patch | view | raw | blame | history
applications/task-work-order/src/assets/images/orderView/orderManage/drone.png patch | view | raw | blame | history
applications/task-work-order/src/views/orderView/orderDataManage/appInnovation/FormDiaLog.vue 2 ●●● patch | view | raw | blame | history
applications/task-work-order/src/views/orderView/orderDataManage/dataObjection/FormDiaLog.vue 36 ●●●● patch | view | raw | blame | history
applications/task-work-order/src/views/orderView/orderManage/inspectionRequest/index.vue 7 ●●●●● patch | view | raw | blame | history
applications/task-work-order/src/views/orderView/orderManage/orderManage/FormDiaLog.vue 117 ●●●● patch | view | raw | blame | history
applications/task-work-order/src/views/orderView/orderManage/orderManage/RefuseOrderDialog.vue 27 ●●●● patch | view | raw | blame | history
applications/task-work-order/src/views/orderView/orderManage/orderManage/index.vue 18 ●●●●● patch | view | raw | blame | history
packages/apis/index.js 13 ●●●●● patch | view | raw | blame | history
packages/apis/system/index.js 7 ●●●●● patch | view | raw | blame | history
uniapps/work-app/build/config/proxy.js 5 ●●●●● patch | view | raw | blame | history
uniapps/work-app/env/.env 1 ●●●● patch | view | raw | blame | history
uniapps/work-app/src/api/work/index1.js 33 ●●●●● patch | view | raw | blame | history
uniapps/work-app/src/config/env.js 1 ●●●● patch | view | raw | blame | history
uniapps/work-app/src/hooks/useGlobalWS.js 2 ●●● patch | view | raw | blame | history
uniapps/work-app/src/pages/login/index.vue 57 ●●●●● patch | view | raw | blame | history
uniapps/work-app/src/static/images/work/share.svg 10 ●●●●● patch | view | raw | blame | history
uniapps/work-app/src/subPackages/workDetail/index.vue 4 ●●● patch | view | raw | blame | history
uniapps/work-app/src/utils/requestGd/index.js 108 ●●●●● patch | view | raw | blame | history
applications/drone-command/src/api/logs.js
@@ -62,6 +62,13 @@
    },
  });
};
export const getOperationLogPage = params => {
  return request({
    url: '/blade-log/log/operationLog/page',
    method: 'get',
    params: { descs: 'create_time', ...params },
  });
};
// 历史轨迹记录
export const getHistoryTrack = (params) => {
  return request({
applications/drone-command/src/page/index/sidebar/sidebarItem.vue
@@ -2,7 +2,7 @@
 * @Author       : yuan
 * @Date         : 2026-01-06 09:47:09
 * @LastEditors  : yuan
 * @LastEditTime : 2026-01-07 15:01:16
 * @LastEditTime : 2026-01-22 15:00:07
 * @FilePath     : \applications\drone-command\src\page\index\sidebar\sidebarItem.vue
 * @Description  : 
 * Copyright 2026 OBKoro1, All Rights Reserved. 
@@ -47,6 +47,7 @@
import { mapGetters } from 'vuex';
import { validatenull } from 'utils/validate';
import website from '@/config/website';
import {saveOperationLog} from '@ztzf/apis'
export default {
  name: 'sidebarItem',
@@ -89,6 +90,12 @@
      return item.meta.roles ? item.meta.roles.includes(this.roles) : true;
    },
    open(item) {
      saveOperationLog({
        requestUri: item[this.pathKey],
        title: item.name,
        type: 0
      })
      this.$router.push({
        path: item[this.pathKey],
        query: item[this.queryKey],
applications/drone-command/src/styles/common/cockpit.scss
@@ -204,6 +204,42 @@
                color: #fff !important;
                .el-date-range-picker__time-header {
                    .el-time-panel {
                        background: #1A1A2A !important;
                        border-radius: 0px 0px 8px 8px;
                        border: none !important;
                        // 将 time-picker-popper 的样式移到这里
                        .el-time-panel__header {
                            background: #161B2C !important;
                            color: #ffffff !important;
                            border-bottom: 1px solid rgba(71, 157, 255, 0.3) !important;
                        }
                        .el-time-panel__content {
                            .el-time-spinner__item {
                                &:hover {
                                    background: transparent !important;
                                }
                                &.active {
                                    color: #479dff !important;
                                    font-weight: bold;
                                }
                            }
                        }
                        .el-time-panel__footer {
                            .el-time-panel__btn {
                                color: #fff;
                            }
                            .el-time-panel__btn:first-child {
                                color: #86909C;
                            }
                        }
                    }
                    .el-input__wrapper {
                        box-shadow: none;
                        background: #2E2E48 !important;
@@ -314,7 +350,7 @@
                        td.in-range div:hover,
                        &.is-week-mode .el-date-table__row.current div,
                        &.is-week-mode .el-date-table__row:hover div {
                            background-color: #284FE3 !important;
                            background-color: #4160D5 !important;
                        }
                    }
                }
applications/drone-command/src/views/api.txt
@@ -1,33 +1,22 @@
## 信号干扰,诱导驱离
## 分页
**接口地址**:`/drone-fw/cockpit/dataCockpit/interferenceAndExpulsion`
**接口地址**:`/blade-log/log/operationLog/page`
**请求方式**:`POST`
**请求方式**:`GET`
**请求数据类型**:`application/json`
**请求数据类型**:`application/x-www-form-urlencoded`
**响应数据类型**:`*/*`
**接口描述**:<p>传入fwEffectEval</p>
**接口描述**:<p>分页</p>
**请求示例**:
```javascript
{
  "alarmRecordId": 0,
  "counterWay": ""
}
```
**请求参数**:
@@ -38,9 +27,12 @@
| 参数名称 | 参数说明 | 请求类型    | 是否必须 | 数据类型 | schema |
| -------- | -------- | ----- | -------- | -------- | ------ |
|fwEffectEvalParam|fwEffectEvalParam|body|true|FwEffectEvalParam|FwEffectEvalParam|
|&emsp;&emsp;alarmRecordId|告警记录ID(ja_fw_drone_alarm_record.id)||false|integer(int64)||
|&emsp;&emsp;counterWay|反制方式:1.信号干扰/2.诱导驱离/无||false|string||
|createBy|操作人|query|false|string||
|current|当前页|query|false|integer(int32)||
|endTime|结束时间|query|false|string||
|size|每页的数量|query|false|integer(int32)||
|startTime|开始时间|query|false|string||
|title|日志类型(0菜单/1按钮)|query|false|string||
**响应状态**:
@@ -48,8 +40,7 @@
| 状态码 | 说明 | schema |
| -------- | -------- | ----- | 
|200|OK|R|
|201|Created||
|200|OK|R«IPage«OperationLogVO»»|
|401|Unauthorized||
|403|Forbidden||
|404|Not Found||
@@ -61,7 +52,18 @@
| 参数名称 | 参数说明 | 类型 | schema |
| -------- | -------- | ----- |----- | 
|code|状态码|integer(int32)|integer(int32)|
|data|承载数据|object||
|data|承载数据|IPage«OperationLogVO»|IPage«OperationLogVO»|
|&emsp;&emsp;current||integer(int64)||
|&emsp;&emsp;pages||integer(int64)||
|&emsp;&emsp;records||array|OperationLogVO|
|&emsp;&emsp;&emsp;&emsp;createBy|操作人|string||
|&emsp;&emsp;&emsp;&emsp;remoteIp|客户端IP|string||
|&emsp;&emsp;&emsp;&emsp;requestUri|请求地址|string||
|&emsp;&emsp;&emsp;&emsp;title|日志标题|string||
|&emsp;&emsp;&emsp;&emsp;type|日志类型(0菜单/1按钮)|string||
|&emsp;&emsp;&emsp;&emsp;userAgent|客户端|string||
|&emsp;&emsp;size||integer(int64)||
|&emsp;&emsp;total||integer(int64)||
|msg|返回消息|string||
|success|是否成功|boolean||
@@ -70,7 +72,22 @@
```javascript
{
    "code": 0,
    "data": {},
    "data": {
        "current": 0,
        "pages": 0,
        "records": [
            {
                "createBy": "",
                "remoteIp": "",
                "requestUri": "",
                "title": "",
                "type": "",
                "userAgent": ""
            }
        ],
        "size": 0,
        "total": 0
    },
    "msg": "",
    "success": true
}
applications/drone-command/src/views/areaManage/partition/FormDiaLog.vue
@@ -116,6 +116,7 @@
                        <el-date-picker class="command-date-picker"
                            popper-class="command-date-picker-popper" v-model="flyDateRange"
                            type="datetimerange" range-separator="至" start-placeholder="开始时间" end-placeholder="结束时间"
                            :disabled-date="disabledFlyDate" :disabled-time="disabledFlyTime"
                            value-format="YYYY-MM-DD HH:mm:ss" clearable />
                    </el-form-item>
                    <el-form-item label="关联派出所" prop="policeStationId">
@@ -226,6 +227,38 @@
    deviceIds: fieldRules(false),
}
function isSameDay (left, right) {
    return (
        left.getFullYear() === right.getFullYear() &&
        left.getMonth() === right.getMonth() &&
        left.getDate() === right.getDate()
    )
}
const disabledFlyDate = time => {
    const startOfToday = new Date()
    startOfToday.setHours(0, 0, 0, 0)
    return time.getTime() < startOfToday.getTime()
}
const disabledFlyTime = date => {
    if (!date) return {}
    const now = new Date()
    if (!isSameDay(date, now)) return {}
    const currentHour = now.getHours()
    const currentMinute = now.getMinutes()
    const currentSecond = now.getSeconds()
    return {
        disabledHours: () => Array.from({ length: currentHour }, (_, index) => index),
        disabledMinutes: hour =>
            hour === currentHour ? Array.from({ length: currentMinute }, (_, index) => index) : [],
        disabledSeconds: (hour, minute) =>
            hour === currentHour && minute === currentMinute
                ? Array.from({ length: currentSecond }, (_, index) => index)
                : [],
    }
}
// 关闭弹框
function handleCancel () {
    visible.value = false
applications/drone-command/src/views/permissionManage/operationLog/index.vue
New file
@@ -0,0 +1,145 @@
<template>
    <basic-container>
        <el-form ref="queryParamsRef" :model="searchParams" class="command-page-history-search">
            <el-form-item label="操作名称" prop="title">
                <el-input
                    class="command-input"
                    v-model="searchParams.title"
                    placeholder="请输入"
                    clearable
                    @clear="handleSearch"
                />
            </el-form-item>
            <el-form-item label="操作人" prop="createBy">
                <el-input
                    class="command-input"
                    v-model="searchParams.createBy"
                    placeholder="请输入"
                    clearable
                    @clear="handleSearch"
                />
            </el-form-item>
            <el-form-item label="操作类型" prop="type">
                <el-select
                    class="command-select"
                    popper-class="command-select-popper"
                    v-model="searchParams.type"
                    placeholder="请选择"
                    clearable
                    @change="handleSearch"
                >
                    <el-option v-for="item in typeOptions" :key="item.value" :label="item.label" :value="item.value" />
                </el-select>
            </el-form-item>
            <el-form-item label="操作时间">
                <el-date-picker
                    popper-class="command-date-picker-popper"
                    class="command-date-picker"
                    v-model="dateRange"
                    type="daterange"
                    range-separator="至"
                    start-placeholder="开始日期"
                    end-placeholder="结束日期"
                    value-format="YYYY-MM-DD HH:mm:ss"
                    @change="handleSearch"
                />
            </el-form-item>
            <el-form-item class="history-search-actions">
                <el-button :icon="RefreshRight" @click="resetForm"></el-button>
                <el-button class="search-btn" :icon="Search" @click="handleSearch"></el-button>
            </el-form-item>
        </el-form>
        <div class="command-table-container" v-loading="loading" element-loading-background="rgba(5, 5, 15, 0.6)">
            <div class="command-table-content command-table-content-bg">
                <el-table class="command-table" :data="list">
                    <el-table-column type="index" show-overflow-tooltip width="64" label="序号" />
                    <el-table-column prop="title" show-overflow-tooltip label="操作名称" />
                    <el-table-column prop="createBy" show-overflow-tooltip label="操作人" />
                    <el-table-column prop="type" show-overflow-tooltip label="操作类型">
                        <template v-slot="{ row }">
                            {{ getTypeLabel(row.type) }}
                        </template>
                    </el-table-column>
                    <el-table-column prop="remoteIp" show-overflow-tooltip label="客户端IP" />
                    <el-table-column prop="requestUri" show-overflow-tooltip label="请求地址" />
                    <el-table-column prop="userAgent" show-overflow-tooltip label="客户端信息" />
                </el-table>
            </div>
            <div class="command-table-pagination">
                <el-pagination
                    popper-class="command-select-popper"
                    v-model:current-page="searchParams.current"
                    v-model:page-size="searchParams.size"
                    layout="total, prev, pager, next, sizes"
                    :total="total"
                    @change="getList"
                />
            </div>
        </div>
    </basic-container>
</template>
<script setup>
import { Search, RefreshRight } from '@element-plus/icons-vue'
import { onMounted, ref } from 'vue'
import { dateRangeFormat } from '@ztzf/utils'
import { getOperationLogPage } from '@/api/logs'
const initSearchParams = () => ({
    title: '',
    createBy: '',
    type: '',
    current: 1,
    size: 10,
})
const searchParams = ref(initSearchParams())
const total = ref(0)
const loading = ref(true)
const list = ref([])
const dateRange = ref([])
const queryParamsRef = ref(null)
const typeOptions = [
    { label: '菜单', value: '1' },
    { label: '按钮', value: '2' },
]
async function getList() {
    const range = dateRangeFormat(dateRange.value)
    loading.value = true
    try {
        const params = { ...searchParams.value, startTime: range[0], endTime: range[1] }
        const res = await getOperationLogPage(params)
        list.value = res?.data?.data?.records ?? []
        total.value = res?.data?.data?.total ?? 0
    } finally {
        loading.value = false
    }
}
function handleSearch() {
    searchParams.value.current = 1
    getList()
}
function resetForm() {
    dateRange.value = []
    queryParamsRef.value?.resetFields()
    searchParams.value.current = 1
    getList()
}
function getTypeLabel(value) {
    const key = value === null || value === undefined ? '' : String(value)
    const match = typeOptions.find(item => item.value === key)
    return match?.label || value || '-'
}
onMounted(() => {
    getList()
})
</script>
<style scoped lang="scss"></style>
applications/task-work-order/src/assets/images/orderView/orderManage/drone.png
applications/task-work-order/src/views/orderView/orderDataManage/appInnovation/FormDiaLog.vue
@@ -8,7 +8,7 @@
        :close-on-click-modal="false"
    >
        <div class="detail-container" v-if="dialogReadonly">
            <el-row>
            <el-row class="detail-row-view">
                <el-col :span="12">
                    <div class="label">案例名称</div>
                    <div class="val">{{ formData.caseName }}</div>
applications/task-work-order/src/views/orderView/orderDataManage/dataObjection/FormDiaLog.vue
@@ -425,19 +425,38 @@
    if (!formData.value.id) return
    const res = await gdDataObjectionDetailApi({ id: formData.value.id })
    formData.value = res?.data?.data ?? {}
    if (formData.value.attachIds) {
        getAttachDetail(formData.value.attachIds)
    }
}
// 获取附件详情
async function getAttachDetail() {
    if (!formData.value.id) return
    const res = await getAttachDetailApi({ id: formData.value.id })
    console.log('附件详情',res.data.data)
    attachmentDetailsList.value=res.data.data
async function getAttachDetail(attachIds) {
    // 如果 attachIds 是字符串,尝试分割成数组
    const ids = typeof attachIds === 'string' ? attachIds.split(',') : Array.isArray(attachIds) ? attachIds : [attachIds]
    const detailPromises = ids.map(id => getAttachDetailApi({ id }))
    const results = await Promise.all(detailPromises)
    attachmentDetailsList.value = results.map(res => res.data.data).filter(Boolean)
    console.log('附件详情', attachmentDetailsList.value)
}
// 处理附件下载
async function handleDownloadAttach() {
console.log('附件');
    if (attachmentDetailsList.value.length === 0) {
        ElMessage.warning('没有可下载的附件')
        return
    }
    // 遍历所有附件并下载
    attachmentDetailsList.value.forEach(attach => {
        if (attach.link) {
            const link = document.createElement('a')
            link.href = attach.link
            link.download = attach.originalName || attach.name
            link.style.display = 'none'
            document.body.appendChild(link)
            link.click()
            document.body.removeChild(link)
        }
    })
}
@@ -453,7 +472,6 @@
    feedbackFormData.value = initFeedbackForm()
    if (dialogMode.value !== 'add') {
        await loadDetail()
        await getAttachDetail()
    }
}
applications/task-work-order/src/views/orderView/orderManage/inspectionRequest/index.vue
@@ -68,8 +68,7 @@
        </el-form>
        <div class="gd-table-toolbar">
            <el-button color="#F2F3F5" @click="requester = !requester">我是{{ requester ? '需求方' : '服务方' }}</el-button>
<!--            <el-button color="#F2F3F5" @click="requester = !requester">我是{{ requester ? '需求方' : '服务方' }}</el-button>-->
            <el-button :icon="Plus" color="#4C34FF" type="primary" @click="openForm('add')">新增</el-button>
            <!--            <el-button :icon="Delete" color="#4C34FF" :disabled="!selectedIds.length" @click="handleDelete()">删除</el-button>-->
        </div>
@@ -129,7 +128,10 @@
import FormDiaLog from './FormDiaLog.vue'
import { gdPatrolTaskPageApi, gdPatrolTaskRemoveApi } from './inspectionRequestApi'
import ViewDiaLog from '@/views/orderView/orderManage/inspectionRequest/ViewDiaLog.vue'
import { useStore } from 'vuex'
const store = useStore()
const requester = computed(() => store.state.user.userInfo?.role_id === '2014158512610869250')
// 初始化查询参数
const initSearchParams = () => ({
    patrolTaskName: '', // 任务名称
@@ -151,7 +153,6 @@
const viewDiaLogRef = ref(null)
const dialogVisible = ref(false)
const viewDiaLogVisible = ref(false)
const requester = ref(true)
const dictObj = ref({
    patrolTaskType: [], // 巡查任务类型
    workOrderType: [], // 工单类型
applications/task-work-order/src/views/orderView/orderManage/orderManage/FormDiaLog.vue
@@ -9,7 +9,7 @@
        :close-on-click-modal="false"
    >
        <div class="content" style="display: flex">
            <div class="processBox" v-if="dialogMode !== 'add'">
            <div class="processBox" v-if="dialogMode !== 'add' && processList.length">
                <el-steps direction="vertical" :active="processList.length">
                    <el-step v-for="item in processList" :title="item.flowName" :description="item.flowDesc" />
                </el-steps>
@@ -72,7 +72,6 @@
                            </el-table-column>
                        </el-table>
                    </template>
                </div>
                <el-form
                    class="gd-dialog-form"
@@ -129,6 +128,7 @@
                                    v-model="formData.deviceLoadDemand"
                                    placeholder="请选择"
                                    clearable
                                    @change="loadDemandChange"
                                >
                                    <el-option
                                        v-for="item in dictObj.deviceLoadDemand"
@@ -161,12 +161,20 @@
            </div>
            <div class="rightBox" v-if="!dialogReadonly">
                <div class="detail-title">推荐设备</div>
                <el-table ref="deviceTableRef" :data="deviceList" row-key="id" @selection-change="handleDeviceSelectionChange">
                    <el-table-column type="selection" width="55" :reserve-selection="true" />
                    <el-table-column prop="nickname" label="设备名称" />
                    <el-table-column prop="deviceName" label="设备型号" />
                    <el-table-column prop="devicePayload" label="设备负载" />
                </el-table>
                <template v-if="formData.deviceLoadDemand && pointList.length">
                    <el-table
                        ref="deviceTableRef"
                        :data="deviceList"
                        row-key="id"
                        @selection-change="handleDeviceSelectionChange"
                    >
                        <el-table-column type="selection" width="55" :reserve-selection="true" />
                        <el-table-column prop="nickname" label="设备名称" />
                        <el-table-column prop="deviceName" label="设备型号" />
                        <el-table-column prop="devicePayload" label="设备负载" />
                    </el-table>
                </template>
                <div v-else>请先选择设备需求并绘制范围</div>
            </div>
        </div>
@@ -194,14 +202,14 @@
                    >
                        发布
                    </el-button>
                    <el-button color="#F2F3F5" v-if="gdStatus === '11'">拒单原因</el-button>
                    <el-button color="#F2F3F5" v-if="gdStatus === '11'" @click="addDescription(2, 'view')">拒单原因</el-button>
                    <el-button color="#F2F3F5" @click="statusChange(3)" v-if="gdStatus === '20'">申请取消</el-button>
                    <el-button color="#4C34FF" @click="requestModification" v-if="gdStatus === '20'" class="save-btn">
                        申请修改
                    </el-button>
                </template>
                <template v-else>
                    <el-button color="#F2F3F5" v-if="gdStatus === '10'" @click="rejectOrders">拒绝接单</el-button>
                    <el-button color="#F2F3F5" v-if="gdStatus === '10'" @click="addDescription(2)">拒绝接单</el-button>
                    <el-button color="#4C34FF" v-if="gdStatus === '10'" @click="statusChange(1)" class="save-btn">接单</el-button>
                    <el-button color="#F2F3F5" v-if="gdStatus === '22'" @click="statusChange(7)">同意修改</el-button>
                    <el-button color="#4C34FF" v-if="gdStatus === '22'" @click="statusChange(8)" class="save-btn">
@@ -211,6 +219,7 @@
                    <el-button color="#4C34FF" v-if="gdStatus === '21'" @click="statusChange(6)" class="save-btn">
                        不同意取消
                    </el-button>
                    <el-button color="#F2F3F5" v-if="gdStatus === '30'" @click="addDescription(9)">协商修改</el-button>
                </template>
            </template>
        </template>
@@ -275,9 +284,13 @@
const refuseOrderDialogRef = ref(null)
let viewer
let drawPolygonExample
let pointList = []
let pointList = ref([])
const requester = inject('requester')
const editStatus = inject('editStatus')
const suddenlyEdit = ref(false)
const processList = ref([])
let viewPlane
const hasPatrolTaskList = computed(() => ['30', '40', '50', '60'].includes(String(formData.value.workOrderStatus)))
// 工单状态:0草稿、10发布中_接单中、11发布中_拒绝接单、20响应中_待拆分、21响应中_申请取消、22响应中_申请修改、23响应中_已取消、
// 24响应中_拒绝取消、25响应中_拒绝修改 30执行中_待全部完成、31执行中_协商修改、40完成待验_待全部验收、50验收通过_待结算、60结算完成_已结算
@@ -289,7 +302,7 @@
    executeStartTime: fieldRules(true),
    deviceLoadDemand: fieldRules(true),
}
// 操作类型:1接单,2拒接接单,3申请取消,4申请修改, 5同意取消 6不同意取消 7.同意修改 7.不同意修改
// 操作类型:1接单,2拒接接单,3申请取消,4申请修改, 5同意取消 6不同意取消 7.同意修改 8.不同意修改 9.协商修改
function statusChange(operationType) {
    gdWorkOrderHandleStatusApi({ operationType: operationType, workOrderId: formData.value.id }).then(res => {
        visible.value = false
@@ -297,7 +310,6 @@
    })
}
const suddenlyEdit = ref(false)
function requestModification() {
    suddenlyEdit.value = true
    dialogMode.value = 'edit'
@@ -313,12 +325,24 @@
    emit('success')
}
function rejectOrders() {
// 填写说明
function addDescription(type, mode = 'add') {
    let formLabel = ''
    switch (type) {
        case 2:
            formLabel = '拒绝原因'
            break
        case 9:
            formLabel = '修改原因'
            break
    }
    rejectVisible.value = true
    nextTick(() => {
        refuseOrderDialogRef.value.open({
            mode: 'add',
            row: { workOrderId: formData.value.id },
            mode,
            row: formData.value,
            type,
            formLabel,
        })
    })
}
@@ -326,11 +350,23 @@
// 获取推荐设备列表
async function getDeviceList() {
    try {
        const res = await gdManageDeviceListApi({ current: 1, size: 1000, ids: formData.value.recommendDeviceIds })
        const str = [...pointList.value, pointList.value[0]].map(item => `${item.longitude} ${item.latitude}`).join(',')
        let geom = `POLYGON((${str}))`
        const res = await gdManageDeviceListApi({
            ids: formData.value.recommendDeviceIds,
            devicePayload: dialogMode.value === 'add' ? formData.value.deviceLoadDemand : '',
            geom: dialogMode.value === 'add' ? geom : '',
        })
        deviceList.value = res?.data?.data ?? []
    } catch (error) {
        console.error('获取设备列表失败:', error)
    }
}
function loadDemandChange() {
    formData.value.recommendDeviceIds = ''
    getDeviceList()
    syncSelection()
}
// 设备选择变化
@@ -343,13 +379,13 @@
async function handleSubmit() {
    const isValid = await formRef.value?.validate().catch(() => false)
    if (!isValid) return
    if (!pointList.length) {
    if (!pointList.value.length) {
        ElMessage.warning('请绘制范围')
        return
    }
    submitting.value = true
    try {
        const str = [...pointList, pointList[0]].map(item => `${item.longitude} ${item.latitude}`).join(',')
        const str = [...pointList.value, pointList.value[0]].map(item => `${item.longitude} ${item.latitude}`).join(',')
        formData.value.geom = `POLYGON((${str}))`
        if (!formData.value.id) {
            formData.value.workOrderStatus = '10'
@@ -372,7 +408,6 @@
    }
)
const processList = ref([])
function loadList() {
    gdWorkOrderFlowListApi({ workOrderId: formData.value.id }).then(res => {
        processList.value = res.data.data
@@ -397,19 +432,21 @@
// 绘制完成回调
const drawFinished = async data => {
    pointList = _.cloneDeep(data).map(item => {
    pointList.value = _.cloneDeep(data).map(item => {
        const val = cartesian3Convert(item, viewer)
        return { ...val, lng: val.longitude, lat: val.latitude }
    })
    await getDeviceList()
    syncSelection()
}
// 编辑面
function editPolygon() {
    if (!formData.value?.geom) return
    pointList = geomAnalysis(formData.value.geom)
    pointList.value = geomAnalysis(formData.value.geom)
    drawPolygonExample?.destroy()
    drawPolygonExample = new DrawPolygon(viewer)
    let noClosedList = _.cloneDeep(pointList)
    let noClosedList = _.cloneDeep(pointList.value)
    noClosedList.pop()
    drawPolygonExample.initPolygon(
        viewer,
@@ -418,12 +455,11 @@
    drawPolygonExample.subscribe('getPolygonPositions', drawFinished)
}
let viewPlane
// 查看面
function viewPolygon() {
    if (!formData.value?.geom) return
    pointList = geomAnalysis(formData.value.geom)
    const result = pointList.map(item => [item.longitude, item.latitude]).flat()
    pointList.value = geomAnalysis(formData.value.geom)
    const result = pointList.value.map(item => [item.longitude, item.latitude]).flat()
    viewPlane = viewer.entities?.add({
        customType: 'control_group',
        position: Cesium.Cartesian3.fromDegrees(result[0], result[1]),
@@ -435,7 +471,7 @@
        },
    })
    flyVisual({
        positionsData: pointList.map(i => [i.longitude, i.latitude, i.height || 0]),
        positionsData: pointList.value.map(i => [i.longitude, i.latitude, i.height || 0]),
        viewer,
    })
}
@@ -452,11 +488,33 @@
    })
}
const hasPatrolTaskList = computed(() => ['30','40','50','60'].includes(String(formData.value.workOrderStatus)))
// 获取巡检任务
function gdPatrolTaskPage() {
    gdPatrolTaskPageApi({ current: 1, size: 1000, workOrderId: formData.value.id }).then(res => {
        patrolTaskList.value = res.data.data.records || []
    })
}
function setMapDevice() {
    gdManageDeviceListApi().then(res => {
        renderingDevice(res?.data?.data || [])
    })
}
import droneIcon from '@/assets/images/orderView/orderManage/drone.png'
function renderingDevice(list) {
   // 渲染红点
    list.forEach(item => {
        const position = Cesium.Cartesian3.fromDegrees(item.longitude || 115.00, item.latitude || 27.11)
        viewer.entities.add({
            position: position,
            billboard: {
                image: droneIcon,
                width: 40,
                height: 40,
                verticalOrigin: Cesium.VerticalOrigin.CENTER,
            },
        })
    })
}
@@ -473,6 +531,7 @@
        dialogMode.value === 'edit' ? editPolygon() : viewPolygon()
    }
    await getDeviceList()
    // setMapDevice()
    await nextTick()
    syncSelection()
}
applications/task-work-order/src/views/orderView/orderManage/orderManage/RefuseOrderDialog.vue
@@ -2,12 +2,21 @@
    <el-dialog
        class="gd-dialog"
        v-model="visible"
        :title="titleEnum[dialogMode]"
        title="编辑"
        @closed="visible = false"
        destroy-on-close
        :close-on-click-modal="false"
    >
        <div v-if="dialogReadonly">
            <el-row class="detail-row-view">
                <el-col :span="24">
                    <div class="label">{{formLabelStr}}:</div>
                    <div class="val">{{ formData.rejectReason || '' }}</div>
                </el-col>
            </el-row>
        </div>
        <el-form
            v-else
            class="gd-dialog-form"
            ref="formRef"
            :model="formData"
@@ -17,7 +26,7 @@
        >
            <el-row>
                <el-col :span="24">
                    <el-form-item label="拒绝原因" prop="rejectReason">
                    <el-form-item :label="formLabelStr" prop="rejectReason">
                        <el-input
                            class="gd-input"
                            v-model="formData.rejectReason"
@@ -49,12 +58,14 @@
<script setup>
import { computed, ref } from 'vue'
import { ElMessage } from 'element-plus'
import { fieldRules } from '@ztzf/utils'
import { fieldRules, getDictLabel } from '@ztzf/utils'
import { gdWorkOrderHandleStatusApi } from '@/views/orderView/orderManage/orderManage/orderManageApi'
import { pxToRem } from '@/utils/rem'
import dayjs from 'dayjs'
// 初始化表单数据
const initForm = () => ({
    operationType: 2,
    operationType: null,
    rejectReason: '',
    workOrderId: null,
})
@@ -79,7 +90,7 @@
    submitting.value = true
    gdWorkOrderHandleStatusApi(formData.value)
        .then(res => {
            ElMessage.success('拒绝接单成功')
            ElMessage.success('提交成功')
            visible.value = false
            emit('success')
        })
@@ -88,10 +99,12 @@
        })
}
let formLabelStr = ref('原因')
// 打开弹框
async function open({ mode = 'add', row } = {}) {
async function open({ mode = 'add', row, formLabel, type } = {}) {
    dialogMode.value = mode
    formData.value = {...formData.value,...row}
    formLabelStr.value = formLabel
    formData.value = { workOrderId: row.id, operationType: type, rejectReason: row.rejectReason }
}
defineExpose({ open })
applications/task-work-order/src/views/orderView/orderManage/orderManage/index.vue
@@ -1,6 +1,6 @@
<template>
    <basic-container>
        <el-tabs v-model="activeName" class="demo-tabs" @tab-click="handleClick">
        <el-tabs class="gd-tabs" v-model="activeName" @tab-click="tabsClick">
            <el-tab-pane label="全部 " name="all"></el-tab-pane>
            <el-tab-pane label="我的任务工单" name="my"></el-tab-pane>
        </el-tabs>
@@ -73,10 +73,10 @@
        <div class="gd-table-toolbar">
<!--            <el-button color="#F2F3F5" @click="requester = !requester">我是{{ requester ? '需求方' : '服务方' }}</el-button>-->
            <el-button color="#F2F3F5" @click="previewVisible = true">预览</el-button>
<!--            <el-button color="#F2F3F5" @click="previewVisible = true">预览</el-button>-->
            <el-button :icon="Plus" color="#4C34FF" type="primary" @click="openForm('add')">新增</el-button>
            <el-button :icon="Delete" color="#4C34FF" :disabled="!selectedIds.length" @click="handleDelete()">删除</el-button>
            <el-button :icon="Plus" color="#4C34FF" type="primary" @click="openForm('add')" v-if="requester">新增</el-button>
            <el-button :icon="Delete" color="#4C34FF" :disabled="!selectedIds.length" @click="handleDelete()" v-if="requester">删除</el-button>
        </div>
        <div class="gd-table-container" v-loading="loading">
@@ -108,13 +108,13 @@
                        </template>
                    </el-table-column>
                    <el-table-column prop="serviceParty" show-overflow-tooltip label="服务方名称" />
                    <el-table-column prop="createDeptName" show-overflow-tooltip label="需求方名称" />
                    <el-table-column prop="createUserName" show-overflow-tooltip label="需求方名称" />
                    <el-table-column prop="createTime" show-overflow-tooltip label="工单创建时间" />
                    <el-table-column label="操作" class-name="operation-btns">
                        <template v-slot="{ row }">
                            <el-link @click="openFormChange(row)">查看</el-link>
                            <!--<el-link @click="openForm('edit', row)">编辑</el-link>-->
                            <el-link @click="handleDelete(row)">删除</el-link>
                            <el-link @click="handleDelete(row)" v-if="requester">删除</el-link>
                        </template>
                    </el-table-column>
                </el-table>
@@ -193,7 +193,7 @@
            ...searchParams.value,
            startTime: range[0],
            endTime: range[1],
            createUser: activeName.value === 'user' ? store.state.user.userInfo.user_id : '',
            createUser: activeName.value === 'my' ? store.state.user.userInfo.user_id : '',
        })
        list.value = res?.data?.data?.records ?? []
        total.value = res?.data?.data?.total ?? 0
@@ -216,7 +216,9 @@
    getList()
}
function handleClick(tab,event) {
function tabsClick(tab, event) {
    activeName.value = tab.props.name
    resetForm()
    handleSearch()
}
packages/apis/index.js
@@ -1 +1,12 @@
export * from './job';
/*
 * @Author       : yuan
 * @Date         : 2026-01-07 11:04:47
 * @LastEditors  : yuan
 * @LastEditTime : 2026-01-22 14:50:34
 * @FilePath     : \packages\apis\index.js
 * @Description  :
 * Copyright 2026 OBKoro1, All Rights Reserved.
 * 2026-01-07 11:04:47
 */
export * from './job'
export * from './system'
packages/apis/system/index.js
New file
@@ -0,0 +1,7 @@
export const saveOperationLog = (data) => {
    return window._request({
        url: `/blade-log/log/operationLog/save`,
        method: 'post',
        data,
    })
}
uniapps/work-app/build/config/proxy.js
@@ -20,6 +20,11 @@
      changeOrigin: true,
      rewrite: path => path.replace(new RegExp(`^${VITE_API_PREFIX}`), ''),
    },
    [VITE_API_PREFIX_GD]: {
      target: configEnv?.[envName || VITE_APP_ENV]?.VITE_API_BASE_URL_GD,
      changeOrigin: true,
      rewrite: path => path.replace(new RegExp(`^${VITE_API_PREFIX_GD}`), ''),
    },
  }
  return proxy
}
uniapps/work-app/env/.env
@@ -12,6 +12,7 @@
# API代理前缀
VITE_API_PREFIX=/api
VITE_API_PREFIX_GD=/api-gd
# 删除console
VITE_DROP_CONSOLE=false
uniapps/work-app/src/api/work/index1.js
New file
@@ -0,0 +1,33 @@
import { request } from "@/utils/requestGd/index.js"
import website from '@/config/website'
// 获取工单列表
export const getGdList = data => {
    return request({
        url: '/system/user/profile/update',
        method: 'post',
        data,
    });
};
// 工单用户登录接口
export const ticketLoginInterfaceApi = (tenantId, deptId, roleId, username, password, type, key, code) => {
    return request({
        url: '/blade-auth/oauth/token',
        method: 'post',
        header: {
            'Tenant-Id': tenantId,
            'Dept-Id': website.switchMode ? deptId : '',
            'Role-Id': website.switchMode ? roleId : '',
            'Captcha-Key': key,
            'Captcha-Code': code,
        },
        params: {
            tenantId,
            username,
            password,
            grant_type: 'password',
            scope: 'all',
            type,
        },
    })
}
uniapps/work-app/src/config/env.js
@@ -15,6 +15,7 @@
  VITE_APP_WEBVIEW_URL: 'https://wrj.shuixiongit.com/drone-app-web-view/#/webViewWrapper',
  // VITE_APP_WEBVIEW_URL: 'https://192.168.1.157:5176/mobile-web-view/#/webViewWrapper',
  VITE_API_BASE_URL: 'http://218.202.104.82:8200',
  VITE_API_BASE_URL_GD: 'https://wrj.shuixiongit.com/api',
  // VITE_API_BASE_URL: 'https://aisky.org.cn/api',
  VITE_APP_WS_API_URL:'wss://wrj.shuixiongit.com/drone-wss/api/v1/ws',
uniapps/work-app/src/hooks/useGlobalWS.js
@@ -14,7 +14,7 @@
    // 消息处理
  function messageHandler(payload) {
    console.log('🌐 全局WebSocket收到消息111111111111111111111:', payload)
    // console.log('🌐 全局WebSocket收到消息111111111111111111111:', payload)
    // 先尝试直接处理消息(适用于mobile-web-view的voiceCallDetail页面的消息格式)
    const t = (payload.type || '').toString()
    // const bizCode = payload.biz_code || ''
uniapps/work-app/src/pages/login/index.vue
@@ -42,6 +42,7 @@
<script setup>
import { getAssetsImage } from "@/utils/index.js";
import {ticketLoginInterfaceApi} from '@/api/work/index1.js'
import md5 from "js-md5";
import { loginByUsername } from "@/api/user/index.js";
import { useUserStore, useLocationStore } from "@/store/index.js";
@@ -86,8 +87,13 @@
    url: '/subPackages/userDetail/password/index'
  });
}
const loginFormGd = ref({
  username: "zhx",
  password: "123456",
});
async function submit() {
  // 调用工单登录接口
  await gdLogin();
  if (!loginForm.value.username.trim()) {
    uni.showToast({
      title: "请输入用户名",
@@ -137,7 +143,7 @@
      userInfo.key,
      userInfo.code
    );
    userStore.setUserInfo(res.data.data);
    userStore.setUserInfo(res.data.data);
    // 请求位置权限并初始化位置服务
    try {
      // 请求位置权限
@@ -152,7 +158,9 @@
    } catch (error) {
      console.error('处理位置服务失败:', error);
    }
    uni.reLaunch({
      url: "/pages/work/index",
    });
@@ -168,9 +176,50 @@
      icon: "none",
      duration: 2000,
    });
    // 即使原始登录失败,也尝试调用工单登录接口
    await gdLogin();
  }
}
// 工单登录
async function gdLogin() {
  let userInfo = {
    tenantId: "000000",
    deptId: "",
    roleId: "",
    username: loginFormGd.value.username,
    password: loginFormGd.value.password,
    type: "account",
    code: "",
    key: "",
  };
  try {
    const res = await ticketLoginInterfaceApi(
      userInfo.tenantId,
      userInfo.deptId,
      userInfo.roleId,
      userInfo.username,
      md5(userInfo.password),
      userInfo.type,
      userInfo.key,
      userInfo.code
    );
    console.log('工单登录',res)
    // 存储工单token到用户信息中
    if (res.data?.access_token) {
      const currentUserInfo = userStore.$state.userInfo;
      userStore.setUserInfo({
        ...currentUserInfo,
        gd_access_token: res.data.access_token
      });
    }
  } catch (error) {
    const errorMsg =
      error.data?.error_description !== "Bad credentials"
        ? error.data?.error_description
        : "登录失败,请重试";
    console.error('工单登录失败:', errorMsg);
  }
}
// 从本地存储加载记住的密码
onMounted(() => {
  const savedUserInfo = uni.getStorageSync("rememberedUser");
uniapps/work-app/src/static/images/work/share.svg
New file
@@ -0,0 +1,10 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="interactive-button/reply" clip-path="url(#clip0_67_2892)">
<path id="Vector" fill-rule="evenodd" clip-rule="evenodd" d="M12.0021 2.91712C11.4337 2.31088 10.4167 2.71307 10.4167 3.54407L10.4167 6.25C5.58418 6.25 1.66667 10.1675 1.66667 15C1.66667 15.069 1.66747 15.1378 1.66906 15.2063C1.68601 15.9378 2.5645 16.2952 3.0882 15.7893C4.66313 14.268 6.80489 13.3333 9.16667 13.3333C9.59145 13.3333 10.0088 13.3635 10.4167 13.4218V16.4559C10.4167 17.2869 11.4337 17.6891 12.0021 17.0829L17.8408 10.8549C18.2916 10.3741 18.2916 9.6259 17.8408 9.14507L12.0021 2.91712ZM12.0833 5.44065L16.3577 10L12.0833 14.5594V12.0583L11.4304 11.9135C10.7008 11.7518 9.94319 11.6667 9.16667 11.6667C7.09177 11.6667 5.15777 12.2739 3.53383 13.3197C4.28823 10.2188 7.08361 7.91667 10.4167 7.91667C10.6691 7.91667 10.9182 7.92984 11.1633 7.95548L12.0833 8.05173V5.44065Z" fill="#4E5969"/>
</g>
<defs>
<clipPath id="clip0_67_2892">
<rect width="20" height="20" fill="white" transform="matrix(-1 0 0 1 20 0)"/>
</clipPath>
</defs>
</svg>
uniapps/work-app/src/subPackages/workDetail/index.vue
@@ -4,7 +4,7 @@
        <!-- 自定义导航栏 -->
        <u-navbar title="工单详情" :is-back="true" back-text="" :back-icon-size="40" @left-click="onBackClick">
            <template #right>
                <div class="share-btn" @click="onShareClick">分享</div>
                <div class="share-btn" @click="onShareClick"><img src="@/static/images/work/share.svg" alt=""></div>
            </template>
        </u-navbar>
@@ -427,6 +427,8 @@
          font-weight: 400;
          font-size: 28rpx;
          color: #1d6fe9;
          text-decoration: underline;
          text-align: right;
          //white-space: nowrap;
          ///* 禁止换行 */
          //overflow: hidden;
uniapps/work-app/src/utils/requestGd/index.js
New file
@@ -0,0 +1,108 @@
import Request from "uview-plus/libs/luch-request/index"
import configEnv from "@/config/env.js";
import {getEnvObj} from "@/utils/index.js";
import { useUserStore } from "@/store/index.js";
import website from '@/config/website'
import { Base64} from 'js-base64'
const http = new Request()
export function request (config) {
  return new Promise((resolve, reject) => {
    http
      .request(config)
      .then(res => {
        resolve(res)
      })
      .catch(err => {
        reject(err)
      })
  })
}
// 请求拦截器
function requestInterceptors(http) {
  http.interceptors.request.use((config) => {
    const {detail} = useUserStore().$state?.userInfo || {}
    // 假设有token值需要在头部需要携带
    let accessToken = useUserStore()?.$state?.userInfo?.gd_access_token;
    if (accessToken) {
      config.header['Blade-Auth'] = 'bearer ' + accessToken;
    }
    if (detail?.areaCode) {
      config.header['areaCode'] = detail.areaCode
    }
    // 安全请求header
    config.header['Blade-Requested-With'] = 'BladeHttpRequest';
    // 客户端认证参数
    config.header['Authorization'] = 'Basic ' + Base64.encode(website.clientId + ':' + website.clientSecret);
    return config
  }, config => { // 可使用async await 做异步操作
    return Promise.reject(config)
  })
}
function responseInterceptors(http) {
    http.interceptors.response.use((response) => {
        let res = response
        const status = res.data.error_code || res.data.code || res.statusCode
        const message = res?.data?.msg || res?.data?.error_description || res?.data?.message || '系统错误'
        if (status !== 200) {
            uni.showToast({title: message, icon: 'none'});
            return Promise.reject(response);
        }
        return response;
    }, (response) => {
        let res = response
        const message = res?.data?.msg || res?.data?.error_description || res?.data?.message || '系统错误'
        /*  对响应错误做点什么 (statusCode !== 200)*/
        uni.showToast({title: message, icon: 'none'});
        if (response.statusCode === 401) {
            const pages = getCurrentPages()
            const currentPage = pages[pages.length - 1]
            uni.reLaunch({
                url: `/pages/login/index?redirect=/${currentPage.route}`
            })
        }
        return Promise.reject(response)
    })
}
// 引入拦截器配置
export function setupRequest () {
  http.setConfig(defaultConfig => {
    /* defaultConfig 为默认全局配置 */
    defaultConfig.baseURL = getEnvObj().VITE_API_BASE_URL_GD
    // #ifdef H5
    if (import.meta.env.VITE_APP_PROXY === "true") {
      defaultConfig.baseURL = import.meta.env.VITE_API_PREFIX_GD
    }
    // #endif
    return defaultConfig
  })
  requestInterceptors(http)
  responseInterceptors(http)
}
export function get (url, config) {
  return request({ ...config, url, method: "GET" })
}
export function post (url, config) {
  return request({ ...config, url, method: "POST" })
}
export function upload (url, config) {
  return request({ ...config, url, method: "UPLOAD" })
}
export function download (url, config) {
  return request({ ...config, url, method: "DOWNLOAD" })
}
export default setupRequest