吉安感知网项目-前端
chenyao
10 hours ago 150ba60e3436b3e6a418b0a66723938b5dc2cfb8
feat:处置功能
6 files modified
7 files added
3627 ■■■■ changed files
applications/task-work-order/src/views/orderView/orderManage/clueEvents/DistributeDiaLog.vue 14 ●●●● patch | view | raw | blame | history
applications/task-work-order/src/views/orderView/orderManage/clueEvents/ViewDiaLog.vue 19 ●●●● patch | view | raw | blame | history
applications/task-work-order/src/views/orderView/orderManage/clueEvents/index.vue 8 ●●●● patch | view | raw | blame | history
applications/task-work-order/src/views/orderView/orderManage/eventManage/OrderStepBar.vue 225 ●●●●● patch | view | raw | blame | history
applications/task-work-order/src/views/orderView/orderManage/eventManage/ViewDiaLog.vue 343 ●●●●● patch | view | raw | blame | history
applications/task-work-order/src/views/orderView/orderManage/eventManage/eventManageApi.js 25 ●●●●● patch | view | raw | blame | history
applications/task-work-order/src/views/orderView/orderManage/eventManage/index.vue 231 ●●●●● patch | view | raw | blame | history
uniapps/work-app/src/api/work/index.js 82 ●●●●● patch | view | raw | blame | history
uniapps/work-app/src/pages/work/index.vue 643 ●●●● patch | view | raw | blame | history
uniapps/work-app/src/subPackages/workDetail/HandleWork/index.vue 295 ●●●●● patch | view | raw | blame | history
uniapps/work-app/src/subPackages/workDetail/HandleWorking/index-copy.vue 470 ●●●●● patch | view | raw | blame | history
uniapps/work-app/src/subPackages/workDetail/HandleWorking/index.vue 381 ●●●●● patch | view | raw | blame | history
uniapps/work-app/src/subPackages/workDetail/index.vue 891 ●●●●● patch | view | raw | blame | history
applications/task-work-order/src/views/orderView/orderManage/clueEvents/DistributeDiaLog.vue
@@ -12,7 +12,13 @@
            <el-row>
                <el-col :span="24">
                    <el-form-item label="事件名称" prop="eventName">
                        <el-input class="gd-input" v-model="formData.eventName" placeholder="请输入" clearable />
                        <el-input
                            :disabled="distributeStatus === 2"
                            class="gd-input"
                            v-model="formData.eventName"
                            placeholder="请输入"
                            clearable
                        />
                    </el-form-item>
                </el-col>
                <el-col :span="24">
@@ -79,6 +85,7 @@
const userList = ref([])
const currentRow = ref(null) // 当前线索数据
const workOrderId = ref(null) // 工单ID
const distributeStatus = ref(null)
// 校验规则
const rules = {
@@ -119,7 +126,7 @@
            eventName: formData.value.eventName,
            latitude: currentRow.value.latitude || 0,
            longitude: currentRow.value.longitude || 0,
            resultId: currentRow.value.id,
            resultId: currentRow.value.resultId ? currentRow.value.resultId : currentRow.value.id,
            workOrderId: workOrderId.value,
        }
        await gdTaskResultDistributeApi(params)
@@ -139,9 +146,12 @@
// 打开弹框
async function open({ row, workOrderId: orderId } = {}) {
    console.log('333',row)
    currentRow.value = row
    workOrderId.value = orderId
    formData.value = initForm()
    formData.value.eventName = row.eventName
    distributeStatus.value = row.distributeStatus || row.eventStatus
}
defineExpose({ open })
applications/task-work-order/src/views/orderView/orderManage/clueEvents/ViewDiaLog.vue
@@ -1,5 +1,5 @@
<template>
    <el-dialog class="gd-dialog" v-model="visible" title="查看" @closed="visible = false" width="1000px" destroy-on-close>
    <el-dialog class="gd-dialog" v-model="visible" title="查看" @closed="handleClose" width="1000px" destroy-on-close>
        <div v-loading="loading">
            <el-table :data="list" class="gd-dialog-table">
                <el-table-column label="线索缩略图" width="120">
@@ -60,12 +60,7 @@
        />
    </el-dialog>
    <VideoPlayDialog
        ref="videoPlayDialogRef"
        v-if="VideoShow"
        v-model="VideoShow"
        :playUrl="currentVideo.resultUrl"
    />
    <VideoPlayDialog ref="videoPlayDialogRef" v-if="VideoShow" v-model="VideoShow" :playUrl="currentVideo.resultUrl" />
</template>
<script setup>
@@ -83,6 +78,7 @@
const permission = computed(() => store.state.user.permission)
console.log(permission.value.clueEvents_distribute, 'permission')
const emit = defineEmits(['success'])
const visible = defineModel()
const loading = ref(false)
const list = ref([])
@@ -119,11 +115,11 @@
    if (!currentRow.value?.id) return
    loading.value = true
    try {
        const res = await gdTaskResultListApi({ patrolTaskId: currentRow.value.id})
        const res = await gdTaskResultListApi({ patrolTaskId: currentRow.value.id })
        list.value = await Promise.all(
            (res?.data?.data ?? []).map(async item => {
                if (item.attachmentType !== 2) return item
                const aiImg = await getAiImg(item.resultUrl,item.geojson)
                const aiImg = await getAiImg(item.resultUrl, item.geojson)
                return { ...item, aiImg }
            })
        )
@@ -155,6 +151,11 @@
    await getList()
}
function handleClose() {
    emit('success')
    visible = false
}
defineExpose({ open })
</script>
applications/task-work-order/src/views/orderView/orderManage/clueEvents/index.vue
@@ -105,7 +105,7 @@
            </div>
        </div>
        <ViewDiaLog ref="viewDiaLogRef" v-if="viewDiaLogVisible" v-model="viewDiaLogVisible" />
        <ViewDiaLog ref="viewDiaLogRef" v-if="viewDiaLogVisible" v-model="viewDiaLogVisible" @success="getList" />
    </basic-container>
</template>
<script setup>
@@ -118,11 +118,7 @@
import { getDeptTree } from '@/api/system/dept'
import ViewDiaLog from './ViewDiaLog.vue'
import { useStore } from 'vuex'
import {
    getTaskTypeLabel,
    normalizeTaskTypeOptions,
    taskTypeCascaderProps,
} from '../taskTypeOptions'
import { getTaskTypeLabel, normalizeTaskTypeOptions, taskTypeCascaderProps } from '../taskTypeOptions'
// 初始化查询参数
const initSearchParams = () => ({
applications/task-work-order/src/views/orderView/orderManage/eventManage/OrderStepBar.vue
New file
@@ -0,0 +1,225 @@
<template>
    <div class="timeline-wrapper">
        <div v-for="(stage, stageIndex) in processList" :key="stage.id || stageIndex" class="stage-group">
            <!-- 大节点 Stage Node -->
<!--            <div class="stage-node">-->
<!--                <div class="stage-icon" :class="{ reached: 1 === 1 }">-->
<!--                    <el-icon v-if="1 === 1"><Check /></el-icon>-->
<!--                    <span v-else class="stage-index">{{ stageIndex + 1 }}</span>-->
<!--                </div>-->
<!--                <span class="stage-name" :class="{ reached: true }">{{ stage.newStatusName || stage.statusName || '-' }}</span>-->
<!--            </div>-->
            <!-- 小节点 Child Nodes -->
            <div class="child-list">
                <template v-for="(record, recordIndex) in getStageRecords(stage)" :key="record.id || recordIndex">
                    <div class="child-node">
                        <div class="child-left">
                            <span class="child-status">
                                {{
                                    record.operateContent ||
                                    record.operationContent ||
                                    record.disposeContent ||
                                    record.content ||
                                    record.remark ||
                                    stage.operateContent ||
                                    '-'
                                }}
                            </span>
                        </div>
                        <div class="child-dot"></div>
                        <div class="child-right">
                            <span class="child-operator">
                                {{
                                    record.operateUserName ||
                                    record.operatorName ||
                                    record.operateUser ||
                                    record.operator ||
                                    record.userName ||
                                    stage.operateUserName ||
                                    '-'
                                }}
                            </span>
                            <span class="child-time">
                                {{ record.operateTime || record.createTime || record.updateTime || stage.operateTime || '-' }}
                            </span>
                        </div>
                    </div>
                </template>
                <!-- 连接线 -->
                <div class="connector-line" :class="{ reached: 1 === 1 }"></div>
            </div>
        </div>
    </div>
</template>
<script setup>
import { Check } from '@element-plus/icons-vue'
const props = defineProps({
    processList: {
        type: Array,
        default: () => [],
    },
})
// 获取状态对应的事件记录
function getStageRecords(stage) {
    if (Array.isArray(stage.child) && stage.child.length) return stage.child
    if (Array.isArray(stage.children) && stage.children.length) return stage.children
    if (Array.isArray(stage.records) && stage.records.length) return stage.records
    return [stage]
}
</script>
<style scoped lang="scss">
.timeline-wrapper {
    padding-left: 100px;
    position: relative;
    .stage-group {
        position: relative;
    }
    // 大节点 - Stage Node
    .stage-node {
        display: flex;
        align-items: center;
        gap: 12px;
        .stage-icon {
            width: 28px;
            height: 28px;
            border-radius: 50%;
            background: #dbdde2;
            display: flex;
            align-items: center;
            justify-content: center;
            color: #fff;
            font-size: 14px;
            font-weight: 600;
            flex-shrink: 0;
            position: relative;
            z-index: 2;
            &.reached {
                background: #1a1a2e;
            }
            .el-icon {
                font-size: 16px;
            }
            .stage-index {
                font-size: 12px;
                color: #4e5969;
            }
        }
        .stage-name {
            width: 70px;
            text-align: right;
            position: absolute;
            left: -80px;
            font-family: Roboto, Roboto;
            font-weight: bold;
            font-size: 16px;
            color: rgba(0, 0, 0, 0.45);
            &.reached {
                color: #4c34ff;
            }
        }
    }
    // 小节点 - Child Nodes
    .child-list {
        position: relative;
        left: -92px;
        min-height: 80px;
        overflow: visible;
        // 连接线
        .connector-line {
            position: absolute;
            left: 105px;
            width: 1px;
            height: calc(100% - 5px);
            top: 50%;
            transform: translateY(-50%);
            background: #cccccc;
            z-index: 1;
            &.reached {
                background: #4c34ff;
            }
        }
    }
    .child-node {
        display: flex;
        align-items: flex-start;
        position: relative;
        padding: 40px 0;
        &:last-child::before {
            bottom: 50%;
        }
        .child-left {
            width: 100px;
            flex-shrink: 0;
            text-align: right;
            padding-right: 20px;
            font-family: Source Han Sans CN, Source Han Sans CN;
            font-weight: 400;
            font-size: 14px;
            color: rgba(0, 0, 0, 0.85);
            .child-status {
                white-space: normal;
                overflow-wrap: anywhere;
            }
        }
        .child-dot {
            width: 12px;
            height: 12px;
            border-radius: 50%;
            background: #1a1a2e;
            flex-shrink: 0;
            position: relative;
            z-index: 2;
            margin-top: 4px;
        }
        .child-right {
            flex: 1;
            min-width: 0;
            padding-left: 12px;
            display: flex;
            flex-direction: column;
            gap: 2px;
            position: relative;
            .child-operator {
                font-family: Source Han Sans CN, Source Han Sans CN;
                font-weight: 400;
                font-size: 14px;
                color: rgba(0, 0, 0, 0.85);
                white-space: nowrap;
            }
            .child-time {
                position: absolute;
                top: 30px;
                white-space: nowrap;
                font-family: Source Han Sans CN, Source Han Sans CN;
                font-weight: 400;
                font-size: 14px;
                color: rgba(0, 0, 0, 0.45);
            }
        }
    }
}
</style>
applications/task-work-order/src/views/orderView/orderManage/eventManage/ViewDiaLog.vue
New file
@@ -0,0 +1,343 @@
<template>
    <el-dialog
        class="gd-dialog eventDialog"
        v-model="visible"
        title="查看"
        @closed="visible = false"
        width="80%"
        destroy-on-close
    >
        <div class="content" style="display: flex">
            <div class="processBox">
                <div class="detail-title">事件记录</div>
                <div class="process">
                    <!--                    <OrderStepBar :processList="statusFlows" />-->
                    <!--                    <el-steps direction="vertical" :active="1 === 1" space="100">-->
                    <!--                        <el-step-->
                    <!--                            v-for="(item, index) in statusFlows"-->
                    <!--                            @key="item.id"-->
                    <!--                            :title="item.newStatusName"-->
                    <!--                            :description="item.operateUserName"-->
                    <!--                        ></el-step>-->
                    <!--                    </el-steps>-->
                    <el-steps direction="vertical" :active="statusFlows.length" space="100" finish-status="success">
                        <el-step v-for="(item, index) in statusFlows" :key="item.id">
                            <template #title>
                                <div class="step-title">
                                    <span class="status-name">{{ item.newStatusName }}</span>
                                    <span class="operator-name">{{ item.operateUserName }}</span>
                                </div>
                            </template>
                            <template #description>
                                <div class="step-desc">
                                    <span>{{ item.operateTime }}</span>
                                    <span v-if="item.remark" class="remark">{{ item.remark }}</span>
                                </div>
                            </template>
                        </el-step>
                    </el-steps>
                </div>
            </div>
            <div class="leftBox">
                <div>
                    <div class="detail-title">事件详情</div>
                    <el-row class="detail-row-view">
                        <el-col :span="12">
                            <div class="label">事件名称</div>
                            <div class="val">{{ eventInfo.eventName }}</div>
                        </el-col>
                        <el-col :span="12">
                            <div class="label">事件编号</div>
                            <div class="val">{{ eventInfo.eventNum }}</div>
                        </el-col>
                        <el-col :span="12">
                            <div class="label">事件状态</div>
                            <div class="val">{{ getStatusLabel(eventInfo.eventStatus) }}</div>
                        </el-col>
                        <el-col :span="12">
                            <div class="label">事件时间</div>
                            <div class="val">
                                {{ dayjs(eventInfo.shootTime).format('YYYY-MM-DD') }}
                            </div>
                        </el-col>
                        <el-col :span="12">
                            <div class="label">处置人员</div>
                            <div class="val">{{ eventInfo.disposeUserName }}</div>
                        </el-col>
                        <el-col :span="12">
                            <div class="label">处置部门</div>
                            <div class="val">{{ eventInfo.disposeDeptName }}</div>
                        </el-col>
                    </el-row>
                </div>
                <div class="img_flow">
                    <div class="imgurl">
                        <el-image
                            :src="eventInfo.eventImageUrl"
                            :preview-src-list="[eventInfo.eventImageUrl]"
                            fit="cover"
                            preview-teleported
                        />
                    </div>
                    <div class="flow">
                        <div v-if="disposeRecords.length === 0">
                            <el-empty description="暂无数据" />
                        </div>
                        <div v-else class="flow-card" v-for="item in disposeRecords" :key="item.id">
                            <div class="flow-row1">
                                <span class="flow-name">{{ item.disposeUserName }}</span>
                                <span class="flow-status">{{ item.disposeStatusName }}</span>
                            </div>
                            <div class="flow-row2">{{ item.disposeContent }}</div>
                            <div class="flow-row3" v-if="item.attachUrl?.length">
                                <el-image
                                    v-for="(url, imageIndex) in item.attachUrl"
                                    :key="`${item.id || 'record'}-${imageIndex}`"
                                    class="flow-img-item"
                                    :src="url"
                                    :preview-src-list="item.attachUrl"
                                    fit="cover"
                                    preview-teleported
                                />
                                <!--                                <img-->
                                <!--                                    v-for="(url, imageIndex) in item.attachUrl"-->
                                <!--                                    :key="`${item.id || 'record'}-${imageIndex}`"-->
                                <!--                                    class="flow-img-item"-->
                                <!--                                    :src="url"-->
                                <!--                                    alt=""-->
                                <!--                                />-->
                            </div>
                            <div class="flow-row4">{{ item.operateTime }}</div>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </el-dialog>
</template>
<script setup>
import dayjs from 'dayjs'
import { getDetailFull } from './eventManageApi'
import OrderStepBar from './OrderStepBar.vue'
import { ref } from 'vue'
const visible = defineModel()
const eventInfo = ref({})
const disposeRecords = ref([])
const statusFlows = ref([])
// 获取状态文本
const getStatusLabel = status => {
    const map = {
        0: '待确认',
        1: '处置中',
        2: '已退回',
        3: '已处置',
        4: '已完成',
    }
    return map[status] || '未知'
}
// 兼容事件记录接口中不同版本的字段名称
function normalizeStatusFlow(item = {}) {
    return {
        ...item,
        newStatusName: item.newStatusName || item.statusName || item.eventStatusName || '-',
        operateContent:
            item.operateContent || item.operationContent || item.disposeContent || item.content || item.remark || '-',
        operateUserName:
            item.operateUserName || item.operatorName || item.operateUser || item.operator || item.userName || '-',
        operateTime: item.operateTime || item.createTime || item.updateTime || '-',
    }
}
// 打开弹框
async function open({ row } = {}) {
    if (!row?.id) return
    const res = await getDetailFull({ id: row.id })
    const detail = res?.data?.data || {}
    eventInfo.value = detail.eventInfo || {}
    disposeRecords.value = detail.disposeRecords || []
    statusFlows.value = (detail.statusFlows || []).map(normalizeStatusFlow)
}
defineExpose({ open })
</script>
<style lang="scss">
.eventDialog {
    .el-dialog__body {
        display: flex;
        flex-direction: column;
        .content {
            height: 0;
            flex: 1;
        }
    }
}
</style>
<style scoped lang="scss">
.content {
    display: flex;
    gap: 0 20px;
    //height: 630px;
    .leftBox {
        width: 0;
        flex: 1;
        display: flex;
        overflow-y: auto;
        overflow-x: hidden;
        flex-direction: column;
        scrollbar-width: none;
        &::-webkit-scrollbar {
            display: none;
        }
        //.gd-cesium {
        //    position: relative;
        //    //overflow: hidden;
        //    width: 100%;
        //    height: 390px;
        //}
        .img_flow {
            //border: 1px solid red;
            display: flex;
            justify-content: space-between;
            height: 436px;
            margin-top: 20px;
            .imgurl {
                flex: 1;
                height: 100%;
                img {
                    height: 100%;
                    width: 100%;
                }
            }
            .flow {
                margin-left: 10px;
                flex: 1;
                min-width: 0;
                min-height: 0;
                overflow-y: auto;
                overflow-x: hidden;
                scrollbar-width: none;
                &::-webkit-scrollbar {
                    display: none;
                }
                .flow-card {
                    display: flex;
                    flex-direction: column;
                    gap: 8px;
                    margin-bottom: 8px;
                    padding: 12px;
                    border: 1px solid #e4e4e4;
                    border-radius: 8px;
                    background: #fff;
                    .flow-row1 {
                        display: flex;
                        justify-content: space-between;
                        align-items: center;
                    }
                    .flow-row2 {
                        font-size: 14px;
                        font-weight: 500;
                    }
                    .flow-row3 {
                        display: flex;
                        flex-wrap: wrap;
                        gap: 8px;
                        .flow-img-item {
                            width: 60px;
                            height: 60px;
                            flex: 0 0 60px;
                            border: 1px solid #e4e4e4;
                            border-radius: 4px;
                            background: #f5f5f5;
                            object-fit: cover;
                        }
                    }
                    .flow-row4 {
                        font-size: 12px;
                        color: #999;
                    }
                }
            }
        }
    }
    .rightBox {
        width: 350px;
        display: flex;
        flex-direction: column;
        .title {
            font-weight: 500;
            font-size: 14px;
            color: #272e37;
        }
        ::v-deep(.el-table) {
            tr th {
                font-weight: normal;
            }
        }
    }
    .processBox {
        display: flex;
        flex-direction: column;
        .process {
            height: 100%;
            padding: 12px 16px 24px 16px;
            width: 312px;
            border: 1px solid #e4e4e4;
            border-radius: 8px;
            background: #fff;
            overflow: auto;
        }
    }
    .step-title {
        display: flex;
        justify-content: space-between;
        align-items: center;
        width: 100%;
        padding-right: 10px;
    }
    .status-name {
        font-weight: 500;
        color: #4c34ff;
        font-size: 14px;
    }
    .operator-name {
        color: #909399;
        font-size: 13px;
        margin-left: 20px;
    }
    .step-desc {
        color: #999999;
        font-size: 12px;
    }
    .remark {
        margin-left: 12px;
        color: #e6a23c;
    }
    :deep(.el-step__icon.is-text) {
        border-color: #4c34ff;
    }
    :deep(.el-step__head.is-success) {
        color: #4c34ff;
        height: 100px;
        border-color: #4c34ff;
    }
}
</style>
applications/task-work-order/src/views/orderView/orderManage/eventManage/eventManageApi.js
New file
@@ -0,0 +1,25 @@
import request from '@/axios'
// 获取工单列表
export const getGdList = params => {
    return request({
        url: '/drone-gd/workOrder/gdClueEvent/list',
        method: 'get',
        params,
    })
}
export const getDetailFull = params => {
    return request({
        url: '/drone-gd/workOrder/gdClueEvent/detailFull',
        method: 'get',
        params,
    })
}
// 数量统计
export const getCount = params => {
    return request({
        url: '/drone-gd/workOrder/gdClueEvent/count',
        method: 'get',
        params,
    })
}
applications/task-work-order/src/views/orderView/orderManage/eventManage/index.vue
New file
@@ -0,0 +1,231 @@
<!-- 线索事件管理 -->
<template>
    <basic-container>
        <el-form ref="queryParamsRef" :model="searchParams" class="gd-search-form">
            <el-form-item label="模糊搜索" prop="patrolTaskName">
                <el-input
                    class="gd-input gray"
                    v-model="searchParams.keyword"
                    placeholder="请输入"
                    clearable
                    @clear="handleSearch"
                />
            </el-form-item>
            <el-form-item label="事件状态" prop="taskStatus">
                <el-select
                    class="gd-select"
                    popper-class="gd-select-popper"
                    v-model="searchParams.eventStatus"
                    placeholder="请选择"
                    clearable
                    @change="handleSearch"
                >
                    <el-option v-for="item in eventStatus" :key="item.label" :label="item.label" :value="item.value" />
                </el-select>
            </el-form-item>
            <el-form-item label="执行时间" prop="executeTime">
                <el-date-picker
                    class="gd-date-picker gray"
                    popper-class="gd-date-picker-popper"
                    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="gd-search-actions">
                <el-button :icon="RefreshRight" @click="resetForm"></el-button>
                <el-button class="search-btn" :icon="Search" @click="handleSearch"></el-button>
            </el-form-item>
        </el-form>
        <div class="gd-table-container" v-loading="loading">
            <div class="gd-table-content gd-table-content-bg">
                <el-table class="gd-table" :data="list">
                    <el-table-column type="index" width="64" label="序号" />
                    <el-table-column prop="eventName" show-overflow-tooltip label="事件名称" />
                    <el-table-column prop="eventNum" show-overflow-tooltip label="事件编号" />
                    <el-table-column prop="eventStatus" show-overflow-tooltip label="事件状态">
                        <template v-slot="{ row }">
                            {{ eventStatusMap[row.eventStatus] }}
                        </template>
                    </el-table-column>
                    <el-table-column prop="shootTime" show-overflow-tooltip label="事件时间" />
                    <el-table-column prop="disposeUserName" show-overflow-tooltip label="处置人员" />
                    <el-table-column prop="disposeDeptName" show-overflow-tooltip label="处置部门" />
                    <el-table-column label="操作" class-name="operation-btns" fixed="right">
                        <template v-slot="{ row }">
                            <el-link type="primary" @click="viewDiaLogView(row)">查看</el-link>
                            <el-link v-if="row.eventStatus === 2" type="primary" @click="viewDistribute(row)">分发</el-link>
                        </template>
                    </el-table-column>
                </el-table>
            </div>
            <div class="gd-pagination-parent">
                <el-pagination
                    popper-class="gd-select-popper"
                    v-model:current-page="searchParams.current"
                    v-model:page-size="searchParams.size"
                    layout="total, prev, pager, next, sizes"
                    :total="total"
                    @change="getList"
                />
            </div>
        </div>
        <ViewDiaLog ref="viewDiaLogRef" v-if="viewDiaLogVisible" v-model="viewDiaLogVisible" />
        <DistributeDiaLog
            ref="distributeDialogRef"
            v-if="distributeDialogVisible"
            v-model="distributeDialogVisible"
            @success="getList"
        />
    </basic-container>
</template>
<script setup>
import { Search, RefreshRight } from '@element-plus/icons-vue'
import { computed, onMounted, ref } from 'vue'
import { getDictionaryByCode } from '@/api/system/dictbiz'
import { dateRangeFormat, getDictLabel } from '@ztzf/utils'
// import { getDictListApi } from '@/api/zkxt'
import { getGdList } from './eventManageApi'
import { getDeptTree } from '@/api/system/dept'
import ViewDiaLog from './ViewDiaLog.vue'
import { useStore } from 'vuex'
import { getTaskTypeLabel, normalizeTaskTypeOptions, taskTypeCascaderProps } from '../taskTypeOptions'
import DistributeDiaLog from '@/views/orderView/orderManage/clueEvents/DistributeDiaLog.vue'
// 初始化查询参数
const initSearchParams = () => ({
    eventStatus: '',
    keyword: '',
    current: 1, // 当前页
    size: 10, // 每页大小
    startTime: '',
    endTime: '',
    onlyMine: 0,
})
const distributeDialogVisible = ref(false)
const searchParams = ref(initSearchParams()) // 查询参数
const dateRange = ref([]) // 执行时间范围
const total = ref(0) // 总条数
const loading = ref(true) // 列表加载中
const list = ref([]) // 列表数据
const queryParamsRef = ref(null) // 查询表单实例
const viewDiaLogRef = ref(null)
const distributeDialogRef = ref(null)
const viewDiaLogVisible = ref(false)
const deptTree = ref([]) // 部门树
const treeProps = {
    label: 'name',
    children: 'children',
}
const dictObj = ref({
    workOrderType: [], // 巡查任务类型
    taskStatus: [], // 巡查任务状态
})
provide('dictObj', dictObj)
provide('deptTree', deptTree)
provide('treeProps', treeProps)
// 事件状态
const eventStatus = ref([
    { label: '待确认', value: 0 },
    { label: '处置中', value: 1 },
    { label: '已退回', value: 2 },
    { label: '已处置', value: 3 },
    { label: '已完成', value: 4 },
])
// 事件状态映射
const eventStatusMap = computed(() => {
    const map = {}
    eventStatus.value.forEach(item => {
        map[item.value] = item.label
    })
    return map
})
// 获取列表
async function getList() {
    const range = dateRangeFormat(dateRange.value)
    loading.value = true
    try {
        const res = await getGdList({
            ...searchParams.value,
            startTime: range[0],
            endTime: range[1],
        })
        list.value = res?.data?.data?.records ?? []
        total.value = res?.data?.data?.total ?? 0
    } finally {
        loading.value = false
    }
}
// 查询
function handleSearch() {
    searchParams.value.current = 1
    getList()
}
// 重置查询
function resetForm() {
    queryParamsRef.value?.resetFields()
    dateRange.value = []
    searchParams.value.current = 1
    getList()
}
// 查看弹框
function viewDiaLogView(row) {
    viewDiaLogVisible.value = true
    nextTick(() => {
        viewDiaLogRef.value?.open({ row })
    })
}
// 分发
function viewDistribute(row) {
    distributeDialogVisible.value = true
    nextTick(() => {
        distributeDialogRef.value?.open({ row })
    })
}
// 获取字典
function getDictList() {
    getDictionaryByCode('workOrderType,taskStatus').then(res => {
        dictObj.value = res.data.data
    })
    // getDictListApi('task_inspection_type').then(res => {
    //     workOrderTypeXT.value = normalizeTaskTypeOptions(res.data.data || [])
    // })
}
const store = useStore()
const userInfo = computed(() => store.state.user.userInfo)
// 获取部门树
function getDeptTreeFun() {
    getDeptTree(userInfo.value.dept_id).then(res => {
        deptTree.value = res.data.data
    })
}
onMounted(() => {
    getList()
    getDictList()
    getDeptTreeFun()
})
</script>
<style scoped lang="scss"></style>
uniapps/work-app/src/api/work/index.js
@@ -1,5 +1,8 @@
import { request } from "@/utils/requestGd/index.js"
import website from '@/config/website'
import { getEnvObj } from '@/utils/index.js'
import { useUserStore } from '@/store/index.js'
import { Base64 } from 'js-base64'
// 工单用户登录接口
@@ -59,8 +62,79 @@
// 分享缓存数据
export const shareCacheApi = (data) => {
  return request({
    url: '/drone-gd/workOrder/gdPatrolTask/share-cache-data',
    method: 'post',
    data,
  })
        url: '/drone-gd/workOrder/gdPatrolTask/share-cache-data',
        method: 'post',
        data,
    })
}
// 新增处置记录
export const addDisposeRecord = data => {
    return request({
        url: '/drone-gd/workOrder/gdClueEvent/addDisposeRecord',
        method: 'post',
        data,
    })
}
// 更新状态
export const updateStatus = data => {
    return request({
        url: '/drone-gd/workOrder/gdClueEvent/updateStatus',
        method: 'post',
        data,
    })
}
// 获取处置记录列表
export const getDisposeRecords = params => {
    return request({
        url: '/drone-gd/workOrder/gdClueEvent/disposeRecords',
        method: 'get',
        params,
    })
}
// 上传文件
export const uploadFileAPI = filePath => {
    let baseURL = getEnvObj().VITE_API_BASE_URL_GD
    // #ifdef H5
    if (import.meta.env.VITE_APP_PROXY === 'true') {
        baseURL = import.meta.env.VITE_API_PREFIX_GD
    }
    // #endif
    const userInfo = useUserStore()?.$state?.userInfo || {}
    const accessToken = userInfo.gd_access_token
    const areaCode = userInfo.detail?.areaCode
    const header = {
        'Blade-Requested-With': 'BladeHttpRequest',
        Authorization: `Basic ${Base64.encode(`${website.clientId}:${website.clientSecret}`)}`,
    }
    if (accessToken) header['Blade-Auth'] = `bearer ${accessToken}`
    if (areaCode) header.areaCode = areaCode
    return new Promise((resolve, reject) => {
        uni.uploadFile({
            url: `${baseURL.replace(/\/$/, '')}/blade-resource/oss/endpoint/put-file`,
            filePath,
            name: 'file',
            header,
            success: response => {
                try {
                    const responseData = typeof response.data === 'string' ? JSON.parse(response.data) : response.data
                    const status = Number(responseData?.error_code ?? responseData?.code ?? response.statusCode)
                    if (![0, 200].includes(status)) {
                        reject(responseData)
                        return
                    }
                    resolve(responseData)
                } catch (error) {
                    reject(error)
                }
            },
            fail: reject,
        })
    })
}
uniapps/work-app/src/pages/work/index.vue
@@ -1,18 +1,23 @@
<template>
  <div class="eventTickets" :style="{ paddingTop: topMargin + 'px' }">
    <div class="searchTop">
      <up-search placeholder="请输入关键字搜索"  :animation="true"  v-model="listParams.keyword" :show-action="false" @search="handleSearch" @clear="handleClear"></up-search>
    </div>
    <div class="listBox">
      <div class="tabs-container">
        <up-tabs :list="tabList" @click="handleClick"></up-tabs>
      </div>
<!-- @scrolltolower="loadMore" -->
      <scroll-view class="eventBox" scroll-y :lower-threshold="80" >
        <div class="eventList">
          <div class="eventItem"  v-for="(item,index) in dataList" :key="index">
            <image
    <div class="eventTickets" :style="{ paddingTop: topMargin + 'px' }">
        <div class="searchTop">
            <up-search
                placeholder="请输入关键字搜索"
                :animation="true"
                v-model="listParams.keyword"
                :show-action="false"
                @search="handleSearch"
                @clear="handleClear"
            ></up-search>
        </div>
        <div class="listBox">
            <div class="tabs-container">
                <up-tabs :scrollable="true" :list="tabList" @click="handleClick"></up-tabs>
            </div>
            <scroll-view class="eventBox" scroll-y :lower-threshold="80" @scrolltolower="loadMore">
                <div class="eventList">
                    <div class="eventItem" v-for="(item, index) in dataList" :key="index">
                        <image
                            v-if="[1, 2].includes(item.attachmentType)"
                            :src="item?.aiImg || item?.eventImageUrl"
                            mode="aspectFill"
@@ -21,88 +26,112 @@
                        <div v-if="item.attachmentType === 3" class="videoBox" @click="detailHandle(item)">
                            <div class="playIcon"></div>
                        </div>
            <div class="informationDisplay">
                        <div class="informationDisplay">
                            <div class="itemTitle">{{ item.eventName }}</div>
              <div class="itemContent">{{ formatDate(item.createTime) }}</div>
            </div>
          </div>
        </div>
                            <div class="itemContent">{{ formatDate(item.createTime) }}</div>
                        </div>
                    </div>
                </div>
        <!-- 暂无数据提示 -->
        <div class="noData" v-if="!loading && dataList.length === 0">
          <text>暂无数据</text>
        </div>
                <!-- 暂无数据提示 -->
                <div class="noData" v-if="!loading && dataList.length === 0">
                    <text>暂无数据</text>
                </div>
        <!-- 加载提示 -->
        <div class="loadingMore">
          <text v-if="loading">加载中...</text>
        </div>
      </scroll-view>
    </div>
                <!-- 加载提示 -->
                <div class="loadingMore">
                    <text v-if="loading">加载中...</text>
                    <text v-else-if="!hasMore">没有更多了</text>
                </div>
            </scroll-view>
        </div>
        <canvas
            canvas-id="ai-image-canvas"
            id="ai-image-canvas"
            class="ai-image-canvas"
            :style="{ width: canvasSize.width + 'px', height: canvasSize.height + 'px' }"
        ></canvas>
  </div>
    </div>
</template>
<script setup>
import { getCurrentInstance, nextTick } from 'vue'
import { useUserStore } from '@/store/index.js'
import {getGdList,getstatusCount} from '@/api/work/index.js'
import dayjs from 'dayjs';
import { getStatusBarHeight } from '@/utils/common';
import { getGdList, getstatusCount } from '@/api/work/index.js'
import dayjs from 'dayjs'
import { getStatusBarHeight } from '@/utils/common'
const canvasOwner = getCurrentInstance()?.proxy
const userStore = useUserStore()
const userInfo = userStore.userInfo
const dataList = ref([])
const currentTab=ref('all')
const currentTab = ref('all')
const loading = ref(false)
const hasMore = ref(true)
const tabList = ref([
  {
    name: '全部工单',
    key: 'all',
    badge: {
      value: 0,
      showZero: true
    },
  },
  {
    name: '我的工单',
    key: 'myTickets',
    badge: {
      value: 0,
      showZero: true
    }
  }
    {
        name: '全部工单',
        key: 'all',
        badge: {
            value: 0,
            showZero: true,
        },
    },
    {
        name: '我的工单',
        key: 'myTickets',
        badge: {
            value: 0,
            showZero: true,
        },
    },
    {
        name: '处置中',
        key: 'disposing',
        badge: {
            value: 0,
            showZero: true,
        },
    },
    {
        name: '已处置',
        key: 'handled',
        badge: {
            value: 0,
            showZero: true,
        },
    },
    {
        name: '已完成',
        key: 'completed',
        badge: {
            value: 0,
            showZero: true,
        },
    },
])
const formatDate = (dateString) => {
  return dayjs(dateString).format('MM/DD HH:mm');
};
// tab key 与事件状态码映射
const tabStatusMap = {
    disposing: 1,
    handled: 3,
    completed: 4,
}
const formatDate = dateString => {
    return dayjs(dateString).format('MM/DD HH:mm')
}
const listParams = ref({
  current: 1,
  size: 12,
  source: 1,
  department:'',
  keyword:''
    current: 1,
    size: 12,
    source: 1,
    department: '',
    keyword: '',
    eventStatus: '',
})
const mockGeojson = "[{\"score\":0.89990234375,\"bbox\":{\"x_cen\":195.5,\"y_cen\":326.5,\"width\":117.0,\"height\":265.0},\"class_name\":\"car\",\"algorithmId\":\"e71116098eeb1d60cfebd04d30653b151\"},{\"score\":0.89306640625,\"bbox\":{\"x_cen\":1194.5,\"y_cen\":559.5,\"width\":115.0,\"height\":261.0},\"class_name\":\"car\",\"algorithmId\":\"e71116098eeb1d60cfebd04d30653b151\"},{\"score\":0.88720703125,\"bbox\":{\"x_cen\":179.0,\"y_cen\":955.5,\"width\":124.0,\"height\":249.0},\"class_name\":\"car\",\"algorithmId\":\"e71116098eeb1d60cfebd04d30653b151\"},{\"score\":0.88330078125,\"bbox\":{\"x_cen\":1198.5,\"y_cen\":260.5,\"width\":115.0,\"height\":285.0},\"class_name\":\"car\",\"algorithmId\":\"e71116098eeb1d60cfebd04d30653b151\"},{\"score\":0.84716796875,\"bbox\":{\"x_cen\":204.5,\"y_cen\":71.5,\"width\":115.0,\"height\":143.0},\"class_name\":\"car\",\"algorithmId\":\"e71116098eeb1d60cfebd04d30653b151\"},{\"score\":0.83203125,\"bbox\":{\"x_cen\":186.0,\"y_cen\":657.5,\"width\":114.0,\"height\":269.0},\"class_name\":\"car\",\"algorithmId\":\"e71116098eeb1d60cfebd04d30653b151\"},{\"score\":0.78662109375,\"bbox\":{\"x_cen\":1205.5,\"y_cen\":49.5,\"width\":117.0,\"height\":99.0},\"class_name\":\"car\",\"algorithmId\":\"e71116098eeb1d60cfebd04d30653b151\"}]"
const mockGeojson =
    '[{"score":0.89990234375,"bbox":{"x_cen":195.5,"y_cen":326.5,"width":117.0,"height":265.0},"class_name":"car","algorithmId":"e71116098eeb1d60cfebd04d30653b151"},{"score":0.89306640625,"bbox":{"x_cen":1194.5,"y_cen":559.5,"width":115.0,"height":261.0},"class_name":"car","algorithmId":"e71116098eeb1d60cfebd04d30653b151"},{"score":0.88720703125,"bbox":{"x_cen":179.0,"y_cen":955.5,"width":124.0,"height":249.0},"class_name":"car","algorithmId":"e71116098eeb1d60cfebd04d30653b151"},{"score":0.88330078125,"bbox":{"x_cen":1198.5,"y_cen":260.5,"width":115.0,"height":285.0},"class_name":"car","algorithmId":"e71116098eeb1d60cfebd04d30653b151"},{"score":0.84716796875,"bbox":{"x_cen":204.5,"y_cen":71.5,"width":115.0,"height":143.0},"class_name":"car","algorithmId":"e71116098eeb1d60cfebd04d30653b151"},{"score":0.83203125,"bbox":{"x_cen":186.0,"y_cen":657.5,"width":114.0,"height":269.0},"class_name":"car","algorithmId":"e71116098eeb1d60cfebd04d30653b151"},{"score":0.78662109375,"bbox":{"x_cen":1205.5,"y_cen":49.5,"width":117.0,"height":99.0},"class_name":"car","algorithmId":"e71116098eeb1d60cfebd04d30653b151"}]'
const canvasSize = ref({
    width: 1,
    height: 1
    height: 1,
})
const parseAiFrame = aiFrameSource => {
@@ -125,26 +154,29 @@
            fail: error => {
                console.log(error)
                resolve(null)
            }
            },
        })
    })
const canvasToTempFilePath = (width, height) =>
    new Promise(resolve => {
        uni.canvasToTempFilePath({
            canvasId: 'ai-image-canvas',
            width,
            height,
            destWidth: width,
            destHeight: height,
            fileType: 'jpg',
            quality: 0.92,
            success: res => resolve(res.tempFilePath),
            fail: error => {
                console.log(error)
                resolve('')
            }
        }, canvasOwner)
        uni.canvasToTempFilePath(
            {
                canvasId: 'ai-image-canvas',
                width,
                height,
                destWidth: width,
                destHeight: height,
                fileType: 'jpg',
                quality: 0.92,
                success: res => resolve(res.tempFilePath),
                fail: error => {
                    console.log(error)
                    resolve('')
                },
            },
            canvasOwner
        )
    })
const drawAiImage = async (url, aiFrameSource) => {
@@ -200,202 +232,224 @@
}
const getDataList = () => {
  if (loading.value || !hasMore.value) return
    if (loading.value || !hasMore.value) return
  loading.value = true
  const params = {
    current: listParams.value.current,
    size: listParams.value.size,
    keyword:listParams.value.keyword,
    onlyMine:currentTab.value=== 'myTickets' ? 1 : 0
  }
  getGdList(params).then(async res => {
    const response = res.data.data
        const list = []
        for (const item of response) {
            // const aiImg = await drawAiImage(item.eventImageUrl, mockGeojson)
            if (item.attachmentType !== 2) {
                list.push(item)
            }else{
                const aiImg = await drawAiImage(item.eventImageUrl, item.geojson)
                list.push({ ...item, aiImg })
    loading.value = true
    const params = {
        current: listParams.value.current,
        size: listParams.value.size,
        keyword: listParams.value.keyword,
        onlyMine: currentTab.value === 'myTickets' ? 1 : 0,
        eventStatus: listParams.value.eventStatus,
    }
    getGdList(params)
        .then(async res => {
            const resData = res.data.data
            const response = resData.records
            const list = []
            for (const item of response) {
                // const aiImg = await drawAiImage(item.eventImageUrl, mockGeojson)
                if (item.attachmentType !== 2) {
                    list.push(item)
                } else {
                    const aiImg = await drawAiImage(item.eventImageUrl, item.geojson)
                    list.push({ ...item, aiImg })
                }
            }
        }
        dataList.value = list
    // 根据当前页码决定是替换还是追加数据
    // if (listParams.value.current === 1) {
    //   dataList.value = response
    // } else {
    //   dataList.value = [...dataList.value, ...response]
    // }
    // // 判断是否还有更多数据
    // if (response.length < listParams.value.size || response.current >= response.pages) {
    //   hasMore.value = false
    // } else {
    //   hasMore.value = true
    // }
  }).finally(() => {
    loading.value = false
  })
}
const getstatusCountData=()=>{
  const params={
    keyword:listParams.value.keyword,
  }
    getstatusCount(params).then(res=>{
        const response = res.data.data
         const {  totalCount,myCount } = response
        tabList.value.forEach(tab=>{
            if(tab.key === 'all'){
        tab.badge.value = (totalCount || 0).toString()
        tab.badge.showZero = true
            }else if(tab.key === 'myTickets'){
                tab.badge.value = (myCount || 0).toString()
        tab.badge.showZero = true
            // 根据当前页码决定是替换还是追加数据
            if (listParams.value.current === 1) {
                dataList.value = list
            } else {
                dataList.value = [...dataList.value, ...list]
            }
            // 判断是否还有更多数据
            if (list.length < listParams.value.size || resData.current >= resData.pages) {
                hasMore.value = false
            } else {
                hasMore.value = true
            }
        })
        .finally(() => {
            loading.value = false
        })
}
const getstatusCountData = () => {
    const params = {
        keyword: listParams.value.keyword,
    }
    getstatusCount(params).then(res => {
        const response = res.data.data
        const { totalCount, myCount, disposingCount, disposedCount, completedCount } = response
        tabList.value.forEach(tab => {
            if (tab.key === 'all') {
                tab.badge.value = (totalCount || 0).toString()
                tab.badge.showZero = true
            } else if (tab.key === 'myTickets') {
                tab.badge.value = (myCount || 0).toString()
                tab.badge.showZero = true
            } else if (tab.key === 'disposing') {
                tab.badge.value = (disposingCount || 0).toString()
                tab.badge.showZero = true
            } else if (tab.key === 'handled') {
                tab.badge.value = (disposedCount || 0).toString()
                tab.badge.showZero = true
            } else if (tab.key === 'completed') {
                tab.badge.value = (completedCount || 0).toString()
                tab.badge.showZero = true
            }
        })
    })
}
const handleClick = (item) => {
  currentTab.value = item.key
const handleClick = item => {
    currentTab.value = item.key
    listParams.value.eventStatus = tabStatusMap[item.key] ?? ''
  listParams.value.current = 1
  hasMore.value = true
  dataList.value = []
  getDataList()
    listParams.value.current = 1
    hasMore.value = true
    dataList.value = []
    getDataList()
}
const loadMore = () => {
  if (loading.value || !hasMore.value) return
  listParams.value.current++
  getDataList()
    if (loading.value || !hasMore.value) return
    listParams.value.current++
    getDataList()
}
const detailHandle = (val) => {
  uni.navigateTo({
    url: `/subPackages/workDetail/index?id=${val.id}` + (val.aiImg ? `&aiImg=${val.aiImg}` : ''),
  })
const detailHandle = val => {
    uni.navigateTo({
        url: `/subPackages/workDetail/index?id=${val.id}` + (val.aiImg ? `&aiImg=${val.aiImg}` : ''),
    })
}
// 搜索功能
const handleSearch = () => {
  listParams.value.current = 1
  hasMore.value = true
  dataList.value = []
  getDataList()
  getstatusCountData()
    listParams.value.current = 1
    hasMore.value = true
    dataList.value = []
    getDataList()
    getstatusCountData()
}
// 清除搜索
const handleClear = () => {
  listParams.value.keyword = ''
  listParams.value.current = 1
  hasMore.value = true
  dataList.value = []
  getDataList()
  getstatusCountData()
    listParams.value.keyword = ''
    listParams.value.current = 1
    hasMore.value = true
    dataList.value = []
    getDataList()
    getstatusCountData()
}
const topMargin = getStatusBarHeight()
onShow(() => {
  listParams.value.current = 1
  hasMore.value = true
  dataList.value = []
  getDataList()
  getstatusCountData()
    listParams.value.current = 1
    hasMore.value = true
    dataList.value = []
    getDataList()
    getstatusCountData()
})
</script>
<style scoped lang="scss">
.eventTickets {
  padding: 0 20rpx;
  display: flex;
  flex-direction: column;
  height: 100%;
  box-sizing: border-box;
  width: 100%;
  overflow-x: hidden;
  .searchTop {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding-right: 24rpx;
    width: 100%;
    height: 108rpx;
    :deep(){
      .u-search__content{
        background: #fff !important;
      }
      .u-search__content__input{
        background: #fff !important;
      }
    }
  }
  :deep(){
    padding: 0 20rpx;
    display: flex;
    flex-direction: column;
    height: 100%;
    box-sizing: border-box;
    width: 100%;
    overflow-x: hidden;
    .searchTop {
        display: flex;
        align-items: center;
        justify-content: space-between;
        padding-right: 24rpx;
        width: 100%;
        height: 108rpx;
        :deep() {
            .u-search__content {
                background: #fff !important;
            }
            .u-search__content__input {
                background: #fff !important;
            }
        }
    }
    :deep() {
        .u-tabs__wrapper__nav__line {
            // width: 80rpx !important;
            background: #1d6fe9 !important;
        }
        .u-badge {
            background-color: #1d6fe9 !important;
            margin-top: -14px !important;
            margin-left: -5px !important;
        }
    }
    tabs-container {
        display: flex;
        align-items: center;
        height: 98rpx;
        width: 100%;
        overflow-x: auto; // 横向滚动
        overflow-y: hidden;
        white-space: nowrap; // 防止换行
        -webkit-overflow-scrolling: touch; // 移动端平滑滚动
    .u-tabs__wrapper__nav__line{
      // width: 80rpx !important;
      background: #1D6FE9 !important;
    }
    .u-badge {
      background-color: #1d6fe9 !important;
      margin-top: -14px !important;
      margin-left: -5px !important;
    }
  }
        // 隐藏滚动条(可选)
        &::-webkit-scrollbar {
            height: 0;
            display: none;
        }
    }
    // 子元素不压缩
    .tabs-container > * {
        flex-shrink: 0;
    }
    .listBox {
        position: relative;
        display: flex;
        flex-direction: column;
        flex: 1;
        width: 100%;
        box-sizing: border-box;
    }
    .eventBox {
        padding: 20rpx 0;
        overflow-y: auto;
        overflow-x: hidden;
        height: 0;
        flex-grow: 1;
        align-content: flex-start;
        width: 100%;
        box-sizing: border-box;
    }
  .tabs-container{
    display: flex;
    justify-content: center;
    height: 98rpx;
  }
  .listBox {
    position: relative;
    display: flex;
    flex-direction: column;
    flex: 1;
    width: 100%;
    box-sizing: border-box;
  }
  .eventBox {
    padding: 20rpx 0;
    overflow-y: auto;
    overflow-x: hidden;
    height: 0;
    flex-grow: 1;
    align-content: flex-start;
    width: 100%;
    box-sizing: border-box;
  }
    .eventList {
        display: flex;
        flex-wrap: wrap;
        gap: 20rpx;
        width: 100%;
        box-sizing: border-box;
        justify-content: space-between;
  .eventList {
    display: flex;
    flex-wrap: wrap;
    gap: 20rpx;
    width: 100%;
    box-sizing: border-box;
    justify-content: space-between;
        .eventItem {
            width: calc(50% - 10rpx);
            background-color: #fff;
            border-radius: 12rpx;
            overflow: hidden;
            position: relative;
            box-sizing: border-box;
            max-width: 100%;
    .eventItem {
      width: calc(50% - 10rpx);
      background-color: #fff;
      border-radius: 12rpx;
      overflow: hidden;
      position: relative;
      box-sizing: border-box;
      max-width: 100%;
      image,
            image,
            .videoBox {
        width: 100%;
        height: 208rpx;
        border-radius: 12rpx;
        overflow: hidden;
                width: 100%;
                height: 208rpx;
                border-radius: 12rpx;
                overflow: hidden;
                display: block;
      }
            }
            .videoBox {
                background: linear-gradient(135deg, #eef4ff 0%, #dfe8f8 100%);
                display: flex;
@@ -422,61 +476,60 @@
                    border-left: 22rpx solid #fff;
                }
            }
      .informationDisplay{
        width: 100%;
        position: absolute;
        bottom: 3px;
        background: rgba(11,11,11,0.35);
        border-radius: 0rpx 0rpx 12rpx 12rpx;
        display: flex;
        align-items: center;
        justify-content: space-between;
        padding: 10rpx 14rpx;
            .informationDisplay {
                width: 100%;
                position: absolute;
                bottom: 3px;
                background: rgba(11, 11, 11, 0.35);
                border-radius: 0rpx 0rpx 12rpx 12rpx;
                display: flex;
                align-items: center;
                justify-content: space-between;
                padding: 10rpx 14rpx;
                box-sizing: border-box;
        .itemTitle {
          flex: 1;
                .itemTitle {
                    flex: 1;
                    min-width: 0;
          font-family: Source Han Sans CN, Source Han Sans CN;
          font-weight: 500;
          font-size: 24rpx;
          color: #FFFFFF;
          white-space: nowrap;
          overflow: hidden;
          text-overflow: ellipsis;
        }
        .itemContent {
          font-family: Source Han Sans CN, Source Han Sans CN;
          font-weight: 400;
          font-size: 24rpx;
          color: #FFFFFF;
          margin-left: 12rpx;
                    font-family: Source Han Sans CN, Source Han Sans CN;
                    font-weight: 500;
                    font-size: 24rpx;
                    color: #ffffff;
                    white-space: nowrap;
                    overflow: hidden;
                    text-overflow: ellipsis;
                }
        }
      }
    }
  }
                .itemContent {
                    font-family: Source Han Sans CN, Source Han Sans CN;
                    font-weight: 400;
                    font-size: 24rpx;
                    color: #ffffff;
                    margin-left: 12rpx;
                    white-space: nowrap;
                }
            }
        }
    }
  .loadingMore {
    display: flex;
    justify-content: center;
    align-items: center;
    height: 80rpx;
    font-size: 28rpx;
    color: #999;
    width: 100%;
  }
    .loadingMore {
        display: flex;
        justify-content: center;
        align-items: center;
        height: 80rpx;
        font-size: 28rpx;
        color: #999;
        width: 100%;
    }
  .noData {
    display: flex;
    justify-content: center;
    align-items: center;
    height: 300rpx;
    font-size: 28rpx;
    color: #999;
    width: 100%;
  }
    .noData {
        display: flex;
        justify-content: center;
        align-items: center;
        height: 300rpx;
        font-size: 28rpx;
        color: #999;
        width: 100%;
    }
    .ai-image-canvas {
        position: fixed;
uniapps/work-app/src/subPackages/workDetail/HandleWork/index.vue
New file
@@ -0,0 +1,295 @@
<template>
    <view class="handle-work-page">
        <u-navbar title="处置记录" :is-back="true" back-text="" :back-icon-size="40" @left-click="onBackClick"></u-navbar>
        <!-- 把上传处置放在滚动区域外面,固定在头部 -->
        <view class="upload_handle" @click="uploadHadle">+上传处置</view>
        <scroll-view class="record-scroll" scroll-y :lower-threshold="80" @scrolltolower="loadMore">
            <view class="record-list">
                <view class="record-card" v-for="(item, index) in recordList" :key="index">
                    <!-- 第一行:名称 + 状态 -->
                    <view class="card-row-top">
                        <text class="card-name">{{ item.disposeUserName }}</text>
                        <text class="card-status" :class="statusClass(item.disposeStatus)">
                            {{ getStatusLabel(item.disposeStatus) }}
                        </text>
                    </view>
                    <!-- 第二行:标题 -->
                    <view class="card-title">
                        <text>{{ item.disposeContent }}</text>
                    </view>
                    <!-- 第三行:图片列表 -->
                    <view class="card-images" v-if="item.attachUrl && item.attachUrl.length">
                        <image
                            v-for="(img, imgIdx) in item.attachUrl"
                            :key="imgIdx"
                            :src="img"
                            mode="aspectFill"
                            class="card-image"
                            @click="previewImage(item.attachUrl, imgIdx)"
                        />
                    </view>
                    <!-- 第四行:日期 -->
                    <view class="card-row-bottom">
                        <text class="card-date">{{ formatDate(item.operateTime) }}</text>
                    </view>
                </view>
            </view>
            <!-- 暂无数据 -->
            <view class="noData" v-if="!loading && recordList.length === 0">
                <text>暂无处置记录</text>
            </view>
            <!-- 加载提示 -->
            <view class="loadingMore">
                <text v-if="loading">加载中...</text>
                <text v-else-if="!hasMore && recordList.length > 0">没有更多了</text>
            </view>
        </scroll-view>
    </view>
</template>
<script setup>
import { ref } from 'vue'
import { onLoad } from '@dcloudio/uni-app'
import dayjs from 'dayjs'
import { getDisposeRecords } from '@/api/work/index.js'
const eventId = ref('')
const eventStatus = ref('')
const recordList = ref([])
const loading = ref(false)
const hasMore = ref(true)
const listParams = ref({
    current: 1,
    size: 3,
})
// 获取状态文本
const getStatusLabel = status => {
    const map = {
        0: '待确认',
        1: '处置中',
        2: '已退回',
        3: '已处置',
        4: '已完成',
    }
    return map[status] || '未知'
}
// 状态样式类名
const statusClass = status => {
    if (status === 1) return 'status-processing'
    if (status === 3) return 'status-handled'
    if (status === 4) return 'status-completed'
    return ''
}
// 格式化日期
const formatDate = dateString => {
    if (!dateString) return ''
    return dayjs(dateString).format('YYYY-MM-DD HH:mm')
}
// 读取工单标识
onLoad(options => {
    eventId.value = options?.id ?? ''
    eventStatus.value = options?.eventStatus ?? ''
    getRecordList()
})
onShow(() => {
    getRecordList()
})
// 获取处置记录列表
const getRecordList = async () => {
    if (loading.value || !hasMore.value) return
    loading.value = true
    const res = await getDisposeRecords({ eventId: eventId.value })
    recordList.value = res.data.data
    loading.value = false
}
function uploadHadle() {
    uni.navigateTo({
        url: `/subPackages/workDetail/HandleWorking/index?id=${eventId.value}&eventStatus=${eventStatus.value}`,
    })
}
// 加载更多
const loadMore = () => {
    if (loading.value || !hasMore.value) return
    listParams.value.current++
    getRecordList()
}
// 图片预览
const previewImage = (attachments, index) => {
    uni.previewImage({
        urls: attachments,
        current: index,
    })
}
// 返回按钮点击事件
const onBackClick = () => {
    uni.navigateBack()
}
</script>
<style scoped lang="scss">
.handle-work-page {
    padding-top: 88rpx;
    :deep() {
        .u-navbar__content {
            background: url('https://wrj.shuixiongit.com/aiskyminio/cloud-bucket/ztzf_app_assets/images/user/bg.png')
                no-repeat !important;
            background-size: 100% !important;
            height: 88rpx !important;
        }
    }
    display: flex;
    flex-direction: column;
    height: 100vh; // 新增:占满全屏
    background: #f6f6f6;
    box-sizing: border-box;
    :deep(.u-navbar) {
        position: fixed !important;
        top: 0;
        left: 0;
        right: 0;
        z-index: 100;
    }
    :deep(.u-navbar__content) {
        height: 88rpx !important;
    }
}
// 上传处置按钮 - 移到滚动区域外面
.upload_handle {
    height: 80rpx; // 改大一点,方便点击
    display: flex;
    align-items: center;
    justify-content: flex-end;
    padding-right: 24rpx;
    margin: 60rpx 0 12rpx 0; // 上下间距
    color: #1d6fe9;
    font-size: 28rpx;
    flex-shrink: 0; // 防止被压缩
    background: #f6f6f6; // 背景色,避免和滚动区域混在一起
}
.record-scroll {
    flex: 1; // 占满剩余高度
    // padding-top: 24rpx;
    padding: 0 24rpx 24rpx 24rpx; // 左右下内边距
    box-sizing: border-box;
}
.record-list {
    display: flex;
    flex-direction: column;
    gap: 20rpx;
    padding-bottom: 10rpx;
}
.record-card {
    background: #ffffff;
    border-radius: 12rpx;
    padding: 24rpx;
    box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.06);
}
.card-row-top {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 16rpx;
}
.card-name {
    font-family: Source Han Sans CN, Source Han Sans CN;
    font-weight: 500;
    font-size: 28rpx;
    color: #222324;
}
.card-status {
    font-family: Source Han Sans CN, Source Han Sans CN;
    font-weight: 400;
    font-size: 24rpx;
    padding: 4rpx 16rpx;
    border-radius: 6rpx;
    color: #ffffff;
    background: #999;
    &.status-processing {
        background: #1d6fe9;
    }
    &.status-handled {
        background: #e6a23c;
    }
    &.status-completed {
        background: #00b578;
    }
}
.card-title {
    font-family: Source Han Sans CN, Source Han Sans CN;
    font-weight: 400;
    font-size: 30rpx;
    color: #222324;
    line-height: 1.6;
    margin-bottom: 16rpx;
    word-break: break-all;
}
.card-images {
    display: flex;
    gap: 12rpx;
    margin-bottom: 16rpx;
    overflow: hidden;
}
.card-image {
    width: 160rpx;
    height: 160rpx;
    border-radius: 8rpx;
    flex-shrink: 0;
}
.card-row-bottom {
    display: flex;
    justify-content: flex-end;
}
.card-date {
    font-family: Source Han Sans CN, Source Han Sans CN;
    font-weight: 400;
    font-size: 24rpx;
    color: #999;
}
.noData {
    display: flex;
    justify-content: center;
    align-items: center;
    height: 400rpx;
    font-size: 28rpx;
    color: #999;
}
.loadingMore {
    display: flex;
    justify-content: center;
    align-items: center;
    height: 80rpx;
    font-size: 28rpx;
    color: #999;
}
</style>
uniapps/work-app/src/subPackages/workDetail/HandleWorking/index-copy.vue
New file
@@ -0,0 +1,470 @@
<template>
    <view class="handle-page">
        <u-navbar title="待办处置" :is-back="true" back-text="" :back-icon-size="40" @left-click="onBackClick"></u-navbar>
        <scroll-view class="form-scroll" scroll-y>
            <view class="form-section description-section">
                <view class="section-header">
                    <text class="section-title">处置描述</text>
                </view>
                <view class="description-wrapper">
                    <textarea
                        v-model="formData.disposeContent"
                        class="description-input"
                        maxlength="140"
                        placeholder="请输入"
                        placeholder-class="description-placeholder"
                        :show-confirm-bar="false"
                    />
                    <text class="word-count">{{ descriptionLength }}/140</text>
                </view>
            </view>
            <!--            <view class="form-section status-section">-->
            <!--                <text class="section-title">处置中</text>-->
            <!--                <switch class="status-switch" :checked="formData.isHandling" color="#00b578" @change="handleStatusChange" />-->
            <!--            </view>-->
            <view class="form-section upload-section">
                <view class="section-header upload-header">
                    <text class="section-title">附件上传</text>
                </view>
                <!--                <button class="select-file-btn" @click="handleChooseFile">选择文件</button>-->
                <u-button color="#1D6FE9" class="select-file-btn" :disabled="uploading" @click="handleChooseFile">
                    {{ uploading ? '上传中...' : '选择文件' }}
                </u-button>
                <view v-if="attachmentList.length" class="attachment-list">
                    <view v-for="(file, index) in attachmentList" :key="`${file.path}-${index}`" class="attachment-item">
                        <view class="file-info">
                            <text class="file-name">{{ file.name }}</text>
                            <text v-if="file.size" class="file-size">{{ formatFileSize(file.size) }}</text>
                        </view>
                        <text class="remove-file-btn" @click.stop="handleRemoveFile(index)">×</text>
                    </view>
                </view>
            </view>
        </scroll-view>
        <view class="action-footer">
            <u-button color="#AEAEAE" class="custom-style" @click="handleCancel">取消</u-button>
            <u-button color="#1D6FE9" class="custom-style" :disabled="submitting || uploading" @click="handleSubmit">
                {{ submitting ? '提交中...' : '确定' }}
            </u-button>
        </view>
    </view>
</template>
<script setup>
import { computed, ref } from 'vue'
import { onLoad } from '@dcloudio/uni-app'
import { backGdApi, addDisposeRecord, uploadFileAPI } from '@/api/work/index.js'
const MAX_FILE_COUNT = 9
const eventId = ref('')
const eventStatus = ref(null)
const submitting = ref(false)
const uploading = ref(false)
const attachmentList = ref([])
const formData = ref({
    disposeContent: '',
    isHandling: true,
})
const navbarTitleStyle = {
    color: '#ffffff',
    fontSize: '34rpx',
    fontWeight: '500',
}
// 计算处置描述字数
const descriptionLength = computed(() => formData.value.disposeContent.length)
// 读取当前工单标识
onLoad(options => {
    eventId.value = options?.id ?? ''
})
// 返回上一页
function handleBack() {
    uni.navigateBack()
}
// 切换处置状态
// function handleStatusChange(event) {
//     formData.value.isHandling = event.detail.value
// }
// 提取文件名称
function getFileName(filePath = '') {
    const normalizedPath = filePath.replace(/\\/g, '/')
    return decodeURIComponent(normalizedPath.split('/').pop() || '未命名文件')
}
// 上传单个已选择文件
async function uploadSelectedFile(file) {
    const response = await uploadFileAPI(file.path)
    const link = response?.data?.link || response?.data?.url || response?.link
    if (!link) throw new Error('上传接口未返回文件地址')
    return { ...file, link }
}
// 处理已选择文件
async function handleChooseSuccess(result) {
    const selectedFiles = result.tempFiles?.length
        ? result.tempFiles
        : (result.tempFilePaths ?? []).map(path => ({ path }))
    const availableCount = MAX_FILE_COUNT - attachmentList.value.length
    const normalizedFiles = selectedFiles
        .slice(0, availableCount)
        .map(file => {
            const path = file.path || file.tempFilePath || ''
            return {
                name: file.name || getFileName(path),
                path,
                size: Number(file.size) || 0,
            }
        })
        .filter(file => file.path)
    if (!normalizedFiles.length) return
    uploading.value = true
    uni.showLoading({ title: '上传中...', mask: true })
    let failedCount = 0
    try {
        const uploadResults = await Promise.all(
            normalizedFiles.map(async file => {
                try {
                    return { success: true, file: await uploadSelectedFile(file) }
                } catch {
                    return { success: false, file }
                }
            })
        )
        const uploadedFiles = uploadResults.filter(item => item.success).map(item => item.file)
        failedCount = uploadResults.length - uploadedFiles.length
        attachmentList.value.push(...uploadedFiles)
    } finally {
        uni.hideLoading()
        uploading.value = false
    }
    uni.showToast({
        title: failedCount ? `${failedCount}个文件上传失败` : '上传成功',
        icon: failedCount ? 'none' : 'success',
    })
}
// 处理选择文件失败
function handleChooseFail(error) {
    if (error?.errMsg?.includes('cancel')) return
    uni.showToast({ title: '选择文件失败', icon: 'none' })
}
// 选择附件
function handleChooseFile() {
    if (uploading.value) return
    const availableCount = MAX_FILE_COUNT - attachmentList.value.length
    if (availableCount <= 0) {
        uni.showToast({ title: `最多选择${MAX_FILE_COUNT}个附件`, icon: 'none' })
        return
    }
    const options = {
        count: availableCount,
        type: 'all',
        success: handleChooseSuccess,
        fail: handleChooseFail,
    }
    // #ifdef H5
    uni.chooseFile(options)
    // #endif
    // #ifdef MP-WEIXIN
    uni.chooseMessageFile(options)
    // #endif
    // #ifdef APP-PLUS
    uni.chooseImage({ ...options, sourceType: ['album', 'camera'] })
    // #endif
}
// 移除附件
function handleRemoveFile(index) {
    attachmentList.value.splice(index, 1)
}
// 格式化文件大小
function formatFileSize(size) {
    if (size < 1024) return `${size} B`
    if (size < 1024 * 1024) return `${(size / 1024).toFixed(1)} KB`
    return `${(size / 1024 / 1024).toFixed(1)} MB`
}
// 取消处置
function handleCancel() {
    uni.navigateBack()
}
// 提交处置信息
async function handleSubmit() {
    if (uploading.value) {
        uni.showToast({ title: '请等待附件上传完成', icon: 'none' })
        return
    }
    const disposeContent = formData.value.disposeContent.trim()
    if (!disposeContent) {
        uni.showToast({ title: '请输入处置描述', icon: 'none' })
        return
    }
    if (!eventId.value) {
        uni.showToast({ title: '工单信息缺失', icon: 'none' })
        return
    }
    submitting.value = true
    try {
        await addDisposeRecord({
            eventId: eventId.value,
            recordType: 1,
            disposeContent: formData.value.disposeContent,
            targetEventStatus: eventStatus.value == 1 ? 3 : eventStatus.value,
            updateEventStatus: true,
            attachUrl: attachmentList.value.map(file => file.link),
            // operateTime: '2026-07-30 14:00:00',
            // eventStatus: formData.value.isHandling ? '1' : '3',
            // handleDesc,
            // attachmentList: attachmentList.value.map(file => ({
            //     attachName: file.name,
            //     filePath: file.path,
            // })),
        })
        uni.showToast({ title: '提交成功', icon: 'success' })
        setTimeout(() => {
            submitting.value = false
            uni.navigateBack()
            // uni.navigateTo({
            //     url: `/subPackages/workDetail/HandleWork/index?id=${eventId.value}`,
            // })
        }, 1200)
    } catch {
        submitting.value = false
        uni.showToast({ title: '提交失败,请重试', icon: 'none' })
    }
}
// 返回按钮点击事件
const onBackClick = () => {
    uni.navigateBack()
}
onLoad(options => {
    console.log(options, '9090')
    eventId.value = options?.id || ''
    eventStatus.value = options?.eventStatus || ''
})
</script>
<style scoped lang="scss">
.handle-page {
    padding-top: 88rpx;
    :deep() {
        .u-navbar__content {
            background: url('https://wrj.shuixiongit.com/aiskyminio/cloud-bucket/ztzf_app_assets/images/user/bg.png')
                no-repeat !important;
            background-size: 100% !important;
            height: 88rpx !important;
        }
    }
    display: flex;
    flex-direction: column;
    box-sizing: border-box;
    height: calc(100vh);
    //padding-top: 88rpx;
    //background: #111827;
    //color: #f5f7fa;
    //:deep(.u-navbar__content) {
    //    height: 88rpx !important;
    //    background: #151c29 !important;
    //}
}
.form-scroll {
    box-sizing: border-box;
    height: 0;
    flex: 1;
    padding: 60rpx 24rpx 24rpx;
}
.form-section {
    box-sizing: border-box;
    margin-bottom: 20rpx;
    background: #ffffff;
    border: 1rpx solid rgba(255, 255, 255, 0.03);
}
.section-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    height: 82rpx;
    padding: 0 22rpx;
    border-bottom: 1rpx solid #eeeeee;
}
.section-title {
    font-size: 30rpx;
    font-weight: 400;
    color: #222324;
}
.description-wrapper {
    position: relative;
}
.word-count {
    position: absolute;
    right: 22rpx;
    bottom: 16rpx;
    font-size: 24rpx;
    color: #999;
}
.description-input {
    display: block;
    box-sizing: border-box;
    width: 100%;
    height: 350rpx;
    padding: 24rpx 22rpx;
    font-size: 30rpx;
    line-height: 1.6;
    color: #222324;
    background: transparent;
}
:deep(.description-placeholder) {
    color: #999;
}
.status-section {
    display: flex;
    align-items: center;
    justify-content: space-between;
    height: 104rpx;
    padding: 0 20rpx 0 22rpx;
}
.status-switch {
    transform: scale(0.84);
    transform-origin: right center;
}
.upload-section {
    padding-bottom: 22rpx;
}
.upload-header {
    margin-bottom: 0;
}
.select-file-btn {
    width: 210rpx;
    height: 70rpx;
    margin: 0;
    font-size: 28rpx;
    line-height: 70rpx;
    //color: #ffffff;
    //background: #409eff;
    //border-radius: 4rpx;
}
.select-file-btn::after,
.action-btn::after {
    border: 0;
}
.custom-style {
    width: 276rpx;
    height: 76rpx;
    font-family: 'Source Han Sans CN';
    font-weight: 400;
    font-size: 28rpx;
}
.attachment-list {
    padding: 10rpx 22rpx 0;
}
.attachment-item {
    display: flex;
    align-items: center;
    justify-content: space-between;
    min-height: 70rpx;
    border-bottom: 1rpx solid #eeeeee;
}
.file-info {
    display: flex;
    align-items: center;
    width: 0;
    flex: 1;
}
.file-name {
    overflow: hidden;
    font-size: 26rpx;
    color: #222324;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.file-size {
    margin-left: 14rpx;
    font-size: 22rpx;
    color: #999;
    white-space: nowrap;
}
.remove-file-btn {
    width: 56rpx;
    font-size: 42rpx;
    line-height: 56rpx;
    color: #999;
    text-align: right;
}
.action-footer {
    display: flex;
    align-items: center;
    gap: 28rpx;
    box-sizing: border-box;
    padding: 22rpx 28rpx calc(22rpx + env(safe-area-inset-bottom));
    //background: #111827;
}
.action-btn {
    height: 82rpx;
    margin: 0;
    flex: 1;
    font-size: 30rpx;
    line-height: 80rpx;
    color: #ffffff;
    border-radius: 6rpx;
}
.cancel-btn {
    color: #00b578;
    background: transparent;
    border: 2rpx solid #00b578;
}
.confirm-btn {
    background: #00b578;
    border: 2rpx solid #00b578;
}
.confirm-btn[disabled] {
    color: rgba(255, 255, 255, 0.72);
    background: #087b58;
}
</style>
uniapps/work-app/src/subPackages/workDetail/HandleWorking/index.vue
New file
@@ -0,0 +1,381 @@
<script setup>
import { onLoad } from '@dcloudio/uni-app'
onLoad(options => {
    console.log(options, '9090')
    // eventId.value = options?.id || ''
    // eventStatus.value = options?.eventStatus || ''
})
</script>
<template>
    <div class="workDetailContainer">
        <!-- 自定义导航栏 -->
        <u-navbar title="工单详情" :is-back="true" back-text="" :back-icon-size="40" @left-click="onBackClick">
            <template #right>
                <div v-if="isH5" class="tapShare-icon"><img src="/static/images/work/share.svg" @click="onShareClick" /></div>
                <div v-else class="share-btn" @click="onShareClick"></div>
            </template>
        </u-navbar>
        <div class="detailTop">打发打发打发打发</div>
    </div>
</template>
<style lang="scss" scoped>
    .workDetailContainer {
        // 添加导航栏高度的padding-top,避免内容被遮挡
        padding-top: 88rpx;
        :deep() {
            .u-navbar__content {
                background: url('https://wrj.shuixiongit.com/aiskyminio/cloud-bucket/ztzf_app_assets/images/user/bg.png')
                no-repeat !important;
                background-size: 100% !important;
                height: 88rpx !important;
            }
        } // 分享按钮样式
        .share-btn {
            // #ifdef APP-PLUS
            width: 40rpx;
            height: 40rpx;
            background-image: url('/static/images/work/share.svg');
            background-size: contain;
            background-repeat: no-repeat;
            background-position: center;
            // #endif
        }
        .tapShare-icon {
            width: 40rpx;
            height: 40rpx;
            img {
                width: 40rpx;
                height: 40rpx;
            }
        }
        .detailTop {
            // #ifdef APP-PLUS
            padding-top: 60rpx;
            // #endif
            .image-container {
                position: relative;
                width: 100%;
                height: 410rpx;
                .detailImage {
                    width: 100%;
                    height: 100%;
                    display: block;
                    object-fit: cover;
                }
                // 轮播图容器
                :deep(.u-swiper) {
                    // 指示器容器 - 修改为居中对齐
                    .u-swiper__indicator {
                        position: absolute;
                        left: 50%;
                        transform: translateX(-50%);
                        bottom: 20rpx;
                        display: flex;
                        justify-content: center;
                        align-items: center;
                        z-index: 10;
                    }
                    .u-swiper-indicator__wrapper__dot--active {
                        width: 5px !important;
                    }
                }
            }
        }
        .worderContainer {
            padding: 0 24rpx;
            padding-bottom: 4rpx;
            background: #f6f6f6;
            margin-top: 20rpx;
            height: calc(100vh - 530rpx);
            overflow: auto;
        }
        .workOrderContent {
            margin-top: 30epx;
            background-color: #fff;
            border-radius: 12rpx;
            padding: 20rpx;
            margin-bottom: 34rpx;
            .workOrderTitle {
                font-family: Source Han Sans CN, Source Han Sans CN;
                font-weight: bold;
                font-size: 32rpx;
                color: #222324;
            }
            .workOrderContainer {
                .orderRow {
                    display: flex;
                    justify-content: space-between;
                    align-items: center;
                    min-height: 96rpx;
                    border-bottom: 1px solid #f5f5f5;
                    color: #7b7b7b;
                    .rowTitle {
                        font-family: Source Han Sans CN, Source Han Sans CN;
                        font-weight: 400;
                        font-size: 30rpx;
                        color: #222324;
                        white-space: nowrap;
                    }
                    .rowAddress {
                        font-family: Source Han Sans CN, Source Han Sans CN;
                        font-weight: 400;
                        font-size: 28rpx;
                        color: #1d6fe9;
                        text-decoration: underline;
                        text-align: right;
                        padding-top: 1px;
                        padding-left: 5px;
                        padding-right: 2px;
                    }
                    .status-picker {
                        .picker-value {
                            font-family: Source Han Sans CN, Source Han Sans CN;
                            font-weight: 400;
                            font-size: 28rpx;
                            color: #1d6fe9;
                            display: flex;
                            align-items: center;
                            gap: 8rpx;
                        }
                        .picker-arrow {
                            font-size: 20rpx;
                            color: #999;
                        }
                    }
                }
            }
        }
        .actionButton {
            display: flex;
            justify-content: center;
            align-items: center;
            box-sizing: border-box;
            width: 100%;
            height: 61px;
            padding: 0 24rpx;
            border-radius: 6px 6px 6px 6px;
            .btngroups {
                display: flex;
                align-items: center;
                width: 100%;
                min-width: 0;
                :deep(.u-button) {
                    width: 0 !important;
                    min-width: 0;
                    margin: 0 !important;
                    flex: 1;
                    height: 76rpx !important;
                }
                &:not(.three-actions) {
                    :deep(.u-button + .u-button) {
                        margin-left: 20rpx !important;
                    }
                }
                &.three-actions {
                    justify-content: center;
                    :deep(.u-button) {
                        width: 210rpx !important;
                        flex: 0 0 210rpx;
                    }
                    :deep(.u-button:last-child) {
                        margin-left: 20rpx !important;
                    }
                }
            }
        }
    }
    .complete-modal-overlay {
        position: fixed;
        top: 0;
        right: 0;
        bottom: 0;
        left: 0;
        z-index: 10000;
        display: flex;
        align-items: center;
        justify-content: center;
        box-sizing: border-box;
        padding: 40rpx;
        background: rgba(0, 0, 0, 0.55);
    }
    .complete-modal {
        box-sizing: border-box;
        width: 620rpx;
        max-width: 100%;
        padding: 32rpx;
        background: #ffffff;
        border-radius: 12rpx;
    }
    .complete-modal-title {
        margin-bottom: 28rpx;
        font-size: 32rpx;
        font-weight: 500;
        color: #222324;
        text-align: center;
    }
    .complete-input-wrapper {
        position: relative;
        box-sizing: border-box;
        height: 220rpx;
        padding-bottom: 38rpx;
        background: #f6f7f9;
        border: 1rpx solid #e5e7eb;
        border-radius: 6rpx;
    }
    .complete-input {
        box-sizing: border-box;
        width: 100%;
        height: 100%;
        padding: 20rpx;
        font-size: 28rpx;
        line-height: 1.5;
        color: #222324;
    }
    .complete-word-count {
        position: absolute;
        right: 16rpx;
        bottom: 10rpx;
        font-size: 22rpx;
        color: #999999;
    }
    .complete-modal-actions {
        display: flex;
        gap: 24rpx;
        margin-top: 30rpx;
    }
    .complete-action-btn {
        height: 76rpx;
        margin: 0;
        flex: 1;
        font-size: 28rpx;
        line-height: 76rpx;
        border-radius: 6rpx;
    }
    .complete-action-btn::after {
        border: 0;
    }
    .complete-cancel-btn {
        color: #666666;
        background: #f1f2f4;
    }
    .complete-confirm-btn {
        color: #ffffff;
        background: #1d6fe9;
    }
    .complete-action-btn[disabled] {
        opacity: 0.65;
    }
    /* 分享弹出层样式 */
    .share-modal-overlay {
        position: fixed;
        top: 0;
        left: 0;
        right: 0;
        bottom: 0;
        background-color: rgba(0, 0, 0, 0.6);
        display: flex;
        justify-content: flex-end;
        align-items: flex-end;
        z-index: 9999;
    }
    .share-modal-content {
        background-color: #fff;
        border-radius: 20rpx 20rpx 0 0;
        padding: 30rpx;
        width: 100%;
        max-height: 80vh;
        overflow-y: auto;
    }
    .share-modal-header {
        display: flex;
        justify-content: space-between;
        align-items: center;
        margin-bottom: 30rpx;
    }
    .share-modal-title {
        font-size: 36rpx;
        font-weight: bold;
        color: #222324;
        text-align: center;
        flex: 1;
    }
    .share-modal-close {
        font-size: 60rpx;
        color: #999;
        line-height: 40rpx;
    }
    .share-modal-body {
        display: flex;
        flex-wrap: wrap;
        justify-content: space-around;
        padding-bottom: 30rpx;
    }
    .share-modal-footer {
        display: flex;
        justify-content: space-around;
        padding-top: 30rpx;
        border-top: 1px solid #f0f0f0;
    }
    .share-item {
        display: flex;
        flex-direction: column;
        align-items: center;
        margin: 20rpx;
        width: 120rpx;
    }
    .share-icon {
        width: 90rpx;
        height: 90rpx;
        border-radius: 50%;
        margin-bottom: 15rpx;
        background-size: contain;
        background-repeat: no-repeat;
        background-position: center;
    }
    .share-icon.wechat {
        background-image: url('@/static/images/work/wechat.svg');
    }
    .share-icon.moments {
        background-image: url('@/static/images/work/moments.svg');
    }
    .share-icon.link {
        background-image: url('@/static/images/work/link.svg');
    }
    .share-name {
        font-size: 24rpx;
        color: #666;
        margin-top: 10rpx;
    }
</style>
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 v-if="isH5" class="tapShare-icon"><img src="/static/images/work/share.svg"  @click="onShareClick"/></div>
                <div v-if="isH5" class="tapShare-icon"><img src="/static/images/work/share.svg" @click="onShareClick" /></div>
                <div v-else class="share-btn" @click="onShareClick"></div>
            </template>
        </u-navbar>
@@ -24,184 +24,390 @@
                ></u-swiper>
            </div>
        </div>
    <!-- 工单内容 -->
    <div class="worderContainer">
      <div class="workOrderContent">
        <div class="workOrderTitle">工单内容</div>
        <div class="workOrderContainer">
          <div class="orderRow">
            <div class="rowTitle">工单名称</div>
            <div>{{ workDetailData.eventName }}</div>
          </div>
          <div class="orderRow">
            <div class="rowTitle">工单编号</div>
            <div>{{ workDetailData.eventNum }}</div>
          </div>
          <div class="orderRow">
            <div class="rowTitle">工单处置人</div>
            <div>{{ workDetailData.disposeUserName }}</div>
          </div>
          <div class="orderRow">
            <div class="rowTitle">处置部门</div>
            <div>{{ workDetailData.disposeDeptName }}</div>
          </div>
          <div class="orderRow">
            <div class="rowTitle">拍摄时间</div>
            <div>{{ workDetailData.shootTime }}</div>
          </div>
          <div class="orderRow">
            <div class="rowTitle">分发人员</div>
            <div>{{ workDetailData.distributeUserName }}</div>
          </div>
          <div class="orderRow">
            <div class="rowTitle">分发部门</div>
            <div>{{ workDetailData.distributeDeptName }}</div>
          </div>
        <!-- 工单内容 -->
        <div class="worderContainer">
            <div class="workOrderContent">
                <div class="workOrderTitle">工单内容</div>
                <div class="workOrderContainer">
                    <div class="orderRow">
                        <div class="rowTitle">工单名称</div>
                        <div>{{ workDetailData.eventName }}</div>
                    </div>
                    <div class="orderRow">
                        <div class="rowTitle">工单编号</div>
                        <div>{{ workDetailData.eventNum }}</div>
                    </div>
                    <div class="orderRow">
                        <div class="rowTitle">工单状态</div>
                        <template v-if="[1, 3].includes(workDetailData.eventStatus)">
                            <picker
                                mode="selector"
                                :range="statusOptions"
                                range-key="name"
                                :value="statusPickerIndex"
                                @change="onStatusChange"
                                class="status-picker"
                            >
                                <div class="picker-value">
                                    {{ currentStatusLabel || '请选择' }}
                                    <text class="picker-arrow">▼</text>
                                </div>
                            </picker>
                        </template>
                        <div v-else>{{ getStatusLabel(workDetailData.eventStatus) }}</div>
                    </div>
                    <div class="orderRow">
                        <div class="rowTitle">工单处置人</div>
                        <div>{{ workDetailData.disposeUserName }}</div>
                    </div>
                    <div class="orderRow">
                        <div class="rowTitle">处置部门</div>
                        <div>{{ workDetailData.disposeDeptName }}</div>
                    </div>
                    <div class="orderRow">
                        <div class="rowTitle">拍摄时间</div>
                        <div>{{ workDetailData.shootTime }}</div>
                    </div>
                    <div class="orderRow">
                        <div class="rowTitle">分发人员</div>
                        <div>{{ workDetailData.distributeUserName }}</div>
                    </div>
                    <div class="orderRow">
                        <div class="rowTitle">分发部门</div>
                        <div>{{ workDetailData.distributeDeptName }}</div>
                    </div>
          <div class="orderRow">
            <div class="rowTitle">分发时间</div>
            <div>{{ workDetailData.distributeTime }}</div>
          </div>
          <div class="orderRow">
            <div class="rowTitle">工单位置</div>
            <div class="rowAddress" @click="jumpMap(workDetailData)">{{ workDetailData.eventLocation }}</div>
          </div>
        </div>
      </div>
    </div>
    <!-- 操作按钮 -->
    <div class="actionButton" >
      <div class="btngroups" v-if="workDetailData.eventStatus !== 3">
        <up-button type="primary"  color="#AEAEAE" text="退回" @click="rejectTicket"></up-button>
        <up-button type="primary" color="#1D6FE9" text="确认" @click="confirmTheTicket"></up-button>
      </div>
      <div class="btngroups" v-if="workDetailData.eventStatus === 3">
        <up-button type="primary" color="#1D6FE9" text="已确认" @click="confirmedHandle"></up-button>
      </div>
    </div>
    <!-- 分享弹出层 -->
    <div v-if="showShareModal" class="share-modal-overlay" @click="onShareModalClose">
      <div class="share-modal-content" @click.stop>
        <div class="share-modal-header">
          <div class="share-modal-title">分享到</div>
          <!-- <div class="share-modal-close" @click="onShareModalClose">×</div> -->
        </div>
        <div class="share-modal-body">
<!--          <div class="share-item" @click="shareToWechat">-->
<!--            <div class="share-icon wechat"></div>-->
<!--            <div class="share-name">微信好友</div>-->
<!--          </div>-->
<!--          <div class="share-item" @click="shareToMoments">-->
<!--            <div class="share-icon moments"></div>-->
<!--            <div class="share-name">朋友圈</div>-->
<!--          </div>-->
          <div class="share-item" @click="copyLink">
            <div class="share-icon link"></div>
            <div class="share-name">复制链接</div>
          </div>
        </div>
      </div>
    </div>
  </div>
                    <div class="orderRow">
                        <div class="rowTitle">分发时间</div>
                        <div>{{ workDetailData.distributeTime }}</div>
                    </div>
                    <div class="orderRow">
                        <div class="rowTitle">工单位置</div>
                        <div class="rowAddress" @click="jumpMap(workDetailData)">{{ workDetailData.eventLocation }}</div>
                    </div>
                </div>
            </div>
            <!-- 操作按钮 -->
            <div class="actionButton">
                <div class="btngroups" v-if="workDetailData.eventStatus === 0">
                    <up-button type="primary" color="#AEAEAE" text="退回" @click="rejectTicket"></up-button>
                    <up-button type="primary" color="#1D6FE9" text="待确认" @click="confirmTheTicket"></up-button>
                </div>
                <div class="btngroups" v-if="workDetailData.eventStatus === 1">
                    <up-button type="primary" color="#1D6FE9" text="处置记录" @click="handlelogs"></up-button>
                    <up-button type="primary" color="#1D6FE9" text="上传处置" @click="uploadHandle"></up-button>
                </div>
                <div class="btngroups" v-if="workDetailData.eventStatus === 3">
                    <up-button type="primary" color="#1D6FE9" text="处置记录" @click="handlelogs"></up-button>
                    <up-button type="primary" color="#1D6FE9" text="继续上传处置" @click="uploadHandle"></up-button>
                    <up-button type="primary" color="#1D6FE9" text="完成" @click="confirmedHandle"></up-button>
                </div>
                <div class="btngroups" v-if="workDetailData.eventStatus === 4">
                    <up-button type="primary" color="#1D6FE9" text="处置记录" @click="handlelogs"></up-button>
                </div>
            </div>
        </div>
        <!-- 完成情况弹窗 -->
        <view v-if="showCompleteModal" class="complete-modal-overlay" @click="closeCompleteModal">
            <view class="complete-modal" @click.stop>
                <view class="complete-modal-title">填写完成情况</view>
                <view class="complete-input-wrapper">
                    <textarea
                        v-model="completeContent"
                        class="complete-input"
                        maxlength="200"
                        placeholder="请输入完成情况"
                        :show-confirm-bar="false"
                    />
                    <text class="complete-word-count">{{ completeContent.length }}/200</text>
                </view>
                <view class="complete-modal-actions">
                    <button class="complete-action-btn complete-cancel-btn" :disabled="completing" @click="closeCompleteModal">
                        取消
                    </button>
                    <button class="complete-action-btn complete-confirm-btn" :disabled="completing" @click="submitComplete">
                        {{ completing ? '提交中...' : '确认' }}
                    </button>
                </view>
            </view>
        </view>
        <!-- 分享弹出层 -->
        <div v-if="showShareModal" class="share-modal-overlay" @click="onShareModalClose">
            <div class="share-modal-content" @click.stop>
                <div class="share-modal-header">
                    <div class="share-modal-title">分享到</div>
                    <!-- <div class="share-modal-close" @click="onShareModalClose">×</div> -->
                </div>
                <div class="share-modal-body">
                    <!--          <div class="share-item" @click="shareToWechat">-->
                    <!--            <div class="share-icon wechat"></div>-->
                    <!--            <div class="share-name">微信好友</div>-->
                    <!--          </div>-->
                    <!--          <div class="share-item" @click="shareToMoments">-->
                    <!--            <div class="share-icon moments"></div>-->
                    <!--            <div class="share-name">朋友圈</div>-->
                    <!--          </div>-->
                    <div class="share-item" @click="copyLink">
                        <div class="share-icon link"></div>
                        <div class="share-name">复制链接</div>
                    </div>
                </div>
            </div>
        </div>
    </div>
</template>
<script setup>
// import { getShowImg, getSmallImg } from '@/utils/util'
import {getGddetailedData,backGdApi,shareCacheApi} from '/src/api/work/index.js'
import { getGddetailedData, backGdApi, shareCacheApi, updateStatus } from '/src/api/work/index.js'
import { getWebViewUrl } from '@/utils/index.js'
import WebViewPlus from '@/components/WebViewPlus.vue'
import { onShow } from '@dcloudio/uni-app'
const eventNum = ref('')
// 工单内容
const workDetailData = ref({})
// 环境判断
const isH5 = ref(false)
const sWebViewRef = ref(null);
const viewUrl = ref("");
onLoad(async options => {
  eventNum.value = options.id
  await getDataList(options.id,options.aiImg)
  checkEnvironment()
  viewUrl.value = getWebViewUrl("/workDetail", { workDetailData: JSON.stringify(workDetailData.value) });
const sWebViewRef = ref(null)
const viewUrl = ref('')
const showCompleteModal = ref(false)
const completeContent = ref('')
const completing = ref(false)
// 状态选项列表
const statusOptions = [
    { name: '处置中', value: 1 },
    { name: '已处置', value: 3 },
    // { name: '已完成', value: 4 },
]
// 当前选中状态的索引
const statusPickerIndex = computed(() => {
    const status = workDetailData.value?.eventStatus
    return statusOptions.findIndex(item => item.value === status)
})
const getDataList = async (val,aiImg) => {
  const params = {
    id: val,
  }
  const res = await getGddetailedData(params)
  const response = res.data.data
  workDetailData.value = {...response,aiImg}
// 当前状态文本
const currentStatusLabel = computed(() => {
    const option = statusOptions.find(item => item.value === workDetailData.value?.eventStatus)
    return option?.name || ''
})
onLoad(async options => {
    eventNum.value = options.id
    await getDataList(options.id, options.aiImg)
    checkEnvironment()
    viewUrl.value = getWebViewUrl('/workDetail', { workDetailData: JSON.stringify(workDetailData.value) })
})
onShow(() => {
    getDataList(eventNum.value)
})
const getDataList = async (val, aiImg) => {
    const params = {
        id: val,
    }
    const res = await getGddetailedData(params)
    const response = res.data.data
    workDetailData.value = { ...response, aiImg }
}
// 获取状态文本
const getStatusLabel = status => {
    const map = {
        0: '待确认',
        1: '处置中',
        2: '已退回',
        3: '已处置',
        4: '已完成',
    }
    return map[status] || '未知'
}
// 切换状态
const onStatusChange = e => {
    const index = e.detail.value
    const selectedOption = statusOptions[index]
    if (!selectedOption) return
    const id = workDetailData.value.id
    if (workDetailData.value.eventStatus === 1 && selectedOption.value === 3) {
        // 跳转上传处置页面
        uni.navigateTo({
            url: `/subPackages/workDetail/HandleWorking/index?id=${workDetailData.value.id}&eventStatus=${selectedOption.value}`,
        })
    }
    if (workDetailData.value.eventStatus === 3 && selectedOption.value === 1) {
        updateStatus({
            eventId: workDetailData.value.id,
            targetStatus: 1,
        }).then(res => {
            workDetailData.value.eventStatus = selectedOption.value
        })
    }
    // console.log(selectedOption, '3333')
    // backGdApi({ eventId: id, eventStatus: String(selectedOption.value) })
    //     .then(res => {
    //         uni.showToast({
    //             title: '状态更新成功',
    //             icon: 'success',
    //             duration: 1500,
    //         })
    //         workDetailData.value.eventStatus = selectedOption.value
    //     })
    //     .catch(() => {
    //         uni.showToast({
    //             title: '状态更新失败',
    //             icon: 'none',
    //         })
    //     })
}
// 检查运行环境
const checkEnvironment = () => {
  const systemInfo = uni.getSystemInfoSync()
  // #ifdef APP-PLUS
  isH5.value = false
  // #endif
    const systemInfo = uni.getSystemInfoSync()
    // #ifdef APP-PLUS
    isH5.value = false
    // #endif
  // #ifdef H5
  isH5.value = true
  // #endif
    // #ifdef H5
    isH5.value = true
    // #endif
  // console.log('当前环境:', isH5.value ? 'H5' : 'App')
    // console.log('当前环境:', isH5.value ? 'H5' : 'App')
}
// 图片预览
const previewImage = index => {
  if (getImageList.value.length === 0) return
  const currentIndex = typeof index === 'number' ? index : 0
  uni.previewImage({
    urls: getImageList.value,
    current: currentIndex,
  })
    if (getImageList.value.length === 0) return
    const currentIndex = typeof index === 'number' ? index : 0
    uni.previewImage({
        urls: getImageList.value,
        current: currentIndex,
    })
}
const getImageList = computed(() => {
  const imageArr = []
  const detail = workDetailData.value
  if (detail.eventImageUrl) {
    imageArr.push(detail.aiImg || detail.eventImageUrl)
  }
  return imageArr
    const imageArr = []
    const detail = workDetailData.value
    if (detail.eventImageUrl) {
        imageArr.push(detail.aiImg || detail.eventImageUrl)
    }
    return imageArr
})
// 跳转地图
const jumpMap = item => {
  // const contactStr = encodeURIComponent(JSON.stringify(item))
    const contactStr = JSON.stringify(item)
  uni.navigateTo({
    url: `/subPackages/workDetail/mapWork/index?eventNum=${contactStr}`,
  })
    // const contactStr = encodeURIComponent(JSON.stringify(item))
    const contactStr = JSON.stringify(item)
    uni.navigateTo({
        url: `/subPackages/workDetail/mapWork/index?eventNum=${contactStr}`,
    })
}
// 退回
const rejectTicket = () => {
  const id = workDetailData.value.id
  backGdApi({eventId : id,eventStatus:'0'}).then(res => {
    uni.showToast({
      title: '退回成功',
      icon: 'success',
      duration: 1500
    })
    setTimeout(() => {
      uni.navigateBack()
    })
  })
    // const id = workDetailData.value.id
    // backGdApi({ eventId: id, eventStatus: '0' }).then(res => {
    //     uni.showToast({
    //         title: '退回成功',
    //         icon: 'success',
    //         duration: 1500,
    //     })
    //     setTimeout(() => {
    //         uni.navigateBack()
    //     })
    // })
    updateStatus({
        eventId: workDetailData.value.id,
        targetStatus: 2,
    }).then(res => {
        uni.showToast({
            title: '退回成功',
            icon: 'success',
            duration: 1500,
        })
        setTimeout(() => {
            uni.navigateBack()
        })
    })
}
// 确认工单
const confirmTheTicket = () => {
  const id = workDetailData.value.id
  backGdApi({eventId : id,eventStatus:'1'}).then(res => {
    uni.showToast({
      title: '确认成功',
      icon: 'success',
      duration: 1500
    })
    setTimeout(() => {
      uni.navigateBack()
    })
  })
    // const id = workDetailData.value.id
    // backGdApi({ eventId: id, eventStatus: '1' }).then(res => {
    //     uni.showToast({
    //         title: '确认成功',
    //         icon: 'success',
    //         duration: 1500,
    //     })
    //     setTimeout(() => {
    //         uni.navigateBack()
    //     })
    // })
    updateStatus({
        eventId: workDetailData.value.id,
        targetStatus: 1,
    }).then(res => {
        uni.showToast({
            title: '确认成功',
            icon: 'success',
            duration: 1500,
        })
        setTimeout(() => {
            // uni.navigateBack()
            workDetailData.value.eventStatus = 1
        })
    })
}
// 已确认
// 处置记录 - 跳转至处置记录页面
function handlelogs() {
    uni.navigateTo({
        url: `/subPackages/workDetail/HandleWork/index?id=${workDetailData.value.id}&eventStatus=${workDetailData.value.eventStatus}`,
    })
}
// 上传处置记录
function uploadHandle() {
    uni.navigateTo({
        url: `/subPackages/workDetail/HandleWorking/index?id=${workDetailData.value.id}&eventStatus=${workDetailData.value.eventStatus}`,
    })
}
// 打开完成情况弹窗
const confirmedHandle = () => {
  uni.navigateBack()
    completeContent.value = ''
    showCompleteModal.value = true
}
// 关闭完成情况弹窗
const closeCompleteModal = () => {
    if (completing.value) return
    showCompleteModal.value = false
}
// 提交完成情况
const submitComplete = async () => {
    const operateContent = completeContent.value.trim()
    if (!operateContent) {
        uni.showToast({ title: '请输入完成情况', icon: 'none' })
        return
    }
    completing.value = true
    try {
        await updateStatus({
            eventId: workDetailData.value.id,
            targetStatus: 4,
            operateContent,
        })
        uni.showToast({ title: '操作成功', icon: 'success' })
        setTimeout(() => {
            uni.navigateBack()
        }, 1000)
    } catch {
        completing.value = false
        uni.showToast({ title: '操作失败,请重试', icon: 'none' })
    }
}
// 分享模态框状态
const showShareModal = ref(false)
@@ -228,8 +434,8 @@
// 复制链接
const copyLink = async () => {
  const res = await shareCacheApi(workDetailData.value)
  const shareUrl = getWebViewUrl("/workDetail", { workDetailData: JSON.stringify(res.data.data) })
    const res = await shareCacheApi(workDetailData.value)
    const shareUrl = getWebViewUrl('/workDetail', { workDetailData: JSON.stringify(res.data.data) })
    try {
        await uni.setClipboardData({
            data: shareUrl,
@@ -279,7 +485,7 @@
            type: 0,
            title: workDetailData.value.event_name || '工单详情',
            summary: '查看工单详情',
      href: getWebViewUrl("/workDetail", { workDetailData: JSON.stringify(workDetailData.value) }),
            href: getWebViewUrl('/workDetail', { workDetailData: JSON.stringify(workDetailData.value) }),
            imageUrl: workDetailData.value.eventImageUrl,
            success: () => {
                uni.showToast({ title: '分享成功', icon: 'success' })
@@ -313,152 +519,280 @@
// 返回按钮点击事件
const onBackClick = () => {
  uni.navigateBack()
    uni.navigateBack()
}
// 监听导航栏右侧按钮点击事件(兼容原来的配置)
const onNavigationBarButtonTap = e => {
  if (e.index === 0) {
    onShareClick()
  }
    if (e.index === 0) {
        onShareClick()
    }
}
</script>
<style lang="scss" scoped>
.workDetailContainer {
  // 添加导航栏高度的padding-top,避免内容被遮挡
  padding-top: 88rpx;
  :deep(){
  .u-navbar__content{
    background: url("https://wrj.shuixiongit.com/aiskyminio/cloud-bucket/ztzf_app_assets/images/user/bg.png")  no-repeat !important;
    background-size: 100%!important;
    height: 88rpx !important;
  }
}  // 分享按钮样式
  .share-btn {
    // #ifdef APP-PLUS
    width: 40rpx;
    height: 40rpx;
    background-image: url('/static/images/work/share.svg');
    background-size: contain;
    background-repeat: no-repeat;
    background-position: center;
    // #endif
  }
  .tapShare-icon {
    width: 40rpx;
    height: 40rpx;
    img {
      width: 40rpx;
      height: 40rpx;
    }
  }
  .detailTop {
  // #ifdef APP-PLUS
  padding-top: 60rpx;
  // #endif
    .image-container {
      position: relative;
      width: 100%;
      height: 410rpx;
    // 添加导航栏高度的padding-top,避免内容被遮挡
    padding-top: 88rpx;
    :deep() {
        .u-navbar__content {
            background: url('https://wrj.shuixiongit.com/aiskyminio/cloud-bucket/ztzf_app_assets/images/user/bg.png')
                no-repeat !important;
            background-size: 100% !important;
            height: 88rpx !important;
        }
    } // 分享按钮样式
    .share-btn {
        // #ifdef APP-PLUS
        width: 40rpx;
        height: 40rpx;
        background-image: url('/static/images/work/share.svg');
        background-size: contain;
        background-repeat: no-repeat;
        background-position: center;
        // #endif
    }
    .tapShare-icon {
        width: 40rpx;
        height: 40rpx;
        img {
            width: 40rpx;
            height: 40rpx;
        }
    }
    .detailTop {
        // #ifdef APP-PLUS
        padding-top: 60rpx;
        // #endif
        .image-container {
            position: relative;
            width: 100%;
            height: 410rpx;
      .detailImage {
        width: 100%;
        height: 100%;
        display: block;
        object-fit: cover;
      }
      // 轮播图容器
      :deep(.u-swiper) {
        // 指示器容器 - 修改为居中对齐
        .u-swiper__indicator {
          position: absolute;
          left: 50%;
          transform: translateX(-50%);
          bottom: 20rpx;
          display: flex;
          justify-content: center;
          align-items: center;
          z-index: 10;
        }
        .u-swiper-indicator__wrapper__dot--active {
          width: 5px !important;
        }
      }
            .detailImage {
                width: 100%;
                height: 100%;
                display: block;
                object-fit: cover;
            }
            // 轮播图容器
            :deep(.u-swiper) {
                // 指示器容器 - 修改为居中对齐
                .u-swiper__indicator {
                    position: absolute;
                    left: 50%;
                    transform: translateX(-50%);
                    bottom: 20rpx;
                    display: flex;
                    justify-content: center;
                    align-items: center;
                    z-index: 10;
                }
                .u-swiper-indicator__wrapper__dot--active {
                    width: 5px !important;
                }
            }
        }
    }
    }
  }
    .worderContainer {
        padding: 0 24rpx;
        padding-bottom: 4rpx;
        background: #f6f6f6;
        margin-top: 20rpx;
        height: calc(100vh - 530rpx);
        overflow: auto;
    }
    .workOrderContent {
        margin-top: 30epx;
        background-color: #fff;
        border-radius: 12rpx;
        padding: 20rpx;
        margin-bottom: 34rpx;
        .workOrderTitle {
            font-family: Source Han Sans CN, Source Han Sans CN;
            font-weight: bold;
            font-size: 32rpx;
            color: #222324;
        }
        .workOrderContainer {
            .orderRow {
                display: flex;
                justify-content: space-between;
                align-items: center;
                min-height: 96rpx;
                border-bottom: 1px solid #f5f5f5;
                color: #7b7b7b;
                .rowTitle {
                    font-family: Source Han Sans CN, Source Han Sans CN;
                    font-weight: 400;
                    font-size: 30rpx;
                    color: #222324;
                    white-space: nowrap;
                }
                .rowAddress {
                    font-family: Source Han Sans CN, Source Han Sans CN;
                    font-weight: 400;
                    font-size: 28rpx;
                    color: #1d6fe9;
                    text-decoration: underline;
                    text-align: right;
  .worderContainer {
    padding: 0 24rpx;
    padding-bottom: 4rpx;
    background: #f6f6f6;
    margin-top: 20rpx;
  }
  .workOrderContent {
    margin-top: 30epx;
    background-color: #fff;
    border-radius: 12rpx;
    padding: 20rpx;
    margin-bottom: 34rpx;
    .workOrderTitle {
      font-family: Source Han Sans CN, Source Han Sans CN;
      font-weight: bold;
      font-size: 32rpx;
      color: #222324;
    }
    .workOrderContainer {
      .orderRow {
        display: flex;
        justify-content: space-between;
        align-items: center;
        min-height: 96rpx;
        border-bottom: 1px solid #f5f5f5;
        color: #7b7b7b;
        .rowTitle {
          font-family: Source Han Sans CN, Source Han Sans CN;
          font-weight: 400;
          font-size: 30rpx;
          color: #222324;
          white-space: nowrap;
        }
        .rowAddress {
          font-family: Source Han Sans CN, Source Han Sans CN;
          font-weight: 400;
          font-size: 28rpx;
          color: #1d6fe9;
          text-decoration: underline;
          text-align: right;
                    padding-top: 1px;
                    padding-left: 5px;
                    padding-right: 2px;
                }
                .status-picker {
                    .picker-value {
                        font-family: Source Han Sans CN, Source Han Sans CN;
                        font-weight: 400;
                        font-size: 28rpx;
                        color: #1d6fe9;
                        display: flex;
                        align-items: center;
                        gap: 8rpx;
                    }
                    .picker-arrow {
                        font-size: 20rpx;
                        color: #999;
                    }
                }
            }
        }
    }
          padding-top: 1px;
          padding-left: 5px;
          padding-right: 2px;
        }
      }
    }
  }
    .actionButton {
        display: flex;
        justify-content: center;
        align-items: center;
        box-sizing: border-box;
        width: 100%;
        height: 61px;
        padding: 0 24rpx;
  .actionButton {
    display: flex;
    justify-content: center;
    align-items: center;
    width: 100%;
    height: 61px;
        border-radius: 6px 6px 6px 6px;
    border-radius: 6px 6px 6px 6px;
        .btngroups {
            display: flex;
            align-items: center;
            width: 100%;
            min-width: 0;
            :deep(.u-button) {
                width: 0 !important;
                min-width: 0;
                margin: 0 !important;
                flex: 1;
                height: 76rpx !important;
            }
            &:not(.three-actions) {
                :deep(.u-button + .u-button) {
                    margin-left: 20rpx !important;
                }
            }
            &.three-actions {
                justify-content: center;
                :deep(.u-button) {
                    width: 210rpx !important;
                    flex: 0 0 210rpx;
                }
                :deep(.u-button:last-child) {
                    margin-left: 20rpx !important;
                }
            }
        }
    }
}
    .btngroups {
      display: flex;
      justify-content: space-between;
      align-items: center;
      :deep(.u-button) {
        width: 276rpx !important;
        height: 76rpx !important;
        &:last-child {
          margin-left: 30rpx;
        }
      }
    }
  }
.complete-modal-overlay {
    position: fixed;
    top: 0;
    right: 0;
    bottom: 0;
    left: 0;
    z-index: 10000;
    display: flex;
    align-items: center;
    justify-content: center;
    box-sizing: border-box;
    padding: 40rpx;
    background: rgba(0, 0, 0, 0.55);
}
.complete-modal {
    box-sizing: border-box;
    width: 620rpx;
    max-width: 100%;
    padding: 32rpx;
    background: #ffffff;
    border-radius: 12rpx;
}
.complete-modal-title {
    margin-bottom: 28rpx;
    font-size: 32rpx;
    font-weight: 500;
    color: #222324;
    text-align: center;
}
.complete-input-wrapper {
    position: relative;
    box-sizing: border-box;
    height: 220rpx;
    padding-bottom: 38rpx;
    background: #f6f7f9;
    border: 1rpx solid #e5e7eb;
    border-radius: 6rpx;
}
.complete-input {
    box-sizing: border-box;
    width: 100%;
    height: 100%;
    padding: 20rpx;
    font-size: 28rpx;
    line-height: 1.5;
    color: #222324;
}
.complete-word-count {
    position: absolute;
    right: 16rpx;
    bottom: 10rpx;
    font-size: 22rpx;
    color: #999999;
}
.complete-modal-actions {
    display: flex;
    gap: 24rpx;
    margin-top: 30rpx;
}
.complete-action-btn {
    height: 76rpx;
    margin: 0;
    flex: 1;
    font-size: 28rpx;
    line-height: 76rpx;
    border-radius: 6rpx;
}
.complete-action-btn::after {
    border: 0;
}
.complete-cancel-btn {
    color: #666666;
    background: #f1f2f4;
}
.complete-confirm-btn {
    color: #ffffff;
    background: #1d6fe9;
}
.complete-action-btn[disabled] {
    opacity: 0.65;
}
/* 分享弹出层样式 */
@@ -544,7 +878,6 @@
.share-icon.moments {
    background-image: url('@/static/images/work/moments.svg');
}
.share-icon.link {
    background-image: url('@/static/images/work/link.svg');