Files
Environment-Monitoring-System/Design/frontend_Design/Frontend_Design.md
ChuXun 4ce487588a 1
2025-10-19 20:31:01 +08:00

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 核心模块

  1. 认证模块: 处理用户登录、注册、密码重置等功能
  2. 仪表盘模块: 展示系统概览、数据统计和图表
  3. 地图模块: 展示地理信息、网格分布和污染热图
  4. 任务管理模块: 处理任务分配、查看和审核
  5. 反馈管理模块: 处理用户反馈的提交和管理
  6. 人员管理模块: 管理系统用户、角色和权限
  7. 个人中心模块: 查看和修改个人信息

3. 页面结构

3.1 基础布局

系统将采用以下几种基础布局:

  1. 主布局: 包含侧边导航栏、顶部导航栏和内容区域
  2. 认证布局: 用于登录、注册等不需要导航的页面
  3. 空白布局: 用于全屏展示的页面,如地图全屏模式

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-showv-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 分支自动部署到生产环境