14 KiB
14 KiB
EMS 前端设计文档
1. 技术栈
- 前端框架: Vue 3
- 构建工具: Vite
- 状态管理: Pinia
- 路由管理: Vue Router
- UI 组件库: Element Plus
- HTTP 客户端: Axios
- CSS 预处理器: SCSS
- 类型检查: TypeScript
- 地图组件: Leaflet.js
- 图表库: ECharts
- 表单验证: Vee-Validate
- 国际化: Vue I18n
- 测试框架: Vitest
2. 架构设计
2.1 目录结构
ems-frontend/
├── public/ # 静态资源
├── src/
│ ├── api/ # API 请求模块
│ │ ├── auth.ts # 认证相关 API
│ │ ├── dashboard.ts # 仪表盘相关 API
│ │ └── ...
│ ├── assets/ # 静态资源
│ │ ├── styles/ # 全局样式
│ │ └── images/ # 图片资源
│ ├── components/ # 通用组件
│ │ ├── common/ # 基础组件
│ │ ├── layout/ # 布局组件
│ │ └── business/ # 业务组件
│ ├── composables/ # 组合式函数
│ ├── config/ # 配置文件
│ ├── constants/ # 常量定义
│ ├── directives/ # 自定义指令
│ ├── hooks/ # 自定义 Hooks
│ ├── locales/ # 国际化资源
│ ├── router/ # 路由配置
│ ├── stores/ # Pinia 状态管理
│ ├── types/ # TypeScript 类型定义
│ ├── utils/ # 工具函数
│ ├── views/ # 页面组件
│ │ ├── auth/ # 认证相关页面
│ │ ├── dashboard/ # 仪表盘页面
│ │ └── ...
│ ├── App.vue # 根组件
│ ├── main.ts # 入口文件
│ └── env.d.ts # 环境变量类型声明
├── .env # 环境变量
├── .env.development # 开发环境变量
├── .env.production # 生产环境变量
├── index.html # HTML 模板
├── package.json # 依赖配置
├── tsconfig.json # TypeScript 配置
└── vite.config.ts # Vite 配置
2.2 核心模块
- 认证模块: 处理用户登录、注册、密码重置等功能
- 仪表盘模块: 展示系统概览、数据统计和图表
- 地图模块: 展示地理信息、网格分布和污染热图
- 任务管理模块: 处理任务分配、查看和审核
- 反馈管理模块: 处理用户反馈的提交和管理
- 人员管理模块: 管理系统用户、角色和权限
- 个人中心模块: 查看和修改个人信息
3. 页面结构
3.1 基础布局
系统将采用以下几种基础布局:
- 主布局: 包含侧边导航栏、顶部导航栏和内容区域
- 认证布局: 用于登录、注册等不需要导航的页面
- 空白布局: 用于全屏展示的页面,如地图全屏模式
3.2 响应式设计
- 采用移动优先的响应式设计
- 断点设置:
- 移动端: < 768px
- 平板: 768px - 1024px
- 桌面: > 1024px
- 关键组件的响应式行为:
- 侧边栏在移动端变为可折叠抽屉
- 表格在移动端优化为卡片式布局
- 表单在移动端采用单列布局
4. 路由设计
4.1 路由结构
const routes = [
// 认证相关路由
{
path: '/auth',
component: AuthLayout,
children: [
{ path: 'login', component: LoginView },
{ path: 'register', component: RegisterView },
{ path: 'forgot-password', component: ForgotPasswordView },
{ path: 'reset-password', component: ResetPasswordView },
],
meta: { requiresAuth: false }
},
// 主应用路由
{
path: '/',
component: MainLayout,
children: [
{ path: '', component: DashboardView, meta: { requiresAuth: true, roles: ['ADMIN', 'DECISION_MAKER'] } },
{ path: 'map', component: MapView, meta: { requiresAuth: false } },
{ path: 'tasks', component: TasksView, meta: { requiresAuth: true } },
{ path: 'feedback', component: FeedbackView, meta: { requiresAuth: true } },
{ path: 'personnel', component: PersonnelView, meta: { requiresAuth: true, roles: ['ADMIN'] } },
{ path: 'profile', component: ProfileView, meta: { requiresAuth: true } },
]
},
// 错误页面路由
{ path: '/404', component: NotFoundView },
{ path: '/403', component: ForbiddenView },
{ path: '/:pathMatch(.*)*', redirect: '/404' }
]
4.2 路由守卫
router.beforeEach((to, from, next) => {
const authStore = useAuthStore();
// 检查路由是否需要认证
const requiresAuth = to.matched.some(record => record.meta.requiresAuth !== false);
// 检查用户角色权限
const hasRequiredRole = to.matched.every(record => {
if (!record.meta.roles) return true;
return authStore.user && record.meta.roles.includes(authStore.user.role);
});
if (requiresAuth && !authStore.isAuthenticated) {
// 未登录,重定向到登录页
next({ path: '/auth/login', query: { redirect: to.fullPath } });
} else if (requiresAuth && !hasRequiredRole) {
// 无权限访问
next({ path: '/403' });
} else {
// 正常导航
next();
}
});
5. 状态管理
5.1 Pinia Store 设计
// 认证状态管理
export const useAuthStore = defineStore('auth', {
state: () => ({
user: null,
token: localStorage.getItem('token'),
error: null,
}),
getters: {
isAuthenticated: state => !!state.token,
userRole: state => state.user?.role || null,
},
actions: {
async login(credentials) { /* 实现登录逻辑 */ },
async logout() { /* 实现登出逻辑 */ },
async fetchUserProfile() { /* 获取用户信息 */ },
}
});
// 任务状态管理
export const useTaskStore = defineStore('task', {
state: () => ({
tasks: [],
currentTask: null,
loading: false,
error: null,
}),
getters: {
tasksByStatus: state => status => state.tasks.filter(task => task.status === status),
},
actions: {
async fetchTasks(filters) { /* 获取任务列表 */ },
async fetchTaskById(id) { /* 获取任务详情 */ },
async assignTask(taskId, userId) { /* 分配任务 */ },
async submitTask(taskId, data) { /* 提交任务 */ },
}
});
// 其他模块状态管理...
5.2 持久化策略
- 使用 localStorage 存储认证 token
- 关键用户设置使用 localStorage 持久化
- 敏感数据不进行持久化,每次会话重新获取
6. API 请求封装
6.1 基础请求客户端
// api/client.ts
import axios from 'axios';
import { useAuthStore } from '@/stores/auth';
const apiClient = axios.create({
baseURL: import.meta.env.VITE_API_BASE_URL,
timeout: 10000,
headers: {
'Content-Type': 'application/json',
}
});
// 请求拦截器
apiClient.interceptors.request.use(
config => {
const authStore = useAuthStore();
if (authStore.token) {
config.headers.Authorization = `Bearer ${authStore.token}`;
}
return config;
},
error => Promise.reject(error)
);
// 响应拦截器
apiClient.interceptors.response.use(
response => response,
error => {
const authStore = useAuthStore();
// 处理 401 未授权错误
if (error.response && error.response.status === 401) {
authStore.logout();
router.push('/auth/login');
}
return Promise.reject(error);
}
);
export default apiClient;
6.2 模块化 API 封装
// api/auth.ts
import apiClient from './client';
export const authApi = {
login: (credentials) => apiClient.post('/auth/login', credentials),
register: (userData) => apiClient.post('/auth/signup', userData),
requestPasswordReset: (email) => apiClient.post('/auth/request-password-reset', { email }),
resetPassword: (data) => apiClient.post('/auth/reset-password', data),
};
// api/tasks.ts
import apiClient from './client';
export const taskApi = {
getTasks: (params) => apiClient.get('/management/tasks', { params }),
getTaskById: (id) => apiClient.get(`/management/tasks/${id}`),
assignTask: (taskId, data) => apiClient.post(`/management/tasks/${taskId}/assign`, data),
reviewTask: (taskId, data) => apiClient.post(`/management/tasks/${taskId}/review`, data),
};
// 其他模块 API...
7. 组件设计
7.1 基础组件
- AppButton: 统一按钮样式和行为
- AppCard: 卡片容器组件
- AppForm: 表单容器组件
- AppInput: 输入框组件
- AppSelect: 下拉选择组件
- AppTable: 表格组件
- AppPagination: 分页组件
- AppModal: 模态框组件
- AppAlert: 提示框组件
7.2 业务组件
- TaskCard: 任务卡片组件
- FeedbackForm: 反馈提交表单
- UserSelector: 用户选择器
- StatusBadge: 状态标签组件
- MapGrid: 地图网格组件
- PollutionTypeIcon: 污染类型图标
- TaskFilterPanel: 任务筛选面板
- StatisticCard: 统计数据卡片
8. 主题设计
8.1 色彩系统
- 主色: #1890ff (蓝色)
- 成功色: #52c41a (绿色)
- 警告色: #faad14 (黄色)
- 错误色: #f5222d (红色)
- 中性色:
- 标题: #262626
- 正文: #595959
- 辅助文字: #8c8c8c
- 禁用: #bfbfbf
- 边框: #d9d9d9
- 分割线: #f0f0f0
- 背景: #f5f5f5
- 表格头部: #fafafa
8.2 字体系统
- 主字体: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'PingFang SC', 'Helvetica Neue', Arial, sans-serif
- 代码字体: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, Courier, monospace
- 字体大小:
- 小号: 12px
- 正文: 14px
- 标题: 16px, 18px, 20px, 24px
8.3 间距系统
- 基础单位: 4px
- 间距尺寸:
- xs: 4px
- sm: 8px
- md: 16px
- lg: 24px
- xl: 32px
- xxl: 48px
8.4 阴影系统
- 轻微阴影: 0 2px 8px rgba(0, 0, 0, 0.15)
- 中等阴影: 0 4px 12px rgba(0, 0, 0, 0.15)
- 深度阴影: 0 8px 16px rgba(0, 0, 0, 0.15)
9. 国际化
支持中文和英文两种语言,使用 Vue I18n 实现国际化。
// locales/zh-CN.json
{
"auth": {
"login": "登录",
"register": "注册",
"forgotPassword": "忘记密码",
"email": "邮箱",
"password": "密码",
"confirmPassword": "确认密码"
},
"dashboard": {
"title": "仪表盘",
"stats": "统计数据",
"tasks": "任务概览",
"feedback": "反馈概览"
}
}
// locales/en-US.json
{
"auth": {
"login": "Login",
"register": "Register",
"forgotPassword": "Forgot Password",
"email": "Email",
"password": "Password",
"confirmPassword": "Confirm Password"
},
"dashboard": {
"title": "Dashboard",
"stats": "Statistics",
"tasks": "Task Overview",
"feedback": "Feedback Overview"
}
}
10. 权限控制
10.1 角色定义
- ADMIN: 系统管理员,拥有所有权限
- SUPERVISOR: 主管,负责任务分配和审核
- GRID_WORKER: 网格员,负责执行任务
- DECISION_MAKER: 决策者,查看数据和报表
10.2 权限指令
// 创建自定义指令控制元素的显示/隐藏
app.directive('permission', {
mounted(el, binding) {
const { value } = binding;
const authStore = useAuthStore();
if (value && !authStore.hasPermission(value)) {
el.parentNode && el.parentNode.removeChild(el);
}
}
});
// 使用示例
<button v-permission="'ADMIN'">删除用户</button>
<div v-permission="['ADMIN', 'SUPERVISOR']">管理内容</div>
11. 错误处理
11.1 全局错误处理
// 全局错误处理
app.config.errorHandler = (err, vm, info) => {
console.error('全局错误:', err);
// 上报错误到监控系统
errorReportingService.report(err, {
component: vm?.$options?.name,
info,
user: useAuthStore().user?.id
});
// 显示用户友好的错误提示
ElMessage.error('操作失败,请稍后重试');
};
11.2 API 错误处理
// 统一处理 API 错误
export const handleApiError = (error) => {
if (error.response) {
// 服务器响应错误
const { status, data } = error.response;
switch (status) {
case 400:
ElMessage.error(data.message || '请求参数错误');
break;
case 401:
ElMessage.error('会话已过期,请重新登录');
break;
case 403:
ElMessage.error('没有权限执行此操作');
break;
case 404:
ElMessage.error('请求的资源不存在');
break;
case 500:
ElMessage.error('服务器内部错误,请稍后重试');
break;
default:
ElMessage.error('请求失败,请稍后重试');
}
} else if (error.request) {
// 请求发出但没有收到响应
ElMessage.error('网络连接失败,请检查网络设置');
} else {
// 请求配置错误
ElMessage.error('请求配置错误');
}
return Promise.reject(error);
};
12. 性能优化
12.1 代码分割
- 使用动态导入实现路由级别的代码分割
- 将大型第三方库单独打包
12.2 资源优化
- 图片资源使用 WebP 格式并进行压缩
- 使用 SVG 图标代替图片图标
- 使用字体图标减少 HTTP 请求
12.3 渲染优化
- 使用虚拟滚动处理长列表
- 合理使用
v-show和v-if - 大型表格使用分页加载
- 使用
keep-alive缓存组件状态
13. 测试策略
13.1 单元测试
- 使用 Vitest 进行单元测试
- 重点测试工具函数、Hooks 和小型组件
- 使用 Vue Test Utils 测试组件
13.2 端到端测试
- 使用 Cypress 进行端到端测试
- 覆盖关键用户流程:
- 用户注册和登录
- 任务创建和分配
- 反馈提交和处理
14. 部署策略
14.1 构建配置
- 开发环境:
vite build --mode development - 测试环境:
vite build --mode staging - 生产环境:
vite build --mode production
14.2 环境变量
# .env.development
VITE_API_BASE_URL=http://localhost:8080/api
VITE_APP_TITLE=EMS开发环境
# .env.production
VITE_API_BASE_URL=/api
VITE_APP_TITLE=环境监测系统
14.3 CI/CD 流程
- 使用 GitHub Actions 自动化构建和部署
- 提交到
develop分支自动部署到测试环境 - 提交到
main分支自动部署到生产环境