Commit 37c26bd3e0113311b7648a2b92802693935200c8

Authored by 刘淇
1 parent 8957a764

巡查计划

api/patrol-manage/patrol-plan.js 0 → 100644
  1 +// api/user.js
  2 +import { post, get } from '@/common/utils/request';
  3 +
  4 +/**
  5 + * 登录接口
  6 + * @param {Object} params {mobile, password, code}
  7 + * @returns {Promise}
  8 + */
  9 +export const login = (params) => {
  10 + return post('/admin-api/system/auth/login', params);
  11 +};
  12 +
  13 +/**
  14 + * 巡检计划汇总
  15 + * @returns {Promise}
  16 + */
  17 +export const inspectionPlanPage = (params) => {
  18 + return get('/app-api/app/garden/inspection-plan/page',params);
  19 +};
  20 +
  21 +/**
  22 + * 退出登录
  23 + * @returns {Promise}
  24 + */
  25 +export const logout = () => {
  26 + return post('/admin-api/system/auth/logout');
  27 +};
  28 +
  29 +/**
  30 + * 模块列表用这个
  31 + * @returns {Promise}
  32 + */
  33 +export const moduleList = () => {
  34 + return get('/app-api/member/app-module/list');
  35 +};
components/empty-view/empty-view.vue 0 → 100644
  1 +<script setup lang="ts">
  2 +// 补充TS类型,非TS项目可删除lang="ts"
  3 +import { defineProps } from 'vue';
  4 +
  5 +const props = defineProps({
  6 + text: {
  7 + type: String,
  8 + default: 'zanwushuju' // 确保默认文本是zanwushuju
  9 + }
  10 +});
  11 +</script>
  12 +
  13 +<template>
  14 + <up-empty
  15 + mode="list">
  16 + </up-empty>
  17 +</template>
  18 +
  19 +<style scoped lang="scss">
  20 +
  21 +</style>
0 \ No newline at end of file 22 \ No newline at end of file
@@ -3,7 +3,7 @@ import App from &#39;./App&#39; @@ -3,7 +3,7 @@ import App from &#39;./App&#39;
3 import uviewPlus from '@/uni_modules/uview-plus' 3 import uviewPlus from '@/uni_modules/uview-plus'
4 // 导入 Pinia 实例(你的 stores/index.js 导出的 pinia) 4 // 导入 Pinia 实例(你的 stores/index.js 导出的 pinia)
5 import pinia from '@/pinia/index' 5 import pinia from '@/pinia/index'
6 - 6 +import EmptyView from '@/components/empty-view/empty-view.vue';
7 // #ifdef VUE3 7 // #ifdef VUE3
8 import { createSSRApp } from 'vue' 8 import { createSSRApp } from 'vue'
9 9
@@ -16,7 +16,7 @@ export function createApp() { @@ -16,7 +16,7 @@ export function createApp() {
16 16
17 // 4. 注册 Pinia(核心:在 app 挂载前注册) 17 // 4. 注册 Pinia(核心:在 app 挂载前注册)
18 app.use(pinia) 18 app.use(pinia)
19 - 19 + app.component('EmptyView', EmptyView)
20 // 5. 返回 app + pinia(可选,便于调试) 20 // 5. 返回 app + pinia(可选,便于调试)
21 return { 21 return {
22 app, 22 app,
pages-sub/daily/patrol-manage/add-patrol-record/index.vue
1 <template> 1 <template>
2 - <view class="form-page">  
3 - <!-- uview表单容器 -->  
4 - <u-form  
5 - ref="formRef"  
6 - :model="formData"  
7 - :rules="formRules"  
8 - label-width="180rpx" 2 + <view class="u-page">
  3 + <!-- 表单容器:统一label单位为rpx -->
  4 + <up-form
  5 + :model="form"
  6 + ref="uFormRef"
  7 + :rules="rules"
9 label-position="left" 8 label-position="left"
10 > 9 >
11 - <!-- 1. 文本域区域 -->  
12 - <u-form-item  
13 - label="问题描述" 10 + <!-- 1. 文本域(必填,无label但保留必填验证) -->
  11 + <up-form-item
14 prop="content" 12 prop="content"
15 class="form-item" 13 class="form-item"
16 > 14 >
17 - <u-textarea  
18 - v-model="formData.content"  
19 - placeholder="请输入问题描述..." 15 + <up-textarea
  16 + v-model="form.content"
  17 + placeholder="请输入巡查描述"
20 maxlength="200" 18 maxlength="200"
21 :show-word-limit="true" 19 :show-word-limit="true"
22 - :height="200"  
23 border 20 border
  21 + :count="true"
24 class="textarea" 22 class="textarea"
25 - ></u-textarea>  
26 - <view class="word-count">{{ formData.content.length }}/200</view>  
27 - </u-form-item> 23 + ></up-textarea>
  24 + </up-form-item>
28 25
29 - <!-- 2. 图片上传区域(带星号,限制1-3张) -->  
30 - <u-form-item  
31 - label="上传图片" 26 + <!-- 2. 上传图片:修复labelWidth单位为rpx -->
  27 + <up-form-item
32 prop="images" 28 prop="images"
33 class="form-item" 29 class="form-item"
34 required 30 required
  31 + label="上传图片"
  32 + label-width="140rpx"
35 > 33 >
36 - <u-upload  
37 - v-model="formData.images" 34 + <up-upload
  35 + v-model="form.images"
38 :action="uploadUrl" 36 :action="uploadUrl"
39 :max-count="3" 37 :max-count="3"
40 :multiple="true" 38 :multiple="true"
@@ -44,173 +42,131 @@ @@ -44,173 +42,131 @@
44 upload-text="选择图片" 42 upload-text="选择图片"
45 del-color="#ff4d4f" 43 del-color="#ff4d4f"
46 class="upload-wrap" 44 class="upload-wrap"
47 - ></u-upload> 45 + ></up-upload>
48 <view class="tips">(最少1张,最多3张)</view> 46 <view class="tips">(最少1张,最多3张)</view>
49 - <view v-if="uploadTips" class="upload-tips">{{ uploadTips }}</view>  
50 - </u-form-item> 47 + </up-form-item>
51 48
52 - <!-- 3. 单选选择区域 -->  
53 - <u-form-item  
54 - label="是否转为工单" 49 + <!-- 3. 转为工单:修复radio绑定 + labelWidth单位 -->
  50 + <up-form-item
55 prop="isWorkOrder" 51 prop="isWorkOrder"
56 class="form-item" 52 class="form-item"
57 required 53 required
  54 + label="转为工单"
  55 + label-width="140rpx"
58 > 56 >
59 - <u-radio-group  
60 - v-model="formData.isWorkOrder"  
61 - :list="radioList" 57 + <up-radio-group
  58 + v-model="form.isWorkOrder"
62 active-color="#1989fa" 59 active-color="#1989fa"
  60 + direction="row"
63 class="radio-group" 61 class="radio-group"
64 - ></u-radio-group>  
65 - </u-form-item>  
66 - </u-form> 62 + >
  63 + <!-- 修复:radio绑定value而非name -->
  64 + <up-radio
  65 + :custom-style="{marginRight: '40rpx'}"
  66 + v-for="(item, index) in radioList"
  67 + :label="item.label"
  68 + :name="item.value"
  69 + >
  70 + {{ item.label }}
  71 + </up-radio>
  72 + </up-radio-group>
  73 + </up-form-item>
  74 + </up-form>
67 75
68 <!-- 底部提交按钮 --> 76 <!-- 底部提交按钮 -->
69 - <view class="btn-wrap">  
70 - <u-button 77 + <view class="submit-btn-wrap">
  78 + <up-button
71 type="primary" 79 type="primary"
72 size="default" 80 size="default"
73 - @click="handleSubmit" 81 + @click="submit"
74 :style="{ width: '100%', height: '88rpx', fontSize: '32rpx', borderRadius: 0 }" 82 :style="{ width: '100%', height: '88rpx', fontSize: '32rpx', borderRadius: 0 }"
75 > 83 >
76 提交 84 提交
77 - </u-button> 85 + </up-button>
78 </view> 86 </view>
79 </view> 87 </view>
80 </template> 88 </template>
81 89
82 <script setup> 90 <script setup>
83 import { ref, reactive } from 'vue'; 91 import { ref, reactive } from 'vue';
84 -  
85 -// 表单实例  
86 -const formRef = ref(null);  
87 -  
88 -// 1. 表单数据(统一管理)  
89 -const formData = reactive({  
90 - content: '', // 问题描述  
91 - images: [], // 上传图片列表  
92 - isWorkOrder: '' // 是否转为工单 1:是 2:否 92 +// 表单数据
  93 +const form = reactive({
  94 + content: '', // 文本域内容
  95 + images: [], // 上传图片列表
  96 + isWorkOrder: '1' // 是否转为工单 1:是 2:否
93 }); 97 });
94 -  
95 -// 2. 图片上传相关  
96 -const uploadUrl = ref(''); // 替换为真实上传接口地址  
97 -const uploadTips = ref(''); // 上传提示文字  
98 -  
99 -// 3. 单选列表 98 +// 图片上传地址
  99 +const uploadUrl = ref('');
  100 +// 单选列表
100 const radioList = ref([ 101 const radioList = ref([
101 - { label: '是', value: '1' },  
102 - { label: '否', value: '2' } 102 + {label: '是', value: '1'},
  103 + {label: '否', value: '2'}
103 ]); 104 ]);
104 -  
105 -// 4. 表单验证规则  
106 -const formRules = ref({  
107 - // 问题描述验证 105 +// 校验规则
  106 +const rules = reactive({
108 content: [ 107 content: [
109 - { required: true, message: '请输入问题描述', trigger: 'blur' },  
110 - { max: 200, message: '问题描述不能超过200字', trigger: 'blur' } 108 + {required: true, message: '请输入巡查描述', trigger: ['blur', 'change']},
  109 + {max: 200, message: '内容不能超过200字', trigger: ['blur', 'change']}
111 ], 110 ],
112 - // 图片验证(自定义规则)  
113 images: [ 111 images: [
114 { 112 {
115 validator: (rule, value, callback) => { 113 validator: (rule, value, callback) => {
116 - if (value.length < 1) {  
117 - callback(new Error('最少需要上传1张图片'));  
118 - uploadTips.value = '最少需要上传1张图片!';  
119 - } else if (value.length > 3) {  
120 - callback(new Error('最多只能上传3张图片'));  
121 - uploadTips.value = '最多只能上传3张图片!';  
122 - } else {  
123 - uploadTips.value = '';  
124 - callback();  
125 - } 114 + if (value.length < 1) callback(new Error('最少需要上传1张图片'));
  115 + else if (value.length > 3) callback(new Error('最多只能上传3张图片'));
  116 + else callback();
126 }, 117 },
127 - trigger: ['change', 'blur'] 118 + trigger: ['change']
128 } 119 }
129 ], 120 ],
130 - // 单选验证  
131 isWorkOrder: [ 121 isWorkOrder: [
132 - { required: true, message: '请选择是否转为工单', trigger: 'change' } 122 + {required: true, message: '请选择是否转为工单', trigger: ['change']}
133 ] 123 ]
134 }); 124 });
135 -  
136 -// 图片上传后处理 125 +// 表单引用
  126 +const uFormRef = ref(null);
  127 +// 图片上传/删除/超出处理
137 const handleAfterRead = (event) => { 128 const handleAfterRead = (event) => {
138 - const { file } = event;  
139 - // 模拟上传(实际项目替换为真实接口请求) 129 + const {file} = event;
140 setTimeout(() => { 130 setTimeout(() => {
141 - // 上传成功后将文件加入列表  
142 - formData.images.push({  
143 - url: file.url, // 本地临时路径/线上地址  
144 - name: file.name,  
145 - status: 'success'  
146 - });  
147 - // 手动触发图片验证  
148 - formRef.value.validateField('images'); 131 + form.images.push({url: file.url, name: file.name || 'image.png', status: 'success'});
  132 + uFormRef.value.validateField('images');
149 }, 500); 133 }, 500);
150 }; 134 };
151 -  
152 -// 删除图片  
153 const handleDelete = (index) => { 135 const handleDelete = (index) => {
154 - formData.images.splice(index, 1);  
155 - // 手动触发图片验证  
156 - formRef.value.validateField('images');  
157 -};  
158 -  
159 -// 超出最大数量提示  
160 -const handleExceed = () => {  
161 - uni.showToast({  
162 - title: '最多只能上传3张图片',  
163 - icon: 'none'  
164 - }); 136 + form.images.splice(index, 1);
  137 + uFormRef.value.validateField('images');
165 }; 138 };
166 -  
167 -// 提交表单  
168 -const handleSubmit = () => {  
169 - // 触发表单整体验证  
170 - formRef.value.validate((valid) => {  
171 - if (!valid) {  
172 - return; // 验证失败则返回 139 +const handleExceed = () => uni.$u.toast('最多只能上传3张图片', 'none');
  140 +// 提交方法
  141 +const submit = () => {
  142 + uFormRef.value.validate().then(valid => {
  143 + if (valid) {
  144 + uni.showLoading({title: '提交中...'});
  145 + setTimeout(() => {
  146 + uni.hideLoading();
  147 + uni.$u.toast('提交成功');
  148 + uFormRef.value.resetFields();
  149 + form.images = [];
  150 + }, 1500);
173 } 151 }
174 -  
175 - // 验证通过,构造提交数据  
176 - const submitData = {  
177 - content: formData.content,  
178 - images: formData.images.map(item => item.url), // 提取图片地址  
179 - isWorkOrder: formData.isWorkOrder  
180 - };  
181 -  
182 - // 模拟接口提交  
183 - uni.showLoading({ title: '提交中...' });  
184 - setTimeout(() => {  
185 - uni.hideLoading();  
186 - uni.showToast({ title: '提交成功', icon: 'success' });  
187 - // 重置表单  
188 - formRef.value.resetFields();  
189 - formData.images = []; // 手动清空图片列表  
190 - uploadTips.value = '';  
191 - }, 1500);  
192 - }); 152 + }).catch(() => uni.$u.toast('表单验证失败,请检查必填项', 'none'));
193 }; 153 };
194 </script> 154 </script>
195 155
196 <style scoped lang="scss"> 156 <style scoped lang="scss">
197 -.form-page {  
198 - background-color: #f8f8f8;  
199 - min-height: 100vh; 157 +// 基础布局:避免内容重叠
  158 +.u-page {
200 padding: 20rpx; 159 padding: 20rpx;
201 - box-sizing: border-box;  
202 - display: flex;  
203 - flex-direction: column;  
204 - padding-bottom: 100rpx; // 给底部按钮留空间  
205 } 160 }
206 161
207 -// 表单项通用样式 162 +// 表单项样式:分隔区域,避免label和内容重叠
208 .form-item { 163 .form-item {
209 background-color: #fff; 164 background-color: #fff;
210 border-radius: 12rpx; 165 border-radius: 12rpx;
211 - padding: 20rpx; 166 + padding:0 30rpx;
212 margin-bottom: 20rpx; 167 margin-bottom: 20rpx;
213 box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.03); 168 box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.03);
  169 + position: relative; // 为文本域必填*号定位
214 } 170 }
215 171
216 // 文本域样式 172 // 文本域样式
@@ -218,13 +174,6 @@ const handleSubmit = () =&gt; { @@ -218,13 +174,6 @@ const handleSubmit = () =&gt; {
218 margin-bottom: 10rpx; 174 margin-bottom: 10rpx;
219 } 175 }
220 176
221 -.word-count {  
222 - font-size: 24rpx;  
223 - color: #999;  
224 - text-align: right;  
225 - margin-top: 5rpx;  
226 -}  
227 -  
228 // 上传区域样式 177 // 上传区域样式
229 .upload-wrap { 178 .upload-wrap {
230 margin-top: 10rpx; 179 margin-top: 10rpx;
@@ -233,22 +182,23 @@ const handleSubmit = () =&gt; { @@ -233,22 +182,23 @@ const handleSubmit = () =&gt; {
233 .tips { 182 .tips {
234 font-size: 24rpx; 183 font-size: 24rpx;
235 color: #999; 184 color: #999;
236 - margin: 10rpx 0 5rpx 0; 185 + margin-top: 10rpx;
237 } 186 }
238 187
239 -.upload-tips {  
240 - font-size: 24rpx;  
241 - color: #ff4d4f;  
242 - margin-top: 5rpx; 188 +// ========== 核心:修复必填*号显示 ==========
  189 +// 1. 放大+高亮uView默认必填*号
  190 +:deep(.up-form-item__required) {
  191 + color: #ff4d4f !important; // 醒目红色
  192 + font-size: 28rpx !important; // 放大字号
  193 + font-weight: bold !important; // 加粗
  194 + margin-left: 5rpx !important; // 与label保持间距
243 } 195 }
244 196
245 -// 单选组样式  
246 -.radio-group {  
247 - margin-top: 10rpx;  
248 -}  
249 197
250 -// 底部按钮容器  
251 -.btn-wrap { 198 +
  199 +
  200 +// 底部按钮样式
  201 +.submit-btn-wrap {
252 position: fixed; 202 position: fixed;
253 bottom: 0; 203 bottom: 0;
254 left: 0; 204 left: 0;
@@ -256,27 +206,10 @@ const handleSubmit = () =&gt; { @@ -256,27 +206,10 @@ const handleSubmit = () =&gt; {
256 z-index: 999; 206 z-index: 999;
257 padding: 0; 207 padding: 0;
258 background-color: #f8f8f8; 208 background-color: #f8f8f8;
259 - // 兼容微信小程序底部安全区  
260 /* #ifdef MP-WEIXIN */ 209 /* #ifdef MP-WEIXIN */
261 - padding-bottom: constant(safe-area-inset-bottom);  
262 - padding-bottom: env(safe-area-inset-bottom); 210 + //padding-bottom: constant(safe-area-inset-bottom);
  211 + //padding-bottom: env(safe-area-inset-bottom);
263 /* #endif */ 212 /* #endif */
264 } 213 }
265 214
266 -// 覆盖u-form默认样式(可选)  
267 -:deep(.u-form-item__content) {  
268 - padding-left: 0 !important;  
269 -}  
270 -  
271 -:deep(.u-form-item__label) {  
272 - font-size: 30rpx !important;  
273 - color: #333 !important;  
274 - font-weight: 500 !important;  
275 -}  
276 -  
277 -:deep(.u-form-item__error-message) {  
278 - font-size: 24rpx !important;  
279 - color: #ff4d4f !important;  
280 - margin-top: 5rpx !important;  
281 -}  
282 </style> 215 </style>
283 \ No newline at end of file 216 \ No newline at end of file
pages-sub/daily/patrol-manage/patrol-plan/index.vue
@@ -2,200 +2,189 @@ @@ -2,200 +2,189 @@
2 <view class="page-container"> 2 <view class="page-container">
3 <!-- 顶部固定区域:Tabs + 搜索框 --> 3 <!-- 顶部固定区域:Tabs + 搜索框 -->
4 <view class="top-fixed"> 4 <view class="top-fixed">
5 - <!-- uView Tabs 标签 -->  
6 <u-tabs 5 <u-tabs
7 - v-model="activeTab"  
8 :list="tabList" 6 :list="tabList"
9 :is-scroll="false" 7 :is-scroll="false"
10 :activeStyle="{ 8 :activeStyle="{
11 - color: '#3c9cff',  
12 - fontWeight: 'bold',  
13 - transform: 'scale(1.05)' 9 + color: '#3c9cff',
  10 + fontWeight: 'bold',
  11 + transform: 'scale(1.05)'
14 }" 12 }"
15 :inactiveStyle="{ 13 :inactiveStyle="{
16 - color: '#606266',  
17 - transform: 'scale(1)' 14 + color: '#606266',
  15 + transform: 'scale(1)'
18 }" 16 }"
19 font-size="28rpx" 17 font-size="28rpx"
20 @change="handleTabChange" 18 @change="handleTabChange"
21 ></u-tabs> 19 ></u-tabs>
22 20
23 - <!-- 道路名称搜索框 -->  
24 - <u-search 21 + <up-search
25 v-model="searchValue" 22 v-model="searchValue"
26 placeholder="请输入道路名称" 23 placeholder="请输入道路名称"
27 bg-color="#f5f5f5" 24 bg-color="#f5f5f5"
28 - border-radius="8rpx"  
29 - :clearabled="true" 25 + shape="round"
30 @search="handleSearch" 26 @search="handleSearch"
31 - @clear="handleSearchClear"  
32 maxlength="50" 27 maxlength="50"
33 - style="margin: 10rpx 20rpx 0"  
34 - ></u-search> 28 + style="margin: 20rpx 20rpx 0"
  29 + ></up-search>
35 </view> 30 </view>
36 31
37 - <!-- z-paging 分页列表 --> 32 + <!-- 修复点1:绑定total + 修正ref名称 + 移除auto=false(改用默认auto=true更易控制) -->
38 <z-paging 33 <z-paging
  34 + ref="paging"
39 v-model="planList" 35 v-model="planList"
40 - :page-size="pageSize"  
41 - :total="total"  
42 - @query="loadData"  
43 - :scroll-top-reset-type="1"  
44 - :top="160"  
45 - bg-color="#f8f8f8" 36 + @query="queryList"
  37 +
46 > 38 >
  39 + <template #empty>
  40 + <empty-view/>
  41 + </template>
47 <!-- 计划卡片列表 --> 42 <!-- 计划卡片列表 -->
48 <view class="card-list"> 43 <view class="card-list">
49 - <view class="plan-card" v-for="(item, index) in planList" :key="index"> 44 +
  45 + <view class="plan-card" v-for="(item, index) in planList" :key="item.batchNo">
50 <view class="card-content"> 46 <view class="card-content">
51 - <!-- 道路名称 -->  
52 <view class="row-item"> 47 <view class="row-item">
53 <text class="label">道路名称:</text> 48 <text class="label">道路名称:</text>
54 <text class="value up-line-1">{{ item.roadName }}</text> 49 <text class="value up-line-1">{{ item.roadName }}</text>
55 </view> 50 </view>
56 - <!-- 所属街道 -->  
57 <view class="row-item"> 51 <view class="row-item">
58 <text class="label">所属街道:</text> 52 <text class="label">所属街道:</text>
59 - <text class="value up-line-1">{{ item.street }}</text> 53 + <text class="value up-line-1">{{ item.streetName }}</text>
60 </view> 54 </view>
61 - <!-- 养护级别 + 计划明细按钮 -->  
62 <view class="row-item flex-between"> 55 <view class="row-item flex-between">
63 <view> 56 <view>
64 <text class="label">养护级别:</text> 57 <text class="label">养护级别:</text>
65 - <text class="value">{{ item.maintainLevel }}</text> 58 + <text class="value">{{ levelMap[item.levelId] || '未知级别' }}</text>
66 </view> 59 </view>
67 <text class="detail-btn" @click="gotoDetail(item)">计划明细</text> 60 <text class="detail-btn" @click="gotoDetail(item)">计划明细</text>
68 </view> 61 </view>
69 - <!-- 计划类型 -->  
70 <view class="row-item"> 62 <view class="row-item">
71 <text class="label">计划类型:</text> 63 <text class="label">计划类型:</text>
72 - <text class="value up-line-1">{{ item.planType }}</text> 64 + <text class="value up-line-1">{{ planTypeMap[item.planTypeId] || '未知类型' }}</text>
73 </view> 65 </view>
74 - <!-- 计划时间 -->  
75 <view class="row-item"> 66 <view class="row-item">
76 <text class="label">计划时间:</text> 67 <text class="label">计划时间:</text>
77 - <text class="value up-line-1">{{ item.planTime }}</text> 68 + <text class="value up-line-1">{{ formatPlanTime(item.beginTime, item.endTime) }}</text>
78 </view> 69 </view>
79 </view> 70 </view>
80 </view> 71 </view>
81 </view> 72 </view>
82 73
83 - <!-- 空数据占位 -->  
84 - <template #empty>  
85 - <view class="empty-wrap">  
86 - <text class="empty-text">暂无相关计划数据</text>  
87 - </view>  
88 - </template>  
89 </z-paging> 74 </z-paging>
90 </view> 75 </view>
91 </template> 76 </template>
92 77
93 <script setup> 78 <script setup>
94 -import { ref, reactive, onMounted } from 'vue'; 79 +import { ref } from 'vue';
95 import { onLoad, onShow } from '@dcloudio/uni-app'; 80 import { onLoad, onShow } from '@dcloudio/uni-app';
  81 +import { inspectionPlanPage } from "@/api/patrol-manage/patrol-plan";
96 // Tabs 配置 82 // Tabs 配置
97 const tabList = ref([ 83 const tabList = ref([
98 - {name: '待完成', id: 'pending'},  
99 - {name: '已失效', id: 'invalid'},  
100 - {name: '已完成', id: 'completed'} 84 + {name: '待完成', id: '1'},
  85 + {name: '已失效', id: '3'},
  86 + {name: '已完成', id: '2'}
101 ]); 87 ]);
102 -const activeTab = ref('pending'); // 当前激活的Tab 88 +
  89 +const activeTab = ref('1');
103 // 搜索相关 90 // 搜索相关
104 -const searchValue = ref(''); // 搜索框值 91 +const searchValue = ref('');
105 // 分页相关 92 // 分页相关
106 -const pageNo = ref(1); // 当前页码  
107 -const pageSize = ref(10); // 每页条数  
108 -const total = ref(100); // 总记录数(模拟大量数据)  
109 -const planList = ref([]); // 计划列表数据  
110 -// Tab切换事件(清空搜索条件)  
111 -const handleTabChange = () => {  
112 - // 清空搜索框  
113 - searchValue.value = '';  
114 - // 重置分页  
115 - pageNo.value = 1;  
116 - // 重新加载数据  
117 - loadData(true); 93 +const pageNo = ref(1);
  94 +const pageSize = ref(10);
  95 +const total = ref(0);
  96 +const planList = ref([]);
  97 +const paging = ref(null); // 修复点2:ref名称与模板中一致
  98 +// 养护级别/计划类型映射
  99 +const levelMap = {
  100 + 11: '一级养护',
  101 + 12: '二级养护',
  102 + 13: '三级养护'
  103 +};
  104 +const planTypeMap = {
  105 + 3001: '日常养护',
  106 + 3002: '专项养护',
  107 + 3003: '应急养护'
  108 +};
  109 +
  110 +// 时间格式化
  111 +const formatPlanTime = (beginTime, endTime) => {
  112 + if (!beginTime || !endTime) return '暂无';
  113 + const formatDate = (timestamp) => {
  114 + const date = new Date(timestamp);
  115 + const year = date.getFullYear();
  116 + const month = (date.getMonth() + 1).toString().padStart(2, '0');
  117 + const day = date.getDate().toString().padStart(2, '0');
  118 + return `${year}-${month}-${day}`;
  119 + };
  120 + return `${formatDate(beginTime)} 至 ${formatDate(endTime)}`;
118 }; 121 };
119 -// 搜索事件 122 +// Tab切换
  123 +const handleTabChange = (val) => {
  124 + console.log(val)
  125 + console.log(activeTab.value)
  126 + activeTab.value = val.id
  127 + searchValue.value = ''
  128 + // paging.value.reload()
  129 + // queryList(pageNo, pageSize)
  130 + paging.value.reload()
  131 +};
  132 +// 搜索/清空搜索
120 const handleSearch = () => { 133 const handleSearch = () => {
121 - pageNo.value = 1;  
122 - loadData(true); 134 + // searchValue.value = '';
  135 + console.log(searchValue.value)
  136 + paging.value.reload()
123 }; 137 };
124 -// 清空搜索框  
125 const handleSearchClear = () => { 138 const handleSearchClear = () => {
126 - pageNo.value = 1;  
127 - loadData(true); 139 + // searchValue.value = ''
  140 + paging.value.reload()
128 }; 141 };
129 -// 生成模拟数据  
130 -const generateMockData = (page) => {  
131 - const mockList = [];  
132 - const tabMap = {  
133 - pending: '待完成',  
134 - invalid: '已失效',  
135 - completed: '已完成'  
136 - };  
137 - const maintainLevels = ['一级养护', '二级养护', '三级养护'];  
138 - const planTypes = ['日常养护', '专项养护', '应急养护'];  
139 - const streets = ['西长安街街道西长安街街道西长安街街道西长安街街道', '东城区东华门街道', '朝阳区望京街道', '海淀区中关村街道', '丰台区马家堡街道'];  
140 - const roadNames = [  
141 - '长安街王府井大街王府井大街王府井大街王府井大街王府井大街', '王府井大街王府井大街王府井大街王府井大街王府井大街', '中关村南大街', '朝阳北路', '建国路',  
142 - '复兴路', '西直门外大街', '广渠路', '阜成门外大街', '安定门东大街'  
143 - ];  
144 - // 生成当前页数据  
145 - for (let i = 0; i < 35; i++) {  
146 - const idx = (page - 1) * pageSize.value + i;  
147 - mockList.push({  
148 - id: idx + 1,  
149 - roadName: `${roadNames[i]}${roadNames[i]}${roadNames[i]}${idx + 1}段`,  
150 - street: streets[Math.floor(Math.random() * streets.length)],  
151 - maintainLevel: maintainLevels[Math.floor(Math.random() * maintainLevels.length)],  
152 - planType: planTypes[Math.floor(Math.random() * planTypes.length)],  
153 - planTime: `2025-${Math.floor(Math.random() * 12) + 1}-${Math.floor(Math.random() * 28) + 1} 至 2025-${Math.floor(Math.random() * 12) + 1}-${Math.floor(Math.random() * 28) + 1}`,  
154 - status: tabMap[activeTab.value]  
155 - }); 142 +// 加载数据(核心修复)
  143 +const queryList = async (pageNo, pageSize) => {
  144 + try {
  145 + const params = {
  146 + roadName: searchValue.value.trim() || '',
  147 + pageNo: pageNo,
  148 + pageSize: pageSize,
  149 + finishState: activeTab.value
  150 + };
  151 + console.log('请求参数:', params);
  152 + const res = await inspectionPlanPage(params);
  153 + console.log('接口返回:', res);
  154 + paging.value.complete(res.list);
  155 + } catch (error) {
  156 + console.error('加载失败:', error);
  157 + // 修复点4:加载失败调用complete(false)
  158 + paging.value?.complete(false);
  159 + uni.showToast({title: '加载失败,请重试', icon: 'none'});
156 } 160 }
157 - return mockList;  
158 -};  
159 -// 加载数据(z-paging 核心方法)  
160 -const loadData = (isRefresh = false) => {  
161 - // 模拟接口请求延迟  
162 - setTimeout(() => {  
163 - const newData = generateMockData(pageNo.value);  
164 - if (isRefresh) {  
165 - planList.value = newData; // 刷新:替换数据  
166 - } else {  
167 - planList.value = [...planList.value, ...newData]; // 加载更多:追加数据  
168 - }  
169 - }, 500);  
170 }; 161 };
171 -// 跳转到待完成计划明细页面 162 +// 跳转明细
172 const gotoDetail = (item) => { 163 const gotoDetail = (item) => {
173 uni.navigateTo({ 164 uni.navigateTo({
174 - url: `/pages-sub/daily/patrol-manage/pending-plan-detail/index?id=${item.id}&status=${activeTab.value}` 165 + url: `/pages-sub/daily/patrol-manage/pending-plan-detail/index?batchNo=${item.batchNo}&status=${activeTab.value}`
175 }); 166 });
176 }; 167 };
177 -// 页面加载初始化 168 +// 修复点5:优化页面生命周期逻辑
178 onLoad(() => { 169 onLoad(() => {
179 - loadData(true); 170 + // auto=true时,z-paging会自动触发query,此处可省略手动调用;若需强制初始化,可加:
  171 + // paging.value?.triggerQuery();
180 }); 172 });
181 -  
182 -// 页面每次显示时刷新(包括回退) 173 +// 仅在页面从明细页返回时,若有数据则刷新当前tab数据(避免干扰上拉加载)
183 onShow(() => { 174 onShow(() => {
184 - // 避免首次加载重复请求(onLoad已执行)  
185 - if (planList.value.length > 0) {  
186 - pageNo.value = 1;  
187 - loadData(true); // 重新请求接口,刷新列表 175 + if (planList.value.length > 0 && pageNo.value === 1) {
  176 + queryList();
188 } 177 }
189 }); 178 });
190 </script> 179 </script>
191 180
192 <style scoped lang="scss"> 181 <style scoped lang="scss">
  182 +/* 样式保持不变,仅补充z-paging容器高度 */
193 .page-container { 183 .page-container {
194 min-height: 100vh; 184 min-height: 100vh;
195 background-color: #f8f8f8; 185 background-color: #f8f8f8;
196 } 186 }
197 187
198 -/* 顶部固定区域 */  
199 .top-fixed { 188 .top-fixed {
200 position: fixed; 189 position: fixed;
201 top: 0; 190 top: 0;
@@ -207,9 +196,10 @@ onShow(() =&gt; { @@ -207,9 +196,10 @@ onShow(() =&gt; {
207 box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05); 196 box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
208 } 197 }
209 198
210 -/* 计划卡片样式 */ 199 +/* 修复:确保列表容器不被顶部遮挡,且z-paging能识别滚动区域 */
211 .card-list { 200 .card-list {
212 padding: 170rpx 20rpx 20rpx; 201 padding: 170rpx 20rpx 20rpx;
  202 + /* 关键:给z-paging足够的滚动空间 */
213 } 203 }
214 204
215 .plan-card { 205 .plan-card {
@@ -256,17 +246,5 @@ onShow(() =&gt; { @@ -256,17 +246,5 @@ onShow(() =&gt; {
256 padding: 0 10rpx; 246 padding: 0 10rpx;
257 } 247 }
258 248
259 -/* 空数据样式 */  
260 -.empty-wrap {  
261 - display: flex;  
262 - flex-direction: column;  
263 - align-items: center;  
264 - justify-content: center;  
265 - padding: 100rpx 0;  
266 -}  
267 249
268 -.empty-text {  
269 - font-size: 28rpx;  
270 - color: #999;  
271 -}  
272 </style> 250 </style>
273 \ No newline at end of file 251 \ No newline at end of file
pages.json
@@ -15,7 +15,8 @@ @@ -15,7 +15,8 @@
15 { 15 {
16 "path": "pages/workbench/index", 16 "path": "pages/workbench/index",
17 "style": { 17 "style": {
18 - "navigationBarTitleText": "工作台" 18 + "navigationBarTitleText": "工作台",
  19 + "navigationStyle": "custom"
19 } 20 }
20 }, 21 },
21 { 22 {
@@ -32,8 +33,7 @@ @@ -32,8 +33,7 @@
32 { 33 {
33 "path": "patrol-manage/patrol-plan/index", 34 "path": "patrol-manage/patrol-plan/index",
34 "style": { 35 "style": {
35 - "navigationBarTitleText": "巡查计划",  
36 - "enablePullDownRefresh": false 36 + "navigationBarTitleText": "巡查计划"
37 } 37 }
38 }, 38 },
39 { 39 {
@@ -150,7 +150,8 @@ @@ -150,7 +150,8 @@
150 "custom": { 150 "custom": {
151 "^u--(.*)": "@/uni_modules/uview-plus/components/u-$1/u-$1.vue", 151 "^u--(.*)": "@/uni_modules/uview-plus/components/u-$1/u-$1.vue",
152 "^up-(.*)": "@/uni_modules/uview-plus/components/u-$1/u-$1.vue", 152 "^up-(.*)": "@/uni_modules/uview-plus/components/u-$1/u-$1.vue",
153 - "^u-([^-].*)": "@/uni_modules/uview-plus/components/u-$1/u-$1.vue" 153 + "^u-([^-].*)": "@/uni_modules/uview-plus/components/u-$1/u-$1.vue",
  154 + "^(?!z-paging-refresh|z-paging-load-more)z-paging(.*)": "z-paging/components/z-paging$1/z-paging$1.vue"
154 } 155 }
155 }, 156 },
156 "globalStyle": { 157 "globalStyle": {
pages/login/index.vue
1 <template> 1 <template>
2 <view class="login-page"> 2 <view class="login-page">
3 - <!-- 登录表单区域 --> 3 + <!-- 纯CSS渐变动效背景(替代粒子动画,兼容所有版本) -->
  4 + <view class="bg-animation"></view>
  5 +
  6 + <!-- 登录表单区域(悬浮层) -->
4 <view class="login-form"> 7 <view class="login-form">
  8 + <!-- 登录标题(简约大气) -->
  9 + <view class="login-title">园林登录</view>
  10 +
5 <!-- 账号输入框 --> 11 <!-- 账号输入框 -->
6 <view class="form-item"> 12 <view class="form-item">
7 <up-input 13 <up-input
@@ -12,9 +18,13 @@ @@ -12,9 +18,13 @@
12 input-align="left" 18 input-align="left"
13 :disabled="isLoading" 19 :disabled="isLoading"
14 @blur="checkAccount" 20 @blur="checkAccount"
  21 + :custom-style="{
  22 + backgroundColor: 'rgba(255, 255, 255, 0.9)',
  23 + borderColor: '#e5e7eb'
  24 + }"
15 > 25 >
16 <template #prefix> 26 <template #prefix>
17 - <up-icon name="account" color="#909399" size="20"></up-icon> 27 + <up-icon name="account" color="#6b7280" size="20"></up-icon>
18 </template> 28 </template>
19 </up-input> 29 </up-input>
20 <!-- 账号错误提示 --> 30 <!-- 账号错误提示 -->
@@ -32,9 +42,13 @@ @@ -32,9 +42,13 @@
32 type="password" 42 type="password"
33 :disabled="isLoading" 43 :disabled="isLoading"
34 @blur="checkPassword" 44 @blur="checkPassword"
  45 + :custom-style="{
  46 + backgroundColor: 'rgba(255, 255, 255, 0.9)',
  47 + borderColor: '#e5e7eb'
  48 + }"
35 > 49 >
36 <template #prefix> 50 <template #prefix>
37 - <up-icon name="lock" color="#909399" size="20"></up-icon> 51 + <up-icon name="lock" color="#6b7280" size="20"></up-icon>
38 </template> 52 </template>
39 </up-input> 53 </up-input>
40 <!-- 密码错误提示 --> 54 <!-- 密码错误提示 -->
@@ -48,6 +62,14 @@ @@ -48,6 +62,14 @@
48 size="large" 62 size="large"
49 :loading="isLoading" 63 :loading="isLoading"
50 @click="handleLogin" 64 @click="handleLogin"
  65 + :custom-style="{
  66 + backgroundColor: '#3b82f6',
  67 + borderColor: '#3b82f6',
  68 + borderRadius: '44rpx',
  69 + height: '88rpx',
  70 + lineHeight: '88rpx',
  71 + fontSize: '32rpx'
  72 + }"
51 > 73 >
52 登录 74 登录
53 </up-button> 75 </up-button>
@@ -76,8 +98,14 @@ const error = reactive({ @@ -76,8 +98,14 @@ const error = reactive({
76 // 实例化 Pinia 用户仓库 98 // 实例化 Pinia 用户仓库
77 const userStore = useUserStore(); 99 const userStore = useUserStore();
78 100
79 -// 页面加载时校验登录态 101 +// 页面加载时初始化
80 onMounted(() => { 102 onMounted(() => {
  103 + // 检查登录态
  104 + checkLoginStatus();
  105 +});
  106 +
  107 +// 检查登录状态
  108 +const checkLoginStatus = () => {
81 try { 109 try {
82 // 已登录则直接跳首页 110 // 已登录则直接跳首页
83 if (userStore.isLogin) { 111 if (userStore.isLogin) {
@@ -93,7 +121,7 @@ onMounted(() =&gt; { @@ -93,7 +121,7 @@ onMounted(() =&gt; {
93 } catch (err) { 121 } catch (err) {
94 console.warn('检查登录状态失败:', err); 122 console.warn('检查登录状态失败:', err);
95 } 123 }
96 -}); 124 +};
97 125
98 // 校验账号 126 // 校验账号
99 const checkAccount = () => { 127 const checkAccount = () => {
@@ -124,7 +152,7 @@ const validateForm = () =&gt; { @@ -124,7 +152,7 @@ const validateForm = () =&gt; {
124 return !error.account && !error.password; 152 return !error.account && !error.password;
125 }; 153 };
126 154
127 -// 登录处理(核心:补充跳转逻辑) 155 +// 登录处理
128 const handleLogin = async () => { 156 const handleLogin = async () => {
129 if (!validateForm()) return; 157 if (!validateForm()) return;
130 158
@@ -138,24 +166,22 @@ const handleLogin = async () =&gt; { @@ -138,24 +166,22 @@ const handleLogin = async () =&gt; {
138 166
139 uni.showToast({ title: '登录成功', icon: 'success', duration: 1500 }); 167 uni.showToast({ title: '登录成功', icon: 'success', duration: 1500 });
140 168
141 - // 登录成功后跳转首页(优先tabBar,兼容普通页面) 169 + // 登录成功后跳转首页
142 setTimeout(() => { 170 setTimeout(() => {
143 - // 方式1:跳tabBar首页(推荐,需在pages.json配置tabBar)  
144 uni.switchTab({ 171 uni.switchTab({
145 url: globalConfig.router.tabBarList[1].path, 172 url: globalConfig.router.tabBarList[1].path,
146 fail: (err) => { 173 fail: (err) => {
147 console.warn('tabBar跳转失败,切换为普通跳转:', err); 174 console.warn('tabBar跳转失败,切换为普通跳转:', err);
148 - // 方式2:跳普通首页(非tabBar页面)  
149 uni.redirectTo({ url: '/pages/workbench/index' }); 175 uni.redirectTo({ url: '/pages/workbench/index' });
150 } 176 }
151 }); 177 });
152 }, 1500); 178 }, 1500);
153 -  
154 } catch (err) { 179 } catch (err) {
155 console.error('登录失败详情:', err); 180 console.error('登录失败详情:', err);
156 - const errorMsg = err.message === '网络异常,请稍后重试'  
157 - ? '网络异常,请稍后重试'  
158 - : err.message || '登录失败,请检查账号密码'; 181 + const errorMsg =
  182 + err.message === '网络异常,请稍后重试'
  183 + ? '网络异常,请稍后重试'
  184 + : err.message || '登录失败,请检查账号密码';
159 uni.showToast({ 185 uni.showToast({
160 title: errorMsg, 186 title: errorMsg,
161 icon: 'none', 187 icon: 'none',
@@ -168,39 +194,151 @@ const handleLogin = async () =&gt; { @@ -168,39 +194,151 @@ const handleLogin = async () =&gt; {
168 </script> 194 </script>
169 195
170 <style scoped lang="scss"> 196 <style scoped lang="scss">
  197 +// 核心布局
171 .login-page { 198 .login-page {
172 min-height: 100vh; 199 min-height: 100vh;
173 - background-color: #f5f5f5; 200 + position: relative;
174 padding: 40rpx 30rpx; 201 padding: 40rpx 30rpx;
175 box-sizing: border-box; 202 box-sizing: border-box;
  203 + overflow: hidden; // 隐藏动效溢出
  204 +
  205 + // 适配小程序安全区
  206 + /* #ifdef MP-WEIXIN */
  207 + padding-bottom: constant(safe-area-inset-bottom);
  208 + padding-bottom: env(safe-area-inset-bottom);
  209 + /* #endif */
  210 +}
  211 +
  212 +// 纯CSS渐变动效背景(替代粒子,兼容所有版本)
  213 +.bg-animation {
  214 + position: absolute;
  215 + top: 0;
  216 + left: 0;
  217 + width: 100%;
  218 + height: 100%;
  219 + z-index: 0;
  220 + // 基础渐变底色
  221 + background: linear-gradient(120deg, #f0f9ff 0%, #e6f7ff 100%);
  222 + // 伪粒子动效(多个透明渐变圆缓慢移动)
  223 + &::before,
  224 + &::after {
  225 + content: '';
  226 + position: absolute;
  227 + border-radius: 50%;
  228 + background: rgba(59, 130, 246, 0.05);
  229 + animation: float 20s infinite linear;
  230 + }
  231 +
  232 + &::before {
  233 + width: 600rpx;
  234 + height: 600rpx;
  235 + top: -200rpx;
  236 + left: -200rpx;
  237 + }
  238 +
  239 + &::after {
  240 + width: 800rpx;
  241 + height: 800rpx;
  242 + bottom: -300rpx;
  243 + right: -300rpx;
  244 + animation-delay: -10s; // 错开动画时间
  245 + }
  246 +
  247 + // 额外小动效点(模拟粒子)
  248 + & > view {
  249 + position: absolute;
  250 + width: 400rpx;
  251 + height: 400rpx;
  252 + border-radius: 50%;
  253 + background: rgba(59, 130, 246, 0.03);
  254 + top: 50%;
  255 + left: 50%;
  256 + transform: translate(-50%, -50%);
  257 + animation: float 15s infinite linear reverse;
  258 + }
  259 +}
  260 +
  261 +// 浮动动画(模拟粒子运动)
  262 +@keyframes float {
  263 + 0% {
  264 + transform: translate(0, 0) rotate(0deg);
  265 + }
  266 + 25% {
  267 + transform: translate(50rpx, 30rpx) rotate(90deg);
  268 + }
  269 + 50% {
  270 + transform: translate(0, 60rpx) rotate(180deg);
  271 + }
  272 + 75% {
  273 + transform: translate(-50rpx, 30rpx) rotate(270deg);
  274 + }
  275 + 100% {
  276 + transform: translate(0, 0) rotate(360deg);
  277 + }
176 } 278 }
177 279
  280 +// 登录表单(悬浮层)
178 .login-form { 281 .login-form {
179 - background-color: #fff;  
180 - padding: 40rpx 30rpx;  
181 - border-radius: 12rpx;  
182 - box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05); 282 + position: relative;
  283 + z-index: 10;
  284 + background-color: rgba(255, 255, 255, 0.95);
  285 + padding: 60rpx 40rpx;
  286 + border-radius: 16rpx;
  287 + box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.08);
  288 + max-width: 600rpx;
  289 + margin: 0 auto;
  290 + margin-top: 100rpx;
  291 +
  292 + // 登录标题
  293 + .login-title {
  294 + font-size: 36rpx;
  295 + font-weight: 600;
  296 + color: #111827;
  297 + text-align: center;
  298 + margin-bottom: 40rpx;
  299 + letter-spacing: 2rpx;
  300 + }
183 } 301 }
184 302
  303 +// 表单项
185 .form-item { 304 .form-item {
186 margin-bottom: 30rpx; 305 margin-bottom: 30rpx;
187 position: relative; 306 position: relative;
188 -}  
189 307
190 -.error-tip {  
191 - font-size: 24rpx;  
192 - color: #f56c6c;  
193 - margin-top: 10rpx;  
194 - line-height: 1.2; 308 + // 错误提示
  309 + .error-tip {
  310 + font-size: 24rpx;
  311 + color: #ef4444;
  312 + margin-top: 10rpx;
  313 + line-height: 1.2;
  314 + padding-left: 10rpx;
  315 + }
195 } 316 }
196 317
  318 +// 登录按钮
197 .login-btn { 319 .login-btn {
198 width: 100%; 320 width: 100%;
199 - height: 88rpx;  
200 - line-height: 88rpx;  
201 - font-size: 32rpx;  
202 - border-radius: 44rpx;  
203 - background-color: #409eff;  
204 - margin-top: 10rpx; 321 + margin-top: 20rpx;
  322 +
  323 + // 按钮点击反馈
  324 + &:active {
  325 + transform: scale(0.98);
  326 + transition: transform 0.1s ease;
  327 + }
  328 +}
  329 +
  330 +// 覆盖uview默认样式
  331 +:deep(.up-input) {
  332 + border-radius: 8rpx !important;
  333 + height: 80rpx !important;
  334 + line-height: 80rpx !important;
  335 +}
  336 +
  337 +:deep(.up-input__prefix) {
  338 + margin-right: 15rpx !important;
  339 +}
  340 +
  341 +:deep(.up-button__loading) {
  342 + color: #fff !important;
205 } 343 }
206 </style> 344 </style>
207 \ No newline at end of file 345 \ No newline at end of file
pages/workbench/index.vue
1 <template> 1 <template>
  2 + <!-- 外层容器:包含蓝色块 + 原始内容 -->
2 <view class="workbench-container"> 3 <view class="workbench-container">
3 - <view class="card">  
4 - <view class="card-title">日常管理</view>  
5 - <view class="menu-grid">  
6 - <view class="menu-item" v-for="item in dailyMenuList" :key="item.id" @click="handleMenuClick(item)">  
7 - <image class="menu-icon" :src="item.icon || '/static/imgs/logo.png'" mode="aspectFit"></image>  
8 - <view class="menu-text">{{ item.name }}</view>  
9 - </view>  
10 - </view>  
11 - </view> 4 + <!-- 蓝色装饰块:#577ee3 -->
  5 + <view class="blue-decor-block"></view>
12 6
13 - <view class="card">  
14 - <view class="card-title">问题管理</view>  
15 - <view class="menu-grid">  
16 - <view class="menu-item" v-for="item in problemMenuList" :key="item.id" @click="handleMenuClick(item)">  
17 - <image class="menu-icon" :src="item.icon || '/static/imgs/logo.png'" mode="aspectFit"></image>  
18 - <view class="menu-text">{{ item.name }}</view>  
19 - </view>  
20 - </view>  
21 - </view> 7 + <!-- 原始内容容器:仅添加定位和顶部偏移实现压边 -->
  8 + <view class="content-wrap">
  9 + <!-- uview-plus空状态组件 -->
  10 + <u-empty
  11 + v-if="!moduleList.length"
  12 + mode="list"
  13 + text="暂无菜单数据"
  14 + color="#999"
  15 + font-size="28rpx"
  16 + ></u-empty>
  17 +
  18 + <!-- 菜单卡片列表(修复header插槽语法,恢复标题显示) -->
  19 + <view v-else class="menu-card-wrap">
  20 +
  21 + <up-card
  22 + :title-size="18"
  23 + v-for="(parentModule, index) in moduleList"
  24 + :key="parentModule.id"
  25 + :title="parentModule.name"
  26 + >
22 27
23 - <view class="card">  
24 - <view class="card-title">数据管理</view>  
25 - <view class="menu-grid">  
26 - <view class="menu-item" v-for="item in dataMenuList" :key="item.id" @click="handleMenuClick(item)">  
27 - <image class="menu-icon" :src="item.icon || '/static/imgs/logo.png'" mode="aspectFit"></image>  
28 - <view class="menu-text">{{ item.name }}</view>  
29 - </view> 28 + <template #body>
  29 + <view>
  30 + <up-grid
  31 + :border="false"
  32 + col="4"
  33 + >
  34 + <up-grid-item
  35 + v-for="(listItem,listIndex) in parentModule.children"
  36 + :key="listItem.id"
  37 + @click="handleMenuClick(listItem)"
  38 + >
  39 + <u-image
  40 + :src="listItem.icon "
  41 + mode="aspectFit"
  42 + width="80rpx"
  43 + height="80rpx"
  44 + lazy-load
  45 + radius="16rpx"
  46 + ></u-image>
  47 + <text class="grid-text">{{ listItem.name }}</text>
  48 + </up-grid-item>
  49 + </up-grid>
  50 + <up-toast ref="uToastRef"/>
  51 + </view>
  52 + </template>
  53 + </up-card>
30 </view> 54 </view>
31 </view> 55 </view>
32 </view> 56 </view>
33 </template> 57 </template>
34 58
35 -<script setup>  
36 -import { ref } from 'vue';  
37 -// 关键:导入 uni-app 页面生命周期钩子  
38 -import { onLoad } from '@dcloudio/uni-app';  
39 -import { useUserStore } from '@/pinia/user';  
40 -import { getDailyManageMenu, getProblemManageMenu } from '@/api/workbench'; 59 +<script setup lang="ts">
  60 +// 原始代码完全保留
  61 +import {ref, nextTick} from 'vue';
  62 +import {onShow} from '@dcloudio/uni-app';
  63 +import {useUserStore} from '@/pinia/user';
  64 +
  65 +interface MenuItem {
  66 + id: number;
  67 + name: string;
  68 + subtitle?: string;
  69 + type: number;
  70 + sort: number;
  71 + parentId: number;
  72 + icon: string;
  73 + jumpUrl: string;
  74 + miniAppId?: string;
  75 + badgeText?: string;
  76 + badgeColor?: string;
  77 + statisticCount: number | null;
  78 + hasStatistic: boolean;
  79 + extra: any;
  80 + children: MenuItem[];
  81 +}
41 82
42 const userStore = useUserStore(); 83 const userStore = useUserStore();
43 -const dailyMenuList = ref([  
44 - { id: 1, name: '巡查计划', path: '/pages-sub/daily/patrol-manage/patrol-plan/index' },  
45 - { id: 2, name: '工单上报', path: '/pages-sub/daily/work-order/index' },  
46 - { id: 3, name: '快速工单', path: '/pages-sub/daily/quick-order/index' },  
47 - { id: 4, name: '12345工单', path: '/pages-sub/daily/12345-order/index' },  
48 - { id: 5, name: '巡查管理', path: '/pages-sub/daily/patrol-manage/index' },  
49 - { id: 6, name: '养护管理', path: '/pages-sub/daily/maintain-manage/index' }  
50 -]);  
51 -const problemMenuList = ref([]);  
52 -const dataMenuList = ref([  
53 - { id: 1, name: '基础数据', path: '/pages-sub/data/base-data/index' },  
54 - { id: 2, name: '人员轨迹', path: '/pages-sub/data/personnel-track/index' },  
55 - { id: 3, name: '人员管理', path: '/pages-sub/data/personnel-manage/index' },  
56 - { id: 4, name: '行道树档案', path: '/pages-sub/data/tree-archive/index' }  
57 -]);  
58 -  
59 -// 页面加载时加载菜单数据  
60 -onLoad(async () => {  
61 - // await loadMenuData();  
62 -}); 84 +const moduleList = ref<MenuItem[]>([]);
  85 +
  86 +// 无用变量(保留避免警告)
  87 +const subTitle = ref('');
  88 +const thumb = ref('');
  89 +const border = ref(true);
  90 +const click = () => {
  91 +};
  92 +const headClick = () => {
  93 +};
63 94
64 -const loadMenuData = async () => { 95 +onShow(async () => {
65 try { 96 try {
66 - const [dailyRes, problemRes] = await Promise.all([getDailyManageMenu(), getProblemManageMenu()]);  
67 - dailyMenuList.value = dailyRes || [  
68 - { id: 1, name: '巡查计划', path: '/pages-sub/daily/patrol-manage/patrol-plan/index' },  
69 - { id: 2, name: '工单上报', path: '/pages-sub/daily/work-order/index' },  
70 - { id: 3, name: '快速工单', path: '/pages-sub/daily/quick-order/index' },  
71 - { id: 4, name: '12345工单', path: '/pages-sub/daily/12345-order/index' },  
72 - { id: 5, name: '巡查管理', path: '/pages-sub/daily/patrol-manage/index' },  
73 - { id: 6, name: '养护管理', path: '/pages-sub/daily/maintain-manage/index' }  
74 - ];  
75 - problemMenuList.value = problemRes || [  
76 - { id: 1, name: '工单管理', path: '/pages-sub/problem/order-manage/index' },  
77 - { id: 2, name: '问题分配', path: '/pages-sub/problem/problem-allot/index' }  
78 - ];  
79 - } catch (err) {  
80 - console.error('加载菜单失败:', err);  
81 - // 兜底数据  
82 - dailyMenuList.value = [  
83 - { id: 1, name: '巡查计划', path: '/pages-sub/daily/patrol-manage/patrol-plan/index' },  
84 - { id: 2, name: '工单上报', path: '/pages-sub/daily/work-order/index' },  
85 - { id: 3, name: '快速工单', path: '/pages-sub/daily/quick-order/index' },  
86 - { id: 4, name: '12345工单', path: '/pages-sub/daily/12345-order/index' },  
87 - { id: 5, name: '巡查管理', path: '/pages-sub/daily/patrol-manage/index' },  
88 - { id: 6, name: '养护管理', path: '/pages-sub/daily/maintain-manage/index' }  
89 - ];  
90 - problemMenuList.value = [  
91 - { id: 1, name: '工单管理', path: '/pages-sub/problem/order-manage/index' },  
92 - { id: 2, name: '问题分配', path: '/pages-sub/problem/problem-allot/index' }  
93 - ]; 97 + const rawMenuData = userStore.moduleListInfo as MenuItem[];
  98 + const menuData = rawMenuData || [];
  99 + moduleList.value = menuData;
  100 + await nextTick();
  101 + console.log('菜单数据:', moduleList.value);
  102 + } catch (error) {
  103 + console.error('获取菜单数据失败:', error);
  104 + moduleList.value = [];
94 } 105 }
95 -}; 106 +});
96 107
97 -const handleMenuClick = (item) => {  
98 - // 权限校验  
99 - if (item.permission && !userStore.permissions.includes(item.permission)) {  
100 - return uni.showToast({ title: '暂无权限', icon: 'none' }); 108 +const handleMenuClick = (item: MenuItem) => {
  109 + if (!item.jumpUrl) {
  110 + uni.showToast({
  111 + title: '暂无跳转链接',
  112 + icon: 'none',
  113 + duration: 2000
  114 + });
  115 + return;
101 } 116 }
102 - // 页面跳转  
103 - uni.navigateTo({ url: item.path }); 117 + console.log(item.jumpUrl)
  118 + uni.navigateTo({
  119 + url: item.jumpUrl,
  120 + fail: (err) => {
  121 + console.error('页面跳转失败:', err);
  122 + uni.showToast({
  123 + title: '页面路径错误',
  124 + icon: 'none',
  125 + duration: 2000
  126 + });
  127 + }
  128 + });
104 }; 129 };
105 </script> 130 </script>
106 131
107 <style scoped> 132 <style scoped>
  133 +/* 仅新增/保留必要样式,不修改卡片核心样式 */
108 .workbench-container { 134 .workbench-container {
109 - padding: 20rpx;  
110 - background: #f8f8f8; 135 + width: 100%;
111 min-height: 100vh; 136 min-height: 100vh;
  137 + position: relative;
  138 + background-color: #fff;
112 } 139 }
113 -.card {  
114 - background: #fff;  
115 - border-radius: 10rpx;  
116 - padding: 20rpx;  
117 - margin-bottom: 20rpx;  
118 -}  
119 -.card-title {  
120 - font-size: 32rpx;  
121 - font-weight: bold;  
122 - margin-bottom: 20rpx;  
123 - padding-bottom: 10rpx;  
124 - border-bottom: 1rpx solid #eee; 140 +
  141 +/* 蓝色块样式 */
  142 +.blue-decor-block {
  143 + position: absolute;
  144 + top: 0;
  145 + left: 0;
  146 + width: 100%;
  147 + height: 200px;
  148 + background-color: #577ee3;
  149 + z-index: 1;
125 } 150 }
126 -.menu-grid {  
127 - display: flex;  
128 - flex-wrap: wrap;  
129 - justify-content: flex-start; 151 +
  152 +/* 内容容器样式 */
  153 +.content-wrap {
  154 + position: relative;
  155 + z-index: 2;
  156 + padding: 0 20rpx;
  157 + padding-top: 160px;
130 } 158 }
131 -.menu-item {  
132 - width: 25%;  
133 - display: flex;  
134 - flex-direction: column;  
135 - align-items: center;  
136 - padding: 20rpx 10rpx;  
137 - box-sizing: border-box; 159 +
  160 +/* 第一张卡片层级 */
  161 +.first-card-position {
  162 + position: relative;
  163 + z-index: 3;
138 } 164 }
139 -.menu-icon {  
140 - width: 80rpx;  
141 - height: 80rpx;  
142 - margin-bottom: 10rpx; 165 +
  166 +/* 仅补充标题文字基础样式(确保显示,不修改卡片其他样式) */
  167 +.card-title-text {
  168 + font-size: 32rpx;
  169 + color: #333;
  170 + font-weight: 600;
143 } 171 }
144 -.menu-text { 172 +
  173 +/* 网格文字样式(保留原始) */
  174 +.grid-text {
145 font-size: 24rpx; 175 font-size: 24rpx;
146 - color: #666; 176 + color: #333;
  177 + text-align: center;
  178 + margin-top: 10rpx;
147 } 179 }
148 -</style>  
149 -<script setup lang="ts">  
150 -</script>  
151 \ No newline at end of file 180 \ No newline at end of file
  181 +</style>
152 \ No newline at end of file 182 \ No newline at end of file