From 150ba60e3436b3e6a418b0a66723938b5dc2cfb8 Mon Sep 17 00:00:00 2001
From: chenyao <1219716595@qq.com>
Date: Fri, 31 Jul 2026 17:17:14 +0800
Subject: [PATCH] feat:处置功能
---
uniapps/work-app/src/api/work/index.js | 82 +
uniapps/work-app/src/pages/work/index.vue | 643 +++++----
uniapps/work-app/src/subPackages/workDetail/HandleWork/index.vue | 295 ++++
applications/task-work-order/src/views/orderView/orderManage/eventManage/index.vue | 231 +++
applications/task-work-order/src/views/orderView/orderManage/clueEvents/DistributeDiaLog.vue | 14
applications/task-work-order/src/views/orderView/orderManage/clueEvents/index.vue | 8
applications/task-work-order/src/views/orderView/orderManage/eventManage/OrderStepBar.vue | 225 +++
applications/task-work-order/src/views/orderView/orderManage/eventManage/ViewDiaLog.vue | 343 +++++
applications/task-work-order/src/views/orderView/orderManage/eventManage/eventManageApi.js | 25
applications/task-work-order/src/views/orderView/orderManage/clueEvents/ViewDiaLog.vue | 19
uniapps/work-app/src/subPackages/workDetail/HandleWorking/index.vue | 381 ++++++
uniapps/work-app/src/subPackages/workDetail/HandleWorking/index-copy.vue | 470 +++++++
uniapps/work-app/src/subPackages/workDetail/index.vue | 891 ++++++++++----
13 files changed, 3,032 insertions(+), 595 deletions(-)
diff --git a/applications/task-work-order/src/views/orderView/orderManage/clueEvents/DistributeDiaLog.vue b/applications/task-work-order/src/views/orderView/orderManage/clueEvents/DistributeDiaLog.vue
index 6ee6ffc..92039c2 100644
--- a/applications/task-work-order/src/views/orderView/orderManage/clueEvents/DistributeDiaLog.vue
+++ b/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 })
diff --git a/applications/task-work-order/src/views/orderView/orderManage/clueEvents/ViewDiaLog.vue b/applications/task-work-order/src/views/orderView/orderManage/clueEvents/ViewDiaLog.vue
index 7e13fa1..1eca57c 100644
--- a/applications/task-work-order/src/views/orderView/orderManage/clueEvents/ViewDiaLog.vue
+++ b/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>
diff --git a/applications/task-work-order/src/views/orderView/orderManage/clueEvents/index.vue b/applications/task-work-order/src/views/orderView/orderManage/clueEvents/index.vue
index 1d83c34..51ae432 100644
--- a/applications/task-work-order/src/views/orderView/orderManage/clueEvents/index.vue
+++ b/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 = () => ({
diff --git a/applications/task-work-order/src/views/orderView/orderManage/eventManage/OrderStepBar.vue b/applications/task-work-order/src/views/orderView/orderManage/eventManage/OrderStepBar.vue
new file mode 100644
index 0000000..4089173
--- /dev/null
+++ b/applications/task-work-order/src/views/orderView/orderManage/eventManage/OrderStepBar.vue
@@ -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>
diff --git a/applications/task-work-order/src/views/orderView/orderManage/eventManage/ViewDiaLog.vue b/applications/task-work-order/src/views/orderView/orderManage/eventManage/ViewDiaLog.vue
new file mode 100644
index 0000000..d3a0bbb
--- /dev/null
+++ b/applications/task-work-order/src/views/orderView/orderManage/eventManage/ViewDiaLog.vue
@@ -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>
diff --git a/applications/task-work-order/src/views/orderView/orderManage/eventManage/eventManageApi.js b/applications/task-work-order/src/views/orderView/orderManage/eventManage/eventManageApi.js
new file mode 100644
index 0000000..57089b7
--- /dev/null
+++ b/applications/task-work-order/src/views/orderView/orderManage/eventManage/eventManageApi.js
@@ -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,
+ })
+}
diff --git a/applications/task-work-order/src/views/orderView/orderManage/eventManage/index.vue b/applications/task-work-order/src/views/orderView/orderManage/eventManage/index.vue
new file mode 100644
index 0000000..aaf7997
--- /dev/null
+++ b/applications/task-work-order/src/views/orderView/orderManage/eventManage/index.vue
@@ -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>
diff --git a/uniapps/work-app/src/api/work/index.js b/uniapps/work-app/src/api/work/index.js
index ca93e1a..569e4a9 100644
--- a/uniapps/work-app/src/api/work/index.js
+++ b/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,
+ })
+ })
}
diff --git a/uniapps/work-app/src/pages/work/index.vue b/uniapps/work-app/src/pages/work/index.vue
index 31499da..205c21e 100644
--- a/uniapps/work-app/src/pages/work/index.vue
+++ b/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;
diff --git a/uniapps/work-app/src/subPackages/workDetail/HandleWork/index.vue b/uniapps/work-app/src/subPackages/workDetail/HandleWork/index.vue
new file mode 100644
index 0000000..09db68c
--- /dev/null
+++ b/uniapps/work-app/src/subPackages/workDetail/HandleWork/index.vue
@@ -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>
diff --git a/uniapps/work-app/src/subPackages/workDetail/HandleWorking/index-copy.vue b/uniapps/work-app/src/subPackages/workDetail/HandleWorking/index-copy.vue
new file mode 100644
index 0000000..e668b0d
--- /dev/null
+++ b/uniapps/work-app/src/subPackages/workDetail/HandleWorking/index-copy.vue
@@ -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>
diff --git a/uniapps/work-app/src/subPackages/workDetail/HandleWorking/index.vue b/uniapps/work-app/src/subPackages/workDetail/HandleWorking/index.vue
new file mode 100644
index 0000000..61781f5
--- /dev/null
+++ b/uniapps/work-app/src/subPackages/workDetail/HandleWorking/index.vue
@@ -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>
diff --git a/uniapps/work-app/src/subPackages/workDetail/index.vue b/uniapps/work-app/src/subPackages/workDetail/index.vue
index b1b1c57..9488846 100644
--- a/uniapps/work-app/src/subPackages/workDetail/index.vue
+++ b/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');
--
Gitblit v1.9.3