Commit d433e7f692e28b46cbbd3aa5bc02047f3aa07bc8

Authored by 刘淇
1 parent 7b1f488f

工单

pages-sub/daily/maintain-manage/add-record.vue
1 <template> 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 <up-form 4 <up-form
5 label-position="left" 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 <up-form-item 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 </up-form-item> 21 </up-form-item>
26 22
27 - <!-- 2. 道路名称(下拉框) --> 23 + <!-- 2. 上传图片 -->
28 <up-form-item 24 <up-form-item
29 - label="道路名称"  
30 - prop="roadName"  
31 - border-bottom 25 + label="上传图片"
  26 + prop="images"
32 required 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 border-bottom 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 <up-upload 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 multiple 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 ></up-upload> 41 ></up-upload>
95 </up-form-item> 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 </up-form-item> 64 </up-form-item>
121 </up-form> 65 </up-form>
122 </view> 66 </view>
@@ -125,271 +69,270 @@ @@ -125,271 +69,270 @@
125 <view class="fixed-bottom-btn-wrap"> 69 <view class="fixed-bottom-btn-wrap">
126 <up-button 70 <up-button
127 type="primary" 71 type="primary"
128 - text="提交工单"  
129 - @click="submitWorkOrder" 72 + text="提交"
  73 + @click="submitInspect"
  74 + :style="{ width: '100%', height: '88rpx', fontSize: '32rpx', borderRadius: 0 }"
130 ></up-button> 75 ></up-button>
131 </view> 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 </view> 77 </view>
151 </template> 78 </template>
152 79
153 <script setup lang="ts"> 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 import type { UniFormRef } from '@/uni_modules/uview-plus/types' 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 try { 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 } else { 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 } catch (err) { 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 </script> 325 </script>
379 326
380 <style lang="scss" scoped> 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 background: #fff; 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 \ No newline at end of file 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 \ No newline at end of file 548 \ No newline at end of file
pages-sub/problem/work-order-manage/index.vue
@@ -377,7 +377,7 @@ const confirmReject = async () =&gt; { @@ -377,7 +377,7 @@ const confirmReject = async () =&gt; {
377 // 新增工单 377 // 新增工单
378 const handleAddOrder = () => { 378 const handleAddOrder = () => {
379 uni.navigateTo({ 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,6 +125,12 @@
125 "style": { "navigationBarTitleText": "工单管理" } 125 "style": { "navigationBarTitleText": "工单管理" }
126 }, 126 },
127 { 127 {
  128 + "path": "work-order-manage/add-order",
  129 + "style": { "navigationBarTitleText": "新增工单" }
  130 + },
  131 +
  132 +
  133 + {
128 "path": "problem-allot/index", 134 "path": "problem-allot/index",
129 "style": { "navigationBarTitleText": "问题分配" } 135 "style": { "navigationBarTitleText": "问题分配" }
130 } 136 }