Commit e6a0428542af9f5d8f619cfba554d8d993893ca5
1 parent
dfa5aac5
单图情况 宽高70*70
Showing
14 changed files
with
461 additions
and
134 deletions
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 '@/uni_modules/uview-plus'; | @@ -108,6 +108,7 @@ import { timeFormat } from '@/uni_modules/uview-plus'; | ||
| 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 () => { | @@ -822,7 +823,5 @@ const submitWorkOrder = async () => { | ||
| 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(() => { | @@ -307,9 +386,14 @@ onShow(() => { | ||
| 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) => { | @@ -321,7 +405,7 @@ const echoOrderData = (orderItem) => { | ||
| 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) => { | @@ -332,25 +416,70 @@ const echoOrderData = (orderItem) => { | ||
| 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) => { | @@ -425,7 +554,20 @@ const handleActionSheetSelect = (e) => { | ||
| 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 = () => { | @@ -445,31 +587,8 @@ const chooseWorkLocation = () => { | ||
| 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 = () => { | @@ -495,13 +614,20 @@ const hideKeyboard = () => { | ||
| 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 () => { | @@ -514,11 +640,10 @@ const submitWorkOrder = async () => { | ||
| 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 () => { | @@ -567,7 +692,7 @@ const submitWorkOrder = async () => { | ||
| 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 () => { | @@ -583,6 +708,7 @@ const submitWorkOrder = async () => { | ||
| 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) => { | @@ -399,7 +401,16 @@ const handleSearch = (val) => { | ||
| 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 () => { | @@ -640,7 +651,7 @@ const handleAcceptModalConfirm = async () => { | ||
| 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<any>({ | @@ -407,7 +415,7 @@ const orderDetail = ref<any>({ | ||
| 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<any>({ | @@ -450,7 +458,7 @@ const orderDetail = ref<any>({ | ||
| 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) => { | @@ -476,7 +484,7 @@ const getLimitReason = (reason: string | null | undefined) => { | ||
| 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) => { | @@ -742,7 +750,7 @@ const handleProcess = async (item: any) => { | ||
| 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 () => { | @@ -812,13 +820,17 @@ const handleAcceptModalConfirm = async () => { | ||
| 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(() => { | @@ -888,13 +900,14 @@ onShow(() => { | ||
| 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(() => { | @@ -973,8 +986,7 @@ onShow(() => { | ||
| 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(() => { | @@ -1034,4 +1046,11 @@ onShow(() => { | ||
| 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 = () => { | @@ -107,10 +107,10 @@ const checkLoginStatus = () => { | ||
| 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 () => { | @@ -157,7 +157,7 @@ const handleLogin = async () => { | ||
| 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 () => { | @@ -165,10 +165,10 @@ const handleLogin = async () => { | ||
| 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(() => { | @@ -98,6 +98,39 @@ const filteredModuleList = computed(() => { | ||
| 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('user', { | @@ -23,14 +28,22 @@ export const useUserStore = defineStore('user', { | ||
| 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('user', { | @@ -38,6 +51,7 @@ export const useUserStore = defineStore('user', { | ||
| 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('user', { | @@ -70,6 +84,9 @@ export const useUserStore = defineStore('user', { | ||
| 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('user', { | @@ -77,6 +94,73 @@ export const useUserStore = defineStore('user', { | ||
| 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('user', { | @@ -136,6 +220,9 @@ export const useUserStore = defineStore('user', { | ||
| 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('user', { | @@ -146,6 +233,7 @@ export const useUserStore = defineStore('user', { | ||
| 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('user', { | @@ -164,6 +252,7 @@ export const useUserStore = defineStore('user', { | ||
| 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('user', { | @@ -197,6 +286,9 @@ export const useUserStore = defineStore('user', { | ||
| 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('user', { | @@ -212,6 +304,7 @@ export const useUserStore = defineStore('user', { | ||
| 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('user', { | @@ -222,6 +315,7 @@ export const useUserStore = defineStore('user', { | ||
| 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('user', { | @@ -231,6 +325,6 @@ export const useUserStore = defineStore('user', { | ||
| 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 |