wuzhen提交删除xg-xx前端

This commit is contained in:
zlwu authored and wuzhen committed 2025-07-14 14:32:31 +08:00
1 parent cbae932faa
commit a886333f13
32 files changed
+249 -14105

No files matched your search

+7 -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>
@@ -394,12 +400,7 @@
<artifactId>ruoyi-aqbw</artifactId>
<version>${revision}</version>
</dependency>
<!-- 学工管理模块 - 学生信息 -->
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-xg-xx</artifactId>
<version>${revision}</version>
</dependency>
</dependencies>
</dependencyManagement>
@@ -1 +0,0 @@
{"doc":" 信息采集批次视图对象 xg_collection_batch\n\n @author Cursor AI\n @date 2025-01-12\n","fields":[{"name":"id","doc":" 主键ID\n"},{"name":"batchCode","doc":" 批次编号\n"},{"name":"batchName","doc":" 批次名称\n"},{"name":"batchDescription","doc":" 批次描述\n"},{"name":"batchType","doc":" 批次类型\n"},{"name":"academicYear","doc":" 学年\n"},{"name":"semester","doc":" 学期\n"},{"name":"scopeType","doc":" 适用范围\n"},{"name":"startTime","doc":" 开始时间\n"},{"name":"endTime","doc":" 结束时间\n"},{"name":"batchStatus","doc":" 批次状态\n"},{"name":"publishStatus","doc":" 发布状态\n"},{"name":"responsiblePersonName","doc":" 负责人姓名\n"},{"name":"contactPhone","doc":" 联系电话\n"},{"name":"remark","doc":" 备注\n"},{"name":"createTime","doc":" 创建时间\n"},{"name":"createBy","doc":" 创建者\n"},{"name":"updateTime","doc":" 更新时间\n"},{"name":"updateBy","doc":" 更新者\n"},{"name":"totalStudents","doc":" 总学生数\n"},{"name":"submittedStudents","doc":" 已提交学生数\n"},{"name":"approvedStudents","doc":" 已通过学生数\n"},{"name":"rejectedStudents","doc":" 已拒绝学生数\n"},{"name":"completionRate","doc":" 完成率\n"}],"enumConstants":[],"methods":[{"name":"getId","paramTypes":[],"doc":"主键ID"},{"name":"getBatchCode","paramTypes":[],"doc":"批次编号"},{"name":"getBatchName","paramTypes":[],"doc":"批次名称"},{"name":"getBatchDescription","paramTypes":[],"doc":"批次描述"},{"name":"getBatchType","paramTypes":[],"doc":"批次类型"},{"name":"getAcademicYear","paramTypes":[],"doc":"学年"},{"name":"getSemester","paramTypes":[],"doc":"学期"},{"name":"getScopeType","paramTypes":[],"doc":"适用范围"},{"name":"getStartTime","paramTypes":[],"doc":"开始时间"},{"name":"getEndTime","paramTypes":[],"doc":"结束时间"},{"name":"getBatchStatus","paramTypes":[],"doc":"批次状态"},{"name":"getPublishStatus","paramTypes":[],"doc":"发布状态"},{"name":"getResponsiblePersonName","paramTypes":[],"doc":"负责人姓名"},{"name":"getContactPhone","paramTypes":[],"doc":"联系电话"},{"name":"getRemark","paramTypes":[],"doc":"备注"},{"name":"getCreateTime","paramTypes":[],"doc":"创建时间"},{"name":"getCreateBy","paramTypes":[],"doc":"创建者"},{"name":"getUpdateTime","paramTypes":[],"doc":"更新时间"},{"name":"getUpdateBy","paramTypes":[],"doc":"更新者"},{"name":"getTotalStudents","paramTypes":[],"doc":"总学生数"},{"name":"getSubmittedStudents","paramTypes":[],"doc":"已提交学生数"},{"name":"getApprovedStudents","paramTypes":[],"doc":"已通过学生数"},{"name":"getRejectedStudents","paramTypes":[],"doc":"已拒绝学生数"},{"name":"getCompletionRate","paramTypes":[],"doc":"完成率"},{"name":"setId","paramTypes":["java.lang.Long"],"doc":"主键ID"},{"name":"setBatchCode","paramTypes":["java.lang.String"],"doc":"批次编号"},{"name":"setBatchName","paramTypes":["java.lang.String"],"doc":"批次名称"},{"name":"setBatchDescription","paramTypes":["java.lang.String"],"doc":"批次描述"},{"name":"setBatchType","paramTypes":["java.lang.String"],"doc":"批次类型"},{"name":"setAcademicYear","paramTypes":["java.lang.String"],"doc":"学年"},{"name":"setSemester","paramTypes":["java.lang.String"],"doc":"学期"},{"name":"setScopeType","paramTypes":["java.lang.String"],"doc":"适用范围"},{"name":"setStartTime","paramTypes":["java.util.Date"],"doc":"开始时间"},{"name":"setEndTime","paramTypes":["java.util.Date"],"doc":"结束时间"},{"name":"setBatchStatus","paramTypes":["java.lang.String"],"doc":"批次状态"},{"name":"setPublishStatus","paramTypes":["java.lang.String"],"doc":"发布状态"},{"name":"setResponsiblePersonName","paramTypes":["java.lang.String"],"doc":"负责人姓名"},{"name":"setContactPhone","paramTypes":["java.lang.String"],"doc":"联系电话"},{"name":"setRemark","paramTypes":["java.lang.String"],"doc":"备注"},{"name":"setCreateTime","paramTypes":["java.util.Date"],"doc":"创建时间"},{"name":"setCreateBy","paramTypes":["java.lang.Long"],"doc":"创建者"},{"name":"setUpdateTime","paramTypes":["java.util.Date"],"doc":"更新时间"},{"name":"setUpdateBy","paramTypes":["java.lang.Long"],"doc":"更新者"},{"name":"setTotalStudents","paramTypes":["java.lang.Integer"],"doc":"总学生数"},{"name":"setSubmittedStudents","paramTypes":["java.lang.Integer"],"doc":"已提交学生数"},{"name":"setApprovedStudents","paramTypes":["java.lang.Integer"],"doc":"已通过学生数"},{"name":"setRejectedStudents","paramTypes":["java.lang.Integer"],"doc":"已拒绝学生数"},{"name":"setCompletionRate","paramTypes":["java.lang.Double"],"doc":"完成率"}],"constructors":[]}
-116
View File
@@ -1,116 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>ruoyi-modules</artifactId>
<groupId>org.dromara</groupId>
<version>${revision}</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>ruoyi-xg-xx</artifactId>
<description>
学工管理-学生信息管理模块
</description>
<dependencies>
<!-- 通用工具 -->
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-core</artifactId>
</dependency>
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-doc</artifactId>
</dependency>
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-mybatis</artifactId>
</dependency>
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-log</artifactId>
</dependency>
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-security</artifactId>
</dependency>
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-web</artifactId>
</dependency>
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-excel</artifactId>
</dependency>
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-idempotent</artifactId>
</dependency>
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-tenant</artifactId>
</dependency>
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-social</artifactId>
</dependency>
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-ratelimiter</artifactId>
</dependency>
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-mail</artifactId>
</dependency>
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-system</artifactId>
</dependency>
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-job</artifactId>
</dependency>
<!-- 代码生成-->
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-generator</artifactId>
</dependency>
<!-- demo模块 -->
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-demo</artifactId>
</dependency>
<!-- 工作流模块 -->
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-workflow</artifactId>
</dependency>
<!-- Spring Boot Test 测试依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
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
**状态**: 设计中
+34 -2
View File
@@ -1,4 +1,36 @@
org.dromara.workflow.domain.vo.FlowCategoryVo
org.dromara.hq.domain.vo.HqPmRepairProgressVo
org.dromara.hq.domain.vo.HqPmRepairOrderVo
org.dromara.hq.domain.vo.HqSatSurveyQuestionVo
org.dromara.hq.domain.vo.HqPmRepairCategoryVo
org.dromara.hq.domain.bo.HqSatOptionBo
org.dromara.hq.domain.vo.HqSatCategoryVo
org.dromara.hq.domain.bo.HqPmRepairAttachmentBo
org.dromara.hq.domain.bo.HqHallInfoBo
org.dromara.hq.domain.vo.HqVenueInfoVo
org.dromara.hq.domain.bo.HqSatSurveyBo
org.dromara.hq.domain.vo.HqVenueReservationVo
org.dromara.hq.domain.vo.HqSatSurveyVo
org.dromara.hq.domain.vo.HqSatRecordVo
org.dromara.hq.domain.bo.HqHallReservationBo
org.dromara.hq.domain.bo.HqPmServiceEvaluationBo
org.dromara.hq.domain.bo.HqSatRecordBo
org.dromara.workflow.domain.vo.TestLeaveVo
org.dromara.hq.domain.bo.HqSatQuestionBo
org.dromara.workflow.domain.bo.TestLeaveBo
org.dromara.workflow.domain.bo.FlowCategoryBo
org.dromara.hq.domain.bo.HqSatDetailBo
org.dromara.hq.domain.bo.HqVenueInfoBo
org.dromara.hq.domain.bo.HqPmRepairProgressBo
org.dromara.hq.domain.vo.HqPmRepairAttachmentVo
org.dromara.hq.domain.bo.HqPmRepairOrderBo
org.dromara.hq.domain.bo.HqPmRepairCategoryBo
org.dromara.hq.domain.vo.HqPmServiceEvaluationVo
org.dromara.hq.domain.vo.HqSatOptionVo
org.dromara.hq.domain.vo.HqHallReservationVo
org.dromara.workflow.domain.vo.FlowCategoryVo
org.dromara.hq.domain.bo.HqSatCategoryBo
org.dromara.hq.domain.vo.HqSatDetailVo
org.dromara.hq.domain.vo.HqSatQuestionVo
org.dromara.hq.domain.bo.HqSatSurveyQuestionBo
org.dromara.hq.domain.bo.HqVenueReservationBo
org.dromara.workflow.domain.bo.FlowCategoryBo
org.dromara.hq.domain.vo.HqHallInfoVo
-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>