Commit 7a96cf5063e3d3a58ac990c89873850b222f24cc

Authored by 刘淇
1 parent 1f1f236c

重新提交

common/utils/common.js
1 export const nextStepMap = { 1 export const nextStepMap = {
2 ylTeamLeader: { 2 ylTeamLeader: {
3 name: '养护组长分配', 3 name: '养护组长分配',
4 - btnText:'分配', 4 + btnText: '分配',
5 operateTypePass: 110, 5 operateTypePass: 110,
6 - backShow:true 6 + operateTypeNoPass: 210, //养护组长退回:210
  7 + backShow: true,
  8 + renewShow: false
7 }, 9 },
8 ylWorker: { 10 ylWorker: {
9 name: '养护员待实施', 11 name: '养护员待实施',
10 - btnText:'实施', 12 + btnText: '实施',
11 operateTypePass: 120, 13 operateTypePass: 120,
12 - backShow:true 14 + backShow: true,
  15 + renewShow: false
13 }, 16 },
14 ylTeamLeaderConfirm: { 17 ylTeamLeaderConfirm: {
15 name: '养护组长验收', 18 name: '养护组长验收',
16 - btnText:'验收', 19 + btnText: '验收',
17 operateTypePass: 130, //养护组长验收通过: 130 20 operateTypePass: 130, //养护组长验收通过: 130
18 operateTypeNoPass: 230, // 养护组长验收不通过:230 21 operateTypeNoPass: 230, // 养护组长验收不通过:230
19 - backShow:false 22 + backShow: false,
  23 + renewShow: false
20 }, 24 },
21 - ylInspector:{ 25 + ylInspector: {
22 name: '巡查员验收', 26 name: '巡查员验收',
23 - btnText:'验收', 27 + btnText: '验收',
24 operateTypePass: 140, //巡查员验收通过: 140 28 operateTypePass: 140, //巡查员验收通过: 140
25 operateTypeNoPass: 240, // 巡查员验收不通过:230 29 operateTypeNoPass: 240, // 巡查员验收不通过:230
26 - backShow:false  
27 - } 30 + backShow: false,
  31 + renewShow: false
  32 + },
  33 + ylInspectorStart: {
  34 + name: '发起人确认',
  35 + btnText: '结束工单',
  36 + operateTypePass: 200, //巡查员结束工单:200
  37 + operateTypeNoPass: 240, // 巡查员验收不通过:230
  38 + operateTypeRenew: 100, //巡查员重新发起:100
  39 + backShow: false,
  40 + renewShow: true
  41 + },
28 } 42 }
29 \ No newline at end of file 43 \ No newline at end of file
pages-sub/problem/work-order-manage/add-order.vue
@@ -38,7 +38,6 @@ @@ -38,7 +38,6 @@
38 disabled-color="#ffffff" 38 disabled-color="#ffffff"
39 placeholder="请先选择工单位置" 39 placeholder="请先选择工单位置"
40 border="none" 40 border="none"
41 -  
42 ></up-input> 41 ></up-input>
43 <template #right> 42 <template #right>
44 <up-icon name="arrow-right" size="16" ></up-icon> 43 <up-icon name="arrow-right" size="16" ></up-icon>
@@ -167,12 +166,13 @@ @@ -167,12 +166,13 @@
167 166
168 <script setup> 167 <script setup>
169 import { ref, reactive, watch } from 'vue' 168 import { ref, reactive, watch } from 'vue'
170 -import { onReady, onShow } from '@dcloudio/uni-app'; 169 +import { onReady, onShow, onLoad } from '@dcloudio/uni-app';
171 import { useUploadImgs } from '@/common/utils/useUploadImgs' // 引入改造后的上传逻辑 170 import { useUploadImgs } from '@/common/utils/useUploadImgs' // 引入改造后的上传逻辑
172 import { getRoadListByLatLng } from '@/api/common' 171 import { getRoadListByLatLng } from '@/api/common'
173 -import { universalApproval } from '@/api/work-order-manage/work-order-manage' 172 +import { universalApproval, workorderCreate } from '@/api/work-order-manage/work-order-manage'
174 import { timeFormat } from '@/uni_modules/uview-plus' 173 import { timeFormat } from '@/uni_modules/uview-plus'
175 import { nextStepMap } from '@/common/utils/common' 174 import { nextStepMap } from '@/common/utils/common'
  175 +
176 // ========== 表单Ref ========== 176 // ========== 表单Ref ==========
177 const workOrderFormRef = ref(null) 177 const workOrderFormRef = ref(null)
178 178
@@ -203,6 +203,10 @@ const currentActionSheetData = reactive({ @@ -203,6 +203,10 @@ const currentActionSheetData = reactive({
203 const show = ref(false) 203 const show = ref(false)
204 const finishDate = ref(Date.now()) 204 const finishDate = ref(Date.now())
205 205
  206 +// ========== 重新提交相关状态(核心:本地存储读取) ==========
  207 +const isRenew = ref(false); // 是否为重新提交状态
  208 +const renewOrderData = ref(null); // 重新提交的原有工单数据
  209 +
206 // ========== 下拉列表数据 ========== 210 // ========== 下拉列表数据 ==========
207 const roadNameList = ref([]) 211 const roadNameList = ref([])
208 const orderNameList = ref([]) 212 const orderNameList = ref([])
@@ -244,6 +248,43 @@ const workOrderFormRules = reactive({ @@ -244,6 +248,43 @@ const workOrderFormRules = reactive({
244 }) 248 })
245 249
246 // ========== 生命周期 ========== 250 // ========== 生命周期 ==========
  251 +// 页面加载:读取本地存储的工单数据(核心改造)
  252 +onLoad((options) => {
  253 + // 判断是否为重新提交状态
  254 + console.log('434')
  255 + console.log(options)
  256 + if (options.isRenew ==1 && options.tempKey) {
  257 + isRenew.value = true;
  258 + const tempKey = options.tempKey;
  259 +
  260 + // 1. 从本地同步存储中读取完整工单数据
  261 + try {
  262 + const orderData = uni.getStorageSync(tempKey);
  263 + if (orderData && typeof orderData === 'object') {
  264 + renewOrderData.value = orderData;
  265 +
  266 + console.log('123213')
  267 + console.log(orderData)
  268 + // 2. 回显工单数据到表单
  269 + echoOrderData(renewOrderData.value);
  270 + } else {
  271 + uni.showToast({ title: '工单数据不存在,无法重新提交', icon: 'none' });
  272 + // 跳转回列表页
  273 + setTimeout(() => uni.navigateBack(), 1000);
  274 + return;
  275 + }
  276 + } catch (error) {
  277 + console.error('读取工单数据失败:', error);
  278 + uni.showToast({ title: '数据读取异常,无法重新提交', icon: 'none' });
  279 + setTimeout(() => uni.navigateBack(), 1000);
  280 + return;
  281 + } finally {
  282 + // 3. 关键:用完即删,避免本地冗余存储和数据泄露
  283 + uni.removeStorageSync(tempKey);
  284 + }
  285 + }
  286 +});
  287 +
247 onReady(() => { 288 onReady(() => {
248 // 设置表单校验规则 289 // 设置表单校验规则
249 if (workOrderFormRef.value) { 290 if (workOrderFormRef.value) {
@@ -265,6 +306,49 @@ onShow(() =&gt; { @@ -265,6 +306,49 @@ onShow(() =&gt; {
265 console.log('紧急程度列表:', pressingTypeList.value) 306 console.log('紧急程度列表:', pressingTypeList.value)
266 }) 307 })
267 308
  309 +// ========== 核心方法:工单数据回显 ==========
  310 +const echoOrderData = (orderItem) => {
  311 + // 1. 基础表单字段回显(适配工单字段与表单字段映射)
  312 + workOrderForm.roadId = orderItem.roadId || 0;
  313 + workOrderForm.roadName = orderItem.roadName || '';
  314 + workOrderForm.workLocation = orderItem.lonLatAddress || orderItem.roadName || '';
  315 + workOrderForm.orderName = orderItem.orderName || '';
  316 + workOrderForm.pressingType = orderItem.pressingType || '';
  317 + workOrderForm.pressingTypeName = uni.$dict.getDictLabel('workorder_pressing_type', orderItem.pressingType) || '';
  318 + workOrderForm.problemDesc = orderItem.remark || '';
  319 + workOrderForm.lat = orderItem.lat || 0;
  320 + workOrderForm.lon = orderItem.lon || 0;
  321 + workOrderForm.finishDate = orderItem.finishDate || timeFormat(new Date(), 'yyyy-mm-dd hh:MM:ss');
  322 +
  323 + // 2. 上传图片回显(兼容useUploadImgs格式)
  324 + if (orderItem.problemsImgs && Array.isArray(orderItem.problemsImgs) && orderItem.problemsImgs.length > 0) {
  325 + const imgList = orderItem.problemsImgs.map((imgUrl, index) => ({
  326 + url: imgUrl,
  327 + name: `renew_img_${index}`,
  328 + status: 'success' // 标记为已上传状态
  329 + }));
  330 + problemImgs.imgList = imgList;
  331 + problemImgs.rawImgList.value = imgList;
  332 + }
  333 +
  334 + // 3. 自动获取道路列表(保证下拉框正常使用)
  335 + if (orderItem.lat && orderItem.lon) {
  336 + getRoadListByLatLng({
  337 + companyCode: 'sls',
  338 + latitude: orderItem.lat,
  339 + longitude: orderItem.lon
  340 + }).then((roadRes) => {
  341 + if (Array.isArray(roadRes)) {
  342 + roadNameList.value = roadRes.map((item) => ({
  343 + name: item.roadName || '',
  344 + code: item.roadCode || '',
  345 + id: item.roadId || 0
  346 + }));
  347 + }
  348 + }).catch(err => console.error('回显道路列表失败:', err));
  349 + }
  350 +};
  351 +
268 // ========== 方法定义 ========== 352 // ========== 方法定义 ==========
269 /** 353 /**
270 * 打开通用下拉弹窗 354 * 打开通用下拉弹窗
@@ -327,9 +411,9 @@ const handleActionSheetSelect = (e) =&gt; { @@ -327,9 +411,9 @@ const handleActionSheetSelect = (e) =&gt; {
327 break 411 break
328 case 'pressingType': 412 case 'pressingType':
329 console.log(e) 413 console.log(e)
330 - workOrderForm.pressingType =e.value 414 + workOrderForm.pressingType = e.value
331 workOrderForm.pressingTypeName = e.name 415 workOrderForm.pressingTypeName = e.name
332 - workOrderFormRef.value?.validateField('pressingType') 416 + workOrderFormRef.value?.validateField('pressingTypeName')
333 break 417 break
334 } 418 }
335 // 关闭弹窗 419 // 关闭弹窗
@@ -410,14 +494,15 @@ const hideKeyboard = () =&gt; { @@ -410,14 +494,15 @@ const hideKeyboard = () =&gt; {
410 } 494 }
411 495
412 /** 496 /**
413 - * 提交工单 497 + * 提交工单(区分新增/重新提交,接口隔离)
414 */ 498 */
415 const submitWorkOrder = async () => { 499 const submitWorkOrder = async () => {
416 try { 500 try {
417 // 先执行表单校验 501 // 先执行表单校验
418 await workOrderFormRef.value.validate() 502 await workOrderFormRef.value.validate()
419 503
420 - const submitData = { 504 + // 构建公共提交数据
  505 + const commonSubmitData = {
421 roadId: workOrderForm.roadId, 506 roadId: workOrderForm.roadId,
422 roadName: workOrderForm.roadName, 507 roadName: workOrderForm.roadName,
423 imgs: problemImgs.getSuccessImgUrls(), // 复用上传逻辑的URL获取方法 508 imgs: problemImgs.getSuccessImgUrls(), // 复用上传逻辑的URL获取方法
@@ -436,18 +521,36 @@ const submitWorkOrder = async () =&gt; { @@ -436,18 +521,36 @@ const submitWorkOrder = async () =&gt; {
436 521
437 // 显示加载中 522 // 显示加载中
438 uni.showLoading({ title: '提交中...' }) 523 uni.showLoading({ title: '提交中...' })
439 -  
440 - // 调用提交接口  
441 - const res = await universalApproval(submitData) 524 + let res
  525 +
  526 + // 核心:根据状态区分接口调用
  527 + if (isRenew.value) {
  528 + // 重新提交:调用 universalApproval 接口
  529 + const renewSubmitData = {
  530 + // 原有工单必要参数
  531 + workerDataId: renewOrderData.value.id,
  532 + taskKey: renewOrderData.value.taskKey,
  533 + taskId: renewOrderData.value.taskId,
  534 + operateType: nextStepMap[renewOrderData.value.taskKey]?.operateTypeRenew || '',
  535 + agree: 0,
  536 + reason: '重新提交工单',
  537 + // 新编辑的工单数据
  538 + ...commonSubmitData
  539 + }
  540 + res = await universalApproval(renewSubmitData)
  541 + } else {
  542 + // 新增工单:调用原有 workorderCreate 接口(不影响原有功能)
  543 + res = await workorderCreate(commonSubmitData)
  544 + }
442 545
443 uni.hideLoading() 546 uni.hideLoading()
444 uni.showToast({ 547 uni.showToast({
445 - title: '工单提交成功', 548 + title: isRenew.value ? '重新提交成功' : '工单提交成功',
446 icon: 'success', 549 icon: 'success',
447 duration: 1000 550 duration: 1000
448 }) 551 })
449 552
450 - // 延迟跳转 553 + // 延迟跳转回列表页
451 setTimeout(() => { 554 setTimeout(() => {
452 uni.redirectTo({ 555 uni.redirectTo({
453 url: '/pages-sub/problem/work-order-manage/index' 556 url: '/pages-sub/problem/work-order-manage/index'
@@ -460,9 +563,9 @@ const submitWorkOrder = async () =&gt; { @@ -460,9 +563,9 @@ const submitWorkOrder = async () =&gt; {
460 // 区分是表单校验失败还是接口调用失败 563 // 区分是表单校验失败还是接口调用失败
461 if (!Array.isArray(error)) { 564 if (!Array.isArray(error)) {
462 // 接口调用失败 565 // 接口调用失败
463 - console.error('工单提交失败:', error) 566 + console.error(isRenew.value ? '工单重新提交失败:' : '工单提交失败:', error)
464 uni.showToast({ 567 uni.showToast({
465 - title: '提交失败,请重试', 568 + title: isRenew.value ? '重新提交失败,请重试' : '提交失败,请重试',
466 icon: 'none', 569 icon: 'none',
467 duration: 2000 570 duration: 2000
468 }) 571 })
pages-sub/problem/work-order-manage/index.vue
@@ -100,6 +100,11 @@ @@ -100,6 +100,11 @@
100 <up-button type="warning" size="mini" @click="handleReject(item)" 100 <up-button type="warning" size="mini" @click="handleReject(item)"
101 v-show="nextStepMap[item.taskKey].backShow">回退 101 v-show="nextStepMap[item.taskKey].backShow">回退
102 </up-button> 102 </up-button>
  103 +
  104 + <up-button type="success" size="mini" @click="handleRenew(item)"
  105 + v-show="nextStepMap[item.taskKey].renewShow">重新提交
  106 + </up-button>
  107 +
103 <up-button type="primary" size="mini" @click="handleProcess(item)">{{ 108 <up-button type="primary" size="mini" @click="handleProcess(item)">{{
104 nextStepMap[item.taskKey].btnText 109 nextStepMap[item.taskKey].btnText
105 }} 110 }}
@@ -139,7 +144,6 @@ @@ -139,7 +144,6 @@
139 <view class="u-line-1 u-body-value">{{ item.remark || '无' }}</view> 144 <view class="u-line-1 u-body-value">{{ item.remark || '无' }}</view>
140 </view> 145 </view>
141 146
142 -  
143 <view class="u-body-item u-flex common-justify-between common-item-center"> 147 <view class="u-body-item u-flex common-justify-between common-item-center">
144 <view class="u-body-item-title">紧急程度: 148 <view class="u-body-item-title">紧急程度:
145 {{ uni.$dict.getDictLabel('workorder_pressing_type', item.pressingType) }} 149 {{ uni.$dict.getDictLabel('workorder_pressing_type', item.pressingType) }}
@@ -165,18 +169,31 @@ @@ -165,18 +169,31 @@
165 </up-button> 169 </up-button>
166 </view> 170 </view>
167 171
168 - <!-- 回退原因弹窗 -->  
169 - <up-popup v-model="rejectPopupShow" mode="center" :close-on-click-overlay="false">  
170 - <view class="reject-popup">  
171 - <view class="popup-title">回退原因</view> 172 + <!-- 回退原因弹窗:替换为up-modal(核心修改) -->
  173 + <up-modal
  174 + :show="rejectModalShow"
  175 + title="回退原因"
  176 + :closeOnClickOverlay="false"
  177 + :showConfirmButton="true"
  178 + :showCancelButton="true"
  179 + @cancel="handleRejectModalCancel"
  180 + @confirm="confirmReject"
  181 + >
  182 + <view class="reject-modal-content">
  183 + <!-- 回退原因 必填textarea -->
  184 + <!-- <view class="textarea-label">-->
  185 + <!-- 回退原因 <text class="required-mark">*</text>-->
  186 + <!-- </view>-->
172 <up-textarea 187 <up-textarea
173 - v-model="rejectReason" 188 + v-model.trim="rejectReason"
174 placeholder="请输入回退原因(必填)" 189 placeholder="请输入回退原因(必填)"
175 - :required="true"  
176 - maxlength="-1" 190 +
177 rows="6" 191 rows="6"
178 - class="mt-20" 192 + :count="200"
  193 + maxlength="200"
  194 + class="reject-textarea"
179 /> 195 />
  196 + <!-- 上传图片(选填) -->
180 <view class="upload-wrap mt-20"> 197 <view class="upload-wrap mt-20">
181 <view class="upload-title">上传图片(选填)</view> 198 <view class="upload-title">上传图片(选填)</view>
182 <up-upload 199 <up-upload
@@ -188,14 +205,10 @@ @@ -188,14 +205,10 @@
188 max-count="3" 205 max-count="3"
189 /> 206 />
190 </view> 207 </view>
191 - <view class="popup-btn-wrap mt-40">  
192 - <up-button type="default" size="medium" @click="rejectPopupShow = false" class="mr-20">取消</up-button>  
193 - <up-button type="primary" size="medium" @click="confirmReject">确认提交</up-button>  
194 - </view>  
195 </view> 208 </view>
196 - </up-popup> 209 + </up-modal>
197 210
198 - <!-- 新增:养护组长验收弹窗 up-modal --> 211 + <!-- 养护组长验收弹窗 up-modal -->
199 <up-modal 212 <up-modal
200 :show="acceptModalShow" 213 :show="acceptModalShow"
201 title="验收" 214 title="验收"
@@ -225,8 +238,6 @@ @@ -225,8 +238,6 @@
225 count 238 count
226 /> 239 />
227 </view> 240 </view>
228 -  
229 -  
230 </view> 241 </view>
231 </up-modal> 242 </up-modal>
232 </view> 243 </view>
@@ -245,6 +256,7 @@ import { @@ -245,6 +256,7 @@ import {
245 // 假设从用户store获取角色信息 256 // 假设从用户store获取角色信息
246 import { useUserStore } from '@/pinia/user'; 257 import { useUserStore } from '@/pinia/user';
247 import { nextStepMap } from '@/common/utils/common' 258 import { nextStepMap } from '@/common/utils/common'
  259 +import { workorderCreate } from "../../../api/work-order-manage/work-order-manage";
248 // ========== 状态管理 ========== 260 // ========== 状态管理 ==========
249 const userStore = useUserStore(); 261 const userStore = useUserStore();
250 // 标签页切换 262 // 标签页切换
@@ -269,14 +281,14 @@ const paging = ref(null); @@ -269,14 +281,14 @@ const paging = ref(null);
269 const orderList = ref([]); 281 const orderList = ref([]);
270 // 角色控制(巡查员显示新增按钮) 282 // 角色控制(巡查员显示新增按钮)
271 const isInspector = computed(() => { 283 const isInspector = computed(() => {
272 - // 假设用户角色字段为role,巡查员标识为inspector  
273 - return userStore.userInfo.roles.includes('yl_inspector') 284 + // 增加可选链,避免用户信息不存在报错
  285 + return userStore.userInfo?.roles?.includes('yl_inspector') || false;
274 }); 286 });
275 -// 回退弹窗相关  
276 -const rejectPopupShow = ref(false);  
277 -const rejectReason = ref('');  
278 -const rejectFileList = ref([]);  
279 -const currentRejectItem = ref(null); 287 +// 回退弹窗相关(核心修改:将rejectPopupShow改为rejectModalShow)
  288 +const rejectModalShow = ref(false); // 回退modal显示开关
  289 +const rejectReason = ref(''); // 回退原因
  290 +const rejectFileList = ref([]); // 回退上传图片列表
  291 +const currentRejectItem = ref(null); // 当前回退工单
280 // 上传地址(根据实际接口配置) 292 // 上传地址(根据实际接口配置)
281 const uploadUrl = ref('https://xxx.com/upload'); 293 const uploadUrl = ref('https://xxx.com/upload');
282 // ========== 新增:养护组长验收弹窗相关状态 ========== 294 // ========== 新增:养护组长验收弹窗相关状态 ==========
@@ -337,73 +349,163 @@ const handleDetail = (item) =&gt; { @@ -337,73 +349,163 @@ const handleDetail = (item) =&gt; {
337 url: `/pages-sub/problem/work-order-manage/order-detail?taskId=${item.taskId}&activeTab=${activeTab.value}&processInstanceId=${item.processInstanceId}` 349 url: `/pages-sub/problem/work-order-manage/order-detail?taskId=${item.taskId}&activeTab=${activeTab.value}&processInstanceId=${item.processInstanceId}`
338 }); 350 });
339 }; 351 };
  352 +
  353 +// 待办-重新提交工单(跳转到新增页面并携带工单数据)
  354 +
  355 +const generateTempKey = () => {
  356 + return 'renew_order_' + Date.now() + '_' + Math.floor(Math.random() * 10000);
  357 +};
  358 +
  359 +// 待办-重新提交工单(改造后:大数据存本地,仅传唯一标识)
  360 +const handleRenew = (item) => {
  361 + // 校验工单有效性
  362 + if (!item || !item.id) {
  363 + uni.showToast({title: '工单信息异常,无法重新提交', icon: 'none'});
  364 + return;
  365 + }
  366 +
  367 + // 1. 生成唯一临时标识
  368 + const tempKey = generateTempKey();
  369 +
  370 + // 2. 将完整工单数据存入本地临时存储(同步存储,确保数据立即生效)
  371 + try {
  372 + uni.setStorageSync(tempKey, item);
  373 + } catch (error) {
  374 + console.error('存储工单数据失败:', error);
  375 + uni.showToast({title: '数据存储异常,无法重新提交', icon: 'none'});
  376 + return;
  377 + }
  378 +
  379 + // 3. URL 仅传递「唯一标识」和「重新提交标记」(数据量极小,无长度问题)
  380 + uni.navigateTo({
  381 + url: `/pages-sub/problem/work-order-manage/add-order?isRenew=1&tempKey=${tempKey}`
  382 + });
  383 +};
  384 +
340 // 待办-处理工单 385 // 待办-处理工单
341 const handleProcess = async (item) => { 386 const handleProcess = async (item) => {
342 console.log(nextStepMap[item.taskKey].name) 387 console.log(nextStepMap[item.taskKey].name)
343 try { 388 try {
344 - if (nextStepMap[item.taskKey].name == '养护组长分配') { 389 + if (nextStepMap[item.taskKey]?.name == '养护组长分配') {
345 uni.navigateTo({ 390 uni.navigateTo({
346 url: `/pages-sub/problem/work-order-manage/distribution-order?taskId=${item.taskId}&orderNo=${item.orderNo}` 391 url: `/pages-sub/problem/work-order-manage/distribution-order?taskId=${item.taskId}&orderNo=${item.orderNo}`
347 }) 392 })
348 } 393 }
349 - if (nextStepMap[item.taskKey].name == '养护员待实施') { 394 + if (nextStepMap[item.taskKey]?.name == '养护员待实施') {
350 uni.navigateTo({ 395 uni.navigateTo({
351 url: `/pages-sub/problem/work-order-manage/add-maintain-order?taskId=${item.taskId}&id=${item.id}&orderNo=${item.orderNo}` 396 url: `/pages-sub/problem/work-order-manage/add-maintain-order?taskId=${item.taskId}&id=${item.id}&orderNo=${item.orderNo}`
352 }) 397 })
353 } 398 }
354 // 养护组长验收 - 打开弹窗 399 // 养护组长验收 - 打开弹窗
355 - if (nextStepMap[item.taskKey].name == '养护组长验收') {  
356 - console.log('123') 400 + if (nextStepMap[item.taskKey]?.name == '养护组长验收') {
357 currentAcceptItem.value = item; // 存储当前工单信息 401 currentAcceptItem.value = item; // 存储当前工单信息
358 acceptReason.value = ''; // 清空上次的验收原因 402 acceptReason.value = ''; // 清空上次的验收原因
359 acceptRadioValue.value = '0'; // 重置默认选中“通过” 403 acceptRadioValue.value = '0'; // 重置默认选中“通过”
360 acceptModalShow.value = true; // 显示验收弹窗 404 acceptModalShow.value = true; // 显示验收弹窗
361 } 405 }
362 -  
363 // 巡查员验收 - 打开弹窗 406 // 巡查员验收 - 打开弹窗
364 - if (nextStepMap[item.taskKey].name == '巡查员验收') {  
365 - console.log('456') 407 + if (nextStepMap[item.taskKey]?.name == '巡查员验收') {
366 currentAcceptItem.value = item; // 存储当前工单信息 408 currentAcceptItem.value = item; // 存储当前工单信息
367 acceptReason.value = ''; // 清空上次的验收原因 409 acceptReason.value = ''; // 清空上次的验收原因
368 acceptRadioValue.value = '0'; // 重置默认选中“通过” 410 acceptRadioValue.value = '0'; // 重置默认选中“通过”
369 acceptModalShow.value = true; // 显示验收弹窗 411 acceptModalShow.value = true; // 显示验收弹窗
370 } 412 }
371 413
  414 + // 发起人确认
  415 + if (nextStepMap[item.taskKey]?.name == '发起人确认') {
  416 + console.log(item)
  417 + // currentAcceptItem.value = item; // 存储当前工单信息
  418 + uni.showModal({
  419 + title: "结束工单",
  420 + content: "请确定是否结束工单?",
  421 + success: async function (res) {
  422 + if (res.confirm) {
  423 + // 构建请求参数
  424 + const requestData = {
  425 + // fileUrls: rejectFileList.value.map(file => file.url || ''),
  426 + "workerDataId": item.id,
  427 + "taskKey": item.taskKey,
  428 + "taskId": item.taskId,
  429 + "operateType": nextStepMap[item.taskKey].operateTypeNoPass,
  430 + "agree": 1,
  431 + "reason": '结束工单'
  432 + };
  433 + // 调用回退工单接口
  434 + const res = await universalApproval(requestData);
  435 + uni.showToast({title: '结束成功', icon: 'success', duration: 1000});
  436 + rejectModalShow.value = false;
  437 + paging.value?.reload(); // 刷新列表
  438 + } else if (res.cancel) {
  439 + console.log("用户点击取消");
  440 + }
  441 + },
  442 + });
  443 + }
  444 +
  445 +
  446 +
372 } catch (error) { 447 } catch (error) {
373 console.error('处理工单失败:', error); 448 console.error('处理工单失败:', error);
374 uni.showToast({title: '处理失败,请重试', icon: 'none'}); 449 uni.showToast({title: '处理失败,请重试', icon: 'none'});
375 } 450 }
376 }; 451 };
377 -// 待办-回退工单 452 +// 待办-回退工单(打开回退modal)
378 const handleReject = (item) => { 453 const handleReject = (item) => {
  454 + console.log('123213')
  455 + // 校验工单有效性
  456 + if (!item || !item.id) {
  457 + uni.showToast({title: '工单信息异常,无法回退', icon: 'none'});
  458 + return;
  459 + }
379 currentRejectItem.value = item; 460 currentRejectItem.value = item;
  461 + rejectReason.value = ''; // 清空上次输入
  462 + rejectFileList.value = []; // 清空上次上传图片
  463 + rejectModalShow.value = true; // 显示回退modal
  464 +};
  465 +// 回退modal - 取消按钮
  466 +const handleRejectModalCancel = () => {
  467 + rejectModalShow.value = false;
380 rejectReason.value = ''; 468 rejectReason.value = '';
381 rejectFileList.value = []; 469 rejectFileList.value = [];
382 - rejectPopupShow.value = true;  
383 }; 470 };
384 // 确认回退工单 471 // 确认回退工单
385 const confirmReject = async () => { 472 const confirmReject = async () => {
386 - if (!rejectReason.value.trim()) {  
387 - uni.showToast({title: '请填写回退原因', icon: 'none'}); 473 + // 严格校验回退原因(去除首尾空格)
  474 + const rejectReasonTrim = rejectReason.value.trim();
  475 + if (!rejectReasonTrim) {
  476 + uni.showToast({title: '请填写回退原因', icon: 'none', duration: 2000});
  477 + return;
  478 + }
  479 + // 校验当前工单有效性
  480 + if (!currentRejectItem.value || !currentRejectItem.value.id) {
  481 + uni.showToast({title: '工单信息异常,无法提交', icon: 'none', duration: 2000});
  482 + rejectModalShow.value = false;
388 return; 483 return;
389 } 484 }
390 try { 485 try {
  486 + // 显示加载中,防止重复提交
  487 + uni.showLoading({title: '提交中...', mask: true});
  488 + // 构建请求参数
  489 + const requestData = {
  490 + // fileUrls: rejectFileList.value.map(file => file.url || ''),
  491 + "workerDataId": currentRejectItem.value.id,
  492 + "taskKey": currentRejectItem.value.taskKey,
  493 + "taskId": currentRejectItem.value.taskId,
  494 + "operateType": nextStepMap[currentRejectItem.value.taskKey].operateTypeNoPass,
  495 + "agree": 1,
  496 + "reason": rejectReasonTrim
  497 + };
391 // 调用回退工单接口 498 // 调用回退工单接口
392 - await uni.request({  
393 - url: '/api/order/reject',  
394 - method: 'POST',  
395 - data: {  
396 - orderId: currentRejectItem.value.id,  
397 - reason: rejectReason.value,  
398 - fileUrls: rejectFileList.value.map(file => file.url)  
399 - }  
400 - });  
401 - uni.showToast({title: '回退成功', icon: 'success'});  
402 - rejectPopupShow.value = false; 499 + const res = await universalApproval(requestData);
  500 + uni.showToast({title: '回退成功', icon: 'success', duration: 1000});
  501 + rejectModalShow.value = false;
403 paging.value?.reload(); // 刷新列表 502 paging.value?.reload(); // 刷新列表
404 } catch (error) { 503 } catch (error) {
405 console.error('回退工单失败:', error); 504 console.error('回退工单失败:', error);
406 - uni.showToast({title: '回退失败,请重试', icon: 'none'}); 505 + uni.showToast({title: '网络异常,回退失败', icon: 'none', duration: 1000});
  506 + } finally {
  507 + // 隐藏加载中
  508 + uni.hideLoading();
407 } 509 }
408 }; 510 };
409 // 新增工单 511 // 新增工单
@@ -412,20 +514,20 @@ const handleAddOrder = () =&gt; { @@ -412,20 +514,20 @@ const handleAddOrder = () =&gt; {
412 url: '/pages-sub/problem/work-order-manage/add-order' 514 url: '/pages-sub/problem/work-order-manage/add-order'
413 }); 515 });
414 }; 516 };
415 -// 上传图片-读取后 517 +// 上传图片-读取后(避免重复添加)
416 const handleAfterRead = (file) => { 518 const handleAfterRead = (file) => {
417 - rejectFileList.value.push(file); 519 + if (!file) return;
  520 + const isExist = rejectFileList.value.some(item => item.url === file.url);
  521 + if (!isExist && rejectFileList.value.length < 3) {
  522 + rejectFileList.value.push(file);
  523 + }
418 }; 524 };
419 -// 上传图片-删除 525 +// 上传图片-删除(安全删除,避免索引越界)
420 const handleDeleteFile = (index) => { 526 const handleDeleteFile = (index) => {
  527 + if (index < 0 || index >= rejectFileList.value.length) return;
421 rejectFileList.value.splice(index, 1); 528 rejectFileList.value.splice(index, 1);
422 }; 529 };
423 // ========== 新增:养护组长验收弹窗事件 ========== 530 // ========== 新增:养护组长验收弹窗事件 ==========
424 -// 验收弹窗 - 取消按钮  
425 -const handleAcceptModalCancel = () => {  
426 - acceptModalShow.value = false;  
427 - acceptReason.value = ''; // 清空验收原因  
428 -};  
429 // 验收弹窗 - 确定按钮(含表单校验) 531 // 验收弹窗 - 确定按钮(含表单校验)
430 const handleAcceptModalConfirm = async () => { 532 const handleAcceptModalConfirm = async () => {
431 // 1. 校验验收原因是否为空 533 // 1. 校验验收原因是否为空
@@ -433,7 +535,7 @@ const handleAcceptModalConfirm = async () =&gt; { @@ -433,7 +535,7 @@ const handleAcceptModalConfirm = async () =&gt; {
433 uni.showToast({title: '请填写验收原因', icon: 'none', duration: 2000}); 535 uni.showToast({title: '请填写验收原因', icon: 'none', duration: 2000});
434 return; 536 return;
435 } 537 }
436 - // 2. 校验验收原因长度(虽textarea已限制maxlength,此处做兜底校验) 538 + // 2. 校验验收原因长度
437 if (acceptReason.value.length > 200) { 539 if (acceptReason.value.length > 200) {
438 uni.showToast({title: '验收原因最多200字', icon: 'none', duration: 2000}); 540 uni.showToast({title: '验收原因最多200字', icon: 'none', duration: 2000});
439 return; 541 return;
@@ -442,7 +544,7 @@ const handleAcceptModalConfirm = async () =&gt; { @@ -442,7 +544,7 @@ const handleAcceptModalConfirm = async () =&gt; {
442 // 3. 调用验收接口 544 // 3. 调用验收接口
443 console.log(currentAcceptItem.value) 545 console.log(currentAcceptItem.value)
444 let postData = {} 546 let postData = {}
445 - if(currentAcceptItem.value.taskKey == 'ylTeamLeaderConfirm'){ // 养护组长验收 547 + if (currentAcceptItem.value?.taskKey == 'ylTeamLeaderConfirm') { // 养护组长验收
446 postData = { 548 postData = {
447 "taskKey": "ylTeamLeaderConfirm", 549 "taskKey": "ylTeamLeaderConfirm",
448 "taskId": currentAcceptItem.value.taskId, 550 "taskId": currentAcceptItem.value.taskId,
@@ -450,16 +552,15 @@ const handleAcceptModalConfirm = async () =&gt; { @@ -450,16 +552,15 @@ const handleAcceptModalConfirm = async () =&gt; {
450 "reason": acceptReason.value.trim() 552 "reason": acceptReason.value.trim()
451 } 553 }
452 } 554 }
453 - if(currentAcceptItem.value.taskKey == 'ylInspector'){ // 巡查员验收 555 + if (currentAcceptItem.value?.taskKey == 'ylInspector') { // 巡查员验收
454 postData = { 556 postData = {
455 "taskKey": "ylTeamLeaderConfirm", 557 "taskKey": "ylTeamLeaderConfirm",
456 "taskId": currentAcceptItem.value.taskId, 558 "taskId": currentAcceptItem.value.taskId,
457 "operateType": acceptRadioValue.value == 0 ? nextStepMap['ylTeamLeaderConfirm'].operateTypePass : nextStepMap['ylTeamLeaderConfirm'].operateTypeNoPass, 559 "operateType": acceptRadioValue.value == 0 ? nextStepMap['ylTeamLeaderConfirm'].operateTypePass : nextStepMap['ylTeamLeaderConfirm'].operateTypeNoPass,
458 "reason": acceptReason.value.trim(), 560 "reason": acceptReason.value.trim(),
459 - "agree":acceptRadioValue.value 561 + "agree": acceptRadioValue.value
460 } 562 }
461 } 563 }
462 -  
463 const acceptRes = await universalApproval(postData); 564 const acceptRes = await universalApproval(postData);
464 // 4. 操作成功处理 565 // 4. 操作成功处理
465 uni.showToast({title: '提交成功', icon: 'success', duration: 1500}); 566 uni.showToast({title: '提交成功', icon: 'success', duration: 1500});
@@ -482,6 +583,7 @@ onShow(() =&gt; { @@ -482,6 +583,7 @@ onShow(() =&gt; {
482 <style scoped lang="scss"> 583 <style scoped lang="scss">
483 .page-container { 584 .page-container {
484 min-height: 100vh; 585 min-height: 100vh;
  586 + background-color: #fafafa;
485 } 587 }
486 588
487 // 顶部固定区域 589 // 顶部固定区域
@@ -516,7 +618,59 @@ onShow(() =&gt; { @@ -516,7 +618,59 @@ onShow(() =&gt; {
516 } 618 }
517 } 619 }
518 620
519 -// 新增:养护组长验收弹窗样式 621 +// 工单卡片样式
  622 +.order-card {
  623 + margin: 0 20rpx 20rpx;
  624 + background: #fff;
  625 + border-radius: 12rpx;
  626 + box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.04);
  627 +}
  628 +
  629 +.card-body {
  630 +
  631 +}
  632 +
  633 +
  634 +// 回退modal样式
  635 +.reject-modal-content {
  636 + width: 100%;
  637 + box-sizing: border-box;
  638 + padding: 10rpx 0;
  639 +}
  640 +
  641 +.textarea-label {
  642 + font-size: 28rpx;
  643 + color: #333;
  644 + margin-bottom: 10rpx;
  645 +
  646 + .required-mark {
  647 + color: #f56c6c;
  648 + margin-left: 4rpx;
  649 + }
  650 +}
  651 +
  652 +.reject-textarea {
  653 + font-size: 28rpx;
  654 + padding: 16rpx;
  655 + border: 1rpx solid #e4e7ed;
  656 + border-radius: 8rpx;
  657 +}
  658 +
  659 +.upload-wrap {
  660 + margin-top: 20rpx;
  661 +
  662 + .upload-title {
  663 + font-size: 28rpx;
  664 + color: #333;
  665 + margin-bottom: 10rpx;
  666 + }
  667 +}
  668 +
  669 +.mt-20 {
  670 + margin-top: 20rpx;
  671 +}
  672 +
  673 +// 养护组长验收弹窗样式
520 .accept-modal-content { 674 .accept-modal-content {
521 width: 100%; 675 width: 100%;
522 box-sizing: border-box; 676 box-sizing: border-box;
@@ -532,6 +686,7 @@ onShow(() =&gt; { @@ -532,6 +686,7 @@ onShow(() =&gt; {
532 686
533 .textarea-wrap { 687 .textarea-wrap {
534 width: 100%; 688 width: 100%;
  689 + margin-top: 30rpx;
535 } 690 }
536 691
537 .modal-btn-wrap { 692 .modal-btn-wrap {