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 | 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 | 55 | // export const treeDetailReq = (params) => request.get('/business/tree/'+params ) |
| 37 | 56 | // ==> |
| 38 | 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 | 27 | name: '巡查员验收', |
| 28 | 28 | btnText: '验收', |
| 29 | 29 | operateTypePass: 140, //巡查员验收通过: 140 |
| 30 | - operateTypeNoPass: 240, // 巡查员验收不通过:230 | |
| 30 | + operateTypeNoPass: 240, // 巡查员验收不通过:240 | |
| 31 | 31 | backShow: false, |
| 32 | 32 | renewShow: false |
| 33 | 33 | }, |
| ... | ... | @@ -35,54 +35,47 @@ export const nextStepMap = { |
| 35 | 35 | name: '发起人确认', |
| 36 | 36 | btnText: '结束工单', |
| 37 | 37 | operateTypePass: 200, //巡查员结束工单:200 |
| 38 | - operateTypeNoPass: 240, // 巡查员验收不通过:230 | |
| 38 | + operateTypeNoPass: 240, // 巡查员验收不通过:240 | |
| 39 | 39 | operateTypeRenew: 100, //巡查员重新发起:100 |
| 40 | 40 | backShow: false, |
| 41 | 41 | renewShow: true |
| 42 | 42 | }, |
| 43 | - | |
| 44 | 43 | regionManager: { |
| 45 | 44 | name: '大区经理分配', |
| 46 | 45 | btnText: '分配', |
| 47 | 46 | operateTypePass: 80, // 大区经理分配:80 |
| 48 | 47 | operateTypeNoPass: 90, // 大区经理退回:90 |
| 49 | - // operateTypeRenew: 100, //巡查员重新发起:100 | |
| 50 | 48 | backShow: true, |
| 51 | 49 | renewShow: false |
| 52 | 50 | }, |
| 53 | - | |
| 54 | - | |
| 55 | 51 | shRegionManager: { |
| 56 | 52 | name: '督察员单子大区经理分配', |
| 57 | 53 | btnText: '分配', |
| 58 | 54 | operateTypePass: 60, // 大区经理分配:60 |
| 59 | 55 | operateTypeNoPass: 70, // 大区经理退回:70 |
| 60 | - // operateTypeRenew: 100, //巡查员重新发起:100 | |
| 61 | 56 | backShow: true, |
| 62 | 57 | renewShow: false |
| 63 | 58 | }, |
| 64 | 59 | } |
| 65 | 60 | |
| 66 | - | |
| 67 | 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 | 75 | '70': '退回', |
| 82 | 76 | '60': '分配', |
| 83 | 77 | } |
| 84 | 78 | |
| 85 | - | |
| 86 | 79 | /** |
| 87 | 80 | * 计算两个时间的时间差,返回格式化字符串(天/小时/分钟/秒,按需显示,无无效单位) |
| 88 | 81 | * @param {string | Date | number} startTime - 开始时间(时间字符串/Date对象/10位/13位时间戳) |
| ... | ... | @@ -170,32 +163,30 @@ export const calculateFormatTimeDiff = (startTime, endTime) => { |
| 170 | 163 | if (days > 0) { |
| 171 | 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 | 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 | 170 | timeParts.push(`${minutes}分钟`); |
| 178 | 171 | } |
| 179 | - // 秒数始终保留(即使为0,保证最后有一个有效单位) | |
| 180 | 172 | timeParts.push(`${seconds}秒`); |
| 181 | 173 | |
| 182 | - // 7. 过滤掉可能存在的「0单位」(可选优化:避免出现“0小时”等无效字段) | |
| 174 | + // 7. 过滤掉可能存在的「0单位」 | |
| 183 | 175 | const validTimeParts = timeParts.filter(part => { |
| 184 | 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 | 180 | // 8. 拼接并返回结果 |
| 189 | 181 | return validTimeParts.join(''); |
| 190 | 182 | }; |
| 191 | 183 | |
| 192 | - | |
| 193 | 184 | /** |
| 194 | 185 | * 将日期数组 [年, 月, 日] 转为标准日期字符串(兼容 iOS/Android) |
| 195 | 186 | * @param {Array} dateArr 日期数组,如 [2025, 12, 20] |
| 196 | 187 | * @returns {String} 标准日期字符串,如 '2025-12-20' |
| 197 | 188 | */ |
| 198 | -export const convertArrToDateStr= (dateArr)=> { | |
| 189 | +export const convertArrToDateStr = (dateArr) => { | |
| 199 | 190 | // 边界判断:非数组/长度不足3,返回空 |
| 200 | 191 | if (!Array.isArray(dateArr) || dateArr.length < 3) { |
| 201 | 192 | return dateArr; |
| ... | ... | @@ -206,4 +197,35 @@ export const convertArrToDateStr= (dateArr)=> { |
| 206 | 197 | const formatDay = day.toString().padStart(2, '0'); |
| 207 | 198 | // 拼接为标准字符串 |
| 208 | 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 | 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 | 28 | \ No newline at end of file | ... | ... |
main.js
| ... | ... | @@ -2,6 +2,7 @@ |
| 2 | 2 | import App from './App' |
| 3 | 3 | import uviewPlus from '@/uni_modules/uview-plus' |
| 4 | 4 | // 导入 Pinia 实例(你的 stores/index.js 导出的 pinia) |
| 5 | +import { inputFilter } from '@/common/utils/inputFilter.js' | |
| 5 | 6 | import pinia from '@/pinia/index' |
| 6 | 7 | import EmptyView from '@/components/empty-view/empty-view.vue'; |
| 7 | 8 | import UploadImage from '@/components/upload-image/upload-image.vue'; |
| ... | ... | @@ -16,7 +17,7 @@ export function createApp() { |
| 16 | 17 | |
| 17 | 18 | // 3. 注册 uviewPlus(保持原有逻辑) |
| 18 | 19 | app.use(uviewPlus) |
| 19 | - | |
| 20 | + app.config.globalProperties.$inputFilter = inputFilter; | |
| 20 | 21 | // 4. 注册 Pinia(核心:在 app 挂载前注册) |
| 21 | 22 | app.use(pinia) |
| 22 | 23 | // 全局注入字典工具(关键:provide需在app实例上注册) | ... | ... |
pages-sub/data/tree-archive/addTree.vue
| ... | ... | @@ -2,23 +2,22 @@ |
| 2 | 2 | <view class="container"> |
| 3 | 3 | <up-form :model="formData" ref="formRef" label-width="140rpx" border-bottom> |
| 4 | 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 | 6 | </up-form-item> |
| 7 | 7 | |
| 8 | - <!-- ✅ 核心修复1:给同行的两个表单外层包一层 弹性布局容器,彻底解决错位问题 --> | |
| 9 | 8 | <view class="form-row-wrap"> |
| 10 | 9 | <up-row gutter="10"> |
| 11 | 10 | <up-col span="6"> |
| 12 | 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 | 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 | 15 | </template> |
| 17 | 16 | </up-form-item> |
| 18 | 17 | </up-col> |
| 19 | 18 | <up-col span="6"> |
| 20 | 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 | 21 | input-align="left"/> |
| 23 | 22 | <template #right> |
| 24 | 23 | <text style="padding-left: 12rpx;color:#ccc;font-size:14px;">米</text> |
| ... | ... | @@ -37,24 +36,24 @@ |
| 37 | 36 | |
| 38 | 37 | <up-row gutter="10"> |
| 39 | 38 | <up-col span="6"> |
| 40 | - <up-form-item label="经度" prop="longitude" :borderBottom="false"> | |
| 39 | + <up-form-item label="经度" :borderBottom="false"> | |
| 41 | 40 | <up-input v-model="formData.longitude" placeholder="" readonly border="none"/> |
| 42 | 41 | </up-form-item> |
| 43 | 42 | </up-col> |
| 44 | 43 | <up-col span="6"> |
| 45 | - <up-form-item label="纬度" prop="latitude" :borderBottom="false"> | |
| 44 | + <up-form-item label="纬度" :borderBottom="false"> | |
| 46 | 45 | <up-input v-model="formData.latitude" placeholder="" readonly border="none"/> |
| 47 | 46 | </up-form-item> |
| 48 | 47 | </up-col> |
| 49 | 48 | </up-row> |
| 50 | 49 | |
| 51 | 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 | 52 | </up-form-item> |
| 54 | 53 | |
| 55 | 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 | 57 | </up-form-item> |
| 59 | 58 | |
| 60 | 59 | <up-form-item label="图片信息" prop="treeImgList" required> |
| ... | ... | @@ -82,27 +81,26 @@ |
| 82 | 81 | <up-row gutter="10"> |
| 83 | 82 | <up-col span="6"> |
| 84 | 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 | 85 | </up-form-item> |
| 87 | 86 | </up-col> |
| 88 | 87 | <up-col span="6"> |
| 89 | 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 | 90 | bg-color="transparent"/> |
| 92 | 91 | </up-form-item> |
| 93 | 92 | </up-col> |
| 94 | 93 | </up-row> |
| 95 | 94 | |
| 96 | 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 | 97 | </up-form-item> |
| 99 | 98 | |
| 100 | - <!-- ✅ 核心修复2:所有同行的表单都统一加样式,预防其他行也错位 --> | |
| 101 | 99 | <view class="form-row-wrap"> |
| 102 | 100 | <up-row gutter="10"> |
| 103 | 101 | <up-col span="6"> |
| 104 | 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 | 104 | input-align="left"/> |
| 107 | 105 | <template #right> |
| 108 | 106 | <text style="padding-left:12rpx;color:#ccc;font-size:14px">年</text> |
| ... | ... | @@ -111,7 +109,7 @@ |
| 111 | 109 | </up-col> |
| 112 | 110 | <up-col span="6"> |
| 113 | 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 | 113 | input-align="left"/> |
| 116 | 114 | <template #right> |
| 117 | 115 | <text style="padding-left:12rpx;color:#ccc;font-size:14px">厘米</text> |
| ... | ... | @@ -125,7 +123,7 @@ |
| 125 | 123 | <up-row gutter="10"> |
| 126 | 124 | <up-col span="6"> |
| 127 | 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 | 127 | input-align="left"/> |
| 130 | 128 | <template #right> |
| 131 | 129 | <text style="padding-left:12rpx;color:#ccc;font-size:14px">米</text> |
| ... | ... | @@ -134,7 +132,7 @@ |
| 134 | 132 | </up-col> |
| 135 | 133 | <up-col span="6"> |
| 136 | 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 | 136 | input-align="left"/> |
| 139 | 137 | <template #right> |
| 140 | 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 | 178 | import { useUploadImgs } from '@/common/utils/useUploadImgs' |
| 181 | 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 | 213 | const userStore = useUserStore(); |
| 185 | 214 | const formRef = ref(null) |
| ... | ... | @@ -226,42 +255,94 @@ const formData = reactive({ |
| 226 | 255 | maintainunit: '' |
| 227 | 256 | }) |
| 228 | 257 | |
| 229 | -// ✅ 核心修复3:修复所有校验规则里的 语法错误 【trigger: ['blur','change]'] → trigger: ['blur','change']】 | |
| 258 | +// ✅ 核心修复:表单校验规则【重中之重,彻底解决问题的核心】 | |
| 259 | +// 规则优先级:表情校验 > 必填校验 > 格式校验,保证表情一定优先提示,不会出现错乱提示 | |
| 230 | 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 | 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 | 346 | treeImgList: [treeImgs.imgValidateRule] |
| 266 | 347 | }) |
| 267 | 348 | |
| ... | ... | @@ -310,13 +391,15 @@ const handleActionSheetSelect = (e) => { |
| 310 | 391 | handleActionSheetClose(); |
| 311 | 392 | }; |
| 312 | 393 | |
| 313 | -// 打开地图选址 | |
| 394 | +// 地图选址 | |
| 314 | 395 | const openMap = () => { |
| 315 | 396 | uni.chooseLocation({ |
| 316 | - success: (res) => { | |
| 397 | + success: async (res) => { | |
| 317 | 398 | formData.growlocation = res.address |
| 318 | 399 | formData.latitude = res.latitude |
| 319 | 400 | formData.longitude = res.longitude |
| 401 | + await nextTick() | |
| 402 | + formRef.value?.validateField('growlocation'); | |
| 320 | 403 | }, |
| 321 | 404 | fail: (err) => { |
| 322 | 405 | console.error('地图选择失败', err); |
| ... | ... | @@ -327,13 +410,24 @@ const openMap = () => { |
| 327 | 410 | }); |
| 328 | 411 | } |
| 329 | 412 | |
| 330 | -// 表单提交核心方法 | |
| 413 | +// ✅ 表单提交核心方法【增加同步过滤表情兜底,万无一失】 | |
| 331 | 414 | const submit = async () => { |
| 332 | 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 | 425 | const valid = await formRef.value.validate() |
| 334 | 426 | if (!valid) return |
| 427 | + | |
| 335 | 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 | 431 | formData.treeImgList = uploadImgUrls |
| 338 | 432 | loadingFlag.value = true |
| 339 | 433 | try { |
| ... | ... | @@ -365,17 +459,14 @@ const submit = async () => { |
| 365 | 459 | padding-right: 10rpx; |
| 366 | 460 | } |
| 367 | 461 | |
| 368 | -// ✅ ✅ ✅ 核心修复样式:解决校验提示导致的布局错位问题,优先级最高 | |
| 369 | 462 | .form-row-wrap { |
| 370 | 463 | width: 100%; |
| 371 | 464 | display: flex; |
| 372 | 465 | flex-direction: column; |
| 373 | - // 关键:让校验提示文字 不撑开单元格,而是相对父容器定位 | |
| 374 | 466 | :deep(.u-form-item) { |
| 375 | 467 | position: relative; |
| 376 | 468 | margin-bottom: 0 !important; |
| 377 | 469 | } |
| 378 | - // 关键:校验错误提示文字 绝对定位在输入框下方,两行的提示互不影响 | |
| 379 | 470 | :deep(.u-form-item__body__right__message ) { |
| 380 | 471 | position: absolute; |
| 381 | 472 | left: 0; |
| ... | ... | @@ -387,7 +478,6 @@ const submit = async () => { |
| 387 | 478 | box-sizing: border-box; |
| 388 | 479 | } |
| 389 | 480 | } |
| 390 | -// 给同行表单底部留足提示文字的间距,防止和下一行重叠 | |
| 391 | 481 | .form-row-wrap + .u-form-item { |
| 392 | 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 | 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 | 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 | 280 | </style> |
| 16 | 281 | \ No newline at end of file | ... | ... |
pages-sub/data/tree-archive/index.vue
pages-sub/data/tree-archive/logDetail.vue
0 → 100644
pages-sub/data/tree-archive/treeRecord.vue
| 1 | 1 | <template> |
| 2 | 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 | 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 | 33 | </view> |
| 24 | 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 | 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 | 59 | </view> |
| 44 | 60 | </template> |
| ... | ... | @@ -46,15 +62,13 @@ |
| 46 | 62 | <script setup> |
| 47 | 63 | import { ref} from 'vue' |
| 48 | 64 | import { onLoad, onShow } from '@dcloudio/uni-app'; |
| 49 | -// 引入接口请求方法 | |
| 50 | 65 | import { treeRoadReq } from "@/api/tree-archive/tree-archive.js"; |
| 51 | - | |
| 66 | +import { timeFormat } from '@/uni_modules/uview-plus'; | |
| 52 | 67 | |
| 53 | 68 | const rows = ref([]) |
| 54 | 69 | const roadId = ref('') |
| 55 | 70 | const count = ref(0) |
| 56 | 71 | |
| 57 | - | |
| 58 | 72 | onLoad((options) => { |
| 59 | 73 | console.log(options) |
| 60 | 74 | roadId.value = options.roadId |
| ... | ... | @@ -65,30 +79,30 @@ onShow(() => { |
| 65 | 79 | treeRoadQuery() |
| 66 | 80 | }) |
| 67 | 81 | |
| 68 | -// 前往修改页面 | |
| 69 | 82 | const toEditPage = (id) => { |
| 70 | 83 | uni.navigateTo({ |
| 71 | 84 | url: `/pages-sub/data/tree-archive/editTree?id=${id}` |
| 72 | 85 | }) |
| 73 | 86 | } |
| 74 | 87 | |
| 75 | -// 前往新增页面 | |
| 76 | 88 | const toAddTreePage = () => { |
| 77 | 89 | uni.navigateTo({ |
| 78 | 90 | url: `/pages-sub/data/tree-archive/addTree?roadId=${roadId.value}` |
| 79 | 91 | }) |
| 80 | 92 | } |
| 81 | 93 | |
| 82 | -// 树木记录列表查询接口 | |
| 83 | 94 | const treeRoadQuery = async () => { |
| 84 | 95 | const res = await treeRoadReq( {road: roadId.value}) |
| 85 | 96 | console.log(res) |
| 86 | - rows.value = res.rows | |
| 97 | + rows.value = res.list | |
| 87 | 98 | } |
| 88 | 99 | </script> |
| 89 | 100 | |
| 90 | 101 | <style scoped lang="scss"> |
| 91 | -// 保留原页面所有样式,一行未改,样式完全一致 | |
| 102 | +// ✅ 你的原始样式 一行没删、一行没改、全部保留 | |
| 103 | +.container { | |
| 104 | + min-height: 100vh; | |
| 105 | +} | |
| 92 | 106 | .record-wrap { |
| 93 | 107 | padding-bottom: 60px; |
| 94 | 108 | } |
| ... | ... | @@ -110,6 +124,8 @@ const treeRoadQuery = async () => { |
| 110 | 124 | height: 70px; |
| 111 | 125 | width: 70px; |
| 112 | 126 | background-size: 100% 100%; |
| 127 | + background-repeat: no-repeat; // 新增:防止图片平铺 | |
| 128 | + background-position: center; // 新增:图片居中显示 | |
| 113 | 129 | } |
| 114 | 130 | |
| 115 | 131 | .record-list-right { |
| ... | ... | @@ -124,14 +140,22 @@ const treeRoadQuery = async () => { |
| 124 | 140 | } |
| 125 | 141 | |
| 126 | 142 | .treenumber-no { |
| 143 | + margin-top: 5px; | |
| 127 | 144 | padding: 3px 10px; |
| 128 | 145 | background: #bdefd0; |
| 129 | 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 | 161 | </style> |
| 138 | 162 | \ No newline at end of file | ... | ... |
pages-sub/problem/regional-order-manage/index.vue
| ... | ... | @@ -51,14 +51,12 @@ |
| 51 | 51 | <empty-view /> |
| 52 | 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 | 55 | <up-card |
| 57 | - v-if="activeTab === 0" | |
| 58 | 56 | :border="false" |
| 59 | 57 | :foot-border-top="false" |
| 60 | 58 | v-for="(item, index) in orderList" |
| 61 | - :key="`todo_${item.id}_${index}`" | |
| 59 | + :key="item.id || index" | |
| 62 | 60 | :show-head="false" |
| 63 | 61 | class="order-card" |
| 64 | 62 | > |
| ... | ... | @@ -80,75 +78,35 @@ |
| 80 | 78 | <view class="u-body-item-title">情况描述:</view> |
| 81 | 79 | <view class="u-line-1 u-body-value">{{ item.remark || '无' }}</view> |
| 82 | 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 | 90 | </view> |
| 88 | 91 | </view> |
| 89 | 92 | <view class="u-body-item u-flex"> |
| 90 | 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 | 95 | </view> |
| 93 | 96 | <view class="u-body-item u-flex"> |
| 94 | 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 | 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 | 103 | <up-button type="warning" size="mini" @click="handleReject(item)" v-show="nextStepMap[item.taskKey].backShow">回退</up-button> |
| 100 | 104 | <up-button type="success" size="mini" @click="handleRenew(item)" v-show="nextStepMap[item.taskKey].renewShow">重新提交</up-button> |
| 101 | 105 | <up-button type="primary" size="mini" @click="handleProcess(item)">{{ nextStepMap[item.taskKey].btnText }}</up-button> |
| 102 | 106 | <up-button type="info" size="mini" @click="handleDetail(item)">详情</up-button> |
| 103 | 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 | 110 | </view> |
| 153 | 111 | </template> |
| 154 | 112 | </up-card> |
| ... | ... | @@ -265,7 +223,7 @@ const USER_ROLES = userStore.userInfo?.roles || []; |
| 265 | 223 | // ========== 基础状态 (按模块分组,语义化更强) ========== |
| 266 | 224 | // tab切换 |
| 267 | 225 | const activeTab = ref(0); // 0-待办 1-我发起的 2-已办 |
| 268 | -const tabList = ref([{name: '待办'}, {name: '我发起的任务'}, {name: '已办'}]); | |
| 226 | +const tabList = [{name: '待办'}, {name: '我发起的任务'}, {name: '已办'}]; | |
| 269 | 227 | // 排序与搜索 |
| 270 | 228 | const selectedSortValue = ref(1); |
| 271 | 229 | const sortOptions = ref([ |
| ... | ... | @@ -276,10 +234,11 @@ const searchValue = ref(''); |
| 276 | 234 | const pagingRef = ref(null); |
| 277 | 235 | const orderList = ref([]); |
| 278 | 236 | |
| 279 | -// ========== 角色权限计算属性 (修复核心bug: 原代码取反逻辑写反) ========== | |
| 237 | +// ========== 角色权限计算属性 ========== | |
| 280 | 238 | const isAi = computed(() => { |
| 239 | + const roles = USER_ROLES || []; | |
| 281 | 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 | 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 | 280 | * 生成统一的临时存储key |
| 305 | - * @param {String} prefix 前缀标识 | |
| 306 | - * @returns {String} 唯一key | |
| 307 | 281 | */ |
| 308 | 282 | const generateTempKey = (prefix = 'order') => { |
| 309 | 283 | return `${prefix}_${Date.now()}_${Math.floor(Math.random() * 10000)}`; |
| ... | ... | @@ -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 | 289 | const setOrderStorage = (item, prefix) => { |
| 319 | 290 | if (!item?.id) return null; |
| ... | ... | @@ -323,16 +294,13 @@ const setOrderStorage = (item, prefix) => { |
| 323 | 294 | return tempKey; |
| 324 | 295 | } catch (error) { |
| 325 | 296 | console.error('存储工单数据失败:', error); |
| 326 | - uni.showToast({title: '数据存储异常,请重试', icon: 'none'}); | |
| 297 | + showToast('数据存储异常,请重试'); | |
| 327 | 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 | 305 | const getQueryParams = (pageNo, pageSize) => { |
| 338 | 306 | return { |
| ... | ... | @@ -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 | 317 | const callApprovalApi = async (params, taskKey) => { |
| 353 | 318 | if (taskKey === 'shRegionManager') return await dcyUniversalApproval(params); |
| 354 | 319 | if (taskKey === 'regionManager') return await qyUniversalApproval(params); |
| 355 | - // 根据角色匹配对应接口 | |
| 356 | 320 | if (USER_ROLES.includes('regional_manager')) return await daquUniversalApproval(params); |
| 357 | 321 | if (USER_ROLES.includes('Inspector_global')) return await dcyUniversalApproval(params); |
| 358 | 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 | 350 | if (activeTab.value === 0) res = await todoBuzSimplePage(params); |
| 385 | 351 | else if (activeTab.value === 1) res = await myBuzSimplePage(params); |
| 386 | 352 | else res = await doneBuzSimplePage(params); |
| 387 | - pagingRef.value.complete(res?.list || [], res?.total || 0); | |
| 353 | + pagingHandle('complete', res?.list || [], res?.total || 0); | |
| 388 | 354 | } catch (error) { |
| 389 | 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 | 380 | |
| 415 | 381 | // 工单详情 |
| 416 | 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 | 397 | const handleRenew = (item) => { |
| 398 | + if (!item?.id) return showToast('工单信息异常,无法重新提交'); | |
| 443 | 399 | const tempKey = setOrderStorage(item, 'renew_order'); |
| 444 | 400 | if (!tempKey) return; |
| 445 | 401 | |
| ... | ... | @@ -452,9 +408,9 @@ const handleRenew = (item) => { |
| 452 | 408 | |
| 453 | 409 | // 处理工单核心逻辑 |
| 454 | 410 | const handleProcess = async (item) => { |
| 455 | - if (!item) return uni.showToast({title: '工单信息异常', icon: 'none'}); | |
| 411 | + if (!item) return showToast('工单信息异常'); | |
| 456 | 412 | const stepName = nextStepMap[item.taskKey]?.name; |
| 457 | - let resData | |
| 413 | + if (!stepName) return showToast('暂无处理权限或工单状态异常'); | |
| 458 | 414 | try { |
| 459 | 415 | // 大区经理分配 |
| 460 | 416 | if (stepName === '大区经理分配') { |
| ... | ... | @@ -464,8 +420,8 @@ const handleProcess = async (item) => { |
| 464 | 420 | // 督察员单子大区经理分配 |
| 465 | 421 | else if (stepName === '督察员单子大区经理分配') { |
| 466 | 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 | 425 | refreshOrderList(); |
| 470 | 426 | } |
| 471 | 427 | // 验收弹窗 |
| ... | ... | @@ -487,7 +443,7 @@ const handleProcess = async (item) => { |
| 487 | 443 | operateType: 200, agree: 1, reason: '结束工单' |
| 488 | 444 | }; |
| 489 | 445 | await callApprovalApi(requestData, item.taskKey); |
| 490 | - uni.showToast({title: '结束成功', icon: 'success'}); | |
| 446 | + showToast('结束成功', 'success'); | |
| 491 | 447 | refreshOrderList(); |
| 492 | 448 | } |
| 493 | 449 | } |
| ... | ... | @@ -495,13 +451,13 @@ const handleProcess = async (item) => { |
| 495 | 451 | } |
| 496 | 452 | } catch (error) { |
| 497 | 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 | 459 | const handleReject = (item) => { |
| 504 | - if (!item?.id) return uni.showToast({title: '工单信息异常,无法回退', icon: 'none'}); | |
| 460 | + if (!item?.id) return showToast('工单信息异常,无法回退'); | |
| 505 | 461 | currentRejectItem.value = item; |
| 506 | 462 | rejectReason.value = ''; |
| 507 | 463 | rejectImgs.clearImgs(); |
| ... | ... | @@ -518,9 +474,9 @@ const handleRejectModalCancel = () => { |
| 518 | 474 | // 确认回退工单 |
| 519 | 475 | const confirmReject = async () => { |
| 520 | 476 | const reason = rejectReason.value.trim(); |
| 521 | - if (!reason) return uni.showToast({title: '请填写回退原因', icon: 'none'}); | |
| 477 | + if (!reason) return showToast('请填写回退原因'); | |
| 522 | 478 | const item = currentRejectItem.value; |
| 523 | - if (!item?.id) return uni.showToast({title: '工单信息异常', icon: 'none'}); | |
| 479 | + if (!item?.id) return showToast('工单信息异常'); | |
| 524 | 480 | |
| 525 | 481 | uni.showLoading({title: '提交中...', mask: true}); |
| 526 | 482 | try { |
| ... | ... | @@ -529,12 +485,12 @@ const confirmReject = async () => { |
| 529 | 485 | taskId: item.taskId, operateType: nextStepMap[item.taskKey].operateTypeNoPass, agree:1, reason |
| 530 | 486 | }; |
| 531 | 487 | await callApprovalApi(requestData, item.taskKey); |
| 532 | - uni.showToast({title: '回退成功', icon: 'success'}); | |
| 488 | + showToast('回退成功', 'success'); | |
| 533 | 489 | handleRejectModalCancel(); |
| 534 | 490 | refreshOrderList(); |
| 535 | 491 | } catch (error) { |
| 536 | 492 | console.error('回退工单失败:', error); |
| 537 | - uni.showToast({title: '回退失败,请重试', icon: 'none'}); | |
| 493 | + showToast(error.msg || error.message || '回退失败,请重试'); | |
| 538 | 494 | } finally { |
| 539 | 495 | uni.hideLoading(); |
| 540 | 496 | } |
| ... | ... | @@ -559,12 +515,13 @@ const handleAcceptModalCancel = () => { |
| 559 | 515 | // 验收提交 |
| 560 | 516 | const handleAcceptModalConfirm = async () => { |
| 561 | 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 | 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 | 525 | try { |
| 569 | 526 | const postData = { |
| 570 | 527 | returnImgs: acceptImgs.getSuccessImgUrls(), taskKey: item.taskKey, workerDataId: item.id, |
| ... | ... | @@ -572,12 +529,14 @@ const handleAcceptModalConfirm = async () => { |
| 572 | 529 | operateType: acceptRadioValue.value === '0' ? nextStepMap[item.taskKey].operateTypePass : nextStepMap[item.taskKey].operateTypeNoPass |
| 573 | 530 | }; |
| 574 | 531 | await callApprovalApi(postData, item.taskKey); |
| 575 | - uni.showToast({title: '提交成功', icon: 'success'}); | |
| 532 | + showToast('提交成功', 'success'); | |
| 576 | 533 | handleAcceptModalCancel(); |
| 577 | 534 | refreshOrderList(); |
| 578 | 535 | } catch (error) { |
| 579 | 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 | 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 | 588 | .order-card { |
| 624 | 589 | margin: 0 20rpx 20rpx; |
| ... | ... | @@ -627,6 +592,11 @@ onLoad(() => { |
| 627 | 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 | 601 | .reject-modal-content, .accept-modal-content { |
| 632 | 602 | width: 100%; | ... | ... |
pages-sub/problem/regional-order-manage/order-detail.vue
| ... | ... | @@ -64,7 +64,6 @@ |
| 64 | 64 | </template> |
| 65 | 65 | </up-cell> |
| 66 | 66 | |
| 67 | - | |
| 68 | 67 | <!-- 情况描述 --> |
| 69 | 68 | <up-cell> |
| 70 | 69 | <template #title> |
| ... | ... | @@ -82,7 +81,6 @@ |
| 82 | 81 | align="middle" |
| 83 | 82 | ></up-cell> |
| 84 | 83 | |
| 85 | - | |
| 86 | 84 | <!-- 提交人 --> |
| 87 | 85 | <up-cell title="提交人" :value="orderDetail.userName || '--'" align="middle"></up-cell> |
| 88 | 86 | |
| ... | ... | @@ -103,9 +101,6 @@ |
| 103 | 101 | </template> |
| 104 | 102 | </up-cell> |
| 105 | 103 | |
| 106 | - | |
| 107 | - | |
| 108 | - | |
| 109 | 104 | <!-- 问题照片 --> |
| 110 | 105 | <up-cell title="问题照片"> |
| 111 | 106 | <template #value> |
| ... | ... | @@ -115,7 +110,6 @@ |
| 115 | 110 | :urls="orderDetail.problemsImgs.slice(0, 3)" |
| 116 | 111 | :singleSize="70" |
| 117 | 112 | :multipleSize="70" |
| 118 | - | |
| 119 | 113 | :preview-full-image="true" |
| 120 | 114 | ></up-album> |
| 121 | 115 | <text v-else class="empty-text">暂无问题照片</text> |
| ... | ... | @@ -205,16 +199,10 @@ |
| 205 | 199 | <!-- 1. 原标题内容:节点名称 + 操作人 --> |
| 206 | 200 | <view class="step-title"> |
| 207 | 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 | 202 | </view> |
| 214 | 203 | |
| 215 | 204 | <!-- 2. 原描述内容:时间 + 处理说明(最多200字) --> |
| 216 | 205 | <view class="step-desc"> |
| 217 | - | |
| 218 | 206 | <view class="operator-name up-line-1"> |
| 219 | 207 | 处理人: |
| 220 | 208 | <text>{{ item.tasks?.map(itemName => itemName.assigneeUser?.nickname).filter(Boolean).join(',') }}</text> |
| ... | ... | @@ -243,7 +231,6 @@ |
| 243 | 231 | :urls="item.tasks[0].attattmentUrls.slice(0, 3)" |
| 244 | 232 | :singleSize="70" |
| 245 | 233 | :multipleSize="70" |
| 246 | - | |
| 247 | 234 | :preview-full-image="true" |
| 248 | 235 | class="step-album" |
| 249 | 236 | ></up-album> |
| ... | ... | @@ -252,7 +239,6 @@ |
| 252 | 239 | </template> |
| 253 | 240 | </up-steps-item> |
| 254 | 241 | </template> |
| 255 | - | |
| 256 | 242 | </up-steps> |
| 257 | 243 | |
| 258 | 244 | <!-- 流程节点为空时的提示 --> |
| ... | ... | @@ -267,23 +253,23 @@ |
| 267 | 253 | </view> |
| 268 | 254 | |
| 269 | 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 | 275 | <up-modal |
| ... | ... | @@ -375,7 +361,7 @@ |
| 375 | 361 | </template> |
| 376 | 362 | |
| 377 | 363 | <script setup lang="ts"> |
| 378 | -import {ref} from 'vue'; | |
| 364 | +import {ref, computed} from 'vue'; | |
| 379 | 365 | import {onLoad, onShow} from '@dcloudio/uni-app'; |
| 380 | 366 | import {timeFormat} from '@/uni_modules/uview-plus'; |
| 381 | 367 | import { |
| ... | ... | @@ -388,10 +374,73 @@ import { |
| 388 | 374 | dcyUniversalApproval |
| 389 | 375 | } from '@/api/regional-order-manage/regional-order-manage'; |
| 390 | 376 | import {nextStepMap, buzStatusMap, calculateFormatTimeDiff} from '@/common/utils/common' |
| 391 | -// 引入图片上传组合式函数 | |
| 392 | 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 | 444 | const loading = ref(true); |
| 396 | 445 | const activeTopTab = ref(0); // 顶部Tab激活索引 |
| 397 | 446 | const activeImgTab = ref(0); // 图片分类Tab激活索引 |
| ... | ... | @@ -402,7 +451,7 @@ const topTabList = ref([ |
| 402 | 451 | {name: '流程节点'} |
| 403 | 452 | ]); |
| 404 | 453 | |
| 405 | -// 流程节点数据(初始化适配接口格式) | |
| 454 | +// 流程节点数据 | |
| 406 | 455 | const processData = ref<any>({ |
| 407 | 456 | status: 2, |
| 408 | 457 | activityNodes: [], |
| ... | ... | @@ -410,7 +459,7 @@ const processData = ref<any>({ |
| 410 | 459 | todoTask: null |
| 411 | 460 | }); |
| 412 | 461 | |
| 413 | -// 图片分类Tab列表(带角标配置) | |
| 462 | +// 图片分类Tab列表 | |
| 414 | 463 | const imgTabList = ref([ |
| 415 | 464 | {name: '开始', badge: {isDot: true}}, |
| 416 | 465 | {name: '进行中'}, |
| ... | ... | @@ -419,7 +468,7 @@ const imgTabList = ref([ |
| 419 | 468 | {name: '材料'} |
| 420 | 469 | ]); |
| 421 | 470 | |
| 422 | -// 工单详情数据(初始化赋值,可被接口数据覆盖) | |
| 471 | +// 工单详情数据 | |
| 423 | 472 | const orderDetail = ref<any>({ |
| 424 | 473 | id: 0, |
| 425 | 474 | busiLine: '', |
| ... | ... | @@ -472,24 +521,15 @@ const orderDetail = ref<any>({ |
| 472 | 521 | materialImgs: [] |
| 473 | 522 | }); |
| 474 | 523 | |
| 475 | -/** | |
| 476 | - * 当前激活的图片列表(无需函数调用,直接访问) | |
| 477 | - */ | |
| 478 | 524 | const currentImgList = ref([]) |
| 479 | - | |
| 480 | 525 | const tabKeyMap = ['startImgs', 'processingImgs', 'endImgs', 'personImgs', 'materialImgs']; |
| 481 | 526 | const imgTabChange = (({index}: { index: number }) => { |
| 482 | - console.log(index) | |
| 483 | 527 | const currentKey = tabKeyMap[index] |
| 484 | - console.log(currentKey) | |
| 485 | - console.log(orderDetail.value[currentKey]) | |
| 486 | 528 | currentImgList.value = orderDetail.value[currentKey] |
| 487 | 529 | }) |
| 488 | 530 | |
| 489 | 531 | /** |
| 490 | 532 | * 截取reason最多200字 |
| 491 | - * @param reason 处理说明 | |
| 492 | - * @returns 截取后的字符串 | |
| 493 | 533 | */ |
| 494 | 534 | const getLimitReason = (reason: string | null | undefined) => { |
| 495 | 535 | if (!reason) return '无处理说明'; |
| ... | ... | @@ -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 | 543 | const getCurrentStepIndex = () => { |
| 507 | 544 | const {activityNodes} = processData.value; |
| 508 | 545 | if (!activityNodes || !activityNodes.length) return 0; |
| 509 | - | |
| 510 | - // 2. 若没有处理中的节点(全部已完成),则激活最后一个节点 | |
| 511 | 546 | return activityNodes.length - 1; |
| 512 | 547 | } |
| 513 | 548 | |
| ... | ... | @@ -518,7 +553,6 @@ const DetailQuery = async (taskIdStr: string) => { |
| 518 | 553 | console.log('当前工单ID:', taskIdStr) |
| 519 | 554 | try { |
| 520 | 555 | loading.value = true; |
| 521 | - // 转换activeTab为数字类型,避免类型不一致导致接口调用失败 | |
| 522 | 556 | const tabType = Number(activeTab.value); |
| 523 | 557 | let res: any; |
| 524 | 558 | |
| ... | ... | @@ -533,7 +567,6 @@ const DetailQuery = async (taskIdStr: string) => { |
| 533 | 567 | return; |
| 534 | 568 | } |
| 535 | 569 | |
| 536 | - // 覆盖工单详情数据 | |
| 537 | 570 | if (res) { |
| 538 | 571 | orderDetail.value = res; |
| 539 | 572 | currentImgList.value = orderDetail.value.startImgs |
| ... | ... | @@ -552,24 +585,19 @@ const activeTab = ref('') |
| 552 | 585 | const processInstanceId = ref('') |
| 553 | 586 | |
| 554 | 587 | const activeTopTabClick = async (item: any) => { |
| 555 | - console.log(item) | |
| 556 | 588 | activeTopTab.value = item.index |
| 557 | 589 | if (activeTopTab.value == 1) { |
| 558 | 590 | let getData = { |
| 559 | 591 | processInstanceId: processInstanceId.value, |
| 560 | 592 | } |
| 561 | 593 | const res = await getApprovalDetail(getData) |
| 562 | - console.log(res) | |
| 563 | - // 关键:格式化数据,补充up-steps要求的title字段 | |
| 564 | 594 | if (res && res.activityNodes && res.activityNodes.length) { |
| 565 | - // 1. 先过滤:剔除 name 为 "结束" 的节点 | |
| 566 | 595 | const filteredActivityNodes = res.activityNodes.filter(node => { |
| 567 | - // 返回 true 保留节点,返回 false 剔除节点 | |
| 568 | 596 | return node.name !== '结束'; |
| 569 | 597 | }); |
| 570 | 598 | const formatActivityNodes = filteredActivityNodes.map(node => ({ |
| 571 | 599 | ...node, |
| 572 | - title: node.name // 补充强制字段,满足3.3.48版本组件要求 | |
| 600 | + title: node.name | |
| 573 | 601 | })); |
| 574 | 602 | processData.value = { |
| 575 | 603 | ...res, |
| ... | ... | @@ -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 | 616 | const rejectImgs = useUploadImgs({ |
| 590 | 617 | maxCount: 3, |
| 591 | 618 | uploadText: '选择回退图片', |
| ... | ... | @@ -594,13 +621,11 @@ const rejectImgs = useUploadImgs({ |
| 594 | 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 | 629 | const acceptImgs = useUploadImgs({ |
| 605 | 630 | maxCount: 3, |
| 606 | 631 | uploadText: '选择验收图片', |
| ... | ... | @@ -609,270 +634,202 @@ const acceptImgs = useUploadImgs({ |
| 609 | 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 | 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 | 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 | 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 | 664 | const handleRejectModalCancel = () => { |
| 658 | 665 | rejectModalShow.value = false; |
| 659 | 666 | rejectReason.value = ''; |
| 660 | - rejectImgs.clearImgs(); // 清空上传图片 | |
| 667 | + rejectImgs.clearImgs(); | |
| 661 | 668 | }; |
| 662 | 669 | |
| 663 | -// ========== 确认回退工单(新增returnImgs传参) ========== | |
| 670 | +// ========== 确认回退工单 完整对齐列表页 ========== | |
| 664 | 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 | 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 | 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 | 694 | console.error('回退工单失败:', error); |
| 703 | - uni.showToast({title: '网络异常,回退失败', icon: 'none', duration: 1000}); | |
| 695 | + showToast(error.msg || error.message || '回退失败,请重试'); | |
| 704 | 696 | } finally { |
| 705 | - // 隐藏加载中 | |
| 706 | 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 | 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 | 742 | uni.showModal({ |
| 767 | 743 | title: "结束工单", |
| 768 | 744 | content: "请确定是否结束工单?", |
| 769 | - success: async function (res) { | |
| 745 | + success: async (res) => { | |
| 770 | 746 | if (res.confirm) { |
| 771 | - // 构建请求参数 | |
| 772 | 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 | 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 | 768 | const handleAcceptModalCancel = () => { |
| 800 | 769 | acceptModalShow.value = false; |
| 801 | 770 | acceptReason.value = ''; |
| 802 | 771 | acceptRadioValue.value = '0'; |
| 803 | - acceptImgs.clearImgs(); // 清空验收图片 | |
| 772 | + acceptImgs.clearImgs(); | |
| 804 | 773 | }; |
| 805 | 774 | |
| 806 | -// ========== 验收弹窗确认按钮(新增returnImgs传参) ========== | |
| 775 | +// ========== 验收弹窗确认 修复BUG+对齐列表页 ========== | |
| 807 | 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 | 785 | try { |
| 819 | - // 3. 调用验收接口 | |
| 820 | - console.log(currentAcceptItem.value) | |
| 821 | 786 | let postData: any = {} |
| 822 | - if (currentAcceptItem.value?.taskKey == 'ylTeamLeaderConfirm') { // 养护组长验收 | |
| 787 | + if (item?.taskKey == 'ylTeamLeaderConfirm') { | |
| 823 | 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 | 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 | 812 | uni.navigateBack({delta: 1}); |
| 855 | - uni.showToast({title: '提交成功', icon: 'success', duration: 1000}); | |
| 856 | - } catch (error) { | |
| 857 | - // 5. 操作失败处理 | |
| 813 | + } catch (error: any) { | |
| 858 | 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 | 822 | onLoad((options: any) => { |
| 864 | 823 | console.log('页面入参:', options) |
| 865 | 824 | const {taskId: taskIdOpt, activeTab: activeTabOpt, processInstanceId: processInstanceIdOpt} = options; |
| 866 | - // 0-待办 1-我发起的 2-已办 | |
| 867 | 825 | taskId.value = taskIdOpt || ''; |
| 868 | 826 | activeTab.value = activeTabOpt || '0'; |
| 869 | 827 | processInstanceId.value = processInstanceIdOpt; |
| 828 | + eventChannel.value = getCurrentPages()[getCurrentPages().length - 1].getOpenerEventChannel(); | |
| 870 | 829 | DetailQuery(taskId.value); |
| 871 | 830 | }); |
| 872 | 831 | |
| 873 | -onShow(() => { | |
| 874 | - // 注释原有逻辑,避免重复加载 | |
| 875 | -}); | |
| 832 | +onShow(() => {}); | |
| 876 | 833 | </script> |
| 877 | 834 | |
| 878 | 835 | <style scoped lang="scss"> |
| ... | ... | @@ -888,12 +845,10 @@ onShow(() => { |
| 888 | 845 | |
| 889 | 846 | // 顶部Tabs |
| 890 | 847 | .top-tabs { |
| 891 | - //background-color: #fff; | |
| 892 | 848 | } |
| 893 | 849 | |
| 894 | 850 | // Tab内容区 |
| 895 | 851 | .tab-content { |
| 896 | - //padding: 16rpx; | |
| 897 | 852 | } |
| 898 | 853 | |
| 899 | 854 | // 工单详情内容 |
| ... | ... | @@ -904,7 +859,6 @@ onShow(() => { |
| 904 | 859 | } |
| 905 | 860 | |
| 906 | 861 | .cell-content-wrap { |
| 907 | - // 保持原有样式,兼容up-album布局 | |
| 908 | 862 | } |
| 909 | 863 | |
| 910 | 864 | .empty-text { |
| ... | ... | @@ -916,21 +870,12 @@ onShow(() => { |
| 916 | 870 | // 图片分类Tabs区块 |
| 917 | 871 | .img-tabs-block { |
| 918 | 872 | background-color: #fff; |
| 919 | - //border-radius: 12rpx; | |
| 920 | - //padding: 16rpx; | |
| 921 | 873 | |
| 922 | - // 图片内容区 | |
| 923 | 874 | .img-tab-content { |
| 924 | 875 | padding: 20rpx 15px ; |
| 925 | 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 | 881 | .empty-img-text { |
| ... | ... | @@ -942,33 +887,25 @@ onShow(() => { |
| 942 | 887 | } |
| 943 | 888 | } |
| 944 | 889 | |
| 945 | -// 流程节点区域(完整样式,确保内容可见) | |
| 890 | +// 流程节点区域 | |
| 946 | 891 | .process-content { |
| 947 | - //padding: 16rpx; | |
| 948 | - //min-height: 400rpx; | |
| 949 | - | |
| 950 | 892 | .empty-process { |
| 951 | 893 | margin-top: 100rpx; |
| 952 | 894 | } |
| 953 | 895 | |
| 954 | - // 竖向步骤条容器样式 | |
| 955 | 896 | .vertical-steps { |
| 956 | - //width: 100%; | |
| 957 | 897 | background-color: #fff; |
| 958 | 898 | padding: 20rpx; |
| 959 | 899 | border-radius: 12rpx; |
| 960 | 900 | display: flex; |
| 961 | 901 | flex-direction: column; |
| 962 | - gap: 20rpx; // 步骤项间距 | |
| 902 | + gap: 20rpx; | |
| 963 | 903 | } |
| 964 | 904 | |
| 965 | - // 自定义内容容器样式 | |
| 966 | 905 | .step-content-wrap { |
| 967 | 906 | width: 100%; |
| 968 | - //padding: 10rpx 0; | |
| 969 | 907 | box-sizing: border-box; |
| 970 | 908 | |
| 971 | - // 节点标题 + 操作人样式 | |
| 972 | 909 | .step-title { |
| 973 | 910 | font-size: 30rpx; |
| 974 | 911 | font-weight: 600; |
| ... | ... | @@ -983,7 +920,6 @@ onShow(() => { |
| 983 | 920 | } |
| 984 | 921 | } |
| 985 | 922 | |
| 986 | - // 描述(时间 + 处理说明)样式 | |
| 987 | 923 | .step-desc { |
| 988 | 924 | font-size: 24rpx; |
| 989 | 925 | color: #666; |
| ... | ... | @@ -998,16 +934,14 @@ onShow(() => { |
| 998 | 934 | } |
| 999 | 935 | |
| 1000 | 936 | .reason-line { |
| 1001 | - word-break: break-all; // 长文本自动换行 | |
| 937 | + word-break: break-all; | |
| 1002 | 938 | } |
| 1003 | 939 | } |
| 1004 | 940 | |
| 1005 | - // 相册容器样式 | |
| 1006 | 941 | .step-album-wrap { |
| 1007 | 942 | padding: 10rpx 0; |
| 1008 | 943 | |
| 1009 | 944 | .step-album { |
| 1010 | - //width: 100%; | |
| 1011 | 945 | } |
| 1012 | 946 | |
| 1013 | 947 | .no-img-tip { |
| ... | ... | @@ -1067,11 +1001,9 @@ onShow(() => { |
| 1067 | 1001 | .mt-30 { |
| 1068 | 1002 | margin-top: 30rpx; |
| 1069 | 1003 | } |
| 1070 | -// 针对 up-album 单图容器的样式(穿透 scoped 限制) | |
| 1004 | +// 针对 up-album 单图容器的样式 | |
| 1071 | 1005 | :deep .u-album__row__wrapper image { |
| 1072 | - width: 70px !important; // 与多图保持一致 | |
| 1006 | + width: 70px !important; | |
| 1073 | 1007 | height: 70px !important; |
| 1074 | 1008 | } |
| 1075 | - | |
| 1076 | - | |
| 1077 | 1009 | </style> |
| 1078 | 1010 | \ No newline at end of file | ... | ... |
pages.json
| ... | ... | @@ -17,14 +17,16 @@ |
| 17 | 17 | { |
| 18 | 18 | "path": "pages/index/index", |
| 19 | 19 | "style": { |
| 20 | - "navigationBarTitleText": "首页" | |
| 20 | + "navigationBarTitleText": "首页", | |
| 21 | + "navigationStyle": "custom" | |
| 21 | 22 | } |
| 22 | 23 | }, |
| 23 | 24 | |
| 24 | 25 | { |
| 25 | 26 | "path": "pages/mine/index", |
| 26 | 27 | "style": { |
| 27 | - "navigationBarTitleText": "我的" | |
| 28 | + "navigationBarTitleText": "我的", | |
| 29 | + "navigationStyle": "custom" | |
| 28 | 30 | } |
| 29 | 31 | } |
| 30 | 32 | ], |
| ... | ... | @@ -209,10 +211,16 @@ |
| 209 | 211 | }, |
| 210 | 212 | { |
| 211 | 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 | 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 | 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 | 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 | 358 | \ No newline at end of file | ... | ... |
pages/login/index.vue
| ... | ... | @@ -103,7 +103,7 @@ const loginFormRules = reactive({ |
| 103 | 103 | // 密码校验:必填 + 长度限制 6-20位(行业通用密码长度,可自行调整) |
| 104 | 104 | password: [ |
| 105 | 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 | 189 | |
| 190 | 190 | // 顶部:从左到右 由浅到深 线性渐变 过渡自然柔和 无突兀色块 |
| 191 | 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 | 193 | color: #fff; |
| 194 | - padding: 240rpx 30rpx 80rpx; | |
| 194 | + padding: 220rpx 30rpx 200rpx; | |
| 195 | 195 | text-align: left; |
| 196 | - border-bottom-left-radius: 120rpx; | |
| 196 | + //border-bottom-left-radius: 120rpx; | |
| 197 | 197 | position: relative; |
| 198 | 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 | 202 | .welcome-text { |
| 202 | - font-size: 46rpx; | |
| 203 | + font-size: 23px; | |
| 203 | 204 | display: block; |
| 204 | 205 | margin-bottom: 10px; |
| 205 | 206 | font-weight: 500; |
| ... | ... | @@ -207,7 +208,7 @@ const handleLogin = async () => { |
| 207 | 208 | |
| 208 | 209 | .platform-name { |
| 209 | 210 | margin-bottom: 10px; |
| 210 | - font-size: 46rpx; | |
| 211 | + font-size: 23px; | |
| 211 | 212 | display: block; |
| 212 | 213 | opacity: 0.95; |
| 213 | 214 | } |
| ... | ... | @@ -215,46 +216,48 @@ const handleLogin = async () => { |
| 215 | 216 | |
| 216 | 217 | .login-form { |
| 217 | 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 | 223 | position: relative; |
| 223 | 224 | z-index: 10; |
| 224 | 225 | box-sizing: border-box; |
| 225 | 226 | |
| 226 | 227 | .login-title { |
| 227 | - font-size: 38rpx; | |
| 228 | + font-size: 19px; | |
| 228 | 229 | font-weight: 600; |
| 229 | 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 | 237 | :deep(.u-form-item) { |
| 237 | - margin-bottom: 20rpx; | |
| 238 | + margin-bottom: 10px; | |
| 238 | 239 | position: relative; |
| 239 | 240 | } |
| 240 | 241 | |
| 241 | -// 登录按钮:和顶部完全同款 从左到右 由浅到深 线性渐变 + 保留所有原尺寸 | |
| 242 | +// 登录按钮 | |
| 242 | 243 | .login-btn { |
| 243 | - margin-top: 40rpx; | |
| 244 | + margin-top: 20px; | |
| 244 | 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 | 257 | .copyright { |
| 255 | 258 | width: 100%; |
| 256 | 259 | text-align: center; |
| 257 | - font-size: 24rpx; | |
| 260 | + font-size: 12px; | |
| 258 | 261 | color: #999; |
| 259 | 262 | position: fixed; |
| 260 | 263 | bottom: 100px; | ... | ... |
pages/mine/index.vue
| 1 | 1 | <template> |
| 2 | 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 | 16 | </view> |
| 17 | + | |
| 33 | 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 | 43 | <view v-if="userStore.isLogin" class="logout-btn-wrap"> |
| 37 | 44 | <up-button |
| 38 | 45 | type="primary" |
| 39 | 46 | size="large" |
| 47 | + shape="circle" | |
| 40 | 48 | @click="confirmLogout" |
| 49 | + class="login-btn" | |
| 41 | 50 | > |
| 42 | 51 | 退出登录 |
| 43 | 52 | </up-button> |
| ... | ... | @@ -49,6 +58,7 @@ |
| 49 | 58 | import { computed } from 'vue' |
| 50 | 59 | import { useUserStore } from '@/pinia/user' |
| 51 | 60 | import { showModal, onShow } from '@dcloudio/uni-app' |
| 61 | +import {translateRoles} from '@/common/utils/common' | |
| 52 | 62 | // 初始化Pinia仓库 |
| 53 | 63 | const userStore = useUserStore() |
| 54 | 64 | |
| ... | ... | @@ -68,9 +78,7 @@ const handleLogin = () => { |
| 68 | 78 | * 确认退出登录(带二次确认) |
| 69 | 79 | */ |
| 70 | 80 | const confirmLogout = async () => { |
| 71 | - console.log('13213') | |
| 72 | 81 | try { |
| 73 | - console.log('434') | |
| 74 | 82 | const res = await uni.showModal({ |
| 75 | 83 | title: '提示', |
| 76 | 84 | content: '确定要退出登录吗?', |
| ... | ... | @@ -105,64 +113,64 @@ export default { |
| 105 | 113 | |
| 106 | 114 | <style lang="scss" scoped> |
| 107 | 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 | 160 | .logout-btn-wrap { |
| 166 | 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 | 176 | </style> |
| 169 | 177 | \ No newline at end of file | ... | ... |
pages/workbench/index.vue
| ... | ... | @@ -9,7 +9,11 @@ |
| 9 | 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 | 19 | <view class="content-wrap"> |
| ... | ... | @@ -59,7 +63,7 @@ |
| 59 | 63 | </template> |
| 60 | 64 | |
| 61 | 65 | <script setup lang="ts"> |
| 62 | -import { ref, computed } from 'vue'; // 新增 computed | |
| 66 | +import { ref, computed } from 'vue'; | |
| 63 | 67 | import { onShow } from '@dcloudio/uni-app'; |
| 64 | 68 | import { useUserStore } from '@/pinia/user'; |
| 65 | 69 | import globalConfig from '@/common/config/global'; |
| ... | ... | @@ -83,9 +87,12 @@ interface MenuItem { |
| 83 | 87 | children: MenuItem[]; |
| 84 | 88 | } |
| 85 | 89 | |
| 90 | +// ========== 全局变量定义区 ========== | |
| 86 | 91 | const loading = ref(true); |
| 87 | 92 | const userStore = useUserStore(); |
| 88 | 93 | const moduleList = ref<MenuItem[]>([]); |
| 94 | +// ✅ 修复2:声明全局的用户信息变量,模板可访问 + 初始化空对象兜底,杜绝undefined | |
| 95 | +const userInfo = ref<any>(cache.get(globalConfig.cache.userInfoKey) || userStore.userInfo || {}); | |
| 89 | 96 | |
| 90 | 97 | // 计算属性:过滤出有子节点的父模块(children 存在且长度 > 0) |
| 91 | 98 | const filteredModuleList = computed(() => { |
| ... | ... | @@ -98,36 +105,34 @@ const filteredModuleList = computed(() => { |
| 98 | 105 | onShow(async () => { |
| 99 | 106 | try { |
| 100 | 107 | loading.value = true; |
| 108 | + // ✅ 修复3:重新赋值全局userInfo,保证数据最新 | |
| 109 | + userInfo.value = cache.get(globalConfig.cache.userInfoKey) || userStore.userInfo || {}; | |
| 101 | 110 | |
| 102 | - // ========== 核心新增:登录状态判断 ========== | |
| 103 | - // 1. 定义登录判断逻辑(多维度校验,确保准确性) | |
| 111 | + // 登录状态判断(多维度校验,确保准确性) | |
| 104 | 112 | const isLogin = () => { |
| 105 | - | |
| 106 | 113 | // 从缓存获取token(核心登录标识) |
| 107 | 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 | 123 | if (!isLogin()) { |
| 116 | 124 | uni.showToast({ title: '请先登录', icon: 'none', duration: 1500 }); |
| 117 | 125 | // 延迟跳转,确保提示语正常显示 |
| 118 | 126 | setTimeout(() => { |
| 119 | - // 使用reLaunch跳转,清空页面栈,避免返回当前菜单页 | |
| 120 | 127 | uni.reLaunch({ |
| 121 | - url: '/pages/login/index', // 替换为你的实际登录页路径 | |
| 128 | + url: '/pages/login/index', | |
| 122 | 129 | fail: (err) => { |
| 123 | 130 | console.error('跳转登录页失败:', err); |
| 124 | 131 | uni.showToast({ title: '跳转登录页异常', icon: 'none' }); |
| 125 | 132 | } |
| 126 | 133 | }); |
| 127 | 134 | }, 1500); |
| 128 | - // 隐藏加载状态 | |
| 129 | 135 | loading.value = false; |
| 130 | - // 终止后续代码执行 | |
| 131 | 136 | return; |
| 132 | 137 | } |
| 133 | 138 | |
| ... | ... | @@ -169,15 +174,33 @@ const handleMenuClick = (item: MenuItem) => { |
| 169 | 174 | top: 0; |
| 170 | 175 | left: 0; |
| 171 | 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 | 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 | 200 | .content-wrap { |
| 178 | 201 | position: relative; |
| 179 | 202 | z-index: 2; |
| 180 | - padding: 120px 0 0; | |
| 203 | + padding: 150px 0 0; | |
| 181 | 204 | display: flex; |
| 182 | 205 | flex-direction: column; |
| 183 | 206 | gap: 20rpx; |
| ... | ... | @@ -197,6 +220,7 @@ const handleMenuClick = (item: MenuItem) => { |
| 197 | 220 | display: flex; |
| 198 | 221 | flex-wrap: wrap; |
| 199 | 222 | gap: 20rpx; |
| 223 | + //padding: 20rpx; // ✅ 优化:增加内边距,内容不贴边 | |
| 200 | 224 | } |
| 201 | 225 | |
| 202 | 226 | .content-block { | ... | ... |
static/imgs/bg.jpg
0 → 100644
42.2 KB
static/imgs/default-avatar.png