Commit e6a0428542af9f5d8f619cfba554d8d993893ca5

Authored by 刘淇
1 parent dfa5aac5

单图情况 宽高70*70

api/user.js
... ... @@ -41,3 +41,11 @@ export const moduleList = () => {
41 41 export const getSimpleDictDataList = () => {
42 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 23 expireTimeKey: 'jcss_token_expire',
24 24 userIdKey:'jcss_user_id',
25 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 29 appName: 'JCSS管理系统',
29 30 tokenExpireTime: 7 * 24 * 60 * 60 * 1000
... ...
common/utils/common.js
... ... @@ -162,4 +162,23 @@ export const calculateFormatTimeDiff = (startTime, endTime) => {
162 162  
163 163 // 8. 拼接并返回结果
164 164 return validTimeParts.join('');
165   -};
166 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 186 \ No newline at end of file
... ...
pages-sub/daily/patrol-manage/add-patrol-record.vue
... ... @@ -6,7 +6,7 @@
6 6 label-position="left"
7 7 :model="inspectForm"
8 8 ref="inspectFormRef"
9   - labelWidth="190rpx"
  9 + labelWidth="200rpx"
10 10 >
11 11 <!-- 1. 巡查描述 -->
12 12 <up-form-item
... ... @@ -110,10 +110,20 @@
110 110 border="none"
111 111 readonly
112 112 placeholder="点击选择时间"
  113 + >
  114 +
113 115  
114   - ></up-input>
  116 + </up-input>
115 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 127 </template>
118 128 </up-form-item>
119 129  
... ... @@ -292,6 +302,12 @@ export default {
292 302 this.show = false
293 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 312 deleteImg(event) {
297 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 108 import { ref } from 'vue';
109 109 import { onLoad, onShow } from '@dcloudio/uni-app';
110 110 import { inspectionPlanPage } from "@/api/patrol-manage/patrol-plan";
  111 +import { convertArrToDateStr } from "@/common/utils/common";
111 112 // Tabs 配置
112 113 const tabList = ref([
113 114 {name: '待完成', id: '1'},
... ...
pages-sub/daily/quick-order/order-detail.vue
... ... @@ -21,7 +21,7 @@
21 21 <up-cell
22 22 title="工单编号"
23 23 :value="orderDetail.orderNo || '--'"
24   - class="up-line-1"
  24 +
25 25 align="middle"
26 26 ></up-cell>
27 27  
... ... @@ -42,7 +42,6 @@
42 42 <up-cell
43 43 title="工单名称"
44 44 :value="orderDetail.orderName || '--'"
45   - class="up-line-1"
46 45 align="middle"
47 46 ></up-cell>
48 47  
... ...
pages-sub/problem/work-order-manage/add-maintain-order.vue
... ... @@ -64,6 +64,7 @@
64 64 active-color="#1989fa"
65 65 inactive-color="#666666"
66 66 @change = "imgTabChange"
  67 +
67 68 >
68 69 <!-- 使用content插槽自定义每个tab的内容,添加红色*号 -->
69 70 <template #content="{ item, index }">
... ... @@ -822,7 +823,5 @@ const submitWorkOrder = async () =&gt; {
822 823 font-weight: bold;
823 824 }
824 825  
825   -.tab-text {
826   - font-size: 28rpx;
827   -}
  826 +
828 827 </style>
829 828 \ No newline at end of file
... ...
pages-sub/problem/work-order-manage/add-order.vue
... ... @@ -7,6 +7,29 @@
7 7 ref="workOrderFormRef"
8 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 33 <!-- 1. 工单位置(地图选择) -->
11 34 <up-form-item
12 35 label="工单位置"
... ... @@ -101,7 +124,7 @@
101 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 128 <up-upload
106 129 :file-list="problemImgs.imgList.value||[]"
107 130 @after-read="problemImgs.uploadImgs"
... ... @@ -128,7 +151,15 @@
128 151 placeholder="点击选择时间"
129 152 ></up-input>
130 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 163 </template>
133 164 </up-form-item>
134 165 </up-form>
... ... @@ -165,13 +196,51 @@
165 196 </template>
166 197  
167 198 <script setup>
168   -import { ref, reactive, watch } from 'vue'
  199 +import { ref, reactive } from 'vue'
169 200 import { onReady, onShow, onLoad } from '@dcloudio/uni-app';
170   -import { useUploadImgs } from '@/common/utils/useUploadImgs' // 引入改造后的上传逻辑
  201 +import { useUploadImgs } from '@/common/utils/useUploadImgs'
171 202 import { getRoadListByLatLng } from '@/api/common'
172 203 import { universalApproval, workorderCreate } from '@/api/work-order-manage/work-order-manage'
173 204 import { timeFormat } from '@/uni_modules/uview-plus'
174 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 245 // ========== 表单Ref ==========
177 246 const workOrderFormRef = ref(null)
... ... @@ -190,7 +259,6 @@ if (!Array.isArray(problemImgs.rawImgList.value)) {
190 259 problemImgs.rawImgList.value = [];
191 260 }
192 261  
193   -
194 262 // ========== 页面状态 ==========
195 263 // 通用弹窗控制
196 264 const showActionSheet = ref(false)
... ... @@ -213,22 +281,26 @@ const roadNameList = ref([])
213 281 const orderNameList = ref([])
214 282 const pressingTypeList = ref([])
215 283  
216   -// ========== 工单表单数据 ==========
  284 +// ========== 工单表单数据(核心调整:新增busiLineCn字段,绑定业务线) ==========
217 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 300 const workOrderFormRules = reactive({
  301 + busiLineCn: [ // 新增:业务线必选校验
  302 + { type: 'string', required: true, message: '请选择业务线', trigger: ['change', 'blur'] }
  303 + ],
232 304 workLocation: [
233 305 { type: 'string', required: true, message: '请选择工单位置', trigger: ['change', 'blur'] }
234 306 ],
... ... @@ -249,12 +321,19 @@ const workOrderFormRules = reactive({
249 321 })
250 322  
251 323 // ========== 生命周期 ==========
252   -// 页面加载:读取本地存储的工单数据(核心改造
  324 +// 页面加载:读取本地存储的工单数据(适配workOrderForm.busiLineCn
253 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 334 console.log('434')
256 335 console.log(options)
257   - if (options.isRenew ==1 && options.tempKey) {
  336 + if (options.isRenew == 1 && options.tempKey) {
258 337 isRenew.value = true;
259 338 const tempKey = options.tempKey;
260 339  
... ... @@ -307,9 +386,14 @@ onShow(() =&gt; {
307 386 console.log('紧急程度列表:', pressingTypeList.value)
308 387 })
309 388  
310   -// ========== 核心方法:工单数据回显 ==========
  389 +// ========== 核心方法:工单数据回显(适配workOrderForm.busiLineCn) ==========
311 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 397 workOrderForm.roadId = orderItem.roadId || 0;
314 398 workOrderForm.roadName = orderItem.roadName || '';
315 399 workOrderForm.workLocation = orderItem.lonLatAddress || orderItem.roadName || '';
... ... @@ -321,7 +405,7 @@ const echoOrderData = (orderItem) =&gt; {
321 405 workOrderForm.lon = orderItem.lon || 0;
322 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 409 if (orderItem.problemsImgs && Array.isArray(orderItem.problemsImgs) && orderItem.problemsImgs.length > 0) {
326 410 const imgList = orderItem.problemsImgs.map((imgUrl, index) => ({
327 411 url: imgUrl,
... ... @@ -332,25 +416,70 @@ const echoOrderData = (orderItem) =&gt; {
332 416 problemImgs.rawImgList.value = imgList;
333 417 }
334 418  
335   - // 3. 自动获取道路列表(保证下拉框正常使用
  419 + // 4. 自动获取道路列表(修改:传递当前选中的业务线英文标识
336 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 554 * 返回上一页
426 555 */
427 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 587 workOrderFormRef.value?.validateField('workLocation')
446 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 593 fail: (err) => {
475 594 console.error('选择位置失败:', err)
... ... @@ -495,13 +614,20 @@ const hideKeyboard = () =&gt; {
495 614 }
496 615  
497 616 /**
498   - * 提交工单(区分新增/重新提交,接口隔离
  617 + * 提交工单(区分新增/重新提交,接口隔离:从workOrderForm.busiLineCn取值
499 618 */
500 619 const submitWorkOrder = async () => {
501 620 try {
502   - // 先执行表单校验
  621 + // 先执行表单校验(包含业务线校验)
503 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 632 const commonSubmitData = {
507 633 roadId: workOrderForm.roadId,
... ... @@ -514,11 +640,10 @@ const submitWorkOrder = async () =&gt; {
514 640 lonLatAddress: workOrderForm.workLocation,
515 641 pressingType: workOrderForm.pressingType,
516 642 orderName: workOrderForm.orderName,
517   - // expectedFinishDate: workOrderForm.expectedFinishDate,
518 643 expectedFinishDate: new Date(workOrderForm.expectedFinishDate).getTime(),
519 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 692 // 接口调用失败
568 693 console.error(isRenew.value ? '工单重新提交失败:' : '工单提交失败:', error)
569 694 uni.showToast({
570   - title: isRenew.value ? '重新提交失败,请重试' : '提交失败,请重试',
  695 + title: isRenew.value ? error.msg : error.msg,
571 696 icon: 'none',
572 697 duration: 2000
573 698 })
... ... @@ -583,6 +708,7 @@ const submitWorkOrder = async () =&gt; {
583 708 padding-bottom: 100rpx; // 给底部按钮留空间
584 709 }
585 710  
  711 +
586 712 // 工单表单内容容器
587 713 .work-order-form-content {
588 714 background: #fff;
... ...
pages-sub/problem/work-order-manage/index.vue
... ... @@ -51,6 +51,7 @@
51 51 v-model="orderList"
52 52 @query="queryList"
53 53 :auto-show-system-loading="true"
  54 +
54 55 >
55 56 <template #empty>
56 57 <empty-view/>
... ... @@ -75,7 +76,7 @@
75 76 </view>
76 77 <view class="u-body-item u-flex">
77 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 80 </view>
80 81 <view class="u-body-item u-flex">
81 82 <view class="u-body-item-title">工单名称:</view>
... ... @@ -253,6 +254,7 @@
253 254 :file-list="acceptImgs.imgList"
254 255 @after-read="acceptImgs.uploadImgs"
255 256 @delete="acceptImgs.deleteImg"
  257 +
256 258 multiple
257 259 width="70"
258 260 height="70"
... ... @@ -268,7 +270,7 @@
268 270  
269 271 <script setup>
270 272 import { ref, computed, watch } from 'vue';
271   -import { onShow } from '@dcloudio/uni-app';
  273 +import { onShow, onLoad } from '@dcloudio/uni-app';
272 274 import { timeFormat } from '@/uni_modules/uview-plus';
273 275 import {
274 276 myBuzSimplePage,
... ... @@ -276,7 +278,7 @@ import {
276 278 doneBuzSimplePage,
277 279 universalApproval
278 280 } from '@/api/work-order-manage/work-order-manage'
279   -// 假设从用户store获取角色信息
  281 +// 从用户store获取角色信息
280 282 import { useUserStore } from '@/pinia/user';
281 283 import { nextStepMap, buzStatusMap } from '@/common/utils/common'
282 284 // 引入图片上传组合式函数(与参考页面一致)
... ... @@ -399,7 +401,16 @@ const handleSearch = (val) =&gt; {
399 401 const handleDetail = (item) => {
400 402 // 0-待办 1我发起的- 2-已办
401 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 651 };
641 652  
642 653 // 页面初始化
643   -onShow(() => {
  654 +onLoad(() => {
644 655 // 初始化加载列表
645 656 paging.value?.reload();
646 657 });
... ...
pages-sub/problem/work-order-manage/order-detail.vue
... ... @@ -38,12 +38,12 @@
38 38 <view style="min-width: 200rpx">工单位置</view>
39 39 </template>
40 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 42 </template>
43 43 </up-cell>
44 44  
45 45 <!-- 工单名称 -->
46   - <up-cell align="middle">
  46 + <up-cell align="middle">
47 47 <template #title>
48 48 <view style="min-width: 200rpx">工单名称</view>
49 49 </template>
... ... @@ -99,7 +99,9 @@
99 99 <up-album
100 100 v-if="!!orderDetail.problemsImgs?.length"
101 101 :urls="orderDetail.problemsImgs.slice(0, 3)"
102   - singleSize="70"
  102 + :singleSize="70"
  103 + :multipleSize="70"
  104 +
103 105 :preview-full-image="true"
104 106 ></up-album>
105 107 <text v-else class="empty-text">暂无问题照片</text>
... ... @@ -134,7 +136,9 @@
134 136 </template>
135 137 <template #value>
136 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 142 </view>
139 143 </template>
140 144 </up-cell>
... ... @@ -154,10 +158,11 @@
154 158 <up-album
155 159 v-if="currentImgList.length"
156 160 :urls="currentImgList.slice(0, 3)"
157   - singleSize="80"
158   - multipleSize="80"
  161 + :singleSize="70"
  162 + :multipleSize="70"
  163 +
159 164 :preview-full-image="true"
160   - class="img-album"
  165 + class="img-album custom-album"
161 166 ></up-album>
162 167 <text v-else class="empty-img-text">暂无图片</text>
163 168 </view>
... ... @@ -176,19 +181,21 @@
176 181 inactive-color="#999"
177 182 class="vertical-steps"
178 183 >
179   - <template >
  184 + <template>
180 185 <up-steps-item
181 186 v-for="(item, index) in processData.activityNodes"
182 187 :key="`${item.id}_${index}`"
183 188 >
184 189 <!-- 唯一自定义模板:content,包含标题、描述、相册所有内容 -->
185 190 <template #content>
186   - <view class="step-content-wrap" >
  191 + <view class="step-content-wrap">
187 192 <!-- 1. 原标题内容:节点名称 + 操作人 -->
188 193 <view class="step-title">
189 194 {{ item.name }}
190 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 199 </text>
193 200 </view>
194 201  
... ... @@ -202,7 +209,7 @@
202 209 </view>
203 210  
204 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 213 </view>
207 214  
208 215 <!-- 原因行 -->
... ... @@ -216,8 +223,9 @@
216 223 <up-album
217 224 v-if="item.tasks && item.tasks[0]?.attattmentUrls && item.tasks[0].attattmentUrls.length"
218 225 :urls="item.tasks[0].attattmentUrls.slice(0, 3)"
219   - singleSize="70"
220   - multipleSize="70"
  226 + :singleSize="70"
  227 + :multipleSize="70"
  228 +
221 229 :preview-full-image="true"
222 230 class="step-album"
223 231 ></up-album>
... ... @@ -359,9 +367,9 @@ import {
359 367 getApprovalDetail,
360 368 universalApproval
361 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 375 const loading = ref(true);
... ... @@ -407,7 +415,7 @@ const orderDetail = ref&lt;any&gt;({
407 415 curingLevelName: '',
408 416 commitDate: 0,
409 417 finishDate: 0,
410   - expectedFinishDate:0,
  418 + expectedFinishDate: 0,
411 419 pressingType: 0,
412 420 userId: 0,
413 421 userName: '',
... ... @@ -450,7 +458,7 @@ const orderDetail = ref&lt;any&gt;({
450 458 const currentImgList = ref([])
451 459  
452 460 const tabKeyMap = ['startImgs', 'processingImgs', 'endImgs', 'personImgs', 'materialImgs'];
453   -const imgTabChange = (({index}: {index: number}) => {
  461 +const imgTabChange = (({index}: { index: number }) => {
454 462 console.log(index)
455 463 const currentKey = tabKeyMap[index]
456 464 console.log(currentKey)
... ... @@ -476,7 +484,7 @@ const getLimitReason = (reason: string | null | undefined) =&gt; {
476 484 * @returns {number} 当前激活的步骤索引(从0开始)
477 485 */
478 486 const getCurrentStepIndex = () => {
479   - const { activityNodes } = processData.value;
  487 + const {activityNodes} = processData.value;
480 488 if (!activityNodes || !activityNodes.length) return 0;
481 489  
482 490 // 2. 若没有处理中的节点(全部已完成),则激活最后一个节点
... ... @@ -568,7 +576,7 @@ const rejectImgs = useUploadImgs({
568 576 // 监听上传实例响应式变化,解决u-upload不刷新问题
569 577 watch(() => rejectImgs.rawImgList.value, (newVal) => {
570 578 rejectImgs.imgList = newVal
571   -}, { deep: true })
  579 +}, {deep: true})
572 580  
573 581 // ========== 验收弹窗相关状态(新增图片上传实例) ==========
574 582 const acceptModalShow = ref(false); // 验收弹窗显示开关
... ... @@ -586,7 +594,7 @@ const acceptImgs = useUploadImgs({
586 594 // 监听验收图片上传实例响应式变化
587 595 watch(() => acceptImgs.rawImgList.value, (newVal) => {
588 596 acceptImgs.imgList = newVal
589   -}, { deep: true })
  597 +}, {deep: true})
590 598  
591 599 // ========== 生成临时key ==========
592 600 const generateTempKey = () => {
... ... @@ -742,7 +750,7 @@ const handleProcess = async (item: any) =&gt; {
742 750 const requestData = {
743 751 "returnImgs": rejectImgs.getSuccessImgUrls(),
744 752 "workerDataId": item.id,
745   - "taskKey":'ylInspectorStart',
  753 + "taskKey": 'ylInspectorStart',
746 754 "taskId": item.taskId,
747 755 "operateType": 200,
748 756 "agree": 1,
... ... @@ -812,13 +820,17 @@ const handleAcceptModalConfirm = async () =&gt; {
812 820 }
813 821 const acceptRes = await universalApproval(postData);
814 822 // 4. 操作成功处理
815   - uni.showToast({title: '提交成功', icon: 'success', duration: 1000});
  823 +
816 824 handleAcceptModalCancel(); // 清空状态
817 825 // 重新获取工单详情,刷新页面
818 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 834 } catch (error) {
823 835 // 5. 操作失败处理
824 836 console.error('验收失败:', error);
... ... @@ -888,13 +900,14 @@ onShow(() =&gt; {
888 900 // 图片内容区
889 901 .img-tab-content {
890 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 908 .img-album {
897   - width: 100%;
  909 + //width: 100%;
  910 +
898 911 }
899 912  
900 913 .empty-img-text {
... ... @@ -973,8 +986,7 @@ onShow(() =&gt; {
973 986 padding: 10rpx 0;
974 987  
975 988 .step-album {
976   - width: 100%;
977   - max-width: 300rpx;
  989 + //width: 100%;
978 990 }
979 991  
980 992 .no-img-tip {
... ... @@ -1034,4 +1046,11 @@ onShow(() =&gt; {
1034 1046 .mt-30 {
1035 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 1056 </style>
1038 1057 \ No newline at end of file
... ...
pages.json
1 1 {
2 2 "pages": [
3 3 {
4   - "path": "pages/login/index",
  4 + "path": "pages/workbench/index",
5 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 12 "style": {
12   - "navigationBarTitleText": "首页"
  13 + "navigationBarTitleText": "登录"
13 14 }
14 15 },
15 16 {
16   - "path": "pages/workbench/index",
  17 + "path": "pages/index/index",
17 18 "style": {
18   - "navigationBarTitleText": "工作台",
19   - "navigationStyle": "custom"
  19 + "navigationBarTitleText": "首页"
20 20 }
21 21 },
  22 +
22 23 {
23 24 "path": "pages/mine/index",
24 25 "style": {
... ... @@ -91,7 +92,7 @@
91 92  
92 93 {
93 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 107 // 已登录则直接跳首页
108 108 if (userStore.isLogin) {
109 109 uni.switchTab({
110   - url: globalConfig.router.tabBarList[1].path,
  110 + url: '/pages/workbench/index',
111 111 fail: () => {
112 112 // 非tabBar页面用redirectTo
113   - uni.redirectTo({ url: '/pages/workbench/index' });
  113 + uni.reLaunch({ url: '/pages/workbench/index' });
114 114 }
115 115 });
116 116 return;
... ... @@ -157,7 +157,7 @@ const handleLogin = async () =&gt; {
157 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 163 setTimeout(() => {
... ... @@ -165,10 +165,10 @@ const handleLogin = async () =&gt; {
165 165 url: globalConfig.router.tabBarList[1].path,
166 166 fail: (err) => {
167 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 172 } catch (err) {
173 173 console.error('登录失败详情:', err);
174 174 const errorMsg =
... ...
pages/workbench/index.vue
... ... @@ -98,6 +98,39 @@ const filteredModuleList = computed(() =&gt; {
98 98 onShow(async () => {
99 99 try {
100 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 134 const rawMenuData = userStore.moduleListInfo || cache.get(globalConfig.cache.moduleListKey);
102 135 moduleList.value = rawMenuData || [];
103 136 loading.value = false;
... ...
pinia/user.js
1 1 import { defineStore } from 'pinia';
2 2 import cache from '@/common/utils/cache';
3 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 10 export const useUserStore = defineStore('user', {
7 11 // 修复1:删除重复的 state 定义,只保留根层级的 state
8 12 state: () => ({
9 13 token: cache.get(globalConfig.cache.tokenKey) || '', // 初始值从缓存取(兼容持久化)
  14 + refreshToken: cache.get(globalConfig.cache.refreshTokenKey) || '', // 新增:Refresh Token 状态
10 15 userInfo: cache.get(globalConfig.cache.userInfoKey) || {},
11 16 userId: cache.get(globalConfig.cache.userIdKey) || '',
12 17 moduleListInfo: cache.get(globalConfig.cache.moduleListKey) || '',
... ... @@ -23,14 +28,22 @@ export const useUserStore = defineStore(&#39;user&#39;, {
23 28 }
24 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 41 actions: {
30 42 async login(params) {
31 43 try {
32 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 48 if (!accessToken) {
36 49 throw new Error('登录失败,未获取到令牌');
... ... @@ -38,6 +51,7 @@ export const useUserStore = defineStore(&#39;user&#39;, {
38 51  
39 52 // 更新 Pinia state
40 53 this.token = accessToken;
  54 + this.refreshToken = refreshToken || ''; // 新增:存储Refresh Token
41 55 this.expireTime = expiresTime;
42 56 this.userId = userId;
43 57 this.userInfo = {};
... ... @@ -70,6 +84,9 @@ export const useUserStore = defineStore(&#39;user&#39;, {
70 84 uni.showToast({ title: '获取字典失败,可正常使用', icon: 'none' });
71 85 }
72 86  
  87 + // 新增:登录成功后,启动 Token 自动刷新定时器
  88 + this.startRefreshTokenTimer();
  89 +
73 90 return { ...res, userInfo, moduleListInfo };
74 91 } catch (err) {
75 92 console.error('登录流程失败:', err);
... ... @@ -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 164 // 修复3:重构字典获取方法(加 Token 校验 + 强制携带 Token + 宽松错误处理)
81 165 async getAndSaveDictData() {
82 166 // 前置校验:无登录态直接返回,不请求接口
... ... @@ -136,6 +220,9 @@ export const useUserStore = defineStore(&#39;user&#39;, {
136 220 },
137 221  
138 222 logout() {
  223 + // 新增:退出登录时,停止刷新定时器
  224 + this.stopRefreshTokenTimer();
  225 +
139 226 const pages = getCurrentPages();
140 227 if (pages.length === 0) return;
141 228  
... ... @@ -146,6 +233,7 @@ export const useUserStore = defineStore(&#39;user&#39;, {
146 233  
147 234 if (currentPageRoute === loginPath) {
148 235 this.token = '';
  236 + this.refreshToken = ''; // 新增:清空Refresh Token
149 237 this.userInfo = {};
150 238 this.userId = '';
151 239 this.moduleListInfo = '';
... ... @@ -164,6 +252,7 @@ export const useUserStore = defineStore(&#39;user&#39;, {
164 252 console.error('退出登录接口调用失败:', err);
165 253 } finally {
166 254 this.token = '';
  255 + this.refreshToken = ''; // 新增:清空Refresh Token
167 256 this.userInfo = {};
168 257 this.userId = '';
169 258 this.moduleListInfo = '';
... ... @@ -197,6 +286,9 @@ export const useUserStore = defineStore(&#39;user&#39;, {
197 286 }
198 287 return false;
199 288 }
  289 +
  290 + // 新增:已登录时,启动刷新定时器(防止页面刷新后定时器丢失)
  291 + this.startRefreshTokenTimer();
200 292 return true;
201 293 }
202 294 },
... ... @@ -212,6 +304,7 @@ export const useUserStore = defineStore(&#39;user&#39;, {
212 304 serializer: {
213 305 serialize: (state) => {
214 306 uni.setStorageSync(globalConfig.cache.tokenKey, state.token);
  307 + uni.setStorageSync(globalConfig.cache.refreshTokenKey, state.refreshToken); // 新增:持久化Refresh Token
215 308 uni.setStorageSync(globalConfig.cache.userIdKey, state.userId);
216 309 uni.setStorageSync(globalConfig.cache.expireTimeKey, state.expireTime);
217 310 uni.setStorageSync(globalConfig.cache.userInfoKey, state.userInfo);
... ... @@ -222,6 +315,7 @@ export const useUserStore = defineStore(&#39;user&#39;, {
222 315 deserialize: (value) => {
223 316 return {
224 317 token: uni.getStorageSync(globalConfig.cache.tokenKey) || '',
  318 + refreshToken: uni.getStorageSync(globalConfig.cache.refreshTokenKey) || '', // 新增:读取Refresh Token
225 319 userId: uni.getStorageSync(globalConfig.cache.userIdKey) || '',
226 320 expireTime: uni.getStorageSync(globalConfig.cache.expireTimeKey) || 0,
227 321 userInfo: uni.getStorageSync(globalConfig.cache.userInfoKey) || {},
... ... @@ -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 331 \ No newline at end of file
... ...