Commit cf70629b5ba9167a51c7e2599a877a6a37985f83

Authored by 刘淇
1 parent 4570c70a

养护计划 照片 自己写样式

api/index/index.js 0 → 100644
  1 +
  2 +import { post, get } from '@/common/utils/request';
  3 +
  4 +
  5 +/**
  6 + * 任务完成情况统计
  7 + * @param {Object} params {mobile, password, code}
  8 + * @returns {Promise}
  9 + */
  10 +export const getTaskCompletionSummary = (params) => {
  11 + return post('/app-api/homePage/summary/taskCompletionSummary', params);
  12 +};
  13 +
  14 +
  15 +/**
  16 + * 任务待办已办
  17 + * @param {Object} params {mobile, password, code}
  18 + * @returns {Promise}
  19 + */
  20 +export const getTaskDetails = (params) => {
  21 + return post('/app-api/homePage/summary/taskDetails', params);
  22 +};
  23 +
components/uni-charts/uni-charts.vue
@@ -88,7 +88,7 @@ export default { @@ -88,7 +88,7 @@ export default {
88 this.drawLine() 88 this.drawLine()
89 } 89 }
90 }, 90 },
91 - // 折线图绘制(终极版:Y轴整数 + 自适应极值 + 修复文字折叠 + 优化图例间距 91 + // 折线图绘制(移除平滑过渡,改为纯直线
92 drawLine () { 92 drawLine () {
93 if (!this.data.length || !this.categories.length) return; 93 if (!this.data.length || !this.categories.length) return;
94 94
@@ -100,8 +100,7 @@ export default { @@ -100,8 +100,7 @@ export default {
100 xAxis = {}, 100 xAxis = {},
101 yAxis = {}, 101 yAxis = {},
102 legend = {}, 102 legend = {},
103 - color = ['#25AF69', '#B34C17'],  
104 - lineSmooth = true 103 + color = ['#25AF69', '#B34C17']
105 } = this.option 104 } = this.option
106 105
107 // 清空画布 106 // 清空画布
@@ -154,7 +153,7 @@ export default { @@ -154,7 +153,7 @@ export default {
154 ctx.lineTo(width - gridRight, y) 153 ctx.lineTo(width - gridRight, y)
155 ctx.stroke() 154 ctx.stroke()
156 155
157 - // ========== 核心修改:Y轴显示整数(无小数点) ========== 156 + // Y轴显示整数(无小数点)
158 ctx.setFillStyle(yAxis.axisLabel?.color || '#666') 157 ctx.setFillStyle(yAxis.axisLabel?.color || '#666')
159 ctx.setFontSize(yAxis.axisLabel?.fontSize || 12) 158 ctx.setFontSize(yAxis.axisLabel?.fontSize || 12)
160 const val = minVal + (i * yTickStep) 159 const val = minVal + (i * yTickStep)
@@ -211,7 +210,7 @@ export default { @@ -211,7 +210,7 @@ export default {
211 }) 210 })
212 } 211 }
213 212
214 - // 绘制平滑折线(无数据点) 213 + // ========== 核心修改:移除平滑曲线,改为纯直线连接 ==========
215 this.data.forEach((series, seriesIdx) => { 214 this.data.forEach((series, seriesIdx) => {
216 const seriesColor = series.color || color[seriesIdx % color.length] 215 const seriesColor = series.color || color[seriesIdx % color.length]
217 216
@@ -226,21 +225,8 @@ export default { @@ -226,21 +225,8 @@ export default {
226 if (index === 0) { 225 if (index === 0) {
227 ctx.moveTo(x, y) 226 ctx.moveTo(x, y)
228 } else { 227 } else {
229 - if (lineSmooth && index < series.data.length - 1) {  
230 - const prevX = gridLeft + (index - 1) * xStep  
231 - const prevY = gridTop + drawHeight - ((series.data[index - 1] - minVal) / valRange) * drawHeight  
232 - const nextX = gridLeft + (index + 1) * xStep  
233 - const nextY = gridTop + drawHeight - ((series.data[index + 1] - minVal) / valRange) * drawHeight  
234 -  
235 - const cp1x = (prevX + x) / 2  
236 - const cp1y = prevY  
237 - const cp2x = (x + nextX) / 2  
238 - const cp2y = y  
239 -  
240 - ctx.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y)  
241 - } else {  
242 - ctx.lineTo(x, y)  
243 - } 228 + // 直接直线连接,移除所有贝塞尔曲线平滑逻辑
  229 + ctx.lineTo(x, y)
244 } 230 }
245 }) 231 })
246 232
@@ -249,7 +235,7 @@ export default { @@ -249,7 +235,7 @@ export default {
249 235
250 ctx.draw() 236 ctx.draw()
251 }, 237 },
252 - // K线图绘制(同步修改Y轴为整数 238 + // K线图绘制(同步修改Y轴为整数,K线本身无平滑逻辑,保持原样
253 drawKline () { 239 drawKline () {
254 const ctx = uni.createCanvasContext('uni-charts', this) 240 const ctx = uni.createCanvasContext('uni-charts', this)
255 this.ctx = ctx 241 this.ctx = ctx
@@ -322,7 +308,7 @@ export default { @@ -322,7 +308,7 @@ export default {
322 } 308 }
323 }) 309 })
324 310
325 - // 绘制K线 311 + // 绘制K线(K线本身就是直线,无平滑逻辑)
326 this.data.forEach((item, index) => { 312 this.data.forEach((item, index) => {
327 const { open, high, low, close } = item 313 const { open, high, low, close } = item
328 const x = gridLeft + index * xStep + xStep / 2 314 const x = gridLeft + index * xStep + xStep / 2
@@ -331,7 +317,7 @@ export default { @@ -331,7 +317,7 @@ export default {
331 const yHigh = gridTop + drawHeight - ((high - minVal) / valRange) * drawHeight 317 const yHigh = gridTop + drawHeight - ((high - minVal) / valRange) * drawHeight
332 const yLow = gridTop + drawHeight - ((low - minVal) / valRange) * drawHeight 318 const yLow = gridTop + drawHeight - ((low - minVal) / valRange) * drawHeight
333 319
334 - // 绘制高低线 320 + // 绘制高低线(直线)
335 ctx.setStrokeStyle(close >= open ? color[0] : color[1]) 321 ctx.setStrokeStyle(close >= open ? color[0] : color[1])
336 ctx.setLineWidth(1) 322 ctx.setLineWidth(1)
337 ctx.beginPath() 323 ctx.beginPath()
pages-sub/daily/maintain-manage/finish-plan-detail.vue
@@ -9,22 +9,15 @@ @@ -9,22 +9,15 @@
9 ></up-loading-page> 9 ></up-loading-page>
10 10
11 <!-- 内容容器 --> 11 <!-- 内容容器 -->
12 - <template v-else >  
13 - <view class="content-wrap" v-for="(i, index) in orderDetail" :key="index">  
14 - 12 + <template v-else>
  13 + <view class="content-wrap" v-for="(i, index) in orderDetail" :key="index">
15 <!-- 工单详情内容 --> 14 <!-- 工单详情内容 -->
16 <up-cell-group :border="false" inset> 15 <up-cell-group :border="false" inset>
17 <!-- 1. 工单计划名称 --> 16 <!-- 1. 工单计划名称 -->
18 - <up-cell  
19 - align="middle"  
20 - >  
21 - 17 + <up-cell align="middle">
22 <template #title> 18 <template #title>
23 - <view class="up-line-1">{{i.planName || '--'}}</view> 19 + <view class="up-line-1">{{i.planName || '--'}}</view>
24 </template> 20 </template>
25 -<!-- <template #value>-->  
26 -<!-- <view class="up-line-1">{{i.remark || '&#45;&#45;'}}</view>-->  
27 -<!-- </template>-->  
28 </up-cell> 21 </up-cell>
29 22
30 <!-- 2. 工单位置 --> 23 <!-- 2. 工单位置 -->
@@ -44,34 +37,42 @@ @@ -44,34 +37,42 @@
44 <!-- 4. 情况描述 --> 37 <!-- 4. 情况描述 -->
45 <up-cell 38 <up-cell
46 title="计划有效期" 39 title="计划有效期"
47 - :value="`${timeFormat(i.beginTime,'yyyy-mm-dd')} 至 ${timeFormat(i. endTime,'yyyy-mm-dd')}`" 40 + :value="`${timeFormat(i.beginTime,'yyyy-mm-dd')} 至 ${timeFormat(i.endTime,'yyyy-mm-dd')}`"
48 align="middle" 41 align="middle"
49 ></up-cell> 42 ></up-cell>
50 43
51 - <!-- 5. 问题照片(核心修复:判断条件+空值处理) --> 44 + <!-- 5. 问题照片(核心修复:兼容WebP + 强制尺寸) -->
52 <up-cell title="照片"> 45 <up-cell title="照片">
53 <template #value> 46 <template #value>
54 <view class="cell-content-wrap"> 47 <view class="cell-content-wrap">
55 - <up-album  
56 - v-if="!!i.beginImgList?.length"  
57 - :urls="i.beginImgList || []"  
58 - singleSize="70"  
59 - :preview-full-image="true"  
60 - ></up-album> 48 + <!-- 修复1:手动渲染图片,兼容WebP + 强制尺寸 -->
  49 + <view v-if="!!i.beginImgList?.length" class="album-container">
  50 + <view
  51 + class="album-item"
  52 + v-for="(imgUrl, imgIndex) in formatImageUrls(i.beginImgList)"
  53 + :key="imgIndex"
  54 + @click="previewImage(imgUrl, formatImageUrls(i.beginImgList))"
  55 + >
  56 + <image
  57 + :src="imgUrl"
  58 + mode="widthFix"
  59 + class="album-image"
  60 + :style="{width: '70px', height: '70px', objectFit: 'cover'}"
  61 + ></image>
  62 + </view>
  63 + </view>
61 <text v-else class="empty-text">暂无问题照片</text> 64 <text v-else class="empty-text">暂无问题照片</text>
62 </view> 65 </view>
63 </template> 66 </template>
64 </up-cell> 67 </up-cell>
65 68
66 <!-- 7. 处理结果 --> 69 <!-- 7. 处理结果 -->
67 - <up-cell  
68 - align="middle"  
69 - > 70 + <up-cell align="middle">
70 <template #title> 71 <template #title>
71 - <view style="min-width: 200rpx">巡查描述</view> 72 + <view style="min-width: 200rpx">巡查描述</view>
72 </template> 73 </template>
73 <template #value> 74 <template #value>
74 - <view class="up-line-1 common-text-color" >{{i.remark || '--'}}</view> 75 + <view class="up-line-1 common-text-color">{{i.remark || '--'}}</view>
75 </template> 76 </template>
76 </up-cell> 77 </up-cell>
77 78
@@ -81,7 +82,6 @@ @@ -81,7 +82,6 @@
81 align="middle" 82 align="middle"
82 ></up-cell> 83 ></up-cell>
83 84
84 -  
85 <up-cell 85 <up-cell
86 title="提交人" 86 title="提交人"
87 :value="i.userName || '--'" 87 :value="i.userName || '--'"
@@ -89,7 +89,7 @@ @@ -89,7 +89,7 @@
89 :border="false" 89 :border="false"
90 ></up-cell> 90 ></up-cell>
91 </up-cell-group> 91 </up-cell-group>
92 - </view> 92 + </view>
93 </template> 93 </template>
94 </view> 94 </view>
95 </template> 95 </template>
@@ -99,10 +99,44 @@ import {ref} from &#39;vue&#39;; @@ -99,10 +99,44 @@ import {ref} from &#39;vue&#39;;
99 import {detailList} from "@/api/maintain-manage/maintain-manage"; 99 import {detailList} from "@/api/maintain-manage/maintain-manage";
100 import {onLoad} from '@dcloudio/uni-app'; 100 import {onLoad} from '@dcloudio/uni-app';
101 import {timeFormat} from '@/uni_modules/uview-plus'; 101 import {timeFormat} from '@/uni_modules/uview-plus';
  102 +
102 // 状态管理 103 // 状态管理
103 const loading = ref(true); 104 const loading = ref(true);
104 const orderDetail = ref([]); 105 const orderDetail = ref([]);
105 106
  107 +/**
  108 + * 格式化图片URL(兼容WebP + 清理非法字符)
  109 + * @param urls 原始图片列表
  110 + */
  111 +const formatImageUrls = (urls) => {
  112 + if (!Array.isArray(urls) || urls.length === 0) return [];
  113 +
  114 + return urls.map(url => {
  115 + if (typeof url !== 'string') return '';
  116 + // 1. 清理URL开头的非法字符(如数字、空格)
  117 + const validStart = url.indexOf('http');
  118 + const cleanUrl = validStart > 0 ? url.substring(validStart) : url;
  119 + // 2. 小程序兼容:WebP转JPEG(可选,后端无转换则注释)
  120 + // return cleanUrl.replace('.webp', '.jpeg');
  121 + return cleanUrl;
  122 + }).filter(url => !!url);
  123 +};
  124 +
  125 +/**
  126 + * 预览图片
  127 + * @param currentUrl 当前点击的图片
  128 + * @param allUrls 所有图片列表
  129 + */
  130 +const previewImage = (currentUrl, allUrls) => {
  131 + uni.previewImage({
  132 + current: currentUrl,
  133 + urls: allUrls,
  134 + fail: (err) => {
  135 + console.error('预览图片失败:', err);
  136 + uni.showToast({title: '预览图片失败', icon: 'none'});
  137 + }
  138 + });
  139 +};
106 140
107 /** 141 /**
108 * 获取工单详情 142 * 获取工单详情
@@ -111,14 +145,18 @@ const getOrderDetail = async (planNo: string) =&gt; { @@ -111,14 +145,18 @@ const getOrderDetail = async (planNo: string) =&gt; {
111 try { 145 try {
112 loading.value = true; 146 loading.value = true;
113 let queryData = { 147 let queryData = {
114 - planNo:planNo,  
115 - pageSize:100,  
116 - pageNo:1,  
117 - } 148 + planNo: planNo,
  149 + pageSize: 100,
  150 + pageNo: 1,
  151 + };
118 const res = await detailList(queryData); 152 const res = await detailList(queryData);
119 console.log('接口返回:', res); 153 console.log('接口返回:', res);
120 154
121 - orderDetail.value = res.list; 155 + // 修复2:正确解析接口数据(res.data.list 而非 res.list)
  156 + orderDetail.value = res.data?.list || res.list || [];
  157 +
  158 + // 调试:打印格式化后的图片URL
  159 + console.log('格式化后图片URL:', formatImageUrls(orderDetail.value[0]?.beginImgList || []));
122 } catch (error) { 160 } catch (error) {
123 console.error('获取工单详情失败:', error); 161 console.error('获取工单详情失败:', error);
124 uni.showToast({title: '加载失败,请重试', icon: 'none'}); 162 uni.showToast({title: '加载失败,请重试', icon: 'none'});
@@ -140,7 +178,6 @@ onLoad((options) =&gt; { @@ -140,7 +178,6 @@ onLoad((options) =&gt; {
140 </script> 178 </script>
141 179
142 <style scoped lang="scss"> 180 <style scoped lang="scss">
143 -  
144 // 内容容器 181 // 内容容器
145 .content-wrap { 182 .content-wrap {
146 background: #fff; 183 background: #fff;
@@ -149,5 +186,37 @@ onLoad((options) =&gt; { @@ -149,5 +186,37 @@ onLoad((options) =&gt; {
149 margin-bottom: 30rpx; 186 margin-bottom: 30rpx;
150 } 187 }
151 188
  189 +// 图片容器样式
  190 +.cell-content-wrap {
  191 + padding: 10rpx 0;
  192 +}
  193 +
  194 +// 自定义相册容器
  195 +.album-container {
  196 + display: flex;
  197 + flex-wrap: wrap;
  198 + gap: 10rpx; // 图片间距
  199 +}
  200 +
  201 +// 单张图片样式
  202 +.album-item {
  203 + width: 70px;
  204 + height: 70px;
  205 + border-radius: 4rpx;
  206 + overflow: hidden;
  207 +}
152 208
  209 +// 图片样式(强制尺寸 + 覆盖)
  210 +.album-image {
  211 + width: 100%;
  212 + height: 100%;
  213 + object-fit: cover; // 保持比例裁剪,避免拉伸
  214 +}
  215 +
  216 +// 空文本样式
  217 +.empty-text {
  218 + color: #999;
  219 + font-size: 14px;
  220 + line-height: 70px; // 和图片高度对齐
  221 +}
153 </style> 222 </style>
154 \ No newline at end of file 223 \ No newline at end of file
pages-sub/data/tree-archive/addTree.vue
@@ -453,7 +453,7 @@ const submit = async () =&gt; { @@ -453,7 +453,7 @@ const submit = async () =&gt; {
453 453
454 <style scoped lang="scss"> 454 <style scoped lang="scss">
455 .container { 455 .container {
456 - padding: 25rpx; 456 + padding: 15px;
457 box-sizing: border-box; 457 box-sizing: border-box;
458 background: #fff; 458 background: #fff;
459 } 459 }
pages-sub/data/tree-archive/editTree.vue
@@ -193,16 +193,16 @@ @@ -193,16 +193,16 @@
193 <view class="card-body-inner"> 193 <view class="card-body-inner">
194 <view class="record-list-left" :style="`background-image: url(${item.treephotoone});`"></view> 194 <view class="record-list-left" :style="`background-image: url(${item.treephotoone});`"></view>
195 <view class="record-list-right"> 195 <view class="record-list-right">
196 - <view class="record-list-right-title"> 196 + <view class="up-flex up-flex-items-center up-flex-between">
197 <view class="u-line-1 treetypeName">{{ item.treetype }}</view> 197 <view class="u-line-1 treetypeName">{{ item.treetype }}</view>
198 <view style="text-align: right">{{ timeFormat(item.updatetime) }}</view> 198 <view style="text-align: right">{{ timeFormat(item.updatetime) }}</view>
199 </view> 199 </view>
200 - <view class=" fs-align__center" style="margin: 5px 0">  
201 - <img src="../../../static/imgs/tree/tree-high.png" style="width:14px;height:14px;margin-right:6px;" 200 + <view class=" fs-align__center" style="margin: 8px 0">
  201 + <img src="../../../static/imgs/tree/tree-high.png" style="width:12px;height:12px;margin-right:6px;"
202 alt=""> 高度:{{ item.treeheight }} 米 202 alt=""> 高度:{{ item.treeheight }} 米
203 </view> 203 </view>
204 <view class=" fs-align__center"> 204 <view class=" fs-align__center">
205 - <img src="../../../static/imgs/tree/treearound.png" style="width:14px;height:14px;margin-right:6px;" 205 + <img src="../../../static/imgs/tree/treearound.png" style="width:12px;height:12px;margin-right:6px;"
206 alt="">胸径:{{ item.dbh }} 厘米 206 alt="">胸径:{{ item.dbh }} 厘米
207 </view> 207 </view>
208 </view> 208 </view>
@@ -601,7 +601,8 @@ const toLogDetailPage = (i) =&gt; { @@ -601,7 +601,8 @@ const toLogDetailPage = (i) =&gt; {
601 } 601 }
602 602
603 .base-info-wrap { 603 .base-info-wrap {
604 - padding: 20rpx 15rpx; 604 + padding: 15px 15px;
  605 + background: #fff;
605 } 606 }
606 607
607 .log-wrap { 608 .log-wrap {
@@ -614,7 +615,7 @@ const toLogDetailPage = (i) =&gt; { @@ -614,7 +615,7 @@ const toLogDetailPage = (i) =&gt; {
614 615
615 .treetypeName { 616 .treetypeName {
616 flex: 1; 617 flex: 1;
617 - font-size: 16px; 618 + font-size: 14px;
618 font-weight: bold; 619 font-weight: bold;
619 } 620 }
620 621
@@ -632,10 +633,6 @@ const toLogDetailPage = (i) =&gt; { @@ -632,10 +633,6 @@ const toLogDetailPage = (i) =&gt; {
632 overflow: hidden; 633 overflow: hidden;
633 } 634 }
634 635
635 -.record-list-right-title {  
636 - display: flex;  
637 - justify-content: space-between;  
638 -}  
639 636
640 .treenumber-no { 637 .treenumber-no {
641 padding: 3px 10px; 638 padding: 3px 10px;
@@ -646,6 +643,7 @@ const toLogDetailPage = (i) =&gt; { @@ -646,6 +643,7 @@ const toLogDetailPage = (i) =&gt; {
646 643
647 .card-body-inner { 644 .card-body-inner {
648 display: flex; 645 display: flex;
  646 + font-size: 12px;
649 } 647 }
650 648
651 .fs-align__center { 649 .fs-align__center {
pages-sub/data/tree-archive/treeRecord.vue
@@ -20,15 +20,15 @@ @@ -20,15 +20,15 @@
20 <view class="record-list-left" :style="`background-image: url(${i.treephoto});`"></view> 20 <view class="record-list-left" :style="`background-image: url(${i.treephoto});`"></view>
21 21
22 <view class="record-list-right"> 22 <view class="record-list-right">
23 - <view class="record-list-right-title"> 23 + <view class="up-flex up-flex-items-center up-flex-between">
24 <view class="u-line-1 treetypeName">{{ i.treetype }}</view> 24 <view class="u-line-1 treetypeName">{{ i.treetype }}</view>
25 <view style="text-align: right">{{ timeFormat(i.updatetime) }}</view> 25 <view style="text-align: right">{{ timeFormat(i.updatetime) }}</view>
26 </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 }} 米 27 + <view class="up-flex up-flex-items-center" style="margin: 8px 0">
  28 + <img src="../../../static/imgs/tree/tree-high.png" style="width: 12px;height: 12px;margin-right: 6px;" alt=""> 高度:{{ i.treeheight }} 米
29 </view> 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 }} 厘米 30 + <view class="up-flex up-flex-items-center">
  31 + <img src="../../../static/imgs/tree/treearound.png" style="width: 12px;height: 12px;margin-right: 6px;" alt="">胸径:{{ i.dbh }} 厘米
32 </view> 32 </view>
33 </view> 33 </view>
34 </view> 34 </view>
@@ -124,7 +124,7 @@ const treeRoadQuery = async () =&gt; { @@ -124,7 +124,7 @@ const treeRoadQuery = async () =&gt; {
124 124
125 .treetypeName { 125 .treetypeName {
126 flex: 1; 126 flex: 1;
127 - font-size: 16px; 127 + font-size: 14px;
128 font-weight: bold; 128 font-weight: bold;
129 } 129 }
130 130
@@ -142,10 +142,6 @@ const treeRoadQuery = async () =&gt; { @@ -142,10 +142,6 @@ const treeRoadQuery = async () =&gt; {
142 overflow: hidden; 142 overflow: hidden;
143 } 143 }
144 144
145 -.record-list-right-title {  
146 - display: flex;  
147 - justify-content: space-between;  
148 -}  
149 145
150 .treenumber-no { 146 .treenumber-no {
151 margin-top: 5px; 147 margin-top: 5px;
@@ -165,5 +161,6 @@ const treeRoadQuery = async () =&gt; { @@ -165,5 +161,6 @@ const treeRoadQuery = async () =&gt; {
165 } 161 }
166 .card-body-inner{ 162 .card-body-inner{
167 display: flex; 163 display: flex;
  164 + font-size: 12px;
168 } 165 }
169 </style> 166 </style>
170 \ No newline at end of file 167 \ No newline at end of file
pages/index/index.vue
1 <template> 1 <template>
2 - <view class="home-page"> 2 + <view class="page-container">
3 <!-- 用户信息栏 --> 3 <!-- 用户信息栏 -->
4 <view class="user-info-bar"> 4 <view class="user-info-bar">
5 <view class="user-info"> 5 <view class="user-info">
@@ -9,7 +9,7 @@ @@ -9,7 +9,7 @@
9 </view> 9 </view>
10 </view> 10 </view>
11 <view class="msg-icon" @click="handleMsgClick" hover-class="msg-icon--hover"> 11 <view class="msg-icon" @click="handleMsgClick" hover-class="msg-icon--hover">
12 - <up-icon name="chat" color="#fff" size="24" /> 12 + <up-icon name="chat" color="#fff" size="24"/>
13 <view class="msg-badge" v-if="msgCount > 0"> 13 <view class="msg-badge" v-if="msgCount > 0">
14 <up-badge type="error" max="999" :value="msgCount"></up-badge> 14 <up-badge type="error" max="999" :value="msgCount"></up-badge>
15 </view> 15 </view>
@@ -24,13 +24,14 @@ @@ -24,13 +24,14 @@
24 <view class="task-chart-card"> 24 <view class="task-chart-card">
25 <view class="card-header"> 25 <view class="card-header">
26 <view class="unit-tip">单位: 个</view> 26 <view class="unit-tip">单位: 个</view>
27 - <view class="date-picker-wrap" @click="openDatePicker"> 27 + <view class="date-picker-wrap">
28 <neo-datetime-pro 28 <neo-datetime-pro
29 v-model="dateRange" 29 v-model="dateRange"
30 type="daterange" 30 type="daterange"
31 :clearable="false" 31 :clearable="false"
32 placeholder="请选择日期范围" 32 placeholder="请选择日期范围"
33 @confirm="handleDateConfirm" 33 @confirm="handleDateConfirm"
  34 + :max-date="maxDate"
34 /> 35 />
35 </view> 36 </view>
36 </view> 37 </view>
@@ -48,74 +49,85 @@ @@ -48,74 +49,85 @@
48 </view> 49 </view>
49 </view> 50 </view>
50 51
51 - <!-- 待办/已办切换栏 -->  
52 - <view class="tab-switch-bar">  
53 - <view  
54 - class="tab-item"  
55 - :class="{ active: currentTab === 'todo' }"  
56 - @click="switchTab('todo')"  
57 - hover-class="tab-item--hover"  
58 - >  
59 - 待办事项({{ todoList.length }})  
60 - <view class="tab-active-line" v-if="currentTab === 'todo'"></view>  
61 - </view>  
62 - <view  
63 - class="tab-item"  
64 - :class="{ active: currentTab === 'done' }"  
65 - @click="switchTab('done')"  
66 - hover-class="tab-item--hover"  
67 - >  
68 - 已办事项({{ doneList.length }})  
69 - <view class="tab-active-line" v-if="currentTab === 'done'"></view>  
70 - </view>  
71 - </view>  
72 -  
73 - <!-- 事项列表 -->  
74 - <view class="task-list-container">  
75 - <up-empty v-if="!currentTaskList.length" text="暂无相关事项" />  
76 - <view  
77 - class="task-item"  
78 - v-for="(item, index) in currentTaskList"  
79 - :key="index"  
80 - @click="handleTaskClick(item)"  
81 - hover-class="task-item--hover"  
82 - >  
83 - <view class="task-name">  
84 - {{ item.name }}  
85 - </view>  
86 - <view class="task-meta">  
87 - <view class="urgency-tag" :class="`urgency-tag--${getUrgencyType(item.urgency)}`">  
88 - {{ item.urgency || '普通' }} 52 + <!-- 使用up-tabs组件实现Tab切换 -->
  53 + <up-tabs
  54 + :list="tabList"
  55 + active-color="#0A86F4"
  56 + inactive-color="#666"
  57 + font-size="14px"
  58 + line-width="40"
  59 + line-height="3"
  60 + @change="handleTabChange"
  61 + class="task-tab-container"
  62 + scrollable="false"
  63 + ></up-tabs>
  64 +
  65 + <!-- z-paging分页列表 -->
  66 + <z-paging
  67 + ref="paging"
  68 + v-model="currentTaskList"
  69 + @query="queryList"
  70 + :auto-show-system-loading="true"
  71 + :page-size="10"
  72 + use-page-scroll
  73 + >
  74 + <template #empty>
  75 + <up-empty />
  76 + </template>
  77 + <view class="task-list-container">
  78 + <view
  79 + class="task-item"
  80 + v-for="(item,index) in currentTaskList"
  81 + :key="`${item.taskName}_${index}`"
  82 + hover-class="task-item--hover"
  83 + @click="handleTaskClick(item)"
  84 + >
  85 + <view class="task-item__content">
  86 + <view class="task-item__name u-line-1">
  87 + {{ item.taskName || '无' }}
  88 + </view>
  89 + <view class="task-item__footer u-flex common-item-center common-justify-between"
  90 + style="font-size: 13px; margin-top: 5px;">
  91 + <view class="urgency-tag" >
  92 + 紧急程度: {{ item.pressingType }}
  93 + </view>
  94 + <view style="font-size: 13px;color: #333">{{
  95 + timeFormat(item.busiDateTime, 'yyyy-mm-dd hh:MM:ss')
  96 + }}
  97 + </view>
  98 + </view>
89 </view> 99 </view>
90 - <view class="task-time">{{ timeFormat(item.time) }}</view>  
91 </view> 100 </view>
92 </view> 101 </view>
93 - </view> 102 + </z-paging>
94 </view> 103 </view>
95 </view> 104 </view>
96 </template> 105 </template>
97 106
98 -<script setup>  
99 -import { ref, watch, computed } from 'vue';  
100 -import { onShow } from '@dcloudio/uni-app'  
101 -import { useUserStore } from '@/pinia/user';  
102 -import { timeFormat } from '@/uni_modules/uview-plus'; 107 +<script setup lang="ts">
  108 +import {ref, watch, computed, reactive} from 'vue';
  109 +import {onShow} from '@dcloudio/uni-app'
  110 +import {useUserStore} from '@/pinia/user';
  111 +import {timeFormat} from '@/uni_modules/uview-plus';
103 import uniCharts from '@/components/uni-charts/uni-charts.vue'; 112 import uniCharts from '@/components/uni-charts/uni-charts.vue';
104 -import { getUnreadCount } from "@/api/user";  
105 -  
106 -// ========== 1. 常量抽离 ==========  
107 -const URGENCY_MAP = {  
108 - 特急: 'urgent',  
109 - 紧急: 'high',  
110 - 一般: 'normal',  
111 - 普通: 'normal',  
112 - '--': 'normal'  
113 -}; 113 +import {getUnreadCount} from "@/api/user";
  114 +import {getTaskCompletionSummary, getTaskDetails} from "@/api/index/index";
  115 +
114 116
115 // ========== 2. 响应式数据 ========== 117 // ========== 2. 响应式数据 ==========
116 -const msgCount = ref();  
117 -const currentTab = ref('todo'); 118 +const msgCount = ref(0);
  119 +const currentTab = ref('todo'); // todo:待办 done:已办
118 const dateRange = ref([]); 120 const dateRange = ref([]);
  121 +const maxDate = ref(timeFormat(new Date(), 'yyyy-mm-dd'));
  122 +
  123 +// Tab列表配置
  124 +const tabList = ref([]);
  125 +const todoTotal = ref(0); // 待办事项总数
  126 +const doneTotal = ref(0); // 已办事项总数
  127 +
  128 +// 分页相关
  129 +const paging = ref(null);
  130 +const currentTaskList = ref([]); // 当前显示的任务列表(分页数据)
119 131
120 // 折线图数据配置 132 // 折线图数据配置
121 const chartWidth = ref(0); 133 const chartWidth = ref(0);
@@ -143,12 +155,12 @@ const klineOption = ref({ @@ -143,12 +155,12 @@ const klineOption = ref({
143 bottom: '20%' 155 bottom: '20%'
144 }, 156 },
145 xAxis: { 157 xAxis: {
146 - axisLabel: { fontSize: 12, color: '#666' } 158 + axisLabel: {fontSize: 12, color: '#666'}
147 }, 159 },
148 yAxis: { 160 yAxis: {
149 min: 0, 161 min: 0,
150 - splitLine: { lineStyle: { color: '#f5f5f7' } },  
151 - axisLabel: { fontSize: 12, color: '#666' } 162 + splitLine: {lineStyle: {color: '#f5f5f7'}},
  163 + axisLabel: {fontSize: 12, color: '#666'}
152 }, 164 },
153 tooltip: { 165 tooltip: {
154 trigger: 'axis', 166 trigger: 'axis',
@@ -161,100 +173,178 @@ const klineOption = ref({ @@ -161,100 +173,178 @@ const klineOption = ref({
161 legend: { 173 legend: {
162 show: true, 174 show: true,
163 top: '5%', 175 top: '5%',
164 - textStyle: { fontSize: 12 } 176 + textStyle: {fontSize: 12}
165 } 177 }
166 }); 178 });
167 179
168 -// 任务列表数据  
169 -const todoList = ref([  
170 - { name: '绿地卫生验收', urgency: '特急', time: '2025-12-31 15:45:23' },  
171 - { name: '阜成门内大街年度计划巡检', urgency: '--', time: '2025-12-31 09:02:00' },  
172 - { name: '金融街年度计划巡检', urgency: '--', time: '2025-12-31 09:02:00' },  
173 - { name: '示例事项', urgency: '一般', time: '2026-01-04 10:00:00' }  
174 -]);  
175 -const doneList = ref([  
176 - { name: '道路清洁验收', urgency: '普通', time: '2025-12-30 14:20:00' }  
177 -]);  
178 -  
179 // ========== 3. 计算属性 ========== 180 // ========== 3. 计算属性 ==========
180 const userStore = useUserStore(); 181 const userStore = useUserStore();
181 const userName = computed(() => userStore.userInfo?.user?.nickname || '用户'); 182 const userName = computed(() => userStore.userInfo?.user?.nickname || '用户');
182 -const currentTaskList = computed(() => {  
183 - return currentTab.value === 'todo' ? todoList.value : doneList.value;  
184 -});  
185 183
186 // ========== 4. 工具函数 ========== 184 // ========== 4. 工具函数 ==========
187 const rpx2px = (rpx) => { 185 const rpx2px = (rpx) => {
188 - const systemInfo = wx.getSystemInfoSync(); 186 + const systemInfo = uni.getSystemInfoSync();
189 return Math.floor(rpx * (systemInfo.screenWidth / 750)); 187 return Math.floor(rpx * (systemInfo.screenWidth / 750));
190 }; 188 };
191 189
192 -const getUrgencyType = (urgency) => {  
193 - return URGENCY_MAP[urgency] || 'normal';  
194 -};  
195 190
196 -const initRecent7Days = () => { 191 +const initRecent7Days = async () => {
197 const now = new Date(); 192 const now = new Date();
198 const sevenDaysAgo = new Date(); 193 const sevenDaysAgo = new Date();
199 sevenDaysAgo.setDate(now.getDate() - 6); 194 sevenDaysAgo.setDate(now.getDate() - 6);
200 - dateRange.value = [sevenDaysAgo, now]; 195 + // 格式化日期为 YYYY-MM-DD 格式
  196 + dateRange.value = [
  197 + timeFormat(sevenDaysAgo, 'yyyy-mm-dd'),
  198 + timeFormat(now, 'yyyy-mm-dd')
  199 + ];
  200 +
  201 + initChartData(timeFormat(sevenDaysAgo, 'yyyy-mm-dd'), timeFormat(now, 'yyyy-mm-dd'));
201 }; 202 };
202 203
203 -const getUnread = async ()=>{  
204 - const res = await getUnreadCount()  
205 - console.log(res)  
206 - msgCount.value = res  
207 -} 204 +const getUnread = async () => {
  205 + try {
  206 + const res = await getUnreadCount();
  207 + msgCount.value = res || 0;
  208 + } catch (error) {
  209 + console.error('获取未读消息数失败:', error);
  210 + msgCount.value = 0;
  211 + }
  212 +};
208 213
209 /** 214 /**
210 - * 初始化折线图数据 215 + * 初始化图表数据
  216 + * @param {string} beginTime 开始时间
  217 + * @param {string} endTime 结束时间
211 */ 218 */
212 -const fetchKlineData = () => {  
213 - // 模拟数据  
214 - const rawData = [  
215 - { date: '12.21', done: 10, total: 110 },  
216 - { date: '12.22', done: 35, total: 70 },  
217 - { date: '12.23', done: 55, total: 728 },  
218 - { date: '12.24', done: 35, total: 65 },  
219 - { date: '12.25', done: 65, total: 78 },  
220 - { date: '12.26', done: 50, total: 272 },  
221 - { date: '12.27', done: 72, total: 92 }  
222 - ]; 219 +const initChartData = async (beginTime, endTime) => {
  220 + try {
  221 + let postData = {
  222 + beginTime: beginTime,
  223 + endTime: endTime
  224 + };
  225 + const res = await getTaskCompletionSummary(postData);
  226 + console.log('图表数据:', res);
  227 + klineCategories.value = res.map(item => item.currentDate);
  228 + klineChartData.value[0].data = res.map(item => item.completedNum);
  229 + klineChartData.value[1].data = res.map(item => item.countpendingNum);
  230 + } catch (error) {
  231 + console.error('获取图表数据失败:', error);
  232 + }
  233 +};
223 234
224 - // 转换为图表格式  
225 - klineCategories.value = rawData.map(item => item.date);  
226 - klineChartData.value[0].data = rawData.map(item => item.done);  
227 - klineChartData.value[1].data = rawData.map(item => item.total); 235 +/**
  236 + * 获取所有Tab的总数(初始化时调用)
  237 + */
  238 +const getAllTabTotal = async () => {
  239 + try {
  240 + // 同时请求待办和已办的总数(只请求第1页,每页1条,仅获取total)
  241 + const [todoRes, doneRes] = await Promise.all([
  242 + getTaskDetails({queryType: 1, pageNo: 1, pageSize: 1}),
  243 + getTaskDetails({queryType: 2, pageNo: 1, pageSize: 1})
  244 + ]);
  245 +
  246 + // 更新总数
  247 + todoTotal.value = todoRes?.total || 0;
  248 + doneTotal.value = doneRes?.total || 0;
  249 + console.log('已办:')
  250 + console.log(doneRes)
  251 + tabList.value = [
  252 + {name: `待办事项(${todoTotal.value})`},
  253 + {name: `已办事项(${doneTotal.value})`},
  254 + ]
  255 + console.log('初始化总数:', {todoTotal: todoTotal.value, doneTotal: doneTotal.value});
  256 + } catch (error) {
  257 + console.error('获取Tab总数失败:', error);
  258 + // 保底:如果已办请求失败,至少保证待办数据正常
  259 + }
228 }; 260 };
229 261
230 // ========== 5. 业务逻辑 ========== 262 // ========== 5. 业务逻辑 ==========
231 -const switchTab = (tabType) => {  
232 - if (currentTab.value === tabType) return;  
233 - currentTab.value = tabType; 263 +/**
  264 + * up-tabs切换事件处理
  265 + * @param {object} item 选中的Tab项
  266 + */
  267 +const handleTabChange = (item) => {
  268 + // 解析Tab类型(todo/done)
  269 + console.log(item)
  270 + const newTab = item.name.includes('待办') ? 'todo' : 'done';
  271 + if (currentTab.value === newTab) return;
  272 +
  273 + currentTab.value = newTab;
  274 + // 切换标签后刷新分页数据
  275 + paging.value?.reload();
234 }; 276 };
235 277
236 -const openDatePicker = () => {}; 278 +/**
  279 + * 分页查询列表数据
  280 + * @param {number} pageNo 页码
  281 + * @param {number} pageSize 每页条数
  282 + */
  283 +const queryList = async (pageNo, pageSize) => {
  284 + try {
  285 + // 1:待完成 2:已完成
  286 + const queryType = currentTab.value === 'todo' ? 1 : 2;
  287 + const postData = {
  288 + queryType,
  289 + pageNo,
  290 + pageSize,
  291 + };
  292 +
  293 + const res = await getTaskDetails(postData);
  294 + console.log(`${currentTab.value}列表数据:`, res);
  295 +
  296 + // 更新当前Tab的总数
  297 + if (currentTab.value === 'todo') {
  298 + todoTotal.value = res?.total || 0;
  299 + } else {
  300 + doneTotal.value = res?.total || 0;
  301 + }
237 302
  303 + // 适配z-paging分页(注意接口返回结构是res.data.list)
  304 + paging.value.complete(res?.list || [], res?.total || 0);
  305 + } catch (error) {
  306 + console.error(`加载${currentTab.value}列表失败:`, error);
  307 + paging.value?.complete(false);
  308 + uni.showToast({title: '加载失败,请重试', icon: 'none'});
  309 + }
  310 +};
  311 +
  312 +// 日期选择确认事件
238 const handleDateConfirm = (e) => { 313 const handleDateConfirm = (e) => {
239 - if (!e?.value?.length) return;  
240 - fetchKlineData(); 314 + console.log('日期选择确认:', e);
  315 +
  316 + // 更新选中的日期范围
  317 + dateRange.value = e;
  318 +
  319 + // 加载选择日期后的图表数据
  320 + initChartData(e[0], e[1]);
241 }; 321 };
242 322
243 const handleMsgClick = () => { 323 const handleMsgClick = () => {
244 - wx.navigateTo({ url: '/pages-sub/msg/index' }); 324 + uni.navigateTo({url: '/pages-sub/msg/index'});
245 }; 325 };
246 326
247 const handleTaskClick = (item) => { 327 const handleTaskClick = (item) => {
248 - wx.navigateTo({ url: `/pages-sub/task/detail?id=${item.id || ''}` }); 328 + // uni.navigateTo({url: `/pages-sub/task/detail?id=${item.id || ''}`});
249 }; 329 };
250 330
251 // ========== 6. 生命周期 ========== 331 // ========== 6. 生命周期 ==========
252 -onShow(() => { 332 +onShow(async () => {
  333 + // 初始化图表尺寸
253 chartWidth.value = rpx2px(750 - 60); 334 chartWidth.value = rpx2px(750 - 60);
254 chartHeight.value = rpx2px(300); 335 chartHeight.value = rpx2px(300);
  336 +
  337 + // 初始化默认日期范围(最近7天)
255 initRecent7Days(); 338 initRecent7Days();
256 - fetchKlineData();  
257 - getUnread() 339 +
  340 + // 获取未读消息数
  341 + getUnread();
  342 + await getAllTabTotal();
  343 +
  344 + // 初始化分页数据(待办列表)
  345 + if (paging.value) {
  346 + paging.value.reload();
  347 + }
258 }); 348 });
259 </script> 349 </script>
260 350
@@ -267,21 +357,22 @@ $text-color-light: #666; @@ -267,21 +357,22 @@ $text-color-light: #666;
267 $text-color-placeholder: #999; 357 $text-color-placeholder: #999;
268 $bg-color: #f5f5f7; 358 $bg-color: #f5f5f7;
269 $card-bg: #fff; 359 $card-bg: #fff;
270 -$border-radius: 32rpx;  
271 -$card-radius: 10rpx;  
272 -$spacing-sm: 10rpx;  
273 -$spacing-md: 20rpx;  
274 -$spacing-lg: 30rpx;  
275 -  
276 -.home-page {  
277 - background-color: $bg-color; 360 +$border-radius: 16px;
  361 +$card-radius: 5px;
  362 +$spacing-sm: 5px;
  363 +$spacing-md: 10px;
  364 +$spacing-lg: 15px;
  365 +$border-color: #e5e5e5; // 新增边框颜色变量
  366 +
  367 +.page-container {
278 min-height: 100vh; 368 min-height: 100vh;
  369 + background-color: $bg-color;
279 } 370 }
280 371
281 .user-info-bar { 372 .user-info-bar {
282 background: url("https://img.jichengshanshui.com.cn:28207/appimg/bg.jpg") no-repeat; 373 background: url("https://img.jichengshanshui.com.cn:28207/appimg/bg.jpg") no-repeat;
283 background-size: 100% 100%; 374 background-size: 100% 100%;
284 - padding: 200rpx $spacing-lg 270rpx; 375 + padding: 100px $spacing-lg 135px;
285 display: flex; 376 display: flex;
286 justify-content: space-between; 377 justify-content: space-between;
287 align-items: center; 378 align-items: center;
@@ -292,16 +383,16 @@ $spacing-lg: 30rpx; @@ -292,16 +383,16 @@ $spacing-lg: 30rpx;
292 .username { 383 .username {
293 font-size: 16px; 384 font-size: 16px;
294 font-weight: 500; 385 font-weight: 500;
295 - margin-bottom: 8rpx; 386 + margin-bottom: 4px;
296 } 387 }
297 388
298 .login-desc { 389 .login-desc {
299 - font-size: 16px; 390 + font-size: 16px;
300 } 391 }
301 392
302 .msg-icon { 393 .msg-icon {
303 position: relative; 394 position: relative;
304 - padding: 10rpx; 395 + padding: 5px;
305 border-radius: 50%; 396 border-radius: 50%;
306 transition: background-color 0.2s; 397 transition: background-color 0.2s;
307 398
@@ -311,27 +402,27 @@ $spacing-lg: 30rpx; @@ -311,27 +402,27 @@ $spacing-lg: 30rpx;
311 402
312 .msg-badge { 403 .msg-badge {
313 position: absolute; 404 position: absolute;
314 - top: -10rpx;  
315 - right: -10rpx; 405 + top: -5px;
  406 + right: -5px;
316 } 407 }
317 } 408 }
318 } 409 }
319 410
320 .content-wrap { 411 .content-wrap {
321 - margin-top: -245rpx; 412 + margin-top: -122px;
322 border-radius: $border-radius $border-radius 0 0; 413 border-radius: $border-radius $border-radius 0 0;
323 background-color: $bg-color; 414 background-color: $bg-color;
324 position: relative; 415 position: relative;
325 z-index: 2; 416 z-index: 2;
326 overflow: hidden; 417 overflow: hidden;
  418 + padding-bottom: $spacing-lg;
327 } 419 }
328 420
329 .module-title { 421 .module-title {
330 - padding: $spacing-lg $spacing-lg 0;  
331 - font-size: 30rpx; 422 + padding: $spacing-lg;
  423 + font-size: 15px;
332 font-weight: 600; 424 font-weight: 600;
333 color: $text-color-light; 425 color: $text-color-light;
334 - margin-bottom: $spacing-sm;  
335 } 426 }
336 427
337 .task-chart-card { 428 .task-chart-card {
@@ -344,13 +435,13 @@ $spacing-lg: 30rpx; @@ -344,13 +435,13 @@ $spacing-lg: 30rpx;
344 display: flex; 435 display: flex;
345 justify-content: space-between; 436 justify-content: space-between;
346 align-items: center; 437 align-items: center;
347 - font-size: 24rpx; 438 + font-size: 12px;
348 color: $text-color-placeholder; 439 color: $text-color-placeholder;
349 margin-bottom: $spacing-md; 440 margin-bottom: $spacing-md;
350 441
351 .date-picker-wrap { 442 .date-picker-wrap {
352 - padding: 4rpx 8rpx;  
353 - border-radius: 6rpx; 443 + padding: 2px 4px;
  444 + border-radius: 3px;
354 transition: background-color 0.2s; 445 transition: background-color 0.2s;
355 446
356 &:active { 447 &:active {
@@ -361,105 +452,58 @@ $spacing-lg: 30rpx; @@ -361,105 +452,58 @@ $spacing-lg: 30rpx;
361 452
362 .chart-container { 453 .chart-container {
363 width: 100%; 454 width: 100%;
364 - height: 300rpx; 455 + height: 150px;
365 display: flex; 456 display: flex;
366 align-items: center; 457 align-items: center;
367 justify-content: center; 458 justify-content: center;
368 } 459 }
369 } 460 }
370 461
371 -.tab-switch-bar {  
372 - display: flex;  
373 - margin: 0 $spacing-lg;  
374 - background-color: $card-bg;  
375 - border-radius: $card-radius $card-radius 0 0;  
376 -  
377 - .tab-item {  
378 - flex: 1;  
379 - text-align: center;  
380 - padding: $spacing-md 0;  
381 - font-size: 28rpx;  
382 - color: $text-color-light;  
383 - position: relative;  
384 - transition: color 0.2s;  
385 -  
386 - &--hover {  
387 - background-color: $bg-color;  
388 - }  
389 -  
390 - &.active {  
391 - color: $primary-color;  
392 - font-weight: 500;  
393 - }  
394 -  
395 - .tab-active-line {  
396 - position: absolute;  
397 - bottom: 0;  
398 - left: 0;  
399 - width: 100%;  
400 - height: 4rpx;  
401 - background-color: $primary-color;  
402 - }  
403 - } 462 +.task-tab-container {
  463 + position: relative;
  464 + z-index: 4;
  465 + margin: 0 $spacing-lg $spacing-sm;
404 } 466 }
405 467
406 .task-list-container { 468 .task-list-container {
407 - background-color: $card-bg;  
408 margin: 0 $spacing-lg; 469 margin: 0 $spacing-lg;
409 - padding: $spacing-md;  
410 - border-radius: 0 0 $card-radius $card-radius;  
411 - box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);  
412 -  
413 - .task-item {  
414 - padding: $spacing-md;  
415 - border-bottom: 1px solid $bg-color;  
416 - transition: background-color 0.2s;  
417 -  
418 - &--hover {  
419 - background-color: $bg-color;  
420 - }  
421 -  
422 - &:last-child {  
423 - border-bottom: none;  
424 - } 470 + background-color: $card-bg;
  471 + border-radius: $card-radius;
  472 + overflow: hidden; // 防止边框溢出圆角
  473 +}
425 474
426 - .task-name {  
427 - font-size: 28rpx;  
428 - color: $text-color;  
429 - margin-bottom: $spacing-sm;  
430 - white-space: nowrap;  
431 - overflow: hidden;  
432 - text-overflow: ellipsis;  
433 - } 475 +.task-item {
  476 + padding: $spacing-md;
  477 + transition: background-color 0.2s;
  478 + border-bottom: 1px solid $border-color;
  479 + &:last-child {
  480 + border-bottom: none;
  481 + }
434 482
435 - .task-meta {  
436 - display: flex;  
437 - justify-content: space-between;  
438 - align-items: center;  
439 - font-size: 24rpx; 483 + &--hover {
  484 + background-color: $bg-color;
  485 + }
440 486
441 - .urgency-tag {  
442 - padding: 2rpx 8rpx;  
443 - border-radius: 4rpx;  
444 - color: #fff; 487 + &__content {
  488 + width: 100%;
  489 + }
445 490
446 - &--urgent {  
447 - background-color: $danger-color;  
448 - } 491 + &__name {
  492 + font-size: 13px;
  493 + color: $text-color;
  494 + margin-bottom: $spacing-sm;
  495 + }
449 496
450 - &--high {  
451 - background-color: #fa8c16;  
452 - } 497 + &__footer {
  498 + display: flex;
  499 + align-items: center;
  500 + justify-content: space-between;
  501 + }
  502 +}
453 503
454 - &--normal {  
455 - background-color: $text-color-placeholder;  
456 - }  
457 - } 504 +// 紧急程度标签样式
  505 +.urgency-tag {
  506 + color: #7D7D7D;
458 507
459 - .task-time {  
460 - color: $text-color-placeholder;  
461 - }  
462 - }  
463 - }  
464 } 508 }
465 </style> 509 </style>
466 \ No newline at end of file 510 \ No newline at end of file
pages/login/index.vue
1 <template> 1 <template>
2 <view class="login-page"> 2 <view class="login-page">
3 - <!-- 顶部标题区 - 加高 + 左下大圆角 + 底部蓝色过滤阴影 + 从浅到深 蓝色渐变背景 --> 3 + <!-- 顶部标题区 -->
4 <view class="top-title"> 4 <view class="top-title">
5 <text class="welcome-text">你好,欢迎光临</text> 5 <text class="welcome-text">你好,欢迎光临</text>
6 <text class="platform-name">全域智能运营管理平台</text> 6 <text class="platform-name">全域智能运营管理平台</text>
7 </view> 7 </view>
8 8
9 - <!-- 登录表单区域 - 往上偏移 盖住top-title一部分 --> 9 + <!-- 登录表单区域 -->
10 <view class="login-form"> 10 <view class="login-form">
11 - <!-- 登录标题 -->  
12 - <view class="login-title">账户登录</view> 11 + <!-- uview-plus的Tabs组件 -->
  12 + <up-tabs
  13 + :list="tabList"
  14 + active-color="#0A86F4"
  15 + inactive-color="#666"
  16 + font-size="16px"
  17 + line-width="40"
  18 + line-height="3"
  19 + @change="handleTabChange"
  20 + ></up-tabs>
13 21
14 - <!-- ✅ 核心:和工单页同款 up-form 表单校验容器 --> 22 + <!-- 表单校验容器 -->
15 <up-form 23 <up-form
16 label-position="left" 24 label-position="left"
17 :model="form" 25 :model="form"
18 ref="loginFormRef" 26 ref="loginFormRef"
19 labelWidth="0" 27 labelWidth="0"
20 > 28 >
21 - <!-- 账号输入框 + 校验 -->  
22 - <up-form-item  
23 - prop="account"  
24 - > 29 + <!-- 手机号输入框 -->
  30 + <up-form-item v-if="loginType === '手机号登录'" prop="mobile">
  31 + <up-input
  32 + v-model="form.mobile"
  33 + border="surround"
  34 + clearable
  35 + maxlength="11"
  36 + input-align="left"
  37 + fontSize="16px"
  38 + :disabled="isLoading"
  39 + shape="circle"
  40 + placeholder="请输入手机号"
  41 + @blur="() => loginFormRef.validateField('mobile')"
  42 + />
  43 + </up-form-item>
  44 +
  45 + <!-- 账号输入框 -->
  46 + <up-form-item v-else prop="account">
25 <up-input 47 <up-input
26 v-model="form.account" 48 v-model="form.account"
27 border="surround" 49 border="surround"
@@ -32,15 +54,14 @@ @@ -32,15 +54,14 @@
32 :disabled="isLoading" 54 :disabled="isLoading"
33 shape="circle" 55 shape="circle"
34 placeholder="请输入账户" 56 placeholder="请输入账户"
35 - selectionStart="15"  
36 - selectionEnd="15"  
37 @blur="() => loginFormRef.validateField('account')" 57 @blur="() => loginFormRef.validateField('account')"
38 /> 58 />
39 </up-form-item> 59 </up-form-item>
40 60
41 - <!-- 密码输入框 + 校验 --> 61 + <!-- 密码输入框 -->
42 <up-form-item 62 <up-form-item
43 prop="password" 63 prop="password"
  64 + class="password-item"
44 > 65 >
45 <up-input 66 <up-input
46 v-model="form.password" 67 v-model="form.password"
@@ -52,11 +73,21 @@ @@ -52,11 +73,21 @@
52 type="password" 73 type="password"
53 :disabled="isLoading" 74 :disabled="isLoading"
54 shape="circle" 75 shape="circle"
55 - selectionStart="5"  
56 - selectionEnd="5"  
57 @blur="() => loginFormRef.validateField('password')" 76 @blur="() => loginFormRef.validateField('password')"
58 /> 77 />
59 </up-form-item> 78 </up-form-item>
  79 +
  80 + <!-- 记住密码单独一行,居右显示 -->
  81 + <view class="remember-wrap">
  82 + <up-checkbox
  83 + :customStyle="{marginBottom: '8px'}"
  84 + label="记住密码"
  85 + name="agree"
  86 + usedAlone
  87 + v-model:checked="rememberPwd"
  88 + >
  89 + </up-checkbox>
  90 + </view>
60 </up-form> 91 </up-form>
61 92
62 <!-- 登录按钮 --> 93 <!-- 登录按钮 -->
@@ -82,92 +113,165 @@ import { ref, reactive, onMounted, nextTick } from &#39;vue&#39;; @@ -82,92 +113,165 @@ import { ref, reactive, onMounted, nextTick } from &#39;vue&#39;;
82 import { useUserStore } from '@/pinia/user'; 113 import { useUserStore } from '@/pinia/user';
83 import globalConfig from '@/common/config/global'; 114 import globalConfig from '@/common/config/global';
84 115
85 -// ========== 【全局实例 & 基础状态】 和工单页写法一致 ========== 116 +// 全局实例 & 基础状态
86 const userStore = useUserStore(); 117 const userStore = useUserStore();
87 -const loginFormRef = ref(null); // 表单ref 用于校验  
88 -const isLoading = ref(false); // 登录加载状态 118 +const loginFormRef = ref(null);
  119 +const isLoading = ref(false);
  120 +
  121 +// Tabs配置(name直接存储显示文本)
  122 +const tabList = ref([
  123 + { name: '手机号登录' },
  124 + { name: '账号登录' }
  125 +]);
  126 +const loginType = ref('手机号登录'); // 登录类型标识
  127 +
  128 +// 记住密码(默认选中)
  129 +const rememberPwd = ref(true);
89 130
90 -// ========== 【表单数据】 和工单页一致的 reactive 声明 ========== 131 +// 表单数据
91 const form = reactive({ 132 const form = reactive({
92 - account: '', // 账号  
93 - password: '' // 密码 133 + account: '', // 账号
  134 + mobile: '', // 手机号
  135 + password: '' // 密码
94 }); 136 });
95 137
96 -// ========== 【表单校验规则】 ✅核心 和工单页1:1同款校验规则写法 ========== 138 +// 表单校验规则
97 const loginFormRules = reactive({ 139 const loginFormRules = reactive({
98 - // 账号校验:必填 + 长度限制 2-30位(合理账号长度,可自行调整)  
99 account: [ 140 account: [
100 { type: 'string', required: true, message: '请输入登录账号', trigger: ['change', 'blur'] }, 141 { type: 'string', required: true, message: '请输入登录账号', trigger: ['change', 'blur'] },
101 { type: 'string', min: 2, max: 30, message: '账号长度为2-30个字符', trigger: ['change', 'blur'] } 142 { type: 'string', min: 2, max: 30, message: '账号长度为2-30个字符', trigger: ['change', 'blur'] }
102 ], 143 ],
103 - // 密码校验:必填 + 长度限制 6-20位(行业通用密码长度,可自行调整) 144 + mobile: [
  145 + { type: 'string', required: true, message: '请输入手机号', trigger: ['change', 'blur'] },
  146 + { type: 'string', pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号格式', trigger: ['change', 'blur'] }
  147 + ],
104 password: [ 148 password: [
105 { type: 'string', required: true, message: '请输入登录密码', trigger: ['change', 'blur'] }, 149 { type: 'string', required: true, message: '请输入登录密码', trigger: ['change', 'blur'] },
106 { type: 'string', min: 3, max: 20, message: '密码长度为3-20个字符', trigger: ['change', 'blur'] } 150 { type: 'string', min: 3, max: 20, message: '密码长度为3-20个字符', trigger: ['change', 'blur'] }
107 ] 151 ]
108 }); 152 });
109 153
110 -// ========== 【生命周期】 nextTick 设置校验规则 和工单页一致 ========== 154 +// Tabs切换事件
  155 +const handleTabChange = ({name}) => {
  156 + console.log(name)
  157 + if (isLoading.value) return;
  158 + loginType.value = name;
  159 +
  160 + // 切换时清空另一类输入框
  161 + if (name === '手机号登录') {
  162 + form.account = '';
  163 + } else {
  164 + form.mobile = '';
  165 + }
  166 +
  167 + // 清空校验状态
  168 + nextTick(() => {
  169 + loginFormRef.value?.clearValidate();
  170 + });
  171 +};
  172 +
  173 +// 生命周期
111 onMounted(() => { 174 onMounted(() => {
112 // 检查登录态 175 // 检查登录态
113 checkLoginStatus(); 176 checkLoginStatus();
114 - // nextTick确保表单挂载完成再赋值规则,修复校验不生效bug,和工单页写法一致 177 +
  178 + // 初始化表单校验规则
115 nextTick(() => { 179 nextTick(() => {
116 loginFormRef.value?.setRules(loginFormRules); 180 loginFormRef.value?.setRules(loginFormRules);
117 }); 181 });
  182 +
  183 + // 读取缓存的账号/密码
  184 + if (rememberPwd.value) {
  185 + try {
  186 + const savedAccount = uni.getStorageSync('login_account') || '';
  187 + const savedMobile = uni.getStorageSync('login_mobile') || '';
  188 + const savedPwd = uni.getStorageSync('login_password') || '';
  189 +
  190 + if (savedAccount) {
  191 + form.account = savedAccount;
  192 + loginType.value = '账号登录';
  193 +
  194 + } else if (savedMobile) {
  195 + form.mobile = savedMobile;
  196 + loginType.value = '手机号登录';
  197 + }
  198 + form.password = savedPwd;
  199 + } catch (err) {
  200 + console.warn('读取缓存密码失败:', err);
  201 + }
  202 + }
118 }); 203 });
119 204
120 -// ========== 【检查登录状态】 原有逻辑不变 ========== 205 +// 检查登录状态
121 const checkLoginStatus = () => { 206 const checkLoginStatus = () => {
122 try { 207 try {
123 - // 已登录则直接跳首页  
124 if (userStore.isLogin) { 208 if (userStore.isLogin) {
125 uni.switchTab({ 209 uni.switchTab({
126 url: '/pages/workbench/index', 210 url: '/pages/workbench/index',
127 - fail: () => {  
128 - uni.reLaunch({ url: '/pages/workbench/index' });  
129 - } 211 + fail: () => uni.reLaunch({ url: '/pages/workbench/index' })
130 }); 212 });
131 - return;  
132 } 213 }
133 } catch (err) { 214 } catch (err) {
134 console.warn('检查登录状态失败:', err); 215 console.warn('检查登录状态失败:', err);
135 } 216 }
136 }; 217 };
137 218
138 -// ========== 【核心登录方法】 ✅ 完整表单校验 + 登录逻辑,和工单页submit提交逻辑一致 ========== 219 +// 登录方法
139 const handleLogin = async () => { 220 const handleLogin = async () => {
140 try { 221 try {
141 - // ✅ 第一步:先执行表单整体校验,通过后再执行登录逻辑(和工单页submitWorkOrder写法一致) 222 + // 表单校验
142 await loginFormRef.value.validate(); 223 await loginFormRef.value.validate();
143 -  
144 isLoading.value = true; 224 isLoading.value = true;
145 - // 执行登录请求  
146 - await userStore.login({  
147 - username: form.account,  
148 - password: form.password  
149 - });  
150 225
151 - uni.showToast({ title: '登录成功', icon: 'success', duration: 1000 }); 226 + // 组装登录参数
  227 + const loginParams = { password: form.password };
  228 + if (loginType.value === '手机号登录') {
  229 + // loginParams.mobile = form.mobile;
  230 + loginParams.username = form.mobile;
  231 + } else {
  232 + loginParams.username = form.account;
  233 + }
  234 +
  235 + // 执行登录
  236 + await userStore.login(loginParams);
152 237
153 - // 登录成功后跳转首页 238 + // 保存记住密码
  239 + if (rememberPwd.value) {
  240 + try {
  241 + if (loginType.value === '手机号登录') {
  242 + uni.setStorageSync('login_mobile', form.mobile);
  243 + uni.removeStorageSync('login_account');
  244 + } else {
  245 + uni.setStorageSync('login_account', form.account);
  246 + uni.removeStorageSync('login_mobile');
  247 + }
  248 + uni.setStorageSync('login_password', form.password);
  249 + } catch (err) {
  250 + console.warn('保存密码失败:', err);
  251 + }
  252 + } else {
  253 + // 清除缓存
  254 + uni.removeStorageSync('login_account');
  255 + uni.removeStorageSync('login_mobile');
  256 + uni.removeStorageSync('login_password');
  257 + }
  258 +
  259 + // 登录成功提示+跳转
  260 + uni.showToast({ title: '登录成功', icon: 'success', duration: 1000 });
154 setTimeout(() => { 261 setTimeout(() => {
155 uni.switchTab({ 262 uni.switchTab({
156 url: globalConfig.router.tabBarList[1].path, 263 url: globalConfig.router.tabBarList[1].path,
157 - fail: (err) => {  
158 - console.warn('tabBar跳转失败,切换为普通跳转:', err);  
159 - uni.reLaunch({ url: '/pages/workbench/index' });  
160 - } 264 + fail: () => uni.reLaunch({ url: '/pages/workbench/index' })
161 }); 265 });
162 }, 1000); 266 }, 1000);
163 } catch (err) { 267 } catch (err) {
164 - // ✅ 校验失败/登录失败 统一捕获提示,和工单页异常处理一致 268 + // 错误处理
165 if (!Array.isArray(err)) { 269 if (!Array.isArray(err)) {
166 console.error('登录失败:', err); 270 console.error('登录失败:', err);
167 uni.showToast({ 271 uni.showToast({
168 - title: err.message || '账号或密码错误,请重试', 272 + title: err.msg || '账号或密码错误,请重试',
169 icon: 'none', 273 icon: 'none',
170 - duration: 2000 274 + duration: 1000
171 }); 275 });
172 } 276 }
173 } finally { 277 } finally {
@@ -187,18 +291,16 @@ const handleLogin = async () =&gt; { @@ -187,18 +291,16 @@ const handleLogin = async () =&gt; {
187 background: #f5f7fa; 291 background: #f5f7fa;
188 } 292 }
189 293
190 -// 顶部:从左到右 由浅到深 线性渐变 过渡自然柔和 无突兀色块 294 +// 顶部样式
191 .top-title { 295 .top-title {
192 - //background: linear-gradient(120deg, #4299e1 0%, #2970e8 50%, #2563eb 100%); 296 + background: url("https://img.jichengshanshui.com.cn:28207/appimg/bg.jpg") no-repeat;
  297 + background-size: 100% 100%;
193 color: #fff; 298 color: #fff;
194 - padding: 220rpx 30rpx 200rpx; 299 + padding: 200rpx 0 200rpx 40rpx;
195 text-align: left; 300 text-align: left;
196 - //border-bottom-left-radius: 120rpx;  
197 position: relative; 301 position: relative;
198 z-index: 1; 302 z-index: 1;
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%; 303 +
202 .welcome-text { 304 .welcome-text {
203 font-size: 23px; 305 font-size: 23px;
204 display: block; 306 display: block;
@@ -223,37 +325,64 @@ const handleLogin = async () =&gt; { @@ -223,37 +325,64 @@ const handleLogin = async () =&gt; {
223 position: relative; 325 position: relative;
224 z-index: 10; 326 z-index: 10;
225 box-sizing: border-box; 327 box-sizing: border-box;
  328 +}
226 329
227 - .login-title {  
228 - font-size: 19px;  
229 - font-weight: 600;  
230 - color: #666;  
231 - margin-bottom: 15px;  
232 - letter-spacing: 1px; 330 +// Tabs样式适配
  331 +:deep(.u-tabs) {
  332 + margin-bottom: 15px;
  333 + .u-tabs__content {
  334 + height: auto !important;
  335 + }
  336 + .u-tab-item {
  337 + padding: 0 10px;
233 } 338 }
234 } 339 }
235 340
236 -// 适配表单校验的间距 341 +// 表单间距
237 :deep(.u-form-item) { 342 :deep(.u-form-item) {
238 margin-bottom: 10px; 343 margin-bottom: 10px;
239 position: relative; 344 position: relative;
240 } 345 }
241 346
  347 +// 密码项样式
  348 +.password-item {
  349 + position: relative;
  350 + margin-bottom: 5px !important;
  351 +}
  352 +
  353 +// ✅ 核心优化:记住密码完全居右对齐
  354 +.remember-wrap {
  355 + //text-align: right;
  356 + padding: 0; // 移除多余内边距
  357 + margin: 10px 0 20px;
  358 +
  359 + :deep(.u-checkbox) {
  360 + font-size: 12px;
  361 + color: #666;
  362 + justify-content: flex-end;
  363 + // 移除复选框的默认左边距,确保居右紧凑
  364 + margin-left: 0 !important;
  365 +
  366 + .u-checkbox__label {
  367 + margin-left: 5px;
  368 + }
  369 + }
  370 +}
  371 +
242 // 登录按钮 372 // 登录按钮
243 .login-btn { 373 .login-btn {
244 - margin-top: 20px; 374 + margin-top: 10px;
245 width: 100%; 375 width: 100%;
246 height: 44px; 376 height: 44px;
247 line-height: 44px; 377 line-height: 44px;
248 border-radius: 4px; 378 border-radius: 4px;
249 font-size: 16px; 379 font-size: 16px;
250 -  
251 background: #0A86F4; 380 background: #0A86F4;
252 box-shadow: 0px 4px 6px 1px rgba(25,94,215,0.5); 381 box-shadow: 0px 4px 6px 1px rgba(25,94,215,0.5);
253 border-radius: 23px; 382 border-radius: 23px;
254 } 383 }
255 384
256 - 385 +// 版权信息
257 .copyright { 386 .copyright {
258 width: 100%; 387 width: 100%;
259 text-align: center; 388 text-align: center;
pages/mine/index.vue
1 <template> 1 <template>
2 - <view class="user-center-page"> 2 + <view class="page-container">
3 <!-- 顶部用户信息栏 --> 3 <!-- 顶部用户信息栏 -->
4 <view class="header-bg"> 4 <view class="header-bg">
5 <up-avatar 5 <up-avatar
@@ -114,7 +114,7 @@ export default { @@ -114,7 +114,7 @@ export default {
114 </script> 114 </script>
115 115
116 <style lang="scss" scoped> 116 <style lang="scss" scoped>
117 -.user-center-page { 117 +.page-container {
118 118
119 } 119 }
120 120
@@ -142,25 +142,25 @@ export default { @@ -142,25 +142,25 @@ export default {
142 } 142 }
143 143
144 .user-info-content { 144 .user-info-content {
145 - margin-left: 30rpx; 145 + margin-left: 15px;
146 flex: 1; 146 flex: 1;
147 147
148 .user-name { 148 .user-name {
149 - font-size: 32rpx; 149 + font-size: 16px;
150 font-weight: 600; 150 font-weight: 600;
151 color: #ffffff; 151 color: #ffffff;
152 - margin-bottom: 10rpx;  
153 - text-shadow: 0 1rpx 3rpx rgba(0, 0, 0, 0.1); 152 + margin-bottom: 5px;
  153 + text-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
154 } 154 }
155 155
156 .user-phone { 156 .user-phone {
157 - font-size: 26rpx; 157 + font-size: 13px;
158 color: rgba(255, 255, 255, 0.9); 158 color: rgba(255, 255, 255, 0.9);
159 } 159 }
160 } 160 }
161 // 退出登录按钮 161 // 退出登录按钮
162 .logout-btn-wrap { 162 .logout-btn-wrap {
163 - margin: 200rpx 20rpx 20rpx; 163 + margin: 100px 10px 10px;
164 } 164 }
165 // 登录按钮 165 // 登录按钮
166 .login-btn { 166 .login-btn {
pages/workbench/index.vue
@@ -10,7 +10,6 @@ @@ -10,7 +10,6 @@
10 10
11 <!-- 蓝色装饰块 --> 11 <!-- 蓝色装饰块 -->
12 <view class="blue-decor-block" v-show="!loading"> 12 <view class="blue-decor-block" v-show="!loading">
13 - <!-- ✅ 修复1:增加可选链+默认值,防止数据为undefined报错 -->  
14 <text class="welcome-text u-line-1">你好{{userInfo?.user?.nickname || ''}},欢迎登录</text> 13 <text class="welcome-text u-line-1">你好{{userInfo?.user?.nickname || ''}},欢迎登录</text>
15 <text class="platform-name">全域智能运营管理平台</text> 14 <text class="platform-name">全域智能运营管理平台</text>
16 </view> 15 </view>
@@ -46,10 +45,10 @@ @@ -46,10 +45,10 @@
46 <u-image 45 <u-image
47 :src="listItem.icon" 46 :src="listItem.icon"
48 mode="aspectFit" 47 mode="aspectFit"
49 - width="80rpx"  
50 - height="80rpx" 48 + width="40px"
  49 + height="40px"
51 lazy-load 50 lazy-load
52 - radius="16rpx" 51 + radius="8px"
53 class="block-icon" 52 class="block-icon"
54 ></u-image> 53 ></u-image>
55 <text class="grid-text">{{ listItem.name }}</text> 54 <text class="grid-text">{{ listItem.name }}</text>
@@ -91,13 +90,11 @@ interface MenuItem { @@ -91,13 +90,11 @@ interface MenuItem {
91 const loading = ref(true); 90 const loading = ref(true);
92 const userStore = useUserStore(); 91 const userStore = useUserStore();
93 const moduleList = ref<MenuItem[]>([]); 92 const moduleList = ref<MenuItem[]>([]);
94 -// ✅ 修复2:声明全局的用户信息变量,模板可访问 + 初始化空对象兜底,杜绝undefined  
95 const userInfo = ref<any>(cache.get(globalConfig.cache.userInfoKey) || userStore.userInfo || {}); 93 const userInfo = ref<any>(cache.get(globalConfig.cache.userInfoKey) || userStore.userInfo || {});
96 94
97 // 计算属性:过滤出有子节点的父模块(children 存在且长度 > 0) 95 // 计算属性:过滤出有子节点的父模块(children 存在且长度 > 0)
98 const filteredModuleList = computed(() => { 96 const filteredModuleList = computed(() => {
99 return moduleList.value.filter(item => { 97 return moduleList.value.filter(item => {
100 - // 确保 children 是数组且长度大于 0  
101 return Array.isArray(item.children) && item.children.length > 0; 98 return Array.isArray(item.children) && item.children.length > 0;
102 }); 99 });
103 }); 100 });
@@ -105,24 +102,19 @@ const filteredModuleList = computed(() =&gt; { @@ -105,24 +102,19 @@ const filteredModuleList = computed(() =&gt; {
105 onShow(async () => { 102 onShow(async () => {
106 try { 103 try {
107 loading.value = true; 104 loading.value = true;
108 - // ✅ 修复3:重新赋值全局userInfo,保证数据最新  
109 userInfo.value = cache.get(globalConfig.cache.userInfoKey) || userStore.userInfo || {}; 105 userInfo.value = cache.get(globalConfig.cache.userInfoKey) || userStore.userInfo || {};
110 106
111 // 登录状态判断(多维度校验,确保准确性) 107 // 登录状态判断(多维度校验,确保准确性)
112 const isLogin = () => { 108 const isLogin = () => {
113 - // 从缓存获取token(核心登录标识)  
114 const token = cache.get(globalConfig.cache.tokenKey) || userStore.token; 109 const token = cache.get(globalConfig.cache.tokenKey) || userStore.token;
115 - // 从全局变量取值,无需再声明局部变量  
116 const userInfoVal = userInfo.value; 110 const userInfoVal = userInfo.value;
117 console.log('当前用户信息:', userInfoVal?.user); 111 console.log('当前用户信息:', userInfoVal?.user);
118 - // 满足任一核心条件即视为已登录  
119 return !!token && !!userInfoVal; 112 return !!token && !!userInfoVal;
120 }; 113 };
121 114
122 // 未登录处理:跳转登录页,阻止后续逻辑执行 115 // 未登录处理:跳转登录页,阻止后续逻辑执行
123 if (!isLogin()) { 116 if (!isLogin()) {
124 uni.showToast({ title: '请先登录', icon: 'none', duration: 1500 }); 117 uni.showToast({ title: '请先登录', icon: 'none', duration: 1500 });
125 - // 延迟跳转,确保提示语正常显示  
126 setTimeout(() => { 118 setTimeout(() => {
127 uni.reLaunch({ 119 uni.reLaunch({
128 url: '/pages/login/index', 120 url: '/pages/login/index',
@@ -174,15 +166,14 @@ const handleMenuClick = (item: MenuItem) =&gt; { @@ -174,15 +166,14 @@ const handleMenuClick = (item: MenuItem) =&gt; {
174 top: 0; 166 top: 0;
175 left: 0; 167 left: 0;
176 width: 100%; 168 width: 100%;
177 - height: 260px; 169 + padding: 90px 15px 135px;
178 background: url("https://img.jichengshanshui.com.cn:28207/appimg/bg.jpg") no-repeat; 170 background: url("https://img.jichengshanshui.com.cn:28207/appimg/bg.jpg") no-repeat;
179 background-size: 100% 100%; 171 background-size: 100% 100%;
180 z-index: 1; 172 z-index: 1;
181 color:#fff; 173 color:#fff;
182 text-align: left; 174 text-align: left;
183 - padding-left: 20px; 175 +
184 .welcome-text { 176 .welcome-text {
185 - padding-top: 80px;  
186 font-size: 16px; 177 font-size: 16px;
187 display: block; 178 display: block;
188 margin-bottom: 10px; 179 margin-bottom: 10px;
@@ -200,10 +191,10 @@ const handleMenuClick = (item: MenuItem) =&gt; { @@ -200,10 +191,10 @@ const handleMenuClick = (item: MenuItem) =&gt; {
200 .content-wrap { 191 .content-wrap {
201 position: relative; 192 position: relative;
202 z-index: 2; 193 z-index: 2;
203 - padding: 140px 0 0; 194 + padding: 150px 0 0;
204 display: flex; 195 display: flex;
205 flex-direction: column; 196 flex-direction: column;
206 - gap: 20rpx; 197 + gap: 10px;
207 } 198 }
208 199
209 .menu-card-wrap { 200 .menu-card-wrap {
@@ -212,25 +203,24 @@ const handleMenuClick = (item: MenuItem) =&gt; { @@ -212,25 +203,24 @@ const handleMenuClick = (item: MenuItem) =&gt; {
212 203
213 .card-container { 204 .card-container {
214 --u-card-content-padding: 0; 205 --u-card-content-padding: 0;
215 - border-radius: 8rpx; 206 + border-radius: 4px;
216 overflow: hidden; 207 overflow: hidden;
217 } 208 }
218 209
219 .card-content { 210 .card-content {
220 display: flex; 211 display: flex;
221 flex-wrap: wrap; 212 flex-wrap: wrap;
222 - gap: 20rpx;  
223 - //padding: 20rpx; // ✅ 优化:增加内边距,内容不贴边 213 + gap: 10px;
224 } 214 }
225 215
226 .content-block { 216 .content-block {
227 - width: calc((100% - 3 * 20rpx) / 4); 217 + width: calc((100% - 3 * 10px) / 4);
228 display: flex; 218 display: flex;
229 flex-direction: column; 219 flex-direction: column;
230 align-items: center; 220 align-items: center;
231 justify-content: center; 221 justify-content: center;
232 - padding: 14rpx 0;  
233 - border-radius: 8rpx; 222 + padding: 7px 0;
  223 + border-radius: 4px;
234 touch-action: manipulation; 224 touch-action: manipulation;
235 transition: background-color 0.2s; 225 transition: background-color 0.2s;
236 } 226 }
@@ -245,10 +235,10 @@ const handleMenuClick = (item: MenuItem) =&gt; { @@ -245,10 +235,10 @@ const handleMenuClick = (item: MenuItem) =&gt; {
245 } 235 }
246 236
247 .grid-text { 237 .grid-text {
248 - font-size: 26rpx; 238 + font-size: 13px;
249 color: #333; 239 color: #333;
250 text-align: center; 240 text-align: center;
251 - margin-top: 10rpx; 241 + margin-top: 5px;
252 display: block; 242 display: block;
253 } 243 }
254 </style> 244 </style>
255 \ No newline at end of file 245 \ No newline at end of file