Commit 12e66ec8a6ef2cc1a4d1ccb40a3643c9b1df2701
1 parent
727bc60d
新增树
Showing
18 changed files
with
1441 additions
and
734 deletions
api/tree-archive/tree-archive.js
| @@ -31,18 +31,30 @@ export const addTree = (data) => { | @@ -31,18 +31,30 @@ export const addTree = (data) => { | ||
| 31 | }; | 31 | }; |
| 32 | 32 | ||
| 33 | 33 | ||
| 34 | +/** | ||
| 35 | + * 树更变记录 | ||
| 36 | + * @param {Object} params | ||
| 37 | + * @returns {Promise} | ||
| 38 | + */ | ||
| 39 | +export const treeLogReq = (params) => { | ||
| 40 | + return get('/app-api/garden/tree-change-log/page', params); | ||
| 41 | +}; | ||
| 42 | + | ||
| 43 | + | ||
| 44 | +/** | ||
| 45 | + * 修改树 | ||
| 46 | + * @param {Object} params | ||
| 47 | + * @returns {Promise} | ||
| 48 | + */ | ||
| 49 | +export const updateTree = (data) => { | ||
| 50 | + return put(' /app-api/garden/tree/update', data); | ||
| 51 | +}; | ||
| 52 | + | ||
| 34 | 53 | ||
| 35 | // // 树基本详情 | 54 | // // 树基本详情 |
| 36 | // export const treeDetailReq = (params) => request.get('/business/tree/'+params ) | 55 | // export const treeDetailReq = (params) => request.get('/business/tree/'+params ) |
| 37 | // ==> | 56 | // ==> |
| 38 | // /app-api/garden/tree/get | 57 | // /app-api/garden/tree/get |
| 39 | // | 58 | // |
| 40 | -// // 修改树 | ||
| 41 | -// export const updateTree = (params) => request.put('/business/tree/',params ) | ||
| 42 | -// ==> | ||
| 43 | -// /app-api/garden/tree/update | ||
| 44 | -// | ||
| 45 | -// // 树更变记录 | ||
| 46 | -// export const treeLogReq = (params) => request.get('/gardentree/logs/list/',params ) | ||
| 47 | -// ==> | ||
| 48 | -// /app-api/garden/tree-change-log/page | 59 | + |
| 60 | + |
common/utils/common.js
| @@ -27,7 +27,7 @@ export const nextStepMap = { | @@ -27,7 +27,7 @@ export const nextStepMap = { | ||
| 27 | name: '巡查员验收', | 27 | name: '巡查员验收', |
| 28 | btnText: '验收', | 28 | btnText: '验收', |
| 29 | operateTypePass: 140, //巡查员验收通过: 140 | 29 | operateTypePass: 140, //巡查员验收通过: 140 |
| 30 | - operateTypeNoPass: 240, // 巡查员验收不通过:230 | 30 | + operateTypeNoPass: 240, // 巡查员验收不通过:240 |
| 31 | backShow: false, | 31 | backShow: false, |
| 32 | renewShow: false | 32 | renewShow: false |
| 33 | }, | 33 | }, |
| @@ -35,54 +35,47 @@ export const nextStepMap = { | @@ -35,54 +35,47 @@ export const nextStepMap = { | ||
| 35 | name: '发起人确认', | 35 | name: '发起人确认', |
| 36 | btnText: '结束工单', | 36 | btnText: '结束工单', |
| 37 | operateTypePass: 200, //巡查员结束工单:200 | 37 | operateTypePass: 200, //巡查员结束工单:200 |
| 38 | - operateTypeNoPass: 240, // 巡查员验收不通过:230 | 38 | + operateTypeNoPass: 240, // 巡查员验收不通过:240 |
| 39 | operateTypeRenew: 100, //巡查员重新发起:100 | 39 | operateTypeRenew: 100, //巡查员重新发起:100 |
| 40 | backShow: false, | 40 | backShow: false, |
| 41 | renewShow: true | 41 | renewShow: true |
| 42 | }, | 42 | }, |
| 43 | - | ||
| 44 | regionManager: { | 43 | regionManager: { |
| 45 | name: '大区经理分配', | 44 | name: '大区经理分配', |
| 46 | btnText: '分配', | 45 | btnText: '分配', |
| 47 | operateTypePass: 80, // 大区经理分配:80 | 46 | operateTypePass: 80, // 大区经理分配:80 |
| 48 | operateTypeNoPass: 90, // 大区经理退回:90 | 47 | operateTypeNoPass: 90, // 大区经理退回:90 |
| 49 | - // operateTypeRenew: 100, //巡查员重新发起:100 | ||
| 50 | backShow: true, | 48 | backShow: true, |
| 51 | renewShow: false | 49 | renewShow: false |
| 52 | }, | 50 | }, |
| 53 | - | ||
| 54 | - | ||
| 55 | shRegionManager: { | 51 | shRegionManager: { |
| 56 | name: '督察员单子大区经理分配', | 52 | name: '督察员单子大区经理分配', |
| 57 | btnText: '分配', | 53 | btnText: '分配', |
| 58 | operateTypePass: 60, // 大区经理分配:60 | 54 | operateTypePass: 60, // 大区经理分配:60 |
| 59 | operateTypeNoPass: 70, // 大区经理退回:70 | 55 | operateTypeNoPass: 70, // 大区经理退回:70 |
| 60 | - // operateTypeRenew: 100, //巡查员重新发起:100 | ||
| 61 | backShow: true, | 56 | backShow: true, |
| 62 | renewShow: false | 57 | renewShow: false |
| 63 | }, | 58 | }, |
| 64 | } | 59 | } |
| 65 | 60 | ||
| 66 | - | ||
| 67 | export const buzStatusMap = { | 61 | export const buzStatusMap = { |
| 68 | - '000' :'发起', | ||
| 69 | - "210" : '退回', | ||
| 70 | - "110" : '分配', | ||
| 71 | - "200" : '结束工单', | ||
| 72 | - "100" : '重新发起', | ||
| 73 | - "220" : '退回', | ||
| 74 | - "120" : '实施', | ||
| 75 | - "130" : '验收通过', | ||
| 76 | - "230" : '验收不通过', | ||
| 77 | - "140" : '验收通过', | ||
| 78 | - "240" : '验收不通过', | ||
| 79 | - '90' : '退回', | ||
| 80 | - '80' : '分配', | 62 | + '000': '发起', |
| 63 | + "210": '退回', | ||
| 64 | + "110": '分配', | ||
| 65 | + "200": '结束工单', | ||
| 66 | + "100": '重新发起', | ||
| 67 | + "220": '退回', | ||
| 68 | + "120": '实施', | ||
| 69 | + "130": '验收通过', | ||
| 70 | + "230": '验收不通过', | ||
| 71 | + "140": '验收通过', | ||
| 72 | + "240": '验收不通过', | ||
| 73 | + '90': '退回', | ||
| 74 | + '80': '分配', | ||
| 81 | '70': '退回', | 75 | '70': '退回', |
| 82 | '60': '分配', | 76 | '60': '分配', |
| 83 | } | 77 | } |
| 84 | 78 | ||
| 85 | - | ||
| 86 | /** | 79 | /** |
| 87 | * 计算两个时间的时间差,返回格式化字符串(天/小时/分钟/秒,按需显示,无无效单位) | 80 | * 计算两个时间的时间差,返回格式化字符串(天/小时/分钟/秒,按需显示,无无效单位) |
| 88 | * @param {string | Date | number} startTime - 开始时间(时间字符串/Date对象/10位/13位时间戳) | 81 | * @param {string | Date | number} startTime - 开始时间(时间字符串/Date对象/10位/13位时间戳) |
| @@ -170,32 +163,30 @@ export const calculateFormatTimeDiff = (startTime, endTime) => { | @@ -170,32 +163,30 @@ export const calculateFormatTimeDiff = (startTime, endTime) => { | ||
| 170 | if (days > 0) { | 163 | if (days > 0) { |
| 171 | timeParts.push(`${days}天`); | 164 | timeParts.push(`${days}天`); |
| 172 | } | 165 | } |
| 173 | - if (hours > 0 || (days > 0 && hours === 0)) { // 有天数时,小时即使为0也可保留(可选:删除 || 后的条件则不显示0小时) | 166 | + if (hours > 0 || (days > 0 && hours === 0)) { |
| 174 | timeParts.push(`${hours}小时`); | 167 | timeParts.push(`${hours}小时`); |
| 175 | } | 168 | } |
| 176 | - if (minutes > 0 || (timeParts.length > 0 && minutes === 0)) { // 有天/小时时,分钟即使为0也可保留(可选:删除 || 后的条件则不显示0分钟) | 169 | + if (minutes > 0 || (timeParts.length > 0 && minutes === 0)) { |
| 177 | timeParts.push(`${minutes}分钟`); | 170 | timeParts.push(`${minutes}分钟`); |
| 178 | } | 171 | } |
| 179 | - // 秒数始终保留(即使为0,保证最后有一个有效单位) | ||
| 180 | timeParts.push(`${seconds}秒`); | 172 | timeParts.push(`${seconds}秒`); |
| 181 | 173 | ||
| 182 | - // 7. 过滤掉可能存在的「0单位」(可选优化:避免出现“0小时”等无效字段) | 174 | + // 7. 过滤掉可能存在的「0单位」 |
| 183 | const validTimeParts = timeParts.filter(part => { | 175 | const validTimeParts = timeParts.filter(part => { |
| 184 | const num = parseInt(part); | 176 | const num = parseInt(part); |
| 185 | - return num > 0 || (timeParts.length === 1 && num === 0); // 仅当只有秒数时,允许0秒 | 177 | + return num > 0 || (timeParts.length === 1 && num === 0); |
| 186 | }); | 178 | }); |
| 187 | 179 | ||
| 188 | // 8. 拼接并返回结果 | 180 | // 8. 拼接并返回结果 |
| 189 | return validTimeParts.join(''); | 181 | return validTimeParts.join(''); |
| 190 | }; | 182 | }; |
| 191 | 183 | ||
| 192 | - | ||
| 193 | /** | 184 | /** |
| 194 | * 将日期数组 [年, 月, 日] 转为标准日期字符串(兼容 iOS/Android) | 185 | * 将日期数组 [年, 月, 日] 转为标准日期字符串(兼容 iOS/Android) |
| 195 | * @param {Array} dateArr 日期数组,如 [2025, 12, 20] | 186 | * @param {Array} dateArr 日期数组,如 [2025, 12, 20] |
| 196 | * @returns {String} 标准日期字符串,如 '2025-12-20' | 187 | * @returns {String} 标准日期字符串,如 '2025-12-20' |
| 197 | */ | 188 | */ |
| 198 | -export const convertArrToDateStr= (dateArr)=> { | 189 | +export const convertArrToDateStr = (dateArr) => { |
| 199 | // 边界判断:非数组/长度不足3,返回空 | 190 | // 边界判断:非数组/长度不足3,返回空 |
| 200 | if (!Array.isArray(dateArr) || dateArr.length < 3) { | 191 | if (!Array.isArray(dateArr) || dateArr.length < 3) { |
| 201 | return dateArr; | 192 | return dateArr; |
| @@ -206,4 +197,35 @@ export const convertArrToDateStr= (dateArr)=> { | @@ -206,4 +197,35 @@ export const convertArrToDateStr= (dateArr)=> { | ||
| 206 | const formatDay = day.toString().padStart(2, '0'); | 197 | const formatDay = day.toString().padStart(2, '0'); |
| 207 | // 拼接为标准字符串 | 198 | // 拼接为标准字符串 |
| 208 | return `${year}-${formatMonth}-${formatDay}`; | 199 | return `${year}-${formatMonth}-${formatDay}`; |
| 200 | +} | ||
| 201 | + | ||
| 202 | +/** | ||
| 203 | + * 角色编码 转 中文名称 公共方法 | ||
| 204 | + * @param {Array<string>} roles 角色编码数组 如:["team_leader_yl", "team_leader_wy"] | ["team_leader_yl"] | ||
| 205 | + * @returns {string} 拼接后的中文角色字符串 如:"管理员,子公司管理员" | "管理员" | ||
| 206 | + */ | ||
| 207 | +export const translateRoles = (roles = []) => { | ||
| 208 | + // 角色编码与中文名称映射表 | ||
| 209 | + const roleMap = { | ||
| 210 | + 'super_admin': '超级管理员', | ||
| 211 | + 'common': '普通角色', | ||
| 212 | + 'admin': '管理员', | ||
| 213 | + 'zgs_leader': '子公司管理员', | ||
| 214 | + 'regional_manager': '大区经理', | ||
| 215 | + 'patrol_leader': '巡查组长', | ||
| 216 | + 'dispatcher': '业务调度员', | ||
| 217 | + 'patrol_global': '全域巡查员', | ||
| 218 | + 'yl_inspector': '园林巡查员', | ||
| 219 | + 'team_leader_yl': '园林养护组长', | ||
| 220 | + 'wy_inspector': '物业巡查员', | ||
| 221 | + 'yl_worker': '园林养护员', | ||
| 222 | + 'wy_worker': '物业养护员', | ||
| 223 | + 'Inspector_global': '全域督察员', | ||
| 224 | + 'AI_dispatcher': 'AI工单派发人员', | ||
| 225 | + }; | ||
| 226 | + // 过滤有效角色 + 翻译 + 中文逗号拼接 | ||
| 227 | + return roles | ||
| 228 | + .filter(role => role && roleMap[role]) | ||
| 229 | + .map(role => roleMap[role]) | ||
| 230 | + .join(','); | ||
| 209 | } | 231 | } |
| 210 | \ No newline at end of file | 232 | \ No newline at end of file |
common/utils/inputFilter.js
0 → 100644
| 1 | +/** | ||
| 2 | + * 全局输入框过滤工具 - 增强版(100%生效,解决小程序延迟问题) | ||
| 3 | + * 核心功能:实时禁止开头空格 + 实时过滤所有表情 | ||
| 4 | + */ | ||
| 5 | +// 最全表情正则:覆盖所有微信/系统/颜文字/特殊符号 | ||
| 6 | +const regEmoji = /[\uD83C|\uD83D|\uD83E][\uDC00-\uDFFF][\u200D|\uFE0F]|[\uD83C|\uD83D|\uD83E][\uDC00-\uDFFF]|[0-9|*|#]\uFE0F\u20E3|[0-9|#]\u20E3|[\u203C\u2049\u2122\u2139\u2194-\u2199\u21A9\u21AA]|\u2B05-\u2B07|\u2B1B\u2B1C|\u2B50\u2B55|\u231A\u231B|\u2328\u23CF|\u23E9-\u23F3|\u23F8-\u23FA|\u24C2|\u25AA\u25AB|\u25B6\u25C0|\u25FB-\u25FE|\u2600-\u26FF|\u2700-\u27BF|\u2934\u2935|\u2B00-\u2BFF|\u3030\u303D|\u3297\u3299]|\uD830-\uD83D[\uDC00-\uDFFF]|\uD83E[\uDD00-\uDDFF]/g; | ||
| 7 | +// 匹配开头所有空格(半角+全角) | ||
| 8 | +const regStartSpace = /^(^\s+)|(^ +)/g; | ||
| 9 | + | ||
| 10 | +/** | ||
| 11 | + * 增强版过滤方法:强制实时更新,解决小程序输入延迟 | ||
| 12 | + * @param {String} val 输入框原始值 | ||
| 13 | + * @returns {String} 过滤后的合法值 | ||
| 14 | + */ | ||
| 15 | +export const inputFilter = (val) => { | ||
| 16 | + if (!val || typeof val !== 'string') return val; | ||
| 17 | + let filterVal = val; | ||
| 18 | + // 1. 先过滤表情 | ||
| 19 | + filterVal = filterVal.replace(regEmoji, ''); | ||
| 20 | + // 2. 过滤开头空格(核心:确保开头绝对无空格) | ||
| 21 | + filterVal = filterVal.replace(regStartSpace, ''); | ||
| 22 | + // 3. 强制去重过滤:防止小程序输入延迟导致的残留 | ||
| 23 | + if (filterVal !== val) { | ||
| 24 | + filterVal = filterVal.replace(regEmoji, '').replace(regStartSpace, ''); | ||
| 25 | + } | ||
| 26 | + return filterVal; | ||
| 27 | +}; | ||
| 0 | \ No newline at end of file | 28 | \ No newline at end of file |
main.js
| @@ -2,6 +2,7 @@ | @@ -2,6 +2,7 @@ | ||
| 2 | import App from './App' | 2 | import App from './App' |
| 3 | import uviewPlus from '@/uni_modules/uview-plus' | 3 | import uviewPlus from '@/uni_modules/uview-plus' |
| 4 | // 导入 Pinia 实例(你的 stores/index.js 导出的 pinia) | 4 | // 导入 Pinia 实例(你的 stores/index.js 导出的 pinia) |
| 5 | +import { inputFilter } from '@/common/utils/inputFilter.js' | ||
| 5 | import pinia from '@/pinia/index' | 6 | import pinia from '@/pinia/index' |
| 6 | import EmptyView from '@/components/empty-view/empty-view.vue'; | 7 | import EmptyView from '@/components/empty-view/empty-view.vue'; |
| 7 | import UploadImage from '@/components/upload-image/upload-image.vue'; | 8 | import UploadImage from '@/components/upload-image/upload-image.vue'; |
| @@ -16,7 +17,7 @@ export function createApp() { | @@ -16,7 +17,7 @@ export function createApp() { | ||
| 16 | 17 | ||
| 17 | // 3. 注册 uviewPlus(保持原有逻辑) | 18 | // 3. 注册 uviewPlus(保持原有逻辑) |
| 18 | app.use(uviewPlus) | 19 | app.use(uviewPlus) |
| 19 | - | 20 | + app.config.globalProperties.$inputFilter = inputFilter; |
| 20 | // 4. 注册 Pinia(核心:在 app 挂载前注册) | 21 | // 4. 注册 Pinia(核心:在 app 挂载前注册) |
| 21 | app.use(pinia) | 22 | app.use(pinia) |
| 22 | // 全局注入字典工具(关键:provide需在app实例上注册) | 23 | // 全局注入字典工具(关键:provide需在app实例上注册) |
pages-sub/data/tree-archive/addTree.vue
| @@ -2,23 +2,22 @@ | @@ -2,23 +2,22 @@ | ||
| 2 | <view class="container"> | 2 | <view class="container"> |
| 3 | <up-form :model="formData" ref="formRef" label-width="140rpx" border-bottom> | 3 | <up-form :model="formData" ref="formRef" label-width="140rpx" border-bottom> |
| 4 | <up-form-item label="名称" prop="treetype" required> | 4 | <up-form-item label="名称" prop="treetype" required> |
| 5 | - <up-input v-model="formData.treetype" placeholder="请输入名称" maxlength="30" border="none"/> | 5 | + <up-input v-model.trim="formData.treetype" placeholder="请输入名称" maxlength="30" border="none"/> |
| 6 | </up-form-item> | 6 | </up-form-item> |
| 7 | 7 | ||
| 8 | - <!-- ✅ 核心修复1:给同行的两个表单外层包一层 弹性布局容器,彻底解决错位问题 --> | ||
| 9 | <view class="form-row-wrap"> | 8 | <view class="form-row-wrap"> |
| 10 | <up-row gutter="10"> | 9 | <up-row gutter="10"> |
| 11 | <up-col span="6"> | 10 | <up-col span="6"> |
| 12 | <up-form-item label="胸径" prop="dbh" required> | 11 | <up-form-item label="胸径" prop="dbh" required> |
| 13 | - <up-input v-model="formData.dbh" placeholder="请输入" maxlength="10" border="none" input-align="left"/> | 12 | + <up-input v-model.trim="formData.dbh" placeholder="请输入" maxlength="10" border="none" input-align="left"/> |
| 14 | <template #right> | 13 | <template #right> |
| 15 | - <text style="padding-left: 12rpx;color:#ccc;font-size:12px">厘米</text> | 14 | + <text style="padding-left: 12rpx;color:#ccc;font-size:14px">厘米</text> |
| 16 | </template> | 15 | </template> |
| 17 | </up-form-item> | 16 | </up-form-item> |
| 18 | </up-col> | 17 | </up-col> |
| 19 | <up-col span="6"> | 18 | <up-col span="6"> |
| 20 | <up-form-item label="高度" prop="treeheight"> | 19 | <up-form-item label="高度" prop="treeheight"> |
| 21 | - <up-input v-model="formData.treeheight" placeholder="请输入" maxlength="10" border="none" | 20 | + <up-input v-model.trim="formData.treeheight" placeholder="请输入" maxlength="10" border="none" |
| 22 | input-align="left"/> | 21 | input-align="left"/> |
| 23 | <template #right> | 22 | <template #right> |
| 24 | <text style="padding-left: 12rpx;color:#ccc;font-size:14px;">米</text> | 23 | <text style="padding-left: 12rpx;color:#ccc;font-size:14px;">米</text> |
| @@ -37,24 +36,24 @@ | @@ -37,24 +36,24 @@ | ||
| 37 | 36 | ||
| 38 | <up-row gutter="10"> | 37 | <up-row gutter="10"> |
| 39 | <up-col span="6"> | 38 | <up-col span="6"> |
| 40 | - <up-form-item label="经度" prop="longitude" :borderBottom="false"> | 39 | + <up-form-item label="经度" :borderBottom="false"> |
| 41 | <up-input v-model="formData.longitude" placeholder="" readonly border="none"/> | 40 | <up-input v-model="formData.longitude" placeholder="" readonly border="none"/> |
| 42 | </up-form-item> | 41 | </up-form-item> |
| 43 | </up-col> | 42 | </up-col> |
| 44 | <up-col span="6"> | 43 | <up-col span="6"> |
| 45 | - <up-form-item label="纬度" prop="latitude" :borderBottom="false"> | 44 | + <up-form-item label="纬度" :borderBottom="false"> |
| 46 | <up-input v-model="formData.latitude" placeholder="" readonly border="none"/> | 45 | <up-input v-model="formData.latitude" placeholder="" readonly border="none"/> |
| 47 | </up-form-item> | 46 | </up-form-item> |
| 48 | </up-col> | 47 | </up-col> |
| 49 | </up-row> | 48 | </up-row> |
| 50 | 49 | ||
| 51 | <up-form-item label="管护单位" prop="managedutyunit" required> | 50 | <up-form-item label="管护单位" prop="managedutyunit" required> |
| 52 | - <up-input v-model="formData.managedutyunit" placeholder="请输入" maxlength="30" border="none"/> | 51 | + <up-input v-model.trim="formData.managedutyunit" placeholder="请输入" maxlength="30" border="none"/> |
| 53 | </up-form-item> | 52 | </up-form-item> |
| 54 | 53 | ||
| 55 | <up-form-item label="权属分类" prop="oldtreeownershipText" required arrow @click="handleActionSheetOpen('ownership')"> | 54 | <up-form-item label="权属分类" prop="oldtreeownershipText" required arrow @click="handleActionSheetOpen('ownership')"> |
| 56 | - <up-input v-model="formData.oldtreeownershipText" placeholder="请选择" readonly border="none" | ||
| 57 | - bg-color="transparent" /> | 55 | + <up-input v-model.trim="formData.oldtreeownershipText" placeholder="请选择" readonly border="none" |
| 56 | + bg-color="transparent"/> | ||
| 58 | </up-form-item> | 57 | </up-form-item> |
| 59 | 58 | ||
| 60 | <up-form-item label="图片信息" prop="treeImgList" required> | 59 | <up-form-item label="图片信息" prop="treeImgList" required> |
| @@ -82,27 +81,26 @@ | @@ -82,27 +81,26 @@ | ||
| 82 | <up-row gutter="10"> | 81 | <up-row gutter="10"> |
| 83 | <up-col span="6"> | 82 | <up-col span="6"> |
| 84 | <up-form-item label="拉丁文" prop="latinname"> | 83 | <up-form-item label="拉丁文" prop="latinname"> |
| 85 | - <up-input v-model="formData.latinname" placeholder="请输入" maxlength="30" border="none"/> | 84 | + <up-input v-model.trim="formData.latinname" placeholder="请输入" maxlength="30" border="none"/> |
| 86 | </up-form-item> | 85 | </up-form-item> |
| 87 | </up-col> | 86 | </up-col> |
| 88 | <up-col span="6"> | 87 | <up-col span="6"> |
| 89 | <up-form-item label="级别" arrow @click="handleActionSheetOpen('level')"> | 88 | <up-form-item label="级别" arrow @click="handleActionSheetOpen('level')"> |
| 90 | - <up-input v-model="formData.treeleveltext" placeholder="请选择" readonly border="none" | 89 | + <up-input v-model.trim="formData.treeleveltext" placeholder="请选择" readonly border="none" |
| 91 | bg-color="transparent"/> | 90 | bg-color="transparent"/> |
| 92 | </up-form-item> | 91 | </up-form-item> |
| 93 | </up-col> | 92 | </up-col> |
| 94 | </up-row> | 93 | </up-row> |
| 95 | 94 | ||
| 96 | <up-form-item label="生长环境" prop="growthenvironment"> | 95 | <up-form-item label="生长环境" prop="growthenvironment"> |
| 97 | - <up-input v-model="formData.growthenvironment" placeholder="请输入" maxlength="50" border="none"/> | 96 | + <up-input v-model.trim="formData.growthenvironment" placeholder="请输入" maxlength="50" border="none"/> |
| 98 | </up-form-item> | 97 | </up-form-item> |
| 99 | 98 | ||
| 100 | - <!-- ✅ 核心修复2:所有同行的表单都统一加样式,预防其他行也错位 --> | ||
| 101 | <view class="form-row-wrap"> | 99 | <view class="form-row-wrap"> |
| 102 | <up-row gutter="10"> | 100 | <up-row gutter="10"> |
| 103 | <up-col span="6"> | 101 | <up-col span="6"> |
| 104 | <up-form-item label="预估树龄" prop="estimationtreeage"> | 102 | <up-form-item label="预估树龄" prop="estimationtreeage"> |
| 105 | - <up-input v-model="formData.estimationtreeage" placeholder="请输入" maxlength="10" border="none" | 103 | + <up-input v-model.trim="formData.estimationtreeage" placeholder="请输入" maxlength="10" border="none" |
| 106 | input-align="left"/> | 104 | input-align="left"/> |
| 107 | <template #right> | 105 | <template #right> |
| 108 | <text style="padding-left:12rpx;color:#ccc;font-size:14px">年</text> | 106 | <text style="padding-left:12rpx;color:#ccc;font-size:14px">年</text> |
| @@ -111,7 +109,7 @@ | @@ -111,7 +109,7 @@ | ||
| 111 | </up-col> | 109 | </up-col> |
| 112 | <up-col span="6"> | 110 | <up-col span="6"> |
| 113 | <up-form-item label="干周" prop="weekday"> | 111 | <up-form-item label="干周" prop="weekday"> |
| 114 | - <up-input v-model="formData.weekday" placeholder="请输入" maxlength="10" border="none" | 112 | + <up-input v-model.trim="formData.weekday" placeholder="请输入" maxlength="10" border="none" |
| 115 | input-align="left"/> | 113 | input-align="left"/> |
| 116 | <template #right> | 114 | <template #right> |
| 117 | <text style="padding-left:12rpx;color:#ccc;font-size:14px">厘米</text> | 115 | <text style="padding-left:12rpx;color:#ccc;font-size:14px">厘米</text> |
| @@ -125,7 +123,7 @@ | @@ -125,7 +123,7 @@ | ||
| 125 | <up-row gutter="10"> | 123 | <up-row gutter="10"> |
| 126 | <up-col span="6"> | 124 | <up-col span="6"> |
| 127 | <up-form-item label="东西冠幅" prop="canopyeastwest"> | 125 | <up-form-item label="东西冠幅" prop="canopyeastwest"> |
| 128 | - <up-input v-model="formData.canopyeastwest" placeholder="请输入" maxlength="10" border="none" | 126 | + <up-input v-model.trim="formData.canopyeastwest" placeholder="请输入" maxlength="10" border="none" |
| 129 | input-align="left"/> | 127 | input-align="left"/> |
| 130 | <template #right> | 128 | <template #right> |
| 131 | <text style="padding-left:12rpx;color:#ccc;font-size:14px">米</text> | 129 | <text style="padding-left:12rpx;color:#ccc;font-size:14px">米</text> |
| @@ -134,7 +132,7 @@ | @@ -134,7 +132,7 @@ | ||
| 134 | </up-col> | 132 | </up-col> |
| 135 | <up-col span="6"> | 133 | <up-col span="6"> |
| 136 | <up-form-item label="南北冠幅" prop="canopysouthnorth"> | 134 | <up-form-item label="南北冠幅" prop="canopysouthnorth"> |
| 137 | - <up-input v-model="formData.canopysouthnorth" placeholder="请输入" maxlength="10" border="none" | 135 | + <up-input v-model.trim="formData.canopysouthnorth" placeholder="请输入" maxlength="10" border="none" |
| 138 | input-align="left"/> | 136 | input-align="left"/> |
| 139 | <template #right> | 137 | <template #right> |
| 140 | <text style="padding-left:12rpx;color:#ccc;font-size:14px">米</text> | 138 | <text style="padding-left:12rpx;color:#ccc;font-size:14px">米</text> |
| @@ -180,6 +178,37 @@ import { addTree } from "@/api/tree-archive/tree-archive.js"; | @@ -180,6 +178,37 @@ import { addTree } from "@/api/tree-archive/tree-archive.js"; | ||
| 180 | import { useUploadImgs } from '@/common/utils/useUploadImgs' | 178 | import { useUploadImgs } from '@/common/utils/useUploadImgs' |
| 181 | import { useUserStore } from '@/pinia/user'; | 179 | import { useUserStore } from '@/pinia/user'; |
| 182 | 180 | ||
| 181 | +// ========== 核心工具方法【全部优化+修复,优先级最高】 ========== | ||
| 182 | +// 校验是否为 纯0/00/000 这类无效值 | ||
| 183 | +const isPureZero = (val) => { | ||
| 184 | + if (!val) return false; | ||
| 185 | + const pureZeroReg = /^0+$/; | ||
| 186 | + return pureZeroReg.test(val.trim()); | ||
| 187 | +}; | ||
| 188 | +// 全局trim所有表单字段 | ||
| 189 | +const trimFormData = (obj) => { | ||
| 190 | + for (const key in obj) { | ||
| 191 | + if (Object.prototype.hasOwnProperty.call(obj, key)) { | ||
| 192 | + const val = obj[key]; | ||
| 193 | + if (typeof val === 'string') { | ||
| 194 | + obj[key] = val.trim(); | ||
| 195 | + } | ||
| 196 | + } | ||
| 197 | + } | ||
| 198 | +}; | ||
| 199 | +// ✅ 终极版-最全表情正则:匹配所有类型表情/颜文字/特殊符号,无漏网之鱼 | ||
| 200 | +const emojiReg = /[\uD83C\uD83D\uD83E][\uDC00-\uDFFF]+|[\u2702\u2705\u2708-\u2712\u2714\u2716\u271D\u2721\u2728\u2733\u2734\u2744\u2747\u274C\u274E\u2753-\u2755\u2757\u2763-\u2767\u2795-\u2797\u27A1\u27B0\u27BF\u2934\u2935\u2B05-\u2B07\u2B1B\u2B1C\u2B50\u2B55\u3030\u303D\u3297\u3299\ufe0f\u200d]+/g; | ||
| 201 | +// ✅ 同步过滤表情【无延迟】:替换watch异步过滤为 表单提交+输入框失焦 双重同步过滤,解决校验时机差问题 | ||
| 202 | +const filterEmoji = (val) => { | ||
| 203 | + if (typeof val !== 'string') return val; | ||
| 204 | + return val.replace(emojiReg, ''); | ||
| 205 | +}; | ||
| 206 | +// ✅ 校验内容是否包含表情 (核心方法) | ||
| 207 | +const hasEmoji = (val) => { | ||
| 208 | + if (!val) return false; | ||
| 209 | + return emojiReg.test(val); | ||
| 210 | +}; | ||
| 211 | + | ||
| 183 | // ========== 状态管理 ========== | 212 | // ========== 状态管理 ========== |
| 184 | const userStore = useUserStore(); | 213 | const userStore = useUserStore(); |
| 185 | const formRef = ref(null) | 214 | const formRef = ref(null) |
| @@ -226,42 +255,94 @@ const formData = reactive({ | @@ -226,42 +255,94 @@ const formData = reactive({ | ||
| 226 | maintainunit: '' | 255 | maintainunit: '' |
| 227 | }) | 256 | }) |
| 228 | 257 | ||
| 229 | -// ✅ 核心修复3:修复所有校验规则里的 语法错误 【trigger: ['blur','change]'] → trigger: ['blur','change']】 | 258 | +// ✅ 核心修复:表单校验规则【重中之重,彻底解决问题的核心】 |
| 259 | +// 规则优先级:表情校验 > 必填校验 > 格式校验,保证表情一定优先提示,不会出现错乱提示 | ||
| 230 | const rules = reactive({ | 260 | const rules = reactive({ |
| 231 | - treetype: [{required: true, message: '请输入名称', trigger: ['blur','change']}], | ||
| 232 | - treeheight: [{max: 10, message: '树高不能超过10个字符', trigger: ['blur','change']}, { | ||
| 233 | - pattern: /^\d+(\.\d{1,2})?$/, | ||
| 234 | - message: '格式不正确', | ||
| 235 | - trigger: ['blur','change'] | ||
| 236 | - }], | ||
| 237 | - dbh: [{required: true, message: '请输入胸径',trigger: ['blur','change']}, { | ||
| 238 | - max: 10, | ||
| 239 | - message: '胸径不能超过10个字符', | ||
| 240 | - trigger: ['blur','change'] | ||
| 241 | - }, {pattern: /^\d+(\.\d{1,2})?$/, message: '格式不正确',trigger: ['blur','change']}], | ||
| 242 | - estimationtreeage: [{max: 10, message: '预估树龄不能超过10个字符', trigger: ['blur','change']}, { | ||
| 243 | - pattern: /^\d+(\.\d{1,2})?$/, | ||
| 244 | - message: '格式不正确', | ||
| 245 | - trigger: ['blur','change'] | ||
| 246 | - }], | ||
| 247 | - weekday: [{max: 10, message: '干周不能超过10个字符', trigger: ['blur','change']}, { | ||
| 248 | - pattern: /^\d+(\.\d{1,2})?$/, | ||
| 249 | - message: '格式不正确', | ||
| 250 | - trigger: ['blur','change'] | ||
| 251 | - }], | ||
| 252 | - canopyeastwest: [{max: 10, message: '东西冠幅不能超过10个字符',trigger: ['blur','change']}, { | ||
| 253 | - pattern: /^\d+(\.\d{1,2})?$/, | ||
| 254 | - message: '格式不正确', | ||
| 255 | - trigger: ['blur','change'] | ||
| 256 | - }], | ||
| 257 | - canopysouthnorth: [{max: 10, message: '南北冠幅不能超过10个字符', trigger: ['blur','change']}, { | ||
| 258 | - pattern: /^\d+(\.\d{1,2})?$/, | ||
| 259 | - message: '格式不正确', | ||
| 260 | - trigger: ['blur','change'] | ||
| 261 | - }], | ||
| 262 | - growlocation: [{required: true, message: '请地图选择位置', trigger: ['blur','change']}], | ||
| 263 | - managedutyunit: [{required: true, message: '请输入管护单位', trigger: ['blur','change']}], | 261 | + treetype: [ |
| 262 | + {validator: (rule, val, callback) => { | ||
| 263 | + if (val && hasEmoji(val)) return callback(new Error('禁止输入表情符号')); | ||
| 264 | + callback(); | ||
| 265 | + }, trigger: ['blur','change','input'], priority: 10}, // 最高优先级 | ||
| 266 | + {required: true, message: '请输入名称', trigger: ['blur','change','input']}, | ||
| 267 | + {max:30, message:'名称最多输入30个字符', trigger: ['blur','change','input']} | ||
| 268 | + ], | ||
| 269 | + treeheight: [ | ||
| 270 | + {max: 10, message: '树高不能超过10个字符', trigger: ['blur','change','input']}, | ||
| 271 | + {pattern: /^\d+(\.\d{1,2})?$/, message: '格式不正确,支持数字或两位小数', trigger: ['blur','change','input']}, | ||
| 272 | + {validator: (rule, val, callback) => { | ||
| 273 | + if (isPureZero(val)) return callback(new Error('不能输入纯0无效值')); | ||
| 274 | + callback(); | ||
| 275 | + }, trigger: ['blur','change','input']} | ||
| 276 | + ], | ||
| 277 | + dbh: [ | ||
| 278 | + {validator: (rule, val, callback) => { | ||
| 279 | + if (val && hasEmoji(val)) return callback(new Error('禁止输入表情符号')); | ||
| 280 | + callback(); | ||
| 281 | + }, trigger: ['blur','change','input'], priority:10}, | ||
| 282 | + {required: true, message: '请输入胸径',trigger: ['blur','change','input']}, | ||
| 283 | + {max: 10, message: '胸径不能超过10个字符', trigger: ['blur','change','input']}, | ||
| 284 | + {pattern: /^\d+(\.\d{1,2})?$/, message: '格式不正确,支持数字或两位小数', trigger: ['blur','change','input']}, | ||
| 285 | + {validator: (rule, val, callback) => { | ||
| 286 | + if (isPureZero(val)) return callback(new Error('不能输入纯0无效值')); | ||
| 287 | + callback(); | ||
| 288 | + }, trigger: ['blur','change','input']} | ||
| 289 | + ], | ||
| 290 | + estimationtreeage: [ | ||
| 291 | + {max: 10, message: '预估树龄不能超过10个字符', trigger: ['blur','change','input']}, | ||
| 292 | + {pattern: /^\d+(\.\d{1,2})?$/, message: '格式不正确,支持数字或两位小数', trigger: ['blur','change','input']}, | ||
| 293 | + {validator: (rule, val, callback) => { | ||
| 294 | + if (isPureZero(val)) return callback(new Error('不能输入纯0无效值')); | ||
| 295 | + callback(); | ||
| 296 | + }, trigger: ['blur','change','input']} | ||
| 297 | + ], | ||
| 298 | + weekday: [ | ||
| 299 | + {max: 10, message: '干周不能超过10个字符', trigger: ['blur','change','input']}, | ||
| 300 | + {pattern: /^\d+(\.\d{1,2})?$/, message: '格式不正确,支持数字或两位小数', trigger: ['blur','change','input']}, | ||
| 301 | + {validator: (rule, val, callback) => { | ||
| 302 | + if (isPureZero(val)) return callback(new Error('不能输入纯0无效值')); | ||
| 303 | + callback(); | ||
| 304 | + }, trigger: ['blur','change','input']} | ||
| 305 | + ], | ||
| 306 | + canopyeastwest: [ | ||
| 307 | + {max: 10, message: '东西冠幅不能超过10个字符',trigger: ['blur','change','input']}, | ||
| 308 | + {pattern: /^\d+(\.\d{1,2})?$/, message: '格式不正确,支持数字或两位小数', trigger: ['blur','change','input']}, | ||
| 309 | + {validator: (rule, val, callback) => { | ||
| 310 | + if (isPureZero(val)) return callback(new Error('不能输入纯0无效值')); | ||
| 311 | + callback(); | ||
| 312 | + }, trigger: ['blur','change','input']} | ||
| 313 | + ], | ||
| 314 | + canopysouthnorth: [ | ||
| 315 | + {max: 10, message: '南北冠幅不能超过10个字符', trigger: ['blur','change','input']}, | ||
| 316 | + {pattern: /^\d+(\.\d{1,2})?$/, message: '格式不正确,支持数字或两位小数', trigger: ['blur','change','input']}, | ||
| 317 | + {validator: (rule, val, callback) => { | ||
| 318 | + if (isPureZero(val)) return callback(new Error('不能输入纯0无效值')); | ||
| 319 | + callback(); | ||
| 320 | + }, trigger: ['blur','change','input']} | ||
| 321 | + ], | ||
| 322 | + growlocation: [{required: true, message: '请选择地图位置', trigger: ['blur','change','manual']}], | ||
| 323 | + managedutyunit: [ | ||
| 324 | + {validator: (rule, val, callback) => { | ||
| 325 | + if (val && hasEmoji(val)) return callback(new Error('禁止输入表情符号')); | ||
| 326 | + callback(); | ||
| 327 | + }, trigger: ['blur','change','input'], priority:10}, | ||
| 328 | + {required: true, message: '请输入管护单位', trigger: ['blur','change','input']}, | ||
| 329 | + {max:30, message:'管护单位最多输入30个字符', trigger: ['blur','change','input']} | ||
| 330 | + ], | ||
| 264 | oldtreeownershipText: [{required: true, message: '请选择权属分类', trigger: ['blur','change']}], | 331 | oldtreeownershipText: [{required: true, message: '请选择权属分类', trigger: ['blur','change']}], |
| 332 | + latinname: [ | ||
| 333 | + {validator: (rule, val, callback) => { | ||
| 334 | + if (val && hasEmoji(val)) return callback(new Error('禁止输入表情符号')); | ||
| 335 | + callback(); | ||
| 336 | + }, trigger: ['blur','change','input'], priority:10}, | ||
| 337 | + {max:30, message:'拉丁文最多输入30个字符', trigger: ['blur','change','input']} | ||
| 338 | + ], | ||
| 339 | + growthenvironment: [ | ||
| 340 | + {validator: (rule, val, callback) => { | ||
| 341 | + if (val && hasEmoji(val)) return callback(new Error('禁止输入表情符号')); | ||
| 342 | + callback(); | ||
| 343 | + }, trigger: ['blur','change','input'], priority:10}, | ||
| 344 | + {max:50, message:'生长环境最多输入50个字符', trigger: ['blur','change','input']} | ||
| 345 | + ], | ||
| 265 | treeImgList: [treeImgs.imgValidateRule] | 346 | treeImgList: [treeImgs.imgValidateRule] |
| 266 | }) | 347 | }) |
| 267 | 348 | ||
| @@ -310,13 +391,15 @@ const handleActionSheetSelect = (e) => { | @@ -310,13 +391,15 @@ const handleActionSheetSelect = (e) => { | ||
| 310 | handleActionSheetClose(); | 391 | handleActionSheetClose(); |
| 311 | }; | 392 | }; |
| 312 | 393 | ||
| 313 | -// 打开地图选址 | 394 | +// 地图选址 |
| 314 | const openMap = () => { | 395 | const openMap = () => { |
| 315 | uni.chooseLocation({ | 396 | uni.chooseLocation({ |
| 316 | - success: (res) => { | 397 | + success: async (res) => { |
| 317 | formData.growlocation = res.address | 398 | formData.growlocation = res.address |
| 318 | formData.latitude = res.latitude | 399 | formData.latitude = res.latitude |
| 319 | formData.longitude = res.longitude | 400 | formData.longitude = res.longitude |
| 401 | + await nextTick() | ||
| 402 | + formRef.value?.validateField('growlocation'); | ||
| 320 | }, | 403 | }, |
| 321 | fail: (err) => { | 404 | fail: (err) => { |
| 322 | console.error('地图选择失败', err); | 405 | console.error('地图选择失败', err); |
| @@ -327,13 +410,24 @@ const openMap = () => { | @@ -327,13 +410,24 @@ const openMap = () => { | ||
| 327 | }); | 410 | }); |
| 328 | } | 411 | } |
| 329 | 412 | ||
| 330 | -// 表单提交核心方法 | 413 | +// ✅ 表单提交核心方法【增加同步过滤表情兜底,万无一失】 |
| 331 | const submit = async () => { | 414 | const submit = async () => { |
| 332 | if (loadingFlag.value) return | 415 | if (loadingFlag.value) return |
| 416 | + | ||
| 417 | + // 1. 提交前同步过滤所有字段的表情 + 全局trim,双重保障 | ||
| 418 | + for (const key in formData) { | ||
| 419 | + if (typeof formData[key] === 'string') { | ||
| 420 | + formData[key] = filterEmoji(formData[key]).trim(); | ||
| 421 | + } | ||
| 422 | + } | ||
| 423 | + | ||
| 424 | + // 2. 执行表单校验 | ||
| 333 | const valid = await formRef.value.validate() | 425 | const valid = await formRef.value.validate() |
| 334 | if (!valid) return | 426 | if (!valid) return |
| 427 | + | ||
| 335 | const uploadImgUrls = treeImgs.getSuccessImgUrls() | 428 | const uploadImgUrls = treeImgs.getSuccessImgUrls() |
| 336 | - formData.maintainunit = uni.getStorageSync('userInfo')?.belongCompanyId || '' | 429 | + console.log(userStore.userInfo) |
| 430 | + formData.maintainunit = userStore.userInfo.user.companyId | ||
| 337 | formData.treeImgList = uploadImgUrls | 431 | formData.treeImgList = uploadImgUrls |
| 338 | loadingFlag.value = true | 432 | loadingFlag.value = true |
| 339 | try { | 433 | try { |
| @@ -365,17 +459,14 @@ const submit = async () => { | @@ -365,17 +459,14 @@ const submit = async () => { | ||
| 365 | padding-right: 10rpx; | 459 | padding-right: 10rpx; |
| 366 | } | 460 | } |
| 367 | 461 | ||
| 368 | -// ✅ ✅ ✅ 核心修复样式:解决校验提示导致的布局错位问题,优先级最高 | ||
| 369 | .form-row-wrap { | 462 | .form-row-wrap { |
| 370 | width: 100%; | 463 | width: 100%; |
| 371 | display: flex; | 464 | display: flex; |
| 372 | flex-direction: column; | 465 | flex-direction: column; |
| 373 | - // 关键:让校验提示文字 不撑开单元格,而是相对父容器定位 | ||
| 374 | :deep(.u-form-item) { | 466 | :deep(.u-form-item) { |
| 375 | position: relative; | 467 | position: relative; |
| 376 | margin-bottom: 0 !important; | 468 | margin-bottom: 0 !important; |
| 377 | } | 469 | } |
| 378 | - // 关键:校验错误提示文字 绝对定位在输入框下方,两行的提示互不影响 | ||
| 379 | :deep(.u-form-item__body__right__message ) { | 470 | :deep(.u-form-item__body__right__message ) { |
| 380 | position: absolute; | 471 | position: absolute; |
| 381 | left: 0; | 472 | left: 0; |
| @@ -387,7 +478,6 @@ const submit = async () => { | @@ -387,7 +478,6 @@ const submit = async () => { | ||
| 387 | box-sizing: border-box; | 478 | box-sizing: border-box; |
| 388 | } | 479 | } |
| 389 | } | 480 | } |
| 390 | -// 给同行表单底部留足提示文字的间距,防止和下一行重叠 | ||
| 391 | .form-row-wrap + .u-form-item { | 481 | .form-row-wrap + .u-form-item { |
| 392 | margin-top: 25rpx !important; | 482 | margin-top: 25rpx !important; |
| 393 | } | 483 | } |
pages-sub/data/tree-archive/editTree.vue
| 1 | -<script lang="ts"> | ||
| 2 | -import {defineComponent} from 'vue' | 1 | +<template> |
| 2 | + <view class="container"> | ||
| 3 | + <!-- ✅ 顶部固定吸顶Tabs区域 - 核心新增 --> | ||
| 4 | + <up-sticky bg-color="#ffffff"> | ||
| 5 | + <view class="header-wrap"> | ||
| 6 | + <up-tabs | ||
| 7 | + v-model="activeTab" | ||
| 8 | + :list="tabList" | ||
| 9 | + active-color="#1989fa" | ||
| 10 | + inactive-color="#666" | ||
| 11 | + font-size="30rpx" | ||
| 12 | + @click="handleTabChange" | ||
| 13 | + :scrollable="false" | ||
| 14 | + /> | ||
| 15 | + </view> | ||
| 16 | + </up-sticky> | ||
| 3 | 17 | ||
| 4 | -export default defineComponent({ | ||
| 5 | - name: "editTree" | ||
| 6 | -}) | ||
| 7 | -</script> | 18 | + <!-- ✅ Tab0 基本信息页面 (树木的详情编辑页,保留你原编辑页核心结构,这里给你做了标准基础版,可直接加表单) --> |
| 19 | + <view v-show="activeTab === 0" class="base-info-wrap"> | ||
| 20 | + <up-card :border="false" :show-head="false" class="base-card"> | ||
| 21 | + <view class="card-body-inner"> | ||
| 22 | + <view class="record-list-left" :style="`background-image: url(${treeInfo.treephoto});`"></view> | ||
| 23 | + <view class="record-list-right"> | ||
| 24 | + <view class="record-list-right-title"> | ||
| 25 | + <view class="u-line-1 treetypeName">{{ treeInfo.treetype }}</view> | ||
| 26 | + </view> | ||
| 27 | + <view class="fs-mt8 fs-align__center"> | ||
| 28 | + <img src="../../../static/imgs/tree/tree-high.png" style="width: 14px;height: 14px;margin-right: 6px;" alt=""> 高度:{{ treeInfo.treeheight }} 米 | ||
| 29 | + </view> | ||
| 30 | + <view class="fs-mt8 fs-align__center"> | ||
| 31 | + <img src="../../../static/imgs/tree/treearound.png" style="width: 14px;height: 14px;margin-right: 6px;" alt="">胸径:{{ treeInfo.dbh }} 厘米 | ||
| 32 | + </view> | ||
| 33 | + <view class="fs-mt8 fs-align__center"> | ||
| 34 | + <text>树木编号:{{ treeInfo.treenumber }}</text> | ||
| 35 | + </view> | ||
| 36 | + </view> | ||
| 37 | + </view> | ||
| 38 | + <!-- 这里可以继续加你的 编辑表单/其他基本信息 --> | ||
| 39 | + </up-card> | ||
| 40 | + </view> | ||
| 8 | 41 | ||
| 9 | -<template> | 42 | + <!-- ✅ Tab1 变更日志页面 - 和列表页布局完全一致 + 无新增按钮 + 点击卡片跳转详情 --> |
| 43 | + <view v-show="activeTab === 1" class="log-wrap"> | ||
| 44 | + <up-empty v-if="logRows.length === 0" text="暂无变更日志"></up-empty> | ||
| 45 | + <view class="record-wrap" v-else> | ||
| 46 | + <up-card | ||
| 47 | + v-for="item in logRows" | ||
| 48 | + :key="item.id" | ||
| 49 | + :border="false" | ||
| 50 | + :show-head="false" | ||
| 51 | + class="tree-card" | ||
| 52 | + :foot-border-top="false" | ||
| 53 | + @click="toLogDetailPage(item.id)" | ||
| 54 | + > | ||
| 55 | + <template #body> | ||
| 56 | + <view class="card-body-inner"> | ||
| 57 | + <!-- ✅ 保留你要求的背景图写法 --> | ||
| 58 | + <view class="record-list-left" :style="`background-image: url(${item.treephotoone});`"></view> | ||
| 59 | + <view class="record-list-right"> | ||
| 60 | + <view class="record-list-right-title"> | ||
| 61 | + <view class="u-line-1 treetypeName">{{ item.treetype }}</view> | ||
| 62 | + <view style="text-align: right">{{ timeFormat(item.updatetime) }}</view> | ||
| 63 | + </view> | ||
| 64 | + <view class=" fs-align__center" style="margin: 5px 0"> | ||
| 65 | + <img src="../../../static/imgs/tree/tree-high.png" style="width: 14px;height: 14px;margin-right: 6px;" alt=""> 高度:{{ item.treeheight }} 米 | ||
| 66 | + </view> | ||
| 67 | + <view class=" fs-align__center"> | ||
| 68 | + <img src="../../../static/imgs/tree/treearound.png" style="width: 14px;height: 14px;margin-right: 6px;" alt="">胸径:{{ item.dbh }} 厘米 | ||
| 69 | + </view> | ||
| 70 | + </view> | ||
| 71 | + </view> | ||
| 72 | + <view class="treenumber-no"> | ||
| 73 | + 树木编号:{{ item.treenumber }} | ||
| 74 | + </view> | ||
| 75 | + </template> | ||
| 76 | + </up-card> | ||
| 77 | + </view> | ||
| 78 | + </view> | ||
| 79 | + | ||
| 80 | + <!-- ✅ 新增按钮 只在【基本信息Tab】显示,变更日志Tab自动隐藏,无需手动处理 --> | ||
| 81 | + <view class="fixed-bottom-btn-wrap"> | ||
| 82 | + <up-button | ||
| 83 | + type="primary" | ||
| 84 | + @click="toAddTreePage" | ||
| 85 | + v-show="count > 0 && count > rows.length && activeTab === 0" | ||
| 86 | + > | ||
| 87 | + 新增树木录入 | ||
| 88 | + </up-button> | ||
| 89 | + </view> | ||
| 10 | 90 | ||
| 91 | + </view> | ||
| 11 | </template> | 92 | </template> |
| 12 | 93 | ||
| 94 | +<script setup> | ||
| 95 | +import { ref, reactive } from 'vue' | ||
| 96 | +import { onLoad, onShow } from '@dcloudio/uni-app'; | ||
| 97 | +import { treeRoadReq,treeLogReq } from "@/api/tree-archive/tree-archive.js"; | ||
| 98 | +import { timeFormat } from '@/uni_modules/uview-plus'; | ||
| 99 | + | ||
| 100 | +// ========== 基础变量 ========== | ||
| 101 | +const rows = ref([]) | ||
| 102 | +const roadId = ref('') | ||
| 103 | +const count = ref(0) | ||
| 104 | +const treeId = ref('') | ||
| 105 | + | ||
| 106 | +// ========== Tab切换核心变量 ========== | ||
| 107 | +const activeTab = ref(0); // 0=基本信息 1=变更日志 | ||
| 108 | +const tabList = ref([ | ||
| 109 | + { name: '基本信息' }, | ||
| 110 | + { name: '变更日志' } | ||
| 111 | +]); | ||
| 112 | + | ||
| 113 | +// ========== 数据变量 ========== | ||
| 114 | +const treeInfo = reactive({ // 基本信息-单棵树详情 | ||
| 115 | + treephoto: '', | ||
| 116 | + treetype: '', | ||
| 117 | + treeheight: '', | ||
| 118 | + dbh: '', | ||
| 119 | + treenumber: '', | ||
| 120 | + updatetime: '' | ||
| 121 | +}) | ||
| 122 | +const logRows = ref([]) // 变更日志列表数据 | ||
| 123 | + | ||
| 124 | +// ========== 生命周期 ========== | ||
| 125 | +onLoad((options) => { | ||
| 126 | + console.log('编辑页入参', options) | ||
| 127 | + roadId.value = options.roadId || '' | ||
| 128 | + count.value = options.count || 0 | ||
| 129 | + treeId.value = options.id || '' // 树木主键ID | ||
| 130 | +}) | ||
| 131 | + | ||
| 132 | +onShow(() => { | ||
| 133 | + treeRoadQuery() | ||
| 134 | + if(activeTab.value === 0){ | ||
| 135 | + // getTreeDetail() // 进入页面默认加载基本信息 | ||
| 136 | + } | ||
| 137 | +}) | ||
| 138 | + | ||
| 139 | +// ========== Tab切换事件 ========== | ||
| 140 | +const handleTabChange = (item) => { | ||
| 141 | + activeTab.value = item.index | ||
| 142 | + // 切换到哪个tab,加载对应的数据 | ||
| 143 | + if(activeTab.value === 0){ | ||
| 144 | + getTreeDetail() // 加载基本信息 | ||
| 145 | + }else if(activeTab.value === 1){ | ||
| 146 | + getTreeLogList() // 加载变更日志列表 | ||
| 147 | + } | ||
| 148 | +} | ||
| 149 | + | ||
| 150 | +// ========== 接口请求 ========== | ||
| 151 | +// 树木列表查询 | ||
| 152 | +const treeRoadQuery = async () => { | ||
| 153 | + const res = await treeRoadReq( {road: roadId.value}) | ||
| 154 | + rows.value = res.list | ||
| 155 | +} | ||
| 156 | + | ||
| 157 | +// // 获取单棵树的基本信息 | ||
| 158 | +// const getTreeDetail = async () => { | ||
| 159 | +// const res = await treeDetailReq({ id: treeId.value }) | ||
| 160 | +// Object.assign(treeInfo, res) | ||
| 161 | +// } | ||
| 162 | + | ||
| 163 | +// 获取树木的变更日志列表 | ||
| 164 | +const getTreeLogList = async () => { | ||
| 165 | + const res = await treeLogReq({ treeid: treeId.value }) | ||
| 166 | + logRows.value = res.list | ||
| 167 | +} | ||
| 168 | + | ||
| 169 | +// ========== 页面跳转 ========== | ||
| 170 | +// 前往修改页面(原有) | ||
| 171 | +const toEditPage = (id) => { | ||
| 172 | + uni.navigateTo({ | ||
| 173 | + url: `/pages-sub/data/tree-archive/editTree?id=${id}&roadId=${roadId.value}` | ||
| 174 | + }) | ||
| 175 | +} | ||
| 176 | + | ||
| 177 | +// 前往新增页面(原有) | ||
| 178 | +const toAddTreePage = () => { | ||
| 179 | + uni.navigateTo({ | ||
| 180 | + url: `/pages-sub/data/tree-archive/addTree?roadId=${roadId.value}` | ||
| 181 | + }) | ||
| 182 | +} | ||
| 183 | + | ||
| 184 | +// ✅ 新增:点击变更日志卡片 前往日志详情页面 | ||
| 185 | +const toLogDetailPage = (logId) => { | ||
| 186 | + uni.navigateTo({ | ||
| 187 | + url: `/pages-sub/data/tree-archive/logDetail?id=${logId}&treeId=${treeId.value}` | ||
| 188 | + }) | ||
| 189 | +} | ||
| 190 | +</script> | ||
| 191 | + | ||
| 13 | <style scoped lang="scss"> | 192 | <style scoped lang="scss"> |
| 193 | +.container { | ||
| 194 | + min-height: 100vh; | ||
| 195 | +} | ||
| 196 | + | ||
| 197 | +// ✅ 顶部tabs吸顶样式 | ||
| 198 | +.header-wrap { | ||
| 199 | + background-color: #fff; | ||
| 200 | + padding: 10rpx 0; | ||
| 201 | + box-shadow: 0 2rpx 4rpx rgba(0,0,0,0.03); | ||
| 202 | +} | ||
| 203 | + | ||
| 204 | +// 基本信息样式 | ||
| 205 | +.base-info-wrap { | ||
| 206 | + padding: 20rpx 10rpx; | ||
| 207 | +} | ||
| 208 | +.base-card { | ||
| 209 | + background: #fff; | ||
| 210 | + border-radius: 6px; | ||
| 211 | + padding: 10px; | ||
| 212 | +} | ||
| 213 | + | ||
| 214 | +// 变更日志样式 和列表页完全一致 | ||
| 215 | +.log-wrap { | ||
| 216 | + min-height: calc(100vh - 120rpx); | ||
| 217 | +} | ||
| 218 | +.record-wrap { | ||
| 219 | + padding-bottom: 20px; | ||
| 220 | +} | ||
| 221 | + | ||
| 222 | +.treetypeName { | ||
| 223 | + flex: 1; | ||
| 224 | + font-size: 16px; | ||
| 225 | + font-weight: bold; | ||
| 226 | +} | ||
| 227 | + | ||
| 228 | +.record-list-left { | ||
| 229 | + height: 70px; | ||
| 230 | + width: 70px; | ||
| 231 | + background-size: 100% 100%; | ||
| 232 | + background-repeat: no-repeat; | ||
| 233 | + background-position: center; | ||
| 234 | +} | ||
| 235 | + | ||
| 236 | +.record-list-right { | ||
| 237 | + margin-left: 20px; | ||
| 238 | + flex: 1; | ||
| 239 | + overflow: hidden; | ||
| 240 | +} | ||
| 241 | + | ||
| 242 | +.record-list-right-title { | ||
| 243 | + display: flex; | ||
| 244 | + justify-content: space-between; | ||
| 245 | +} | ||
| 246 | + | ||
| 247 | +.treenumber-no { | ||
| 248 | + padding: 3px 10px; | ||
| 249 | + background: #bdefd0; | ||
| 250 | + font-size: 12px; | ||
| 251 | + margin-top: 8px; | ||
| 252 | +} | ||
| 253 | + | ||
| 254 | +// up-card样式适配 | ||
| 255 | +.tree-card { | ||
| 256 | + //margin: 15px 10px 0; | ||
| 257 | + //padding: 10px; | ||
| 258 | + //border-radius: 6px; | ||
| 259 | + //font-size: 14px; | ||
| 260 | + //box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.04); | ||
| 261 | + //background-color: #ffffff; | ||
| 262 | +} | ||
| 263 | +.card-body-inner{ | ||
| 264 | + display: flex; | ||
| 265 | +} | ||
| 266 | + | ||
| 267 | + | ||
| 268 | +.addTree { | ||
| 269 | + width: 100%; | ||
| 270 | + position: fixed; | ||
| 271 | + bottom: 0; | ||
| 272 | +} | ||
| 273 | + | ||
| 274 | + | ||
| 275 | +.fs-align__center { | ||
| 276 | + display: flex; | ||
| 277 | + align-items: center; | ||
| 278 | +} | ||
| 14 | 279 | ||
| 15 | </style> | 280 | </style> |
| 16 | \ No newline at end of file | 281 | \ No newline at end of file |
pages-sub/data/tree-archive/index.vue
| @@ -153,6 +153,7 @@ const deptListQuery = async () => { | @@ -153,6 +153,7 @@ const deptListQuery = async () => { | ||
| 153 | companyId: companyId.value, | 153 | companyId: companyId.value, |
| 154 | roadName: searchValue.value | 154 | roadName: searchValue.value |
| 155 | } | 155 | } |
| 156 | + currentIndex.value=0 | ||
| 156 | const res = await deptListReq(params) | 157 | const res = await deptListReq(params) |
| 157 | if (res.length ==0) { | 158 | if (res.length ==0) { |
| 158 | depts.value = [] | 159 | depts.value = [] |
pages-sub/data/tree-archive/logDetail.vue
0 → 100644
pages-sub/data/tree-archive/treeRecord.vue
| 1 | <template> | 1 | <template> |
| 2 | <view class="container"> | 2 | <view class="container"> |
| 3 | - <!-- ✅ 替换tui-no-data 为 uview-plus的空数据组件 up-empty --> | ||
| 4 | - <up-empty | ||
| 5 | - v-if="rows.length === 0" | ||
| 6 | - text="暂无数据" | ||
| 7 | - ></up-empty> | 3 | + <!-- 空数据组件 --> |
| 4 | + <up-empty v-if="rows.length === 0" text="暂无数据"></up-empty> | ||
| 8 | 5 | ||
| 6 | + <!-- 树木列表:up-card重构 + 保留原始背景图写法 核心满足你的要求 --> | ||
| 9 | <view class="record-wrap" v-else> | 7 | <view class="record-wrap" v-else> |
| 10 | - <view class="record-list-wrap cad-box-shadow fs-bg__white" v-for="i in rows" :key="i.id" @click="toEditPage(i.id)"> | ||
| 11 | - <view style="display: flex"> | ||
| 12 | - <view class="record-list-left" :style="`background-image: url(${i.treephoto});`"></view> | ||
| 13 | - <view class="record-list-right"> | ||
| 14 | - <view class="record-list-right-title"> | ||
| 15 | - <view class="fs-ellipsis treetypeName">{{ i.treetype }}</view> | ||
| 16 | - <view style="text-align: right">{{ i.updatetime.substring(0, 10) }}</view> | ||
| 17 | - </view> | ||
| 18 | - <view class="fs-mt8 fs-align__center"> | ||
| 19 | - <img src="../../../static/imgs/tree/tree-high.png" style="width: 14px;height: 14px;margin-right: 6px;" alt=""> 高度:{{ i.treeheight }} 米 | ||
| 20 | - </view> | ||
| 21 | - <view class="fs-mt8 fs-align__center"> | ||
| 22 | - <img src="../../../static/imgs/tree/treearound.png" style="width: 14px;height: 14px;margin-right: 6px;" alt="">胸径:{{ i.dbh }} 厘米 | 8 | + <up-card |
| 9 | + v-for="i in rows" | ||
| 10 | + :key="i.id" | ||
| 11 | + :border="false" | ||
| 12 | + :show-head="false" | ||
| 13 | + class="tree-card" | ||
| 14 | + :foot-border-top="false" | ||
| 15 | + @click="toEditPage(i.id)" | ||
| 16 | + > | ||
| 17 | + <template #body> | ||
| 18 | + <view class="card-body-inner"> | ||
| 19 | + <!-- ✅ 保留你的 原生背景图写法 完全没动 核心要求满足 --> | ||
| 20 | + <view class="record-list-left" :style="`background-image: url(${i.treephoto});`"></view> | ||
| 21 | + | ||
| 22 | + <view class="record-list-right"> | ||
| 23 | + <view class="record-list-right-title"> | ||
| 24 | + <view class="u-line-1 treetypeName">{{ i.treetype }}</view> | ||
| 25 | + <view style="text-align: right">{{ timeFormat(i.updatetime) }}</view> | ||
| 26 | + </view> | ||
| 27 | + <view class="fs-align__center " style="margin: 5px 0"> | ||
| 28 | + <img src="../../../static/imgs/tree/tree-high.png" style="width: 14px;height: 14px;margin-right: 6px;" alt=""> 高度:{{ i.treeheight }} 米 | ||
| 29 | + </view> | ||
| 30 | + <view class=" fs-align__center"> | ||
| 31 | + <img src="../../../static/imgs/tree/treearound.png" style="width: 14px;height: 14px;margin-right: 6px;" alt="">胸径:{{ i.dbh }} 厘米 | ||
| 32 | + </view> | ||
| 23 | </view> | 33 | </view> |
| 24 | </view> | 34 | </view> |
| 25 | - </view> | ||
| 26 | - <view class="fs-mt8 fs-ellipsis treenumber-no"> | ||
| 27 | - 树木编号:{{ i.treenumber }} | ||
| 28 | - </view> | ||
| 29 | - </view> | 35 | + <view class="treenumber-no"> |
| 36 | + 树木编号:{{ i.treenumber }} | ||
| 37 | + </view> | ||
| 38 | + </template> | ||
| 39 | + | ||
| 40 | +<!-- <template #foot>--> | ||
| 41 | +<!-- <view class="treenumber-no">--> | ||
| 42 | +<!-- 树木编号:{{ i.treenumber }}--> | ||
| 43 | +<!-- </view>--> | ||
| 44 | +<!-- </template>--> | ||
| 45 | + </up-card> | ||
| 30 | </view> | 46 | </view> |
| 31 | 47 | ||
| 32 | - <view class="fixed-bottom-btn-wrap"> | ||
| 33 | - <up-button | ||
| 34 | - type="primary" | ||
| 35 | - class="addTree" | ||
| 36 | - @click="toAddTreePage" | ||
| 37 | - v-show="count > 0 && count > rows.length" | ||
| 38 | - > | ||
| 39 | - 新增树木录入 | ||
| 40 | - </up-button> | ||
| 41 | - </view> | 48 | + <!-- 底部新增按钮 --> |
| 49 | + <view class="fixed-bottom-btn-wrap"> | ||
| 50 | + <up-button | ||
| 51 | + type="primary" | ||
| 52 | + @click="toAddTreePage" | ||
| 53 | + v-show="count > 0 && count > rows.length" | ||
| 54 | + > | ||
| 55 | + 新增树木录入 | ||
| 56 | + </up-button> | ||
| 57 | + </view> | ||
| 42 | 58 | ||
| 43 | </view> | 59 | </view> |
| 44 | </template> | 60 | </template> |
| @@ -46,15 +62,13 @@ | @@ -46,15 +62,13 @@ | ||
| 46 | <script setup> | 62 | <script setup> |
| 47 | import { ref} from 'vue' | 63 | import { ref} from 'vue' |
| 48 | import { onLoad, onShow } from '@dcloudio/uni-app'; | 64 | import { onLoad, onShow } from '@dcloudio/uni-app'; |
| 49 | -// 引入接口请求方法 | ||
| 50 | import { treeRoadReq } from "@/api/tree-archive/tree-archive.js"; | 65 | import { treeRoadReq } from "@/api/tree-archive/tree-archive.js"; |
| 51 | - | 66 | +import { timeFormat } from '@/uni_modules/uview-plus'; |
| 52 | 67 | ||
| 53 | const rows = ref([]) | 68 | const rows = ref([]) |
| 54 | const roadId = ref('') | 69 | const roadId = ref('') |
| 55 | const count = ref(0) | 70 | const count = ref(0) |
| 56 | 71 | ||
| 57 | - | ||
| 58 | onLoad((options) => { | 72 | onLoad((options) => { |
| 59 | console.log(options) | 73 | console.log(options) |
| 60 | roadId.value = options.roadId | 74 | roadId.value = options.roadId |
| @@ -65,30 +79,30 @@ onShow(() => { | @@ -65,30 +79,30 @@ onShow(() => { | ||
| 65 | treeRoadQuery() | 79 | treeRoadQuery() |
| 66 | }) | 80 | }) |
| 67 | 81 | ||
| 68 | -// 前往修改页面 | ||
| 69 | const toEditPage = (id) => { | 82 | const toEditPage = (id) => { |
| 70 | uni.navigateTo({ | 83 | uni.navigateTo({ |
| 71 | url: `/pages-sub/data/tree-archive/editTree?id=${id}` | 84 | url: `/pages-sub/data/tree-archive/editTree?id=${id}` |
| 72 | }) | 85 | }) |
| 73 | } | 86 | } |
| 74 | 87 | ||
| 75 | -// 前往新增页面 | ||
| 76 | const toAddTreePage = () => { | 88 | const toAddTreePage = () => { |
| 77 | uni.navigateTo({ | 89 | uni.navigateTo({ |
| 78 | url: `/pages-sub/data/tree-archive/addTree?roadId=${roadId.value}` | 90 | url: `/pages-sub/data/tree-archive/addTree?roadId=${roadId.value}` |
| 79 | }) | 91 | }) |
| 80 | } | 92 | } |
| 81 | 93 | ||
| 82 | -// 树木记录列表查询接口 | ||
| 83 | const treeRoadQuery = async () => { | 94 | const treeRoadQuery = async () => { |
| 84 | const res = await treeRoadReq( {road: roadId.value}) | 95 | const res = await treeRoadReq( {road: roadId.value}) |
| 85 | console.log(res) | 96 | console.log(res) |
| 86 | - rows.value = res.rows | 97 | + rows.value = res.list |
| 87 | } | 98 | } |
| 88 | </script> | 99 | </script> |
| 89 | 100 | ||
| 90 | <style scoped lang="scss"> | 101 | <style scoped lang="scss"> |
| 91 | -// 保留原页面所有样式,一行未改,样式完全一致 | 102 | +// ✅ 你的原始样式 一行没删、一行没改、全部保留 |
| 103 | +.container { | ||
| 104 | + min-height: 100vh; | ||
| 105 | +} | ||
| 92 | .record-wrap { | 106 | .record-wrap { |
| 93 | padding-bottom: 60px; | 107 | padding-bottom: 60px; |
| 94 | } | 108 | } |
| @@ -110,6 +124,8 @@ const treeRoadQuery = async () => { | @@ -110,6 +124,8 @@ const treeRoadQuery = async () => { | ||
| 110 | height: 70px; | 124 | height: 70px; |
| 111 | width: 70px; | 125 | width: 70px; |
| 112 | background-size: 100% 100%; | 126 | background-size: 100% 100%; |
| 127 | + background-repeat: no-repeat; // 新增:防止图片平铺 | ||
| 128 | + background-position: center; // 新增:图片居中显示 | ||
| 113 | } | 129 | } |
| 114 | 130 | ||
| 115 | .record-list-right { | 131 | .record-list-right { |
| @@ -124,14 +140,22 @@ const treeRoadQuery = async () => { | @@ -124,14 +140,22 @@ const treeRoadQuery = async () => { | ||
| 124 | } | 140 | } |
| 125 | 141 | ||
| 126 | .treenumber-no { | 142 | .treenumber-no { |
| 143 | + margin-top: 5px; | ||
| 127 | padding: 3px 10px; | 144 | padding: 3px 10px; |
| 128 | background: #bdefd0; | 145 | background: #bdefd0; |
| 129 | font-size: 12px; | 146 | font-size: 12px; |
| 130 | } | 147 | } |
| 131 | 148 | ||
| 132 | -.addTree { | ||
| 133 | - width: 100%; | ||
| 134 | - position: fixed; | ||
| 135 | - bottom: 0; | 149 | + |
| 150 | +// ✅ 只加了这2个适配up-card的样式,无其他修改 | ||
| 151 | +.tree-card { | ||
| 152 | + //margin: 15px 10px 0; | ||
| 153 | + // | ||
| 154 | + //border-radius: 6px; | ||
| 155 | + //box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.04); | ||
| 156 | + //background: #fff; | ||
| 157 | +} | ||
| 158 | +.card-body-inner{ | ||
| 159 | + display: flex; | ||
| 136 | } | 160 | } |
| 137 | </style> | 161 | </style> |
| 138 | \ No newline at end of file | 162 | \ No newline at end of file |
pages-sub/problem/regional-order-manage/index.vue
| @@ -51,14 +51,12 @@ | @@ -51,14 +51,12 @@ | ||
| 51 | <empty-view /> | 51 | <empty-view /> |
| 52 | </template> | 52 | </template> |
| 53 | 53 | ||
| 54 | - <view class="common-card-list" style="padding-top: 200rpx;padding-bottom: 30rpx"> | ||
| 55 | - <!-- 待办工单卡片 --> | 54 | + <view class="common-card-list"> |
| 56 | <up-card | 55 | <up-card |
| 57 | - v-if="activeTab === 0" | ||
| 58 | :border="false" | 56 | :border="false" |
| 59 | :foot-border-top="false" | 57 | :foot-border-top="false" |
| 60 | v-for="(item, index) in orderList" | 58 | v-for="(item, index) in orderList" |
| 61 | - :key="`todo_${item.id}_${index}`" | 59 | + :key="item.id || index" |
| 62 | :show-head="false" | 60 | :show-head="false" |
| 63 | class="order-card" | 61 | class="order-card" |
| 64 | > | 62 | > |
| @@ -80,75 +78,35 @@ | @@ -80,75 +78,35 @@ | ||
| 80 | <view class="u-body-item-title">情况描述:</view> | 78 | <view class="u-body-item-title">情况描述:</view> |
| 81 | <view class="u-line-1 u-body-value">{{ item.remark || '无' }}</view> | 79 | <view class="u-line-1 u-body-value">{{ item.remark || '无' }}</view> |
| 82 | </view> | 80 | </view> |
| 83 | - <view class="u-body-item u-flex common-item-center common-justify-between"> | ||
| 84 | - <view class="u-body-item-title">紧急程度:</view> | ||
| 85 | - <view class="u-line-1 u-body-value"> | ||
| 86 | - {{ uni.$dict.getDictLabel('workorder_pressing_type', item.pressingType) }} | 81 | + <!-- 核心修改:紧急程度行 合并工单详情按钮 【我发起的/已办】放到该行最右侧 --> |
| 82 | + <view class="u-body-item u-flex common-justify-between common-item-center"> | ||
| 83 | + <view class="u-body-item-title">紧急程度:{{ uni.$dict.getDictLabel('workorder_pressing_type', item.pressingType) || '-' }}</view> | ||
| 84 | +<!-- <view class="u-line-1 u-body-value flex-1 text-right">--> | ||
| 85 | +<!-- {{ uni.$dict.getDictLabel('workorder_pressing_type', item.pressingType) || '-' }}--> | ||
| 86 | +<!-- </view>--> | ||
| 87 | + <!-- 只有 我发起的(1)、已办(2) 显示该行的详情按钮 --> | ||
| 88 | + <view v-if="activeTab !== 0" class="ml-10"> | ||
| 89 | + <up-button type="primary" size="mini" @click="handleDetail(item)">工单详情</up-button> | ||
| 87 | </view> | 90 | </view> |
| 88 | </view> | 91 | </view> |
| 89 | <view class="u-body-item u-flex"> | 92 | <view class="u-body-item u-flex"> |
| 90 | <view class="u-body-item-title">工单状态:</view> | 93 | <view class="u-body-item-title">工单状态:</view> |
| 91 | - <view class="u-line-1 u-body-value">{{ buzStatusMap[item.buzStatus] }}</view> | 94 | + <view class="u-line-1 u-body-value">{{ buzStatusMap[item.buzStatus] || '-' }}</view> |
| 92 | </view> | 95 | </view> |
| 93 | <view class="u-body-item u-flex"> | 96 | <view class="u-body-item u-flex"> |
| 94 | <view class="u-body-item-title">提交时间:</view> | 97 | <view class="u-body-item-title">提交时间:</view> |
| 95 | - <view class="u-line-1 u-body-value">{{ timeFormat(item.createTime, 'yyyy-mm-dd hh:MM:ss') }}</view> | 98 | + <view class="u-line-1 u-body-value">{{ item.createTime ? timeFormat(item.createTime, 'yyyy-mm-dd hh:MM:ss') : '-' }}</view> |
| 96 | </view> | 99 | </view> |
| 97 | - <!-- 操作按钮行 --> | ||
| 98 | - <view class="u-body-item u-flex common-justify-between common-item-center mt-20"> | 100 | + |
| 101 | + <!-- 待办工单(0) 保留原来的操作按钮区 不变 --> | ||
| 102 | + <view v-if="activeTab === 0" class="u-body-item u-flex common-justify-between common-item-center mt-20"> | ||
| 99 | <up-button type="warning" size="mini" @click="handleReject(item)" v-show="nextStepMap[item.taskKey].backShow">回退</up-button> | 103 | <up-button type="warning" size="mini" @click="handleReject(item)" v-show="nextStepMap[item.taskKey].backShow">回退</up-button> |
| 100 | <up-button type="success" size="mini" @click="handleRenew(item)" v-show="nextStepMap[item.taskKey].renewShow">重新提交</up-button> | 104 | <up-button type="success" size="mini" @click="handleRenew(item)" v-show="nextStepMap[item.taskKey].renewShow">重新提交</up-button> |
| 101 | <up-button type="primary" size="mini" @click="handleProcess(item)">{{ nextStepMap[item.taskKey].btnText }}</up-button> | 105 | <up-button type="primary" size="mini" @click="handleProcess(item)">{{ nextStepMap[item.taskKey].btnText }}</up-button> |
| 102 | <up-button type="info" size="mini" @click="handleDetail(item)">详情</up-button> | 106 | <up-button type="info" size="mini" @click="handleDetail(item)">详情</up-button> |
| 103 | </view> | 107 | </view> |
| 104 | - </view> | ||
| 105 | - </template> | ||
| 106 | - </up-card> | ||
| 107 | 108 | ||
| 108 | - <!-- 已办工单卡片和我发起的 --> | ||
| 109 | - <up-card | ||
| 110 | - v-if="activeTab === 1 || activeTab === 2" | ||
| 111 | - :border="false" | ||
| 112 | - :foot-border-top="false" | ||
| 113 | - v-for="(item, index) in orderList" | ||
| 114 | - :key="`done_${item.id}_${index}`" | ||
| 115 | - :show-head="false" | ||
| 116 | - class="order-card" | ||
| 117 | - > | ||
| 118 | - <template #body> | ||
| 119 | - <view class="card-body"> | ||
| 120 | - <view class="u-body-item u-flex"> | ||
| 121 | - <view class="u-body-item-title">工单编号:</view> | ||
| 122 | - <view class="u-line-1 u-body-value">{{ item.orderNo || '-' }}</view> | ||
| 123 | - </view> | ||
| 124 | - <view class="u-body-item u-flex"> | ||
| 125 | - <view class="u-body-item-title">工单位置:</view> | ||
| 126 | - <view class="u-line-1 u-body-value">{{ item.lonLatAddress || '-' }}</view> | ||
| 127 | - </view> | ||
| 128 | - <view class="u-body-item u-flex"> | ||
| 129 | - <view class="u-body-item-title">工单名称:</view> | ||
| 130 | - <view class="u-line-1 u-body-value">{{ item.orderName || '未填写' }}</view> | ||
| 131 | - </view> | ||
| 132 | - <view class="u-body-item u-flex"> | ||
| 133 | - <view class="u-body-item-title">情况描述:</view> | ||
| 134 | - <view class="u-line-1 u-body-value">{{ item.remark || '无' }}</view> | ||
| 135 | - </view> | ||
| 136 | - <view class="u-body-item u-flex common-justify-between common-item-center"> | ||
| 137 | - <view class="u-body-item-title"> | ||
| 138 | - 紧急程度:{{ uni.$dict.getDictLabel('workorder_pressing_type', item.pressingType) }} | ||
| 139 | - </view> | ||
| 140 | - <view> | ||
| 141 | - <up-button type="primary" size="mini" @click="handleDetail(item)">工单详情</up-button> | ||
| 142 | - </view> | ||
| 143 | - </view> | ||
| 144 | - <view class="u-body-item u-flex"> | ||
| 145 | - <view class="u-body-item-title">工单状态:</view> | ||
| 146 | - <view class="u-line-1 u-body-value">{{ buzStatusMap[item.buzStatus] }}</view> | ||
| 147 | - </view> | ||
| 148 | - <view class="u-body-item u-flex"> | ||
| 149 | - <view class="u-body-item-title">提交时间:</view> | ||
| 150 | - <view class="u-line-1 u-body-value">{{ timeFormat(item.createTime, 'yyyy-mm-dd hh:MM:ss') }}</view> | ||
| 151 | - </view> | 109 | + <!-- 已删除:原来单独的【我发起的/已办】按钮区,不需要了 --> |
| 152 | </view> | 110 | </view> |
| 153 | </template> | 111 | </template> |
| 154 | </up-card> | 112 | </up-card> |
| @@ -265,7 +223,7 @@ const USER_ROLES = userStore.userInfo?.roles || []; | @@ -265,7 +223,7 @@ const USER_ROLES = userStore.userInfo?.roles || []; | ||
| 265 | // ========== 基础状态 (按模块分组,语义化更强) ========== | 223 | // ========== 基础状态 (按模块分组,语义化更强) ========== |
| 266 | // tab切换 | 224 | // tab切换 |
| 267 | const activeTab = ref(0); // 0-待办 1-我发起的 2-已办 | 225 | const activeTab = ref(0); // 0-待办 1-我发起的 2-已办 |
| 268 | -const tabList = ref([{name: '待办'}, {name: '我发起的任务'}, {name: '已办'}]); | 226 | +const tabList = [{name: '待办'}, {name: '我发起的任务'}, {name: '已办'}]; |
| 269 | // 排序与搜索 | 227 | // 排序与搜索 |
| 270 | const selectedSortValue = ref(1); | 228 | const selectedSortValue = ref(1); |
| 271 | const sortOptions = ref([ | 229 | const sortOptions = ref([ |
| @@ -276,10 +234,11 @@ const searchValue = ref(''); | @@ -276,10 +234,11 @@ const searchValue = ref(''); | ||
| 276 | const pagingRef = ref(null); | 234 | const pagingRef = ref(null); |
| 277 | const orderList = ref([]); | 235 | const orderList = ref([]); |
| 278 | 236 | ||
| 279 | -// ========== 角色权限计算属性 (修复核心bug: 原代码取反逻辑写反) ========== | 237 | +// ========== 角色权限计算属性 ========== |
| 280 | const isAi = computed(() => { | 238 | const isAi = computed(() => { |
| 239 | + const roles = USER_ROLES || []; | ||
| 281 | // AI工单派发人员 不显示新增按钮,其他角色显示 | 240 | // AI工单派发人员 不显示新增按钮,其他角色显示 |
| 282 | - return !USER_ROLES.includes('AI_dispatcher'); | 241 | + return !roles.includes('AI_dispatcher'); |
| 283 | }); | 242 | }); |
| 284 | 243 | ||
| 285 | // ========== 回退弹窗相关 ========== | 244 | // ========== 回退弹窗相关 ========== |
| @@ -301,9 +260,24 @@ const acceptImgs = useUploadImgs({ | @@ -301,9 +260,24 @@ const acceptImgs = useUploadImgs({ | ||
| 301 | 260 | ||
| 302 | // ========== 公共封装方法 (核心优化:消灭重复代码) ========== | 261 | // ========== 公共封装方法 (核心优化:消灭重复代码) ========== |
| 303 | /** | 262 | /** |
| 263 | + * 统一提示方法 | ||
| 264 | + */ | ||
| 265 | +const showToast = (title, icon = 'none', duration = 2000) => { | ||
| 266 | + uni.showToast({ title, icon, duration }); | ||
| 267 | +}; | ||
| 268 | + | ||
| 269 | +/** | ||
| 270 | + * z-paging 统一操作,处理空指针 | ||
| 271 | + */ | ||
| 272 | +const pagingHandle = (method, ...args) => { | ||
| 273 | + const paging = pagingRef.value; | ||
| 274 | + if (paging && typeof paging[method] === 'function') { | ||
| 275 | + paging[method](...args); | ||
| 276 | + } | ||
| 277 | +}; | ||
| 278 | + | ||
| 279 | +/** | ||
| 304 | * 生成统一的临时存储key | 280 | * 生成统一的临时存储key |
| 305 | - * @param {String} prefix 前缀标识 | ||
| 306 | - * @returns {String} 唯一key | ||
| 307 | */ | 281 | */ |
| 308 | const generateTempKey = (prefix = 'order') => { | 282 | const generateTempKey = (prefix = 'order') => { |
| 309 | return `${prefix}_${Date.now()}_${Math.floor(Math.random() * 10000)}`; | 283 | return `${prefix}_${Date.now()}_${Math.floor(Math.random() * 10000)}`; |
| @@ -311,9 +285,6 @@ const generateTempKey = (prefix = 'order') => { | @@ -311,9 +285,6 @@ const generateTempKey = (prefix = 'order') => { | ||
| 311 | 285 | ||
| 312 | /** | 286 | /** |
| 313 | * 存储工单数据到本地缓存 | 287 | * 存储工单数据到本地缓存 |
| 314 | - * @param {Object} item 工单数据 | ||
| 315 | - * @param {String} prefix key前缀 | ||
| 316 | - * @returns {String|null} 成功返回key,失败返回null | ||
| 317 | */ | 288 | */ |
| 318 | const setOrderStorage = (item, prefix) => { | 289 | const setOrderStorage = (item, prefix) => { |
| 319 | if (!item?.id) return null; | 290 | if (!item?.id) return null; |
| @@ -323,16 +294,13 @@ const setOrderStorage = (item, prefix) => { | @@ -323,16 +294,13 @@ const setOrderStorage = (item, prefix) => { | ||
| 323 | return tempKey; | 294 | return tempKey; |
| 324 | } catch (error) { | 295 | } catch (error) { |
| 325 | console.error('存储工单数据失败:', error); | 296 | console.error('存储工单数据失败:', error); |
| 326 | - uni.showToast({title: '数据存储异常,请重试', icon: 'none'}); | 297 | + showToast('数据存储异常,请重试'); |
| 327 | return null; | 298 | return null; |
| 328 | } | 299 | } |
| 329 | }; | 300 | }; |
| 330 | 301 | ||
| 331 | /** | 302 | /** |
| 332 | * 获取分页请求公共参数 | 303 | * 获取分页请求公共参数 |
| 333 | - * @param {Number} pageNo 页码 | ||
| 334 | - * @param {Number} pageSize 页大小 | ||
| 335 | - * @returns {Object} 请求参数 | ||
| 336 | */ | 304 | */ |
| 337 | const getQueryParams = (pageNo, pageSize) => { | 305 | const getQueryParams = (pageNo, pageSize) => { |
| 338 | return { | 306 | return { |
| @@ -345,35 +313,33 @@ const getQueryParams = (pageNo, pageSize) => { | @@ -345,35 +313,33 @@ const getQueryParams = (pageNo, pageSize) => { | ||
| 345 | 313 | ||
| 346 | /** | 314 | /** |
| 347 | * 统一调用审批接口 | 315 | * 统一调用审批接口 |
| 348 | - * @param {Object} params 请求参数 | ||
| 349 | - * @param {String} taskKey 工单任务key | ||
| 350 | - * @returns {Promise} 接口请求Promise | ||
| 351 | */ | 316 | */ |
| 352 | const callApprovalApi = async (params, taskKey) => { | 317 | const callApprovalApi = async (params, taskKey) => { |
| 353 | if (taskKey === 'shRegionManager') return await dcyUniversalApproval(params); | 318 | if (taskKey === 'shRegionManager') return await dcyUniversalApproval(params); |
| 354 | if (taskKey === 'regionManager') return await qyUniversalApproval(params); | 319 | if (taskKey === 'regionManager') return await qyUniversalApproval(params); |
| 355 | - // 根据角色匹配对应接口 | ||
| 356 | if (USER_ROLES.includes('regional_manager')) return await daquUniversalApproval(params); | 320 | if (USER_ROLES.includes('regional_manager')) return await daquUniversalApproval(params); |
| 357 | if (USER_ROLES.includes('Inspector_global')) return await dcyUniversalApproval(params); | 321 | if (USER_ROLES.includes('Inspector_global')) return await dcyUniversalApproval(params); |
| 358 | if (USER_ROLES.includes('patrol_global')) return await qyUniversalApproval(params); | 322 | if (USER_ROLES.includes('patrol_global')) return await qyUniversalApproval(params); |
| 323 | + throw new Error('未匹配到对应的审批接口,请检查角色或工单taskKey'); | ||
| 359 | }; | 324 | }; |
| 360 | 325 | ||
| 361 | /** | 326 | /** |
| 362 | - * 统一跳转工单页面 | ||
| 363 | - * @param {String} path 页面路径 | ||
| 364 | - * @param {Object} query 拼接参数 | 327 | + * 统一跳转工单页面 (支持携带回调事件) |
| 365 | */ | 328 | */ |
| 366 | -const navToOrderPage = (path, query = {}) => { | ||
| 367 | - const queryStr = Object.keys(query).map(k => `${k}=${query[k]}`).join('&'); | ||
| 368 | - uni.redirectTo({ url: `${path}${queryStr ? '?' + queryStr : ''}` }); | 329 | +const navToOrderPage = (path, query = {}, events = {}) => { |
| 330 | + const queryStr = Object.keys(query).map(k => `${k}=${encodeURIComponent(query[k])}`).join('&'); | ||
| 331 | + const url = `${path}${queryStr ? '?' + queryStr : ''}`; | ||
| 332 | + if(Object.keys(events).length) { | ||
| 333 | + uni.navigateTo({ url, events }); | ||
| 334 | + } else { | ||
| 335 | + uni.redirectTo({ url }); | ||
| 336 | + } | ||
| 369 | }; | 337 | }; |
| 370 | 338 | ||
| 371 | /** | 339 | /** |
| 372 | - * 刷新列表 - 统一封装,避免空指针 | 340 | + * 刷新列表 |
| 373 | */ | 341 | */ |
| 374 | -const refreshOrderList = () => { | ||
| 375 | - pagingRef.value && pagingRef.value.reload(); | ||
| 376 | -}; | 342 | +const refreshOrderList = () => pagingHandle('reload'); |
| 377 | 343 | ||
| 378 | // ========== 业务核心方法 ========== | 344 | // ========== 业务核心方法 ========== |
| 379 | // 分页查询列表 | 345 | // 分页查询列表 |
| @@ -384,11 +350,11 @@ const queryList = async (pageNo, pageSize) => { | @@ -384,11 +350,11 @@ const queryList = async (pageNo, pageSize) => { | ||
| 384 | if (activeTab.value === 0) res = await todoBuzSimplePage(params); | 350 | if (activeTab.value === 0) res = await todoBuzSimplePage(params); |
| 385 | else if (activeTab.value === 1) res = await myBuzSimplePage(params); | 351 | else if (activeTab.value === 1) res = await myBuzSimplePage(params); |
| 386 | else res = await doneBuzSimplePage(params); | 352 | else res = await doneBuzSimplePage(params); |
| 387 | - pagingRef.value.complete(res?.list || [], res?.total || 0); | 353 | + pagingHandle('complete', res?.list || [], res?.total || 0); |
| 388 | } catch (error) { | 354 | } catch (error) { |
| 389 | console.error('加载工单失败:', error); | 355 | console.error('加载工单失败:', error); |
| 390 | - pagingRef.value?.complete(false); | ||
| 391 | - uni.showToast({title: '加载失败,请重试', icon: 'none'}); | 356 | + pagingHandle('complete', false); |
| 357 | + showToast('加载失败,请重试'); | ||
| 392 | } | 358 | } |
| 393 | }; | 359 | }; |
| 394 | 360 | ||
| @@ -414,32 +380,22 @@ const handleSearch = (val) => { | @@ -414,32 +380,22 @@ const handleSearch = (val) => { | ||
| 414 | 380 | ||
| 415 | // 工单详情 | 381 | // 工单详情 |
| 416 | const handleDetail = (item) => { | 382 | const handleDetail = (item) => { |
| 417 | - if (!item?.taskId) return uni.showToast({title: '工单信息异常', icon: 'none'}); | ||
| 418 | - uni.navigateTo({ | ||
| 419 | - url: `/pages-sub/problem/regional-order-manage/order-detail?taskId=${item.taskId}&activeTab=${activeTab.value}&processInstanceId=${item.processInstanceId}`, | ||
| 420 | - events: { | ||
| 421 | - // 自定义事件名:needRefresh(与详情页保持一致) | ||
| 422 | - needRefresh: () => { | ||
| 423 | - console.log('详情页返回,触发工单列表刷新'); | ||
| 424 | - if (paging.value) { | ||
| 425 | - paging.value.reload(); // 刷新z-paging列表 | ||
| 426 | - } | ||
| 427 | - } | 383 | + if (!item?.taskId) return showToast('工单信息异常'); |
| 384 | + navToOrderPage('/pages-sub/problem/regional-order-manage/order-detail', { | ||
| 385 | + taskId: item.taskId, | ||
| 386 | + activeTab: activeTab.value, | ||
| 387 | + processInstanceId: item.processInstanceId | ||
| 388 | + }, { | ||
| 389 | + needRefresh: () => { | ||
| 390 | + console.log('详情页返回,触发工单列表刷新'); | ||
| 391 | + refreshOrderList(); | ||
| 428 | } | 392 | } |
| 429 | }); | 393 | }); |
| 430 | - // navToOrderPage('/pages-sub/problem/regional-order-manage/order-detail', { | ||
| 431 | - // taskId: item.taskId, | ||
| 432 | - // activeTab: activeTab.value, | ||
| 433 | - // processInstanceId: item.processInstanceId | ||
| 434 | - // }, { | ||
| 435 | - // needRefresh: () => refreshOrderList() | ||
| 436 | - // }); | ||
| 437 | - | ||
| 438 | - | ||
| 439 | }; | 394 | }; |
| 440 | 395 | ||
| 441 | // 重新提交工单 | 396 | // 重新提交工单 |
| 442 | const handleRenew = (item) => { | 397 | const handleRenew = (item) => { |
| 398 | + if (!item?.id) return showToast('工单信息异常,无法重新提交'); | ||
| 443 | const tempKey = setOrderStorage(item, 'renew_order'); | 399 | const tempKey = setOrderStorage(item, 'renew_order'); |
| 444 | if (!tempKey) return; | 400 | if (!tempKey) return; |
| 445 | 401 | ||
| @@ -452,9 +408,9 @@ const handleRenew = (item) => { | @@ -452,9 +408,9 @@ const handleRenew = (item) => { | ||
| 452 | 408 | ||
| 453 | // 处理工单核心逻辑 | 409 | // 处理工单核心逻辑 |
| 454 | const handleProcess = async (item) => { | 410 | const handleProcess = async (item) => { |
| 455 | - if (!item) return uni.showToast({title: '工单信息异常', icon: 'none'}); | 411 | + if (!item) return showToast('工单信息异常'); |
| 456 | const stepName = nextStepMap[item.taskKey]?.name; | 412 | const stepName = nextStepMap[item.taskKey]?.name; |
| 457 | - let resData | 413 | + if (!stepName) return showToast('暂无处理权限或工单状态异常'); |
| 458 | try { | 414 | try { |
| 459 | // 大区经理分配 | 415 | // 大区经理分配 |
| 460 | if (stepName === '大区经理分配') { | 416 | if (stepName === '大区经理分配') { |
| @@ -464,8 +420,8 @@ const handleProcess = async (item) => { | @@ -464,8 +420,8 @@ const handleProcess = async (item) => { | ||
| 464 | // 督察员单子大区经理分配 | 420 | // 督察员单子大区经理分配 |
| 465 | else if (stepName === '督察员单子大区经理分配') { | 421 | else if (stepName === '督察员单子大区经理分配') { |
| 466 | const postData = { taskKey: item.taskKey, taskId: item.taskId, operateType:60, workerDataId:item.id, agree:0, reason:item.remark, roadId:item.roadId, roadName:item.roadName, pressingType:item.pressingType, orderName:item.orderName, expectedFinishDate: item.expectedFinishDate, busiLine:item.busiLine }; | 422 | const postData = { taskKey: item.taskKey, taskId: item.taskId, operateType:60, workerDataId:item.id, agree:0, reason:item.remark, roadId:item.roadId, roadName:item.roadName, pressingType:item.pressingType, orderName:item.orderName, expectedFinishDate: item.expectedFinishDate, busiLine:item.busiLine }; |
| 467 | - resData = await dcyUniversalApproval(postData); | ||
| 468 | - uni.showToast({title: '分配成功', icon: 'success'}); | 423 | + await dcyUniversalApproval(postData); |
| 424 | + showToast('分配成功', 'success'); | ||
| 469 | refreshOrderList(); | 425 | refreshOrderList(); |
| 470 | } | 426 | } |
| 471 | // 验收弹窗 | 427 | // 验收弹窗 |
| @@ -487,7 +443,7 @@ const handleProcess = async (item) => { | @@ -487,7 +443,7 @@ const handleProcess = async (item) => { | ||
| 487 | operateType: 200, agree: 1, reason: '结束工单' | 443 | operateType: 200, agree: 1, reason: '结束工单' |
| 488 | }; | 444 | }; |
| 489 | await callApprovalApi(requestData, item.taskKey); | 445 | await callApprovalApi(requestData, item.taskKey); |
| 490 | - uni.showToast({title: '结束成功', icon: 'success'}); | 446 | + showToast('结束成功', 'success'); |
| 491 | refreshOrderList(); | 447 | refreshOrderList(); |
| 492 | } | 448 | } |
| 493 | } | 449 | } |
| @@ -495,13 +451,13 @@ const handleProcess = async (item) => { | @@ -495,13 +451,13 @@ const handleProcess = async (item) => { | ||
| 495 | } | 451 | } |
| 496 | } catch (error) { | 452 | } catch (error) { |
| 497 | console.error('处理工单失败:', error); | 453 | console.error('处理工单失败:', error); |
| 498 | - uni.showToast({title: resData.msg, icon: 'none'}); | 454 | + showToast(error.msg || error.message || '处理失败,请重试'); |
| 499 | } | 455 | } |
| 500 | }; | 456 | }; |
| 501 | 457 | ||
| 502 | // 回退工单-打开弹窗 | 458 | // 回退工单-打开弹窗 |
| 503 | const handleReject = (item) => { | 459 | const handleReject = (item) => { |
| 504 | - if (!item?.id) return uni.showToast({title: '工单信息异常,无法回退', icon: 'none'}); | 460 | + if (!item?.id) return showToast('工单信息异常,无法回退'); |
| 505 | currentRejectItem.value = item; | 461 | currentRejectItem.value = item; |
| 506 | rejectReason.value = ''; | 462 | rejectReason.value = ''; |
| 507 | rejectImgs.clearImgs(); | 463 | rejectImgs.clearImgs(); |
| @@ -518,9 +474,9 @@ const handleRejectModalCancel = () => { | @@ -518,9 +474,9 @@ const handleRejectModalCancel = () => { | ||
| 518 | // 确认回退工单 | 474 | // 确认回退工单 |
| 519 | const confirmReject = async () => { | 475 | const confirmReject = async () => { |
| 520 | const reason = rejectReason.value.trim(); | 476 | const reason = rejectReason.value.trim(); |
| 521 | - if (!reason) return uni.showToast({title: '请填写回退原因', icon: 'none'}); | 477 | + if (!reason) return showToast('请填写回退原因'); |
| 522 | const item = currentRejectItem.value; | 478 | const item = currentRejectItem.value; |
| 523 | - if (!item?.id) return uni.showToast({title: '工单信息异常', icon: 'none'}); | 479 | + if (!item?.id) return showToast('工单信息异常'); |
| 524 | 480 | ||
| 525 | uni.showLoading({title: '提交中...', mask: true}); | 481 | uni.showLoading({title: '提交中...', mask: true}); |
| 526 | try { | 482 | try { |
| @@ -529,12 +485,12 @@ const confirmReject = async () => { | @@ -529,12 +485,12 @@ const confirmReject = async () => { | ||
| 529 | taskId: item.taskId, operateType: nextStepMap[item.taskKey].operateTypeNoPass, agree:1, reason | 485 | taskId: item.taskId, operateType: nextStepMap[item.taskKey].operateTypeNoPass, agree:1, reason |
| 530 | }; | 486 | }; |
| 531 | await callApprovalApi(requestData, item.taskKey); | 487 | await callApprovalApi(requestData, item.taskKey); |
| 532 | - uni.showToast({title: '回退成功', icon: 'success'}); | 488 | + showToast('回退成功', 'success'); |
| 533 | handleRejectModalCancel(); | 489 | handleRejectModalCancel(); |
| 534 | refreshOrderList(); | 490 | refreshOrderList(); |
| 535 | } catch (error) { | 491 | } catch (error) { |
| 536 | console.error('回退工单失败:', error); | 492 | console.error('回退工单失败:', error); |
| 537 | - uni.showToast({title: '回退失败,请重试', icon: 'none'}); | 493 | + showToast(error.msg || error.message || '回退失败,请重试'); |
| 538 | } finally { | 494 | } finally { |
| 539 | uni.hideLoading(); | 495 | uni.hideLoading(); |
| 540 | } | 496 | } |
| @@ -559,12 +515,13 @@ const handleAcceptModalCancel = () => { | @@ -559,12 +515,13 @@ const handleAcceptModalCancel = () => { | ||
| 559 | // 验收提交 | 515 | // 验收提交 |
| 560 | const handleAcceptModalConfirm = async () => { | 516 | const handleAcceptModalConfirm = async () => { |
| 561 | const reason = acceptReason.value.trim(); | 517 | const reason = acceptReason.value.trim(); |
| 562 | - if (!reason) return uni.showToast({title: '请填写验收原因', icon: 'none'}); | ||
| 563 | - if (reason.length > 200) return uni.showToast({title: '验收原因最多200字', icon: 'none'}); | 518 | + if (!reason) return showToast('请填写验收原因'); |
| 519 | + if (reason.length > 200) return showToast('验收原因最多200字'); | ||
| 564 | 520 | ||
| 565 | const item = currentAcceptItem.value; | 521 | const item = currentAcceptItem.value; |
| 566 | - if (!item?.id) return uni.showToast({title: '工单信息异常', icon: 'none'}); | 522 | + if (!item?.id) return showToast('工单信息异常'); |
| 567 | 523 | ||
| 524 | + uni.showLoading({title: '提交中...', mask: true}); | ||
| 568 | try { | 525 | try { |
| 569 | const postData = { | 526 | const postData = { |
| 570 | returnImgs: acceptImgs.getSuccessImgUrls(), taskKey: item.taskKey, workerDataId: item.id, | 527 | returnImgs: acceptImgs.getSuccessImgUrls(), taskKey: item.taskKey, workerDataId: item.id, |
| @@ -572,12 +529,14 @@ const handleAcceptModalConfirm = async () => { | @@ -572,12 +529,14 @@ const handleAcceptModalConfirm = async () => { | ||
| 572 | operateType: acceptRadioValue.value === '0' ? nextStepMap[item.taskKey].operateTypePass : nextStepMap[item.taskKey].operateTypeNoPass | 529 | operateType: acceptRadioValue.value === '0' ? nextStepMap[item.taskKey].operateTypePass : nextStepMap[item.taskKey].operateTypeNoPass |
| 573 | }; | 530 | }; |
| 574 | await callApprovalApi(postData, item.taskKey); | 531 | await callApprovalApi(postData, item.taskKey); |
| 575 | - uni.showToast({title: '提交成功', icon: 'success'}); | 532 | + showToast('提交成功', 'success'); |
| 576 | handleAcceptModalCancel(); | 533 | handleAcceptModalCancel(); |
| 577 | refreshOrderList(); | 534 | refreshOrderList(); |
| 578 | } catch (error) { | 535 | } catch (error) { |
| 579 | console.error('验收失败:', error); | 536 | console.error('验收失败:', error); |
| 580 | - uni.showToast({title: '验收提交失败,请重试', icon: 'none'}); | 537 | + showToast(error.msg || error.message || '验收提交失败,请重试'); |
| 538 | + } finally { | ||
| 539 | + uni.hideLoading(); | ||
| 581 | } | 540 | } |
| 582 | }; | 541 | }; |
| 583 | 542 | ||
| @@ -619,6 +578,12 @@ onLoad(() => { | @@ -619,6 +578,12 @@ onLoad(() => { | ||
| 619 | } | 578 | } |
| 620 | } | 579 | } |
| 621 | 580 | ||
| 581 | +// 工单列表样式 | ||
| 582 | +.common-card-list { | ||
| 583 | + padding: calc(var(--status-bar-height) + 140rpx) 0 30rpx; | ||
| 584 | + box-sizing: border-box; | ||
| 585 | +} | ||
| 586 | + | ||
| 622 | // 工单卡片样式 | 587 | // 工单卡片样式 |
| 623 | .order-card { | 588 | .order-card { |
| 624 | margin: 0 20rpx 20rpx; | 589 | margin: 0 20rpx 20rpx; |
| @@ -627,6 +592,11 @@ onLoad(() => { | @@ -627,6 +592,11 @@ onLoad(() => { | ||
| 627 | box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.04); | 592 | box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.04); |
| 628 | } | 593 | } |
| 629 | 594 | ||
| 595 | +// 新增样式:解决按钮和文字间距、排版问题 | ||
| 596 | +.flex-1 { flex: 1; } | ||
| 597 | +.text-right { text-align: right; } | ||
| 598 | +.ml-10 { margin-left: 10rpx; } | ||
| 599 | + | ||
| 630 | // 弹窗公共样式 | 600 | // 弹窗公共样式 |
| 631 | .reject-modal-content, .accept-modal-content { | 601 | .reject-modal-content, .accept-modal-content { |
| 632 | width: 100%; | 602 | width: 100%; |
pages-sub/problem/regional-order-manage/order-detail.vue
| @@ -64,7 +64,6 @@ | @@ -64,7 +64,6 @@ | ||
| 64 | </template> | 64 | </template> |
| 65 | </up-cell> | 65 | </up-cell> |
| 66 | 66 | ||
| 67 | - | ||
| 68 | <!-- 情况描述 --> | 67 | <!-- 情况描述 --> |
| 69 | <up-cell> | 68 | <up-cell> |
| 70 | <template #title> | 69 | <template #title> |
| @@ -82,7 +81,6 @@ | @@ -82,7 +81,6 @@ | ||
| 82 | align="middle" | 81 | align="middle" |
| 83 | ></up-cell> | 82 | ></up-cell> |
| 84 | 83 | ||
| 85 | - | ||
| 86 | <!-- 提交人 --> | 84 | <!-- 提交人 --> |
| 87 | <up-cell title="提交人" :value="orderDetail.userName || '--'" align="middle"></up-cell> | 85 | <up-cell title="提交人" :value="orderDetail.userName || '--'" align="middle"></up-cell> |
| 88 | 86 | ||
| @@ -103,9 +101,6 @@ | @@ -103,9 +101,6 @@ | ||
| 103 | </template> | 101 | </template> |
| 104 | </up-cell> | 102 | </up-cell> |
| 105 | 103 | ||
| 106 | - | ||
| 107 | - | ||
| 108 | - | ||
| 109 | <!-- 问题照片 --> | 104 | <!-- 问题照片 --> |
| 110 | <up-cell title="问题照片"> | 105 | <up-cell title="问题照片"> |
| 111 | <template #value> | 106 | <template #value> |
| @@ -115,7 +110,6 @@ | @@ -115,7 +110,6 @@ | ||
| 115 | :urls="orderDetail.problemsImgs.slice(0, 3)" | 110 | :urls="orderDetail.problemsImgs.slice(0, 3)" |
| 116 | :singleSize="70" | 111 | :singleSize="70" |
| 117 | :multipleSize="70" | 112 | :multipleSize="70" |
| 118 | - | ||
| 119 | :preview-full-image="true" | 113 | :preview-full-image="true" |
| 120 | ></up-album> | 114 | ></up-album> |
| 121 | <text v-else class="empty-text">暂无问题照片</text> | 115 | <text v-else class="empty-text">暂无问题照片</text> |
| @@ -205,16 +199,10 @@ | @@ -205,16 +199,10 @@ | ||
| 205 | <!-- 1. 原标题内容:节点名称 + 操作人 --> | 199 | <!-- 1. 原标题内容:节点名称 + 操作人 --> |
| 206 | <view class="step-title"> | 200 | <view class="step-title"> |
| 207 | {{ item.name }} | 201 | {{ item.name }} |
| 208 | -<!-- <text class="operator-name">--> | ||
| 209 | -<!-- {{--> | ||
| 210 | -<!-- item.tasks && item.tasks[0]?.assigneeUser?.nickname ? '(' + item.tasks[0].assigneeUser.nickname + ')' : '(未知操作人)'--> | ||
| 211 | -<!-- }}--> | ||
| 212 | -<!-- </text>--> | ||
| 213 | </view> | 202 | </view> |
| 214 | 203 | ||
| 215 | <!-- 2. 原描述内容:时间 + 处理说明(最多200字) --> | 204 | <!-- 2. 原描述内容:时间 + 处理说明(最多200字) --> |
| 216 | <view class="step-desc"> | 205 | <view class="step-desc"> |
| 217 | - | ||
| 218 | <view class="operator-name up-line-1"> | 206 | <view class="operator-name up-line-1"> |
| 219 | 处理人: | 207 | 处理人: |
| 220 | <text>{{ item.tasks?.map(itemName => itemName.assigneeUser?.nickname).filter(Boolean).join(',') }}</text> | 208 | <text>{{ item.tasks?.map(itemName => itemName.assigneeUser?.nickname).filter(Boolean).join(',') }}</text> |
| @@ -243,7 +231,6 @@ | @@ -243,7 +231,6 @@ | ||
| 243 | :urls="item.tasks[0].attattmentUrls.slice(0, 3)" | 231 | :urls="item.tasks[0].attattmentUrls.slice(0, 3)" |
| 244 | :singleSize="70" | 232 | :singleSize="70" |
| 245 | :multipleSize="70" | 233 | :multipleSize="70" |
| 246 | - | ||
| 247 | :preview-full-image="true" | 234 | :preview-full-image="true" |
| 248 | class="step-album" | 235 | class="step-album" |
| 249 | ></up-album> | 236 | ></up-album> |
| @@ -252,7 +239,6 @@ | @@ -252,7 +239,6 @@ | ||
| 252 | </template> | 239 | </template> |
| 253 | </up-steps-item> | 240 | </up-steps-item> |
| 254 | </template> | 241 | </template> |
| 255 | - | ||
| 256 | </up-steps> | 242 | </up-steps> |
| 257 | 243 | ||
| 258 | <!-- 流程节点为空时的提示 --> | 244 | <!-- 流程节点为空时的提示 --> |
| @@ -267,23 +253,23 @@ | @@ -267,23 +253,23 @@ | ||
| 267 | </view> | 253 | </view> |
| 268 | 254 | ||
| 269 | <!-- activeTab==0的时候才出现, 也就是待办 --> | 255 | <!-- activeTab==0的时候才出现, 也就是待办 --> |
| 270 | -<!-- <view v-if="activeTab==0&&nextStepMap[orderDetail.taskKey]" class="fixed-bottom-btn-wrap">--> | ||
| 271 | -<!-- <view class="u-body-item u-flex common-justify-between common-item-center ">--> | ||
| 272 | -<!-- <up-button type="warning" size="normal" @click="handleReject(orderDetail)"--> | ||
| 273 | -<!-- v-show="nextStepMap[orderDetail.taskKey].backShow">回退--> | ||
| 274 | -<!-- </up-button>--> | 256 | + <view v-if="activeTab==0&&nextStepMap[orderDetail.taskKey]" class="fixed-bottom-btn-wrap"> |
| 257 | + <view class="u-body-item u-flex common-justify-between common-item-center "> | ||
| 258 | + <up-button type="warning" size="normal" @click="handleReject(orderDetail)" | ||
| 259 | + v-show="nextStepMap[orderDetail.taskKey].backShow">回退 | ||
| 260 | + </up-button> | ||
| 275 | 261 | ||
| 276 | -<!-- <up-button type="success" size="normal" @click="handleRenew(orderDetail)"--> | ||
| 277 | -<!-- v-show="nextStepMap[orderDetail.taskKey].renewShow">重新提交--> | ||
| 278 | -<!-- </up-button>--> | 262 | + <up-button type="success" size="normal" @click="handleRenew(orderDetail)" |
| 263 | + v-show="nextStepMap[orderDetail.taskKey].renewShow">重新提交 | ||
| 264 | + </up-button> | ||
| 279 | 265 | ||
| 280 | -<!-- <up-button type="primary" size="normal" @click="handleProcess(orderDetail)">{{--> | ||
| 281 | -<!-- nextStepMap[orderDetail.taskKey].btnText--> | ||
| 282 | -<!-- }}--> | ||
| 283 | -<!-- </up-button>--> | 266 | + <up-button type="primary" size="normal" @click="handleProcess(orderDetail)">{{ |
| 267 | + nextStepMap[orderDetail.taskKey].btnText | ||
| 268 | + }} | ||
| 269 | + </up-button> | ||
| 284 | 270 | ||
| 285 | -<!-- </view>--> | ||
| 286 | -<!-- </view>--> | 271 | + </view> |
| 272 | + </view> | ||
| 287 | 273 | ||
| 288 | <!-- 回退原因弹窗(新增图片上传) --> | 274 | <!-- 回退原因弹窗(新增图片上传) --> |
| 289 | <up-modal | 275 | <up-modal |
| @@ -375,7 +361,7 @@ | @@ -375,7 +361,7 @@ | ||
| 375 | </template> | 361 | </template> |
| 376 | 362 | ||
| 377 | <script setup lang="ts"> | 363 | <script setup lang="ts"> |
| 378 | -import {ref} from 'vue'; | 364 | +import {ref, computed} from 'vue'; |
| 379 | import {onLoad, onShow} from '@dcloudio/uni-app'; | 365 | import {onLoad, onShow} from '@dcloudio/uni-app'; |
| 380 | import {timeFormat} from '@/uni_modules/uview-plus'; | 366 | import {timeFormat} from '@/uni_modules/uview-plus'; |
| 381 | import { | 367 | import { |
| @@ -388,10 +374,73 @@ import { | @@ -388,10 +374,73 @@ import { | ||
| 388 | dcyUniversalApproval | 374 | dcyUniversalApproval |
| 389 | } from '@/api/regional-order-manage/regional-order-manage'; | 375 | } from '@/api/regional-order-manage/regional-order-manage'; |
| 390 | import {nextStepMap, buzStatusMap, calculateFormatTimeDiff} from '@/common/utils/common' | 376 | import {nextStepMap, buzStatusMap, calculateFormatTimeDiff} from '@/common/utils/common' |
| 391 | -// 引入图片上传组合式函数 | ||
| 392 | import {useUploadImgs} from '@/common/utils/useUploadImgs' | 377 | import {useUploadImgs} from '@/common/utils/useUploadImgs' |
| 378 | +import { useUserStore } from '@/pinia/user'; | ||
| 379 | + | ||
| 380 | +// ========== 全局实例 & 常量 ========== | ||
| 381 | +const userStore = useUserStore(); | ||
| 382 | +const USER_ROLES = userStore.userInfo?.roles || []; | ||
| 383 | +// ========== 页面通信事件通道 解决未定义BUG ========== | ||
| 384 | +const eventChannel = ref<any>(null); | ||
| 385 | + | ||
| 386 | + | ||
| 387 | +// ========== 公共封装方法 (和列表页完全一致) ========== | ||
| 388 | +/** | ||
| 389 | + * 统一提示方法 | ||
| 390 | + */ | ||
| 391 | +const showToast = (title: string, icon: 'none'|'success'|'loading' = 'none', duration = 2000) => { | ||
| 392 | + uni.showToast({ title, icon, duration }); | ||
| 393 | +}; | ||
| 394 | + | ||
| 395 | +/** | ||
| 396 | + * 生成统一的临时存储key | ||
| 397 | + */ | ||
| 398 | +const generateTempKey = (prefix = 'order') => { | ||
| 399 | + return `${prefix}_${Date.now()}_${Math.floor(Math.random() * 10000)}`; | ||
| 400 | +}; | ||
| 401 | + | ||
| 402 | +/** | ||
| 403 | + * 存储工单数据到本地缓存 | ||
| 404 | + */ | ||
| 405 | +const setOrderStorage = (item: any, prefix: string) => { | ||
| 406 | + if (!item?.id) return null; | ||
| 407 | + const tempKey = generateTempKey(prefix); | ||
| 408 | + try { | ||
| 409 | + uni.setStorageSync(tempKey, item); | ||
| 410 | + return tempKey; | ||
| 411 | + } catch (error) { | ||
| 412 | + console.error('存储工单数据失败:', error); | ||
| 413 | + showToast('数据存储异常,请重试'); | ||
| 414 | + return null; | ||
| 415 | + } | ||
| 416 | +}; | ||
| 393 | 417 | ||
| 394 | -// 状态管理 | 418 | +/** |
| 419 | + * 统一调用审批接口 (角色+taskKey匹配) | ||
| 420 | + */ | ||
| 421 | +const callApprovalApi = async (params: any, taskKey: string) => { | ||
| 422 | + if (taskKey === 'shRegionManager') return await dcyUniversalApproval(params); | ||
| 423 | + if (taskKey === 'regionManager') return await qyUniversalApproval(params); | ||
| 424 | + if (USER_ROLES.includes('regional_manager')) return await daquUniversalApproval(params); | ||
| 425 | + if (USER_ROLES.includes('Inspector_global')) return await dcyUniversalApproval(params); | ||
| 426 | + if (USER_ROLES.includes('patrol_global')) return await qyUniversalApproval(params); | ||
| 427 | + throw new Error('未匹配到对应的审批接口,请检查角色或工单taskKey'); | ||
| 428 | +}; | ||
| 429 | + | ||
| 430 | +/** | ||
| 431 | + * 统一跳转工单页面 支持事件回调+参数转义 | ||
| 432 | + */ | ||
| 433 | +const navToOrderPage = (path: string, query = {}, events = {}) => { | ||
| 434 | + const queryStr = Object.keys(query).map(k => `${k}=${encodeURIComponent(query[k])}`).join('&'); | ||
| 435 | + const url = `${path}${queryStr ? '?' + queryStr : ''}`; | ||
| 436 | + if(Object.keys(events).length) { | ||
| 437 | + uni.navigateTo({ url, events }); | ||
| 438 | + } else { | ||
| 439 | + uni.redirectTo({ url }); | ||
| 440 | + } | ||
| 441 | +}; | ||
| 442 | + | ||
| 443 | +// ========== 基础状态 ========== | ||
| 395 | const loading = ref(true); | 444 | const loading = ref(true); |
| 396 | const activeTopTab = ref(0); // 顶部Tab激活索引 | 445 | const activeTopTab = ref(0); // 顶部Tab激活索引 |
| 397 | const activeImgTab = ref(0); // 图片分类Tab激活索引 | 446 | const activeImgTab = ref(0); // 图片分类Tab激活索引 |
| @@ -402,7 +451,7 @@ const topTabList = ref([ | @@ -402,7 +451,7 @@ const topTabList = ref([ | ||
| 402 | {name: '流程节点'} | 451 | {name: '流程节点'} |
| 403 | ]); | 452 | ]); |
| 404 | 453 | ||
| 405 | -// 流程节点数据(初始化适配接口格式) | 454 | +// 流程节点数据 |
| 406 | const processData = ref<any>({ | 455 | const processData = ref<any>({ |
| 407 | status: 2, | 456 | status: 2, |
| 408 | activityNodes: [], | 457 | activityNodes: [], |
| @@ -410,7 +459,7 @@ const processData = ref<any>({ | @@ -410,7 +459,7 @@ const processData = ref<any>({ | ||
| 410 | todoTask: null | 459 | todoTask: null |
| 411 | }); | 460 | }); |
| 412 | 461 | ||
| 413 | -// 图片分类Tab列表(带角标配置) | 462 | +// 图片分类Tab列表 |
| 414 | const imgTabList = ref([ | 463 | const imgTabList = ref([ |
| 415 | {name: '开始', badge: {isDot: true}}, | 464 | {name: '开始', badge: {isDot: true}}, |
| 416 | {name: '进行中'}, | 465 | {name: '进行中'}, |
| @@ -419,7 +468,7 @@ const imgTabList = ref([ | @@ -419,7 +468,7 @@ const imgTabList = ref([ | ||
| 419 | {name: '材料'} | 468 | {name: '材料'} |
| 420 | ]); | 469 | ]); |
| 421 | 470 | ||
| 422 | -// 工单详情数据(初始化赋值,可被接口数据覆盖) | 471 | +// 工单详情数据 |
| 423 | const orderDetail = ref<any>({ | 472 | const orderDetail = ref<any>({ |
| 424 | id: 0, | 473 | id: 0, |
| 425 | busiLine: '', | 474 | busiLine: '', |
| @@ -472,24 +521,15 @@ const orderDetail = ref<any>({ | @@ -472,24 +521,15 @@ const orderDetail = ref<any>({ | ||
| 472 | materialImgs: [] | 521 | materialImgs: [] |
| 473 | }); | 522 | }); |
| 474 | 523 | ||
| 475 | -/** | ||
| 476 | - * 当前激活的图片列表(无需函数调用,直接访问) | ||
| 477 | - */ | ||
| 478 | const currentImgList = ref([]) | 524 | const currentImgList = ref([]) |
| 479 | - | ||
| 480 | const tabKeyMap = ['startImgs', 'processingImgs', 'endImgs', 'personImgs', 'materialImgs']; | 525 | const tabKeyMap = ['startImgs', 'processingImgs', 'endImgs', 'personImgs', 'materialImgs']; |
| 481 | const imgTabChange = (({index}: { index: number }) => { | 526 | const imgTabChange = (({index}: { index: number }) => { |
| 482 | - console.log(index) | ||
| 483 | const currentKey = tabKeyMap[index] | 527 | const currentKey = tabKeyMap[index] |
| 484 | - console.log(currentKey) | ||
| 485 | - console.log(orderDetail.value[currentKey]) | ||
| 486 | currentImgList.value = orderDetail.value[currentKey] | 528 | currentImgList.value = orderDetail.value[currentKey] |
| 487 | }) | 529 | }) |
| 488 | 530 | ||
| 489 | /** | 531 | /** |
| 490 | * 截取reason最多200字 | 532 | * 截取reason最多200字 |
| 491 | - * @param reason 处理说明 | ||
| 492 | - * @returns 截取后的字符串 | ||
| 493 | */ | 533 | */ |
| 494 | const getLimitReason = (reason: string | null | undefined) => { | 534 | const getLimitReason = (reason: string | null | undefined) => { |
| 495 | if (!reason) return '无处理说明'; | 535 | if (!reason) return '无处理说明'; |
| @@ -498,16 +538,11 @@ const getLimitReason = (reason: string | null | undefined) => { | @@ -498,16 +538,11 @@ const getLimitReason = (reason: string | null | undefined) => { | ||
| 498 | } | 538 | } |
| 499 | 539 | ||
| 500 | /** | 540 | /** |
| 501 | - * 动态获取当前步骤索引(核心:根据接口数据的status字段判断) | ||
| 502 | - * status=1:当前步骤(处理中) | ||
| 503 | - * status=2:已完成步骤 | ||
| 504 | - * @returns {number} 当前激活的步骤索引(从0开始) | 541 | + * 动态获取当前步骤索引 |
| 505 | */ | 542 | */ |
| 506 | const getCurrentStepIndex = () => { | 543 | const getCurrentStepIndex = () => { |
| 507 | const {activityNodes} = processData.value; | 544 | const {activityNodes} = processData.value; |
| 508 | if (!activityNodes || !activityNodes.length) return 0; | 545 | if (!activityNodes || !activityNodes.length) return 0; |
| 509 | - | ||
| 510 | - // 2. 若没有处理中的节点(全部已完成),则激活最后一个节点 | ||
| 511 | return activityNodes.length - 1; | 546 | return activityNodes.length - 1; |
| 512 | } | 547 | } |
| 513 | 548 | ||
| @@ -518,7 +553,6 @@ const DetailQuery = async (taskIdStr: string) => { | @@ -518,7 +553,6 @@ const DetailQuery = async (taskIdStr: string) => { | ||
| 518 | console.log('当前工单ID:', taskIdStr) | 553 | console.log('当前工单ID:', taskIdStr) |
| 519 | try { | 554 | try { |
| 520 | loading.value = true; | 555 | loading.value = true; |
| 521 | - // 转换activeTab为数字类型,避免类型不一致导致接口调用失败 | ||
| 522 | const tabType = Number(activeTab.value); | 556 | const tabType = Number(activeTab.value); |
| 523 | let res: any; | 557 | let res: any; |
| 524 | 558 | ||
| @@ -533,7 +567,6 @@ const DetailQuery = async (taskIdStr: string) => { | @@ -533,7 +567,6 @@ const DetailQuery = async (taskIdStr: string) => { | ||
| 533 | return; | 567 | return; |
| 534 | } | 568 | } |
| 535 | 569 | ||
| 536 | - // 覆盖工单详情数据 | ||
| 537 | if (res) { | 570 | if (res) { |
| 538 | orderDetail.value = res; | 571 | orderDetail.value = res; |
| 539 | currentImgList.value = orderDetail.value.startImgs | 572 | currentImgList.value = orderDetail.value.startImgs |
| @@ -552,24 +585,19 @@ const activeTab = ref('') | @@ -552,24 +585,19 @@ const activeTab = ref('') | ||
| 552 | const processInstanceId = ref('') | 585 | const processInstanceId = ref('') |
| 553 | 586 | ||
| 554 | const activeTopTabClick = async (item: any) => { | 587 | const activeTopTabClick = async (item: any) => { |
| 555 | - console.log(item) | ||
| 556 | activeTopTab.value = item.index | 588 | activeTopTab.value = item.index |
| 557 | if (activeTopTab.value == 1) { | 589 | if (activeTopTab.value == 1) { |
| 558 | let getData = { | 590 | let getData = { |
| 559 | processInstanceId: processInstanceId.value, | 591 | processInstanceId: processInstanceId.value, |
| 560 | } | 592 | } |
| 561 | const res = await getApprovalDetail(getData) | 593 | const res = await getApprovalDetail(getData) |
| 562 | - console.log(res) | ||
| 563 | - // 关键:格式化数据,补充up-steps要求的title字段 | ||
| 564 | if (res && res.activityNodes && res.activityNodes.length) { | 594 | if (res && res.activityNodes && res.activityNodes.length) { |
| 565 | - // 1. 先过滤:剔除 name 为 "结束" 的节点 | ||
| 566 | const filteredActivityNodes = res.activityNodes.filter(node => { | 595 | const filteredActivityNodes = res.activityNodes.filter(node => { |
| 567 | - // 返回 true 保留节点,返回 false 剔除节点 | ||
| 568 | return node.name !== '结束'; | 596 | return node.name !== '结束'; |
| 569 | }); | 597 | }); |
| 570 | const formatActivityNodes = filteredActivityNodes.map(node => ({ | 598 | const formatActivityNodes = filteredActivityNodes.map(node => ({ |
| 571 | ...node, | 599 | ...node, |
| 572 | - title: node.name // 补充强制字段,满足3.3.48版本组件要求 | 600 | + title: node.name |
| 573 | })); | 601 | })); |
| 574 | processData.value = { | 602 | processData.value = { |
| 575 | ...res, | 603 | ...res, |
| @@ -581,11 +609,10 @@ const activeTopTabClick = async (item: any) => { | @@ -581,11 +609,10 @@ const activeTopTabClick = async (item: any) => { | ||
| 581 | } | 609 | } |
| 582 | } | 610 | } |
| 583 | 611 | ||
| 584 | -// ========== 回退弹窗相关状态(新增图片上传实例) ========== | ||
| 585 | -const rejectModalShow = ref(false); // 回退modal显示开关 | ||
| 586 | -const rejectReason = ref(''); // 回退原因 | ||
| 587 | -const currentRejectItem = ref<any>(null); // 当前回退工单 | ||
| 588 | -// 回退图片上传配置 | 612 | +// ========== 回退弹窗相关 ========== |
| 613 | +const rejectModalShow = ref(false); | ||
| 614 | +const rejectReason = ref(''); | ||
| 615 | +const currentRejectItem = ref<any>(null); | ||
| 589 | const rejectImgs = useUploadImgs({ | 616 | const rejectImgs = useUploadImgs({ |
| 590 | maxCount: 3, | 617 | maxCount: 3, |
| 591 | uploadText: '选择回退图片', | 618 | uploadText: '选择回退图片', |
| @@ -594,13 +621,11 @@ const rejectImgs = useUploadImgs({ | @@ -594,13 +621,11 @@ const rejectImgs = useUploadImgs({ | ||
| 594 | fieldName: 'rejectImgs' | 621 | fieldName: 'rejectImgs' |
| 595 | }) | 622 | }) |
| 596 | 623 | ||
| 597 | - | ||
| 598 | -// ========== 验收弹窗相关状态(新增图片上传实例) ========== | ||
| 599 | -const acceptModalShow = ref(false); // 验收弹窗显示开关 | ||
| 600 | -const acceptRadioValue = ref('0'); // 单选框值,默认0(通过) | ||
| 601 | -const acceptReason = ref(''); // 验收原因 | ||
| 602 | -const currentAcceptItem = ref<any>(null); // 当前验收的工单项 | ||
| 603 | -// 验收图片上传配置 | 624 | +// ========== 验收弹窗相关 ========== |
| 625 | +const acceptModalShow = ref(false); | ||
| 626 | +const acceptRadioValue = ref('0'); | ||
| 627 | +const acceptReason = ref(''); | ||
| 628 | +const currentAcceptItem = ref<any>(null); | ||
| 604 | const acceptImgs = useUploadImgs({ | 629 | const acceptImgs = useUploadImgs({ |
| 605 | maxCount: 3, | 630 | maxCount: 3, |
| 606 | uploadText: '选择验收图片', | 631 | uploadText: '选择验收图片', |
| @@ -609,270 +634,202 @@ const acceptImgs = useUploadImgs({ | @@ -609,270 +634,202 @@ const acceptImgs = useUploadImgs({ | ||
| 609 | fieldName: 'acceptImgs' | 634 | fieldName: 'acceptImgs' |
| 610 | }) | 635 | }) |
| 611 | 636 | ||
| 612 | - | ||
| 613 | -// ========== 生成临时key ========== | ||
| 614 | -const generateTempKey = () => { | ||
| 615 | - return 'renew_order_' + Date.now() + '_' + Math.floor(Math.random() * 10000); | ||
| 616 | -}; | ||
| 617 | - | ||
| 618 | -// ========== handleRenew 重新提交工单 ========== | 637 | +// ========== 重新提交工单 完整对齐列表页 ========== |
| 619 | const handleRenew = (item: any) => { | 638 | const handleRenew = (item: any) => { |
| 620 | - // 校验工单有效性 | ||
| 621 | - if (!item || !item.id) { | ||
| 622 | - uni.showToast({title: '工单信息异常,无法重新提交', icon: 'none'}); | ||
| 623 | - return; | 639 | + if (!item?.id) { |
| 640 | + return showToast('工单信息异常,无法重新提交'); | ||
| 624 | } | 641 | } |
| 642 | + const tempKey = setOrderStorage(item, 'renew_order'); | ||
| 643 | + if (!tempKey) return; | ||
| 625 | 644 | ||
| 626 | - // 1. 生成唯一临时标识 | ||
| 627 | - const tempKey = generateTempKey(); | ||
| 628 | - // 2. 将完整工单数据存入本地临时存储(同步存储,确保数据立即生效) | ||
| 629 | - try { | ||
| 630 | - uni.setStorageSync(tempKey, item); | ||
| 631 | - } catch (error) { | ||
| 632 | - console.error('存储工单数据失败:', error); | ||
| 633 | - uni.showToast({title: '数据存储异常,无法重新提交', icon: 'none'}); | ||
| 634 | - return; | ||
| 635 | - } | 645 | + const pageUrl = USER_ROLES.includes('patrol_global') |
| 646 | + ? '/pages-sub/problem/regional-order-manage/add-patrol-order' | ||
| 647 | + : '/pages-sub/problem/regional-order-manage/add-order'; | ||
| 636 | 648 | ||
| 637 | - // 3. URL 仅传递「唯一标识」和「重新提交标记」(数据量极小,无长度问题) | ||
| 638 | - uni.navigateTo({ | ||
| 639 | - url: `/pages-sub/problem/regional-order-manage/add-patrol-order?isRenew=1&tempKey=${tempKey}` | ||
| 640 | - }); | 649 | + navToOrderPage(pageUrl, { isRenew: 1, tempKey }); |
| 641 | }; | 650 | }; |
| 642 | 651 | ||
| 643 | -// ========== handleReject 打开回退弹窗 ========== | 652 | +// ========== 打开回退弹窗 ========== |
| 644 | const handleReject = (item: any) => { | 653 | const handleReject = (item: any) => { |
| 645 | - // 校验工单有效性 | ||
| 646 | - if (!item || !item.id) { | ||
| 647 | - uni.showToast({title: '工单信息异常,无法回退', icon: 'none'}); | ||
| 648 | - return; | 654 | + if (!item?.id) { |
| 655 | + return showToast('工单信息异常,无法回退'); | ||
| 649 | } | 656 | } |
| 650 | currentRejectItem.value = item; | 657 | currentRejectItem.value = item; |
| 651 | - rejectReason.value = ''; // 清空上次输入 | ||
| 652 | - rejectImgs.clearImgs(); // 清空上传图片 | ||
| 653 | - rejectModalShow.value = true; // 显示回退modal | 658 | + rejectReason.value = ''; |
| 659 | + rejectImgs.clearImgs(); | ||
| 660 | + rejectModalShow.value = true; | ||
| 654 | }; | 661 | }; |
| 655 | 662 | ||
| 656 | -// ========== 回退弹窗取消按钮 ========== | 663 | +// ========== 回退弹窗取消 ========== |
| 657 | const handleRejectModalCancel = () => { | 664 | const handleRejectModalCancel = () => { |
| 658 | rejectModalShow.value = false; | 665 | rejectModalShow.value = false; |
| 659 | rejectReason.value = ''; | 666 | rejectReason.value = ''; |
| 660 | - rejectImgs.clearImgs(); // 清空上传图片 | 667 | + rejectImgs.clearImgs(); |
| 661 | }; | 668 | }; |
| 662 | 669 | ||
| 663 | -// ========== 确认回退工单(新增returnImgs传参) ========== | 670 | +// ========== 确认回退工单 完整对齐列表页 ========== |
| 664 | const confirmReject = async () => { | 671 | const confirmReject = async () => { |
| 665 | - // 严格校验回退原因(去除首尾空格) | ||
| 666 | - const rejectReasonTrim = rejectReason.value.trim(); | ||
| 667 | - if (!rejectReasonTrim) { | ||
| 668 | - uni.showToast({title: '请填写回退原因', icon: 'none', duration: 1000}); | ||
| 669 | - return; | ||
| 670 | - } | ||
| 671 | - // 校验当前工单有效性 | ||
| 672 | - if (!currentRejectItem.value || !currentRejectItem.value.id) { | ||
| 673 | - uni.showToast({title: '工单信息异常,无法提交', icon: 'none', duration: 1000}); | ||
| 674 | - rejectModalShow.value = false; | ||
| 675 | - return; | ||
| 676 | - } | ||
| 677 | - try { | ||
| 678 | - // 显示加载中,防止重复提交 | ||
| 679 | - uni.showLoading({title: '提交中...', mask: true}); | 672 | + const reason = rejectReason.value.trim(); |
| 673 | + if (!reason) return showToast('请填写回退原因'); | ||
| 674 | + const item = currentRejectItem.value; | ||
| 675 | + if (!item?.id) return showToast('工单信息异常'); | ||
| 680 | 676 | ||
| 681 | - // 构建请求参数(新增returnImgs) | 677 | + uni.showLoading({title: '提交中...', mask: true}); |
| 678 | + try { | ||
| 682 | const requestData = { | 679 | const requestData = { |
| 683 | - "returnImgs": rejectImgs.getSuccessImgUrls(), // 回退图片URL数组 | ||
| 684 | - "workerDataId": currentRejectItem.value.id, | ||
| 685 | - "taskKey": currentRejectItem.value.taskKey, | ||
| 686 | - "taskId": currentRejectItem.value.taskId, | ||
| 687 | - "operateType": nextStepMap[currentRejectItem.value.taskKey].operateTypeNoPass, | 680 | + "returnImgs": rejectImgs.getSuccessImgUrls(), |
| 681 | + "workerDataId": item.id, | ||
| 682 | + "taskKey": item.taskKey, | ||
| 683 | + "taskId": item.taskId, | ||
| 684 | + "operateType": nextStepMap[item.taskKey].operateTypeNoPass, | ||
| 688 | "agree": 1, | 685 | "agree": 1, |
| 689 | - "reason": rejectReasonTrim | 686 | + "reason": reason |
| 690 | }; | 687 | }; |
| 691 | - // 调用回退工单接口 | ||
| 692 | - const res = await qyUniversalApproval(requestData); | ||
| 693 | - uni.showToast({title: '回退成功', icon: 'success', duration: 1000}); | ||
| 694 | - | ||
| 695 | - rejectModalShow.value = false; | ||
| 696 | - // 重新获取工单详情,刷新页面 | ||
| 697 | - // await DetailQuery(taskId.value); | ||
| 698 | - uni.reLaunch({ | ||
| 699 | - url: `/pages-sub/problem/regional-order-manage/index` | ||
| 700 | - }); | ||
| 701 | - } catch (error) { | 688 | + await callApprovalApi(requestData, item.taskKey); |
| 689 | + showToast('回退成功', 'success'); | ||
| 690 | + handleRejectModalCancel(); | ||
| 691 | + eventChannel.value.emit('needRefresh'); | ||
| 692 | + uni.navigateBack({ delta: 1 }); | ||
| 693 | + } catch (error: any) { | ||
| 702 | console.error('回退工单失败:', error); | 694 | console.error('回退工单失败:', error); |
| 703 | - uni.showToast({title: '网络异常,回退失败', icon: 'none', duration: 1000}); | 695 | + showToast(error.msg || error.message || '回退失败,请重试'); |
| 704 | } finally { | 696 | } finally { |
| 705 | - // 隐藏加载中 | ||
| 706 | uni.hideLoading(); | 697 | uni.hideLoading(); |
| 707 | } | 698 | } |
| 708 | }; | 699 | }; |
| 709 | 700 | ||
| 710 | -// ========== handleProcess 处理工单 ========== | ||
| 711 | -const handleProcess = async (item) => { | ||
| 712 | - console.log(nextStepMap[item.taskKey].name) | 701 | +// ========== 处理工单核心方法 完整对齐列表页+全BUG修复 ========== |
| 702 | +const handleProcess = async (item: any) => { | ||
| 703 | + if (!item) return showToast('工单信息异常'); | ||
| 704 | + const stepName = nextStepMap[item.taskKey]?.name; | ||
| 705 | + if (!stepName) return showToast('暂无处理权限或工单状态异常'); | ||
| 706 | + | ||
| 707 | + uni.showLoading({title: '处理中...', mask: true}); | ||
| 713 | try { | 708 | try { |
| 714 | - if (nextStepMap[item.taskKey]?.name == '大区经理分配') { | ||
| 715 | - // ① 生成唯一临时key(统一规则,避免冲突) | ||
| 716 | - const tempKey = `distribute_order_${Date.now()}_${Math.floor(Math.random() * 10000)}`; | ||
| 717 | - // ② 存储完整item到本地缓存(同步存储,确保立即生效) | ||
| 718 | - try { | ||
| 719 | - uni.setStorageSync(tempKey, item); | ||
| 720 | - } catch (error) { | ||
| 721 | - console.error('存储分配工单数据失败:', error); | ||
| 722 | - uni.showToast({title: '数据存储异常,无法跳转', icon: 'none'}); | ||
| 723 | - return; | ||
| 724 | - } | ||
| 725 | - // ③ URL仅传递临时key,无其他冗余参数 | ||
| 726 | - uni.navigateTo({ | ||
| 727 | - url: `/pages-sub/problem/regional-order-manage/distribution-order?tempKey=${tempKey}` | ||
| 728 | - }) | 709 | + // 大区经理分配 |
| 710 | + if (stepName === '大区经理分配') { | ||
| 711 | + const tempKey = setOrderStorage(item, 'distribute_order'); | ||
| 712 | + tempKey && navToOrderPage('/pages-sub/problem/regional-order-manage/distribution-order', { tempKey }); | ||
| 729 | } | 713 | } |
| 730 | - if (nextStepMap[item.taskKey]?.name == '养护员待实施') { | ||
| 731 | - // ① 生成唯一临时key(和重新提交工单逻辑一致,避免冲突) | ||
| 732 | - const tempKey = `maintain_order_${Date.now()}_${Math.floor(Math.random() * 10000)}`; | ||
| 733 | - | ||
| 734 | - // ② 存储完整item到本地缓存(同步存储,确保立即生效) | ||
| 735 | - try { | ||
| 736 | - uni.setStorageSync(tempKey, item); | ||
| 737 | - } catch (error) { | ||
| 738 | - console.error('存储养护工单数据失败:', error); | ||
| 739 | - uni.showToast({title: '数据存储异常,无法跳转', icon: 'none'}); | ||
| 740 | - return; | ||
| 741 | - } | ||
| 742 | - | ||
| 743 | - // ③ URL仅传递临时key(可选:携带必要简单参数,方便目标页面快速使用) | ||
| 744 | - uni.navigateTo({ | ||
| 745 | - url: `/pages-sub/problem/regional-order-manage/add-maintain-order?tempKey=${tempKey}` | ||
| 746 | - }) | 714 | + // 养护员待实施 |
| 715 | + else if (stepName === '养护员待实施') { | ||
| 716 | + const tempKey = setOrderStorage(item, 'maintain_order'); | ||
| 717 | + tempKey && navToOrderPage('/pages-sub/problem/regional-order-manage/add-maintain-order', { tempKey }); | ||
| 747 | } | 718 | } |
| 748 | - // 养护组长验收 - 打开弹窗 | ||
| 749 | - if (nextStepMap[item.taskKey]?.name == '养护组长验收') { | ||
| 750 | - currentAcceptItem.value = item; // 存储当前工单信息 | ||
| 751 | - acceptReason.value = ''; // 清空上次的验收原因 | ||
| 752 | - acceptRadioValue.value = '0'; // 重置默认选中“通过” | ||
| 753 | - acceptModalShow.value = true; // 显示验收弹窗 | 719 | + // 督察员单子大区经理分配 |
| 720 | + else if (stepName === '督察员单子大区经理分配') { | ||
| 721 | + const postData = { | ||
| 722 | + taskKey: item.taskKey, taskId: item.taskId, operateType:60, workerDataId:item.id, | ||
| 723 | + agree:0, reason:item.remark, roadId:item.roadId, roadName:item.roadName, | ||
| 724 | + pressingType:item.pressingType, orderName:item.orderName, | ||
| 725 | + expectedFinishDate: item.expectedFinishDate, busiLine:item.busiLine | ||
| 726 | + }; | ||
| 727 | + await dcyUniversalApproval(postData); | ||
| 728 | + showToast('分配成功', 'success'); | ||
| 729 | + eventChannel.value.emit('needRefresh'); | ||
| 730 | + uni.navigateBack({ delta: 1 }); | ||
| 754 | } | 731 | } |
| 755 | - // 巡查员验收 - 打开弹窗 | ||
| 756 | - if (nextStepMap[item.taskKey]?.name == '巡查员验收') { | ||
| 757 | - currentAcceptItem.value = item; // 存储当前工单信息 | ||
| 758 | - acceptReason.value = ''; // 清空上次的验收原因 | ||
| 759 | - acceptRadioValue.value = '0'; // 重置默认选中“通过” | ||
| 760 | - acceptModalShow.value = true; // 显示验收弹窗 | 732 | + // 验收弹窗 |
| 733 | + else if (['养护组长验收', '巡查员验收'].includes(stepName)) { | ||
| 734 | + currentAcceptItem.value = item; | ||
| 735 | + acceptReason.value = ''; | ||
| 736 | + acceptRadioValue.value = '0'; | ||
| 737 | + acceptModalShow.value = true; | ||
| 761 | } | 738 | } |
| 762 | - | ||
| 763 | - // 发起人确认 | ||
| 764 | - if (nextStepMap[item.taskKey]?.name == '发起人确认') { | ||
| 765 | - console.log(item) | 739 | + // 发起人确认-结束工单 |
| 740 | + else if (stepName === '发起人确认') { | ||
| 741 | + uni.hideLoading(); | ||
| 766 | uni.showModal({ | 742 | uni.showModal({ |
| 767 | title: "结束工单", | 743 | title: "结束工单", |
| 768 | content: "请确定是否结束工单?", | 744 | content: "请确定是否结束工单?", |
| 769 | - success: async function (res) { | 745 | + success: async (res) => { |
| 770 | if (res.confirm) { | 746 | if (res.confirm) { |
| 771 | - // 构建请求参数 | ||
| 772 | const requestData = { | 747 | const requestData = { |
| 773 | - "returnImgs": rejectImgs.getSuccessImgUrls(), // 改造后:获取上传成功的图片URL | ||
| 774 | - "workerDataId": item.id, | ||
| 775 | - "taskKey":'ylInspectorStart', | ||
| 776 | - "taskId": item.taskId, | ||
| 777 | - "operateType": 200, | ||
| 778 | - "agree": 1, | ||
| 779 | - "reason": '结束工单' | 748 | + workerDataId: item.id, taskKey: 'ylInspectorStart', taskId: item.taskId, |
| 749 | + operateType: 200, agree: 1, reason: '结束工单' | ||
| 780 | }; | 750 | }; |
| 781 | - // 调用回退工单接口 | ||
| 782 | - const res = await qyUniversalApproval(requestData); | ||
| 783 | - uni.showToast({title: '结束成功', icon: 'success', duration: 1000}); | ||
| 784 | - rejectModalShow.value = false; | ||
| 785 | - paging.value?.reload(); // 刷新列表 | ||
| 786 | - } else if (res.cancel) { | ||
| 787 | - console.log("用户点击取消"); | 751 | + await callApprovalApi(requestData, item.taskKey); |
| 752 | + showToast('结束成功', 'success'); | ||
| 753 | + eventChannel.value.emit('needRefresh'); | ||
| 754 | + uni.navigateBack({ delta: 1 }); | ||
| 788 | } | 755 | } |
| 789 | }, | 756 | }, |
| 790 | }); | 757 | }); |
| 791 | } | 758 | } |
| 792 | - } catch (error) { | 759 | + } catch (error: any) { |
| 793 | console.error('处理工单失败:', error); | 760 | console.error('处理工单失败:', error); |
| 794 | - uni.showToast({title: '处理失败,请重试', icon: 'none'}); | 761 | + showToast(error.msg || error.message || '处理失败,请重试'); |
| 762 | + } finally { | ||
| 763 | + uni.hideLoading(); | ||
| 795 | } | 764 | } |
| 796 | }; | 765 | }; |
| 797 | 766 | ||
| 798 | -// ========== 验收弹窗取消按钮(新增清空图片) ========== | 767 | +// ========== 验收弹窗取消 ========== |
| 799 | const handleAcceptModalCancel = () => { | 768 | const handleAcceptModalCancel = () => { |
| 800 | acceptModalShow.value = false; | 769 | acceptModalShow.value = false; |
| 801 | acceptReason.value = ''; | 770 | acceptReason.value = ''; |
| 802 | acceptRadioValue.value = '0'; | 771 | acceptRadioValue.value = '0'; |
| 803 | - acceptImgs.clearImgs(); // 清空验收图片 | 772 | + acceptImgs.clearImgs(); |
| 804 | }; | 773 | }; |
| 805 | 774 | ||
| 806 | -// ========== 验收弹窗确认按钮(新增returnImgs传参) ========== | 775 | +// ========== 验收弹窗确认 修复BUG+对齐列表页 ========== |
| 807 | const handleAcceptModalConfirm = async () => { | 776 | const handleAcceptModalConfirm = async () => { |
| 808 | - // 1. 校验验收原因是否为空 | ||
| 809 | - if (!acceptReason.value.trim()) { | ||
| 810 | - uni.showToast({title: '请填写验收原因', icon: 'none', duration: 1000}); | ||
| 811 | - return; | ||
| 812 | - } | ||
| 813 | - // 2. 校验验收原因长度 | ||
| 814 | - if (acceptReason.value.length > 200) { | ||
| 815 | - uni.showToast({title: '验收原因最多200字', icon: 'none', duration: 1000}); | ||
| 816 | - return; | ||
| 817 | - } | 777 | + const reason = acceptReason.value.trim(); |
| 778 | + if (!reason) return showToast('请填写验收原因'); | ||
| 779 | + if (reason.length > 200) return showToast('验收原因最多200字'); | ||
| 780 | + | ||
| 781 | + const item = currentAcceptItem.value; | ||
| 782 | + if (!item?.id) return showToast('工单信息异常'); | ||
| 783 | + | ||
| 784 | + uni.showLoading({title: '提交中...', mask: true}); | ||
| 818 | try { | 785 | try { |
| 819 | - // 3. 调用验收接口 | ||
| 820 | - console.log(currentAcceptItem.value) | ||
| 821 | let postData: any = {} | 786 | let postData: any = {} |
| 822 | - if (currentAcceptItem.value?.taskKey == 'ylTeamLeaderConfirm') { // 养护组长验收 | 787 | + if (item?.taskKey == 'ylTeamLeaderConfirm') { |
| 823 | postData = { | 788 | postData = { |
| 824 | - "returnImgs": acceptImgs.getSuccessImgUrls(), // 验收图片URL数组 | ||
| 825 | - "taskKey": currentAcceptItem.value.taskKey, // ylTeamLeaderConfirm | ||
| 826 | - "workerDataId": currentAcceptItem.value.id, | ||
| 827 | - "taskId": currentAcceptItem.value.taskId, | ||
| 828 | - "operateType": acceptRadioValue.value == 0 ? nextStepMap[currentAcceptItem.value.taskKey].operateTypePass : nextStepMap[currentAcceptItem.value.taskKey].operateTypeNoPass, | ||
| 829 | - "reason": acceptReason.value.trim() | 789 | + returnImgs: acceptImgs.getSuccessImgUrls(), |
| 790 | + taskKey: item.taskKey, | ||
| 791 | + workerDataId: item.id, | ||
| 792 | + taskId: item.taskId, | ||
| 793 | + operateType: acceptRadioValue.value == 0 ? nextStepMap[item.taskKey].operateTypePass : nextStepMap[item.taskKey].operateTypeNoPass, | ||
| 794 | + reason | ||
| 830 | } | 795 | } |
| 831 | } | 796 | } |
| 832 | - if (currentAcceptItem.value?.taskKey == 'ylInspector') { // 巡查员验收 | 797 | + if (item?.taskKey == 'ylInspector') { |
| 833 | postData = { | 798 | postData = { |
| 834 | - "returnImgs": acceptImgs.getSuccessImgUrls(), // 验收图片URL数组 | ||
| 835 | - "taskKey": currentAcceptItem.value.taskKey, //ylInspector | ||
| 836 | - "taskId": currentAcceptItem.value.taskId, | ||
| 837 | - "workerDataId": currentAcceptItem.value.id, | ||
| 838 | - "operateType": acceptRadioValue.value == 0 ? nextStepMap[currentAcceptItem.value.taskKey].operateTypePass : nextStepMap[currentAcceptItem.value.taskKey].operateTypeNoPass, | ||
| 839 | - "reason": acceptReason.value.trim(), | ||
| 840 | - "agree": acceptRadioValue.value | 799 | + returnImgs: acceptImgs.getSuccessImgUrls(), |
| 800 | + taskKey: item.taskKey, | ||
| 801 | + taskId: item.taskId, | ||
| 802 | + workerDataId: item.id, | ||
| 803 | + operateType: acceptRadioValue.value == 0 ? nextStepMap[item.taskKey].operateTypePass : nextStepMap[item.taskKey].operateTypeNoPass, | ||
| 804 | + reason, | ||
| 805 | + agree: acceptRadioValue.value | ||
| 841 | } | 806 | } |
| 842 | } | 807 | } |
| 843 | - const acceptRes = await daquUniversalApproval(postData); | ||
| 844 | - // 4. 操作成功处理 | ||
| 845 | - | ||
| 846 | - handleAcceptModalCancel(); // 清空状态 | ||
| 847 | - // 重新获取工单详情,刷新页面 | ||
| 848 | - // await DetailQuery(taskId.value); | ||
| 849 | - // uni.reLaunch({ | ||
| 850 | - // url: `/pages-sub/problem/work-order-manage/index` | ||
| 851 | - // }); | ||
| 852 | - eventChannel.emit('needRefresh'); | ||
| 853 | - // 4. 返回列表页 | 808 | + await callApprovalApi(postData, item.taskKey); |
| 809 | + showToast('提交成功', 'success'); | ||
| 810 | + handleAcceptModalCancel(); | ||
| 811 | + eventChannel.vlue.emit('needRefresh'); | ||
| 854 | uni.navigateBack({delta: 1}); | 812 | uni.navigateBack({delta: 1}); |
| 855 | - uni.showToast({title: '提交成功', icon: 'success', duration: 1000}); | ||
| 856 | - } catch (error) { | ||
| 857 | - // 5. 操作失败处理 | 813 | + } catch (error: any) { |
| 858 | console.error('验收失败:', error); | 814 | console.error('验收失败:', error); |
| 859 | - uni.showToast({title: '验收提交失败,请重试', icon: 'none', duration: 1000}); | 815 | + showToast(error.msg || error.message || '验收提交失败,请重试'); |
| 816 | + } finally { | ||
| 817 | + uni.hideLoading(); | ||
| 860 | } | 818 | } |
| 861 | }; | 819 | }; |
| 862 | 820 | ||
| 821 | +// ========== 生命周期 ========== | ||
| 863 | onLoad((options: any) => { | 822 | onLoad((options: any) => { |
| 864 | console.log('页面入参:', options) | 823 | console.log('页面入参:', options) |
| 865 | const {taskId: taskIdOpt, activeTab: activeTabOpt, processInstanceId: processInstanceIdOpt} = options; | 824 | const {taskId: taskIdOpt, activeTab: activeTabOpt, processInstanceId: processInstanceIdOpt} = options; |
| 866 | - // 0-待办 1-我发起的 2-已办 | ||
| 867 | taskId.value = taskIdOpt || ''; | 825 | taskId.value = taskIdOpt || ''; |
| 868 | activeTab.value = activeTabOpt || '0'; | 826 | activeTab.value = activeTabOpt || '0'; |
| 869 | processInstanceId.value = processInstanceIdOpt; | 827 | processInstanceId.value = processInstanceIdOpt; |
| 828 | + eventChannel.value = getCurrentPages()[getCurrentPages().length - 1].getOpenerEventChannel(); | ||
| 870 | DetailQuery(taskId.value); | 829 | DetailQuery(taskId.value); |
| 871 | }); | 830 | }); |
| 872 | 831 | ||
| 873 | -onShow(() => { | ||
| 874 | - // 注释原有逻辑,避免重复加载 | ||
| 875 | -}); | 832 | +onShow(() => {}); |
| 876 | </script> | 833 | </script> |
| 877 | 834 | ||
| 878 | <style scoped lang="scss"> | 835 | <style scoped lang="scss"> |
| @@ -888,12 +845,10 @@ onShow(() => { | @@ -888,12 +845,10 @@ onShow(() => { | ||
| 888 | 845 | ||
| 889 | // 顶部Tabs | 846 | // 顶部Tabs |
| 890 | .top-tabs { | 847 | .top-tabs { |
| 891 | - //background-color: #fff; | ||
| 892 | } | 848 | } |
| 893 | 849 | ||
| 894 | // Tab内容区 | 850 | // Tab内容区 |
| 895 | .tab-content { | 851 | .tab-content { |
| 896 | - //padding: 16rpx; | ||
| 897 | } | 852 | } |
| 898 | 853 | ||
| 899 | // 工单详情内容 | 854 | // 工单详情内容 |
| @@ -904,7 +859,6 @@ onShow(() => { | @@ -904,7 +859,6 @@ onShow(() => { | ||
| 904 | } | 859 | } |
| 905 | 860 | ||
| 906 | .cell-content-wrap { | 861 | .cell-content-wrap { |
| 907 | - // 保持原有样式,兼容up-album布局 | ||
| 908 | } | 862 | } |
| 909 | 863 | ||
| 910 | .empty-text { | 864 | .empty-text { |
| @@ -916,21 +870,12 @@ onShow(() => { | @@ -916,21 +870,12 @@ onShow(() => { | ||
| 916 | // 图片分类Tabs区块 | 870 | // 图片分类Tabs区块 |
| 917 | .img-tabs-block { | 871 | .img-tabs-block { |
| 918 | background-color: #fff; | 872 | background-color: #fff; |
| 919 | - //border-radius: 12rpx; | ||
| 920 | - //padding: 16rpx; | ||
| 921 | 873 | ||
| 922 | - // 图片内容区 | ||
| 923 | .img-tab-content { | 874 | .img-tab-content { |
| 924 | padding: 20rpx 15px ; | 875 | padding: 20rpx 15px ; |
| 925 | text-align: center; | 876 | text-align: center; |
| 926 | - //min-height: 120rpx; | ||
| 927 | - //display: flex; | ||
| 928 | - //align-items: center; | ||
| 929 | - //justify-content: center; | ||
| 930 | - // | ||
| 931 | - .img-album { | ||
| 932 | - //width: 100%; | ||
| 933 | 877 | ||
| 878 | + .img-album { | ||
| 934 | } | 879 | } |
| 935 | 880 | ||
| 936 | .empty-img-text { | 881 | .empty-img-text { |
| @@ -942,33 +887,25 @@ onShow(() => { | @@ -942,33 +887,25 @@ onShow(() => { | ||
| 942 | } | 887 | } |
| 943 | } | 888 | } |
| 944 | 889 | ||
| 945 | -// 流程节点区域(完整样式,确保内容可见) | 890 | +// 流程节点区域 |
| 946 | .process-content { | 891 | .process-content { |
| 947 | - //padding: 16rpx; | ||
| 948 | - //min-height: 400rpx; | ||
| 949 | - | ||
| 950 | .empty-process { | 892 | .empty-process { |
| 951 | margin-top: 100rpx; | 893 | margin-top: 100rpx; |
| 952 | } | 894 | } |
| 953 | 895 | ||
| 954 | - // 竖向步骤条容器样式 | ||
| 955 | .vertical-steps { | 896 | .vertical-steps { |
| 956 | - //width: 100%; | ||
| 957 | background-color: #fff; | 897 | background-color: #fff; |
| 958 | padding: 20rpx; | 898 | padding: 20rpx; |
| 959 | border-radius: 12rpx; | 899 | border-radius: 12rpx; |
| 960 | display: flex; | 900 | display: flex; |
| 961 | flex-direction: column; | 901 | flex-direction: column; |
| 962 | - gap: 20rpx; // 步骤项间距 | 902 | + gap: 20rpx; |
| 963 | } | 903 | } |
| 964 | 904 | ||
| 965 | - // 自定义内容容器样式 | ||
| 966 | .step-content-wrap { | 905 | .step-content-wrap { |
| 967 | width: 100%; | 906 | width: 100%; |
| 968 | - //padding: 10rpx 0; | ||
| 969 | box-sizing: border-box; | 907 | box-sizing: border-box; |
| 970 | 908 | ||
| 971 | - // 节点标题 + 操作人样式 | ||
| 972 | .step-title { | 909 | .step-title { |
| 973 | font-size: 30rpx; | 910 | font-size: 30rpx; |
| 974 | font-weight: 600; | 911 | font-weight: 600; |
| @@ -983,7 +920,6 @@ onShow(() => { | @@ -983,7 +920,6 @@ onShow(() => { | ||
| 983 | } | 920 | } |
| 984 | } | 921 | } |
| 985 | 922 | ||
| 986 | - // 描述(时间 + 处理说明)样式 | ||
| 987 | .step-desc { | 923 | .step-desc { |
| 988 | font-size: 24rpx; | 924 | font-size: 24rpx; |
| 989 | color: #666; | 925 | color: #666; |
| @@ -998,16 +934,14 @@ onShow(() => { | @@ -998,16 +934,14 @@ onShow(() => { | ||
| 998 | } | 934 | } |
| 999 | 935 | ||
| 1000 | .reason-line { | 936 | .reason-line { |
| 1001 | - word-break: break-all; // 长文本自动换行 | 937 | + word-break: break-all; |
| 1002 | } | 938 | } |
| 1003 | } | 939 | } |
| 1004 | 940 | ||
| 1005 | - // 相册容器样式 | ||
| 1006 | .step-album-wrap { | 941 | .step-album-wrap { |
| 1007 | padding: 10rpx 0; | 942 | padding: 10rpx 0; |
| 1008 | 943 | ||
| 1009 | .step-album { | 944 | .step-album { |
| 1010 | - //width: 100%; | ||
| 1011 | } | 945 | } |
| 1012 | 946 | ||
| 1013 | .no-img-tip { | 947 | .no-img-tip { |
| @@ -1067,11 +1001,9 @@ onShow(() => { | @@ -1067,11 +1001,9 @@ onShow(() => { | ||
| 1067 | .mt-30 { | 1001 | .mt-30 { |
| 1068 | margin-top: 30rpx; | 1002 | margin-top: 30rpx; |
| 1069 | } | 1003 | } |
| 1070 | -// 针对 up-album 单图容器的样式(穿透 scoped 限制) | 1004 | +// 针对 up-album 单图容器的样式 |
| 1071 | :deep .u-album__row__wrapper image { | 1005 | :deep .u-album__row__wrapper image { |
| 1072 | - width: 70px !important; // 与多图保持一致 | 1006 | + width: 70px !important; |
| 1073 | height: 70px !important; | 1007 | height: 70px !important; |
| 1074 | } | 1008 | } |
| 1075 | - | ||
| 1076 | - | ||
| 1077 | </style> | 1009 | </style> |
| 1078 | \ No newline at end of file | 1010 | \ No newline at end of file |
pages.json
| @@ -17,14 +17,16 @@ | @@ -17,14 +17,16 @@ | ||
| 17 | { | 17 | { |
| 18 | "path": "pages/index/index", | 18 | "path": "pages/index/index", |
| 19 | "style": { | 19 | "style": { |
| 20 | - "navigationBarTitleText": "首页" | 20 | + "navigationBarTitleText": "首页", |
| 21 | + "navigationStyle": "custom" | ||
| 21 | } | 22 | } |
| 22 | }, | 23 | }, |
| 23 | 24 | ||
| 24 | { | 25 | { |
| 25 | "path": "pages/mine/index", | 26 | "path": "pages/mine/index", |
| 26 | "style": { | 27 | "style": { |
| 27 | - "navigationBarTitleText": "我的" | 28 | + "navigationBarTitleText": "我的", |
| 29 | + "navigationStyle": "custom" | ||
| 28 | } | 30 | } |
| 29 | } | 31 | } |
| 30 | ], | 32 | ], |
| @@ -209,10 +211,16 @@ | @@ -209,10 +211,16 @@ | ||
| 209 | }, | 211 | }, |
| 210 | { | 212 | { |
| 211 | "path": "tree-archive/editTree", | 213 | "path": "tree-archive/editTree", |
| 212 | - "style": { "navigationBarTitleText": "编辑行道树" } | 214 | + "style": { "navigationBarTitleText": "树木信息" } |
| 215 | + }, | ||
| 216 | + { | ||
| 217 | + "path": "tree-archive/logDetail", | ||
| 218 | + "style": { "navigationBarTitleText": "树木详情" } | ||
| 213 | } | 219 | } |
| 214 | 220 | ||
| 215 | 221 | ||
| 222 | + | ||
| 223 | + | ||
| 216 | ] | 224 | ] |
| 217 | } | 225 | } |
| 218 | ], | 226 | ], |
pages/index/index.vue
| 1 | <template> | 1 | <template> |
| 2 | - <view class="content"> | ||
| 3 | -<!-- <image class="logo" src="/static/logo.png"></image>--> | ||
| 4 | - <view class="text-area"> | ||
| 5 | - <text class="title">{{title}}</text> | ||
| 6 | - </view> | ||
| 7 | - </view> | 2 | + <view class="home-page"> |
| 3 | + <!-- 用户信息栏 --> | ||
| 4 | + <view class="user-info-bar"> | ||
| 5 | + <view class="user-info"> | ||
| 6 | + <image class="avatar" src="../../static/imgs/default-avatar.png" mode="widthFix"></image> | ||
| 7 | + <view class="user-text"> | ||
| 8 | + <view class="username">林晓明</view> | ||
| 9 | + <view class="login-time">上次登录1天前</view> | ||
| 10 | + </view> | ||
| 11 | + </view> | ||
| 12 | + <view class="msg-icon"> | ||
| 13 | + <u-icon name="message" color="#fff" size="32rpx" /> | ||
| 14 | + <view class="msg-badge">5</view> | ||
| 15 | + </view> | ||
| 16 | + </view> | ||
| 17 | + | ||
| 18 | + <!-- 任务完成情况卡片 --> | ||
| 19 | + <view class="task-card"> | ||
| 20 | + <view class="card-title">任务完成情况</view> | ||
| 21 | + <view class="card-header"> | ||
| 22 | + <view class="unit">单位: 个</view> | ||
| 23 | + <!-- ✅ 核心修改:日期范围改为可点击的选择器 --> | ||
| 24 | + <view class="date-range" @click="openDatePicker"> | ||
| 25 | +<!-- {{ dateText }}--> | ||
| 26 | + <neo-datetime-pro | ||
| 27 | + v-model="dateRange" | ||
| 28 | + type="daterange" | ||
| 29 | + placeholder="请选择日期范围" | ||
| 30 | + /> | ||
| 31 | + </view> | ||
| 32 | + </view> | ||
| 33 | + <!-- 折线图 --> | ||
| 34 | + <view class="chart-wrap"> | ||
| 35 | + <!-- <u-line-chart--> | ||
| 36 | + <!-- :chart-data="chartData"--> | ||
| 37 | + <!-- :x-axis="xAxis"--> | ||
| 38 | + <!-- :y-axis="yAxis"--> | ||
| 39 | + <!-- :legend="legend"--> | ||
| 40 | + <!-- height="300rpx"--> | ||
| 41 | + <!-- ></u-line-chart>--> | ||
| 42 | + </view> | ||
| 43 | + </view> | ||
| 44 | + | ||
| 45 | + <!-- 待办/已办切换栏 --> | ||
| 46 | + <view class="tab-bar"> | ||
| 47 | + <view | ||
| 48 | + class="tab-item" | ||
| 49 | + :class="{ active: currentTab === 'todo' }" | ||
| 50 | + @click="currentTab = 'todo'" | ||
| 51 | + > | ||
| 52 | + 待办事项(5) | ||
| 53 | + <view class="tab-active-line" v-if="currentTab === 'todo'"></view> | ||
| 54 | + </view> | ||
| 55 | + <view | ||
| 56 | + class="tab-item" | ||
| 57 | + :class="{ active: currentTab === 'done' }" | ||
| 58 | + @click="currentTab = 'done'" | ||
| 59 | + > | ||
| 60 | + 已办事项(89) | ||
| 61 | + <view class="tab-active-line" v-if="currentTab === 'done'"></view> | ||
| 62 | + </view> | ||
| 63 | + </view> | ||
| 64 | + | ||
| 65 | + <!-- 事项列表 --> | ||
| 66 | + <view class="task-list"> | ||
| 67 | + <view class="task-item" v-for="(item, index) in currentTaskList" :key="index"> | ||
| 68 | + <view class="task-name">事项名称: {{ item.name }}{{ item.name }}{{ item.name }}{{ item.name }}{{ item.name }}{{ item.name }}</view> | ||
| 69 | + <view class="task-desc"> | ||
| 70 | + <view>紧急程度: {{ item.urgency }}</view> | ||
| 71 | + <view class="task-time">{{ item.time }}</view> | ||
| 72 | + </view> | ||
| 73 | + </view> | ||
| 74 | + </view> | ||
| 75 | + | ||
| 76 | + | ||
| 77 | + </view> | ||
| 8 | </template> | 78 | </template> |
| 9 | 79 | ||
| 10 | -<script> | ||
| 11 | - export default { | ||
| 12 | - data() { | ||
| 13 | - return { | ||
| 14 | - title: 'Hello' | ||
| 15 | - } | ||
| 16 | - }, | ||
| 17 | - onLoad() { | 80 | +<script setup> |
| 81 | +import { ref, reactive, watch } from 'vue'; | ||
| 82 | + | ||
| 83 | +// 折线图数据 | ||
| 84 | +const chartData = reactive([ | ||
| 85 | + { | ||
| 86 | + name: '已完成任务数', | ||
| 87 | + data: [5, 35, 55, 40, 65, 50, 70], | ||
| 88 | + color: '#4CAF50' | ||
| 89 | + }, | ||
| 90 | + { | ||
| 91 | + name: '待完成总任务数', | ||
| 92 | + data: [5, 70, 75, 70, 75, 75, 90], | ||
| 93 | + color: '#E53935' | ||
| 94 | + } | ||
| 95 | +]); | ||
| 96 | +const xAxis = reactive(['12.28', '12.29', '12.30', '12.31', '01.01', '01.02', '01.03']); | ||
| 97 | +const yAxis = reactive([0, 25, 50, 75, 100]); | ||
| 98 | +const legend = reactive(['已完成任务数', '待完成总任务数']); | ||
| 99 | + | ||
| 100 | +// 标签切换 | ||
| 101 | +const currentTab = ref('todo'); | ||
| 102 | +const dateRange=ref( []) | ||
| 103 | +// ✅ 核心新增:时间选择器相关数据 | ||
| 104 | +const showDatePicker = ref(false); | ||
| 105 | +// 默认显示的时间文本 | ||
| 106 | +const dateText = ref('2025/12/28—2026/01/04'); | ||
| 107 | +// 选中的开始/结束时间 | ||
| 108 | +const selectStartDate = ref(''); | ||
| 109 | +const selectEndDate = ref(''); | ||
| 110 | + | ||
| 111 | +// 打开时间选择器 | ||
| 112 | +const openDatePicker = () => { | ||
| 113 | + showDatePicker.value = true; | ||
| 114 | +}; | ||
| 115 | + | ||
| 116 | +// 确认选择时间 | ||
| 117 | +const confirmDate = (e) => { | ||
| 118 | + // 格式化选中的时间 | ||
| 119 | + selectStartDate.value = formatDate(e.value[0]); | ||
| 120 | + selectEndDate.value = formatDate(e.value[1]); | ||
| 121 | + dateText.value = `${selectStartDate.value}—${selectEndDate.value}`; | ||
| 122 | + showDatePicker.value = false; | ||
| 123 | + // 这里可以加:时间改变后重新请求图表数据的逻辑 | ||
| 124 | + // getChartData(selectStartDate.value, selectEndDate.value) | ||
| 125 | +}; | ||
| 18 | 126 | ||
| 19 | - }, | ||
| 20 | - methods: { | 127 | +// 时间格式化函数:YYYY/MM/DD 格式 |
| 128 | +const formatDate = (dateVal) => { | ||
| 129 | + const year = dateVal.getFullYear(); | ||
| 130 | + const month = (dateVal.getMonth() + 1).toString().padStart(2, '0'); | ||
| 131 | + const day = dateVal.getDate().toString().padStart(2, '0'); | ||
| 132 | + return `${year}/${month}/${day}`; | ||
| 133 | +}; | ||
| 21 | 134 | ||
| 22 | - } | ||
| 23 | - } | 135 | +// 待办事项数据 |
| 136 | +const todoList = reactive([ | ||
| 137 | + { | ||
| 138 | + name: '绿地卫生验收', | ||
| 139 | + urgency: '特急', | ||
| 140 | + time: '2025-12-31 15:45:23' | ||
| 141 | + }, | ||
| 142 | + { | ||
| 143 | + name: '阜成门内大街年度计划巡检', | ||
| 144 | + urgency: '--', | ||
| 145 | + time: '2025-12-31 09:02:00' | ||
| 146 | + }, | ||
| 147 | + { | ||
| 148 | + name: '金融街年度计划巡检', | ||
| 149 | + urgency: '--', | ||
| 150 | + time: '2025-12-31 09:02:00' | ||
| 151 | + }, | ||
| 152 | + { | ||
| 153 | + name: '金融街年度计划巡检', | ||
| 154 | + urgency: '--', | ||
| 155 | + time: '2025-12-31 09:02:00' | ||
| 156 | + }, | ||
| 157 | + { | ||
| 158 | + name: '示例事项', | ||
| 159 | + urgency: '一般', | ||
| 160 | + time: '2026-01-04 10:00:00' | ||
| 161 | + } | ||
| 162 | +]); | ||
| 163 | + | ||
| 164 | +// 已办事项数据(示例) | ||
| 165 | +const doneList = reactive([ | ||
| 166 | + { | ||
| 167 | + name: '道路清洁验收', | ||
| 168 | + urgency: '普通', | ||
| 169 | + time: '2025-12-30 14:20:00' | ||
| 170 | + } | ||
| 171 | +]); | ||
| 172 | + | ||
| 173 | +// 当前显示的事项列表 | ||
| 174 | +const currentTaskList = ref(todoList); | ||
| 175 | +// 监听标签切换 | ||
| 176 | +watch(currentTab, (newVal) => { | ||
| 177 | + currentTaskList.value = newVal === 'todo' ? todoList : doneList; | ||
| 178 | +}); | ||
| 24 | </script> | 179 | </script> |
| 25 | 180 | ||
| 26 | -<style> | ||
| 27 | - .content { | ||
| 28 | - display: flex; | ||
| 29 | - flex-direction: column; | ||
| 30 | - align-items: center; | ||
| 31 | - justify-content: center; | ||
| 32 | - } | ||
| 33 | - | ||
| 34 | - .logo { | ||
| 35 | - height: 200rpx; | ||
| 36 | - width: 200rpx; | ||
| 37 | - margin-top: 200rpx; | ||
| 38 | - margin-left: auto; | ||
| 39 | - margin-right: auto; | ||
| 40 | - margin-bottom: 50rpx; | ||
| 41 | - } | ||
| 42 | - | ||
| 43 | - .text-area { | ||
| 44 | - display: flex; | ||
| 45 | - justify-content: center; | ||
| 46 | - } | ||
| 47 | - | ||
| 48 | - .title { | ||
| 49 | - font-size: 36rpx; | ||
| 50 | - color: #8f8f94; | ||
| 51 | - } | ||
| 52 | -</style> | 181 | +<style scoped lang="scss"> |
| 182 | +.home-page { | ||
| 183 | + background-color: #f5f7fa; | ||
| 184 | + min-height: 100vh; | ||
| 185 | +} | ||
| 186 | + | ||
| 187 | +/* 用户信息栏 */ | ||
| 188 | +.user-info-bar { | ||
| 189 | + background-color: #1677ff; | ||
| 190 | + padding: 180rpx 30rpx 120rpx; | ||
| 191 | + display: flex; | ||
| 192 | + justify-content: space-between; | ||
| 193 | + align-items: center; | ||
| 194 | + color: #fff; | ||
| 195 | + z-index: 1; | ||
| 196 | + position: relative; | ||
| 197 | + | ||
| 198 | + .user-info { | ||
| 199 | + display: flex; | ||
| 200 | + align-items: center; | ||
| 201 | + | ||
| 202 | + .avatar { | ||
| 203 | + width: 80rpx; | ||
| 204 | + height: 80rpx; | ||
| 205 | + border-radius: 50%; | ||
| 206 | + margin-right: 20rpx; | ||
| 207 | + } | ||
| 208 | + | ||
| 209 | + .username { | ||
| 210 | + font-size: 32rpx; | ||
| 211 | + font-weight: 500; | ||
| 212 | + } | ||
| 213 | + | ||
| 214 | + .login-time { | ||
| 215 | + font-size: 24rpx; | ||
| 216 | + opacity: 0.8; | ||
| 217 | + } | ||
| 218 | + } | ||
| 219 | + | ||
| 220 | + .msg-icon { | ||
| 221 | + position: relative; | ||
| 222 | + | ||
| 223 | + .msg-badge { | ||
| 224 | + position: absolute; | ||
| 225 | + top: -10rpx; | ||
| 226 | + right: -10rpx; | ||
| 227 | + background-color: #ff3d00; | ||
| 228 | + color: #fff; | ||
| 229 | + font-size: 20rpx; | ||
| 230 | + width: 28rpx; | ||
| 231 | + height: 28rpx; | ||
| 232 | + border-radius: 50%; | ||
| 233 | + display: flex; | ||
| 234 | + align-items: center; | ||
| 235 | + justify-content: center; | ||
| 236 | + } | ||
| 237 | + } | ||
| 238 | +} | ||
| 239 | + | ||
| 240 | +/* 任务完成情况卡片 */ | ||
| 241 | +.task-card { | ||
| 242 | + background-color: #fff; | ||
| 243 | + margin: 0 30rpx; | ||
| 244 | + margin-top: -60rpx; | ||
| 245 | + border-radius: 16rpx 16rpx 0 0; | ||
| 246 | + padding: 20rpx; | ||
| 247 | + box-shadow: 0 2rpx 10rpx rgba(0,0,0,0.05); | ||
| 248 | + position: relative; | ||
| 249 | + z-index: 2; | ||
| 250 | + | ||
| 251 | + .card-title { | ||
| 252 | + font-size: 30rpx; | ||
| 253 | + font-weight: 600; | ||
| 254 | + margin-bottom: 15rpx; | ||
| 255 | + } | ||
| 256 | + | ||
| 257 | + .card-header { | ||
| 258 | + display: flex; | ||
| 259 | + justify-content: space-between; | ||
| 260 | + font-size: 24rpx; | ||
| 261 | + color: #999; | ||
| 262 | + margin-bottom: 10rpx; | ||
| 263 | + align-items: center; | ||
| 264 | + } | ||
| 265 | + | ||
| 266 | + // ✅ 日期选择器样式 | ||
| 267 | + .date-range { | ||
| 268 | + display: flex; | ||
| 269 | + align-items: center; | ||
| 270 | + gap: 6rpx; | ||
| 271 | + padding: 4rpx 8rpx; | ||
| 272 | + border-radius: 6rpx; | ||
| 273 | + &:active { | ||
| 274 | + background-color: #f5f5f5; | ||
| 275 | + } | ||
| 276 | + } | ||
| 277 | + .date-icon { | ||
| 278 | + margin-top: 2rpx; | ||
| 279 | + } | ||
| 280 | + | ||
| 281 | + .chart-wrap { | ||
| 282 | + width: 100%; | ||
| 283 | + height: 300rpx; | ||
| 284 | + } | ||
| 285 | +} | ||
| 286 | + | ||
| 287 | +/* 待办/已办切换栏 */ | ||
| 288 | +.tab-bar { | ||
| 289 | + display: flex; | ||
| 290 | + background-color: #fff; | ||
| 291 | + margin: 0 30rpx; | ||
| 292 | + border-radius: 0; | ||
| 293 | + overflow: hidden; | ||
| 294 | + | ||
| 295 | + .tab-item { | ||
| 296 | + flex: 1; | ||
| 297 | + text-align: center; | ||
| 298 | + padding: 20rpx 0; | ||
| 299 | + font-size: 28rpx; | ||
| 300 | + color: #666; | ||
| 301 | + position: relative; | ||
| 302 | + | ||
| 303 | + &.active { | ||
| 304 | + color: #1677ff; | ||
| 305 | + font-weight: 500; | ||
| 306 | + } | ||
| 307 | + | ||
| 308 | + .tab-active-line { | ||
| 309 | + position: absolute; | ||
| 310 | + bottom: 0; | ||
| 311 | + left: 0; | ||
| 312 | + width: 100%; | ||
| 313 | + height: 4rpx; | ||
| 314 | + background-color: #1677ff; | ||
| 315 | + } | ||
| 316 | + } | ||
| 317 | +} | ||
| 318 | + | ||
| 319 | +/* 事项列表 */ | ||
| 320 | +.task-list { | ||
| 321 | + background-color: #fff; | ||
| 322 | + margin: 0 30rpx ; | ||
| 323 | + border-radius: 0 0 16rpx 16rpx; | ||
| 324 | + padding: 10rpx 20rpx; | ||
| 325 | + box-shadow: 0 2rpx 10rpx rgba(0,0,0,0.05); | ||
| 326 | + | ||
| 327 | + .task-item { | ||
| 328 | + padding: 20rpx 0; | ||
| 329 | + border-bottom: 1px solid #f5f5f5; | ||
| 330 | + | ||
| 331 | + &:last-child { | ||
| 332 | + border-bottom: none; | ||
| 333 | + } | ||
| 334 | + | ||
| 335 | + .task-name { | ||
| 336 | + font-size: 28rpx; | ||
| 337 | + color: #333; | ||
| 338 | + margin-bottom: 10rpx; | ||
| 339 | + // ✅ 修复超长文字溢出:单行显示+超出省略 | ||
| 340 | + white-space: nowrap; | ||
| 341 | + overflow: hidden; | ||
| 342 | + text-overflow: ellipsis; | ||
| 343 | + } | ||
| 344 | + | ||
| 345 | + .task-desc { | ||
| 346 | + display: flex; | ||
| 347 | + justify-content: space-between; | ||
| 348 | + font-size: 24rpx; | ||
| 349 | + color: #666; | ||
| 350 | + | ||
| 351 | + .task-time { | ||
| 352 | + color: #999; | ||
| 353 | + } | ||
| 354 | + } | ||
| 355 | + } | ||
| 356 | +} | ||
| 357 | +</style> | ||
| 53 | \ No newline at end of file | 358 | \ No newline at end of file |
pages/login/index.vue
| @@ -103,7 +103,7 @@ const loginFormRules = reactive({ | @@ -103,7 +103,7 @@ const loginFormRules = reactive({ | ||
| 103 | // 密码校验:必填 + 长度限制 6-20位(行业通用密码长度,可自行调整) | 103 | // 密码校验:必填 + 长度限制 6-20位(行业通用密码长度,可自行调整) |
| 104 | password: [ | 104 | password: [ |
| 105 | { type: 'string', required: true, message: '请输入登录密码', trigger: ['change', 'blur'] }, | 105 | { type: 'string', required: true, message: '请输入登录密码', trigger: ['change', 'blur'] }, |
| 106 | - { type: 'string', min: 6, max: 20, message: '密码长度为6-20个字符', trigger: ['change', 'blur'] } | 106 | + { type: 'string', min: 3, max: 20, message: '密码长度为3-20个字符', trigger: ['change', 'blur'] } |
| 107 | ] | 107 | ] |
| 108 | }); | 108 | }); |
| 109 | 109 | ||
| @@ -189,17 +189,18 @@ const handleLogin = async () => { | @@ -189,17 +189,18 @@ const handleLogin = async () => { | ||
| 189 | 189 | ||
| 190 | // 顶部:从左到右 由浅到深 线性渐变 过渡自然柔和 无突兀色块 | 190 | // 顶部:从左到右 由浅到深 线性渐变 过渡自然柔和 无突兀色块 |
| 191 | .top-title { | 191 | .top-title { |
| 192 | - background: linear-gradient(120deg, #4299e1 0%, #2970e8 50%, #2563eb 100%); | 192 | + //background: linear-gradient(120deg, #4299e1 0%, #2970e8 50%, #2563eb 100%); |
| 193 | color: #fff; | 193 | color: #fff; |
| 194 | - padding: 240rpx 30rpx 80rpx; | 194 | + padding: 220rpx 30rpx 200rpx; |
| 195 | text-align: left; | 195 | text-align: left; |
| 196 | - border-bottom-left-radius: 120rpx; | 196 | + //border-bottom-left-radius: 120rpx; |
| 197 | position: relative; | 197 | position: relative; |
| 198 | z-index: 1; | 198 | z-index: 1; |
| 199 | - box-shadow: 0 25rpx 40rpx -15rpx rgba(37, 99, 235, 0.3); | ||
| 200 | - | 199 | + //box-shadow: 0 25rpx 40rpx -15rpx rgba(37, 99, 235, 0.3); |
| 200 | + background: url("https://img.jichengshanshui.com.cn:28207/appimg/bg.jpg") no-repeat; | ||
| 201 | + background-size: 100% 100%; | ||
| 201 | .welcome-text { | 202 | .welcome-text { |
| 202 | - font-size: 46rpx; | 203 | + font-size: 23px; |
| 203 | display: block; | 204 | display: block; |
| 204 | margin-bottom: 10px; | 205 | margin-bottom: 10px; |
| 205 | font-weight: 500; | 206 | font-weight: 500; |
| @@ -207,7 +208,7 @@ const handleLogin = async () => { | @@ -207,7 +208,7 @@ const handleLogin = async () => { | ||
| 207 | 208 | ||
| 208 | .platform-name { | 209 | .platform-name { |
| 209 | margin-bottom: 10px; | 210 | margin-bottom: 10px; |
| 210 | - font-size: 46rpx; | 211 | + font-size: 23px; |
| 211 | display: block; | 212 | display: block; |
| 212 | opacity: 0.95; | 213 | opacity: 0.95; |
| 213 | } | 214 | } |
| @@ -215,46 +216,48 @@ const handleLogin = async () => { | @@ -215,46 +216,48 @@ const handleLogin = async () => { | ||
| 215 | 216 | ||
| 216 | .login-form { | 217 | .login-form { |
| 217 | background-color: #fff; | 218 | background-color: #fff; |
| 218 | - padding: 40rpx 30rpx; | ||
| 219 | - border-radius: 16rpx; | ||
| 220 | - box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.06); | ||
| 221 | - margin: -60rpx 40rpx 0; | 219 | + padding: 20px 15px; |
| 220 | + border-radius: 8px; | ||
| 221 | + box-shadow: 0 2px 10px rgba(0, 0, 0, 0.06); | ||
| 222 | + margin: -90px 20px 0; | ||
| 222 | position: relative; | 223 | position: relative; |
| 223 | z-index: 10; | 224 | z-index: 10; |
| 224 | box-sizing: border-box; | 225 | box-sizing: border-box; |
| 225 | 226 | ||
| 226 | .login-title { | 227 | .login-title { |
| 227 | - font-size: 38rpx; | 228 | + font-size: 19px; |
| 228 | font-weight: 600; | 229 | font-weight: 600; |
| 229 | color: #666; | 230 | color: #666; |
| 230 | - margin-bottom: 30rpx; | ||
| 231 | - letter-spacing: 1rpx; | 231 | + margin-bottom: 15px; |
| 232 | + letter-spacing: 1px; | ||
| 232 | } | 233 | } |
| 233 | } | 234 | } |
| 234 | 235 | ||
| 235 | // 适配表单校验的间距 | 236 | // 适配表单校验的间距 |
| 236 | :deep(.u-form-item) { | 237 | :deep(.u-form-item) { |
| 237 | - margin-bottom: 20rpx; | 238 | + margin-bottom: 10px; |
| 238 | position: relative; | 239 | position: relative; |
| 239 | } | 240 | } |
| 240 | 241 | ||
| 241 | -// 登录按钮:和顶部完全同款 从左到右 由浅到深 线性渐变 + 保留所有原尺寸 | 242 | +// 登录按钮 |
| 242 | .login-btn { | 243 | .login-btn { |
| 243 | - margin-top: 40rpx; | 244 | + margin-top: 20px; |
| 244 | width: 100%; | 245 | width: 100%; |
| 245 | - height: 88rpx; | ||
| 246 | - line-height: 88rpx; | ||
| 247 | - border-radius: 8rpx; | ||
| 248 | - font-size: 32rpx; | ||
| 249 | - background: linear-gradient(120deg, #4299e1 0%, #2970e8 50%, #2563eb 100%) !important; | ||
| 250 | - border: none !important; | ||
| 251 | - &::after { border: none !important; } | 246 | + height: 44px; |
| 247 | + line-height: 44px; | ||
| 248 | + border-radius: 4px; | ||
| 249 | + font-size: 16px; | ||
| 250 | + | ||
| 251 | + background: #0A86F4; | ||
| 252 | + box-shadow: 0px 4px 6px 1px rgba(25,94,215,0.5); | ||
| 253 | + border-radius: 23px; | ||
| 252 | } | 254 | } |
| 253 | 255 | ||
| 256 | + | ||
| 254 | .copyright { | 257 | .copyright { |
| 255 | width: 100%; | 258 | width: 100%; |
| 256 | text-align: center; | 259 | text-align: center; |
| 257 | - font-size: 24rpx; | 260 | + font-size: 12px; |
| 258 | color: #999; | 261 | color: #999; |
| 259 | position: fixed; | 262 | position: fixed; |
| 260 | bottom: 100px; | 263 | bottom: 100px; |
pages/mine/index.vue
| 1 | <template> | 1 | <template> |
| 2 | <view class="user-center-page"> | 2 | <view class="user-center-page"> |
| 3 | <!-- 顶部用户信息栏 --> | 3 | <!-- 顶部用户信息栏 --> |
| 4 | - <view class="user-info-header"> | ||
| 5 | - <!-- 渐变背景层 --> | ||
| 6 | - <view class="header-bg"></view> | ||
| 7 | - <!-- 用户信息主体 --> | ||
| 8 | - <view class="user-info-wrap"> | ||
| 9 | - <!-- 头像 --> | ||
| 10 | - <up-avatar | ||
| 11 | - :src="userStore.isLogin ? userInfo.avatar : '/static/imgs/default-avatar.png'" | ||
| 12 | - size="100rpx" | ||
| 13 | - shape="circle" | ||
| 14 | - class="user-avatar" | ||
| 15 | - ></up-avatar> | ||
| 16 | - | ||
| 17 | - <!-- 用户名+手机号 --> | ||
| 18 | - <view class="user-info-content"> | ||
| 19 | - <view class="user-name">{{ userStore.isLogin ? userInfo.username : '未登录' }}</view> | ||
| 20 | - <view class="user-phone">{{ userStore.isLogin ? userInfo.nickname : '--------' }}</view> | ||
| 21 | - </view> | ||
| 22 | - | ||
| 23 | - <!-- 登录入口 --> | ||
| 24 | - <up-button | ||
| 25 | - v-if="!userStore.isLogin" | ||
| 26 | - type="primary" | ||
| 27 | - size="mini" | ||
| 28 | - @click="handleLogin" | ||
| 29 | - > | ||
| 30 | - 立即登录 | ||
| 31 | - </up-button> | 4 | + <view class="header-bg"> |
| 5 | + <up-avatar | ||
| 6 | + :src="'https://img.jichengshanshui.com.cn:28207/appimg/default-avatar.png'" | ||
| 7 | + size="100rpx" | ||
| 8 | + shape="square" | ||
| 9 | + class="user-avatar" | ||
| 10 | + ></up-avatar> | ||
| 11 | + | ||
| 12 | + <!-- 用户名+手机号 --> | ||
| 13 | + <view class="user-info-content"> | ||
| 14 | + <view class="user-name">{{ userStore.isLogin ? userInfo.username : '未登录' }}</view> | ||
| 15 | + <view class="user-phone">{{ userStore.isLogin ? userInfo.nickname : '--------' }}</view> | ||
| 32 | </view> | 16 | </view> |
| 17 | + | ||
| 33 | </view> | 18 | </view> |
| 34 | 19 | ||
| 20 | + | ||
| 21 | + <view class="mine-content"> | ||
| 22 | + <up-cell-group title="个人信息"> | ||
| 23 | + <up-cell title="姓名" :value="userInfo.nickname"></up-cell> | ||
| 24 | + <up-cell title="账号" :value="userInfo.username"></up-cell> | ||
| 25 | + <up-cell title="电话" :value="userInfo.username"></up-cell> | ||
| 26 | + <up-cell title="角色" :value="translateRoles(userStore.userInfo.roles)"></up-cell> | ||
| 27 | + </up-cell-group> | ||
| 28 | + </view> | ||
| 29 | + | ||
| 30 | + | ||
| 31 | + <up-button | ||
| 32 | + v-if="!userStore.isLogin" | ||
| 33 | + type="primary" | ||
| 34 | + size="mini" | ||
| 35 | + shape="circle" | ||
| 36 | + @click="handleLogin" | ||
| 37 | + class="login-btn" | ||
| 38 | + > | ||
| 39 | + 立即登录 | ||
| 40 | + </up-button> | ||
| 41 | + | ||
| 35 | <!-- 退出登录按钮 (仅登录状态显示) --> | 42 | <!-- 退出登录按钮 (仅登录状态显示) --> |
| 36 | <view v-if="userStore.isLogin" class="logout-btn-wrap"> | 43 | <view v-if="userStore.isLogin" class="logout-btn-wrap"> |
| 37 | <up-button | 44 | <up-button |
| 38 | type="primary" | 45 | type="primary" |
| 39 | size="large" | 46 | size="large" |
| 47 | + shape="circle" | ||
| 40 | @click="confirmLogout" | 48 | @click="confirmLogout" |
| 49 | + class="login-btn" | ||
| 41 | > | 50 | > |
| 42 | 退出登录 | 51 | 退出登录 |
| 43 | </up-button> | 52 | </up-button> |
| @@ -49,6 +58,7 @@ | @@ -49,6 +58,7 @@ | ||
| 49 | import { computed } from 'vue' | 58 | import { computed } from 'vue' |
| 50 | import { useUserStore } from '@/pinia/user' | 59 | import { useUserStore } from '@/pinia/user' |
| 51 | import { showModal, onShow } from '@dcloudio/uni-app' | 60 | import { showModal, onShow } from '@dcloudio/uni-app' |
| 61 | +import {translateRoles} from '@/common/utils/common' | ||
| 52 | // 初始化Pinia仓库 | 62 | // 初始化Pinia仓库 |
| 53 | const userStore = useUserStore() | 63 | const userStore = useUserStore() |
| 54 | 64 | ||
| @@ -68,9 +78,7 @@ const handleLogin = () => { | @@ -68,9 +78,7 @@ const handleLogin = () => { | ||
| 68 | * 确认退出登录(带二次确认) | 78 | * 确认退出登录(带二次确认) |
| 69 | */ | 79 | */ |
| 70 | const confirmLogout = async () => { | 80 | const confirmLogout = async () => { |
| 71 | - console.log('13213') | ||
| 72 | try { | 81 | try { |
| 73 | - console.log('434') | ||
| 74 | const res = await uni.showModal({ | 82 | const res = await uni.showModal({ |
| 75 | title: '提示', | 83 | title: '提示', |
| 76 | content: '确定要退出登录吗?', | 84 | content: '确定要退出登录吗?', |
| @@ -105,64 +113,64 @@ export default { | @@ -105,64 +113,64 @@ export default { | ||
| 105 | 113 | ||
| 106 | <style lang="scss" scoped> | 114 | <style lang="scss" scoped> |
| 107 | .user-center-page { | 115 | .user-center-page { |
| 108 | - min-height: 100vh; | ||
| 109 | - background-color: #f5f7fa; | 116 | + |
| 110 | } | 117 | } |
| 111 | 118 | ||
| 112 | -// 顶部用户信息栏 | ||
| 113 | -.user-info-header { | ||
| 114 | - position: relative; | ||
| 115 | - padding-bottom: 40rpx; | ||
| 116 | - | ||
| 117 | - .header-bg { | ||
| 118 | - position: absolute; | ||
| 119 | - top: 0; | ||
| 120 | - left: 0; | ||
| 121 | - width: 100%; | ||
| 122 | - height: 320rpx; | ||
| 123 | - background: linear-gradient(135deg, #409eff, #67c23a); | ||
| 124 | - border-radius: 0 0 20rpx 20rpx; | ||
| 125 | - z-index: 1; | ||
| 126 | - } | 119 | +.header-bg { |
| 120 | + position:relative; | ||
| 121 | + top: 0; | ||
| 122 | + left: 0; | ||
| 123 | + width: 100%; | ||
| 124 | + height: 230px; | ||
| 125 | + //height: 120px; | ||
| 126 | + background: url("https://img.jichengshanshui.com.cn:28207/appimg/bg.jpg") no-repeat; | ||
| 127 | + background-size: 100% 100%; | ||
| 128 | + z-index: -1; | ||
| 129 | + display: flex; | ||
| 130 | + padding: 80px 0 0 20px; | ||
| 131 | +} | ||
| 132 | +.mine-content{ | ||
| 133 | + margin-top: -150px; | ||
| 134 | + border-radius: 30px 30px 0 0; | ||
| 135 | + background: #fff; | ||
| 136 | +} | ||
| 127 | 137 | ||
| 128 | - .user-info-wrap { | ||
| 129 | - position: relative; | ||
| 130 | - z-index: 2; | ||
| 131 | - display: flex; | ||
| 132 | - align-items: center; | ||
| 133 | - padding: 60rpx 30rpx 0; | ||
| 134 | - | ||
| 135 | - .user-avatar { | ||
| 136 | - border: 4rpx solid #ffffff; | ||
| 137 | - box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.1); | ||
| 138 | - transition: transform 0.2s ease; | ||
| 139 | - &:active { | ||
| 140 | - transform: scale(0.95); | ||
| 141 | - } | ||
| 142 | - } | 138 | +.user-avatar { |
| 143 | 139 | ||
| 144 | - .user-info-content { | ||
| 145 | - margin-left: 30rpx; | ||
| 146 | - flex: 1; | ||
| 147 | - | ||
| 148 | - .user-name { | ||
| 149 | - font-size: 32rpx; | ||
| 150 | - font-weight: 600; | ||
| 151 | - color: #ffffff; | ||
| 152 | - margin-bottom: 10rpx; | ||
| 153 | - text-shadow: 0 1rpx 3rpx rgba(0, 0, 0, 0.1); | ||
| 154 | - } | ||
| 155 | - | ||
| 156 | - .user-phone { | ||
| 157 | - font-size: 26rpx; | ||
| 158 | - color: rgba(255, 255, 255, 0.9); | ||
| 159 | - } | ||
| 160 | - } | ||
| 161 | - } | ||
| 162 | } | 140 | } |
| 163 | 141 | ||
| 142 | +.user-info-content { | ||
| 143 | + margin-left: 30rpx; | ||
| 144 | + flex: 1; | ||
| 145 | + | ||
| 146 | + .user-name { | ||
| 147 | + font-size: 32rpx; | ||
| 148 | + font-weight: 600; | ||
| 149 | + color: #ffffff; | ||
| 150 | + margin-bottom: 10rpx; | ||
| 151 | + text-shadow: 0 1rpx 3rpx rgba(0, 0, 0, 0.1); | ||
| 152 | + } | ||
| 153 | + | ||
| 154 | + .user-phone { | ||
| 155 | + font-size: 26rpx; | ||
| 156 | + color: rgba(255, 255, 255, 0.9); | ||
| 157 | + } | ||
| 158 | +} | ||
| 164 | // 退出登录按钮 | 159 | // 退出登录按钮 |
| 165 | .logout-btn-wrap { | 160 | .logout-btn-wrap { |
| 166 | margin: 200rpx 20rpx 20rpx; | 161 | margin: 200rpx 20rpx 20rpx; |
| 167 | } | 162 | } |
| 163 | +// 登录按钮 | ||
| 164 | +.login-btn { | ||
| 165 | + margin-top: 20px; | ||
| 166 | + width: 100%; | ||
| 167 | + height: 44px; | ||
| 168 | + line-height: 44px; | ||
| 169 | + border-radius: 4px; | ||
| 170 | + font-size: 16px; | ||
| 171 | + | ||
| 172 | + background: #0A86F4; | ||
| 173 | + box-shadow: 0px 4px 6px 1px rgba(25,94,215,0.5); | ||
| 174 | + border-radius: 23px; | ||
| 175 | +} | ||
| 168 | </style> | 176 | </style> |
| 169 | \ No newline at end of file | 177 | \ No newline at end of file |
pages/workbench/index.vue
| @@ -9,7 +9,11 @@ | @@ -9,7 +9,11 @@ | ||
| 9 | ></up-loading-page> | 9 | ></up-loading-page> |
| 10 | 10 | ||
| 11 | <!-- 蓝色装饰块 --> | 11 | <!-- 蓝色装饰块 --> |
| 12 | - <view class="blue-decor-block" v-show="!loading"></view> | 12 | + <view class="blue-decor-block" v-show="!loading"> |
| 13 | + <!-- ✅ 修复1:增加可选链+默认值,防止数据为undefined报错 --> | ||
| 14 | + <text class="welcome-text u-line-1">你好{{userInfo?.user?.nickname || ''}},欢迎登录</text> | ||
| 15 | + <text class="platform-name">全域智能运营管理平台</text> | ||
| 16 | + </view> | ||
| 13 | 17 | ||
| 14 | <!-- 内容容器 --> | 18 | <!-- 内容容器 --> |
| 15 | <view class="content-wrap"> | 19 | <view class="content-wrap"> |
| @@ -59,7 +63,7 @@ | @@ -59,7 +63,7 @@ | ||
| 59 | </template> | 63 | </template> |
| 60 | 64 | ||
| 61 | <script setup lang="ts"> | 65 | <script setup lang="ts"> |
| 62 | -import { ref, computed } from 'vue'; // 新增 computed | 66 | +import { ref, computed } from 'vue'; |
| 63 | import { onShow } from '@dcloudio/uni-app'; | 67 | import { onShow } from '@dcloudio/uni-app'; |
| 64 | import { useUserStore } from '@/pinia/user'; | 68 | import { useUserStore } from '@/pinia/user'; |
| 65 | import globalConfig from '@/common/config/global'; | 69 | import globalConfig from '@/common/config/global'; |
| @@ -83,9 +87,12 @@ interface MenuItem { | @@ -83,9 +87,12 @@ interface MenuItem { | ||
| 83 | children: MenuItem[]; | 87 | children: MenuItem[]; |
| 84 | } | 88 | } |
| 85 | 89 | ||
| 90 | +// ========== 全局变量定义区 ========== | ||
| 86 | const loading = ref(true); | 91 | const loading = ref(true); |
| 87 | const userStore = useUserStore(); | 92 | const userStore = useUserStore(); |
| 88 | const moduleList = ref<MenuItem[]>([]); | 93 | const moduleList = ref<MenuItem[]>([]); |
| 94 | +// ✅ 修复2:声明全局的用户信息变量,模板可访问 + 初始化空对象兜底,杜绝undefined | ||
| 95 | +const userInfo = ref<any>(cache.get(globalConfig.cache.userInfoKey) || userStore.userInfo || {}); | ||
| 89 | 96 | ||
| 90 | // 计算属性:过滤出有子节点的父模块(children 存在且长度 > 0) | 97 | // 计算属性:过滤出有子节点的父模块(children 存在且长度 > 0) |
| 91 | const filteredModuleList = computed(() => { | 98 | const filteredModuleList = computed(() => { |
| @@ -98,36 +105,34 @@ const filteredModuleList = computed(() => { | @@ -98,36 +105,34 @@ const filteredModuleList = computed(() => { | ||
| 98 | onShow(async () => { | 105 | onShow(async () => { |
| 99 | try { | 106 | try { |
| 100 | loading.value = true; | 107 | loading.value = true; |
| 108 | + // ✅ 修复3:重新赋值全局userInfo,保证数据最新 | ||
| 109 | + userInfo.value = cache.get(globalConfig.cache.userInfoKey) || userStore.userInfo || {}; | ||
| 101 | 110 | ||
| 102 | - // ========== 核心新增:登录状态判断 ========== | ||
| 103 | - // 1. 定义登录判断逻辑(多维度校验,确保准确性) | 111 | + // 登录状态判断(多维度校验,确保准确性) |
| 104 | const isLogin = () => { | 112 | const isLogin = () => { |
| 105 | - | ||
| 106 | // 从缓存获取token(核心登录标识) | 113 | // 从缓存获取token(核心登录标识) |
| 107 | const token = cache.get(globalConfig.cache.tokenKey) || userStore.token; | 114 | const token = cache.get(globalConfig.cache.tokenKey) || userStore.token; |
| 108 | - // 从缓存/Pinia获取用户信息 | ||
| 109 | - const userInfo = cache.get(globalConfig.cache.userInfoKey) || userStore.userInfo; | 115 | + // 从全局变量取值,无需再声明局部变量 |
| 116 | + const userInfoVal = userInfo.value; | ||
| 117 | + console.log('当前用户信息:', userInfoVal?.user); | ||
| 110 | // 满足任一核心条件即视为已登录 | 118 | // 满足任一核心条件即视为已登录 |
| 111 | - return (!!token && !!userInfo); | 119 | + return !!token && !!userInfoVal; |
| 112 | }; | 120 | }; |
| 113 | 121 | ||
| 114 | - // 2. 未登录处理:跳转登录页,阻止后续逻辑执行 | 122 | + // 未登录处理:跳转登录页,阻止后续逻辑执行 |
| 115 | if (!isLogin()) { | 123 | if (!isLogin()) { |
| 116 | uni.showToast({ title: '请先登录', icon: 'none', duration: 1500 }); | 124 | uni.showToast({ title: '请先登录', icon: 'none', duration: 1500 }); |
| 117 | // 延迟跳转,确保提示语正常显示 | 125 | // 延迟跳转,确保提示语正常显示 |
| 118 | setTimeout(() => { | 126 | setTimeout(() => { |
| 119 | - // 使用reLaunch跳转,清空页面栈,避免返回当前菜单页 | ||
| 120 | uni.reLaunch({ | 127 | uni.reLaunch({ |
| 121 | - url: '/pages/login/index', // 替换为你的实际登录页路径 | 128 | + url: '/pages/login/index', |
| 122 | fail: (err) => { | 129 | fail: (err) => { |
| 123 | console.error('跳转登录页失败:', err); | 130 | console.error('跳转登录页失败:', err); |
| 124 | uni.showToast({ title: '跳转登录页异常', icon: 'none' }); | 131 | uni.showToast({ title: '跳转登录页异常', icon: 'none' }); |
| 125 | } | 132 | } |
| 126 | }); | 133 | }); |
| 127 | }, 1500); | 134 | }, 1500); |
| 128 | - // 隐藏加载状态 | ||
| 129 | loading.value = false; | 135 | loading.value = false; |
| 130 | - // 终止后续代码执行 | ||
| 131 | return; | 136 | return; |
| 132 | } | 137 | } |
| 133 | 138 | ||
| @@ -169,15 +174,33 @@ const handleMenuClick = (item: MenuItem) => { | @@ -169,15 +174,33 @@ const handleMenuClick = (item: MenuItem) => { | ||
| 169 | top: 0; | 174 | top: 0; |
| 170 | left: 0; | 175 | left: 0; |
| 171 | width: 100%; | 176 | width: 100%; |
| 172 | - height: 200px; | ||
| 173 | - background-color: #577ee3; | 177 | + height: 280px; |
| 178 | + background: url("https://img.jichengshanshui.com.cn:28207/appimg/bg.jpg") no-repeat; | ||
| 179 | + background-size: 100% 100%; | ||
| 174 | z-index: 1; | 180 | z-index: 1; |
| 181 | + color:#fff; | ||
| 182 | + text-align: left; | ||
| 183 | + padding-left: 20px; | ||
| 184 | + .welcome-text { | ||
| 185 | + padding-top: 80px; | ||
| 186 | + font-size: 20px; | ||
| 187 | + display: block; | ||
| 188 | + margin-bottom: 10px; | ||
| 189 | + font-weight: 500; | ||
| 190 | + } | ||
| 191 | + | ||
| 192 | + .platform-name { | ||
| 193 | + margin-bottom: 10px; | ||
| 194 | + font-size: 20px; | ||
| 195 | + display: block; | ||
| 196 | + opacity: 0.95; | ||
| 197 | + } | ||
| 175 | } | 198 | } |
| 176 | 199 | ||
| 177 | .content-wrap { | 200 | .content-wrap { |
| 178 | position: relative; | 201 | position: relative; |
| 179 | z-index: 2; | 202 | z-index: 2; |
| 180 | - padding: 120px 0 0; | 203 | + padding: 150px 0 0; |
| 181 | display: flex; | 204 | display: flex; |
| 182 | flex-direction: column; | 205 | flex-direction: column; |
| 183 | gap: 20rpx; | 206 | gap: 20rpx; |
| @@ -197,6 +220,7 @@ const handleMenuClick = (item: MenuItem) => { | @@ -197,6 +220,7 @@ const handleMenuClick = (item: MenuItem) => { | ||
| 197 | display: flex; | 220 | display: flex; |
| 198 | flex-wrap: wrap; | 221 | flex-wrap: wrap; |
| 199 | gap: 20rpx; | 222 | gap: 20rpx; |
| 223 | + //padding: 20rpx; // ✅ 优化:增加内边距,内容不贴边 | ||
| 200 | } | 224 | } |
| 201 | 225 | ||
| 202 | .content-block { | 226 | .content-block { |
static/imgs/bg.jpg
0 → 100644
42.2 KB
static/imgs/default-avatar.png