Commit 12e66ec8a6ef2cc1a4d1ccb40a3643c9b1df2701

Authored by 刘淇
1 parent 727bc60d

新增树

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

42.2 KB

static/imgs/default-avatar.png

30.1 KB | W: | H:

3.93 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin