Commit 3d474e84d54ca77493cf71019fc199efbff8e661

Authored by 刘淇
1 parent 6c3b6516

快速工单 加上业务线

@@ -128,4 +128,6 @@ page { @@ -128,4 +128,6 @@ page {
128 font-size: 14px; 128 font-size: 14px;
129 color: #606266 129 color: #606266
130 } 130 }
  131 +
  132 +
131 </style> 133 </style>
132 \ No newline at end of file 134 \ No newline at end of file
api/work-order-manage/work-order-manage.js
@@ -110,3 +110,24 @@ export const todoBuzSimplePage = (params) =&gt; { @@ -110,3 +110,24 @@ export const todoBuzSimplePage = (params) =&gt; {
110 export const workorderCreate = (data) => { 110 export const workorderCreate = (data) => {
111 return post('/app-api/bpm/garden/workorder/create',data); 111 return post('/app-api/bpm/garden/workorder/create',data);
112 }; 112 };
  113 +
  114 +
  115 +/**
  116 + * 大区经理问题工单创建
  117 + * @returns {Promise}
  118 + */
  119 +export const regionmgrWorkorderCreat = (data) => {
  120 + return post('/app-api/bpm/regionmgr/workorder',data);
  121 +};
  122 +
  123 +/**
  124 + * app端统一审批入口 -- 大区经理
  125 + * @returns {Promise}
  126 + */
  127 +export const regionmgrUniversalApproval = (params) => {
  128 + return post('/app-api/bpm/regionmgr/workorder/universalApproval',params);
  129 +};
  130 +
  131 +
  132 +
  133 +
pages-sub/daily/maintain-manage/index.vue
@@ -83,7 +83,7 @@ @@ -83,7 +83,7 @@
83 83
84 <view class="u-body-item u-flex common-item-center common-justify-between"> 84 <view class="u-body-item u-flex common-item-center common-justify-between">
85 <view class="u-body-item-title">养护级别: {{uni.$dict.getDictLabel('conserve_level', item.levelId) || '-'}}</view> 85 <view class="u-body-item-title">养护级别: {{uni.$dict.getDictLabel('conserve_level', item.levelId) || '-'}}</view>
86 - <view class="u-line-1"> 86 + <view class="">
87 <up-button 87 <up-button
88 type="primary" 88 type="primary"
89 size="mini" 89 size="mini"
@@ -95,12 +95,12 @@ @@ -95,12 +95,12 @@
95 </view> 95 </view>
96 </view> 96 </view>
97 97
98 - <view class="u-body-item u-flex">  
99 - <view class="u-body-item-title">计划时间:</view>  
100 - <view class="u-line-1 u-body-value">{{ timeFormat(item.beginTime, 'yyyy-mm-dd') || '-' }} 至  
101 - {{ timeFormat(item.endTime, 'yyyy-mm-dd') || '-' }}  
102 - </view>  
103 - </view> 98 +<!-- <view class="u-body-item u-flex">-->
  99 +<!-- <view class="u-body-item-title">计划时间:</view>-->
  100 +<!-- <view class="u-line-1 u-body-value">{{ timeFormat(item.beginTime, 'yyyy-mm-dd') || '-' }} 至-->
  101 +<!-- {{ timeFormat(item.endTime, 'yyyy-mm-dd') || '-' }}-->
  102 +<!-- </view>-->
  103 +<!-- </view>-->
104 </view> 104 </view>
105 </template> 105 </template>
106 </up-card> 106 </up-card>
@@ -138,7 +138,7 @@ @@ -138,7 +138,7 @@
138 138
139 <view class="u-body-item u-flex common-item-center common-justify-between"> 139 <view class="u-body-item u-flex common-item-center common-justify-between">
140 <view class="u-body-item-title">计划完成次数: {{ item.planNum || 0 }}</view> 140 <view class="u-body-item-title">计划完成次数: {{ item.planNum || 0 }}</view>
141 - <view class="u-line-1" v-if="item.finishState!==3"> 141 + <view class="" v-if="item.finishState!==3">
142 <up-button 142 <up-button
143 type="primary" 143 type="primary"
144 size="mini" 144 size="mini"
pages-sub/daily/maintain-manage/pending-plan-detail.vue
@@ -41,13 +41,13 @@ @@ -41,13 +41,13 @@
41 <!-- 已完成次数 + 查看记录按钮 --> 41 <!-- 已完成次数 + 查看记录按钮 -->
42 <view class="u-body-item u-flex common-item-center common-justify-between"> 42 <view class="u-body-item u-flex common-item-center common-justify-between">
43 <view class="u-body-item-title">已完成次数: {{ i.planFinishNum || 0 }} 次</view> 43 <view class="u-body-item-title">已完成次数: {{ i.planFinishNum || 0 }} 次</view>
44 - <view class="u-line-1"> 44 + <view class="">
45 <up-button 45 <up-button
46 type="primary" 46 type="primary"
47 size="mini" 47 size="mini"
48 @click="gotoFinishPlanDetail(i)" 48 @click="gotoFinishPlanDetail(i)"
49 class="submit-record-btn" 49 class="submit-record-btn"
50 - :style="{ width: '80px', height: '28px', fontSize: '14px', borderRadius: 4 }" 50 +
51 > 51 >
52 查看记录 52 查看记录
53 </up-button> 53 </up-button>
pages-sub/daily/maintain-manage/road-detail-list.vue
@@ -76,7 +76,7 @@ @@ -76,7 +76,7 @@
76 76
77 <view class="u-body-item u-flex common-item-center common-justify-between"> 77 <view class="u-body-item u-flex common-item-center common-justify-between">
78 <view class="u-body-item-title">计划完成次数: {{ item.planNum || 0 }}</view> 78 <view class="u-body-item-title">计划完成次数: {{ item.planNum || 0 }}</view>
79 - <view class="u-line-1"> 79 + <view class="">
80 <up-button 80 <up-button
81 type="primary" 81 type="primary"
82 size="mini" 82 size="mini"
pages-sub/daily/patrol-manage/index.vue
@@ -71,7 +71,7 @@ @@ -71,7 +71,7 @@
71 71
72 <view class="u-body-item u-flex common-item-center common-justify-between"> 72 <view class="u-body-item u-flex common-item-center common-justify-between">
73 <view class="u-body-item-title">养护级别: {{uni.$dict.getDictLabel('conserve_level', item.levelId) || '-'}}</view> 73 <view class="u-body-item-title">养护级别: {{uni.$dict.getDictLabel('conserve_level', item.levelId) || '-'}}</view>
74 - <view class="u-line-1"> 74 + <view class="">
75 <up-button 75 <up-button
76 type="primary" 76 type="primary"
77 size="mini" 77 size="mini"
pages-sub/daily/patrol-manage/pending-plan-detail.vue
@@ -36,7 +36,7 @@ @@ -36,7 +36,7 @@
36 36
37 <view class="u-body-item u-flex common-item-center common-justify-between"> 37 <view class="u-body-item u-flex common-item-center common-justify-between">
38 <view class="u-body-item-title">计划完成次数: {{ item.planNum || 0 }}</view> 38 <view class="u-body-item-title">计划完成次数: {{ item.planNum || 0 }}</view>
39 - <view class="u-line-1"> 39 + <view class="">
40 <up-button 40 <up-button
41 v-if="item.finishState ==3" 41 v-if="item.finishState ==3"
42 type="primary" 42 type="primary"
pages-sub/daily/quick-order/add-order.vue
@@ -8,6 +8,27 @@ @@ -8,6 +8,27 @@
8 ref="workOrderFormRef" 8 ref="workOrderFormRef"
9 labelWidth="160rpx" 9 labelWidth="160rpx"
10 > 10 >
  11 +
  12 + <up-form-item
  13 + label="业务线"
  14 + prop="busiLineCn"
  15 + border-bottom
  16 + required
  17 + >
  18 + <up-radio-group
  19 + v-model="workOrderForm.busiLineCn"
  20 + placement="row"
  21 + @change="handleBusiLineChange"
  22 + >
  23 + <up-radio
  24 + v-for="item in busiLineOptions"
  25 + :key="item.name"
  26 + :label="item.name"
  27 + :name="item.name"
  28 + ></up-radio>
  29 + </up-radio-group>
  30 +
  31 + </up-form-item>
11 <!-- 1. 工单位置(地图选择) --> 32 <!-- 1. 工单位置(地图选择) -->
12 <up-form-item 33 <up-form-item
13 label="工单位置" 34 label="工单位置"
@@ -39,7 +60,6 @@ @@ -39,7 +60,6 @@
39 disabled-color="#ffffff" 60 disabled-color="#ffffff"
40 placeholder="请先选择工单位置" 61 placeholder="请先选择工单位置"
41 border="none" 62 border="none"
42 -  
43 ></up-input> 63 ></up-input>
44 <template #right> 64 <template #right>
45 <up-icon name="arrow-right" size="16" ></up-icon> 65 <up-icon name="arrow-right" size="16" ></up-icon>
@@ -154,24 +174,25 @@ @@ -154,24 +174,25 @@
154 </view> 174 </view>
155 </template> 175 </template>
156 176
157 -<script setup lang="ts">  
158 -import {ref} from 'vue'  
159 -  
160 -import type {UniFormRef} from '@/uni_modules/uview-plus/types'  
161 -// 定义ref供选项式API使用  
162 -const workOrderFormRef = ref<UniFormRef>(null)  
163 -  
164 -</script>  
165 -  
166 <script lang="ts"> 177 <script lang="ts">
167 -import {getRoadListByLatLng} from '@/api/common'  
168 -import {uploadImages} from '@/common/utils/upload';  
169 -import {createQuick} from '@/api/quick-order/quick-order'  
170 - 178 +import { getRoadListByLatLng } from '@/api/common'
  179 +import { uploadImages } from '@/common/utils/upload';
  180 +import { createQuick } from '@/api/quick-order/quick-order'
  181 +import { useUserStore } from '@/pinia/user';
171 182
172 export default { 183 export default {
173 data() { 184 data() {
174 return { 185 return {
  186 + // 业务线相关
  187 + busiLineMap: {
  188 + 'yl': '园林',
  189 + 'sz': '市政',
  190 + 'wy': '物业',
  191 + '园林': 'yl',
  192 + '市政': 'sz',
  193 + '物业': 'wy'
  194 + },
  195 + busiLineOptions: [], // 业务线选项列表
175 // 问题照片列表 196 // 问题照片列表
176 problemImgsList: [], 197 problemImgsList: [],
177 // 完成照片列表 198 // 完成照片列表
@@ -181,36 +202,36 @@ export default { @@ -181,36 +202,36 @@ export default {
181 showOrderName: false, 202 showOrderName: false,
182 // 下拉列表数据 203 // 下拉列表数据
183 roadNameList: [], 204 roadNameList: [],
184 - orderNameList: [  
185 - // {name: '绿地卫生', code: 'ORDER001'},  
186 - // {name: '设施维修', code: 'ORDER002'},  
187 - // {name: '垃圾清理', code: 'ORDER003'}  
188 - ], 205 + orderNameList: [],
189 // 工单表单数据 206 // 工单表单数据
190 workOrderForm: { 207 workOrderForm: {
  208 + busiLineCn: '', // 业务线中文名称
191 roadId: 0, // 道路ID 209 roadId: 0, // 道路ID
192 roadName: '', // 道路名称 210 roadName: '', // 道路名称
193 workLocation: '', // 工单位置 211 workLocation: '', // 工单位置
194 orderName: '', // 工单名称 212 orderName: '', // 工单名称
195 problemDesc: '', // 情况描述 213 problemDesc: '', // 情况描述
196 - handleResult: '', // 处理结果描述(不必填) 214 + handleResult: '', // 处理结果描述(不必填)
197 lat: 0, // 纬度 215 lat: 0, // 纬度
198 lon: 0 // 经度 216 lon: 0 // 经度
199 }, 217 },
200 // 表单校验规则 218 // 表单校验规则
201 workOrderFormRules: { 219 workOrderFormRules: {
  220 + busiLineCn: [ // 业务线必选校验
  221 + { type: 'string', required: true, message: '请选择业务线', trigger: ['change', 'blur'] }
  222 + ],
202 workLocation: [ 223 workLocation: [
203 - {type: 'string', required: true, message: '请选择工单位置', trigger: ['change', 'blur']} 224 + { type: 'string', required: true, message: '请选择工单位置', trigger: ['change', 'blur'] }
204 ], 225 ],
205 roadName: [ 226 roadName: [
206 - {type: 'string', required: true, message: '请选择道路名称', trigger: ['change', 'blur']} 227 + { type: 'string', required: true, message: '请选择道路名称', trigger: ['change', 'blur'] }
207 ], 228 ],
208 orderName: [ 229 orderName: [
209 - {type: 'string', required: true, message: '请选择工单名称', trigger: ['change', 'blur']} 230 + { type: 'string', required: true, message: '请选择工单名称', trigger: ['change', 'blur'] }
210 ], 231 ],
211 problemDesc: [ 232 problemDesc: [
212 - {type: 'string', required: true, message: '请输入情况描述', trigger: ['change', 'blur']},  
213 - {type: 'string', min: 3, max: 200, message: '情况描述需3-200字', trigger: ['change', 'blur']} 233 + { type: 'string', required: true, message: '请输入情况描述', trigger: ['change', 'blur'] },
  234 + { type: 'string', min: 3, max: 200, message: '情况描述需3-200字', trigger: ['change', 'blur'] }
214 ], 235 ],
215 problemImgs: [ 236 problemImgs: [
216 { 237 {
@@ -218,7 +239,6 @@ export default { @@ -218,7 +239,6 @@ export default {
218 message: '请上传问题照片', 239 message: '请上传问题照片',
219 trigger: 'change', 240 trigger: 'change',
220 validator: (rule, value, callback) => { 241 validator: (rule, value, callback) => {
221 - // 自定义校验规则:检查是否有成功上传的图片  
222 const hasSuccessImg = this.problemImgsList.some(item => item.status === 'success') 242 const hasSuccessImg = this.problemImgsList.some(item => item.status === 'success')
223 hasSuccessImg ? callback() : callback(new Error('请上传至少1张问题照片')) 243 hasSuccessImg ? callback() : callback(new Error('请上传至少1张问题照片'))
224 } 244 }
@@ -230,75 +250,145 @@ export default { @@ -230,75 +250,145 @@ export default {
230 message: '请上传完成照片', 250 message: '请上传完成照片',
231 trigger: 'change', 251 trigger: 'change',
232 validator: (rule, value, callback) => { 252 validator: (rule, value, callback) => {
233 - // 自定义校验规则:检查是否有成功上传的图片  
234 const hasSuccessImg = this.completeImgsList.some(item => item.status === 'success') 253 const hasSuccessImg = this.completeImgsList.some(item => item.status === 'success')
235 hasSuccessImg ? callback() : callback(new Error('请上传至少1张完成照片')) 254 hasSuccessImg ? callback() : callback(new Error('请上传至少1张完成照片'))
236 } 255 }
237 } 256 }
238 - ],  
239 - 257 + ]
240 } 258 }
241 } 259 }
242 }, 260 },
  261 + onLoad() {
  262 + // 初始化业务线选项
  263 + this.initBusiLineOptions();
  264 + // 默认选中第一个业务线
  265 + if (this.busiLineOptions && this.busiLineOptions.length > 0) {
  266 + this.workOrderForm.busiLineCn = this.busiLineOptions[0].name;
  267 + console.log('默认选中业务线:', this.workOrderForm.busiLineCn);
  268 + }
  269 + },
243 onReady() { 270 onReady() {
244 - // 兼容微信小程序,通过setRules设置校验规则  
245 - this.$refs.workOrderFormRef.setRules(this.workOrderFormRules)  
246 - console.log('工单表单规则初始化完成') 271 + // 设置表单校验规则
  272 + this.$refs.workOrderFormRef.setRules(this.workOrderFormRules);
  273 + console.log('工单表单规则初始化完成');
247 }, 274 },
248 - onShow(){  
249 - console.log(uni.$dict.getDictLabel('ai_image_status', 20))  
250 - console.log(uni.$dict.getDictSimpleList('work_name'))  
251 - this.orderNameList = uni.$dict.transformLabelValueToNameValue(uni.$dict.getDictSimpleList('work_name'))  
252 - console.log(this.orderNameList) 275 + onShow() {
  276 + this.orderNameList = uni.$dict.transformLabelValueToNameValue(uni.$dict.getDictSimpleList('work_name'));
  277 + console.log('工单名称列表:', this.orderNameList);
253 }, 278 },
254 methods: { 279 methods: {
  280 + // 初始化业务线选项
  281 + initBusiLineOptions() {
  282 + // 获取pinia中的用户信息
  283 + const userStore = useUserStore();
  284 + if (userStore.userInfo?.user?.busiLine) {
  285 + const rawBusiLines = userStore.userInfo.user.busiLine.split(',');
  286 + // 生成中文名称列表
  287 + this.busiLineOptions = rawBusiLines.map(item => ({
  288 + name: this.busiLineMap[item.trim()] // name=中文名称(园林/市政/物业)
  289 + }));
  290 + }
  291 + console.log('业务线选项:', this.busiLineOptions);
  292 + },
  293 + // 工具方法:通过中文名称获取对应的英文标识
  294 + getBusiLineEnByCn(cnName) {
  295 + return this.busiLineMap[cnName] || '';
  296 + },
  297 + // 业务线切换事件
  298 + handleBusiLineChange() {
  299 + // 清空道路信息
  300 + this.workOrderForm.roadName = '';
  301 + this.workOrderForm.roadId = 0;
  302 + this.roadNameList = [];
  303 + // 若已选择位置,重新请求对应业务线的道路列表
  304 + if (this.workOrderForm.workLocation) {
  305 + this.getRoadListByBusiLine();
  306 + }
  307 + },
  308 + // 根据选中业务线请求道路列表
  309 + async getRoadListByBusiLine() {
  310 + if (!this.workOrderForm.lat || !this.workOrderForm.lon) {
  311 + return;
  312 + }
  313 + // 中文转英文标识
  314 + const busiLineEn = this.getBusiLineEnByCn(this.workOrderForm.busiLineCn);
  315 + if (!busiLineEn) {
  316 + uni.showToast({ title: '业务线标识异常', icon: 'none' });
  317 + return;
  318 + }
  319 +
  320 + try {
  321 + uni.showLoading({ title: '获取道路名称中...' });
  322 + const roadRes = await getRoadListByLatLng({
  323 + busiLine: busiLineEn, // 传递业务线英文标识
  324 + latitude: this.workOrderForm.lat,
  325 + longitude: this.workOrderForm.lon
  326 + });
  327 + uni.hideLoading();
  328 + if (Array.isArray(roadRes)) {
  329 + this.roadNameList = roadRes.map((item) => ({
  330 + name: item.roadName || '',
  331 + code: item.roadCode || '',
  332 + id: item.roadId || 0
  333 + }));
  334 + } else {
  335 + this.roadNameList = [{ name: '未查询到道路名称', code: '', id: 0 }];
  336 + uni.showToast({ title: '未查询到该位置的道路信息', icon: 'none' });
  337 + }
  338 + } catch (err) {
  339 + uni.hideLoading();
  340 + console.error('获取道路名称失败:', err);
  341 + uni.showToast({ title: '获取道路名称失败,请重试', icon: 'none' });
  342 + this.roadNameList = [{ name: '获取失败,请重新选择位置', code: '', id: 0 }];
  343 + }
  344 + },
255 /** 345 /**
256 * 返回上一页 346 * 返回上一页
257 */ 347 */
258 navigateBack() { 348 navigateBack() {
259 - uni.navigateBack() 349 + uni.navigateBack();
260 }, 350 },
261 /** 351 /**
262 * 删除图片 352 * 删除图片
263 */ 353 */
264 deleteImg(event, type) { 354 deleteImg(event, type) {
265 - console.log('删除图片事件:', event, '类型:', type) 355 + console.log('删除图片事件:', event, '类型:', type);
266 if (type === 'problemImgsList') { 356 if (type === 'problemImgsList') {
267 - this.problemImgsList.splice(event.index, 1) 357 + this.problemImgsList.splice(event.index, 1);
268 } else if (type === 'completeImgsList') { 358 } else if (type === 'completeImgsList') {
269 - this.completeImgsList.splice(event.index, 1) 359 + this.completeImgsList.splice(event.index, 1);
270 } 360 }
271 // 删除图片后重新校验图片字段 361 // 删除图片后重新校验图片字段
272 if (type === 'problemImgsList') { 362 if (type === 'problemImgsList') {
273 - this.$refs.workOrderFormRef.validateField('problemImgs') 363 + this.$refs.workOrderFormRef.validateField('problemImgs');
274 } else if (type === 'completeImgsList') { 364 } else if (type === 'completeImgsList') {
275 - this.$refs.workOrderFormRef.validateField('completeImgs') 365 + this.$refs.workOrderFormRef.validateField('completeImgs');
276 } 366 }
277 - uni.showToast({title: '图片删除成功', icon: 'success'}) 367 + uni.showToast({ title: '图片删除成功', icon: 'success' });
278 }, 368 },
279 /** 369 /**
280 * 上传图片 370 * 上传图片
281 */ 371 */
282 async uploadImgs(event, type) { 372 async uploadImgs(event, type) {
283 - console.log('上传图片事件:', event, '类型:', type)  
284 - const fileList = Array.isArray(event.file) ? event.file : [event.file]  
285 - const targetImgList = type === 'problemImgsList' ? this.problemImgsList : this.completeImgsList 373 + console.log('上传图片事件:', event, '类型:', type);
  374 + const fileList = Array.isArray(event.file) ? event.file : [event.file];
  375 + const targetImgList = type === 'problemImgsList' ? this.problemImgsList : this.completeImgsList;
286 376
287 - const filePaths = fileList.map(item => item.url) 377 + const filePaths = fileList.map(item => item.url);
288 const tempItems = fileList.map(item => ({ 378 const tempItems = fileList.map(item => ({
289 ...item, 379 ...item,
290 status: 'uploading', 380 status: 'uploading',
291 message: '上传中' 381 message: '上传中'
292 - }))  
293 - const startIndex = targetImgList.length  
294 - targetImgList.push(...tempItems) 382 + }));
  383 + const startIndex = targetImgList.length;
  384 + targetImgList.push(...tempItems);
295 385
296 try { 386 try {
297 const uploadResultUrls = await uploadImages({ 387 const uploadResultUrls = await uploadImages({
298 filePaths: filePaths, 388 filePaths: filePaths,
299 ignoreError: true 389 ignoreError: true
300 - })  
301 - console.log('上传成功的URL列表:', uploadResultUrls) 390 + });
  391 + console.log('上传成功的URL列表:', uploadResultUrls);
302 392
303 uploadResultUrls.forEach((url, index) => { 393 uploadResultUrls.forEach((url, index) => {
304 if (targetImgList[startIndex + index]) { 394 if (targetImgList[startIndex + index]) {
@@ -307,150 +397,131 @@ export default { @@ -307,150 +397,131 @@ export default {
307 status: 'success', 397 status: 'success',
308 message: '', 398 message: '',
309 url: url 399 url: url
310 - }) 400 + });
311 } 401 }
312 - }) 402 + });
313 403
314 if (uploadResultUrls.length < fileList.length) { 404 if (uploadResultUrls.length < fileList.length) {
315 - const failCount = fileList.length - uploadResultUrls.length 405 + const failCount = fileList.length - uploadResultUrls.length;
316 for (let i = uploadResultUrls.length; i < fileList.length; i++) { 406 for (let i = uploadResultUrls.length; i < fileList.length; i++) {
317 if (targetImgList[startIndex + i]) { 407 if (targetImgList[startIndex + i]) {
318 targetImgList.splice(startIndex + i, 1, { 408 targetImgList.splice(startIndex + i, 1, {
319 ...fileList[i], 409 ...fileList[i],
320 status: 'failed', 410 status: 'failed',
321 message: '上传失败' 411 message: '上传失败'
322 - }) 412 + });
323 } 413 }
324 } 414 }
325 - uni.showToast({title: `成功上传${uploadResultUrls.length}张,失败${failCount}张`, icon: 'none'}) 415 + uni.showToast({ title: `成功上传${uploadResultUrls.length}张,失败${failCount}张`, icon: 'none' });
326 } else { 416 } else {
327 - uni.showToast({title: `成功上传${fileList.length}张图片`, icon: 'success'}) 417 + uni.showToast({ title: `成功上传${fileList.length}张图片`, icon: 'success' });
328 } 418 }
329 419
330 // 上传完成后重新校验图片字段 420 // 上传完成后重新校验图片字段
331 if (type === 'problemImgsList') { 421 if (type === 'problemImgsList') {
332 - this.$refs.workOrderFormRef.validateField('problemImgs') 422 + this.$refs.workOrderFormRef.validateField('problemImgs');
333 } else if (type === 'completeImgsList') { 423 } else if (type === 'completeImgsList') {
334 - this.$refs.workOrderFormRef.validateField('completeImgs') 424 + this.$refs.workOrderFormRef.validateField('completeImgs');
335 } 425 }
336 } catch (err) { 426 } catch (err) {
337 - console.error('图片上传失败:', err) 427 + console.error('图片上传失败:', err);
338 for (let i = 0; i < fileList.length; i++) { 428 for (let i = 0; i < fileList.length; i++) {
339 if (targetImgList[startIndex + i]) { 429 if (targetImgList[startIndex + i]) {
340 targetImgList.splice(startIndex + i, 1, { 430 targetImgList.splice(startIndex + i, 1, {
341 ...fileList[i], 431 ...fileList[i],
342 status: 'failed', 432 status: 'failed',
343 message: '上传失败' 433 message: '上传失败'
344 - }) 434 + });
345 } 435 }
346 } 436 }
347 - uni.showToast({title: '图片上传失败,请重试', icon: 'none'}) 437 + uni.showToast({ title: '图片上传失败,请重试', icon: 'none' });
348 438
349 // 上传失败后重新校验图片字段 439 // 上传失败后重新校验图片字段
350 if (type === 'problemImgsList') { 440 if (type === 'problemImgsList') {
351 - this.$refs.workOrderFormRef.validateField('problemImgs') 441 + this.$refs.workOrderFormRef.validateField('problemImgs');
352 } else if (type === 'completeImgsList') { 442 } else if (type === 'completeImgsList') {
353 - this.$refs.workOrderFormRef.validateField('completeImgs') 443 + this.$refs.workOrderFormRef.validateField('completeImgs');
354 } 444 }
355 } 445 }
356 }, 446 },
357 /** 447 /**
358 - * 选择工单位置(移除isSubmitting判断) 448 + * 选择工单位置
359 */ 449 */
360 chooseWorkLocation() { 450 chooseWorkLocation() {
361 - let that = this 451 + let that = this;
362 uni.chooseLocation({ 452 uni.chooseLocation({
363 success: async (res) => { 453 success: async (res) => {
364 - that.workOrderForm.roadName = ''  
365 - that.workOrderForm.roadId = 0  
366 - that.roadNameList = []  
367 -  
368 - that.workOrderForm.workLocation = res.name  
369 - that.workOrderForm.lat = res.latitude  
370 - that.workOrderForm.lon = res.longitude  
371 -  
372 - that.$refs.workOrderFormRef.validateField('workLocation')  
373 - that.$refs.workOrderFormRef.validateField('roadName')  
374 -  
375 - try {  
376 - uni.showLoading({title: '获取道路名称中...'})  
377 - const roadRes = await getRoadListByLatLng({  
378 - companyCode: 'sls',  
379 - latitude: res.latitude,  
380 - longitude: res.longitude  
381 - })  
382 - uni.hideLoading()  
383 -  
384 - if (Array.isArray(roadRes)) {  
385 - that.roadNameList = roadRes.map((item) => ({  
386 - name: item.roadName || '',  
387 - code: item.roadCode || '',  
388 - id: item.roadId || 0  
389 - }))  
390 - } else {  
391 - that.roadNameList = [{name: '未查询到道路名称', code: '', id: 0}]  
392 - uni.showToast({title: '未查询到该位置的道路信息', icon: 'none'})  
393 - }  
394 - } catch (err) {  
395 - uni.hideLoading()  
396 - console.error('获取道路名称失败:', err)  
397 - uni.showToast({title: '获取道路名称失败,请重试', icon: 'none'})  
398 - that.roadNameList = [{name: '获取失败,请重新选择位置', code: '', id: 0}]  
399 - } 454 + that.workOrderForm.roadName = '';
  455 + that.workOrderForm.roadId = 0;
  456 + that.roadNameList = [];
  457 +
  458 + that.workOrderForm.workLocation = res.name;
  459 + that.workOrderForm.lat = res.latitude;
  460 + that.workOrderForm.lon = res.longitude;
  461 +
  462 + that.$refs.workOrderFormRef.validateField('workLocation');
  463 + that.$refs.workOrderFormRef.validateField('roadName');
  464 +
  465 + // 调用封装的方法,自动携带业务线
  466 + await that.getRoadListByBusiLine();
400 }, 467 },
401 fail: (err) => { 468 fail: (err) => {
402 - console.error('选择位置失败:', err)  
403 - uni.showToast({title: '选择位置失败:' + err.errMsg, icon: 'none'}) 469 + console.error('选择位置失败:', err);
  470 + uni.showToast({ title: '选择位置失败:' + err.errMsg, icon: 'none' });
404 } 471 }
405 - }) 472 + });
406 }, 473 },
407 /** 474 /**
408 * 选择道路名称 475 * 选择道路名称
409 */ 476 */
410 handleRoadNameSelect(e) { 477 handleRoadNameSelect(e) {
411 - console.log('选择道路名称:', e)  
412 - this.workOrderForm.roadName = e.name  
413 - this.workOrderForm.roadId = e.code  
414 - this.showRoadName = false  
415 - this.$refs.workOrderFormRef.validateField('roadName') 478 + console.log('选择道路名称:', e);
  479 + this.workOrderForm.roadName = e.name;
  480 + this.workOrderForm.roadId = e.code;
  481 + this.showRoadName = false;
  482 + this.$refs.workOrderFormRef.validateField('roadName');
416 }, 483 },
417 /** 484 /**
418 * 选择工单名称 485 * 选择工单名称
419 */ 486 */
420 handleOrderNameSelect(e) { 487 handleOrderNameSelect(e) {
421 - console.log(e)  
422 - this.workOrderForm.orderName = e.name  
423 - this.showOrderName = false  
424 - this.$refs.workOrderFormRef.validateField('orderName') 488 + console.log(e);
  489 + this.workOrderForm.orderName = e.name;
  490 + this.showOrderName = false;
  491 + this.$refs.workOrderFormRef.validateField('orderName');
425 }, 492 },
426 /** 493 /**
427 * 隐藏键盘 494 * 隐藏键盘
428 */ 495 */
429 hideKeyboard() { 496 hideKeyboard() {
430 - uni.hideKeyboard() 497 + uni.hideKeyboard();
431 }, 498 },
432 /** 499 /**
433 * 提取图片URL数组 500 * 提取图片URL数组
434 */ 501 */
435 getImgUrlList(imgList) { 502 getImgUrlList(imgList) {
436 - return imgList.filter(item => item.status === 'success').map(item => item.url) 503 + return imgList.filter(item => item.status === 'success').map(item => item.url);
437 }, 504 },
438 /** 505 /**
439 - * 提交工单(添加成功跳转逻辑)  
440 - */  
441 - /**  
442 - * 提交工单(添加成功跳转逻辑) 506 + * 提交工单
443 */ 507 */
444 async submitWorkOrder() { 508 async submitWorkOrder() {
445 try { 509 try {
446 - // 先执行表单校验(改用async/await写法,更易维护)  
447 - await this.$refs.workOrderFormRef.validate() 510 + // 先执行表单校验
  511 + await this.$refs.workOrderFormRef.validate();
  512 +
  513 + // 中文转英文标识
  514 + const busiLineEn = this.getBusiLineEnByCn(this.workOrderForm.busiLineCn);
  515 + if (!busiLineEn) {
  516 + uni.showToast({ title: '业务线选择异常,请重新选择', icon: 'none' });
  517 + return;
  518 + }
448 519
449 const submitData = { 520 const submitData = {
450 roadId: this.workOrderForm.roadId, 521 roadId: this.workOrderForm.roadId,
451 roadName: this.workOrderForm.roadName, 522 roadName: this.workOrderForm.roadName,
452 problemsImgs: this.getImgUrlList(this.problemImgsList), 523 problemsImgs: this.getImgUrlList(this.problemImgsList),
453 - endImgs : this.getImgUrlList(this.completeImgsList), 524 + endImgs: this.getImgUrlList(this.completeImgsList),
454 remark: this.workOrderForm.problemDesc.trim(), 525 remark: this.workOrderForm.problemDesc.trim(),
455 handleResult: this.workOrderForm.handleResult.trim(), 526 handleResult: this.workOrderForm.handleResult.trim(),
456 latLonType: 2, 527 latLonType: 2,
@@ -460,50 +531,43 @@ export default { @@ -460,50 +531,43 @@ export default {
460 pressingType: 2, 531 pressingType: 2,
461 orderName: this.workOrderForm.orderName, 532 orderName: this.workOrderForm.orderName,
462 sourceId: 1, 533 sourceId: 1,
463 - sourceName: '园林', 534 + sourceName: this.workOrderForm.busiLineCn, // 业务线中文名称
464 thirdWorkNo: '', 535 thirdWorkNo: '',
465 - busiLine:'yl'  
466 - } 536 + busiLine: busiLineEn // 业务线英文标识(yl/sz/wy)
  537 + };
467 538
468 // 显示加载中 539 // 显示加载中
469 - uni.showLoading({title: '提交中...'}) 540 + uni.showLoading({ title: '提交中...' });
470 541
471 - // 调用提交接口(await需要async函数支持)  
472 - const res = await createQuick(submitData) 542 + // 调用提交接口
  543 + const res = await createQuick(submitData);
473 544
474 - uni.hideLoading() 545 + uni.hideLoading();
475 uni.showToast({ 546 uni.showToast({
476 title: '工单提交成功', 547 title: '工单提交成功',
477 icon: 'success', 548 icon: 'success',
478 duration: 1000 549 duration: 1000
479 - }) 550 + });
480 551
481 - // 延迟跳转(等待提示框显示完成) 552 + // 延迟跳转
482 setTimeout(() => { 553 setTimeout(() => {
483 uni.reLaunch({ 554 uni.reLaunch({
484 url: '/pages-sub/daily/quick-order/index' 555 url: '/pages-sub/daily/quick-order/index'
485 - })  
486 - }, 1000) 556 + });
  557 + }, 1000);
487 } catch (error) { 558 } catch (error) {
488 // 隐藏加载框 559 // 隐藏加载框
489 - uni.hideLoading() 560 + uni.hideLoading();
490 561
491 // 区分是表单校验失败还是接口调用失败 562 // 区分是表单校验失败还是接口调用失败
492 - if (Array.isArray(error)) {  
493 - // 表单校验失败  
494 - // uni.showToast({  
495 - // title: '表单填写不完整,请检查',  
496 - // icon: 'none',  
497 - // duration: 2000  
498 - // })  
499 - } else { 563 + if (!Array.isArray(error)) {
500 // 接口调用失败 564 // 接口调用失败
501 - console.error('工单提交失败:', error) 565 + console.error('工单提交失败:', error);
502 uni.showToast({ 566 uni.showToast({
503 title: '提交失败,请重试', 567 title: '提交失败,请重试',
504 icon: 'none', 568 icon: 'none',
505 duration: 2000 569 duration: 2000
506 - }) 570 + });
507 } 571 }
508 } 572 }
509 } 573 }
@@ -522,6 +586,4 @@ export default { @@ -522,6 +586,4 @@ export default {
522 background: #fff; 586 background: #fff;
523 margin-bottom: 100rpx; 587 margin-bottom: 100rpx;
524 } 588 }
525 -  
526 -  
527 </style> 589 </style>
528 \ No newline at end of file 590 \ No newline at end of file
pages-sub/problem/regional-order-manage/index.vue
1 -<script lang="ts">  
2 -import {defineComponent} from 'vue' 1 +<template>
  2 + <view class="page-container">
  3 + <!-- 顶部固定区域 -->
  4 + <up-sticky>
  5 + <view class="header-wrap">
  6 + <!-- 第一行:u-tabs 待办/已办切换 :scrollable="false"-->
  7 + <up-tabs
  8 + v-model="activeTab"
  9 + :list="tabList"
  10 + active-color="#1989fa"
  11 + inactive-color="#666"
  12 + font-size="30rpx"
  13 + @click="handleTabChange"
  14 + />
3 15
4 -export default defineComponent({  
5 - name: "index"  
6 -})  
7 -</script> 16 + <!-- 第二行:下拉框 + 搜索框 -->
  17 + <view class="search-header">
  18 + <!-- 左侧下拉框 -->
  19 + <view class="select-wrap common-text-color">
  20 + <up-select
  21 + v-model:current="selectedSortValue"
  22 + :options="sortOptions"
  23 + :showOptionsLabel="true"
  24 + @select="handleSortChange"
  25 + border="surround"
  26 + :style="{ flex: 1 }"
  27 + />
  28 + </view>
8 29
9 -<template> 30 + <!-- 右侧搜索框 -->
  31 + <view class="search-input-wrap">
  32 + <up-search
  33 + v-model="searchValue"
  34 + placeholder="请输入关键字"
  35 + @search="handleSearch"
  36 + bg-color="#f5f5f5"
  37 + :clearabled="false"
  38 + :show-action="true"
  39 + actionText="搜索"
  40 + :animation="true"
  41 + @custom="handleSearch"
  42 + />
  43 + </view>
  44 + </view>
  45 + </view>
  46 + </up-sticky>
  47 +
  48 + <!-- 列表容器 -->
  49 + <z-paging
  50 + ref="paging"
  51 + v-model="orderList"
  52 + @query="queryList"
  53 + :auto-show-system-loading="true"
  54 +
  55 + >
  56 + <template #empty>
  57 + <empty-view/>
  58 + </template>
  59 +
  60 + <view class="common-card-list" style="padding-top: 200rpx;padding-bottom: 30rpx">
  61 + <!-- 待办工单卡片 -->
  62 + <up-card
  63 + v-if="activeTab == 0"
  64 + :border="false"
  65 + :foot-border-top="false"
  66 + v-for="(item, index) in orderList"
  67 + :key="`todo_${item.orderNo}_${index}`"
  68 + :show-head="false"
  69 + class="order-card"
  70 + >
  71 + <template #body>
  72 + <view class="card-body">
  73 + <view class="u-body-item u-flex">
  74 + <view class="u-body-item-title">工单编号:</view>
  75 + <view class="u-line-1 u-body-value">{{ item.orderNo || '-' }}</view>
  76 + </view>
  77 + <view class="u-body-item u-flex">
  78 + <view class="u-body-item-title">工单位置:</view>
  79 + <view class="u-line-1 u-body-value">{{ item.lonLatAddress || '-' }}</view>
  80 + </view>
  81 + <view class="u-body-item u-flex">
  82 + <view class="u-body-item-title">工单名称:</view>
  83 + <view class="u-line-1 u-body-value">{{ item.orderName || '未填写' }}</view>
  84 + </view>
  85 + <view class="u-body-item u-flex">
  86 + <view class="u-body-item-title">情况描述:</view>
  87 + <view class="u-line-1 u-body-value">{{ item.remark || '无' }}</view>
  88 + </view>
  89 + <view class="u-body-item u-flex common-item-center common-justify-between">
  90 + <view class="u-body-item-title">紧急程度:</view>
  91 + <view class="u-line-1 u-body-value">
  92 + {{ uni.$dict.getDictLabel('workorder_pressing_type', item.pressingType) }}
  93 + </view>
  94 + </view>
  95 + <view class="u-body-item u-flex">
  96 + <view class="u-body-item-title">工单状态:</view>
  97 + <view class="u-line-1 u-body-value">{{buzStatusMap[item.buzStatus] }}</view>
  98 + </view>
  99 + <view class="u-body-item u-flex">
  100 + <view class="u-body-item-title">提交时间:</view>
  101 + <view class="u-line-1 u-body-value">{{ timeFormat(item.createTime, 'yyyy-mm-dd hh:MM:ss') }}</view>
  102 + </view>
  103 + <!-- 操作按钮行 -->
  104 + <view class="u-body-item u-flex common-justify-between common-item-center mt-20">
  105 + <up-button type="warning" size="mini" @click="handleReject(item)"
  106 + v-show="nextStepMap[item.taskKey].backShow">回退
  107 + </up-button>
  108 +
  109 + <up-button type="success" size="mini" @click="handleRenew(item)"
  110 + v-show="nextStepMap[item.taskKey].renewShow">重新提交
  111 + </up-button>
  112 +
  113 + <up-button type="primary" size="mini" @click="handleProcess(item)">{{
  114 + nextStepMap[item.taskKey].btnText
  115 + }}
  116 + </up-button>
  117 + <up-button type="info" size="mini" @click="handleDetail(item)">详情</up-button>
  118 + </view>
  119 + </view>
  120 + </template>
  121 + </up-card>
  122 +
  123 + <!-- 已办工单卡片和我发起的 -->
  124 + <up-card
  125 + v-if="activeTab == 2||activeTab == 1"
  126 + :border="false"
  127 + :foot-border-top="false"
  128 + v-for="(item, index) in orderList"
  129 + :key="`done_${item.orderNo}_${index}`"
  130 + :show-head="false"
  131 + class="order-card"
  132 + >
  133 + <template #body>
  134 + <view class="card-body">
  135 + <view class="u-body-item u-flex">
  136 + <view class="u-body-item-title">工单编号:</view>
  137 + <view class="u-line-1 u-body-value">{{ item.orderNo || '-' }}</view>
  138 + </view>
  139 + <view class="u-body-item u-flex">
  140 + <view class="u-body-item-title">工单位置:</view>
  141 + <view class="u-line-1 u-body-value">{{ item.roadName || '-' }}</view>
  142 + </view>
  143 + <view class="u-body-item u-flex">
  144 + <view class="u-body-item-title">工单名称:</view>
  145 + <view class="u-line-1 u-body-value">{{ item.orderName || '未填写' }}</view>
  146 + </view>
  147 + <view class="u-body-item u-flex">
  148 + <view class="u-body-item-title">情况描述:</view>
  149 + <view class="u-line-1 u-body-value">{{ item.remark || '无' }}</view>
  150 + </view>
  151 +
  152 + <view class="u-body-item u-flex common-justify-between common-item-center">
  153 + <view class="u-body-item-title">紧急程度:
  154 + {{ uni.$dict.getDictLabel('workorder_pressing_type', item.pressingType) }}
  155 + </view>
  156 + <view class=" ">
  157 + <up-button type="primary" size="mini" @click="handleDetail(item)">工单详情</up-button>
  158 + </view>
  159 + </view>
  160 + <view class="u-body-item u-flex">
  161 + <view class="u-body-item-title">工单状态:</view>
  162 + <view class="u-line-1 u-body-value">{{buzStatusMap[item.buzStatus] }}</view>
  163 + </view>
  164 + <view class="u-body-item u-flex">
  165 + <view class="u-body-item-title">提交时间:</view>
  166 + <view class="u-line-1 u-body-value">{{ timeFormat(item.createTime, 'yyyy-mm-dd hh:MM:ss') }}</view>
  167 + </view>
  168 + </view>
  169 + </template>
  170 + </up-card>
  171 + </view>
  172 + </z-paging>
  173 +
  174 + <!-- 底部新增工单按钮(仅巡查员显示) -->
  175 +<!-- <view v-if="isInspector" class="fixed-bottom-btn-wrap">-->
  176 +<!-- <up-button type="primary" size="large" @click="handleAddOrder">-->
  177 +<!-- 新增工单-->
  178 +<!-- </up-button>-->
  179 +<!-- </view>-->
  180 +
  181 + <view class="fixed-bottom-btn-wrap">
  182 + <up-button type="primary" size="large" @click="handleAddOrder">
  183 + 新增工单
  184 + </up-button>
  185 + </view>
  186 +
  187 +
  188 + <!-- 回退原因弹窗:替换为up-modal(核心修改) -->
  189 + <up-modal
  190 + :show="rejectModalShow"
  191 + title="回退原因"
  192 + :closeOnClickOverlay="false"
  193 + :showConfirmButton="true"
  194 + :showCancelButton="true"
  195 + @cancel="handleRejectModalCancel"
  196 + @confirm="confirmReject"
  197 + >
  198 + <view class="reject-modal-content">
  199 + <!-- 回退原因 必填textarea -->
  200 + <up-textarea
  201 + v-model.trim="rejectReason"
  202 + placeholder="请输入回退原因(必填)"
  203 + rows="6"
  204 + :count="200"
  205 + maxlength="200"
  206 + class="reject-textarea"
  207 + />
  208 + <!-- 上传图片(选填)- 按照参考页面改造 -->
  209 + <view class="upload-wrap mt-20">
  210 + <view class="upload-title">上传图片(选填)</view>
  211 + <up-upload
  212 + :file-list="rejectImgs.rawImgList.value|| []"
  213 + @after-read="rejectImgs.uploadImgs"
  214 + @delete="rejectImgs.deleteImg"
  215 + multiple
  216 + width="70"
  217 + height="70"
  218 + :max-count="rejectImgs.uploadConfig.maxCount"
  219 + :upload-text="rejectImgs.uploadConfig.uploadText"
  220 + :size-type="rejectImgs.uploadConfig.sizeType"
  221 + />
  222 + </view>
  223 + </view>
  224 + </up-modal>
  225 +
  226 + <!-- 验收弹窗 up-modal(含图片上传) -->
  227 + <up-modal
  228 + :show="acceptModalShow"
  229 + title="验收"
  230 + :closeOnClickOverlay="false"
  231 + :showConfirmButton="true"
  232 + :showCancelButton="true"
  233 + @cancel="handleAcceptModalCancel"
  234 + @confirm="handleAcceptModalConfirm"
  235 + >
  236 + <view class="accept-modal-content">
  237 + <!-- 第一行:单选框(通过/不通过,默认通过) -->
  238 + <view class="radio-group-wrap">
  239 + <up-radio-group v-model="acceptRadioValue">
  240 + <up-radio name="0" label="通过"></up-radio>
  241 + <up-radio name="1" label="不通过"></up-radio>
  242 + </up-radio-group>
  243 + </view>
  244 +
  245 + <!-- 第二行:必填textarea,最多200字 -->
  246 + <view class="textarea-wrap mt-30">
  247 + <up-textarea
  248 + v-model.trim="acceptReason"
  249 + placeholder="请输入验收原因(必填,最多200字)"
  250 + :required="true"
  251 + maxlength="200"
  252 + rows="5"
  253 + count
  254 + />
  255 + </view>
10 256
  257 + <!-- 验收图片上传(选填,参考回退弹窗样式) -->
  258 + <view class="upload-wrap mt-20">
  259 + <view class="upload-title">上传验收图片(选填)</view>
  260 + <up-upload
  261 + :file-list="acceptImgs.rawImgList.value || []"
  262 + @after-read="acceptImgs.uploadImgs"
  263 + @delete="acceptImgs.deleteImg"
  264 +
  265 + multiple
  266 + width="70"
  267 + height="70"
  268 + :max-count="acceptImgs.uploadConfig.maxCount"
  269 + :upload-text="acceptImgs.uploadConfig.uploadText"
  270 + :size-type="acceptImgs.uploadConfig.sizeType"
  271 + />
  272 + </view>
  273 + </view>
  274 + </up-modal>
  275 + </view>
11 </template> 276 </template>
12 277
  278 +<script setup>
  279 +import { ref, computed, watch } from 'vue';
  280 +import { onShow, onLoad } from '@dcloudio/uni-app';
  281 +import { timeFormat } from '@/uni_modules/uview-plus';
  282 +import {
  283 + myBuzSimplePage,
  284 + todoBuzSimplePage,
  285 + doneBuzSimplePage,
  286 + universalApproval
  287 +} from '@/api/work-order-manage/work-order-manage'
  288 +// 从用户store获取角色信息
  289 +import { useUserStore } from '@/pinia/user';
  290 +import { nextStepMap, buzStatusMap } from '@/common/utils/common'
  291 +// 引入图片上传组合式函数(与参考页面一致)
  292 +import { useUploadImgs } from '@/common/utils/useUploadImgs'
  293 +
  294 +// ========== 状态管理 ==========
  295 +const userStore = useUserStore();
  296 +// 标签页切换
  297 +const activeTab = ref(0); // 0-待办 1-我发起的 2-已办
  298 +const tabList = ref([
  299 + {name: '待办'},
  300 + {name: '我发起的任务'},
  301 + {name: '已办'}
  302 +]);
  303 +// 排序下拉框
  304 +const selectedSortValue = ref(1);
  305 +const sortOptions = ref([
  306 + {name: '位置', id: 1},
  307 + {name: '名称', id: 2},
  308 + {name: '描述', id: 3},
  309 + {name: '编号', id: 4},
  310 +]);
  311 +// 搜索
  312 +const searchValue = ref('');
  313 +// 分页
  314 +const paging = ref(null);
  315 +const orderList = ref([]);
  316 +// 角色控制(巡查员显示新增按钮)
  317 +const isInspector = computed(() => {
  318 + // 增加可选链,避免用户信息不存在报错
  319 + return userStore.userInfo?.roles?.includes('yl_inspector') || false;
  320 +});
  321 +// 回退弹窗相关
  322 +const rejectModalShow = ref(false); // 回退modal显示开关
  323 +const rejectReason = ref(''); // 回退原因
  324 +const currentRejectItem = ref(null); // 当前回退工单
  325 +
  326 +// 回退图片上传配置(与参考页面风格一致)
  327 +const rejectImgs = useUploadImgs({
  328 + maxCount: 3, // 最多上传3张
  329 + uploadText: '选择回退图片', // 自定义上传提示文字
  330 + sizeType: ['compressed'], // 仅上传压缩图
  331 + formRef: null, // 该弹窗无表单校验
  332 + fieldName: 'rejectImgs' // 自定义字段名
  333 +})
  334 +
  335 +
  336 +// ========== 验收弹窗相关状态(含图片上传) ==========
  337 +const acceptModalShow = ref(false); // 验收弹窗显示开关
  338 +const acceptRadioValue = ref('0'); // 单选框值,默认0(通过)
  339 +const acceptReason = ref(''); // 验收原因
  340 +const currentAcceptItem = ref(null); // 当前验收的工单项
  341 +
  342 +// 验收图片上传配置(独立实例,参考回退弹窗)
  343 +const acceptImgs = useUploadImgs({
  344 + maxCount: 3, // 最多上传3张,与回退弹窗一致
  345 + uploadText: '选择验收图片', // 自定义上传提示文字
  346 + sizeType: ['compressed'], // 仅上传压缩图,优化性能
  347 + formRef: null, // 验收弹窗无表单校验
  348 + fieldName: 'acceptImgs' // 自定义字段名,区分回退图片
  349 +})
  350 +
  351 +// 分页查询列表
  352 +const queryList = async (pageNo, pageSize) => {
  353 + try {
  354 + const apiParams = {
  355 + searchContent: searchValue.value.trim() || '',
  356 + pageNo,
  357 + pageSize,
  358 + type: selectedSortValue.value // 1-位置 2-工单名称 3-情况描述 4-工单编号
  359 + };
  360 + let res;
  361 + if (activeTab.value == 0) {
  362 + // 待办工单
  363 + res = await todoBuzSimplePage(apiParams);
  364 + } else if (activeTab.value == 1) {
  365 + // 我发起的任务
  366 + res = await myBuzSimplePage(apiParams);
  367 + } else {
  368 + // 已办工单
  369 + res = await doneBuzSimplePage(apiParams);
  370 + }
  371 + // 适配z-paging分页
  372 + paging.value.complete(res.list, res.total);
  373 + } catch (error) {
  374 + console.error('加载工单失败:', error);
  375 + paging.value?.complete(false);
  376 + uni.showToast({title: '加载失败,请重试', icon: 'none'});
  377 + }
  378 +};
  379 +
  380 +// ========== 事件处理 ==========
  381 +// 标签页切换
  382 +const handleTabChange = (item) => {
  383 + console.log(item)
  384 + activeTab.value = item.index;
  385 + paging.value?.reload(); // 切换标签页刷新列表
  386 +};
  387 +// 排序变更
  388 +const handleSortChange = (val) => {
  389 + selectedSortValue.value = val.id;
  390 + searchValue.value = '';
  391 + paging.value?.reload(); // 排序变更刷新列表
  392 +};
  393 +// 搜索
  394 +const handleSearch = (val) => {
  395 + searchValue.value = val;
  396 + paging.value?.reload(); // 搜索刷新列表
  397 +};
  398 +// 工单详情
  399 +const handleDetail = (item) => {
  400 + // 0-待办 1我发起的- 2-已办
  401 + uni.navigateTo({
  402 + url: `/pages-sub/problem/regional-order-manage/order-detail?taskId=${item.taskId}&activeTab=${activeTab.value}&processInstanceId=${item.processInstanceId}`,
  403 + events: {
  404 + // 自定义事件名:needRefresh(与详情页保持一致)
  405 + needRefresh: () => {
  406 + console.log('详情页返回,触发工单列表刷新');
  407 + if (paging.value) {
  408 + paging.value.reload(); // 刷新z-paging列表
  409 + }
  410 + }
  411 + }
  412 + });
  413 +};
  414 +
  415 +// 生成临时key
  416 +const generateTempKey = () => {
  417 + return 'renew_order_' + Date.now() + '_' + Math.floor(Math.random() * 10000);
  418 +};
  419 +
  420 +// 待办-重新提交工单(改造后:大数据存本地,仅传唯一标识)
  421 +const handleRenew = (item) => {
  422 + // 校验工单有效性
  423 + if (!item || !item.id) {
  424 + uni.showToast({title: '工单信息异常,无法重新提交', icon: 'none'});
  425 + return;
  426 + }
  427 +
  428 + // 1. 生成唯一临时标识
  429 + const tempKey = generateTempKey();
  430 +
  431 + // 2. 将完整工单数据存入本地临时存储(同步存储,确保数据立即生效)
  432 + try {
  433 + uni.setStorageSync(tempKey, item);
  434 + } catch (error) {
  435 + console.error('存储工单数据失败:', error);
  436 + uni.showToast({title: '数据存储异常,无法重新提交', icon: 'none'});
  437 + return;
  438 + }
  439 +
  440 + // 3. URL 仅传递「唯一标识」和「重新提交标记」(数据量极小,无长度问题)
  441 + uni.navigateTo({
  442 + url: `/pages-sub/problem/work-order-manage/add-order?isRenew=1&tempKey=${tempKey}`
  443 + });
  444 +};
  445 +
  446 +// 待办-处理工单
  447 +const handleProcess = async (item) => {
  448 + console.log(nextStepMap[item.taskKey].name)
  449 + try {
  450 + if (nextStepMap[item.taskKey]?.name == '养护组长分配') {
  451 + uni.navigateTo({
  452 + url: `/pages-sub/problem/work-order-manage/distribution-order?taskId=${item.taskId}&orderNo=${item.orderNo}&id=${item.id}&busiLine=${item.busiLine}`
  453 + })
  454 + }
  455 + if (nextStepMap[item.taskKey]?.name == '养护员待实施') {
  456 +
  457 + // ① 生成唯一临时key(和重新提交工单逻辑一致,避免冲突)
  458 + const tempKey = `maintain_order_${Date.now()}_${Math.floor(Math.random() * 10000)}`;
  459 +
  460 + // ② 存储完整item到本地缓存(同步存储,确保立即生效)
  461 + try {
  462 + uni.setStorageSync(tempKey, item);
  463 + } catch (error) {
  464 + console.error('存储养护工单数据失败:', error);
  465 + uni.showToast({title: '数据存储异常,无法跳转', icon: 'none'});
  466 + return;
  467 + }
  468 +
  469 + // ③ URL仅传递临时key(可选:携带必要简单参数,方便目标页面快速使用)
  470 + uni.navigateTo({
  471 + url: `/pages-sub/problem/regional-order-manage/add-maintain-order?tempKey=${tempKey}`
  472 + })
  473 + }
  474 + // 养护组长验收 - 打开弹窗
  475 + if (nextStepMap[item.taskKey]?.name == '养护组长验收') {
  476 + currentAcceptItem.value = item; // 存储当前工单信息
  477 + acceptReason.value = ''; // 清空上次的验收原因
  478 + acceptRadioValue.value = '0'; // 重置默认选中“通过”
  479 + acceptModalShow.value = true; // 显示验收弹窗
  480 + }
  481 + // 巡查员验收 - 打开弹窗
  482 + if (nextStepMap[item.taskKey]?.name == '巡查员验收') {
  483 + currentAcceptItem.value = item; // 存储当前工单信息
  484 + acceptReason.value = ''; // 清空上次的验收原因
  485 + acceptRadioValue.value = '0'; // 重置默认选中“通过”
  486 + acceptModalShow.value = true; // 显示验收弹窗
  487 + }
  488 +
  489 + // 发起人确认
  490 + if (nextStepMap[item.taskKey]?.name == '发起人确认') {
  491 + console.log(item)
  492 + uni.showModal({
  493 + title: "结束工单",
  494 + content: "请确定是否结束工单?",
  495 + success: async function (res) {
  496 + if (res.confirm) {
  497 + // 构建请求参数
  498 + const requestData = {
  499 + "returnImgs": rejectImgs.getSuccessImgUrls(), // 改造后:获取上传成功的图片URL
  500 + "workerDataId": item.id,
  501 + "taskKey":'ylInspectorStart',
  502 + "taskId": item.taskId,
  503 + "operateType": 200,
  504 + "agree": 1,
  505 + "reason": '结束工单'
  506 + };
  507 + // 调用回退工单接口
  508 + const res = await universalApproval(requestData);
  509 + uni.showToast({title: '结束成功', icon: 'success', duration: 1000});
  510 + rejectModalShow.value = false;
  511 + paging.value?.reload(); // 刷新列表
  512 + } else if (res.cancel) {
  513 + console.log("用户点击取消");
  514 + }
  515 + },
  516 + });
  517 + }
  518 + } catch (error) {
  519 + console.error('处理工单失败:', error);
  520 + uni.showToast({title: '处理失败,请重试', icon: 'none'});
  521 + }
  522 +};
  523 +
  524 +// 待办-回退工单(打开回退modal)
  525 +const handleReject = (item) => {
  526 + console.log('123213')
  527 + // 校验工单有效性
  528 + if (!item || !item.id) {
  529 + uni.showToast({title: '工单信息异常,无法回退', icon: 'none'});
  530 + return;
  531 + }
  532 + currentRejectItem.value = item;
  533 + rejectReason.value = ''; // 清空上次输入
  534 + rejectImgs.clearImgs(); // 改造后:使用组合式函数的清空方法
  535 + rejectModalShow.value = true; // 显示回退modal
  536 +};
  537 +
  538 +// 回退modal - 取消按钮
  539 +const handleRejectModalCancel = () => {
  540 + rejectModalShow.value = false;
  541 + rejectReason.value = '';
  542 + rejectImgs.clearImgs(); // 改造后:使用组合式函数的清空方法
  543 +};
  544 +
  545 +// 确认回退工单
  546 +const confirmReject = async () => {
  547 + // 严格校验回退原因(去除首尾空格)
  548 + const rejectReasonTrim = rejectReason.value.trim();
  549 + if (!rejectReasonTrim) {
  550 + uni.showToast({title: '请填写回退原因', icon: 'none', duration: 1000});
  551 + return;
  552 + }
  553 + // 校验当前工单有效性
  554 + if (!currentRejectItem.value || !currentRejectItem.value.id) {
  555 + uni.showToast({title: '工单信息异常,无法提交', icon: 'none', duration:1000});
  556 + rejectModalShow.value = false;
  557 + return;
  558 + }
  559 + try {
  560 + // 显示加载中,防止重复提交
  561 + uni.showLoading({title: '提交中...', mask: true});
  562 +
  563 + // 构建请求参数
  564 + const requestData = {
  565 + "returnImgs": rejectImgs.getSuccessImgUrls(), // 改造后:获取上传成功的图片URL数组
  566 + "workerDataId": currentRejectItem.value.id,
  567 + "taskKey": currentRejectItem.value.taskKey,
  568 + "taskId": currentRejectItem.value.taskId,
  569 + "operateType": nextStepMap[currentRejectItem.value.taskKey].operateTypeNoPass,
  570 + "agree": 1,
  571 + "reason": rejectReasonTrim
  572 + };
  573 + // 调用回退工单接口
  574 + const res = await universalApproval(requestData);
  575 + uni.showToast({title: '回退成功', icon: 'success', duration: 1000});
  576 + rejectModalShow.value = false;
  577 + paging.value?.reload(); // 刷新列表
  578 + } catch (error) {
  579 + console.error('回退工单失败:', error);
  580 + uni.showToast({title: '网络异常,回退失败', icon: 'none', duration: 1000});
  581 + } finally {
  582 + // 隐藏加载中
  583 + uni.hideLoading();
  584 + }
  585 +};
  586 +
  587 +// 新增工单
  588 +const handleAddOrder = () => {
  589 + uni.navigateTo({
  590 + url: '/pages-sub/problem/regional-order-manage/add-order'
  591 + });
  592 +};
  593 +
  594 +// 验收弹窗 - 取消按钮(清空状态)
  595 +const handleAcceptModalCancel = () => {
  596 + acceptModalShow.value = false;
  597 + acceptReason.value = ''; // 清空验收原因
  598 + acceptRadioValue.value = '0'; // 重置单选框为“通过”
  599 + acceptImgs.clearImgs(); // 清空验收图片
  600 +};
  601 +
  602 +// 验收弹窗 - 确定按钮(含returnImgs传参)
  603 +const handleAcceptModalConfirm = async () => {
  604 + // 1. 校验验收原因是否为空
  605 + if (!acceptReason.value.trim()) {
  606 + uni.showToast({title: '请填写验收原因', icon: 'none', duration: 1000});
  607 + return;
  608 + }
  609 + // 2. 校验验收原因长度
  610 + if (acceptReason.value.length > 200) {
  611 + uni.showToast({title: '验收原因最多200字', icon: 'none', duration: 1000});
  612 + return;
  613 + }
  614 + try {
  615 + // 3. 构建请求参数(含returnImgs)
  616 + console.log(currentAcceptItem.value)
  617 + let postData = {}
  618 + if (currentAcceptItem.value?.taskKey == 'ylTeamLeaderConfirm') { // 养护组长验收
  619 + postData = {
  620 + "returnImgs": acceptImgs.getSuccessImgUrls(), // 验收图片URL数组
  621 + "taskKey": currentAcceptItem.value.taskKey,
  622 + "workerDataId": currentAcceptItem.value.id,
  623 + "taskId": currentAcceptItem.value.taskId,
  624 + "operateType": acceptRadioValue.value == 0 ? nextStepMap[currentAcceptItem.value.taskKey].operateTypePass : nextStepMap[currentAcceptItem.value.taskKey].operateTypeNoPass,
  625 + "reason": acceptReason.value.trim()
  626 + }
  627 + }
  628 + if (currentAcceptItem.value?.taskKey == 'ylInspector') { // 巡查员验收
  629 + postData = {
  630 + "returnImgs": acceptImgs.getSuccessImgUrls(), // 验收图片URL数组
  631 + "taskKey": currentAcceptItem.value.taskKey,
  632 + "taskId": currentAcceptItem.value.taskId,
  633 + "workerDataId": currentAcceptItem.value.id,
  634 + "operateType": acceptRadioValue.value == 0 ? nextStepMap[currentAcceptItem.value.taskKey].operateTypePass : nextStepMap[currentAcceptItem.value.taskKey].operateTypeNoPass,
  635 + "reason": acceptReason.value.trim(),
  636 + "agree": acceptRadioValue.value
  637 + }
  638 + }
  639 + const acceptRes = await universalApproval(postData);
  640 + // 4. 操作成功处理
  641 + uni.showToast({title: '提交成功', icon: 'success', duration: 1000});
  642 + handleAcceptModalCancel(); // 清空状态
  643 + paging.value?.reload(); // 刷新工单列表
  644 + } catch (error) {
  645 + // 5. 操作失败处理
  646 + console.error('验收失败:', error);
  647 + uni.showToast({title: '验收提交失败,请重试', icon: 'none', duration: 1000});
  648 + }
  649 +};
  650 +
  651 +// 页面初始化
  652 +onLoad(() => {
  653 + // 初始化加载列表
  654 + paging.value?.reload();
  655 +});
  656 +</script>
  657 +
13 <style scoped lang="scss"> 658 <style scoped lang="scss">
  659 +.page-container {
  660 + min-height: 100vh;
  661 + background-color: #fafafa;
  662 +}
  663 +
  664 +// 顶部固定区域
  665 +.header-wrap {
  666 + background-color: #fff;
  667 +}
  668 +
  669 +// 搜索栏样式
  670 +.search-header {
  671 + display: flex;
  672 + align-items: center;
  673 + padding: 20rpx;
  674 + box-sizing: border-box;
  675 +
  676 + .select-wrap {
  677 + width: 120rpx;
  678 + margin-right: 20rpx;
  679 +
  680 + :deep(.u-select) {
  681 + width: 100%;
  682 + font-size: 28rpx;
  683 + }
  684 +
  685 + :deep(.u-input__placeholder) {
  686 + font-size: 28rpx;
  687 + }
  688 + }
  689 +
  690 + .search-input-wrap {
  691 + flex: 1;
  692 + }
  693 +}
  694 +
  695 +// 工单卡片样式
  696 +.order-card {
  697 + margin: 0 20rpx 20rpx;
  698 + background: #fff;
  699 + border-radius: 12rpx;
  700 + box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.04);
  701 +}
  702 +
  703 +.card-body {
  704 +
  705 +}
  706 +
  707 +// 回退modal样式
  708 +.reject-modal-content {
  709 + width: 100%;
  710 + box-sizing: border-box;
  711 + padding: 10rpx 0;
  712 +}
  713 +
  714 +.textarea-label {
  715 + font-size: 28rpx;
  716 + color: #333;
  717 + margin-bottom: 10rpx;
  718 +
  719 + .required-mark {
  720 + color: #f56c6c;
  721 + margin-left: 4rpx;
  722 + }
  723 +}
  724 +
  725 +.upload-wrap {
  726 + margin-top: 20rpx;
  727 +
  728 + .upload-title {
  729 + font-size: 28rpx;
  730 + color: #333;
  731 + margin-bottom: 10rpx;
  732 + }
  733 +}
  734 +
  735 +.mt-20 {
  736 + margin-top: 20rpx;
  737 +}
  738 +
  739 +.mt-30 {
  740 + margin-top: 30rpx;
  741 +}
  742 +
  743 +// 养护组长验收弹窗样式
  744 +.accept-modal-content {
  745 + width: 100%;
  746 + box-sizing: border-box;
  747 +}
  748 +
  749 +.radio-group-wrap {
  750 + display: flex;
  751 + align-items: center;
  752 + gap: 40rpx; // 单选框之间的间距
  753 + font-size: 28rpx;
  754 + margin-bottom: 20rpx;
  755 +}
  756 +
  757 +.textarea-wrap {
  758 + width: 100%;
  759 + margin-top: 30rpx;
  760 +}
14 761
  762 +.modal-btn-wrap {
  763 + display: flex;
  764 + align-items: center;
  765 + justify-content: flex-end;
  766 + padding-right: 10rpx;
  767 +}
15 </style> 768 </style>
16 \ No newline at end of file 769 \ No newline at end of file
pages-sub/problem/regional-order-manage/order-detail.vue
1 -<script lang="ts">  
2 -import {defineComponent} from 'vue' 1 +<template>
  2 + <view class="page-container">
  3 + <!-- 页面级加载组件 -->
  4 + <up-loading-page
  5 + v-if="loading"
  6 + :loading="true"
  7 + title="加载中..."
  8 + color="#3c9cff"
  9 + ></up-loading-page>
3 10
4 -export default defineComponent({  
5 - name: "order-detail"  
6 -})  
7 -</script> 11 + <!-- 主内容容器 -->
  12 + <view v-else class="main-content">
  13 + <!-- 顶部固定Tabs -->
  14 + <up-sticky :bgColor="'#fff'">
  15 + <up-tabs
  16 + v-model="activeTopTab"
  17 + :list="topTabList"
  18 + :scrollable="false"
  19 + sticky
  20 + active-color="#3c9cff"
  21 + inactive-color="#666"
  22 + class="top-tabs"
  23 + @click="activeTopTabClick"
  24 + ></up-tabs>
  25 + </up-sticky>
8 26
9 -<template> 27 + <!-- 顶部Tab内容区 -->
  28 + <view class="tab-content">
  29 + <!-- 1. 工单详情Tab -->
  30 + <view v-show="activeTopTab == 0" class="detail-content">
  31 + <up-cell-group :border="false" class="detail-cell-group">
  32 + <!-- 工单编号 -->
  33 + <up-cell title="工单编号" :value="orderDetail.orderNo || '--'" align="middle"></up-cell>
  34 +
  35 + <!-- 工单位置 -->
  36 + <up-cell align="middle">
  37 + <template #title>
  38 + <view style="min-width: 200rpx">工单位置</view>
  39 + </template>
  40 + <template #value>
  41 + <view class="common-text-color up-line-1">{{ orderDetail.lonLatAddress || '--' }}</view>
  42 + </template>
  43 + </up-cell>
  44 +
  45 + <!-- 工单名称 -->
  46 + <up-cell align="middle">
  47 + <template #title>
  48 + <view style="min-width: 200rpx">工单名称</view>
  49 + </template>
  50 + <template #value>
  51 + <view class="common-text-color up-line-1">{{ orderDetail.orderName || '--' }}</view>
  52 + </template>
  53 + </up-cell>
  54 +
  55 + <!-- 情况描述 -->
  56 + <up-cell>
  57 + <template #title>
  58 + <view style="min-width: 200rpx">情况描述</view>
  59 + </template>
  60 + <template #value>
  61 + <view class="common-text-color up-line-1">{{ orderDetail.remark || '--' }}</view>
  62 + </template>
  63 + </up-cell>
  64 +
  65 + <!-- 紧急程度 -->
  66 + <up-cell
  67 + title="紧急程度"
  68 + :value="uni.$dict.getDictLabel('workorder_pressing_type', orderDetail.pressingType) || '--'"
  69 + align="middle"
  70 + ></up-cell>
  71 +
  72 + <!-- 提交人 -->
  73 + <up-cell title="提交人" :value="orderDetail.userName || '--'" align="middle"></up-cell>
  74 +
  75 + <!-- 提交时间 -->
  76 + <up-cell
  77 + title="提交时间"
  78 + :value="timeFormat(orderDetail.createTime, 'yyyy-mm-dd hh:MM:ss') || '--'"
  79 + align="middle"
  80 + ></up-cell>
  81 +
  82 + <!-- 处理结果 -->
  83 + <up-cell v-if="orderDetail.handleResult">
  84 + <template #title>
  85 + <view style="min-width: 200rpx">处理结果</view>
  86 + </template>
  87 + <template #value>
  88 + <view class="common-text-color up-line-1">{{ orderDetail.handleResult || '--' }}</view>
  89 + </template>
  90 + </up-cell>
  91 +
  92 + <!-- 街道名称 -->
  93 + <up-cell title="街道名称" :value="orderDetail.streetName || '--'" align="middle"></up-cell>
  94 +
  95 + <!-- 问题照片 -->
  96 + <up-cell title="问题照片">
  97 + <template #value>
  98 + <view class="cell-content-wrap">
  99 + <up-album
  100 + v-if="!!orderDetail.problemsImgs?.length"
  101 + :urls="orderDetail.problemsImgs.slice(0, 3)"
  102 + :singleSize="70"
  103 + :multipleSize="70"
  104 +
  105 + :preview-full-image="true"
  106 + ></up-album>
  107 + <text v-else class="empty-text">暂无问题照片</text>
  108 + </view>
  109 + </template>
  110 + </up-cell>
  111 +
  112 + <!-- 希望完成时间 -->
  113 + <up-cell
  114 + v-if="orderDetail.expectedFinishDate"
  115 + title="希望完成时间"
  116 + :value="timeFormat(orderDetail.expectedFinishDate, 'yyyy-mm-dd hh:MM:ss')"
  117 + align="middle"
  118 + :border="false"
  119 + ></up-cell>
  120 +
  121 + <up-cell
  122 + v-if="orderDetail.finishDate"
  123 + title="工单完结时间"
  124 + :value="timeFormat(orderDetail.finishDate, 'yyyy-mm-dd hh:MM:ss')"
  125 + align="middle"
  126 + :border="false"
  127 + ></up-cell>
  128 + </up-cell-group>
  129 +
  130 + <!-- 图片分类Tabs区块 -->
  131 + <view class="img-tabs-block" v-if="orderDetail.startImgs.length>0">
  132 + <up-cell-group :border="false">
  133 + <up-cell>
  134 + <template #title>
  135 + <view style="min-width: 200rpx">共同处理人</view>
  136 + </template>
  137 + <template #value>
  138 + <view class="common-text-color up-line-1">
  139 + {{
  140 + Array.isArray(orderDetail.coHandlersName) && orderDetail.coHandlersName.length > 0 ? orderDetail.coHandlersName.join(',') : '--'
  141 + }}
  142 + </view>
  143 + </template>
  144 + </up-cell>
  145 + </up-cell-group>
  146 + <up-tabs
  147 + @change='imgTabChange'
  148 + v-model="activeImgTab"
  149 + :list="imgTabList"
  150 + :scrollable="false"
  151 + active-color="#3c9cff"
  152 + inactive-color="#666"
  153 + class="img-tabs"
  154 + ></up-tabs>
  155 +
  156 + <!-- 图片Tab内容区 -->
  157 + <view class="img-tab-content">
  158 + <up-album
  159 + v-if="currentImgList.length"
  160 + :urls="currentImgList.slice(0, 3)"
  161 + :singleSize="70"
  162 + :multipleSize="70"
  163 + :preview-full-image="true"
  164 + class="img-album custom-album"
  165 + ></up-album>
  166 + <text v-else class="empty-img-text">养护员未上传图片</text>
  167 + </view>
  168 + </view>
  169 + </view>
  170 +
  171 + <!-- 2. 流程节点Tab -->
  172 + <view v-show="activeTopTab == 1" class="process-content">
  173 + <!-- 竖向步骤条:动态绑定current属性 -->
  174 + <up-steps
  175 + v-if="processData.activityNodes && processData.activityNodes.length"
  176 + :list="processData.activityNodes"
  177 + :current="getCurrentStepIndex()"
  178 + direction="column"
  179 + active-color="#3c9cff"
  180 + inactive-color="#999"
  181 + class="vertical-steps"
  182 + >
  183 + <template>
  184 + <up-steps-item
  185 + v-for="(item, index) in processData.activityNodes"
  186 + :key="`${item.id}_${index}`"
  187 + >
  188 + <!-- 唯一自定义模板:content,包含标题、描述、相册所有内容 -->
  189 + <template #content>
  190 + <view class="step-content-wrap">
  191 + <!-- 1. 原标题内容:节点名称 + 操作人 -->
  192 + <view class="step-title">
  193 + {{ item.name }}
  194 + <text class="operator-name">
  195 + {{
  196 + item.tasks && item.tasks[0]?.assigneeUser?.nickname ? '(' + item.tasks[0].assigneeUser.nickname + ')' : '(未知操作人)'
  197 + }}
  198 + </text>
  199 + </view>
  200 +
  201 + <!-- 2. 原描述内容:时间 + 处理说明(最多200字) -->
  202 + <view class="step-desc">
  203 + <!-- 时间行 -->
  204 + <view class="time-line">
  205 + 处理时间:{{ timeFormat(item.startTime, 'yyyy-mm-dd hh:MM:ss') }}
  206 + <text v-if="item.endTime"> 至 {{ timeFormat(item.endTime, 'yyyy-mm-dd hh:MM:ss') }}</text>
  207 + <text v-else class="processing-tag">(处理中)</text>
  208 + </view>
  209 +
  210 + <view class="reason-line up-line-1" v-if="index!==0&&item.endTime">
  211 + 总耗时:{{ calculateFormatTimeDiff(item.startTime, item.endTime) }}
  212 + </view>
  213 +
  214 + <!-- 原因行 -->
  215 + <view class="reason-line up-line-2" v-if="item.tasks && item.tasks[0]?.reason">
  216 + 描述:{{ getLimitReason(item.tasks && item.tasks[0]?.reason) }}
  217 + </view>
  218 + </view>
  219 +
  220 + <!-- 3. 原相册内容:预留up-album -->
  221 + <view class="step-album-wrap">
  222 + <up-album
  223 + v-if="item.tasks && item.tasks[0]?.attattmentUrls && item.tasks[0].attattmentUrls.length"
  224 + :urls="item.tasks[0].attattmentUrls.slice(0, 3)"
  225 + :singleSize="70"
  226 + :multipleSize="70"
  227 +
  228 + :preview-full-image="true"
  229 + class="step-album"
  230 + ></up-album>
  231 + </view>
  232 + </view>
  233 + </template>
  234 + </up-steps-item>
  235 + </template>
  236 +
  237 + </up-steps>
  238 +
  239 + <!-- 流程节点为空时的提示 -->
  240 + <up-empty
  241 + v-else
  242 + mode="data"
  243 + title="暂无流程节点数据"
  244 + class="empty-process"
  245 + ></up-empty>
  246 + </view>
  247 + </view>
  248 + </view>
  249 +
  250 + <!-- activeTab==0的时候才出现, 也就是待办 -->
  251 + <view v-if="activeTab==0&&nextStepMap[orderDetail.taskKey]" class="fixed-bottom-btn-wrap">
  252 + <view class="u-body-item u-flex common-justify-between common-item-center ">
  253 + <up-button type="warning" size="normal" @click="handleReject(orderDetail)"
  254 + v-show="nextStepMap[orderDetail.taskKey].backShow">回退
  255 + </up-button>
  256 +
  257 + <up-button type="success" size="normal" @click="handleRenew(orderDetail)"
  258 + v-show="nextStepMap[orderDetail.taskKey].renewShow">重新提交
  259 + </up-button>
10 260
  261 + <up-button type="primary" size="normal" @click="handleProcess(orderDetail)">{{
  262 + nextStepMap[orderDetail.taskKey].btnText
  263 + }}
  264 + </up-button>
  265 +
  266 + </view>
  267 + </view>
  268 +
  269 + <!-- 回退原因弹窗(新增图片上传) -->
  270 + <up-modal
  271 + :show="rejectModalShow"
  272 + title="回退原因"
  273 + :closeOnClickOverlay="false"
  274 + :showConfirmButton="true"
  275 + :showCancelButton="true"
  276 + @cancel="handleRejectModalCancel"
  277 + @confirm="confirmReject"
  278 + >
  279 + <view class="reject-modal-content">
  280 + <up-textarea
  281 + v-model.trim="rejectReason"
  282 + placeholder="请输入回退原因(必填)"
  283 + rows="6"
  284 + :count="200"
  285 + maxlength="200"
  286 + class="reject-textarea"
  287 + />
  288 + <!-- 新增:回退图片上传(选填) -->
  289 + <view class="upload-wrap mt-20">
  290 + <view class="upload-title">上传回退图片(选填)</view>
  291 + <up-upload
  292 + :file-list="rejectImgs.rawImgList.value || []"
  293 + @after-read="rejectImgs.uploadImgs"
  294 + @delete="rejectImgs.deleteImg"
  295 + multiple
  296 + width="70"
  297 + height="70"
  298 + :max-count="rejectImgs.uploadConfig.maxCount"
  299 + :upload-text="rejectImgs.uploadConfig.uploadText"
  300 + :size-type="rejectImgs.uploadConfig.sizeType"
  301 + />
  302 + </view>
  303 + </view>
  304 + </up-modal>
  305 +
  306 + <!-- 验收弹窗(新增图片上传) -->
  307 + <up-modal
  308 + :show="acceptModalShow"
  309 + title="验收"
  310 + :closeOnClickOverlay="false"
  311 + :showConfirmButton="true"
  312 + :showCancelButton="true"
  313 + @cancel="handleAcceptModalCancel"
  314 + @confirm="handleAcceptModalConfirm"
  315 + >
  316 + <view class="accept-modal-content">
  317 + <!-- 第一行:单选框(通过/不通过,默认通过) -->
  318 + <view class="radio-group-wrap">
  319 + <up-radio-group v-model="acceptRadioValue">
  320 + <up-radio name="0" label="通过"></up-radio>
  321 + <up-radio name="1" label="不通过"></up-radio>
  322 + </up-radio-group>
  323 + </view>
  324 +
  325 + <!-- 第二行:必填textarea,最多200字 -->
  326 + <view class="textarea-wrap mt-30">
  327 + <up-textarea
  328 + v-model.trim="acceptReason"
  329 + placeholder="请输入验收原因(必填,最多200字)"
  330 + :required="true"
  331 + maxlength="200"
  332 + rows="5"
  333 + count
  334 + />
  335 + </view>
  336 +
  337 + <!-- 新增:验收图片上传(选填) -->
  338 + <view class="upload-wrap mt-20">
  339 + <view class="upload-title">上传验收图片(选填)</view>
  340 + <up-upload
  341 + :file-list="acceptImgs.rawImgList.value || []"
  342 + @after-read="acceptImgs.uploadImgs"
  343 + @delete="acceptImgs.deleteImg"
  344 + multiple
  345 + width="70"
  346 + height="70"
  347 + :max-count="acceptImgs.uploadConfig.maxCount"
  348 + :upload-text="acceptImgs.uploadConfig.uploadText"
  349 + :size-type="acceptImgs.uploadConfig.sizeType"
  350 + />
  351 + </view>
  352 + </view>
  353 + </up-modal>
  354 +
  355 + </view>
11 </template> 356 </template>
12 357
  358 +<script setup lang="ts">
  359 +import {ref, watch} from 'vue';
  360 +import {onLoad, onShow} from '@dcloudio/uni-app';
  361 +import {timeFormat} from '@/uni_modules/uview-plus';
  362 +import {
  363 + getMyTaskDetail,
  364 + getDoneTaskDetail,
  365 + getTodoTaskDetail,
  366 + getApprovalDetail,
  367 + universalApproval
  368 +} from '@/api/work-order-manage/work-order-manage';
  369 +import {nextStepMap, buzStatusMap, calculateFormatTimeDiff} from '@/common/utils/common'
  370 +// 引入图片上传组合式函数
  371 +import {useUploadImgs} from '@/common/utils/useUploadImgs'
  372 +
  373 +// 状态管理
  374 +const loading = ref(true);
  375 +const activeTopTab = ref(0); // 顶部Tab激活索引
  376 +const activeImgTab = ref(0); // 图片分类Tab激活索引
  377 +
  378 +// 顶部Tab列表
  379 +const topTabList = ref([
  380 + {name: '工单详情'},
  381 + {name: '流程节点'}
  382 +]);
  383 +
  384 +// 流程节点数据(初始化适配接口格式)
  385 +const processData = ref<any>({
  386 + status: 2,
  387 + activityNodes: [],
  388 + formFieldsPermission: null,
  389 + todoTask: null
  390 +});
  391 +
  392 +// 图片分类Tab列表(带角标配置)
  393 +const imgTabList = ref([
  394 + {name: '开始', badge: {isDot: true}},
  395 + {name: '进行中'},
  396 + {name: '结束', badge: {isDot: true}},
  397 + {name: '人员'},
  398 + {name: '材料'}
  399 +]);
  400 +
  401 +// 工单详情数据(初始化赋值,可被接口数据覆盖)
  402 +const orderDetail = ref<any>({
  403 + id: 0,
  404 + busiLine: '',
  405 + orderNo: '',
  406 + orderName: '',
  407 + sourceId: 0,
  408 + sourceName: '',
  409 + roadId: 0,
  410 + roadName: '',
  411 + streetId: '',
  412 + streetName: '',
  413 + curingLevelId: 0,
  414 + curingLevelName: '',
  415 + commitDate: 0,
  416 + finishDate: 0,
  417 + expectedFinishDate: 0,
  418 + pressingType: 0,
  419 + userId: 0,
  420 + userName: '',
  421 + companyId: 0,
  422 + latLonType: 0,
  423 + lat: 0,
  424 + lon: 0,
  425 + lonLatAddress: '',
  426 + thirdWorkNo: '',
  427 + thirdPushState: 0,
  428 + buzStatus: null,
  429 + status: 0,
  430 + processInstanceId: '',
  431 + remark: '',
  432 + handleResult: '',
  433 + createTime: 0,
  434 + fileNames: null,
  435 + coHandlers: null,
  436 + coHandlersName: null,
  437 + companyName: null,
  438 + userMobile: null,
  439 + taskId: '',
  440 + taskName: '',
  441 + taskKey: '',
  442 + processDefinitionName: '',
  443 + startTime: 0,
  444 + endTime: 0,
  445 + key_: '',
  446 + problemsImgs: [],
  447 + startImgs: [],
  448 + processingImgs: [],
  449 + endImgs: [],
  450 + personImgs: [],
  451 + materialImgs: []
  452 +});
  453 +
  454 +/**
  455 + * 当前激活的图片列表(无需函数调用,直接访问)
  456 + */
  457 +const currentImgList = ref([])
  458 +
  459 +const tabKeyMap = ['startImgs', 'processingImgs', 'endImgs', 'personImgs', 'materialImgs'];
  460 +const imgTabChange = (({index}: { index: number }) => {
  461 + console.log(index)
  462 + const currentKey = tabKeyMap[index]
  463 + console.log(currentKey)
  464 + console.log(orderDetail.value[currentKey])
  465 + currentImgList.value = orderDetail.value[currentKey]
  466 +})
  467 +
  468 +/**
  469 + * 截取reason最多200字
  470 + * @param reason 处理说明
  471 + * @returns 截取后的字符串
  472 + */
  473 +const getLimitReason = (reason: string | null | undefined) => {
  474 + if (!reason) return '无处理说明';
  475 + if (reason.length <= 200) return reason;
  476 + return reason.substring(0, 200) + '...';
  477 +}
  478 +
  479 +/**
  480 + * 动态获取当前步骤索引(核心:根据接口数据的status字段判断)
  481 + * status=1:当前步骤(处理中)
  482 + * status=2:已完成步骤
  483 + * @returns {number} 当前激活的步骤索引(从0开始)
  484 + */
  485 +const getCurrentStepIndex = () => {
  486 + const {activityNodes} = processData.value;
  487 + if (!activityNodes || !activityNodes.length) return 0;
  488 +
  489 + // 2. 若没有处理中的节点(全部已完成),则激活最后一个节点
  490 + return activityNodes.length - 1;
  491 +}
  492 +
  493 +/**
  494 + * 获取工单详情
  495 + */
  496 +const DetailQuery = async (taskIdStr: string) => {
  497 + console.log('当前工单ID:', taskIdStr)
  498 + try {
  499 + loading.value = true;
  500 + // 转换activeTab为数字类型,避免类型不一致导致接口调用失败
  501 + const tabType = Number(activeTab.value);
  502 + let res: any;
  503 +
  504 + if (tabType === 0) {
  505 + res = await getTodoTaskDetail({taskId: taskIdStr});
  506 + } else if (tabType === 1) {
  507 + res = await getMyTaskDetail({taskId: taskIdStr});
  508 + } else if (tabType === 2) {
  509 + res = await getDoneTaskDetail({taskId: taskIdStr});
  510 + } else {
  511 + uni.showToast({title: '无效的工单类型', icon: 'none'});
  512 + return;
  513 + }
  514 +
  515 + // 覆盖工单详情数据
  516 + if (res) {
  517 + orderDetail.value = res;
  518 + currentImgList.value = orderDetail.value.startImgs
  519 + }
  520 + } catch (error) {
  521 + console.error('获取工单详情失败:', error);
  522 + uni.showToast({title: '加载失败,请重试', icon: 'none'});
  523 + } finally {
  524 + loading.value = false;
  525 + }
  526 +};
  527 +
  528 +// 页面加载参数
  529 +const taskId = ref('')
  530 +const activeTab = ref('')
  531 +const processInstanceId = ref('')
  532 +
  533 +const activeTopTabClick = async (item: any) => {
  534 + console.log(item)
  535 + activeTopTab.value = item.index
  536 + if (activeTopTab.value == 1) {
  537 + let getData = {
  538 + processInstanceId: processInstanceId.value,
  539 + }
  540 + const res = await getApprovalDetail(getData)
  541 + console.log(res)
  542 + // 关键:格式化数据,补充up-steps要求的title字段
  543 + if (res && res.activityNodes && res.activityNodes.length) {
  544 + // 1. 先过滤:剔除 name 为 "结束" 的节点
  545 + const filteredActivityNodes = res.activityNodes.filter(node => {
  546 + // 返回 true 保留节点,返回 false 剔除节点
  547 + return node.name !== '结束';
  548 + });
  549 + const formatActivityNodes = filteredActivityNodes.map(node => ({
  550 + ...node,
  551 + title: node.name // 补充强制字段,满足3.3.48版本组件要求
  552 + }));
  553 + processData.value = {
  554 + ...res,
  555 + activityNodes: formatActivityNodes
  556 + };
  557 + } else {
  558 + processData.value = res;
  559 + }
  560 + }
  561 +}
  562 +
  563 +// ========== 回退弹窗相关状态(新增图片上传实例) ==========
  564 +const rejectModalShow = ref(false); // 回退modal显示开关
  565 +const rejectReason = ref(''); // 回退原因
  566 +const currentRejectItem = ref<any>(null); // 当前回退工单
  567 +// 回退图片上传配置
  568 +const rejectImgs = useUploadImgs({
  569 + maxCount: 3,
  570 + uploadText: '选择回退图片',
  571 + sizeType: ['compressed'],
  572 + formRef: null,
  573 + fieldName: 'rejectImgs'
  574 +})
  575 +
  576 +
  577 +// ========== 验收弹窗相关状态(新增图片上传实例) ==========
  578 +const acceptModalShow = ref(false); // 验收弹窗显示开关
  579 +const acceptRadioValue = ref('0'); // 单选框值,默认0(通过)
  580 +const acceptReason = ref(''); // 验收原因
  581 +const currentAcceptItem = ref<any>(null); // 当前验收的工单项
  582 +// 验收图片上传配置
  583 +const acceptImgs = useUploadImgs({
  584 + maxCount: 3,
  585 + uploadText: '选择验收图片',
  586 + sizeType: ['compressed'],
  587 + formRef: null,
  588 + fieldName: 'acceptImgs'
  589 +})
  590 +
  591 +
  592 +// ========== 生成临时key ==========
  593 +const generateTempKey = () => {
  594 + return 'renew_order_' + Date.now() + '_' + Math.floor(Math.random() * 10000);
  595 +};
  596 +
  597 +// ========== handleRenew 重新提交工单 ==========
  598 +const handleRenew = (item: any) => {
  599 + // 校验工单有效性
  600 + if (!item || !item.id) {
  601 + uni.showToast({title: '工单信息异常,无法重新提交', icon: 'none'});
  602 + return;
  603 + }
  604 +
  605 + // 1. 生成唯一临时标识
  606 + const tempKey = generateTempKey();
  607 +
  608 + // 2. 将完整工单数据存入本地临时存储(同步存储,确保数据立即生效)
  609 + try {
  610 + uni.setStorageSync(tempKey, item);
  611 + } catch (error) {
  612 + console.error('存储工单数据失败:', error);
  613 + uni.showToast({title: '数据存储异常,无法重新提交', icon: 'none'});
  614 + return;
  615 + }
  616 +
  617 + // 3. URL 仅传递「唯一标识」和「重新提交标记」
  618 + uni.navigateTo({
  619 + url: `/pages-sub/problem/work-order-manage/add-order?isRenew=1&tempKey=${tempKey}`
  620 + });
  621 +};
  622 +
  623 +// ========== handleReject 打开回退弹窗 ==========
  624 +const handleReject = (item: any) => {
  625 + // 校验工单有效性
  626 + if (!item || !item.id) {
  627 + uni.showToast({title: '工单信息异常,无法回退', icon: 'none'});
  628 + return;
  629 + }
  630 + currentRejectItem.value = item;
  631 + rejectReason.value = ''; // 清空上次输入
  632 + rejectImgs.clearImgs(); // 清空上传图片
  633 + rejectModalShow.value = true; // 显示回退modal
  634 +};
  635 +
  636 +// ========== 回退弹窗取消按钮 ==========
  637 +const handleRejectModalCancel = () => {
  638 + rejectModalShow.value = false;
  639 + rejectReason.value = '';
  640 + rejectImgs.clearImgs(); // 清空上传图片
  641 +};
  642 +
  643 +// ========== 确认回退工单(新增returnImgs传参) ==========
  644 +const confirmReject = async () => {
  645 + // 严格校验回退原因(去除首尾空格)
  646 + const rejectReasonTrim = rejectReason.value.trim();
  647 + if (!rejectReasonTrim) {
  648 + uni.showToast({title: '请填写回退原因', icon: 'none', duration: 1000});
  649 + return;
  650 + }
  651 + // 校验当前工单有效性
  652 + if (!currentRejectItem.value || !currentRejectItem.value.id) {
  653 + uni.showToast({title: '工单信息异常,无法提交', icon: 'none', duration: 1000});
  654 + rejectModalShow.value = false;
  655 + return;
  656 + }
  657 + try {
  658 + // 显示加载中,防止重复提交
  659 + uni.showLoading({title: '提交中...', mask: true});
  660 +
  661 + // 构建请求参数(新增returnImgs)
  662 + const requestData = {
  663 + "returnImgs": rejectImgs.getSuccessImgUrls(), // 回退图片URL数组
  664 + "workerDataId": currentRejectItem.value.id,
  665 + "taskKey": currentRejectItem.value.taskKey,
  666 + "taskId": currentRejectItem.value.taskId,
  667 + "operateType": nextStepMap[currentRejectItem.value.taskKey].operateTypeNoPass,
  668 + "agree": 1,
  669 + "reason": rejectReasonTrim
  670 + };
  671 + // 调用回退工单接口
  672 + const res = await universalApproval(requestData);
  673 + uni.showToast({title: '回退成功', icon: 'success', duration: 1000});
  674 +
  675 + rejectModalShow.value = false;
  676 + // 重新获取工单详情,刷新页面
  677 + // await DetailQuery(taskId.value);
  678 + uni.reLaunch({
  679 + url: `/pages-sub/problem/work-order-manage/index`
  680 + });
  681 + } catch (error) {
  682 + console.error('回退工单失败:', error);
  683 + uni.showToast({title: '网络异常,回退失败', icon: 'none', duration: 1000});
  684 + } finally {
  685 + // 隐藏加载中
  686 + uni.hideLoading();
  687 + }
  688 +};
  689 +
  690 +// ========== handleProcess 处理工单 ==========
  691 +const handleProcess = async (item: any) => {
  692 + console.log(nextStepMap[item.taskKey]?.name)
  693 + try {
  694 + if (nextStepMap[item.taskKey]?.name == '养护组长分配') {
  695 + uni.navigateTo({
  696 + url: `/pages-sub/problem/work-order-manage/distribution-order?taskId=${item.taskId}&orderNo=${item.orderNo}&id=${item.id}`
  697 + })
  698 + }
  699 + if (nextStepMap[item.taskKey]?.name == '养护员待实施') {
  700 + // ① 生成唯一临时key
  701 + const tempKey = `maintain_order_${Date.now()}_${Math.floor(Math.random() * 10000)}`;
  702 +
  703 + // ② 存储完整item到本地缓存
  704 + try {
  705 + uni.setStorageSync(tempKey, item);
  706 + } catch (error) {
  707 + console.error('存储养护工单数据失败:', error);
  708 + uni.showToast({title: '数据存储异常,无法跳转', icon: 'none'});
  709 + return;
  710 + }
  711 +
  712 + // ③ URL仅传递临时key
  713 + uni.navigateTo({
  714 + url: `/pages-sub/problem/work-order-manage/add-maintain-order?tempKey=${tempKey}`
  715 + })
  716 + }
  717 + // 养护组长验收 - 打开弹窗
  718 + if (nextStepMap[item.taskKey]?.name == '养护组长验收') {
  719 + currentAcceptItem.value = item; // 存储当前工单信息
  720 + acceptReason.value = ''; // 清空上次的验收原因
  721 + acceptRadioValue.value = '0'; // 重置默认选中“通过”
  722 + acceptImgs.clearImgs(); // 清空验收图片
  723 + acceptModalShow.value = true; // 显示验收弹窗
  724 + }
  725 + // 巡查员验收 - 打开弹窗
  726 + if (nextStepMap[item.taskKey]?.name == '巡查员验收') {
  727 + currentAcceptItem.value = item; // 存储当前工单信息
  728 + acceptReason.value = ''; // 清空上次的验收原因
  729 + acceptRadioValue.value = '0'; // 重置默认选中“通过”
  730 + acceptImgs.clearImgs(); // 清空验收图片
  731 + acceptModalShow.value = true; // 显示验收弹窗
  732 + }
  733 +
  734 + // 发起人确认
  735 + if (nextStepMap[item.taskKey]?.name == '发起人确认') {
  736 + console.log(item)
  737 + uni.showModal({
  738 + title: "结束工单",
  739 + content: "请确定是否结束工单?",
  740 + success: async function (res) {
  741 + if (res.confirm) {
  742 + // 构建请求参数(携带returnImgs)
  743 + const requestData = {
  744 + "returnImgs": rejectImgs.getSuccessImgUrls(),
  745 + "workerDataId": item.id,
  746 + "taskKey": 'ylInspectorStart',
  747 + "taskId": item.taskId,
  748 + "operateType": 200,
  749 + "agree": 1,
  750 + "reason": '结束工单'
  751 + };
  752 + // 调用回退工单接口
  753 + const res = await universalApproval(requestData);
  754 + uni.showToast({title: '结束成功', icon: 'success', duration: 1000});
  755 + // 重新获取工单详情,刷新页面
  756 + await DetailQuery(taskId.value);
  757 + } else if (res.cancel) {
  758 + console.log("用户点击取消");
  759 + }
  760 + },
  761 + });
  762 + }
  763 + } catch (error) {
  764 + console.error('处理工单失败:', error);
  765 + uni.showToast({title: '处理失败,请重试', icon: 'none'});
  766 + }
  767 +};
  768 +
  769 +// ========== 验收弹窗取消按钮(新增清空图片) ==========
  770 +const handleAcceptModalCancel = () => {
  771 + acceptModalShow.value = false;
  772 + acceptReason.value = '';
  773 + acceptRadioValue.value = '0';
  774 + acceptImgs.clearImgs(); // 清空验收图片
  775 +};
  776 +
  777 +// ========== 验收弹窗确认按钮(新增returnImgs传参) ==========
  778 +const handleAcceptModalConfirm = async () => {
  779 + // 1. 校验验收原因是否为空
  780 + if (!acceptReason.value.trim()) {
  781 + uni.showToast({title: '请填写验收原因', icon: 'none', duration: 1000});
  782 + return;
  783 + }
  784 + // 2. 校验验收原因长度
  785 + if (acceptReason.value.length > 200) {
  786 + uni.showToast({title: '验收原因最多200字', icon: 'none', duration: 1000});
  787 + return;
  788 + }
  789 + try {
  790 + // 3. 调用验收接口
  791 + console.log(currentAcceptItem.value)
  792 + let postData: any = {}
  793 + if (currentAcceptItem.value?.taskKey == 'ylTeamLeaderConfirm') { // 养护组长验收
  794 + postData = {
  795 + "returnImgs": acceptImgs.getSuccessImgUrls(), // 验收图片URL数组
  796 + "taskKey": currentAcceptItem.value.taskKey, // ylTeamLeaderConfirm
  797 + "workerDataId": currentAcceptItem.value.id,
  798 + "taskId": currentAcceptItem.value.taskId,
  799 + "operateType": acceptRadioValue.value == 0 ? nextStepMap[currentAcceptItem.value.taskKey].operateTypePass : nextStepMap[currentAcceptItem.value.taskKey].operateTypeNoPass,
  800 + "reason": acceptReason.value.trim()
  801 + }
  802 + }
  803 + if (currentAcceptItem.value?.taskKey == 'ylInspector') { // 巡查员验收
  804 + postData = {
  805 + "returnImgs": acceptImgs.getSuccessImgUrls(), // 验收图片URL数组
  806 + "taskKey": currentAcceptItem.value.taskKey, //ylInspector
  807 + "taskId": currentAcceptItem.value.taskId,
  808 + "workerDataId": currentAcceptItem.value.id,
  809 + "operateType": acceptRadioValue.value == 0 ? nextStepMap[currentAcceptItem.value.taskKey].operateTypePass : nextStepMap[currentAcceptItem.value.taskKey].operateTypeNoPass,
  810 + "reason": acceptReason.value.trim(),
  811 + "agree": acceptRadioValue.value
  812 + }
  813 + }
  814 + const acceptRes = await universalApproval(postData);
  815 + // 4. 操作成功处理
  816 +
  817 + handleAcceptModalCancel(); // 清空状态
  818 + // 重新获取工单详情,刷新页面
  819 + // await DetailQuery(taskId.value);
  820 + // uni.reLaunch({
  821 + // url: `/pages-sub/problem/work-order-manage/index`
  822 + // });
  823 + eventChannel.emit('needRefresh');
  824 + // 4. 返回列表页
  825 + uni.navigateBack({delta: 1});
  826 + uni.showToast({title: '提交成功', icon: 'success', duration: 1000});
  827 + } catch (error) {
  828 + // 5. 操作失败处理
  829 + console.error('验收失败:', error);
  830 + uni.showToast({title: '验收提交失败,请重试', icon: 'none', duration: 1000});
  831 + }
  832 +};
  833 +
  834 +onLoad((options: any) => {
  835 + console.log('页面入参:', options)
  836 + const {taskId: taskIdOpt, activeTab: activeTabOpt, processInstanceId: processInstanceIdOpt} = options;
  837 + // 0-待办 1-我发起的 2-已办
  838 + taskId.value = taskIdOpt || '';
  839 + activeTab.value = activeTabOpt || '0';
  840 + processInstanceId.value = processInstanceIdOpt;
  841 + DetailQuery(taskId.value);
  842 +});
  843 +
  844 +onShow(() => {
  845 + // 注释原有逻辑,避免重复加载
  846 +});
  847 +</script>
  848 +
13 <style scoped lang="scss"> 849 <style scoped lang="scss">
  850 +// 全局样式
  851 +.page-container {
  852 + min-height: 100vh;
  853 +}
  854 +
  855 +// 主内容容器
  856 +.main-content {
  857 + padding-bottom: 80rpx;
  858 +}
  859 +
  860 +// 顶部Tabs
  861 +.top-tabs {
  862 + //background-color: #fff;
  863 +}
  864 +
  865 +// Tab内容区
  866 +.tab-content {
  867 + //padding: 16rpx;
  868 +}
  869 +
  870 +// 工单详情内容
  871 +.detail-content {
  872 + .detail-cell-group {
  873 + background-color: #fff;
  874 + margin-bottom: 20rpx;
  875 + }
  876 +
  877 + .cell-content-wrap {
  878 + // 保持原有样式,兼容up-album布局
  879 + }
  880 +
  881 + .empty-text {
  882 + color: #999;
  883 + font-size: 26rpx;
  884 + }
  885 +}
  886 +
  887 +// 图片分类Tabs区块
  888 +.img-tabs-block {
  889 + background-color: #fff;
  890 + //border-radius: 12rpx;
  891 + //padding: 16rpx;
  892 +
  893 + // 图片内容区
  894 + .img-tab-content {
  895 + padding: 20rpx 15px ;
  896 + text-align: center;
  897 + //min-height: 120rpx;
  898 + //display: flex;
  899 + //align-items: center;
  900 + //justify-content: center;
  901 + //
  902 + .img-album {
  903 + //width: 100%;
  904 +
  905 + }
  906 +
  907 + .empty-img-text {
  908 + height: 75px;
  909 + line-height: 75px;
  910 + color: #999;
  911 + font-size: 28rpx;
  912 + }
  913 + }
  914 +}
  915 +
  916 +// 流程节点区域(完整样式,确保内容可见)
  917 +.process-content {
  918 + //padding: 16rpx;
  919 + //min-height: 400rpx;
  920 +
  921 + .empty-process {
  922 + margin-top: 100rpx;
  923 + }
  924 +
  925 + // 竖向步骤条容器样式
  926 + .vertical-steps {
  927 + //width: 100%;
  928 + background-color: #fff;
  929 + padding: 20rpx;
  930 + border-radius: 12rpx;
  931 + display: flex;
  932 + flex-direction: column;
  933 + gap: 20rpx; // 步骤项间距
  934 + }
  935 +
  936 + // 自定义内容容器样式
  937 + .step-content-wrap {
  938 + width: 100%;
  939 + //padding: 10rpx 0;
  940 + box-sizing: border-box;
  941 +
  942 + // 节点标题 + 操作人样式
  943 + .step-title {
  944 + font-size: 30rpx;
  945 + font-weight: 600;
  946 + color: #333;
  947 + margin-bottom: 12rpx;
  948 +
  949 + .operator-name {
  950 + font-size: 30rpx;
  951 + font-weight: 600;
  952 + color: #333;
  953 + margin-left: 20rpx;
  954 + }
  955 + }
  956 +
  957 + // 描述(时间 + 处理说明)样式
  958 + .step-desc {
  959 + font-size: 24rpx;
  960 + color: #666;
  961 + line-height: 1.6;
  962 + margin-bottom: 12rpx;
  963 +
  964 + .time-line {
  965 + margin-bottom: 8rpx;
  966 +
  967 + .processing-tag {
  968 + color: #f59e0b;
  969 + margin-left: 10rpx;
  970 + }
  971 + }
  972 +
  973 + .reason-line {
  974 + word-break: break-all; // 长文本自动换行
  975 + }
  976 + }
  977 +
  978 + // 相册容器样式
  979 + .step-album-wrap {
  980 + padding: 10rpx 0;
  981 +
  982 + .step-album {
  983 + //width: 100%;
  984 + }
  985 +
  986 + .no-img-tip {
  987 + font-size: 24rpx;
  988 + color: #999;
  989 + margin-top: 10rpx;
  990 + }
  991 + }
  992 + }
  993 +}
  994 +
  995 +// 上传图片通用样式
  996 +.upload-wrap {
  997 + margin-top: 20rpx;
  998 +
  999 + .upload-title {
  1000 + font-size: 28rpx;
  1001 + color: #333;
  1002 + margin-bottom: 10rpx;
  1003 + }
  1004 +}
  1005 +
  1006 +.mt-20 {
  1007 + margin-top: 20rpx;
  1008 +}
  1009 +
  1010 +// 回退弹窗样式
  1011 +.reject-modal-content {
  1012 + width: 100%;
  1013 + box-sizing: border-box;
  1014 + padding: 10rpx 0;
  1015 +}
  1016 +
  1017 +.reject-textarea {
  1018 + width: 100%;
  1019 +}
  1020 +
  1021 +// 验收弹窗样式
  1022 +.accept-modal-content {
  1023 + width: 100%;
  1024 + box-sizing: border-box;
  1025 +}
  1026 +
  1027 +.radio-group-wrap {
  1028 + display: flex;
  1029 + align-items: center;
  1030 + gap: 40rpx;
  1031 + font-size: 28rpx;
  1032 + margin-bottom: 20rpx;
  1033 +}
  1034 +
  1035 +.textarea-wrap {
  1036 + width: 100%;
  1037 + margin-top: 30rpx;
  1038 +}
  1039 +
  1040 +.mt-30 {
  1041 + margin-top: 30rpx;
  1042 +}
  1043 +// 针对 up-album 单图容器的样式(穿透 scoped 限制)
  1044 +:deep .u-album__row__wrapper image {
  1045 + width: 70px !important; // 与多图保持一致
  1046 + height: 70px !important;
  1047 +}
  1048 +
14 1049
15 </style> 1050 </style>
16 \ No newline at end of file 1051 \ No newline at end of file
pages-sub/problem/work-order-manage/add-order.vue
@@ -5,7 +5,7 @@ @@ -5,7 +5,7 @@
5 label-position="left" 5 label-position="left"
6 :model="workOrderForm" 6 :model="workOrderForm"
7 ref="workOrderFormRef" 7 ref="workOrderFormRef"
8 - labelWidth="190rpx" 8 + labelWidth="200rpx"
9 > 9 >
10 <!-- 业务线单选框:移入form内,作为第一个表单项(核心调整) --> 10 <!-- 业务线单选框:移入form内,作为第一个表单项(核心调整) -->
11 <up-form-item 11 <up-form-item
pages-sub/problem/work-order-manage/distribution-order.vue
@@ -97,6 +97,7 @@ const assigneeList = ref([]) // 养护员列表 @@ -97,6 +97,7 @@ const assigneeList = ref([]) // 养护员列表
97 // ========== 工单表单数据(仅保留所需字段) ========== 97 // ========== 工单表单数据(仅保留所需字段) ==========
98 const workOrderForm = reactive({ 98 const workOrderForm = reactive({
99 id:'', 99 id:'',
  100 + busiLine:'',
100 taskId:'', 101 taskId:'',
101 orderNo: '', // 工单编号(对应接口参数id) 102 orderNo: '', // 工单编号(对应接口参数id)
102 assigneeId: '', // 养护员ID(对应接口参数nextAssignee) 103 assigneeId: '', // 养护员ID(对应接口参数nextAssignee)
@@ -120,6 +121,9 @@ onLoad((options) =&gt; { @@ -120,6 +121,9 @@ onLoad((options) =&gt; {
120 workOrderForm.orderNo = options.orderNo 121 workOrderForm.orderNo = options.orderNo
121 workOrderForm.taskId = options.taskId 122 workOrderForm.taskId = options.taskId
122 workOrderForm.id = options.id 123 workOrderForm.id = options.id
  124 + workOrderForm.busiLine = options.busiLine
  125 +
  126 +
123 } 127 }
124 console.log('从URL获取工单编号:', workOrderForm.orderNo) 128 console.log('从URL获取工单编号:', workOrderForm.orderNo)
125 }) 129 })
@@ -141,8 +145,8 @@ const loadAssigneeList = async () =&gt; { @@ -141,8 +145,8 @@ const loadAssigneeList = async () =&gt; {
141 console.log(userStore.userInfo.user.busiLine) 145 console.log(userStore.userInfo.user.busiLine)
142 let queryData = { 146 let queryData = {
143 // roleCode:userStore.userInfo.roles, 147 // roleCode:userStore.userInfo.roles,
144 - roleCode: 'yl_worker',  
145 - busiLine: 'yl', 148 + // roleCode: 'yl_worker',
  149 + busiLine: workOrderForm.busiLine,
146 // busiLine: userStore.userInfo.user.busiLine, 150 // busiLine: userStore.userInfo.user.busiLine,
147 pageNo: 1, 151 pageNo: 1,
148 pageSize: 100 152 pageSize: 100
pages-sub/problem/work-order-manage/index.vue
@@ -202,7 +202,7 @@ @@ -202,7 +202,7 @@
202 <view class="upload-wrap mt-20"> 202 <view class="upload-wrap mt-20">
203 <view class="upload-title">上传图片(选填)</view> 203 <view class="upload-title">上传图片(选填)</view>
204 <up-upload 204 <up-upload
205 - :file-list="rejectImgs.imgList" 205 + :file-list="rejectImgs.rawImgList.value|| []"
206 @after-read="rejectImgs.uploadImgs" 206 @after-read="rejectImgs.uploadImgs"
207 @delete="rejectImgs.deleteImg" 207 @delete="rejectImgs.deleteImg"
208 multiple 208 multiple
@@ -251,7 +251,7 @@ @@ -251,7 +251,7 @@
251 <view class="upload-wrap mt-20"> 251 <view class="upload-wrap mt-20">
252 <view class="upload-title">上传验收图片(选填)</view> 252 <view class="upload-title">上传验收图片(选填)</view>
253 <up-upload 253 <up-upload
254 - :file-list="acceptImgs.imgList" 254 + :file-list="acceptImgs.rawImgList.value || []"
255 @after-read="acceptImgs.uploadImgs" 255 @after-read="acceptImgs.uploadImgs"
256 @delete="acceptImgs.deleteImg" 256 @delete="acceptImgs.deleteImg"
257 257
@@ -325,10 +325,6 @@ const rejectImgs = useUploadImgs({ @@ -325,10 +325,6 @@ const rejectImgs = useUploadImgs({
325 fieldName: 'rejectImgs' // 自定义字段名 325 fieldName: 'rejectImgs' // 自定义字段名
326 }) 326 })
327 327
328 -// 监听上传实例响应式变化,解决u-upload不刷新问题  
329 -watch(() => rejectImgs.rawImgList.value, (newVal) => {  
330 - rejectImgs.imgList = newVal  
331 -}, { deep: true })  
332 328
333 // ========== 验收弹窗相关状态(含图片上传) ========== 329 // ========== 验收弹窗相关状态(含图片上传) ==========
334 const acceptModalShow = ref(false); // 验收弹窗显示开关 330 const acceptModalShow = ref(false); // 验收弹窗显示开关
@@ -345,11 +341,6 @@ const acceptImgs = useUploadImgs({ @@ -345,11 +341,6 @@ const acceptImgs = useUploadImgs({
345 fieldName: 'acceptImgs' // 自定义字段名,区分回退图片 341 fieldName: 'acceptImgs' // 自定义字段名,区分回退图片
346 }) 342 })
347 343
348 -// 监听验收图片上传实例响应式变化,解决u-upload不刷新问题  
349 -watch(() => acceptImgs.rawImgList.value, (newVal) => {  
350 - acceptImgs.imgList = newVal  
351 -}, { deep: true })  
352 -  
353 // 分页查询列表 344 // 分页查询列表
354 const queryList = async (pageNo, pageSize) => { 345 const queryList = async (pageNo, pageSize) => {
355 try { 346 try {
@@ -451,7 +442,7 @@ const handleProcess = async (item) =&gt; { @@ -451,7 +442,7 @@ const handleProcess = async (item) =&gt; {
451 try { 442 try {
452 if (nextStepMap[item.taskKey]?.name == '养护组长分配') { 443 if (nextStepMap[item.taskKey]?.name == '养护组长分配') {
453 uni.navigateTo({ 444 uni.navigateTo({
454 - url: `/pages-sub/problem/work-order-manage/distribution-order?taskId=${item.taskId}&orderNo=${item.orderNo}&id=${item.id}` 445 + url: `/pages-sub/problem/work-order-manage/distribution-order?taskId=${item.taskId}&orderNo=${item.orderNo}&id=${item.id}&busiLine=${item.busiLine}`
455 }) 446 })
456 } 447 }
457 if (nextStepMap[item.taskKey]?.name == '养护员待实施') { 448 if (nextStepMap[item.taskKey]?.name == '养护员待实施') {
pages-sub/problem/work-order-manage/order-detail.vue
@@ -160,11 +160,10 @@ @@ -160,11 +160,10 @@
160 :urls="currentImgList.slice(0, 3)" 160 :urls="currentImgList.slice(0, 3)"
161 :singleSize="70" 161 :singleSize="70"
162 :multipleSize="70" 162 :multipleSize="70"
163 -  
164 :preview-full-image="true" 163 :preview-full-image="true"
165 class="img-album custom-album" 164 class="img-album custom-album"
166 ></up-album> 165 ></up-album>
167 - <text v-else class="empty-img-text">暂无图片</text> 166 + <text v-else class="empty-img-text">养护员未上传图片</text>
168 </view> 167 </view>
169 </view> 168 </view>
170 </view> 169 </view>
@@ -290,7 +289,7 @@ @@ -290,7 +289,7 @@
290 <view class="upload-wrap mt-20"> 289 <view class="upload-wrap mt-20">
291 <view class="upload-title">上传回退图片(选填)</view> 290 <view class="upload-title">上传回退图片(选填)</view>
292 <up-upload 291 <up-upload
293 - :file-list="rejectImgs.imgList" 292 + :file-list="rejectImgs.rawImgList.value || []"
294 @after-read="rejectImgs.uploadImgs" 293 @after-read="rejectImgs.uploadImgs"
295 @delete="rejectImgs.deleteImg" 294 @delete="rejectImgs.deleteImg"
296 multiple 295 multiple
@@ -339,7 +338,7 @@ @@ -339,7 +338,7 @@
339 <view class="upload-wrap mt-20"> 338 <view class="upload-wrap mt-20">
340 <view class="upload-title">上传验收图片(选填)</view> 339 <view class="upload-title">上传验收图片(选填)</view>
341 <up-upload 340 <up-upload
342 - :file-list="acceptImgs.imgList" 341 + :file-list="acceptImgs.rawImgList.value || []"
343 @after-read="acceptImgs.uploadImgs" 342 @after-read="acceptImgs.uploadImgs"
344 @delete="acceptImgs.deleteImg" 343 @delete="acceptImgs.deleteImg"
345 multiple 344 multiple
@@ -573,10 +572,7 @@ const rejectImgs = useUploadImgs({ @@ -573,10 +572,7 @@ const rejectImgs = useUploadImgs({
573 formRef: null, 572 formRef: null,
574 fieldName: 'rejectImgs' 573 fieldName: 'rejectImgs'
575 }) 574 })
576 -// 监听上传实例响应式变化,解决u-upload不刷新问题  
577 -watch(() => rejectImgs.rawImgList.value, (newVal) => {  
578 - rejectImgs.imgList = newVal  
579 -}, {deep: true}) 575 +
580 576
581 // ========== 验收弹窗相关状态(新增图片上传实例) ========== 577 // ========== 验收弹窗相关状态(新增图片上传实例) ==========
582 const acceptModalShow = ref(false); // 验收弹窗显示开关 578 const acceptModalShow = ref(false); // 验收弹窗显示开关
@@ -591,10 +587,7 @@ const acceptImgs = useUploadImgs({ @@ -591,10 +587,7 @@ const acceptImgs = useUploadImgs({
591 formRef: null, 587 formRef: null,
592 fieldName: 'acceptImgs' 588 fieldName: 'acceptImgs'
593 }) 589 })
594 -// 监听验收图片上传实例响应式变化  
595 -watch(() => acceptImgs.rawImgList.value, (newVal) => {  
596 - acceptImgs.imgList = newVal  
597 -}, {deep: true}) 590 +
598 591
599 // ========== 生成临时key ========== 592 // ========== 生成临时key ==========
600 const generateTempKey = () => { 593 const generateTempKey = () => {
@@ -900,6 +893,7 @@ onShow(() =&gt; { @@ -900,6 +893,7 @@ onShow(() =&gt; {
900 // 图片内容区 893 // 图片内容区
901 .img-tab-content { 894 .img-tab-content {
902 padding: 20rpx 15px ; 895 padding: 20rpx 15px ;
  896 + text-align: center;
903 //min-height: 120rpx; 897 //min-height: 120rpx;
904 //display: flex; 898 //display: flex;
905 //align-items: center; 899 //align-items: center;
@@ -911,8 +905,8 @@ onShow(() =&gt; { @@ -911,8 +905,8 @@ onShow(() =&gt; {
911 } 905 }
912 906
913 .empty-img-text { 907 .empty-img-text {
914 - height: 100px;  
915 - line-height: 100px; 908 + height: 75px;
  909 + line-height: 75px;
916 color: #999; 910 color: #999;
917 font-size: 28rpx; 911 font-size: 28rpx;
918 } 912 }