Commit 5822009abe1e19a5ab8ad949d282ab46236da93b

Authored by 刘淇
1 parent d433e7f6

工单管理--新增工单

pages-sub/problem/work-order-manage/add-order.vue
1 <template> 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" 2 + <view class="u-page">
  3 + <!-- 核心:将所有 up-form-item 包裹在同一个 up-form 内 -->
  4 + <view class="work-order-form-content commonPageLRpadding">
  5 + <up-form
  6 + label-position="left"
  7 + :model="workOrderForm"
  8 + ref="workOrderFormRef"
  9 + labelWidth="160rpx"
  10 + >
  11 + <!-- 1. 工单位置(地图选择) -->
  12 + <up-form-item
  13 + label="工单位置"
  14 + prop="workLocation"
  15 + border-bottom
  16 + required
  17 + @click="chooseWorkLocation(); hideKeyboard()"
73 > 18 >
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> 19 + <up-input
  20 + v-model="workOrderForm.workLocation"
  21 + border="none"
  22 + readonly
  23 + suffix-icon="map-fill"
  24 + placeholder="点击选择工单位置"
  25 + ></up-input>
  26 + </up-form-item>
  27 +
  28 + <!-- 2. 道路名称(下拉框:位置选择后才可点击) -->
  29 + <up-form-item
  30 + label="道路名称"
  31 + prop="roadName"
  32 + border-bottom
  33 + required
  34 + @click="workOrderForm.workLocation ? (showRoadName = true, hideKeyboard()) : uni.showToast({title: '请先选择工单位置', icon: 'none'})"
  35 + >
  36 + <up-input
  37 + v-model="workOrderForm.roadName"
  38 + disabled
  39 + disabled-color="#ffffff"
  40 + placeholder="请先选择工单位置"
  41 + border="none"
  42 + :placeholder-style="workOrderForm.workLocation ? '' : 'color:#999;'"
  43 + ></up-input>
  44 + <template #right>
  45 + <up-icon name="arrow-right" size="16" :color="workOrderForm.workLocation ? '#333' : '#999'"></up-icon>
109 </template> 46 </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" 47 + </up-form-item>
  48 +
  49 + <!-- 3. 工单名称(下拉框) -->
  50 + <up-form-item
  51 + label="工单名称"
  52 + prop="orderName"
  53 + border-bottom
  54 + required
  55 + @click="showOrderName = true; hideKeyboard()"
121 > 56 >
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> 57 + <up-input
  58 + v-model="workOrderForm.orderName"
  59 + disabled
  60 + disabled-color="#ffffff"
  61 + placeholder="请选择工单名称"
  62 + border="none"
  63 + ></up-input>
  64 + <template #right>
  65 + <up-icon name="arrow-right" size="16"></up-icon>
154 </template> 66 </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> 67 + </up-form-item>
  68 +
  69 + <!-- 新增:紧急程度选择 -->
  70 + <up-form-item
  71 + label="紧急程度"
  72 + prop="pressingType"
  73 + border-bottom
  74 + required
  75 + @click="showPressingType = true; hideKeyboard()"
  76 + >
  77 + <up-input
  78 + v-model="workOrderForm.pressingTypeName"
  79 + disabled
  80 + disabled-color="#ffffff"
  81 + placeholder="请选择紧急程度"
  82 + border="none"
  83 + ></up-input>
  84 + <template #right>
  85 + <up-icon name="arrow-right" size="16"></up-icon>
  86 + </template>
  87 + </up-form-item>
165 88
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> 89 +
  90 + <!-- 4. 情况描述(文本域) -->
  91 + <up-form-item
  92 + label="情况描述"
  93 + prop="problemDesc"
  94 + required
  95 + >
  96 + <up-textarea
  97 + placeholder="请输入情况描述(最多200字)"
  98 + v-model="workOrderForm.problemDesc"
  99 + count
  100 + maxlength="200"
  101 + rows="4"
  102 + @blur="() => $refs.workOrderFormRef.validateField('problemDesc')"
  103 + ></up-textarea>
  104 + </up-form-item>
  105 +
  106 + <!-- 问题照片(移除完成照片相关代码) -->
  107 + <up-form-item label="问题照片" prop="problemImgs" required>
180 <up-upload 108 <up-upload
181 - :action="uploadUrl"  
182 - :file-list="rejectFileList"  
183 - @after-read="handleAfterRead"  
184 - @delete="handleDeleteFile" 109 + :file-list="problemImgsList"
  110 + @after-read="(event) => uploadImgs(event, 'problemImgsList')"
  111 + @delete="(event) => deleteImg(event, 'problemImgsList')"
185 multiple 112 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 -} 113 + :max-count="3"
  114 + upload-text="选择问题照片"
  115 + ></up-upload>
  116 + </up-form-item>
426 117
427 -// 搜索栏样式  
428 -.search-header {  
429 - display: flex;  
430 - align-items: center;  
431 - padding: 20rpx;  
432 - box-sizing: border-box;  
433 118
434 - .select-wrap {  
435 - width: 120rpx;  
436 - margin-right: 20rpx;  
437 119
438 - :deep(.u-select) {  
439 - width: 100%;  
440 - font-size: 28rpx;  
441 - } 120 + <!-- 完成时间 -->
  121 + <up-form-item
  122 + label="完成时间"
  123 + prop="finishTime"
  124 + @click="show=true;hideKeyboard()"
  125 + >
  126 + <up-input
  127 + v-model="workOrderForm.finishTime"
  128 + border="none"
  129 + readonly
  130 + placeholder="点击选择时间"
  131 + >
  132 +
  133 + </up-input>
  134 + <template #right>
  135 + <up-icon name="arrow-right" size="16"></up-icon>
  136 + </template>
  137 + </up-form-item>
442 138
443 - :deep(.u-input__placeholder) {  
444 - font-size: 28rpx;  
445 - }  
446 - }  
447 139
448 - .search-input-wrap {  
449 - flex: 1;  
450 - }  
451 -} 140 + </up-form>
  141 + </view>
452 142
453 -// 工单卡片样式  
454 -.common-card-list {  
455 - padding: 20rpx;  
456 -} 143 + <!-- 底部提交按钮 -->
  144 + <view class="fixed-bottom-btn-wrap">
  145 + <up-button
  146 + type="primary"
  147 + text="提交工单"
  148 + @click="submitWorkOrder"
  149 + ></up-button>
  150 + </view>
457 151
458 -.order-card {  
459 - margin-bottom: 20rpx;  
460 - background-color: #fff;  
461 - border-radius: 12rpx;  
462 - overflow: hidden;  
463 -} 152 + <!-- 道路名称下拉弹窗 -->
  153 + <up-action-sheet
  154 + :show="showRoadName"
  155 + :actions="roadNameList"
  156 + title="请选择道路名称"
  157 + @close="showRoadName = false"
  158 + @select="handleRoadNameSelect"
  159 + ></up-action-sheet>
  160 +
  161 + <!-- 工单名称下拉弹窗 -->
  162 + <up-action-sheet
  163 + :show="showOrderName"
  164 + :actions="orderNameList"
  165 + title="请选择工单名称"
  166 + @close="showOrderName = false"
  167 + @select="handleOrderNameSelect"
  168 + ></up-action-sheet>
  169 +
  170 + <!-- 紧急程度下拉弹窗 -->
  171 + <up-action-sheet
  172 + :show="showPressingType"
  173 + :actions="pressingTypeList"
  174 + title="请选择紧急程度"
  175 + @close="showPressingType = false"
  176 + @select="handlePressingTypeSelect"
  177 + ></up-action-sheet>
  178 + <!-- 完成时间选择器 -->
  179 + <up-datetime-picker
  180 + :show="show"
  181 + v-model="finishTime"
  182 + mode="datetime"
  183 + :min-date="new Date()"
  184 +
  185 + @cancel="show = false"
  186 + @confirm="finishTimeConfirm"
  187 + ></up-datetime-picker>
464 188
465 -.card-body {  
466 - padding: 20rpx;  
467 189
468 - .u-body-item {  
469 - margin-bottom: 16rpx;  
470 - font-size: 28rpx; 190 + </view>
  191 +</template>
471 192
472 - &:last-child {  
473 - margin-bottom: 0;  
474 - } 193 +<script setup lang="ts">
  194 +import {ref} from 'vue'
  195 +import type {UniFormRef} from '@/uni_modules/uview-plus/types'
  196 +// 定义ref供选项式API使用
  197 +const workOrderFormRef = ref<UniFormRef>(null)
  198 +</script>
475 199
476 - .u-body-item-title {  
477 - color: #666;  
478 - min-width: 120rpx; 200 +<script lang="ts">
  201 +import {getRoadListByLatLng} from '@/api/common'
  202 +import {uploadImages} from '@/common/utils/upload';
  203 +import {createQuick} from '@/api/quick-order/quick-order'
  204 +import { timeFormat } from '@/uni_modules/uview-plus' // 引入时间格式化工具
  205 +
  206 +export default {
  207 + data() {
  208 + return {
  209 + // 问题照片列表
  210 + problemImgsList: [],
  211 + // 弹窗显隐控制
  212 + showRoadName: false,
  213 + showOrderName: false,
  214 + showPressingType: false,
  215 + show: false,
  216 + showBirthday: false, // 生日弹窗控制
  217 + // 下拉列表数据
  218 + roadNameList: [],
  219 + orderNameList: [],
  220 + pressingTypeList: [],
  221 + finishTime:Date.now(),
  222 + // 工单表单数据
  223 + workOrderForm: {
  224 + roadId: 0, // 道路ID
  225 + roadName: '', // 道路名称
  226 + workLocation: '', // 工单位置
  227 + orderName: '', // 工单名称
  228 + pressingType: 0, // 紧急程度值(提交接口用)
  229 + pressingTypeName: '', // 紧急程度名称(显示用)
  230 + problemDesc: '', // 情况描述
  231 + lat: 0, // 纬度
  232 + lon: 0, // 经度
  233 + finishTime: '', // 完成时间
  234 + },
  235 +
  236 + // 表单校验规则
  237 + workOrderFormRules: {
  238 + workLocation: [
  239 + {type: 'string', required: true, message: '请选择工单位置', trigger: ['change', 'blur']}
  240 + ],
  241 + roadName: [
  242 + {type: 'string', required: true, message: '请选择道路名称', trigger: ['change', 'blur']}
  243 + ],
  244 + orderName: [
  245 + {type: 'string', required: true, message: '请选择工单名称', trigger: ['change', 'blur']}
  246 + ],
  247 + pressingType: [
  248 + {type: 'number', required: true, message: '请选择紧急程度', trigger: ['change']}
  249 + ],
  250 + birthday: [ // 新增:生日校验规则
  251 + {type: 'string', required: true, message: '请选择生日', trigger: ['change']}
  252 + ],
  253 + problemDesc: [
  254 + {type: 'string', required: true, message: '请输入情况描述', trigger: ['change', 'blur']},
  255 + {type: 'string', min: 3, max: 200, message: '情况描述需3-200字', trigger: ['change', 'blur']}
  256 + ],
  257 + problemImgs: [
  258 + {
  259 + required: true,
  260 + message: '请上传问题照片',
  261 + trigger: 'change',
  262 + validator: (rule, value, callback) => {
  263 + const hasSuccessImg = this.problemImgsList.some(item => item.status === 'success')
  264 + hasSuccessImg ? callback() : callback(new Error('请上传至少1张问题照片'))
  265 + }
  266 + }
  267 + ]
  268 + }
479 } 269 }
480 -  
481 - .u-body-value {  
482 - color: #333;  
483 - flex: 1; 270 + },
  271 +
  272 + onReady() {
  273 + // 兼容微信小程序,通过setRules设置校验规则
  274 + this.$refs.workOrderFormRef.setRules(this.workOrderFormRules)
  275 + console.log('工单表单规则初始化完成')
  276 + },
  277 +
  278 + onShow(){
  279 + console.log(uni.$dict.getDictLabel('ai_image_status', 20))
  280 + console.log(uni.$dict.getDictSimpleList('work_name'))
  281 +
  282 + // 初始化工单名称列表
  283 + this.orderNameList = uni.$dict.transformLabelValueToNameValue(uni.$dict.getDictSimpleList('work_name'))
  284 + console.log('工单名称列表:', this.orderNameList)
  285 +
  286 + // 初始化紧急程度列表
  287 + this.pressingTypeList = uni.$dict.transformLabelValueToNameValue(uni.$dict.getDictSimpleList('workorder_pressing_type'))
  288 + console.log('紧急程度列表:', this.pressingTypeList)
  289 + },
  290 +
  291 + methods: {
  292 + /**
  293 + * 返回上一页
  294 + */
  295 + navigateBack() {
  296 + uni.navigateBack()
  297 + },
  298 +
  299 + /**
  300 + * 删除图片
  301 + */
  302 + deleteImg(event, type) {
  303 + console.log('删除图片事件:', event, '类型:', type)
  304 + if (type === 'problemImgsList') {
  305 + this.problemImgsList.splice(event.index, 1)
  306 + this.$refs.workOrderFormRef.validateField('problemImgs')
  307 + }
  308 + uni.showToast({title: '图片删除成功', icon: 'success'})
  309 + },
  310 +
  311 + /**
  312 + * 上传图片
  313 + */
  314 + async uploadImgs(event, type) {
  315 + console.log('上传图片事件:', event, '类型:', type)
  316 + if (type !== 'problemImgsList') return
  317 +
  318 + const fileList = Array.isArray(event.file) ? event.file : [event.file]
  319 + const targetImgList = this.problemImgsList
  320 +
  321 + const filePaths = fileList.map(item => item.url)
  322 + const tempItems = fileList.map(item => ({
  323 + ...item,
  324 + status: 'uploading',
  325 + message: '上传中'
  326 + }))
  327 + const startIndex = targetImgList.length
  328 + targetImgList.push(...tempItems)
  329 +
  330 + try {
  331 + const uploadResultUrls = await uploadImages({
  332 + filePaths: filePaths,
  333 + ignoreError: true
  334 + })
  335 + console.log('上传成功的URL列表:', uploadResultUrls)
  336 +
  337 + uploadResultUrls.forEach((url, index) => {
  338 + if (targetImgList[startIndex + index]) {
  339 + targetImgList.splice(startIndex + index, 1, {
  340 + ...fileList[index],
  341 + status: 'success',
  342 + message: '',
  343 + url: url
  344 + })
  345 + }
  346 + })
  347 +
  348 + if (uploadResultUrls.length < fileList.length) {
  349 + const failCount = fileList.length - uploadResultUrls.length
  350 + for (let i = uploadResultUrls.length; i < fileList.length; i++) {
  351 + if (targetImgList[startIndex + i]) {
  352 + targetImgList.splice(startIndex + i, 1, {
  353 + ...fileList[i],
  354 + status: 'failed',
  355 + message: '上传失败'
  356 + })
  357 + }
  358 + }
  359 + uni.showToast({title: `成功上传${uploadResultUrls.length}张,失败${failCount}张`, icon: 'none'})
  360 + } else {
  361 + uni.showToast({title: `成功上传${fileList.length}张图片`, icon: 'success'})
  362 + }
  363 +
  364 + this.$refs.workOrderFormRef.validateField('problemImgs')
  365 + } catch (err) {
  366 + console.error('图片上传失败:', err)
  367 + for (let i = 0; i < fileList.length; i++) {
  368 + if (targetImgList[startIndex + i]) {
  369 + targetImgList.splice(startIndex + i, 1, {
  370 + ...fileList[i],
  371 + status: 'failed',
  372 + message: '上传失败'
  373 + })
  374 + }
  375 + }
  376 + uni.showToast({title: '图片上传失败,请重试', icon: 'none'})
  377 + this.$refs.workOrderFormRef.validateField('problemImgs')
  378 + }
  379 + },
  380 +
  381 + /**
  382 + * 选择工单位置
  383 + */
  384 + chooseWorkLocation() {
  385 + let that = this
  386 + uni.chooseLocation({
  387 + success: async (res) => {
  388 + that.workOrderForm.roadName = ''
  389 + that.workOrderForm.roadId = 0
  390 + that.roadNameList = []
  391 +
  392 + that.workOrderForm.workLocation = res.name
  393 + that.workOrderForm.lat = res.latitude
  394 + that.workOrderForm.lon = res.longitude
  395 +
  396 + that.$refs.workOrderFormRef.validateField('workLocation')
  397 + that.$refs.workOrderFormRef.validateField('roadName')
  398 +
  399 + try {
  400 + uni.showLoading({title: '获取道路名称中...'})
  401 + const roadRes = await getRoadListByLatLng({
  402 + companyCode: 'sls',
  403 + latitude: res.latitude,
  404 + longitude: res.longitude
  405 + })
  406 + uni.hideLoading()
  407 +
  408 + if (Array.isArray(roadRes)) {
  409 + that.roadNameList = roadRes.map((item) => ({
  410 + name: item.roadName || '',
  411 + code: item.roadCode || '',
  412 + id: item.roadId || 0
  413 + }))
  414 + } else {
  415 + that.roadNameList = [{name: '未查询到道路名称', code: '', id: 0}]
  416 + uni.showToast({title: '未查询到该位置的道路信息', icon: 'none'})
  417 + }
  418 + } catch (err) {
  419 + uni.hideLoading()
  420 + console.error('获取道路名称失败:', err)
  421 + uni.showToast({title: '获取道路名称失败,请重试', icon: 'none'})
  422 + that.roadNameList = [{name: '获取失败,请重新选择位置', code: '', id: 0}]
  423 + }
  424 + },
  425 + fail: (err) => {
  426 + console.error('选择位置失败:', err)
  427 + uni.showToast({title: '选择位置失败:' + err.errMsg, icon: 'none'})
  428 + }
  429 + })
  430 + },
  431 +
  432 + /**
  433 + * 选择道路名称
  434 + */
  435 + handleRoadNameSelect(e) {
  436 + console.log('选择道路名称:', e)
  437 + this.workOrderForm.roadName = e.name
  438 + this.workOrderForm.roadId = e.code
  439 + this.showRoadName = false
  440 + this.$refs.workOrderFormRef.validateField('roadName')
  441 + },
  442 +
  443 + /**
  444 + * 选择工单名称
  445 + */
  446 + handleOrderNameSelect(e) {
  447 + console.log('选择工单名称:', e)
  448 + this.workOrderForm.orderName = e.name
  449 + this.showOrderName = false
  450 + this.$refs.workOrderFormRef.validateField('orderName')
  451 + },
  452 +
  453 + /**
  454 + * 选择紧急程度
  455 + */
  456 + handlePressingTypeSelect(e) {
  457 + console.log('选择紧急程度:', e)
  458 + this.workOrderForm.pressingType = Number(e.value)
  459 + this.workOrderForm.pressingTypeName = e.name
  460 + this.showPressingType = false
  461 + this.$refs.workOrderFormRef.validateField('pressingType')
  462 + },
  463 +
  464 + /**
  465 + * 完成时间确认
  466 + */
  467 + finishTimeConfirm(e) {
  468 + console.log('选择的完成时间:', e)
  469 + this.workOrderForm.finishTime = timeFormat(e.value, 'yyyy-mm-dd hh:MM:ss')
  470 + this.show = false;
  471 + },
  472 +
  473 +
  474 + /**
  475 + * 隐藏键盘
  476 + */
  477 + hideKeyboard() {
  478 + uni.hideKeyboard()
  479 + },
  480 +
  481 + /**
  482 + * 提取图片URL数组
  483 + */
  484 + getImgUrlList(imgList) {
  485 + return imgList.filter(item => item.status === 'success').map(item => item.url)
  486 + },
  487 +
  488 + /**
  489 + * 提交工单
  490 + */
  491 + async submitWorkOrder() {
  492 + try {
  493 + // 先执行表单校验
  494 + await this.$refs.workOrderFormRef.validate()
  495 +
  496 + const submitData = {
  497 + roadId: this.workOrderForm.roadId,
  498 + roadName: this.workOrderForm.roadName,
  499 + imgs: this.getImgUrlList(this.problemImgsList),
  500 + remark: this.workOrderForm.problemDesc,
  501 + latLonType: 2,
  502 + lat: this.workOrderForm.lat,
  503 + lon: this.workOrderForm.lon,
  504 + lonLatAddress: this.workOrderForm.workLocation,
  505 + pressingType: this.workOrderForm.pressingType,
  506 + orderName: this.workOrderForm.orderName,
  507 + birthday: this.workOrderForm.birthday, // 新增:生日字段提交
  508 + finishTime: this.workOrderForm.finishTime, // 新增:完成时间提交
  509 + sourceId: 1,
  510 + sourceName: '园林',
  511 + thirdWorkNo: '',
  512 + busiLine:'yl'
  513 + }
  514 +
  515 + // 显示加载中
  516 + uni.showLoading({title: '提交中...'})
  517 +
  518 + // 调用提交接口
  519 + const res = await createQuick(submitData)
  520 +
  521 + uni.hideLoading()
  522 + uni.showToast({
  523 + title: '工单提交成功',
  524 + icon: 'success',
  525 + duration: 1000
  526 + })
  527 +
  528 + // 延迟跳转
  529 + setTimeout(() => {
  530 + uni.redirectTo({
  531 + url: '/pages-sub/daily/quick-order/index'
  532 + })
  533 + }, 1000)
  534 + } catch (error) {
  535 + // 隐藏加载框
  536 + uni.hideLoading()
  537 +
  538 + // 区分是表单校验失败还是接口调用失败
  539 + if (Array.isArray(error)) {
  540 + // 表单校验失败(无需额外提示,uView会自动提示)
  541 + } else {
  542 + // 接口调用失败
  543 + console.error('工单提交失败:', error)
  544 + uni.showToast({
  545 + title: '提交失败,请重试',
  546 + icon: 'none',
  547 + duration: 2000
  548 + })
  549 + }
  550 + }
484 } 551 }
485 } 552 }
486 -  
487 - .mt-20 {  
488 - margin-top: 20rpx;  
489 - }  
490 -}  
491 -  
492 -// 公共flex样式  
493 -.u-flex {  
494 - display: flex;  
495 - align-items: center;  
496 } 553 }
  554 +</script>
497 555
498 -.common-justify-between {  
499 - justify-content: space-between; 556 +<style lang="scss" scoped>
  557 +// 全局页面样式
  558 +.u-page {
  559 + min-height: 100vh;
500 } 560 }
501 561
502 -.common-item-center {  
503 - align-items: center; 562 +// 工单表单内容容器
  563 +.work-order-form-content {
  564 + background: #fff;
504 } 565 }
505 566
506 -// 底部按钮 567 +// 底部按钮样式
507 .fixed-bottom-btn-wrap { 568 .fixed-bottom-btn-wrap {
508 - position: fixed;  
509 - bottom: 0;  
510 - left: 0;  
511 - right: 0;  
512 padding: 20rpx; 569 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 - } 570 + background: #fff;
546 } 571 }
547 </style> 572 </style>
548 \ No newline at end of file 573 \ No newline at end of file