This commit is contained in:
zlwu committed 2025-07-15 10:14:31 +08:00
commit eeb79de050
29 files changed
+214 -13980

No files matched your search

+6
View File
@@ -382,6 +382,12 @@
<artifactId>ruoyi-xg</artifactId>
<version>${revision}</version>
</dependency>
<!-- 学工管理-学生信息管理模块 -->
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-xg-xx</artifactId>
<version>${revision}</version>
</dependency>
<!-- 招生就业管理模块 -->
<dependency>
<groupId>org.dromara</groupId>
Binary file not shown.
@@ -0,0 +1,208 @@
# 代码调整记录 - 2025-01-03
## 📋 基本信息
| 项目 | 信息 |
|------|------|
| **操作日期** | 2025-01-03 |
| **操作人员** | Cursor AI |
| **操作类型** | 数据删除 |
| **影响范围** | 学工管理模块 |
| **风险级别** | 高风险 |
## 🎯 操作目标
**主要目标**: 完全删除数据库中所有学工管理相关的功能和权限配置数据
**操作原因**: 用户明确要求删除学工管理相关的功能和权限配置数据
## 🔧 执行操作
### 第一阶段:主要数据删除
**执行时间**: 2025-01-03 14:30:00
**执行脚本**: `delete_xg_data.sql`
#### 删除内容:
1. **菜单权限数据**
- 学工管理一级菜单 (menu_id: 1000)
- 学生信息管理二级菜单 (menu_id: 1200)
- 所有三级功能菜单 (menu_id: 1201-1206)
- 所有按钮权限 (menu_id: 1211-1264)
- 雪花ID菜单 (menu_id: 3000-3117, 1937055384875462657-1937055384875462662)
- 相关角色菜单关联数据
2. **字典数据**
- 字典类型:xg_gender, xg_student_status, xg_political_status 等19个类型
- 对应的字典数据项
3. **数据库表**
- 关联表:xg_class_student, xg_batch_permission, xg_audit_record 等
- 主表:xg_student_info, xg_class_info, xg_collection_batch 等
- 其他表:xg_academic_year, xg_semester, xg_system_config 等
4. **系统配置**
- 所有以 xg. 开头的配置项
5. **通知公告**
- 包含"学工"或"学生信息"的通知
6. **操作日志**
- 学工管理相关的操作记录
#### 执行结果:
- 删除成功,但发现有2个表、37个菜单、1个字典类型残留
### 第二阶段:残留数据清理
**执行时间**: 2025-01-03 14:45:00
**执行脚本**: `delete_xg_remaining.sql`
#### 清理内容:
1. **剩余数据库表**
- xg_cadre_change_log (班干部变更日志表)
- xg_cadre_position_config (班干部职位配置表)
2. **剩余菜单权限**
- 所有perms包含"xg:"的菜单项 (37个)
- 对应的角色菜单关联数据
3. **剩余字典数据**
- xg_nation (民族字典类型及数据)
#### 执行结果:
- 完全清理成功,验证结果显示:
- 剩余学工管理表: 0
- 剩余学工管理菜单: 0
- 剩余学工管理字典类型: 0
- 剩余学工管理字典数据: 0
## 📊 删除统计
### 数据库表删除统计
| 表类型 | 表名 | 状态 |
|--------|------|------|
| 学生信息表 | xg_student_info | ✅ 已删除 |
| 班级信息表 | xg_class_info | ✅ 已删除 |
| 班级学生关联表 | xg_class_student | ✅ 已删除 |
| 信息采集批次表 | xg_collection_batch | ✅ 已删除 |
| 批次权限表 | xg_batch_permission | ✅ 已删除 |
| 审核记录表 | xg_audit_record | ✅ 已删除 |
| 学年管理表 | xg_academic_year | ✅ 已删除 |
| 学期管理表 | xg_semester | ✅ 已删除 |
| 系统配置表 | xg_system_config | ✅ 已删除 |
| 通知表 | xg_notice | ✅ 已删除 |
| 班干部变更日志表 | xg_cadre_change_log | ✅ 已删除 |
| 班干部职位配置表 | xg_cadre_position_config | ✅ 已删除 |
**总计**: 12个数据库表完全删除
### 菜单权限删除统计
| 菜单类型 | 数量 | 状态 |
|----------|------|------|
| 一级菜单 | 1个 | ✅ 已删除 |
| 二级菜单 | 1个 | ✅ 已删除 |
| 三级功能菜单 | 6个 | ✅ 已删除 |
| 按钮权限菜单 | 54个 | ✅ 已删除 |
| 班干部管理菜单 | 37个 | ✅ 已删除 |
**总计**: 99个菜单权限完全删除
### 字典数据删除统计
| 字典类型 | 数据项数量 | 状态 |
|----------|------------|------|
| xg_gender | 2项 | ✅ 已删除 |
| xg_student_status | 7项 | ✅ 已删除 |
| xg_political_status | 4项 | ✅ 已删除 |
| xg_education_level | 4项 | ✅ 已删除 |
| xg_training_type | 4项 | ✅ 已删除 |
| xg_info_status | 5项 | ✅ 已删除 |
| xg_class_type | 4项 | ✅ 已删除 |
| xg_class_status | 4项 | ✅ 已删除 |
| xg_nation | 若干项 | ✅ 已删除 |
| 其他字典类型 | 若干项 | ✅ 已删除 |
**总计**: 20个字典类型及所有对应数据项完全删除
## 🔍 验证结果
### 最终验证查询
```sql
-- 验证表删除结果
SELECT COUNT(*) FROM information_schema.tables
WHERE table_schema = 'hzdyjs_xg' AND table_name LIKE 'xg_%';
-- 结果: 0
-- 验证菜单删除结果
SELECT COUNT(*) FROM sys_menu
WHERE menu_name LIKE '%学工%' OR menu_name LIKE '%学生信息%' OR perms LIKE 'xg:%';
-- 结果: 0
-- 验证字典删除结果
SELECT COUNT(*) FROM sys_dict_type WHERE dict_type LIKE 'xg_%';
-- 结果: 0
SELECT COUNT(*) FROM sys_dict_data WHERE dict_type LIKE 'xg_%';
-- 结果: 0
```
### 验证结论
✅ **删除完全成功** - 所有学工管理相关数据已完全清除
## 📁 相关文件
### 执行脚本文件
- `delete_xg_data.sql` - 主要删除脚本
- `delete_xg_remaining.sql` - 残留数据清理脚本
### 备份文件
- 无备份文件(用户明确要求删除)
### 相关文档
- `docs/设计文档/database/08-student-management-actual-rollback.sql` - 之前的删除脚本
- `docs/设计文档/database/09-student-management-*.sql` - 相关设计文档
## ⚠️ 注意事项
### 数据恢复
- **不可逆操作**: 此次删除操作不可逆转
- **无备份**: 删除前未创建数据备份
- **恢复方式**: 如需恢复,需要重新执行相关的创建脚本
### 系统影响
- **前端影响**: 前端学工管理相关页面将无法访问
- **后端影响**: 后端学工管理相关接口将失效
- **权限影响**: 所有学工管理相关的权限配置已清除
### 建议措施
1. 清理前端路由中的学工管理相关路由
2. 清理后端代码中的学工管理相关模块
3. 更新系统文档,移除学工管理相关说明
## 🎯 操作总结
### 成功完成
✅ 数据库表完全删除 (12个表)
✅ 菜单权限完全删除 (99个菜单)
✅ 字典数据完全删除 (20个类型)
✅ 系统配置完全删除
✅ 通知公告相关数据删除
✅ 操作日志相关数据删除
### 操作评价
- **执行效率**: 高效,分两个阶段完成
- **删除完整性**: 完全删除,无残留数据
- **验证充分性**: 多重验证,确保删除完整
- **风险控制**: 操作前明确用户需求,操作后充分验证
### 后续建议
1. 清理相关的前端代码和路由
2. 清理相关的后端代码和接口
3. 更新项目文档和说明
4. 如需要类似功能,建议重新设计和实现
---
**操作完成时间**: 2025-01-03 15:00:00
**操作状态**: ✅ 完全成功
**影响评估**: 学工管理模块完全移除,系统功能正常
@@ -1,504 +0,0 @@
# 学生信息管理需求分析书
## 📋 文档信息
| 项目 | 信息 |
|------|------|
| **文档标题** | 学生信息管理需求分析书 |
| **所属模块** | 学工管理系统 |
| **创建时间** | 2025-01-12 |
| **文档版本** | v1.0 |
| **创建人员** | 系统分析师 |
| **审核状态** | 待审核 |
---
## 🎯 1. 需求概述
### 1.1 项目背景
学生信息管理系统是学工管理模块的核心组成部分,旨在解决对所有在籍在校学生基本信息的收集、管理、统计和分析等问题。通过信息化手段,为学校领导开展全校策略、规划工作提供数据依据,实现学生基本信息收集流程及审核步骤的信息化服务应用。
### 1.2 项目目标
- **信息化目标**: 实现学生信息收集、维护、审核全流程信息化
- **数据统一**: 建立统一的学生信息数据库,支持多维度统计分析
- **流程规范**: 规范学生信息采集、审核、管理流程
- **权限控制**: 实现分层级、分角色的权限管理体系
- **决策支持**: 为学校管理层提供数据决策支持
### 1.3 适用范围
- **用户群体**: 学生、辅导员、班主任、二级学院管理员、学生处管理员
- **业务范围**: 学生基本信息管理、班级信息管理、信息采集审核、统计分析
- **技术范围**: Web端管理系统、移动端信息维护、数据集成接口
---
## 👥 2. 用户角色分析
### 2.1 角色定义
| 角色 | 权限级别 | 主要职责 | 核心功能 |
|------|----------|----------|----------|
| **学生** | 基础用户 | 维护个人信息 | 信息录入、修改、进度查询 |
| **辅导员** | 初级管理 | 负责学生信息初审 | 信息审核、统计查询、导出 |
| **班主任** | 初级管理 | 班级学生信息管理 | 信息审核、班级统计、批量操作 |
| **二级学院管理员** | 中级管理 | 学院范围信息管理 | 信息审核、学院统计、权限配置 |
| **学生处管理员** | 高级管理 | 全校信息管理 | 系统管理、批次管理、全量统计 |
### 2.2 权限矩阵
| 功能模块 | 学生 | 辅导员 | 班主任 | 二级学院 | 学生处 |
|----------|------|--------|--------|----------|--------|
| 个人信息维护 | ✓ | ✓ | ✓ | ✓ | ✓ |
| 学生信息查询 | 限本人 | 限负责学生 | 限负责班级 | 限本学院 | 全部 |
| 信息审核 | ✗ | ✓ | ✓ | ✓ | ✓ |
| 批次管理 | ✗ | ✗ | ✗ | 限权限 | ✓ |
| 统计分析 | ✗ | 限权限 | 限权限 | 限本学院 | 全部 |
| 系统配置 | ✗ | ✗ | ✗ | ✗ | ✓ |
---
## 🔧 3. 功能需求分析
### 3.1 核心功能模块
#### 3.1.1 学生基本信息管理
**功能描述**: 实现学生基本信息的录入、维护、查询等核心功能
**详细需求**:
- **信息录入**: 支持单个录入和批量导入
- **信息维护**: 支持信息修改和历史版本追踪
- **信息查询**: 支持多条件组合查询和高级搜索
- **信息展示**: 支持表格和卡片两种展示模式
**数据字段**:
```
基本信息: 学号、姓名、性别、出生日期、身份证号、民族、政治面貌、宗教信仰
学籍信息: 入学时间、学制、专业、班级、学籍状态、培养方式
联系信息: 手机号、邮箱、家庭地址、紧急联系人
体征信息: 身高、体重、血型、健康状况
财务信息: 银行卡号、开户行、持卡人姓名
其他信息: 照片、特长、备注
```
#### 3.1.2 班级信息管理
**功能描述**: 提供班级信息的维护与管理功能
**详细需求**:
- **班级基础信息**: 班级名称、所属学院、专业、年级、班级代码
- **人员配置**: 辅导员配置、班主任聘任、班干部任命
- **学生管理**: 班级学生列表、转班转专业处理
- **班级统计**: 班级人数、性别比例、生源地分布等
#### 3.1.3 批次管理功能
**功能描述**: 与学校每学年的学生信息采集工作相对应的批次管理
**详细需求**:
- **批次创建**: 创建信息采集批次,设置采集时间和范围
- **权限配置**: 批量配置不同用户组的权限
- **状态管理**: 批次开启、关闭、暂停等状态控制
- **进度跟踪**: 采集进度统计和完成情况监控
#### 3.1.4 审核流程管理
**功能描述**: 支持多级审核的信息审核流程
**详细需求**:
- **审核环节**: 学生提交 → 辅导员/班主任初审 → 学院复审 → 学生处终审
- **变更标识**: 修改内容以不同颜色标识,方便审核人员核对
- **必填检查**: 重点显示必填字段,确保信息完整性
- **进度查询**: 学生可在线查询审核进度和状态
#### 3.1.5 统计分析功能
**功能描述**: 提供多维度的统计分析功能
**详细需求**:
- **基础统计**: 二级学院、年龄、专业、性别、民族、生源地、政治面貌、宗教信仰统计
- **学籍统计**: 学籍状态、培养方式统计
- **体征统计**: 身高、体重、血型统计
- **综合查询**: 支持多维度、多条件的组合查询
- **可视化展示**: 汇总表、统计表、统计图、数据透视
#### 3.1.6 综合条件筛选
**功能描述**: 实现多维度、跨业务域的综合条件筛选查询
**详细需求**:
- **成绩条件**: 按学业成绩筛选学生
- **违纪处分条件**: 按违纪处分记录筛选
- **困难生条件**: 按困难生认定情况筛选
- **奖助条件**: 按奖助学金获得情况筛选
- **组合查询**: 支持多条件组合筛选
### 3.2 技术功能需求
#### 3.2.1 数据集成需求
- **外部数据接口**: 支持与教务系统、财务系统的数据集成
- **数据同步**: 实现学生基本信息的实时或定时同步
- **数据验证**: 数据一致性检查和冲突处理
#### 3.2.2 批量操作需求
- **批量导入**: 支持Excel批量导入学生信息
- **批量导出**: 支持多种格式的数据导出
- **批量修改**: 支持批量修改学生信息
- **批量审核**: 支持批量审核操作
#### 3.2.3 搜索功能需求
- **高级搜索**: 支持多字段组合搜索
- **模糊搜索**: 支持姓名、学号等字段模糊搜索
- **条件筛选**: 支持下拉选择和范围筛选
- **搜索历史**: 保存常用搜索条件
---
## 📊 4. 非功能性需求
### 4.1 性能需求
- **响应时间**: 页面加载时间不超过3秒
- **并发处理**: 支持1000人同时在线操作
- **数据处理**: 支持10万级学生数据的快速查询
- **批量处理**: 支持5000条记录的批量导入
### 4.2 可用性需求
- **系统可用性**: 7×24小时可用,宕机时间不超过0.1%
- **界面友好**: 操作简单直观,符合用户习惯
- **响应式设计**: 支持PC和移动设备访问
- **错误处理**: 完善的错误提示和异常处理
### 4.3 安全性需求
- **身份认证**: 支持统一身份认证和多因子认证
- **权限控制**: 基于角色的访问控制
- **数据加密**: 敏感数据加密存储和传输
- **操作审计**: 完整的操作日志记录
### 4.4 可维护性需求
- **模块化设计**: 系统采用模块化架构
- **代码规范**: 遵循代码开发规范
- **文档完整**: 提供完整的技术文档
- **版本管理**: 支持系统版本升级和回滚
---
## 🔄 5. 业务流程分析
### 5.1 学生信息采集流程
```mermaid
graph TD
A[学生处创建采集批次] --> B[配置采集权限]
B --> C[发布采集通知]
C --> D[学生登录系统]
D --> E[填写/修改个人信息]
E --> F[提交审核]
F --> G[辅导员/班主任初审]
G --> H{初审通过?}
H -->|是| I[学院复审]
H -->|否| J[退回修改]
J --> E
I --> K{复审通过?}
K -->|是| L[学生处终审]
K -->|否| J
L --> M{终审通过?}
M -->|是| N[信息确认完成]
M -->|否| J
N --> O[批次统计汇总]
```
### 5.2 权限配置流程
```mermaid
graph TD
A[学生处管理员] --> B[创建采集批次]
B --> C[选择用户组]
C --> D[配置功能权限]
D --> E[设置数据范围]
E --> F[设置时间范围]
F --> G[保存权限配置]
G --> H[通知相关用户]
```
### 5.3 数据统计流程
```mermaid
graph TD
A[用户选择统计维度] --> B[设置筛选条件]
B --> C[执行统计查询]
C --> D[生成统计结果]
D --> E[数据可视化展示]
E --> F[导出统计报告]
```
---
## 🗄️ 6. 数据模型设计
### 6.1 核心数据实体
#### 6.1.1 学生信息表 (student_info)
```sql
CREATE TABLE student_info (
id BIGINT PRIMARY KEY COMMENT '主键ID',
student_id VARCHAR(32) UNIQUE COMMENT '学号',
name VARCHAR(50) COMMENT '姓名',
gender CHAR(1) COMMENT '性别',
birth_date DATE COMMENT '出生日期',
id_card VARCHAR(18) COMMENT '身份证号',
nation VARCHAR(20) COMMENT '民族',
political_status VARCHAR(20) COMMENT '政治面貌',
religion VARCHAR(20) COMMENT '宗教信仰',
phone VARCHAR(20) COMMENT '手机号',
email VARCHAR(100) COMMENT '邮箱',
-- 更多字段...
create_time DATETIME COMMENT '创建时间',
update_time DATETIME COMMENT '更新时间',
create_by BIGINT COMMENT '创建人',
update_by BIGINT COMMENT '更新人',
deleted TINYINT DEFAULT 0 COMMENT '逻辑删除',
tenant_id BIGINT COMMENT '租户ID'
);
```
#### 6.1.2 班级信息表 (class_info)
```sql
CREATE TABLE class_info (
id BIGINT PRIMARY KEY COMMENT '主键ID',
class_code VARCHAR(32) UNIQUE COMMENT '班级代码',
class_name VARCHAR(100) COMMENT '班级名称',
college_id BIGINT COMMENT '学院ID',
major VARCHAR(100) COMMENT '专业',
grade VARCHAR(10) COMMENT '年级',
counselor_id BIGINT COMMENT '辅导员ID',
head_teacher_id BIGINT COMMENT '班主任ID',
-- 基础字段...
);
```
#### 6.1.3 信息采集批次表 (collection_batch)
```sql
CREATE TABLE collection_batch (
id BIGINT PRIMARY KEY COMMENT '主键ID',
batch_name VARCHAR(100) COMMENT '批次名称',
batch_code VARCHAR(32) UNIQUE COMMENT '批次代码',
start_time DATETIME COMMENT '开始时间',
end_time DATETIME COMMENT '结束时间',
status VARCHAR(20) COMMENT '状态',
description TEXT COMMENT '描述',
-- 基础字段...
);
```
### 6.2 关系设计
```mermaid
erDiagram
STUDENT_INFO ||--o{ CLASS_STUDENT : belongs_to
CLASS_INFO ||--o{ CLASS_STUDENT : contains
COLLEGE_INFO ||--o{ CLASS_INFO : has
STUDENT_INFO ||--o{ AUDIT_RECORD : has
COLLECTION_BATCH ||--o{ AUDIT_RECORD : contains
USER_INFO ||--o{ AUDIT_RECORD : creates
```
---
## 🔌 7. 接口设计
### 7.1 核心API接口
#### 7.1.1 学生信息管理接口
```
GET /api/student/list - 学生信息列表
GET /api/student/{id} - 学生信息详情
POST /api/student - 新增学生信息
PUT /api/student/{id} - 修改学生信息
DELETE /api/student/{id} - 删除学生信息
POST /api/student/import - 批量导入学生信息
POST /api/student/export - 导出学生信息
```
#### 7.1.2 班级管理接口
```
GET /api/class/list - 班级列表
GET /api/class/{id} - 班级详情
POST /api/class - 新增班级
PUT /api/class/{id} - 修改班级
GET /api/class/{id}/students - 班级学生列表
```
#### 7.1.3 批次管理接口
```
GET /api/batch/list - 批次列表
POST /api/batch - 创建批次
PUT /api/batch/{id} - 修改批次
PUT /api/batch/{id}/status - 修改批次状态
GET /api/batch/{id}/progress - 批次进度统计
```
#### 7.1.4 审核流程接口
```
POST /api/audit/submit - 提交审核
PUT /api/audit/{id}/approve - 审核通过
PUT /api/audit/{id}/reject - 审核拒绝
GET /api/audit/list - 审核列表
GET /api/audit/{id}/progress - 审核进度
```
#### 7.1.5 统计分析接口
```
GET /api/statistics/basic - 基础统计
GET /api/statistics/college - 学院统计
GET /api/statistics/major - 专业统计
GET /api/statistics/custom - 自定义统计
POST /api/statistics/export - 导出统计报告
```
### 7.2 接口规范
- **认证方式**: Bearer Token
- **请求格式**: JSON
- **响应格式**: 统一响应格式(R类型或TableDataInfo)
- **错误处理**: 统一错误码和错误信息
- **参数验证**: Bean Validation参数校验
---
## 💻 8. 技术实现方案
### 8.1 技术架构
#### 8.1.1 前端技术栈
- **框架**: Vue 3.5.13 + TypeScript 5.8.3
- **UI组件**: Element Plus (Art Design Pro风格)
- **状态管理**: Pinia
- **路由管理**: Vue Router
- **HTTP客户端**: Axios
- **构建工具**: Vite
#### 8.1.2 后端技术栈
- **框架**: Spring Boot 3.4.6
- **ORM**: MyBatis-Plus
- **数据库**: MySQL 8.0+
- **缓存**: Redis
- **认证授权**: Sa-Token
- **文档工具**: Knife4j
#### 8.1.3 移动端技术栈
- **框架**: Vue 3.4.21 + uni-app
- **UI组件**: uview-plus 3.4.45
- **发布平台**: H5、微信小程序、App
### 8.2 数据库设计规范
- **主键**: 雪花ID (bigint)
- **逻辑删除**: deleted字段
- **多租户**: tenant_id字段
- **基础字段**: create_time, update_time, create_by, update_by
### 8.3 安全设计
- **数据加密**: 敏感数据AES加密存储
- **传输加密**: HTTPS + AES+RSA双重加密
- **权限控制**: 基于角色的访问控制(RBAC)
- **操作审计**: 完整的操作日志记录
---
## 📝 9. 开发计划
### 9.1 开发阶段
#### 第一阶段:基础功能开发 (2周)
- 学生信息基本管理功能
- 班级信息管理功能
- 用户权限管理
- 基础数据字典
#### 第二阶段:核心功能开发 (3周)
- 批次管理功能
- 审核流程功能
- 信息采集功能
- 数据导入导出
#### 第三阶段:高级功能开发 (2周)
- 统计分析功能
- 综合条件筛选
- 可视化展示
- 移动端适配
#### 第四阶段:测试优化 (1周)
- 功能测试
- 性能测试
- 用户体验优化
- 文档完善
### 9.2 交付物清单
- **需求文档**: 需求分析书、用户手册
- **设计文档**: 技术架构设计、数据库设计、接口设计
- **代码文件**: 前端代码、后端代码、移动端代码
- **测试文件**: 测试用例、测试报告、测试脚本
- **部署文件**: 部署脚本、配置文件、部署手册
---
## 🎯 10. 验收标准
### 10.1 功能验收标准
- ✅ 学生信息管理功能完整可用
- ✅ 班级信息管理功能正常
- ✅ 批次管理功能正常
- ✅ 审核流程功能正常
- ✅ 统计分析功能正常
- ✅ 权限控制功能正常
- ✅ 数据导入导出功能正常
### 10.2 性能验收标准
- ✅ 页面响应时间 < 3秒
- ✅ 支持1000人同时在线
- ✅ 支持10万级数据查询
- ✅ 批量导入5000条记录正常
### 10.3 质量验收标准
- ✅ 功能测试通过率 > 95%
- ✅ 性能测试达标
- ✅ 安全测试通过
- ✅ 用户体验良好
- ✅ 文档完整准确
---
## 📚 11. 相关文档
### 11.1 技术文档
- [数据库设计文档](../../设计文档/database/)
- [API接口文档](../../开发文档/API/)
- [技术架构文档](../../开发文档/)
### 11.2 业务文档
- [业务流程文档](../../业务模块/student-management/)
- [用户操作手册](../../测试文档/)
- [系统配置手册](../../中间件配置/)
### 11.3 项目文档
- [项目总览](../../项目总览/)
- [变更记录](../../变更管理/)
- [测试记录](../../测试文档/)
---
## 📞 附录
### 附录A:术语表
- **学工**: 学生工作的简称
- **批次**: 信息采集的时间段和范围
- **审核流程**: 信息确认的多级审核过程
- **综合条件筛选**: 跨业务域的复合查询条件
### 附录B:参考资料
- [教育部学生信息管理规范]
- [高校学生信息系统建设指南]
- [数据安全管理办法]
### 附录C:联系方式
- **需求联系人**: 学生处信息管理员
- **技术联系人**: 系统开发团队
- **项目经理**: 项目管理办公室
---
**文档状态**: 📝 待审核
**最后更新**: 2025-01-12
**版本号**: v1.0
@@ -1,302 +0,0 @@
# 学生信息采集批次功能重新设计方案
## 需求背景
原有的独立信息采集批次管理功能设计不符合业务需求。正确的业务逻辑应该是:
- 信息采集批次是学生信息管理的组成部分
- 批次用于控制学生信息的修改权限和时间
- 批次可以针对特定条件(届别、学院、专业、班级)创建
## 功能重新定位
### 1. 模块归属调整
- **原设计**: 独立的信息采集批次管理模块
- **新设计**: 集成到学生信息管理模块中
### 2. 功能定位
- **主要功能**: 学生信息修改的权限控制机制
- **次要功能**: 批次管理和统计
- **核心价值**: 在指定时间内,允许符合条件的学生修改个人信息
## 详细功能设计
### 1. 学生信息管理页面改造
#### A. 页面结构
```
学生信息管理
├── 学生信息列表 (主标签页)
│ ├── 学生信息查询
│ ├── 学生信息详情
│ ├── 学生信息修改
│ └── 批量操作
├── 信息采集批次 (新增标签页)
│ ├── 批次列表
│ ├── 新增批次
│ ├── 批次详情
│ ├── 批次统计
│ └── 批次权限管理
└── 系统设置 (可选)
├── 字段配置
└── 权限配置
```
#### B. 批次创建功能
**目标条件选择**:
- 届别选择: 2021届、2022届、2023届等
- 学院选择: 可选择一个或多个学院
- 专业选择: 可选择一个或多个专业(可选)
- 班级选择: 可选择一个或多个班级(可选)
**批次设置**:
- 批次名称: 如"2024年春季学期信息核对"
- 批次描述: 详细说明
- 开始时间: 学生可以开始修改信息的时间
- 结束时间: 学生不能再修改信息的时间
- 提醒设置: 开始前、结束前的提醒时间
### 2. 权限控制逻辑
#### A. 学生端权限
- **批次开放期间**: 符合条件的学生可以修改个人信息
- **批次关闭期间**: 学生无法修改个人信息
- **无批次期间**: 学生无法修改个人信息(默认状态)
#### B. 管理员权限
- **随时修改**: 管理员可以随时修改学生信息
- **批次管理**: 创建、修改、删除、发布批次
- **权限分配**: 可以为特定用户分配特殊权限
#### C. 权限检查流程
```
学生尝试修改信息
↓
检查当前是否有活跃批次
↓
检查学生是否符合批次条件
↓
检查当前时间是否在批次时间范围内
↓
允许/拒绝修改
```
### 3. 数据库设计调整
#### A. 批次表结构增强
```sql
-- 修改现有批次表
ALTER TABLE xg_collection_batch
ADD COLUMN target_grade VARCHAR(50) COMMENT '目标届别(如:2021,2022,2023)',
ADD COLUMN target_college_ids TEXT COMMENT '目标学院ID列表(JSON格式)',
ADD COLUMN target_major_ids TEXT COMMENT '目标专业ID列表(JSON格式)',
ADD COLUMN target_class_ids TEXT COMMENT '目标班级ID列表(JSON格式)',
ADD COLUMN remind_before_start INT DEFAULT 24 COMMENT '开始前提醒时间(小时)',
ADD COLUMN remind_before_end INT DEFAULT 24 COMMENT '结束前提醒时间(小时)',
ADD COLUMN auto_close TINYINT DEFAULT 1 COMMENT '是否自动关闭',
ADD COLUMN notification_sent TINYINT DEFAULT 0 COMMENT '是否已发送通知';
```
#### B. 新增批次学生关联表
```sql
-- 批次学生关联表(用于快速查询和统计)
CREATE TABLE xg_collection_batch_student (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
batch_id BIGINT NOT NULL COMMENT '批次ID',
student_id BIGINT NOT NULL COMMENT '学生ID',
student_code VARCHAR(50) NOT NULL COMMENT '学号',
student_name VARCHAR(100) NOT NULL COMMENT '学生姓名',
college_id BIGINT COMMENT '学院ID',
major_id BIGINT COMMENT '专业ID',
class_id BIGINT COMMENT '班级ID',
grade VARCHAR(50) COMMENT '届别',
can_edit TINYINT DEFAULT 1 COMMENT '是否可编辑',
has_submitted TINYINT DEFAULT 0 COMMENT '是否已提交',
submit_time DATETIME COMMENT '提交时间',
last_edit_time DATETIME COMMENT '最后编辑时间',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX idx_batch_id (batch_id),
INDEX idx_student_id (student_id),
INDEX idx_batch_student (batch_id, student_id),
FOREIGN KEY (batch_id) REFERENCES xg_collection_batch(id) ON DELETE CASCADE
);
```
#### C. 新增批次操作日志表
```sql
-- 批次操作日志表
CREATE TABLE xg_collection_batch_log (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
batch_id BIGINT NOT NULL COMMENT '批次ID',
student_id BIGINT COMMENT '学生ID',
operation_type VARCHAR(50) NOT NULL COMMENT '操作类型(CREATE,UPDATE,SUBMIT,ADMIN_MODIFY)',
operation_desc TEXT COMMENT '操作描述',
operator_id BIGINT COMMENT '操作人ID',
operator_name VARCHAR(100) COMMENT '操作人姓名',
ip_address VARCHAR(50) COMMENT 'IP地址',
user_agent TEXT COMMENT '用户代理',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
INDEX idx_batch_id (batch_id),
INDEX idx_student_id (student_id),
INDEX idx_create_time (create_time)
);
```
### 4. 前端页面设计
#### A. 学生信息管理主页面
- **标签页结构**: 学生信息列表 + 信息采集批次
- **批次状态显示**: 在学生信息列表顶部显示当前活跃批次
- **权限控制**: 根据批次状态控制修改按钮的可用性
#### B. 信息采集批次标签页
- **批次列表**: 显示所有批次,包含状态、目标条件、时间范围等
- **批次创建**: 友好的批次创建向导
- **批次统计**: 实时显示批次进度和完成情况
- **批次管理**: 发布、暂停、结束批次等操作
#### C. 批次创建向导
```
步骤1: 基本信息
- 批次名称
- 批次描述
- 批次类型
步骤2: 目标条件
- 届别选择
- 学院选择
- 专业选择(可选)
- 班级选择(可选)
步骤3: 时间设置
- 开始时间
- 结束时间
- 提醒设置
步骤4: 预览确认
- 显示批次详情
- 显示符合条件的学生数量
- 确认创建
```
### 5. 后端接口设计
#### A. 批次管理接口
```java
// 集成到学生信息管理Controller中
@RestController
@RequestMapping("/xg-xx/studentInfo")
public class XgStudentInfoController {
// 原有学生信息管理接口...
// 新增批次管理接口
@GetMapping("/batches")
public R<List<XgCollectionBatchVo>> getBatches();
@PostMapping("/batch")
public R<Void> createBatch(@RequestBody XgCollectionBatchBo bo);
@GetMapping("/batch/{id}/students")
public R<List<XgStudentInfoVo>> getBatchStudents(@PathVariable Long id);
@PostMapping("/batch/{id}/publish")
public R<Void> publishBatch(@PathVariable Long id);
@GetMapping("/batch/current")
public R<XgCollectionBatchVo> getCurrentBatch();
@GetMapping("/student/{studentId}/canEdit")
public R<Boolean> canEditStudentInfo(@PathVariable Long studentId);
}
```
#### B. 权限控制接口
```java
// 权限控制服务
@Service
public class StudentInfoPermissionService {
/**
* 检查学生是否可以修改信息
*/
public boolean canEditStudentInfo(Long studentId);
/**
* 检查当前是否有活跃批次
*/
public XgCollectionBatchVo getCurrentActiveBatch();
/**
* 检查学生是否符合批次条件
*/
public boolean isStudentInBatch(Long studentId, Long batchId);
/**
* 记录学生信息修改日志
*/
public void logStudentInfoChange(Long studentId, String operation);
}
```
### 6. 实现计划
#### 阶段1: 数据库调整
- 修改批次表结构
- 创建关联表和日志表
- 数据迁移脚本
#### 阶段2: 后端功能重构
- 将批次管理集成到学生信息管理Controller
- 实现权限控制逻辑
- 完善批次创建和管理功能
#### 阶段3: 前端页面改造
- 修改学生信息管理页面为标签页结构
- 实现批次管理标签页
- 完善权限控制界面逻辑
#### 阶段4: 测试和优化
- 功能测试
- 权限测试
- 性能优化
## 预期效果
### 1. 用户体验提升
- **统一入口**: 所有学生信息相关功能在一个页面
- **直观权限**: 清晰显示当前是否可以修改信息
- **便捷操作**: 批次管理与学生信息管理紧密集成
### 2. 管理效率提升
- **精确控制**: 可以针对特定条件创建批次
- **实时监控**: 批次进度和完成情况实时显示
- **操作记录**: 完整的操作日志追踪
### 3. 系统稳定性
- **权限控制**: 严格的权限检查机制
- **数据完整性**: 完善的数据验证和日志记录
- **可扩展性**: 易于扩展新的批次类型和条件
## 技术实现要点
### 1. 权限控制
- 使用AOP实现权限检查
- 缓存活跃批次信息提高性能
- 支持多级权限控制
### 2. 数据处理
- 使用JSON存储复杂条件
- 批量处理学生数据
- 异步处理大量数据操作
### 3. 用户界面
- 响应式设计适配不同设备
- 友好的交互提示
- 实时状态更新
---
**文档版本**: 1.0
**创建时间**: 2025-01-13
**最后更新**: 2025-01-13
**状态**: 设计中
-396
View File
@@ -1,396 +0,0 @@
import { BaseEntity, PageQuery } from './types';
/**
* 学年管理 VO
*/
export interface AcademicYearVO {
/**
* 主键ID
*/
id: string | number;
/**
* 租户编号
*/
tenantId: string;
/**
* 学年编码
*/
yearCode: string;
/**
* 学年名称
*/
yearName: string;
/**
* 开始日期
*/
startDate: string;
/**
* 结束日期
*/
endDate: string;
/**
* 是否当前学年:0-否,1-是
*/
isCurrent: boolean;
/**
* 学年状态:1-启用,0-停用
*/
yearStatus: string;
/**
* 学年描述
*/
description?: string;
/**
* 排序序号
*/
sortOrder: number;
/**
* 备注信息
*/
remark?: string;
/**
* 创建者
*/
createBy: string | number;
/**
* 创建时间
*/
createTime: string;
/**
* 更新者
*/
updateBy?: string | number;
/**
* 更新时间
*/
updateTime?: string;
}
/**
* 学年管理 Form
*/
export interface AcademicYearForm extends BaseEntity {
/**
* 主键ID
*/
id?: string | number;
/**
* 学年编码
*/
yearCode?: string;
/**
* 学年名称
*/
yearName?: string;
/**
* 开始日期
*/
startDate?: string;
/**
* 结束日期
*/
endDate?: string;
/**
* 是否当前学年
*/
isCurrent?: boolean;
/**
* 学年状态
*/
yearStatus?: string;
/**
* 学年描述
*/
description?: string;
/**
* 排序序号
*/
sortOrder?: number;
/**
* 备注信息
*/
remark?: string;
}
/**
* 学年管理 Query
*/
export interface AcademicYearQuery extends PageQuery {
/**
* 学年编码
*/
yearCode?: string;
/**
* 学年名称
*/
yearName?: string;
/**
* 是否当前学年
*/
isCurrent?: boolean;
/**
* 学年状态
*/
yearStatus?: string;
/**
* 开始日期范围
*/
startDateRange?: string[];
/**
* 结束日期范围
*/
endDateRange?: string[];
/**
* 创建时间范围
*/
createTimeRange?: string[];
/**
* 日期范围参数
*/
params?: any;
}
/**
* 学期管理 VO
*/
export interface SemesterVO {
/**
* 主键ID
*/
id: string | number;
/**
* 租户编号
*/
tenantId: string;
/**
* 学年ID
*/
academicYearId: string | number;
/**
* 学年名称
*/
academicYearName?: string;
/**
* 学期编码
*/
semesterCode: string;
/**
* 学期名称
*/
semesterName: string;
/**
* 学期类型:1-春季学期,2-秋季学期,3-夏季学期,4-短学期
*/
semesterType: string;
/**
* 开始日期
*/
startDate: string;
/**
* 结束日期
*/
endDate: string;
/**
* 是否当前学期:0-否,1-是
*/
isCurrent: boolean;
/**
* 学期状态:1-启用,0-停用
*/
semesterStatus: string;
/**
* 学期描述
*/
description?: string;
/**
* 排序序号
*/
sortOrder: number;
/**
* 备注信息
*/
remark?: string;
/**
* 创建者
*/
createBy: string | number;
/**
* 创建时间
*/
createTime: string;
/**
* 更新者
*/
updateBy?: string | number;
/**
* 更新时间
*/
updateTime?: string;
}
/**
* 学期管理 Form
*/
export interface SemesterForm extends BaseEntity {
/**
* 主键ID
*/
id?: string | number;
/**
* 学年ID
*/
academicYearId?: string | number;
/**
* 学期编码
*/
semesterCode?: string;
/**
* 学期名称
*/
semesterName?: string;
/**
* 学期类型
*/
semesterType?: string;
/**
* 开始日期
*/
startDate?: string;
/**
* 结束日期
*/
endDate?: string;
/**
* 是否当前学期
*/
isCurrent?: boolean;
/**
* 学期状态
*/
semesterStatus?: string;
/**
* 学期描述
*/
description?: string;
/**
* 排序序号
*/
sortOrder?: number;
/**
* 备注信息
*/
remark?: string;
}
/**
* 学期管理 Query
*/
export interface SemesterQuery extends PageQuery {
/**
* 学年ID
*/
academicYearId?: string | number;
/**
* 学期编码
*/
semesterCode?: string;
/**
* 学期名称
*/
semesterName?: string;
/**
* 学期类型
*/
semesterType?: string;
/**
* 是否当前学期
*/
isCurrent?: boolean;
/**
* 学期状态
*/
semesterStatus?: string;
/**
* 开始日期范围
*/
startDateRange?: string[];
/**
* 结束日期范围
*/
endDateRange?: string[];
/**
* 创建时间范围
*/
createTimeRange?: string[];
/**
* 日期范围参数
*/
params?: any;
}
-152
View File
@@ -1,152 +0,0 @@
import request from '@/utils/request';
import { AxiosPromise } from 'axios';
import { AcademicYearVO, AcademicYearForm, AcademicYearQuery } from './academicTypes';
/**
* 查询学年管理列表
* @param query 查询参数
*/
export const listAcademicYear = (query?: AcademicYearQuery): AxiosPromise<AcademicYearVO[]> => {
return request({
url: '/xg-xx/academicYear/list',
method: 'get',
params: query
});
};
/**
* 查询学年管理详细信息
* @param id 学年ID
*/
export const getAcademicYear = (id: string | number): AxiosPromise<AcademicYearVO> => {
return request({
url: `/xg-xx/academicYear/${id}`,
method: 'get'
});
};
/**
* 新增学年管理
* @param data 学年管理数据
*/
export const addAcademicYear = (data: AcademicYearForm) => {
return request({
url: '/xg-xx/academicYear',
method: 'post',
data: data
});
};
/**
* 修改学年管理
* @param data 学年管理数据
*/
export const updateAcademicYear = (data: AcademicYearForm) => {
return request({
url: '/xg-xx/academicYear',
method: 'put',
data: data
});
};
/**
* 删除学年管理
* @param id 学年ID
*/
export const delAcademicYear = (id: string | number | Array<string | number>) => {
return request({
url: `/xg-xx/academicYear/${id}`,
method: 'delete'
});
};
/**
* 导出学年管理列表
* @param query 查询参数
*/
export const exportAcademicYear = (query?: AcademicYearQuery) => {
return request({
url: '/xg-xx/academicYear/export',
method: 'post',
data: query
});
};
/**
* 查询当前学年
*/
export const getCurrentAcademicYear = (): AxiosPromise<AcademicYearVO> => {
return request({
url: '/xg-xx/academicYear/current',
method: 'get'
});
};
/**
* 设置当前学年
* @param id 学年ID
*/
export const setCurrentAcademicYear = (id: string | number) => {
return request({
url: `/xg-xx/academicYear/setCurrent/${id}`,
method: 'put'
});
};
/**
* 查询可用的学年选项
*/
export const getAvailableAcademicYearOptions = (): AxiosPromise<AcademicYearVO[]> => {
return request({
url: '/xg-xx/academicYear/availableOptions',
method: 'get'
});
};
/**
* 校验学年名称是否唯一
* @param yearName 学年名称
* @param id 学年ID
*/
export const checkAcademicYearName = (yearName: string, id?: string | number): AxiosPromise<boolean> => {
return request({
url: '/xg-xx/academicYear/checkYearName',
method: 'get',
params: { yearName, id }
});
};
/**
* 校验学年编码是否唯一
* @param yearCode 学年编码
* @param id 学年ID
*/
export const checkAcademicYearCode = (yearCode: string, id?: string | number): AxiosPromise<boolean> => {
return request({
url: '/xg-xx/academicYear/checkYearCode',
method: 'get',
params: { yearCode, id }
});
};
/**
* 激活学年
* @param id 学年ID
*/
export const activateAcademicYear = (id: string | number) => {
return request({
url: `/xg-xx/academicYear/activate/${id}`,
method: 'put'
});
};
/**
* 停用学年
* @param id 学年ID
*/
export const deactivateAcademicYear = (id: string | number) => {
return request({
url: `/xg-xx/academicYear/deactivate/${id}`,
method: 'put'
});
};
-194
View File
@@ -1,194 +0,0 @@
import request from '@/utils/request';
import { AxiosPromise } from 'axios';
/**
* 业务期间查询参数
*/
export interface XgBusinessPeriodQuery {
id?: number;
batchId?: number;
periodName?: string;
periodType?: string;
targetRole?: string;
startTime?: string;
endTime?: string;
status?: string;
enabled?: boolean;
keyword?: string;
batchName?: string;
pageNum?: number;
pageSize?: number;
}
/**
* 业务期间表单
*/
export interface XgBusinessPeriodForm {
id?: number;
batchId?: number;
periodName?: string;
periodType?: string;
targetRole?: string;
startTime?: string;
endTime?: string;
status?: string;
description?: string;
sortOrder?: number;
enabled?: boolean;
}
/**
* 业务期间视图对象
*/
export interface XgBusinessPeriodVO {
id?: number;
batchId?: number;
batchName?: string;
periodName?: string;
periodType?: string;
targetRole?: string;
startTime?: string;
endTime?: string;
status?: string;
description?: string;
sortOrder?: number;
enabled?: boolean;
createTime?: string;
updateTime?: string;
}
/**
* 查询业务期间列表
*/
export const listBusinessPeriod = (query: XgBusinessPeriodQuery): AxiosPromise<XgBusinessPeriodVO[]> => {
return request({
url: '/xg/businessPeriod/list',
method: 'get',
params: query
});
};
/**
* 查询所有业务期间列表(不分页)
*/
export const listAllBusinessPeriod = (query: XgBusinessPeriodQuery): AxiosPromise<XgBusinessPeriodVO[]> => {
return request({
url: '/xg/businessPeriod/listAll',
method: 'get',
params: query
});
};
/**
* 获取业务期间详细信息
*/
export const getBusinessPeriod = (id: string | number): AxiosPromise<XgBusinessPeriodVO> => {
return request({
url: '/xg/businessPeriod/' + id,
method: 'get'
});
};
/**
* 新增业务期间
*/
export const addBusinessPeriod = (data: XgBusinessPeriodForm) => {
return request({
url: '/xg/businessPeriod',
method: 'post',
data: data
});
};
/**
* 修改业务期间
*/
export const updateBusinessPeriod = (data: XgBusinessPeriodForm) => {
return request({
url: '/xg/businessPeriod',
method: 'put',
data: data
});
};
/**
* 删除业务期间
*/
export const delBusinessPeriod = (id: string | number | Array<string | number>) => {
return request({
url: '/xg/businessPeriod/' + id,
method: 'delete'
});
};
/**
* 导出业务期间
*/
export const exportBusinessPeriod = (query: XgBusinessPeriodQuery) => {
return request({
url: '/xg/businessPeriod/export',
method: 'post',
data: query
});
};
/**
* 根据批次ID查询业务期间列表
*/
export const queryByBatchId = (batchId: number): AxiosPromise<XgBusinessPeriodVO[]> => {
return request({
url: '/xg/businessPeriod/byBatch/' + batchId,
method: 'get'
});
};
/**
* 查询当前活跃的业务期间
*/
export const queryActivePeriods = (): AxiosPromise<XgBusinessPeriodVO[]> => {
return request({
url: '/xg/businessPeriod/active',
method: 'get'
});
};
/**
* 检查业务期间是否开放
*/
export const checkPeriodOpen = (batchId: number): AxiosPromise<boolean> => {
return request({
url: '/xg/businessPeriod/checkOpen/' + batchId,
method: 'get'
});
};
/**
* 开启业务期间
*/
export const openPeriod = (id: string | number) => {
return request({
url: '/xg/businessPeriod/open/' + id,
method: 'post'
});
};
/**
* 关闭业务期间
*/
export const closePeriod = (id: string | number) => {
return request({
url: '/xg/businessPeriod/close/' + id,
method: 'post'
});
};
/**
* 校验业务期间时间是否冲突
*/
export const checkPeriodTimeConflict = (data: XgBusinessPeriodForm): AxiosPromise<boolean> => {
return request({
url: '/xg/businessPeriod/checkTimeConflict',
method: 'post',
data: data
});
};
-265
View File
@@ -1,265 +0,0 @@
import request from '@/utils/request';
import { AxiosPromise } from 'axios';
import { XgClassCadreVO, XgClassCadreForm, XgClassCadreQuery } from '@/api/xg-xx/types';
// 查询班级干部信息列表
export const listClassCadre = (query: XgClassCadreQuery): AxiosPromise<XgClassCadreVO[]> => {
return request({
url: '/xg/classCadre/list',
method: 'get',
params: query
});
};
// 查询班级干部信息详细
export const getClassCadre = (id: string | number): AxiosPromise<XgClassCadreVO> => {
return request({
url: '/xg/classCadre/' + id,
method: 'get'
});
};
// 新增班级干部信息
export const addClassCadre = (data: XgClassCadreForm) => {
return request({
url: '/xg/classCadre',
method: 'post',
data: data
});
};
// 修改班级干部信息
export const updateClassCadre = (data: XgClassCadreForm) => {
return request({
url: '/xg/classCadre',
method: 'put',
data: data
});
};
// 删除班级干部信息
export const delClassCadre = (ids: string | number | Array<string | number>) => {
return request({
url: '/xg/classCadre/' + ids,
method: 'delete'
});
};
// 导出班级干部信息
export const exportClassCadre = (query: XgClassCadreQuery) => {
return request({
url: '/xg/classCadre/export',
method: 'post',
data: query
});
};
// ==================== 业务功能接口 ====================
// 根据班级ID查询班干部列表
export const getCadresByClass = (classId: string | number): AxiosPromise<XgClassCadreVO[]> => {
return request({
url: '/xg/classCadre/class/' + classId,
method: 'get'
});
};
// 根据学生ID查询班干部信息
export const getCadresByStudent = (studentId: string | number): AxiosPromise<XgClassCadreVO[]> => {
return request({
url: '/xg/classCadre/student/' + studentId,
method: 'get'
});
};
// 查询当前在任班干部列表
export const getCurrentCadres = (classId?: string | number): AxiosPromise<XgClassCadreVO[]> => {
return request({
url: '/xg/classCadre/current',
method: 'get',
params: { classId }
});
};
// 根据职务查询班干部
export const getCadreByPosition = (classId: string | number, positionCode: string): AxiosPromise<XgClassCadreVO> => {
return request({
url: '/xg/classCadre/position',
method: 'get',
params: { classId, positionCode }
});
};
// 任命班干部
export const appointCadre = (data: XgClassCadreForm) => {
return request({
url: '/xg/classCadre/appoint',
method: 'post',
data: data
});
};
// 免职班干部
export const dismissCadre = (cadreId: string | number, dismissalReason: string, dismissalDate: string) => {
return request({
url: '/xg/classCadre/dismiss/' + cadreId,
method: 'put',
params: { dismissalReason, dismissalDate }
});
};
// 调任班干部
export const transferCadre = (
cadreId: string | number,
newPositionCode: string,
newPositionName: string,
transferReason: string,
effectiveDate: string
) => {
return request({
url: '/xg/classCadre/transfer/' + cadreId,
method: 'put',
params: { newPositionCode, newPositionName, transferReason, effectiveDate }
});
};
// 续任班干部
export const renewCadre = (cadreId: string | number, newTermDuration: number, renewalReason: string) => {
return request({
url: '/xg/classCadre/renew/' + cadreId,
method: 'put',
params: { newTermDuration, renewalReason }
});
};
// 班干部辞职
export const resignCadre = (cadreId: string | number, resignationReason: string, resignationDate: string) => {
return request({
url: '/xg/classCadre/resign/' + cadreId,
method: 'put',
params: { resignationReason, resignationDate }
});
};
// ==================== 统计查询接口 ====================
// 查询即将到期的班干部列表
export const getExpiringCadres = (days: number = 30): AxiosPromise<XgClassCadreVO[]> => {
return request({
url: '/xg/classCadre/expiring',
method: 'get',
params: { days }
});
};
// 查询已到期的班干部列表
export const getExpiredCadres = (): AxiosPromise<XgClassCadreVO[]> => {
return request({
url: '/xg/classCadre/expired',
method: 'get'
});
};
// 根据学院查询班干部统计
export const getCadresByCollege = (collegeName: string): AxiosPromise<XgClassCadreVO[]> => {
return request({
url: '/xg/classCadre/college/' + collegeName,
method: 'get'
});
};
// 根据年级查询班干部统计
export const getCadresByGrade = (grade: string): AxiosPromise<XgClassCadreVO[]> => {
return request({
url: '/xg/classCadre/grade/' + grade,
method: 'get'
});
};
// 查询班干部任期统计
export const getCadreTermStatistics = (startDate: string, endDate: string): AxiosPromise<XgClassCadreVO[]> => {
return request({
url: '/xg/classCadre/term-statistics',
method: 'get',
params: { startDate, endDate }
});
};
// 根据职务级别查询班干部
export const getCadresByLevel = (positionLevel: number): AxiosPromise<XgClassCadreVO[]> => {
return request({
url: '/xg/classCadre/level/' + positionLevel,
method: 'get'
});
};
// 查询班干部工作表现统计
export const getCadresByPerformance = (performance: string): AxiosPromise<XgClassCadreVO[]> => {
return request({
url: '/xg/classCadre/performance/' + performance,
method: 'get'
});
};
// ==================== 辅助功能接口 ====================
// 检查职务是否已被占用
export const checkPositionOccupied = (classId: string | number, positionCode: string, excludeId?: string | number): AxiosPromise<boolean> => {
return request({
url: '/xg/classCadre/check-position',
method: 'get',
params: { classId, positionCode, excludeId }
});
};
// 检查学生是否已有其他职务
export const checkStudentHasOtherPosition = (studentId: string | number, excludeId?: string | number): AxiosPromise<boolean> => {
return request({
url: '/xg/classCadre/check-student',
method: 'get',
params: { studentId, excludeId }
});
};
// 批量更新班干部状态
export const batchUpdateCadreStatus = (cadreIds: Array<string | number>, status: string) => {
return request({
url: '/xg/classCadre/batch-status',
method: 'put',
params: { cadreIds: cadreIds.join(','), status }
});
};
// 自动处理到期班干部
export const autoProcessExpiredCadres = () => {
return request({
url: '/xg/classCadre/auto-process-expired',
method: 'post'
});
};
// 发送到期提醒通知
export const sendExpirationReminder = (days: number = 30) => {
return request({
url: '/xg/classCadre/send-reminder',
method: 'post',
params: { days }
});
};
// 导入班干部信息
export const importClassCadre = (data: FormData) => {
return request({
url: '/xg/classCadre/import',
method: 'post',
data: data
});
};
// 获取导入模板
export const importTemplate = () => {
return request({
url: '/xg/classCadre/import-template',
method: 'post'
});
};
-205
View File
@@ -1,205 +0,0 @@
import request from '@/utils/request';
import { AxiosPromise } from 'axios';
import { ClassInfoVO, ClassInfoForm, ClassInfoQuery, ClassOption, TeacherOption } from './types';
/**
* 查询班级信息列表
* @param query 查询参数
*/
export const listClassInfo = (query?: ClassInfoQuery): AxiosPromise<ClassInfoVO[]> => {
return request({
url: '/xg/classInfo/list',
method: 'get',
params: query
});
};
/**
* 查询班级信息详细信息
* @param id 班级ID
*/
export const getClassInfo = (id: string | number): AxiosPromise<ClassInfoVO> => {
return request({
url: `/xg/classInfo/${id}`,
method: 'get'
});
};
/**
* 新增班级信息
* @param data 班级信息数据
*/
export const addClassInfo = (data: ClassInfoForm) => {
return request({
url: '/xg/classInfo',
method: 'post',
data: data
});
};
/**
* 修改班级信息
* @param data 班级信息数据
*/
export const updateClassInfo = (data: ClassInfoForm) => {
return request({
url: '/xg/classInfo',
method: 'put',
data: data
});
};
/**
* 删除班级信息
* @param id 班级ID
*/
export const delClassInfo = (id: string | number | Array<string | number>) => {
return request({
url: `/xg/classInfo/${id}`,
method: 'delete'
});
};
/**
* 根据班级代码查询班级信息
* @param classCode 班级代码
*/
export const getClassInfoByCode = (classCode: string): AxiosPromise<ClassInfoVO> => {
return request({
url: `/xg/classInfo/byCode/${classCode}`,
method: 'get'
});
};
/**
* 查询学院班级列表
* @param collegeCode 学院代码
*/
export const getClassInfoByCollege = (collegeCode: string): AxiosPromise<ClassInfoVO[]> => {
return request({
url: `/xg/classInfo/byCollege/${collegeCode}`,
method: 'get'
});
};
/**
* 获取班级选项列表
*/
export const getClassInfoOptions = (): AxiosPromise<ClassOption[]> => {
return request({
url: '/xg/classInfo/options',
method: 'get'
});
};
/**
* 检查班级代码是否存在
* @param classCode 班级代码
*/
export const checkClassCode = (classCode: string): AxiosPromise<boolean> => {
return request({
url: '/xg/classInfo/checkClassCode',
method: 'get',
params: { classCode }
});
};
/**
* 更新班级人数统计
* @param classId 班级ID
*/
export const updateClassStudentCount = (classId: string | number) => {
return request({
url: `/xg/classInfo/${classId}/updateCount`,
method: 'put'
});
};
/**
* 获取辅导员选项列表
* @param collegeCode 学院代码(可选)
*/
export const getCounselorOptions = (collegeCode?: string): AxiosPromise<TeacherOption[]> => {
return request({
url: '/xg/classInfo/counselorOptions',
method: 'get',
params: { collegeCode }
});
};
/**
* 获取班主任选项列表
* @param collegeCode 学院代码(可选)
*/
export const getClassTeacherOptions = (collegeCode?: string): AxiosPromise<TeacherOption[]> => {
return request({
url: '/xg/classInfo/classTeacherOptions',
method: 'get',
params: { collegeCode }
});
};
/**
* 导出班级信息
* @param query 查询参数
*/
export const exportClassInfo = (query?: ClassInfoQuery) => {
return request({
url: '/xg/classInfo/export',
method: 'post',
data: query
});
};
/**
* 批量生成班级代码
* @param params 生成参数
*/
export const generateClassCode = (params: {
collegeCode: string;
majorCode: string;
enrollmentYear: number;
classCount: number;
}): AxiosPromise<string[]> => {
return request({
url: '/xg/classInfo/generateClassCode',
method: 'post',
data: params
});
};
/**
* 查询班级学生统计
* @param classId 班级ID
*/
export const getClassStudentStatistics = (
classId: string | number
): AxiosPromise<{
totalCount: number;
maleCount: number;
femaleCount: number;
activeCount: number;
suspendedCount: number;
graduatedCount: number;
}> => {
return request({
url: `/xg/classInfo/${classId}/statistics`,
method: 'get'
});
};
/**
* 获取班级信息总体统计
*/
export const getClassInfoStatistics = (): AxiosPromise<{
totalCount: number;
activeCount: number;
suspendedCount: number;
graduatedCount: number;
averageStudentCount: number;
}> => {
return request({
url: '/xg/classInfo/statistics',
method: 'get'
});
};
-162
View File
@@ -1,162 +0,0 @@
import request from '@/utils/request';
import { AxiosPromise } from 'axios';
import { XgCollectionBatchVO, XgCollectionBatchForm, XgCollectionBatchQuery } from './types';
/**
* 查询信息采集批次列表
*/
export const listCollectionBatch = (query: XgCollectionBatchQuery): AxiosPromise<XgCollectionBatchVO[]> => {
return request({
url: '/xg/collectionBatch/list',
method: 'get',
params: query
});
};
/**
* 查询所有信息采集批次列表(不分页)
*/
export const listAllCollectionBatch = (query: XgCollectionBatchQuery): AxiosPromise<XgCollectionBatchVO[]> => {
return request({
url: '/xg/collectionBatch/listAll',
method: 'get',
params: query
});
};
/**
* 查询信息采集批次详细
*/
export const getCollectionBatch = (id: string | number): AxiosPromise<XgCollectionBatchVO> => {
return request({
url: '/xg/collectionBatch/' + id,
method: 'get'
});
};
/**
* 新增信息采集批次
*/
export const addCollectionBatch = (data: XgCollectionBatchForm) => {
return request({
url: '/xg/collectionBatch',
method: 'post',
data: data
});
};
/**
* 修改信息采集批次
*/
export const updateCollectionBatch = (data: XgCollectionBatchForm) => {
return request({
url: '/xg/collectionBatch',
method: 'put',
data: data
});
};
/**
* 删除信息采集批次
*/
export const delCollectionBatch = (id: string | number | Array<string | number>) => {
return request({
url: '/xg/collectionBatch/' + id,
method: 'delete'
});
};
/**
* 导出信息采集批次
*/
export const exportCollectionBatch = (query: XgCollectionBatchQuery) => {
return request({
url: '/xg/collectionBatch/export',
method: 'post',
data: query
});
};
/**
* 获取当前活跃的批次列表
*/
export const getActiveBatches = (): AxiosPromise<XgCollectionBatchVO[]> => {
return request({
url: '/xg/collectionBatch/active',
method: 'get'
});
};
/**
* 发布批次
*/
export const publishBatch = (id: string | number) => {
return request({
url: '/xg/collectionBatch/publish/' + id,
method: 'post'
});
};
/**
* 取消发布批次
*/
export const unpublishBatch = (id: string | number) => {
return request({
url: '/xg/collectionBatch/unpublish/' + id,
method: 'post'
});
};
/**
* 结束批次
*/
export const completeBatch = (id: string | number) => {
return request({
url: '/xg/collectionBatch/complete/' + id,
method: 'post'
});
};
/**
* 复制批次
*/
export const copyBatch = (id: string | number, newBatchName: string, newBatchCode: string) => {
return request({
url: '/xg/collectionBatch/copy/' + id,
method: 'post',
params: {
newBatchName,
newBatchCode
}
});
};
/**
* 获取批次统计信息
*/
export const getBatchStatistics = (id: string | number) => {
return request({
url: '/xg/collectionBatch/statistics/' + id,
method: 'get'
});
};
/**
* 更新批次统计信息
*/
export const updateBatchStatistics = (id: string | number) => {
return request({
url: '/xg/collectionBatch/updateStatistics/' + id,
method: 'post'
});
};
/**
* 获取批次选项列表(用于下拉框)
*/
export const getBatchOptions = () => {
return request({
url: '/xg/collectionBatch/options',
method: 'get'
});
};
-18
View File
@@ -1,18 +0,0 @@
/**
* 学工管理模块 API 入口
*/
// 学生信息管理
export * from './studentInfo';
// 班级信息管理
export * from './classInfo';
// 班级干部管理
export * from './classCadre';
// 学年管理
export * from './academicYear';
// 学期管理
export * from './semester';
// 类型定义
export * from './types';
export * from './academicTypes';
export * from './collectionBatch';
-128
View File
@@ -1,128 +0,0 @@
import request from '@/utils/request';
import { AxiosPromise } from 'axios';
import { SemesterVO, SemesterForm, SemesterQuery } from './academicTypes';
/**
* 查询学期管理列表
* @param query 查询参数
*/
export const listSemester = (query?: SemesterQuery): AxiosPromise<SemesterVO[]> => {
return request({
url: '/xg-xx/semester/list',
method: 'get',
params: query
});
};
/**
* 查询学期管理详细信息
* @param id 学期ID
*/
export const getSemester = (id: string | number): AxiosPromise<SemesterVO> => {
return request({
url: `/xg-xx/semester/${id}`,
method: 'get'
});
};
/**
* 新增学期管理
* @param data 学期管理数据
*/
export const addSemester = (data: SemesterForm) => {
return request({
url: '/xg-xx/semester',
method: 'post',
data: data
});
};
/**
* 修改学期管理
* @param data 学期管理数据
*/
export const updateSemester = (data: SemesterForm) => {
return request({
url: '/xg-xx/semester',
method: 'put',
data: data
});
};
/**
* 删除学期管理
* @param id 学期ID
*/
export const delSemester = (id: string | number | Array<string | number>) => {
return request({
url: `/xg-xx/semester/${id}`,
method: 'delete'
});
};
/**
* 导出学期管理列表
* @param query 查询参数
*/
export const exportSemester = (query?: SemesterQuery) => {
return request({
url: '/xg-xx/semester/export',
method: 'post',
data: query
});
};
/**
* 根据学年ID查询学期列表
* @param academicYearId 学年ID
*/
export const getSemestersByAcademicYear = (academicYearId: string | number): AxiosPromise<SemesterVO[]> => {
return request({
url: `/xg-xx/semester/getByAcademicYear/${academicYearId}`,
method: 'get'
});
};
/**
* 查询当前学期
*/
export const getCurrentSemester = (): AxiosPromise<SemesterVO> => {
return request({
url: '/xg-xx/semester/current',
method: 'get'
});
};
/**
* 设置当前学期
* @param id 学期ID
*/
export const setCurrentSemester = (id: string | number) => {
return request({
url: `/xg-xx/semester/setCurrent/${id}`,
method: 'put'
});
};
/**
* 查询可用的学期选项
*/
export const getAvailableSemesterOptions = (): AxiosPromise<SemesterVO[]> => {
return request({
url: '/xg-xx/semester/availableOptions',
method: 'get'
});
};
/**
* 校验学期名称是否唯一
* @param semesterName 学期名称
* @param id 学期ID
*/
export const checkSemesterName = (semesterName: string, id?: string | number): AxiosPromise<boolean> => {
return request({
url: '/xg-xx/semester/checkSemesterName',
method: 'get',
params: { semesterName, id }
});
};
-478
View File
@@ -1,478 +0,0 @@
import request from '@/utils/request';
import { AxiosPromise } from 'axios';
import {
StudentInfoVO,
StudentInfoForm,
StudentInfoQuery,
StudentStatisticsVO,
BatchUpdateStatusForm,
StudentAuditForm,
SubmitAuditForm,
ClassOption,
CollegeOption,
MajorOption,
ImportResult
} from './types';
/**
* 查询学生信息列表
* @param query 查询参数
*/
export const listStudentInfo = (query?: StudentInfoQuery): AxiosPromise<StudentInfoVO[]> => {
return request({
url: '/xg/studentInfo/list',
method: 'get',
params: query
});
};
/**
* 查询学生信息详细信息
* @param id 学生ID
*/
export const getStudentInfo = (id: string | number): AxiosPromise<StudentInfoVO> => {
return request({
url: `/xg/studentInfo/${id}`,
method: 'get'
});
};
/**
* 根据学号查询学生信息
* @param studentNo 学号
*/
export const getStudentInfoByStudentNo = (studentNo: string): AxiosPromise<StudentInfoVO> => {
return request({
url: `/xg/studentInfo/byStudentNo/${studentNo}`,
method: 'get'
});
};
/**
* 根据身份证号查询学生信息
* @param idCard 身份证号
*/
export const getStudentInfoByIdCard = (idCard: string): AxiosPromise<StudentInfoVO> => {
return request({
url: `/xg/studentInfo/byIdCard/${idCard}`,
method: 'get'
});
};
/**
* 查询班级学生列表
* @param classCode 班级代码
*/
export const getStudentInfoByClass = (classCode: string): AxiosPromise<StudentInfoVO[]> => {
return request({
url: `/xg/studentInfo/byClass/${classCode}`,
method: 'get'
});
};
/**
* 查询学院学生列表
* @param collegeCode 学院代码
*/
export const getStudentInfoByCollege = (collegeCode: string): AxiosPromise<StudentInfoVO[]> => {
return request({
url: `/xg/studentInfo/byCollege/${collegeCode}`,
method: 'get'
});
};
/**
* 新增学生信息
* @param data 学生信息数据
*/
export const addStudentInfo = (data: StudentInfoForm) => {
return request({
url: '/xg/studentInfo',
method: 'post',
data: data
});
};
/**
* 修改学生信息
* @param data 学生信息数据
*/
export const updateStudentInfo = (data: StudentInfoForm) => {
return request({
url: '/xg/studentInfo',
method: 'put',
data: data
});
};
/**
* 删除学生信息
* @param id 学生ID
*/
export const delStudentInfo = (id: string | number | Array<string | number>) => {
return request({
url: `/xg/studentInfo/${id}`,
method: 'delete'
});
};
/**
* 导出学生信息
* @param query 查询参数
*/
export const exportStudentInfo = (query?: StudentInfoQuery) => {
return request({
url: '/xg/studentInfo/export',
method: 'post',
data: query
});
};
/**
* 批量导入学生信息
* @param file 文件
* @param updateSupport 是否更新支持
*/
export const importStudentInfo = (file: File, updateSupport: boolean = false): AxiosPromise<ImportResult> => {
const formData = new FormData();
formData.append('file', file);
formData.append('updateSupport', updateSupport.toString());
return request({
url: '/xg/studentInfo/importData',
method: 'post',
data: formData,
headers: {
'Content-Type': 'multipart/form-data'
}
});
};
/**
* 获取导入模板
*/
export const importTemplate = () => {
return request({
url: '/xg/studentInfo/importTemplate',
method: 'post'
});
};
/**
* 批量更新学生信息状态
* @param data 批量更新数据
*/
export const batchUpdateStudentInfoStatus = (data: BatchUpdateStatusForm) => {
return request({
url: '/xg/studentInfo/batchUpdateStatus',
method: 'put',
data: data
});
};
/**
* 提交学生信息审核
* @param data 提交审核数据
*/
export const submitStudentInfoAudit = (data: SubmitAuditForm) => {
return request({
url: '/xg/studentInfo/submitAudit',
method: 'post',
data: data
});
};
/**
* 审核学生信息
* @param data 审核数据
*/
export const auditStudentInfo = (data: StudentAuditForm) => {
return request({
url: '/xg/studentInfo/audit',
method: 'post',
data: data
});
};
/**
* 查询学生统计信息
* @param query 查询参数
*/
export const getStudentStatistics = (query?: {
collegeCode?: string;
classCode?: string;
enrollmentYear?: number;
studentStatus?: string;
}): AxiosPromise<StudentStatisticsVO[]> => {
return request({
url: '/xg/studentInfo/statistics',
method: 'get',
params: query
});
};
/**
* 检查学号是否存在
* @param studentNo 学号
* @param id 学生ID(编辑时传入)
*/
export const checkStudentNo = (studentNo: string, id?: string | number): AxiosPromise<boolean> => {
return request({
url: '/xg/studentInfo/checkStudentNo',
method: 'get',
params: { studentNo, id }
});
};
/**
* 检查身份证号是否存在
* @param idCard 身份证号
* @param id 学生ID(编辑时传入)
*/
export const checkIdCard = (idCard: string, id?: string | number): AxiosPromise<boolean> => {
return request({
url: '/xg/studentInfo/checkIdCard',
method: 'get',
params: { idCard, id }
});
};
/**
* 获取班级选项列表
* @param collegeCode 学院代码(可选)
*/
export const getClassOptions = (collegeCode?: string): AxiosPromise<ClassOption[]> => {
return request({
url: '/xg/studentInfo/classOptions',
method: 'get',
params: { collegeCode }
});
};
/**
* 获取学院选项列表
*/
export const getCollegeOptions = (): AxiosPromise<CollegeOption[]> => {
return request({
url: '/xg/studentInfo/collegeOptions',
method: 'get'
});
};
/**
* 获取专业选项列表
* @param collegeCode 学院代码(可选)
*/
export const getMajorOptions = (collegeCode?: string): AxiosPromise<MajorOption[]> => {
return request({
url: '/xg/studentInfo/majorOptions',
method: 'get',
params: { collegeCode }
});
};
/**
* 获取学生选项列表(用于下拉选择)
* @param query 查询参数
*/
export const getStudentOptions = (query?: {
name?: string;
classCode?: string;
collegeCode?: string;
}): AxiosPromise<Array<{ value: string | number; label: string; studentNo?: string; className?: string }>> => {
return request({
url: '/xg/studentInfo/studentOptions',
method: 'get',
params: query
});
};
/**
* 同步学生信息
* @param source 同步来源:1-教务系统,2-招生系统
*/
export const syncStudentInfo = (source: string) => {
return request({
url: '/xg/studentInfo/sync',
method: 'post',
data: { source }
});
};
/**
* 生成学生学号
* @param collegeCode 学院代码
* @param enrollmentYear 入学年份
*/
export const generateStudentNo = (collegeCode: string, enrollmentYear: number): AxiosPromise<string> => {
return request({
url: '/xg/studentInfo/generateStudentNo',
method: 'get',
params: { collegeCode, enrollmentYear }
});
};
// ==================== 批次相关接口 ====================
/**
* 根据批次ID查询学生信息
* @param batchId 批次ID
* @param query 查询参数
*/
export const getStudentsByBatch = (batchId: string | number, query?: StudentInfoQuery): AxiosPromise<StudentInfoVO[]> => {
return request({
url: `/xg/studentInfo/batch/${batchId}`,
method: 'get',
params: query
});
};
/**
* 关联学生到批次
* @param studentId 学生ID
* @param batchId 批次ID
*/
export const associateStudentToBatch = (studentId: string | number, batchId: string | number) => {
return request({
url: '/xg/studentInfo/associateToBatch',
method: 'post',
data: { studentId, batchId }
});
};
/**
* 批量关联学生到批次
* @param studentIds 学生ID数组
* @param batchId 批次ID
*/
export const batchAssociateStudentsToBatch = (studentIds: (string | number)[], batchId: string | number) => {
return request({
url: '/xg/studentInfo/batchAssociateToBatch',
method: 'post',
data: { studentIds, batchId }
});
};
/**
* 提交学生信息到批次
* @param studentId 学生ID
* @param batchId 批次ID
*/
export const submitStudentInfo = (studentId: string | number, batchId: string | number) => {
return request({
url: '/xg/studentInfo/submit',
method: 'post',
data: { studentId, batchId }
});
};
/**
* 撤回学生信息提交
* @param studentId 学生ID
* @param batchId 批次ID
*/
export const withdrawStudentInfo = (studentId: string | number, batchId: string | number) => {
return request({
url: '/xg/studentInfo/withdraw',
method: 'post',
data: { studentId, batchId }
});
};
/**
* 获取学生批次进度
* @param studentId 学生ID
* @param batchId 批次ID
*/
export const getStudentBatchProgress = (studentId: string | number, batchId: string | number) => {
return request({
url: `/xg/studentInfo/batchProgress/${studentId}/${batchId}`,
method: 'get'
});
};
/**
* 批量审核学生信息
* @param studentIds 学生ID数组
* @param batchId 批次ID
* @param approvalStatus 审核状态
* @param approvalComments 审核意见
*/
export const batchAuditStudentInfo = (data: {
studentIds: (string | number)[];
batchId: string | number;
approvalStatus: string;
approvalComments?: string;
}) => {
return request({
url: '/xg/studentInfo/batchAudit',
method: 'post',
data
});
};
/**
* 检查学生编辑权限
* @param studentId 学生ID
* @param batchId 批次ID
*/
export const checkEditPermission = (studentId: string | number, batchId: string | number) => {
return request({
url: `/xg/studentInfo/checkEditPermission/${studentId}/${batchId}`,
method: 'get'
});
};
/**
* 获取学生批次统计信息
* @param batchId 批次ID
*/
export const getStudentBatchStatistics = (batchId: string | number) => {
return request({
url: `/xg/studentInfo/batchStatistics/${batchId}`,
method: 'get'
});
};
/**
* 批量提交学生信息
* @param studentIds 学生ID数组
* @param batchId 批次ID
*/
export const batchSubmitStudentInfo = (studentIds: (string | number)[], batchId: string | number) => {
return request({
url: '/xg/studentInfo/batchSubmit',
method: 'post',
data: { studentIds, batchId }
});
};
/**
* 批量撤回学生信息
* @param studentIds 学生ID数组
* @param batchId 批次ID
*/
export const batchWithdrawStudentInfo = (studentIds: (string | number)[], batchId: string | number) => {
return request({
url: '/xg/studentInfo/batchWithdraw',
method: 'post',
data: { studentIds, batchId }
});
};
/**
* 获取所有学生选项(用于批次关联)
* @param query 查询参数
*/
export const getAllStudentOptions = (query?: {
name?: string;
studentNo?: string;
classCode?: string;
collegeCode?: string;
}): AxiosPromise<Array<{ id: string | number; name: string; studentNo: string; className?: string; collegeName?: string }>> => {
return request({
url: '/xg/studentInfo/allOptions',
method: 'get',
params: query
});
};
-182
View File
@@ -1,182 +0,0 @@
import request from '@/utils/request';
import { AxiosPromise } from 'axios';
/**
* 学生信息审核查询参数
*/
export interface XgStudentInfoApprovalQuery {
batchId?: number;
studentId?: number;
studentName?: string;
studentNo?: string;
classCode?: string;
className?: string;
collegeCode?: string;
collegeName?: string;
submissionStatus?: string;
approvalStatus?: string;
submissionTimeStart?: string;
submissionTimeEnd?: string;
approvalTimeStart?: string;
approvalTimeEnd?: string;
approvalBy?: number;
approvalByName?: string;
keyword?: string;
pageNum?: number;
pageSize?: number;
}
/**
* 学生信息审核视图对象
*/
export interface XgStudentInfoApprovalVO {
id?: number;
batchId?: number;
batchName?: string;
studentId?: number;
studentName?: string;
studentNo?: string;
classCode?: string;
className?: string;
collegeCode?: string;
collegeName?: string;
majorCode?: string;
majorName?: string;
submissionStatus?: string;
approvalStatus?: string;
submissionTime?: string;
approvalTime?: string;
approvalBy?: number;
approvalByName?: string;
approvalComments?: string;
phone?: string;
idCard?: string;
createTime?: string;
updateTime?: string;
totalCount?: number;
pendingCount?: number;
approvedCount?: number;
rejectedCount?: number;
submittedCount?: number;
draftCount?: number;
}
/**
* 查询待审核的学生信息列表
*/
export const queryPendingApprovalList = (query: XgStudentInfoApprovalQuery): AxiosPromise<XgStudentInfoApprovalVO[]> => {
return request({
url: '/xg/studentInfoApproval/pending',
method: 'get',
params: query
});
};
/**
* 查询已审核的学生信息列表
*/
export const queryApprovedList = (query: XgStudentInfoApprovalQuery): AxiosPromise<XgStudentInfoApprovalVO[]> => {
return request({
url: '/xg/studentInfoApproval/approved',
method: 'get',
params: query
});
};
/**
* 审核学生信息
*/
export const approveStudentInfo = (studentId: number, batchId: number, approved: boolean, comments?: string) => {
return request({
url: '/xg/studentInfoApproval/approve',
method: 'post',
params: {
studentId,
batchId,
approved,
comments
}
});
};
/**
* 批量审核学生信息
*/
export const batchApproveStudentInfo = (studentIds: number[], batchId: number, approved: boolean, comments?: string) => {
return request({
url: '/xg/studentInfoApproval/batchApprove',
method: 'post',
data: studentIds,
params: {
batchId,
approved,
comments
}
});
};
/**
* 撤销审核
*/
export const revokeApproval = (studentId: number, batchId: number, reason?: string) => {
return request({
url: '/xg/studentInfoApproval/revoke',
method: 'post',
params: {
studentId,
batchId,
reason
}
});
};
/**
* 查询学生信息审核历史
*/
export const queryApprovalHistory = (studentId: number, batchId: number): AxiosPromise<XgStudentInfoApprovalVO[]> => {
return request({
url: '/xg/studentInfoApproval/history',
method: 'get',
params: {
studentId,
batchId
}
});
};
/**
* 获取审核统计信息
*/
export const getApprovalStatistics = (batchId: number): AxiosPromise<XgStudentInfoApprovalVO> => {
return request({
url: '/xg/studentInfoApproval/statistics',
method: 'get',
params: {
batchId
}
});
};
/**
* 检查审核权限
*/
export const checkApprovalPermission = (batchId: number): AxiosPromise<boolean> => {
return request({
url: '/xg/studentInfoApproval/checkPermission',
method: 'get',
params: {
batchId
}
});
};
/**
* 导出审核结果
*/
export const exportApprovalResults = (query: XgStudentInfoApprovalQuery) => {
return request({
url: '/xg/studentInfoApproval/export',
method: 'post',
data: query
});
};
File diff suppressed because it is too large. Load diff
-306
View File
@@ -1,306 +0,0 @@
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryFormRef" :inline="true" v-show="showSearch" label-width="68px">
<el-form-item label="学年编码" prop="yearCode">
<el-input v-model="queryParams.yearCode" placeholder="请输入学年编码" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="学年名称" prop="yearName">
<el-input v-model="queryParams.yearName" placeholder="请输入学年名称" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="学年状态" prop="yearStatus">
<el-select v-model="queryParams.yearStatus" placeholder="请选择学年状态" clearable>
<el-option label="启用" value="1" />
<el-option label="停用" value="0" />
</el-select>
</el-form-item>
<el-form-item label="是否当前" prop="isCurrent">
<el-select v-model="queryParams.isCurrent" placeholder="请选择是否当前学年" clearable>
<el-option label="是" :value="true" />
<el-option label="否" :value="false" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['xg:academicYear:add']">新增</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['xg:academicYear:edit']">修改</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['xg:academicYear:remove']"
>删除</el-button
>
</el-col>
<el-col :span="1.5">
<el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['xg:academicYear:export']">导出</el-button>
</el-col>
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
<el-table v-loading="loading" :data="academicYearList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="学年编码" align="center" prop="yearCode" />
<el-table-column label="学年名称" align="center" prop="yearName" />
<el-table-column label="开始日期" align="center" prop="startDate" width="180">
<template #default="scope">
<span>{{ parseTime(scope.row.startDate, '{y}-{m}-{d}') }}</span>
</template>
</el-table-column>
<el-table-column label="结束日期" align="center" prop="endDate" width="180">
<template #default="scope">
<span>{{ parseTime(scope.row.endDate, '{y}-{m}-{d}') }}</span>
</template>
</el-table-column>
<el-table-column label="是否当前" align="center" prop="isCurrent">
<template #default="scope">
<dict-tag :options="sys_yes_no" :value="scope.row.isCurrent" />
</template>
</el-table-column>
<el-table-column label="学年状态" align="center" prop="yearStatus">
<template #default="scope">
<dict-tag :options="sys_normal_disable" :value="scope.row.yearStatus" />
</template>
</el-table-column>
<el-table-column label="排序" align="center" prop="sortOrder" />
<el-table-column label="创建时间" align="center" prop="createTime" width="180">
<template #default="scope">
<span>{{ parseTime(scope.row.createTime, '{y}-{m}-{d}') }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template #default="scope">
<el-tooltip content="修改" placement="top">
<el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['xg:academicYear:edit']"></el-button>
</el-tooltip>
<el-tooltip content="删除" placement="top">
<el-button link type="danger" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['xg:academicYear:remove']"></el-button>
</el-tooltip>
<el-tooltip content="设为当前" placement="top" v-if="!scope.row.isCurrent">
<el-button link type="success" icon="Check" @click="handleSetCurrent(scope.row)" v-hasPermi="['xg:academicYear:edit']"></el-button>
</el-tooltip>
</template>
</el-table-column>
</el-table>
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
<!-- 添加或修改学年管理对话框 -->
<el-dialog :title="dialog.title" v-model="dialog.visible" width="500px" append-to-body>
<el-form ref="academicYearFormRef" :model="form" :rules="rules" label-width="80px">
<el-form-item label="学年编码" prop="yearCode">
<el-input v-model="form.yearCode" placeholder="请输入学年编码" />
</el-form-item>
<el-form-item label="学年名称" prop="yearName">
<el-input v-model="form.yearName" placeholder="请输入学年名称" />
</el-form-item>
<el-form-item label="开始日期" prop="startDate">
<el-date-picker clearable v-model="form.startDate" type="date" value-format="YYYY-MM-DD" placeholder="请选择开始日期" />
</el-form-item>
<el-form-item label="结束日期" prop="endDate">
<el-date-picker clearable v-model="form.endDate" type="date" value-format="YYYY-MM-DD" placeholder="请选择结束日期" />
</el-form-item>
<el-form-item label="是否当前" prop="isCurrent">
<el-radio-group v-model="form.isCurrent">
<el-radio :value="true">是</el-radio>
<el-radio :value="false">否</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="学年状态" prop="yearStatus">
<el-radio-group v-model="form.yearStatus">
<el-radio value="1">启用</el-radio>
<el-radio value="0">停用</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="排序序号" prop="sortOrder">
<el-input-number v-model="form.sortOrder" :min="0" />
</el-form-item>
<el-form-item label="学年描述" prop="description">
<el-input v-model="form.description" type="textarea" placeholder="请输入学年描述" />
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input v-model="form.remark" type="textarea" placeholder="请输入备注" />
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="cancel">取 消</el-button>
<el-button type="primary" @click="submitForm">确 定</el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup name="AcademicYear" lang="ts">
import {
listAcademicYear,
getAcademicYear,
delAcademicYear,
addAcademicYear,
updateAcademicYear,
setCurrentAcademicYear,
exportAcademicYear
} from '@/api/xg-xx/academicYear';
import { AcademicYearVO, AcademicYearQuery, AcademicYearForm } from '@/api/xg-xx/academicTypes';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const { sys_yes_no, sys_normal_disable } = proxy?.useDict('sys_yes_no', 'sys_normal_disable');
const academicYearList = ref<AcademicYearVO[]>([]);
const loading = ref(true);
const showSearch = ref(true);
const ids = ref<Array<string | number>>([]);
const single = ref(true);
const multiple = ref(true);
const total = ref(0);
const queryFormRef = ref<ElFormInstance>();
const academicYearFormRef = ref<ElFormInstance>();
const dialog = reactive<DialogOption>({
visible: false,
title: ''
});
const initFormData: AcademicYearForm = {
id: undefined,
yearCode: '',
yearName: '',
startDate: '',
endDate: '',
isCurrent: false,
yearStatus: '1',
sortOrder: 0,
description: '',
remark: ''
};
const data = reactive<PageData<AcademicYearForm, AcademicYearQuery>>({
form: { ...initFormData },
queryParams: {
pageNum: 1,
pageSize: 10,
yearCode: '',
yearName: '',
yearStatus: '',
isCurrent: undefined,
params: {}
},
rules: {
yearCode: [{ required: true, message: '学年编码不能为空', trigger: 'blur' }],
yearName: [{ required: true, message: '学年名称不能为空', trigger: 'blur' }],
startDate: [{ required: true, message: '开始日期不能为空', trigger: 'blur' }],
endDate: [{ required: true, message: '结束日期不能为空', trigger: 'blur' }],
yearStatus: [{ required: true, message: '学年状态不能为空', trigger: 'change' }],
sortOrder: [{ required: true, message: '排序序号不能为空', trigger: 'blur' }]
}
});
const { queryParams, form, rules } = toRefs(data);
/** 查询学年管理列表 */
const getList = async () => {
loading.value = true;
const res = await listAcademicYear(queryParams.value);
academicYearList.value = res.rows;
total.value = res.total;
loading.value = false;
};
/** 取消按钮 */
const cancel = () => {
reset();
dialog.visible = false;
};
/** 表单重置 */
const reset = () => {
form.value = { ...initFormData };
academicYearFormRef.value?.resetFields();
};
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.value.pageNum = 1;
getList();
};
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value?.resetFields();
handleQuery();
};
/** 多选框选中数据 */
const handleSelectionChange = (selection: AcademicYearVO[]) => {
ids.value = selection.map((item) => item.id);
single.value = selection.length !== 1;
multiple.value = !selection.length;
};
/** 新增按钮操作 */
const handleAdd = () => {
reset();
dialog.visible = true;
dialog.title = '添加学年管理';
};
/** 修改按钮操作 */
const handleUpdate = async (row?: AcademicYearVO) => {
reset();
const _id = row?.id || ids.value[0];
const res = await getAcademicYear(_id);
Object.assign(form.value, res.data);
dialog.visible = true;
dialog.title = '修改学年管理';
};
/** 提交按钮 */
const submitForm = () => {
academicYearFormRef.value?.validate(async (valid: boolean) => {
if (valid) {
if (form.value.id) {
await updateAcademicYear(form.value);
proxy?.$modal.msgSuccess('修改成功');
} else {
await addAcademicYear(form.value);
proxy?.$modal.msgSuccess('新增成功');
}
dialog.visible = false;
await getList();
}
});
};
/** 删除按钮操作 */
const handleDelete = async (row?: AcademicYearVO) => {
const _ids = row?.id || ids.value;
await proxy?.$modal.confirm('是否确认删除学年管理编号为"' + _ids + '"的数据项?');
await delAcademicYear(_ids);
proxy?.$modal.msgSuccess('删除成功');
await getList();
};
/** 设为当前学年 */
const handleSetCurrent = async (row: AcademicYearVO) => {
await proxy?.$modal.confirm('是否确认设置"' + row.yearName + '"为当前学年?');
await setCurrentAcademicYear(row.id);
proxy?.$modal.msgSuccess('设置成功');
await getList();
};
/** 导出按钮操作 */
const handleExport = () => {
proxy?.$download.excel(exportAcademicYear, queryParams.value, `学年管理_${new Date().getTime()}.xlsx`);
};
onMounted(() => {
getList();
});
</script>
@@ -1,697 +0,0 @@
<template>
<div class="p-2 page-container">
<!-- 搜索区域 -->
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
<div v-show="showSearch" class="mb-[10px]">
<el-card shadow="hover">
<el-form ref="queryFormRef" :model="queryParams" label-width="100px">
<el-row :gutter="20">
<el-col :span="6">
<el-form-item label="批次选择" prop="batchId">
<el-select v-model="queryParams.batchId" placeholder="请选择批次" clearable style="width: 100%">
<el-option v-for="batch in batchOptions" :key="batch.id" :label="batch.batchName" :value="batch.id" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="期间名称" prop="periodName">
<el-input v-model="queryParams.periodName" placeholder="请输入期间名称" clearable @keyup.enter="handleQuery" />
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="期间类型" prop="periodType">
<el-select v-model="queryParams.periodType" placeholder="请选择期间类型" clearable style="width: 100%">
<el-option label="学生填报" value="STUDENT_SUBMIT" />
<el-option label="辅导员审核" value="COUNSELOR_REVIEW" />
<el-option label="学院审核" value="COLLEGE_REVIEW" />
<el-option label="学校审核" value="SCHOOL_REVIEW" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="目标角色" prop="targetRole">
<el-select v-model="queryParams.targetRole" placeholder="请选择目标角色" clearable style="width: 100%">
<el-option label="学生" value="STUDENT" />
<el-option label="辅导员" value="COUNSELOR" />
<el-option label="班主任" value="CLASS_TEACHER" />
<el-option label="学院管理员" value="COLLEGE_ADMIN" />
<el-option label="学校管理员" value="SCHOOL_ADMIN" />
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="6">
<el-form-item label="状态" prop="status">
<el-select v-model="queryParams.status" placeholder="请选择状态" clearable style="width: 100%">
<el-option label="未开始" value="PENDING" />
<el-option label="进行中" value="ACTIVE" />
<el-option label="已结束" value="CLOSED" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="启用状态" prop="enabled">
<el-select v-model="queryParams.enabled" placeholder="请选择启用状态" clearable style="width: 100%">
<el-option label="启用" :value="true" />
<el-option label="禁用" :value="false" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-col>
</el-row>
</el-form>
</el-card>
</div>
</transition>
<!-- 主要内容区域 -->
<el-card shadow="hover">
<!-- 操作按钮区域 -->
<div class="toolbar-wrapper">
<div class="toolbar-left">
<el-button type="primary" icon="Plus" @click="handleAdd" v-hasPermi="['xg:businessPeriod:add']">
新增期间
</el-button>
<el-button type="success" icon="Edit" :disabled="single" @click="handleUpdate" v-hasPermi="['xg:businessPeriod:edit']">
修改
</el-button>
<el-button type="danger" icon="Delete" :disabled="multiple" @click="handleDelete" v-hasPermi="['xg:businessPeriod:remove']">
删除
</el-button>
</div>
<div class="toolbar-right">
<el-button type="warning" icon="Download" @click="handleExport" v-hasPermi="['xg:businessPeriod:export']">
导出
</el-button>
<el-button type="info" icon="Refresh" @click="handleRefresh">
刷新
</el-button>
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
</div>
</div>
<!-- 表格区域 -->
<div class="table-wrapper">
<el-table
v-loading="loading"
:data="periodList"
@selection-change="handleSelectionChange"
@sort-change="handleSortChange"
row-key="id"
border
stripe
size="small"
>
<el-table-column type="selection" width="50" align="center" />
<el-table-column label="序号" type="index" width="60" align="center" />
<el-table-column label="期间名称" prop="periodName" width="150" align="center" show-overflow-tooltip />
<el-table-column label="批次名称" prop="batchName" width="150" align="center" show-overflow-tooltip />
<el-table-column label="期间类型" prop="periodType" width="120" align="center">
<template #default="scope">
<el-tag :type="getPeriodTypeTagType(scope.row.periodType)" size="small">
{{ getPeriodTypeText(scope.row.periodType) }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="目标角色" prop="targetRole" width="120" align="center">
<template #default="scope">
<el-tag :type="getTargetRoleTagType(scope.row.targetRole)" size="small">
{{ getTargetRoleText(scope.row.targetRole) }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="开始时间" prop="startTime" width="160" align="center" sortable="custom">
<template #default="scope">
<span>{{ proxy.parseTime(scope.row.startTime, '{y}-{m}-{d} {h}:{i}') }}</span>
</template>
</el-table-column>
<el-table-column label="结束时间" prop="endTime" width="160" align="center" sortable="custom">
<template #default="scope">
<span>{{ proxy.parseTime(scope.row.endTime, '{y}-{m}-{d} {h}:{i}') }}</span>
</template>
</el-table-column>
<el-table-column label="状态" prop="status" width="100" align="center">
<template #default="scope">
<el-tag :type="getStatusTagType(scope.row.status)" size="small">
{{ getStatusText(scope.row.status) }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="启用状态" prop="enabled" width="100" align="center">
<template #default="scope">
<el-switch
v-model="scope.row.enabled"
@change="handleToggleEnabled(scope.row)"
:disabled="!hasPermission(['xg:businessPeriod:edit'])"
/>
</template>
</el-table-column>
<el-table-column label="排序" prop="sortOrder" width="80" align="center" />
<el-table-column label="描述" prop="description" width="200" align="center" show-overflow-tooltip />
<el-table-column label="操作" align="center" width="200" fixed="right" class-name="small-padding fixed-width">
<template #default="scope">
<div class="action-buttons">
<el-tooltip content="查看" placement="top">
<el-button link type="info" icon="View" size="small" @click="handleView(scope.row)" />
</el-tooltip>
<el-tooltip content="修改" placement="top">
<el-button link type="primary" icon="Edit" size="small" @click="handleUpdate(scope.row)" v-hasPermi="['xg:businessPeriod:edit']" />
</el-tooltip>
<el-tooltip content="删除" placement="top">
<el-button link type="danger" icon="Delete" size="small" @click="handleDelete(scope.row)" v-hasPermi="['xg:businessPeriod:remove']" />
</el-tooltip>
<template v-if="scope.row.status === 'PENDING'">
<el-tooltip content="开启" placement="top">
<el-button link type="success" icon="VideoPlay" size="small" @click="handleOpen(scope.row)" v-hasPermi="['xg:businessPeriod:manage']" />
</el-tooltip>
</template>
<template v-else-if="scope.row.status === 'ACTIVE'">
<el-tooltip content="关闭" placement="top">
<el-button link type="warning" icon="VideoPause" size="small" @click="handleClose(scope.row)" v-hasPermi="['xg:businessPeriod:manage']" />
</el-tooltip>
</template>
</div>
</template>
</el-table-column>
</el-table>
<div class="pagination-container">
<pagination v-show="total > 0" :total="total" :page="queryParams.pageNum" :limit="queryParams.pageSize" @pagination="getList" />
</div>
</div>
</el-card>
<!-- 新增/修改对话框 -->
<el-dialog :title="dialog.title" v-model="dialog.visible" width="800px" append-to-body>
<el-form ref="periodFormRef" :model="form" :rules="rules" label-width="120px">
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="批次选择" prop="batchId">
<el-select v-model="form.batchId" placeholder="请选择批次" style="width: 100%">
<el-option v-for="batch in batchOptions" :key="batch.id" :label="batch.batchName" :value="batch.id" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="期间名称" prop="periodName">
<el-input v-model="form.periodName" placeholder="请输入期间名称" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="期间类型" prop="periodType">
<el-select v-model="form.periodType" placeholder="请选择期间类型" style="width: 100%">
<el-option label="学生填报" value="STUDENT_SUBMIT" />
<el-option label="辅导员审核" value="COUNSELOR_REVIEW" />
<el-option label="学院审核" value="COLLEGE_REVIEW" />
<el-option label="学校审核" value="SCHOOL_REVIEW" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="目标角色" prop="targetRole">
<el-select v-model="form.targetRole" placeholder="请选择目标角色" style="width: 100%">
<el-option label="学生" value="STUDENT" />
<el-option label="辅导员" value="COUNSELOR" />
<el-option label="班主任" value="CLASS_TEACHER" />
<el-option label="学院管理员" value="COLLEGE_ADMIN" />
<el-option label="学校管理员" value="SCHOOL_ADMIN" />
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="开始时间" prop="startTime">
<el-date-picker
v-model="form.startTime"
type="datetime"
placeholder="请选择开始时间"
style="width: 100%"
format="YYYY-MM-DD HH:mm:ss"
value-format="YYYY-MM-DD HH:mm:ss"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="结束时间" prop="endTime">
<el-date-picker
v-model="form.endTime"
type="datetime"
placeholder="请选择结束时间"
style="width: 100%"
format="YYYY-MM-DD HH:mm:ss"
value-format="YYYY-MM-DD HH:mm:ss"
/>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="排序" prop="sortOrder">
<el-input-number v-model="form.sortOrder" :min="0" :max="9999" style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="启用状态" prop="enabled">
<el-switch v-model="form.enabled" />
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="24">
<el-form-item label="描述" prop="description">
<el-input
v-model="form.description"
type="textarea"
:rows="3"
placeholder="请输入期间描述"
maxlength="500"
show-word-limit
/>
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="cancel">取消</el-button>
<el-button type="primary" @click="submitForm">确定</el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted, getCurrentInstance } from 'vue';
import { ElMessage, ElMessageBox } from 'element-plus';
import {
listBusinessPeriod,
getBusinessPeriod,
addBusinessPeriod,
updateBusinessPeriod,
delBusinessPeriod,
openPeriod,
closePeriod,
checkPeriodTimeConflict,
exportBusinessPeriod,
type XgBusinessPeriodQuery,
type XgBusinessPeriodForm,
type XgBusinessPeriodVO
} from '@/api/xg-xx/businessPeriod';
import { listAllCollectionBatch } from '@/api/xg-xx/collectionBatch';
const { proxy } = getCurrentInstance() as any;
// 响应式数据
const loading = ref(false);
const showSearch = ref(true);
const ids = ref<number[]>([]);
const single = ref(true);
const multiple = ref(true);
const total = ref(0);
const periodList = ref<XgBusinessPeriodVO[]>([]);
const batchOptions = ref<any[]>([]);
// 查询参数
const queryParams = reactive<XgBusinessPeriodQuery>({
pageNum: 1,
pageSize: 10,
batchId: undefined,
periodName: undefined,
periodType: undefined,
targetRole: undefined,
status: undefined,
enabled: undefined
});
// 对话框
const dialog = reactive({
visible: false,
title: '',
type: 'add' as 'add' | 'edit'
});
// 表单数据
const form = reactive<XgBusinessPeriodForm>({
id: undefined,
batchId: undefined,
periodName: '',
periodType: '',
targetRole: '',
startTime: '',
endTime: '',
status: 'PENDING',
description: '',
sortOrder: 0,
enabled: true
});
// 表单校验规则
const rules = {
batchId: [{ required: true, message: '请选择批次', trigger: 'change' }],
periodName: [{ required: true, message: '请输入期间名称', trigger: 'blur' }],
periodType: [{ required: true, message: '请选择期间类型', trigger: 'change' }],
targetRole: [{ required: true, message: '请选择目标角色', trigger: 'change' }],
startTime: [{ required: true, message: '请选择开始时间', trigger: 'change' }],
endTime: [{ required: true, message: '请选择结束时间', trigger: 'change' }]
};
// 获取列表数据
const getList = async () => {
loading.value = true;
try {
const response = await listBusinessPeriod(queryParams);
periodList.value = response.rows;
total.value = response.total;
} catch (error) {
console.error('获取列表失败:', error);
} finally {
loading.value = false;
}
};
// 获取批次选项
const getBatchOptions = async () => {
try {
const response = await listAllCollectionBatch({});
batchOptions.value = response.data;
} catch (error) {
console.error('获取批次选项失败:', error);
}
};
// 搜索
const handleQuery = () => {
queryParams.pageNum = 1;
getList();
};
// 重置搜索
const resetQuery = () => {
proxy.resetForm('queryFormRef');
handleQuery();
};
// 多选框选中数据
const handleSelectionChange = (selection: XgBusinessPeriodVO[]) => {
ids.value = selection.map(item => item.id!);
single.value = selection.length !== 1;
multiple.value = !selection.length;
};
// 排序变化
const handleSortChange = (column: any) => {
// 处理排序逻辑
getList();
};
// 新增
const handleAdd = () => {
reset();
dialog.visible = true;
dialog.title = '新增业务期间';
dialog.type = 'add';
};
// 修改
const handleUpdate = async (row?: XgBusinessPeriodVO) => {
reset();
const id = row?.id || ids.value[0];
try {
const response = await getBusinessPeriod(id);
Object.assign(form, response.data);
dialog.visible = true;
dialog.title = '修改业务期间';
dialog.type = 'edit';
} catch (error) {
console.error('获取详情失败:', error);
}
};
// 查看
const handleView = (row: XgBusinessPeriodVO) => {
// 实现查看逻辑
ElMessage.info('查看功能待实现');
};
// 删除
const handleDelete = async (row?: XgBusinessPeriodVO) => {
const periodIds = row?.id ? [row.id] : ids.value;
try {
await ElMessageBox.confirm('确定要删除选中的业务期间吗?', '系统提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
});
await delBusinessPeriod(periodIds);
ElMessage.success('删除成功');
getList();
} catch (error) {
console.error('删除失败:', error);
}
};
// 开启期间
const handleOpen = async (row: XgBusinessPeriodVO) => {
try {
await ElMessageBox.confirm(`确定要开启期间"${row.periodName}"吗?`, '系统提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
});
await openPeriod(row.id!);
ElMessage.success('开启成功');
getList();
} catch (error) {
console.error('开启失败:', error);
}
};
// 关闭期间
const handleClose = async (row: XgBusinessPeriodVO) => {
try {
await ElMessageBox.confirm(`确定要关闭期间"${row.periodName}"吗?`, '系统提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
});
await closePeriod(row.id!);
ElMessage.success('关闭成功');
getList();
} catch (error) {
console.error('关闭失败:', error);
}
};
// 切换启用状态
const handleToggleEnabled = async (row: XgBusinessPeriodVO) => {
try {
await updateBusinessPeriod({
id: row.id,
enabled: row.enabled
});
ElMessage.success('状态更新成功');
} catch (error) {
console.error('状态更新失败:', error);
// 恢复原状态
row.enabled = !row.enabled;
}
};
// 提交表单
const submitForm = async () => {
try {
// 时间冲突检查
const conflictResponse = await checkPeriodTimeConflict(form);
if (conflictResponse.data) {
ElMessage.error('时间范围与其他期间冲突,请重新选择');
return;
}
if (dialog.type === 'add') {
await addBusinessPeriod(form);
ElMessage.success('新增成功');
} else {
await updateBusinessPeriod(form);
ElMessage.success('修改成功');
}
dialog.visible = false;
getList();
} catch (error) {
console.error('提交失败:', error);
}
};
// 取消
const cancel = () => {
dialog.visible = false;
reset();
};
// 重置表单
const reset = () => {
Object.assign(form, {
id: undefined,
batchId: undefined,
periodName: '',
periodType: '',
targetRole: '',
startTime: '',
endTime: '',
status: 'PENDING',
description: '',
sortOrder: 0,
enabled: true
});
proxy.resetForm('periodFormRef');
};
// 导出
const handleExport = () => {
proxy?.download(
'xg/businessPeriod/export',
queryParams,
`业务期间_${proxy.parseTime(new Date(), '{y}{m}{d}{h}{i}{s}')}.xlsx`
);
};
// 刷新
const handleRefresh = () => {
getList();
};
// 权限检查
const hasPermission = (permissions: string[]) => {
return proxy.hasPermi(permissions);
};
// 获取期间类型标签类型
const getPeriodTypeTagType = (type: string) => {
const typeMap: Record<string, string> = {
'STUDENT_SUBMIT': 'primary',
'COUNSELOR_REVIEW': 'success',
'COLLEGE_REVIEW': 'warning',
'SCHOOL_REVIEW': 'danger'
};
return typeMap[type] || 'info';
};
// 获取期间类型文本
const getPeriodTypeText = (type: string) => {
const typeMap: Record<string, string> = {
'STUDENT_SUBMIT': '学生填报',
'COUNSELOR_REVIEW': '辅导员审核',
'COLLEGE_REVIEW': '学院审核',
'SCHOOL_REVIEW': '学校审核'
};
return typeMap[type] || '未知';
};
// 获取目标角色标签类型
const getTargetRoleTagType = (role: string) => {
const roleMap: Record<string, string> = {
'STUDENT': 'primary',
'COUNSELOR': 'success',
'CLASS_TEACHER': 'warning',
'COLLEGE_ADMIN': 'danger',
'SCHOOL_ADMIN': 'info'
};
return roleMap[role] || 'info';
};
// 获取目标角色文本
const getTargetRoleText = (role: string) => {
const roleMap: Record<string, string> = {
'STUDENT': '学生',
'COUNSELOR': '辅导员',
'CLASS_TEACHER': '班主任',
'COLLEGE_ADMIN': '学院管理员',
'SCHOOL_ADMIN': '学校管理员'
};
return roleMap[role] || '未知';
};
// 获取状态标签类型
const getStatusTagType = (status: string) => {
const statusMap: Record<string, string> = {
'PENDING': 'info',
'ACTIVE': 'success',
'CLOSED': 'warning'
};
return statusMap[status] || 'info';
};
// 获取状态文本
const getStatusText = (status: string) => {
const statusMap: Record<string, string> = {
'PENDING': '未开始',
'ACTIVE': '进行中',
'CLOSED': '已结束'
};
return statusMap[status] || '未知';
};
// 初始化
onMounted(() => {
getBatchOptions();
getList();
});
</script>
<style scoped>
.page-container {
background-color: #f5f5f5;
min-height: 100vh;
}
.toolbar-wrapper {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
}
.toolbar-left {
display: flex;
align-items: center;
gap: 8px;
}
.toolbar-right {
display: flex;
align-items: center;
gap: 8px;
}
.table-wrapper {
margin-top: 16px;
}
.action-buttons {
display: flex;
gap: 4px;
justify-content: center;
}
.pagination-container {
display: flex;
justify-content: center;
margin-top: 16px;
}
</style>
@@ -1,276 +0,0 @@
<template>
<el-dialog :title="dialogTitle" v-model="dialogVisible" width="600px" append-to-body @close="handleClose">
<el-form ref="formRef" :model="form" :rules="rules" label-width="120px">
<el-form-item label="学生信息">
<el-input :value="`${cadreData.studentNo} - ${cadreData.studentName}`" disabled />
</el-form-item>
<el-form-item label="当前职务">
<el-input :value="cadreData.positionName" disabled />
</el-form-item>
<!-- 调任操作专用字段 -->
<template v-if="operationType === 'transfer'">
<el-form-item label="新职务" prop="newPositionCode">
<el-select v-model="form.newPositionCode" placeholder="请选择新职务" style="width: 100%" @change="handlePositionChange">
<el-option v-for="item in positionOptions" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
<el-form-item label="生效日期" prop="effectiveDate">
<el-date-picker v-model="form.effectiveDate" type="date" placeholder="选择生效日期" style="width: 100%" />
</el-form-item>
</template>
<!-- 续任操作专用字段 -->
<template v-if="operationType === 'renew'">
<el-form-item label="新任期时长" prop="newTermDuration">
<el-input-number v-model="form.newTermDuration" :min="1" :max="48" placeholder="请输入新任期时长(月)" style="width: 100%" />
</el-form-item>
</template>
<!-- 重新任命操作专用字段 -->
<template v-if="operationType === 'reappoint'">
<el-form-item label="新职务" prop="newPositionCode">
<el-select v-model="form.newPositionCode" placeholder="请选择新职务" style="width: 100%" @change="handlePositionChange">
<el-option v-for="item in positionOptions" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
<el-form-item label="任职日期" prop="appointmentDate">
<el-date-picker v-model="form.appointmentDate" type="date" placeholder="选择任职日期" style="width: 100%" />
</el-form-item>
<el-form-item label="任期开始日期" prop="termStartDate">
<el-date-picker v-model="form.termStartDate" type="date" placeholder="选择任期开始日期" style="width: 100%" />
</el-form-item>
<el-form-item label="任期时长(月)" prop="termDuration">
<el-input-number v-model="form.termDuration" :min="1" :max="48" placeholder="请输入任期时长" style="width: 100%" />
</el-form-item>
</template>
<el-form-item label="操作日期" prop="operationDate">
<el-date-picker v-model="form.operationDate" type="date" placeholder="选择操作日期" style="width: 100%" />
</el-form-item>
<el-form-item :label="reasonLabel" prop="reason">
<el-input v-model="form.reason" type="textarea" :rows="3" :placeholder="reasonPlaceholder" />
</el-form-item>
<el-form-item label="操作备注" prop="operationRemark">
<el-input v-model="form.operationRemark" type="textarea" :rows="2" placeholder="请输入操作备注" />
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="handleClose">取 消</el-button>
<el-button type="primary" @click="handleConfirm">确 定</el-button>
</div>
</template>
</el-dialog>
</template>
<script setup lang="ts">
import { ref, reactive, computed, watch } from 'vue';
import type { FormInstance, FormRules } from 'element-plus';
import type { XgClassCadreVO } from '@/api/xg-xx/types';
interface Props {
visible: boolean;
operationType: string;
cadreData: XgClassCadreVO;
}
interface Emits {
(e: 'update:visible', value: boolean): void;
(e: 'confirm', data: any): void;
}
const props = defineProps<Props>();
const emit = defineEmits<Emits>();
const formRef = ref<FormInstance>();
const form = reactive({
operationDate: new Date().toISOString().split('T')[0],
reason: '',
operationRemark: '',
newPositionCode: '',
newPositionName: '',
newTermDuration: 12,
effectiveDate: '',
appointmentDate: '',
termStartDate: '',
termDuration: 12
});
const positionOptions = ref([
{ value: 'class_monitor', label: '班长' },
{ value: 'deputy_monitor', label: '副班长' },
{ value: 'study_committee', label: '学习委员' },
{ value: 'organization_committee', label: '组织委员' },
{ value: 'publicity_committee', label: '宣传委员' },
{ value: 'sports_committee', label: '体育委员' },
{ value: 'life_committee', label: '生活委员' },
{ value: 'literary_committee', label: '文艺委员' },
{ value: 'discipline_committee', label: '纪律委员' },
{ value: 'psychology_committee', label: '心理委员' }
]);
const dialogVisible = computed({
get: () => props.visible,
set: (value) => emit('update:visible', value)
});
const dialogTitle = computed(() => {
switch (props.operationType) {
case 'dismiss':
return '免职操作';
case 'transfer':
return '调任操作';
case 'renew':
return '续任操作';
case 'resign':
return '辞职操作';
case 'reappoint':
return '重新任命';
default:
return '操作';
}
});
const reasonLabel = computed(() => {
switch (props.operationType) {
case 'dismiss':
return '免职原因';
case 'transfer':
return '调任原因';
case 'renew':
return '续任原因';
case 'resign':
return '辞职原因';
case 'reappoint':
return '任命原因';
default:
return '操作原因';
}
});
const reasonPlaceholder = computed(() => {
switch (props.operationType) {
case 'dismiss':
return '请输入免职原因';
case 'transfer':
return '请输入调任原因';
case 'renew':
return '请输入续任原因';
case 'resign':
return '请输入辞职原因';
case 'reappoint':
return '请输入任命原因';
default:
return '请输入操作原因';
}
});
const rules = computed<FormRules>(() => {
const baseRules = {
operationDate: [{ required: true, message: '操作日期不能为空', trigger: 'change' }],
reason: [{ required: true, message: `${reasonLabel.value}不能为空`, trigger: 'blur' }]
};
if (props.operationType === 'transfer') {
baseRules.newPositionCode = [{ required: true, message: '新职务不能为空', trigger: 'change' }];
baseRules.effectiveDate = [{ required: true, message: '生效日期不能为空', trigger: 'change' }];
}
if (props.operationType === 'renew') {
baseRules.newTermDuration = [{ required: true, message: '新任期时长不能为空', trigger: 'blur' }];
}
if (props.operationType === 'reappoint') {
baseRules.newPositionCode = [{ required: true, message: '新职务不能为空', trigger: 'change' }];
baseRules.appointmentDate = [{ required: true, message: '任职日期不能为空', trigger: 'change' }];
baseRules.termStartDate = [{ required: true, message: '任期开始日期不能为空', trigger: 'change' }];
baseRules.termDuration = [{ required: true, message: '任期时长不能为空', trigger: 'blur' }];
}
return baseRules;
});
const handlePositionChange = (positionCode: string) => {
const position = positionOptions.value.find((item) => item.value === positionCode);
if (position) {
form.newPositionName = position.label;
}
};
const handleConfirm = () => {
formRef.value?.validate((valid) => {
if (valid) {
const operationData = {
type: props.operationType,
cadreId: props.cadreData.id,
operationDate: form.operationDate,
reason: form.reason,
operationRemark: form.operationRemark
};
if (props.operationType === 'transfer') {
operationData.newPositionCode = form.newPositionCode;
operationData.newPositionName = form.newPositionName;
operationData.effectiveDate = form.effectiveDate;
}
if (props.operationType === 'renew') {
operationData.newTermDuration = form.newTermDuration;
}
if (props.operationType === 'reappoint') {
operationData.newPositionCode = form.newPositionCode;
operationData.newPositionName = form.newPositionName;
operationData.appointmentDate = form.appointmentDate;
operationData.termStartDate = form.termStartDate;
operationData.termDuration = form.termDuration;
}
emit('confirm', operationData);
handleClose();
}
});
};
const handleClose = () => {
dialogVisible.value = false;
resetForm();
};
const resetForm = () => {
Object.assign(form, {
operationDate: new Date().toISOString().split('T')[0],
reason: '',
operationRemark: '',
newPositionCode: '',
newPositionName: '',
newTermDuration: 12,
effectiveDate: '',
appointmentDate: '',
termStartDate: '',
termDuration: 12
});
formRef.value?.resetFields();
};
// 监听对话框打开,重置表单
watch(
() => props.visible,
(newVal) => {
if (newVal) {
resetForm();
}
}
);
</script>
<style scoped>
.dialog-footer {
text-align: right;
}
</style>
-771
View File
@@ -1,771 +0,0 @@
<template>
<div class="app-container">
<!-- 搜索区域 -->
<el-form v-show="showSearch" ref="queryFormRef" :model="queryParams" :inline="true" label-width="68px">
<el-form-item label="班级" prop="classId">
<el-select v-model="queryParams.classId" placeholder="请选择班级" clearable style="width: 200px">
<el-option v-for="item in classOptions" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
<el-form-item label="学号" prop="studentNo">
<el-input v-model="queryParams.studentNo" placeholder="请输入学号" clearable style="width: 200px" @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="姓名" prop="studentName">
<el-input v-model="queryParams.studentName" placeholder="请输入学生姓名" clearable style="width: 200px" @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="职务" prop="positionCode">
<el-select v-model="queryParams.positionCode" placeholder="请选择职务" clearable style="width: 200px">
<el-option v-for="item in positionOptions" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
<el-form-item label="任职状态" prop="cadreStatus">
<el-select v-model="queryParams.cadreStatus" placeholder="请选择任职状态" clearable style="width: 200px">
<el-option label="在任" value="1" />
<el-option label="到期" value="2" />
<el-option label="辞职" value="3" />
<el-option label="免职" value="4" />
<el-option label="调任" value="5" />
</el-select>
</el-form-item>
<el-form-item label="是否现任" prop="isCurrent">
<el-select v-model="queryParams.isCurrent" placeholder="请选择是否现任" clearable style="width: 200px">
<el-option label="是" :value="true" />
<el-option label="否" :value="false" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<!-- 工具栏 -->
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['xg:classCadre:add']">新增</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['xg:classCadre:edit']">修改</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['xg:classCadre:remove']"
>删除</el-button
>
</el-col>
<el-col :span="1.5">
<el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['xg:classCadre:export']">导出</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="info" plain icon="Upload" @click="handleImport" v-hasPermi="['xg:classCadre:import']">导入</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="primary" plain icon="Clock" @click="handleExpiring">到期提醒</el-button>
</el-col>
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
<!-- 表格数据 -->
<el-table v-loading="loading" :data="classCadreList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="序号" type="index" width="50" align="center" />
<el-table-column label="学号" align="center" prop="studentNo" min-width="120" />
<el-table-column label="姓名" align="center" prop="studentName" min-width="100" />
<el-table-column label="班级" align="center" prop="className" min-width="150" />
<el-table-column label="职务" align="center" prop="positionName" min-width="120">
<template #default="scope">
<el-tag :type="getPositionTagType(scope.row.positionLevel)">
{{ scope.row.positionName }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="任职状态" align="center" prop="cadreStatus" min-width="100">
<template #default="scope">
<el-tag :type="getCadreStatusTagType(scope.row.cadreStatus)">
{{ getCadreStatusText(scope.row.cadreStatus) }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="是否现任" align="center" prop="isCurrent" min-width="100">
<template #default="scope">
<el-tag :type="scope.row.isCurrent ? 'success' : 'info'">
{{ scope.row.isCurrent ? '是' : '否' }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="任职日期" align="center" prop="appointmentDate" min-width="120" />
<el-table-column label="任期结束" align="center" prop="termEndDate" min-width="120">
<template #default="scope">
<span v-if="scope.row.termEndDate">
{{ scope.row.termEndDate }}
<el-tag v-if="scope.row.daysRemaining <= 30 && scope.row.daysRemaining > 0" type="warning" size="small">
{{ scope.row.daysRemaining }}天到期
</el-tag>
<el-tag v-else-if="scope.row.daysRemaining <= 0" type="danger" size="small"> 已到期 </el-tag>
</span>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column label="工作表现" align="center" prop="workPerformance" min-width="100">
<template #default="scope">
<el-tag v-if="scope.row.workPerformance" :type="getPerformanceTagType(scope.row.workPerformance)">
{{ getPerformanceText(scope.row.workPerformance) }}
</el-tag>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" min-width="280">
<template #default="scope">
<el-tooltip content="查看详情" placement="top">
<el-button link type="primary" icon="View" @click="handleView(scope.row)" v-hasPermi="['xg:classCadre:query']" />
</el-tooltip>
<el-tooltip content="编辑" placement="top">
<el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['xg:classCadre:edit']" />
</el-tooltip>
<el-tooltip content="删除" placement="top">
<el-button link type="danger" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['xg:classCadre:remove']" />
</el-tooltip>
<!-- 任免操作按钮 -->
<el-dropdown v-if="scope.row.isCurrent" @command="(command) => handleCadreOperation(command, scope.row)">
<el-button link type="warning" icon="Operation">
任免 <el-icon class="el-icon--right"><arrow-down /></el-icon>
</el-button>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item command="dismiss" v-hasPermi="['xg:classCadre:dismiss']">免职</el-dropdown-item>
<el-dropdown-item command="transfer" v-hasPermi="['xg:classCadre:transfer']">调任</el-dropdown-item>
<el-dropdown-item command="renew" v-hasPermi="['xg:classCadre:renew']">续任</el-dropdown-item>
<el-dropdown-item command="resign" v-hasPermi="['xg:classCadre:resign']">辞职</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
<!-- 重新任命按钮 -->
<el-tooltip v-if="!scope.row.isCurrent" content="重新任命" placement="top">
<el-button link type="success" icon="UserFilled" @click="handleReappoint(scope.row)" v-hasPermi="['xg:classCadre:appoint']" />
</el-tooltip>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
<!-- 添加或修改班级干部信息对话框 -->
<el-dialog :title="dialog.title" v-model="dialog.visible" width="800px" append-to-body>
<el-form ref="classCadreFormRef" :model="form" :rules="rules" label-width="120px">
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="班级" prop="classId">
<el-select v-model="form.classId" placeholder="请选择班级" style="width: 100%">
<el-option v-for="item in classOptions" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="学生" prop="studentId">
<el-select
v-model="form.studentId"
placeholder="请选择学生"
filterable
remote
:remote-method="remoteStudentMethod"
:loading="studentLoading"
style="width: 100%"
@change="handleStudentChange"
>
<el-option v-for="item in studentOptions" :key="item.value" :label="`${item.studentNo} - ${item.label}`" :value="item.value" />
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="职务" prop="positionCode">
<el-select v-model="form.positionCode" placeholder="请选择职务" style="width: 100%" @change="handlePositionChange">
<el-option v-for="item in positionOptions" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="任职日期" prop="appointmentDate">
<el-date-picker v-model="form.appointmentDate" type="date" placeholder="选择任职日期" style="width: 100%" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="任期开始日期" prop="termStartDate">
<el-date-picker v-model="form.termStartDate" type="date" placeholder="选择任期开始日期" style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="任期时长(月)" prop="termDuration">
<el-input-number v-model="form.termDuration" :min="1" :max="48" placeholder="请输入任期时长" style="width: 100%" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="是否兼职" prop="isConcurrent">
<el-radio-group v-model="form.isConcurrent">
<el-radio :value="false">否</el-radio>
<el-radio :value="true">是</el-radio>
</el-radio-group>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="任命原因" prop="appointmentReason">
<el-input v-model="form.appointmentReason" placeholder="请输入任命原因" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="联系电话" prop="contactPhone">
<el-input v-model="form.contactPhone" placeholder="请输入联系电话" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="联系邮箱" prop="contactEmail">
<el-input v-model="form.contactEmail" placeholder="请输入联系邮箱" />
</el-form-item>
</el-col>
</el-row>
<el-form-item label="工作计划" prop="workPlan">
<el-input v-model="form.workPlan" type="textarea" :rows="3" placeholder="请输入工作计划" />
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input v-model="form.remark" type="textarea" :rows="2" placeholder="请输入备注" />
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="cancel">取 消</el-button>
<el-button type="primary" @click="submitForm">确 定</el-button>
</div>
</template>
</el-dialog>
<!-- 查看详情对话框 -->
<el-dialog title="班干部详情" v-model="viewDialog.visible" width="900px" append-to-body>
<el-descriptions :column="2" border>
<el-descriptions-item label="学号">{{ viewData.studentNo }}</el-descriptions-item>
<el-descriptions-item label="姓名">{{ viewData.studentName }}</el-descriptions-item>
<el-descriptions-item label="班级">{{ viewData.className }}</el-descriptions-item>
<el-descriptions-item label="职务">{{ viewData.positionName }}</el-descriptions-item>
<el-descriptions-item label="任职日期">{{ viewData.appointmentDate }}</el-descriptions-item>
<el-descriptions-item label="任期开始">{{ viewData.termStartDate }}</el-descriptions-item>
<el-descriptions-item label="任期结束">{{ viewData.termEndDate || '-' }}</el-descriptions-item>
<el-descriptions-item label="任期时长">{{ viewData.termDuration }}个月</el-descriptions-item>
<el-descriptions-item label="是否现任">
<el-tag :type="viewData.isCurrent ? 'success' : 'info'">
{{ viewData.isCurrent ? '是' : '否' }}
</el-tag>
</el-descriptions-item>
<el-descriptions-item label="任职状态">
<el-tag :type="getCadreStatusTagType(viewData.cadreStatus)">
{{ getCadreStatusText(viewData.cadreStatus) }}
</el-tag>
</el-descriptions-item>
<el-descriptions-item label="是否兼职">
{{ viewData.isConcurrent ? '是' : '否' }}
</el-descriptions-item>
<el-descriptions-item label="工作表现">
<el-tag v-if="viewData.workPerformance" :type="getPerformanceTagType(viewData.workPerformance)">
{{ getPerformanceText(viewData.workPerformance) }}
</el-tag>
<span v-else>-</span>
</el-descriptions-item>
<el-descriptions-item label="任命原因" :span="2">{{ viewData.appointmentReason || '-' }}</el-descriptions-item>
<el-descriptions-item label="联系电话">{{ viewData.contactPhone || '-' }}</el-descriptions-item>
<el-descriptions-item label="联系邮箱">{{ viewData.contactEmail || '-' }}</el-descriptions-item>
<el-descriptions-item label="工作计划" :span="2">{{ viewData.workPlan || '-' }}</el-descriptions-item>
<el-descriptions-item label="主要成就" :span="2">{{ viewData.achievements || '-' }}</el-descriptions-item>
<el-descriptions-item label="备注" :span="2">{{ viewData.remark || '-' }}</el-descriptions-item>
</el-descriptions>
</el-dialog>
<!-- 任免操作对话框 -->
<CadreOperationDialog
v-model:visible="operationDialog.visible"
:operation-type="operationDialog.type"
:cadre-data="operationDialog.cadreData"
@confirm="handleOperationConfirm"
/>
<!-- 导入对话框 -->
<ImportDialog
v-model:visible="importDialog.visible"
:upload-url="'/xg/classCadre/import'"
:template-url="'/xg/classCadre/import-template'"
@success="handleImportSuccess"
/>
</div>
</template>
<script setup name="ClassCadre" lang="ts">
import { ref, reactive, onMounted, getCurrentInstance } from 'vue';
import { ElMessage, ElMessageBox } from 'element-plus';
import type { FormInstance, FormRules } from 'element-plus';
import {
listClassCadre,
getClassCadre,
addClassCadre,
updateClassCadre,
delClassCadre,
exportClassCadre,
dismissCadre,
transferCadre,
renewCadre,
resignCadre,
getExpiringCadres
} from '@/api/xg-xx/classCadre';
import { listClassInfo } from '@/api/xg-xx/classInfo';
import { listStudentInfo } from '@/api/xg-xx/studentInfo';
import type { XgClassCadreVO, XgClassCadreForm, XgClassCadreQuery } from '@/api/xg-xx/types';
import CadreOperationDialog from './components/CadreOperationDialog.vue';
import ImportDialog from '@/components/ImportDialog/index.vue';
const { proxy } = getCurrentInstance()!;
// 数据
const classCadreList = ref<XgClassCadreVO[]>([]);
const loading = ref(true);
const showSearch = ref(true);
const ids = ref<Array<string | number>>([]);
const single = ref(true);
const multiple = ref(true);
const total = ref(0);
// 查询参数
const queryParams = reactive<XgClassCadreQuery>({
pageNum: 1,
pageSize: 10,
classId: undefined,
studentNo: undefined,
studentName: undefined,
positionCode: undefined,
cadreStatus: undefined,
isCurrent: undefined
});
// 表单相关
const classCadreFormRef = ref<FormInstance>();
const dialog = reactive({
visible: false,
title: ''
});
const form = reactive<XgClassCadreForm>({
id: undefined,
classId: undefined,
studentId: undefined,
positionCode: undefined,
appointmentDate: undefined,
termStartDate: undefined,
termDuration: 12,
isConcurrent: false,
appointmentReason: undefined,
contactPhone: undefined,
contactEmail: undefined,
workPlan: undefined,
remark: undefined
});
const rules = reactive<FormRules<XgClassCadreForm>>({
classId: [{ required: true, message: '班级不能为空', trigger: 'change' }],
studentId: [{ required: true, message: '学生不能为空', trigger: 'change' }],
positionCode: [{ required: true, message: '职务不能为空', trigger: 'change' }],
appointmentDate: [{ required: true, message: '任职日期不能为空', trigger: 'blur' }],
termStartDate: [{ required: true, message: '任期开始日期不能为空', trigger: 'blur' }],
termDuration: [{ required: true, message: '任期时长不能为空', trigger: 'blur' }]
});
// 选项数据
const classOptions = ref<Array<{ value: string | number; label: string }>>([]);
const studentOptions = ref<Array<{ value: string | number; label: string; studentNo: string }>>([]);
const positionOptions = ref([
{ value: 'class_monitor', label: '班长' },
{ value: 'deputy_monitor', label: '副班长' },
{ value: 'study_committee', label: '学习委员' },
{ value: 'organization_committee', label: '组织委员' },
{ value: 'publicity_committee', label: '宣传委员' },
{ value: 'sports_committee', label: '体育委员' },
{ value: 'life_committee', label: '生活委员' },
{ value: 'literary_committee', label: '文艺委员' },
{ value: 'discipline_committee', label: '纪律委员' },
{ value: 'psychology_committee', label: '心理委员' }
]);
const studentLoading = ref(false);
// 查看详情
const viewDialog = reactive({
visible: false
});
const viewData = ref<XgClassCadreVO>({} as XgClassCadreVO);
// 任免操作
const operationDialog = reactive({
visible: false,
type: '',
cadreData: {} as XgClassCadreVO
});
// 导入
const importDialog = reactive({
visible: false
});
// 查询表单引用
const queryFormRef = ref<FormInstance>();
/** 查询班级干部信息列表 */
const getList = async () => {
loading.value = true;
try {
const res = await listClassCadre(queryParams);
classCadreList.value = res.rows;
total.value = res.total;
} catch (error) {
console.error('获取班级干部信息列表失败:', error);
} finally {
loading.value = false;
}
};
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.pageNum = 1;
getList();
};
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value?.resetFields();
handleQuery();
};
/** 多选框选中数据 */
const handleSelectionChange = (selection: XgClassCadreVO[]) => {
ids.value = selection.map((item) => item.id);
single.value = selection.length !== 1;
multiple.value = !selection.length;
};
/** 新增按钮操作 */
const handleAdd = () => {
reset();
dialog.visible = true;
dialog.title = '添加班级干部信息';
};
/** 修改按钮操作 */
const handleUpdate = async (row?: XgClassCadreVO) => {
reset();
const id = row?.id || ids.value[0];
try {
const res = await getClassCadre(id);
Object.assign(form, res.data);
dialog.visible = true;
dialog.title = '修改班级干部信息';
} catch (error) {
console.error('获取班级干部信息失败:', error);
}
};
/** 查看详情 */
const handleView = async (row: XgClassCadreVO) => {
try {
const res = await getClassCadre(row.id);
viewData.value = res.data;
viewDialog.visible = true;
} catch (error) {
console.error('获取班级干部信息失败:', error);
}
};
/** 提交按钮 */
const submitForm = () => {
classCadreFormRef.value?.validate(async (valid) => {
if (valid) {
try {
if (form.id) {
await updateClassCadre(form);
ElMessage.success('修改成功');
} else {
await addClassCadre(form);
ElMessage.success('新增成功');
}
dialog.visible = false;
getList();
} catch (error) {
console.error('提交失败:', error);
}
}
});
};
/** 删除按钮操作 */
const handleDelete = async (row?: XgClassCadreVO) => {
const deleteIds = row?.id ? [row.id] : ids.value;
try {
await ElMessageBox.confirm('是否确认删除班级干部信息编号为"' + deleteIds + '"的数据项?', '警告', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
});
await delClassCadre(deleteIds);
ElMessage.success('删除成功');
getList();
} catch (error) {
console.error('删除失败:', error);
}
};
/** 导出按钮操作 */
const handleExport = () => {
proxy?.download(
'/xg/classCadre/export',
{
...queryParams
},
`class_cadre_${new Date().getTime()}.xlsx`
);
};
/** 导入按钮操作 */
const handleImport = () => {
importDialog.visible = true;
};
/** 导入成功回调 */
const handleImportSuccess = () => {
getList();
};
/** 到期提醒 */
const handleExpiring = async () => {
try {
const res = await getExpiringCadres(30);
if (res.data.length > 0) {
ElMessage.warning(`有 ${res.data.length} 名班干部即将到期,请及时处理`);
} else {
ElMessage.success('暂无即将到期的班干部');
}
} catch (error) {
console.error('获取到期提醒失败:', error);
}
};
/** 任免操作 */
const handleCadreOperation = (command: string, row: XgClassCadreVO) => {
operationDialog.type = command;
operationDialog.cadreData = row;
operationDialog.visible = true;
};
/** 重新任命 */
const handleReappoint = (row: XgClassCadreVO) => {
handleCadreOperation('reappoint', row);
};
/** 任免操作确认 */
const handleOperationConfirm = async (operationData: any) => {
try {
switch (operationData.type) {
case 'dismiss':
await dismissCadre(operationData.cadreId, operationData.reason, operationData.operationDate);
ElMessage.success('免职成功');
break;
case 'transfer':
await transferCadre(
operationData.cadreId,
operationData.newPositionCode,
operationData.newPositionName,
operationData.reason,
operationData.effectiveDate
);
ElMessage.success('调任成功');
break;
case 'renew':
await renewCadre(operationData.cadreId, operationData.newTermDuration, operationData.reason);
ElMessage.success('续任成功');
break;
case 'resign':
await resignCadre(operationData.cadreId, operationData.reason, operationData.operationDate);
ElMessage.success('辞职成功');
break;
}
getList();
} catch (error) {
console.error('操作失败:', error);
}
};
/** 表单重置 */
const reset = () => {
Object.assign(form, {
id: undefined,
classId: undefined,
studentId: undefined,
positionCode: undefined,
appointmentDate: undefined,
termStartDate: undefined,
termDuration: 12,
isConcurrent: false,
appointmentReason: undefined,
contactPhone: undefined,
contactEmail: undefined,
workPlan: undefined,
remark: undefined
});
classCadreFormRef.value?.resetFields();
};
/** 取消按钮 */
const cancel = () => {
dialog.visible = false;
reset();
};
/** 远程搜索学生 */
const remoteStudentMethod = async (query: string) => {
if (query) {
studentLoading.value = true;
try {
const res = await listStudentInfo({ studentNo: query, name: query, pageSize: 20 });
studentOptions.value = res.rows.map((item) => ({
value: item.id,
label: item.name,
studentNo: item.studentNo
}));
} catch (error) {
console.error('搜索学生失败:', error);
} finally {
studentLoading.value = false;
}
}
};
/** 学生选择变化 */
const handleStudentChange = (studentId: string | number) => {
const student = studentOptions.value.find((item) => item.value === studentId);
if (student) {
form.studentNo = student.studentNo;
form.studentName = student.label;
}
};
/** 职务选择变化 */
const handlePositionChange = (positionCode: string) => {
const position = positionOptions.value.find((item) => item.value === positionCode);
if (position) {
form.positionName = position.label;
}
};
/** 获取班级选项 */
const getClassOptions = async () => {
try {
const res = await listClassInfo({ pageSize: 1000 });
classOptions.value = res.rows.map((item) => ({
value: item.id,
label: `${item.classCode} - ${item.className}`
}));
} catch (error) {
console.error('获取班级选项失败:', error);
}
};
// 辅助方法
const getPositionTagType = (level: number) => {
switch (level) {
case 1:
return 'danger'; // 班长
case 2:
return 'warning'; // 副班长
case 3:
return 'success'; // 委员
default:
return 'info'; // 普通成员
}
};
const getCadreStatusTagType = (status: string) => {
switch (status) {
case '1':
return 'success'; // 在任
case '2':
return 'warning'; // 到期
case '3':
return 'info'; // 辞职
case '4':
return 'danger'; // 免职
case '5':
return 'primary'; // 调任
default:
return 'info';
}
};
const getCadreStatusText = (status: string) => {
switch (status) {
case '1':
return '在任';
case '2':
return '到期';
case '3':
return '辞职';
case '4':
return '免职';
case '5':
return '调任';
default:
return '未知';
}
};
const getPerformanceTagType = (performance: string) => {
switch (performance) {
case 'excellent':
return 'success';
case 'good':
return 'primary';
case 'average':
return 'warning';
case 'poor':
return 'danger';
default:
return 'info';
}
};
const getPerformanceText = (performance: string) => {
switch (performance) {
case 'excellent':
return '优秀';
case 'good':
return '良好';
case 'average':
return '一般';
case 'poor':
return '较差';
default:
return '未知';
}
};
// 初始化
onMounted(() => {
getList();
getClassOptions();
});
</script>
<style scoped>
.app-container {
padding: 20px;
}
</style>
@@ -1,504 +0,0 @@
<template>
<div class="app-container">
<el-row :gutter="20">
<!-- 统计卡片 -->
<el-col :span="6" v-for="item in statisticsCards" :key="item.key">
<el-card class="statistics-card">
<div class="statistics-content">
<div class="statistics-icon" :style="{ backgroundColor: item.color }">
<el-icon :size="24">
<component :is="item.icon" />
</el-icon>
</div>
<div class="statistics-info">
<div class="statistics-title">{{ item.title }}</div>
<div class="statistics-value">{{ item.value }}</div>
</div>
</div>
</el-card>
</el-col>
</el-row>
<el-row :gutter="20" class="mt-4">
<!-- 职务分布图 -->
<el-col :span="12">
<el-card>
<template #header>
<div class="card-header">
<span>职务分布</span>
</div>
</template>
<div ref="positionChartRef" style="height: 300px"></div>
</el-card>
</el-col>
<!-- 任职状态分布图 -->
<el-col :span="12">
<el-card>
<template #header>
<div class="card-header">
<span>任职状态分布</span>
</div>
</template>
<div ref="statusChartRef" style="height: 300px"></div>
</el-card>
</el-col>
</el-row>
<el-row :gutter="20" class="mt-4">
<!-- 学院统计 -->
<el-col :span="12">
<el-card>
<template #header>
<div class="card-header">
<span>各学院班干部数量</span>
</div>
</template>
<div ref="collegeChartRef" style="height: 300px"></div>
</el-card>
</el-col>
<!-- 工作表现分布 -->
<el-col :span="12">
<el-card>
<template #header>
<div class="card-header">
<span>工作表现分布</span>
</div>
</template>
<div ref="performanceChartRef" style="height: 300px"></div>
</el-card>
</el-col>
</el-row>
<el-row :gutter="20" class="mt-4">
<!-- 到期提醒列表 -->
<el-col :span="24">
<el-card>
<template #header>
<div class="card-header">
<span>即将到期的班干部</span>
<el-button type="primary" size="small" @click="refreshExpiringList">刷新</el-button>
</div>
</template>
<el-table :data="expiringList" style="width: 100%">
<el-table-column prop="studentNo" label="学号" width="120" />
<el-table-column prop="studentName" label="姓名" width="100" />
<el-table-column prop="className" label="班级" width="150" />
<el-table-column prop="positionName" label="职务" width="120" />
<el-table-column prop="termEndDate" label="任期结束日期" width="120" />
<el-table-column prop="daysRemaining" label="剩余天数" width="100">
<template #default="scope">
<el-tag :type="scope.row.daysRemaining <= 7 ? 'danger' : 'warning'"> {{ scope.row.daysRemaining }}天 </el-tag>
</template>
</el-table-column>
<el-table-column prop="contactPhone" label="联系电话" width="120" />
<el-table-column label="操作" width="120">
<template #default="scope">
<el-button type="primary" size="small" @click="handleContact(scope.row)"> 联系 </el-button>
</template>
</el-table-column>
</el-table>
</el-card>
</el-col>
</el-row>
</div>
</template>
<script setup name="ClassCadreStatistics" lang="ts">
import { ref, reactive, onMounted, nextTick } from 'vue';
import * as echarts from 'echarts';
import { ElMessage } from 'element-plus';
import { User, UserFilled, Trophy, Clock } from '@element-plus/icons-vue';
import { listClassCadre, getExpiringCadres, getCadresByCollege, getCadresByPerformance } from '@/api/xg-xx/classCadre';
import type { XgClassCadreVO } from '@/api/xg-xx/types';
// 统计卡片数据
const statisticsCards = ref([
{
key: 'total',
title: '总班干部数',
value: 0,
icon: User,
color: '#409EFF'
},
{
key: 'current',
title: '在任班干部',
value: 0,
icon: UserFilled,
color: '#67C23A'
},
{
key: 'excellent',
title: '优秀班干部',
value: 0,
icon: Trophy,
color: '#E6A23C'
},
{
key: 'expiring',
title: '即将到期',
value: 0,
icon: Clock,
color: '#F56C6C'
}
]);
// 图表引用
const positionChartRef = ref<HTMLDivElement>();
const statusChartRef = ref<HTMLDivElement>();
const collegeChartRef = ref<HTMLDivElement>();
const performanceChartRef = ref<HTMLDivElement>();
// 到期提醒列表
const expiringList = ref<XgClassCadreVO[]>([]);
// 图表实例
let positionChart: echarts.ECharts;
let statusChart: echarts.ECharts;
let collegeChart: echarts.ECharts;
let performanceChart: echarts.ECharts;
/** 加载统计数据 */
const loadStatistics = async () => {
try {
// 获取总班干部数据
const totalRes = await listClassCadre({ pageSize: 10000 });
const totalData = totalRes.rows;
// 统计总数
statisticsCards.value[0].value = totalData.length;
// 统计在任班干部
const currentCadres = totalData.filter((item) => item.isCurrent);
statisticsCards.value[1].value = currentCadres.length;
// 统计优秀班干部
const excellentCadres = totalData.filter((item) => item.workPerformance === 'excellent');
statisticsCards.value[2].value = excellentCadres.length;
// 统计即将到期
const expiringRes = await getExpiringCadres(30);
statisticsCards.value[3].value = expiringRes.data.length;
expiringList.value = expiringRes.data;
// 初始化图表
await nextTick();
initPositionChart(totalData);
initStatusChart(totalData);
initCollegeChart(totalData);
initPerformanceChart(totalData);
} catch (error) {
console.error('加载统计数据失败:', error);
}
};
/** 初始化职务分布图 */
const initPositionChart = (data: XgClassCadreVO[]) => {
if (!positionChartRef.value) return;
positionChart = echarts.init(positionChartRef.value);
// 统计职务分布
const positionMap = new Map();
data.forEach((item) => {
const count = positionMap.get(item.positionName) || 0;
positionMap.set(item.positionName, count + 1);
});
const positionData = Array.from(positionMap.entries()).map(([name, value]) => ({
name,
value
}));
const option = {
tooltip: {
trigger: 'item'
},
series: [
{
name: '职务分布',
type: 'pie',
radius: '50%',
data: positionData,
emphasis: {
itemStyle: {
shadowBlur: 10,
shadowOffsetX: 0,
shadowColor: 'rgba(0, 0, 0, 0.5)'
}
}
}
]
};
positionChart.setOption(option);
};
/** 初始化任职状态分布图 */
const initStatusChart = (data: XgClassCadreVO[]) => {
if (!statusChartRef.value) return;
statusChart = echarts.init(statusChartRef.value);
// 统计任职状态分布
const statusMap = new Map();
data.forEach((item) => {
const statusText = getCadreStatusText(item.cadreStatus);
const count = statusMap.get(statusText) || 0;
statusMap.set(statusText, count + 1);
});
const statusData = Array.from(statusMap.entries()).map(([name, value]) => ({
name,
value
}));
const option = {
tooltip: {
trigger: 'item'
},
series: [
{
name: '任职状态',
type: 'pie',
radius: ['40%', '70%'],
data: statusData,
emphasis: {
itemStyle: {
shadowBlur: 10,
shadowOffsetX: 0,
shadowColor: 'rgba(0, 0, 0, 0.5)'
}
}
}
]
};
statusChart.setOption(option);
};
/** 初始化学院统计图 */
const initCollegeChart = (data: XgClassCadreVO[]) => {
if (!collegeChartRef.value) return;
collegeChart = echarts.init(collegeChartRef.value);
// 统计学院分布
const collegeMap = new Map();
data.forEach((item) => {
const college = item.collegeName || '未知学院';
const count = collegeMap.get(college) || 0;
collegeMap.set(college, count + 1);
});
const colleges = Array.from(collegeMap.keys());
const values = Array.from(collegeMap.values());
const option = {
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'shadow'
}
},
xAxis: {
type: 'category',
data: colleges,
axisLabel: {
interval: 0,
rotate: 45
}
},
yAxis: {
type: 'value'
},
series: [
{
name: '班干部数量',
type: 'bar',
data: values,
itemStyle: {
color: '#409EFF'
}
}
]
};
collegeChart.setOption(option);
};
/** 初始化工作表现分布图 */
const initPerformanceChart = (data: XgClassCadreVO[]) => {
if (!performanceChartRef.value) return;
performanceChart = echarts.init(performanceChartRef.value);
// 统计工作表现分布
const performanceMap = new Map();
data.forEach((item) => {
if (item.workPerformance) {
const performanceText = getPerformanceText(item.workPerformance);
const count = performanceMap.get(performanceText) || 0;
performanceMap.set(performanceText, count + 1);
}
});
const performanceData = Array.from(performanceMap.entries()).map(([name, value]) => ({
name,
value
}));
const option = {
tooltip: {
trigger: 'item'
},
series: [
{
name: '工作表现',
type: 'pie',
radius: '50%',
data: performanceData,
emphasis: {
itemStyle: {
shadowBlur: 10,
shadowOffsetX: 0,
shadowColor: 'rgba(0, 0, 0, 0.5)'
}
}
}
]
};
performanceChart.setOption(option);
};
/** 刷新即将到期列表 */
const refreshExpiringList = async () => {
try {
const res = await getExpiringCadres(30);
expiringList.value = res.data;
statisticsCards.value[3].value = res.data.length;
ElMessage.success('刷新成功');
} catch (error) {
console.error('刷新失败:', error);
}
};
/** 联系班干部 */
const handleContact = (row: XgClassCadreVO) => {
if (row.contactPhone) {
window.open(`tel:${row.contactPhone}`);
} else {
ElMessage.warning('该班干部未设置联系电话');
}
};
// 辅助方法
const getCadreStatusText = (status: string) => {
switch (status) {
case '1':
return '在任';
case '2':
return '到期';
case '3':
return '辞职';
case '4':
return '免职';
case '5':
return '调任';
default:
return '未知';
}
};
const getPerformanceText = (performance: string) => {
switch (performance) {
case 'excellent':
return '优秀';
case 'good':
return '良好';
case 'average':
return '一般';
case 'poor':
return '较差';
default:
return '未知';
}
};
// 响应式处理
const handleResize = () => {
positionChart?.resize();
statusChart?.resize();
collegeChart?.resize();
performanceChart?.resize();
};
onMounted(() => {
loadStatistics();
window.addEventListener('resize', handleResize);
});
// 清理
onBeforeUnmount(() => {
window.removeEventListener('resize', handleResize);
positionChart?.dispose();
statusChart?.dispose();
collegeChart?.dispose();
performanceChart?.dispose();
});
</script>
<style scoped>
.app-container {
padding: 20px;
}
.statistics-card {
margin-bottom: 20px;
}
.statistics-content {
display: flex;
align-items: center;
}
.statistics-icon {
width: 60px;
height: 60px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
color: white;
margin-right: 20px;
}
.statistics-info {
flex: 1;
}
.statistics-title {
font-size: 14px;
color: #666;
margin-bottom: 8px;
}
.statistics-value {
font-size: 24px;
font-weight: bold;
color: #333;
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
}
.mt-4 {
margin-top: 20px;
}
</style>
-277
View File
@@ -1,277 +0,0 @@
<template>
<div class="app-container">
<el-card class="box-card">
<template #header>
<div class="card-header">
<span>班级信息导入</span>
</div>
</template>
<el-steps :active="activeStep" finish-status="success" align-center>
<el-step title="下载模板" description="下载Excel导入模板"></el-step>
<el-step title="填写数据" description="按模板格式填写班级数据"></el-step>
<el-step title="上传文件" description="上传填写好的Excel文件"></el-step>
<el-step title="导入完成" description="查看导入结果"></el-step>
</el-steps>
<div class="import-content" v-if="activeStep === 0">
<div class="step-content">
<el-alert title="使用说明" type="info" :closable="false" show-icon>
<p>1. 请先下载班级信息导入模板</p>
<p>2. 按照模板格式填写班级信息</p>
<p>3. 班级代码、班级名称为必填项</p>
<p>4. 确保学院代码、专业代码在系统中存在</p>
</el-alert>
<div class="step-actions">
<el-button type="primary" @click="downloadTemplate">
<el-icon><Download /></el-icon>
下载导入模板
</el-button>
<el-button @click="nextStep">下一步</el-button>
</div>
</div>
</div>
<div class="import-content" v-if="activeStep === 1">
<div class="step-content">
<el-alert title="填写要求" type="warning" :closable="false" show-icon>
<p>• 班级代码必须唯一,建议格式:学院代码+年级+专业+序号</p>
<p>• 班级名称不能重复</p>
<p>• 入学年份和毕业年份必须为4位数字</p>
<p>• 最大学生人数建议在20-60人之间</p>
</el-alert>
<div class="step-actions">
<el-button @click="prevStep">上一步</el-button>
<el-button type="primary" @click="nextStep">下一步</el-button>
</div>
</div>
</div>
<div class="import-content" v-if="activeStep === 2">
<div class="step-content">
<el-upload
ref="uploadRef"
class="upload-demo"
drag
:action="uploadAction"
:headers="uploadHeaders"
:data="uploadData"
:before-upload="beforeUpload"
:on-success="handleUploadSuccess"
:on-error="handleUploadError"
:file-list="fileList"
:limit="1"
accept=".xlsx,.xls"
>
<el-icon class="el-icon--upload"><upload-filled /></el-icon>
<div class="el-upload__text">将Excel文件拖到此处,或<em>点击上传</em></div>
<template #tip>
<div class="el-upload__tip">只能上传xlsx/xls文件,且不超过10MB</div>
</template>
</el-upload>
<div class="upload-options">
<el-checkbox v-model="updateSupport"> 是否更新已经存在的班级数据 </el-checkbox>
</div>
<div class="step-actions">
<el-button @click="prevStep">上一步</el-button>
<el-button type="primary" @click="startImport" :loading="uploading"> 开始导入 </el-button>
</div>
</div>
</div>
<div class="import-content" v-if="activeStep === 3">
<div class="step-content">
<el-result :icon="importResult.success ? 'success' : 'error'" :title="importResult.title" :sub-title="importResult.message">
<template #extra>
<el-button type="primary" @click="resetImport">重新导入</el-button>
<el-button @click="goToList">查看班级列表</el-button>
</template>
</el-result>
<div v-if="importResult.detail" class="import-detail">
<el-card>
<template #header>
<span>导入详情</span>
</template>
<div v-html="importResult.detail"></div>
</el-card>
</div>
</div>
</div>
</el-card>
</div>
</template>
<script setup name="ClassInfoImport" lang="ts">
import { ref, reactive } from 'vue';
import { ElMessage } from 'element-plus';
import { getToken } from '@/utils/auth';
import { useRouter } from 'vue-router';
const router = useRouter();
// 组件状态
const activeStep = ref(0);
const uploading = ref(false);
const fileList = ref([]);
const updateSupport = ref(false);
// 上传配置
const uploadAction = ref(import.meta.env.VITE_APP_BASE_API + '/xg/classInfo/importData');
const uploadHeaders = ref({
Authorization: 'Bearer ' + getToken()
});
const uploadData = reactive({
updateSupport: false
});
// 导入结果
const importResult = reactive({
success: false,
title: '',
message: '',
detail: ''
});
/** 下载模板 */
const downloadTemplate = () => {
// 这里应该调用下载模板的API
window.open(import.meta.env.VITE_APP_BASE_API + '/xg/classInfo/importTemplate', '_blank');
ElMessage.success('模板下载已开始');
};
/** 下一步 */
const nextStep = () => {
if (activeStep.value < 3) {
activeStep.value++;
}
};
/** 上一步 */
const prevStep = () => {
if (activeStep.value > 0) {
activeStep.value--;
}
};
/** 上传前检查 */
const beforeUpload = (file: any) => {
const isExcel = file.type === 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' || file.type === 'application/vnd.ms-excel';
const isLt10M = file.size / 1024 / 1024 < 10;
if (!isExcel) {
ElMessage.error('上传文件只能是 Excel 格式!');
return false;
}
if (!isLt10M) {
ElMessage.error('上传文件大小不能超过 10MB!');
return false;
}
uploadData.updateSupport = updateSupport.value;
return true;
};
/** 开始导入 */
const startImport = () => {
if (fileList.value.length === 0) {
ElMessage.warning('请先选择要上传的文件');
return;
}
uploading.value = true;
// 文件会自动上传,这里只需要更新状态
};
/** 上传成功处理 */
const handleUploadSuccess = (response: any) => {
uploading.value = false;
if (response.code === 200) {
importResult.success = true;
importResult.title = '导入成功';
importResult.message = response.msg;
importResult.detail = response.data || '';
activeStep.value = 3;
} else {
importResult.success = false;
importResult.title = '导入失败';
importResult.message = response.msg || '导入过程中发生错误';
importResult.detail = response.data || '';
activeStep.value = 3;
}
};
/** 上传失败处理 */
const handleUploadError = () => {
uploading.value = false;
importResult.success = false;
importResult.title = '导入失败';
importResult.message = '文件上传失败,请检查网络连接或文件格式';
importResult.detail = '';
activeStep.value = 3;
};
/** 重新导入 */
const resetImport = () => {
activeStep.value = 0;
fileList.value = [];
updateSupport.value = false;
importResult.success = false;
importResult.title = '';
importResult.message = '';
importResult.detail = '';
};
/** 跳转到班级列表 */
const goToList = () => {
router.push('/xg-xx/classInfo');
};
</script>
<style scoped>
.import-content {
margin: 40px 0;
}
.step-content {
text-align: center;
padding: 20px;
}
.step-actions {
margin-top: 30px;
}
.step-actions .el-button {
margin: 0 10px;
}
.upload-demo {
margin: 20px 0;
}
.upload-options {
margin: 20px 0;
text-align: left;
}
.import-detail {
margin-top: 20px;
text-align: left;
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
}
.el-alert p {
margin: 5px 0;
}
</style>
File diff suppressed because it is too large. Load diff
@@ -1,916 +0,0 @@
<template>
<div class="p-2 page-container">
<!-- 搜索区域 -->
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
<div v-show="showSearch" class="mb-[10px]">
<el-card shadow="hover">
<el-form ref="queryFormRef" :model="queryParams" label-width="100px">
<el-row :gutter="20">
<el-col :span="6">
<el-form-item label="批次编码" prop="batchCode">
<el-input v-model="queryParams.batchCode" placeholder="请输入批次编码" clearable @keyup.enter="handleQuery" />
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="批次名称" prop="batchName">
<el-input v-model="queryParams.batchName" placeholder="请输入批次名称" clearable @keyup.enter="handleQuery" />
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="批次类型" prop="batchType">
<el-select v-model="queryParams.batchType" placeholder="请选择批次类型" clearable style="width: 100%">
<el-option label="常规批次" value="regular" />
<el-option label="专项批次" value="special" />
<el-option label="紧急批次" value="urgent" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="批次状态" prop="status">
<el-select v-model="queryParams.status" placeholder="请选择批次状态" clearable style="width: 100%">
<el-option label="草稿" value="draft" />
<el-option label="进行中" value="active" />
<el-option label="已完成" value="completed" />
<el-option label="已取消" value="cancelled" />
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="6">
<el-form-item label="学年" prop="academicYearId">
<el-select v-model="queryParams.academicYearId" placeholder="请选择学年" clearable style="width: 100%">
<el-option v-for="year in academicYearOptions" :key="year.value" :label="year.label" :value="year.value" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="是否发布" prop="isPublished">
<el-select v-model="queryParams.isPublished" placeholder="请选择发布状态" clearable style="width: 100%">
<el-option label="已发布" :value="true" />
<el-option label="未发布" :value="false" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-col>
</el-row>
</el-form>
</el-card>
</div>
</transition>
<!-- 主要内容区域 -->
<el-card shadow="never" class="button-card table-card">
<template #header>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['xg:collectionBatch:add']">新增批次</el-button>
</el-col>
<el-col :span="1.5">
<el-button icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['xg:collectionBatch:edit']">修改</el-button>
</el-col>
<el-col :span="1.5">
<el-button icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['xg:collectionBatch:remove']">删除</el-button>
</el-col>
<el-col :span="1.5">
<el-button icon="Download" @click="handleExport" v-hasPermi="['xg:collectionBatch:export']">导出</el-button>
</el-col>
<el-col :span="1.5">
<el-button icon="Promotion" :disabled="single" @click="handlePublish" v-hasPermi="['xg:collectionBatch:publish']">发布</el-button>
</el-col>
<el-col :span="1.5">
<el-button icon="Finished" :disabled="single" @click="handleComplete" v-hasPermi="['xg:collectionBatch:complete']">结束</el-button>
</el-col>
<right-toolbar v-model="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
</template>
<div class="table-container">
<el-table v-loading="loading" border :data="collectionBatchList" @selection-change="handleSelectionChange" style="width: 100%" class="scroll-table">
<el-table-column type="selection" width="55" align="center" fixed="left" />
<el-table-column label="批次编码" align="center" prop="batchCode" width="120" show-overflow-tooltip fixed="left" />
<el-table-column label="批次名称" align="center" prop="batchName" width="180" show-overflow-tooltip fixed="left" />
<el-table-column label="批次类型" align="center" prop="batchType" width="100">
<template #default="scope">
<el-tag :type="getBatchTypeTagType(scope.row.batchType)" size="small">
{{ getBatchTypeText(scope.row.batchType) }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="学年" align="center" prop="academicYearName" width="120" show-overflow-tooltip />
<el-table-column label="学期" align="center" prop="semesterName" width="120" show-overflow-tooltip />
<el-table-column label="目标范围" align="center" prop="targetScope" width="100">
<template #default="scope">
<el-tag :type="getTargetScopeTagType(scope.row.targetScope)" size="small">
{{ getTargetScopeText(scope.row.targetScope) }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="开始时间" align="center" prop="startTime" width="160">
<template #default="scope">
<span>{{ proxy.parseTime(scope.row.startTime, '{y}-{m}-{d} {h}:{i}') }}</span>
</template>
</el-table-column>
<el-table-column label="结束时间" align="center" prop="endTime" width="160">
<template #default="scope">
<span>{{ proxy.parseTime(scope.row.endTime, '{y}-{m}-{d} {h}:{i}') }}</span>
</template>
</el-table-column>
<el-table-column label="批次状态" align="center" prop="status" width="100">
<template #default="scope">
<el-tag :type="getStatusTagType(scope.row.status)" size="small">
{{ getStatusText(scope.row.status) }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="发布状态" align="center" prop="isPublished" width="100">
<template #default="scope">
<el-tag :type="scope.row.isPublished ? 'success' : 'info'" size="small">
{{ scope.row.isPublished ? '已发布' : '未发布' }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="完成进度" align="center" width="120">
<template #default="scope">
<el-progress :percentage="scope.row.completionRate" :stroke-width="6" :show-text="false" />
<div class="text-xs text-gray-500 mt-1">
{{ scope.row.completedCount }}/{{ scope.row.totalCount }}
</div>
</template>
</el-table-column>
<el-table-column label="创建时间" align="center" prop="createTime" width="160">
<template #default="scope">
<span>{{ proxy.parseTime(scope.row.createTime, '{y}-{m}-{d} {h}:{i}') }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" width="200" fixed="right" class-name="small-padding fixed-width">
<template #default="scope">
<div class="action-buttons">
<el-tooltip content="查看" placement="top">
<el-button link type="info" icon="View" size="small" @click="handleView(scope.row)" v-hasPermi="['xg:collectionBatch:query']"></el-button>
</el-tooltip>
<el-tooltip content="修改" placement="top">
<el-button link type="primary" icon="Edit" size="small" @click="handleUpdate(scope.row)" v-hasPermi="['xg:collectionBatch:edit']"></el-button>
</el-tooltip>
<el-tooltip content="删除" placement="top">
<el-button link type="danger" icon="Delete" size="small" @click="handleDelete(scope.row)" v-hasPermi="['xg:collectionBatch:remove']"></el-button>
</el-tooltip>
<el-tooltip content="统计" placement="top">
<el-button link type="warning" icon="DataAnalysis" size="small" @click="handleStatistics(scope.row)" v-hasPermi="['xg:collectionBatch:statistics']"></el-button>
</el-tooltip>
<el-tooltip content="复制" placement="top">
<el-button link type="success" icon="DocumentCopy" size="small" @click="handleCopy(scope.row)" v-hasPermi="['xg:collectionBatch:add']"></el-button>
</el-tooltip>
</div>
</template>
</el-table-column>
</el-table>
<div class="pagination-container">
<pagination v-show="total > 0" :total="total" :page="queryParams.pageNum" :limit="queryParams.pageSize" @pagination="getList" />
</div>
</div>
</el-card>
<!-- 新增/修改批次对话框 -->
<el-dialog :title="dialog.title" v-model="dialog.visible" width="900px" append-to-body>
<el-form ref="collectionBatchFormRef" :model="form" :rules="rules" label-width="120px">
<el-tabs v-model="activeTab" type="border-card">
<el-tab-pane label="基本信息" name="basic">
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="批次编码" prop="batchCode">
<el-input v-model="form.batchCode" placeholder="请输入批次编码" :disabled="dialog.type === 'edit'" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="批次名称" prop="batchName">
<el-input v-model="form.batchName" placeholder="请输入批次名称" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="批次类型" prop="batchType">
<el-select v-model="form.batchType" placeholder="请选择批次类型" style="width: 100%">
<el-option label="常规批次" value="regular" />
<el-option label="专项批次" value="special" />
<el-option label="紧急批次" value="urgent" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="目标范围" prop="targetScope">
<el-select v-model="form.targetScope" placeholder="请选择目标范围" style="width: 100%">
<el-option label="全部学生" value="all" />
<el-option label="指定学院" value="college" />
<el-option label="指定班级" value="class" />
<el-option label="指定学生" value="student" />
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="关联学年" prop="academicYearId">
<el-select v-model="form.academicYearId" placeholder="请选择学年" style="width: 100%">
<el-option v-for="year in academicYearOptions" :key="year.value" :label="year.label" :value="year.value" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="关联学期" prop="semesterId">
<el-select v-model="form.semesterId" placeholder="请选择学期" style="width: 100%">
<el-option v-for="semester in semesterOptions" :key="semester.value" :label="semester.label" :value="semester.value" />
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="开始时间" prop="startTime">
<el-date-picker v-model="form.startTime" type="datetime" placeholder="选择开始时间" style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="结束时间" prop="endTime">
<el-date-picker v-model="form.endTime" type="datetime" placeholder="选择结束时间" style="width: 100%" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="批次状态" prop="status">
<el-select v-model="form.status" placeholder="请选择批次状态" style="width: 100%">
<el-option label="草稿" value="draft" />
<el-option label="进行中" value="active" />
<el-option label="已完成" value="completed" />
<el-option label="已取消" value="cancelled" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="排序序号" prop="sortOrder">
<el-input-number v-model="form.sortOrder" :min="0" :max="9999" placeholder="排序序号" style="width: 100%" />
</el-form-item>
</el-col>
</el-row>
</el-tab-pane>
<el-tab-pane label="高级设置" name="advanced">
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="自动提醒" prop="autoRemind">
<el-switch v-model="form.autoRemind" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="提醒天数" prop="remindDays">
<el-input-number v-model="form.remindDays" :min="1" :max="30" placeholder="提醒天数" style="width: 100%" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="24">
<el-form-item label="批次描述" prop="description">
<el-input v-model="form.description" type="textarea" :rows="4" placeholder="请输入批次描述" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="24">
<el-form-item label="通知内容" prop="noticeContent">
<el-input v-model="form.noticeContent" type="textarea" :rows="4" placeholder="请输入通知内容" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="24">
<el-form-item label="备注" prop="remark">
<el-input v-model="form.remark" type="textarea" :rows="3" placeholder="请输入备注" />
</el-form-item>
</el-col>
</el-row>
</el-tab-pane>
</el-tabs>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="submitForm">确 定</el-button>
<el-button @click="cancelForm">取 消</el-button>
</div>
</template>
</el-dialog>
<!-- 查看详情对话框 -->
<el-dialog title="批次详情" v-model="detailDialog.visible" width="800px" append-to-body>
<el-descriptions :column="2" border>
<el-descriptions-item label="批次编码">{{ detailForm.batchCode }}</el-descriptions-item>
<el-descriptions-item label="批次名称">{{ detailForm.batchName }}</el-descriptions-item>
<el-descriptions-item label="批次类型">
<el-tag :type="getBatchTypeTagType(detailForm.batchType)" size="small">
{{ getBatchTypeText(detailForm.batchType) }}
</el-tag>
</el-descriptions-item>
<el-descriptions-item label="目标范围">
<el-tag :type="getTargetScopeTagType(detailForm.targetScope)" size="small">
{{ getTargetScopeText(detailForm.targetScope) }}
</el-tag>
</el-descriptions-item>
<el-descriptions-item label="关联学年">{{ detailForm.academicYearName }}</el-descriptions-item>
<el-descriptions-item label="关联学期">{{ detailForm.semesterName || '-' }}</el-descriptions-item>
<el-descriptions-item label="开始时间">{{ proxy.parseTime(detailForm.startTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</el-descriptions-item>
<el-descriptions-item label="结束时间">{{ proxy.parseTime(detailForm.endTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</el-descriptions-item>
<el-descriptions-item label="批次状态">
<el-tag :type="getStatusTagType(detailForm.status)" size="small">
{{ getStatusText(detailForm.status) }}
</el-tag>
</el-descriptions-item>
<el-descriptions-item label="发布状态">
<el-tag :type="detailForm.isPublished ? 'success' : 'info'" size="small">
{{ detailForm.isPublished ? '已发布' : '未发布' }}
</el-tag>
</el-descriptions-item>
<el-descriptions-item label="完成进度">
<el-progress :percentage="detailForm.completionRate" :stroke-width="6" />
<div class="text-xs text-gray-500 mt-1">
{{ detailForm.completedCount }}/{{ detailForm.totalCount }}
</div>
</el-descriptions-item>
<el-descriptions-item label="自动提醒">
<el-tag :type="detailForm.autoRemind ? 'success' : 'info'" size="small">
{{ detailForm.autoRemind ? '开启' : '关闭' }}
</el-tag>
</el-descriptions-item>
<el-descriptions-item label="提醒天数">{{ detailForm.remindDays }}天</el-descriptions-item>
<el-descriptions-item label="排序序号">{{ detailForm.sortOrder }}</el-descriptions-item>
<el-descriptions-item label="创建时间">{{ proxy.parseTime(detailForm.createTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</el-descriptions-item>
<el-descriptions-item label="更新时间">{{ proxy.parseTime(detailForm.updateTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</el-descriptions-item>
<el-descriptions-item label="批次描述" :span="2">{{ detailForm.description || '-' }}</el-descriptions-item>
<el-descriptions-item label="通知内容" :span="2">{{ detailForm.noticeContent || '-' }}</el-descriptions-item>
<el-descriptions-item label="备注" :span="2">{{ detailForm.remark || '-' }}</el-descriptions-item>
</el-descriptions>
<template #footer>
<div class="dialog-footer">
<el-button @click="detailDialog.visible = false">关 闭</el-button>
</div>
</template>
</el-dialog>
<!-- 复制批次对话框 -->
<el-dialog title="复制批次" v-model="copyDialog.visible" width="500px" append-to-body>
<el-form ref="copyFormRef" :model="copyForm" :rules="copyRules" label-width="120px">
<el-form-item label="新批次编码" prop="batchCode">
<el-input v-model="copyForm.batchCode" placeholder="请输入新批次编码" />
</el-form-item>
<el-form-item label="新批次名称" prop="batchName">
<el-input v-model="copyForm.batchName" placeholder="请输入新批次名称" />
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="submitCopyForm">确 定</el-button>
<el-button @click="copyDialog.visible = false">取 消</el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup lang="ts" name="CollectionBatch">
// 组件导入和基本设置
import { nextTick, onMounted, reactive, ref, computed } from 'vue';
import { ElMessage, ElMessageBox } from 'element-plus';
import { getCurrentInstance } from 'vue';
import {
listCollectionBatch,
getCollectionBatch,
addCollectionBatch,
updateCollectionBatch,
delCollectionBatch,
publishBatch,
unpublishBatch,
completeBatch,
copyBatch,
getBatchStatistics,
updateBatchStatistics
} from '@/api/xg-xx/collectionBatch';
import { listAcademicYear } from '@/api/xg-xx/academicYear';
import { listSemester } from '@/api/xg-xx/semester';
import type { XgCollectionBatchVO, XgCollectionBatchForm } from '@/api/xg-xx/types';
const { proxy } = getCurrentInstance() as any;
// 响应式数据
const collectionBatchList = ref<XgCollectionBatchVO[]>([]);
const loading = ref(true);
const showSearch = ref(true);
const ids = ref<Array<string | number>>([]);
const single = ref(true);
const multiple = ref(true);
const total = ref(0);
const queryFormRef = ref();
const collectionBatchFormRef = ref();
const copyFormRef = ref();
const activeTab = ref('basic');
// 选项数据
const academicYearOptions = ref([]);
const semesterOptions = ref([]);
// 查询参数
const queryParams = ref({
pageNum: 1,
pageSize: 10,
batchCode: undefined,
batchName: undefined,
batchType: undefined,
status: undefined,
academicYearId: undefined,
isPublished: undefined
});
// 表单参数
const form = ref<XgCollectionBatchForm>({});
// 详情表单
const detailForm = ref<XgCollectionBatchVO>({});
// 复制表单
const copyForm = ref({
batchCode: '',
batchName: ''
});
// 当前复制的批次ID
const copyBatchId = ref<string | number>('');
// 表单校验
const rules = ref({
batchCode: [{ required: true, message: '批次编码不能为空', trigger: 'blur' }],
batchName: [{ required: true, message: '批次名称不能为空', trigger: 'blur' }],
batchType: [{ required: true, message: '批次类型不能为空', trigger: 'change' }],
targetScope: [{ required: true, message: '目标范围不能为空', trigger: 'change' }],
academicYearId: [{ required: true, message: '关联学年不能为空', trigger: 'change' }],
startTime: [{ required: true, message: '开始时间不能为空', trigger: 'change' }],
endTime: [{ required: true, message: '结束时间不能为空', trigger: 'change' }],
status: [{ required: true, message: '批次状态不能为空', trigger: 'change' }]
});
// 复制表单校验
const copyRules = ref({
batchCode: [{ required: true, message: '批次编码不能为空', trigger: 'blur' }],
batchName: [{ required: true, message: '批次名称不能为空', trigger: 'blur' }]
});
// 对话框
const dialog = reactive({
visible: false,
title: '',
type: 'add'
});
const detailDialog = reactive({
visible: false
});
const copyDialog = reactive({
visible: false
});
// 生命周期钩子
onMounted(() => {
getList();
getAcademicYearOptions();
getSemesterOptions();
});
// 获取批次列表
const getList = async () => {
loading.value = true;
try {
const res = await listCollectionBatch(queryParams.value);
collectionBatchList.value = res.rows;
total.value = res.total;
} catch (error) {
console.error('获取批次列表失败:', error);
ElMessage.error('获取批次列表失败,请稍后重试');
} finally {
loading.value = false;
}
};
// 获取学年选项
const getAcademicYearOptions = async () => {
try {
const res = await listAcademicYear({ pageNum: 1, pageSize: 1000 });
academicYearOptions.value = res.rows.map(item => ({
value: item.id,
label: item.yearName
}));
} catch (error) {
console.error('获取学年选项失败:', error);
}
};
// 获取学期选项
const getSemesterOptions = async () => {
try {
const res = await listSemester({ pageNum: 1, pageSize: 1000 });
semesterOptions.value = res.rows.map(item => ({
value: item.id,
label: item.semesterName
}));
} catch (error) {
console.error('获取学期选项失败:', error);
}
};
// 搜索按钮操作
const handleQuery = () => {
queryParams.value.pageNum = 1;
getList();
};
// 重置按钮操作
const resetQuery = () => {
queryFormRef.value?.resetFields();
handleQuery();
};
// 多选框选中数据
const handleSelectionChange = (selection: XgCollectionBatchVO[]) => {
ids.value = selection.map(item => item.id);
single.value = selection.length !== 1;
multiple.value = !selection.length;
};
// 新增按钮操作
const handleAdd = () => {
reset();
dialog.visible = true;
dialog.title = '新增信息采集批次';
dialog.type = 'add';
};
// 修改按钮操作
const handleUpdate = async (row?: XgCollectionBatchVO) => {
reset();
const batchId = row?.id || ids.value[0];
try {
const res = await getCollectionBatch(batchId);
Object.assign(form.value, res.data);
dialog.visible = true;
dialog.title = '修改信息采集批次';
dialog.type = 'edit';
} catch (error) {
console.error('获取批次详情失败:', error);
ElMessage.error('获取批次详情失败,请稍后重试');
}
};
// 查看详情
const handleView = async (row: XgCollectionBatchVO) => {
try {
const res = await getCollectionBatch(row.id);
Object.assign(detailForm.value, res.data);
detailDialog.visible = true;
} catch (error) {
console.error('获取批次详情失败:', error);
ElMessage.error('获取批次详情失败,请稍后重试');
}
};
// 删除按钮操作
const handleDelete = async (row?: XgCollectionBatchVO) => {
const batchIds = row?.id || ids.value;
let confirmMessage = '';
if (row) {
confirmMessage = `是否确认删除批次"${row.batchName}"?`;
} else {
const selectedBatches = collectionBatchList.value.filter(item => ids.value.includes(item.id));
if (selectedBatches.length === 1) {
confirmMessage = `是否确认删除批次"${selectedBatches[0].batchName}"?`;
} else {
confirmMessage = `是否确认删除选中的${selectedBatches.length}个批次?`;
}
}
try {
await ElMessageBox.confirm(confirmMessage, '系统提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
});
await delCollectionBatch(batchIds);
ElMessage.success('删除成功');
await getList();
} catch (error) {
console.error('删除失败:', error);
}
};
// 发布按钮操作
const handlePublish = async () => {
const batchId = ids.value[0];
const selectedBatch = collectionBatchList.value.find(item => item.id === batchId);
try {
await ElMessageBox.confirm(`是否确认发布批次"${selectedBatch?.batchName}"?`, '系统提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
});
await publishBatch(batchId);
ElMessage.success('发布成功');
await getList();
} catch (error) {
console.error('发布失败:', error);
}
};
// 结束按钮操作
const handleComplete = async () => {
const batchId = ids.value[0];
const selectedBatch = collectionBatchList.value.find(item => item.id === batchId);
try {
await ElMessageBox.confirm(`是否确认结束批次"${selectedBatch?.batchName}"?`, '系统提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
});
await completeBatch(batchId);
ElMessage.success('结束成功');
await getList();
} catch (error) {
console.error('结束失败:', error);
}
};
// 复制按钮操作
const handleCopy = (row: XgCollectionBatchVO) => {
copyBatchId.value = row.id;
copyForm.value = {
batchCode: `${row.batchCode}_copy`,
batchName: `${row.batchName}_复制`
};
copyDialog.visible = true;
};
// 统计按钮操作
const handleStatistics = async (row: XgCollectionBatchVO) => {
try {
// 更新统计信息
await updateBatchStatistics(row.id);
// 获取统计数据
const res = await getBatchStatistics(row.id);
ElMessageBox.alert(`
<div style="text-align: left;">
<h4>批次统计信息</h4>
<p>批次名称:${row.batchName}</p>
<p>总人数:${res.data.totalCount}</p>
<p>已完成:${res.data.completedCount}</p>
<p>完成率:${res.data.completionRate}%</p>
<p>更新时间:${proxy.parseTime(new Date(), '{y}-{m}-{d} {h}:{i}:{s}')}</p>
</div>
`, '统计信息', {
dangerouslyUseHTMLString: true,
type: 'info'
});
} catch (error) {
console.error('获取统计信息失败:', error);
ElMessage.error('获取统计信息失败,请稍后重试');
}
};
// 导出按钮操作
const handleExport = () => {
proxy?.download(
'xg/collectionBatch/export',
{
...queryParams.value
},
`信息采集批次_${proxy.parseTime(new Date(), '{y}{m}{d}{h}{i}{s}')}.xlsx`
);
};
// 提交表单
const submitForm = async () => {
if (!collectionBatchFormRef.value) return;
try {
await collectionBatchFormRef.value.validate();
if (dialog.type === 'add') {
await addCollectionBatch(form.value);
ElMessage.success('新增成功');
} else if (dialog.type === 'edit') {
await updateCollectionBatch(form.value);
ElMessage.success('修改成功');
}
dialog.visible = false;
await getList();
} catch (error) {
console.error('提交失败:', error);
}
};
// 提交复制表单
const submitCopyForm = async () => {
if (!copyFormRef.value) return;
try {
await copyFormRef.value.validate();
await copyBatch(copyBatchId.value, copyForm.value);
ElMessage.success('复制成功');
copyDialog.visible = false;
await getList();
} catch (error) {
console.error('复制失败:', error);
}
};
// 取消表单
const cancelForm = () => {
dialog.visible = false;
reset();
};
// 表单重置
const reset = () => {
form.value = {
batchCode: undefined,
batchName: undefined,
batchType: 'regular',
targetScope: 'all',
academicYearId: undefined,
semesterId: undefined,
startTime: undefined,
endTime: undefined,
status: 'draft',
autoRemind: true,
remindDays: 3,
sortOrder: 0,
description: undefined,
noticeContent: undefined,
remark: undefined
};
collectionBatchFormRef.value?.resetFields();
};
// 获取批次类型标签类型
const getBatchTypeTagType = (batchType: string) => {
switch (batchType) {
case 'regular':
return '';
case 'special':
return 'warning';
case 'urgent':
return 'danger';
default:
return 'info';
}
};
// 获取批次类型文本
const getBatchTypeText = (batchType: string) => {
switch (batchType) {
case 'regular':
return '常规';
case 'special':
return '专项';
case 'urgent':
return '紧急';
default:
return '未知';
}
};
// 获取目标范围标签类型
const getTargetScopeTagType = (targetScope: string) => {
switch (targetScope) {
case 'all':
return 'success';
case 'college':
return 'warning';
case 'class':
return '';
case 'student':
return 'info';
default:
return 'info';
}
};
// 获取目标范围文本
const getTargetScopeText = (targetScope: string) => {
switch (targetScope) {
case 'all':
return '全部';
case 'college':
return '学院';
case 'class':
return '班级';
case 'student':
return '学生';
default:
return '未知';
}
};
// 获取状态标签类型
const getStatusTagType = (status: string) => {
switch (status) {
case 'draft':
return 'info';
case 'active':
return 'success';
case 'completed':
return '';
case 'cancelled':
return 'danger';
default:
return 'info';
}
};
// 获取状态文本
const getStatusText = (status: string) => {
switch (status) {
case 'draft':
return '草稿';
case 'active':
return '进行中';
case 'completed':
return '已完成';
case 'cancelled':
return '已取消';
default:
return '未知';
}
};
</script>
<style scoped>
/* 页面容器样式 */
.page-container {
padding: 8px;
}
/* 表格卡片布局 */
.table-card {
margin-top: 0;
}
.table-card :deep(.el-card__body) {
padding: 0px 20px 20px 20px !important;
}
/* 移除按钮栏底部边框 */
.button-card :deep(.el-card__header) {
border-bottom: none !important;
}
/* 分页容器 */
.pagination-container {
display: flex;
justify-content: flex-end;
padding: 16px 20px 0 0 !important;
background-color: #fff;
margin-top: 0px !important;
}
/* 操作按钮样式 */
.action-buttons {
display: flex;
gap: 4px;
justify-content: center;
align-items: center;
}
/* 对话框底部样式 */
.dialog-footer {
text-align: right;
}
/* 表格容器 */
.table-container {
background-color: #fff;
}
/* 滚动表格 */
.scroll-table {
overflow-x: auto;
}
</style>
-348
View File
@@ -1,348 +0,0 @@
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryFormRef" :inline="true" v-show="showSearch" label-width="68px">
<el-form-item label="学年" prop="academicYearId">
<el-select v-model="queryParams.academicYearId" placeholder="请选择学年" clearable>
<el-option v-for="item in academicYearOptions" :key="item.id" :label="item.yearName" :value="item.id" />
</el-select>
</el-form-item>
<el-form-item label="学期编码" prop="semesterCode">
<el-input v-model="queryParams.semesterCode" placeholder="请输入学期编码" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="学期名称" prop="semesterName">
<el-input v-model="queryParams.semesterName" placeholder="请输入学期名称" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="学期类型" prop="semesterType">
<el-select v-model="queryParams.semesterType" placeholder="请选择学期类型" clearable>
<el-option label="春季学期" value="1" />
<el-option label="秋季学期" value="2" />
<el-option label="夏季学期" value="3" />
<el-option label="短学期" value="4" />
</el-select>
</el-form-item>
<el-form-item label="学期状态" prop="semesterStatus">
<el-select v-model="queryParams.semesterStatus" placeholder="请选择学期状态" clearable>
<el-option label="启用" value="1" />
<el-option label="停用" value="0" />
</el-select>
</el-form-item>
<el-form-item label="是否当前" prop="isCurrent">
<el-select v-model="queryParams.isCurrent" placeholder="请选择是否当前学期" clearable>
<el-option label="是" :value="true" />
<el-option label="否" :value="false" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['xg:semester:add']">新增</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['xg:semester:edit']">修改</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['xg:semester:remove']">删除</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['xg:semester:export']">导出</el-button>
</el-col>
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
<el-table v-loading="loading" :data="semesterList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="学年名称" align="center" prop="academicYearName" />
<el-table-column label="学期编码" align="center" prop="semesterCode" />
<el-table-column label="学期名称" align="center" prop="semesterName" />
<el-table-column label="学期类型" align="center" prop="semesterType">
<template #default="scope">
<dict-tag :options="semester_type" :value="scope.row.semesterType" />
</template>
</el-table-column>
<el-table-column label="开始日期" align="center" prop="startDate" width="180">
<template #default="scope">
<span>{{ parseTime(scope.row.startDate, '{y}-{m}-{d}') }}</span>
</template>
</el-table-column>
<el-table-column label="结束日期" align="center" prop="endDate" width="180">
<template #default="scope">
<span>{{ parseTime(scope.row.endDate, '{y}-{m}-{d}') }}</span>
</template>
</el-table-column>
<el-table-column label="是否当前" align="center" prop="isCurrent">
<template #default="scope">
<dict-tag :options="sys_yes_no" :value="scope.row.isCurrent" />
</template>
</el-table-column>
<el-table-column label="学期状态" align="center" prop="semesterStatus">
<template #default="scope">
<dict-tag :options="sys_normal_disable" :value="scope.row.semesterStatus" />
</template>
</el-table-column>
<el-table-column label="排序" align="center" prop="sortOrder" />
<el-table-column label="创建时间" align="center" prop="createTime" width="180">
<template #default="scope">
<span>{{ parseTime(scope.row.createTime, '{y}-{m}-{d}') }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template #default="scope">
<el-tooltip content="修改" placement="top">
<el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['xg:semester:edit']"></el-button>
</el-tooltip>
<el-tooltip content="删除" placement="top">
<el-button link type="danger" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['xg:semester:remove']"></el-button>
</el-tooltip>
<el-tooltip content="设为当前" placement="top" v-if="!scope.row.isCurrent">
<el-button link type="success" icon="Check" @click="handleSetCurrent(scope.row)" v-hasPermi="['xg:semester:edit']"></el-button>
</el-tooltip>
</template>
</el-table-column>
</el-table>
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
<!-- 添加或修改学期管理对话框 -->
<el-dialog :title="dialog.title" v-model="dialog.visible" width="500px" append-to-body>
<el-form ref="semesterFormRef" :model="form" :rules="rules" label-width="80px">
<el-form-item label="学年" prop="academicYearId">
<el-select v-model="form.academicYearId" placeholder="请选择学年" @change="handleAcademicYearChange">
<el-option v-for="item in academicYearOptions" :key="item.id" :label="item.yearName" :value="item.id" />
</el-select>
</el-form-item>
<el-form-item label="学期编码" prop="semesterCode">
<el-input v-model="form.semesterCode" placeholder="请输入学期编码" />
</el-form-item>
<el-form-item label="学期名称" prop="semesterName">
<el-input v-model="form.semesterName" placeholder="请输入学期名称" />
</el-form-item>
<el-form-item label="学期类型" prop="semesterType">
<el-select v-model="form.semesterType" placeholder="请选择学期类型">
<el-option label="春季学期" value="1" />
<el-option label="秋季学期" value="2" />
<el-option label="夏季学期" value="3" />
<el-option label="短学期" value="4" />
</el-select>
</el-form-item>
<el-form-item label="开始日期" prop="startDate">
<el-date-picker clearable v-model="form.startDate" type="date" value-format="YYYY-MM-DD" placeholder="请选择开始日期" />
</el-form-item>
<el-form-item label="结束日期" prop="endDate">
<el-date-picker clearable v-model="form.endDate" type="date" value-format="YYYY-MM-DD" placeholder="请选择结束日期" />
</el-form-item>
<el-form-item label="是否当前" prop="isCurrent">
<el-radio-group v-model="form.isCurrent">
<el-radio :value="true">是</el-radio>
<el-radio :value="false">否</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="学期状态" prop="semesterStatus">
<el-radio-group v-model="form.semesterStatus">
<el-radio value="1">启用</el-radio>
<el-radio value="0">停用</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="排序序号" prop="sortOrder">
<el-input-number v-model="form.sortOrder" :min="0" />
</el-form-item>
<el-form-item label="学期描述" prop="description">
<el-input v-model="form.description" type="textarea" placeholder="请输入学期描述" />
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input v-model="form.remark" type="textarea" placeholder="请输入备注" />
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="cancel">取 消</el-button>
<el-button type="primary" @click="submitForm">确 定</el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup name="Semester" lang="ts">
import { listSemester, getSemester, delSemester, addSemester, updateSemester, setCurrentSemester, exportSemester } from '@/api/xg-xx/semester';
import { getAvailableAcademicYearOptions } from '@/api/xg-xx/academicYear';
import { SemesterVO, SemesterQuery, SemesterForm, AcademicYearVO } from '@/api/xg-xx/academicTypes';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const { sys_yes_no, sys_normal_disable, semester_type } = proxy?.useDict('sys_yes_no', 'sys_normal_disable', 'semester_type');
const semesterList = ref<SemesterVO[]>([]);
const academicYearOptions = ref<AcademicYearVO[]>([]);
const loading = ref(true);
const showSearch = ref(true);
const ids = ref<Array<string | number>>([]);
const single = ref(true);
const multiple = ref(true);
const total = ref(0);
const queryFormRef = ref<ElFormInstance>();
const semesterFormRef = ref<ElFormInstance>();
const dialog = reactive<DialogOption>({
visible: false,
title: ''
});
const initFormData: SemesterForm = {
id: undefined,
academicYearId: undefined,
semesterCode: '',
semesterName: '',
semesterType: '',
startDate: '',
endDate: '',
isCurrent: false,
semesterStatus: '1',
sortOrder: 0,
description: '',
remark: ''
};
const data = reactive<PageData<SemesterForm, SemesterQuery>>({
form: { ...initFormData },
queryParams: {
pageNum: 1,
pageSize: 10,
academicYearId: undefined,
semesterCode: '',
semesterName: '',
semesterType: '',
semesterStatus: '',
isCurrent: undefined,
params: {}
},
rules: {
academicYearId: [{ required: true, message: '学年不能为空', trigger: 'change' }],
semesterCode: [{ required: true, message: '学期编码不能为空', trigger: 'blur' }],
semesterName: [{ required: true, message: '学期名称不能为空', trigger: 'blur' }],
semesterType: [{ required: true, message: '学期类型不能为空', trigger: 'change' }],
startDate: [{ required: true, message: '开始日期不能为空', trigger: 'blur' }],
endDate: [{ required: true, message: '结束日期不能为空', trigger: 'blur' }],
semesterStatus: [{ required: true, message: '学期状态不能为空', trigger: 'change' }],
sortOrder: [{ required: true, message: '排序序号不能为空', trigger: 'blur' }]
}
});
const { queryParams, form, rules } = toRefs(data);
/** 查询学期管理列表 */
const getList = async () => {
loading.value = true;
const res = await listSemester(queryParams.value);
semesterList.value = res.rows;
total.value = res.total;
loading.value = false;
};
/** 获取学年选项 */
const getAcademicYearOptions = async () => {
const res = await getAvailableAcademicYearOptions();
academicYearOptions.value = res.data;
};
/** 取消按钮 */
const cancel = () => {
reset();
dialog.visible = false;
};
/** 表单重置 */
const reset = () => {
form.value = { ...initFormData };
semesterFormRef.value?.resetFields();
};
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.value.pageNum = 1;
getList();
};
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value?.resetFields();
handleQuery();
};
/** 多选框选中数据 */
const handleSelectionChange = (selection: SemesterVO[]) => {
ids.value = selection.map((item) => item.id);
single.value = selection.length !== 1;
multiple.value = !selection.length;
};
/** 新增按钮操作 */
const handleAdd = () => {
reset();
dialog.visible = true;
dialog.title = '添加学期管理';
};
/** 修改按钮操作 */
const handleUpdate = async (row?: SemesterVO) => {
reset();
const _id = row?.id || ids.value[0];
const res = await getSemester(_id);
Object.assign(form.value, res.data);
dialog.visible = true;
dialog.title = '修改学期管理';
};
/** 学年变化处理 */
const handleAcademicYearChange = () => {
// 可以根据学年自动填充一些默认值
};
/** 提交按钮 */
const submitForm = () => {
semesterFormRef.value?.validate(async (valid: boolean) => {
if (valid) {
if (form.value.id) {
await updateSemester(form.value);
proxy?.$modal.msgSuccess('修改成功');
} else {
await addSemester(form.value);
proxy?.$modal.msgSuccess('新增成功');
}
dialog.visible = false;
await getList();
}
});
};
/** 删除按钮操作 */
const handleDelete = async (row?: SemesterVO) => {
const _ids = row?.id || ids.value;
await proxy?.$modal.confirm('是否确认删除学期管理编号为"' + _ids + '"的数据项?');
await delSemester(_ids);
proxy?.$modal.msgSuccess('删除成功');
await getList();
};
/** 设为当前学期 */
const handleSetCurrent = async (row: SemesterVO) => {
await proxy?.$modal.confirm('是否确认设置"' + row.semesterName + '"为当前学期?');
await setCurrentSemester(row.id);
proxy?.$modal.msgSuccess('设置成功');
await getList();
};
/** 导出按钮操作 */
const handleExport = () => {
proxy?.$download.excel(exportSemester, queryParams.value, `学期管理_${new Date().getTime()}.xlsx`);
};
onMounted(() => {
getList();
getAcademicYearOptions();
});
</script>
@@ -1,774 +0,0 @@
<template>
<div class="p-2 page-container">
<!-- 批次选择区域 -->
<el-card shadow="hover" class="mb-4">
<template #header>
<div class="card-header">
<span>批次管理</span>
<div class="header-actions">
<el-select v-model="selectedBatchId" placeholder="请选择批次" @change="handleBatchChange" style="width: 200px; margin-right: 10px;">
<el-option v-for="batch in batchOptions" :key="batch.id" :label="batch.batchName" :value="batch.id" />
</el-select>
<el-button type="primary" icon="Refresh" @click="refreshBatchInfo">刷新</el-button>
</div>
</div>
</template>
<!-- 批次信息展示 -->
<div v-if="currentBatch" class="batch-info">
<el-row :gutter="20">
<el-col :span="6">
<el-statistic title="批次名称" :value="currentBatch.batchName" />
</el-col>
<el-col :span="6">
<el-statistic title="批次状态" :value="getBatchStatusText(currentBatch.status)" />
</el-col>
<el-col :span="6">
<el-statistic title="总学生数" :value="batchStatistics.totalCount" />
</el-col>
<el-col :span="6">
<el-statistic title="已提交数" :value="batchStatistics.submittedCount" />
</el-col>
</el-row>
<el-row :gutter="20" class="mt-4">
<el-col :span="6">
<el-statistic title="已通过数" :value="batchStatistics.approvedCount" />
</el-col>
<el-col :span="6">
<el-statistic title="已拒绝数" :value="batchStatistics.rejectedCount" />
</el-col>
<el-col :span="6">
<el-statistic title="完成率" :value="batchStatistics.completionRate" suffix="%" />
</el-col>
<el-col :span="6">
<div class="progress-info">
<el-progress :percentage="batchStatistics.completionRate" :stroke-width="8" />
</div>
</el-col>
</el-row>
</div>
</el-card>
<!-- 搜索区域 -->
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
<div v-show="showSearch" class="mb-[10px]">
<el-card shadow="hover">
<el-form ref="queryFormRef" :model="queryParams" label-width="100px">
<el-row :gutter="20">
<el-col :span="6">
<el-form-item label="学生姓名" prop="name">
<el-input v-model="queryParams.name" placeholder="请输入学生姓名" clearable @keyup.enter="handleQuery" />
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="学号" prop="studentNo">
<el-input v-model="queryParams.studentNo" placeholder="请输入学号" clearable @keyup.enter="handleQuery" />
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="班级" prop="className">
<el-input v-model="queryParams.className" placeholder="请输入班级名称" clearable @keyup.enter="handleQuery" />
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="提交状态" prop="submissionStatus">
<el-select v-model="queryParams.submissionStatus" placeholder="请选择提交状态" clearable style="width: 100%">
<el-option label="草稿" value="DRAFT" />
<el-option label="已提交" value="SUBMITTED" />
<el-option label="已通过" value="APPROVED" />
<el-option label="已拒绝" value="REJECTED" />
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="6">
<el-form-item label="学院" prop="collegeName">
<el-input v-model="queryParams.collegeName" placeholder="请输入学院名称" clearable @keyup.enter="handleQuery" />
</el-form-item>
</el-col>
<el-col :span="18">
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-col>
</el-row>
</el-form>
</el-card>
</div>
</transition>
<!-- 主要内容区域 -->
<el-card shadow="hover">
<!-- 操作按钮区域 -->
<div class="toolbar-wrapper">
<div class="toolbar-left">
<el-button type="primary" icon="Plus" @click="handleAssociate" :disabled="!selectedBatchId">
关联学生到批次
</el-button>
<el-button type="success" icon="Select" :disabled="!multiple || ids.length === 0" @click="handleBatchAssociate">
批量关联
</el-button>
<el-button type="warning" icon="Upload" :disabled="!multiple || ids.length === 0" @click="handleBatchSubmit">
批量提交
</el-button>
<el-button type="info" icon="Download" :disabled="!multiple || ids.length === 0" @click="handleBatchWithdraw">
批量撤回
</el-button>
</div>
<div class="toolbar-right">
<el-button type="danger" icon="Download" @click="handleExport">
导出
</el-button>
<el-button type="info" icon="Refresh" @click="handleRefresh">
刷新
</el-button>
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
</div>
</div>
<!-- 表格区域 -->
<div class="table-wrapper">
<el-table
v-loading="loading"
:data="studentList"
@selection-change="handleSelectionChange"
@sort-change="handleSortChange"
row-key="id"
border
stripe
size="small"
>
<el-table-column type="selection" width="50" align="center" />
<el-table-column label="序号" type="index" width="60" align="center" />
<el-table-column label="学生姓名" prop="name" width="100" align="center" show-overflow-tooltip />
<el-table-column label="学号" prop="studentNo" width="120" align="center" show-overflow-tooltip />
<el-table-column label="班级" prop="className" width="120" align="center" show-overflow-tooltip />
<el-table-column label="学院" prop="collegeName" width="120" align="center" show-overflow-tooltip />
<el-table-column label="专业" prop="majorName" width="120" align="center" show-overflow-tooltip />
<el-table-column label="联系电话" prop="phone" width="120" align="center" show-overflow-tooltip />
<el-table-column label="提交状态" prop="submissionStatus" width="100" align="center">
<template #default="scope">
<el-tag :type="getSubmissionStatusTagType(scope.row.submissionStatus)" size="small">
{{ getSubmissionStatusText(scope.row.submissionStatus) }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="提交时间" prop="submissionTime" width="160" align="center" sortable="custom">
<template #default="scope">
<span>{{ scope.row.submissionTime ? proxy.parseTime(scope.row.submissionTime, '{y}-{m}-{d} {h}:{i}') : '-' }}</span>
</template>
</el-table-column>
<el-table-column label="审核状态" prop="approvalStatus" width="100" align="center">
<template #default="scope">
<el-tag :type="getApprovalStatusTagType(scope.row.approvalStatus)" size="small">
{{ getApprovalStatusText(scope.row.approvalStatus) }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" align="center" width="200" fixed="right" class-name="small-padding fixed-width">
<template #default="scope">
<div class="action-buttons">
<el-tooltip content="查看详情" placement="top">
<el-button link type="info" icon="View" size="small" @click="handleView(scope.row)" />
</el-tooltip>
<el-tooltip content="编辑" placement="top">
<el-button link type="primary" icon="Edit" size="small" @click="handleEdit(scope.row)" />
</el-tooltip>
<el-tooltip content="查看进度" placement="top">
<el-button link type="warning" icon="DataAnalysis" size="small" @click="handleProgress(scope.row)" />
</el-tooltip>
<template v-if="scope.row.submissionStatus === 'DRAFT'">
<el-tooltip content="提交" placement="top">
<el-button link type="success" icon="Upload" size="small" @click="handleSubmit(scope.row)" />
</el-tooltip>
</template>
<template v-else-if="scope.row.submissionStatus === 'SUBMITTED'">
<el-tooltip content="撤回" placement="top">
<el-button link type="danger" icon="Download" size="small" @click="handleWithdraw(scope.row)" />
</el-tooltip>
</template>
</div>
</template>
</el-table-column>
</el-table>
<div class="pagination-container">
<pagination v-show="total > 0" :total="total" :page="queryParams.pageNum" :limit="queryParams.pageSize" @pagination="getList" />
</div>
</div>
</el-card>
<!-- 关联学生到批次对话框 -->
<el-dialog title="关联学生到批次" v-model="associateDialog.visible" width="600px" append-to-body>
<el-form ref="associateFormRef" :model="associateForm" :rules="associateRules" label-width="100px">
<el-form-item label="选择批次" prop="batchId">
<el-select v-model="associateForm.batchId" placeholder="请选择批次" style="width: 100%">
<el-option v-for="batch in batchOptions" :key="batch.id" :label="batch.batchName" :value="batch.id" />
</el-select>
</el-form-item>
<el-form-item label="选择学生" prop="studentIds">
<el-select v-model="associateForm.studentIds" multiple placeholder="请选择学生" style="width: 100%">
<el-option v-for="student in allStudents" :key="student.id" :label="`${student.name}(${student.studentNo})`" :value="student.id" />
</el-select>
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="associateDialog.visible = false">取消</el-button>
<el-button type="primary" @click="submitAssociate">确定</el-button>
</div>
</template>
</el-dialog>
<!-- 学生进度对话框 -->
<el-dialog title="学生批次进度" v-model="progressDialog.visible" width="800px" append-to-body>
<div v-if="progressInfo" class="progress-content">
<el-descriptions :column="2" border>
<el-descriptions-item label="学生姓名">{{ progressInfo.studentName }}</el-descriptions-item>
<el-descriptions-item label="学号">{{ progressInfo.studentNo }}</el-descriptions-item>
<el-descriptions-item label="批次名称">{{ progressInfo.batchName }}</el-descriptions-item>
<el-descriptions-item label="提交状态">
<el-tag :type="getSubmissionStatusTagType(progressInfo.submissionStatus)">
{{ getSubmissionStatusText(progressInfo.submissionStatus) }}
</el-tag>
</el-descriptions-item>
<el-descriptions-item label="审核状态">
<el-tag :type="getApprovalStatusTagType(progressInfo.approvalStatus)">
{{ getApprovalStatusText(progressInfo.approvalStatus) }}
</el-tag>
</el-descriptions-item>
<el-descriptions-item label="提交时间">
{{ progressInfo.submissionTime ? proxy.parseTime(progressInfo.submissionTime, '{y}-{m}-{d} {h}:{i}:{s}') : '-' }}
</el-descriptions-item>
<el-descriptions-item label="审核时间">
{{ progressInfo.approvalTime ? proxy.parseTime(progressInfo.approvalTime, '{y}-{m}-{d} {h}:{i}:{s}') : '-' }}
</el-descriptions-item>
<el-descriptions-item label="审核意见" :span="2">
{{ progressInfo.approvalComments || '-' }}
</el-descriptions-item>
</el-descriptions>
</div>
</el-dialog>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted, getCurrentInstance } from 'vue';
import { ElMessage, ElMessageBox } from 'element-plus';
import { listAllCollectionBatch } from '@/api/xg-xx/collectionBatch';
import {
getStudentsByBatch,
associateStudentToBatch,
batchAssociateStudentsToBatch,
submitStudentInfo,
withdrawStudentInfo,
getStudentBatchProgress,
batchAuditStudentInfo,
checkEditPermission,
getStudentBatchStatistics,
batchSubmitStudentInfo,
batchWithdrawStudentInfo,
getAllStudentOptions
} from '@/api/xg-xx/studentInfo';
const { proxy } = getCurrentInstance() as any;
// 响应式数据
const loading = ref(false);
const showSearch = ref(true);
const ids = ref<number[]>([]);
const single = ref(true);
const multiple = ref(true);
const total = ref(0);
const studentList = ref<any[]>([]);
const batchOptions = ref<any[]>([]);
const allStudents = ref<any[]>([]);
const selectedBatchId = ref<number>();
const currentBatch = ref<any>();
// 批次统计信息
const batchStatistics = reactive({
totalCount: 0,
submittedCount: 0,
approvedCount: 0,
rejectedCount: 0,
completionRate: 0
});
// 查询参数
const queryParams = reactive({
pageNum: 1,
pageSize: 10,
batchId: undefined as number | undefined,
name: undefined,
studentNo: undefined,
className: undefined,
submissionStatus: undefined,
collegeName: undefined
});
// 关联对话框
const associateDialog = reactive({
visible: false
});
const associateForm = reactive({
batchId: undefined as number | undefined,
studentIds: [] as number[]
});
const associateRules = {
batchId: [{ required: true, message: '请选择批次', trigger: 'change' }],
studentIds: [{ required: true, message: '请选择学生', trigger: 'change' }]
};
// 进度对话框
const progressDialog = reactive({
visible: false
});
const progressInfo = ref<any>();
// 获取列表数据
const getList = async () => {
if (!selectedBatchId.value) {
studentList.value = [];
total.value = 0;
return;
}
loading.value = true;
try {
const response = await getStudentsByBatch(selectedBatchId.value, queryParams);
studentList.value = response.rows;
total.value = response.total;
} catch (error) {
console.error('获取列表失败:', error);
ElMessage.error('获取学生列表失败');
} finally {
loading.value = false;
}
};
// 获取批次选项
const getBatchOptions = async () => {
try {
const response = await listAllCollectionBatch({});
batchOptions.value = response.data;
} catch (error) {
console.error('获取批次选项失败:', error);
ElMessage.error('获取批次选项失败');
}
};
// 获取所有学生选项
const getAllStudentsList = async () => {
try {
const response = await getAllStudentOptions();
allStudents.value = response.data;
} catch (error) {
console.error('获取学生选项失败:', error);
ElMessage.error('获取学生选项失败');
}
};
// 批次改变处理
const handleBatchChange = (batchId: number) => {
selectedBatchId.value = batchId;
queryParams.batchId = batchId;
currentBatch.value = batchOptions.value.find(batch => batch.id === batchId);
// 获取批次统计信息
getBatchStatistics();
// 刷新列表
getList();
};
// 获取批次统计信息
const getBatchStatistics = async () => {
if (!selectedBatchId.value) return;
try {
const response = await getStudentBatchStatistics(selectedBatchId.value);
Object.assign(batchStatistics, response.data);
} catch (error) {
console.error('获取批次统计失败:', error);
ElMessage.error('获取批次统计失败');
}
};
// 刷新批次信息
const refreshBatchInfo = () => {
getBatchStatistics();
getList();
};
// 搜索
const handleQuery = () => {
queryParams.pageNum = 1;
getList();
};
// 重置搜索
const resetQuery = () => {
proxy.resetForm('queryFormRef');
handleQuery();
};
// 多选框选中数据
const handleSelectionChange = (selection: any[]) => {
ids.value = selection.map(item => item.id);
single.value = selection.length !== 1;
multiple.value = !selection.length;
};
// 排序变化
const handleSortChange = (column: any) => {
getList();
};
// 关联学生到批次
const handleAssociate = () => {
associateDialog.visible = true;
associateForm.batchId = selectedBatchId.value;
associateForm.studentIds = [];
// 获取所有学生选项
getAllStudentsList();
};
// 批量关联
const handleBatchAssociate = () => {
if (ids.value.length === 0) {
ElMessage.warning('请选择要关联的学生');
return;
}
associateDialog.visible = true;
associateForm.batchId = selectedBatchId.value;
associateForm.studentIds = ids.value;
// 获取所有学生选项
getAllStudentsList();
};
// 提交关联
const submitAssociate = async () => {
if (!associateForm.batchId || associateForm.studentIds.length === 0) {
ElMessage.warning('请选择批次和学生');
return;
}
try {
await batchAssociateStudentsToBatch(associateForm.studentIds, associateForm.batchId);
ElMessage.success('关联成功');
associateDialog.visible = false;
// 刷新统计信息和列表
getBatchStatistics();
getList();
} catch (error) {
console.error('关联失败:', error);
ElMessage.error('关联失败');
}
};
// 批量提交
const handleBatchSubmit = async () => {
if (ids.value.length === 0) {
ElMessage.warning('请选择要提交的学生');
return;
}
if (!selectedBatchId.value) {
ElMessage.warning('请先选择批次');
return;
}
try {
await ElMessageBox.confirm('确定要批量提交选中的学生信息吗?', '系统提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
});
await batchSubmitStudentInfo(ids.value, selectedBatchId.value);
ElMessage.success('批量提交成功');
// 刷新统计信息和列表
getBatchStatistics();
getList();
} catch (error) {
if (error !== 'cancel') {
console.error('批量提交失败:', error);
ElMessage.error('批量提交失败');
}
}
};
// 批量撤回
const handleBatchWithdraw = async () => {
if (ids.value.length === 0) {
ElMessage.warning('请选择要撤回的学生');
return;
}
if (!selectedBatchId.value) {
ElMessage.warning('请先选择批次');
return;
}
try {
await ElMessageBox.confirm('确定要批量撤回选中的学生信息吗?', '系统提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
});
await batchWithdrawStudentInfo(ids.value, selectedBatchId.value);
ElMessage.success('批量撤回成功');
// 刷新统计信息和列表
getBatchStatistics();
getList();
} catch (error) {
if (error !== 'cancel') {
console.error('批量撤回失败:', error);
ElMessage.error('批量撤回失败');
}
}
};
// 查看详情
const handleView = (row: any) => {
proxy.$router.push(`/xg-xx/studentInfo/detail/${row.id}`);
};
// 编辑
const handleEdit = (row: any) => {
proxy.$router.push(`/xg-xx/studentInfo/edit/${row.id}`);
};
// 提交
const handleSubmit = async (row: any) => {
if (!selectedBatchId.value) {
ElMessage.warning('请先选择批次');
return;
}
try {
await ElMessageBox.confirm('确定要提交该学生信息吗?', '系统提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
});
await submitStudentInfo(row.id, selectedBatchId.value);
ElMessage.success('提交成功');
// 刷新统计信息和列表
getBatchStatistics();
getList();
} catch (error) {
if (error !== 'cancel') {
console.error('提交失败:', error);
ElMessage.error('提交失败');
}
}
};
// 撤回
const handleWithdraw = async (row: any) => {
if (!selectedBatchId.value) {
ElMessage.warning('请先选择批次');
return;
}
try {
await ElMessageBox.confirm('确定要撤回该学生信息吗?', '系统提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
});
await withdrawStudentInfo(row.id, selectedBatchId.value);
ElMessage.success('撤回成功');
// 刷新统计信息和列表
getBatchStatistics();
getList();
} catch (error) {
if (error !== 'cancel') {
console.error('撤回失败:', error);
ElMessage.error('撤回失败');
}
}
};
// 查看进度
const handleProgress = async (row: any) => {
if (!selectedBatchId.value) {
ElMessage.warning('请先选择批次');
return;
}
try {
const response = await getStudentBatchProgress(row.id, selectedBatchId.value);
progressInfo.value = response.data;
progressDialog.visible = true;
} catch (error) {
console.error('获取进度失败:', error);
ElMessage.error('获取进度失败');
}
};
// 导出
const handleExport = () => {
proxy?.download(
'xg/studentInfo/export',
queryParams,
`学生信息_${proxy.parseTime(new Date(), '{y}{m}{d}{h}{i}{s}')}.xlsx`
);
};
// 刷新
const handleRefresh = () => {
getList();
};
// 获取批次状态文本
const getBatchStatusText = (status: string) => {
const statusMap: Record<string, string> = {
'DRAFT': '草稿',
'ACTIVE': '进行中',
'COMPLETED': '已完成',
'CANCELLED': '已取消'
};
return statusMap[status] || '未知';
};
// 获取提交状态标签类型
const getSubmissionStatusTagType = (status: string): 'success' | 'warning' | 'info' | 'primary' | 'danger' => {
const statusMap: Record<string, 'success' | 'warning' | 'info' | 'primary' | 'danger'> = {
'DRAFT': 'info',
'SUBMITTED': 'warning',
'APPROVED': 'success',
'REJECTED': 'danger'
};
return statusMap[status] || 'info';
};
// 获取提交状态文本
const getSubmissionStatusText = (status: string) => {
const statusMap: Record<string, string> = {
'DRAFT': '草稿',
'SUBMITTED': '已提交',
'APPROVED': '已通过',
'REJECTED': '已拒绝'
};
return statusMap[status] || '未知';
};
// 获取审核状态标签类型
const getApprovalStatusTagType = (status: string): 'success' | 'warning' | 'info' | 'primary' | 'danger' => {
const statusMap: Record<string, 'success' | 'warning' | 'info' | 'primary' | 'danger'> = {
'PENDING': 'warning',
'APPROVED': 'success',
'REJECTED': 'danger'
};
return statusMap[status] || 'info';
};
// 获取审核状态文本
const getApprovalStatusText = (status: string) => {
const statusMap: Record<string, string> = {
'PENDING': '待审核',
'APPROVED': '已通过',
'REJECTED': '已拒绝'
};
return statusMap[status] || '未知';
};
// 初始化
onMounted(() => {
getBatchOptions();
});
</script>
<style scoped>
.page-container {
background-color: #f5f5f5;
min-height: 100vh;
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
}
.header-actions {
display: flex;
align-items: center;
}
.batch-info {
padding: 16px 0;
}
.toolbar-wrapper {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
}
.toolbar-left {
display: flex;
align-items: center;
gap: 8px;
}
.toolbar-right {
display: flex;
align-items: center;
gap: 8px;
}
.table-wrapper {
margin-top: 16px;
}
.action-buttons {
display: flex;
gap: 4px;
justify-content: center;
}
.pagination-container {
display: flex;
justify-content: center;
margin-top: 16px;
}
.progress-info {
padding: 8px 0;
}
.progress-content {
padding: 16px 0;
}
.mt-4 {
margin-top: 16px;
}
</style>
@@ -1,779 +0,0 @@
<template>
<div class="batch-management-container">
<!-- 搜索区域 -->
<el-card shadow="hover" class="mb-4">
<el-form ref="queryFormRef" :model="queryParams" label-width="100px">
<el-row :gutter="20">
<el-col :span="6">
<el-form-item label="批次名称" prop="batchName">
<el-input v-model="queryParams.batchName" placeholder="请输入批次名称" clearable @keyup.enter="handleQuery" />
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="批次类型" prop="batchType">
<el-select v-model="queryParams.batchType" placeholder="请选择批次类型" clearable style="width: 100%">
<el-option label="常规批次" value="regular" />
<el-option label="专项批次" value="special" />
<el-option label="紧急批次" value="urgent" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="批次状态" prop="status">
<el-select v-model="queryParams.status" placeholder="请选择批次状态" clearable style="width: 100%">
<el-option label="草稿" value="DRAFT" />
<el-option label="活跃" value="ACTIVE" />
<el-option label="已完成" value="COMPLETED" />
<el-option label="已取消" value="CANCELLED" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-col>
</el-row>
</el-form>
</el-card>
<!-- 主要内容区域 -->
<el-card shadow="hover">
<!-- 操作按钮区域 -->
<div class="toolbar-wrapper">
<div class="toolbar-left">
<el-button type="primary" icon="Plus" @click="handleAdd">
新增批次
</el-button>
<el-button type="success" icon="Upload" :disabled="!multiple || ids.length === 0" @click="handleBatchPublish">
批量发布
</el-button>
<el-button type="warning" icon="Download" :disabled="!multiple || ids.length === 0" @click="handleBatchUnpublish">
批量取消发布
</el-button>
<el-button type="danger" icon="Delete" :disabled="!multiple || ids.length === 0" @click="handleDelete">
批量删除
</el-button>
</div>
<div class="toolbar-right">
<el-button type="primary" icon="Download" @click="handleExport">
导出
</el-button>
<el-button type="info" icon="Refresh" @click="handleRefresh">
刷新
</el-button>
</div>
</div>
<!-- 表格区域 -->
<div class="table-wrapper">
<el-table
v-loading="loading"
:data="batchList"
@selection-change="handleSelectionChange"
@sort-change="handleSortChange"
row-key="id"
border
stripe
size="small"
>
<el-table-column type="selection" width="50" align="center" />
<el-table-column label="序号" type="index" width="60" align="center" />
<el-table-column label="批次名称" prop="batchName" width="150" align="center" show-overflow-tooltip />
<el-table-column label="批次编码" prop="batchCode" width="120" align="center" show-overflow-tooltip />
<el-table-column label="批次类型" prop="batchType" width="100" align="center">
<template #default="scope">
<el-tag :type="getBatchTypeTagType(scope.row.batchType)" size="small">
{{ getBatchTypeText(scope.row.batchType) }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="状态" prop="status" width="100" align="center">
<template #default="scope">
<el-tag :type="getStatusTagType(scope.row.status)" size="small">
{{ getStatusText(scope.row.status) }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="发布状态" prop="isPublished" width="100" align="center">
<template #default="scope">
<el-tag :type="scope.row.isPublished ? 'success' : 'info'" size="small">
{{ scope.row.isPublished ? '已发布' : '未发布' }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="开始时间" prop="startTime" width="160" align="center" sortable="custom">
<template #default="scope">
<span>{{ scope.row.startTime ? parseTime(scope.row.startTime, '{y}-{m}-{d} {h}:{i}') : '-' }}</span>
</template>
</el-table-column>
<el-table-column label="结束时间" prop="endTime" width="160" align="center" sortable="custom">
<template #default="scope">
<span>{{ scope.row.endTime ? parseTime(scope.row.endTime, '{y}-{m}-{d} {h}:{i}') : '-' }}</span>
</template>
</el-table-column>
<el-table-column label="总人数" prop="totalCount" width="80" align="center" />
<el-table-column label="完成率" prop="completionRate" width="100" align="center">
<template #default="scope">
<el-progress :percentage="scope.row.completionRate" :stroke-width="6" :show-text="false" />
<span class="ml-2">{{ scope.row.completionRate }}%</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" width="200" fixed="right" class-name="small-padding fixed-width">
<template #default="scope">
<div class="action-buttons">
<el-tooltip content="查看详情" placement="top">
<el-button link type="info" icon="View" size="small" @click="handleView(scope.row)" />
</el-tooltip>
<el-tooltip content="编辑" placement="top">
<el-button link type="primary" icon="Edit" size="small" @click="handleEdit(scope.row)" />
</el-tooltip>
<el-tooltip content="统计" placement="top">
<el-button link type="warning" icon="DataAnalysis" size="small" @click="handleStatistics(scope.row)" />
</el-tooltip>
<template v-if="!scope.row.isPublished">
<el-tooltip content="发布" placement="top">
<el-button link type="success" icon="Upload" size="small" @click="handlePublish(scope.row)" />
</el-tooltip>
</template>
<template v-else>
<el-tooltip content="取消发布" placement="top">
<el-button link type="danger" icon="Download" size="small" @click="handleUnpublish(scope.row)" />
</el-tooltip>
</template>
<el-tooltip content="复制" placement="top">
<el-button link type="info" icon="CopyDocument" size="small" @click="handleCopy(scope.row)" />
</el-tooltip>
<el-tooltip content="删除" placement="top">
<el-button link type="danger" icon="Delete" size="small" @click="handleDelete(scope.row)" />
</el-tooltip>
</div>
</template>
</el-table-column>
</el-table>
<div class="pagination-container">
<el-pagination
v-show="total > 0"
:total="total"
:page-size="queryParams.pageSize"
:current-page="queryParams.pageNum"
layout="total, sizes, prev, pager, next, jumper"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
/>
</div>
</div>
</el-card>
<!-- 批次详情对话框 -->
<el-dialog
:title="dialogTitle"
v-model="batchDialog.visible"
width="800px"
append-to-body
>
<el-form ref="batchFormRef" :model="batchForm" :rules="batchRules" label-width="120px">
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="批次名称" prop="batchName">
<el-input v-model="batchForm.batchName" placeholder="请输入批次名称" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="批次编码" prop="batchCode">
<el-input v-model="batchForm.batchCode" placeholder="请输入批次编码" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="批次类型" prop="batchType">
<el-select v-model="batchForm.batchType" placeholder="请选择批次类型" style="width: 100%">
<el-option label="常规批次" value="regular" />
<el-option label="专项批次" value="special" />
<el-option label="紧急批次" value="urgent" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="目标范围" prop="targetScope">
<el-select v-model="batchForm.targetScope" placeholder="请选择目标范围" style="width: 100%">
<el-option label="全部学生" value="all" />
<el-option label="按学院" value="college" />
<el-option label="按班级" value="class" />
<el-option label="按学生" value="student" />
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="开始时间" prop="startTime">
<el-date-picker
v-model="batchForm.startTime"
type="datetime"
placeholder="请选择开始时间"
format="YYYY-MM-DD HH:mm"
value-format="YYYY-MM-DD HH:mm:ss"
style="width: 100%"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="结束时间" prop="endTime">
<el-date-picker
v-model="batchForm.endTime"
type="datetime"
placeholder="请选择结束时间"
format="YYYY-MM-DD HH:mm"
value-format="YYYY-MM-DD HH:mm:ss"
style="width: 100%"
/>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="自动提醒" prop="autoRemind">
<el-switch v-model="batchForm.autoRemind" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="提醒天数" prop="remindDays">
<el-input-number v-model="batchForm.remindDays" :min="1" :max="30" placeholder="结束前几天提醒" />
</el-form-item>
</el-col>
</el-row>
<el-form-item label="批次描述" prop="description">
<el-input v-model="batchForm.description" type="textarea" :rows="3" placeholder="请输入批次描述" />
</el-form-item>
<el-form-item label="通知内容" prop="noticeContent">
<el-input v-model="batchForm.noticeContent" type="textarea" :rows="4" placeholder="请输入通知内容" />
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="batchDialog.visible = false">取消</el-button>
<el-button type="primary" @click="submitBatch">确定</el-button>
</div>
</template>
</el-dialog>
<!-- 批次统计对话框 -->
<el-dialog title="批次统计" v-model="statisticsDialog.visible" width="900px" append-to-body>
<div v-if="statisticsInfo" class="statistics-content">
<el-row :gutter="20" class="mb-4">
<el-col :span="6">
<el-statistic title="总人数" :value="statisticsInfo.totalCount" />
</el-col>
<el-col :span="6">
<el-statistic title="已提交" :value="statisticsInfo.submittedCount" />
</el-col>
<el-col :span="6">
<el-statistic title="已通过" :value="statisticsInfo.approvedCount" />
</el-col>
<el-col :span="6">
<el-statistic title="完成率" :value="statisticsInfo.completionRate" suffix="%" />
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-progress :percentage="statisticsInfo.completionRate" :stroke-width="12" />
</el-col>
<el-col :span="12">
<div class="statistics-chart">
<!-- 这里可以集成图表组件 -->
<p>统计图表区域</p>
</div>
</el-col>
</el-row>
</div>
</el-dialog>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted, getCurrentInstance, computed } from 'vue';
import { ElMessage, ElMessageBox } from 'element-plus';
import { parseTime } from '@/utils/ruoyi';
import {
listCollectionBatch,
getCollectionBatch,
addCollectionBatch,
updateCollectionBatch,
delCollectionBatch,
publishCollectionBatch,
unpublishCollectionBatch,
getCollectionBatchStatistics,
copyCollectionBatch
} from '@/api/xg-xx/collectionBatch';
const { proxy } = getCurrentInstance() as any;
// 定义 emit
const emit = defineEmits(['batch-created', 'batch-updated', 'batch-deleted']);
// 响应式数据
const loading = ref(false);
const ids = ref<number[]>([]);
const single = ref(true);
const multiple = ref(true);
const total = ref(0);
const batchList = ref<any[]>([]);
// 查询参数
const queryParams = reactive({
pageNum: 1,
pageSize: 10,
batchName: undefined,
batchType: undefined,
status: undefined
});
// 批次对话框
const batchDialog = reactive({
visible: false,
type: 'add'
});
const batchForm = reactive({
id: undefined,
batchName: '',
batchCode: '',
batchType: 'regular',
targetScope: 'all',
startTime: '',
endTime: '',
autoRemind: true,
remindDays: 3,
description: '',
noticeContent: ''
});
const batchRules = {
batchName: [{ required: true, message: '请输入批次名称', trigger: 'blur' }],
batchCode: [{ required: true, message: '请输入批次编码', trigger: 'blur' }],
batchType: [{ required: true, message: '请选择批次类型', trigger: 'change' }],
targetScope: [{ required: true, message: '请选择目标范围', trigger: 'change' }],
startTime: [{ required: true, message: '请选择开始时间', trigger: 'change' }],
endTime: [{ required: true, message: '请选择结束时间', trigger: 'change' }]
};
// 统计对话框
const statisticsDialog = reactive({
visible: false
});
const statisticsInfo = ref<any>();
// 计算属性
const dialogTitle = computed(() => {
return batchDialog.type === 'add' ? '新增批次' : '编辑批次';
});
// 获取列表数据
const getList = async () => {
loading.value = true;
try {
const response = await listCollectionBatch(queryParams);
batchList.value = response.rows;
total.value = response.total;
} catch (error) {
console.error('获取批次列表失败:', error);
ElMessage.error('获取批次列表失败');
} finally {
loading.value = false;
}
};
// 搜索
const handleQuery = () => {
queryParams.pageNum = 1;
getList();
};
// 重置搜索
const resetQuery = () => {
proxy.resetForm('queryFormRef');
handleQuery();
};
// 多选框选中数据
const handleSelectionChange = (selection: any[]) => {
ids.value = selection.map(item => item.id);
single.value = selection.length !== 1;
multiple.value = !selection.length;
};
// 排序变化
const handleSortChange = (column: any) => {
getList();
};
// 分页大小变化
const handleSizeChange = (val: number) => {
queryParams.pageSize = val;
getList();
};
// 当前页变化
const handleCurrentChange = (val: number) => {
queryParams.pageNum = val;
getList();
};
// 新增批次
const handleAdd = () => {
resetBatchForm();
batchDialog.type = 'add';
batchDialog.visible = true;
};
// 编辑批次
const handleEdit = async (row: any) => {
try {
const response = await getCollectionBatch(row.id);
Object.assign(batchForm, response.data);
batchDialog.type = 'edit';
batchDialog.visible = true;
} catch (error) {
console.error('获取批次详情失败:', error);
ElMessage.error('获取批次详情失败');
}
};
// 查看详情
const handleView = async (row: any) => {
try {
const response = await getCollectionBatch(row.id);
Object.assign(batchForm, response.data);
batchDialog.type = 'view';
batchDialog.visible = true;
} catch (error) {
console.error('获取批次详情失败:', error);
ElMessage.error('获取批次详情失败');
}
};
// 批次统计
const handleStatistics = async (row: any) => {
try {
const response = await getCollectionBatchStatistics(row.id);
statisticsInfo.value = response.data;
statisticsDialog.visible = true;
} catch (error) {
console.error('获取批次统计失败:', error);
ElMessage.error('获取批次统计失败');
}
};
// 发布批次
const handlePublish = async (row: any) => {
try {
await ElMessageBox.confirm('确定要发布该批次吗?', '系统提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
});
await publishCollectionBatch(row.id);
ElMessage.success('发布成功');
getList();
emit('batch-updated');
} catch (error) {
if (error !== 'cancel') {
console.error('发布失败:', error);
ElMessage.error('发布失败');
}
}
};
// 取消发布
const handleUnpublish = async (row: any) => {
try {
await ElMessageBox.confirm('确定要取消发布该批次吗?', '系统提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
});
await unpublishCollectionBatch(row.id);
ElMessage.success('取消发布成功');
getList();
emit('batch-updated');
} catch (error) {
if (error !== 'cancel') {
console.error('取消发布失败:', error);
ElMessage.error('取消发布失败');
}
}
};
// 复制批次
const handleCopy = async (row: any) => {
try {
await ElMessageBox.confirm('确定要复制该批次吗?', '系统提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'info'
});
await copyCollectionBatch(row.id, {
batchName: row.batchName + '_副本',
batchCode: row.batchCode + '_copy'
});
ElMessage.success('复制成功');
getList();
emit('batch-created');
} catch (error) {
if (error !== 'cancel') {
console.error('复制失败:', error);
ElMessage.error('复制失败');
}
}
};
// 批量发布
const handleBatchPublish = async () => {
if (ids.value.length === 0) {
ElMessage.warning('请选择要发布的批次');
return;
}
try {
await ElMessageBox.confirm('确定要批量发布选中的批次吗?', '系统提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
});
// 批量发布逻辑
for (const id of ids.value) {
await publishCollectionBatch(id);
}
ElMessage.success('批量发布成功');
getList();
emit('batch-updated');
} catch (error) {
if (error !== 'cancel') {
console.error('批量发布失败:', error);
ElMessage.error('批量发布失败');
}
}
};
// 批量取消发布
const handleBatchUnpublish = async () => {
if (ids.value.length === 0) {
ElMessage.warning('请选择要取消发布的批次');
return;
}
try {
await ElMessageBox.confirm('确定要批量取消发布选中的批次吗?', '系统提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
});
// 批量取消发布逻辑
for (const id of ids.value) {
await unpublishCollectionBatch(id);
}
ElMessage.success('批量取消发布成功');
getList();
emit('batch-updated');
} catch (error) {
if (error !== 'cancel') {
console.error('批量取消发布失败:', error);
ElMessage.error('批量取消发布失败');
}
}
};
// 删除批次
const handleDelete = async (row?: any) => {
const batchIds = row ? [row.id] : ids.value;
if (batchIds.length === 0) {
ElMessage.warning('请选择要删除的批次');
return;
}
try {
await ElMessageBox.confirm('确定要删除选中的批次吗?', '系统提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
});
await delCollectionBatch(batchIds);
ElMessage.success('删除成功');
getList();
emit('batch-deleted');
} catch (error) {
if (error !== 'cancel') {
console.error('删除失败:', error);
ElMessage.error('删除失败');
}
}
};
// 导出
const handleExport = () => {
proxy?.download(
'xg/collectionBatch/export',
queryParams,
`批次信息_${parseTime(new Date(), '{y}{m}{d}{h}{i}{s}')}.xlsx`
);
};
// 刷新
const handleRefresh = () => {
getList();
};
// 提交批次
const submitBatch = async () => {
try {
const valid = await proxy.$refs.batchFormRef.validate();
if (!valid) return;
if (batchDialog.type === 'add') {
await addCollectionBatch(batchForm);
ElMessage.success('新增成功');
emit('batch-created');
} else {
await updateCollectionBatch(batchForm);
ElMessage.success('修改成功');
emit('batch-updated');
}
batchDialog.visible = false;
getList();
} catch (error) {
console.error('提交失败:', error);
ElMessage.error('提交失败');
}
};
// 重置表单
const resetBatchForm = () => {
Object.assign(batchForm, {
id: undefined,
batchName: '',
batchCode: '',
batchType: 'regular',
targetScope: 'all',
startTime: '',
endTime: '',
autoRemind: true,
remindDays: 3,
description: '',
noticeContent: ''
});
};
// 获取批次类型标签类型
const getBatchTypeTagType = (type: string) => {
const typeMap: Record<string, string> = {
'regular': '',
'special': 'warning',
'urgent': 'danger'
};
return typeMap[type] || '';
};
// 获取批次类型文本
const getBatchTypeText = (type: string) => {
const typeMap: Record<string, string> = {
'regular': '常规',
'special': '专项',
'urgent': '紧急'
};
return typeMap[type] || '未知';
};
// 获取状态标签类型
const getStatusTagType = (status: string) => {
const statusMap: Record<string, string> = {
'DRAFT': 'info',
'ACTIVE': 'success',
'COMPLETED': 'warning',
'CANCELLED': 'danger'
};
return statusMap[status] || '';
};
// 获取状态文本
const getStatusText = (status: string) => {
const statusMap: Record<string, string> = {
'DRAFT': '草稿',
'ACTIVE': '活跃',
'COMPLETED': '已完成',
'CANCELLED': '已取消'
};
return statusMap[status] || '未知';
};
// 初始化
onMounted(() => {
getList();
});
</script>
<style scoped>
.batch-management-container {
min-height: 100vh;
}
.toolbar-wrapper {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
}
.toolbar-left {
display: flex;
gap: 8px;
}
.toolbar-right {
display: flex;
gap: 8px;
}
.table-wrapper {
margin-top: 16px;
}
.pagination-container {
display: flex;
justify-content: center;
margin-top: 16px;
}
.action-buttons {
display: flex;
gap: 4px;
}
.statistics-content {
padding: 16px;
}
.statistics-chart {
display: flex;
justify-content: center;
align-items: center;
height: 200px;
background: #f5f5f5;
border-radius: 4px;
}
</style>
-925
View File
@@ -1,925 +0,0 @@
<template>
<div class="p-2 page-container">
<!-- 页面标题 -->
<div class="page-header mb-4">
<h2 class="page-title">学生信息管理</h2>
<p class="page-description">管理学生基本信息、信息采集批次以及审核流程</p>
</div>
<!-- 标签页结构 -->
<el-tabs v-model="activeTab" type="card" class="main-tabs">
<!-- 学生信息列表标签页 -->
<el-tab-pane label="学生信息列表" name="studentList">
<div class="tab-content">
<!-- 批次选择区域 -->
<el-card shadow="hover" class="mb-4">
<template #header>
<div class="card-header">
<span>批次选择</span>
<div class="header-actions">
<el-select v-model="selectedBatchId" placeholder="选择批次" @change="handleBatchChange" style="width: 200px; margin-right: 10px;">
<el-option label="全部学生" :value="null" />
<el-option v-for="batch in batchOptions" :key="batch.id" :label="batch.batchName" :value="batch.id" />
</el-select>
<el-button type="primary" icon="Refresh" @click="refreshBatchInfo">刷新</el-button>
</div>
</div>
</template>
<!-- 批次统计信息 -->
<div v-if="currentBatch" class="batch-info">
<el-row :gutter="20">
<el-col :span="6">
<el-statistic title="批次名称" :value="currentBatch.batchName" />
</el-col>
<el-col :span="6">
<el-statistic title="批次状态" :value="getBatchStatusText(currentBatch.status)" />
</el-col>
<el-col :span="6">
<el-statistic title="总学生数" :value="batchStatistics.totalCount" />
</el-col>
<el-col :span="6">
<el-statistic title="已提交数" :value="batchStatistics.submittedCount" />
</el-col>
</el-row>
<el-row :gutter="20" class="mt-4">
<el-col :span="6">
<el-statistic title="已通过数" :value="batchStatistics.approvedCount" />
</el-col>
<el-col :span="6">
<el-statistic title="已拒绝数" :value="batchStatistics.rejectedCount" />
</el-col>
<el-col :span="6">
<el-statistic title="完成率" :value="batchStatistics.completionRate" suffix="%" />
</el-col>
<el-col :span="6">
<div class="progress-info">
<el-progress :percentage="batchStatistics.completionRate" :stroke-width="8" />
</div>
</el-col>
</el-row>
</div>
</el-card>
<!-- 搜索区域 -->
<el-card shadow="hover" class="mb-4">
<el-form ref="queryFormRef" :model="queryParams" label-width="100px">
<el-row :gutter="20">
<el-col :span="6">
<el-form-item label="学生姓名" prop="name">
<el-input v-model="queryParams.name" placeholder="请输入学生姓名" clearable @keyup.enter="handleQuery" />
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="学号" prop="studentNo">
<el-input v-model="queryParams.studentNo" placeholder="请输入学号" clearable @keyup.enter="handleQuery" />
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="班级" prop="className">
<el-input v-model="queryParams.className" placeholder="请输入班级名称" clearable @keyup.enter="handleQuery" />
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="提交状态" prop="submissionStatus">
<el-select v-model="queryParams.submissionStatus" placeholder="请选择提交状态" clearable style="width: 100%">
<el-option label="草稿" value="DRAFT" />
<el-option label="已提交" value="SUBMITTED" />
<el-option label="已通过" value="APPROVED" />
<el-option label="已拒绝" value="REJECTED" />
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="6">
<el-form-item label="学院" prop="collegeName">
<el-input v-model="queryParams.collegeName" placeholder="请输入学院名称" clearable @keyup.enter="handleQuery" />
</el-form-item>
</el-col>
<el-col :span="18">
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-col>
</el-row>
</el-form>
</el-card>
<!-- 主要内容区域 -->
<el-card shadow="hover">
<!-- 操作按钮区域 -->
<div class="toolbar-wrapper">
<div class="toolbar-left">
<el-button type="primary" icon="Plus" @click="handleAdd">
新增学生
</el-button>
<el-button type="success" icon="Select" :disabled="!multiple || ids.length === 0" @click="handleBatchAssociate">
批量关联批次
</el-button>
<el-button type="warning" icon="Upload" :disabled="!multiple || ids.length === 0" @click="handleBatchSubmit">
批量提交
</el-button>
<el-button type="info" icon="Download" :disabled="!multiple || ids.length === 0" @click="handleBatchWithdraw">
批量撤回
</el-button>
<el-button type="danger" icon="Delete" :disabled="!multiple || ids.length === 0" @click="handleDelete">
批量删除
</el-button>
</div>
<div class="toolbar-right">
<el-button type="primary" icon="Upload" @click="handleImport">
导入
</el-button>
<el-button type="danger" icon="Download" @click="handleExport">
导出
</el-button>
<el-button type="info" icon="Refresh" @click="handleRefresh">
刷新
</el-button>
</div>
</div>
<!-- 表格区域 -->
<div class="table-wrapper">
<el-table
v-loading="loading"
:data="studentList"
@selection-change="handleSelectionChange"
@sort-change="handleSortChange"
row-key="id"
border
stripe
size="small"
>
<el-table-column type="selection" width="50" align="center" />
<el-table-column label="序号" type="index" width="60" align="center" />
<el-table-column label="学生姓名" prop="name" width="100" align="center" show-overflow-tooltip />
<el-table-column label="学号" prop="studentNo" width="120" align="center" show-overflow-tooltip />
<el-table-column label="班级" prop="className" width="120" align="center" show-overflow-tooltip />
<el-table-column label="学院" prop="collegeName" width="120" align="center" show-overflow-tooltip />
<el-table-column label="专业" prop="majorName" width="120" align="center" show-overflow-tooltip />
<el-table-column label="联系电话" prop="phone" width="120" align="center" show-overflow-tooltip />
<el-table-column label="提交状态" prop="submissionStatus" width="100" align="center">
<template #default="scope">
<el-tag :type="getSubmissionStatusTagType(scope.row.submissionStatus)" size="small">
{{ getSubmissionStatusText(scope.row.submissionStatus) }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="提交时间" prop="submissionTime" width="160" align="center" sortable="custom">
<template #default="scope">
<span>{{ scope.row.submissionTime ? parseTime(scope.row.submissionTime, '{y}-{m}-{d} {h}:{i}') : '-' }}</span>
</template>
</el-table-column>
<el-table-column label="审核状态" prop="approvalStatus" width="100" align="center">
<template #default="scope">
<el-tag :type="getApprovalStatusTagType(scope.row.approvalStatus)" size="small">
{{ getApprovalStatusText(scope.row.approvalStatus) }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" align="center" width="200" fixed="right" class-name="small-padding fixed-width">
<template #default="scope">
<div class="action-buttons">
<el-tooltip content="查看详情" placement="top">
<el-button link type="info" icon="View" size="small" @click="handleView(scope.row)" />
</el-tooltip>
<el-tooltip content="编辑" placement="top">
<el-button link type="primary" icon="Edit" size="small" @click="handleEdit(scope.row)" />
</el-tooltip>
<el-tooltip content="查看进度" placement="top">
<el-button link type="warning" icon="DataAnalysis" size="small" @click="handleProgress(scope.row)" />
</el-tooltip>
<template v-if="scope.row.submissionStatus === 'DRAFT'">
<el-tooltip content="提交" placement="top">
<el-button link type="success" icon="Upload" size="small" @click="handleSubmit(scope.row)" />
</el-tooltip>
</template>
<template v-else-if="scope.row.submissionStatus === 'SUBMITTED'">
<el-tooltip content="撤回" placement="top">
<el-button link type="danger" icon="Download" size="small" @click="handleWithdraw(scope.row)" />
</el-tooltip>
</template>
<el-tooltip content="删除" placement="top">
<el-button link type="danger" icon="Delete" size="small" @click="handleDelete(scope.row)" />
</el-tooltip>
</div>
</template>
</el-table-column>
</el-table>
<div class="pagination-container">
<el-pagination
v-show="total > 0"
:total="total"
:page-size="queryParams.pageSize"
:current-page="queryParams.pageNum"
layout="total, sizes, prev, pager, next, jumper"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
/>
</div>
</div>
</el-card>
</div>
</el-tab-pane>
<!-- 信息采集批次标签页 -->
<el-tab-pane label="信息采集批次" name="batchManagement">
<div class="tab-content">
<!-- 批次管理组件 -->
<BatchManagementComponent
@batch-created="handleBatchCreated"
@batch-updated="handleBatchUpdated"
@batch-deleted="handleBatchDeleted"
/>
</div>
</el-tab-pane>
</el-tabs>
<!-- 关联批次对话框 -->
<el-dialog title="关联学生到批次" v-model="associateDialog.visible" width="600px" append-to-body>
<el-form ref="associateFormRef" :model="associateForm" :rules="associateRules" label-width="100px">
<el-form-item label="选择批次" prop="batchId">
<el-select v-model="associateForm.batchId" placeholder="请选择批次" style="width: 100%">
<el-option v-for="batch in batchOptions" :key="batch.id" :label="batch.batchName" :value="batch.id" />
</el-select>
</el-form-item>
<el-form-item label="已选学生" prop="studentIds">
<div class="selected-students">
<el-tag v-for="student in selectedStudents" :key="student.id" closable @close="removeStudent(student.id)">
{{ student.name }}({{ student.studentNo }})
</el-tag>
</div>
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="associateDialog.visible = false">取消</el-button>
<el-button type="primary" @click="submitAssociate">确定</el-button>
</div>
</template>
</el-dialog>
<!-- 学生进度对话框 -->
<el-dialog title="学生批次进度" v-model="progressDialog.visible" width="800px" append-to-body>
<div v-if="progressInfo" class="progress-content">
<el-descriptions :column="2" border>
<el-descriptions-item label="学生姓名">{{ progressInfo.studentName }}</el-descriptions-item>
<el-descriptions-item label="学号">{{ progressInfo.studentNo }}</el-descriptions-item>
<el-descriptions-item label="批次名称">{{ progressInfo.batchName }}</el-descriptions-item>
<el-descriptions-item label="提交状态">
<el-tag :type="getSubmissionStatusTagType(progressInfo.submissionStatus)">
{{ getSubmissionStatusText(progressInfo.submissionStatus) }}
</el-tag>
</el-descriptions-item>
<el-descriptions-item label="审核状态">
<el-tag :type="getApprovalStatusTagType(progressInfo.approvalStatus)">
{{ getApprovalStatusText(progressInfo.approvalStatus) }}
</el-tag>
</el-descriptions-item>
<el-descriptions-item label="提交时间">
{{ progressInfo.submissionTime ? parseTime(progressInfo.submissionTime, '{y}-{m}-{d} {h}:{i}:{s}') : '-' }}
</el-descriptions-item>
<el-descriptions-item label="审核时间">
{{ progressInfo.approvalTime ? parseTime(progressInfo.approvalTime, '{y}-{m}-{d} {h}:{i}:{s}') : '-' }}
</el-descriptions-item>
<el-descriptions-item label="审核意见" :span="2">
{{ progressInfo.approvalComments || '-' }}
</el-descriptions-item>
</el-descriptions>
</div>
</el-dialog>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted, getCurrentInstance, computed } from 'vue';
import { ElMessage, ElMessageBox } from 'element-plus';
import { parseTime } from '@/utils/ruoyi';
import { listAllCollectionBatch } from '@/api/xg-xx/collectionBatch';
import {
listStudentInfo,
getStudentsByBatch,
addStudentInfo,
updateStudentInfo,
delStudentInfo,
batchAssociateStudentsToBatch,
submitStudentInfo,
withdrawStudentInfo,
getStudentBatchProgress,
getStudentBatchStatistics,
batchSubmitStudentInfo,
batchWithdrawStudentInfo
} from '@/api/xg-xx/studentInfo';
import BatchManagementComponent from './components/BatchManagementComponent.vue';
const { proxy } = getCurrentInstance() as any;
// 响应式数据
const activeTab = ref('studentList');
const loading = ref(false);
const ids = ref<number[]>([]);
const single = ref(true);
const multiple = ref(true);
const total = ref(0);
const studentList = ref<any[]>([]);
const batchOptions = ref<any[]>([]);
const selectedBatchId = ref<number | null>(null);
const currentBatch = ref<any>();
// 批次统计信息
const batchStatistics = reactive({
totalCount: 0,
submittedCount: 0,
approvedCount: 0,
rejectedCount: 0,
completionRate: 0
});
// 查询参数
const queryParams = reactive({
pageNum: 1,
pageSize: 10,
batchId: undefined as number | undefined,
name: undefined,
studentNo: undefined,
className: undefined,
submissionStatus: undefined,
collegeName: undefined
});
// 关联对话框
const associateDialog = reactive({
visible: false
});
const associateForm = reactive({
batchId: undefined as number | undefined,
studentIds: [] as number[]
});
const associateRules = {
batchId: [{ required: true, message: '请选择批次', trigger: 'change' }],
studentIds: [{ required: true, message: '请选择学生', trigger: 'change' }]
};
// 进度对话框
const progressDialog = reactive({
visible: false
});
const progressInfo = ref<any>();
// 计算已选学生
const selectedStudents = computed(() => {
return studentList.value.filter(student => ids.value.includes(student.id));
});
// 获取列表数据
const getList = async () => {
loading.value = true;
try {
let response;
if (selectedBatchId.value) {
response = await getStudentsByBatch(selectedBatchId.value, queryParams);
} else {
response = await listStudentInfo(queryParams);
}
studentList.value = response.rows;
total.value = response.total;
} catch (error) {
console.error('获取列表失败:', error);
ElMessage.error('获取学生列表失败');
} finally {
loading.value = false;
}
};
// 获取批次选项
const getBatchOptions = async () => {
try {
const response = await listAllCollectionBatch({});
batchOptions.value = response.data;
} catch (error) {
console.error('获取批次选项失败:', error);
ElMessage.error('获取批次选项失败');
}
};
// 批次改变处理
const handleBatchChange = (batchId: number) => {
selectedBatchId.value = batchId;
queryParams.batchId = batchId;
currentBatch.value = batchOptions.value.find(batch => batch.id === batchId);
// 获取批次统计信息
if (batchId) {
getBatchStatistics();
}
// 刷新列表
getList();
};
// 获取批次统计信息
const getBatchStatistics = async () => {
if (!selectedBatchId.value) return;
try {
const response = await getStudentBatchStatistics(selectedBatchId.value);
Object.assign(batchStatistics, response.data);
} catch (error) {
console.error('获取批次统计失败:', error);
ElMessage.error('获取批次统计失败');
}
};
// 刷新批次信息
const refreshBatchInfo = () => {
if (selectedBatchId.value) {
getBatchStatistics();
}
getList();
};
// 搜索
const handleQuery = () => {
queryParams.pageNum = 1;
getList();
};
// 重置搜索
const resetQuery = () => {
proxy.resetForm('queryFormRef');
handleQuery();
};
// 多选框选中数据
const handleSelectionChange = (selection: any[]) => {
ids.value = selection.map(item => item.id);
single.value = selection.length !== 1;
multiple.value = !selection.length;
};
// 排序变化
const handleSortChange = (column: any) => {
getList();
};
// 分页大小变化
const handleSizeChange = (val: number) => {
queryParams.pageSize = val;
getList();
};
// 当前页变化
const handleCurrentChange = (val: number) => {
queryParams.pageNum = val;
getList();
};
// 新增学生
const handleAdd = () => {
proxy.$router.push('/xg-xx/studentInfo/add');
};
// 批量关联
const handleBatchAssociate = () => {
if (ids.value.length === 0) {
ElMessage.warning('请选择要关联的学生');
return;
}
associateDialog.visible = true;
associateForm.batchId = selectedBatchId.value;
associateForm.studentIds = ids.value;
};
// 移除学生
const removeStudent = (studentId: number) => {
const index = associateForm.studentIds.indexOf(studentId);
if (index > -1) {
associateForm.studentIds.splice(index, 1);
}
};
// 提交关联
const submitAssociate = async () => {
if (!associateForm.batchId || associateForm.studentIds.length === 0) {
ElMessage.warning('请选择批次和学生');
return;
}
try {
await batchAssociateStudentsToBatch(associateForm.studentIds, associateForm.batchId);
ElMessage.success('关联成功');
associateDialog.visible = false;
// 刷新统计信息和列表
if (selectedBatchId.value) {
getBatchStatistics();
}
getList();
} catch (error) {
console.error('关联失败:', error);
ElMessage.error('关联失败');
}
};
// 批量提交
const handleBatchSubmit = async () => {
if (ids.value.length === 0) {
ElMessage.warning('请选择要提交的学生');
return;
}
if (!selectedBatchId.value) {
ElMessage.warning('请先选择批次');
return;
}
try {
await ElMessageBox.confirm('确定要批量提交选中的学生信息吗?', '系统提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
});
await batchSubmitStudentInfo(ids.value, selectedBatchId.value);
ElMessage.success('批量提交成功');
// 刷新统计信息和列表
getBatchStatistics();
getList();
} catch (error) {
if (error !== 'cancel') {
console.error('批量提交失败:', error);
ElMessage.error('批量提交失败');
}
}
};
// 批量撤回
const handleBatchWithdraw = async () => {
if (ids.value.length === 0) {
ElMessage.warning('请选择要撤回的学生');
return;
}
if (!selectedBatchId.value) {
ElMessage.warning('请先选择批次');
return;
}
try {
await ElMessageBox.confirm('确定要批量撤回选中的学生信息吗?', '系统提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
});
await batchWithdrawStudentInfo(ids.value, selectedBatchId.value);
ElMessage.success('批量撤回成功');
// 刷新统计信息和列表
getBatchStatistics();
getList();
} catch (error) {
if (error !== 'cancel') {
console.error('批量撤回失败:', error);
ElMessage.error('批量撤回失败');
}
}
};
// 删除学生
const handleDelete = async (row?: any) => {
const studentIds = row ? [row.id] : ids.value;
if (studentIds.length === 0) {
ElMessage.warning('请选择要删除的学生');
return;
}
try {
await ElMessageBox.confirm('确定要删除选中的学生信息吗?', '系统提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
});
await delStudentInfo(studentIds);
ElMessage.success('删除成功');
// 刷新统计信息和列表
if (selectedBatchId.value) {
getBatchStatistics();
}
getList();
} catch (error) {
if (error !== 'cancel') {
console.error('删除失败:', error);
ElMessage.error('删除失败');
}
}
};
// 查看详情
const handleView = (row: any) => {
proxy.$router.push(`/xg-xx/studentInfo/detail/${row.id}`);
};
// 编辑
const handleEdit = (row: any) => {
proxy.$router.push(`/xg-xx/studentInfo/edit/${row.id}`);
};
// 提交
const handleSubmit = async (row: any) => {
if (!selectedBatchId.value) {
ElMessage.warning('请先选择批次');
return;
}
try {
await ElMessageBox.confirm('确定要提交该学生信息吗?', '系统提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
});
await submitStudentInfo(row.id, selectedBatchId.value);
ElMessage.success('提交成功');
// 刷新统计信息和列表
getBatchStatistics();
getList();
} catch (error) {
if (error !== 'cancel') {
console.error('提交失败:', error);
ElMessage.error('提交失败');
}
}
};
// 撤回
const handleWithdraw = async (row: any) => {
if (!selectedBatchId.value) {
ElMessage.warning('请先选择批次');
return;
}
try {
await ElMessageBox.confirm('确定要撤回该学生信息吗?', '系统提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
});
await withdrawStudentInfo(row.id, selectedBatchId.value);
ElMessage.success('撤回成功');
// 刷新统计信息和列表
getBatchStatistics();
getList();
} catch (error) {
if (error !== 'cancel') {
console.error('撤回失败:', error);
ElMessage.error('撤回失败');
}
}
};
// 查看进度
const handleProgress = async (row: any) => {
if (!selectedBatchId.value) {
ElMessage.warning('请先选择批次');
return;
}
try {
const response = await getStudentBatchProgress(row.id, selectedBatchId.value);
progressInfo.value = response.data;
progressDialog.visible = true;
} catch (error) {
console.error('获取进度失败:', error);
ElMessage.error('获取进度失败');
}
};
// 导入
const handleImport = () => {
proxy.$router.push('/xg-xx/studentInfo/import');
};
// 导出
const handleExport = () => {
proxy?.download(
'xg/studentInfo/export',
queryParams,
`学生信息_${parseTime(new Date(), '{y}{m}{d}{h}{i}{s}')}.xlsx`
);
};
// 刷新
const handleRefresh = () => {
getList();
};
// 获取批次状态文本
const getBatchStatusText = (status: string) => {
const statusMap: Record<string, string> = {
'DRAFT': '草稿',
'ACTIVE': '进行中',
'COMPLETED': '已完成',
'CANCELLED': '已取消'
};
return statusMap[status] || '未知';
};
// 获取提交状态标签类型
const getSubmissionStatusTagType = (status: string): 'success' | 'warning' | 'info' | 'primary' | 'danger' => {
const statusMap: Record<string, 'success' | 'warning' | 'info' | 'primary' | 'danger'> = {
'DRAFT': 'info',
'SUBMITTED': 'warning',
'APPROVED': 'success',
'REJECTED': 'danger'
};
return statusMap[status] || 'info';
};
// 获取提交状态文本
const getSubmissionStatusText = (status: string) => {
const statusMap: Record<string, string> = {
'DRAFT': '草稿',
'SUBMITTED': '已提交',
'APPROVED': '已通过',
'REJECTED': '已拒绝'
};
return statusMap[status] || '未知';
};
// 获取审核状态标签类型
const getApprovalStatusTagType = (status: string): 'success' | 'warning' | 'info' | 'primary' | 'danger' => {
const statusMap: Record<string, 'success' | 'warning' | 'info' | 'primary' | 'danger'> = {
'PENDING': 'warning',
'APPROVED': 'success',
'REJECTED': 'danger'
};
return statusMap[status] || 'info';
};
// 获取审核状态文本
const getApprovalStatusText = (status: string) => {
const statusMap: Record<string, string> = {
'PENDING': '待审核',
'APPROVED': '已通过',
'REJECTED': '已拒绝'
};
return statusMap[status] || '未知';
};
// 批次创建成功回调
const handleBatchCreated = () => {
ElMessage.success('批次创建成功');
getBatchOptions(); // 刷新批次选项
if (activeTab.value === 'batchManagement') {
getList(); // 刷新学生列表
}
};
// 批次更新成功回调
const handleBatchUpdated = () => {
ElMessage.success('批次更新成功');
getBatchOptions(); // 刷新批次选项
if (activeTab.value === 'batchManagement') {
getList(); // 刷新学生列表
}
};
// 批次删除成功回调
const handleBatchDeleted = () => {
ElMessage.success('批次删除成功');
getBatchOptions(); // 刷新批次选项
if (activeTab.value === 'batchManagement') {
getList(); // 刷新学生列表
}
};
// 初始化
onMounted(() => {
getBatchOptions();
getList();
});
</script>
<style scoped>
.page-container {
background-color: #f5f5f5;
min-height: 100vh;
}
.page-header {
text-align: center;
margin-bottom: 20px;
}
.page-title {
font-size: 24px;
margin-bottom: 8px;
}
.page-description {
font-size: 16px;
color: #606266;
}
.main-tabs {
margin-top: 20px;
}
.tab-content {
padding: 16px;
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
}
.header-actions {
display: flex;
align-items: center;
}
.batch-info {
padding: 16px 0;
}
.toolbar-wrapper {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
}
.toolbar-left {
display: flex;
align-items: center;
gap: 8px;
}
.toolbar-right {
display: flex;
align-items: center;
gap: 8px;
}
.table-wrapper {
margin-top: 16px;
}
.action-buttons {
display: flex;
gap: 4px;
justify-content: center;
}
.pagination-container {
display: flex;
justify-content: center;
margin-top: 16px;
}
.progress-info {
padding: 8px 0;
}
.progress-content {
padding: 16px 0;
}
.selected-students {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
.mt-4 {
margin-top: 16px;
}
.mb-4 {
margin-bottom: 16px;
}
</style>
@@ -1,630 +0,0 @@
<template>
<div class="p-2 page-container">
<!-- 搜索区域 -->
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
<div v-show="showSearch" class="mb-[10px]">
<el-card shadow="hover">
<el-form ref="queryFormRef" :model="queryParams" label-width="100px">
<el-row :gutter="20">
<el-col :span="6">
<el-form-item label="批次选择" prop="batchId">
<el-select v-model="queryParams.batchId" placeholder="请选择批次" clearable style="width: 100%">
<el-option v-for="batch in batchOptions" :key="batch.id" :label="batch.batchName" :value="batch.id" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="学生姓名" prop="studentName">
<el-input v-model="queryParams.studentName" placeholder="请输入学生姓名" clearable @keyup.enter="handleQuery" />
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="学号" prop="studentNo">
<el-input v-model="queryParams.studentNo" placeholder="请输入学号" clearable @keyup.enter="handleQuery" />
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="提交状态" prop="submissionStatus">
<el-select v-model="queryParams.submissionStatus" placeholder="请选择提交状态" clearable style="width: 100%">
<el-option label="草稿" value="DRAFT" />
<el-option label="已提交" value="SUBMITTED" />
<el-option label="已通过" value="APPROVED" />
<el-option label="已拒绝" value="REJECTED" />
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="6">
<el-form-item label="审核状态" prop="approvalStatus">
<el-select v-model="queryParams.approvalStatus" placeholder="请选择审核状态" clearable style="width: 100%">
<el-option label="待审核" value="PENDING" />
<el-option label="已通过" value="APPROVED" />
<el-option label="已拒绝" value="REJECTED" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="班级" prop="className">
<el-input v-model="queryParams.className" placeholder="请输入班级名称" clearable @keyup.enter="handleQuery" />
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="学院" prop="collegeName">
<el-input v-model="queryParams.collegeName" placeholder="请输入学院名称" clearable @keyup.enter="handleQuery" />
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-col>
</el-row>
</el-form>
</el-card>
</div>
</transition>
<!-- 主要内容区域 -->
<el-card shadow="hover">
<!-- 操作按钮区域 -->
<div class="toolbar-wrapper">
<div class="toolbar-left">
<el-button-group>
<el-button
:type="currentTab === 'pending' ? 'primary' : 'default'"
@click="switchTab('pending')"
icon="Clock"
>
待审核 <el-badge :value="pendingCount" class="ml-1" />
</el-button>
<el-button
:type="currentTab === 'approved' ? 'primary' : 'default'"
@click="switchTab('approved')"
icon="Check"
>
已审核 <el-badge :value="approvedCount" class="ml-1" />
</el-button>
</el-button-group>
</div>
<div class="toolbar-right">
<el-button
type="success"
icon="Select"
:disabled="!multiple || ids.length === 0"
@click="handleBatchApprove(true)"
v-hasPermi="['xg:studentInfo:approve']"
>
批量通过
</el-button>
<el-button
type="danger"
icon="Close"
:disabled="!multiple || ids.length === 0"
@click="handleBatchApprove(false)"
v-hasPermi="['xg:studentInfo:approve']"
>
批量拒绝
</el-button>
<el-button
type="warning"
icon="Download"
@click="handleExport"
v-hasPermi="['xg:studentInfo:export']"
>
导出
</el-button>
<el-button
type="info"
icon="Refresh"
@click="handleRefresh"
>
刷新
</el-button>
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
</div>
</div>
<!-- 表格区域 -->
<div class="table-wrapper">
<el-table
v-loading="loading"
:data="approvalList"
@selection-change="handleSelectionChange"
@sort-change="handleSortChange"
row-key="id"
border
stripe
size="small"
>
<el-table-column type="selection" width="50" align="center" />
<el-table-column label="序号" type="index" width="60" align="center" />
<el-table-column label="学生姓名" prop="studentName" width="100" align="center" show-overflow-tooltip />
<el-table-column label="学号" prop="studentNo" width="120" align="center" show-overflow-tooltip />
<el-table-column label="班级" prop="className" width="120" align="center" show-overflow-tooltip />
<el-table-column label="学院" prop="collegeName" width="120" align="center" show-overflow-tooltip />
<el-table-column label="批次名称" prop="batchName" width="150" align="center" show-overflow-tooltip />
<el-table-column label="提交状态" prop="submissionStatus" width="100" align="center">
<template #default="scope">
<el-tag :type="getSubmissionStatusTagType(scope.row.submissionStatus)" size="small">
{{ getSubmissionStatusText(scope.row.submissionStatus) }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="审核状态" prop="approvalStatus" width="100" align="center">
<template #default="scope">
<el-tag :type="getApprovalStatusTagType(scope.row.approvalStatus)" size="small">
{{ getApprovalStatusText(scope.row.approvalStatus) }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="提交时间" prop="submissionTime" width="160" align="center" sortable="custom">
<template #default="scope">
<span>{{ proxy.parseTime(scope.row.submissionTime, '{y}-{m}-{d} {h}:{i}') }}</span>
</template>
</el-table-column>
<el-table-column label="审核时间" prop="approvalTime" width="160" align="center" sortable="custom">
<template #default="scope">
<span>{{ proxy.parseTime(scope.row.approvalTime, '{y}-{m}-{d} {h}:{i}') }}</span>
</template>
</el-table-column>
<el-table-column label="审核人" prop="approvalByName" width="100" align="center" show-overflow-tooltip />
<el-table-column label="审核意见" prop="approvalComments" width="200" align="center" show-overflow-tooltip />
<el-table-column label="操作" align="center" width="200" fixed="right" class-name="small-padding fixed-width">
<template #default="scope">
<div class="action-buttons">
<el-tooltip content="查看详情" placement="top">
<el-button link type="info" icon="View" size="small" @click="handleView(scope.row)" />
</el-tooltip>
<el-tooltip content="审核历史" placement="top">
<el-button link type="warning" icon="Document" size="small" @click="handleHistory(scope.row)" />
</el-tooltip>
<template v-if="scope.row.approvalStatus === 'PENDING'">
<el-tooltip content="通过" placement="top">
<el-button link type="success" icon="Check" size="small" @click="handleApprove(scope.row, true)" v-hasPermi="['xg:studentInfo:approve']" />
</el-tooltip>
<el-tooltip content="拒绝" placement="top">
<el-button link type="danger" icon="Close" size="small" @click="handleApprove(scope.row, false)" v-hasPermi="['xg:studentInfo:approve']" />
</el-tooltip>
</template>
<template v-else>
<el-tooltip content="撤销审核" placement="top">
<el-button link type="warning" icon="RefreshLeft" size="small" @click="handleRevoke(scope.row)" v-hasPermi="['xg:studentInfo:approve']" />
</el-tooltip>
</template>
</div>
</template>
</el-table-column>
</el-table>
<div class="pagination-container">
<pagination v-show="total > 0" :total="total" :page="queryParams.pageNum" :limit="queryParams.pageSize" @pagination="getList" />
</div>
</div>
</el-card>
<!-- 审核对话框 -->
<el-dialog :title="approvalDialog.title" v-model="approvalDialog.visible" width="600px" append-to-body>
<el-form ref="approvalFormRef" :model="approvalForm" :rules="approvalRules" label-width="100px">
<el-form-item label="审核结果" prop="approved">
<el-radio-group v-model="approvalForm.approved">
<el-radio :label="true">通过</el-radio>
<el-radio :label="false">拒绝</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="审核意见" prop="comments">
<el-input
v-model="approvalForm.comments"
type="textarea"
:rows="4"
placeholder="请输入审核意见"
maxlength="500"
show-word-limit
/>
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="approvalDialog.visible = false">取消</el-button>
<el-button type="primary" @click="submitApproval">确定</el-button>
</div>
</template>
</el-dialog>
<!-- 审核历史对话框 -->
<el-dialog title="审核历史" v-model="historyDialog.visible" width="800px" append-to-body>
<el-timeline>
<el-timeline-item
v-for="item in historyList"
:key="item.id"
:timestamp="proxy.parseTime(item.createTime, '{y}-{m}-{d} {h}:{i}:{s}')"
placement="top"
>
<el-card>
<div class="history-item">
<div class="history-header">
<span class="history-action">{{ getHistoryActionText(item.approvalStatus) }}</span>
<span class="history-user">{{ item.approvalByName }}</span>
</div>
<div class="history-content" v-if="item.approvalComments">
{{ item.approvalComments }}
</div>
</div>
</el-card>
</el-timeline-item>
</el-timeline>
</el-dialog>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted, getCurrentInstance } from 'vue';
import { ElMessage, ElMessageBox } from 'element-plus';
import {
queryPendingApprovalList,
queryApprovedList,
approveStudentInfo,
batchApproveStudentInfo,
revokeApproval,
queryApprovalHistory,
getApprovalStatistics,
exportApprovalResults,
type XgStudentInfoApprovalQuery,
type XgStudentInfoApprovalVO
} from '@/api/xg-xx/studentInfoApproval';
import { listAllCollectionBatch } from '@/api/xg-xx/collectionBatch';
const { proxy } = getCurrentInstance() as any;
// 响应式数据
const loading = ref(false);
const showSearch = ref(true);
const ids = ref<number[]>([]);
const single = ref(true);
const multiple = ref(true);
const total = ref(0);
const approvalList = ref<XgStudentInfoApprovalVO[]>([]);
const batchOptions = ref<any[]>([]);
const currentTab = ref('pending');
const pendingCount = ref(0);
const approvedCount = ref(0);
// 查询参数
const queryParams = reactive<XgStudentInfoApprovalQuery>({
pageNum: 1,
pageSize: 10,
batchId: undefined,
studentName: undefined,
studentNo: undefined,
submissionStatus: undefined,
approvalStatus: undefined,
className: undefined,
collegeName: undefined
});
// 审核对话框
const approvalDialog = reactive({
visible: false,
title: '',
type: 'single' as 'single' | 'batch'
});
const approvalForm = reactive({
approved: true,
comments: '',
studentIds: [] as number[],
batchId: undefined as number | undefined
});
const approvalRules = {
approved: [{ required: true, message: '请选择审核结果', trigger: 'change' }]
};
// 审核历史对话框
const historyDialog = reactive({
visible: false
});
const historyList = ref<XgStudentInfoApprovalVO[]>([]);
// 获取列表数据
const getList = async () => {
loading.value = true;
try {
let response;
if (currentTab.value === 'pending') {
response = await queryPendingApprovalList(queryParams);
} else {
response = await queryApprovedList(queryParams);
}
approvalList.value = response.rows;
total.value = response.total;
// 获取统计信息
if (queryParams.batchId) {
const statsResponse = await getApprovalStatistics(queryParams.batchId);
pendingCount.value = statsResponse.data.pendingCount || 0;
approvedCount.value = (statsResponse.data.approvedCount || 0) + (statsResponse.data.rejectedCount || 0);
}
} catch (error) {
console.error('获取列表失败:', error);
} finally {
loading.value = false;
}
};
// 获取批次选项
const getBatchOptions = async () => {
try {
const response = await listAllCollectionBatch({});
batchOptions.value = response.data;
} catch (error) {
console.error('获取批次选项失败:', error);
}
};
// 切换标签页
const switchTab = (tab: string) => {
currentTab.value = tab;
queryParams.pageNum = 1;
getList();
};
// 搜索
const handleQuery = () => {
queryParams.pageNum = 1;
getList();
};
// 重置搜索
const resetQuery = () => {
proxy.resetForm('queryFormRef');
handleQuery();
};
// 多选框选中数据
const handleSelectionChange = (selection: XgStudentInfoApprovalVO[]) => {
ids.value = selection.map(item => item.studentId!);
single.value = selection.length !== 1;
multiple.value = !selection.length;
};
// 排序变化
const handleSortChange = (column: any) => {
// 处理排序逻辑
getList();
};
// 查看详情
const handleView = (row: XgStudentInfoApprovalVO) => {
// 跳转到学生信息详情页面
proxy.$router.push(`/xg-xx/studentInfo/detail/${row.studentId}`);
};
// 审核操作
const handleApprove = (row: XgStudentInfoApprovalVO, approved: boolean) => {
approvalDialog.visible = true;
approvalDialog.title = approved ? '审核通过' : '审核拒绝';
approvalDialog.type = 'single';
approvalForm.approved = approved;
approvalForm.comments = '';
approvalForm.studentIds = [row.studentId!];
approvalForm.batchId = row.batchId;
};
// 批量审核
const handleBatchApprove = (approved: boolean) => {
if (ids.value.length === 0) {
ElMessage.warning('请选择要审核的学生');
return;
}
approvalDialog.visible = true;
approvalDialog.title = approved ? '批量审核通过' : '批量审核拒绝';
approvalDialog.type = 'batch';
approvalForm.approved = approved;
approvalForm.comments = '';
approvalForm.studentIds = ids.value;
approvalForm.batchId = queryParams.batchId;
};
// 提交审核
const submitApproval = async () => {
try {
if (approvalDialog.type === 'single') {
await approveStudentInfo(
approvalForm.studentIds[0],
approvalForm.batchId!,
approvalForm.approved,
approvalForm.comments
);
} else {
await batchApproveStudentInfo(
approvalForm.studentIds,
approvalForm.batchId!,
approvalForm.approved,
approvalForm.comments
);
}
ElMessage.success('审核成功');
approvalDialog.visible = false;
getList();
} catch (error) {
console.error('审核失败:', error);
}
};
// 撤销审核
const handleRevoke = async (row: XgStudentInfoApprovalVO) => {
try {
await ElMessageBox.confirm('确定要撤销该学生的审核吗?', '系统提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
});
await revokeApproval(row.studentId!, row.batchId!);
ElMessage.success('撤销成功');
getList();
} catch (error) {
console.error('撤销失败:', error);
}
};
// 查看审核历史
const handleHistory = async (row: XgStudentInfoApprovalVO) => {
try {
const response = await queryApprovalHistory(row.studentId!, row.batchId!);
historyList.value = response.data;
historyDialog.visible = true;
} catch (error) {
console.error('获取审核历史失败:', error);
}
};
// 导出
const handleExport = () => {
proxy?.download(
'xg/studentInfoApproval/export',
queryParams,
`学生信息审核结果_${proxy.parseTime(new Date(), '{y}{m}{d}{h}{i}{s}')}.xlsx`
);
};
// 刷新
const handleRefresh = () => {
getList();
};
// 获取提交状态标签类型
const getSubmissionStatusTagType = (status: string) => {
const statusMap: Record<string, string> = {
'DRAFT': 'info',
'SUBMITTED': 'warning',
'APPROVED': 'success',
'REJECTED': 'danger'
};
return statusMap[status] || 'info';
};
// 获取提交状态文本
const getSubmissionStatusText = (status: string) => {
const statusMap: Record<string, string> = {
'DRAFT': '草稿',
'SUBMITTED': '已提交',
'APPROVED': '已通过',
'REJECTED': '已拒绝'
};
return statusMap[status] || '未知';
};
// 获取审核状态标签类型
const getApprovalStatusTagType = (status: string) => {
const statusMap: Record<string, string> = {
'PENDING': 'warning',
'APPROVED': 'success',
'REJECTED': 'danger'
};
return statusMap[status] || 'info';
};
// 获取审核状态文本
const getApprovalStatusText = (status: string) => {
const statusMap: Record<string, string> = {
'PENDING': '待审核',
'APPROVED': '已通过',
'REJECTED': '已拒绝'
};
return statusMap[status] || '未知';
};
// 获取历史操作文本
const getHistoryActionText = (status: string) => {
const statusMap: Record<string, string> = {
'PENDING': '提交审核',
'APPROVED': '审核通过',
'REJECTED': '审核拒绝'
};
return statusMap[status] || '未知操作';
};
// 初始化
onMounted(() => {
getBatchOptions();
getList();
});
</script>
<style scoped>
.page-container {
background-color: #f5f5f5;
min-height: 100vh;
}
.toolbar-wrapper {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
}
.toolbar-left {
display: flex;
align-items: center;
}
.toolbar-right {
display: flex;
align-items: center;
gap: 8px;
}
.table-wrapper {
margin-top: 16px;
}
.action-buttons {
display: flex;
gap: 4px;
justify-content: center;
}
.pagination-container {
display: flex;
justify-content: center;
margin-top: 16px;
}
.history-item {
padding: 8px 0;
}
.history-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 8px;
}
.history-action {
font-weight: bold;
color: #409eff;
}
.history-user {
color: #666;
font-size: 12px;
}
.history-content {
color: #333;
line-height: 1.5;
background-color: #f9f9f9;
padding: 8px;
border-radius: 4px;
}
</style>