feat: 完善前后端核心功能模块

后端:
- 新增认证(auth)、任务(tasks)、映射(mappings)、对账(reconciliation)、异常(exceptions)、导出(exports) API
- 新增核心模块: database, security, permissions, tenant, exceptions, error_handlers
- 新增数据模型: user, company, reconciliation_task, field_mapping, uploaded_file 等
- 新增服务层: ai_recognizer, file_parser, file_storage, mapping, reconciliation 等
- 添加数据库迁移脚本

前端:
- 新增登录页面和仪表盘页面
- 新增任务列表、任务详情、字段映射页面
- 新增异常处理页面和规则设置页面
- 新增 API 代理路由 /api/[...path]
- 新增 UI 组件库 (button, card, dialog, input, table 等)
- 新增 auth 组件 (ProtectedRoute, PermissionGate)
- 新增 layout 组件 (Header, Sidebar)
- 新增 mapping 组件 (FieldMappingTable, AISuggestionPanel)
- 新增 API 客户端和 hooks (useAsync, useToast, usePermission 等)
- 新增状态管理 (auth-store, company-store, ui-store)
- 集成 Tailwind CSS 和 shadcn/ui 组件库

其他:
- 添加 Alembic 数据库迁移配置
- 添加初始化示例数据脚本
- 更新项目文档
This commit is contained in:
freedakgmail
2026-07-07 09:04:47 +08:00
parent 8487f6eadf
commit 33b4c734aa
117 changed files with 22070 additions and 295 deletions
+45
View File
@@ -0,0 +1,45 @@
from datetime import datetime
from typing import Any, Optional
from pydantic import BaseModel, ConfigDict, Field
class AuditLogCreate(BaseModel):
"""创建审计日志的 Schema"""
company_id: int = Field(..., description="企业 ID")
user_id: Optional[int] = Field(None, description="用户 ID")
action: str = Field(..., max_length=50, description="操作类型")
resource_type: str = Field(..., max_length=100, description="资源类型")
resource_id: Optional[str] = Field(None, max_length=200, description="资源 ID")
details: Optional[dict[str, Any]] = Field(None, description="操作详情")
ip_address: Optional[str] = Field(None, max_length=50, description="IP 地址")
user_agent: Optional[str] = Field(None, description="User-Agent")
class AuditLogResponse(BaseModel):
"""审计日志响应 Schema"""
model_config = ConfigDict(from_attributes=True)
id: int
company_id: int
user_id: Optional[int]
action: str
resource_type: str
resource_id: Optional[str]
details: Optional[dict[str, Any]]
ip_address: Optional[str]
user_agent: Optional[str]
created_at: datetime
class AuditLogQuery(BaseModel):
"""审计日志查询参数"""
company_id: Optional[int] = None
user_id: Optional[int] = None
action: Optional[str] = None
resource_type: Optional[str] = None
resource_id: Optional[str] = None
start_date: Optional[datetime] = None
end_date: Optional[datetime] = None
skip: int = Field(default=0, ge=0)
limit: int = Field(default=50, ge=1, le=1000)
+38
View File
@@ -0,0 +1,38 @@
from datetime import datetime
from typing import Optional
from pydantic import BaseModel, ConfigDict, Field
class CompanyBase(BaseModel):
"""Company 基础 Schema"""
name: str = Field(..., min_length=1, max_length=200, description="企业名称")
plan: str = Field(default="免费版", description="套餐类型")
data_retention_months: int = Field(default=12, ge=1, le=120, description="数据保留月数")
max_users: int = Field(default=5, ge=1, description="最大用户数")
class CompanyCreate(CompanyBase):
"""创建 Company 的 Schema"""
pass
class CompanyUpdate(BaseModel):
"""更新 Company 的 Schema"""
name: Optional[str] = Field(None, min_length=1, max_length=200)
plan: Optional[str] = None
data_retention_months: Optional[int] = Field(None, ge=1, le=120)
status: Optional[str] = None
max_users: Optional[int] = Field(None, ge=1)
is_trial: Optional[bool] = None
class CompanyResponse(CompanyBase):
"""Company 响应 Schema"""
model_config = ConfigDict(from_attributes=True)
id: int
status: str
is_trial: bool
created_at: datetime
updated_at: datetime
+147
View File
@@ -0,0 +1,147 @@
"""
字段映射相关 Pydantic Schema
"""
from datetime import datetime
from typing import List, Optional, Dict, Any
from pydantic import BaseModel, Field
# ===== 字段映射 Schema =====
class FieldMappingBase(BaseModel):
"""字段映射基础 schema"""
source_field: str = Field(..., description="源字段名")
standard_field: str = Field(..., description="标准字段名")
confidence: float = Field(..., ge=0, le=1, description="置信度 0-1")
reasoning: Optional[str] = Field(None, description="AI 判断依据")
sample_values: Optional[List[Any]] = Field(None, description="样例值")
is_skipped: bool = Field(False, description="是否跳过")
class FieldMappingCreate(FieldMappingBase):
"""创建字段映射"""
file_id: int = Field(..., description="文件ID")
class FieldMappingUpdate(BaseModel):
"""更新字段映射"""
standard_field: Optional[str] = Field(None, description="标准字段名")
is_skipped: Optional[bool] = Field(None, description="是否跳过")
class FieldMappingResponse(FieldMappingBase):
"""字段映射响应"""
id: int
company_id: int
file_id: int
confirmed: bool
confirmed_by: Optional[int] = None
confirmed_at: Optional[datetime] = None
class Config:
from_attributes = True
class FieldMappingWithFileResponse(FieldMappingResponse):
"""带文件信息的字段映射响应"""
file_type: Optional[str] = None
file_name: Optional[str] = None
# ===== 批量操作 Schema =====
class ConfirmMappingsRequest(BaseModel):
"""批量确认映射请求"""
mapping_ids: List[int] = Field(..., description="要确认的映射ID列表")
class ConfirmMappingsResponse(BaseModel):
"""批量确认映射响应"""
confirmed_count: int = Field(..., description="确认数量")
mappings: List[FieldMappingResponse]
class SaveAsRuleRequest(BaseModel):
"""保存为规则请求"""
mapping_id: int = Field(..., description="映射ID")
class SaveAsRuleResponse(BaseModel):
"""保存为规则响应"""
rule_id: int
source_field: str
target_field: str
message: str
# ===== 规则 Schema =====
class CompanyRuleBase(BaseModel):
"""企业规则基础 schema"""
rule_type: str = Field(..., description="规则类型")
match_condition: Dict[str, Any] = Field(..., description="匹配条件")
target_value: str = Field(..., description="目标值")
priority: int = Field(0, description="优先级")
description: Optional[str] = Field(None, description="规则描述")
class CompanyRuleResponse(CompanyRuleBase):
"""企业规则响应"""
id: int
company_id: int
status: str
match_count: int
last_used_at: Optional[datetime] = None
created_at: datetime
updated_at: datetime
class Config:
from_attributes = True
class UpdateRuleStatusRequest(BaseModel):
"""更新规则状态请求"""
status: str = Field(..., description="状态 ACTIVE/INACTIVE")
class RecognizeFieldsRequest(BaseModel):
"""识别字段请求"""
file_id: int = Field(..., description="文件ID")
file_type: str = Field("工资表", description="文件类型")
class RecognizeFieldsResponse(BaseModel):
"""识别字段响应"""
file_id: int
mappings: List[FieldMappingResponse]
high_confidence_count: int = Field(..., description="高置信度数量")
medium_confidence_count: int = Field(..., description="中等置信度数量")
low_confidence_count: int = Field(..., description="低置信度数量")
needs_review: bool = Field(..., description="是否需要人工确认")
# ===== 文件映射概览 =====
class FileMappingSummary(BaseModel):
"""文件映射概览"""
file_id: int
file_type: str
file_name: str
total_fields: int
confirmed_fields: int
high_confidence: int
medium_confidence: int
low_confidence: int
mappings: List[FieldMappingResponse]
class TaskMappingOverview(BaseModel):
"""任务映射概览"""
task_id: int
total_files: int
total_fields: int
confirmed_fields: int
needs_review: bool
files: List[FileMappingSummary]
+56
View File
@@ -0,0 +1,56 @@
from datetime import datetime
from typing import Optional
from pydantic import BaseModel, ConfigDict, EmailStr, Field
class UserBase(BaseModel):
"""User 基础 Schema"""
email: EmailStr = Field(..., description="邮箱")
full_name: str = Field(..., min_length=1, max_length=100, description="姓名")
role: str = Field(default="会计", description="角色")
class UserCreate(UserBase):
"""创建用户的 Schema"""
password: str = Field(..., min_length=6, max_length=50, description="密码")
company_id: int = Field(..., description="企业ID")
class UserUpdate(BaseModel):
"""更新用户的 Schema"""
full_name: Optional[str] = Field(None, min_length=1, max_length=100)
role: Optional[str] = None
permissions: Optional[dict] = None
status: Optional[str] = None
class UserResponse(UserBase):
"""用户响应 Schema"""
model_config = ConfigDict(from_attributes=True)
id: int
company_id: int
status: str
permissions: Optional[dict]
last_login_at: Optional[datetime]
created_at: datetime
updated_at: datetime
class UserLogin(BaseModel):
"""用户登录 Schema"""
email: EmailStr = Field(..., description="邮箱")
password: str = Field(..., description="密码")
class Token(BaseModel):
"""Token Schema"""
access_token: str
token_type: str = "bearer"
class TokenData(BaseModel):
"""Token 数据 Schema"""
user_id: Optional[int] = None
email: Optional[str] = None