Commit e6a0428542af9f5d8f619cfba554d8d993893ca5

Authored by 刘淇
1 parent dfa5aac5

单图情况 宽高70*70

api/user.js
@@ -41,3 +41,11 @@ export const moduleList = () => { @@ -41,3 +41,11 @@ export const moduleList = () => {
41 export const getSimpleDictDataList = () => { 41 export const getSimpleDictDataList = () => {
42 return get('/admin-api/system/dict-data/simple-list') 42 return get('/admin-api/system/dict-data/simple-list')
43 } 43 }
  44 +
  45 +/**
  46 + * 刷新token
  47 + * @returns {Promise}
  48 + */
  49 +export const refreshToken = (params) => {
  50 + return post('/admin-api/system/auth/refresh-token?refreshToken='+params)
  51 +}
common/config/global.js
@@ -23,7 +23,8 @@ export default { @@ -23,7 +23,8 @@ export default {
23 expireTimeKey: 'jcss_token_expire', 23 expireTimeKey: 'jcss_token_expire',
24 userIdKey:'jcss_user_id', 24 userIdKey:'jcss_user_id',
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 }, 28 },
28 appName: 'JCSS管理系统', 29 appName: 'JCSS管理系统',
29 tokenExpireTime: 7 * 24 * 60 * 60 * 1000 30 tokenExpireTime: 7 * 24 * 60 * 60 * 1000
common/utils/common.js
@@ -162,4 +162,23 @@ export const calculateFormatTimeDiff = (startTime, endTime) => { @@ -162,4 +162,23 @@ export const calculateFormatTimeDiff = (startTime, endTime) => {
162 162
163 // 8. 拼接并返回结果 163 // 8. 拼接并返回结果
164 return validTimeParts.join(''); 164 return validTimeParts.join('');
165 -};  
166 \ No newline at end of file 165 \ No newline at end of file
  166 +};
  167 +
  168 +
  169 +/**
  170 + * 将日期数组 [年, 月, 日] 转为标准日期字符串(兼容 iOS/Android)
  171 + * @param {Array} dateArr 日期数组,如 [2025, 12, 20]
  172 + * @returns {String} 标准日期字符串,如 '2025-12-20'
  173 + */
  174 +export const convertArrToDateStr= (dateArr)=> {
  175 + // 边界判断:非数组/长度不足3,返回空
  176 + if (!Array.isArray(dateArr) || dateArr.length < 3) {
  177 + return dateArr;
  178 + }
  179 + // 解构年、月、日,补零处理(确保格式统一,如 12月→12,2月→02)
  180 + const [year, month, day] = dateArr;
  181 + const formatMonth = month.toString().padStart(2, '0');
  182 + const formatDay = day.toString().padStart(2, '0');
  183 + // 拼接为标准字符串
  184 + return `${year}-${formatMonth}-${formatDay}`;
  185 +}
167 \ No newline at end of file 186 \ No newline at end of file
pages-sub/daily/patrol-manage/add-patrol-record.vue
@@ -6,7 +6,7 @@ @@ -6,7 +6,7 @@
6 label-position="left" 6 label-position="left"
7 :model="inspectForm" 7 :model="inspectForm"
8 ref="inspectFormRef" 8 ref="inspectFormRef"
9 - labelWidth="190rpx" 9 + labelWidth="200rpx"
10 > 10 >
11 <!-- 1. 巡查描述 --> 11 <!-- 1. 巡查描述 -->
12 <up-form-item 12 <up-form-item
@@ -110,10 +110,20 @@ @@ -110,10 +110,20 @@
110 border="none" 110 border="none"
111 readonly 111 readonly
112 placeholder="点击选择时间" 112 placeholder="点击选择时间"
  113 + >
  114 +
113 115
114 - ></up-input> 116 + </up-input>
115 <template #right> 117 <template #right>
116 - <up-icon name="arrow-right" size="16"></up-icon> 118 + <!-- 核心修复:用view包裹图标,添加双重@click.stop -->
  119 + <view v-if="inspectForm.expectedFinishDate" @click.stop>
  120 + <up-icon
  121 + name="close"
  122 + size="16"
  123 + @click.stop="clearExpectedFinishDate"
  124 + ></up-icon>
  125 + </view>
  126 + <up-icon name="arrow-right" size="16" v-else></up-icon>
117 </template> 127 </template>
118 </up-form-item> 128 </up-form-item>
119 129
@@ -292,6 +302,12 @@ export default { @@ -292,6 +302,12 @@ export default {
292 this.show = false 302 this.show = false
293 this.$refs.inspectFormRef.validateField('expectedFinishDate') 303 this.$refs.inspectFormRef.validateField('expectedFinishDate')
294 }, 304 },
  305 + // 新增:清除希望完成时间的方法
  306 + clearExpectedFinishDate() {
  307 + this.inspectForm.expectedFinishDate = ''
  308 + // 清除后触发一次字段校验,确保校验状态更新
  309 + this.$refs.inspectFormRef.validateField('expectedFinishDate')
  310 + },
295 311
296 deleteImg(event) { 312 deleteImg(event) {
297 this.imagesList.splice(event.index, 1) 313 this.imagesList.splice(event.index, 1)
pages-sub/daily/patrol-manage/index.vue
@@ -108,6 +108,7 @@ import { timeFormat } from &#39;@/uni_modules/uview-plus&#39;; @@ -108,6 +108,7 @@ import { timeFormat } from &#39;@/uni_modules/uview-plus&#39;;
108 import { ref } from 'vue'; 108 import { ref } from 'vue';
109 import { onLoad, onShow } from '@dcloudio/uni-app'; 109 import { onLoad, onShow } from '@dcloudio/uni-app';
110 import { inspectionPlanPage } from "@/api/patrol-manage/patrol-plan"; 110 import { inspectionPlanPage } from "@/api/patrol-manage/patrol-plan";
  111 +import { convertArrToDateStr } from "@/common/utils/common";
111 // Tabs 配置 112 // Tabs 配置
112 const tabList = ref([ 113 const tabList = ref([
113 {name: '待完成', id: '1'}, 114 {name: '待完成', id: '1'},
pages-sub/daily/quick-order/order-detail.vue
@@ -21,7 +21,7 @@ @@ -21,7 +21,7 @@
21 <up-cell 21 <up-cell
22 title="工单编号" 22 title="工单编号"
23 :value="orderDetail.orderNo || '--'" 23 :value="orderDetail.orderNo || '--'"
24 - class="up-line-1" 24 +
25 align="middle" 25 align="middle"
26 ></up-cell> 26 ></up-cell>
27 27
@@ -42,7 +42,6 @@ @@ -42,7 +42,6 @@
42 <up-cell 42 <up-cell
43 title="工单名称" 43 title="工单名称"
44 :value="orderDetail.orderName || '--'" 44 :value="orderDetail.orderName || '--'"
45 - class="up-line-1"  
46 align="middle" 45 align="middle"
47 ></up-cell> 46 ></up-cell>
48 47
pages-sub/problem/work-order-manage/add-maintain-order.vue
@@ -64,6 +64,7 @@ @@ -64,6 +64,7 @@
64 active-color="#1989fa" 64 active-color="#1989fa"
65 inactive-color="#666666" 65 inactive-color="#666666"
66 @change = "imgTabChange" 66 @change = "imgTabChange"
  67 +
67 > 68 >
68 <!-- 使用content插槽自定义每个tab的内容,添加红色*号 --> 69 <!-- 使用content插槽自定义每个tab的内容,添加红色*号 -->
69 <template #content="{ item, index }"> 70 <template #content="{ item, index }">
@@ -822,7 +823,5 @@ const submitWorkOrder = async () =&gt; { @@ -822,7 +823,5 @@ const submitWorkOrder = async () =&gt; {
822 font-weight: bold; 823 font-weight: bold;
823 } 824 }
824 825
825 -.tab-text {  
826 - font-size: 28rpx;  
827 -} 826 +
828 </style> 827 </style>
829 \ No newline at end of file 828 \ No newline at end of file
pages-sub/problem/work-order-manage/add-order.vue
@@ -7,6 +7,29 @@ @@ -7,6 +7,29 @@
7 ref="workOrderFormRef" 7 ref="workOrderFormRef"
8 labelWidth="190rpx" 8 labelWidth="190rpx"
9 > 9 >
  10 + <!-- 业务线单选框:移入form内,作为第一个表单项(核心调整) -->
  11 + <up-form-item
  12 + label="业务线"
  13 + prop="busiLineCn"
  14 + border-bottom
  15 + required
  16 + >
  17 + <up-radio-group
  18 + v-model="workOrderForm.busiLineCn"
  19 + placement="row"
  20 + @change="handleBusiLineChange"
  21 + >
  22 + <up-radio
  23 +
  24 + v-for="item in busiLineOptions"
  25 + :key="item.name"
  26 + :label="item.name"
  27 + :name="item.name"
  28 + ></up-radio>
  29 + </up-radio-group>
  30 +
  31 + </up-form-item>
  32 +
10 <!-- 1. 工单位置(地图选择) --> 33 <!-- 1. 工单位置(地图选择) -->
11 <up-form-item 34 <up-form-item
12 label="工单位置" 35 label="工单位置"
@@ -101,7 +124,7 @@ @@ -101,7 +124,7 @@
101 </up-form-item> 124 </up-form-item>
102 125
103 <!-- 问题照片(核心修复:绑定纯数组) --> 126 <!-- 问题照片(核心修复:绑定纯数组) -->
104 - <up-form-item label="问题照片" prop="problemImgs" required> 127 + <up-form-item label="问题照片" prop="problemImgs" border-bottom required>
105 <up-upload 128 <up-upload
106 :file-list="problemImgs.imgList.value||[]" 129 :file-list="problemImgs.imgList.value||[]"
107 @after-read="problemImgs.uploadImgs" 130 @after-read="problemImgs.uploadImgs"
@@ -128,7 +151,15 @@ @@ -128,7 +151,15 @@
128 placeholder="点击选择时间" 151 placeholder="点击选择时间"
129 ></up-input> 152 ></up-input>
130 <template #right> 153 <template #right>
131 - <up-icon name="arrow-right" size="16"></up-icon> 154 + <!-- 核心修复:用view包裹图标,添加双重@click.stop -->
  155 + <view v-if="workOrderForm.expectedFinishDate" @click.stop>
  156 + <up-icon
  157 + name="close"
  158 + size="16"
  159 + @click.stop="clearExpectedFinishDate"
  160 + ></up-icon>
  161 + </view>
  162 + <up-icon name="arrow-right" size="16" v-else></up-icon>
132 </template> 163 </template>
133 </up-form-item> 164 </up-form-item>
134 </up-form> 165 </up-form>
@@ -165,13 +196,51 @@ @@ -165,13 +196,51 @@
165 </template> 196 </template>
166 197
167 <script setup> 198 <script setup>
168 -import { ref, reactive, watch } from 'vue' 199 +import { ref, reactive } from 'vue'
169 import { onReady, onShow, onLoad } from '@dcloudio/uni-app'; 200 import { onReady, onShow, onLoad } from '@dcloudio/uni-app';
170 -import { useUploadImgs } from '@/common/utils/useUploadImgs' // 引入改造后的上传逻辑 201 +import { useUploadImgs } from '@/common/utils/useUploadImgs'
171 import { getRoadListByLatLng } from '@/api/common' 202 import { getRoadListByLatLng } from '@/api/common'
172 import { universalApproval, workorderCreate } from '@/api/work-order-manage/work-order-manage' 203 import { universalApproval, workorderCreate } from '@/api/work-order-manage/work-order-manage'
173 import { timeFormat } from '@/uni_modules/uview-plus' 204 import { timeFormat } from '@/uni_modules/uview-plus'
174 import { nextStepMap } from '@/common/utils/common' 205 import { nextStepMap } from '@/common/utils/common'
  206 +import { useUserStore } from '@/pinia/user';
  207 +
  208 +// ========== 状态管理 ==========
  209 +const userStore = useUserStore();
  210 +
  211 +// ========== 业务线相关状态(核心改造:已整合到workOrderForm中,删除冗余selectedBusiLine) ==========
  212 +// 业务线映射表(键:英文标识,值:中文名称;新增反向映射:中文->英文)
  213 +const busiLineMap = ref({
  214 + 'yl': '园林',
  215 + 'sz': '市政',
  216 + 'wy': '物业',
  217 + // 反向映射:用于通过中文名称(workOrderForm.busiLineCn)获取对应的英文标识
  218 + '园林': 'yl',
  219 + '市政': 'sz',
  220 + '物业': 'wy'
  221 +});
  222 +
  223 +// 业务线选项列表(仅保留name字段=中文名称,适配单选框配置)
  224 +const busiLineOptions = ref([]);
  225 +// 格式化业务线选项(兼容1个/2个/3个的情况,生成中文名称列表)
  226 +const formatBusiLineOptions = () => {
  227 + if (!userStore.userInfo?.user?.busiLine) {
  228 + busiLineOptions.value = [];
  229 + return;
  230 + }
  231 + const rawBusiLines = userStore.userInfo.user.busiLine.split(','); // 拆分字符串为数组 ["yl", "sz", "wy"]
  232 + console.log(rawBusiLines)
  233 + // 核心改造:仅生成name字段(值为中文名称),适配单选框:label="item.name"
  234 + busiLineOptions.value = rawBusiLines.map(item => ({
  235 + name: busiLineMap.value[item.trim()] // name=中文名称(园林/市政/物业)
  236 + }));
  237 + console.log(busiLineOptions.value)
  238 +};
  239 +
  240 +// ========== 工具方法:通过中文名称获取对应的英文标识 ==========
  241 +const getBusiLineEnByCn = (cnName) => {
  242 + return busiLineMap.value[cnName] || '';
  243 +};
175 244
176 // ========== 表单Ref ========== 245 // ========== 表单Ref ==========
177 const workOrderFormRef = ref(null) 246 const workOrderFormRef = ref(null)
@@ -190,7 +259,6 @@ if (!Array.isArray(problemImgs.rawImgList.value)) { @@ -190,7 +259,6 @@ if (!Array.isArray(problemImgs.rawImgList.value)) {
190 problemImgs.rawImgList.value = []; 259 problemImgs.rawImgList.value = [];
191 } 260 }
192 261
193 -  
194 // ========== 页面状态 ========== 262 // ========== 页面状态 ==========
195 // 通用弹窗控制 263 // 通用弹窗控制
196 const showActionSheet = ref(false) 264 const showActionSheet = ref(false)
@@ -213,22 +281,26 @@ const roadNameList = ref([]) @@ -213,22 +281,26 @@ const roadNameList = ref([])
213 const orderNameList = ref([]) 281 const orderNameList = ref([])
214 const pressingTypeList = ref([]) 282 const pressingTypeList = ref([])
215 283
216 -// ========== 工单表单数据 ========== 284 +// ========== 工单表单数据(核心调整:新增busiLineCn字段,绑定业务线) ==========
217 const workOrderForm = reactive({ 285 const workOrderForm = reactive({
218 - roadId: 0, // 道路ID  
219 - roadName: '', // 道路名称  
220 - workLocation: '', // 工单位置  
221 - orderName: '', // 工单名称  
222 - pressingType: '', // 紧急程度值(提交接口用)  
223 - pressingTypeName: '', // 紧急程度名称(显示用)  
224 - problemDesc: '', // 情况描述  
225 - lat: 0, // 纬度  
226 - lon: 0, // 经度  
227 - expectedFinishDate: '', // 希望完成时间 286 + busiLineCn: '', // 新增:业务线中文名称(用于form绑定、校验、回显)
  287 + roadId: 0, // 原有字段
  288 + roadName: '', // 原有字段
  289 + workLocation: '', // 原有字段
  290 + orderName: '', // 原有字段
  291 + pressingType: '', // 原有字段
  292 + pressingTypeName: '', // 原有字段
  293 + problemDesc: '', // 原有字段
  294 + lat: 0, // 原有字段
  295 + lon: 0, // 原有字段
  296 + expectedFinishDate: '', // 原有字段
228 }) 297 })
229 298
230 -// ========== 表单校验规则 ========== 299 +// ========== 表单校验规则(核心调整:新增busiLineCn校验规则) ==========
231 const workOrderFormRules = reactive({ 300 const workOrderFormRules = reactive({
  301 + busiLineCn: [ // 新增:业务线必选校验
  302 + { type: 'string', required: true, message: '请选择业务线', trigger: ['change', 'blur'] }
  303 + ],
232 workLocation: [ 304 workLocation: [
233 { type: 'string', required: true, message: '请选择工单位置', trigger: ['change', 'blur'] } 305 { type: 'string', required: true, message: '请选择工单位置', trigger: ['change', 'blur'] }
234 ], 306 ],
@@ -249,12 +321,19 @@ const workOrderFormRules = reactive({ @@ -249,12 +321,19 @@ const workOrderFormRules = reactive({
249 }) 321 })
250 322
251 // ========== 生命周期 ========== 323 // ========== 生命周期 ==========
252 -// 页面加载:读取本地存储的工单数据(核心改造 324 +// 页面加载:读取本地存储的工单数据(适配workOrderForm.busiLineCn
253 onLoad((options) => { 325 onLoad((options) => {
  326 + // 初始化业务线选项
  327 + formatBusiLineOptions();
  328 + // 默认选中第一个业务线(赋值给workOrderForm.busiLineCn,核心调整)
  329 + if (busiLineOptions.value.length > 0) {
  330 + workOrderForm.busiLineCn = busiLineOptions.value[0].name;
  331 + }
  332 +
254 // 判断是否为重新提交状态 333 // 判断是否为重新提交状态
255 console.log('434') 334 console.log('434')
256 console.log(options) 335 console.log(options)
257 - if (options.isRenew ==1 && options.tempKey) { 336 + if (options.isRenew == 1 && options.tempKey) {
258 isRenew.value = true; 337 isRenew.value = true;
259 const tempKey = options.tempKey; 338 const tempKey = options.tempKey;
260 339
@@ -307,9 +386,14 @@ onShow(() =&gt; { @@ -307,9 +386,14 @@ onShow(() =&gt; {
307 console.log('紧急程度列表:', pressingTypeList.value) 386 console.log('紧急程度列表:', pressingTypeList.value)
308 }) 387 })
309 388
310 -// ========== 核心方法:工单数据回显 ========== 389 +// ========== 核心方法:工单数据回显(适配workOrderForm.busiLineCn) ==========
311 const echoOrderData = (orderItem) => { 390 const echoOrderData = (orderItem) => {
312 - // 1. 基础表单字段回显(适配工单字段与表单字段映射) 391 + // 1. 回显业务线:将工单的英文busiLine转为中文名称,赋值给workOrderForm.busiLineCn(核心调整)
  392 + if (orderItem.busiLine) {
  393 + workOrderForm.busiLineCn = busiLineMap.value[orderItem.busiLine]; // 英文->中文(如wy->物业)
  394 + }
  395 +
  396 + // 2. 原有基础表单字段回显
313 workOrderForm.roadId = orderItem.roadId || 0; 397 workOrderForm.roadId = orderItem.roadId || 0;
314 workOrderForm.roadName = orderItem.roadName || ''; 398 workOrderForm.roadName = orderItem.roadName || '';
315 workOrderForm.workLocation = orderItem.lonLatAddress || orderItem.roadName || ''; 399 workOrderForm.workLocation = orderItem.lonLatAddress || orderItem.roadName || '';
@@ -321,7 +405,7 @@ const echoOrderData = (orderItem) =&gt; { @@ -321,7 +405,7 @@ const echoOrderData = (orderItem) =&gt; {
321 workOrderForm.lon = orderItem.lon || 0; 405 workOrderForm.lon = orderItem.lon || 0;
322 workOrderForm.expectedFinishDate = timeFormat(orderItem.expectedFinishDate, 'yyyy-mm-dd hh:MM:ss') || timeFormat(new Date(), 'yyyy-mm-dd hh:MM:ss'); 406 workOrderForm.expectedFinishDate = timeFormat(orderItem.expectedFinishDate, 'yyyy-mm-dd hh:MM:ss') || timeFormat(new Date(), 'yyyy-mm-dd hh:MM:ss');
323 407
324 - // 2. 上传图片回显(兼容useUploadImgs格式) 408 + // 3. 上传图片回显(兼容useUploadImgs格式)
325 if (orderItem.problemsImgs && Array.isArray(orderItem.problemsImgs) && orderItem.problemsImgs.length > 0) { 409 if (orderItem.problemsImgs && Array.isArray(orderItem.problemsImgs) && orderItem.problemsImgs.length > 0) {
326 const imgList = orderItem.problemsImgs.map((imgUrl, index) => ({ 410 const imgList = orderItem.problemsImgs.map((imgUrl, index) => ({
327 url: imgUrl, 411 url: imgUrl,
@@ -332,25 +416,70 @@ const echoOrderData = (orderItem) =&gt; { @@ -332,25 +416,70 @@ const echoOrderData = (orderItem) =&gt; {
332 problemImgs.rawImgList.value = imgList; 416 problemImgs.rawImgList.value = imgList;
333 } 417 }
334 418
335 - // 3. 自动获取道路列表(保证下拉框正常使用 419 + // 4. 自动获取道路列表(修改:传递当前选中的业务线英文标识
336 if (orderItem.lat && orderItem.lon) { 420 if (orderItem.lat && orderItem.lon) {
337 - getRoadListByLatLng({  
338 - companyCode: 'sls',  
339 - latitude: orderItem.lat,  
340 - longitude: orderItem.lon  
341 - }).then((roadRes) => {  
342 - if (Array.isArray(roadRes)) {  
343 - roadNameList.value = roadRes.map((item) => ({  
344 - name: item.roadName || '',  
345 - code: item.roadCode || '',  
346 - id: item.roadId || 0  
347 - }));  
348 - }  
349 - }).catch(err => console.error('回显道路列表失败:', err)); 421 + getRoadListByBusiLine(); // 改用封装后的方法,自动携带选中的业务线
350 } 422 }
351 }; 423 };
352 424
353 -// ========== 方法定义 ========== 425 +// ========== 业务线相关方法(适配workOrderForm.busiLineCn) ==========
  426 +/**
  427 + * 业务线切换事件:清空道路信息 + 重新请求道路列表
  428 + */
  429 +const handleBusiLineChange = () => {
  430 + // 1. 清空已渲染的道路名称和表单字段
  431 + workOrderForm.roadName = '';
  432 + workOrderForm.roadId = 0;
  433 + // 2. 清空道路名称列表数据
  434 + roadNameList.value = [];
  435 + // 3. 若已选择工单位置,重新请求对应业务线的道路列表
  436 + if (workOrderForm.workLocation) {
  437 + getRoadListByBusiLine();
  438 + }
  439 +};
  440 +
  441 +/**
  442 + * 封装:根据当前选中业务线(中文)获取英文标识,再请求道路列表
  443 + */
  444 +const getRoadListByBusiLine = async () => {
  445 + if (!workOrderForm.lat || !workOrderForm.lon) {
  446 + return;
  447 + }
  448 + // 核心:从workOrderForm.busiLineCn获取中文名称,转为英文标识(核心调整)
  449 + const busiLineEn = getBusiLineEnByCn(workOrderForm.busiLineCn);
  450 + if (!busiLineEn) {
  451 + uni.showToast({ title: '业务线标识异常', icon: 'none' });
  452 + return;
  453 + }
  454 +
  455 + try {
  456 + uni.showLoading({ title: '获取道路名称中...' });
  457 + const roadRes = await getRoadListByLatLng({
  458 + busiLine: busiLineEn, // 传递英文标识(yl/sz/wy),接口正常使用
  459 + // companyCode: 'sls', // 传递英文标识(yl/sz/wy),接口正常使用
  460 + latitude: workOrderForm.lat,
  461 + longitude: workOrderForm.lon
  462 + });
  463 + uni.hideLoading();
  464 + if (Array.isArray(roadRes)) {
  465 + roadNameList.value = roadRes.map((item) => ({
  466 + name: item.roadName || '',
  467 + code: item.roadCode || '',
  468 + id: item.roadId || 0
  469 + }));
  470 + } else {
  471 + roadNameList.value = [{ name: '未查询到道路名称', code: '', id: 0 }];
  472 + uni.showToast({ title: '未查询到该位置的道路信息', icon: 'none' });
  473 + }
  474 + } catch (err) {
  475 + uni.hideLoading();
  476 + console.error('获取道路名称失败:', err);
  477 + uni.showToast({ title: '获取道路名称失败,请重试', icon: 'none' });
  478 + roadNameList.value = [{ name: '获取失败,请重新选择位置', code: '', id: 0 }];
  479 + }
  480 +};
  481 +
  482 +// ========== 通用弹窗方法 ==========
354 /** 483 /**
355 * 打开通用下拉弹窗 484 * 打开通用下拉弹窗
356 */ 485 */
@@ -425,7 +554,20 @@ const handleActionSheetSelect = (e) =&gt; { @@ -425,7 +554,20 @@ const handleActionSheetSelect = (e) =&gt; {
425 * 返回上一页 554 * 返回上一页
426 */ 555 */
427 const navigateBack = () => { 556 const navigateBack = () => {
428 - uni.navigateBack() 557 + uni.reLaunch({
  558 + url: '/pages-sub/problem/work-order-manage/index', // 列表页路径
  559 + fail: () => {
  560 + // 兜底:若 reLaunch 失败,返回2层(跳过提交页)
  561 + uni.navigateBack({ delta: 2 });
  562 + }
  563 + });
  564 +}
  565 +
  566 +// 新增:清除希望完成时间的方法
  567 +const clearExpectedFinishDate = ()=> {
  568 + workOrderForm.expectedFinishDate = ''
  569 + // 清除后触发一次字段校验,确保校验状态更新
  570 + // this.$refs.inspectFormRef.validateField('expectedFinishDate')
429 } 571 }
430 572
431 /** 573 /**
@@ -445,31 +587,8 @@ const chooseWorkLocation = () =&gt; { @@ -445,31 +587,8 @@ const chooseWorkLocation = () =&gt; {
445 workOrderFormRef.value?.validateField('workLocation') 587 workOrderFormRef.value?.validateField('workLocation')
446 workOrderFormRef.value?.validateField('roadName') 588 workOrderFormRef.value?.validateField('roadName')
447 589
448 - try {  
449 - uni.showLoading({ title: '获取道路名称中...' })  
450 - const roadRes = await getRoadListByLatLng({  
451 - companyCode: 'sls',  
452 - latitude: res.latitude,  
453 - longitude: res.longitude  
454 - })  
455 - uni.hideLoading()  
456 -  
457 - if (Array.isArray(roadRes)) {  
458 - roadNameList.value = roadRes.map((item) => ({  
459 - name: item.roadName || '',  
460 - code: item.roadCode || '',  
461 - id: item.roadId || 0  
462 - }))  
463 - } else {  
464 - roadNameList.value = [{ name: '未查询到道路名称', code: '', id: 0 }]  
465 - uni.showToast({ title: '未查询到该位置的道路信息', icon: 'none' })  
466 - }  
467 - } catch (err) {  
468 - uni.hideLoading()  
469 - console.error('获取道路名称失败:', err)  
470 - uni.showToast({ title: '获取道路名称失败,请重试', icon: 'none' })  
471 - roadNameList.value = [{ name: '获取失败,请重新选择位置', code: '', id: 0 }]  
472 - } 590 + // 改用封装后的方法,自动携带选中的业务线英文标识
  591 + await getRoadListByBusiLine();
473 }, 592 },
474 fail: (err) => { 593 fail: (err) => {
475 console.error('选择位置失败:', err) 594 console.error('选择位置失败:', err)
@@ -495,13 +614,20 @@ const hideKeyboard = () =&gt; { @@ -495,13 +614,20 @@ const hideKeyboard = () =&gt; {
495 } 614 }
496 615
497 /** 616 /**
498 - * 提交工单(区分新增/重新提交,接口隔离 617 + * 提交工单(区分新增/重新提交,接口隔离:从workOrderForm.busiLineCn取值
499 */ 618 */
500 const submitWorkOrder = async () => { 619 const submitWorkOrder = async () => {
501 try { 620 try {
502 - // 先执行表单校验 621 + // 先执行表单校验(包含业务线校验)
503 await workOrderFormRef.value.validate() 622 await workOrderFormRef.value.validate()
504 623
  624 + // 核心:从workOrderForm.busiLineCn获取中文名称,转为英文标识(核心调整)
  625 + const busiLineEn = getBusiLineEnByCn(workOrderForm.busiLineCn);
  626 + if (!busiLineEn) {
  627 + uni.showToast({ title: '业务线选择异常,请重新选择', icon: 'none' });
  628 + return;
  629 + }
  630 +
505 // 构建公共提交数据 631 // 构建公共提交数据
506 const commonSubmitData = { 632 const commonSubmitData = {
507 roadId: workOrderForm.roadId, 633 roadId: workOrderForm.roadId,
@@ -514,11 +640,10 @@ const submitWorkOrder = async () =&gt; { @@ -514,11 +640,10 @@ const submitWorkOrder = async () =&gt; {
514 lonLatAddress: workOrderForm.workLocation, 640 lonLatAddress: workOrderForm.workLocation,
515 pressingType: workOrderForm.pressingType, 641 pressingType: workOrderForm.pressingType,
516 orderName: workOrderForm.orderName, 642 orderName: workOrderForm.orderName,
517 - // expectedFinishDate: workOrderForm.expectedFinishDate,  
518 expectedFinishDate: new Date(workOrderForm.expectedFinishDate).getTime(), 643 expectedFinishDate: new Date(workOrderForm.expectedFinishDate).getTime(),
519 sourceId: 1, 644 sourceId: 1,
520 - sourceName: '园林',  
521 - busiLine: 'yl' 645 + sourceName: workOrderForm.busiLineCn, // 从form字段获取业务线中文名称
  646 + busiLine: busiLineEn // 传递英文标识(yl/sz/wy),接口正常使用
522 } 647 }
523 648
524 // 显示加载中 649 // 显示加载中
@@ -567,7 +692,7 @@ const submitWorkOrder = async () =&gt; { @@ -567,7 +692,7 @@ const submitWorkOrder = async () =&gt; {
567 // 接口调用失败 692 // 接口调用失败
568 console.error(isRenew.value ? '工单重新提交失败:' : '工单提交失败:', error) 693 console.error(isRenew.value ? '工单重新提交失败:' : '工单提交失败:', error)
569 uni.showToast({ 694 uni.showToast({
570 - title: isRenew.value ? '重新提交失败,请重试' : '提交失败,请重试', 695 + title: isRenew.value ? error.msg : error.msg,
571 icon: 'none', 696 icon: 'none',
572 duration: 2000 697 duration: 2000
573 }) 698 })
@@ -583,6 +708,7 @@ const submitWorkOrder = async () =&gt; { @@ -583,6 +708,7 @@ const submitWorkOrder = async () =&gt; {
583 padding-bottom: 100rpx; // 给底部按钮留空间 708 padding-bottom: 100rpx; // 给底部按钮留空间
584 } 709 }
585 710
  711 +
586 // 工单表单内容容器 712 // 工单表单内容容器
587 .work-order-form-content { 713 .work-order-form-content {
588 background: #fff; 714 background: #fff;
pages-sub/problem/work-order-manage/index.vue
@@ -51,6 +51,7 @@ @@ -51,6 +51,7 @@
51 v-model="orderList" 51 v-model="orderList"
52 @query="queryList" 52 @query="queryList"
53 :auto-show-system-loading="true" 53 :auto-show-system-loading="true"
  54 +
54 > 55 >
55 <template #empty> 56 <template #empty>
56 <empty-view/> 57 <empty-view/>
@@ -75,7 +76,7 @@ @@ -75,7 +76,7 @@
75 </view> 76 </view>
76 <view class="u-body-item u-flex"> 77 <view class="u-body-item u-flex">
77 <view class="u-body-item-title">工单位置:</view> 78 <view class="u-body-item-title">工单位置:</view>
78 - <view class="u-line-1 u-body-value">{{ item.roadName || '-' }}</view> 79 + <view class="u-line-1 u-body-value">{{ item.lonLatAddress || '-' }}</view>
79 </view> 80 </view>
80 <view class="u-body-item u-flex"> 81 <view class="u-body-item u-flex">
81 <view class="u-body-item-title">工单名称:</view> 82 <view class="u-body-item-title">工单名称:</view>
@@ -253,6 +254,7 @@ @@ -253,6 +254,7 @@
253 :file-list="acceptImgs.imgList" 254 :file-list="acceptImgs.imgList"
254 @after-read="acceptImgs.uploadImgs" 255 @after-read="acceptImgs.uploadImgs"
255 @delete="acceptImgs.deleteImg" 256 @delete="acceptImgs.deleteImg"
  257 +
256 multiple 258 multiple
257 width="70" 259 width="70"
258 height="70" 260 height="70"
@@ -268,7 +270,7 @@ @@ -268,7 +270,7 @@
268 270
269 <script setup> 271 <script setup>
270 import { ref, computed, watch } from 'vue'; 272 import { ref, computed, watch } from 'vue';
271 -import { onShow } from '@dcloudio/uni-app'; 273 +import { onShow, onLoad } from '@dcloudio/uni-app';
272 import { timeFormat } from '@/uni_modules/uview-plus'; 274 import { timeFormat } from '@/uni_modules/uview-plus';
273 import { 275 import {
274 myBuzSimplePage, 276 myBuzSimplePage,
@@ -276,7 +278,7 @@ import { @@ -276,7 +278,7 @@ import {
276 doneBuzSimplePage, 278 doneBuzSimplePage,
277 universalApproval 279 universalApproval
278 } from '@/api/work-order-manage/work-order-manage' 280 } from '@/api/work-order-manage/work-order-manage'
279 -// 假设从用户store获取角色信息 281 +// 从用户store获取角色信息
280 import { useUserStore } from '@/pinia/user'; 282 import { useUserStore } from '@/pinia/user';
281 import { nextStepMap, buzStatusMap } from '@/common/utils/common' 283 import { nextStepMap, buzStatusMap } from '@/common/utils/common'
282 // 引入图片上传组合式函数(与参考页面一致) 284 // 引入图片上传组合式函数(与参考页面一致)
@@ -399,7 +401,16 @@ const handleSearch = (val) =&gt; { @@ -399,7 +401,16 @@ const handleSearch = (val) =&gt; {
399 const handleDetail = (item) => { 401 const handleDetail = (item) => {
400 // 0-待办 1我发起的- 2-已办 402 // 0-待办 1我发起的- 2-已办
401 uni.navigateTo({ 403 uni.navigateTo({
402 - url: `/pages-sub/problem/work-order-manage/order-detail?taskId=${item.taskId}&activeTab=${activeTab.value}&processInstanceId=${item.processInstanceId}` 404 + url: `/pages-sub/problem/work-order-manage/order-detail?taskId=${item.taskId}&activeTab=${activeTab.value}&processInstanceId=${item.processInstanceId}`,
  405 + events: {
  406 + // 自定义事件名:needRefresh(与详情页保持一致)
  407 + needRefresh: () => {
  408 + console.log('详情页返回,触发工单列表刷新');
  409 + if (paging.value) {
  410 + paging.value.reload(); // 刷新z-paging列表
  411 + }
  412 + }
  413 + }
403 }); 414 });
404 }; 415 };
405 416
@@ -640,7 +651,7 @@ const handleAcceptModalConfirm = async () =&gt; { @@ -640,7 +651,7 @@ const handleAcceptModalConfirm = async () =&gt; {
640 }; 651 };
641 652
642 // 页面初始化 653 // 页面初始化
643 -onShow(() => { 654 +onLoad(() => {
644 // 初始化加载列表 655 // 初始化加载列表
645 paging.value?.reload(); 656 paging.value?.reload();
646 }); 657 });
pages-sub/problem/work-order-manage/order-detail.vue
@@ -38,12 +38,12 @@ @@ -38,12 +38,12 @@
38 <view style="min-width: 200rpx">工单位置</view> 38 <view style="min-width: 200rpx">工单位置</view>
39 </template> 39 </template>
40 <template #value> 40 <template #value>
41 - <view class="common-text-color up-line-1">{{ orderDetail.roadName || '--' }}</view> 41 + <view class="common-text-color up-line-1">{{ orderDetail.lonLatAddress || '--' }}</view>
42 </template> 42 </template>
43 </up-cell> 43 </up-cell>
44 44
45 <!-- 工单名称 --> 45 <!-- 工单名称 -->
46 - <up-cell align="middle"> 46 + <up-cell align="middle">
47 <template #title> 47 <template #title>
48 <view style="min-width: 200rpx">工单名称</view> 48 <view style="min-width: 200rpx">工单名称</view>
49 </template> 49 </template>
@@ -99,7 +99,9 @@ @@ -99,7 +99,9 @@
99 <up-album 99 <up-album
100 v-if="!!orderDetail.problemsImgs?.length" 100 v-if="!!orderDetail.problemsImgs?.length"
101 :urls="orderDetail.problemsImgs.slice(0, 3)" 101 :urls="orderDetail.problemsImgs.slice(0, 3)"
102 - singleSize="70" 102 + :singleSize="70"
  103 + :multipleSize="70"
  104 +
103 :preview-full-image="true" 105 :preview-full-image="true"
104 ></up-album> 106 ></up-album>
105 <text v-else class="empty-text">暂无问题照片</text> 107 <text v-else class="empty-text">暂无问题照片</text>
@@ -134,7 +136,9 @@ @@ -134,7 +136,9 @@
134 </template> 136 </template>
135 <template #value> 137 <template #value>
136 <view class="common-text-color up-line-1"> 138 <view class="common-text-color up-line-1">
137 - {{ Array.isArray(orderDetail.coHandlersName) && orderDetail.coHandlersName.length > 0 ? orderDetail.coHandlersName.join(',') : '--' }} 139 + {{
  140 + Array.isArray(orderDetail.coHandlersName) && orderDetail.coHandlersName.length > 0 ? orderDetail.coHandlersName.join(',') : '--'
  141 + }}
138 </view> 142 </view>
139 </template> 143 </template>
140 </up-cell> 144 </up-cell>
@@ -154,10 +158,11 @@ @@ -154,10 +158,11 @@
154 <up-album 158 <up-album
155 v-if="currentImgList.length" 159 v-if="currentImgList.length"
156 :urls="currentImgList.slice(0, 3)" 160 :urls="currentImgList.slice(0, 3)"
157 - singleSize="80"  
158 - multipleSize="80" 161 + :singleSize="70"
  162 + :multipleSize="70"
  163 +
159 :preview-full-image="true" 164 :preview-full-image="true"
160 - class="img-album" 165 + class="img-album custom-album"
161 ></up-album> 166 ></up-album>
162 <text v-else class="empty-img-text">暂无图片</text> 167 <text v-else class="empty-img-text">暂无图片</text>
163 </view> 168 </view>
@@ -176,19 +181,21 @@ @@ -176,19 +181,21 @@
176 inactive-color="#999" 181 inactive-color="#999"
177 class="vertical-steps" 182 class="vertical-steps"
178 > 183 >
179 - <template > 184 + <template>
180 <up-steps-item 185 <up-steps-item
181 v-for="(item, index) in processData.activityNodes" 186 v-for="(item, index) in processData.activityNodes"
182 :key="`${item.id}_${index}`" 187 :key="`${item.id}_${index}`"
183 > 188 >
184 <!-- 唯一自定义模板:content,包含标题、描述、相册所有内容 --> 189 <!-- 唯一自定义模板:content,包含标题、描述、相册所有内容 -->
185 <template #content> 190 <template #content>
186 - <view class="step-content-wrap" > 191 + <view class="step-content-wrap">
187 <!-- 1. 原标题内容:节点名称 + 操作人 --> 192 <!-- 1. 原标题内容:节点名称 + 操作人 -->
188 <view class="step-title"> 193 <view class="step-title">
189 {{ item.name }} 194 {{ item.name }}
190 <text class="operator-name"> 195 <text class="operator-name">
191 - {{ item.tasks && item.tasks[0]?.assigneeUser?.nickname ? '(' + item.tasks[0].assigneeUser.nickname + ')' : '(未知操作人)' }} 196 + {{
  197 + item.tasks && item.tasks[0]?.assigneeUser?.nickname ? '(' + item.tasks[0].assigneeUser.nickname + ')' : '(未知操作人)'
  198 + }}
192 </text> 199 </text>
193 </view> 200 </view>
194 201
@@ -202,7 +209,7 @@ @@ -202,7 +209,7 @@
202 </view> 209 </view>
203 210
204 <view class="reason-line up-line-1" v-if="index!==0&&item.endTime"> 211 <view class="reason-line up-line-1" v-if="index!==0&&item.endTime">
205 - 总耗时:{{ calculateFormatTimeDiff(item.startTime , item.endTime) }} 212 + 总耗时:{{ calculateFormatTimeDiff(item.startTime, item.endTime) }}
206 </view> 213 </view>
207 214
208 <!-- 原因行 --> 215 <!-- 原因行 -->
@@ -216,8 +223,9 @@ @@ -216,8 +223,9 @@
216 <up-album 223 <up-album
217 v-if="item.tasks && item.tasks[0]?.attattmentUrls && item.tasks[0].attattmentUrls.length" 224 v-if="item.tasks && item.tasks[0]?.attattmentUrls && item.tasks[0].attattmentUrls.length"
218 :urls="item.tasks[0].attattmentUrls.slice(0, 3)" 225 :urls="item.tasks[0].attattmentUrls.slice(0, 3)"
219 - singleSize="70"  
220 - multipleSize="70" 226 + :singleSize="70"
  227 + :multipleSize="70"
  228 +
221 :preview-full-image="true" 229 :preview-full-image="true"
222 class="step-album" 230 class="step-album"
223 ></up-album> 231 ></up-album>
@@ -359,9 +367,9 @@ import { @@ -359,9 +367,9 @@ import {
359 getApprovalDetail, 367 getApprovalDetail,
360 universalApproval 368 universalApproval
361 } from '@/api/work-order-manage/work-order-manage'; 369 } from '@/api/work-order-manage/work-order-manage';
362 -import { nextStepMap, buzStatusMap, calculateFormatTimeDiff } from '@/common/utils/common' 370 +import {nextStepMap, buzStatusMap, calculateFormatTimeDiff} from '@/common/utils/common'
363 // 引入图片上传组合式函数 371 // 引入图片上传组合式函数
364 -import { useUploadImgs } from '@/common/utils/useUploadImgs' 372 +import {useUploadImgs} from '@/common/utils/useUploadImgs'
365 373
366 // 状态管理 374 // 状态管理
367 const loading = ref(true); 375 const loading = ref(true);
@@ -407,7 +415,7 @@ const orderDetail = ref&lt;any&gt;({ @@ -407,7 +415,7 @@ const orderDetail = ref&lt;any&gt;({
407 curingLevelName: '', 415 curingLevelName: '',
408 commitDate: 0, 416 commitDate: 0,
409 finishDate: 0, 417 finishDate: 0,
410 - expectedFinishDate:0, 418 + expectedFinishDate: 0,
411 pressingType: 0, 419 pressingType: 0,
412 userId: 0, 420 userId: 0,
413 userName: '', 421 userName: '',
@@ -450,7 +458,7 @@ const orderDetail = ref&lt;any&gt;({ @@ -450,7 +458,7 @@ const orderDetail = ref&lt;any&gt;({
450 const currentImgList = ref([]) 458 const currentImgList = ref([])
451 459
452 const tabKeyMap = ['startImgs', 'processingImgs', 'endImgs', 'personImgs', 'materialImgs']; 460 const tabKeyMap = ['startImgs', 'processingImgs', 'endImgs', 'personImgs', 'materialImgs'];
453 -const imgTabChange = (({index}: {index: number}) => { 461 +const imgTabChange = (({index}: { index: number }) => {
454 console.log(index) 462 console.log(index)
455 const currentKey = tabKeyMap[index] 463 const currentKey = tabKeyMap[index]
456 console.log(currentKey) 464 console.log(currentKey)
@@ -476,7 +484,7 @@ const getLimitReason = (reason: string | null | undefined) =&gt; { @@ -476,7 +484,7 @@ const getLimitReason = (reason: string | null | undefined) =&gt; {
476 * @returns {number} 当前激活的步骤索引(从0开始) 484 * @returns {number} 当前激活的步骤索引(从0开始)
477 */ 485 */
478 const getCurrentStepIndex = () => { 486 const getCurrentStepIndex = () => {
479 - const { activityNodes } = processData.value; 487 + const {activityNodes} = processData.value;
480 if (!activityNodes || !activityNodes.length) return 0; 488 if (!activityNodes || !activityNodes.length) return 0;
481 489
482 // 2. 若没有处理中的节点(全部已完成),则激活最后一个节点 490 // 2. 若没有处理中的节点(全部已完成),则激活最后一个节点
@@ -568,7 +576,7 @@ const rejectImgs = useUploadImgs({ @@ -568,7 +576,7 @@ const rejectImgs = useUploadImgs({
568 // 监听上传实例响应式变化,解决u-upload不刷新问题 576 // 监听上传实例响应式变化,解决u-upload不刷新问题
569 watch(() => rejectImgs.rawImgList.value, (newVal) => { 577 watch(() => rejectImgs.rawImgList.value, (newVal) => {
570 rejectImgs.imgList = newVal 578 rejectImgs.imgList = newVal
571 -}, { deep: true }) 579 +}, {deep: true})
572 580
573 // ========== 验收弹窗相关状态(新增图片上传实例) ========== 581 // ========== 验收弹窗相关状态(新增图片上传实例) ==========
574 const acceptModalShow = ref(false); // 验收弹窗显示开关 582 const acceptModalShow = ref(false); // 验收弹窗显示开关
@@ -586,7 +594,7 @@ const acceptImgs = useUploadImgs({ @@ -586,7 +594,7 @@ const acceptImgs = useUploadImgs({
586 // 监听验收图片上传实例响应式变化 594 // 监听验收图片上传实例响应式变化
587 watch(() => acceptImgs.rawImgList.value, (newVal) => { 595 watch(() => acceptImgs.rawImgList.value, (newVal) => {
588 acceptImgs.imgList = newVal 596 acceptImgs.imgList = newVal
589 -}, { deep: true }) 597 +}, {deep: true})
590 598
591 // ========== 生成临时key ========== 599 // ========== 生成临时key ==========
592 const generateTempKey = () => { 600 const generateTempKey = () => {
@@ -742,7 +750,7 @@ const handleProcess = async (item: any) =&gt; { @@ -742,7 +750,7 @@ const handleProcess = async (item: any) =&gt; {
742 const requestData = { 750 const requestData = {
743 "returnImgs": rejectImgs.getSuccessImgUrls(), 751 "returnImgs": rejectImgs.getSuccessImgUrls(),
744 "workerDataId": item.id, 752 "workerDataId": item.id,
745 - "taskKey":'ylInspectorStart', 753 + "taskKey": 'ylInspectorStart',
746 "taskId": item.taskId, 754 "taskId": item.taskId,
747 "operateType": 200, 755 "operateType": 200,
748 "agree": 1, 756 "agree": 1,
@@ -812,13 +820,17 @@ const handleAcceptModalConfirm = async () =&gt; { @@ -812,13 +820,17 @@ const handleAcceptModalConfirm = async () =&gt; {
812 } 820 }
813 const acceptRes = await universalApproval(postData); 821 const acceptRes = await universalApproval(postData);
814 // 4. 操作成功处理 822 // 4. 操作成功处理
815 - uni.showToast({title: '提交成功', icon: 'success', duration: 1000}); 823 +
816 handleAcceptModalCancel(); // 清空状态 824 handleAcceptModalCancel(); // 清空状态
817 // 重新获取工单详情,刷新页面 825 // 重新获取工单详情,刷新页面
818 // await DetailQuery(taskId.value); 826 // await DetailQuery(taskId.value);
819 - uni.reLaunch({  
820 - url: `/pages-sub/problem/work-order-manage/index`  
821 - }); 827 + // uni.reLaunch({
  828 + // url: `/pages-sub/problem/work-order-manage/index`
  829 + // });
  830 + eventChannel.emit('needRefresh');
  831 + // 4. 返回列表页
  832 + uni.navigateBack({delta: 1});
  833 + uni.showToast({title: '提交成功', icon: 'success', duration: 1000});
822 } catch (error) { 834 } catch (error) {
823 // 5. 操作失败处理 835 // 5. 操作失败处理
824 console.error('验收失败:', error); 836 console.error('验收失败:', error);
@@ -888,13 +900,14 @@ onShow(() =&gt; { @@ -888,13 +900,14 @@ onShow(() =&gt; {
888 // 图片内容区 900 // 图片内容区
889 .img-tab-content { 901 .img-tab-content {
890 padding: 20rpx 15px ; 902 padding: 20rpx 15px ;
891 - min-height: 120rpx;  
892 - display: flex;  
893 - align-items: center;  
894 - justify-content: center;  
895 - 903 + //min-height: 120rpx;
  904 + //display: flex;
  905 + //align-items: center;
  906 + //justify-content: center;
  907 + //
896 .img-album { 908 .img-album {
897 - width: 100%; 909 + //width: 100%;
  910 +
898 } 911 }
899 912
900 .empty-img-text { 913 .empty-img-text {
@@ -973,8 +986,7 @@ onShow(() =&gt; { @@ -973,8 +986,7 @@ onShow(() =&gt; {
973 padding: 10rpx 0; 986 padding: 10rpx 0;
974 987
975 .step-album { 988 .step-album {
976 - width: 100%;  
977 - max-width: 300rpx; 989 + //width: 100%;
978 } 990 }
979 991
980 .no-img-tip { 992 .no-img-tip {
@@ -1034,4 +1046,11 @@ onShow(() =&gt; { @@ -1034,4 +1046,11 @@ onShow(() =&gt; {
1034 .mt-30 { 1046 .mt-30 {
1035 margin-top: 30rpx; 1047 margin-top: 30rpx;
1036 } 1048 }
  1049 +// 针对 up-album 单图容器的样式(穿透 scoped 限制)
  1050 +:deep .u-album__row__wrapper image {
  1051 + width: 70px !important; // 与多图保持一致
  1052 + height: 70px !important;
  1053 +}
  1054 +
  1055 +
1037 </style> 1056 </style>
1038 \ No newline at end of file 1057 \ No newline at end of file
pages.json
1 { 1 {
2 "pages": [ 2 "pages": [
3 { 3 {
4 - "path": "pages/login/index", 4 + "path": "pages/workbench/index",
5 "style": { 5 "style": {
6 - "navigationBarTitleText": "登录" 6 + "navigationBarTitleText": "工作台",
  7 + "navigationStyle": "custom"
7 } 8 }
8 }, 9 },
9 { 10 {
10 - "path": "pages/index/index", 11 + "path": "pages/login/index",
11 "style": { 12 "style": {
12 - "navigationBarTitleText": "首页" 13 + "navigationBarTitleText": "登录"
13 } 14 }
14 }, 15 },
15 { 16 {
16 - "path": "pages/workbench/index", 17 + "path": "pages/index/index",
17 "style": { 18 "style": {
18 - "navigationBarTitleText": "工作台",  
19 - "navigationStyle": "custom" 19 + "navigationBarTitleText": "首页"
20 } 20 }
21 }, 21 },
  22 +
22 { 23 {
23 "path": "pages/mine/index", 24 "path": "pages/mine/index",
24 "style": { 25 "style": {
@@ -91,7 +92,7 @@ @@ -91,7 +92,7 @@
91 92
92 { 93 {
93 "path": "maintain-manage/road-detail-list", 94 "path": "maintain-manage/road-detail-list",
94 - "style": { "navigationBarTitleText": "道路明细列表" } 95 + "style": { "navigationBarTitleText": "计划明细" }
95 }, 96 },
96 97
97 { 98 {
pages/login/index.vue
@@ -107,10 +107,10 @@ const checkLoginStatus = () =&gt; { @@ -107,10 +107,10 @@ const checkLoginStatus = () =&gt; {
107 // 已登录则直接跳首页 107 // 已登录则直接跳首页
108 if (userStore.isLogin) { 108 if (userStore.isLogin) {
109 uni.switchTab({ 109 uni.switchTab({
110 - url: globalConfig.router.tabBarList[1].path, 110 + url: '/pages/workbench/index',
111 fail: () => { 111 fail: () => {
112 // 非tabBar页面用redirectTo 112 // 非tabBar页面用redirectTo
113 - uni.redirectTo({ url: '/pages/workbench/index' }); 113 + uni.reLaunch({ url: '/pages/workbench/index' });
114 } 114 }
115 }); 115 });
116 return; 116 return;
@@ -157,7 +157,7 @@ const handleLogin = async () =&gt; { @@ -157,7 +157,7 @@ const handleLogin = async () =&gt; {
157 password: form.password 157 password: form.password
158 }); 158 });
159 159
160 - uni.showToast({ title: '登录成功', icon: 'success', duration: 1500 }); 160 + uni.showToast({ title: '登录成功', icon: 'success', duration: 1000 });
161 161
162 // 登录成功后跳转首页 162 // 登录成功后跳转首页
163 setTimeout(() => { 163 setTimeout(() => {
@@ -165,10 +165,10 @@ const handleLogin = async () =&gt; { @@ -165,10 +165,10 @@ const handleLogin = async () =&gt; {
165 url: globalConfig.router.tabBarList[1].path, 165 url: globalConfig.router.tabBarList[1].path,
166 fail: (err) => { 166 fail: (err) => {
167 console.warn('tabBar跳转失败,切换为普通跳转:', err); 167 console.warn('tabBar跳转失败,切换为普通跳转:', err);
168 - uni.redirectTo({ url: '/pages/workbench/index' }); 168 + uni.reLaunch({ url: '/pages/workbench/index' });
169 } 169 }
170 }); 170 });
171 - }, 1500); 171 + }, 1000);
172 } catch (err) { 172 } catch (err) {
173 console.error('登录失败详情:', err); 173 console.error('登录失败详情:', err);
174 const errorMsg = 174 const errorMsg =
pages/workbench/index.vue
@@ -98,6 +98,39 @@ const filteredModuleList = computed(() =&gt; { @@ -98,6 +98,39 @@ const filteredModuleList = computed(() =&gt; {
98 onShow(async () => { 98 onShow(async () => {
99 try { 99 try {
100 loading.value = true; 100 loading.value = true;
  101 +
  102 + // ========== 核心新增:登录状态判断 ==========
  103 + // 1. 定义登录判断逻辑(多维度校验,确保准确性)
  104 + const isLogin = () => {
  105 +
  106 + // 从缓存获取token(核心登录标识)
  107 + const token = cache.get(globalConfig.cache.tokenKey) || userStore.token;
  108 + // 从缓存/Pinia获取用户信息
  109 + const userInfo = cache.get(globalConfig.cache.userInfoKey) || userStore.userInfo;
  110 + // 满足任一核心条件即视为已登录
  111 + return (!!token && !!userInfo);
  112 + };
  113 +
  114 + // 2. 未登录处理:跳转登录页,阻止后续逻辑执行
  115 + if (!isLogin()) {
  116 + uni.showToast({ title: '请先登录', icon: 'none', duration: 1500 });
  117 + // 延迟跳转,确保提示语正常显示
  118 + setTimeout(() => {
  119 + // 使用reLaunch跳转,清空页面栈,避免返回当前菜单页
  120 + uni.reLaunch({
  121 + url: '/pages/login/login', // 替换为你的实际登录页路径
  122 + fail: (err) => {
  123 + console.error('跳转登录页失败:', err);
  124 + uni.showToast({ title: '跳转登录页异常', icon: 'none' });
  125 + }
  126 + });
  127 + }, 1500);
  128 + // 隐藏加载状态
  129 + loading.value = false;
  130 + // 终止后续代码执行
  131 + return;
  132 + }
  133 +
101 const rawMenuData = userStore.moduleListInfo || cache.get(globalConfig.cache.moduleListKey); 134 const rawMenuData = userStore.moduleListInfo || cache.get(globalConfig.cache.moduleListKey);
102 moduleList.value = rawMenuData || []; 135 moduleList.value = rawMenuData || [];
103 loading.value = false; 136 loading.value = false;
pinia/user.js
1 import { defineStore } from 'pinia'; 1 import { defineStore } from 'pinia';
2 import cache from '@/common/utils/cache'; 2 import cache from '@/common/utils/cache';
3 import globalConfig from '@/common/config/global'; 3 import globalConfig from '@/common/config/global';
4 -import { login, getUserInfo, logout, moduleList, getSimpleDictDataList } from '@/api/user'; 4 +// 新增:导入 refreshToken 接口
  5 +import { login, getUserInfo, logout, moduleList, getSimpleDictDataList, refreshToken } from '@/api/user';
  6 +
  7 +// 新增:定义刷新Token的定时器标识(避免重复创建定时器)
  8 +let refreshTokenTimer = null;
5 9
6 export const useUserStore = defineStore('user', { 10 export const useUserStore = defineStore('user', {
7 // 修复1:删除重复的 state 定义,只保留根层级的 state 11 // 修复1:删除重复的 state 定义,只保留根层级的 state
8 state: () => ({ 12 state: () => ({
9 token: cache.get(globalConfig.cache.tokenKey) || '', // 初始值从缓存取(兼容持久化) 13 token: cache.get(globalConfig.cache.tokenKey) || '', // 初始值从缓存取(兼容持久化)
  14 + refreshToken: cache.get(globalConfig.cache.refreshTokenKey) || '', // 新增:Refresh Token 状态
10 userInfo: cache.get(globalConfig.cache.userInfoKey) || {}, 15 userInfo: cache.get(globalConfig.cache.userInfoKey) || {},
11 userId: cache.get(globalConfig.cache.userIdKey) || '', 16 userId: cache.get(globalConfig.cache.userIdKey) || '',
12 moduleListInfo: cache.get(globalConfig.cache.moduleListKey) || '', 17 moduleListInfo: cache.get(globalConfig.cache.moduleListKey) || '',
@@ -23,14 +28,22 @@ export const useUserStore = defineStore(&#39;user&#39;, { @@ -23,14 +28,22 @@ export const useUserStore = defineStore(&#39;user&#39;, {
23 } 28 }
24 return true; 29 return true;
25 }, 30 },
26 - permissions: (state) => state.userInfo.permissions || [] 31 + permissions: (state) => state.userInfo.permissions || [],
  32 + // 新增:判断是否需要刷新Token(过期前30秒触发,避免已过期才刷新)
  33 + needRefreshToken: (state) => {
  34 + if (!state.token || !state.refreshToken) return false;
  35 + const remainingTime = state.expireTime - Date.now();
  36 + // 剩余时间大于0秒 且 小于30秒(即将过期)
  37 + return remainingTime > 0 && remainingTime < 30 * 1000;
  38 + }
27 }, 39 },
28 40
29 actions: { 41 actions: {
30 async login(params) { 42 async login(params) {
31 try { 43 try {
32 const res = await login(params); 44 const res = await login(params);
33 - const { accessToken, expiresTime, userId } = res; 45 + // 新增:从登录接口返回值中获取 refreshToken(若接口返回字段名不一致,可调整,如 res.refresh_token)
  46 + const { accessToken, expiresTime, userId, refreshToken } = res;
34 47
35 if (!accessToken) { 48 if (!accessToken) {
36 throw new Error('登录失败,未获取到令牌'); 49 throw new Error('登录失败,未获取到令牌');
@@ -38,6 +51,7 @@ export const useUserStore = defineStore(&#39;user&#39;, { @@ -38,6 +51,7 @@ export const useUserStore = defineStore(&#39;user&#39;, {
38 51
39 // 更新 Pinia state 52 // 更新 Pinia state
40 this.token = accessToken; 53 this.token = accessToken;
  54 + this.refreshToken = refreshToken || ''; // 新增:存储Refresh Token
41 this.expireTime = expiresTime; 55 this.expireTime = expiresTime;
42 this.userId = userId; 56 this.userId = userId;
43 this.userInfo = {}; 57 this.userInfo = {};
@@ -70,6 +84,9 @@ export const useUserStore = defineStore(&#39;user&#39;, { @@ -70,6 +84,9 @@ export const useUserStore = defineStore(&#39;user&#39;, {
70 uni.showToast({ title: '获取字典失败,可正常使用', icon: 'none' }); 84 uni.showToast({ title: '获取字典失败,可正常使用', icon: 'none' });
71 } 85 }
72 86
  87 + // 新增:登录成功后,启动 Token 自动刷新定时器
  88 + this.startRefreshTokenTimer();
  89 +
73 return { ...res, userInfo, moduleListInfo }; 90 return { ...res, userInfo, moduleListInfo };
74 } catch (err) { 91 } catch (err) {
75 console.error('登录流程失败:', err); 92 console.error('登录流程失败:', err);
@@ -77,6 +94,73 @@ export const useUserStore = defineStore(&#39;user&#39;, { @@ -77,6 +94,73 @@ export const useUserStore = defineStore(&#39;user&#39;, {
77 } 94 }
78 }, 95 },
79 96
  97 + // 新增:调用刷新Token接口,返回刷新是否成功
  98 + async refreshTokenApi() {
  99 + // 前置校验:无Refresh Token直接返回失败
  100 + if (!this.refreshToken) {
  101 + console.warn('无刷新令牌,无法刷新Token');
  102 + this.logout(); // 无刷新令牌,直接退出登录
  103 + return false;
  104 + }
  105 +
  106 + try {
  107 + // 调用修正后的 refreshToken 接口,传入当前的 refreshToken
  108 + const res = await refreshToken(this.refreshToken);
  109 +
  110 + // 校验接口返回结果(根据你的实际接口返回格式调整)
  111 + if (!res || !res.accessToken) {
  112 + throw new Error('刷新Token失败,未获取到新令牌');
  113 + }
  114 +
  115 + // 更新 Token 相关状态
  116 + this.token = res.accessToken; // 新的访问令牌
  117 + this.refreshToken = res.refreshToken || this.refreshToken; // 若接口返回新的Refresh Token则更新
  118 + this.expireTime = res.expiresTime; // 新的过期时间
  119 + console.log('Token 刷新成功');
  120 + return true;
  121 + } catch (err) {
  122 + console.error('刷新Token失败:', err);
  123 + // 刷新失败,直接退出登录
  124 + this.logout();
  125 + return false;
  126 + }
  127 + },
  128 +
  129 + // 新增:启动 Token 自动刷新定时器
  130 + startRefreshTokenTimer() {
  131 + // 先清除已有定时器,避免重复创建
  132 + if (refreshTokenTimer) {
  133 + clearTimeout(refreshTokenTimer);
  134 + refreshTokenTimer = null;
  135 + }
  136 +
  137 + // 计算剩余时间(过期前30秒执行刷新)
  138 + const remainingTime = this.expireTime - Date.now() - 30 * 1000;
  139 + if (remainingTime <= 0) {
  140 + // 剩余时间不足,立即执行刷新
  141 + this.refreshTokenApi();
  142 + return;
  143 + }
  144 +
  145 + // 设置定时器,到期后执行刷新
  146 + refreshTokenTimer = setTimeout(async () => {
  147 + const refreshSuccess = await this.refreshTokenApi();
  148 + // 刷新成功后,重新启动定时器(循环自动刷新)
  149 + if (refreshSuccess) {
  150 + this.startRefreshTokenTimer();
  151 + }
  152 + }, remainingTime);
  153 + console.log(`Token 自动刷新定时器已启动,将在 ${Math.ceil(remainingTime / 1000)} 秒后执行刷新`);
  154 + },
  155 +
  156 + // 新增:停止 Token 自动刷新定时器(避免内存泄漏)
  157 + stopRefreshTokenTimer() {
  158 + if (refreshTokenTimer) {
  159 + clearTimeout(refreshTokenTimer);
  160 + refreshTokenTimer = null;
  161 + }
  162 + },
  163 +
80 // 修复3:重构字典获取方法(加 Token 校验 + 强制携带 Token + 宽松错误处理) 164 // 修复3:重构字典获取方法(加 Token 校验 + 强制携带 Token + 宽松错误处理)
81 async getAndSaveDictData() { 165 async getAndSaveDictData() {
82 // 前置校验:无登录态直接返回,不请求接口 166 // 前置校验:无登录态直接返回,不请求接口
@@ -136,6 +220,9 @@ export const useUserStore = defineStore(&#39;user&#39;, { @@ -136,6 +220,9 @@ export const useUserStore = defineStore(&#39;user&#39;, {
136 }, 220 },
137 221
138 logout() { 222 logout() {
  223 + // 新增:退出登录时,停止刷新定时器
  224 + this.stopRefreshTokenTimer();
  225 +
139 const pages = getCurrentPages(); 226 const pages = getCurrentPages();
140 if (pages.length === 0) return; 227 if (pages.length === 0) return;
141 228
@@ -146,6 +233,7 @@ export const useUserStore = defineStore(&#39;user&#39;, { @@ -146,6 +233,7 @@ export const useUserStore = defineStore(&#39;user&#39;, {
146 233
147 if (currentPageRoute === loginPath) { 234 if (currentPageRoute === loginPath) {
148 this.token = ''; 235 this.token = '';
  236 + this.refreshToken = ''; // 新增:清空Refresh Token
149 this.userInfo = {}; 237 this.userInfo = {};
150 this.userId = ''; 238 this.userId = '';
151 this.moduleListInfo = ''; 239 this.moduleListInfo = '';
@@ -164,6 +252,7 @@ export const useUserStore = defineStore(&#39;user&#39;, { @@ -164,6 +252,7 @@ export const useUserStore = defineStore(&#39;user&#39;, {
164 console.error('退出登录接口调用失败:', err); 252 console.error('退出登录接口调用失败:', err);
165 } finally { 253 } finally {
166 this.token = ''; 254 this.token = '';
  255 + this.refreshToken = ''; // 新增:清空Refresh Token
167 this.userInfo = {}; 256 this.userInfo = {};
168 this.userId = ''; 257 this.userId = '';
169 this.moduleListInfo = ''; 258 this.moduleListInfo = '';
@@ -197,6 +286,9 @@ export const useUserStore = defineStore(&#39;user&#39;, { @@ -197,6 +286,9 @@ export const useUserStore = defineStore(&#39;user&#39;, {
197 } 286 }
198 return false; 287 return false;
199 } 288 }
  289 +
  290 + // 新增:已登录时,启动刷新定时器(防止页面刷新后定时器丢失)
  291 + this.startRefreshTokenTimer();
200 return true; 292 return true;
201 } 293 }
202 }, 294 },
@@ -212,6 +304,7 @@ export const useUserStore = defineStore(&#39;user&#39;, { @@ -212,6 +304,7 @@ export const useUserStore = defineStore(&#39;user&#39;, {
212 serializer: { 304 serializer: {
213 serialize: (state) => { 305 serialize: (state) => {
214 uni.setStorageSync(globalConfig.cache.tokenKey, state.token); 306 uni.setStorageSync(globalConfig.cache.tokenKey, state.token);
  307 + uni.setStorageSync(globalConfig.cache.refreshTokenKey, state.refreshToken); // 新增:持久化Refresh Token
215 uni.setStorageSync(globalConfig.cache.userIdKey, state.userId); 308 uni.setStorageSync(globalConfig.cache.userIdKey, state.userId);
216 uni.setStorageSync(globalConfig.cache.expireTimeKey, state.expireTime); 309 uni.setStorageSync(globalConfig.cache.expireTimeKey, state.expireTime);
217 uni.setStorageSync(globalConfig.cache.userInfoKey, state.userInfo); 310 uni.setStorageSync(globalConfig.cache.userInfoKey, state.userInfo);
@@ -222,6 +315,7 @@ export const useUserStore = defineStore(&#39;user&#39;, { @@ -222,6 +315,7 @@ export const useUserStore = defineStore(&#39;user&#39;, {
222 deserialize: (value) => { 315 deserialize: (value) => {
223 return { 316 return {
224 token: uni.getStorageSync(globalConfig.cache.tokenKey) || '', 317 token: uni.getStorageSync(globalConfig.cache.tokenKey) || '',
  318 + refreshToken: uni.getStorageSync(globalConfig.cache.refreshTokenKey) || '', // 新增:读取Refresh Token
225 userId: uni.getStorageSync(globalConfig.cache.userIdKey) || '', 319 userId: uni.getStorageSync(globalConfig.cache.userIdKey) || '',
226 expireTime: uni.getStorageSync(globalConfig.cache.expireTimeKey) || 0, 320 expireTime: uni.getStorageSync(globalConfig.cache.expireTimeKey) || 0,
227 userInfo: uni.getStorageSync(globalConfig.cache.userInfoKey) || {}, 321 userInfo: uni.getStorageSync(globalConfig.cache.userInfoKey) || {},
@@ -231,6 +325,6 @@ export const useUserStore = defineStore(&#39;user&#39;, { @@ -231,6 +325,6 @@ export const useUserStore = defineStore(&#39;user&#39;, {
231 }; 325 };
232 } 326 }
233 }, 327 },
234 - paths: [] 328 + // paths: []
235 } 329 }
236 }); 330 });
237 \ No newline at end of file 331 \ No newline at end of file