This commit is contained in:
zlwu committed 2026-01-04 10:00:08 +08:00
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
**确认状态**: ✅ 已确认