save code
This commit is contained in:
@@ -89,6 +89,23 @@ cat WORKSTANDARDS.md
|
|||||||
|
|
||||||
## 文档资源
|
## 文档资源
|
||||||
|
|
||||||
- [UI设计规范](docs/ui-design-spec.md)
|
### 产品文档
|
||||||
- [设计方案](docs/plans/)
|
- [产品设计文档](docs/产品设计文档.md) - 产品需求文档(PRD)
|
||||||
- [团队协作规范](docs/TEAM-COLLABORATION.md)
|
- [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)
|
||||||
|
|||||||
@@ -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()
|
||||||
@@ -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数据
|
||||||
@@ -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
|
||||||
@@ -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 <token>`)
|
||||||
|
- 统一响应格式:
|
||||||
|
```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
|
||||||
+864
@@ -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 <token>`
|
||||||
|
- **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 <token>
|
||||||
|
```
|
||||||
|
|
||||||
|
**响应**:
|
||||||
|
```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 <token>
|
||||||
|
```
|
||||||
|
|
||||||
|
**响应**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"message": "登出成功",
|
||||||
|
"data": null,
|
||||||
|
"error_code": null
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 3. 用户管理API
|
||||||
|
|
||||||
|
### 3.1 获取用户列表
|
||||||
|
|
||||||
|
**接口**: `GET /users`
|
||||||
|
|
||||||
|
**权限**: 仅管理员 (admin)
|
||||||
|
|
||||||
|
**请求头**:
|
||||||
|
```
|
||||||
|
Authorization: Bearer <token>
|
||||||
|
```
|
||||||
|
|
||||||
|
**查询参数**:
|
||||||
|
| 参数 | 类型 | 必填 | 说明 |
|
||||||
|
|------|------|------|------|
|
||||||
|
| 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 <token>
|
||||||
|
```
|
||||||
|
|
||||||
|
**请求体**:
|
||||||
|
```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 <token>
|
||||||
|
```
|
||||||
|
|
||||||
|
**路径参数**:
|
||||||
|
| 参数 | 类型 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| 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 <token>
|
||||||
|
```
|
||||||
|
|
||||||
|
**路径参数**:
|
||||||
|
| 参数 | 类型 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| 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 <token>
|
||||||
|
```
|
||||||
|
|
||||||
|
**路径参数**:
|
||||||
|
| 参数 | 类型 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| id | int | 用户ID |
|
||||||
|
|
||||||
|
**响应**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"message": "用户删除成功",
|
||||||
|
"data": null,
|
||||||
|
"error_code": null
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.6 重置用户密码
|
||||||
|
|
||||||
|
**接口**: `POST /users/{id}/reset-password`
|
||||||
|
|
||||||
|
**权限**: 仅管理员 (admin)
|
||||||
|
|
||||||
|
**请求头**:
|
||||||
|
```
|
||||||
|
Authorization: Bearer <token>
|
||||||
|
```
|
||||||
|
|
||||||
|
**路径参数**:
|
||||||
|
| 参数 | 类型 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| id | int | 用户ID |
|
||||||
|
|
||||||
|
**请求体**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"new_password": "newpassword123"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**响应**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"message": "密码重置成功",
|
||||||
|
"data": null,
|
||||||
|
"error_code": null
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 4. 项目管理API
|
||||||
|
|
||||||
|
### 4.1 获取项目列表
|
||||||
|
|
||||||
|
**接口**: `GET /projects`
|
||||||
|
|
||||||
|
**权限**: 所有用户
|
||||||
|
|
||||||
|
**请求头**:
|
||||||
|
```
|
||||||
|
Authorization: Bearer <token>
|
||||||
|
```
|
||||||
|
|
||||||
|
**查询参数**:
|
||||||
|
| 参数 | 类型 | 必填 | 说明 |
|
||||||
|
|------|------|------|------|
|
||||||
|
| 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 <token>
|
||||||
|
```
|
||||||
|
|
||||||
|
**路径参数**:
|
||||||
|
| 参数 | 类型 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| 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 <token>
|
||||||
|
```
|
||||||
|
|
||||||
|
**请求体**:
|
||||||
|
```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 <token>
|
||||||
|
```
|
||||||
|
|
||||||
|
**路径参数**:
|
||||||
|
| 参数 | 类型 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| 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 <token>
|
||||||
|
```
|
||||||
|
|
||||||
|
**路径参数**:
|
||||||
|
| 参数 | 类型 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| id | int | 项目ID |
|
||||||
|
|
||||||
|
**响应**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"message": "项目删除成功",
|
||||||
|
"data": null,
|
||||||
|
"error_code": null
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.6 项目统计API
|
||||||
|
|
||||||
|
#### 4.6.1 基础统计
|
||||||
|
|
||||||
|
**接口**: `GET /projects/statistics`
|
||||||
|
|
||||||
|
**权限**: 所有用户
|
||||||
|
|
||||||
|
**请求头**:
|
||||||
|
```
|
||||||
|
Authorization: Bearer <token>
|
||||||
|
```
|
||||||
|
|
||||||
|
**查询参数**:
|
||||||
|
| 参数 | 类型 | 必填 | 说明 |
|
||||||
|
|------|------|------|------|
|
||||||
|
| 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 <token>
|
||||||
|
```
|
||||||
|
|
||||||
|
**查询参数**:
|
||||||
|
| 参数 | 类型 | 必填 | 说明 |
|
||||||
|
|------|------|------|------|
|
||||||
|
| 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 <token>
|
||||||
|
```
|
||||||
|
|
||||||
|
**查询参数**:
|
||||||
|
| 参数 | 类型 | 必填 | 说明 |
|
||||||
|
|------|------|------|------|
|
||||||
|
| 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`
|
||||||
@@ -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文件
|
||||||
|
- ✨ 创建交付说明文档
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**设计文档准备就绪,可以开始前端开发工作!**
|
||||||
|
|
||||||
|
加油!!!
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
# Corporate Precision - 设计哲学
|
||||||
|
|
||||||
|
## 运动名称
|
||||||
|
|
||||||
|
Corporate Precision(企业精确)
|
||||||
|
|
||||||
|
## 设计哲学
|
||||||
|
|
||||||
|
**秩序作为视觉语言**
|
||||||
|
|
||||||
|
设计哲学建立在精确秩序和系统性清晰之上。每一个视觉元素都必须经过严格计算,位置、间距、比例都要遵循数学般精确的规则。这不是随意的美学,而是理性的视觉表达,将企业管理系统的严谨本质转化为可感知的视觉语言。这种精确不是机械的重复,而是经过无数次调整和优化的结果,最终呈现出看似简单却蕴含深度的视觉秩序。
|
||||||
|
|
||||||
|
**色彩的功能主义**
|
||||||
|
|
||||||
|
色彩在此不是装饰,而是信息载体。蓝色作为主色调传达稳定与信任,绿色表示成功与进行中,红色警示风险与错误。每种颜色都有其特定的语义和功能边界,绝不为美学效果而牺牲信息传递的清晰度。色彩的使用经过精心的对比度测试,确保在任何光线条件下都保持最佳可读性。这种克制而精准的色彩运用,是经过反复推敲的成果,每一个像素的选择都体现了设计者的专业素养。
|
||||||
|
|
||||||
|
**空间与呼吸感**
|
||||||
|
|
||||||
|
空间不是空白,而是设计的有机构成。充足的内边距和外边距创造了视觉呼吸空间,让信息层次分明,不至于让用户感到压迫。每个区块的间距都遵循8px网格系统,确保整个界面的和谐统一。这种对空间关系的把控,是经过长期专业训练才能达到的境界,体现了设计者对用户体验的深刻理解。
|
||||||
|
|
||||||
|
**形式的极简主义**
|
||||||
|
|
||||||
|
组件设计追求极致的简洁,去除一切不必要的装饰。边框细至1px,圆角保持在4px,阴影轻微而精致。这种极简不是偷工减料,而是经过无数稿迭代后的最终选择,每一个视觉元素的存在都有其明确的理由。表单、按钮、卡片等组件都遵循统一的设计语言,看起来简约却包含丰富的细节,是顶级专业设计的典型特征。
|
||||||
|
|
||||||
|
**系统的可扩展性**
|
||||||
|
|
||||||
|
设计系统具有极强的可扩展性,能够适应不断增长的功能需求而不失一致性。从原子组件到分子组件,再到整个页面布局,都遵循同样的设计原则。这种系统化思维确保了无论添加多少新功能,整个系统都能保持统一的美学和用户体验。这是经过多年实践验证的设计方法论,是顶级产品设计团队的标志性特征。
|
||||||
@@ -0,0 +1,934 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>海洋项目管理系统 - 设计参考</title>
|
||||||
|
<style>
|
||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
||||||
|
background: #f0f2f5;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-selector {
|
||||||
|
position: fixed;
|
||||||
|
top: 10px;
|
||||||
|
left: 10px;
|
||||||
|
z-index: 1000;
|
||||||
|
background: white;
|
||||||
|
padding: 15px;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 2px 8px rgba(0,0,0,0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-selector h3 {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
font-size: 16px;
|
||||||
|
color: #1890ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-selector button {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
padding: 8px 12px;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
background: #1890ff;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-selector button:hover {
|
||||||
|
background: #40a9ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-selector button.active {
|
||||||
|
background: #096dd9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page.active {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Login Page Styles */
|
||||||
|
.login-page {
|
||||||
|
min-height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
background: linear-gradient(135deg, #001529 0%, #1890ff 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-box {
|
||||||
|
width: 400px;
|
||||||
|
background: white;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 40px;
|
||||||
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-title {
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #1890ff;
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-label {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-input {
|
||||||
|
width: 100%;
|
||||||
|
height: 40px;
|
||||||
|
padding: 8px 12px;
|
||||||
|
border: 1px solid #d9d9d9;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-input:focus {
|
||||||
|
border-color: #1890ff;
|
||||||
|
outline: none;
|
||||||
|
box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-group {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-group input {
|
||||||
|
margin-right: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-login {
|
||||||
|
width: 100%;
|
||||||
|
height: 40px;
|
||||||
|
background: #1890ff;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 500;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-login:hover {
|
||||||
|
background: #40a9ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Main Layout Styles */
|
||||||
|
.main-layout {
|
||||||
|
display: flex;
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar {
|
||||||
|
width: 256px;
|
||||||
|
background: white;
|
||||||
|
border-right: 1px solid #e8e8e8;
|
||||||
|
padding-top: 64px;
|
||||||
|
position: fixed;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-item {
|
||||||
|
padding: 12px 24px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
color: #666;
|
||||||
|
cursor: pointer;
|
||||||
|
border-left: 3px solid transparent;
|
||||||
|
transition: all 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-item:hover {
|
||||||
|
background: #e6f7ff;
|
||||||
|
color: #1890ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-item.active {
|
||||||
|
background: #e6f7ff;
|
||||||
|
color: #1890ff;
|
||||||
|
border-left-color: #1890ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
height: 64px;
|
||||||
|
background: #001529;
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 256px;
|
||||||
|
right: 0;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0 24px;
|
||||||
|
color: white;
|
||||||
|
z-index: 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-info {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
margin-left: 256px;
|
||||||
|
margin-top: 64px;
|
||||||
|
padding: 24px;
|
||||||
|
background: #f0f2f5;
|
||||||
|
min-height: calc(100vh - 64px);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Dashboard Styles */
|
||||||
|
.stat-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(4, 1fr);
|
||||||
|
gap: 24px;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-card {
|
||||||
|
background: white;
|
||||||
|
padding: 24px;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-label {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #666;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-value {
|
||||||
|
font-size: 32px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #1890ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-section {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 2fr 1fr;
|
||||||
|
gap: 24px;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
background: white;
|
||||||
|
padding: 24px;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-title {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #333;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-placeholder {
|
||||||
|
height: 300px;
|
||||||
|
background: #fafafa;
|
||||||
|
border-radius: 4px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
color: #999;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.recent-project {
|
||||||
|
padding: 12px 0;
|
||||||
|
border-bottom: 1px solid #f0f0f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-name {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-meta {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #999;
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-amount {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #1890ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Project List Styles */
|
||||||
|
.toolbar {
|
||||||
|
background: white;
|
||||||
|
padding: 16px 24px;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
||||||
|
margin-bottom: 24px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbar-left {
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-input {
|
||||||
|
width: 240px;
|
||||||
|
height: 32px;
|
||||||
|
padding: 4px 12px;
|
||||||
|
border: 1px solid #d9d9d9;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select-input {
|
||||||
|
width: 120px;
|
||||||
|
height: 32px;
|
||||||
|
padding: 4px 12px;
|
||||||
|
border: 1px solid #d9d9d9;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary {
|
||||||
|
height: 32px;
|
||||||
|
padding: 0 16px;
|
||||||
|
background: #1890ff;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary:hover {
|
||||||
|
background: #40a9ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-container {
|
||||||
|
background: white;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
}
|
||||||
|
|
||||||
|
thead th {
|
||||||
|
background: #fafafa;
|
||||||
|
padding: 12px 16px;
|
||||||
|
text-align: left;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #333;
|
||||||
|
border-bottom: 1px solid #e8e8e8;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
tbody td {
|
||||||
|
padding: 12px 16px;
|
||||||
|
border-bottom: 1px solid #e8e8e8;
|
||||||
|
color: #666;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
tbody tr:hover {
|
||||||
|
background: #fafafa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-link {
|
||||||
|
color: #1890ff;
|
||||||
|
cursor: pointer;
|
||||||
|
margin-right: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-link:hover {
|
||||||
|
color: #40a9ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 2px 8px;
|
||||||
|
border-radius: 2px;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag-blue {
|
||||||
|
background: #e6f7ff;
|
||||||
|
border: 1px solid #91d5ff;
|
||||||
|
color: #1890ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag-green {
|
||||||
|
background: #f6ffed;
|
||||||
|
border: 1px solid #b7eb8f;
|
||||||
|
color: #52c41a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination {
|
||||||
|
height: 48px;
|
||||||
|
background: white;
|
||||||
|
border-top: 1px solid #e8e8e8;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-btn {
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
border: 1px solid #d9d9d9;
|
||||||
|
border-radius: 4px;
|
||||||
|
background: white;
|
||||||
|
color: #666;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-btn:hover {
|
||||||
|
border-color: #1890ff;
|
||||||
|
color: #1890ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-btn.active {
|
||||||
|
background: #1890ff;
|
||||||
|
color: white;
|
||||||
|
border-color: #1890ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Project Detail Styles */
|
||||||
|
.breadcrumb {
|
||||||
|
height: 32px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-title {
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
gap: 24px;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-card {
|
||||||
|
background: white;
|
||||||
|
padding: 24px;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-card-title {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #333;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
padding-bottom: 12px;
|
||||||
|
border-bottom: 1px solid #e8e8e8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-item {
|
||||||
|
display: flex;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-item-label {
|
||||||
|
width: 120px;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-item-value {
|
||||||
|
flex: 1;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.full-width {
|
||||||
|
grid-column: span 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-group {
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-edit {
|
||||||
|
height: 32px;
|
||||||
|
padding: 0 16px;
|
||||||
|
background: #1890ff;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-delete {
|
||||||
|
height: 32px;
|
||||||
|
padding: 0 16px;
|
||||||
|
background: #ff4d4f;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<!-- Page Selector -->
|
||||||
|
<div class="page-selector">
|
||||||
|
<h3>页面切换</h3>
|
||||||
|
<button class="active" onclick="showPage('login')">登录页面</button>
|
||||||
|
<button onclick="showPage('dashboard')">仪表盘</button>
|
||||||
|
<button onclick="showPage('project-list')">项目列表</button>
|
||||||
|
<button onclick="showPage('project-detail')">项目详情</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Login Page -->
|
||||||
|
<div id="login" class="page login-page active">
|
||||||
|
<div class="login-box">
|
||||||
|
<h1 class="login-title">海洋项目管理系统</h1>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="form-label">用户名</label>
|
||||||
|
<input type="text" class="form-input" placeholder="请输入用户名">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="form-label">密码</label>
|
||||||
|
<input type="password" class="form-input" placeholder="请输入密码">
|
||||||
|
</div>
|
||||||
|
<div class="checkbox-group">
|
||||||
|
<input type="checkbox" id="remember">
|
||||||
|
<label for="remember">记住密码</label>
|
||||||
|
</div>
|
||||||
|
<button class="btn-login">登录</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Dashboard Page -->
|
||||||
|
<div id="dashboard" class="page">
|
||||||
|
<div class="main-layout">
|
||||||
|
<div class="sidebar">
|
||||||
|
<div class="menu-item active">📊 仪表盘</div>
|
||||||
|
<div class="menu-item">📁 项目管理</div>
|
||||||
|
<div class="menu-item">👥 用户管理</div>
|
||||||
|
<div class="menu-item">📈 项目统计</div>
|
||||||
|
</div>
|
||||||
|
<div style="flex: 1">
|
||||||
|
<div class="header">
|
||||||
|
<div class="logo">海洋项目管理系统</div>
|
||||||
|
<div class="user-info">
|
||||||
|
<span>张三 (市场部)</span>
|
||||||
|
<button class="btn-primary" style="height: 28px; font-size: 12px;">登出</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="content">
|
||||||
|
<h2 style="margin-bottom: 24px; font-size: 24px; font-weight: 600; color: #333;">仪表盘</h2>
|
||||||
|
<div class="stat-grid">
|
||||||
|
<div class="stat-card">
|
||||||
|
<div class="stat-label">项目总数</div>
|
||||||
|
<div class="stat-value">156</div>
|
||||||
|
</div>
|
||||||
|
<div class="stat-card">
|
||||||
|
<div class="stat-label">进行中</div>
|
||||||
|
<div class="stat-value">68</div>
|
||||||
|
</div>
|
||||||
|
<div class="stat-card">
|
||||||
|
<div class="stat-label">已完成</div>
|
||||||
|
<div class="stat-value">88</div>
|
||||||
|
</div>
|
||||||
|
<div class="stat-card">
|
||||||
|
<div class="stat-label">总合同金额(万元)</div>
|
||||||
|
<div class="stat-value">12,450</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="chart-section">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-title">项目趋势图</div>
|
||||||
|
<div class="chart-placeholder">
|
||||||
|
[柱状图/折线图显示区域]
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-title">最近项目</div>
|
||||||
|
<div class="recent-project">
|
||||||
|
<div class="project-name">某电力基建工程项目</div>
|
||||||
|
<div class="project-meta">2026-01-25</div>
|
||||||
|
<div class="project-amount">950万元</div>
|
||||||
|
</div>
|
||||||
|
<div class="recent-project">
|
||||||
|
<div class="project-name">业扩工程项目</div>
|
||||||
|
<div class="project-meta">2026-01-24</div>
|
||||||
|
<div class="project-amount">500万元</div>
|
||||||
|
</div>
|
||||||
|
<div class="recent-project">
|
||||||
|
<div class="project-name">客户工程项目</div>
|
||||||
|
<div class="project-meta">2026-01-23</div>
|
||||||
|
<div class="project-amount">300万元</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Project List Page -->
|
||||||
|
<div id="project-list" class="page">
|
||||||
|
<div class="main-layout">
|
||||||
|
<div class="sidebar">
|
||||||
|
<div class="menu-item">📊 仪表盘</div>
|
||||||
|
<div class="menu-item active">📁 项目管理</div>
|
||||||
|
<div class="menu-item">👥 用户管理</div>
|
||||||
|
<div class="menu-item">📈 项目统计</div>
|
||||||
|
</div>
|
||||||
|
<div style="flex: 1">
|
||||||
|
<div class="header">
|
||||||
|
<div class="logo">海洋项目管理系统</div>
|
||||||
|
<div class="user-info">
|
||||||
|
<span>张三 (市场部)</span>
|
||||||
|
<button class="btn-primary" style="height: 28px; font-size: 12px;">登出</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="content">
|
||||||
|
<h2 style="margin-bottom: 24px; font-size: 24px; font-weight: 600; color: #333;">项目管理</h2>
|
||||||
|
<div class="toolbar">
|
||||||
|
<div class="toolbar-left">
|
||||||
|
<input type="text" class="search-input" placeholder="搜索项目名称、合同编号">
|
||||||
|
<select class="select-input">
|
||||||
|
<option>工程类别</option>
|
||||||
|
<option>基建</option>
|
||||||
|
<option>业扩</option>
|
||||||
|
<option>客户</option>
|
||||||
|
</select>
|
||||||
|
<select class="select-input">
|
||||||
|
<option>所属项目部</option>
|
||||||
|
<option>项目部一</option>
|
||||||
|
<option>项目部二</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<button class="btn-primary">+ 新建项目</button>
|
||||||
|
</div>
|
||||||
|
<div class="table-container">
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>合同编号</th>
|
||||||
|
<th>项目名称</th>
|
||||||
|
<th>工程类别</th>
|
||||||
|
<th>签订日期</th>
|
||||||
|
<th>合同金额(万元)</th>
|
||||||
|
<th>状态</th>
|
||||||
|
<th>操作</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>PRJ2026001</td>
|
||||||
|
<td>某电力基建工程项目</td>
|
||||||
|
<td><span class="tag tag-blue">基建</span></td>
|
||||||
|
<td>2026-01-25</td>
|
||||||
|
<td>950.00</td>
|
||||||
|
<td><span class="tag tag-green">进行中</span></td>
|
||||||
|
<td>
|
||||||
|
<span class="action-link">查看</span>
|
||||||
|
<span class="action-link">编辑</span>
|
||||||
|
<span class="action-link" style="color: #ff4d4f;">删除</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>PRJ2026002</td>
|
||||||
|
<td>业扩工程项目</td>
|
||||||
|
<td><span class="tag tag-blue">业扩</span></td>
|
||||||
|
<td>2026-01-24</td>
|
||||||
|
<td>500.00</td>
|
||||||
|
<td><span class="tag tag-green">进行中</span></td>
|
||||||
|
<td>
|
||||||
|
<span class="action-link">查看</span>
|
||||||
|
<span class="action-link">编辑</span>
|
||||||
|
<span class="action-link" style="color: #ff4d4f;">删除</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>PRJ2026003</td>
|
||||||
|
<td>客户工程项目</td>
|
||||||
|
<td><span class="tag tag-blue">客户</span></td>
|
||||||
|
<td>2026-01-23</td>
|
||||||
|
<td>300.00</td>
|
||||||
|
<td><span class="tag" style="background: #f5f5f5; color: #999;">已完成</span></td>
|
||||||
|
<td>
|
||||||
|
<span class="action-link">查看</span>
|
||||||
|
<span class="action-link">编辑</span>
|
||||||
|
<span class="action-link" style="color: #ff4d4f;">删除</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<div class="pagination">
|
||||||
|
<button class="page-btn"><</button>
|
||||||
|
<button class="page-btn active">1</button>
|
||||||
|
<button class="page-btn">2</button>
|
||||||
|
<button class="page-btn">3</button>
|
||||||
|
<button class="page-btn">></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Project Detail Page -->
|
||||||
|
<div id="project-detail" class="page">
|
||||||
|
<div class="main-layout">
|
||||||
|
<div class="sidebar">
|
||||||
|
<div class="menu-item">📊 仪表盘</div>
|
||||||
|
<div class="menu-item active">📁 项目管理</div>
|
||||||
|
<div class="menu-item">👥 用户管理</div>
|
||||||
|
<div class="menu-item">📈 项目统计</div>
|
||||||
|
</div>
|
||||||
|
<div style="flex: 1">
|
||||||
|
<div class="header">
|
||||||
|
<div class="logo">海洋项目管理系统</div>
|
||||||
|
<div class="user-info">
|
||||||
|
<span>张三 (市场部)</span>
|
||||||
|
<button class="btn-primary" style="height: 28px; font-size: 12px;">登出</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="content">
|
||||||
|
<div class="breadcrumb">首页 > 项目管理 > 项目详情</div>
|
||||||
|
<div class="page-header">
|
||||||
|
<h1 class="page-title">某电力基建工程项目</h1>
|
||||||
|
<div class="btn-group">
|
||||||
|
<button class="btn-edit">编辑</button>
|
||||||
|
<button class="btn-delete">删除</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="info-grid">
|
||||||
|
<div class="info-card">
|
||||||
|
<div class="info-card-title">基本信息</div>
|
||||||
|
<div class="info-item">
|
||||||
|
<div class="info-item-label">合同编号</div>
|
||||||
|
<div class="info-item-value">PRJ2026001</div>
|
||||||
|
</div>
|
||||||
|
<div class="info-item">
|
||||||
|
<div class="info-item-label">项目名称</div>
|
||||||
|
<div class="info-item-value">某电力基建工程项目</div>
|
||||||
|
</div>
|
||||||
|
<div class="info-item">
|
||||||
|
<div class="info-item-label">工程类别</div>
|
||||||
|
<div class="info-item-value"><span class="tag tag-blue">基建</span></div>
|
||||||
|
</div>
|
||||||
|
<div class="info-item">
|
||||||
|
<div class="info-item-label">合同金额</div>
|
||||||
|
<div class="info-item-value">950.00 万元</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="info-card">
|
||||||
|
<div class="info-card-title">项目时间</div>
|
||||||
|
<div class="info-item">
|
||||||
|
<div class="info-item-label">签订日期</div>
|
||||||
|
<div class="info-item-value">2026-01-25</div>
|
||||||
|
</div>
|
||||||
|
<div class="info-item">
|
||||||
|
<div class="info-item-label">开工日期</div>
|
||||||
|
<div class="info-item-value">2026-01-15</div>
|
||||||
|
</div>
|
||||||
|
<div class="info-item">
|
||||||
|
<div class="info-item-label">计划竣工日期</div>
|
||||||
|
<div class="info-item-value">2026-12-31</div>
|
||||||
|
</div>
|
||||||
|
<div class="info-item">
|
||||||
|
<div class="info-item-label">实际竣工日期</div>
|
||||||
|
<div class="info-item-value">-</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="info-card">
|
||||||
|
<div class="info-card-title">成本管理</div>
|
||||||
|
<div class="info-item">
|
||||||
|
<div class="info-item-label">总体成本控制</div>
|
||||||
|
<div class="info-item-value">800.00 万元</div>
|
||||||
|
</div>
|
||||||
|
<div class="info-item">
|
||||||
|
<div class="info-item-label">人工成本控制</div>
|
||||||
|
<div class="info-item-value">300.00 万元</div>
|
||||||
|
</div>
|
||||||
|
<div class="info-item">
|
||||||
|
<div class="info-item-label">材料成本控制</div>
|
||||||
|
<div class="info-item-value">400.00 万元</div>
|
||||||
|
</div>
|
||||||
|
<div class="info-item">
|
||||||
|
<div class="info-item-label">其他费用控制</div>
|
||||||
|
<div class="info-item-value">100.00 万元</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="info-card">
|
||||||
|
<div class="info-card-title">合同财务</div>
|
||||||
|
<div class="info-item">
|
||||||
|
<div class="info-item-label">合同金额</div>
|
||||||
|
<div class="info-item-value">950.00 万元</div>
|
||||||
|
</div>
|
||||||
|
<div class="info-item">
|
||||||
|
<div class="info-item-label">质保金比例</div>
|
||||||
|
<div class="info-item-value">5.00%</div>
|
||||||
|
</div>
|
||||||
|
<div class="info-item">
|
||||||
|
<div class="info-item-label">质保金金额</div>
|
||||||
|
<div class="info-item-value">47.50 万元</div>
|
||||||
|
</div>
|
||||||
|
<div class="info-item">
|
||||||
|
<div class="info-item-label">质保到期日</div>
|
||||||
|
<div class="info-item-value">2028-12-31</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="info-card">
|
||||||
|
<div class="info-card-title">收款付款</div>
|
||||||
|
<div class="info-item">
|
||||||
|
<div class="info-item-label">项目进度</div>
|
||||||
|
<div class="info-item-value">60.00%</div>
|
||||||
|
</div>
|
||||||
|
<div class="info-item">
|
||||||
|
<div class="info-item-label">应收款金额</div>
|
||||||
|
<div class="info-item-value">570.00 万元</div>
|
||||||
|
</div>
|
||||||
|
<div class="info-item">
|
||||||
|
<div class="info-item-label">实际收款金额</div>
|
||||||
|
<div class="info-item-value">475.00 万元</div>
|
||||||
|
</div>
|
||||||
|
<div class="info-item">
|
||||||
|
<div class="info-item-label">收款完成率</div>
|
||||||
|
<div class="info-item-value">50.00%</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="info-card">
|
||||||
|
<div class="info-card-title">结算信息</div>
|
||||||
|
<div class="info-item">
|
||||||
|
<div class="info-item-label">成本结算金额</div>
|
||||||
|
<div class="info-item-value">-</div>
|
||||||
|
</div>
|
||||||
|
<div class="info-item">
|
||||||
|
<div class="info-item-label">到期结算项目</div>
|
||||||
|
<div class="info-item-value">0 个</div>
|
||||||
|
</div>
|
||||||
|
<div class="info-item">
|
||||||
|
<div class="info-item-label">未结算项目</div>
|
||||||
|
<div class="info-item-value">0 个</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="info-card full-width">
|
||||||
|
<div class="info-card-title">项目管理</div>
|
||||||
|
<div class="info-item">
|
||||||
|
<div class="info-item-label">所属项目部</div>
|
||||||
|
<div class="info-item-value">项目部一</div>
|
||||||
|
</div>
|
||||||
|
<div class="info-item">
|
||||||
|
<div class="info-item-label">项目负责人</div>
|
||||||
|
<div class="info-item-value">李四 13900000001</div>
|
||||||
|
</div>
|
||||||
|
<div class="info-item">
|
||||||
|
<div class="info-item-label">工程款拨付方式</div>
|
||||||
|
<div class="info-item-value">按进度付款</div>
|
||||||
|
</div>
|
||||||
|
<div class="info-item">
|
||||||
|
<div class="info-item-label">存在的问题</div>
|
||||||
|
<div class="info-item-value">-</div>
|
||||||
|
</div>
|
||||||
|
<div class="info-item">
|
||||||
|
<div class="info-item-label">建议措施</div>
|
||||||
|
<div class="info-item-value">-</div>
|
||||||
|
</div>
|
||||||
|
<div class="info-item">
|
||||||
|
<div class="info-item-label">备注</div>
|
||||||
|
<div class="info-item-value">备注信息</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function showPage(pageId) {
|
||||||
|
// Hide all pages
|
||||||
|
document.querySelectorAll('.page').forEach(page => {
|
||||||
|
page.classList.remove('active');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Remove active class from all buttons
|
||||||
|
document.querySelectorAll('.page-selector button').forEach(btn => {
|
||||||
|
btn.classList.remove('active');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Show selected page
|
||||||
|
document.getElementById(pageId).classList.add('active');
|
||||||
|
|
||||||
|
// Add active class to clicked button
|
||||||
|
event.target.classList.add('active');
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
-963
@@ -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 <token>`)
|
|
||||||
- **响应格式**:
|
|
||||||
```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 <token>`
|
|
||||||
- **响应**:
|
|
||||||
```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 <token>`
|
|
||||||
- **响应**:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"success": true,
|
|
||||||
"message": "登出成功"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 6.3 用户管理API
|
|
||||||
|
|
||||||
#### 5.3.1 获取用户列表
|
|
||||||
- **URL**: `GET /api/v1/users`
|
|
||||||
- **Header**: `Authorization: Bearer <token>`
|
|
||||||
- **权限**: 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 <token>`
|
|
||||||
- **权限**: 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 <token>`
|
|
||||||
- **权限**: admin
|
|
||||||
- **请求体**:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"real_name": "张三",
|
|
||||||
"email": "zhangsan2@example.com",
|
|
||||||
"phone": "13900139000"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 5.3.4 删除用户
|
|
||||||
- **URL**: `DELETE /api/v1/users/{id}`
|
|
||||||
- **Header**: `Authorization: Bearer <token>`
|
|
||||||
- **权限**: admin
|
|
||||||
|
|
||||||
#### 5.3.5 重置用户密码
|
|
||||||
- **URL**: `POST /api/v1/users/{id}/reset-password`
|
|
||||||
- **Header**: `Authorization: Bearer <token>`
|
|
||||||
- **权限**: admin
|
|
||||||
- **请求体**:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"new_password": "newpassword123"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 6.4 项目管理API
|
|
||||||
|
|
||||||
#### 5.4.1 获取项目列表
|
|
||||||
- **URL**: `GET /api/v1/projects`
|
|
||||||
- **Header**: `Authorization: Bearer <token>`
|
|
||||||
- **权限**: 所有用户
|
|
||||||
- **查询参数**:
|
|
||||||
- `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 <token>`
|
|
||||||
- **权限**: 所有用户
|
|
||||||
|
|
||||||
#### 5.4.3 创建项目
|
|
||||||
- **URL**: `POST /api/v1/projects`
|
|
||||||
- **Header**: `Authorization: Bearer <token>`
|
|
||||||
- **权限**: 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 <token>`
|
|
||||||
- **权限**: 所有用户
|
|
||||||
- **请求体**:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"payment_amount": 50000.00,
|
|
||||||
"status": "进行中",
|
|
||||||
"description": "项目更新描述"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 5.4.5 删除项目
|
|
||||||
- **URL**: `DELETE /api/v1/projects/{id}`
|
|
||||||
- **Header**: `Authorization: Bearer <token>`
|
|
||||||
- **权限**: 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/) - 项目设计方案和技术设计文档
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**文档维护**: 本文档由技术总监维护,如有疑问请联系技术总监。
|
|
||||||
@@ -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
|
||||||
|
**审核状态**: 待审核
|
||||||
+403
@@ -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
|
||||||
+620
@@ -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 <token>`)
|
||||||
|
- **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
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||||
|
className={`btn btn-${type} ${className}`}
|
||||||
|
disabled={disabled || loading}
|
||||||
|
onClick={onClick}
|
||||||
|
{...rest}
|
||||||
|
>
|
||||||
|
{loading && <span className="spinner" />}
|
||||||
|
{children}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
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 (
|
||||||
|
<button
|
||||||
|
className={classNames(
|
||||||
|
'btn',
|
||||||
|
`btn-${type}`,
|
||||||
|
`btn-${size}`,
|
||||||
|
{
|
||||||
|
'btn-loading': loading,
|
||||||
|
'btn-block': block,
|
||||||
|
'btn-ghost': ghost,
|
||||||
|
'btn-danger': danger,
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
disabled={disabled || loading}
|
||||||
|
onClick={handleClick}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{loading && <Spin size="small" className="btn-loading-icon" />}
|
||||||
|
{icon && !loading && <span className="btn-icon">{icon}</span>}
|
||||||
|
{children}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
**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 (
|
||||||
|
<AntTable
|
||||||
|
columns={columns}
|
||||||
|
dataSource={dataSource}
|
||||||
|
loading={loading}
|
||||||
|
pagination={pagination}
|
||||||
|
rowKey={rowKey}
|
||||||
|
rowSelection={rowSelection}
|
||||||
|
onRow={onRow}
|
||||||
|
scroll={scroll}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
**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 (
|
||||||
|
<AntForm
|
||||||
|
form={currentForm}
|
||||||
|
initialValues={initialValues}
|
||||||
|
onFinish={onFinish}
|
||||||
|
onFinishFailed={onFinishFailed}
|
||||||
|
layout={layout}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</AntForm>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.4 Modal(弹窗组件)
|
||||||
|
|
||||||
|
```jsx
|
||||||
|
/**
|
||||||
|
* 弹窗组件
|
||||||
|
*/
|
||||||
|
const Modal = ({
|
||||||
|
visible,
|
||||||
|
title,
|
||||||
|
onOk,
|
||||||
|
onCancel,
|
||||||
|
okText = '确定',
|
||||||
|
cancelText = '取消',
|
||||||
|
confirmLoading = false,
|
||||||
|
width = 520,
|
||||||
|
children,
|
||||||
|
...props
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<AntModal
|
||||||
|
visible={visible}
|
||||||
|
title={title}
|
||||||
|
onOk={onOk}
|
||||||
|
onCancel={onCancel}
|
||||||
|
okText={okText}
|
||||||
|
cancelText={cancelText}
|
||||||
|
confirmLoading={confirmLoading}
|
||||||
|
width={width}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</AntModal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.5 Card(卡片组件)
|
||||||
|
|
||||||
|
```jsx
|
||||||
|
/**
|
||||||
|
* 卡片组件
|
||||||
|
*/
|
||||||
|
const Card = ({
|
||||||
|
title,
|
||||||
|
extra,
|
||||||
|
bordered = true,
|
||||||
|
hoverable = false,
|
||||||
|
loading = false,
|
||||||
|
children,
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<AntCard
|
||||||
|
title={title}
|
||||||
|
extra={extra}
|
||||||
|
bordered={bordered}
|
||||||
|
hoverable={hoverable}
|
||||||
|
loading={loading}
|
||||||
|
className={className}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</AntCard>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### 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 (
|
||||||
|
<AntTag color={color || colorMap[type]} {...props}>
|
||||||
|
{children}
|
||||||
|
</AntTag>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
## 4. 业务组件设计
|
||||||
|
|
||||||
|
### 4.1 ProjectCard(项目卡片)
|
||||||
|
|
||||||
|
```jsx
|
||||||
|
/**
|
||||||
|
* 项目卡片组件
|
||||||
|
*/
|
||||||
|
const ProjectCard = ({
|
||||||
|
project,
|
||||||
|
onView,
|
||||||
|
onEdit,
|
||||||
|
onDelete,
|
||||||
|
showActions = true,
|
||||||
|
}) => {
|
||||||
|
const { hasRole } = usePermission();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card
|
||||||
|
hoverable
|
||||||
|
title={
|
||||||
|
<div className="project-card-header">
|
||||||
|
<span>{project.name}</span>
|
||||||
|
<Tag type={project.engineering_type}>
|
||||||
|
{project.engineering_type}
|
||||||
|
</Tag>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
extra={
|
||||||
|
<span className="project-card-no">{project.project_no}</span>
|
||||||
|
}
|
||||||
|
className="project-card"
|
||||||
|
>
|
||||||
|
<div className="project-card-content">
|
||||||
|
<div className="project-card-item">
|
||||||
|
<span className="label">合同金额:</span>
|
||||||
|
<AmountDisplay value={project.contract_amount} />
|
||||||
|
</div>
|
||||||
|
<div className="project-card-item">
|
||||||
|
<span className="label">项目负责人:</span>
|
||||||
|
<span>{project.project_leader}</span>
|
||||||
|
</div>
|
||||||
|
<div className="project-card-item">
|
||||||
|
<span className="label">签订日期:</span>
|
||||||
|
<span>{project.signing_date}</span>
|
||||||
|
</div>
|
||||||
|
<ProgressBar
|
||||||
|
percent={project.cumulative_progress}
|
||||||
|
status={project.cumulative_progress === 100 ? 'success' : 'active'}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{showActions && (
|
||||||
|
<div className="project-card-actions">
|
||||||
|
<Button type="link" onClick={() => onView(project.id)}>
|
||||||
|
查看
|
||||||
|
</Button>
|
||||||
|
{hasRole(['admin']) || project.created_by === getCurrentUserId() ? (
|
||||||
|
<>
|
||||||
|
<Button type="link" onClick={() => onEdit(project.id)}>
|
||||||
|
编辑
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
type="link"
|
||||||
|
danger
|
||||||
|
onClick={() => onDelete(project.id)}
|
||||||
|
>
|
||||||
|
删除
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.2 StatCard(统计卡片)
|
||||||
|
|
||||||
|
```jsx
|
||||||
|
/**
|
||||||
|
* 统计卡片组件
|
||||||
|
*/
|
||||||
|
const StatCard = ({ title, value, icon, color, prefix, suffix }) => {
|
||||||
|
return (
|
||||||
|
<Card className="stat-card">
|
||||||
|
<div className="stat-card-content">
|
||||||
|
<div className="stat-card-left">
|
||||||
|
<div className="stat-card-title">{title}</div>
|
||||||
|
<div className="stat-card-value">
|
||||||
|
{prefix && <span className="prefix">{prefix}</span>}
|
||||||
|
{typeof value === 'number' ? value.toLocaleString() : value}
|
||||||
|
{suffix && <span className="suffix">{suffix}</span>}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{icon && (
|
||||||
|
<div className="stat-card-right">
|
||||||
|
<div className={`stat-card-icon stat-card-icon-${color}`}>
|
||||||
|
{icon}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
**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 <Tag color={statusInfo.color}>{statusInfo.text}</Tag>;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.4 AmountDisplay(金额显示)
|
||||||
|
|
||||||
|
```jsx
|
||||||
|
/**
|
||||||
|
* 金额显示组件
|
||||||
|
*/
|
||||||
|
const AmountDisplay = ({ value, unit = '万元', precision = 2 }) => {
|
||||||
|
const formatAmount = (val) => {
|
||||||
|
if (val === null || val === undefined) return '-';
|
||||||
|
return val.toFixed(precision);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<span className="amount-display">
|
||||||
|
{formatAmount(value)} <span className="unit">{unit}</span>
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.5 ProgressBar(进度条)
|
||||||
|
|
||||||
|
```jsx
|
||||||
|
/**
|
||||||
|
* 进度条组件
|
||||||
|
*/
|
||||||
|
const ProgressBar = ({ percent, status = 'active', showText = true }) => {
|
||||||
|
return (
|
||||||
|
<div className="progress-bar-wrapper">
|
||||||
|
<Progress
|
||||||
|
percent={Math.round(percent)}
|
||||||
|
status={status}
|
||||||
|
showInfo={showText}
|
||||||
|
strokeColor={{
|
||||||
|
'0%': '#108ee9',
|
||||||
|
'100%': '#87d068',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.6 DateRangePicker(日期范围选择器)
|
||||||
|
|
||||||
|
```jsx
|
||||||
|
/**
|
||||||
|
* 日期范围选择器组件
|
||||||
|
*/
|
||||||
|
const DateRangePicker = ({ value, onChange, placeholder }) => {
|
||||||
|
const { RangePicker } = DatePicker;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<RangePicker
|
||||||
|
value={value}
|
||||||
|
onChange={onChange}
|
||||||
|
placeholder={placeholder || ['开始日期', '结束日期']}
|
||||||
|
style={{ width: '100%' }}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
## 5. 高阶组件
|
||||||
|
|
||||||
|
### 5.1 withAuth(认证高阶组件)
|
||||||
|
|
||||||
|
```jsx
|
||||||
|
/**
|
||||||
|
* 认证高阶组件
|
||||||
|
*/
|
||||||
|
const withAuth = (WrappedComponent, requiredRoles = []) => {
|
||||||
|
return (props) => {
|
||||||
|
const { user, isAuthenticated } = useAuth();
|
||||||
|
|
||||||
|
if (!isAuthenticated) {
|
||||||
|
return <Navigate to="/login" replace />;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (requiredRoles.length > 0 && !requiredRoles.includes(user?.role)) {
|
||||||
|
return <Navigate to="/403" replace />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <WrappedComponent {...props} />;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.2 withLoading(加载状态高阶组件)
|
||||||
|
|
||||||
|
```jsx
|
||||||
|
/**
|
||||||
|
* 加载状态高阶组件
|
||||||
|
*/
|
||||||
|
const withLoading = (WrappedComponent) => {
|
||||||
|
return ({ loading, ...props }) => {
|
||||||
|
if (loading) {
|
||||||
|
return <Spin />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <WrappedComponent {...props} />;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
## 6. 组件复用策略
|
||||||
|
|
||||||
|
### 6.1 通过Props自定义
|
||||||
|
- 提供灵活的Props接口
|
||||||
|
- 支持自定义样式和类名
|
||||||
|
- 支持自定义渲染内容
|
||||||
|
|
||||||
|
### 6.2 通过插槽模式
|
||||||
|
- 使用 `children` 传递内容
|
||||||
|
- 使用 `render` 属性传递渲染函数
|
||||||
|
|
||||||
|
### 6.3 通过组合模式
|
||||||
|
- 将复杂组件拆分为多个小组件
|
||||||
|
- 通过组合实现复杂功能
|
||||||
|
|
||||||
|
## 7. 组件性能优化
|
||||||
|
|
||||||
|
### 7.1 React.memo
|
||||||
|
```jsx
|
||||||
|
const MemoComponent = React.memo(({ data }) => {
|
||||||
|
return <div>{data}</div>;
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### 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';
|
||||||
|
|
||||||
|
<List
|
||||||
|
width={600}
|
||||||
|
height={400}
|
||||||
|
rowCount={data.length}
|
||||||
|
rowHeight={50}
|
||||||
|
rowRenderer={({ index, key, style }) => (
|
||||||
|
<div key={key} style={style}>
|
||||||
|
{data[index]}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
```
|
||||||
|
|
||||||
|
## 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(<Button>Click me</Button>);
|
||||||
|
expect(screen.getByText('Click me')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('calls onClick when clicked', () => {
|
||||||
|
const handleClick = jest.fn();
|
||||||
|
render(<Button onClick={handleClick}>Click</Button>);
|
||||||
|
fireEvent.click(screen.getByText('Click'));
|
||||||
|
expect(handleClick).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('shows loading state', () => {
|
||||||
|
render(<Button loading>Click</Button>);
|
||||||
|
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) => <Button {...args} />;
|
||||||
|
|
||||||
|
export const Primary = Template.bind({});
|
||||||
|
Primary.args = {
|
||||||
|
type: 'primary',
|
||||||
|
children: 'Primary Button',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Secondary = Template.bind({});
|
||||||
|
Secondary.args = {
|
||||||
|
type: 'default',
|
||||||
|
children: 'Default Button',
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
## 10. 组件最佳实践
|
||||||
|
|
||||||
|
### 10.1 避免过度抽象
|
||||||
|
- 不要为了复用而过度抽象
|
||||||
|
- 保持组件的简单和直观
|
||||||
|
|
||||||
|
### 10.2 合理拆分组件
|
||||||
|
- 单一职责原则
|
||||||
|
- 组件不应过大(建议不超过300行)
|
||||||
|
|
||||||
|
### 10.3 Props类型检查
|
||||||
|
- 使用PropTypes进行类型检查
|
||||||
|
- 提供默认值
|
||||||
|
|
||||||
|
### 10.4 错误边界
|
||||||
|
```jsx
|
||||||
|
class ErrorBoundary extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = { hasError: false };
|
||||||
|
}
|
||||||
|
|
||||||
|
static getDerivedStateFromError(error) {
|
||||||
|
return { hasError: true };
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidCatch(error, errorInfo) {
|
||||||
|
console.error('Error:', error, errorInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
if (this.state.hasError) {
|
||||||
|
return <div>Something went wrong.</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.props.children;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**文档维护**: 前端程序员
|
||||||
|
**文档类型**: 组件设计文档
|
||||||
|
**最后更新**: 2026-01-25
|
||||||
@@ -0,0 +1,882 @@
|
|||||||
|
# 前端开发指南
|
||||||
|
|
||||||
|
## 1. 开发环境搭建
|
||||||
|
|
||||||
|
### 1.1 安装依赖
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 安装Node.js
|
||||||
|
# 推荐版本:18.x 或更高
|
||||||
|
|
||||||
|
# 进入frontend目录
|
||||||
|
cd frontend
|
||||||
|
|
||||||
|
# 安装依赖
|
||||||
|
npm install
|
||||||
|
```
|
||||||
|
|
||||||
|
### 1.2 环境变量配置
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 复制环境变量模板
|
||||||
|
cp .env.example .env
|
||||||
|
|
||||||
|
# 编辑.env文件
|
||||||
|
VITE_API_BASE_URL=http://localhost:5000/api/v1
|
||||||
|
VITE_APP_TITLE=海洋项目管理系统
|
||||||
|
```
|
||||||
|
|
||||||
|
### 1.3 启动开发服务器
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 启动开发服务器
|
||||||
|
npm run dev
|
||||||
|
|
||||||
|
# 访问 http://localhost:3000
|
||||||
|
```
|
||||||
|
|
||||||
|
### 1.4 常用命令
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 开发
|
||||||
|
npm run dev
|
||||||
|
|
||||||
|
# 构建
|
||||||
|
npm run build
|
||||||
|
|
||||||
|
# 预览构建
|
||||||
|
npm run preview
|
||||||
|
|
||||||
|
# 代码检查
|
||||||
|
npm run lint
|
||||||
|
|
||||||
|
# 代码格式化
|
||||||
|
npm run format
|
||||||
|
|
||||||
|
# 运行测试
|
||||||
|
npm run test
|
||||||
|
|
||||||
|
# 测试覆盖率
|
||||||
|
npm run test:coverage
|
||||||
|
```
|
||||||
|
|
||||||
|
## 2. 代码规范
|
||||||
|
|
||||||
|
### 2.1 ESLint配置
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// .eslintrc.js
|
||||||
|
module.exports = {
|
||||||
|
root: true,
|
||||||
|
env: {
|
||||||
|
browser: true,
|
||||||
|
es2021: true,
|
||||||
|
node: true,
|
||||||
|
},
|
||||||
|
extends: [
|
||||||
|
'eslint:recommended',
|
||||||
|
'plugin:react/recommended',
|
||||||
|
'plugin:react-hooks/recommended',
|
||||||
|
'plugin:react/jsx-runtime',
|
||||||
|
],
|
||||||
|
parserOptions: {
|
||||||
|
ecmaFeatures: {
|
||||||
|
jsx: true,
|
||||||
|
},
|
||||||
|
ecmaVersion: 'latest',
|
||||||
|
sourceType: 'module',
|
||||||
|
},
|
||||||
|
plugins: ['react', 'react-hooks'],
|
||||||
|
rules: {
|
||||||
|
'react/prop-types': 'off',
|
||||||
|
'react/react-in-jsx-scope': 'off',
|
||||||
|
'no-console': ['warn', { allow: ['warn', 'error'] }],
|
||||||
|
},
|
||||||
|
settings: {
|
||||||
|
react: {
|
||||||
|
version: 'detect',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.2 Prettier配置
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// .prettierrc
|
||||||
|
{
|
||||||
|
"semi": true,
|
||||||
|
"singleQuote": true,
|
||||||
|
"tabWidth": 2,
|
||||||
|
"trailingComma": "es5",
|
||||||
|
"printWidth": 100,
|
||||||
|
"arrowParens": "avoid",
|
||||||
|
"endOfLine": "lf"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.3 Git提交规范
|
||||||
|
|
||||||
|
```
|
||||||
|
格式:[类型] 简短描述
|
||||||
|
|
||||||
|
类型:
|
||||||
|
- feat: 新功能
|
||||||
|
- fix: 修复bug
|
||||||
|
- style: 代码格式调整
|
||||||
|
- refactor: 重构
|
||||||
|
- test: 测试
|
||||||
|
- docs: 文档
|
||||||
|
- chore: 构建/工具链
|
||||||
|
|
||||||
|
示例:
|
||||||
|
[frontend] feat: 添加项目列表页面
|
||||||
|
[frontend] fix: 修复登录后Token未保存的问题
|
||||||
|
[frontend] style: 统一代码格式
|
||||||
|
[frontend] refactor: 重构API服务模块
|
||||||
|
[frontend] test: 添加组件单元测试
|
||||||
|
[frontend] docs: 更新开发文档
|
||||||
|
[frontend] chore: 更新依赖版本
|
||||||
|
```
|
||||||
|
|
||||||
|
## 3. 目录结构说明
|
||||||
|
|
||||||
|
### 3.1 src目录
|
||||||
|
|
||||||
|
```
|
||||||
|
src/
|
||||||
|
├── assets/ # 静态资源
|
||||||
|
│ ├── images/ # 图片
|
||||||
|
│ ├── fonts/ # 字体
|
||||||
|
│ └── styles/ # 样式文件
|
||||||
|
├── components/ # 组件
|
||||||
|
│ ├── Layout/ # 布局组件
|
||||||
|
│ ├── Common/ # 通用组件
|
||||||
|
│ └── Business/ # 业务组件
|
||||||
|
├── pages/ # 页面组件
|
||||||
|
├── contexts/ # Context上下文
|
||||||
|
├── hooks/ # 自定义Hooks
|
||||||
|
├── services/ # API服务
|
||||||
|
├── utils/ # 工具函数
|
||||||
|
├── config/ # 配置文件
|
||||||
|
└── router/ # 路由配置
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.2 文件命名规范
|
||||||
|
|
||||||
|
```
|
||||||
|
组件:PascalCase
|
||||||
|
- Button.jsx
|
||||||
|
- ProjectList.jsx
|
||||||
|
- UserForm.jsx
|
||||||
|
|
||||||
|
Hooks:camelCase
|
||||||
|
- useAuth.js
|
||||||
|
- useTable.js
|
||||||
|
- useApi.js
|
||||||
|
|
||||||
|
工具函数:camelCase
|
||||||
|
- formatDate.js
|
||||||
|
- formatMoney.js
|
||||||
|
- validate.js
|
||||||
|
|
||||||
|
样式文件:kebab-case
|
||||||
|
- button.css
|
||||||
|
- project-list.css
|
||||||
|
- user-form.css
|
||||||
|
```
|
||||||
|
|
||||||
|
## 4. 组件开发规范
|
||||||
|
|
||||||
|
### 4.1 函数式组件
|
||||||
|
|
||||||
|
```jsx
|
||||||
|
// 推荐:使用函数式组件 + Hooks
|
||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
|
||||||
|
const MyComponent = ({ title, data }) => {
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// 副作用逻辑
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return <div>{title}</div>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default MyComponent;
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.2 Props类型检查
|
||||||
|
|
||||||
|
```jsx
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
|
const MyComponent = ({ title, count, onAdd }) => {
|
||||||
|
return <div>{title}: {count}</div>;
|
||||||
|
};
|
||||||
|
|
||||||
|
MyComponent.propTypes = {
|
||||||
|
title: PropTypes.string.isRequired,
|
||||||
|
count: PropTypes.number,
|
||||||
|
onAdd: PropTypes.func,
|
||||||
|
};
|
||||||
|
|
||||||
|
MyComponent.defaultProps = {
|
||||||
|
count: 0,
|
||||||
|
onAdd: () => {},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default MyComponent;
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.3 事件处理
|
||||||
|
|
||||||
|
```jsx
|
||||||
|
// 推荐:使用箭头函数或bind绑定
|
||||||
|
const MyComponent = () => {
|
||||||
|
const handleClick = () => {
|
||||||
|
console.log('Clicked');
|
||||||
|
};
|
||||||
|
|
||||||
|
return <button onClick={handleClick}>Click me</button>;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 不推荐:在JSX中直接定义函数
|
||||||
|
const MyComponent = () => {
|
||||||
|
return <button onClick={() => console.log('Clicked')}>Click me</button>;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
## 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 <div className={styles.container}>Content</div>;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### 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 <StyledDiv>Content</StyledDiv>;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6.3 Tailwind CSS(可选)
|
||||||
|
|
||||||
|
```jsx
|
||||||
|
// 使用Tailwind CSS工具类
|
||||||
|
const MyComponent = () => {
|
||||||
|
return (
|
||||||
|
<div className="p-5 bg-gray-100 hover:bg-gray-200">
|
||||||
|
Content
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
## 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 (
|
||||||
|
<MyContext.Provider value={{ state, setState }}>
|
||||||
|
{children}
|
||||||
|
</MyContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
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 (
|
||||||
|
<Routes>
|
||||||
|
<Route path="/" element={<Navigate to="/dashboard" replace />} />
|
||||||
|
<Route path="/dashboard" element={<Dashboard />} />
|
||||||
|
<Route path="/projects" element={<ProjectList />} />
|
||||||
|
<Route path="/projects/:id" element={<ProjectDetail />} />
|
||||||
|
</Routes>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### 8.2 路由参数
|
||||||
|
|
||||||
|
```jsx
|
||||||
|
import { useParams, useSearchParams } from 'react-router-dom';
|
||||||
|
|
||||||
|
const ProjectDetail = () => {
|
||||||
|
const { id } = useParams();
|
||||||
|
const [searchParams] = useSearchParams();
|
||||||
|
|
||||||
|
const keyword = searchParams.get('keyword') || '';
|
||||||
|
|
||||||
|
return <div>Project ID: {id}</div>;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### 8.3 路由守卫
|
||||||
|
|
||||||
|
```jsx
|
||||||
|
const PrivateRoute = ({ children }) => {
|
||||||
|
const { isAuthenticated } = useAuth();
|
||||||
|
|
||||||
|
if (!isAuthenticated) {
|
||||||
|
return <Navigate to="/login" replace />;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 <div>{/* 渲染列表 */}</div>;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### 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 <Table {...} />;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
## 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 <div>Something went wrong.</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.props.children;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用
|
||||||
|
<ErrorBoundary>
|
||||||
|
<App />
|
||||||
|
</ErrorBoundary>
|
||||||
|
```
|
||||||
|
|
||||||
|
### 10.2 错误处理组件
|
||||||
|
|
||||||
|
```jsx
|
||||||
|
const ErrorFallback = ({ error, resetErrorBoundary }) => {
|
||||||
|
return (
|
||||||
|
<div role="alert">
|
||||||
|
<p>Something went wrong:</p>
|
||||||
|
<pre>{error.message}</pre>
|
||||||
|
<button onClick={resetErrorBoundary}>Try again</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
## 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 (
|
||||||
|
<Suspense fallback={<Spin />}>
|
||||||
|
<Routes>
|
||||||
|
<Route path="/projects" element={<ProjectList />} />
|
||||||
|
<Route path="/projects/:id" element={<ProjectDetail />} />
|
||||||
|
</Routes>
|
||||||
|
</Suspense>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### 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 <div onClick={handleClick}>Content</div>;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
## 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(<Button>Click me</Button>);
|
||||||
|
expect(screen.getByText('Click me')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('calls onClick when clicked', () => {
|
||||||
|
const handleClick = jest.fn();
|
||||||
|
render(<Button onClick={handleClick}>Click</Button>);
|
||||||
|
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(<BrowserRouter>{component}</BrowserRouter>);
|
||||||
|
};
|
||||||
|
|
||||||
|
test('user can login', async () => {
|
||||||
|
renderWithRouter(<App />);
|
||||||
|
|
||||||
|
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`);
|
||||||
|
};
|
||||||
|
|
||||||
|
<Profiler id="MyComponent" onRender={onRenderCallback}>
|
||||||
|
<MyComponent />
|
||||||
|
</Profiler>
|
||||||
|
```
|
||||||
|
|
||||||
|
## 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包裹
|
||||||
|
<Suspense fallback={<Loading />}>
|
||||||
|
<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 (
|
||||||
|
<Form form={form} onFinish={onFinish} layout="vertical">
|
||||||
|
<Form.Item
|
||||||
|
name="username"
|
||||||
|
label="用户名"
|
||||||
|
rules={[{ required: true, message: '请输入用户名' }]}
|
||||||
|
>
|
||||||
|
<Input />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item>
|
||||||
|
<Button type="primary" htmlType="submit">提交</Button>
|
||||||
|
</Form.Item>
|
||||||
|
</Form>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**文档维护**: 前端程序员
|
||||||
|
**文档类型**: 前端开发指南
|
||||||
|
**最后更新**: 2026-01-25
|
||||||
@@ -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 <div>{title}</div>;
|
||||||
|
};
|
||||||
|
|
||||||
|
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 (
|
||||||
|
<AuthContext.Provider
|
||||||
|
value={{
|
||||||
|
user,
|
||||||
|
token,
|
||||||
|
loading,
|
||||||
|
login,
|
||||||
|
logout,
|
||||||
|
checkAuth,
|
||||||
|
isAuthenticated: !!user,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</AuthContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 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 (
|
||||||
|
<ThemeContext.Provider value={{ theme, toggleTheme }}>
|
||||||
|
{children}
|
||||||
|
</ThemeContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.2 本地状态管理
|
||||||
|
- 使用 `useState` 管理组件内部状态
|
||||||
|
- 使用 `useReducer` 管理复杂的状态逻辑
|
||||||
|
|
||||||
|
### 4.3 表单状态管理
|
||||||
|
- 使用 Ant Design Form 组件
|
||||||
|
- 使用 `react-hook-form`(可选)
|
||||||
|
|
||||||
|
## 5. 路由设计
|
||||||
|
|
||||||
|
### 5.1 路由配置
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// router/routes.js
|
||||||
|
const routes = [
|
||||||
|
{
|
||||||
|
path: '/login',
|
||||||
|
element: <Login />,
|
||||||
|
meta: { title: '登录', requiresAuth: false },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/',
|
||||||
|
element: <Layout />,
|
||||||
|
meta: { title: '主页', requiresAuth: true },
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: '/dashboard',
|
||||||
|
element: <Dashboard />,
|
||||||
|
meta: { title: '仪表盘', icon: 'DashboardOutlined' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/projects',
|
||||||
|
element: <ProjectList />,
|
||||||
|
meta: { title: '项目管理', icon: 'FileTextOutlined' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/projects/create',
|
||||||
|
element: <ProjectForm />,
|
||||||
|
meta: { title: '创建项目' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/projects/:id',
|
||||||
|
element: <ProjectDetail />,
|
||||||
|
meta: { title: '项目详情' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/projects/:id/edit',
|
||||||
|
element: <ProjectForm />,
|
||||||
|
meta: { title: '编辑项目' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/users',
|
||||||
|
element: <UserList />,
|
||||||
|
meta: { title: '用户管理', icon: 'UserOutlined', requiresRole: ['admin'] },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/users/create',
|
||||||
|
element: <UserForm />,
|
||||||
|
meta: { title: '创建用户', requiresRole: ['admin'] },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/statistics',
|
||||||
|
element: <Statistics />,
|
||||||
|
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 <Navigate to="/login" replace />;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (requiresRole && !requiresRole.includes(user?.role)) {
|
||||||
|
return <Navigate to="/403" replace />;
|
||||||
|
}
|
||||||
|
|
||||||
|
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']) && (
|
||||||
|
<Button type="primary">创建项目</Button>
|
||||||
|
)}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 8. 性能优化
|
||||||
|
|
||||||
|
### 8.1 代码分割
|
||||||
|
```javascript
|
||||||
|
import { lazy, Suspense } from 'react';
|
||||||
|
|
||||||
|
const ProjectList = lazy(() => import('./pages/Projects/ProjectList'));
|
||||||
|
const ProjectDetail = lazy(() => import('./pages/Projects/ProjectDetail'));
|
||||||
|
|
||||||
|
<Suspense fallback={<Spin />}>
|
||||||
|
<ProjectList />
|
||||||
|
</Suspense>
|
||||||
|
```
|
||||||
|
|
||||||
|
### 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
|
||||||
@@ -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: <Login />,
|
||||||
|
meta: {
|
||||||
|
title: '登录',
|
||||||
|
requiresAuth: false,
|
||||||
|
hideInMenu: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/403',
|
||||||
|
element: <Forbidden />,
|
||||||
|
meta: {
|
||||||
|
title: '无权限',
|
||||||
|
requiresAuth: false,
|
||||||
|
hideInMenu: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/404',
|
||||||
|
element: <NotFound />,
|
||||||
|
meta: {
|
||||||
|
title: '页面不存在',
|
||||||
|
requiresAuth: false,
|
||||||
|
hideInMenu: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/',
|
||||||
|
element: <Layout />,
|
||||||
|
meta: {
|
||||||
|
title: '主页',
|
||||||
|
requiresAuth: true,
|
||||||
|
},
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: '/dashboard',
|
||||||
|
element: <Dashboard />,
|
||||||
|
meta: {
|
||||||
|
title: '仪表盘',
|
||||||
|
icon: 'DashboardOutlined',
|
||||||
|
order: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/projects',
|
||||||
|
element: <ProjectList />,
|
||||||
|
meta: {
|
||||||
|
title: '项目管理',
|
||||||
|
icon: 'FileTextOutlined',
|
||||||
|
order: 2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/projects/create',
|
||||||
|
element: <ProjectForm />,
|
||||||
|
meta: {
|
||||||
|
title: '创建项目',
|
||||||
|
parent: '/projects',
|
||||||
|
hideInMenu: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/projects/:id',
|
||||||
|
element: <ProjectDetail />,
|
||||||
|
meta: {
|
||||||
|
title: '项目详情',
|
||||||
|
parent: '/projects',
|
||||||
|
hideInMenu: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/projects/:id/edit',
|
||||||
|
element: <ProjectForm />,
|
||||||
|
meta: {
|
||||||
|
title: '编辑项目',
|
||||||
|
parent: '/projects',
|
||||||
|
hideInMenu: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/users',
|
||||||
|
element: <UserList />,
|
||||||
|
meta: {
|
||||||
|
title: '用户管理',
|
||||||
|
icon: 'UserOutlined',
|
||||||
|
order: 3,
|
||||||
|
requiresRole: ['admin'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/users/create',
|
||||||
|
element: <UserForm />,
|
||||||
|
meta: {
|
||||||
|
title: '创建用户',
|
||||||
|
parent: '/users',
|
||||||
|
requiresRole: ['admin'],
|
||||||
|
hideInMenu: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/users/:id',
|
||||||
|
element: <UserDetail />,
|
||||||
|
meta: {
|
||||||
|
title: '用户详情',
|
||||||
|
parent: '/users',
|
||||||
|
requiresRole: ['admin'],
|
||||||
|
hideInMenu: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/users/:id/edit',
|
||||||
|
element: <UserForm />,
|
||||||
|
meta: {
|
||||||
|
title: '编辑用户',
|
||||||
|
parent: '/users',
|
||||||
|
requiresRole: ['admin'],
|
||||||
|
hideInMenu: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/statistics',
|
||||||
|
element: <Statistics />,
|
||||||
|
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 <Spin />;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查认证状态
|
||||||
|
if (requiresAuth && !isAuthenticated) {
|
||||||
|
return <Navigate to="/login" state={{ from: location }} replace />;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查角色权限
|
||||||
|
if (requiresRole.length > 0 && !requiresRole.includes(user?.role)) {
|
||||||
|
return <Navigate to="/403" replace />;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 (
|
||||||
|
<BrowserRouter>
|
||||||
|
<Routes>
|
||||||
|
{routes.map((route) => {
|
||||||
|
const { path, element, meta, children } = route;
|
||||||
|
|
||||||
|
if (children) {
|
||||||
|
return (
|
||||||
|
<Route
|
||||||
|
key={path}
|
||||||
|
path={path}
|
||||||
|
element={
|
||||||
|
<PrivateRoute meta={meta}>
|
||||||
|
{element}
|
||||||
|
</PrivateRoute>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{children.map((child) => (
|
||||||
|
<Route
|
||||||
|
key={child.path}
|
||||||
|
path={child.path}
|
||||||
|
element={
|
||||||
|
<PrivateRoute meta={child.meta}>
|
||||||
|
{child.element}
|
||||||
|
</PrivateRoute>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</Route>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Route
|
||||||
|
key={path}
|
||||||
|
path={path}
|
||||||
|
element={
|
||||||
|
<PrivateRoute meta={meta}>
|
||||||
|
{element}
|
||||||
|
</PrivateRoute>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
|
||||||
|
{/* 默认重定向 */}
|
||||||
|
<Route path="/" element={<Navigate to="/dashboard" replace />} />
|
||||||
|
|
||||||
|
{/* 404页面 */}
|
||||||
|
<Route path="*" element={<NotFound />} />
|
||||||
|
</Routes>
|
||||||
|
</BrowserRouter>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
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 <Spin />;
|
||||||
|
return <div>{/* 项目详情 */}</div>;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### 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 <Input.Search value={keyword} onChange={(e) => 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 <div>{isEditMode ? '编辑模式' : '创建模式'}</div>;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### 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 (
|
||||||
|
<div>
|
||||||
|
<Button onClick={handleCreate}>创建项目</Button>
|
||||||
|
<Button onClick={() => handleEdit(1)}>编辑项目</Button>
|
||||||
|
<Button onClick={handleBack}>返回</Button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
## 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 <Router />;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
## 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 (
|
||||||
|
<div className="sidebar">
|
||||||
|
<div className="sidebar-logo">
|
||||||
|
<img src="/logo.png" alt="Logo" />
|
||||||
|
<span>海洋项目管理系统</span>
|
||||||
|
</div>
|
||||||
|
<Menu
|
||||||
|
mode="inline"
|
||||||
|
theme="dark"
|
||||||
|
items={menuItems}
|
||||||
|
selectedKeys={selectedKeys}
|
||||||
|
defaultOpenKeys={openKeys}
|
||||||
|
onClick={handleMenuClick}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
## 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 (
|
||||||
|
<Breadcrumb>
|
||||||
|
<Breadcrumb.Item onClick={() => navigate('/home')}>首页</Breadcrumb.Item>
|
||||||
|
{breadcrumbItems.map((item, index) => (
|
||||||
|
<Breadcrumb.Item
|
||||||
|
key={item.path}
|
||||||
|
onClick={() => handleBreadcrumbClick(item.path)}
|
||||||
|
>
|
||||||
|
{item.title}
|
||||||
|
</Breadcrumb.Item>
|
||||||
|
))}
|
||||||
|
</Breadcrumb>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
## 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 (
|
||||||
|
<SwitchTransition>
|
||||||
|
<CSSTransition
|
||||||
|
key={location.pathname}
|
||||||
|
timeout={300}
|
||||||
|
classNames="fade"
|
||||||
|
unmountOnExit
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</CSSTransition>
|
||||||
|
</SwitchTransition>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### 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', <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 (
|
||||||
|
<div>
|
||||||
|
{/* 只有管理员可见 */}
|
||||||
|
<Permission role="admin">
|
||||||
|
<Button>删除项目</Button>
|
||||||
|
</Permission>
|
||||||
|
|
||||||
|
{/* 只有市场部用户可见 */}
|
||||||
|
<Permission role="market">
|
||||||
|
<Button>创建项目</Button>
|
||||||
|
</Permission>
|
||||||
|
|
||||||
|
{/* 没有权限时显示备用内容 */}
|
||||||
|
<Permission permission="edit_project" fallback={<span>无权限</span>}>
|
||||||
|
<Button>编辑项目</Button>
|
||||||
|
</Permission>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
## 10. 路由最佳实践
|
||||||
|
|
||||||
|
### 10.1 路由命名规范
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// 好的路由命名
|
||||||
|
'/projects' // 项目列表
|
||||||
|
'/projects/create' // 创建项目
|
||||||
|
'/projects/:id' // 项目详情
|
||||||
|
'/projects/:id/edit' // 编辑项目
|
||||||
|
|
||||||
|
// 不好的路由命名
|
||||||
|
'/p' // 太短,不清晰
|
||||||
|
'/project-list-page' // 太长,冗余
|
||||||
|
'/projects/1/edit' // 使用硬编码ID
|
||||||
|
```
|
||||||
|
|
||||||
|
### 10.2 路由嵌套
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// 合理的路由嵌套
|
||||||
|
<Route path="/projects" element={<Layout />}>
|
||||||
|
<Route index element={<ProjectList />} />
|
||||||
|
<Route path="create" element={<ProjectForm />} />
|
||||||
|
<Route path=":id" element={<ProjectDetail />} />
|
||||||
|
<Route path=":id/edit" element={<ProjectForm />} />
|
||||||
|
</Route>
|
||||||
|
|
||||||
|
// 不合理的路由嵌套(过深)
|
||||||
|
<Route path="/projects" element={<A />}>
|
||||||
|
<Route path="list" element={<B />}>
|
||||||
|
<Route path=":id" element={<C />}>
|
||||||
|
<Route path="detail" element={<D />} />
|
||||||
|
</Route>
|
||||||
|
</Route>
|
||||||
|
</Route>
|
||||||
|
```
|
||||||
|
|
||||||
|
### 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 (
|
||||||
|
<Suspense fallback={<Spin />}>
|
||||||
|
<Routes>
|
||||||
|
<Route path="/projects" element={<ProjectList />} />
|
||||||
|
<Route path="/projects/:id" element={<ProjectDetail />} />
|
||||||
|
</Routes>
|
||||||
|
</Suspense>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**文档维护**: 前端程序员
|
||||||
|
**文档类型**: 路由设计文档
|
||||||
|
**最后更新**: 2026-01-25
|
||||||
@@ -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 (
|
||||||
|
<AuthContext.Provider value={value}>
|
||||||
|
{children}
|
||||||
|
</AuthContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 使用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 (
|
||||||
|
<ThemeContext.Provider value={value}>
|
||||||
|
{children}
|
||||||
|
</ThemeContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
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 <Child value={value} onChange={setValue} />;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 子组件
|
||||||
|
const Child = ({ value, onChange }) => {
|
||||||
|
return <Input value={value} onChange={(e) => onChange(e.target.value)} />;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
**正确示例:**
|
||||||
|
```jsx
|
||||||
|
// 状态留在子组件
|
||||||
|
const Child = () => {
|
||||||
|
const [value, setValue] = useState('');
|
||||||
|
return <Input value={value} onChange={(e) => setValue(e.target.value)} />;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.3 避免不必要的重渲染
|
||||||
|
|
||||||
|
```jsx
|
||||||
|
// 使用React.memo
|
||||||
|
const ExpensiveComponent = React.memo(({ data }) => {
|
||||||
|
return <div>{/* 渲染逻辑 */}</div>;
|
||||||
|
});
|
||||||
|
|
||||||
|
// 使用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 <AuthContext.Provider value={{ user, timestamp }}>{children}</AuthContext.Provider>;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 正确示例:分离频繁更新的状态
|
||||||
|
const AuthProvider = ({ children }) => {
|
||||||
|
const [user, setUser] = useState(null);
|
||||||
|
const [timestamp, setTimestamp] = useState(Date.now());
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setInterval(() => {
|
||||||
|
setTimestamp(Date.now());
|
||||||
|
}, 1000);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<AuthContext.Provider value={{ user }}>{children}</AuthContext.Provider>
|
||||||
|
<TimestampContext.Provider value={{ timestamp}}>{/* 不渲染 */}</TimestampContext.Provider>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### 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 (
|
||||||
|
<AuthContext.Provider value={{ state, dispatch }}>
|
||||||
|
{children}
|
||||||
|
</AuthContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
## 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
|
||||||
Reference in New Issue
Block a user