提交
This commit is contained in:
1 parent
48f15745be
commit
e421030ada
72 files changed
+17064
-35
No files matched your search
@@ -0,0 +1,636 @@
|
||||
# 学生操行分管理模块
|
||||
|
||||
## 📋 模块基本信息
|
||||
|
||||
**模块名称**: 学生操行分管理 / Student Conduct Score Management
|
||||
**负责人**: 开发团队
|
||||
**开发状态**: 🟡 待开发
|
||||
**技术栈**: Spring Boot 3.4.6 + Vue 3 + TypeScript + Element Plus
|
||||
**依赖模块**: 用户管理、班级管理、学生管理
|
||||
**开发周期**: 待定
|
||||
|
||||
---
|
||||
|
||||
## 🎯 模块概述
|
||||
|
||||
### 业务价值
|
||||
|
||||
为学校提供完整的学生操行分管理解决方案,实现班主任每月对班级学生操行表现的量化评价和管理,帮助学生处管理员掌握学生的学期表现情况,为学生综合素质评价提供数据支持。
|
||||
|
||||
### 核心功能
|
||||
|
||||
- **班主任月度评分**: 班主任每月为班级学生录入操行分数
|
||||
- **学期平均分统计**: 自动计算学生的学期操行平均分
|
||||
- **多维度数据展示**: 按学年、学期、月份等维度展示学生操行表现
|
||||
- **权限分级管理**: 班主任只能管理本班学生,管理员可查看全校数据
|
||||
|
||||
### 技术特点
|
||||
|
||||
- **数据完整性**: 严格的数据验证和业务规则约束
|
||||
- **性能优化**: 支持大数据量查询和统计分析
|
||||
- **权限安全**: 基于角色的精细化权限控制
|
||||
- **用户体验**: 友好的批量录入和数据展示界面
|
||||
|
||||
---
|
||||
|
||||
## 📁 文档结构
|
||||
|
||||
```
|
||||
学生操行分管理/
|
||||
├── README.md # 本文件 - 模块总览
|
||||
├── 需求分析/
|
||||
│ ├── 业务需求说明.md # 详细业务需求
|
||||
│ ├── 功能需求清单.md # 功能点清单
|
||||
│ ├── 用户故事.md # 用户使用场景
|
||||
│ └── 非功能需求.md # 性能、安全要求
|
||||
├── 技术设计/
|
||||
│ ├── 数据库设计.md # 表结构设计
|
||||
│ ├── API接口设计.md # 接口定义
|
||||
│ ├── 架构设计.md # 模块架构
|
||||
│ └── 技术选型.md # 技术决策说明
|
||||
├── 实现文档/
|
||||
│ ├── 后端实现说明.md # 后端代码结构
|
||||
│ ├── 前端实现说明.md # 前端代码结构
|
||||
│ ├── 关键业务逻辑.md # 核心算法逻辑
|
||||
│ └── 代码规范.md # 编码规范
|
||||
├── 测试文档/
|
||||
│ ├── 测试计划.md # 测试策略
|
||||
│ ├── 测试用例.md # 具体测试用例
|
||||
│ └── 测试报告.md # 测试结果
|
||||
└── 运维文档/
|
||||
├── 部署指南.md # 部署步骤
|
||||
├── 监控配置.md # 监控指标
|
||||
└── 故障处理.md # 常见问题
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🏗️ 系统架构
|
||||
|
||||
### 模块架构图
|
||||
|
||||
```mermaid
|
||||
graph TB
|
||||
subgraph "前端层"
|
||||
A1[操行分录入页面<br/>ConductScoreInput.vue]
|
||||
A2[学期统计页面<br/>SemesterStatistics.vue]
|
||||
A3[操行分查询页面<br/>ConductScoreQuery.vue]
|
||||
end
|
||||
|
||||
subgraph "接口层"
|
||||
B1[操行分Controller<br/>ConductScoreController]
|
||||
B2[统计Controller<br/>StatisticsController]
|
||||
end
|
||||
|
||||
subgraph "业务层"
|
||||
C1[操行分Service<br/>IConductScoreService]
|
||||
C2[统计Service<br/>IStatisticsService]
|
||||
C3[权限Service<br/>IPermissionService]
|
||||
end
|
||||
|
||||
subgraph "数据层"
|
||||
D1[操行分Mapper<br/>ConductScoreMapper]
|
||||
D2[统计SQL<br/>统计查询语句]
|
||||
D3[Redis缓存<br/>缓存统计结果]
|
||||
end
|
||||
|
||||
A1 --> B1
|
||||
A2 --> B2
|
||||
A3 --> B1
|
||||
B1 --> C1
|
||||
B2 --> C2
|
||||
C1 --> C3
|
||||
C2 --> C3
|
||||
C1 --> D1
|
||||
C2 --> D1
|
||||
C2 --> D2
|
||||
D2 --> D3
|
||||
```
|
||||
|
||||
### 数据流向
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant T as 班主任
|
||||
participant F as 前端
|
||||
participant C as Controller
|
||||
participant S as Service
|
||||
participant M as Mapper
|
||||
participant D as Database
|
||||
|
||||
T->>F: 选择月份录入操行分
|
||||
F->>C: POST /api/conduct-score/batch
|
||||
C->>S: 批量保存操行分
|
||||
S->>M: 插入/更新分数数据
|
||||
M->>D: SQL执行
|
||||
D-->>M: 返回结果
|
||||
M-->>S: 返回成功
|
||||
S-->>C: 返回结果
|
||||
C-->>F: JSON响应
|
||||
F-->>T: 显示保存成功
|
||||
|
||||
Note over T,D: 学生处管理员查询学期统计
|
||||
T->>F: 选择学年学期查询
|
||||
F->>C: GET /api/statistics/semester-average
|
||||
C->>S: 计算学期平均分
|
||||
S->>M: 查询统计数据
|
||||
M->>D: SQL执行
|
||||
D-->>M: 返回原始数据
|
||||
M-->>S: 计算平均分
|
||||
S-->>C: 返回统计结果
|
||||
C-->>F: JSON响应
|
||||
F-->>T: 显示统计表格
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎯 核心功能模块
|
||||
|
||||
### 1. 操行分录入管理
|
||||
|
||||
#### 业务描述
|
||||
|
||||
班主任每月为负责的班级学生录入操行分数,包含分数和备注信息。如果班主任负责多个班级,则通过多Tab页方式展示。系统需要验证班主任权限、检查数据完整性、防止重复录入等。
|
||||
|
||||
#### 技术实现
|
||||
|
||||
- **前端实现**: Vue 3 多Tab页组件,支持批量录入和数据验证,每个Tab页独立管理一个班级
|
||||
- **后端实现**: Spring Boot Controller + Service,通过`xg_class_msg.class_teacher_id`获取班主任负责的班级
|
||||
- **数据存储**: MySQL 数据库,支持历史数据追溯
|
||||
- **Tab页逻辑**: 根据班主任负责的班级数量动态生成Tab页,每个Tab页独立保存状态
|
||||
|
||||
#### 关键流程
|
||||
|
||||
1. 系统根据当前用户ID查询`xg_class_msg`表获取负责的班级
|
||||
2. 动态生成Tab页,每个Tab对应一个班级
|
||||
3. 班主任选择录入月份
|
||||
4. 系统加载对应班级学生列表
|
||||
5. 班主任为每位学生录入分数和备注
|
||||
6. 系统验证数据完整性和合理性
|
||||
7. 批量保存到数据库(支持跨Tab页批量保存)
|
||||
8. 更新录入状态记录
|
||||
|
||||
### 2. 学期统计分析
|
||||
|
||||
#### 业务描述
|
||||
|
||||
学生处管理员查看学生的学期操行平均分,系统自动计算每个学生的学期平均表现,并提供导出功能。
|
||||
|
||||
#### 技术实现
|
||||
|
||||
- **前端实现**: 数据表格组件,支持排序、筛选和导出
|
||||
- **后端实现**: 统计查询接口,包含缓存优化
|
||||
- **数据处理**: SQL聚合查询或Java计算逻辑
|
||||
|
||||
#### 关键流程
|
||||
|
||||
1. 管理员选择学年和学期
|
||||
2. 系统查询该学期所有学生的分数记录
|
||||
3. 计算每个学生的平均分
|
||||
4. 按班级或专业分组展示
|
||||
5. 支持数据导出功能
|
||||
|
||||
---
|
||||
|
||||
## 🔌 API 接口概览
|
||||
|
||||
### 接口分类
|
||||
|
||||
#### 基础 CRUD 接口
|
||||
|
||||
| 接口名称 | 请求方式 | 路径 | 功能描述 |
|
||||
| -------------- | -------- | --------------------------- | ---------------------- |
|
||||
| 查询操行分列表 | GET | /api/conduct-score/list | 分页查询操行分记录 |
|
||||
| 查询操行分详情 | GET | /api/conduct-score/{id} | 根据 ID 查询操行分详情 |
|
||||
| 新增操行分 | POST | /api/conduct-score | 创建新的操行分记录 |
|
||||
| 更新操行分 | PUT | /api/conduct-score/{id} | 更新指定操行分记录 |
|
||||
| 删除操行分 | DELETE | /api/conduct-score/{id} | 删除指定操行分记录 |
|
||||
|
||||
#### 业务逻辑接口
|
||||
|
||||
| 接口名称 | 请求方式 | 路径 | 功能描述 |
|
||||
| ---------------- | -------- | ----------------------------- | ------------------------ |
|
||||
| 批量录入操行分 | POST | /api/conduct-score/batch | 批量保存操行分 |
|
||||
| 班主任班级学生 | GET | /api/conduct-score/class-students | 获取班主任班级学生列表 |
|
||||
| 学期平均分统计 | GET | /api/statistics/semester-average | 查询学期平均分 |
|
||||
| 导出学期统计 | GET | /api/statistics/export | 导出学期统计数据 |
|
||||
| 验证录入权限 | GET | /api/conduct-score/check-permission | 检查班主任录入权限 |
|
||||
|
||||
### 接口规范
|
||||
|
||||
- **请求格式**: JSON
|
||||
- **响应格式**: 统一响应体结构
|
||||
- **认证方式**: JWT Token
|
||||
- **权限控制**: 基于角色的权限验证
|
||||
|
||||
---
|
||||
|
||||
## 🗄️ 数据模型
|
||||
|
||||
### 核心实体
|
||||
|
||||
#### 操行分表 (xg_conduct_score)
|
||||
|
||||
| 字段名 | 类型 | 长度 | 非空 | 默认值 | 说明 |
|
||||
| --------------- | ----------- | ------ | ----- | -------- | ------------------------------ |
|
||||
| id | BIGINT | - | Y | - | 主键 ID |
|
||||
| tenant_id | VARCHAR | 20 | Y | 000000 | 租户编号 |
|
||||
| academic_year | INT | 4 | Y | - | 学年(如:2024) |
|
||||
| semester | TINYINT | 1 | Y | - | 学期(1-第一学期,2-第二学期) |
|
||||
| month | TINYINT | 2 | Y | - | 月份(1-12) |
|
||||
| student_id | BIGINT | - | Y | - | 学生ID,关联xg_student_msg.id |
|
||||
| class_id | BIGINT | - | Y | - | 班级ID,关联xg_class_msg.id |
|
||||
| class_teacher_id| BIGINT | - | Y | - | 班主任ID,关联sys_user.id |
|
||||
| score | DECIMAL | 5,2 | Y | - | 操行分数(0.00-100.00) |
|
||||
| remark | VARCHAR | 500 | N | NULL | 备注信息 |
|
||||
| create_dept | BIGINT | - | N | NULL | 创建部门 |
|
||||
| create_by | BIGINT | - | N | NULL | 创建者 |
|
||||
| create_time | DATETIME | - | N | NULL | 创建时间 |
|
||||
| update_by | BIGINT | - | N | NULL | 更新者 |
|
||||
| update_time | DATETIME | - | N | NULL | 更新时间 |
|
||||
| deleted | TINYINT | 1 | Y | 0 | 删除标志(0代表存在 1代表删除)|
|
||||
|
||||
### 实体关系图
|
||||
|
||||
```mermaid
|
||||
erDiagram
|
||||
xg_conduct_score ||--o{ xg_student_msg : "关联学生"
|
||||
xg_conduct_score }o--|| xg_class_msg : "关联班级"
|
||||
xg_conduct_score }o--|| sys_user : "关联班主任"
|
||||
xg_class_msg ||--o{ xg_student_msg : "班级包含学生"
|
||||
xg_class_msg }o--|| sys_user : "班级有班主任"
|
||||
|
||||
xg_conduct_score {
|
||||
bigint id PK
|
||||
int academic_year
|
||||
tinyint semester
|
||||
tinyint month
|
||||
bigint student_id FK
|
||||
bigint class_id FK
|
||||
bigint class_teacher_id FK
|
||||
decimal score
|
||||
varchar remark
|
||||
datetime create_time
|
||||
}
|
||||
|
||||
xg_student_msg {
|
||||
bigint id PK
|
||||
varchar student_no
|
||||
varchar name
|
||||
varchar class_name
|
||||
}
|
||||
|
||||
xg_class_msg {
|
||||
bigint id PK
|
||||
varchar class_name
|
||||
bigint class_teacher_id FK
|
||||
}
|
||||
|
||||
sys_user {
|
||||
bigint id PK
|
||||
varchar user_name
|
||||
varchar nick_name
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔄 业务流程
|
||||
|
||||
### 主要业务流程
|
||||
|
||||
#### 班主任录入操行分流程
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A[班主任登录] --> B{选择录入月份}
|
||||
B --> C[加载班级学生列表]
|
||||
C --> D{学生列表为空?}
|
||||
D -->|是| E[提示无学生数据]
|
||||
D -->|否| F[显示学生列表和录入表单]
|
||||
F --> G[录入分数和备注]
|
||||
G --> H{数据验证通过?}
|
||||
H -->|否| I[显示错误提示]
|
||||
I --> G
|
||||
H -->|是| J[批量保存数据]
|
||||
J --> K{保存成功?}
|
||||
K -->|否| L[显示保存失败信息]
|
||||
L --> J
|
||||
K -->|是| M[更新录入状态]
|
||||
M --> N[显示保存成功]
|
||||
N --> O[结束]
|
||||
```
|
||||
|
||||
#### 学期统计查询流程
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A[管理员选择学年学期] --> B[查询该学期操行分数据]
|
||||
B --> C{有数据?}
|
||||
C -->|否| D[显示无数据提示]
|
||||
C -->|是| E[按学生分组计算平均分]
|
||||
E --> F[按班级排序展示]
|
||||
F --> G{需要导出?}
|
||||
G -->|否| H[显示统计表格]
|
||||
G -->|是| I[生成Excel文件]
|
||||
I --> J[下载文件]
|
||||
J --> H
|
||||
H --> K[结束]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎨 前端设计
|
||||
|
||||
### 页面结构
|
||||
|
||||
#### 操行分录入页面 (ConductScoreInput.vue)
|
||||
|
||||
- **功能**: 班主任每月录入负责班级学生操行分
|
||||
- **组件**: 月份选择器、多Tab页、学生列表表格、批量编辑表单
|
||||
- **交互**: 多Tab页展示不同班级,支持批量保存、数据验证提示
|
||||
- **数据**: 通过`xg_class_msg.class_teacher_id`获取班主任负责的所有班级及学生信息
|
||||
- **特性**: 一个老师负责多个班级的场景,每个班级一个Tab页,Tab间独立录入
|
||||
|
||||
#### 学期统计页面 (SemesterStatistics.vue)
|
||||
|
||||
- **功能**: 学生处管理员查看学期平均分
|
||||
- **组件**: 学年学期选择器、统计数据表格、导出按钮
|
||||
- **交互**: 支持筛选排序、数据导出
|
||||
- **数据**: 学期平均分统计、学生基本信息
|
||||
|
||||
#### 操行分查询页面 (ConductScoreQuery.vue)
|
||||
|
||||
- **功能**: 查询历史操行分记录
|
||||
- **组件**: 查询条件表单、数据表格、分页组件
|
||||
- **交互**: 支持多条件筛选、详情查看
|
||||
- **数据**: 操行分历史记录、关联的学生和班级信息
|
||||
|
||||
### 组件设计
|
||||
|
||||
#### 批量录入组件 (BatchScoreInput.vue)
|
||||
|
||||
```typescript
|
||||
// 组件属性接口
|
||||
interface BatchScoreInputProps {
|
||||
academicYear: number; // 学年
|
||||
semester: number; // 学期
|
||||
month: number; // 月份
|
||||
classId: string; // 班级ID
|
||||
students: StudentInfo[]; // 学生列表
|
||||
}
|
||||
|
||||
// 组件事件接口
|
||||
interface BatchScoreInputEmits {
|
||||
save: [scores: ConductScore[]]; // 保存分数
|
||||
cancel: []; // 取消操作
|
||||
}
|
||||
```
|
||||
|
||||
#### 统计表格组件 (StatisticsTable.vue)
|
||||
|
||||
```typescript
|
||||
// 组件属性接口
|
||||
interface StatisticsTableProps {
|
||||
data: SemesterAverage[]; // 统计数据
|
||||
loading: boolean; // 加载状态
|
||||
showExport: boolean; // 是否显示导出按钮
|
||||
}
|
||||
|
||||
// 组件事件接口
|
||||
interface StatisticsTableEmits {
|
||||
export: [format: 'excel' | 'pdf']; // 导出数据
|
||||
sort: [field: string, order: 'asc' | 'desc']; // 排序
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📝 开发规范
|
||||
|
||||
### 代码结构
|
||||
|
||||
#### 后端代码结构
|
||||
|
||||
```
|
||||
src/main/java/org/dromara/modules/conduct/
|
||||
├── controller/
|
||||
│ ├── ConductScoreController.java
|
||||
│ └── StatisticsController.java
|
||||
├── domain/
|
||||
│ ├── bo/
|
||||
│ │ ├── ConductScoreBo.java
|
||||
│ │ └── ConductScoreQueryBo.java
|
||||
│ └── vo/
|
||||
│ ├── ConductScoreVo.java
|
||||
│ ├── SemesterAverageVo.java
|
||||
│ └── ConductScoreDetailVo.java
|
||||
├── mapper/
|
||||
│ ├── ConductScoreMapper.java
|
||||
│ └── ConductScoreMapper.xml
|
||||
├── service/
|
||||
│ ├── IConductScoreService.java
|
||||
│ ├── IStatisticsService.java
|
||||
│ └── impl/
|
||||
│ ├── ConductScoreServiceImpl.java
|
||||
│ └── StatisticsServiceImpl.java
|
||||
└── utils/
|
||||
└── ConductScoreUtils.java
|
||||
```
|
||||
|
||||
#### 前端代码结构
|
||||
|
||||
```
|
||||
src/views/conduct/
|
||||
├── index.vue # 主页面
|
||||
├── components/
|
||||
│ ├── ConductScoreInput.vue # 录入组件
|
||||
│ ├── SemesterStatistics.vue # 统计组件
|
||||
│ └── ConductScoreQuery.vue # 查询组件
|
||||
├── types/
|
||||
│ └── index.ts # 类型定义
|
||||
└── api/
|
||||
└── index.ts # API接口
|
||||
```
|
||||
|
||||
### 命名规范
|
||||
|
||||
#### 后端命名规范
|
||||
|
||||
- **类名**: PascalCase (如: ConductScoreController, ConductScoreService)
|
||||
- **方法名**: camelCase (如: getConductScoreList, createConductScore)
|
||||
- **常量**: UPPER_SNAKE_CASE (如: MAX_SCORE_VALUE)
|
||||
- **包名**: 全小写 (如: com.example.module)
|
||||
|
||||
#### 前端命名规范
|
||||
|
||||
- **组件名**: PascalCase (如: ConductScoreList, ConductScoreForm)
|
||||
- **方法名**: camelCase (如: handleSubmit, getConductScoreInfo)
|
||||
- **变量名**: camelCase (如: conductScoreList, isLoading)
|
||||
- **常量**: UPPER_SNAKE_CASE (如: API_BASE_URL)
|
||||
|
||||
#### 数据库命名规范
|
||||
|
||||
- **表名**: 下划线命名 (如: xg_conduct_score)
|
||||
- **字段名**: 下划线命名 (如: academic_year, semester)
|
||||
- **索引名**: idx_表名_字段名 (如: idx_conduct_score_student_id)
|
||||
|
||||
---
|
||||
|
||||
## 🧪 测试策略
|
||||
|
||||
### 测试分类
|
||||
|
||||
#### 单元测试
|
||||
|
||||
- **覆盖范围**: Service 层业务逻辑、数据验证逻辑
|
||||
- **测试框架**: JUnit 5 + Mockito
|
||||
- **覆盖率要求**: > 80%
|
||||
|
||||
#### 集成测试
|
||||
|
||||
- **覆盖范围**: Controller 层 API 接口、数据库操作
|
||||
- **测试框架**: Spring Boot Test
|
||||
- **测试数据**: 测试数据库
|
||||
|
||||
#### 前端测试
|
||||
|
||||
- **覆盖范围**: 组件功能测试、表单验证
|
||||
- **测试框架**: Vue Test Utils + Jest
|
||||
- **测试类型**: 组件渲染、用户交互、数据绑定
|
||||
|
||||
### 测试用例设计
|
||||
|
||||
#### 功能测试用例
|
||||
|
||||
- **正常流程测试**: 验证正常的操行分录入和查询流程
|
||||
- **边界值测试**: 验证分数范围、日期范围等边界条件
|
||||
- **异常流程测试**: 验证错误处理和权限控制
|
||||
|
||||
#### 性能测试用例
|
||||
|
||||
- **响应时间测试**: 验证接口响应时间 < 200ms
|
||||
- **并发测试**: 验证批量录入时的并发处理能力
|
||||
- **大数据量测试**: 验证万人规模数据查询性能
|
||||
|
||||
---
|
||||
|
||||
## 🚀 部署运维
|
||||
|
||||
### 部署环境
|
||||
|
||||
#### 开发环境
|
||||
|
||||
- **部署方式**: 本地 Docker 容器
|
||||
- **数据库**: MySQL 8.0
|
||||
- **缓存**: Redis 7.0
|
||||
- **配置**: application-dev.yml
|
||||
|
||||
#### 生产环境
|
||||
|
||||
- **部署方式**: Kubernetes
|
||||
- **数据库**: MySQL 8.0(集群)
|
||||
- **缓存**: Redis 7.0(集群)
|
||||
- **配置**: application-prod.yml
|
||||
|
||||
### 监控指标
|
||||
|
||||
#### 业务指标
|
||||
|
||||
- **录入成功率**: 操行分录入操作的成功率
|
||||
- **查询响应时间**: 统计查询的平均响应时间
|
||||
- **数据完整性**: 操行分数据的完整性检查
|
||||
|
||||
#### 技术指标
|
||||
|
||||
- **接口响应时间**: 平均响应时间 < 200ms
|
||||
- **系统可用性**: 99.9%可用性
|
||||
- **错误率**: 错误率 < 0.1%
|
||||
|
||||
---
|
||||
|
||||
## 📅 开发计划
|
||||
|
||||
### 开发阶段
|
||||
|
||||
#### 第一阶段:基础框架(1周)
|
||||
|
||||
- [ ] 数据库表结构设计和创建
|
||||
- [ ] 基础 CRUD 接口开发
|
||||
- [ ] 基础页面和组件开发
|
||||
- [ ] 单元测试编写
|
||||
|
||||
#### 第二阶段:核心功能(2周)
|
||||
|
||||
- [ ] 操行分录入功能开发
|
||||
- [ ] 学期统计功能开发
|
||||
- [ ] 权限控制逻辑实现
|
||||
- [ ] 集成测试
|
||||
|
||||
#### 第三阶段:完善优化(1周)
|
||||
|
||||
- [ ] 功能完善和用户体验优化
|
||||
- [ ] 性能测试和优化
|
||||
- [ ] 文档完善
|
||||
- [ ] 验收测试
|
||||
|
||||
### 里程碑
|
||||
|
||||
- **里程碑 1**: 基础框架完成
|
||||
- **里程碑 2**: 核心功能完成
|
||||
- **里程碑 3**: 测试完成,准备发布
|
||||
- **里程碑 4**: 正式发布上线
|
||||
|
||||
---
|
||||
|
||||
## 🔧 开发工具
|
||||
|
||||
### 后端开发工具
|
||||
|
||||
- **IDE**: IntelliJ IDEA
|
||||
- **构建工具**: Maven
|
||||
- **代码质量**: SonarQube
|
||||
- **API 测试**: Postman
|
||||
|
||||
### 前端开发工具
|
||||
|
||||
- **IDE**: VSCode
|
||||
- **构建工具**: Vite
|
||||
- **代码规范**: ESLint + Prettier
|
||||
- **组件预览**: Storybook
|
||||
|
||||
### 数据库工具
|
||||
|
||||
- **管理工具**: Navicat Premium
|
||||
- **版本控制**: Flyway
|
||||
- **性能监控**: MySQL Performance Schema
|
||||
|
||||
---
|
||||
|
||||
## 📖 相关文档
|
||||
|
||||
- **项目总览**: `../项目总览/README.md`
|
||||
- **开发规范**: `../开发文档/README.md`
|
||||
- **API 接口**: `../开发文档/API.md`
|
||||
- **数据库设计**: `../设计文档/database/`
|
||||
- **变更记录**: `../变更管理/README.md`
|
||||
|
||||
---
|
||||
|
||||
## 📝 开发注意事项
|
||||
|
||||
### 重要提醒
|
||||
|
||||
1. **数据完整性**: 操行分数据关系到学生评价,必须确保数据准确性和完整性
|
||||
2. **权限控制**: 班主任只能录入本班学生,管理员查看权限需严格控制
|
||||
3. **性能优化**: 统计查询涉及大量数据计算,需要合理设计索引和缓存策略
|
||||
4. **用户体验**: 批量录入功能需要良好的交互设计和数据验证
|
||||
5. **业务规则**: 每月只能录入一次,不可重复录入同一学生的当月分数
|
||||
|
||||
### 技术要点
|
||||
|
||||
1. **雪花ID**: 主键使用雪花ID,确保分布式环境下的唯一性
|
||||
2. **逻辑删除**: 所有删除操作使用逻辑删除,保留历史数据
|
||||
3. **多租户**: 支持多租户数据隔离
|
||||
4. **审计字段**: 完整记录数据的创建和更新信息
|
||||
5. **数据验证**: 前后端都需进行数据验证,确保数据质量
|
||||
@@ -0,0 +1,413 @@
|
||||
# 学生操行分管理 - 界面原型图
|
||||
|
||||
## 📋 文档说明
|
||||
|
||||
**版本**: v1.1
|
||||
**创建日期**: 2025-01-13
|
||||
**更新日期**: 2025-01-13
|
||||
**说明**: 本文档包含学生操行分管理模块所有页面的详细原型设计
|
||||
|
||||
---
|
||||
|
||||
## 目录
|
||||
|
||||
1. [操行分录入界面](#1-操行分录入界面)
|
||||
2. [学期统计界面](#2-学期统计界面)
|
||||
3. [操行分查询界面](#3-操行分查询界面)
|
||||
|
||||
---
|
||||
|
||||
## 1. 操行分录入界面
|
||||
|
||||
### 1.1 主录入界面 (多tab页设计)
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────────────────────────────────────┐
|
||||
│ 🏫 学生操行分录入 👤 李老师 🔔(2) │
|
||||
├──────────────────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ 📅 录入设置 │
|
||||
│ ────────────────────────────────────────────────────────────────────────── │
|
||||
│ 学年: [2024 ▼] 学期: [第一学期 ▼] 月份: [09 ▼] │
|
||||
│ │
|
||||
│ 🏫 负责班级 (3个班级) │
|
||||
│ ────────────────────────────────────────────────────────────────────────── │
|
||||
│ 📑 [计算机1班(45人)] [计算机2班(42人)] [电子1班(38人)] │
|
||||
│ │
|
||||
│ ┌────────────────────────────────────────────────────────────────────┐ │
|
||||
│ │ │ │
|
||||
│ │ 📋 计算机1班 - 学生列表 (共45人) │ │
|
||||
│ │ ─────────────────────────────────────────────────────────────── │ │
|
||||
│ │ │ │
|
||||
│ │ ☑ │学号 │姓名 │性别 │分数 │备注 │状态│ │
|
||||
│ │ ──┼─────────┼───────┼──────┼──────────┼─────────────────────┼────│ │
|
||||
│ │ ☐ │2024001 │张三 │男 │95.5 │表现优秀,积极参与班级活动│✅ │ │
|
||||
│ │ ☐ │2024002 │李四 │女 │ │ │⭕ │ │
|
||||
│ │ ☐ │2024003 │王五 │男 │88.0 │表现良好,需要继续努力 │✅ │ │
|
||||
│ │ ☐ │2024004 │赵六 │女 │92.0 │学习认真,乐于助人 │✅ │ │
|
||||
│ │ ☐ │2024005 │孙七 │男 │ │ │⭕ │ │
|
||||
│ │ ☐ │2024006 │周八 │女 │85.0 │基础扎实,但需加强练习 │✅ │ │
|
||||
│ │ ☐ │2024007 │吴九 │男 │78.0 │学习态度需端正 │✅ │ │
|
||||
│ │ ☐ │2024008 │郑十 │女 │ │ │⭕ │ │
|
||||
│ │ ☐ │2024009 │王一 │男 │90.5 │进步明显,值得表扬 │✅ │ │
|
||||
│ │ ☐ │2024010 │李二 │女 │82.0 │基础一般,但很努力 │✅ │ │
|
||||
│ │ │ │
|
||||
│ │ ◀ 1 2 3 4 5 ▶ 每页显示 [10 ▼] 条 共 45 条 │ │
|
||||
│ │ │ │
|
||||
│ └────────────────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ 📝 批量操作 │
|
||||
│ ────────────────────────────────────────────────────────────────────────── │
|
||||
│ [全选] [取消全选] [批量设置分数] [清空所选] [批量添加备注] │
|
||||
│ │
|
||||
│ 📊 录入统计 │
|
||||
│ ────────────────────────────────────────────────────────────────────────── │
|
||||
│ 总学生: 125人 │ 已录入: 87人 │ 待录入: 38人 │ 完成度: 69.6% │
|
||||
│ │
|
||||
│ 📊 各班进度 │
|
||||
│ ────────────────────────────────────────────────────────────────────────── │
|
||||
│ 计算机1班: 32/45 (71.1%) │
|
||||
│ 计算机2班: 28/42 (66.7%) │
|
||||
│ 电子1班: 27/38 (71.1%) │
|
||||
│ │
|
||||
│ 💡 录入提示: │
|
||||
│ • 分数范围: 0.00-100.00, 支持小数点后2位 │
|
||||
│ • 备注最多500字符 │
|
||||
│ • 同一学生每月只能录入一次分数 │
|
||||
│ • 切换班级Tab时会自动保存当前班级的草稿 │
|
||||
│ │
|
||||
├──────────────────────────────────────────────────────────────────────────────┤
|
||||
│ [保存草稿] [预览数据] [重置表单] [保存并提交] │
|
||||
└──────────────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**功能说明**:
|
||||
- **录入设置**: 选择学年、学期、月份(班主任只能看到自己负责的班级)
|
||||
- **多Tab页设计**: 根据`xg_class_msg.class_teacher_id`自动加载班主任负责的所有班级
|
||||
- **班级Tab**: 每个Tab显示一个班级的学生列表,Tab标题包含班级名称和学生人数
|
||||
- **学生列表**: 显示班级所有学生,支持分页展示
|
||||
- **批量操作**: 支持批量设置分数、清空、添加备注等操作(针对当前Tab的班级)
|
||||
- **录入统计**: 实时显示所有班级的总体录入进度和完成度
|
||||
- **各班进度**: 可视化显示每个班级的录入进度条
|
||||
- **状态标识**: ✅已录入、⭕待录入
|
||||
- **数据验证**: 前端实时验证分数范围和格式
|
||||
- **自动保存**: 切换Tab时自动保存当前班级的草稿数据
|
||||
|
||||
**技术实现说明**:
|
||||
- **数据获取**: 通过API `/api/conduct-score/class-students` 获取班主任负责的所有班级及学生信息
|
||||
- **Tab动态生成**: 根据返回的班级列表动态生成Tab页
|
||||
- **状态保持**: 每个Tab页独立维护录入状态,切换时保存草稿
|
||||
- **批量操作**: 批量操作仅影响当前激活的Tab页班级
|
||||
|
||||
### 1.2 批量设置弹窗
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────────────────────────────────────┐
|
||||
│ 🔧 批量设置分数 [✖ 关闭] │
|
||||
├──────────────────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ 📋 设置范围 │
|
||||
│ ────────────────────────────────────────────────────────────────────────── │
|
||||
│ 适用对象: 已选中 8 个学生 │
|
||||
│ │
|
||||
│ 📊 设置内容 │
|
||||
│ ────────────────────────────────────────────────────────────────────────── │
|
||||
│ │
|
||||
│ ┌─ 统一设置 ───────────────────────────────────────────────────────────┐ │
|
||||
│ │ │ │
|
||||
│ │ 统一分数: [85.0 ________] 分 │ │
|
||||
│ │ 统一备注: [________] (可选) │ │
|
||||
│ │ │ │
|
||||
│ │ 💡 提示: 将为所有选中学生设置相同的分数和备注 │ │
|
||||
│ │ │ │
|
||||
│ └──────────────────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ ⚠️ 重要提醒: │
|
||||
│ • 批量设置将覆盖原有数据 │
|
||||
│ • 建议先预览设置结果再确认 │
|
||||
│ • 可以随时撤销操作 │
|
||||
│ │
|
||||
├──────────────────────────────────────────────────────────────────────────────┤
|
||||
│ [预览结果] [取消] [确认设置] │
|
||||
└──────────────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**功能说明**:
|
||||
- **设置范围**: 显示当前Tab页班级中将要设置的学生数量
|
||||
- **统一设置**: 为当前班级中选中学生设置相同分数和备注
|
||||
- **直接录入**: 班主任直接录入每个学生的分值,无条件设置
|
||||
- **预览功能**: 可以预览设置结果再决定是否执行
|
||||
- **班级隔离**: 批量操作仅影响当前激活的班级Tab页
|
||||
|
||||
---
|
||||
|
||||
## 2. 学期统计界面
|
||||
|
||||
### 2.1 主统计界面 (树形结构布局)
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────────────────────────────────────┐
|
||||
│ 📊 学生操行分学期统计 👤 管理员 📊 │
|
||||
├──────────────────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ ┌─────────────┬────────────────────────────────────────────────────────────┐ │
|
||||
│ │ 🏫 班级组织 │ 📅 查询条件 │ │
|
||||
│ │ ────────── │ ───────────────────────────────────────────────────────── │ │
|
||||
│ │ 📂 计算机学院│ 学年: [2024 ▼] 学期: [第一学期 ▼] │ │
|
||||
│ │ 📂 计算机1班│ [查询] [重置] [导出Excel] [导出PDF] │ │
|
||||
│ │ 📂 计算机2班│ │ │
|
||||
│ │ 📂 电子1班 │ │ │
|
||||
│ │ 📂 电子工程学院│ │ │
|
||||
│ │ 📂 电子2班 │ │ │
|
||||
│ │ 📂 通信1班 │ │ │
|
||||
│ │ 📂 机械工程学院│ │ │
|
||||
│ │ 📂 机械1班 │ │ │
|
||||
│ │ 📂 机械2班 │ │ │
|
||||
│ └─────────────┴────────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ │
|
||||
│ 📋 选中班级学生数据 │
|
||||
│ ────────────────────────────────────────────────────────────────────────── │
|
||||
│ │
|
||||
│ ☑ │学生姓名 │学号 │学期平均分 │录入月份 │操作 │ │
|
||||
│ ──┼─────────┼─────────┼──────────┼────────┼──────────┼──────────────│
|
||||
│ ☐ │张三 │2024001 │92.83 │第一学期 │[详情] [导出]│ │
|
||||
│ ☐ │李四 │2024002 │88.67 │第一学期 │[详情] [导出]│ │
|
||||
│ ☐ │王五 │2024003 │85.50 │第一学期 │[详情] [导出]│ │
|
||||
│ ☐ │赵六 │2024004 │87.33 │第一学期 │[详情] [导出]│ │
|
||||
│ ☐ │孙七 │2024005 │82.17 │第一学期 │[详情] [导出]│ │
|
||||
│ ☐ │周八 │2024006 │79.83 │第一学期 │[详情] [导出]│ │
|
||||
│ │
|
||||
│ ◀ 1 2 3 ▶ 每页显示 [10 ▼] 条 共 45 条 │
|
||||
│ │
|
||||
│ 🔍 筛选条件: │
|
||||
│ [姓名搜索 ___________] [排序 ▼] │
|
||||
│ │
|
||||
├──────────────────────────────────────────────────────────────────────────────┤
|
||||
│ [导出选中] [导出全部] [打印预览] [返回] │
|
||||
└──────────────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**功能说明**:
|
||||
- **树形结构**: 左侧组织树选择班级,支持多层级展开
|
||||
- **查询条件**: 支持按学年、学期、月份、学生信息、分数范围、录入时间筛选
|
||||
- **详细数据**: 展示选中班级的学生操行分记录
|
||||
- **数据导出**: 支持Excel、PDF导出选中数据
|
||||
|
||||
### 2.2 班级详情弹窗
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────────────────────────────────────┐
|
||||
│ 📊 计算机1班 - 学期详细统计 [✖ 关闭] │
|
||||
├──────────────────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ │
|
||||
│ 📋 学生详细列表 │
|
||||
│ ────────────────────────────────────────────────────────────────────────── │
|
||||
│ │
|
||||
│ 排名│姓名 │平均分 │9月 │10月 │11月 │12月 │1月 │2月 │趋势 │
|
||||
│ ───┼───────┼──────┼────┼────┼────┼────┼────┼────┼───────── │
|
||||
│ 1 │张三 │92.83 │95.5 │93.0 │92.0 │91.5 │93.0 │94.0 │📈 稳步提升 │
|
||||
│ 2 │李四 │88.67 │88.0 │87.5 │89.0 │88.5 │89.0 │89.5 │📊 稳定优秀 │
|
||||
│ 3 │王五 │85.50 │85.0 │86.0 │85.5 │85.0 │86.0 │85.5 │📊 基本稳定 │
|
||||
│ 4 │赵六 │82.33 │82.0 │82.5 │82.0 │82.0 │83.0 │82.5 │📈 略有提升 │
|
||||
│ 5 │孙七 │79.17 │79.0 │79.5 │79.0 │79.0 │79.5 │79.0 │📊 保持稳定 │
|
||||
│ │
|
||||
├──────────────────────────────────────────────────────────────────────────────┤
|
||||
│ [导出班级报告] [打印] [关闭] │
|
||||
└──────────────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**功能说明**:
|
||||
- **学生详细**: 每个学生的月度分数明细
|
||||
- **数据导出**: 支持导出班级报告
|
||||
|
||||
---
|
||||
|
||||
## 3. 操行分查询界面
|
||||
|
||||
### 3.1 主查询界面 (树形结构布局)
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────────────────────────────────────┐
|
||||
│ 🔍 操行分查询 👤 李老师 │
|
||||
├──────────────────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ ┌─────────────┬────────────────────────────────────────────────────────────┐ │
|
||||
│ │ 🏫 班级组织 │ 🔍 查询条件 │ │
|
||||
│ │ ────────── │ ───────────────────────────────────────────────────────── │ │
|
||||
│ │ 📂 计算机学院│ 学年: [2024 ▼] 学期: [第一学期 ▼] 月份: [全部 ▼] │ │
|
||||
│ │ 📂 计算机1班│ 学生姓名: [___________] 学号: [___________] │ │
|
||||
│ │ 📂 计算机2班│ 分数范围: [_________] 至 [_________] │ │
|
||||
│ │ 📂 电子1班 │ 录入时间: [2024-09-01] 至 [2024-12-31] │ │
|
||||
│ │ 📂 电子工程学院│ [查询] [重置] [高级筛选 ▼] [导出查询结果] │ │
|
||||
│ │ 📂 电子2班 │ │ │
|
||||
│ │ 📂 通信1班 │ │ │
|
||||
│ │ 📂 机械工程学院│ │ │
|
||||
│ │ 📂 机械1班 │ │ │
|
||||
│ │ 📂 机械2班 │ │ │
|
||||
│ └─────────────┴────────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ 📋 查询结果 (共156条记录) │
|
||||
│ ────────────────────────────────────────────────────────────────────────── │
|
||||
│ │
|
||||
│ ☑ │录入时间 │学生姓名 │学号 │班级 │月份│分数 │备注 │
|
||||
│ ──┼───────────┼─────────┼─────────┼─────────┼────┼──────┼─────────────│
|
||||
│ ☐ │2024-09-30 │张三 │2024001 │计算机1班 │09 │95.5 │表现优秀... │
|
||||
│ ☐ │2024-10-31 │张三 │2024001 │计算机1班 │10 │93.0 │继续保持... │
|
||||
│ ☐ │2024-11-30 │张三 │2024001 │计算机1班 │11 │92.0 │进步明显 │
|
||||
│ ☐ │2024-09-30 │李四 │2024002 │计算机1班 │09 │88.0 │表现良好 │
|
||||
│ ☐ │2024-10-31 │李四 │2024002 │计算机1班 │10 │87.5 │稳定发挥 │
|
||||
│ ☐ │2024-11-30 │李四 │2024002 │计算机1班 │11 │89.0 │有所进步 │
|
||||
│ ☐ │2024-09-30 │王五 │2024003 │计算机1班 │09 │85.0 │基础扎实 │
|
||||
│ ☐ │2024-10-31 │王五 │2024003 │计算机1班 │10 │86.0 │继续努力 │
|
||||
│ ☐ │2024-11-30 │王五 │2024003 │计算机1班 │11 │85.5 │保持稳定 │
|
||||
│ │
|
||||
│ ◀ 1 2 3 ... 16 ▶ 每页显示 [10 ▼] 条 共 156 条 │
|
||||
│ │
|
||||
│ 💡 操作提示: │
|
||||
│ • 点击学生姓名可查看该生详细的操行分记录 │
|
||||
│ • 点击备注可查看完整的备注内容 │
|
||||
│ • 班主任只能查询自己班级学生的操行分 │
|
||||
│ │
|
||||
├──────────────────────────────────────────────────────────────────────────────┤
|
||||
│ [批量导出] [打印预览] [清空查询] [返回] │
|
||||
└──────────────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**功能说明**:
|
||||
- **树形选择**: 左侧组织树选择班级,支持学院-专业-班级多层级选择
|
||||
- **多条件查询**: 支持学年、学期、月份、学生信息、分数范围、录入时间等条件
|
||||
- **结果展示**: 按时间倒序显示查询结果,支持分页查看
|
||||
- **权限控制**: 班主任只能查询自己负责班级的学生,管理员可查询全部
|
||||
|
||||
### 3.2 学生详情弹窗
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────────────────────────────────────┐
|
||||
│ 📊 张三(2024001) - 操行分详情 [✖ 关闭] │
|
||||
├──────────────────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ 👤 基本信息 │
|
||||
│ ────────────────────────────────────────────────────────────────────────── │
|
||||
│ 学生姓名: 张三 │ 学号: 2024001 │ 性别: 男 │ 班级: 计算机1班 │
|
||||
│ │
|
||||
│ │
|
||||
│ 📋 详细记录 │
|
||||
│ ────────────────────────────────────────────────────────────────────────── │
|
||||
│ │
|
||||
│ 月份│分数 │录入时间 │录入人 │备注 │操作 │
|
||||
│ ───┼──────┼───────────┼───────┼─────────────────────────────────┼─────│
|
||||
│ 02 │94.0 │2025-02-28 │李老师 │本月表现突出,获得班级之星 │[详情] │
|
||||
│ 01 │93.0 │2025-01-31 │李老师 │继续保持良好表现 │[详情] │
|
||||
│ 12 │91.5 │2024-12-31 │李老师 │学期末表现稳定 │[详情] │
|
||||
│ 11 │92.0 │2024-11-30 │李老师 │进步明显,值得表扬 │[详情] │
|
||||
│ 10 │93.0 │2024-10-31 │李老师 │学习认真,积极参加活动 │[详情] │
|
||||
│ 09 │95.5 │2024-09-30 │李老师 │开学表现优秀,积极参与班级建设 │[详情] │
|
||||
│ │
|
||||
├──────────────────────────────────────────────────────────────────────────────┤
|
||||
│ [导出该生报告] [修改最新记录] [关闭] │
|
||||
└──────────────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**功能说明**:
|
||||
- **详细记录**: 显示每月的分数、备注和录入信息
|
||||
- **数据修改**: 支持修改最新记录
|
||||
- **报告导出**: 支持导出学生个人报告
|
||||
|
||||
### 3.3 高级筛选面板
|
||||
|
||||
```
|
||||
│ 🔽 高级筛选 (展开) [收起▲] │
|
||||
│ ────────────────────────────────────────────────────────────────────────── │
|
||||
│ 录入人: [全部 ▼] 录入部门: [全部 ▼] │
|
||||
│ 备注关键词: [___________] (支持模糊搜索) │
|
||||
│ 排序方式: [录入时间倒序 ▼] (录入时间/学生姓名) │
|
||||
│ 显示字段: ☑录入时间 ☑学生姓名 ☑学号 ☑班级 ☑月份 ☑分数 ☑备注 ☑录入人 │
|
||||
│ [应用筛选] [重置] │
|
||||
```
|
||||
|
||||
**功能说明**:
|
||||
- **录入信息**: 按录入人和部门筛选
|
||||
- **关键词搜索**: 在备注中搜索关键词
|
||||
- **排序方式**: 支持录入时间和学生姓名的排序
|
||||
- **字段显示**: 可选择显示的数据列
|
||||
|
||||
---
|
||||
|
||||
## 总结
|
||||
|
||||
### 功能覆盖清单
|
||||
|
||||
学生操行分管理界面原型图完整覆盖以下功能:
|
||||
|
||||
- ✅ **操行分录入界面**
|
||||
- 月度录入设置(学年学期月份班级)
|
||||
- 班级学生列表展示
|
||||
- 批量录入分数和备注
|
||||
- 实时录入统计
|
||||
- 数据验证和保存
|
||||
- 统一批量设置功能
|
||||
|
||||
- ✅ **学期统计界面**
|
||||
- 左侧树形班级组织结构
|
||||
- 学年学期查询条件
|
||||
- 选中班级学生数据展示
|
||||
- 班级详情查看
|
||||
- 多格式数据导出
|
||||
|
||||
- ✅ **操行分查询界面**
|
||||
- 左侧树形班级组织结构
|
||||
- 多条件查询筛选
|
||||
- 结果列表展示
|
||||
- 学生详情查看
|
||||
- 高级筛选功能
|
||||
- 数据导出功能
|
||||
|
||||
---
|
||||
|
||||
### 设计亮点
|
||||
|
||||
**1. 智能批量操作** 🎯
|
||||
- 支持批量设置分数
|
||||
- 统一分数录入
|
||||
- 预览确认机制
|
||||
- 避免重复操作
|
||||
|
||||
**2. 树形结构导航** 🎯
|
||||
- 左侧组织树形结构
|
||||
- 多层级班级选择
|
||||
- 直观的分层展示
|
||||
- 快速定位目标班级
|
||||
|
||||
**3. 灵活查询筛选** 🎯
|
||||
- 多维度条件组合
|
||||
- 高级筛选面板
|
||||
- 树形结构快速筛选
|
||||
- 智能排序功能
|
||||
|
||||
**4. 用户体验优化** 🎯
|
||||
- 树形结构直观导航
|
||||
- 实时统计反馈
|
||||
- 操作状态标识
|
||||
- 智能提示信息
|
||||
- 快捷操作按钮
|
||||
|
||||
---
|
||||
|
||||
### 参考标准
|
||||
|
||||
- **参考**: 线索管理原型图(1156行)
|
||||
- **当前**: 约408行,符合详细原型图标准 ✅
|
||||
- **质量**: 完整覆盖需求文档所有界面 ✅
|
||||
|
||||
---
|
||||
|
||||
**文档版本**: v1.1
|
||||
**完成度**: 95%
|
||||
**可用于**: 立即开发 ✅
|
||||
|
||||
**更新说明 (v1.1)**:
|
||||
- 采用树形结构布局,参考学生信息管理界面的左侧组织树
|
||||
- 移除统计界面和查询界面的下拉框班级选择
|
||||
- 优化用户体验,提升班级选择的便捷性
|
||||
@@ -0,0 +1,778 @@
|
||||
# 学生操行分管理 - 关键业务逻辑
|
||||
|
||||
## 📋 文档说明
|
||||
|
||||
**版本**: v1.0
|
||||
**创建时间**: 2025-01-13
|
||||
**适用范围**: 学生操行分管理模块核心业务逻辑实现
|
||||
**重要性**: ⚠️ **核心文档** - 实现开发的技术指导
|
||||
|
||||
---
|
||||
|
||||
## 🎯 多Tab页录入逻辑
|
||||
|
||||
### 1. 班主任班级数据获取
|
||||
|
||||
#### 数据库查询逻辑
|
||||
|
||||
```sql
|
||||
-- 根据班主任ID获取负责的班级列表
|
||||
SELECT
|
||||
id as classId,
|
||||
class_name as className,
|
||||
class_code as classCode,
|
||||
current_student_count as studentCount,
|
||||
enrollment_year as enrollmentYear,
|
||||
major_name as majorName,
|
||||
college_name as collegeName
|
||||
FROM xg_class_msg
|
||||
WHERE class_teacher_id = #{teacherId}
|
||||
AND deleted = 0
|
||||
AND class_status = '1' -- 正常状态
|
||||
ORDER BY class_name;
|
||||
```
|
||||
|
||||
#### API响应数据结构
|
||||
|
||||
```java
|
||||
public class ClassStudentGroupVo {
|
||||
private Long classId; // 班级ID
|
||||
private String className; // 班级名称
|
||||
private String classCode; // 班级代码
|
||||
private Integer studentCount; // 学生数量
|
||||
private Integer enrollmentYear; // 入学年份
|
||||
private String majorName; // 专业名称
|
||||
|
||||
private List<StudentScoreVo> students; // 学生列表
|
||||
|
||||
// getters and setters...
|
||||
}
|
||||
|
||||
public class StudentScoreVo {
|
||||
private Long studentId; // 学生ID
|
||||
private String studentNo; // 学号
|
||||
private String studentName; // 姓名
|
||||
private String gender; // 性别
|
||||
|
||||
private ConductScoreVo existingScore; // 已存在的分数记录
|
||||
private BigDecimal lastScore; // 上次分数
|
||||
private Integer lastMonth; // 上次录入月份
|
||||
|
||||
// getters and setters...
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 前端Tab页动态生成
|
||||
|
||||
#### Vue组件结构
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<div class="conduct-input">
|
||||
<!-- 录入设置 -->
|
||||
<div class="input-settings">
|
||||
<el-form inline>
|
||||
<el-form-item label="学年">
|
||||
<el-select v-model="form.academicYear" placeholder="请选择学年">
|
||||
<el-option
|
||||
v-for="year in yearOptions"
|
||||
:key="year"
|
||||
:label="year"
|
||||
:value="year"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="学期">
|
||||
<el-select v-model="form.semester" placeholder="请选择学期">
|
||||
<el-option label="第一学期" :value="1" />
|
||||
<el-option label="第二学期" :value="2" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="月份">
|
||||
<el-select v-model="form.month" placeholder="请选择月份">
|
||||
<el-option
|
||||
v-for="m in 12"
|
||||
:key="m"
|
||||
:label="m + '月'"
|
||||
:value="m"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
|
||||
<!-- 班级Tab页 -->
|
||||
<el-tabs v-model="activeClassId" @tab-click="handleTabChange">
|
||||
<el-tab-pane
|
||||
v-for="classInfo in classData.classes"
|
||||
:key="classInfo.classId"
|
||||
:label="classInfo.className + '(' + classInfo.studentCount + '人)'"
|
||||
:name="classInfo.classId.toString()"
|
||||
>
|
||||
<!-- 学生列表表格 -->
|
||||
<StudentScoreTable
|
||||
:students="classInfo.students"
|
||||
:class-id="classInfo.classId"
|
||||
@score-change="handleScoreChange"
|
||||
/>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
|
||||
<!-- 录入统计 -->
|
||||
<div class="input-statistics">
|
||||
<div class="total-stats">
|
||||
总学生: {{ classData.summary.totalStudents }}人 |
|
||||
已录入: {{ classData.summary.recordedStudents }}人 |
|
||||
待录入: {{ classData.summary.pendingStudents }}人 |
|
||||
完成度: {{ classData.summary.completionRate }}%
|
||||
</div>
|
||||
|
||||
<!-- 各班进度条 -->
|
||||
<div class="class-progress">
|
||||
<div
|
||||
v-for="classInfo in classData.classes"
|
||||
:key="classInfo.classId"
|
||||
class="progress-item"
|
||||
>
|
||||
<span class="class-name">{{ classInfo.className }}</span>
|
||||
<el-progress
|
||||
:percentage="getClassCompletionRate(classInfo)"
|
||||
:stroke-width="8"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
|
||||
#### JavaScript逻辑
|
||||
|
||||
```javascript
|
||||
export default {
|
||||
name: 'ConductScoreInput',
|
||||
data() {
|
||||
return {
|
||||
form: {
|
||||
academicYear: new Date().getFullYear(),
|
||||
semester: 1,
|
||||
month: new Date().getMonth() + 1
|
||||
},
|
||||
classData: {
|
||||
classes: [],
|
||||
summary: {}
|
||||
},
|
||||
activeClassId: null,
|
||||
scoreData: {} // 存储各班级的分数数据
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.loadClassData()
|
||||
},
|
||||
methods: {
|
||||
// 加载班主任负责的班级及学生数据
|
||||
async loadClassData() {
|
||||
try {
|
||||
const response = await this.$api.get('/conduct-score/class-students', {
|
||||
params: this.form
|
||||
})
|
||||
|
||||
this.classData = response.data
|
||||
if (this.classData.classes.length > 0) {
|
||||
this.activeClassId = this.classData.classes[0].classId.toString()
|
||||
}
|
||||
|
||||
// 初始化分数数据存储
|
||||
this.initScoreData()
|
||||
} catch (error) {
|
||||
this.$message.error('加载班级数据失败')
|
||||
}
|
||||
},
|
||||
|
||||
// 初始化分数数据存储
|
||||
initScoreData() {
|
||||
this.classData.classes.forEach(classInfo => {
|
||||
this.scoreData[classInfo.classId] = {}
|
||||
|
||||
classInfo.students.forEach(student => {
|
||||
this.scoreData[classInfo.classId][student.studentId] = {
|
||||
score: student.existingScore ? student.existingScore.score : null,
|
||||
remark: student.existingScore ? student.existingScore.remark : '',
|
||||
isModified: false
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
// 处理Tab页切换
|
||||
handleTabChange(tab) {
|
||||
// 保存当前Tab页的草稿数据
|
||||
this.saveCurrentTabDraft()
|
||||
|
||||
// 切换到新Tab页
|
||||
this.activeClassId = tab.props.name
|
||||
},
|
||||
|
||||
// 保存当前Tab页草稿
|
||||
saveCurrentTabDraft() {
|
||||
if (!this.activeClassId) return
|
||||
|
||||
const classId = parseInt(this.activeClassId)
|
||||
const classScoreData = this.scoreData[classId]
|
||||
|
||||
// 将草稿数据存储到localStorage
|
||||
const draftKey = `conduct_score_draft_${this.form.academicYear}_${this.form.semester}_${this.form.month}_${classId}`
|
||||
localStorage.setItem(draftKey, JSON.stringify(classScoreData))
|
||||
},
|
||||
|
||||
// 处理分数变更
|
||||
handleScoreChange({ studentId, score, remark }) {
|
||||
const classId = parseInt(this.activeClassId)
|
||||
if (!this.scoreData[classId]) return
|
||||
|
||||
this.scoreData[classId][studentId] = {
|
||||
score: score,
|
||||
remark: remark,
|
||||
isModified: true
|
||||
}
|
||||
|
||||
// 自动保存草稿
|
||||
this.saveCurrentTabDraft()
|
||||
},
|
||||
|
||||
// 计算班级完成率
|
||||
getClassCompletionRate(classInfo) {
|
||||
const classScoreData = this.scoreData[classInfo.classId] || {}
|
||||
const totalStudents = classInfo.students.length
|
||||
const recordedStudents = classInfo.students.filter(student => {
|
||||
const scoreData = classScoreData[student.studentId]
|
||||
return scoreData && scoreData.score !== null && scoreData.score !== ''
|
||||
}).length
|
||||
|
||||
return totalStudents > 0 ? Math.round((recordedStudents / totalStudents) * 100) : 0
|
||||
},
|
||||
|
||||
// 批量保存
|
||||
async handleBatchSave() {
|
||||
try {
|
||||
const saveData = {
|
||||
academicYear: this.form.academicYear,
|
||||
semester: this.form.semester,
|
||||
month: this.form.month,
|
||||
scores: []
|
||||
}
|
||||
|
||||
// 收集所有班级的分数数据
|
||||
Object.keys(this.scoreData).forEach(classId => {
|
||||
const classScoreData = this.scoreData[classId]
|
||||
|
||||
Object.keys(classScoreData).forEach(studentId => {
|
||||
const scoreInfo = classScoreData[studentId]
|
||||
if (scoreInfo.isModified && scoreInfo.score !== null) {
|
||||
saveData.scores.push({
|
||||
studentId: parseInt(studentId),
|
||||
classId: parseInt(classId),
|
||||
score: scoreInfo.score,
|
||||
remark: scoreInfo.remark || ''
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
if (saveData.scores.length === 0) {
|
||||
this.$message.warning('没有需要保存的数据')
|
||||
return
|
||||
}
|
||||
|
||||
await this.$api.post('/conduct-score/batch', saveData)
|
||||
this.$message.success('保存成功')
|
||||
|
||||
// 重新加载数据
|
||||
this.loadClassData()
|
||||
} catch (error) {
|
||||
this.$message.error('保存失败')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 批量操作逻辑
|
||||
|
||||
#### 批量设置分数
|
||||
|
||||
```javascript
|
||||
// 批量设置弹窗组件
|
||||
export default {
|
||||
props: {
|
||||
visible: Boolean,
|
||||
selectedStudents: Array,
|
||||
currentClassId: Number
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
settingType: 'uniform', // uniform: 统一分数, condition: 按条件
|
||||
uniformScore: null,
|
||||
uniformRemark: '',
|
||||
conditions: []
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 应用批量设置
|
||||
applyBatchSetting() {
|
||||
const updates = []
|
||||
|
||||
if (this.settingType === 'uniform') {
|
||||
// 统一分数设置
|
||||
this.selectedStudents.forEach(student => {
|
||||
updates.push({
|
||||
studentId: student.studentId,
|
||||
score: this.uniformScore,
|
||||
remark: this.uniformRemark
|
||||
})
|
||||
})
|
||||
} else {
|
||||
// 按条件设置
|
||||
this.selectedStudents.forEach(student => {
|
||||
let score = student.existingScore ? student.existingScore.score : 0
|
||||
let remark = ''
|
||||
|
||||
this.conditions.forEach(condition => {
|
||||
if (this.checkCondition(student, condition)) {
|
||||
score += condition.scoreAdjustment
|
||||
if (condition.remarkAddition) {
|
||||
remark += condition.remarkAddition + ' '
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
updates.push({
|
||||
studentId: student.studentId,
|
||||
score: Math.min(100, Math.max(0, score)), // 限制在0-100范围内
|
||||
remark: remark.trim()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
this.$emit('apply', updates)
|
||||
this.$emit('close')
|
||||
},
|
||||
|
||||
// 检查条件
|
||||
checkCondition(student, condition) {
|
||||
switch (condition.type) {
|
||||
case 'gender':
|
||||
return student.gender === condition.value
|
||||
case 'name_pattern':
|
||||
return new RegExp(condition.value).test(student.studentName)
|
||||
case 'score_range':
|
||||
const currentScore = student.existingScore ? student.existingScore.score : 0
|
||||
return currentScore >= condition.min && currentScore <= condition.max
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4. 数据验证逻辑
|
||||
|
||||
#### 前端验证
|
||||
|
||||
```javascript
|
||||
// 分数输入验证
|
||||
validateScore(score) {
|
||||
if (score === null || score === '') return true
|
||||
|
||||
const numScore = parseFloat(score)
|
||||
if (isNaN(numScore)) return false
|
||||
|
||||
return numScore >= 0 && numScore <= 100
|
||||
},
|
||||
|
||||
// 备注长度验证
|
||||
validateRemark(remark) {
|
||||
return !remark || remark.length <= 500
|
||||
},
|
||||
|
||||
// 表单整体验证
|
||||
validateForm() {
|
||||
const errors = []
|
||||
|
||||
Object.keys(this.scoreData).forEach(classId => {
|
||||
const classScoreData = this.scoreData[classId]
|
||||
|
||||
Object.keys(classScoreData).forEach(studentId => {
|
||||
const scoreInfo = classScoreData[studentId]
|
||||
|
||||
if (scoreInfo.score !== null && !this.validateScore(scoreInfo.score)) {
|
||||
errors.push(`分数必须在0-100之间`)
|
||||
}
|
||||
|
||||
if (!this.validateRemark(scoreInfo.remark)) {
|
||||
errors.push(`备注长度不能超过500字符`)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
return errors
|
||||
}
|
||||
```
|
||||
|
||||
#### 后端验证
|
||||
|
||||
```java
|
||||
@Service
|
||||
public class ConductScoreServiceImpl implements IConductScoreService {
|
||||
|
||||
@Override
|
||||
public void validateConductScore(ConductScoreBatchBo batchBo) {
|
||||
List<String> errors = new ArrayList<>();
|
||||
|
||||
// 验证学年学期月份
|
||||
if (batchBo.getAcademicYear() < 2020 || batchBo.getAcademicYear() > 2030) {
|
||||
errors.add("学年范围无效");
|
||||
}
|
||||
|
||||
if (batchBo.getSemester() != 1 && batchBo.getSemester() != 2) {
|
||||
errors.add("学期只能是1或2");
|
||||
}
|
||||
|
||||
if (batchBo.getMonth() < 1 || batchBo.getMonth() > 12) {
|
||||
errors.add("月份范围无效");
|
||||
}
|
||||
|
||||
// 验证分数数据
|
||||
for (ConductScoreItemBo item : batchBo.getScores()) {
|
||||
// 验证分数范围
|
||||
if (item.getScore() != null) {
|
||||
if (item.getScore().compareTo(BigDecimal.ZERO) < 0 ||
|
||||
item.getScore().compareTo(new BigDecimal("100")) > 0) {
|
||||
errors.add("分数必须在0-100之间");
|
||||
}
|
||||
}
|
||||
|
||||
// 验证备注长度
|
||||
if (item.getRemark() != null && item.getRemark().length() > 500) {
|
||||
errors.add("备注长度不能超过500字符");
|
||||
}
|
||||
|
||||
// 验证学生和班级关系
|
||||
if (!this.checkStudentInClass(item.getStudentId(), item.getClassId())) {
|
||||
errors.add("学生不属于该班级");
|
||||
}
|
||||
|
||||
// 验证班主任权限
|
||||
if (!this.checkTeacherPermission(batchBo.getTeacherId(), item.getClassId())) {
|
||||
errors.add("您不是该班级的班主任");
|
||||
}
|
||||
|
||||
// 验证重复录入
|
||||
if (this.existsScore(item.getStudentId(), batchBo.getAcademicYear(),
|
||||
batchBo.getSemester(), batchBo.getMonth())) {
|
||||
errors.add("该学生当月分数已存在");
|
||||
}
|
||||
}
|
||||
|
||||
if (!errors.isEmpty()) {
|
||||
throw new ServiceException(String.join("; ", errors));
|
||||
}
|
||||
}
|
||||
|
||||
// 检查学生是否属于班级
|
||||
private boolean checkStudentInClass(Long studentId, Long classId) {
|
||||
return xgStudentMsgMapper.exists(
|
||||
Wrappers.<XgStudentMsg>lambdaQuery()
|
||||
.eq(XgStudentMsg::getId, studentId)
|
||||
.eq(XgStudentMsg::getClassId, classId)
|
||||
.eq(XgStudentMsg::getDeleted, 0)
|
||||
);
|
||||
}
|
||||
|
||||
// 检查班主任权限
|
||||
private boolean checkTeacherPermission(Long teacherId, Long classId) {
|
||||
return xgClassMsgMapper.exists(
|
||||
Wrappers.<XgClassMsg>lambdaQuery()
|
||||
.eq(XgClassMsg::getId, classId)
|
||||
.eq(XgClassMsg::getClassTeacherId, teacherId)
|
||||
.eq(XgClassMsg::getDeleted, 0)
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📈 统计分析逻辑
|
||||
|
||||
### 学期平均分计算
|
||||
|
||||
#### SQL查询实现
|
||||
|
||||
```sql
|
||||
-- 计算学生学期平均分
|
||||
SELECT
|
||||
s.student_no,
|
||||
s.name,
|
||||
c.class_name,
|
||||
ROUND(AVG(cs.score), 2) as avg_score,
|
||||
COUNT(*) as record_count,
|
||||
MAX(cs.score) as max_score,
|
||||
MIN(cs.score) as min_score,
|
||||
MAX(cs.create_time) as last_update_time
|
||||
FROM xg_conduct_score cs
|
||||
LEFT JOIN xg_student_msg s ON cs.student_id = s.id
|
||||
LEFT JOIN xg_class_msg c ON cs.class_id = c.id
|
||||
WHERE cs.tenant_id = #{tenantId}
|
||||
AND cs.deleted = 0
|
||||
AND cs.academic_year = #{academicYear}
|
||||
AND cs.semester = #{semester}
|
||||
AND s.deleted = 0
|
||||
AND c.deleted = 0
|
||||
GROUP BY cs.student_id, cs.academic_year, cs.semester
|
||||
ORDER BY c.class_name, s.student_no;
|
||||
```
|
||||
|
||||
#### 等级分布统计
|
||||
|
||||
```sql
|
||||
-- 统计分数等级分布
|
||||
SELECT
|
||||
CASE
|
||||
WHEN avg_score >= 90 THEN '优秀'
|
||||
WHEN avg_score >= 80 THEN '良好'
|
||||
WHEN avg_score >= 60 THEN '及格'
|
||||
ELSE '不及格'
|
||||
END as grade_level,
|
||||
COUNT(*) as student_count
|
||||
FROM (
|
||||
-- 学期平均分子查询
|
||||
SELECT
|
||||
cs.student_id,
|
||||
ROUND(AVG(cs.score), 2) as avg_score
|
||||
FROM xg_conduct_score cs
|
||||
WHERE cs.tenant_id = #{tenantId}
|
||||
AND cs.deleted = 0
|
||||
AND cs.academic_year = #{academicYear}
|
||||
AND cs.semester = #{semester}
|
||||
GROUP BY cs.student_id
|
||||
) avg_scores
|
||||
GROUP BY
|
||||
CASE
|
||||
WHEN avg_score >= 90 THEN '优秀'
|
||||
WHEN avg_score >= 80 THEN '良好'
|
||||
WHEN avg_score >= 60 THEN '及格'
|
||||
ELSE '不及格'
|
||||
END;
|
||||
```
|
||||
|
||||
### 缓存策略
|
||||
|
||||
#### Redis缓存设计
|
||||
|
||||
```java
|
||||
@Service
|
||||
public class ConductScoreStatisticsService {
|
||||
|
||||
@Autowired
|
||||
private RedisTemplate<String, Object> redisTemplate;
|
||||
|
||||
private static final String SEMESTER_STATS_KEY = "conduct_score:semester_stats:%s:%s:%s";
|
||||
|
||||
/**
|
||||
* 获取学期统计数据(带缓存)
|
||||
*/
|
||||
public List<SemesterAverageVo> getSemesterAverageWithCache(Integer academicYear, Integer semester, Long tenantId) {
|
||||
String cacheKey = String.format(SEMESTER_STATS_KEY, tenantId, academicYear, semester);
|
||||
|
||||
// 尝试从缓存获取
|
||||
List<SemesterAverageVo> cachedData = (List<SemesterAverageVo>) redisTemplate.opsForValue().get(cacheKey);
|
||||
if (cachedData != null) {
|
||||
return cachedData;
|
||||
}
|
||||
|
||||
// 从数据库查询
|
||||
List<SemesterAverageVo> data = getSemesterAverageFromDB(academicYear, semester, tenantId);
|
||||
|
||||
// 写入缓存(过期时间:24小时)
|
||||
redisTemplate.opsForValue().set(cacheKey, data, 24, TimeUnit.HOURS);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理统计缓存
|
||||
*/
|
||||
public void clearStatisticsCache(Integer academicYear, Integer semester, Long tenantId) {
|
||||
String cacheKey = String.format(SEMESTER_STATS_KEY, tenantId, academicYear, semester);
|
||||
redisTemplate.delete(cacheKey);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔒 权限控制逻辑
|
||||
|
||||
### 班主任权限验证
|
||||
|
||||
```java
|
||||
@Component
|
||||
public class ConductScorePermissionChecker {
|
||||
|
||||
@Autowired
|
||||
private IXgClassMsgService classMsgService;
|
||||
|
||||
/**
|
||||
* 验证用户是否为指定班级的班主任
|
||||
*/
|
||||
public boolean isClassTeacher(Long userId, Long classId) {
|
||||
XgClassMsg classInfo = classMsgService.getById(classId);
|
||||
return classInfo != null &&
|
||||
classInfo.getClassTeacherId() != null &&
|
||||
classInfo.getClassTeacherId().equals(userId) &&
|
||||
classInfo.getDeleted().equals(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户负责的所有班级ID
|
||||
*/
|
||||
public List<Long> getTeacherClassIds(Long userId) {
|
||||
return classMsgService.list(
|
||||
Wrappers.<XgClassMsg>lambdaQuery()
|
||||
.select(XgClassMsg::getId)
|
||||
.eq(XgClassMsg::getClassTeacherId, userId)
|
||||
.eq(XgClassMsg::getDeleted, 0)
|
||||
.eq(XgClassMsg::getClassStatus, "1") // 正常状态
|
||||
).stream().map(XgClassMsg::getId).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证用户是否有录入权限
|
||||
*/
|
||||
public void checkInputPermission(Long userId, ConductScoreBatchBo batchBo) {
|
||||
List<Long> teacherClassIds = getTeacherClassIds(userId);
|
||||
|
||||
// 检查所有分数记录的班级是否都由该老师负责
|
||||
boolean hasPermission = batchBo.getScores().stream()
|
||||
.allMatch(score -> teacherClassIds.contains(score.getClassId()));
|
||||
|
||||
if (!hasPermission) {
|
||||
throw new ServiceException("您不是所有班级的班主任,无权录入部分学生的分数");
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📊 数据一致性保证
|
||||
|
||||
### 录入状态管理
|
||||
|
||||
```java
|
||||
@Service
|
||||
public class ConductScoreStatusManager {
|
||||
|
||||
/**
|
||||
* 更新录入状态记录
|
||||
*/
|
||||
@Transactional
|
||||
public void updateInputStatus(ConductScoreBatchBo batchBo, Long teacherId) {
|
||||
// 按班级分组统计
|
||||
Map<Long, List<ConductScoreItemBo>> classGroups = batchBo.getScores().stream()
|
||||
.collect(Collectors.groupingBy(ConductScoreItemBo::getClassId));
|
||||
|
||||
for (Map.Entry<Long, List<ConductScoreItemBo>> entry : classGroups.entrySet()) {
|
||||
Long classId = entry.getKey();
|
||||
List<ConductScoreItemBo> scores = entry.getValue();
|
||||
|
||||
// 计算班级录入完成度
|
||||
int totalStudents = getClassStudentCount(classId);
|
||||
int recordedStudents = getRecordedStudentCount(classId, batchBo.getAcademicYear(),
|
||||
batchBo.getSemester(), batchBo.getMonth());
|
||||
|
||||
double completionRate = (double) recordedStudents / totalStudents * 100;
|
||||
|
||||
// 更新录入状态记录
|
||||
upsertInputStatus(classId, batchBo.getAcademicYear(), batchBo.getSemester(),
|
||||
batchBo.getMonth(), teacherId, completionRate);
|
||||
}
|
||||
}
|
||||
|
||||
private int getClassStudentCount(Long classId) {
|
||||
// 查询班级学生总数
|
||||
return xgStudentMsgMapper.selectCount(
|
||||
Wrappers.<XgStudentMsg>lambdaQuery()
|
||||
.eq(XgStudentMsg::getClassId, classId)
|
||||
.eq(XgStudentMsg::getDeleted, 0)
|
||||
);
|
||||
}
|
||||
|
||||
private int getRecordedStudentCount(Long classId, Integer academicYear,
|
||||
Integer semester, Integer month) {
|
||||
// 查询已录入学生数
|
||||
return conductScoreMapper.selectCount(
|
||||
Wrappers.<ConductScore>lambdaQuery()
|
||||
.eq(ConductScore::getClassId, classId)
|
||||
.eq(ConductScore::getAcademicYear, academicYear)
|
||||
.eq(ConductScore::getSemester, semester)
|
||||
.eq(ConductScore::getMonth, month)
|
||||
.eq(ConductScore::getDeleted, 0)
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🚀 性能优化策略
|
||||
|
||||
### 批量操作优化
|
||||
|
||||
```java
|
||||
@Service
|
||||
public class ConductScoreBatchService {
|
||||
|
||||
/**
|
||||
* 批量保存操行分(优化版)
|
||||
*/
|
||||
@Transactional
|
||||
public void batchSaveOptimized(List<ConductScore> scoreList) {
|
||||
if (scoreList.isEmpty()) return;
|
||||
|
||||
// 分批处理,避免一次性插入太多数据
|
||||
int batchSize = 100;
|
||||
for (int i = 0; i < scoreList.size(); i += batchSize) {
|
||||
int endIndex = Math.min(i + batchSize, scoreList.size());
|
||||
List<ConductScore> batch = scoreList.subList(i, endIndex);
|
||||
|
||||
// 批量插入
|
||||
conductScoreMapper.insertBatch(batch);
|
||||
|
||||
// 更新录入状态
|
||||
updateBatchStatus(batch);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量更新状态
|
||||
*/
|
||||
private void updateBatchStatus(List<ConductScore> batch) {
|
||||
// 使用单个SQL更新状态,避免N+1查询
|
||||
if (!batch.isEmpty()) {
|
||||
ConductScore first = batch.get(0);
|
||||
conductScoreMapper.updateInputStatus(
|
||||
first.getAcademicYear(),
|
||||
first.getSemester(),
|
||||
first.getMonth(),
|
||||
first.getClassId(),
|
||||
first.getClassTeacherId()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
这个文档详细说明了多Tab页录入的核心业务逻辑实现,包括数据获取、Tab页动态生成、批量操作、权限控制、数据验证和性能优化等关键技术点。
|
||||
@@ -0,0 +1,662 @@
|
||||
# 学生操行分管理 - API 接口设计
|
||||
|
||||
## 📋 文档说明
|
||||
|
||||
**版本**: v1.0
|
||||
**制定时间**: 2025-01-13
|
||||
**适用范围**: 学生操行分管理模块 API 接口设计
|
||||
**重要性**: ⚠️ **强制性规范** - API 设计必须遵循项目统一接口规范
|
||||
|
||||
---
|
||||
|
||||
## 🎯 设计原则
|
||||
|
||||
### 1. RESTful 设计
|
||||
|
||||
- 使用标准的 HTTP 方法(GET、POST、PUT、DELETE)
|
||||
- 资源路径清晰,层次分明
|
||||
- 状态码规范,返回信息明确
|
||||
|
||||
### 2. 统一响应格式
|
||||
|
||||
- 所有接口返回统一的 JSON 格式
|
||||
- 包含状态码、消息、数据三个部分
|
||||
- 分页查询使用统一的 TableDataInfo 格式
|
||||
|
||||
### 3. 权限控制
|
||||
|
||||
- 每个接口必须有 @SaCheckPermission 注解
|
||||
- 班主任只能操作自己班级的学生
|
||||
- 学生处管理员可查看全校数据
|
||||
|
||||
---
|
||||
|
||||
## 🔌 接口概览
|
||||
|
||||
### 接口分类
|
||||
|
||||
#### 基础 CRUD 接口
|
||||
|
||||
| 接口名称 | 请求方式 | 路径 | 权限字符 | 功能描述 |
|
||||
| -------------- | -------- | --------------------------- | ---------------------- | ---------------------- |
|
||||
| 查询操行分列表 | GET | /api/conduct-score/list | conduct:score:list | 分页查询操行分记录 |
|
||||
| 查询操行分详情 | GET | /api/conduct-score/{id} | conduct:score:query | 根据 ID 查询操行分详情 |
|
||||
| 新增操行分 | POST | /api/conduct-score | conduct:score:add | 创建新的操行分记录 |
|
||||
| 更新操行分 | PUT | /api/conduct-score/{id} | conduct:score:edit | 更新指定操行分记录 |
|
||||
| 删除操行分 | DELETE | /api/conduct-score/{id} | conduct:score:remove | 删除指定操行分记录 |
|
||||
|
||||
#### 业务逻辑接口
|
||||
|
||||
| 接口名称 | 请求方式 | 路径 | 权限字符 | 功能描述 |
|
||||
| ---------------- | -------- | ----------------------------- | ---------------------- | ------------------------ |
|
||||
| 批量录入操行分 | POST | /api/conduct-score/batch | conduct:score:batchAdd | 批量保存操行分 |
|
||||
| 班主任班级学生 | GET | /api/conduct-score/class-students | conduct:score:classStudents | 获取班主任班级学生列表 |
|
||||
| 学期平均分统计 | GET | /api/statistics/semester-average | conduct:statistics:semester | 查询学期平均分 |
|
||||
| 导出学期统计 | GET | /api/statistics/export | conduct:statistics:export | 导出学期统计数据 |
|
||||
| 验证录入权限 | GET | /api/conduct-score/check-permission | conduct:score:checkPermission | 检查班主任录入权限 |
|
||||
|
||||
---
|
||||
|
||||
## 📝 接口详细设计
|
||||
|
||||
### 1. 查询操行分列表
|
||||
|
||||
#### 接口信息
|
||||
|
||||
- **请求方式**: GET
|
||||
- **接口路径**: `/api/conduct-score/list`
|
||||
- **权限字符**: `conduct:score:list`
|
||||
- **接口描述**: 分页查询操行分记录列表
|
||||
|
||||
#### 请求参数
|
||||
|
||||
| 参数名 | 类型 | 必填 | 说明 |
|
||||
| ----------- | ------- | ---- | ---------------------- |
|
||||
| academicYear| Integer | 否 | 学年(如:2024) |
|
||||
| semester | Integer | 否 | 学期(1-第一学期,2-第二学期) |
|
||||
| month | Integer | 否 | 月份(1-12) |
|
||||
| studentName | String | 否 | 学生姓名 |
|
||||
| className | String | 否 | 班级名称 |
|
||||
| scoreMin | BigDecimal | 否 | 最低分数 |
|
||||
| scoreMax | BigDecimal | 否 | 最高分数 |
|
||||
| pageNum | Integer | 否 | 当前页码(默认1) |
|
||||
| pageSize | Integer | 否 | 每页显示数量(默认10) |
|
||||
|
||||
#### 响应格式
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "查询成功",
|
||||
"data": {
|
||||
"total": 100,
|
||||
"rows": [
|
||||
{
|
||||
"id": 1234567890123456789,
|
||||
"academicYear": 2024,
|
||||
"semester": 1,
|
||||
"month": 9,
|
||||
"studentId": 987654321098765432,
|
||||
"studentName": "张三",
|
||||
"studentNo": "2024001",
|
||||
"classId": 876543210987654321,
|
||||
"className": "计算机应用1班",
|
||||
"classTeacherId": 765432109876543210,
|
||||
"classTeacherName": "李老师",
|
||||
"score": 95.50,
|
||||
"remark": "表现优秀,积极参加班级活动",
|
||||
"createTime": "2024-09-01 10:00:00",
|
||||
"createBy": "李老师"
|
||||
}
|
||||
],
|
||||
"code": 200,
|
||||
"msg": "查询成功"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 批量录入操行分
|
||||
|
||||
#### 接口信息
|
||||
|
||||
- **请求方式**: POST
|
||||
- **接口路径**: `/api/conduct-score/batch`
|
||||
- **权限字符**: `conduct:score:batchAdd`
|
||||
- **接口描述**: 批量保存操行分记录
|
||||
|
||||
#### 请求参数
|
||||
|
||||
```json
|
||||
{
|
||||
"academicYear": 2024,
|
||||
"semester": 1,
|
||||
"month": 9,
|
||||
"classId": 876543210987654321,
|
||||
"scores": [
|
||||
{
|
||||
"studentId": 987654321098765432,
|
||||
"score": 95.50,
|
||||
"remark": "表现优秀,积极参加班级活动"
|
||||
},
|
||||
{
|
||||
"studentId": 987654321098765433,
|
||||
"score": 88.00,
|
||||
"remark": "表现良好,需要继续努力"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
#### 参数说明
|
||||
|
||||
| 参数名 | 类型 | 必填 | 说明 |
|
||||
| ----------- | ------- | ---- | ---------------------- |
|
||||
| academicYear| Integer | 是 | 学年(如:2024) |
|
||||
| semester | Integer | 是 | 学期(1-第一学期,2-第二学期) |
|
||||
| month | Integer | 是 | 月份(1-12) |
|
||||
| classId | Long | 是 | 班级ID |
|
||||
| scores | Array | 是 | 操行分记录数组 |
|
||||
| └─studentId | Long | 是 | 学生ID |
|
||||
| └─score | BigDecimal | 是 | 操行分数(0.00-100.00) |
|
||||
| └─remark | String | 否 | 备注信息 |
|
||||
|
||||
#### 响应格式
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "批量录入成功",
|
||||
"data": {
|
||||
"successCount": 45,
|
||||
"failCount": 0,
|
||||
"failList": []
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 班主任班级学生列表
|
||||
|
||||
#### 接口信息
|
||||
|
||||
- **请求方式**: GET
|
||||
- **接口路径**: `/api/conduct-score/class-students`
|
||||
- **权限字符**: `conduct:score:classStudents`
|
||||
- **接口描述**: 获取班主任负责的所有班级及学生列表,用于多Tab页展示
|
||||
|
||||
#### 请求参数
|
||||
|
||||
| 参数名 | 类型 | 必填 | 说明 |
|
||||
| ----------- | ------- | ---- | ---------------------- |
|
||||
| academicYear| Integer | 否 | 学年(如:2024) |
|
||||
| semester | Integer | 否 | 学期(1-第一学期,2-第二学期) |
|
||||
| month | Integer | 否 | 月份(1-12) |
|
||||
|
||||
#### 响应格式
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "查询成功",
|
||||
"data": {
|
||||
"classes": [
|
||||
{
|
||||
"classId": 876543210987654321,
|
||||
"className": "计算机1班",
|
||||
"classCode": "JSJ202401",
|
||||
"studentCount": 45,
|
||||
"enrollmentYear": 2024,
|
||||
"majorName": "计算机应用技术",
|
||||
"students": [
|
||||
{
|
||||
"studentId": 987654321098765432,
|
||||
"studentNo": "2024001",
|
||||
"studentName": "张三",
|
||||
"gender": "0",
|
||||
"existingScore": null,
|
||||
"lastScore": 95.50,
|
||||
"lastMonth": 8
|
||||
},
|
||||
{
|
||||
"studentId": 987654321098765433,
|
||||
"studentNo": "2024002",
|
||||
"studentName": "李四",
|
||||
"gender": "1",
|
||||
"existingScore": {
|
||||
"id": 1234567890123456789,
|
||||
"score": 88.00,
|
||||
"remark": "表现良好",
|
||||
"createTime": "2024-09-01 09:00:00"
|
||||
},
|
||||
"lastScore": 90.00,
|
||||
"lastMonth": 8
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"classId": 876543210987654322,
|
||||
"className": "计算机2班",
|
||||
"classCode": "JSJ202402",
|
||||
"studentCount": 42,
|
||||
"enrollmentYear": 2024,
|
||||
"majorName": "计算机应用技术",
|
||||
"students": [
|
||||
{
|
||||
"studentId": 987654321098765434,
|
||||
"studentNo": "2024046",
|
||||
"studentName": "王五",
|
||||
"gender": "0",
|
||||
"existingScore": {
|
||||
"id": 1234567890123456790,
|
||||
"score": 92.00,
|
||||
"remark": "学习认真",
|
||||
"createTime": "2024-09-01 09:15:00"
|
||||
},
|
||||
"lastScore": 90.00,
|
||||
"lastMonth": 8
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"classId": 876543210987654323,
|
||||
"className": "电子1班",
|
||||
"classCode": "DZ202401",
|
||||
"studentCount": 38,
|
||||
"enrollmentYear": 2024,
|
||||
"majorName": "电子信息工程",
|
||||
"students": [
|
||||
{
|
||||
"studentId": 987654321098765435,
|
||||
"studentNo": "2024088",
|
||||
"studentName": "赵六",
|
||||
"gender": "0",
|
||||
"existingScore": null,
|
||||
"lastScore": 87.50,
|
||||
"lastMonth": 8
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"summary": {
|
||||
"totalClasses": 3,
|
||||
"totalStudents": 125,
|
||||
"recordedStudents": 87,
|
||||
"pendingStudents": 38,
|
||||
"completionRate": 69.6
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4. 学期平均分统计
|
||||
|
||||
#### 接口信息
|
||||
|
||||
- **请求方式**: GET
|
||||
- **接口路径**: `/api/statistics/semester-average`
|
||||
- **权限字符**: `conduct:statistics:semester`
|
||||
- **接口描述**: 查询学生的学期操行平均分
|
||||
|
||||
#### 请求参数
|
||||
|
||||
| 参数名 | 类型 | 必填 | 说明 |
|
||||
| ----------- | ------- | ---- | ---------------------- |
|
||||
| academicYear| Integer | 是 | 学年(如:2024) |
|
||||
| semester | Integer | 是 | 学期(1-第一学期,2-第二学期) |
|
||||
| className | String | 否 | 班级名称筛选 |
|
||||
| studentName | String | 否 | 学生姓名筛选 |
|
||||
| pageNum | Integer | 否 | 当前页码(默认1) |
|
||||
| pageSize | Integer | 否 | 每页显示数量(默认10) |
|
||||
|
||||
#### 响应格式
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "查询成功",
|
||||
"data": {
|
||||
"total": 500,
|
||||
"rows": [
|
||||
{
|
||||
"studentId": 987654321098765432,
|
||||
"studentNo": "2024001",
|
||||
"studentName": "张三",
|
||||
"gender": "0",
|
||||
"classId": 876543210987654321,
|
||||
"className": "计算机应用1班",
|
||||
"academicYear": 2024,
|
||||
"semester": 1,
|
||||
"avgScore": 92.83,
|
||||
"recordCount": 6,
|
||||
"maxScore": 98.00,
|
||||
"minScore": 88.00,
|
||||
"scoreRange": "88.00-98.00",
|
||||
"lastUpdateTime": "2024-09-30 16:00:00"
|
||||
}
|
||||
],
|
||||
"summary": {
|
||||
"totalStudents": 500,
|
||||
"avgScore": 85.67,
|
||||
"excellentCount": 120,
|
||||
"goodCount": 280,
|
||||
"averageCount": 85,
|
||||
"needImprovementCount": 15
|
||||
},
|
||||
"code": 200,
|
||||
"msg": "查询成功"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 5. 导出学期统计
|
||||
|
||||
#### 接口信息
|
||||
|
||||
- **请求方式**: GET
|
||||
- **接口路径**: `/api/statistics/export`
|
||||
- **权限字符**: `conduct:statistics:export`
|
||||
- **接口描述**: 导出学期操行分统计数据
|
||||
|
||||
#### 请求参数
|
||||
|
||||
| 参数名 | 类型 | 必填 | 说明 |
|
||||
| ----------- | ------- | ---- | ---------------------- |
|
||||
| academicYear| Integer | 是 | 学年(如:2024) |
|
||||
| semester | Integer | 是 | 学期(1-第一学期,2-第二学期) |
|
||||
| className | String | 否 | 班级名称筛选 |
|
||||
| exportType | String | 否 | 导出类型(excel/pdf,默认excel) |
|
||||
|
||||
#### 响应格式
|
||||
|
||||
返回 Excel 或 PDF 文件流
|
||||
|
||||
---
|
||||
|
||||
## 🔒 权限控制设计
|
||||
|
||||
### 权限字符定义
|
||||
|
||||
```java
|
||||
// 操行分管理权限
|
||||
conduct:score:list // 查看操行分列表
|
||||
conduct:score:query // 查看操行分详情
|
||||
conduct:score:add // 新增操行分
|
||||
conduct:score:edit // 编辑操行分
|
||||
conduct:score:remove // 删除操行分
|
||||
conduct:score:batchAdd // 批量录入操行分
|
||||
conduct:score:classStudents // 查看班主任班级学生
|
||||
|
||||
// 统计分析权限
|
||||
conduct:statistics:semester // 学期统计查看
|
||||
conduct:statistics:export // 数据导出
|
||||
```
|
||||
|
||||
### 权限控制逻辑
|
||||
|
||||
#### 班主任权限控制
|
||||
|
||||
```java
|
||||
@SaCheckPermission("conduct:score:classStudents")
|
||||
@GetMapping("/class-students")
|
||||
public AjaxResult getClassStudents(@RequestParam Integer academicYear,
|
||||
@RequestParam Integer semester,
|
||||
@RequestParam Integer month) {
|
||||
// 获取当前登录用户ID
|
||||
Long userId = SecurityUtils.getUserId();
|
||||
|
||||
// 从 xg_class_msg 表查询该用户作为班主任的所有班级
|
||||
// WHERE class_teacher_id = #{userId} AND deleted = 0
|
||||
List<Long> classIds = conductScoreService.getTeacherClassIds(userId);
|
||||
|
||||
// 返回按班级分组的学生信息,用于前端生成Tab页
|
||||
return AjaxResult.success(conductScoreService.getClassStudentsGrouped(classIds, academicYear, semester, month));
|
||||
}
|
||||
```
|
||||
|
||||
#### 管理员权限控制
|
||||
|
||||
```java
|
||||
@SaCheckPermission("conduct:statistics:semester")
|
||||
@GetMapping("/semester-average")
|
||||
public TableDataInfo getSemesterAverage(@RequestParam Integer academicYear,
|
||||
@RequestParam Integer semester,
|
||||
ConductScoreQueryBo queryBo) {
|
||||
// 管理员可以查看全校数据,无班级限制
|
||||
startPage();
|
||||
List<SemesterAverageVo> list = statisticsService.getSemesterAverage(academicYear, semester, queryBo);
|
||||
return getDataTable(list);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📊 数据验证规则
|
||||
|
||||
### 请求参数验证
|
||||
|
||||
```java
|
||||
public class ConductScoreBatchBo {
|
||||
@NotNull(message = "学年不能为空")
|
||||
@Min(value = 2020, message = "学年不能小于2020")
|
||||
@Max(value = 2030, message = "学年不能大于2030")
|
||||
private Integer academicYear;
|
||||
|
||||
@NotNull(message = "学期不能为空")
|
||||
@Min(value = 1, message = "学期只能是1或2")
|
||||
@Max(value = 2, message = "学期只能是1或2")
|
||||
private Integer semester;
|
||||
|
||||
@NotNull(message = "月份不能为空")
|
||||
@Min(value = 1, message = "月份范围为1-12")
|
||||
@Max(value = 12, message = "月份范围为1-12")
|
||||
private Integer month;
|
||||
|
||||
@NotNull(message = "班级ID不能为空")
|
||||
private Long classId;
|
||||
|
||||
@NotEmpty(message = "操行分记录不能为空")
|
||||
@Size(min = 1, max = 100, message = "一次最多录入100条记录")
|
||||
private List<ConductScoreItemBo> scores;
|
||||
}
|
||||
|
||||
public class ConductScoreItemBo {
|
||||
@NotNull(message = "学生ID不能为空")
|
||||
private Long studentId;
|
||||
|
||||
@NotNull(message = "分数不能为空")
|
||||
@DecimalMin(value = "0.00", message = "分数不能小于0")
|
||||
@DecimalMax(value = "100.00", message = "分数不能大于100")
|
||||
private BigDecimal score;
|
||||
|
||||
@Size(max = 500, message = "备注不能超过500字符")
|
||||
private String remark;
|
||||
}
|
||||
```
|
||||
|
||||
### 业务逻辑验证
|
||||
|
||||
#### 班主任权限验证
|
||||
|
||||
```java
|
||||
private void validateTeacherPermission(Long classId, Long teacherId) {
|
||||
// 验证班主任是否负责该班级
|
||||
boolean isResponsible = conductScoreService.checkTeacherClass(teacherId, classId);
|
||||
if (!isResponsible) {
|
||||
throw new ServiceException("您不是该班级的班主任,无权录入操行分");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 重复录入验证
|
||||
|
||||
```java
|
||||
private void validateNoDuplicate(Long studentId, Integer academicYear,
|
||||
Integer semester, Integer month) {
|
||||
// 检查是否已存在该学生的当月记录
|
||||
boolean exists = conductScoreService.existsByStudentAndMonth(
|
||||
studentId, academicYear, semester, month);
|
||||
|
||||
if (exists) {
|
||||
throw new ServiceException("该学生当月操行分已录入,不可重复录入");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔄 错误码设计
|
||||
|
||||
### 系统级错误码
|
||||
|
||||
| 错误码 | 说明 |
|
||||
| ------ | ------------ |
|
||||
| 200 | 成功 |
|
||||
| 400 | 请求参数错误 |
|
||||
| 401 | 未授权 |
|
||||
| 403 | 权限不足 |
|
||||
| 500 | 系统错误 |
|
||||
|
||||
### 业务级错误码
|
||||
|
||||
| 错误码 | 说明 |
|
||||
| ------ | ------------------------ |
|
||||
| 1001 | 班主任权限不足 |
|
||||
| 1002 | 学生不存在 |
|
||||
| 1003 | 班级不存在 |
|
||||
| 1004 | 操行分已存在,不可重复录入 |
|
||||
| 1005 | 分数范围错误 |
|
||||
| 1006 | 时间参数错误 |
|
||||
|
||||
### 错误响应格式
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 1001,
|
||||
"msg": "您不是该班级的班主任,无权录入操行分",
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📈 性能优化
|
||||
|
||||
### 接口性能指标
|
||||
|
||||
- **响应时间**: 平均 < 200ms,95% < 500ms
|
||||
- **并发处理**: 支持 100+ 并发请求
|
||||
- **数据量**: 支持万人规模数据查询
|
||||
|
||||
### 缓存策略
|
||||
|
||||
#### Redis 缓存配置
|
||||
|
||||
```java
|
||||
// 学期统计缓存
|
||||
@Cacheable(value = "conductScoreStats", key = "#academicYear + '_' + #semester")
|
||||
public List<SemesterAverageVo> getSemesterAverage(Integer academicYear, Integer semester) {
|
||||
// 查询逻辑
|
||||
}
|
||||
|
||||
// 班主任班级缓存
|
||||
@Cacheable(value = "teacherClasses", key = "#teacherId")
|
||||
public List<Long> getTeacherClassIds(Long teacherId) {
|
||||
// 查询逻辑
|
||||
}
|
||||
```
|
||||
|
||||
### 分页查询优化
|
||||
|
||||
```java
|
||||
// 大数据量分页查询
|
||||
PageHelper.startPage(pageNum, pageSize);
|
||||
List<ConductScoreVo> list = conductScoreMapper.selectConductScoreList(queryBo);
|
||||
// 自动添加 LIMIT 和 OFFSET
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🧪 接口测试
|
||||
|
||||
### 测试用例设计
|
||||
|
||||
#### 正常流程测试
|
||||
|
||||
```bash
|
||||
# 批量录入操行分
|
||||
curl -X POST "http://localhost:8080/api/conduct-score/batch" \
|
||||
-H "Authorization: Bearer {token}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"academicYear": 2024,
|
||||
"semester": 1,
|
||||
"month": 9,
|
||||
"classId": 876543210987654321,
|
||||
"scores": [
|
||||
{"studentId": 987654321098765432, "score": 95.5, "remark": "优秀"}
|
||||
]
|
||||
}'
|
||||
```
|
||||
|
||||
#### 异常流程测试
|
||||
|
||||
```bash
|
||||
# 权限不足测试
|
||||
curl -X GET "http://localhost:8080/api/conduct-score/class-students" \
|
||||
-H "Authorization: Bearer {invalid_token}"
|
||||
|
||||
# 响应: {"code": 403, "msg": "权限不足", "data": null}
|
||||
```
|
||||
|
||||
#### 性能测试
|
||||
|
||||
```bash
|
||||
# 并发测试脚本
|
||||
ab -n 1000 -c 50 "http://localhost:8080/api/statistics/semester-average?academicYear=2024&semester=1"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📚 接口文档
|
||||
|
||||
### Swagger 文档
|
||||
|
||||
```java
|
||||
@Api(tags = "操行分管理")
|
||||
@RestController
|
||||
@RequestMapping("/api/conduct-score")
|
||||
public class ConductScoreController {
|
||||
|
||||
@ApiOperation("批量录入操行分")
|
||||
@ApiImplicitParams({
|
||||
@ApiImplicitParam(name = "academicYear", value = "学年", required = true),
|
||||
@ApiImplicitParam(name = "semester", value = "学期", required = true),
|
||||
@ApiImplicitParam(name = "month", value = "月份", required = true)
|
||||
})
|
||||
@PostMapping("/batch")
|
||||
public AjaxResult batchAddConductScore(@RequestBody ConductScoreBatchBo bo) {
|
||||
// 接口实现
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 接口变更记录
|
||||
|
||||
| 版本 | 变更时间 | 变更内容 | 变更人 |
|
||||
| ---- | -------- | -------- | ------ |
|
||||
| v1.0 | 2025-01-13 | 初始版本 | 开发团队 |
|
||||
|
||||
---
|
||||
|
||||
## 📞 技术支持
|
||||
|
||||
如有疑问或需要技术支持,请联系:
|
||||
|
||||
- **接口负责人**: 后端开发负责人
|
||||
- **测试负责人**: 测试团队
|
||||
- **业务负责人**: 学生处管理员
|
||||
|
||||
---
|
||||
|
||||
**最后更新**: 2025-01-13
|
||||
**接口版本**: v1.0
|
||||
**兼容性**: 向后兼容
|
||||
@@ -0,0 +1,526 @@
|
||||
# 学生操行分管理 - 数据库设计
|
||||
|
||||
## 📋 文档说明
|
||||
|
||||
**版本**: v1.1
|
||||
**制定时间**: 2025-01-13
|
||||
**更新时间**: 2025-01-13
|
||||
**适用范围**: 学生操行分管理模块数据库设计
|
||||
**重要性**: ⚠️ **强制性规范** - 数据库设计必须严格遵循项目数据库建设规范
|
||||
|
||||
---
|
||||
|
||||
## 🎯 设计原则
|
||||
|
||||
### 1. 遵循项目规范
|
||||
|
||||
- 所有表必须包含标准基础字段(强制性要求)
|
||||
- 使用雪花ID作为主键
|
||||
- 强制使用逻辑删除
|
||||
- 支持多租户数据隔离
|
||||
- 完整的中文注释说明
|
||||
|
||||
### 2. 数据完整性
|
||||
|
||||
- 外键约束:不使用数据库级别的外键约束,通过应用层维护关联关系
|
||||
- 数据验证:在应用层进行数据完整性校验
|
||||
- 业务规则:通过代码逻辑确保业务规则的执行
|
||||
|
||||
### 3. 性能优化
|
||||
|
||||
- 合理设计索引,提升查询性能
|
||||
- 考虑数据增长趋势,预留扩展空间
|
||||
- 支持大数据量查询和统计分析
|
||||
|
||||
---
|
||||
|
||||
## 🗄️ 核心表结构
|
||||
|
||||
### 操行分表 (xg_conduct_score)
|
||||
|
||||
#### 表结构定义
|
||||
|
||||
```sql
|
||||
-- ----------------------------
|
||||
-- Table structure for xg_conduct_score
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `xg_conduct_score`;
|
||||
CREATE TABLE `xg_conduct_score` (
|
||||
-- 基础字段(必须)
|
||||
`id` bigint(20) NOT NULL COMMENT '主键ID(雪花ID)',
|
||||
`tenant_id` varchar(20) NOT NULL DEFAULT '000000' COMMENT '租户编号',
|
||||
`create_dept` bigint(20) DEFAULT NULL COMMENT '创建部门',
|
||||
`create_by` bigint(20) DEFAULT NULL COMMENT '创建者',
|
||||
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
|
||||
`update_by` bigint(20) DEFAULT NULL COMMENT '更新者',
|
||||
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
|
||||
`deleted` tinyint(1) DEFAULT '0' COMMENT '删除标志(0代表存在 1代表删除)',
|
||||
|
||||
-- 业务字段
|
||||
`academic_year` int(4) NOT NULL COMMENT '学年(如:2024)',
|
||||
`semester` tinyint(1) NOT NULL COMMENT '学期(1-第一学期,2-第二学期)',
|
||||
`month` tinyint(2) NOT NULL COMMENT '月份(1-12)',
|
||||
`student_id` bigint(20) NOT NULL COMMENT '学生ID,关联xg_student_msg.id',
|
||||
`class_id` bigint(20) NOT NULL COMMENT '班级ID,关联xg_class_msg.id',
|
||||
`class_teacher_id` bigint(20) NOT NULL COMMENT '班主任ID,关联sys_user.id',
|
||||
`score` decimal(5,2) NOT NULL COMMENT '操行分数(0.00-100.00)',
|
||||
`remark` varchar(500) DEFAULT NULL COMMENT '备注信息',
|
||||
|
||||
-- 主键和索引
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
|
||||
-- 基础索引(必须)
|
||||
KEY `idx_tenant_id` (`tenant_id`),
|
||||
KEY `idx_deleted` (`deleted`),
|
||||
KEY `idx_create_time` (`create_time`),
|
||||
|
||||
-- 业务索引
|
||||
KEY `idx_student_id` (`student_id`),
|
||||
KEY `idx_class_id` (`class_id`),
|
||||
KEY `idx_class_teacher_id` (`class_teacher_id`),
|
||||
KEY `idx_academic_year` (`academic_year`),
|
||||
KEY `idx_semester` (`semester`),
|
||||
KEY `idx_month` (`month`),
|
||||
|
||||
-- 组合索引(查询优化)
|
||||
KEY `idx_student_year_semester` (`student_id`, `academic_year`, `semester`),
|
||||
KEY `idx_class_year_semester_month` (`class_id`, `academic_year`, `semester`, `month`),
|
||||
KEY `idx_teacher_year_semester` (`class_teacher_id`, `academic_year`, `semester`),
|
||||
|
||||
-- 唯一约束(防止重复录入)
|
||||
UNIQUE KEY `uk_student_year_semester_month` (`student_id`, `academic_year`, `semester`, `month`, `tenant_id`, `deleted`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='学生操行分表';
|
||||
```
|
||||
|
||||
#### 字段详细说明
|
||||
|
||||
| 字段名 | 类型 | 长度 | 非空 | 默认值 | 说明 |
|
||||
| --------------- | ----------- | ------ | ----- | -------- | ------------------------------ |
|
||||
| id | BIGINT | 20 | Y | - | 主键ID(雪花ID) |
|
||||
| tenant_id | VARCHAR | 20 | Y | 000000 | 租户编号 |
|
||||
| create_dept | BIGINT | 20 | N | NULL | 创建部门 |
|
||||
| create_by | BIGINT | 20 | N | NULL | 创建者 |
|
||||
| create_time | DATETIME | - | N | NULL | 创建时间 |
|
||||
| update_by | BIGINT | 20 | N | NULL | 更新者 |
|
||||
| update_time | DATETIME | - | N | NULL | 更新时间 |
|
||||
| deleted | TINYINT | 1 | Y | 0 | 删除标志(0存在 1删除) |
|
||||
| academic_year | INT | 4 | Y | - | 学年(如:2024) |
|
||||
| semester | TINYINT | 1 | Y | - | 学期(1-第一学期,2-第二学期) |
|
||||
| month | TINYINT | 2 | Y | - | 月份(1-12) |
|
||||
| student_id | BIGINT | 20 | Y | - | 学生ID,关联xg_student_msg.id |
|
||||
| class_id | BIGINT | 20 | Y | - | 班级ID,关联xg_class_msg.id |
|
||||
| class_teacher_id| BIGINT | 20 | Y | - | 班主任ID,关联sys_user.id |
|
||||
| score | DECIMAL | 5,2 | Y | - | 操行分数(0.00-100.00) |
|
||||
| remark | VARCHAR | 500 | N | NULL | 备注信息 |
|
||||
|
||||
---
|
||||
|
||||
## 🔍 索引设计详解
|
||||
|
||||
### 基础索引(强制)
|
||||
|
||||
```sql
|
||||
-- 租户索引(必须)
|
||||
KEY `idx_tenant_id` (`tenant_id`),
|
||||
|
||||
-- 删除标志索引(必须)
|
||||
KEY `idx_deleted` (`deleted`),
|
||||
|
||||
-- 创建时间索引(建议)
|
||||
KEY `idx_create_time` (`create_time`),
|
||||
```
|
||||
|
||||
### 业务索引
|
||||
|
||||
```sql
|
||||
-- 关联字段索引
|
||||
KEY `idx_student_id` (`student_id`),
|
||||
KEY `idx_class_id` (`class_id`),
|
||||
KEY `idx_class_teacher_id` (`class_teacher_id`),
|
||||
|
||||
-- 时间维度索引
|
||||
KEY `idx_academic_year` (`academic_year`),
|
||||
KEY `idx_semester` (`semester`),
|
||||
KEY `idx_month` (`month`),
|
||||
```
|
||||
|
||||
### 组合索引(性能优化)
|
||||
|
||||
```sql
|
||||
-- 学生学期查询优化
|
||||
KEY `idx_student_year_semester` (`student_id`, `academic_year`, `semester`),
|
||||
|
||||
-- 班级月度录入查询优化
|
||||
KEY `idx_class_year_semester_month` (`class_id`, `academic_year`, `semester`, `month`),
|
||||
|
||||
-- 班主任学期统计查询优化
|
||||
KEY `idx_teacher_year_semester` (`class_teacher_id`, `academic_year`, `semester`),
|
||||
```
|
||||
|
||||
### 唯一约束
|
||||
|
||||
```sql
|
||||
-- 防止同一学生同月重复录入
|
||||
UNIQUE KEY `uk_student_year_semester_month` (`student_id`, `academic_year`, `semester`, `month`, `tenant_id`, `deleted`)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔗 表关系设计
|
||||
|
||||
### 实体关系图
|
||||
|
||||
```mermaid
|
||||
erDiagram
|
||||
xg_conduct_score ||--o{ xg_student_msg : "关联学生"
|
||||
xg_conduct_score }o--|| xg_class_msg : "关联班级"
|
||||
xg_conduct_score }o--|| sys_user : "关联班主任"
|
||||
xg_class_msg ||--o{ xg_student_msg : "班级包含学生"
|
||||
xg_class_msg }o--|| sys_user : "班级有班主任"
|
||||
|
||||
xg_conduct_score {
|
||||
bigint id PK
|
||||
int academic_year
|
||||
tinyint semester
|
||||
tinyint month
|
||||
bigint student_id FK
|
||||
bigint class_id FK
|
||||
bigint class_teacher_id FK
|
||||
decimal score
|
||||
varchar remark
|
||||
datetime create_time
|
||||
}
|
||||
|
||||
xg_student_msg {
|
||||
bigint id PK
|
||||
varchar student_no
|
||||
varchar name
|
||||
bigint class_id FK
|
||||
}
|
||||
|
||||
xg_class_msg {
|
||||
bigint id PK
|
||||
varchar class_name
|
||||
bigint class_teacher_id FK
|
||||
}
|
||||
|
||||
sys_user {
|
||||
bigint id PK
|
||||
varchar user_name
|
||||
varchar nick_name
|
||||
}
|
||||
```
|
||||
|
||||
### 关联关系说明
|
||||
|
||||
1. **xg_conduct_score → xg_student_msg**
|
||||
- 关联字段:student_id
|
||||
- 关联说明:操行分记录所属的学生
|
||||
- 约束:学生必须存在且有效
|
||||
|
||||
2. **xg_conduct_score → xg_class_msg**
|
||||
- 关联字段:class_id
|
||||
- 关联说明:操行分记录所属的班级
|
||||
- 约束:班级必须存在且有效
|
||||
|
||||
3. **xg_conduct_score → sys_user**
|
||||
- 关联字段:class_teacher_id
|
||||
- 关联说明:录入操行分的班主任
|
||||
- 约束:用户必须存在且为班主任角色
|
||||
|
||||
---
|
||||
|
||||
## 📋 字典表设计
|
||||
|
||||
### 字典类型说明
|
||||
|
||||
#### 1. 学期字典 (conduct_semester)
|
||||
- **用途**: 界面显示学期的中文标签
|
||||
- **数据范围**: 1-第一学期,2-第二学期
|
||||
- **使用场景**: 录入界面、查询界面、统计界面的学期选择
|
||||
|
||||
#### 2. 月份字典 (conduct_month)
|
||||
- **用途**: 界面显示月份的中文标签
|
||||
- **数据范围**: 1-12月
|
||||
- **使用场景**: 录入界面、查询界面的月份选择
|
||||
|
||||
### 字典数据结构
|
||||
|
||||
```sql
|
||||
-- 字典类型表
|
||||
CREATE TABLE sys_dict_type (
|
||||
dict_id bigint(20) NOT NULL AUTO_INCREMENT COMMENT '字典主键',
|
||||
dict_name varchar(100) DEFAULT '' COMMENT '字典名称',
|
||||
dict_type varchar(100) NOT NULL DEFAULT '' COMMENT '字典类型',
|
||||
status char(1) DEFAULT '0' COMMENT '状态(0正常 1停用)',
|
||||
create_by bigint(20) DEFAULT NULL COMMENT '创建者',
|
||||
create_time datetime DEFAULT NULL COMMENT '创建时间',
|
||||
update_by bigint(20) DEFAULT NULL COMMENT '更新者',
|
||||
update_time datetime DEFAULT NULL COMMENT '更新时间',
|
||||
remark varchar(500) DEFAULT NULL COMMENT '备注',
|
||||
PRIMARY KEY (dict_id),
|
||||
UNIQUE KEY dict_type (dict_type)
|
||||
);
|
||||
|
||||
-- 字典数据表
|
||||
CREATE TABLE sys_dict_data (
|
||||
dict_code bigint(20) NOT NULL AUTO_INCREMENT COMMENT '字典编码',
|
||||
dict_sort int(4) DEFAULT '0' COMMENT '字典排序',
|
||||
dict_label varchar(100) DEFAULT '' COMMENT '字典标签',
|
||||
dict_value varchar(100) DEFAULT '' COMMENT '字典键值',
|
||||
dict_type varchar(100) DEFAULT '' COMMENT '字典类型',
|
||||
css_class varchar(100) DEFAULT NULL COMMENT '样式属性(其他样式扩展)',
|
||||
list_class varchar(100) DEFAULT NULL COMMENT '表格回显样式',
|
||||
is_default char(1) DEFAULT 'N' COMMENT '是否默认(Y是 N否)',
|
||||
status char(1) DEFAULT '0' COMMENT '状态(0正常 1停用)',
|
||||
create_by bigint(20) DEFAULT NULL COMMENT '创建者',
|
||||
create_time datetime DEFAULT NULL COMMENT '创建时间',
|
||||
update_by bigint(20) DEFAULT NULL COMMENT '更新者',
|
||||
update_time datetime DEFAULT NULL COMMENT '更新时间',
|
||||
remark varchar(500) DEFAULT NULL COMMENT '备注',
|
||||
PRIMARY KEY (dict_code)
|
||||
);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📊 统计查询设计
|
||||
|
||||
### 学期平均分统计
|
||||
|
||||
#### SQL查询示例
|
||||
|
||||
```sql
|
||||
-- 学生学期平均分统计
|
||||
SELECT
|
||||
s.student_no,
|
||||
s.name,
|
||||
c.class_name,
|
||||
cs.academic_year,
|
||||
cs.semester,
|
||||
ROUND(AVG(cs.score), 2) as avg_score,
|
||||
COUNT(*) as record_count
|
||||
FROM xg_conduct_score cs
|
||||
LEFT JOIN xg_student_msg s ON cs.student_id = s.id
|
||||
LEFT JOIN xg_class_msg c ON cs.class_id = c.id
|
||||
WHERE cs.tenant_id = #{tenantId}
|
||||
AND cs.deleted = 0
|
||||
AND cs.academic_year = #{academicYear}
|
||||
AND cs.semester = #{semester}
|
||||
AND s.deleted = 0
|
||||
AND c.deleted = 0
|
||||
GROUP BY cs.student_id, cs.academic_year, cs.semester
|
||||
ORDER BY c.class_name, s.student_no;
|
||||
```
|
||||
|
||||
#### 班主任班级统计
|
||||
|
||||
```sql
|
||||
-- 班主任查看本班学生操行分
|
||||
SELECT
|
||||
s.student_no,
|
||||
s.name,
|
||||
cs.month,
|
||||
cs.score,
|
||||
cs.remark,
|
||||
cs.create_time
|
||||
FROM xg_conduct_score cs
|
||||
LEFT JOIN xg_student_msg s ON cs.student_id = s.id
|
||||
WHERE cs.tenant_id = #{tenantId}
|
||||
AND cs.deleted = 0
|
||||
AND cs.class_teacher_id = #{teacherId}
|
||||
AND cs.academic_year = #{academicYear}
|
||||
AND cs.semester = #{semester}
|
||||
AND cs.month = #{month}
|
||||
AND s.deleted = 0
|
||||
ORDER BY s.student_no;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔒 数据安全设计
|
||||
|
||||
### 数据隔离
|
||||
|
||||
1. **租户隔离**
|
||||
- 所有查询必须包含 `tenant_id = #{tenantId}` 条件
|
||||
- 防止跨租户数据访问
|
||||
|
||||
2. **权限隔离**
|
||||
- 班主任只能查询自己班级的学生
|
||||
- 学生处管理员可以查询全校数据
|
||||
- 通过 `class_teacher_id` 字段控制数据访问权限
|
||||
|
||||
### 数据验证
|
||||
|
||||
1. **分数范围验证**
|
||||
- score 字段必须在 0.00-100.00 范围内
|
||||
- 小数点后最多2位
|
||||
|
||||
2. **时间逻辑验证**
|
||||
- academic_year 必须是有效的年份
|
||||
- semester 只能是 1 或 2
|
||||
- month 必须在 1-12 范围内
|
||||
|
||||
3. **关联数据验证**
|
||||
- student_id 对应的学生必须存在
|
||||
- class_id 对应的班级必须存在
|
||||
- class_teacher_id 对应的用户必须存在且为班主任
|
||||
|
||||
---
|
||||
|
||||
## 📈 性能优化策略
|
||||
|
||||
### 索引优化
|
||||
|
||||
1. **查询模式分析**
|
||||
- 按学生查询历史记录:`student_id + 时间维度`
|
||||
- 按班级批量录入:`class_id + 时间维度`
|
||||
- 按班主任统计:`class_teacher_id + 时间维度`
|
||||
- 学期统计查询:`academic_year + semester`
|
||||
|
||||
2. **组合索引设计**
|
||||
- `idx_student_year_semester`:学生学期查询
|
||||
- `idx_class_year_semester_month`:班级月度录入
|
||||
- `idx_teacher_year_semester`:班主任统计
|
||||
|
||||
### 数据分区(预留)
|
||||
|
||||
```sql
|
||||
-- 按学年分区(未来扩展)
|
||||
PARTITION BY RANGE (academic_year) (
|
||||
PARTITION p2024 VALUES LESS THAN (2025),
|
||||
PARTITION p2025 VALUES LESS THAN (2026),
|
||||
PARTITION p_future VALUES LESS THAN MAXVALUE
|
||||
);
|
||||
```
|
||||
|
||||
### 缓存策略
|
||||
|
||||
1. **学期统计缓存**
|
||||
- 将学期平均分统计结果缓存到 Redis
|
||||
- 缓存键:`conduct_score:stats:{tenantId}:{academicYear}:{semester}`
|
||||
- 过期时间:24小时
|
||||
|
||||
2. **班级学生缓存**
|
||||
- 缓存班主任的班级学生列表
|
||||
- 缓存键:`conduct_score:class_students:{teacherId}`
|
||||
- 过期时间:1小时
|
||||
|
||||
---
|
||||
|
||||
## 🔄 数据迁移
|
||||
|
||||
### 初始数据
|
||||
|
||||
#### 1. 学期字典
|
||||
```sql
|
||||
-- 学期字典类型
|
||||
INSERT INTO sys_dict_type (dict_name, dict_type, status, create_by, create_time, remark)
|
||||
VALUES ('学期', 'conduct_semester', '0', 1, NOW(), '学生操行分管理学期字典');
|
||||
|
||||
-- 学期字典数据
|
||||
INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, status, create_by, create_time, remark) VALUES
|
||||
(1, '第一学期', '1', 'conduct_semester', '0', 1, NOW(), '上学期'),
|
||||
(2, '第二学期', '2', 'conduct_semester', '0', 1, NOW(), '下学期');
|
||||
```
|
||||
|
||||
#### 2. 月份字典
|
||||
```sql
|
||||
-- 月份字典类型
|
||||
INSERT INTO sys_dict_type (dict_name, dict_type, status, create_by, create_time, remark)
|
||||
VALUES ('月份', 'conduct_month', '0', 1, NOW(), '学生操行分管理月份字典');
|
||||
|
||||
-- 月份字典数据
|
||||
INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, status, create_by, create_time, remark) VALUES
|
||||
(1, '1月', '1', 'conduct_month', '0', 1, NOW(), '一月份'),
|
||||
(2, '2月', '2', 'conduct_month', '0', 1, NOW(), '二月份'),
|
||||
(3, '3月', '3', 'conduct_month', '0', 1, NOW(), '三月份'),
|
||||
(4, '4月', '4', 'conduct_month', '0', 1, NOW(), '四月份'),
|
||||
(5, '5月', '5', 'conduct_month', '0', 1, NOW(), '五月份'),
|
||||
(6, '6月', '6', 'conduct_month', '0', 1, NOW(), '六月份'),
|
||||
(7, '7月', '7', 'conduct_month', '0', 1, NOW(), '七月份'),
|
||||
(8, '8月', '8', 'conduct_month', '0', 1, NOW(), '八月份'),
|
||||
(9, '9月', '9', 'conduct_month', '0', 1, NOW(), '九月份'),
|
||||
(10, '10月', '10', 'conduct_month', '0', 1, NOW(), '十月份'),
|
||||
(11, '11月', '11', 'conduct_month', '0', 1, NOW(), '十一月份'),
|
||||
(12, '12月', '12', 'conduct_month', '0', 1, NOW(), '十二月份');
|
||||
```
|
||||
|
||||
### 历史数据迁移(如果需要)
|
||||
|
||||
```sql
|
||||
-- 从其他系统迁移历史操行分数据
|
||||
INSERT INTO xg_conduct_score (
|
||||
id, tenant_id, academic_year, semester, month,
|
||||
student_id, class_id, class_teacher_id, score, remark,
|
||||
create_by, create_time
|
||||
)
|
||||
SELECT
|
||||
#{snowflakeId}, '000000', academic_year, semester, month,
|
||||
student_id, class_id, class_teacher_id, score, remark,
|
||||
#{currentUserId}, NOW()
|
||||
FROM temp_conduct_score_data;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ✅ 数据完整性检查
|
||||
|
||||
### 定期检查脚本
|
||||
|
||||
```sql
|
||||
-- 检查孤立记录(学生不存在)
|
||||
SELECT cs.* FROM xg_conduct_score cs
|
||||
LEFT JOIN xg_student_msg s ON cs.student_id = s.id
|
||||
WHERE cs.deleted = 0 AND (s.id IS NULL OR s.deleted = 1);
|
||||
|
||||
-- 检查孤立记录(班级不存在)
|
||||
SELECT cs.* FROM xg_conduct_score cs
|
||||
LEFT JOIN xg_class_msg c ON cs.class_id = c.id
|
||||
WHERE cs.deleted = 0 AND (c.id IS NULL OR c.deleted = 1);
|
||||
|
||||
-- 检查孤立记录(班主任不存在)
|
||||
SELECT cs.* FROM xg_conduct_score cs
|
||||
LEFT JOIN sys_user u ON cs.class_teacher_id = u.user_id
|
||||
WHERE cs.deleted = 0 AND u.user_id IS NULL;
|
||||
|
||||
-- 检查分数范围异常
|
||||
SELECT * FROM xg_conduct_score
|
||||
WHERE deleted = 0 AND (score < 0 OR score > 100);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📝 使用说明
|
||||
|
||||
### 建表顺序
|
||||
|
||||
1. 确保依赖表已存在:`xg_student_msg`、`xg_class_msg`、`sys_user`
|
||||
2. 执行操行分表创建脚本
|
||||
3. 创建相关索引
|
||||
4. 初始化字典数据:学期字典、月份字典
|
||||
|
||||
### 注意事项
|
||||
|
||||
1. **数据一致性**:录入时必须确保学生、班级、班主任的关联关系正确
|
||||
2. **性能监控**:定期监控查询性能,根据实际使用情况调整索引
|
||||
3. **数据备份**:重要数据定期备份,防止数据丢失
|
||||
4. **审计记录**:所有数据变更都要记录审计日志
|
||||
|
||||
---
|
||||
|
||||
## 📞 技术支持
|
||||
|
||||
如有疑问或需要技术支持,请联系:
|
||||
|
||||
- **技术负责人**:项目技术负责人
|
||||
- **数据库管理员**:负责数据库运维
|
||||
- **业务负责人**:学生处管理员
|
||||
|
||||
---
|
||||
|
||||
**最后更新**: 2025-01-13
|
||||
**数据库版本**: MySQL 8.0+
|
||||
**字符集**: utf8mb4
|
||||
**字典表**: 已定义学期和月份字典
|
||||
@@ -0,0 +1,473 @@
|
||||
# 学生操行分管理 - 业务需求说明
|
||||
|
||||
## 📋 文档说明
|
||||
|
||||
**版本**: v1.0
|
||||
**制定时间**: 2025-01-13
|
||||
**适用范围**: 学生操行分管理模块业务需求分析
|
||||
**重要性**: ⚠️ **核心文档** - 系统开发的基础和验收标准
|
||||
|
||||
---
|
||||
|
||||
## 🎯 需求概述
|
||||
|
||||
### 业务背景
|
||||
|
||||
学校需要建立完整的学生操行评价体系,帮助班主任每月对班级学生进行操行表现量化评价,并为学生处管理员提供学期统计分析功能。该系统将操行分作为学生综合素质评价的重要组成部分,为学校管理决策提供数据支持。
|
||||
|
||||
### 业务价值
|
||||
|
||||
1. **量化评价**: 将班主任对学生的操行评价从定性转为定量,便于管理和比较
|
||||
2. **过程记录**: 记录学生每月操行表现变化趋势,为学生成长提供参考
|
||||
3. **统计分析**: 学期结束自动生成平均分统计,为学生评优评先提供依据
|
||||
4. **管理决策**: 为学生处管理工作提供数据化支撑
|
||||
|
||||
### 需求目标
|
||||
|
||||
实现一个完整的学生操行分管理系统,包括:
|
||||
- 班主任每月操行分录入功能
|
||||
- 学生处管理员学期统计查看功能
|
||||
- 完整的数据查询和导出功能
|
||||
- 严格的权限控制和数据安全
|
||||
|
||||
---
|
||||
|
||||
## 👥 用户角色分析
|
||||
|
||||
### 1. 班主任
|
||||
|
||||
#### 用户画像
|
||||
|
||||
- **身份**: 学校各班级的任课教师或班主任
|
||||
- **职责**: 负责班级学生的日常管理和操行评价
|
||||
- **技术水平**: 中等,熟悉办公软件操作
|
||||
- **使用频率**: 每月1次(月末录入当月操行分)
|
||||
|
||||
#### 业务需求
|
||||
|
||||
1. **班级学生查看**
|
||||
- 查看自己负责班级的学生名单
|
||||
- 了解学生基本信息(学号、姓名、性别等)
|
||||
|
||||
2. **操行分录入**
|
||||
- 选择录入月份(默认为当月)
|
||||
- 为每位学生录入操行分数(0-100分)
|
||||
- 添加相应的备注说明
|
||||
- 批量保存所有学生的分数
|
||||
|
||||
3. **历史记录查看**
|
||||
- 查看本班学生历史操行分记录
|
||||
- 了解学生的操行表现变化趋势
|
||||
|
||||
#### 使用场景
|
||||
|
||||
- **月末评价**: 每月最后一天,班主任登录系统录入当月操行分
|
||||
- **学期总结**: 学期结束前,查看班级整体操行表现情况
|
||||
- **学生沟通**: 与学生或家长沟通时参考操行分记录
|
||||
|
||||
### 2. 学生处管理员
|
||||
|
||||
#### 用户画像
|
||||
|
||||
- **身份**: 学校学生处工作人员
|
||||
- **职责**: 负责全校学生管理工作和数据统计
|
||||
- **技术水平**: 较高,熟悉数据分析和报表制作
|
||||
- **使用频率**: 每周1-2次,学期结束时频率更高
|
||||
|
||||
#### 业务需求
|
||||
|
||||
1. **学期统计分析**
|
||||
- 选择学年和学期,查看全校学生操行平均分
|
||||
- 按班级、年级等维度进行分组统计
|
||||
- 查看优秀、良好、一般等分数段分布
|
||||
|
||||
2. **数据查询筛选**
|
||||
- 按班级、姓名等条件筛选学生
|
||||
- 查看学生的详细操行分记录
|
||||
- 导出统计数据用于报表制作
|
||||
|
||||
3. **数据导出**
|
||||
- 导出Excel格式的统计报表
|
||||
- 支持自定义导出字段和格式
|
||||
|
||||
#### 使用场景
|
||||
|
||||
- **学期考核**: 学期结束时生成学生操行表现统计报表
|
||||
- **评优评先**: 为学生评优、奖学金评选提供数据依据
|
||||
- **家校沟通**: 为家长会或家访提供学生表现数据
|
||||
- **管理工作**: 分析班级管理效果,为教学管理提供参考
|
||||
|
||||
---
|
||||
|
||||
## 📋 功能需求清单
|
||||
|
||||
### 核心功能需求
|
||||
|
||||
#### F01: 操行分录入管理
|
||||
|
||||
**优先级**: 高 (P1)
|
||||
**需求描述**: 班主任每月为班级学生录入操行分数
|
||||
|
||||
**功能点**:
|
||||
- F01.1: 选择录入月份(支持选择历史月份补录)
|
||||
- F01.2: 显示班级学生列表(包含学号、姓名、性别)
|
||||
- F01.3: 为每位学生录入分数(0.00-100.00,保留2位小数)
|
||||
- F01.4: 添加备注信息(最多500字符)
|
||||
- F01.5: 批量保存所有学生的操行分
|
||||
- F01.6: 验证数据完整性(分数必填,备注可选)
|
||||
- F01.7: 防止重复录入(同一学生同月只能录入一次)
|
||||
|
||||
**验收标准**:
|
||||
- ✅ 可成功录入45人班级的操行分(5分钟内完成)
|
||||
- ✅ 数据验证完整,分数范围正确
|
||||
- ✅ 重复录入时给出明确提示
|
||||
|
||||
#### F02: 学期平均分统计
|
||||
|
||||
**优先级**: 高 (P1)
|
||||
**需求描述**: 自动计算并展示学生的学期操行平均分
|
||||
|
||||
**功能点**:
|
||||
- F02.1: 选择学年和学期进行统计
|
||||
- F02.2: 显示学生列表及平均分
|
||||
- F02.3: 展示统计信息(最高分、最低分、记录次数)
|
||||
- F02.4: 按班级分组显示
|
||||
- F02.5: 支持分页展示(默认每页20条)
|
||||
- F02.6: 实时计算平均分(基于每月分数)
|
||||
|
||||
**验收标准**:
|
||||
- ✅ 正确计算学期平均分(6个月分数平均)
|
||||
- ✅ 统计信息准确(最大值、最小值、记录数)
|
||||
- ✅ 班级分组清晰,数据排序合理
|
||||
|
||||
#### F03: 数据查询功能
|
||||
|
||||
**优先级**: 中 (P2)
|
||||
**需求描述**: 提供灵活的数据查询和筛选功能
|
||||
|
||||
**功能点**:
|
||||
- F03.1: 按学年、学期、月份筛选
|
||||
- F03.2: 按学生姓名、班级名称筛选
|
||||
- F03.3: 按分数范围筛选(最低分-最高分)
|
||||
- F03.4: 支持组合条件查询
|
||||
- F03.5: 结果列表展示(支持排序)
|
||||
- F03.6: 详情查看(查看单条记录的详细信息)
|
||||
|
||||
**验收标准**:
|
||||
- ✅ 多条件组合查询正常工作
|
||||
- ✅ 查询结果准确,响应时间<2秒
|
||||
- ✅ 列表排序功能正常
|
||||
|
||||
#### F04: 数据导出功能
|
||||
|
||||
**优先级**: 中 (P2)
|
||||
**需求描述**: 支持将统计数据导出为Excel文件
|
||||
|
||||
**功能点**:
|
||||
- F04.1: 导出学期平均分统计表
|
||||
- F04.2: 支持自定义导出字段
|
||||
- F04.3: Excel格式,支持公式计算
|
||||
- F04.4: 文件命名规范(包含学年学期信息)
|
||||
|
||||
**验收标准**:
|
||||
- ✅ 导出文件格式正确,数据完整
|
||||
- ✅ 文件名包含必要信息,便于识别
|
||||
- ✅ 大数据量导出不影响系统性能
|
||||
|
||||
### 辅助功能需求
|
||||
|
||||
#### F05: 权限控制
|
||||
|
||||
**优先级**: 高 (P1)
|
||||
**需求描述**: 严格的权限控制,确保数据安全
|
||||
|
||||
**功能点**:
|
||||
- F05.1: 班主任只能录入自己班级的学生
|
||||
- F05.2: 学生处管理员可查看全校数据
|
||||
- F05.3: 操作权限验证(增删改查分离)
|
||||
- F05.4: 数据隔离(不同用户看到不同数据)
|
||||
|
||||
**验收标准**:
|
||||
- ✅ 班主任无法录入其他班级学生分数
|
||||
- ✅ 管理员可查看所有班级数据
|
||||
- ✅ 权限不足时给出明确提示
|
||||
|
||||
#### F06: 数据验证
|
||||
|
||||
**优先级**: 高 (P1)
|
||||
**需求描述**: 完整的数据验证机制
|
||||
|
||||
**功能点**:
|
||||
- F06.1: 分数范围验证(0.00-100.00)
|
||||
- F06.2: 必填字段验证
|
||||
- F06.3: 数据类型验证
|
||||
- F06.4: 关联数据存在性验证
|
||||
- F06.5: 业务规则验证(不可重复录入)
|
||||
|
||||
**验收标准**:
|
||||
- ✅ 所有验证规则正常工作
|
||||
- ✅ 错误信息明确易懂
|
||||
- ✅ 验证失败时数据不会保存
|
||||
|
||||
---
|
||||
|
||||
## 🔄 业务流程
|
||||
|
||||
### 主要业务流程
|
||||
|
||||
#### 班主任录入操行分流程
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A[班主任登录系统] --> B[选择操行分录入菜单]
|
||||
B --> C[选择录入月份]
|
||||
C --> D[系统加载班级学生列表]
|
||||
D --> E[班主任录入学生分数和备注]
|
||||
E --> F{数据验证通过?}
|
||||
F -->|否| G[显示错误信息,重新录入]
|
||||
G --> E
|
||||
F -->|是| H[点击保存按钮]
|
||||
H --> I[系统批量保存数据]
|
||||
I --> J{保存成功?}
|
||||
J -->|否| K[显示保存失败信息]
|
||||
K --> I
|
||||
J -->|是| L[显示保存成功提示]
|
||||
L --> M[返回列表页面或继续其他操作]
|
||||
```
|
||||
|
||||
#### 学生处管理员查看统计流程
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A[管理员登录系统] --> B[选择操行分统计菜单]
|
||||
B --> C[选择学年和学期]
|
||||
C --> D[系统查询统计数据]
|
||||
D --> E[显示学生平均分列表]
|
||||
E --> F{需要筛选?}
|
||||
F -->|是| G[输入筛选条件]
|
||||
G --> H[重新查询显示]
|
||||
H --> I[查看详细统计信息]
|
||||
F -->|否| I
|
||||
I --> J{需要导出?}
|
||||
J -->|否| K[查看完毕,结束]
|
||||
J -->|是| L[选择导出格式]
|
||||
L --> M[生成并下载文件]
|
||||
M --> K
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📊 数据需求
|
||||
|
||||
### 数据录入需求
|
||||
|
||||
#### 操行分数据字段
|
||||
|
||||
| 字段名称 | 数据类型 | 必填 | 取值范围 | 说明 |
|
||||
| -------- | -------- | ---- | -------- | ---- |
|
||||
| 学年 | 整数 | 是 | 2020-2030 | 如:2024 |
|
||||
| 学期 | 整数 | 是 | 1-2 | 1=第一学期,2=第二学期 |
|
||||
| 月份 | 整数 | 是 | 1-12 | 月份 |
|
||||
| 学生ID | 长整数 | 是 | - | 关联学生表 |
|
||||
| 班级ID | 长整数 | 是 | - | 关联班级表 |
|
||||
| 班主任ID | 长整数 | 是 | - | 关联用户表 |
|
||||
| 操行分数 | 小数 | 是 | 0.00-100.00 | 保留2位小数 |
|
||||
| 备注 | 文本 | 否 | 最多500字符 | 评价说明 |
|
||||
|
||||
#### 数据约束
|
||||
|
||||
1. **唯一性约束**: 同一学生在同一学年、学期、月份只能有一条记录
|
||||
2. **关联约束**: 学生必须属于班主任负责的班级
|
||||
3. **时间约束**: 录入月份不能超过当前月份
|
||||
4. **分数约束**: 必须在0-100范围内,允许小数点后2位
|
||||
|
||||
### 数据查询需求
|
||||
|
||||
#### 统计数据字段
|
||||
|
||||
| 字段名称 | 计算方式 | 显示格式 | 说明 |
|
||||
| -------- | -------- | -------- | ---- |
|
||||
| 学生基本信息 | 关联查询 | 学号+姓名 | 从学生表获取 |
|
||||
| 班级信息 | 关联查询 | 班级名称 | 从班级表获取 |
|
||||
| 学期平均分 | AVG(每月分数) | 保留2位小数 | 6个月分数的平均值 |
|
||||
| 最高分数 | MAX(每月分数) | 保留2位小数 | 学期内最高分 |
|
||||
| 最低分数 | MIN(每月分数) | 保留2位小数 | 学期内最低分 |
|
||||
| 记录次数 | COUNT(*) | 整数 | 有效记录数 |
|
||||
| 最后更新时间 | MAX(更新时间) | 日期时间 | 数据最后更新时间 |
|
||||
|
||||
### 数据导出需求
|
||||
|
||||
#### Excel导出格式
|
||||
|
||||
| 列名 | 数据类型 | 说明 |
|
||||
| ---- | -------- | ---- |
|
||||
| 学号 | 文本 | 学生学号 |
|
||||
| 姓名 | 文本 | 学生姓名 |
|
||||
| 性别 | 文本 | 男/女 |
|
||||
| 班级 | 文本 | 班级名称 |
|
||||
| 学期平均分 | 数值 | 保留2位小数 |
|
||||
| 最高分 | 数值 | 学期最高分 |
|
||||
| 最低分 | 数值 | 学期最低分 |
|
||||
| 记录次数 | 数值 | 有效记录数 |
|
||||
|
||||
---
|
||||
|
||||
## 🎯 非功能需求
|
||||
|
||||
### 性能需求
|
||||
|
||||
1. **响应时间**
|
||||
- 页面加载:< 3秒
|
||||
- 数据查询:< 2秒
|
||||
- 数据保存:< 5秒(批量保存45人数据)
|
||||
|
||||
2. **并发处理**
|
||||
- 支持同时在线用户:50人
|
||||
- 支持并发录入:10人同时录入
|
||||
|
||||
3. **数据量**
|
||||
- 支持学生数:5000人
|
||||
- 支持历史数据:3年
|
||||
|
||||
### 安全性需求
|
||||
|
||||
1. **权限控制**
|
||||
- 班主任数据隔离:只能操作本班数据
|
||||
- 操作审计:记录所有数据变更操作
|
||||
- 登录验证:必须登录才能操作
|
||||
|
||||
2. **数据安全**
|
||||
- 数据加密:敏感数据加密存储
|
||||
- 数据备份:定期自动备份
|
||||
- 数据恢复:支持数据恢复
|
||||
|
||||
### 易用性需求
|
||||
|
||||
1. **界面设计**
|
||||
- 简洁直观的操作界面
|
||||
- 清晰的数据展示格式
|
||||
- 友好的错误提示信息
|
||||
|
||||
2. **操作便捷**
|
||||
- 批量操作支持
|
||||
- 快捷键支持(Enter键切换输入框)
|
||||
- 数据验证实时提示
|
||||
|
||||
### 可维护性需求
|
||||
|
||||
1. **代码结构**
|
||||
- 模块化设计,职责分离
|
||||
- 统一的编码规范
|
||||
- 完整的注释文档
|
||||
|
||||
2. **日志记录**
|
||||
- 操作日志:记录用户操作
|
||||
- 错误日志:记录系统异常
|
||||
- 审计日志:记录数据变更
|
||||
|
||||
---
|
||||
|
||||
## 🔄 业务规则
|
||||
|
||||
### 录入规则
|
||||
|
||||
1. **时间规则**
|
||||
- 每月最后3天可以录入当月分数
|
||||
- 可以补录之前月份的分数(班主任权限)
|
||||
- 不能录入未来月份的分数
|
||||
|
||||
2. **权限规则**
|
||||
- 班主任只能录入自己负责班级的学生
|
||||
- 同一学生每月只能被录入一次分数
|
||||
- 录入后可以修改,但需要记录修改历史
|
||||
|
||||
3. **数据规则**
|
||||
- 分数必须在0-100范围内
|
||||
- 备注信息最多500字符
|
||||
- 所有必填字段必须填写完整
|
||||
|
||||
### 统计规则
|
||||
|
||||
1. **计算规则**
|
||||
- 学期平均分 = 6个月分数的算术平均值
|
||||
- 如果某月缺失分数,该月不计入平均分计算
|
||||
- 平均分保留2位小数
|
||||
|
||||
2. **展示规则**
|
||||
- 按班级分组显示
|
||||
- 按平均分降序排列
|
||||
- 显示统计概览信息
|
||||
|
||||
---
|
||||
|
||||
## 📋 验收标准
|
||||
|
||||
### 功能验收标准
|
||||
|
||||
1. **操行分录入功能**
|
||||
- ✅ 班主任可成功录入45人班级的所有操行分
|
||||
- ✅ 数据验证完整,错误信息明确
|
||||
- ✅ 重复录入时正确提示并阻止
|
||||
|
||||
2. **统计功能**
|
||||
- ✅ 学期平均分计算正确
|
||||
- ✅ 统计数据展示完整
|
||||
- ✅ 查询筛选功能正常
|
||||
|
||||
3. **权限控制**
|
||||
- ✅ 班主任只能看到自己班级数据
|
||||
- ✅ 管理员可查看全校数据
|
||||
- ✅ 权限不足时正确提示
|
||||
|
||||
### 性能验收标准
|
||||
|
||||
1. **响应时间**
|
||||
- ✅ 页面加载时间 < 3秒
|
||||
- ✅ 数据查询时间 < 2秒
|
||||
- ✅ 批量保存时间 < 5秒
|
||||
|
||||
2. **并发性能**
|
||||
- ✅ 支持10个用户同时录入
|
||||
- ✅ 系统响应正常,无明显卡顿
|
||||
|
||||
### 兼容性验收标准
|
||||
|
||||
1. **浏览器兼容**
|
||||
- ✅ Chrome 80+
|
||||
- ✅ Firefox 75+
|
||||
- ✅ Edge 80+
|
||||
|
||||
2. **移动端适配**
|
||||
- ✅ 支持主流移动设备
|
||||
- ✅ 响应式布局正常
|
||||
|
||||
---
|
||||
|
||||
## 📞 需求确认
|
||||
|
||||
### 需求评审记录
|
||||
|
||||
| 评审时间 | 评审人员 | 评审内容 | 评审结果 |
|
||||
| -------- | -------- | -------- | -------- |
|
||||
| 2025-01-13 | 产品经理、开发团队、班主任代表、学生处管理员 | 业务需求说明 | ✅ 通过 |
|
||||
|
||||
### 需求变更记录
|
||||
|
||||
| 变更时间 | 变更内容 | 变更原因 | 影响评估 |
|
||||
| -------- | -------- | -------- | -------- |
|
||||
| - | - | - | - |
|
||||
|
||||
---
|
||||
|
||||
## 📋 相关文档
|
||||
|
||||
- **技术设计**: `../技术设计/数据库设计.md`
|
||||
- **API接口**: `../技术设计/API接口设计.md`
|
||||
- **用户故事**: `用户故事.md`
|
||||
- **非功能需求**: `非功能需求.md`
|
||||
|
||||
---
|
||||
|
||||
**最后更新**: 2025-01-13
|
||||
**需求版本**: v1.0
|
||||
**确认状态**: ✅ 已确认
|
||||
Reference in new issue
Block a user