工作统计系统采用前后端分离架构,后端使用Python Flask框架提供RESTful API,前端使用React构建单页应用,数据存储使用PostgreSQL(测试环境使用SQLite)。系统支持人员管理、物品管理、工作记录管理,以及Excel报表导出功能。
graph TB
subgraph Frontend
React[React SPA]
end
subgraph Backend
API[Flask API Server]
Services[Business Services]
Models[SQLAlchemy Models]
Export[Excel Export Service]
end
subgraph Database
PG[(PostgreSQL/SQLite)]
end
React -->|HTTP GET/POST| API
API --> Services
Services --> Models
Services --> Export
Models --> PG
backend/
├── app/
│ ├── __init__.py # Flask app factory
│ ├── config.py # Configuration
│ ├── models/
│ │ ├── __init__.py
│ │ ├── person.py # Person model
│ │ ├── item.py # Item model
│ │ └── work_record.py # WorkRecord model
│ ├── routes/
│ │ ├── __init__.py
│ │ ├── person.py # Person API routes
│ │ ├── item.py # Item API routes
│ │ ├── work_record.py # WorkRecord API routes
│ │ └── export.py # Export API routes
│ ├── services/
│ │ ├── __init__.py
│ │ ├── person_service.py
│ │ ├── item_service.py
│ │ ├── work_record_service.py
│ │ └── export_service.py
│ └── utils/
│ ├── __init__.py
│ └── validators.py
├── tests/
│ ├── __init__.py
│ ├── conftest.py
│ ├── test_person.py
│ ├── test_item.py
│ ├── test_work_record.py
│ └── test_export.py
├── requirements.txt
└── run.py
系统使用 Flask-RESTX 自动生成 Swagger API 文档:
http://localhost:5000/api/docs功能:
支持导出 OpenAPI 3.0 规范
# Flask-RESTX 配置示例
from flask_restx import Api, Resource, fields
api = Api(
title='Work Statistics API',
version='1.0',
description='工作统计系统 API 文档',
doc='/api/docs'
)
# 定义数据模型
person_model = api.model('Person', {
'id': fields.Integer(description='人员ID'),
'name': fields.String(required=True, description='人员姓名')
})
frontend/
├── src/
│ ├── components/
│ │ ├── Layout.jsx # 主布局(侧边栏+内容区)
│ │ ├── PersonList.jsx # 人员列表(Table组件)
│ │ ├── PersonForm.jsx # 人员表单(Modal+Form)
│ │ ├── ItemList.jsx # 物品列表
│ │ ├── ItemForm.jsx # 物品表单
│ │ ├── WorkRecordList.jsx # 工作记录列表(带筛选)
│ │ ├── WorkRecordForm.jsx # 工作记录表单
│ │ └── Dashboard.jsx # 仪表盘(统计卡片+图表)
│ ├── services/
│ │ └── api.js # API调用封装
│ ├── App.jsx
│ └── index.jsx
├── package.json
└── vite.config.js
使用 Ant Design 组件库,遵循以下设计原则:
| 页面 | 主要组件 | 功能 |
|---|---|---|
| 人员管理 | Table + Modal + Form | 列表展示、新增/编辑弹窗 |
| 物品管理 | Table + Modal + Form | 列表展示、新增/编辑弹窗 |
| 工作记录 | Table + DatePicker + Select | 列表、日期筛选、人员筛选 |
| 仪表盘 | Card + Statistic | 今日统计、快捷操作 |
| 导出 | Button + DatePicker | 选择月份/年份导出Excel |
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/persons |
List all persons |
| GET | /api/persons/<id> |
Get person by ID |
| POST | /api/persons/create |
Create new person |
| POST | /api/persons/update |
Update person |
| POST | /api/persons/delete |
Delete person |
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/items |
List all items |
| GET | /api/items/<id> |
Get item by ID |
| POST | /api/items/create |
Create new item |
| POST | /api/items/update |
Update item |
| POST | /api/items/delete |
Delete item |
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/work-records |
List work records (with filters) |
| GET | /api/work-records/<id> |
Get work record by ID |
| POST | /api/work-records/create |
Create new work record |
| POST | /api/work-records/update |
Update work record |
| POST | /api/work-records/delete |
Delete work record |
| GET | /api/work-records/daily-summary |
Get daily summary |
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/export/monthly?year=&month= |
Export monthly Excel |
| GET | /api/export/yearly?year= |
Export yearly Excel |
All API responses follow this structure:
{
"success": true,
"data": { ... },
"message": "Operation successful"
}
Error response:
{
"success": false,
"error": "Error message",
"code": "VALIDATION_ERROR"
}
class Person:
id: int (primary key, auto-increment)
name: str (required, non-empty)
created_at: datetime
updated_at: datetime
class Item:
id: int (primary key, auto-increment)
name: str (required, non-empty)
unit_price: float (required, positive, supports decimal values like 10.50)
created_at: datetime
updated_at: datetime
Note: unit_price 使用浮点数类型,支持小数点(如 10.50、25.75 等)。在数据库中使用 NUMERIC(10, 2) 或 FLOAT 类型存储以保证精度。
class WorkRecord:
id: int (primary key, auto-increment)
person_id: int (foreign key -> Person.id)
item_id: int (foreign key -> Item.id)
work_date: date (required)
quantity: int (required, positive)
created_at: datetime
updated_at: datetime
# Computed property
@property
def total_price(self) -> float:
return self.item.unit_price * self.quantity # Returns float, e.g., 10.50 * 5 = 52.50
Note: total_price 是计算属性,返回浮点数,等于 unit_price * quantity。
erDiagram
Person ||--o{ WorkRecord : has
Item ||--o{ WorkRecord : has
Person {
int id PK
string name
datetime created_at
datetime updated_at
}
Item {
int id PK
string name
decimal unit_price
datetime created_at
datetime updated_at
}
WorkRecord {
int id PK
int person_id FK
int item_id FK
date work_date
int quantity
datetime created_at
datetime updated_at
}
| 人员 | 日期 | 物品 | 单价 | 数量 | 总价 |
|---|---|---|---|---|---|
| 张三 | 2024-01-01 | 物品A | 10.50 | 5 | 52.50 |
| 张三 | 2024-01-01 | 物品B | 20.75 | 3 | 62.25 |
| 人员 | 总金额 |
|---|---|
| 张三 | 1500.00 |
| 李四 | 2000.00 |
| 合计 | 3500.00 |
| 人员 | 1月 | 2月 | ... | 12月 | 年度合计 |
|---|---|---|---|---|---|
| 张三 | 1500 | 1800 | ... | 2000 | 20000 |
| 李四 | 2000 | 2200 | ... | 2500 | 25000 |
| 合计 | 3500 | 4000 | ... | 4500 | 45000 |
A property is a characteristic or behavior that should hold true across all valid executions of a system—essentially, a formal statement about what the system should do. Properties serve as the bridge between human-readable specifications and machine-verifiable correctness guarantees.
For any valid person name, creating a person, then retrieving it by ID, should return the same name. Updating the person's name and retrieving again should return the new name. Deleting the person should make it no longer retrievable.
Validates: Requirements 1.1, 1.2, 1.3, 1.4
For any string composed entirely of whitespace (including empty string), attempting to create or update a person with that name should be rejected with a validation error.
Validates: Requirements 1.5
For any valid item (non-empty name, positive unit_price), creating an item, then retrieving it by ID, should return the same name and unit_price. Updating the item and retrieving again should return the updated values. Deleting the item should make it no longer retrievable.
Validates: Requirements 2.1, 2.2, 2.3, 2.4
For any item with empty name or non-positive unit_price, attempting to create or update should be rejected with a validation error.
Validates: Requirements 2.5
For any work record with a valid person, item, and positive quantity, the total_price should equal item.unit_price * quantity.
Validates: Requirements 3.1, 3.3
For any set of work records and a filter (person_id or date_range), the filtered results should contain only records matching the filter criteria.
Validates: Requirements 3.2
For any quantity that is zero or negative, attempting to create or update a work record should be rejected with a validation error.
Validates: Requirements 3.5
For any non-existent person_id or item_id, attempting to create a work record should be rejected with a reference error.
Validates: Requirements 3.6
For any date with work records, the daily summary total for each person should equal the sum of their individual work record total_prices for that date.
Validates: Requirements 4.1, 4.2
For any year and month with work records, the exported Excel should contain exactly all work records for that month, and the summary sheet totals should equal the sum of detail records.
Validates: Requirements 5.1, 5.3
For any year with work records, the exported Excel should contain exactly all work records for that year, and the monthly breakdown totals should sum to the yearly total.
Validates: Requirements 6.1, 6.3
For any API request, the response should contain either {success: true, data: ...} or {success: false, error: ...} structure.
Validates: Requirements 8.3
For any person or item with associated work records, deleting that person or item should either fail with an error or cascade delete the associated work records (based on configuration).
Validates: Requirements 7.4
{
"success": False,
"error": "Human-readable error message",
"code": "ERROR_CODE", # e.g., VALIDATION_ERROR, NOT_FOUND, REFERENCE_ERROR
"details": {} # Optional additional details
}
Unit tests focus on specific examples and edge cases:
Property-based tests verify universal properties across many generated inputs:
# conftest.py
import pytest
from hypothesis import settings
# Configure hypothesis for minimum 100 examples
settings.register_profile("ci", max_examples=100)
settings.load_profile("ci")