550 lines
14 KiB
Markdown
550 lines
14 KiB
Markdown
# 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 核心模块
|
|
|
|
1. **认证模块**: 处理用户登录、注册、密码重置等功能
|
|
2. **仪表盘模块**: 展示系统概览、数据统计和图表
|
|
3. **地图模块**: 展示地理信息、网格分布和污染热图
|
|
4. **任务管理模块**: 处理任务分配、查看和审核
|
|
5. **反馈管理模块**: 处理用户反馈的提交和管理
|
|
6. **人员管理模块**: 管理系统用户、角色和权限
|
|
7. **个人中心模块**: 查看和修改个人信息
|
|
|
|
## 3. 页面结构
|
|
|
|
### 3.1 基础布局
|
|
|
|
系统将采用以下几种基础布局:
|
|
|
|
1. **主布局**: 包含侧边导航栏、顶部导航栏和内容区域
|
|
2. **认证布局**: 用于登录、注册等不需要导航的页面
|
|
3. **空白布局**: 用于全屏展示的页面,如地图全屏模式
|
|
|
|
### 3.2 响应式设计
|
|
|
|
- 采用移动优先的响应式设计
|
|
- 断点设置:
|
|
- 移动端: < 768px
|
|
- 平板: 768px - 1024px
|
|
- 桌面: > 1024px
|
|
- 关键组件的响应式行为:
|
|
- 侧边栏在移动端变为可折叠抽屉
|
|
- 表格在移动端优化为卡片式布局
|
|
- 表单在移动端采用单列布局
|
|
|
|
## 4. 路由设计
|
|
|
|
### 4.1 路由结构
|
|
|
|
```javascript
|
|
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 路由守卫
|
|
|
|
```javascript
|
|
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 设计
|
|
|
|
```typescript
|
|
// 认证状态管理
|
|
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 基础请求客户端
|
|
|
|
```typescript
|
|
// 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 封装
|
|
|
|
```typescript
|
|
// 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 实现国际化。
|
|
|
|
```typescript
|
|
// 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 权限指令
|
|
|
|
```typescript
|
|
// 创建自定义指令控制元素的显示/隐藏
|
|
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 全局错误处理
|
|
|
|
```typescript
|
|
// 全局错误处理
|
|
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 错误处理
|
|
|
|
```typescript
|
|
// 统一处理 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` 分支自动部署到生产环境 |