Commit d433e7f692e28b46cbbd3aa5bc02047f3aa07bc8

Authored by 刘淇
1 parent 7b1f488f

工单

pages-sub/daily/maintain-manage/add-record.vue
1 1 <template>
2   - <view class="u-page">
3   - <view class="work-order-form-content commonPageLRpadding">
  2 + <view class="page-container">
  3 + <view class="inspect-form-content commonPageLRpadding">
4 4 <up-form
5 5 label-position="left"
6   - :model="workOrderForm"
7   - ref="workOrderFormRef"
8   - labelWidth="160rpx"
  6 + :model="inspectForm"
  7 + ref="inspectFormRef"
  8 + labelWidth="140rpx"
9 9 >
10   - <!-- 1. 工单位置(地图选择) -->
  10 + <!-- 1. 巡查描述(文本域) -->
11 11 <up-form-item
12   - label="工单位置"
13   - prop="workLocation"
14   - border-bottom
15   - required
16   - @click="chooseWorkLocation(); hideKeyboard()"
  12 + prop="content"
  13 + class="form-item"
17 14 >
18   - <up-input
19   - v-model="workOrderForm.workLocation"
20   - border="none"
21   - readonly
22   - suffix-icon="map-fill"
23   - placeholder="点击选择工单位置"
24   - ></up-input>
  15 + <up-textarea
  16 + v-model="inspectForm.remark"
  17 + placeholder="请输入巡查描述"
  18 + maxlength="200"
  19 + count
  20 + ></up-textarea>
25 21 </up-form-item>
26 22  
27   - <!-- 2. 道路名称(下拉框) -->
  23 + <!-- 2. 上传图片 -->
28 24 <up-form-item
29   - label="道路名称"
30   - prop="roadName"
31   - border-bottom
  25 + label="上传图片"
  26 + prop="images"
32 27 required
33   - @click="workOrderForm.workLocation ? (showRoadName = true, hideKeyboard()) : uni.showToast({title: '请先选择工单位置', icon: 'none'})"
34   - >
35   - <up-input
36   - v-model="workOrderForm.roadName"
37   - disabled
38   - disabled-color="#ffffff"
39   - placeholder="请先选择工单位置"
40   - border="none"
41   - :placeholder-style="workOrderForm.workLocation ? '' : 'color:#999;'"
42   - ></up-input>
43   - <template #right>
44   - <up-icon name="arrow-right" size="16" :color="workOrderForm.workLocation ? '#333' : '#999'"></up-icon>
45   - </template>
46   - </up-form-item>
47   -
48   - <!-- 3. 工单名称(下拉框) -->
49   - <up-form-item
50   - label="工单名称"
51   - prop="orderName"
52 28 border-bottom
53   - required
54   - @click="showOrderName = true; hideKeyboard()"
55   - >
56   - <up-input
57   - v-model="workOrderForm.orderName"
58   - disabled
59   - disabled-color="#ffffff"
60   - placeholder="请选择工单名称"
61   - border="none"
62   - ></up-input>
63   - <template #right>
64   - <up-icon name="arrow-right" size="16"></up-icon>
65   - </template>
66   - </up-form-item>
67   -
68   - <!-- 4. 情况描述(文本域) -->
69   - <up-form-item
70   - label="情况描述"
71   - prop="problemDesc"
72   - required
  29 + class="form-item"
73 30 >
74   - <up-textarea
75   - placeholder="请输入情况描述(最多200字)"
76   - v-model="workOrderForm.problemDesc"
77   - count
78   - maxlength="200"
79   - rows="4"
80   - @blur="() => workOrderFormRef.value?.validateField('problemDesc')"
81   - ></up-textarea>
82   - </up-form-item>
83   -
84   - <!-- 问题照片(复用公共上传) -->
85   - <up-form-item label="问题照片" prop="problemImgs" required>
86 31 <up-upload
87   - :file-list="problemImgs.imgList"
88   - @after-read="problemImgs.uploadImgs"
89   - @delete="problemImgs.deleteImg"
  32 + :file-list="imagesList"
  33 + @after-read="(event) => uploadImgs(event)"
  34 + @delete="(event) => deleteImg(event)"
  35 + @on-exceed="handleExceed"
90 36 multiple
91   - :max-count="problemImgs.uploadConfig.maxCount"
92   - :upload-text="problemImgs.uploadConfig.uploadText"
93   - :sizeType="problemImgs.uploadConfig.sizeType"
  37 + :max-count="3"
  38 + upload-text="选择图片"
  39 + del-color="#ff4d4f"
  40 + class="upload-wrap"
94 41 ></up-upload>
95 42 </up-form-item>
96 43  
97   - <!-- 完成照片(复用公共上传) -->
98   - <up-form-item label="完成照片" prop="completeImgs" required class="mt-20">
99   - <up-upload
100   - :file-list="completeImgs.imgList"
101   - @after-read="completeImgs.uploadImgs"
102   - @delete="completeImgs.deleteImg"
103   - multiple
104   - :max-count="completeImgs.uploadConfig.maxCount"
105   - :upload-text="completeImgs.uploadConfig.uploadText"
106   - :sizeType="completeImgs.uploadConfig.sizeType"
107   - ></up-upload>
108   - </up-form-item>
  44 + <!-- 3. 完成进度(滑块) -->
  45 + <up-form-item
  46 + label="完成进度"
  47 + prop="progress"
109 48  
110   - <!-- 处理结果(不必填) -->
111   - <up-form-item label="处理结果" prop="handleResult" class="mt-20">
112   - <up-textarea
113   - placeholder="请输入处理结果描述(最多200字,选填)"
114   - v-model="workOrderForm.handleResult"
115   - count
116   - maxlength="200"
117   - rows="4"
118   - @blur="() => workOrderFormRef.value?.validateField('handleResult')"
119   - ></up-textarea>
  49 + class="form-item"
  50 + >
  51 + <view class="progress-wrap">
  52 + <up-slider
  53 + v-model="inspectForm.progress"
  54 + :min="initProgress"
  55 + :max="100"
  56 + active-color="#1989fa"
  57 + inactive-color="#e5e5e5"
  58 + block-size="24"
  59 + :showValue="true"
  60 + class="progress-slider"
  61 + @changing="handleProgressChange"
  62 + ></up-slider>
  63 + </view>
120 64 </up-form-item>
121 65 </up-form>
122 66 </view>
... ... @@ -125,271 +69,270 @@
125 69 <view class="fixed-bottom-btn-wrap">
126 70 <up-button
127 71 type="primary"
128   - text="提交工单"
129   - @click="submitWorkOrder"
  72 + text="提交"
  73 + @click="submitInspect"
  74 + :style="{ width: '100%', height: '88rpx', fontSize: '32rpx', borderRadius: 0 }"
130 75 ></up-button>
131 76 </view>
132   -
133   - <!-- 道路名称下拉弹窗 -->
134   - <up-action-sheet
135   - :show="showRoadName"
136   - :actions="roadNameList"
137   - title="请选择道路名称"
138   - @close="showRoadName = false"
139   - @select="handleRoadNameSelect"
140   - ></up-action-sheet>
141   -
142   - <!-- 工单名称下拉弹窗 -->
143   - <up-action-sheet
144   - :show="showOrderName"
145   - :actions="orderNameList"
146   - title="请选择工单名称"
147   - @close="showOrderName = false"
148   - @select="handleOrderNameSelect"
149   - ></up-action-sheet>
150 77 </view>
151 78 </template>
152 79  
153 80 <script setup lang="ts">
154   -import { ref, reactive } from 'vue'
155   -import { onReady, onShow } from '@dcloudio/uni-app' // 从uni-app导入小程序生命周期
  81 +import { ref } from 'vue'
156 82 import type { UniFormRef } from '@/uni_modules/uview-plus/types'
157   -import { useUploadImgs } from '@/common/utils/useUploadImgs' // 引入公共上传逻辑
158   -import { getRoadListByLatLng } from '@/api/common'
159   -import { createQuick } from '@/api/quick-order/quick-order'
160   -
161   -// 表单Ref
162   -const workOrderFormRef = ref<UniFormRef>(null)
163   -
164   -// ========== 复用公共上传逻辑 ==========
165   -// 1. 问题照片配置
166   -const problemImgs = useUploadImgs({
167   - maxCount: 3, // 可自定义数量
168   - uploadText: '选择问题照片',
169   - sizeType: ['compressed'],
170   - formRef: workOrderFormRef,
171   - fieldName: 'problemImgs'
172   -})
173   -
174   -// 2. 完成照片配置
175   -const completeImgs = useUploadImgs({
176   - maxCount: 3,
177   - uploadText: '选择完成照片',
178   - sizeType: ['compressed'],
179   - formRef: workOrderFormRef,
180   - fieldName: 'completeImgs'
181   -})
182   -
183   -// ========== 页面数据 ==========
184   -const showRoadName = ref(false)
185   -const showOrderName = ref(false)
186   -const roadNameList = ref<any[]>([])
187   -const orderNameList = ref<any[]>([])
188   -
189   -// 工单表单数据
190   -const workOrderForm = reactive({
191   - roadId: 0,
192   - roadName: '',
193   - workLocation: '',
194   - orderName: '',
195   - problemDesc: '',
196   - handleResult: '',
197   - lat: 0,
198   - lon: 0
199   -})
200   -
201   -// 表单校验规则
202   -const workOrderFormRules = reactive({
203   - workLocation: [
204   - { type: 'string', required: true, message: '请选择工单位置', trigger: ['change', 'blur'] }
205   - ],
206   - roadName: [
207   - { type: 'string', required: true, message: '请选择道路名称', trigger: ['change', 'blur'] }
208   - ],
209   - orderName: [
210   - { type: 'string', required: true, message: '请选择工单名称', trigger: ['change', 'blur'] }
211   - ],
212   - problemDesc: [
213   - { type: 'string', required: true, message: '请输入情况描述', trigger: ['change', 'blur'] },
214   - { type: 'string', min: 3, max: 200, message: '情况描述需3-200字', trigger: ['change', 'blur'] }
215   - ],
216   - problemImgs: [problemImgs.imgValidateRule], // 复用校验规则
217   - completeImgs: [completeImgs.imgValidateRule] // 复用校验规则
218   -})
219   -
220   -// ========== 生命周期 ==========
221   -onReady(() => {
222   - // 设置表单校验规则
223   - workOrderFormRef.value?.setRules(workOrderFormRules)
224   - console.log('工单表单规则初始化完成')
225   -})
226   -
227   -onShow(() => {
228   - console.log(uni.$dict.getDictLabel('ai_image_status', 20))
229   - console.log(uni.$dict.getDictSimpleList('work_name'))
230   - orderNameList.value = uni.$dict.transformLabelValueToNameValue(uni.$dict.getDictSimpleList('work_name'))
231   - console.log(orderNameList.value)
232   -})
233   -
234   -// ========== 方法 ==========
235   -/**
236   - * 返回上一页
237   - */
238   -const navigateBack = () => {
239   - uni.navigateBack()
240   -}
241   -
242   -/**
243   - * 隐藏键盘
244   - */
245   -const hideKeyboard = () => {
246   - uni.hideKeyboard()
247   -}
  83 +const inspectFormRef = ref<UniFormRef>(null)
  84 +</script>
248 85  
249   -/**
250   - * 选择工单位置
251   - */
252   -const chooseWorkLocation = async () => {
253   - uni.chooseLocation({
254   - success: async (res) => {
255   - workOrderForm.roadName = ''
256   - workOrderForm.roadId = 0
257   - roadNameList.value = []
  86 +<script lang="ts">
  87 +import { uploadImages } from '@/common/utils/upload'
  88 +import { maintainCreate } from "@/api/maintain-manage/maintain-manage"
  89 +
  90 +export default {
  91 + data() {
  92 + return {
  93 + imagesList: [],
  94 + initProgress: 0,
  95 + inspectForm: {
  96 + remark: '',
  97 + progress: 0
  98 + },
  99 + paramsOptins: {},
  100 + inspectFormRules: {
  101 + images: [
  102 + {
  103 + required: true,
  104 + message: '请上传图片',
  105 + trigger: 'change',
  106 + validator: (rule, value, callback) => {
  107 + const hasSuccessImg = this.imagesList.some(item => item.status === 'success')
  108 + const imgCount = this.imagesList.filter(item => item.status === 'success').length
  109 + if (!hasSuccessImg || imgCount < 1) {
  110 + callback(new Error('最少需要上传1张图片'))
  111 + } else if (imgCount > 3) {
  112 + callback(new Error('最多只能上传3张图片'))
  113 + } else {
  114 + callback()
  115 + }
  116 + }
  117 + }
  118 + ],
  119 + // progress: [
  120 + // {
  121 + //
  122 + // required: true,
  123 + // message: '请设置完成进度',
  124 + // trigger: ['change'],
  125 + // validator: (rule, value, callback) => {
  126 + // // 第一步:校验是否为空/0
  127 + // if (!value && value !== 0) {
  128 + // callback(new Error('请设置完成进度'))
  129 + // }
  130 + // // 第二步:校验是否大于初始进度
  131 + // else if (value <= this.initProgress) {
  132 + // callback(new Error(`完成进度必须大于${this.initProgress}%`))
  133 + // }
  134 + // // 校验通过
  135 + // else {
  136 + // callback()
  137 + // }
  138 + // }
  139 + // }
  140 + // ]
  141 + }
  142 + }
  143 + },
  144 + onLoad(option) {
  145 + console.log('页面参数:', option)
  146 + this.paramsOptins = option
  147 + // 初始化初始进度
  148 + this.initProgress = option.finishPercent ? Number(option.finishPercent)+1 : 0
  149 + // 关键修复:初始进度值设为 初始进度+1,避免刚进入就触发校验失败
  150 + this.inspectForm.progress = this.initProgress
  151 + console.log('初始进度值:', this.initProgress)
  152 + },
  153 + onReady() {
  154 + this.$refs.inspectFormRef.setRules(this.inspectFormRules)
  155 + console.log('巡查表单规则初始化完成')
  156 + },
  157 + methods: {
  158 + /**
  159 + * 进度滑块变化处理 - 核心优化:实时触发校验 + 状态更新
  160 + */
  161 + handleProgressChange(value) {
  162 + // // 1. 强制修正非法值(兜底)
  163 + // console.log(value)
  164 + // if (value <= this.initProgress) {
  165 + // console.log('123')
  166 + // this.inspectForm.progress = this.initProgress + 1
  167 + // uni.showToast({
  168 + // title: `进度不能低于${this.initProgress}%`,
  169 + // icon: 'none',
  170 + // duration: 1500
  171 + // })
  172 + // }
  173 + //
  174 + // // 2. 关键:手动触发progress字段的校验,实时更新提示状态
  175 + // this.$nextTick(async () => {
  176 + // try {
  177 + // // 触发单个字段校验
  178 + // await this.$refs.inspectFormRef.validateField('progress')
  179 + // } catch (err) {
  180 + // // 校验失败时uView会自动显示提示,此处无需额外处理
  181 + // console.log('进度校验失败:', err)
  182 + // }
  183 + // })
  184 + },
258 185  
259   - workOrderForm.workLocation = res.name
260   - workOrderForm.lat = res.latitude
261   - workOrderForm.lon = res.longitude
  186 + /**
  187 + * 删除图片
  188 + */
  189 + deleteImg(event) {
  190 + console.log('删除图片事件:', event)
  191 + this.imagesList.splice(event.index, 1)
  192 + this.$refs.inspectFormRef.validateField('images')
  193 + uni.showToast({ title: '图片删除成功', icon: 'success' })
  194 + },
262 195  
263   - workOrderFormRef.value?.validateField('workLocation')
264   - workOrderFormRef.value?.validateField('roadName')
  196 + /**
  197 + * 上传图片
  198 + */
  199 + async uploadImgs(event) {
  200 + console.log('上传图片事件:', event)
  201 + const fileList = Array.isArray(event.file) ? event.file : [event.file]
  202 + const targetImgList = this.imagesList
  203 + const filePaths = fileList.map(item => item.url)
  204 + const tempItems = fileList.map(item => ({
  205 + ...item,
  206 + status: 'uploading',
  207 + message: '上传中'
  208 + }))
  209 + const startIndex = targetImgList.length
  210 + targetImgList.push(...tempItems)
265 211  
266 212 try {
267   - uni.showLoading({ title: '获取道路名称中...' })
268   - const roadRes = await getRoadListByLatLng({
269   - companyCode: 'sls',
270   - latitude: res.latitude,
271   - longitude: res.longitude
  213 + const uploadResultUrls = await uploadImages({
  214 + filePaths: filePaths,
  215 + ignoreError: true
  216 + })
  217 + console.log('上传成功的URL列表:', uploadResultUrls)
  218 +
  219 + uploadResultUrls.forEach((url, index) => {
  220 + if (targetImgList[startIndex + index]) {
  221 + targetImgList.splice(startIndex + index, 1, {
  222 + ...fileList[index],
  223 + status: 'success',
  224 + message: '',
  225 + url: url
  226 + })
  227 + }
272 228 })
273   - uni.hideLoading()
274 229  
275   - if (Array.isArray(roadRes)) {
276   - roadNameList.value = roadRes.map((item) => ({
277   - name: item.roadName || '',
278   - code: item.roadCode || '',
279   - id: item.roadId || 0
280   - }))
  230 + if (uploadResultUrls.length < fileList.length) {
  231 + const failCount = fileList.length - uploadResultUrls.length
  232 + for (let i = uploadResultUrls.length; i < fileList.length; i++) {
  233 + if (targetImgList[startIndex + i]) {
  234 + targetImgList.splice(startIndex + i, 1, {
  235 + ...fileList[i],
  236 + status: 'failed',
  237 + message: '上传失败'
  238 + })
  239 + }
  240 + }
  241 + uni.showToast({ title: `成功上传${uploadResultUrls.length}张,失败${failCount}张`, icon: 'none' })
281 242 } else {
282   - roadNameList.value = [{ name: '未查询到道路名称', code: '', id: 0 }]
283   - uni.showToast({ title: '未查询到该位置的道路信息', icon: 'none' })
  243 + uni.showToast({ title: `成功上传${fileList.length}张图片`, icon: 'success' })
284 244 }
  245 +
  246 + this.$refs.inspectFormRef.validateField('images')
285 247 } catch (err) {
286   - uni.hideLoading()
287   - console.error('获取道路名称失败:', err)
288   - uni.showToast({ title: '获取道路名称失败,请重试', icon: 'none' })
289   - roadNameList.value = [{ name: '获取失败,请重新选择位置', code: '', id: 0 }]
  248 + console.error('图片上传失败:', err)
  249 + for (let i = 0; i < fileList.length; i++) {
  250 + if (targetImgList[startIndex + i]) {
  251 + targetImgList.splice(startIndex + i, 1, {
  252 + ...fileList[i],
  253 + status: 'failed',
  254 + message: '上传失败'
  255 + })
  256 + }
  257 + }
  258 + uni.showToast({ title: '图片上传失败,请重试', icon: 'none' })
  259 + this.$refs.inspectFormRef.validateField('images')
290 260 }
291 261 },
292   - fail: (err) => {
293   - console.error('选择位置失败:', err)
294   - uni.showToast({ title: '选择位置失败:' + err.errMsg, icon: 'none' })
295   - }
296   - })
297   -}
298 262  
299   -/**
300   - * 选择道路名称
301   - */
302   -const handleRoadNameSelect = (e: any) => {
303   - console.log('选择道路名称:', e)
304   - workOrderForm.roadName = e.name
305   - workOrderForm.roadId = e.code
306   - showRoadName.value = false
307   - workOrderFormRef.value?.validateField('roadName')
308   -}
309   -
310   -/**
311   - * 选择工单名称
312   - */
313   -const handleOrderNameSelect = (e: any) => {
314   - console.log(e)
315   - workOrderForm.orderName = e.name
316   - showOrderName.value = false
317   - workOrderFormRef.value?.validateField('orderName')
318   -}
319   -
320   -/**
321   - * 提交工单
322   - */
323   -const submitWorkOrder = async () => {
324   - try {
325   - // 表单校验
326   - await workOrderFormRef.value?.validate()
  263 + /**
  264 + * 处理图片超出数量限制
  265 + */
  266 + handleExceed() {
  267 + uni.showToast({ title: '最多只能上传3张图片', icon: 'none' })
  268 + },
327 269  
328   - const submitData = {
329   - roadId: workOrderForm.roadId,
330   - roadName: workOrderForm.roadName,
331   - imgs: problemImgs.getSuccessImgUrls(), // 复用获取成功URL方法
332   - longRangeImgList: completeImgs.getSuccessImgUrls(), // 复用获取成功URL方法
333   - remark: workOrderForm.problemDesc,
334   - handleResult: workOrderForm.handleResult,
335   - latLonType: 2,
336   - lat: workOrderForm.lat,
337   - lon: workOrderForm.lon,
338   - lonLatAddress: workOrderForm.workLocation,
339   - pressingType: 2,
340   - orderName: workOrderForm.orderName,
341   - sourceId: 1,
342   - sourceName: '园林',
343   - thirdWorkNo: ''
344   - }
  270 + /**
  271 + * 提取图片URL数组
  272 + */
  273 + getImgUrlList(imgList) {
  274 + return imgList.filter(item => item.status === 'success').map(item => item.url)
  275 + },
345 276  
346   - // 显示加载中
347   - uni.showLoading({ title: '提交中...' })
  277 + /**
  278 + * 提交巡查表单
  279 + */
  280 + async submitInspect() {
  281 + console.log('当前完成进度:', this.inspectForm.progress)
  282 + try {
  283 + await this.$refs.inspectFormRef.validate()
  284 + console.log('图片列表:', this.imagesList)
  285 +
  286 + const submitData = {
  287 + totalFinishPercent: this.inspectForm.progress,
  288 + "planNo": this.paramsOptins.planNo,
  289 + "imgHost": "1",
  290 + "beginImg": this.getImgUrlList(this.imagesList),
  291 + "commonUserList": [],
  292 + "remark": this.inspectForm.remark
  293 + }
348 294  
349   - // 调用提交接口
350   - const res = await createQuick(submitData)
  295 + uni.showLoading({ title: '提交中...' })
  296 + await maintainCreate(submitData)
  297 + uni.hideLoading()
351 298  
352   - uni.hideLoading()
353   - uni.showToast({
354   - title: '工单提交成功',
355   - icon: 'success',
356   - duration: 1000
357   - })
  299 + uni.showToast({
  300 + title: '提交成功',
  301 + icon: 'success',
  302 + duration: 1000
  303 + })
358 304  
359   - // 延迟跳转
360   - setTimeout(() => {
361   - uni.redirectTo({
362   - url: '/pages-sub/daily/quick-order/index'
363   - })
364   - }, 1000)
365   - } catch (error) {
366   - uni.hideLoading()
  305 + setTimeout(() => {
  306 + uni.redirectTo({
  307 + url: '/pages-sub/daily/maintain-manage/index'
  308 + })
  309 + }, 1000)
367 310  
368   - if (!Array.isArray(error)) {
369   - console.error('工单提交失败:', error)
370   - uni.showToast({
371   - title: '提交失败,请重试',
372   - icon: 'none',
373   - duration: 2000
374   - })
  311 + } catch (error) {
  312 + uni.hideLoading()
  313 + if (!Array.isArray(error)) {
  314 + console.error('巡查表单提交失败:', error)
  315 + uni.showToast({
  316 + title: '提交失败,请重试',
  317 + icon: 'none',
  318 + duration: 2000
  319 + })
  320 + }
  321 + }
375 322 }
376 323 }
377 324 }
378 325 </script>
379 326  
380 327 <style lang="scss" scoped>
381   -// 全局页面样式
382   -.u-page {
383   - min-height: 100vh;
384   -}
385   -
386   -// 工单表单内容容器
387   -.work-order-form-content {
  328 +.inspect-form-content {
388 329 background: #fff;
  330 + padding: 20rpx;
  331 + margin-bottom: 20rpx;
389 332 }
390 333  
391   -.fixed-bottom-btn-wrap {
392   - padding: 20rpx;
393   - background: #fff;
  334 +.progress-wrap {
  335 + width: 83%;
394 336 }
395   -</style>
396 337 \ No newline at end of file
  338 +</style>
  339 +
... ...
pages-sub/problem/work-order-manage/add-order.vue 0 → 100644
  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 +
  14 + @change="handleTabChange"
  15 + />
  16 +
  17 + <!-- 第二行:下拉框 + 搜索框 -->
  18 + <view class="search-header">
  19 + <!-- 左侧下拉框 -->
  20 + <view class="select-wrap">
  21 + <up-select
  22 + v-model:current="selectedSortValue"
  23 + :options="sortOptions"
  24 + :showOptionsLabel="true"
  25 + @select="handleSortChange"
  26 + border="surround"
  27 + :style="{ flex: 1 }"
  28 + />
  29 + </view>
  30 +
  31 + <!-- 右侧搜索框 -->
  32 + <view class="search-input-wrap">
  33 + <up-search
  34 + v-model="searchValue"
  35 + placeholder="请输入关键字"
  36 + @search="handleSearch"
  37 + bg-color="#f5f5f5"
  38 + :clearabled="false"
  39 + :show-action="true"
  40 + actionText="搜索"
  41 + :animation="true"
  42 + @custom="handleSearch"
  43 + />
  44 + </view>
  45 + </view>
  46 + </view>
  47 + </up-sticky>
  48 +
  49 + <!-- 列表容器 -->
  50 + <z-paging
  51 + ref="paging"
  52 + v-model="orderList"
  53 + @query="queryList"
  54 + :auto-show-system-loading="true"
  55 +
  56 + >
  57 + <template #empty>
  58 + <view class="empty-view" style="padding: 100rpx 0; text-align: center; color: #999;">
  59 + 暂无工单数据
  60 + </view>
  61 + </template>
  62 +
  63 + <view class="common-card-list" slot="top">
  64 + <!-- 待办工单卡片 -->
  65 + <up-card
  66 + v-if="activeTab === 0"
  67 + :border="false"
  68 + :foot-border-top="false"
  69 + v-for="(item, index) in orderList"
  70 + :key="`todo_${item.orderNo}_${index}`"
  71 + :show-head="false"
  72 + class="order-card"
  73 + >
  74 + <template #body>
  75 + <view class="card-body">
  76 + <view class="u-body-item u-flex">
  77 + <view class="u-body-item-title">工单编号:</view>
  78 + <view class="u-line-1 u-body-value">{{ item.orderNo || '-' }}</view>
  79 + </view>
  80 + <view class="u-body-item u-flex">
  81 + <view class="u-body-item-title">工单位置:</view>
  82 + <view class="u-line-1 u-body-value">{{ item.roadName || '-' }}</view>
  83 + </view>
  84 + <view class="u-body-item u-flex">
  85 + <view class="u-body-item-title">工单名称:</view>
  86 + <view class="u-line-1 u-body-value">{{ item.orderName || '未填写' }}</view>
  87 + </view>
  88 + <view class="u-body-item u-flex">
  89 + <view class="u-body-item-title">情况描述:</view>
  90 + <view class="u-line-1 u-body-value">{{ item.remark || '无' }}</view>
  91 + </view>
  92 + <view class="u-body-item u-flex">
  93 + <view class="u-body-item-title">紧急程度:</view>
  94 + <view class="u-line-1 u-body-value">
  95 + <up-tag :type="getUrgencyType(item.urgencyLevel)">{{ item.urgencyLevel || '普通' }}</up-tag>
  96 + </view>
  97 + </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.createTime, 'yyyy-mm-dd hh:MM:ss') }}</view>
  101 + </view>
  102 + <!-- 操作按钮行 -->
  103 + <view class="u-body-item u-flex common-justify-between common-item-center mt-20">
  104 + <up-button type="warning" size="mini" @click="handleReject(item)">回退</up-button>
  105 + <up-button type="primary" size="mini" @click="handleProcess(item)">处理</up-button>
  106 + <up-button type="info" size="mini" @click="handleDetail(item)">详情</up-button>
  107 + </view>
  108 + </view>
  109 + </template>
  110 + </up-card>
  111 +
  112 + <!-- 已办工单卡片 -->
  113 + <up-card
  114 + v-if="activeTab === 1"
  115 + :border="false"
  116 + :foot-border-top="false"
  117 + v-for="(item, index) in orderList"
  118 + :key="`done_${item.orderNo}_${index}`"
  119 + :show-head="false"
  120 + class="order-card"
  121 + >
  122 + <template #body>
  123 + <view class="card-body">
  124 + <view class="u-body-item u-flex">
  125 + <view class="u-body-item-title">工单编号:</view>
  126 + <view class="u-line-1 u-body-value">{{ item.orderNo || '-' }}</view>
  127 + </view>
  128 + <view class="u-body-item u-flex">
  129 + <view class="u-body-item-title">工单位置:</view>
  130 + <view class="u-line-1 u-body-value">{{ item.roadName || '-' }}</view>
  131 + </view>
  132 + <view class="u-body-item u-flex">
  133 + <view class="u-body-item-title">工单名称:</view>
  134 + <view class="u-line-1 u-body-value">{{ item.orderName || '未填写' }}</view>
  135 + </view>
  136 + <view class="u-body-item u-flex">
  137 + <view class="u-body-item-title">情况描述:</view>
  138 + <view class="u-line-1 u-body-value">{{ item.remark || '无' }}</view>
  139 + </view>
  140 + <view class="u-body-item u-flex common-justify-between common-item-center">
  141 + <view class="u-flex">
  142 + <view class="u-body-item-title">紧急程度:</view>
  143 + <view class="u-line-1 u-body-value">
  144 + <up-tag :type="getUrgencyType(item.urgencyLevel)">{{ item.urgencyLevel || '普通' }}</up-tag>
  145 + </view>
  146 + </view>
  147 + <up-button type="info" size="mini" @click="handleDetail(item)">工单详情</up-button>
  148 + </view>
  149 + <view class="u-body-item u-flex">
  150 + <view class="u-body-item-title">提交时间:</view>
  151 + <view class="u-line-1 u-body-value">{{ timeFormat(item.createTime, 'yyyy-mm-dd hh:MM:ss') }}</view>
  152 + </view>
  153 + </view>
  154 + </template>
  155 + </up-card>
  156 + </view>
  157 + </z-paging>
  158 +
  159 + <!-- 底部新增工单按钮(仅巡查员显示) -->
  160 + <view v-if="isInspector" class="fixed-bottom-btn-wrap">
  161 + <up-button type="primary" size="large" @click="handleAddOrder">
  162 + 新增工单
  163 + </up-button>
  164 + </view>
  165 +
  166 + <!-- 回退原因弹窗 -->
  167 + <up-popup v-model="rejectPopupShow" mode="center" :close-on-click-overlay="false">
  168 + <view class="reject-popup">
  169 + <view class="popup-title">回退原因</view>
  170 + <up-textarea
  171 + v-model="rejectReason"
  172 + placeholder="请输入回退原因(必填)"
  173 + :required="true"
  174 + maxlength="-1"
  175 + rows="4"
  176 + class="mt-20"
  177 + />
  178 + <view class="upload-wrap mt-20">
  179 + <view class="upload-title">上传图片(选填)</view>
  180 + <up-upload
  181 + :action="uploadUrl"
  182 + :file-list="rejectFileList"
  183 + @after-read="handleAfterRead"
  184 + @delete="handleDeleteFile"
  185 + multiple
  186 + max-count="3"
  187 + />
  188 + </view>
  189 + <view class="popup-btn-wrap mt-40">
  190 + <up-button type="default" size="medium" @click="rejectPopupShow = false" class="mr-20">取消</up-button>
  191 + <up-button type="primary" size="medium" @click="confirmReject">确认提交</up-button>
  192 + </view>
  193 + </view>
  194 + </up-popup>
  195 + </view>
  196 +</template>
  197 +
  198 +<script setup>
  199 +import { ref, computed, onMounted } from 'vue';
  200 +import { timeFormat } from '@/uni_modules/uview-plus';
  201 +// 假设从用户store获取角色信息
  202 +import { useUserStore } from '@/pinia/user';
  203 +
  204 +// ========== 状态管理 ==========
  205 +const userStore = useUserStore();
  206 +// 标签页切换
  207 +const activeTab = ref(0); // 0-待办 1-已办
  208 +const tabList = ref([
  209 + { name: '待办' },
  210 + { name: '已办' }
  211 +]);
  212 +// 排序下拉框
  213 +const selectedSortValue = ref(1);
  214 +const sortOptions = ref([
  215 + { name: '位置', id: 1 },
  216 + { name: '名称', id: 2 },
  217 + { name: '描述', id: 3 },
  218 + { name: '编号', id: 4 },
  219 +]);
  220 +// 搜索
  221 +const searchValue = ref('');
  222 +// 分页
  223 +const paging = ref(null);
  224 +const orderList = ref([]);
  225 +// 角色控制(巡查员显示新增按钮)
  226 +const isInspector = computed(() => {
  227 + // 假设用户角色字段为role,巡查员标识为inspector
  228 + return userStore.userInfo.roles === 'yl_inspector';
  229 +});
  230 +// 回退弹窗相关
  231 +const rejectPopupShow = ref(false);
  232 +const rejectReason = ref('');
  233 +const rejectFileList = ref([]);
  234 +const currentRejectItem = ref(null);
  235 +// 上传地址(根据实际接口配置)
  236 +const uploadUrl = ref('https://xxx.com/upload');
  237 +
  238 +// ========== 接口请求 ==========
  239 +// 待办工单接口
  240 +const getTodoOrderList = async (params) => {
  241 + // 替换为实际待办工单接口
  242 + const res = await uni.request({
  243 + url: '/api/order/todo',
  244 + method: 'POST',
  245 + data: params
  246 + });
  247 + return res.data;
  248 +};
  249 +
  250 +// 已办工单接口
  251 +const getDoneOrderList = async (params) => {
  252 + // 替换为实际已办工单接口
  253 + const res = await uni.request({
  254 + url: '/api/order/done',
  255 + method: 'POST',
  256 + data: params
  257 + });
  258 + return res.data;
  259 +};
  260 +
  261 +// 分页查询列表
  262 +const queryList = async (pageNo, pageSize) => {
  263 + try {
  264 + const apiParams = {
  265 + searchContent: searchValue.value.trim() || '',
  266 + pageNo,
  267 + pageSize,
  268 + type: selectedSortValue.value // 1-位置 2-工单名称 3-情况描述 4-工单编号
  269 + };
  270 +
  271 + let res;
  272 + if (activeTab.value === 0) {
  273 + // 待办工单
  274 + res = await getTodoOrderList(apiParams);
  275 + } else {
  276 + // 已办工单
  277 + res = await getDoneOrderList(apiParams);
  278 + }
  279 +
  280 + // 适配z-paging分页
  281 + paging.value.complete(res.list, res.total);
  282 + } catch (error) {
  283 + console.error('加载工单失败:', error);
  284 + paging.value?.complete(false);
  285 + uni.showToast({ title: '加载失败,请重试', icon: 'none' });
  286 + }
  287 +};
  288 +
  289 +// ========== 事件处理 ==========
  290 +// 标签页切换
  291 +const handleTabChange = (index) => {
  292 + activeTab.value = index;
  293 + paging.value?.reload(); // 切换标签页刷新列表
  294 +};
  295 +
  296 +// 排序变更
  297 +const handleSortChange = (val) => {
  298 + selectedSortValue.value = val.id;
  299 + searchValue.value = '';
  300 + paging.value?.reload(); // 排序变更刷新列表
  301 +};
  302 +
  303 +// 搜索
  304 +const handleSearch = (val) => {
  305 + searchValue.value = val;
  306 + paging.value?.reload(); // 搜索刷新列表
  307 +};
  308 +
  309 +// 工单详情
  310 +const handleDetail = (item) => {
  311 + uni.navigateTo({
  312 + url: `/pages-sub/daily/quick-order/order-detail?id=${item.id}`
  313 + });
  314 +};
  315 +
  316 +// 待办-处理工单
  317 +const handleProcess = async (item) => {
  318 + try {
  319 + // 调用处理工单接口,获取跳转标识
  320 + const res = await uni.request({
  321 + url: '/api/order/process',
  322 + method: 'POST',
  323 + data: { orderId: item.id }
  324 + });
  325 +
  326 + const { jumpType, jumpUrl } = res.data;
  327 + // 根据返回标识跳转不同页面
  328 + if (jumpType === 1) {
  329 + uni.navigateTo({ url: jumpUrl });
  330 + } else if (jumpType === 2) {
  331 + uni.redirectTo({ url: jumpUrl });
  332 + } else if (jumpType === 3) {
  333 + uni.switchTab({ url: jumpUrl });
  334 + }
  335 + } catch (error) {
  336 + console.error('处理工单失败:', error);
  337 + uni.showToast({ title: '处理失败,请重试', icon: 'none' });
  338 + }
  339 +};
  340 +
  341 +// 待办-回退工单
  342 +const handleReject = (item) => {
  343 + currentRejectItem.value = item;
  344 + rejectReason.value = '';
  345 + rejectFileList.value = [];
  346 + rejectPopupShow.value = true;
  347 +};
  348 +
  349 +// 确认回退工单
  350 +const confirmReject = async () => {
  351 + if (!rejectReason.value.trim()) {
  352 + uni.showToast({ title: '请填写回退原因', icon: 'none' });
  353 + return;
  354 + }
  355 +
  356 + try {
  357 + // 调用回退工单接口
  358 + await uni.request({
  359 + url: '/api/order/reject',
  360 + method: 'POST',
  361 + data: {
  362 + orderId: currentRejectItem.value.id,
  363 + reason: rejectReason.value,
  364 + fileUrls: rejectFileList.value.map(file => file.url)
  365 + }
  366 + });
  367 +
  368 + uni.showToast({ title: '回退成功', icon: 'success' });
  369 + rejectPopupShow.value = false;
  370 + paging.value?.reload(); // 刷新列表
  371 + } catch (error) {
  372 + console.error('回退工单失败:', error);
  373 + uni.showToast({ title: '回退失败,请重试', icon: 'none' });
  374 + }
  375 +};
  376 +
  377 +// 新增工单
  378 +const handleAddOrder = () => {
  379 + uni.navigateTo({
  380 + url: '/pages-sub/problem/work-order-manage/add-order'
  381 + });
  382 +};
  383 +
  384 +// 紧急程度标签类型转换
  385 +const getUrgencyType = (level) => {
  386 + switch (level) {
  387 + case '紧急':
  388 + return 'danger';
  389 + case '重要':
  390 + return 'warning';
  391 + case '普通':
  392 + return 'info';
  393 + default:
  394 + return 'default';
  395 + }
  396 +};
  397 +
  398 +// 上传图片-读取后
  399 +const handleAfterRead = (file) => {
  400 + rejectFileList.value.push(file);
  401 +};
  402 +
  403 +// 上传图片-删除
  404 +const handleDeleteFile = (index) => {
  405 + rejectFileList.value.splice(index, 1);
  406 +};
  407 +
  408 +// 页面初始化
  409 +onMounted(() => {
  410 + // 初始化加载列表
  411 + paging.value?.reload();
  412 +});
  413 +</script>
  414 +
  415 +<style scoped lang="scss">
  416 +.page-container {
  417 + min-height: 100vh;
  418 + background-color: #f8f8f8;
  419 +}
  420 +
  421 +// 顶部固定区域
  422 +.header-wrap {
  423 + background-color: #fff;
  424 + box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
  425 +}
  426 +
  427 +// 搜索栏样式
  428 +.search-header {
  429 + display: flex;
  430 + align-items: center;
  431 + padding: 20rpx;
  432 + box-sizing: border-box;
  433 +
  434 + .select-wrap {
  435 + width: 120rpx;
  436 + margin-right: 20rpx;
  437 +
  438 + :deep(.u-select) {
  439 + width: 100%;
  440 + font-size: 28rpx;
  441 + }
  442 +
  443 + :deep(.u-input__placeholder) {
  444 + font-size: 28rpx;
  445 + }
  446 + }
  447 +
  448 + .search-input-wrap {
  449 + flex: 1;
  450 + }
  451 +}
  452 +
  453 +// 工单卡片样式
  454 +.common-card-list {
  455 + padding: 20rpx;
  456 +}
  457 +
  458 +.order-card {
  459 + margin-bottom: 20rpx;
  460 + background-color: #fff;
  461 + border-radius: 12rpx;
  462 + overflow: hidden;
  463 +}
  464 +
  465 +.card-body {
  466 + padding: 20rpx;
  467 +
  468 + .u-body-item {
  469 + margin-bottom: 16rpx;
  470 + font-size: 28rpx;
  471 +
  472 + &:last-child {
  473 + margin-bottom: 0;
  474 + }
  475 +
  476 + .u-body-item-title {
  477 + color: #666;
  478 + min-width: 120rpx;
  479 + }
  480 +
  481 + .u-body-value {
  482 + color: #333;
  483 + flex: 1;
  484 + }
  485 + }
  486 +
  487 + .mt-20 {
  488 + margin-top: 20rpx;
  489 + }
  490 +}
  491 +
  492 +// 公共flex样式
  493 +.u-flex {
  494 + display: flex;
  495 + align-items: center;
  496 +}
  497 +
  498 +.common-justify-between {
  499 + justify-content: space-between;
  500 +}
  501 +
  502 +.common-item-center {
  503 + align-items: center;
  504 +}
  505 +
  506 +// 底部按钮
  507 +.fixed-bottom-btn-wrap {
  508 + position: fixed;
  509 + bottom: 0;
  510 + left: 0;
  511 + right: 0;
  512 + padding: 20rpx;
  513 + background-color: #fff;
  514 + box-shadow: 0 -2rpx 10rpx rgba(0, 0, 0, 0.05);
  515 +
  516 + :deep(.u-button) {
  517 + width: 100%;
  518 + height: 88rpx;
  519 + font-size: 32rpx;
  520 + }
  521 +}
  522 +
  523 +// 回退弹窗样式
  524 +.reject-popup {
  525 + width: 680rpx;
  526 + padding: 30rpx;
  527 + background-color: #fff;
  528 + border-radius: 12rpx;
  529 +
  530 + .popup-title {
  531 + font-size: 32rpx;
  532 + font-weight: 600;
  533 + color: #333;
  534 + }
  535 +
  536 + .upload-title {
  537 + font-size: 28rpx;
  538 + color: #666;
  539 + margin-bottom: 10rpx;
  540 + }
  541 +
  542 + .popup-btn-wrap {
  543 + display: flex;
  544 + justify-content: flex-end;
  545 + }
  546 +}
  547 +</style>
0 548 \ No newline at end of file
... ...
pages-sub/problem/work-order-manage/index.vue
... ... @@ -377,7 +377,7 @@ const confirmReject = async () =&gt; {
377 377 // 新增工单
378 378 const handleAddOrder = () => {
379 379 uni.navigateTo({
380   - url: '/pages-sub/daily/quick-order/add-order'
  380 + url: '/pages-sub/problem/work-order-manage/add-order'
381 381 });
382 382 };
383 383  
... ...
pages.json
... ... @@ -125,6 +125,12 @@
125 125 "style": { "navigationBarTitleText": "工单管理" }
126 126 },
127 127 {
  128 + "path": "work-order-manage/add-order",
  129 + "style": { "navigationBarTitleText": "新增工单" }
  130 + },
  131 +
  132 +
  133 + {
128 134 "path": "problem-allot/index",
129 135 "style": { "navigationBarTitleText": "问题分配" }
130 136 }
... ...