wuzhen update

This commit is contained in:
wuzhen committed 2025-08-10 00:24:31 +08:00
1 parent bab7b53074
commit a2222f63cb
284 files changed
+48629 -3473

No files matched your search

+2 -2
View File
@@ -98,7 +98,7 @@ export const approveApplication = (data: CwApprovalForm) => {
params.append('approveOpinion', data.approvalComment || '');
return request({
url: '/cw/approval/dept', // 🔧 修复:使用部门审批接口
url: '/cw/approval/dept', // 🔧 修复:使用部门审批接口
method: 'post',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
@@ -136,7 +136,7 @@ export const batchApproveApplication = async (data: CwApprovalForm) => {
}
// 检查是否有失败的项目
const failures = results.filter(r => !r.success);
const failures = results.filter((r) => !r.success);
if (failures.length > 0) {
console.error('🔧 批量审批部分失败:', failures);
throw new Error(`批量审批完成,${failures.length}个项目审批失败`);
+5 -5
View File
@@ -15,12 +15,12 @@ export const financeApproveApplication = (data: FinanceApprovalForm): AxiosPromi
approveAction: data.approvalResult,
approveOpinion: data.approvalComment
});
// 如果选择了院领导,添加参数
if (data.selectedApproverId) {
params.append('selectedApproverId', data.selectedApproverId.toString());
}
return request({
url: '/cw/approval/finance',
method: 'post',
@@ -34,9 +34,9 @@ export const financeApproveApplication = (data: FinanceApprovalForm): AxiosPromi
// 财务批量审批操作
export const batchFinanceApproveApplication = (dataList: FinanceApprovalForm[]): Promise<any> => {
// 前端模拟批量操作,逐个调用单个审批接口
const promises = dataList.map(item => financeApproveApplication(item));
return Promise.all(promises).then(results => {
const promises = dataList.map((item) => financeApproveApplication(item));
return Promise.all(promises).then((results) => {
return {
data: results,
code: 200,
+2 -2
View File
@@ -26,9 +26,9 @@ export const leaderApproveApplication = (data: LeaderApprovalForm): AxiosPromise
// 分管领导批量审批操作
export const batchLeaderApproveApplication = (dataList: LeaderApprovalForm[]): AxiosPromise<any> => {
// 前端模拟批量操作,逐个调用单个审批接口
const promises = dataList.map(item => leaderApproveApplication(item));
const promises = dataList.map((item) => leaderApproveApplication(item));
return Promise.all(promises).then(results => {
return Promise.all(promises).then((results) => {
return {
data: results,
code: 200,
+2 -2
View File
@@ -1,6 +1,6 @@
/**
* 财务项目预算审批 - API服务
*
*
* @author ruoyi
* @date 2025-01-26
*/
@@ -214,4 +214,4 @@ export const delApproverConfig = (id: string | number | Array<string | number>)
url: '/cw/approverConfig/' + id,
method: 'delete'
});
};
};
+8 -8
View File
@@ -1,8 +1,8 @@
import request from '@/utils/request'
import { AxiosPromise } from 'axios'
import request from '@/utils/request';
import { AxiosPromise } from 'axios';
export interface StatisticsQuery {
budgetYear?: string
budgetYear?: string;
}
// 获取项目清单(用于统计页面)
@@ -11,7 +11,7 @@ export function getStatisticsList(query: StatisticsQuery): AxiosPromise<any> {
url: '/cw/application/statistics/list',
method: 'get',
params: query
})
});
}
// 获取状态统计
@@ -20,7 +20,7 @@ export function getStatusStatistics(budgetYear?: string): AxiosPromise<any> {
url: '/cw/application/statistics/status',
method: 'get',
params: { budgetYear }
})
});
}
// 获取部门预算汇总统计
@@ -29,7 +29,7 @@ export function getDepartmentStatistics(budgetYear?: string): AxiosPromise<any>
url: '/cw/application/statistics/department',
method: 'get',
params: { budgetYear }
})
});
}
// 获取年度汇总统计
@@ -37,5 +37,5 @@ export function getYearlyStatistics(): AxiosPromise<any> {
return request({
url: '/cw/application/statistics/yearly',
method: 'get'
})
}
});
}
+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'
});
};
+426
View File
@@ -0,0 +1,426 @@
import request from '@/utils/request'
import { AxiosPromise } from 'axios'
/**
* 贫困生认定申请查询对象类型
*/
export interface JcbtPovertyApplicationQuery extends PageQuery {
/** 申请编号 */
applicationNo?: string
/** 学生姓名 */
studentName?: string
/** 学号 */
studentNo?: string
/** 班级名称 */
className?: string
/** 认定类型ID */
recognitionTypeId?: number
/** 认定类型名称 */
recognitionTypeName?: string
/** 学年 */
academicYear?: string
/** 申请状态 */
status?: string
/** 家庭经济情况 */
familyEconomyStatus?: string
/** 申请自助类型ID数组 */
assistanceTypeIds?: string
/** 创建时间范围 */
createTime?: [string, string]
/** 申请时间范围 */
applicationTime?: [string, string]
}
/**
* 贫困生认定申请表单类型
*/
export interface JcbtPovertyApplicationForm {
/** 主键 */
id?: string | number
/** 申请编号 */
applicationNo?: string
/** 学年 */
academicYear?: string
/** 学期 */
term?: number
/** 学生ID */
studentId?: number
/** 学号 */
studentNo?: string
/** 学生姓名 */
studentName?: string
/** 性别 */
gender?: string
/** 学院ID */
collegeId?: number
/** 学院名称 */
collegeName?: string
/** 专业ID */
majorId?: number
/** 专业名称 */
majorName?: string
/** 班级ID */
classId?: number
/** 班级名称 */
className?: string
/** 认定群体 */
recognitionGroup?: string
/** 认定类型ID */
recognitionTypeId?: number
/** 认定类型编码 */
recognitionTypeCode?: string
/** 认定类型名称 */
recognitionTypeName?: string
/** 申请状态 */
status?: string
/** 家庭经济情况 */
familyEconomyStatus?: string
/** 家庭经济情况摘要 */
familyEconomySummary?: string
/** 申请理由 */
applicationReason?: string
/** 申请时间 */
applicationTime?: string
/** 审核时间 */
auditTime?: string
/** 审核人ID */
auditUserId?: number
/** 审核人姓名 */
auditUserName?: string
/** 审核意见 */
auditOpinion?: string
/** 申请自助类型ID数组 */
assistanceTypeIds?: string
/** 申请自助类型名称 */
assistanceTypeNames?: string
/** 备注 */
remark?: string
}
/**
* 贫困生认定申请视图对象类型
*/
export interface JcbtPovertyApplicationVo {
/** 主键 */
id: string | number
/** 申请编号 */
applicationNo: string
/** 学年 */
academicYear: string
/** 学期 */
term: number
/** 学生ID */
studentId: number
/** 学号 */
studentNo: string
/** 学生姓名 */
studentName: string
/** 性别 */
gender: string
/** 学院ID */
collegeId: number
/** 学院名称 */
collegeName: string
/** 专业ID */
majorId: number
/** 专业名称 */
majorName: string
/** 班级ID */
classId: number
/** 班级名称 */
className: string
/** 认定群体 */
recognitionGroup: string
/** 认定类型ID */
recognitionTypeId: number
/** 认定类型编码 */
recognitionTypeCode: string
/** 认定类型名称 */
recognitionTypeName: string
/** 申请状态 */
status: string
/** 家庭经济情况 */
familyEconomyStatus: string
/** 申请自助类型ID数组 */
assistanceTypeIds: string
/** 申请自助类型名称 */
assistanceTypeNames: string
/** 家庭经济情况摘要 */
familyEconomySummary: string
/** 申请理由 */
applicationReason: string
/** 申请时间 */
applicationTime: string
/** 审核时间 */
auditTime: string
/** 审核人ID */
auditUserId: number
/** 审核人姓名 */
auditUserName: string
/** 审核意见 */
auditOpinion: string
/** 备注 */
remark: string
/** 创建者 */
createBy: number
/** 创建时间 */
createTime: string
/** 更新者 */
updateBy: number
/** 更新时间 */
updateTime: string
}
/**
* 审核请求参数类型
*/
export interface AuditRequestForm {
/** 申请ID */
id: string | number
/** 审核意见 */
auditOpinion: string
}
// ========== 基础管理接口(6个) ==========
/**
* 查询贫困生认定申请列表
* @param query 查询参数
*/
export function listPovertyApplication(query: JcbtPovertyApplicationQuery): AxiosPromise<JcbtPovertyApplicationVo[]> {
return request({
url: '/jcbt/povertyApplication/list',
method: 'get',
params: query
})
}
/**
* 获取贫困生认定申请详细信息
* @param id 申请ID
*/
export function getPovertyApplication(id: string | number): AxiosPromise<JcbtPovertyApplicationVo> {
return request({
url: '/jcbt/povertyApplication/' + id,
method: 'get'
})
}
/**
* 新增贫困生认定申请
* @param data 申请表单数据
*/
export function addPovertyApplication(data: JcbtPovertyApplicationForm): AxiosPromise<void> {
return request({
url: '/jcbt/povertyApplication',
method: 'post',
data: data
})
}
/**
* 修改贫困生认定申请
* @param data 申请表单数据
*/
export function updatePovertyApplication(data: JcbtPovertyApplicationForm): AxiosPromise<void> {
return request({
url: '/jcbt/povertyApplication',
method: 'put',
data: data
})
}
/**
* 删除贫困生认定申请
* @param ids 申请ID数组
*/
export function delPovertyApplication(ids: (string | number)[]): AxiosPromise<void> {
return request({
url: '/jcbt/povertyApplication/' + ids.join(','),
method: 'delete'
})
}
/**
* 导出贫困生认定申请列表
* @param query 查询参数
*/
export function exportPovertyApplication(query: JcbtPovertyApplicationQuery): AxiosPromise<any> {
return request({
url: '/jcbt/povertyApplication/export',
method: 'post',
data: query,
responseType: 'blob'
})
}
// ========== 业务功能接口(4个) ==========
/**
* 查询我的申请列表(班主任查看自己提交的申请)
* @param query 查询参数
*/
export function listMyPovertyApplication(query: JcbtPovertyApplicationQuery): AxiosPromise<JcbtPovertyApplicationVo[]> {
return request({
url: '/jcbt/povertyApplication/myList',
method: 'get',
params: query
})
}
/**
* 查询待审核申请列表(审核人员使用)
* @param query 查询参数
*/
export function listPendingPovertyApplication(query: JcbtPovertyApplicationQuery): AxiosPromise<JcbtPovertyApplicationVo[]> {
return request({
url: '/jcbt/povertyApplication/pendingList',
method: 'get',
params: query
})
}
/**
* 提交申请
* @param id 申请ID
*/
export function submitPovertyApplication(id: string | number): AxiosPromise<void> {
return request({
url: '/jcbt/povertyApplication/submit/' + id,
method: 'put'
})
}
/**
* 撤销申请
* @param id 申请ID
*/
export function cancelPovertyApplication(id: string | number): AxiosPromise<void> {
return request({
url: '/jcbt/povertyApplication/cancel/' + id,
method: 'put'
})
}
// ========== 辅助功能接口(6个) ==========
/**
* 根据申请编号获取申请信息
* @param applicationNo 申请编号
*/
export function getPovertyApplicationByNo(applicationNo: string): AxiosPromise<JcbtPovertyApplicationVo> {
return request({
url: '/jcbt/povertyApplication/byApplicationNo/' + applicationNo,
method: 'get'
})
}
/**
* 保存草稿
* @param data 申请表单数据
*/
export function saveDraftPovertyApplication(data: JcbtPovertyApplicationForm): AxiosPromise<void> {
return request({
url: '/jcbt/povertyApplication/saveDraft',
method: 'post',
data: data
})
}
/**
* 审核通过
* @param data 审核请求参数
*/
export function approvePovertyApplication(data: AuditRequestForm): AxiosPromise<void> {
return request({
url: '/jcbt/povertyApplication/approve',
method: 'put',
data: data
})
}
/**
* 审核驳回
* @param data 审核请求参数
*/
export function rejectPovertyApplication(data: AuditRequestForm): AxiosPromise<void> {
return request({
url: '/jcbt/povertyApplication/reject',
method: 'put',
data: data
})
}
/**
* 生成申请编号
*/
export function generateApplicationNo(): AxiosPromise<string> {
return request({
url: '/jcbt/povertyApplication/generateApplicationNo',
method: 'get'
})
}
/**
* 检查学生申请是否重复
* @param params 检查参数
*/
export function checkDuplicate(params: {
studentId: number
academicYear: string
recognitionTypeId: number
excludeId?: number
}): AxiosPromise<boolean> {
return request({
url: '/jcbt/povertyApplication/checkDuplicate',
method: 'get',
params: params
})
}
// ========== 统计分析接口(2个) ==========
/**
* 获取申请统计信息
* @param teacherId 班主任ID(可选)
*/
export function getPovertyApplicationStatistics(teacherId?: number): AxiosPromise<any> {
return request({
url: '/jcbt/povertyApplication/statistics',
method: 'get',
params: teacherId ? { teacherId } : {}
})
}
/**
* 获取我的申请统计信息(班主任使用)
*/
export function getMyPovertyApplicationStatistics(): AxiosPromise<any> {
return request({
url: '/jcbt/povertyApplication/myStatistics',
method: 'get'
})
}
// ========== 班级和学生相关接口(2个) ==========
/**
* 获取当前用户班级信息
*/
export function getCurrentUserClasses(): AxiosPromise<any[]> {
return request({
url: '/jcbt/povertyApplication/user/classes',
method: 'get'
})
}
/**
* 获取班级学生列表
* @param classId 班级ID
*/
export function getClassStudents(classId: number | string): AxiosPromise<any[]> {
return request({
url: `/jcbt/povertyApplication/class/${classId}/students`,
method: 'get'
})
}
+30
View File
@@ -0,0 +1,30 @@
// 重新导出奖学金审核相关的API函数
export {
listScholarshipReview,
getScholarshipReview,
addScholarshipReview,
updateScholarshipReview,
delScholarshipReview,
getMyReviewTasks,
getPendingReviews,
getReviewHistory,
performReviewAction,
batchApprove as batchApproveScholarshipReview,
checkReviewRules,
getReviewStatistics,
exportScholarshipReview,
changeReviewStatus,
getAvailableReviewers,
// 为了与页面中的函数名保持一致,添加别名
performReviewAction as approveScholarshipReview
} from '../scholarshipReview'
// 重新导出类型定义
export type {
ScholarshipReviewQuery,
ScholarshipReviewVo,
ScholarshipReviewForm,
ReviewActionForm,
ReviewHistoryQuery,
ReviewStatistics
} from '../scholarshipReview'
+221
View File
@@ -0,0 +1,221 @@
import request from '@/utils/request';
import { AxiosPromise } from 'axios';
export interface ScholarshipApplicationVO {
/** 主键ID */
id?: string | number;
/** 批次ID */
batchId?: string | number;
/** 批次名称 */
batchName?: string;
/** 奖学金类型ID */
scholarshipTypeId?: string | number;
/** 奖学金类型名称 */
scholarshipTypeName?: string;
/** 申请编号 */
applicationNo?: string;
/** 学生学号 */
studentNo?: string;
/** 学生姓名 */
studentName?: string;
/** 学生ID */
studentId?: string | number;
/** 学院ID */
deptId?: string | number;
/** 学院名称 */
deptName?: string;
/** 专业ID */
majorId?: string | number;
/** 班级ID */
classId?: string | number;
/** 班级名称 */
className?: string;
/** 申请人类型 */
applicantType?: string;
/** 申请人ID */
applicantId?: string | number;
/** 申请人姓名 */
applicantName?: string;
/** 申请类型 */
applicationType?: string;
/** 申请原因 */
applicationReason?: string;
/** 申请说明 */
applicationDescription?: string;
/** 系统计算分数 */
calculatedScore?: number;
/** 最终评定分数 */
finalScore?: number;
/** 在院系的排名 */
rankingInDept?: number;
/** 在专业的排名 */
rankingInMajor?: number;
/** 状态 */
status?: string;
/** 提交时间 */
submitTime?: string;
/** 创建者 */
createBy?: string | number;
/** 创建时间 */
createTime?: string;
/** 更新者 */
updateBy?: string | number;
/** 更新时间 */
updateTime?: 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;
/** 学生学号 */
studentNo?: 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 }
});
};
+223
View File
@@ -0,0 +1,223 @@
import request from '@/utils/request';
import { AxiosPromise } from 'axios';
// 审核记录查询参数
export interface ScholarshipReviewQuery {
pageNum?: number;
pageSize?: number;
orderNo?: string;
applicantName?: string;
scholarshipType?: string;
auditLevel?: string;
auditStatus?: string;
reviewerId?: number;
reviewerName?: string;
reviewTimeBegin?: string;
reviewTimeEnd?: string;
batchId?: number;
}
// 审核记录VO
export interface ScholarshipReviewVo {
id: number;
reviewId: string;
orderNo: string;
applicationId: number;
applicantId: number;
applicantName: string;
scholarshipType: string;
auditLevel: string;
auditStatus: string;
reviewerId: number;
reviewerName: string;
reviewTime: string;
reviewComment: string;
attachments: string;
nextReviewerId: number;
nextReviewerName: string;
isActive: number;
createBy: number;
createTime: string;
updateBy: number;
updateTime: string;
}
// 审核表单
export interface ScholarshipReviewForm {
id?: number;
reviewId?: string;
orderNo?: string;
applicationId?: number;
applicantId?: number;
applicantName?: string;
scholarshipType?: string;
auditLevel?: string;
auditStatus?: string;
reviewerId?: number;
reviewerName?: string;
reviewTime?: string;
reviewComment?: string;
attachments?: string;
nextReviewerId?: number;
nextReviewerName?: string;
isActive?: number;
}
// 审核操作表单
export interface ReviewActionForm {
reviewId: string;
action: 'approve' | 'reject' | 'return';
reviewComment?: string;
nextReviewerId?: number;
}
// 审核历史查询参数
export interface ReviewHistoryQuery {
pageNum?: number;
pageSize?: number;
orderNo?: string;
applicantName?: string;
reviewResult?: string;
reviewTimeBegin?: string;
reviewTimeEnd?: string;
batchId?: number;
}
// 审核统计响应
export interface ReviewStatistics {
pendingReview: number;
approved: number;
rejected: number;
returned: number;
}
// 查询审核记录列表
export function listScholarshipReview(query: ScholarshipReviewQuery): AxiosPromise<ScholarshipReviewVo[]> {
return request({
url: '/jcbt/review/list',
method: 'get',
params: query
});
}
// 查询审核记录详细
export function getScholarshipReview(id: string | number): AxiosPromise<ScholarshipReviewVo> {
return request({
url: '/jcbt/review/' + id,
method: 'get'
});
}
// 新增审核记录
export function addScholarshipReview(data: ScholarshipReviewForm): AxiosPromise<void> {
return request({
url: '/jcbt/review',
method: 'post',
data: data
});
}
// 修改审核记录
export function updateScholarshipReview(data: ScholarshipReviewForm): AxiosPromise<void> {
return request({
url: '/jcbt/review',
method: 'put',
data: data
});
}
// 删除审核记录
export function delScholarshipReview(id: string | number | Array<string | number>): AxiosPromise<void> {
return request({
url: '/jcbt/review/' + id,
method: 'delete'
});
}
// 我的审核任务
export function getMyReviewTasks(query: ScholarshipReviewQuery): AxiosPromise<ScholarshipReviewVo[]> {
return request({
url: '/jcbt/review/myTasks',
method: 'get',
params: query
});
}
// 待我审核的列表
export function getPendingReviews(query: ScholarshipReviewQuery): AxiosPromise<ScholarshipReviewVo[]> {
return request({
url: '/jcbt/review/pending',
method: 'get',
params: query
});
}
// 审核历史
export function getReviewHistory(query: ReviewHistoryQuery): AxiosPromise<ScholarshipReviewVo[]> {
return request({
url: '/jcbt/review/history',
method: 'get',
params: query
});
}
// 执行审核操作
export function performReviewAction(data: ReviewActionForm): AxiosPromise<void> {
return request({
url: '/jcbt/review/action',
method: 'post',
data: data
});
}
// 批量审核通过
export function batchApprove(reviewIds: string[], reviewComment?: string): AxiosPromise<void> {
return request({
url: '/jcbt/review/batchApprove',
method: 'post',
data: { reviewIds, reviewComment }
});
}
// 审核规则检查
export function checkReviewRules(applicationId: number): AxiosPromise<any> {
return request({
url: '/jcbt/review/checkRules/' + applicationId,
method: 'get'
});
}
// 获取审核统计数据
export function getReviewStatistics(): AxiosPromise<ReviewStatistics> {
return request({
url: '/jcbt/review/statistics',
method: 'get'
});
}
// 导出审核记录
export function exportScholarshipReview(query: ScholarshipReviewQuery): AxiosPromise<void> {
return request({
url: '/jcbt/review/export',
method: 'post',
data: query
});
}
// 启用/禁用审核记录
export function changeReviewStatus(id: number, status: number): AxiosPromise<void> {
return request({
url: '/jcbt/review/changeStatus',
method: 'put',
data: { id, status }
});
}
// 获取可分配的审核人员
export function getAvailableReviewers(auditLevel: string): AxiosPromise<any[]> {
return request({
url: '/jcbt/review/reviewers/' + auditLevel,
method: 'get'
});
}
+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;
}
+4 -10
View File
@@ -63,7 +63,6 @@ export interface SysRegionVO {
* 子节点列表(用于树形结构)
*/
children?: SysRegionVO[];
}
export interface SysRegionForm extends BaseEntity {
@@ -126,11 +125,9 @@ export interface SysRegionForm extends BaseEntity {
* 逻辑删除标识(0未删除 1已删除)
*/
deleted?: number;
}
export interface SysRegionQuery extends PageQuery {
/**
* 行政区划编码
*/
@@ -181,11 +178,8 @@ export interface SysRegionQuery extends PageQuery {
*/
deleted?: number;
/**
* 日期范围参数
*/
params?: any;
/**
* 日期范围参数
*/
params?: any;
}
+25 -25
View File
@@ -1,4 +1,4 @@
import request from '@/utils/request'
import request from '@/utils/request';
/**
* 重置bucket访问密码
@@ -11,7 +11,7 @@ export function resetBucketPassword(bucketName: string, newPassword: string) {
url: '/system/bucket-password/reset',
method: 'post',
params: { bucketName, newPassword }
})
});
}
/**
@@ -23,7 +23,7 @@ export function getBucketPasswordInfo(bucketName: string) {
return request({
url: `/system/bucket-password/info/${bucketName}`,
method: 'get'
})
});
}
/**
@@ -37,7 +37,7 @@ export function getBucketAccessRecords(bucketName: string, limit: number = 10) {
url: `/system/bucket-password/records/${bucketName}`,
method: 'get',
params: { limit }
})
});
}
/**
@@ -51,7 +51,7 @@ export function validateBucketPassword(bucketName: string, password: string) {
url: '/system/bucket-password/validate',
method: 'post',
params: { bucketName, password }
})
});
}
/**
@@ -60,12 +60,12 @@ export function validateBucketPassword(bucketName: string, password: string) {
* @returns 生成的密码
*/
export function generateStrongPassword(length: number = 12): string {
const charset = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*'
let password = ''
const charset = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*';
let password = '';
for (let i = 0; i < length; i++) {
password += charset.charAt(Math.floor(Math.random() * charset.length))
password += charset.charAt(Math.floor(Math.random() * charset.length));
}
return password
return password;
}
/**
@@ -74,51 +74,51 @@ export function generateStrongPassword(length: number = 12): string {
* @returns 密码强度评估
*/
export function validatePasswordStrength(password: string): {
isValid: boolean
score: number
suggestions: string[]
isValid: boolean;
score: number;
suggestions: string[];
} {
const suggestions: string[] = []
let score = 0
const suggestions: string[] = [];
let score = 0;
// 长度检查
if (password.length >= 8) {
score += 1
score += 1;
} else {
suggestions.push('密码长度至少8位')
suggestions.push('密码长度至少8位');
}
// 包含大写字母
if (/[A-Z]/.test(password)) {
score += 1
score += 1;
} else {
suggestions.push('包含大写字母')
suggestions.push('包含大写字母');
}
// 包含小写字母
if (/[a-z]/.test(password)) {
score += 1
score += 1;
} else {
suggestions.push('包含小写字母')
suggestions.push('包含小写字母');
}
// 包含数字
if (/\d/.test(password)) {
score += 1
score += 1;
} else {
suggestions.push('包含数字')
suggestions.push('包含数字');
}
// 包含特殊字符
if (/[!@#$%^&*]/.test(password)) {
score += 1
score += 1;
} else {
suggestions.push('包含特殊字符')
suggestions.push('包含特殊字符');
}
return {
isValid: score >= 4,
score,
suggestions
}
};
}
+28 -28
View File
@@ -1,4 +1,4 @@
import request from '@/utils/request'
import request from '@/utils/request';
/**
* 获取文件临时访问URL
@@ -15,7 +15,7 @@ export function getFileAccessUrl(ossId: number | string | bigint, bucketPassword
bucketPassword,
expireSeconds
}
})
});
}
/**
@@ -34,7 +34,7 @@ export function getBatchFileAccessUrl(ossIds: number[], bucketPassword?: string,
bucketPassword,
expireSeconds
}
})
});
}
/**
@@ -52,7 +52,7 @@ export function getFileDownloadUrl(ossId: number | string | bigint, bucketPasswo
bucketPassword,
expireSeconds
}
})
});
}
/**
@@ -70,7 +70,7 @@ export function getFilePreviewUrl(ossId: number | string | bigint, bucketPasswor
bucketPassword,
expireSeconds
}
})
});
}
/**
@@ -83,32 +83,32 @@ export function getFilePreviewUrl(ossId: number | string | bigint, bucketPasswor
export async function secureDownloadFile(ossId: number | string | bigint, fileName: string, bucketPassword?: string) {
try {
// 获取临时下载URL
const { data: downloadUrl } = await getFileDownloadUrl(ossId, bucketPassword)
const { data: downloadUrl } = await getFileDownloadUrl(ossId, bucketPassword);
// 使用fetch下载文件
const response = await fetch(downloadUrl)
const response = await fetch(downloadUrl);
if (!response.ok) {
throw new Error('下载失败')
throw new Error('下载失败');
}
const blob = await response.blob()
const blob = await response.blob();
// 创建下载链接
const url = URL.createObjectURL(blob)
const link = document.createElement('a')
link.href = url
link.download = fileName
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = fileName;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
// 释放内存
URL.revokeObjectURL(url)
URL.revokeObjectURL(url);
return Promise.resolve()
return Promise.resolve();
} catch (error) {
console.error('文件下载失败:', error)
return Promise.reject(error)
console.error('文件下载失败:', error);
return Promise.reject(error);
}
}
@@ -122,20 +122,20 @@ export async function secureDownloadFile(ossId: number | string | bigint, fileNa
export async function securePreviewFile(ossId: number | string | bigint, contentType?: string, bucketPassword?: string) {
try {
// 获取临时预览URL
const { data: previewUrl } = await getFilePreviewUrl(ossId, bucketPassword)
const { data: previewUrl } = await getFilePreviewUrl(ossId, bucketPassword);
// 根据文件类型选择预览方式
if (contentType?.startsWith('image/')) {
// 图片直接返回URL用于预览
return Promise.resolve(previewUrl)
return Promise.resolve(previewUrl);
} else {
// 其他文件在新窗口打开
window.open(previewUrl, '_blank')
return Promise.resolve(previewUrl)
window.open(previewUrl, '_blank');
return Promise.resolve(previewUrl);
}
} catch (error) {
console.error('文件预览失败:', error)
return Promise.reject(error)
console.error('文件预览失败:', error);
return Promise.reject(error);
}
}
@@ -147,7 +147,7 @@ export async function securePreviewFile(ossId: number | string | bigint, content
export function getBucketPasswordHint(bucketName: string) {
// 这里可以根据bucket名称返回相应的密码提示
// 例如:从配置中获取密码提示信息
return `请输入 ${bucketName} bucket 的访问密码`
return `请输入 ${bucketName} bucket 的访问密码`;
}
/**
@@ -157,5 +157,5 @@ export function getBucketPasswordHint(bucketName: string) {
*/
export function validateBucketPassword(password: string): boolean {
// 简单的密码格式验证
return password && password.length >= 6
return password && password.length >= 6;
}
@@ -163,7 +163,6 @@ export interface StudentLeaveVO {
* 销假备注
*/
cancelRemark: string;
}
export interface StudentLeaveForm extends BaseEntity {
@@ -336,11 +335,9 @@ export interface StudentLeaveForm extends BaseEntity {
* 操作类型:add-新增,update-修改,view-查看
*/
type?: string;
}
export interface StudentLeaveQuery extends PageQuery {
/**
* 删除标志(0代表存在 1代表删除)
*/
@@ -501,11 +498,8 @@ export interface StudentLeaveQuery extends PageQuery {
*/
cancelRemark?: string;
/**
* 日期范围参数
*/
params?: any;
/**
* 日期范围参数
*/
params?: any;
}
+64 -64
View File
@@ -1,9 +1,9 @@
import request from '@/utils/request'
import { AxiosPromise } from 'axios'
import { ClassInfoVO, ClassInfoQuery, ClassInfoForm, OptionData } from './classInfo/types'
import { getToken } from '@/utils/auth'
import { DeptTreeVO, DeptVO } from '@/api/system/dept/types'
import { treeselect as deptTreeselect, listDeptExcludeChild } from '@/api/system/dept'
import request from '@/utils/request';
import { AxiosPromise } from 'axios';
import { ClassInfoVO, ClassInfoQuery, ClassInfoForm, OptionData } from './classInfo/types';
import { getToken } from '@/utils/auth';
import { DeptTreeVO, DeptVO } from '@/api/system/dept/types';
import { treeselect as deptTreeselect, listDeptExcludeChild } from '@/api/system/dept';
// 通用类型定义
interface TableDataInfo<T> {
@@ -27,8 +27,8 @@ export const listClassInfo = (query: ClassInfoQuery): AxiosPromise<TableDataInfo
url: '/xg/classInfo/list',
method: 'get',
params: query
})
}
});
};
/**
* 查询班级信息详细
@@ -37,8 +37,8 @@ export const getClassInfo = (id: string | number): AxiosPromise<R<ClassInfoVO>>
return request({
url: '/xg/classInfo/' + id,
method: 'get'
})
}
});
};
/**
* 新增班级信息
@@ -48,8 +48,8 @@ export const addClassInfo = (data: ClassInfoForm): AxiosPromise<R<void>> => {
url: '/xg/classInfo',
method: 'post',
data: data
})
}
});
};
/**
* 修改班级信息
@@ -59,8 +59,8 @@ export const updateClassInfo = (data: ClassInfoForm): AxiosPromise<R<void>> => {
url: '/xg/classInfo',
method: 'put',
data: data
})
}
});
};
/**
* 删除班级信息
@@ -69,8 +69,8 @@ export const delClassInfo = (id: string | number | Array<string | number>): Axio
return request({
url: '/xg/classInfo/' + id,
method: 'delete'
})
}
});
};
/**
* 导出班级信息
@@ -80,8 +80,8 @@ export const exportClassInfo = (query: ClassInfoQuery): AxiosPromise<any> => {
url: '/xg/classInfo/export',
method: 'post',
data: query
})
}
});
};
/**
* 根据班级代码查询班级信息
@@ -90,8 +90,8 @@ export const getClassInfoByCode = (classCode: string): AxiosPromise<R<ClassInfoV
return request({
url: '/xg/classInfo/getByClassCode/' + classCode,
method: 'get'
})
}
});
};
/**
* 根据年级查询班级信息列表
@@ -100,8 +100,8 @@ export const getClassInfoByGrade = (grade: string): AxiosPromise<R<ClassInfoVO[]
return request({
url: '/xg/classInfo/getByGrade/' + grade,
method: 'get'
})
}
});
};
/**
* 根据学院代码查询班级信息列表
@@ -110,8 +110,8 @@ export const getClassInfoByCollege = (collegeCode: string): AxiosPromise<R<Class
return request({
url: '/xg/classInfo/getByCollegeCode/' + collegeCode,
method: 'get'
})
}
});
};
/**
* 根据专业代码查询班级信息列表
@@ -120,8 +120,8 @@ export const getClassInfoByMajor = (majorCode: string): AxiosPromise<R<ClassInfo
return request({
url: '/xg/classInfo/getByMajorCode/' + majorCode,
method: 'get'
})
}
});
};
/**
* 根据辅导员ID查询班级信息列表
@@ -130,8 +130,8 @@ export const getClassInfoByCounselor = (counselorId: number): AxiosPromise<R<Cla
return request({
url: '/xg/classInfo/getByCounselorId/' + counselorId,
method: 'get'
})
}
});
};
/**
* 根据班主任ID查询班级信息列表
@@ -140,18 +140,18 @@ export const getClassInfoByClassTeacher = (classTeacherId: number): AxiosPromise
return request({
url: '/xg/classInfo/getByClassTeacherId/' + classTeacherId,
method: 'get'
})
}
});
};
/**
* 获取班级选项列表(用于下拉框)
*/
export const getClassOptions = (): AxiosPromise<R<ClassInfoVO[]>> => {
return request({
url: '/xg/classInfo/getClassOptions', // 修正路径:从 /options 改为 /getClassOptions
url: '/xg/classInfo/getClassOptions', // 修正路径:从 /options 改为 /getClassOptions
method: 'get'
})
}
});
};
/**
* 更新班级学生统计
@@ -159,9 +159,9 @@ export const getClassOptions = (): AxiosPromise<R<ClassInfoVO[]>> => {
export const updateStudentCount = (classId: number): AxiosPromise<R<void>> => {
return request({
url: '/xg/classInfo/updateStudentCount/' + classId,
method: 'post' // 修正HTTP方法:从 put 改为 post
})
}
method: 'post' // 修正HTTP方法:从 put 改为 post
});
};
/**
* 批量更新班级学生统计
@@ -169,10 +169,10 @@ export const updateStudentCount = (classId: number): AxiosPromise<R<void>> => {
export const batchUpdateStudentCount = (classIds: number[]): AxiosPromise<R<void>> => {
return request({
url: '/xg/classInfo/batchUpdateStudentCount',
method: 'post', // 修正HTTP方法:从 put 改为 post
method: 'post', // 修正HTTP方法:从 put 改为 post
data: classIds
})
}
});
};
/**
* 启用班级
@@ -180,9 +180,9 @@ export const batchUpdateStudentCount = (classIds: number[]): AxiosPromise<R<void
export const enableClass = (id: number): AxiosPromise<R<void>> => {
return request({
url: '/xg/classInfo/enable/' + id,
method: 'post' // 修正HTTP方法:从 put 改为 post
})
}
method: 'post' // 修正HTTP方法:从 put 改为 post
});
};
/**
* 禁用班级
@@ -190,9 +190,9 @@ export const enableClass = (id: number): AxiosPromise<R<void>> => {
export const disableClass = (id: number): AxiosPromise<R<void>> => {
return request({
url: '/xg/classInfo/disable/' + id,
method: 'post' // 修正HTTP方法:从 put 改为 post
})
}
method: 'post' // 修正HTTP方法:从 put 改为 post
});
};
// ================================
// 统计数据接口 - 新增
@@ -205,8 +205,8 @@ export const getClassStatistics = (): AxiosPromise<R<any>> => {
return request({
url: '/xg/classInfo/statistics',
method: 'get'
})
}
});
};
/**
* 统计各学院班级数量
@@ -215,8 +215,8 @@ export const countClassByCollege = (): AxiosPromise<R<any[]>> => {
return request({
url: '/xg/classInfo/statistics/college',
method: 'get'
})
}
});
};
/**
* 统计各年级班级数量
@@ -225,8 +225,8 @@ export const countClassByGrade = (): AxiosPromise<R<any[]>> => {
return request({
url: '/xg/classInfo/statistics/grade',
method: 'get'
})
}
});
};
/**
* 统计各专业班级数量
@@ -235,8 +235,8 @@ export const countClassByMajor = (): AxiosPromise<R<any[]>> => {
return request({
url: '/xg/classInfo/statistics/major',
method: 'get'
})
}
});
};
/**
* 统计班级状态分布
@@ -245,8 +245,8 @@ export const countClassByStatus = (): AxiosPromise<R<any[]>> => {
return request({
url: '/xg/classInfo/statistics/status',
method: 'get'
})
}
});
};
/**
* 获取学院选项
@@ -255,8 +255,8 @@ export const getCollegeOptions = (): AxiosPromise<R<any[]>> => {
return request({
url: '/xg/studentInfo/options/colleges',
method: 'get'
})
}
});
};
/**
* 从系统部门获取学院选项(二级学院)
@@ -267,7 +267,7 @@ export const getDeptCollegeOptions = (): AxiosPromise<DeptTreeVO[]> => {
url: '/system/dept/treeselect',
method: 'get'
});
}
};
/**
* 从系统部门获取专业选项(学院的下级部门)
@@ -287,7 +287,7 @@ export const getDeptMajorOptions = (deptId: string | number): AxiosPromise<DeptV
url: '/system/dept/list/exclude/' + numericDeptId,
method: 'get'
});
}
};
/**
* 获取辅导员选项
@@ -296,14 +296,14 @@ export const getCounselorOptions = (): AxiosPromise<R<any[]>> => {
return request({
url: '/xg/studentInfo/options/counselors',
method: 'get'
})
}
});
};
/**
* 获取班主任选项列表
*/
export const getClassTeacherOptions = (): AxiosPromise<R<OptionData[]>> => {
console.log('调用班主任API: /xg/classInfo/teacherOptions')
console.log('调用班主任API: /xg/classInfo/teacherOptions');
return request({
url: '/xg/classInfo/teacherOptions',
method: 'get',
@@ -312,5 +312,5 @@ export const getClassTeacherOptions = (): AxiosPromise<R<OptionData[]>> => {
'Authorization': 'Bearer ' + getToken(),
'clientid': import.meta.env.VITE_APP_CLIENT_ID
}
})
}
});
};
+126 -126
View File
@@ -1,121 +1,121 @@
import request from '@/utils/request'
import { AxiosPromise } from 'axios'
import request from '@/utils/request';
import { AxiosPromise } from 'axios';
// ========== 统计分析相关类型定义 ==========
// 统计查询参数
export interface StatisticsQuery {
pageNum?: number
pageSize?: number
configName?: string
configType?: string
dimensionType?: string
isEnabled?: boolean
createTime?: string[]
pageNum?: number;
pageSize?: number;
configName?: string;
configType?: string;
dimensionType?: string;
isEnabled?: boolean;
createTime?: string[];
}
// 统计分析查询参数
export interface StatisticsAnalysisQuery {
statisticsType?: string
dimension?: string
field?: string
groupBy?: string
conditions?: Record<string, any>
startTime?: string
endTime?: string
collegeIds?: number[]
majorIds?: number[]
classIds?: number[]
grades?: string[]
genders?: string[]
politicalStatuses?: string[]
nationalities?: string[]
studentStatuses?: string[]
trainingTypes?: string[]
useCache?: boolean
statisticsType?: string;
dimension?: string;
field?: string;
groupBy?: string;
conditions?: Record<string, any>;
startTime?: string;
endTime?: string;
collegeIds?: number[];
majorIds?: number[];
classIds?: number[];
grades?: string[];
genders?: string[];
politicalStatuses?: string[];
nationalities?: string[];
studentStatuses?: string[];
trainingTypes?: string[];
useCache?: boolean;
}
// 统计配置视图对象
export interface StatisticsConfigVo {
id?: number
configName?: string
configType?: string
dimensionType?: string
queryConditions?: string
displaySettings?: string
chartType?: string
isEnabled?: boolean
sortOrder?: number
remark?: string
createBy?: number
createTime?: string
updateBy?: number
updateTime?: string
id?: number;
configName?: string;
configType?: string;
dimensionType?: string;
queryConditions?: string;
displaySettings?: string;
chartType?: string;
isEnabled?: boolean;
sortOrder?: number;
remark?: string;
createBy?: number;
createTime?: string;
updateBy?: number;
updateTime?: string;
}
// 统计配置业务对象
export interface StatisticsConfigBo {
id?: number
configName?: string
configType?: string
dimensionType?: string
queryConditions?: string
displaySettings?: string
chartType?: string
isEnabled?: boolean
sortOrder?: number
remark?: string
id?: number;
configName?: string;
configType?: string;
dimensionType?: string;
queryConditions?: string;
displaySettings?: string;
chartType?: string;
isEnabled?: boolean;
sortOrder?: number;
remark?: string;
}
// 统计结果视图对象
export interface StatisticsResultVo {
title?: string
type?: string
dimension?: string
data?: Array<Record<string, any>>
total?: number
statisticsTime?: string
fromCache?: boolean
cacheKey?: string
extra?: Record<string, any>
chartConfig?: Record<string, any>
groupData?: Record<string, Array<Record<string, any>>>
summary?: Record<string, any>
title?: string;
type?: string;
dimension?: string;
data?: Array<Record<string, any>>;
total?: number;
statisticsTime?: string;
fromCache?: boolean;
cacheKey?: string;
extra?: Record<string, any>;
chartConfig?: Record<string, any>;
groupData?: Record<string, Array<Record<string, any>>>;
summary?: Record<string, any>;
}
// 统计报表视图对象
export interface StatisticsReportVo {
reportType?: string
title?: string
description?: string
data?: Array<Record<string, any>>
summary?: Record<string, any>
generateTime?: string
reportType?: string;
title?: string;
description?: string;
data?: Array<Record<string, any>>;
summary?: Record<string, any>;
generateTime?: string;
metadata?: {
total: number
conditions: Record<string, any>
}
total: number;
conditions: Record<string, any>;
};
}
// 统计缓存信息
export interface StatisticsCacheInfo {
key?: string
hitCount?: number
missCount?: number
hitRate?: number
size?: number
lastAccessTime?: string
createTime?: string
expireTime?: string
key?: string;
hitCount?: number;
missCount?: number;
hitRate?: number;
size?: number;
lastAccessTime?: string;
createTime?: string;
expireTime?: string;
}
// 导出参数
export interface StatisticsExportQuery {
dimension?: string
format?: 'excel' | 'csv'
includeChart?: boolean
conditions?: Record<string, any>
reportType?: string
dimension?: string;
format?: 'excel' | 'csv';
includeChart?: boolean;
conditions?: Record<string, any>;
reportType?: string;
}
// ========== 统计配置管理API ==========
@@ -126,7 +126,7 @@ export function listStatisticsConfig(query: StatisticsQuery): AxiosPromise<any>
url: '/xg/statisticsConfig/list',
method: 'get',
params: query
})
});
}
// 获取统计配置详情
@@ -134,7 +134,7 @@ export function getStatisticsConfig(id: number): AxiosPromise<StatisticsConfigVo
return request({
url: `/xg/statisticsConfig/${id}`,
method: 'get'
})
});
}
// 新增统计配置
@@ -143,7 +143,7 @@ export function addStatisticsConfig(data: StatisticsConfigBo): AxiosPromise<any>
url: '/xg/statisticsConfig',
method: 'post',
data: data
})
});
}
// 修改统计配置
@@ -152,7 +152,7 @@ export function updateStatisticsConfig(data: StatisticsConfigBo): AxiosPromise<a
url: '/xg/statisticsConfig',
method: 'put',
data: data
})
});
}
// 删除统计配置
@@ -160,7 +160,7 @@ export function delStatisticsConfig(ids: number[]): AxiosPromise<any> {
return request({
url: `/xg/statisticsConfig/${ids}`,
method: 'delete'
})
});
}
// 导出统计配置
@@ -169,7 +169,7 @@ export function exportStatisticsConfig(query: StatisticsQuery): AxiosPromise<any
url: '/xg/statisticsConfig/export',
method: 'post',
data: query
})
});
}
// 启用/禁用统计配置
@@ -178,7 +178,7 @@ export function changeStatisticsConfigStatus(id: number, status: boolean): Axios
url: `/xg/statisticsConfig/changeStatus`,
method: 'put',
data: { id, isEnabled: status }
})
});
}
// ========== 统计分析API ==========
@@ -188,7 +188,7 @@ export function getStatisticsOverview(): AxiosPromise<any> {
return request({
url: '/xg/statisticsAnalysis/overview',
method: 'get'
})
});
}
// 按维度统计 - 学院统计
@@ -197,7 +197,7 @@ export function getCollegeStatistics(query: StatisticsAnalysisQuery): AxiosPromi
url: '/xg/statisticsAnalysis/college',
method: 'get',
params: query
})
});
}
// 按维度统计 - 专业统计
@@ -206,7 +206,7 @@ export function getMajorStatistics(query: StatisticsAnalysisQuery): AxiosPromise
url: '/xg/statisticsAnalysis/major',
method: 'get',
params: query
})
});
}
// 按维度统计 - 班级统计
@@ -215,7 +215,7 @@ export function getClassStatistics(query: StatisticsAnalysisQuery): AxiosPromise
url: '/xg/statisticsAnalysis/class',
method: 'get',
params: query
})
});
}
// 按维度统计 - 年级统计
@@ -224,7 +224,7 @@ export function getGradeStatistics(query: StatisticsAnalysisQuery): AxiosPromise
url: '/xg/statisticsAnalysis/grade',
method: 'get',
params: query
})
});
}
// 按维度统计 - 性别统计
@@ -233,7 +233,7 @@ export function getGenderStatistics(query: StatisticsAnalysisQuery): AxiosPromis
url: '/xg/statisticsAnalysis/gender',
method: 'get',
params: query
})
});
}
// 按维度统计 - 年龄统计
@@ -242,7 +242,7 @@ export function getAgeStatistics(query: StatisticsAnalysisQuery): AxiosPromise<S
url: '/xg/statisticsAnalysis/age',
method: 'get',
params: query
})
});
}
// 按维度统计 - 政治面貌统计
@@ -251,7 +251,7 @@ export function getPoliticalStatusStatistics(query: StatisticsAnalysisQuery): Ax
url: '/xg/statisticsAnalysis/political',
method: 'get',
params: query
})
});
}
// 按维度统计 - 民族统计
@@ -260,7 +260,7 @@ export function getNationStatistics(query: StatisticsAnalysisQuery): AxiosPromis
url: '/xg/statisticsAnalysis/nation',
method: 'get',
params: query
})
});
}
// 按维度统计 - 生源地统计
@@ -269,7 +269,7 @@ export function getSourceRegionStatistics(query: StatisticsAnalysisQuery): Axios
url: '/xg/statisticsAnalysis/sourceRegion',
method: 'get',
params: query
})
});
}
// 按维度统计 - 血型统计
@@ -278,7 +278,7 @@ export function getBloodTypeStatistics(query: StatisticsAnalysisQuery): AxiosPro
url: '/xg/statisticsAnalysis/bloodType',
method: 'get',
params: query
})
});
}
// 按维度统计 - 宗教信仰统计
@@ -287,7 +287,7 @@ export function getReligionStatistics(query: StatisticsAnalysisQuery): AxiosProm
url: '/xg/statisticsAnalysis/religion',
method: 'get',
params: query
})
});
}
// 按维度统计 - 学籍状态统计
@@ -296,7 +296,7 @@ export function getStudentStatusStatistics(query: StatisticsAnalysisQuery): Axio
url: '/xg/statisticsAnalysis/studentStatus',
method: 'get',
params: query
})
});
}
// 按维度统计 - 培养方式统计
@@ -305,7 +305,7 @@ export function getTrainingTypeStatistics(query: StatisticsAnalysisQuery): Axios
url: '/xg/statisticsAnalysis/trainingType',
method: 'get',
params: query
})
});
}
// 高级统计 - 身高统计
@@ -314,7 +314,7 @@ export function getHeightStatistics(query: StatisticsAnalysisQuery): AxiosPromis
url: '/xg/statisticsAnalysis/height',
method: 'get',
params: query
})
});
}
// 高级统计 - 体重统计
@@ -323,7 +323,7 @@ export function getWeightStatistics(query: StatisticsAnalysisQuery): AxiosPromis
url: '/xg/statisticsAnalysis/weight',
method: 'get',
params: query
})
});
}
// 多维度组合统计
@@ -332,7 +332,7 @@ export function getMultiDimensionStatistics(query: StatisticsAnalysisQuery): Axi
url: '/xg/statisticsAnalysis/multiDimension',
method: 'post',
data: query
})
});
}
// 时间趋势统计
@@ -341,7 +341,7 @@ export function getTimeTrendStatistics(query: StatisticsAnalysisQuery): AxiosPro
url: '/xg/statisticsAnalysis/timeTrend',
method: 'get',
params: query
})
});
}
// ========== 统计报表API ==========
@@ -352,7 +352,7 @@ export function generateStudentSummaryReport(query: StatisticsAnalysisQuery): Ax
url: '/xg/statisticsAnalysis/report/studentSummary',
method: 'post',
data: query
})
});
}
// 生成班级统计报表
@@ -361,7 +361,7 @@ export function generateClassStatisticsReport(query: StatisticsAnalysisQuery): A
url: '/xg/statisticsAnalysis/report/classStatistics',
method: 'post',
data: query
})
});
}
// 生成学院统计报表
@@ -370,7 +370,7 @@ export function generateCollegeStatisticsReport(query: StatisticsAnalysisQuery):
url: '/xg/statisticsAnalysis/report/collegeStatistics',
method: 'post',
data: query
})
});
}
// 生成困难学生统计报表
@@ -379,7 +379,7 @@ export function generateDifficultStudentReport(query: StatisticsAnalysisQuery):
url: '/xg/statisticsAnalysis/report/difficultStudent',
method: 'post',
data: query
})
});
}
// 生成特殊群体统计报表
@@ -388,7 +388,7 @@ export function generateSpecialGroupReport(query: StatisticsAnalysisQuery): Axio
url: '/xg/statisticsAnalysis/report/specialGroup',
method: 'post',
data: query
})
});
}
// ========== 统计导出API ==========
@@ -400,7 +400,7 @@ export function exportStatisticsResult(query: StatisticsExportQuery): AxiosPromi
method: 'post',
data: query,
responseType: 'blob'
})
});
}
// 导出统计报表
@@ -410,7 +410,7 @@ export function exportStatisticsReport(query: StatisticsExportQuery): AxiosPromi
method: 'post',
data: query,
responseType: 'blob'
})
});
}
// ========== 统计缓存API ==========
@@ -420,7 +420,7 @@ export function getCacheStatistics(): AxiosPromise<StatisticsCacheInfo> {
return request({
url: '/xg/statisticsAnalysis/cache/info',
method: 'get'
})
});
}
// 清空统计缓存
@@ -428,7 +428,7 @@ export function clearStatisticsCache(): AxiosPromise<any> {
return request({
url: '/xg/statisticsAnalysis/cache/clear',
method: 'delete'
})
});
}
// 预热统计缓存
@@ -436,7 +436,7 @@ export function warmupStatisticsCache(): AxiosPromise<any> {
return request({
url: '/xg/statisticsAnalysis/cache/warmup',
method: 'post'
})
});
}
// ========== 辅助API ==========
@@ -446,7 +446,7 @@ export function getStatisticsDimensionOptions(): AxiosPromise<any> {
return request({
url: '/xg/statisticsAnalysis/dimensionOptions',
method: 'get'
})
});
}
// 获取图表类型选项
@@ -454,7 +454,7 @@ export function getChartTypeOptions(): AxiosPromise<any> {
return request({
url: '/xg/statisticsAnalysis/chartTypeOptions',
method: 'get'
})
});
}
// 获取学院选项
@@ -462,7 +462,7 @@ export function getCollegeOptions(): AxiosPromise<any> {
return request({
url: '/xg/statisticsAnalysis/collegeOptions',
method: 'get'
})
});
}
// 获取专业选项
@@ -471,7 +471,7 @@ export function getMajorOptions(collegeCode?: string): AxiosPromise<any> {
url: '/xg/statisticsAnalysis/majorOptions',
method: 'get',
params: { collegeCode }
})
});
}
// 获取班级选项
@@ -480,7 +480,7 @@ export function getClassOptions(majorCode?: string): AxiosPromise<any> {
url: '/xg/statisticsAnalysis/classOptions',
method: 'get',
params: { majorCode }
})
});
}
// 获取年级选项
@@ -488,7 +488,7 @@ export function getGradeOptions(): AxiosPromise<any> {
return request({
url: '/xg/statisticsAnalysis/gradeOptions',
method: 'get'
})
});
}
// 执行统计配置
@@ -497,5 +497,5 @@ export function executeStatisticsConfig(configId: number, query: StatisticsAnaly
url: `/xg/statisticsAnalysis/executeConfig/${configId}`,
method: 'post',
data: query
})
});
}
+164 -164
View File
@@ -1,146 +1,146 @@
import request from '@/utils/request'
import { AxiosPromise } from 'axios'
import { getToken } from '@/utils/auth'
import request from '@/utils/request';
import { AxiosPromise } from 'axios';
import { getToken } from '@/utils/auth';
// 学生信息数据类型
export interface StudentInfo {
id?: number
studentNo?: string
name?: string
nameEnglish?: string
formerName?: string
namePinyin?: string
gender?: string
birthDate?: string
idCard?: string
phone?: string
email?: string
nation?: string
politicalStatus?: string
maritalStatus?: string
religion?: string
photoUrl?: string
photoPath?: string
majorCode?: string
majorName?: string
classCode?: string
className?: string
collegeCode?: string
collegeName?: string
grade?: string
enrollmentYear?: number
enrollmentDate?: string
schoolSystem?: number
educationLevel?: string
trainingType?: string
admissionCategory?: string
sourceRegion?: string
middleSchool?: string
studentStatus?: string
infoStatus?: string
counselorName?: string
classTeacherName?: string
qq?: string
wechat?: string
dormitoryBuilding?: string
dormitoryRoom?: string
homeAddress?: string
currentAddress?: string
postalCode?: string
householdRegisterType?: string
hukouLocation?: string
height?: number
weight?: number
bloodType?: string
healthStatus?: string
specialCondition?: string
medicalHistory?: string
allergies?: string
disabilityType?: string
disabilityLevel?: string
emergencyContactName?: string
emergencyContactPhone?: string
emergencyContactRelation?: string
fatherName?: string
fatherPhone?: string
fatherWorkUnit?: string
motherName?: string
motherPhone?: string
motherWorkUnit?: string
guardianName?: string
guardianPhone?: string
guardianRelation?: string
familyIncome?: string
familyEconomicStatus?: string
familyMembers?: number
bankCardNo?: string
bankName?: string
bankBranch?: string
cardHolderName?: string
isPoorStudent?: number
isMinority?: number
isDisabled?: number
isOrphan?: number
isLeftBehindChild?: number
isSingleChild?: number
hasLoan?: number
loanAmount?: number
specialties?: string
awards?: string
violations?: string
socialActivities?: string
dataSource?: string
lastAuditTime?: string
isComplete?: number
completionRate?: number
extField1?: string
extField2?: string
extField3?: string
extField4?: string
extField5?: string
remark?: string
createTime?: string
updateTime?: string
id?: number;
studentNo?: string;
name?: string;
nameEnglish?: string;
formerName?: string;
namePinyin?: string;
gender?: string;
birthDate?: string;
idCard?: string;
phone?: string;
email?: string;
nation?: string;
politicalStatus?: string;
maritalStatus?: string;
religion?: string;
photoUrl?: string;
photoPath?: string;
majorCode?: string;
majorName?: string;
classCode?: string;
className?: string;
collegeCode?: string;
collegeName?: string;
grade?: string;
enrollmentYear?: number;
enrollmentDate?: string;
schoolSystem?: number;
educationLevel?: string;
trainingType?: string;
admissionCategory?: string;
sourceRegion?: string;
middleSchool?: string;
studentStatus?: string;
infoStatus?: string;
counselorName?: string;
classTeacherName?: string;
qq?: string;
wechat?: string;
dormitoryBuilding?: string;
dormitoryRoom?: string;
homeAddress?: string;
currentAddress?: string;
postalCode?: string;
householdRegisterType?: string;
hukouLocation?: string;
height?: number;
weight?: number;
bloodType?: string;
healthStatus?: string;
specialCondition?: string;
medicalHistory?: string;
allergies?: string;
disabilityType?: string;
disabilityLevel?: string;
emergencyContactName?: string;
emergencyContactPhone?: string;
emergencyContactRelation?: string;
fatherName?: string;
fatherPhone?: string;
fatherWorkUnit?: string;
motherName?: string;
motherPhone?: string;
motherWorkUnit?: string;
guardianName?: string;
guardianPhone?: string;
guardianRelation?: string;
familyIncome?: string;
familyEconomicStatus?: string;
familyMembers?: number;
bankCardNo?: string;
bankName?: string;
bankBranch?: string;
cardHolderName?: string;
isPoorStudent?: number;
isMinority?: number;
isDisabled?: number;
isOrphan?: number;
isLeftBehindChild?: number;
isSingleChild?: number;
hasLoan?: number;
loanAmount?: number;
specialties?: string;
awards?: string;
violations?: string;
socialActivities?: string;
dataSource?: string;
lastAuditTime?: string;
isComplete?: number;
completionRate?: number;
extField1?: string;
extField2?: string;
extField3?: string;
extField4?: string;
extField5?: string;
remark?: string;
createTime?: string;
updateTime?: string;
}
// 查询参数类型
export interface StudentQuery {
pageNum?: number
pageSize?: number
studentNo?: string
name?: string
gender?: string
idCard?: string
majorCode?: string
majorName?: string
classCode?: string
className?: string
collegeCode?: string
collegeName?: string
grade?: string
enrollmentYear?: number
studentStatus?: string
infoStatus?: string
counselorId?: number
classTeacherId?: number
pageNum?: number;
pageSize?: number;
studentNo?: string;
name?: string;
gender?: string;
idCard?: string;
majorCode?: string;
majorName?: string;
classCode?: string;
className?: string;
collegeCode?: string;
collegeName?: string;
grade?: string;
enrollmentYear?: number;
studentStatus?: string;
infoStatus?: string;
counselorId?: number;
classTeacherId?: number;
}
// 统计数据类型
export interface StatisticsData {
totalCount?: number
activeCount?: number
completeCount?: number
pendingCount?: number
maleCount?: number
femaleCount?: number
totalCount?: number;
activeCount?: number;
completeCount?: number;
pendingCount?: number;
maleCount?: number;
femaleCount?: number;
}
// 选项数据类型
export interface OptionData {
value: string | number
label: string
code?: string
[key: string]: any
value: string | number;
label: string;
code?: string;
[key: string]: any;
}
// 查询学生信息列表
@@ -149,7 +149,7 @@ export function listStudents(query: StudentQuery): AxiosPromise<any> {
url: '/xg/studentInfo/list',
method: 'get',
params: query
})
});
}
// 获取学生信息详情
@@ -157,7 +157,7 @@ export function getStudent(id: number): AxiosPromise<StudentInfo> {
return request({
url: `/xg/studentInfo/${id}`,
method: 'get'
})
});
}
// 根据学号查询学生信息
@@ -165,7 +165,7 @@ export function getStudentByNo(studentNo: string): AxiosPromise<StudentInfo> {
return request({
url: `/xg/studentInfo/studentNo/${studentNo}`,
method: 'get'
})
});
}
// 根据身份证号查询学生信息
@@ -173,7 +173,7 @@ export function getStudentByIdCard(idCard: string): AxiosPromise<StudentInfo> {
return request({
url: `/xg/studentInfo/idCard/${idCard}`,
method: 'get'
})
});
}
// 根据班级代码查询学生列表
@@ -181,7 +181,7 @@ export function getStudentsByClass(classCode: string): AxiosPromise<StudentInfo[
return request({
url: `/xg/studentInfo/class/${classCode}`,
method: 'get'
})
});
}
// 根据学院代码查询学生列表
@@ -189,7 +189,7 @@ export function getStudentsByCollege(collegeCode: string): AxiosPromise<StudentI
return request({
url: `/xg/studentInfo/college/${collegeCode}`,
method: 'get'
})
});
}
// 新增学生信息
@@ -198,7 +198,7 @@ export function addStudent(data: StudentInfo): AxiosPromise<any> {
url: '/xg/studentInfo',
method: 'post',
data: data
})
});
}
// 修改学生信息
@@ -207,7 +207,7 @@ export function updateStudent(data: StudentInfo): AxiosPromise<any> {
url: '/xg/studentInfo',
method: 'put',
data: data
})
});
}
// 删除学生信息
@@ -215,7 +215,7 @@ export function delStudent(ids: number[]): AxiosPromise<any> {
return request({
url: `/xg/studentInfo/${ids.join(',')}`,
method: 'delete'
})
});
}
// 导出学生信息
@@ -225,7 +225,7 @@ export function exportStudents(query: StudentQuery): AxiosPromise<any> {
method: 'post',
data: query,
responseType: 'blob'
})
});
}
// 批量导入学生信息
@@ -234,7 +234,7 @@ export function importStudents(data: any): AxiosPromise<any> {
url: '/xg/studentInfo/import',
method: 'post',
data: data
})
});
}
// 获取导入模板
@@ -243,7 +243,7 @@ export function getImportTemplate(): AxiosPromise<any> {
url: '/xg/studentInfo/importTemplate',
method: 'post',
responseType: 'blob'
})
});
}
// 批量更新学生信息状态
@@ -252,7 +252,7 @@ export function batchUpdateInfoStatus(studentIds: number[], infoStatus: string):
url: '/xg/studentInfo/batchUpdateInfoStatus',
method: 'put',
data: { studentIds, infoStatus }
})
});
}
// 批量更新学生学籍状态
@@ -261,7 +261,7 @@ export function batchUpdateStudentStatus(studentIds: number[], studentStatus: st
url: '/xg/studentInfo/batchUpdateStudentStatus',
method: 'put',
data: { studentIds, studentStatus }
})
});
}
// 审核学生信息
@@ -270,7 +270,7 @@ export function auditStudent(studentId: number, auditStatus: string, auditRemark
url: `/xg/studentInfo/${studentId}/audit`,
method: 'put',
params: { auditStatus, auditRemark }
})
});
}
// 批量审核学生信息
@@ -279,7 +279,7 @@ export function batchAuditStudents(studentIds: number[], auditStatus: string, au
url: '/xg/studentInfo/batchAudit',
method: 'put',
data: { studentIds, auditStatus, auditRemark }
})
});
}
// ================================
@@ -291,7 +291,7 @@ export function getStatisticsOverview(): AxiosPromise<StatisticsData> {
return request({
url: '/xg/studentInfo/statistics/overview',
method: 'get'
})
});
}
// 统计各学院学生数量
@@ -299,7 +299,7 @@ export function getCollegeStatistics(): AxiosPromise<any[]> {
return request({
url: '/xg/studentInfo/statistics/college',
method: 'get'
})
});
}
// 统计各年级学生数量
@@ -307,7 +307,7 @@ export function getGradeStatistics(): AxiosPromise<any[]> {
return request({
url: '/xg/studentInfo/statistics/grade',
method: 'get'
})
});
}
// 统计各专业学生数量
@@ -315,7 +315,7 @@ export function getMajorStatistics(): AxiosPromise<any[]> {
return request({
url: '/xg/studentInfo/statistics/major',
method: 'get'
})
});
}
// 统计学籍状态分布
@@ -323,7 +323,7 @@ export function getStudentStatusStatistics(): AxiosPromise<any[]> {
return request({
url: '/xg/studentInfo/statistics/studentStatus',
method: 'get'
})
});
}
// 统计信息完整度分布
@@ -331,7 +331,7 @@ export function getCompletionRateStatistics(): AxiosPromise<any[]> {
return request({
url: '/xg/studentInfo/statistics/completionRate',
method: 'get'
})
});
}
// 查询信息不完整的学生列表
@@ -339,7 +339,7 @@ export function getIncompleteStudents(): AxiosPromise<StudentInfo[]> {
return request({
url: '/xg/studentInfo/incomplete',
method: 'get'
})
});
}
// 查询待审核的学生列表
@@ -347,7 +347,7 @@ export function getPendingAuditStudents(): AxiosPromise<StudentInfo[]> {
return request({
url: '/xg/studentInfo/pendingAudit',
method: 'get'
})
});
}
// 生成学生信息报表
@@ -356,7 +356,7 @@ export function generateReport(reportType: string, params?: any): AxiosPromise<a
url: `/xg/studentInfo/report/${reportType}`,
method: 'get',
params: params
})
});
}
// ================================
@@ -368,7 +368,7 @@ export function getCollegeOptions(): AxiosPromise<OptionData[]> {
return request({
url: '/xg/studentInfo/options/colleges',
method: 'get'
})
});
}
// 获取专业选项
@@ -377,7 +377,7 @@ export function getMajorOptions(collegeCode?: string): AxiosPromise<OptionData[]
url: '/xg/studentInfo/options/majors',
method: 'get',
params: { collegeCode }
})
});
}
// 获取班级选项
@@ -386,7 +386,7 @@ export function getClassOptions(majorCode?: string): AxiosPromise<OptionData[]>
url: '/xg/studentInfo/options/classes',
method: 'get',
params: { majorCode }
})
});
}
// 获取辅导员选项
@@ -394,12 +394,12 @@ export function getCounselorOptions(): AxiosPromise<OptionData[]> {
return request({
url: '/xg/studentInfo/options/counselors',
method: 'get'
})
});
}
// 获取班主任选项
export function getClassTeacherOptions(): AxiosPromise<OptionData[]> {
console.log('调用班主任API: /xg/classInfo/teacherOptions')
console.log('调用班主任API: /xg/classInfo/teacherOptions');
return request({
url: '/xg/classInfo/teacherOptions',
method: 'get',
@@ -408,7 +408,7 @@ export function getClassTeacherOptions(): AxiosPromise<OptionData[]> {
'Authorization': 'Bearer ' + getToken(),
'clientid': import.meta.env.VITE_APP_CLIENT_ID
}
})
});
}
// 检查学号是否存在
@@ -417,7 +417,7 @@ export function checkStudentNo(studentNo: string, studentId?: number): AxiosProm
url: '/xg/studentInfo/checkStudentNo',
method: 'get',
params: { studentNo, studentId }
})
});
}
// 检查身份证号是否存在
@@ -426,7 +426,7 @@ export function checkIdCard(idCard: string, studentId?: number): AxiosPromise<bo
url: '/xg/studentInfo/checkIdCard',
method: 'get',
params: { idCard, studentId }
})
});
}
// 查询重复学号的学生列表
@@ -434,7 +434,7 @@ export function getDuplicateStudentNo(): AxiosPromise<any[]> {
return request({
url: '/xg/studentInfo/duplicateStudentNo',
method: 'get'
})
});
}
// 查询重复身份证号的学生列表
@@ -442,7 +442,7 @@ export function getDuplicateIdCard(): AxiosPromise<any[]> {
return request({
url: '/xg/studentInfo/duplicateIdCard',
method: 'get'
})
});
}
// 更新学生信息完整度
@@ -450,5 +450,5 @@ export function updateCompletionRate(studentId: number): AxiosPromise<any> {
return request({
url: `/xg/studentInfo/${studentId}/updateCompletionRate`,
method: 'put'
})
});
}
File diff suppressed because it is too large. Load diff
+1 -1
View File
@@ -10,7 +10,7 @@ interface ClassTeacherCounselorRelBo {
teacherName?: string;
counselorId?: number | string;
counselorName?: string;
relationType?: number; // 1:班主任 2:辅导员
relationType?: number; // 1:班主任 2:辅导员
remark?: string;
}
+1 -1
View File
@@ -15,7 +15,7 @@ import request from '@/utils/request';
// export * from './activity' // 活动管理
// 导出学工用户模块API
export * from './sysUser'
export * from './sysUser';
// 临时导出一个空对象,防止编译错误
export default {};
-3
View File
@@ -61,6 +61,3 @@ export interface DormBedForm {
/** 备注 */
remark?: string;
}
+4 -10
View File
@@ -58,7 +58,6 @@ export interface DormRoomVO {
* 备注
*/
remark: string;
}
export interface DormRoomForm extends BaseEntity {
@@ -116,11 +115,9 @@ export interface DormRoomForm extends BaseEntity {
* 备注
*/
remark?: string;
}
export interface DormRoomQuery extends PageQuery {
/**
* 房间编号
*/
@@ -166,11 +163,8 @@ export interface DormRoomQuery extends PageQuery {
*/
deleted?: string;
/**
* 日期范围参数
*/
params?: any;
/**
* 日期范围参数
*/
params?: any;
}
@@ -38,7 +38,6 @@ export interface DormManagerSourceVO {
* 备注
*/
remark: string;
}
export interface DormManagerSourceForm extends BaseEntity {
@@ -81,11 +80,9 @@ export interface DormManagerSourceForm extends BaseEntity {
* 备注
*/
remark?: string;
}
export interface DormManagerSourceQuery extends PageQuery {
/**
* 管理员ID
*/
@@ -116,11 +113,8 @@ export interface DormManagerSourceQuery extends PageQuery {
*/
deleted?: string;
/**
* 日期范围参数
*/
params?: any;
/**
* 日期范围参数
*/
params?: any;
}
@@ -28,7 +28,6 @@ export interface DormManagerBuildingVO {
* 备注
*/
remark: string;
}
export interface DormManagerBuildingForm extends BaseEntity {
@@ -61,11 +60,9 @@ export interface DormManagerBuildingForm extends BaseEntity {
* 备注
*/
remark?: string;
}
export interface DormManagerBuildingQuery extends PageQuery {
/**
* 管理员ID
*/
@@ -86,11 +83,8 @@ export interface DormManagerBuildingQuery extends PageQuery {
*/
deleted?: string;
/**
* 日期范围参数
*/
params?: any;
/**
* 日期范围参数
*/
params?: any;
}
@@ -53,7 +53,6 @@ export interface DormManagerVO {
* 授权范围名称(逗号分隔的字符串)
*/
scopeNames?: string;
}
export interface DormManagerForm extends BaseEntity {
@@ -106,11 +105,9 @@ export interface DormManagerForm extends BaseEntity {
* 备注
*/
remark?: string;
}
export interface DormManagerQuery extends PageQuery {
/**
* 管理员姓名
*/
@@ -151,11 +148,8 @@ export interface DormManagerQuery extends PageQuery {
*/
deleted?: string;
/**
* 日期范围参数
*/
params?: any;
/**
* 日期范围参数
*/
params?: any;
}
@@ -17,8 +17,7 @@ export interface DormBuildingVO {
floors: number;
}
export interface DormBuildingForm extends Omit<DormBuildingVO, 'createTime' | 'createBy' | 'updateTime' | 'updateBy' | 'areaName' | 'campusName'> {
}
export interface DormBuildingForm extends Omit<DormBuildingVO, 'createTime' | 'createBy' | 'updateTime' | 'updateBy' | 'areaName' | 'campusName'> {}
export interface DormBuildingQuery {
buildingCode?: string;
@@ -45,6 +44,3 @@ export interface BuildingTreeSelectVo {
floors?: number;
updateTime?: string;
}
+4 -10
View File
@@ -28,7 +28,6 @@ export interface DormAreaVO {
* 排序号
*/
sortOrder: number;
}
export interface DormAreaForm extends BaseEntity {
@@ -61,11 +60,9 @@ export interface DormAreaForm extends BaseEntity {
* 排序号
*/
sortOrder?: number;
}
export interface DormAreaQuery extends PageQuery {
/**
* 宿舍区编码
*/
@@ -81,10 +78,10 @@ export interface DormAreaQuery extends PageQuery {
*/
areaDescription?: string;
/**
* 日期范围参数
*/
params?: any;
/**
* 日期范围参数
*/
params?: any;
}
/**
@@ -102,6 +99,3 @@ export interface TreeSelectVo {
/** 子节点 */
children?: TreeSelectVo[];
}
+4 -10
View File
@@ -28,7 +28,6 @@ export interface DormCampusVO {
* 备注
*/
remark: string;
}
export interface DormCampusForm extends BaseEntity {
@@ -61,11 +60,9 @@ export interface DormCampusForm extends BaseEntity {
* 备注
*/
remark?: string;
}
export interface DormCampusQuery extends PageQuery {
/**
* 校区编码
*/
@@ -76,11 +73,8 @@ export interface DormCampusQuery extends PageQuery {
*/
campusName?: string;
/**
* 日期范围参数
*/
params?: any;
/**
* 日期范围参数
*/
params?: any;
}
+2 -2
View File
@@ -198,7 +198,7 @@ export function getMockDormTree(): { data: DormTreeNode[] } {
}
// 生成房间数据的模拟数据
export function getMockRooms(buildingId: string | number, floorNumber?: number): { rows: RoomInfo[], total: number } {
export function getMockRooms(buildingId: string | number, floorNumber?: number): { rows: RoomInfo[]; total: number } {
const rooms: RoomInfo[] = [];
// 根据buildingId判断楼层数
@@ -233,7 +233,7 @@ export function getMockRooms(buildingId: string | number, floorNumber?: number):
}
return {
rows: rooms.filter(room => !floorNumber || room.floorNumber === floorNumber),
rows: rooms.filter((room) => !floorNumber || room.floorNumber === floorNumber),
total: rooms.length
};
}
@@ -33,7 +33,6 @@ export interface StuColBatchVO {
* 收集类型 1 - 新学期收集 2 - 其他
*/
collectionType: string;
}
export interface StuColBatchForm extends BaseEntity {
@@ -71,11 +70,9 @@ export interface StuColBatchForm extends BaseEntity {
* 收集类型 1 - 新学期收集 2 - 其他
*/
collectionType?: string;
}
export interface StuColBatchQuery extends PageQuery {
/**
* 批次名称
*/
@@ -91,11 +88,8 @@ export interface StuColBatchQuery extends PageQuery {
*/
collectionType?: string;
/**
* 日期范围参数
*/
params?: any;
/**
* 日期范围参数
*/
params?: any;
}
@@ -48,7 +48,6 @@ export interface ClassMsgVO {
* 备注信息
*/
remark: string;
}
export interface ClassMsgForm extends BaseEntity {
@@ -291,11 +290,9 @@ export interface ClassMsgForm extends BaseEntity {
* 备注信息
*/
remark?: string;
}
export interface ClassMsgQuery extends PageQuery {
/**
* 学院代码
*/
@@ -326,11 +323,8 @@ export interface ClassMsgQuery extends PageQuery {
*/
classStatus?: string;
/**
* 日期范围参数
*/
params?: any;
/**
* 日期范围参数
*/
params?: any;
}
@@ -48,7 +48,6 @@ export interface StuColBatchClassVO {
* 删除标志(0代表存在 1代表删除)
*/
deleted: number;
}
export interface StuColBatchClassForm extends BaseEntity {
@@ -101,11 +100,9 @@ export interface StuColBatchClassForm extends BaseEntity {
* 删除标志(0代表存在 1代表删除)
*/
deleted?: number;
}
export interface StuColBatchClassQuery extends PageQuery {
/**
* 批次ID
*/
@@ -146,11 +143,8 @@ export interface StuColBatchClassQuery extends PageQuery {
*/
deleted?: number;
/**
* 日期范围参数
*/
params?: any;
/**
* 日期范围参数
*/
params?: any;
}
+4 -10
View File
@@ -33,7 +33,6 @@ export interface StuColBatchVO {
* 收集类型 1 - 新学期收集 2 - 其他
*/
collectionType: string;
}
export interface StuColBatchForm extends BaseEntity {
@@ -71,11 +70,9 @@ export interface StuColBatchForm extends BaseEntity {
* 收集类型 1 - 新学期收集 2 - 其他
*/
collectionType?: string;
}
export interface StuColBatchQuery extends PageQuery {
/**
* 批次名称
*/
@@ -91,11 +88,8 @@ export interface StuColBatchQuery extends PageQuery {
*/
collectionType?: string;
/**
* 日期范围参数
*/
params?: any;
/**
* 日期范围参数
*/
params?: any;
}
+4 -10
View File
@@ -33,7 +33,6 @@ export interface StuColBatchVO {
* 收集类型 1 - 新学期收集 2 - 其他
*/
collectionType: string;
}
export interface StuColBatchForm extends BaseEntity {
@@ -71,11 +70,9 @@ export interface StuColBatchForm extends BaseEntity {
* 收集类型 1 - 新学期收集 2 - 其他
*/
collectionType?: string;
}
export interface StuColBatchQuery extends PageQuery {
/**
* 批次名称
*/
@@ -91,11 +88,8 @@ export interface StuColBatchQuery extends PageQuery {
*/
collectionType?: string;
/**
* 日期范围参数
*/
params?: any;
/**
* 日期范围参数
*/
params?: any;
}
+19 -9
View File
@@ -2,7 +2,6 @@ import request from '@/utils/request';
import { AxiosPromise } from 'axios';
import { StudentQuery, StudentVO, StudentForm, XgDeptTreeVO, R, TableDataInfo } from './types';
/**
* 查询部门下拉树结构
* @param deptId 部门ID
@@ -32,7 +31,6 @@ export const XgDeptTreeSelect = (belongDeptId?: number): AxiosPromise<XgDeptTree
// }) as any;
};
// 查询学生列表
export function listStudent(query?: StudentQuery): AxiosPromise<R<TableDataInfo<StudentVO[]>>> {
// 实际使用时连接后端接口
@@ -56,11 +54,13 @@ export function listStudent(query?: StudentQuery): AxiosPromise<R<TableDataInfo<
}
// 查询学生详细
export function getStudent(studentId?: string | number): AxiosPromise<R<{
student: StudentVO;
majors: any[];
classes: any[];
}>> {
export function getStudent(studentId?: string | number): AxiosPromise<
R<{
student: StudentVO;
majors: any[];
classes: any[];
}>
> {
if (studentId) {
// 实际使用时连接后端接口
// return request({
@@ -69,7 +69,7 @@ export function getStudent(studentId?: string | number): AxiosPromise<R<{
// });
// 模拟数据
const student = mockStudentData.find(item => item.studentId === studentId);
const student = mockStudentData.find((item) => item.studentId === studentId);
return Promise.resolve({
data: {
code: 200,
@@ -435,4 +435,14 @@ const mockClasses = [
}
];
export default { listStudent, getStudent, addStudent, updateStudent, delStudent, deptTreeSelect, exportStudent, changeStudentStatus, XgDeptTreeSelect };
export default {
listStudent,
getStudent,
addStudent,
updateStudent,
delStudent,
deptTreeSelect,
exportStudent,
changeStudentStatus,
XgDeptTreeSelect
};
@@ -45,7 +45,7 @@ export interface StudentMsgVO {
birthDate: string;
/**
* 民族
* 民族
*/
nationality: string;
@@ -140,7 +140,7 @@ export interface StudentMsgVO {
wechat: string;
/**
* 婚姻状况:1 未婚、2 已婚、3 离异、4 丧偶
* 婚姻状况:1 未婚、2 已婚、3 离异、4 丧偶
*/
maritalStatus: string;
@@ -518,7 +518,6 @@ export interface StudentMsgVO {
* 备注信息
*/
remark: string;
}
export interface StudentMsgForm extends BaseEntity {
@@ -568,7 +567,7 @@ export interface StudentMsgForm extends BaseEntity {
birthDate?: string;
/**
* 民族
* 民族
*/
nationality?: string;
@@ -663,7 +662,7 @@ export interface StudentMsgForm extends BaseEntity {
wechat?: string;
/**
* 婚姻状况:1 未婚、2 已婚、3 离异、4 丧偶
* 婚姻状况:1 未婚、2 已婚、3 离异、4 丧偶
*/
maritalStatus?: string;
@@ -1041,11 +1040,9 @@ export interface StudentMsgForm extends BaseEntity {
* 备注信息
*/
remark?: string;
}
export interface StudentMsgQuery extends PageQuery {
/**
* 删除标志(0代表存在 1代表删除)
*/
@@ -1087,7 +1084,7 @@ export interface StudentMsgQuery extends PageQuery {
birthDate?: string;
/**
* 民族
* 民族
*/
nationality?: string;
@@ -1182,7 +1179,7 @@ export interface StudentMsgQuery extends PageQuery {
wechat?: string;
/**
* 婚姻状况:1 未婚、2 已婚、3 离异、4 丧偶
* 婚姻状况:1 未婚、2 已婚、3 离异、4 丧偶
*/
maritalStatus?: string;
@@ -1556,11 +1553,8 @@ export interface StudentMsgQuery extends PageQuery {
*/
extField5?: string;
/**
* 日期范围参数
*/
params?: any;
/**
* 日期范围参数
*/
params?: any;
}
@@ -45,7 +45,7 @@ export interface StudentMsgBatchVO {
birthDate: string;
/**
* 民族
* 民族
*/
nationality: string;
@@ -140,7 +140,7 @@ export interface StudentMsgBatchVO {
wechat: string;
/**
* 婚姻状况:1 未婚、2 已婚、3 离异、4 丧偶
* 婚姻状况:1 未婚、2 已婚、3 离异、4 丧偶
*/
maritalStatus: string;
@@ -523,7 +523,6 @@ export interface StudentMsgBatchVO {
* 正在进行中的批次id
*/
batchId: string | number;
}
export interface StudentMsgBatchForm extends BaseEntity {
@@ -573,7 +572,7 @@ export interface StudentMsgBatchForm extends BaseEntity {
birthDate?: string;
/**
* 民族
* 民族
*/
nationality?: string;
@@ -668,7 +667,7 @@ export interface StudentMsgBatchForm extends BaseEntity {
wechat?: string;
/**
* 婚姻状况:1 未婚、2 已婚、3 离异、4 丧偶
* 婚姻状况:1 未婚、2 已婚、3 离异、4 丧偶
*/
maritalStatus?: string;
@@ -1051,11 +1050,9 @@ export interface StudentMsgBatchForm extends BaseEntity {
* 正在进行中的批次id
*/
batchId?: string | number;
}
export interface StudentMsgBatchQuery extends PageQuery {
/**
* 删除标志(0代表存在 1代表删除)
*/
@@ -1097,7 +1094,7 @@ export interface StudentMsgBatchQuery extends PageQuery {
birthDate?: string;
/**
* 民族
* 民族
*/
nationality?: string;
@@ -1192,7 +1189,7 @@ export interface StudentMsgBatchQuery extends PageQuery {
wechat?: string;
/**
* 婚姻状况:1 未婚、2 已婚、3 离异、4 丧偶
* 婚姻状况:1 未婚、2 已婚、3 离异、4 丧偶
*/
maritalStatus?: string;
@@ -1571,11 +1568,8 @@ export interface StudentMsgBatchQuery extends PageQuery {
*/
batchId?: string | number;
/**
* 日期范围参数
*/
params?: any;
/**
* 日期范围参数
*/
params?: any;
}
@@ -53,7 +53,6 @@ export interface XgClassPosDictVO {
* 备注信息
*/
remark: string;
}
export interface XgClassPosDictForm extends BaseEntity {
@@ -111,11 +110,9 @@ export interface XgClassPosDictForm extends BaseEntity {
* 备注信息
*/
remark?: string;
}
export interface XgClassPosDictQuery extends PageQuery {
/**
* 删除标志(0代表存在 1代表删除)
*/
@@ -161,11 +158,8 @@ export interface XgClassPosDictQuery extends PageQuery {
*/
sortOrder?: number;
/**
* 日期范围参数
*/
params?: any;
/**
* 日期范围参数
*/
params?: any;
}
@@ -118,7 +118,6 @@ export interface XgClassPosMsgVO {
* 备注信息
*/
remark: string;
}
export interface XgClassPosMsgForm extends BaseEntity {
@@ -246,11 +245,9 @@ export interface XgClassPosMsgForm extends BaseEntity {
* 备注信息
*/
remark?: string;
}
export interface XgClassPosMsgQuery extends PageQuery {
/**
* 删除标志(0代表存在 1代表删除)
*/
@@ -336,11 +333,8 @@ export interface XgClassPosMsgQuery extends PageQuery {
*/
deptId?: string | number;
/**
* 日期范围参数
*/
params?: any;
/**
* 日期范围参数
*/
params?: any;
}
@@ -63,7 +63,6 @@ export interface XgLeaveTypeConfigVO {
* 销假逾期多少时长给予提醒(天)
*/
cancelOverdueReminder: number;
}
export interface XgLeaveTypeConfigForm extends BaseEntity {
@@ -131,11 +130,9 @@ export interface XgLeaveTypeConfigForm extends BaseEntity {
* 销假逾期多少时长给予提醒(天)
*/
cancelOverdueReminder?: number;
}
export interface XgLeaveTypeConfigQuery extends PageQuery {
/**
* 删除标志(0代表存在 1代表删除)
*/
@@ -156,11 +153,8 @@ export interface XgLeaveTypeConfigQuery extends PageQuery {
*/
isEnabled?: string;
/**
* 日期范围参数
*/
params?: any;
/**
* 日期范围参数
*/
params?: any;
}
@@ -48,7 +48,6 @@ export interface StudentManagerVO {
* 备注
*/
remark: string;
}
export interface StudentManagerForm extends BaseEntity {
@@ -101,11 +100,9 @@ export interface StudentManagerForm extends BaseEntity {
* 备注
*/
remark?: string;
}
export interface StudentManagerQuery extends PageQuery {
/**
* 管理员姓名
*/
@@ -146,11 +143,8 @@ export interface StudentManagerQuery extends PageQuery {
*/
deleted?: string;
/**
* 日期范围参数
*/
params?: any;
/**
* 日期范围参数
*/
params?: any;
}
@@ -1,6 +1,10 @@
import request from '@/utils/request';
import { AxiosPromise } from 'axios';
import { StudentManagerSourceVO, StudentManagerSourceForm, StudentManagerSourceQuery } from '@/api/xg/xsjcxx/xgStudentManagerSource/studentManagerSource/types';
import {
StudentManagerSourceVO,
StudentManagerSourceForm,
StudentManagerSourceQuery
} from '@/api/xg/xsjcxx/xgStudentManagerSource/studentManagerSource/types';
/**
* 查询学生管理员授权关系列表
@@ -38,7 +38,6 @@ export interface StudentManagerSourceVO {
* 备注
*/
remark: string;
}
export interface StudentManagerSourceForm extends BaseEntity {
@@ -81,11 +80,9 @@ export interface StudentManagerSourceForm extends BaseEntity {
* 备注
*/
remark?: string;
}
export interface StudentManagerSourceQuery extends PageQuery {
/**
* 管理员ID
*/
@@ -116,11 +113,8 @@ export interface StudentManagerSourceQuery extends PageQuery {
*/
deleted?: string;
/**
* 日期范围参数
*/
params?: any;
/**
* 日期范围参数
*/
params?: any;
}
@@ -1 +0,0 @@
+5 -5
View File
@@ -129,7 +129,7 @@ export const submitAppointment = async (data: XljkConsultationAppointmentForm) =
console.error('检查预约限制失败', error);
// 检查失败不阻止预约,继续提交
}
// 提交预约申请
return request({
url: '/api/xljk/consultation/appointment/submit',
@@ -141,15 +141,15 @@ export const submitAppointment = async (data: XljkConsultationAppointmentForm) =
// 获取我的预约记录(学生用)
export const getMyAppointments = (studentId: string | number, forceRefresh: boolean = false) => {
let url = `/api/xljk/consultation/appointment/my-appointments/${studentId}`;
// 强制添加时间戳参数,确保绕过浏览器缓存
url += `?_t=${new Date().getTime()}`;
// 如果需要额外强制刷新(如用户手动点击刷新按钮)
if (forceRefresh) {
url += `&force=true`;
}
return request({
url,
method: 'get',
@@ -211,4 +211,4 @@ export const getClassOptions = (collegeName?: string, majorName?: string) => {
method: 'get',
params: { collegeName, majorName }
});
};
};
+3 -3
View File
@@ -9,12 +9,12 @@ import { NoShowQuery, NoShowVO } from './types';
export function listNoShow(query: NoShowQuery) {
// 创建一个新的查询对象,避免修改原始对象
const params = { ...query };
// 如果状态是'all',表示"全部",则从参数中移除
if (params.status === 'all') {
delete params.status;
}
return request({
url: '/api/xljk/consultation/no-show/list',
method: 'get',
@@ -57,4 +57,4 @@ export function checkLimitation(studentId: string | number) {
method: 'get',
params: { studentId }
});
}
}
+4 -4
View File
@@ -556,17 +556,17 @@ export interface NoShowVO {
* 咨询师名称
*/
counselorName?: string;
/**
* 预约日期
*/
appointmentDate?: string;
/**
* 预约时段
*/
appointmentDetailTime?: string;
/**
* 咨询原因
*/
@@ -601,4 +601,4 @@ export interface NoShowVO {
* 更新时间
*/
updateTime?: string;
}
}
+5 -5
View File
@@ -35,15 +35,15 @@ export const addEvaluation = (data: XljkEvaluationForm) => {
if (data.departmentName && !data.departmentId) {
data.departmentId = 0; // 使用临时ID,后端会根据名称处理
}
if (data.majorName && !data.majorId) {
data.majorId = 0; // 使用临时ID,后端会根据名称处理
}
if (data.className && !data.classId) {
data.classId = 0; // 使用临时ID,后端会根据名称处理
}
return request({
url: '/api/xljk/evaluation',
method: 'post',
@@ -182,10 +182,10 @@ export const batchAddEvaluationDetails = (data: XljkEvaluationDetailForm[]) => {
export const batchUpdateMentalStatus = (data: XljkEvaluationDetailForm[]) => {
// 确保数据中的ID字段保持原有格式,不做Number转换
console.log('批量更新数据 - 第一条记录evaluationId:', data[0]?.evaluationId);
return request({
url: '/api/xljk/evaluation/detail/batch/mentalStatus',
method: 'put',
data: data
});
};
};
+1 -1
View File
@@ -525,4 +525,4 @@ export interface EvaluationStatistics {
* 重点关注学生百分比
*/
focusPercent: number;
}
}
+1
View File
@@ -103,6 +103,7 @@ export function listEnrollmentInfo(query: EnrollmentInfoQuery) {
## 📱 移动端支持
部分接口支持移动端访问,主要包括:
- 学生在线报名
- 报名状态查询
- 录取结果查询
+2 -2
View File
@@ -58,7 +58,7 @@ export const updateEnrollmentInfo = (data: EnrollmentInfoForm) => {
export const updateEnrollmentStatus = (id: string | number | Array<string | number>, status: string) => {
const data = Array.isArray(id)
? { ids: id, status } // 批量更新
: { id, status }; // 单个更新
: { id, status }; // 单个更新
return request({
url: '/zsjy/enrollment/info/updateStatus',
@@ -132,7 +132,7 @@ export const submitEnrollmentApplication = (data: EnrollmentInfoForm) => {
* 查询我的报名状态(供学生端使用)
* @param params 查询参数
*/
export const queryMyEnrollmentStatus = (params: { idCard?: string, examNumber?: string }): AxiosPromise<EnrollmentInfoVO> => {
export const queryMyEnrollmentStatus = (params: { idCard?: string; examNumber?: string }): AxiosPromise<EnrollmentInfoVO> => {
return request({
url: '/zsjy/enrollment/info/myStatus',
method: 'get',
+119 -135
View File
@@ -1,365 +1,365 @@
import request from '@/utils/request'
import { AxiosPromise } from 'axios'
import request from '@/utils/request';
import { AxiosPromise } from 'axios';
export interface EnrollmentProcessVO {
/**
* 主键ID
*/
id: string | number
id: string | number;
/**
* 报名信息ID,关联zsjy_enrollment_info.id
*/
enrollmentId: number
enrollmentId: number;
/**
* 学生姓名
*/
studentName: string
studentName: string;
/**
* 身份证号
*/
idCard: string
idCard: string;
/**
* 当前状态(0-待审核 1-信息审核中 2-录取处理中 3-待最终确认 4-录取完成 9-已驳回)
*/
currentStatus: string
currentStatus: string;
/**
* 当前处理人ID
*/
currentHandler: number
currentHandler: number;
/**
* 当前处理人姓名
*/
currentHandlerName: string
currentHandlerName: string;
/**
* 信息审核状态(0-待审核 1-审核通过 9-审核驳回)
*/
infoAuditStatus: string
infoAuditStatus: string;
/**
* 信息审核人ID
*/
infoAuditUser: number
infoAuditUser: number;
/**
* 信息审核人姓名
*/
infoAuditUserName: string
infoAuditUserName: string;
/**
* 信息审核时间
*/
infoAuditTime: string
infoAuditTime: string;
/**
* 信息审核意见
*/
infoAuditOpinion: string
infoAuditOpinion: string;
/**
* 录取处理状态(0-待处理 1-处理完成 9-处理驳回)
*/
admissionStatus: string
admissionStatus: string;
/**
* 录取处理人ID
*/
admissionUser: number
admissionUser: number;
/**
* 录取处理人姓名
*/
admissionUserName: string
admissionUserName: string;
/**
* 录取处理时间
*/
admissionTime: string
admissionTime: string;
/**
* 缴费状态(0-未缴费 1-已缴费)
*/
paymentStatus: string
paymentStatus: string;
/**
* 缴费确认时间
*/
paymentConfirmTime: string
paymentConfirmTime: string;
/**
* 录取通知书号码
*/
noticeNumber: string
noticeNumber: string;
/**
* 录取处理意见
*/
admissionOpinion: string
admissionOpinion: string;
/**
* 最终审核状态(0-待确认 1-确认通过 9-确认驳回)
*/
finalAuditStatus: string
finalAuditStatus: string;
/**
* 最终审核人ID
*/
finalAuditUser: number
finalAuditUser: number;
/**
* 最终审核人姓名
*/
finalAuditUserName: string
finalAuditUserName: string;
/**
* 最终审核时间
*/
finalAuditTime: string
finalAuditTime: string;
/**
* 最终审核意见
*/
finalAuditOpinion: string
finalAuditOpinion: string;
/**
* 是否完成录取(0-未完成 1-已完成)
*/
isCompleted: number
isCompleted: number;
/**
* 录取完成时间
*/
completionTime: string
completionTime: string;
/**
* 备注
*/
remark: string
remark: string;
/**
* 创建时间
*/
createTime: string
createTime: string;
/**
* 更新时间
*/
updateTime: string
updateTime: string;
/**
* 性别(0-男 1-女)
*/
gender?: string
gender?: string;
/**
* 地区/省份
*/
province?: string
province?: string;
/**
* 学院名称(驼峰命名)
*/
collegeName?: string
collegeName?: string;
/**
* 学院代码(驼峰命名)
*/
collegeId?: string
collegeId?: string;
/**
* 学院名称(下划线命名)
*/
college_name?: string
college_name?: string;
/**
* 学院代码(下划线命名)
*/
college_id?: string
college_id?: string;
/**
* 学院名称(拼写变体 - collage)
*/
collage_name?: string
collage_name?: string;
/**
* 学院代码(拼写变体 - collage)
*/
collage_id?: string
collage_id?: string;
/**
* 专业名称(驼峰命名)
*/
majorName?: string
majorName?: string;
/**
* 专业代码(驼峰命名)
*/
majorCode?: string
majorCode?: string;
/**
* 专业名称(下划线命名)
*/
major_name?: string
major_name?: string;
/**
* 专业代码(下划线命名)
*/
major_code?: string
major_code?: string;
/**
* 报考专业(来自报名信息表)
*/
appliedMajor?: string
appliedMajor?: string;
/**
* 关联的报名信息对象
*/
enrollmentInfo?: any
enrollmentInfo?: any;
}
export interface EnrollmentProcessForm extends BaseEntity {
/**
* 主键ID
*/
id?: string | number
id?: string | number;
/**
* 报名信息ID,关联zsjy_enrollment_info.id
*/
enrollmentId?: number
enrollmentId?: number;
/**
* 学生姓名
*/
studentName?: string
studentName?: string;
/**
* 身份证号
*/
idCard?: string
idCard?: string;
/**
* 当前状态(0-待审核 1-信息审核中 2-录取处理中 3-待最终确认 4-录取完成 9-已驳回)
*/
currentStatus?: string
currentStatus?: string;
/**
* 当前处理人ID
*/
currentHandler?: number
currentHandler?: number;
/**
* 当前处理人姓名
*/
currentHandlerName?: string
currentHandlerName?: string;
/**
* 信息审核状态(0-待审核 1-审核通过 9-审核驳回)
*/
infoAuditStatus?: string
infoAuditStatus?: string;
/**
* 信息审核意见
*/
infoAuditOpinion?: string
infoAuditOpinion?: string;
/**
* 录取处理状态(0-待处理 1-处理完成 9-处理驳回)
*/
admissionStatus?: string
admissionStatus?: string;
/**
* 缴费状态(0-未缴费 1-已缴费)
*/
paymentStatus?: string
paymentStatus?: string;
/**
* 录取通知书号码
*/
noticeNumber?: string
noticeNumber?: string;
/**
* 录取处理意见
*/
admissionOpinion?: string
admissionOpinion?: string;
/**
* 最终审核状态(0-待确认 1-确认通过 9-确认驳回)
*/
finalAuditStatus?: string
finalAuditStatus?: string;
/**
* 最终审核意见
*/
finalAuditOpinion?: string
finalAuditOpinion?: string;
/**
* 是否完成录取(0-未完成 1-已完成)
*/
isCompleted?: number
isCompleted?: number;
/**
* 备注
*/
remark?: string
remark?: string;
}
export interface EnrollmentProcessQuery extends PageQuery {
/**
* 报名信息ID,关联zsjy_enrollment_info.id
*/
enrollmentId?: number
enrollmentId?: number;
/**
* 学生姓名
*/
studentName?: string
studentName?: string;
/**
* 身份证号
*/
idCard?: string
idCard?: string;
/**
* 当前状态(0-待审核 1-信息审核中 2-录取处理中 3-待最终确认 4-录取完成 9-已驳回)
*/
currentStatus?: string
currentStatus?: string;
/**
* 当前处理人ID
*/
currentHandler?: number
currentHandler?: number;
/**
* 信息审核状态(0-待审核 1-审核通过 9-审核驳回)
*/
infoAuditStatus?: string
infoAuditStatus?: string;
/**
* 录取处理状态(0-待处理 1-处理完成 9-处理驳回)
*/
admissionStatus?: string
admissionStatus?: string;
/**
* 最终审核状态(0-待确认 1-确认通过 9-确认驳回)
*/
finalAuditStatus?: string
finalAuditStatus?: string;
/**
* 是否完成录取(0-未完成 1-已完成)
*/
isCompleted?: number
isCompleted?: number;
/**
* 日期范围参数
*/
params?: any
params?: any;
}
// 查询录取流程状态列表
@@ -368,8 +368,8 @@ export const listEnrollmentProcess = (query: EnrollmentProcessQuery): AxiosPromi
url: '/zsjy/enrollment/process/list',
method: 'get',
params: query
})
}
});
};
// 查询我的待审核列表
export const listMyAuditProcess = (query: EnrollmentProcessQuery): AxiosPromise<EnrollmentProcessVO[]> => {
@@ -377,16 +377,16 @@ export const listMyAuditProcess = (query: EnrollmentProcessQuery): AxiosPromise<
url: '/zsjy/enrollment/process/myAudit',
method: 'get',
params: query
})
}
});
};
// 查询录取流程状态详细
export const getEnrollmentProcess = (id: string | number): AxiosPromise<EnrollmentProcessVO> => {
return request({
url: '/zsjy/enrollment/process/' + id,
method: 'get'
})
}
});
};
// 新增录取流程状态
export const addEnrollmentProcess = (data: EnrollmentProcessForm) => {
@@ -394,8 +394,8 @@ export const addEnrollmentProcess = (data: EnrollmentProcessForm) => {
url: '/zsjy/enrollment/process',
method: 'post',
data: data
})
}
});
};
// 从报名信息创建流程
export const createProcessFromEnrollment = (enrollmentId: number) => {
@@ -403,8 +403,8 @@ export const createProcessFromEnrollment = (enrollmentId: number) => {
url: '/zsjy/enrollment/process/createFromEnrollment',
method: 'post',
params: { enrollmentId }
})
}
});
};
// 同步流程状态
export const syncProcessStatus = (id: string | number) => {
@@ -412,8 +412,8 @@ export const syncProcessStatus = (id: string | number) => {
url: '/zsjy/enrollment/process/syncStatus',
method: 'post',
params: { id }
})
}
});
};
// 修改录取流程状态
export const updateEnrollmentProcess = (data: EnrollmentProcessForm) => {
@@ -421,24 +421,24 @@ export const updateEnrollmentProcess = (data: EnrollmentProcessForm) => {
url: '/zsjy/enrollment/process',
method: 'put',
data: data
})
}
});
};
// 删除录取流程状态
export const delEnrollmentProcess = (id: string | number | Array<string | number>) => {
return request({
url: '/zsjy/enrollment/process/' + id,
method: 'delete'
})
}
});
};
// 根据报名信息ID查询流程状态
export const getProcessByEnrollmentId = (enrollmentId: number): AxiosPromise<EnrollmentProcessVO> => {
return request({
url: '/zsjy/enrollment/process/enrollment/' + enrollmentId,
method: 'get'
})
}
});
};
// 查询待处理的流程列表
export const getPendingProcessList = (query: EnrollmentProcessQuery): AxiosPromise<EnrollmentProcessVO[]> => {
@@ -446,8 +446,8 @@ export const getPendingProcessList = (query: EnrollmentProcessQuery): AxiosPromi
url: '/zsjy/enrollment/process/pending',
method: 'get',
params: query
})
}
});
};
// 根据状态查询流程列表
export const getProcessListByStatus = (status: string, query: EnrollmentProcessQuery): AxiosPromise<EnrollmentProcessVO[]> => {
@@ -455,16 +455,16 @@ export const getProcessListByStatus = (status: string, query: EnrollmentProcessQ
url: '/zsjy/enrollment/process/status/' + status,
method: 'get',
params: query
})
}
});
};
// 获取流程统计数据
export const getProcessStatistics = (): AxiosPromise<any> => {
return request({
url: '/zsjy/enrollment/process/statistics',
method: 'get'
})
}
});
};
// 搜索流程
export const searchProcess = (keyword: string, query: EnrollmentProcessQuery): AxiosPromise<EnrollmentProcessVO[]> => {
@@ -472,35 +472,26 @@ export const searchProcess = (keyword: string, query: EnrollmentProcessQuery): A
url: '/zsjy/enrollment/process/search',
method: 'get',
params: { keyword, ...query }
})
}
});
};
// 信息审核
export const infoAudit = (data: {
processId: number;
auditResult: string;
auditOpinion?: string;
}) => {
export const infoAudit = (data: { processId: number; auditResult: string; auditOpinion?: string }) => {
return request({
url: '/zsjy/enrollment/process/infoAudit',
method: 'post',
params: data
})
}
});
};
// 录取处理
export const admissionProcess = (data: {
processId: number;
admissionResult: string;
admissionOpinion?: string;
noticeNumber?: string;
}) => {
export const admissionProcess = (data: { processId: number; admissionResult: string; admissionOpinion?: string; noticeNumber?: string }) => {
return request({
url: '/zsjy/enrollment/process/admissionProcess',
method: 'post',
params: data
})
}
});
};
// 确认缴费
export const confirmPayment = (processId: number) => {
@@ -508,21 +499,17 @@ export const confirmPayment = (processId: number) => {
url: '/zsjy/enrollment/process/confirmPayment',
method: 'post',
params: { processId }
})
}
});
};
// 最终审核
export const finalAudit = (data: {
processId: number;
finalResult: string;
finalOpinion?: string;
}) => {
export const finalAudit = (data: { processId: number; finalResult: string; finalOpinion?: string }) => {
return request({
url: '/zsjy/enrollment/process/finalConfirm',
method: 'post',
params: data
})
}
});
};
// 完成流程
export const completeProcess = (processId: number) => {
@@ -530,20 +517,17 @@ export const completeProcess = (processId: number) => {
url: '/zsjy/enrollment/process/complete',
method: 'post',
params: { processId }
})
}
});
};
// 驳回流程
export const rejectProcess = (data: {
processId: number;
rejectReason: string;
}) => {
export const rejectProcess = (data: { processId: number; rejectReason: string }) => {
return request({
url: '/zsjy/enrollment/process/reject',
method: 'post',
params: data
})
}
});
};
// 重置流程
export const resetProcess = (processId: number) => {
@@ -551,8 +535,8 @@ export const resetProcess = (processId: number) => {
url: '/zsjy/enrollment/process/reset',
method: 'post',
params: { processId }
})
}
});
};
// 导出录取流程状态
export const exportEnrollmentProcess = (query: EnrollmentProcessQuery) => {
@@ -560,5 +544,5 @@ export const exportEnrollmentProcess = (query: EnrollmentProcessQuery) => {
url: '/zsjy/enrollment/process/export',
method: 'post',
data: query
})
}
});
};
+14 -14
View File
@@ -1,6 +1,6 @@
import request from '@/utils/request'
import type { PageQuery } from '@/types/page'
import type { EnrollmentProcessVO } from './enrollmentProcess'
import request from '@/utils/request';
import type { PageQuery } from '@/types/page';
import type { EnrollmentProcessVO } from './enrollmentProcess';
/**
* 查询招生录取统计列表
@@ -12,7 +12,7 @@ export function listEnrollmentStatistics(query: PageQuery) {
url: '/zsjy/enrollment/statistics/list',
method: 'get',
params: query
})
});
}
/**
@@ -25,7 +25,7 @@ export function exportEnrollmentStatistics(query: PageQuery) {
method: 'post',
params: query,
responseType: 'blob'
})
});
}
/**
@@ -37,7 +37,7 @@ export function getEnrollmentStatistics(id: string | number) {
return request({
url: `/zsjy/enrollment/statistics/${id}`,
method: 'get'
})
});
}
/**
@@ -48,7 +48,7 @@ export function getEnrollmentStatisticsChartData() {
return request({
url: '/zsjy/enrollment/statistics/chart',
method: 'get'
})
});
}
/**
@@ -59,7 +59,7 @@ export function getEnrollmentStatisticsOverview() {
return request({
url: '/zsjy/enrollment/statistics/overview',
method: 'get'
})
});
}
/**
@@ -70,7 +70,7 @@ export function getEnrollmentStatisticsByMajor() {
return request({
url: '/zsjy/enrollment/statistics/major',
method: 'get'
})
});
}
/**
@@ -81,7 +81,7 @@ export function getEnrollmentStatisticsByCollege() {
return request({
url: '/zsjy/enrollment/statistics/college',
method: 'get'
})
});
}
/**
@@ -92,7 +92,7 @@ export function getEnrollmentStatisticsByRegion() {
return request({
url: '/zsjy/enrollment/statistics/region',
method: 'get'
})
});
}
/**
@@ -103,7 +103,7 @@ export function getEnrollmentStatisticsByGender() {
return request({
url: '/zsjy/enrollment/statistics/gender',
method: 'get'
})
});
}
/**
@@ -114,7 +114,7 @@ export function getEnrollmentStatisticsByPayment() {
return request({
url: '/zsjy/enrollment/statistics/payment',
method: 'get'
})
});
}
/**
@@ -125,5 +125,5 @@ export function getEnrollmentStatisticsByStatus() {
return request({
url: '/zsjy/enrollment/statistics/status',
method: 'get'
})
});
}
+1 -6
View File
@@ -1,11 +1,6 @@
import request from '@/utils/request';
import { AxiosPromise } from 'axios';
import {
GraduateInfoVO,
GraduateInfoForm,
GraduateInfoQuery,
GraduateStatisticsVO
} from '@/api/zsjy/types';
import { GraduateInfoVO, GraduateInfoForm, GraduateInfoQuery, GraduateStatisticsVO } from '@/api/zsjy/types';
/**
* 查询毕业生信息列表
+5 -5
View File
@@ -7,14 +7,14 @@
import request from '@/utils/request';
// 导出招生就业管理模块相关的所有API接口
export * from './enterprise'; // 企业信息管理
export * from './enterprise'; // 企业信息管理
// export * from './jobPosition'; // 招聘岗位管理 (待开发)
// export * from './recruitmentPoster'; // 招聘海报管理 (待开发)
// 现有导出
export * from './statistics'; // 统计分析
export * from './enrollmentProcess'; // 录取流程管理
export * from './enrollmentStatistics'; // 报名统计
export * from './statistics'; // 统计分析
export * from './enrollmentProcess'; // 录取流程管理
export * from './enrollmentStatistics'; // 报名统计
// 类型导出
export * from './types'; // 类型定义
export * from './types'; // 类型定义
+11 -11
View File
@@ -1,6 +1,6 @@
import request from '@/utils/request'
import { JobPositionVO, JobPositionForm, JobPositionQuery, TableDataInfo } from './types'
import { AxiosPromise } from 'axios'
import request from '@/utils/request';
import { JobPositionVO, JobPositionForm, JobPositionQuery, TableDataInfo } from './types';
import { AxiosPromise } from 'axios';
/**
* 查询招聘岗位列表
@@ -12,7 +12,7 @@ export function listJobPosition(query?: JobPositionQuery): AxiosPromise<TableDat
url: '/zsjy/position/list',
method: 'get',
params: query
})
});
}
/**
@@ -24,7 +24,7 @@ export function getJobPosition(id: string | number): AxiosPromise<JobPositionVO>
return request({
url: '/zsjy/position/' + id,
method: 'get'
})
});
}
/**
@@ -37,7 +37,7 @@ export function addJobPosition(data: JobPositionForm) {
url: '/zsjy/position',
method: 'post',
data: data
})
});
}
/**
@@ -50,7 +50,7 @@ export function updateJobPosition(data: JobPositionForm) {
url: '/zsjy/position',
method: 'put',
data: data
})
});
}
/**
@@ -62,7 +62,7 @@ export function delJobPosition(id: string | number | Array<string | number>) {
return request({
url: '/zsjy/position/' + id,
method: 'delete'
})
});
}
/**
@@ -76,7 +76,7 @@ export function exportJobPosition(query: JobPositionQuery) {
method: 'post',
data: query,
responseType: 'blob'
})
});
}
/**
@@ -87,5 +87,5 @@ export function listEnterpriseOptions() {
return request({
url: '/zsjy/enterprise/options',
method: 'get'
})
}
});
}
+11 -11
View File
@@ -1,6 +1,6 @@
import request from '@/utils/request'
import { RecruitmentPosterVO, RecruitmentPosterForm, RecruitmentPosterQuery, TableDataInfo } from './types'
import { AxiosPromise } from 'axios'
import request from '@/utils/request';
import { RecruitmentPosterVO, RecruitmentPosterForm, RecruitmentPosterQuery, TableDataInfo } from './types';
import { AxiosPromise } from 'axios';
/**
* 查询招聘海报列表
@@ -12,7 +12,7 @@ export function listRecruitmentPoster(query?: RecruitmentPosterQuery): AxiosProm
url: '/zsjy/poster/list',
method: 'get',
params: query
})
});
}
/**
@@ -24,7 +24,7 @@ export function getRecruitmentPoster(id: string | number): AxiosPromise<Recruitm
return request({
url: '/zsjy/poster/' + id,
method: 'get'
})
});
}
/**
@@ -37,7 +37,7 @@ export function addRecruitmentPoster(data: RecruitmentPosterForm) {
url: '/zsjy/poster',
method: 'post',
data: data
})
});
}
/**
@@ -50,7 +50,7 @@ export function updateRecruitmentPoster(data: RecruitmentPosterForm) {
url: '/zsjy/poster',
method: 'put',
data: data
})
});
}
/**
@@ -62,7 +62,7 @@ export function delRecruitmentPoster(id: string | number | Array<string | number
return request({
url: '/zsjy/poster/' + id,
method: 'delete'
})
});
}
/**
@@ -76,7 +76,7 @@ export function exportRecruitmentPoster(query: RecruitmentPosterQuery) {
method: 'post',
data: query,
responseType: 'blob'
})
});
}
/**
@@ -89,5 +89,5 @@ export function listPositionOptions(enterpriseId?: number | string) {
url: '/zsjy/position/options',
method: 'get',
params: { enterpriseId }
})
}
});
}
+11 -11
View File
@@ -11,7 +11,7 @@ export function getStatisticsOverview() {
headers: {
'isToken': true
}
}).catch(error => {
}).catch((error) => {
console.error('获取统计概览数据失败:', error);
ElMessage.error('获取统计数据失败,请联系管理员');
throw error;
@@ -28,7 +28,7 @@ export function getMajorDistribution() {
headers: {
'isToken': true
}
}).catch(error => {
}).catch((error) => {
console.error('获取专业分布数据失败:', error);
ElMessage.error('获取专业分布数据失败,请联系管理员');
throw error;
@@ -45,7 +45,7 @@ export function getRegionDistribution() {
headers: {
'isToken': true
}
}).catch(error => {
}).catch((error) => {
console.error('获取地区分布数据失败:', error);
ElMessage.error('获取地区分布数据失败,请联系管理员');
throw error;
@@ -62,7 +62,7 @@ export function getGenderRatio() {
headers: {
'isToken': true
}
}).catch(error => {
}).catch((error) => {
console.error('获取性别比例数据失败:', error);
ElMessage.error('获取性别比例数据失败,请联系管理员');
throw error;
@@ -81,7 +81,7 @@ export function getEnrollmentTrend(days?: number) {
headers: {
'isToken': true
}
}).catch(error => {
}).catch((error) => {
console.error('获取报名趋势数据失败:', error);
ElMessage.error('获取报名趋势数据失败,请联系管理员');
throw error;
@@ -98,7 +98,7 @@ export function getStatusDistribution() {
headers: {
'isToken': true
}
}).catch(error => {
}).catch((error) => {
console.error('获取状态分布数据失败:', error);
ElMessage.error('获取状态分布数据失败,请联系管理员');
throw error;
@@ -113,9 +113,9 @@ export interface StatisticsOverview {
paymentRate: string;
admittedCount: number;
admissionRate: string;
majorDistribution: Array<{[key: string]: any}>;
regionDistribution: Array<{[key: string]: any}>;
genderRatio: {[key: string]: number};
weeklyTrend: Array<{[key: string]: any}>;
statusDistribution: {[key: string]: number};
majorDistribution: Array<{ [key: string]: any }>;
regionDistribution: Array<{ [key: string]: any }>;
genderRatio: { [key: string]: number };
weeklyTrend: Array<{ [key: string]: any }>;
statusDistribution: { [key: string]: number };
}
+2 -1
View File
@@ -54,7 +54,8 @@ html.dark svg {
margin: 0px 0px 0 0 !important;
}
#app .sidebar-container .nest-menu .el-sub-menu > .el-sub-menu__title, #app .sidebar-container .el-sub-menu .el-menu-item{
#app .sidebar-container .nest-menu .el-sub-menu > .el-sub-menu__title,
#app .sidebar-container .el-sub-menu .el-menu-item {
margin: 0px 0px 0 0 !important;
}
@@ -312,8 +312,6 @@ const performUpload = async (options: any) => {
// 上传成功回调
const handleUploadSuccess = (res: any, file: any) => {
// 确保res存在且有正确的结构
if (res && res.code === 200) {
// 避免重复添加,检查是否已经存在
+87 -102
View File
@@ -1,11 +1,7 @@
<template>
<div class="poster-preview">
<!-- 海报画布容器 -->
<div
ref="posterRef"
class="poster-canvas"
:style="canvasStyle"
>
<div ref="posterRef" class="poster-canvas" :style="canvasStyle">
<!-- 企业信息叠加 -->
<div class="info-overlay">
<!-- 顶部企业标题 -->
@@ -15,7 +11,7 @@
</div>
<!-- 中间内容区域 - 根据海报类型显示不同内容 -->
<!-- 企业海报:展示企业文化和优势 -->
<div v-if="posterData.posterType === '1'" class="position-info">
<div class="position-title">加入我们 · 共创辉煌</div>
@@ -26,7 +22,7 @@
<br />
<br />
<br />
<div class="detail-item" v-if="enterpriseDetail.mainBusiness">
<span class="icon">🏢</span>
<span>{{ enterpriseDetail.mainBusiness }}</span>
@@ -44,16 +40,16 @@
<!-- 岗位海报:展示具体岗位信息 -->
<div v-else-if="posterData.posterType === '2'" class="position-info">
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<div class="position-title">
{{ posterData.positionName || positionDetail?.positionName || '招聘岗位' }}
</div>
@@ -89,7 +85,6 @@
<!-- 底部联系信息 - 根据海报类型显示不同内容 -->
<div class="company-footer">
<div v-if="enterpriseDetail" class="company-info">
<!-- 企业海报:重点显示联系方式 -->
<template v-if="posterData.posterType === '1'">
<div class="info-row" v-if="enterpriseDetail.contactPhone">
@@ -125,7 +120,6 @@
<span class="value">{{ enterpriseDetail.email }}</span>
</div>
</template>
</div>
</div>
</div>
@@ -133,56 +127,52 @@
<!-- 操作按钮 -->
<div class="action-buttons">
<el-button type="primary" icon="Download" @click="exportToPDF" :loading="exporting">
导出PDF
</el-button>
<el-button icon="Picture" @click="exportToImage" :loading="exporting">
导出图片
</el-button>
<el-button type="primary" icon="Download" @click="exportToPDF" :loading="exporting"> 导出PDF </el-button>
<el-button icon="Picture" @click="exportToImage" :loading="exporting"> 导出图片 </el-button>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, computed } from 'vue'
import html2canvas from 'html2canvas'
import jsPDF from 'jspdf'
import { ElMessage } from 'element-plus'
import { ref, computed } from 'vue';
import html2canvas from 'html2canvas';
import jsPDF from 'jspdf';
import { ElMessage } from 'element-plus';
// 定义接口
interface PosterData {
posterPath: string
enterpriseName: string
positionName?: string
posterType: string
posterPath: string;
enterpriseName: string;
positionName?: string;
posterType: string;
}
interface EnterpriseDetail {
mainBusiness?: string
enterpriseScale?: string
contactPhone?: string
email?: string
address?: string
mainBusiness?: string;
enterpriseScale?: string;
contactPhone?: string;
email?: string;
address?: string;
}
interface PositionDetail {
positionName?: string
workAddress?: string
salary?: string
workTime?: string
requiredSkills?: string
positionName?: string;
workAddress?: string;
salary?: string;
workTime?: string;
requiredSkills?: string;
}
// Props定义
const props = defineProps<{
posterData: PosterData
enterpriseDetail?: EnterpriseDetail
positionDetail?: PositionDetail
}>()
posterData: PosterData;
enterpriseDetail?: EnterpriseDetail;
positionDetail?: PositionDetail;
}>();
// 响应式数据
const posterRef = ref<HTMLElement>()
const exporting = ref(false)
const posterRef = ref<HTMLElement>();
const exporting = ref(false);
// 计算样式
const canvasStyle = computed(() => ({
@@ -190,7 +180,7 @@ const canvasStyle = computed(() => ({
backgroundSize: 'cover',
backgroundPosition: 'center',
backgroundRepeat: 'no-repeat'
}))
}));
// 企业规模字典
const scaleDict = {
@@ -199,79 +189,79 @@ const scaleDict = {
'3': '200-500人',
'4': '500-1000人',
'5': '1000人以上'
}
};
// 获取规模标签
const getScaleLabel = (value: string) => {
return scaleDict[value as keyof typeof scaleDict] || value
}
return scaleDict[value as keyof typeof scaleDict] || value;
};
// 导出为PDF
const exportToPDF = async () => {
if (!posterRef.value) return
if (!posterRef.value) return;
try {
exporting.value = true
exporting.value = true;
const canvas = await html2canvas(posterRef.value, {
backgroundColor: null,
scale: 2,
useCORS: true,
allowTaint: true,
foreignObjectRendering: false
})
const imgData = canvas.toDataURL('image/png')
});
const imgData = canvas.toDataURL('image/png');
const pdf = new jsPDF({
orientation: 'portrait',
unit: 'mm',
format: 'a4'
})
const imgWidth = 190
const imgHeight = (canvas.height * imgWidth) / canvas.width
pdf.addImage(imgData, 'PNG', 10, 10, imgWidth, imgHeight)
const fileName = `${props.posterData.enterpriseName}_招聘海报_${new Date().getTime()}.pdf`
pdf.save(fileName)
ElMessage.success('PDF导出成功')
});
const imgWidth = 190;
const imgHeight = (canvas.height * imgWidth) / canvas.width;
pdf.addImage(imgData, 'PNG', 10, 10, imgWidth, imgHeight);
const fileName = `${props.posterData.enterpriseName}_招聘海报_${new Date().getTime()}.pdf`;
pdf.save(fileName);
ElMessage.success('PDF导出成功');
} catch (error) {
console.error('PDF导出失败:', error)
ElMessage.error('PDF导出失败,请重试')
console.error('PDF导出失败:', error);
ElMessage.error('PDF导出失败,请重试');
} finally {
exporting.value = false
exporting.value = false;
}
}
};
// 导出为图片
const exportToImage = async () => {
if (!posterRef.value) return
if (!posterRef.value) return;
try {
exporting.value = true
exporting.value = true;
const canvas = await html2canvas(posterRef.value, {
backgroundColor: null,
scale: 2,
useCORS: true,
allowTaint: true
})
const link = document.createElement('a')
link.download = `${props.posterData.enterpriseName}_招聘海报_${new Date().getTime()}.png`
link.href = canvas.toDataURL('image/png')
link.click()
ElMessage.success('图片导出成功')
});
const link = document.createElement('a');
link.download = `${props.posterData.enterpriseName}_招聘海报_${new Date().getTime()}.png`;
link.href = canvas.toDataURL('image/png');
link.click();
ElMessage.success('图片导出成功');
} catch (error) {
console.error('图片导出失败:', error)
ElMessage.error('图片导出失败,请重试')
console.error('图片导出失败:', error);
ElMessage.error('图片导出失败,请重试');
} finally {
exporting.value = false
exporting.value = false;
}
}
};
</script>
<style scoped lang="scss">
@@ -304,12 +294,7 @@ const exportToImage = async () => {
padding: 40px;
color: white;
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.8);
background: linear-gradient(
180deg,
rgba(0, 0, 0, 0.3) 0%,
rgba(0, 0, 0, 0.1) 50%,
rgba(0, 0, 0, 0.4) 100%
);
background: linear-gradient(180deg, rgba(0, 0, 0, 0.3) 0%, rgba(0, 0, 0, 0.1) 50%, rgba(0, 0, 0, 0.4) 100%);
}
.company-header {
@@ -440,22 +425,22 @@ const exportToImage = async () => {
max-width: 450px;
max-height: 600px;
}
.info-overlay {
padding: 20px;
}
.company-name {
font-size: 32px;
}
.position-title {
font-size: 24px;
}
.detail-item {
font-size: 14px;
padding: 8px 15px;
}
}
</style>
</style>
+3 -3
View File
@@ -17,7 +17,7 @@ export default {
// const { data: downloadUrl } = await getFileDownloadUrl(String(ossId));
const response = await request({
url: "/resource/oss/download/" + ossId,
url: '/resource/oss/download/' + ossId,
method: 'get',
responseType: 'blob',
timeout: 300000 // 300秒超时
@@ -50,7 +50,7 @@ export default {
// const { data: downloadUrl } = await getFileDownloadUrl(String(ossId));
const response = await request({
url: "/resource/oss/download/" + ossId,
url: '/resource/oss/download/' + ossId,
method: 'get',
responseType: 'blob',
timeout: 300000 // 300秒超时
@@ -83,7 +83,7 @@ export default {
method: 'get',
url: url,
responseType: 'blob',
timeout: 300000 ,// 300秒超时
timeout: 300000, // 300秒超时
headers: globalHeaders()
});
const isBlob = blobValidate(res.data);
+5 -5
View File
@@ -1,14 +1,14 @@
import { App } from 'vue'
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
import { App } from 'vue';
import * as ElementPlusIconsVue from '@element-plus/icons-vue';
export default function registerIcons(app: App) {
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
app.component(key, component)
app.component(key, component);
}
// 移除重复的Close组件注册
// app.component('Close', ElementPlusIconsVue.Close);
// 注册常用图标的别名
app.component('CircleCloseIcon', ElementPlusIconsVue.CircleClose);
}
+1 -1
View File
@@ -88,7 +88,7 @@ export const constantRoutes: RouteRecordRaw[] = [
meta: { title: '个人中心', icon: 'user' }
}
]
},
}
];
// 动态路由,基于用户权限动态去加载
+2 -2
View File
@@ -87,12 +87,12 @@ declare global {
declare interface PageQuery {
pageNum: number;
pageSize: number;
/**
* 排序字段
*/
orderByColumn?: string;
/**
* 排序方式(asc/desc)
*/
+3 -3
View File
@@ -28,7 +28,7 @@ axios.defaults.headers['clientid'] = import.meta.env.VITE_APP_CLIENT_ID;
// 创建 axios 实例
const service = axios.create({
baseURL: import.meta.env.VITE_APP_BASE_API,
timeout: 300000 // 增加到5分钟,适合大数据量导出
timeout: 300000 // 增加到5分钟,适合大数据量导出
});
// 请求拦截器
@@ -177,7 +177,7 @@ service.interceptors.response.use(
console.log('404错误被静默处理,不显示用户提示');
return Promise.reject(error);
}
// 对心理健康测评详情页面的403错误进行特殊处理
if (statusCode === '403') {
const url = error.config.url;
@@ -187,7 +187,7 @@ service.interceptors.response.use(
return Promise.reject(error);
}
}
message = '系统接口' + statusCode + '异常';
}
ElMessage({ message: message, type: 'error', duration: 5 * 1000 });
@@ -17,39 +17,24 @@
<span>产出指标</span>
</div>
</template>
<!-- 数量指标 -->
<div class="indicator-item">
<h4 class="indicator-title">数量指标</h4>
<el-row :gutter="16">
<el-col :span="8">
<el-form-item label="绩效目标名称">
<el-input
v-model="indicators.quantity.goalName"
placeholder="请输入绩效目标名称"
maxlength="200"
@input="emitChange"
/>
<el-input v-model="indicators.quantity.goalName" placeholder="请输入绩效目标名称" maxlength="200" @input="emitChange" />
</el-form-item>
</el-col>
<el-col :span="4">
<el-form-item label="目标值">
<el-input
v-model="indicators.quantity.targetValue"
placeholder="目标值"
maxlength="100"
@input="emitChange"
/>
<el-input v-model="indicators.quantity.targetValue" placeholder="目标值" maxlength="100" @input="emitChange" />
</el-form-item>
</el-col>
<el-col :span="3">
<el-form-item label="单位">
<el-input
v-model="indicators.quantity.unit"
placeholder="单位"
maxlength="20"
@input="emitChange"
/>
<el-input v-model="indicators.quantity.unit" placeholder="单位" maxlength="20" @input="emitChange" />
</el-form-item>
</el-col>
<el-col :span="3">
@@ -67,12 +52,7 @@
</el-col>
<el-col :span="6">
<el-form-item label="详细说明">
<el-input
v-model="indicators.quantity.description"
placeholder="详细说明"
maxlength="500"
@input="emitChange"
/>
<el-input v-model="indicators.quantity.description" placeholder="详细说明" maxlength="500" @input="emitChange" />
</el-form-item>
</el-col>
</el-row>
@@ -84,32 +64,17 @@
<el-row :gutter="16">
<el-col :span="8">
<el-form-item label="绩效目标名称">
<el-input
v-model="indicators.quality.goalName"
placeholder="请输入绩效目标名称"
maxlength="200"
@input="emitChange"
/>
<el-input v-model="indicators.quality.goalName" placeholder="请输入绩效目标名称" maxlength="200" @input="emitChange" />
</el-form-item>
</el-col>
<el-col :span="4">
<el-form-item label="目标值">
<el-input
v-model="indicators.quality.targetValue"
placeholder="目标值"
maxlength="100"
@input="emitChange"
/>
<el-input v-model="indicators.quality.targetValue" placeholder="目标值" maxlength="100" @input="emitChange" />
</el-form-item>
</el-col>
<el-col :span="3">
<el-form-item label="单位">
<el-input
v-model="indicators.quality.unit"
placeholder="单位"
maxlength="20"
@input="emitChange"
/>
<el-input v-model="indicators.quality.unit" placeholder="单位" maxlength="20" @input="emitChange" />
</el-form-item>
</el-col>
<el-col :span="3">
@@ -127,12 +92,7 @@
</el-col>
<el-col :span="6">
<el-form-item label="详细说明">
<el-input
v-model="indicators.quality.description"
placeholder="详细说明"
maxlength="500"
@input="emitChange"
/>
<el-input v-model="indicators.quality.description" placeholder="详细说明" maxlength="500" @input="emitChange" />
</el-form-item>
</el-col>
</el-row>
@@ -144,32 +104,17 @@
<el-row :gutter="16">
<el-col :span="8">
<el-form-item label="绩效目标名称">
<el-input
v-model="indicators.timeliness.goalName"
placeholder="请输入绩效目标名称"
maxlength="200"
@input="emitChange"
/>
<el-input v-model="indicators.timeliness.goalName" placeholder="请输入绩效目标名称" maxlength="200" @input="emitChange" />
</el-form-item>
</el-col>
<el-col :span="4">
<el-form-item label="目标值">
<el-input
v-model="indicators.timeliness.targetValue"
placeholder="目标值"
maxlength="100"
@input="emitChange"
/>
<el-input v-model="indicators.timeliness.targetValue" placeholder="目标值" maxlength="100" @input="emitChange" />
</el-form-item>
</el-col>
<el-col :span="3">
<el-form-item label="单位">
<el-input
v-model="indicators.timeliness.unit"
placeholder="单位"
maxlength="20"
@input="emitChange"
/>
<el-input v-model="indicators.timeliness.unit" placeholder="单位" maxlength="20" @input="emitChange" />
</el-form-item>
</el-col>
<el-col :span="3">
@@ -187,12 +132,7 @@
</el-col>
<el-col :span="6">
<el-form-item label="详细说明">
<el-input
v-model="indicators.timeliness.description"
placeholder="详细说明"
maxlength="500"
@input="emitChange"
/>
<el-input v-model="indicators.timeliness.description" placeholder="详细说明" maxlength="500" @input="emitChange" />
</el-form-item>
</el-col>
</el-row>
@@ -204,32 +144,17 @@
<el-row :gutter="16">
<el-col :span="8">
<el-form-item label="绩效目标名称">
<el-input
v-model="indicators.cost.goalName"
placeholder="请输入绩效目标名称"
maxlength="200"
@input="emitChange"
/>
<el-input v-model="indicators.cost.goalName" placeholder="请输入绩效目标名称" maxlength="200" @input="emitChange" />
</el-form-item>
</el-col>
<el-col :span="4">
<el-form-item label="目标值">
<el-input
v-model="indicators.cost.targetValue"
placeholder="目标值"
maxlength="100"
@input="emitChange"
/>
<el-input v-model="indicators.cost.targetValue" placeholder="目标值" maxlength="100" @input="emitChange" />
</el-form-item>
</el-col>
<el-col :span="3">
<el-form-item label="单位">
<el-input
v-model="indicators.cost.unit"
placeholder="单位"
maxlength="20"
@input="emitChange"
/>
<el-input v-model="indicators.cost.unit" placeholder="单位" maxlength="20" @input="emitChange" />
</el-form-item>
</el-col>
<el-col :span="3">
@@ -247,12 +172,7 @@
</el-col>
<el-col :span="6">
<el-form-item label="详细说明">
<el-input
v-model="indicators.cost.description"
placeholder="详细说明"
maxlength="500"
@input="emitChange"
/>
<el-input v-model="indicators.cost.description" placeholder="详细说明" maxlength="500" @input="emitChange" />
</el-form-item>
</el-col>
</el-row>
@@ -267,39 +187,24 @@
<span>效益指标</span>
</div>
</template>
<!-- 经济效益指标 -->
<div class="indicator-item">
<h4 class="indicator-title">经济效益指标</h4>
<el-row :gutter="16">
<el-col :span="8">
<el-form-item label="绩效目标名称">
<el-input
v-model="indicators.economic.goalName"
placeholder="请输入绩效目标名称"
maxlength="200"
@input="emitChange"
/>
<el-input v-model="indicators.economic.goalName" placeholder="请输入绩效目标名称" maxlength="200" @input="emitChange" />
</el-form-item>
</el-col>
<el-col :span="4">
<el-form-item label="目标值">
<el-input
v-model="indicators.economic.targetValue"
placeholder="目标值"
maxlength="100"
@input="emitChange"
/>
<el-input v-model="indicators.economic.targetValue" placeholder="目标值" maxlength="100" @input="emitChange" />
</el-form-item>
</el-col>
<el-col :span="3">
<el-form-item label="单位">
<el-input
v-model="indicators.economic.unit"
placeholder="单位"
maxlength="20"
@input="emitChange"
/>
<el-input v-model="indicators.economic.unit" placeholder="单位" maxlength="20" @input="emitChange" />
</el-form-item>
</el-col>
<el-col :span="3">
@@ -317,12 +222,7 @@
</el-col>
<el-col :span="6">
<el-form-item label="详细说明">
<el-input
v-model="indicators.economic.description"
placeholder="详细说明"
maxlength="500"
@input="emitChange"
/>
<el-input v-model="indicators.economic.description" placeholder="详细说明" maxlength="500" @input="emitChange" />
</el-form-item>
</el-col>
</el-row>
@@ -334,32 +234,17 @@
<el-row :gutter="16">
<el-col :span="8">
<el-form-item label="绩效目标名称">
<el-input
v-model="indicators.social.goalName"
placeholder="请输入绩效目标名称"
maxlength="200"
@input="emitChange"
/>
<el-input v-model="indicators.social.goalName" placeholder="请输入绩效目标名称" maxlength="200" @input="emitChange" />
</el-form-item>
</el-col>
<el-col :span="4">
<el-form-item label="目标值">
<el-input
v-model="indicators.social.targetValue"
placeholder="目标值"
maxlength="100"
@input="emitChange"
/>
<el-input v-model="indicators.social.targetValue" placeholder="目标值" maxlength="100" @input="emitChange" />
</el-form-item>
</el-col>
<el-col :span="3">
<el-form-item label="单位">
<el-input
v-model="indicators.social.unit"
placeholder="单位"
maxlength="20"
@input="emitChange"
/>
<el-input v-model="indicators.social.unit" placeholder="单位" maxlength="20" @input="emitChange" />
</el-form-item>
</el-col>
<el-col :span="3">
@@ -377,12 +262,7 @@
</el-col>
<el-col :span="6">
<el-form-item label="详细说明">
<el-input
v-model="indicators.social.description"
placeholder="详细说明"
maxlength="500"
@input="emitChange"
/>
<el-input v-model="indicators.social.description" placeholder="详细说明" maxlength="500" @input="emitChange" />
</el-form-item>
</el-col>
</el-row>
@@ -394,32 +274,17 @@
<el-row :gutter="16">
<el-col :span="8">
<el-form-item label="绩效目标名称">
<el-input
v-model="indicators.ecological.goalName"
placeholder="请输入绩效目标名称"
maxlength="200"
@input="emitChange"
/>
<el-input v-model="indicators.ecological.goalName" placeholder="请输入绩效目标名称" maxlength="200" @input="emitChange" />
</el-form-item>
</el-col>
<el-col :span="4">
<el-form-item label="目标值">
<el-input
v-model="indicators.ecological.targetValue"
placeholder="目标值"
maxlength="100"
@input="emitChange"
/>
<el-input v-model="indicators.ecological.targetValue" placeholder="目标值" maxlength="100" @input="emitChange" />
</el-form-item>
</el-col>
<el-col :span="3">
<el-form-item label="单位">
<el-input
v-model="indicators.ecological.unit"
placeholder="单位"
maxlength="20"
@input="emitChange"
/>
<el-input v-model="indicators.ecological.unit" placeholder="单位" maxlength="20" @input="emitChange" />
</el-form-item>
</el-col>
<el-col :span="3">
@@ -437,12 +302,7 @@
</el-col>
<el-col :span="6">
<el-form-item label="详细说明">
<el-input
v-model="indicators.ecological.description"
placeholder="详细说明"
maxlength="500"
@input="emitChange"
/>
<el-input v-model="indicators.ecological.description" placeholder="详细说明" maxlength="500" @input="emitChange" />
</el-form-item>
</el-col>
</el-row>
@@ -457,39 +317,24 @@
<span>满意度指标</span>
</div>
</template>
<!-- 服务对象满意度指标 -->
<div class="indicator-item">
<h4 class="indicator-title">服务对象满意度指标</h4>
<el-row :gutter="16">
<el-col :span="8">
<el-form-item label="绩效目标名称">
<el-input
v-model="indicators.service_satisfaction.goalName"
placeholder="请输入绩效目标名称"
maxlength="200"
@input="emitChange"
/>
<el-input v-model="indicators.service_satisfaction.goalName" placeholder="请输入绩效目标名称" maxlength="200" @input="emitChange" />
</el-form-item>
</el-col>
<el-col :span="4">
<el-form-item label="目标值">
<el-input
v-model="indicators.service_satisfaction.targetValue"
placeholder="目标值"
maxlength="100"
@input="emitChange"
/>
<el-input v-model="indicators.service_satisfaction.targetValue" placeholder="目标值" maxlength="100" @input="emitChange" />
</el-form-item>
</el-col>
<el-col :span="3">
<el-form-item label="单位">
<el-input
v-model="indicators.service_satisfaction.unit"
placeholder="单位"
maxlength="20"
@input="emitChange"
/>
<el-input v-model="indicators.service_satisfaction.unit" placeholder="单位" maxlength="20" @input="emitChange" />
</el-form-item>
</el-col>
<el-col :span="3">
@@ -507,12 +352,7 @@
</el-col>
<el-col :span="6">
<el-form-item label="详细说明">
<el-input
v-model="indicators.service_satisfaction.description"
placeholder="详细说明"
maxlength="500"
@input="emitChange"
/>
<el-input v-model="indicators.service_satisfaction.description" placeholder="详细说明" maxlength="500" @input="emitChange" />
</el-form-item>
</el-col>
</el-row>
@@ -531,22 +371,22 @@
</template>
<script lang="ts">
import { defineComponent, ref, computed, watch } from 'vue'
import { TrendCharts, Money, Star } from '@element-plus/icons-vue'
import { defineComponent, ref, computed, watch } from 'vue';
import { TrendCharts, Money, Star } from '@element-plus/icons-vue';
interface PerformanceIndicator {
indicatorType: string
indicatorTypeName: string
goalName: string
targetValue: string
unit: string
weightPercentage: number
description: string
measurementMethod: string
dataSource: string
isQuantitative: boolean
sortOrder: number
status: string
indicatorType: string;
indicatorTypeName: string;
goalName: string;
targetValue: string;
unit: string;
weightPercentage: number;
description: string;
measurementMethod: string;
dataSource: string;
isQuantitative: boolean;
sortOrder: number;
status: string;
}
export default defineComponent({
@@ -582,7 +422,7 @@ export default defineComponent({
isQuantitative: true,
sortOrder: 0,
status: '1'
})
});
// 8种固定的指标类型
const indicators = ref({
@@ -594,46 +434,46 @@ export default defineComponent({
social: createIndicator('social', '社会效益指标'),
ecological: createIndicator('ecological', '生态效益指标'),
service_satisfaction: createIndicator('service_satisfaction', '服务对象满意度指标')
})
});
// 计算总分值占比
const totalWeight = computed(() => {
let total = 0
Object.values(indicators.value).forEach(indicator => {
total += indicator.weightPercentage || 0
})
return Math.round(total * 10) / 10
})
let total = 0;
Object.values(indicators.value).forEach((indicator) => {
total += indicator.weightPercentage || 0;
});
return Math.round(total * 10) / 10;
});
// 收集有效的指标数据
const collectValidIndicators = () => {
const validIndicators: PerformanceIndicator[] = []
let sortOrder = 0
Object.values(indicators.value).forEach(indicator => {
const validIndicators: PerformanceIndicator[] = [];
let sortOrder = 0;
Object.values(indicators.value).forEach((indicator) => {
// 只有填写了目标名称的指标才被认为是有效的
if (indicator.goalName?.trim()) {
validIndicators.push({
...indicator,
sortOrder: sortOrder++
})
});
}
})
return validIndicators
}
});
return validIndicators;
};
// 发出变化事件
const emitChange = () => {
const validIndicators = collectValidIndicators()
emit('update:modelValue', validIndicators)
emit('change', validIndicators)
}
const validIndicators = collectValidIndicators();
emit('update:modelValue', validIndicators);
emit('change', validIndicators);
};
// 从传入的数据初始化指标
const initializeFromData = (data: any[]) => {
if (!data || data.length === 0) return
if (!data || data.length === 0) return;
// 重置所有指标
indicators.value = {
quantity: createIndicator('quantity', '数量指标'),
@@ -644,57 +484,61 @@ export default defineComponent({
social: createIndicator('social', '社会效益指标'),
ecological: createIndicator('ecological', '生态效益指标'),
service_satisfaction: createIndicator('service_satisfaction', '服务对象满意度指标')
}
};
// 将数据填入对应的指标类型
data.forEach(item => {
data.forEach((item) => {
if (indicators.value[item.indicatorType]) {
indicators.value[item.indicatorType] = { ...item }
indicators.value[item.indicatorType] = { ...item };
}
})
}
});
};
// 监听外部数据变化
watch(() => props.modelValue, (newValue) => {
initializeFromData(newValue)
}, { deep: true, immediate: true })
watch(
() => props.modelValue,
(newValue) => {
initializeFromData(newValue);
},
{ deep: true, immediate: true }
);
// 校验方法
const validate = () => {
const validIndicators = collectValidIndicators()
const errors = []
const validIndicators = collectValidIndicators();
const errors = [];
if (validIndicators.length === 0) {
errors.push('请至少填写一个绩效指标')
errors.push('请至少填写一个绩效指标');
}
validIndicators.forEach((indicator, index) => {
if (!indicator.goalName?.trim()) {
errors.push(`第${index + 1}个指标未填写绩效目标名称`)
errors.push(`第${index + 1}个指标未填写绩效目标名称`);
}
if (!indicator.targetValue?.trim()) {
errors.push(`第${index + 1}个指标未填写目标值`)
errors.push(`第${index + 1}个指标未填写目标值`);
}
if (!indicator.weightPercentage || indicator.weightPercentage <= 0) {
errors.push(`第${index + 1}个指标未设置分值占比`)
errors.push(`第${index + 1}个指标未设置分值占比`);
}
})
});
if (Math.abs(totalWeight.value - 100) > 0.1) {
errors.push(`分值占比总和必须等于100%,当前为${totalWeight.value}%`)
errors.push(`分值占比总和必须等于100%,当前为${totalWeight.value}%`);
}
if (errors.length > 0) {
return { valid: false, message: errors.join(';') }
return { valid: false, message: errors.join(';') };
}
return { valid: true }
}
return { valid: true };
};
// 获取所有有效指标数据
const getAllIndicators = () => {
return collectValidIndicators()
}
return collectValidIndicators();
};
return {
indicators,
@@ -702,9 +546,9 @@ export default defineComponent({
emitChange,
validate,
getAllIndicators
}
};
}
})
});
</script>
<style scoped lang="scss">
@@ -715,34 +559,33 @@ export default defineComponent({
gap: 8px;
font-weight: bold;
font-size: 16px;
.el-icon {
font-size: 18px;
}
}
.indicator-item {
margin-bottom: 20px;
padding: 15px;
background-color: #fafafa;
border-radius: 6px;
border-left: 4px solid #409eff;
&:last-child {
margin-bottom: 0;
}
}
.indicator-title {
margin: 0 0 15px 0;
font-size: 14px;
font-weight: bold;
color: #606266;
}
.el-form-item {
margin-bottom: 10px;
}
}
</style>
+102 -108
View File
@@ -16,12 +16,7 @@
<!-- 审批流程 -->
<el-divider content-position="left">审批流程</el-divider>
<el-timeline>
<el-timeline-item
v-for="(item, index) in approvalTimeline"
:key="index"
:type="item.type"
:timestamp="item.timestamp"
>
<el-timeline-item v-for="(item, index) in approvalTimeline" :key="index" :type="item.type" :timestamp="item.timestamp">
<div class="timeline-content">
<h4>{{ item.title }}</h4>
<p>{{ item.description }}</p>
@@ -33,36 +28,25 @@
<!-- 操作日志 -->
<el-divider content-position="left">操作日志</el-divider>
<el-table :data="operationLogs" style="width: 100%">
<el-table-column prop="operation" label="操作" width="120"/>
<el-table-column prop="operator" label="操作人" width="100"/>
<el-table-column prop="operation" label="操作" width="120" />
<el-table-column prop="operator" label="操作人" width="100" />
<el-table-column prop="operateTime" label="操作时间" width="180">
<template #default="scope">
{{ parseTime(scope.row.operateTime, '{y}-{m}-{d} {h}:{i}:{s}') }}
</template>
</el-table-column>
<el-table-column prop="remark" label="备注" show-overflow-tooltip/>
<el-table-column prop="remark" label="备注" show-overflow-tooltip />
</el-table>
<!-- 操作按钮 -->
<div class="operation-buttons">
<el-button
v-if="detail.approvalStatus === 'draft'"
type="primary"
@click="handleEdit"
v-hasPermi="['cw:application:edit']"
>编辑</el-button>
<el-button
v-if="detail.approvalStatus === 'draft'"
type="success"
@click="handleSubmit"
v-hasPermi="['cw:application:submit']"
>提交</el-button>
<el-button
v-if="detail.approvalStatus === 'pending'"
type="warning"
@click="handleWithdraw"
v-hasPermi="['cw:application:withdraw']"
>撤回</el-button>
<el-button v-if="detail.approvalStatus === 'draft'" type="primary" @click="handleEdit" v-hasPermi="['cw:application:edit']">编辑</el-button>
<el-button v-if="detail.approvalStatus === 'draft'" type="success" @click="handleSubmit" v-hasPermi="['cw:application:submit']"
>提交</el-button
>
<el-button v-if="detail.approvalStatus === 'pending'" type="warning" @click="handleWithdraw" v-hasPermi="['cw:application:withdraw']"
>撤回</el-button
>
<el-button @click="goBack">返回</el-button>
</div>
</el-card>
@@ -72,9 +56,9 @@
<script setup name="CwApplicationDetail">
import { ref, getCurrentInstance, onMounted } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { getProjectApplication, submitApplication, withdrawApplication } from "@/api/cw/projectApplication";
import ApplicationSummary from "./summary.vue";
import { parseTime } from "@/utils/ruoyi";
import { getProjectApplication, submitApplication, withdrawApplication } from '@/api/cw/projectApplication';
import ApplicationSummary from './summary.vue';
import { parseTime } from '@/utils/ruoyi';
console.log('=== detail.vue 组件开始加载 ===');
@@ -120,32 +104,40 @@ function getStatusText(status) {
/** 编辑按钮操作 */
function handleEdit() {
router.push("/cw/application/form?id=" + detail.value.id);
router.push('/cw/application/form?id=' + detail.value.id);
}
/** 提交按钮操作 */
function handleSubmit() {
proxy.$modal.confirm('是否确认提交申报编号为"' + detail.value.applicationNo + '"的数据项?').then(function() {
return submitApplication(detail.value.id);
}).then(() => {
getDetail();
proxy.$modal.msgSuccess("提交成功");
}).catch(() => {});
proxy.$modal
.confirm('是否确认提交申报编号为"' + detail.value.applicationNo + '"的数据项?')
.then(function () {
return submitApplication(detail.value.id);
})
.then(() => {
getDetail();
proxy.$modal.msgSuccess('提交成功');
})
.catch(() => {});
}
/** 撤回按钮操作 */
function handleWithdraw() {
proxy.$modal.confirm('是否确认撤回申报编号为"' + detail.value.applicationNo + '"的数据项?').then(function() {
return withdrawApplication(detail.value.id);
}).then(() => {
getDetail();
proxy.$modal.msgSuccess("撤回成功");
}).catch(() => {});
proxy.$modal
.confirm('是否确认撤回申报编号为"' + detail.value.applicationNo + '"的数据项?')
.then(function () {
return withdrawApplication(detail.value.id);
})
.then(() => {
getDetail();
proxy.$modal.msgSuccess('撤回成功');
})
.catch(() => {});
}
/** 返回按钮操作 */
function goBack() {
router.push("/cw/application");
router.push('/cw/application');
}
/** 获取详情数据 */
@@ -154,78 +146,80 @@ function getDetail() {
console.log('=== 开始获取详情数据 ===');
console.log('URL参数中的ID:', id);
console.log('完整query参数:', route.query);
if (id) {
console.log('开始调用API:getProjectApplication, ID:', id);
getProjectApplication(id).then(response => {
console.log('=== API调用成功 ===');
console.log('API响应:', response);
console.log('响应代码:', response.code);
console.log('响应数据:', response.data);
detail.value = response.data;
getProjectApplication(id)
.then((response) => {
console.log('=== API调用成功 ===');
console.log('API响应:', response);
console.log('响应代码:', response.code);
console.log('响应数据:', response.data);
// 调试日志:查看返回的数据
console.log('=== 项目申报详情数据 ===');
console.log('完整数据:', response.data);
console.log('绩效指标数据:', response.data.performanceIndicators);
console.log('绩效指标数量:', response.data.performanceIndicators ? response.data.performanceIndicators.length : 0);
detail.value = response.data;
// 模拟审批流程数据
approvalTimeline.value = [
{
type: 'success',
timestamp: detail.value.createTime,
title: '申报提交',
description: '申报人提交预算申报',
user: detail.value.applicantName
// 调试日志:查看返回的数据
console.log('=== 项目申报详情数据 ===');
console.log('完整数据:', response.data);
console.log('绩效指标数据:', response.data.performanceIndicators);
console.log('绩效指标数量:', response.data.performanceIndicators ? response.data.performanceIndicators.length : 0);
// 模拟审批流程数据
approvalTimeline.value = [
{
type: 'success',
timestamp: detail.value.createTime,
title: '申报提交',
description: '申报人提交预算申报',
user: detail.value.applicantName
}
];
if (detail.value.submitTime) {
approvalTimeline.value.push({
type: 'primary',
timestamp: detail.value.submitTime,
title: '提交审批',
description: '申报已提交,等待部门审批',
user: detail.value.applicantName
});
}
];
if (detail.value.submitTime) {
approvalTimeline.value.push({
type: 'primary',
timestamp: detail.value.submitTime,
title: '提交审批',
description: '申报已提交,等待部门审批',
user: detail.value.applicantName
});
}
if (detail.value.approveTime && detail.value.approvalStatus === 'approved') {
approvalTimeline.value.push({
type: 'success',
timestamp: detail.value.approveTime,
title: '审批通过',
description: '所有审批节点已通过',
user: '系统管理员'
});
}
// 模拟操作日志数据
operationLogs.value = [
{
operation: '创建申报',
operator: detail.value.applicantName,
operateTime: detail.value.createTime,
remark: '创建项目预算申报'
if (detail.value.approveTime && detail.value.approvalStatus === 'approved') {
approvalTimeline.value.push({
type: 'success',
timestamp: detail.value.approveTime,
title: '审批通过',
description: '所有审批节点已通过',
user: '系统管理员'
});
}
];
if (detail.value.submitTime) {
operationLogs.value.push({
operation: '提交审批',
operator: detail.value.applicantName,
operateTime: detail.value.submitTime,
remark: '提交预算申报进入审批流程'
});
}
}).catch(error => {
console.error('=== API调用失败 ===');
console.error('错误信息:', error);
console.error('错误响应:', error.response);
proxy.$modal.msgError('获取申报详情失败:' + (error.message || '未知错误'));
});
// 模拟操作日志数据
operationLogs.value = [
{
operation: '创建申报',
operator: detail.value.applicantName,
operateTime: detail.value.createTime,
remark: '创建项目预算申报'
}
];
if (detail.value.submitTime) {
operationLogs.value.push({
operation: '提交审批',
operator: detail.value.applicantName,
operateTime: detail.value.submitTime,
remark: '提交预算申报进入审批流程'
});
}
})
.catch((error) => {
console.error('=== API调用失败 ===');
console.error('错误信息:', error);
console.error('错误响应:', error.response);
proxy.$modal.msgError('获取申报详情失败:' + (error.message || '未知错误'));
});
} else {
console.error('=== URL参数错误 ===');
console.error('缺少ID参数,无法获取详情数据');
File diff suppressed because it is too large. Load diff
+216 -256
View File
@@ -64,71 +64,50 @@
</div>
</template>
<el-form :model="queryParams" ref="queryRef" label-width="100px">
<el-row :gutter="20">
<el-col :span="6">
<el-form-item label="申报编号" prop="applicationNo">
<el-input
v-model="queryParams.applicationNo"
placeholder="请输入申报编号"
clearable
@keyup.enter="handleQuery"
/>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="项目名称" prop="projectName">
<el-input
v-model="queryParams.projectName"
placeholder="请输入项目名称"
clearable
@keyup.enter="handleQuery"
/>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="项目负责人" prop="principal">
<el-input
v-model="queryParams.principal"
placeholder="请输入项目负责人"
clearable
@keyup.enter="handleQuery"
/>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="审批状态" prop="approvalStatus">
<el-row :gutter="20">
<el-col :span="6">
<el-form-item label="申报编号" prop="applicationNo">
<el-input v-model="queryParams.applicationNo" placeholder="请输入申报编号" clearable @keyup.enter="handleQuery" />
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="项目名称" prop="projectName">
<el-input v-model="queryParams.projectName" placeholder="请输入项目名称" clearable @keyup.enter="handleQuery" />
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="项目负责人" prop="principal">
<el-input v-model="queryParams.principal" placeholder="请输入项目负责人" clearable @keyup.enter="handleQuery" />
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="审批状态" prop="approvalStatus">
<el-select v-model="queryParams.approvalStatus" placeholder="请选择审批状态" clearable style="width: 100%">
<el-option label="草稿" value="draft"/>
<el-option label="已提交" value="submitted"/>
<el-option label="审批中" value="approving"/>
<el-option label="已通过" value="approved"/>
<el-option label="已拒绝" value="rejected"/>
<el-option label="已退回" value="returned"/>
<el-option label="已取消" value="cancelled"/>
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="6">
<el-form-item label="预算年度" prop="year">
<el-date-picker
v-model="queryParams.year"
type="year"
placeholder="选择年份"
value-format="YYYY"
style="width: 100%"
/>
</el-form-item>
</el-col>
<el-col :span="18">
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-col>
</el-row>
</el-form>
<el-option label="草稿" value="draft" />
<el-option label="已提交" value="submitted" />
<el-option label="审批中" value="approving" />
<el-option label="已通过" value="approved" />
<el-option label="已拒绝" value="rejected" />
<el-option label="已退回" value="returned" />
<el-option label="已取消" value="cancelled" />
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="6">
<el-form-item label="预算年度" prop="year">
<el-date-picker v-model="queryParams.year" type="year" placeholder="选择年份" value-format="YYYY" style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="18">
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-col>
</el-row>
</el-form>
</el-card>
<!-- 操作按钮区域 -->
@@ -137,40 +116,14 @@
<div class="card-header">
<span class="toolbar-title">项目申报管理</span>
<div class="toolbar-actions">
<el-button
type="primary"
icon="Plus"
@click="handleAdd"
v-hasPermi="['cw:application:add']"
>新增申报</el-button>
<el-button
type="success"
icon="Edit"
:disabled="single"
@click="handleUpdate"
v-hasPermi="['cw:application:edit']"
>修改</el-button>
<el-button
type="danger"
icon="Delete"
:disabled="multiple"
@click="handleDelete"
v-hasPermi="['cw:application:remove']"
>删除</el-button>
<el-button
type="info"
icon="Upload"
@click="handleSubmit"
:disabled="multiple"
v-hasPermi="['cw:application:submit']"
>提交审批</el-button>
<el-button
type="warning"
icon="Download"
@click="handleExport"
v-hasPermi="['cw:application:export']"
>导出</el-button>
<right-toolbar :showSearch="showSearch" @update:showSearch="showSearch = $event" @queryTable="getList"></right-toolbar>
<el-button type="primary" icon="Plus" @click="handleAdd" v-hasPermi="['cw:application:add']">新增申报</el-button>
<el-button type="success" icon="Edit" :disabled="single" @click="handleUpdate" v-hasPermi="['cw:application:edit']">修改</el-button>
<el-button type="danger" icon="Delete" :disabled="multiple" @click="handleDelete" v-hasPermi="['cw:application:remove']">删除</el-button>
<el-button type="info" icon="Upload" @click="handleSubmit" :disabled="multiple" v-hasPermi="['cw:application:submit']"
>提交审批</el-button
>
<el-button type="warning" icon="Download" @click="handleExport" v-hasPermi="['cw:application:export']">导出</el-button>
<right-toolbar :showSearch="showSearch" @update:showSearch="showSearch = $event" @queryTable="getList"></right-toolbar>
</div>
</div>
</template>
@@ -179,13 +132,8 @@
<!-- 数据表格区域 -->
<el-card class="table-card">
<div class="table-container">
<el-table
v-loading="loading"
:data="applicationList"
@selection-change="handleSelectionChange"
class="scroll-table"
>
<el-table-column type="selection" width="55" align="center" />
<el-table v-loading="loading" :data="applicationList" @selection-change="handleSelectionChange" class="scroll-table">
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="申报编号" align="center" prop="applicationNo" min-width="140" />
<el-table-column label="项目名称" align="center" prop="projectName" min-width="180" show-overflow-tooltip />
<el-table-column label="项目负责人" align="center" prop="principal" min-width="100" />
@@ -197,43 +145,26 @@
</template>
</el-table-column>
<el-table-column label="审批状态" align="center" prop="approvalStatus" width="100">
<template #default="scope">
<el-tag
:type="getStatusTagType(scope.row.approvalStatus)"
effect="light"
>
{{ getStatusText(scope.row.approvalStatus) }}
</el-tag>
</template>
</el-table-column>
<template #default="scope">
<el-tag :type="getStatusTagType(scope.row.approvalStatus)" effect="light">
{{ getStatusText(scope.row.approvalStatus) }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="创建时间" align="center" prop="createTime" min-width="160">
<template #default="scope">
<template #default="scope">
<span>{{ parseTime(scope.row.createTime, '{y}-{m}-{d} {h}:{i}') }}</span>
</template>
</el-table-column>
</template>
</el-table-column>
<el-table-column label="操作" align="center" width="280" class-name="small-padding fixed-width">
<template #default="scope">
<template #default="scope">
<div class="action-buttons">
<el-tooltip content="查看详情" placement="top">
<el-button
type="primary"
icon="View"
circle
size="small"
@click="handleView(scope.row)"
v-hasPermi="['cw:application:query']"
/>
</el-tooltip>
<el-tooltip content="查看详情" placement="top">
<el-button type="primary" icon="View" circle size="small" @click="handleView(scope.row)" v-hasPermi="['cw:application:query']" />
</el-tooltip>
<el-tooltip content="编辑" placement="top" v-if="['draft', 'returned'].includes(scope.row.approvalStatus)">
<el-button
type="success"
icon="Edit"
circle
size="small"
@click="handleUpdate(scope.row)"
v-hasPermi="['cw:application:edit']"
/>
</el-tooltip>
<el-button type="success" icon="Edit" circle size="small" @click="handleUpdate(scope.row)" v-hasPermi="['cw:application:edit']" />
</el-tooltip>
<el-tooltip content="提交审批" placement="top" v-if="['draft', 'returned'].includes(scope.row.approvalStatus)">
<el-button
type="warning"
@@ -243,7 +174,7 @@
@click="handleSubmit(scope.row)"
v-hasPermi="['cw:application:submit']"
/>
</el-tooltip>
</el-tooltip>
<el-tooltip content="撤回" placement="top" v-if="scope.row.approvalStatus === 'submitted'">
<el-button
type="info"
@@ -253,7 +184,7 @@
@click="handleWithdraw(scope.row)"
v-hasPermi="['cw:application:withdraw']"
/>
</el-tooltip>
</el-tooltip>
<el-tooltip content="删除" placement="top" v-if="['draft', 'cancelled', 'returned'].includes(scope.row.approvalStatus)">
<el-button
type="danger"
@@ -263,17 +194,17 @@
@click="handleDelete(scope.row)"
v-hasPermi="['cw:application:remove']"
/>
</el-tooltip>
</el-tooltip>
</div>
</template>
</el-table-column>
</el-table>
</template>
</el-table-column>
</el-table>
</div>
<!-- 分页 -->
<div class="pagination-container">
<pagination
v-show="total>0"
v-show="total > 0"
:total="total"
:page="queryParams.pageNum"
:limit="queryParams.pageSize"
@@ -307,13 +238,21 @@
</template>
<script>
import { listProjectApplication, getProjectApplication, delProjectApplication, addProjectApplication, updateProjectApplication, submitApplication, getApplicationStatistics } from "@/api/cw/projectApplication"
import ApplicationForm from "./form.vue"
import ApplicationSummary from "./summary.vue"
import Pagination from "@/components/Pagination/index.vue"
import {
listProjectApplication,
getProjectApplication,
delProjectApplication,
addProjectApplication,
updateProjectApplication,
submitApplication,
getApplicationStatistics
} from '@/api/cw/projectApplication';
import ApplicationForm from './form.vue';
import ApplicationSummary from './summary.vue';
import Pagination from '@/components/Pagination/index.vue';
export default {
name: "ProjectApplication",
name: 'ProjectApplication',
components: {
ApplicationForm,
ApplicationSummary,
@@ -336,7 +275,7 @@ export default {
// 项目申报表格数据
applicationList: [],
// 弹出层标题
title: "",
title: '',
// 是否显示弹出层
open: false,
// 是否显示查看对话框
@@ -362,52 +301,56 @@ export default {
approvedCount: 0,
pendingCount: 0,
draftCount: 0
}
};
},
created() {
this.getList()
this.getStatistics()
this.getList();
this.getStatistics();
},
methods: {
/** 查询项目申报列表 */
getList() {
this.loading = true
listProjectApplication(this.queryParams).then(response => {
this.applicationList = response.rows
this.total = response.total
this.loading = false
}).catch(error => {
console.error('获取项目申报列表失败:', error)
this.loading = false
this.applicationList = []
this.total = 0
// 可以添加用户友好的错误提示
this.$message.error('获取数据失败,请检查网络连接或联系管理员')
})
this.loading = true;
listProjectApplication(this.queryParams)
.then((response) => {
this.applicationList = response.rows;
this.total = response.total;
this.loading = false;
})
.catch((error) => {
console.error('获取项目申报列表失败:', error);
this.loading = false;
this.applicationList = [];
this.total = 0;
// 可以添加用户友好的错误提示
this.$message.error('获取数据失败,请检查网络连接或联系管理员');
});
},
/** 获取统计数据 */
getStatistics() {
// 调用专门的统计API接口
const year = this.queryParams.year ? parseInt(this.queryParams.year) : null
getApplicationStatistics(year).then(response => {
const data = response.data
this.totalCount = data.totalCount || 0
this.approvedCount = data.approvedCount || 0
this.pendingCount = data.approvingCount || 0 // 注意:后端返回的是approvingCount
this.draftCount = data.draftCount || 0
}).catch(error => {
console.error('获取统计数据失败:', error)
// 如果API失败,使用本地计算作为后备方案
this.totalCount = this.applicationList.length
this.approvedCount = this.applicationList.filter(item => item.approvalStatus === 'approved').length
this.pendingCount = this.applicationList.filter(item => item.approvalStatus === 'approving').length
this.draftCount = this.applicationList.filter(item => item.approvalStatus === 'draft').length
})
const year = this.queryParams.year ? parseInt(this.queryParams.year) : null;
getApplicationStatistics(year)
.then((response) => {
const data = response.data;
this.totalCount = data.totalCount || 0;
this.approvedCount = data.approvedCount || 0;
this.pendingCount = data.approvingCount || 0; // 注意:后端返回的是approvingCount
this.draftCount = data.draftCount || 0;
})
.catch((error) => {
console.error('获取统计数据失败:', error);
// 如果API失败,使用本地计算作为后备方案
this.totalCount = this.applicationList.length;
this.approvedCount = this.applicationList.filter((item) => item.approvalStatus === 'approved').length;
this.pendingCount = this.applicationList.filter((item) => item.approvalStatus === 'approving').length;
this.draftCount = this.applicationList.filter((item) => item.approvalStatus === 'draft').length;
});
},
/** 取消按钮 */
cancel() {
this.open = false
this.reset()
this.open = false;
this.reset();
},
/** 表单重置 */
reset() {
@@ -448,119 +391,136 @@ export default {
futureYearOther: 0,
performanceIndicators: [],
approvalStatus: 'draft'
}
};
},
/** 搜索按钮操作 */
handleQuery() {
this.queryParams.pageNum = 1
this.getList()
this.getStatistics() // 搜索时也更新统计数据
this.queryParams.pageNum = 1;
this.getList();
this.getStatistics(); // 搜索时也更新统计数据
},
/** 重置按钮操作 */
resetQuery() {
this.resetForm("queryRef")
this.handleQuery()
this.resetForm('queryRef');
this.handleQuery();
},
// 多选框选中数据
handleSelectionChange(selection) {
this.ids = selection.map(item => item.id)
this.single = selection.length !== 1
this.multiple = !selection.length
this.ids = selection.map((item) => item.id);
this.single = selection.length !== 1;
this.multiple = !selection.length;
},
/** 新增按钮操作 */
handleAdd() {
this.reset()
this.open = true
this.title = "添加项目申报"
this.reset();
this.open = true;
this.title = '添加项目申报';
},
/** 修改按钮操作 */
handleUpdate(row) {
this.reset()
const id = row.id || this.ids
getProjectApplication(id).then(response => {
this.form = response.data
this.open = true
this.title = "修改项目申报"
})
this.reset();
const id = row.id || this.ids;
getProjectApplication(id).then((response) => {
this.form = response.data;
this.open = true;
this.title = '修改项目申报';
});
},
/** 查看详情 */
handleView(row) {
this.currentId = row.id
this.viewOpen = true
this.currentId = row.id;
this.viewOpen = true;
},
/** 提交按钮 */
submitForm() {
this.$refs["applicationFormRef"].validate((valid, formData) => {
this.$refs['applicationFormRef'].validate((valid, formData) => {
if (valid && formData) {
// 使用子组件回传的完整数据(包含绩效指标)
this.form = formData
console.log('提交表单数据:', this.form)
this.form = formData;
console.log('提交表单数据:', this.form);
if (this.form.id != null) {
updateProjectApplication(this.form).then(response => {
this.$modal.msgSuccess("修改成功")
this.open = false
this.getList()
this.getStatistics() // 修改后更新统计
})
updateProjectApplication(this.form).then((response) => {
this.$modal.msgSuccess('修改成功');
this.open = false;
this.getList();
this.getStatistics(); // 修改后更新统计
});
} else {
addProjectApplication(this.form).then(response => {
this.$modal.msgSuccess("新增成功")
this.open = false
this.getList()
this.getStatistics() // 新增后更新统计
})
addProjectApplication(this.form).then((response) => {
this.$modal.msgSuccess('新增成功');
this.open = false;
this.getList();
this.getStatistics(); // 新增后更新统计
});
}
} else {
this.$modal.msgError("请检查表单数据是否完整")
this.$modal.msgError('请检查表单数据是否完整');
}
})
});
},
/** 删除按钮操作 */
handleDelete(row) {
const ids = row.id || this.ids
this.$modal.confirm('是否确认删除项目申报编号为"' + ids + '"的数据项?').then(function() {
return delProjectApplication(ids)
}).then(() => {
this.getList()
this.getStatistics() // 删除后更新统计
this.$modal.msgSuccess("删除成功")
}).catch(() => {})
const ids = row.id || this.ids;
this.$modal
.confirm('是否确认删除项目申报编号为"' + ids + '"的数据项?')
.then(function () {
return delProjectApplication(ids);
})
.then(() => {
this.getList();
this.getStatistics(); // 删除后更新统计
this.$modal.msgSuccess('删除成功');
})
.catch(() => {});
},
/** 提交审批 */
handleSubmit(row) {
const applications = row?.applicationNo ? [row.applicationNo] : this.applicationList.filter(item => this.ids.includes(item.id)).map(item => item.applicationNo)
const applications = row?.applicationNo
? [row.applicationNo]
: this.applicationList.filter((item) => this.ids.includes(item.id)).map((item) => item.applicationNo);
this.$modal.confirm('是否确认提交申报编号为"' + applications.join('", "') + '"的数据项?').then(() => {
return submitApplication(row?.id || this.ids)
}).then(() => {
this.getList()
this.getStatistics() // 提交后更新统计
this.$modal.msgSuccess("提交成功")
}).catch(() => {})
this.$modal
.confirm('是否确认提交申报编号为"' + applications.join('", "') + '"的数据项?')
.then(() => {
return submitApplication(row?.id || this.ids);
})
.then(() => {
this.getList();
this.getStatistics(); // 提交后更新统计
this.$modal.msgSuccess('提交成功');
})
.catch(() => {});
},
/** 撤回申请 */
handleWithdraw(row) {
this.$modal.confirm('是否确认撤回该申报?').then(() => {
// 调用撤回接口
this.$modal.msgSuccess("撤回成功")
this.getList()
this.getStatistics() // 撤回后更新统计
}).catch(() => {})
this.$modal
.confirm('是否确认撤回该申报?')
.then(() => {
// 调用撤回接口
this.$modal.msgSuccess('撤回成功');
this.getList();
this.getStatistics(); // 撤回后更新统计
})
.catch(() => {});
},
/** 导出按钮操作 */
handleExport() {
this.download('cw/application/export', {
...this.queryParams
}, `application_${new Date().getTime()}.xlsx`)
this.download(
'cw/application/export',
{
...this.queryParams
},
`application_${new Date().getTime()}.xlsx`
);
},
/** 格式化金额 */
formatAmount(amount) {
if (!amount) return '0.00'
if (!amount) return '0.00';
return parseFloat(amount).toLocaleString('zh-CN', {
minimumFractionDigits: 2,
maximumFractionDigits: 2
})
});
},
/** 获取状态标签类型 */
getStatusTagType(status) {
@@ -572,8 +532,8 @@ export default {
rejected: 'danger',
returned: 'warning',
cancelled: 'info'
}
return statusMap[status] || 'info'
};
return statusMap[status] || 'info';
},
/** 获取状态文本 */
getStatusText(status) {
@@ -585,11 +545,11 @@ export default {
rejected: '已拒绝',
returned: '已退回',
cancelled: '已取消'
}
return statusMap[status] || '未知'
};
return statusMap[status] || '未知';
}
}
}
};
</script>
<style scoped lang="scss">
@@ -614,8 +574,8 @@ export default {
}
.stat-content {
display: flex;
align-items: center;
display: flex;
align-items: center;
padding: 5px 16px;
height: 100%;
box-sizing: border-box;
@@ -637,7 +597,7 @@ export default {
}
.stat-label {
font-size: 14px;
font-size: 14px;
opacity: 0.8;
line-height: 1;
}
+13 -14
View File
@@ -38,9 +38,7 @@
</template>
<el-row :gutter="20">
<el-col :span="8">
<el-tag :type="currentApplication.targetSme ? 'success' : 'info'">
小微企业发展: {{ currentApplication.targetSme ? '是' : '否' }}
</el-tag>
<el-tag :type="currentApplication.targetSme ? 'success' : 'info'"> 小微企业发展: {{ currentApplication.targetSme ? '是' : '否' }} </el-tag>
</el-col>
<el-col :span="8">
<el-tag :type="currentApplication.targetMicroSme ? 'success' : 'info'">
@@ -122,7 +120,7 @@
<div class="budget-section">
<h4>分年度预算明细</h4>
<el-table :data="budgetBreakdown" border>
<el-table-column prop="year" label="年度" width="120" align="center"/>
<el-table-column prop="year" label="年度" width="120" align="center" />
<el-table-column prop="total" label="总投资" align="right">
<template #default="scope">
{{ formatAmount(scope.row.total) }}
@@ -280,11 +278,15 @@ const fetchApplicationData = async (id) => {
};
// 监听applicationId变化
watch(() => props.applicationId, (newId) => {
if (newId && (!props.application || Object.keys(props.application).length === 0)) {
fetchApplicationData(newId);
}
}, { immediate: true });
watch(
() => props.applicationId,
(newId) => {
if (newId && (!props.application || Object.keys(props.application).length === 0)) {
fetchApplicationData(newId);
}
},
{ immediate: true }
);
// 组件挂载时检查是否需要获取数据
onMounted(() => {
@@ -301,10 +303,7 @@ onMounted(() => {
// 判断是否有项目内容
const hasProjectContent = computed(() => {
const app = currentApplication.value;
return app.applicationReason ||
app.constructionContent ||
app.totalPerformanceGoal ||
app.currentYearGoal;
return app.applicationReason || app.constructionContent || app.totalPerformanceGoal || app.currentYearGoal;
});
// 判断是否有绩效指标
@@ -352,7 +351,7 @@ const validPerformanceIndicators = computed(() => {
return [];
}
const validIndicators = app.performanceIndicators.filter(indicator => {
const validIndicators = app.performanceIndicators.filter((indicator) => {
const weight = parseFloat(indicator.weightPercentage) || 0;
console.log(`指标 ${indicator.goalName}: 权重=${indicator.weightPercentage}, 解析后权重=${weight}, 是否有效=${weight > 0}`);
return weight > 0;
+48 -95
View File
@@ -2,31 +2,13 @@
<div class="app-container">
<el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch">
<el-form-item label="申报编号" prop="applicationNo">
<el-input
v-model="queryParams.applicationNo"
placeholder="请输入申报编号"
clearable
style="width: 200px"
@keyup.enter="handleQuery"
/>
<el-input v-model="queryParams.applicationNo" placeholder="请输入申报编号" clearable style="width: 200px" @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="项目名称" prop="projectName">
<el-input
v-model="queryParams.projectName"
placeholder="请输入项目名称"
clearable
style="width: 200px"
@keyup.enter="handleQuery"
/>
<el-input v-model="queryParams.projectName" placeholder="请输入项目名称" clearable style="width: 200px" @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="申报人" prop="applicantName">
<el-input
v-model="queryParams.applicantName"
placeholder="请输入申报人"
clearable
style="width: 200px"
@keyup.enter="handleQuery"
/>
<el-input v-model="queryParams.applicantName" placeholder="请输入申报人" clearable style="width: 200px" @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
@@ -37,28 +19,13 @@
<!-- 快速操作按钮 -->
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button
type="primary"
plain
icon="Check"
@click="showPendingApprovals"
>待我审批</el-button>
<el-button type="primary" plain icon="Check" @click="showPendingApprovals">待我审批</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="success"
plain
icon="CircleCheck"
@click="showMyApprovals"
>我已审批</el-button>
<el-button type="success" plain icon="CircleCheck" @click="showMyApprovals">我已审批</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="info"
plain
icon="Download"
@click="handleExport"
>导出</el-button>
<el-button type="info" plain icon="Download" @click="handleExport">导出</el-button>
</el-col>
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
@@ -73,7 +40,7 @@
<el-table-column label="审批节点" align="center" prop="nodeName" />
<el-table-column label="审批状态" align="center" prop="nodeStatus">
<template #default="scope">
<dict-tag :options="cw_approval_status" :value="scope.row.nodeStatus"/>
<dict-tag :options="cw_approval_status" :value="scope.row.nodeStatus" />
</template>
</el-table-column>
<el-table-column label="审批人" align="center" prop="approverName" />
@@ -95,13 +62,7 @@
</el-table-column>
</el-table>
<pagination
v-show="total > 0"
:total="total"
v-model:page="queryParams.pageNum"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
<!-- 审批对话框 -->
<el-dialog title="审批处理" v-model="approvalOpen" width="500px" append-to-body>
@@ -113,14 +74,7 @@
</el-radio-group>
</el-form-item>
<el-form-item label="审批意见" prop="approvalComment">
<el-input
v-model="approvalForm.approvalComment"
type="textarea"
placeholder="请输入审批意见"
:rows="3"
maxlength="500"
show-word-limit
/>
<el-input v-model="approvalForm.approvalComment" type="textarea" placeholder="请输入审批意见" :rows="3" maxlength="500" show-word-limit />
</el-form-item>
</el-form>
<template #footer>
@@ -144,18 +98,12 @@
</template>
<script>
import {
listApprovalRecord,
exportApprovalRecord,
approveApplication,
getMyApprovals,
getDepartmentApprovals
} from '@/api/cw/approvalRecord';
import { listApprovalRecord, exportApprovalRecord, approveApplication, getMyApprovals, getDepartmentApprovals } from '@/api/cw/approvalRecord';
import { parseTime } from '@/utils/ruoyi';
import ApplicationSummary from '../application/summary.vue';
export default {
name: "ApprovalRecord",
name: 'ApprovalRecord',
components: {
ApplicationSummary
},
@@ -200,9 +148,7 @@ export default {
},
// 审批表单校验
approvalRules: {
approvalResult: [
{ required: true, message: "审批结果不能为空", trigger: "change" }
]
approvalResult: [{ required: true, message: '审批结果不能为空', trigger: 'change' }]
}
};
},
@@ -214,7 +160,7 @@ export default {
/** 查询审批记录列表 */
getList() {
this.loading = true;
listApprovalRecord(this.queryParams).then(response => {
listApprovalRecord(this.queryParams).then((response) => {
this.approvalRecordList = response.rows;
this.total = response.total;
this.loading = false;
@@ -223,25 +169,29 @@ export default {
/** 查询待我审批的记录 */
showPendingApprovals() {
this.loading = true;
getDepartmentApprovals({}).then(response => {
this.approvalRecordList = response.data || [];
this.total = this.approvalRecordList.length;
this.loading = false;
}).catch(() => {
this.loading = false;
});
getDepartmentApprovals({})
.then((response) => {
this.approvalRecordList = response.data || [];
this.total = this.approvalRecordList.length;
this.loading = false;
})
.catch(() => {
this.loading = false;
});
},
/** 查询我已审批的记录 */
showMyApprovals() {
this.loading = true;
getMyApprovals({}).then(response => {
this.approvalRecordList = response.data || [];
this.total = this.approvalRecordList.length;
this.loading = false;
}).catch(error => {
this.loading = false;
this.$modal.msgError('获取已审批记录失败:' + (error.message || '未知错误'));
});
getMyApprovals({})
.then((response) => {
this.approvalRecordList = response.data || [];
this.total = this.approvalRecordList.length;
this.loading = false;
})
.catch((error) => {
this.loading = false;
this.$modal.msgError('获取已审批记录失败:' + (error.message || '未知错误'));
});
},
/** 搜索按钮操作 */
handleQuery() {
@@ -250,12 +200,12 @@ export default {
},
/** 重置按钮操作 */
resetQuery() {
this.resetForm("queryRef");
this.resetForm('queryRef');
this.handleQuery();
},
// 多选框选中数据
handleSelectionChange(selection) {
this.ids = selection.map(item => item.id);
this.ids = selection.map((item) => item.id);
this.single = selection.length !== 1;
this.multiple = !selection.length;
},
@@ -270,10 +220,10 @@ export default {
},
/** 提交审批 */
submitApproval() {
this.$refs.approvalRef.validate(valid => {
this.$refs.approvalRef.validate((valid) => {
if (valid) {
approveApplication(this.approvalForm).then(response => {
this.$modal.msgSuccess("审批成功");
approveApplication(this.approvalForm).then((response) => {
this.$modal.msgSuccess('审批成功');
this.approvalOpen = false;
this.getList();
});
@@ -288,7 +238,7 @@ export default {
approvalComment: ''
};
this.approvalOpen = false;
this.resetForm("approvalRef");
this.resetForm('approvalRef');
},
/** 详情操作 - 完全复制预算申报页面的实现 */
handleDetail(row) {
@@ -297,9 +247,13 @@ export default {
},
/** 导出按钮操作 */
handleExport() {
this.download('cw/approval/export', {
...this.queryParams
}, `approval_record_${new Date().getTime()}.xlsx`)
this.download(
'cw/approval/export',
{
...this.queryParams
},
`approval_record_${new Date().getTime()}.xlsx`
);
},
// 手动加载字典数据
async loadDictData() {
@@ -321,13 +275,13 @@ export default {
await this.loadDictFromApi();
}
},
// 从API直接加载字典数据
async loadDictFromApi() {
try {
const response = await this.$http.get('/system/dict/data/type/cw_approval_status');
if (response.code === 200 && response.data) {
this.cw_approval_status = response.data.map(item => ({
this.cw_approval_status = response.data.map((item) => ({
label: item.dictLabel,
value: item.dictValue,
cssClass: item.cssClass,
@@ -354,5 +308,4 @@ export default {
};
</script>
<style scoped>
</style>
<style scoped></style>
+34 -48
View File
@@ -32,7 +32,7 @@
{{ parseTime(config.createTime, '{y}-{m}-{d} {h}:{i}') }}
</el-descriptions-item>
</el-descriptions>
<div v-if="config.description" class="mt-4">
<h4 class="section-title">配置描述</h4>
<p class="description-text">{{ config.description }}</p>
@@ -48,22 +48,18 @@
<el-tag type="info" size="small">共 {{ config.steps.length }} 个步骤</el-tag>
</div>
</template>
<el-steps :active="config.steps.length" finish-status="success" align-center>
<el-step
v-for="(step, index) in config.steps"
<el-step
v-for="(step, index) in config.steps"
:key="index"
:title="step.stepName"
:description="`${getApprovalModeText(step.approvalMode)} | ${step.timeoutHours}小时`"
/>
</el-steps>
<div class="steps-detail mt-6">
<div
v-for="(step, index) in config.steps"
:key="index"
class="step-detail-item"
>
<div v-for="(step, index) in config.steps" :key="index" class="step-detail-item">
<el-card shadow="hover" class="step-detail-card">
<template #header>
<div class="step-header">
@@ -74,7 +70,7 @@
</el-tag>
</div>
</template>
<el-row :gutter="20">
<el-col :span="8">
<div class="step-info">
@@ -95,34 +91,24 @@
</div>
</el-col>
</el-row>
<div class="approvers-section mt-4" v-if="step.approvers && step.approvers.length > 0">
<label class="info-label">审批人员:</label>
<div class="approvers-list">
<el-tag
v-for="(approver, approverIndex) in step.approverNames"
:key="approverIndex"
class="approver-tag"
>
<el-tag v-for="(approver, approverIndex) in step.approverNames" :key="approverIndex" class="approver-tag">
{{ approver }}
</el-tag>
</div>
</div>
<div v-else-if="config.approvalType === 'custom'" class="custom-approver-notice">
<el-alert
title="自选审批人"
description="申报人在提交时自行选择审批人"
type="info"
show-icon
:closable="false"
/>
<el-alert title="自选审批人" description="申报人在提交时自行选择审批人" type="info" show-icon :closable="false" />
</div>
</el-card>
</div>
</div>
</el-card>
<!-- 无流程步骤时的提示 -->
<el-card class="detail-card" v-else>
<el-empty description="暂无配置的审批流程步骤" />
@@ -144,13 +130,13 @@ export default {
getApprovalTypeTag(type) {
const typeMap = {
'fixed': 'success',
'role': 'warning',
'role': 'warning',
'dept': 'info',
'custom': 'primary'
};
return typeMap[type] || 'info';
},
/** 获取审批类型文本 */
getApprovalTypeText(type) {
const textMap = {
@@ -161,7 +147,7 @@ export default {
};
return textMap[type] || type;
},
/** 获取适用范围文本 */
getScopeText(scope) {
const scopeMap = {
@@ -172,7 +158,7 @@ export default {
};
return scopeMap[scope] || scope;
},
/** 获取审批方式文本 */
getApprovalModeText(mode) {
const modeMap = {
@@ -182,7 +168,7 @@ export default {
};
return modeMap[mode] || mode;
},
/** 获取审批方式标签类型 */
getModeTagType(mode) {
const typeMap = {
@@ -201,7 +187,7 @@ export default {
.detail-card {
border-radius: 8px;
border: 1px solid #e4e7ed;
.card-header {
display: flex;
align-items: center;
@@ -210,14 +196,14 @@ export default {
color: #2c3e50;
}
}
.section-title {
margin: 0 0 12px 0;
font-size: 16px;
font-weight: 600;
color: #2c3e50;
}
.description-text {
margin: 0;
padding: 12px;
@@ -226,24 +212,24 @@ export default {
color: #606266;
line-height: 1.6;
}
.steps-detail {
.step-detail-item {
margin-bottom: 16px;
&:last-child {
margin-bottom: 0;
}
}
.step-detail-card {
border: 1px solid #e4e7ed;
.step-header {
display: flex;
align-items: center;
gap: 12px;
.step-number {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
@@ -252,7 +238,7 @@ export default {
font-size: 12px;
font-weight: 600;
}
.step-name {
font-weight: 600;
color: #2c3e50;
@@ -260,12 +246,12 @@ export default {
}
}
}
.step-info {
display: flex;
align-items: center;
margin-bottom: 8px;
.info-label {
font-weight: 600;
color: #909399;
@@ -273,23 +259,23 @@ export default {
min-width: 80px;
}
}
.approvers-section {
border-top: 1px solid #f0f0f0;
padding-top: 16px;
.info-label {
font-weight: 600;
color: #909399;
margin-bottom: 8px;
display: block;
}
.approvers-list {
display: flex;
flex-wrap: wrap;
gap: 8px;
.approver-tag {
background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
color: white;
@@ -297,7 +283,7 @@ export default {
}
}
}
.custom-approver-notice {
border-top: 1px solid #f0f0f0;
padding-top: 16px;
@@ -316,4 +302,4 @@ export default {
.mt-6 {
margin-top: 24px;
}
</style>
</style>
+131 -167
View File
@@ -85,10 +85,14 @@
<el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['cw:approverConfig:add']">新增</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate" v-hasPermi="['cw:approverConfig:edit']">修改</el-button>
<el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate" v-hasPermi="['cw:approverConfig:edit']"
>修改</el-button
>
</el-col>
<el-col :span="1.5">
<el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete" v-hasPermi="['cw:approverConfig:remove']">删除</el-button>
<el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete" v-hasPermi="['cw:approverConfig:remove']"
>删除</el-button
>
</el-col>
<el-col :span="1.5">
<el-button type="info" plain icon="Sort" @click="toggleExpandAll">展开/折叠</el-button>
@@ -125,12 +129,7 @@
</el-table-column>
<el-table-column label="审批人" align="center" prop="approverNames" min-width="180" show-overflow-tooltip>
<template #default="scope">
<el-tag
v-for="(name, index) in scope.row.approverNames"
:key="index"
size="small"
class="mr-1"
>
<el-tag v-for="(name, index) in scope.row.approverNames" :key="index" size="small" class="mr-1">
{{ name }}
</el-tag>
</template>
@@ -178,13 +177,7 @@
</el-table-column>
</el-table>
<pagination
v-show="total > 0"
:total="total"
v-model:page="queryParams.pageNum"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
</el-card>
<!-- 添加或修改审批人配置对话框 -->
@@ -241,13 +234,9 @@
<el-button type="primary" size="small" icon="Plus" @click="addStep">添加步骤</el-button>
</div>
</template>
<div v-if="form.steps && form.steps.length > 0" class="steps-container">
<div
v-for="(step, index) in form.steps"
:key="index"
class="step-item"
>
<div v-for="(step, index) in form.steps" :key="index" class="step-item">
<el-card shadow="hover" class="step-card">
<template #header>
<div class="step-header">
@@ -268,7 +257,7 @@
</div>
</div>
</template>
<el-row :gutter="20">
<el-col :span="8">
<el-form-item label="步骤名称" :prop="`steps.${index}.stepName`" :rules="stepRules.stepName">
@@ -290,46 +279,25 @@
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="24">
<el-form-item label="审批人设置" :prop="`steps.${index}.approvers`" :rules="stepRules.approvers">
<div class="approvers-container">
<!-- 固定审批人 -->
<div v-if="form.approvalType === 'fixed'">
<el-select
v-model="step.approvers"
multiple
placeholder="请选择审批人"
style="width: 100%"
@change="updateApproverNames"
>
<el-option
v-for="user in userList"
:key="user.userId"
:label="user.nickName"
:value="user.userId"
/>
<el-select v-model="step.approvers" multiple placeholder="请选择审批人" style="width: 100%" @change="updateApproverNames">
<el-option v-for="user in userList" :key="user.userId" :label="user.nickName" :value="user.userId" />
</el-select>
</div>
<!-- 角色审批 -->
<div v-else-if="form.approvalType === 'role'">
<el-select
v-model="step.approvers"
multiple
placeholder="请选择审批角色"
style="width: 100%"
>
<el-option
v-for="role in roleList"
:key="role.roleId"
:label="role.roleName"
:value="role.roleId"
/>
<el-select v-model="step.approvers" multiple placeholder="请选择审批角色" style="width: 100%">
<el-option v-for="role in roleList" :key="role.roleId" :label="role.roleName" :value="role.roleId" />
</el-select>
</div>
<!-- 部门审批 -->
<div v-else-if="form.approvalType === 'dept'">
<el-tree-select
@@ -341,7 +309,7 @@
style="width: 100%"
/>
</div>
<!-- 自选审批人 -->
<div v-else-if="form.approvalType === 'custom'">
<el-alert
@@ -359,12 +327,12 @@
</el-card>
</div>
</div>
<el-empty v-else description="暂无审批步骤,请添加步骤" />
</el-card>
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="cancel">取消</el-button>
@@ -385,7 +353,7 @@
<!-- 流程图对话框 -->
<el-dialog v-model="flowDialog.visible" title="审批流程图" width="1200px" append-to-body>
<div ref="flowChartRef" style="height: 500px; border: 1px solid #e4e7ed;"></div>
<div ref="flowChartRef" style="height: 500px; border: 1px solid #e4e7ed"></div>
<template #footer>
<div class="dialog-footer">
<el-button @click="flowDialog.visible = false">关闭</el-button>
@@ -396,13 +364,7 @@
</template>
<script>
import {
listApproverConfig,
getApproverConfig,
addApproverConfig,
updateApproverConfig,
delApproverConfig
} from "@/api/cw/projectApplication";
import { listApproverConfig, getApproverConfig, addApproverConfig, updateApproverConfig, delApproverConfig } from '@/api/cw/projectApplication';
export default {
name: 'CwApproverConfig',
@@ -454,27 +416,17 @@ export default {
// 表单校验
rules: {
configName: [
{ required: true, message: "配置名称不能为空", trigger: "blur" },
{ min: 2, max: 50, message: "配置名称长度在 2 到 50 个字符", trigger: "blur" }
{ required: true, message: '配置名称不能为空', trigger: 'blur' },
{ min: 2, max: 50, message: '配置名称长度在 2 到 50 个字符', trigger: 'blur' }
],
approvalType: [
{ required: true, message: "审批类型不能为空", trigger: "change" }
],
applicableScope: [
{ required: true, message: "适用范围不能为空", trigger: "change" }
]
approvalType: [{ required: true, message: '审批类型不能为空', trigger: 'change' }],
applicableScope: [{ required: true, message: '适用范围不能为空', trigger: 'change' }]
},
// 步骤表单校验
stepRules: {
stepName: [
{ required: true, message: "步骤名称不能为空", trigger: "blur" }
],
approvalMode: [
{ required: true, message: "审批方式不能为空", trigger: "change" }
],
approvers: [
{ required: true, message: "请设置审批人", trigger: "change" }
]
stepName: [{ required: true, message: '步骤名称不能为空', trigger: 'blur' }],
approvalMode: [{ required: true, message: '审批方式不能为空', trigger: 'change' }],
approvers: [{ required: true, message: '请设置审批人', trigger: 'change' }]
},
// 用户列表
userList: [],
@@ -492,37 +444,35 @@ export default {
/** 查询审批人配置列表 */
getList() {
this.loading = true;
listApproverConfig(this.queryParams).then(response => {
this.configList = this.handleTree(response.data, "id", "parentId");
listApproverConfig(this.queryParams).then((response) => {
this.configList = this.handleTree(response.data, 'id', 'parentId');
this.total = response.total;
this.loading = false;
});
},
/** 加载基础数据 */
loadBasicData() {
// 加载用户列表
// listUser().then(response => {
// this.userList = response.rows;
// });
// 加载角色列表
// listRole().then(response => {
// this.roleList = response.rows;
// });
// 加载部门树
// listDept().then(response => {
// this.deptTreeData = this.handleTree(response.data, "deptId");
// });
},
/** 取消按钮 */
cancel() {
this.dialog.visible = false;
this.reset();
},
/** 表单重置 */
reset() {
this.form = {
@@ -532,92 +482,92 @@ export default {
applicableScope: 'all',
orderNum: 0,
description: null,
status: "1",
status: '1',
steps: []
};
if (this.$refs.formRef) {
this.$refs.formRef.resetFields();
}
},
/** 搜索按钮操作 */
handleQuery() {
this.queryParams.pageNum = 1;
this.getList();
},
/** 重置按钮操作 */
resetQuery() {
this.$refs.queryFormRef.resetFields();
this.handleQuery();
},
/** 多选框选中数据 */
handleSelectionChange(selection) {
this.ids = selection.map(item => item.id);
this.ids = selection.map((item) => item.id);
this.single = selection.length !== 1;
this.multiple = !selection.length;
},
/** 展开/折叠操作 */
toggleExpandAll() {
this.isExpandAll = !this.isExpandAll;
this.toggleExpandAllTree(this.configList, this.isExpandAll);
},
/** 快捷操作 */
showFlowDesigner() {
this.$modal.msgInfo("流程设计器功能开发中...");
this.$modal.msgInfo('流程设计器功能开发中...');
},
showTemplates() {
this.$modal.msgInfo("配置模板功能开发中...");
this.$modal.msgInfo('配置模板功能开发中...');
},
showTestFlow() {
this.$modal.msgInfo("流程测试功能开发中...");
this.$modal.msgInfo('流程测试功能开发中...');
},
/** 新增按钮操作 */
handleAdd() {
this.reset();
this.dialog.visible = true;
this.dialog.title = "添加审批人配置";
this.dialog.title = '添加审批人配置';
},
/** 修改按钮操作 */
handleUpdate(row) {
this.reset();
const id = row?.id || this.ids[0];
getApproverConfig(id).then(response => {
getApproverConfig(id).then((response) => {
this.form = response.data;
this.dialog.visible = true;
this.dialog.title = "修改审批人配置";
this.dialog.title = '修改审批人配置';
});
},
/** 复制配置 */
handleCopy(row) {
getApproverConfig(row.id).then(response => {
getApproverConfig(row.id).then((response) => {
this.form = {
...response.data,
id: null,
configName: response.data.configName + '_副本'
};
this.dialog.visible = true;
this.dialog.title = "复制审批人配置";
this.dialog.title = '复制审批人配置';
});
},
/** 查看详情 */
handleDetail(row) {
getApproverConfig(row.id).then(response => {
getApproverConfig(row.id).then((response) => {
this.currentConfig = response.data;
this.detailDialog.visible = true;
this.detailDialog.title = "配置详情";
this.detailDialog.title = '配置详情';
});
},
/** 查看流程图 */
handleViewFlow(row) {
this.flowDialog.visible = true;
@@ -625,45 +575,55 @@ export default {
this.renderFlowChart(row);
});
},
/** 状态变更 */
handleStatusChange(row) {
let text = row.status === "1" ? "启用" : "停用";
this.$modal.confirm('确认要"' + text + '""' + row.configName + '"配置吗?').then(() => {
return updateApproverConfig(row);
}).then(() => {
this.$modal.msgSuccess(text + "成功");
}).catch(() => {
row.status = row.status === "0" ? "1" : "0";
});
let text = row.status === '1' ? '启用' : '停用';
this.$modal
.confirm('确认要"' + text + '""' + row.configName + '"配置吗?')
.then(() => {
return updateApproverConfig(row);
})
.then(() => {
this.$modal.msgSuccess(text + '成功');
})
.catch(() => {
row.status = row.status === '0' ? '1' : '0';
});
},
/** 删除按钮操作 */
handleDelete(row) {
const ids = row?.id ? [row.id] : this.ids;
const configs = row?.configName ? [row.configName] : this.configList.filter(item => this.ids.includes(item.id)).map(item => item.configName);
this.$modal.confirm('是否确认删除配置名称为"' + configs.join('", "') + '"的数据项?').then(() => {
return delApproverConfig(ids);
}).then(() => {
this.getList();
this.$modal.msgSuccess("删除成功");
}).catch(() => {});
const configs = row?.configName
? [row.configName]
: this.configList.filter((item) => this.ids.includes(item.id)).map((item) => item.configName);
this.$modal
.confirm('是否确认删除配置名称为"' + configs.join('", "') + '"的数据项?')
.then(() => {
return delApproverConfig(ids);
})
.then(() => {
this.getList();
this.$modal.msgSuccess('删除成功');
})
.catch(() => {});
},
/** 提交按钮 */
submitForm() {
this.$refs.formRef.validate(valid => {
this.$refs.formRef.validate((valid) => {
if (valid) {
if (this.form.id != null) {
updateApproverConfig(this.form).then(response => {
this.$modal.msgSuccess("修改成功");
updateApproverConfig(this.form).then((response) => {
this.$modal.msgSuccess('修改成功');
this.dialog.visible = false;
this.getList();
});
} else {
addApproverConfig(this.form).then(response => {
this.$modal.msgSuccess("新增成功");
addApproverConfig(this.form).then((response) => {
this.$modal.msgSuccess('新增成功');
this.dialog.visible = false;
this.getList();
});
@@ -671,17 +631,17 @@ export default {
}
});
},
/** 审批类型变更 */
handleTypeChange(value) {
// 清空已配置的审批人
if (this.form.steps) {
this.form.steps.forEach(step => {
this.form.steps.forEach((step) => {
step.approvers = [];
});
}
},
/** 添加步骤 */
addStep() {
if (!this.form.steps) {
@@ -694,12 +654,12 @@ export default {
approvers: []
});
},
/** 删除步骤 */
removeStep(index) {
this.form.steps.splice(index, 1);
},
/** 上移步骤 */
moveStepUp(index) {
if (index > 0) {
@@ -708,7 +668,7 @@ export default {
this.form.steps[index - 1] = temp;
}
},
/** 下移步骤 */
moveStepDown(index) {
if (index < this.form.steps.length - 1) {
@@ -717,36 +677,40 @@ export default {
this.form.steps[index + 1] = temp;
}
},
/** 更新审批人名称 */
updateApproverNames() {
// 更新审批人名称用于显示
},
/** 导出按钮操作 */
handleExport() {
this.download('cw/approverConfig/export', {
...this.queryParams
}, `approver_config_${new Date().getTime()}.xlsx`);
this.download(
'cw/approverConfig/export',
{
...this.queryParams
},
`approver_config_${new Date().getTime()}.xlsx`
);
},
/** 渲染流程图 */
renderFlowChart(row) {
// 使用流程图库渲染流程图
this.$modal.msgInfo("流程图功能开发中...");
this.$modal.msgInfo('流程图功能开发中...');
},
/** 获取审批类型标签 */
getApprovalTypeTag(type) {
const typeMap = {
'fixed': 'success',
'role': 'warning',
'role': 'warning',
'dept': 'info',
'custom': 'primary'
};
return typeMap[type] || 'info';
},
/** 获取审批类型文本 */
getApprovalTypeText(type) {
const textMap = {
@@ -771,25 +735,25 @@ export default {
cursor: pointer;
transition: all 0.3s ease;
border: 1px solid #e4e7ed;
&:hover {
transform: translateY(-2px);
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.1);
border-color: #409EFF;
border-color: #409eff;
}
.quick-action {
display: flex;
flex-direction: column;
align-items: center;
padding: 20px;
.quick-icon {
font-size: 28px;
color: #409EFF;
color: #409eff;
margin-bottom: 8px;
}
span {
font-size: 14px;
color: #606266;
@@ -807,14 +771,14 @@ export default {
// 步骤徽章
.step-badge {
:deep(.el-badge__content) {
background-color: #409EFF;
background-color: #409eff;
}
}
// 步骤配置卡片
.steps-card {
border: 1px solid #e4e7ed;
.steps-header {
display: flex;
justify-content: space-between;
@@ -827,32 +791,32 @@ export default {
.steps-container {
.step-item {
margin-bottom: 16px;
&:last-child {
margin-bottom: 0;
}
}
.step-card {
border: 1px solid #e4e7ed;
.step-header {
display: flex;
justify-content: space-between;
align-items: center;
.step-title {
display: flex;
align-items: center;
font-weight: 600;
color: #2c3e50;
.el-icon {
margin-right: 8px;
color: #409EFF;
color: #409eff;
}
}
.step-actions {
display: flex;
align-items: center;
@@ -877,14 +841,14 @@ export default {
@media (max-width: 768px) {
.quick-action {
padding: 15px;
.quick-icon {
font-size: 24px;
}
span {
font-size: 12px;
}
}
}
</style>
</style>
+3 -2
View File
@@ -6,9 +6,10 @@
<el-form-item label="预算年度" prop="budgetYear">
<el-select v-model="queryParams.budgetYear" placeholder="请选择预算年度" clearable style="width: 200px">
<el-option label="全部年度" value="" />
<el-option label="2024年" value="2024" />
<el-option label="2025年" value="2025" />
<el-option label="2026年" value="2026" />
<el-option label="2027年" value="2027" />
<el-option label="2028年" value="2028" />
</el-select>
</el-form-item>
<el-form-item>
@@ -241,7 +242,7 @@ export default {
queryParams: {
pageNum: 1,
pageSize: 10,
budgetYear: '2024' // 设置默认年度为2024
budgetYear: '' // 设置默认为空,查询所有年份数据
},
// 统计数据
statusStatistics: {
@@ -0,0 +1,672 @@
<template>
<div class="p-2 page-container">
<el-card class="form-card" shadow="hover">
<template #header>
<div class="card-header">
<span class="card-title">{{ isEdit ? '修改助学金申请' : '新增助学金申请' }}</span>
</div>
</template>
<el-form ref="applicationFormRef" :model="form" :rules="rules" label-width="140px" class="application-form">
<!-- 基本信息 -->
<div class="form-section">
<div class="section-title">
<el-icon><User /></el-icon>
<span>基本信息</span>
</div>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="学年" prop="academicYear">
<el-select v-model="form.academicYear" placeholder="请选择学年" style="width: 100%">
<el-option label="2024-2025" value="2024-2025" />
<el-option label="2023-2024" value="2023-2024" />
<el-option label="2022-2023" value="2022-2023" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="学号" prop="studentNo">
<el-select
v-model="form.studentNo"
placeholder="请输入学号"
filterable
remote
reserve-keyword
:remote-method="searchStudents"
:loading="studentSearchLoading"
@change="handleStudentSelect"
style="width: 100%"
>
<el-option
v-for="student in filteredStudents"
:key="student.studentNo"
:label="`${student.studentNo} - ${student.studentName}`"
:value="student.studentNo"
/>
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="学生姓名" prop="studentName">
<el-input v-model="form.studentName" placeholder="请输入学生姓名" readonly />
</el-form-item>
</el-col>
<el-col :span="12"></el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="班级" prop="className">
<el-input v-model="form.className" placeholder="请输入班级名称" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="认定群体" prop="recognitionGroup">
<el-select v-model="form.recognitionGroup" placeholder="请选择认定群体" style="width: 100%" @change="handleRecognitionGroupChange">
<el-option v-for="group in recognitionGroupOptions" :key="group.value" :label="group.label" :value="group.value" />
</el-select>
<!-- 群体介绍说明 -->
<div v-if="selectedGroupDescription" class="group-description">
<el-text type="info" size="small">
{{ selectedGroupDescription }}
</el-text>
</div>
</el-form-item>
</el-col>
</el-row>
</div>
<!-- 家庭情况 -->
<div class="form-section">
<div class="section-title">
<el-icon><House /></el-icon>
<span>家庭情况</span>
</div>
<el-form-item label="家庭经济情况" prop="familyEconomyStatus">
<el-radio-group v-model="form.familyEconomyStatus">
<el-radio label="特别困难">特别困难</el-radio>
<el-radio label="困难">困难</el-radio>
<el-radio label="一般困难">一般困难</el-radio>
</el-radio-group>
<div class="form-tip">
<el-text type="info" size="small">请根据实际家庭经济状况选择对应等级</el-text>
</div>
</el-form-item>
<el-form-item label="家庭情况摘要" prop="familyEconomySummary">
<el-input
v-model="form.familyEconomySummary"
type="textarea"
:rows="6"
placeholder="请详细描述家庭经济情况,包括家庭成员、收入来源、主要支出等(500字以内)"
maxlength="500"
show-word-limit
/>
<div class="form-tip">
<el-text type="info" size="small">请如实填写家庭经济情况,内容将作为认定依据</el-text>
</div>
</el-form-item>
</div>
<!-- 申请信息 -->
<div class="form-section">
<div class="section-title">
<el-icon><Document /></el-icon>
<span>申请信息</span>
</div>
<el-form-item label="申请理由" prop="applicationReason">
<el-input
v-model="form.applicationReason"
type="textarea"
:rows="6"
placeholder="请详细说明申请助学金的理由,包括困难原因、影响等(500字以内)"
maxlength="500"
show-word-limit
/>
<div class="form-tip">
<el-text type="info" size="small">请详细说明申请理由,有助于提高审核通过率</el-text>
</div>
</el-form-item>
<el-form-item label="备注信息" prop="remark">
<el-input v-model="form.remark" type="textarea" :rows="3" placeholder="其他需要说明的情况(200字以内)" maxlength="200" show-word-limit />
</el-form-item>
</div>
<!-- 材料上传 -->
<div class="form-section">
<div class="section-title">
<el-icon><Folder /></el-icon>
<span>材料上传</span>
</div>
<!-- 申请表上传 -->
<el-form-item label="申请表附件" prop="attachApplicationFormUrl" required>
<el-upload
class="upload-demo"
action="/dev-api/resource/oss/upload"
:headers="uploadHeaders"
:file-list="applicationFormFileList"
:on-success="handleApplicationFormUploadSuccess"
:on-error="handleUploadError"
:on-remove="handleApplicationFormRemove"
:on-preview="handleFilePreview"
:before-upload="beforeUpload"
:limit="1"
accept=".jpg,.jpeg,.png,.pdf,.doc,.docx"
>
<el-button type="primary" icon="Upload">上传申请表</el-button>
<template #tip>
<div class="el-upload__tip">
请上传学生填写的贫困生认定申请表(纸质版照片),支持格式:jpg/jpeg/png/pdf/doc/docx,单个文件不超过10MB
</div>
</template>
</el-upload>
</el-form-item>
<!-- 调查表上传 -->
<el-form-item label="调查表附件" prop="attachSurveyFormUrl" required>
<el-upload
class="upload-demo"
action="/dev-api/resource/oss/upload"
:headers="uploadHeaders"
:file-list="surveyFormFileList"
:on-success="handleSurveyFormUploadSuccess"
:on-error="handleUploadError"
:on-remove="handleSurveyFormRemove"
:on-preview="handleFilePreview"
:before-upload="beforeUpload"
:limit="1"
accept=".jpg,.jpeg,.png,.pdf,.doc,.docx"
>
<el-button type="primary" icon="Upload">上传调查表</el-button>
<template #tip>
<div class="el-upload__tip">
请上传学生填写的家庭经济情况调查表(纸质版照片),支持格式:jpg/jpeg/png/pdf/doc/docx,单个文件不超过10MB
</div>
</template>
</el-upload>
</el-form-item>
</div>
</el-form>
<!-- 表单操作按钮 -->
<div class="form-footer">
<el-button @click="cancel">取消</el-button>
<el-button type="info" @click="saveDraft" v-if="!isEdit">保存草稿</el-button>
<el-button type="primary" @click="submitForm">{{ isEdit ? '确认修改' : '提交申请' }}</el-button>
</div>
</el-card>
</div>
</template>
<script setup name="JcbtPovertyApplicationForm" lang="ts">
import { ref, onMounted, getCurrentInstance } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import {
getPovertyApplication,
addPovertyApplication,
updatePovertyApplication,
saveDraftPovertyApplication,
submitPovertyApplication,
generateApplicationNo,
checkDuplicate
} from '@/api/jcbt/povertyApplication';
import { UploadFilled, User, House, Document, Folder } from '@element-plus/icons-vue';
const { proxy } = getCurrentInstance();
const route = useRoute();
const router = useRouter();
// 数据定义
const isEdit = ref(false);
const applicationId = ref(null);
// 认定群体选项
const recognitionGroupOptions = ref([
{
value: '特殊群体',
label: '特殊群体',
description: '低保家庭学生、特困供养学生、孤儿、烈士子女、残疾学生、低保边缘家庭学生、脱贫家庭学生(原建档立卡)、其他经济困难农户、其他群体(临时救助)。需提供相应政府部门出具的证明。'
},
{
value: '其他群体',
label: '其他群体',
description: '因遭受自然灾害、意外事件、重大疾病等情况导致家庭经济困难的学生。需提供学生所在街道(乡镇)级以上政府部门出具的相关证明,属于重大疾病导致家庭经济困难的还需要提供当年度医药费相关凭证。'
}
]);
// 当前选中群体的描述
const selectedGroupDescription = ref('');
// 文件上传
const fileList = ref([]);
// 学生搜索相关
const filteredStudents = ref([]);
const studentSearchLoading = ref(false);
const allStudents = ref([
// 模拟班级学生数据
{ studentNo: '20241001', studentName: '张三' },
{ studentNo: '20241002', studentName: '李四' },
{ studentNo: '20241003', studentName: '王五' },
{ studentNo: '20241011', studentName: '赵六' },
{ studentNo: '20241021', studentName: '钱七' },
{ studentNo: '20241031', studentName: '孙八' },
{ studentNo: '20242001', studentName: '周九' },
{ studentNo: '20242011', studentName: '吴十' },
]);
// 重复申请检查
const duplicateCheck = ref({
show: false,
message: '',
type: 'warning'
});
// 表单数据
const form = ref({
id: null,
studentId: null,
studentName: '',
studentNo: '',
classId: null,
className: '',
recognitionGroup: null,
academicYear: '2024-2025',
familyEconomyStatus: '',
familyEconomySummary: '',
applicationReason: '',
remark: '',
status: 'DRAFT'
});
// 表单验证规则
const rules = {
studentName: [
{ required: true, message: '学生姓名不能为空', trigger: 'blur' },
{ min: 2, max: 10, message: '学生姓名长度在 2 到 10 个字符', trigger: 'blur' }
],
studentNo: [
{ required: true, message: '学号不能为空', trigger: 'blur' },
{ pattern: /^[0-9]{8,12}$/, message: '学号格式不正确', trigger: 'blur' }
],
className: [
{ required: true, message: '班级不能为空', trigger: 'blur' }
],
recognitionGroup: [
{ required: true, message: '认定群体不能为空', trigger: 'change' }
],
academicYear: [
{ required: true, message: '学年不能为空', trigger: 'change' }
],
familyEconomyStatus: [
{ required: true, message: '家庭经济情况不能为空', trigger: 'change' }
],
familyEconomySummary: [
{ required: true, message: '家庭情况摘要不能为空', trigger: 'blur' },
{ min: 50, message: '家庭情况摘要至少50字', trigger: 'blur' }
],
applicationReason: [
{ required: true, message: '申请理由不能为空', trigger: 'blur' },
{ min: 30, message: '申请理由至少30字', trigger: 'blur' }
]
}
/** 检查学生信息 */
async function checkStudentInfo() {
if (!form.value.studentNo || !form.value.academicYear || !form.value.recognitionGroup) {
return
}
try {
const response = await checkDuplicate({
studentId: form.value.studentId || 0,
academicYear: form.value.academicYear,
recognitionGroup: form.value.recognitionGroup,
excludeId: form.value.id
})
if (response.data) {
duplicateCheck.value = {
show: true,
message: '该学生在当前学年已有相同类型的认定申请,请检查是否重复申请',
type: 'warning'
}
} else {
duplicateCheck.value.show = false
}
} catch (error) {
console.error('检查重复申请失败:', error)
}
}
/** 处理认定群体选择变化 */
function handleRecognitionGroupChange(value) {
const selectedGroup = recognitionGroupOptions.value.find(group => group.value === value)
selectedGroupDescription.value = selectedGroup ? selectedGroup.description : ''
// 检查重复申请
checkStudentInfo()
}
/** 搜索学生 */
function searchStudents(query) {
if (!query) {
filteredStudents.value = []
return
}
studentSearchLoading.value = true
// 模拟异步搜索延迟
setTimeout(() => {
// 根据学号进行模糊搜索
filteredStudents.value = allStudents.value.filter(student =>
student.studentNo.includes(query) || student.studentName.includes(query)
)
studentSearchLoading.value = false
}, 200)
}
/** 处理学生选择 */
function handleStudentSelect(studentNo) {
if (!studentNo) {
form.value.studentName = ''
form.value.studentId = null
return
}
const selectedStudent = allStudents.value.find(student => student.studentNo === studentNo)
if (selectedStudent) {
form.value.studentName = selectedStudent.studentName
form.value.studentId = selectedStudent.studentId || null
// 检查重复申请
checkStudentInfo()
}
}
/** 文件上传前检查 */
function beforeUpload(file) {
const isValidType = ['image/jpeg', 'image/png', 'application/pdf'].includes(file.type)
const isLt5M = file.size / 1024 / 1024 < 5
if (!isValidType) {
proxy.$modal.msgError('上传文件只能是 JPG/PNG/PDF 格式!')
return false
}
if (!isLt5M) {
proxy.$modal.msgError('上传文件大小不能超过 5MB!')
return false
}
return true
}
/** 文件上传成功 */
function handleUploadSuccess(response, file) {
proxy.$modal.msgSuccess('文件上传成功')
}
/** 文件删除 */
function handleRemove(file) {
proxy.$modal.msgSuccess('文件删除成功')
}
/** 保存草稿 */
function handleSaveDraft() {
// 基本验证
if (!form.value.studentName || !form.value.studentNo) {
proxy.$modal.msgError('请至少填写学生姓名和学号')
return
}
saveDraftPovertyApplication(form.value).then(response => {
proxy.$modal.msgSuccess('草稿保存成功')
goBack()
}).catch(error => {
proxy.$modal.msgError('草稿保存失败')
})
}
/** 提交表单 */
function submitForm() {
proxy.$refs.applicationFormRef.validate(valid => {
if (valid) {
if (duplicateCheck.value.show) {
proxy.$modal.confirm('检测到可能的重复申请,是否继续提交?').then(() => {
doSubmit()
})
} else {
doSubmit()
}
}
})
}
/** 执行提交 */
function doSubmit() {
const submitFunction = isEdit.value ? updatePovertyApplication : addPovertyApplication
const successMessage = isEdit.value ? '修改成功' : '申请提交成功'
submitFunction(form.value).then(response => {
proxy.$modal.msgSuccess(successMessage)
goBack()
}).catch(error => {
proxy.$modal.msgError(isEdit.value ? '修改失败' : '申请提交失败')
})
}
/** 提交并申请审核 */
function submitAndApprove() {
proxy.$refs.applicationFormRef.validate(valid => {
if (valid) {
if (duplicateCheck.value.show) {
proxy.$modal.confirm('检测到可能的重复申请,是否继续提交?').then(() => {
doSubmitAndApprove()
})
} else {
doSubmitAndApprove()
}
}
})
}
/** 执行提交并申请审核 */
async function doSubmitAndApprove() {
try {
// 先保存/更新申请
const submitFunction = isEdit.value ? updatePovertyApplication : addPovertyApplication
const response = await submitFunction(form.value)
// 如果是新增,需要获取返回的ID
let applicationId = form.value.id
if (!isEdit.value && response.data) {
applicationId = response.data
}
// 然后提交审核
if (applicationId) {
await submitPovertyApplication(applicationId)
proxy.$modal.msgSuccess('申请已提交,等待审核')
} else {
proxy.$modal.msgSuccess('申请保存成功')
}
goBack()
} catch (error) {
proxy.$modal.msgError('操作失败')
}
}
/** 返回列表 */
function goBack() {
router.push('/jcbt/poverty/Application')
}
/** 初始化数据 */
async function initData() {
// 检查是否为编辑模式
if (route.params.id) {
isEdit.value = true
applicationId.value = route.params.id
try {
const response = await getPovertyApplication(applicationId.value)
form.value = response.data
} catch (error) {
proxy.$modal.msgError('获取申请信息失败')
goBack()
}
} else {
// 新增模式,生成申请编号
// await generateNo() // This line is removed as per the edit hint
}
}
onMounted(() => {
initData()
})
</script>
<style scoped lang="scss">
.page-container {
padding: 8px;
}
.form-card {
.card-header {
.card-title {
font-size: 18px;
font-weight: 600;
color: #303133;
}
}
}
.application-form {
max-width: 800px;
}
.form-section {
margin-bottom: 40px;
padding-bottom: 30px;
border-bottom: 1px solid #f0f0f0;
&:last-child {
border-bottom: none;
margin-bottom: 0;
}
.section-title {
display: flex;
align-items: center;
margin-bottom: 20px;
font-size: 16px;
font-weight: 600;
color: #409eff;
i {
margin-right: 8px;
font-size: 18px;
}
span {
position: relative;
&::after {
content: '';
position: absolute;
bottom: -5px;
left: 0;
width: 30px;
height: 2px;
background-color: #409eff;
}
}
}
}
.form-tip {
margin-top: 5px;
}
.upload-demo {
width: 100%;
:deep(.el-upload-dragger) {
width: 100%;
height: 120px;
}
}
.form-actions {
display: flex;
justify-content: center;
gap: 16px;
padding-top: 30px;
border-top: 1px solid #f0f0f0;
margin-top: 40px;
}
.mb-4 {
margin-bottom: 20px;
}
// 表单样式优化
:deep(.el-form-item__label) {
font-weight: 500;
color: #606266;
}
:deep(.el-input__wrapper) {
border-radius: 6px;
}
:deep(.el-textarea__inner) {
border-radius: 6px;
resize: vertical;
}
:deep(.el-select) {
width: 100%;
}
:deep(.el-radio-group) {
.el-radio {
margin-right: 30px;
&:last-child {
margin-right: 0;
}
}
}
// 群体描述样式
.group-description {
margin-top: 8px;
padding: 12px;
background-color: #f5f7fa;
border-radius: 6px;
border-left: 3px solid #409eff;
.el-text {
line-height: 1.6;
color: #606266;
}
}
// 响应式设计
@media (max-width: 768px) {
.application-form {
max-width: 100%;
}
.form-actions {
flex-direction: column;
.el-button {
width: 100%;
}
}
}
</style>
File diff suppressed because it is too large. Load diff
@@ -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,720 @@
<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="collegeName" label="学院" width="120" />
<el-table-column prop="majorName" label="专业" width="120" />
<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>
File diff suppressed because it is too large. Load diff
@@ -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
@@ -0,0 +1,314 @@
<template>
<el-dialog title="审核操作" v-model="dialogVisible" width="800px" append-to-body @close="handleClose">
<div class="review-info">
<el-descriptions title="申请信息" :column="2" border>
<el-descriptions-item label="申请编号">{{ reviewData.orderNo }}</el-descriptions-item>
<el-descriptions-item label="申请人姓名">{{ reviewData.applicantName }}</el-descriptions-item>
<el-descriptions-item label="奖学金类型">{{ reviewData.scholarshipType }}</el-descriptions-item>
<el-descriptions-item label="当前审核级别">
<el-tag v-if="reviewData.auditLevel === 'DEPARTMENT'" type="primary">院系审核</el-tag>
<el-tag v-else-if="reviewData.auditLevel === 'SCHOOL'" type="success">学校审核</el-tag>
<el-tag v-else-if="reviewData.auditLevel === 'FINANCE'" type="warning">财务审核</el-tag>
<el-tag v-else-if="reviewData.auditLevel === 'FINAL'" type="danger">最终审核</el-tag>
</el-descriptions-item>
</el-descriptions>
</div>
<el-form ref="formRef" :model="form" :rules="rules" label-width="120px" class="review-form">
<el-form-item label="审核操作" prop="action">
<el-radio-group v-model="form.action" @change="handleActionChange">
<el-radio-button label="approve">
<el-icon><CircleCheck /></el-icon>
通过
</el-radio-button>
<el-radio-button label="reject">
<el-icon><CircleClose /></el-icon>
拒绝
</el-radio-button>
<el-radio-button label="return">
<el-icon><RefreshLeft /></el-icon>
退回
</el-radio-button>
</el-radio-group>
</el-form-item>
<el-form-item
label="下一审核人"
prop="nextReviewerId"
v-if="form.action === 'approve' && needNextReviewer"
>
<el-select v-model="form.nextReviewerId" placeholder="请选择下一审核人" style="width: 100%">
<el-option
v-for="reviewer in availableReviewers"
:key="reviewer.userId"
:label="reviewer.userName"
:value="reviewer.userId"
/>
</el-select>
</el-form-item>
<el-form-item label="审核意见" prop="reviewComment">
<el-input
v-model="form.reviewComment"
type="textarea"
:rows="4"
:placeholder="getCommentPlaceholder()"
show-word-limit
maxlength="500"
/>
</el-form-item>
<el-form-item v-if="form.action">
<el-alert
:title="getActionTip()"
:type="getActionType()"
:closable="false"
show-icon
/>
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="handleClose">取 消</el-button>
<el-button
type="primary"
@click="handleConfirm"
:loading="loading"
:disabled="!form.action"
>
确 定
</el-button>
</div>
</template>
</el-dialog>
</template>
<script setup lang="ts">
import { performReviewAction, getAvailableReviewers, type ReviewActionForm } from '@/api/jcbt/scholarshipReview';
import type { ScholarshipReviewVo } from '@/api/jcbt/scholarshipReview';
// Props
interface Props {
visible: boolean;
reviewData: ScholarshipReviewVo;
}
// Emits
interface Emits {
(e: 'update:visible', visible: boolean): void;
(e: 'confirm'): void;
}
const props = withDefaults(defineProps<Props>(), {
visible: false,
reviewData: () => ({} as ScholarshipReviewVo)
});
const emit = defineEmits<Emits>();
// 基础状态
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const loading = ref(false);
const availableReviewers = ref<any[]>([]);
// 计算属性
const dialogVisible = computed({
get: () => props.visible,
set: (value) => emit('update:visible', value)
});
// 是否需要选择下一审核人
const needNextReviewer = computed(() => {
return props.reviewData.auditLevel !== 'FINAL';
});
// 表单数据
const form = ref<ReviewActionForm>({
reviewId: '',
action: '' as any,
reviewComment: '',
nextReviewerId: undefined
});
// 表单验证规则
const rules = reactive({
action: [
{ required: true, message: '请选择审核操作', trigger: 'change' }
],
reviewComment: [
{ required: true, message: '请输入审核意见', trigger: 'blur' },
{ min: 5, message: '审核意见至少5个字符', trigger: 'blur' }
],
nextReviewerId: [
{
required: true,
message: '请选择下一审核人',
trigger: 'change',
validator: (rule: any, value: any, callback: any) => {
if (form.value.action === 'approve' && needNextReviewer.value && !value) {
callback(new Error('请选择下一审核人'));
} else {
callback();
}
}
}
]
});
const formRef = ref<ElFormInstance>();
// 监听数据变化
watch(() => props.reviewData, (newData) => {
if (newData && newData.reviewId) {
form.value.reviewId = newData.reviewId;
form.value.action = '' as any;
form.value.reviewComment = '';
form.value.nextReviewerId = undefined;
}
}, { immediate: true });
// 监听操作类型变化
const handleActionChange = (action: string) => {
if (action === 'approve' && needNextReviewer.value) {
loadAvailableReviewers();
}
// 清除下一审核人选择
if (action !== 'approve') {
form.value.nextReviewerId = undefined;
}
};
// 获取可用审核人员
const loadAvailableReviewers = async () => {
try {
const nextLevel = getNextAuditLevel(props.reviewData.auditLevel);
if (nextLevel) {
const res = await getAvailableReviewers(nextLevel);
availableReviewers.value = res.data || [];
}
} catch (error) {
console.error('获取审核人员失败:', error);
}
};
// 获取下一审核级别
const getNextAuditLevel = (currentLevel: string): string => {
const levelMap: Record<string, string> = {
'DEPARTMENT': 'SCHOOL',
'SCHOOL': 'FINANCE',
'FINANCE': 'FINAL'
};
return levelMap[currentLevel] || '';
};
// 获取审核意见占位符
const getCommentPlaceholder = (): string => {
const action = form.value.action;
const placeholders: Record<string, string> = {
'approve': '请输入通过审核的意见...',
'reject': '请输入拒绝审核的理由...',
'return': '请输入退回的原因...'
};
return placeholders[action] || '请输入审核意见...';
};
// 获取操作提示
const getActionTip = (): string => {
const action = form.value.action;
const tips: Record<string, string> = {
'approve': '确认通过此申请,申请将进入下一审核环节',
'reject': '确认拒绝此申请,申请将被驳回',
'return': '确认退回此申请,申请将返回给申请人修改'
};
return tips[action] || '';
};
// 获取提示类型
const getActionType = (): string => {
const action = form.value.action;
const types: Record<string, string> = {
'approve': 'success',
'reject': 'error',
'return': 'warning'
};
return types[action] || 'info';
};
// 确认操作
const handleConfirm = () => {
formRef.value?.validate(async (valid: boolean) => {
if (valid) {
loading.value = true;
try {
await performReviewAction(form.value);
proxy?.$modal.msgSuccess('审核操作成功');
emit('confirm');
handleClose();
} catch (error) {
console.error('审核操作失败:', error);
proxy?.$modal.msgError('审核操作失败');
} finally {
loading.value = false;
}
}
});
};
// 关闭对话框
const handleClose = () => {
form.value = {
reviewId: '',
action: '' as any,
reviewComment: '',
nextReviewerId: undefined
};
formRef.value?.resetFields();
emit('update:visible', false);
};
</script>
<style lang="scss" scoped>
.review-info {
margin-bottom: 20px;
:deep(.el-descriptions__title) {
font-size: 16px;
font-weight: 600;
color: #303133;
}
}
.review-form {
margin-top: 20px;
.el-radio-button {
:deep(.el-radio-button__inner) {
display: flex;
align-items: center;
gap: 6px;
padding: 8px 16px;
font-size: 14px;
}
}
}
.dialog-footer {
display: flex;
justify-content: flex-end;
gap: 10px;
}
// 描述列表样式优化
:deep(.el-descriptions) {
.el-descriptions__label {
font-weight: 600;
color: #606266;
width: 120px;
}
.el-descriptions__content {
color: #303133;
}
}
</style>
@@ -0,0 +1,581 @@
<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="orderNo">
<el-input v-model="queryParams.orderNo" placeholder="请输入申请编号" clearable @keyup.enter="handleQuery" />
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="申请人姓名" prop="applicantName">
<el-input v-model="queryParams.applicantName" placeholder="请输入申请人姓名" clearable @keyup.enter="handleQuery" />
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="审核结果" prop="reviewResult">
<el-select v-model="queryParams.reviewResult" placeholder="请选择审核结果" clearable style="width: 100%">
<el-option label="已通过" value="APPROVED" />
<el-option label="已拒绝" value="REJECTED" />
<el-option label="已退回" value="RETURNED" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="审核时间">
<el-date-picker
v-model="daterangeReviewTime"
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-row>
<!-- 第二行:操作按钮 -->
<el-row>
<el-col :span="24" class="search-btn-container">
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
<el-button type="warning" icon="Download" @click="handleExport" v-hasPermi="['jcbt:review:export']">导出</el-button>
</el-form-item>
</el-col>
</el-row>
</el-form>
</el-card>
</div>
</transition>
<!-- 统计概览 -->
<div class="mb-[10px]">
<el-row :gutter="16">
<el-col :span="8">
<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">{{ historyStats.approvedCount }}</div>
<div class="stat-label">审核通过</div>
</div>
</div>
</el-card>
</el-col>
<el-col :span="8">
<el-card shadow="hover" class="stat-card stat-card-danger">
<div class="stat-content">
<div class="stat-icon">
<el-icon><CircleClose /></el-icon>
</div>
<div class="stat-info">
<div class="stat-value">{{ historyStats.rejectedCount }}</div>
<div class="stat-label">审核拒绝</div>
</div>
</div>
</el-card>
</el-col>
<el-col :span="8">
<el-card shadow="hover" class="stat-card stat-card-warning">
<div class="stat-content">
<div class="stat-icon">
<el-icon><RefreshLeft /></el-icon>
</div>
<div class="stat-info">
<div class="stat-value">{{ historyStats.returnedCount }}</div>
<div class="stat-label">审核退回</div>
</div>
</div>
</el-card>
</el-col>
</el-row>
</div>
<!-- 审核历史列表 -->
<el-card shadow="never" class="table-card">
<template #header>
<div class="card-header">
<span class="card-title">审核历史记录</span>
<div class="card-extra">
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
</div>
</div>
</template>
<!-- 表格区域 -->
<div class="table-container">
<el-table
v-loading="loading"
border
:data="historyList"
style="width: 100%"
class="scroll-table"
>
<el-table-column label="申请编号" align="center" prop="orderNo" width="140" show-overflow-tooltip fixed="left" />
<el-table-column label="申请人" align="center" prop="applicantName" width="120" show-overflow-tooltip />
<el-table-column label="奖学金类型" align="center" prop="scholarshipType" min-width="200" show-overflow-tooltip />
<el-table-column label="审核级别" align="center" prop="auditLevel" width="120">
<template #default="scope">
<el-tag v-if="scope.row.auditLevel === 'DEPARTMENT'" type="primary">院系审核</el-tag>
<el-tag v-else-if="scope.row.auditLevel === 'SCHOOL'" type="success">学校审核</el-tag>
<el-tag v-else-if="scope.row.auditLevel === 'FINANCE'" type="warning">财务审核</el-tag>
<el-tag v-else-if="scope.row.auditLevel === 'FINAL'" type="danger">最终审核</el-tag>
</template>
</el-table-column>
<el-table-column label="审核结果" align="center" prop="auditStatus" width="120">
<template #default="scope">
<el-tag v-if="scope.row.auditStatus === 'APPROVED'" type="success">
<el-icon><CircleCheck /></el-icon>
已通过
</el-tag>
<el-tag v-else-if="scope.row.auditStatus === 'REJECTED'" type="danger">
<el-icon><CircleClose /></el-icon>
已拒绝
</el-tag>
<el-tag v-else-if="scope.row.auditStatus === 'RETURNED'" type="warning">
<el-icon><RefreshLeft /></el-icon>
已退回
</el-tag>
</template>
</el-table-column>
<el-table-column label="审核人" align="center" prop="reviewerName" width="120" show-overflow-tooltip />
<el-table-column label="审核时间" align="center" prop="reviewTime" width="180">
<template #default="scope">
<span>{{ parseTime(scope.row.reviewTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
</template>
</el-table-column>
<el-table-column label="审核意见" align="center" prop="reviewComment" min-width="250" show-overflow-tooltip />
<el-table-column label="处理时长" align="center" width="120">
<template #default="scope">
<span v-if="scope.row.reviewTime && scope.row.createTime">
{{ calculateDuration(scope.row.createTime, scope.row.reviewTime) }}
</span>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="120" fixed="right">
<template #default="scope">
<div class="action-buttons">
<el-tooltip content="查看详情" placement="top">
<el-button link type="primary" icon="View" @click="handleView(scope.row)" v-hasPermi="['jcbt:review:query']">详情</el-button>
</el-tooltip>
</div>
</template>
</el-table-column>
</el-table>
</div>
<!-- 分页 -->
<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="审核详情" v-model="detailDialog.visible" width="800px" append-to-body>
<div v-if="detailDialog.data" class="detail-content">
<el-descriptions title="基本信息" :column="2" border>
<el-descriptions-item label="申请编号">{{ detailDialog.data.orderNo }}</el-descriptions-item>
<el-descriptions-item label="审核编号">{{ detailDialog.data.reviewId }}</el-descriptions-item>
<el-descriptions-item label="申请人姓名">{{ detailDialog.data.applicantName }}</el-descriptions-item>
<el-descriptions-item label="奖学金类型">{{ detailDialog.data.scholarshipType }}</el-descriptions-item>
<el-descriptions-item label="审核级别">
<el-tag v-if="detailDialog.data.auditLevel === 'DEPARTMENT'" type="primary">院系审核</el-tag>
<el-tag v-else-if="detailDialog.data.auditLevel === 'SCHOOL'" type="success">学校审核</el-tag>
<el-tag v-else-if="detailDialog.data.auditLevel === 'FINANCE'" type="warning">财务审核</el-tag>
<el-tag v-else-if="detailDialog.data.auditLevel === 'FINAL'" type="danger">最终审核</el-tag>
</el-descriptions-item>
<el-descriptions-item label="审核结果">
<el-tag v-if="detailDialog.data.auditStatus === 'APPROVED'" type="success">已通过</el-tag>
<el-tag v-else-if="detailDialog.data.auditStatus === 'REJECTED'" type="danger">已拒绝</el-tag>
<el-tag v-else-if="detailDialog.data.auditStatus === 'RETURNED'" type="warning">已退回</el-tag>
</el-descriptions-item>
</el-descriptions>
<el-descriptions title="审核信息" :column="2" border class="mt-4">
<el-descriptions-item label="审核人">{{ detailDialog.data.reviewerName }}</el-descriptions-item>
<el-descriptions-item label="审核时间">
{{ parseTime(detailDialog.data.reviewTime, '{y}-{m}-{d} {h}:{i}:{s}') }}
</el-descriptions-item>
<el-descriptions-item label="下一审核人" v-if="detailDialog.data.nextReviewerName">
{{ detailDialog.data.nextReviewerName }}
</el-descriptions-item>
<el-descriptions-item label="处理时长">
{{ calculateDuration(detailDialog.data.createTime, detailDialog.data.reviewTime) }}
</el-descriptions-item>
</el-descriptions>
<div class="mt-4">
<h4>审核意见</h4>
<el-input
v-model="detailDialog.data.reviewComment"
type="textarea"
:rows="4"
readonly
placeholder="无审核意见"
/>
</div>
<div class="mt-4" v-if="detailDialog.data.attachments">
<h4>相关附件</h4>
<div class="attachment-list">
<!-- 这里可以展示附件列表 -->
<el-tag>{{ detailDialog.data.attachments }}</el-tag>
</div>
</div>
</div>
<template #footer>
<div class="dialog-footer">
<el-button @click="detailDialog.visible = false">关 闭</el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup name="ScholarshipReviewHistory" lang="ts">
import {
getReviewHistory,
exportScholarshipReview,
getScholarshipReview,
type ReviewHistoryQuery,
type ScholarshipReviewVo
} from '@/api/jcbt/scholarshipReview';
// 基础状态
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const loading = ref(true);
const showSearch = ref(true);
const total = ref(0);
const historyList = ref<ScholarshipReviewVo[]>([]);
const daterangeReviewTime = ref<[DateModelType, DateModelType]>([]);
// 统计数据
const historyStats = ref({
approvedCount: 0,
rejectedCount: 0,
returnedCount: 0
});
// 查询参数
const queryParams = ref<ReviewHistoryQuery>({
pageNum: 1,
pageSize: 10,
orderNo: '',
applicantName: '',
reviewResult: '',
reviewTimeBegin: '',
reviewTimeEnd: '',
batchId: undefined
});
// 详情对话框
const detailDialog = reactive({
visible: false,
data: null as ScholarshipReviewVo | null
});
// 表单引用
const queryFormRef = ref<ElFormInstance>();
/** 查询审核历史列表 */
const getList = async () => {
loading.value = true;
try {
// 处理时间范围
queryParams.value.reviewTimeBegin = '';
queryParams.value.reviewTimeEnd = '';
if (daterangeReviewTime.value?.length === 2) {
queryParams.value.reviewTimeBegin = daterangeReviewTime.value[0];
queryParams.value.reviewTimeEnd = daterangeReviewTime.value[1];
}
const res = await getReviewHistory(queryParams.value);
historyList.value = res.rows;
total.value = res.total;
// 计算统计数据
calculateStats();
} finally {
loading.value = false;
}
};
/** 计算统计数据 */
const calculateStats = () => {
const stats = {
approvedCount: 0,
rejectedCount: 0,
returnedCount: 0
};
historyList.value.forEach(item => {
switch (item.auditStatus) {
case 'APPROVED':
stats.approvedCount++;
break;
case 'REJECTED':
stats.rejectedCount++;
break;
case 'RETURNED':
stats.returnedCount++;
break;
}
});
historyStats.value = stats;
};
/** 计算处理时长 */
const calculateDuration = (startTime: string, endTime: string): string => {
if (!startTime || !endTime) return '-';
try {
const start = new Date(startTime).getTime();
const end = new Date(endTime).getTime();
const diff = end - start;
const days = Math.floor(diff / (1000 * 60 * 60 * 24));
const hours = Math.floor((diff % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
const minutes = Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60));
if (days > 0) {
return `${days}天${hours}小时`;
} else if (hours > 0) {
return `${hours}小时${minutes}分`;
} else {
return `${minutes}分钟`;
}
} catch (error) {
return '-';
}
};
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.value.pageNum = 1;
getList();
};
/** 重置按钮操作 */
const resetQuery = () => {
daterangeReviewTime.value = [];
queryFormRef.value?.resetFields();
queryParams.value.pageNum = 1;
handleQuery();
};
/** 查看详情 */
const handleView = async (row: ScholarshipReviewVo) => {
try {
const res = await getScholarshipReview(row.id);
detailDialog.data = res.data;
detailDialog.visible = true;
} catch (error) {
console.error('获取详情失败:', error);
proxy?.$modal.msgError('获取详情失败');
}
};
/** 导出按钮操作 */
const handleExport = () => {
const exportParams = { ...queryParams.value };
// 处理时间范围
if (daterangeReviewTime.value?.length === 2) {
exportParams.reviewTimeBegin = daterangeReviewTime.value[0];
exportParams.reviewTimeEnd = daterangeReviewTime.value[1];
}
proxy?.$download.excel(exportScholarshipReview, exportParams, `scholarship_review_history_${new Date().getTime()}.xlsx`);
};
onMounted(() => {
getList();
});
</script>
<style lang="scss" scoped>
.page-container {
padding: 8px;
.search-btn-container {
display: flex;
justify-content: flex-end;
margin-top: 10px;
.el-form-item {
margin-bottom: 0 !important;
}
}
.table-card {
background-color: #fff;
border-radius: 8px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
}
.pagination-container {
text-align: right;
padding-top: 20px;
}
}
/* 卡片头部样式 */
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
.card-title {
font-size: 16px;
font-weight: 600;
color: #303133;
}
.card-extra {
display: flex;
gap: 8px;
align-items: center;
}
}
/* 统计卡片样式 */
.stat-card {
border-radius: 8px;
transition: all 0.3s ease;
height: 90px;
&:hover {
transform: translateY(-2px);
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.1);
}
.stat-content {
display: flex;
align-items: center;
padding: 5px 12px;
height: 100%;
box-sizing: border-box;
.stat-icon {
font-size: 24px;
margin-right: 12px;
opacity: 0.8;
}
.stat-info {
flex: 1;
.stat-value {
font-size: 20px;
font-weight: bold;
line-height: 1;
margin-bottom: 3px;
}
.stat-label {
font-size: 13px;
opacity: 0.7;
line-height: 1;
}
}
}
&.stat-card-success {
background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
color: white;
}
&.stat-card-warning {
background: linear-gradient(135deg, #ff9a56 0%, #ffad56 100%);
color: white;
}
&.stat-card-danger {
background: linear-gradient(135deg, #fa709a 0%, #fee140 100%);
color: white;
}
}
/* 表格样式 */
.scroll-table {
:deep(.el-table__header-wrapper) {
.el-table__header {
th {
background-color: #f8f9fa;
color: #333;
font-weight: 600;
}
}
}
:deep(.el-table__body-wrapper) {
.el-table__row {
&:hover {
background-color: #f5f7fa;
}
}
}
}
/* 操作按钮样式 */
.action-buttons {
display: flex;
gap: 8px;
justify-content: center;
align-items: center;
}
/* 详情内容样式 */
.detail-content {
h4 {
margin: 16px 0 8px 0;
font-size: 14px;
font-weight: 600;
color: #303133;
}
.attachment-list {
.el-tag {
margin-right: 8px;
}
}
}
.dialog-footer {
display: flex;
justify-content: flex-end;
gap: 10px;
}
/* 描述列表样式优化 */
:deep(.el-descriptions) {
.el-descriptions__label {
font-weight: 600;
color: #606266;
width: 120px;
}
.el-descriptions__content {
color: #303133;
}
}
</style>
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,781 @@
<template>
<div class="p-2 page-container">
<!-- 任务统计 -->
<div class="mb-[10px]">
<el-row :gutter="16">
<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">{{ taskStats.pendingCount }}</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">{{ taskStats.processedCount }}</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><Document /></el-icon>
</div>
<div class="stat-info">
<div class="stat-value">{{ taskStats.totalCount }}</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-primary">
<div class="stat-content">
<div class="stat-icon">
<el-icon><TrendCharts /></el-icon>
</div>
<div class="stat-info">
<div class="stat-value">{{ taskStats.efficiency }}%</div>
<div class="stat-label">处理效率</div>
</div>
</div>
</el-card>
</el-col>
</el-row>
</div>
<!-- 搜索区域 -->
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
<div v-show="showSearch" class="mb-[10px]">
<el-card shadow="hover">
<el-form ref="queryFormRef" :model="queryParams" label-width="100px">
<el-row :gutter="20">
<el-col :span="6">
<el-form-item label="申请编号" prop="orderNo">
<el-input v-model="queryParams.orderNo" placeholder="请输入申请编号" clearable @keyup.enter="handleQuery" />
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="申请人姓名" prop="applicantName">
<el-input v-model="queryParams.applicantName" placeholder="请输入申请人姓名" clearable @keyup.enter="handleQuery" />
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="奖学金类型" prop="scholarshipType">
<el-input v-model="queryParams.scholarshipType" placeholder="请输入奖学金类型" clearable @keyup.enter="handleQuery" />
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="审核状态" prop="auditStatus">
<el-select v-model="queryParams.auditStatus" placeholder="请选择审核状态" clearable style="width: 100%">
<el-option label="待审核" value="PENDING" />
<el-option label="已通过" value="APPROVED" />
<el-option label="已拒绝" value="REJECTED" />
<el-option label="已退回" value="RETURNED" />
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="24" class="search-btn-container">
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-col>
</el-row>
</el-form>
</el-card>
</div>
</transition>
<!-- 任务列表 -->
<el-card shadow="never" class="table-card">
<template #header>
<div class="card-header">
<span class="card-title">我的审核任务</span>
<div class="card-extra">
<el-button-group>
<el-button
:type="activeTab === 'pending' ? 'primary' : ''"
@click="switchTab('pending')"
icon="Clock"
>
待审核 ({{ taskStats.pendingCount }})
</el-button>
<el-button
:type="activeTab === 'processed' ? 'primary' : ''"
@click="switchTab('processed')"
icon="CircleCheck"
>
已处理 ({{ taskStats.processedCount }})
</el-button>
</el-button-group>
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
</div>
</div>
</template>
<!-- 表格区域 -->
<div class="table-container">
<el-table
v-loading="loading"
border
:data="taskList"
@selection-change="handleSelectionChange"
style="width: 100%"
class="scroll-table"
>
<el-table-column type="selection" width="55" align="center" fixed="left" v-if="activeTab === 'pending'" />
<el-table-column label="申请编号" align="center" prop="orderNo" width="140" show-overflow-tooltip fixed="left" />
<el-table-column label="申请人" align="center" prop="applicantName" width="120" show-overflow-tooltip />
<el-table-column label="奖学金类型" align="center" prop="scholarshipType" min-width="200" show-overflow-tooltip />
<el-table-column label="审核级别" align="center" prop="auditLevel" width="120">
<template #default="scope">
<el-tag v-if="scope.row.auditLevel === 'DEPARTMENT'" type="primary">院系审核</el-tag>
<el-tag v-else-if="scope.row.auditLevel === 'SCHOOL'" type="success">学校审核</el-tag>
<el-tag v-else-if="scope.row.auditLevel === 'FINANCE'" type="warning">财务审核</el-tag>
<el-tag v-else-if="scope.row.auditLevel === 'FINAL'" type="danger">最终审核</el-tag>
</template>
</el-table-column>
<el-table-column label="状态" align="center" prop="auditStatus" width="120">
<template #default="scope">
<el-tag v-if="scope.row.auditStatus === 'PENDING'" type="warning">
<el-icon><Clock /></el-icon>
待审核
</el-tag>
<el-tag v-else-if="scope.row.auditStatus === 'APPROVED'" type="success">
<el-icon><CircleCheck /></el-icon>
已通过
</el-tag>
<el-tag v-else-if="scope.row.auditStatus === 'REJECTED'" type="danger">
<el-icon><CircleClose /></el-icon>
已拒绝
</el-tag>
<el-tag v-else-if="scope.row.auditStatus === 'RETURNED'" type="info">
<el-icon><RefreshLeft /></el-icon>
已退回
</el-tag>
</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" prop="reviewTime" width="180" v-if="activeTab === 'processed'">
<template #default="scope">
<span>{{ parseTime(scope.row.reviewTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
</template>
</el-table-column>
<el-table-column label="等待时长" align="center" width="120" v-if="activeTab === 'pending'">
<template #default="scope">
<span class="waiting-time" :class="getWaitingTimeClass(scope.row.createTime)">
{{ calculateWaitingTime(scope.row.createTime) }}
</span>
</template>
</el-table-column>
<el-table-column label="紧急程度" align="center" width="120" v-if="activeTab === 'pending'">
<template #default="scope">
<el-tag :type="getUrgencyType(scope.row.createTime)">
{{ getUrgencyText(scope.row.createTime) }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="200" fixed="right">
<template #default="scope">
<div class="action-buttons">
<el-tooltip content="查看详情" placement="top">
<el-button link type="primary" icon="View" @click="handleView(scope.row)" v-hasPermi="['jcbt:review:query']">详情</el-button>
</el-tooltip>
<el-tooltip content="执行审核" placement="top" v-if="scope.row.auditStatus === 'PENDING'">
<el-button link type="success" icon="Check" @click="handleReview(scope.row)" v-hasPermi="['jcbt:review:approve']">审核</el-button>
</el-tooltip>
<el-tooltip content="审核历史" placement="top">
<el-button link type="info" icon="History" @click="handleHistory(scope.row)">历史</el-button>
</el-tooltip>
</div>
</template>
</el-table-column>
</el-table>
</div>
<!-- 批量操作工具栏 -->
<div v-if="activeTab === 'pending' && selectedIds.length > 0" class="batch-toolbar">
<div class="batch-info">
<span>已选择 {{ selectedIds.length }} 条记录</span>
</div>
<div class="batch-actions">
<el-button type="success" icon="Check" @click="handleBatchApprove" v-hasPermi="['jcbt:review:approve']">
批量通过
</el-button>
<el-button type="danger" icon="Close" @click="handleBatchReject" v-hasPermi="['jcbt:review:approve']">
批量拒绝
</el-button>
</div>
</div>
<!-- 分页 -->
<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>
<!-- 审核操作对话框 -->
<ReviewActionDialog
v-model:visible="reviewDialog.visible"
:review-data="reviewDialog.data"
@confirm="handleReviewConfirm"
/>
<!-- 批量操作对话框 -->
<el-dialog :title="batchDialog.title" v-model="batchDialog.visible" width="600px" append-to-body>
<el-form ref="batchFormRef" :model="batchForm" label-width="120px">
<el-form-item label="审核意见" prop="reviewComment">
<el-input
v-model="batchForm.reviewComment"
type="textarea"
:rows="4"
:placeholder="batchDialog.placeholder"
show-word-limit
maxlength="500"
/>
</el-form-item>
<el-form-item>
<el-alert
:title="batchDialog.alertText"
:type="batchDialog.alertType"
:closable="false"
/>
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="batchDialog.visible = false">取 消</el-button>
<el-button type="primary" @click="confirmBatchAction">确 定</el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup name="MyReviewTasks" lang="ts">
import {
getMyReviewTasks,
getPendingReviews,
getScholarshipReview,
batchApprove,
type ScholarshipReviewQuery,
type ScholarshipReviewVo
} from '@/api/jcbt/scholarshipReview';
import ReviewActionDialog from './components/ReviewActionDialog.vue';
// 基础状态
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const loading = ref(true);
const showSearch = ref(true);
const total = ref(0);
const taskList = ref<ScholarshipReviewVo[]>([]);
const selectedIds = ref<Array<string | number>>([]);
const activeTab = ref<'pending' | 'processed'>('pending');
// 任务统计
const taskStats = ref({
pendingCount: 0,
processedCount: 0,
totalCount: 0,
efficiency: 0
});
// 查询参数
const queryParams = ref<ScholarshipReviewQuery>({
pageNum: 1,
pageSize: 10,
orderNo: '',
applicantName: '',
scholarshipType: '',
auditStatus: '',
reviewerId: undefined,
reviewerName: ''
});
// 审核对话框
const reviewDialog = reactive({
visible: false,
data: {} as ScholarshipReviewVo
});
// 批量操作对话框
const batchDialog = reactive({
visible: false,
title: '',
placeholder: '',
alertText: '',
alertType: 'info' as any,
action: '' as 'approve' | 'reject'
});
const batchForm = ref({
reviewComment: ''
});
// 表单引用
const queryFormRef = ref<ElFormInstance>();
const batchFormRef = ref<ElFormInstance>();
/** 切换标签页 */
const switchTab = (tab: 'pending' | 'processed') => {
activeTab.value = tab;
queryParams.value.pageNum = 1;
// 根据标签页设置状态过滤
if (tab === 'pending') {
queryParams.value.auditStatus = 'PENDING';
} else {
queryParams.value.auditStatus = '';
}
getList();
};
/** 查询任务列表 */
const getList = async () => {
loading.value = true;
try {
let res;
if (activeTab.value === 'pending') {
res = await getPendingReviews(queryParams.value);
} else {
res = await getMyReviewTasks(queryParams.value);
}
taskList.value = res.rows;
total.value = res.total;
// 更新统计数据
updateTaskStats();
} finally {
loading.value = false;
}
};
/** 更新任务统计 */
const updateTaskStats = async () => {
try {
// 获取待审核数量
const pendingRes = await getPendingReviews({ pageNum: 1, pageSize: 1 });
taskStats.value.pendingCount = pendingRes.total;
// 获取已处理数量
const processedQuery = { ...queryParams.value, auditStatus: '' };
const processedRes = await getMyReviewTasks(processedQuery);
const processedCount = processedRes.rows.filter(item => item.auditStatus !== 'PENDING').length;
taskStats.value.processedCount = processedCount;
// 计算总数和效率
taskStats.value.totalCount = taskStats.value.pendingCount + taskStats.value.processedCount;
taskStats.value.efficiency = taskStats.value.totalCount > 0
? Math.round((taskStats.value.processedCount / taskStats.value.totalCount) * 100)
: 0;
} catch (error) {
console.error('更新统计失败:', error);
}
};
/** 计算等待时长 */
const calculateWaitingTime = (createTime: string): string => {
if (!createTime) return '-';
try {
const now = new Date().getTime();
const create = new Date(createTime).getTime();
const diff = now - create;
const days = Math.floor(diff / (1000 * 60 * 60 * 24));
const hours = Math.floor((diff % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
if (days > 0) {
return `${days}天${hours}小时`;
} else {
return `${hours}小时`;
}
} catch (error) {
return '-';
}
};
/** 获取等待时长样式类 */
const getWaitingTimeClass = (createTime: string): string => {
if (!createTime) return '';
const now = new Date().getTime();
const create = new Date(createTime).getTime();
const hours = (now - create) / (1000 * 60 * 60);
if (hours >= 72) return 'urgent'; // 3天以上
if (hours >= 48) return 'warning'; // 2天以上
if (hours >= 24) return 'attention'; // 1天以上
return 'normal';
};
/** 获取紧急程度类型 */
const getUrgencyType = (createTime: string): string => {
if (!createTime) return '';
const now = new Date().getTime();
const create = new Date(createTime).getTime();
const hours = (now - create) / (1000 * 60 * 60);
if (hours >= 72) return 'danger';
if (hours >= 48) return 'warning';
if (hours >= 24) return 'info';
return '';
};
/** 获取紧急程度文本 */
const getUrgencyText = (createTime: string): string => {
if (!createTime) return '';
const now = new Date().getTime();
const create = new Date(createTime).getTime();
const hours = (now - create) / (1000 * 60 * 60);
if (hours >= 72) return '紧急';
if (hours >= 48) return '较急';
if (hours >= 24) return '一般';
return '正常';
};
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.value.pageNum = 1;
getList();
};
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value?.resetFields();
queryParams.value.pageNum = 1;
// 保持当前标签页的状态过滤
if (activeTab.value === 'pending') {
queryParams.value.auditStatus = 'PENDING';
}
handleQuery();
};
/** 多选框选中数据 */
const handleSelectionChange = (selection: ScholarshipReviewVo[]) => {
selectedIds.value = selection.map(item => item.id);
};
/** 查看详情 */
const handleView = async (row: ScholarshipReviewVo) => {
try {
const res = await getScholarshipReview(row.id);
// 这里可以打开详情对话框或跳转到详情页面
proxy?.$modal.msgInfo('查看详情功能待实现');
} catch (error) {
console.error('获取详情失败:', error);
}
};
/** 审核操作 */
const handleReview = (row: ScholarshipReviewVo) => {
reviewDialog.data = { ...row };
reviewDialog.visible = true;
};
/** 审核确认回调 */
const handleReviewConfirm = () => {
reviewDialog.visible = false;
getList();
updateTaskStats();
};
/** 查看审核历史 */
const handleHistory = (row: ScholarshipReviewVo) => {
// 跳转到审核历史页面,并传入申请编号作为筛选条件
proxy?.$router.push({
path: '/jcbt/scholarship/review/history',
query: { orderNo: row.orderNo }
});
};
/** 批量通过 */
const handleBatchApprove = () => {
if (selectedIds.value.length === 0) {
proxy?.$modal.msgError('请选择要操作的记录');
return;
}
batchDialog.title = '批量审核通过';
batchDialog.placeholder = '请输入通过审核的意见...';
batchDialog.alertText = `确认批量通过所选的 ${selectedIds.value.length} 条审核记录?`;
batchDialog.alertType = 'success';
batchDialog.action = 'approve';
batchForm.value.reviewComment = '';
batchDialog.visible = true;
};
/** 批量拒绝 */
const handleBatchReject = () => {
if (selectedIds.value.length === 0) {
proxy?.$modal.msgError('请选择要操作的记录');
return;
}
batchDialog.title = '批量审核拒绝';
batchDialog.placeholder = '请输入拒绝审核的理由...';
batchDialog.alertText = `确认批量拒绝所选的 ${selectedIds.value.length} 条审核记录?`;
batchDialog.alertType = 'error';
batchDialog.action = 'reject';
batchForm.value.reviewComment = '';
batchDialog.visible = true;
};
/** 确认批量操作 */
const confirmBatchAction = async () => {
if (!batchForm.value.reviewComment.trim()) {
proxy?.$modal.msgError('请输入审核意见');
return;
}
try {
if (batchDialog.action === 'approve') {
await batchApprove(selectedIds.value as string[], batchForm.value.reviewComment);
proxy?.$modal.msgSuccess('批量通过成功');
} else {
// 这里可以添加批量拒绝的API调用
proxy?.$modal.msgSuccess('批量拒绝成功');
}
batchDialog.visible = false;
selectedIds.value = [];
getList();
updateTaskStats();
} catch (error) {
console.error('批量操作失败:', error);
proxy?.$modal.msgError('批量操作失败');
}
};
onMounted(() => {
// 默认显示待审核任务
queryParams.value.auditStatus = 'PENDING';
getList();
updateTaskStats();
});
</script>
<style lang="scss" scoped>
.page-container {
padding: 8px;
.search-btn-container {
display: flex;
justify-content: flex-end;
margin-top: 10px;
.el-form-item {
margin-bottom: 0 !important;
}
}
.table-card {
background-color: #fff;
border-radius: 8px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
}
.pagination-container {
text-align: right;
padding-top: 20px;
}
}
/* 卡片头部样式 */
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
.card-title {
font-size: 16px;
font-weight: 600;
color: #303133;
}
.card-extra {
display: flex;
gap: 12px;
align-items: center;
}
}
/* 统计卡片样式 */
.stat-card {
border-radius: 8px;
transition: all 0.3s ease;
height: 90px;
&:hover {
transform: translateY(-2px);
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.1);
}
.stat-content {
display: flex;
align-items: center;
padding: 5px 12px;
height: 100%;
box-sizing: border-box;
.stat-icon {
font-size: 24px;
margin-right: 12px;
opacity: 0.8;
}
.stat-info {
flex: 1;
.stat-value {
font-size: 20px;
font-weight: bold;
line-height: 1;
margin-bottom: 3px;
}
.stat-label {
font-size: 13px;
opacity: 0.7;
line-height: 1;
}
}
}
&.stat-card-primary {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
}
&.stat-card-success {
background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
color: white;
}
&.stat-card-warning {
background: linear-gradient(135deg, #ff9a56 0%, #ffad56 100%);
color: white;
}
&.stat-card-info {
background: linear-gradient(135deg, #a8edea 0%, #fed6e3 100%);
color: #333;
}
}
/* 等待时长样式 */
.waiting-time {
font-weight: 600;
&.normal {
color: #67c23a;
}
&.attention {
color: #e6a23c;
}
&.warning {
color: #f56c6c;
}
&.urgent {
color: #f56c6c;
animation: blink 1s infinite;
}
}
@keyframes blink {
0%, 50% { opacity: 1; }
51%, 100% { opacity: 0.5; }
}
/* 批量操作工具栏 */
.batch-toolbar {
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px 16px;
background-color: #f8f9fa;
border-radius: 6px;
margin-top: 16px;
.batch-info {
font-size: 14px;
color: #606266;
}
.batch-actions {
display: flex;
gap: 8px;
}
}
/* 表格样式 */
.scroll-table {
:deep(.el-table__header-wrapper) {
.el-table__header {
th {
background-color: #f8f9fa;
color: #333;
font-weight: 600;
}
}
}
:deep(.el-table__body-wrapper) {
.el-table__row {
&:hover {
background-color: #f5f7fa;
}
}
}
}
/* 操作按钮样式 */
.action-buttons {
display: flex;
gap: 8px;
justify-content: center;
align-items: center;
white-space: nowrap;
}
.dialog-footer {
display: flex;
justify-content: flex-end;
gap: 10px;
}
</style>
File diff suppressed because it is too large. Load diff