# 海洋项目管理系统 - 产品交互文档(前端开发版) ## 文档说明 本文档面向前端开发人员,详细描述系统各功能模块的交互流程、状态管理、API调用时机和实现细节。 **版本**: v1.0 **更新日期**: 2026-01-25 **适用系统版本**: v1.0 --- ## 目录 1. [系统整体架构](#1-系统整体架构) 2. [认证与授权](#2-认证与授权) 3. [页面导航与路由](#3-页面导航与路由) 4. [登录模块](#4-登录模块) 5. [仪表盘模块](#5-仪表盘模块) 6. [项目管理模块](#6-项目管理模块) 7. [用户管理模块](#7-用户管理模块) 8. [项目统计模块](#8-项目统计模块) 9. [通用组件与工具](#9-通用组件与工具) 10. [状态管理](#10-状态管理) 11. [错误处理](#11-错误处理) 12. [性能优化](#12-性能优化) --- ## 1. 系统整体架构 ### 1.1 技术栈 ``` ┌─────────────────────────────────────────────────────────┐ │ 前端架构 │ ├─────────────────────────────────────────────────────────┤ │ 框架: React 18 + TypeScript │ │ 状态管理: Redux Toolkit + React Query │ │ 路由: React Router v6 │ │ UI组件库: Ant Design 5.x │ │ HTTP客户端: Axios │ │ 图表库: Apache ECharts │ │ 构建工具: Vite │ └─────────────────────────────────────────────────────────┘ ``` ### 1.2 目录结构 ``` src/ ├── api/ # API接口定义 │ ├── auth.ts # 认证相关API │ ├── projects.ts # 项目相关API │ ├── users.ts # 用户相关API │ └── statistics.ts # 统计相关API ├── components/ # 通用组件 │ ├── Layout/ # 布局组件 │ ├── Table/ # 表格组件 │ ├── Form/ # 表单组件 │ ├── Card/ # 卡片组件 │ ├── Tag/ # 标签组件 │ └── Modal/ # 模态框组件 ├── pages/ # 页面组件 │ ├── Login/ # 登录页 │ ├── Dashboard/ # 仪表盘 │ ├── Projects/ # 项目管理 │ ├── Users/ # 用户管理 │ └── Statistics/ # 项目统计 ├── store/ # 状态管理 │ ├── authSlice.ts # 认证状态 │ ├── projectSlice.ts # 项目状态 │ └── userSlice.ts # 用户状态 ├── hooks/ # 自定义Hooks │ ├── useAuth.ts # 认证Hook │ ├── useProjects.ts # 项目Hook │ └── useStatistics.ts # 统计Hook ├── utils/ # 工具函数 │ ├── request.ts # Axios封装 │ ├── format.ts # 格式化函数 │ └── validate.ts # 验证函数 ├── types/ # TypeScript类型定义 │ ├── auth.ts # 认证类型 │ ├── project.ts # 项目类型 │ └── user.ts # 用户类型 └── App.tsx # 应用入口 ``` ### 1.3 API基础配置 **Base URL**: `http://localhost:5000/api/v1` **请求拦截器**: ```typescript // utils/request.ts axios.interceptors.request.use((config) => { // 添加Token const token = localStorage.getItem('token'); if (token) { config.headers.Authorization = `Bearer ${token}`; } return config; }); ``` **响应拦截器**: ```typescript axios.interceptors.response.use( (response) => { // 统一处理成功响应 return response.data.data; }, (error) => { // 统一处理错误响应 handleApiError(error); return Promise.reject(error); } ); ``` --- ## 2. 认证与授权 ### 2.1 认证流程 ``` 用户登录 → 输入用户名密码 → 调用登录API → 获取Token → 存储Token → 跳转首页 ``` ### 2.2 Token管理 **Token存储位置**: `localStorage` **Token结构**: ```typescript interface AuthToken { token: string; token_type: string; user: UserInfo; } ``` **Token过期处理**: - Token有效期:24小时 - 每次API调用检查Token是否过期 - 过期时自动跳转登录页 ### 2.3 权限控制 **权限类型**: - `admin`: 管理员 - `market`: 市场部 - `other`: 其他部门 **权限检查Hook**: ```typescript // hooks/useAuth.ts export const useAuth = () => { const user = useSelector((state: RootState) => state.auth.user); const hasPermission = (permission: string) => { return checkPermission(user.role, permission); }; return { user, hasPermission }; }; ``` **页面级权限**: ```typescript // 路由守卫 const ProtectedRoute = ({ children, requiredRole }) => { const { user } = useAuth(); if (!user) return ; if (requiredRole && !hasPermission(user.role, requiredRole)) { return
您没有访问该页面的权限
; } return children; }; ``` **组件级权限**: ```typescript const { hasPermission } = useAuth(); {hasPermission('admin') && } ``` --- ## 3. 页面导航与路由 ### 3.1 路由配置 ```typescript // router.tsx const routes = [ { path: '/login', element: }, { path: '/', element: , children: [ { path: '', element: }, { path: 'dashboard', element: }, { path: 'projects', element: }, { path: 'projects/:id', element: }, { path: 'users', element: , meta: { requiresRole: 'admin' } }, { path: 'statistics', element: }, ], }, ]; ``` ### 3.2 面包屑导航 **实现方式**: ```typescript // components/Breadcrumb.tsx const Breadcrumb = () => { const location = useLocation(); const breadcrumbItems = generateBreadcrumb(location.pathname); return ; }; ``` **面包屑数据结构**: ```typescript interface BreadcrumbItem { label: string; path?: string; } ``` --- ## 4. 登录模块 ### 4.1 页面交互流程 ``` ┌─────────────────────────────────────────────┐ │ 1. 用户输入用户名和密码 │ └────────────┬──────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────┐ │ 2. 前端验证:检查必填字段 │ │ - 用户名不能为空 │ │ - 密码不能为空 │ └────────────┬──────────────────────────────┘ │ 验证通过 ▼ ┌─────────────────────────────────────────────┐ │ 3. 调用登录API: POST /auth/login │ │ 请求体: { username, password } │ └────────────┬──────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────┐ │ 4. API响应处理 │ │ - 成功: 存储Token,跳转首页 │ │ - 失败: 显示错误提示 │ └─────────────────────────────────────────────┘ ``` ### 4.2 表单验证规则 ```typescript // utils/validate.ts export const loginValidationSchema = { username: { required: '请输入用户名', minLength: { value: 3, message: '用户名至少3个字符' }, }, password: { required: '请输入密码', minLength: { value: 6, message: '密码至少6个字符' }, }, }; ``` ### 4.3 记住密码功能 **实现逻辑**: ```typescript // 本地存储加密的密码(可选) const savePassword = (username: string, password: string) => { const encrypted = btoa(`${username}:${password}`); localStorage.setItem('savedCredentials', encrypted); }; const loadSavedPassword = () => { const saved = localStorage.getItem('savedCredentials'); if (saved) { const decoded = atob(saved); const [username, password] = decoded.split(':'); return { username, password }; } return null; }; ``` ### 4.4 登录状态 **Loading状态**: - 点击登录按钮后,按钮显示loading状态 - 禁用登录按钮,防止重复提交 **错误提示**: - 用户名或密码错误:显示红色提示 - 网络错误:显示网络错误提示 ### 4.5 API调用 ```typescript // api/auth.ts export const login = (data: LoginRequest) => { return axios.post('/auth/login', data); }; ``` **请求参数**: ```typescript interface LoginRequest { username: string; password: string; } ``` **响应数据**: ```typescript interface LoginResponse { token: string; token_type: string; user: UserInfo; } ``` --- ## 5. 仪表盘模块 ### 5.1 页面加载流程 ``` ┌─────────────────────────────────────────────┐ │ 1. 页面初始化 │ └────────────┬──────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────┐ │ 2. 并行调用API: │ │ - GET /projects/statistics │ │ - GET /projects?page=1&page_size=5 │ └────────────┬──────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────┐ │ 3. 数据加载完成,渲染统计卡片和图表 │ └─────────────────────────────────────────────┘ ``` ### 5.2 统计数据获取 **API调用**: ```typescript // api/statistics.ts export const getBasicStatistics = (params?: StatisticsFilter) => { return axios.get('/projects/statistics', { params }); }; ``` **响应数据**: ```typescript interface StatisticsResponse { total_count: number; total_investment: number; total_contract_amount: number; total_settlement_amount: number; total_receipt_amount: number; total_payment_amount: number; avg_receipt_completion_rate: number; avg_payment_completion_rate: number; avg_cumulative_progress: number; } ``` ### 5.3 统计卡片渲染 **卡片数据映射**: ```typescript const statCards = [ { label: '项目总数', value: statistics.total_count, icon: '📊', }, { label: '进行中', value: calculateInProgressCount(statistics), icon: '🔄', }, { label: '已完成', value: calculateCompletedCount(statistics), icon: '✅', }, { label: '总合同金额(万元)', value: formatNumber(statistics.total_contract_amount), icon: '💰', }, ]; ``` ### 5.4 项目趋势图 **数据获取**: ```typescript export const getTimelineStatistics = (params: TimelineParams) => { return axios.get('/projects/statistics/timeline', { params }); }; ``` **图表配置**: ```typescript const chartOption = { xAxis: { type: 'category', data: ['1月', '2月', '3月', '4月', '5月', '6月'], }, yAxis: { type: 'value', }, series: [ { name: '项目数量', type: 'bar', data: [20, 30, 40, 35, 50, 45], }, { name: '合同金额', type: 'line', yAxisIndex: 1, data: [1000, 1500, 2000, 1800, 2500, 2200], }, ], }; ``` ### 5.5 最近项目列表 **数据获取**: ```typescript export const getRecentProjects = () => { return axios.get('/projects', { params: { page: 1, page_size: 5, sort_by: 'created_at', sort_order: 'desc', }, }); }; ``` **点击交互**: ```typescript const handleProjectClick = (projectId: number) => { navigate(`/projects/${projectId}`); }; ``` ### 5.6 刷新机制 **自动刷新**: - 每5分钟自动刷新统计数据 - 用户手动刷新:点击刷新按钮 **实现**: ```typescript useEffect(() => { const interval = setInterval(() => { refetchStatistics(); }, 5 * 60 * 1000); // 5分钟 return () => clearInterval(interval); }, []); ``` --- ## 6. 项目管理模块 ### 6.1 项目列表页 #### 6.1.1 页面加载流程 ``` ┌─────────────────────────────────────────────┐ │ 1. 页面初始化,加载项目列表 │ └────────────┬──────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────┐ │ 2. 调用API: GET /projects │ │ 查询参数: page, page_size, 筛选条件 │ └────────────┬──────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────┐ │ 3. 渲染表格,显示分页 │ └─────────────────────────────────────────────┘ ``` #### 6.1.2 搜索功能 **实时搜索**: ```typescript // 防抖处理,避免频繁请求 const debouncedSearch = useDebounce(keyword, 500); useEffect(() => { if (debouncedSearch) { fetchProjects({ keyword: debouncedSearch }); } }, [debouncedSearch]); ``` **搜索字段**: - 项目名称 - 合同编号 - 业主单位 #### 6.1.3 筛选功能 **筛选条件**: ```typescript interface ProjectFilter { project_no?: string; engineering_type?: string; project_department?: string; signing_date_start?: string; signing_date_end?: string; contract_amount_min?: number; contract_amount_max?: number; keyword?: string; } ``` **筛选交互**: 1. 选择筛选条件 2. 点击"筛选"按钮或按Enter键 3. 调用API获取筛选后的数据 4. 更新表格显示 **清空筛选**: ```typescript const handleClearFilters = () => { setFilters({}); fetchProjects({ page: 1 }); }; ``` #### 6.1.4 排序功能 **点击表头排序**: ```typescript const handleSort = (field: string) => { const newOrder = currentSort.field === field ? currentSort.order === 'asc' ? 'desc' : 'asc' : 'asc'; setSort({ field, order: newOrder }); fetchProjects({ sort_by: field, sort_order: newOrder, }); }; ``` **排序字段**: - 签订日期 (signing_date) - 合同金额 (contract_amount) - 创建时间 (created_at) #### 6.1.5 分页功能 **分页参数**: ```typescript interface PaginationParams { page: number; page_size: number; } ``` **分页交互**: ```typescript const handlePageChange = (page: number) => { fetchProjects({ page }); // 滚动到表格顶部 window.scrollTo({ top: 0, behavior: 'smooth' }); }; ``` #### 6.1.6 新建项目 **按钮点击**: ```typescript const handleCreateProject = () => { // 打开新建项目模态框 setModalVisible(true); setModalMode('create'); }; ``` **模态框表单**: ```typescript const [form] = Form.useForm(); const handleSubmit = async (values: ProjectFormData) => { try { // 表单验证 await form.validateFields(); // 调用API创建项目 await createProject(values); // 关闭模态框 setModalVisible(false); // 刷新列表 refetchProjects(); // 显示成功提示 message.success('项目创建成功'); } catch (error) { // 显示错误提示 message.error('创建失败,请重试'); } }; ``` #### 6.1.7 查看项目详情 **点击"查看"按钮**: ```typescript const handleViewProject = (projectId: number) => { navigate(`/projects/${projectId}`); }; ``` #### 6.1.8 编辑项目 **点击"编辑"按钮**: ```typescript const handleEditProject = async (projectId: number) => { try { // 获取项目详情 const project = await getProjectDetail(projectId); // 填充表单 form.setFieldsValue(project); // 打开编辑模态框 setModalVisible(true); setModalMode('edit'); setEditingId(projectId); } catch (error) { message.error('获取项目信息失败'); } }; ``` **权限控制**: - 市场部用户:只能编辑自己创建的项目 - 其他部门用户:只能编辑财务、成本等字段 #### 6.1.9 删除项目 **点击"删除"按钮**: ```typescript const handleDeleteProject = (projectId: number) => { Modal.confirm({ title: '确认删除', content: '确定要删除该项目吗?此操作不可恢复。', onOk: async () => { try { await deleteProject(projectId); message.success('项目删除成功'); refetchProjects(); } catch (error) { message.error('删除失败,请重试'); } }, }); }; ``` **权限控制**: - 市场部用户:只能删除自己创建的项目 - 管理员:可以删除所有项目 ### 6.2 项目详情页 #### 6.2.1 页面加载流程 ``` ┌─────────────────────────────────────────────┐ │ 1. 页面初始化,从URL获取项目ID │ └────────────┬──────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────┐ │ 2. 调用API: GET /projects/:id │ └────────────┬──────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────┐ │ 3. 渲染项目详情,按分组显示 │ └─────────────────────────────────────────────┘ ``` #### 6.2.2 数据获取 **API调用**: ```typescript export const getProjectDetail = (id: number) => { return axios.get(`/projects/${id}`); }; ``` #### 6.2.3 信息分组显示 **分组配置**: ```typescript const infoGroups = [ { title: '基本信息', fields: [ { label: '合同编号', key: 'project_no' }, { label: '项目名称', key: 'name' }, { label: '工程类别', key: 'engineering_type', render: renderTag }, { label: '合同金额', key: 'contract_amount', render: formatMoney }, ], }, { title: '项目时间', fields: [ { label: '签订日期', key: 'signing_date', render: formatDate }, { label: '开工日期', key: 'start_date', render: formatDate }, { label: '计划竣工日期', key: 'planned_end_date', render: formatDate }, { label: '实际竣工日期', key: 'actual_end_date', render: formatDate }, ], }, // ... 其他分组 ]; ``` #### 6.2.4 编辑项目 **权限控制**: ```typescript const canEdit = () => { if (user.role === 'admin') return true; if (user.role === 'market' && project.created_by === user.id) return true; if (user.role === 'other') return 'partial'; // 部分字段可编辑 return false; }; ``` **表单字段权限**: - 市场部用户:所有字段可编辑 - 其他部门用户:只能编辑成本、财务、进度相关字段 - 基础信息字段(合同编号、项目名称等)设置为只读 #### 6.2.5 删除项目 **二次确认**: ```typescript Modal.confirm({ title: '确认删除', content: (

确定要删除该项目吗?

项目名称:{project.name}

合同编号:{project.project_no}

此操作不可恢复!

), okText: '确认删除', okType: 'danger', onOk: handleDelete, }); ``` #### 6.2.6 返回列表 ```typescript const handleBack = () => { navigate('/projects'); }; ``` ### 6.3 项目表单 #### 6.3.1 表单字段验证 **必填字段**: ```typescript const requiredFields = { project_no: '合同编号', name: '项目名称', engineering_type: '工程类别', contract_amount: '合同金额', }; ``` **验证规则**: ```typescript export const projectValidationRules = { project_no: [ { required: true, message: '请输入合同编号' }, { pattern: /^[A-Z0-9]+$/, message: '合同编号格式不正确' }, ], name: [ { required: true, message: '请输入项目名称' }, { min: 2, message: '项目名称至少2个字符' }, ], engineering_type: [ { required: true, message: '请选择工程类别' }, ], contract_amount: [ { required: true, message: '请输入合同金额' }, { type: 'number', min: 0, message: '合同金额必须大于0' }, ], signing_date: [ { required: true, message: '请选择签订日期' }, ], // ... 其他字段验证 }; ``` #### 6.3.2 表单提交 **提交前验证**: ```typescript const handleSubmit = async () => { try { // 验证所有字段 const values = await form.validateFields(); // 格式化数据 const formattedData = formatProjectData(values); // 提交API if (mode === 'create') { await createProject(formattedData); } else { await updateProject(editingId, formattedData); } // 成功处理 message.success(mode === 'create' ? '项目创建成功' : '项目更新成功'); handleClose(); } catch (error) { // 错误处理 if (error.errorFields) { // 表单验证错误 message.error('请检查表单填写是否正确'); } else { // API错误 message.error('操作失败,请重试'); } } }; ``` #### 6.3.3 表单字段分组 **分组配置**: ```typescript const formGroups = [ { title: '基本信息', fields: ['project_no', 'name', 'engineering_type', 'contract_amount'], }, { title: '项目时间', fields: ['signing_date', 'start_date', 'planned_end_date', 'actual_end_date'], }, // ... 其他分组 ]; ``` #### 6.3.4 动态字段显示 **根据角色显示/隐藏字段**: ```typescript const getVisibleFields = (role: string, mode: 'create' | 'edit') => { const fields = allFields; // 其他部门用户编辑时,隐藏基础信息字段 if (role === 'other' && mode === 'edit') { return fields.filter(field => !isBaseField(field.key)); } return fields; }; ``` --- ## 7. 用户管理模块 ### 7.1 权限控制 **页面级权限**: ```typescript // 只有管理员可以访问用户管理页面 const UserManagementPage = () => { const { user, hasPermission } = useAuth(); if (!hasPermission('admin')) { return
您没有访问该页面的权限
; } return ; }; ``` ### 7.2 用户列表 #### 7.2.1 数据获取 **API调用**: ```typescript export const getUsers = (params: UserListParams) => { return axios.get('/users', { params }); }; ``` **查询参数**: ```typescript interface UserListParams { page: number; page_size: number; department?: string; role?: string; keyword?: string; } ``` #### 7.2.2 搜索用户 **搜索字段**: - 用户名 - 真实姓名 - 邮箱 #### 7.2.3 筛选用户 **筛选条件**: - 部门 - 角色 ### 7.3 新建用户 #### 7.3.1 表单字段 ```typescript interface CreateUserForm { username: string; // 必填,唯一 password: string; // 必填,最少6位 real_name: string; // 必填 department: string; // 必填 role: string; // 必填:admin/market/other email?: string; // 可选,唯一 phone?: string; // 可选 } ``` #### 7.3.2 表单验证 ```typescript export const userValidationRules = { username: [ { required: true, message: '请输入用户名' }, { min: 3, message: '用户名至少3个字符' }, { max: 20, message: '用户名最多20个字符' }, { pattern: /^[a-zA-Z0-9_]+$/, message: '用户名只能包含字母、数字和下划线' }, ], password: [ { required: true, message: '请输入密码' }, { min: 6, message: '密码至少6个字符' }, { max: 20, message: '密码最多20个字符' }, ], real_name: [ { required: true, message: '请输入真实姓名' }, { min: 2, message: '真实姓名至少2个字符' }, ], email: [ { type: 'email', message: '邮箱格式不正确' }, ], phone: [ { pattern: /^1[3-9]\d{9}$/, message: '手机号格式不正确' }, ], }; ``` #### 7.3.3 提交处理 ```typescript const handleCreateUser = async (values: CreateUserForm) => { try { await createUser(values); message.success('用户创建成功'); refetchUsers(); handleClose(); } catch (error) { if (error.response?.data?.error_code === '2002') { message.error('用户名已存在'); } else if (error.response?.data?.error_code === '1001') { message.error('邮箱已被使用'); } else { message.error('创建失败,请重试'); } } }; ``` ### 7.4 编辑用户 #### 7.4.1 加载用户信息 ```typescript const handleEditUser = async (userId: number) => { try { const user = await getUserDetail(userId); form.setFieldsValue(user); setModalVisible(true); setEditingId(userId); } catch (error) { message.error('获取用户信息失败'); } }; ``` #### 7.4.2 用户名只读 ```typescript ``` ### 7.5 删除用户 ```typescript const handleDeleteUser = (userId: number) => { Modal.confirm({ title: '确认删除', content: '确定要删除该用户吗?', onOk: async () => { try { await deleteUser(userId); message.success('用户删除成功'); refetchUsers(); } catch (error) { message.error('删除失败,请重试'); } }, }); }; ``` ### 7.6 重置密码 ```typescript const handleResetPassword = (userId: number) => { Modal.confirm({ title: '重置密码', content: ( <>

确定要重置该用户的密码吗?

), onOk: async (values) => { try { await resetUserPassword(userId, values.newPassword); message.success('密码重置成功'); } catch (error) { message.error('密码重置失败'); } }, }); }; ``` --- ## 8. 项目统计模块 ### 8.1 基础统计 #### 8.1.1 数据获取 ```typescript export const getBasicStatistics = (filters?: StatisticsFilter) => { return axios.get('/projects/statistics', { params: filters }); }; ``` #### 8.1.2 统计卡片渲染 ```typescript const statCards = [ { title: '项目总数', value: statistics.total_count, icon: '📊', color: '#1890ff', }, { title: '总投资金额(万元)', value: formatNumber(statistics.total_investment), icon: '💰', color: '#52c41a', }, { title: '总合同金额(万元)', value: formatNumber(statistics.total_contract_amount), icon: '💵', color: '#1890ff', }, { title: '总收款金额(万元)', value: formatNumber(statistics.total_receipt_amount), icon: '📥', color: '#52c41a', }, { title: '总付款金额(万元)', value: formatNumber(statistics.total_payment_amount), icon: '📤', color: '#fa8c16', }, { title: '平均收款完成率', value: formatPercentage(statistics.avg_receipt_completion_rate), icon: '📈', color: '#1890ff', }, { title: '平均付款完成率', value: formatPercentage(statistics.avg_payment_completion_rate), icon: '📉', color: '#52c41a', }, { title: '平均项目进度', value: formatPercentage(statistics.avg_cumulative_progress), icon: '⚡', color: '#1890ff', }, ]; ``` ### 8.2 分组统计 #### 8.2.1 数据获取 ```typescript export const getGroupStatistics = (params: GroupStatisticsParams) => { return axios.get('/projects/statistics/group', { params }); }; ``` **参数**: ```typescript interface GroupStatisticsParams { group_by: 'engineering_type' | 'project_department'; signing_date_start?: string; signing_date_end?: string; } ``` #### 8.2.2 饼图渲染 ```typescript const pieChartOption = { tooltip: { trigger: 'item', formatter: '{a}
{b}: {c} ({d}%)', }, series: [ { name: '工程类别', type: 'pie', radius: '50%', data: groupStatistics.map(item => ({ value: item.count, name: item.engineering_type, })), }, ], }; ``` #### 8.2.3 柱状图渲染 ```typescript const barChartOption = { xAxis: { type: 'category', data: groupStatistics.map(item => item.engineering_type), }, yAxis: { type: 'value', }, series: [ { name: '合同金额', type: 'bar', data: groupStatistics.map(item => item.total_contract_amount), }, ], }; ``` ### 8.3 时间维度统计 #### 8.3.1 数据获取 ```typescript export const getTimelineStatistics = (params: TimelineParams) => { return axios.get('/projects/statistics/timeline', { params }); }; ``` **参数**: ```typescript interface TimelineParams { time_field: 'signing_date' | 'start_date' | 'planned_end_date'; group_by: 'day' | 'month' | 'year'; start_date?: string; end_date?: string; engineering_type?: string; } ``` #### 8.3.2 折线图渲染 ```typescript const lineChartOption = { xAxis: { type: 'category', data: timelineStatistics.map(item => item.month), }, yAxis: { type: 'value', }, series: [ { name: '项目数量', type: 'line', data: timelineStatistics.map(item => item.count), }, { name: '合同金额', type: 'line', yAxisIndex: 1, data: timelineStatistics.map(item => item.total_contract_amount), }, ], }; ``` ### 8.4 筛选功能 #### 8.4.1 筛选条件 ```typescript interface StatisticsFilter { engineering_type?: string; signing_date_start?: string; signing_date_end?: string; contract_amount_min?: number; contract_amount_max?: number; } ``` #### 8.4.2 筛选交互 ```typescript const handleFilterChange = (filters: StatisticsFilter) => { setFilters(filters); // 重新获取统计数据 refetchStatistics(filters); }; ``` ### 8.5 导出报表 ```typescript const handleExport = async () => { try { // 调用导出API const blob = await exportStatistics(filters); // 创建下载链接 const url = window.URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `项目统计_${formatDate(new Date(), 'YYYY-MM-DD')}.xlsx`; a.click(); // 释放资源 window.URL.revokeObjectURL(url); message.success('报表导出成功'); } catch (error) { message.error('导出失败,请重试'); } }; ``` --- ## 9. 通用组件与工具 ### 9.1 布局组件 #### 9.1.1 顶部导航栏 ```typescript // components/Layout/Header.tsx const Header = () => { const { user } = useAuth(); const navigate = useNavigate(); const handleLogout = () => { Modal.confirm({ title: '确认退出', content: '确定要退出登录吗?', onOk: async () => { await logout(); navigate('/login'); }, }); }; return (
海洋项目管理系统
{user.real_name?.[0]} {user.real_name} ({user.department})
); }; ``` #### 9.1.2 侧边栏 ```typescript // components/Layout/Sidebar.tsx const Sidebar = () => { const location = useLocation(); const { user } = useAuth(); const menuItems = [ { key: '/dashboard', label: '仪表盘', icon: }, { key: '/projects', label: '项目管理', icon: }, ...(user.role === 'admin' ? [ { key: '/users', label: '用户管理', icon: }, ] : []), { key: '/statistics', label: '项目统计', icon: }, ]; return (
); }; ``` ### 9.2 表格组件 #### 9.2.1 可配置表格 ```typescript // components/Table/ProTable.tsx interface ProTableProps { columns: ColumnType[]; data: T[]; loading?: boolean; pagination?: PaginationProps; onRow?: (record: T) => { onClick?: () => void }; } const ProTable = >({ columns, data, loading, pagination, onRow, }: ProTableProps) => { return ( ); }; ``` #### 9.2.2 自定义列渲染 ```typescript const projectColumns = [ { title: '合同编号', dataIndex: 'project_no', key: 'project_no', sorter: true, }, { title: '工程类别', dataIndex: 'engineering_type', key: 'engineering_type', render: (type: string) => {type}, }, { title: '合同金额', dataIndex: 'contract_amount', key: 'contract_amount', render: (amount: number) => `${amount.toFixed(2)} 万元`, align: 'right', }, { title: '操作', key: 'action', render: (_, record) => ( ), }, ]; ``` ### 9.3 表单组件 #### 9.3.1 日期选择器 ```typescript ``` #### 9.3.2 数字输入框 ```typescript ``` ### 9.4 标签组件 ```typescript // components/Tag/index.tsx interface TagProps { type?: 'blue' | 'green' | 'orange' | 'red' | 'gray'; children: React.ReactNode; } export const Tag = ({ type = 'blue', children }: TagProps) => { const colorMap = { blue: { bg: '#e6f7ff', color: '#1890ff', border: '#91d5ff' }, green: { bg: '#f6ffed', color: '#52c41a', border: '#b7eb8f' }, orange: { bg: '#fff7e6', color: '#fa8c16', border: '#ffd591' }, red: { bg: '#fff1f0', color: '#ff4d4f', border: '#ffa39e' }, gray: { bg: '#f5f5f5', color: '#999999', border: '#d9d9d9' }, }; const style = colorMap[type]; return ( {children} ); }; ``` --- ## 10. 状态管理 ### 10.1 Redux Store配置 ```typescript // store/index.ts import { configureStore } from '@reduxjs/toolkit'; import authReducer from './authSlice'; import projectReducer from './projectSlice'; import userReducer from './userSlice'; export const store = configureStore({ reducer: { auth: authReducer, projects: projectReducer, users: userReducer, }, }); export type RootState = ReturnType; export type AppDispatch = typeof store.dispatch; ``` ### 10.2 认证状态管理 ```typescript // store/authSlice.ts interface AuthState { user: UserInfo | null; token: string | null; isAuthenticated: boolean; } const authSlice = createSlice({ name: 'auth', initialState: { user: null, token: null, isAuthenticated: false, } as AuthState, reducers: { setCredentials: (state, action) => { state.user = action.payload.user; state.token = action.payload.token; state.isAuthenticated = true; }, clearCredentials: (state) => { state.user = null; state.token = null; state.isAuthenticated = false; }, }, }); export const { setCredentials, clearCredentials } = authSlice.actions; ``` ### 10.3 项目状态管理 ```typescript // store/projectSlice.ts interface ProjectState { list: Project[]; detail: Project | null; total: number; loading: boolean; } const projectSlice = createSlice({ name: 'projects', initialState: { list: [], detail: null, total: 0, loading: false, } as ProjectState, reducers: { setProjects: (state, action) => { state.list = action.payload.items; state.total = action.payload.total; }, setProjectDetail: (state, action) => { state.detail = action.payload; }, setLoading: (state, action) => { state.loading = action.payload; }, }, }); export const { setProjects, setProjectDetail, setLoading } = projectSlice.actions; ``` ### 10.4 React Query集成 ```typescript // hooks/useProjects.ts export const useProjects = (params: ProjectListParams) => { return useQuery( ['projects', params], () => getProjects(params), { keepPreviousData: true, staleTime: 5 * 60 * 1000, // 5分钟 } ); }; export const useProjectDetail = (id: number) => { return useQuery(['project', id], () => getProjectDetail(id), { enabled: !!id, }); }; export const useCreateProject = () => { const queryClient = useQueryClient(); return useMutation(createProject, { onSuccess: () => { queryClient.invalidateQueries(['projects']); message.success('项目创建成功'); }, }); }; export const useUpdateProject = () => { const queryClient = useQueryClient(); return useMutation(updateProject, { onSuccess: (data, variables) => { queryClient.invalidateQueries(['projects']); queryClient.invalidateQueries(['project', variables.id]); message.success('项目更新成功'); }, }); }; ``` --- ## 11. 错误处理 ### 11.1 API错误处理 ```typescript // utils/request.ts axios.interceptors.response.use( (response) => { return response.data; }, (error) => { if (error.response) { const { status, data } = error.response; switch (status) { case 400: message.error(data.message || '参数错误'); break; case 401: message.error('登录已过期,请重新登录'); // 清除Token,跳转登录页 store.dispatch(clearCredentials()); window.location.href = '/login'; break; case 403: message.error('您没有权限执行此操作'); break; case 404: message.error('请求的资源不存在'); break; case 500: message.error('服务器错误,请稍后重试'); break; default: message.error('操作失败,请重试'); } } else if (error.request) { message.error('网络错误,请检查网络连接'); } else { message.error('操作失败,请重试'); } return Promise.reject(error); } ); ``` ### 11.2 表单验证错误 ```typescript const handleSubmit = async () => { try { const values = await form.validateFields(); // 处理表单提交 } catch (error) { if (error.errorFields) { // 显示第一个错误字段的提示 const firstError = error.errorFields[0]; message.error(firstError.errors[0].message); // 聚焦到错误字段 form.scrollToField(firstError.name); } } }; ``` ### 11.3 加载错误处理 ```typescript const { data, isLoading, error } = useProjects(params); if (isLoading) { return ; } if (error) { return (

数据加载失败

); } ``` --- ## 12. 性能优化 ### 12.1 虚拟滚动 **适用于**:大数据量列表(1000+条) ```typescript import { FixedSizeList } from 'react-window'; const ProjectList = ({ projects }) => { return ( {({ index, style }) => (
)}
); }; ``` ### 12.2 防抖和节流 **防抖**:搜索输入 ```typescript import { useDebouncedValue } from 'usehooks-ts'; const ProjectSearch = () => { const [keyword, setKeyword] = useState(''); const debouncedKeyword = useDebouncedValue(keyword, 500); useEffect(() => { if (debouncedKeyword) { fetchProjects({ keyword: debouncedKeyword }); } }, [debouncedKeyword]); return setKeyword(e.target.value)} />; }; ``` **节流**:滚动加载 ```typescript import { useThrottle } from 'usehooks-ts'; const InfiniteScroll = () => { const handleScroll = useThrottle(() => { if (isNearBottom()) { loadMore(); } }, 200); return
...
; }; ``` ### 12.3 懒加载 **路由懒加载**: ```typescript const Dashboard = lazy(() => import('./pages/Dashboard')); const ProjectList = lazy(() => import('./pages/ProjectList')); const App = () => ( }> } /> } /> ); ``` ### 12.4 图片懒加载 ```typescript const LazyImage = ({ src, alt }) => { const [loaded, setLoaded] = useState(false); const imgRef = useRef(null); useEffect(() => { const observer = new IntersectionObserver( (entries) => { entries.forEach((entry) => { if (entry.isIntersecting) { setLoaded(true); observer.unobserve(entry.target); } }); }, { threshold: 0.1 } ); if (imgRef.current) { observer.observe(imgRef.current); } return () => observer.disconnect(); }, []); return (
{loaded ? {alt} : }
); }; ``` ### 12.5 缓存策略 **React Query缓存**: ```typescript // 默认缓存时间:5分钟 const queryClient = new QueryClient({ defaultOptions: { queries: { staleTime: 5 * 60 * 1000, cacheTime: 10 * 60 * 1000, retry: 1, }, }, }); ``` --- ## 附录 ### A. API错误码对照表 | 错误码 | 说明 | HTTP状态码 | 处理方式 | |--------|------|-----------|---------| | 1001 | 参数验证失败 | 400 | 显示字段级错误提示 | | 1002 | 用户名或密码错误 | 401 | 提示"用户名或密码错误" | | 1003 | Token无效或过期 | 401 | 跳转登录页 | | 2001 | 资源不存在 | 404 | 显示"资源不存在" | | 2002 | 资源已存在 | 409 | 显示"已存在"提示 | | 3001 | 权限不足 | 403 | 显示"无权限"提示 | | 5000 | 服务器内部错误 | 500 | 显示"服务器错误" | ### B. 常用格式化函数 ```typescript // utils/format.ts // 格式化金额 export const formatMoney = (amount: number): string => { return amount.toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2, }); }; // 格式化百分比 export const formatPercentage = (value: number): string => { return `${(value * 100).toFixed(2)}%`; }; // 格式化日期 export const formatDate = (date: string | Date): string => { const d = new Date(date); return d.toLocaleDateString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit', }); }; // 格式化日期时间 export const formatDateTime = (date: string | Date): string => { const d = new Date(date); return d.toLocaleString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', }); }; ``` ### C. TypeScript类型定义 ```typescript // types/project.ts export interface Project { id: number; project_no: string; power_contract_no?: string; name: string; engineering_type: '基建' | '业扩' | '客户' | '营销' | '检修'; owner_unit?: string; owner_contact?: string; contract_amount: number; signing_date?: string; start_date?: string; planned_end_date?: string; actual_end_date?: string; project_department?: string; project_leader?: string; payment_method?: string; // ... 其他字段 } export interface ProjectListParams { page: number; page_size: number; project_no?: string; engineering_type?: string; project_department?: string; signing_date_start?: string; signing_date_end?: string; contract_amount_min?: number; contract_amount_max?: number; keyword?: string; sort_by?: string; sort_order?: 'asc' | 'desc'; } export interface ProjectFormData { project_no: string; name: string; engineering_type: string; contract_amount: number; // ... 其他字段 } ``` ```typescript // types/user.ts export interface UserInfo { id: number; username: string; real_name: string; department: string; role: 'admin' | 'market' | 'other'; email?: string; phone?: string; is_active: boolean; } export interface CreateUserForm { username: string; password: string; real_name: string; department: string; role: string; email?: string; phone?: string; } ``` ```typescript // types/statistics.ts export interface StatisticsResponse { total_count: number; total_investment: number; total_contract_amount: number; total_settlement_amount: number; total_receipt_amount: number; total_payment_amount: number; avg_receipt_completion_rate: number; avg_payment_completion_rate: number; avg_cumulative_progress: number; } export interface GroupStatisticsItem { engineering_type: string; count: number; total_contract_amount: number; total_receipt_amount: number; total_payment_amount: number; } export interface TimelineStatisticsItem { month: string; count: number; total_contract_amount: number; } ``` --- **文档结束** 如有任何疑问,请参考: - API文档:`docs/desigen/api.md` - UI设计规范:`docs/desigen/ui-design-spec.md` - 设计参考预览:`docs/desigen/design-preview.html` 加油!!!