Commit 3cbb87547081deaf9f903201077ab1a9e5a32fc4

Authored by 刘淇
1 parent b055b398

动态字典

common/config/env.js
@@ -5,7 +5,7 @@ const env = { @@ -5,7 +5,7 @@ const env = {
5 // baseUrl: 'https://test.jichengshanshui.com.cn:28302', 5 // baseUrl: 'https://test.jichengshanshui.com.cn:28302',
6 // https://test.jichengshanshui.com.cn:28302 6 // https://test.jichengshanshui.com.cn:28302
7 7
8 - baseUrl: 'http://192.168.1.111:48081', 8 + baseUrl: 'https://test.jichengshanshui.com.cn:28302',
9 9
10 uploadUrl: 'https://test.jichengshanshui.com.cn:28302', 10 uploadUrl: 'https://test.jichengshanshui.com.cn:28302',
11 // baseApi:'admin-api', 11 // baseApi:'admin-api',
common/config/global.js
@@ -25,6 +25,7 @@ export default { @@ -25,6 +25,7 @@ export default {
25 moduleListKey:'jcss_module_list', 25 moduleListKey:'jcss_module_list',
26 dictDataKey:'jcss_dict_data', 26 dictDataKey:'jcss_dict_data',
27 refreshTokenKey: 'jcss_user_refresh_token', // 新增:刷新令牌缓存key 27 refreshTokenKey: 'jcss_user_refresh_token', // 新增:刷新令牌缓存key
  28 + dictListKey: 'jcss_dict_list' // 新增这一行
28 }, 29 },
29 appName: 'JCSS管理系统', 30 appName: 'JCSS管理系统',
30 tokenExpireTime: 7 * 24 * 60 * 60 * 1000 31 tokenExpireTime: 7 * 24 * 60 * 60 * 1000
pages-sub/daily/quick-order/add-order.vue
@@ -2,204 +2,88 @@ @@ -2,204 +2,88 @@
2 <view class="page-container"> 2 <view class="page-container">
3 <!-- 核心:将所有 up-form-item 包裹在同一个 up-form 内 --> 3 <!-- 核心:将所有 up-form-item 包裹在同一个 up-form 内 -->
4 <view class="work-order-form-content commonPageLRpadding"> 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 -  
12 - <up-form-item  
13 - label="业务线"  
14 - prop="busiLineCn"  
15 - border-bottom  
16 - required  
17 - >  
18 - <up-radio-group  
19 - v-model="workOrderForm.busiLineCn"  
20 - placement="row"  
21 - @change="handleBusiLineChange"  
22 - >  
23 - <up-radio  
24 - v-for="item in busiLineOptions"  
25 - :key="item.name"  
26 - :label="item.name"  
27 - :name="item.name"  
28 - ></up-radio> 5 + <up-form label-position="left" :model="workOrderForm" ref="workOrderFormRef" labelWidth="160rpx">
  6 + <up-form-item label="业务线" prop="busiLine" border-bottom required>
  7 + <up-radio-group v-model="busiLineDisplay" placement="row" @change="handleBusiLineChange">
  8 + <up-radio v-for="item in busiLineOptions" :key="item.value" :label="item.label" :name="item.label"
  9 + > </up-radio>
29 </up-radio-group> 10 </up-radio-group>
30 -  
31 </up-form-item> 11 </up-form-item>
32 <!-- 1. 工单位置(地图选择) --> 12 <!-- 1. 工单位置(地图选择) -->
33 - <up-form-item  
34 - label="工单位置"  
35 - prop="workLocation"  
36 - border-bottom  
37 - required  
38 - @click="chooseWorkLocation(); hideKeyboard()"  
39 - >  
40 - <up-input  
41 - v-model="workOrderForm.workLocation"  
42 - border="none"  
43 - readonly  
44 - suffix-icon="map-fill"  
45 - placeholder="点击选择工单位置"  
46 - ></up-input> 13 + <up-form-item label="工单位置" prop="workLocation" border-bottom required
  14 + @click="chooseWorkLocation(); hideKeyboard()">
  15 + <up-input v-model="workOrderForm.workLocation" border="none" readonly suffix-icon="map-fill"
  16 + placeholder="点击选择工单位置"></up-input>
47 </up-form-item> 17 </up-form-item>
48 -  
49 <!-- 2. 道路名称(下拉框:位置选择后才可点击) --> 18 <!-- 2. 道路名称(下拉框:位置选择后才可点击) -->
50 - <up-form-item  
51 - label="道路名称"  
52 - prop="roadName"  
53 - border-bottom  
54 - required  
55 - @click="workOrderForm.workLocation ? (showRoadName = true, hideKeyboard()) : uni.showToast({title: '请先选择工单位置', icon: 'none'})"  
56 - >  
57 - <up-input  
58 - v-model="workOrderForm.roadName"  
59 - readonly  
60 - disabled-color="#ffffff"  
61 - placeholder="请先选择工单位置"  
62 - border="none"  
63 - ></up-input> 19 + <up-form-item label="道路名称" prop="roadName" border-bottom required
  20 + @click="workOrderForm.workLocation ? (showRoadName = true, hideKeyboard()) : uni.showToast({ title: '请先选择工单位置', icon: 'none' })">
  21 + <up-input v-model="workOrderForm.roadName" readonly disabled-color="#ffffff" placeholder="请先选择工单位置"
  22 + border="none"></up-input>
64 <template #right> 23 <template #right>
65 - <up-icon name="arrow-right" size="16" ></up-icon> 24 + <up-icon name="arrow-right" size="16"></up-icon>
66 </template> 25 </template>
67 </up-form-item> 26 </up-form-item>
68 -  
69 <!-- 3. 问题类型(下拉框) --> 27 <!-- 3. 问题类型(下拉框) -->
70 - <up-form-item  
71 - label="问题类型"  
72 - prop="orderName"  
73 - border-bottom  
74 - required  
75 - @click="showOrderName = true; hideKeyboard()"  
76 - >  
77 - <up-input  
78 - v-model="workOrderForm.orderName"  
79 - disabled  
80 - disabled-color="#ffffff"  
81 - placeholder="请选择问题类型"  
82 - border="none"  
83 - ></up-input> 28 + <up-form-item label="问题类型" prop="orderName" border-bottom required
  29 + @click="showOrderName = true; hideKeyboard()">
  30 + <up-input v-model="workOrderForm.orderName" disabled disabled-color="#ffffff" placeholder="请选择问题类型"
  31 + border="none"></up-input>
84 <template #right> 32 <template #right>
85 <up-icon name="arrow-right" size="16"></up-icon> 33 <up-icon name="arrow-right" size="16"></up-icon>
86 </template> 34 </template>
87 </up-form-item> 35 </up-form-item>
88 -  
89 <!-- 4. 情况描述(文本域) --> 36 <!-- 4. 情况描述(文本域) -->
90 - <up-form-item  
91 - label="情况描述"  
92 - prop="problemDesc"  
93 - required  
94 - >  
95 - <up-textarea  
96 - placeholder="请输入情况描述(最多200字)"  
97 - v-model.trim="workOrderForm.problemDesc"  
98 - count  
99 - maxlength="200"  
100 - rows="4"  
101 - @blur="() => $refs.workOrderFormRef.validateField('problemDesc')"  
102 - ></up-textarea> 37 + <up-form-item label="情况描述" prop="problemDesc" required>
  38 + <up-textarea placeholder="请输入情况描述(最多200字)" v-model.trim="workOrderForm.problemDesc" count maxlength="200"
  39 + rows="4" @blur="() => $refs.workOrderFormRef.validateField('problemDesc')"></up-textarea>
103 </up-form-item> 40 </up-form-item>
104 -  
105 <!-- 问题照片 --> 41 <!-- 问题照片 -->
106 <up-form-item label="问题照片" prop="problemImgs" required> 42 <up-form-item label="问题照片" prop="problemImgs" required>
107 - <up-upload  
108 - :file-list="problemImgsList"  
109 - @after-read="(event) => uploadImgs(event, 'problemImgsList')"  
110 - @delete="(event) => deleteImg(event, 'problemImgsList')"  
111 - multiple  
112 - width="70"  
113 - height="70"  
114 - :max-count="3"  
115 - upload-text="选择问题照片"  
116 - ></up-upload> 43 + <up-upload :file-list="problemImgsList" @after-read="(event) => uploadImgs(event, 'problemImgsList')"
  44 + @delete="(event) => deleteImg(event, 'problemImgsList')" multiple width="70" height="70" :max-count="3"
  45 + upload-text="选择问题照片"></up-upload>
117 </up-form-item> 46 </up-form-item>
118 -  
119 <!-- 完成照片 --> 47 <!-- 完成照片 -->
120 <up-form-item label="完成照片" prop="completeImgs" required class="mt-20"> 48 <up-form-item label="完成照片" prop="completeImgs" required class="mt-20">
121 - <up-upload  
122 - :file-list="completeImgsList"  
123 - @after-read="(event) => uploadImgs(event, 'completeImgsList')"  
124 - @delete="(event) => deleteImg(event, 'completeImgsList')"  
125 - multiple  
126 - width="70"  
127 - height="70"  
128 - :max-count="3"  
129 - :sizeType="['compressed']"  
130 - upload-text="选择完成照片"  
131 - ></up-upload> 49 + <up-upload :file-list="completeImgsList" @after-read="(event) => uploadImgs(event, 'completeImgsList')"
  50 + @delete="(event) => deleteImg(event, 'completeImgsList')" multiple width="70" height="70" :max-count="3"
  51 + :sizeType="['compressed']" upload-text="选择完成照片"></up-upload>
132 </up-form-item> 52 </up-form-item>
133 -  
134 <!-- 处理结果(不必填) --> 53 <!-- 处理结果(不必填) -->
135 <up-form-item label="处理结果" prop="handleResult" class="mt-20"> 54 <up-form-item label="处理结果" prop="handleResult" class="mt-20">
136 - <up-textarea  
137 - placeholder="请输入处理结果描述(最多200字,选填)"  
138 - v-model.trim="workOrderForm.handleResult"  
139 - count  
140 - maxlength="200"  
141 - rows="4"  
142 - @blur="() => $refs.workOrderFormRef.validateField('handleResult')"  
143 - ></up-textarea> 55 + <up-textarea placeholder="请输入处理结果描述(最多200字,选填)" v-model.trim="workOrderForm.handleResult" count
  56 + maxlength="200" rows="4" @blur="() => $refs.workOrderFormRef.validateField('handleResult')"></up-textarea>
144 </up-form-item> 57 </up-form-item>
145 -  
146 </up-form> 58 </up-form>
147 </view> 59 </view>
148 <!-- 底部提交按钮(移除loading和disabled属性) --> 60 <!-- 底部提交按钮(移除loading和disabled属性) -->
149 <view class="fixed-bottom-btn-wrap"> 61 <view class="fixed-bottom-btn-wrap">
150 - <up-button  
151 - type="primary"  
152 - size="large"  
153 - text="提交工单"  
154 - @click="submitWorkOrder"  
155 - ></up-button> 62 + <up-button type="primary" size="large" text="提交工单" @click="submitWorkOrder"></up-button>
156 </view> 63 </view>
157 -  
158 <!-- 道路名称下拉弹窗 --> 64 <!-- 道路名称下拉弹窗 -->
159 - <up-action-sheet  
160 - :show="showRoadName"  
161 - :actions="roadNameList"  
162 - title="请选择道路名称"  
163 - @close="showRoadName = false"  
164 - @select="handleRoadNameSelect"  
165 - ></up-action-sheet>  
166 - 65 + <up-action-sheet :show="showRoadName" :actions="roadNameList" title="请选择道路名称" @close="showRoadName = false"
  66 + @select="handleRoadNameSelect"></up-action-sheet>
167 <!-- 问题类型下拉弹窗 --> 67 <!-- 问题类型下拉弹窗 -->
168 - <up-action-sheet  
169 - :show="showOrderName"  
170 - :actions="orderNameList"  
171 - title="请选择问题类型"  
172 - @close="showOrderName = false"  
173 - @select="handleOrderNameSelect"  
174 - ></up-action-sheet> 68 + <up-action-sheet :show="showOrderName" :actions="orderNameList" title="请选择问题类型" @close="showOrderName = false"
  69 + @select="handleOrderNameSelect"></up-action-sheet>
175 </view> 70 </view>
176 </template> 71 </template>
177 -  
178 <script lang="ts"> 72 <script lang="ts">
179 import { getRoadListByLatLng } from '@/api/common' 73 import { getRoadListByLatLng } from '@/api/common'
180 import { uploadImages } from '@/common/utils/upload'; 74 import { uploadImages } from '@/common/utils/upload';
181 import { createQuick } from '@/api/quick-order/quick-order' 75 import { createQuick } from '@/api/quick-order/quick-order'
182 import { useUserStore } from '@/pinia/user'; 76 import { useUserStore } from '@/pinia/user';
183 -  
184 export default { 77 export default {
  78 + computed: {
  79 + busiLineOptions() {
  80 + const userStore = useUserStore();
  81 + return userStore.dictList || []
  82 + }
  83 + },
185 data() { 84 data() {
186 return { 85 return {
187 - // 业务线相关  
188 - busiLineMap: {  
189 - 'yl': '园林',  
190 - 'sz': '市政',  
191 - 'wy': '物业',  
192 - 'zx': '秩序管理',  
193 - 'hj': '环境卫生',  
194 - 'yw': '常规运维',  
195 - '园林': 'yl',  
196 - '市政': 'sz',  
197 - '物业': 'wy',  
198 - '秩序管理': 'zx',  
199 - '环境卫生': 'hj',  
200 - '常规运维' : 'yw'  
201 - },  
202 - busiLineOptions: [], // 业务线选项列表 86 + busiLineDisplay: '', // 单选框展示用,存中文label
203 // 问题照片列表 87 // 问题照片列表
204 problemImgsList: [], 88 problemImgsList: [],
205 // 完成照片列表 89 // 完成照片列表
@@ -212,7 +96,7 @@ export default { @@ -212,7 +96,7 @@ export default {
212 orderNameList: [], 96 orderNameList: [],
213 // 工单表单数据 97 // 工单表单数据
214 workOrderForm: { 98 workOrderForm: {
215 - busiLineCn: '', // 业务线中文名称 99 + busiLine: '', // 业务线value yl / sz / wy
216 roadId: 0, // 道路ID 100 roadId: 0, // 道路ID
217 roadName: '', // 道路名称 101 roadName: '', // 道路名称
218 workLocation: '', // 工单位置 102 workLocation: '', // 工单位置
@@ -224,7 +108,7 @@ export default { @@ -224,7 +108,7 @@ export default {
224 }, 108 },
225 // 表单校验规则 109 // 表单校验规则
226 workOrderFormRules: { 110 workOrderFormRules: {
227 - busiLineCn: [ // 业务线必选校验 111 + busiLine: [
228 { type: 'string', required: true, message: '请选择业务线', trigger: ['change', 'blur'] } 112 { type: 'string', required: true, message: '请选择业务线', trigger: ['change', 'blur'] }
229 ], 113 ],
230 workLocation: [ 114 workLocation: [
@@ -266,12 +150,11 @@ export default { @@ -266,12 +150,11 @@ export default {
266 } 150 }
267 }, 151 },
268 onLoad() { 152 onLoad() {
269 - // 初始化业务线选项  
270 - this.initBusiLineOptions();  
271 // 默认选中第一个业务线 153 // 默认选中第一个业务线
272 if (this.busiLineOptions && this.busiLineOptions.length > 0) { 154 if (this.busiLineOptions && this.busiLineOptions.length > 0) {
273 - this.workOrderForm.busiLineCn = this.busiLineOptions[0].name;  
274 - console.log('默认选中业务线:', this.workOrderForm.busiLineCn); 155 + this.workOrderForm.busiLine = this.busiLineOptions[0].value;
  156 + this.busiLineDisplay = this.busiLineOptions[0].label;
  157 + console.log('默认选中业务线value:', this.workOrderForm.busiLine);
275 } 158 }
276 }, 159 },
277 onReady() { 160 onReady() {
@@ -284,25 +167,13 @@ export default { @@ -284,25 +167,13 @@ export default {
284 console.log('问题类型列表:', this.orderNameList); 167 console.log('问题类型列表:', this.orderNameList);
285 }, 168 },
286 methods: { 169 methods: {
287 - // 初始化业务线选项  
288 - initBusiLineOptions() {  
289 - // 获取pinia中的用户信息  
290 - const userStore = useUserStore();  
291 - if (userStore.userInfo?.user?.busiLine) {  
292 - const rawBusiLines = userStore.userInfo.user.busiLine.split(',');  
293 - // 生成中文名称列表  
294 - this.busiLineOptions = rawBusiLines.map(item => ({  
295 - name: this.busiLineMap[item.trim()] // name=中文名称(园林/市政/物业)  
296 - }));  
297 - }  
298 - console.log('业务线选项:', this.busiLineOptions);  
299 - },  
300 - // 工具方法:通过中文名称获取对应的英文标识  
301 - getBusiLineEnByCn(cnName) {  
302 - return this.busiLineMap[cnName] || '';  
303 - },  
304 // 业务线切换事件 170 // 业务线切换事件
305 - handleBusiLineChange() { 171 + handleBusiLineChange(selectedLabel) {
  172 + // 根据选中的中文label反向找到value,存入表单
  173 + const selectedItem = this.busiLineOptions.find(item => item.label === selectedLabel)
  174 + if (selectedItem) {
  175 + this.workOrderForm.busiLine = selectedItem.value;
  176 + }
306 // 清空道路信息 177 // 清空道路信息
307 this.workOrderForm.roadName = ''; 178 this.workOrderForm.roadName = '';
308 this.workOrderForm.roadId = 0; 179 this.workOrderForm.roadId = 0;
@@ -314,20 +185,13 @@ export default { @@ -314,20 +185,13 @@ export default {
314 }, 185 },
315 // 根据选中业务线请求道路列表 186 // 根据选中业务线请求道路列表
316 async getRoadListByBusiLine() { 187 async getRoadListByBusiLine() {
317 - if (!this.workOrderForm.lat || !this.workOrderForm.lon) {  
318 - return;  
319 - }  
320 - // 中文转英文标识  
321 - const busiLineEn = this.getBusiLineEnByCn(this.workOrderForm.busiLineCn);  
322 - if (!busiLineEn) {  
323 - uni.showToast({ title: '业务线标识异常', icon: 'none' }); 188 + if (!this.workOrderForm.lat || !this.workOrderForm.lon || !this.workOrderForm.busiLine) {
324 return; 189 return;
325 } 190 }
326 -  
327 try { 191 try {
328 uni.showLoading({ title: '获取道路名称中...' }); 192 uni.showLoading({ title: '获取道路名称中...' });
329 const roadRes = await getRoadListByLatLng({ 193 const roadRes = await getRoadListByLatLng({
330 - busiLine: busiLineEn, // 传递业务线英文标识 194 + busiLine: this.workOrderForm.busiLine, // 直接传递value
331 latitude: this.workOrderForm.lat, 195 latitude: this.workOrderForm.lat,
332 longitude: this.workOrderForm.lon 196 longitude: this.workOrderForm.lon
333 }); 197 });
@@ -380,7 +244,6 @@ export default { @@ -380,7 +244,6 @@ export default {
380 console.log('上传图片事件:', event, '类型:', type); 244 console.log('上传图片事件:', event, '类型:', type);
381 const fileList = Array.isArray(event.file) ? event.file : [event.file]; 245 const fileList = Array.isArray(event.file) ? event.file : [event.file];
382 const targetImgList = type === 'problemImgsList' ? this.problemImgsList : this.completeImgsList; 246 const targetImgList = type === 'problemImgsList' ? this.problemImgsList : this.completeImgsList;
383 -  
384 const filePaths = fileList.map(item => item.url); 247 const filePaths = fileList.map(item => item.url);
385 const tempItems = fileList.map(item => ({ 248 const tempItems = fileList.map(item => ({
386 ...item, 249 ...item,
@@ -389,14 +252,12 @@ export default { @@ -389,14 +252,12 @@ export default {
389 })); 252 }));
390 const startIndex = targetImgList.length; 253 const startIndex = targetImgList.length;
391 targetImgList.push(...tempItems); 254 targetImgList.push(...tempItems);
392 -  
393 try { 255 try {
394 const uploadResultUrls = await uploadImages({ 256 const uploadResultUrls = await uploadImages({
395 filePaths: filePaths, 257 filePaths: filePaths,
396 ignoreError: true 258 ignoreError: true
397 }); 259 });
398 console.log('上传成功的URL列表:', uploadResultUrls); 260 console.log('上传成功的URL列表:', uploadResultUrls);
399 -  
400 uploadResultUrls.forEach((url, index) => { 261 uploadResultUrls.forEach((url, index) => {
401 if (targetImgList[startIndex + index]) { 262 if (targetImgList[startIndex + index]) {
402 targetImgList.splice(startIndex + index, 1, { 263 targetImgList.splice(startIndex + index, 1, {
@@ -407,7 +268,6 @@ export default { @@ -407,7 +268,6 @@ export default {
407 }); 268 });
408 } 269 }
409 }); 270 });
410 -  
411 if (uploadResultUrls.length < fileList.length) { 271 if (uploadResultUrls.length < fileList.length) {
412 const failCount = fileList.length - uploadResultUrls.length; 272 const failCount = fileList.length - uploadResultUrls.length;
413 for (let i = uploadResultUrls.length; i < fileList.length; i++) { 273 for (let i = uploadResultUrls.length; i < fileList.length; i++) {
@@ -423,7 +283,6 @@ export default { @@ -423,7 +283,6 @@ export default {
423 } else { 283 } else {
424 uni.showToast({ title: `成功上传${fileList.length}张图片`, icon: 'success' }); 284 uni.showToast({ title: `成功上传${fileList.length}张图片`, icon: 'success' });
425 } 285 }
426 -  
427 // 上传完成后重新校验图片字段 286 // 上传完成后重新校验图片字段
428 if (type === 'problemImgsList') { 287 if (type === 'problemImgsList') {
429 this.$refs.workOrderFormRef.validateField('problemImgs'); 288 this.$refs.workOrderFormRef.validateField('problemImgs');
@@ -431,7 +290,6 @@ export default { @@ -431,7 +290,6 @@ export default {
431 this.$refs.workOrderFormRef.validateField('completeImgs'); 290 this.$refs.workOrderFormRef.validateField('completeImgs');
432 } 291 }
433 } catch (err) { 292 } catch (err) {
434 - console.error('图片上传失败:', err);  
435 for (let i = 0; i < fileList.length; i++) { 293 for (let i = 0; i < fileList.length; i++) {
436 if (targetImgList[startIndex + i]) { 294 if (targetImgList[startIndex + i]) {
437 targetImgList.splice(startIndex + i, 1, { 295 targetImgList.splice(startIndex + i, 1, {
@@ -442,7 +300,6 @@ export default { @@ -442,7 +300,6 @@ export default {
442 } 300 }
443 } 301 }
444 uni.showToast({ title: '图片上传失败,请重试', icon: 'none' }); 302 uni.showToast({ title: '图片上传失败,请重试', icon: 'none' });
445 -  
446 // 上传失败后重新校验图片字段 303 // 上传失败后重新校验图片字段
447 if (type === 'problemImgsList') { 304 if (type === 'problemImgsList') {
448 this.$refs.workOrderFormRef.validateField('problemImgs'); 305 this.$refs.workOrderFormRef.validateField('problemImgs');
@@ -461,14 +318,11 @@ export default { @@ -461,14 +318,11 @@ export default {
461 that.workOrderForm.roadName = ''; 318 that.workOrderForm.roadName = '';
462 that.workOrderForm.roadId = 0; 319 that.workOrderForm.roadId = 0;
463 that.roadNameList = []; 320 that.roadNameList = [];
464 -  
465 that.workOrderForm.workLocation = res.name; 321 that.workOrderForm.workLocation = res.name;
466 that.workOrderForm.lat = res.latitude; 322 that.workOrderForm.lat = res.latitude;
467 that.workOrderForm.lon = res.longitude; 323 that.workOrderForm.lon = res.longitude;
468 -  
469 that.$refs.workOrderFormRef.validateField('workLocation'); 324 that.$refs.workOrderFormRef.validateField('workLocation');
470 that.$refs.workOrderFormRef.validateField('roadName'); 325 that.$refs.workOrderFormRef.validateField('roadName');
471 -  
472 // 调用封装的方法,自动携带业务线 326 // 调用封装的方法,自动携带业务线
473 await that.getRoadListByBusiLine(); 327 await that.getRoadListByBusiLine();
474 }, 328 },
@@ -516,14 +370,12 @@ export default { @@ -516,14 +370,12 @@ export default {
516 try { 370 try {
517 // 先执行表单校验 371 // 先执行表单校验
518 await this.$refs.workOrderFormRef.validate(); 372 await this.$refs.workOrderFormRef.validate();
519 -  
520 - // 中文转英文标识  
521 - const busiLineEn = this.getBusiLineEnByCn(this.workOrderForm.busiLineCn);  
522 - if (!busiLineEn) { 373 + if (!this.workOrderForm.busiLine) {
523 uni.showToast({ title: '业务线选择异常,请重新选择', icon: 'none' }); 374 uni.showToast({ title: '业务线选择异常,请重新选择', icon: 'none' });
524 return; 375 return;
525 } 376 }
526 - 377 + // 根据value找到中文label
  378 + const sourceName = this.busiLineOptions.find(item => item.value === this.workOrderForm.busiLine)?.label || '';
527 const submitData = { 379 const submitData = {
528 roadId: this.workOrderForm.roadId, 380 roadId: this.workOrderForm.roadId,
529 roadName: this.workOrderForm.roadName, 381 roadName: this.workOrderForm.roadName,
@@ -538,24 +390,20 @@ export default { @@ -538,24 +390,20 @@ export default {
538 pressingType: 2, 390 pressingType: 2,
539 orderName: this.workOrderForm.orderName, 391 orderName: this.workOrderForm.orderName,
540 sourceId: 1, 392 sourceId: 1,
541 - sourceName: this.workOrderForm.busiLineCn, // 业务线中文名称 393 + sourceName: sourceName, // 业务线中文名称
542 thirdWorkNo: '', 394 thirdWorkNo: '',
543 - busiLine: busiLineEn // 业务线英文标识(yl/sz/wy) 395 + busiLine: this.workOrderForm.busiLine // 业务线英文标识(yl/sz/wy)
544 }; 396 };
545 -  
546 // 显示加载中 397 // 显示加载中
547 uni.showLoading({ title: '提交中...' }); 398 uni.showLoading({ title: '提交中...' });
548 -  
549 // 调用提交接口 399 // 调用提交接口
550 const res = await createQuick(submitData); 400 const res = await createQuick(submitData);
551 -  
552 uni.hideLoading(); 401 uni.hideLoading();
553 uni.showToast({ 402 uni.showToast({
554 title: '工单提交成功', 403 title: '工单提交成功',
555 icon: 'success', 404 icon: 'success',
556 duration: 1000 405 duration: 1000
557 }); 406 });
558 -  
559 // 延迟跳转 407 // 延迟跳转
560 setTimeout(() => { 408 setTimeout(() => {
561 uni.reLaunch({ 409 uni.reLaunch({
@@ -565,7 +413,6 @@ export default { @@ -565,7 +413,6 @@ export default {
565 } catch (error) { 413 } catch (error) {
566 // 隐藏加载框 414 // 隐藏加载框
567 uni.hideLoading(); 415 uni.hideLoading();
568 -  
569 // 区分是表单校验失败还是接口调用失败 416 // 区分是表单校验失败还是接口调用失败
570 if (!Array.isArray(error)) { 417 if (!Array.isArray(error)) {
571 // 接口调用失败 418 // 接口调用失败
@@ -581,16 +428,14 @@ export default { @@ -581,16 +428,14 @@ export default {
581 } 428 }
582 } 429 }
583 </script> 430 </script>
584 -  
585 <style lang="scss" scoped> 431 <style lang="scss" scoped>
586 // 全局页面样式 432 // 全局页面样式
587 .page-container { 433 .page-container {
588 min-height: 100vh; 434 min-height: 100vh;
589 } 435 }
590 -  
591 // 工单表单内容容器 436 // 工单表单内容容器
592 .work-order-form-content { 437 .work-order-form-content {
593 background: #fff; 438 background: #fff;
594 margin-bottom: 100rpx; 439 margin-bottom: 100rpx;
595 } 440 }
596 -</style>  
597 \ No newline at end of file 441 \ No newline at end of file
  442 +</style>
pages-sub/problem/regional-order-manage/add-order.vue
1 <template> 1 <template>
2 <view class="page-container"> 2 <view class="page-container">
3 <view class="work-order-form-content commonPageLRpadding"> 3 <view class="work-order-form-content commonPageLRpadding">
4 - <up-form  
5 - label-position="left"  
6 - :model="baseForm"  
7 - ref="workOrderFormRef"  
8 - labelWidth="200rpx"  
9 - > 4 + <up-form label-position="left" :model="baseForm" ref="workOrderFormRef" labelWidth="200rpx">
10 <!-- 1. 工单位置(地图选择)【公共项】 --> 5 <!-- 1. 工单位置(地图选择)【公共项】 -->
11 - <up-form-item  
12 - label="工单位置"  
13 - prop="workLocation"  
14 - border-bottom  
15 - required  
16 - @click="chooseWorkLocation(); hideKeyboard()"  
17 - >  
18 - <up-input  
19 - v-model="baseForm.workLocation"  
20 - border="none"  
21 - readonly  
22 - suffix-icon="map-fill"  
23 - placeholder="点击选择工单位置"  
24 - ></up-input> 6 + <up-form-item label="工单位置" prop="workLocation" border-bottom required
  7 + @click="chooseWorkLocation(); hideKeyboard()">
  8 + <up-input v-model="baseForm.workLocation" border="none" readonly suffix-icon="map-fill"
  9 + placeholder="点击选择工单位置"></up-input>
25 </up-form-item> 10 </up-form-item>
26 -  
27 <!-- 2. 情况描述【公共项】 --> 11 <!-- 2. 情况描述【公共项】 -->
28 - <up-form-item  
29 - label="情况描述"  
30 - prop="problemDesc"  
31 - required  
32 - border-bottom  
33 - >  
34 - <up-textarea  
35 - placeholder="请输入情况描述(最多200字)"  
36 - v-model.trim="baseForm.problemDesc"  
37 - count  
38 - maxlength="200"  
39 - rows="4"  
40 - @blur="() => workOrderFormRef.validateField('problemDesc')"  
41 - ></up-textarea> 12 + <up-form-item label="情况描述" prop="problemDesc" required border-bottom>
  13 + <up-textarea placeholder="请输入情况描述(最多200字)" v-model.trim="baseForm.problemDesc" count maxlength="200" rows="4"
  14 + @blur="() => workOrderFormRef.validateField('problemDesc')"></up-textarea>
42 </up-form-item> 15 </up-form-item>
43 -  
44 <!-- 3. 问题照片【公共项】 --> 16 <!-- 3. 问题照片【公共项】 -->
45 - <up-form-item label="问题照片" prop="problemImgs" required >  
46 - <up-upload  
47 - :file-list="problemImgs.imgList.value || []"  
48 - @after-read="problemImgs.uploadImgs"  
49 - @delete="problemImgs.deleteImg"  
50 - multiple  
51 - :width="70"  
52 - :height="70"  
53 - :max-count="problemImgs.uploadConfig.maxCount"  
54 - :upload-text="problemImgs.uploadText"  
55 - :size-type="problemImgs.uploadConfig.sizeType"  
56 - ></up-upload> 17 + <up-form-item label="问题照片" prop="problemImgs" required>
  18 + <up-upload :file-list="problemImgs.imgList.value || []" @after-read="problemImgs.uploadImgs"
  19 + @delete="problemImgs.deleteImg" multiple :width="70" :height="70"
  20 + :max-count="problemImgs.uploadConfig.maxCount" :upload-text="problemImgs.uploadText"
  21 + :size-type="problemImgs.uploadConfig.sizeType"></up-upload>
57 </up-form-item> 22 </up-form-item>
58 -  
59 <!-- 多组派单区域 --> 23 <!-- 多组派单区域 -->
60 <view class="dispatch-list-wrap"> 24 <view class="dispatch-list-wrap">
61 <view class="dispatch-title">派单情况</view> 25 <view class="dispatch-title">派单情况</view>
62 -  
63 <!-- 循环渲染每组派单 --> 26 <!-- 循环渲染每组派单 -->
64 <view class="dispatch-item" v-for="(item, idx) in dispatchList" :key="idx"> 27 <view class="dispatch-item" v-for="(item, idx) in dispatchList" :key="idx">
65 <view class="item-head"> 28 <view class="item-head">
66 <text>派单{{ idx + 1 }}</text> 29 <text>派单{{ idx + 1 }}</text>
67 <!-- 数量大于1才显示红色关闭X --> 30 <!-- 数量大于1才显示红色关闭X -->
68 - <up-icon  
69 - v-if="dispatchList.length > 1"  
70 - name="close"  
71 - color="#f53f3f"  
72 - size="26"  
73 - class="close-icon"  
74 - @click="delDispatchItem(idx)"  
75 - ></up-icon> 31 + <up-icon v-if="dispatchList.length > 1" name="close" color="#f53f3f" size="26" class="close-icon"
  32 + @click="delDispatchItem(idx)"></up-icon>
76 </view> 33 </view>
77 -  
78 <!-- 业务线 --> 34 <!-- 业务线 -->
79 - <up-form-item  
80 - label="业务线"  
81 - prop="busiLineCn"  
82 - border-bottom  
83 - required  
84 - @click="handleActionSheetOpen('busiLine', idx); hideKeyboard()"  
85 - >  
86 - <up-input  
87 - v-model="item.busiLineCn"  
88 - disabled  
89 - disabled-color="#ffffff"  
90 - placeholder="请选择业务线"  
91 - border="none"  
92 - ></up-input> 35 + <up-form-item label="业务线" prop="busiLineCn" border-bottom required
  36 + @click="handleActionSheetOpen('busiLine', idx); hideKeyboard()">
  37 + <up-input v-model="item.busiLineCn" disabled disabled-color="#ffffff" placeholder="请选择业务线"
  38 + border="none"></up-input>
93 <template #right> 39 <template #right>
94 <up-icon name="arrow-right" size="16"></up-icon> 40 <up-icon name="arrow-right" size="16"></up-icon>
95 </template> 41 </template>
96 </up-form-item> 42 </up-form-item>
97 -  
98 <!-- 问题类型 --> 43 <!-- 问题类型 -->
99 - <up-form-item  
100 - label="问题类型"  
101 - prop="orderName"  
102 - border-bottom  
103 - required  
104 - @click="openCascader(idx); hideKeyboard()"  
105 - >  
106 - <up-input  
107 - v-model="item.orderName"  
108 - disabled  
109 - disabled-color="#ffffff"  
110 - placeholder="请选择问题类型"  
111 - border="none"  
112 - ></up-input> 44 + <up-form-item label="问题类型" prop="orderName" border-bottom required
  45 + @click="openCascader(idx); hideKeyboard()">
  46 + <up-input v-model="item.orderName" disabled disabled-color="#ffffff" placeholder="请选择问题类型"
  47 + border="none"></up-input>
113 <template #right> 48 <template #right>
114 <up-icon name="arrow-right" size="16"></up-icon> 49 <up-icon name="arrow-right" size="16"></up-icon>
115 </template> 50 </template>
116 </up-form-item> 51 </up-form-item>
117 -  
118 <!-- 道路名称 --> 52 <!-- 道路名称 -->
119 - <up-form-item  
120 - label="道路名称"  
121 - prop="roadName"  
122 - border-bottom  
123 - required  
124 - @click="handleActionSheetOpen('roadName', idx); hideKeyboard()"  
125 - >  
126 - <up-input  
127 - v-model="item.roadName"  
128 - readonly  
129 - disabled-color="#ffffff"  
130 - placeholder="请先选择工单位置"  
131 - border="none"  
132 - ></up-input> 53 + <up-form-item label="道路名称" prop="roadName" border-bottom required
  54 + @click="handleActionSheetOpen('roadName', idx); hideKeyboard()">
  55 + <up-input v-model="item.roadName" readonly disabled-color="#ffffff" placeholder="请先选择工单位置"
  56 + border="none"></up-input>
133 <template #right> 57 <template #right>
134 <up-icon name="arrow-right" size="16"></up-icon> 58 <up-icon name="arrow-right" size="16"></up-icon>
135 </template> 59 </template>
136 </up-form-item> 60 </up-form-item>
137 -  
138 <!-- 紧急程度 --> 61 <!-- 紧急程度 -->
139 - <up-form-item  
140 - label="紧急程度"  
141 - prop="pressingTypeName"  
142 - border-bottom  
143 - required  
144 - @click="handleActionSheetOpen('pressingType', idx); hideKeyboard()"  
145 - >  
146 - <up-input  
147 - v-model="item.pressingTypeName"  
148 - disabled  
149 - disabled-color="#ffffff"  
150 - placeholder="请选择紧急程度"  
151 - border="none"  
152 - ></up-input> 62 + <up-form-item label="紧急程度" prop="pressingTypeName" border-bottom required
  63 + @click="handleActionSheetOpen('pressingType', idx); hideKeyboard()">
  64 + <up-input v-model="item.pressingTypeName" disabled disabled-color="#ffffff" placeholder="请选择紧急程度"
  65 + border="none"></up-input>
153 <template #right> 66 <template #right>
154 <up-icon name="arrow-right" size="16"></up-icon> 67 <up-icon name="arrow-right" size="16"></up-icon>
155 </template> 68 </template>
156 </up-form-item> 69 </up-form-item>
157 -  
158 <!-- 每组派单独立:希望完成时间 --> 70 <!-- 每组派单独立:希望完成时间 -->
159 - <up-form-item  
160 - label="希望完成时间"  
161 -  
162 - @click="openItemDatePicker(idx); hideKeyboard()"  
163 - >  
164 - <up-input  
165 - v-model="item.expectedFinishDate"  
166 - border="none"  
167 - readonly  
168 - placeholder="点击选择时间"  
169 - ></up-input> 71 + <up-form-item label="希望完成时间" @click="openItemDatePicker(idx); hideKeyboard()">
  72 + <up-input v-model="item.expectedFinishDate" border="none" readonly placeholder="点击选择时间"></up-input>
170 <template #right> 73 <template #right>
171 <view v-if="item.expectedFinishDate" @click.stop> 74 <view v-if="item.expectedFinishDate" @click.stop>
172 <up-icon name="close" size="16" @click.stop="clearItemDate(idx)"></up-icon> 75 <up-icon name="close" size="16" @click.stop="clearItemDate(idx)"></up-icon>
@@ -176,81 +79,49 @@ @@ -176,81 +79,49 @@
176 </up-form-item> 79 </up-form-item>
177 </view> 80 </view>
178 </view> 81 </view>
179 -  
180 <!-- 新增派单按钮 --> 82 <!-- 新增派单按钮 -->
181 <view class="add-dispatch-btn-wrap" v-if="!isRenew"> 83 <view class="add-dispatch-btn-wrap" v-if="!isRenew">
182 - <up-button  
183 - type="primary"  
184 - plain  
185 - text="新增派单"  
186 - :disabled="dispatchList.length >= 3"  
187 - @click="addDispatchItem"  
188 - ></up-button> 84 + <up-button type="primary" plain text="新增派单"
  85 + :disabled="dispatchList.length >= 3 || getAvailableBusiLineList().length === 0"
  86 + @click="addDispatchItem"></up-button>
189 </view> 87 </view>
190 </up-form> 88 </up-form>
191 </view> 89 </view>
192 -  
193 <!-- 底部提交按钮 --> 90 <!-- 底部提交按钮 -->
194 <view class="fixed-bottom-btn-wrap"> 91 <view class="fixed-bottom-btn-wrap">
195 <up-button type="primary" text="提交工单" @click="submitWorkOrder"></up-button> 92 <up-button type="primary" text="提交工单" @click="submitWorkOrder"></up-button>
196 </view> 93 </view>
197 -  
198 <!-- 通用下拉弹窗:业务线 / 道路 / 紧急程度 共用 --> 94 <!-- 通用下拉弹窗:业务线 / 道路 / 紧急程度 共用 -->
199 - <up-action-sheet  
200 - :show="showActionSheet"  
201 - :actions="currentActionSheetData.list"  
202 - :title="currentActionSheetData.title"  
203 - @close="handleActionSheetClose"  
204 - @select="handleActionSheetSelect"  
205 - ></up-action-sheet>  
206 - 95 + <up-action-sheet :show="showActionSheet" :actions="currentActionSheetData.list"
  96 + :title="currentActionSheetData.title" @close="handleActionSheetClose"
  97 + @select="handleActionSheetSelect"></up-action-sheet>
207 <!-- 问题类型 级联选择器 --> 98 <!-- 问题类型 级联选择器 -->
208 - <up-cascader  
209 - v-model="cascaderValue"  
210 - :data="cascaderOptions"  
211 - v-model:show="showCascader"  
212 - title="问题类型"  
213 - :auto-close="false"  
214 - @confirm="cascaderConfirm"  
215 - ></up-cascader>  
216 - 99 + <up-cascader v-model="cascaderValue" :data="cascaderOptions" v-model:show="showCascader" title="问题类型"
  100 + :auto-close="false" @confirm="cascaderConfirm"></up-cascader>
217 <!-- 单独时间选择器(给每组派单用) --> 101 <!-- 单独时间选择器(给每组派单用) -->
218 - <up-datetime-picker  
219 - :show="showItemDatePicker"  
220 - v-model="tempDateValue"  
221 - mode="datetime"  
222 - :min-date="Date.now()"  
223 - @cancel="closeItemDatePicker"  
224 - @confirm="itemDateConfirm"  
225 - ></up-datetime-picker> 102 + <up-datetime-picker :show="showItemDatePicker" v-model="tempDateValue" mode="datetime" :min-date="Date.now()"
  103 + @cancel="closeItemDatePicker" @confirm="itemDateConfirm"></up-datetime-picker>
226 </view> 104 </view>
227 </template> 105 </template>
228 -  
229 <script setup> 106 <script setup>
230 -import { ref, reactive, nextTick } from 'vue' 107 +import { ref, reactive, nextTick, computed } from 'vue'
231 import { onReady, onShow, onLoad, onBackPress } from '@dcloudio/uni-app'; 108 import { onReady, onShow, onLoad, onBackPress } from '@dcloudio/uni-app';
232 import { useUploadImgs } from '@/common/utils/useUploadImgs' 109 import { useUploadImgs } from '@/common/utils/useUploadImgs'
233 import { getRoadListByLatLng, getProblemListByType } from '@/api/common' 110 import { getRoadListByLatLng, getProblemListByType } from '@/api/common'
234 import { 111 import {
235 - daquUniversalApproval, qyUniversalApproval, qyWorkorderCreate,dycCreateBatchNew,  
236 - dcyUniversalApproval, daquWorkorderCreate, dcyWorkorderCreate 112 +daquUniversalApproval, qyUniversalApproval, qyWorkorderCreate, dycCreateBatchNew,
  113 +dcyUniversalApproval, daquWorkorderCreate, dcyWorkorderCreate
237 } from '@/api/regional-order-manage/regional-order-manage' 114 } from '@/api/regional-order-manage/regional-order-manage'
238 import { timeFormat } from '@/uni_modules/uview-plus' 115 import { timeFormat } from '@/uni_modules/uview-plus'
239 import { nextStepMap } from '@/common/utils/common' 116 import { nextStepMap } from '@/common/utils/common'
240 import { useUserStore } from '@/pinia/user'; 117 import { useUserStore } from '@/pinia/user';
241 -  
242 // ========== 常量配置区 ========== 118 // ========== 常量配置区 ==========
243 const CONST = { 119 const CONST = {
244 - DICT_PRESSING_TYPE: 'workorder_pressing_type',  
245 - UPLOAD_CONFIG: { maxCount: 3, uploadText: '选择问题照片', sizeType: ['compressed'] },  
246 - PAGE_ORDER_LIST: '/pages-sub/problem/regional-order-manage/index',  
247 - BUSI_LINE_MAP: {  
248 - yl: '园林', sz: '市政', wy: '物业', '园林': 'yl', '市政': 'sz', '物业': 'wy',  
249 - 'zx': '秩序管理', 'hj': '环境卫生', '秩序管理': 'zx', '环境卫生': 'hj'  
250 - },  
251 - ROLE: { MANAGER: 'regional_manager', INSPECTOR: 'Inspector_global', PATROL: 'patrol_global' , LEADER: 'Inspector_global_leader'} 120 +DICT_PRESSING_TYPE: 'workorder_pressing_type',
  121 +UPLOAD_CONFIG: { maxCount: 3, uploadText: '选择问题照片', sizeType: ['compressed'] },
  122 +PAGE_ORDER_LIST: '/pages-sub/problem/regional-order-manage/index',
  123 +ROLE: { MANAGER: 'regional_manager', INSPECTOR: 'Inspector_global', PATROL: 'patrol_global', LEADER: 'Inspector_global_leader' }
252 } 124 }
253 -  
254 // ========== 全局实例 & 基础状态 ========== 125 // ========== 全局实例 & 基础状态 ==========
255 const userStore = useUserStore(); 126 const userStore = useUserStore();
256 const USER_ROLES = userStore.userInfo?.roles || []; 127 const USER_ROLES = userStore.userInfo?.roles || [];
@@ -258,556 +129,554 @@ const workOrderFormRef = ref(null); @@ -258,556 +129,554 @@ const workOrderFormRef = ref(null);
258 const showActionSheet = ref(false); 129 const showActionSheet = ref(false);
259 const isRenew = ref(false); 130 const isRenew = ref(false);
260 const renewOrderData = ref(null); 131 const renewOrderData = ref(null);
261 -  
262 // 当前操作的派单项下标 132 // 当前操作的派单项下标
263 const currentDispatchIndex = ref(0); 133 const currentDispatchIndex = ref(0);
264 -  
265 // 三级级联选择器 134 // 三级级联选择器
266 const showCascader = ref(false); 135 const showCascader = ref(false);
267 const cascaderValue = ref([]); 136 const cascaderValue = ref([]);
268 const cascaderOptions = ref([]); 137 const cascaderOptions = ref([]);
269 -  
270 // 下拉弹窗数据 138 // 下拉弹窗数据
271 const currentActionSheetData = reactive({ type: '', list: [], title: '', index: 0 }); 139 const currentActionSheetData = reactive({ type: '', list: [], title: '', index: 0 });
272 const roadNameList = ref([]); 140 const roadNameList = ref([]);
273 const pressingTypeList = ref([]); 141 const pressingTypeList = ref([]);
274 const busiLineOptions = ref([]); 142 const busiLineOptions = ref([]);
275 const currentBusiLineCode = ref(''); 143 const currentBusiLineCode = ref('');
276 -  
277 // 每组派单独立时间选择器 144 // 每组派单独立时间选择器
278 const showItemDatePicker = ref(false); 145 const showItemDatePicker = ref(false);
279 const tempDateValue = ref(Date.now()); 146 const tempDateValue = ref(Date.now());
280 const curSelectDateIndex = ref(-1); 147 const curSelectDateIndex = ref(-1);
281 -  
282 // 图片上传 148 // 图片上传
283 const problemImgs = useUploadImgs({ 149 const problemImgs = useUploadImgs({
284 - ...CONST.UPLOAD_CONFIG,  
285 - formRef: workOrderFormRef,  
286 - fieldName: 'problemImgs' 150 +...CONST.UPLOAD_CONFIG,
  151 +formRef: workOrderFormRef,
  152 +fieldName: 'problemImgs'
287 }) 153 })
288 if (!Array.isArray(problemImgs.rawImgList.value)) problemImgs.rawImgList.value = []; 154 if (!Array.isArray(problemImgs.rawImgList.value)) problemImgs.rawImgList.value = [];
289 155
  156 +// ===================== 【动态构建业务线双向映射,从 userStore.dictList】=====================
  157 +let busiLineEnToCnMap = {}; // code -> 中文
  158 +let busiLineCnToEnMap = {}; // 中文 -> code
  159 +/**
  160 + * 从dictList构建业务线双向map
  161 + * @param {Array} dictList userStore.dictList
  162 + */
  163 +const buildBusiLineMap = (dictList) => {
  164 + busiLineEnToCnMap = {};
  165 + busiLineCnToEnMap = {};
  166 + if (!Array.isArray(dictList)) return;
  167 + dictList.forEach(item => {
  168 + const code = item.value?.trim();
  169 + const name = item.label?.trim();
  170 + if (code && name) {
  171 + busiLineEnToCnMap[code] = name;
  172 + busiLineCnToEnMap[name] = code;
  173 + }
  174 + })
  175 +}
  176 +// 工具方法(替换原来硬编码BUSI_LINE_MAP)
  177 +const busiLineCnToEn = (cn) => busiLineCnToEnMap[cn] || '';
  178 +const busiLineEnToCn = (en) => busiLineEnToCnMap[en] || '';
  179 +// ==========================================================================
  180 +
  181 +// ===================== 【新增方法:获取已占用业务线code】=====================
  182 +const getUsedBusiCodeList = () => {
  183 +return dispatchList.value
  184 + .filter(item => !!item.busiLineCode)
  185 + .map(item => item.busiLineCode)
  186 +}
  187 +/**
  188 + * 获取【当前可选业务线】:排除已经被其他派单项占用的业务线
  189 + * @param {number} currentIdx 当前正在操作的派单项下标
  190 + */
  191 +const getAvailableBusiLineList = (currentIdx = -1) => {
  192 +const usedCodes = getUsedBusiCodeList()
  193 +return busiLineOptions.value.filter(opt => {
  194 +// 如果是当前这条自己已经选中的业务线,保留;其他派单项占用的过滤掉
  195 +const isSelfSelected = dispatchList.value[currentIdx]?.busiLineCode === opt.value
  196 +const isUsedByOther = usedCodes.includes(opt.value) && !isSelfSelected
  197 +return !isUsedByOther
  198 + })
  199 +}
  200 +// ==========================================================================
290 // ========== 公共表单(所有派单共用) ========== 201 // ========== 公共表单(所有派单共用) ==========
291 const baseForm = reactive({ 202 const baseForm = reactive({
292 - workLocation: '',  
293 - problemDesc: '',  
294 - lat: 0,  
295 - lon: 0 203 +workLocation: '',
  204 +problemDesc: '',
  205 +lat: 0,
  206 +lon: 0
296 }) 207 })
297 -  
298 // ========== 派单列表(多组派单核心数组) ========== 208 // ========== 派单列表(多组派单核心数组) ==========
299 const createDispatchItem = (defaultBusiLine = null) => { 209 const createDispatchItem = (defaultBusiLine = null) => {
300 - return {  
301 - busiLineCn: defaultBusiLine?.name || '',  
302 - busiLineCode: defaultBusiLine?.value || '',  
303 - roadId: 0,  
304 - roadName: '',  
305 - orderName: '',  
306 - orderCode: '',  
307 - pressingType: '',  
308 - pressingTypeName: '',  
309 - expectedFinishDate: '' // 每组独立完成时间 210 +return {
  211 +busiLineCn: defaultBusiLine?.name || '',
  212 +busiLineCode: defaultBusiLine?.value || '',
  213 +roadId: 0,
  214 +roadName: '',
  215 +orderName: '',
  216 +orderCode: '',
  217 +pressingType: '',
  218 +pressingTypeName: '',
  219 +expectedFinishDate: '' // 每组独立完成时间
310 } 220 }
311 } 221 }
312 // 初始化默认 1 组派单 222 // 初始化默认 1 组派单
313 const dispatchList = ref([createDispatchItem()]) 223 const dispatchList = ref([createDispatchItem()])
314 -  
315 // ========== 表单校验规则 ========== 224 // ========== 表单校验规则 ==========
316 const workOrderFormRules = reactive({ 225 const workOrderFormRules = reactive({
317 - workLocation: [{ type: 'string', required: true, message: '请选择工单位置', trigger: ['change', 'blur'] }],  
318 - problemDesc: [ 226 +workLocation: [{ type: 'string', required: true, message: '请选择工单位置', trigger: ['change', 'blur'] }],
  227 +problemDesc: [
319 { type: 'string', required: true, message: '请输入情况描述', trigger: ['change', 'blur'] }, 228 { type: 'string', required: true, message: '请输入情况描述', trigger: ['change', 'blur'] },
320 { type: 'string', min: 3, max: 200, message: '情况描述需3-200字', trigger: ['change', 'blur'] } 229 { type: 'string', min: 3, max: 200, message: '情况描述需3-200字', trigger: ['change', 'blur'] }
321 ], 230 ],
322 - problemImgs: [problemImgs.imgValidateRule] 231 +problemImgs: [problemImgs.imgValidateRule]
323 }) 232 })
324 -  
325 // ========== 工具方法 ========== 233 // ========== 工具方法 ==========
326 -const busiLineCnToEn = (cn) => CONST.BUSI_LINE_MAP[cn] || '';  
327 -const busiLineEnToCn = (en) => CONST.BUSI_LINE_MAP[en] || '';  
328 const toOrderListPage = () => uni.reLaunch({ url: CONST.PAGE_ORDER_LIST }); 234 const toOrderListPage = () => uni.reLaunch({ url: CONST.PAGE_ORDER_LIST });
329 const formatDate = (dateVal, fmt = 'yyyy-mm-dd hh:MM:ss') => dateVal ? timeFormat(dateVal, fmt) : ''; 235 const formatDate = (dateVal, fmt = 'yyyy-mm-dd hh:MM:ss') => dateVal ? timeFormat(dateVal, fmt) : '';
330 const dateToTimestamp = (dateStr) => dateStr ? new Date(dateStr).getTime() : ''; 236 const dateToTimestamp = (dateStr) => dateStr ? new Date(dateStr).getTime() : '';
331 -  
332 /** 237 /**
333 * 加载三级问题类型级联数据 238 * 加载三级问题类型级联数据
334 */ 239 */
335 const loadProblemCascader = async (parentCode) => { 240 const loadProblemCascader = async (parentCode) => {
336 - if (!parentCode) {  
337 - cascaderOptions.value = [];  
338 - return; 241 +if (!parentCode) {
  242 +cascaderOptions.value = [];
  243 +return;
339 } 244 }
340 - try {  
341 - uni.showLoading({ title: '加载类型中...' });  
342 - const res = await getProblemListByType({ parentCode });  
343 - cascaderOptions.value = res|| []; 245 +try {
  246 +uni.showLoading({ title: '加载类型中...' });
  247 +const res = await getProblemListByType({ parentCode });
  248 +cascaderOptions.value = res || [];
344 } catch (err) { 249 } catch (err) {
345 - console.error('加载问题类型失败', err);  
346 - uni.showToast({ title: '问题类型加载失败', icon: 'none' });  
347 - cascaderOptions.value = []; 250 +console.error('加载问题类型失败', err);
  251 +uni.showToast({ title: '问题类型加载失败', icon: 'none' });
  252 +cascaderOptions.value = [];
348 } finally { 253 } finally {
349 - uni.hideLoading(); 254 +uni.hideLoading();
350 } 255 }
351 }; 256 };
352 -  
353 /** 打开级联选择器 */ 257 /** 打开级联选择器 */
354 const openCascader = (idx) => { 258 const openCascader = (idx) => {
355 - currentDispatchIndex.value = idx;  
356 - const item = dispatchList.value[idx];  
357 - if (!item.busiLineCode) {  
358 - uni.showToast({ title: '请先选择业务线', icon: 'none' });  
359 - return; 259 +currentDispatchIndex.value = idx;
  260 +const item = dispatchList.value[idx];
  261 +if (!item.busiLineCode) {
  262 +uni.showToast({ title: '请先选择业务线', icon: 'none' });
  263 +return;
360 } 264 }
361 - if (!cascaderOptions.value || cascaderOptions.value.length === 0) {  
362 - uni.showToast({ title: '正在加载问题类型,请稍等', icon: 'none' });  
363 - loadProblemCascader(item.busiLineCode);  
364 - return; 265 +if (!cascaderOptions.value || cascaderOptions.value.length === 0) {
  266 +uni.showToast({ title: '正在加载问题类型,请稍等', icon: 'none' });
  267 +loadProblemCascader(item.busiLineCode);
  268 +return;
365 } 269 }
366 - cascaderValue.value = [];  
367 - showCascader.value = true; 270 +cascaderValue.value = [];
  271 +showCascader.value = true;
368 }; 272 };
369 -  
370 /** 级联选择确认回调 */ 273 /** 级联选择确认回调 */
371 const cascaderConfirm = (selectedArr) => { 274 const cascaderConfirm = (selectedArr) => {
372 - const idx = currentDispatchIndex.value;  
373 - const item = dispatchList.value[idx];  
374 - console.log('最终选中值数组:', selectedArr);  
375 -  
376 - const getLabelByValueList = (list, valList) => {  
377 - const labelArr = [];  
378 - let currentList = list;  
379 - for (const val of valList) {  
380 - const target = currentList.find(item => item.value === val);  
381 - if (!target) break;  
382 - labelArr.push(target.label);  
383 - currentList = target.children || []; 275 +const idx = currentDispatchIndex.value;
  276 +const item = dispatchList.value[idx];
  277 +console.log('最终选中值数组:', selectedArr);
  278 +const getLabelByValueList = (list, valList) => {
  279 +const labelArr = [];
  280 +let currentList = list;
  281 +for (const val of valList) {
  282 +const target = currentList.find(item => item.value === val);
  283 +if (!target) break;
  284 +labelArr.push(target.label);
  285 +currentList = target.children || [];
384 } 286 }
385 - return labelArr.join('/'); 287 +return labelArr.join('/');
386 }; 288 };
387 -  
388 - const fullLabel = getLabelByValueList(cascaderOptions.value, selectedArr);  
389 - item.orderName = fullLabel;  
390 - item.orderCode = selectedArr.at(-1);  
391 - cascaderValue.value = selectedArr;  
392 - workOrderFormRef.value?.validateField('orderName'); 289 +const fullLabel = getLabelByValueList(cascaderOptions.value, selectedArr);
  290 +item.orderName = fullLabel;
  291 +item.orderCode = selectedArr.at(-1);
  292 +cascaderValue.value = selectedArr;
  293 +workOrderFormRef.value?.validateField('orderName');
393 }; 294 };
394 -  
395 // ========== 每组派单 时间选择相关 ========== 295 // ========== 每组派单 时间选择相关 ==========
396 const openItemDatePicker = (idx) => { 296 const openItemDatePicker = (idx) => {
397 - curSelectDateIndex.value = idx;  
398 - tempDateValue.value = Date.now();  
399 - showItemDatePicker.value = true; 297 +curSelectDateIndex.value = idx;
  298 +tempDateValue.value = Date.now();
  299 +showItemDatePicker.value = true;
400 }; 300 };
401 const closeItemDatePicker = () => { 301 const closeItemDatePicker = () => {
402 - showItemDatePicker.value = false;  
403 - curSelectDateIndex.value = -1; 302 +showItemDatePicker.value = false;
  303 +curSelectDateIndex.value = -1;
404 }; 304 };
405 const itemDateConfirm = (e) => { 305 const itemDateConfirm = (e) => {
406 - const idx = curSelectDateIndex.value;  
407 - if (idx > -1 && dispatchList.value[idx]) {  
408 - dispatchList.value[idx].expectedFinishDate = formatDate(e.value); 306 +const idx = curSelectDateIndex.value;
  307 +if (idx > -1 && dispatchList.value[idx]) {
  308 +dispatchList.value[idx].expectedFinishDate = formatDate(e.value);
409 } 309 }
410 - closeItemDatePicker(); 310 +closeItemDatePicker();
411 }; 311 };
412 const clearItemDate = (idx) => { 312 const clearItemDate = (idx) => {
413 - dispatchList.value[idx].expectedFinishDate = ''; 313 +dispatchList.value[idx].expectedFinishDate = '';
414 }; 314 };
415 -  
416 // ========== 新增 / 删除 派单项 ========== 315 // ========== 新增 / 删除 派单项 ==========
417 /** 新增派单 */ 316 /** 新增派单 */
418 const addDispatchItem = () => { 317 const addDispatchItem = () => {
419 - if (dispatchList.value.length >= 3) return;  
420 - // 新项:业务线默认第一个,其余置空  
421 - const firstBusi = busiLineOptions.value[0] || null;  
422 - dispatchList.value.push(createDispatchItem(firstBusi));  
423 - if(firstBusi) {  
424 - currentBusiLineCode.value = firstBusi.value;  
425 - loadProblemCascader(firstBusi.value); 318 +if (dispatchList.value.length >= 3) return;
  319 +// 【修改】新增取剩下可用业务线第一条
  320 +const availList = getAvailableBusiLineList()
  321 +const firstBusi = availList[0] || null;
  322 +dispatchList.value.push(createDispatchItem(firstBusi));
  323 +if (firstBusi) {
  324 +currentBusiLineCode.value = firstBusi.value;
  325 +loadProblemCascader(firstBusi.value);
426 } 326 }
427 }; 327 };
428 -  
429 /** 删除派单项 */ 328 /** 删除派单项 */
430 const delDispatchItem = (idx) => { 329 const delDispatchItem = (idx) => {
431 - if (dispatchList.value.length <= 1) return;  
432 - dispatchList.value.splice(idx, 1); 330 +if (dispatchList.value.length <= 1) return;
  331 +dispatchList.value.splice(idx, 1);
433 }; 332 };
434 -  
435 -  
436 // 生命周期 333 // 生命周期
437 onLoad(async (options) => { 334 onLoad(async (options) => {
438 - initBusiLineOptions();  
439 - // 初始化第一组派单默认业务线  
440 - if (busiLineOptions.value.length) {  
441 - const firstLine = busiLineOptions.value[0];  
442 - dispatchList.value[0].busiLineCn = firstLine.name;  
443 - dispatchList.value[0].busiLineCode = firstLine.value;  
444 - currentBusiLineCode.value = firstLine.value;  
445 - // 非重新提交才默认加载级联  
446 - if (!(options.isRenew == 1 && options.tempKey)) {  
447 - loadProblemCascader(firstLine.value); 335 +initBusiLineOptions();
  336 +// 初始化第一组派单默认业务线
  337 +if (busiLineOptions.value.length) {
  338 +const availList = getAvailableBusiLineList(0)
  339 +const firstLine = availList[0];
  340 +dispatchList.value[0].busiLineCn = firstLine.name;
  341 +dispatchList.value[0].busiLineCode = firstLine.value;
  342 +currentBusiLineCode.value = firstLine.value;
  343 +// 非重新提交才默认加载级联
  344 +if (!(options.isRenew == 1 && options.tempKey)) {
  345 +loadProblemCascader(firstLine.value);
448 } 346 }
449 } 347 }
450 - // 重新提交场景:等待回显完成  
451 - if (options.isRenew == 1 && options.tempKey) {  
452 - await handleRenewOptions(options.tempKey); 348 +// 重新提交场景:等待回显完成
  349 +if (options.isRenew == 1 && options.tempKey) {
  350 +await handleRenewOptions(options.tempKey);
453 } 351 }
454 }); 352 });
455 -  
456 onReady(() => { 353 onReady(() => {
457 - nextTick(() => {  
458 - workOrderFormRef.value?.setRules(workOrderFormRules); 354 +nextTick(() => {
  355 +workOrderFormRef.value?.setRules(workOrderFormRules);
459 }); 356 });
460 }); 357 });
461 -  
462 onShow(() => { 358 onShow(() => {
463 - pressingTypeList.value = uni.$dict.transformLabelValueToNameValue(uni.$dict.getDictSimpleList(CONST.DICT_PRESSING_TYPE)); 359 +pressingTypeList.value = uni.$dict.transformLabelValueToNameValue(uni.$dict.getDictSimpleList(CONST.DICT_PRESSING_TYPE));
464 }); 360 });
465 -  
466 // 物理返回拦截 361 // 物理返回拦截
467 onBackPress(() => { 362 onBackPress(() => {
468 - uni.redirectTo({ url: CONST.PAGE_ORDER_LIST });  
469 - return true; 363 +uni.redirectTo({ url: CONST.PAGE_ORDER_LIST });
  364 +return true;
470 }); 365 });
471 -  
472 // ========== 业务线相关 ========== 366 // ========== 业务线相关 ==========
473 const initBusiLineOptions = () => { 367 const initBusiLineOptions = () => {
474 - const rawBusiLines = userStore.userInfo?.user?.busiLine?.split(',') || [];  
475 - busiLineOptions.value = rawBusiLines.map(item => ({  
476 - name: busiLineEnToCn(item.trim()),  
477 - value: item.trim() 368 +// 从userStore.dictList构建双向映射
  369 +buildBusiLineMap(userStore.dictList);
  370 +// 用户拥有的业务线code数组
  371 +const rawBusiLines = userStore.userInfo?.user?.busiLine?.split(',') || [];
  372 +// 过滤dict,只保留用户有权限的业务线,组装 {name, value}
  373 +busiLineOptions.value = userStore.dictList
  374 + .filter(dictItem => rawBusiLines.includes(dictItem.value))
  375 + .map(dictItem => ({
  376 + name: dictItem.label,
  377 + value: dictItem.value
478 })); 378 }));
479 }; 379 };
480 -  
481 /** 业务线切换联动清空数据并重新加载级联 */ 380 /** 业务线切换联动清空数据并重新加载级联 */
482 const handleBusiLineChange = (code, idx) => { 381 const handleBusiLineChange = (code, idx) => {
483 - const item = dispatchList.value[idx];  
484 - item.roadName = '';  
485 - item.roadId = 0;  
486 - item.orderName = '';  
487 - item.orderCode = '';  
488 - cascaderValue.value = [];  
489 - roadNameList.value = [];  
490 -  
491 - currentBusiLineCode.value = code;  
492 - loadProblemCascader(code);  
493 -  
494 - if(baseForm.workLocation) getRoadListByBusiLine(); 382 +const item = dispatchList.value[idx];
  383 +item.roadName = '';
  384 +item.roadId = 0;
  385 +item.orderName = '';
  386 +item.orderCode = '';
  387 +cascaderValue.value = [];
  388 +roadNameList.value = [];
  389 +currentBusiLineCode.value = code;
  390 +loadProblemCascader(code);
  391 +if (baseForm.workLocation) getRoadListByBusiLine();
495 }; 392 };
496 -  
497 /** 根据经纬度+业务线获取道路列表 */ 393 /** 根据经纬度+业务线获取道路列表 */
498 const getRoadListByBusiLine = async () => { 394 const getRoadListByBusiLine = async () => {
499 - if (!baseForm.lat || !baseForm.lon) return;  
500 - const busiLineEn = busiLineCnToEn(dispatchList.value[0].busiLineCn);  
501 - if (!busiLineEn) return uni.showToast({ title: '业务线选择异常', icon: 'none' });  
502 -  
503 - try {  
504 - uni.showLoading({ title: '获取道路名称中...' });  
505 - const roadRes = await getRoadListByLatLng({  
506 - busiLine: busiLineEn,  
507 - latitude: baseForm.lat,  
508 - longitude: baseForm.lon 395 +if (!baseForm.lat || !baseForm.lon) return;
  396 +const busiLineEn = busiLineCnToEn(dispatchList.value[0].busiLineCn);
  397 +if (!busiLineEn) return uni.showToast({ title: '业务线选择异常', icon: 'none' });
  398 +try {
  399 +uni.showLoading({ title: '获取道路名称中...' });
  400 +const roadRes = await getRoadListByLatLng({
  401 +busiLine: busiLineEn,
  402 +latitude: baseForm.lat,
  403 +longitude: baseForm.lon
509 }); 404 });
510 - roadNameList.value = Array.isArray(roadRes)  
511 - ? roadRes.map(item => ({ name: item.roadName || '', code: item.roadCode || '', id: item.roadId || 0 }))  
512 - : [{ name: '未查询到道路名称', code: '', id: 0 }];  
513 - !Array.isArray(roadRes) && uni.showToast({ title: '未查询到该位置的道路信息', icon: 'none' }); 405 +roadNameList.value = Array.isArray(roadRes)
  406 +? roadRes.map(item => ({ name: item.roadName || '', code: item.roadCode || '', id: item.roadId || 0 }))
  407 +: [{ name: '未查询到道路名称', code: '', id: 0 }];
  408 +!Array.isArray(roadRes) && uni.showToast({ title: '未查询到该位置的道路信息', icon: 'none' });
514 } catch (err) { 409 } catch (err) {
515 - console.error('获取道路名称失败:', err);  
516 - roadNameList.value = [{ name: '获取失败,请重新选择位置', code: '', id: 0 }];  
517 - uni.showToast({ title: '获取道路名称失败,请重试', icon: 'none' }); 410 +console.error('获取道路名称失败:', err);
  411 +roadNameList.value = [{ name: '获取失败,请重新选择位置', code: '', id: 0 }];
  412 +uni.showToast({ title: '获取道路名称失败,请重试', icon: 'none' });
518 } finally { 413 } finally {
519 - uni.hideLoading(); 414 +uni.hideLoading();
520 } 415 }
521 }; 416 };
522 -  
523 // ========== 通用下拉弹窗方法(支持下标) ========== 417 // ========== 通用下拉弹窗方法(支持下标) ==========
524 const handleActionSheetOpen = (type, idx) => { 418 const handleActionSheetOpen = (type, idx) => {
525 - currentDispatchIndex.value = idx;  
526 - if (type === 'roadName' && !baseForm.workLocation) {  
527 - return uni.showToast({ title: '请先选择工单位置', icon: 'none' }); 419 +currentDispatchIndex.value = idx;
  420 +if (type === 'roadName' && !baseForm.workLocation) {
  421 +return uni.showToast({ title: '请先选择工单位置', icon: 'none' });
528 } 422 }
529 - const configMap = {  
530 - busiLine: { title: '请选择业务线', list: busiLineOptions.value },  
531 - roadName: { title: '请选择道路名称', list: roadNameList.value },  
532 - pressingType: { title: '请选择紧急程度', list: pressingTypeList.value } 423 +const configMap = {
  424 +// 【重点改动:业务线弹窗,传入当前下标,过滤掉已占用业务线】
  425 +busiLine: { title: '请选择业务线', list: getAvailableBusiLineList(idx) },
  426 +roadName: { title: '请选择道路名称', list: roadNameList.value },
  427 +pressingType: { title: '请选择紧急程度', list: pressingTypeList.value }
533 }; 428 };
534 - Object.assign(currentActionSheetData, configMap[type], { type, index: idx });  
535 - showActionSheet.value = true; 429 +Object.assign(currentActionSheetData, configMap[type], { type, index: idx });
  430 +showActionSheet.value = true;
536 }; 431 };
537 -  
538 const handleActionSheetClose = () => { 432 const handleActionSheetClose = () => {
539 - showActionSheet.value = false;  
540 - Object.assign(currentActionSheetData, { type: '', list: [], title: '', index: 0 }); 433 +showActionSheet.value = false;
  434 +Object.assign(currentActionSheetData, { type: '', list: [], title: '', index: 0 });
541 }; 435 };
542 -  
543 const handleActionSheetSelect = (e) => { 436 const handleActionSheetSelect = (e) => {
544 - const { type, index } = currentActionSheetData;  
545 - const item = dispatchList.value[index];  
546 - switch (type) {  
547 - case 'busiLine':  
548 - item.busiLineCn = e.name;  
549 - item.busiLineCode = e.value;  
550 - handleBusiLineChange(e.value, index);  
551 - break;  
552 - case 'roadName':  
553 - item.roadName = e.name;  
554 - item.roadId = e.code;  
555 - console.log('选中道路对象e:', e)  
556 - console.log('赋值后 roadId:', item.roadName, '类型:', typeof item.roadId)  
557 - break;  
558 - case 'pressingType':  
559 - item.pressingType = e.value;  
560 - item.pressingTypeName = e.name;  
561 - break; 437 +const { type, index } = currentActionSheetData;
  438 +const item = dispatchList.value[index];
  439 +switch (type) {
  440 +case 'busiLine':
  441 +item.busiLineCn = e.name;
  442 +item.busiLineCode = e.value;
  443 +handleBusiLineChange(e.value, index);
  444 +break;
  445 +case 'roadName':
  446 +item.roadName = e.name;
  447 +item.roadId = e.code;
  448 +console.log('选中道路对象e:', e)
  449 +console.log('赋值后 roadId:', item.roadName, '类型:', typeof item.roadId)
  450 +break;
  451 +case 'pressingType':
  452 +item.pressingType = e.value;
  453 +item.pressingTypeName = e.name;
  454 +break;
562 } 455 }
563 - handleActionSheetClose(); 456 +handleActionSheetClose();
564 }; 457 };
565 -  
566 // ========== 页面交互方法 ========== 458 // ========== 页面交互方法 ==========
567 const hideKeyboard = () => uni.hideKeyboard(); 459 const hideKeyboard = () => uni.hideKeyboard();
568 -  
569 /** 选择地图位置 */ 460 /** 选择地图位置 */
570 const chooseWorkLocation = () => { 461 const chooseWorkLocation = () => {
571 - uni.chooseLocation({  
572 - success: async (res) => {  
573 - baseForm.workLocation = res.name;  
574 - baseForm.lat = res.latitude;  
575 - baseForm.lon = res.longitude;  
576 - // 清空所有组道路  
577 - dispatchList.value.forEach(item => {  
578 - item.roadName = '';  
579 - item.roadId = 0; 462 +uni.chooseLocation({
  463 +success: async (res) => {
  464 +baseForm.workLocation = res.name;
  465 +baseForm.lat = res.latitude;
  466 +baseForm.lon = res.longitude;
  467 +// 清空所有组道路
  468 +dispatchList.value.forEach(item => {
  469 +item.roadName = '';
  470 +item.roadId = 0;
580 }) 471 })
581 - workOrderFormRef.value?.validateField(['workLocation']);  
582 - await getRoadListByBusiLine(); 472 +workOrderFormRef.value?.validateField(['workLocation']);
  473 +await getRoadListByBusiLine();
583 }, 474 },
584 - fail: (err) => {  
585 - console.error('选择位置失败:', err);  
586 - const errMsg = err.errMsg.includes('auth deny') ? '请开启位置权限后重试' : '选择位置失败,请重试';  
587 - uni.showToast({ title: errMsg, icon: 'none' }); 475 +fail: (err) => {
  476 +console.error('选择位置失败:', err);
  477 +const errMsg = err.errMsg.includes('auth deny') ? '请开启位置权限后重试' : '选择位置失败,请重试';
  478 +uni.showToast({ title: errMsg, icon: 'none' });
588 } 479 }
589 }) 480 })
590 }; 481 };
591 -  
592 /** 重新提交数据回显 */ 482 /** 重新提交数据回显 */
593 const handleRenewOptions = async (tempKey) => { 483 const handleRenewOptions = async (tempKey) => {
594 - isRenew.value = true;  
595 - try {  
596 - const orderData = uni.getStorageSync(tempKey);  
597 - if (orderData && typeof orderData === 'object') {  
598 - renewOrderData.value = orderData;  
599 - await echoOrderData(orderData); // 等待回显执行完毕 484 +isRenew.value = true;
  485 +try {
  486 +const orderData = uni.getStorageSync(tempKey);
  487 +if (orderData && typeof orderData === 'object') {
  488 +renewOrderData.value = orderData;
  489 +await echoOrderData(orderData); // 等待回显执行完毕
600 } else { 490 } else {
601 - uni.showToast({ title: '工单数据不存在,无法重新提交', icon: 'none' });  
602 - setTimeout(toOrderListPage, 1000); 491 +uni.showToast({ title: '工单数据不存在,无法重新提交', icon: 'none' });
  492 +setTimeout(toOrderListPage, 1000);
603 } 493 }
604 } catch (error) { 494 } catch (error) {
605 - console.error('读取工单数据失败:', error);  
606 - uni.showToast({ title: '数据读取异常,无法重新提交', icon: 'none' });  
607 - setTimeout(toOrderListPage, 1000); 495 +console.error('读取工单数据失败:', error);
  496 +uni.showToast({ title: '数据读取异常,无法重新提交', icon: 'none' });
  497 +setTimeout(toOrderListPage, 1000);
608 } finally { 498 } finally {
609 - uni.removeStorageSync(tempKey); 499 +uni.removeStorageSync(tempKey);
610 } 500 }
611 }; 501 };
612 -  
613 -  
614 /** 工单数据回显 */ 502 /** 工单数据回显 */
615 const echoOrderData = async (orderItem) => { 503 const echoOrderData = async (orderItem) => {
616 - const busiCode = orderItem.busiLine || '';  
617 - // 回显公共字段  
618 - baseForm.workLocation = orderItem.lonLatAddress || orderItem.roadName || '';  
619 - baseForm.problemDesc = orderItem.remark || '';  
620 - baseForm.lat = orderItem.lat || 0;  
621 - baseForm.lon = orderItem.lon || 0;  
622 - console.log(orderItem)  
623 - console.log(orderItem.orderCode)  
624 -  
625 - // 回显第一组派单  
626 - if(dispatchList.value[0]){  
627 - const firstItem = dispatchList.value[0]  
628 - firstItem.busiLineCn = busiLineEnToCn(busiCode);  
629 - firstItem.busiLineCode = busiCode;  
630 - firstItem.roadId = orderItem.roadId || 0;  
631 -  
632 - firstItem.roadName = orderItem.roadName || '';  
633 - firstItem.orderName = orderItem.orderName || '';  
634 - firstItem.orderCode = orderItem.orderCode || ''; // 回显编码  
635 - firstItem.pressingType = orderItem.pressingType || '';  
636 - firstItem.pressingTypeName = uni.$dict.getDictLabel(CONST.DICT_PRESSING_TYPE, orderItem.pressingType) || '';  
637 - firstItem.expectedFinishDate = orderItem.expectedFinishDate ? formatDate(orderItem.expectedFinishDate) : ''; 504 +const busiCode = orderItem.busiLine || '';
  505 +// 回显公共字段
  506 +baseForm.workLocation = orderItem.lonLatAddress || orderItem.roadName || '';
  507 +baseForm.problemDesc = orderItem.remark || '';
  508 +baseForm.lat = orderItem.lat || 0;
  509 +baseForm.lon = orderItem.lon || 0;
  510 +console.log(orderItem)
  511 +console.log(orderItem.orderCode)
  512 +// 回显第一组派单
  513 +if (dispatchList.value[0]) {
  514 +const firstItem = dispatchList.value[0]
  515 +firstItem.busiLineCn = busiLineEnToCn(busiCode);
  516 +firstItem.busiLineCode = busiCode;
  517 +firstItem.roadId = orderItem.roadId || 0;
  518 +firstItem.roadName = orderItem.roadName || '';
  519 +firstItem.orderName = orderItem.orderName || '';
  520 +firstItem.orderCode = orderItem.orderCode || ''; // 回显编码
  521 +firstItem.pressingType = orderItem.pressingType || '';
  522 +firstItem.pressingTypeName = uni.$dict.getDictLabel(CONST.DICT_PRESSING_TYPE, orderItem.pressingType) || '';
  523 +firstItem.expectedFinishDate = orderItem.expectedFinishDate ? formatDate(orderItem.expectedFinishDate) : '';
638 } 524 }
639 -  
640 - // 图片回显  
641 - if (Array.isArray(orderItem.problemsImgs) && orderItem.problemsImgs.length) {  
642 - const imgList = orderItem.problemsImgs.map((url, idx) => ({ url, name: `renew_img_${idx}`, status: 'success' }));  
643 - problemImgs.imgList.value = imgList;  
644 - problemImgs.rawImgList.value = imgList; 525 +// 图片回显
  526 +if (Array.isArray(orderItem.problemsImgs) && orderItem.problemsImgs.length) {
  527 +const imgList = orderItem.problemsImgs.map((url, idx) => ({ url, name: `renew_img_${idx}`, status: 'success' }));
  528 +problemImgs.imgList.value = imgList;
  529 +problemImgs.rawImgList.value = imgList;
645 } 530 }
646 -  
647 - // ========== 关键修复:业务线加载完级联数据后,回填 cascaderValue ==========  
648 - if (busiCode) {  
649 - // 先加载级联选项  
650 - await loadProblemCascader(busiCode);  
651 - // 根据回显的 orderCode 反向匹配级联路径,赋值给 cascaderValue  
652 - const targetCode = orderItem.orderCode  
653 - if (targetCode && cascaderOptions.value.length) {  
654 - // 递归查找级联选中路径  
655 - const findCascaderPath = (list, targetVal, path = []) => {  
656 - for (const node of list) {  
657 - const newPath = [...path, node.value]  
658 - if (node.value === targetVal) {  
659 - return newPath 531 +// ========== 关键修复:业务线加载完级联数据后,回填 cascaderValue ==========
  532 +if (busiCode) {
  533 +// 先加载级联选项
  534 +await loadProblemCascader(busiCode);
  535 +// 根据回显的 orderCode 反向匹配级联路径,赋值给 cascaderValue
  536 +const targetCode = orderItem.orderCode
  537 +if (targetCode && cascaderOptions.value.length) {
  538 +// 递归查找级联选中路径
  539 +const findCascaderPath = (list, targetVal, path = []) => {
  540 +for (const node of list) {
  541 +const newPath = [...path, node.value]
  542 +if (node.value === targetVal) {
  543 +return newPath
660 } 544 }
661 - if (node.children && node.children.length) {  
662 - const res = findCascaderPath(node.children, targetVal, newPath)  
663 - if (res) return res 545 +if (node.children && node.children.length) {
  546 +const res = findCascaderPath(node.children, targetVal, newPath)
  547 +if (res) return res
664 } 548 }
665 } 549 }
666 - return null 550 +return null
667 } 551 }
668 - const selectPath = findCascaderPath(cascaderOptions.value, targetCode)  
669 - if (selectPath) {  
670 - cascaderValue.value = selectPath 552 +const selectPath = findCascaderPath(cascaderOptions.value, targetCode)
  553 +if (selectPath) {
  554 +cascaderValue.value = selectPath
671 } 555 }
672 } 556 }
673 } 557 }
674 -  
675 - if (orderItem.lat && orderItem.lon) getRoadListByBusiLine(); 558 +if (orderItem.lat && orderItem.lon) getRoadListByBusiLine();
676 }; 559 };
677 -  
678 // ========== 接口分发(按角色) ========== 560 // ========== 接口分发(按角色) ==========
679 const callApiByRole = async (params, isRenewFlag) => { 561 const callApiByRole = async (params, isRenewFlag) => {
680 - if (isRenewFlag) {  
681 - if (USER_ROLES.includes(CONST.ROLE.MANAGER)) return await daquUniversalApproval(params);  
682 - if (USER_ROLES.includes(CONST.ROLE.INSPECTOR)) return await dcyUniversalApproval(params);  
683 - if (USER_ROLES.includes(CONST.ROLE.LEADER)) return await dcyUniversalApproval(params);  
684 - if (USER_ROLES.includes(CONST.ROLE.PATROL)) return await qyUniversalApproval(params); 562 +if (isRenewFlag) {
  563 +if (USER_ROLES.includes(CONST.ROLE.MANAGER)) return await daquUniversalApproval(params);
  564 +if (USER_ROLES.includes(CONST.ROLE.INSPECTOR)) return await dcyUniversalApproval(params);
  565 +if (USER_ROLES.includes(CONST.ROLE.LEADER)) return await dcyUniversalApproval(params);
  566 +if (USER_ROLES.includes(CONST.ROLE.PATROL)) return await qyUniversalApproval(params);
685 } else { 567 } else {
686 - console.log('dasda')  
687 - console.log(CONST.ROLE.INSPECTOR)  
688 - if (USER_ROLES.includes(CONST.ROLE.MANAGER)) return await daquWorkorderCreate(params);  
689 - // if (USER_ROLES.includes(CONST.ROLE.INSPECTOR)) return await dcyWorkorderCreate(params);  
690 - if (USER_ROLES.includes(CONST.ROLE.INSPECTOR)) return await dycCreateBatchNew(params);  
691 - if (USER_ROLES.includes(CONST.ROLE.LEADER)) return await dycCreateBatchNew(params);  
692 - if (USER_ROLES.includes(CONST.ROLE.PATROL)) return await qyWorkorderCreate(params);  
693 -  
694 - 568 +console.log('dasda')
  569 +console.log(CONST.ROLE.INSPECTOR)
  570 +if (USER_ROLES.includes(CONST.ROLE.MANAGER)) return await daquWorkorderCreate(params);
  571 +// if (USER_ROLES.includes(CONST.ROLE.INSPECTOR)) return await dcyWorkorderCreate(params);
  572 +if (USER_ROLES.includes(CONST.ROLE.INSPECTOR)) return await dycCreateBatchNew(params);
  573 +if (USER_ROLES.includes(CONST.ROLE.LEADER)) return await dycCreateBatchNew(params);
  574 +if (USER_ROLES.includes(CONST.ROLE.PATROL)) return await qyWorkorderCreate(params);
695 } 575 }
696 }; 576 };
697 -  
698 // ========== 提交工单(纯数组提交,无 dispatchList,校验 roadId 非0) ========== 577 // ========== 提交工单(纯数组提交,无 dispatchList,校验 roadId 非0) ==========
699 const submitWorkOrder = async () => { 578 const submitWorkOrder = async () => {
700 - try {  
701 - // 校验公共表单  
702 - await workOrderFormRef.value.validate();  
703 -  
704 - // 校验:同一业务线 + 同一问题类型 不允许重复 579 +try {
  580 +// 校验公共表单
  581 +await workOrderFormRef.value.validate();
  582 +// 校验:同一业务线 + 同一问题类型 不允许重复
705 const list = dispatchList.value; 583 const list = dispatchList.value;
706 const keySet = new Set(); 584 const keySet = new Set();
707 let hasRepeat = false; 585 let hasRepeat = false;
708 -  
709 for (const item of list) { 586 for (const item of list) {
710 - const busi = (item.busiLineCn || '').trim();  
711 - const type = (item.orderName || '').trim();  
712 - // 拼接唯一标识  
713 - const uniqueKey = `${busi}_${type}`;  
714 - if (keySet.has(uniqueKey)) {  
715 - hasRepeat = true;  
716 - break;  
717 - }  
718 - keySet.add(uniqueKey);  
719 -}  
720 - 587 +const busi = (item.busiLineCn || '').trim();
  588 +const type = (item.orderName || '').trim();
  589 +// 拼接唯一标识
  590 +const uniqueKey = `${busi}_${type}`;
  591 +if (keySet.has(uniqueKey)) {
  592 +hasRepeat = true;
  593 +break;
  594 + }
  595 +keySet.add(uniqueKey);
  596 + }
721 if (hasRepeat) { 597 if (hasRepeat) {
722 - uni.showToast({  
723 - title: '存在同业务同问题类型,请修改后再提交',  
724 - icon: 'none'  
725 - });  
726 - return;  
727 -}  
728 - // ==============================================  
729 -  
730 - // 组装纯数组,不再包裹 dispatchList  
731 - const submitArr = dispatchList.value.map(item => {  
732 - return {  
733 - roadId: item.roadId,  
734 - roadName: item.roadName,  
735 - orderName: item.orderName,  
736 - orderCode: item.orderCode,  
737 - pressingType: item.pressingType,  
738 - busiLine: busiLineCnToEn(item.busiLineCn),  
739 - busiType: busiLineCnToEn(item.busiLineCn),  
740 - expectedFinishDate: dateToTimestamp(item.expectedFinishDate),  
741 - problemsImgs: problemImgs.getSuccessImgUrls(),  
742 - remark: baseForm.problemDesc.trim(),  
743 - latLonType: 2,  
744 - lat: baseForm.lat,  
745 - lon: baseForm.lon,  
746 - lonLatAddress: baseForm.workLocation,  
747 - sourceId: 1,  
748 - sourceName: item.busiLineCn 598 +uni.showToast({
  599 +title: '存在同业务同问题类型,请修改后再提交',
  600 +icon: 'none'
  601 + });
  602 +return;
  603 + }
  604 +// ==============================================
  605 +// 组装纯数组,不再包裹 dispatchList
  606 +const submitArr = dispatchList.value.map(item => {
  607 +return {
  608 +roadId: item.roadId,
  609 +roadName: item.roadName,
  610 +orderName: item.orderName,
  611 +orderCode: item.orderCode,
  612 +pressingType: item.pressingType,
  613 +busiLine: busiLineCnToEn(item.busiLineCn),
  614 +busiType: busiLineCnToEn(item.busiLineCn),
  615 +expectedFinishDate: dateToTimestamp(item.expectedFinishDate),
  616 +problemsImgs: problemImgs.getSuccessImgUrls(),
  617 +remark: baseForm.problemDesc.trim(),
  618 +latLonType: 2,
  619 +lat: baseForm.lat,
  620 +lon: baseForm.lon,
  621 +lonLatAddress: baseForm.workLocation,
  622 +sourceId: 1,
  623 +sourceName: item.busiLineCn
749 } 624 }
750 }); 625 });
751 -  
752 - // 逐项校验:保证 roadId 不为 0,以及其他必填项  
753 - for (let i = 0; i < submitArr.length; i++) {  
754 - const row = submitArr[i];  
755 - const num = i + 1;  
756 - if (!row.roadId || row.roadId === 0) {  
757 - uni.showToast({ title: `第${num}条派单请选择道路名称`, icon: 'none' });  
758 - return; 626 +// 逐项校验:保证 roadId 不为 0,以及其他必填项
  627 +for (let i = 0; i < submitArr.length; i++) {
  628 +const row = submitArr[i];
  629 +const num = i + 1;
  630 +if (!row.roadId || row.roadId === 0) {
  631 +uni.showToast({ title: `第${num}条派单请选择道路名称`, icon: 'none' });
  632 +return;
759 } 633 }
760 - if (!row.busiLine) {  
761 - uni.showToast({ title: `第${num}条派单请选择业务线`, icon: 'none' });  
762 - return; 634 +if (!row.busiLine) {
  635 +uni.showToast({ title: `第${num}条派单请选择业务线`, icon: 'none' });
  636 +return;
763 } 637 }
764 - if (!row.orderCode) {  
765 - uni.showToast({ title: `第${num}条派单请选择问题类型`, icon: 'none' });  
766 - return; 638 +if (!row.orderCode) {
  639 +uni.showToast({ title: `第${num}条派单请选择问题类型`, icon: 'none' });
  640 +return;
767 } 641 }
768 - if (!row.pressingType) {  
769 - uni.showToast({ title: `第${num}条派单请选择紧急程度`, icon: 'none' });  
770 - return; 642 +if (!row.pressingType) {
  643 +uni.showToast({ title: `第${num}条派单请选择紧急程度`, icon: 'none' });
  644 +return;
771 } 645 }
772 } 646 }
773 -  
774 - uni.showLoading({ title: '提交中...', mask: true });  
775 -  
776 - let submitParams;  
777 - if (isRenew.value) {  
778 - // 重新提交:外层对象 + list 承载数组  
779 - submitParams = {  
780 - workerDataId: renewOrderData.value.id,  
781 - taskKey: renewOrderData.value.taskKey,  
782 - taskId: renewOrderData.value.taskId,  
783 - operateType: nextStepMap[renewOrderData.value.taskKey]?.operateTypeRenew || '',  
784 - agree: 0,  
785 - reason: baseForm.problemDesc.trim(),  
786 - ...submitArr[0] 647 +uni.showLoading({ title: '提交中...', mask: true });
  648 +let submitParams;
  649 +if (isRenew.value) {
  650 +// 重新提交:外层对象 + list 承载数组
  651 +submitParams = {
  652 +workerDataId: renewOrderData.value.id,
  653 +taskKey: renewOrderData.value.taskKey,
  654 +taskId: renewOrderData.value.taskId,
  655 +operateType: nextStepMap[renewOrderData.value.taskKey]?.operateTypeRenew || '',
  656 +agree: 0,
  657 +reason: baseForm.problemDesc.trim(),
  658 +...submitArr[0]
787 }; 659 };
788 } else { 660 } else {
789 - // 新建工单:根节点直接为纯数组  
790 - submitParams = submitArr; 661 +// 新建工单:根节点直接为纯数组
  662 +submitParams = submitArr;
791 } 663 }
792 -  
793 - console.log(submitParams)  
794 -  
795 - await callApiByRole(submitParams, isRenew.value);  
796 -  
797 - uni.showToast({  
798 - title: isRenew.value ? '重新提交成功' : '工单提交成功',  
799 - icon: 'success',  
800 - duration: 1000 664 +console.log(submitParams)
  665 +await callApiByRole(submitParams, isRenew.value);
  666 +uni.showToast({
  667 +title: isRenew.value ? '重新提交成功' : '工单提交成功',
  668 +icon: 'success',
  669 +duration: 1000
801 }); 670 });
802 - setTimeout(toOrderListPage, 1000); 671 +setTimeout(toOrderListPage, 1000);
803 } catch (error) { 672 } catch (error) {
804 - uni.hideLoading();  
805 - if (!Array.isArray(error)) {  
806 - console.error(isRenew.value ? '工单重新提交失败:' : '工单提交失败:', error);  
807 - uni.showToast({  
808 - title: error.msg || '提交失败,请重试',  
809 - icon: 'none',  
810 - duration: 2000 673 +uni.hideLoading();
  674 +if (!Array.isArray(error)) {
  675 +console.error(isRenew.value ? '工单重新提交失败:' : '工单提交失败:', error);
  676 +uni.showToast({
  677 +title: error.msg || '提交失败,请重试',
  678 +icon: 'none',
  679 +duration: 2000
811 }); 680 });
812 } 681 }
813 } 682 }
@@ -866,4 +735,4 @@ if (hasRepeat) { @@ -866,4 +735,4 @@ if (hasRepeat) {
866 .add-dispatch-btn-wrap { 735 .add-dispatch-btn-wrap {
867 margin: 20rpx 0; 736 margin: 20rpx 0;
868 } 737 }
869 -</style>  
870 \ No newline at end of file 738 \ No newline at end of file
  739 +</style>
pages-sub/problem/work-order-manage/add-order.vue
@@ -3,20 +3,18 @@ @@ -3,20 +3,18 @@
3 <view class="work-order-form-content commonPageLRpadding"> 3 <view class="work-order-form-content commonPageLRpadding">
4 <up-form label-position="left" :model="workOrderForm" ref="workOrderFormRef" labelWidth="200rpx"> 4 <up-form label-position="left" :model="workOrderForm" ref="workOrderFormRef" labelWidth="200rpx">
5 <!-- 业务线单选框:移入form内,作为第一个表单项(核心调整) --> 5 <!-- 业务线单选框:移入form内,作为第一个表单项(核心调整) -->
6 - <up-form-item label="业务线" prop="busiLineCn" border-bottom required>  
7 - <up-radio-group v-model="workOrderForm.busiLineCn" placement="row" @change="handleBusiLineChange">  
8 - <up-radio v-for="item in busiLineOptions" :key="item.name" :label="item.name" :name="item.name"></up-radio> 6 + <up-form-item label="业务线" prop="busiLine" border-bottom required>
  7 + <up-radio-group v-model="busiLineDisplay" placement="row" @change="handleBusiLineChange">
  8 + <up-radio v-for="item in busiLineOptions" :key="item.value" :label="item.label"
  9 + :name="item.label"></up-radio>
9 </up-radio-group> 10 </up-radio-group>
10 -  
11 </up-form-item> 11 </up-form-item>
12 -  
13 <!-- 1. 工单位置(地图选择) --> 12 <!-- 1. 工单位置(地图选择) -->
14 <up-form-item label="工单位置" prop="workLocation" border-bottom required 13 <up-form-item label="工单位置" prop="workLocation" border-bottom required
15 @click="chooseWorkLocation(); hideKeyboard()"> 14 @click="chooseWorkLocation(); hideKeyboard()">
16 <up-input v-model="workOrderForm.workLocation" border="none" readonly suffix-icon="map-fill" 15 <up-input v-model="workOrderForm.workLocation" border="none" readonly suffix-icon="map-fill"
17 placeholder="点击选择工单位置"></up-input> 16 placeholder="点击选择工单位置"></up-input>
18 </up-form-item> 17 </up-form-item>
19 -  
20 <!-- 2. 道路名称(下拉框) --> 18 <!-- 2. 道路名称(下拉框) -->
21 <up-form-item label="道路名称" prop="roadName" border-bottom required 19 <up-form-item label="道路名称" prop="roadName" border-bottom required
22 @click="handleActionSheetOpen('roadName'); hideKeyboard()"> 20 @click="handleActionSheetOpen('roadName'); hideKeyboard()">
@@ -26,7 +24,6 @@ @@ -26,7 +24,6 @@
26 <up-icon name="arrow-right" size="16"></up-icon> 24 <up-icon name="arrow-right" size="16"></up-icon>
27 </template> 25 </template>
28 </up-form-item> 26 </up-form-item>
29 -  
30 <!-- 3. 问题类型(下拉框) --> 27 <!-- 3. 问题类型(下拉框) -->
31 <up-form-item label="问题类型" prop="orderName" border-bottom required 28 <up-form-item label="问题类型" prop="orderName" border-bottom required
32 @click="handleActionSheetOpen('orderName'); hideKeyboard()"> 29 @click="handleActionSheetOpen('orderName'); hideKeyboard()">
@@ -36,7 +33,6 @@ @@ -36,7 +33,6 @@
36 <up-icon name="arrow-right" size="16"></up-icon> 33 <up-icon name="arrow-right" size="16"></up-icon>
37 </template> 34 </template>
38 </up-form-item> 35 </up-form-item>
39 -  
40 <!-- 新增:紧急程度选择 --> 36 <!-- 新增:紧急程度选择 -->
41 <up-form-item label="紧急程度" prop="pressingTypeName" border-bottom required 37 <up-form-item label="紧急程度" prop="pressingTypeName" border-bottom required
42 @click="handleActionSheetOpen('pressingType'); hideKeyboard()"> 38 @click="handleActionSheetOpen('pressingType'); hideKeyboard()">
@@ -46,13 +42,11 @@ @@ -46,13 +42,11 @@
46 <up-icon name="arrow-right" size="16"></up-icon> 42 <up-icon name="arrow-right" size="16"></up-icon>
47 </template> 43 </template>
48 </up-form-item> 44 </up-form-item>
49 -  
50 <!-- 4. 情况描述(文本域) --> 45 <!-- 4. 情况描述(文本域) -->
51 <up-form-item label="情况描述" prop="problemDesc" required> 46 <up-form-item label="情况描述" prop="problemDesc" required>
52 <up-textarea placeholder="请输入情况描述(最多200字)" v-model.trim="workOrderForm.problemDesc" count maxlength="200" 47 <up-textarea placeholder="请输入情况描述(最多200字)" v-model.trim="workOrderForm.problemDesc" count maxlength="200"
53 rows="4" @blur="() => workOrderFormRef.validateField('problemDesc')"></up-textarea> 48 rows="4" @blur="() => workOrderFormRef.validateField('problemDesc')"></up-textarea>
54 </up-form-item> 49 </up-form-item>
55 -  
56 <!-- 问题照片(核心修复:绑定纯数组) --> 50 <!-- 问题照片(核心修复:绑定纯数组) -->
57 <up-form-item label="问题照片" prop="problemImgs" border-bottom required> 51 <up-form-item label="问题照片" prop="problemImgs" border-bottom required>
58 <up-upload :file-list="problemImgs.imgList.value || []" @after-read="problemImgs.uploadImgs" 52 <up-upload :file-list="problemImgs.imgList.value || []" @after-read="problemImgs.uploadImgs"
@@ -60,7 +54,6 @@ @@ -60,7 +54,6 @@
60 :max-count="problemImgs.uploadConfig.maxCount" :upload-text="problemImgs.uploadConfig.uploadText" 54 :max-count="problemImgs.uploadConfig.maxCount" :upload-text="problemImgs.uploadConfig.uploadText"
61 :size-type="problemImgs.uploadConfig.sizeType"></up-upload> 55 :size-type="problemImgs.uploadConfig.sizeType"></up-upload>
62 </up-form-item> 56 </up-form-item>
63 -  
64 <!-- 完成时间 --> 57 <!-- 完成时间 -->
65 <up-form-item label="希望完成时间" prop="expectedFinishDate" @click="show = true; hideKeyboard()"> 58 <up-form-item label="希望完成时间" prop="expectedFinishDate" @click="show = true; hideKeyboard()">
66 <up-input v-model="workOrderForm.expectedFinishDate" border="none" readonly placeholder="点击选择时间"></up-input> 59 <up-input v-model="workOrderForm.expectedFinishDate" border="none" readonly placeholder="点击选择时间"></up-input>
@@ -74,25 +67,21 @@ @@ -74,25 +67,21 @@
74 </up-form-item> 67 </up-form-item>
75 </up-form> 68 </up-form>
76 </view> 69 </view>
77 -  
78 <!-- 底部提交按钮 --> 70 <!-- 底部提交按钮 -->
79 <view class="fixed-bottom-btn-wrap"> 71 <view class="fixed-bottom-btn-wrap">
80 <up-button type="primary" text="提交工单" @click="submitWorkOrder"></up-button> 72 <up-button type="primary" text="提交工单" @click="submitWorkOrder"></up-button>
81 </view> 73 </view>
82 -  
83 <!-- 合并后的通用下拉弹窗 --> 74 <!-- 合并后的通用下拉弹窗 -->
84 <up-action-sheet :show="showActionSheet" :actions="currentActionSheetData.list" 75 <up-action-sheet :show="showActionSheet" :actions="currentActionSheetData.list"
85 :title="currentActionSheetData.title" @close="handleActionSheetClose" 76 :title="currentActionSheetData.title" @close="handleActionSheetClose"
86 @select="handleActionSheetSelect"></up-action-sheet> 77 @select="handleActionSheetSelect"></up-action-sheet>
87 -  
88 <!-- 完成时间选择器 --> 78 <!-- 完成时间选择器 -->
89 <up-datetime-picker :show="show" v-model="expectedFinishDate" mode="datetime" :min-date="new Date()" 79 <up-datetime-picker :show="show" v-model="expectedFinishDate" mode="datetime" :min-date="new Date()"
90 @cancel="show = false" @confirm="expectedFinishDateConfirm"></up-datetime-picker> 80 @cancel="show = false" @confirm="expectedFinishDateConfirm"></up-datetime-picker>
91 </view> 81 </view>
92 </template> 82 </template>
93 -  
94 <script setup> 83 <script setup>
95 -import { ref, reactive } from 'vue' 84 +import { ref, reactive, computed } from 'vue'
96 import { onReady, onShow, onLoad } from '@dcloudio/uni-app'; 85 import { onReady, onShow, onLoad } from '@dcloudio/uni-app';
97 import { useUploadImgs } from '@/common/utils/useUploadImgs' 86 import { useUploadImgs } from '@/common/utils/useUploadImgs'
98 import { getRoadListByLatLng } from '@/api/common' 87 import { getRoadListByLatLng } from '@/api/common'
@@ -100,52 +89,16 @@ import { universalApproval, workorderCreate } from &#39;@/api/work-order-manage/work @@ -100,52 +89,16 @@ import { universalApproval, workorderCreate } from &#39;@/api/work-order-manage/work
100 import { timeFormat } from '@/uni_modules/uview-plus' 89 import { timeFormat } from '@/uni_modules/uview-plus'
101 import { nextStepMap } from '@/common/utils/common' 90 import { nextStepMap } from '@/common/utils/common'
102 import { useUserStore } from '@/pinia/user'; 91 import { useUserStore } from '@/pinia/user';
103 -  
104 // ========== 状态管理 ========== 92 // ========== 状态管理 ==========
105 const userStore = useUserStore(); 93 const userStore = useUserStore();
106 -  
107 -// ========== 业务线相关状态(核心改造:已整合到workOrderForm中,删除冗余selectedBusiLine) ==========  
108 -// 业务线映射表(键:英文标识,值:中文名称;新增反向映射:中文->英文)  
109 -const busiLineMap = ref({  
110 - 'yl': '园林',  
111 - 'sz': '市政',  
112 - 'wy': '物业',  
113 - 'zx': '秩序管理',  
114 - 'hj': '环境卫生',  
115 - 'yw': '常规运维',  
116 - '园林': 'yl',  
117 - '市政': 'sz',  
118 - '物业': 'wy',  
119 - '秩序管理': 'zx',  
120 - '环境卫生': 'hj',  
121 - '常规运维': 'yw'  
122 -});  
123 -  
124 -// 业务线选项列表(仅保留name字段=中文名称,适配单选框配置)  
125 -const busiLineOptions = ref([]);  
126 -// 格式化业务线选项(兼容1个/2个/3个的情况,生成中文名称列表)  
127 -const formatBusiLineOptions = () => {  
128 - if (!userStore.userInfo?.user?.busiLine) {  
129 - busiLineOptions.value = [];  
130 - return;  
131 - }  
132 - const rawBusiLines = userStore.userInfo.user.busiLine.split(','); // 拆分字符串为数组 ["yl", "sz", "wy"]  
133 - console.log(rawBusiLines)  
134 - // 核心改造:仅生成name字段(值为中文名称),适配单选框:label="item.name"  
135 - busiLineOptions.value = rawBusiLines.map(item => ({  
136 - name: busiLineMap.value[item.trim()] // name=中文名称(园林/市政/物业)  
137 - }));  
138 - console.log(busiLineOptions.value)  
139 -};  
140 -  
141 -// ========== 工具方法:通过中文名称获取对应的英文标识 ==========  
142 -const getBusiLineEnByCn = (cnName) => {  
143 - return busiLineMap.value[cnName] || '';  
144 -};  
145 - 94 +// 业务线选项,直接从pinia dictList拿
  95 +const busiLineOptions = computed(() => {
  96 + return userStore.dictList || []
  97 +})
146 // ========== 表单Ref ========== 98 // ========== 表单Ref ==========
147 const workOrderFormRef = ref(null) 99 const workOrderFormRef = ref(null)
148 - 100 +// 业务线单选框展示用(存中文label)
  101 +const busiLineDisplay = ref('')
149 // ========== 公共上传逻辑复用 ========== 102 // ========== 公共上传逻辑复用 ==========
150 const problemImgs = useUploadImgs({ 103 const problemImgs = useUploadImgs({
151 maxCount: 3, 104 maxCount: 3,
@@ -154,12 +107,10 @@ const problemImgs = useUploadImgs({ @@ -154,12 +107,10 @@ const problemImgs = useUploadImgs({
154 formRef: workOrderFormRef, 107 formRef: workOrderFormRef,
155 fieldName: 'problemImgs' 108 fieldName: 'problemImgs'
156 }) 109 })
157 -  
158 // 无需再手动初始化普通数组,直接使用 ref 响应式数据 110 // 无需再手动初始化普通数组,直接使用 ref 响应式数据
159 if (!Array.isArray(problemImgs.rawImgList.value)) { 111 if (!Array.isArray(problemImgs.rawImgList.value)) {
160 problemImgs.rawImgList.value = []; 112 problemImgs.rawImgList.value = [];
161 } 113 }
162 -  
163 // ========== 页面状态 ========== 114 // ========== 页面状态 ==========
164 // 通用弹窗控制 115 // 通用弹窗控制
165 const showActionSheet = ref(false) 116 const showActionSheet = ref(false)
@@ -172,19 +123,16 @@ const currentActionSheetData = reactive({ @@ -172,19 +123,16 @@ const currentActionSheetData = reactive({
172 // 完成时间选择器控制 123 // 完成时间选择器控制
173 const show = ref(false) 124 const show = ref(false)
174 const expectedFinishDate = ref(Date.now()) 125 const expectedFinishDate = ref(Date.now())
175 -  
176 // ========== 重新提交相关状态(核心:本地存储读取) ========== 126 // ========== 重新提交相关状态(核心:本地存储读取) ==========
177 const isRenew = ref(false); // 是否为重新提交状态 127 const isRenew = ref(false); // 是否为重新提交状态
178 const renewOrderData = ref(null); // 重新提交的原有工单数据 128 const renewOrderData = ref(null); // 重新提交的原有工单数据
179 -  
180 // ========== 下拉列表数据 ========== 129 // ========== 下拉列表数据 ==========
181 const roadNameList = ref([]) 130 const roadNameList = ref([])
182 const orderNameList = ref([]) 131 const orderNameList = ref([])
183 const pressingTypeList = ref([]) 132 const pressingTypeList = ref([])
184 -  
185 -// ========== 工单表单数据(核心调整:新增busiLineCn字段,绑定业务线) ========== 133 +// ========== 工单表单数据 ==========
186 const workOrderForm = reactive({ 134 const workOrderForm = reactive({
187 - busiLineCn: '', // 新增:业务线中文名称(用于form绑定、校验、回显) 135 + busiLine: '', // 业务线value,yl / sz / wy
188 roadId: 0, // 原有字段 136 roadId: 0, // 原有字段
189 roadName: '', // 原有字段 137 roadName: '', // 原有字段
190 workLocation: '', // 原有字段 138 workLocation: '', // 原有字段
@@ -196,10 +144,9 @@ const workOrderForm = reactive({ @@ -196,10 +144,9 @@ const workOrderForm = reactive({
196 lon: 0, // 原有字段 144 lon: 0, // 原有字段
197 expectedFinishDate: '', // 原有字段 145 expectedFinishDate: '', // 原有字段
198 }) 146 })
199 -  
200 -// ========== 表单校验规则(核心调整:新增busiLineCn校验规则) ========== 147 +// ========== 表单校验规则 ==========
201 const workOrderFormRules = reactive({ 148 const workOrderFormRules = reactive({
202 - busiLineCn: [ // 新增:业务线必选校验 149 + busiLine: [
203 { type: 'string', required: true, message: '请选择业务线', trigger: ['change', 'blur'] } 150 { type: 'string', required: true, message: '请选择业务线', trigger: ['change', 'blur'] }
204 ], 151 ],
205 workLocation: [ 152 workLocation: [
@@ -220,30 +167,25 @@ const workOrderFormRules = reactive({ @@ -220,30 +167,25 @@ const workOrderFormRules = reactive({
220 ], 167 ],
221 problemImgs: [problemImgs.imgValidateRule] // 复用上传校验规则 168 problemImgs: [problemImgs.imgValidateRule] // 复用上传校验规则
222 }) 169 })
223 -  
224 // ========== 生命周期 ========== 170 // ========== 生命周期 ==========
225 -// 页面加载:读取本地存储的工单数据(适配workOrderForm.busiLineCn) 171 +// 页面加载
226 onLoad((options) => { 172 onLoad((options) => {
227 - // 初始化业务线选项  
228 - formatBusiLineOptions();  
229 - // 默认选中第一个业务线(赋值给workOrderForm.busiLineCn,核心调整) 173 + // 默认选中第一个业务线
230 if (busiLineOptions.value.length > 0) { 174 if (busiLineOptions.value.length > 0) {
231 - workOrderForm.busiLineCn = busiLineOptions.value[0].name; 175 + // ✅ 业务线:表单存value,单选展示中文label
  176 + workOrderForm.busiLine = busiLineOptions.value[0].value
  177 + busiLineDisplay.value = busiLineOptions.value[0].label
232 } 178 }
233 -  
234 // 判断是否为重新提交状态 179 // 判断是否为重新提交状态
235 console.log(options) 180 console.log(options)
236 if (options.isRenew == 1 && options.tempKey) { 181 if (options.isRenew == 1 && options.tempKey) {
237 isRenew.value = true; 182 isRenew.value = true;
238 const tempKey = options.tempKey; 183 const tempKey = options.tempKey;
239 -  
240 // 1. 从本地同步存储中读取完整工单数据 184 // 1. 从本地同步存储中读取完整工单数据
241 try { 185 try {
242 const orderData = uni.getStorageSync(tempKey); 186 const orderData = uni.getStorageSync(tempKey);
243 if (orderData && typeof orderData === 'object') { 187 if (orderData && typeof orderData === 'object') {
244 renewOrderData.value = orderData; 188 renewOrderData.value = orderData;
245 -  
246 - console.log('123213')  
247 console.log(orderData) 189 console.log(orderData)
248 // 2. 回显工单数据到表单 190 // 2. 回显工单数据到表单
249 echoOrderData(renewOrderData.value); 191 echoOrderData(renewOrderData.value);
@@ -264,7 +206,6 @@ onLoad((options) =&gt; { @@ -264,7 +206,6 @@ onLoad((options) =&gt; {
264 } 206 }
265 } 207 }
266 }); 208 });
267 -  
268 onReady(() => { 209 onReady(() => {
269 // 设置表单校验规则 210 // 设置表单校验规则
270 if (workOrderFormRef.value) { 211 if (workOrderFormRef.value) {
@@ -272,27 +213,26 @@ onReady(() =&gt; { @@ -272,27 +213,26 @@ onReady(() =&gt; {
272 } 213 }
273 console.log('工单表单规则初始化完成') 214 console.log('工单表单规则初始化完成')
274 }) 215 })
275 -  
276 onShow(() => { 216 onShow(() => {
277 console.log(uni.$dict.getDictLabel('ai_image_status', 20)) 217 console.log(uni.$dict.getDictLabel('ai_image_status', 20))
278 console.log(uni.$dict.getDictSimpleList('work_name')) 218 console.log(uni.$dict.getDictSimpleList('work_name'))
279 -  
280 // 初始化问题类型列表 219 // 初始化问题类型列表
281 orderNameList.value = uni.$dict.transformLabelValueToNameValue(uni.$dict.getDictSimpleList('work_name')) 220 orderNameList.value = uni.$dict.transformLabelValueToNameValue(uni.$dict.getDictSimpleList('work_name'))
282 console.log('问题类型列表:', orderNameList.value) 221 console.log('问题类型列表:', orderNameList.value)
283 -  
284 // 初始化紧急程度列表 222 // 初始化紧急程度列表
285 pressingTypeList.value = uni.$dict.transformLabelValueToNameValue(uni.$dict.getDictSimpleList('workorder_pressing_type')) 223 pressingTypeList.value = uni.$dict.transformLabelValueToNameValue(uni.$dict.getDictSimpleList('workorder_pressing_type'))
286 console.log('紧急程度列表:', pressingTypeList.value) 224 console.log('紧急程度列表:', pressingTypeList.value)
287 }) 225 })
288 -  
289 -// ========== 核心方法:工单数据回显(适配workOrderForm.busiLineCn) ========== 226 +// ========== 核心方法:工单数据回显 ==========
290 const echoOrderData = (orderItem) => { 227 const echoOrderData = (orderItem) => {
291 - // 1. 回显业务线:将工单的英文busiLine转为中文名称,赋值给workOrderForm.busiLineCn(核心调整) 228 + // 业务线回显,后端给的是value,需要自动找到中文赋值给单选框
292 if (orderItem.busiLine) { 229 if (orderItem.busiLine) {
293 - workOrderForm.busiLineCn = busiLineMap.value[orderItem.busiLine]; // 英文->中文(如wy->物业) 230 + workOrderForm.busiLine = orderItem.busiLine;
  231 + const findItem = busiLineOptions.value.find(item => item.value === orderItem.busiLine)
  232 + if (findItem) {
  233 + busiLineDisplay.value = findItem.label
  234 + }
294 } 235 }
295 -  
296 // 2. 原有基础表单字段回显 236 // 2. 原有基础表单字段回显
297 workOrderForm.roadId = orderItem.roadId || 0; 237 workOrderForm.roadId = orderItem.roadId || 0;
298 workOrderForm.roadName = orderItem.roadName || ''; 238 workOrderForm.roadName = orderItem.roadName || '';
@@ -304,7 +244,6 @@ const echoOrderData = (orderItem) =&gt; { @@ -304,7 +244,6 @@ const echoOrderData = (orderItem) =&gt; {
304 workOrderForm.lat = orderItem.lat || 0; 244 workOrderForm.lat = orderItem.lat || 0;
305 workOrderForm.lon = orderItem.lon || 0; 245 workOrderForm.lon = orderItem.lon || 0;
306 workOrderForm.expectedFinishDate = orderItem.expectedFinishDate ? timeFormat(orderItem.expectedFinishDate, 'yyyy-mm-dd hh:MM:ss') : ''; 246 workOrderForm.expectedFinishDate = orderItem.expectedFinishDate ? timeFormat(orderItem.expectedFinishDate, 'yyyy-mm-dd hh:MM:ss') : '';
307 -  
308 // 3. 上传图片回显(兼容useUploadImgs格式) 247 // 3. 上传图片回显(兼容useUploadImgs格式)
309 if (orderItem.problemsImgs && Array.isArray(orderItem.problemsImgs) && orderItem.problemsImgs.length > 0) { 248 if (orderItem.problemsImgs && Array.isArray(orderItem.problemsImgs) && orderItem.problemsImgs.length > 0) {
310 const imgList = orderItem.problemsImgs.map((imgUrl, index) => ({ 249 const imgList = orderItem.problemsImgs.map((imgUrl, index) => ({
@@ -315,18 +254,22 @@ const echoOrderData = (orderItem) =&gt; { @@ -315,18 +254,22 @@ const echoOrderData = (orderItem) =&gt; {
315 problemImgs.imgList.value = imgList; 254 problemImgs.imgList.value = imgList;
316 problemImgs.rawImgList.value = imgList; 255 problemImgs.rawImgList.value = imgList;
317 } 256 }
318 -  
319 - // 4. 自动获取道路列表(修改:传递当前选中的业务线英文标识) 257 + // 4. 自动获取道路列表
320 if (orderItem.lat && orderItem.lon) { 258 if (orderItem.lat && orderItem.lon) {
321 - getRoadListByBusiLine(); // 改用封装后的方法,自动携带选中的业务线 259 + getRoadListByBusiLine();
322 } 260 }
323 }; 261 };
324 -  
325 -// ========== 业务线相关方法(适配workOrderForm.busiLineCn) ========== 262 +// ========== 业务线相关方法 ==========
326 /** 263 /**
327 * 业务线切换事件:清空道路信息 + 重新请求道路列表 264 * 业务线切换事件:清空道路信息 + 重新请求道路列表
  265 + * selectedLabel 是选中的中文label
328 */ 266 */
329 -const handleBusiLineChange = () => { 267 +const handleBusiLineChange = (selectedLabel) => {
  268 + // 根据中文label反向找到对应的value存入表单
  269 + const selectedItem = busiLineOptions.value.find(item => item.label === selectedLabel)
  270 + if (selectedItem) {
  271 + workOrderForm.busiLine = selectedItem.value
  272 + }
330 // 1. 清空已渲染的道路名称和表单字段 273 // 1. 清空已渲染的道路名称和表单字段
331 workOrderForm.roadName = ''; 274 workOrderForm.roadName = '';
332 workOrderForm.roadId = 0; 275 workOrderForm.roadId = 0;
@@ -337,26 +280,17 @@ const handleBusiLineChange = () =&gt; { @@ -337,26 +280,17 @@ const handleBusiLineChange = () =&gt; {
337 getRoadListByBusiLine(); 280 getRoadListByBusiLine();
338 } 281 }
339 }; 282 };
340 -  
341 /** 283 /**
342 - * 封装:根据当前选中业务线(中文)获取英文标识,再请求道路列表 284 + * 封装:根据当前选中业务线,请求道路列表
343 */ 285 */
344 const getRoadListByBusiLine = async () => { 286 const getRoadListByBusiLine = async () => {
345 - if (!workOrderForm.lat || !workOrderForm.lon) {  
346 - return;  
347 - }  
348 - // 核心:从workOrderForm.busiLineCn获取中文名称,转为英文标识(核心调整)  
349 - const busiLineEn = getBusiLineEnByCn(workOrderForm.busiLineCn);  
350 - if (!busiLineEn) {  
351 - uni.showToast({ title: '业务线标识异常', icon: 'none' }); 287 + if (!workOrderForm.lat || !workOrderForm.lon || !workOrderForm.busiLine) {
352 return; 288 return;
353 } 289 }
354 -  
355 try { 290 try {
356 uni.showLoading({ title: '获取道路名称中...' }); 291 uni.showLoading({ title: '获取道路名称中...' });
357 const roadRes = await getRoadListByLatLng({ 292 const roadRes = await getRoadListByLatLng({
358 - busiLine: busiLineEn, // 传递英文标识(yl/sz/wy),接口正常使用  
359 - // companyCode: 'sls', // 传递英文标识(yl/sz/wy),接口正常使用 293 + busiLine: workOrderForm.busiLine,
360 latitude: workOrderForm.lat, 294 latitude: workOrderForm.lat,
361 longitude: workOrderForm.lon 295 longitude: workOrderForm.lon
362 }); 296 });
@@ -378,7 +312,6 @@ const getRoadListByBusiLine = async () =&gt; { @@ -378,7 +312,6 @@ const getRoadListByBusiLine = async () =&gt; {
378 roadNameList.value = [{ name: '获取失败,请重新选择位置', code: '', id: 0 }]; 312 roadNameList.value = [{ name: '获取失败,请重新选择位置', code: '', id: 0 }];
379 } 313 }
380 }; 314 };
381 -  
382 // ========== 通用弹窗方法 ========== 315 // ========== 通用弹窗方法 ==========
383 /** 316 /**
384 * 打开通用下拉弹窗 317 * 打开通用下拉弹窗
@@ -389,7 +322,6 @@ const handleActionSheetOpen = (type) =&gt; { @@ -389,7 +322,6 @@ const handleActionSheetOpen = (type) =&gt; {
389 uni.showToast({ title: '请先选择工单位置', icon: 'none' }) 322 uni.showToast({ title: '请先选择工单位置', icon: 'none' })
390 return 323 return
391 } 324 }
392 -  
393 // 配置当前弹窗参数 325 // 配置当前弹窗参数
394 const configMap = { 326 const configMap = {
395 roadName: { 327 roadName: {
@@ -405,13 +337,11 @@ const handleActionSheetOpen = (type) =&gt; { @@ -405,13 +337,11 @@ const handleActionSheetOpen = (type) =&gt; {
405 list: pressingTypeList.value 337 list: pressingTypeList.value
406 } 338 }
407 } 339 }
408 -  
409 currentActionSheetData.type = type 340 currentActionSheetData.type = type
410 currentActionSheetData.title = configMap[type].title 341 currentActionSheetData.title = configMap[type].title
411 currentActionSheetData.list = configMap[type].list 342 currentActionSheetData.list = configMap[type].list
412 showActionSheet.value = true 343 showActionSheet.value = true
413 } 344 }
414 -  
415 /** 345 /**
416 * 关闭通用下拉弹窗 346 * 关闭通用下拉弹窗
417 */ 347 */
@@ -422,7 +352,6 @@ const handleActionSheetClose = () =&gt; { @@ -422,7 +352,6 @@ const handleActionSheetClose = () =&gt; {
422 currentActionSheetData.list = [] 352 currentActionSheetData.list = []
423 currentActionSheetData.title = '' 353 currentActionSheetData.title = ''
424 } 354 }
425 -  
426 /** 355 /**
427 * 通用下拉弹窗选择事件 356 * 通用下拉弹窗选择事件
428 */ 357 */
@@ -449,7 +378,6 @@ const handleActionSheetSelect = (e) =&gt; { @@ -449,7 +378,6 @@ const handleActionSheetSelect = (e) =&gt; {
449 // 关闭弹窗 378 // 关闭弹窗
450 showActionSheet.value = false 379 showActionSheet.value = false
451 } 380 }
452 -  
453 /** 381 /**
454 * 返回上一页 382 * 返回上一页
455 */ 383 */
@@ -462,14 +390,12 @@ const navigateBack = () =&gt; { @@ -462,14 +390,12 @@ const navigateBack = () =&gt; {
462 } 390 }
463 }); 391 });
464 } 392 }
465 -  
466 -// 新增:清除希望完成时间的方法 393 +/**
  394 + * 清除希望完成时间的方法
  395 + */
467 const clearExpectedFinishDate = () => { 396 const clearExpectedFinishDate = () => {
468 workOrderForm.expectedFinishDate = '' 397 workOrderForm.expectedFinishDate = ''
469 - // 清除后触发一次字段校验,确保校验状态更新  
470 - // this.$refs.inspectFormRef.validateField('expectedFinishDate')  
471 } 398 }
472 -  
473 /** 399 /**
474 * 选择工单位置 400 * 选择工单位置
475 */ 401 */
@@ -479,15 +405,11 @@ const chooseWorkLocation = () =&gt; { @@ -479,15 +405,11 @@ const chooseWorkLocation = () =&gt; {
479 workOrderForm.roadName = '' 405 workOrderForm.roadName = ''
480 workOrderForm.roadId = 0 406 workOrderForm.roadId = 0
481 roadNameList.value = [] 407 roadNameList.value = []
482 -  
483 workOrderForm.workLocation = res.name 408 workOrderForm.workLocation = res.name
484 workOrderForm.lat = res.latitude 409 workOrderForm.lat = res.latitude
485 workOrderForm.lon = res.longitude 410 workOrderForm.lon = res.longitude
486 -  
487 workOrderFormRef.value?.validateField('workLocation') 411 workOrderFormRef.value?.validateField('workLocation')
488 workOrderFormRef.value?.validateField('roadName') 412 workOrderFormRef.value?.validateField('roadName')
489 -  
490 - // 改用封装后的方法,自动携带选中的业务线英文标识  
491 await getRoadListByBusiLine(); 413 await getRoadListByBusiLine();
492 }, 414 },
493 fail: (err) => { 415 fail: (err) => {
@@ -496,7 +418,6 @@ const chooseWorkLocation = () =&gt; { @@ -496,7 +418,6 @@ const chooseWorkLocation = () =&gt; {
496 } 418 }
497 }) 419 })
498 } 420 }
499 -  
500 /** 421 /**
501 * 完成时间确认 422 * 完成时间确认
502 */ 423 */
@@ -505,25 +426,20 @@ const expectedFinishDateConfirm = (e) =&gt; { @@ -505,25 +426,20 @@ const expectedFinishDateConfirm = (e) =&gt; {
505 workOrderForm.expectedFinishDate = timeFormat(e.value, 'yyyy-mm-dd hh:MM:ss') 426 workOrderForm.expectedFinishDate = timeFormat(e.value, 'yyyy-mm-dd hh:MM:ss')
506 show.value = false 427 show.value = false
507 } 428 }
508 -  
509 /** 429 /**
510 * 隐藏键盘 430 * 隐藏键盘
511 */ 431 */
512 const hideKeyboard = () => { 432 const hideKeyboard = () => {
513 uni.hideKeyboard() 433 uni.hideKeyboard()
514 } 434 }
515 -  
516 /** 435 /**
517 - * 提交工单(区分新增/重新提交,接口隔离:从workOrderForm.busiLineCn取值) 436 + * 提交工单
518 */ 437 */
519 const submitWorkOrder = async () => { 438 const submitWorkOrder = async () => {
520 try { 439 try {
521 - // 先执行表单校验(包含业务线校验) 440 + // 先执行表单校验
522 await workOrderFormRef.value.validate() 441 await workOrderFormRef.value.validate()
523 -  
524 - // 核心:从workOrderForm.busiLineCn获取中文名称,转为英文标识(核心调整)  
525 - const busiLineEn = getBusiLineEnByCn(workOrderForm.busiLineCn);  
526 - if (!busiLineEn) { 442 + if (!workOrderForm.busiLine) {
527 uni.showToast({ title: '业务线选择异常,请重新选择', icon: 'none' }); 443 uni.showToast({ title: '业务线选择异常,请重新选择', icon: 'none' });
528 return; 444 return;
529 } 445 }
@@ -532,7 +448,7 @@ const submitWorkOrder = async () =&gt; { @@ -532,7 +448,7 @@ const submitWorkOrder = async () =&gt; {
532 const commonSubmitData = { 448 const commonSubmitData = {
533 roadId: workOrderForm.roadId, 449 roadId: workOrderForm.roadId,
534 roadName: workOrderForm.roadName, 450 roadName: workOrderForm.roadName,
535 - problemsImgs: problemImgs.getSuccessImgUrls(), // 复用上传逻辑的URL获取方法 451 + problemsImgs: problemImgs.getSuccessImgUrls(),
536 remark: workOrderForm.problemDesc.trim(), 452 remark: workOrderForm.problemDesc.trim(),
537 latLonType: 2, 453 latLonType: 2,
538 lat: workOrderForm.lat, 454 lat: workOrderForm.lat,
@@ -542,8 +458,8 @@ const submitWorkOrder = async () =&gt; { @@ -542,8 +458,8 @@ const submitWorkOrder = async () =&gt; {
542 orderName: workOrderForm.orderName, 458 orderName: workOrderForm.orderName,
543 expectedFinishDate: workOrderForm.expectedFinishDate == "" ? "" : new Date(workOrderForm.expectedFinishDate).getTime(), 459 expectedFinishDate: workOrderForm.expectedFinishDate == "" ? "" : new Date(workOrderForm.expectedFinishDate).getTime(),
544 sourceId: 1, 460 sourceId: 1,
545 - sourceName: workOrderForm.busiLineCn, // 从form字段获取业务线中文名称  
546 - busiLine: busiLineEn // 传递英文标识(yl/sz/wy),接口正常使用 461 + sourceName: busiLineOptions.value.find(item => item.value === workOrderForm.busiLine)?.label || '',
  462 + busiLine: workOrderForm.busiLine
547 } 463 }
548 console.log('工单数据') 464 console.log('工单数据')
549 console.log('完成时间:' + workOrderForm.expectedFinishDate) 465 console.log('完成时间:' + workOrderForm.expectedFinishDate)
@@ -551,34 +467,28 @@ const submitWorkOrder = async () =&gt; { @@ -551,34 +467,28 @@ const submitWorkOrder = async () =&gt; {
551 // 显示加载中 467 // 显示加载中
552 uni.showLoading({ title: '提交中...' }) 468 uni.showLoading({ title: '提交中...' })
553 let res 469 let res
554 -  
555 - // 核心:根据状态区分接口调用  
556 if (isRenew.value) { 470 if (isRenew.value) {
557 // 重新提交:调用 universalApproval 接口 471 // 重新提交:调用 universalApproval 接口
558 const renewSubmitData = { 472 const renewSubmitData = {
559 - // 原有工单必要参数  
560 workerDataId: renewOrderData.value.id, 473 workerDataId: renewOrderData.value.id,
561 taskKey: renewOrderData.value.taskKey, 474 taskKey: renewOrderData.value.taskKey,
562 taskId: renewOrderData.value.taskId, 475 taskId: renewOrderData.value.taskId,
563 operateType: nextStepMap[renewOrderData.value.taskKey]?.operateTypeRenew || '', 476 operateType: nextStepMap[renewOrderData.value.taskKey]?.operateTypeRenew || '',
564 agree: 0, 477 agree: 0,
565 reason: workOrderForm.problemDesc.trim(), 478 reason: workOrderForm.problemDesc.trim(),
566 - // 新编辑的工单数据  
567 ...commonSubmitData 479 ...commonSubmitData
568 } 480 }
569 res = await universalApproval(renewSubmitData) 481 res = await universalApproval(renewSubmitData)
570 } else { 482 } else {
571 - // 新增工单:调用原有 workorderCreate 接口(不影响原有功能) 483 + // 新增工单:调用原有 workorderCreate 接口
572 res = await workorderCreate(commonSubmitData) 484 res = await workorderCreate(commonSubmitData)
573 } 485 }
574 -  
575 uni.hideLoading() 486 uni.hideLoading()
576 uni.showToast({ 487 uni.showToast({
577 title: isRenew.value ? '重新提交成功' : '工单提交成功', 488 title: isRenew.value ? '重新提交成功' : '工单提交成功',
578 icon: 'success', 489 icon: 'success',
579 duration: 1000 490 duration: 1000
580 }) 491 })
581 -  
582 // 延迟跳转回列表页 492 // 延迟跳转回列表页
583 setTimeout(() => { 493 setTimeout(() => {
584 uni.reLaunch({ 494 uni.reLaunch({
@@ -588,13 +498,12 @@ const submitWorkOrder = async () =&gt; { @@ -588,13 +498,12 @@ const submitWorkOrder = async () =&gt; {
588 } catch (error) { 498 } catch (error) {
589 // 隐藏加载框 499 // 隐藏加载框
590 uni.hideLoading() 500 uni.hideLoading()
591 -  
592 // 区分是表单校验失败还是接口调用失败 501 // 区分是表单校验失败还是接口调用失败
593 if (!Array.isArray(error)) { 502 if (!Array.isArray(error)) {
594 // 接口调用失败 503 // 接口调用失败
595 console.error(isRenew.value ? '工单重新提交失败:' : '工单提交失败:', error) 504 console.error(isRenew.value ? '工单重新提交失败:' : '工单提交失败:', error)
596 uni.showToast({ 505 uni.showToast({
597 - title: isRenew.value ? error.msg : error.msg, 506 + title: error.msg || '提交失败',
598 icon: 'none', 507 icon: 'none',
599 duration: 2000 508 duration: 2000
600 }) 509 })
@@ -602,7 +511,6 @@ const submitWorkOrder = async () =&gt; { @@ -602,7 +511,6 @@ const submitWorkOrder = async () =&gt; {
602 } 511 }
603 } 512 }
604 </script> 513 </script>
605 -  
606 <style lang="scss" scoped> 514 <style lang="scss" scoped>
607 // 全局页面样式 515 // 全局页面样式
608 .page-container { 516 .page-container {
@@ -610,9 +518,8 @@ const submitWorkOrder = async () =&gt; { @@ -610,9 +518,8 @@ const submitWorkOrder = async () =&gt; {
610 padding-bottom: 100rpx; // 给底部按钮留空间 518 padding-bottom: 100rpx; // 给底部按钮留空间
611 } 519 }
612 520
613 -  
614 // 工单表单内容容器 521 // 工单表单内容容器
615 .work-order-form-content { 522 .work-order-form-content {
616 background: #fff; 523 background: #fff;
617 } 524 }
618 -</style>  
619 \ No newline at end of file 525 \ No newline at end of file
  526 +</style>
pinia/user.js
1 -import { defineStore } from 'pinia';  
2 -import cache from '@/common/utils/cache';  
3 -import globalConfig from '@/common/config/global'; 1 +import { defineStore } from "pinia";
  2 +import cache from "@/common/utils/cache";
  3 +import globalConfig from "@/common/config/global";
4 // 新增:导入 refreshToken 接口 4 // 新增:导入 refreshToken 接口
5 -import { login,smsLogin, getUserInfo, logout, moduleList, getSimpleDictDataList, refreshToken } from '@/api/user';  
6 - 5 +import {
  6 + login,
  7 + smsLogin,
  8 + getUserInfo,
  9 + logout,
  10 + moduleList,
  11 + getSimpleDictDataList,
  12 + refreshToken,
  13 +} from "@/api/user";
7 // 新增:定义刷新Token的定时器标识(避免重复创建定时器) 14 // 新增:定义刷新Token的定时器标识(避免重复创建定时器)
8 let refreshTokenTimer = null; 15 let refreshTokenTimer = null;
9 -  
10 -export const useUserStore = defineStore('user', { 16 +export const useUserStore = defineStore("user", {
11 // 修复1:删除重复的 state 定义,只保留根层级的 state 17 // 修复1:删除重复的 state 定义,只保留根层级的 state
12 state: () => ({ 18 state: () => ({
13 - token: cache.get(globalConfig.cache.tokenKey) || '', // 初始值从缓存取(兼容持久化)  
14 - refreshToken: cache.get(globalConfig.cache.refreshTokenKey) || '', // 新增:Refresh Token 状态 19 + token: cache.get(globalConfig.cache.tokenKey) || "", // 初始值从缓存取(兼容持久化)
  20 + refreshToken: cache.get(globalConfig.cache.refreshTokenKey) || "", // 新增:Refresh Token 状态
15 userInfo: cache.get(globalConfig.cache.userInfoKey) || {}, 21 userInfo: cache.get(globalConfig.cache.userInfoKey) || {},
16 - userId: cache.get(globalConfig.cache.userIdKey) || '',  
17 - moduleListInfo: cache.get(globalConfig.cache.moduleListKey) || '', 22 + userId: cache.get(globalConfig.cache.userIdKey) || "",
  23 + moduleListInfo: cache.get(globalConfig.cache.moduleListKey) || "",
18 expireTime: cache.get(globalConfig.cache.expireTimeKey) || 0, 24 expireTime: cache.get(globalConfig.cache.expireTimeKey) || 0,
19 dictData: cache.get(globalConfig.cache.dictDataKey) || {}, 25 dictData: cache.get(globalConfig.cache.dictDataKey) || {},
20 - logoutLoading: false 26 + dictList: cache.get(globalConfig.cache.dictListKey) || [], // 用来存放【过滤后的】字典数组
  27 + logoutLoading: false,
21 }), 28 }),
22 -  
23 getters: { 29 getters: {
24 isLogin: (state) => { 30 isLogin: (state) => {
25 if (!state.token) return false; 31 if (!state.token) return false;
@@ -35,104 +41,114 @@ export const useUserStore = defineStore(&#39;user&#39;, { @@ -35,104 +41,114 @@ export const useUserStore = defineStore(&#39;user&#39;, {
35 const remainingTime = state.expireTime - Date.now(); 41 const remainingTime = state.expireTime - Date.now();
36 // 剩余时间大于0秒 且 小于30秒(即将过期) 42 // 剩余时间大于0秒 且 小于30秒(即将过期)
37 return remainingTime > 0 && remainingTime < 30 * 1000; 43 return remainingTime > 0 && remainingTime < 30 * 1000;
38 - } 44 + },
39 }, 45 },
40 -  
41 actions: { 46 actions: {
42 async login(params) { 47 async login(params) {
43 try { 48 try {
44 // smsLogin 49 // smsLogin
45 - let res  
46 - if(params.type=='sms'){ 50 + let res;
  51 + if (params.type == "sms") {
47 res = await smsLogin(params); 52 res = await smsLogin(params);
48 - }else{ 53 + } else {
49 res = await login(params); 54 res = await login(params);
50 } 55 }
51 -  
52 // 新增:从登录接口返回值中获取 refreshToken(若接口返回字段名不一致,可调整,如 res.refresh_token) 56 // 新增:从登录接口返回值中获取 refreshToken(若接口返回字段名不一致,可调整,如 res.refresh_token)
53 const { accessToken, expiresTime, userId, refreshToken } = res; 57 const { accessToken, expiresTime, userId, refreshToken } = res;
54 -  
55 if (!accessToken) { 58 if (!accessToken) {
56 - throw new Error('登录失败,未获取到令牌'); 59 + throw new Error("登录失败,未获取到令牌");
57 } 60 }
58 -  
59 // 更新 Pinia state 61 // 更新 Pinia state
60 this.token = accessToken; 62 this.token = accessToken;
61 - this.refreshToken = refreshToken || ''; // 新增:存储Refresh Token 63 + this.refreshToken = refreshToken || ""; // 新增:存储Refresh Token
62 this.expireTime = expiresTime; 64 this.expireTime = expiresTime;
63 this.userId = userId; 65 this.userId = userId;
64 this.userInfo = {}; 66 this.userInfo = {};
65 -  
66 // 等待 Pinia 持久化同步 67 // 等待 Pinia 持久化同步
67 - await new Promise(resolve => setTimeout(resolve, 50));  
68 - 68 + await new Promise((resolve) => setTimeout(resolve, 50));
69 // 获取用户信息 69 // 获取用户信息
70 const userInfo = await this.getUserInfo(); 70 const userInfo = await this.getUserInfo();
71 this.userInfo = userInfo; 71 this.userInfo = userInfo;
72 -  
73 // 获取模块列表 72 // 获取模块列表
74 let moduleListInfo = null; 73 let moduleListInfo = null;
75 try { 74 try {
76 moduleListInfo = await this.getModuleList(); 75 moduleListInfo = await this.getModuleList();
77 this.moduleListInfo = moduleListInfo; 76 this.moduleListInfo = moduleListInfo;
78 } catch (moduleErr) { 77 } catch (moduleErr) {
79 - console.warn('获取模块列表失败(不影响登录):', moduleErr);  
80 - uni.showToast({ title: '获取模块列表失败,可正常登录', icon: 'none' }); 78 + console.warn("获取模块列表失败(不影响登录):", moduleErr);
  79 + uni.showToast({
  80 + title: "获取模块列表失败,可正常登录",
  81 + icon: "none",
  82 + });
81 } 83 }
82 -  
83 // 修复2:调用字典接口时,不直接抛错(避免阻断登录) 84 // 修复2:调用字典接口时,不直接抛错(避免阻断登录)
84 try { 85 try {
85 const dictRes = await this.getAndSaveDictData(); 86 const dictRes = await this.getAndSaveDictData();
86 // 仅接口返回成功时才更新字典 87 // 仅接口返回成功时才更新字典
87 this.dictData = dictRes || {}; 88 this.dictData = dictRes || {};
88 - console.log('字典数据获取成功:', this.dictData); 89 + console.log("字典数据获取成功:", this.dictData);
  90 + // 获取原始完整字典
  91 + const originDictList = uni.$dict.getDictSimpleList("business_line");
  92 + console.log("原始完整字典:", originDictList);
  93 +
  94 + // ========== 核心:根据 userInfo.busiLine 过滤,结果存入 dictList ==========
  95 +
  96 + // this.userInfo?.busiLine
  97 +
  98 + console.log("用户信息", this.userInfo.user);
  99 + if (this.userInfo.user.busiLine && originDictList?.length) {
  100 + const busiValueArr = this.userInfo.user.busiLine
  101 + .split(",")
  102 + .map((item) => item.trim())
  103 + .filter(Boolean);
  104 + this.dictList = originDictList.filter((item) => {
  105 + return busiValueArr.includes(item.value);
  106 + });
  107 + } else {
  108 + this.dictList = [];
  109 + }
  110 + console.log("过滤完成,dictList = ", this.dictList);
89 } catch (dictErr) { 111 } catch (dictErr) {
90 - console.warn('获取字典失败(不影响登录):', dictErr);  
91 - uni.showToast({ title: '获取字典失败,可正常使用', icon: 'none' }); 112 + console.warn("获取字典失败(不影响登录):", dictErr);
  113 + uni.showToast({ title: "获取字典失败,可正常使用", icon: "none" });
  114 + this.dictList = []; // 异常情况置空
92 } 115 }
93 -  
94 // 新增:登录成功后,启动 Token 自动刷新定时器 116 // 新增:登录成功后,启动 Token 自动刷新定时器
95 this.startRefreshTokenTimer(); 117 this.startRefreshTokenTimer();
96 -  
97 return { ...res, userInfo, moduleListInfo }; 118 return { ...res, userInfo, moduleListInfo };
98 } catch (err) { 119 } catch (err) {
99 - console.error('登录流程失败:', err); 120 + console.error("登录流程失败:", err);
100 throw err; 121 throw err;
101 } 122 }
102 }, 123 },
103 -  
104 // 新增:调用刷新Token接口,返回刷新是否成功 124 // 新增:调用刷新Token接口,返回刷新是否成功
105 async refreshTokenApi() { 125 async refreshTokenApi() {
106 // 前置校验:无Refresh Token直接返回失败 126 // 前置校验:无Refresh Token直接返回失败
107 if (!this.refreshToken) { 127 if (!this.refreshToken) {
108 - console.warn('无刷新令牌,无法刷新Token'); 128 + console.warn("无刷新令牌,无法刷新Token");
109 this.logout(); // 无刷新令牌,直接退出登录 129 this.logout(); // 无刷新令牌,直接退出登录
110 return false; 130 return false;
111 } 131 }
112 -  
113 try { 132 try {
114 // 调用修正后的 refreshToken 接口,传入当前的 refreshToken 133 // 调用修正后的 refreshToken 接口,传入当前的 refreshToken
115 const res = await refreshToken(this.refreshToken); 134 const res = await refreshToken(this.refreshToken);
116 -  
117 // 校验接口返回结果(根据你的实际接口返回格式调整) 135 // 校验接口返回结果(根据你的实际接口返回格式调整)
118 if (!res || !res.accessToken) { 136 if (!res || !res.accessToken) {
119 - throw new Error('刷新Token失败,未获取到新令牌'); 137 + throw new Error("刷新Token失败,未获取到新令牌");
120 } 138 }
121 -  
122 // 更新 Token 相关状态 139 // 更新 Token 相关状态
123 this.token = res.accessToken; // 新的访问令牌 140 this.token = res.accessToken; // 新的访问令牌
124 this.refreshToken = res.refreshToken || this.refreshToken; // 若接口返回新的Refresh Token则更新 141 this.refreshToken = res.refreshToken || this.refreshToken; // 若接口返回新的Refresh Token则更新
125 this.expireTime = res.expiresTime; // 新的过期时间 142 this.expireTime = res.expiresTime; // 新的过期时间
126 - console.log('Token 刷新成功'); 143 + console.log("Token 刷新成功");
127 return true; 144 return true;
128 } catch (err) { 145 } catch (err) {
129 - console.error('刷新Token失败:', err); 146 + console.error("刷新Token失败:", err);
130 // 刷新失败,直接退出登录 147 // 刷新失败,直接退出登录
131 this.logout(); 148 this.logout();
132 return false; 149 return false;
133 } 150 }
134 }, 151 },
135 -  
136 // 新增:启动 Token 自动刷新定时器 152 // 新增:启动 Token 自动刷新定时器
137 startRefreshTokenTimer() { 153 startRefreshTokenTimer() {
138 // 先清除已有定时器,避免重复创建 154 // 先清除已有定时器,避免重复创建
@@ -140,7 +156,6 @@ export const useUserStore = defineStore(&#39;user&#39;, { @@ -140,7 +156,6 @@ export const useUserStore = defineStore(&#39;user&#39;, {
140 clearTimeout(refreshTokenTimer); 156 clearTimeout(refreshTokenTimer);
141 refreshTokenTimer = null; 157 refreshTokenTimer = null;
142 } 158 }
143 -  
144 // 计算剩余时间(过期前30秒执行刷新) 159 // 计算剩余时间(过期前30秒执行刷新)
145 const remainingTime = this.expireTime - Date.now() - 30 * 1000; 160 const remainingTime = this.expireTime - Date.now() - 30 * 1000;
146 if (remainingTime <= 0) { 161 if (remainingTime <= 0) {
@@ -148,7 +163,6 @@ export const useUserStore = defineStore(&#39;user&#39;, { @@ -148,7 +163,6 @@ export const useUserStore = defineStore(&#39;user&#39;, {
148 this.refreshTokenApi(); 163 this.refreshTokenApi();
149 return; 164 return;
150 } 165 }
151 -  
152 // 设置定时器,到期后执行刷新 166 // 设置定时器,到期后执行刷新
153 refreshTokenTimer = setTimeout(async () => { 167 refreshTokenTimer = setTimeout(async () => {
154 const refreshSuccess = await this.refreshTokenApi(); 168 const refreshSuccess = await this.refreshTokenApi();
@@ -157,9 +171,10 @@ export const useUserStore = defineStore(&#39;user&#39;, { @@ -157,9 +171,10 @@ export const useUserStore = defineStore(&#39;user&#39;, {
157 this.startRefreshTokenTimer(); 171 this.startRefreshTokenTimer();
158 } 172 }
159 }, remainingTime); 173 }, remainingTime);
160 - console.log(`Token 自动刷新定时器已启动,将在 ${Math.ceil(remainingTime / 1000)} 秒后执行刷新`); 174 + console.log(
  175 + `Token 自动刷新定时器已启动,将在 ${Math.ceil(remainingTime / 1000)} 秒后执行刷新`,
  176 + );
161 }, 177 },
162 -  
163 // 新增:停止 Token 自动刷新定时器(避免内存泄漏) 178 // 新增:停止 Token 自动刷新定时器(避免内存泄漏)
164 stopRefreshTokenTimer() { 179 stopRefreshTokenTimer() {
165 if (refreshTokenTimer) { 180 if (refreshTokenTimer) {
@@ -167,171 +182,169 @@ export const useUserStore = defineStore(&#39;user&#39;, { @@ -167,171 +182,169 @@ export const useUserStore = defineStore(&#39;user&#39;, {
167 refreshTokenTimer = null; 182 refreshTokenTimer = null;
168 } 183 }
169 }, 184 },
170 -  
171 // 修复3:重构字典获取方法(加 Token 校验 + 强制携带 Token + 宽松错误处理) 185 // 修复3:重构字典获取方法(加 Token 校验 + 强制携带 Token + 宽松错误处理)
172 async getAndSaveDictData() { 186 async getAndSaveDictData() {
173 // 前置校验:无登录态直接返回,不请求接口 187 // 前置校验:无登录态直接返回,不请求接口
174 if (!this.isLogin) { 188 if (!this.isLogin) {
175 - console.warn('未登录,跳过字典获取');  
176 - return { code: -1, msg: '未登录' }; 189 + console.warn("未登录,跳过字典获取");
  190 + return { code: -1, msg: "未登录" };
177 } 191 }
178 -  
179 try { 192 try {
180 // 强制携带 Token(和 getModuleList 保持一致,避免拦截器同步延迟) 193 // 强制携带 Token(和 getModuleList 保持一致,避免拦截器同步延迟)
181 const res = await getSimpleDictDataList( 194 const res = await getSimpleDictDataList(
182 {}, // 接口入参(按需传,比如 dictType: ['level']) 195 {}, // 接口入参(按需传,比如 dictType: ['level'])
183 - { header: { 'Authorization': `Bearer ${this.token}` } } 196 + { header: { Authorization: `Bearer ${this.token}` } },
184 ); 197 );
185 -  
186 // 校验接口返回码(核心:避免非 0 码数据存入) 198 // 校验接口返回码(核心:避免非 0 码数据存入)
187 if (res.code !== 0) { 199 if (res.code !== 0) {
188 - console.warn('字典接口返回失败:', res.msg); 200 + console.warn("字典接口返回失败:", res.msg);
189 return res; // 返回错误信息,但不抛错 201 return res; // 返回错误信息,但不抛错
190 } 202 }
191 return res; 203 return res;
192 } catch (err) { 204 } catch (err) {
193 // 修复4:宽松错误处理,只打印日志,不抛错(避免阻断登录) 205 // 修复4:宽松错误处理,只打印日志,不抛错(避免阻断登录)
194 - console.error('字典接口请求异常:', err);  
195 - return { code: -2, msg: '接口请求异常:' + (err.message || '网络错误') }; 206 + console.error("字典接口请求异常:", err);
  207 + return {
  208 + code: -2,
  209 + msg: "接口请求异常:" + (err.message || "网络错误"),
  210 + };
196 } 211 }
197 }, 212 },
198 -  
199 async getModuleList() { 213 async getModuleList() {
200 try { 214 try {
201 if (!this.token) { 215 if (!this.token) {
202 - throw new Error('未获取到登录令牌,无法获取模块列表'); 216 + throw new Error("未获取到登录令牌,无法获取模块列表");
203 } 217 }
204 -  
205 - const res = await moduleList({}, {  
206 - header: { 'Authorization': `Bearer ${this.token}` }  
207 - }); 218 + const res = await moduleList(
  219 + {},
  220 + {
  221 + header: { Authorization: `Bearer ${this.token}` },
  222 + },
  223 + );
208 return res; 224 return res;
209 } catch (err) { 225 } catch (err) {
210 - console.error('获取用户菜单失败:', err);  
211 - if (err?.data?.code === 401 || err?.message.includes('401')) {  
212 - throw new Error('登录态已过期,请重新登录'); 226 + console.error("获取用户菜单失败:", err);
  227 + if (err?.data?.code === 401 || err?.message.includes("401")) {
  228 + throw new Error("登录态已过期,请重新登录");
213 } else { 229 } else {
214 - throw new Error('获取用户菜单失败,请重新登录'); 230 + throw new Error("获取用户菜单失败,请重新登录");
215 } 231 }
216 } 232 }
217 }, 233 },
218 -  
219 async getUserInfo() { 234 async getUserInfo() {
220 try { 235 try {
221 const res = await getUserInfo(); 236 const res = await getUserInfo();
222 return res; 237 return res;
223 } catch (err) { 238 } catch (err) {
224 - console.error('获取用户信息失败:', err);  
225 - throw new Error('获取用户信息失败,请重新登录'); 239 + console.error("获取用户信息失败:", err);
  240 + throw new Error("获取用户信息失败,请重新登录");
226 } 241 }
227 }, 242 },
228 -  
229 logout() { 243 logout() {
230 // 新增:退出登录时,停止刷新定时器 244 // 新增:退出登录时,停止刷新定时器
231 this.stopRefreshTokenTimer(); 245 this.stopRefreshTokenTimer();
232 -  
233 const pages = getCurrentPages(); 246 const pages = getCurrentPages();
234 if (pages.length === 0) return; 247 if (pages.length === 0) return;
235 -  
236 const currentPageRoute = pages[pages.length - 1].route; 248 const currentPageRoute = pages[pages.length - 1].route;
237 const loginPath = globalConfig.router.loginPath 249 const loginPath = globalConfig.router.loginPath
238 - .replace(/^\//, '')  
239 - .split('?')[0];  
240 - 250 + .replace(/^\//, "")
  251 + .split("?")[0];
241 if (currentPageRoute === loginPath) { 252 if (currentPageRoute === loginPath) {
242 - this.token = '';  
243 - this.refreshToken = ''; // 新增:清空Refresh Token 253 + this.token = "";
  254 + this.refreshToken = ""; // 新增:清空Refresh Token
244 this.userInfo = {}; 255 this.userInfo = {};
245 - this.userId = '';  
246 - this.moduleListInfo = ''; 256 + this.userId = "";
  257 + this.moduleListInfo = "";
247 this.expireTime = 0; 258 this.expireTime = 0;
248 this.dictData = {}; 259 this.dictData = {};
  260 + this.dictList = [];
249 return; 261 return;
250 } 262 }
251 -  
252 const logoutWithLock = async () => { 263 const logoutWithLock = async () => {
253 if (this.logoutLoading) return; 264 if (this.logoutLoading) return;
254 this.logoutLoading = true; 265 this.logoutLoading = true;
255 -  
256 try { 266 try {
257 await logout(); 267 await logout();
258 } catch (err) { 268 } catch (err) {
259 - console.error('退出登录接口调用失败:', err); 269 + console.error("退出登录接口调用失败:", err);
260 } finally { 270 } finally {
261 - this.token = '';  
262 - this.refreshToken = ''; // 新增:清空Refresh Token 271 + this.token = "";
  272 + this.refreshToken = ""; // 新增:清空Refresh Token
263 this.userInfo = {}; 273 this.userInfo = {};
264 - this.userId = '';  
265 - this.moduleListInfo = ''; 274 + this.userId = "";
  275 + this.moduleListInfo = "";
266 this.dictData = {}; 276 this.dictData = {};
  277 + this.dictList = [];
267 this.expireTime = 0; 278 this.expireTime = 0;
268 this.logoutLoading = false; 279 this.logoutLoading = false;
269 -  
270 uni.redirectTo({ 280 uni.redirectTo({
271 - url: globalConfig.router.loginPath 281 + url: globalConfig.router.loginPath,
272 }); 282 });
273 } 283 }
274 }; 284 };
275 -  
276 logoutWithLock(); 285 logoutWithLock();
277 }, 286 },
278 -  
279 checkLogin() { 287 checkLogin() {
280 if (!this.isLogin) { 288 if (!this.isLogin) {
281 const pages = getCurrentPages(); 289 const pages = getCurrentPages();
282 if (pages.length === 0) return false; 290 if (pages.length === 0) return false;
283 -  
284 const currentPageRoute = pages[pages.length - 1].route; 291 const currentPageRoute = pages[pages.length - 1].route;
285 const loginPath = globalConfig.router.loginPath 292 const loginPath = globalConfig.router.loginPath
286 - .replace(/^\//, '')  
287 - .split('?')[0];  
288 - 293 + .replace(/^\//, "")
  294 + .split("?")[0];
289 if (currentPageRoute !== loginPath) { 295 if (currentPageRoute !== loginPath) {
290 uni.redirectTo({ 296 uni.redirectTo({
291 - url: globalConfig.router.loginPath 297 + url: globalConfig.router.loginPath,
292 }); 298 });
293 } 299 }
294 return false; 300 return false;
295 } 301 }
296 -  
297 // 新增:已登录时,启动刷新定时器(防止页面刷新后定时器丢失) 302 // 新增:已登录时,启动刷新定时器(防止页面刷新后定时器丢失)
298 this.startRefreshTokenTimer(); 303 this.startRefreshTokenTimer();
299 return true; 304 return true;
300 - } 305 + },
301 }, 306 },
302 -  
303 persist: { 307 persist: {
304 enabled: true, 308 enabled: true,
305 - key: 'user_store', 309 + key: "user_store",
306 storage: { 310 storage: {
307 getItem: (key) => uni.getStorageSync(key), 311 getItem: (key) => uni.getStorageSync(key),
308 setItem: (key, value) => uni.setStorageSync(key, value), 312 setItem: (key, value) => uni.setStorageSync(key, value),
309 - removeItem: (key) => uni.removeStorageSync(key) 313 + removeItem: (key) => uni.removeStorageSync(key),
310 }, 314 },
311 serializer: { 315 serializer: {
312 serialize: (state) => { 316 serialize: (state) => {
313 uni.setStorageSync(globalConfig.cache.tokenKey, state.token); 317 uni.setStorageSync(globalConfig.cache.tokenKey, state.token);
314 - uni.setStorageSync(globalConfig.cache.refreshTokenKey, state.refreshToken); // 新增:持久化Refresh Token 318 + uni.setStorageSync(
  319 + globalConfig.cache.refreshTokenKey,
  320 + state.refreshToken,
  321 + );
315 uni.setStorageSync(globalConfig.cache.userIdKey, state.userId); 322 uni.setStorageSync(globalConfig.cache.userIdKey, state.userId);
316 uni.setStorageSync(globalConfig.cache.expireTimeKey, state.expireTime); 323 uni.setStorageSync(globalConfig.cache.expireTimeKey, state.expireTime);
317 uni.setStorageSync(globalConfig.cache.userInfoKey, state.userInfo); 324 uni.setStorageSync(globalConfig.cache.userInfoKey, state.userInfo);
318 - uni.setStorageSync(globalConfig.cache.moduleListKey, state.moduleListInfo); 325 + uni.setStorageSync(
  326 + globalConfig.cache.moduleListKey,
  327 + state.moduleListInfo,
  328 + );
319 uni.setStorageSync(globalConfig.cache.dictDataKey, state.dictData); 329 uni.setStorageSync(globalConfig.cache.dictDataKey, state.dictData);
  330 + uni.setStorageSync(globalConfig.cache.dictListKey, state.dictList);
320 return state; 331 return state;
321 }, 332 },
322 deserialize: (value) => { 333 deserialize: (value) => {
323 return { 334 return {
324 - token: uni.getStorageSync(globalConfig.cache.tokenKey) || '',  
325 - refreshToken: uni.getStorageSync(globalConfig.cache.refreshTokenKey) || '', // 新增:读取Refresh Token  
326 - userId: uni.getStorageSync(globalConfig.cache.userIdKey) || '', 335 + token: uni.getStorageSync(globalConfig.cache.tokenKey) || "",
  336 + refreshToken:
  337 + uni.getStorageSync(globalConfig.cache.refreshTokenKey) || "",
  338 + userId: uni.getStorageSync(globalConfig.cache.userIdKey) || "",
327 expireTime: uni.getStorageSync(globalConfig.cache.expireTimeKey) || 0, 339 expireTime: uni.getStorageSync(globalConfig.cache.expireTimeKey) || 0,
328 userInfo: uni.getStorageSync(globalConfig.cache.userInfoKey) || {}, 340 userInfo: uni.getStorageSync(globalConfig.cache.userInfoKey) || {},
329 - moduleListInfo: uni.getStorageSync(globalConfig.cache.moduleListKey) || '', 341 + moduleListInfo:
  342 + uni.getStorageSync(globalConfig.cache.moduleListKey) || "",
330 dictData: uni.getStorageSync(globalConfig.cache.dictDataKey) || {}, 343 dictData: uni.getStorageSync(globalConfig.cache.dictDataKey) || {},
331 - logoutLoading: false 344 + dictList: uni.getStorageSync(globalConfig.cache.dictListKey) || [],
  345 + logoutLoading: false,
332 }; 346 };
333 - } 347 + },
334 }, 348 },
335 - // paths: []  
336 - }  
337 -});  
338 \ No newline at end of file 349 \ No newline at end of file
  350 + },
  351 +});