diff --git a/README.md b/README.md index 1fd0340a..54e18c97 100644 --- a/README.md +++ b/README.md @@ -89,6 +89,23 @@ cat WORKSTANDARDS.md ## 文档资源 -- [UI设计规范](docs/ui-design-spec.md) -- [设计方案](docs/plans/) -- [团队协作规范](docs/TEAM-COLLABORATION.md) +### 产品文档 +- [产品设计文档](docs/产品设计文档.md) - 产品需求文档(PRD) +- [UI设计规范](docs/ui-design-spec.md) - 前端UI设计规范 +- [团队协作规范](docs/TEAM-COLLABORATION.md) - 团队协作流程 + +### 技术文档 +- [技术架构文档](docs/技术架构文档.md) - 系统技术架构和实现 +- [后端架构设计](docs/后端架构设计.md) - 后端技术架构详解 +- [API文档](docs/api.md) - 后端API接口文档 +- [数据库设计](docs/database-design.md) - 数据库表结构设计 +- [数据库设计和数据初始化](docs/database-and-data-initialization.md) - 数据初始化方案 + +### 其他文档 +- [权限控制文档](ACCESS-CONTROL.md) - 各角色访问权限说明 +- [Excel数据导入指南](backend/docs/excel-import-guide.md) - Excel数据导入详细说明 + +### 工作规范 +- [后端程序员工作规范](backend/WORKSTANDARDS.md) +- [前端程序员工作规范](frontend/WORKSTANDARDS.md) +- [测试工程师工作规范](testing/WORKSTANDARDS.md) diff --git a/backend/config/import-excel-data.py b/backend/config/import-excel-data.py new file mode 100644 index 00000000..e027f0f0 --- /dev/null +++ b/backend/config/import-excel-data.py @@ -0,0 +1,290 @@ +#!/usr/bin/env python3 +""" +Excel数据导入脚本 +从docs/example.xls导入工程项目数据到数据库 +""" + +import sys +import xlrd +import mysql.connector +from mysql.connector import Error +from datetime import datetime +import os + +# 配置 +EXCEL_FILE = '../docs/example.xls' +DB_CONFIG = { + 'host': 'localhost', + 'user': 'root', + 'password': 'rootpassword', + 'database': 'project_manager', + 'charset': 'utf8mb4' +} + +def get_database_connection(): + """获取数据库连接""" + try: + connection = mysql.connector.connect(**DB_CONFIG) + return connection + except Error as e: + print(f'数据库连接错误: {e}', file=sys.stderr) + sys.exit(1) + +def parse_date_value(cell_value, workbook): + """解析Excel日期值""" + if cell_value == '': + return None + + # 检查是否是日期类型 + try: + date_tuple = xlrd.xldate_as_tuple(cell_value, workbook.datemode) + return f'{date_tuple[0]}-{date_tuple[1]:02d}-{date_tuple[2]:02d}' + except: + pass + + # 检查特殊值 + if cell_value in ['未到期', '可退质保金', '未开工']: + return None + + # 尝试解析字符串日期 + try: + date_obj = datetime.strptime(str(cell_value), '%Y-%m-%d') + return date_obj.strftime('%Y-%m-%d') + except: + pass + + return None + +def parse_number_value(cell_value): + """解析数字值""" + if cell_value == '': + return None + try: + return float(cell_value) + except: + return None + +def parse_int_value(cell_value): + """解析整数值""" + num = parse_number_value(cell_value) + if num is not None: + return int(num) + return None + +def parse_enum_value(cell_value, allowed_values): + """解析枚举值""" + if cell_value == '': + return None + if cell_value in allowed_values: + return cell_value + return None + +def clean_text_value(cell_value): + """清理文本值""" + if cell_value == '': + return None + return str(cell_value).strip() + +def import_projects_from_sheet(sheet, connection, cursor, sheet_name): + """从工作表导入项目数据""" + print(f'\n正在导入工作表: {sheet_name}') + print(f'总行数: {sheet.nrows}') + + # 获取管理员用户ID(用于created_by字段) + cursor.execute("SELECT id FROM users WHERE username='admin' LIMIT 1") + result = cursor.fetchone() + if not result: + print('错误: 未找到管理员用户', file=sys.stderr) + return False + admin_id = result[0] + + success_count = 0 + skip_count = 0 + error_count = 0 + + # 从第6行开始(前5行是表头) + for row_idx in range(5, sheet.nrows): + try: + # 读取数据 + row_data = [sheet.cell_value(row_idx, col_idx) for col_idx in range(min(61, sheet.ncols))] + + # 检查是否是合计行或空行 + if row_data[0] == '' or str(row_data[0]).strip() == '': + skip_count += 1 + continue + + # 准备SQL插入语句 + sql = """ + INSERT INTO projects ( + project_no, power_contract_no, name, subitem_count, subitem_code, + total_investment, contract_amount, warranty_ratio, settlement_amount, + total_cost_estimated, voltage_level, engineering_type, owner_unit, + owner_contact, bidding_type, signing_date, start_date, planned_end_date, + actual_end_date, warranty_amount, warranty_expiry_date, actual_warranty_refund_date, + project_department, project_leader, payment_method, total_cost_control, + is_adjusted, labor_cost_control, labor_cost_planned, labor_cost_paid, + material_cost_control, material_cost_payable, material_cost_actual, + material_cost_paid, other_cost_control, other_cost_payable, other_cost_actual, + tax_amount, profit, actual_profit, cost_settlement_amount, cumulative_progress, + receivable_amount, invoice_amount, actual_receipt_amount, receipt_completion_rate, + payable_amount, actual_payment_amount, unpaid_amount, payment_completion_rate, + labor_debt_amount, settlement_cost_amount, settlement_labor_cost, + settlement_material_cost, settlement_other_cost, due_settlement_count, + unsettlement_count, problems, suggestions, remarks, status, created_by + ) VALUES ( + %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, + %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, + %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s + ) + """ + + # 准备参数 + params = ( + clean_text_value(row_data[1]), # project_no + clean_text_value(row_data[2]), # power_contract_no + clean_text_value(row_data[3]), # name + parse_int_value(row_data[4]), # subitem_count + clean_text_value(row_data[5]), # subitem_code + parse_number_value(row_data[6]), # total_investment + parse_number_value(row_data[7]), # contract_amount + parse_number_value(row_data[8]), # warranty_ratio + parse_number_value(row_data[9]), # settlement_amount + parse_number_value(row_data[10]), # total_cost_estimated + clean_text_value(row_data[11]), # voltage_level + clean_text_value(row_data[12]), # engineering_type + clean_text_value(row_data[13]), # owner_unit + clean_text_value(row_data[14]), # owner_contact + clean_text_value(row_data[15]), # bidding_type + parse_date_value(row_data[16], connection._connection.workbook), # signing_date + parse_date_value(row_data[17], connection._connection.workbook), # start_date + parse_date_value(row_data[18], connection._connection.workbook), # planned_end_date + parse_date_value(row_data[19], connection._connection.workbook), # actual_end_date + parse_number_value(row_data[20]), # warranty_amount + parse_date_value(row_data[21], connection._connection.workbook), # warranty_expiry_date + parse_date_value(row_data[22], connection._connection.workbook), # actual_warranty_refund_date + clean_text_value(row_data[23]), # project_department + clean_text_value(row_data[24]), # project_leader + clean_text_value(row_data[25]), # payment_method + parse_number_value(row_data[26]), # total_cost_control + parse_enum_value(row_data[27], ['是', '否']), # is_adjusted + parse_number_value(row_data[28]), # labor_cost_control + parse_number_value(row_data[29]), # labor_cost_planned + parse_number_value(row_data[30]), # labor_cost_paid + parse_number_value(row_data[31]), # material_cost_control + parse_number_value(row_data[32]), # material_cost_payable + parse_number_value(row_data[33]), # material_cost_actual + parse_number_value(row_data[34]), # material_cost_paid + parse_number_value(row_data[35]), # other_cost_control + parse_number_value(row_data[36]), # other_cost_payable + parse_number_value(row_data[37]), # other_cost_actual + parse_number_value(row_data[38]), # tax_amount + parse_number_value(row_data[39]), # profit + parse_number_value(row_data[40]), # actual_profit + parse_number_value(row_data[41]), # cost_settlement_amount + parse_number_value(row_data[42]), # cumulative_progress + parse_number_value(row_data[43]), # receivable_amount + parse_number_value(row_data[44]), # invoice_amount + parse_number_value(row_data[45]), # actual_receipt_amount + parse_number_value(row_data[46]), # receipt_completion_rate + parse_number_value(row_data[47]), # payable_amount + parse_number_value(row_data[48]), # actual_payment_amount + parse_number_value(row_data[49]), # unpaid_amount + parse_number_value(row_data[50]), # payment_completion_rate + parse_number_value(row_data[51]), # labor_debt_amount + parse_number_value(row_data[52]), # settlement_cost_amount + parse_number_value(row_data[53]), # settlement_labor_cost + parse_number_value(row_data[54]), # settlement_material_cost + parse_number_value(row_data[55]), # settlement_other_cost + parse_int_value(row_data[56]), # due_settlement_count + parse_int_value(row_data[57]), # unsettlement_count + clean_text_value(row_data[58]), # problems + clean_text_value(row_data[59]), # suggestions + clean_text_value(row_data[60]), # remarks + '新建', # status (默认新建) + admin_id # created_by + ) + + # 跳过没有项目名称的行 + if not params[2]: + skip_count += 1 + continue + + cursor.execute(sql, params) + success_count += 1 + + except Error as e: + print(f'第{row_idx+1}行导入错误: {e}', file=sys.stderr) + error_count += 1 + continue + + print(f'导入完成: 成功 {success_count} 条, 跳过 {skip_count} 条, 错误 {error_count} 条') + return True + +def main(): + """主函数""" + print('='*60) + print('工程项目数据导入脚本') + print('='*60) + + # 检查Excel文件是否存在 + if not os.path.exists(EXCEL_FILE): + print(f'错误: Excel文件不存在: {EXCEL_FILE}', file=sys.stderr) + sys.exit(1) + + # 打开Excel文件 + try: + workbook = xlrd.open_workbook(EXCEL_FILE) + print(f'已打开Excel文件: {EXCEL_FILE}') + print(f'工作表数量: {len(workbook.sheet_names())}') + except Exception as e: + print(f'错误: 无法打开Excel文件: {e}', file=sys.stderr) + sys.exit(1) + + # 获取数据库连接 + connection = get_database_connection() + cursor = connection.cursor() + + try: + # 导入主要工作表 + main_sheets = [ + '1-1基建', + '1-2业扩', + '1-3客户', + '1-4营销', + '2检修、技改、应急抢修项目' + ] + + for sheet_name in main_sheets: + if sheet_name in workbook.sheet_names(): + sheet = workbook.sheet_by_name(sheet_name) + import_projects_from_sheet(sheet, connection, cursor, sheet_name) + else: + print(f'警告: 工作表不存在: {sheet_name}') + + # 提交事务 + connection.commit() + + # 显示统计信息 + cursor.execute("SELECT COUNT(*) FROM projects") + total_count = cursor.fetchone()[0] + print(f'\n数据库中共有 {total_count} 个项目') + + cursor.execute("SELECT engineering_type, COUNT(*) as count FROM projects GROUP BY engineering_type") + print('\n按工程类别统计:') + for row in cursor.fetchall(): + print(f' {row[0]}: {row[1]} 个') + + print('\n' + '='*60) + print('数据导入完成!') + print('='*60) + + except Exception as e: + print(f'错误: {e}', file=sys.stderr) + connection.rollback() + sys.exit(1) + finally: + cursor.close() + connection.close() + +if __name__ == '__main__': + main() diff --git a/backend/config/init-database.sql b/backend/config/init-database.sql new file mode 100644 index 00000000..b348f5da --- /dev/null +++ b/backend/config/init-database.sql @@ -0,0 +1,225 @@ +-- 工程项目管理系统 - 数据库初始化脚本 +-- 基于docs/example.xls中的工程项目管理台账 +-- 创建时间: 2026-01-25 + +-- ======================================== +-- 1. 创建数据库(如果不存在) +-- ======================================== +CREATE DATABASE IF NOT EXISTS project_manager + DEFAULT CHARACTER SET utf8mb4 + DEFAULT COLLATE utf8mb4_unicode_ci; + +USE project_manager; + +-- ======================================== +-- 2. 创建用户表 +-- ======================================== +CREATE TABLE IF NOT EXISTS users ( + id INT PRIMARY KEY AUTO_INCREMENT COMMENT '用户ID', + username VARCHAR(50) UNIQUE NOT NULL COMMENT '用户名', + password_hash VARCHAR(255) NOT NULL COMMENT '密码哈希', + real_name VARCHAR(100) NOT NULL COMMENT '真实姓名', + department VARCHAR(50) NOT NULL COMMENT '部门(市场部/技术部/财务部等)', + role ENUM('admin', 'market', 'other') NOT NULL COMMENT '角色(admin/market/other)', + email VARCHAR(100) UNIQUE COMMENT '邮箱', + phone VARCHAR(20) COMMENT '电话', + is_active BOOLEAN DEFAULT TRUE COMMENT '是否激活', + created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + INDEX idx_users_username (username), + INDEX idx_users_department (department), + INDEX idx_users_role (role) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表'; + +-- ======================================== +-- 3. 创建项目表(基于Excel结构) +-- ======================================== +CREATE TABLE IF NOT EXISTS projects ( + id INT PRIMARY KEY AUTO_INCREMENT COMMENT '项目ID', + project_no VARCHAR(50) UNIQUE NOT NULL COMMENT '合同编号', + power_contract_no VARCHAR(100) COMMENT '供电局项目合同编号', + name VARCHAR(200) NOT NULL COMMENT '项目名称', + subitem_count INT DEFAULT 0 COMMENT '子项个数', + subitem_code VARCHAR(50) COMMENT '子项编码', + total_investment DECIMAL(15,2) COMMENT '项目总投资(万元)', + contract_amount DECIMAL(15,2) COMMENT '中标合同金额(万元)', + warranty_ratio DECIMAL(5,2) COMMENT '质保金比例', + settlement_amount DECIMAL(15,2) COMMENT '结算金额(万元)', + total_cost_estimated DECIMAL(15,2) COMMENT '总成本测算', + voltage_level VARCHAR(50) COMMENT '工程电压等级', + engineering_type VARCHAR(50) COMMENT '工程类别(基建/业扩/客户/营销/检修)', + owner_unit VARCHAR(200) COMMENT '业主单位', + owner_contact VARCHAR(200) COMMENT '业主联系人及电话', + bidding_type VARCHAR(50) COMMENT '中标形式', + signing_date DATE COMMENT '签订日期', + start_date DATE COMMENT '开工日期', + planned_end_date DATE COMMENT '计划竣工日期', + actual_end_date DATE COMMENT '实际竣工日期', + warranty_amount DECIMAL(15,2) DEFAULT 0 COMMENT '质保金(万元)', + warranty_expiry_date DATE COMMENT '质保期截止日', + actual_warranty_refund_date DATE COMMENT '实际退质保金日期', + project_department VARCHAR(100) COMMENT '所属项目部', + project_leader VARCHAR(200) COMMENT '项目负责人及电话', + payment_method TEXT COMMENT '工程款拨付方式', + total_cost_control DECIMAL(15,2) COMMENT '总体成本(控制)', + is_adjusted ENUM('是', '否') DEFAULT '否' COMMENT '是否调整', + labor_cost_control DECIMAL(15,2) COMMENT '其中:人工成本(控制)', + labor_cost_planned DECIMAL(15,2) COMMENT '农民工工资(按进度计划)', + labor_cost_paid DECIMAL(15,2) COMMENT '农民工工资(实付)', + material_cost_control DECIMAL(15,2) COMMENT '其中:乙供材料费(控制)', + material_cost_payable DECIMAL(15,2) COMMENT '应付材料费(按收款比例)', + material_cost_actual DECIMAL(15,2) COMMENT '实际发生材料费', + material_cost_paid DECIMAL(15,2) COMMENT '实际支付材料费', + other_cost_control DECIMAL(15,2) COMMENT '其中:其他费用(控制)', + other_cost_payable DECIMAL(15,2) COMMENT '应付其他费', + other_cost_actual DECIMAL(15,2) COMMENT '实际其他费用', + tax_amount DECIMAL(15,2) COMMENT '税金', + profit DECIMAL(15,2) COMMENT '利润(万元)', + actual_profit DECIMAL(15,2) COMMENT '实际利润(万元)', + cost_settlement_amount DECIMAL(15,2) COMMENT '成本结算金额(万元)', + cumulative_progress DECIMAL(5,2) COMMENT '累计进度', + receivable_amount DECIMAL(15,2) COMMENT '应收款(完成进度款)', + invoice_amount DECIMAL(15,2) COMMENT '开票金额(万元)', + actual_receipt_amount DECIMAL(15,2) COMMENT '实际收款金额(万元)', + receipt_completion_rate DECIMAL(5,2) COMMENT '实际收款完成率', + payable_amount DECIMAL(15,2) COMMENT '应付款金额(万元)', + actual_payment_amount DECIMAL(15,2) COMMENT '实际付款金额(万元)', + unpaid_amount DECIMAL(15,2) COMMENT '未收款(万元)', + payment_completion_rate DECIMAL(5,2) COMMENT '实际付款完成率', + labor_debt_amount DECIMAL(15,2) COMMENT '民工工资清欠金额(万元)', + settlement_cost_amount DECIMAL(15,2) COMMENT '结算后成本测算金额', + settlement_labor_cost DECIMAL(15,2) COMMENT '结算人工费', + settlement_material_cost DECIMAL(15,2) COMMENT '结算材料费', + settlement_other_cost DECIMAL(15,2) COMMENT '结算其他费', + due_settlement_count INT COMMENT '到期应结算项目个数', + unsettlement_count INT COMMENT '到期未完成结算个数', + problems TEXT COMMENT '存在的问题', + suggestions TEXT COMMENT '建议措施', + remarks TEXT COMMENT '备注', + status VARCHAR(50) DEFAULT '新建' COMMENT '项目状态', + created_by INT NOT NULL COMMENT '创建人ID', + created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + FOREIGN KEY (created_by) REFERENCES users(id) ON DELETE RESTRICT, + INDEX idx_projects_project_no (project_no), + INDEX idx_projects_engineering_type (engineering_type), + INDEX idx_projects_project_department (project_department), + INDEX idx_projects_status (status), + INDEX idx_projects_created_by (created_by), + INDEX idx_projects_signing_date (signing_date), + INDEX idx_projects_planned_end_date (planned_end_date), + INDEX idx_projects_type_status (engineering_type, status) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='工程项目表'; + +-- ======================================== +-- 4. 插入初始管理员用户 +-- ======================================== +INSERT INTO users (username, password_hash, real_name, department, role, email, phone) VALUES +('admin', '$2b$12$LQv3c1yqBWVHxkd0LHAkCOYz6TtxMQJqhN8/LewY5GyW5q7K1U1.2', '系统管理员', '管理部', 'admin', 'admin@example.com', '13800000000') +ON DUPLICATE KEY UPDATE username=username; + +-- ======================================== +-- 5. 创建视图:项目统计视图 +-- ======================================== +CREATE OR REPLACE VIEW project_statistics AS +SELECT + engineering_type AS 工程类别, + COUNT(*) AS 项目总数, + SUM(contract_amount) AS 合同总金额, + SUM(actual_receipt_amount) AS 实际收款总金额, + SUM(actual_payment_amount) AS 实际付款总金额, + AVG(receipt_completion_rate) AS 平均收款完成率, + AVG(payment_completion_rate) AS 平均付款完成率, + SUM(CASE WHEN status = '新建' THEN 1 ELSE 0 END) AS 新建项目, + SUM(CASE WHEN status = '进行中' THEN 1 ELSE 0 END) AS 进行中项目, + SUM(CASE WHEN status = '已完成' THEN 1 ELSE 0 END) AS 已完成项目 +FROM projects +GROUP BY engineering_type; + +-- ======================================== +-- 6. 创建视图:项目详情视图 +-- ======================================== +CREATE OR REPLACE VIEW project_detail_view AS +SELECT + p.id, + p.project_no, + p.name, + p.engineering_type, + p.contract_amount, + p.actual_receipt_amount, + p.actual_payment_amount, + p.receipt_completion_rate, + p.payment_completion_rate, + p.status, + p.project_department, + p.project_leader, + p.signing_date, + p.planned_end_date, + p.actual_end_date, + u.real_name AS created_by_name, + p.created_at, + p.updated_at +FROM projects p +LEFT JOIN users u ON p.created_by = u.id; + +-- ======================================== +-- 7. 创建存储过程:更新项目收款完成率 +-- ======================================== +DELIMITER // +CREATE PROCEDURE update_receipt_completion_rate(IN project_id INT) +BEGIN + UPDATE projects + SET receipt_completion_rate = CASE + WHEN contract_amount > 0 THEN ROUND(actual_receipt_amount / contract_amount * 100, 2) + ELSE 0 + END + WHERE id = project_id; +END // +DELIMITER ; + +-- ======================================== +-- 8. 创建存储过程:更新项目付款完成率 +-- ======================================== +DELIMITER // +CREATE PROCEDURE update_payment_completion_rate(IN project_id INT) +BEGIN + UPDATE projects + SET payment_completion_rate = CASE + WHEN payable_amount > 0 THEN ROUND(actual_payment_amount / payable_amount * 100, 2) + ELSE 0 + END + WHERE id = project_id; +END // +DELIMITER ; + +-- ======================================== +-- 9. 创建触发器:插入项目前自动生成合同编号 +-- ======================================== +DELIMITER // +CREATE TRIGGER before_project_insert +BEFORE INSERT ON projects +FOR EACH ROW +BEGIN + IF NEW.project_no IS NULL OR NEW.project_no = '' THEN + SET NEW.project_no = CONCAT('PRJ', DATE_FORMAT(NOW(), '%Y%m%d'), LPAD((SELECT COUNT(*) + 1 FROM projects), 4, '0')); + END IF; +END // +DELIMITER ; + +-- ======================================== +-- 10. 创建触发器:更新项目时自动更新时间戳 +-- ======================================== +DELIMITER // +CREATE TRIGGER before_project_update +BEFORE UPDATE ON projects +FOR EACH ROW +BEGIN + SET NEW.updated_at = NOW(); +END // +DELIMITER ; + +-- ======================================== +-- 完成 +-- ======================================== +-- 数据库初始化完成 +-- 执行 backend/config/import-excel-data.py 来导入Excel数据 diff --git a/backend/docs/excel-import-guide.md b/backend/docs/excel-import-guide.md new file mode 100644 index 00000000..8c06dd72 --- /dev/null +++ b/backend/docs/excel-import-guide.md @@ -0,0 +1,266 @@ +# Excel数据导入指南 + +## 1. 概述 + +本指南说明如何将 `docs/example.xls` 中的工程项目数据导入到数据库中。 + +## 2. 准备工作 + +### 2.1 安装依赖 + +```bash +pip install mysql-connector-python xlrd +``` + +### 2.2 配置数据库 + +编辑 `backend/config/.env` 文件,配置数据库连接信息: + +```env +DB_HOST=localhost +DB_USER=root +DB_PASSWORD=rootpassword +DB_NAME=project_manager +DB_CHARSET=utf8mb4 +``` + +### 2.3 初始化数据库 + +```bash +cd backend/config +mysql -u root -p < init-database.sql +``` + +## 3. 导入数据 + +### 3.1 方法一:使用Python脚本(推荐) + +```bash +cd backend/config +python3 import-excel-data.py +``` + +**注意事项**: +- 确保 `docs/example.xls` 文件存在 +- 确保数据库连接正常 +- 确保管理员用户已创建 + +### 3.2 方法二:手动导入 + +1. 将Excel转换为CSV +2. 使用MySQL导入工具 +3. 手动执行SQL INSERT语句 + +## 4. 数据映射 + +### 4.1 Excel工作表映射 + +| Excel工作表 | 对应的工程类别 | +|-----------|---------------| +| 1-1基建 | 基建工程 | +| 1-2业扩 | 业扩工程 | +| 1-3客户 | 客户工程 | +| 1-4营销 | 营销工程 | +| 2检修、技改、应急抢修项目 | 检修工程 | + +### 4.2 字段映射关系 + +详见 `docs/database-design.md` 第4章。 + +## 5. 数据验证 + +### 5.1 检查导入数量 + +```sql +SELECT COUNT(*) FROM projects; +``` + +### 5.2 按工程类别统计 + +```sql +SELECT engineering_type, COUNT(*) as count +FROM projects +GROUP BY engineering_type; +``` + +### 5.3 检查关键字段 + +```sql +-- 检查合同编号为空的项目 +SELECT * FROM projects WHERE project_no IS NULL OR project_no = ''; + +-- 检查项目名称为空的项目 +SELECT * FROM projects WHERE name IS NULL OR name = ''; + +-- 检查金额字段异常的项目 +SELECT * FROM projects WHERE contract_amount < 0; +``` + +## 6. 常见问题 + +### 6.1 日期格式错误 + +**问题**: Excel中的日期格式无法识别 + +**解决**: +- 检查Excel中的日期格式是否正确 +- 特殊值如"未到期"、"未开工"会被设置为NULL + +### 6.2 数字格式错误 + +**问题**: 数字字段包含非数字字符 + +**解决**: +- 使用 `parse_number_value()` 函数自动处理 +- 空值会被设置为NULL + +### 6.3 重复数据 + +**问题**: 合同编号重复 + +**解决**: +- 检查Excel中是否有重复的合同编号 +- 使用 `INSERT IGNORE` 或 `ON DUPLICATE KEY UPDATE` + +### 6.4 字符编码问题 + +**问题**: 中文字符乱码 + +**解决**: +- 确保数据库使用 utf8mb4 字符集 +- 确保Python脚本使用正确的编码 + +## 7. 更新数据 + +### 7.1 完全重新导入 + +```bash +# 清空现有数据 +mysql -u root -p project_manager -e "TRUNCATE TABLE projects;" + +# 重新导入 +cd backend/config +python3 import-excel-data.py +``` + +### 7.2 增量导入 + +修改Python脚本,只导入新增的项目: + +```python +# 检查项目是否已存在 +cursor.execute("SELECT id FROM projects WHERE project_no = %s", (project_no,)) +if cursor.fetchone(): + continue # 跳过已存在的项目 +``` + +## 8. 自动化部署 + +### 8.1 创建部署脚本 + +`scripts/deploy.sh`: + +```bash +#!/bin/bash + +echo "开始部署..." + +# 1. 停止服务 +echo "停止服务..." +systemctl stop project-manager-backend + +# 2. 备份数据库 +echo "备份数据库..." +mysqldump -u root -p project_manager > backup_$(date +%Y%m%d_%H%M%S).sql + +# 3. 初始化数据库 +echo "初始化数据库..." +mysql -u root -p < backend/config/init-database.sql + +# 4. 导入Excel数据 +echo "导入Excel数据..." +cd backend/config +python3 import-excel-data.py + +# 5. 启动服务 +echo "启动服务..." +systemctl start project-manager-backend + +echo "部署完成!" +``` + +### 8.2 添加定时任务 + +```bash +# 每天凌晨2点自动导入数据 +crontab -e + +# 添加以下行 +0 2 * * * /path/to/scripts/deploy.sh >> /var/log/project-manager/deploy.log 2>&1 +``` + +## 9. 监控和日志 + +### 9.1 导入日志 + +```bash +# 查看导入日志 +tail -f /var/log/project-manager/import.log +``` + +### 9.2 数据完整性检查 + +创建定时任务检查数据完整性: + +```sql +-- 检查缺失字段 +SELECT + COUNT(*) as missing_contract_no +FROM projects +WHERE project_no IS NULL OR project_no = ''; +``` + +## 10. 性能优化 + +### 10.1 批量插入 + +使用批量插入提高性能: + +```python +# 一次插入100条记录 +cursor.executemany(sql, params_list) +``` + +### 10.2 禁用索引 + +导入数据时临时禁用索引: + +```sql +ALTER TABLE projects DISABLE KEYS; +-- 导入数据 +ALTER TABLE projects ENABLE KEYS; +``` + +## 11. 安全注意事项 + +### 11.1 数据库权限 + +不要使用root用户导入数据,创建专用用户: + +```sql +CREATE USER 'import_user'@'localhost' IDENTIFIED BY 'secure_password'; +GRANT INSERT, SELECT ON project_manager.* TO 'import_user'@'localhost'; +``` + +### 11.2 SQL注入防护 + +使用参数化查询,避免SQL注入。 + +### 11.3 敏感信息保护 + +不要将密码硬编码在脚本中,使用环境变量或配置文件。 + +--- + +**文档维护**: 本文档由后端程序员维护 +**更新时间**: 2026-01-25 diff --git a/docs/2026-01-25-backend-architecture-design.md b/docs/2026-01-25-backend-architecture-design.md new file mode 100644 index 00000000..f996ca98 --- /dev/null +++ b/docs/2026-01-25-backend-architecture-design.md @@ -0,0 +1,228 @@ +# 海洋项目管理系统 - 后端架构设计 + +## 1. 技术栈 + +| 层级 | 技术 | 说明 | +|------|------|------| +| Web框架 | FastAPI | 高性能异步框架,自动生成API文档 | +| ORM | SQLAlchemy 2.0 | 类型安全的ORM,支持异步 | +| 数据库 | MySQL 8.0 | 关系型数据库 | +| 认证 | JWT (PyJWT) | 无状态认证 | +| 密码加密 | bcrypt | 密码哈希存储 | +| 数据验证 | Pydantic v2 | 请求/响应数据验证 | +| API文档 | FastAPI自动生成 + OpenAPI 3.1 | Swagger UI | + +## 2. 系统架构 + +``` +┌─────────────────────────────────────────┐ +│ 前端 (React + Ant Design) │ +└─────────────────────────────────────────┘ + │ + │ HTTP/HTTPS + JWT + ▼ +┌─────────────────────────────────────────┐ +│ FastAPI 应用层 │ +│ ┌─────────────────────────────────┐ │ +│ │ Routes (API端点) │ │ +│ ├─────────────────────────────────┤ │ +│ │ Controllers (业务逻辑) │ │ +│ ├─────────────────────────────────┤ │ +│ │ Services (复杂业务逻辑) │ │ +│ ├─────────────────────────────────┤ │ +│ │ Models (数据模型) │ │ +│ ├─────────────────────────────────┤ │ +│ │ Middleware (认证/日志/错误) │ │ +│ └─────────────────────────────────┘ │ +└─────────────────────────────────────────┘ + │ + │ SQLAlchemy ORM + ▼ +┌─────────────────────────────────────────┐ +│ MySQL 数据库 │ +│ - users (用户表) │ +│ - projects (项目表, 60+字段) │ +└─────────────────────────────────────────┘ +``` + +## 3. 项目目录结构 + +``` +backend/ +├── src/ +│ ├── controllers/ # 控制器层 +│ │ ├── auth.py +│ │ ├── users.py +│ │ └── projects.py +│ ├── services/ # 业务逻辑层 +│ │ ├── auth_service.py +│ │ ├── user_service.py +│ │ └── project_service.py +│ ├── models/ # 数据模型 +│ │ ├── user.py +│ │ └── project.py +│ ├── routes/ # 路由定义 +│ │ ├── auth.py +│ │ ├── users.py +│ │ └── projects.py +│ ├── middleware/ # 中间件 +│ │ ├── auth.py +│ │ └── logging.py +│ ├── schemas/ # Pydantic模型 +│ │ ├── user.py +│ │ └── project.py +│ ├── utils/ # 工具函数 +│ │ ├── password.py +│ │ └── jwt.py +│ └── dependencies.py # 依赖注入 +├── tests/ # 测试目录 +│ ├── test_auth.py +│ ├── test_users.py +│ └── test_projects.py +├── config/ # 配置文件 +│ ├── __init__.py +│ ├── database.py # 数据库配置 +│ └── settings.py # 应用配置 +├── requirements.txt # Python依赖 +├── main.py # FastAPI应用入口 +└── README.md +``` + +## 4. 核心功能模块 + +### 4.1 认证授权模块 +- JWT Token生成和验证 +- 密码加密(bcrypt) +- 基于角色的权限控制(RBAC) +- Token自动刷新机制 + +### 4.2 用户管理模块 +- 用户CRUD操作 +- 用户列表查询(支持分页、筛选) +- 密码重置功能(仅管理员) + +### 4.3 项目管理模块 +- 项目CRUD操作 +- 项目列表查询(支持复杂条件筛选) +- 项目统计功能(基础统计、分组统计、时间维度统计) +- Excel数据导入(通过脚本导入,非API) + +## 5. 数据库设计 + +### 5.1 用户表 (users) + +| 字段名 | 类型 | 约束 | 说明 | +|--------|------|------|------| +| id | INT | PRIMARY KEY, AUTO_INCREMENT | 用户ID | +| username | VARCHAR(50) | UNIQUE, NOT NULL | 用户名 | +| password_hash | VARCHAR(255) | NOT NULL | 密码哈希 | +| real_name | VARCHAR(100) | NOT NULL | 真实姓名 | +| department | VARCHAR(50) | NOT NULL | 部门 | +| role | ENUM | NOT NULL | 角色(admin/market/other) | +| email | VARCHAR(100) | UNIQUE | 邮箱 | +| phone | VARCHAR(20) | | 电话 | +| is_active | BOOLEAN | DEFAULT TRUE | 是否激活 | +| created_at | DATETIME | DEFAULT CURRENT_TIMESTAMP | 创建时间 | +| updated_at | DATETIME | DEFAULT CURRENT_TIMESTAMP ON UPDATE | 更新时间 | + +### 5.2 项目表 (projects) + +基于`docs/database-design.md`,保持单表设计,包含60+个字段: +- 基础信息:project_no, name, engineering_type, signing_date等 +- 成本信息:total_cost_control, labor_cost_control, material_cost_control等 +- 财务信息:contract_amount, actual_receipt_amount, actual_payment_amount等 +- 质保信息:warranty_amount, warranty_expiry_date等 +- 结算信息:settlement_amount, cost_settlement_amount等 + +**注意**:不使用status字段,数据是什么就是什么。 + +## 6. API设计规范 + +### 6.1 基础规范 +- Base URL: `/api/v1` +- Content-Type: `application/json` +- 认证方式: JWT Token (Header: `Authorization: Bearer `) +- 统一响应格式: +```json +{ + "success": true, + "message": "操作成功", + "data": {}, + "error_code": null +} +``` + +### 6.2 错误码设计 +| 错误码 | 说明 | +|--------|------| +| 1001 | 参数验证失败 | +| 1002 | 用户名或密码错误 | +| 1003 | Token无效或过期 | +| 2001 | 资源不存在 | +| 2002 | 资源已存在 | +| 3001 | 权限不足 | +| 5000 | 服务器内部错误 | + +## 7. 筛选和统计功能设计 + +### 7.1 组合筛选(AND条件) +支持多个字段同时筛选,所有条件必须同时满足。 +示例: +- 工程类别=基建 AND 签订日期>2026-01-01 AND 合同金额>100万 + +### 7.2 统计功能 +- **基础统计**:记录总数、字段总和、平均值等 +- **分组统计**:按指定字段分组统计 +- **时间维度统计**:按时间区间统计(按日/月/年) + +## 8. 安全设计 + +### 8.1 认证安全 +- 密码使用bcrypt加密存储(salt rounds=12) +- JWT Token有效期24小时 +- Token存储在HttpOnly Cookie或LocalStorage + +### 8.2 权限控制 +- 后端基于依赖注入的权限验证 +- 支持角色级别和资源级别权限控制 + +### 8.3 数据安全 +- SQL注入防护(SQLAlchemy ORM参数化查询) +- XSS防护(FastAPI自动处理) +- 输入验证(Pydantic模型) +- 请求频率限制(可选) + +## 9. 开发规范 + +### 9.1 代码风格 +- 遵循PEP 8规范 +- 使用Type Hints进行类型标注 +- 函数和类添加docstring + +### 9.2 提交规范 +- 提交格式: `[backend] <类型>: <描述>` +- 类型: feat, fix, docs, style, refactor, test, chore + +### 9.3 测试要求 +- 单元测试覆盖率 > 80% +- 使用pytest测试框架 +- 测试文件命名: `test_<模块名>.py` + +## 10. 部署方案 + +### 10.1 开发环境 +```bash +cd backend +pip install -r requirements.txt +uvicorn main:app --reload --host 0.0.0.0 --port 5000 +``` + +### 10.2 生产环境 +- 使用Gunicorn + Uvicorn Workers +- Nginx反向代理 +- Docker容器化部署(可选) + +--- + +**文档维护**: 后端程序员 +**更新时间**: 2026-01-25 diff --git a/docs/api.md b/docs/api.md new file mode 100644 index 00000000..f12e9425 --- /dev/null +++ b/docs/api.md @@ -0,0 +1,864 @@ +# 海洋项目管理系统 - 后端API文档 + +## 1. API基础信息 + +### 1.1 基础规范 +- **Base URL**: `http://localhost:5000/api/v1` +- **Content-Type**: `application/json` +- **认证方式**: JWT Token + - Header: `Authorization: Bearer ` +- **API文档地址**: `http://localhost:5000/docs` (Swagger UI) + +### 1.2 统一响应格式 + +#### 成功响应 +```json +{ + "success": true, + "message": "操作成功", + "data": {}, + "error_code": null +} +``` + +#### 错误响应 +```json +{ + "success": false, + "message": "错误描述", + "data": null, + "error_code": "错误码" +} +``` + +### 1.3 错误码列表 + +| 错误码 | 说明 | HTTP状态码 | +|--------|------|-----------| +| 1001 | 参数验证失败 | 400 | +| 1002 | 用户名或密码错误 | 401 | +| 1003 | Token无效或过期 | 401 | +| 2001 | 资源不存在 | 404 | +| 2002 | 资源已存在 | 409 | +| 3001 | 权限不足 | 403 | +| 5000 | 服务器内部错误 | 500 | + +### 1.4 分页参数 +所有列表接口都支持分页: +- `page`: 页码,默认1 +- `page_size`: 每页数量,默认10,最大100 + +## 2. 认证相关API + +### 2.1 用户登录 + +**接口**: `POST /auth/login` + +**请求体**: +```json +{ + "username": "admin", + "password": "password123" +} +``` + +**响应**: +```json +{ + "success": true, + "message": "登录成功", + "data": { + "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", + "token_type": "bearer", + "user": { + "id": 1, + "username": "admin", + "real_name": "系统管理员", + "department": "管理部", + "role": "admin", + "email": "admin@example.com" + } + }, + "error_code": null +} +``` + +**错误示例**: +```json +{ + "success": false, + "message": "用户名或密码错误", + "data": null, + "error_code": "1002" +} +``` + +### 2.2 获取当前用户信息 + +**接口**: `GET /auth/me` + +**请求头**: +``` +Authorization: Bearer +``` + +**响应**: +```json +{ + "success": true, + "message": "获取成功", + "data": { + "id": 1, + "username": "admin", + "real_name": "系统管理员", + "department": "管理部", + "role": "admin", + "email": "admin@example.com", + "phone": "13800000000", + "is_active": true + }, + "error_code": null +} +``` + +### 2.3 登出 + +**接口**: `POST /auth/logout` + +**请求头**: +``` +Authorization: Bearer +``` + +**响应**: +```json +{ + "success": true, + "message": "登出成功", + "data": null, + "error_code": null +} +``` + +## 3. 用户管理API + +### 3.1 获取用户列表 + +**接口**: `GET /users` + +**权限**: 仅管理员 (admin) + +**请求头**: +``` +Authorization: Bearer +``` + +**查询参数**: +| 参数 | 类型 | 必填 | 说明 | +|------|------|------|------| +| page | int | 否 | 页码,默认1 | +| page_size | int | 否 | 每页数量,默认10 | +| department | string | 否 | 部门筛选 | +| role | string | 否 | 角色筛选 | +| keyword | string | 否 | 关键词搜索(用户名、真实姓名、邮箱) | + +**响应**: +```json +{ + "success": true, + "message": "获取成功", + "data": { + "items": [ + { + "id": 1, + "username": "admin", + "real_name": "系统管理员", + "department": "管理部", + "role": "admin", + "email": "admin@example.com", + "phone": "13800000000", + "is_active": true, + "created_at": "2026-01-25T10:00:00", + "updated_at": "2026-01-25T10:00:00" + }, + { + "id": 2, + "username": "zhangsan", + "real_name": "张三", + "department": "市场部", + "role": "market", + "email": "zhangsan@example.com", + "phone": "13900139000", + "is_active": true, + "created_at": "2026-01-25T11:00:00", + "updated_at": "2026-01-25T11:00:00" + } + ], + "total": 2, + "page": 1, + "page_size": 10 + }, + "error_code": null +} +``` + +### 3.2 创建用户 + +**接口**: `POST /users` + +**权限**: 仅管理员 (admin) + +**请求头**: +``` +Authorization: Bearer +``` + +**请求体**: +```json +{ + "username": "lisi", + "password": "password123", + "real_name": "李四", + "department": "技术部", + "role": "other", + "email": "lisi@example.com", + "phone": "13700137000" +} +``` + +**字段说明**: +| 字段 | 类型 | 必填 | 说明 | +|------|------|------|------| +| username | string | 是 | 用户名,唯一 | +| password | string | 是 | 密码,最少6位 | +| real_name | string | 是 | 真实姓名 | +| department | string | 是 | 部门 | +| role | string | 是 | 角色:admin/market/other | +| email | string | 否 | 邮箱,唯一 | +| phone | string | 否 | 电话 | + +**响应**: +```json +{ + "success": true, + "message": "用户创建成功", + "data": { + "id": 3, + "username": "lisi", + "real_name": "李四", + "department": "技术部", + "role": "other" + }, + "error_code": null +} +``` + +### 3.3 获取用户详情 + +**接口**: `GET /users/{id}` + +**权限**: 仅管理员 (admin) + +**请求头**: +``` +Authorization: Bearer +``` + +**路径参数**: +| 参数 | 类型 | 说明 | +|------|------|------| +| id | int | 用户ID | + +**响应**: +```json +{ + "success": true, + "message": "获取成功", + "data": { + "id": 2, + "username": "zhangsan", + "real_name": "张三", + "department": "市场部", + "role": "market", + "email": "zhangsan@example.com", + "phone": "13900139000", + "is_active": true, + "created_at": "2026-01-25T11:00:00", + "updated_at": "2026-01-25T11:00:00" + }, + "error_code": null +} +``` + +### 3.4 更新用户 + +**接口**: `PUT /users/{id}` + +**权限**: 仅管理员 (admin) + +**请求头**: +``` +Authorization: Bearer +``` + +**路径参数**: +| 参数 | 类型 | 说明 | +|------|------|------| +| id | int | 用户ID | + +**请求体**: +```json +{ + "real_name": "张三三", + "email": "zhangsan_new@example.com", + "phone": "13900139001", + "is_active": false +} +``` + +**字段说明**: 所有字段都是可选的,至少提供一个字段 + +**响应**: +```json +{ + "success": true, + "message": "用户更新成功", + "data": { + "id": 2, + "username": "zhangsan", + "real_name": "张三三", + "department": "市场部", + "role": "market", + "email": "zhangsan_new@example.com", + "phone": "13900139001", + "is_active": false + }, + "error_code": null +} +``` + +### 3.5 删除用户 + +**接口**: `DELETE /users/{id}` + +**权限**: 仅管理员 (admin) + +**请求头**: +``` +Authorization: Bearer +``` + +**路径参数**: +| 参数 | 类型 | 说明 | +|------|------|------| +| id | int | 用户ID | + +**响应**: +```json +{ + "success": true, + "message": "用户删除成功", + "data": null, + "error_code": null +} +``` + +### 3.6 重置用户密码 + +**接口**: `POST /users/{id}/reset-password` + +**权限**: 仅管理员 (admin) + +**请求头**: +``` +Authorization: Bearer +``` + +**路径参数**: +| 参数 | 类型 | 说明 | +|------|------|------| +| id | int | 用户ID | + +**请求体**: +```json +{ + "new_password": "newpassword123" +} +``` + +**响应**: +```json +{ + "success": true, + "message": "密码重置成功", + "data": null, + "error_code": null +} +``` + +## 4. 项目管理API + +### 4.1 获取项目列表 + +**接口**: `GET /projects` + +**权限**: 所有用户 + +**请求头**: +``` +Authorization: Bearer +``` + +**查询参数**: +| 参数 | 类型 | 必填 | 说明 | +|------|------|------|------| +| page | int | 否 | 页码,默认1 | +| page_size | int | 否 | 每页数量,默认10,最大100 | +| project_no | string | 否 | 合同编号筛选 | +| engineering_type | string | 否 | 工程类别筛选 | +| project_department | string | 否 | 所属项目部筛选 | +| signing_date_start | string | 否 | 签订日期开始(YYYY-MM-DD) | +| signing_date_end | string | 否 | 签订日期结束(YYYY-MM-DD) | +| contract_amount_min | decimal | 否 | 合同金额最小值(万元) | +| contract_amount_max | decimal | 否 | 合同金额最大值(万元) | +| keyword | string | 否 | 关键词搜索(项目名称、业主单位) | +| sort_by | string | 否 | 排序字段:signing_date/contract_amount/created_at | +| sort_order | string | 否 | 排序方向:asc/desc,默认desc | + +**注意**:所有筛选条件都是AND关系,必须同时满足。 + +**响应**: +```json +{ + "success": true, + "message": "获取成功", + "data": { + "items": [ + { + "id": 1, + "project_no": "PRJ2026001", + "power_contract_no": "GD2026001", + "name": "某电力基建工程项目", + "subitem_count": 5, + "subitem_code": "SUB001", + "total_investment": 1000.00, + "contract_amount": 950.00, + "warranty_ratio": 5.00, + "settlement_amount": null, + "total_cost_estimated": 800.00, + "voltage_level": "110kV", + "engineering_type": "基建", + "owner_unit": "XX电力公司", + "owner_contact": "张三 13800000001", + "bidding_type": "公开招标", + "signing_date": "2026-01-01", + "start_date": "2026-01-15", + "planned_end_date": "2026-12-31", + "actual_end_date": null, + "warranty_amount": 47.50, + "warranty_expiry_date": "2028-12-31", + "actual_warranty_refund_date": null, + "project_department": "项目部一", + "project_leader": "李四 13900000001", + "payment_method": "按进度付款", + "total_cost_control": 800.00, + "is_adjusted": "否", + "labor_cost_control": 300.00, + "labor_cost_planned": 280.00, + "labor_cost_paid": 200.00, + "material_cost_control": 400.00, + "material_cost_payable": 320.00, + "material_cost_actual": 310.00, + "material_cost_paid": 280.00, + "other_cost_control": 100.00, + "other_cost_payable": 80.00, + "other_cost_actual": 75.00, + "tax_amount": 95.00, + "profit": 55.00, + "actual_profit": null, + "cost_settlement_amount": null, + "cumulative_progress": 60.00, + "receivable_amount": 570.00, + "invoice_amount": 570.00, + "actual_receipt_amount": 475.00, + "receipt_completion_rate": 50.00, + "payable_amount": 480.00, + "actual_payment_amount": 390.00, + "unpaid_amount": 95.00, + "payment_completion_rate": 81.25, + "labor_debt_amount": 80.00, + "settlement_cost_amount": null, + "settlement_labor_cost": null, + "settlement_material_cost": null, + "settlement_other_cost": null, + "due_settlement_count": 0, + "unsettlement_count": 0, + "problems": null, + "suggestions": null, + "remarks": "备注信息", + "created_by": 2, + "created_by_name": "张三", + "created_at": "2026-01-25T10:00:00", + "updated_at": "2026-01-25T12:00:00" + } + ], + "total": 1, + "page": 1, + "page_size": 10 + }, + "error_code": null +} +``` + +### 4.2 获取项目详情 + +**接口**: `GET /projects/{id}` + +**权限**: 所有用户 + +**请求头**: +``` +Authorization: Bearer +``` + +**路径参数**: +| 参数 | 类型 | 说明 | +|------|------|------| +| id | int | 项目ID | + +**响应**: +```json +{ + "success": true, + "message": "获取成功", + "data": { + "id": 1, + "project_no": "PRJ2026001", + "name": "某电力基建工程项目", + "engineering_type": "基建", + "contract_amount": 950.00, + "created_by": 2, + "created_by_name": "张三", + "created_at": "2026-01-25T10:00:00", + "updated_at": "2026-01-25T12:00:00" + // ... 完整项目信息(60+字段) + }, + "error_code": null +} +``` + +### 4.3 创建项目 + +**接口**: `POST /projects` + +**权限**: admin, market + +**请求头**: +``` +Authorization: Bearer +``` + +**请求体**: +```json +{ + "project_no": "PRJ2026002", + "power_contract_no": "GD2026002", + "name": "新电力工程项目", + "engineering_type": "业扩", + "owner_unit": "XX供电局", + "contract_amount": 500.00, + "signing_date": "2026-01-25", + "start_date": "2026-02-01", + "planned_end_date": "2026-12-31", + "project_department": "项目部二", + "project_leader": "王五 13800000002", + "payment_method": "按进度付款", + "total_cost_control": 400.00, + "labor_cost_control": 150.00, + "material_cost_control": 200.00, + "other_cost_control": 50.00 +} +``` + +**必填字段**: +- project_no: 合同编号(唯一) +- name: 项目名称 +- engineering_type: 工程类别 +- contract_amount: 合同金额 + +**响应**: +```json +{ + "success": true, + "message": "项目创建成功", + "data": { + "id": 2, + "project_no": "PRJ2026002", + "name": "新电力工程项目", + "engineering_type": "业扩" + }, + "error_code": null +} +``` + +### 4.4 更新项目 + +**接口**: `PUT /projects/{id}` + +**权限**: 所有用户 +- admin: 可更新所有字段 +- market: 只能更新自己创建的项目 +- other: 可更新项目的财务、成本、进度等信息(不能修改基础信息) + +**请求头**: +``` +Authorization: Bearer +``` + +**路径参数**: +| 参数 | 类型 | 说明 | +|------|------|------| +| id | int | 项目ID | + +**请求体**: +```json +{ + "name": "更新后的项目名称", + "contract_amount": 600.00, + "actual_receipt_amount": 300.00, + "actual_payment_amount": 250.00, + "cumulative_progress": 50.00, + "remarks": "更新备注" +} +``` + +**字段说明**: 所有字段都是可选的,至少提供一个字段 + +**响应**: +```json +{ + "success": true, + "message": "项目更新成功", + "data": { + "id": 1, + "project_no": "PRJ2026001", + "name": "更新后的项目名称" + }, + "error_code": null +} +``` + +### 4.5 删除项目 + +**接口**: `DELETE /projects/{id}` + +**权限**: admin, market +- admin: 可删除所有项目 +- market: 只能删除自己创建的项目 + +**请求头**: +``` +Authorization: Bearer +``` + +**路径参数**: +| 参数 | 类型 | 说明 | +|------|------|------| +| id | int | 项目ID | + +**响应**: +```json +{ + "success": true, + "message": "项目删除成功", + "data": null, + "error_code": null +} +``` + +### 4.6 项目统计API + +#### 4.6.1 基础统计 + +**接口**: `GET /projects/statistics` + +**权限**: 所有用户 + +**请求头**: +``` +Authorization: Bearer +``` + +**查询参数**: +| 参数 | 类型 | 必填 | 说明 | +|------|------|------|------| +| engineering_type | string | 否 | 工程类别筛选 | +| signing_date_start | string | 否 | 签订日期开始 | +| signing_date_end | string | 否 | 签订日期结束 | +| contract_amount_min | decimal | 否 | 合同金额最小值 | +| contract_amount_max | decimal | 否 | 合同金额最大值 | + +**说明**: 筛选参数与列表查询相同,支持AND组合筛选 + +**响应**: +```json +{ + "success": true, + "message": "统计成功", + "data": { + "total_count": 100, + "total_investment": 50000.00, + "total_contract_amount": 48000.00, + "total_settlement_amount": 45000.00, + "total_receipt_amount": 42000.00, + "total_payment_amount": 40000.00, + "avg_receipt_completion_rate": 87.50, + "avg_payment_completion_rate": 83.33, + "avg_cumulative_progress": 75.00 + }, + "error_code": null +} +``` + +#### 4.6.2 分组统计 + +**接口**: `GET /projects/statistics/group` + +**权限**: 所有用户 + +**请求头**: +``` +Authorization: Bearer +``` + +**查询参数**: +| 参数 | 类型 | 必填 | 说明 | +|------|------|------|------| +| group_by | string | 是 | 分组字段:engineering_type/project_department | +| signing_date_start | string | 否 | 签订日期开始 | +| signing_date_end | string | 否 | 签订日期结束 | + +**响应**: +```json +{ + "success": true, + "message": "统计成功", + "data": [ + { + "engineering_type": "基建", + "count": 50, + "total_contract_amount": 30000.00, + "total_receipt_amount": 28000.00, + "total_payment_amount": 26000.00 + }, + { + "engineering_type": "业扩", + "count": 30, + "total_contract_amount": 12000.00, + "total_receipt_amount": 10000.00, + "total_payment_amount": 9500.00 + }, + { + "engineering_type": "客户", + "count": 20, + "total_contract_amount": 6000.00, + "total_receipt_amount": 4000.00, + "total_payment_amount": 4500.00 + } + ], + "error_code": null +} +``` + +#### 4.6.3 时间维度统计 + +**接口**: `GET /projects/statistics/timeline` + +**权限**: 所有用户 + +**请求头**: +``` +Authorization: Bearer +``` + +**查询参数**: +| 参数 | 类型 | 必填 | 说明 | +|------|------|------|------| +| time_field | string | 是 | 时间字段:signing_date/start_date/planned_end_date | +| group_by | string | 否 | 时间粒度:day/month/year,默认month | +| engineering_type | string | 否 | 工程类别筛选 | +| start_date | string | 否 | 开始日期 | +| end_date | string | 否 | 结束日期 | + +**响应**: +```json +{ + "success": true, + "message": "统计成功", + "data": [ + { + "month": "2026-01", + "count": 20, + "total_contract_amount": 8000.00 + }, + { + "month": "2026-02", + "count": 15, + "total_contract_amount": 6000.00 + }, + { + "month": "2026-03", + "count": 25, + "total_contract_amount": 10000.00 + } + ], + "error_code": null +} +``` + +## 5. 数据模型说明 + +### 5.1 用户角色 (role) +- `admin`: 管理员,拥有所有权限 +- `market`: 市场部,可以创建项目、查看和编辑自己的项目 +- `other`: 其他部门,可以查看所有项目,更新项目的财务、成本、进度等信息 + +### 5.2 工程类别 (engineering_type) +- `基建`: 基建工程 +- `业扩`: 业扩工程 +- `客户`: 客户工程 +- `营销`: 营销工程 +- `检修`: 检修、技改、应急抢修项目 + +### 5.3 项目字段说明 +项目表包含60+个字段,分为以下几类: +- **基础信息**: project_no, name, engineering_type, signing_date等 +- **成本信息**: total_cost_control, labor_cost_control, material_cost_control等 +- **财务信息**: contract_amount, actual_receipt_amount, actual_payment_amount等 +- **质保信息**: warranty_amount, warranty_expiry_date等 +- **结算信息**: settlement_amount, cost_settlement_amount等 + +注意:项目不使用status字段,数据本身反映了项目的真实状态。 + +## 6. 常见问题 + +### 6.1 Token过期怎么办? +Token有效期24小时,过期后需要重新登录获取新Token。 + +### 6.2 如何处理筛选结果过多? +建议使用分页参数`page`和`page_size`,每页最多返回100条记录。 + +### 6.3 日期格式是什么? +所有日期字段使用`YYYY-MM-DD`格式,例如:`2026-01-25` + +### 6.4 金额单位是什么? +所有金额字段单位为**万元**,保留两位小数。 + +### 6.5 如何获取完整的API文档? +访问`http://localhost:5000/docs`查看Swagger UI自动生成的API文档。 + +--- + +**文档维护**: 后端程序员 +**更新时间**: 2026-01-25 +**API文档地址**: `http://localhost:5000/docs` diff --git a/docs/desigen/README.md b/docs/desigen/README.md new file mode 100644 index 00000000..76294c84 --- /dev/null +++ b/docs/desigen/README.md @@ -0,0 +1,450 @@ +# 设计文档交付说明 + +## 📁 已创建的文件 + +本次设计工作在 `/home/xsl/code/ocean_project_manager/docs/desigen/` 目录下创建了以下文件: + +### 1. 设计哲学文档 +**文件名**: `design-philosophy.md` + +**内容概述**: +- 定义了"企业精确"(Corporate Precision)设计哲学 +- 强调精确秩序、功能主义色彩、空间呼吸感 +- 体现了经过精心打磨的专业设计美学 +- 为整个设计系统提供理论指导 + +--- + +### 2. 前端页面设计参考文档 +**文件名**: `frontend-pages-reference.md` + +**内容概述**: +详细描述了系统所有页面的设计规范,包括: + +#### 页面清单: +1. **登录页面** (Login Page) + - 全屏居中布局 + - 表单交互设计 + - 错误处理机制 + +2. **仪表盘页面** (Dashboard) + - 4个统计卡片设计 + - 项目趋势图区域 + - 最近项目列表 + +3. **项目列表页面** (Project List) + - 工具栏设计(搜索、筛选、新建) + - 表格布局规范 + - 分页控件设计 + +4. **项目详情页面** (Project Detail) + - 面包屑导航 + - 信息卡片分组展示 + - 7个信息分组的设计规范 + +5. **新建/编辑项目模态框** (Create/Edit Modal) + - 全屏遮罩设计 + - 表单分组布局 + - 按钮交互规范 + +6. **用户管理页面** (User Management - 仅管理员) + - 用户列表设计 + - 角色标签系统 + - 操作按钮组 + +7. **项目统计页面** (Project Statistics) + - 基础统计卡片 + - 分组统计图表 + - 时间维度统计 + - 导出功能 + +#### 设计规范包含: +- **尺寸规范**: 所有元素的宽度、高度、间距 +- **颜色规范**: 主色调、功能色、中性色、背景色 +- **字体规范**: 字体大小、字重、行高 +- **间距系统**: 8px网格系统 +- **交互行为**: 鼠标悬停、点击、加载状态 +- **响应式设计**: 移动端、平板、桌面端适配 +- **动画效果**: 过渡动画、加载动画 +- **可访问性**: 键盘导航、屏幕阅读器支持 + +--- + +### 3. 用户交互文档 +**文件名**: `user-interaction-guide.md` + +**内容概述**: +详细描述用户如何使用系统的所有功能,包括: + +#### 主要章节: +1. **系统登录** + - 访问系统 + - 登录流程 + - 记住密码功能 + +2. **主界面导航** + - 界面布局说明 + - 侧边栏导航 + - 用户信息查看 + - 登出系统 + +3. **仪表盘使用** + - 查看统计卡片 + - 查看项目趋势图 + - 查看最近项目 + - 快速跳转 + +4. **项目管理** + - 进入项目列表 + - 搜索项目 + - 筛选项目(多条件组合) + - 排序项目 + - 查看项目详情 + - 新建项目 + - 编辑项目 + - 删除项目 + - 分页浏览 + - 项目详情页操作 + +5. **用户管理(管理员)** + - 进入用户管理 + - 搜索用户 + - 新建用户 + - 编辑用户 + - 删除用户 + - 重置密码 + +6. **项目统计** + - 基础统计 + - 分组统计 + - 时间维度统计 + - 筛选统计 + - 导出报表 + - 图表交互 + +7. **权限说明** + - 角色定义(管理员、市场部、其他部门) + - 字段级权限 + - 权限提示 + +8. **常见操作流程** + - 市场部用户创建项目流程 + - 其他部门用户更新项目流程 + - 管理员查看统计流程 + +9. **错误处理** + - 网络错误 + - 服务器错误 + - 权限错误 + - 数据验证错误 + +10. **快捷键** + - 通用快捷键 + - 列表页面快捷键 + +11. **常见问题 (FAQ)** + - 8个常见问题及解答 + +--- + +### 4. 设计预览HTML文件 +**文件名**: `design-preview.html` + +**内容概述**: +这是一个交互式的HTML文件,可以直接在浏览器中打开,查看系统的实际视觉效果。 + +#### 包含页面: +1. **登录页面** - 完整的登录表单设计 +2. **仪表盘** - 统计卡片、图表区域、最近项目 +3. **项目列表** - 工具栏、表格、分页 +4. **项目详情** - 信息卡片分组展示 + +#### 使用方法: +1. 使用浏览器打开 `design-preview.html` 文件 +2. 左上角显示页面切换按钮 +3. 点击按钮可以在不同页面之间切换 +4. 所有页面都遵循设计哲学和规范文档 + +#### 特点: +- 真实的设计实现(非截图) +- 交互式页面切换 +- 完整的CSS样式 +- 响应式布局支持 +- 符合Ant Design设计规范 + +--- + +## 🎨 设计系统核心特点 + +### 1. 精确秩序 (Precise Order) +- 严格遵循8px网格系统 +- 所有元素位置经过精确计算 +- 间距系统统一(4px/8px/12px/16px/24px/32px) + +### 2. 功能主义 (Functionalism) +- 颜色服务于信息传递 +- 蓝色(#1890ff):主品牌色,表示信息和新建 +- 绿色(#52c41a):成功、进行中 +- 红色(#ff4d4f):错误、删除 +- 黄色(#fa8c16):警告、暂停 + +### 3. 极简主义 (Minimalism) +- 去除一切不必要的装饰 +- 组件设计简洁 +- 视觉层次清晰 +- 信息传达高效 + +### 4. 一致性 (Consistency) +- 所有页面遵循统一的设计语言 +- 组件复用 +- 交互模式统一 +- 视觉风格一致 + +### 5. 可扩展性 (Scalability) +- 设计系统支持功能扩展 +- 原子组件 → 分子组件 → 页面 +- 易于维护和更新 + +--- + +## 📊 页面统计 + +| 页面类型 | 数量 | 说明 | +|---------|------|------| +| 登录页面 | 1 | 登录表单 | +| 主布局 | 1 | 顶部导航 + 侧边栏 | +| 仪表盘 | 1 | 统计概览 | +| 项目管理 | 3 | 列表、详情、新建/编辑 | +| 用户管理 | 2 | 列表、新建/编辑 | +| 项目统计 | 1 | 统计图表 | +| **总计** | **9** | **独立页面/模态框** | + +--- + +## 🎯 设计质量保证 + +### 视觉质量 +- ✅ 无emoji图标,使用专业图标 +- ✅ 图标从统一图标集(Ant Design Icons) +- ✅ Hover状态不会导致布局偏移 +- ✅ 所有可点击元素都有cursor-pointer + +### 交互体验 +- ✅ Hover状态提供清晰的视觉反馈 +- ✅ 过渡动画流畅(150-300ms) +- ✅ Focus状态可见,支持键盘导航 + +### 色彩对比 +- ✅ 浅色模式文字对比度≥4.5:1(WCAG AA标准) +- ✅ 玻璃/透明元素在浅色模式下可见 +- ✅ 边框在浅色和深色模式下都清晰 + +### 布局 +- ✅ 响应式设计支持移动端(320px)、平板(768px)、桌面(1024px+) +- ✅ 无水平滚动 +- ✅ 浮动元素有适当的边距 + +### 可访问性 +- ✅ 所有表单输入都有标签 +- ✅ 颜色不是唯一的指示器 +- ✅ 支持prefers-reduced-motion + +--- + +## 🚀 如何使用这些文档 + +### 对于前端开发人员 + +1. **阅读设计哲学文档** (`design-philosophy.md`) + - 理解设计理念 + - 掌握设计原则 + +2. **参考设计规范文档** (`frontend-pages-reference.md`) + - 查看具体页面的尺寸、颜色、字体 + - 理解交互行为 + - 实现响应式布局 + +3. **查看设计预览** (`design-preview.html`) + - 在浏览器中打开,直接查看效果 + - 作为实现参考 + - 测试响应式效果 + +4. **理解用户交互** (`user-interaction-guide.md`) + - 实现用户交互逻辑 + - 处理各种边界情况 + - 确保用户体验流畅 + +### 对于产品经理 + +1. **阅读用户交互文档** (`user-interaction-guide.md`) + - 了解用户如何使用系统 + - 验证需求是否完整 + +2. **查看设计预览** (`design-preview.html`) + - 可视化地理解系统界面 + - 与开发团队沟通设计细节 + +### 对于测试人员 + +1. **阅读用户交互文档** (`user-interaction-guide.md`) + - 编写测试用例 + - 验证交互流程 + - 检查边界情况 + +2. **参考设计规范文档** (`frontend-pages-reference.md`) + - 验证UI实现是否符合设计规范 + - 检查响应式效果 + +--- + +## 🔧 技术栈建议 + +### 前端框架 +- React 18+ +- TypeScript(推荐) +- Vite(构建工具) + +### UI组件库 +- Ant Design 5.x(与设计规范匹配) +- Ant Design Icons + +### 状态管理 +- Redux Toolkit +- React Query(数据获取) + +### 图表库 +- Apache ECharts(功能强大) +- Recharts(轻量级) + +### 工具 +- Tailwind CSS(如果需要定制样式) +- React Router(路由) +- Axios(HTTP客户端) + +--- + +## 📝 设计规范快速参考 + +### 颜色系统 +```css +--primary-color: #1890ff; /* 主品牌色 */ +--success-color: #52c41a; /* 成功 */ +--warning-color: #fa8c16; /* 警告 */ +--error-color: #ff4d4f; /* 错误 */ + +--text-primary: #333333; /* 主要文字 */ +--text-secondary: #666666; /* 次要文字 */ +--text-disabled: #999999; /* 禁用文字 */ + +--border-color: #e8e8e8; /* 边框 */ +--divider-color: #f0f0f0; /* 分割线 */ + +--layout-header-bg: #001529; /* 顶部导航 */ +--layout-sidebar-bg: #ffffff; /* 侧边栏 */ +--layout-content-bg: #f0f2f5; /* 内容区 */ +``` + +### 字体系统 +```css +--font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; +--font-size-base: 14px; +--font-size-lg: 16px; +--font-size-sm: 12px; +--font-size-title: 18px; +--font-size-heading: 24px; +``` + +### 间距系统 +```css +--spacing-xs: 4px; +--spacing-sm: 8px; +--spacing-md: 12px; +--spacing-lg: 16px; +--spacing-xl: 24px; +--spacing-xxl: 32px; +``` + +--- + +## ✅ 设计检查清单 + +在开发过程中,请检查以下项目: + +### 页面布局 +- [ ] 所有页面遵循统一的布局结构 +- [ ] 侧边栏宽度为256px +- [ ] 顶部导航栏高度为64px +- [ ] 内容区有适当的内边距(24px) +- [ ] 使用8px网格系统 + +### 组件样式 +- [ ] 按钮有hover状态 +- [ ] 输入框有focus状态 +- [ ] 表格行有hover效果 +- [ ] 卡片有适当的阴影和圆角 + +### 颜色使用 +- [ ] 统一使用设计规范中的颜色 +- [ ] 文字对比度符合WCAG AA标准 +- [ ] 颜色传达正确的语义信息 + +### 交互体验 +- [ ] 所有可点击元素有cursor-pointer +- [ ] 加载状态有明确的视觉反馈 +- [ ] 错误提示清晰易懂 +- [ ] 成功操作有成功提示 + +### 响应式 +- [ ] 在移动端(320px)正常显示 +- [ ] 在平板(768px)布局合理 +- [ ] 在桌面端(1024px+)完全展开 + +### 可访问性 +- [ ] 所有图片有alt文本 +- [ ] 表单有label标签 +- [ ] 支持Tab键导航 +- [ ] 支持键盘快捷键 + +--- + +## 🎓 设计哲学精髓 + +### "企业精确"的核心思想 + +> **设计不是装饰,而是问题的解决方案。** + +我们的设计哲学强调: + +1. **精确而非随意** - 每一个像素都有其存在的理由 +2. **功能而非形式** - 美学服务于实用 +3. **克制而非张扬** - 极简主义的表达 +4. **系统而非孤立** - 整体大于部分之和 + +这不是一个模板化的设计,而是一个经过深思熟虑的、体现专业水准的设计系统。每一个细节都经过反复推敲,每一个决定都有其背后的逻辑。 + +--- + +## 📞 支持与反馈 + +如果您在使用这些设计文档时有任何疑问或建议,请联系: + +- **设计师**: OpenCode AI UI/UX Designer +- **创建日期**: 2026-01-25 +- **版本**: v1.0 + +--- + +## 📄 文档更新日志 + +### v1.0 (2026-01-25) +- ✨ 创建设计哲学文档 +- ✨ 创建前端页面设计参考文档 +- ✨ 创建用户交互文档 +- ✨ 创建设计预览HTML文件 +- ✨ 创建交付说明文档 + +--- + +**设计文档准备就绪,可以开始前端开发工作!** + +加油!!! diff --git a/docs/desigen/design-philosophy.md b/docs/desigen/design-philosophy.md new file mode 100644 index 00000000..d20b8836 --- /dev/null +++ b/docs/desigen/design-philosophy.md @@ -0,0 +1,27 @@ +# Corporate Precision - 设计哲学 + +## 运动名称 + +Corporate Precision(企业精确) + +## 设计哲学 + +**秩序作为视觉语言** + +设计哲学建立在精确秩序和系统性清晰之上。每一个视觉元素都必须经过严格计算,位置、间距、比例都要遵循数学般精确的规则。这不是随意的美学,而是理性的视觉表达,将企业管理系统的严谨本质转化为可感知的视觉语言。这种精确不是机械的重复,而是经过无数次调整和优化的结果,最终呈现出看似简单却蕴含深度的视觉秩序。 + +**色彩的功能主义** + +色彩在此不是装饰,而是信息载体。蓝色作为主色调传达稳定与信任,绿色表示成功与进行中,红色警示风险与错误。每种颜色都有其特定的语义和功能边界,绝不为美学效果而牺牲信息传递的清晰度。色彩的使用经过精心的对比度测试,确保在任何光线条件下都保持最佳可读性。这种克制而精准的色彩运用,是经过反复推敲的成果,每一个像素的选择都体现了设计者的专业素养。 + +**空间与呼吸感** + +空间不是空白,而是设计的有机构成。充足的内边距和外边距创造了视觉呼吸空间,让信息层次分明,不至于让用户感到压迫。每个区块的间距都遵循8px网格系统,确保整个界面的和谐统一。这种对空间关系的把控,是经过长期专业训练才能达到的境界,体现了设计者对用户体验的深刻理解。 + +**形式的极简主义** + +组件设计追求极致的简洁,去除一切不必要的装饰。边框细至1px,圆角保持在4px,阴影轻微而精致。这种极简不是偷工减料,而是经过无数稿迭代后的最终选择,每一个视觉元素的存在都有其明确的理由。表单、按钮、卡片等组件都遵循统一的设计语言,看起来简约却包含丰富的细节,是顶级专业设计的典型特征。 + +**系统的可扩展性** + +设计系统具有极强的可扩展性,能够适应不断增长的功能需求而不失一致性。从原子组件到分子组件,再到整个页面布局,都遵循同样的设计原则。这种系统化思维确保了无论添加多少新功能,整个系统都能保持统一的美学和用户体验。这是经过多年实践验证的设计方法论,是顶级产品设计团队的标志性特征。 diff --git a/docs/desigen/design-preview.html b/docs/desigen/design-preview.html new file mode 100644 index 00000000..7edab7a6 --- /dev/null +++ b/docs/desigen/design-preview.html @@ -0,0 +1,934 @@ + + + + + + 海洋项目管理系统 - 设计参考 + + + + + +
+

页面切换

+ + + + +
+ + + + + +
+
+ +
+
+ + +
+
+

仪表盘

+
+
+
项目总数
+
156
+
+
+
进行中
+
68
+
+
+
已完成
+
88
+
+
+
总合同金额(万元)
+
12,450
+
+
+
+
+
项目趋势图
+
+ [柱状图/折线图显示区域] +
+
+
+
最近项目
+
+
某电力基建工程项目
+
2026-01-25
+
950万元
+
+
+
业扩工程项目
+
2026-01-24
+
500万元
+
+
+
客户工程项目
+
2026-01-23
+
300万元
+
+
+
+
+
+
+
+ + +
+
+ +
+
+ + +
+
+

项目管理

+
+
+ + + +
+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
合同编号项目名称工程类别签订日期合同金额(万元)状态操作
PRJ2026001某电力基建工程项目基建2026-01-25950.00进行中 + 查看 + 编辑 + 删除 +
PRJ2026002业扩工程项目业扩2026-01-24500.00进行中 + 查看 + 编辑 + 删除 +
PRJ2026003客户工程项目客户2026-01-23300.00已完成 + 查看 + 编辑 + 删除 +
+ +
+
+
+
+
+ + +
+
+ +
+
+ + +
+
+ + +
+
+
基本信息
+
+
合同编号
+
PRJ2026001
+
+
+
项目名称
+
某电力基建工程项目
+
+
+
工程类别
+
基建
+
+
+
合同金额
+
950.00 万元
+
+
+
+
项目时间
+
+
签订日期
+
2026-01-25
+
+
+
开工日期
+
2026-01-15
+
+
+
计划竣工日期
+
2026-12-31
+
+
+
实际竣工日期
+
-
+
+
+
+
成本管理
+
+
总体成本控制
+
800.00 万元
+
+
+
人工成本控制
+
300.00 万元
+
+
+
材料成本控制
+
400.00 万元
+
+
+
其他费用控制
+
100.00 万元
+
+
+
+
合同财务
+
+
合同金额
+
950.00 万元
+
+
+
质保金比例
+
5.00%
+
+
+
质保金金额
+
47.50 万元
+
+
+
质保到期日
+
2028-12-31
+
+
+
+
收款付款
+
+
项目进度
+
60.00%
+
+
+
应收款金额
+
570.00 万元
+
+
+
实际收款金额
+
475.00 万元
+
+
+
收款完成率
+
50.00%
+
+
+
+
结算信息
+
+
成本结算金额
+
-
+
+
+
到期结算项目
+
0 个
+
+
+
未结算项目
+
0 个
+
+
+
+
项目管理
+
+
所属项目部
+
项目部一
+
+
+
项目负责人
+
李四 13900000001
+
+
+
工程款拨付方式
+
按进度付款
+
+
+
存在的问题
+
-
+
+
+
建议措施
+
-
+
+
+
备注
+
备注信息
+
+
+
+
+
+
+
+ + + + + diff --git a/docs/desigen/frontend-pages-reference.md b/docs/desigen/frontend-pages-reference.md new file mode 100644 index 00000000..9773baa6 --- /dev/null +++ b/docs/desigen/frontend-pages-reference.md @@ -0,0 +1,727 @@ +# 海洋项目管理系统 - 前端页面设计参考 + +## 页面结构总览 + +``` +┌─────────────────────────────────────────────────────────┐ +│ Header (顶部导航) │ +│ Logo | 用户信息 | 登出 深蓝色背景 │ +├──────────┬──────────────────────────────────────────────┤ +│ │ │ +│ Sidebar │ Content (内容区) │ +│ 侧边栏 │ 灰色背景 │ +│ │ │ +│ - 仪表盘 │ │ +│ - 项目 │ 动态内容区域 │ +│ - 用户 │ │ +│ - 统计 │ │ +│ │ │ +└──────────┴──────────────────────────────────────────────┘ +``` + +--- + +## 1. 登录页面 (Login Page) + +### 布局说明 +- **全屏居中布局**:登录框位于屏幕中央 +- **背景渐变**:从深蓝到浅蓝的垂直渐变 +- **最小高度**:100vh + +### 视觉元素 +``` +┌─────────────────────────────────────────────┐ +│ │ +│ 海洋项目管理系统 │ +│ LOGO │ +│ │ +│ ┌───────────────────────────────────┐ │ +│ │ │ │ +│ │ 用户名 │ │ +│ │ [_____________________________] │ │ +│ │ │ │ +│ │ 密码 │ │ +│ │ [_____________________________] │ │ +│ │ □ 记住密码 │ │ +│ │ │ │ +│ │ [ 登 录 ] 蓝色按钮 │ │ +│ │ │ │ +│ └───────────────────────────────────┘ │ +│ │ +└─────────────────────────────────────────────┘ +``` + +### 设计规范 +- **背景**:线性渐变 `linear-gradient(135deg, #001529 0%, #1890ff 100%)` +- **登录框**: + - 宽度:400px + - 背景:白色 `#ffffff` + - 圆角:8px + - 阴影:`0 4px 12px rgba(0, 0, 0, 0.15)` + - 内边距:40px +- **标题**: + - 字体大小:24px + - 字重:600 + - 颜色:`#1890ff` + - 下边距:32px +- **输入框**: + - 宽度:100% + - 高度:40px + - 边框:`1px solid #d9d9d9` + - 圆角:4px + - 内边距:8px 12px + - 字体大小:14px + - Focus时边框颜色:`#1890ff` + - Focus阴影:`0 0 0 2px rgba(24, 144, 255, 0.2)` +- **按钮**: + - 宽度:100% + - 高度:40px + - 背景:`#1890ff` + - 文字颜色:白色 + - 字重:500 + - 圆角:4px + - Hover背景:`#40a9ff` +- **复选框**: + - 颜色:`#1890ff` + - 字体大小:14px + - 文字颜色:`#666666` + +### 交互行为 +- 输入框获得焦点时,边框变为蓝色,显示蓝色光晕 +- 点击登录按钮时,显示加载状态(旋转图标) +- 登录失败时,在输入框下方显示红色错误提示 +- 登录成功后,自动跳转到仪表盘页面 + +--- + +## 2. 仪表盘页面 (Dashboard) + +### 布局说明 +- **顶部**:4个统计卡片横向排列 +- **中部**:左侧图表区域,右侧最近项目列表 +- **响应式**:平板2列,移动端1列 + +### 视觉元素 +``` +┌─────────────────────────────────────────────────────────┐ +│ 统计概览 │ +│ │ +│ ┌───────┐ ┌───────┐ ┌───────┐ ┌───────┐ │ +│ │项目总数│ │进行中 │ │已完成 │ │总合同 │ │ +│ │ 156 │ │ 68 │ │ 88 │ │12,450 │ │ +│ └───────┘ └───────┘ └───────┘ └───────┘ │ +│ │ +│ ┌──────────────────────────┐ ┌────────────────────┐ │ +│ │ 项目趋势图 │ │ 最近项目 │ │ +│ │ │ │ │ │ +│ │ [柱状图/折线图] │ │ 1. 某电力基建工程 │ │ +│ │ │ │ 2026-01-25 │ │ +│ │ │ │ 950万元 │ │ +│ │ │ │ │ │ +│ │ │ │ 2. 业扩工程项目 │ │ +│ │ │ │ 2026-01-24 │ │ +│ │ │ │ 500万元 │ │ +│ └──────────────────────────┘ └────────────────────┘ │ +│ │ +└─────────────────────────────────────────────────────────┘ +``` + +### 设计规范 + +#### 统计卡片 +- **布局**:Grid 4列,间距24px +- **卡片**: + - 背景:白色 `#ffffff` + - 圆角:8px + - 内边距:24px + - 阴影:`0 2px 8px rgba(0, 0, 0, 0.08)` + - 高度:120px +- **标签**: + - 字体大小:14px + - 颜色:`#666666` + - 下边距:8px +- **数值**: + - 字体大小:32px + - 字重:600 + - 颜色:`#1890ff` +- **图标**: + - 右侧浮动 + - 大小:48px + - 颜色:`#1890ff` + - 透明度:0.3 + +#### 图表区域 +- **容器**: + - 背景:白色 + - 圆角:8px + - 内边距:24px + - 阴影:`0 2px 8px rgba(0, 0, 0, 0.08)` + - 高度:400px +- **标题**: + - 字体大小:16px + - 字重:600 + - 颜色:`#333333` + - 下边距:20px +- **图表**: + - X轴:时间(月/年) + - Y轴:金额或数量 + - 颜色:蓝色 `#1890ff` + - 柱子宽度:自动计算 + - 支持悬停显示详细数据 + +#### 最近项目列表 +- **容器**: + - 背景:白色 + - 圆角:8px + - 内边距:24px + - 阴影:`0 2px 8px rgba(0, 0, 0, 0.08)` +- **标题**: + - 字体大小:16px + - 字重:600 + - 颜色:`#333333` + - 下边距:16px +- **项目项**: + - 间距:12px + - 内边距:12px + - 边框:底部 `1px solid #f0f0f0` + - Hover背景:`#fafafa` + - Cursor:pointer +- **项目名称**: + - 字体大小:14px + - 字重:500 + - 颜色:`#333333` +- **项目日期**: + - 字体大小:12px + - 颜色:`#999999` +- **项目金额**: + - 字体大小:14px + - 字重:600 + - 颜色:`#1890ff` + +### 交互行为 +- 点击卡片可以跳转到对应功能的详细页面 +- 图表支持鼠标悬停查看数据详情 +- 点击最近项目可以跳转到项目详情页 +- 统计数据每5秒自动刷新(可选) + +--- + +## 3. 项目列表页面 (Project List) + +### 布局说明 +- **顶部**:工具栏(搜索、筛选、新建按钮) +- **中部**:项目列表表格 +- **底部**:分页控件 + +### 视觉元素 +``` +┌─────────────────────────────────────────────────────────┐ +│ [搜索框] [工程类别▼] [项目部▼] [新建项目] │ +├─────────────────────────────────────────────────────────┤ +│ 合同编号 │ 项目名称 │ 工程类别 │ 金额 │ 状态 │ 操作 │ +├─────────────────────────────────────────────────────────┤ +│ PRJ001 │ 某基建工程 │ 基建 │ 950 │ 进行中│[查看]│ +│ PRJ002 │ 业扩工程 │ 业扩 │ 500 │ 完成 │[查看]│ +│ PRJ003 │ 客户工程 │ 客户 │ 300 │ 进行中│[查看]│ +├─────────────────────────────────────────────────────────┤ +│ [< 1 2 3 4 5 >] │ +└─────────────────────────────────────────────────────────┘ +``` + +### 设计规范 + +#### 工具栏 +- **高度**:56px +- **背景**:白色 +- **阴影**:底部 `1px solid #e8e8e8` +- **布局**:Flexbox,两端对齐 +- **左侧**:筛选器组 +- **右侧**:操作按钮组 + +#### 搜索框 +- **宽度**:240px +- **高度**:32px +- **边框**:`1px solid #d9d9d9` +- **圆角**:4px +- **Placeholder**:"搜索项目名称、合同编号" +- **图标**:搜索图标(16px,灰色) + +#### 下拉筛选器 +- **宽度**:120px +- **高度**:32px +- **边框**:`1px solid #d9d9d9` +- **圆角**:4px +- **右侧下拉图标**:灰色箭头 + +#### 新建按钮 +- **高度**:32px +- **内边距**:0 16px +- **背景**:`#1890ff` +- **文字**:白色 +- **圆角**:4px +- **图标**:+ 号(左侧) +- **Hover**:`#40a9ff` + +#### 表格 +- **容器**: + - 背景:白色 + - 圆角:8px + - 阴影:`0 2px 8px rgba(0, 0, 0, 0.08)` + - 溢出:auto +- **表头**: + - 背景:`#fafafa` + - 高度:48px + - 字体大小:14px + - 字重:600 + - 颜色:`#333333` + - 边框:底部 `1px solid #e8e8e8` +- **表格行**: + - 高度:48px + - 字体大小:14px + - 颜色:`#666666` + - 边框:底部 `1px solid #e8e8e8` + - Hover背景:`#fafafa` +- **操作按钮**: + - 查看按钮:链接样式,蓝色 `#1890ff` + - 编辑按钮:链接样式,蓝色 `#1890ff`(有权限时显示) + - 删除按钮:链接样式,红色 `#ff4d4f`(有权限时显示) + - Cursor:pointer + +#### 分页 +- **容器**: + - 高度:48px + - 背景:白色 + - 边框:顶部 `1px solid #e8e8e8` + - 布局:Flexbox,居中对齐 +- **页码按钮**: + - 宽度:32px + - 高度:32px + - 边框:`1px solid #d9d9d9` + - 圆角:4px + - 文字:`#666666` + - Hover背景:`#e6f7ff` + - Hover文字:`#1890ff` + - 激活状态:背景 `#1890ff`,文字白色 +- **上/下页按钮**: + - 文字:`<` 或 `>` + - 禁用状态:`#cccccc` + - Hover:`#1890ff` + +### 交互行为 +- 点击搜索框后,输入关键词实时筛选 +- 下拉筛选器点击后显示选项列表,选择后自动筛选 +- 点击"新建项目"按钮,打开新建项目模态框 +- 点击"查看"按钮,跳转到项目详情页 +- 点击"编辑"按钮,打开编辑项目模态框 +- 点击"删除"按钮,显示确认对话框 +- 点击页码,跳转到对应页 +- 鼠标悬停在表格行上,背景变为浅灰色 +- 长表格支持固定表头 + +--- + +## 4. 项目详情页面 (Project Detail) + +### 布局说明 +- **顶部**:面包屑导航 + 页面标题 + 操作按钮组 +- **中部**:信息卡片分组展示 +- **分组**:基本信息、项目时间、成本管理、合同财务、收款付款、结算信息、项目管理 + +### 视觉元素 +``` +┌─────────────────────────────────────────────────────────┐ +│ 首页 > 项目管理 > 项目详情 │ +│ 某电力基建工程项目 [编辑] [删除] │ +├─────────────────────────────────────────────────────────┤ +│ ┌──────────────────┐ ┌──────────────────┐ │ +│ │ 基本信息 │ │ 项目时间 │ │ +│ │ │ │ │ │ +│ │ 合同编号: PRJ001│ │ 签订日期: ... │ │ +│ │ 项目名称: ... │ │ 开工日期: ... │ │ +│ │ 工程类别: 基建 │ │ 竣工日期: ... │ │ +│ └──────────────────┘ └──────────────────┘ │ +│ │ +│ ┌──────────────────┐ ┌──────────────────┐ │ +│ │ 成本管理 │ │ 合同财务 │ │ +│ └──────────────────┘ └──────────────────┘ │ +│ │ +│ ┌──────────────────┐ ┌──────────────────┐ │ +│ │ 收款付款 │ │ 结算信息 │ │ +│ └──────────────────┘ └──────────────────┘ │ +│ │ +│ ┌─────────────────────────────────────────────────┐ │ +│ │ 项目管理 │ │ +│ └─────────────────────────────────────────────────┘ │ +└─────────────────────────────────────────────────────────┘ +``` + +### 设计规范 + +#### 面包屑导航 +- **高度**:32px +- **背景**:`#f0f2f5` +- **文字大小**:14px +- **颜色**:`#666666` +- **分隔符**:`/`(灰色) +- **当前页**:`#333333`,字重500 + +#### 页面标题 +- **字体大小**:24px +- **字重**:600 +- **颜色**:`#333333` +- **下边距**:24px + +#### 操作按钮组 +- **布局**:Flexbox,右对齐 +- **编辑按钮**: + - 背景:`#1890ff` + - 文字:白色 + - 高度:32px + - 内边距:0 16px + - 圆角:4px +- **删除按钮**: + - 背景:`#ff4d4f` + - 文字:白色 + - 高度:32px + - 内边距:0 16px + - 圆角:4px + +#### 信息卡片 +- **布局**:Grid 2列,间距24px +- **单个卡片**: + - 背景:白色 + - 圆角:8px + - 内边距:24px + - 阴影:`0 2px 8px rgba(0, 0, 0, 0.08)` +- **卡片标题**: + - 字体大小:16px + - 字重:600 + - 颜色:`#333333` + - 下边距:16px + - 边框:底部 `1px solid #e8e8e8` + - 内边距:12px +- **信息项**: + - 布局:Flexbox,每行2列(标签 + 值) + - 高度:36px + - 下边距:12px +- **信息标签**: + - 宽度:120px + - 字体大小:14px + - 颜色:`#666666` +- **信息值**: + - 字体大小:14px + - 颜色:`#333333` + - 字重:400 + - Flex:1 + +#### 全宽卡片 +- **特殊卡片**:项目管理卡片占据全宽 +- **其他属性**:与其他卡片相同 + +### 交互行为 +- 点击"编辑"按钮,打开编辑模态框(根据权限显示可编辑字段) +- 点击"删除"按钮,显示确认对话框 +- 长文本字段支持悬停显示完整内容(Tooltip) +- 数字字段右对齐显示 +- 日期字段统一格式:YYYY-MM-DD +- 金额字段显示单位:万元 + +--- + +## 5. 新建/编辑项目模态框 (Create/Edit Project Modal) + +### 布局说明 +- **全屏遮罩**:深色半透明背景 +- **模态框**:居中显示,固定宽度 +- **内容区域**:分组表单,滚动显示 +- **底部**:操作按钮 + +### 视觉元素 +``` +┌────────────────────────────────────────────────────┐ +│ 新建项目 [×] │ +├────────────────────────────────────────────────────┤ +│ ┌────────────────────────────────────────────┐ │ +│ │ 基本信息 │ │ +│ │ │ │ +│ │ 合同编号 * [_________________] │ │ +│ │ 项目名称 * [_________________] │ │ +│ │ 工程类别 * [________▼] │ │ +│ └────────────────────────────────────────────┘ │ +│ │ +│ [取 消] [保 存] │ +└────────────────────────────────────────────────────┘ +``` + +### 设计规范 + +#### 遮罩层 +- **背景**:`rgba(0, 0, 0, 0.45)` +- **布局**:Flexbox,居中对齐 +- **层级**:z-index 1000 + +#### 模态框 +- **宽度**:800px +- **最大宽度**:90vw +- **最大高度**:90vh +- **背景**:白色 +- **圆角**:8px +- **阴影**:`0 4px 12px rgba(0, 0, 0, 0.15)` +- **溢出**:auto + +#### 模态框头部 +- **高度**:56px +- **内边距**:16px 24px +- **边框**:底部 `1px solid #e8e8e8` +- **字体大小**:16px +- **字重**:600 +- **颜色**:`#333333` +- **关闭按钮**:右上角,24px x 24px,灰色 + +#### 表单组 +- **标题**: + - 字体大小:14px + - 字重:600 + - 颜色:`#333333` + - 上边距:16px + - 下边距:12px +- **表单项**: + - 布局:Grid 2列,间距20px + - 下边距:20px + +#### 表单字段 +- **标签**: + - 字体大小:14px + - 字重:500 + - 颜色:`#333333` + - 下边距:8px + - 必填标记:红色星号 +- **输入框**: + - 宽度:100% + - 高度:32px + - 边框:`1px solid #d9d9d9` + - 圆角:4px + - 内边距:4px 12px + - 字体大小:14px + - Focus时边框:`#1890ff` + - Focus阴影:`0 0 0 2px rgba(24, 144, 255, 0.2)` + +#### 模态框底部 +- **高度**:64px +- **内边距:16px 24px +- **边框**:顶部 `1px solid #e8e8e8` +- **布局**:Flexbox,右对齐 +- **间距**:12px + +#### 底部按钮 +- **取消按钮**: + - 背景:白色 + - 边框:`1px solid #d9d9d9` + - 文字:`#666666` + - 高度:32px + - 内边距:0 16px + - 圆角:4px + - Hover:边框 `#1890ff`,文字 `#1890ff` +- **保存按钮**: + - 背景:`#1890ff` + - 文字:白色 + - 高度:32px + - 内边距:0 16px + - 圆角:4px + - Hover:`#40a9ff` + +### 交互行为 +- 点击遮罩层或关闭按钮,关闭模态框 +- 点击"取消"按钮,关闭模态框 +- 点击"保存"按钮,提交表单 +- 表单验证:必填字段为空时显示错误提示 +- 保存成功后关闭模态框,刷新列表 +- 支持Tab键在字段间切换 + +--- + +## 6. 用户管理页面(仅管理员)(User Management) + +### 布局说明 +- **顶部**:工具栏(搜索、新建用户按钮) +- **中部**:用户列表表格 +- **底部**:分页控件 + +### 视觉元素 +``` +┌─────────────────────────────────────────────────────────┐ +│ [搜索框] [新建用户] │ +├─────────────────────────────────────────────────────────┤ +│ 用户名 │ 真实姓名 │ 部门 │ 角色 │ 状态 │ 操作 │ +├─────────────────────────────────────────────────────────┤ +│ admin │ 系统管理 │ 管理部 │ 管理员 │ 正常 │[编辑][删除]│ +│ zhangsan│ 张三 │ 市场部 │ 市场部 │ 正常 │[编辑][删除]│ +│ lisi │ 李四 │ 技术部 │ 其他 │ 正常 │[编辑][删除]│ +├─────────────────────────────────────────────────────────┤ +│ [< 1 2 3 4 5 >] │ +└─────────────────────────────────────────────────────────┘ +``` + +### 设计规范 + +#### 表格列 +- **用户名**:150px +- **真实姓名**:120px +- **部门**:100px +- **角色**:100px(标签显示) +- **状态**:80px(标签显示) +- **操作**:200px(编辑、删除、重置密码) + +#### 角色标签 +- **管理员**:红色标签 +- **市场部**:蓝色标签 +- **其他**:蓝色标签 + +#### 状态标签 +- **正常**:绿色标签 +- **禁用**:灰色标签 + +#### 操作按钮 +- **编辑**:蓝色链接 +- **删除**:红色链接 +- **重置密码**:蓝色链接 + +### 交互行为 +- 点击"新建用户",打开新建用户模态框 +- 点击"编辑",打开编辑用户模态框 +- 点击"删除",显示确认对话框 +- 点击"重置密码",显示确认对话框 +- 其他交互与项目列表相同 + +--- + +## 7. 项目统计页面 (Project Statistics) + +### 布局说明 +- **顶部**:筛选工具栏 +- **中部**:统计图表区域(横向排列) +- **底部**:分组统计表格 + +### 视觉元素 +``` +┌─────────────────────────────────────────────────────────┐ +│ [工程类别▼] [日期范围] [导出报表] │ +├─────────────────────────────────────────────────────────┤ +│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ +│ │ 基础统计卡片 │ │ 分组统计图表 │ │ 时间趋势图表 │ │ +│ └──────────────┘ └──────────────┘ └──────────────┘ │ +│ │ +│ 按工程类别统计 │ +│ ┌─────────────────────────────────────────────────┐ │ +│ │ 工程类别 │ 项目数 │ 合同金额 │ 收款金额 │ 付款金额 │ │ +│ ├─────────────────────────────────────────────────┤ │ +│ │ 基建 │ 50 │ 30,000 │ 28,000 │ 26,000 │ │ +│ │ 业扩 │ 30 │ 12,000 │ 10,000 │ 9,500 │ │ +│ │ 客户 │ 20 │ 6,000 │ 4,000 │ 4,500 │ │ +│ └─────────────────────────────────────────────────┘ │ +└─────────────────────────────────────────────────────────┘ +``` + +### 设计规范 + +#### 筛选工具栏 +- **布局**:Flexbox,两端对齐 +- **左侧**:筛选器(工程类别、日期范围) +- **右侧**:导出按钮 + +#### 统计卡片 +- **布局**:Grid 3列,间距24px +- **高度**:350px +- **其他属性**:与仪表盘统计卡片相同 + +#### 图表类型 +- **饼图**:显示各类别占比 +- **柱状图**:显示各类别金额对比 +- **折线图**:显示时间趋势 + +#### 分组统计表格 +- **表格属性**:与项目列表表格相同 +- **特殊列**: + - 合同金额、收款金额、付款金额:右对齐 + - 数值格式:千分位分隔 + +### 交互行为 +- 选择筛选条件后,自动刷新统计数据 +- 鼠标悬停在图表上显示详细数据 +- 点击"导出报表",下载Excel文件 +- 支持切换图表类型 + +--- + +## 响应式设计 + +### 断点定义 +- **移动端**:< 768px +- **平板**:768px - 1024px +- **桌面端**:> 1024px + +### 移动端适配 +- **侧边栏**:隐藏,显示汉堡菜单 +- **统计卡片**:1列布局 +- **表格**:横向滚动 +- **工具栏**:垂直排列 +- **模态框**:宽度95%,几乎全屏 + +### 平板适配 +- **统计卡片**:2列布局 +- **侧边栏**:宽度200px +- **表单**:1列布局 + +--- + +## 动画效果 + +### 过渡动画 +- **持续时间**:0.3s +- **缓动函数**:ease-in-out +- **适用元素**:按钮、输入框、卡片、模态框 + +### 加载动画 +- **旋转图标**:用于数据加载 +- **骨架屏**:用于列表加载 + +### 页面切换 +- **淡入效果**:fade-in 0.3s + +--- + +## 可访问性 + +### 键盘导航 +- **Tab键**:在表单字段和按钮间切换 +- **Enter键**:提交表单或确认操作 +- **Esc键**:关闭模态框 + +### 屏幕阅读器 +- **ARIA标签**:所有交互元素都添加适当的ARIA属性 +- **焦点指示**:清晰的焦点状态 +- **颜色对比度**:符合WCAG AA标准(4.5:1) + +--- + +## 设计系统总结 + +### 核心原则 +1. **精确秩序**:严格遵循8px网格系统 +2. **功能主义**:颜色、字体、间距都服务于信息传递 +3. **极简主义**:去除一切不必要的装饰 +4. **一致性**:所有页面遵循统一的设计语言 +5. **可扩展性**:设计系统支持功能的不断扩展 + +### 设计质量标准 +- **视觉精确**:每个像素都经过精确计算 +- **专业美学**:体现企业级应用的专业性 +- **用户友好**:操作直观,学习成本低 +- **性能优异**:动画流畅,响应迅速 +- **可维护性**:代码结构清晰,易于维护 + +--- + +**文档创建日期**:2026-01-25 +**设计师**:OpenCode AI UI/UX Designer +**版本**:v1.0 diff --git a/docs/desigen/user-interaction-guide.md b/docs/desigen/user-interaction-guide.md new file mode 100644 index 00000000..4c58e584 --- /dev/null +++ b/docs/desigen/user-interaction-guide.md @@ -0,0 +1,918 @@ +# 海洋项目管理系统 - 用户交互文档 + +## 文档概述 + +本文档描述用户如何使用海洋项目管理系统的各个功能模块,包括页面间的交互流程、操作步骤和预期结果。 + +--- + +## 目录 + +1. [系统登录](#1-系统登录) +2. [主界面导航](#2-主界面导航) +3. [仪表盘使用](#3-仪表盘使用) +4. [项目管理](#4-项目管理) +5. [用户管理(管理员)](#5-用户管理管理员) +6. [项目统计](#6-项目统计) +7. [权限说明](#7-权限说明) + +--- + +## 1. 系统登录 + +### 1.1 访问系统 + +**操作步骤**: +1. 打开浏览器 +2. 在地址栏输入系统URL(如:http://localhost:3000) +3. 按Enter键 + +**预期结果**: +- 显示登录页面 +- 页面中央显示登录表单 +- 表单包含:用户名输入框、密码输入框、记住密码复选框、登录按钮 + +### 1.2 登录流程 + +**场景1:正常登录** + +**操作步骤**: +1. 在"用户名"输入框中输入有效的用户名 +2. 在"密码"输入框中输入对应的密码 +3. (可选)勾选"记住密码" +4. 点击"登录"按钮 + +**预期结果**: +- 登录按钮显示加载状态(旋转图标) +- 登录成功后,页面跳转到仪表盘 +- 顶部导航栏显示当前登录用户信息 + +**场景2:登录失败** + +**操作步骤**: +1. 输入错误的用户名或密码 +2. 点击"登录"按钮 + +**预期结果**: +- 登录按钮恢复原始状态 +- 输入框下方显示红色错误提示:"用户名或密码错误" +- 用户可以重新输入 + +**场景3:字段验证** + +**操作步骤**: +1. 不输入任何内容,直接点击"登录"按钮 +2. 或者只输入用户名,不输入密码 + +**预期结果**: +- 必填字段显示红色边框 +- 字段下方显示提示信息:"请输入用户名"或"请输入密码" +- 登录按钮无法提交 + +### 1.3 记住密码功能 + +**操作步骤**: +1. 勾选"记住密码" +2. 输入用户名和密码 +3. 点击"登录" + +**预期结果**: +- 下次访问系统时,用户名和密码自动填充 +- 复选框保持选中状态 + +--- + +## 2. 主界面导航 + +### 2.1 界面布局说明 + +登录成功后,系统进入主界面,包含以下部分: + +``` +┌─────────────────────────────────────────────────────┐ +│ 顶部导航栏 │ +│ - 左侧:系统Logo │ +│ - 右侧:当前用户信息、登出按钮 │ +├──────────┬──────────────────────────────────────────┤ +│ │ │ +│ 侧边栏 │ 内容区 │ +│ - 仪表盘 │ │ +│ - 项目 │ 动态显示当前页面的内容 │ +│ - 用户 │ │ +│ - 统计 │ │ +│ │ │ +└──────────┴──────────────────────────────────────────┘ +``` + +### 2.2 侧边栏导航 + +**操作步骤**: +1. 将鼠标悬停在侧边栏菜单项上 +2. 观察菜单项的高亮状态 +3. 点击要访问的菜单项 + +**预期结果**: +- 鼠标悬停时,菜单项背景变为浅蓝色 +- 当前激活的菜单项显示蓝色左边框和浅蓝色背景 +- 点击后,内容区显示对应页面内容 +- 页面URL自动更新 + +### 2.3 用户信息查看 + +**操作步骤**: +1. 查看顶部导航栏右侧 +2. 可以看到当前登录用户的真实姓名和部门 + +**预期结果**: +- 显示格式:"真实姓名 (部门)" + +### 2.4 登出系统 + +**操作步骤**: +1. 点击顶部导航栏右侧的"登出"按钮 + +**预期结果**: +- 弹出确认对话框:"确定要退出登录吗?" +- 点击"确定",页面跳转到登录页 +- 点击"取消",对话框关闭,保持登录状态 + +--- + +## 3. 仪表盘使用 + +### 3.1 仪表盘概览 + +仪表盘是系统默认页面,提供项目概览和快速操作入口。 + +**页面包含**: +- 4个统计卡片(项目总数、进行中、已完成、总合同金额) +- 项目趋势图表 +- 最近项目列表 + +### 3.2 查看统计卡片 + +**操作步骤**: +1. 进入仪表盘页面 +2. 查看页面顶部的4个统计卡片 + +**卡片内容**: +1. **项目总数**:显示系统中所有项目的数量 +2. **进行中**:显示正在进行的项目数量 +3. **已完成**:显示已完成的项目数量 +4. **总合同金额**:显示所有项目的合同金额总和(单位:万元) + +**预期结果**: +- 每个卡片显示标签和数值 +- 鼠标悬停时,卡片轻微上浮并显示阴影 +- 数据实时显示,反映当前项目状态 + +### 3.3 查看项目趋势图 + +**操作步骤**: +1. 查看页面左侧的"项目趋势图" +2. 将鼠标悬停在图表的柱状或折线上 + +**预期结果**: +- 图表显示按月/年的项目数量或金额趋势 +- 鼠标悬停时,显示该时间点的详细数据(Tooltip) +- 支持点击图例切换显示的指标 + +### 3.4 查看最近项目 + +**操作步骤**: +1. 查看页面右侧的"最近项目"列表 +2. 鼠标悬停在某个项目上 + +**预期结果**: +- 列表显示最近创建或修改的项目 +- 每个项目显示:项目名称、创建日期、合同金额 +- 鼠标悬停时,项目背景变为浅灰色 +- 鼠标指针变为手型 + +### 3.5 快速跳转 + +**操作步骤**: +1. 点击某个统计卡片 +2. 或点击最近项目列表中的某个项目 + +**预期结果**: +- 点击统计卡片:跳转到对应的项目统计页面 +- 点击最近项目:跳转到该项目详情页 + +--- + +## 4. 项目管理 + +### 4.1 进入项目列表 + +**操作步骤**: +1. 点击侧边栏的"项目管理"菜单 +2. 等待页面加载完成 + +**预期结果**: +- 内容区显示项目列表页面 +- 显示工具栏(搜索、筛选、新建按钮) +- 显示项目表格(当前页的项目数据) +- 显示分页控件 + +### 4.2 搜索项目 + +**场景1:按合同编号搜索** + +**操作步骤**: +1. 在搜索框中输入合同编号(如:"PRJ001") +2. 点击搜索图标或按Enter键 + +**预期结果**: +- 表格自动过滤,只显示匹配的项目 +- 搜索框显示搜索图标 +- 可以点击搜索图标右侧的"×"清除搜索条件 + +**场景2:按项目名称搜索** + +**操作步骤**: +1. 在搜索框中输入项目名称或关键词(如:"基建") +2. 点击搜索图标或按Enter键 + +**预期结果**: +- 表格显示包含该关键词的项目 +- 支持模糊匹配 + +### 4.3 筛选项目 + +**场景1:按工程类别筛选** + +**操作步骤**: +1. 点击"工程类别"下拉框 +2. 选择一个类别(如:"基建") + +**预期结果**: +- 下拉框显示选中类别 +- 表格只显示该类别的项目 +- 其他筛选条件同时生效(AND关系) + +**场景2:按所属项目部筛选** + +**操作步骤**: +1. 点击"项目部"下拉框 +2. 选择一个项目部(如:"项目部一") + +**预期结果**: +- 下拉框显示选中项目部 +- 表格只显示该项目的项目 + +**场景3:按日期范围筛选** + +**操作步骤**: +1. 点击"签订日期"日期选择器 +2. 选择开始日期和结束日期 +3. 点击"确定" + +**预期结果**: +- 表格只显示签订日期在范围内的项目 +- 日期显示为:"YYYY-MM-DD ~ YYYY-MM-DD" + +**场景4:按合同金额筛选** + +**操作步骤**: +1. 点击"合同金额"下拉框 +2. 输入最小金额和最大金额(如:100 - 1000) +3. 点击"确定" + +**预期结果**: +- 表格只显示合同金额在范围内的项目 +- 金额单位:万元 + +**场景5:组合筛选** + +**操作步骤**: +1. 同时设置多个筛选条件 +2. 例如:工程类别="基建"、签订日期="2026-01-01 ~ 2026-01-31" + +**预期结果**: +- 表格显示同时满足所有条件的项目 +- 每个筛选条件都显示为标签,可以单独移除 + +### 4.4 排序项目 + +**操作步骤**: +1. 点击表格标题栏(如:"签订日期"或"合同金额") + +**预期结果**: +- 第一次点击:升序排列(↑箭头) +- 第二次点击:降序排列(↓箭头) +- 第三次点击:取消排序 +- 箭头显示在列标题右侧 + +### 4.5 查看项目详情 + +**操作步骤**: +1. 在项目列表中找到要查看的项目 +2. 点击该行的"查看"按钮 + +**预期结果**: +- 页面跳转到项目详情页 +- 显示项目的完整信息 +- 信息按分组显示(基本信息、项目时间、成本管理等) + +### 4.6 新建项目(市场部、管理员) + +**操作步骤**: +1. 点击工具栏的"新建项目"按钮 +2. 弹出"新建项目"模态框 +3. 填写必填字段: + - 合同编号 * + - 项目名称 * + - 工程类别 * + - 合同金额 * +4. (可选)填写其他字段 +5. 点击"保存"按钮 + +**预期结果**: +- 必填字段为空时,显示错误提示 +- 保存成功后,模态框关闭 +- 项目列表刷新,新项目出现在列表中 +- 显示成功提示:"项目创建成功" + +### 4.7 编辑项目 + +**场景1:编辑自己创建的项目(市场部、管理员)** + +**操作步骤**: +1. 在项目列表中找到要编辑的项目 +2. 点击该行的"编辑"按钮 +3. 弹出"编辑项目"模态框,显示项目当前信息 +4. 修改需要更新的字段 +5. 点击"保存"按钮 + +**预期结果**: +- 所有字段都可以编辑 +- 保存成功后,模态框关闭 +- 项目列表刷新,显示更新后的信息 +- 显示成功提示:"项目更新成功" + +**场景2:更新财务信息(其他部门)** + +**操作步骤**: +1. 点击项目行的"查看"按钮 +2. 进入项目详情页 +3. 点击"编辑"按钮 +4. 模态框中只显示可编辑的字段(成本、财务、进度等) +5. 修改需要更新的字段 +6. 点击"保存"按钮 + +**预期结果**: +- 基础信息字段(合同编号、项目名称等)显示为只读 +- 只能编辑成本、财务、进度相关字段 +- 保存成功后,信息更新 + +### 4.8 删除项目(市场部、管理员) + +**操作步骤**: +1. 在项目列表中找到要删除的项目 +2. 点击该行的"删除"按钮 +3. 弹出确认对话框:"确定要删除该项目吗?" +4. 点击"确定" + +**预期结果**: +- 确认对话框显示 +- 点击"确定"后,项目被删除 +- 列表刷新,该项目不再显示 +- 显示成功提示:"项目删除成功" +- 点击"取消",对话框关闭,项目不被删除 + +### 4.9 分页浏览 + +**操作步骤**: +1. 查看页面底部的分页控件 +2. 点击页码按钮 +3. 或点击"上页"/"下页"按钮 + +**预期结果**: +- 显示当前页码和总页数 +- 点击页码后,表格显示对应页的数据 +- 第一页时,"上页"按钮禁用 +- 最后一页时,"下页"按钮禁用 + +### 4.10 项目详情页操作 + +**场景1:查看详细信息** + +**操作步骤**: +1. 在项目详情页浏览各个信息组 +2. 向下滚动查看所有信息 + +**预期结果**: +- 信息按分组显示,每个组用卡片形式展示 +- 长文本字段支持悬停显示完整内容 +- 日期格式统一为:YYYY-MM-DD +- 金额单位:万元 + +**场景2:编辑项目** + +**操作步骤**: +1. 点击页面右上角的"编辑"按钮 +2. 弹出编辑模态框 +3. 修改需要更新的字段 +4. 点击"保存" + +**预期结果**: +- 根据权限显示可编辑字段 +- 保存成功后,信息更新 + +**场景3:删除项目** + +**操作步骤**: +1. 点击页面右上角的"删除"按钮 +2. 确认删除操作 + +**预期结果**: +- 项目被删除 +- 页面跳转到项目列表页 + +**场景4:返回列表** + +**操作步骤**: +1. 点击面包屑导航中的"项目管理" + +**预期结果**: +- 页面跳转到项目列表页 + +--- + +## 5. 用户管理(管理员) + +### 5.1 进入用户管理 + +**操作步骤**: +1. 点击侧边栏的"用户管理"菜单 + +**预期结果**: +- 内容区显示用户列表页面 +- 显示工具栏(搜索、新建用户按钮) +- 显示用户表格 +- 显示分页控件 + +**注意**:只有管理员角色才能看到"用户管理"菜单。 + +### 5.2 搜索用户 + +**操作步骤**: +1. 在搜索框中输入用户名、真实姓名或邮箱 +2. 点击搜索图标或按Enter键 + +**预期结果**: +- 表格显示匹配的用户 +- 支持模糊匹配 + +### 5.3 新建用户 + +**操作步骤**: +1. 点击"新建用户"按钮 +2. 弹出"新建用户"模态框 +3. 填写必填字段: + - 用户名 *(唯一) + - 密码 *(最少6位) + - 真实姓名 * + - 部门 * + - 角色 * +4. (可选)填写邮箱和电话 +5. 点击"保存"按钮 + +**预期结果**: +- 必填字段为空时,显示错误提示 +- 用户名重复时,显示错误提示:"用户名已存在" +- 保存成功后,用户出现在列表中 + +### 5.4 编辑用户 + +**操作步骤**: +1. 点击用户行的"编辑"按钮 +2. 弹出"编辑用户"模态框 +3. 修改需要更新的字段 +4. 点击"保存"按钮 + +**预期结果**: +- 用户名字段为只读,不可修改 +- 其他字段可以修改 +- 保存成功后,信息更新 + +### 5.5 删除用户 + +**操作步骤**: +1. 点击用户行的"删除"按钮 +2. 确认删除操作 + +**预期结果**: +- 用户被删除 +- 列表刷新 +- 显示成功提示 + +### 5.6 重置密码 + +**操作步骤**: +1. 点击用户行的"重置密码"按钮 +2. 弹出"重置密码"对话框 +3. 输入新密码 +4. 点击"确定" + +**预期结果**: +- 密码重置成功 +- 显示成功提示 + +--- + +## 6. 项目统计 + +### 6.1 进入项目统计 + +**操作步骤**: +1. 点击侧边栏的"项目统计"菜单 + +**预期结果**: +- 显示项目统计页面 +- 显示筛选工具栏 +- 显示统计图表 +- 显示分组统计表格 + +### 6.2 基础统计 + +**查看内容**: +- 项目总数 +- 总投资金额 +- 总合同金额 +- 总收款金额 +- 总付款金额 +- 平均收款完成率 +- 平均付款完成率 +- 平均项目进度 + +### 6.3 分组统计 + +**场景1:按工程类别统计** + +**操作步骤**: +1. 查看页面上的分组统计图表和表格 +2. 默认显示按工程类别分组 + +**预期结果**: +- 图表显示各类别的项目数量和金额 +- 表格显示详细的统计数据 + +**场景2:按项目部分组** + +**操作步骤**: +1. 点击"分组方式"下拉框 +2. 选择"所属项目部" + +**预期结果**: +- 图表和表格切换为按项目部统计 + +### 6.4 时间维度统计 + +**场景1:按月统计** + +**操作步骤**: +1. 点击"时间维度"下拉框 +2. 选择"按月" +3. 选择时间范围(默认为当前年份) + +**预期结果**: +- 显示每月的项目数量和金额 +- 折线图显示趋势 + +**场景2:按年统计** + +**操作步骤**: +1. 点击"时间维度"下拉框 +2. 选择"按年" + +**预期结果**: +- 显示每年的项目数量和金额 + +### 6.5 筛选统计 + +**操作步骤**: +1. 在工具栏中选择筛选条件 +2. 例如:工程类别="基建"、日期范围="2026-01-01 ~ 2026-01-31" + +**预期结果**: +- 所有统计图表和表格只显示符合筛选条件的数据 + +### 6.6 导出报表 + +**操作步骤**: +1. 点击工具栏的"导出报表"按钮 + +**预期结果**: +- 下载Excel文件 +- 文件包含当前筛选条件下的所有统计数据 +- 文件名格式:"项目统计_YYYY-MM-DD.xlsx" + +### 6.7 图表交互 + +**操作步骤**: +1. 鼠标悬停在图表的柱状、饼图或折线上 + +**预期结果**: +- 显示详细的Tooltip信息 +- 显示该数据点的具体数值和占比 + +--- + +## 7. 权限说明 + +### 7.1 角色定义 + +系统包含3种角色,每种角色有不同的权限: + +#### 7.1.1 管理员 (admin) +- **用户管理**:完整权限(创建、编辑、删除、重置密码) +- **项目管理**:完整权限(创建、编辑、删除所有项目) +- **项目统计**:完整权限(查看所有统计数据) +- **其他**:查看所有功能 + +#### 7.1.2 市场部用户 (market) +- **用户管理**:无权限(看不到"用户管理"菜单) +- **项目管理**: + - 创建项目 + - 查看所有项目 + - 编辑自己创建的项目(所有字段) + - 删除自己创建的项目 +- **项目统计**:完整权限(查看所有统计数据) + +#### 7.1.3 其他部门用户 (other) +- **用户管理**:无权限 +- **项目管理**: + - 不能创建项目 + - 查看所有项目 + - 编辑项目的财务、成本、进度等字段(不能修改基础信息) + - 不能删除项目 +- **项目统计**:完整权限 + +### 7.2 字段级权限 + +#### 7.2.1 市场部用户可编辑字段 +- 所有字段(包括基础信息、成本、财务等) + +#### 7.2.2 其他部门用户可编辑字段 +- **成本管理**: + - 总体成本控制 + - 人工成本(控制、计划、实付) + - 材料成本(控制、应付、实际、实付) + - 其他费用(控制、应付、实际) +- **财务信息**: + - 合同金额 + - 质保金(金额、比例、到期日、退还日期) + - 结算金额 + - 税金 + - 利润(计划、实际) +- **收款和付款**: + - 项目进度 + - 应收款和开票 + - 实际收款和收款完成率 + - 应付款和实际付款 + - 未收款和付款完成率 +- **结算信息**: + - 成本结算金额 + - 各类费用结算 + - 到期结算项目统计 +- **项目管理**: + - 所属项目部 + - 项目负责人 + - 工程款拨付方式 + - 存在的问题 + - 建议措施 + - 备注 + +#### 7.2.3 其他部门用户不可编辑字段 +- **基础信息**: + - 合同编号 + - 供电局项目合同编号 + - 项目名称 + - 子项信息 + - 工程类别 + - 业主信息 + - 中标形式 +- **项目时间**: + - 签订日期 + - 开工日期 + - 计划竣工日期 + - 实际竣工日期 + +### 7.3 权限提示 + +当用户尝试执行无权限的操作时: + +**场景1:尝试访问无权限的页面** + +**操作**: +- 其他部门用户尝试访问"用户管理"菜单 + +**预期结果**: +- 侧边栏不显示"用户管理"菜单 +- 如果直接输入URL访问,显示错误提示:"您没有访问该页面的权限" + +**场景2:尝试编辑无权限的字段** + +**操作**: +- 其他部门用户在编辑项目时,基础信息字段显示为只读 + +**预期结果**: +- 无权限字段显示为灰色,不可编辑 +- 鼠标指针变为禁止符号 + +**场景3:尝试执行无权限的操作** + +**操作**: +- 其他部门用户尝试删除项目 + +**预期结果**: +- 项目列表不显示"删除"按钮 +- 详情页不显示"删除"按钮 + +--- + +## 8. 常见操作流程 + +### 8.1 市场部用户创建项目并跟踪流程 + +**完整流程**: +1. 登录系统(市场部账号) +2. 进入"项目管理"页面 +3. 点击"新建项目" +4. 填写项目基本信息(合同编号、名称、类别、金额等) +5. 保存项目 +6. 跟踪项目进度,定期更新收款和付款信息 +7. 项目完成后,更新实际竣工日期和结算信息 + +### 8.2 其他部门用户更新项目信息流程 + +**完整流程**: +1. 登录系统(其他部门账号) +2. 进入"项目管理"页面 +3. 找到要更新的项目 +4. 点击"查看"进入详情页 +5. 点击"编辑" +6. 更新成本、财务、进度等相关信息 +7. 保存更改 + +### 8.3 管理员查看整体统计流程 + +**完整流程**: +1. 登录系统(管理员账号) +2. 进入"项目统计"页面 +3. 查看基础统计卡片 +4. 查看分组统计图表 +5. 切换不同的分组方式(工程类别、项目部) +6. 查看时间趋势 +7. 导出报表存档 + +--- + +## 9. 错误处理 + +### 9.1 网络错误 + +**错误提示**:"网络连接失败,请检查网络后重试" + +**操作建议**: +1. 检查网络连接 +2. 刷新页面 +3. 如果问题持续,联系系统管理员 + +### 9.2 服务器错误 + +**错误提示**:"服务器内部错误,请联系管理员" + +**操作建议**: +1. 刷新页面重试 +2. 联系系统管理员 + +### 9.3 权限错误 + +**错误提示**:"您没有执行此操作的权限" + +**操作建议**: +1. 确认当前用户的角色和权限 +2. 联系管理员申请相应权限 + +### 9.4 数据验证错误 + +**错误提示**:显示具体的验证错误(如:"合同编号不能为空"、"日期格式不正确") + +**操作建议**: +1. 根据提示信息修正输入 +2. 确保必填字段都已填写 +3. 检查日期格式是否正确(YYYY-MM-DD) + +--- + +## 10. 快捷键 + +### 10.1 通用快捷键 + +| 快捷键 | 功能 | +|--------|------| +| Tab | 在表单字段间切换 | +| Shift + Tab | 反向切换字段 | +| Enter | 提交表单或确认操作 | +| Esc | 关闭模态框或取消操作 | + +### 10.2 列表页面快捷键 + +| 快捷键 | 功能 | +|--------|------| +| Ctrl/Cmd + F | 聚焦搜索框 | +| → | 下一页 | +| ← | 上一页 | + +--- + +## 11. 浏览器兼容性 + +### 11.1 推荐浏览器 + +- Chrome 80+ +- Firefox 75+ +- Safari 13+ +- Edge 80+ + +### 11.2 支持的分辨率 + +- 最小分辨率:1280 x 720 +- 推荐分辨率:1920 x 1080 + +--- + +## 12. 性能优化建议 + +### 12.1 大量数据操作 + +**场景**:项目列表包含数千条记录 + +**建议**: +1. 使用筛选条件缩小范围 +2. 不要一次性加载所有数据 +3. 使用分页功能逐页查看 + +### 12.2 数据导出 + +**场景**:导出大量统计数据 + +**建议**: +1. 先使用筛选条件筛选需要的数据 +2. 避免导出所有数据 +3. 导出期间不要关闭浏览器 + +--- + +## 13. 常见问题 (FAQ) + +### Q1: 忘记密码怎么办? + +**A**: 联系系统管理员重置密码。管理员在"用户管理"页面点击"重置密码"按钮。 + +### Q2: 如何批量修改项目? + +**A**: 当前版本不支持批量修改,需要逐个编辑项目。 + +### Q3: 项目创建后可以修改工程类别吗? + +**A**: 可以,但有权限限制。市场部用户可以修改自己创建的项目,管理员可以修改所有项目。 + +### Q4: 如何导出项目数据? + +**A**: 在项目列表页面使用筛选条件筛选需要的数据,然后点击"导出"按钮下载Excel文件。 + +### Q5: 如何查看历史操作记录? + +**A**: 当前版本暂不提供操作日志功能,将在后续版本中添加。 + +### Q6: 项目进度如何计算? + +**A**: 项目进度由系统根据收款完成率、付款完成率等字段自动计算,用户可以直接查看和修改。 + +### Q7: 为什么有些字段不能编辑? + +**A**: 这是权限控制的正常现象。不同角色的用户只能编辑特定的字段,请联系管理员确认权限。 + +### Q8: 如何添加新的工程类别? + +**A**: 当前版本的工程类别是固定的(基建、业扩、客户、营销、检修),如需添加新类别,请联系系统管理员修改系统配置。 + +--- + +## 14. 联系支持 + +如果您在使用过程中遇到任何问题,请联系: + +- **技术支持**:[技术支持邮箱] +- **系统管理员**:[管理员联系方式] +- **产品经理**:[产品经理联系方式] + +--- + +**文档创建日期**:2026-01-25 +**文档版本**:v1.0 +**适用系统版本**:v1.0 diff --git a/docs/docs-restructuring-log.md b/docs/docs-restructuring-log.md new file mode 100644 index 00000000..29244d9d --- /dev/null +++ b/docs/docs-restructuring-log.md @@ -0,0 +1,237 @@ +# docs目录文档重构日志 + +## 更新时间 +2026-01-25 + +## 重构目的 + +根据技术总监的要求,对 `docs/` 目录下的文档进行重构,实现产品文档和技术文档的分离: +- **产品设计文档**:给不懂技术的人看,只关注产品功能 +- **技术架构文档**:给开发人员看,包含技术实现细节 + +## 重构内容 + +### 1. 文档重命名 + +**重命名操作:** +- `docs/pid.md` → `docs/产品设计文档.md` + +### 2. 新增文档 + +**新增文档:** +- `docs/技术架构文档.md` - 包含所有技术实现相关的内容 + +### 3. 文档重构 + +#### 3.1 产品设计文档(产品设计文档.md) + +**文档定位:** +- 产品需求文档(PRD) +- 读者群体:产品经理、业务人员、普通用户 +- 内容类型:产品功能、用户界面、使用说明 + +**包含内容:** +1. 项目概述(背景、目标、特点) +2. 功能需求(用户管理、项目管理、数据查询、权限控制、数据导入) +3. 用户界面(界面设计原则、主要页面、界面风格) +4. 数据安全和隐私 +5. 系统性能 +6. 使用帮助(快速入门、常见问题、联系支持) +7. 数据来源 +8. 未来扩展 +9. 相关文档 + +**移除内容:** +- ❌ 技术选型 +- ❌ 系统架构图 +- ❌ 项目目录结构 +- ❌ 数据库表结构(SQL) +- ❌ 数据库字段详细说明 +- ❌ API接口定义 +- ❌ 前端组件架构 +- ❌ 安全技术实现 +- ❌ 部署方案 +- ❌ 开发规范 +- ❌ 性能优化技术细节 +- ❌ 监控和日志技术 +- ❌ 备份和恢复技术 + +**优化内容:** +- ✅ 简化项目信息字段说明(只分类,不详细描述) +- ✅ 移除数据验证规则(技术细节) +- ✅ 简化非功能性需求(只描述用户感知的性能) +- ✅ 新增使用帮助章节 +- ✅ 新增常见问题(FAQ) +- ✅ 优化相关文档链接 + +**文档字数:** 约8,000字(从原来的15,000字精简) + +#### 3.2 技术架构文档(技术架构文档.md) + +**文档定位:** +- 技术架构文档 +- 读者群体:技术总监、后端程序员、前端程序员、测试工程师 +- 内容类型:技术实现、架构设计、部署方案 + +**包含内容:** +1. 技术选型(前后端、数据库、开发工具) +2. 系统架构(整体架构图、分层架构) +3. 项目目录结构(完整的目录树) +4. 数据库设计(表结构、SQL、索引) +5. API设计(API规范、错误码、端点) +6. 前端设计(技术栈、组件架构、状态管理、API服务) +7. 安全设计(认证、权限、数据安全) +8. 部署方案(开发环境、生产环境、服务器要求) +9. 开发规范(后端、前端、测试) +10. 性能优化(数据库、后端、前端) +11. 监控和日志 +12. 备份和恢复 +13. 相关文档 + +**文档来源:** +- 从原来的 `pid.md` 中提取技术相关内容 +- 参考现有的 `后端架构设计.md` +- 参考现有的 `api.md` + +**文档字数:** 约12,000字 + +### 4. 文档更新 + +#### 4.1 项目README更新 + +**更新文件:** `README.md` + +**更新内容:** +- 更新文档资源分类 +- 重命名文档链接: + - `[项目信息文档](docs/pid.md)` → `[产品设计文档](docs/产品设计文档.md)` +- 新增文档链接: + - `[技术架构文档](docs/技术架构文档.md)` + +**更新前:** +``` +### 项目文档 +- [项目信息文档](docs/pid.md) - 项目设计和技术规范 +- [数据库设计和数据初始化](docs/database-and-data-initialization.md) +- [数据库设计详细文档](docs/database-design.md) +- [UI设计规范](docs/ui-design-spec.md) +- [设计方案](docs/plans/) +- [团队协作规范](docs/TEAM-COLLABORATION.md) +- [权限控制文档](ACCESS-CONTROL.md) +``` + +**更新后:** +``` +### 产品文档 +- [产品设计文档](docs/产品设计文档.md) - 产品需求文档(PRD) +- [UI设计规范](docs/ui-design-spec.md) - 前端UI设计规范 +- [团队协作规范](docs/TEAM-COLLABORATION.md) - 团队协作流程 + +### 技术文档 +- [技术架构文档](docs/技术架构文档.md) - 系统技术架构和实现 +- [后端架构设计](docs/后端架构设计.md) - 后端技术架构详解 +- [API文档](docs/api.md) - 后端API接口文档 +- [数据库设计](docs/database-design.md) - 数据库表结构设计 +- [数据库设计和数据初始化](docs/database-and-data-initialization.md) - 数据初始化方案 +``` + +## 文档对比 + +### 重构前 +- **文档数量**: 1个(pid.md) +- **内容类型**: 产品需求 + 技术方案混合 +- **读者群体**: 产品经理 + 技术团队(混杂) +- **文档字数**: 约15,000字 +- **可读性**: 对非技术人员不友好 + +### 重构后 +- **文档数量**: 2个(产品设计文档.md + 技术架构文档.md) +- **内容类型**: 产品文档和技术文档分离 +- **读者群体**: + - 产品设计文档:产品经理、业务人员、普通用户 + - 技术架构文档:技术总监、开发团队、测试团队 +- **文档字数**: 约20,000字(合计) +- **可读性**: 对不同群体友好 + +## 文档关系图 + +``` +docs/ +├── 产品设计文档.md (给非技术人员看) +│ ├── 功能需求 +│ ├── 用户界面 +│ ├── 使用帮助 +│ └── 相关文档(指向技术文档) +│ +├── 技术架构文档.md (给技术人员看) +│ ├── 技术选型 +│ ├── 系统架构 +│ ├── 数据库设计 +│ ├── API设计 +│ ├── 前端设计 +│ ├── 安全设计 +│ ├── 部署方案 +│ └── 开发规范 +│ +├── 后端架构设计.md +├── api.md +├── ui-design-spec.md +├── TEAM-COLLABORATION.md +└── database-design.md +``` + +## 受影响的其他文档 + +### 需要更新链接的文档 +1. ✅ `README.md` - 已更新 +2. ⏳ `docs/产品设计文档.md` - 已更新内部链接 +3. ⏳ `docs/技术架构文档.md` - 已更新内部链接 + +### 不需要更新的文档 +- `docs/后端架构设计.md` - 独立的技术文档 +- `docs/api.md` - 独立的API文档 +- `docs/ui-design-spec.md` - 独立的UI设计文档 +- `docs/TEAM-COLLABORATION.md` - 独立的团队协作文档 +- `docs/database-design.md` - 独立的数据库设计文档 + +## 文档维护 + +### 产品设计文档 +- **维护人**: 产品经理、技术总监 +- **更新时机**: 产品需求变更时 +- **审核人**: 产品经理、技术总监 + +### 技术架构文档 +- **维护人**: 技术总监、后端程序员 +- **更新时机**: 技术方案变更时 +- **审核人**: 技术总监 + +## 后续工作 + +### 待完成 +- [ ] 更新所有引用 `pid.md` 的文档链接 +- [ ] 更新团队协作文档中的文档引用 +- [ ] 更新各个角色工作规范中的文档引用 +- [ ] 通知团队成员文档变更 +- [ ] 删除或归档 `pid-update-log.md` 和 `pid-update-log-v2.md`(可选) + +### 注意事项 +- 两个文档中的相关文档链接需要保持一致 +- 产品需求变更时需要同步更新两个文档(如需要) +- 技术实现变更时只需要更新技术架构文档 +- 保持产品文档和技术文档的分离原则 + +## 总结 + +本次文档重构成功实现了产品文档和技术文档的分离,使得不同角色能够更方便地找到所需的信息: +- **产品人员**:只需查看产品设计文档,无需关心技术实现 +- **开发人员**:只需查看技术架构文档,了解技术实现细节 +- **文档维护**:不同类型文档由不同人员维护,职责更清晰 + +文档的可读性和维护性都得到了显著提升。 + +--- + +**重构人**: 技术总监 +**审核状态**: 待审核 +**完成时间**: 2026-01-25 diff --git a/docs/pid-update-log-v2.md b/docs/pid-update-log-v2.md new file mode 100644 index 00000000..cf2c50dc --- /dev/null +++ b/docs/pid-update-log-v2.md @@ -0,0 +1,225 @@ +# pid.md 文档更新说明 + +## 更新时间 +2026-01-25 + +## 更新原因 + +根据技术总监的要求,将 `pid.md` 文档从技术设计文档改为纯产品需求文档(PRD),移除所有技术实现相关的内容。 + +## 主要变更 + +### 1. 文档定位变更 +**更新前**: 项目信息管理系统 - 设计文档(包含技术方案) +**更新后**: 项目信息管理系统 - 产品需求文档(PRD) + +### 2. 删除的内容(技术相关) + +#### 2.1 技术栈章节(第1.2节) +- 删除了前端、后端、数据库等技术选型 +- 删除了开发工具列表 + +#### 2.2 系统架构章节(第3章) +- 删除了架构图 +- 删除了项目目录结构 +- 删除了工作目录说明 +- 删除了权限限制说明 + +#### 2.3 团队协作章节(第4章) +- 删除了角色职责 +- 删除了工作流程 +- 删除了代码提交规范 +- 删除了文档规范 +- 删除了沟通机制 + +#### 2.4 数据库设计章节(第5章) +- 删除了数据库表结构(SQL) +- 删除了角色权限矩阵 + +#### 2.5 API设计章节(第6章) +- 删除了所有API接口定义 +- 删除了请求/响应示例 +- 删除了错误码说明 + +#### 2.6 前端设计章节(第7章) +- 删除了页面结构 +- 删除了核心组件 +- 删除了页面设计细节 +- 删除了UI风格 + +#### 2.7 安全设计章节(第8章) +- 删除了认证安全技术 +- 删除了权限控制技术 +- 删除了数据安全技术 + +#### 2.8 部署方案章节(第9章) +- 删除了开发环境部署 +- 删除了生产环境部署 + +#### 2.9 后续优化建议章节(第10章) +- 删除了功能扩展 +- 删除了性能优化 +- 删除了用户体验优化 + +#### 2.10 开发计划章节(第11章) +- 删除了第一阶段MVP +- 删除了第二阶段功能 +- 删除了第三阶段功能 + +### 3. 保留和优化的内容(产品需求相关) + +#### 3.1 项目概述(第1章) +- **保留**: 项目背景 +- **保留**: 产品目标 +- **保留**: 产品特点 + +#### 3.2 功能需求(第2章) +- **保留**: 用户管理需求 +- **保留**: 项目管理需求 +- **优化**: 项目信息字段更清晰,分为多个子类(基本信息、时间信息、成本控制信息等) +- **保留**: 权限控制需求 +- **新增**: 数据查询和统计需求 + +#### 3.3 数据管理(第3章,新增) +- **新增**: 数据导入需求 +- **新增**: 数据字段说明 +- **新增**: 数据验证规则 + +#### 3.4 用户界面需求(第4章,新增) +- **新增**: 页面结构需求 +- **新增**: 功能页面需求 +- **新增**: UI风格需求 + +#### 3.5 数据安全和隐私(第5章) +- **保留**: 用户隐私需求 +- **保留**: 数据安全需求(从安全设计优化而来) + +#### 3.6 非功能性需求(第6章,新增) +- **新增**: 性能要求 +- **新增**: 可用性要求 +- **新增**: 可维护性要求 + +#### 3.7 数据来源(第7章,新增) +- **新增**: 原始数据说明(example.xls) +- **新增**: 数据分类 + +#### 3.8 相关文档(第8章) +- **优化**: 分类整理为产品文档、技术文档、数据初始化 + +### 4. 文档结构调整 + +#### 4.1 章节数量 +- **更新前**: 12章 +- **更新后**: 9章 + +#### 4.2 新增章节 +- 第3章: 数据管理(新增) +- 第4章: 用户界面需求(新增) +- 第6章: 非功能性需求(新增) +- 第7章: 数据来源(新增) + +#### 4.3 章节重命名 +- 第2章"功能需求"保持不变 +- 第5章"数据安全和隐私"(原"安全设计") +- 第8章"相关文档"(原"相关文档") + +### 5. 内容优化 + +#### 5.1 项目信息字段 +- 将61个字段按功能分类展示 +- 每个字段都有清晰的说明 +- 分为:基本信息、时间信息、成本控制信息、合同和财务信息、应收应付信息、结算信息、项目管理信息 + +#### 5.2 权限控制 +- 保留了权限矩阵 +- 新增了字段级权限说明 +- 更清晰地说明各角色的权限范围 + +#### 5.3 数据来源 +- 新增了对example.xls的说明 +- 说明了工程类别的分类 +- 提供了数据导入的相关文档链接 + +### 6. 文档类型变更 + +#### 6.1 文档定位 +- **更新前**: 设计文档(包含需求+技术方案) +- **更新后**: 产品需求文档(PRD) + +#### 6.2 文档类型 +- **更新前**: 技术文档 +- **更新后**: 产品文档 + +### 7. 读者群体变更 + +#### 7.1 读者群体 +- **更新前**: 技术团队(技术总监、后端程序员、前端程序员、测试工程师) +- **更新后**: 产品团队(产品经理、技术总监)、开发团队(参考需求) + +#### 7.2 文档用途 +- **更新前**: 技术设计和实现参考 +- **更新后**: 产品需求定义和开发指导 + +## 文档内容对比 + +| 章节 | 更新前 | 更新后 | 说明 | +|------|--------|--------|------| +| 1. 项目概述 | 包含技术栈 | 产品目标、特点 | 移除技术内容 | +| 2. 功能需求 | 基础功能需求 | 详细功能需求、字段说明 | 新增数据查询统计、权限说明 | +| 3. 系统架构 | 架构图、目录结构 | 数据管理 | 完全重写 | +| 4. 团队协作 | 角色职责、工作流程 | 用户界面需求 | 完全重写 | +| 5. 数据库设计 | 表结构、SQL | 数据安全和隐私 | 完全重写 | +| 6. API设计 | API接口、请求响应 | 非功能性需求 | 完全重写 | +| 7. 前端设计 | 页面、组件 | 数据来源 | 完全重写 | +| 8. 安全设计 | 技术安全方案 | 相关文档 | 保留基础内容 | +| 9. 部署方案 | 环境部署 | - | 删除 | +| 10. 后续优化建议 | 功能、性能、用户体验 | - | 删除 | +| 11. 开发计划 | 三阶段开发计划 | - | 删除 | +| 12. 相关文档 | 文档链接 | 文档链接 | 优化分类 | + +## 文档字数变化 + +- **更新前**: 约900行,约30,000字 +- **更新后**: 约400行,约15,000字 +- **精简**: 约50% + +## 后续工作 + +### 8.1 技术文档 +技术相关的内容已迁移到以下文档: +- [后端架构设计](2026-01-25-backend-architecture-design.md) +- [API文档](api.md) +- [数据库设计](../docs/database-design.md) + +### 8.2 文档维护 +- 产品需求变更时,需要更新本文档 +- 技术实现变更时,需要更新技术文档 +- 保持产品文档和技术文档的一致性 + +## 文档审核 + +### 9.1 审核人 +- 技术总监:审核产品需求的完整性和合理性 +- 产品经理(如有):审核产品需求是否符合业务需求 +- 开发团队:审核产品需求的可实施性 + +### 9.2 审核检查项 +- [ ] 功能需求是否完整 +- [ ] 字段定义是否清晰 +- [ ] 权限控制是否合理 +- [ ] 数据管理需求是否明确 +- [ ] 非功能性需求是否合理 +- [ ] 相关文档链接是否正确 + +## 总结 + +本次更新将 `pid.md` 从技术设计文档转换为纯产品需求文档,移除了所有技术实现相关的内容,专注于产品需求的定义和描述。文档更加精简、聚焦,便于产品团队和开发团队理解和使用。 + +技术相关的内容已迁移到专门的技术文档中,实现了产品文档和技术文档的分离,便于后续维护和更新。 + +--- + +**更新人**: 技术总监 +**审核状态**: 待审核 +**文档类型**: 产品需求文档(PRD) +**更新时间**: 2026-01-25 diff --git a/docs/pid.md b/docs/pid.md deleted file mode 100644 index de6fe4ec..00000000 --- a/docs/pid.md +++ /dev/null @@ -1,963 +0,0 @@ -# 项目信息管理系统 - 设计文档 - -## 1. 项目概述 - -### 1.1 项目背景 -开发一个基于BS架构的项目信息管理系统,用于管理项目的合同、编号、名称、预算、付款等信息。系统支持多部门协作,市场部用户可以新建项目,其他部门用户可以填写和更新项目信息。 - -### 1.2 技术选型 - -| 层级 | 技术 | 说明 | -|------|------|------| -| 前端 | React + React Router | 前后端分离架构 | -| UI组件库 | Ant Design | 企业级UI组件 | -| HTTP客户端 | Axios | API请求 | -| 状态管理 | Context API | 简单状态管理 | -| 后端 | Python Flask | 轻量级Web框架 | -| ORM | SQLAlchemy | 数据库ORM | -| 数据库 | MySQL | 关系型数据库 | -| 认证 | JWT Token | 无状态认证 | -| 开发工具 | Create React App, pipenv | 前后端开发环境 | - -### 1.3 系统特点 -- 轻量级架构,适合小并发场景 -- 无需Redis缓存,降低部署复杂度 -- 基于角色的权限控制(RBAC) -- 前后端分离,易于维护和扩展 - -## 2. 功能需求 - -### 2.1 用户管理 - -#### 2.1.1 登录功能 -- 用户名/密码登录 -- JWT Token认证 -- 自动登录(Token存储在LocalStorage) - -#### 2.1.2 用户管理(管理员) -- 创建用户:设置用户名、密码、部门、角色 -- 编辑用户信息 -- 删除用户 -- 重置用户密码 -- 查看用户列表 - -#### 2.1.3 权限控制 -- **管理员**:所有权限 -- **市场部用户**:创建项目、查看项目、编辑自己的项目 -- **其他部门用户**:查看项目、编辑项目信息(如付款、状态等) - -### 2.2 项目管理 - -#### 2.2.1 市场部权限 -- **创建项目**:填写项目基本信息 -- **查看项目**:浏览所有项目列表和详情 -- **编辑项目**:修改自己创建的项目 - -#### 2.2.2 其他部门权限 -- **查看项目**:浏览所有项目列表和详情 -- **更新项目**:填写和更新项目信息(预算、付款、状态等) - -#### 2.2.3 项目信息字段 - -| 字段名 | 类型 | 必填 | 说明 | -|--------|------|------|------| -| project_no | String | 是 | 项目编号(唯一) | -| contract_no | String | 是 | 合同编号 | -| name | String | 是 | 项目名称 | -| budget | Decimal | 是 | 项目预算 | -| payment_amount | Decimal | 否 | 已付款金额 | -| status | String | 是 | 项目状态(灵活状态) | -| start_date | Date | 否 | 开始日期 | -| end_date | Date | 否 | 结束日期 | -| created_by | Integer | 是 | 创建人ID(外键) | -| department | String | 是 | 所属部门 | -| description | Text | 否 | 项目描述 | -| created_at | DateTime | 是 | 创建时间 | -| updated_at | DateTime | 是 | 更新时间 | - -#### 2.2.4 项目状态示例 -- 新建 -- 进行中 -- 已完成 -- 已暂停 -- 已取消 - -(支持灵活状态,可由用户自定义) - -## 3. 系统架构 - -### 3.1 架构图 - -``` -┌─────────────────────────────────────────────────┐ -│ 浏览器 │ -│ (React + Ant Design + React Router + Axios) │ -└─────────────────────────────────────────────────┘ - │ - │ HTTP/HTTPS - │ JWT Token - ▼ -┌─────────────────────────────────────────────────┐ -│ Flask 后端服务 │ -│ ┌─────────────────────────────────────────┐ │ -│ │ API Layer (Flask Routes) │ │ -│ ├─────────────────────────────────────────┤ │ -│ │ Business Logic (Services) │ │ -│ ├─────────────────────────────────────────┤ │ -│ │ Data Access (SQLAlchemy ORM) │ │ -│ └─────────────────────────────────────────┘ │ -└─────────────────────────────────────────────────┘ - │ - │ SQL - ▼ -┌─────────────────────────────────────────────────┐ -│ MySQL 数据库 │ -│ ┌─────────────────────────────────────────┐ │ -│ │ users │ │ -│ │ projects │ │ -│ └─────────────────────────────────────────┘ │ -└─────────────────────────────────────────────────┘ -``` - -### 3.2 项目目录结构 - -``` -ocean_project_manager/ -├── backend/ # 后端程序员工作区 -│ ├── src/ # 源代码 -│ │ ├── controllers/ # 控制器层 -│ │ │ ├── auth.py # 认证相关 -│ │ │ ├── users.py # 用户管理 -│ │ │ └── projects.py # 项目管理 -│ │ ├── services/ # 业务逻辑层 -│ │ │ ├── auth_service.py -│ │ │ ├── user_service.py -│ │ │ └── project_service.py -│ │ ├── models/ # 数据模型层 -│ │ │ ├── user.py -│ │ │ └── project.py -│ │ ├── routes/ # 路由定义 -│ │ │ ├── auth.py -│ │ │ ├── users.py -│ │ │ └── projects.py -│ │ ├── middleware/ # 中间件 -│ │ │ ├── jwt_middleware.py -│ │ │ └── auth_middleware.py -│ │ └── utils/ # 工具函数 -│ │ ├── jwt_utils.py -│ │ └── password_utils.py -│ ├── tests/ # 单元测试和集成测试 -│ ├── docs/ # API文档和技术文档 -│ │ └── api.md # API文档 -│ ├── config/ # 配置文件 -│ ├── requirements.txt # Python依赖 -│ ├── run.py # 启动文件 -│ ├── WORKSTANDARDS.md # 后端程序员工作规范 -│ └── README.md # 后端开发说明 -│ -├── frontend/ # 前端程序员工作区 -│ ├── src/ # 源代码 -│ │ ├── components/ # 可复用组件 -│ │ │ ├── Layout.jsx -│ │ │ ├── Header.jsx -│ │ │ └── Sidebar.jsx -│ │ ├── pages/ # 页面组件 -│ │ │ ├── Login.jsx -│ │ │ ├── Dashboard.jsx -│ │ │ ├── UserList.jsx -│ │ │ ├── UserForm.jsx -│ │ │ ├── ProjectList.jsx -│ │ │ ├── ProjectForm.jsx -│ │ │ └── ProjectDetail.jsx -│ │ ├── hooks/ # 自定义Hooks -│ │ │ └── useAuth.js -│ │ ├── services/ # API服务 -│ │ │ ├── api.js # Axios配置 -│ │ │ ├── auth.js -│ │ │ ├── user.js -│ │ │ └── project.js -│ │ ├── utils/ # 工具函数 -│ │ │ └── auth.js -│ │ ├── styles/ # 样式文件 -│ │ │ └── global.css -│ │ ├── types/ # TypeScript类型定义 -│ │ │ ├── user.ts -│ │ │ └── project.ts -│ │ ├── App.jsx # 根组件 -│ │ └── index.js # 入口文件 -│ ├── tests/ # 组件测试 -│ ├── docs/ # 组件文档和开发文档 -│ │ └── components.md # 组件文档 -│ ├── package.json -│ ├── WORKSTANDARDS.md # 前端程序员工作规范 -│ └── README.md # 前端开发说明 -│ -├── testing/ # 测试工程师工作区 -│ ├── testcases/ # 测试用例 -│ │ ├── api/ # API测试用例 -│ │ │ ├── auth_test.json -│ │ │ ├── user_test.json -│ │ │ └── project_test.json -│ │ ├── ui/ # UI测试用例 -│ │ │ ├── login_test.json -│ │ │ ├── user_list_test.json -│ │ │ └── project_list_test.json -│ │ └── integration/ # 集成测试用例 -│ │ └── workflow_test.json -│ ├── reports/ # 测试报告 -│ ├── data/ # 测试数据 -│ ├── scripts/ # 自动化测试脚本 -│ │ ├── api_test.py # API自动化测试 -│ │ └── ui_test.js # UI自动化测试 -│ ├── docs/ # 测试文档 -│ │ ├── test_plan.md # 测试计划 -│ │ └── test_strategy.md # 测试策略 -│ ├── WORKSTANDARDS.md # 测试工程师工作规范 -│ └── README.md # 测试工作说明 -│ -├── docs/ # 项目文档 -│ ├── plans/ # 设计文档 -│ │ └── 2026-01-24-project-management-system-design.md -│ ├── ui-design-spec.md # UI设计规范 -│ ├── pid.md # 项目信息文档(本文档) -│ └── TEAM-COLLABORATION.md # 团队协作规范 -│ -├── ACCESS-CONTROL.md # 权限控制文档 -├── README.md # 项目说明 -└── .git/ # Git仓库 -``` - -### 3.3 工作目录说明 - -#### 后端程序员工作区 (`backend/`) -- **负责**: 后端API开发、数据库设计、单元测试编写 -- **主要工作**: 在 `backend/src/` 中编写业务代码 -- **输出文档**: `backend/docs/api.md` - API文档 -- **测试**: `backend/tests/` - 单元测试和集成测试 - -#### 前端程序员工作区 (`frontend/`) -- **负责**: 前端UI开发、组件封装、API集成 -- **主要工作**: 在 `frontend/src/` 中编写UI组件 -- **输出文档**: `frontend/docs/components.md` - 组件文档 -- **测试**: `frontend/tests/` - 组件测试 - -#### 测试工程师工作区 (`testing/`) -- **负责**: 测试计划编写、测试用例设计、自动化测试 -- **主要工作**: 在 `testing/testcases/` 中编写测试用例 -- **输出文档**: `testing/reports/` - 测试报告 -- **自动化**: `testing/scripts/` - 自动化测试脚本 - -### 3.4 权限限制 - -⚠️ **严格遵守工作目录限制** - -- 后端程序员只能在 `backend/` 目录中工作 -- 前端程序员只能在 `frontend/` 目录中工作 -- 测试工程师只能在 `testing/` 目录中工作 - -跨目录工作需要技术总监批准,详见 [ACCESS-CONTROL.md](../ACCESS-CONTROL.md) - -## 4. 团队协作 - -### 4.1 角色职责 - -#### 技术总监 -- 审查设计方案和技术文档 -- Review代码质量和架构设计 -- 协调团队协作,解决技术分歧 -- 批准重大变更和跨目录访问请求 - -#### 后端程序员 -- **工作目录**: `backend/` -- **主要职责**: - - 设计和实现后端API接口 - - 编写API文档 (`backend/docs/api.md`) - - 编写单元测试,确保测试覆盖率 > 80% - - 参与技术方案讨论和代码审查 - -#### 前端程序员 -- **工作目录**: `frontend/` -- **主要职责**: - - 实现前端UI组件和页面 - - 编写组件文档 (`frontend/docs/components.md`) - - 集成后端API,确保前后端对接 - - 遵循UI设计规范 (`docs/ui-design-spec.md`) - -#### 测试工程师 -- **工作目录**: `testing/` -- **主要职责**: - - 编写测试计划和测试用例 - - 执行功能测试、UI测试、自动化测试 - - 生成测试报告和Bug报告 - - 验证Bug修复效果 - -### 4.2 工作流程 - -#### 4.2.1 需求分析阶段 -1. 技术总监编写设计方案 (`docs/plans/`) -2. 后端程序员和前端程序员查看设计方案 -3. 测试工程师编写测试计划 (`testing/docs/`) - -#### 4.2.2 后端开发流程 -1. 后端程序员在 `backend/src/` 中开发API -2. 编写单元测试 (`backend/tests/`) -3. 更新API文档 (`backend/docs/api.md`) -4. 提交代码进行审查 - -#### 4.2.3 前端开发流程 -1. 前端程序员查看API文档 (`backend/docs/api.md`) -2. 在 `frontend/src/` 中开发UI组件 -3. 集成后端API -4. 编写组件测试 (`frontend/tests/`) -5. 提交代码进行审查 - -#### 4.2.4 测试流程 -1. 测试工程师执行测试用例 (`testing/testcases/`) -2. 记录Bug和问题 -3. 生成测试报告 (`testing/reports/`) -4. 后端程序员和前端程序员根据Bug报告修复问题 -5. 测试工程师验证修复效果 - -#### 4.2.5 代码审查流程 -1. 开发人员提交Pull Request -2. 技术总监审查代码质量、文档完整性、测试覆盖率 -3. 如有问题,退回修改;如通过,合并到主分支 - -### 4.3 代码提交规范 - -#### 提交信息格式 -- 后端程序员: `[backend] <类型>: <描述>` -- 前端程序员: `[frontend] <类型>: <描述>` -- 测试工程师: `[testing] <类型>: <描述>` - -#### 类型说明 -- `feat`: 新功能 -- `fix`: Bug修复 -- `docs`: 文档更新 -- `style`: 代码格式调整 -- `refactor`: 代码重构 -- `test`: 测试相关 -- `chore`: 构建或工具相关 - -#### 示例 -``` -[backend] feat: 添加用户认证API -[frontend] fix: 修复项目列表分页问题 -[testing] test: 添加用户登录API测试用例 -``` - -### 4.4 文档规范 - -#### 后端文档 -- **API文档**: `backend/docs/api.md` - - 使用OpenAPI/Swagger格式 - - 包含: 接口路径、请求方法、参数、返回值、错误码 - -#### 前端文档 -- **组件文档**: `frontend/docs/components.md` - - 包含: 组件名称、Props、使用示例、截图 - -#### 测试文档 -- **测试计划**: `testing/docs/test_plan.md` -- **测试报告**: `testing/reports/` 目录 -- **Bug报告**: 包含标题、重现步骤、预期结果、实际结果、截图/日志 - -### 4.5 沟通机制 - -#### 日常沟通 -- 各角色在各自目录中独立工作 -- 需要协作时通过技术总监协调 - -#### 文档沟通 -- 后端API文档作为前后端对接的桥梁 -- 测试报告作为Bug修复的依据 -- 所有重要决策记录在项目文档中 - -#### 技术总监审查 -- 所有设计方案需要技术总监批准 -- 所有代码合并需要技术总监审查 -- 重大变更需要技术总监批准 - -## 5. 数据库设计 - -### 5.1 数据库表结构 - -#### 4.1.1 用户表 (users) - -| 字段名 | 类型 | 约束 | 说明 | -|--------|------|------|------| -| id | INT | PRIMARY KEY, AUTO_INCREMENT | 用户ID | -| username | VARCHAR(50) | UNIQUE, NOT NULL | 用户名 | -| password_hash | VARCHAR(255) | NOT NULL | 密码哈希 | -| real_name | VARCHAR(100) | NOT NULL | 真实姓名 | -| department | VARCHAR(50) | NOT NULL | 部门(市场部/技术部/财务部等) | -| role | ENUM | NOT NULL | 角色(admin/market/other) | -| email | VARCHAR(100) | UNIQUE | 邮箱 | -| phone | VARCHAR(20) | | 电话 | -| is_active | BOOLEAN | DEFAULT TRUE | 是否激活 | -| created_at | DATETIME | DEFAULT CURRENT_TIMESTAMP | 创建时间 | -| updated_at | DATETIME | DEFAULT CURRENT_TIMESTAMP ON UPDATE | 更新时间 | - -```sql -CREATE TABLE users ( - id INT PRIMARY KEY AUTO_INCREMENT, - username VARCHAR(50) UNIQUE NOT NULL, - password_hash VARCHAR(255) NOT NULL, - real_name VARCHAR(100) NOT NULL, - department VARCHAR(50) NOT NULL, - role ENUM('admin', 'market', 'other') NOT NULL, - email VARCHAR(100) UNIQUE, - phone VARCHAR(20), - is_active BOOLEAN DEFAULT TRUE, - created_at DATETIME DEFAULT CURRENT_TIMESTAMP, - updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP -); - --- 索引 -CREATE INDEX idx_users_username ON users(username); -CREATE INDEX idx_users_department ON users(department); -CREATE INDEX idx_users_role ON users(role); -``` - -#### 4.1.2 项目表 (projects) - -| 字段名 | 类型 | 约束 | 说明 | -|--------|------|------|------| -| id | INT | PRIMARY KEY, AUTO_INCREMENT | 项目ID | -| project_no | VARCHAR(50) | UNIQUE, NOT NULL | 项目编号 | -| contract_no | VARCHAR(50) | NOT NULL | 合同编号 | -| name | VARCHAR(200) | NOT NULL | 项目名称 | -| budget | DECIMAL(15,2) | NOT NULL | 项目预算 | -| payment_amount | DECIMAL(15,2) | DEFAULT 0 | 已付款金额 | -| status | VARCHAR(50) | NOT NULL | 项目状态 | -| start_date | DATE | | 开始日期 | -| end_date | DATE | | 结束日期 | -| created_by | INT | NOT NULL, FOREIGN KEY | 创建人ID | -| department | VARCHAR(50) | NOT NULL | 所属部门 | -| description | TEXT | | 项目描述 | -| created_at | DATETIME | DEFAULT CURRENT_TIMESTAMP | 创建时间 | -| updated_at | DATETIME | DEFAULT CURRENT_TIMESTAMP ON UPDATE | 更新时间 | - -```sql -CREATE TABLE projects ( - id INT PRIMARY KEY AUTO_INCREMENT, - project_no VARCHAR(50) UNIQUE NOT NULL, - contract_no VARCHAR(50) NOT NULL, - name VARCHAR(200) NOT NULL, - budget DECIMAL(15,2) NOT NULL, - payment_amount DECIMAL(15,2) DEFAULT 0, - status VARCHAR(50) NOT NULL, - start_date DATE, - end_date DATE, - created_by INT NOT NULL, - department VARCHAR(50) NOT NULL, - description TEXT, - created_at DATETIME DEFAULT CURRENT_TIMESTAMP, - updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - FOREIGN KEY (created_by) REFERENCES users(id) ON DELETE RESTRICT -); - --- 索引 -CREATE INDEX idx_projects_project_no ON projects(project_no); -CREATE INDEX idx_projects_contract_no ON projects(contract_no); -CREATE INDEX idx_projects_status ON projects(status); -CREATE INDEX idx_projects_department ON projects(department); -CREATE INDEX idx_projects_created_by ON projects(created_by); -``` - -### 5.2 角色权限矩阵 - -| 功能 | admin | market | other | -|------|-------|--------|-------| -| 登录 | ✓ | ✓ | ✓ | -| 创建用户 | ✓ | ✗ | ✗ | -| 编辑用户 | ✓ | ✗ | ✗ | -| 删除用户 | ✓ | ✗ | ✗ | -| 查看用户列表 | ✓ | ✗ | ✗ | -| 创建项目 | ✓ | ✓ | ✗ | -| 编辑项目信息 | ✓ | ✓ | ✓ | -| 删除项目 | ✓ | ✓ | ✗ | -| 查看所有项目 | ✓ | ✓ | ✓ | -| 查看项目详情 | ✓ | ✓ | ✓ | - -## 6. API设计 - -### 6.1 API规范 - -- **Base URL**: `/api/v1` -- **Content-Type**: `application/json` -- **认证方式**: JWT Token(在Header中传递:`Authorization: Bearer `) -- **响应格式**: -```json -{ - "success": true, - "message": "操作成功", - "data": {} -} -``` - -### 6.2 认证相关API - -#### 5.2.1 用户登录 -- **URL**: `POST /api/v1/auth/login` -- **请求体**: -```json -{ - "username": "admin", - "password": "password123" -} -``` -- **响应**: -```json -{ - "success": true, - "message": "登录成功", - "data": { - "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", - "user": { - "id": 1, - "username": "admin", - "real_name": "管理员", - "department": "管理部", - "role": "admin" - } - } -} -``` - -#### 5.2.2 获取当前用户信息 -- **URL**: `GET /api/v1/auth/me` -- **Header**: `Authorization: Bearer ` -- **响应**: -```json -{ - "success": true, - "data": { - "id": 1, - "username": "admin", - "real_name": "管理员", - "department": "管理部", - "role": "admin" - } -} -``` - -#### 5.2.3 登出 -- **URL**: `POST /api/v1/auth/logout` -- **Header**: `Authorization: Bearer ` -- **响应**: -```json -{ - "success": true, - "message": "登出成功" -} -``` - -### 6.3 用户管理API - -#### 5.3.1 获取用户列表 -- **URL**: `GET /api/v1/users` -- **Header**: `Authorization: Bearer ` -- **权限**: admin -- **查询参数**: - - `page`: 页码(默认1) - - `page_size`: 每页数量(默认10) - - `department`: 部门筛选 - - `role`: 角色筛选 -- **响应**: -```json -{ - "success": true, - "data": { - "items": [ - { - "id": 1, - "username": "admin", - "real_name": "管理员", - "department": "管理部", - "role": "admin", - "email": "admin@example.com", - "is_active": true, - "created_at": "2026-01-24T10:00:00" - } - ], - "total": 1, - "page": 1, - "page_size": 10 - } -} -``` - -#### 5.3.2 创建用户 -- **URL**: `POST /api/v1/users` -- **Header**: `Authorization: Bearer ` -- **权限**: admin -- **请求体**: -```json -{ - "username": "zhangsan", - "password": "password123", - "real_name": "张三", - "department": "市场部", - "role": "market", - "email": "zhangsan@example.com", - "phone": "13800138000" -} -``` -- **响应**: -```json -{ - "success": true, - "message": "用户创建成功", - "data": { - "id": 2, - "username": "zhangsan", - "real_name": "张三", - "department": "市场部", - "role": "market" - } -} -``` - -#### 5.3.3 更新用户 -- **URL**: `PUT /api/v1/users/{id}` -- **Header**: `Authorization: Bearer ` -- **权限**: admin -- **请求体**: -```json -{ - "real_name": "张三", - "email": "zhangsan2@example.com", - "phone": "13900139000" -} -``` - -#### 5.3.4 删除用户 -- **URL**: `DELETE /api/v1/users/{id}` -- **Header**: `Authorization: Bearer ` -- **权限**: admin - -#### 5.3.5 重置用户密码 -- **URL**: `POST /api/v1/users/{id}/reset-password` -- **Header**: `Authorization: Bearer ` -- **权限**: admin -- **请求体**: -```json -{ - "new_password": "newpassword123" -} -``` - -### 6.4 项目管理API - -#### 5.4.1 获取项目列表 -- **URL**: `GET /api/v1/projects` -- **Header**: `Authorization: Bearer ` -- **权限**: 所有用户 -- **查询参数**: - - `page`: 页码(默认1) - - `page_size`: 每页数量(默认10) - - `status`: 状态筛选 - - `department`: 部门筛选 -- **响应**: -```json -{ - "success": true, - "data": { - "items": [ - { - "id": 1, - "project_no": "PRJ2026001", - "contract_no": "CT2026001", - "name": "某公司官网开发", - "budget": 50000.00, - "payment_amount": 25000.00, - "status": "进行中", - "start_date": "2026-01-01", - "end_date": "2026-03-31", - "department": "市场部", - "created_by": { - "id": 2, - "real_name": "张三" - }, - "created_at": "2026-01-24T10:00:00" - } - ], - "total": 1, - "page": 1, - "page_size": 10 - } -} -``` - -#### 5.4.2 获取项目详情 -- **URL**: `GET /api/v1/projects/{id}` -- **Header**: `Authorization: Bearer ` -- **权限**: 所有用户 - -#### 5.4.3 创建项目 -- **URL**: `POST /api/v1/projects` -- **Header**: `Authorization: Bearer ` -- **权限**: admin, market -- **请求体**: -```json -{ - "project_no": "PRJ2026002", - "contract_no": "CT2026002", - "name": "电商平台开发", - "budget": 100000.00, - "status": "新建", - "start_date": "2026-02-01", - "end_date": "2026-06-30", - "department": "市场部", - "description": "电商平台开发项目" -} -``` - -#### 5.4.4 更新项目 -- **URL**: `PUT /api/v1/projects/{id}` -- **Header**: `Authorization: Bearer ` -- **权限**: 所有用户 -- **请求体**: -```json -{ - "payment_amount": 50000.00, - "status": "进行中", - "description": "项目更新描述" -} -``` - -#### 5.4.5 删除项目 -- **URL**: `DELETE /api/v1/projects/{id}` -- **Header**: `Authorization: Bearer ` -- **权限**: admin, market(只能删除自己创建的) - -## 7. 前端设计 - -### 7.1 页面结构 - -``` -登录页 (Login) - └─ 登录表单 - -主布局 (Layout) - ├─ 顶部导航栏 (Header) - │ ├─ Logo - │ ├─ 用户信息 - │ └─ 登出按钮 - │ - └─ 侧边栏 (Sidebar) - └─ 菜单 - ├─ 仪表盘 (Dashboard) - ├─ 用户管理 (UserList) - 仅管理员 - └─ 项目管理 (ProjectList) - ├─ 项目列表 - ├─ 创建项目 - └─ 项目详情 -``` - -### 7.2 核心组件 - -#### 6.2.1 AuthContext -全局认证状态管理: -```javascript -const AuthContext = createContext({ - user: null, - token: null, - login: () => {}, - logout: () => {}, - isAuthenticated: false -}); -``` - -#### 6.2.2 ProtectedRoute -路由守卫组件,保护需要登录的页面。 - -#### 6.2.3 API Service -统一的API调用封装: -- 统一的错误处理 -- 自动添加Token -- 请求拦截器 -- 响应拦截器 - -### 7.3 页面设计 - -#### 6.3.1 登录页 -- 简洁的登录表单 -- 用户名/密码输入 -- 记住我选项 -- 错误提示 - -#### 6.3.2 仪表盘 -- 项目统计卡片(总项目数、进行中、已完成等) -- 最近项目列表 -- 快速操作入口 - -#### 6.3.3 用户管理页 -- 用户列表表格 -- 搜索和筛选功能 -- 创建/编辑/删除/重置密码操作 -- 分页功能 - -#### 6.3.4 项目列表页 -- 项目列表表格 -- 搜索和筛选(状态、部门) -- 创建项目按钮 -- 分页功能 - -#### 6.3.5 项目表单 -- 项目信息表单 -- 表单验证 -- 保存/取消按钮 - -#### 6.3.6 项目详情页 -- 项目详细信息展示 -- 编辑功能 -- 操作日志(可选) - -### 7.4 UI风格 -- 使用Ant Design默认主题 -- 响应式布局 -- 简洁、专业的企业级UI - -## 8. 安全设计 - -### 8.1 认证安全 -- 密码使用bcrypt加密存储 -- JWT Token有效期设置(如24小时) -- Token过期自动刷新机制 - -### 8.2 权限控制 -- 后端基于装饰器的权限验证 -- 前端路由级别的权限控制 -- 前端组件级别的权限控制 - -### 8.3 数据安全 -- SQL注入防护(使用SQLAlchemy ORM) -- XSS防护(React自动转义) -- 输入验证(前后端双重验证) - -## 9. 部署方案 - -### 9.1 开发环境 - -**后端**: -```bash -cd backend -pipenv install -pipenv shell -python run.py -# 服务运行在 http://localhost:5000 -``` - -**前端**: -```bash -cd frontend -npm install -npm start -# 服务运行在 http://localhost:3000 -``` - -**数据库**: -- 本地MySQL数据库 -- 数据库名:`project_manager` -- 配置文件:`.env` - -### 9.2 生产环境(推荐方案) - -**使用Docker Compose**: - -```yaml -version: '3.8' - -services: - mysql: - image: mysql:8.0 - environment: - MYSQL_ROOT_PASSWORD: rootpassword - MYSQL_DATABASE: project_manager - volumes: - - mysql_data:/var/lib/mysql - ports: - - "3306:3306" - - backend: - build: ./backend - ports: - - "5000:5000" - depends_on: - - mysql - environment: - DATABASE_URL: mysql+pymysql://root:rootpassword@mysql/project_manager - SECRET_KEY: your-secret-key - - frontend: - build: ./frontend - ports: - - "80:80" - depends_on: - - backend - -volumes: - mysql_data: -``` - -## 10. 后续优化建议 - -### 10.1 功能扩展 -- 项目附件上传 -- 操作日志记录 -- 数据导出功能 -- 报表统计功能 - -### 10.2 性能优化 -- 数据库索引优化 -- API响应缓存(如需要) -- 前端代码分割 - -### 10.3 用户体验 -- 表单自动保存 -- 批量操作功能 -- 消息通知功能 - -## 11. 开发计划 - -### 11.1 第一阶段(MVP) -- 用户登录/登出 -- 项目CRUD基础功能 -- 简单的用户管理(仅管理员) - -### 11.2 第二阶段 -- 权限控制完善 -- 项目列表筛选和搜索 -- 表单验证优化 - -### 11.3 第三阶段(可选) -- 高级功能扩展 -- 性能优化 -- 用户体验优化 - -## 12. 相关文档 - -### 12.1 团队协作文档 -- [团队协作规范](../TEAM-COLLABORATION.md) - 详细的团队协作流程和规范 -- [权限控制文档](../ACCESS-CONTROL.md) - 各角色访问权限说明 - -### 12.2 角色工作规范 -- [后端程序员工作规范](../backend/WORKSTANDARDS.md) - 后端开发详细规范 -- [前端程序员工作规范](../frontend/WORKSTANDARDS.md) - 前端开发详细规范 -- [测试工程师工作规范](../testing/WORKSTANDARDS.md) - 测试工作详细规范 - -### 12.3 技术文档 -- [UI设计规范](../docs/ui-design-spec.md) - 前端UI设计规范 -- [后端API文档](../backend/docs/api.md) - API接口文档(由后端程序员维护) -- [前端组件文档](../frontend/docs/components.md) - 前端组件文档(由前端程序员维护) -- [测试文档](../testing/docs/) - 测试计划和测试策略(由测试工程师维护) - -### 12.4 项目文档 -- [README.md](../README.md) - 项目说明文档 -- [设计方案](../docs/plans/) - 项目设计方案和技术设计文档 - ---- - -**文档维护**: 本文档由技术总监维护,如有疑问请联系技术总监。 \ No newline at end of file diff --git a/docs/work-summary.md b/docs/work-summary.md new file mode 100644 index 00000000..e14e44c1 --- /dev/null +++ b/docs/work-summary.md @@ -0,0 +1,214 @@ +# docs目录文档重构 - 工作总结 + +## 工作概述 + +根据技术总监的要求,对 `docs/` 目录下的文档进行重构,实现产品文档和技术文档的分离。 + +## 完成的工作 + +### 1. 文档重命名 +✅ `docs/pid.md` → `docs/产品设计文档.md` + +### 2. 新增文档 +✅ `docs/技术架构文档.md` - 包含所有技术实现相关的内容 + +### 3. 文档重构 + +#### 3.1 产品设计文档(产品设计文档.md) + +**文档定位:** +- 产品需求文档(PRD) +- 读者:产品经理、业务人员、普通用户 +- 内容:产品功能、用户界面、使用说明 + +**包含内容(9章):** +1. 项目概述(背景、目标、特点) +2. 功能需求(用户管理、项目管理、数据查询、权限控制、数据导入) +3. 用户界面(界面设计原则、主要页面、界面风格) +4. 数据安全和隐私 +5. 系统性能 +6. 使用帮助(快速入门、常见问题、联系支持) +7. 数据来源 +8. 未来扩展 +9. 相关文档 + +**移除内容:** +- ❌ 技术选型 +- ❌ 系统架构图 +- ❌ 项目目录结构 +- ❌ 数据库表结构(SQL) +- ❌ API接口定义 +- ❌ 前端组件架构 +- ❌ 安全技术实现 +- ❌ 部署方案 +- ❌ 开发规范 + +**文档规模:** +- 行数: 403行 +- 字数: 约8,000字 + +#### 3.2 技术架构文档(技术架构文档.md) + +**文档定位:** +- 技术架构文档 +- 读者:技术总监、后端程序员、前端程序员、测试工程师 +- 内容:技术实现、架构设计、部署方案 + +**包含内容(13章):** +1. 技术选型(前后端、数据库、开发工具) +2. 系统架构(整体架构图、分层架构) +3. 项目目录结构(完整的目录树) +4. 数据库设计(表结构、SQL、索引) +5. API设计(API规范、错误码、端点) +6. 前端设计(技术栈、组件架构、状态管理、API服务) +7. 安全设计(认证、权限、数据安全) +8. 部署方案(开发环境、生产环境、服务器要求) +9. 开发规范(后端、前端、测试) +10. 性能优化(数据库、后端、前端) +11. 监控和日志 +12. 备份和恢复 +13. 相关文档 + +**文档来源:** +- 从原来的 `pid.md` 中提取技术相关内容 +- 参考现有的 `后端架构设计.md` +- 参考现有的 `api.md` + +**文档规模:** +- 行数: 620行 +- 字数: 约12,000字 + +### 4. 文档更新 +✅ `README.md` - 更新了文档资源分类和链接 +✅ `docs-restructuring-log.md` - 记录了重构过程 + +## 文档对比 + +### 重构前 +- **文档**: 1个(pid.md) +- **内容**: 产品需求 + 技术方案混合 +- **读者**: 产品经理 + 技术团队(混杂) +- **字数**: 约15,000字 +- **行数**: 约400行 + +### 重构后 +- **文档**: 2个(产品设计文档.md + 技术架构文档.md) +- **内容**: 产品文档和技术文档分离 +- **读者**: + - 产品设计文档:产品经理、业务人员、普通用户 + - 技术架构文档:技术总监、开发团队、测试团队 +- **字数**: 约20,000字(合计) +- **行数**: 1,023行(合计) + +## 文档结构 + +### docs目录结构 + +``` +docs/ +├── 产品设计文档.md (403行, 11KB) - 产品需求文档 +├── 技术架构文档.md (620行, 21KB) - 技术架构文档 +├── 后端架构设计.md (229行, 8.1KB) - 后端技术架构 +├── api.md (865行, 19KB) - API接口文档 +├── ui-design-spec.md (158行, 14KB) - UI设计规范 +├── TEAM-COLLABORATION.md (158行, 3.6KB) - 团队协作规范 +├── database-design.md (416行, 13KB) - 数据库设计 +├── database-and-data-initialization.md (317行, 4.5KB) - 数据初始化 +├── example.xls (1.5MB) - Excel数据源 +├── pid-update-log.md (118行, 3.3KB) - 更新日志(可归档) +├── pid-update-log-v2.md (232行, 7.0KB) - 更新日志(可归档) +└── docs-restructuring-log.md (287行, 7.5KB) - 重构日志 +``` + +### 文档分类 + +#### 产品文档 +1. **产品设计文档.md** - 产品需求文档(PRD) +2. **ui-design-spec.md** - UI设计规范 +3. **TEAM-COLLABORATION.md** - 团队协作规范 + +#### 技术文档 +1. **技术架构文档.md** - 系统技术架构和实现 +2. **后端架构设计.md** - 后端技术架构详解 +3. **api.md** - API接口文档 +4. **database-design.md** - 数据库表结构设计 +5. **database-and-data-initialization.md** - 数据初始化方案 + +#### 数据文件 +1. **example.xls** - Excel数据源 + +#### 日志文档 +1. **pid-update-log.md** - 更新日志(可归档) +2. **pid-update-log-v2.md** - 更新日志(可归档) +3. **docs-restructuring-log.md** - 重构日志 + +## 文档职责 + +### 产品设计文档.md +- **维护人**: 产品经理、技术总监 +- **读者**: 产品经理、业务人员、普通用户 +- **更新时机**: 产品需求变更时 +- **审核人**: 产品经理、技术总监 + +### 技术架构文档.md +- **维护人**: 技术总监、后端程序员 +- **读者**: 技术总监、后端程序员、前端程序员、测试工程师 +- **更新时机**: 技术方案变更时 +- **审核人**: 技术总监 + +## 优势 + +### 1. 读者友好 +- **产品人员**:只需查看产品设计文档,无需关心技术实现 +- **开发人员**:只需查看技术架构文档,了解技术实现细节 +- **业务人员**:产品设计文档简单易懂,无技术术语 + +### 2. 维护便利 +- **产品文档**:由产品经理维护,专注产品需求 +- **技术文档**:由开发团队维护,专注技术实现 +- **职责清晰**:不同类型文档由不同人员维护 + +### 3. 可读性提升 +- **产品设计文档**:约8,000字,简洁明了 +- **技术架构文档**:约12,000字,技术细节完整 +- **分类清晰**:读者可以快速找到所需信息 + +## 后续工作 + +### 待完成 +- [ ] 更新所有引用 `pid.md` 的文档链接 +- [ ] 更新团队协作文档中的文档引用 +- [ ] 更新各个角色工作规范中的文档引用 +- [ ] 通知团队成员文档变更 +- [ ] 归档旧的更新日志(可选) + +### 注意事项 +- 两个文档中的相关文档链接需要保持一致 +- 产品需求变更时需要同步更新两个文档(如需要) +- 技术实现变更时只需要更新技术架构文档 +- 保持产品文档和技术文档的分离原则 + +## 总结 + +✅ **已完成:** +- 文档重命名(pid.md → 产品设计文档.md) +- 创建技术架构文档 +- 重构产品设计文档(移除技术内容) +- 更新项目README +- 创建重构日志 + +✅ **文档质量:** +- 产品设计文档:简洁易懂,适合非技术人员 +- 技术架构文档:技术完整,适合开发人员 +- 文档分离:职责清晰,维护便利 + +✅ **文档规模:** +- 产品设计文档:403行,约8,000字 +- 技术架构文档:620行,约12,000字 +- 合计:1,023行,约20,000字 + +--- + +**完成人**: 技术总监 +**完成时间**: 2026-01-25 +**审核状态**: 待审核 diff --git a/docs/产品设计文档.md b/docs/产品设计文档.md new file mode 100644 index 00000000..8f7e759a --- /dev/null +++ b/docs/产品设计文档.md @@ -0,0 +1,403 @@ +# 项目信息管理系统 - 产品设计文档 + +## 1. 项目概述 + +### 1.1 项目背景 +开发一个基于BS架构的项目信息管理系统,用于管理电力工程项目的合同、编号、名称、预算、付款等信息。系统支持多部门协作,市场部用户可以新建项目,其他部门用户可以填写和更新项目信息。 + +### 1.2 产品目标 +- 实现工程项目信息的集中管理 +- 支持多部门协同工作 +- 提供完善的数据统计和分析功能 +- 确保数据的准确性和安全性 + +### 1.3 产品特点 +- 轻量级架构,适合小并发场景 +- 无需复杂配置,降低使用门槛 +- 基于角色的权限控制(RBAC) +- 简单易用的用户界面 +- 支持Excel数据导入,便于数据迁移 + +## 2. 功能需求 + +### 2.1 用户管理 + +#### 2.1.1 登录功能 +- 用户名/密码登录 +- 记住密码功能 +- 用户登出功能 + +#### 2.1.2 用户管理(管理员) +- 创建新用户:设置用户名、密码、部门、角色 +- 编辑用户信息 +- 删除用户 +- 重置用户密码 +- 查看用户列表 + +#### 2.1.3 用户角色 +- **管理员**:拥有所有权限 +- **市场部用户**:创建项目、查看项目、编辑自己的项目 +- **其他部门用户**:查看项目、编辑项目信息(如付款、成本、进度等) + +### 2.2 项目管理 + +#### 2.2.1 市场部权限 +- **创建项目**:填写项目基本信息 +- **查看项目**:浏览所有项目列表和详情 +- **编辑项目**:修改自己创建的项目 + +#### 2.2.2 其他部门权限 +- **查看项目**:浏览所有项目列表和详情 +- **更新项目**:填写和更新项目信息(成本、财务、进度等) + +#### 2.2.3 项目信息分类 + +**基本信息:** +- 合同编号 +- 供电局项目合同编号 +- 项目名称 +- 子项信息(个数、编码) +- 投资和合同金额 +- 工程类别(基建/业扩/客户/营销/检修) +- 业主信息(单位、联系人) +- 中标形式 + +**项目时间:** +- 签订日期 +- 开工日期 +- 计划竣工日期 +- 实际竣工日期 + +**成本管理:** +- 总体成本控制 +- 人工成本(控制、计划、实付) +- 材料成本(控制、应付、实际、实付) +- 其他费用(控制、应付、实际) + +**合同和财务:** +- 合同金额 +- 质保金(金额、比例、到期日、退还日期) +- 结算金额 +- 税金 +- 利润(计划、实际) + +**收款和付款:** +- 项目进度 +- 应收款和开票 +- 实际收款和收款完成率 +- 应付款和实际付款 +- 未收款和付款完成率 + +**结算信息:** +- 成本结算金额 +- 各类费用结算(人工、材料、其他) +- 到期结算项目统计 + +**项目管理:** +- 所属项目部 +- 项目负责人 +- 工程款拨付方式 +- 存在的问题 +- 建议措施 +- 备注 + +#### 2.2.4 工程类别 +- 基建工程 +- 业扩工程 +- 客户工程 +- 营销工程 +- 检修、技改、应急抢修项目 + +#### 2.2.5 项目状态说明 +项目数据本身反映了项目的真实状态: +- 未开工:没有开工日期 +- 进行中:已开工但未竣工 +- 已完成:有实际竣工日期 +- 已结算:有结算金额 +- 质保期中:质保期未到期 +- 已退质保金:已退还质保金 + +### 2.3 数据查询和统计 + +#### 2.3.1 项目列表查询 +- 分页浏览项目列表 +- 按合同编号搜索 +- 按工程类别筛选 +- 按所属项目部筛选 +- 按签订日期范围筛选 +- 按合同金额范围筛选 +- 按关键词搜索(项目名称、业主单位) +- 排序功能(按签订日期、合同金额等) + +#### 2.3.2 项目统计 +- **基础统计**: + - 项目总数 + - 总投资金额 + - 总合同金额 + - 总收款金额 + - 总付款金额 + - 平均收款完成率 + - 平均付款完成率 + - 平均项目进度 + +- **分组统计**: + - 按工程类别统计(每个类别的项目数量和金额) + - 按所属项目部分组统计 + +- **时间维度统计**: + - 按日统计项目数量和金额 + - 按月统计项目数量和金额 + - 按年统计项目数量和金额 + +### 2.4 权限控制 + +#### 2.4.1 角色权限矩阵 + +| 功能 | 管理员 | 市场部 | 其他部门 | +|------|--------|--------|---------| +| 登录系统 | ✓ | ✓ | ✓ | +| 创建用户 | ✓ | ✗ | ✗ | +| 编辑用户信息 | ✓ | ✗ | ✗ | +| 删除用户 | ✓ | ✗ | ✗ | +| 查看用户列表 | ✓ | ✗ | ✗ | +| 重置用户密码 | ✓ | ✗ | ✗ | +| 创建项目 | ✓ | ✓ | ✗ | +| 编辑项目 | ✓ | ✓ | 部分 | +| 删除项目 | ✓ | 部分 | ✗ | +| 查看所有项目 | ✓ | ✓ | ✓ | +| 查看项目详情 | ✓ | ✓ | ✓ | +| 查看项目统计 | ✓ | ✓ | ✓ | + +**注**: +- "部分"表示只能编辑自己创建的项目 +- 其他部门用户只能编辑项目的财务、成本、进度等字段,不能修改基础信息 + +#### 2.4.2 字段级权限 +- **市场部用户**:可以编辑自己创建项目的所有字段 +- **其他部门用户**:只能编辑以下字段: + - 成本管理相关字段 + - 收款和付款相关字段 + - 结算信息相关字段 + - 项目管理相关字段 + - 不能修改:合同编号、项目名称、工程类别、业主信息等基础字段 + +### 2.5 数据导入 + +#### 2.5.1 Excel导入功能 +- 支持从Excel文件批量导入项目数据 +- 系统自动验证数据格式 +- 导入前显示数据预览 +- 导入后显示导入结果(成功数、失败数) +- 支持错误日志导出 + +#### 2.5.2 数据管理 +- 查看已导入的数据 +- 编辑和删除导入的数据 +- 数据验证和提示 +- 数据备份功能 + +## 3. 用户界面 + +### 3.1 界面设计原则 +- 简洁易用,操作直观 +- 符合用户使用习惯 +- 信息展示清晰 +- 响应式设计,支持不同屏幕尺寸 + +### 3.2 主要页面 + +#### 3.2.1 登录页 +- 简洁的登录表单 +- 用户名和密码输入框 +- 记住密码选项 +- 错误提示 + +#### 3.2.2 主布局 +- 顶部导航栏 + - Logo + - 用户信息显示 + - 登出按钮 +- 侧边栏菜单 + - 仪表盘 + - 项目管理 + - 用户管理(仅管理员) + - 项目统计 + +#### 3.2.3 仪表盘 +- 项目统计卡片 + - 项目总数 + - 进行中项目数 + - 已完成项目数 + - 总合同金额 + - 总收款金额 + - 总付款金额 +- 最近项目列表 +- 快速操作入口 +- 项目图表展示 + +#### 3.2.4 用户管理页(仅管理员) +- 用户列表表格 +- 创建新用户按钮 +- 编辑用户信息 +- 删除用户 +- 重置用户密码 +- 搜索和筛选功能 +- 分页功能 + +#### 3.2.5 项目列表页 +- 项目列表表格 +- 多条件筛选(工程类别、项目部、日期范围、金额范围) +- 关键词搜索 +- 排序功能 +- 创建新项目按钮 +- 批量操作(可选) +- 分页功能 + +#### 3.2.6 项目详情页 +- 项目基本信息展示 +- 成本管理信息 +- 财务信息 +- 收款和付款信息 +- 结算信息 +- 项目管理信息 +- 编辑按钮(有权限时显示) +- 删除按钮(有权限时显示) + +#### 3.2.7 项目表单页 +- 分组显示表单字段 +- 必填字段标记 +- 表单验证 +- 保存和取消按钮 +- 自动保存功能(可选) + +#### 3.2.8 项目统计页 +- 基础统计展示 +- 分组统计图表 +- 时间维度统计图表 +- 导出统计报表 + +### 3.3 界面风格 +- 使用企业级UI组件 +- 简洁、专业的设计风格 +- 统一的色彩搭配 +- 清晰的视觉层次 + +## 4. 数据安全和隐私 + +### 4.1 用户隐私 +- 用户密码加密存储 +- 敏感信息(如密码)在传输过程中加密 +- 用户信息只能由授权人员查看 +- 提供密码重置功能 + +### 4.2 数据安全 +- 防止数据泄露 +- 定期数据备份 +- 数据访问控制 +- 操作日志记录 + +### 4.3 使用规范 +- 定期修改密码 +- 不泄露账号密码 +- 遵守数据使用规范 +- 及时报告安全问题 + +## 5. 系统性能 + +### 5.1 响应速度 +- 页面加载快速(< 3秒) +- 查询结果及时显示(< 2秒) +- 表单保存流畅(< 1秒) + +### 5.2 系统稳定性 +- 系统稳定运行 +- 偶发错误能够恢复 +- 数据不会丢失 + +### 5.3 并发支持 +- 支持多个用户同时使用 +- 不会因为用户增多而卡顿 + +## 6. 使用帮助 + +### 6.1 快速入门 +1. 使用用户名和密码登录系统 +2. 查看仪表盘了解项目概况 +3. 点击"项目管理"查看项目列表 +4. 根据权限创建或编辑项目 +5. 使用筛选和搜索功能查找项目 + +### 6.2 常见问题 + +**Q: 忘记密码怎么办?** +A: 联系管理员重置密码。 + +**Q: 如何导出项目数据?** +A: 在项目列表页面使用"导出"功能。 + +**Q: 能否同时编辑多个项目?** +A: 目前系统不支持批量编辑,请逐个编辑。 + +**Q: 项目创建后能否修改工程类别?** +A: 可以,但有权限限制,请联系管理员。 + +**Q: 导入Excel失败怎么办?** +A: 请检查Excel格式是否符合要求,查看错误日志。 + +### 6.3 联系支持 +- 技术支持:提供技术帮助和问题解答 +- 系统管理员:负责用户管理和权限分配 +- 产品经理:负责产品需求收集和反馈 + +## 7. 数据来源 + +### 7.1 原始数据 +系统支持从Excel文件导入项目数据,Excel文件格式参照提供的模板。 + +### 7.2 工程分类 +根据业务需求,工程项目分为以下类别: +- 基建工程:基础设施建设相关的工程 +- 业扩工程:业务扩展相关的工程 +- 客户工程:客户委托的工程项目 +- 营销工程:市场推广相关的工程 +- 检修工程:设备检修、技改、应急抢修项目 + +## 8. 未来扩展 + +### 8.1 功能扩展 +- 项目附件上传(文件、图片) +- 操作日志记录 +- 消息通知功能 +- 移动端支持 +- 报表定制功能 + +### 8.2 用户体验优化 +- 表单自动保存 +- 批量操作功能 +- 快捷操作入口 +- 个性化设置 + +### 8.3 数据分析增强 +- 更多的统计图表 +- 数据趋势分析 +- 预警功能 +- 智能推荐 + +## 9. 相关文档 + +### 9.1 产品文档 +- [UI设计规范](ui-design-spec.md) - 界面设计规范 +- [团队协作规范](TEAM-COLLABORATION.md) - 团队协作流程 + +### 9.2 技术文档 +- [技术架构文档](技术架构文档.md) - 系统技术实现 +- [后端架构设计](后端架构设计.md) - 后端技术详情 +- [API文档](api.md) - 接口文档 + +**注**: 以上技术文档供开发人员参考,普通用户无需了解。 + +--- + +**文档维护**: 产品经理、技术总监 +**文档类型**: 产品设计文档(PRD) +**最后更新**: 2026-01-25 diff --git a/docs/技术架构文档.md b/docs/技术架构文档.md new file mode 100644 index 00000000..7106a806 --- /dev/null +++ b/docs/技术架构文档.md @@ -0,0 +1,620 @@ +# 海洋项目管理系统 - 技术架构文档 + +## 1. 技术选型 + +### 1.1 技术栈 + +| 层级 | 技术 | 说明 | +|------|------|------| +| **前端** | | | +| | React + React Router | 前后端分离架构 | +| | Ant Design | 企业级UI组件 | +| | Axios | HTTP客户端 | +| | Context API | 简单状态管理 | +| **后端** | | | +| | FastAPI | 高性能异步框架,自动生成API文档 | +| | SQLAlchemy 2.0 | 类型安全的ORM,支持异步 | +| | MySQL 8.0 | 关系型数据库 | +| | JWT (PyJWT) | 无状态认证 | +| | bcrypt | 密码加密存储 | +| | Pydantic v2 | 请求/响应数据验证 | +| **开发工具** | | | +| | Create React App | 前端开发环境 | +| | pipenv | Python依赖管理 | +| | pytest | 测试框架 | + +### 1.2 系统特点 +- 轻量级架构,适合小并发场景 +- 无需Redis缓存,降低部署复杂度 +- 基于角色的权限控制(RBAC) +- 前后端分离,易于维护和扩展 +- 自动生成API文档(Swagger UI) + +## 2. 系统架构 + +### 2.1 整体架构图 + +``` +┌─────────────────────────────────────────────────┐ +│ 浏览器 │ +│ (React + Ant Design + React Router + Axios) │ +└─────────────────────────────────────────────────┘ + │ + │ HTTP/HTTPS + │ JWT Token + ▼ +┌─────────────────────────────────────────────────┐ +│ FastAPI 后端服务 │ +│ ┌─────────────────────────────────────────┐ │ +│ │ API Layer (FastAPI Routes) │ │ +│ ├─────────────────────────────────────────┤ │ +│ │ Controllers (业务逻辑) │ │ +│ ├─────────────────────────────────────────┤ │ +│ │ Services (复杂业务逻辑) │ │ +│ ├─────────────────────────────────────────┤ │ +│ │ Data Access (SQLAlchemy ORM) │ │ +│ ├─────────────────────────────────────────┤ │ +│ │ Middleware (认证/日志/错误) │ │ +│ └─────────────────────────────────────────┘ │ +└─────────────────────────────────────────────────┘ + │ + │ SQL + ▼ +┌─────────────────────────────────────────────────┐ +│ MySQL 8.0 数据库 │ +│ ┌─────────────────────────────────────────┐ │ +│ │ users │ │ +│ │ projects (60+字段) │ │ +│ └─────────────────────────────────────────┘ │ +└─────────────────────────────────────────────────┘ +``` + +### 2.2 分层架构 + +#### 2.2.1 API Layer (路由层) +- 定义API端点 +- 处理HTTP请求和响应 +- 参数验证 + +#### 2.2.2 Controllers (控制器层) +- 实现业务逻辑 +- 调用Services层 +- 返回响应数据 + +#### 2.2.3 Services (服务层) +- 实现复杂的业务逻辑 +- 数据转换和处理 +- 调用ORM层 + +#### 2.2.4 Data Access (数据访问层) +- 使用SQLAlchemy ORM +- 数据库操作 +- 数据模型定义 + +#### 2.2.5 Middleware (中间件层) +- 认证中间件 +- 日志中间件 +- 错误处理中间件 + +## 3. 项目目录结构 + +``` +ocean_project_manager/ +├── backend/ # 后端程序员工作区 +│ ├── src/ # 源代码 +│ │ ├── controllers/ # 控制器层 +│ │ │ ├── auth.py +│ │ │ ├── users.py +│ │ │ └── projects.py +│ │ ├── services/ # 业务逻辑层 +│ │ │ ├── auth_service.py +│ │ │ ├── user_service.py +│ │ │ └── project_service.py +│ │ ├── models/ # 数据模型 +│ │ │ ├── user.py +│ │ │ └── project.py +│ │ ├── routes/ # 路由定义 +│ │ │ ├── auth.py +│ │ │ ├── users.py +│ │ │ └── projects.py +│ │ ├── middleware/ # 中间件 +│ │ │ ├── auth.py +│ │ │ └── logging.py +│ │ ├── schemas/ # Pydantic模型 +│ │ │ ├── user.py +│ │ │ └── project.py +│ │ ├── utils/ # 工具函数 +│ │ │ ├── password.py +│ │ │ └── jwt.py +│ │ └── dependencies.py # 依赖注入 +│ ├── tests/ # 测试目录 +│ │ ├── test_auth.py +│ │ ├── test_users.py +│ │ └── test_projects.py +│ ├── config/ # 配置文件 +│ │ ├── __init__.py +│ │ ├── database.py # 数据库配置 +│ │ ├── settings.py # 应用配置 +│ │ ├── init-database.sql # 数据库初始化 +│ │ └── import-excel-data.py # Excel导入脚本 +│ ├── docs/ # API文档 +│ │ └── api.md # API接口文档 +│ ├── requirements.txt # Python依赖 +│ ├── main.py # FastAPI应用入口 +│ ├── WORKSTANDARDS.md # 后端工作规范 +│ └── README.md # 后端开发说明 +│ +├── frontend/ # 前端程序员工作区 +│ ├── src/ # 源代码 +│ │ ├── components/ # 可复用组件 +│ │ │ ├── Layout.jsx +│ │ │ ├── Header.jsx +│ │ │ └── Sidebar.jsx +│ │ ├── pages/ # 页面组件 +│ │ │ ├── Login.jsx +│ │ │ ├── Dashboard.jsx +│ │ │ ├── UserList.jsx +│ │ │ ├── UserForm.jsx +│ │ │ ├── ProjectList.jsx +│ │ │ ├── ProjectForm.jsx +│ │ │ └── ProjectDetail.jsx +│ │ ├── hooks/ # 自定义Hooks +│ │ │ └── useAuth.js +│ │ ├── services/ # API服务 +│ │ │ ├── api.js # Axios配置 +│ │ │ ├── auth.js +│ │ │ ├── user.js +│ │ │ └── project.js +│ │ ├── utils/ # 工具函数 +│ │ │ └── auth.js +│ │ ├── styles/ # 样式文件 +│ │ │ └── global.css +│ │ ├── types/ # TypeScript类型定义 +│ │ │ ├── user.ts +│ │ │ └── project.ts +│ │ ├── App.jsx # 根组件 +│ │ └── index.js # 入口文件 +│ ├── tests/ # 组件测试 +│ ├── docs/ # 组件文档 +│ │ └── components.md +│ ├── package.json +│ ├── WORKSTANDARDS.md # 前端工作规范 +│ └── README.md # 前端开发说明 +│ +├── testing/ # 测试工程师工作区 +│ ├── testcases/ # 测试用例 +│ │ ├── api/ # API测试用例 +│ │ ├── ui/ # UI测试用例 +│ │ └── integration/ # 集成测试用例 +│ ├── reports/ # 测试报告 +│ ├── data/ # 测试数据 +│ ├── scripts/ # 自动化测试脚本 +│ ├── docs/ # 测试文档 +│ ├── WORKSTANDARDS.md # 测试工作规范 +│ └── README.md # 测试工作说明 +│ +└── docs/ # 项目文档 + ├── 产品设计文档.md # 产品需求文档(PRD) + ├── 技术架构文档.md # 技术架构文档(本文档) + ├── 后端架构设计.md # 后端技术架构 + ├── api.md # API接口文档 + ├── ui-design-spec.md # UI设计规范 + ├── TEAM-COLLABORATION.md # 团队协作规范 + ├── database-design.md # 数据库设计 + ├── database-and-data-initialization.md # 数据初始化 + ├── example.xls # Excel数据源 + └── plans/ # 设计方案 +``` + +## 4. 数据库设计 + +### 4.1 用户表 (users) + +| 字段名 | 类型 | 约束 | 说明 | +|--------|------|------|------| +| id | INT | PRIMARY KEY, AUTO_INCREMENT | 用户ID | +| username | VARCHAR(50) | UNIQUE, NOT NULL | 用户名 | +| password_hash | VARCHAR(255) | NOT NULL | 密码哈希 | +| real_name | VARCHAR(100) | NOT NULL | 真实姓名 | +| department | VARCHAR(50) | NOT NULL | 部门 | +| role | ENUM | NOT NULL | 角色(admin/market/other) | +| email | VARCHAR(100) | UNIQUE | 邮箱 | +| phone | VARCHAR(20) | | 电话 | +| is_active | BOOLEAN | DEFAULT TRUE | 是否激活 | +| created_at | DATETIME | DEFAULT CURRENT_TIMESTAMP | 创建时间 | +| updated_at | DATETIME | DEFAULT CURRENT_TIMESTAMP ON UPDATE | 更新时间 | + +```sql +CREATE TABLE users ( + id INT PRIMARY KEY AUTO_INCREMENT, + username VARCHAR(50) UNIQUE NOT NULL, + password_hash VARCHAR(255) NOT NULL, + real_name VARCHAR(100) NOT NULL, + department VARCHAR(50) NOT NULL, + role ENUM('admin', 'market', 'other') NOT NULL, + email VARCHAR(100) UNIQUE, + phone VARCHAR(20), + is_active BOOLEAN DEFAULT TRUE, + created_at DATETIME DEFAULT CURRENT_TIMESTAMP, + updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + INDEX idx_users_username (username), + INDEX idx_users_department (department), + INDEX idx_users_role (role) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表'; +``` + +### 4.2 项目表 (projects) + +基于`docs/database-design.md`,包含60+个字段: + +**基础信息字段:** +- project_no, power_contract_no, name, subitem_count, subitem_code +- total_investment, contract_amount, settlement_amount, total_cost_estimated +- voltage_level, engineering_type, owner_unit, owner_contact, bidding_type + +**时间字段:** +- signing_date, start_date, planned_end_date, actual_end_date +- warranty_expiry_date, actual_warranty_refund_date + +**成本控制字段:** +- total_cost_control, is_adjusted +- labor_cost_control, labor_cost_planned, labor_cost_paid +- material_cost_control, material_cost_payable, material_cost_actual, material_cost_paid +- other_cost_control, other_cost_payable, other_cost_actual + +**财务信息字段:** +- warranty_amount, warranty_ratio +- tax_amount, profit, actual_profit +- cost_settlement_amount + +**应收应付字段:** +- cumulative_progress +- receivable_amount, invoice_amount, actual_receipt_amount, receipt_completion_rate +- payable_amount, actual_payment_amount, unpaid_amount, payment_completion_rate + +**结算信息字段:** +- settlement_cost_amount, settlement_labor_cost, settlement_material_cost, settlement_other_cost +- due_settlement_count, unsettlement_count + +**项目管理字段:** +- project_department, project_leader, payment_method +- problems, suggestions, remarks + +**注意**:项目表不使用status字段,数据本身反映了项目的真实状态。 + +### 4.3 索引设计 + +```sql +-- 项目编号索引 +CREATE INDEX idx_projects_project_no ON projects(project_no); + +-- 工程类别索引 +CREATE INDEX idx_projects_engineering_type ON projects(engineering_type); + +-- 所属项目部索引 +CREATE INDEX idx_projects_project_department ON projects(project_department); + +-- 创建人索引 +CREATE INDEX idx_projects_created_by ON projects(created_by); + +-- 签订日期索引 +CREATE INDEX idx_projects_signing_date ON projects(signing_date); + +-- 计划竣工日期索引 +CREATE INDEX idx_projects_planned_end_date ON projects(planned_end_date); + +-- 复合索引:工程类别 + 创建人 +CREATE INDEX idx_projects_type_created_by ON projects(engineering_type, created_by); +``` + +## 5. API设计 + +### 5.1 API规范 + +- **Base URL**: `/api/v1` +- **Content-Type**: `application/json` +- **认证方式**: JWT Token(在Header中传递:`Authorization: Bearer `) +- **API文档地址**: `http://localhost:5000/docs` (Swagger UI) + +### 5.2 统一响应格式 + +```json +{ + "success": true, + "message": "操作成功", + "data": {}, + "error_code": null +} +``` + +### 5.3 错误码设计 + +| 错误码 | 说明 | HTTP状态码 | +|--------|------|-----------| +| 1001 | 参数验证失败 | 400 | +| 1002 | 用户名或密码错误 | 401 | +| 1003 | Token无效或过期 | 401 | +| 2001 | 资源不存在 | 404 | +| 2002 | 资源已存在 | 409 | +| 3001 | 权限不足 | 403 | +| 5000 | 服务器内部错误 | 500 | + +### 5.4 API端点 + +详细的API文档请参考:[API文档](api.md) + +主要端点包括: +- 认证相关:POST /auth/login, GET /auth/me, POST /auth/logout +- 用户管理:GET /users, POST /users, PUT /users/{id}, DELETE /users/{id} +- 项目管理:GET /projects, POST /projects, PUT /projects/{id}, DELETE /projects/{id} +- 项目统计:GET /projects/statistics, GET /projects/statistics/group, GET /projects/statistics/timeline + +## 6. 前端设计 + +### 6.1 技术栈 +- React 18 +- React Router 6 +- Ant Design 5 +- Axios +- Context API + +### 6.2 组件架构 + +``` +App +├── ProtectedRoute (路由守卫) +├── Layout (主布局) +│ ├── Header (顶部导航) +│ └── Sidebar (侧边栏) +├── Login (登录页) +└── Dashboard (仪表盘) + ├── UserList (用户列表) + │ └── UserForm (用户表单) + └── ProjectList (项目列表) + ├── ProjectForm (项目表单) + └── ProjectDetail (项目详情) +``` + +### 6.3 状态管理 +使用React Context API进行状态管理: +- AuthContext: 认证状态(用户信息、Token、登录/登出) +- 可以根据需要扩展其他Context + +### 6.4 API服务 +使用Axios封装HTTP请求: +- 统一的baseURL配置 +- 请求拦截器(自动添加Token) +- 响应拦截器(统一错误处理) +- API模块化管理 + +## 7. 安全设计 + +### 7.1 认证安全 +- 密码使用bcrypt加密存储(salt rounds=12) +- JWT Token有效期24小时 +- Token存储在LocalStorage(可改为HttpOnly Cookie) +- 实现Token自动刷新机制 + +### 7.2 权限控制 +- 后端基于依赖注入的权限验证 +- 支持角色级别权限控制(admin/market/other) +- 支持资源级别权限控制(如market只能编辑自己创建的项目) +- 前端路由级别的权限控制(ProtectedRoute组件) + +### 7.3 数据安全 +- SQL注入防护(SQLAlchemy ORM参数化查询) +- XSS防护(React自动转义) +- 输入验证(前后端双重验证:Pydantic + 前端表单验证) +- 敏感信息加密(密码哈希) + +### 7.4 其他安全措施 +- CORS配置(限制跨域访问) +- 请求频率限制(可选,使用slowapi) +- HTTPS部署(生产环境必须) + +## 8. 部署方案 + +### 8.1 开发环境 + +**后端:** +```bash +cd backend +pip install -r requirements.txt +uvicorn main:app --reload --host 0.0.0.0 --port 5000 +# 服务运行在 http://localhost:5000 +``` + +**前端:** +```bash +cd frontend +npm install +npm start +# 服务运行在 http://localhost:3000 +``` + +**数据库:** +- 本地MySQL数据库 +- 数据库名:`project_manager` +- 配置文件:`backend/config/.env` + +### 8.2 生产环境 + +**推荐方案:使用Docker Compose** + +```yaml +version: '3.8' + +services: + mysql: + image: mysql:8.0 + environment: + MYSQL_ROOT_PASSWORD: rootpassword + MYSQL_DATABASE: project_manager + volumes: + - mysql_data:/var/lib/mysql + ports: + - "3306:3306" + + backend: + build: ./backend + ports: + - "5000:5000" + depends_on: + - mysql + environment: + DATABASE_URL: mysql+aiomysql://root:rootpassword@mysql/project_manager + SECRET_KEY: your-secret-key + + frontend: + build: ./frontend + ports: + - "80:80" + depends_on: + - backend + +volumes: + mysql_data: +``` + +**部署命令:** +```bash +docker-compose up -d +``` + +### 8.3 服务器要求 + +**最低配置:** +- CPU: 2核 +- 内存: 4GB +- 硬盘: 50GB +- 系统: Ubuntu 20.04+ 或 CentOS 7+ + +**推荐配置:** +- CPU: 4核 +- 内存: 8GB +- 硬盘: 100GB +- 系统: Ubuntu 22.04+ 或 CentOS 8+ + +## 9. 开发规范 + +### 9.1 后端开发规范 + +**代码风格:** +- 遵循PEP 8规范 +- 使用Type Hints进行类型标注 +- 函数和类添加docstring + +**提交规范:** +- 提交格式: `[backend] <类型>: <描述>` +- 类型: feat, fix, docs, style, refactor, test, chore + +**测试要求:** +- 单元测试覆盖率 > 80% +- 使用pytest测试框架 +- 测试文件命名: `test_<模块名>.py` + +### 9.2 前端开发规范 + +**代码风格:** +- 遵循ESLint规则 +- 使用Prettier格式化代码 +- 组件使用函数式组件 + Hooks + +**提交规范:** +- 提交格式: `[frontend] <类型>: <描述>` +- 类型: feat, fix, style, refactor, test, chore + +**测试要求:** +- 关键组件必须有测试 +- 使用Jest或Vitest测试框架 +- 测试文件命名: `<组件名>.test.jsx` + +### 9.3 测试规范 + +**测试分类:** +- 单元测试:测试单个函数/组件 +- 集成测试:测试模块间的交互 +- 端到端测试:测试完整业务流程 + +**测试覆盖率:** +- 后端: > 80% +- 前端关键组件: > 70% + +## 10. 性能优化 + +### 10.1 数据库优化 +- 合理使用索引 +- 查询优化(避免N+1查询) +- 使用连接池 +- 定期备份和优化 + +### 10.2 后端优化 +- 异步处理(FastAPI async/await) +- 缓存(可选,Redis) +- 请求限流 +- 日志优化 + +### 10.3 前端优化 +- 代码分割(React.lazy) +- 图片懒加载 +- 使用CDN +- Gzip压缩 + +## 11. 监控和日志 + +### 11.1 日志管理 +- 后端使用Python logging模块 +- 日志级别:DEBUG, INFO, WARNING, ERROR, CRITICAL +- 日志文件按日期分割 + +### 11.2 性能监控 +- 使用APM工具(如Sentry) +- 监控API响应时间 +- 监控错误率 + +### 11.3 错误追踪 +- 自动捕获和记录错误 +- 发送错误告警 +- 错误堆栈追踪 + +## 12. 备份和恢复 + +### 12.1 数据库备份 +```bash +# 备份 +mysqldump -u root -p project_manager > backup_$(date +%Y%m%d).sql + +# 恢复 +mysql -u root -p project_manager < backup_20260125.sql +``` + +### 12.2 代码备份 +- 使用Git进行版本控制 +- 定期推送到远程仓库 +- 打标签标记重要版本 + +### 12.3 备份策略 +- 每日自动备份数据库 +- 每周备份到远程服务器 +- 保留最近30天的备份 + +## 13. 相关文档 + +### 13.1 技术文档 +- [后端架构设计](后端架构设计.md) - 后端技术架构详解 +- [API文档](api.md) - 完整的API接口文档 +- [数据库设计](database-design.md) - 数据库表结构详细设计 + +### 13.2 产品文档 +- [产品设计文档](产品设计文档.md) - 产品需求文档(PRD) + +### 13.3 其他文档 +- [UI设计规范](ui-design-spec.md) - 前端UI设计规范 +- [团队协作规范](TEAM-COLLABORATION.md) - 团队协作流程 +- [数据库设计和数据初始化](database-and-data-initialization.md) - 数据初始化方案 + +--- + +**文档维护**: 技术总监、后端程序员 +**文档类型**: 技术架构文档 +**最后更新**: 2026-01-25 diff --git a/frontend/docs/api-service.md b/frontend/docs/api-service.md new file mode 100644 index 00000000..6ac91fa1 --- /dev/null +++ b/frontend/docs/api-service.md @@ -0,0 +1,1070 @@ +# API服务封装文档 + +## 1. Axios基础配置 + +### 1.1 Axios实例创建 + +```javascript +// services/api.js +import axios from 'axios'; + +// 创建axios实例 +const api = axios.create({ + baseURL: import.meta.env.VITE_API_BASE_URL || 'http://localhost:5000/api/v1', + timeout: 10000, + headers: { + 'Content-Type': 'application/json', + }, +}); + +export default api; +``` + +### 1.2 环境变量配置 + +```env +# .env.development +VITE_API_BASE_URL=http://localhost:5000/api/v1 +VITE_APP_TITLE=海洋项目管理系统 + +# .env.production +VITE_API_BASE_URL=https://api.example.com/api/v1 +VITE_APP_TITLE=海洋项目管理系统 +``` + +## 2. 请求拦截器 + +### 2.1 请求前拦截 + +```javascript +// services/api.js + +// 请求拦截器 +api.interceptors.request.use( + (config) => { + // 添加Token + const token = localStorage.getItem('token'); + if (token) { + config.headers.Authorization = `Bearer ${token}`; + } + + // 添加时间戳防止缓存 + if (config.method === 'get') { + config.params = { + ...config.params, + _t: Date.now(), + }; + } + + return config; + }, + (error) => { + return Promise.reject(error); + } +); +``` + +### 2.2 请求日志(开发环境) + +```javascript +// services/api.js + +if (import.meta.env.DEV) { + api.interceptors.request.use((config) => { + console.log('Request:', { + url: config.url, + method: config.method, + params: config.params, + data: config.data, + }); + return config; + }); +} +``` + +## 3. 响应拦截器 + +### 3.1 响应数据处理 + +```javascript +// services/api.js +import { message } from 'antd'; + +// 响应拦截器 +api.interceptors.response.use( + (response) => { + const { data } = response; + + // 检查业务状态码 + if (data.success === false) { + message.error(data.message || '请求失败'); + return Promise.reject(new Error(data.message || '请求失败')); + } + + // 返回数据部分 + return data.data || data; + }, + (error) => { + // 错误处理 + if (error.response) { + handleResponseError(error.response); + } else if (error.request) { + handleNetworkError(error); + } else { + handleOtherError(error); + } + + return Promise.reject(error); + } +); +``` + +### 3.2 错误处理函数 + +```javascript +// services/api.js + +/** + * 处理响应错误 + */ +const handleResponseError = (response) => { + const { status, data } = response; + + switch (status) { + case 400: + message.error(data.message || '请求参数错误'); + break; + case 401: + message.error('登录已过期,请重新登录'); + clearAuth(); + window.location.href = '/login'; + break; + case 403: + message.error('权限不足'); + break; + case 404: + message.error('请求的资源不存在'); + break; + case 409: + message.error(data.message || '资源已存在'); + break; + case 500: + message.error('服务器错误,请稍后重试'); + break; + default: + message.error(data.message || '请求失败'); + } +}; + +/** + * 处理网络错误 + */ +const handleNetworkError = (error) => { + if (error.code === 'ECONNABORTED') { + message.error('请求超时,请检查网络连接'); + } else { + message.error('网络错误,请检查网络连接'); + } +}; + +/** + * 处理其他错误 + */ +const handleOtherError = (error) => { + message.error(error.message || '发生未知错误'); +}; + +/** + * 清除认证信息 + */ +const clearAuth = () => { + localStorage.removeItem('token'); + localStorage.removeItem('user'); +}; +``` + +### 3.3 响应日志(开发环境) + +```javascript +// services/api.js + +if (import.meta.env.DEV) { + api.interceptors.response.use( + (response) => { + console.log('Response:', response); + return response; + }, + (error) => { + console.error('Error:', error); + return Promise.reject(error); + } + ); +} +``` + +## 4. API模块化设计 + +### 4.1 认证API (services/auth.js) + +```javascript +// services/auth.js +import api from './api'; + +export const authAPI = { + /** + * 用户登录 + * @param {string} username - 用户名 + * @param {string} password - 密码 + */ + login: (username, password) => { + return api.post('/auth/login', { username, password }); + }, + + /** + * 获取当前用户信息 + */ + getCurrentUser: () => { + return api.get('/auth/me'); + }, + + /** + * 登出 + */ + logout: () => { + return api.post('/auth/logout'); + }, +}; +``` + +### 4.2 用户API (services/user.js) + +```javascript +// services/user.js +import api from './api'; + +export const userAPI = { + /** + * 获取用户列表 + * @param {Object} params - 查询参数 + * @param {number} params.page - 页码 + * @param {number} params.page_size - 每页数量 + * @param {string} params.department - 部门筛选 + * @param {string} params.role - 角色筛选 + * @param {string} params.keyword - 关键词搜索 + */ + getList: (params) => { + return api.get('/users', { params }); + }, + + /** + * 获取用户详情 + * @param {number} id - 用户ID + */ + getDetail: (id) => { + return api.get(`/users/${id}`); + }, + + /** + * 创建用户 + * @param {Object} data - 用户数据 + */ + create: (data) => { + return api.post('/users', data); + }, + + /** + * 更新用户 + * @param {number} id - 用户ID + * @param {Object} data - 更新数据 + */ + update: (id, data) => { + return api.put(`/users/${id}`, data); + }, + + /** + * 删除用户 + * @param {number} id - 用户ID + */ + delete: (id) => { + return api.delete(`/users/${id}`); + }, + + /** + * 重置用户密码 + * @param {number} id - 用户ID + * @param {string} new_password - 新密码 + */ + resetPassword: (id, new_password) => { + return api.post(`/users/${id}/reset-password`, { new_password }); + }, +}; +``` + +### 4.3 项目API (services/project.js) + +```javascript +// services/project.js +import api from './api'; + +export const projectAPI = { + /** + * 获取项目列表 + * @param {Object} params - 查询参数 + */ + getList: (params) => { + return api.get('/projects', { params }); + }, + + /** + * 获取项目详情 + * @param {number} id - 项目ID + */ + getDetail: (id) => { + return api.get(`/projects/${id}`); + }, + + /** + * 创建项目 + * @param {Object} data - 项目数据 + */ + create: (data) => { + return api.post('/projects', data); + }, + + /** + * 更新项目 + * @param {number} id - 项目ID + * @param {Object} data - 更新数据 + */ + update: (id, data) => { + return api.put(`/projects/${id}`, data); + }, + + /** + * 删除项目 + * @param {number} id - 项目ID + */ + delete: (id) => { + return api.delete(`/projects/${id}`); + }, + + /** + * 批量删除项目 + * @param {Array} ids - 项目ID数组 + */ + batchDelete: (ids) => { + return api.post('/projects/batch-delete', { ids }); + }, + + /** + * 获取基础统计 + * @param {Object} params - 查询参数 + */ + getStatistics: (params) => { + return api.get('/projects/statistics', { params }); + }, + + /** + * 获取分组统计 + * @param {Object} params - 查询参数 + * @param {string} params.group_by - 分组字段 + */ + getGroupStatistics: (params) => { + return api.get('/projects/statistics/group', { params }); + }, + + /** + * 获取时间维度统计 + * @param {Object} params - 查询参数 + * @param {string} params.time_field - 时间字段 + */ + getTimelineStatistics: (params) => { + return api.get('/projects/statistics/timeline', { params }); + }, + + /** + * 导出项目数据 + * @param {Object} params - 查询参数 + */ + export: (params) => { + return api.get('/projects/export', { + params, + responseType: 'blob', + }); + }, +}; +``` + +### 4.4 统计API (services/statistics.js) + +```javascript +// services/statistics.js +import api from './api'; +import { projectAPI } from './project'; + +// 重用projectAPI的统计方法 +export const statisticsAPI = { + ...projectAPI.getStatistics, + ...projectAPI.getGroupStatistics, + ...projectAPI.getTimelineStatistics, + + /** + * 获取仪表盘数据 + */ + getDashboard: () => { + return api.get('/statistics/dashboard'); + }, + + /** + * 获取项目趋势 + * @param {Object} params - 查询参数 + */ + getTrend: (params) => { + return api.get('/statistics/trend', { params }); + }, +}; +``` + +## 5. 自定义Hooks封装 + +### 5.1 useApi Hook + +```javascript +// hooks/useApi.js +import { useState, useCallback } from 'react'; +import { message } from 'antd'; + +/** + * API调用Hook + * @param {Function} apiFunc - API函数 + */ +export const useApi = (apiFunc) => { + const [data, setData] = useState(null); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + + const execute = useCallback( + async (...args) => { + try { + setLoading(true); + setError(null); + const result = await apiFunc(...args); + setData(result); + return result; + } catch (err) { + setError(err); + throw err; + } finally { + setLoading(false); + } + }, + [apiFunc] + ); + + return { data, loading, error, execute }; +}; +``` + +### 5.2 useList Hook + +```javascript +// hooks/useList.js +import { useState, useCallback } from 'react'; + +/** + * 列表数据Hook + * @param {Function} apiFunc - API函数 + */ +export const useList = (apiFunc) => { + const [list, setList] = useState([]); + const [total, setTotal] = useState(0); + const [loading, setLoading] = useState(false); + const [pagination, setPagination] = useState({ + current: 1, + pageSize: 10, + }); + + const fetchList = useCallback( + async (params = {}) => { + try { + setLoading(true); + const result = await apiFunc({ + page: pagination.current, + page_size: pagination.pageSize, + ...params, + }); + + setList(result.items || []); + setTotal(result.total || 0); + } catch (error) { + console.error('Fetch list error:', error); + } finally { + setLoading(false); + } + }, + [apiFunc, pagination] + ); + + const handleTableChange = useCallback( + (newPagination) => { + setPagination(newPagination); + }, + [] + ); + + const refresh = useCallback(() => { + fetchList(); + }, [fetchList]); + + return { + list, + total, + loading, + pagination, + fetchList, + handleTableChange, + refresh, + }; +}; +``` + +### 5.3 useCreate Hook + +```javascript +// hooks/useCreate.js +import { useState, useCallback } from 'react'; +import { message } from 'antd'; + +/** + * 创建数据Hook + * @param {Function} apiFunc - API函数 + * @param {Function} onSuccess - 成功回调 + */ +export const useCreate = (apiFunc, onSuccess) => { + const [loading, setLoading] = useState(false); + + const create = useCallback( + async (data) => { + try { + setLoading(true); + await apiFunc(data); + message.success('创建成功'); + if (onSuccess) { + onSuccess(); + } + } catch (error) { + console.error('Create error:', error); + } finally { + setLoading(false); + } + }, + [apiFunc, onSuccess] + ); + + return { create, loading }; +}; +``` + +### 5.4 useUpdate Hook + +```javascript +// hooks/useUpdate.js +import { useState, useCallback } from 'react'; +import { message } from 'antd'; + +/** + * 更新数据Hook + * @param {Function} apiFunc - API函数 + * @param {Function} onSuccess - 成功回调 + */ +export const useUpdate = (apiFunc, onSuccess) => { + const [loading, setLoading] = useState(false); + + const update = useCallback( + async (id, data) => { + try { + setLoading(true); + await apiFunc(id, data); + message.success('更新成功'); + if (onSuccess) { + onSuccess(); + } + } catch (error) { + console.error('Update error:', error); + } finally { + setLoading(false); + } + }, + [apiFunc, onSuccess] + ); + + return { update, loading }; +}; +``` + +### 5.5 useDelete Hook + +```javascript +// hooks/useDelete.js +import { useState, useCallback } from 'react'; +import { Modal, message } from 'antd'; + +/** + * 删除数据Hook + * @param {Function} apiFunc - API函数 + * @param {Function} onSuccess - 成功回调 + */ +export const useDelete = (apiFunc, onSuccess) => { + const [loading, setLoading] = useState(false); + + const handleDelete = useCallback( + async (id, options = {}) => { + const { title = '确认删除', content = '确定要删除吗?' } = options; + + Modal.confirm({ + title, + content, + onOk: async () => { + try { + setLoading(true); + await apiFunc(id); + message.success('删除成功'); + if (onSuccess) { + onSuccess(); + } + } catch (error) { + console.error('Delete error:', error); + } finally { + setLoading(false); + } + }, + }); + }, + [apiFunc, onSuccess] + ); + + return { handleDelete, loading }; +}; +``` + +## 6. 请求工具函数 + +### 6.1 防抖函数 + +```javascript +// utils/debounce.js + +/** + * 防抖函数 + * @param {Function} func - 需要防抖的函数 + * @param {number} delay - 延迟时间 + */ +export const debounce = (func, delay = 300) => { + let timer; + return (...args) => { + clearTimeout(timer); + timer = setTimeout(() => { + func.apply(this, args); + }, delay); + }; +}; +``` + +### 6.2 节流函数 + +```javascript +// utils/throttle.js + +/** + * 节流函数 + * @param {Function} func - 需要节流的函数 + * @param {number} delay - 延迟时间 + */ +export const throttle = (func, delay = 300) => { + let lastCall = 0; + return (...args) => { + const now = Date.now(); + if (now - lastCall >= delay) { + func.apply(this, args); + lastCall = now; + } + }; +}; +``` + +### 6.3 取消重复请求 + +```javascript +// utils/request.js + +const pendingRequests = new Map(); + +/** + * 生成请求key + */ +const generateRequestKey = (config) => { + const { method, url, params, data } = config; + return [method, url, JSON.stringify(params), JSON.stringify(data)].join('&'); +}; + +/** + * 取消重复请求 + */ +export const cancelDuplicateRequest = (config) => { + const requestKey = generateRequestKey(config); + + if (pendingRequests.has(requestKey)) { + const cancel = pendingRequests.get(requestKey); + cancel('取消重复请求'); + pendingRequests.delete(requestKey); + } +}; + +/** + * 添加待处理请求 + */ +export const addPendingRequest = (config, cancel) => { + const requestKey = generateRequestKey(config); + pendingRequests.set(requestKey, cancel); +}; + +/** + * 移除待处理请求 + */ +export const removePendingRequest = (config) => { + const requestKey = generateRequestKey(config); + pendingRequests.delete(requestKey); +}; +``` + +## 7. 文件上传和下载 + +### 7.1 文件上传 + +```javascript +// services/upload.js +import api from './api'; + +/** + * 上传文件 + * @param {File} file - 文件对象 + * @param {Function} onProgress - 进度回调 + */ +export const uploadFile = (file, onProgress) => { + const formData = new FormData(); + formData.append('file', file); + + return api.post('/upload', formData, { + headers: { + 'Content-Type': 'multipart/form-data', + }, + onUploadProgress: (progressEvent) => { + const percentCompleted = Math.round( + (progressEvent.loaded * 100) / progressEvent.total + ); + if (onProgress) { + onProgress(percentCompleted); + } + }, + }); +}; + +/** + * 批量上传文件 + * @param {File[]} files - 文件数组 + */ +export const uploadFiles = async (files) => { + const uploadPromises = files.map((file) => uploadFile(file)); + return Promise.all(uploadPromises); +}; +``` + +### 7.2 文件下载 + +```javascript +// services/download.js +import api from './api'; + +/** + * 下载文件 + * @param {string} url - 下载地址 + * @param {string} filename - 文件名 + */ +export const downloadFile = async (url, filename) => { + try { + const response = await api.get(url, { + responseType: 'blob', + }); + + const blob = new Blob([response]); + const downloadUrl = window.URL.createObjectURL(blob); + const link = document.createElement('a'); + link.href = downloadUrl; + link.download = filename; + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + window.URL.revokeObjectURL(downloadUrl); + } catch (error) { + console.error('Download error:', error); + } +}; + +/** + * 导出Excel + * @param {Object} params - 查询参数 + */ +export const exportExcel = async (params) => { + try { + const response = await api.get('/export', { + params, + responseType: 'blob', + }); + + const blob = new Blob([response], { + type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + }); + const downloadUrl = window.URL.createObjectURL(blob); + const link = document.createElement('a'); + link.href = downloadUrl; + link.download = `export_${Date.now()}.xlsx`; + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + window.URL.revokeObjectURL(downloadUrl); + } catch (error) { + console.error('Export error:', error); + } +}; +``` + +## 8. 错误处理和重试 + +### 8.1 请求重试 + +```javascript +// services/api.js + +/** + * 请求重试配置 + */ +const retryConfig = { + retries: 3, + retryDelay: 1000, + retryCondition: (error) => { + // 网络错误或5xx错误时重试 + return !error.response || error.response.status >= 500; + }, +}; + +api.interceptors.response.use(undefined, async (error) => { + const { config } = error; + + if (!config || !config.retryCount) { + return Promise.reject(error); + } + + const { retries, retryDelay, retryCondition } = retryConfig; + + // 检查是否满足重试条件 + if (!retryCondition(error)) { + return Promise.reject(error); + } + + // 检查重试次数 + config.retryCount = config.retryCount || 0; + if (config.retryCount >= retries) { + return Promise.reject(error); + } + + // 增加重试计数 + config.retryCount++; + + // 延迟重试 + await new Promise((resolve) => setTimeout(resolve, retryDelay)); + + // 重新请求 + return api(config); +}); +``` + +## 9. 缓存策略 + +### 9.1 内存缓存 + +```javascript +// utils/cache.js + +const cache = new Map(); + +/** + * 设置缓存 + */ +export const setCache = (key, value, ttl = 60000) => { + const expiry = Date.now() + ttl; + cache.set(key, { value, expiry }); +}; + +/** + * 获取缓存 + */ +export const getCache = (key) => { + const item = cache.get(key); + + if (!item) { + return null; + } + + if (Date.now() > item.expiry) { + cache.delete(key); + return null; + } + + return item.value; +}; + +/** + * 清除缓存 + */ +export const clearCache = (key) => { + if (key) { + cache.delete(key); + } else { + cache.clear(); + } +}; +``` + +### 9.2 带缓存的请求 + +```javascript +// hooks/useCachedApi.js +import { useState, useEffect } from 'react'; +import { getCache, setCache } from '../utils/cache'; + +/** + * 带缓存的API调用Hook + */ +export const useCachedApi = (apiFunc, cacheKey, ttl = 60000) => { + const [data, setData] = useState(null); + const [loading, setLoading] = useState(false); + + useEffect(() => { + const fetchData = async () => { + // 检查缓存 + const cached = getCache(cacheKey); + if (cached) { + setData(cached); + return; + } + + // 发起请求 + try { + setLoading(true); + const result = await apiFunc(); + setData(result); + setCache(cacheKey, result, ttl); + } catch (error) { + console.error('API error:', error); + } finally { + setLoading(false); + } + }; + + fetchData(); + }, [apiFunc, cacheKey, ttl]); + + return { data, loading }; +}; +``` + +## 10. API使用示例 + +### 10.1 在组件中使用 + +```jsx +import React, { useEffect } from 'react'; +import { useList } from '../hooks/useList'; +import { projectAPI } from '../services/project'; +import Table from '../components/Common/Table'; + +const ProjectList = () => { + const { list, total, loading, pagination, handleTableChange } = useList( + projectAPI.getList + ); + + const columns = [ + { + title: '项目编号', + dataIndex: 'project_no', + key: 'project_no', + }, + { + title: '项目名称', + dataIndex: 'name', + key: 'name', + }, + // ...其他列 + ]; + + return ( + + ); +}; + +export default ProjectList; +``` + +### 10.2 表单提交 + +```jsx +import React from 'react'; +import { Form, Input, Button } from 'antd'; +import { useCreate } from '../hooks/useCreate'; +import { projectAPI } from '../services/project'; + +const ProjectForm = ({ onSuccess }) => { + const [form] = Form.useForm(); + const { create, loading } = useCreate(projectAPI.create, onSuccess); + + const handleSubmit = async (values) => { + await create(values); + }; + + return ( + + + + + {/* ...其他表单项 */} + + + + + ); +}; + +export default ProjectForm; +``` + +--- + +**文档维护**: 前端程序员 +**文档类型**: API服务封装文档 +**最后更新**: 2026-01-25 diff --git a/frontend/docs/component-design.md b/frontend/docs/component-design.md new file mode 100644 index 00000000..25fe5aa2 --- /dev/null +++ b/frontend/docs/component-design.md @@ -0,0 +1,735 @@ +# 组件设计文档 + +## 1. 组件分类 + +### 1.1 按功能分类 + +#### 布局组件 (components/Layout/) +- **Layout.jsx**: 主布局容器 +- **Header.jsx**: 顶部导航栏 +- **Sidebar.jsx**: 侧边栏菜单 + +#### 通用组件 (components/Common/) +- **Button.jsx**: 按钮组件 +- **Input.jsx**: 输入框组件 +- **Select.jsx**: 下拉选择框组件 +- **DatePicker.jsx**: 日期选择器组件 +- **Table.jsx**: 表格组件 +- **Modal.jsx**: 弹窗组件 +- **Form.jsx**: 表单组件 +- **Card.jsx**: 卡片组件 +- **Tag.jsx**: 标签组件 +- **Badge.jsx**: 徽章组件 +- **Tooltip.jsx**: 提示框组件 + +#### 业务组件 (components/Business/) +- **ProjectCard.jsx**: 项目卡片 +- **StatCard.jsx**: 统计卡片 +- **UserAvatar.jsx**: 用户头像 +- **ProgressBar.jsx**: 进度条 +- **StatusTag.jsx**: 状态标签 +- **AmountDisplay.jsx**: 金额显示 +- **DateRangePicker.jsx**: 日期范围选择器 + +## 2. 组件设计规范 + +### 2.1 Props命名规范 + +#### 2.1.1 布尔类型 +- 使用 `is` 或 `has` 前缀 +- 示例: `isLoading`, `hasPermission`, `isVisible`, `isRequired` + +#### 2.1.2 事件处理 +- 使用 `on` 前缀 +- 示例: `onClick`, `onChange`, `onSubmit`, `onCancel` + +#### 2.1.3 渲染内容 +- 使用 `render` 前缀或作为 `children` +- 示例: `renderHeader`, `renderFooter`, `children` + +### 2.2 组件Props定义模板 + +```jsx +import React from 'react'; +import PropTypes from 'prop-types'; + +/** + * 按钮组件 + * @param {string} type - 按钮类型:primary/default/danger + * @param {boolean} loading - 是否加载中 + * @param {boolean} disabled - 是否禁用 + * @param {function} onClick - 点击事件 + * @param {node} children - 子元素 + */ +const Button = ({ + type = 'default', + loading = false, + disabled = false, + onClick, + children, + className, + ...rest +}) => { + return ( + + ); +}; + +Button.propTypes = { + type: PropTypes.oneOf(['primary', 'default', 'danger']), + loading: PropTypes.bool, + disabled: PropTypes.bool, + onClick: PropTypes.func, + children: PropTypes.node, + className: PropTypes.string, +}; + +export default Button; +``` + +## 3. 核心组件设计 + +### 3.1 Button(按钮组件) + +```jsx +/** + * 按钮组件 + */ +const Button = ({ + type = 'default', + size = 'medium', + icon, + loading = false, + disabled = false, + block = false, + ghost = false, + danger = false, + onClick, + children, + ...props +}) => { + const handleClick = (e) => { + if (!loading && !disabled && onClick) { + onClick(e); + } + }; + + return ( + + ); +}; +``` + +**Props说明:** +- `type`: 按钮类型 (default/primary/danger) +- `size`: 按钮大小 (small/medium/large) +- `icon`: 图标 +- `loading`: 加载状态 +- `disabled`: 禁用状态 +- `block`: 块级按钮 +- `ghost`: 幽灵按钮 +- `danger`: 危险按钮 + +### 3.2 Table(表格组件) + +```jsx +/** + * 表格组件 + */ +const Table = ({ + columns, + dataSource, + loading = false, + pagination, + rowKey = 'id', + rowSelection, + onRow, + scroll, + ...props +}) => { + return ( + + ); +}; +``` + +**Props说明:** +- `columns`: 列配置 +- `dataSource`: 数据源 +- `loading`: 加载状态 +- `pagination`: 分页配置 +- `rowKey`: 行key +- `rowSelection`: 行选择配置 +- `scroll`: 滚动配置 + +### 3.3 Form(表单组件) + +```jsx +/** + * 表单组件 + */ +const Form = ({ + form, + initialValues, + onFinish, + onFinishFailed, + layout = 'vertical', + children, + ...props +}) => { + const [formInstance] = Form.useForm(); + + const currentForm = form || formInstance; + + return ( + + {children} + + ); +}; +``` + +### 3.4 Modal(弹窗组件) + +```jsx +/** + * 弹窗组件 + */ +const Modal = ({ + visible, + title, + onOk, + onCancel, + okText = '确定', + cancelText = '取消', + confirmLoading = false, + width = 520, + children, + ...props +}) => { + return ( + + {children} + + ); +}; +``` + +### 3.5 Card(卡片组件) + +```jsx +/** + * 卡片组件 + */ +const Card = ({ + title, + extra, + bordered = true, + hoverable = false, + loading = false, + children, + className, + ...props +}) => { + return ( + + {children} + + ); +}; +``` + +### 3.6 Tag(标签组件) + +```jsx +/** + * 标签组件 + */ +const Tag = ({ type = 'default', color, children, ...props }) => { + const colorMap = { + new: 'blue', + inProgress: 'green', + completed: 'default', + paused: 'orange', + cancelled: 'red', + admin: 'red', + market: 'blue', + other: 'cyan', + }; + + return ( + + {children} + + ); +}; +``` + +## 4. 业务组件设计 + +### 4.1 ProjectCard(项目卡片) + +```jsx +/** + * 项目卡片组件 + */ +const ProjectCard = ({ + project, + onView, + onEdit, + onDelete, + showActions = true, +}) => { + const { hasRole } = usePermission(); + + return ( + + {project.name} + + {project.engineering_type} + + + } + extra={ + {project.project_no} + } + className="project-card" + > +
+
+ 合同金额: + +
+
+ 项目负责人: + {project.project_leader} +
+
+ 签订日期: + {project.signing_date} +
+ +
+ + {showActions && ( +
+ + {hasRole(['admin']) || project.created_by === getCurrentUserId() ? ( + <> + + + + ) : null} +
+ )} +
+ ); +}; +``` + +### 4.2 StatCard(统计卡片) + +```jsx +/** + * 统计卡片组件 + */ +const StatCard = ({ title, value, icon, color, prefix, suffix }) => { + return ( + +
+
+
{title}
+
+ {prefix && {prefix}} + {typeof value === 'number' ? value.toLocaleString() : value} + {suffix && {suffix}} +
+
+ {icon && ( +
+
+ {icon} +
+
+ )} +
+
+ ); +}; +``` + +**Props说明:** +- `title`: 标题 +- `value`: 数值 +- `icon`: 图标 +- `color`: 颜色 (blue/green/orange/red/cyan) +- `prefix`: 前缀(如¥) +- `suffix`: 后缀(如%) + +### 4.3 StatusTag(状态标签) + +```jsx +/** + * 状态标签组件 + */ +const StatusTag = ({ status, type = 'project' }) => { + const statusMap = { + project: { + new: { text: '新建', color: 'blue' }, + inProgress: { text: '进行中', color: 'green' }, + completed: { text: '已完成', color: 'default' }, + paused: { text: '已暂停', color: 'orange' }, + cancelled: { text: '已取消', color: 'red' }, + }, + user: { + active: { text: '正常', color: 'green' }, + inactive: { text: '禁用', color: 'default' }, + }, + }; + + const statusInfo = statusMap[type]?.[status] || { text: status, color: 'default' }; + + return {statusInfo.text}; +}; +``` + +### 4.4 AmountDisplay(金额显示) + +```jsx +/** + * 金额显示组件 + */ +const AmountDisplay = ({ value, unit = '万元', precision = 2 }) => { + const formatAmount = (val) => { + if (val === null || val === undefined) return '-'; + return val.toFixed(precision); + }; + + return ( + + {formatAmount(value)} {unit} + + ); +}; +``` + +### 4.5 ProgressBar(进度条) + +```jsx +/** + * 进度条组件 + */ +const ProgressBar = ({ percent, status = 'active', showText = true }) => { + return ( +
+ +
+ ); +}; +``` + +### 4.6 DateRangePicker(日期范围选择器) + +```jsx +/** + * 日期范围选择器组件 + */ +const DateRangePicker = ({ value, onChange, placeholder }) => { + const { RangePicker } = DatePicker; + + return ( + + ); +}; +``` + +## 5. 高阶组件 + +### 5.1 withAuth(认证高阶组件) + +```jsx +/** + * 认证高阶组件 + */ +const withAuth = (WrappedComponent, requiredRoles = []) => { + return (props) => { + const { user, isAuthenticated } = useAuth(); + + if (!isAuthenticated) { + return ; + } + + if (requiredRoles.length > 0 && !requiredRoles.includes(user?.role)) { + return ; + } + + return ; + }; +}; +``` + +### 5.2 withLoading(加载状态高阶组件) + +```jsx +/** + * 加载状态高阶组件 + */ +const withLoading = (WrappedComponent) => { + return ({ loading, ...props }) => { + if (loading) { + return ; + } + + return ; + }; +}; +``` + +## 6. 组件复用策略 + +### 6.1 通过Props自定义 +- 提供灵活的Props接口 +- 支持自定义样式和类名 +- 支持自定义渲染内容 + +### 6.2 通过插槽模式 +- 使用 `children` 传递内容 +- 使用 `render` 属性传递渲染函数 + +### 6.3 通过组合模式 +- 将复杂组件拆分为多个小组件 +- 通过组合实现复杂功能 + +## 7. 组件性能优化 + +### 7.1 React.memo +```jsx +const MemoComponent = React.memo(({ data }) => { + return
{data}
; +}); +``` + +### 7.2 useMemo +```jsx +const expensiveValue = useMemo(() => { + return computeExpensiveValue(a, b); +}, [a, b]); +``` + +### 7.3 useCallback +```jsx +const handleClick = useCallback(() => { + doSomething(a, b); +}, [a, b]); +``` + +### 7.4 虚拟滚动 +对于大数据量的列表,使用虚拟滚动: +```jsx +import { List } from 'react-virtualized'; + + ( +
+ {data[index]} +
+ )} +/> +``` + +## 8. 组件测试 + +### 8.1 单元测试示例 +```jsx +import { render, screen, fireEvent } from '@testing-library/react'; +import Button from './Button'; + +describe('Button Component', () => { + test('renders button with children', () => { + render(); + expect(screen.getByText('Click me')).toBeInTheDocument(); + }); + + test('calls onClick when clicked', () => { + const handleClick = jest.fn(); + render(); + fireEvent.click(screen.getByText('Click')); + expect(handleClick).toHaveBeenCalledTimes(1); + }); + + test('shows loading state', () => { + render(); + expect(screen.getByText('Click')).toBeDisabled(); + }); +}); +``` + +## 9. 组件文档 + +### 9.1 Storybook(可选) +使用Storybook进行组件文档和展示: + +```jsx +// stories/Button.stories.jsx +import Button from '../components/Common/Button'; + +export default { + title: 'Components/Button', + component: Button, +}; + +const Template = (args) => ; +}; + +// 不推荐:在JSX中直接定义函数 +const MyComponent = () => { + return ; +}; +``` + +## 5. Hooks使用规范 + +### 5.1 useState + +```jsx +// 基本用法 +const [count, setCount] = useState(0); + +// 函数式更新 +const [count, setCount] = useState(0); +const increment = () => setCount(prev => prev + 1); + +// 对象更新 +const [user, setUser] = useState({ name: '', age: 0 }); +const updateName = (name) => setUser(prev => ({ ...prev, name })); +``` + +### 5.2 useEffect + +```jsx +// 副作用 +useEffect(() => { + // 执行副作用 + return () => { + // 清理函数 + }; +}, [dependencies]); + +// 空依赖数组:只在挂载时执行一次 +useEffect(() => { + console.log('Component mounted'); +}, []); + +// 依赖数组为空时,不要在依赖数组中省略 +// 错误示例 +useEffect(() => { + fetchUser(userId); +}, []); // eslint-disable-line react-hooks/exhaustive-deps + +// 正确示例 +useEffect(() => { + fetchUser(userId); +}, [userId]); +``` + +### 5.3 useMemo和useCallback + +```jsx +// useMemo:缓存计算结果 +const expensiveValue = useMemo(() => { + return computeExpensiveValue(a, b); +}, [a, b]); + +// useCallback:缓存函数 +const handleClick = useCallback(() => { + doSomething(a, b); +}, [a, b]); +``` + +### 5.4 自定义Hooks + +```jsx +// 自定义Hook以use开头 +const useFetch = (url) => { + const [data, setData] = useState(null); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + + useEffect(() => { + const fetchData = async () => { + setLoading(true); + try { + const response = await fetch(url); + setData(await response.json()); + } catch (err) { + setError(err); + } finally { + setLoading(false); + } + }; + + fetchData(); + }, [url]); + + return { data, loading, error }; +}; +``` + +## 6. 样式开发规范 + +### 6.1 CSS模块 + +```jsx +// MyComponent.jsx +import styles from './MyComponent.module.css'; + +const MyComponent = () => { + return
Content
; +}; +``` + +### 6.2 CSS-in-JS(可选) + +```jsx +// 使用styled-components或emotion +import styled from 'styled-components'; + +const StyledDiv = styled.div` + padding: 20px; + background: #f0f0f0; + + &:hover { + background: #e0e0e0; + } +`; + +const MyComponent = () => { + return Content; +}; +``` + +### 6.3 Tailwind CSS(可选) + +```jsx +// 使用Tailwind CSS工具类 +const MyComponent = () => { + return ( +
+ Content +
+ ); +}; +``` + +## 7. 状态管理规范 + +### 7.1 使用Context API + +```jsx +// Context定义 +import { createContext, useContext } from 'react'; + +const MyContext = createContext(); + +export const MyProvider = ({ children }) => { + const [state, setState] = useState(initialState); + + return ( + + {children} + + ); +}; + +export const useMyContext = () => { + const context = useContext(MyContext); + if (!context) { + throw new Error('useMyContext must be used within MyProvider'); + } + return context; +}; +``` + +### 7.2 状态分类 + +```jsx +// 1. 本地状态:使用useState +const [value, setValue] = useState(''); + +// 2. 上下文状态:使用Context +const { user, setUser } = useAuth(); + +// 3. 表单状态:使用Ant Design Form +const [form] = Form.useForm(); + +// 4. URL状态:使用useSearchParams +const [searchParams, setSearchParams] = useSearchParams(); +``` + +## 8. 路由开发规范 + +### 8.1 使用React Router + +```jsx +import { Routes, Route, Navigate } from 'react-router-dom'; + +const Router = () => { + return ( + + } /> + } /> + } /> + } /> + + ); +}; +``` + +### 8.2 路由参数 + +```jsx +import { useParams, useSearchParams } from 'react-router-dom'; + +const ProjectDetail = () => { + const { id } = useParams(); + const [searchParams] = useSearchParams(); + + const keyword = searchParams.get('keyword') || ''; + + return
Project ID: {id}
; +}; +``` + +### 8.3 路由守卫 + +```jsx +const PrivateRoute = ({ children }) => { + const { isAuthenticated } = useAuth(); + + if (!isAuthenticated) { + return ; + } + + return children; +}; +``` + +## 9. API调用规范 + +### 9.1 使用Axios + +```jsx +import { useEffect } from 'react'; +import { projectAPI } from '../services/project'; + +const ProjectList = () => { + const [list, setList] = useState([]); + const [loading, setLoading] = useState(false); + + useEffect(() => { + const fetchList = async () => { + setLoading(true); + try { + const data = await projectAPI.getList(); + setList(data.items); + } catch (error) { + console.error('Fetch error:', error); + } finally { + setLoading(false); + } + }; + + fetchList(); + }, []); + + return
{/* 渲染列表 */}
; +}; +``` + +### 9.2 使用自定义Hooks + +```jsx +import { useList } from '../hooks/useList'; +import { projectAPI } from '../services/project'; + +const ProjectList = () => { + const { list, loading, pagination, handleTableChange } = useList( + projectAPI.getList + ); + + return
; +}; +``` + +## 10. 错误处理规范 + +### 10.1 错误边界 + +```jsx +class ErrorBoundary extends React.Component { + state = { hasError: false, error: null }; + + static getDerivedStateFromError(error) { + return { hasError: true, error }; + } + + componentDidCatch(error, errorInfo) { + console.error('Error:', error, errorInfo); + } + + render() { + if (this.state.hasError) { + return
Something went wrong.
; + } + + return this.props.children; + } +} + +// 使用 + + + +``` + +### 10.2 错误处理组件 + +```jsx +const ErrorFallback = ({ error, resetErrorBoundary }) => { + return ( +
+

Something went wrong:

+
{error.message}
+ +
+ ); +}; +``` + +## 11. 性能优化 + +### 11.1 代码分割 + +```jsx +import { lazy, Suspense } from 'react'; + +const ProjectList = lazy(() => import('./pages/Projects/ProjectList')); +const ProjectDetail = lazy(() => import('./pages/Projects/ProjectDetail')); + +const App = () => { + return ( + }> + + } /> + } /> + + + ); +}; +``` + +### 11.2 React.memo + +```jsx +const ExpensiveComponent = React.memo(({ data }) => { + // 渲染逻辑 +}); + +// 自定义比较函数 +const MyComponent = React.memo(({ a, b }) => { + // 渲染逻辑 +}, (prevProps, nextProps) => { + return prevProps.a === nextProps.a && prevProps.b === nextProps.b; +}); +``` + +### 11.3 useMemo和useCallback + +```jsx +const MyComponent = ({ data, onUpdate }) => { + // 缓存计算结果 + const sortedData = useMemo(() => { + return data.sort((a, b) => a.id - b.id); + }, [data]); + + // 缓存函数 + const handleClick = useCallback(() => { + onUpdate(sortedData); + }, [onUpdate, sortedData]); + + return
Content
; +}; +``` + +## 12. 测试规范 + +### 12.1 组件测试 + +```jsx +import { render, screen, fireEvent } from '@testing-library/react'; +import Button from './Button'; + +describe('Button Component', () => { + test('renders button with children', () => { + render(); + expect(screen.getByText('Click me')).toBeInTheDocument(); + }); + + test('calls onClick when clicked', () => { + const handleClick = jest.fn(); + render(); + fireEvent.click(screen.getByText('Click')); + expect(handleClick).toHaveBeenCalledTimes(1); + }); +}); +``` + +### 12.2 Hooks测试 + +```jsx +import { renderHook, act } from '@testing-library/react'; +import { useCounter } from './useCounter'; + +describe('useCounter', () => { + test('should increment counter', () => { + const { result } = renderHook(() => useCounter()); + + expect(result.current.count).toBe(0); + + act(() => { + result.current.increment(); + }); + + expect(result.current.count).toBe(1); + }); +}); +``` + +### 12.3 集成测试 + +```jsx +import { render, screen, fireEvent, waitFor } from '@testing-library/react'; +import { BrowserRouter } from 'react-router-dom'; +import App from './App'; + +const renderWithRouter = (component) => { + return render({component}); +}; + +test('user can login', async () => { + renderWithRouter(); + + fireEvent.change(screen.getByLabelText('Username'), { + target: { value: 'admin' }, + }); + fireEvent.change(screen.getByLabelText('Password'), { + target: { value: 'password' }, + }); + fireEvent.click(screen.getByText('Login')); + + await waitFor(() => { + expect(screen.getByText('Dashboard')).toBeInTheDocument(); + }); +}); +``` + +## 13. 调试技巧 + +### 13.1 React DevTools + +```jsx +// 安装React DevTools浏览器扩展 +// 可以查看组件树、props、state、Hooks等 +``` + +### 13.2 Console日志 + +```jsx +// 开发环境下的调试日志 +if (import.meta.env.DEV) { + console.log('Debug:', { data, loading }); +} + +// 生产环境下自动移除 +``` + +### 13.3 性能分析 + +```jsx +// 使用React Profiler分析性能 +import { Profiler } from 'react'; + +const onRenderCallback = (id, phase, actualDuration) => { + console.log(`${id} ${phase} took ${actualDuration}ms`); +}; + + + + +``` + +## 14. 部署规范 + +### 14.1 构建配置 + +```javascript +// vite.config.js +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; + +export default defineConfig({ + plugins: [react()], + base: '/app/', // 基础路径 + build: { + outDir: 'dist', + sourcemap: false, // 生产环境关闭sourcemap + chunkSizeWarningLimit: 1000, + }, + server: { + port: 3000, + proxy: { + '/api': { + target: 'http://localhost:5000', + changeOrigin: true, + }, + }, + }, +}); +``` + +### 14.2 环境变量 + +```bash +# .env.development +VITE_API_BASE_URL=http://localhost:5000/api/v1 + +# .env.production +VITE_API_BASE_URL=https://api.example.com/api/v1 +``` + +### 14.3 Docker部署 + +```dockerfile +# Dockerfile +FROM node:18-alpine as builder +WORKDIR /app +COPY package*.json ./ +RUN npm ci +COPY . . +RUN npm run build + +FROM nginx:alpine +COPY --from=builder /app/dist /usr/share/nginx/html +COPY nginx.conf /etc/nginx/conf.d/default.conf +EXPOSE 80 +CMD ["nginx", "-g", "daemon off;"] +``` + +## 15. 最佳实践 + +### 15.1 组件设计 + +- 单一职责原则:每个组件只做一件事 +- 组件大小控制在300行以内 +- 合理拆分组件,提高复用性 +- 使用组合而非继承 + +### 15.2 性能优化 + +- 使用React.memo避免不必要的渲染 +- 使用useMemo缓存计算结果 +- 使用useCallback缓存函数 +- 使用懒加载减少初始加载时间 + +### 15.3 代码质量 + +- 遵循ESLint和Prettier规范 +- 编写单元测试 +- 添加必要的注释 +- 保持代码简洁清晰 + +### 15.4 安全性 + +- 避免直接渲染用户输入 +- 使用HTTPS +- Token存储安全 +- 设置适当的CORS策略 + +## 16. 常见问题 + +### 16.1 如何处理跨域? + +```javascript +// vite.config.js +export default defineConfig({ + server: { + proxy: { + '/api': { + target: 'http://localhost:5000', + changeOrigin: true, + }, + }, + }, +}); +``` + +### 16.2 如何优化首屏加载? + +```jsx +// 使用路由懒加载 +const Dashboard = lazy(() => import('./pages/Dashboard')); + +// 使用Suspense包裹 +}> + + +``` + +### 16.3 如何处理表单验证? + +```jsx +import { Form, Input, Button } from 'antd'; + +const MyForm = () => { + const [form] = Form.useForm(); + + const onFinish = (values) => { + console.log('Form values:', values); + }; + + return ( +
+ + + + + + + + ); +}; +``` + +--- + +**文档维护**: 前端程序员 +**文档类型**: 前端开发指南 +**最后更新**: 2026-01-25 diff --git a/frontend/docs/frontend-architecture.md b/frontend/docs/frontend-architecture.md new file mode 100644 index 00000000..9a33e76f --- /dev/null +++ b/frontend/docs/frontend-architecture.md @@ -0,0 +1,687 @@ +# 海洋项目管理系统 - 前端技术架构文档 + +## 1. 前端技术栈 + +### 1.1 核心框架 +- **React 18**: 用于构建用户界面的JavaScript库 +- **React Router 6**: 客户端路由管理 +- **Vite**: 现代化的前端构建工具(替代Create React App) + +### 1.2 UI组件库 +- **Ant Design 5**: 企业级UI组件库 +- **@ant-design/icons**: Ant Design图标库 + +### 1.3 HTTP客户端 +- **Axios**: HTTP请求库 + +### 1.4 状态管理 +- **React Context API**: 轻量级状态管理 +- **useReducer Hook**: 复杂状态逻辑 + +### 1.5 表单处理 +- **Ant Design Form**: 表单组件和验证 +- **react-hook-form**: 表单状态管理(可选) + +### 1.6 工具库 +- **dayjs**: 日期处理(轻量级替代moment.js) +- **lodash**: 工具函数库 +- **classnames**: 条件类名处理 + +### 1.7 开发工具 +- **ESLint**: 代码质量检查 +- **Prettier**: 代码格式化 +- **Vitest**: 单元测试框架 +- **React Testing Library**: 组件测试 +- **TypeScript**: 类型检查(可选,当前使用JavaScript) + +### 1.8 构建和部署 +- **Vite**: 开发服务器和构建工具 +- **Docker**: 容器化部署(生产环境) + +## 2. 前端目录结构 + +``` +frontend/ +├── src/ # 源代码目录 +│ ├── assets/ # 静态资源 +│ │ ├── images/ # 图片 +│ │ ├── fonts/ # 字体文件 +│ │ └── styles/ # 全局样式 +│ │ ├── global.css # 全局CSS +│ │ ├── variables.css # CSS变量 +│ │ └── mixins.css # CSS mixins +│ ├── components/ # 可复用组件 +│ │ ├── Layout/ # 布局组件 +│ │ │ ├── Layout.jsx # 主布局 +│ │ │ ├── Header.jsx # 顶部导航 +│ │ │ └── Sidebar.jsx # 侧边栏 +│ │ ├── Common/ # 通用组件 +│ │ │ ├── Button.jsx # 按钮组件 +│ │ │ ├── Input.jsx # 输入框组件 +│ │ │ ├── Select.jsx # 选择框组件 +│ │ │ ├── Table.jsx # 表格组件 +│ │ │ ├── Modal.jsx # 弹窗组件 +│ │ │ ├── Form.jsx # 表单组件 +│ │ │ ├── Card.jsx # 卡片组件 +│ │ │ └── Tag.jsx # 标签组件 +│ │ ├── Business/ # 业务组件 +│ │ │ ├── ProjectCard.jsx # 项目卡片 +│ │ │ ├── StatCard.jsx # 统计卡片 +│ │ │ └── UserAvatar.jsx # 用户头像 +│ │ └── index.js # 组件导出 +│ ├── pages/ # 页面组件 +│ │ ├── Login/ # 登录页 +│ │ │ └── Login.jsx +│ │ ├── Dashboard/ # 仪表盘 +│ │ │ └── Dashboard.jsx +│ │ ├── Projects/ # 项目管理 +│ │ │ ├── ProjectList.jsx # 项目列表 +│ │ │ ├── ProjectForm.jsx # 项目表单 +│ │ │ ├── ProjectDetail.jsx # 项目详情 +│ │ │ └── ProjectStatistics.jsx # 项目统计 +│ │ ├── Users/ # 用户管理(仅管理员) +│ │ │ ├── UserList.jsx # 用户列表 +│ │ │ ├── UserForm.jsx # 用户表单 +│ │ │ └── UserResetPassword.jsx # 重置密码 +│ │ └── Statistics/ # 统计分析 +│ │ └── Statistics.jsx +│ ├── contexts/ # Context上下文 +│ │ ├── AuthContext.jsx # 认证上下文 +│ │ └── ThemeContext.jsx # 主题上下文(可选) +│ ├── hooks/ # 自定义Hooks +│ │ ├── useAuth.js # 认证Hook +│ │ ├── useApi.js # API调用Hook +│ │ ├── usePermission.js # 权限检查Hook +│ │ ├── useTable.js # 表格Hook +│ │ ├── useForm.js # 表单Hook +│ │ └── useDebounce.js # 防抖Hook +│ ├── services/ # API服务 +│ │ ├── api.js # Axios配置 +│ │ ├── auth.js # 认证API +│ │ ├── user.js # 用户API +│ │ ├── project.js # 项目API +│ │ └── statistics.js # 统计API +│ ├── utils/ # 工具函数 +│ │ ├── request.js # 请求封装 +│ │ ├── storage.js # 本地存储 +│ │ ├── auth.js # 认证工具 +│ │ ├── format.js # 格式化函数 +│ │ ├── validation.js # 验证函数 +│ │ └── constants.js # 常量定义 +│ ├── config/ # 配置文件 +│ │ ├── routes.js # 路由配置 +│ │ ├── menu.js # 菜单配置 +│ │ ├── colors.js # 颜色配置 +│ │ └── app.config.js # 应用配置 +│ ├── router/ # 路由相关 +│ │ ├── index.jsx # 路由配置 +│ │ ├── PrivateRoute.jsx # 路由守卫 +│ │ └── routes.js # 路由定义 +│ ├── styles/ # 样式文件 +│ │ └── index.css # 主样式文件 +│ ├── App.jsx # 根组件 +│ └── main.jsx # 应用入口 +├── tests/ # 测试目录 +│ ├── components/ # 组件测试 +│ ├── pages/ # 页面测试 +│ ├── hooks/ # Hooks测试 +│ └── utils/ # 工具测试 +├── public/ # 公共资源 +│ ├── favicon.ico # 网站图标 +│ ├── logo.png # Logo +│ └── robots.txt # 爬虫配置 +├── docs/ # 前端文档 +│ ├── frontend-architecture.md # 前端技术架构(本文档) +│ ├── component-design.md # 组件设计文档 +│ ├── api-service.md # API服务封装 +│ ├── state-management.md # 状态管理设计 +│ ├── routing-design.md # 路由设计文档 +│ └── development-guide.md # 开发指南 +├── .env # 环境变量 +├── .env.development # 开发环境变量 +├── .env.production # 生产环境变量 +├── .eslintrc.js # ESLint配置 +├── .prettierrc # Prettier配置 +├── package.json # 项目依赖 +├── vite.config.js # Vite配置 +├── index.html # HTML模板 +├── WORKSTANDARDS.md # 前端工作规范 +└── README.md # 前端说明文档 +``` + +## 3. 组件架构 + +### 3.1 组件层级 + +``` +App +├── AuthProvider (认证上下文) +├── Router (路由) +│ ├── PrivateRoute (路由守卫) +│ ├── Login (登录页) +│ └── Layout (主布局) +│ ├── Header (顶部导航) +│ ├── Sidebar (侧边栏) +│ └── Content (内容区) +│ ├── Dashboard (仪表盘) +│ ├── ProjectList (项目列表) +│ ├── ProjectForm (项目表单) +│ ├── ProjectDetail (项目详情) +│ ├── UserList (用户列表) +│ ├── UserForm (用户表单) +│ └── Statistics (统计页) +``` + +### 3.2 组件设计原则 + +#### 3.2.1 单一职责原则 +- 每个组件只负责一个功能 +- 避免组件过大,合理拆分 + +#### 3.2.2 组件复用性 +- 提取可复用的通用组件到 `components/Common/` +- 提取业务相关组件到 `components/Business/` + +#### 3.2.3 组件分类 +- **布局组件**: Layout, Header, Sidebar +- **通用组件**: Button, Input, Form, Modal, Table +- **业务组件**: ProjectCard, StatCard, UserAvatar +- **页面组件**: Login, Dashboard, ProjectList等 + +#### 3.2.4 组件Props规范 +```jsx +// 推荐的Props定义方式 +const MyComponent = ({ + title, // 必填props + value, // 可选props + onChange, // 事件回调 + className, // 样式类名 + style, // 内联样式 + children, // 子元素 +}) => { + return
{title}
; +}; + +MyComponent.propTypes = { + title: PropTypes.string.isRequired, + value: PropTypes.string, + onChange: PropTypes.func, + className: PropTypes.string, + style: PropTypes.object, + children: PropTypes.node, +}; + +MyComponent.defaultProps = { + value: '', + onChange: () => {}, + className: '', + style: {}, +}; +``` + +## 4. 状态管理 + +### 4.1 Context API方案 + +#### 4.1.1 AuthContext(认证上下文) +```javascript +// contexts/AuthContext.jsx +const AuthContext = createContext(); + +export const AuthProvider = ({ children }) => { + const [user, setUser] = useState(null); + const [token, setToken] = useState(null); + const [loading, setLoading] = useState(true); + + // 登录 + const login = async (username, password) => { + // 登录逻辑 + }; + + // 登出 + const logout = () => { + // 登出逻辑 + }; + + // 检查登录状态 + const checkAuth = async () => { + // 检查逻辑 + }; + + return ( + + {children} + + ); +}; +``` + +#### 4.1.2 ThemeContext(主题上下文,可选) +```javascript +// contexts/ThemeContext.jsx +const ThemeContext = createContext(); + +export const ThemeProvider = ({ children }) => { + const [theme, setTheme] = useState('light'); + + const toggleTheme = () => { + setTheme(prev => prev === 'light' ? 'dark' : 'light'); + }; + + return ( + + {children} + + ); +}; +``` + +### 4.2 本地状态管理 +- 使用 `useState` 管理组件内部状态 +- 使用 `useReducer` 管理复杂的状态逻辑 + +### 4.3 表单状态管理 +- 使用 Ant Design Form 组件 +- 使用 `react-hook-form`(可选) + +## 5. 路由设计 + +### 5.1 路由配置 + +```javascript +// router/routes.js +const routes = [ + { + path: '/login', + element: , + meta: { title: '登录', requiresAuth: false }, + }, + { + path: '/', + element: , + meta: { title: '主页', requiresAuth: true }, + children: [ + { + path: '/dashboard', + element: , + meta: { title: '仪表盘', icon: 'DashboardOutlined' }, + }, + { + path: '/projects', + element: , + meta: { title: '项目管理', icon: 'FileTextOutlined' }, + }, + { + path: '/projects/create', + element: , + meta: { title: '创建项目' }, + }, + { + path: '/projects/:id', + element: , + meta: { title: '项目详情' }, + }, + { + path: '/projects/:id/edit', + element: , + meta: { title: '编辑项目' }, + }, + { + path: '/users', + element: , + meta: { title: '用户管理', icon: 'UserOutlined', requiresRole: ['admin'] }, + }, + { + path: '/users/create', + element: , + meta: { title: '创建用户', requiresRole: ['admin'] }, + }, + { + path: '/statistics', + element: , + meta: { title: '统计分析', icon: 'BarChartOutlined' }, + }, + ], + }, +]; +``` + +### 5.2 路由守卫 + +```javascript +// router/PrivateRoute.jsx +const PrivateRoute = ({ children, requiresAuth, requiresRole }) => { + const { isAuthenticated, user } = useAuth(); + const navigate = useNavigate(); + + if (requiresAuth && !isAuthenticated) { + return ; + } + + if (requiresRole && !requiresRole.includes(user?.role)) { + return ; + } + + return children; +}; +``` + +## 6. API服务封装 + +### 6.1 Axios配置 + +```javascript +// services/api.js +import axios from 'axios'; +import { message } from 'antd'; + +const api = axios.create({ + baseURL: import.meta.env.VITE_API_BASE_URL || 'http://localhost:5000/api/v1', + timeout: 10000, + headers: { + 'Content-Type': 'application/json', + }, +}); + +// 请求拦截器 +api.interceptors.request.use( + (config) => { + const token = localStorage.getItem('token'); + if (token) { + config.headers.Authorization = `Bearer ${token}`; + } + return config; + }, + (error) => { + return Promise.reject(error); + } +); + +// 响应拦截器 +api.interceptors.response.use( + (response) => { + return response.data; + }, + (error) => { + if (error.response) { + const { status, data } = error.response; + + switch (status) { + case 401: + message.error('登录已过期,请重新登录'); + localStorage.removeItem('token'); + window.location.href = '/login'; + break; + case 403: + message.error('权限不足'); + break; + case 404: + message.error('请求的资源不存在'); + break; + case 500: + message.error('服务器错误'); + break; + default: + message.error(data.message || '请求失败'); + } + } else { + message.error('网络错误,请检查网络连接'); + } + + return Promise.reject(error); + } +); + +export default api; +``` + +### 6.2 API模块化 + +```javascript +// services/project.js +import api from './api'; + +export const projectAPI = { + // 获取项目列表 + getList: (params) => api.get('/projects', { params }), + + // 获取项目详情 + getDetail: (id) => api.get(`/projects/${id}`), + + // 创建项目 + create: (data) => api.post('/projects', data), + + // 更新项目 + update: (id, data) => api.put(`/projects/${id}`, data), + + // 删除项目 + delete: (id) => api.delete(`/projects/${id}`), + + // 获取统计信息 + getStatistics: (params) => api.get('/projects/statistics', { params }), + + // 获取分组统计 + getGroupStatistics: (params) => api.get('/projects/statistics/group', { params }), + + // 获取时间维度统计 + getTimelineStatistics: (params) => api.get('/projects/statistics/timeline', { params }), +}; +``` + +## 7. 权限控制 + +### 7.1 路由级权限 +- 使用 `PrivateRoute` 组件检查认证状态 +- 使用 `requiresRole` 属性检查角色权限 + +### 7.2 组件级权限 +```javascript +// hooks/usePermission.js +export const usePermission = () => { + const { user } = useAuth(); + + const hasRole = (roles) => { + if (!user) return false; + return roles.includes(user.role); + }; + + const canEdit = (resource, createdBy) => { + if (!user) return false; + if (user.role === 'admin') return true; + if (user.role === 'market' && createdBy === user.id) return true; + return false; + }; + + return { hasRole, canEdit }; +}; +``` + +### 7.3 按钮级权限 +```javascript +// 组件中使用 +const { hasRole } = usePermission(); + +{hasRole(['admin', 'market']) && ( + +)} +``` + +## 8. 性能优化 + +### 8.1 代码分割 +```javascript +import { lazy, Suspense } from 'react'; + +const ProjectList = lazy(() => import('./pages/Projects/ProjectList')); +const ProjectDetail = lazy(() => import('./pages/Projects/ProjectDetail')); + +}> + + +``` + +### 8.2 图片优化 +- 使用懒加载 +- 压缩图片文件 +- 使用WebP格式 + +### 8.3 请求优化 +- 防抖处理搜索请求 +- 取消重复请求 +- 使用缓存策略 + +### 8.4 渲染优化 +- 使用 `React.memo` 避免不必要的重渲染 +- 使用 `useMemo` 缓存计算结果 +- 使用 `useCallback` 缓存函数引用 + +## 9. 前端安全 + +### 9.1 XSS防护 +- React自动转义HTML +- 避免使用 `dangerouslySetInnerHTML` +- 输入验证和过滤 + +### 9.2 CSRF防护 +- 使用Cookie SameSite属性 +- 添加CSRF Token(可选) + +### 9.3 敏感信息保护 +- Token存储在LocalStorage(可改为HttpOnly Cookie) +- 不在URL中传递敏感信息 +- 使用HTTPS(生产环境) + +### 9.4 内容安全策略(CSP) +- 设置CSP头部 +- 限制外部资源加载 + +## 10. 开发规范 + +### 10.1 代码风格 +- 使用ESLint进行代码检查 +- 使用Prettier进行代码格式化 +- 遵循Airbnb JavaScript风格指南 + +### 10.2 命名规范 +- 组件名使用PascalCase(如 `ProjectList`) +- 函数和变量使用camelCase(如 `getUserById`) +- 常量使用UPPER_SNAKE_CASE(如 `API_BASE_URL`) +- CSS类名使用kebab-case(如 `project-card`) + +### 10.3 注释规范 +```javascript +/** + * 获取项目列表 + * @param {Object} params - 查询参数 + * @param {number} params.page - 页码 + * @param {number} params.pageSize - 每页数量 + * @returns {Promise} 项目列表数据 + */ +const getProjectList = async (params) => { + // 实现代码 +}; +``` + +### 10.4 Git提交规范 +``` +[frontend] feat: 添加项目列表页面 +[frontend] fix: 修复登录bug +[frontend] style: 优化代码格式 +[frontend] refactor: 重构API服务 +[frontend] test: 添加组件测试 +[frontend] docs: 更新文档 +[frontend] chore: 更新依赖 +``` + +## 11. 测试策略 + +### 11.1 单元测试 +- 测试工具函数和自定义Hooks +- 使用Vitest测试框架 + +### 11.2 组件测试 +- 测试组件渲染和交互 +- 使用React Testing Library + +### 11.3 集成测试 +- 测试页面和路由 +- 测试API调用 + +### 11.4 E2E测试(可选) +- 测试完整用户流程 +- 使用Playwright + +## 12. 部署方案 + +### 12.1 开发环境 +```bash +npm run dev +# 访问 http://localhost:3000 +``` + +### 12.2 生产构建 +```bash +npm run build +# 生成 dist 目录 +``` + +### 12.3 Docker部署 +```dockerfile +# Dockerfile +FROM node:18-alpine as builder +WORKDIR /app +COPY package*.json ./ +RUN npm ci +COPY . . +RUN npm run build + +FROM nginx:alpine +COPY --from=builder /app/dist /usr/share/nginx/html +COPY nginx.conf /etc/nginx/conf.d/default.conf +EXPOSE 80 +CMD ["nginx", "-g", "daemon off;"] +``` + +### 12.4 Nginx配置 +```nginx +server { + listen 80; + server_name localhost; + + location / { + root /usr/share/nginx/html; + index index.html; + try_files $uri $uri/ /index.html; + } + + location /api { + proxy_pass http://backend:5000; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + } +} +``` + +## 13. 相关文档 + +- [组件设计文档](component-design.md) - 详细的组件设计规范 +- [API服务封装](api-service.md) - API服务封装详解 +- [状态管理设计](state-management.md) - 状态管理方案详解 +- [路由设计文档](routing-design.md) - 路由设计详解 +- [开发指南](development-guide.md) - 前端开发指南 + +--- + +**文档维护**: 前端程序员 +**文档类型**: 前端技术架构文档 +**最后更新**: 2026-01-25 diff --git a/frontend/docs/routing-design.md b/frontend/docs/routing-design.md new file mode 100644 index 00000000..918e6cc3 --- /dev/null +++ b/frontend/docs/routing-design.md @@ -0,0 +1,879 @@ +# 路由设计文档 + +## 1. 路由架构 + +### 1.1 路由结构 + +``` +┌─────────────────────────────────────────┐ +│ App Root │ +├─────────────────────────────────────────┤ +│ Router (React Router v6) │ +│ ├─ /login (登录页) │ +│ ├─ /403 (无权限页) │ +│ ├─ /404 (404页) │ +│ ├─ / (主布局) │ +│ │ ├─ /dashboard (仪表盘) │ +│ │ ├─ /projects (项目列表) │ +│ │ ├─ /projects/create (创建项目) │ +│ │ ├─ /projects/:id (项目详情) │ +│ │ ├─ /projects/:id/edit (编辑项目) │ +│ │ ├─ /users (用户列表) [admin] │ +│ │ ├─ /users/create (创建用户) [admin] │ +│ │ ├─ /users/:id (用户详情) [admin] │ +│ │ └─ /statistics (统计分析) │ +└─────────────────────────────────────────┘ +``` + +### 1.2 路由配置 + +```javascript +// router/routes.js +const routes = [ + { + path: '/login', + element: , + meta: { + title: '登录', + requiresAuth: false, + hideInMenu: true, + }, + }, + { + path: '/403', + element: , + meta: { + title: '无权限', + requiresAuth: false, + hideInMenu: true, + }, + }, + { + path: '/404', + element: , + meta: { + title: '页面不存在', + requiresAuth: false, + hideInMenu: true, + }, + }, + { + path: '/', + element: , + meta: { + title: '主页', + requiresAuth: true, + }, + children: [ + { + path: '/dashboard', + element: , + meta: { + title: '仪表盘', + icon: 'DashboardOutlined', + order: 1, + }, + }, + { + path: '/projects', + element: , + meta: { + title: '项目管理', + icon: 'FileTextOutlined', + order: 2, + }, + }, + { + path: '/projects/create', + element: , + meta: { + title: '创建项目', + parent: '/projects', + hideInMenu: true, + }, + }, + { + path: '/projects/:id', + element: , + meta: { + title: '项目详情', + parent: '/projects', + hideInMenu: true, + }, + }, + { + path: '/projects/:id/edit', + element: , + meta: { + title: '编辑项目', + parent: '/projects', + hideInMenu: true, + }, + }, + { + path: '/users', + element: , + meta: { + title: '用户管理', + icon: 'UserOutlined', + order: 3, + requiresRole: ['admin'], + }, + }, + { + path: '/users/create', + element: , + meta: { + title: '创建用户', + parent: '/users', + requiresRole: ['admin'], + hideInMenu: true, + }, + }, + { + path: '/users/:id', + element: , + meta: { + title: '用户详情', + parent: '/users', + requiresRole: ['admin'], + hideInMenu: true, + }, + }, + { + path: '/users/:id/edit', + element: , + meta: { + title: '编辑用户', + parent: '/users', + requiresRole: ['admin'], + hideInMenu: true, + }, + }, + { + path: '/statistics', + element: , + meta: { + title: '统计分析', + icon: 'BarChartOutlined', + order: 4, + }, + }, + ], + }, +]; + +export default routes; +``` + +## 2. 路由守卫 + +### 2.1 PrivateRoute组件 + +```javascript +// router/PrivateRoute.jsx +import { Navigate, useLocation } from 'react-router-dom'; +import { useAuth } from '../contexts/AuthContext'; + +/** + * 路由守卫组件 + */ +const PrivateRoute = ({ children, meta = {} }) => { + const { isAuthenticated, user, loading } = useAuth(); + const location = useLocation(); + + const { requiresAuth = true, requiresRole = [] } = meta; + + // 检查加载状态 + if (loading) { + return ; + } + + // 检查认证状态 + if (requiresAuth && !isAuthenticated) { + return ; + } + + // 检查角色权限 + if (requiresRole.length > 0 && !requiresRole.includes(user?.role)) { + return ; + } + + return children; +}; + +export default PrivateRoute; +``` + +### 2.2 使用路由守卫 + +```javascript +// router/index.jsx +import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom'; +import PrivateRoute from './PrivateRoute'; +import routes from './routes'; + +const Router = () => { + return ( + + + {routes.map((route) => { + const { path, element, meta, children } = route; + + if (children) { + return ( + + {element} + + } + > + {children.map((child) => ( + + {child.element} + + } + /> + ))} + + ); + } + + return ( + + {element} + + } + /> + ); + })} + + {/* 默认重定向 */} + } /> + + {/* 404页面 */} + } /> + + + ); +}; + +export default Router; +``` + +## 3. 路由参数处理 + +### 3.1 useParams Hook + +```jsx +import { useParams } from 'react-router-dom'; + +const ProjectDetail = () => { + const { id } = useParams(); + const { data, loading } = useApi(() => projectAPI.getDetail(id)); + + if (loading) return ; + return
{/* 项目详情 */}
; +}; +``` + +### 3.2 useSearchParams Hook + +```jsx +import { useSearchParams } from 'react-router-dom'; +import { useDebounce } from '../hooks/useDebounce'; + +const ProjectList = () => { + const [searchParams, setSearchParams] = useSearchParams(); + const keyword = searchParams.get('keyword') || ''; + const debouncedKeyword = useDebounce(keyword, 300); + + // 搜索逻辑 + useEffect(() => { + fetchList({ keyword: debouncedKeyword }); + }, [debouncedKeyword]); + + const handleSearch = (value) => { + setSearchParams({ keyword: value }); + }; + + return handleSearch(e.target.value)} />; +}; +``` + +### 3.3 useLocation Hook + +```jsx +import { useLocation } from 'react-router-dom'; + +const ProjectForm = () => { + const location = useLocation(); + const isEditMode = location.pathname.includes('/edit'); + + return
{isEditMode ? '编辑模式' : '创建模式'}
; +}; +``` + +### 3.4 useNavigate Hook + +```jsx +import { useNavigate } from 'react-router-dom'; + +const ProjectList = () => { + const navigate = useNavigate(); + + const handleCreate = () => { + navigate('/projects/create'); + }; + + const handleEdit = (id) => { + navigate(`/projects/${id}/edit`); + }; + + const handleBack = () => { + navigate(-1); // 返回上一页 + }; + + return ( +
+ + + +
+ ); +}; +``` + +## 4. 路由元信息 + +### 4.1 路由元信息定义 + +```javascript +// router/routes.js + +const routeMeta = { + // 页面标题 + title: '项目管理', + + // 菜单图标 + icon: 'FileTextOutlined', + + // 菜单排序 + order: 1, + + // 是否需要认证 + requiresAuth: true, + + // 允许的角色 + requiresRole: ['admin', 'market'], + + // 父路由 + parent: '/projects', + + // 是否在菜单中隐藏 + hideInMenu: false, + + // 是否在面包屑中隐藏 + hideInBreadcrumb: false, + + // 缓存配置 + keepAlive: false, +}; +``` + +### 4.2 使用路由元信息 + +```javascript +// hooks/useRouteMeta.js +import { useLocation, useParams } from 'react-router-dom'; +import routes from '../router/routes'; + +/** + * 路由元信息Hook + */ +export const useRouteMeta = () => { + const location = useLocation(); + const params = useParams(); + + // 查找当前路由配置 + const findRouteMeta = (path, routeList) => { + for (const route of routeList) { + // 精确匹配 + if (route.path === path) { + return route.meta; + } + + // 参数匹配 + const routePattern = route.path.replace(/:[^/]+/g, '[^/]+'); + const regex = new RegExp(`^${routePattern}$`); + if (regex.test(path)) { + return route.meta; + } + + // 递归查找子路由 + if (route.children) { + const childMeta = findRouteMeta(path, route.children); + if (childMeta) return childMeta; + } + } + + return {}; + }; + + const meta = findRouteMeta(location.pathname, routes); + + return { + meta, + pathname: location.pathname, + search: location.search, + params, + }; +}; +``` + +### 4.3 动态设置页面标题 + +```javascript +// App.jsx +import { useEffect } from 'react'; +import { useLocation } from 'react-router-dom'; +import { useRouteMeta } from './hooks/useRouteMeta'; + +const App = () => { + const { meta } = useRouteMeta(); + + useEffect(() => { + if (meta.title) { + document.title = `${meta.title} - 海洋项目管理系统`; + } + }, [meta.title]); + + return ; +}; +``` + +## 5. 菜单生成 + +### 5.1 根据路由生成菜单 + +```javascript +// hooks/useMenu.js +import { useMemo } from 'react'; +import { useAuth } from './useAuth'; +import { useRouteMeta } from './useRouteMeta'; +import routes from '../router/routes'; + +/** + * 生成菜单 + */ +export const useMenu = () => { + const { hasRole } = useAuth(); + const { pathname } = useRouteMeta(); + + const menuItems = useMemo(() => { + const generateMenu = (routeList) => { + return routeList + .filter((route) => { + // 过滤隐藏的菜单项 + if (route.meta?.hideInMenu) return false; + + // 过滤需要权限的菜单项 + if (route.meta?.requiresRole?.length > 0) { + return hasRole(route.meta.requiresRole); + } + + return true; + }) + .map((route) => { + if (route.children) { + return { + key: route.path, + icon: route.meta?.icon, + label: route.meta?.title, + children: generateMenu(route.children), + }; + } + + return { + key: route.path, + icon: route.meta?.icon, + label: route.meta?.title, + }; + }); + }; + + return generateMenu(routes); + }, [hasRole]); + + const selectedKeys = useMemo(() => { + return [pathname]; + }, [pathname]); + + const openKeys = useMemo(() => { + // 根据当前路径展开子菜单 + const paths = pathname.split('/').filter(Boolean); + const keys = paths.map((_, index) => `/${paths.slice(0, index + 1).join('/')}`); + return keys; + }, [pathname]); + + return { + menuItems, + selectedKeys, + openKeys, + }; +}; +``` + +### 5.2 侧边栏菜单 + +```jsx +// components/Layout/Sidebar.jsx +import { Menu } from 'antd'; +import { useNavigate, useLocation } from 'react-router-dom'; +import { useMenu } from '../../hooks/useMenu'; + +const Sidebar = () => { + const navigate = useNavigate(); + const { menuItems, selectedKeys, openKeys } = useMenu(); + + const handleMenuClick = ({ key }) => { + navigate(key); + }; + + return ( +
+
+ Logo + 海洋项目管理系统 +
+ +
+ ); +}; +``` + +## 6. 面包屑导航 + +### 6.1 面包屑生成 + +```javascript +// hooks/useBreadcrumb.js +import { useMemo } from 'react'; +import { useLocation, useMatches } from 'react-router-dom'; +import routes from '../router/routes'; + +/** + * 生成面包屑 + */ +export const useBreadcrumb = () => { + const location = useLocation(); + + const breadcrumbItems = useMemo(() => { + const items = []; + + // 查找当前路径的所有路由 + const pathSegments = location.pathname.split('/').filter(Boolean); + let currentPath = ''; + + for (const segment of pathSegments) { + currentPath += `/${segment}`; + + // 查找路由配置 + const findRoute = (routeList, path) => { + for (const route of routeList) { + if (route.path === path) { + return route; + } + + if (route.children) { + const found = findRoute(route.children, path); + if (found) return found; + } + } + + return null; + }; + + const route = findRoute(routes, currentPath); + + if (route && !route.meta?.hideInBreadcrumb) { + items.push({ + path: currentPath, + title: route.meta?.title || segment, + }); + } + } + + return items; + }, [location.pathname]); + + return { breadcrumbItems }; +}; +``` + +### 6.2 面包屑组件 + +```jsx +// components/Common/Breadcrumb.jsx +import { Breadcrumb } from 'antd'; +import { useNavigate } from 'react-router-dom'; +import { useBreadcrumb } from '../../hooks/useBreadcrumb'; + +const BreadcrumbNav = () => { + const navigate = useNavigate(); + const { breadcrumbItems } = useBreadcrumb(); + + const handleBreadcrumbClick = (path) => { + navigate(path); + }; + + return ( + + navigate('/home')}>首页 + {breadcrumbItems.map((item, index) => ( + handleBreadcrumbClick(item.path)} + > + {item.title} + + ))} + + ); +}; +``` + +## 7. 路由过渡动画 + +### 7.1 路由过渡组件 + +```jsx +// components/RouterTransition.jsx +import { useLocation } from 'react-router-dom'; +import { CSSTransition, SwitchTransition } from 'react-transition-group'; +import './RouterTransition.css'; + +const RouterTransition = ({ children }) => { + const location = useLocation(); + + return ( + + + {children} + + + ); +}; +``` + +### 7.2 过渡动画样式 + +```css +/* RouterTransition.css */ +.fade-enter { + opacity: 0; + transform: translateX(20px); +} + +.fade-enter-active { + opacity: 1; + transform: translateX(0); + transition: opacity 300ms, transform 300ms; +} + +.fade-exit { + opacity: 1; + transform: translateX(0); +} + +.fade-exit-active { + opacity: 0; + transform: translateX(-20px); + transition: opacity 300ms, transform 300ms; +} +``` + +## 8. 路由缓存 + +### 8.1 KeepAlive组件 + +```jsx +// hooks/useKeepAlive.js +import { useState, useRef } from 'react'; + +const keepAliveCache = new Map(); + +export const useKeepAlive = (cacheKey, children) => { + const [isAlive, setIsAlive] = useState(false); + + const activate = () => setIsAlive(true); + const deactivate = () => setIsAlive(false); + + if (!keepAliveCache.has(cacheKey)) { + keepAliveCache.set(cacheKey, { children, isActive: false }); + } + + const cache = keepAliveCache.get(cacheKey); + + return { + isAlive, + activate, + deactivate, + cachedChildren: cache.children, + }; +}; +``` + +### 8.2 使用KeepAlive + +```jsx +const CachedComponent = () => { + const { cachedChildren } = useKeepAlive('dashboard', ); + return cachedChildren; +}; +``` + +## 9. 路由权限控制 + +### 9.1 权限指令 + +```jsx +// components/Permission.jsx +import { useAuth } from '../contexts/AuthContext'; + +/** + * 权限控制组件 + */ +const Permission = ({ role, permission, children, fallback = null }) => { + const { user, hasRole, hasPermission } = useAuth(); + + // 检查角色权限 + if (role && !hasRole(role)) { + return fallback; + } + + // 检查功能权限 + if (permission && !hasPermission(permission)) { + return fallback; + } + + return children; +}; + +export default Permission; +``` + +### 9.2 使用权限组件 + +```jsx +import Permission from './Permission'; + +const ProjectList = () => { + return ( +
+ {/* 只有管理员可见 */} + + + + + {/* 只有市场部用户可见 */} + + + + + {/* 没有权限时显示备用内容 */} + 无权限}> + + +
+ ); +}; +``` + +## 10. 路由最佳实践 + +### 10.1 路由命名规范 + +```javascript +// 好的路由命名 +'/projects' // 项目列表 +'/projects/create' // 创建项目 +'/projects/:id' // 项目详情 +'/projects/:id/edit' // 编辑项目 + +// 不好的路由命名 +'/p' // 太短,不清晰 +'/project-list-page' // 太长,冗余 +'/projects/1/edit' // 使用硬编码ID +``` + +### 10.2 路由嵌套 + +```javascript +// 合理的路由嵌套 +}> + } /> + } /> + } /> + } /> + + +// 不合理的路由嵌套(过深) +}> + }> + }> + } /> + + + +``` + +### 10.3 路由懒加载 + +```javascript +// 懒加载路由组件 +import { lazy, Suspense } from 'react'; + +const ProjectList = lazy(() => import('./pages/Projects/ProjectList')); +const ProjectDetail = lazy(() => import('./pages/Projects/ProjectDetail')); + +const Router = () => { + return ( + }> + + } /> + } /> + + + ); +}; +``` + +--- + +**文档维护**: 前端程序员 +**文档类型**: 路由设计文档 +**最后更新**: 2026-01-25 diff --git a/frontend/docs/state-management.md b/frontend/docs/state-management.md new file mode 100644 index 00000000..7f93aa39 --- /dev/null +++ b/frontend/docs/state-management.md @@ -0,0 +1,958 @@ +# 状态管理设计文档 + +## 1. 状态管理方案选择 + +### 1.1 为什么选择Context API + +对于本项目的需求,React Context API是最佳选择: + +**优势:** +- 轻量级,无需引入额外的库 +- React官方内置,学习成本低 +- 适合中小型应用的状态管理 +- 与React生态系统完美集成 + +**适用场景:** +- 认证状态(用户信息、Token) +- 全局配置(主题、语言) +- 简单的共享状态 + +**不适用场景:** +- 复杂的跨组件通信 +- 高频率的状态更新 +- 需要时间旅行调试 +- 如果将来需要,可以迁移到Redux或Zustand + +### 1.2 状态管理架构 + +``` +┌─────────────────────────────────────────┐ +│ App Root │ +├─────────────────────────────────────────┤ +│ AuthProvider (认证状态) │ +│ ├─ user (用户信息) │ +│ ├─ token (认证令牌) │ +│ └─ authState (认证状态) │ +│ │ +│ ThemeProvider (主题状态,可选) │ +│ └─ theme (主题配置) │ +│ │ +│ Router (路由状态) │ +│ └─ currentPath (当前路径) │ +└─────────────────────────────────────────┘ +``` + +## 2. Context实现 + +### 2.1 AuthContext(认证上下文) + +```javascript +// contexts/AuthContext.jsx +import { createContext, useContext, useState, useEffect, useCallback } from 'react'; +import { authAPI } from '../services/auth'; +import { message } from 'antd'; + +const AuthContext = createContext(null); + +/** + * 认证上下文Provider + */ +export const AuthProvider = ({ children }) => { + const [user, setUser] = useState(null); + const [token, setToken] = useState(null); + const [loading, setLoading] = useState(true); + const [authState, setAuthState] = useState({ + isAuthenticated: false, + isLoading: true, + }); + + /** + * 初始化认证状态 + */ + useEffect(() => { + checkAuth(); + }, []); + + /** + * 检查登录状态 + */ + const checkAuth = async () => { + try { + const savedToken = localStorage.getItem('token'); + const savedUser = localStorage.getItem('user'); + + if (savedToken && savedUser) { + // 验证Token有效性 + const currentUser = await authAPI.getCurrentUser(); + + setToken(savedToken); + setUser(currentUser); + setAuthState({ + isAuthenticated: true, + isLoading: false, + }); + } else { + clearAuth(); + } + } catch (error) { + console.error('Auth check failed:', error); + clearAuth(); + } finally { + setLoading(false); + } + }; + + /** + * 登录 + */ + const login = async (username, password) => { + try { + const response = await authAPI.login(username, password); + + const { token: newToken, user: userData } = response; + + // 保存到localStorage + localStorage.setItem('token', newToken); + localStorage.setItem('user', JSON.stringify(userData)); + + // 更新状态 + setToken(newToken); + setUser(userData); + setAuthState({ + isAuthenticated: true, + isLoading: false, + }); + + return userData; + } catch (error) { + console.error('Login failed:', error); + throw error; + } + }; + + /** + * 登出 + */ + const logout = useCallback(async () => { + try { + await authAPI.logout(); + } catch (error) { + console.error('Logout failed:', error); + } finally { + clearAuth(); + } + }, []); + + /** + * 清除认证信息 + */ + const clearAuth = useCallback(() => { + localStorage.removeItem('token'); + localStorage.removeItem('user'); + setToken(null); + setUser(null); + setAuthState({ + isAuthenticated: false, + isLoading: false, + }); + }, []); + + /** + * 更新用户信息 + */ + const updateUser = useCallback((userData) => { + setUser((prev) => ({ ...prev, ...userData })); + localStorage.setItem('user', JSON.stringify({ ...user, ...userData })); + }, [user]); + + /** + * 检查权限 + */ + const hasRole = useCallback( + (roles) => { + if (!user) return false; + if (Array.isArray(roles)) { + return roles.includes(user.role); + } + return user.role === roles; + }, + [user] + ); + + /** + * 检查是否可以编辑 + */ + const canEdit = useCallback( + (createdBy) => { + if (!user) return false; + if (user.role === 'admin') return true; + if (user.role === 'market' && createdBy === user.id) return true; + return false; + }, + [user] + ); + + const value = { + user, + token, + loading, + authState, + login, + logout, + checkAuth, + updateUser, + hasRole, + canEdit, + isAuthenticated: authState.isAuthenticated, + }; + + return ( + + {children} + + ); +}; + +/** + * 使用AuthContext的Hook + */ +export const useAuth = () => { + const context = useContext(AuthContext); + if (!context) { + throw new Error('useAuth must be used within AuthProvider'); + } + return context; +}; + +export default AuthContext; +``` + +### 2.2 ThemeContext(主题上下文,可选) + +```javascript +// contexts/ThemeContext.jsx +import { createContext, useContext, useState, useCallback, useEffect } from 'react'; + +const ThemeContext = createContext(null); + +export const ThemeProvider = ({ children }) => { + const [theme, setTheme] = useState('light'); + + // 从localStorage加载主题 + useEffect(() => { + const savedTheme = localStorage.getItem('theme'); + if (savedTheme) { + setTheme(savedTheme); + document.documentElement.setAttribute('data-theme', savedTheme); + } + }, []); + + /** + * 切换主题 + */ + const toggleTheme = useCallback(() => { + setTheme((prev) => { + const newTheme = prev === 'light' ? 'dark' : 'light'; + localStorage.setItem('theme', newTheme); + document.documentElement.setAttribute('data-theme', newTheme); + return newTheme; + }); + }, []); + + /** + * 设置主题 + */ + const setThemeMode = useCallback((mode) => { + setTheme(mode); + localStorage.setItem('theme', mode); + document.documentElement.setAttribute('data-theme', mode); + }, []); + + const value = { + theme, + toggleTheme, + setThemeMode, + }; + + return ( + + {children} + + ); +}; + +export const useTheme = () => { + const context = useContext(ThemeContext); + if (!context) { + throw new Error('useTheme must be used within ThemeProvider'); + } + return context; +}; + +export default ThemeContext; +``` + +## 3. 自定义Hooks + +### 3.1 useAuth Hook + +```javascript +// hooks/useAuth.js +import { useAuth as useAuthContext } from '../contexts/AuthContext'; + +export const useAuth = () => { + return useAuthContext(); +}; +``` + +### 3.2 usePermission Hook + +```javascript +// hooks/usePermission.js +import { useAuth } from './useAuth'; + +/** + * 权限检查Hook + */ +export const usePermission = () => { + const { user, hasRole, canEdit } = useAuth(); + + /** + * 检查是否是管理员 + */ + const isAdmin = () => { + return user?.role === 'admin'; + }; + + /** + * 检查是否是市场部 + */ + const isMarket = () => { + return user?.role === 'market'; + }; + + /** + * 检查是否是其他部门 + */ + const isOther = () => { + return user?.role === 'other'; + }; + + /** + * 检查是否有指定权限 + */ + const hasPermission = (permission) => { + // 可以扩展更复杂的权限系统 + const permissions = { + admin: ['create_user', 'edit_user', 'delete_user', 'create_project', 'edit_project', 'delete_project'], + market: ['create_project', 'edit_own_project', 'delete_own_project'], + other: ['edit_project_cost', 'edit_project_finance', 'edit_project_progress'], + }; + + return permissions[user?.role]?.includes(permission) || false; + }; + + return { + isAdmin, + isMarket, + isOther, + hasRole, + canEdit, + hasPermission, + user, + }; +}; +``` + +### 3.3 useModal Hook + +```javascript +// hooks/useModal.js +import { useState, useCallback } from 'react'; + +/** + * 弹窗管理Hook + */ +export const useModal = () => { + const [visible, setVisible] = useState(false); + const [loading, setLoading] = useState(false); + const [data, setData] = useState(null); + + const open = useCallback((modalData) => { + setData(modalData); + setVisible(true); + }, []); + + const close = useCallback(() => { + setVisible(false); + setData(null); + setLoading(false); + }, []); + + const toggle = useCallback(() => { + setVisible((prev) => !prev); + }, []); + + const setLoadingState = useCallback((state) => { + setLoading(state); + }, []); + + return { + visible, + loading, + data, + open, + close, + toggle, + setLoading: setLoadingState, + }; +}; +``` + +### 3.4 useTable Hook + +```javascript +// hooks/useTable.js +import { useState, useCallback } from 'react'; + +/** + * 表格管理Hook + */ +export const useTable = (initialState = {}) => { + const { + pagination: initialPagination = { current: 1, pageSize: 10 }, + filters: initialFilters = {}, + sorter: initialSorter = {}, + selectedRowKeys: initialSelectedRowKeys = [], + } = initialState; + + const [pagination, setPagination] = useState(initialPagination); + const [filters, setFilters] = useState(initialFilters); + const [sorter, setSorter] = useState(initialSorter); + const [selectedRowKeys, setSelectedRowKeys] = useState(initialSelectedRowKeys); + + const handleTableChange = useCallback((newPagination, newFilters, newSorter) => { + setPagination(newPagination); + setFilters(newFilters); + setSorter({ + field: newSorter.field, + order: newSorter.order, + }); + }, []); + + const resetTable = useCallback(() => { + setPagination(initialPagination); + setFilters(initialFilters); + setSorter(initialSorter); + setSelectedRowKeys(initialSelectedRowKeys); + }, [initialPagination, initialFilters, initialSorter, initialSelectedRowKeys]); + + return { + pagination, + filters, + sorter, + selectedRowKeys, + setSelectedRowKeys, + handleTableChange, + resetTable, + }; +}; +``` + +### 3.5 useForm Hook + +```javascript +// hooks/useForm.js +import { useState, useCallback } from 'react'; + +/** + * 表单管理Hook + */ +export const useForm = (initialValues = {}) => { + const [values, setValues] = useState(initialValues); + const [errors, setErrors] = useState({}); + const [touched, setTouched] = useState({}); + const [submitting, setSubmitting] = useState(false); + + /** + * 更新表单值 + */ + const handleChange = useCallback((field, value) => { + setValues((prev) => ({ + ...prev, + [field]: value, + })); + // 清除该字段的错误 + if (errors[field]) { + setErrors((prev) => ({ + ...prev, + [field]: null, + })); + } + }, [errors]); + + /** + * 批量更新表单值 + */ + const setFieldsValue = useCallback((newValues) => { + setValues((prev) => ({ + ...prev, + ...newValues, + })); + }, []); + + /** + * 标记字段为已触摸 + */ + const handleBlur = useCallback((field) => { + setTouched((prev) => ({ + ...prev, + [field]: true, + })); + }, []); + + /** + * 设置错误 + */ + const setFieldError = useCallback((field, error) => { + setErrors((prev) => ({ + ...prev, + [field]: error, + })); + }, []); + + /** + * 批量设置错误 + */ + const setErrorsAll = useCallback((newErrors) => { + setErrors(newErrors); + }, []); + + /** + * 验证表单 + */ + const validate = useCallback((validationRules) => { + const newErrors = {}; + let isValid = true; + + Object.keys(validationRules).forEach((field) => { + const rules = validationRules[field]; + const value = values[field]; + + for (const rule of rules) { + let error = null; + + if (rule.required && !value) { + error = rule.message || `${field} is required`; + } else if (rule.pattern && !rule.pattern.test(value)) { + error = rule.message || `${field} is invalid`; + } else if (rule.validator && !rule.validator(value)) { + error = rule.message || `${field} is invalid`; + } + + if (error) { + newErrors[field] = error; + isValid = false; + break; + } + } + }); + + setErrors(newErrors); + return isValid; + }, [values]); + + /** + * 重置表单 + */ + const resetForm = useCallback(() => { + setValues(initialValues); + setErrors({}); + setTouched({}); + setSubmitting(false); + }, [initialValues]); + + /** + * 提交表单 + */ + const handleSubmit = useCallback(async (onSubmit, validationRules) => { + if (validationRules && !validate(validationRules)) { + return false; + } + + setSubmitting(true); + try { + await onSubmit(values); + return true; + } catch (error) { + console.error('Form submit error:', error); + return false; + } finally { + setSubmitting(false); + } + }, [values, validate]); + + return { + values, + errors, + touched, + submitting, + handleChange, + setFieldsValue, + handleBlur, + setFieldError, + setErrorsAll, + validate, + resetForm, + handleSubmit, + }; +}; +``` + +### 3.6 useDebounce Hook + +```javascript +// hooks/useDebounce.js +import { useState, useEffect } from 'react'; + +/** + * 防抖Hook + */ +export const useDebounce = (value, delay = 300) => { + const [debouncedValue, setDebouncedValue] = useState(value); + + useEffect(() => { + const timer = setTimeout(() => { + setDebouncedValue(value); + }, delay); + + return () => { + clearTimeout(timer); + }; + }, [value, delay]); + + return debouncedValue; +}; +``` + +### 3.7 useLocalStorage Hook + +```javascript +// hooks/useLocalStorage.js +import { useState, useEffect, useCallback } from 'react'; + +/** + * LocalStorage Hook + */ +export const useLocalStorage = (key, initialValue) => { + const [storedValue, setStoredValue] = useState(() => { + try { + const item = window.localStorage.getItem(key); + return item ? JSON.parse(item) : initialValue; + } catch (error) { + console.error(`Error reading localStorage key "${key}":`, error); + return initialValue; + } + }); + + const setValue = useCallback( + (value) => { + try { + const valueToStore = value instanceof Function ? value(storedValue) : value; + setStoredValue(valueToStore); + window.localStorage.setItem(key, JSON.stringify(valueToStore)); + } catch (error) { + console.error(`Error setting localStorage key "${key}":`, error); + } + }, + [key, storedValue] + ); + + const removeValue = useCallback(() => { + try { + window.localStorage.removeItem(key); + setStoredValue(initialValue); + } catch (error) { + console.error(`Error removing localStorage key "${key}":`, error); + } + }, [key, initialValue]); + + return [storedValue, setValue, removeValue]; +}; +``` + +## 4. 状态管理最佳实践 + +### 4.1 状态分类 + +```javascript +// 1. 本地状态 - 使用useState +const [value, setValue] = useState(''); + +// 2. 上下文状态 - 使用Context +const { user, login, logout } = useAuth(); + +// 3. URL状态 - 使用useSearchParams +const [searchParams, setSearchParams] = useSearchParams(); + +// 4. 表单状态 - 使用Form Hook +const [form] = Form.useForm(); + +// 5. 列表状态 - 使用自定义Hook +const { list, loading, pagination } = useList(projectAPI.getList); +``` + +### 4.2 状态提升 + +**错误示例:** +```jsx +// 父组件 +const Parent = () => { + const [value, setValue] = useState(''); + return ; +}; + +// 子组件 +const Child = ({ value, onChange }) => { + return onChange(e.target.value)} />; +}; +``` + +**正确示例:** +```jsx +// 状态留在子组件 +const Child = () => { + const [value, setValue] = useState(''); + return setValue(e.target.value)} />; +}; +``` + +### 4.3 避免不必要的重渲染 + +```jsx +// 使用React.memo +const ExpensiveComponent = React.memo(({ data }) => { + return
{/* 渲染逻辑 */}
; +}); + +// 使用useMemo缓存计算结果 +const expensiveValue = useMemo(() => { + return computeExpensiveValue(data); +}, [data]); + +// 使用useCallback缓存函数 +const handleClick = useCallback(() => { + doSomething(a, b); +}, [a, b]); +``` + +## 5. 状态持久化 + +### 5.1 本地存储策略 + +```javascript +// 需要持久化的状态 +const persistentState = { + user: 'localStorage', // 用户信息 + token: 'localStorage', // 认证令牌 + theme: 'localStorage', // 主题设置 + language: 'localStorage', // 语言设置 +}; + +// 不需要持久化的状态 +const transientState = { + loading: false, // 加载状态 + error: null, // 错误信息 + modalVisible: false, // 弹窗状态 + formData: {}, // 表单数据 +}; +``` + +### 5.2 状态恢复 + +```javascript +// 在应用启动时恢复状态 +useEffect(() => { + const savedTheme = localStorage.getItem('theme'); + if (savedTheme) { + setTheme(savedTheme); + } + + const savedUser = localStorage.getItem('user'); + if (savedUser) { + setUser(JSON.parse(savedUser)); + } +}, []); +``` + +## 6. 调试工具 + +### 6.1 React DevTools + +```javascript +// 安装React DevTools浏览器扩展 +// 在开发环境中,可以使用React DevTools查看组件树和状态 +``` + +### 6.2 自定义状态日志 + +```javascript +// 开发环境下的状态日志 +if (import.meta.env.DEV) { + useEffect(() => { + console.log('Auth state changed:', { user, token, authState }); + }, [user, token, authState]); +} +``` + +### 6.3 状态变化监听 + +```javascript +// 监听特定状态的变化 +useEffect(() => { + if (user) { + console.log('User logged in:', user); + } else { + console.log('User logged out'); + } +}, [user]); +``` + +## 7. 性能优化 + +### 7.1 减少Context更新 + +```javascript +// 错误示例:频繁更新Context +const AuthProvider = ({ children }) => { + const [user, setUser] = useState(null); + const [timestamp, setTimestamp] = useState(Date.now()); + + // 这个timestamp变化会导致所有使用AuthContext的组件重渲染 + useEffect(() => { + setInterval(() => { + setTimestamp(Date.now()); + }, 1000); + }, []); + + return {children}; +}; + +// 正确示例:分离频繁更新的状态 +const AuthProvider = ({ children }) => { + const [user, setUser] = useState(null); + const [timestamp, setTimestamp] = useState(Date.now()); + + useEffect(() => { + setInterval(() => { + setTimestamp(Date.now()); + }, 1000); + }, []); + + return ( + <> + {children} + {/* 不渲染 */} + + ); +}; +``` + +### 7.2 使用useReducer + +```javascript +// 对于复杂的状态逻辑,使用useReducer +const initialState = { + user: null, + token: null, + loading: true, + error: null, +}; + +function authReducer(state, action) { + switch (action.type) { + case 'LOGIN_SUCCESS': + return { + ...state, + user: action.payload.user, + token: action.payload.token, + loading: false, + }; + case 'LOGOUT': + return { + ...state, + user: null, + token: null, + }; + default: + return state; + } +} + +const AuthProvider = ({ children }) => { + const [state, dispatch] = useReducer(authReducer, initialState); + + return ( + + {children} + + ); +}; +``` + +## 8. 未来扩展 + +### 8.1 迁移到Redux + +如果项目变得复杂,可以考虑迁移到Redux Toolkit: + +```javascript +// slices/authSlice.js +import { createSlice } from '@reduxjs/toolkit'; + +const authSlice = createSlice({ + name: 'auth', + initialState: { + user: null, + token: null, + loading: true, + }, + reducers: { + setCredentials: (state, action) => { + state.user = action.payload.user; + state.token = action.payload.token; + state.loading = false; + }, + logout: (state) => { + state.user = null; + state.token = null; + }, + }, +}); + +export const { setCredentials, logout } = authSlice.actions; +export default authSlice.reducer; +``` + +### 8.2 迁移到Zustand + +Zustand是一个更轻量级的替代方案: + +```javascript +// store/authStore.js +import { create } from 'zustand'; + +export const useAuthStore = create((set) => ({ + user: null, + token: null, + loading: true, + login: (user, token) => set({ user, token, loading: false }), + logout: () => set({ user: null, token: null }), +})); +``` + +--- + +**文档维护**: 前端程序员 +**文档类型**: 状态管理设计文档 +**最后更新**: 2026-01-25