Commit 5822009abe1e19a5ab8ad949d282ab46236da93b

Authored by 刘淇
1 parent d433e7f6

工单管理--新增工单

pages-sub/problem/work-order-manage/add-order.vue
1 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 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 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 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 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 568 .fixed-bottom-btn-wrap {
508   - position: fixed;
509   - bottom: 0;
510   - left: 0;
511   - right: 0;
512 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 572 </style>
548 573 \ No newline at end of file
... ...