1
This commit is contained in:
550
Design/frontend_Design/Frontend_Design.md
Normal file
550
Design/frontend_Design/Frontend_Design.md
Normal file
@@ -0,0 +1,550 @@
|
||||
# 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` 分支自动部署到生产环境
|
||||
Reference in New Issue
Block a user