Commit 37c26bd3e0113311b7648a2b92802693935200c8
1 parent
8957a764
巡查计划
Showing
8 changed files
with
590 additions
and
454 deletions
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 | ... | ... |
main.js
| ... | ... | @@ -3,7 +3,7 @@ import App from './App' |
| 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 = () => { |
| 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 = () => { |
| 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 = () => { |
| 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(() => { |
| 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(() => { |
| 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(() => { |
| 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 = () => { |
| 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 () => { |
| 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 () => { |
| 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 | ... | ... |