wuzhen update

This commit is contained in:
wuzhen committed 2025-08-07 18:40:25 +08:00
1 parent e06d8fca2c
commit 9b7a6e2103
138 files changed
+29581

No files matched your search

+171
View File
@@ -0,0 +1,171 @@
import request from '@/utils/request';
import { AxiosPromise } from 'axios';
import {
ScholarshipApplicationVO,
ScholarshipApplicationForm,
ScholarshipApplicationQuery,
ScholarshipClassQuotaVO,
ScholarshipAttachmentVO
} from './types';
/**
* 查询奖学金申请记录列表
* @param query 查询参数
*/
export const listScholarshipApplication = (query?: ScholarshipApplicationQuery): AxiosPromise<ScholarshipApplicationVO[]> => {
return request({
url: '/jcbt/scholarshipApplication/list',
method: 'get',
params: query
});
};
/**
* 查询奖学金申请记录详细信息
* @param id 申请记录ID
*/
export const getScholarshipApplication = (id: string | number): AxiosPromise<ScholarshipApplicationVO> => {
return request({
url: `/jcbt/scholarshipApplication/${id}`,
method: 'get'
});
};
/**
* 新增奖学金申请记录
* @param data 申请数据
*/
export const addScholarshipApplication = (data: ScholarshipApplicationForm) => {
return request({
url: '/jcbt/scholarshipApplication',
method: 'post',
data: data
});
};
/**
* 修改奖学金申请记录
* @param data 申请数据
*/
export const updateScholarshipApplication = (data: ScholarshipApplicationForm) => {
return request({
url: '/jcbt/scholarshipApplication',
method: 'put',
data: data
});
};
/**
* 删除奖学金申请记录
* @param id 申请记录ID
*/
export const delScholarshipApplication = (id: string | number | Array<string | number>) => {
return request({
url: `/jcbt/scholarshipApplication/${id}`,
method: 'delete'
});
};
/**
* 导出奖学金申请记录
* @param query 查询参数
*/
export const exportScholarshipApplication = (query: ScholarshipApplicationQuery) => {
return request({
url: '/jcbt/scholarshipApplication/export',
method: 'post',
data: query,
responseType: 'blob'
});
};
/**
* 下载导入模板
*/
export const importTemplate = () => {
return request({
url: '/jcbt/scholarshipApplication/importTemplate',
method: 'post',
responseType: 'blob'
});
};
/**
* 批量导入奖学金申请记录
* @param file 导入文件
*/
export const importData = (file: File) => {
const formData = new FormData();
formData.append('file', file);
return request({
url: '/jcbt/scholarshipApplication/import',
method: 'post',
data: formData,
headers: {
'Content-Type': 'multipart/form-data'
}
});
};
/**
* 查询班级奖学金名额
* @param query 查询参数
*/
export const listScholarshipClassQuota = (query?: any): AxiosPromise<ScholarshipClassQuotaVO[]> => {
return request({
url: '/jcbt/scholarshipClassQuota/list',
method: 'get',
params: query
});
};
/**
* 查询奖学金附件信息
* @param applicationId 申请记录ID
*/
export const listScholarshipAttachment = (applicationId: string | number): AxiosPromise<ScholarshipAttachmentVO[]> => {
return request({
url: '/jcbt/scholarshipAttachment/list',
method: 'get',
params: { applicationId }
});
};
/**
* 审核奖学金申请
* @param id 申请记录ID
* @param auditStatus 审核状态
* @param auditRemark 审核备注
*/
export const auditScholarshipApplication = (id: string | number, auditStatus: string, auditRemark?: string) => {
return request({
url: `/jcbt/scholarshipApplication/${id}/audit`,
method: 'put',
params: {
auditStatus,
auditRemark
}
});
};
/**
* 提交奖学金申请
* @param id 申请记录ID
*/
export const submitScholarshipApplication = (id: string | number) => {
return request({
url: `/jcbt/scholarshipApplication/${id}/submit`,
method: 'put'
});
};
/**
* 撤销奖学金申请
* @param id 申请记录ID
*/
export const revokeScholarshipApplication = (id: string | number) => {
return request({
url: `/jcbt/scholarshipApplication/${id}/revoke`,
method: 'put'
});
};
+192
View File
@@ -0,0 +1,192 @@
import request from '@/utils/request';
import { AxiosPromise } from 'axios';
export interface ScholarshipApplicationVO {
/** 主键ID */
id?: string | number;
/** 批次ID */
batchId?: string | number;
/** 奖学金类型ID */
scholarshipTypeId?: string | number;
/** 学生学号 */
studentCode?: string;
/** 学生姓名 */
studentName?: string;
/** 学生ID */
studentId?: string | number;
/** 学院ID */
deptId?: string | number;
/** 学院名称 */
deptName?: string;
/** 班级ID */
classId?: string | number;
/** 班级名称 */
className?: string;
/** 申请人类型 */
applicantType?: string;
/** 申请人ID */
applicantId?: string | number;
/** 申请人姓名 */
applicantName?: string;
/** 申请类型 */
applicationType?: string;
/** 申请原因 */
applicationReason?: string;
/** 申请说明 */
applicationDescription?: string;
/** 状态 */
status?: string;
/** 创建时间 */
createTime?: string;
}
export interface ScholarshipApplicationForm {
/** 主键ID */
id?: string | number;
/** 批次ID */
batchId?: string | number;
/** 奖学金类型ID */
scholarshipTypeId?: string | number;
/** 学生ID列表(班主任勾选) */
studentIds?: number[];
/** 申请人类型 */
applicantType?: string;
/** 申请人ID */
applicantId?: string | number;
/** 申请人姓名 */
applicantName?: string;
/** 申请类型 */
applicationType?: string;
/** 申请原因 */
applicationReason?: string;
/** 申请说明 */
applicationDescription?: string;
}
export interface ScholarshipApplicationQuery extends PageQuery {
/** 批次ID */
batchId?: string | number;
/** 奖学金类型ID */
scholarshipTypeId?: string | number;
/** 学生学号 */
studentCode?: string;
/** 学生姓名 */
studentName?: string;
/** 申请人ID */
applicantId?: string | number;
/** 状态 */
status?: string;
}
// 查询奖学金申请记录列表
export function listScholarshipApplication(query: ScholarshipApplicationQuery): AxiosPromise<ScholarshipApplicationVO[]> {
return request({
url: '/jcbt/scholarshipApplication/list',
method: 'get',
params: query
});
}
// 查询奖学金申请记录详细
export function getScholarshipApplication(id: string | number): AxiosPromise<ScholarshipApplicationVO> {
return request({
url: '/jcbt/scholarshipApplication/' + id,
method: 'get'
});
}
// 新增奖学金申请记录
export function addScholarshipApplication(data: ScholarshipApplicationForm) {
return request({
url: '/jcbt/scholarshipApplication',
method: 'post',
data: data
});
}
// 修改奖学金申请记录
export function updateScholarshipApplication(data: ScholarshipApplicationForm) {
return request({
url: '/jcbt/scholarshipApplication',
method: 'put',
data: data
});
}
// 删除奖学金申请记录
export function delScholarshipApplication(id: string | number | Array<string | number>) {
return request({
url: '/jcbt/scholarshipApplication/' + id,
method: 'delete'
});
}
// 导出奖学金申请记录
export function exportScholarshipApplication(query: ScholarshipApplicationQuery) {
return request({
url: '/jcbt/scholarshipApplication/export',
method: 'post',
data: query
});
}
// ========== 班主任申请专用接口 ==========
// 获取班主任所带班级信息
export function getClassInfoByTeacher(teacherId: string | number): AxiosPromise<any> {
return request({
url: '/jcbt/scholarshipApplication/classInfo',
method: 'get',
params: { teacherId }
});
}
// 获取班级学生列表(用于勾选)
export function getClassStudents(classId: string | number): AxiosPromise<any[]> {
return request({
url: '/jcbt/scholarshipApplication/students',
method: 'get',
params: { classId }
});
}
// 根据批次获取可申请的奖学金类型
export function getScholarshipTypesByBatch(batchId: string | number): AxiosPromise<any[]> {
return request({
url: '/jcbt/scholarshipApplication/scholarshipTypes',
method: 'get',
params: { batchId }
});
}
// 根据批次和班级获取带名额信息的奖学金类型(班主任专用)
export function getScholarshipTypesWithQuota(batchId: string | number, classId: string | number): AxiosPromise<any[]> {
return request({
url: '/jcbt/scholarshipApplication/scholarshipTypesWithQuota',
method: 'get',
params: { batchId, classId }
});
}
// 确认选择班级(多班级时用户选择后调用)
export function confirmClassSelection(teacherId: string | number, classId: string | number): AxiosPromise<any> {
return request({
url: '/jcbt/scholarshipApplication/confirmClass',
method: 'post',
params: { teacherId, classId }
});
}
// 班主任批量申请提交
export function batchSubmitByTeacher(batchId: string | number, scholarshipTypeId: string | number, classId: string | number, studentApplications: any[]) {
return request({
url: '/jcbt/scholarshipApplication/batch',
method: 'post',
params: {
batchId,
scholarshipTypeId,
classId
},
data: studentApplications
});
}
+226
View File
@@ -0,0 +1,226 @@
import request from '@/utils/request';
import { AxiosPromise } from 'axios';
export interface ScholarshipBatchVO {
/** 主键ID */
id?: string | number;
/** 批次编码 */
batchCode?: string;
/** 批次名称 */
batchName?: string;
/** 学年 */
academicYear?: string;
/** 学期 */
semester?: number;
/** 批次类型 */
batchType?: string;
/** 开始时间 */
startTime?: string;
/** 结束时间 */
endTime?: string;
/** 申请开始时间 */
applyStartTime?: string;
/** 申请结束时间 */
applyEndTime?: string;
/** 审核开始时间 */
reviewStartTime?: string;
/** 审核结束时间 */
reviewEndTime?: string;
/** 状态 */
status?: string;
/** 批次描述 */
description?: string;
/** 通知内容 */
noticeContent?: string;
/** 关联的奖学金类型ID列表 */
scholarshipTypeIds?: Array<string | number>;
/** 创建者 */
createBy?: number;
/** 创建时间 */
createTime?: string;
/** 更新者 */
updateBy?: number;
/** 更新时间 */
updateTime?: string;
}
export interface ScholarshipBatchForm {
/** 主键ID */
id?: string | number;
/** 批次编码 */
batchCode?: string;
/** 批次名称 */
batchName?: string;
/** 学年 */
academicYear?: string;
/** 学期 */
semester?: number;
/** 批次类型 */
batchType?: string;
/** 开始时间 */
startTime?: string;
/** 结束时间 */
endTime?: string;
/** 申请开始时间 */
applyStartTime?: string;
/** 申请结束时间 */
applyEndTime?: string;
/** 审核开始时间 */
reviewStartTime?: string;
/** 审核结束时间 */
reviewEndTime?: string;
/** 状态 */
status?: string;
/** 批次描述 */
description?: string;
/** 通知内容 */
noticeContent?: string;
/** 关联的奖学金类型ID列表 */
scholarshipTypeIds?: Array<string | number>;
}
export interface ScholarshipBatchQuery extends PageQuery {
/** 批次编码 */
batchCode?: string;
/** 批次名称 */
batchName?: string;
/** 学年 */
academicYear?: string;
/** 学期 */
semester?: number;
/** 批次类型 */
batchType?: string;
/** 状态 */
status?: string;
/** 创建时间 */
createTime?: string;
}
// 查询奖学金评定批次列表
export function listScholarshipBatch(query: ScholarshipBatchQuery): AxiosPromise<ScholarshipBatchVO[]> {
return request({
url: '/jcbt/scholarshipBatch/list',
method: 'get',
params: query
});
}
// 查询奖学金评定批次详细
export function getScholarshipBatch(id: string | number): AxiosPromise<ScholarshipBatchVO> {
return request({
url: '/jcbt/scholarshipBatch/' + id,
method: 'get'
});
}
// 新增奖学金评定批次
export function addScholarshipBatch(data: ScholarshipBatchForm) {
return request({
url: '/jcbt/scholarshipBatch',
method: 'post',
data: data
});
}
// 修改奖学金评定批次
export function updateScholarshipBatch(data: ScholarshipBatchForm) {
return request({
url: '/jcbt/scholarshipBatch',
method: 'put',
data: data
});
}
// 删除奖学金评定批次
export function delScholarshipBatch(id: string | number | Array<string | number>) {
return request({
url: '/jcbt/scholarshipBatch/' + id,
method: 'delete'
});
}
// 导出奖学金评定批次
export function exportScholarshipBatch(query: ScholarshipBatchQuery) {
return request({
url: '/jcbt/scholarshipBatch/export',
method: 'post',
data: query
});
}
// 根据批次编码查询批次信息
export function getScholarshipBatchByCode(batchCode: string): AxiosPromise<ScholarshipBatchVO> {
return request({
url: '/jcbt/scholarshipBatch/getByCode/' + batchCode,
method: 'get'
});
}
// 根据学年和学期查询批次列表
export function getScholarshipBatchByAcademicYear(academicYear: string, semester?: number): AxiosPromise<ScholarshipBatchVO[]> {
return request({
url: '/jcbt/scholarshipBatch/getByAcademicYear',
method: 'get',
params: {
academicYear,
semester
}
});
}
// 查询启用状态的批次列表
export function getActiveBatches(): AxiosPromise<ScholarshipBatchVO[]> {
return request({
url: '/jcbt/scholarshipBatch/getActiveBatches',
method: 'get'
});
}
// 检查批次编码是否唯一
export function checkBatchCodeUnique(batchCode: string, id?: string | number): AxiosPromise<boolean> {
return request({
url: '/jcbt/scholarshipBatch/checkBatchCodeUnique',
method: 'get',
params: {
batchCode,
id
}
});
}
// 更改批次状态
export function changeScholarshipBatchStatus(id: string | number, status: string) {
return request({
url: '/jcbt/scholarshipBatch/changeStatus',
method: 'put',
params: {
id,
status
}
});
}
// 查询当前进行中的批次
export function getCurrentActiveBatches(): AxiosPromise<ScholarshipBatchVO[]> {
return request({
url: '/jcbt/scholarshipBatch/getCurrentActiveBatches',
method: 'get'
});
}
// 根据状态查询批次列表
export function getScholarshipBatchByStatus(status: string): AxiosPromise<ScholarshipBatchVO[]> {
return request({
url: '/jcbt/scholarshipBatch/getByStatus/' + status,
method: 'get'
});
}
// 检查批次时间是否有冲突
export function checkTimeConflict(data: ScholarshipBatchForm): AxiosPromise<boolean> {
return request({
url: '/jcbt/scholarshipBatch/checkTimeConflict',
method: 'post',
data: data
});
}
+217
View File
@@ -0,0 +1,217 @@
import request from '@/utils/request';
// 奖学金名额分配查询参数类型
export interface ScholarshipQuotaQuery extends PageQuery {
batchId?: number;
scholarshipTypeId?: number;
allocationLevel?: string;
targetDeptId?: number;
status?: string;
isLocked?: number;
}
// 奖学金名额分配对象类型
export interface ScholarshipQuotaVO {
id?: number;
batchId: number;
batchName?: string;
scholarshipTypeId: number;
scholarshipTypeName?: string;
allocationLevel: string;
targetDeptId: number;
targetDeptName?: string;
parentQuotaId?: number;
totalQuota: number;
usedQuota?: number;
availableQuota?: number;
reservedQuota?: number;
allocationRule?: string;
allocationBasis?: string;
allocationMethod?: string;
isAdjustable?: number;
isLocked?: number;
status?: string;
remark?: string;
createBy?: number;
createTime?: string;
updateBy?: number;
updateTime?: string;
children?: ScholarshipQuotaVO[];
}
// 奖学金名额分配表单类型
export interface ScholarshipQuotaForm {
id?: number;
batchId: number;
scholarshipTypeId: number;
allocationLevel: string;
targetDeptId: number;
parentQuotaId?: number;
totalQuota: number;
usedQuota?: number;
reservedQuota?: number;
allocationRule?: string;
allocationBasis?: string;
allocationMethod?: string;
isAdjustable?: number;
status?: string;
remark?: string;
}
// 名额使用统计类型
export interface QuotaStatistics {
totalQuota: number;
usedQuota: number;
availableQuota: number;
usageRate: number;
allocationCount: number;
}
// 批量分配名额类型
export interface BatchAllocateQuota {
allocationLevel: string;
targetDeptId: number;
totalQuota: number;
allocationBasis?: string;
}
// 查询奖学金名额分配列表
export const listScholarshipQuota = (query: ScholarshipQuotaQuery) => {
return request({
url: '/jcbt/scholarshipQuota/list',
method: 'get',
params: query
});
};
// 按批次查询名额分配
export const listScholarshipQuotaByBatch = (batchId: number) => {
return request({
url: `/jcbt/scholarshipQuota/listByBatch/${batchId}`,
method: 'get'
});
};
// 按批次和类型查询名额分配
export const listScholarshipQuotaByBatchAndType = (batchId: number, scholarshipTypeId: number) => {
return request({
url: '/jcbt/scholarshipQuota/listByBatchAndType',
method: 'get',
params: { batchId, scholarshipTypeId }
});
};
// 查询名额分配树形结构
export const getScholarshipQuotaTree = (batchId: number, scholarshipTypeId?: number) => {
return request({
url: '/jcbt/scholarshipQuota/tree',
method: 'get',
params: { batchId, scholarshipTypeId }
});
};
// 查询名额使用统计
export const getScholarshipQuotaStatistics = (batchId: number) => {
return request({
url: `/jcbt/scholarshipQuota/statistics/${batchId}`,
method: 'get'
});
};
// 查询奖学金名额分配详细
export const getScholarshipQuota = (id: number) => {
return request({
url: `/jcbt/scholarshipQuota/${id}`,
method: 'get'
});
};
// 新增奖学金名额分配
export const addScholarshipQuota = (data: ScholarshipQuotaForm) => {
return request({
url: '/jcbt/scholarshipQuota',
method: 'post',
data: data
});
};
// 修改奖学金名额分配
export const updateScholarshipQuota = (data: ScholarshipQuotaForm) => {
return request({
url: '/jcbt/scholarshipQuota',
method: 'put',
data: data
});
};
// 删除奖学金名额分配
export const delScholarshipQuota = (ids: string | number) => {
return request({
url: `/jcbt/scholarshipQuota/${ids}`,
method: 'delete'
});
};
// 导出奖学金名额分配
export const exportScholarshipQuota = (query: ScholarshipQuotaQuery) => {
return request({
url: '/jcbt/scholarshipQuota/export',
method: 'post',
data: query
});
};
// 批量分配名额
export const batchAllocateQuota = (batchId: number, scholarshipTypeId: number, data: BatchAllocateQuota[]) => {
return request({
url: '/jcbt/scholarshipQuota/batchAllocate',
method: 'post',
params: { batchId, scholarshipTypeId },
data: data
});
};
// 调整名额分配
export const adjustQuota = (fromQuotaId: number, toQuotaId: number, adjustAmount: number, reason: string) => {
return request({
url: '/jcbt/scholarshipQuota/adjust',
method: 'post',
params: { fromQuotaId, toQuotaId, adjustAmount, reason }
});
};
// 锁定/解锁名额分配
export const lockScholarshipQuota = (id: number, isLocked: number) => {
return request({
url: `/jcbt/scholarshipQuota/lock/${id}`,
method: 'put',
params: { isLocked }
});
};
// 自动分配名额
export const autoAllocateQuota = (batchId: number, scholarshipTypeId: number, allocationRule: string) => {
return request({
url: '/jcbt/scholarshipQuota/autoAllocate',
method: 'post',
params: { batchId, scholarshipTypeId, allocationRule }
});
};
// 复制名额分配
export const copyScholarshipQuota = (sourceBatchId: number, targetBatchId: number) => {
return request({
url: '/jcbt/scholarshipQuota/copy',
method: 'post',
params: { sourceBatchId, targetBatchId }
});
};
// 更新名额使用
export const updateQuotaUsage = (id: number, usedQuota: number) => {
return request({
url: `/jcbt/scholarshipQuota/updateUsage/${id}`,
method: 'put',
params: { usedQuota }
});
};
+331
View File
@@ -0,0 +1,331 @@
import request from '@/utils/request';
import { AxiosPromise } from 'axios';
export interface ScholarshipTypeVO {
/** 主键ID */
id?: string | number;
/** 奖学金类型编码 */
typeCode?: string;
/** 奖学金名称 */
typeName?: string;
/** 奖学金类别 */
typeCategory?: string;
/** 奖学金等级 */
levelType?: string;
/** 奖励金额 */
awardAmount?: number;
/** 奖学金描述 */
description?: string;
/** 申请指南 */
applicationGuide?: string;
/** 兼得类型 */
compatibilityType?: string;
/** 排序号 */
sortOrder?: number;
/** 状态 */
status?: number;
/** 是否系统内置 */
isSystem?: number;
/** 创建者 */
createBy?: number;
/** 创建时间 */
createTime?: string;
/** 更新者 */
updateBy?: number;
/** 更新时间 */
updateTime?: string;
}
export interface ScholarshipTypeForm {
/** 主键ID */
id?: string | number;
/** 奖学金类型编码 */
typeCode?: string;
/** 奖学金名称 */
typeName?: string;
/** 奖学金类别 */
typeCategory?: string;
/** 奖学金等级 */
levelType?: string;
/** 奖励金额 */
awardAmount?: number;
/** 奖学金描述 */
description?: string;
/** 申请指南 */
applicationGuide?: string;
/** 兼得类型 */
compatibilityType?: string;
/** 排序号 */
sortOrder?: number;
/** 状态 */
status?: number;
/** 是否系统内置 */
isSystem?: number;
/** 自定义互斥类型ID列表 */
exclusiveTypeIds?: Array<string | number>;
}
export interface ScholarshipTypeQuery {
/** 当前页码 */
pageNum?: number;
/** 每页显示记录数 */
pageSize?: number;
/** 奖学金类型编码 */
typeCode?: string;
/** 奖学金名称 */
typeName?: string;
/** 奖学金类别 */
typeCategory?: string;
/** 奖学金等级 */
levelType?: string;
/** 兼得类型 */
compatibilityType?: string;
/** 状态 */
status?: number;
/** 是否系统内置 */
isSystem?: number;
/** 创建时间范围 */
params?: {
beginCreateTime?: string;
endCreateTime?: string;
};
}
export interface ScholarshipTypeOption {
/** 选项值 */
value?: string | number;
/** 选项标签 */
label?: string;
/** 扩展数据 */
[key: string]: any;
}
/**
* 查询奖学金类型列表
* @param query 查询参数
*/
export const listScholarshipType = (query?: ScholarshipTypeQuery): AxiosPromise<ScholarshipTypeVO[]> => {
return request({
url: '/jcbt/scholarshipType/list',
method: 'get',
params: query
});
};
/**
* 查询奖学金类型详细信息
* @param id 奖学金类型ID
*/
export const getScholarshipType = (id: string | number): AxiosPromise<ScholarshipTypeVO> => {
return request({
url: `/jcbt/scholarshipType/${id}`,
method: 'get'
});
};
/**
* 新增奖学金类型
* @param data 奖学金类型数据
*/
export const addScholarshipType = (data: ScholarshipTypeForm) => {
return request({
url: '/jcbt/scholarshipType',
method: 'post',
data: data
});
};
/**
* 修改奖学金类型
* @param data 奖学金类型数据
*/
export const updateScholarshipType = (data: ScholarshipTypeForm) => {
return request({
url: '/jcbt/scholarshipType',
method: 'put',
data: data
});
};
/**
* 删除奖学金类型
* @param id 奖学金类型ID
*/
export const delScholarshipType = (id: string | number | Array<string | number>) => {
return request({
url: `/jcbt/scholarshipType/${id}`,
method: 'delete'
});
};
/**
* 导出奖学金类型数据
* @param query 查询参数
*/
export const exportScholarshipType = (query?: ScholarshipTypeQuery) => {
return request({
url: '/jcbt/scholarshipType/export',
method: 'post',
data: query
});
};
/**
* 根据名称查询奖学金类型
* @param typeName 奖学金名称
*/
export const getScholarshipTypeByName = (typeName: string): AxiosPromise<ScholarshipTypeVO> => {
return request({
url: `/jcbt/scholarshipType/getByName/${typeName}`,
method: 'get'
});
};
/**
* 根据类别查询奖学金类型
* @param typeCategory 奖学金类别
*/
export const getScholarshipTypeByCategory = (typeCategory: string): AxiosPromise<ScholarshipTypeVO[]> => {
return request({
url: `/jcbt/scholarshipType/getByCategory/${typeCategory}`,
method: 'get'
});
};
/**
* 查询启用状态的奖学金类型
*/
export const getActiveScholarshipTypes = (): AxiosPromise<ScholarshipTypeVO[]> => {
return request({
url: '/jcbt/scholarshipType/getActiveTypes',
method: 'get'
});
};
/**
* 根据兼得类型查询奖学金类型
* @param compatibilityType 兼得类型
*/
export const getScholarshipTypeByCompatibilityType = (compatibilityType: string): AxiosPromise<ScholarshipTypeVO[]> => {
return request({
url: `/jcbt/scholarshipType/getByCompatibilityType/${compatibilityType}`,
method: 'get'
});
};
/**
* 检查奖学金名称是否唯一
* @param typeName 奖学金名称
* @param id 排除的ID
*/
export const checkScholarshipTypeNameUnique = (typeName: string, id?: string | number) => {
return request({
url: '/jcbt/scholarshipType/checkTypeNameUnique',
method: 'get',
params: { typeName, id }
});
};
/**
* 批量更新排序号
* @param data 排序数据
*/
export const batchUpdateSort = (data: Array<{ id: string | number; sortOrder: number }>) => {
return request({
url: '/jcbt/scholarshipType/batchUpdateSort',
method: 'put',
data: data
});
};
/**
* 启用/停用奖学金类型
* @param id 奖学金类型ID
* @param status 状态
*/
export const changeScholarshipTypeStatus = (id: string | number, status: number) => {
return request({
url: '/jcbt/scholarshipType/changeStatus',
method: 'put',
params: { id, status }
});
};
/**
* 获取奖学金类型选项列表
*/
export const getScholarshipTypeOptions = (): AxiosPromise<ScholarshipTypeOption[]> => {
return request({
url: '/jcbt/scholarshipType/options',
method: 'get'
});
};
// 兼得关系配置相关接口
export interface CompatibilityConfig {
/** 所有可配置的奖学金类型 */
allTypes?: ScholarshipTypeVO[];
/** 互斥的奖学金类型ID列表 */
exclusiveTypeIds?: number[];
/** 兼容的奖学金类型ID列表 */
compatibleTypeIds?: number[];
/** 互斥的奖学金类型详细信息(包含名称) */
exclusiveTypes?: ScholarshipTypeVO[];
/** 兼容的奖学金类型详细信息(包含名称) */
compatibleTypes?: ScholarshipTypeVO[];
}
/**
* 获取奖学金类型的兼得关系配置
* @param id 奖学金类型ID
*/
export const getCompatibilityConfig = (id: string | number): AxiosPromise<CompatibilityConfig> => {
return request({
url: `/jcbt/scholarshipType/${id}/compatibility`,
method: 'get'
});
};
/**
* 保存奖学金类型的兼得关系配置
* @param id 奖学金类型ID
* @param config 配置数据
*/
export const saveCompatibilityConfig = (
id: string | number,
config: {
exclusiveTypeIds?: number[];
compatibleTypeIds?: number[];
}
) => {
return request({
url: `/jcbt/scholarshipType/${id}/compatibility`,
method: 'post',
data: config
});
};
/**
* 检查两个奖学金类型是否兼容
* @param typeId1 奖学金类型1
* @param typeId2 奖学金类型2
*/
export const checkCompatibility = (typeId1: string | number, typeId2: string | number): AxiosPromise<boolean> => {
return request({
url: '/jcbt/scholarshipType/checkCompatibility',
method: 'get',
params: { typeId1, typeId2 }
});
};
/**
* 获取可与指定奖学金兼得的奖学金类型列表
* @param id 奖学金类型ID
*/
export const getCompatibleTypes = (id: string | number): AxiosPromise<ScholarshipTypeVO[]> => {
return request({
url: `/jcbt/scholarshipType/${id}/compatibleTypes`,
method: 'get'
});
};
+503
View File
@@ -0,0 +1,503 @@
/**
* 奖学金申请模块 - 类型定义
*/
/**
* 奖学金申请记录 VO
*/
export interface ScholarshipApplicationVO {
/**
* 主键ID
*/
id: string | number;
/**
* 租户编号
*/
tenantId: string;
/**
* 学年
*/
academicYear: string;
/**
* 学期
*/
semester: string;
/**
* 奖学金类型
*/
scholarshipType: string;
/**
* 奖学金等级
*/
scholarshipLevel: string;
/**
* 奖学金金额
*/
amount?: number;
/**
* 学生学号
*/
studentId: string;
/**
* 学生姓名
*/
studentName: string;
/**
* 学生性别
*/
studentGender?: string;
/**
* 学生联系方式
*/
studentPhone?: string;
/**
* 学生邮箱
*/
studentEmail?: string;
/**
* 学院名称
*/
collegeName?: string;
/**
* 专业名称
*/
majorName?: string;
/**
* 班级名称
*/
className?: string;
/**
* 年级
*/
grade?: string;
/**
* 学业成绩排名
*/
academicRanking?: number;
/**
* 综合测评排名
*/
comprehensiveRanking?: number;
/**
* 申请理由
*/
applicationReason?: string;
/**
* 申请状态:draft-草稿,submitted-已提交,approved-已通过,rejected-已驳回
*/
applicationStatus: string;
/**
* 申请时间
*/
applicationTime?: string;
/**
* 审核人ID
*/
auditUserId?: string | number;
/**
* 审核人姓名
*/
auditUserName?: string;
/**
* 审核时间
*/
auditTime?: string;
/**
* 审核备注
*/
auditRemark?: string;
/**
* 审核状态:pending-待审核,approved-已通过,rejected-已驳回
*/
auditStatus?: string;
/**
* 是否获奖:0-否,1-是
*/
isAwarded?: string;
/**
* 获奖时间
*/
awardTime?: string;
/**
* 备注
*/
remark?: string;
/**
* 创建人
*/
createBy?: string | number;
/**
* 创建时间
*/
createTime?: string;
/**
* 更新人
*/
updateBy?: string | number;
/**
* 更新时间
*/
updateTime?: string;
}
/**
* 奖学金申请记录表单类型
*/
export interface ScholarshipApplicationForm {
/**
* 主键ID
*/
id?: string | number;
/**
* 学年
*/
academicYear: string;
/**
* 学期
*/
semester: string;
/**
* 奖学金类型
*/
scholarshipType: string;
/**
* 奖学金等级
*/
scholarshipLevel: string;
/**
* 奖学金金额
*/
amount?: number;
/**
* 学生学号
*/
studentId: string;
/**
* 学生姓名
*/
studentName: string;
/**
* 学生性别
*/
studentGender?: string;
/**
* 学生联系方式
*/
studentPhone?: string;
/**
* 学生邮箱
*/
studentEmail?: string;
/**
* 学院名称
*/
collegeName?: string;
/**
* 专业名称
*/
majorName?: string;
/**
* 班级名称
*/
className?: string;
/**
* 年级
*/
grade?: string;
/**
* 学业成绩排名
*/
academicRanking?: number;
/**
* 综合测评排名
*/
comprehensiveRanking?: number;
/**
* 申请理由
*/
applicationReason?: string;
/**
* 申请状态
*/
applicationStatus?: string;
/**
* 审核备注
*/
auditRemark?: string;
/**
* 是否获奖
*/
isAwarded?: string;
/**
* 备注
*/
remark?: string;
}
/**
* 奖学金申请记录查询参数
*/
export interface ScholarshipApplicationQuery extends PageQuery {
/**
* 学年
*/
academicYear?: string;
/**
* 学期
*/
semester?: string;
/**
* 奖学金类型
*/
scholarshipType?: string;
/**
* 奖学金等级
*/
scholarshipLevel?: string;
/**
* 学生学号
*/
studentId?: string;
/**
* 学生姓名
*/
studentName?: string;
/**
* 学院名称
*/
collegeName?: string;
/**
* 专业名称
*/
majorName?: string;
/**
* 班级名称
*/
className?: string;
/**
* 申请状态
*/
applicationStatus?: string;
/**
* 审核状态
*/
auditStatus?: string;
/**
* 是否获奖
*/
isAwarded?: string;
/**
* 申请时间范围
*/
applicationTimeRange?: string[];
/**
* 审核时间范围
*/
auditTimeRange?: string[];
/**
* 获奖时间范围
*/
awardTimeRange?: string[];
}
/**
* 班级奖学金名额 VO
*/
export interface ScholarshipClassQuotaVO {
/**
* 主键ID
*/
id: string | number;
/**
* 学年
*/
academicYear: string;
/**
* 学期
*/
semester: string;
/**
* 奖学金类型
*/
scholarshipType: string;
/**
* 奖学金等级
*/
scholarshipLevel: string;
/**
* 学院名称
*/
collegeName: string;
/**
* 专业名称
*/
majorName: string;
/**
* 班级名称
*/
className: string;
/**
* 分配名额
*/
quotaCount: number;
/**
* 已申请名额
*/
appliedCount?: number;
/**
* 已获奖名额
*/
awardedCount?: number;
/**
* 创建时间
*/
createTime?: string;
}
/**
* 奖学金附件 VO
*/
export interface ScholarshipAttachmentVO {
/**
* 主键ID
*/
id: string | number;
/**
* 申请记录ID
*/
applicationId: string | number;
/**
* 附件类型
*/
attachmentType: string;
/**
* 文件名称
*/
fileName: string;
/**
* 文件路径
*/
filePath: string;
/**
* 文件大小
*/
fileSize?: number;
/**
* 上传时间
*/
uploadTime?: string;
/**
* 备注
*/
remark?: string;
}
/**
* 分页查询基础接口
*/
export interface PageQuery {
/**
* 当前页码
*/
pageNum?: number;
/**
* 每页显示记录数
*/
pageSize?: number;
/**
* 排序字段
*/
orderByColumn?: string;
/**
* 排序方向
*/
isAsc?: string;
}
@@ -0,0 +1,488 @@
<template>
<div class="app-container">
<el-card class="detail-card">
<template #header>
<div class="detail-header">
<span class="detail-title">奖学金申请详情</span>
<div class="header-actions">
<el-button
v-if="canEdit"
type="primary"
icon="Edit"
@click="handleEdit"
>
编辑
</el-button>
<el-button @click="goBack" class="back-button">
<el-icon><ArrowLeft /></el-icon>
返回
</el-button>
</div>
</div>
</template>
<div v-loading="loading">
<!-- 基本信息 -->
<div class="detail-section">
<div class="section-title">
<el-icon><Document /></el-icon>
基本信息
</div>
<el-descriptions :column="2" border>
<el-descriptions-item label="学年">
{{ detail.academicYear || '-' }}
</el-descriptions-item>
<el-descriptions-item label="学期">
{{ detail.semester || '-' }}
</el-descriptions-item>
<el-descriptions-item label="奖学金类型">
{{ detail.scholarshipType || '-' }}
</el-descriptions-item>
<el-descriptions-item label="奖学金等级">
{{ detail.scholarshipLevel || '-' }}
</el-descriptions-item>
<el-descriptions-item label="奖学金金额">
<span v-if="detail.amount" class="amount-text">¥{{ detail.amount }}</span>
<span v-else>-</span>
</el-descriptions-item>
</el-descriptions>
</div>
<!-- 学生信息 -->
<div class="detail-section">
<div class="section-title">
<el-icon><User /></el-icon>
学生信息
</div>
<el-descriptions :column="2" border>
<el-descriptions-item label="学生学号">
{{ detail.studentId || '-' }}
</el-descriptions-item>
<el-descriptions-item label="学生姓名">
{{ detail.studentName || '-' }}
</el-descriptions-item>
<el-descriptions-item label="学生性别">
{{ detail.studentGender || '-' }}
</el-descriptions-item>
<el-descriptions-item label="联系方式">
{{ detail.studentPhone || '-' }}
</el-descriptions-item>
<el-descriptions-item label="学生邮箱" :span="2">
{{ detail.studentEmail || '-' }}
</el-descriptions-item>
</el-descriptions>
</div>
<!-- 班级信息 -->
<div class="detail-section">
<div class="section-title">
<el-icon><School /></el-icon>
班级信息
</div>
<el-descriptions :column="2" border>
<el-descriptions-item label="学院名称">
{{ detail.collegeName || '-' }}
</el-descriptions-item>
<el-descriptions-item label="专业名称">
{{ detail.majorName || '-' }}
</el-descriptions-item>
<el-descriptions-item label="班级名称">
{{ detail.className || '-' }}
</el-descriptions-item>
<el-descriptions-item label="年级">
{{ detail.grade || '-' }}
</el-descriptions-item>
</el-descriptions>
</div>
<!-- 成绩信息 -->
<div class="detail-section">
<div class="section-title">
<el-icon><Trophy /></el-icon>
成绩信息
</div>
<el-descriptions :column="2" border>
<el-descriptions-item label="学业成绩排名">
<span v-if="detail.academicRanking">第{{ detail.academicRanking }}名</span>
<span v-else>-</span>
</el-descriptions-item>
<el-descriptions-item label="综合测评排名">
<span v-if="detail.comprehensiveRanking">第{{ detail.comprehensiveRanking }}名</span>
<span v-else>-</span>
</el-descriptions-item>
</el-descriptions>
</div>
<!-- 申请信息 -->
<div class="detail-section">
<div class="section-title">
<el-icon><Edit /></el-icon>
申请信息
</div>
<el-descriptions :column="1" border>
<el-descriptions-item label="申请理由">
<div class="reason-content">
{{ detail.applicationReason || '-' }}
</div>
</el-descriptions-item>
<el-descriptions-item label="备注" v-if="detail.remark">
<div class="remark-content">
{{ detail.remark }}
</div>
</el-descriptions-item>
</el-descriptions>
</div>
<!-- 状态信息 -->
<div class="detail-section">
<div class="section-title">
<el-icon><InfoFilled /></el-icon>
状态信息
</div>
<el-descriptions :column="2" border>
<el-descriptions-item label="申请状态">
<dict-tag :options="application_status" :value="detail.applicationStatus" />
</el-descriptions-item>
<el-descriptions-item label="审核状态">
<dict-tag :options="audit_status" :value="detail.auditStatus" />
</el-descriptions-item>
<el-descriptions-item label="是否获奖">
<dict-tag :options="yes_no" :value="detail.isAwarded" />
</el-descriptions-item>
<el-descriptions-item label="申请时间">
{{ detail.applicationTime || '-' }}
</el-descriptions-item>
<el-descriptions-item label="审核时间" v-if="detail.auditTime">
{{ detail.auditTime }}
</el-descriptions-item>
<el-descriptions-item label="获奖时间" v-if="detail.awardTime">
{{ detail.awardTime }}
</el-descriptions-item>
</el-descriptions>
</div>
<!-- 审核信息 -->
<div v-if="detail.auditRemark || detail.auditUserName" class="detail-section">
<div class="section-title">
<el-icon><ChatLineSquare /></el-icon>
审核信息
</div>
<el-descriptions :column="1" border>
<el-descriptions-item label="审核人" v-if="detail.auditUserName">
{{ detail.auditUserName }}
</el-descriptions-item>
<el-descriptions-item label="审核备注" v-if="detail.auditRemark">
<div class="audit-remark">
{{ detail.auditRemark }}
</div>
</el-descriptions-item>
</el-descriptions>
</div>
<!-- 附件信息 -->
<div v-if="attachmentList.length > 0" class="detail-section">
<div class="section-title">
<el-icon><Paperclip /></el-icon>
相关附件
</div>
<el-table :data="attachmentList" border>
<el-table-column label="序号" type="index" width="60" align="center" />
<el-table-column label="附件类型" prop="attachmentType" width="120" align="center" />
<el-table-column label="文件名称" prop="fileName" align="center" />
<el-table-column label="文件大小" prop="fileSize" width="100" align="center">
<template #default="scope">
{{ formatFileSize(scope.row.fileSize) }}
</template>
</el-table-column>
<el-table-column label="上传时间" prop="uploadTime" width="180" align="center" />
<el-table-column label="操作" width="100" align="center">
<template #default="scope">
<el-button
type="primary"
link
icon="Download"
@click="handleDownload(scope.row)"
>
下载
</el-button>
</template>
</el-table-column>
</el-table>
</div>
<!-- 操作日志 -->
<div class="detail-section">
<div class="section-title">
<el-icon><Clock /></el-icon>
操作记录
</div>
<el-timeline>
<el-timeline-item
v-if="detail.createTime"
:timestamp="detail.createTime"
type="primary"
>
<div class="timeline-content">
<div class="timeline-title">创建申请</div>
<div class="timeline-desc">申请记录已创建</div>
</div>
</el-timeline-item>
<el-timeline-item
v-if="detail.applicationTime && detail.applicationStatus !== 'draft'"
:timestamp="detail.applicationTime"
type="success"
>
<div class="timeline-content">
<div class="timeline-title">提交申请</div>
<div class="timeline-desc">申请已提交,等待审核</div>
</div>
</el-timeline-item>
<el-timeline-item
v-if="detail.auditTime"
:timestamp="detail.auditTime"
:type="detail.auditStatus === 'approved' ? 'success' : 'danger'"
>
<div class="timeline-content">
<div class="timeline-title">
{{ detail.auditStatus === 'approved' ? '审核通过' : '审核驳回' }}
</div>
<div class="timeline-desc">
审核人:{{ detail.auditUserName || '系统' }}
<span v-if="detail.auditRemark">,备注:{{ detail.auditRemark }}</span>
</div>
</div>
</el-timeline-item>
<el-timeline-item
v-if="detail.awardTime"
:timestamp="detail.awardTime"
type="warning"
>
<div class="timeline-content">
<div class="timeline-title">获得奖学金</div>
<div class="timeline-desc">恭喜获得奖学金</div>
</div>
</el-timeline-item>
</el-timeline>
</div>
</div>
</el-card>
</div>
</template>
<script setup name="ScholarshipApplicationDetail" lang="ts">
import { getScholarshipApplication, listScholarshipAttachment } from '@/api/jcbt';
import { ScholarshipApplicationVO, ScholarshipAttachmentVO } from '@/api/jcbt/types';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const { application_status, audit_status, yes_no } = proxy?.useDict('application_status', 'audit_status', 'yes_no');
const route = useRoute();
const router = useRouter();
// 数据定义
const loading = ref(true);
const detail = ref<ScholarshipApplicationVO>({});
const attachmentList = ref<ScholarshipAttachmentVO[]>([]);
// 计算属性
const canEdit = computed(() => {
// 草稿状态或审核驳回状态可以编辑
return detail.value.applicationStatus === 'draft' || detail.value.auditStatus === 'rejected';
});
/** 获取详情数据 */
const getDetail = async () => {
loading.value = true;
try {
const id = route.params.id as string;
// 获取申请详情
const detailRes = await getScholarshipApplication(id);
detail.value = detailRes.data;
// 获取附件列表
const attachmentRes = await listScholarshipAttachment(id);
attachmentList.value = attachmentRes.data;
} catch (error) {
proxy?.$modal.msgError('获取详情失败');
} finally {
loading.value = false;
}
};
/** 编辑操作 */
const handleEdit = () => {
router.push(`/jcbt/scholarship/application/form/${detail.value.id}`);
};
/** 返回 */
const goBack = () => {
router.back();
};
/** 格式化文件大小 */
const formatFileSize = (size: number): string => {
if (!size) return '-';
const units = ['B', 'KB', 'MB', 'GB'];
let index = 0;
let fileSize = size;
while (fileSize >= 1024 && index < units.length - 1) {
fileSize /= 1024;
index++;
}
return `${fileSize.toFixed(1)} ${units[index]}`;
};
/** 下载附件 */
const handleDownload = (row: ScholarshipAttachmentVO) => {
// 这里可以实现文件下载逻辑
window.open(row.filePath, '_blank');
};
// 组件挂载时获取数据
onMounted(() => {
getDetail();
});
</script>
<style scoped>
/* 详情卡片样式 */
.detail-card {
margin: 20px;
}
.detail-header {
display: flex;
justify-content: space-between;
align-items: center;
}
.detail-title {
font-size: 18px;
font-weight: 600;
color: #303133;
}
.header-actions {
display: flex;
gap: 10px;
}
.back-button {
display: flex;
align-items: center;
gap: 5px;
}
/* 详情分组样式 */
.detail-section {
margin-bottom: 30px;
padding: 20px;
background-color: #fafafa;
border-radius: 8px;
border-left: 4px solid #409eff;
}
.section-title {
display: flex;
align-items: center;
font-size: 16px;
font-weight: 600;
color: #303133;
margin-bottom: 20px;
padding-bottom: 10px;
border-bottom: 1px solid #e4e7ed;
}
.section-title .el-icon {
margin-right: 8px;
color: #409eff;
}
/* 内容样式 */
.amount-text {
color: #f56c6c;
font-weight: 600;
font-size: 16px;
}
.reason-content,
.remark-content,
.audit-remark {
line-height: 1.6;
color: #606266;
white-space: pre-wrap;
word-break: break-all;
}
/* 时间轴样式 */
.timeline-content {
padding-left: 10px;
}
.timeline-title {
font-weight: 600;
color: #303133;
margin-bottom: 5px;
}
.timeline-desc {
color: #909399;
font-size: 14px;
line-height: 1.5;
}
/* 描述列表样式调整 */
:deep(.el-descriptions__label) {
font-weight: 600;
color: #606266;
width: 120px;
}
:deep(.el-descriptions__content) {
color: #303133;
}
/* 表格样式 */
:deep(.el-table th) {
background-color: #f8f9fa;
color: #333;
font-weight: 600;
}
:deep(.el-table td) {
padding: 12px 0;
}
/* 响应式调整 */
@media (max-width: 768px) {
.detail-card {
margin: 10px;
}
.detail-header {
flex-direction: column;
gap: 10px;
}
.header-actions {
width: 100%;
justify-content: space-between;
}
.detail-section {
padding: 15px;
}
:deep(.el-descriptions) {
--el-descriptions-table-border: none;
}
:deep(.el-descriptions__label) {
width: 80px;
}
}
</style>
@@ -0,0 +1,718 @@
<template>
<div class="app-container">
<el-card class="form-card">
<template #header>
<div class="form-header">
<span class="form-title">{{ formTitle }}</span>
<el-button @click="goBack" class="back-button">
<el-icon><ArrowLeft /></el-icon>
返回
</el-button>
</div>
</template>
<el-form
ref="formRef"
:model="form"
:rules="rules"
label-width="140px"
v-loading="formLoading"
>
<!-- 批次选择 -->
<div class="form-section">
<div class="section-title">
<el-icon><Calendar /></el-icon>
批次信息
</div>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="申报批次" prop="batchId">
<el-select
v-model="form.batchId"
placeholder="请选择申报批次"
style="width: 100%"
:disabled="isView"
@change="handleBatchChange"
>
<el-option
v-for="batch in batchList"
: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="scholarshipTypeId">
<el-select
v-model="form.scholarshipTypeId"
placeholder="请选择奖学金类型"
style="width: 100%"
:disabled="isView || !form.batchId"
@change="handleScholarshipTypeChange"
>
<el-option
v-for="type in scholarshipTypes"
:key="type.id"
:value="type.id"
:disabled="type.quotaStatus === 'full'"
>
<div class="scholarship-type-option">
<span
:class="[
'type-name',
type.quotaStatus === 'available' || type.quotaStatus === 'unlimited' ? 'available' : 'full'
]"
>
{{ type.name }}
</span>
<span class="quota-info" v-if="type.quotaStatus === 'unlimited'">
<span class="quota-badge available">
无限制
</span>
</span>
<span class="quota-info" v-else>
<span
:class="[
'quota-badge',
type.quotaStatus === 'available' ? 'available' : 'full'
]"
>
{{ type.availableQuota || 0 }}/{{ type.totalQuota || 0 }}
</span>
</span>
</div>
</el-option>
</el-select>
</el-form-item>
</el-col>
</el-row>
</div>
<!-- 班级信息(自动获取) -->
<div class="form-section" v-if="classInfo.teacherId">
<div class="section-title">
<el-icon><School /></el-icon>
班级信息
</div>
<el-row :gutter="20">
<el-col :span="8">
<el-form-item label="学院名称">
<el-input v-model="classInfo.deptName" readonly />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="专业名称">
<el-input v-model="classInfo.majorName" readonly />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="班级名称">
<el-input v-model="classInfo.className" readonly />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="8">
<el-form-item label="年级">
<el-input v-model="classInfo.grade" readonly />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="班主任">
<el-input v-model="classInfo.teacherName" readonly />
</el-form-item>
</el-col>
</el-row>
</div>
<!-- 学生选择 -->
<div class="form-section">
<div class="section-title">
<el-icon><User /></el-icon>
学生选择
<span v-if="selectedStudents.length > 0" class="selection-count">
(已选择 {{ selectedStudents.length }} 人)
</span>
</div>
<!-- 学生选择表格 -->
<el-table
ref="studentsTable"
:data="filteredStudents"
v-loading="studentsLoading"
@selection-change="handleStudentSelectionChange"
style="width: 100%; margin-bottom: 20px;"
>
<el-table-column type="selection" width="55" />
<el-table-column prop="studentCode" label="学号" width="120" />
<el-table-column prop="studentName" label="姓名" width="100" />
<el-table-column prop="gender" label="性别" width="80" />
<el-table-column prop="phone" label="联系方式" width="130" />
<el-table-column prop="email" label="邮箱" />
<el-table-column label="在校状态" width="100">
<template #default="scope">
<el-tag :type="scope.row.status === '1' ? 'success' : 'danger'">
{{ scope.row.status === '1' ? '在校' : '离校' }}
</el-tag>
</template>
</el-table-column>
</el-table>
<!-- 学生搜索 -->
<div class="student-search">
<el-input
v-model="studentSearchKeyword"
placeholder="搜索学生姓名或学号"
style="width: 300px;"
clearable
/>
</div>
</div>
<!-- 申请信息 -->
<div class="form-section">
<div class="section-title">
<el-icon><Document /></el-icon>
申请信息
</div>
<el-row :gutter="20">
<el-col :span="24">
<el-form-item label="申请类型" prop="applicationType">
<el-select
v-model="form.applicationType"
placeholder="请选择申请类型"
style="width: 300px"
:disabled="isView"
>
<el-option label="教师推荐" value="RECOMMEND" />
<el-option label="直接指定" value="DIRECT" />
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="24">
<el-form-item label="申请原因" prop="applicationReason">
<el-input
v-model="form.applicationReason"
type="textarea"
:rows="4"
placeholder="请输入申请原因"
:disabled="isView"
maxlength="500"
show-word-limit
/>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="24">
<el-form-item label="申请说明" prop="applicationDescription">
<el-input
v-model="form.applicationDescription"
type="textarea"
:rows="4"
placeholder="请输入申请说明(选填)"
:disabled="isView"
maxlength="1000"
show-word-limit
/>
</el-form-item>
</el-col>
</el-row>
</div>
<!-- 操作按钮 -->
<div class="form-actions" v-if="!isView">
<el-button @click="goBack">取消</el-button>
<el-button type="primary" @click="submitForm" :loading="submitLoading">
{{ form.id ? '更新' : '提交申请' }}
</el-button>
</div>
</el-form>
</el-card>
<!-- 班级选择对话框 -->
<el-dialog v-model="showClassSelector" title="选择班级" width="600px" :close-on-click-modal="false">
<div class="class-selector">
<p class="tip">检测到您是多个班级的班主任,请选择要进行奖学金申请的班级:</p>
<el-radio-group v-model="selectedClassId" class="class-options">
<el-radio
v-for="option in classOptions"
:key="option.classId"
:value="option.classId"
class="class-option"
>
<div class="class-info">
<div class="class-name">{{ option.className }}</div>
<div class="class-detail">{{ option.collegeName }} - {{ option.majorName }} - {{ option.grade }}</div>
</div>
</el-radio>
</el-radio-group>
</div>
<template #footer>
<el-button @click="showClassSelector = false">取消</el-button>
<el-button type="primary" @click="confirmClassChoice" :disabled="!selectedClassId">确认选择</el-button>
</template>
</el-dialog>
</div>
</template>
<script setup name="ScholarshipApplicationForm">
import { ref, reactive, computed, onMounted } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { ElMessage, ElMessageBox } from 'element-plus';
import { ArrowLeft, Calendar, School, User, Document } from '@element-plus/icons-vue';
import {
getScholarshipApplication,
addScholarshipApplication,
updateScholarshipApplication,
getClassInfoByTeacher,
getClassStudents,
getScholarshipTypesByBatch,
getScholarshipTypesWithQuota,
batchSubmitByTeacher,
confirmClassSelection
} from '@/api/jcbt/scholarshipApplication';
import { getActiveBatches } from '@/api/jcbt/scholarshipBatch';
import { useUserStore } from '@/store/modules/user';
const route = useRoute();
const router = useRouter();
const userStore = useUserStore();
// 组件状态
const formRef = ref();
const studentsTable = ref();
const formLoading = ref(false);
const studentsLoading = ref(false);
const submitLoading = ref(false);
// 表单数据
const form = reactive({
id: null,
batchId: null,
scholarshipTypeId: null,
studentIds: [],
applicantType: 'TEACHER',
applicantId: userStore.userId,
applicantName: userStore.nickname,
applicationType: 'RECOMMEND',
applicationReason: '',
applicationDescription: ''
});
// 下拉选项数据
const batchList = ref([]);
const scholarshipTypes = ref([]);
const studentList = ref([]);
const selectedStudents = ref([]);
const studentSearchKeyword = ref('');
// 班级信息
const classInfo = reactive({
teacherId: userStore.userId,
teacherName: userStore.nickname,
classId: null,
className: '',
deptId: null,
deptName: '',
majorName: '',
grade: ''
});
// 班级选择对话框状态
const showClassSelector = ref(false);
const classOptions = ref([]);
const selectedClassId = ref('');
// 计算属性
const isView = computed(() => route.query.type === 'view');
const formTitle = computed(() => {
if (isView.value) return '查看奖学金申请';
return form.id ? '编辑奖学金申请' : '新增奖学金申请';
});
const filteredStudents = computed(() => {
if (!studentSearchKeyword.value) return studentList.value;
const keyword = studentSearchKeyword.value.toLowerCase();
return studentList.value.filter(student =>
student.studentName.toLowerCase().includes(keyword) ||
student.studentCode.toLowerCase().includes(keyword)
);
});
// 表单验证规则
const rules = {
batchId: [{ required: true, message: '请选择申报批次', trigger: 'change' }],
scholarshipTypeId: [{ required: true, message: '请选择奖学金类型', trigger: 'change' }],
applicationType: [{ required: true, message: '请选择申请类型', trigger: 'change' }],
applicationReason: [{ required: true, message: '请输入申请原因', trigger: 'blur' }]
};
// 初始化
onMounted(() => {
loadBatchList();
loadClassInfo();
if (route.params.id) {
loadScholarshipApplication(route.params.id);
}
});
// 加载批次列表
const loadBatchList = async () => {
try {
const response = await getActiveBatches();
batchList.value = response.data || [];
} catch (error) {
console.error('加载批次列表失败:', error);
ElMessage.error('加载批次列表失败');
}
};
// 加载班级信息
const loadClassInfo = async () => {
if (!userStore.userId) {
ElMessage.error('未获取到用户信息,请重新登录');
return;
}
try {
const res = await getClassInfoByTeacher(userStore.userId);
console.log('API响应:', res);
if (res && res.data) {
if (res.data.needSelection) {
// 需要用户选择班级
classOptions.value = res.data.classOptions;
showClassSelector.value = true;
console.log('需要用户选择班级,共', classOptions.value.length, '个选项');
} else if (res.data.error) {
ElMessage.error(res.data.error);
} else {
// 直接获取到班级信息
Object.assign(classInfo, res.data);
console.log('自动获取班级信息成功:', res.data);
if (classInfo.classId) {
await loadClassStudents(classInfo.classId);
}
}
}
} catch (error) {
console.error('获取班级信息失败:', error);
ElMessage.error('获取班级信息失败');
}
};
// 确认班级选择
const confirmClassChoice = async () => {
if (!selectedClassId.value) {
ElMessage.warning('请选择一个班级');
return;
}
try {
const res = await confirmClassSelection(userStore.userId, selectedClassId.value);
if (res && res.data && !res.data.error) {
Object.assign(classInfo, res.data);
showClassSelector.value = false;
selectedClassId.value = '';
ElMessage.success('班级选择成功');
if (classInfo.classId) {
await loadClassStudents(classInfo.classId);
}
} else {
ElMessage.error(res.data?.error || '确认班级选择失败');
}
} catch (error) {
console.error('确认班级选择失败:', error);
ElMessage.error('确认班级选择失败');
}
};
// 加载班级学生列表
const loadClassStudents = async (classId) => {
studentsLoading.value = true;
try {
const response = await getClassStudents(classId);
studentList.value = response.data || [];
} catch (error) {
console.error('加载班级学生失败:', error);
ElMessage.error('加载班级学生失败');
} finally {
studentsLoading.value = false;
}
};
// 批次变更处理
const handleBatchChange = async () => {
if (!form.batchId) {
scholarshipTypes.value = [];
form.scholarshipTypeId = null;
return;
}
try {
let response;
// 如果有班级信息,使用带名额信息的API
if (classInfo.classId) {
response = await getScholarshipTypesWithQuota(form.batchId, classInfo.classId);
} else {
// 否则使用普通API
response = await getScholarshipTypesByBatch(form.batchId);
}
const types = response.data || [];
// 确保每个类型都有必要的字段
scholarshipTypes.value = types.map(type => ({
...type,
hasQuota: type.hasQuota !== undefined ? type.hasQuota : true,
totalQuota: type.totalQuota || 0,
usedQuota: type.usedQuota || 0,
availableQuota: type.availableQuota || 0,
quotaStatus: type.quotaStatus || (type.totalQuota > 0 ? (type.availableQuota > 0 ? 'available' : 'full') : 'unlimited')
}));
form.scholarshipTypeId = null;
} catch (error) {
console.error('加载奖学金类型失败:', error);
ElMessage.error('加载奖学金类型失败');
}
};
// 奖学金类型变更处理
const handleScholarshipTypeChange = () => {
// 可以在这里添加额外的逻辑
};
// 学生选择变更处理
const handleStudentSelectionChange = (selection) => {
selectedStudents.value = selection;
form.studentIds = selection.map(student => student.id);
};
// 加载申请详情(编辑模式)
const loadScholarshipApplication = async (id) => {
formLoading.value = true;
try {
const response = await getScholarshipApplication(id);
const data = response.data;
Object.assign(form, data);
// 如果有批次ID,加载对应的奖学金类型
if (data.batchId) {
await handleBatchChange();
}
} catch (error) {
console.error('加载申请详情失败:', error);
ElMessage.error('加载申请详情失败');
} finally {
formLoading.value = false;
}
};
// 提交表单
const submitForm = async () => {
if (!formRef.value) return;
try {
await formRef.value.validate();
// 检查是否选择了学生
if (!form.studentIds || form.studentIds.length === 0) {
ElMessage.error('请至少选择一名学生');
return;
}
submitLoading.value = true;
const submitData = {
...form,
classId: classInfo.classId,
className: classInfo.className,
deptId: classInfo.deptId,
deptName: classInfo.deptName
};
if (form.id) {
await updateScholarshipApplication(submitData);
ElMessage.success('更新成功');
} else {
// 使用班主任批量申请接口
await batchSubmitByTeacher({
batchId: form.batchId,
scholarshipTypeId: form.scholarshipTypeId,
classId: classInfo.classId,
studentApplications: form.studentIds.map(studentId => ({
studentId,
applicationType: form.applicationType,
applicationReason: form.applicationReason,
applicationDescription: form.applicationDescription
}))
});
ElMessage.success('申请提交成功');
}
goBack();
} catch (error) {
console.error('提交失败:', error);
ElMessage.error('提交失败');
} finally {
submitLoading.value = false;
}
};
// 返回
const goBack = () => {
router.back();
};
</script>
<style scoped>
.form-card {
margin: 20px;
}
.form-header {
display: flex;
justify-content: space-between;
align-items: center;
}
.form-title {
font-size: 18px;
font-weight: 600;
}
.back-button {
border: none;
background: none;
color: #606266;
}
.back-button:hover {
color: #409eff;
}
.form-section {
margin-bottom: 30px;
padding: 20px;
background: #f8f9fa;
border-radius: 8px;
}
.section-title {
font-size: 16px;
font-weight: 600;
margin-bottom: 20px;
color: #303133;
display: flex;
align-items: center;
gap: 8px;
}
.selection-count {
font-size: 14px;
color: #409eff;
font-weight: normal;
}
.student-search {
margin-top: 10px;
text-align: right;
}
.form-actions {
text-align: center;
padding: 20px 0;
border-top: 1px solid #ebeef5;
margin-top: 20px;
}
.form-actions .el-button {
margin: 0 10px;
padding: 10px 30px;
}
.class-selector .tip {
margin-bottom: 20px;
color: #666;
font-size: 14px;
}
.class-options .class-option {
display: block;
margin-bottom: 12px;
padding: 12px;
border: 1px solid #e4e7ed;
border-radius: 6px;
transition: all 0.3s;
}
.class-options .class-option:hover {
border-color: #409eff;
background-color: #f0f8ff;
}
.class-info .class-name {
font-weight: bold;
font-size: 16px;
color: #303133;
margin-bottom: 4px;
}
.class-info .class-detail {
font-size: 14px;
color: #606266;
}
/* 奖学金类型选择框样式 */
.scholarship-type-option {
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
}
.type-name {
flex: 1;
font-weight: 500;
}
.type-name.available {
color: #67c23a;
}
.type-name.full {
color: #f56c6c;
}
.quota-info {
margin-left: 8px;
}
.quota-badge {
font-size: 12px;
padding: 2px 6px;
border-radius: 12px;
font-weight: bold;
min-width: 40px;
text-align: center;
}
.quota-badge.available {
background-color: #f0f9ff;
color: #67c23a;
border: 1px solid #b3e19d;
}
.quota-badge.full {
background-color: #fef0f0;
color: #f56c6c;
border: 1px solid #fbc4c4;
}
</style>
@@ -0,0 +1,961 @@
<template>
<div class="app-container">
<el-row :gutter="20">
<!-- 查询面板 -->
<el-col :span="24" class="search-container">
<el-form :model="queryParams" ref="queryFormRef" :inline="true" v-show="showSearch" label-width="80px">
<el-form-item label="学生姓名" prop="studentName">
<el-input
v-model="queryParams.studentName"
placeholder="请输入学生姓名"
clearable
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item label="学号" prop="studentCode">
<el-input
v-model="queryParams.studentCode"
placeholder="请输入学号"
clearable
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item label="申请状态" prop="applicationStatus">
<el-select v-model="queryParams.applicationStatus" placeholder="请选择申请状态" clearable>
<el-option label="草稿" value="draft" />
<el-option label="已提交" value="submitted" />
<el-option label="已审核" value="reviewed" />
</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-col>
<!-- 工具栏 -->
<el-col :span="24">
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button
type="primary"
plain
icon="Plus"
@click="handleAdd"
v-hasPermi="['jcbt:scholarshipApplication:add']"
>班主任批量申请</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="success"
plain
icon="Edit"
:disabled="single"
@click="handleUpdate()"
v-hasPermi="['jcbt:scholarshipApplication:edit']"
>修改</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="danger"
plain
icon="Delete"
:disabled="multiple"
@click="handleDelete()"
v-hasPermi="['jcbt:scholarshipApplication:remove']"
>删除</el-button>
</el-col>
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
</el-col>
<!-- 数据表格 -->
<el-col :span="24">
<el-table v-loading="loading" :data="applicationList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="学号" align="center" prop="studentCode" min-width="120" />
<el-table-column label="学生姓名" align="center" prop="studentName" min-width="100" />
<el-table-column label="班级" align="center" prop="className" min-width="120" />
<el-table-column label="奖学金类型" align="center" prop="scholarshipType" min-width="120" />
<el-table-column label="等级" align="center" prop="scholarshipLevel" min-width="80" />
<el-table-column label="金额" align="center" prop="amount" min-width="100">
<template #default="scope">
<span class="text-primary font-bold">¥{{ scope.row.amount }}</span>
</template>
</el-table-column>
<el-table-column label="申请状态" align="center" prop="applicationStatus" min-width="100">
<template #default="scope">
<el-tag v-if="scope.row.applicationStatus === 'draft'" type="info">草稿</el-tag>
<el-tag v-else-if="scope.row.applicationStatus === 'submitted'" type="warning">已提交</el-tag>
<el-tag v-else-if="scope.row.applicationStatus === 'reviewed'" type="success">已审核</el-tag>
</template>
</el-table-column>
<el-table-column label="审核状态" align="center" prop="auditStatus" min-width="100">
<template #default="scope">
<el-tag v-if="scope.row.auditStatus === 'pending'" type="warning">待审核</el-tag>
<el-tag v-else-if="scope.row.auditStatus === 'approved'" type="success">通过</el-tag>
<el-tag v-else-if="scope.row.auditStatus === 'rejected'" type="danger">拒绝</el-tag>
</template>
</el-table-column>
<el-table-column label="申请时间" align="center" prop="applicationTime" min-width="160">
<template #default="scope">
<span>{{ parseTime(scope.row.applicationTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="160">
<template #default="scope">
<el-tooltip content="修改" placement="top">
<el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['jcbt:scholarshipApplication:edit']"></el-button>
</el-tooltip>
<el-tooltip content="删除" placement="top">
<el-button link type="danger" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['jcbt:scholarshipApplication:remove']"></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-col>
</el-row>
<!-- 班主任批量申请奖学金对话框 -->
<el-dialog :title="dialog.title" v-model="dialog.visible" width="1000px" append-to-body>
<el-form ref="scholarshipApplicationRef" :model="form" :rules="rules" label-width="120px">
<!-- 第一步:选择申报批次 -->
<el-card class="mb-4">
<template #header>
<span>第一步:选择申报批次</span>
</template>
<el-form-item label="申报批次" prop="batchId" required>
<el-select
v-model="selectedBatch"
placeholder="请选择申报批次"
@change="handleBatchChange"
style="width: 100%"
>
<el-option
v-for="batch in batchList"
:key="batch.id"
:label="`${batch.batchName} (${batch.academicYear} ${batch.semester}学期)`"
:value="batch"
/>
</el-select>
</el-form-item>
<div v-if="selectedBatch" class="batch-info mt-4 p-4 bg-blue-50 rounded">
<div class="text-sm text-gray-600">
<p><strong>批次名称:</strong>{{ selectedBatch.batchName }}</p>
<p><strong>学年学期:</strong>{{ selectedBatch.academicYear }} {{ selectedBatch.semester }}学期</p>
<p><strong>申请时间:</strong>{{ selectedBatch.startTime }} 至 {{ selectedBatch.endTime }}</p>
<p><strong>可申请奖学金类型:</strong></p>
<div class="mt-2">
<el-tag
v-for="type in availableScholarshipTypes"
:key="type.id"
class="mr-2 mb-2"
type="success"
>
{{ type.name }} - {{ type.levelType }} (¥{{ type.amount }})
</el-tag>
</div>
</div>
</div>
</el-card>
<!-- 第二步:班级信息显示 -->
<el-card class="mb-4" v-if="selectedBatch">
<template #header>
<span>第二步:班级信息确认</span>
</template>
<div class="class-info-grid">
<div class="info-item">
<label>学院:</label>
<span>{{ classInfo.collegeName || '未获取' }}</span>
</div>
<div class="info-item">
<label>专业:</label>
<span>{{ classInfo.majorName || '未获取' }}</span>
</div>
<div class="info-item">
<label>班级:</label>
<span>{{ classInfo.className || '未获取' }}</span>
</div>
<div class="info-item">
<label>年级:</label>
<span>{{ classInfo.grade || '未获取' }}</span>
</div>
<div class="info-item">
<label>班主任:</label>
<span>{{ classInfo.teacherName || '未获取' }}</span>
</div>
</div>
</el-card>
<!-- 第三步:选择申请学生 -->
<el-card v-if="selectedBatch && classInfo.classId">
<template #header>
<div class="flex justify-between items-center">
<span>第三步:选择申请学生</span>
<div class="flex items-center space-x-4">
<el-input
v-model="studentKeyword"
placeholder="搜索学生姓名或学号"
@input="filterStudents"
style="width: 200px"
clearable
>
<template #prefix>
<el-icon><Search /></el-icon>
</template>
</el-input>
<div class="text-sm text-gray-600">
已选择:{{ selectedStudents.length }} 人
</div>
</div>
</div>
</template>
<div class="mb-4 flex gap-2">
<el-button size="small" @click="selectAllStudents">全选</el-button>
<el-button size="small" @click="clearSelectedStudents">清空</el-button>
</div>
<div class="student-selection-container max-h-96 overflow-y-auto">
<div
v-for="student in filteredStudents"
:key="student.id"
class="student-card p-4 border rounded mb-3"
:class="{ 'border-blue-500 bg-blue-50': isStudentSelected(student.id) }"
>
<div class="flex items-start space-x-4">
<el-checkbox
:model-value="isStudentSelected(student.id)"
@change="toggleStudentSelection(student, $event)"
/>
<div class="flex-1">
<div class="student-basic-info flex items-center space-x-4 mb-2">
<span class="font-bold">{{ student.studentName }}</span>
<span class="text-gray-600">{{ student.studentCode }}</span>
<span class="text-gray-500">{{ student.gender }}</span>
<span class="text-gray-500">{{ student.phone }}</span>
</div>
<div v-if="isStudentSelected(student.id)" class="scholarship-selection mt-3 p-3 bg-gray-50 rounded">
<div class="grid grid-cols-3 gap-4">
<div>
<label class="block text-sm font-medium mb-1">奖学金类型</label>
<el-select
v-model="student.selectedScholarshipType"
placeholder="选择奖学金类型"
@change="updateScholarshipInfo(student)"
style="width: 100%"
>
<el-option
v-for="type in availableScholarshipTypes"
:key="type.id"
:label="`${type.name} (¥${type.amount})`"
:value="type"
/>
</el-select>
</div>
<div>
<label class="block text-sm font-medium mb-1">等级</label>
<el-input
v-model="student.selectedLevel"
:value="student.selectedScholarshipType?.levelType || ''"
readonly
/>
</div>
<div>
<label class="block text-sm font-medium mb-1">金额(元)</label>
<el-input
v-model="student.selectedAmount"
:value="student.selectedScholarshipType?.amount || ''"
readonly
/>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div v-if="filteredStudents.length === 0" class="text-center text-gray-500 py-8">
暂无学生信息,请检查班级设置
</div>
</el-card>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="dialog.visible = false">取 消</el-button>
<el-button
type="primary"
@click="submitBatchApplication"
:disabled="selectedStudents.length === 0"
:loading="submitLoading"
>
提交申请 ({{ selectedStudents.length }}人)
</el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup name="ScholarshipApplication" lang="ts">
import { ref, reactive, computed, getCurrentInstance, ComponentInternalInstance, onMounted, nextTick } from 'vue';
import type { FormInstance, DateModelType } from 'element-plus';
import { Search, Plus, Edit, Delete, Refresh } from '@element-plus/icons-vue';
// 导入API函数和类型
import {
listScholarshipApplication,
getScholarshipApplication,
delScholarshipApplication,
addScholarshipApplication,
updateScholarshipApplication,
exportScholarshipApplication,
getClassInfoByTeacher,
getClassStudents,
getScholarshipTypesByBatch,
batchSubmitByTeacher
} from '@/api/jcbt/scholarshipApplication';
import {
listScholarshipBatch,
getScholarshipBatchByStatus
} from '@/api/jcbt/scholarshipBatch';
// 导入通用类型
import type { PageQuery } from '@/utils/ruoyi';
// 导入用户状态管理
import { useUserStore } from '@/store/modules/user';
// 本地类型定义
interface ScholarshipApplicationVO {
id?: number | string;
batchId?: number | string;
studentId?: string;
studentName?: string;
studentCode?: string;
classId?: number | string;
scholarshipTypeId?: number | string;
scholarshipLevel?: string;
amount?: number;
applicationStatus?: string;
auditStatus?: string;
applicationTime?: string;
createTime?: string;
}
interface QueryParams {
pageNum: number;
pageSize: number;
studentName?: string;
studentCode?: string;
status?: string;
}
interface BatchQuery {
pageNum?: number;
pageSize?: number;
status?: string;
}
interface ScholarshipBatchVO {
id?: number | string;
batchName?: string;
academicYear?: string;
semester?: string;
startTime?: string;
endTime?: string;
status?: string;
createTime?: string;
}
interface ScholarshipBatchQuery {
pageNum?: number;
pageSize?: number;
status?: string;
}
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
// 用户状态管理
const userStore = useUserStore();
// 基础数据定义
const applicationList = ref<ScholarshipApplicationVO[]>([]);
const total = ref(0);
const loading = ref(true);
const showSearch = ref(true);
const ids = ref<Array<string | number>>([]);
const single = ref(true);
const multiple = ref(true);
// 查询参数
const queryParams = ref<QueryParams>({
pageNum: 1,
pageSize: 10,
studentName: '',
studentCode: '',
status: ''
});
// 表单相关
const queryFormRef = ref<FormInstance>();
const scholarshipApplicationRef = ref<FormInstance>();
// 弹出层标题和状态
const title = ref('');
const open = ref(false);
const formLoading = ref(false);
const submitLoading = ref(false);
// 班主任批量申请对话框状态
const dialog = ref({
title: '',
visible: false
});
// 弹出框表单需要的数据
const batchList = ref<ScholarshipBatchVO[]>([]);
const batchOptions = ref([]);
const scholarshipTypes = ref([]);
const availableScholarshipTypes = ref<any[]>([]);
const classInfo = ref({
teacherId: '',
collegeName: '',
majorName: '',
className: '',
grade: '',
teacherName: '',
classId: '',
deptId: ''
});
// 学生相关数据
const classStudents = ref<any[]>([]);
const filteredStudents = ref<any[]>([]);
const selectedStudents = ref<any[]>([]);
const studentKeyword = ref('');
// 表单数据
const form = ref<any>({
batchId: undefined,
scholarshipTypeId: undefined,
applicationType: 'batch',
studentIds: []
});
// 选择的批次
const selectedBatch = ref<ScholarshipBatchVO | null>(null);
// 表单验证规则
const rules = reactive({
batchId: [{ required: true, message: '申报批次不能为空', trigger: 'change' }]
});
/** 查询奖学金申请列表 */
const getList = async () => {
loading.value = true;
try {
const res: any = await listScholarshipApplication(queryParams.value);
// 处理响应数据结构
if (res.data) {
applicationList.value = res.data.rows || res.data.list || res.data || [];
total.value = res.data.total || 0;
}
} catch (error) {
console.error('获取列表失败:', error);
applicationList.value = [];
total.value = 0;
} finally {
loading.value = false;
}
};
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.value.pageNum = 1;
getList();
};
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value?.resetFields();
handleQuery();
};
/** 重置批量申请表单 */
const resetBatchForm = () => {
// 重置选择的批次
selectedBatch.value = null;
availableScholarshipTypes.value = [];
// 重置班级信息
classInfo.value = {
teacherId: '',
collegeName: '',
majorName: '',
className: '',
grade: '',
teacherName: '',
classId: '',
deptId: ''
};
// 重置学生相关数据
classStudents.value = [];
filteredStudents.value = [];
selectedStudents.value = [];
studentKeyword.value = '';
// 重置表单数据
form.value = {
batchId: undefined,
scholarshipTypeId: undefined,
applicationType: 'batch',
studentIds: []
};
// 重置表单验证
scholarshipApplicationRef.value?.resetFields();
};
/** 多选框选中数据 */
const handleSelectionChange = (selection: ScholarshipApplicationVO[]) => {
ids.value = selection.map(item => item.id);
single.value = selection.length !== 1;
multiple.value = !selection.length;
};
/** 新增按钮操作 - 班主任批量申请 */
const handleAdd = async () => {
resetBatchForm();
dialog.value.visible = true;
dialog.value.title = "班主任批量申请奖学金";
// 获取批次列表
await getBatchList();
// 获取班主任班级信息
await getTeacherClassInfo();
};
/** 修改按钮操作 */
const handleUpdate = async (row?: ScholarshipApplicationVO) => {
console.log('修改功能暂未实现', row);
};
/** 删除按钮操作 */
const handleDelete = async (row?: ScholarshipApplicationVO) => {
console.log('删除功能暂未实现', row);
};
/** 获取批次列表 */
const getBatchList = async () => {
try {
console.log('正在获取批次列表...');
// 使用专门的按状态查询方法
const res: any = await getScholarshipBatchByStatus('ACTIVE');
batchList.value = res.data || [];
console.log('成功获取批次列表:', batchList.value);
} catch (error) {
console.error('获取批次列表失败:', error);
batchList.value = [];
}
};
/** 获取班主任班级信息 */
const getTeacherClassInfo = async () => {
try {
console.log('开始获取班级信息...');
// 检查用户是否已登录并有用户ID
if (!userStore.userId) {
console.log('用户未登录,尝试获取用户信息...');
try {
await userStore.getInfo();
} catch (userError) {
console.warn('获取用户信息失败:', userError);
}
}
// 如果仍然没有用户ID,使用默认数据
if (!userStore.userId) {
console.warn('无法获取当前用户ID,使用默认班级信息');
setDefaultClassInfo();
await getStudentList();
return;
}
console.log('当前用户ID:', userStore.userId);
// 尝试调用API获取班级信息
try {
const res: any = await getClassInfoByTeacher(userStore.userId);
console.log('API响应:', res);
if (res && res.data) {
classInfo.value = res.data;
console.log('获取班级信息成功:', res.data);
// 如果有班级信息,获取学生列表
if (classInfo.value.classId) {
await getStudentList();
}
} else {
console.log('API返回的数据为空');
setDefaultClassInfo();
await getStudentList();
}
} catch (apiError) {
console.warn('API调用失败,可能是后端接口未实现:', apiError);
setDefaultClassInfo();
await getStudentList();
}
} catch (error) {
console.error('获取班级信息过程中发生未知错误:', error);
// 确保即使出错也能显示基本信息
setDefaultClassInfo();
await getStudentList();
}
};
/** 设置默认的班级信息 */
const setDefaultClassInfo = () => {
classInfo.value = {
teacherId: userStore.userId || '',
collegeName: '',
majorName: '',
className: '',
grade: '',
teacherName: userStore.nickname || '',
classId: '',
deptId: ''
};
console.log('已设置默认班级信息:', classInfo.value);
};
/** 获取班级学生列表 */
const getStudentList = async () => {
try {
console.log('🔍 开始获取学生列表,班级ID:', classInfo.value.classId);
if (!classInfo.value.classId) {
console.warn('⚠️ 班级ID为空');
setDefaultStudents();
return;
}
// 尝试调用API获取学生列表
try {
const res: any = await getClassStudents(classInfo.value.classId);
console.log('📡 获取学生列表API响应:', res);
if (res && res.data && Array.isArray(res.data) && res.data.length > 0) {
classStudents.value = res.data;
filteredStudents.value = res.data;
console.log('✅ 获取学生列表成功,学生数量:', res.data.length);
console.log('📋 学生数据结构示例:', res.data[0]);
console.log('🔑 学生ID字段:', res.data[0]?.id);
console.log('📝 学生姓名字段:', res.data[0]?.studentName);
} else {
console.log('📭 API返回学生数据为空');
setDefaultStudents();
}
} catch (apiError) {
console.warn('❌ 获取学生列表API调用失败,可能是后端接口未实现:', apiError);
setDefaultStudents();
}
} catch (error) {
console.error('💥 获取学生列表过程中发生未知错误:', error);
// 确保即使出错也能显示基本学生信息
setDefaultStudents();
}
};
/** 设置默认的学生数据 */
const setDefaultStudents = () => {
classStudents.value = [];
filteredStudents.value = [];
console.log('学生数据为空');
};
/** 批次变更处理 */
const handleBatchChange = async (batch: ScholarshipBatchVO) => {
if (batch) {
form.value.batchId = batch.id;
// 获取该批次可申请的奖学金类型
try {
const res: any = await getScholarshipTypesByBatch(batch.id);
availableScholarshipTypes.value = res.data || [];
} catch (error) {
console.error('获取奖学金类型失败:', error);
availableScholarshipTypes.value = [];
}
}
};
/** 筛选学生 */
const filterStudents = () => {
if (!studentKeyword.value) {
filteredStudents.value = classStudents.value;
} else {
filteredStudents.value = classStudents.value.filter((student: any) =>
student.studentName?.includes(studentKeyword.value) ||
student.studentCode?.includes(studentKeyword.value)
);
}
};
/** 检查学生是否已选择 */
const isStudentSelected = (studentId: string | number) => {
return selectedStudents.value.some((s: any) => s.id === studentId);
};
/** 切换学生选择状态 */
const toggleStudentSelection = (student: any, checked: boolean) => {
console.log('🔄 切换学生选择状态:', { student, checked });
if (checked) {
selectedStudents.value.push({
...student,
selectedScholarshipType: null,
selectedLevel: '',
selectedAmount: ''
});
console.log('✅ 添加学生到选择列表:', student.studentName);
} else {
const index = selectedStudents.value.findIndex((s: any) => s.id === student.id);
if (index > -1) {
selectedStudents.value.splice(index, 1);
console.log('❌ 从选择列表移除学生:', student.studentName);
}
}
console.log('📊 当前选择的学生数量:', selectedStudents.value.length);
};
/** 全选学生 */
const selectAllStudents = () => {
console.log('🎯 执行全选操作,当前学生数量:', filteredStudents.value.length);
filteredStudents.value.forEach((student: any) => {
if (!isStudentSelected(student.id)) {
selectedStudents.value.push({
...student,
selectedScholarshipType: null,
selectedLevel: '',
selectedAmount: ''
});
}
});
console.log('✅ 全选完成,已选择学生数量:', selectedStudents.value.length);
};
/** 清空选择的学生 */
const clearSelectedStudents = () => {
console.log('🧹 清空所有选择的学生');
selectedStudents.value = [];
};
/** 更新奖学金信息 */
const updateScholarshipInfo = (student: any) => {
if (student.selectedScholarshipType) {
student.selectedLevel = student.selectedScholarshipType.level;
student.selectedAmount = student.selectedScholarshipType.amount;
}
};
/** 提交批量申请 */
const submitBatchApplication = async () => {
try {
// 表单验证
await scholarshipApplicationRef.value?.validate();
if (selectedStudents.value.length === 0) {
console.error('请至少选择一名学生');
return;
}
// 检查每个学生是否都选择了奖学金类型
const invalidStudents = selectedStudents.value.filter((s: any) => !s.selectedScholarshipType);
if (invalidStudents.length > 0) {
console.error('请为所有选择的学生指定奖学金类型');
return;
}
submitLoading.value = true;
// 检查是否所有学生都选择了相同的奖学金类型(根据后端接口要求)
const scholarshipTypeId = selectedStudents.value[0].selectedScholarshipType.id;
const allSameType = selectedStudents.value.every(s => s.selectedScholarshipType.id === scholarshipTypeId);
if (!allSameType) {
console.error('批量申请要求所有学生选择相同的奖学金类型');
return;
}
// 构造符合后端API的提交数据
const batchId = form.value.batchId;
const classId = classInfo.value.classId;
const studentApplications = selectedStudents.value.map((s: any) => ({
studentId: s.studentId,
studentName: s.studentName,
studentCode: s.studentCode,
scholarshipTypeId: s.selectedScholarshipType.id,
applicationReason: `班主任批量申请 - ${s.selectedScholarshipType.name}`,
applicationDescription: `申请等级:${s.selectedScholarshipType.levelType},金额:${s.selectedScholarshipType.amount}元`
}));
// 调用批量提交API
await batchSubmitByTeacher(batchId, scholarshipTypeId, classId, studentApplications);
console.log('批量申请提交成功');
dialog.value.visible = false;
// 刷新列表
await getList();
} catch (error) {
console.error('提交失败:', error);
} finally {
submitLoading.value = false;
}
};
// 页面加载时获取数据
onMounted(() => {
getList();
getTeacherClassInfo(); // 获取班级信息
});
</script>
<style scoped>
.search-container {
background: #fff;
padding: 20px;
margin-bottom: 10px;
border-radius: 4px;
}
.class-info-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 16px;
margin-top: 12px;
}
.info-item {
display: flex;
align-items: center;
}
.info-item label {
font-weight: 500;
color: #606266;
margin-right: 8px;
min-width: 60px;
}
.student-card {
transition: all 0.3s ease;
}
.student-card:hover {
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.student-basic-info {
border-bottom: 1px solid #f0f0f0;
padding-bottom: 8px;
}
.text-primary {
color: #409eff;
}
.font-bold {
font-weight: 600;
}
.batch-info {
background-color: #f0f9ff;
border: 1px solid #bfdbfe;
}
.mb-4 {
margin-bottom: 1rem;
}
.mt-4 {
margin-top: 1rem;
}
.p-4 {
padding: 1rem;
}
.rounded {
border-radius: 0.375rem;
}
.max-h-96 {
max-height: 24rem;
}
.overflow-y-auto {
overflow-y: auto;
}
.flex {
display: flex;
}
.items-center {
align-items: center;
}
.justify-between {
justify-content: space-between;
}
.space-x-4 > * + * {
margin-left: 1rem;
}
.gap-2 > * + * {
margin-left: 0.5rem;
}
.grid-cols-3 {
grid-template-columns: repeat(3, 1fr);
}
.gap-4 {
gap: 1rem;
}
.text-center {
text-align: center;
}
.py-8 {
padding-top: 2rem;
padding-bottom: 2rem;
}
</style>
@@ -0,0 +1,983 @@
<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">
<!-- 第一行:4个搜索条件 -->
<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="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="academicYear">
<el-select v-model="queryParams.academicYear" placeholder="请选择学年" clearable style="width: 100%">
<el-option label="2028-2029" value="2028-2029" />
<el-option label="2027-2028" value="2027-2028" />
<el-option label="2026-2027" value="2026-2027" />
<el-option label="2025-2026" value="2025-2026" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="学期" prop="semester">
<el-select v-model="queryParams.semester" placeholder="请选择学期" clearable style="width: 100%">
<el-option label="第一学期" :value="1" />
<el-option label="第二学期" :value="2" />
<el-option label="全年" :value="3" />
</el-select>
</el-form-item>
</el-col>
</el-row>
<!-- 第二行:3个搜索条件 + 操作按钮 -->
<el-row :gutter="20">
<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="SUPPLEMENTARY" />
</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 label="创建时间">
<el-date-picker
v-model="daterangeCreateTime"
value-format="YYYY-MM-DD"
type="daterange"
range-separator="-"
start-placeholder="开始日期"
end-placeholder="结束日期"
@change="handleQuery"
style="width: 100%"
></el-date-picker>
</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>
<!-- 统计卡片区域 -->
<div class="mb-[10px]">
<el-row :gutter="16" class="stats-row">
<el-col :span="6">
<el-card shadow="hover" class="stat-card stat-card-primary">
<div class="stat-content">
<div class="stat-icon">
<el-icon><Calendar /></el-icon>
</div>
<div class="stat-info">
<div class="stat-value">{{ statistics.totalBatches }}</div>
<div class="stat-label">批次总数</div>
</div>
</div>
</el-card>
</el-col>
<el-col :span="6">
<el-card shadow="hover" class="stat-card stat-card-success">
<div class="stat-content">
<div class="stat-icon">
<el-icon><CircleCheck /></el-icon>
</div>
<div class="stat-info">
<div class="stat-value">{{ statistics.activeBatches }}</div>
<div class="stat-label">进行中批次</div>
</div>
</div>
</el-card>
</el-col>
<el-col :span="6">
<el-card shadow="hover" class="stat-card stat-card-warning">
<div class="stat-content">
<div class="stat-icon">
<el-icon><Clock /></el-icon>
</div>
<div class="stat-info">
<div class="stat-value">{{ statistics.draftBatches }}</div>
<div class="stat-label">草稿批次</div>
</div>
</div>
</el-card>
</el-col>
<el-col :span="6">
<el-card shadow="hover" class="stat-card stat-card-info">
<div class="stat-content">
<div class="stat-icon">
<el-icon><Finished /></el-icon>
</div>
<div class="stat-info">
<div class="stat-value">{{ statistics.completedBatches }}</div>
<div class="stat-label">已完成批次</div>
</div>
</div>
</el-card>
</el-col>
</el-row>
</div>
<!-- 表格区域 -->
<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="['jcbt:scholarshipBatch:add']">新增</el-button>
</el-col>
<el-col :span="1.5">
<el-button icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['jcbt:scholarshipBatch:edit']">修改</el-button>
</el-col>
<el-col :span="1.5">
<el-button icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['jcbt:scholarshipBatch:remove']">删除</el-button>
</el-col>
<el-col :span="1.5">
<el-button icon="Download" @click="handleExport" v-hasPermi="['jcbt:scholarshipBatch:export']">导出</el-button>
</el-col>
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
</template>
<el-table v-loading="loading" border :data="batchList" @selection-change="handleSelectionChange" @row-dblclick="handleView">
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="批次编码" align="center" prop="batchCode" min-width="120" />
<el-table-column label="批次名称" align="center" prop="batchName" min-width="150" show-overflow-tooltip />
<el-table-column label="学年" align="center" prop="academicYear" width="120" />
<el-table-column label="学期" align="center" prop="semester" width="80">
<template #default="scope">
<span v-if="scope.row.semester === 1">第一学期</span>
<span v-else-if="scope.row.semester === 2">第二学期</span>
<span v-else-if="scope.row.semester === 3">全年</span>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column label="批次类型" align="center" prop="batchType" width="100">
<template #default="scope">
<el-tag v-if="scope.row.batchType === 'REGULAR'" type="primary">常规批次</el-tag>
<el-tag v-else-if="scope.row.batchType === 'SPECIAL'" type="warning">专项批次</el-tag>
<el-tag v-else-if="scope.row.batchType === 'SUPPLEMENTARY'" type="info">补充批次</el-tag>
<el-tag v-else type="info">未知</el-tag>
</template>
</el-table-column>
<el-table-column label="状态" align="center" prop="status" width="100">
<template #default="scope">
<el-tag v-if="scope.row.status === 'DRAFT'" type="info">草稿</el-tag>
<el-tag v-else-if="scope.row.status === 'ACTIVE'" type="success">进行中</el-tag>
<el-tag v-else-if="scope.row.status === 'COMPLETED'" type="primary">已完成</el-tag>
<el-tag v-else-if="scope.row.status === 'CANCELLED'" type="danger">已取消</el-tag>
<el-tag v-else type="info">未知</el-tag>
</template>
</el-table-column>
<el-table-column label="申请时间" align="center" width="200">
<template #default="scope">
<div v-if="scope.row.applyStartTime && scope.row.applyEndTime">
<div>{{ parseTime(scope.row.applyStartTime, '{y}-{m}-{d}') }}</div>
<div style="color: #909399; font-size: 12px;">至 {{ parseTime(scope.row.applyEndTime, '{y}-{m}-{d}') }}</div>
</div>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column label="审核时间" align="center" width="200">
<template #default="scope">
<div v-if="scope.row.reviewStartTime && scope.row.reviewEndTime">
<div>{{ parseTime(scope.row.reviewStartTime, '{y}-{m}-{d}') }}</div>
<div style="color: #909399; font-size: 12px;">至 {{ parseTime(scope.row.reviewEndTime, '{y}-{m}-{d}') }}</div>
</div>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column label="创建时间" align="center" prop="createTime" width="180">
<template #default="scope">
<span>{{ parseTime(scope.row.createTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="280">
<template #default="scope">
<el-tooltip content="查看" placement="top">
<el-button
link
type="primary"
icon="View"
size="default"
style="font-size: 16px; margin-right: 8px; color: blue"
@click="handleView(scope.row)"
v-hasPermi="['jcbt:scholarshipBatch:query']"
></el-button>
</el-tooltip>
<el-tooltip content="修改" placement="top">
<el-button
link
type="primary"
icon="Edit"
size="default"
style="font-size: 16px; margin-right: 8px"
@click="handleUpdate(scope.row)"
v-hasPermi="['jcbt:scholarshipBatch:edit']"
></el-button>
</el-tooltip>
<el-tooltip content="状态变更" placement="top">
<el-button
link
type="warning"
icon="Switch"
size="default"
style="font-size: 16px; margin-right: 8px"
@click="handleStatusChange(scope.row)"
v-hasPermi="['jcbt:scholarshipBatch:edit']"
></el-button>
</el-tooltip>
<el-tooltip content="删除" placement="top">
<el-button
link
type="danger"
icon="Delete"
size="default"
style="font-size: 16px"
@click="handleDelete(scope.row)"
v-hasPermi="['jcbt:scholarshipBatch:remove']"
></el-button>
</el-tooltip>
</template>
</el-table-column>
</el-table>
<div class="pagination-container">
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
</div>
</el-card>
<!-- 添加或修改批次对话框 -->
<el-dialog :title="dialog.title" v-model="dialog.visible" width="1200px" append-to-body>
<el-form ref="batchFormRef" :model="form" :rules="rules" label-width="120px">
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="批次编码" prop="batchCode">
<el-input v-model="form.batchCode" placeholder="请输入批次编码" :disabled="form.id !== undefined" />
</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="8">
<el-form-item label="学年" prop="academicYear">
<el-select v-model="form.academicYear" placeholder="请选择学年" style="width: 100%">
<el-option label="2028-2029" value="2028-2029" />
<el-option label="2027-2028" value="2027-2028" />
<el-option label="2026-2027" value="2026-2027" />
<el-option label="2025-2026" value="2025-2026" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="学期" prop="semester">
<el-select v-model="form.semester" placeholder="请选择学期" style="width: 100%">
<el-option label="第一学期" :value="1" />
<el-option label="第二学期" :value="2" />
<el-option label="全年" :value="3" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="8">
<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="SUPPLEMENTARY" />
</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="选择开始时间"
format="YYYY-MM-DD HH:mm:ss"
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="form.endTime"
type="datetime"
placeholder="选择结束时间"
format="YYYY-MM-DD HH:mm:ss"
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="applyStartTime">
<el-date-picker
v-model="form.applyStartTime"
type="datetime"
placeholder="选择申请开始时间"
format="YYYY-MM-DD HH:mm:ss"
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="applyEndTime">
<el-date-picker
v-model="form.applyEndTime"
type="datetime"
placeholder="选择申请结束时间"
format="YYYY-MM-DD HH:mm:ss"
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="reviewStartTime">
<el-date-picker
v-model="form.reviewStartTime"
type="datetime"
placeholder="选择审核开始时间"
format="YYYY-MM-DD HH:mm:ss"
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="reviewEndTime">
<el-date-picker
v-model="form.reviewEndTime"
type="datetime"
placeholder="选择审核结束时间"
format="YYYY-MM-DD HH:mm:ss"
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="24">
<el-form-item label="奖学金类型" prop="scholarshipTypeIds">
<el-select
v-model="form.scholarshipTypeIds"
multiple
placeholder="请选择奖学金类型"
style="width: 100%"
clearable
collapse-tags
collapse-tags-tooltip
:max-collapse-tags="3"
>
<el-option
v-for="option in scholarshipTypeOptions"
:key="option.value"
:label="option.label"
:value="option.value"
/>
</el-select>
<div class="form-help-text">
选择该批次支持申请的奖学金类型,可多选
</div>
</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"
placeholder="请输入批次描述"
resize="none"
:autosize="{ minRows: 3, maxRows: 5 }"
/>
</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"
placeholder="请输入通知内容"
resize="none"
:autosize="{ minRows: 3, maxRows: 5 }"
/>
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button :loading="buttonLoading" plain type="primary" @click="submitForm">确 定</el-button>
<el-button @click="cancel">取 消</el-button>
</div>
</template>
</el-dialog>
<!-- 状态变更对话框 -->
<el-dialog title="状态变更" v-model="statusDialog.visible" width="400px" append-to-body>
<el-form :model="statusForm" label-width="80px">
<el-form-item label="当前状态">
<el-tag v-if="statusForm.currentStatus === 'DRAFT'" type="info">草稿</el-tag>
<el-tag v-else-if="statusForm.currentStatus === 'ACTIVE'" type="success">进行中</el-tag>
<el-tag v-else-if="statusForm.currentStatus === 'COMPLETED'" type="primary">已完成</el-tag>
<el-tag v-else-if="statusForm.currentStatus === 'CANCELLED'" type="danger">已取消</el-tag>
</el-form-item>
<el-form-item label="变更为">
<el-select v-model="statusForm.newStatus" placeholder="请选择状态" style="width: 100%">
<el-option v-if="statusForm.currentStatus === 'DRAFT'" label="进行中" value="ACTIVE" />
<el-option v-if="statusForm.currentStatus === 'DRAFT'" label="已取消" value="CANCELLED" />
<el-option v-if="statusForm.currentStatus === 'ACTIVE'" label="已完成" value="COMPLETED" />
<el-option v-if="statusForm.currentStatus === 'ACTIVE'" label="已取消" value="CANCELLED" />
</el-select>
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="confirmStatusChange">确 定</el-button>
<el-button @click="statusDialog.visible = false">取 消</el-button>
</div>
</template>
</el-dialog>
<!-- 查看详情对话框 -->
<el-dialog title="批次详情" v-model="viewDialog.visible" width="1000px" append-to-body>
<el-descriptions :column="2" border>
<el-descriptions-item label="批次编码">{{ viewData.batchCode }}</el-descriptions-item>
<el-descriptions-item label="批次名称">{{ viewData.batchName }}</el-descriptions-item>
<el-descriptions-item label="学年">{{ viewData.academicYear }}</el-descriptions-item>
<el-descriptions-item label="学期">
<span v-if="viewData.semester === 1">第一学期</span>
<span v-else-if="viewData.semester === 2">第二学期</span>
<span v-else-if="viewData.semester === 3">全年</span>
</el-descriptions-item>
<el-descriptions-item label="批次类型">
<el-tag v-if="viewData.batchType === 'REGULAR'" type="primary">常规批次</el-tag>
<el-tag v-else-if="viewData.batchType === 'SPECIAL'" type="warning">专项批次</el-tag>
<el-tag v-else-if="viewData.batchType === 'SUPPLEMENTARY'" type="info">补充批次</el-tag>
</el-descriptions-item>
<el-descriptions-item label="状态">
<el-tag v-if="viewData.status === 'DRAFT'" type="info">草稿</el-tag>
<el-tag v-else-if="viewData.status === 'ACTIVE'" type="success">进行中</el-tag>
<el-tag v-else-if="viewData.status === 'COMPLETED'" type="primary">已完成</el-tag>
<el-tag v-else-if="viewData.status === 'CANCELLED'" type="danger">已取消</el-tag>
</el-descriptions-item>
<el-descriptions-item label="开始时间">{{ parseTime(viewData.startTime) }}</el-descriptions-item>
<el-descriptions-item label="结束时间">{{ parseTime(viewData.endTime) }}</el-descriptions-item>
<el-descriptions-item label="申请开始时间">{{ parseTime(viewData.applyStartTime) }}</el-descriptions-item>
<el-descriptions-item label="申请结束时间">{{ parseTime(viewData.applyEndTime) }}</el-descriptions-item>
<el-descriptions-item label="审核开始时间">{{ parseTime(viewData.reviewStartTime) }}</el-descriptions-item>
<el-descriptions-item label="审核结束时间">{{ parseTime(viewData.reviewEndTime) }}</el-descriptions-item>
<el-descriptions-item label="批次描述" :span="2">{{ viewData.description || '-' }}</el-descriptions-item>
<el-descriptions-item label="奖学金类型" :span="2">
<el-tag v-for="typeName in getScholarshipTypeNames(viewData.scholarshipTypeIds)"
:key="typeName"
type="primary"
class="mr-2">
{{ typeName }}
</el-tag>
<span v-if="!viewData.scholarshipTypeIds || viewData.scholarshipTypeIds.length === 0" class="text-gray-400">
暂无关联奖学金类型
</span>
</el-descriptions-item>
<el-descriptions-item label="通知内容" :span="2">{{ viewData.noticeContent || '-' }}</el-descriptions-item>
<el-descriptions-item label="创建时间">{{ parseTime(viewData.createTime) }}</el-descriptions-item>
<el-descriptions-item label="更新时间">{{ parseTime(viewData.updateTime) }}</el-descriptions-item>
</el-descriptions>
</el-dialog>
</div>
</template>
<script setup name="ScholarshipBatch" lang="ts">
import {
listScholarshipBatch,
getScholarshipBatch,
delScholarshipBatch,
addScholarshipBatch,
updateScholarshipBatch,
exportScholarshipBatch,
changeScholarshipBatchStatus,
checkTimeConflict,
type ScholarshipBatchVO,
type ScholarshipBatchForm,
type ScholarshipBatchQuery
} from '@/api/jcbt/scholarshipBatch';
import { listScholarshipType, type ScholarshipTypeVO } from '@/api/jcbt/scholarshipType';
const { proxy } = getCurrentInstance()!;
const { sys_normal_disable } = toRefs<any>(proxy?.useDict('sys_normal_disable'));
const batchList = ref<ScholarshipBatchVO[]>([]);
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 buttonLoading = ref(false);
const queryFormRef = ref<ElFormInstance>();
const batchFormRef = ref<ElFormInstance>();
const dialog = reactive<DialogOption>({
visible: false,
title: ''
});
const statusDialog = reactive<DialogOption>({
visible: false,
title: ''
});
const viewDialog = reactive<DialogOption>({
visible: false,
title: ''
});
const initFormData: ScholarshipBatchForm = {
id: undefined,
batchCode: '',
batchName: '',
academicYear: '',
semester: undefined,
batchType: '',
startTime: '',
endTime: '',
applyStartTime: '',
applyEndTime: '',
reviewStartTime: '',
reviewEndTime: '',
status: 'DRAFT',
description: '',
noticeContent: '',
scholarshipTypeIds: []
};
const data = reactive<PageData<ScholarshipBatchForm, ScholarshipBatchQuery>>({
form: { ...initFormData },
queryParams: {
pageNum: 1,
pageSize: 10,
batchCode: '',
batchName: '',
academicYear: '',
semester: undefined,
batchType: '',
status: '',
createTime: ''
},
rules: {
batchCode: [
{ required: true, message: '批次编码不能为空', trigger: 'blur' }
],
batchName: [
{ required: true, message: '批次名称不能为空', trigger: 'blur' }
],
academicYear: [
{ required: true, message: '学年不能为空', trigger: 'change' }
],
semester: [
{ required: true, message: '学期不能为空', trigger: 'change' }
],
batchType: [
{ required: true, message: '批次类型不能为空', trigger: 'change' }
],
startTime: [
{ required: true, message: '开始时间不能为空', trigger: 'blur' }
],
endTime: [
{ required: true, message: '结束时间不能为空', trigger: 'blur' }
],
applyStartTime: [
{ required: true, message: '申请开始时间不能为空', trigger: 'blur' }
],
applyEndTime: [
{ required: true, message: '申请结束时间不能为空', trigger: 'blur' }
],
scholarshipTypeIds: [
{ required: true, message: '请至少选择一个奖学金类型', trigger: 'change' }
]
}
});
const { queryParams, form, rules } = toRefs(data);
// 时间范围
const daterangeCreateTime = ref<[DateModelType, DateModelType]>(['', '']);
// 状态变更表单
const statusForm = ref({
id: '',
currentStatus: '',
newStatus: ''
});
// 查看详情数据
const viewData = ref<ScholarshipBatchVO>({});
// 统计数据
const statistics = ref({
totalBatches: 0,
activeBatches: 0,
draftBatches: 0,
completedBatches: 0
});
// 奖学金类型选项
const scholarshipTypeOptions = ref<Array<{ value: string | number; label: string }>>([]);
/** 查询批次列表 */
function getList() {
loading.value = true;
if (daterangeCreateTime.value[0] && daterangeCreateTime.value[1]) {
queryParams.value.params = {
beginCreateTime: daterangeCreateTime.value[0],
endCreateTime: daterangeCreateTime.value[1]
};
} else {
queryParams.value.params = {};
}
listScholarshipBatch(queryParams.value).then(response => {
batchList.value = response.rows;
total.value = response.total;
loading.value = false;
// 更新统计数据
updateStatistics();
});
}
/** 更新统计数据 */
function updateStatistics() {
statistics.value.totalBatches = batchList.value.length;
statistics.value.activeBatches = batchList.value.filter(item => item.status === 'ACTIVE').length;
statistics.value.draftBatches = batchList.value.filter(item => item.status === 'DRAFT').length;
statistics.value.completedBatches = batchList.value.filter(item => item.status === 'COMPLETED').length;
}
/** 获取奖学金类型选项 */
async function getScholarshipTypeOptions() {
try {
// 从奖学金类型管理模块获取真实数据
const res = await listScholarshipType({
pageNum: 1,
pageSize: 1000, // 获取所有启用的奖学金类型
status: 1 // 只获取启用状态的类型
});
if (res.rows && res.rows.length > 0) {
scholarshipTypeOptions.value = res.rows.map((item: ScholarshipTypeVO) => ({
value: item.id!,
label: item.typeName!
}));
} else {
scholarshipTypeOptions.value = [];
}
} catch (error) {
console.error('获取奖学金类型选项失败', error);
scholarshipTypeOptions.value = [];
}
}
/** 搜索按钮操作 */
function handleQuery() {
queryParams.value.pageNum = 1;
getList();
}
/** 重置按钮操作 */
function resetQuery() {
daterangeCreateTime.value = ['', ''];
queryFormRef.value?.resetFields();
queryParams.value.pageNum = 1;
queryParams.value.params = {};
handleQuery();
}
/** 多选框选中数据 */
function handleSelectionChange(selection: ScholarshipBatchVO[]) {
ids.value = selection.map(item => item.id!);
single.value = selection.length !== 1;
multiple.value = !selection.length;
}
/** 新增按钮操作 */
function handleAdd() {
reset();
dialog.visible = true;
dialog.title = '添加奖学金评定批次';
}
/** 修改按钮操作 */
async function handleUpdate(row?: ScholarshipBatchVO) {
reset();
const _id = row?.id || ids.value[0];
const res = await getScholarshipBatch(_id);
// 先赋值基本数据
Object.assign(form.value, res.data);
// 确保scholarshipTypeIds正确赋值
if (res.data.scholarshipTypeIds && Array.isArray(res.data.scholarshipTypeIds)) {
form.value.scholarshipTypeIds = [...res.data.scholarshipTypeIds];
} else {
form.value.scholarshipTypeIds = [];
}
dialog.visible = true;
dialog.title = '修改奖学金评定批次';
}
/** 查看详情 */
async function handleView(row: ScholarshipBatchVO) {
const res = await getScholarshipBatch(row.id!);
viewData.value = res.data;
viewDialog.visible = true;
}
/** 提交按钮 */
function submitForm() {
batchFormRef.value?.validate(async valid => {
if (!valid) return;
buttonLoading.value = true;
try {
// 检查时间冲突
const conflictCheck = await checkTimeConflict(form.value);
if (conflictCheck.data) {
proxy?.$modal.msgError('批次时间与现有批次冲突,请重新选择时间');
return;
}
if (form.value.id) {
await updateScholarshipBatch(form.value);
proxy?.$modal.msgSuccess('修改成功');
} else {
await addScholarshipBatch(form.value);
proxy?.$modal.msgSuccess('新增成功');
}
dialog.visible = false;
await getList();
} finally {
buttonLoading.value = false;
}
});
}
/** 删除按钮操作 */
async function handleDelete(row?: ScholarshipBatchVO) {
const _ids = row?.id || ids.value;
await proxy?.$modal.confirm('是否确认删除批次编号为"' + _ids + '"的数据项?');
await delScholarshipBatch(_ids);
proxy?.$modal.msgSuccess('删除成功');
await getList();
}
/** 导出按钮操作 */
function handleExport() {
proxy?.$download.excel(exportScholarshipBatch, queryParams.value, `scholarship_batch_${new Date().getTime()}.xlsx`);
}
/** 状态变更操作 */
function handleStatusChange(row: ScholarshipBatchVO) {
statusForm.value = {
id: row.id as string,
currentStatus: row.status!,
newStatus: ''
};
statusDialog.visible = true;
}
/** 确认状态变更 */
async function confirmStatusChange() {
if (!statusForm.value.newStatus) {
proxy?.$modal.msgError('请选择要变更的状态');
return;
}
await changeScholarshipBatchStatus(statusForm.value.id, statusForm.value.newStatus);
proxy?.$modal.msgSuccess('状态变更成功');
statusDialog.visible = false;
await getList();
}
/** 取消按钮 */
function cancel() {
dialog.visible = false;
reset();
}
/** 表单重置 */
function reset() {
form.value = { ...initFormData };
batchFormRef.value?.resetFields();
}
/** 根据奖学金类型ID获取名称 */
function getScholarshipTypeNames(typeIds: Array<string | number> | undefined): string[] {
if (!typeIds || typeIds.length === 0) return [];
return typeIds.map(id => {
const option = scholarshipTypeOptions.value.find(opt => opt.value === id);
return option ? option.label : `未知类型(${id})`;
});
}
onMounted(() => {
getList();
getScholarshipTypeOptions();
});
</script>
<style scoped>
.form-help-text {
font-size: 12px;
color: #909399;
margin-top: 4px;
line-height: 1.4;
}
.stats-row {
margin-bottom: 16px;
}
.stat-card {
cursor: pointer;
transition: all 0.3s;
}
.stat-card:hover {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
transform: translateY(-2px);
}
.stat-content {
display: flex;
align-items: center;
padding: 16px;
}
.stat-icon {
margin-right: 16px;
font-size: 32px;
width: 48px;
height: 48px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 8px;
}
.stat-info {
flex: 1;
}
.stat-value {
font-size: 24px;
font-weight: bold;
line-height: 1;
margin-bottom: 4px;
}
.stat-label {
font-size: 14px;
opacity: 0.8;
line-height: 1;
}
/* 不同主题颜色 */
.stat-card-primary .stat-icon {
background-color: #ecf5ff;
color: #409eff;
}
.stat-card-primary .stat-value {
color: #409eff;
}
.stat-card-success .stat-icon {
background-color: #f0f9ff;
color: #67c23a;
}
.stat-card-success .stat-value {
color: #67c23a;
}
.stat-card-warning .stat-icon {
background-color: #fdf6ec;
color: #e6a23c;
}
.stat-card-warning .stat-value {
color: #e6a23c;
}
.stat-card-info .stat-icon {
background-color: #f4f4f5;
color: #909399;
}
.stat-card-info .stat-value {
color: #909399;
}
.pagination-container {
margin-top: 16px;
text-align: center;
}
.description-cell {
max-width: 200px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
</style>
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff