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 22 \ No newline at end of file
... ...
... ... @@ -3,7 +3,7 @@ import App from &#39;./App&#39;
3 3 import uviewPlus from '@/uni_modules/uview-plus'
4 4 // 导入 Pinia 实例(你的 stores/index.js 导出的 pinia)
5 5 import pinia from '@/pinia/index'
6   -
  6 +import EmptyView from '@/components/empty-view/empty-view.vue';
7 7 // #ifdef VUE3
8 8 import { createSSRApp } from 'vue'
9 9  
... ... @@ -16,7 +16,7 @@ export function createApp() {
16 16  
17 17 // 4. 注册 Pinia(核心:在 app 挂载前注册)
18 18 app.use(pinia)
19   -
  19 + app.component('EmptyView', EmptyView)
20 20 // 5. 返回 app + pinia(可选,便于调试)
21 21 return {
22 22 app,
... ...
pages-sub/daily/patrol-manage/add-patrol-record/index.vue
1 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 8 label-position="left"
10 9 >
11   - <!-- 1. 文本域区域 -->
12   - <u-form-item
13   - label="问题描述"
  10 + <!-- 1. 文本域(必填,无label但保留必填验证) -->
  11 + <up-form-item
14 12 prop="content"
15 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 18 maxlength="200"
21 19 :show-word-limit="true"
22   - :height="200"
23 20 border
  21 + :count="true"
24 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 28 prop="images"
33 29 class="form-item"
34 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 36 :action="uploadUrl"
39 37 :max-count="3"
40 38 :multiple="true"
... ... @@ -44,173 +42,131 @@
44 42 upload-text="选择图片"
45 43 del-color="#ff4d4f"
46 44 class="upload-wrap"
47   - ></u-upload>
  45 + ></up-upload>
48 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 51 prop="isWorkOrder"
56 52 class="form-item"
57 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 59 active-color="#1989fa"
  60 + direction="row"
63 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 79 type="primary"
72 80 size="default"
73   - @click="handleSubmit"
  81 + @click="submit"
74 82 :style="{ width: '100%', height: '88rpx', fontSize: '32rpx', borderRadius: 0 }"
75 83 >
76 84 提交
77   - </u-button>
  85 + </up-button>
78 86 </view>
79 87 </view>
80 88 </template>
81 89  
82 90 <script setup>
83 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 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 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 111 images: [
114 112 {
115 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 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 128 const handleAfterRead = (event) => {
138   - const { file } = event;
139   - // 模拟上传(实际项目替换为真实接口请求)
  129 + const {file} = event;
140 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 133 }, 500);
150 134 };
151   -
152   -// 删除图片
153 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 154 </script>
195 155  
196 156 <style scoped lang="scss">
197   -.form-page {
198   - background-color: #f8f8f8;
199   - min-height: 100vh;
  157 +// 基础布局:避免内容重叠
  158 +.u-page {
200 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 163 .form-item {
209 164 background-color: #fff;
210 165 border-radius: 12rpx;
211   - padding: 20rpx;
  166 + padding:0 30rpx;
212 167 margin-bottom: 20rpx;
213 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 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 178 .upload-wrap {
230 179 margin-top: 10rpx;
... ... @@ -233,22 +182,23 @@ const handleSubmit = () =&gt; {
233 182 .tips {
234 183 font-size: 24rpx;
235 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 202 position: fixed;
253 203 bottom: 0;
254 204 left: 0;
... ... @@ -256,27 +206,10 @@ const handleSubmit = () =&gt; {
256 206 z-index: 999;
257 207 padding: 0;
258 208 background-color: #f8f8f8;
259   - // 兼容微信小程序底部安全区
260 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 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 215 </style>
283 216 \ No newline at end of file
... ...
pages-sub/daily/patrol-manage/patrol-plan/index.vue
... ... @@ -2,200 +2,189 @@
2 2 <view class="page-container">
3 3 <!-- 顶部固定区域:Tabs + 搜索框 -->
4 4 <view class="top-fixed">
5   - <!-- uView Tabs 标签 -->
6 5 <u-tabs
7   - v-model="activeTab"
8 6 :list="tabList"
9 7 :is-scroll="false"
10 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 13 :inactiveStyle="{
16   - color: '#606266',
17   - transform: 'scale(1)'
  14 + color: '#606266',
  15 + transform: 'scale(1)'
18 16 }"
19 17 font-size="28rpx"
20 18 @change="handleTabChange"
21 19 ></u-tabs>
22 20  
23   - <!-- 道路名称搜索框 -->
24   - <u-search
  21 + <up-search
25 22 v-model="searchValue"
26 23 placeholder="请输入道路名称"
27 24 bg-color="#f5f5f5"
28   - border-radius="8rpx"
29   - :clearabled="true"
  25 + shape="round"
30 26 @search="handleSearch"
31   - @clear="handleSearchClear"
32 27 maxlength="50"
33   - style="margin: 10rpx 20rpx 0"
34   - ></u-search>
  28 + style="margin: 20rpx 20rpx 0"
  29 + ></up-search>
35 30 </view>
36 31  
37   - <!-- z-paging 分页列表 -->
  32 + <!-- 修复点1:绑定total + 修正ref名称 + 移除auto=false(改用默认auto=true更易控制) -->
38 33 <z-paging
  34 + ref="paging"
39 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 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 46 <view class="card-content">
51   - <!-- 道路名称 -->
52 47 <view class="row-item">
53 48 <text class="label">道路名称:</text>
54 49 <text class="value up-line-1">{{ item.roadName }}</text>
55 50 </view>
56   - <!-- 所属街道 -->
57 51 <view class="row-item">
58 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 54 </view>
61   - <!-- 养护级别 + 计划明细按钮 -->
62 55 <view class="row-item flex-between">
63 56 <view>
64 57 <text class="label">养护级别:</text>
65   - <text class="value">{{ item.maintainLevel }}</text>
  58 + <text class="value">{{ levelMap[item.levelId] || '未知级别' }}</text>
66 59 </view>
67 60 <text class="detail-btn" @click="gotoDetail(item)">计划明细</text>
68 61 </view>
69   - <!-- 计划类型 -->
70 62 <view class="row-item">
71 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 65 </view>
74   - <!-- 计划时间 -->
75 66 <view class="row-item">
76 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 69 </view>
79 70 </view>
80 71 </view>
81 72 </view>
82 73  
83   - <!-- 空数据占位 -->
84   - <template #empty>
85   - <view class="empty-wrap">
86   - <text class="empty-text">暂无相关计划数据</text>
87   - </view>
88   - </template>
89 74 </z-paging>
90 75 </view>
91 76 </template>
92 77  
93 78 <script setup>
94   -import { ref, reactive, onMounted } from 'vue';
  79 +import { ref } from 'vue';
95 80 import { onLoad, onShow } from '@dcloudio/uni-app';
  81 +import { inspectionPlanPage } from "@/api/patrol-manage/patrol-plan";
96 82 // Tabs 配置
97 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 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 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 163 const gotoDetail = (item) => {
173 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 169 onLoad(() => {
179   - loadData(true);
  170 + // auto=true时,z-paging会自动触发query,此处可省略手动调用;若需强制初始化,可加:
  171 + // paging.value?.triggerQuery();
180 172 });
181   -
182   -// 页面每次显示时刷新(包括回退)
  173 +// 仅在页面从明细页返回时,若有数据则刷新当前tab数据(避免干扰上拉加载)
183 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 179 </script>
191 180  
192 181 <style scoped lang="scss">
  182 +/* 样式保持不变,仅补充z-paging容器高度 */
193 183 .page-container {
194 184 min-height: 100vh;
195 185 background-color: #f8f8f8;
196 186 }
197 187  
198   -/* 顶部固定区域 */
199 188 .top-fixed {
200 189 position: fixed;
201 190 top: 0;
... ... @@ -207,9 +196,10 @@ onShow(() =&gt; {
207 196 box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
208 197 }
209 198  
210   -/* 计划卡片样式 */
  199 +/* 修复:确保列表容器不被顶部遮挡,且z-paging能识别滚动区域 */
211 200 .card-list {
212 201 padding: 170rpx 20rpx 20rpx;
  202 + /* 关键:给z-paging足够的滚动空间 */
213 203 }
214 204  
215 205 .plan-card {
... ... @@ -256,17 +246,5 @@ onShow(() =&gt; {
256 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 250 </style>
273 251 \ No newline at end of file
... ...
pages.json
... ... @@ -15,7 +15,8 @@
15 15 {
16 16 "path": "pages/workbench/index",
17 17 "style": {
18   - "navigationBarTitleText": "工作台"
  18 + "navigationBarTitleText": "工作台",
  19 + "navigationStyle": "custom"
19 20 }
20 21 },
21 22 {
... ... @@ -32,8 +33,7 @@
32 33 {
33 34 "path": "patrol-manage/patrol-plan/index",
34 35 "style": {
35   - "navigationBarTitleText": "巡查计划",
36   - "enablePullDownRefresh": false
  36 + "navigationBarTitleText": "巡查计划"
37 37 }
38 38 },
39 39 {
... ... @@ -150,7 +150,8 @@
150 150 "custom": {
151 151 "^u--(.*)": "@/uni_modules/uview-plus/components/u-$1/u-$1.vue",
152 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 157 "globalStyle": {
... ...
pages/login/index.vue
1 1 <template>
2 2 <view class="login-page">
3   - <!-- 登录表单区域 -->
  3 + <!-- 纯CSS渐变动效背景(替代粒子动画,兼容所有版本) -->
  4 + <view class="bg-animation"></view>
  5 +
  6 + <!-- 登录表单区域(悬浮层) -->
4 7 <view class="login-form">
  8 + <!-- 登录标题(简约大气) -->
  9 + <view class="login-title">园林登录</view>
  10 +
5 11 <!-- 账号输入框 -->
6 12 <view class="form-item">
7 13 <up-input
... ... @@ -12,9 +18,13 @@
12 18 input-align="left"
13 19 :disabled="isLoading"
14 20 @blur="checkAccount"
  21 + :custom-style="{
  22 + backgroundColor: 'rgba(255, 255, 255, 0.9)',
  23 + borderColor: '#e5e7eb'
  24 + }"
15 25 >
16 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 28 </template>
19 29 </up-input>
20 30 <!-- 账号错误提示 -->
... ... @@ -32,9 +42,13 @@
32 42 type="password"
33 43 :disabled="isLoading"
34 44 @blur="checkPassword"
  45 + :custom-style="{
  46 + backgroundColor: 'rgba(255, 255, 255, 0.9)',
  47 + borderColor: '#e5e7eb'
  48 + }"
35 49 >
36 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 52 </template>
39 53 </up-input>
40 54 <!-- 密码错误提示 -->
... ... @@ -48,6 +62,14 @@
48 62 size="large"
49 63 :loading="isLoading"
50 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 75 </up-button>
... ... @@ -76,8 +98,14 @@ const error = reactive({
76 98 // 实例化 Pinia 用户仓库
77 99 const userStore = useUserStore();
78 100  
79   -// 页面加载时校验登录态
  101 +// 页面加载时初始化
80 102 onMounted(() => {
  103 + // 检查登录态
  104 + checkLoginStatus();
  105 +});
  106 +
  107 +// 检查登录状态
  108 +const checkLoginStatus = () => {
81 109 try {
82 110 // 已登录则直接跳首页
83 111 if (userStore.isLogin) {
... ... @@ -93,7 +121,7 @@ onMounted(() =&gt; {
93 121 } catch (err) {
94 122 console.warn('检查登录状态失败:', err);
95 123 }
96   -});
  124 +};
97 125  
98 126 // 校验账号
99 127 const checkAccount = () => {
... ... @@ -124,7 +152,7 @@ const validateForm = () =&gt; {
124 152 return !error.account && !error.password;
125 153 };
126 154  
127   -// 登录处理(核心:补充跳转逻辑)
  155 +// 登录处理
128 156 const handleLogin = async () => {
129 157 if (!validateForm()) return;
130 158  
... ... @@ -138,24 +166,22 @@ const handleLogin = async () =&gt; {
138 166  
139 167 uni.showToast({ title: '登录成功', icon: 'success', duration: 1500 });
140 168  
141   - // 登录成功后跳转首页(优先tabBar,兼容普通页面)
  169 + // 登录成功后跳转首页
142 170 setTimeout(() => {
143   - // 方式1:跳tabBar首页(推荐,需在pages.json配置tabBar)
144 171 uni.switchTab({
145 172 url: globalConfig.router.tabBarList[1].path,
146 173 fail: (err) => {
147 174 console.warn('tabBar跳转失败,切换为普通跳转:', err);
148   - // 方式2:跳普通首页(非tabBar页面)
149 175 uni.redirectTo({ url: '/pages/workbench/index' });
150 176 }
151 177 });
152 178 }, 1500);
153   -
154 179 } catch (err) {
155 180 console.error('登录失败详情:', err);
156   - const errorMsg = err.message === '网络异常,请稍后重试'
157   - ? '网络异常,请稍后重试'
158   - : err.message || '登录失败,请检查账号密码';
  181 + const errorMsg =
  182 + err.message === '网络异常,请稍后重试'
  183 + ? '网络异常,请稍后重试'
  184 + : err.message || '登录失败,请检查账号密码';
159 185 uni.showToast({
160 186 title: errorMsg,
161 187 icon: 'none',
... ... @@ -168,39 +194,151 @@ const handleLogin = async () =&gt; {
168 194 </script>
169 195  
170 196 <style scoped lang="scss">
  197 +// 核心布局
171 198 .login-page {
172 199 min-height: 100vh;
173   - background-color: #f5f5f5;
  200 + position: relative;
174 201 padding: 40rpx 30rpx;
175 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 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 304 .form-item {
186 305 margin-bottom: 30rpx;
187 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 319 .login-btn {
198 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 344 </style>
207 345 \ No newline at end of file
... ...
pages/workbench/index.vue
1 1 <template>
  2 + <!-- 外层容器:包含蓝色块 + 原始内容 -->
2 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 54 </view>
31 55 </view>
32 56 </view>
33 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 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 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 130 </script>
106 131  
107 132 <style scoped>
  133 +/* 仅新增/保留必要样式,不修改卡片核心样式 */
108 134 .workbench-container {
109   - padding: 20rpx;
110   - background: #f8f8f8;
  135 + width: 100%;
111 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 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 180 \ No newline at end of file
  181 +</style>
152 182 \ No newline at end of file
... ...