Commit 8ee2a9233280f0fdab1ab5722f5071728e9d33bb

Authored by 刘淇
1 parent 9b30ab8c

新增快速工单,原版

pages-sub/daily/quick-order/add-order.vue
1 1 <template>
2 2 <view class="u-page">
3   - <!-- 工单表单容器 -->
4   - <view class="work-order-form-content commonPageLRpadding">
5   - <!-- uview-plus表单(Vue3 适配) -->
6   - <up-form
7   - label-position="left"
8   - :model="workOrderForm"
9   - ref="workOrderFormRef"
10   - labelWidth="220rpx"
11   - :rules="workOrderFormRules"
12   - >
  3 + <!-- 核心:将所有 up-form-item 包裹在同一个 up-form 内 -->
  4 + <up-form
  5 + label-position="left"
  6 + :model="workOrderForm"
  7 + ref="workOrderFormRef"
  8 + labelWidth="220rpx"
  9 + :rules="workOrderFormRules"
  10 + :error-type="['message', 'border']"
  11 + class="work-order-form-container"
  12 + >
  13 + <!-- 工单表单容器 -->
  14 + <view class="work-order-form-content commonPageLRpadding">
13 15 <!-- 1. 工单位置(地图选择) -->
14 16 <up-form-item
15 17 label="工单位置"
... ... @@ -80,75 +82,58 @@
80 82 count
81 83 maxlength="150"
82 84 rows="4"
  85 + @blur="() => workOrderFormRef.value?.validateField('problemDesc')"
83 86 ></up-textarea>
84 87 </up-form-item>
85   - </up-form>
86   -
87   - <!-- 道路名称下拉弹窗 -->
88   - <up-action-sheet
89   - :show="showRoadName"
90   - :actions="roadNameList"
91   - title="请选择道路名称"
92   - @close="showRoadName = false"
93   - @select="handleRoadNameSelect"
94   - ></up-action-sheet>
95   -
96   - <!-- 工单名称下拉弹窗 -->
97   - <up-action-sheet
98   - :show="showOrderName"
99   - :actions="orderNameList"
100   - title="请选择工单名称"
101   - @close="showOrderName = false"
102   - @select="handleOrderNameSelect"
103   - ></up-action-sheet>
104   - </view>
105   -
106   - <!-- 图片分类标签 -->
107   - <up-tabs :list="imgTabList" @click="switchImgTab"></up-tabs>
108   -
109   - <!-- 图片上传组件(根据选中标签显示对应列表) -->
110   - <view class="img-upload-wrap" v-if="activeImgTab === '1'">
111   - <up-form-item label="问题照片" prop="problemImgs" required>
112   - <up-upload
113   - :file-list="problemImgsList"
114   - @after-read="(event) => uploadImgs(event, 'problemImgsList')"
115   - @delete="(event) => deleteImg(event, 'problemImgsList')"
116   - multiple
117   - :max-count="3"
118   - upload-text="选择问题照片"
119   - ></up-upload>
120   - </up-form-item>
121   - </view>
122   - <view class="img-upload-wrap" v-else>
123   - <up-form-item label="完成照片" prop="completeImgs" required>
124   - <up-upload
125   - :file-list="completeImgsList"
126   - @after-read="(event) => uploadImgs(event, 'completeImgsList')"
127   - @delete="(event) => deleteImg(event, 'completeImgsList')"
128   - multiple
129   - :max-count="3"
130   - :sizeType="['compressed']"
131   - upload-text="选择完成照片"
132   - ></up-upload>
133   - </up-form-item>
134   - </view>
  88 + </view>
  89 +
  90 + <!-- 问题照片:独立一行显示(移入 up-form 内) -->
  91 + <view class="img-upload-wrap">
  92 + <up-form-item label="问题照片" prop="problemImgs" required>
  93 + <up-upload
  94 + :file-list="problemImgsList"
  95 + @after-read="(event) => uploadImgs(event, 'problemImgsList')"
  96 + @delete="(event) => deleteImg(event, 'problemImgsList')"
  97 + multiple
  98 + :max-count="3"
  99 + upload-text="选择问题照片"
  100 + ></up-upload>
  101 + </up-form-item>
  102 + </view>
  103 +
  104 + <!-- 完成照片:独立一行显示(移入 up-form 内) -->
  105 + <view class="img-upload-wrap">
  106 + <up-form-item label="完成照片" prop="completeImgs" required>
  107 + <up-upload
  108 + :file-list="completeImgsList"
  109 + @after-read="(event) => uploadImgs(event, 'completeImgsList')"
  110 + @delete="(event) => deleteImg(event, 'completeImgsList')"
  111 + multiple
  112 + :max-count="3"
  113 + :sizeType="['compressed']"
  114 + upload-text="选择完成照片"
  115 + ></up-upload>
  116 + </up-form-item>
  117 + </view>
135 118  
136   - <!-- 处理结果描述(独立显示) -->
137   - <view class="handle-result-wrap">
138   - <up-form-item
139   - label="处理结果"
140   - prop="handleResultDesc"
141   - required
142   - >
143   - <up-textarea
144   - placeholder="请输入处理结果描述(最多200字)"
145   - v-model="workOrderForm.handleResultDesc"
146   - count
147   - maxlength="200"
148   - rows="4"
149   - ></up-textarea>
150   - </up-form-item>
151   - </view>
  119 + <!-- 处理结果描述(独立显示,移入 up-form 内) -->
  120 + <view class="handle-result-wrap">
  121 + <up-form-item
  122 + label="处理结果"
  123 + prop="handleResultDesc"
  124 + required
  125 + >
  126 + <up-textarea
  127 + placeholder="请输入处理结果描述(最多200字)"
  128 + v-model="workOrderForm.handleResultDesc"
  129 + count
  130 + maxlength="200"
  131 + rows="4"
  132 + @blur="() => workOrderFormRef.value?.validateField('handleResultDesc')"
  133 + ></up-textarea>
  134 + </up-form-item>
  135 + </view>
  136 + </up-form>
152 137  
153 138 <!-- 底部提交按钮 -->
154 139 <view class="fixed-bottom-btn-wrap">
... ... @@ -160,6 +145,24 @@
160 145 :disabled="isSubmitting"
161 146 ></up-button>
162 147 </view>
  148 +
  149 + <!-- 道路名称下拉弹窗 -->
  150 + <up-action-sheet
  151 + :show="showRoadName"
  152 + :actions="roadNameList"
  153 + title="请选择道路名称"
  154 + @close="showRoadName = false"
  155 + @select="handleRoadNameSelect"
  156 + ></up-action-sheet>
  157 +
  158 + <!-- 工单名称下拉弹窗 -->
  159 + <up-action-sheet
  160 + :show="showOrderName"
  161 + :actions="orderNameList"
  162 + title="请选择工单名称"
  163 + @close="showOrderName = false"
  164 + @select="handleOrderNameSelect"
  165 + ></up-action-sheet>
163 166 </view>
164 167 </template>
165 168  
... ... @@ -172,19 +175,12 @@ import { getRoadListByLatLng } from &#39;@/api/common&#39;
172 175 import { uploadImages } from '@/common/utils/upload';
173 176 import { createQuick } from '@/api/quick-order/quick-order'
174 177  
175   -// ========== 基础变量定义(语义化命名) ==========
176   -// 图片分类标签列表
177   -const imgTabList = ref([
178   - { name: '问题照片', id: '1' },
179   - { name: '完成照片', id: '2' }
180   -])
181   -// 激活的图片标签(默认选中问题照片)
182   -const activeImgTab = ref('1')
  178 +// ========== 基础变量定义 ==========
183 179 // 问题照片列表
184 180 const problemImgsList = ref<UploadFile[]>([])
185 181 // 完成照片列表
186 182 const completeImgsList = ref<UploadFile[]>([])
187   -// 表单Ref(语义化:工单表单)
  183 +// 表单Ref
188 184 const workOrderFormRef = ref<UniFormRef>(null)
189 185 // 提交加载状态
190 186 const isSubmitting = ref(false)
... ... @@ -199,7 +195,7 @@ const orderNameList = ref([
199 195 { name: '垃圾清理', code: 'ORDER003' }
200 196 ])
201 197  
202   -// ========== 工单表单数据(语义化字段名) ==========
  198 +// ========== 工单表单数据 ==========
203 199 const workOrderForm = reactive({
204 200 roadId: 0, // 道路ID
205 201 roadName: '', // 道路名称
... ... @@ -211,20 +207,20 @@ const workOrderForm = reactive({
211 207 lon: 0 // 经度
212 208 })
213 209  
214   -// ========== 表单校验规则(修复trigger + 语义化) ==========
  210 +// ========== 表单校验规则 ==========
215 211 const workOrderFormRules = reactive({
216 212 workLocation: [
217   - { type: 'string', required: true, message: '请选择工单位置', trigger: 'change' }
  213 + { type: 'string', required: true, message: '请选择工单位置', trigger: ['change', 'blur'] }
218 214 ],
219 215 roadName: [
220   - { type: 'string', required: true, message: '请选择道路名称', trigger: 'change' }
  216 + { type: 'string', required: true, message: '请选择道路名称', trigger: ['change', 'blur'] }
221 217 ],
222 218 orderName: [
223   - { type: 'string', required: true, message: '请选择工单名称', trigger: 'change' }
  219 + { type: 'string', required: true, message: '请选择工单名称', trigger: ['change', 'blur'] }
224 220 ],
225 221 problemDesc: [
226   - { type: 'string', required: true, message: '请输入情况描述', trigger: 'change' },
227   - { type: 'string', min: 3, max: 150, message: '情况描述需3-150字', trigger: 'change' }
  222 + { type: 'string', required: true, message: '请输入情况描述', trigger: ['change', 'blur'] },
  223 + { type: 'string', min: 3, max: 150, message: '情况描述需3-150字', trigger: ['change', 'blur'] }
228 224 ],
229 225 problemImgs: [
230 226 { required: true, message: '请上传问题照片', trigger: 'change' }
... ... @@ -233,16 +229,14 @@ const workOrderFormRules = reactive({
233 229 { required: true, message: '请上传完成照片', trigger: 'change' }
234 230 ],
235 231 handleResultDesc: [
236   - { type: 'string', required: true, message: '请输入处理结果描述', trigger: 'change' },
237   - { type: 'string', min: 3, max: 200, message: '处理结果需3-200字', trigger: 'change' }
  232 + { type: 'string', required: true, message: '请输入处理结果描述', trigger: ['change', 'blur'] },
  233 + { type: 'string', min: 3, max: 200, message: '处理结果需3-200字', trigger: ['change', 'blur'] }
238 234 ]
239 235 })
240 236  
241   -// ========== 方法定义(语义化命名 + 修复逻辑) ==========
  237 +// ========== 方法定义 ==========
242 238 /**
243 239 * 删除图片
244   - * @param event 删除事件
245   - * @param type 图片类型(problemImgsList/completeImgsList)
246 240 */
247 241 const deleteImg = (event: UploadDeleteEvent, type: string) => {
248 242 console.log('删除图片事件:', event, '类型:', type)
... ... @@ -255,19 +249,14 @@ const deleteImg = (event: UploadDeleteEvent, type: string) =&gt; {
255 249 }
256 250  
257 251 /**
258   - * 上传图片(单/多图统一逻辑)
259   - * @param event 上传事件
260   - * @param type 图片类型(problemImgsList/completeImgsList)
  252 + * 上传图片
261 253 */
262 254 const uploadImgs = async (event: { file: UploadFile | UploadFile[] }, type: string) => {
263 255 console.log('上传图片事件:', event, '类型:', type)
264   - // 统一处理为数组格式
265 256 const fileList = Array.isArray(event.file) ? event.file : [event.file]
266 257 const targetImgList = type === 'problemImgsList' ? problemImgsList : completeImgsList
267 258  
268   - // 1. 提取文件路径
269 259 const filePaths = fileList.map(item => item.url)
270   - // 2. 添加上传中临时项
271 260 const tempItems: UploadFile[] = fileList.map(item => ({
272 261 ...item,
273 262 status: 'uploading' as const,
... ... @@ -277,14 +266,12 @@ const uploadImgs = async (event: { file: UploadFile | UploadFile[] }, type: stri
277 266 targetImgList.value.push(...tempItems)
278 267  
279 268 try {
280   - // 3. 调用统一上传方法
281 269 const uploadResultUrls = await uploadImages({
282 270 filePaths: filePaths,
283 271 ignoreError: true
284 272 })
285 273 console.log('上传成功的URL列表:', uploadResultUrls)
286 274  
287   - // 4. 更新成功状态
288 275 uploadResultUrls.forEach((url, index) => {
289 276 if (targetImgList.value[startIndex + index]) {
290 277 targetImgList.value.splice(startIndex + index, 1, {
... ... @@ -296,7 +283,6 @@ const uploadImgs = async (event: { file: UploadFile | UploadFile[] }, type: stri
296 283 }
297 284 })
298 285  
299   - // 5. 处理失败项
300 286 if (uploadResultUrls.length < fileList.length) {
301 287 const failCount = fileList.length - uploadResultUrls.length
302 288 for (let i = uploadResultUrls.length; i < fileList.length; i++) {
... ... @@ -314,7 +300,6 @@ const uploadImgs = async (event: { file: UploadFile | UploadFile[] }, type: stri
314 300 }
315 301 } catch (err) {
316 302 console.error('图片上传失败:', err)
317   - // 标记所有为失败
318 303 for (let i = 0; i < fileList.length; i++) {
319 304 if (targetImgList.value[startIndex + i]) {
320 305 targetImgList.value.splice(startIndex + i, 1, {
... ... @@ -329,16 +314,7 @@ const uploadImgs = async (event: { file: UploadFile | UploadFile[] }, type: stri
329 314 }
330 315  
331 316 /**
332   - * 切换图片标签
333   - * @param item 标签项
334   - */
335   -const switchImgTab = (item: { name: string; id: string }) => {
336   - console.log('切换图片标签:', item)
337   - activeImgTab.value = item.id
338   -}
339   -
340   -/**
341   - * 选择工单位置(地图)- 核心修改:清空道路名称
  317 + * 选择工单位置
342 318 */
343 319 const chooseWorkLocation = () => {
344 320 if (isSubmitting.value) return
... ... @@ -348,17 +324,14 @@ const chooseWorkLocation = () =&gt; {
348 324 success: () => {
349 325 uni.chooseLocation({
350 326 success: async (res) => {
351   - // ========== 核心新增:清空道路名称相关数据 ==========
352   - workOrderForm.roadName = '' // 清空道路名称
353   - workOrderForm.roadId = 0 // 清空道路ID
354   - roadNameList.value = [] // 清空道路名称列表
  327 + workOrderForm.roadName = ''
  328 + workOrderForm.roadId = 0
  329 + roadNameList.value = []
355 330  
356   - // 更新工单位置
357 331 workOrderForm.workLocation = res.name
358 332 workOrderForm.lat = res.latitude
359 333 workOrderForm.lon = res.longitude
360 334  
361   - // 触发工单位置和道路名称的表单校验更新
362 335 workOrderFormRef.value?.validateField('workLocation')
363 336 workOrderFormRef.value?.validateField('roadName')
364 337  
... ... @@ -419,7 +392,6 @@ const chooseWorkLocation = () =&gt; {
419 392  
420 393 /**
421 394 * 选择道路名称
422   - * @param e 选择事件
423 395 */
424 396 const handleRoadNameSelect = (e: UniActionSheetSelectEvent) => {
425 397 console.log('选择道路名称:', e)
... ... @@ -431,7 +403,6 @@ const handleRoadNameSelect = (e: UniActionSheetSelectEvent) =&gt; {
431 403  
432 404 /**
433 405 * 选择工单名称
434   - * @param e 选择事件
435 406 */
436 407 const handleOrderNameSelect = (e: UniActionSheetSelectEvent) => {
437 408 workOrderForm.orderName = e.name
... ... @@ -448,8 +419,6 @@ const hideKeyboard = () =&gt; {
448 419  
449 420 /**
450 421 * 提取图片URL数组
451   - * @param imgList 图片列表
452   - * @returns URL数组
453 422 */
454 423 const getImgUrlList = (imgList: UploadFile[]) => {
455 424 console.log('提取图片URL:', imgList)
... ... @@ -457,29 +426,28 @@ const getImgUrlList = (imgList: UploadFile[]) =&gt; {
457 426 }
458 427  
459 428 /**
460   - * 校验上传图片(兜底校验)
461   - * @returns 是否校验通过
  429 + * 校验上传图片
462 430 */
463 431 const validateUploadImgs = () => {
464   - // 修复:补充返回值,避免函数无返回
465   - if (activeImgTab.value === '1') {
466   - const hasValidImgs = problemImgsList.value.some(item => item.status === 'success')
467   - if (!hasValidImgs) {
468   - uni.showToast({ title: '请上传至少1张问题照片', icon: 'none' })
469   - return false
470   - }
471   - } else {
472   - const hasValidcompleteImgs = completeImgsList.value.some(item => item.status === 'success')
473   - if (!hasValidcompleteImgs) {
474   - uni.showToast({ title: '请上传至少1张完成照片', icon: 'none' })
475   - return false
476   - }
  432 + const hasValidProblemImgs = problemImgsList.value.some(item => item.status === 'success')
  433 + if (!hasValidProblemImgs) {
  434 + uni.showToast({ title: '请上传至少1张问题照片', icon: 'none' })
  435 + workOrderFormRef.value?.validateField('problemImgs')
  436 + return false
477 437 }
  438 +
  439 + const hasValidCompleteImgs = completeImgsList.value.some(item => item.status === 'success')
  440 + if (!hasValidCompleteImgs) {
  441 + uni.showToast({ title: '请上传至少1张完成照片', icon: 'none' })
  442 + workOrderFormRef.value?.validateField('completeImgs')
  443 + return false
  444 + }
  445 +
478 446 return true
479 447 }
480 448  
481 449 /**
482   - * 提交工单(修复核心:await位置 + 表单Ref名称错误)
  450 + * 提交工单
483 451 */
484 452 const submitWorkOrder = async () => {
485 453 if (isSubmitting.value) {
... ... @@ -490,33 +458,36 @@ const submitWorkOrder = async () =&gt; {
490 458 try {
491 459 isSubmitting.value = true
492 460  
493   - // 修复1:兜底校验图片
494 461 if (!validateUploadImgs()) {
495 462 isSubmitting.value = false
496 463 return
497 464 }
498 465  
499   - // 修复2:使用正确的表单Ref名称(workOrderFormRef 而非 uFormRef)
500   - // 修复3:将validate转为Promise形式,配合async/await
501   - const valid = await new Promise((resolve) => {
502   - workOrderFormRef.value?.validate((isValid) => {
  466 + const valid = await new Promise<boolean>((resolve) => {
  467 + workOrderFormRef.value?.validate((isValid, invalidFields) => {
  468 + console.log('校验失败的字段:', invalidFields)
503 469 resolve(isValid)
504 470 })
505 471 })
506 472  
507 473 if (!valid) {
508   - uni.showToast({ title: '表单校验失败,请检查必填项', icon: 'none' })
  474 + const firstInvalidField = Object.keys(workOrderFormRef.value?.invalidFields || {})[0]
  475 + if (firstInvalidField) {
  476 + const el = document.querySelector(`[prop="${firstInvalidField}"]`)
  477 + el?.scrollIntoView({ behavior: 'smooth', block: 'center' })
  478 + }
  479 + uni.showToast({ title: '表单填写不完整,请检查', icon: 'none' })
509 480 isSubmitting.value = false
510 481 return
511 482 }
512 483  
513   - // 构造提交数据
514 484 const submitData = {
515 485 roadId: workOrderForm.roadId,
516 486 roadName: workOrderForm.roadName,
517   - imgs: getImgUrlList(problemImgsList.value), // 问题照片URL数组
518   - longRangeImgList: getImgUrlList(completeImgsList.value), // 完成照片URL数组
519   - remark: activeImgTab.value === '2' ? workOrderForm.handleResultDesc : workOrderForm.problemDesc,
  487 + imgs: getImgUrlList(problemImgsList.value),
  488 + longRangeImgList: getImgUrlList(completeImgsList.value),
  489 + remark: workOrderForm.problemDesc,
  490 + handleResultDesc: workOrderForm.handleResultDesc,
520 491 latLonType: 2,
521 492 lat: workOrderForm.lat,
522 493 lon: workOrderForm.lon,
... ... @@ -529,19 +500,9 @@ const submitWorkOrder = async () =&gt; {
529 500 }
530 501  
531 502 console.log('提交工单数据:', submitData)
532   - // 修复4:await 现在在async函数中,可正常使用
533 503 await createQuick(submitData)
534 504 uni.showToast({ title: '工单提交成功', icon: 'success' })
535 505  
536   - // 可选:提交成功后重置表单
537   - // problemImgsList.value = []
538   - // completeImgsList.value = []
539   - // Object.assign(workOrderForm, {
540   - // roadId: 0, roadName: '', workLocation: '', orderName: '',
541   - // problemDesc: '', handleResultDesc: '', lat: 0, lon: 0
542   - // })
543   - // workOrderFormRef.value?.clearValidate()
544   -
545 506 } catch (error) {
546 507 console.error('提交工单失败:', error)
547 508 uni.showToast({ title: '提交失败,请重试', icon: 'none' })
... ... @@ -552,12 +513,10 @@ const submitWorkOrder = async () =&gt; {
552 513  
553 514 // ========== 页面挂载初始化 ==========
554 515 onMounted(() => {
555   - setTimeout(() => {
556   - if (workOrderFormRef.value && !workOrderFormRef.value.rules) {
557   - workOrderFormRef.value.setRules(workOrderFormRules)
558   - console.log('工单表单规则初始化完成')
559   - }
560   - }, 200)
  516 + if (workOrderFormRef.value && !workOrderFormRef.value.rules) {
  517 + workOrderFormRef.value.setRules(workOrderFormRules)
  518 + console.log('工单表单规则初始化完成')
  519 + }
561 520 })
562 521 </script>
563 522  
... ... @@ -566,10 +525,15 @@ onMounted(() =&gt; {
566 525 .u-page {
567 526 background-color: #f5f5f5;
568 527 min-height: 100vh;
569   - padding-bottom: 100rpx; // 给底部按钮留空间
  528 + padding-bottom: 100rpx;
  529 +}
  530 +
  531 +// 表单容器(新增:包裹整个表单)
  532 +.work-order-form-container {
  533 + width: 100%;
570 534 }
571 535  
572   -// 工单表单容器
  536 +// 工单表单内容容器
573 537 .work-order-form-content {
574 538 background: #fff;
575 539 padding: 20rpx;
... ... @@ -612,7 +576,7 @@ onMounted(() =&gt; {
612 576 }
613 577  
614 578 // 提交按钮区域
615   -.fixed-bottom-btn-wrap { // 修复:原样式类名是submit-btn-wrap,与模板不一致
  579 +.fixed-bottom-btn-wrap {
616 580 position: fixed;
617 581 bottom: 0;
618 582 left: 0;
... ... @@ -630,15 +594,15 @@ onMounted(() =&gt; {
630 594 }
631 595 }
632 596  
633   -// ========== 修复Label折行样式 ==========
  597 +// Label样式
634 598 :deep(.u-form-item__label) {
635   - white-space: nowrap; // 强制不换行
636   - text-overflow: ellipsis; // 超出省略
637   - overflow: hidden; // 隐藏超出部分
638   - line-height: 1.4; // 行高适配
  599 + white-space: nowrap;
  600 + text-overflow: ellipsis;
  601 + overflow: hidden;
  602 + line-height: 1.4;
639 603 }
640 604  
641   -// 表单基础样式适配
  605 +// 表单基础样式
642 606 :deep(.u-form-item) {
643 607 --u-form-item-label-font-size: 28rpx;
644 608 --u-form-item-content-font-size: 28rpx;
... ... @@ -658,7 +622,14 @@ onMounted(() =&gt; {
658 622 z-index: 9999 !important;
659 623 }
660 624  
661   -:deep(.u-tabs) {
662   - background: #fff;
  625 +// 错误提示样式
  626 +:deep(.u-form-item__error-message) {
  627 + font-size: 24rpx !important;
  628 + color: #f56c6c !important;
  629 + margin-top: 5rpx !important;
  630 +}
  631 +
  632 +:deep(.u-form-item--error .u-input__wrap) {
  633 + border-color: #f56c6c !important;
663 634 }
664 635 </style>
665 636 \ No newline at end of file
... ...