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 88 this.drawLine()
89 89 }
90 90 },
91   - // 折线图绘制(终极版:Y轴整数 + 自适应极值 + 修复文字折叠 + 优化图例间距
  91 + // 折线图绘制(移除平滑过渡,改为纯直线
92 92 drawLine () {
93 93 if (!this.data.length || !this.categories.length) return;
94 94  
... ... @@ -100,8 +100,7 @@ export default {
100 100 xAxis = {},
101 101 yAxis = {},
102 102 legend = {},
103   - color = ['#25AF69', '#B34C17'],
104   - lineSmooth = true
  103 + color = ['#25AF69', '#B34C17']
105 104 } = this.option
106 105  
107 106 // 清空画布
... ... @@ -154,7 +153,7 @@ export default {
154 153 ctx.lineTo(width - gridRight, y)
155 154 ctx.stroke()
156 155  
157   - // ========== 核心修改:Y轴显示整数(无小数点) ==========
  156 + // Y轴显示整数(无小数点)
158 157 ctx.setFillStyle(yAxis.axisLabel?.color || '#666')
159 158 ctx.setFontSize(yAxis.axisLabel?.fontSize || 12)
160 159 const val = minVal + (i * yTickStep)
... ... @@ -211,7 +210,7 @@ export default {
211 210 })
212 211 }
213 212  
214   - // 绘制平滑折线(无数据点)
  213 + // ========== 核心修改:移除平滑曲线,改为纯直线连接 ==========
215 214 this.data.forEach((series, seriesIdx) => {
216 215 const seriesColor = series.color || color[seriesIdx % color.length]
217 216  
... ... @@ -226,21 +225,8 @@ export default {
226 225 if (index === 0) {
227 226 ctx.moveTo(x, y)
228 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 235  
250 236 ctx.draw()
251 237 },
252   - // K线图绘制(同步修改Y轴为整数
  238 + // K线图绘制(同步修改Y轴为整数,K线本身无平滑逻辑,保持原样
253 239 drawKline () {
254 240 const ctx = uni.createCanvasContext('uni-charts', this)
255 241 this.ctx = ctx
... ... @@ -322,7 +308,7 @@ export default {
322 308 }
323 309 })
324 310  
325   - // 绘制K线
  311 + // 绘制K线(K线本身就是直线,无平滑逻辑)
326 312 this.data.forEach((item, index) => {
327 313 const { open, high, low, close } = item
328 314 const x = gridLeft + index * xStep + xStep / 2
... ... @@ -331,7 +317,7 @@ export default {
331 317 const yHigh = gridTop + drawHeight - ((high - minVal) / valRange) * drawHeight
332 318 const yLow = gridTop + drawHeight - ((low - minVal) / valRange) * drawHeight
333 319  
334   - // 绘制高低线
  320 + // 绘制高低线(直线)
335 321 ctx.setStrokeStyle(close >= open ? color[0] : color[1])
336 322 ctx.setLineWidth(1)
337 323 ctx.beginPath()
... ...
pages-sub/daily/maintain-manage/finish-plan-detail.vue
... ... @@ -9,22 +9,15 @@
9 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 15 <up-cell-group :border="false" inset>
17 16 <!-- 1. 工单计划名称 -->
18   - <up-cell
19   - align="middle"
20   - >
21   -
  17 + <up-cell align="middle">
22 18 <template #title>
23   - <view class="up-line-1">{{i.planName || '--'}}</view>
  19 + <view class="up-line-1">{{i.planName || '--'}}</view>
24 20 </template>
25   -<!-- <template #value>-->
26   -<!-- <view class="up-line-1">{{i.remark || '&#45;&#45;'}}</view>-->
27   -<!-- </template>-->
28 21 </up-cell>
29 22  
30 23 <!-- 2. 工单位置 -->
... ... @@ -44,34 +37,42 @@
44 37 <!-- 4. 情况描述 -->
45 38 <up-cell
46 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 41 align="middle"
49 42 ></up-cell>
50 43  
51   - <!-- 5. 问题照片(核心修复:判断条件+空值处理) -->
  44 + <!-- 5. 问题照片(核心修复:兼容WebP + 强制尺寸) -->
52 45 <up-cell title="照片">
53 46 <template #value>
54 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 64 <text v-else class="empty-text">暂无问题照片</text>
62 65 </view>
63 66 </template>
64 67 </up-cell>
65 68  
66 69 <!-- 7. 处理结果 -->
67   - <up-cell
68   - align="middle"
69   - >
  70 + <up-cell align="middle">
70 71 <template #title>
71   - <view style="min-width: 200rpx">巡查描述</view>
  72 + <view style="min-width: 200rpx">巡查描述</view>
72 73 </template>
73 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 76 </template>
76 77 </up-cell>
77 78  
... ... @@ -81,7 +82,6 @@
81 82 align="middle"
82 83 ></up-cell>
83 84  
84   -
85 85 <up-cell
86 86 title="提交人"
87 87 :value="i.userName || '--'"
... ... @@ -89,7 +89,7 @@
89 89 :border="false"
90 90 ></up-cell>
91 91 </up-cell-group>
92   - </view>
  92 + </view>
93 93 </template>
94 94 </view>
95 95 </template>
... ... @@ -99,10 +99,44 @@ import {ref} from &#39;vue&#39;;
99 99 import {detailList} from "@/api/maintain-manage/maintain-manage";
100 100 import {onLoad} from '@dcloudio/uni-app';
101 101 import {timeFormat} from '@/uni_modules/uview-plus';
  102 +
102 103 // 状态管理
103 104 const loading = ref(true);
104 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 145 try {
112 146 loading.value = true;
113 147 let queryData = {
114   - planNo:planNo,
115   - pageSize:100,
116   - pageNo:1,
117   - }
  148 + planNo: planNo,
  149 + pageSize: 100,
  150 + pageNo: 1,
  151 + };
118 152 const res = await detailList(queryData);
119 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 160 } catch (error) {
123 161 console.error('获取工单详情失败:', error);
124 162 uni.showToast({title: '加载失败,请重试', icon: 'none'});
... ... @@ -140,7 +178,6 @@ onLoad((options) =&gt; {
140 178 </script>
141 179  
142 180 <style scoped lang="scss">
143   -
144 181 // 内容容器
145 182 .content-wrap {
146 183 background: #fff;
... ... @@ -149,5 +186,37 @@ onLoad((options) =&gt; {
149 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 222 </style>
154 223 \ No newline at end of file
... ...
pages-sub/data/tree-archive/addTree.vue
... ... @@ -453,7 +453,7 @@ const submit = async () =&gt; {
453 453  
454 454 <style scoped lang="scss">
455 455 .container {
456   - padding: 25rpx;
  456 + padding: 15px;
457 457 box-sizing: border-box;
458 458 background: #fff;
459 459 }
... ...
pages-sub/data/tree-archive/editTree.vue
... ... @@ -193,16 +193,16 @@
193 193 <view class="card-body-inner">
194 194 <view class="record-list-left" :style="`background-image: url(${item.treephotoone});`"></view>
195 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 197 <view class="u-line-1 treetypeName">{{ item.treetype }}</view>
198 198 <view style="text-align: right">{{ timeFormat(item.updatetime) }}</view>
199 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 202 alt=""> 高度:{{ item.treeheight }} 米
203 203 </view>
204 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 206 alt="">胸径:{{ item.dbh }} 厘米
207 207 </view>
208 208 </view>
... ... @@ -601,7 +601,8 @@ const toLogDetailPage = (i) =&gt; {
601 601 }
602 602  
603 603 .base-info-wrap {
604   - padding: 20rpx 15rpx;
  604 + padding: 15px 15px;
  605 + background: #fff;
605 606 }
606 607  
607 608 .log-wrap {
... ... @@ -614,7 +615,7 @@ const toLogDetailPage = (i) =&gt; {
614 615  
615 616 .treetypeName {
616 617 flex: 1;
617   - font-size: 16px;
  618 + font-size: 14px;
618 619 font-weight: bold;
619 620 }
620 621  
... ... @@ -632,10 +633,6 @@ const toLogDetailPage = (i) =&gt; {
632 633 overflow: hidden;
633 634 }
634 635  
635   -.record-list-right-title {
636   - display: flex;
637   - justify-content: space-between;
638   -}
639 636  
640 637 .treenumber-no {
641 638 padding: 3px 10px;
... ... @@ -646,6 +643,7 @@ const toLogDetailPage = (i) =&gt; {
646 643  
647 644 .card-body-inner {
648 645 display: flex;
  646 + font-size: 12px;
649 647 }
650 648  
651 649 .fs-align__center {
... ...
pages-sub/data/tree-archive/treeRecord.vue
... ... @@ -20,15 +20,15 @@
20 20 <view class="record-list-left" :style="`background-image: url(${i.treephoto});`"></view>
21 21  
22 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 24 <view class="u-line-1 treetypeName">{{ i.treetype }}</view>
25 25 <view style="text-align: right">{{ timeFormat(i.updatetime) }}</view>
26 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 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 32 </view>
33 33 </view>
34 34 </view>
... ... @@ -124,7 +124,7 @@ const treeRoadQuery = async () =&gt; {
124 124  
125 125 .treetypeName {
126 126 flex: 1;
127   - font-size: 16px;
  127 + font-size: 14px;
128 128 font-weight: bold;
129 129 }
130 130  
... ... @@ -142,10 +142,6 @@ const treeRoadQuery = async () =&gt; {
142 142 overflow: hidden;
143 143 }
144 144  
145   -.record-list-right-title {
146   - display: flex;
147   - justify-content: space-between;
148   -}
149 145  
150 146 .treenumber-no {
151 147 margin-top: 5px;
... ... @@ -165,5 +161,6 @@ const treeRoadQuery = async () =&gt; {
165 161 }
166 162 .card-body-inner{
167 163 display: flex;
  164 + font-size: 12px;
168 165 }
169 166 </style>
170 167 \ No newline at end of file
... ...
pages/index/index.vue
1 1 <template>
2   - <view class="home-page">
  2 + <view class="page-container">
3 3 <!-- 用户信息栏 -->
4 4 <view class="user-info-bar">
5 5 <view class="user-info">
... ... @@ -9,7 +9,7 @@
9 9 </view>
10 10 </view>
11 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 13 <view class="msg-badge" v-if="msgCount > 0">
14 14 <up-badge type="error" max="999" :value="msgCount"></up-badge>
15 15 </view>
... ... @@ -24,13 +24,14 @@
24 24 <view class="task-chart-card">
25 25 <view class="card-header">
26 26 <view class="unit-tip">单位: 个</view>
27   - <view class="date-picker-wrap" @click="openDatePicker">
  27 + <view class="date-picker-wrap">
28 28 <neo-datetime-pro
29 29 v-model="dateRange"
30 30 type="daterange"
31 31 :clearable="false"
32 32 placeholder="请选择日期范围"
33 33 @confirm="handleDateConfirm"
  34 + :max-date="maxDate"
34 35 />
35 36 </view>
36 37 </view>
... ... @@ -48,74 +49,85 @@
48 49 </view>
49 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 99 </view>
90   - <view class="task-time">{{ timeFormat(item.time) }}</view>
91 100 </view>
92 101 </view>
93   - </view>
  102 + </z-paging>
94 103 </view>
95 104 </view>
96 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 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 117 // ========== 2. 响应式数据 ==========
116   -const msgCount = ref();
117   -const currentTab = ref('todo');
  118 +const msgCount = ref(0);
  119 +const currentTab = ref('todo'); // todo:待办 done:已办
118 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 133 const chartWidth = ref(0);
... ... @@ -143,12 +155,12 @@ const klineOption = ref({
143 155 bottom: '20%'
144 156 },
145 157 xAxis: {
146   - axisLabel: { fontSize: 12, color: '#666' }
  158 + axisLabel: {fontSize: 12, color: '#666'}
147 159 },
148 160 yAxis: {
149 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 165 tooltip: {
154 166 trigger: 'axis',
... ... @@ -161,100 +173,178 @@ const klineOption = ref({
161 173 legend: {
162 174 show: true,
163 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 180 // ========== 3. 计算属性 ==========
180 181 const userStore = useUserStore();
181 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 184 // ========== 4. 工具函数 ==========
187 185 const rpx2px = (rpx) => {
188   - const systemInfo = wx.getSystemInfoSync();
  186 + const systemInfo = uni.getSystemInfoSync();
189 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 192 const now = new Date();
198 193 const sevenDaysAgo = new Date();
199 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 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 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 323 const handleMsgClick = () => {
244   - wx.navigateTo({ url: '/pages-sub/msg/index' });
  324 + uni.navigateTo({url: '/pages-sub/msg/index'});
245 325 };
246 326  
247 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 331 // ========== 6. 生命周期 ==========
252   -onShow(() => {
  332 +onShow(async () => {
  333 + // 初始化图表尺寸
253 334 chartWidth.value = rpx2px(750 - 60);
254 335 chartHeight.value = rpx2px(300);
  336 +
  337 + // 初始化默认日期范围(最近7天)
255 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 349 </script>
260 350  
... ... @@ -267,21 +357,22 @@ $text-color-light: #666;
267 357 $text-color-placeholder: #999;
268 358 $bg-color: #f5f5f7;
269 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 368 min-height: 100vh;
  369 + background-color: $bg-color;
279 370 }
280 371  
281 372 .user-info-bar {
282 373 background: url("https://img.jichengshanshui.com.cn:28207/appimg/bg.jpg") no-repeat;
283 374 background-size: 100% 100%;
284   - padding: 200rpx $spacing-lg 270rpx;
  375 + padding: 100px $spacing-lg 135px;
285 376 display: flex;
286 377 justify-content: space-between;
287 378 align-items: center;
... ... @@ -292,16 +383,16 @@ $spacing-lg: 30rpx;
292 383 .username {
293 384 font-size: 16px;
294 385 font-weight: 500;
295   - margin-bottom: 8rpx;
  386 + margin-bottom: 4px;
296 387 }
297 388  
298 389 .login-desc {
299   - font-size: 16px;
  390 + font-size: 16px;
300 391 }
301 392  
302 393 .msg-icon {
303 394 position: relative;
304   - padding: 10rpx;
  395 + padding: 5px;
305 396 border-radius: 50%;
306 397 transition: background-color 0.2s;
307 398  
... ... @@ -311,27 +402,27 @@ $spacing-lg: 30rpx;
311 402  
312 403 .msg-badge {
313 404 position: absolute;
314   - top: -10rpx;
315   - right: -10rpx;
  405 + top: -5px;
  406 + right: -5px;
316 407 }
317 408 }
318 409 }
319 410  
320 411 .content-wrap {
321   - margin-top: -245rpx;
  412 + margin-top: -122px;
322 413 border-radius: $border-radius $border-radius 0 0;
323 414 background-color: $bg-color;
324 415 position: relative;
325 416 z-index: 2;
326 417 overflow: hidden;
  418 + padding-bottom: $spacing-lg;
327 419 }
328 420  
329 421 .module-title {
330   - padding: $spacing-lg $spacing-lg 0;
331   - font-size: 30rpx;
  422 + padding: $spacing-lg;
  423 + font-size: 15px;
332 424 font-weight: 600;
333 425 color: $text-color-light;
334   - margin-bottom: $spacing-sm;
335 426 }
336 427  
337 428 .task-chart-card {
... ... @@ -344,13 +435,13 @@ $spacing-lg: 30rpx;
344 435 display: flex;
345 436 justify-content: space-between;
346 437 align-items: center;
347   - font-size: 24rpx;
  438 + font-size: 12px;
348 439 color: $text-color-placeholder;
349 440 margin-bottom: $spacing-md;
350 441  
351 442 .date-picker-wrap {
352   - padding: 4rpx 8rpx;
353   - border-radius: 6rpx;
  443 + padding: 2px 4px;
  444 + border-radius: 3px;
354 445 transition: background-color 0.2s;
355 446  
356 447 &:active {
... ... @@ -361,105 +452,58 @@ $spacing-lg: 30rpx;
361 452  
362 453 .chart-container {
363 454 width: 100%;
364   - height: 300rpx;
  455 + height: 150px;
365 456 display: flex;
366 457 align-items: center;
367 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 468 .task-list-container {
407   - background-color: $card-bg;
408 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 509 </style>
466 510 \ No newline at end of file
... ...
pages/login/index.vue
1 1 <template>
2 2 <view class="login-page">
3   - <!-- 顶部标题区 - 加高 + 左下大圆角 + 底部蓝色过滤阴影 + 从浅到深 蓝色渐变背景 -->
  3 + <!-- 顶部标题区 -->
4 4 <view class="top-title">
5 5 <text class="welcome-text">你好,欢迎光临</text>
6 6 <text class="platform-name">全域智能运营管理平台</text>
7 7 </view>
8 8  
9   - <!-- 登录表单区域 - 往上偏移 盖住top-title一部分 -->
  9 + <!-- 登录表单区域 -->
10 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 23 <up-form
16 24 label-position="left"
17 25 :model="form"
18 26 ref="loginFormRef"
19 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 47 <up-input
26 48 v-model="form.account"
27 49 border="surround"
... ... @@ -32,15 +54,14 @@
32 54 :disabled="isLoading"
33 55 shape="circle"
34 56 placeholder="请输入账户"
35   - selectionStart="15"
36   - selectionEnd="15"
37 57 @blur="() => loginFormRef.validateField('account')"
38 58 />
39 59 </up-form-item>
40 60  
41   - <!-- 密码输入框 + 校验 -->
  61 + <!-- 密码输入框 -->
42 62 <up-form-item
43 63 prop="password"
  64 + class="password-item"
44 65 >
45 66 <up-input
46 67 v-model="form.password"
... ... @@ -52,11 +73,21 @@
52 73 type="password"
53 74 :disabled="isLoading"
54 75 shape="circle"
55   - selectionStart="5"
56   - selectionEnd="5"
57 76 @blur="() => loginFormRef.validateField('password')"
58 77 />
59 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 91 </up-form>
61 92  
62 93 <!-- 登录按钮 -->
... ... @@ -82,92 +113,165 @@ import { ref, reactive, onMounted, nextTick } from &#39;vue&#39;;
82 113 import { useUserStore } from '@/pinia/user';
83 114 import globalConfig from '@/common/config/global';
84 115  
85   -// ========== 【全局实例 & 基础状态】 和工单页写法一致 ==========
  116 +// 全局实例 & 基础状态
86 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 132 const form = reactive({
92   - account: '', // 账号
93   - password: '' // 密码
  133 + account: '', // 账号
  134 + mobile: '', // 手机号
  135 + password: '' // 密码
94 136 });
95 137  
96   -// ========== 【表单校验规则】 ✅核心 和工单页1:1同款校验规则写法 ==========
  138 +// 表单校验规则
97 139 const loginFormRules = reactive({
98   - // 账号校验:必填 + 长度限制 2-30位(合理账号长度,可自行调整)
99 140 account: [
100 141 { type: 'string', required: true, message: '请输入登录账号', trigger: ['change', 'blur'] },
101 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 148 password: [
105 149 { type: 'string', required: true, message: '请输入登录密码', trigger: ['change', 'blur'] },
106 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 174 onMounted(() => {
112 175 // 检查登录态
113 176 checkLoginStatus();
114   - // nextTick确保表单挂载完成再赋值规则,修复校验不生效bug,和工单页写法一致
  177 +
  178 + // 初始化表单校验规则
115 179 nextTick(() => {
116 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 206 const checkLoginStatus = () => {
122 207 try {
123   - // 已登录则直接跳首页
124 208 if (userStore.isLogin) {
125 209 uni.switchTab({
126 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 214 } catch (err) {
134 215 console.warn('检查登录状态失败:', err);
135 216 }
136 217 };
137 218  
138   -// ========== 【核心登录方法】 ✅ 完整表单校验 + 登录逻辑,和工单页submit提交逻辑一致 ==========
  219 +// 登录方法
139 220 const handleLogin = async () => {
140 221 try {
141   - // ✅ 第一步:先执行表单整体校验,通过后再执行登录逻辑(和工单页submitWorkOrder写法一致)
  222 + // 表单校验
142 223 await loginFormRef.value.validate();
143   -
144 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 261 setTimeout(() => {
155 262 uni.switchTab({
156 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 266 }, 1000);
163 267 } catch (err) {
164   - // ✅ 校验失败/登录失败 统一捕获提示,和工单页异常处理一致
  268 + // 错误处理
165 269 if (!Array.isArray(err)) {
166 270 console.error('登录失败:', err);
167 271 uni.showToast({
168   - title: err.message || '账号或密码错误,请重试',
  272 + title: err.msg || '账号或密码错误,请重试',
169 273 icon: 'none',
170   - duration: 2000
  274 + duration: 1000
171 275 });
172 276 }
173 277 } finally {
... ... @@ -187,18 +291,16 @@ const handleLogin = async () =&gt; {
187 291 background: #f5f7fa;
188 292 }
189 293  
190   -// 顶部:从左到右 由浅到深 线性渐变 过渡自然柔和 无突兀色块
  294 +// 顶部样式
191 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 298 color: #fff;
194   - padding: 220rpx 30rpx 200rpx;
  299 + padding: 200rpx 0 200rpx 40rpx;
195 300 text-align: left;
196   - //border-bottom-left-radius: 120rpx;
197 301 position: relative;
198 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 304 .welcome-text {
203 305 font-size: 23px;
204 306 display: block;
... ... @@ -223,37 +325,64 @@ const handleLogin = async () =&gt; {
223 325 position: relative;
224 326 z-index: 10;
225 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 342 :deep(.u-form-item) {
238 343 margin-bottom: 10px;
239 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 373 .login-btn {
244   - margin-top: 20px;
  374 + margin-top: 10px;
245 375 width: 100%;
246 376 height: 44px;
247 377 line-height: 44px;
248 378 border-radius: 4px;
249 379 font-size: 16px;
250   -
251 380 background: #0A86F4;
252 381 box-shadow: 0px 4px 6px 1px rgba(25,94,215,0.5);
253 382 border-radius: 23px;
254 383 }
255 384  
256   -
  385 +// 版权信息
257 386 .copyright {
258 387 width: 100%;
259 388 text-align: center;
... ...
pages/mine/index.vue
1 1 <template>
2   - <view class="user-center-page">
  2 + <view class="page-container">
3 3 <!-- 顶部用户信息栏 -->
4 4 <view class="header-bg">
5 5 <up-avatar
... ... @@ -114,7 +114,7 @@ export default {
114 114 </script>
115 115  
116 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 142 }
143 143  
144 144 .user-info-content {
145   - margin-left: 30rpx;
  145 + margin-left: 15px;
146 146 flex: 1;
147 147  
148 148 .user-name {
149   - font-size: 32rpx;
  149 + font-size: 16px;
150 150 font-weight: 600;
151 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 156 .user-phone {
157   - font-size: 26rpx;
  157 + font-size: 13px;
158 158 color: rgba(255, 255, 255, 0.9);
159 159 }
160 160 }
161 161 // 退出登录按钮
162 162 .logout-btn-wrap {
163   - margin: 200rpx 20rpx 20rpx;
  163 + margin: 100px 10px 10px;
164 164 }
165 165 // 登录按钮
166 166 .login-btn {
... ...
pages/workbench/index.vue
... ... @@ -10,7 +10,6 @@
10 10  
11 11 <!-- 蓝色装饰块 -->
12 12 <view class="blue-decor-block" v-show="!loading">
13   - <!-- ✅ 修复1:增加可选链+默认值,防止数据为undefined报错 -->
14 13 <text class="welcome-text u-line-1">你好{{userInfo?.user?.nickname || ''}},欢迎登录</text>
15 14 <text class="platform-name">全域智能运营管理平台</text>
16 15 </view>
... ... @@ -46,10 +45,10 @@
46 45 <u-image
47 46 :src="listItem.icon"
48 47 mode="aspectFit"
49   - width="80rpx"
50   - height="80rpx"
  48 + width="40px"
  49 + height="40px"
51 50 lazy-load
52   - radius="16rpx"
  51 + radius="8px"
53 52 class="block-icon"
54 53 ></u-image>
55 54 <text class="grid-text">{{ listItem.name }}</text>
... ... @@ -91,13 +90,11 @@ interface MenuItem {
91 90 const loading = ref(true);
92 91 const userStore = useUserStore();
93 92 const moduleList = ref<MenuItem[]>([]);
94   -// ✅ 修复2:声明全局的用户信息变量,模板可访问 + 初始化空对象兜底,杜绝undefined
95 93 const userInfo = ref<any>(cache.get(globalConfig.cache.userInfoKey) || userStore.userInfo || {});
96 94  
97 95 // 计算属性:过滤出有子节点的父模块(children 存在且长度 > 0)
98 96 const filteredModuleList = computed(() => {
99 97 return moduleList.value.filter(item => {
100   - // 确保 children 是数组且长度大于 0
101 98 return Array.isArray(item.children) && item.children.length > 0;
102 99 });
103 100 });
... ... @@ -105,24 +102,19 @@ const filteredModuleList = computed(() =&gt; {
105 102 onShow(async () => {
106 103 try {
107 104 loading.value = true;
108   - // ✅ 修复3:重新赋值全局userInfo,保证数据最新
109 105 userInfo.value = cache.get(globalConfig.cache.userInfoKey) || userStore.userInfo || {};
110 106  
111 107 // 登录状态判断(多维度校验,确保准确性)
112 108 const isLogin = () => {
113   - // 从缓存获取token(核心登录标识)
114 109 const token = cache.get(globalConfig.cache.tokenKey) || userStore.token;
115   - // 从全局变量取值,无需再声明局部变量
116 110 const userInfoVal = userInfo.value;
117 111 console.log('当前用户信息:', userInfoVal?.user);
118   - // 满足任一核心条件即视为已登录
119 112 return !!token && !!userInfoVal;
120 113 };
121 114  
122 115 // 未登录处理:跳转登录页,阻止后续逻辑执行
123 116 if (!isLogin()) {
124 117 uni.showToast({ title: '请先登录', icon: 'none', duration: 1500 });
125   - // 延迟跳转,确保提示语正常显示
126 118 setTimeout(() => {
127 119 uni.reLaunch({
128 120 url: '/pages/login/index',
... ... @@ -174,15 +166,14 @@ const handleMenuClick = (item: MenuItem) =&gt; {
174 166 top: 0;
175 167 left: 0;
176 168 width: 100%;
177   - height: 260px;
  169 + padding: 90px 15px 135px;
178 170 background: url("https://img.jichengshanshui.com.cn:28207/appimg/bg.jpg") no-repeat;
179 171 background-size: 100% 100%;
180 172 z-index: 1;
181 173 color:#fff;
182 174 text-align: left;
183   - padding-left: 20px;
  175 +
184 176 .welcome-text {
185   - padding-top: 80px;
186 177 font-size: 16px;
187 178 display: block;
188 179 margin-bottom: 10px;
... ... @@ -200,10 +191,10 @@ const handleMenuClick = (item: MenuItem) =&gt; {
200 191 .content-wrap {
201 192 position: relative;
202 193 z-index: 2;
203   - padding: 140px 0 0;
  194 + padding: 150px 0 0;
204 195 display: flex;
205 196 flex-direction: column;
206   - gap: 20rpx;
  197 + gap: 10px;
207 198 }
208 199  
209 200 .menu-card-wrap {
... ... @@ -212,25 +203,24 @@ const handleMenuClick = (item: MenuItem) =&gt; {
212 203  
213 204 .card-container {
214 205 --u-card-content-padding: 0;
215   - border-radius: 8rpx;
  206 + border-radius: 4px;
216 207 overflow: hidden;
217 208 }
218 209  
219 210 .card-content {
220 211 display: flex;
221 212 flex-wrap: wrap;
222   - gap: 20rpx;
223   - //padding: 20rpx; // ✅ 优化:增加内边距,内容不贴边
  213 + gap: 10px;
224 214 }
225 215  
226 216 .content-block {
227   - width: calc((100% - 3 * 20rpx) / 4);
  217 + width: calc((100% - 3 * 10px) / 4);
228 218 display: flex;
229 219 flex-direction: column;
230 220 align-items: center;
231 221 justify-content: center;
232   - padding: 14rpx 0;
233   - border-radius: 8rpx;
  222 + padding: 7px 0;
  223 + border-radius: 4px;
234 224 touch-action: manipulation;
235 225 transition: background-color 0.2s;
236 226 }
... ... @@ -245,10 +235,10 @@ const handleMenuClick = (item: MenuItem) =&gt; {
245 235 }
246 236  
247 237 .grid-text {
248   - font-size: 26rpx;
  238 + font-size: 13px;
249 239 color: #333;
250 240 text-align: center;
251   - margin-top: 10rpx;
  241 + margin-top: 5px;
252 242 display: block;
253 243 }
254 244 </style>
255 245 \ No newline at end of file
... ...