wuzhen update
This commit is contained in:
commit
0037cb4e7d
1503 files changed
+74822
-196654
No files matched your search
+3
-2
@@ -8,8 +8,8 @@
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite serve --mode development",
|
||||
"build:prod": "vite build --mode production",
|
||||
"build:dev": "vite build --mode development",
|
||||
"build:prod": "node --max-old-space-size=8192 node_modules/vite/bin/vite.js build --mode production",
|
||||
"build:dev": "node --max-old-space-size=8192 node_modules/vite/bin/vite.js build --mode development",
|
||||
"preview": "vite preview",
|
||||
"lint:eslint": "eslint",
|
||||
"lint:eslint:fix": "eslint --fix",
|
||||
@@ -42,6 +42,7 @@
|
||||
"js-cookie": "3.0.5",
|
||||
"jsencrypt": "3.3.2",
|
||||
"jspdf": "^3.0.1",
|
||||
"jspdf-font": "^1.0.7",
|
||||
"nprogress": "0.2.0",
|
||||
"pinia": "3.0.2",
|
||||
"screenfull": "6.0.2",
|
||||
|
||||
@@ -6,6 +6,7 @@ export interface ScholarshipApplicationVO {
|
||||
id?: string | number;
|
||||
/** 批次ID */
|
||||
batchId?: string | number;
|
||||
<<<<<<< HEAD
|
||||
/** 批次名称 */
|
||||
batchName?: string;
|
||||
/** 奖学金类型ID */
|
||||
@@ -16,6 +17,12 @@ export interface ScholarshipApplicationVO {
|
||||
applicationNo?: string;
|
||||
/** 学生学号 */
|
||||
studentNo?: string;
|
||||
=======
|
||||
/** 奖学金类型ID */
|
||||
scholarshipTypeId?: string | number;
|
||||
/** 学生学号 */
|
||||
studentCode?: string;
|
||||
>>>>>>> 4b9dbe40fb94a83d379f7d16211dec6d78c23755
|
||||
/** 学生姓名 */
|
||||
studentName?: string;
|
||||
/** 学生ID */
|
||||
@@ -24,8 +31,11 @@ export interface ScholarshipApplicationVO {
|
||||
deptId?: string | number;
|
||||
/** 学院名称 */
|
||||
deptName?: string;
|
||||
<<<<<<< HEAD
|
||||
/** 专业ID */
|
||||
majorId?: string | number;
|
||||
=======
|
||||
>>>>>>> 4b9dbe40fb94a83d379f7d16211dec6d78c23755
|
||||
/** 班级ID */
|
||||
classId?: string | number;
|
||||
/** 班级名称 */
|
||||
@@ -42,6 +52,7 @@ export interface ScholarshipApplicationVO {
|
||||
applicationReason?: string;
|
||||
/** 申请说明 */
|
||||
applicationDescription?: string;
|
||||
<<<<<<< HEAD
|
||||
/** 系统计算分数 */
|
||||
calculatedScore?: number;
|
||||
/** 最终评定分数 */
|
||||
@@ -62,6 +73,12 @@ export interface ScholarshipApplicationVO {
|
||||
updateBy?: string | number;
|
||||
/** 更新时间 */
|
||||
updateTime?: string;
|
||||
=======
|
||||
/** 状态 */
|
||||
status?: string;
|
||||
/** 创建时间 */
|
||||
createTime?: string;
|
||||
>>>>>>> 4b9dbe40fb94a83d379f7d16211dec6d78c23755
|
||||
}
|
||||
|
||||
export interface ScholarshipApplicationForm {
|
||||
@@ -93,7 +110,11 @@ export interface ScholarshipApplicationQuery extends PageQuery {
|
||||
/** 奖学金类型ID */
|
||||
scholarshipTypeId?: string | number;
|
||||
/** 学生学号 */
|
||||
<<<<<<< HEAD
|
||||
studentNo?: string;
|
||||
=======
|
||||
studentCode?: string;
|
||||
>>>>>>> 4b9dbe40fb94a83d379f7d16211dec6d78c23755
|
||||
/** 学生姓名 */
|
||||
studentName?: string;
|
||||
/** 申请人ID */
|
||||
@@ -202,12 +223,16 @@ export function confirmClassSelection(teacherId: string | number, classId: strin
|
||||
}
|
||||
|
||||
// 班主任批量申请提交
|
||||
<<<<<<< HEAD
|
||||
export function batchSubmitByTeacher(
|
||||
batchId: string | number,
|
||||
scholarshipTypeId: string | number,
|
||||
classId: string | number,
|
||||
studentApplications: any[]
|
||||
) {
|
||||
=======
|
||||
export function batchSubmitByTeacher(batchId: string | number, scholarshipTypeId: string | number, classId: string | number, studentApplications: any[]) {
|
||||
>>>>>>> 4b9dbe40fb94a83d379f7d16211dec6d78c23755
|
||||
return request({
|
||||
url: '/jcbt/scholarshipApplication/batch',
|
||||
method: 'post',
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
import request from '@/utils/request';
|
||||
|
||||
// 应聘申请表单类型
|
||||
export interface ApplicationForm {
|
||||
jobId: string | number;
|
||||
applicantName: string;
|
||||
gender: string;
|
||||
contactPhone: string;
|
||||
contactEmail: string;
|
||||
age: number;
|
||||
education: string;
|
||||
educationBackground: string;
|
||||
experience: string;
|
||||
selfDescription: string;
|
||||
remark?: string;
|
||||
}
|
||||
|
||||
// 提交应聘申请
|
||||
export function submitJobApplication(data: ApplicationForm) {
|
||||
return request({
|
||||
url: '/rs/application/submit',
|
||||
method: 'post',
|
||||
data: data
|
||||
});
|
||||
}
|
||||
|
||||
// 增加岗位浏览次数
|
||||
export function incrementJobViewCount(jobId: string | number) {
|
||||
return request({
|
||||
url: `/rs/job/view/${jobId}`,
|
||||
method: 'post'
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,227 @@
|
||||
import request from '@/utils/request';
|
||||
|
||||
// 职位查询参数类型
|
||||
export interface JobQuery {
|
||||
pageNum: number;
|
||||
pageSize: number;
|
||||
title?: string;
|
||||
department?: string;
|
||||
isActive?: string;
|
||||
params?: Record<string, any>;
|
||||
}
|
||||
|
||||
// 职位数据类型
|
||||
export interface JobVO {
|
||||
jobId: number | string;
|
||||
title: string;
|
||||
department: string;
|
||||
salaryRange: string;
|
||||
location: string;
|
||||
description: string;
|
||||
requirements: string;
|
||||
isActive: string;
|
||||
applicantCount: number;
|
||||
viewCount: number;
|
||||
publishTime: string; // 发布时间
|
||||
createTime: string;
|
||||
updateTime: string;
|
||||
createBy: string;
|
||||
updateBy: string;
|
||||
remark: string;
|
||||
}
|
||||
|
||||
// 职位表单类型
|
||||
export interface JobForm {
|
||||
jobId?: number | string;
|
||||
title: string;
|
||||
department: string;
|
||||
salaryRange: string;
|
||||
location: string;
|
||||
description: string;
|
||||
requirements: string;
|
||||
isActive: string;
|
||||
publishTime?: string; // 发布时间
|
||||
remark?: string;
|
||||
}
|
||||
|
||||
// 申请记录类型
|
||||
export interface ApplicationVO {
|
||||
applicationId: number | string;
|
||||
jobId: number | string;
|
||||
jobTitle: string;
|
||||
applicantId: number | string;
|
||||
applicantName: string;
|
||||
gender: string; // 性别
|
||||
age: number | string; // 年龄
|
||||
status: string;
|
||||
applyTime: string;
|
||||
resumePath: string; // 简历路径
|
||||
selfDescription: string; // 自我介绍
|
||||
attachmentPath?: string; // 附件路径
|
||||
attachmentName?: string; // 附件名称
|
||||
hrNotes: string; // HR备注
|
||||
contactPhone: string; // 联系电话
|
||||
contactEmail: string; // 联系邮箱
|
||||
education: string; // 学历
|
||||
educationBackground: string; // 教育背景
|
||||
experience: string; // 工作经验
|
||||
remark: string;
|
||||
createBy: string;
|
||||
createTime: string;
|
||||
updateBy: string;
|
||||
updateTime: string;
|
||||
}
|
||||
|
||||
// 查询职位列表
|
||||
export function listJob(query: JobQuery) {
|
||||
return request({
|
||||
url: '/rs/job/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
});
|
||||
}
|
||||
|
||||
// 查询公开职位列表(用于招聘门户)
|
||||
export function listPublicJob(query: JobQuery) {
|
||||
return request({
|
||||
url: '/rs/job/public/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
});
|
||||
}
|
||||
|
||||
// 查询职位详细
|
||||
export function getJob(jobId: string | number) {
|
||||
return request({
|
||||
url: `/rs/job/${jobId}`,
|
||||
method: 'get'
|
||||
});
|
||||
}
|
||||
|
||||
// 查询公开职位详细(用于招聘门户)
|
||||
export function getPublicJob(jobId: string | number) {
|
||||
return request({
|
||||
url: `/rs/job/public/${jobId}`,
|
||||
method: 'get'
|
||||
});
|
||||
}
|
||||
|
||||
// 新增职位
|
||||
export function addJob(data: JobForm) {
|
||||
return request({
|
||||
url: '/rs/job',
|
||||
method: 'post',
|
||||
data: data
|
||||
});
|
||||
}
|
||||
|
||||
// 修改职位
|
||||
export function updateJob(data: JobForm) {
|
||||
return request({
|
||||
url: '/rs/job',
|
||||
method: 'put',
|
||||
data: data
|
||||
});
|
||||
}
|
||||
|
||||
// 删除职位
|
||||
export function delJob(jobId: string | number | Array<string | number>) {
|
||||
return request({
|
||||
url: `/rs/job/${typeof jobId === 'object' ? jobId.join(',') : jobId}`,
|
||||
method: 'delete'
|
||||
});
|
||||
}
|
||||
|
||||
// 切换职位状态
|
||||
export function toggleJobStatus(jobId: string | number) {
|
||||
return request({
|
||||
url: `/rs/job/toggle/${jobId}`,
|
||||
method: 'put'
|
||||
});
|
||||
}
|
||||
|
||||
// 导出职位
|
||||
export function exportJob(query: JobQuery) {
|
||||
return request({
|
||||
url: '/rs/job/export',
|
||||
method: 'get',
|
||||
params: query,
|
||||
responseType: 'blob'
|
||||
});
|
||||
}
|
||||
|
||||
// 查询职位申请记录列表
|
||||
export function listApplications(jobId: string | number, query: any) {
|
||||
return request({
|
||||
url: `/rs/application/list/${jobId}`,
|
||||
method: 'get',
|
||||
params: query
|
||||
});
|
||||
}
|
||||
|
||||
// 查询所有申请记录列表
|
||||
export function listAllApplications(query: any) {
|
||||
return request({
|
||||
url: '/rs/application/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
});
|
||||
}
|
||||
|
||||
// 更新申请状态
|
||||
export function updateApplicationStatus(applicationId: string | number, status: string) {
|
||||
return request({
|
||||
url: `/rs/application/status/${applicationId}/${status}`,
|
||||
method: 'put'
|
||||
});
|
||||
}
|
||||
|
||||
// 更新应聘者信息(包含状态和HR备注)
|
||||
export function updateApplicationInfo(applicationId: string | number, data: { status: string; hrNotes?: string }) {
|
||||
return request({
|
||||
url: `/rs/application/update/${applicationId}`,
|
||||
method: 'put',
|
||||
data: data
|
||||
});
|
||||
}
|
||||
|
||||
// 获取申请详情
|
||||
export function getApplication(applicationId: string | number) {
|
||||
return request({
|
||||
url: `/rs/application/${applicationId}`,
|
||||
method: 'get'
|
||||
});
|
||||
}
|
||||
|
||||
// 导出申请记录
|
||||
export function exportApplication(query: any) {
|
||||
return request({
|
||||
url: '/rs/application/export',
|
||||
method: 'get',
|
||||
params: query,
|
||||
responseType: 'blob'
|
||||
});
|
||||
}
|
||||
|
||||
// 导出简历文件
|
||||
export function exportResumePdf(applicationId: string | number) {
|
||||
return request({
|
||||
url: `/rs/application/exportPdf/${applicationId}`,
|
||||
method: 'get',
|
||||
responseType: 'blob'
|
||||
});
|
||||
}
|
||||
|
||||
// 上传附件
|
||||
export function uploadAttachment(applicationId: string | number, file: File) {
|
||||
const formData = new FormData();
|
||||
formData.append('file', file);
|
||||
return request({
|
||||
url: `/rs/application/attachment/${applicationId}`,
|
||||
method: 'post',
|
||||
data: formData,
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data'
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,522 @@
|
||||
import request from '@/utils/request';
|
||||
|
||||
// 出国申请查询参数类型
|
||||
export interface OverseasApplicationQuery {
|
||||
pageNum: number;
|
||||
pageSize: number;
|
||||
applicantName?: string;
|
||||
department?: string;
|
||||
purpose?: string;
|
||||
destination?: string;
|
||||
status?: string;
|
||||
approvalLevel?: string;
|
||||
currentApprover?: string;
|
||||
applicantId?: number;
|
||||
budgetMin?: number;
|
||||
budgetMax?: number;
|
||||
applyTimeBegin?: string;
|
||||
applyTimeEnd?: string;
|
||||
startDateBegin?: string;
|
||||
startDateEnd?: string;
|
||||
params?: Record<string, any>;
|
||||
}
|
||||
|
||||
// 出国申请数据类型
|
||||
export interface OverseasApplicationVO {
|
||||
applicationId: number | string;
|
||||
applicantId: number;
|
||||
applicantName: string;
|
||||
department: string;
|
||||
contactPhone: string;
|
||||
purpose: string;
|
||||
purposeDetail: string;
|
||||
destination: string;
|
||||
destinationCity: string;
|
||||
startDate: string;
|
||||
endDate: string;
|
||||
duration: number;
|
||||
budget: number;
|
||||
invitation: string;
|
||||
itinerary: string;
|
||||
accommodationPlan: string;
|
||||
transportationPlan: string;
|
||||
status: string;
|
||||
currentApprover: string;
|
||||
approvalLevel: string;
|
||||
applyTime: string;
|
||||
approvalTime: string;
|
||||
adminRemark: string;
|
||||
applicantRemark: string;
|
||||
createTime: string;
|
||||
updateTime: string;
|
||||
createBy: string;
|
||||
updateBy: string;
|
||||
// 显示字段
|
||||
statusText: string;
|
||||
purposeText: string;
|
||||
approvalLevelText: string;
|
||||
}
|
||||
|
||||
// 出国申请表单类型
|
||||
export interface OverseasApplicationForm {
|
||||
applicationId?: number | string;
|
||||
applicantId?: number;
|
||||
applicantName?: string;
|
||||
department?: string; // 自动匹配,前端不需要输入
|
||||
contactPhone?: string; // 联系电话
|
||||
purpose: string;
|
||||
purposeDetail: string;
|
||||
destination: string;
|
||||
destinationCity?: string; // 目的地城市
|
||||
startDate: string;
|
||||
endDate: string;
|
||||
duration?: number; // 自动计算
|
||||
budget: number;
|
||||
invitation?: string;
|
||||
itinerary: string;
|
||||
accommodationPlan?: string;
|
||||
transportationPlan?: string;
|
||||
applicantRemark?: string;
|
||||
}
|
||||
|
||||
// 审批请求类型
|
||||
export interface ApprovalRequest {
|
||||
applicationId: number | string;
|
||||
approvalStatus: string;
|
||||
approvalOpinion?: string;
|
||||
}
|
||||
|
||||
// 出国记录查询参数类型
|
||||
export interface OverseasRecordQuery {
|
||||
pageNum: number;
|
||||
pageSize: number;
|
||||
applicantName?: string;
|
||||
department?: string;
|
||||
purpose?: string;
|
||||
destination?: string;
|
||||
completionStatus?: string;
|
||||
applicantId?: number;
|
||||
applicationId?: number;
|
||||
actualStartDateBegin?: string;
|
||||
actualStartDateEnd?: string;
|
||||
actualEndDateBegin?: string;
|
||||
actualEndDateEnd?: string;
|
||||
params?: Record<string, any>;
|
||||
}
|
||||
|
||||
// 出国记录数据类型
|
||||
export interface OverseasRecordVO {
|
||||
recordId: number | string;
|
||||
applicationId: number;
|
||||
applicantId: number;
|
||||
applicantName: string;
|
||||
department: string;
|
||||
contactPhone: string;
|
||||
purpose: string;
|
||||
purposeDetail: string;
|
||||
destination: string;
|
||||
destinationCity: string;
|
||||
actualStartDate: string;
|
||||
actualEndDate: string;
|
||||
actualDuration: number;
|
||||
actualBudget: number;
|
||||
invitation: string;
|
||||
itinerary: string;
|
||||
accommodationPlan: string;
|
||||
transportationPlan: string;
|
||||
completionStatus: string;
|
||||
achievement: string;
|
||||
reportContent: string;
|
||||
certificatePath: string;
|
||||
createTime: string;
|
||||
updateTime: string;
|
||||
createBy: string;
|
||||
updateBy: string;
|
||||
// 显示字段
|
||||
purposeText: string;
|
||||
completionStatusText: string;
|
||||
}
|
||||
|
||||
// 出国记录表单类型
|
||||
export interface OverseasRecordForm {
|
||||
recordId?: number | string;
|
||||
applicationId: number;
|
||||
applicantId?: number;
|
||||
applicantName?: string;
|
||||
department?: string;
|
||||
position?: string;
|
||||
contactPhone?: string;
|
||||
contactEmail?: string;
|
||||
purpose?: string;
|
||||
purposeDetail?: string;
|
||||
destination?: string;
|
||||
destinationCity?: string;
|
||||
actualStartDate: string;
|
||||
actualEndDate: string;
|
||||
actualDuration?: number;
|
||||
actualBudget: number;
|
||||
completionStatus: string;
|
||||
achievement?: string;
|
||||
reportContent?: string;
|
||||
certificatePath?: string;
|
||||
}
|
||||
|
||||
// 完成状态更新请求类型
|
||||
export interface CompletionRequest {
|
||||
recordId: number | string;
|
||||
completionStatus: string;
|
||||
achievement?: string;
|
||||
reportContent?: string;
|
||||
}
|
||||
|
||||
// 出国审批记录查询参数类型
|
||||
export interface OverseasApprovalQuery {
|
||||
pageNum: number;
|
||||
pageSize: number;
|
||||
applicationId?: number;
|
||||
approverId?: number;
|
||||
approverName?: string;
|
||||
approvalLevel?: string;
|
||||
approvalStatus?: string;
|
||||
approvalTimeBegin?: string;
|
||||
approvalTimeEnd?: string;
|
||||
applicantName?: string;
|
||||
department?: string;
|
||||
destination?: string;
|
||||
params?: Record<string, any>;
|
||||
}
|
||||
|
||||
// 出国审批记录数据类型
|
||||
export interface OverseasApprovalVO {
|
||||
approvalId: number | string;
|
||||
applicationId: number;
|
||||
approverId: number;
|
||||
approverName: string;
|
||||
approvalLevel: string;
|
||||
approvalStatus: string;
|
||||
approvalOpinion: string;
|
||||
approvalTime: string;
|
||||
createTime: string;
|
||||
updateTime: string;
|
||||
createBy: string;
|
||||
updateBy: string;
|
||||
// ==================== 申请信息相关字段 ====================
|
||||
applicantId: number;
|
||||
applicantName: string;
|
||||
department: string;
|
||||
purpose: string;
|
||||
destination: string;
|
||||
startDate: string;
|
||||
endDate: string;
|
||||
duration: number;
|
||||
// 显示字段
|
||||
approvalLevelText: string;
|
||||
approvalStatusText: string;
|
||||
purposeText: string;
|
||||
}
|
||||
|
||||
// ==================== 出国申请相关API ====================
|
||||
|
||||
// 查询出国申请列表
|
||||
export function listOverseasApplication(query: OverseasApplicationQuery) {
|
||||
return request({
|
||||
url: '/rs/overseas/application/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
});
|
||||
}
|
||||
|
||||
// 查询我的出国申请列表
|
||||
export function listMyOverseasApplication(query: OverseasApplicationQuery) {
|
||||
return request({
|
||||
url: '/rs/overseas/application/my/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
});
|
||||
}
|
||||
|
||||
// 查询待审批的申请列表
|
||||
export function listPendingOverseasApplication(query: OverseasApplicationQuery) {
|
||||
return request({
|
||||
url: '/rs/overseas/application/pending/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
});
|
||||
}
|
||||
|
||||
// 根据状态查询申请列表
|
||||
export function listOverseasApplicationByStatus(status: string) {
|
||||
return request({
|
||||
url: `/rs/overseas/application/status/${status}`,
|
||||
method: 'get'
|
||||
});
|
||||
}
|
||||
|
||||
// 查询出国申请详细
|
||||
export function getOverseasApplication(applicationId: string | number) {
|
||||
return request({
|
||||
url: `/rs/overseas/application/${applicationId}`,
|
||||
method: 'get'
|
||||
});
|
||||
}
|
||||
|
||||
// 新增出国申请
|
||||
export function addOverseasApplication(data: OverseasApplicationForm) {
|
||||
return request({
|
||||
url: '/rs/overseas/application',
|
||||
method: 'post',
|
||||
data: data
|
||||
});
|
||||
}
|
||||
|
||||
// 修改出国申请
|
||||
export function updateOverseasApplication(data: OverseasApplicationForm) {
|
||||
return request({
|
||||
url: '/rs/overseas/application',
|
||||
method: 'put',
|
||||
data: data
|
||||
});
|
||||
}
|
||||
|
||||
// 提交出国申请
|
||||
export function submitOverseasApplication(applicationId: string | number) {
|
||||
return request({
|
||||
url: `/rs/overseas/application/submit/${applicationId}`,
|
||||
method: 'put'
|
||||
});
|
||||
}
|
||||
|
||||
// 重新提交被拒绝的出国申请
|
||||
export function resubmitOverseasApplication(applicationId: string | number, data: OverseasApplicationForm) {
|
||||
return request({
|
||||
url: `/rs/overseas/application/resubmit/${applicationId}`,
|
||||
method: 'put',
|
||||
data: data
|
||||
});
|
||||
}
|
||||
|
||||
// 取消出国申请
|
||||
export function cancelOverseasApplication(applicationId: string | number, reason?: string) {
|
||||
return request({
|
||||
url: `/rs/overseas/application/cancel/${applicationId}`,
|
||||
method: 'put',
|
||||
params: { reason }
|
||||
});
|
||||
}
|
||||
|
||||
// 审批出国申请
|
||||
export function approveOverseasApplication(data: ApprovalRequest) {
|
||||
return request({
|
||||
url: '/rs/overseas/application/approve',
|
||||
method: 'post',
|
||||
data: data
|
||||
});
|
||||
}
|
||||
|
||||
// 删除出国申请
|
||||
export function delOverseasApplication(applicationId: string | number | Array<string | number>) {
|
||||
return request({
|
||||
url: `/rs/overseas/application/${typeof applicationId === 'object' ? applicationId.join(',') : applicationId}`,
|
||||
method: 'delete'
|
||||
});
|
||||
}
|
||||
|
||||
// 导出出国申请
|
||||
export function exportOverseasApplication(query: OverseasApplicationQuery) {
|
||||
return request({
|
||||
url: '/rs/overseas/application/export',
|
||||
method: 'post',
|
||||
data: query
|
||||
});
|
||||
}
|
||||
|
||||
// ==================== 出国记录相关API ====================
|
||||
|
||||
// 查询出国记录列表
|
||||
export function listOverseasRecord(query: OverseasRecordQuery) {
|
||||
return request({
|
||||
url: '/rs/overseas/record/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
});
|
||||
}
|
||||
|
||||
// 查询我的出国记录列表
|
||||
export function listMyOverseasRecord(query: OverseasRecordQuery) {
|
||||
return request({
|
||||
url: '/rs/overseas/record/my/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
});
|
||||
}
|
||||
|
||||
// 根据申请ID查询出国记录
|
||||
export function getOverseasRecordByApplication(applicationId: string | number) {
|
||||
return request({
|
||||
url: `/rs/overseas/record/application/${applicationId}`,
|
||||
method: 'get'
|
||||
});
|
||||
}
|
||||
|
||||
// 查询出国记录详细
|
||||
export function getOverseasRecord(recordId: string | number) {
|
||||
return request({
|
||||
url: `/rs/overseas/record/${recordId}`,
|
||||
method: 'get'
|
||||
});
|
||||
}
|
||||
|
||||
// 新增出国记录
|
||||
export function addOverseasRecord(data: OverseasRecordForm) {
|
||||
return request({
|
||||
url: '/rs/overseas/record',
|
||||
method: 'post',
|
||||
data: data
|
||||
});
|
||||
}
|
||||
|
||||
// 修改出国记录
|
||||
export function updateOverseasRecord(data: OverseasRecordForm) {
|
||||
return request({
|
||||
url: '/rs/overseas/record',
|
||||
method: 'put',
|
||||
data: data
|
||||
});
|
||||
}
|
||||
|
||||
// 基于申请信息创建出国记录
|
||||
export function createOverseasRecordFromApplication(applicationId: string | number) {
|
||||
return request({
|
||||
url: `/rs/overseas/record/create/${applicationId}`,
|
||||
method: 'post'
|
||||
});
|
||||
}
|
||||
|
||||
// 更新出国记录完成状态
|
||||
export function updateOverseasRecordCompletion(data: CompletionRequest) {
|
||||
return request({
|
||||
url: '/rs/overseas/record/complete',
|
||||
method: 'put',
|
||||
data: data
|
||||
});
|
||||
}
|
||||
|
||||
// 删除出国记录
|
||||
export function delOverseasRecord(recordId: string | number | Array<string | number>) {
|
||||
return request({
|
||||
url: `/rs/overseas/record/${typeof recordId === 'object' ? recordId.join(',') : recordId}`,
|
||||
method: 'delete'
|
||||
});
|
||||
}
|
||||
|
||||
// 导出出国记录
|
||||
export function exportOverseasRecord(query: OverseasRecordQuery) {
|
||||
return request({
|
||||
url: '/rs/overseas/record/export',
|
||||
method: 'post',
|
||||
data: query
|
||||
});
|
||||
}
|
||||
|
||||
// 初始化出国记录测试数据(临时接口)
|
||||
export function initOverseasRecordTestData() {
|
||||
return request({
|
||||
url: '/rs/overseas/record/initTestData',
|
||||
method: 'post'
|
||||
});
|
||||
}
|
||||
|
||||
// ==================== 出国审批记录相关API ====================
|
||||
|
||||
// 查询出国审批记录列表
|
||||
export function listOverseasApproval(query: OverseasApprovalQuery) {
|
||||
return request({
|
||||
url: '/rs/overseas/approval/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
});
|
||||
}
|
||||
|
||||
// 查询我的审批记录列表
|
||||
export function listMyOverseasApproval(query: OverseasApprovalQuery) {
|
||||
return request({
|
||||
url: '/rs/overseas/approval/my/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
});
|
||||
}
|
||||
|
||||
// 根据申请ID查询审批记录列表
|
||||
export function getOverseasApprovalByApplication(applicationId: string | number) {
|
||||
return request({
|
||||
url: `/rs/overseas/approval/application/${applicationId}`,
|
||||
method: 'get'
|
||||
});
|
||||
}
|
||||
|
||||
// 查询出国审批记录详细
|
||||
export function getOverseasApproval(approvalId: string | number) {
|
||||
return request({
|
||||
url: `/rs/overseas/approval/${approvalId}`,
|
||||
method: 'get'
|
||||
});
|
||||
}
|
||||
|
||||
// 导出出国审批记录
|
||||
export function exportOverseasApproval(query: OverseasApprovalQuery) {
|
||||
return request({
|
||||
url: '/rs/overseas/approval/export',
|
||||
method: 'post',
|
||||
data: query
|
||||
});
|
||||
}
|
||||
|
||||
// 获取出国申请审批历史
|
||||
export function getOverseasApprovalHistory(applicationId: string | number) {
|
||||
return request({
|
||||
url: `/rs/overseas/application/approval/history/${applicationId}`,
|
||||
method: 'get'
|
||||
});
|
||||
}
|
||||
|
||||
// 新增出国审批记录
|
||||
export function addOverseasApproval(data: any) {
|
||||
return request({
|
||||
url: '/rs/overseas/approval',
|
||||
method: 'post',
|
||||
data: data
|
||||
});
|
||||
}
|
||||
|
||||
// 修改出国审批记录
|
||||
export function updateOverseasApproval(data: any) {
|
||||
return request({
|
||||
url: '/rs/overseas/approval',
|
||||
method: 'put',
|
||||
data: data
|
||||
});
|
||||
}
|
||||
|
||||
// 删除出国审批记录
|
||||
export function delOverseasApproval(approvalIds: string | number | Array<string | number>) {
|
||||
return request({
|
||||
url: `/rs/overseas/approval/${approvalIds}`,
|
||||
method: 'delete'
|
||||
});
|
||||
}
|
||||
|
||||
// 修复审批记录数据
|
||||
export function fixOverseasApprovalData() {
|
||||
return request({
|
||||
url: '/rs/overseas/approval/fixData',
|
||||
method: 'post'
|
||||
});
|
||||
}
|
||||
|
||||
// 修复已审批通过但未创建记录的申请
|
||||
export function fixApprovedOverseasRecords() {
|
||||
return request({
|
||||
url: '/rs/overseas/application/fix/records',
|
||||
method: 'post'
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,426 @@
|
||||
import request from '@/utils/request';
|
||||
|
||||
// 培训课程查询参数类型
|
||||
export interface CourseQuery {
|
||||
pageNum: number;
|
||||
pageSize: number;
|
||||
title?: string;
|
||||
category?: string;
|
||||
status?: string;
|
||||
params?: Record<string, any>;
|
||||
}
|
||||
|
||||
// 培训课程数据类型
|
||||
export interface CourseVO {
|
||||
courseId: number | string;
|
||||
title: string;
|
||||
category: string;
|
||||
description: string;
|
||||
startTime: string;
|
||||
endTime: string;
|
||||
duration: number;
|
||||
location: string;
|
||||
maxParticipants: number;
|
||||
currentParticipants: number;
|
||||
status: string;
|
||||
applicationCount: number;
|
||||
createTime: string;
|
||||
updateTime: string;
|
||||
createBy: string;
|
||||
updateBy: string;
|
||||
remark: string;
|
||||
}
|
||||
|
||||
// 培训课程表单类型
|
||||
export interface CourseForm {
|
||||
courseId?: number | string;
|
||||
title: string;
|
||||
category: string;
|
||||
description?: string;
|
||||
startTime: string;
|
||||
endTime: string;
|
||||
duration: number;
|
||||
location?: string;
|
||||
maxParticipants?: number;
|
||||
status?: string;
|
||||
remark?: string;
|
||||
}
|
||||
|
||||
// 培训申请查询参数类型
|
||||
export interface ApplicationQuery {
|
||||
pageNum: number;
|
||||
pageSize: number;
|
||||
courseId?: number | string;
|
||||
courseName?: string;
|
||||
userId?: number | string;
|
||||
userName?: string;
|
||||
department?: string;
|
||||
status?: string;
|
||||
params?: Record<string, any>;
|
||||
}
|
||||
|
||||
// 培训申请数据类型
|
||||
export interface ApplicationVO {
|
||||
applicationId: number | string;
|
||||
courseId: number | string;
|
||||
courseName: string;
|
||||
userId: number | string;
|
||||
userName: string;
|
||||
department: string;
|
||||
position?: string;
|
||||
reason?: string;
|
||||
status: string;
|
||||
applyTime: string;
|
||||
currentApprover?: string;
|
||||
certificatePath?: string;
|
||||
certificateNumber?: string;
|
||||
createTime: string;
|
||||
updateTime: string;
|
||||
createBy: string;
|
||||
updateBy: string;
|
||||
remark: string;
|
||||
}
|
||||
|
||||
// 培训申请表单类型
|
||||
export interface ApplicationForm {
|
||||
applicationId?: number | string;
|
||||
courseId: number | string;
|
||||
courseName?: string; // 自定义课程名称
|
||||
userId?: number | string;
|
||||
userName?: string;
|
||||
department?: string;
|
||||
position?: string;
|
||||
reason?: string;
|
||||
status?: string;
|
||||
location?: string;
|
||||
duration?: number;
|
||||
startTime?: string;
|
||||
endTime?: string;
|
||||
remark?: string;
|
||||
}
|
||||
|
||||
// 培训记录查询参数类型
|
||||
export interface RecordQuery {
|
||||
pageNum: number;
|
||||
pageSize: number;
|
||||
courseId?: number | string;
|
||||
courseName?: string;
|
||||
userId?: number | string;
|
||||
userName?: string;
|
||||
completionStatus?: string;
|
||||
params?: Record<string, any>;
|
||||
}
|
||||
|
||||
// 培训记录数据类型
|
||||
export interface RecordVO {
|
||||
recordId: number | string;
|
||||
applicationId?: number | string;
|
||||
courseId: number | string;
|
||||
courseName: string;
|
||||
userId: number | string;
|
||||
userName: string;
|
||||
department?: string;
|
||||
completionStatus: string;
|
||||
score?: string;
|
||||
duration?: number;
|
||||
completionTime?: string;
|
||||
certificatePath?: string;
|
||||
certificateNumber?: string;
|
||||
feedback?: string;
|
||||
createTime: string;
|
||||
updateTime: string;
|
||||
createBy: string;
|
||||
updateBy: string;
|
||||
remark: string;
|
||||
}
|
||||
|
||||
// 培训记录表单类型
|
||||
export interface RecordForm {
|
||||
recordId?: number | string;
|
||||
applicationId?: number | string;
|
||||
courseId: number | string;
|
||||
courseName?: string;
|
||||
userId: number | string;
|
||||
userName?: string;
|
||||
completionStatus: string;
|
||||
score?: string;
|
||||
duration?: number;
|
||||
completionTime?: string;
|
||||
certificatePath?: string;
|
||||
certificateNumber?: string;
|
||||
feedback?: string;
|
||||
remark?: string;
|
||||
}
|
||||
|
||||
// 查询培训课程列表
|
||||
export function listTrainingCourse(query: CourseQuery) {
|
||||
return request({
|
||||
url: '/rs/training/course/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
});
|
||||
}
|
||||
|
||||
// 获取培训课程详情
|
||||
export function getTrainingCourse(courseId: string | number) {
|
||||
return request({
|
||||
url: `/rs/training/course/${courseId}`,
|
||||
method: 'get'
|
||||
});
|
||||
}
|
||||
|
||||
// 新增培训课程
|
||||
export function addTrainingCourse(data: CourseForm) {
|
||||
return request({
|
||||
url: '/rs/training/course',
|
||||
method: 'post',
|
||||
data: data
|
||||
});
|
||||
}
|
||||
|
||||
// 修改培训课程
|
||||
export function updateTrainingCourse(data: CourseForm) {
|
||||
return request({
|
||||
url: '/rs/training/course',
|
||||
method: 'put',
|
||||
data: data
|
||||
});
|
||||
}
|
||||
|
||||
// 删除培训课程
|
||||
export function delTrainingCourse(courseId: string | number | Array<string | number>) {
|
||||
return request({
|
||||
url: `/rs/training/course/${typeof courseId === 'object' ? courseId.join(',') : courseId}`,
|
||||
method: 'delete'
|
||||
});
|
||||
}
|
||||
|
||||
// 切换培训课程状态
|
||||
export function toggleCourseStatus(courseId: string | number) {
|
||||
return request({
|
||||
url: `/rs/training/course/toggle/${courseId}`,
|
||||
method: 'put'
|
||||
});
|
||||
}
|
||||
|
||||
// 导出培训课程
|
||||
export function exportTrainingCourse(query: CourseQuery) {
|
||||
return request({
|
||||
url: '/rs/training/course/export',
|
||||
method: 'get',
|
||||
params: query,
|
||||
responseType: 'blob'
|
||||
});
|
||||
}
|
||||
|
||||
// 查询培训申请列表
|
||||
export function listTrainingApplication(query: ApplicationQuery) {
|
||||
return request({
|
||||
url: '/rs/training/application/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
});
|
||||
}
|
||||
|
||||
// 获取培训申请详情
|
||||
export function getTrainingApplication(applicationId: string | number) {
|
||||
return request({
|
||||
url: `/rs/training/application/${applicationId}`,
|
||||
method: 'get'
|
||||
});
|
||||
}
|
||||
|
||||
// 提交培训申请
|
||||
export function addTrainingApplication(data: ApplicationForm) {
|
||||
return request({
|
||||
url: '/rs/training/application',
|
||||
method: 'post',
|
||||
data: data
|
||||
});
|
||||
}
|
||||
|
||||
// 修改培训申请
|
||||
export function updateTrainingApplication(data: ApplicationForm) {
|
||||
return request({
|
||||
url: '/rs/training/application',
|
||||
method: 'put',
|
||||
data: data
|
||||
});
|
||||
}
|
||||
|
||||
// 取消培训申请
|
||||
export function cancelTrainingApplication(applicationId: string | number) {
|
||||
return request({
|
||||
url: `/rs/training/application/cancel/${applicationId}`,
|
||||
method: 'put'
|
||||
});
|
||||
}
|
||||
|
||||
// 删除培训申请
|
||||
export function deleteTrainingApplication(applicationId: string | number) {
|
||||
return request({
|
||||
url: `/rs/training/application/${applicationId}`,
|
||||
method: 'delete'
|
||||
});
|
||||
}
|
||||
|
||||
// 导出培训申请
|
||||
export function exportTrainingApplication(query: ApplicationQuery) {
|
||||
return request({
|
||||
url: '/rs/training/application/export',
|
||||
method: 'get',
|
||||
params: query,
|
||||
responseType: 'blob'
|
||||
});
|
||||
}
|
||||
|
||||
// 获取我的培训申请列表
|
||||
export function listMyTrainingApplication(query: ApplicationQuery) {
|
||||
return request({
|
||||
url: '/rs/training/application/my',
|
||||
method: 'get',
|
||||
params: query
|
||||
});
|
||||
}
|
||||
|
||||
// 查询培训记录列表
|
||||
export function listTrainingRecord(query: RecordQuery) {
|
||||
return request({
|
||||
url: '/rs/training/record/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
});
|
||||
}
|
||||
|
||||
// 获取培训记录详情
|
||||
export function getTrainingRecord(recordId: string | number) {
|
||||
return request({
|
||||
url: `/rs/training/record/${recordId}`,
|
||||
method: 'get'
|
||||
});
|
||||
}
|
||||
|
||||
// 新增培训记录
|
||||
export function addTrainingRecord(data: RecordForm) {
|
||||
return request({
|
||||
url: '/rs/training/record',
|
||||
method: 'post',
|
||||
data: data
|
||||
});
|
||||
}
|
||||
|
||||
// 修改培训记录
|
||||
export function updateTrainingRecord(data: RecordForm) {
|
||||
return request({
|
||||
url: '/rs/training/record',
|
||||
method: 'put',
|
||||
data: data
|
||||
});
|
||||
}
|
||||
|
||||
// 删除培训记录
|
||||
export function delTrainingRecord(recordId: string | number | Array<string | number>) {
|
||||
return request({
|
||||
url: `/rs/training/record/${typeof recordId === 'object' ? recordId.join(',') : recordId}`,
|
||||
method: 'delete'
|
||||
});
|
||||
}
|
||||
|
||||
// 上传培训证书
|
||||
export function uploadCertificate(recordId: string | number, file: File) {
|
||||
const formData = new FormData();
|
||||
formData.append('file', file);
|
||||
return request({
|
||||
url: `/rs/training/record/certificate/${recordId}`,
|
||||
method: 'post',
|
||||
data: formData,
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data'
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 导出培训记录
|
||||
export function exportTrainingRecord(query: RecordQuery) {
|
||||
return request({
|
||||
url: '/rs/training/record/export',
|
||||
method: 'get',
|
||||
params: query,
|
||||
responseType: 'blob'
|
||||
});
|
||||
}
|
||||
|
||||
// 提交培训申请(无需权限验证,供普通用户使用)
|
||||
export function submitTrainingApplication(data: ApplicationForm) {
|
||||
return request({
|
||||
url: '/rs/training/application/submit',
|
||||
headers: {
|
||||
isToken: false
|
||||
},
|
||||
method: 'post',
|
||||
data: data
|
||||
});
|
||||
}
|
||||
|
||||
// 更新培训申请状态
|
||||
export function updateApplicationStatus(applicationId: string | number, status: string, rejectReason?: string) {
|
||||
return request({
|
||||
url: `/rs/training/application/status/${applicationId}/${status}`,
|
||||
method: 'put',
|
||||
data: rejectReason ? { opinion: rejectReason } : {}
|
||||
});
|
||||
}
|
||||
|
||||
// 获取我的审批记录列表(当前用户审批过的所有申请)
|
||||
export function listMyApprovalRecord(query: ApplicationQuery) {
|
||||
return request({
|
||||
url: '/rs/training/application/myApproval',
|
||||
method: 'get',
|
||||
params: query
|
||||
});
|
||||
}
|
||||
|
||||
// 导出审批记录
|
||||
export function exportApprovalRecord(query: ApplicationQuery) {
|
||||
return request({
|
||||
url: '/rs/training/application/approval/export',
|
||||
method: 'get',
|
||||
params: query,
|
||||
responseType: 'blob'
|
||||
});
|
||||
}
|
||||
|
||||
// 获取培训申请审批历史
|
||||
export function getTrainingApprovalHistory(applicationId: string | number) {
|
||||
return request({
|
||||
url: `/rs/training/approval/history/${applicationId}`,
|
||||
method: 'get'
|
||||
});
|
||||
}
|
||||
|
||||
// 获取所有审批记录列表(包括所有用户的所有申请记录)
|
||||
export function listAllApprovalRecords(query: ApplicationQuery) {
|
||||
return request({
|
||||
url: '/rs/training/approval/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
});
|
||||
}
|
||||
|
||||
// 处理审批(通过或拒绝)- 会创建审批记录
|
||||
export function processTrainingApproval(applicationId: string | number, status: string, opinion?: string) {
|
||||
return request({
|
||||
url: `/rs/training/approval/process/${applicationId}/${status}`,
|
||||
method: 'put',
|
||||
data: opinion ? { opinion: opinion } : {}
|
||||
});
|
||||
}
|
||||
|
||||
// 重新提交被拒绝的培训申请
|
||||
export function resubmitTrainingApplication(applicationId: string | number, data: ApplicationForm) {
|
||||
return request({
|
||||
url: `/rs/training/application/resubmit/${applicationId}`,
|
||||
method: 'put',
|
||||
data: data
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
import request from '@/utils/request';
|
||||
import { AxiosPromise } from 'axios';
|
||||
import { StudentCancelLeaveVO, StudentCancelLeaveForm, StudentCancelLeaveQuery } from '@/api/workflow/studentCancelLeave/studentCancelLeave/types';
|
||||
|
||||
/**
|
||||
* 查询学生销假列表
|
||||
* @param query
|
||||
* @returns {*}
|
||||
*/
|
||||
|
||||
export const listStudentCancelLeave = (query?: StudentCancelLeaveQuery): AxiosPromise<StudentCancelLeaveVO[]> => {
|
||||
return request({
|
||||
url: '/studentCancelLeave/studentCancelLeave/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 查询学生销假详细
|
||||
* @param id
|
||||
*/
|
||||
export const getStudentCancelLeave = (id: string | number): AxiosPromise<StudentCancelLeaveVO> => {
|
||||
return request({
|
||||
url: '/studentCancelLeave/studentCancelLeave/' + id,
|
||||
method: 'get'
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 新增学生销假
|
||||
* @param data
|
||||
*/
|
||||
export const addStudentCancelLeave = (data: StudentCancelLeaveForm): AxiosPromise<StudentCancelLeaveVO> => {
|
||||
return request({
|
||||
url: '/studentCancelLeave/studentCancelLeave',
|
||||
method: 'post',
|
||||
data: data
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 修改学生销假
|
||||
* @param data
|
||||
*/
|
||||
export const updateStudentCancelLeave = (data: StudentCancelLeaveForm) => {
|
||||
return request({
|
||||
url: '/studentCancelLeave/studentCancelLeave',
|
||||
method: 'put',
|
||||
data: data
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 删除学生销假
|
||||
* @param id
|
||||
*/
|
||||
export const delStudentCancelLeave = (id: string | number | Array<string | number>) => {
|
||||
return request({
|
||||
url: '/studentCancelLeave/studentCancelLeave/' + id,
|
||||
method: 'delete'
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 根据请假ID获取销假ID
|
||||
* @param leaveId
|
||||
*/
|
||||
export const getCancelLeaveIdByLeaveId = (leaveId: string | number): AxiosPromise<number> => {
|
||||
return request({
|
||||
url: `/studentCancelLeave/studentCancelLeave/getCancelLeaveId/${leaveId}`,
|
||||
method: 'get'
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 根据销假ID获取请假ID
|
||||
* @param cancelLeaveId
|
||||
*/
|
||||
export const getLeaveIdByCancelLeaveId = (cancelLeaveId: string | number): AxiosPromise<number> => {
|
||||
return request({
|
||||
url: `/studentCancelLeave/studentCancelLeave/getLeaveId/${cancelLeaveId}`,
|
||||
method: 'get'
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,521 @@
|
||||
export interface StudentCancelLeaveVO {
|
||||
/**
|
||||
* 主键ID(雪花ID)
|
||||
*/
|
||||
id: string | number;
|
||||
|
||||
/**
|
||||
* 删除标志(0代表存在 1代表删除)
|
||||
*/
|
||||
deleted: number;
|
||||
|
||||
/**
|
||||
* 学生ID
|
||||
*/
|
||||
studentId: string | number;
|
||||
|
||||
/**
|
||||
* 学生姓名
|
||||
*/
|
||||
studentName: string;
|
||||
|
||||
/**
|
||||
* 学号
|
||||
*/
|
||||
studentNo: string;
|
||||
|
||||
/**
|
||||
* 班级ID
|
||||
*/
|
||||
classId: string | number;
|
||||
|
||||
/**
|
||||
* 班级名称
|
||||
*/
|
||||
className: string;
|
||||
|
||||
/**
|
||||
* 专业ID
|
||||
*/
|
||||
majorId: string | number;
|
||||
|
||||
/**
|
||||
* 专业名称
|
||||
*/
|
||||
majorName: string;
|
||||
|
||||
/**
|
||||
* 学院ID
|
||||
*/
|
||||
collegeId: string | number;
|
||||
|
||||
/**
|
||||
* 学院名称
|
||||
*/
|
||||
collegeName: string;
|
||||
|
||||
/**
|
||||
* 请假类型编码
|
||||
*/
|
||||
leaveTypeCode: string;
|
||||
|
||||
/**
|
||||
* 请假类型名称
|
||||
*/
|
||||
leaveTypeName: string;
|
||||
|
||||
/**
|
||||
* 开始时间(年月日时分秒)
|
||||
*/
|
||||
startTime: string;
|
||||
|
||||
/**
|
||||
* 结束时间(年月日时分秒)
|
||||
*/
|
||||
endTime: string;
|
||||
|
||||
/**
|
||||
* 请假时长(天)
|
||||
*/
|
||||
leaveDuration: number;
|
||||
|
||||
/**
|
||||
* 是否离校:0-否,1-是
|
||||
*/
|
||||
isLeaveSchool: string;
|
||||
|
||||
/**
|
||||
* 是否需要销假:0-否,1-是
|
||||
*/
|
||||
needCancel: string;
|
||||
|
||||
/**
|
||||
* 紧急联系人
|
||||
*/
|
||||
emergencyContact: string;
|
||||
|
||||
/**
|
||||
* 紧急联系电话
|
||||
*/
|
||||
emergencyPhone: string;
|
||||
|
||||
/**
|
||||
* 请假原因
|
||||
*/
|
||||
leaveReason: string;
|
||||
|
||||
/**
|
||||
* 请假时地理位置
|
||||
*/
|
||||
leaveLocation: string;
|
||||
|
||||
/**
|
||||
* 附件OSS文件ID(逗号分隔)
|
||||
*/
|
||||
attachmentOssIds: string | number;
|
||||
|
||||
/**
|
||||
* 审批状态:"cancel", "已撤销" "draft", "草稿" "waiting", "待审核" "finish", "已完成" "invalid", "已作废" "back", "已退回" "termination", "已终止"
|
||||
*/
|
||||
approvalStatus: string;
|
||||
|
||||
/**
|
||||
* 审批意见
|
||||
*/
|
||||
approvalOpinion: string;
|
||||
|
||||
/**
|
||||
* 审批时间
|
||||
*/
|
||||
approvalTime: string;
|
||||
|
||||
/**
|
||||
* 审批人ID
|
||||
*/
|
||||
approvalBy: number;
|
||||
|
||||
/**
|
||||
* 审批人姓名
|
||||
*/
|
||||
approvalByName: string;
|
||||
|
||||
/**
|
||||
* 销假状态:0-未销假,1-已销假,2-逾期未销假 3 - 无需销假 "cancel", "已撤销" "draft", "草稿" "waiting", "待审核" "finish", "已完成" "invalid", "已作废" "back", "已退回" "termination", "已终止"
|
||||
*/
|
||||
cancelStatus: string;
|
||||
|
||||
/**
|
||||
* 销假时间
|
||||
*/
|
||||
cancelTime: string;
|
||||
|
||||
/**
|
||||
* 销假地理位置
|
||||
*/
|
||||
cancelLocation: string;
|
||||
|
||||
/**
|
||||
* 销假照片OSS文件ID(逗号分隔)
|
||||
*/
|
||||
cancelPhotoOssIds: string | number;
|
||||
|
||||
/**
|
||||
* 销假备注
|
||||
*/
|
||||
cancelRemark: string;
|
||||
|
||||
/**
|
||||
* 学生请假表id
|
||||
*/
|
||||
leaveId: string | number;
|
||||
|
||||
}
|
||||
|
||||
export interface StudentCancelLeaveForm extends BaseEntity {
|
||||
/**
|
||||
* 主键ID(雪花ID)
|
||||
*/
|
||||
id?: string | number;
|
||||
|
||||
/**
|
||||
* 删除标志(0代表存在 1代表删除)
|
||||
*/
|
||||
deleted?: number;
|
||||
|
||||
/**
|
||||
* 学生ID
|
||||
*/
|
||||
studentId?: string | number;
|
||||
|
||||
/**
|
||||
* 学生姓名
|
||||
*/
|
||||
studentName?: string;
|
||||
|
||||
/**
|
||||
* 学号
|
||||
*/
|
||||
studentNo?: string;
|
||||
|
||||
/**
|
||||
* 班级ID
|
||||
*/
|
||||
classId?: string | number;
|
||||
|
||||
/**
|
||||
* 班级名称
|
||||
*/
|
||||
className?: string;
|
||||
|
||||
/**
|
||||
* 专业ID
|
||||
*/
|
||||
majorId?: string | number;
|
||||
|
||||
/**
|
||||
* 专业名称
|
||||
*/
|
||||
majorName?: string;
|
||||
|
||||
/**
|
||||
* 学院ID
|
||||
*/
|
||||
collegeId?: string | number;
|
||||
|
||||
/**
|
||||
* 学院名称
|
||||
*/
|
||||
collegeName?: string;
|
||||
|
||||
/**
|
||||
* 请假类型编码
|
||||
*/
|
||||
leaveTypeCode?: string;
|
||||
|
||||
/**
|
||||
* 请假类型名称
|
||||
*/
|
||||
leaveTypeName?: string;
|
||||
|
||||
/**
|
||||
* 开始时间(年月日时分秒)
|
||||
*/
|
||||
startTime?: string;
|
||||
|
||||
/**
|
||||
* 结束时间(年月日时分秒)
|
||||
*/
|
||||
endTime?: string;
|
||||
|
||||
/**
|
||||
* 请假时长(天)
|
||||
*/
|
||||
leaveDuration?: number;
|
||||
|
||||
/**
|
||||
* 是否离校:0-否,1-是
|
||||
*/
|
||||
isLeaveSchool?: string;
|
||||
|
||||
/**
|
||||
* 是否需要销假:0-否,1-是
|
||||
*/
|
||||
needCancel?: string;
|
||||
|
||||
/**
|
||||
* 紧急联系人
|
||||
*/
|
||||
emergencyContact?: string;
|
||||
|
||||
/**
|
||||
* 紧急联系电话
|
||||
*/
|
||||
emergencyPhone?: string;
|
||||
|
||||
/**
|
||||
* 请假原因
|
||||
*/
|
||||
leaveReason?: string;
|
||||
|
||||
/**
|
||||
* 请假时地理位置
|
||||
*/
|
||||
leaveLocation?: string;
|
||||
|
||||
/**
|
||||
* 附件OSS文件ID(逗号分隔)
|
||||
*/
|
||||
attachmentOssIds?: string | number;
|
||||
|
||||
/**
|
||||
* 审批状态:"cancel", "已撤销" "draft", "草稿" "waiting", "待审核" "finish", "已完成" "invalid", "已作废" "back", "已退回" "termination", "已终止"
|
||||
*/
|
||||
approvalStatus?: string;
|
||||
|
||||
/**
|
||||
* 审批意见
|
||||
*/
|
||||
approvalOpinion?: string;
|
||||
|
||||
/**
|
||||
* 审批时间
|
||||
*/
|
||||
approvalTime?: string;
|
||||
|
||||
/**
|
||||
* 审批人ID
|
||||
*/
|
||||
approvalBy?: number;
|
||||
|
||||
/**
|
||||
* 审批人姓名
|
||||
*/
|
||||
approvalByName?: string;
|
||||
|
||||
/**
|
||||
* 销假状态:0-未销假,1-已销假,2-逾期未销假 3 - 无需销假 "cancel", "已撤销" "draft", "草稿" "waiting", "待审核" "finish", "已完成" "invalid", "已作废" "back", "已退回" "termination", "已终止"
|
||||
*/
|
||||
cancelStatus?: string;
|
||||
|
||||
/**
|
||||
* 销假时间
|
||||
*/
|
||||
cancelTime?: string;
|
||||
|
||||
/**
|
||||
* 销假地理位置
|
||||
*/
|
||||
cancelLocation?: string;
|
||||
|
||||
/**
|
||||
* 销假照片OSS文件ID(逗号分隔)
|
||||
*/
|
||||
cancelPhotoOssIds?: string | number;
|
||||
|
||||
/**
|
||||
* 销假备注
|
||||
*/
|
||||
cancelRemark?: string;
|
||||
|
||||
/**
|
||||
* 学生请假表id
|
||||
*/
|
||||
leaveId?: string | number;
|
||||
|
||||
}
|
||||
|
||||
export interface StudentCancelLeaveQuery extends PageQuery {
|
||||
|
||||
/**
|
||||
* 删除标志(0代表存在 1代表删除)
|
||||
*/
|
||||
deleted?: number;
|
||||
|
||||
/**
|
||||
* 学生ID
|
||||
*/
|
||||
studentId?: string | number;
|
||||
|
||||
/**
|
||||
* 学生姓名
|
||||
*/
|
||||
studentName?: string;
|
||||
|
||||
/**
|
||||
* 学号
|
||||
*/
|
||||
studentNo?: string;
|
||||
|
||||
/**
|
||||
* 班级ID
|
||||
*/
|
||||
classId?: string | number;
|
||||
|
||||
/**
|
||||
* 班级名称
|
||||
*/
|
||||
className?: string;
|
||||
|
||||
/**
|
||||
* 专业ID
|
||||
*/
|
||||
majorId?: string | number;
|
||||
|
||||
/**
|
||||
* 专业名称
|
||||
*/
|
||||
majorName?: string;
|
||||
|
||||
/**
|
||||
* 学院ID
|
||||
*/
|
||||
collegeId?: string | number;
|
||||
|
||||
/**
|
||||
* 学院名称
|
||||
*/
|
||||
collegeName?: string;
|
||||
|
||||
/**
|
||||
* 请假类型编码
|
||||
*/
|
||||
leaveTypeCode?: string;
|
||||
|
||||
/**
|
||||
* 请假类型名称
|
||||
*/
|
||||
leaveTypeName?: string;
|
||||
|
||||
/**
|
||||
* 开始时间(年月日时分秒)
|
||||
*/
|
||||
startTime?: string;
|
||||
|
||||
/**
|
||||
* 结束时间(年月日时分秒)
|
||||
*/
|
||||
endTime?: string;
|
||||
|
||||
/**
|
||||
* 请假时长(天)
|
||||
*/
|
||||
leaveDuration?: number;
|
||||
|
||||
/**
|
||||
* 是否离校:0-否,1-是
|
||||
*/
|
||||
isLeaveSchool?: string;
|
||||
|
||||
/**
|
||||
* 是否需要销假:0-否,1-是
|
||||
*/
|
||||
needCancel?: string;
|
||||
|
||||
/**
|
||||
* 紧急联系人
|
||||
*/
|
||||
emergencyContact?: string;
|
||||
|
||||
/**
|
||||
* 紧急联系电话
|
||||
*/
|
||||
emergencyPhone?: string;
|
||||
|
||||
/**
|
||||
* 请假原因
|
||||
*/
|
||||
leaveReason?: string;
|
||||
|
||||
/**
|
||||
* 请假时地理位置
|
||||
*/
|
||||
leaveLocation?: string;
|
||||
|
||||
/**
|
||||
* 附件OSS文件ID(逗号分隔)
|
||||
*/
|
||||
attachmentOssIds?: string | number;
|
||||
|
||||
/**
|
||||
* 审批状态:"cancel", "已撤销" "draft", "草稿" "waiting", "待审核" "finish", "已完成" "invalid", "已作废" "back", "已退回" "termination", "已终止"
|
||||
*/
|
||||
approvalStatus?: string;
|
||||
|
||||
/**
|
||||
* 审批意见
|
||||
*/
|
||||
approvalOpinion?: string;
|
||||
|
||||
/**
|
||||
* 审批时间
|
||||
*/
|
||||
approvalTime?: string;
|
||||
|
||||
/**
|
||||
* 审批人ID
|
||||
*/
|
||||
approvalBy?: number;
|
||||
|
||||
/**
|
||||
* 审批人姓名
|
||||
*/
|
||||
approvalByName?: string;
|
||||
|
||||
/**
|
||||
* 销假状态:0-未销假,1-已销假,2-逾期未销假 3 - 无需销假 "cancel", "已撤销" "draft", "草稿" "waiting", "待审核" "finish", "已完成" "invalid", "已作废" "back", "已退回" "termination", "已终止"
|
||||
*/
|
||||
cancelStatus?: string;
|
||||
|
||||
/**
|
||||
* 销假时间
|
||||
*/
|
||||
cancelTime?: string;
|
||||
|
||||
/**
|
||||
* 销假地理位置
|
||||
*/
|
||||
cancelLocation?: string;
|
||||
|
||||
/**
|
||||
* 销假照片OSS文件ID(逗号分隔)
|
||||
*/
|
||||
cancelPhotoOssIds?: string | number;
|
||||
|
||||
/**
|
||||
* 销假备注
|
||||
*/
|
||||
cancelRemark?: string;
|
||||
|
||||
/**
|
||||
* 学生请假表id
|
||||
*/
|
||||
leaveId?: string | number;
|
||||
|
||||
/**
|
||||
* 日期范围参数
|
||||
*/
|
||||
params?: any;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -84,3 +84,25 @@ export const downloadImportTemplate = () => {
|
||||
responseType: 'blob'
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 根据学生ID获取班主任ID
|
||||
* @param studentId 学生ID
|
||||
*/
|
||||
export const getClassTeacherId = (studentId: string | number): AxiosPromise<{ data: number }> => {
|
||||
return request({
|
||||
url: `/studentMsg/studentMsg/getClassTeacherId/${studentId}`,
|
||||
method: 'get'
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 根据学生ID获取学院请假审批人列表
|
||||
* @param studentId 学生ID
|
||||
*/
|
||||
export const getCollegeLeaveApprovers = (studentId: string | number): AxiosPromise<{ data: number[] }> => {
|
||||
return request({
|
||||
url: `/studentMsg/studentMsg/getCollegeLeaveApprovers/${studentId}`,
|
||||
method: 'get'
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024">
|
||||
<path d="M832 64H192c-17.7 0-32 14.3-32 32v224h704V96c0-17.7-14.3-32-32-32zM288 232c-22.1 0-40-17.9-40-40s17.9-40 40-40 40 17.9 40 40-17.9 40-40 40zM160 928c0 17.7 14.3 32 32 32h640c17.7 0 32-14.3 32-32V384H160v544zm382-128h-92c-15.5 0-28-12.5-28-28v-38c0-15.5 12.5-28 28-28h92c15.5 0 28 12.5 28 28v38c0 15.5-12.5 28-28 28zm-92-256h220c15.5 0 28 12.5 28 28v38c0 15.5-12.5 28-28 28H450c-15.5 0-28-12.5-28-28v-38c0-15.5 12.5-28 28-28z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 511 B |
@@ -28,7 +28,7 @@
|
||||
</div>
|
||||
</el-tooltip>
|
||||
<!-- 消息 -->
|
||||
<el-tooltip :content="proxy.$t('navbar.message')" effect="dark" placement="bottom">
|
||||
<!-- <el-tooltip :content="proxy.$t('navbar.message')" effect="dark" placement="bottom">
|
||||
<div>
|
||||
<el-popover placement="bottom" trigger="click" transition="el-zoom-in-top" :width="300" :persistent="false">
|
||||
<template #reference>
|
||||
@@ -41,7 +41,7 @@
|
||||
</template>
|
||||
</el-popover>
|
||||
</div>
|
||||
</el-tooltip>
|
||||
</el-tooltip> -->
|
||||
<!-- <el-tooltip content="Github" effect="dark" placement="bottom">
|
||||
<ruo-yi-git id="ruoyi-git" class="right-menu-item hover-effect" />
|
||||
</el-tooltip> -->
|
||||
|
||||
@@ -11,7 +11,7 @@ import { usePermissionStore } from '@/store/modules/permission';
|
||||
import { ElMessage } from 'element-plus';
|
||||
|
||||
NProgress.configure({ showSpinner: false });
|
||||
const whiteList = ['/login', '/register', '/social-callback', '/register*', '/register/*'];
|
||||
const whiteList = ['/login', '/register', '/social-callback', '/register*', '/register/*', '/recruitment-portal', '/recruitment-apply'];
|
||||
|
||||
const isWhiteList = (path: string) => {
|
||||
return whiteList.some((pattern) => isPathMatch(pattern, path));
|
||||
|
||||
@@ -13,6 +13,8 @@ export const useUserStore = defineStore('user', () => {
|
||||
const userId = ref<string | number>('');
|
||||
const tenantId = ref<string>('');
|
||||
const avatar = ref('');
|
||||
const deptName = ref('');
|
||||
const phonenumber = ref('');
|
||||
const roles = ref<Array<string>>([]); // 用户角色编码集合 → 判断路由权限
|
||||
const permissions = ref<Array<string>>([]); // 用户权限编码集合 → 判断按钮权限
|
||||
|
||||
@@ -52,6 +54,8 @@ export const useUserStore = defineStore('user', () => {
|
||||
avatar.value = profile;
|
||||
userId.value = user.userId;
|
||||
tenantId.value = user.tenantId;
|
||||
deptName.value = user.deptName;
|
||||
phonenumber.value = user.phonenumber;
|
||||
return Promise.resolve();
|
||||
}
|
||||
return Promise.reject(err);
|
||||
@@ -74,8 +78,11 @@ export const useUserStore = defineStore('user', () => {
|
||||
userId,
|
||||
tenantId,
|
||||
token,
|
||||
name,
|
||||
nickname,
|
||||
avatar,
|
||||
deptName,
|
||||
phonenumber,
|
||||
roles,
|
||||
permissions,
|
||||
login,
|
||||
|
||||
Vendored
+1
-1
@@ -89,7 +89,7 @@ declare global {
|
||||
pageSize: number;
|
||||
|
||||
/**
|
||||
* 排序字段
|
||||
* 排序字段 老李
|
||||
*/
|
||||
orderByColumn?: string;
|
||||
|
||||
|
||||
@@ -154,17 +154,12 @@
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>月度申报趋势</span>
|
||||
<el-select v-model="selectedYear" @change="handleYearChange" style="width: 120px;">
|
||||
<el-option
|
||||
v-for="year in yearOptions"
|
||||
:key="year"
|
||||
:label="year + '年'"
|
||||
:value="year"
|
||||
/>
|
||||
<el-select v-model="selectedYear" @change="handleYearChange" style="width: 120px">
|
||||
<el-option v-for="year in yearOptions" :key="year" :label="year + '年'" :value="year" />
|
||||
</el-select>
|
||||
</div>
|
||||
</template>
|
||||
<div ref="trendChart" style="height: 350px;"></div>
|
||||
<div ref="trendChart" style="height: 350px"></div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
|
||||
@@ -174,7 +169,7 @@
|
||||
<template #header>
|
||||
<span>状态分布</span>
|
||||
</template>
|
||||
<div ref="statusChart" style="height: 350px;"></div>
|
||||
<div ref="statusChart" style="height: 350px"></div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
@@ -187,13 +182,8 @@
|
||||
<el-button type="primary" icon="Download" @click="handleExportDeptStatistics">导出部门统计</el-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<el-table
|
||||
:data="statistics.deptStatistics"
|
||||
border
|
||||
style="width: 100%"
|
||||
v-loading="statisticsLoading"
|
||||
>
|
||||
|
||||
<el-table :data="statistics.deptStatistics" border style="width: 100%" v-loading="statisticsLoading">
|
||||
<el-table-column prop="deptName" label="部门名称" align="center" min-width="120" show-overflow-tooltip />
|
||||
<el-table-column prop="totalCount" label="申报数量" align="center" width="100" />
|
||||
<el-table-column prop="approvingCount" label="审批中" align="center" width="100">
|
||||
@@ -223,11 +213,7 @@
|
||||
</el-table-column>
|
||||
<el-table-column label="审批率" align="center" width="100">
|
||||
<template #default="scope">
|
||||
<el-progress
|
||||
:percentage="calculateApprovalRate(scope.row)"
|
||||
:status="getProgressStatus(scope.row)"
|
||||
:show-text="false"
|
||||
/>
|
||||
<el-progress :percentage="calculateApprovalRate(scope.row)" :status="getProgressStatus(scope.row)" :show-text="false" />
|
||||
<span class="rate-text">{{ calculateApprovalRate(scope.row) }}%</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
@@ -247,18 +233,15 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, onMounted, nextTick } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import {
|
||||
Plus, User, Clock, DataAnalysis, Files, CircleCheck, CircleClose,
|
||||
Money, Timer, TrendCharts, Finished
|
||||
} from '@element-plus/icons-vue'
|
||||
import * as echarts from 'echarts'
|
||||
import { getApplicationStatistics, getStatisticsByDept, getStatisticsByMonth } from '@/api/cw/projectApplication'
|
||||
import { parseTime } from '@/utils/ruoyi'
|
||||
import { ref, reactive, onMounted, nextTick } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||
import { Plus, User, Clock, DataAnalysis, Files, CircleCheck, CircleClose, Money, Timer, TrendCharts, Finished } from '@element-plus/icons-vue';
|
||||
import * as echarts from 'echarts';
|
||||
import { getApplicationStatistics, getStatisticsByDept, getStatisticsByMonth } from '@/api/cw/projectApplication';
|
||||
import { parseTime } from '@/utils/ruoyi';
|
||||
|
||||
const router = useRouter()
|
||||
const router = useRouter();
|
||||
|
||||
// 响应式数据
|
||||
const statistics = reactive({
|
||||
@@ -271,138 +254,135 @@ const statistics = reactive({
|
||||
monthlyNew: 0,
|
||||
monthlyApproved: 0,
|
||||
deptStatistics: [] as any[]
|
||||
})
|
||||
});
|
||||
|
||||
const selectedYear = ref(new Date().getFullYear())
|
||||
const yearOptions = ref<number[]>([])
|
||||
const statisticsLoading = ref(false)
|
||||
const selectedYear = ref(new Date().getFullYear());
|
||||
const yearOptions = ref<number[]>([]);
|
||||
const statisticsLoading = ref(false);
|
||||
|
||||
// 图表引用
|
||||
const trendChart = ref()
|
||||
const statusChart = ref()
|
||||
let trendChartInstance: echarts.ECharts | null = null
|
||||
let statusChartInstance: echarts.ECharts | null = null
|
||||
const trendChart = ref();
|
||||
const statusChart = ref();
|
||||
let trendChartInstance: echarts.ECharts | null = null;
|
||||
let statusChartInstance: echarts.ECharts | null = null;
|
||||
|
||||
// 生成年份选项
|
||||
const generateYearOptions = () => {
|
||||
const currentYear = new Date().getFullYear()
|
||||
const currentYear = new Date().getFullYear();
|
||||
for (let i = currentYear - 4; i <= currentYear + 1; i++) {
|
||||
yearOptions.value.push(i)
|
||||
yearOptions.value.push(i);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 快捷操作
|
||||
const handleAdd = () => {
|
||||
router.push('/cw/application')
|
||||
}
|
||||
router.push('/cw/application');
|
||||
};
|
||||
|
||||
const showMyApplications = () => {
|
||||
router.push({ path: '/cw/application', query: { tab: 'my' } })
|
||||
}
|
||||
router.push({ path: '/cw/application', query: { tab: 'my' } });
|
||||
};
|
||||
|
||||
const showPendingApplications = () => {
|
||||
router.push({ path: '/cw/application', query: { tab: 'pending' } })
|
||||
}
|
||||
router.push({ path: '/cw/application', query: { tab: 'pending' } });
|
||||
};
|
||||
|
||||
const showStatistics = () => {
|
||||
router.push('/cw/application/summary')
|
||||
}
|
||||
router.push('/cw/application/summary');
|
||||
};
|
||||
|
||||
// 格式化金额
|
||||
const formatAmount = (amount: number | undefined): string => {
|
||||
if (!amount) return '0.00'
|
||||
return amount.toFixed(2)
|
||||
}
|
||||
if (!amount) return '0.00';
|
||||
return amount.toFixed(2);
|
||||
};
|
||||
|
||||
// 计算审批率
|
||||
const calculateApprovalRate = (row: any): number => {
|
||||
if (!row.totalCount || row.totalCount === 0) return 0
|
||||
return Math.round((row.approvedCount / row.totalCount) * 100)
|
||||
}
|
||||
if (!row.totalCount || row.totalCount === 0) return 0;
|
||||
return Math.round((row.approvedCount / row.totalCount) * 100);
|
||||
};
|
||||
|
||||
// 获取进度条状态
|
||||
const getProgressStatus = (row: any): string => {
|
||||
const rate = calculateApprovalRate(row)
|
||||
if (rate >= 80) return 'success'
|
||||
if (rate >= 60) return 'warning'
|
||||
return 'exception'
|
||||
}
|
||||
const rate = calculateApprovalRate(row);
|
||||
if (rate >= 80) return 'success';
|
||||
if (rate >= 60) return 'warning';
|
||||
return 'exception';
|
||||
};
|
||||
|
||||
// 查看部门详情
|
||||
const viewDepartmentDetail = (row: any) => {
|
||||
router.push({
|
||||
path: '/cw/application',
|
||||
query: { deptId: row.deptId, deptName: row.deptName }
|
||||
})
|
||||
}
|
||||
router.push({
|
||||
path: '/cw/application',
|
||||
query: { deptId: row.deptId, deptName: row.deptName }
|
||||
});
|
||||
};
|
||||
|
||||
// 导出部门数据
|
||||
const exportDepartmentData = (row: any) => {
|
||||
ElMessage.success(`导出部门【${row.deptName}】数据功能开发中...`)
|
||||
}
|
||||
ElMessage.success(`导出部门【${row.deptName}】数据功能开发中...`);
|
||||
};
|
||||
|
||||
// 导出部门统计
|
||||
const handleExportDeptStatistics = () => {
|
||||
ElMessage.success('导出部门统计功能开发中...')
|
||||
}
|
||||
ElMessage.success('导出部门统计功能开发中...');
|
||||
};
|
||||
|
||||
// 获取统计数据
|
||||
const loadStatistics = async () => {
|
||||
try {
|
||||
statisticsLoading.value = true
|
||||
const [statsRes, deptRes] = await Promise.all([
|
||||
getApplicationStatistics(selectedYear.value),
|
||||
getStatisticsByDept()
|
||||
])
|
||||
|
||||
statisticsLoading.value = true;
|
||||
const [statsRes, deptRes] = await Promise.all([getApplicationStatistics(selectedYear.value), getStatisticsByDept()]);
|
||||
|
||||
if (statsRes.code === 200) {
|
||||
Object.assign(statistics, statsRes.data)
|
||||
Object.assign(statistics, statsRes.data);
|
||||
}
|
||||
|
||||
|
||||
if (deptRes.code === 200) {
|
||||
statistics.deptStatistics = deptRes.data || []
|
||||
statistics.deptStatistics = deptRes.data || [];
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取统计数据失败:', error)
|
||||
ElMessage.error('获取统计数据失败')
|
||||
console.error('获取统计数据失败:', error);
|
||||
ElMessage.error('获取统计数据失败');
|
||||
} finally {
|
||||
statisticsLoading.value = false
|
||||
statisticsLoading.value = false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 年份变化处理
|
||||
const handleYearChange = () => {
|
||||
loadStatistics()
|
||||
loadTrendChart()
|
||||
}
|
||||
loadStatistics();
|
||||
loadTrendChart();
|
||||
};
|
||||
|
||||
// 加载趋势图表
|
||||
const loadTrendChart = async () => {
|
||||
try {
|
||||
const response = await getStatisticsByMonth(selectedYear.value)
|
||||
const response = await getStatisticsByMonth(selectedYear.value);
|
||||
if (response.code === 200) {
|
||||
renderTrendChart(response.data)
|
||||
renderTrendChart(response.data);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取月度统计失败:', error)
|
||||
console.error('获取月度统计失败:', error);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 渲染趋势图表
|
||||
const renderTrendChart = (data: any[]) => {
|
||||
if (!trendChartInstance) return
|
||||
|
||||
const months = ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月']
|
||||
const applicationCounts = new Array(12).fill(0)
|
||||
const approvedCounts = new Array(12).fill(0)
|
||||
|
||||
data.forEach(item => {
|
||||
const monthIndex = item.month - 1
|
||||
if (!trendChartInstance) return;
|
||||
|
||||
const months = ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'];
|
||||
const applicationCounts = new Array(12).fill(0);
|
||||
const approvedCounts = new Array(12).fill(0);
|
||||
|
||||
data.forEach((item) => {
|
||||
const monthIndex = item.month - 1;
|
||||
if (monthIndex >= 0 && monthIndex < 12) {
|
||||
applicationCounts[monthIndex] = item.applicationCount || 0
|
||||
approvedCounts[monthIndex] = item.approvedCount || 0
|
||||
applicationCounts[monthIndex] = item.applicationCount || 0;
|
||||
approvedCounts[monthIndex] = item.approvedCount || 0;
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
const option = {
|
||||
tooltip: {
|
||||
@@ -437,15 +417,15 @@ const renderTrendChart = (data: any[]) => {
|
||||
itemStyle: { color: '#67C23A' }
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
trendChartInstance.setOption(option)
|
||||
}
|
||||
};
|
||||
|
||||
trendChartInstance.setOption(option);
|
||||
};
|
||||
|
||||
// 渲染状态分布图
|
||||
const renderStatusChart = () => {
|
||||
if (!statusChartInstance) return
|
||||
|
||||
if (!statusChartInstance) return;
|
||||
|
||||
const option = {
|
||||
tooltip: {
|
||||
trigger: 'item',
|
||||
@@ -474,38 +454,38 @@ const renderStatusChart = () => {
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
statusChartInstance.setOption(option)
|
||||
}
|
||||
};
|
||||
|
||||
statusChartInstance.setOption(option);
|
||||
};
|
||||
|
||||
// 初始化图表
|
||||
const initCharts = async () => {
|
||||
await nextTick()
|
||||
|
||||
await nextTick();
|
||||
|
||||
if (trendChart.value) {
|
||||
trendChartInstance = echarts.init(trendChart.value)
|
||||
loadTrendChart()
|
||||
trendChartInstance = echarts.init(trendChart.value);
|
||||
loadTrendChart();
|
||||
}
|
||||
|
||||
|
||||
if (statusChart.value) {
|
||||
statusChartInstance = echarts.init(statusChart.value)
|
||||
renderStatusChart()
|
||||
statusChartInstance = echarts.init(statusChart.value);
|
||||
renderStatusChart();
|
||||
}
|
||||
|
||||
|
||||
// 监听窗口大小变化
|
||||
window.addEventListener('resize', () => {
|
||||
trendChartInstance?.resize()
|
||||
statusChartInstance?.resize()
|
||||
})
|
||||
}
|
||||
trendChartInstance?.resize();
|
||||
statusChartInstance?.resize();
|
||||
});
|
||||
};
|
||||
|
||||
// 组件挂载
|
||||
onMounted(() => {
|
||||
generateYearOptions()
|
||||
loadStatistics()
|
||||
initCharts()
|
||||
})
|
||||
generateYearOptions();
|
||||
loadStatistics();
|
||||
initCharts();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@@ -518,25 +498,25 @@ onMounted(() => {
|
||||
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;
|
||||
@@ -550,32 +530,32 @@ onMounted(() => {
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
|
||||
|
||||
&.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, #fa709a 0%, #fee140 100%);
|
||||
color: white;
|
||||
}
|
||||
|
||||
|
||||
&.stat-card-danger {
|
||||
background: linear-gradient(135deg, #ff6b6b 0%, #ffa726 100%);
|
||||
color: white;
|
||||
}
|
||||
|
||||
|
||||
&.stat-card-info {
|
||||
background: linear-gradient(135deg, #a8edea 0%, #fed6e3 100%);
|
||||
color: #2c3e50;
|
||||
}
|
||||
|
||||
|
||||
:deep(.el-card__body) {
|
||||
padding: 0;
|
||||
}
|
||||
@@ -585,13 +565,13 @@ onMounted(() => {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 20px;
|
||||
|
||||
|
||||
.stat-icon {
|
||||
font-size: 32px;
|
||||
margin-right: 16px;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
|
||||
.stat-info {
|
||||
.stat-value {
|
||||
font-size: 24px;
|
||||
@@ -599,7 +579,7 @@ onMounted(() => {
|
||||
line-height: 1.2;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
|
||||
.stat-label {
|
||||
font-size: 12px;
|
||||
opacity: 0.9;
|
||||
@@ -612,7 +592,7 @@ onMounted(() => {
|
||||
.chart-card {
|
||||
border-radius: 8px;
|
||||
border: 1px solid #e4e7ed;
|
||||
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
@@ -626,7 +606,7 @@ onMounted(() => {
|
||||
.table-card {
|
||||
border-radius: 8px;
|
||||
border: 1px solid #e4e7ed;
|
||||
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
@@ -634,16 +614,16 @@ onMounted(() => {
|
||||
font-weight: 600;
|
||||
color: #2c3e50;
|
||||
}
|
||||
|
||||
|
||||
.amount-text {
|
||||
font-weight: 600;
|
||||
color: #606266;
|
||||
|
||||
|
||||
&.success {
|
||||
color: #67C23A;
|
||||
color: #67c23a;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.rate-text {
|
||||
font-size: 12px;
|
||||
margin-left: 8px;
|
||||
@@ -674,24 +654,24 @@ onMounted(() => {
|
||||
@media (max-width: 768px) {
|
||||
.quick-action {
|
||||
padding: 15px;
|
||||
|
||||
|
||||
.quick-icon {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
|
||||
span {
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.stat-content {
|
||||
padding: 15px;
|
||||
|
||||
|
||||
.stat-icon {
|
||||
font-size: 24px;
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
|
||||
.stat-info .stat-value {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
@@ -125,13 +125,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>
|
||||
|
||||
<!-- 项目详情对话框 -->
|
||||
@@ -172,12 +166,14 @@
|
||||
clearable
|
||||
filterable
|
||||
:loading="loadingApprovers"
|
||||
style="width: 100%">
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-option
|
||||
v-for="approver in availableApprovers"
|
||||
:key="approver.userId"
|
||||
:label="approver.nickName + '(' + approver.userName + ')'"
|
||||
:value="approver.userId">
|
||||
:value="approver.userId"
|
||||
>
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
@@ -212,7 +208,8 @@
|
||||
<el-card>
|
||||
<h4>{{ record.nodeName }}</h4>
|
||||
<p><strong>审批人:</strong>{{ record.approverName }}</p>
|
||||
<p><strong>审批结果:</strong>
|
||||
<p>
|
||||
<strong>审批结果:</strong>
|
||||
<el-tag :type="getApprovalResultType(record.approveAction)">
|
||||
{{ getApprovalResultText(record.approveAction) }}
|
||||
</el-tag>
|
||||
@@ -239,10 +236,10 @@ import {
|
||||
getApprovalHistory,
|
||||
getAvailableApprovers,
|
||||
getFinanceApprovedRecords
|
||||
} from "@/api/cw/approvalRecord";
|
||||
import { financeApproveApplication, batchFinanceApproveApplication } from "@/api/cw/financeApproval";
|
||||
import { useDict } from "@/utils/dict";
|
||||
import ApplicationSummary from "../application/summary.vue";
|
||||
} from '@/api/cw/approvalRecord';
|
||||
import { financeApproveApplication, batchFinanceApproveApplication } from '@/api/cw/financeApproval';
|
||||
import { useDict } from '@/utils/dict';
|
||||
import ApplicationSummary from '../application/summary.vue';
|
||||
|
||||
export default {
|
||||
name: 'CwFinanceApproval',
|
||||
@@ -321,9 +318,7 @@ export default {
|
||||
},
|
||||
// 审批表单验证
|
||||
approvalRules: {
|
||||
approvalResult: [
|
||||
{ required: true, message: "请选择审批结果", trigger: "change" }
|
||||
],
|
||||
approvalResult: [{ required: true, message: '请选择审批结果', trigger: 'change' }],
|
||||
selectedApproverId: [
|
||||
{
|
||||
validator: (rule, value, callback) => {
|
||||
@@ -337,8 +332,8 @@ export default {
|
||||
}
|
||||
],
|
||||
approvalComment: [
|
||||
{ required: true, message: "请输入审批意见", trigger: "blur" },
|
||||
{ min: 5, max: 500, message: "审批意见长度在 5 到 500 个字符", trigger: "blur" }
|
||||
{ required: true, message: '请输入审批意见', trigger: 'blur' },
|
||||
{ min: 5, max: 500, message: '审批意见长度在 5 到 500 个字符', trigger: 'blur' }
|
||||
]
|
||||
},
|
||||
// 可选院领导列表
|
||||
@@ -357,15 +352,17 @@ export default {
|
||||
getList() {
|
||||
this.loading = true;
|
||||
// 默认显示当前用户的待审批记录
|
||||
getFinancePendingApprovals(this.queryParams).then(response => {
|
||||
this.approvalList = response.data || [];
|
||||
this.total = this.approvalList.length;
|
||||
this.loading = false;
|
||||
}).catch(error => {
|
||||
console.error('获取待审批列表失败:', error);
|
||||
this.loading = false;
|
||||
this.$modal.msgError('获取待审批列表失败');
|
||||
});
|
||||
getFinancePendingApprovals(this.queryParams)
|
||||
.then((response) => {
|
||||
this.approvalList = response.data || [];
|
||||
this.total = this.approvalList.length;
|
||||
this.loading = false;
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('获取待审批列表失败:', error);
|
||||
this.loading = false;
|
||||
this.$modal.msgError('获取待审批列表失败');
|
||||
});
|
||||
},
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
@@ -377,7 +374,7 @@ export default {
|
||||
/** 重置按钮操作 */
|
||||
resetQuery() {
|
||||
this.daterangeSubmitTime = [];
|
||||
this.resetForm("queryFormRef");
|
||||
this.resetForm('queryFormRef');
|
||||
this.handleQuery();
|
||||
},
|
||||
|
||||
@@ -385,15 +382,17 @@ export default {
|
||||
showPendingApprovals() {
|
||||
this.loading = true;
|
||||
|
||||
getFinancePendingApprovals({}).then(response => {
|
||||
this.approvalList = response.data || [];
|
||||
this.total = this.approvalList.length;
|
||||
this.loading = false;
|
||||
this.$modal.msgSuccess(`查询到 ${this.total} 条待审批记录`);
|
||||
}).catch(error => {
|
||||
this.loading = false;
|
||||
this.$modal.msgError('获取待审批列表失败');
|
||||
});
|
||||
getFinancePendingApprovals({})
|
||||
.then((response) => {
|
||||
this.approvalList = response.data || [];
|
||||
this.total = this.approvalList.length;
|
||||
this.loading = false;
|
||||
this.$modal.msgSuccess(`查询到 ${this.total} 条待审批记录`);
|
||||
})
|
||||
.catch((error) => {
|
||||
this.loading = false;
|
||||
this.$modal.msgError('获取待审批列表失败');
|
||||
});
|
||||
},
|
||||
|
||||
showMyApprovals() {
|
||||
@@ -401,22 +400,24 @@ export default {
|
||||
console.log('🔍 查询财务已审批记录');
|
||||
|
||||
// 使用专门的财务审批已审批查询
|
||||
getFinanceApprovedRecords({}).then(response => {
|
||||
this.approvalList = response.data || [];
|
||||
this.total = this.approvalList.length;
|
||||
this.loading = false;
|
||||
this.$modal.msgSuccess(`查询到 ${this.total} 条已审批记录`);
|
||||
console.log('✅ 财务已审批记录:', this.approvalList);
|
||||
}).catch(error => {
|
||||
console.error('❌ 获取财务已审批列表失败:', error);
|
||||
this.loading = false;
|
||||
this.$modal.msgError('获取已审批列表失败');
|
||||
});
|
||||
getFinanceApprovedRecords({})
|
||||
.then((response) => {
|
||||
this.approvalList = response.data || [];
|
||||
this.total = this.approvalList.length;
|
||||
this.loading = false;
|
||||
this.$modal.msgSuccess(`查询到 ${this.total} 条已审批记录`);
|
||||
console.log('✅ 财务已审批记录:', this.approvalList);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('❌ 获取财务已审批列表失败:', error);
|
||||
this.loading = false;
|
||||
this.$modal.msgError('获取已审批列表失败');
|
||||
});
|
||||
},
|
||||
|
||||
/** 多选框选中数据 */
|
||||
handleSelectionChange(selection) {
|
||||
this.ids = selection.map(item => item.applicationId);
|
||||
this.ids = selection.map((item) => item.applicationId);
|
||||
this.single = selection.length !== 1;
|
||||
this.multiple = !selection.length;
|
||||
console.log('提取的项目申报ID:', this.ids);
|
||||
@@ -437,7 +438,7 @@ export default {
|
||||
this.approvalForm.applicationId = row.applicationId;
|
||||
this.approvalDialog.title = `财务审批 - ${row.applicationNo}`;
|
||||
this.approvalDialog.visible = true;
|
||||
|
||||
|
||||
// 预加载院领导列表
|
||||
this.loadAvailableApprovers(row.applicationId);
|
||||
},
|
||||
@@ -457,7 +458,7 @@ export default {
|
||||
/** 审批结果改变时的处理 */
|
||||
onApprovalResultChange(value) {
|
||||
console.log('📋 审批结果改变:', value);
|
||||
|
||||
|
||||
if (value === 'approve') {
|
||||
// 选择通过时,加载院领导列表
|
||||
if (this.currentApplicationId) {
|
||||
@@ -472,20 +473,20 @@ export default {
|
||||
/** 获取可选院领导列表 */
|
||||
async loadAvailableApprovers(applicationId) {
|
||||
console.log('🔍 开始加载院领导列表,applicationId:', applicationId);
|
||||
|
||||
|
||||
if (!applicationId) {
|
||||
console.warn('⚠️ applicationId为空,无法获取院领导列表');
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
this.loadingApprovers = true;
|
||||
try {
|
||||
const response = await getAvailableApprovers(applicationId);
|
||||
console.log('📊 API响应成功,数据长度:', response.data?.length);
|
||||
|
||||
|
||||
this.availableApprovers = response.data || [];
|
||||
console.log('✅ 获取院领导列表成功:', this.availableApprovers.length, '个院领导');
|
||||
|
||||
|
||||
if (this.availableApprovers.length === 0) {
|
||||
this.$modal.msgWarning('没有找到可用的院领导,请联系管理员');
|
||||
}
|
||||
@@ -500,20 +501,22 @@ export default {
|
||||
|
||||
/** 提交财务审批 */
|
||||
submitApproval() {
|
||||
this.$refs["approvalFormRef"].validate(valid => {
|
||||
this.$refs['approvalFormRef'].validate((valid) => {
|
||||
if (valid) {
|
||||
this.approvalDialog.loading = true;
|
||||
|
||||
financeApproveApplication(this.approvalForm).then(response => {
|
||||
this.$modal.msgSuccess("财务审批成功");
|
||||
this.approvalDialog.visible = false;
|
||||
this.approvalDialog.loading = false;
|
||||
this.getList();
|
||||
}).catch(error => {
|
||||
console.error('财务审批失败:', error);
|
||||
this.approvalDialog.loading = false;
|
||||
this.$modal.msgError('财务审批失败');
|
||||
});
|
||||
financeApproveApplication(this.approvalForm)
|
||||
.then((response) => {
|
||||
this.$modal.msgSuccess('财务审批成功');
|
||||
this.approvalDialog.visible = false;
|
||||
this.approvalDialog.loading = false;
|
||||
this.getList();
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('财务审批失败:', error);
|
||||
this.approvalDialog.loading = false;
|
||||
this.$modal.msgError('财务审批失败');
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
@@ -526,21 +529,23 @@ export default {
|
||||
}
|
||||
|
||||
this.$modal.confirm('确认要批量通过选中的申报吗?').then(() => {
|
||||
const batchData = this.ids.map(applicationId => ({
|
||||
const batchData = this.ids.map((applicationId) => ({
|
||||
applicationId: applicationId,
|
||||
approvalResult: 'approve',
|
||||
approvalComment: '财务批量审批通过'
|
||||
}));
|
||||
|
||||
batchFinanceApproveApplication(batchData).then(response => {
|
||||
this.$modal.msgSuccess("批量审批成功");
|
||||
this.getList();
|
||||
this.ids = [];
|
||||
this.multiple = true;
|
||||
}).catch(error => {
|
||||
console.error('批量审批失败:', error);
|
||||
this.$modal.msgError('批量审批失败');
|
||||
});
|
||||
batchFinanceApproveApplication(batchData)
|
||||
.then((response) => {
|
||||
this.$modal.msgSuccess('批量审批成功');
|
||||
this.getList();
|
||||
this.ids = [];
|
||||
this.multiple = true;
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('批量审批失败:', error);
|
||||
this.$modal.msgError('批量审批失败');
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
@@ -552,40 +557,48 @@ export default {
|
||||
}
|
||||
|
||||
this.$modal.confirm('确认要批量退回选中的申报吗?').then(() => {
|
||||
const batchData = this.ids.map(applicationId => ({
|
||||
const batchData = this.ids.map((applicationId) => ({
|
||||
applicationId: applicationId,
|
||||
approvalResult: 'reject',
|
||||
approvalComment: '财务审批退回,请修改后重新提交'
|
||||
}));
|
||||
|
||||
batchFinanceApproveApplication(batchData).then(response => {
|
||||
this.$modal.msgSuccess("批量退回成功");
|
||||
this.getList();
|
||||
this.ids = [];
|
||||
this.multiple = true;
|
||||
}).catch(error => {
|
||||
console.error('批量退回失败:', error);
|
||||
this.$modal.msgError('批量退回失败');
|
||||
});
|
||||
batchFinanceApproveApplication(batchData)
|
||||
.then((response) => {
|
||||
this.$modal.msgSuccess('批量退回成功');
|
||||
this.getList();
|
||||
this.ids = [];
|
||||
this.multiple = true;
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('批量退回失败:', error);
|
||||
this.$modal.msgError('批量退回失败');
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
/** 审批历史 */
|
||||
handleApprovalHistory(row) {
|
||||
getApprovalHistory(row.applicationId).then(response => {
|
||||
this.approvalHistory = response.data || [];
|
||||
this.historyDialog.visible = true;
|
||||
}).catch(error => {
|
||||
console.error('获取审批历史失败:', error);
|
||||
this.$modal.msgError('获取审批历史失败');
|
||||
});
|
||||
getApprovalHistory(row.applicationId)
|
||||
.then((response) => {
|
||||
this.approvalHistory = response.data || [];
|
||||
this.historyDialog.visible = true;
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('获取审批历史失败:', error);
|
||||
this.$modal.msgError('获取审批历史失败');
|
||||
});
|
||||
},
|
||||
|
||||
/** 导出按钮操作 */
|
||||
handleExport() {
|
||||
this.download('cw/approval/export', {
|
||||
...this.queryParams
|
||||
}, `finance_approval_${new Date().getTime()}.xlsx`);
|
||||
this.download(
|
||||
'cw/approval/export',
|
||||
{
|
||||
...this.queryParams
|
||||
},
|
||||
`finance_approval_${new Date().getTime()}.xlsx`
|
||||
);
|
||||
},
|
||||
|
||||
/** 格式化金额 */
|
||||
@@ -597,30 +610,42 @@ export default {
|
||||
/** 获取时间线类型 */
|
||||
getTimelineType(action) {
|
||||
switch (action) {
|
||||
case 'approve': return 'success';
|
||||
case 'reject': return 'danger';
|
||||
case 'submit': return 'primary';
|
||||
default: return 'info';
|
||||
case 'approve':
|
||||
return 'success';
|
||||
case 'reject':
|
||||
return 'danger';
|
||||
case 'submit':
|
||||
return 'primary';
|
||||
default:
|
||||
return 'info';
|
||||
}
|
||||
},
|
||||
|
||||
/** 获取审批结果类型 */
|
||||
getApprovalResultType(action) {
|
||||
switch (action) {
|
||||
case 'approve': return 'success';
|
||||
case 'reject': return 'danger';
|
||||
case 'submit': return 'primary';
|
||||
default: return 'info';
|
||||
case 'approve':
|
||||
return 'success';
|
||||
case 'reject':
|
||||
return 'danger';
|
||||
case 'submit':
|
||||
return 'primary';
|
||||
default:
|
||||
return 'info';
|
||||
}
|
||||
},
|
||||
|
||||
/** 获取审批结果文本 */
|
||||
getApprovalResultText(action) {
|
||||
switch (action) {
|
||||
case 'approve': return '通过';
|
||||
case 'reject': return '退回';
|
||||
case 'submit': return '提交';
|
||||
default: return '待处理';
|
||||
case 'approve':
|
||||
return '通过';
|
||||
case 'reject':
|
||||
return '退回';
|
||||
case 'submit':
|
||||
return '提交';
|
||||
default:
|
||||
return '待处理';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -125,13 +125,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>
|
||||
|
||||
<!-- 项目详情对话框 -->
|
||||
@@ -166,14 +160,7 @@
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="审批意见" prop="approvalComment">
|
||||
<el-input
|
||||
v-model="approvalForm.approvalComment"
|
||||
type="textarea"
|
||||
:rows="4"
|
||||
placeholder="请输入审批意见"
|
||||
maxlength="500"
|
||||
show-word-limit
|
||||
/>
|
||||
<el-input v-model="approvalForm.approvalComment" type="textarea" :rows="4" placeholder="请输入审批意见" maxlength="500" show-word-limit />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
@@ -196,7 +183,8 @@
|
||||
<el-card>
|
||||
<h4>{{ record.nodeName }}</h4>
|
||||
<p><strong>审批人:</strong>{{ record.approverName }}</p>
|
||||
<p><strong>审批结果:</strong>
|
||||
<p>
|
||||
<strong>审批结果:</strong>
|
||||
<el-tag :type="getApprovalResultType(record.approveAction)">
|
||||
{{ getApprovalResultText(record.approveAction) }}
|
||||
</el-tag>
|
||||
@@ -221,10 +209,10 @@ import {
|
||||
exportApprovalRecord,
|
||||
getLeaderPendingApprovals,
|
||||
getLeaderApprovedApprovals
|
||||
} from "@/api/cw/approvalRecord";
|
||||
import { leaderApproveApplication, batchLeaderApproveApplication } from "@/api/cw/leaderApproval";
|
||||
import { useDict } from "@/utils/dict";
|
||||
import ApplicationSummary from "../application/summary.vue";
|
||||
} from '@/api/cw/approvalRecord';
|
||||
import { leaderApproveApplication, batchLeaderApproveApplication } from '@/api/cw/leaderApproval';
|
||||
import { useDict } from '@/utils/dict';
|
||||
import ApplicationSummary from '../application/summary.vue';
|
||||
|
||||
export default {
|
||||
name: 'CwLeaderApproval',
|
||||
@@ -302,12 +290,10 @@ export default {
|
||||
},
|
||||
// 审批表单验证
|
||||
approvalRules: {
|
||||
approvalResult: [
|
||||
{ required: true, message: "请选择审批结果", trigger: "change" }
|
||||
],
|
||||
approvalResult: [{ required: true, message: '请选择审批结果', trigger: 'change' }],
|
||||
approvalComment: [
|
||||
{ required: true, message: "请输入审批意见", trigger: "blur" },
|
||||
{ min: 5, max: 500, message: "审批意见长度在 5 到 500 个字符", trigger: "blur" }
|
||||
{ required: true, message: '请输入审批意见', trigger: 'blur' },
|
||||
{ min: 5, max: 500, message: '审批意见长度在 5 到 500 个字符', trigger: 'blur' }
|
||||
]
|
||||
},
|
||||
// 审批历史数据
|
||||
@@ -322,15 +308,17 @@ export default {
|
||||
getList() {
|
||||
this.loading = true;
|
||||
// 使用学院领导专用的待审批记录查询
|
||||
getLeaderPendingApprovals(this.queryParams).then(response => {
|
||||
this.approvalList = response.data || [];
|
||||
this.total = this.approvalList.length;
|
||||
this.loading = false;
|
||||
}).catch(error => {
|
||||
console.error('获取待审批列表失败:', error);
|
||||
this.loading = false;
|
||||
this.$modal.msgError('获取待审批列表失败');
|
||||
});
|
||||
getLeaderPendingApprovals(this.queryParams)
|
||||
.then((response) => {
|
||||
this.approvalList = response.data || [];
|
||||
this.total = this.approvalList.length;
|
||||
this.loading = false;
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('获取待审批列表失败:', error);
|
||||
this.loading = false;
|
||||
this.$modal.msgError('获取待审批列表失败');
|
||||
});
|
||||
},
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
@@ -342,7 +330,7 @@ export default {
|
||||
/** 重置按钮操作 */
|
||||
resetQuery() {
|
||||
this.daterangeSubmitTime = [];
|
||||
this.resetForm("queryFormRef");
|
||||
this.resetForm('queryFormRef');
|
||||
this.handleQuery();
|
||||
},
|
||||
|
||||
@@ -351,39 +339,43 @@ export default {
|
||||
console.log('=== 🔍 查询学院领导待审批 ===');
|
||||
this.loading = true;
|
||||
|
||||
getLeaderPendingApprovals({}).then(response => {
|
||||
console.log('✅ 学院领导待审批数据获取成功:', response.data);
|
||||
this.approvalList = response.data || [];
|
||||
this.total = this.approvalList.length;
|
||||
this.loading = false;
|
||||
this.$modal.msgSuccess(`查询到 ${this.total} 条待审批记录`);
|
||||
}).catch(error => {
|
||||
console.error('❌ 获取学院领导待审批列表失败:', error);
|
||||
this.loading = false;
|
||||
this.$modal.msgError('获取待审批列表失败');
|
||||
});
|
||||
getLeaderPendingApprovals({})
|
||||
.then((response) => {
|
||||
console.log('✅ 学院领导待审批数据获取成功:', response.data);
|
||||
this.approvalList = response.data || [];
|
||||
this.total = this.approvalList.length;
|
||||
this.loading = false;
|
||||
this.$modal.msgSuccess(`查询到 ${this.total} 条待审批记录`);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('❌ 获取学院领导待审批列表失败:', error);
|
||||
this.loading = false;
|
||||
this.$modal.msgError('获取待审批列表失败');
|
||||
});
|
||||
},
|
||||
|
||||
showMyApprovals() {
|
||||
console.log('=== 🔍 查询我已审批 ===');
|
||||
this.loading = true;
|
||||
|
||||
getLeaderApprovedApprovals({}).then(response => {
|
||||
console.log('✅ 已审批数据获取成功:', response.data);
|
||||
this.approvalList = response.data || [];
|
||||
this.total = this.approvalList.length;
|
||||
this.loading = false;
|
||||
this.$modal.msgSuccess(`查询到 ${this.total} 条已审批记录`);
|
||||
}).catch(error => {
|
||||
console.error('❌ 获取已审批列表失败:', error);
|
||||
this.loading = false;
|
||||
this.$modal.msgError('获取已审批列表失败');
|
||||
});
|
||||
getLeaderApprovedApprovals({})
|
||||
.then((response) => {
|
||||
console.log('✅ 已审批数据获取成功:', response.data);
|
||||
this.approvalList = response.data || [];
|
||||
this.total = this.approvalList.length;
|
||||
this.loading = false;
|
||||
this.$modal.msgSuccess(`查询到 ${this.total} 条已审批记录`);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('❌ 获取已审批列表失败:', error);
|
||||
this.loading = false;
|
||||
this.$modal.msgError('获取已审批列表失败');
|
||||
});
|
||||
},
|
||||
|
||||
/** 多选框选中数据 */
|
||||
handleSelectionChange(selection) {
|
||||
this.ids = selection.map(item => item.applicationId);
|
||||
this.ids = selection.map((item) => item.applicationId);
|
||||
this.single = selection.length !== 1;
|
||||
this.multiple = !selection.length;
|
||||
console.log('提取的项目申报ID:', this.ids);
|
||||
@@ -417,20 +409,22 @@ export default {
|
||||
|
||||
/** 提交审批 */
|
||||
submitApproval() {
|
||||
this.$refs["approvalFormRef"].validate(valid => {
|
||||
this.$refs['approvalFormRef'].validate((valid) => {
|
||||
if (valid) {
|
||||
this.approvalDialog.loading = true;
|
||||
|
||||
leaderApproveApplication(this.approvalForm).then(response => {
|
||||
this.$modal.msgSuccess("审批成功");
|
||||
this.approvalDialog.visible = false;
|
||||
this.approvalDialog.loading = false;
|
||||
this.getList();
|
||||
}).catch(error => {
|
||||
console.error('审批失败:', error);
|
||||
this.approvalDialog.loading = false;
|
||||
this.$modal.msgError('审批失败');
|
||||
});
|
||||
leaderApproveApplication(this.approvalForm)
|
||||
.then((response) => {
|
||||
this.$modal.msgSuccess('审批成功');
|
||||
this.approvalDialog.visible = false;
|
||||
this.approvalDialog.loading = false;
|
||||
this.getList();
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('审批失败:', error);
|
||||
this.approvalDialog.loading = false;
|
||||
this.$modal.msgError('审批失败');
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
@@ -443,21 +437,23 @@ export default {
|
||||
}
|
||||
|
||||
this.$modal.confirm('确认要批量通过选中的申报吗?').then(() => {
|
||||
const batchData = this.ids.map(applicationId => ({
|
||||
const batchData = this.ids.map((applicationId) => ({
|
||||
applicationId: applicationId,
|
||||
approvalResult: 'approve',
|
||||
approvalComment: '分管领导批量审批通过'
|
||||
}));
|
||||
|
||||
batchLeaderApproveApplication(batchData).then(response => {
|
||||
this.$modal.msgSuccess("批量审批成功");
|
||||
this.getList();
|
||||
this.ids = [];
|
||||
this.multiple = true;
|
||||
}).catch(error => {
|
||||
console.error('批量审批失败:', error);
|
||||
this.$modal.msgError('批量审批失败');
|
||||
});
|
||||
batchLeaderApproveApplication(batchData)
|
||||
.then((response) => {
|
||||
this.$modal.msgSuccess('批量审批成功');
|
||||
this.getList();
|
||||
this.ids = [];
|
||||
this.multiple = true;
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('批量审批失败:', error);
|
||||
this.$modal.msgError('批量审批失败');
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
@@ -469,40 +465,48 @@ export default {
|
||||
}
|
||||
|
||||
this.$modal.confirm('确认要批量退回选中的申报吗?').then(() => {
|
||||
const batchData = this.ids.map(applicationId => ({
|
||||
const batchData = this.ids.map((applicationId) => ({
|
||||
applicationId: applicationId,
|
||||
approvalResult: 'reject',
|
||||
approvalComment: '分管领导审批退回,请修改后重新提交'
|
||||
}));
|
||||
|
||||
batchLeaderApproveApplication(batchData).then(response => {
|
||||
this.$modal.msgSuccess("批量退回成功");
|
||||
this.getList();
|
||||
this.ids = [];
|
||||
this.multiple = true;
|
||||
}).catch(error => {
|
||||
console.error('批量退回失败:', error);
|
||||
this.$modal.msgError('批量退回失败');
|
||||
});
|
||||
batchLeaderApproveApplication(batchData)
|
||||
.then((response) => {
|
||||
this.$modal.msgSuccess('批量退回成功');
|
||||
this.getList();
|
||||
this.ids = [];
|
||||
this.multiple = true;
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('批量退回失败:', error);
|
||||
this.$modal.msgError('批量退回失败');
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
/** 审批历史 */
|
||||
handleApprovalHistory(row) {
|
||||
getApprovalHistory(row.applicationId).then(response => {
|
||||
this.approvalHistory = response.data || [];
|
||||
this.historyDialog.visible = true;
|
||||
}).catch(error => {
|
||||
console.error('获取审批历史失败:', error);
|
||||
this.$modal.msgError('获取审批历史失败');
|
||||
});
|
||||
getApprovalHistory(row.applicationId)
|
||||
.then((response) => {
|
||||
this.approvalHistory = response.data || [];
|
||||
this.historyDialog.visible = true;
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('获取审批历史失败:', error);
|
||||
this.$modal.msgError('获取审批历史失败');
|
||||
});
|
||||
},
|
||||
|
||||
/** 导出按钮操作 */
|
||||
handleExport() {
|
||||
this.download('cw/approval/export', {
|
||||
...this.queryParams
|
||||
}, `leader_approval_${new Date().getTime()}.xlsx`);
|
||||
this.download(
|
||||
'cw/approval/export',
|
||||
{
|
||||
...this.queryParams
|
||||
},
|
||||
`leader_approval_${new Date().getTime()}.xlsx`
|
||||
);
|
||||
},
|
||||
|
||||
/** 格式化金额 */
|
||||
@@ -514,30 +518,42 @@ export default {
|
||||
/** 获取时间线类型 */
|
||||
getTimelineType(action) {
|
||||
switch (action) {
|
||||
case 'approve': return 'success';
|
||||
case 'reject': return 'danger';
|
||||
case 'submit': return 'primary';
|
||||
default: return 'info';
|
||||
case 'approve':
|
||||
return 'success';
|
||||
case 'reject':
|
||||
return 'danger';
|
||||
case 'submit':
|
||||
return 'primary';
|
||||
default:
|
||||
return 'info';
|
||||
}
|
||||
},
|
||||
|
||||
/** 获取审批结果类型 */
|
||||
getApprovalResultType(action) {
|
||||
switch (action) {
|
||||
case 'approve': return 'success';
|
||||
case 'reject': return 'danger';
|
||||
case 'submit': return 'primary';
|
||||
default: return 'info';
|
||||
case 'approve':
|
||||
return 'success';
|
||||
case 'reject':
|
||||
return 'danger';
|
||||
case 'submit':
|
||||
return 'primary';
|
||||
default:
|
||||
return 'info';
|
||||
}
|
||||
},
|
||||
|
||||
/** 获取审批结果文本 */
|
||||
getApprovalResultText(action) {
|
||||
switch (action) {
|
||||
case 'approve': return '通过';
|
||||
case 'reject': return '退回';
|
||||
case 'submit': return '提交';
|
||||
default: return '待处理';
|
||||
case 'approve':
|
||||
return '通过';
|
||||
case 'reject':
|
||||
return '退回';
|
||||
case 'submit':
|
||||
return '提交';
|
||||
default:
|
||||
return '待处理';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,7 +64,7 @@
|
||||
<el-col :span="12">
|
||||
<div class="chart-container">
|
||||
<h4>项目状态分布</h4>
|
||||
<div ref="statusChart" style="height: 300px;"></div>
|
||||
<div ref="statusChart" style="height: 300px"></div>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
@@ -74,9 +74,7 @@
|
||||
<el-table-column prop="statusLabel" label="状态" width="120" />
|
||||
<el-table-column prop="count" label="数量" align="center" />
|
||||
<el-table-column label="占比" align="center">
|
||||
<template #default="scope">
|
||||
{{ getPercentage(scope.row.count, statusStatistics.totalCount) }}%
|
||||
</template>
|
||||
<template #default="scope"> {{ getPercentage(scope.row.count, statusStatistics.totalCount) }}% </template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
@@ -101,9 +99,7 @@
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="通过率" align="center" width="100">
|
||||
<template #default="scope">
|
||||
{{ getPercentage(scope.row.approvedCount, scope.row.projectCount) }}%
|
||||
</template>
|
||||
<template #default="scope"> {{ getPercentage(scope.row.approvedCount, scope.row.projectCount) }}% </template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-tab-pane>
|
||||
@@ -125,24 +121,14 @@
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="通过率" align="center" width="100">
|
||||
<template #default="scope">
|
||||
{{ getPercentage(scope.row.approvedCount, scope.row.projectCount) }}%
|
||||
</template>
|
||||
<template #default="scope"> {{ getPercentage(scope.row.approvedCount, scope.row.projectCount) }}% </template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-tab-pane>
|
||||
|
||||
<!-- 项目清单 -->
|
||||
<el-tab-pane label="项目清单" name="project">
|
||||
<el-table
|
||||
:data="projectList"
|
||||
stripe
|
||||
v-loading="loading"
|
||||
class="scroll-table"
|
||||
border
|
||||
:height="600"
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-table :data="projectList" stripe v-loading="loading" class="scroll-table" border :height="600" style="width: 100%">
|
||||
<el-table-column prop="applicationNo" label="申报编号" width="160" fixed="left" />
|
||||
<el-table-column prop="projectName" label="项目名称" width="200" show-overflow-tooltip />
|
||||
<el-table-column prop="departmentName" label="申报部门" width="150" />
|
||||
@@ -214,16 +200,11 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {
|
||||
getStatisticsList,
|
||||
getStatusStatistics,
|
||||
getDepartmentStatistics,
|
||||
getYearlyStatistics
|
||||
} from '@/api/cw/statistics'
|
||||
import { parseTime } from '@/utils/ruoyi'
|
||||
import * as echarts from 'echarts'
|
||||
import Pagination from '@/components/Pagination/index.vue'
|
||||
import ApplicationSummary from '@/views/cw/application/summary.vue'
|
||||
import { getStatisticsList, getStatusStatistics, getDepartmentStatistics, getYearlyStatistics } from '@/api/cw/statistics';
|
||||
import { parseTime } from '@/utils/ruoyi';
|
||||
import * as echarts from 'echarts';
|
||||
import Pagination from '@/components/Pagination/index.vue';
|
||||
import ApplicationSummary from '@/views/cw/application/summary.vue';
|
||||
|
||||
export default {
|
||||
name: 'CwStatistics',
|
||||
@@ -274,37 +255,33 @@ export default {
|
||||
applicationId: null
|
||||
},
|
||||
currentApplication: null
|
||||
}
|
||||
};
|
||||
},
|
||||
created() {
|
||||
this.getStatisticsData()
|
||||
this.getStatisticsData();
|
||||
},
|
||||
mounted() {
|
||||
this.$nextTick(() => {
|
||||
this.initStatusChart()
|
||||
})
|
||||
this.initStatusChart();
|
||||
});
|
||||
},
|
||||
beforeUnmount() {
|
||||
if (this.statusChartInstance) {
|
||||
this.statusChartInstance.dispose()
|
||||
this.statusChartInstance.dispose();
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
/** 获取统计数据 */
|
||||
async getStatisticsData() {
|
||||
try {
|
||||
await Promise.all([
|
||||
this.getStatusStatistics(),
|
||||
this.getDepartmentStatistics(),
|
||||
this.getYearlyStatistics()
|
||||
])
|
||||
if (this.activeTab === 'project') {
|
||||
this.getProjectList()
|
||||
await Promise.all([this.getStatusStatistics(), this.getDepartmentStatistics(), this.getYearlyStatistics()]);
|
||||
if (this.activeTab === 'project') {
|
||||
this.getProjectList();
|
||||
}
|
||||
} finally {
|
||||
// 确保初始化完成后重置loading状态
|
||||
if (this.activeTab !== 'project') {
|
||||
this.loading = false
|
||||
this.loading = false;
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -312,53 +289,53 @@ export default {
|
||||
/** 获取状态统计 */
|
||||
async getStatusStatistics() {
|
||||
try {
|
||||
const response = await getStatusStatistics(this.queryParams.budgetYear)
|
||||
this.statusStatistics = response.data
|
||||
this.updateStatusChart()
|
||||
const response = await getStatusStatistics(this.queryParams.budgetYear);
|
||||
this.statusStatistics = response.data;
|
||||
this.updateStatusChart();
|
||||
} catch (error) {
|
||||
console.error('获取状态统计失败:', error)
|
||||
this.$modal.msgError('获取状态统计失败')
|
||||
console.error('获取状态统计失败:', error);
|
||||
this.$modal.msgError('获取状态统计失败');
|
||||
}
|
||||
},
|
||||
|
||||
/** 获取部门统计 */
|
||||
async getDepartmentStatistics() {
|
||||
try {
|
||||
const response = await getDepartmentStatistics(this.queryParams.budgetYear)
|
||||
this.departmentStatistics = response.data
|
||||
const response = await getDepartmentStatistics(this.queryParams.budgetYear);
|
||||
this.departmentStatistics = response.data;
|
||||
} catch (error) {
|
||||
console.error('获取部门统计失败:', error)
|
||||
this.$modal.msgError('获取部门统计失败')
|
||||
console.error('获取部门统计失败:', error);
|
||||
this.$modal.msgError('获取部门统计失败');
|
||||
}
|
||||
},
|
||||
|
||||
/** 获取年度统计 */
|
||||
async getYearlyStatistics() {
|
||||
try {
|
||||
const response = await getYearlyStatistics()
|
||||
this.yearlyStatistics = response.data
|
||||
const response = await getYearlyStatistics();
|
||||
this.yearlyStatistics = response.data;
|
||||
} catch (error) {
|
||||
console.error('获取年度统计失败:', error)
|
||||
this.$modal.msgError('获取年度统计失败')
|
||||
console.error('获取年度统计失败:', error);
|
||||
this.$modal.msgError('获取年度统计失败');
|
||||
}
|
||||
},
|
||||
|
||||
/** 获取项目清单 */
|
||||
async getProjectList() {
|
||||
try {
|
||||
this.loading = true
|
||||
console.log('🎯 [统计页面-项目清单] 开始获取数据,查询参数:', this.queryParams)
|
||||
this.loading = true;
|
||||
console.log('🎯 [统计页面-项目清单] 开始获取数据,查询参数:', this.queryParams);
|
||||
|
||||
const response = await getStatisticsList({
|
||||
...this.queryParams,
|
||||
type: 'project'
|
||||
})
|
||||
});
|
||||
|
||||
console.log('🎯 [统计页面-项目清单] API响应:', response)
|
||||
console.log('🎯 [统计页面-项目清单] API响应:', response);
|
||||
|
||||
if (response.code === 200) {
|
||||
this.projectList = response.rows || []
|
||||
this.projectTotal = response.total || 0
|
||||
this.projectList = response.rows || [];
|
||||
this.projectTotal = response.total || 0;
|
||||
|
||||
// 如果没有数据,添加一些测试数据来演示样式效果
|
||||
if (this.projectList.length === 0) {
|
||||
@@ -414,79 +391,79 @@ export default {
|
||||
expectedEndDate: '2024-07-31',
|
||||
remarks: '校园道路两侧绿化带改造及景观提升'
|
||||
}
|
||||
]
|
||||
this.projectTotal = 3
|
||||
];
|
||||
this.projectTotal = 3;
|
||||
}
|
||||
|
||||
console.log('🎯 [统计页面-项目清单] 数据设置完成,项目列表长度:', this.projectList?.length || 0)
|
||||
console.log('🎯 [统计页面-项目清单] 数据设置完成,项目列表长度:', this.projectList?.length || 0);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('🎯 [统计页面-项目清单] 获取数据失败:', error)
|
||||
this.$modal.msgError('获取项目清单失败')
|
||||
console.error('🎯 [统计页面-项目清单] 获取数据失败:', error);
|
||||
this.$modal.msgError('获取项目清单失败');
|
||||
} finally {
|
||||
this.loading = false
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
handleQuery() {
|
||||
this.queryParams.pageNum = 1
|
||||
this.getStatisticsData()
|
||||
this.queryParams.pageNum = 1;
|
||||
this.getStatisticsData();
|
||||
},
|
||||
|
||||
/** 重置按钮操作 */
|
||||
resetQuery() {
|
||||
this.queryParams.budgetYear = ''
|
||||
this.handleQuery()
|
||||
this.queryParams.budgetYear = '';
|
||||
this.handleQuery();
|
||||
},
|
||||
|
||||
/** Tab切换 */
|
||||
handleTabClick(tab) {
|
||||
if (tab.name === 'project') {
|
||||
this.getProjectList()
|
||||
this.getProjectList();
|
||||
}
|
||||
},
|
||||
|
||||
/** 查看项目详情 */
|
||||
handleViewProject(row) {
|
||||
console.log('🔍 [统计页面] 查看项目详情:', row)
|
||||
console.log('🔍 [统计页面] 查看项目详情:', row);
|
||||
|
||||
this.currentApplication = row
|
||||
this.detailDialog.title = `项目详情 - ${row.projectName || '未命名项目'}`
|
||||
this.detailDialog.visible = true
|
||||
this.currentApplication = row;
|
||||
this.detailDialog.title = `项目详情 - ${row.projectName || '未命名项目'}`;
|
||||
this.detailDialog.visible = true;
|
||||
|
||||
console.log('🔍 [统计页面] 设置currentApplication:', this.currentApplication)
|
||||
console.log('🔍 [统计页面] applicationId:', row.applicationId)
|
||||
console.log('🔍 [统计页面] 设置currentApplication:', this.currentApplication);
|
||||
console.log('🔍 [统计页面] applicationId:', row.applicationId);
|
||||
},
|
||||
|
||||
/** 初始化状态图表 */
|
||||
initStatusChart() {
|
||||
if (this.$refs.statusChart) {
|
||||
this.statusChartInstance = echarts.init(this.$refs.statusChart)
|
||||
this.updateStatusChart()
|
||||
this.statusChartInstance = echarts.init(this.$refs.statusChart);
|
||||
this.updateStatusChart();
|
||||
|
||||
// 监听窗口大小变化
|
||||
window.addEventListener('resize', () => {
|
||||
if (this.statusChartInstance) {
|
||||
this.statusChartInstance.resize()
|
||||
this.statusChartInstance.resize();
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
/** 更新状态图表 */
|
||||
updateStatusChart() {
|
||||
if (!this.statusChartInstance || !this.statusStatistics.statusStatistics) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
// 过滤掉数量为0的状态,只显示有数据的状态
|
||||
const data = this.statusStatistics.statusStatistics
|
||||
.filter(item => item.count > 0)
|
||||
.map(item => ({
|
||||
.filter((item) => item.count > 0)
|
||||
.map((item) => ({
|
||||
name: item.statusLabel,
|
||||
value: item.count
|
||||
}))
|
||||
}));
|
||||
|
||||
// 如果没有数据,显示空状态提示
|
||||
if (data.length === 0) {
|
||||
@@ -500,9 +477,9 @@ export default {
|
||||
fontSize: 16
|
||||
}
|
||||
}
|
||||
}
|
||||
this.statusChartInstance.setOption(option)
|
||||
return
|
||||
};
|
||||
this.statusChartInstance.setOption(option);
|
||||
return;
|
||||
}
|
||||
|
||||
// 定义状态对应的颜色
|
||||
@@ -514,7 +491,7 @@ export default {
|
||||
'已拒绝': '#F56C6C',
|
||||
'已退回': '#E6A23C',
|
||||
'已取消': '#909399'
|
||||
}
|
||||
};
|
||||
|
||||
const option = {
|
||||
tooltip: {
|
||||
@@ -541,8 +518,8 @@ export default {
|
||||
}
|
||||
},
|
||||
itemStyle: {
|
||||
color: function(params) {
|
||||
return statusColors[params.name] || '#409EFF'
|
||||
color: function (params) {
|
||||
return statusColors[params.name] || '#409EFF';
|
||||
}
|
||||
},
|
||||
label: {
|
||||
@@ -554,41 +531,41 @@ export default {
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
this.statusChartInstance.setOption(option, true)
|
||||
this.statusChartInstance.setOption(option, true);
|
||||
},
|
||||
|
||||
/** 获取已通过项目数量 */
|
||||
getApprovedCount() {
|
||||
const approvedStat = this.statusStatistics.statusStatistics?.find(item => item.status === 'approved')
|
||||
return approvedStat ? approvedStat.count : 0
|
||||
const approvedStat = this.statusStatistics.statusStatistics?.find((item) => item.status === 'approved');
|
||||
return approvedStat ? approvedStat.count : 0;
|
||||
},
|
||||
|
||||
/** 格式化金额 */
|
||||
formatAmount(amount) {
|
||||
if (!amount) return '0.00'
|
||||
return Number(amount).toFixed(2)
|
||||
if (!amount) return '0.00';
|
||||
return Number(amount).toFixed(2);
|
||||
},
|
||||
|
||||
/** 计算百分比 */
|
||||
getPercentage(numerator, denominator) {
|
||||
if (!denominator || denominator === 0) return '0.00'
|
||||
return ((numerator / denominator) * 100).toFixed(2)
|
||||
if (!denominator || denominator === 0) return '0.00';
|
||||
return ((numerator / denominator) * 100).toFixed(2);
|
||||
},
|
||||
|
||||
parseTime,
|
||||
|
||||
/** 查看项目详情 */
|
||||
viewDetail(row) {
|
||||
console.log('查看项目详情:', row)
|
||||
this.currentApplication = row
|
||||
this.detailDialog.visible = true
|
||||
this.detailDialog.title = `项目详情 - ${row.projectName}`
|
||||
this.detailDialog.applicationId = row.applicationId
|
||||
console.log('查看项目详情:', row);
|
||||
this.currentApplication = row;
|
||||
this.detailDialog.visible = true;
|
||||
this.detailDialog.title = `项目详情 - ${row.projectName}`;
|
||||
this.detailDialog.applicationId = row.applicationId;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -4,37 +4,38 @@
|
||||
<el-form :model="form" label-width="120px">
|
||||
<el-form-item label="测试数据1">
|
||||
<el-button @click="loadTestData1" type="primary">加载单个OSS ID</el-button>
|
||||
<span style="margin-left: 10px; color: #666;">测试单个OSS文件回显</span>
|
||||
<span style="margin-left: 10px; color: #666">测试单个OSS文件回显</span>
|
||||
</el-form-item>
|
||||
|
||||
|
||||
<el-form-item label="测试数据2">
|
||||
<el-button @click="loadTestData2" type="primary">加载多个OSS ID</el-button>
|
||||
<span style="margin-left: 10px; color: #666;">测试多个OSS文件回显</span>
|
||||
<span style="margin-left: 10px; color: #666">测试多个OSS文件回显</span>
|
||||
</el-form-item>
|
||||
|
||||
|
||||
<el-form-item label="测试数据3">
|
||||
<el-button @click="loadTestData3" type="primary">加载URL格式</el-button>
|
||||
<span style="margin-left: 10px; color: #666;">测试直接URL回显</span>
|
||||
<span style="margin-left: 10px; color: #666">测试直接URL回显</span>
|
||||
</el-form-item>
|
||||
|
||||
|
||||
<el-form-item label="清空数据">
|
||||
<el-button @click="clearData" type="danger">清空</el-button>
|
||||
<span style="margin-left: 10px; color: #666;">清空所有图片数据</span>
|
||||
<span style="margin-left: 10px; color: #666">清空所有图片数据</span>
|
||||
</el-form-item>
|
||||
|
||||
|
||||
<el-form-item label="当前值">
|
||||
<el-input v-model="form.images" type="textarea" :rows="3" readonly />
|
||||
</el-form-item>
|
||||
|
||||
|
||||
<el-form-item label="图片上传">
|
||||
<ImageUpload v-model="form.images" :limit="5" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
|
||||
<div class="test-info">
|
||||
<h3>测试说明</h3>
|
||||
<ul>
|
||||
<li><strong>功能验证</strong>:
|
||||
<li>
|
||||
<strong>功能验证</strong>:
|
||||
<ol>
|
||||
<li>界面打开时历史图片是否正常显示(通过认证接口获取blob URL)</li>
|
||||
<li>点击图片预览是否正常工作</li>
|
||||
@@ -42,7 +43,8 @@
|
||||
<li>删除图片功能是否正常</li>
|
||||
</ol>
|
||||
</li>
|
||||
<li><strong>技术要点</strong>:
|
||||
<li>
|
||||
<strong>技术要点</strong>:
|
||||
<ul>
|
||||
<li>使用 Promise.all 并行处理多个文件的blob URL转换</li>
|
||||
<li>缓存机制避免重复请求相同图片的认证数据</li>
|
||||
@@ -89,30 +91,31 @@ const clearData = () => {
|
||||
<style lang="scss" scoped>
|
||||
.image-upload-test {
|
||||
padding: 20px;
|
||||
|
||||
|
||||
.test-info {
|
||||
margin-top: 30px;
|
||||
padding: 20px;
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 4px;
|
||||
|
||||
|
||||
h3 {
|
||||
margin-top: 0;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
ul, ol {
|
||||
|
||||
ul,
|
||||
ol {
|
||||
margin-left: 20px;
|
||||
|
||||
|
||||
li {
|
||||
margin-bottom: 8px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
strong {
|
||||
color: #409EFF;
|
||||
color: #409eff;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
@@ -149,8 +149,11 @@
|
||||
<el-table-column prop="studentCode" label="学号" width="120" />
|
||||
<el-table-column prop="studentName" label="姓名" width="100" />
|
||||
<el-table-column prop="gender" label="性别" width="80" />
|
||||
<<<<<<< HEAD
|
||||
<el-table-column prop="collegeName" label="学院" width="120" />
|
||||
<el-table-column prop="majorName" label="专业" width="120" />
|
||||
=======
|
||||
>>>>>>> 4b9dbe40fb94a83d379f7d16211dec6d78c23755
|
||||
<el-table-column prop="phone" label="联系方式" width="130" />
|
||||
<el-table-column prop="email" label="邮箱" />
|
||||
<el-table-column label="在校状态" width="100">
|
||||
|
||||
@@ -12,20 +12,34 @@
|
||||
@keyup.enter="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<<<<<<< HEAD
|
||||
<el-form-item label="学号" prop="studentNo">
|
||||
<el-input
|
||||
v-model="queryParams.studentNo"
|
||||
=======
|
||||
<el-form-item label="学号" prop="studentCode">
|
||||
<el-input
|
||||
v-model="queryParams.studentCode"
|
||||
>>>>>>> 4b9dbe40fb94a83d379f7d16211dec6d78c23755
|
||||
placeholder="请输入学号"
|
||||
clearable
|
||||
@keyup.enter="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<<<<<<< HEAD
|
||||
<el-form-item label="申请状态" prop="status">
|
||||
<el-select v-model="queryParams.status" placeholder="请选择申请状态" clearable>
|
||||
<el-option label="草稿" value="draft" />
|
||||
<el-option label="待审核" value="waiting" />
|
||||
<el-option label="已完成" value="complete" />
|
||||
<el-option label="已拒绝" value="rejected" />
|
||||
=======
|
||||
<el-form-item label="申请状态" prop="applicationStatus">
|
||||
<el-select v-model="queryParams.applicationStatus" placeholder="请选择申请状态" clearable>
|
||||
<el-option label="草稿" value="draft" />
|
||||
<el-option label="已提交" value="submitted" />
|
||||
<el-option label="已审核" value="reviewed" />
|
||||
>>>>>>> 4b9dbe40fb94a83d379f7d16211dec6d78c23755
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
@@ -75,6 +89,7 @@
|
||||
<el-col :span="24">
|
||||
<el-table v-loading="loading" :data="applicationList" @selection-change="handleSelectionChange">
|
||||
<el-table-column type="selection" width="55" align="center" />
|
||||
<<<<<<< HEAD
|
||||
<el-table-column label="学号" align="center" prop="studentNo" min-width="120" />
|
||||
<el-table-column label="学生姓名" align="center" prop="studentName" min-width="100" />
|
||||
<el-table-column label="学院" align="center" prop="collegeName" min-width="120" />
|
||||
@@ -104,11 +119,41 @@
|
||||
<el-table-column label="申请时间" align="center" prop="createTime" min-width="160">
|
||||
<template #default="scope">
|
||||
<span>{{ parseTime(scope.row.createTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
|
||||
=======
|
||||
<el-table-column label="学号" align="center" prop="studentCode" min-width="120" />
|
||||
<el-table-column label="学生姓名" align="center" prop="studentName" min-width="100" />
|
||||
<el-table-column label="班级" align="center" prop="className" min-width="120" />
|
||||
<el-table-column label="奖学金类型" align="center" prop="scholarshipType" min-width="120" />
|
||||
<el-table-column label="等级" align="center" prop="scholarshipLevel" min-width="80" />
|
||||
<el-table-column label="金额" align="center" prop="amount" min-width="100">
|
||||
<template #default="scope">
|
||||
<span class="text-primary font-bold">¥{{ scope.row.amount }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="申请状态" align="center" prop="applicationStatus" min-width="100">
|
||||
<template #default="scope">
|
||||
<el-tag v-if="scope.row.applicationStatus === 'draft'" type="info">草稿</el-tag>
|
||||
<el-tag v-else-if="scope.row.applicationStatus === 'submitted'" type="warning">已提交</el-tag>
|
||||
<el-tag v-else-if="scope.row.applicationStatus === 'reviewed'" type="success">已审核</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="审核状态" align="center" prop="auditStatus" min-width="100">
|
||||
<template #default="scope">
|
||||
<el-tag v-if="scope.row.auditStatus === 'pending'" type="warning">待审核</el-tag>
|
||||
<el-tag v-else-if="scope.row.auditStatus === 'approved'" type="success">通过</el-tag>
|
||||
<el-tag v-else-if="scope.row.auditStatus === 'rejected'" type="danger">拒绝</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="申请时间" align="center" prop="applicationTime" min-width="160">
|
||||
<template #default="scope">
|
||||
<span>{{ parseTime(scope.row.applicationTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
|
||||
>>>>>>> 4b9dbe40fb94a83d379f7d16211dec6d78c23755
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="160">
|
||||
<template #default="scope">
|
||||
<el-tooltip content="修改" placement="top">
|
||||
<<<<<<< HEAD
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
@@ -116,6 +161,9 @@
|
||||
@click="handleUpdate(scope.row)"
|
||||
v-hasPermi="['jcbt:scholarshipApplication:edit']"
|
||||
></el-button>
|
||||
=======
|
||||
<el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['jcbt:scholarshipApplication:edit']"></el-button>
|
||||
>>>>>>> 4b9dbe40fb94a83d379f7d16211dec6d78c23755
|
||||
</el-tooltip>
|
||||
<el-tooltip content="删除" placement="top">
|
||||
<el-button link type="danger" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['jcbt:scholarshipApplication:remove']"></el-button>
|
||||
@@ -169,6 +217,7 @@
|
||||
v-for="type in availableScholarshipTypes"
|
||||
:key="type.id"
|
||||
class="mr-2 mb-2"
|
||||
<<<<<<< HEAD
|
||||
:type="getQuotaStatusType(type)"
|
||||
>
|
||||
<div class="flex flex-col">
|
||||
@@ -207,6 +256,14 @@
|
||||
总选择学生数: {{ selectedStudents.length }} 人
|
||||
</div>
|
||||
</div>
|
||||
=======
|
||||
type="success"
|
||||
>
|
||||
{{ type.name }} - {{ type.levelType }} (¥{{ type.amount }})
|
||||
</el-tag>
|
||||
</div>
|
||||
</div>
|
||||
>>>>>>> 4b9dbe40fb94a83d379f7d16211dec6d78c23755
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
@@ -278,11 +335,16 @@
|
||||
<div class="flex items-start space-x-4">
|
||||
<el-checkbox
|
||||
:model-value="isStudentSelected(student.id)"
|
||||
<<<<<<< HEAD
|
||||
@change="(val) => toggleStudentSelection(student, !!val)"
|
||||
=======
|
||||
@change="toggleStudentSelection(student, $event)"
|
||||
>>>>>>> 4b9dbe40fb94a83d379f7d16211dec6d78c23755
|
||||
/>
|
||||
<div class="flex-1">
|
||||
<div class="student-basic-info flex items-center space-x-4 mb-2">
|
||||
<span class="font-bold">{{ student.studentName }}</span>
|
||||
<<<<<<< HEAD
|
||||
<span class="text-gray-600">{{ student.studentNo }}</span>
|
||||
<span class="text-gray-500">{{ student.gender }}</span>
|
||||
<span class="text-gray-500">{{ student.phone }}</span>
|
||||
@@ -293,6 +355,12 @@
|
||||
<span v-if="student.className">{{ student.className }}</span>
|
||||
<span v-if="student.grade">{{ student.grade }}级</span>
|
||||
</div>
|
||||
=======
|
||||
<span class="text-gray-600">{{ student.studentCode }}</span>
|
||||
<span class="text-gray-500">{{ student.gender }}</span>
|
||||
<span class="text-gray-500">{{ student.phone }}</span>
|
||||
</div>
|
||||
>>>>>>> 4b9dbe40fb94a83d379f7d16211dec6d78c23755
|
||||
|
||||
<div v-if="isStudentSelected(student.id)" class="scholarship-selection mt-3 p-3 bg-gray-50 rounded">
|
||||
<div class="grid grid-cols-3 gap-4">
|
||||
@@ -309,7 +377,10 @@
|
||||
:key="type.id"
|
||||
:label="`${type.name} (¥${type.amount})`"
|
||||
:value="type"
|
||||
<<<<<<< HEAD
|
||||
:disabled="isScholarshipTypeDisabled(student, type)"
|
||||
=======
|
||||
>>>>>>> 4b9dbe40fb94a83d379f7d16211dec6d78c23755
|
||||
/>
|
||||
</el-select>
|
||||
</div>
|
||||
@@ -317,7 +388,11 @@
|
||||
<label class="block text-sm font-medium mb-1">等级</label>
|
||||
<el-input
|
||||
v-model="student.selectedLevel"
|
||||
<<<<<<< HEAD
|
||||
:value="String(student.selectedScholarshipType?.levelType || '')"
|
||||
=======
|
||||
:value="student.selectedScholarshipType?.levelType || ''"
|
||||
>>>>>>> 4b9dbe40fb94a83d379f7d16211dec6d78c23755
|
||||
readonly
|
||||
/>
|
||||
</div>
|
||||
@@ -361,8 +436,11 @@
|
||||
|
||||
<script setup name="ScholarshipApplication" lang="ts">
|
||||
import { ref, reactive, computed, getCurrentInstance, ComponentInternalInstance, onMounted, nextTick } from 'vue';
|
||||
<<<<<<< HEAD
|
||||
import { useRouter } from 'vue-router';
|
||||
import { ElMessage } from 'element-plus';
|
||||
=======
|
||||
>>>>>>> 4b9dbe40fb94a83d379f7d16211dec6d78c23755
|
||||
import type { FormInstance, DateModelType } from 'element-plus';
|
||||
import { Search, Plus, Edit, Delete, Refresh } from '@element-plus/icons-vue';
|
||||
|
||||
@@ -377,7 +455,10 @@ import {
|
||||
getClassInfoByTeacher,
|
||||
getClassStudents,
|
||||
getScholarshipTypesByBatch,
|
||||
<<<<<<< HEAD
|
||||
getScholarshipTypesWithQuota,
|
||||
=======
|
||||
>>>>>>> 4b9dbe40fb94a83d379f7d16211dec6d78c23755
|
||||
batchSubmitByTeacher
|
||||
} from '@/api/jcbt/scholarshipApplication';
|
||||
|
||||
@@ -386,27 +467,45 @@ import {
|
||||
getScholarshipBatchByStatus
|
||||
} from '@/api/jcbt/scholarshipBatch';
|
||||
|
||||
<<<<<<< HEAD
|
||||
// 导入用户状态管理
|
||||
import { useUserStore } from '@/store/modules/user';
|
||||
|
||||
// 导入路由
|
||||
const router = useRouter();
|
||||
|
||||
=======
|
||||
// 导入通用类型
|
||||
import type { PageQuery } from '@/utils/ruoyi';
|
||||
|
||||
// 导入用户状态管理
|
||||
import { useUserStore } from '@/store/modules/user';
|
||||
|
||||
>>>>>>> 4b9dbe40fb94a83d379f7d16211dec6d78c23755
|
||||
// 本地类型定义
|
||||
interface ScholarshipApplicationVO {
|
||||
id?: number | string;
|
||||
batchId?: number | string;
|
||||
<<<<<<< HEAD
|
||||
studentId?: string | number;
|
||||
studentName?: string;
|
||||
studentNo?: string;
|
||||
=======
|
||||
studentId?: string;
|
||||
studentName?: string;
|
||||
studentCode?: string;
|
||||
>>>>>>> 4b9dbe40fb94a83d379f7d16211dec6d78c23755
|
||||
classId?: number | string;
|
||||
scholarshipTypeId?: number | string;
|
||||
scholarshipLevel?: string;
|
||||
amount?: number;
|
||||
applicationStatus?: string;
|
||||
auditStatus?: string;
|
||||
<<<<<<< HEAD
|
||||
currentStatus?: string;
|
||||
status?: string; // 前端兼容字段,与currentStatus保持一致
|
||||
=======
|
||||
>>>>>>> 4b9dbe40fb94a83d379f7d16211dec6d78c23755
|
||||
applicationTime?: string;
|
||||
createTime?: string;
|
||||
}
|
||||
@@ -415,6 +514,7 @@ interface QueryParams {
|
||||
pageNum: number;
|
||||
pageSize: number;
|
||||
studentName?: string;
|
||||
<<<<<<< HEAD
|
||||
studentNo?: string;
|
||||
status?: string;
|
||||
}
|
||||
@@ -434,6 +534,15 @@ interface ScholarshipApplicationQuery {
|
||||
studentNo?: string;
|
||||
studentName?: string;
|
||||
applicantId?: string | number;
|
||||
=======
|
||||
studentCode?: string;
|
||||
status?: string;
|
||||
}
|
||||
|
||||
interface BatchQuery {
|
||||
pageNum?: number;
|
||||
pageSize?: number;
|
||||
>>>>>>> 4b9dbe40fb94a83d379f7d16211dec6d78c23755
|
||||
status?: string;
|
||||
}
|
||||
|
||||
@@ -448,6 +557,15 @@ interface ScholarshipBatchVO {
|
||||
createTime?: string;
|
||||
}
|
||||
|
||||
<<<<<<< HEAD
|
||||
=======
|
||||
interface ScholarshipBatchQuery {
|
||||
pageNum?: number;
|
||||
pageSize?: number;
|
||||
status?: string;
|
||||
}
|
||||
|
||||
>>>>>>> 4b9dbe40fb94a83d379f7d16211dec6d78c23755
|
||||
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
||||
|
||||
// 用户状态管理
|
||||
@@ -467,7 +585,11 @@ const queryParams = ref<QueryParams>({
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
studentName: '',
|
||||
<<<<<<< HEAD
|
||||
studentNo: '',
|
||||
=======
|
||||
studentCode: '',
|
||||
>>>>>>> 4b9dbe40fb94a83d379f7d16211dec6d78c23755
|
||||
status: ''
|
||||
});
|
||||
|
||||
@@ -528,6 +650,7 @@ const rules = reactive({
|
||||
/** 查询奖学金申请列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true;
|
||||
<<<<<<< HEAD
|
||||
const res = await listScholarshipApplication(queryParams.value);
|
||||
console.log('=== API响应调试 ===');
|
||||
console.log('完整响应:', res);
|
||||
@@ -536,6 +659,22 @@ const getList = async () => {
|
||||
applicationList.value = res.rows || [];
|
||||
total.value = res.total || 0;
|
||||
loading.value = false;
|
||||
=======
|
||||
try {
|
||||
const res: any = await listScholarshipApplication(queryParams.value);
|
||||
// 处理响应数据结构
|
||||
if (res.data) {
|
||||
applicationList.value = res.data.rows || res.data.list || res.data || [];
|
||||
total.value = res.data.total || 0;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取列表失败:', error);
|
||||
applicationList.value = [];
|
||||
total.value = 0;
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
>>>>>>> 4b9dbe40fb94a83d379f7d16211dec6d78c23755
|
||||
};
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
@@ -608,6 +747,7 @@ const handleAdd = async () => {
|
||||
|
||||
/** 修改按钮操作 */
|
||||
const handleUpdate = async (row?: ScholarshipApplicationVO) => {
|
||||
<<<<<<< HEAD
|
||||
if (!row || !row.id) {
|
||||
ElMessage.error('请选择要修改的申请记录');
|
||||
return;
|
||||
@@ -621,6 +761,9 @@ const handleUpdate = async (row?: ScholarshipApplicationVO) => {
|
||||
|
||||
// 跳转到编辑页面
|
||||
router.push(`/jcbt/scholarship/application/form/${row.id}`);
|
||||
=======
|
||||
console.log('修改功能暂未实现', row);
|
||||
>>>>>>> 4b9dbe40fb94a83d379f7d16211dec6d78c23755
|
||||
};
|
||||
|
||||
/** 删除按钮操作 */
|
||||
@@ -701,7 +844,11 @@ const getTeacherClassInfo = async () => {
|
||||
/** 设置默认的班级信息 */
|
||||
const setDefaultClassInfo = () => {
|
||||
classInfo.value = {
|
||||
<<<<<<< HEAD
|
||||
teacherId: String(userStore.userId || ''),
|
||||
=======
|
||||
teacherId: userStore.userId || '',
|
||||
>>>>>>> 4b9dbe40fb94a83d379f7d16211dec6d78c23755
|
||||
collegeName: '',
|
||||
majorName: '',
|
||||
className: '',
|
||||
@@ -763,6 +910,7 @@ const handleBatchChange = async (batch: ScholarshipBatchVO) => {
|
||||
if (batch) {
|
||||
form.value.batchId = batch.id;
|
||||
|
||||
<<<<<<< HEAD
|
||||
// 获取该批次可申请的奖学金类型(带名额信息)
|
||||
try {
|
||||
console.log('🎯 获取奖学金类型,批次ID:', batch.id, '班级ID:', classInfo.value.classId);
|
||||
@@ -780,11 +928,20 @@ const handleBatchChange = async (batch: ScholarshipBatchVO) => {
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('❌ 获取奖学金类型失败:', error);
|
||||
=======
|
||||
// 获取该批次可申请的奖学金类型
|
||||
try {
|
||||
const res: any = await getScholarshipTypesByBatch(batch.id);
|
||||
availableScholarshipTypes.value = res.data || [];
|
||||
} catch (error) {
|
||||
console.error('获取奖学金类型失败:', error);
|
||||
>>>>>>> 4b9dbe40fb94a83d379f7d16211dec6d78c23755
|
||||
availableScholarshipTypes.value = [];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
<<<<<<< HEAD
|
||||
/** 获取名额状态对应的标签类型 */
|
||||
const getQuotaStatusType = (type: any) => {
|
||||
if (!type.hasQuota && type.quotaStatus === 'unlimited') {
|
||||
@@ -946,6 +1103,8 @@ const isScholarshipTypeDisabled = (student: any, scholarshipType: any) => {
|
||||
return false;
|
||||
};
|
||||
|
||||
=======
|
||||
>>>>>>> 4b9dbe40fb94a83d379f7d16211dec6d78c23755
|
||||
/** 筛选学生 */
|
||||
const filterStudents = () => {
|
||||
if (!studentKeyword.value) {
|
||||
@@ -953,7 +1112,11 @@ const filterStudents = () => {
|
||||
} else {
|
||||
filteredStudents.value = classStudents.value.filter((student: any) =>
|
||||
student.studentName?.includes(studentKeyword.value) ||
|
||||
<<<<<<< HEAD
|
||||
student.studentNo?.includes(studentKeyword.value)
|
||||
=======
|
||||
student.studentCode?.includes(studentKeyword.value)
|
||||
>>>>>>> 4b9dbe40fb94a83d379f7d16211dec6d78c23755
|
||||
);
|
||||
}
|
||||
};
|
||||
@@ -968,6 +1131,7 @@ const toggleStudentSelection = (student: any, checked: boolean) => {
|
||||
console.log('🔄 切换学生选择状态:', { student, checked });
|
||||
|
||||
if (checked) {
|
||||
<<<<<<< HEAD
|
||||
// 直接使用原始学生对象,初始化奖学金相关字段
|
||||
student.selectedScholarshipType = null;
|
||||
student.selectedLevel = '';
|
||||
@@ -986,12 +1150,22 @@ const toggleStudentSelection = (student: any, checked: boolean) => {
|
||||
ElMessage.warning(`${scholarshipType.name} 名额已满,无法自动选择`);
|
||||
}
|
||||
}
|
||||
=======
|
||||
selectedStudents.value.push({
|
||||
...student,
|
||||
selectedScholarshipType: null,
|
||||
selectedLevel: '',
|
||||
selectedAmount: ''
|
||||
});
|
||||
console.log('✅ 添加学生到选择列表:', student.studentName);
|
||||
>>>>>>> 4b9dbe40fb94a83d379f7d16211dec6d78c23755
|
||||
} else {
|
||||
const index = selectedStudents.value.findIndex((s: any) => s.id === student.id);
|
||||
if (index > -1) {
|
||||
selectedStudents.value.splice(index, 1);
|
||||
console.log('❌ 从选择列表移除学生:', student.studentName);
|
||||
}
|
||||
<<<<<<< HEAD
|
||||
// 清理原始学生对象的奖学金相关字段
|
||||
student.selectedScholarshipType = null;
|
||||
student.selectedLevel = '';
|
||||
@@ -1004,6 +1178,11 @@ const toggleStudentSelection = (student: any, checked: boolean) => {
|
||||
nextTick(() => {
|
||||
// 强制更新响应式数据
|
||||
});
|
||||
=======
|
||||
}
|
||||
|
||||
console.log('📊 当前选择的学生数量:', selectedStudents.value.length);
|
||||
>>>>>>> 4b9dbe40fb94a83d379f7d16211dec6d78c23755
|
||||
};
|
||||
|
||||
/** 全选学生 */
|
||||
@@ -1032,6 +1211,7 @@ const clearSelectedStudents = () => {
|
||||
|
||||
/** 更新奖学金信息 */
|
||||
const updateScholarshipInfo = (student: any) => {
|
||||
<<<<<<< HEAD
|
||||
console.log('🔄 更新奖学金信息:', { studentName: student.studentName, selectedType: student.selectedScholarshipType?.name });
|
||||
|
||||
if (student.selectedScholarshipType) {
|
||||
@@ -1062,20 +1242,29 @@ const updateScholarshipInfo = (student: any) => {
|
||||
// 清空选择时也要清空相关字段
|
||||
student.selectedLevel = '';
|
||||
student.selectedAmount = '';
|
||||
=======
|
||||
if (student.selectedScholarshipType) {
|
||||
student.selectedLevel = student.selectedScholarshipType.level;
|
||||
student.selectedAmount = student.selectedScholarshipType.amount;
|
||||
>>>>>>> 4b9dbe40fb94a83d379f7d16211dec6d78c23755
|
||||
}
|
||||
};
|
||||
|
||||
/** 提交批量申请 */
|
||||
const submitBatchApplication = async () => {
|
||||
try {
|
||||
<<<<<<< HEAD
|
||||
console.log('🚀 开始批量申请提交流程');
|
||||
console.log('📊 当前选择的学生数据:', selectedStudents.value);
|
||||
console.log('📋 可用奖学金类型数据:', availableScholarshipTypes.value);
|
||||
|
||||
=======
|
||||
>>>>>>> 4b9dbe40fb94a83d379f7d16211dec6d78c23755
|
||||
// 表单验证
|
||||
await scholarshipApplicationRef.value?.validate();
|
||||
|
||||
if (selectedStudents.value.length === 0) {
|
||||
<<<<<<< HEAD
|
||||
console.error('❌ 请至少选择一名学生');
|
||||
return;
|
||||
}
|
||||
@@ -1092,6 +1281,19 @@ const submitBatchApplication = async () => {
|
||||
|
||||
console.log('✅ 奖学金类型选择验证通过');
|
||||
|
||||
=======
|
||||
console.error('请至少选择一名学生');
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查每个学生是否都选择了奖学金类型
|
||||
const invalidStudents = selectedStudents.value.filter((s: any) => !s.selectedScholarshipType);
|
||||
if (invalidStudents.length > 0) {
|
||||
console.error('请为所有选择的学生指定奖学金类型');
|
||||
return;
|
||||
}
|
||||
|
||||
>>>>>>> 4b9dbe40fb94a83d379f7d16211dec6d78c23755
|
||||
submitLoading.value = true;
|
||||
|
||||
// 检查是否所有学生都选择了相同的奖学金类型(根据后端接口要求)
|
||||
@@ -1107,12 +1309,20 @@ const submitBatchApplication = async () => {
|
||||
const batchId = form.value.batchId;
|
||||
const classId = classInfo.value.classId;
|
||||
const studentApplications = selectedStudents.value.map((s: any) => ({
|
||||
<<<<<<< HEAD
|
||||
studentId: String(s.studentId || s.id),
|
||||
studentName: s.studentName,
|
||||
studentNo: s.studentNo,
|
||||
scholarshipTypeId: s.selectedScholarshipType.id,
|
||||
deptId: classInfo.value.deptId,
|
||||
applicationReason: `班主任批量申请 - ${s.selectedScholarshipType.name}`,
|
||||
=======
|
||||
studentId: s.studentId,
|
||||
studentName: s.studentName,
|
||||
studentCode: s.studentCode,
|
||||
scholarshipTypeId: s.selectedScholarshipType.id,
|
||||
applicationReason: `班主任批量申请 - ${s.selectedScholarshipType.name}`,
|
||||
>>>>>>> 4b9dbe40fb94a83d379f7d16211dec6d78c23755
|
||||
applicationDescription: `申请等级:${s.selectedScholarshipType.levelType},金额:${s.selectedScholarshipType.amount}元`
|
||||
}));
|
||||
|
||||
@@ -1120,7 +1330,10 @@ const submitBatchApplication = async () => {
|
||||
await batchSubmitByTeacher(batchId, scholarshipTypeId, classId, studentApplications);
|
||||
|
||||
console.log('批量申请提交成功');
|
||||
<<<<<<< HEAD
|
||||
ElMessage.success('批量申请提交成功');
|
||||
=======
|
||||
>>>>>>> 4b9dbe40fb94a83d379f7d16211dec6d78c23755
|
||||
dialog.value.visible = false;
|
||||
|
||||
// 刷新列表
|
||||
@@ -1128,7 +1341,10 @@ const submitBatchApplication = async () => {
|
||||
|
||||
} catch (error) {
|
||||
console.error('提交失败:', error);
|
||||
<<<<<<< HEAD
|
||||
ElMessage.error('提交失败,请重试');
|
||||
=======
|
||||
>>>>>>> 4b9dbe40fb94a83d379f7d16211dec6d78c23755
|
||||
} finally {
|
||||
submitLoading.value = false;
|
||||
}
|
||||
@@ -1254,6 +1470,7 @@ onMounted(() => {
|
||||
padding-top: 2rem;
|
||||
padding-bottom: 2rem;
|
||||
}
|
||||
<<<<<<< HEAD
|
||||
|
||||
.flex-col {
|
||||
flex-direction: column;
|
||||
@@ -1314,4 +1531,6 @@ onMounted(() => {
|
||||
.flex-1 {
|
||||
flex: 1;
|
||||
}
|
||||
=======
|
||||
>>>>>>> 4b9dbe40fb94a83d379f7d16211dec6d78c23755
|
||||
</style>
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,871 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<el-card class="box-card">
|
||||
<!-- 审批管理选项卡 -->
|
||||
<el-tabs v-model="activeTab" type="border-card" @tab-click="handleTabClick">
|
||||
<!-- 待审批申请 -->
|
||||
<el-tab-pane label="待审批申请" name="pending">
|
||||
<div class="tab-content">
|
||||
<!-- 搜索区域 -->
|
||||
<transition :enter-active-class="animate.searchAnimate.enter" :leave-active-class="animate.searchAnimate.leave">
|
||||
<div v-show="showPendingSearch" class="mb-[10px]">
|
||||
<el-card shadow="hover">
|
||||
<el-form ref="pendingQueryForm" :model="pendingQueryParams" label-width="100px">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="5">
|
||||
<el-form-item label="申请人" prop="applicantName">
|
||||
<el-input
|
||||
v-model="pendingQueryParams.applicantName"
|
||||
placeholder="请输入申请人姓名"
|
||||
clearable
|
||||
@keyup.enter="handlePendingQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="5">
|
||||
<el-form-item label="所属部门" prop="department">
|
||||
<el-input
|
||||
v-model="pendingQueryParams.department"
|
||||
placeholder="请输入所属部门"
|
||||
clearable
|
||||
@keyup.enter="handlePendingQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="5">
|
||||
<el-form-item label="目的地" prop="destination">
|
||||
<el-input
|
||||
v-model="pendingQueryParams.destination"
|
||||
placeholder="请输入目的地国家"
|
||||
clearable
|
||||
@keyup.enter="handlePendingQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="9">
|
||||
<el-form-item label="出国时间">
|
||||
<el-date-picker
|
||||
v-model="pendingDateRange"
|
||||
style="width: 100%"
|
||||
type="daterange"
|
||||
range-separator="-"
|
||||
start-placeholder="开始日期"
|
||||
end-placeholder="结束日期"
|
||||
value-format="YYYY-MM-DD"
|
||||
@change="handlePendingDateRangeChange"
|
||||
/>
|
||||
</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="handlePendingQuery">搜索</el-button>
|
||||
<el-button icon="Refresh" @click="resetPendingQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
</el-card>
|
||||
</div>
|
||||
</transition>
|
||||
|
||||
<!-- 操作按钮区域 -->
|
||||
<el-card shadow="never" class="button-card table-card">
|
||||
<template #header>
|
||||
<el-row :gutter="10" class="mb8">
|
||||
<el-col :span="1.5">
|
||||
<el-button icon="Download" @click="handleExportPending" v-hasPermi="['rs:overseas:export']">导出</el-button>
|
||||
</el-col>
|
||||
<right-toolbar v-model:showSearch="showPendingSearch" @queryTable="getPendingList"></right-toolbar>
|
||||
</el-row>
|
||||
</template>
|
||||
|
||||
<!-- 待审批申请表格 -->
|
||||
<el-table v-loading="pendingLoading" border :data="pendingList" @row-dblclick="handleViewApplication">
|
||||
<el-table-column label="申请人" align="center" prop="applicantName" width="100" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="所属部门" align="center" prop="department" width="140" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="出国目的" align="center" prop="purposeText" width="100" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="目的地" align="center" prop="destination" width="120" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="出国时间" align="center" min-width="220" :show-overflow-tooltip="true">
|
||||
<template #default="scope"> {{ formatDate(scope.row.startDate) }} 至 {{ formatDate(scope.row.endDate) }} </template>
|
||||
</el-table-column>
|
||||
<el-table-column label="天数" align="center" prop="duration" width="60" />
|
||||
<el-table-column label="申请时间" align="center" prop="applyTime" width="160" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="申请状态" align="center" width="100">
|
||||
<template #default="scope">
|
||||
<el-tag :type="getStatusTagType(scope.row.status)">{{ scope.row.statusText }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" align="center" width="180">
|
||||
<template #default="scope">
|
||||
<el-tooltip content="查看详情" placement="top">
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
icon="View"
|
||||
@click="handleViewApplication(scope.row)"
|
||||
v-hasPermi="['rs:overseas:query']"
|
||||
></el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="查看审批历史" placement="top" v-if="scope.row.status !== '0'">
|
||||
<el-button
|
||||
link
|
||||
type="info"
|
||||
icon="List"
|
||||
@click.stop="handleViewApprovalHistory(scope.row)"
|
||||
v-hasPermi="['rs:overseas:query']"
|
||||
></el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="通过" placement="top" v-if="scope.row.status === '0'">
|
||||
<el-button
|
||||
link
|
||||
type="success"
|
||||
icon="Check"
|
||||
@click="handleApprovalPass(scope.row)"
|
||||
v-hasPermi="['rs:overseas:approve']"
|
||||
></el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="拒绝" placement="top" v-if="scope.row.status === '0'">
|
||||
<el-button
|
||||
link
|
||||
type="danger"
|
||||
icon="Close"
|
||||
@click="handleApprovalReject(scope.row)"
|
||||
v-hasPermi="['rs:overseas:approve']"
|
||||
></el-button>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<pagination
|
||||
v-show="pendingTotal > 0"
|
||||
:total="pendingTotal"
|
||||
v-model:page="pendingQueryParams.pageNum"
|
||||
v-model:limit="pendingQueryParams.pageSize"
|
||||
@pagination="getPendingList"
|
||||
/>
|
||||
</el-card>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
|
||||
<!-- 审批记录 -->
|
||||
<el-tab-pane label="审批记录" name="record">
|
||||
<div class="tab-content">
|
||||
<!-- 搜索区域 -->
|
||||
<transition :enter-active-class="animate.searchAnimate.enter" :leave-active-class="animate.searchAnimate.leave">
|
||||
<div v-show="showRecordSearch" class="mb-[10px]">
|
||||
<el-card shadow="hover">
|
||||
<el-form ref="recordQueryForm" :model="recordQueryParams" label-width="100px">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="5">
|
||||
<el-form-item label="申请人" prop="applicantName">
|
||||
<el-input
|
||||
v-model="recordQueryParams.applicantName"
|
||||
placeholder="请输入申请人姓名"
|
||||
clearable
|
||||
@keyup.enter="handleRecordQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="5">
|
||||
<el-form-item label="所属部门" prop="department">
|
||||
<el-input v-model="recordQueryParams.department" placeholder="请输入所属部门" clearable @keyup.enter="handleRecordQuery" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="5">
|
||||
<el-form-item label="目的地" prop="destination">
|
||||
<el-input
|
||||
v-model="recordQueryParams.destination"
|
||||
placeholder="请输入目的地国家"
|
||||
clearable
|
||||
@keyup.enter="handleRecordQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="9">
|
||||
<el-form-item label="审批时间">
|
||||
<el-date-picker
|
||||
v-model="recordDateRange"
|
||||
style="width: 100%"
|
||||
type="daterange"
|
||||
range-separator="-"
|
||||
start-placeholder="开始日期"
|
||||
end-placeholder="结束日期"
|
||||
value-format="YYYY-MM-DD"
|
||||
@change="handleRecordDateRangeChange"
|
||||
/>
|
||||
</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="handleRecordQuery">搜索</el-button>
|
||||
<el-button icon="Refresh" @click="resetRecordQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
</el-card>
|
||||
</div>
|
||||
</transition>
|
||||
|
||||
<!-- 操作按钮区域 -->
|
||||
<el-card shadow="never" class="button-card table-card">
|
||||
<template #header>
|
||||
<el-row :gutter="10" class="mb8">
|
||||
<el-col :span="1.5">
|
||||
<el-button icon="Download" @click="handleExportRecord" v-hasPermi="['rs:overseas:export']">导出</el-button>
|
||||
</el-col>
|
||||
<right-toolbar v-model:showSearch="showRecordSearch" @queryTable="getRecordList"></right-toolbar>
|
||||
</el-row>
|
||||
</template>
|
||||
|
||||
<!-- 审批记录表格 -->
|
||||
<el-table v-loading="recordLoading" border :data="recordList" @row-dblclick="handleViewRecord">
|
||||
<el-table-column label="申请人" align="center" prop="applicantName" width="100" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="所属部门" align="center" prop="department" width="140" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="出国目的" align="center" prop="purposeText" width="100" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="目的地" align="center" prop="destination" width="120" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="出国时间" align="center" min-width="220" :show-overflow-tooltip="true">
|
||||
<template #default="scope"> {{ formatDate(scope.row.startDate) }} 至 {{ formatDate(scope.row.endDate) }} </template>
|
||||
</el-table-column>
|
||||
<el-table-column label="天数" align="center" prop="duration" width="60" />
|
||||
<el-table-column label="审批时间" align="center" prop="approvalTime" width="160" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="审批状态" align="center" width="100">
|
||||
<template #default="scope">
|
||||
<el-tag :type="getApprovalStatusTagType(scope.row.approvalStatus)">{{ scope.row.approvalStatusText }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="120">
|
||||
<template #default="scope">
|
||||
<el-tooltip content="查看详情" placement="top">
|
||||
<el-button link type="primary" icon="View" @click="handleViewRecord(scope.row)" v-hasPermi="['rs:overseas:query']"></el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="查看审批历史" placement="top">
|
||||
<el-button
|
||||
link
|
||||
type="info"
|
||||
icon="List"
|
||||
@click.stop="handleViewApprovalHistory(scope.row)"
|
||||
v-hasPermi="['rs:overseas:query']"
|
||||
></el-button>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<pagination
|
||||
v-show="recordTotal > 0"
|
||||
:total="recordTotal"
|
||||
v-model:page="recordQueryParams.pageNum"
|
||||
v-model:limit="recordQueryParams.pageSize"
|
||||
@pagination="getRecordList"
|
||||
/>
|
||||
</el-card>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</el-card>
|
||||
|
||||
<!-- 申请详情对话框 -->
|
||||
<el-dialog title="申请详情" v-model="viewDialog.visible" width="800px" append-to-body>
|
||||
<el-descriptions v-if="viewData" :column="2" border>
|
||||
<el-descriptions-item label="申请人">{{ viewData.applicantName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="所属部门">{{ viewData.department }}</el-descriptions-item>
|
||||
<el-descriptions-item label="联系电话">{{ viewData.contactPhone }}</el-descriptions-item>
|
||||
|
||||
<el-descriptions-item label="出国目的">{{ viewData.purposeText }}</el-descriptions-item>
|
||||
<el-descriptions-item label="目的地">{{ viewData.destination }}</el-descriptions-item>
|
||||
<el-descriptions-item label="开始日期">{{ formatDate(viewData.startDate) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="结束日期">{{ formatDate(viewData.endDate) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="出国天数">{{ viewData.duration }}天</el-descriptions-item>
|
||||
<el-descriptions-item label="预算金额">¥{{ formatMoney(viewData.budget) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="申请状态">
|
||||
<el-tag :type="getStatusTagType(viewData.status)">{{ viewData.statusText }}</el-tag>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="申请时间" :span="2">{{ viewData.applyTime }}</el-descriptions-item>
|
||||
<el-descriptions-item label="目的详细说明" :span="2">{{ viewData.purposeDetail }}</el-descriptions-item>
|
||||
<el-descriptions-item label="行程安排" :span="2">{{ viewData.itinerary }}</el-descriptions-item>
|
||||
<el-descriptions-item label="申请人备注" :span="2" v-if="viewData.applicantRemark">{{ viewData.applicantRemark }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
|
||||
<!-- 查看审批记录 -->
|
||||
<div class="mt-4">
|
||||
<h4>审批记录</h4>
|
||||
<el-table :data="applicationApprovalList" border>
|
||||
<el-table-column label="审批人" align="center" prop="approverName" />
|
||||
<el-table-column label="审批级别" align="center" prop="approvalLevelText" />
|
||||
<el-table-column label="审批结果" align="center" prop="approvalStatusText">
|
||||
<template #default="scope">
|
||||
<el-tag :type="getApprovalStatusTagType(scope.row.approvalStatus)">{{ scope.row.approvalStatusText }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="审批意见" align="center" prop="approvalOpinion" show-overflow-tooltip />
|
||||
<el-table-column label="审批时间" align="center" prop="approvalTime" />
|
||||
</el-table>
|
||||
</div>
|
||||
|
||||
<template #footer v-if="viewData && viewData.status === '0'">
|
||||
<div class="dialog-footer">
|
||||
<el-button type="success" @click="handleApprovalPass(viewData)">通过</el-button>
|
||||
<el-button type="danger" @click="handleApprovalReject(viewData)">拒绝</el-button>
|
||||
<el-button @click="viewDialog.visible = false">关 闭</el-button>
|
||||
</div>
|
||||
</template>
|
||||
<template #footer v-else>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="viewDialog.visible = false">关 闭</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 审批对话框 -->
|
||||
<el-dialog :title="approvalDialog.title" v-model="approvalDialog.visible" width="600px" append-to-body>
|
||||
<el-form ref="approvalFormRef" :model="approvalForm" :rules="approvalRules" label-width="100px">
|
||||
<el-form-item label="审批意见" prop="approvalOpinion">
|
||||
<el-input v-model="approvalForm.approvalOpinion" type="textarea" :rows="4" placeholder="请输入审批意见" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button type="primary" @click="submitApproval">确 定</el-button>
|
||||
<el-button @click="cancelApproval">取 消</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 审批历史对话框 -->
|
||||
<el-dialog title="审批历史" v-model="approvalHistoryDialog.visible" width="900px" top="5vh" :close-on-click-modal="false">
|
||||
<!-- 申请基本信息 -->
|
||||
<div style="margin-bottom: 20px; padding: 15px; background-color: #f8f9fa; border-radius: 4px">
|
||||
<h4 style="margin: 0 0 10px 0; color: #303133">申请信息</h4>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="8">
|
||||
<span style="color: #909399">申请人:</span>
|
||||
<span style="font-weight: 500">{{ currentApplication.applicantName }}</span>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<span style="color: #909399">目的地:</span>
|
||||
<span style="font-weight: 500">{{ currentApplication.destination }}</span>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<span style="color: #909399">申请时间:</span>
|
||||
<span style="font-weight: 500">{{ parseTime(currentApplication.applyTime) }}</span>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
|
||||
<!-- 审批历史列表 -->
|
||||
<el-table v-loading="approvalHistoryLoading" :data="approvalHistoryList" border style="width: 100%">
|
||||
<el-table-column label="审批级别" align="center" prop="approvalLevel" width="100">
|
||||
<template #default="scope">
|
||||
<span v-if="scope.row.approvalLevel === '1'">部门主管</span>
|
||||
<span v-else-if="scope.row.approvalLevel === '2'">分管领导</span>
|
||||
<span v-else-if="scope.row.approvalLevel === '3'">校领导</span>
|
||||
<span v-else>{{ scope.row.approvalLevel }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="审批人" align="center" prop="approverName" width="120" />
|
||||
<el-table-column label="审批时间" align="center" prop="approvalTime" width="160">
|
||||
<template #default="scope">
|
||||
<span>{{ parseTime(scope.row.approvalTime) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="审批结果" align="center" prop="approvalStatus" width="100">
|
||||
<template #default="scope">
|
||||
<el-tag :type="getApprovalStatusTagType(scope.row.approvalStatus)">
|
||||
<span v-if="scope.row.approvalStatus === '0'">待审核</span>
|
||||
<span v-else-if="scope.row.approvalStatus === '1'">通过</span>
|
||||
<span v-else-if="scope.row.approvalStatus === '2'">拒绝</span>
|
||||
<span v-else>{{ scope.row.approvalStatus }}</span>
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="审批意见" align="center" prop="approvalOpinion" min-width="200" :show-overflow-tooltip="true" />
|
||||
</el-table>
|
||||
|
||||
<!-- 无数据提示 -->
|
||||
<el-empty v-if="!approvalHistoryLoading && approvalHistoryList.length === 0" description="暂无审批历史记录" />
|
||||
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="approvalHistoryDialog.visible = false">关 闭</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="OverseasApproval">
|
||||
import { ref, reactive, toRefs, onMounted, getCurrentInstance } from 'vue';
|
||||
import {
|
||||
listPendingOverseasApplication,
|
||||
listOverseasApproval,
|
||||
getOverseasApplication,
|
||||
getOverseasApprovalByApplication,
|
||||
getOverseasApprovalHistory,
|
||||
approveOverseasApplication,
|
||||
exportOverseasApplication,
|
||||
exportOverseasApproval,
|
||||
type OverseasApplicationQuery,
|
||||
type OverseasApplicationVO,
|
||||
type OverseasApprovalQuery,
|
||||
type OverseasApprovalVO,
|
||||
type ApprovalRequest,
|
||||
fixOverseasApprovalData
|
||||
} from '@/api/rs/overseas';
|
||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||
import animate from '@/animate';
|
||||
|
||||
// 响应式数据
|
||||
const { proxy } = getCurrentInstance() as any;
|
||||
|
||||
const state = reactive({
|
||||
// 选项卡相关
|
||||
activeTab: 'pending',
|
||||
|
||||
// 待审批列表相关
|
||||
pendingLoading: false,
|
||||
pendingList: [] as OverseasApplicationVO[],
|
||||
pendingTotal: 0,
|
||||
showPendingSearch: true,
|
||||
pendingDateRange: [] as string[],
|
||||
pendingQueryParams: {
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
applicantName: '',
|
||||
department: '',
|
||||
destination: '',
|
||||
status: '0', // 只查询待审核状态
|
||||
startDateBegin: '',
|
||||
startDateEnd: ''
|
||||
} as OverseasApplicationQuery,
|
||||
|
||||
// 审批记录列表相关
|
||||
recordLoading: false,
|
||||
recordList: [] as OverseasApprovalVO[],
|
||||
recordTotal: 0,
|
||||
showRecordSearch: true,
|
||||
recordDateRange: [] as string[],
|
||||
recordQueryParams: {
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
applicationId: undefined,
|
||||
approvalLevel: '',
|
||||
approvalStatus: '',
|
||||
applicantName: '',
|
||||
department: '',
|
||||
destination: '',
|
||||
approvalTimeBegin: '',
|
||||
approvalTimeEnd: ''
|
||||
} as OverseasApprovalQuery,
|
||||
|
||||
// 查看详情
|
||||
viewDialog: {
|
||||
visible: false
|
||||
},
|
||||
viewData: null as OverseasApplicationVO | null,
|
||||
applicationApprovalList: [] as OverseasApprovalVO[],
|
||||
|
||||
// 审批表单
|
||||
approvalDialog: {
|
||||
visible: false,
|
||||
title: ''
|
||||
},
|
||||
approvalForm: {
|
||||
applicationId: '',
|
||||
approvalStatus: '1',
|
||||
approvalOpinion: ''
|
||||
} as ApprovalRequest,
|
||||
approvalRules: {
|
||||
approvalOpinion: [{ required: true, message: '请输入审批意见', trigger: 'blur' }]
|
||||
},
|
||||
|
||||
// 审批历史
|
||||
approvalHistoryDialog: {
|
||||
visible: false
|
||||
},
|
||||
approvalHistoryLoading: false,
|
||||
approvalHistoryList: [] as OverseasApprovalVO[],
|
||||
currentApplication: {
|
||||
applicationId: 0,
|
||||
applicantName: '',
|
||||
destination: '',
|
||||
applyTime: '',
|
||||
status: '0'
|
||||
},
|
||||
|
||||
// 字典数据
|
||||
approvalLevelOptions: [
|
||||
{ label: '部门主管', value: '1' },
|
||||
{ label: '分管领导', value: '2' },
|
||||
{ label: '校领导', value: '3' }
|
||||
],
|
||||
approvalStatusOptions: [
|
||||
{ label: '待审核', value: '0' },
|
||||
{ label: '通过', value: '1' },
|
||||
{ label: '拒绝', value: '2' }
|
||||
]
|
||||
});
|
||||
|
||||
const {
|
||||
activeTab,
|
||||
pendingLoading,
|
||||
pendingList,
|
||||
pendingTotal,
|
||||
showPendingSearch,
|
||||
pendingDateRange,
|
||||
pendingQueryParams,
|
||||
recordLoading,
|
||||
recordList,
|
||||
recordTotal,
|
||||
showRecordSearch,
|
||||
recordDateRange,
|
||||
recordQueryParams,
|
||||
viewDialog,
|
||||
viewData,
|
||||
applicationApprovalList,
|
||||
approvalDialog,
|
||||
approvalForm,
|
||||
approvalRules,
|
||||
approvalHistoryDialog,
|
||||
approvalHistoryLoading,
|
||||
approvalHistoryList,
|
||||
currentApplication,
|
||||
approvalLevelOptions,
|
||||
approvalStatusOptions
|
||||
} = toRefs(state);
|
||||
|
||||
// 页面加载时
|
||||
onMounted(() => {
|
||||
getPendingList();
|
||||
getRecordList(); // 同时加载审批记录数据
|
||||
});
|
||||
|
||||
// 标签页切换
|
||||
const handleTabClick = (tab: any) => {
|
||||
if (tab.name === 'pending') {
|
||||
getPendingList();
|
||||
} else if (tab.name === 'record') {
|
||||
getRecordList();
|
||||
}
|
||||
};
|
||||
|
||||
// 获取待审批列表
|
||||
const getPendingList = async () => {
|
||||
state.pendingLoading = true;
|
||||
try {
|
||||
const response = await listPendingOverseasApplication(state.pendingQueryParams);
|
||||
state.pendingList = response.rows;
|
||||
state.pendingTotal = response.total;
|
||||
} catch (error: any) {
|
||||
console.error('获取待审批列表失败:', error);
|
||||
proxy.$modal.msgError('获取待审批列表失败: ' + (error?.message || String(error)));
|
||||
} finally {
|
||||
state.pendingLoading = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 获取审批记录列表
|
||||
const getRecordList = async () => {
|
||||
console.log('🔍 开始获取审批记录列表...');
|
||||
console.log('查询参数:', state.recordQueryParams);
|
||||
|
||||
state.recordLoading = true;
|
||||
try {
|
||||
const response = await listOverseasApproval(state.recordQueryParams);
|
||||
console.log('📡 API响应:', response);
|
||||
console.log('📊 响应数据结构-weizhong:', {
|
||||
code: response.code,
|
||||
msg: response.msg,
|
||||
rows: response.rows,
|
||||
total: response.total,
|
||||
rowsLength: response.rows ? response.rows.length : 0
|
||||
});
|
||||
|
||||
state.recordList = response.rows || [];
|
||||
state.recordTotal = response.total || 0;
|
||||
|
||||
console.log('💾 设置后的状态:', {
|
||||
recordList: state.recordList,
|
||||
recordTotal: state.recordTotal,
|
||||
listLength: state.recordList.length
|
||||
});
|
||||
|
||||
if (!response.rows || response.rows.length === 0) {
|
||||
console.warn('⚠️ 审批记录列表为空');
|
||||
} else {
|
||||
console.log('✅ 成功获取到', response.rows.length, '条审批记录');
|
||||
console.log('第一条记录示例:', response.rows[0]);
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error('❌ 获取审批记录列表失败:', error);
|
||||
console.error('错误详情:', {
|
||||
message: error.message,
|
||||
status: error.status,
|
||||
response: error.response
|
||||
});
|
||||
proxy.$modal.msgError('获取审批记录列表失败: ' + (error?.message || String(error)));
|
||||
} finally {
|
||||
state.recordLoading = false;
|
||||
console.log('🏁 审批记录列表加载完成');
|
||||
}
|
||||
};
|
||||
|
||||
// 搜索查询
|
||||
const handlePendingQuery = () => {
|
||||
state.pendingQueryParams.pageNum = 1;
|
||||
getPendingList();
|
||||
};
|
||||
|
||||
const handleRecordQuery = () => {
|
||||
state.recordQueryParams.pageNum = 1;
|
||||
getRecordList();
|
||||
};
|
||||
|
||||
// 重置搜索
|
||||
const resetPendingQuery = () => {
|
||||
proxy.resetForm('pendingQueryForm');
|
||||
state.pendingDateRange = [];
|
||||
state.pendingQueryParams.status = '0'; // 保持查询待审核状态
|
||||
handlePendingQuery();
|
||||
};
|
||||
|
||||
const resetRecordQuery = () => {
|
||||
proxy.resetForm('recordQueryForm');
|
||||
handleRecordQuery();
|
||||
};
|
||||
|
||||
// 处理日期范围变化
|
||||
const handlePendingDateRangeChange = (dates: string[] | null) => {
|
||||
if (dates && dates.length === 2) {
|
||||
state.pendingQueryParams.startDateBegin = dates[0];
|
||||
state.pendingQueryParams.startDateEnd = dates[1];
|
||||
} else {
|
||||
state.pendingQueryParams.startDateBegin = '';
|
||||
state.pendingQueryParams.startDateEnd = '';
|
||||
}
|
||||
};
|
||||
|
||||
// 查看申请详情
|
||||
const handleViewApplication = async (row: OverseasApplicationVO) => {
|
||||
try {
|
||||
const response = await getOverseasApplication(row.applicationId);
|
||||
state.viewData = response.data;
|
||||
|
||||
// 获取审批记录
|
||||
const approvalResponse = await getOverseasApprovalByApplication(row.applicationId);
|
||||
state.applicationApprovalList = approvalResponse.data;
|
||||
|
||||
state.viewDialog.visible = true;
|
||||
} catch (error) {
|
||||
console.error('获取申请详情失败:', error);
|
||||
}
|
||||
};
|
||||
|
||||
// 审批通过
|
||||
const handleApprovalPass = (row: OverseasApplicationVO) => {
|
||||
state.approvalForm.applicationId = row.applicationId;
|
||||
state.approvalForm.approvalStatus = '1';
|
||||
state.approvalForm.approvalOpinion = '';
|
||||
state.approvalDialog.title = '通过';
|
||||
state.approvalDialog.visible = true;
|
||||
};
|
||||
|
||||
// 审批拒绝
|
||||
const handleApprovalReject = (row: OverseasApplicationVO) => {
|
||||
state.approvalForm.applicationId = row.applicationId;
|
||||
state.approvalForm.approvalStatus = '2';
|
||||
state.approvalForm.approvalOpinion = '';
|
||||
state.approvalDialog.title = '拒绝';
|
||||
state.approvalDialog.visible = true;
|
||||
};
|
||||
|
||||
// 提交审批
|
||||
const submitApproval = () => {
|
||||
proxy.$refs.approvalFormRef.validate(async (valid: boolean) => {
|
||||
if (valid) {
|
||||
try {
|
||||
await approveOverseasApplication(state.approvalForm);
|
||||
ElMessage.success('审批成功');
|
||||
state.approvalDialog.visible = false;
|
||||
state.viewDialog.visible = false;
|
||||
getPendingList();
|
||||
} catch (error) {
|
||||
console.error('审批失败:', error);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 取消审批
|
||||
const cancelApproval = () => {
|
||||
state.approvalDialog.visible = false;
|
||||
state.approvalForm = {
|
||||
applicationId: '',
|
||||
approvalStatus: '1',
|
||||
approvalOpinion: ''
|
||||
};
|
||||
};
|
||||
|
||||
// 导出
|
||||
const handleExportPending = async () => {
|
||||
try {
|
||||
await exportOverseasApplication(state.pendingQueryParams);
|
||||
ElMessage.success('导出成功');
|
||||
} catch (error) {
|
||||
console.error('导出失败:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const handleExportRecord = async () => {
|
||||
try {
|
||||
await exportOverseasApproval(state.recordQueryParams);
|
||||
ElMessage.success('导出成功');
|
||||
} catch (error) {
|
||||
console.error('导出失败:', error);
|
||||
}
|
||||
};
|
||||
|
||||
// 查看审批记录详情
|
||||
const handleViewRecord = async (row: OverseasApprovalVO) => {
|
||||
try {
|
||||
// 通过申请ID获取申请详情
|
||||
const response = await getOverseasApplication(row.applicationId);
|
||||
state.viewData = response.data;
|
||||
|
||||
// 获取完整的审批记录历史
|
||||
const approvalResponse = await getOverseasApprovalByApplication(row.applicationId);
|
||||
state.applicationApprovalList = approvalResponse.data;
|
||||
|
||||
// 显示查看对话框
|
||||
state.viewDialog.visible = true;
|
||||
} catch (error: any) {
|
||||
console.error('获取审批记录详情失败:', error);
|
||||
proxy.$modal.msgError('获取详情失败: ' + (error?.message || String(error)));
|
||||
}
|
||||
};
|
||||
|
||||
// 修复审批记录数据
|
||||
const handleFixData = async () => {
|
||||
try {
|
||||
proxy.$modal.confirm('确定要修复审批记录数据吗?这将重置所有测试数据。').then(async () => {
|
||||
const response = await fixOverseasApprovalData();
|
||||
proxy.$modal.msgSuccess(response.msg || '数据修复成功!');
|
||||
// 重新加载列表
|
||||
getRecordList();
|
||||
});
|
||||
} catch (error: any) {
|
||||
console.error('修复数据失败:', error);
|
||||
proxy.$modal.msgError('修复数据失败: ' + (error?.message || String(error)));
|
||||
}
|
||||
};
|
||||
|
||||
// 工具方法
|
||||
const formatDate = (date: string) => {
|
||||
if (!date) return '';
|
||||
return new Date(date).toLocaleDateString();
|
||||
};
|
||||
|
||||
const formatMoney = (amount: number) => {
|
||||
if (!amount) return '0.00';
|
||||
return amount.toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
|
||||
};
|
||||
|
||||
const getStatusTagType = (status: string): 'success' | 'warning' | 'danger' | 'info' | 'primary' => {
|
||||
const typeMap: Record<string, 'success' | 'warning' | 'danger' | 'info' | 'primary'> = {
|
||||
'0': 'warning', // 待审核
|
||||
'1': 'success', // 已通过
|
||||
'2': 'danger', // 已拒绝
|
||||
'3': 'info', // 已取消
|
||||
'4': 'success' // 已完成
|
||||
};
|
||||
return typeMap[status] || 'info';
|
||||
};
|
||||
|
||||
// 查看审批历史
|
||||
const handleViewApprovalHistory = async (row: any) => {
|
||||
approvalHistoryLoading.value = true;
|
||||
approvalHistoryDialog.value.visible = true;
|
||||
|
||||
// 确保有applicationId
|
||||
const applicationId = row.applicationId || 0;
|
||||
if (!applicationId) {
|
||||
ElMessage.warning('无法获取申请ID,请重试');
|
||||
approvalHistoryLoading.value = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// 设置当前应用信息
|
||||
Object.assign(currentApplication.value, {
|
||||
applicationId: applicationId,
|
||||
applicantName: row.applicantName || '未知用户',
|
||||
destination: row.destination || '未知目的地',
|
||||
applyTime: row.applyTime || '',
|
||||
status: row.status || '0'
|
||||
});
|
||||
|
||||
try {
|
||||
// 获取审批历史记录
|
||||
const historyResponse = await getOverseasApprovalHistory(applicationId);
|
||||
|
||||
if (historyResponse.code === 200 && historyResponse.data) {
|
||||
approvalHistoryList.value = historyResponse.data || [];
|
||||
} else {
|
||||
console.warn('审批历史API响应不成功:', historyResponse);
|
||||
approvalHistoryList.value = [];
|
||||
ElMessage.warning('获取审批历史记录未返回有效数据');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取审批历史记录失败:', error);
|
||||
ElMessage.error('获取审批历史记录失败');
|
||||
approvalHistoryList.value = [];
|
||||
} finally {
|
||||
approvalHistoryLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const getApprovalStatusTagType = (status: string): 'success' | 'warning' | 'danger' | 'info' | 'primary' => {
|
||||
const typeMap: Record<string, 'success' | 'warning' | 'danger' | 'info' | 'primary'> = {
|
||||
'0': 'warning', // 待审核
|
||||
'1': 'success', // 通过
|
||||
'2': 'danger' // 拒绝
|
||||
};
|
||||
return typeMap[status] || 'info';
|
||||
};
|
||||
|
||||
// 审批记录日期范围变化处理
|
||||
const handleRecordDateRangeChange = (dates: [string, string] | null) => {
|
||||
if (dates && dates.length === 2) {
|
||||
state.recordQueryParams.approvalTimeBegin = dates[0];
|
||||
state.recordQueryParams.approvalTimeEnd = dates[1];
|
||||
} else {
|
||||
state.recordQueryParams.approvalTimeBegin = '';
|
||||
state.recordQueryParams.approvalTimeEnd = '';
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.search-btn-container {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.button-card {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.table-card {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.mt-4 {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
</style>
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,728 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<el-card class="box-card">
|
||||
<!-- 出国审批管理选项卡 -->
|
||||
<el-tabs v-model="activeTab" type="border-card" @tab-click="handleTabClick">
|
||||
<!-- 出国审批标签页 -->
|
||||
<el-tab-pane label="出国审批" name="approval">
|
||||
<div class="tab-content">
|
||||
<!-- 搜索区域 -->
|
||||
<transition enter-active-class="animate__animated animate__fadeInDown" leave-active-class="animate__animated animate__fadeOutUp">
|
||||
<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="5">
|
||||
<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="5">
|
||||
<el-form-item label="审批状态" prop="approvalStatus">
|
||||
<el-select v-model="queryParams.approvalStatus" placeholder="请选择审批状态" clearable style="width: 100%">
|
||||
<el-option v-for="dict in rs_approval_status" :key="dict.value" :label="dict.label" :value="dict.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="5">
|
||||
<el-form-item label="出国目的" prop="purpose">
|
||||
<el-select v-model="queryParams.purpose" placeholder="请选择出国目的" clearable style="width: 100%">
|
||||
<el-option v-for="dict in rs_overseas_purpose" :key="dict.value" :label="dict.label" :value="dict.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="9">
|
||||
<el-form-item label="审批时间">
|
||||
<el-date-picker
|
||||
v-model="dateRange"
|
||||
style="width: 100%"
|
||||
type="daterange"
|
||||
range-separator="-"
|
||||
start-placeholder="开始日期"
|
||||
end-placeholder="结束日期"
|
||||
@change="handleDateRangeChange"
|
||||
/>
|
||||
</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="button-card table-card">
|
||||
<template #header>
|
||||
<el-row :gutter="10" class="mb8">
|
||||
<el-col :span="1.5">
|
||||
<el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['rs:overseas: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="approvalList"
|
||||
@selection-change="handleSelectionChange"
|
||||
@row-dblclick="handleView"
|
||||
style="width: 100%"
|
||||
class="overseas-approval-table"
|
||||
empty-text="暂无数据"
|
||||
>
|
||||
<el-table-column type="selection" width="55" align="center" />
|
||||
<el-table-column label="申请人" align="center" prop="applicantName" width="100" show-overflow-tooltip />
|
||||
<el-table-column label="出国目的" align="center" prop="purposeText" width="110" show-overflow-tooltip />
|
||||
<el-table-column label="目的地" align="center" prop="destination" min-width="120" show-overflow-tooltip />
|
||||
<el-table-column label="出国时间" align="center" min-width="200" show-overflow-tooltip>
|
||||
<template #default="scope">
|
||||
<span v-if="scope.row.startDate && scope.row.endDate">
|
||||
{{ parseTime(scope.row.startDate, '{y}-{m}-{d}') }} 至 {{ parseTime(scope.row.endDate, '{y}-{m}-{d}') }}
|
||||
</span>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="预算金额" align="center" width="110">
|
||||
<template #default="scope">
|
||||
<span v-if="scope.row.budget">¥{{ formatMoney(scope.row.budget) }}</span>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="审批人" align="center" prop="approverName" width="120" show-overflow-tooltip />
|
||||
<el-table-column label="审批状态" align="center" prop="approvalStatus" width="120">
|
||||
<template #default="scope">
|
||||
<el-tag :type="getApprovalStatusTagType(scope.row.approvalStatus)">
|
||||
<dict-tag :options="rs_approval_status" :value="scope.row.approvalStatus" />
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="审批时间" align="center" prop="approvalTime" width="160" show-overflow-tooltip>
|
||||
<template #default="scope">
|
||||
<span>{{ scope.row.approvalTime ? parseTime(scope.row.approvalTime, '{y}-{m}-{d} {h}:{i}:{s}') : '待审批' }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" align="center" width="120">
|
||||
<template #default="scope">
|
||||
<el-tooltip content="查看详情" placement="top">
|
||||
<el-button link type="primary" icon="View" @click="handleView(scope.row)" v-hasPermi="['rs:overseas:query']"></el-button>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<pagination
|
||||
v-show="total > 0"
|
||||
:total="total"
|
||||
v-model:page="queryParams.pageNum"
|
||||
v-model:limit="queryParams.pageSize"
|
||||
@pagination="getList"
|
||||
/>
|
||||
</el-card>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
|
||||
<!-- 审批记录标签页 -->
|
||||
<el-tab-pane label="审批记录" name="record">
|
||||
<div class="tab-content">
|
||||
<!-- 搜索区域 -->
|
||||
<transition enter-active-class="animate__animated animate__fadeInDown" leave-active-class="animate__animated animate__fadeOutUp">
|
||||
<div v-show="showRecordSearch" class="mb-[10px]">
|
||||
<el-card shadow="hover">
|
||||
<el-form ref="recordQueryForm" :model="recordQueryParams" label-width="100px">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="6">
|
||||
<el-form-item label="申请人" prop="applicantName">
|
||||
<el-input
|
||||
v-model="recordQueryParams.applicantName"
|
||||
placeholder="请输入申请人姓名"
|
||||
clearable
|
||||
@keyup.enter="handleRecordQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="审批人" prop="approverName">
|
||||
<el-input
|
||||
v-model="recordQueryParams.approverName"
|
||||
placeholder="请输入审批人姓名"
|
||||
clearable
|
||||
@keyup.enter="handleRecordQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="审批状态" prop="approvalStatus">
|
||||
<el-select v-model="recordQueryParams.approvalStatus" placeholder="请选择审批状态" clearable style="width: 100%">
|
||||
<el-option v-for="dict in rs_approval_status" :key="dict.value" :label="dict.label" :value="dict.value" />
|
||||
</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="handleRecordQuery">搜索</el-button>
|
||||
<el-button icon="Refresh" @click="resetRecordQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
</el-card>
|
||||
</div>
|
||||
</transition>
|
||||
|
||||
<!-- 操作按钮区域 -->
|
||||
<el-card shadow="never" class="button-card table-card">
|
||||
<template #header>
|
||||
<el-row :gutter="10" class="mb8">
|
||||
<el-col :span="1.5">
|
||||
<el-button icon="Download" @click="handleExportRecord" v-hasPermi="['rs:overseas:export']">导出</el-button>
|
||||
</el-col>
|
||||
<right-toolbar v-model:showSearch="showRecordSearch" @queryTable="getRecordList"></right-toolbar>
|
||||
</el-row>
|
||||
</template>
|
||||
|
||||
<!-- 审批记录表格 -->
|
||||
<el-table
|
||||
v-loading="recordLoading"
|
||||
border
|
||||
:data="recordList"
|
||||
@row-dblclick="handleViewRecord"
|
||||
style="width: 100%"
|
||||
empty-text="暂无数据"
|
||||
>
|
||||
<el-table-column type="selection" width="55" align="center" />
|
||||
<el-table-column label="申请人" align="center" prop="applicantName" width="100" show-overflow-tooltip />
|
||||
<el-table-column label="出国目的" align="center" prop="purposeText" width="110" show-overflow-tooltip />
|
||||
<el-table-column label="目的地" align="center" prop="destination" min-width="120" show-overflow-tooltip />
|
||||
<el-table-column label="出国时间" align="center" min-width="200" show-overflow-tooltip>
|
||||
<template #default="scope">
|
||||
<span v-if="scope.row.startDate && scope.row.endDate">
|
||||
{{ parseTime(scope.row.startDate, '{y}-{m}-{d}') }} 至 {{ parseTime(scope.row.endDate, '{y}-{m}-{d}') }}
|
||||
</span>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="审批人" align="center" prop="approverName" width="120" show-overflow-tooltip />
|
||||
<el-table-column label="审批级别" align="center" prop="approvalLevel" width="110">
|
||||
<template #default="scope">
|
||||
<dict-tag :options="rs_approval_level" :value="scope.row.approvalLevel" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="审批状态" align="center" prop="approvalStatus" width="120">
|
||||
<template #default="scope">
|
||||
<el-tag :type="getApprovalStatusTagType(scope.row.approvalStatus)">
|
||||
<dict-tag :options="rs_approval_status" :value="scope.row.approvalStatus" />
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="审批时间" align="center" prop="approvalTime" width="160" show-overflow-tooltip>
|
||||
<template #default="scope">
|
||||
<span>{{ scope.row.approvalTime ? parseTime(scope.row.approvalTime, '{y}-{m}-{d} {h}:{i}:{s}') : '待审批' }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="审批意见" align="center" prop="approvalOpinion" min-width="200" show-overflow-tooltip />
|
||||
<el-table-column label="操作" align="center" width="200">
|
||||
<template #default="scope">
|
||||
<el-tooltip content="查看详情" placement="top">
|
||||
<el-button link type="primary" icon="View" @click="handleViewRecord(scope.row)" v-hasPermi="['rs:overseas:query']"></el-button>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<pagination
|
||||
v-show="recordTotal > 0"
|
||||
:total="recordTotal"
|
||||
v-model:page="recordQueryParams.pageNum"
|
||||
v-model:limit="recordQueryParams.pageSize"
|
||||
@pagination="getRecordList"
|
||||
/>
|
||||
</el-card>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</el-card>
|
||||
|
||||
<!-- 添加或修改出国审批对话框 -->
|
||||
<el-dialog :title="title" v-model="open" width="500px" append-to-body>
|
||||
<el-form ref="approvalRef" :model="form" :rules="rules" label-width="80px">
|
||||
<el-form-item label="申请ID" prop="applicationId">
|
||||
<el-input v-model="form.applicationId" placeholder="请输入申请ID" />
|
||||
</el-form-item>
|
||||
<el-form-item label="审批人ID" prop="approverId">
|
||||
<el-input v-model="form.approverId" placeholder="请输入审批人ID" />
|
||||
</el-form-item>
|
||||
<el-form-item label="审批人姓名" prop="approverName">
|
||||
<el-input v-model="form.approverName" placeholder="请输入审批人姓名" />
|
||||
</el-form-item>
|
||||
<el-form-item label="审批级别" prop="approvalLevel">
|
||||
<el-select v-model="form.approvalLevel" placeholder="请选择审批级别">
|
||||
<el-option v-for="dict in rs_approval_level" :key="dict.value" :label="dict.label" :value="dict.value"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="审批状态" prop="approvalStatus">
|
||||
<el-radio-group v-model="form.approvalStatus">
|
||||
<el-radio v-for="dict in rs_approval_status" :key="dict.value" :label="dict.value">{{ dict.label }}</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="审批意见" prop="approvalOpinion">
|
||||
<el-input v-model="form.approvalOpinion" type="textarea" placeholder="请输入内容" />
|
||||
</el-form-item>
|
||||
<el-form-item label="审批时间" prop="approvalTime">
|
||||
<el-date-picker clearable v-model="form.approvalTime" type="date" value-format="YYYY-MM-DD" placeholder="请选择审批时间"> </el-date-picker>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button type="primary" @click="submitForm">确 定</el-button>
|
||||
<el-button @click="cancel">取 消</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 查看详情对话框 -->
|
||||
<el-dialog title="审批详情" v-model="detailOpen" width="800px" append-to-body>
|
||||
<el-descriptions :column="2" border>
|
||||
<el-descriptions-item label="审批ID">{{ detailData.approvalId }}</el-descriptions-item>
|
||||
<el-descriptions-item label="申请ID">{{ detailData.applicationId }}</el-descriptions-item>
|
||||
<el-descriptions-item label="申请人">{{ detailData.applicantName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="所属部门">{{ detailData.department }}</el-descriptions-item>
|
||||
<el-descriptions-item label="出国目的">{{ detailData.purposeText }}</el-descriptions-item>
|
||||
<el-descriptions-item label="目的地">{{ detailData.destination }}</el-descriptions-item>
|
||||
<el-descriptions-item label="出国时间">
|
||||
<span v-if="detailData.startDate && detailData.endDate">
|
||||
{{ parseTime(detailData.startDate, '{y}-{m}-{d}') }} 至 {{ parseTime(detailData.endDate, '{y}-{m}-{d}') }}
|
||||
</span>
|
||||
<span v-else>-</span>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="预算金额">
|
||||
<span v-if="detailData.budget">¥{{ formatMoney(detailData.budget) }}</span>
|
||||
<span v-else>-</span>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="审批人">{{ detailData.approverName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="审批级别">
|
||||
<dict-tag :options="rs_approval_level" :value="detailData.approvalLevel" />
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="审批状态">
|
||||
<el-tag :type="getApprovalStatusTagType(detailData.approvalStatus)">
|
||||
<dict-tag :options="rs_approval_status" :value="detailData.approvalStatus" />
|
||||
</el-tag>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="审批时间">
|
||||
{{ detailData.approvalTime ? parseTime(detailData.approvalTime, '{y}-{m}-{d} {h}:{i}:{s}') : '待审批' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="审批意见" :span="2">{{ detailData.approvalOpinion || '待审批' }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 审批记录详情对话框 -->
|
||||
<el-dialog title="审批记录详情" v-model="recordDetailOpen" width="800px" append-to-body>
|
||||
<el-descriptions :column="2" border>
|
||||
<el-descriptions-item label="审批ID">{{ recordDetailData.approvalId }}</el-descriptions-item>
|
||||
<el-descriptions-item label="申请ID">{{ recordDetailData.applicationId }}</el-descriptions-item>
|
||||
<el-descriptions-item label="申请人">{{ recordDetailData.applicantName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="审批人">{{ recordDetailData.approverName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="审批级别">
|
||||
<dict-tag :options="rs_approval_level" :value="recordDetailData.approvalLevel" />
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="审批状态">
|
||||
<el-tag :type="getApprovalStatusTagType(recordDetailData.approvalStatus)">
|
||||
<dict-tag :options="rs_approval_status" :value="recordDetailData.approvalStatus" />
|
||||
</el-tag>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="审批时间">
|
||||
{{ parseTime(recordDetailData.approvalTime, '{y}-{m}-{d} {h}:{i}:{s}') }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="审批意见" :span="2">{{ recordDetailData.approvalOpinion }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup name="OverseasApproval">
|
||||
import {
|
||||
listOverseasApproval,
|
||||
// listCombinedOverseasApproval,
|
||||
getOverseasApproval,
|
||||
delOverseasApproval,
|
||||
addOverseasApproval,
|
||||
updateOverseasApproval
|
||||
} from '@/api/rs/overseas';
|
||||
import { ref, reactive, toRefs, onMounted, getCurrentInstance, nextTick } from 'vue';
|
||||
import { parseTime } from '@/utils/ruoyi';
|
||||
|
||||
const { proxy } = getCurrentInstance();
|
||||
const { rs_approval_status, rs_approval_level, rs_overseas_purpose } = proxy.useDict(
|
||||
'rs_approval_status',
|
||||
'rs_approval_level',
|
||||
'rs_overseas_purpose'
|
||||
);
|
||||
|
||||
// 选项卡相关
|
||||
const activeTab = ref('approval');
|
||||
|
||||
// 出国审批相关
|
||||
const approvalList = ref([]);
|
||||
const open = ref(false);
|
||||
const detailOpen = ref(false);
|
||||
const loading = ref(true);
|
||||
const showSearch = ref(true);
|
||||
const ids = ref([]);
|
||||
const single = ref(true);
|
||||
const multiple = ref(true);
|
||||
const total = ref(0);
|
||||
const title = ref('');
|
||||
const detailData = ref({});
|
||||
const dateRange = ref([]);
|
||||
|
||||
// 审批记录相关
|
||||
const recordList = ref([]);
|
||||
const recordLoading = ref(false);
|
||||
const recordTotal = ref(0);
|
||||
const showRecordSearch = ref(true);
|
||||
const recordDetailOpen = ref(false);
|
||||
const recordDetailData = ref({});
|
||||
|
||||
const data = reactive({
|
||||
form: {},
|
||||
queryParams: {
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
applicationId: null,
|
||||
approverId: null,
|
||||
approverName: null,
|
||||
approvalLevel: null,
|
||||
approvalStatus: null,
|
||||
approvalTimeBegin: null,
|
||||
approvalTimeEnd: null,
|
||||
applicantName: null, // 申请人姓名搜索
|
||||
purpose: null // 出国目的搜索
|
||||
},
|
||||
recordQueryParams: {
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
applicantName: null,
|
||||
approverName: null,
|
||||
approvalStatus: null,
|
||||
approvalTimeBegin: null,
|
||||
approvalTimeEnd: null
|
||||
},
|
||||
rules: {
|
||||
applicationId: [{ required: true, message: '申请ID不能为空', trigger: 'blur' }],
|
||||
approverId: [{ required: true, message: '审批人ID不能为空', trigger: 'blur' }],
|
||||
approverName: [{ required: true, message: '审批人姓名不能为空', trigger: 'blur' }],
|
||||
approvalLevel: [{ required: true, message: '审批级别不能为空', trigger: 'change' }],
|
||||
approvalStatus: [{ required: true, message: '审批状态不能为空', trigger: 'change' }]
|
||||
}
|
||||
});
|
||||
|
||||
const { queryParams, recordQueryParams, form, rules } = toRefs(data);
|
||||
|
||||
/** 格式化金额 */
|
||||
function formatMoney(money) {
|
||||
if (!money) return '0.00';
|
||||
return parseFloat(money).toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
|
||||
}
|
||||
|
||||
/** 获取审批状态标签类型 */
|
||||
function getApprovalStatusTagType(status) {
|
||||
const statusMap = {
|
||||
'0': 'warning', // 待审批
|
||||
'1': 'success', // 已通过
|
||||
'2': 'danger', // 已拒绝
|
||||
'3': 'info' // 已撤回
|
||||
};
|
||||
return statusMap[status] || 'info';
|
||||
}
|
||||
|
||||
/** 选项卡切换 */
|
||||
function handleTabClick(tab) {
|
||||
console.log('切换到选项卡:', tab.name);
|
||||
if (tab.name === 'approval') {
|
||||
getList();
|
||||
} else if (tab.name === 'record') {
|
||||
getRecordList();
|
||||
}
|
||||
}
|
||||
|
||||
/** 查询出国审批列表 */
|
||||
function getList() {
|
||||
loading.value = true;
|
||||
console.log('临时清空出国审批列表内容');
|
||||
|
||||
// 临时清空列表内容
|
||||
approvalList.value = [];
|
||||
total.value = 0;
|
||||
loading.value = false;
|
||||
|
||||
console.log('出国审批列表已清空');
|
||||
}
|
||||
|
||||
/** 查询审批记录列表 */
|
||||
function getRecordList() {
|
||||
recordLoading.value = true;
|
||||
// 审批记录只显示已完成的审批记录,不包含待审批申请
|
||||
listOverseasApproval(recordQueryParams.value)
|
||||
.then((response) => {
|
||||
console.log('审批记录response:', response);
|
||||
recordList.value = response.rows || [];
|
||||
recordTotal.value = response.total || 0;
|
||||
recordLoading.value = false;
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('获取审批记录列表失败:', error);
|
||||
recordList.value = [];
|
||||
recordTotal.value = 0;
|
||||
recordLoading.value = false;
|
||||
proxy.$modal.msgError('获取审批记录失败,请稍后重试');
|
||||
});
|
||||
}
|
||||
|
||||
/** 日期范围变化处理 */
|
||||
function handleDateRangeChange(dates) {
|
||||
if (dates && dates.length === 2) {
|
||||
queryParams.value.approvalTimeBegin = dates[0];
|
||||
queryParams.value.approvalTimeEnd = dates[1];
|
||||
} else {
|
||||
queryParams.value.approvalTimeBegin = null;
|
||||
queryParams.value.approvalTimeEnd = null;
|
||||
}
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
function handleQuery() {
|
||||
queryParams.value.pageNum = 1;
|
||||
getList();
|
||||
}
|
||||
|
||||
/** 重置按钮操作 */
|
||||
function resetQuery() {
|
||||
dateRange.value = [];
|
||||
queryParams.value.approvalTimeBegin = null;
|
||||
queryParams.value.approvalTimeEnd = null;
|
||||
proxy.resetForm('queryFormRef');
|
||||
handleQuery();
|
||||
}
|
||||
|
||||
/** 审批记录搜索按钮操作 */
|
||||
function handleRecordQuery() {
|
||||
recordQueryParams.value.pageNum = 1;
|
||||
getRecordList();
|
||||
}
|
||||
|
||||
/** 审批记录重置按钮操作 */
|
||||
function resetRecordQuery() {
|
||||
proxy.resetForm('recordQueryForm');
|
||||
handleRecordQuery();
|
||||
}
|
||||
|
||||
// 取消按钮
|
||||
function cancel() {
|
||||
open.value = false;
|
||||
reset();
|
||||
}
|
||||
|
||||
// 表单重置
|
||||
function reset() {
|
||||
form.value = {
|
||||
approvalId: null,
|
||||
applicationId: null,
|
||||
approverId: null,
|
||||
approverName: null,
|
||||
approvalLevel: null,
|
||||
approvalStatus: null,
|
||||
approvalOpinion: null,
|
||||
approvalTime: null
|
||||
};
|
||||
proxy.resetForm('approvalRef');
|
||||
}
|
||||
|
||||
// 多选框选中数据
|
||||
function handleSelectionChange(selection) {
|
||||
ids.value = selection.map((item) => item.approvalId);
|
||||
single.value = selection.length != 1;
|
||||
multiple.value = !selection.length;
|
||||
}
|
||||
|
||||
/** 新增按钮操作 */
|
||||
function handleAdd() {
|
||||
reset();
|
||||
open.value = true;
|
||||
title.value = '添加出国审批';
|
||||
}
|
||||
|
||||
/** 修改按钮操作 */
|
||||
function handleUpdate(row) {
|
||||
reset();
|
||||
const _approvalId = row.approvalId || ids.value;
|
||||
getOverseasApproval(_approvalId).then((response) => {
|
||||
form.value = response.data;
|
||||
open.value = true;
|
||||
title.value = '修改出国审批';
|
||||
});
|
||||
}
|
||||
|
||||
/** 查看详情 */
|
||||
function handleView(row) {
|
||||
// 如果是待审批的申请(没有approvalId),直接显示申请信息
|
||||
if (!row.approvalId) {
|
||||
detailData.value = row;
|
||||
detailOpen.value = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// 如果是已有的审批记录,查询完整信息
|
||||
getOverseasApproval(row.approvalId).then((response) => {
|
||||
detailData.value = response.data;
|
||||
detailOpen.value = true;
|
||||
});
|
||||
}
|
||||
|
||||
/** 查看审批记录详情 */
|
||||
function handleViewRecord(row) {
|
||||
getOverseasApproval(row.approvalId).then((response) => {
|
||||
recordDetailData.value = response.data;
|
||||
recordDetailOpen.value = true;
|
||||
});
|
||||
}
|
||||
|
||||
/** 提交按钮 */
|
||||
function submitForm() {
|
||||
proxy.$refs['approvalRef'].validate((valid) => {
|
||||
if (valid) {
|
||||
if (form.value.approvalId != null) {
|
||||
updateOverseasApproval(form.value).then((response) => {
|
||||
proxy.$modal.msgSuccess('修改成功');
|
||||
open.value = false;
|
||||
getList();
|
||||
});
|
||||
} else {
|
||||
addOverseasApproval(form.value).then((response) => {
|
||||
proxy.$modal.msgSuccess('新增成功');
|
||||
open.value = false;
|
||||
getList();
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/** 删除按钮操作 */
|
||||
function handleDelete(row) {
|
||||
const _approvalIds = row.approvalId || ids.value;
|
||||
proxy.$modal
|
||||
.confirm('是否确认删除出国审批编号为"' + _approvalIds + '"的数据项?')
|
||||
.then(function () {
|
||||
return delOverseasApproval(_approvalIds);
|
||||
})
|
||||
.then(() => {
|
||||
getList();
|
||||
proxy.$modal.msgSuccess('删除成功');
|
||||
})
|
||||
.catch(() => {});
|
||||
}
|
||||
|
||||
/** 导出按钮操作 */
|
||||
function handleExport() {
|
||||
proxy.download(
|
||||
'rs/overseas/approval/export',
|
||||
{
|
||||
...queryParams.value
|
||||
},
|
||||
`approval_${new Date().getTime()}.xlsx`
|
||||
);
|
||||
}
|
||||
|
||||
/** 导出审批记录 */
|
||||
function handleExportRecord() {
|
||||
proxy.download(
|
||||
'rs/overseas/approval/export',
|
||||
{
|
||||
...recordQueryParams.value
|
||||
},
|
||||
`approval_record_${new Date().getTime()}.xlsx`
|
||||
);
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// 默认加载出国审批列表
|
||||
getList();
|
||||
console.log('页面加载时,当前选项卡:', activeTab.value);
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 页面容器基础样式 */
|
||||
.app-container {
|
||||
padding: 20px;
|
||||
margin: 0;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* 卡片样式 */
|
||||
.box-card {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
/* 选项卡内容区域 */
|
||||
.tab-content {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/* 搜索按钮容器 */
|
||||
.search-btn-container {
|
||||
text-align: left;
|
||||
padding-top: 10px;
|
||||
}
|
||||
|
||||
/* 表格卡片样式 */
|
||||
.table-card {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.button-card {
|
||||
border: none;
|
||||
}
|
||||
|
||||
/* 表格样式优化 */
|
||||
:deep(.overseas-approval-table) {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
/* 表格空数据状态样式 */
|
||||
:deep(.el-table__empty-block) {
|
||||
width: 100% !important;
|
||||
display: flex !important;
|
||||
justify-content: center !important;
|
||||
align-items: center !important;
|
||||
padding: 40px 0 !important;
|
||||
}
|
||||
|
||||
:deep(.el-table__empty-text) {
|
||||
color: #909399 !important;
|
||||
font-size: 14px !important;
|
||||
text-align: center !important;
|
||||
}
|
||||
|
||||
/* 响应式布局优化 */
|
||||
@media (max-width: 1200px) {
|
||||
:deep(.overseas-approval-table .el-table__body-wrapper),
|
||||
:deep(.overseas-approval-table .el-table__header-wrapper) {
|
||||
overflow-x: auto;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,583 @@
|
||||
<template>
|
||||
<div class="recruitment-apply">
|
||||
<!-- 头部信息 -->
|
||||
<div class="header-banner">
|
||||
<div class="banner-content">
|
||||
<h1 class="apply-title">在线应聘</h1>
|
||||
<p class="apply-subtitle">请填写个人信息和简历,我们会尽快与您联系</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="apply-container">
|
||||
<div class="apply-layout">
|
||||
<!-- 左侧:岗位信息卡片 -->
|
||||
<div class="job-info-section">
|
||||
<el-card v-if="jobInfo" class="job-info-card" shadow="hover">
|
||||
<template #header>
|
||||
<div class="job-header">
|
||||
<el-icon><Briefcase /></el-icon>
|
||||
<span>应聘岗位信息</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div class="job-details">
|
||||
<div class="job-title">{{ jobInfo.title }}</div>
|
||||
<div class="job-meta">
|
||||
<span class="meta-item">
|
||||
<el-icon><OfficeBuilding /></el-icon>
|
||||
{{ jobInfo.department }}
|
||||
</span>
|
||||
<span class="meta-item">
|
||||
<el-icon><Location /></el-icon>
|
||||
{{ jobInfo.location }}
|
||||
</span>
|
||||
<span class="meta-item">
|
||||
<el-icon><Money /></el-icon>
|
||||
{{ jobInfo.salaryRange }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- 招聘状态信息 -->
|
||||
<div class="job-status">
|
||||
<div class="status-item">
|
||||
<el-icon><Calendar /></el-icon>
|
||||
<span>发布时间:{{ formatDate(jobInfo.publishTime) }}</span>
|
||||
</div>
|
||||
<div class="status-item">
|
||||
<el-icon><View /></el-icon>
|
||||
<span>浏览次数:{{ jobInfo.viewCount || 0 }}</span>
|
||||
</div>
|
||||
<div class="status-item">
|
||||
<el-icon><User /></el-icon>
|
||||
<span>申请人数:{{ jobInfo.applicantCount || 0 }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 岗位描述 -->
|
||||
<div class="job-description" v-if="jobInfo.description">
|
||||
<h3>岗位描述</h3>
|
||||
<div v-html="jobInfo.description"></div>
|
||||
</div>
|
||||
|
||||
<!-- 岗位要求 -->
|
||||
<div class="job-requirements" v-if="jobInfo.requirements">
|
||||
<h3>岗位要求</h3>
|
||||
<div v-html="jobInfo.requirements"></div>
|
||||
</div>
|
||||
|
||||
<!-- 备注信息 -->
|
||||
<div class="job-remark" v-if="jobInfo.remark">
|
||||
<h3>其他说明</h3>
|
||||
<div>{{ jobInfo.remark }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
|
||||
<!-- 右侧:应聘表单 -->
|
||||
<div class="apply-form-section">
|
||||
<el-card class="apply-form-card" shadow="never">
|
||||
<template #header>
|
||||
<div class="form-header">
|
||||
<el-icon><User /></el-icon>
|
||||
<span>个人信息</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<el-form ref="applyFormRef" :model="applyForm" :rules="applyRules" label-width="120px" class="apply-form">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="姓名" prop="applicantName">
|
||||
<el-input v-model="applyForm.applicantName" placeholder="请输入您的姓名" maxlength="50" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="性别" prop="gender">
|
||||
<el-radio-group v-model="applyForm.gender">
|
||||
<el-radio value="男">男</el-radio>
|
||||
<el-radio value="女">女</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="联系电话" prop="contactPhone">
|
||||
<el-input v-model="applyForm.contactPhone" placeholder="请输入手机号码" maxlength="11" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="邮箱地址" prop="contactEmail">
|
||||
<el-input v-model="applyForm.contactEmail" placeholder="请输入邮箱地址" maxlength="100" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="年龄" prop="age">
|
||||
<el-input-number v-model="applyForm.age" :min="18" :max="65" style="width: 100%" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="最高学历" prop="education">
|
||||
<el-select v-model="applyForm.education" placeholder="请选择" style="width: 100%">
|
||||
<el-option label="高中" value="高中" />
|
||||
<el-option label="专科" value="专科" />
|
||||
<el-option label="本科" value="本科" />
|
||||
<el-option label="硕士" value="硕士" />
|
||||
<el-option label="博士" value="博士" />
|
||||
<el-option label="其他" value="其他" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-form-item label="教育背景" prop="educationBackground">
|
||||
<el-input
|
||||
v-model="applyForm.educationBackground"
|
||||
type="textarea"
|
||||
:rows="4"
|
||||
placeholder="请详细描述您的教育背景,包括毕业院校、专业、获得的荣誉等(500字以内)"
|
||||
maxlength="500"
|
||||
show-word-limit
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="工作经验" prop="experience">
|
||||
<el-input
|
||||
v-model="applyForm.experience"
|
||||
type="textarea"
|
||||
:rows="4"
|
||||
placeholder="请简要描述您的工作经验和技能特长(500字以内)"
|
||||
maxlength="500"
|
||||
show-word-limit
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="自我介绍" prop="selfDescription">
|
||||
<el-input
|
||||
v-model="applyForm.selfDescription"
|
||||
type="textarea"
|
||||
:rows="6"
|
||||
placeholder="请介绍您的个人情况、特长和应聘理由(1000字以内)"
|
||||
maxlength="1000"
|
||||
show-word-limit
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="其他说明" prop="remark">
|
||||
<el-input
|
||||
v-model="applyForm.remark"
|
||||
type="textarea"
|
||||
:rows="3"
|
||||
placeholder="其他需要说明的信息(选填)"
|
||||
maxlength="300"
|
||||
show-word-limit
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<div class="form-actions">
|
||||
<el-button @click="goBack">返回</el-button>
|
||||
<el-button type="primary" @click="submitApplication" :loading="submitting"> 提交应聘申请 </el-button>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="RecruitmentApply">
|
||||
import { ref, reactive, onMounted } from 'vue';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import { ElMessage, ElMessageBox, type FormInstance } from 'element-plus';
|
||||
import { Briefcase, OfficeBuilding, Location, Money, User, Calendar, View } from '@element-plus/icons-vue';
|
||||
import { getPublicJob, type JobVO } from '@/api/rs/job';
|
||||
import { submitJobApplication, type ApplicationForm } from '@/api/rs/application';
|
||||
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
|
||||
// 岗位信息
|
||||
const jobInfo = ref<JobVO | null>(null);
|
||||
|
||||
// 表单引用
|
||||
const applyFormRef = ref<FormInstance>();
|
||||
|
||||
// 提交状态
|
||||
const submitting = ref(false);
|
||||
|
||||
// 应聘表单数据
|
||||
const applyForm = reactive({
|
||||
jobId: '',
|
||||
applicantName: '',
|
||||
gender: '',
|
||||
contactPhone: '',
|
||||
contactEmail: '',
|
||||
age: undefined as number | undefined,
|
||||
education: '',
|
||||
educationBackground: '',
|
||||
experience: '',
|
||||
selfDescription: '',
|
||||
remark: ''
|
||||
});
|
||||
|
||||
// 表单验证规则
|
||||
const applyRules = {
|
||||
applicantName: [
|
||||
{ required: true, message: '请输入姓名', trigger: 'blur' },
|
||||
{ min: 2, max: 50, message: '姓名长度在 2 到 50 个字符', trigger: 'blur' }
|
||||
],
|
||||
gender: [{ required: true, message: '请选择性别', trigger: 'change' }],
|
||||
contactPhone: [
|
||||
{ required: true, message: '请输入联系电话', trigger: 'blur' },
|
||||
{ pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号码', trigger: 'blur' }
|
||||
],
|
||||
contactEmail: [
|
||||
{ required: true, message: '请输入邮箱地址', trigger: 'blur' },
|
||||
{ type: 'email' as const, message: '请输入正确的邮箱地址', trigger: 'blur' }
|
||||
],
|
||||
age: [
|
||||
{ required: true, message: '请输入年龄', trigger: 'blur' },
|
||||
{ type: 'number' as const, min: 18, max: 65, message: '年龄应在18-65岁之间', trigger: 'blur' }
|
||||
],
|
||||
education: [{ required: true, message: '请选择学历', trigger: 'change' }],
|
||||
educationBackground: [
|
||||
{ required: true, message: '请填写教育背景', trigger: 'blur' },
|
||||
{ min: 10, max: 500, message: '教育背景描述应在 10 到 500 字符之间', trigger: 'blur' }
|
||||
],
|
||||
experience: [
|
||||
{ required: true, message: '请填写工作经验', trigger: 'blur' },
|
||||
{ min: 10, max: 500, message: '工作经验描述应在 10 到 500 字符之间', trigger: 'blur' }
|
||||
],
|
||||
selfDescription: [
|
||||
{ required: true, message: '请填写自我介绍', trigger: 'blur' },
|
||||
{ min: 20, max: 1000, message: '自我介绍应在 20 到 1000 字符之间', trigger: 'blur' }
|
||||
]
|
||||
};
|
||||
|
||||
// 获取岗位信息
|
||||
const getJobInfo = async (jobId: string) => {
|
||||
try {
|
||||
const response = await getPublicJob(jobId);
|
||||
jobInfo.value = response.data;
|
||||
applyForm.jobId = jobId;
|
||||
} catch (error) {
|
||||
console.error('获取岗位信息失败:', error);
|
||||
ElMessage.error('获取岗位信息失败');
|
||||
goBack();
|
||||
}
|
||||
};
|
||||
|
||||
// 格式化日期
|
||||
const formatDate = (timestamp: string) => {
|
||||
if (!timestamp) return '';
|
||||
const date = new Date(timestamp);
|
||||
const year = date.getFullYear();
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0');
|
||||
const day = String(date.getDate()).padStart(2, '0');
|
||||
const hours = String(date.getHours()).padStart(2, '0');
|
||||
const minutes = String(date.getMinutes()).padStart(2, '0');
|
||||
return `${year}-${month}-${day} ${hours}:${minutes}`;
|
||||
};
|
||||
|
||||
// 提交应聘申请
|
||||
const submitApplication = async () => {
|
||||
if (!applyFormRef.value) return;
|
||||
|
||||
// 表单验证
|
||||
const valid = await applyFormRef.value.validate().catch(() => false);
|
||||
if (!valid) return;
|
||||
|
||||
try {
|
||||
submitting.value = true;
|
||||
|
||||
// 调用应聘申请API
|
||||
await submitJobApplicationData(applyForm as ApplicationForm);
|
||||
|
||||
ElMessage.success('应聘申请提交成功!我们会尽快与您联系。');
|
||||
|
||||
// 显示成功对话框
|
||||
await ElMessageBox.alert('您的应聘申请已成功提交!我们会在3个工作日内与您联系,请保持电话畅通。', '申请成功', {
|
||||
confirmButtonText: '确定',
|
||||
type: 'success'
|
||||
});
|
||||
|
||||
// 跳转回招聘门户
|
||||
router.push('/recruitment-portal');
|
||||
} catch (error) {
|
||||
console.error('提交应聘申请失败:', error);
|
||||
ElMessage.error('提交应聘申请失败,请稍后重试');
|
||||
} finally {
|
||||
submitting.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 提交应聘申请API调用
|
||||
const submitJobApplicationData = async (applicationData: ApplicationForm) => {
|
||||
return await submitJobApplication(applicationData);
|
||||
};
|
||||
|
||||
// 返回上一页
|
||||
const goBack = () => {
|
||||
router.go(-1);
|
||||
};
|
||||
|
||||
// 页面初始化
|
||||
onMounted(() => {
|
||||
const jobId = route.query.jobId as string;
|
||||
if (jobId) {
|
||||
getJobInfo(jobId);
|
||||
} else {
|
||||
ElMessage.error('缺少岗位信息');
|
||||
goBack();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.recruitment-apply {
|
||||
background-color: #f5f7fa;
|
||||
min-height: 100vh;
|
||||
|
||||
.header-banner {
|
||||
background-color: #409eff;
|
||||
color: white;
|
||||
padding: 40px 0;
|
||||
text-align: center;
|
||||
|
||||
.banner-content {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 0 20px;
|
||||
|
||||
.apply-title {
|
||||
font-size: 2rem;
|
||||
margin-bottom: 10px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.apply-subtitle {
|
||||
font-size: 1rem;
|
||||
opacity: 0.8;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.apply-container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 30px 20px;
|
||||
|
||||
.apply-layout {
|
||||
display: flex;
|
||||
gap: 30px;
|
||||
align-items: flex-start;
|
||||
|
||||
.job-info-section,
|
||||
.apply-form-section {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.job-info-section {
|
||||
flex: 0 0 40%;
|
||||
max-width: 40%;
|
||||
}
|
||||
|
||||
.apply-form-section {
|
||||
flex: 0 0 60%;
|
||||
max-width: 60%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 左侧岗位信息卡片
|
||||
.job-info-card {
|
||||
height: 100%;
|
||||
position: sticky;
|
||||
top: 20px;
|
||||
|
||||
.el-card__body {
|
||||
max-height: calc(100vh - 250px);
|
||||
overflow-y: auto;
|
||||
padding-right: 10px;
|
||||
scrollbar-width: thin;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background-color: #dcdfe6;
|
||||
border-radius: 3px;
|
||||
}
|
||||
}
|
||||
|
||||
.job-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-weight: 600;
|
||||
color: #2c3e50;
|
||||
}
|
||||
|
||||
.job-details {
|
||||
.job-title {
|
||||
font-size: 1.5rem;
|
||||
font-weight: 600;
|
||||
color: #2c3e50;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.job-meta {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 20px;
|
||||
|
||||
.meta-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
color: #666;
|
||||
font-size: 0.9rem;
|
||||
|
||||
.el-icon {
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.job-status,
|
||||
.job-description,
|
||||
.job-requirements,
|
||||
.job-remark {
|
||||
margin-top: 20px;
|
||||
border-top: 1px solid #eee;
|
||||
padding-top: 15px;
|
||||
|
||||
h3 {
|
||||
font-size: 1.1rem;
|
||||
margin-bottom: 10px;
|
||||
color: #2c3e50;
|
||||
}
|
||||
|
||||
div {
|
||||
color: #666;
|
||||
line-height: 1.6;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
}
|
||||
}
|
||||
|
||||
.job-status {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
|
||||
.status-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 14px;
|
||||
color: #606266;
|
||||
|
||||
.el-icon {
|
||||
margin-right: 8px;
|
||||
color: #909399;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 右侧应聘表单卡片
|
||||
.apply-form-card {
|
||||
.form-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-weight: 600;
|
||||
color: #2c3e50;
|
||||
}
|
||||
|
||||
.apply-form {
|
||||
.el-form-item {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.form-actions {
|
||||
text-align: center;
|
||||
margin-top: 30px;
|
||||
padding-top: 20px;
|
||||
border-top: 1px solid #eee;
|
||||
|
||||
.el-button {
|
||||
min-width: 120px;
|
||||
margin: 0 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 卡片通用样式
|
||||
.job-info-card,
|
||||
.apply-form-card {
|
||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
|
||||
border-radius: 4px;
|
||||
margin-bottom: 20px;
|
||||
|
||||
.el-card__header {
|
||||
padding: 15px 20px;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
background-color: #fafafa;
|
||||
}
|
||||
|
||||
.el-card__body {
|
||||
padding: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
// 响应式布局
|
||||
@media (max-width: 768px) {
|
||||
.apply-container {
|
||||
.apply-layout {
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
|
||||
.job-info-section,
|
||||
.apply-form-section {
|
||||
flex: 0 0 100%;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.job-info-section {
|
||||
.job-info-card {
|
||||
position: relative;
|
||||
top: 0;
|
||||
|
||||
.el-card__body {
|
||||
max-height: 500px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 大屏幕优化 */
|
||||
@media (min-width: 1440px) {
|
||||
.apply-container {
|
||||
max-width: 1400px;
|
||||
}
|
||||
}
|
||||
|
||||
/* 超大屏幕优化 */
|
||||
@media (min-width: 1920px) {
|
||||
.apply-container {
|
||||
max-width: 1800px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,749 @@
|
||||
<template>
|
||||
<div class="recruitment-portal">
|
||||
<!-- 头部横幅 -->
|
||||
<div class="header-banner">
|
||||
<div class="banner-content">
|
||||
<h1 class="portal-title">人才招聘</h1>
|
||||
<p class="portal-subtitle">诚邀天下英才,共创美好未来</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 导航面包屑 -->
|
||||
<div class="breadcrumb-container">
|
||||
<el-breadcrumb separator=">">
|
||||
<el-breadcrumb-item>首页</el-breadcrumb-item>
|
||||
<el-breadcrumb-item>人事管理</el-breadcrumb-item>
|
||||
<el-breadcrumb-item active>招聘公告</el-breadcrumb-item>
|
||||
</el-breadcrumb>
|
||||
</div>
|
||||
|
||||
<div class="portal-container">
|
||||
<!-- 左侧导航 -->
|
||||
<div class="sidebar">
|
||||
<div class="nav-section">
|
||||
<h3 class="nav-title">招聘信息</h3>
|
||||
<ul class="nav-list">
|
||||
<li class="nav-item active">
|
||||
<el-icon><Briefcase /></el-icon>
|
||||
<span>招聘公告</span>
|
||||
</li>
|
||||
<!-- 已删除岗位列表和应聘指南 -->
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 主要内容区域 -->
|
||||
<div class="main-content">
|
||||
<!-- 搜索区域已删除 -->
|
||||
|
||||
<!-- 岗位列表 -->
|
||||
<div class="job-list-section">
|
||||
<el-card shadow="never">
|
||||
<template #header>
|
||||
<div class="list-header">
|
||||
<div class="header-info">
|
||||
<h3>招聘岗位列表</h3>
|
||||
<span class="job-count">共 {{ jobList.length }} 个岗位</span>
|
||||
</div>
|
||||
<div class="view-options">
|
||||
<el-radio-group v-model="viewMode" size="small">
|
||||
<el-radio-button label="list">
|
||||
<el-icon><List /></el-icon>
|
||||
列表
|
||||
</el-radio-button>
|
||||
<el-radio-button label="card">
|
||||
<el-icon><Grid /></el-icon>
|
||||
卡片
|
||||
</el-radio-button>
|
||||
</el-radio-group>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 列表视图 -->
|
||||
<div v-if="viewMode === 'list'" class="list-view">
|
||||
<el-table :data="filteredJobList" stripe style="width: 100%">
|
||||
<el-table-column prop="title" label="岗位名称" min-width="200">
|
||||
<template #default="scope">
|
||||
<el-link type="primary" @click="viewJobDetail(scope.row)" class="job-title-link">
|
||||
{{ scope.row.title }}
|
||||
</el-link>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="department" label="院系单位" width="150" />
|
||||
<el-table-column prop="isActive" label="状态" width="120">
|
||||
<template #default="scope">
|
||||
<el-tag :type="getJobTypeColor(scope.row.isActive)">
|
||||
{{ getJobTypeLabel(scope.row.isActive) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="salaryRange" label="薪资待遇" width="150" />
|
||||
<el-table-column prop="location" label="工作地点" width="120" />
|
||||
<el-table-column prop="publishTime" label="发布时间" width="120">
|
||||
<template #default="scope">
|
||||
{{ formatDate(scope.row.publishTime) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="publishTime" label="发布时间" width="120">
|
||||
<template #default="scope">
|
||||
<span>{{ formatDate(scope.row.publishTime) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="160" fixed="right">
|
||||
<template #default="scope">
|
||||
<div class="operation-buttons">
|
||||
<el-button type="primary" size="small" @click="viewJobDetail(scope.row)"> 查看详情 </el-button>
|
||||
<el-button type="success" size="small" @click="applyJob(scope.row)" :disabled="scope.row.isActive === '1'">
|
||||
立即应聘
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
|
||||
<!-- 卡片视图 -->
|
||||
<div v-else class="card-view">
|
||||
<el-row :gutter="20">
|
||||
<el-col v-for="job in filteredJobList" :key="job.jobId" :lg="8" :md="12" :sm="24" class="job-card-col">
|
||||
<el-card class="job-card" shadow="hover" @click="viewJobDetail(job)">
|
||||
<div class="job-card-header">
|
||||
<h4 class="job-card-title">{{ job.title }}</h4>
|
||||
<el-tag :type="getJobTypeColor(job.isActive)" size="small">
|
||||
{{ getJobTypeLabel(job.isActive) }}
|
||||
</el-tag>
|
||||
</div>
|
||||
|
||||
<div class="job-card-info">
|
||||
<div class="info-item">
|
||||
<el-icon><OfficeBuilding /></el-icon>
|
||||
<span>{{ job.department }}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<el-icon><Location /></el-icon>
|
||||
<span>{{ job.location }}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<el-icon><Money /></el-icon>
|
||||
<span>{{ job.salaryRange }}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<el-icon><Calendar /></el-icon>
|
||||
<span>发布:{{ formatDate(job.publishTime) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="job-card-footer">
|
||||
<el-button type="primary" size="small" @click.stop="applyJob(job)" :disabled="job.isActive === '1'"> 立即应聘 </el-button>
|
||||
<span class="publish-time">{{ formatDate(job.publishTime) }}</span>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 岗位详情对话框 -->
|
||||
<el-dialog v-model="jobDetailVisible" :title="currentJob.title" width="800px" destroy-on-close>
|
||||
<div v-if="currentJob" class="job-detail">
|
||||
<div class="detail-header">
|
||||
<div class="job-basic-info">
|
||||
<h3>{{ currentJob.title }}</h3>
|
||||
<div class="job-meta">
|
||||
<el-tag :type="getJobTypeColor(currentJob.isActive)">
|
||||
{{ getJobTypeLabel(currentJob.isActive) }}
|
||||
</el-tag>
|
||||
<span class="meta-item">{{ currentJob.department }}</span>
|
||||
<span class="meta-item">{{ currentJob.location }}</span>
|
||||
<span class="meta-item">{{ currentJob.salaryRange }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-divider />
|
||||
|
||||
<div class="detail-content">
|
||||
<div class="content-section">
|
||||
<h4>岗位职责</h4>
|
||||
<div class="content-text" v-html="currentJob.description"></div>
|
||||
</div>
|
||||
|
||||
<div class="content-section">
|
||||
<h4>任职要求</h4>
|
||||
<div class="content-text" v-html="currentJob.requirements"></div>
|
||||
</div>
|
||||
|
||||
<div class="content-section">
|
||||
<h4>招聘信息</h4>
|
||||
<el-descriptions :column="2" border>
|
||||
<el-descriptions-item label="发布时间">
|
||||
{{ formatDate(currentJob.publishTime) }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="截止时间">
|
||||
<span>{{ formatDate(currentJob.publishTime) }}</span>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="浏览次数"> {{ currentJob.viewCount || 0 }} 次 </el-descriptions-item>
|
||||
<el-descriptions-item label="应聘人数"> {{ currentJob.applicantCount || 0 }} 人 </el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="jobDetailVisible = false">关闭</el-button>
|
||||
<el-button type="primary" @click="applyJob(currentJob)" :disabled="currentJob.isActive === '1'"> 立即应聘 </el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="RecruitmentPortal">
|
||||
import { ref, reactive, computed, onMounted } from 'vue';
|
||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||
import { Search, Refresh, Briefcase, Document, User, List, Grid, OfficeBuilding, Location, Money, Calendar } from '@element-plus/icons-vue';
|
||||
import { listPublicJob, getPublicJob, type JobVO, type JobQuery } from '@/api/rs/job';
|
||||
import { incrementJobViewCount } from '@/api/rs/application';
|
||||
import { parseTime } from '@/utils/ruoyi';
|
||||
|
||||
// 响应式数据
|
||||
const loading = ref(false);
|
||||
const viewMode = ref('list');
|
||||
const jobDetailVisible = ref(false);
|
||||
const currentJob = ref<JobVO | any>({});
|
||||
|
||||
// 搜索表单已删除
|
||||
|
||||
// 岗位列表数据
|
||||
const jobList = ref<JobVO[]>([]);
|
||||
const total = ref(0);
|
||||
|
||||
// 查询参数
|
||||
const queryParams = reactive<JobQuery>({
|
||||
pageNum: 1,
|
||||
pageSize: 100, // 门户页面显示更多数据
|
||||
title: '',
|
||||
department: '',
|
||||
isActive: '0', // 只显示启用的岗位
|
||||
params: {}
|
||||
});
|
||||
|
||||
// 部门选项 - 从实际数据中动态获取
|
||||
const departmentOptions = ref<Array<{ label: string; value: string }>>([]);
|
||||
|
||||
// 计算属性:过滤后的岗位列表
|
||||
const filteredJobList = computed(() => {
|
||||
return jobList.value;
|
||||
});
|
||||
|
||||
// 获取招聘类型标签
|
||||
const getJobTypeLabel = (isActive: string) => {
|
||||
return isActive === '0' ? '招聘中' : '已停止';
|
||||
};
|
||||
|
||||
// 获取招聘类型颜色
|
||||
const getJobTypeColor = (isActive: string) => {
|
||||
return isActive === '0' ? 'success' : 'danger';
|
||||
};
|
||||
|
||||
// 格式化日期
|
||||
const formatDate = (dateStr: string) => {
|
||||
if (!dateStr) return '';
|
||||
return parseTime(dateStr, '{y}-{m}-{d}');
|
||||
};
|
||||
|
||||
// 检查是否过期(使用publishTime判断,假设30天有效期)
|
||||
const isJobExpired = (publishTime: string) => {
|
||||
if (!publishTime) return false;
|
||||
const publishDate = new Date(publishTime);
|
||||
const expiryDate = new Date(publishDate.getTime() + 30 * 24 * 60 * 60 * 1000); // 30天有效期
|
||||
return expiryDate < new Date();
|
||||
};
|
||||
|
||||
// 获取截止时间样式类
|
||||
const getJobStatusClass = (publishTime: string, isActive: string) => {
|
||||
if (isActive === '1') {
|
||||
return 'job-inactive';
|
||||
}
|
||||
if (isJobExpired(publishTime)) {
|
||||
return 'job-expired';
|
||||
}
|
||||
return '';
|
||||
};
|
||||
|
||||
// 获取岗位列表
|
||||
const getJobList = async () => {
|
||||
loading.value = true;
|
||||
try {
|
||||
const response = await listPublicJob(queryParams);
|
||||
jobList.value = response.rows || [];
|
||||
total.value = response.total || 0;
|
||||
|
||||
// 动态获取部门列表
|
||||
updateDepartmentOptions();
|
||||
} catch (error) {
|
||||
console.error('获取岗位列表失败:', error);
|
||||
ElMessage.error('获取岗位列表失败');
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 更新部门选项
|
||||
const updateDepartmentOptions = () => {
|
||||
const departments = [...new Set(jobList.value.map((job) => job.department))];
|
||||
departmentOptions.value = departments.map((dept) => ({
|
||||
label: dept,
|
||||
value: dept
|
||||
}));
|
||||
};
|
||||
|
||||
// 搜索功能已删除
|
||||
|
||||
// 查看岗位详情
|
||||
const viewJobDetail = async (job: JobVO) => {
|
||||
try {
|
||||
const response = await getPublicJob(job.jobId);
|
||||
currentJob.value = response.data;
|
||||
jobDetailVisible.value = true;
|
||||
|
||||
// 增加浏览次数
|
||||
await incrementViewCount(job.jobId);
|
||||
} catch (error) {
|
||||
console.error('获取岗位详情失败:', error);
|
||||
ElMessage.error('获取岗位详情失败');
|
||||
}
|
||||
};
|
||||
|
||||
// 增加浏览次数
|
||||
const incrementViewCount = async (jobId: string | number) => {
|
||||
try {
|
||||
// 调用增加浏览次数的API
|
||||
await incrementJobViewCount(jobId);
|
||||
|
||||
// 更新本地数据
|
||||
const job = jobList.value.find((j) => j.jobId === jobId);
|
||||
if (job) {
|
||||
job.viewCount = (job.viewCount || 0) + 1;
|
||||
}
|
||||
} catch (error) {
|
||||
// 浏览次数更新失败不影响用户体验
|
||||
console.warn('更新浏览次数失败:', error);
|
||||
}
|
||||
};
|
||||
|
||||
// 应聘岗位
|
||||
const applyJob = (job: JobVO) => {
|
||||
if (job.isActive === '1') {
|
||||
ElMessage.warning('该岗位已停止招聘');
|
||||
return;
|
||||
}
|
||||
|
||||
if (isJobExpired(job.publishTime || '')) {
|
||||
ElMessage.warning('该岗位已过应聘期限');
|
||||
return;
|
||||
}
|
||||
|
||||
ElMessageBox.confirm(`确定要应聘"${job.title}"岗位吗?`, '确认应聘', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'info'
|
||||
})
|
||||
.then(() => {
|
||||
// 跳转到应聘表单页面
|
||||
window.open(`/recruitment-apply?jobId=${job.jobId}`, '_blank');
|
||||
})
|
||||
.catch(() => {
|
||||
// 取消应聘
|
||||
});
|
||||
};
|
||||
|
||||
// 页面初始化
|
||||
onMounted(() => {
|
||||
getJobList();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.recruitment-portal {
|
||||
min-height: 100vh;
|
||||
background: #f8f9fa;
|
||||
|
||||
.header-banner {
|
||||
background: linear-gradient(135deg, #2c3e50 0%, #34495e 100%);
|
||||
color: white;
|
||||
padding: 60px 0;
|
||||
text-align: center;
|
||||
|
||||
.banner-content {
|
||||
margin: 0 auto;
|
||||
padding: 0 30px;
|
||||
|
||||
.portal-title {
|
||||
font-size: 3.5rem;
|
||||
font-weight: 700;
|
||||
margin-bottom: 20px;
|
||||
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.portal-subtitle {
|
||||
font-size: 1.5rem;
|
||||
opacity: 0.9;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.breadcrumb-container {
|
||||
background: white;
|
||||
padding: 15px 0;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
||||
border-bottom: 1px solid #e8ecf0;
|
||||
|
||||
.el-breadcrumb {
|
||||
margin: 0 auto;
|
||||
padding: 0 30px;
|
||||
}
|
||||
}
|
||||
|
||||
.portal-container {
|
||||
margin: 0;
|
||||
padding: 30px;
|
||||
display: flex;
|
||||
gap: 25px;
|
||||
min-height: calc(100vh - 200px);
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
width: 240px;
|
||||
flex-shrink: 0;
|
||||
|
||||
.nav-section {
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
padding: 20px;
|
||||
margin-bottom: 20px;
|
||||
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08);
|
||||
border: 1px solid #e8ecf0;
|
||||
|
||||
.nav-title {
|
||||
font-size: 1.2rem;
|
||||
font-weight: 600;
|
||||
margin-bottom: 20px;
|
||||
color: #2c3e50;
|
||||
border-bottom: 2px solid #e3f2fd;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
.nav-list {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
|
||||
.nav-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 12px 15px;
|
||||
margin-bottom: 8px;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
color: #555;
|
||||
|
||||
&:hover {
|
||||
background: #f8f9fa;
|
||||
color: #667eea;
|
||||
}
|
||||
|
||||
&.active {
|
||||
background: linear-gradient(135deg, #667eea, #764ba2);
|
||||
color: white;
|
||||
box-shadow: 0 2px 8px rgba(102, 126, 234, 0.3);
|
||||
}
|
||||
|
||||
.el-icon {
|
||||
margin-right: 10px;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.main-content {
|
||||
flex: 1;
|
||||
min-width: 0; // 确保flex子项能够收缩
|
||||
|
||||
/* 搜索区域样式已删除 */
|
||||
|
||||
.job-list-section {
|
||||
.list-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
.header-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 15px;
|
||||
|
||||
h3 {
|
||||
margin: 0;
|
||||
color: #2c3e50;
|
||||
}
|
||||
|
||||
.job-count {
|
||||
color: #666;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.list-view {
|
||||
.job-title-link {
|
||||
font-weight: 600;
|
||||
text-decoration: none;
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
.deadline-passed {
|
||||
color: #f56c6c;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.deadline-warning {
|
||||
color: #e6a23c;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
|
||||
.card-view {
|
||||
.job-card-col {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.job-card {
|
||||
height: 100%;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.job-card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 15px;
|
||||
|
||||
.job-card-title {
|
||||
margin: 0;
|
||||
font-size: 1.1rem;
|
||||
font-weight: 600;
|
||||
color: #2c3e50;
|
||||
flex: 1;
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.job-card-info {
|
||||
margin-bottom: 15px;
|
||||
flex: 1;
|
||||
|
||||
.info-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 8px;
|
||||
color: #666;
|
||||
font-size: 0.9rem;
|
||||
|
||||
.el-icon {
|
||||
margin-right: 8px;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.job-card-footer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
.publish-time {
|
||||
font-size: 0.8rem;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.job-detail {
|
||||
.detail-header {
|
||||
.job-basic-info {
|
||||
h3 {
|
||||
margin-bottom: 15px;
|
||||
color: #2c3e50;
|
||||
}
|
||||
|
||||
.job-meta {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 15px;
|
||||
flex-wrap: wrap;
|
||||
|
||||
.meta-item {
|
||||
color: #666;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.detail-content {
|
||||
.content-section {
|
||||
margin-bottom: 25px;
|
||||
|
||||
h4 {
|
||||
color: #2c3e50;
|
||||
margin-bottom: 15px;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.content-text {
|
||||
line-height: 1.8;
|
||||
color: #555;
|
||||
|
||||
ul {
|
||||
padding-left: 20px;
|
||||
}
|
||||
|
||||
li {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.dialog-footer {
|
||||
text-align: right;
|
||||
|
||||
.el-button {
|
||||
margin-left: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 响应式设计
|
||||
@media (max-width: 1200px) {
|
||||
.recruitment-portal {
|
||||
.portal-container {
|
||||
padding: 20px;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
width: 220px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.recruitment-portal {
|
||||
.header-banner {
|
||||
padding: 40px 0;
|
||||
|
||||
.portal-title {
|
||||
font-size: 2.5rem;
|
||||
}
|
||||
|
||||
.portal-subtitle {
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
}
|
||||
|
||||
.portal-container {
|
||||
flex-direction: column;
|
||||
padding: 15px;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 大屏幕优化
|
||||
@media (min-width: 1400px) {
|
||||
.recruitment-portal {
|
||||
.portal-container {
|
||||
padding: 40px;
|
||||
gap: 30px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 表格响应式优化
|
||||
.main-content {
|
||||
.el-table {
|
||||
min-width: 100%;
|
||||
|
||||
.el-table__body-wrapper {
|
||||
overflow-x: auto;
|
||||
}
|
||||
}
|
||||
|
||||
// 操作按钮对齐样式
|
||||
.operation-buttons {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
|
||||
.el-button {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
// 卡片网格优化
|
||||
.el-row {
|
||||
margin: 0 -10px;
|
||||
|
||||
.el-col {
|
||||
padding: 0 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 小屏幕表格优化
|
||||
@media (max-width: 768px) {
|
||||
.main-content {
|
||||
.el-table {
|
||||
font-size: 0.85rem;
|
||||
|
||||
.el-table__cell {
|
||||
padding: 8px 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.job-card-col {
|
||||
padding: 0 5px !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -12,28 +12,33 @@
|
||||
班级信息管理模块提供完整的班级信息管理功能,包括:
|
||||
|
||||
### 📊 统计概览
|
||||
|
||||
- 总班级数统计
|
||||
- 正常班级数统计
|
||||
- 总学生数统计
|
||||
- 平均学生数统计
|
||||
|
||||
### 🔍 查询功能
|
||||
|
||||
- 班级代码、班级名称模糊查询
|
||||
- 年级、学院、专业级联查询
|
||||
- 班级类型、班级状态筛选
|
||||
- 班主任姓名查询
|
||||
|
||||
### 📝 基础管理
|
||||
|
||||
- 班级信息新增、修改、删除
|
||||
- 班级信息详情查看
|
||||
- 班级信息批量导出
|
||||
|
||||
### 👥 人员配置
|
||||
|
||||
- 班主任、辅导员配置
|
||||
- 班干部信息管理(班长、学习委员、生活委员等)
|
||||
- 联系方式管理
|
||||
|
||||
### 📈 统计功能
|
||||
|
||||
- 学生数量统计更新
|
||||
- 批量统计刷新
|
||||
- 实时数据同步
|
||||
@@ -41,12 +46,14 @@
|
||||
## 🎨 页面设计
|
||||
|
||||
### 页面布局
|
||||
|
||||
- **搜索区域**: 8个搜索条件,支持组合查询
|
||||
- **统计卡片**: 4个统计指标,渐变色彩设计
|
||||
- **表格区域**: 完整的班级信息展示
|
||||
- **操作按钮**: 查看、编辑、删除、统计更新
|
||||
|
||||
### 对话框设计
|
||||
|
||||
- **新增/编辑**: 分页签设计(基本信息、人员配置、联系方式)
|
||||
- **详情查看**: 描述列表展示完整信息
|
||||
- **响应式布局**: 适配不同屏幕尺寸
|
||||
@@ -54,18 +61,21 @@
|
||||
## 🔧 技术特性
|
||||
|
||||
### 前端技术
|
||||
|
||||
- **Vue 3.5.13** + **TypeScript 5.8.3**
|
||||
- **Element Plus** UI组件库
|
||||
- **Composition API** 组合式API
|
||||
- **响应式设计** 适配多端
|
||||
|
||||
### 样式设计
|
||||
|
||||
- **参考后勤管理模块** 保持风格一致
|
||||
- **渐变色卡片** 美观的统计展示
|
||||
- **悬停效果** 良好的交互体验
|
||||
- **操作按钮** 图标化设计
|
||||
|
||||
### 数据处理
|
||||
|
||||
- **级联选择** 学院-专业联动
|
||||
- **表单验证** 完整的输入校验
|
||||
- **状态管理** 响应式数据更新
|
||||
@@ -76,6 +86,7 @@
|
||||
### 16个标准API接口
|
||||
|
||||
#### 基础CRUD接口
|
||||
|
||||
1. `GET /list` - 查询班级信息列表
|
||||
2. `POST /export` - 导出班级信息列表
|
||||
3. `GET /{id}` - 获取班级信息详情
|
||||
@@ -84,6 +95,7 @@
|
||||
6. `DELETE /{ids}` - 删除班级信息
|
||||
|
||||
#### 业务查询接口
|
||||
|
||||
7. `GET /getByClassCode/{classCode}` - 根据班级代码查询
|
||||
8. `GET /getByGrade/{grade}` - 根据年级查询
|
||||
9. `GET /getByCollegeCode/{collegeCode}` - 根据学院代码查询
|
||||
@@ -92,6 +104,7 @@
|
||||
12. `GET /getByClassTeacherId/{classTeacherId}` - 根据班主任ID查询
|
||||
|
||||
#### 辅助功能接口
|
||||
|
||||
13. `GET /getClassOptions` - 获取班级选项列表
|
||||
14. `POST /updateStudentCount/{id}` - 更新班级学生统计
|
||||
15. `POST /batchUpdateStudentCount` - 批量更新学生统计
|
||||
@@ -100,6 +113,7 @@
|
||||
## 🎯 权限控制
|
||||
|
||||
### 权限编码
|
||||
|
||||
- `xg:classInfo:list` - 班级信息查询
|
||||
- `xg:classInfo:query` - 班级信息详情
|
||||
- `xg:classInfo:add` - 班级信息新增
|
||||
@@ -108,6 +122,7 @@
|
||||
- `xg:classInfo:export` - 班级信息导出
|
||||
|
||||
### 按钮权限
|
||||
|
||||
- 所有操作按钮都配置了对应的权限控制
|
||||
- 使用 `v-hasPermi` 指令进行权限验证
|
||||
- 无权限时自动隐藏相关按钮
|
||||
@@ -115,24 +130,28 @@
|
||||
## 📊 数据字典
|
||||
|
||||
### 班级类型 (xg_class_type)
|
||||
|
||||
- `regular` - 普通班级
|
||||
- `honor` - 荣誉班级
|
||||
- `experimental` - 实验班级
|
||||
- `special` - 特殊班级
|
||||
|
||||
### 班级状态 (xg_class_status)
|
||||
|
||||
- `1` - 正常
|
||||
- `2` - 暂停
|
||||
- `3` - 毕业
|
||||
- `4` - 解散
|
||||
|
||||
### 年级选项 (xg_grade_options)
|
||||
|
||||
- 支持动态配置年级选项
|
||||
- 适配不同学制需求
|
||||
|
||||
## 🔄 数据流程
|
||||
|
||||
### 查询流程
|
||||
|
||||
1. 用户输入查询条件
|
||||
2. 前端组装查询参数
|
||||
3. 调用列表查询API
|
||||
@@ -140,6 +159,7 @@
|
||||
5. 前端渲染表格和统计
|
||||
|
||||
### 新增/修改流程
|
||||
|
||||
1. 用户填写表单信息
|
||||
2. 前端表单验证
|
||||
3. 调用新增/修改API
|
||||
@@ -148,6 +168,7 @@
|
||||
6. 刷新列表和统计
|
||||
|
||||
### 统计更新流程
|
||||
|
||||
1. 用户点击统计更新
|
||||
2. 调用统计更新API
|
||||
3. 后端重新计算学生数
|
||||
@@ -157,18 +178,21 @@
|
||||
## 🎨 样式规范
|
||||
|
||||
### 色彩设计
|
||||
|
||||
- **主色调**: 蓝色系 (#409eff)
|
||||
- **统计卡片**: 渐变色设计
|
||||
- **状态标签**: 语义化颜色
|
||||
- **交互效果**: 悬停和点击反馈
|
||||
|
||||
### 布局规范
|
||||
|
||||
- **页面间距**: 8px 外边距
|
||||
- **组件间距**: 10px 组件间距
|
||||
- **表格样式**: 斑马纹和悬停效果
|
||||
- **按钮样式**: 图标化小按钮
|
||||
|
||||
### 响应式设计
|
||||
|
||||
- **搜索区域**: 4列布局,支持响应式
|
||||
- **统计卡片**: 4列布局,移动端自适应
|
||||
- **表格列**: 固定宽度和自适应结合
|
||||
@@ -177,6 +201,7 @@
|
||||
## 📝 使用说明
|
||||
|
||||
### 基本操作
|
||||
|
||||
1. **查询班级**: 使用搜索条件筛选班级信息
|
||||
2. **新增班级**: 点击"新增班级"按钮,填写班级信息
|
||||
3. **修改班级**: 点击表格行的"编辑"按钮
|
||||
@@ -184,12 +209,14 @@
|
||||
5. **删除班级**: 选择班级后点击"删除"按钮
|
||||
|
||||
### 高级功能
|
||||
|
||||
1. **批量操作**: 选择多个班级进行批量删除
|
||||
2. **导出数据**: 根据查询条件导出Excel文件
|
||||
3. **统计更新**: 手动更新班级学生统计数据
|
||||
4. **级联查询**: 学院-专业联动查询
|
||||
|
||||
### 注意事项
|
||||
|
||||
- 班级代码必须唯一,系统会自动校验
|
||||
- 删除班级前请确认该班级下无学生信息
|
||||
- 统计数据会定期自动更新,也可手动刷新
|
||||
@@ -198,12 +225,14 @@
|
||||
## 🚀 扩展功能
|
||||
|
||||
### 计划功能
|
||||
|
||||
- 班级学生管理集成
|
||||
- 班级活动管理
|
||||
- 班级成绩统计
|
||||
- 班级考勤管理
|
||||
|
||||
### 技术优化
|
||||
|
||||
- 虚拟滚动优化大数据量
|
||||
- 缓存机制提升查询性能
|
||||
- 实时数据推送
|
||||
@@ -214,4 +243,4 @@
|
||||
**📅 创建时间**: 2025-01-01
|
||||
**👨💻 开发者**: Cursor AI
|
||||
**📝 版本**: v1.0.0
|
||||
**🔄 更新**: 持续迭代中
|
||||
**🔄 更新**: 持续迭代中
|
||||
@@ -59,12 +59,7 @@
|
||||
<el-col :span="6">
|
||||
<el-form-item label="班主任" prop="classTeacherId">
|
||||
<el-select v-model="queryParams.classTeacherId" placeholder="请选择班主任" clearable style="width: 100%">
|
||||
<el-option
|
||||
v-for="teacher in classTeacherOptions"
|
||||
:key="teacher.value"
|
||||
:label="teacher.label"
|
||||
:value="teacher.value"
|
||||
/>
|
||||
<el-option v-for="teacher in classTeacherOptions" :key="teacher.value" :label="teacher.label" :value="teacher.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
@@ -476,8 +471,8 @@ import {
|
||||
getClassStatistics, // 新增统计接口导入
|
||||
getDeptCollegeOptions, // 新增从系统部门获取学院
|
||||
getDeptMajorOptions // 新增从系统部门获取专业
|
||||
} from '@/api/xg-xx/classInfo';
|
||||
import { ClassInfoVO, ClassInfoQuery, ClassInfoForm, ClassStatisticsData, OptionData } from '@/api/xg-xx/classInfo/types';
|
||||
} from '@/api/xg-xx-bak/classInfo';
|
||||
import { ClassInfoVO, ClassInfoQuery, ClassInfoForm, ClassStatisticsData, OptionData } from '@/api/xg-xx-bak/classInfo/types';
|
||||
|
||||
// 类型定义
|
||||
interface DialogOption {
|
||||
@@ -668,7 +663,6 @@ const getList = async () => {
|
||||
total: total.value,
|
||||
listLength: classInfoList.value.length
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('获取列表失败:', error);
|
||||
classInfoList.value = [];
|
||||
@@ -697,11 +691,11 @@ const getOptions = async () => {
|
||||
console.log('部门树根节点数量:', rootDepts.length);
|
||||
|
||||
// 处理每个一级部门
|
||||
rootDepts.forEach(rootDept => {
|
||||
rootDepts.forEach((rootDept) => {
|
||||
console.log(`处理一级部门: ${rootDept.label}, ID: ${rootDept.id}`);
|
||||
// 处理一级部门下的二级学院
|
||||
if (rootDept.children && rootDept.children.length > 0) {
|
||||
rootDept.children.forEach(child => {
|
||||
rootDept.children.forEach((child) => {
|
||||
// 确保ID是有效的数字
|
||||
const childId = child.id;
|
||||
if (childId && !isNaN(Number(childId))) {
|
||||
@@ -805,7 +799,7 @@ const handleCollegeChange = async (deptId: string | number) => {
|
||||
// 处理响应数据
|
||||
if (res.data && Array.isArray(res.data)) {
|
||||
// 转换为选项格式
|
||||
const deptMajors = res.data.map(dept => {
|
||||
const deptMajors = res.data.map((dept) => {
|
||||
// 确保使用数字类型的ID
|
||||
const majorId = Number(dept.deptId);
|
||||
console.log(`专业选项: ${dept.deptName}, ID: ${majorId}`);
|
||||
@@ -856,7 +850,7 @@ const handleFormCollegeChange = async (deptId: string | number) => {
|
||||
// 处理响应数据
|
||||
if (res.data && Array.isArray(res.data)) {
|
||||
// 转换为选项格式
|
||||
const deptMajors = res.data.map(dept => {
|
||||
const deptMajors = res.data.map((dept) => {
|
||||
// 确保使用数字类型的ID
|
||||
const majorId = Number(dept.deptId);
|
||||
console.log(`表单专业选项: ${dept.deptName}, ID: ${majorId}`);
|
||||
@@ -889,7 +883,7 @@ const handleFormCollegeChange = async (deptId: string | number) => {
|
||||
|
||||
/** 专业变更处理 */
|
||||
const handleMajorChange = (majorCode: string | number) => {
|
||||
const selectedMajor = formMajorOptions.value.find(option => option.value === Number(majorCode));
|
||||
const selectedMajor = formMajorOptions.value.find((option) => option.value === Number(majorCode));
|
||||
form.value.majorName = selectedMajor ? selectedMajor.label : '';
|
||||
console.log('专业变更,选择专业:', majorCode, '专业名称:', form.value.majorName);
|
||||
};
|
||||
@@ -898,7 +892,7 @@ const handleMajorChange = (majorCode: string | number) => {
|
||||
/** 班主任变更处理 */
|
||||
const handleClassTeacherChange = (teacherId: string | number) => {
|
||||
form.value.classTeacherId = teacherId;
|
||||
const selectedTeacher = classTeacherOptions.value.find(option => option.value === teacherId);
|
||||
const selectedTeacher = classTeacherOptions.value.find((option) => option.value === teacherId);
|
||||
form.value.classTeacherName = selectedTeacher ? selectedTeacher.label : '';
|
||||
};
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ web/src/views/xg-xx/collectionBatch/
|
||||
## 🎯 功能特性
|
||||
|
||||
### 1. 批次列表管理
|
||||
|
||||
- ✅ 批次列表展示(支持分页)
|
||||
- ✅ 多条件搜索过滤
|
||||
- ✅ 批次状态和类型标签展示
|
||||
@@ -33,24 +34,28 @@ web/src/views/xg-xx/collectionBatch/
|
||||
- ✅ 批次操作(新增、编辑、删除)
|
||||
|
||||
### 2. 批次表单管理
|
||||
|
||||
- ✅ 批次新增和编辑
|
||||
- ✅ 分组表单设计(基本信息、时间设置、目标设置等)
|
||||
- ✅ 表单验证
|
||||
- ✅ 保存和发布功能
|
||||
|
||||
### 3. 批次详情查看
|
||||
|
||||
- ✅ 批次详细信息展示
|
||||
- ✅ 卡片式布局
|
||||
- ✅ 实时状态更新
|
||||
- ✅ 批次操作按钮
|
||||
|
||||
### 4. 统计分析
|
||||
|
||||
- ✅ 批次统计数据展示
|
||||
- ✅ 图表可视化(ECharts)
|
||||
- ✅ 学院完成情况统计
|
||||
- ✅ 多维度数据分析
|
||||
|
||||
### 5. 权限配置
|
||||
|
||||
- ✅ 角色权限管理
|
||||
- ✅ 批量权限配置
|
||||
- ✅ 权限有效期设置
|
||||
@@ -68,6 +73,7 @@ web/src/views/xg-xx/collectionBatch/
|
||||
## 📦 组件说明
|
||||
|
||||
### BatchStatusTag.vue
|
||||
|
||||
批次状态标签组件,用于显示批次的当前状态。
|
||||
|
||||
```vue
|
||||
@@ -75,11 +81,13 @@ web/src/views/xg-xx/collectionBatch/
|
||||
```
|
||||
|
||||
**Props:**
|
||||
|
||||
- `status`: 批次状态(draft, published, active, paused, ended, archived)
|
||||
- `showIcon`: 是否显示图标
|
||||
- `effect`: 标签效果(dark, light, plain)
|
||||
|
||||
### BatchTypeTag.vue
|
||||
|
||||
批次类型标签组件,用于显示批次的类型。
|
||||
|
||||
```vue
|
||||
@@ -87,11 +95,13 @@ web/src/views/xg-xx/collectionBatch/
|
||||
```
|
||||
|
||||
**Props:**
|
||||
|
||||
- `type`: 批次类型(student_info, scholarship, evaluation, other)
|
||||
- `showIcon`: 是否显示图标
|
||||
- `effect`: 标签效果
|
||||
|
||||
### ProgressBar.vue
|
||||
|
||||
进度条组件,用于显示批次的完成进度。
|
||||
|
||||
```vue
|
||||
@@ -99,6 +109,7 @@ web/src/views/xg-xx/collectionBatch/
|
||||
```
|
||||
|
||||
**Props:**
|
||||
|
||||
- `current`: 当前完成数量
|
||||
- `total`: 总数量
|
||||
- `strokeWidth`: 进度条宽度
|
||||
@@ -106,6 +117,7 @@ web/src/views/xg-xx/collectionBatch/
|
||||
- `showDetail`: 是否显示详细信息
|
||||
|
||||
### BatchActions.vue
|
||||
|
||||
批次操作组件,提供批次相关的操作按钮。
|
||||
|
||||
```vue
|
||||
@@ -113,9 +125,11 @@ web/src/views/xg-xx/collectionBatch/
|
||||
```
|
||||
|
||||
**Props:**
|
||||
|
||||
- `batch`: 批次数据对象
|
||||
|
||||
**Events:**
|
||||
|
||||
- `start`: 开始批次
|
||||
- `pause`: 暂停批次
|
||||
- `resume`: 恢复批次
|
||||
@@ -127,6 +141,7 @@ web/src/views/xg-xx/collectionBatch/
|
||||
- `delete`: 删除批次
|
||||
|
||||
### SearchForm.vue
|
||||
|
||||
搜索表单组件,用于批次列表的搜索过滤。
|
||||
|
||||
```vue
|
||||
@@ -134,6 +149,7 @@ web/src/views/xg-xx/collectionBatch/
|
||||
```
|
||||
|
||||
**Events:**
|
||||
|
||||
- `search`: 搜索事件,返回查询参数
|
||||
- `reset`: 重置事件
|
||||
|
||||
@@ -142,16 +158,19 @@ web/src/views/xg-xx/collectionBatch/
|
||||
### 1. 批次管理流程
|
||||
|
||||
1. **创建批次**
|
||||
|
||||
- 点击"新增批次"按钮
|
||||
- 填写批次基本信息、时间设置、目标设置等
|
||||
- 保存草稿或直接发布
|
||||
|
||||
2. **管理批次**
|
||||
|
||||
- 在列表中查看批次状态和进度
|
||||
- 使用搜索功能快速定位批次
|
||||
- 通过操作按钮管理批次状态
|
||||
|
||||
3. **查看统计**
|
||||
|
||||
- 进入统计页面查看批次数据
|
||||
- 查看图表分析和学院完成情况
|
||||
- 导出统计报告
|
||||
@@ -248,14 +267,17 @@ getOverallStatistics(): Promise<any>
|
||||
### 常见问题
|
||||
|
||||
1. **页面无法访问**
|
||||
|
||||
- 检查路由配置是否正确
|
||||
- 确认用户权限是否充足
|
||||
|
||||
2. **数据加载失败**
|
||||
|
||||
- 检查 API 接口是否正常
|
||||
- 确认后端服务是否启动
|
||||
|
||||
3. **图表显示异常**
|
||||
|
||||
- 检查 ECharts 是否正确引入
|
||||
- 确认数据格式是否正确
|
||||
|
||||
@@ -282,4 +304,4 @@ getOverallStatistics(): Promise<any>
|
||||
|
||||
**版本信息**:V1.0.0
|
||||
**更新时间**:2025-01-17
|
||||
**维护团队**:前端开发组
|
||||
**维护团队**:前端开发组
|
||||
@@ -7,55 +7,17 @@
|
||||
</el-button>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item
|
||||
v-if="batch.batchStatus === 'draft'"
|
||||
command="start"
|
||||
icon="VideoPlay"
|
||||
>
|
||||
开始批次
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item
|
||||
v-if="batch.batchStatus === 'active'"
|
||||
command="pause"
|
||||
icon="VideoPause"
|
||||
>
|
||||
暂停批次
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item
|
||||
v-if="batch.batchStatus === 'paused'"
|
||||
command="resume"
|
||||
icon="VideoPlay"
|
||||
>
|
||||
恢复批次
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item
|
||||
v-if="['active', 'paused'].includes(batch.batchStatus)"
|
||||
command="end"
|
||||
icon="VideoCamera"
|
||||
>
|
||||
结束批次
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item v-if="batch.batchStatus === 'draft'" command="start" icon="VideoPlay"> 开始批次 </el-dropdown-item>
|
||||
<el-dropdown-item v-if="batch.batchStatus === 'active'" command="pause" icon="VideoPause"> 暂停批次 </el-dropdown-item>
|
||||
<el-dropdown-item v-if="batch.batchStatus === 'paused'" command="resume" icon="VideoPlay"> 恢复批次 </el-dropdown-item>
|
||||
<el-dropdown-item v-if="['active', 'paused'].includes(batch.batchStatus)" command="end" icon="VideoCamera"> 结束批次 </el-dropdown-item>
|
||||
<el-dropdown-item divided />
|
||||
<el-dropdown-item command="copy" icon="CopyDocument">
|
||||
复制批次
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item command="statistics" icon="TrendCharts">
|
||||
统计分析
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item command="permission" icon="Lock">
|
||||
权限配置
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item command="copy" icon="CopyDocument"> 复制批次 </el-dropdown-item>
|
||||
<el-dropdown-item command="statistics" icon="TrendCharts"> 统计分析 </el-dropdown-item>
|
||||
<el-dropdown-item command="permission" icon="Lock"> 权限配置 </el-dropdown-item>
|
||||
<el-dropdown-item divided />
|
||||
<el-dropdown-item command="export" icon="Download">
|
||||
导出数据
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item
|
||||
command="delete"
|
||||
icon="Delete"
|
||||
style="color: #f56c6c"
|
||||
>
|
||||
删除批次
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item command="export" icon="Download"> 导出数据 </el-dropdown-item>
|
||||
<el-dropdown-item command="delete" icon="Delete" style="color: #f56c6c"> 删除批次 </el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
@@ -63,7 +25,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { CollectionBatchVO } from '@/api/xg-xx/types';
|
||||
import { CollectionBatchVO } from '@/api/xg-xx-bak/types';
|
||||
|
||||
interface Props {
|
||||
batch: CollectionBatchVO;
|
||||
|
||||
@@ -46,7 +46,7 @@ const typeConfig = {
|
||||
};
|
||||
|
||||
const tagType = computed(() => {
|
||||
return typeConfig[props.type as keyof typeof typeConfig]?.type as 'primary' | 'success' | 'warning' | 'info' | 'danger' || 'info';
|
||||
return (typeConfig[props.type as keyof typeof typeConfig]?.type as 'primary' | 'success' | 'warning' | 'info' | 'danger') || 'info';
|
||||
});
|
||||
|
||||
const typeText = computed(() => {
|
||||
|
||||
@@ -1,12 +1,6 @@
|
||||
<template>
|
||||
<div class="progress-container">
|
||||
<el-progress
|
||||
:percentage="percentage"
|
||||
:stroke-width="strokeWidth"
|
||||
:color="progressColor"
|
||||
:show-text="showText"
|
||||
:format="format"
|
||||
/>
|
||||
<el-progress :percentage="percentage" :stroke-width="strokeWidth" :color="progressColor" :show-text="showText" :format="format" />
|
||||
<div v-if="showDetail" class="progress-detail">
|
||||
<span class="detail-text">{{ current }}/{{ total }}</span>
|
||||
<span v-if="showRate" class="rate-text">({{ percentage.toFixed(1) }}%)</span>
|
||||
|
||||
@@ -4,22 +4,12 @@
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="6">
|
||||
<el-form-item label="批次名称" prop="batchName">
|
||||
<el-input
|
||||
v-model="searchForm.batchName"
|
||||
placeholder="请输入批次名称"
|
||||
clearable
|
||||
@keyup.enter="handleSearch"
|
||||
/>
|
||||
<el-input v-model="searchForm.batchName" placeholder="请输入批次名称" clearable @keyup.enter="handleSearch" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="批次编码" prop="batchCode">
|
||||
<el-input
|
||||
v-model="searchForm.batchCode"
|
||||
placeholder="请输入批次编码"
|
||||
clearable
|
||||
@keyup.enter="handleSearch"
|
||||
/>
|
||||
<el-input v-model="searchForm.batchCode" placeholder="请输入批次编码" clearable @keyup.enter="handleSearch" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
@@ -49,22 +39,12 @@
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="6">
|
||||
<el-form-item label="学年学期" prop="academicYear">
|
||||
<el-input
|
||||
v-model="searchForm.academicYear"
|
||||
placeholder="请输入学年学期"
|
||||
clearable
|
||||
@keyup.enter="handleSearch"
|
||||
/>
|
||||
<el-input v-model="searchForm.academicYear" placeholder="请输入学年学期" clearable @keyup.enter="handleSearch" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="目标届别" prop="targetGrades">
|
||||
<el-input
|
||||
v-model="searchForm.targetGrades"
|
||||
placeholder="请输入目标届别"
|
||||
clearable
|
||||
@keyup.enter="handleSearch"
|
||||
/>
|
||||
<el-input v-model="searchForm.targetGrades" placeholder="请输入目标届别" clearable @keyup.enter="handleSearch" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
@@ -94,7 +74,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive } from 'vue';
|
||||
import { CollectionBatchQuery } from '@/api/xg-xx/types';
|
||||
import { CollectionBatchQuery } from '@/api/xg-xx-bak/types';
|
||||
|
||||
const emit = defineEmits<{
|
||||
search: [query: CollectionBatchQuery];
|
||||
|
||||
@@ -229,44 +229,17 @@
|
||||
>
|
||||
结束批次
|
||||
</el-button>
|
||||
<el-button
|
||||
type="info"
|
||||
icon="Edit"
|
||||
@click="handleEdit"
|
||||
v-hasPermi="['xg:collectionBatch:edit']"
|
||||
>
|
||||
编辑批次
|
||||
</el-button>
|
||||
<el-button
|
||||
type="success"
|
||||
icon="TrendCharts"
|
||||
@click="handleStatistics"
|
||||
v-hasPermi="['xg:collectionBatch:statistics']"
|
||||
>
|
||||
统计分析
|
||||
</el-button>
|
||||
<el-button
|
||||
type="warning"
|
||||
icon="CopyDocument"
|
||||
@click="handleCopy"
|
||||
v-hasPermi="['xg:collectionBatch:add']"
|
||||
>
|
||||
复制批次
|
||||
</el-button>
|
||||
<el-button type="info" icon="Edit" @click="handleEdit" v-hasPermi="['xg:collectionBatch:edit']"> 编辑批次 </el-button>
|
||||
<el-button type="success" icon="TrendCharts" @click="handleStatistics" v-hasPermi="['xg:collectionBatch:statistics']"> 统计分析 </el-button>
|
||||
<el-button type="warning" icon="CopyDocument" @click="handleCopy" v-hasPermi="['xg:collectionBatch:add']"> 复制批次 </el-button>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup name="CollectionBatchDetail" lang="ts">
|
||||
import {
|
||||
getCollectionBatch,
|
||||
startBatch,
|
||||
pauseBatch,
|
||||
resumeBatch,
|
||||
endBatch
|
||||
} from '@/api/xg-xx/collectionBatch';
|
||||
import { CollectionBatchVO } from '@/api/xg-xx/types';
|
||||
import { getCollectionBatch, startBatch, pauseBatch, resumeBatch, endBatch } from '@/api/xg-xx-bak/collectionBatch';
|
||||
import { CollectionBatchVO } from '@/api/xg-xx-bak/types';
|
||||
|
||||
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
||||
const route = useRoute();
|
||||
|
||||
@@ -288,12 +288,8 @@
|
||||
</template>
|
||||
|
||||
<script setup name="CollectionBatchForm" lang="ts">
|
||||
import {
|
||||
getCollectionBatch,
|
||||
addCollectionBatch,
|
||||
updateCollectionBatch
|
||||
} from '@/api/xg-xx/collectionBatch';
|
||||
import { CollectionBatchForm } from '@/api/xg-xx/types';
|
||||
import { getCollectionBatch, addCollectionBatch, updateCollectionBatch } from '@/api/xg-xx-bak/collectionBatch';
|
||||
import { CollectionBatchForm } from '@/api/xg-xx-bak/types';
|
||||
|
||||
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
||||
const route = useRoute();
|
||||
@@ -339,18 +335,10 @@ const rules = ref({
|
||||
{ required: true, message: '批次名称不能为空', trigger: 'blur' },
|
||||
{ min: 2, max: 100, message: '批次名称长度在 2 到 100 个字符', trigger: 'blur' }
|
||||
],
|
||||
batchType: [
|
||||
{ required: true, message: '请选择批次类型', trigger: 'change' }
|
||||
],
|
||||
academicYear: [
|
||||
{ required: true, message: '学年学期不能为空', trigger: 'blur' }
|
||||
],
|
||||
startTime: [
|
||||
{ required: true, message: '开始时间不能为空', trigger: 'change' }
|
||||
],
|
||||
endTime: [
|
||||
{ required: true, message: '结束时间不能为空', trigger: 'change' }
|
||||
]
|
||||
batchType: [{ required: true, message: '请选择批次类型', trigger: 'change' }],
|
||||
academicYear: [{ required: true, message: '学年学期不能为空', trigger: 'blur' }],
|
||||
startTime: [{ required: true, message: '开始时间不能为空', trigger: 'change' }],
|
||||
endTime: [{ required: true, message: '结束时间不能为空', trigger: 'change' }]
|
||||
});
|
||||
|
||||
/** 获取批次详情 */
|
||||
|
||||
@@ -62,12 +62,7 @@
|
||||
<el-row :gutter="20">
|
||||
<el-col :xs="24" :sm="12" :md="8" :lg="6" :xl="6">
|
||||
<el-form-item label="批次名称" prop="batchName">
|
||||
<el-input
|
||||
v-model="queryParams.batchName"
|
||||
placeholder="请输入批次名称"
|
||||
clearable
|
||||
@keyup.enter="handleQuery"
|
||||
/>
|
||||
<el-input v-model="queryParams.batchName" placeholder="请输入批次名称" clearable @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="12" :md="8" :lg="6" :xl="6">
|
||||
@@ -145,14 +140,7 @@
|
||||
|
||||
<!-- 数据表格 -->
|
||||
<el-card class="table-card" shadow="never">
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
:data="batchList"
|
||||
class="scroll-table"
|
||||
@selection-change="handleSelectionChange"
|
||||
stripe
|
||||
height="600"
|
||||
>
|
||||
<el-table v-loading="loading" :data="batchList" class="scroll-table" @selection-change="handleSelectionChange" stripe height="600">
|
||||
<el-table-column type="selection" width="55" align="center" />
|
||||
<el-table-column label="批次名称" prop="batchName" min-width="200" show-overflow-tooltip />
|
||||
<el-table-column label="批次类型" prop="batchType" width="120" align="center">
|
||||
@@ -192,28 +180,13 @@
|
||||
<template #default="scope">
|
||||
<div class="action-buttons">
|
||||
<el-tooltip content="查看详情" placement="top">
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
icon="View"
|
||||
@click="handleDetail(scope.row)"
|
||||
/>
|
||||
<el-button link type="primary" icon="View" @click="handleDetail(scope.row)" />
|
||||
</el-tooltip>
|
||||
<el-tooltip content="编辑" placement="top">
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
icon="Edit"
|
||||
@click="handleUpdate(scope.row)"
|
||||
/>
|
||||
<el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" />
|
||||
</el-tooltip>
|
||||
<el-tooltip content="删除" placement="top">
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
icon="Delete"
|
||||
@click="handleDelete(scope.row)"
|
||||
/>
|
||||
<el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" />
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</template>
|
||||
@@ -236,20 +209,8 @@
|
||||
</el-card>
|
||||
|
||||
<!-- 新增/编辑对话框 -->
|
||||
<el-dialog
|
||||
v-model="open"
|
||||
:title="title"
|
||||
width="800px"
|
||||
class="batch-dialog"
|
||||
append-to-body
|
||||
>
|
||||
<el-form
|
||||
ref="batchFormRef"
|
||||
:model="form"
|
||||
:rules="rules"
|
||||
label-width="100px"
|
||||
class="batch-form"
|
||||
>
|
||||
<el-dialog v-model="open" :title="title" width="800px" class="batch-dialog" append-to-body>
|
||||
<el-form ref="batchFormRef" :model="form" :rules="rules" label-width="100px" class="batch-form">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="批次编码" prop="batchCode">
|
||||
@@ -314,12 +275,7 @@
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-form-item label="批次描述" prop="description">
|
||||
<el-input
|
||||
v-model="form.description"
|
||||
type="textarea"
|
||||
:rows="4"
|
||||
placeholder="请输入批次描述"
|
||||
/>
|
||||
<el-input v-model="form.description" type="textarea" :rows="4" placeholder="请输入批次描述" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
@@ -333,32 +289,27 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, toRefs, onMounted } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { listCollectionBatch, getCollectionBatch, delCollectionBatch, exportCollectionBatch, addCollectionBatch, updateCollectionBatch } from '@/api/xg-xx/collectionBatch'
|
||||
import { ref, reactive, toRefs, onMounted } from 'vue';
|
||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||
import {
|
||||
Collection,
|
||||
Select,
|
||||
User,
|
||||
DocumentCopy,
|
||||
Plus,
|
||||
Download,
|
||||
Search,
|
||||
Refresh,
|
||||
View,
|
||||
Edit,
|
||||
Delete
|
||||
} from '@element-plus/icons-vue'
|
||||
listCollectionBatch,
|
||||
getCollectionBatch,
|
||||
delCollectionBatch,
|
||||
exportCollectionBatch,
|
||||
addCollectionBatch,
|
||||
updateCollectionBatch
|
||||
} from '@/api/xg-xx-bak/collectionBatch';
|
||||
import { Collection, Select, User, DocumentCopy, Plus, Download, Search, Refresh, View, Edit, Delete } from '@element-plus/icons-vue';
|
||||
|
||||
// 响应式数据
|
||||
const batchList = ref([])
|
||||
const loading = ref(false)
|
||||
const total = ref(0)
|
||||
const open = ref(false)
|
||||
const title = ref('')
|
||||
const batchList = ref([]);
|
||||
const loading = ref(false);
|
||||
const total = ref(0);
|
||||
const open = ref(false);
|
||||
const title = ref('');
|
||||
|
||||
const queryFormRef = ref()
|
||||
const batchFormRef = ref()
|
||||
const queryFormRef = ref();
|
||||
const batchFormRef = ref();
|
||||
|
||||
// 统计数据
|
||||
const statistics = ref({
|
||||
@@ -366,7 +317,7 @@ const statistics = ref({
|
||||
activeBatches: 0,
|
||||
totalStudents: 0,
|
||||
completedBatches: 0
|
||||
})
|
||||
});
|
||||
|
||||
// 表单数据
|
||||
const initFormData = {
|
||||
@@ -379,7 +330,7 @@ const initFormData = {
|
||||
startTime: '',
|
||||
endTime: '',
|
||||
description: ''
|
||||
}
|
||||
};
|
||||
|
||||
const data = reactive({
|
||||
form: { ...initFormData },
|
||||
@@ -399,9 +350,9 @@ const data = reactive({
|
||||
startTime: [{ required: true, message: '开始时间不能为空', trigger: 'change' }],
|
||||
endTime: [{ required: true, message: '结束时间不能为空', trigger: 'change' }]
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
const { queryParams, form, rules } = toRefs(data)
|
||||
const { queryParams, form, rules } = toRefs(data);
|
||||
|
||||
// 工具方法
|
||||
const getBatchTypeTagType = (type: string) => {
|
||||
@@ -410,9 +361,9 @@ const getBatchTypeTagType = (type: string) => {
|
||||
semester: 'success',
|
||||
monthly: 'warning',
|
||||
custom: 'info'
|
||||
}
|
||||
return typeMap[type] || 'info'
|
||||
}
|
||||
};
|
||||
return typeMap[type] || 'info';
|
||||
};
|
||||
|
||||
const getBatchTypeText = (type: string) => {
|
||||
const typeMap = {
|
||||
@@ -420,20 +371,20 @@ const getBatchTypeText = (type: string) => {
|
||||
semester: '学期采集',
|
||||
monthly: '月度采集',
|
||||
custom: '自定义'
|
||||
}
|
||||
return typeMap[type] || type
|
||||
}
|
||||
};
|
||||
return typeMap[type] || type;
|
||||
};
|
||||
|
||||
const getStatusTagType = (status: string) => {
|
||||
const statusMap = {
|
||||
'1': 'info', // 草稿
|
||||
'2': 'warning', // 发布
|
||||
'3': 'success', // 进行中
|
||||
'4': 'primary', // 已结束
|
||||
'5': 'info' // 已归档
|
||||
}
|
||||
return statusMap[status] || 'info'
|
||||
}
|
||||
'1': 'info', // 草稿
|
||||
'2': 'warning', // 发布
|
||||
'3': 'success', // 进行中
|
||||
'4': 'primary', // 已结束
|
||||
'5': 'info' // 已归档
|
||||
};
|
||||
return statusMap[status] || 'info';
|
||||
};
|
||||
|
||||
const getStatusText = (status: string) => {
|
||||
const statusMap = {
|
||||
@@ -442,175 +393,173 @@ const getStatusText = (status: string) => {
|
||||
'3': '进行中',
|
||||
'4': '已结束',
|
||||
'5': '已归档'
|
||||
}
|
||||
return statusMap[status] || status
|
||||
}
|
||||
};
|
||||
return statusMap[status] || status;
|
||||
};
|
||||
|
||||
const getProgressColor = (percentage: number) => {
|
||||
if (percentage >= 80) return '#67c23a'
|
||||
if (percentage >= 60) return '#e6a23c'
|
||||
return '#f56c6c'
|
||||
}
|
||||
if (percentage >= 80) return '#67c23a';
|
||||
if (percentage >= 60) return '#e6a23c';
|
||||
return '#f56c6c';
|
||||
};
|
||||
|
||||
// 查询方法
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
loading.value = true;
|
||||
try {
|
||||
console.log('开始获取批次列表...')
|
||||
const response = await listCollectionBatch(queryParams.value)
|
||||
console.log('API响应:', response)
|
||||
console.log('开始获取批次列表...');
|
||||
const response = await listCollectionBatch(queryParams.value);
|
||||
console.log('API响应:', response);
|
||||
|
||||
// 兼容不同的响应格式
|
||||
batchList.value = response.rows || response.data || []
|
||||
total.value = response.total || 0
|
||||
batchList.value = response.rows || response.data || [];
|
||||
total.value = response.total || 0;
|
||||
|
||||
// 更新统计数据
|
||||
updateStatistics()
|
||||
updateStatistics();
|
||||
|
||||
ElMessage.success('数据加载成功')
|
||||
ElMessage.success('数据加载成功');
|
||||
} catch (error) {
|
||||
console.error('获取批次列表失败:', error)
|
||||
ElMessage.error('数据加载失败')
|
||||
batchList.value = []
|
||||
total.value = 0
|
||||
console.error('获取批次列表失败:', error);
|
||||
ElMessage.error('数据加载失败');
|
||||
batchList.value = [];
|
||||
total.value = 0;
|
||||
} finally {
|
||||
loading.value = false
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 更新统计数据
|
||||
const updateStatistics = () => {
|
||||
statistics.value = {
|
||||
totalBatches: batchList.value.length,
|
||||
activeBatches: batchList.value.filter(item => item.batchStatus === '3').length, // 进行中
|
||||
activeBatches: batchList.value.filter((item) => item.batchStatus === '3').length, // 进行中
|
||||
totalStudents: batchList.value.reduce((sum, item) => sum + (item.totalStudents || 0), 0),
|
||||
completedBatches: batchList.value.filter(item => item.batchStatus === '4').length // 已结束
|
||||
}
|
||||
}
|
||||
completedBatches: batchList.value.filter((item) => item.batchStatus === '4').length // 已结束
|
||||
};
|
||||
};
|
||||
|
||||
// 查询按钮
|
||||
const handleQuery = () => {
|
||||
queryParams.value.pageNum = 1
|
||||
getList()
|
||||
}
|
||||
queryParams.value.pageNum = 1;
|
||||
getList();
|
||||
};
|
||||
|
||||
// 重置按钮
|
||||
const resetQuery = () => {
|
||||
queryFormRef.value?.resetFields()
|
||||
handleQuery()
|
||||
}
|
||||
queryFormRef.value?.resetFields();
|
||||
handleQuery();
|
||||
};
|
||||
|
||||
// 新增按钮
|
||||
const handleAdd = () => {
|
||||
reset()
|
||||
open.value = true
|
||||
title.value = '新增批次'
|
||||
}
|
||||
reset();
|
||||
open.value = true;
|
||||
title.value = '新增批次';
|
||||
};
|
||||
|
||||
// 修改按钮
|
||||
const handleUpdate = (row: any) => {
|
||||
reset()
|
||||
form.value = { ...row }
|
||||
open.value = true
|
||||
title.value = '修改批次'
|
||||
}
|
||||
reset();
|
||||
form.value = { ...row };
|
||||
open.value = true;
|
||||
title.value = '修改批次';
|
||||
};
|
||||
|
||||
// 详情按钮
|
||||
const handleDetail = (row: any) => {
|
||||
ElMessage.info(`查看批次详情: ${row.batchName}`)
|
||||
}
|
||||
ElMessage.info(`查看批次详情: ${row.batchName}`);
|
||||
};
|
||||
|
||||
// 删除按钮
|
||||
const handleDelete = (row: any) => {
|
||||
ElMessageBox.confirm(
|
||||
`确定删除批次"${row.batchName}"吗?`,
|
||||
'删除确认',
|
||||
{
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}
|
||||
).then(() => {
|
||||
ElMessage.success('删除成功')
|
||||
getList()
|
||||
}).catch(() => {
|
||||
ElMessage.info('已取消删除')
|
||||
ElMessageBox.confirm(`确定删除批次"${row.batchName}"吗?`, '删除确认', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
})
|
||||
}
|
||||
.then(() => {
|
||||
ElMessage.success('删除成功');
|
||||
getList();
|
||||
})
|
||||
.catch(() => {
|
||||
ElMessage.info('已取消删除');
|
||||
});
|
||||
};
|
||||
|
||||
// 导出按钮
|
||||
const handleExport = () => {
|
||||
ElMessage.info('导出功能开发中...')
|
||||
}
|
||||
ElMessage.info('导出功能开发中...');
|
||||
};
|
||||
|
||||
// 表格选择
|
||||
const handleSelectionChange = (selection: any[]) => {
|
||||
console.log('选择的行:', selection)
|
||||
}
|
||||
console.log('选择的行:', selection);
|
||||
};
|
||||
|
||||
// 分页
|
||||
const handleSizeChange = (size: number) => {
|
||||
queryParams.value.pageSize = size
|
||||
getList()
|
||||
}
|
||||
queryParams.value.pageSize = size;
|
||||
getList();
|
||||
};
|
||||
|
||||
const handleCurrentChange = (page: number) => {
|
||||
queryParams.value.pageNum = page
|
||||
getList()
|
||||
}
|
||||
queryParams.value.pageNum = page;
|
||||
getList();
|
||||
};
|
||||
|
||||
// 表单提交
|
||||
const submitForm = async () => {
|
||||
if (!batchFormRef.value) return
|
||||
if (!batchFormRef.value) return;
|
||||
|
||||
try {
|
||||
const valid = await batchFormRef.value.validate()
|
||||
const valid = await batchFormRef.value.validate();
|
||||
if (valid) {
|
||||
const formData = { ...form.value }
|
||||
const formData = { ...form.value };
|
||||
|
||||
if (form.value.id) {
|
||||
// 编辑模式
|
||||
await updateCollectionBatch(formData)
|
||||
ElMessage.success('修改成功')
|
||||
await updateCollectionBatch(formData);
|
||||
ElMessage.success('修改成功');
|
||||
} else {
|
||||
// 新增模式
|
||||
await addCollectionBatch(formData)
|
||||
ElMessage.success('新增成功')
|
||||
await addCollectionBatch(formData);
|
||||
ElMessage.success('新增成功');
|
||||
}
|
||||
|
||||
open.value = false
|
||||
await getList()
|
||||
open.value = false;
|
||||
await getList();
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error('保存失败:', error)
|
||||
console.error('保存失败:', error);
|
||||
// 显示详细的错误信息
|
||||
if (error.response && error.response.data && error.response.data.msg) {
|
||||
ElMessage.error(error.response.data.msg)
|
||||
ElMessage.error(error.response.data.msg);
|
||||
} else if (error.message) {
|
||||
ElMessage.error(error.message)
|
||||
ElMessage.error(error.message);
|
||||
} else {
|
||||
ElMessage.error('保存失败,请重试')
|
||||
ElMessage.error('保存失败,请重试');
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 取消
|
||||
const cancel = () => {
|
||||
open.value = false
|
||||
reset()
|
||||
}
|
||||
open.value = false;
|
||||
reset();
|
||||
};
|
||||
|
||||
// 重置表单
|
||||
const reset = () => {
|
||||
form.value = { ...initFormData }
|
||||
batchFormRef.value?.resetFields()
|
||||
}
|
||||
form.value = { ...initFormData };
|
||||
batchFormRef.value?.resetFields();
|
||||
};
|
||||
|
||||
// 初始化
|
||||
onMounted(() => {
|
||||
console.log('页面初始化...')
|
||||
getList()
|
||||
})
|
||||
console.log('页面初始化...');
|
||||
getList();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -13,12 +13,7 @@
|
||||
<el-form :model="queryParams" inline>
|
||||
<el-form-item label="选择批次">
|
||||
<el-select v-model="queryParams.batchId" placeholder="请选择批次" @change="handleBatchChange" style="width: 300px">
|
||||
<el-option
|
||||
v-for="batch in batchList"
|
||||
:key="batch.id"
|
||||
:label="`${batch.batchName} (${batch.batchCode})`"
|
||||
:value="batch.id"
|
||||
/>
|
||||
<el-option v-for="batch in batchList" :key="batch.id" :label="`${batch.batchName} (${batch.batchCode})`" :value="batch.id" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
@@ -44,42 +39,27 @@
|
||||
<el-table-column prop="targetScope" label="管理范围" align="center" min-width="200" show-overflow-tooltip />
|
||||
<el-table-column label="查看权限" align="center" width="100">
|
||||
<template #default="scope">
|
||||
<el-switch
|
||||
v-model="scope.row.canView"
|
||||
@change="handlePermissionChange(scope.row, 'canView')"
|
||||
/>
|
||||
<el-switch v-model="scope.row.canView" @change="handlePermissionChange(scope.row, 'canView')" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="编辑权限" align="center" width="100">
|
||||
<template #default="scope">
|
||||
<el-switch
|
||||
v-model="scope.row.canEdit"
|
||||
@change="handlePermissionChange(scope.row, 'canEdit')"
|
||||
/>
|
||||
<el-switch v-model="scope.row.canEdit" @change="handlePermissionChange(scope.row, 'canEdit')" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="审核权限" align="center" width="100">
|
||||
<template #default="scope">
|
||||
<el-switch
|
||||
v-model="scope.row.canAudit"
|
||||
@change="handlePermissionChange(scope.row, 'canAudit')"
|
||||
/>
|
||||
<el-switch v-model="scope.row.canAudit" @change="handlePermissionChange(scope.row, 'canAudit')" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="统计权限" align="center" width="100">
|
||||
<template #default="scope">
|
||||
<el-switch
|
||||
v-model="scope.row.canStatistics"
|
||||
@change="handlePermissionChange(scope.row, 'canStatistics')"
|
||||
/>
|
||||
<el-switch v-model="scope.row.canStatistics" @change="handlePermissionChange(scope.row, 'canStatistics')" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="导出权限" align="center" width="100">
|
||||
<template #default="scope">
|
||||
<el-switch
|
||||
v-model="scope.row.canExport"
|
||||
@change="handlePermissionChange(scope.row, 'canExport')"
|
||||
/>
|
||||
<el-switch v-model="scope.row.canExport" @change="handlePermissionChange(scope.row, 'canExport')" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="有效期" align="center" width="180">
|
||||
@@ -102,13 +82,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>
|
||||
|
||||
<!-- 权限配置对话框 -->
|
||||
@@ -230,8 +204,8 @@
|
||||
</template>
|
||||
|
||||
<script setup name="CollectionBatchPermission" lang="ts">
|
||||
import { getActiveBatches } from '@/api/xg-xx/collectionBatch';
|
||||
import { CollectionBatchVO } from '@/api/xg-xx/types';
|
||||
import { getActiveBatches } from '@/api/xg-xx-bak/collectionBatch';
|
||||
import { CollectionBatchVO } from '@/api/xg-xx-bak/types';
|
||||
|
||||
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
||||
const router = useRouter();
|
||||
|
||||
@@ -6,12 +6,7 @@
|
||||
<span class="text-lg font-semibold">批次统计分析</span>
|
||||
<div class="flex space-x-2">
|
||||
<el-select v-model="selectedBatch" placeholder="选择批次" @change="handleBatchChange" style="width: 200px">
|
||||
<el-option
|
||||
v-for="batch in batchList"
|
||||
:key="batch.id"
|
||||
:label="batch.batchName"
|
||||
:value="batch.id"
|
||||
/>
|
||||
<el-option v-for="batch in batchList" :key="batch.id" :label="batch.batchName" :value="batch.id" />
|
||||
</el-select>
|
||||
<el-button @click="goBack" icon="Back">返回</el-button>
|
||||
</div>
|
||||
@@ -79,7 +74,7 @@
|
||||
<span>完成度分布</span>
|
||||
</div>
|
||||
</template>
|
||||
<div ref="completionChartRef" style="width: 100%; height: 300px;"></div>
|
||||
<div ref="completionChartRef" style="width: 100%; height: 300px"></div>
|
||||
</el-card>
|
||||
|
||||
<!-- 进度趋势图 -->
|
||||
@@ -90,7 +85,7 @@
|
||||
<span>提交趋势</span>
|
||||
</div>
|
||||
</template>
|
||||
<div ref="trendChartRef" style="width: 100%; height: 300px;"></div>
|
||||
<div ref="trendChartRef" style="width: 100%; height: 300px"></div>
|
||||
</el-card>
|
||||
</div>
|
||||
|
||||
@@ -111,20 +106,12 @@
|
||||
<el-table-column prop="pendingCount" label="待审核" align="center" width="100" />
|
||||
<el-table-column label="提交率" align="center" width="120">
|
||||
<template #default="scope">
|
||||
<el-progress
|
||||
:percentage="scope.row.submitRate || 0"
|
||||
:stroke-width="8"
|
||||
:color="getProgressColor(scope.row.submitRate || 0)"
|
||||
/>
|
||||
<el-progress :percentage="scope.row.submitRate || 0" :stroke-width="8" :color="getProgressColor(scope.row.submitRate || 0)" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="完成率" align="center" width="120">
|
||||
<template #default="scope">
|
||||
<el-progress
|
||||
:percentage="scope.row.completionRate || 0"
|
||||
:stroke-width="8"
|
||||
:color="getProgressColor(scope.row.completionRate || 0)"
|
||||
/>
|
||||
<el-progress :percentage="scope.row.completionRate || 0" :stroke-width="8" :color="getProgressColor(scope.row.completionRate || 0)" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
@@ -140,7 +127,7 @@
|
||||
<span>年级分布</span>
|
||||
</div>
|
||||
</template>
|
||||
<div ref="gradeChartRef" style="width: 100%; height: 250px;"></div>
|
||||
<div ref="gradeChartRef" style="width: 100%; height: 250px"></div>
|
||||
</el-card>
|
||||
|
||||
<!-- 专业分布 -->
|
||||
@@ -151,7 +138,7 @@
|
||||
<span>专业分布</span>
|
||||
</div>
|
||||
</template>
|
||||
<div ref="majorChartRef" style="width: 100%; height: 250px;"></div>
|
||||
<div ref="majorChartRef" style="width: 100%; height: 250px"></div>
|
||||
</el-card>
|
||||
</div>
|
||||
</el-card>
|
||||
@@ -165,14 +152,14 @@ import {
|
||||
getOverallStatistics,
|
||||
getBatchCompletionStatistics,
|
||||
getBatchDimensionStatistics
|
||||
} from '@/api/xg-xx/collectionBatch';
|
||||
} from '@/api/xg-xx-bak/collectionBatch';
|
||||
import {
|
||||
CollectionBatchVO,
|
||||
BatchStatistics,
|
||||
BatchCompletionStatistics,
|
||||
CollegeCompletionStatistics,
|
||||
BatchDimensionStatistics
|
||||
} from '@/api/xg-xx/types';
|
||||
} from '@/api/xg-xx-bak/types';
|
||||
import * as echarts from 'echarts';
|
||||
|
||||
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
||||
@@ -269,10 +256,7 @@ const handleBatchChange = () => {
|
||||
|
||||
/** 加载统计数据 */
|
||||
const loadStatistics = async () => {
|
||||
await Promise.all([
|
||||
getBatchCompletionStats(),
|
||||
getDimensionStats()
|
||||
]);
|
||||
await Promise.all([getBatchCompletionStats(), getDimensionStats()]);
|
||||
|
||||
// 更新图表
|
||||
nextTick(() => {
|
||||
@@ -362,8 +346,8 @@ const updateTrendChart = () => {
|
||||
const date = new Date();
|
||||
date.setDate(date.getDate() - i);
|
||||
dates.push(date.toISOString().split('T')[0]);
|
||||
submitted.push(Math.floor(Math.random() * 100) + (completionStats.value.submittedCount || 0) * (7 - i) / 7);
|
||||
approved.push(Math.floor(Math.random() * 80) + (completionStats.value.approvedCount || 0) * (7 - i) / 7);
|
||||
submitted.push(Math.floor(Math.random() * 100) + ((completionStats.value.submittedCount || 0) * (7 - i)) / 7);
|
||||
approved.push(Math.floor(Math.random() * 80) + ((completionStats.value.approvedCount || 0) * (7 - i)) / 7);
|
||||
}
|
||||
|
||||
const option = {
|
||||
@@ -447,14 +431,14 @@ const updateGradeChart = () => {
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: gradeStats.value.items.map(item => item.label)
|
||||
data: gradeStats.value.items.map((item) => item.label)
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value'
|
||||
},
|
||||
series: [
|
||||
{
|
||||
data: gradeStats.value.items.map(item => item.count),
|
||||
data: gradeStats.value.items.map((item) => item.count),
|
||||
type: 'bar',
|
||||
itemStyle: {
|
||||
color: '#5470c6'
|
||||
@@ -489,7 +473,7 @@ const updateMajorChart = () => {
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: majorStats.value.items.map(item => item.label),
|
||||
data: majorStats.value.items.map((item) => item.label),
|
||||
axisLabel: {
|
||||
rotate: 45
|
||||
}
|
||||
@@ -499,7 +483,7 @@ const updateMajorChart = () => {
|
||||
},
|
||||
series: [
|
||||
{
|
||||
data: majorStats.value.items.map(item => item.count),
|
||||
data: majorStats.value.items.map((item) => item.count),
|
||||
type: 'bar',
|
||||
itemStyle: {
|
||||
color: '#91cc75'
|
||||
|
||||
@@ -86,24 +86,19 @@
|
||||
<el-tag type="primary" size="small">{{ scope.row.batchType }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="状态" prop="batchStatus" width="120" align="center">
|
||||
<template #default="scope">
|
||||
<el-tag type="success" size="small">{{ getStatusText(scope.row.batchStatus) }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="学生数" prop="totalStudents" width="120" align="center">
|
||||
<template #default="scope">
|
||||
<span class="student-count">{{ scope.row.totalStudents }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="状态" prop="batchStatus" width="120" align="center">
|
||||
<template #default="scope">
|
||||
<el-tag type="success" size="small">{{ getStatusText(scope.row.batchStatus) }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="学生数" prop="totalStudents" width="120" align="center">
|
||||
<template #default="scope">
|
||||
<span class="student-count">{{ scope.row.totalStudents }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="完成率" prop="completionRate" width="150" align="center">
|
||||
<template #default="scope">
|
||||
<el-progress
|
||||
:percentage="scope.row.completionRate"
|
||||
:stroke-width="6"
|
||||
:show-text="false"
|
||||
color="#5d87ff"
|
||||
/>
|
||||
<el-progress :percentage="scope.row.completionRate" :stroke-width="6" :show-text="false" color="#5d87ff" />
|
||||
<div class="progress-text">{{ scope.row.completionRate }}%</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
@@ -140,31 +135,18 @@
|
||||
<div v-else-if="apiResult">
|
||||
<pre>{{ JSON.stringify(apiResult, null, 2) }}</pre>
|
||||
</div>
|
||||
<div v-else class="no-data">
|
||||
点击"测试 API"按钮查看结果
|
||||
</div>
|
||||
<div v-else class="no-data">点击"测试 API"按钮查看结果</div>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { listCollectionBatch } from '@/api/xg-xx/collectionBatch'
|
||||
import {
|
||||
Collection,
|
||||
Select,
|
||||
User,
|
||||
DocumentCopy,
|
||||
Plus,
|
||||
Download,
|
||||
View,
|
||||
Edit,
|
||||
Delete,
|
||||
Loading
|
||||
} from '@element-plus/icons-vue'
|
||||
import { ref } from 'vue';
|
||||
import { listCollectionBatch } from '@/api/xg-xx-bak/collectionBatch';
|
||||
import { Collection, Select, User, DocumentCopy, Plus, Download, View, Edit, Delete, Loading } from '@element-plus/icons-vue';
|
||||
|
||||
const loading = ref(false)
|
||||
const apiResult = ref(null)
|
||||
const loading = ref(false);
|
||||
const apiResult = ref(null);
|
||||
|
||||
// 状态处理方法
|
||||
const getStatusText = (status: string) => {
|
||||
@@ -174,9 +156,9 @@ const getStatusText = (status: string) => {
|
||||
'3': '进行中',
|
||||
'4': '已结束',
|
||||
'5': '已归档'
|
||||
}
|
||||
return statusMap[status] || status
|
||||
}
|
||||
};
|
||||
return statusMap[status] || status;
|
||||
};
|
||||
|
||||
const testData = ref([
|
||||
{
|
||||
@@ -200,25 +182,25 @@ const testData = ref([
|
||||
totalStudents: 0,
|
||||
completionRate: 0
|
||||
}
|
||||
])
|
||||
]);
|
||||
|
||||
const testApi = async () => {
|
||||
loading.value = true
|
||||
loading.value = true;
|
||||
try {
|
||||
console.log('开始测试 API...')
|
||||
console.log('开始测试 API...');
|
||||
const response = await listCollectionBatch({
|
||||
pageNum: 1,
|
||||
pageSize: 10
|
||||
})
|
||||
console.log('API 响应:', response)
|
||||
apiResult.value = response
|
||||
});
|
||||
console.log('API 响应:', response);
|
||||
apiResult.value = response;
|
||||
} catch (error) {
|
||||
console.error('API 测试失败:', error)
|
||||
apiResult.value = { error: (error as Error).message }
|
||||
console.error('API 测试失败:', error);
|
||||
apiResult.value = { error: (error as Error).message };
|
||||
} finally {
|
||||
loading.value = false
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -1,19 +1,11 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
title="缓存管理"
|
||||
v-model="dialogVisible"
|
||||
width="600px"
|
||||
:before-close="handleClose"
|
||||
append-to-body
|
||||
>
|
||||
<el-dialog title="缓存管理" v-model="dialogVisible" width="600px" :before-close="handleClose" append-to-body>
|
||||
<div class="cache-manage-container" v-loading="loading">
|
||||
<!-- 缓存统计信息 -->
|
||||
<div class="cache-stats">
|
||||
<div class="stats-header">
|
||||
<h3>缓存统计信息</h3>
|
||||
<el-button type="primary" icon="Refresh" @click="refreshCacheInfo" :loading="refreshing">
|
||||
刷新
|
||||
</el-button>
|
||||
<el-button type="primary" icon="Refresh" @click="refreshCacheInfo" :loading="refreshing"> 刷新 </el-button>
|
||||
</div>
|
||||
|
||||
<el-row :gutter="16" class="stats-cards">
|
||||
@@ -82,13 +74,7 @@
|
||||
<div class="operation-title">清空缓存</div>
|
||||
<div class="operation-desc">清空所有统计分析缓存数据,下次查询将重新计算</div>
|
||||
</div>
|
||||
<el-button
|
||||
type="danger"
|
||||
:loading="clearingCache"
|
||||
@click="handleClearCache"
|
||||
>
|
||||
清空缓存
|
||||
</el-button>
|
||||
<el-button type="danger" :loading="clearingCache" @click="handleClearCache"> 清空缓存 </el-button>
|
||||
</div>
|
||||
|
||||
<div class="operation-item">
|
||||
@@ -96,13 +82,7 @@
|
||||
<div class="operation-title">预热缓存</div>
|
||||
<div class="operation-desc">预先加载常用统计数据到缓存,提升查询性能</div>
|
||||
</div>
|
||||
<el-button
|
||||
type="success"
|
||||
:loading="warmingCache"
|
||||
@click="handleWarmupCache"
|
||||
>
|
||||
预热缓存
|
||||
</el-button>
|
||||
<el-button type="success" :loading="warmingCache" @click="handleWarmupCache"> 预热缓存 </el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -126,20 +106,11 @@
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="最大缓存数">
|
||||
<el-input-number
|
||||
v-model="cacheConfig.maxSize"
|
||||
:min="10"
|
||||
:max="1000"
|
||||
:step="10"
|
||||
@change="handleConfigChange"
|
||||
/>
|
||||
<el-input-number v-model="cacheConfig.maxSize" :min="10" :max="1000" :step="10" @change="handleConfigChange" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="自动清理">
|
||||
<el-switch
|
||||
v-model="cacheConfig.autoClean"
|
||||
@change="handleConfigChange"
|
||||
/>
|
||||
<el-switch v-model="cacheConfig.autoClean" @change="handleConfigChange" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
@@ -148,44 +119,37 @@
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="handleClose">关闭</el-button>
|
||||
<el-button type="primary" @click="handleSaveConfig" :loading="saving">
|
||||
保存配置
|
||||
</el-button>
|
||||
<el-button type="primary" @click="handleSaveConfig" :loading="saving"> 保存配置 </el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, computed, watch, onMounted } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import {
|
||||
getCacheStatistics,
|
||||
clearStatisticsCache,
|
||||
warmupStatisticsCache,
|
||||
type StatisticsCacheInfo
|
||||
} from '@/api/xg-xx/statistics'
|
||||
import { ref, reactive, computed, watch, onMounted } from 'vue';
|
||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||
import { getCacheStatistics, clearStatisticsCache, warmupStatisticsCache, type StatisticsCacheInfo } from '@/api/xg-xx-bak/statistics';
|
||||
|
||||
// Props
|
||||
interface Props {
|
||||
visible: boolean
|
||||
visible: boolean;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
visible: false
|
||||
})
|
||||
});
|
||||
|
||||
// Emits
|
||||
const emit = defineEmits<{
|
||||
'update:visible': [value: boolean]
|
||||
}>()
|
||||
'update:visible': [value: boolean];
|
||||
}>();
|
||||
|
||||
// 响应式数据
|
||||
const loading = ref(false)
|
||||
const refreshing = ref(false)
|
||||
const clearingCache = ref(false)
|
||||
const warmingCache = ref(false)
|
||||
const saving = ref(false)
|
||||
const loading = ref(false);
|
||||
const refreshing = ref(false);
|
||||
const clearingCache = ref(false);
|
||||
const warmingCache = ref(false);
|
||||
const saving = ref(false);
|
||||
|
||||
// 缓存信息
|
||||
const cacheInfo = reactive<StatisticsCacheInfo>({
|
||||
@@ -197,165 +161,160 @@ const cacheInfo = reactive<StatisticsCacheInfo>({
|
||||
lastAccessTime: '',
|
||||
createTime: '',
|
||||
expireTime: ''
|
||||
})
|
||||
});
|
||||
|
||||
// 缓存配置
|
||||
const cacheConfig = reactive({
|
||||
expireTime: 30, // 分钟
|
||||
maxSize: 100,
|
||||
autoClean: true
|
||||
})
|
||||
});
|
||||
|
||||
// 计算属性
|
||||
const dialogVisible = computed({
|
||||
get: () => props.visible,
|
||||
set: (value) => emit('update:visible', value)
|
||||
})
|
||||
});
|
||||
|
||||
// 监听器
|
||||
watch(() => props.visible, (newVal) => {
|
||||
if (newVal) {
|
||||
loadCacheInfo()
|
||||
watch(
|
||||
() => props.visible,
|
||||
(newVal) => {
|
||||
if (newVal) {
|
||||
loadCacheInfo();
|
||||
}
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
// 方法定义
|
||||
const loadCacheInfo = async () => {
|
||||
loading.value = true
|
||||
loading.value = true;
|
||||
try {
|
||||
const response = await getCacheStatistics()
|
||||
Object.assign(cacheInfo, response.data)
|
||||
const response = await getCacheStatistics();
|
||||
Object.assign(cacheInfo, response.data);
|
||||
} catch (error) {
|
||||
console.error('获取缓存信息失败:', error)
|
||||
ElMessage.error('获取缓存信息失败')
|
||||
console.error('获取缓存信息失败:', error);
|
||||
ElMessage.error('获取缓存信息失败');
|
||||
} finally {
|
||||
loading.value = false
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const refreshCacheInfo = async () => {
|
||||
refreshing.value = true
|
||||
refreshing.value = true;
|
||||
try {
|
||||
const response = await getCacheStatistics()
|
||||
Object.assign(cacheInfo, response.data)
|
||||
ElMessage.success('缓存信息已刷新')
|
||||
const response = await getCacheStatistics();
|
||||
Object.assign(cacheInfo, response.data);
|
||||
ElMessage.success('缓存信息已刷新');
|
||||
} catch (error) {
|
||||
console.error('刷新缓存信息失败:', error)
|
||||
ElMessage.error('刷新缓存信息失败')
|
||||
console.error('刷新缓存信息失败:', error);
|
||||
ElMessage.error('刷新缓存信息失败');
|
||||
} finally {
|
||||
refreshing.value = false
|
||||
refreshing.value = false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleClearCache = async () => {
|
||||
try {
|
||||
await ElMessageBox.confirm(
|
||||
'确定要清空所有统计分析缓存吗?此操作不可恢复。',
|
||||
'清空缓存',
|
||||
{
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}
|
||||
)
|
||||
await ElMessageBox.confirm('确定要清空所有统计分析缓存吗?此操作不可恢复。', '清空缓存', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
});
|
||||
|
||||
clearingCache.value = true
|
||||
await clearStatisticsCache()
|
||||
clearingCache.value = true;
|
||||
await clearStatisticsCache();
|
||||
|
||||
ElMessage.success('缓存已清空')
|
||||
await refreshCacheInfo()
|
||||
ElMessage.success('缓存已清空');
|
||||
await refreshCacheInfo();
|
||||
} catch (error) {
|
||||
if (error !== 'cancel') {
|
||||
console.error('清空缓存失败:', error)
|
||||
ElMessage.error('清空缓存失败')
|
||||
console.error('清空缓存失败:', error);
|
||||
ElMessage.error('清空缓存失败');
|
||||
}
|
||||
} finally {
|
||||
clearingCache.value = false
|
||||
clearingCache.value = false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleWarmupCache = async () => {
|
||||
try {
|
||||
await ElMessageBox.confirm(
|
||||
'确定要预热缓存吗?此操作可能需要一些时间。',
|
||||
'预热缓存',
|
||||
{
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'info'
|
||||
}
|
||||
)
|
||||
await ElMessageBox.confirm('确定要预热缓存吗?此操作可能需要一些时间。', '预热缓存', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'info'
|
||||
});
|
||||
|
||||
warmingCache.value = true
|
||||
await warmupStatisticsCache()
|
||||
warmingCache.value = true;
|
||||
await warmupStatisticsCache();
|
||||
|
||||
ElMessage.success('缓存预热完成')
|
||||
await refreshCacheInfo()
|
||||
ElMessage.success('缓存预热完成');
|
||||
await refreshCacheInfo();
|
||||
} catch (error) {
|
||||
if (error !== 'cancel') {
|
||||
console.error('预热缓存失败:', error)
|
||||
ElMessage.error('预热缓存失败')
|
||||
console.error('预热缓存失败:', error);
|
||||
ElMessage.error('预热缓存失败');
|
||||
}
|
||||
} finally {
|
||||
warmingCache.value = false
|
||||
warmingCache.value = false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleConfigChange = () => {
|
||||
// 配置变更时的处理逻辑
|
||||
console.log('缓存配置变更:', cacheConfig)
|
||||
}
|
||||
console.log('缓存配置变更:', cacheConfig);
|
||||
};
|
||||
|
||||
const handleSaveConfig = async () => {
|
||||
saving.value = true
|
||||
saving.value = true;
|
||||
try {
|
||||
// 这里应该调用保存配置的API
|
||||
// await saveCacheConfig(cacheConfig)
|
||||
|
||||
// 模拟保存
|
||||
await new Promise(resolve => setTimeout(resolve, 1000))
|
||||
await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||
|
||||
ElMessage.success('配置已保存')
|
||||
ElMessage.success('配置已保存');
|
||||
} catch (error) {
|
||||
console.error('保存配置失败:', error)
|
||||
ElMessage.error('保存配置失败')
|
||||
console.error('保存配置失败:', error);
|
||||
ElMessage.error('保存配置失败');
|
||||
} finally {
|
||||
saving.value = false
|
||||
saving.value = false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
dialogVisible.value = false
|
||||
}
|
||||
dialogVisible.value = false;
|
||||
};
|
||||
|
||||
// 工具方法
|
||||
const formatBytes = (bytes: number): string => {
|
||||
if (bytes === 0) return '0 B'
|
||||
if (bytes === 0) return '0 B';
|
||||
|
||||
const k = 1024
|
||||
const sizes = ['B', 'KB', 'MB', 'GB']
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k))
|
||||
const k = 1024;
|
||||
const sizes = ['B', 'KB', 'MB', 'GB'];
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||
|
||||
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]
|
||||
}
|
||||
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
||||
};
|
||||
|
||||
const formatTime = (timeStr: string): string => {
|
||||
if (!timeStr) return '-'
|
||||
if (!timeStr) return '-';
|
||||
|
||||
try {
|
||||
const date = new Date(timeStr)
|
||||
return date.toLocaleString('zh-CN')
|
||||
const date = new Date(timeStr);
|
||||
return date.toLocaleString('zh-CN');
|
||||
} catch (error) {
|
||||
return timeStr
|
||||
return timeStr;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 生命周期
|
||||
onMounted(() => {
|
||||
if (props.visible) {
|
||||
loadCacheInfo()
|
||||
loadCacheInfo();
|
||||
}
|
||||
})
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -1,11 +1,5 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
title="配置详情"
|
||||
v-model="dialogVisible"
|
||||
width="600px"
|
||||
:before-close="handleClose"
|
||||
append-to-body
|
||||
>
|
||||
<el-dialog title="配置详情" v-model="dialogVisible" width="600px" :before-close="handleClose" append-to-body>
|
||||
<div class="config-detail-container" v-loading="loading">
|
||||
<el-descriptions :column="2" border>
|
||||
<el-descriptions-item label="配置名称">
|
||||
@@ -73,33 +67,33 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import type { StatisticsConfigVo } from '@/api/xg-xx/statistics'
|
||||
import { computed } from 'vue';
|
||||
import type { StatisticsConfigVo } from '@/api/xg-xx-bak/statistics';
|
||||
|
||||
// Props
|
||||
interface Props {
|
||||
visible: boolean
|
||||
configData?: StatisticsConfigVo | null
|
||||
visible: boolean;
|
||||
configData?: StatisticsConfigVo | null;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
visible: false,
|
||||
configData: null
|
||||
})
|
||||
});
|
||||
|
||||
// Emits
|
||||
const emit = defineEmits<{
|
||||
'update:visible': [value: boolean]
|
||||
}>()
|
||||
'update:visible': [value: boolean];
|
||||
}>();
|
||||
|
||||
// 响应式数据
|
||||
const loading = false
|
||||
const loading = false;
|
||||
|
||||
// 计算属性
|
||||
const dialogVisible = computed({
|
||||
get: () => props.visible,
|
||||
set: (value) => emit('update:visible', value)
|
||||
})
|
||||
});
|
||||
|
||||
// 方法定义
|
||||
const getDimensionTypeName = (type?: string): string => {
|
||||
@@ -117,36 +111,36 @@ const getDimensionTypeName = (type?: string): string => {
|
||||
religion: '宗教信仰统计',
|
||||
studentStatus: '学籍状态统计',
|
||||
trainingType: '培养方式统计'
|
||||
}
|
||||
};
|
||||
|
||||
return type ? dimensionMap[type] || type : '-'
|
||||
}
|
||||
return type ? dimensionMap[type] || type : '-';
|
||||
};
|
||||
|
||||
const formatTime = (timeStr?: string): string => {
|
||||
if (!timeStr) return '-'
|
||||
if (!timeStr) return '-';
|
||||
|
||||
try {
|
||||
const date = new Date(timeStr)
|
||||
return date.toLocaleString('zh-CN')
|
||||
const date = new Date(timeStr);
|
||||
return date.toLocaleString('zh-CN');
|
||||
} catch (error) {
|
||||
return timeStr
|
||||
return timeStr;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const formatJson = (jsonStr?: string): string => {
|
||||
if (!jsonStr) return ''
|
||||
if (!jsonStr) return '';
|
||||
|
||||
try {
|
||||
const parsed = JSON.parse(jsonStr)
|
||||
return JSON.stringify(parsed, null, 2)
|
||||
const parsed = JSON.parse(jsonStr);
|
||||
return JSON.stringify(parsed, null, 2);
|
||||
} catch (error) {
|
||||
return jsonStr
|
||||
return jsonStr;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
dialogVisible.value = false
|
||||
}
|
||||
dialogVisible.value = false;
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -1,24 +1,8 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
:title="isEdit ? '编辑配置' : '新增配置'"
|
||||
v-model="dialogVisible"
|
||||
width="600px"
|
||||
:before-close="handleClose"
|
||||
append-to-body
|
||||
>
|
||||
<el-form
|
||||
ref="configForm"
|
||||
:model="form"
|
||||
:rules="rules"
|
||||
label-width="100px"
|
||||
v-loading="loading"
|
||||
>
|
||||
<el-dialog :title="isEdit ? '编辑配置' : '新增配置'" v-model="dialogVisible" width="600px" :before-close="handleClose" append-to-body>
|
||||
<el-form ref="configForm" :model="form" :rules="rules" label-width="100px" v-loading="loading">
|
||||
<el-form-item label="配置名称" prop="configName">
|
||||
<el-input
|
||||
v-model="form.configName"
|
||||
placeholder="请输入配置名称"
|
||||
clearable
|
||||
/>
|
||||
<el-input v-model="form.configName" placeholder="请输入配置名称" clearable />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="配置类型" prop="configType">
|
||||
@@ -57,47 +41,23 @@
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="查询条件" prop="queryConditions">
|
||||
<el-input
|
||||
v-model="form.queryConditions"
|
||||
type="textarea"
|
||||
:rows="3"
|
||||
placeholder="请输入查询条件(JSON格式)"
|
||||
/>
|
||||
<el-input v-model="form.queryConditions" type="textarea" :rows="3" placeholder="请输入查询条件(JSON格式)" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="显示设置" prop="displaySettings">
|
||||
<el-input
|
||||
v-model="form.displaySettings"
|
||||
type="textarea"
|
||||
:rows="3"
|
||||
placeholder="请输入显示设置(JSON格式)"
|
||||
/>
|
||||
<el-input v-model="form.displaySettings" type="textarea" :rows="3" placeholder="请输入显示设置(JSON格式)" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="排序序号" prop="sortOrder">
|
||||
<el-input-number
|
||||
v-model="form.sortOrder"
|
||||
:min="0"
|
||||
:max="999"
|
||||
placeholder="排序序号"
|
||||
/>
|
||||
<el-input-number v-model="form.sortOrder" :min="0" :max="999" placeholder="排序序号" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="状态" prop="isEnabled">
|
||||
<el-switch
|
||||
v-model="form.isEnabled"
|
||||
active-text="启用"
|
||||
inactive-text="禁用"
|
||||
/>
|
||||
<el-switch v-model="form.isEnabled" active-text="启用" inactive-text="禁用" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="备注" prop="remark">
|
||||
<el-input
|
||||
v-model="form.remark"
|
||||
type="textarea"
|
||||
:rows="2"
|
||||
placeholder="请输入备注"
|
||||
/>
|
||||
<el-input v-model="form.remark" type="textarea" :rows="2" placeholder="请输入备注" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
@@ -113,38 +73,38 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, computed, watch } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { ref, reactive, computed, watch } from 'vue';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import {
|
||||
addStatisticsConfig,
|
||||
updateStatisticsConfig,
|
||||
getStatisticsConfig,
|
||||
type StatisticsConfigVo,
|
||||
type StatisticsConfigBo
|
||||
} from '@/api/xg-xx/statistics'
|
||||
} from '@/api/xg-xx-bak/statistics';
|
||||
|
||||
// Props
|
||||
interface Props {
|
||||
visible: boolean
|
||||
configData?: StatisticsConfigVo | null
|
||||
isEdit?: boolean
|
||||
visible: boolean;
|
||||
configData?: StatisticsConfigVo | null;
|
||||
isEdit?: boolean;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
visible: false,
|
||||
configData: null,
|
||||
isEdit: false
|
||||
})
|
||||
});
|
||||
|
||||
// Emits
|
||||
const emit = defineEmits<{
|
||||
'update:visible': [value: boolean]
|
||||
'refresh': []
|
||||
}>()
|
||||
'update:visible': [value: boolean];
|
||||
'refresh': [];
|
||||
}>();
|
||||
|
||||
// 响应式数据
|
||||
const loading = ref(false)
|
||||
const configForm = ref()
|
||||
const loading = ref(false);
|
||||
const configForm = ref();
|
||||
|
||||
// 表单数据
|
||||
const form = reactive<StatisticsConfigBo>({
|
||||
@@ -158,7 +118,7 @@ const form = reactive<StatisticsConfigBo>({
|
||||
isEnabled: true,
|
||||
sortOrder: 0,
|
||||
remark: ''
|
||||
})
|
||||
});
|
||||
|
||||
// 表单验证规则
|
||||
const rules = {
|
||||
@@ -166,54 +126,45 @@ const rules = {
|
||||
{ required: true, message: '请输入配置名称', trigger: 'blur' },
|
||||
{ min: 2, max: 50, message: '配置名称长度在 2 到 50 个字符', trigger: 'blur' }
|
||||
],
|
||||
configType: [
|
||||
{ required: true, message: '请选择配置类型', trigger: 'change' }
|
||||
],
|
||||
dimensionType: [
|
||||
{ required: true, message: '请选择维度类型', trigger: 'change' }
|
||||
],
|
||||
chartType: [
|
||||
{ required: true, message: '请选择图表类型', trigger: 'change' }
|
||||
],
|
||||
queryConditions: [
|
||||
{ validator: validateJson, trigger: 'blur' }
|
||||
],
|
||||
displaySettings: [
|
||||
{ validator: validateJson, trigger: 'blur' }
|
||||
],
|
||||
sortOrder: [
|
||||
{ type: 'number' as const, message: '排序序号必须是数字', trigger: 'blur' }
|
||||
]
|
||||
}
|
||||
configType: [{ required: true, message: '请选择配置类型', trigger: 'change' }],
|
||||
dimensionType: [{ required: true, message: '请选择维度类型', trigger: 'change' }],
|
||||
chartType: [{ required: true, message: '请选择图表类型', trigger: 'change' }],
|
||||
queryConditions: [{ validator: validateJson, trigger: 'blur' }],
|
||||
displaySettings: [{ validator: validateJson, trigger: 'blur' }],
|
||||
sortOrder: [{ type: 'number' as const, message: '排序序号必须是数字', trigger: 'blur' }]
|
||||
};
|
||||
|
||||
// 计算属性
|
||||
const dialogVisible = computed({
|
||||
get: () => props.visible,
|
||||
set: (value) => emit('update:visible', value)
|
||||
})
|
||||
});
|
||||
|
||||
// 监听器
|
||||
watch(() => props.visible, (newVal) => {
|
||||
if (newVal) {
|
||||
resetForm()
|
||||
if (props.isEdit && props.configData) {
|
||||
loadConfigData()
|
||||
watch(
|
||||
() => props.visible,
|
||||
(newVal) => {
|
||||
if (newVal) {
|
||||
resetForm();
|
||||
if (props.isEdit && props.configData) {
|
||||
loadConfigData();
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
// 方法定义
|
||||
function validateJson(rule: any, value: string, callback: any) {
|
||||
if (!value) {
|
||||
callback()
|
||||
return
|
||||
callback();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
JSON.parse(value)
|
||||
callback()
|
||||
JSON.parse(value);
|
||||
callback();
|
||||
} catch (error) {
|
||||
callback(new Error('请输入有效的JSON格式'))
|
||||
callback(new Error('请输入有效的JSON格式'));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -229,18 +180,18 @@ const resetForm = () => {
|
||||
isEnabled: true,
|
||||
sortOrder: 0,
|
||||
remark: ''
|
||||
})
|
||||
});
|
||||
|
||||
configForm.value?.clearValidate()
|
||||
}
|
||||
configForm.value?.clearValidate();
|
||||
};
|
||||
|
||||
const loadConfigData = async () => {
|
||||
if (!props.configData?.id) return
|
||||
if (!props.configData?.id) return;
|
||||
|
||||
loading.value = true
|
||||
loading.value = true;
|
||||
try {
|
||||
const response = await getStatisticsConfig(props.configData.id)
|
||||
const data = response.data
|
||||
const response = await getStatisticsConfig(props.configData.id);
|
||||
const data = response.data;
|
||||
|
||||
Object.assign(form, {
|
||||
id: data.id,
|
||||
@@ -253,54 +204,54 @@ const loadConfigData = async () => {
|
||||
isEnabled: data.isEnabled,
|
||||
sortOrder: data.sortOrder,
|
||||
remark: data.remark
|
||||
})
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('加载配置数据失败:', error)
|
||||
ElMessage.error('加载配置数据失败')
|
||||
console.error('加载配置数据失败:', error);
|
||||
ElMessage.error('加载配置数据失败');
|
||||
} finally {
|
||||
loading.value = false
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleConfigTypeChange = (value: string) => {
|
||||
if (value !== 'dimension') {
|
||||
form.dimensionType = ''
|
||||
form.dimensionType = '';
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleSubmit = async () => {
|
||||
if (!configForm.value) return
|
||||
if (!configForm.value) return;
|
||||
|
||||
try {
|
||||
await configForm.value.validate()
|
||||
await configForm.value.validate();
|
||||
} catch (error) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
loading.value = true
|
||||
loading.value = true;
|
||||
|
||||
try {
|
||||
if (props.isEdit) {
|
||||
await updateStatisticsConfig(form)
|
||||
ElMessage.success('配置更新成功')
|
||||
await updateStatisticsConfig(form);
|
||||
ElMessage.success('配置更新成功');
|
||||
} else {
|
||||
await addStatisticsConfig(form)
|
||||
ElMessage.success('配置创建成功')
|
||||
await addStatisticsConfig(form);
|
||||
ElMessage.success('配置创建成功');
|
||||
}
|
||||
|
||||
emit('refresh')
|
||||
handleClose()
|
||||
emit('refresh');
|
||||
handleClose();
|
||||
} catch (error) {
|
||||
console.error('保存配置失败:', error)
|
||||
ElMessage.error('保存配置失败')
|
||||
console.error('保存配置失败:', error);
|
||||
ElMessage.error('保存配置失败');
|
||||
} finally {
|
||||
loading.value = false
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
dialogVisible.value = false
|
||||
}
|
||||
dialogVisible.value = false;
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -1,23 +1,11 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
title="统计配置管理"
|
||||
v-model="dialogVisible"
|
||||
width="80%"
|
||||
:before-close="handleClose"
|
||||
append-to-body
|
||||
class="config-manage-dialog"
|
||||
>
|
||||
<el-dialog title="统计配置管理" v-model="dialogVisible" width="80%" :before-close="handleClose" append-to-body class="config-manage-dialog">
|
||||
<div class="config-manage-container">
|
||||
<!-- 搜索区域 -->
|
||||
<div class="search-section">
|
||||
<el-form :model="queryParams" ref="queryRef" :inline="true" label-width="80px">
|
||||
<el-form-item label="配置名称" prop="configName">
|
||||
<el-input
|
||||
v-model="queryParams.configName"
|
||||
placeholder="请输入配置名称"
|
||||
clearable
|
||||
@keyup.enter="handleQuery"
|
||||
/>
|
||||
<el-input v-model="queryParams.configName" placeholder="请输入配置名称" clearable @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
<el-form-item label="配置类型" prop="configType">
|
||||
<el-select v-model="queryParams.configType" placeholder="请选择配置类型" clearable>
|
||||
@@ -53,12 +41,7 @@
|
||||
|
||||
<!-- 配置列表 -->
|
||||
<div class="table-section">
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
:data="configList"
|
||||
@selection-change="handleSelectionChange"
|
||||
class="config-table"
|
||||
>
|
||||
<el-table v-loading="loading" :data="configList" @selection-change="handleSelectionChange" class="config-table">
|
||||
<el-table-column type="selection" width="55" align="center" />
|
||||
<el-table-column label="配置名称" prop="configName" width="150" />
|
||||
<el-table-column label="配置类型" prop="configType" width="100">
|
||||
@@ -81,10 +64,7 @@
|
||||
</el-table-column>
|
||||
<el-table-column label="状态" prop="isEnabled" width="80" align="center">
|
||||
<template #default="scope">
|
||||
<el-switch
|
||||
v-model="scope.row.isEnabled"
|
||||
@change="handleStatusChange(scope.row)"
|
||||
/>
|
||||
<el-switch v-model="scope.row.isEnabled" @change="handleStatusChange(scope.row)" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="排序" prop="sortOrder" width="80" align="center" />
|
||||
@@ -93,27 +73,9 @@
|
||||
<el-table-column label="操作" width="120" align="center" fixed="right">
|
||||
<template #default="scope">
|
||||
<div class="action-buttons">
|
||||
<el-button
|
||||
type="primary"
|
||||
icon="View"
|
||||
size="small"
|
||||
@click="handleView(scope.row)"
|
||||
title="查看"
|
||||
/>
|
||||
<el-button
|
||||
type="success"
|
||||
icon="Edit"
|
||||
size="small"
|
||||
@click="handleUpdate(scope.row)"
|
||||
title="编辑"
|
||||
/>
|
||||
<el-button
|
||||
type="danger"
|
||||
icon="Delete"
|
||||
size="small"
|
||||
@click="handleDelete(scope.row)"
|
||||
title="删除"
|
||||
/>
|
||||
<el-button type="primary" icon="View" size="small" @click="handleView(scope.row)" title="查看" />
|
||||
<el-button type="success" icon="Edit" size="small" @click="handleUpdate(scope.row)" title="编辑" />
|
||||
<el-button type="danger" icon="Delete" size="small" @click="handleDelete(scope.row)" title="删除" />
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
@@ -140,16 +102,12 @@
|
||||
:visible="editVisible"
|
||||
:config-data="selectedConfig"
|
||||
:is-edit="isEdit"
|
||||
@update:visible="(val) => editVisible = val"
|
||||
@update:visible="(val) => (editVisible = val)"
|
||||
@refresh="getList"
|
||||
/>
|
||||
|
||||
<!-- 配置详情对话框 -->
|
||||
<ConfigDetailDialog
|
||||
:visible="detailVisible"
|
||||
:config-data="selectedConfig"
|
||||
@update:visible="(val) => detailVisible = val"
|
||||
/>
|
||||
<ConfigDetailDialog :visible="detailVisible" :config-data="selectedConfig" @update:visible="(val) => (detailVisible = val)" />
|
||||
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
@@ -160,8 +118,8 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, computed, onMounted } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { ref, reactive, computed, onMounted } from 'vue';
|
||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||
import {
|
||||
listStatisticsConfig,
|
||||
delStatisticsConfig,
|
||||
@@ -169,28 +127,28 @@ import {
|
||||
exportStatisticsConfig,
|
||||
type StatisticsQuery,
|
||||
type StatisticsConfigVo
|
||||
} from '@/api/xg-xx/statistics'
|
||||
import ConfigEditDialog from './ConfigEditDialog.vue'
|
||||
import ConfigDetailDialog from './ConfigDetailDialog.vue'
|
||||
} from '@/api/xg-xx-bak/statistics';
|
||||
import ConfigEditDialog from './ConfigEditDialog.vue';
|
||||
import ConfigDetailDialog from './ConfigDetailDialog.vue';
|
||||
|
||||
// Props
|
||||
interface Props {
|
||||
visible: boolean
|
||||
visible: boolean;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
visible: false
|
||||
})
|
||||
});
|
||||
|
||||
// Emits
|
||||
const emit = defineEmits<{
|
||||
'update:visible': [value: boolean]
|
||||
'refresh': []
|
||||
}>()
|
||||
'update:visible': [value: boolean];
|
||||
'refresh': [];
|
||||
}>();
|
||||
|
||||
// 响应式数据
|
||||
const loading = ref(false)
|
||||
const queryRef = ref()
|
||||
const loading = ref(false);
|
||||
const queryRef = ref();
|
||||
|
||||
// 查询参数
|
||||
const queryParams = reactive<StatisticsQuery>({
|
||||
@@ -199,166 +157,162 @@ const queryParams = reactive<StatisticsQuery>({
|
||||
configName: '',
|
||||
configType: '',
|
||||
isEnabled: undefined
|
||||
})
|
||||
});
|
||||
|
||||
// 列表数据
|
||||
const configList = ref<StatisticsConfigVo[]>([])
|
||||
const total = ref(0)
|
||||
const configList = ref<StatisticsConfigVo[]>([]);
|
||||
const total = ref(0);
|
||||
|
||||
// 选择数据
|
||||
const selectedIds = ref<number[]>([])
|
||||
const selectedRows = ref<StatisticsConfigVo[]>([])
|
||||
const single = ref(true)
|
||||
const multiple = ref(true)
|
||||
const selectedIds = ref<number[]>([]);
|
||||
const selectedRows = ref<StatisticsConfigVo[]>([]);
|
||||
const single = ref(true);
|
||||
const multiple = ref(true);
|
||||
|
||||
// 对话框状态
|
||||
const editVisible = ref(false)
|
||||
const detailVisible = ref(false)
|
||||
const selectedConfig = ref<StatisticsConfigVo | null>(null)
|
||||
const isEdit = ref(false)
|
||||
const editVisible = ref(false);
|
||||
const detailVisible = ref(false);
|
||||
const selectedConfig = ref<StatisticsConfigVo | null>(null);
|
||||
const isEdit = ref(false);
|
||||
|
||||
// 计算属性
|
||||
const dialogVisible = computed({
|
||||
get: () => props.visible,
|
||||
set: (value) => emit('update:visible', value)
|
||||
})
|
||||
});
|
||||
|
||||
// 方法定义
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
loading.value = true;
|
||||
try {
|
||||
const response = await listStatisticsConfig(queryParams)
|
||||
configList.value = response.rows
|
||||
total.value = response.total
|
||||
const response = await listStatisticsConfig(queryParams);
|
||||
configList.value = response.rows;
|
||||
total.value = response.total;
|
||||
} catch (error) {
|
||||
console.error('获取配置列表失败:', error)
|
||||
ElMessage.error('获取配置列表失败')
|
||||
console.error('获取配置列表失败:', error);
|
||||
ElMessage.error('获取配置列表失败');
|
||||
} finally {
|
||||
loading.value = false
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleQuery = () => {
|
||||
queryParams.pageNum = 1
|
||||
getList()
|
||||
}
|
||||
queryParams.pageNum = 1;
|
||||
getList();
|
||||
};
|
||||
|
||||
const resetQuery = () => {
|
||||
queryRef.value?.resetFields()
|
||||
queryParams.pageNum = 1
|
||||
queryParams.pageSize = 10
|
||||
queryParams.configName = ''
|
||||
queryParams.configType = ''
|
||||
queryParams.isEnabled = undefined
|
||||
getList()
|
||||
}
|
||||
queryRef.value?.resetFields();
|
||||
queryParams.pageNum = 1;
|
||||
queryParams.pageSize = 10;
|
||||
queryParams.configName = '';
|
||||
queryParams.configType = '';
|
||||
queryParams.isEnabled = undefined;
|
||||
getList();
|
||||
};
|
||||
|
||||
const handleSelectionChange = (selection: StatisticsConfigVo[]) => {
|
||||
selectedIds.value = selection.map(item => item.id!).filter(id => id !== undefined)
|
||||
selectedRows.value = selection
|
||||
single.value = selection.length !== 1
|
||||
multiple.value = !selection.length
|
||||
}
|
||||
selectedIds.value = selection.map((item) => item.id!).filter((id) => id !== undefined);
|
||||
selectedRows.value = selection;
|
||||
single.value = selection.length !== 1;
|
||||
multiple.value = !selection.length;
|
||||
};
|
||||
|
||||
const handleAdd = () => {
|
||||
selectedConfig.value = null
|
||||
isEdit.value = false
|
||||
editVisible.value = true
|
||||
}
|
||||
selectedConfig.value = null;
|
||||
isEdit.value = false;
|
||||
editVisible.value = true;
|
||||
};
|
||||
|
||||
const handleUpdate = (row?: StatisticsConfigVo) => {
|
||||
const config = row || selectedRows.value[0]
|
||||
selectedConfig.value = config
|
||||
isEdit.value = true
|
||||
editVisible.value = true
|
||||
}
|
||||
const config = row || selectedRows.value[0];
|
||||
selectedConfig.value = config;
|
||||
isEdit.value = true;
|
||||
editVisible.value = true;
|
||||
};
|
||||
|
||||
const handleView = (row: StatisticsConfigVo) => {
|
||||
selectedConfig.value = row
|
||||
detailVisible.value = true
|
||||
}
|
||||
selectedConfig.value = row;
|
||||
detailVisible.value = true;
|
||||
};
|
||||
|
||||
const handleDelete = async (row?: StatisticsConfigVo) => {
|
||||
const ids = row ? [row.id!] : selectedIds.value
|
||||
const names = row ? [row.configName!] : selectedRows.value.map(item => item.configName!)
|
||||
const ids = row ? [row.id!] : selectedIds.value;
|
||||
const names = row ? [row.configName!] : selectedRows.value.map((item) => item.configName!);
|
||||
|
||||
try {
|
||||
await ElMessageBox.confirm(
|
||||
`确定要删除配置"${names.join('、')}"吗?`,
|
||||
'删除确认',
|
||||
{
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}
|
||||
)
|
||||
await ElMessageBox.confirm(`确定要删除配置"${names.join('、')}"吗?`, '删除确认', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
});
|
||||
|
||||
await delStatisticsConfig(ids)
|
||||
ElMessage.success('删除成功')
|
||||
getList()
|
||||
await delStatisticsConfig(ids);
|
||||
ElMessage.success('删除成功');
|
||||
getList();
|
||||
} catch (error) {
|
||||
if (error !== 'cancel') {
|
||||
console.error('删除失败:', error)
|
||||
ElMessage.error('删除失败')
|
||||
console.error('删除失败:', error);
|
||||
ElMessage.error('删除失败');
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleStatusChange = async (row: StatisticsConfigVo) => {
|
||||
try {
|
||||
await changeStatisticsConfigStatus(row.id!, row.isEnabled!)
|
||||
ElMessage.success(`${row.isEnabled ? '启用' : '禁用'}成功`)
|
||||
await changeStatisticsConfigStatus(row.id!, row.isEnabled!);
|
||||
ElMessage.success(`${row.isEnabled ? '启用' : '禁用'}成功`);
|
||||
} catch (error) {
|
||||
console.error('状态变更失败:', error)
|
||||
ElMessage.error('状态变更失败')
|
||||
console.error('状态变更失败:', error);
|
||||
ElMessage.error('状态变更失败');
|
||||
// 恢复原状态
|
||||
row.isEnabled = !row.isEnabled
|
||||
row.isEnabled = !row.isEnabled;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleExport = async () => {
|
||||
try {
|
||||
const response = await exportStatisticsConfig(queryParams)
|
||||
const response = await exportStatisticsConfig(queryParams);
|
||||
// 处理文件下载
|
||||
const blob = new Blob([response.data], {
|
||||
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
|
||||
})
|
||||
const url = window.URL.createObjectURL(blob)
|
||||
const link = document.createElement('a')
|
||||
link.href = url
|
||||
link.download = `统计配置_${new Date().toISOString().slice(0, 10)}.xlsx`
|
||||
document.body.appendChild(link)
|
||||
link.click()
|
||||
document.body.removeChild(link)
|
||||
window.URL.revokeObjectURL(url)
|
||||
});
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
const link = document.createElement('a');
|
||||
link.href = url;
|
||||
link.download = `统计配置_${new Date().toISOString().slice(0, 10)}.xlsx`;
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
window.URL.revokeObjectURL(url);
|
||||
|
||||
ElMessage.success('导出成功')
|
||||
ElMessage.success('导出成功');
|
||||
} catch (error) {
|
||||
console.error('导出失败:', error)
|
||||
ElMessage.error('导出失败')
|
||||
console.error('导出失败:', error);
|
||||
ElMessage.error('导出失败');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleSizeChange = (size: number) => {
|
||||
queryParams.pageSize = size
|
||||
getList()
|
||||
}
|
||||
queryParams.pageSize = size;
|
||||
getList();
|
||||
};
|
||||
|
||||
const handleCurrentChange = (page: number) => {
|
||||
queryParams.pageNum = page
|
||||
getList()
|
||||
}
|
||||
queryParams.pageNum = page;
|
||||
getList();
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
dialogVisible.value = false
|
||||
}
|
||||
dialogVisible.value = false;
|
||||
};
|
||||
|
||||
// 生命周期
|
||||
onMounted(() => {
|
||||
if (props.visible) {
|
||||
getList()
|
||||
getList();
|
||||
}
|
||||
})
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -1,18 +1,6 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
title="导出统计数据"
|
||||
v-model="dialogVisible"
|
||||
width="500px"
|
||||
:before-close="handleClose"
|
||||
append-to-body
|
||||
>
|
||||
<el-form
|
||||
ref="exportForm"
|
||||
:model="form"
|
||||
:rules="rules"
|
||||
label-width="100px"
|
||||
v-loading="loading"
|
||||
>
|
||||
<el-dialog title="导出统计数据" v-model="dialogVisible" width="500px" :before-close="handleClose" append-to-body>
|
||||
<el-form ref="exportForm" :model="form" :rules="rules" label-width="100px" v-loading="loading">
|
||||
<el-form-item label="导出格式" prop="format">
|
||||
<el-radio-group v-model="form.format">
|
||||
<el-radio label="excel">Excel格式</el-radio>
|
||||
@@ -21,17 +9,11 @@
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="包含内容" prop="includeChart">
|
||||
<el-checkbox v-model="form.includeChart" :disabled="form.format === 'csv'">
|
||||
包含图表
|
||||
</el-checkbox>
|
||||
<el-checkbox v-model="form.includeChart" :disabled="form.format === 'csv'"> 包含图表 </el-checkbox>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="文件名称" prop="fileName">
|
||||
<el-input
|
||||
v-model="form.fileName"
|
||||
placeholder="请输入文件名称"
|
||||
:suffix="form.format === 'excel' ? '.xlsx' : '.csv'"
|
||||
/>
|
||||
<el-input v-model="form.fileName" placeholder="请输入文件名称" :suffix="form.format === 'excel' ? '.xlsx' : '.csv'" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="数据预览">
|
||||
@@ -40,18 +22,12 @@
|
||||
<span>共 {{ statisticsData.length }} 条数据</span>
|
||||
</div>
|
||||
<div class="preview-content">
|
||||
<div
|
||||
v-for="(item, index) in statisticsData.slice(0, 5)"
|
||||
:key="index"
|
||||
class="preview-item"
|
||||
>
|
||||
<div v-for="(item, index) in statisticsData.slice(0, 5)" :key="index" class="preview-item">
|
||||
<span class="item-label">{{ item.label }}</span>
|
||||
<span class="item-value">{{ item.value }}</span>
|
||||
<span class="item-percentage">{{ item.percentage }}%</span>
|
||||
</div>
|
||||
<div v-if="statisticsData.length > 5" class="preview-more">
|
||||
...还有 {{ statisticsData.length - 5 }} 条数据
|
||||
</div>
|
||||
<div v-if="statisticsData.length > 5" class="preview-more">...还有 {{ statisticsData.length - 5 }} 条数据</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-form-item>
|
||||
@@ -60,91 +36,93 @@
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="handleClose">取消</el-button>
|
||||
<el-button type="primary" @click="handleExport" :loading="loading">
|
||||
导出
|
||||
</el-button>
|
||||
<el-button type="primary" @click="handleExport" :loading="loading"> 导出 </el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, computed, watch } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { exportStatisticsResult, type StatisticsExportQuery } from '@/api/xg-xx/statistics'
|
||||
import { ref, reactive, computed, watch } from 'vue';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { exportStatisticsResult, type StatisticsExportQuery } from '@/api/xg-xx-bak/statistics';
|
||||
|
||||
// Props
|
||||
interface Props {
|
||||
visible: boolean
|
||||
queryParams: any
|
||||
visible: boolean;
|
||||
queryParams: any;
|
||||
statisticsData: Array<{
|
||||
label: string
|
||||
value: number
|
||||
percentage: number
|
||||
}>
|
||||
label: string;
|
||||
value: number;
|
||||
percentage: number;
|
||||
}>;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
visible: false,
|
||||
queryParams: () => ({}),
|
||||
statisticsData: () => []
|
||||
})
|
||||
});
|
||||
|
||||
// Emits
|
||||
const emit = defineEmits<{
|
||||
'update:visible': [value: boolean]
|
||||
}>()
|
||||
'update:visible': [value: boolean];
|
||||
}>();
|
||||
|
||||
// 响应式数据
|
||||
const loading = ref(false)
|
||||
const exportForm = ref()
|
||||
const loading = ref(false);
|
||||
const exportForm = ref();
|
||||
|
||||
// 表单数据
|
||||
const form = reactive({
|
||||
format: 'excel' as 'excel' | 'csv',
|
||||
includeChart: true,
|
||||
fileName: ''
|
||||
})
|
||||
});
|
||||
|
||||
// 表单验证规则
|
||||
const rules = {
|
||||
format: [
|
||||
{ required: true, message: '请选择导出格式', trigger: 'change' }
|
||||
],
|
||||
format: [{ required: true, message: '请选择导出格式', trigger: 'change' }],
|
||||
fileName: [
|
||||
{ required: true, message: '请输入文件名称', trigger: 'blur' },
|
||||
{ min: 1, max: 50, message: '文件名称长度在 1 到 50 个字符', trigger: 'blur' }
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
// 计算属性
|
||||
const dialogVisible = computed({
|
||||
get: () => props.visible,
|
||||
set: (value) => emit('update:visible', value)
|
||||
})
|
||||
});
|
||||
|
||||
// 监听器
|
||||
watch(() => props.visible, (newVal) => {
|
||||
if (newVal) {
|
||||
resetForm()
|
||||
generateFileName()
|
||||
watch(
|
||||
() => props.visible,
|
||||
(newVal) => {
|
||||
if (newVal) {
|
||||
resetForm();
|
||||
generateFileName();
|
||||
}
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
watch(() => form.format, (newVal) => {
|
||||
if (newVal === 'csv') {
|
||||
form.includeChart = false
|
||||
watch(
|
||||
() => form.format,
|
||||
(newVal) => {
|
||||
if (newVal === 'csv') {
|
||||
form.includeChart = false;
|
||||
}
|
||||
generateFileName();
|
||||
}
|
||||
generateFileName()
|
||||
})
|
||||
);
|
||||
|
||||
// 方法定义
|
||||
const generateFileName = () => {
|
||||
const now = new Date()
|
||||
const dateStr = now.toISOString().slice(0, 10).replace(/-/g, '')
|
||||
const timeStr = now.toTimeString().slice(0, 8).replace(/:/g, '')
|
||||
const now = new Date();
|
||||
const dateStr = now.toISOString().slice(0, 10).replace(/-/g, '');
|
||||
const timeStr = now.toTimeString().slice(0, 8).replace(/:/g, '');
|
||||
|
||||
let prefix = '统计数据'
|
||||
let prefix = '统计数据';
|
||||
if (props.queryParams.dimension) {
|
||||
const dimensionMap: Record<string, string> = {
|
||||
college: '学院统计',
|
||||
@@ -160,70 +138,68 @@ const generateFileName = () => {
|
||||
religion: '宗教信仰统计',
|
||||
studentStatus: '学籍状态统计',
|
||||
trainingType: '培养方式统计'
|
||||
}
|
||||
prefix = dimensionMap[props.queryParams.dimension] || '统计数据'
|
||||
};
|
||||
prefix = dimensionMap[props.queryParams.dimension] || '统计数据';
|
||||
}
|
||||
|
||||
form.fileName = `${prefix}_${dateStr}_${timeStr}`
|
||||
}
|
||||
form.fileName = `${prefix}_${dateStr}_${timeStr}`;
|
||||
};
|
||||
|
||||
const resetForm = () => {
|
||||
form.format = 'excel'
|
||||
form.includeChart = true
|
||||
form.fileName = ''
|
||||
}
|
||||
form.format = 'excel';
|
||||
form.includeChart = true;
|
||||
form.fileName = '';
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
dialogVisible.value = false
|
||||
}
|
||||
dialogVisible.value = false;
|
||||
};
|
||||
|
||||
const handleExport = async () => {
|
||||
if (!exportForm.value) return
|
||||
if (!exportForm.value) return;
|
||||
|
||||
try {
|
||||
await exportForm.value.validate()
|
||||
await exportForm.value.validate();
|
||||
} catch (error) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
loading.value = true
|
||||
loading.value = true;
|
||||
|
||||
try {
|
||||
const exportQuery: StatisticsExportQuery = {
|
||||
...props.queryParams,
|
||||
format: form.format,
|
||||
includeChart: form.includeChart
|
||||
}
|
||||
};
|
||||
|
||||
const response = await exportStatisticsResult(exportQuery)
|
||||
const response = await exportStatisticsResult(exportQuery);
|
||||
|
||||
// 创建下载链接
|
||||
const blob = new Blob([response.data], {
|
||||
type: form.format === 'excel'
|
||||
? 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
|
||||
: 'text/csv;charset=utf-8'
|
||||
})
|
||||
type: form.format === 'excel' ? 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' : 'text/csv;charset=utf-8'
|
||||
});
|
||||
|
||||
const url = window.URL.createObjectURL(blob)
|
||||
const link = document.createElement('a')
|
||||
link.href = url
|
||||
link.download = `${form.fileName}.${form.format === 'excel' ? 'xlsx' : 'csv'}`
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
const link = document.createElement('a');
|
||||
link.href = url;
|
||||
link.download = `${form.fileName}.${form.format === 'excel' ? 'xlsx' : 'csv'}`;
|
||||
|
||||
document.body.appendChild(link)
|
||||
link.click()
|
||||
document.body.removeChild(link)
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
|
||||
window.URL.revokeObjectURL(url)
|
||||
window.URL.revokeObjectURL(url);
|
||||
|
||||
ElMessage.success('导出成功')
|
||||
handleClose()
|
||||
ElMessage.success('导出成功');
|
||||
handleClose();
|
||||
} catch (error) {
|
||||
console.error('导出失败:', error)
|
||||
ElMessage.error('导出失败')
|
||||
console.error('导出失败:', error);
|
||||
ElMessage.error('导出失败');
|
||||
} finally {
|
||||
loading.value = false
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -23,22 +23,22 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, onUnmounted, watch, nextTick } from 'vue'
|
||||
import * as echarts from 'echarts'
|
||||
import { ref, onMounted, onUnmounted, watch, nextTick } from 'vue';
|
||||
import * as echarts from 'echarts';
|
||||
|
||||
// Props
|
||||
interface ChartData {
|
||||
label: string
|
||||
value: number
|
||||
percentage: number
|
||||
label: string;
|
||||
value: number;
|
||||
percentage: number;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
title?: string
|
||||
data: ChartData[]
|
||||
chartType?: 'bar' | 'pie' | 'line' | 'doughnut'
|
||||
loading?: boolean
|
||||
height?: number
|
||||
title?: string;
|
||||
data: ChartData[];
|
||||
chartType?: 'bar' | 'pie' | 'line' | 'doughnut';
|
||||
loading?: boolean;
|
||||
height?: number;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
@@ -47,82 +47,101 @@ const props = withDefaults(defineProps<Props>(), {
|
||||
chartType: 'bar',
|
||||
loading: false,
|
||||
height: 400
|
||||
})
|
||||
});
|
||||
|
||||
// Emits
|
||||
const emit = defineEmits<{
|
||||
'chart-click': [data: ChartData]
|
||||
'chart-type-change': [type: string]
|
||||
}>()
|
||||
'chart-click': [data: ChartData];
|
||||
'chart-type-change': [type: string];
|
||||
}>();
|
||||
|
||||
// 响应式数据
|
||||
const chartRef = ref<HTMLElement>()
|
||||
const chartInstance = ref<echarts.ECharts>()
|
||||
const currentChartType = ref(props.chartType)
|
||||
const chartRef = ref<HTMLElement>();
|
||||
const chartInstance = ref<echarts.ECharts>();
|
||||
const currentChartType = ref(props.chartType);
|
||||
|
||||
// 图表配色方案
|
||||
const colorScheme = [
|
||||
'#5d87ff', '#49beff', '#ff9a56', '#ffad56',
|
||||
'#4facfe', '#00f2fe', '#a8edea', '#fed6e3',
|
||||
'#667eea', '#764ba2', '#f093fb', '#f5576c',
|
||||
'#4facfe', '#00f2fe', '#43e97b', '#38f9d7'
|
||||
]
|
||||
'#5d87ff',
|
||||
'#49beff',
|
||||
'#ff9a56',
|
||||
'#ffad56',
|
||||
'#4facfe',
|
||||
'#00f2fe',
|
||||
'#a8edea',
|
||||
'#fed6e3',
|
||||
'#667eea',
|
||||
'#764ba2',
|
||||
'#f093fb',
|
||||
'#f5576c',
|
||||
'#4facfe',
|
||||
'#00f2fe',
|
||||
'#43e97b',
|
||||
'#38f9d7'
|
||||
];
|
||||
|
||||
// 监听器
|
||||
watch(() => props.data, () => {
|
||||
renderChart()
|
||||
}, { deep: true })
|
||||
watch(
|
||||
() => props.data,
|
||||
() => {
|
||||
renderChart();
|
||||
},
|
||||
{ deep: true }
|
||||
);
|
||||
|
||||
watch(() => props.loading, (newVal) => {
|
||||
if (!newVal) {
|
||||
nextTick(() => {
|
||||
renderChart()
|
||||
})
|
||||
watch(
|
||||
() => props.loading,
|
||||
(newVal) => {
|
||||
if (!newVal) {
|
||||
nextTick(() => {
|
||||
renderChart();
|
||||
});
|
||||
}
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
// 方法定义
|
||||
const initChart = () => {
|
||||
if (!chartRef.value) return
|
||||
if (!chartRef.value) return;
|
||||
|
||||
chartInstance.value = echarts.init(chartRef.value)
|
||||
chartInstance.value = echarts.init(chartRef.value);
|
||||
|
||||
// 监听图表点击事件
|
||||
chartInstance.value.on('click', (params: any) => {
|
||||
const clickedData = props.data.find(item => item.label === params.name)
|
||||
const clickedData = props.data.find((item) => item.label === params.name);
|
||||
if (clickedData) {
|
||||
emit('chart-click', clickedData)
|
||||
emit('chart-click', clickedData);
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
// 监听窗口大小变化
|
||||
window.addEventListener('resize', handleResize)
|
||||
}
|
||||
window.addEventListener('resize', handleResize);
|
||||
};
|
||||
|
||||
const handleResize = () => {
|
||||
if (chartInstance.value) {
|
||||
chartInstance.value.resize()
|
||||
chartInstance.value.resize();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleChartTypeChange = (type: string) => {
|
||||
currentChartType.value = type as any
|
||||
emit('chart-type-change', type)
|
||||
renderChart()
|
||||
}
|
||||
currentChartType.value = type as any;
|
||||
emit('chart-type-change', type);
|
||||
renderChart();
|
||||
};
|
||||
|
||||
const renderChart = () => {
|
||||
if (!chartInstance.value || !props.data || props.data.length === 0) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
const option = generateChartOption()
|
||||
chartInstance.value.setOption(option, true)
|
||||
}
|
||||
const option = generateChartOption();
|
||||
chartInstance.value.setOption(option, true);
|
||||
};
|
||||
|
||||
const generateChartOption = (): echarts.EChartsCoreOption => {
|
||||
const labels = props.data.map(item => item.label)
|
||||
const values = props.data.map(item => item.value)
|
||||
const labels = props.data.map((item) => item.label);
|
||||
const values = props.data.map((item) => item.value);
|
||||
|
||||
const baseOption = {
|
||||
tooltip: {
|
||||
@@ -149,7 +168,7 @@ const generateChartOption = (): echarts.EChartsCoreOption => {
|
||||
bottom: '3%',
|
||||
containLabel: true
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
switch (currentChartType.value) {
|
||||
case 'pie':
|
||||
@@ -159,31 +178,33 @@ const generateChartOption = (): echarts.EChartsCoreOption => {
|
||||
...baseOption.tooltip,
|
||||
formatter: '{a} <br/>{b}: {c} ({d}%)'
|
||||
},
|
||||
series: [{
|
||||
name: props.title,
|
||||
type: 'pie',
|
||||
radius: '60%',
|
||||
center: ['40%', '50%'],
|
||||
data: props.data.map((item, index) => ({
|
||||
name: item.label,
|
||||
value: item.value,
|
||||
itemStyle: {
|
||||
color: colorScheme[index % colorScheme.length]
|
||||
series: [
|
||||
{
|
||||
name: props.title,
|
||||
type: 'pie',
|
||||
radius: '60%',
|
||||
center: ['40%', '50%'],
|
||||
data: props.data.map((item, index) => ({
|
||||
name: item.label,
|
||||
value: item.value,
|
||||
itemStyle: {
|
||||
color: colorScheme[index % colorScheme.length]
|
||||
}
|
||||
})),
|
||||
emphasis: {
|
||||
itemStyle: {
|
||||
shadowBlur: 10,
|
||||
shadowOffsetX: 0,
|
||||
shadowColor: 'rgba(0, 0, 0, 0.5)'
|
||||
}
|
||||
},
|
||||
label: {
|
||||
show: true,
|
||||
formatter: '{b}: {d}%'
|
||||
}
|
||||
})),
|
||||
emphasis: {
|
||||
itemStyle: {
|
||||
shadowBlur: 10,
|
||||
shadowOffsetX: 0,
|
||||
shadowColor: 'rgba(0, 0, 0, 0.5)'
|
||||
}
|
||||
},
|
||||
label: {
|
||||
show: true,
|
||||
formatter: '{b}: {d}%'
|
||||
}
|
||||
}]
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
case 'doughnut':
|
||||
return {
|
||||
@@ -192,31 +213,33 @@ const generateChartOption = (): echarts.EChartsCoreOption => {
|
||||
...baseOption.tooltip,
|
||||
formatter: '{a} <br/>{b}: {c} ({d}%)'
|
||||
},
|
||||
series: [{
|
||||
name: props.title,
|
||||
type: 'pie',
|
||||
radius: ['40%', '70%'],
|
||||
center: ['40%', '50%'],
|
||||
data: props.data.map((item, index) => ({
|
||||
name: item.label,
|
||||
value: item.value,
|
||||
itemStyle: {
|
||||
color: colorScheme[index % colorScheme.length]
|
||||
series: [
|
||||
{
|
||||
name: props.title,
|
||||
type: 'pie',
|
||||
radius: ['40%', '70%'],
|
||||
center: ['40%', '50%'],
|
||||
data: props.data.map((item, index) => ({
|
||||
name: item.label,
|
||||
value: item.value,
|
||||
itemStyle: {
|
||||
color: colorScheme[index % colorScheme.length]
|
||||
}
|
||||
})),
|
||||
emphasis: {
|
||||
itemStyle: {
|
||||
shadowBlur: 10,
|
||||
shadowOffsetX: 0,
|
||||
shadowColor: 'rgba(0, 0, 0, 0.5)'
|
||||
}
|
||||
},
|
||||
label: {
|
||||
show: true,
|
||||
formatter: '{b}: {d}%'
|
||||
}
|
||||
})),
|
||||
emphasis: {
|
||||
itemStyle: {
|
||||
shadowBlur: 10,
|
||||
shadowOffsetX: 0,
|
||||
shadowColor: 'rgba(0, 0, 0, 0.5)'
|
||||
}
|
||||
},
|
||||
label: {
|
||||
show: true,
|
||||
formatter: '{b}: {d}%'
|
||||
}
|
||||
}]
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
case 'line':
|
||||
return {
|
||||
@@ -259,28 +282,30 @@ const generateChartOption = (): echarts.EChartsCoreOption => {
|
||||
}
|
||||
}
|
||||
},
|
||||
series: [{
|
||||
name: props.title,
|
||||
type: 'line',
|
||||
data: values,
|
||||
smooth: true,
|
||||
symbol: 'circle',
|
||||
symbolSize: 6,
|
||||
lineStyle: {
|
||||
color: colorScheme[0],
|
||||
width: 3
|
||||
},
|
||||
itemStyle: {
|
||||
color: colorScheme[0]
|
||||
},
|
||||
areaStyle: {
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||
{ offset: 0, color: colorScheme[0] + '40' },
|
||||
{ offset: 1, color: colorScheme[0] + '10' }
|
||||
])
|
||||
series: [
|
||||
{
|
||||
name: props.title,
|
||||
type: 'line',
|
||||
data: values,
|
||||
smooth: true,
|
||||
symbol: 'circle',
|
||||
symbolSize: 6,
|
||||
lineStyle: {
|
||||
color: colorScheme[0],
|
||||
width: 3
|
||||
},
|
||||
itemStyle: {
|
||||
color: colorScheme[0]
|
||||
},
|
||||
areaStyle: {
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||
{ offset: 0, color: colorScheme[0] + '40' },
|
||||
{ offset: 1, color: colorScheme[0] + '10' }
|
||||
])
|
||||
}
|
||||
}
|
||||
}]
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
default: // bar
|
||||
return {
|
||||
@@ -323,61 +348,63 @@ const generateChartOption = (): echarts.EChartsCoreOption => {
|
||||
}
|
||||
}
|
||||
},
|
||||
series: [{
|
||||
name: props.title,
|
||||
type: 'bar',
|
||||
data: values.map((value, index) => ({
|
||||
value,
|
||||
itemStyle: {
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||
{ offset: 0, color: colorScheme[index % colorScheme.length] },
|
||||
{ offset: 1, color: colorScheme[index % colorScheme.length] + '80' }
|
||||
])
|
||||
}
|
||||
})),
|
||||
barWidth: '60%',
|
||||
emphasis: {
|
||||
itemStyle: {
|
||||
shadowBlur: 10,
|
||||
shadowOffsetX: 0,
|
||||
shadowColor: 'rgba(0, 0, 0, 0.5)'
|
||||
series: [
|
||||
{
|
||||
name: props.title,
|
||||
type: 'bar',
|
||||
data: values.map((value, index) => ({
|
||||
value,
|
||||
itemStyle: {
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||
{ offset: 0, color: colorScheme[index % colorScheme.length] },
|
||||
{ offset: 1, color: colorScheme[index % colorScheme.length] + '80' }
|
||||
])
|
||||
}
|
||||
})),
|
||||
barWidth: '60%',
|
||||
emphasis: {
|
||||
itemStyle: {
|
||||
shadowBlur: 10,
|
||||
shadowOffsetX: 0,
|
||||
shadowColor: 'rgba(0, 0, 0, 0.5)'
|
||||
}
|
||||
}
|
||||
}
|
||||
}]
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const exportChart = (type: 'png' | 'jpg' | 'svg' = 'png') => {
|
||||
if (!chartInstance.value) return null
|
||||
if (!chartInstance.value) return null;
|
||||
|
||||
return chartInstance.value.getDataURL({
|
||||
type: type,
|
||||
pixelRatio: 2,
|
||||
backgroundColor: '#fff'
|
||||
})
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 暴露方法给父组件
|
||||
defineExpose({
|
||||
exportChart,
|
||||
resize: handleResize
|
||||
})
|
||||
});
|
||||
|
||||
// 生命周期
|
||||
onMounted(() => {
|
||||
nextTick(() => {
|
||||
initChart()
|
||||
renderChart()
|
||||
})
|
||||
})
|
||||
initChart();
|
||||
renderChart();
|
||||
});
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
if (chartInstance.value) {
|
||||
chartInstance.value.dispose()
|
||||
chartInstance.value.dispose();
|
||||
}
|
||||
window.removeEventListener('resize', handleResize)
|
||||
})
|
||||
window.removeEventListener('resize', handleResize);
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -63,32 +63,17 @@
|
||||
<el-form :model="queryParams" ref="queryRef" :inline="true" label-width="80px">
|
||||
<el-form-item label="统计维度" prop="dimension">
|
||||
<el-select v-model="queryParams.dimension" placeholder="请选择统计维度" clearable>
|
||||
<el-option
|
||||
v-for="item in dimensionOptions"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
<el-option v-for="item in dimensionOptions" :key="item.value" :label="item.label" :value="item.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="学院" prop="collegeIds">
|
||||
<el-select v-model="queryParams.collegeIds" placeholder="请选择学院" clearable multiple @change="handleCollegeChange">
|
||||
<el-option
|
||||
v-for="item in collegeOptions"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
<el-option v-for="item in collegeOptions" :key="item.value" :label="item.label" :value="item.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="专业" prop="majorIds">
|
||||
<el-select v-model="queryParams.majorIds" placeholder="请选择专业" clearable multiple>
|
||||
<el-option
|
||||
v-for="item in majorOptions"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
<el-option v-for="item in majorOptions" :key="item.value" :label="item.label" :value="item.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<div class="search-btn-container">
|
||||
@@ -135,11 +120,7 @@
|
||||
<div class="data-title">统计数据详情</div>
|
||||
</template>
|
||||
<div class="data-list">
|
||||
<div
|
||||
v-for="(item, index) in statisticsData"
|
||||
:key="index"
|
||||
class="data-item"
|
||||
>
|
||||
<div v-for="(item, index) in statisticsData" :key="index" class="data-item">
|
||||
<div class="item-info">
|
||||
<div class="item-label">{{ item.label }}</div>
|
||||
<div class="item-value">{{ item.value }}</div>
|
||||
@@ -163,27 +144,20 @@
|
||||
:visible="exportVisible"
|
||||
:query-params="queryParams"
|
||||
:statistics-data="statisticsData"
|
||||
@update:visible="(val) => exportVisible = val"
|
||||
@update:visible="(val) => (exportVisible = val)"
|
||||
/>
|
||||
|
||||
<!-- 配置管理对话框 -->
|
||||
<ConfigManageDialog
|
||||
:visible="configVisible"
|
||||
@update:visible="(val) => configVisible = val"
|
||||
@refresh="handleQuery"
|
||||
/>
|
||||
<ConfigManageDialog :visible="configVisible" @update:visible="(val) => (configVisible = val)" @refresh="handleQuery" />
|
||||
|
||||
<!-- 缓存管理对话框 -->
|
||||
<CacheManageDialog
|
||||
:visible="cacheVisible"
|
||||
@update:visible="(val) => cacheVisible = val"
|
||||
/>
|
||||
<CacheManageDialog :visible="cacheVisible" @update:visible="(val) => (cacheVisible = val)" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { ref, reactive, onMounted } from 'vue';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import {
|
||||
getStatisticsOverview,
|
||||
getStatisticsDimensionOptions,
|
||||
@@ -201,20 +175,20 @@ import {
|
||||
getBloodTypeStatistics,
|
||||
getTrainingTypeStatistics,
|
||||
type StatisticsAnalysisQuery
|
||||
} from '@/api/xg-xx/statistics'
|
||||
import ExportDialog from './components/ExportDialog.vue'
|
||||
import StatisticsChart from './components/StatisticsChart.vue'
|
||||
import CacheManageDialog from './components/CacheManageDialog.vue'
|
||||
import ConfigManageDialog from './components/ConfigManageDialog.vue'
|
||||
} from '@/api/xg-xx-bak/statistics';
|
||||
import ExportDialog from './components/ExportDialog.vue';
|
||||
import StatisticsChart from './components/StatisticsChart.vue';
|
||||
import CacheManageDialog from './components/CacheManageDialog.vue';
|
||||
import ConfigManageDialog from './components/ConfigManageDialog.vue';
|
||||
|
||||
// 响应式数据
|
||||
const loading = ref(false)
|
||||
const chartRef = ref<HTMLElement>()
|
||||
const chartType = ref<'bar' | 'pie' | 'line' | 'doughnut'>('bar')
|
||||
const exportVisible = ref(false)
|
||||
const configVisible = ref(false)
|
||||
const cacheVisible = ref(false)
|
||||
const currentDimensionLabel = ref('')
|
||||
const loading = ref(false);
|
||||
const chartRef = ref<HTMLElement>();
|
||||
const chartType = ref<'bar' | 'pie' | 'line' | 'doughnut'>('bar');
|
||||
const exportVisible = ref(false);
|
||||
const configVisible = ref(false);
|
||||
const cacheVisible = ref(false);
|
||||
const currentDimensionLabel = ref('');
|
||||
|
||||
// 统计概览
|
||||
const overview = reactive({
|
||||
@@ -222,7 +196,7 @@ const overview = reactive({
|
||||
totalColleges: 0,
|
||||
totalMajors: 0,
|
||||
totalClasses: 0
|
||||
})
|
||||
});
|
||||
|
||||
// 查询参数
|
||||
const queryParams = reactive<StatisticsAnalysisQuery>({
|
||||
@@ -238,132 +212,134 @@ const queryParams = reactive<StatisticsAnalysisQuery>({
|
||||
studentStatuses: [],
|
||||
trainingTypes: [],
|
||||
useCache: true
|
||||
})
|
||||
});
|
||||
|
||||
// 选项数据
|
||||
const dimensionOptions = ref<Array<{ label: string; value: string }>>([])
|
||||
const collegeOptions = ref<Array<{ label: string; value: string }>>([])
|
||||
const majorOptions = ref<Array<{ label: string; value: string }>>([])
|
||||
const dimensionOptions = ref<Array<{ label: string; value: string }>>([]);
|
||||
const collegeOptions = ref<Array<{ label: string; value: string }>>([]);
|
||||
const majorOptions = ref<Array<{ label: string; value: string }>>([]);
|
||||
|
||||
// 统计数据
|
||||
const statisticsData = ref<Array<{
|
||||
label: string
|
||||
value: number
|
||||
percentage: number
|
||||
}>>([])
|
||||
const statisticsData = ref<
|
||||
Array<{
|
||||
label: string;
|
||||
value: number;
|
||||
percentage: number;
|
||||
}>
|
||||
>([]);
|
||||
|
||||
// 方法定义
|
||||
const getOverview = async () => {
|
||||
try {
|
||||
const response = await getStatisticsOverview()
|
||||
Object.assign(overview, response.data)
|
||||
const response = await getStatisticsOverview();
|
||||
Object.assign(overview, response.data);
|
||||
} catch (error) {
|
||||
console.error('获取概览失败:', error)
|
||||
console.error('获取概览失败:', error);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const getDimensionOptions = async () => {
|
||||
try {
|
||||
const response = await getStatisticsDimensionOptions()
|
||||
dimensionOptions.value = response.data
|
||||
const response = await getStatisticsDimensionOptions();
|
||||
dimensionOptions.value = response.data;
|
||||
} catch (error) {
|
||||
console.error('获取维度选项失败:', error)
|
||||
console.error('获取维度选项失败:', error);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const getCollegeList = async () => {
|
||||
try {
|
||||
const response = await getCollegeOptions()
|
||||
collegeOptions.value = response.data
|
||||
const response = await getCollegeOptions();
|
||||
collegeOptions.value = response.data;
|
||||
} catch (error) {
|
||||
console.error('获取学院选项失败:', error)
|
||||
console.error('获取学院选项失败:', error);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const getMajorList = async (collegeId?: string) => {
|
||||
try {
|
||||
const response = await getMajorOptions(collegeId)
|
||||
majorOptions.value = response.data
|
||||
const response = await getMajorOptions(collegeId);
|
||||
majorOptions.value = response.data;
|
||||
} catch (error) {
|
||||
console.error('获取专业选项失败:', error)
|
||||
console.error('获取专业选项失败:', error);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleQuery = async () => {
|
||||
if (!queryParams.dimension) {
|
||||
ElMessage.warning('请选择统计维度')
|
||||
return
|
||||
ElMessage.warning('请选择统计维度');
|
||||
return;
|
||||
}
|
||||
|
||||
loading.value = true
|
||||
loading.value = true;
|
||||
try {
|
||||
let response
|
||||
let response;
|
||||
|
||||
// 根据统计维度调用不同的API
|
||||
switch (queryParams.dimension) {
|
||||
case 'college':
|
||||
response = await getCollegeStatistics(queryParams)
|
||||
break
|
||||
response = await getCollegeStatistics(queryParams);
|
||||
break;
|
||||
case 'major':
|
||||
response = await getMajorStatistics(queryParams)
|
||||
break
|
||||
response = await getMajorStatistics(queryParams);
|
||||
break;
|
||||
case 'class':
|
||||
response = await getClassStatistics(queryParams)
|
||||
break
|
||||
response = await getClassStatistics(queryParams);
|
||||
break;
|
||||
case 'grade':
|
||||
response = await getGradeStatistics(queryParams)
|
||||
break
|
||||
response = await getGradeStatistics(queryParams);
|
||||
break;
|
||||
case 'gender':
|
||||
response = await getGenderStatistics(queryParams)
|
||||
break
|
||||
response = await getGenderStatistics(queryParams);
|
||||
break;
|
||||
case 'age':
|
||||
response = await getAgeStatistics(queryParams)
|
||||
break
|
||||
response = await getAgeStatistics(queryParams);
|
||||
break;
|
||||
case 'political_status':
|
||||
response = await getPoliticalStatusStatistics(queryParams)
|
||||
break
|
||||
response = await getPoliticalStatusStatistics(queryParams);
|
||||
break;
|
||||
case 'nationality':
|
||||
response = await getNationStatistics(queryParams)
|
||||
break
|
||||
response = await getNationStatistics(queryParams);
|
||||
break;
|
||||
case 'source_region':
|
||||
response = await getSourceRegionStatistics(queryParams)
|
||||
break
|
||||
response = await getSourceRegionStatistics(queryParams);
|
||||
break;
|
||||
case 'blood_type':
|
||||
response = await getBloodTypeStatistics(queryParams)
|
||||
break
|
||||
response = await getBloodTypeStatistics(queryParams);
|
||||
break;
|
||||
case 'training_type':
|
||||
response = await getTrainingTypeStatistics(queryParams)
|
||||
break
|
||||
response = await getTrainingTypeStatistics(queryParams);
|
||||
break;
|
||||
default:
|
||||
ElMessage.error('不支持的统计维度')
|
||||
return
|
||||
ElMessage.error('不支持的统计维度');
|
||||
return;
|
||||
}
|
||||
|
||||
const result = response.data
|
||||
const result = response.data;
|
||||
|
||||
if (result.data && result.data.length > 0) {
|
||||
statisticsData.value = result.data.map(item => ({
|
||||
statisticsData.value = result.data.map((item) => ({
|
||||
label: item.label || item.name || item.key,
|
||||
value: Number(item.value || item.count),
|
||||
percentage: Number(((Number(item.value || item.count) / (result.total || 1)) * 100).toFixed(2))
|
||||
}))
|
||||
}));
|
||||
|
||||
// 设置当前维度标签
|
||||
const dimensionOption = dimensionOptions.value.find(opt => opt.value === queryParams.dimension)
|
||||
currentDimensionLabel.value = dimensionOption ? dimensionOption.label : queryParams.dimension
|
||||
const dimensionOption = dimensionOptions.value.find((opt) => opt.value === queryParams.dimension);
|
||||
currentDimensionLabel.value = dimensionOption ? dimensionOption.label : queryParams.dimension;
|
||||
} else {
|
||||
statisticsData.value = []
|
||||
ElMessage.warning('暂无统计数据')
|
||||
statisticsData.value = [];
|
||||
ElMessage.warning('暂无统计数据');
|
||||
}
|
||||
|
||||
ElMessage.success('统计分析完成')
|
||||
ElMessage.success('统计分析完成');
|
||||
} catch (error) {
|
||||
console.error('统计分析失败:', error)
|
||||
ElMessage.error('统计分析失败')
|
||||
console.error('统计分析失败:', error);
|
||||
ElMessage.error('统计分析失败');
|
||||
} finally {
|
||||
loading.value = false
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const resetQuery = () => {
|
||||
Object.assign(queryParams, {
|
||||
@@ -377,65 +353,61 @@ const resetQuery = () => {
|
||||
nationalities: [],
|
||||
studentStatuses: [],
|
||||
trainingTypes: []
|
||||
})
|
||||
statisticsData.value = []
|
||||
}
|
||||
});
|
||||
statisticsData.value = [];
|
||||
};
|
||||
|
||||
const handleQuickAnalysis = () => {
|
||||
if (!queryParams.dimension) {
|
||||
queryParams.dimension = 'college'
|
||||
queryParams.dimension = 'college';
|
||||
}
|
||||
handleQuery()
|
||||
}
|
||||
handleQuery();
|
||||
};
|
||||
|
||||
const handleExport = () => {
|
||||
if (statisticsData.value.length === 0) {
|
||||
ElMessage.warning('请先执行统计分析')
|
||||
return
|
||||
ElMessage.warning('请先执行统计分析');
|
||||
return;
|
||||
}
|
||||
exportVisible.value = true
|
||||
}
|
||||
exportVisible.value = true;
|
||||
};
|
||||
|
||||
const handleChartClick = (data: any) => {
|
||||
console.log('图表点击:', data)
|
||||
console.log('图表点击:', data);
|
||||
// 可以在这里处理图表点击事件
|
||||
}
|
||||
};
|
||||
|
||||
const handleChartTypeChange = (type: string) => {
|
||||
chartType.value = type as 'bar' | 'pie' | 'line' | 'doughnut'
|
||||
}
|
||||
chartType.value = type as 'bar' | 'pie' | 'line' | 'doughnut';
|
||||
};
|
||||
|
||||
const handleConfigManage = () => {
|
||||
configVisible.value = true
|
||||
}
|
||||
configVisible.value = true;
|
||||
};
|
||||
|
||||
const handleCacheManage = () => {
|
||||
cacheVisible.value = true
|
||||
}
|
||||
cacheVisible.value = true;
|
||||
};
|
||||
|
||||
// 初始化
|
||||
onMounted(async () => {
|
||||
await Promise.all([
|
||||
getOverview(),
|
||||
getDimensionOptions(),
|
||||
getCollegeList()
|
||||
])
|
||||
})
|
||||
await Promise.all([getOverview(), getDimensionOptions(), getCollegeList()]);
|
||||
});
|
||||
|
||||
// 学院变更处理
|
||||
const handleCollegeChange = async () => {
|
||||
// 清空专业选择
|
||||
queryParams.majorIds = []
|
||||
queryParams.majorIds = [];
|
||||
|
||||
// 获取当前选中学院的专业选项
|
||||
if (queryParams.collegeIds && queryParams.collegeIds.length > 0) {
|
||||
// 如果选择了学院,获取对应的专业选项
|
||||
await getMajorList(queryParams.collegeIds[0].toString())
|
||||
await getMajorList(queryParams.collegeIds[0].toString());
|
||||
} else {
|
||||
// 如果没有选择学院,获取所有专业选项
|
||||
await getMajorList()
|
||||
await getMajorList();
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -1,11 +1,5 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
:title="dialogTitle"
|
||||
v-model="dialogVisible"
|
||||
width="60%"
|
||||
:before-close="handleClose"
|
||||
append-to-body
|
||||
>
|
||||
<el-dialog :title="dialogTitle" v-model="dialogVisible" width="60%" :before-close="handleClose" append-to-body>
|
||||
<div class="audit-container">
|
||||
<!-- 审核信息 -->
|
||||
<div v-if="!batchMode" class="student-info">
|
||||
@@ -21,11 +15,7 @@
|
||||
</el-tag>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="信息完整度">
|
||||
<el-progress
|
||||
:percentage="studentData.completionRate || 0"
|
||||
:color="getCompletionColor(studentData.completionRate)"
|
||||
:stroke-width="6"
|
||||
/>
|
||||
<el-progress :percentage="studentData.completionRate || 0" :color="getCompletionColor(studentData.completionRate)" :stroke-width="6" />
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</div>
|
||||
@@ -33,12 +23,7 @@
|
||||
<!-- 批量审核信息 -->
|
||||
<div v-else class="batch-info">
|
||||
<h4>批量审核</h4>
|
||||
<el-alert
|
||||
title="批量审核提示"
|
||||
type="warning"
|
||||
:closable="false"
|
||||
show-icon
|
||||
>
|
||||
<el-alert title="批量审核提示" type="warning" :closable="false" show-icon>
|
||||
<div slot="title">
|
||||
<p>即将审核 {{ selectedIds.length }} 条学生信息</p>
|
||||
<p>请选择审核结果并填写审核意见</p>
|
||||
@@ -47,13 +32,7 @@
|
||||
</div>
|
||||
|
||||
<!-- 审核表单 -->
|
||||
<el-form
|
||||
ref="auditForm"
|
||||
:model="auditForm"
|
||||
:rules="auditRules"
|
||||
label-width="120px"
|
||||
style="margin-top: 20px"
|
||||
>
|
||||
<el-form ref="auditForm" :model="auditForm" :rules="auditRules" label-width="120px" style="margin-top: 20px">
|
||||
<el-form-item label="审核结果" prop="auditStatus">
|
||||
<el-radio-group v-model="auditForm.auditStatus">
|
||||
<el-radio label="1">审核通过</el-radio>
|
||||
@@ -62,14 +41,7 @@
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="审核意见" prop="auditRemark">
|
||||
<el-input
|
||||
v-model="auditForm.auditRemark"
|
||||
type="textarea"
|
||||
:rows="4"
|
||||
placeholder="请输入审核意见"
|
||||
maxlength="500"
|
||||
show-word-limit
|
||||
/>
|
||||
<el-input v-model="auditForm.auditRemark" type="textarea" :rows="4" placeholder="请输入审核意见" maxlength="500" show-word-limit />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
@@ -90,9 +62,7 @@
|
||||
</el-tag>
|
||||
</div>
|
||||
<div class="history-auditor">审核人:{{ item.auditorName }}</div>
|
||||
<div class="history-remark" v-if="item.auditRemark">
|
||||
审核意见:{{ item.auditRemark }}
|
||||
</div>
|
||||
<div class="history-remark" v-if="item.auditRemark">审核意见:{{ item.auditRemark }}</div>
|
||||
</div>
|
||||
</el-timeline-item>
|
||||
</el-timeline>
|
||||
@@ -100,16 +70,14 @@
|
||||
</div>
|
||||
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button type="primary" @click="submitAudit" :loading="loading">
|
||||
确认审核
|
||||
</el-button>
|
||||
<el-button type="primary" @click="submitAudit" :loading="loading"> 确认审核 </el-button>
|
||||
<el-button @click="handleClose">取消</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { auditStudent, batchAuditStudents } from '@/api/xg-xx/student'
|
||||
import { auditStudent, batchAuditStudents } from '@/api/xg-xx-bak/student';
|
||||
|
||||
export default {
|
||||
name: 'AuditDialog',
|
||||
@@ -139,36 +107,34 @@ export default {
|
||||
auditRemark: ''
|
||||
},
|
||||
auditRules: {
|
||||
auditStatus: [
|
||||
{ required: true, message: '请选择审核结果', trigger: 'change' }
|
||||
],
|
||||
auditStatus: [{ required: true, message: '请选择审核结果', trigger: 'change' }],
|
||||
auditRemark: [
|
||||
{ required: true, message: '请输入审核意见', trigger: 'blur' },
|
||||
{ min: 5, max: 500, message: '审核意见长度在5到500个字符', trigger: 'blur' }
|
||||
]
|
||||
},
|
||||
auditHistory: []
|
||||
}
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
dialogVisible: {
|
||||
get() {
|
||||
return this.visible
|
||||
return this.visible;
|
||||
},
|
||||
set(val) {
|
||||
this.$emit('update:visible', val)
|
||||
this.$emit('update:visible', val);
|
||||
}
|
||||
},
|
||||
dialogTitle() {
|
||||
return this.batchMode ? '批量审核学生信息' : '审核学生信息'
|
||||
return this.batchMode ? '批量审核学生信息' : '审核学生信息';
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
visible(val) {
|
||||
if (val) {
|
||||
this.initForm()
|
||||
this.initForm();
|
||||
if (!this.batchMode) {
|
||||
this.loadAuditHistory()
|
||||
this.loadAuditHistory();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -179,7 +145,7 @@ export default {
|
||||
this.auditForm = {
|
||||
auditStatus: '1',
|
||||
auditRemark: ''
|
||||
}
|
||||
};
|
||||
},
|
||||
/** 加载审核历史 */
|
||||
loadAuditHistory() {
|
||||
@@ -198,44 +164,38 @@ export default {
|
||||
auditorName: '李老师',
|
||||
auditRemark: '信息完整,审核通过'
|
||||
}
|
||||
]
|
||||
];
|
||||
},
|
||||
/** 提交审核 */
|
||||
submitAudit() {
|
||||
this.$refs.auditForm.validate(valid => {
|
||||
this.$refs.auditForm.validate((valid) => {
|
||||
if (valid) {
|
||||
this.loading = true
|
||||
this.loading = true;
|
||||
|
||||
const auditData = {
|
||||
auditStatus: this.auditForm.auditStatus,
|
||||
auditRemark: this.auditForm.auditRemark
|
||||
}
|
||||
};
|
||||
|
||||
let apiCall
|
||||
let apiCall;
|
||||
if (this.batchMode) {
|
||||
apiCall = batchAuditStudents(
|
||||
this.selectedIds,
|
||||
auditData.auditStatus,
|
||||
auditData.auditRemark
|
||||
)
|
||||
apiCall = batchAuditStudents(this.selectedIds, auditData.auditStatus, auditData.auditRemark);
|
||||
} else {
|
||||
apiCall = auditStudent(
|
||||
this.studentData.id,
|
||||
auditData.auditStatus,
|
||||
auditData.auditRemark
|
||||
)
|
||||
apiCall = auditStudent(this.studentData.id, auditData.auditStatus, auditData.auditRemark);
|
||||
}
|
||||
|
||||
apiCall.then(response => {
|
||||
this.$modal.msgSuccess('审核成功')
|
||||
this.handleClose()
|
||||
this.$emit('refresh')
|
||||
this.loading = false
|
||||
}).catch(() => {
|
||||
this.loading = false
|
||||
})
|
||||
apiCall
|
||||
.then((response) => {
|
||||
this.$modal.msgSuccess('审核成功');
|
||||
this.handleClose();
|
||||
this.$emit('refresh');
|
||||
this.loading = false;
|
||||
})
|
||||
.catch(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
}
|
||||
})
|
||||
});
|
||||
},
|
||||
/** 获取信息状态类型 */
|
||||
getInfoStatusType(status) {
|
||||
@@ -245,8 +205,8 @@ export default {
|
||||
'3': 'info',
|
||||
'4': 'danger',
|
||||
'5': 'info'
|
||||
}
|
||||
return statusMap[status] || 'info'
|
||||
};
|
||||
return statusMap[status] || 'info';
|
||||
},
|
||||
/** 获取信息状态文本 */
|
||||
getInfoStatusText(status) {
|
||||
@@ -256,24 +216,24 @@ export default {
|
||||
'3': '审核中',
|
||||
'4': '已驳回',
|
||||
'5': '已锁定'
|
||||
}
|
||||
return statusMap[status] || '未知'
|
||||
};
|
||||
return statusMap[status] || '未知';
|
||||
},
|
||||
/** 获取完整度颜色 */
|
||||
getCompletionColor(rate) {
|
||||
if (rate >= 90) return '#67C23A'
|
||||
if (rate >= 70) return '#E6A23C'
|
||||
if (rate >= 50) return '#F56C6C'
|
||||
return '#909399'
|
||||
if (rate >= 90) return '#67C23A';
|
||||
if (rate >= 70) return '#E6A23C';
|
||||
if (rate >= 50) return '#F56C6C';
|
||||
return '#909399';
|
||||
},
|
||||
/** 关闭弹窗 */
|
||||
handleClose() {
|
||||
this.dialogVisible = false
|
||||
this.initForm()
|
||||
this.auditHistory = []
|
||||
this.dialogVisible = false;
|
||||
this.initForm();
|
||||
this.auditHistory = [];
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -1,20 +1,9 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
:title="dialogTitle"
|
||||
v-model="dialogVisible"
|
||||
width="60%"
|
||||
:before-close="handleClose"
|
||||
append-to-body
|
||||
>
|
||||
<el-dialog :title="dialogTitle" v-model="dialogVisible" width="60%" :before-close="handleClose" append-to-body>
|
||||
<div class="batch-operation-container">
|
||||
<!-- 操作信息 -->
|
||||
<div class="operation-info">
|
||||
<el-alert
|
||||
:title="alertTitle"
|
||||
type="warning"
|
||||
:closable="false"
|
||||
show-icon
|
||||
>
|
||||
<el-alert :title="alertTitle" type="warning" :closable="false" show-icon>
|
||||
<div slot="title">
|
||||
<p>{{ alertTitle }}</p>
|
||||
<p>选中了 {{ selectedIds.length }} 条学生信息</p>
|
||||
@@ -24,12 +13,7 @@
|
||||
|
||||
<!-- 批量更新状态 -->
|
||||
<div v-if="operationType === 'updateStatus'" class="operation-form">
|
||||
<el-form
|
||||
ref="statusForm"
|
||||
:model="statusForm"
|
||||
:rules="statusRules"
|
||||
label-width="120px"
|
||||
>
|
||||
<el-form ref="statusForm" :model="statusForm" :rules="statusRules" label-width="120px">
|
||||
<el-form-item label="状态类型" prop="statusType">
|
||||
<el-radio-group v-model="statusForm.statusType">
|
||||
<el-radio label="info">信息状态</el-radio>
|
||||
@@ -38,41 +22,20 @@
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="目标状态" prop="targetStatus">
|
||||
<el-select
|
||||
v-model="statusForm.targetStatus"
|
||||
placeholder="请选择目标状态"
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-option
|
||||
v-for="option in statusOptions"
|
||||
:key="option.value"
|
||||
:label="option.label"
|
||||
:value="option.value"
|
||||
/>
|
||||
<el-select v-model="statusForm.targetStatus" placeholder="请选择目标状态" style="width: 100%">
|
||||
<el-option v-for="option in statusOptions" :key="option.value" :label="option.label" :value="option.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="操作说明" prop="remark">
|
||||
<el-input
|
||||
v-model="statusForm.remark"
|
||||
type="textarea"
|
||||
:rows="3"
|
||||
placeholder="请输入操作说明"
|
||||
maxlength="200"
|
||||
show-word-limit
|
||||
/>
|
||||
<el-input v-model="statusForm.remark" type="textarea" :rows="3" placeholder="请输入操作说明" maxlength="200" show-word-limit />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
|
||||
<!-- 更新完整度 -->
|
||||
<div v-if="operationType === 'updateCompletion'" class="operation-form">
|
||||
<el-alert
|
||||
title="更新完整度说明"
|
||||
type="info"
|
||||
:closable="false"
|
||||
show-icon
|
||||
>
|
||||
<el-alert title="更新完整度说明" type="info" :closable="false" show-icon>
|
||||
<div slot="title">
|
||||
<p>将重新计算选中学生的信息完整度</p>
|
||||
<p>系统会根据必填字段的填写情况自动计算完整度百分比</p>
|
||||
@@ -82,12 +45,7 @@
|
||||
|
||||
<!-- 数据清理 -->
|
||||
<div v-if="operationType === 'dataClean'" class="operation-form">
|
||||
<el-form
|
||||
ref="cleanForm"
|
||||
:model="cleanForm"
|
||||
:rules="cleanRules"
|
||||
label-width="120px"
|
||||
>
|
||||
<el-form ref="cleanForm" :model="cleanForm" :rules="cleanRules" label-width="120px">
|
||||
<el-form-item label="清理类型" prop="cleanType">
|
||||
<el-checkbox-group v-model="cleanForm.cleanType">
|
||||
<el-checkbox label="duplicateStudentNo">重复学号</el-checkbox>
|
||||
@@ -110,12 +68,7 @@
|
||||
|
||||
<!-- 数据导出 -->
|
||||
<div v-if="operationType === 'export'" class="operation-form">
|
||||
<el-form
|
||||
ref="exportForm"
|
||||
:model="exportForm"
|
||||
:rules="exportRules"
|
||||
label-width="120px"
|
||||
>
|
||||
<el-form ref="exportForm" :model="exportForm" :rules="exportRules" label-width="120px">
|
||||
<el-form-item label="导出格式" prop="exportFormat">
|
||||
<el-radio-group v-model="exportForm.exportFormat">
|
||||
<el-radio label="excel">Excel格式</el-radio>
|
||||
@@ -136,11 +89,7 @@
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="文件名称" prop="fileName">
|
||||
<el-input
|
||||
v-model="exportForm.fileName"
|
||||
placeholder="请输入文件名称"
|
||||
style="width: 300px"
|
||||
>
|
||||
<el-input v-model="exportForm.fileName" placeholder="请输入文件名称" style="width: 300px">
|
||||
<template slot="append">.{{ exportForm.exportFormat }}</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
@@ -150,12 +99,7 @@
|
||||
<!-- 选中数据预览 -->
|
||||
<div class="selected-data-preview">
|
||||
<h4>选中数据预览(前5条)</h4>
|
||||
<el-table
|
||||
:data="previewData"
|
||||
size="small"
|
||||
max-height="200"
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-table :data="previewData" size="small" max-height="200" style="width: 100%">
|
||||
<el-table-column prop="studentNo" label="学号" width="120" />
|
||||
<el-table-column prop="name" label="姓名" width="100" />
|
||||
<el-table-column prop="gender" label="性别" width="60">
|
||||
@@ -183,21 +127,14 @@
|
||||
</div>
|
||||
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button type="primary" @click="submitOperation" :loading="loading">
|
||||
确认执行
|
||||
</el-button>
|
||||
<el-button type="primary" @click="submitOperation" :loading="loading"> 确认执行 </el-button>
|
||||
<el-button @click="handleClose">取消</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {
|
||||
batchUpdateInfoStatus,
|
||||
batchUpdateStudentStatus,
|
||||
updateCompletionRate,
|
||||
exportStudents
|
||||
} from '@/api/xg-xx/student'
|
||||
import { batchUpdateInfoStatus, batchUpdateStudentStatus, updateCompletionRate, exportStudents } from '@/api/xg-xx-bak/student';
|
||||
|
||||
export default {
|
||||
name: 'BatchOperationDialog',
|
||||
@@ -228,24 +165,16 @@ export default {
|
||||
remark: ''
|
||||
},
|
||||
statusRules: {
|
||||
statusType: [
|
||||
{ required: true, message: '请选择状态类型', trigger: 'change' }
|
||||
],
|
||||
targetStatus: [
|
||||
{ required: true, message: '请选择目标状态', trigger: 'change' }
|
||||
]
|
||||
statusType: [{ required: true, message: '请选择状态类型', trigger: 'change' }],
|
||||
targetStatus: [{ required: true, message: '请选择目标状态', trigger: 'change' }]
|
||||
},
|
||||
cleanForm: {
|
||||
cleanType: [],
|
||||
cleanAction: 'mark'
|
||||
},
|
||||
cleanRules: {
|
||||
cleanType: [
|
||||
{ required: true, message: '请选择清理类型', trigger: 'change' }
|
||||
],
|
||||
cleanAction: [
|
||||
{ required: true, message: '请选择处理方式', trigger: 'change' }
|
||||
]
|
||||
cleanType: [{ required: true, message: '请选择清理类型', trigger: 'change' }],
|
||||
cleanAction: [{ required: true, message: '请选择处理方式', trigger: 'change' }]
|
||||
},
|
||||
exportForm: {
|
||||
exportFormat: 'excel',
|
||||
@@ -253,25 +182,19 @@ export default {
|
||||
fileName: '学生信息导出'
|
||||
},
|
||||
exportRules: {
|
||||
exportFormat: [
|
||||
{ required: true, message: '请选择导出格式', trigger: 'change' }
|
||||
],
|
||||
exportFields: [
|
||||
{ required: true, message: '请选择导出字段', trigger: 'change' }
|
||||
],
|
||||
fileName: [
|
||||
{ required: true, message: '请输入文件名称', trigger: 'blur' }
|
||||
]
|
||||
exportFormat: [{ required: true, message: '请选择导出格式', trigger: 'change' }],
|
||||
exportFields: [{ required: true, message: '请选择导出字段', trigger: 'change' }],
|
||||
fileName: [{ required: true, message: '请输入文件名称', trigger: 'blur' }]
|
||||
}
|
||||
}
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
dialogVisible: {
|
||||
get() {
|
||||
return this.visible
|
||||
return this.visible;
|
||||
},
|
||||
set(val) {
|
||||
this.$emit('update:visible', val)
|
||||
this.$emit('update:visible', val);
|
||||
}
|
||||
},
|
||||
dialogTitle() {
|
||||
@@ -280,8 +203,8 @@ export default {
|
||||
updateCompletion: '批量更新完整度',
|
||||
dataClean: '数据清理',
|
||||
export: '批量导出'
|
||||
}
|
||||
return titleMap[this.operationType] || '批量操作'
|
||||
};
|
||||
return titleMap[this.operationType] || '批量操作';
|
||||
},
|
||||
alertTitle() {
|
||||
const titleMap = {
|
||||
@@ -289,8 +212,8 @@ export default {
|
||||
updateCompletion: '即将批量更新信息完整度',
|
||||
dataClean: '即将进行数据清理',
|
||||
export: '即将导出学生数据'
|
||||
}
|
||||
return titleMap[this.operationType] || '批量操作'
|
||||
};
|
||||
return titleMap[this.operationType] || '批量操作';
|
||||
},
|
||||
statusOptions() {
|
||||
if (this.statusForm.statusType === 'info') {
|
||||
@@ -300,7 +223,7 @@ export default {
|
||||
{ label: '审核中', value: '3' },
|
||||
{ label: '已驳回', value: '4' },
|
||||
{ label: '已锁定', value: '5' }
|
||||
]
|
||||
];
|
||||
} else {
|
||||
return [
|
||||
{ label: '在籍', value: '1' },
|
||||
@@ -308,11 +231,11 @@ export default {
|
||||
{ label: '复学', value: '3' },
|
||||
{ label: '退学', value: '4' },
|
||||
{ label: '毕业', value: '5' }
|
||||
]
|
||||
];
|
||||
}
|
||||
},
|
||||
previewData() {
|
||||
return this.selectedRows.slice(0, 5)
|
||||
return this.selectedRows.slice(0, 5);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
@@ -320,98 +243,104 @@ export default {
|
||||
submitOperation() {
|
||||
switch (this.operationType) {
|
||||
case 'updateStatus':
|
||||
this.handleUpdateStatus()
|
||||
break
|
||||
this.handleUpdateStatus();
|
||||
break;
|
||||
case 'updateCompletion':
|
||||
this.handleUpdateCompletion()
|
||||
break
|
||||
this.handleUpdateCompletion();
|
||||
break;
|
||||
case 'dataClean':
|
||||
this.handleDataClean()
|
||||
break
|
||||
this.handleDataClean();
|
||||
break;
|
||||
case 'export':
|
||||
this.handleExport()
|
||||
break
|
||||
this.handleExport();
|
||||
break;
|
||||
}
|
||||
},
|
||||
/** 处理状态更新 */
|
||||
handleUpdateStatus() {
|
||||
this.$refs.statusForm.validate(valid => {
|
||||
this.$refs.statusForm.validate((valid) => {
|
||||
if (valid) {
|
||||
this.loading = true
|
||||
this.loading = true;
|
||||
|
||||
let apiCall
|
||||
let apiCall;
|
||||
if (this.statusForm.statusType === 'info') {
|
||||
apiCall = batchUpdateInfoStatus(this.selectedIds, this.statusForm.targetStatus)
|
||||
apiCall = batchUpdateInfoStatus(this.selectedIds, this.statusForm.targetStatus);
|
||||
} else {
|
||||
apiCall = batchUpdateStudentStatus(this.selectedIds, this.statusForm.targetStatus)
|
||||
apiCall = batchUpdateStudentStatus(this.selectedIds, this.statusForm.targetStatus);
|
||||
}
|
||||
|
||||
apiCall.then(response => {
|
||||
this.$modal.msgSuccess('批量更新成功')
|
||||
this.handleClose()
|
||||
this.$emit('refresh')
|
||||
this.loading = false
|
||||
}).catch(() => {
|
||||
this.loading = false
|
||||
})
|
||||
apiCall
|
||||
.then((response) => {
|
||||
this.$modal.msgSuccess('批量更新成功');
|
||||
this.handleClose();
|
||||
this.$emit('refresh');
|
||||
this.loading = false;
|
||||
})
|
||||
.catch(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
}
|
||||
})
|
||||
});
|
||||
},
|
||||
/** 处理完整度更新 */
|
||||
handleUpdateCompletion() {
|
||||
this.loading = true
|
||||
this.loading = true;
|
||||
|
||||
const promises = this.selectedIds.map(id => updateCompletionRate(id))
|
||||
const promises = this.selectedIds.map((id) => updateCompletionRate(id));
|
||||
|
||||
Promise.all(promises).then(() => {
|
||||
this.$modal.msgSuccess('批量更新完整度成功')
|
||||
this.handleClose()
|
||||
this.$emit('refresh')
|
||||
this.loading = false
|
||||
}).catch(() => {
|
||||
this.loading = false
|
||||
})
|
||||
Promise.all(promises)
|
||||
.then(() => {
|
||||
this.$modal.msgSuccess('批量更新完整度成功');
|
||||
this.handleClose();
|
||||
this.$emit('refresh');
|
||||
this.loading = false;
|
||||
})
|
||||
.catch(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
/** 处理数据清理 */
|
||||
handleDataClean() {
|
||||
this.$refs.cleanForm.validate(valid => {
|
||||
this.$refs.cleanForm.validate((valid) => {
|
||||
if (valid) {
|
||||
this.loading = true
|
||||
this.loading = true;
|
||||
|
||||
// 这里可以调用数据清理API
|
||||
// 暂时使用模拟处理
|
||||
setTimeout(() => {
|
||||
this.$modal.msgSuccess('数据清理完成')
|
||||
this.handleClose()
|
||||
this.$emit('refresh')
|
||||
this.loading = false
|
||||
}, 2000)
|
||||
this.$modal.msgSuccess('数据清理完成');
|
||||
this.handleClose();
|
||||
this.$emit('refresh');
|
||||
this.loading = false;
|
||||
}, 2000);
|
||||
}
|
||||
})
|
||||
});
|
||||
},
|
||||
/** 处理导出 */
|
||||
handleExport() {
|
||||
this.$refs.exportForm.validate(valid => {
|
||||
this.$refs.exportForm.validate((valid) => {
|
||||
if (valid) {
|
||||
this.loading = true
|
||||
this.loading = true;
|
||||
|
||||
const exportQuery = {
|
||||
ids: this.selectedIds,
|
||||
format: this.exportForm.exportFormat,
|
||||
fields: this.exportForm.exportFields
|
||||
}
|
||||
};
|
||||
|
||||
exportStudents(exportQuery).then(response => {
|
||||
const fileName = `${this.exportForm.fileName}.${this.exportForm.exportFormat}`
|
||||
this.$download.excel(response, fileName)
|
||||
this.$modal.msgSuccess('导出成功')
|
||||
this.handleClose()
|
||||
this.loading = false
|
||||
}).catch(() => {
|
||||
this.loading = false
|
||||
})
|
||||
exportStudents(exportQuery)
|
||||
.then((response) => {
|
||||
const fileName = `${this.exportForm.fileName}.${this.exportForm.exportFormat}`;
|
||||
this.$download.excel(response, fileName);
|
||||
this.$modal.msgSuccess('导出成功');
|
||||
this.handleClose();
|
||||
this.loading = false;
|
||||
})
|
||||
.catch(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
}
|
||||
})
|
||||
});
|
||||
},
|
||||
/** 获取学籍状态类型 */
|
||||
getStatusType(status) {
|
||||
@@ -421,8 +350,8 @@ export default {
|
||||
'3': 'info',
|
||||
'4': 'danger',
|
||||
'5': 'primary'
|
||||
}
|
||||
return statusMap[status] || 'info'
|
||||
};
|
||||
return statusMap[status] || 'info';
|
||||
},
|
||||
/** 获取学籍状态文本 */
|
||||
getStatusText(status) {
|
||||
@@ -432,8 +361,8 @@ export default {
|
||||
'3': '复学',
|
||||
'4': '退学',
|
||||
'5': '毕业'
|
||||
}
|
||||
return statusMap[status] || '未知'
|
||||
};
|
||||
return statusMap[status] || '未知';
|
||||
},
|
||||
/** 获取信息状态类型 */
|
||||
getInfoStatusType(status) {
|
||||
@@ -443,8 +372,8 @@ export default {
|
||||
'3': 'info',
|
||||
'4': 'danger',
|
||||
'5': 'info'
|
||||
}
|
||||
return statusMap[status] || 'info'
|
||||
};
|
||||
return statusMap[status] || 'info';
|
||||
},
|
||||
/** 获取信息状态文本 */
|
||||
getInfoStatusText(status) {
|
||||
@@ -454,8 +383,8 @@ export default {
|
||||
'3': '审核中',
|
||||
'4': '已驳回',
|
||||
'5': '已锁定'
|
||||
}
|
||||
return statusMap[status] || '未知'
|
||||
};
|
||||
return statusMap[status] || '未知';
|
||||
},
|
||||
/** 重置表单 */
|
||||
resetForms() {
|
||||
@@ -463,24 +392,24 @@ export default {
|
||||
statusType: 'info',
|
||||
targetStatus: '',
|
||||
remark: ''
|
||||
}
|
||||
};
|
||||
this.cleanForm = {
|
||||
cleanType: [],
|
||||
cleanAction: 'mark'
|
||||
}
|
||||
};
|
||||
this.exportForm = {
|
||||
exportFormat: 'excel',
|
||||
exportFields: ['basic', 'academic', 'contact'],
|
||||
fileName: '学生信息导出'
|
||||
}
|
||||
};
|
||||
},
|
||||
/** 关闭弹窗 */
|
||||
handleClose() {
|
||||
this.dialogVisible = false
|
||||
this.resetForms()
|
||||
this.dialogVisible = false;
|
||||
this.resetForms();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -1,11 +1,5 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
title="批量导入学生信息"
|
||||
v-model="dialogVisible"
|
||||
width="70%"
|
||||
:before-close="handleClose"
|
||||
append-to-body
|
||||
>
|
||||
<el-dialog title="批量导入学生信息" v-model="dialogVisible" width="70%" :before-close="handleClose" append-to-body>
|
||||
<div class="import-container">
|
||||
<!-- 步骤条 -->
|
||||
<el-steps :active="currentStep" finish-status="success" align-center>
|
||||
@@ -20,12 +14,7 @@
|
||||
<!-- 步骤1: 下载模板 -->
|
||||
<div v-if="currentStep === 0" class="step-panel">
|
||||
<div class="template-info">
|
||||
<el-alert
|
||||
title="导入说明"
|
||||
type="info"
|
||||
:closable="false"
|
||||
show-icon
|
||||
>
|
||||
<el-alert title="导入说明" type="info" :closable="false" show-icon>
|
||||
<div slot="title">
|
||||
<h4>导入说明</h4>
|
||||
<ul>
|
||||
@@ -41,13 +30,7 @@
|
||||
</div>
|
||||
|
||||
<div class="template-download">
|
||||
<el-button
|
||||
type="primary"
|
||||
icon="el-icon-download"
|
||||
size="large"
|
||||
@click="downloadTemplate"
|
||||
:loading="downloadLoading"
|
||||
>
|
||||
<el-button type="primary" icon="el-icon-download" size="large" @click="downloadTemplate" :loading="downloadLoading">
|
||||
下载导入模板
|
||||
</el-button>
|
||||
</div>
|
||||
@@ -73,16 +56,12 @@
|
||||
>
|
||||
<i class="el-icon-upload"></i>
|
||||
<div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
|
||||
<div class="el-upload__tip" slot="tip">
|
||||
只能上传Excel文件,且不超过10MB
|
||||
</div>
|
||||
<div class="el-upload__tip" slot="tip">只能上传Excel文件,且不超过10MB</div>
|
||||
</el-upload>
|
||||
</div>
|
||||
|
||||
<div class="upload-options">
|
||||
<el-checkbox v-model="updateSupport">
|
||||
允许更新已存在的学生信息
|
||||
</el-checkbox>
|
||||
<el-checkbox v-model="updateSupport"> 允许更新已存在的学生信息 </el-checkbox>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -104,12 +83,7 @@
|
||||
<!-- 错误信息 -->
|
||||
<div v-if="validationResult.errors && validationResult.errors.length > 0" class="error-list">
|
||||
<h4>错误详情</h4>
|
||||
<el-table
|
||||
:data="validationResult.errors"
|
||||
size="small"
|
||||
max-height="300"
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-table :data="validationResult.errors" size="small" max-height="300" style="width: 100%">
|
||||
<el-table-column prop="row" label="行号" width="80" />
|
||||
<el-table-column prop="field" label="字段" width="120" />
|
||||
<el-table-column prop="value" label="值" width="150" show-overflow-tooltip />
|
||||
@@ -120,12 +94,7 @@
|
||||
<!-- 预览数据 -->
|
||||
<div v-if="validationResult.preview && validationResult.preview.length > 0" class="preview-data">
|
||||
<h4>数据预览(前10条)</h4>
|
||||
<el-table
|
||||
:data="validationResult.preview"
|
||||
size="small"
|
||||
max-height="300"
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-table :data="validationResult.preview" size="small" max-height="300" style="width: 100%">
|
||||
<el-table-column prop="studentNo" label="学号" width="120" />
|
||||
<el-table-column prop="name" label="姓名" width="100" />
|
||||
<el-table-column prop="gender" label="性别" width="60" />
|
||||
@@ -142,16 +111,11 @@
|
||||
<!-- 步骤4: 确认导入 -->
|
||||
<div v-if="currentStep === 3" class="step-panel">
|
||||
<div class="import-confirm">
|
||||
<el-alert
|
||||
title="确认导入"
|
||||
type="warning"
|
||||
:closable="false"
|
||||
show-icon
|
||||
>
|
||||
<el-alert title="确认导入" type="warning" :closable="false" show-icon>
|
||||
<div slot="title">
|
||||
<h4>确认导入</h4>
|
||||
<p>即将导入 {{ validationResult.successCount }} 条学生信息,请确认是否继续?</p>
|
||||
<p v-if="updateSupport" style="color: #E6A23C;">
|
||||
<p v-if="updateSupport" style="color: #e6a23c">
|
||||
<i class="el-icon-warning"></i>
|
||||
注意:已开启更新模式,重复的学生信息将被更新
|
||||
</p>
|
||||
@@ -178,14 +142,7 @@
|
||||
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button v-if="currentStep > 0" @click="prevStep">上一步</el-button>
|
||||
<el-button
|
||||
v-if="currentStep < 3"
|
||||
type="primary"
|
||||
@click="nextStep"
|
||||
:disabled="!canNext"
|
||||
>
|
||||
下一步
|
||||
</el-button>
|
||||
<el-button v-if="currentStep < 3" type="primary" @click="nextStep" :disabled="!canNext"> 下一步 </el-button>
|
||||
<el-button
|
||||
v-if="currentStep === 2"
|
||||
type="success"
|
||||
@@ -195,21 +152,15 @@
|
||||
>
|
||||
开始导入
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="currentStep === 3"
|
||||
type="primary"
|
||||
@click="handleClose"
|
||||
>
|
||||
完成
|
||||
</el-button>
|
||||
<el-button v-if="currentStep === 3" type="primary" @click="handleClose"> 完成 </el-button>
|
||||
<el-button @click="handleClose">取消</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getImportTemplate, importStudents } from '@/api/xg-xx/student'
|
||||
import { getToken } from '@/utils/auth'
|
||||
import { getImportTemplate, importStudents } from '@/api/xg-xx-bak/student';
|
||||
import { getToken } from '@/utils/auth';
|
||||
|
||||
export default {
|
||||
name: 'ImportDialog',
|
||||
@@ -235,87 +186,88 @@ export default {
|
||||
uploadData: {
|
||||
updateSupport: false
|
||||
}
|
||||
}
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
dialogVisible: {
|
||||
get() {
|
||||
return this.visible
|
||||
return this.visible;
|
||||
},
|
||||
set(val) {
|
||||
this.$emit('update:visible', val)
|
||||
this.$emit('update:visible', val);
|
||||
}
|
||||
},
|
||||
canNext() {
|
||||
switch (this.currentStep) {
|
||||
case 0:
|
||||
return true
|
||||
return true;
|
||||
case 1:
|
||||
return this.fileList.length > 0
|
||||
return this.fileList.length > 0;
|
||||
case 2:
|
||||
return this.validationResult && this.validationResult.success
|
||||
return this.validationResult && this.validationResult.success;
|
||||
default:
|
||||
return false
|
||||
return false;
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
updateSupport(val) {
|
||||
this.uploadData.updateSupport = val
|
||||
this.uploadData.updateSupport = val;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
/** 下载模板 */
|
||||
downloadTemplate() {
|
||||
this.downloadLoading = true
|
||||
getImportTemplate().then(response => {
|
||||
this.$download.excel(response, '学生信息导入模板.xlsx')
|
||||
this.downloadLoading = false
|
||||
}).catch(() => {
|
||||
this.downloadLoading = false
|
||||
})
|
||||
this.downloadLoading = true;
|
||||
getImportTemplate()
|
||||
.then((response) => {
|
||||
this.$download.excel(response, '学生信息导入模板.xlsx');
|
||||
this.downloadLoading = false;
|
||||
})
|
||||
.catch(() => {
|
||||
this.downloadLoading = false;
|
||||
});
|
||||
},
|
||||
/** 上传前验证 */
|
||||
beforeUpload(file) {
|
||||
const isExcel = file.type === 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' ||
|
||||
file.type === 'application/vnd.ms-excel'
|
||||
const isLt10M = file.size / 1024 / 1024 < 10
|
||||
const isExcel = file.type === 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' || file.type === 'application/vnd.ms-excel';
|
||||
const isLt10M = file.size / 1024 / 1024 < 10;
|
||||
|
||||
if (!isExcel) {
|
||||
this.$message.error('只能上传Excel文件!')
|
||||
return false
|
||||
this.$message.error('只能上传Excel文件!');
|
||||
return false;
|
||||
}
|
||||
if (!isLt10M) {
|
||||
this.$message.error('文件大小不能超过10MB!')
|
||||
return false
|
||||
this.$message.error('文件大小不能超过10MB!');
|
||||
return false;
|
||||
}
|
||||
return true
|
||||
return true;
|
||||
},
|
||||
/** 上传成功 */
|
||||
handleUploadSuccess(response, file, fileList) {
|
||||
if (response.code === 200) {
|
||||
this.validationResult = response.data
|
||||
this.currentStep = 2
|
||||
this.$message.success('文件上传成功,正在验证数据...')
|
||||
this.validationResult = response.data;
|
||||
this.currentStep = 2;
|
||||
this.$message.success('文件上传成功,正在验证数据...');
|
||||
} else {
|
||||
this.$message.error(response.msg || '上传失败')
|
||||
this.handleRemove(file, fileList)
|
||||
this.$message.error(response.msg || '上传失败');
|
||||
this.handleRemove(file, fileList);
|
||||
}
|
||||
},
|
||||
/** 上传失败 */
|
||||
handleUploadError(error, file, fileList) {
|
||||
this.$message.error('上传失败: ' + error.message)
|
||||
this.handleRemove(file, fileList)
|
||||
this.$message.error('上传失败: ' + error.message);
|
||||
this.handleRemove(file, fileList);
|
||||
},
|
||||
/** 移除文件 */
|
||||
handleRemove(file, fileList) {
|
||||
this.fileList = fileList
|
||||
this.validationResult = null
|
||||
this.fileList = fileList;
|
||||
this.validationResult = null;
|
||||
},
|
||||
/** 上一步 */
|
||||
prevStep() {
|
||||
if (this.currentStep > 0) {
|
||||
this.currentStep--
|
||||
this.currentStep--;
|
||||
}
|
||||
},
|
||||
/** 下一步 */
|
||||
@@ -323,60 +275,62 @@ export default {
|
||||
if (this.currentStep < 3) {
|
||||
if (this.currentStep === 1) {
|
||||
// 开始上传文件
|
||||
this.$refs.upload.submit()
|
||||
return
|
||||
this.$refs.upload.submit();
|
||||
return;
|
||||
}
|
||||
this.currentStep++
|
||||
this.currentStep++;
|
||||
}
|
||||
},
|
||||
/** 开始导入 */
|
||||
startImport() {
|
||||
this.importLoading = true
|
||||
this.importLoading = true;
|
||||
|
||||
const importData = {
|
||||
studentInfoList: this.validationResult.validData,
|
||||
isUpdateSupport: this.updateSupport,
|
||||
operName: this.$store.state.user.name
|
||||
}
|
||||
};
|
||||
|
||||
importStudents(importData).then(response => {
|
||||
this.importResult = {
|
||||
success: response.code === 200,
|
||||
message: response.data || response.msg
|
||||
}
|
||||
this.currentStep = 3
|
||||
this.importLoading = false
|
||||
importStudents(importData)
|
||||
.then((response) => {
|
||||
this.importResult = {
|
||||
success: response.code === 200,
|
||||
message: response.data || response.msg
|
||||
};
|
||||
this.currentStep = 3;
|
||||
this.importLoading = false;
|
||||
|
||||
if (response.code === 200) {
|
||||
this.$message.success('导入成功')
|
||||
this.$emit('refresh')
|
||||
} else {
|
||||
this.$message.error(response.msg || '导入失败')
|
||||
}
|
||||
}).catch(error => {
|
||||
this.importResult = {
|
||||
success: false,
|
||||
message: error.message || '导入失败'
|
||||
}
|
||||
this.currentStep = 3
|
||||
this.importLoading = false
|
||||
})
|
||||
if (response.code === 200) {
|
||||
this.$message.success('导入成功');
|
||||
this.$emit('refresh');
|
||||
} else {
|
||||
this.$message.error(response.msg || '导入失败');
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
this.importResult = {
|
||||
success: false,
|
||||
message: error.message || '导入失败'
|
||||
};
|
||||
this.currentStep = 3;
|
||||
this.importLoading = false;
|
||||
});
|
||||
},
|
||||
/** 重置数据 */
|
||||
resetData() {
|
||||
this.currentStep = 0
|
||||
this.fileList = []
|
||||
this.updateSupport = false
|
||||
this.validationResult = null
|
||||
this.importResult = null
|
||||
this.currentStep = 0;
|
||||
this.fileList = [];
|
||||
this.updateSupport = false;
|
||||
this.validationResult = null;
|
||||
this.importResult = null;
|
||||
},
|
||||
/** 关闭弹窗 */
|
||||
handleClose() {
|
||||
this.dialogVisible = false
|
||||
this.resetData()
|
||||
this.dialogVisible = false;
|
||||
this.resetData();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -1,11 +1,5 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
:title="dialogTitle"
|
||||
v-model="dialogVisible"
|
||||
width="90%"
|
||||
:before-close="handleClose"
|
||||
append-to-body
|
||||
>
|
||||
<el-dialog :title="dialogTitle" v-model="dialogVisible" width="90%" :before-close="handleClose" append-to-body>
|
||||
<div v-loading="loading" class="statistics-container">
|
||||
<!-- 图表选择器 -->
|
||||
<div class="chart-selector">
|
||||
@@ -33,32 +27,11 @@
|
||||
<el-col :span="8">
|
||||
<div class="data-table-wrapper">
|
||||
<h4>详细数据</h4>
|
||||
<el-table
|
||||
:data="tableData"
|
||||
size="small"
|
||||
max-height="400"
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-table-column
|
||||
prop="name"
|
||||
label="名称"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
<el-table-column
|
||||
prop="count"
|
||||
label="数量"
|
||||
width="80"
|
||||
align="right"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="percentage"
|
||||
label="占比"
|
||||
width="80"
|
||||
align="right"
|
||||
>
|
||||
<template slot-scope="scope">
|
||||
{{ scope.row.percentage }}%
|
||||
</template>
|
||||
<el-table :data="tableData" size="small" max-height="400" style="width: 100%">
|
||||
<el-table-column prop="name" label="名称" show-overflow-tooltip />
|
||||
<el-table-column prop="count" label="数量" width="80" align="right" />
|
||||
<el-table-column prop="percentage" label="占比" width="80" align="right">
|
||||
<template slot-scope="scope"> {{ scope.row.percentage }}% </template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
@@ -112,8 +85,8 @@ import {
|
||||
getStudentStatusStatistics,
|
||||
getCompletionRateStatistics,
|
||||
getStatisticsOverview
|
||||
} from '@/api/xg-xx/student'
|
||||
import * as echarts from 'echarts'
|
||||
} from '@/api/xg-xx-bak/student';
|
||||
import * as echarts from 'echarts';
|
||||
|
||||
export default {
|
||||
name: 'StatisticsChartDialog',
|
||||
@@ -141,19 +114,31 @@ export default {
|
||||
average: 0
|
||||
},
|
||||
chartColors: [
|
||||
'#409EFF', '#67C23A', '#E6A23C', '#F56C6C', '#909399',
|
||||
'#5470C6', '#91CC75', '#FAC858', '#EE6666', '#73C0DE',
|
||||
'#3BA272', '#FC8452', '#9A60B4', '#EA7CCC', '#95D4F2'
|
||||
'#409EFF',
|
||||
'#67C23A',
|
||||
'#E6A23C',
|
||||
'#F56C6C',
|
||||
'#909399',
|
||||
'#5470C6',
|
||||
'#91CC75',
|
||||
'#FAC858',
|
||||
'#EE6666',
|
||||
'#73C0DE',
|
||||
'#3BA272',
|
||||
'#FC8452',
|
||||
'#9A60B4',
|
||||
'#EA7CCC',
|
||||
'#95D4F2'
|
||||
]
|
||||
}
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
dialogVisible: {
|
||||
get() {
|
||||
return this.visible
|
||||
return this.visible;
|
||||
},
|
||||
set(val) {
|
||||
this.$emit('update:visible', val)
|
||||
this.$emit('update:visible', val);
|
||||
}
|
||||
},
|
||||
dialogTitle() {
|
||||
@@ -164,182 +149,184 @@ export default {
|
||||
status: '学籍状态统计',
|
||||
completion: '信息完整度统计',
|
||||
gender: '性别分布统计'
|
||||
}
|
||||
return titleMap[this.activeChart] || '统计图表'
|
||||
};
|
||||
return titleMap[this.activeChart] || '统计图表';
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
visible(val) {
|
||||
if (val) {
|
||||
this.activeChart = this.chartType || 'college'
|
||||
this.activeChart = this.chartType || 'college';
|
||||
this.$nextTick(() => {
|
||||
this.initChart()
|
||||
this.loadChartData()
|
||||
})
|
||||
this.initChart();
|
||||
this.loadChartData();
|
||||
});
|
||||
} else {
|
||||
this.destroyChart()
|
||||
this.destroyChart();
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
/** 初始化图表 */
|
||||
initChart() {
|
||||
const chartDom = document.getElementById(this.chartId)
|
||||
const chartDom = document.getElementById(this.chartId);
|
||||
if (chartDom) {
|
||||
this.chartInstance = echarts.init(chartDom)
|
||||
this.chartInstance = echarts.init(chartDom);
|
||||
|
||||
// 监听窗口大小变化
|
||||
window.addEventListener('resize', this.handleResize)
|
||||
window.addEventListener('resize', this.handleResize);
|
||||
}
|
||||
},
|
||||
/** 销毁图表 */
|
||||
destroyChart() {
|
||||
if (this.chartInstance) {
|
||||
this.chartInstance.dispose()
|
||||
this.chartInstance = null
|
||||
this.chartInstance.dispose();
|
||||
this.chartInstance = null;
|
||||
}
|
||||
window.removeEventListener('resize', this.handleResize)
|
||||
window.removeEventListener('resize', this.handleResize);
|
||||
},
|
||||
/** 处理窗口大小变化 */
|
||||
handleResize() {
|
||||
if (this.chartInstance) {
|
||||
this.chartInstance.resize()
|
||||
this.chartInstance.resize();
|
||||
}
|
||||
},
|
||||
/** 图表类型切换 */
|
||||
handleChartChange(type) {
|
||||
this.activeChart = type
|
||||
this.loadChartData()
|
||||
this.activeChart = type;
|
||||
this.loadChartData();
|
||||
},
|
||||
/** 加载图表数据 */
|
||||
loadChartData() {
|
||||
this.loading = true
|
||||
this.loading = true;
|
||||
|
||||
let apiCall
|
||||
let apiCall;
|
||||
switch (this.activeChart) {
|
||||
case 'college':
|
||||
apiCall = getCollegeStatistics()
|
||||
break
|
||||
apiCall = getCollegeStatistics();
|
||||
break;
|
||||
case 'grade':
|
||||
apiCall = getGradeStatistics()
|
||||
break
|
||||
apiCall = getGradeStatistics();
|
||||
break;
|
||||
case 'major':
|
||||
apiCall = getMajorStatistics()
|
||||
break
|
||||
apiCall = getMajorStatistics();
|
||||
break;
|
||||
case 'status':
|
||||
apiCall = getStudentStatusStatistics()
|
||||
break
|
||||
apiCall = getStudentStatusStatistics();
|
||||
break;
|
||||
case 'completion':
|
||||
apiCall = getCompletionRateStatistics()
|
||||
break
|
||||
apiCall = getCompletionRateStatistics();
|
||||
break;
|
||||
case 'gender':
|
||||
apiCall = this.getGenderStatistics()
|
||||
break
|
||||
apiCall = this.getGenderStatistics();
|
||||
break;
|
||||
default:
|
||||
apiCall = getCollegeStatistics()
|
||||
apiCall = getCollegeStatistics();
|
||||
}
|
||||
|
||||
apiCall.then(response => {
|
||||
const data = response.data
|
||||
this.processChartData(data)
|
||||
this.renderChart(data)
|
||||
this.loading = false
|
||||
}).catch(() => {
|
||||
this.loading = false
|
||||
})
|
||||
apiCall
|
||||
.then((response) => {
|
||||
const data = response.data;
|
||||
this.processChartData(data);
|
||||
this.renderChart(data);
|
||||
this.loading = false;
|
||||
})
|
||||
.catch(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
/** 处理图表数据 */
|
||||
processChartData(data) {
|
||||
let tableData = []
|
||||
let total = 0
|
||||
let max = 0
|
||||
let tableData = [];
|
||||
let total = 0;
|
||||
let max = 0;
|
||||
|
||||
switch (this.activeChart) {
|
||||
case 'college':
|
||||
tableData = data.map(item => ({
|
||||
tableData = data.map((item) => ({
|
||||
name: item.collegeName,
|
||||
count: item.count,
|
||||
percentage: 0
|
||||
}))
|
||||
break
|
||||
}));
|
||||
break;
|
||||
case 'grade':
|
||||
tableData = data.map(item => ({
|
||||
tableData = data.map((item) => ({
|
||||
name: item.grade + '级',
|
||||
count: item.count,
|
||||
percentage: 0
|
||||
}))
|
||||
break
|
||||
}));
|
||||
break;
|
||||
case 'major':
|
||||
tableData = data.map(item => ({
|
||||
tableData = data.map((item) => ({
|
||||
name: item.majorName,
|
||||
count: item.count,
|
||||
percentage: 0
|
||||
}))
|
||||
break
|
||||
}));
|
||||
break;
|
||||
case 'status':
|
||||
tableData = data.map(item => ({
|
||||
tableData = data.map((item) => ({
|
||||
name: this.getStatusText(item.studentStatus),
|
||||
count: item.count,
|
||||
percentage: 0
|
||||
}))
|
||||
break
|
||||
}));
|
||||
break;
|
||||
case 'completion':
|
||||
tableData = data.map(item => ({
|
||||
tableData = data.map((item) => ({
|
||||
name: item.rateRange,
|
||||
count: item.count,
|
||||
percentage: 0
|
||||
}))
|
||||
break
|
||||
}));
|
||||
break;
|
||||
case 'gender':
|
||||
tableData = data.map(item => ({
|
||||
tableData = data.map((item) => ({
|
||||
name: item.gender === 'M' ? '男' : '女',
|
||||
count: item.count,
|
||||
percentage: 0
|
||||
}))
|
||||
break
|
||||
}));
|
||||
break;
|
||||
}
|
||||
|
||||
// 计算总数和百分比
|
||||
total = tableData.reduce((sum, item) => sum + item.count, 0)
|
||||
tableData.forEach(item => {
|
||||
item.percentage = total > 0 ? ((item.count / total) * 100).toFixed(1) : 0
|
||||
max = Math.max(max, item.count)
|
||||
})
|
||||
total = tableData.reduce((sum, item) => sum + item.count, 0);
|
||||
tableData.forEach((item) => {
|
||||
item.percentage = total > 0 ? ((item.count / total) * 100).toFixed(1) : 0;
|
||||
max = Math.max(max, item.count);
|
||||
});
|
||||
|
||||
this.tableData = tableData
|
||||
this.tableData = tableData;
|
||||
this.summaryData = {
|
||||
total,
|
||||
categories: tableData.length,
|
||||
max,
|
||||
average: total > 0 ? Math.round(total / tableData.length) : 0
|
||||
}
|
||||
};
|
||||
},
|
||||
/** 渲染图表 */
|
||||
renderChart(data) {
|
||||
if (!this.chartInstance) return
|
||||
if (!this.chartInstance) return;
|
||||
|
||||
const option = this.getChartOption(data)
|
||||
this.chartInstance.setOption(option, true)
|
||||
const option = this.getChartOption(data);
|
||||
this.chartInstance.setOption(option, true);
|
||||
},
|
||||
/** 获取图表配置 */
|
||||
getChartOption(data) {
|
||||
const isBar = ['college', 'major'].includes(this.activeChart)
|
||||
const isPie = ['status', 'completion', 'gender'].includes(this.activeChart)
|
||||
const isBar = ['college', 'major'].includes(this.activeChart);
|
||||
const isPie = ['status', 'completion', 'gender'].includes(this.activeChart);
|
||||
|
||||
if (isPie) {
|
||||
return this.getPieChartOption(data)
|
||||
return this.getPieChartOption(data);
|
||||
} else if (isBar) {
|
||||
return this.getBarChartOption(data)
|
||||
return this.getBarChartOption(data);
|
||||
} else {
|
||||
return this.getLineChartOption(data)
|
||||
return this.getLineChartOption(data);
|
||||
}
|
||||
},
|
||||
/** 获取饼图配置 */
|
||||
getPieChartOption(data) {
|
||||
const pieData = this.tableData.map(item => ({
|
||||
const pieData = this.tableData.map((item) => ({
|
||||
name: item.name,
|
||||
value: item.count
|
||||
}))
|
||||
}));
|
||||
|
||||
return {
|
||||
tooltip: {
|
||||
@@ -349,7 +336,7 @@ export default {
|
||||
legend: {
|
||||
orient: 'vertical',
|
||||
left: 'left',
|
||||
data: pieData.map(item => item.name)
|
||||
data: pieData.map((item) => item.name)
|
||||
},
|
||||
series: [
|
||||
{
|
||||
@@ -375,17 +362,17 @@ export default {
|
||||
data: pieData,
|
||||
itemStyle: {
|
||||
color: (params) => {
|
||||
return this.chartColors[params.dataIndex % this.chartColors.length]
|
||||
return this.chartColors[params.dataIndex % this.chartColors.length];
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
},
|
||||
/** 获取柱状图配置 */
|
||||
getBarChartOption(data) {
|
||||
const xData = this.tableData.map(item => item.name)
|
||||
const yData = this.tableData.map(item => item.count)
|
||||
const xData = this.tableData.map((item) => item.name);
|
||||
const yData = this.tableData.map((item) => item.count);
|
||||
|
||||
return {
|
||||
tooltip: {
|
||||
@@ -418,17 +405,17 @@ export default {
|
||||
data: yData,
|
||||
itemStyle: {
|
||||
color: (params) => {
|
||||
return this.chartColors[params.dataIndex % this.chartColors.length]
|
||||
return this.chartColors[params.dataIndex % this.chartColors.length];
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
},
|
||||
/** 获取折线图配置 */
|
||||
getLineChartOption(data) {
|
||||
const xData = this.tableData.map(item => item.name)
|
||||
const yData = this.tableData.map(item => item.count)
|
||||
const xData = this.tableData.map((item) => item.name);
|
||||
const yData = this.tableData.map((item) => item.count);
|
||||
|
||||
return {
|
||||
tooltip: {
|
||||
@@ -461,19 +448,19 @@ export default {
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
},
|
||||
/** 获取性别统计数据 */
|
||||
getGenderStatistics() {
|
||||
return getStatisticsOverview().then(response => {
|
||||
const data = response.data
|
||||
return getStatisticsOverview().then((response) => {
|
||||
const data = response.data;
|
||||
return {
|
||||
data: [
|
||||
{ gender: 'M', count: data.maleCount || 0 },
|
||||
{ gender: 'F', count: data.femaleCount || 0 }
|
||||
]
|
||||
}
|
||||
})
|
||||
};
|
||||
});
|
||||
},
|
||||
/** 获取学籍状态文本 */
|
||||
getStatusText(status) {
|
||||
@@ -483,8 +470,8 @@ export default {
|
||||
'3': '复学',
|
||||
'4': '退学',
|
||||
'5': '毕业'
|
||||
}
|
||||
return statusMap[status] || '未知'
|
||||
};
|
||||
return statusMap[status] || '未知';
|
||||
},
|
||||
/** 导出图表 */
|
||||
handleExport() {
|
||||
@@ -493,24 +480,24 @@ export default {
|
||||
type: 'png',
|
||||
pixelRatio: 2,
|
||||
backgroundColor: '#fff'
|
||||
})
|
||||
});
|
||||
|
||||
const link = document.createElement('a')
|
||||
link.download = `${this.dialogTitle}.png`
|
||||
link.href = url
|
||||
link.click()
|
||||
const link = document.createElement('a');
|
||||
link.download = `${this.dialogTitle}.png`;
|
||||
link.href = url;
|
||||
link.click();
|
||||
}
|
||||
},
|
||||
/** 关闭弹窗 */
|
||||
handleClose() {
|
||||
this.dialogVisible = false
|
||||
this.destroyChart()
|
||||
this.dialogVisible = false;
|
||||
this.destroyChart();
|
||||
}
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.destroyChart()
|
||||
this.destroyChart();
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -1,11 +1,5 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
title="学生详细信息"
|
||||
v-model="dialogVisible"
|
||||
width="80%"
|
||||
:before-close="handleClose"
|
||||
append-to-body
|
||||
>
|
||||
<el-dialog title="学生详细信息" v-model="dialogVisible" width="80%" :before-close="handleClose" append-to-body>
|
||||
<div v-loading="loading" class="student-detail">
|
||||
<el-tabs v-model="activeTab" type="border-card" class="detail-tabs">
|
||||
<!-- 基本信息 -->
|
||||
@@ -17,11 +11,7 @@
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="6">
|
||||
<div class="photo-container">
|
||||
<el-image
|
||||
:src="studentInfo.photoUrl || '/static/images/default-avatar.png'"
|
||||
fit="cover"
|
||||
class="student-photo"
|
||||
/>
|
||||
<el-image :src="studentInfo.photoUrl || '/static/images/default-avatar.png'" fit="cover" class="student-photo" />
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="18">
|
||||
@@ -507,10 +497,7 @@
|
||||
<el-col :span="8">
|
||||
<div class="info-item">
|
||||
<label>信息完整度:</label>
|
||||
<el-progress
|
||||
:percentage="studentInfo.completionRate || 0"
|
||||
:color="getCompletionColor(studentInfo.completionRate)"
|
||||
/>
|
||||
<el-progress :percentage="studentInfo.completionRate || 0" :color="getCompletionColor(studentInfo.completionRate)" />
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
@@ -547,16 +534,14 @@
|
||||
</div>
|
||||
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button type="primary" @click="handleEdit" v-hasPermi="['xg:studentInfo:edit']">
|
||||
编辑信息
|
||||
</el-button>
|
||||
<el-button type="primary" @click="handleEdit" v-hasPermi="['xg:studentInfo:edit']"> 编辑信息 </el-button>
|
||||
<el-button @click="handleClose">关闭</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getStudent } from '@/api/xg-xx/student'
|
||||
import { getStudent } from '@/api/xg-xx-bak/student';
|
||||
|
||||
export default {
|
||||
name: 'StudentDetailDialog',
|
||||
@@ -575,46 +560,48 @@ export default {
|
||||
loading: false,
|
||||
activeTab: 'basic',
|
||||
studentInfo: {}
|
||||
}
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
dialogVisible: {
|
||||
get() {
|
||||
return this.visible
|
||||
return this.visible;
|
||||
},
|
||||
set(val) {
|
||||
this.$emit('update:visible', val)
|
||||
this.$emit('update:visible', val);
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
visible(val) {
|
||||
if (val && this.studentId) {
|
||||
this.getStudentInfo()
|
||||
this.getStudentInfo();
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
/** 获取学生信息 */
|
||||
getStudentInfo() {
|
||||
this.loading = true
|
||||
getStudent(this.studentId).then(response => {
|
||||
this.studentInfo = response.data
|
||||
this.loading = false
|
||||
}).catch(() => {
|
||||
this.loading = false
|
||||
})
|
||||
this.loading = true;
|
||||
getStudent(this.studentId)
|
||||
.then((response) => {
|
||||
this.studentInfo = response.data;
|
||||
this.loading = false;
|
||||
})
|
||||
.catch(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
/** 编辑按钮 */
|
||||
handleEdit() {
|
||||
this.$emit('edit', this.studentInfo)
|
||||
this.handleClose()
|
||||
this.$emit('edit', this.studentInfo);
|
||||
this.handleClose();
|
||||
},
|
||||
/** 关闭弹窗 */
|
||||
handleClose() {
|
||||
this.dialogVisible = false
|
||||
this.activeTab = 'basic'
|
||||
this.studentInfo = {}
|
||||
this.dialogVisible = false;
|
||||
this.activeTab = 'basic';
|
||||
this.studentInfo = {};
|
||||
},
|
||||
/** 获取学籍状态类型 */
|
||||
getStatusType(status) {
|
||||
@@ -624,8 +611,8 @@ export default {
|
||||
'3': 'info',
|
||||
'4': 'danger',
|
||||
'5': 'primary'
|
||||
}
|
||||
return statusMap[status] || 'info'
|
||||
};
|
||||
return statusMap[status] || 'info';
|
||||
},
|
||||
/** 获取学籍状态文本 */
|
||||
getStatusText(status) {
|
||||
@@ -635,8 +622,8 @@ export default {
|
||||
'3': '复学',
|
||||
'4': '退学',
|
||||
'5': '毕业'
|
||||
}
|
||||
return statusMap[status] || '未知'
|
||||
};
|
||||
return statusMap[status] || '未知';
|
||||
},
|
||||
/** 获取信息状态类型 */
|
||||
getInfoStatusType(status) {
|
||||
@@ -646,8 +633,8 @@ export default {
|
||||
'3': 'info',
|
||||
'4': 'danger',
|
||||
'5': 'info'
|
||||
}
|
||||
return statusMap[status] || 'info'
|
||||
};
|
||||
return statusMap[status] || 'info';
|
||||
},
|
||||
/** 获取信息状态文本 */
|
||||
getInfoStatusText(status) {
|
||||
@@ -657,8 +644,8 @@ export default {
|
||||
'3': '审核中',
|
||||
'4': '已驳回',
|
||||
'5': '已锁定'
|
||||
}
|
||||
return statusMap[status] || '未知'
|
||||
};
|
||||
return statusMap[status] || '未知';
|
||||
},
|
||||
/** 获取数据来源文本 */
|
||||
getDataSourceText(source) {
|
||||
@@ -667,18 +654,18 @@ export default {
|
||||
'2': '批量导入',
|
||||
'3': '系统迁移',
|
||||
'4': '第三方同步'
|
||||
}
|
||||
return sourceMap[source] || '未知'
|
||||
};
|
||||
return sourceMap[source] || '未知';
|
||||
},
|
||||
/** 获取完整度颜色 */
|
||||
getCompletionColor(rate) {
|
||||
if (rate >= 90) return '#67C23A'
|
||||
if (rate >= 70) return '#E6A23C'
|
||||
if (rate >= 50) return '#F56C6C'
|
||||
return '#909399'
|
||||
if (rate >= 90) return '#67C23A';
|
||||
if (rate >= 70) return '#E6A23C';
|
||||
if (rate >= 50) return '#F56C6C';
|
||||
return '#909399';
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -1,18 +1,6 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
:title="dialogTitle"
|
||||
v-model="dialogVisible"
|
||||
width="80%"
|
||||
:before-close="handleClose"
|
||||
append-to-body
|
||||
>
|
||||
<el-form
|
||||
ref="studentForm"
|
||||
:model="form"
|
||||
:rules="rules"
|
||||
label-width="120px"
|
||||
v-loading="loading"
|
||||
>
|
||||
<el-dialog :title="dialogTitle" v-model="dialogVisible" width="80%" :before-close="handleClose" append-to-body>
|
||||
<el-form ref="studentForm" :model="form" :rules="rules" label-width="120px" v-loading="loading">
|
||||
<el-tabs v-model="activeTab" type="border-card">
|
||||
<!-- 基本信息 -->
|
||||
<el-tab-pane label="基本信息" name="basic">
|
||||
@@ -37,12 +25,7 @@
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="出生日期" prop="birthDate">
|
||||
<el-date-picker
|
||||
v-model="form.birthDate"
|
||||
type="date"
|
||||
placeholder="选择出生日期"
|
||||
style="width: 100%"
|
||||
/>
|
||||
<el-date-picker v-model="form.birthDate" type="date" placeholder="选择出生日期" style="width: 100%" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
@@ -98,47 +81,22 @@
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="学院" prop="collegeCode">
|
||||
<el-select
|
||||
v-model="form.collegeCode"
|
||||
placeholder="请选择学院"
|
||||
style="width: 100%"
|
||||
@change="handleCollegeChange"
|
||||
>
|
||||
<el-option
|
||||
v-for="college in collegeOptions"
|
||||
:key="college.value"
|
||||
:label="college.label"
|
||||
:value="college.value"
|
||||
/>
|
||||
<el-select v-model="form.collegeCode" placeholder="请选择学院" style="width: 100%" @change="handleCollegeChange">
|
||||
<el-option v-for="college in collegeOptions" :key="college.value" :label="college.label" :value="college.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="专业" prop="majorCode">
|
||||
<el-select
|
||||
v-model="form.majorCode"
|
||||
placeholder="请选择专业"
|
||||
style="width: 100%"
|
||||
@change="handleMajorChange"
|
||||
>
|
||||
<el-option
|
||||
v-for="major in majorOptions"
|
||||
:key="major.value"
|
||||
:label="major.label"
|
||||
:value="major.value"
|
||||
/>
|
||||
<el-select v-model="form.majorCode" placeholder="请选择专业" style="width: 100%" @change="handleMajorChange">
|
||||
<el-option v-for="major in majorOptions" :key="major.value" :label="major.label" :value="major.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="班级" prop="classCode">
|
||||
<el-select v-model="form.classCode" placeholder="请选择班级" style="width: 100%" @change="handleClassChange">
|
||||
<el-option
|
||||
v-for="cls in classOptions"
|
||||
:key="cls.value"
|
||||
:label="cls.label"
|
||||
:value="cls.value"
|
||||
/>
|
||||
<el-option v-for="cls in classOptions" :key="cls.value" :label="cls.label" :value="cls.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
@@ -154,32 +112,17 @@
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="入学年份" prop="enrollmentYear">
|
||||
<el-input-number
|
||||
v-model="form.enrollmentYear"
|
||||
:min="2000"
|
||||
:max="2030"
|
||||
style="width: 100%"
|
||||
/>
|
||||
<el-input-number v-model="form.enrollmentYear" :min="2000" :max="2030" style="width: 100%" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="入学日期" prop="enrollmentDate">
|
||||
<el-date-picker
|
||||
v-model="form.enrollmentDate"
|
||||
type="date"
|
||||
placeholder="选择入学日期"
|
||||
style="width: 100%"
|
||||
/>
|
||||
<el-date-picker v-model="form.enrollmentDate" type="date" placeholder="选择入学日期" style="width: 100%" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="学制" prop="schoolSystem">
|
||||
<el-input-number
|
||||
v-model="form.schoolSystem"
|
||||
:min="1"
|
||||
:max="8"
|
||||
style="width: 100%"
|
||||
/>
|
||||
<el-input-number v-model="form.schoolSystem" :min="1" :max="8" style="width: 100%" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
@@ -263,22 +206,12 @@
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="身高(cm)" prop="height">
|
||||
<el-input-number
|
||||
v-model="form.height"
|
||||
:min="100"
|
||||
:max="250"
|
||||
style="width: 100%"
|
||||
/>
|
||||
<el-input-number v-model="form.height" :min="100" :max="250" style="width: 100%" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="体重(kg)" prop="weight">
|
||||
<el-input-number
|
||||
v-model="form.weight"
|
||||
:min="30"
|
||||
:max="200"
|
||||
style="width: 100%"
|
||||
/>
|
||||
<el-input-number v-model="form.weight" :min="30" :max="200" style="width: 100%" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
@@ -303,72 +236,37 @@
|
||||
</el-col>
|
||||
<el-col :span="24">
|
||||
<el-form-item label="过敏史" prop="allergies">
|
||||
<el-input
|
||||
v-model="form.allergies"
|
||||
type="textarea"
|
||||
:rows="2"
|
||||
placeholder="请输入过敏史,无则填写'无'"
|
||||
/>
|
||||
<el-input v-model="form.allergies" type="textarea" :rows="2" placeholder="请输入过敏史,无则填写'无'" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24">
|
||||
<el-form-item label="病史" prop="medicalHistory">
|
||||
<el-input
|
||||
v-model="form.medicalHistory"
|
||||
type="textarea"
|
||||
:rows="2"
|
||||
placeholder="请输入病史,无则填写'无'"
|
||||
/>
|
||||
<el-input v-model="form.medicalHistory" type="textarea" :rows="2" placeholder="请输入病史,无则填写'无'" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24">
|
||||
<el-form-item label="特长爱好" prop="specialties">
|
||||
<el-input
|
||||
v-model="form.specialties"
|
||||
type="textarea"
|
||||
:rows="3"
|
||||
placeholder="请输入特长爱好"
|
||||
/>
|
||||
<el-input v-model="form.specialties" type="textarea" :rows="3" placeholder="请输入特长爱好" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24">
|
||||
<el-form-item label="获奖情况" prop="awards">
|
||||
<el-input
|
||||
v-model="form.awards"
|
||||
type="textarea"
|
||||
:rows="3"
|
||||
placeholder="请输入获奖情况,无则填写'无'"
|
||||
/>
|
||||
<el-input v-model="form.awards" type="textarea" :rows="3" placeholder="请输入获奖情况,无则填写'无'" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24">
|
||||
<el-form-item label="违纪记录" prop="violations">
|
||||
<el-input
|
||||
v-model="form.violations"
|
||||
type="textarea"
|
||||
:rows="2"
|
||||
placeholder="请输入违纪记录,无则填写'无'"
|
||||
/>
|
||||
<el-input v-model="form.violations" type="textarea" :rows="2" placeholder="请输入违纪记录,无则填写'无'" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24">
|
||||
<el-form-item label="社会活动" prop="socialActivities">
|
||||
<el-input
|
||||
v-model="form.socialActivities"
|
||||
type="textarea"
|
||||
:rows="3"
|
||||
placeholder="请输入社会活动参与情况"
|
||||
/>
|
||||
<el-input v-model="form.socialActivities" type="textarea" :rows="3" placeholder="请输入社会活动参与情况" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24">
|
||||
<el-form-item label="备注" prop="remark">
|
||||
<el-input
|
||||
v-model="form.remark"
|
||||
type="textarea"
|
||||
:rows="3"
|
||||
placeholder="请输入备注信息"
|
||||
/>
|
||||
<el-input v-model="form.remark" type="textarea" :rows="3" placeholder="请输入备注信息" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
@@ -378,7 +276,7 @@
|
||||
<el-tab-pane label="家庭信息" name="family">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="24">
|
||||
<h3 style="margin-bottom: 20px; color: #409EFF;">紧急联系人</h3>
|
||||
<h3 style="margin-bottom: 20px; color: #409eff">紧急联系人</h3>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="紧急联系人" prop="emergencyContactName">
|
||||
@@ -405,7 +303,7 @@
|
||||
</el-col>
|
||||
|
||||
<el-col :span="24">
|
||||
<h3 style="margin: 20px 0; color: #409EFF;">父母信息</h3>
|
||||
<h3 style="margin: 20px 0; color: #409eff">父母信息</h3>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="父亲姓名" prop="fatherName">
|
||||
@@ -439,7 +337,7 @@
|
||||
</el-col>
|
||||
|
||||
<el-col :span="24">
|
||||
<h3 style="margin: 20px 0; color: #409EFF;">家庭情况</h3>
|
||||
<h3 style="margin: 20px 0; color: #409eff">家庭情况</h3>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="家庭年收入" prop="familyIncome">
|
||||
@@ -465,12 +363,7 @@
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="家庭成员数" prop="familyMembers">
|
||||
<el-input-number
|
||||
v-model="form.familyMembers"
|
||||
:min="1"
|
||||
:max="20"
|
||||
style="width: 100%"
|
||||
/>
|
||||
<el-input-number v-model="form.familyMembers" :min="1" :max="20" style="width: 100%" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
@@ -480,13 +373,7 @@
|
||||
<el-tab-pane label="特殊情况" name="special">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="24">
|
||||
<el-alert
|
||||
title="特殊情况标识"
|
||||
type="info"
|
||||
:closable="false"
|
||||
show-icon
|
||||
style="margin-bottom: 20px"
|
||||
>
|
||||
<el-alert title="特殊情况标识" type="info" :closable="false" show-icon style="margin-bottom: 20px">
|
||||
<template #default>
|
||||
<p>请根据学生实际情况勾选相应的特殊身份标识,用于精准资助和管理。</p>
|
||||
</template>
|
||||
@@ -548,13 +435,7 @@
|
||||
<el-tab-pane label="银行卡信息" name="bankCard">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="24">
|
||||
<el-alert
|
||||
title="银行卡信息提示"
|
||||
type="info"
|
||||
:closable="false"
|
||||
show-icon
|
||||
style="margin-bottom: 20px"
|
||||
>
|
||||
<el-alert title="银行卡信息提示" type="info" :closable="false" show-icon style="margin-bottom: 20px">
|
||||
<template #default>
|
||||
<p>此信息用于奖学金、助学金等资助资金发放,请确保银行卡信息准确无误。</p>
|
||||
<p>建议使用中国工商银行、中国建设银行、中国银行等主要银行的储蓄卡。</p>
|
||||
@@ -564,40 +445,22 @@
|
||||
|
||||
<el-col :span="12">
|
||||
<el-form-item label="持卡人姓名" prop="cardHolderName">
|
||||
<el-input
|
||||
v-model="form.cardHolderName"
|
||||
placeholder="请输入持卡人姓名"
|
||||
:disabled="cardHolderNameDisabled"
|
||||
/>
|
||||
<div style="font-size: 12px; color: #909399; margin-top: 4px;">
|
||||
<el-checkbox v-model="useStudentName" @change="handleUseStudentName">
|
||||
使用学生姓名
|
||||
</el-checkbox>
|
||||
<el-input v-model="form.cardHolderName" placeholder="请输入持卡人姓名" :disabled="cardHolderNameDisabled" />
|
||||
<div style="font-size: 12px; color: #909399; margin-top: 4px">
|
||||
<el-checkbox v-model="useStudentName" @change="handleUseStudentName"> 使用学生姓名 </el-checkbox>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="12">
|
||||
<el-form-item label="银行卡号" prop="bankCardNo">
|
||||
<el-input
|
||||
v-model="form.bankCardNo"
|
||||
placeholder="请输入银行卡号"
|
||||
maxlength="25"
|
||||
show-word-limit
|
||||
@input="handleBankCardInput"
|
||||
/>
|
||||
<el-input v-model="form.bankCardNo" placeholder="请输入银行卡号" maxlength="25" show-word-limit @input="handleBankCardInput" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="12">
|
||||
<el-form-item label="开户银行" prop="bankName">
|
||||
<el-select
|
||||
v-model="form.bankName"
|
||||
placeholder="请选择开户银行"
|
||||
style="width: 100%"
|
||||
filterable
|
||||
allow-create
|
||||
>
|
||||
<el-select v-model="form.bankName" placeholder="请选择开户银行" style="width: 100%" filterable allow-create>
|
||||
<el-option label="中国工商银行" value="中国工商银行" />
|
||||
<el-option label="中国建设银行" value="中国建设银行" />
|
||||
<el-option label="中国银行" value="中国银行" />
|
||||
@@ -619,35 +482,20 @@
|
||||
|
||||
<el-col :span="12">
|
||||
<el-form-item label="开户支行" prop="bankBranch">
|
||||
<el-input
|
||||
v-model="form.bankBranch"
|
||||
placeholder="请输入开户支行名称"
|
||||
maxlength="100"
|
||||
/>
|
||||
<div style="font-size: 12px; color: #909399; margin-top: 4px;">
|
||||
例如:XX市XX区XX支行
|
||||
</div>
|
||||
<el-input v-model="form.bankBranch" placeholder="请输入开户支行名称" maxlength="100" />
|
||||
<div style="font-size: 12px; color: #909399; margin-top: 4px">例如:XX市XX区XX支行</div>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="12">
|
||||
<el-form-item label="开户城市" prop="bankCity">
|
||||
<el-input
|
||||
v-model="form.bankCity"
|
||||
placeholder="请输入开户城市"
|
||||
maxlength="50"
|
||||
/>
|
||||
<el-input v-model="form.bankCity" placeholder="请输入开户城市" maxlength="50" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="12">
|
||||
<el-form-item label="开户省份" prop="bankProvince">
|
||||
<el-select
|
||||
v-model="form.bankProvince"
|
||||
placeholder="请选择开户省份"
|
||||
style="width: 100%"
|
||||
filterable
|
||||
>
|
||||
<el-select v-model="form.bankProvince" placeholder="请选择开户省份" style="width: 100%" filterable>
|
||||
<el-option label="北京市" value="北京市" />
|
||||
<el-option label="天津市" value="天津市" />
|
||||
<el-option label="河北省" value="河北省" />
|
||||
@@ -717,12 +565,7 @@
|
||||
</el-col>
|
||||
|
||||
<el-col :span="24">
|
||||
<el-alert
|
||||
title="重要提醒"
|
||||
type="warning"
|
||||
:closable="false"
|
||||
show-icon
|
||||
>
|
||||
<el-alert title="重要提醒" type="warning" :closable="false" show-icon>
|
||||
<template #default>
|
||||
<p>1. 请确保银行卡信息真实有效,错误信息可能导致资助资金无法正常发放。</p>
|
||||
<p>2. 银行卡应为本人名下的储蓄卡,不建议使用信用卡。</p>
|
||||
@@ -754,7 +597,7 @@ import {
|
||||
getClassOptions,
|
||||
checkStudentNo,
|
||||
checkIdCard
|
||||
} from '@/api/xg-xx/student'
|
||||
} from '@/api/xg-xx-bak/student';
|
||||
|
||||
export default {
|
||||
name: 'StudentEditDialog',
|
||||
@@ -832,9 +675,7 @@ export default {
|
||||
{ required: true, message: '姓名不能为空', trigger: 'blur' },
|
||||
{ min: 2, max: 20, message: '姓名长度在2到20个字符', trigger: 'blur' }
|
||||
],
|
||||
gender: [
|
||||
{ required: true, message: '请选择性别', trigger: 'change' }
|
||||
],
|
||||
gender: [{ required: true, message: '请选择性别', trigger: 'change' }],
|
||||
idCard: [
|
||||
{ required: true, message: '身份证号不能为空', trigger: 'blur' },
|
||||
{ pattern: /(^\d{15}$)|(^\d{18}$)|(^\d{17}(\d|X|x)$)/, message: '身份证号格式不正确', trigger: 'blur' },
|
||||
@@ -844,32 +685,16 @@ export default {
|
||||
{ required: true, message: '手机号不能为空', trigger: 'blur' },
|
||||
{ pattern: /^1[3-9]\d{9}$/, message: '手机号格式不正确', trigger: 'blur' }
|
||||
],
|
||||
email: [
|
||||
{ type: 'email', message: '邮箱格式不正确', trigger: 'blur' }
|
||||
],
|
||||
collegeCode: [
|
||||
{ required: true, message: '请选择学院', trigger: 'change' }
|
||||
],
|
||||
majorCode: [
|
||||
{ required: true, message: '请选择专业', trigger: 'change' }
|
||||
],
|
||||
classCode: [
|
||||
{ required: true, message: '请选择班级', trigger: 'change' }
|
||||
],
|
||||
email: [{ type: 'email', message: '邮箱格式不正确', trigger: 'blur' }],
|
||||
collegeCode: [{ required: true, message: '请选择学院', trigger: 'change' }],
|
||||
majorCode: [{ required: true, message: '请选择专业', trigger: 'change' }],
|
||||
classCode: [{ required: true, message: '请选择班级', trigger: 'change' }],
|
||||
// 紧急联系人验证规则(非必填)
|
||||
emergencyContactName: [
|
||||
{ min: 2, max: 20, message: '紧急联系人姓名长度在2到20个字符', trigger: 'blur' }
|
||||
],
|
||||
emergencyContactPhone: [
|
||||
{ pattern: /^1[3-9]\d{9}$/, message: '紧急联系人电话格式不正确', trigger: 'blur' }
|
||||
],
|
||||
emergencyContactName: [{ min: 2, max: 20, message: '紧急联系人姓名长度在2到20个字符', trigger: 'blur' }],
|
||||
emergencyContactPhone: [{ pattern: /^1[3-9]\d{9}$/, message: '紧急联系人电话格式不正确', trigger: 'blur' }],
|
||||
// 银行卡信息验证规则(非必填)
|
||||
cardHolderName: [
|
||||
{ min: 2, max: 20, message: '持卡人姓名长度在2到20个字符', trigger: 'blur' }
|
||||
],
|
||||
bankCardNo: [
|
||||
{ pattern: /^[0-9]{16,25}$/, message: '银行卡号格式不正确(16-25位数字)', trigger: 'blur' }
|
||||
]
|
||||
cardHolderName: [{ min: 2, max: 20, message: '持卡人姓名长度在2到20个字符', trigger: 'blur' }],
|
||||
bankCardNo: [{ pattern: /^[0-9]{16,25}$/, message: '银行卡号格式不正确(16-25位数字)', trigger: 'blur' }]
|
||||
},
|
||||
collegeOptions: [],
|
||||
majorOptions: [],
|
||||
@@ -877,34 +702,34 @@ export default {
|
||||
// 银行卡相关数据
|
||||
useStudentName: false,
|
||||
cardHolderNameDisabled: false
|
||||
}
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
dialogVisible: {
|
||||
get() {
|
||||
return this.visible
|
||||
return this.visible;
|
||||
},
|
||||
set(val) {
|
||||
this.$emit('update:visible', val)
|
||||
this.$emit('update:visible', val);
|
||||
}
|
||||
},
|
||||
dialogTitle() {
|
||||
return this.isEdit ? '修改学生信息' : '新增学生信息'
|
||||
return this.isEdit ? '修改学生信息' : '新增学生信息';
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
visible(val) {
|
||||
if (val) {
|
||||
this.initForm()
|
||||
this.getCollegeList()
|
||||
this.initForm();
|
||||
this.getCollegeList();
|
||||
} else {
|
||||
this.resetForm()
|
||||
this.resetForm();
|
||||
}
|
||||
},
|
||||
// 监听学生姓名变化,自动更新持卡人姓名
|
||||
'form.name'(newVal) {
|
||||
if (this.useStudentName) {
|
||||
this.form.cardHolderName = newVal
|
||||
this.form.cardHolderName = newVal;
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -914,21 +739,21 @@ export default {
|
||||
if (this.isEdit && this.studentId) {
|
||||
// 编辑模式:获取学生详情
|
||||
try {
|
||||
this.loading = true
|
||||
const response = await getStudent(this.studentId)
|
||||
this.form = { ...response.data }
|
||||
this.loading = true;
|
||||
const response = await getStudent(this.studentId);
|
||||
this.form = { ...response.data };
|
||||
// 如果有学院代码,加载专业和班级选项
|
||||
if (this.form.collegeCode) {
|
||||
await this.getMajorList(this.form.collegeCode)
|
||||
await this.getMajorList(this.form.collegeCode);
|
||||
}
|
||||
if (this.form.majorCode) {
|
||||
await this.getClassList(this.form.majorCode)
|
||||
await this.getClassList(this.form.majorCode);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取学生详情失败:', error)
|
||||
this.$modal.msgError('获取学生详情失败')
|
||||
console.error('获取学生详情失败:', error);
|
||||
this.$modal.msgError('获取学生详情失败');
|
||||
} finally {
|
||||
this.loading = false
|
||||
this.loading = false;
|
||||
}
|
||||
} else {
|
||||
// 新增模式:初始化空表单
|
||||
@@ -1010,184 +835,190 @@ export default {
|
||||
cardType: '储蓄卡',
|
||||
cardStatus: '正常',
|
||||
bankCardRemark: ''
|
||||
}
|
||||
};
|
||||
}
|
||||
},
|
||||
/** 获取学院列表 */
|
||||
getCollegeList() {
|
||||
getCollegeOptions().then(response => {
|
||||
this.collegeOptions = response.data
|
||||
})
|
||||
getCollegeOptions().then((response) => {
|
||||
this.collegeOptions = response.data;
|
||||
});
|
||||
},
|
||||
/** 获取专业列表 */
|
||||
getMajorList(collegeCode) {
|
||||
getMajorOptions(collegeCode).then(response => {
|
||||
this.majorOptions = response.data
|
||||
})
|
||||
getMajorOptions(collegeCode).then((response) => {
|
||||
this.majorOptions = response.data;
|
||||
});
|
||||
},
|
||||
/** 获取班级列表 */
|
||||
getClassList(majorCode) {
|
||||
getClassOptions(majorCode).then(response => {
|
||||
this.classOptions = response.data
|
||||
})
|
||||
getClassOptions(majorCode).then((response) => {
|
||||
this.classOptions = response.data;
|
||||
});
|
||||
},
|
||||
/** 学院变化处理 */
|
||||
handleCollegeChange(collegeCode) {
|
||||
this.form.majorCode = ''
|
||||
this.form.majorName = ''
|
||||
this.form.classCode = ''
|
||||
this.form.className = ''
|
||||
this.majorOptions = []
|
||||
this.classOptions = []
|
||||
this.form.majorCode = '';
|
||||
this.form.majorName = '';
|
||||
this.form.classCode = '';
|
||||
this.form.className = '';
|
||||
this.majorOptions = [];
|
||||
this.classOptions = [];
|
||||
|
||||
if (collegeCode) {
|
||||
// 设置学院名称
|
||||
const selectedCollege = this.collegeOptions.find(item => item.value === collegeCode)
|
||||
this.form.collegeName = selectedCollege ? selectedCollege.label : ''
|
||||
const selectedCollege = this.collegeOptions.find((item) => item.value === collegeCode);
|
||||
this.form.collegeName = selectedCollege ? selectedCollege.label : '';
|
||||
|
||||
this.getMajorList(collegeCode)
|
||||
this.getMajorList(collegeCode);
|
||||
} else {
|
||||
this.form.collegeName = ''
|
||||
this.form.collegeName = '';
|
||||
}
|
||||
},
|
||||
/** 专业变化处理 */
|
||||
handleMajorChange(majorCode) {
|
||||
this.form.classCode = ''
|
||||
this.form.className = ''
|
||||
this.classOptions = []
|
||||
this.form.classCode = '';
|
||||
this.form.className = '';
|
||||
this.classOptions = [];
|
||||
|
||||
if (majorCode) {
|
||||
// 设置专业名称
|
||||
const selectedMajor = this.majorOptions.find(item => item.value === majorCode)
|
||||
this.form.majorName = selectedMajor ? selectedMajor.label : ''
|
||||
const selectedMajor = this.majorOptions.find((item) => item.value === majorCode);
|
||||
this.form.majorName = selectedMajor ? selectedMajor.label : '';
|
||||
|
||||
this.getClassList(majorCode)
|
||||
this.getClassList(majorCode);
|
||||
} else {
|
||||
this.form.majorName = ''
|
||||
this.form.majorName = '';
|
||||
}
|
||||
},
|
||||
/** 班级变化处理 */
|
||||
handleClassChange(classCode) {
|
||||
if (classCode) {
|
||||
// 设置班级名称
|
||||
const selectedClass = this.classOptions.find(item => item.value === classCode)
|
||||
this.form.className = selectedClass ? selectedClass.label : ''
|
||||
const selectedClass = this.classOptions.find((item) => item.value === classCode);
|
||||
this.form.className = selectedClass ? selectedClass.label : '';
|
||||
} else {
|
||||
this.form.className = ''
|
||||
this.form.className = '';
|
||||
}
|
||||
},
|
||||
/** 验证学号 */
|
||||
validateStudentNo(rule, value, callback) {
|
||||
if (!value) {
|
||||
callback()
|
||||
return
|
||||
callback();
|
||||
return;
|
||||
}
|
||||
|
||||
checkStudentNo(value, this.form.id).then(response => {
|
||||
if (response.data) {
|
||||
callback(new Error('学号已存在'))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
}).catch(() => {
|
||||
callback()
|
||||
})
|
||||
checkStudentNo(value, this.form.id)
|
||||
.then((response) => {
|
||||
if (response.data) {
|
||||
callback(new Error('学号已存在'));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
callback();
|
||||
});
|
||||
},
|
||||
/** 验证身份证号 */
|
||||
validateIdCard(rule, value, callback) {
|
||||
if (!value) {
|
||||
callback()
|
||||
return
|
||||
callback();
|
||||
return;
|
||||
}
|
||||
|
||||
checkIdCard(value, this.form.id).then(response => {
|
||||
if (response.data) {
|
||||
callback(new Error('身份证号已存在'))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
}).catch(() => {
|
||||
callback()
|
||||
})
|
||||
checkIdCard(value, this.form.id)
|
||||
.then((response) => {
|
||||
if (response.data) {
|
||||
callback(new Error('身份证号已存在'));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
callback();
|
||||
});
|
||||
},
|
||||
/** 提交表单 */
|
||||
submitForm() {
|
||||
console.log('提交表单开始')
|
||||
this.$refs.studentForm.validate(valid => {
|
||||
console.log('表单验证结果:', valid)
|
||||
console.log('提交表单开始');
|
||||
this.$refs.studentForm.validate((valid) => {
|
||||
console.log('表单验证结果:', valid);
|
||||
if (valid) {
|
||||
this.loading = true
|
||||
console.log('开始提交数据...')
|
||||
this.loading = true;
|
||||
console.log('开始提交数据...');
|
||||
|
||||
const submitData = { ...this.form }
|
||||
console.log('提交的数据:', submitData)
|
||||
const submitData = { ...this.form };
|
||||
console.log('提交的数据:', submitData);
|
||||
|
||||
const apiCall = this.isEdit ? updateStudent(submitData) : addStudent(submitData)
|
||||
const apiCall = this.isEdit ? updateStudent(submitData) : addStudent(submitData);
|
||||
|
||||
apiCall.then(response => {
|
||||
console.log('提交成功:', response)
|
||||
this.$modal.msgSuccess(this.isEdit ? '修改成功' : '新增成功')
|
||||
this.handleClose()
|
||||
this.$emit('refresh')
|
||||
this.loading = false
|
||||
}).catch(error => {
|
||||
console.error('提交失败:', error)
|
||||
this.loading = false
|
||||
// 显示错误信息
|
||||
if (error.response && error.response.data && error.response.data.msg) {
|
||||
this.$modal.msgError(error.response.data.msg)
|
||||
} else if (error.message) {
|
||||
this.$modal.msgError(error.message)
|
||||
} else {
|
||||
this.$modal.msgError(this.isEdit ? '修改失败' : '新增失败')
|
||||
}
|
||||
})
|
||||
apiCall
|
||||
.then((response) => {
|
||||
console.log('提交成功:', response);
|
||||
this.$modal.msgSuccess(this.isEdit ? '修改成功' : '新增成功');
|
||||
this.handleClose();
|
||||
this.$emit('refresh');
|
||||
this.loading = false;
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('提交失败:', error);
|
||||
this.loading = false;
|
||||
// 显示错误信息
|
||||
if (error.response && error.response.data && error.response.data.msg) {
|
||||
this.$modal.msgError(error.response.data.msg);
|
||||
} else if (error.message) {
|
||||
this.$modal.msgError(error.message);
|
||||
} else {
|
||||
this.$modal.msgError(this.isEdit ? '修改失败' : '新增失败');
|
||||
}
|
||||
});
|
||||
} else {
|
||||
console.log('表单验证失败')
|
||||
console.log('表单验证失败');
|
||||
}
|
||||
})
|
||||
});
|
||||
},
|
||||
/** 重置表单 */
|
||||
resetForm() {
|
||||
this.$nextTick(() => {
|
||||
if (this.$refs.studentForm) {
|
||||
this.$refs.studentForm.resetFields()
|
||||
this.$refs.studentForm.resetFields();
|
||||
}
|
||||
})
|
||||
this.activeTab = 'basic'
|
||||
this.majorOptions = []
|
||||
this.classOptions = []
|
||||
});
|
||||
this.activeTab = 'basic';
|
||||
this.majorOptions = [];
|
||||
this.classOptions = [];
|
||||
},
|
||||
/** 银行卡相关方法 */
|
||||
/** 使用学生姓名处理 */
|
||||
handleUseStudentName(val) {
|
||||
if (val) {
|
||||
this.form.cardHolderName = this.form.name
|
||||
this.cardHolderNameDisabled = true
|
||||
this.form.cardHolderName = this.form.name;
|
||||
this.cardHolderNameDisabled = true;
|
||||
} else {
|
||||
this.form.cardHolderName = ''
|
||||
this.cardHolderNameDisabled = false
|
||||
this.form.cardHolderName = '';
|
||||
this.cardHolderNameDisabled = false;
|
||||
}
|
||||
},
|
||||
/** 银行卡号输入处理 */
|
||||
handleBankCardInput(value) {
|
||||
// 移除非数字字符
|
||||
const cleanValue = value.replace(/\D/g, '')
|
||||
this.form.bankCardNo = cleanValue
|
||||
const cleanValue = value.replace(/\D/g, '');
|
||||
this.form.bankCardNo = cleanValue;
|
||||
|
||||
// 简单的银行卡号格式化(每4位加空格显示,但存储时不包含空格)
|
||||
if (cleanValue.length > 0) {
|
||||
const formatted = cleanValue.replace(/(\d{4})(?=\d)/g, '$1 ')
|
||||
const formatted = cleanValue.replace(/(\d{4})(?=\d)/g, '$1 ');
|
||||
// 这里可以添加更多的格式化逻辑
|
||||
}
|
||||
},
|
||||
/** 关闭弹窗 */
|
||||
handleClose() {
|
||||
this.dialogVisible = false
|
||||
this.resetForm()
|
||||
this.dialogVisible = false;
|
||||
this.resetForm();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -62,20 +62,10 @@
|
||||
<el-card shadow="never" class="button-card">
|
||||
<el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch" label-width="68px">
|
||||
<el-form-item label="学号" prop="studentNo">
|
||||
<el-input
|
||||
v-model="queryParams.studentNo"
|
||||
placeholder="请输入学号"
|
||||
clearable
|
||||
@keyup.enter="handleQuery"
|
||||
/>
|
||||
<el-input v-model="queryParams.studentNo" placeholder="请输入学号" clearable @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
<el-form-item label="姓名" prop="name">
|
||||
<el-input
|
||||
v-model="queryParams.name"
|
||||
placeholder="请输入姓名"
|
||||
clearable
|
||||
@keyup.enter="handleQuery"
|
||||
/>
|
||||
<el-input v-model="queryParams.name" placeholder="请输入姓名" clearable @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
<el-form-item label="性别" prop="gender">
|
||||
<el-select v-model="queryParams.gender" placeholder="请选择性别" clearable>
|
||||
@@ -85,32 +75,17 @@
|
||||
</el-form-item>
|
||||
<el-form-item label="学院" prop="collegeCode">
|
||||
<el-select v-model="queryParams.collegeCode" placeholder="请选择学院" clearable @change="handleCollegeChange">
|
||||
<el-option
|
||||
v-for="college in collegeOptions"
|
||||
:key="college.value"
|
||||
:label="college.label"
|
||||
:value="college.value"
|
||||
/>
|
||||
<el-option v-for="college in collegeOptions" :key="college.value" :label="college.label" :value="college.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="专业" prop="majorCode">
|
||||
<el-select v-model="queryParams.majorCode" placeholder="请选择专业" clearable @change="handleMajorChange">
|
||||
<el-option
|
||||
v-for="major in majorOptions"
|
||||
:key="major.value"
|
||||
:label="major.label"
|
||||
:value="major.value"
|
||||
/>
|
||||
<el-option v-for="major in majorOptions" :key="major.value" :label="major.label" :value="major.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="班级" prop="classCode">
|
||||
<el-select v-model="queryParams.classCode" placeholder="请选择班级" clearable>
|
||||
<el-option
|
||||
v-for="classItem in classOptions"
|
||||
:key="classItem.value"
|
||||
:label="classItem.label"
|
||||
:value="classItem.value"
|
||||
/>
|
||||
<el-option v-for="classItem in classOptions" :key="classItem.value" :label="classItem.label" :value="classItem.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="状态" prop="studentStatus">
|
||||
@@ -124,12 +99,7 @@
|
||||
</el-form-item>
|
||||
<el-form-item label="班主任" prop="classTeacherId">
|
||||
<el-select v-model="queryParams.classTeacherId" placeholder="请选择班主任" clearable>
|
||||
<el-option
|
||||
v-for="teacher in classTeacherOptions"
|
||||
:key="teacher.value"
|
||||
:label="teacher.label"
|
||||
:value="teacher.value"
|
||||
/>
|
||||
<el-option v-for="teacher in classTeacherOptions" :key="teacher.value" :label="teacher.label" :value="teacher.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<div class="search-btn-container">
|
||||
@@ -146,92 +116,41 @@
|
||||
<template #header>
|
||||
<el-row :gutter="10" class="mb8">
|
||||
<el-col :span="1.5">
|
||||
<el-button
|
||||
type="primary"
|
||||
plain
|
||||
icon="Plus"
|
||||
@click="handleAdd"
|
||||
v-hasPermi="['xg:studentInfo:add']"
|
||||
>新增</el-button>
|
||||
<el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['xg:studentInfo:add']">新增</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button
|
||||
type="success"
|
||||
plain
|
||||
icon="Edit"
|
||||
:disabled="single"
|
||||
@click="handleUpdate"
|
||||
v-hasPermi="['xg:studentInfo:edit']"
|
||||
>修改</el-button>
|
||||
<el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate" v-hasPermi="['xg:studentInfo:edit']">修改</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button
|
||||
type="danger"
|
||||
plain
|
||||
icon="Delete"
|
||||
:disabled="multiple"
|
||||
@click="handleDelete"
|
||||
v-hasPermi="['xg:studentInfo:remove']"
|
||||
>删除</el-button>
|
||||
<el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete" v-hasPermi="['xg:studentInfo:remove']"
|
||||
>删除</el-button
|
||||
>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button
|
||||
type="warning"
|
||||
plain
|
||||
icon="Download"
|
||||
@click="handleExport"
|
||||
v-hasPermi="['xg:studentInfo:export']"
|
||||
>导出</el-button>
|
||||
<el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['xg:studentInfo:export']">导出</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button
|
||||
type="info"
|
||||
plain
|
||||
icon="Upload"
|
||||
@click="handleImport"
|
||||
v-hasPermi="['xg:studentInfo:import']"
|
||||
>导入</el-button>
|
||||
<el-button type="info" plain icon="Upload" @click="handleImport" v-hasPermi="['xg:studentInfo:import']">导入</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button
|
||||
type="primary"
|
||||
plain
|
||||
icon="DataAnalysis"
|
||||
@click="handleStatistics"
|
||||
v-hasPermi="['xg:studentInfo:statistics']"
|
||||
>统计</el-button>
|
||||
<el-button type="primary" plain icon="DataAnalysis" @click="handleStatistics" v-hasPermi="['xg:studentInfo:statistics']">统计</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button
|
||||
type="success"
|
||||
plain
|
||||
icon="Check"
|
||||
:disabled="multiple"
|
||||
@click="handleBatchAudit"
|
||||
v-hasPermi="['xg:studentInfo:audit']"
|
||||
>批量审核</el-button>
|
||||
<el-button type="success" plain icon="Check" :disabled="multiple" @click="handleBatchAudit" v-hasPermi="['xg:studentInfo:audit']"
|
||||
>批量审核</el-button
|
||||
>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button
|
||||
type="warning"
|
||||
plain
|
||||
icon="Operation"
|
||||
:disabled="multiple"
|
||||
@click="handleBatchOperation"
|
||||
v-hasPermi="['xg:studentInfo:batch']"
|
||||
>批量操作</el-button>
|
||||
<el-button type="warning" plain icon="Operation" :disabled="multiple" @click="handleBatchOperation" v-hasPermi="['xg:studentInfo:batch']"
|
||||
>批量操作</el-button
|
||||
>
|
||||
</el-col>
|
||||
<right-toolbar :showSearch="showSearch" @update:showSearch="(val) => showSearch = val" @queryTable="getList"></right-toolbar>
|
||||
<right-toolbar :showSearch="showSearch" @update:showSearch="(val) => (showSearch = val)" @queryTable="getList"></right-toolbar>
|
||||
</el-row>
|
||||
</template>
|
||||
|
||||
<div class="table-container">
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
:data="studentList"
|
||||
@selection-change="handleSelectionChange"
|
||||
class="scroll-table"
|
||||
>
|
||||
<el-table v-loading="loading" :data="studentList" @selection-change="handleSelectionChange" class="scroll-table">
|
||||
<el-table-column type="selection" width="55" align="center" />
|
||||
<el-table-column label="学号" align="center" prop="studentNo" width="120" />
|
||||
<el-table-column label="姓名" align="center" prop="name" width="100" />
|
||||
@@ -277,40 +196,16 @@
|
||||
<template #default="scope">
|
||||
<div class="action-buttons">
|
||||
<el-tooltip content="查看详情" placement="top">
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
icon="View"
|
||||
@click="handleDetail(scope.row)"
|
||||
v-hasPermi="['xg:studentInfo:query']"
|
||||
/>
|
||||
<el-button link type="primary" icon="View" @click="handleDetail(scope.row)" v-hasPermi="['xg:studentInfo:query']" />
|
||||
</el-tooltip>
|
||||
<el-tooltip content="编辑" placement="top">
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
icon="Edit"
|
||||
@click="handleUpdate(scope.row)"
|
||||
v-hasPermi="['xg:studentInfo:edit']"
|
||||
/>
|
||||
<el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['xg:studentInfo:edit']" />
|
||||
</el-tooltip>
|
||||
<el-tooltip content="删除" placement="top">
|
||||
<el-button
|
||||
link
|
||||
type="danger"
|
||||
icon="Delete"
|
||||
@click="handleDelete(scope.row)"
|
||||
v-hasPermi="['xg:studentInfo:remove']"
|
||||
/>
|
||||
<el-button link type="danger" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['xg:studentInfo:remove']" />
|
||||
</el-tooltip>
|
||||
<el-tooltip content="审核" placement="top">
|
||||
<el-button
|
||||
link
|
||||
type="success"
|
||||
icon="Check"
|
||||
@click="handleAudit(scope.row)"
|
||||
v-hasPermi="['xg:studentInfo:audit']"
|
||||
/>
|
||||
<el-button link type="success" icon="Check" @click="handleAudit(scope.row)" v-hasPermi="['xg:studentInfo:audit']" />
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</template>
|
||||
@@ -325,8 +220,8 @@
|
||||
:total="total"
|
||||
:page="queryParams.pageNum"
|
||||
:limit="queryParams.pageSize"
|
||||
@update:page="(val) => queryParams.pageNum = val"
|
||||
@update:limit="(val) => queryParams.pageSize = val"
|
||||
@update:page="(val) => (queryParams.pageNum = val)"
|
||||
@update:limit="(val) => (queryParams.pageSize = val)"
|
||||
@pagination="getList"
|
||||
/>
|
||||
</div>
|
||||
@@ -336,7 +231,7 @@
|
||||
<StudentDetailDialog
|
||||
:visible="detailVisible"
|
||||
:student-id="selectedStudentId"
|
||||
@update:visible="(val) => detailVisible = val"
|
||||
@update:visible="(val) => (detailVisible = val)"
|
||||
@refresh="getList"
|
||||
/>
|
||||
|
||||
@@ -345,29 +240,22 @@
|
||||
:visible="editVisible"
|
||||
:student-id="selectedStudentId"
|
||||
:is-edit="isEdit"
|
||||
@update:visible="(val) => editVisible = val"
|
||||
@update:visible="(val) => (editVisible = val)"
|
||||
@refresh="getList"
|
||||
/>
|
||||
|
||||
<!-- 统计图表弹窗 -->
|
||||
<StatisticsChartDialog
|
||||
:visible="statisticsVisible"
|
||||
@update:visible="(val) => statisticsVisible = val"
|
||||
/>
|
||||
<StatisticsChartDialog :visible="statisticsVisible" @update:visible="(val) => (statisticsVisible = val)" />
|
||||
|
||||
<!-- 导入弹窗 -->
|
||||
<ImportDialog
|
||||
:visible="importVisible"
|
||||
@update:visible="(val) => importVisible = val"
|
||||
@refresh="getList"
|
||||
/>
|
||||
<ImportDialog :visible="importVisible" @update:visible="(val) => (importVisible = val)" @refresh="getList" />
|
||||
|
||||
<!-- 审核弹窗 -->
|
||||
<AuditDialog
|
||||
:visible="auditVisible"
|
||||
:student-ids="selectedStudentIds"
|
||||
:is-batch="isBatchAudit"
|
||||
@update:visible="(val) => auditVisible = val"
|
||||
@update:visible="(val) => (auditVisible = val)"
|
||||
@refresh="getList"
|
||||
/>
|
||||
|
||||
@@ -375,32 +263,21 @@
|
||||
<BatchOperationDialog
|
||||
:visible="batchOperationVisible"
|
||||
:student-ids="selectedStudentIds"
|
||||
@update:visible="(val) => batchOperationVisible = val"
|
||||
@update:visible="(val) => (batchOperationVisible = val)"
|
||||
@refresh="getList"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {
|
||||
listStudents,
|
||||
getStudent,
|
||||
delStudent,
|
||||
exportStudents,
|
||||
getStatisticsOverview,
|
||||
getClassTeacherOptions
|
||||
} from '@/api/xg-xx/student'
|
||||
import {
|
||||
getCollegeOptions,
|
||||
getMajorOptions,
|
||||
getClassOptions
|
||||
} from '@/api/xg-xx/statistics'
|
||||
import StudentDetailDialog from './components/StudentDetailDialog'
|
||||
import StudentEditDialog from './components/StudentEditDialog'
|
||||
import AuditDialog from './components/AuditDialog'
|
||||
import ImportDialog from './components/ImportDialog'
|
||||
import BatchOperationDialog from './components/BatchOperationDialog'
|
||||
import StatisticsChartDialog from './components/StatisticsChartDialog'
|
||||
import { listStudents, getStudent, delStudent, exportStudents, getStatisticsOverview, getClassTeacherOptions } from '@/api/xg-xx-bak/student';
|
||||
import { getCollegeOptions, getMajorOptions, getClassOptions } from '@/api/xg-xx-bak/statistics';
|
||||
import StudentDetailDialog from './components/StudentDetailDialog';
|
||||
import StudentEditDialog from './components/StudentEditDialog';
|
||||
import AuditDialog from './components/AuditDialog';
|
||||
import ImportDialog from './components/ImportDialog';
|
||||
import BatchOperationDialog from './components/BatchOperationDialog';
|
||||
import StatisticsChartDialog from './components/StatisticsChartDialog';
|
||||
|
||||
export default {
|
||||
name: 'StudentInfo',
|
||||
@@ -477,229 +354,241 @@ export default {
|
||||
isEdit: false,
|
||||
isBatchAudit: false,
|
||||
selectedStudentIds: []
|
||||
}
|
||||
};
|
||||
},
|
||||
created() {
|
||||
this.getList()
|
||||
this.getStatistics()
|
||||
this.getCollegeList()
|
||||
this.getClassTeacherOptions() // 调用API获取班主任数据
|
||||
this.getList();
|
||||
this.getStatistics();
|
||||
this.getCollegeList();
|
||||
this.getClassTeacherOptions(); // 调用API获取班主任数据
|
||||
},
|
||||
methods: {
|
||||
/** 查询学生列表 */
|
||||
getList() {
|
||||
this.loading = true
|
||||
console.log('开始查询学生列表,查询参数:', this.queryParams)
|
||||
this.loading = true;
|
||||
console.log('开始查询学生列表,查询参数:', this.queryParams);
|
||||
|
||||
listStudents(this.queryParams).then(response => {
|
||||
console.log('查询学生列表成功,响应数据:', response)
|
||||
this.studentList = response.rows
|
||||
this.total = response.total
|
||||
this.loading = false
|
||||
}).catch(error => {
|
||||
console.error('查询学生列表失败,错误详情:', error)
|
||||
listStudents(this.queryParams)
|
||||
.then((response) => {
|
||||
console.log('查询学生列表成功,响应数据:', response);
|
||||
this.studentList = response.rows;
|
||||
this.total = response.total;
|
||||
this.loading = false;
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('查询学生列表失败,错误详情:', error);
|
||||
|
||||
// 提供更详细的错误信息
|
||||
let errorMessage = '查询学生列表失败'
|
||||
if (error.response) {
|
||||
// 服务器返回了错误响应
|
||||
const status = error.response.status
|
||||
const data = error.response.data
|
||||
// 提供更详细的错误信息
|
||||
let errorMessage = '查询学生列表失败';
|
||||
if (error.response) {
|
||||
// 服务器返回了错误响应
|
||||
const status = error.response.status;
|
||||
const data = error.response.data;
|
||||
|
||||
if (status === 401) {
|
||||
errorMessage = '认证失败,请重新登录'
|
||||
// 可以在这里跳转到登录页面
|
||||
// this.$router.push('/login')
|
||||
} else if (status === 403) {
|
||||
errorMessage = '权限不足,无法访问学生信息'
|
||||
} else if (status === 500) {
|
||||
errorMessage = '服务器内部错误'
|
||||
if (data && data.msg) {
|
||||
errorMessage += ': ' + data.msg
|
||||
if (status === 401) {
|
||||
errorMessage = '认证失败,请重新登录';
|
||||
// 可以在这里跳转到登录页面
|
||||
// this.$router.push('/login')
|
||||
} else if (status === 403) {
|
||||
errorMessage = '权限不足,无法访问学生信息';
|
||||
} else if (status === 500) {
|
||||
errorMessage = '服务器内部错误';
|
||||
if (data && data.msg) {
|
||||
errorMessage += ': ' + data.msg;
|
||||
}
|
||||
} else if (data && data.msg) {
|
||||
errorMessage = data.msg;
|
||||
}
|
||||
} else if (data && data.msg) {
|
||||
errorMessage = data.msg
|
||||
} else if (error.request) {
|
||||
// 请求发送了但没有收到响应
|
||||
errorMessage = '网络连接失败,请检查网络状态';
|
||||
} else {
|
||||
// 请求配置错误
|
||||
errorMessage = '请求配置错误: ' + error.message;
|
||||
}
|
||||
} else if (error.request) {
|
||||
// 请求发送了但没有收到响应
|
||||
errorMessage = '网络连接失败,请检查网络状态'
|
||||
} else {
|
||||
// 请求配置错误
|
||||
errorMessage = '请求配置错误: ' + error.message
|
||||
}
|
||||
|
||||
this.$modal.msgError(errorMessage)
|
||||
this.loading = false
|
||||
})
|
||||
this.$modal.msgError(errorMessage);
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
/** 获取统计数据 */
|
||||
getStatistics() {
|
||||
getStatisticsOverview().then(response => {
|
||||
console.log('获取统计数据成功:', response)
|
||||
this.statistics = response.data
|
||||
}).catch(error => {
|
||||
console.error('获取统计数据失败:', error)
|
||||
this.$modal.msgError('获取统计数据失败')
|
||||
})
|
||||
getStatisticsOverview()
|
||||
.then((response) => {
|
||||
console.log('获取统计数据成功:', response);
|
||||
this.statistics = response.data;
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('获取统计数据失败:', error);
|
||||
this.$modal.msgError('获取统计数据失败');
|
||||
});
|
||||
},
|
||||
/** 获取学院列表 */
|
||||
getCollegeList() {
|
||||
console.log('开始获取学院列表')
|
||||
getCollegeOptions().then(response => {
|
||||
console.log('获取学院列表成功:', response)
|
||||
this.collegeOptions = response.data || []
|
||||
}).catch(error => {
|
||||
console.error('获取学院列表失败:', error)
|
||||
let errorMessage = '获取学院列表失败'
|
||||
if (error.response && error.response.data && error.response.data.msg) {
|
||||
errorMessage += ': ' + error.response.data.msg
|
||||
}
|
||||
this.$modal.msgError(errorMessage)
|
||||
})
|
||||
console.log('开始获取学院列表');
|
||||
getCollegeOptions()
|
||||
.then((response) => {
|
||||
console.log('获取学院列表成功:', response);
|
||||
this.collegeOptions = response.data || [];
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('获取学院列表失败:', error);
|
||||
let errorMessage = '获取学院列表失败';
|
||||
if (error.response && error.response.data && error.response.data.msg) {
|
||||
errorMessage += ': ' + error.response.data.msg;
|
||||
}
|
||||
this.$modal.msgError(errorMessage);
|
||||
});
|
||||
},
|
||||
/** 获取专业列表 */
|
||||
getMajorList(collegeId) {
|
||||
console.log('开始获取专业列表:', collegeId)
|
||||
getMajorOptions(collegeId).then(response => {
|
||||
console.log('获取专业列表成功:', response)
|
||||
this.majorOptions = response.data || []
|
||||
}).catch(error => {
|
||||
console.error('获取专业列表失败:', error)
|
||||
let errorMessage = '获取专业列表失败'
|
||||
if (error.response && error.response.data && error.response.data.msg) {
|
||||
errorMessage += ': ' + error.response.data.msg
|
||||
}
|
||||
this.$modal.msgError(errorMessage)
|
||||
})
|
||||
console.log('开始获取专业列表:', collegeId);
|
||||
getMajorOptions(collegeId)
|
||||
.then((response) => {
|
||||
console.log('获取专业列表成功:', response);
|
||||
this.majorOptions = response.data || [];
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('获取专业列表失败:', error);
|
||||
let errorMessage = '获取专业列表失败';
|
||||
if (error.response && error.response.data && error.response.data.msg) {
|
||||
errorMessage += ': ' + error.response.data.msg;
|
||||
}
|
||||
this.$modal.msgError(errorMessage);
|
||||
});
|
||||
},
|
||||
/** 获取班级列表 */
|
||||
getClassList(majorId) {
|
||||
console.log('开始获取班级列表:', majorId)
|
||||
getClassOptions(majorId).then(response => {
|
||||
console.log('获取班级列表成功:', response)
|
||||
this.classOptions = response.data || []
|
||||
}).catch(error => {
|
||||
console.error('获取班级列表失败:', error)
|
||||
let errorMessage = '获取班级列表失败'
|
||||
if (error.response && error.response.data && error.response.data.msg) {
|
||||
errorMessage += ': ' + error.response.data.msg
|
||||
}
|
||||
this.$modal.msgError(errorMessage)
|
||||
})
|
||||
console.log('开始获取班级列表:', majorId);
|
||||
getClassOptions(majorId)
|
||||
.then((response) => {
|
||||
console.log('获取班级列表成功:', response);
|
||||
this.classOptions = response.data || [];
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('获取班级列表失败:', error);
|
||||
let errorMessage = '获取班级列表失败';
|
||||
if (error.response && error.response.data && error.response.data.msg) {
|
||||
errorMessage += ': ' + error.response.data.msg;
|
||||
}
|
||||
this.$modal.msgError(errorMessage);
|
||||
});
|
||||
},
|
||||
/** 获取班主任选项 */
|
||||
getClassTeacherOptions() {
|
||||
console.log('开始获取班主任列表')
|
||||
getClassTeacherOptions().then(response => {
|
||||
console.log('获取班主任列表成功,原始响应:', JSON.stringify(response))
|
||||
console.log('开始获取班主任列表');
|
||||
getClassTeacherOptions()
|
||||
.then((response) => {
|
||||
console.log('获取班主任列表成功,原始响应:', JSON.stringify(response));
|
||||
|
||||
// 检查响应中的数据结构
|
||||
if (response.data && typeof response.data === 'object') {
|
||||
console.log('响应数据类型:', Object.prototype.toString.call(response.data))
|
||||
// 检查响应中的数据结构
|
||||
if (response.data && typeof response.data === 'object') {
|
||||
console.log('响应数据类型:', Object.prototype.toString.call(response.data));
|
||||
|
||||
if (response.data.code === 200) {
|
||||
// 标准成功响应
|
||||
console.log('标准成功响应,data字段:', JSON.stringify(response.data.data))
|
||||
this.classTeacherOptions = response.data.data || []
|
||||
} else if (Array.isArray(response.data)) {
|
||||
// 直接返回数组
|
||||
console.log('直接返回数组,长度:', response.data.length)
|
||||
console.log('数组第一个元素:', JSON.stringify(response.data[0]))
|
||||
this.classTeacherOptions = response.data
|
||||
} else if (response.data && Array.isArray(response.data.rows)) {
|
||||
// 分页响应格式
|
||||
console.log('分页响应格式,rows字段:', JSON.stringify(response.data.rows))
|
||||
this.classTeacherOptions = response.data.rows
|
||||
} else {
|
||||
// 其他格式,尝试直接使用
|
||||
console.log('其他格式,直接使用response.data')
|
||||
this.classTeacherOptions = response.data || []
|
||||
}
|
||||
} else {
|
||||
console.log('响应数据为空或不是对象类型')
|
||||
this.classTeacherOptions = []
|
||||
}
|
||||
|
||||
console.log('设置后的班主任选项:', JSON.stringify(this.classTeacherOptions))
|
||||
|
||||
// 检查每个选项的格式是否正确
|
||||
if (this.classTeacherOptions && this.classTeacherOptions.length > 0) {
|
||||
console.log('选项数量:', this.classTeacherOptions.length)
|
||||
this.classTeacherOptions.forEach((option, index) => {
|
||||
console.log(`选项[${index}]:`, JSON.stringify(option))
|
||||
if (!option.value || !option.label) {
|
||||
console.warn(`选项[${index}]格式不正确:`, option)
|
||||
if (response.data.code === 200) {
|
||||
// 标准成功响应
|
||||
console.log('标准成功响应,data字段:', JSON.stringify(response.data.data));
|
||||
this.classTeacherOptions = response.data.data || [];
|
||||
} else if (Array.isArray(response.data)) {
|
||||
// 直接返回数组
|
||||
console.log('直接返回数组,长度:', response.data.length);
|
||||
console.log('数组第一个元素:', JSON.stringify(response.data[0]));
|
||||
this.classTeacherOptions = response.data;
|
||||
} else if (response.data && Array.isArray(response.data.rows)) {
|
||||
// 分页响应格式
|
||||
console.log('分页响应格式,rows字段:', JSON.stringify(response.data.rows));
|
||||
this.classTeacherOptions = response.data.rows;
|
||||
} else {
|
||||
// 其他格式,尝试直接使用
|
||||
console.log('其他格式,直接使用response.data');
|
||||
this.classTeacherOptions = response.data || [];
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 如果没有获取到数据,给出提示
|
||||
if (!this.classTeacherOptions.length) {
|
||||
this.$modal.msgWarning('未找到班主任数据,请确认数据库中是否存在班主任角色(head_teacher)的用户')
|
||||
}
|
||||
}).catch(error => {
|
||||
console.error('获取班主任列表失败,错误详情:', error)
|
||||
|
||||
// 提供更详细的错误信息
|
||||
let errorMessage = '获取班主任列表失败'
|
||||
if (error.response) {
|
||||
const status = error.response.status
|
||||
const data = error.response.data
|
||||
|
||||
if (status === 401) {
|
||||
console.error('认证失败(401),Token可能已过期,尝试重新登录')
|
||||
errorMessage = '认证失败,请重新登录'
|
||||
// 可以在这里添加重新登录的逻辑
|
||||
// this.$store.dispatch('LogOut').then(() => {
|
||||
// this.$router.push('/login')
|
||||
// })
|
||||
} else if (status === 403) {
|
||||
console.error('权限不足(403),无法访问班主任数据')
|
||||
errorMessage = '权限不足,无法获取班主任列表'
|
||||
} else if (data && data.msg) {
|
||||
errorMessage = errorMessage + ': ' + data.msg
|
||||
} else {
|
||||
console.log('响应数据为空或不是对象类型');
|
||||
this.classTeacherOptions = [];
|
||||
}
|
||||
}
|
||||
|
||||
this.$modal.msgError(errorMessage)
|
||||
})
|
||||
console.log('设置后的班主任选项:', JSON.stringify(this.classTeacherOptions));
|
||||
|
||||
// 检查每个选项的格式是否正确
|
||||
if (this.classTeacherOptions && this.classTeacherOptions.length > 0) {
|
||||
console.log('选项数量:', this.classTeacherOptions.length);
|
||||
this.classTeacherOptions.forEach((option, index) => {
|
||||
console.log(`选项[${index}]:`, JSON.stringify(option));
|
||||
if (!option.value || !option.label) {
|
||||
console.warn(`选项[${index}]格式不正确:`, option);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 如果没有获取到数据,给出提示
|
||||
if (!this.classTeacherOptions.length) {
|
||||
this.$modal.msgWarning('未找到班主任数据,请确认数据库中是否存在班主任角色(head_teacher)的用户');
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('获取班主任列表失败,错误详情:', error);
|
||||
|
||||
// 提供更详细的错误信息
|
||||
let errorMessage = '获取班主任列表失败';
|
||||
if (error.response) {
|
||||
const status = error.response.status;
|
||||
const data = error.response.data;
|
||||
|
||||
if (status === 401) {
|
||||
console.error('认证失败(401),Token可能已过期,尝试重新登录');
|
||||
errorMessage = '认证失败,请重新登录';
|
||||
// 可以在这里添加重新登录的逻辑
|
||||
// this.$store.dispatch('LogOut').then(() => {
|
||||
// this.$router.push('/login')
|
||||
// })
|
||||
} else if (status === 403) {
|
||||
console.error('权限不足(403),无法访问班主任数据');
|
||||
errorMessage = '权限不足,无法获取班主任列表';
|
||||
} else if (data && data.msg) {
|
||||
errorMessage = errorMessage + ': ' + data.msg;
|
||||
}
|
||||
}
|
||||
|
||||
this.$modal.msgError(errorMessage);
|
||||
});
|
||||
},
|
||||
/** 学院变化处理 */
|
||||
handleCollegeChange(collegeId) {
|
||||
console.log('学院变化,清空专业和班级选择:', collegeId)
|
||||
this.queryParams.majorCode = null
|
||||
this.queryParams.classCode = null
|
||||
this.majorOptions = []
|
||||
this.classOptions = []
|
||||
this.queryParams.classTeacherId = null // 清空班主任选择
|
||||
console.log('学院变化,清空专业和班级选择:', collegeId);
|
||||
this.queryParams.majorCode = null;
|
||||
this.queryParams.classCode = null;
|
||||
this.majorOptions = [];
|
||||
this.classOptions = [];
|
||||
this.queryParams.classTeacherId = null; // 清空班主任选择
|
||||
|
||||
if (collegeId) {
|
||||
console.log('学院变化,获取专业列表:', collegeId)
|
||||
console.log('学院变化,获取专业列表:', collegeId);
|
||||
// 根据学院ID获取专业列表
|
||||
this.getMajorList(collegeId)
|
||||
this.getMajorList(collegeId);
|
||||
}
|
||||
},
|
||||
/** 专业变化处理 */
|
||||
handleMajorChange(majorId) {
|
||||
console.log('专业变化,清空班级选择:', majorId)
|
||||
this.queryParams.classCode = null
|
||||
this.classOptions = []
|
||||
this.queryParams.classTeacherId = null // 清空班主任选择
|
||||
console.log('专业变化,清空班级选择:', majorId);
|
||||
this.queryParams.classCode = null;
|
||||
this.classOptions = [];
|
||||
this.queryParams.classTeacherId = null; // 清空班主任选择
|
||||
|
||||
if (majorId) {
|
||||
console.log('专业变化,获取班级列表:', majorId)
|
||||
console.log('专业变化,获取班级列表:', majorId);
|
||||
// 根据专业ID获取班级列表
|
||||
this.getClassList(majorId)
|
||||
this.getClassList(majorId);
|
||||
}
|
||||
},
|
||||
/** 搜索按钮操作 */
|
||||
handleQuery() {
|
||||
this.queryParams.pageNum = 1
|
||||
this.getList()
|
||||
this.queryParams.pageNum = 1;
|
||||
this.getList();
|
||||
},
|
||||
/** 重置按钮操作 */
|
||||
resetQuery() {
|
||||
this.$refs.queryRef?.resetFields()
|
||||
this.$refs.queryRef?.resetFields();
|
||||
this.queryParams = {
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
@@ -713,88 +602,96 @@ export default {
|
||||
studentStatus: null,
|
||||
infoStatus: null,
|
||||
classTeacherId: null
|
||||
}
|
||||
};
|
||||
// 重置级联选择器
|
||||
this.majorOptions = []
|
||||
this.classOptions = []
|
||||
this.classTeacherOptions = [] // 重置班主任选项
|
||||
this.handleQuery()
|
||||
this.majorOptions = [];
|
||||
this.classOptions = [];
|
||||
this.classTeacherOptions = []; // 重置班主任选项
|
||||
this.handleQuery();
|
||||
},
|
||||
/** 多选框选中数据 */
|
||||
handleSelectionChange(selection) {
|
||||
this.ids = selection.map(item => item.id)
|
||||
this.selectedIds = selection.map(item => item.id)
|
||||
this.selectedRows = selection
|
||||
this.single = selection.length !== 1
|
||||
this.multiple = !selection.length
|
||||
this.ids = selection.map((item) => item.id);
|
||||
this.selectedIds = selection.map((item) => item.id);
|
||||
this.selectedRows = selection;
|
||||
this.single = selection.length !== 1;
|
||||
this.multiple = !selection.length;
|
||||
},
|
||||
/** 新增按钮操作 */
|
||||
handleAdd() {
|
||||
this.selectedStudentId = null
|
||||
this.isEdit = false
|
||||
this.editVisible = true
|
||||
this.selectedStudentId = null;
|
||||
this.isEdit = false;
|
||||
this.editVisible = true;
|
||||
},
|
||||
/** 修改按钮操作 */
|
||||
handleUpdate(row) {
|
||||
const id = row.id || this.ids[0]
|
||||
this.selectedStudentId = id
|
||||
this.isEdit = true
|
||||
this.editVisible = true
|
||||
const id = row.id || this.ids[0];
|
||||
this.selectedStudentId = id;
|
||||
this.isEdit = true;
|
||||
this.editVisible = true;
|
||||
},
|
||||
/** 查看详情 */
|
||||
handleDetail(row) {
|
||||
this.selectedStudentId = row.id
|
||||
this.detailVisible = true
|
||||
this.selectedStudentId = row.id;
|
||||
this.detailVisible = true;
|
||||
},
|
||||
/** 删除按钮操作 */
|
||||
handleDelete(row) {
|
||||
const ids = row.id ? [row.id] : this.ids
|
||||
this.$modal.confirm('是否确认删除学生编号为"' + ids + '"的数据项?').then(function() {
|
||||
return delStudent(ids)
|
||||
}).then(() => {
|
||||
this.getList()
|
||||
this.$modal.msgSuccess('删除成功')
|
||||
}).catch(() => {})
|
||||
const ids = row.id ? [row.id] : this.ids;
|
||||
this.$modal
|
||||
.confirm('是否确认删除学生编号为"' + ids + '"的数据项?')
|
||||
.then(function () {
|
||||
return delStudent(ids);
|
||||
})
|
||||
.then(() => {
|
||||
this.getList();
|
||||
this.$modal.msgSuccess('删除成功');
|
||||
})
|
||||
.catch(() => {});
|
||||
},
|
||||
/** 导出按钮操作 */
|
||||
handleExport() {
|
||||
this.$modal.confirm('是否确认导出所有学生数据项?').then(() => {
|
||||
this.loading = true
|
||||
return exportStudents(this.queryParams)
|
||||
}).then(response => {
|
||||
this.$download.excel(response, '学生信息.xlsx')
|
||||
this.loading = false
|
||||
}).catch(() => {
|
||||
this.loading = false
|
||||
})
|
||||
this.$modal
|
||||
.confirm('是否确认导出所有学生数据项?')
|
||||
.then(() => {
|
||||
this.loading = true;
|
||||
return exportStudents(this.queryParams);
|
||||
})
|
||||
.then((response) => {
|
||||
this.$download.excel(response, '学生信息.xlsx');
|
||||
this.loading = false;
|
||||
})
|
||||
.catch(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
/** 导入按钮操作 */
|
||||
handleImport() {
|
||||
this.importVisible = true
|
||||
this.importVisible = true;
|
||||
},
|
||||
/** 审核操作 */
|
||||
handleAudit(row) {
|
||||
this.selectedStudentIds = [row.id]
|
||||
this.isBatchAudit = false
|
||||
this.auditVisible = true
|
||||
this.selectedStudentIds = [row.id];
|
||||
this.isBatchAudit = false;
|
||||
this.auditVisible = true;
|
||||
},
|
||||
/** 批量审核 */
|
||||
handleBatchAudit() {
|
||||
this.selectedStudentIds = this.selectedIds
|
||||
this.isBatchAudit = true
|
||||
this.auditVisible = true
|
||||
this.selectedStudentIds = this.selectedIds;
|
||||
this.isBatchAudit = true;
|
||||
this.auditVisible = true;
|
||||
},
|
||||
/** 批量操作 */
|
||||
handleBatchOperation() {
|
||||
if (this.selectedIds.length === 0) {
|
||||
this.$modal.msgError('请选择要操作的数据')
|
||||
return
|
||||
this.$modal.msgError('请选择要操作的数据');
|
||||
return;
|
||||
}
|
||||
this.batchOperationVisible = true
|
||||
this.batchOperationVisible = true;
|
||||
},
|
||||
/** 显示统计图表 */
|
||||
handleStatistics() {
|
||||
this.statisticsVisible = true
|
||||
this.statisticsVisible = true;
|
||||
},
|
||||
/** 获取学籍状态类型 */
|
||||
getStatusType(status) {
|
||||
@@ -804,8 +701,8 @@ export default {
|
||||
'3': 'info',
|
||||
'4': 'danger',
|
||||
'5': 'primary'
|
||||
}
|
||||
return statusMap[status] || 'info'
|
||||
};
|
||||
return statusMap[status] || 'info';
|
||||
},
|
||||
/** 获取学籍状态文本 */
|
||||
getStatusText(status) {
|
||||
@@ -815,8 +712,8 @@ export default {
|
||||
'3': '复学',
|
||||
'4': '退学',
|
||||
'5': '毕业'
|
||||
}
|
||||
return statusMap[status] || '未知'
|
||||
};
|
||||
return statusMap[status] || '未知';
|
||||
},
|
||||
/** 获取信息状态类型 */
|
||||
getInfoStatusType(status) {
|
||||
@@ -826,8 +723,8 @@ export default {
|
||||
'3': 'info',
|
||||
'4': 'danger',
|
||||
'5': 'info'
|
||||
}
|
||||
return statusMap[status] || 'info'
|
||||
};
|
||||
return statusMap[status] || 'info';
|
||||
},
|
||||
/** 获取信息状态文本 */
|
||||
getInfoStatusText(status) {
|
||||
@@ -837,18 +734,18 @@ export default {
|
||||
'3': '审核中',
|
||||
'4': '已驳回',
|
||||
'5': '已锁定'
|
||||
}
|
||||
return statusMap[status] || '未知'
|
||||
};
|
||||
return statusMap[status] || '未知';
|
||||
},
|
||||
/** 获取完整度颜色 */
|
||||
getCompletenessColor(rate) {
|
||||
if (rate >= 90) return '#67C23A'
|
||||
if (rate >= 70) return '#E6A23C'
|
||||
if (rate >= 50) return '#F56C6C'
|
||||
return '#909399'
|
||||
if (rate >= 90) return '#67C23A';
|
||||
if (rate >= 70) return '#E6A23C';
|
||||
if (rate >= 50) return '#F56C6C';
|
||||
return '#909399';
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -334,8 +334,7 @@
|
||||
<el-col :span="8">
|
||||
<el-form-item label="是否独生子女" prop="isSingleChild">
|
||||
<el-select v-model="form.isSingleChild" placeholder="请选择" disabled>
|
||||
<el-option label="否" :value="0" />
|
||||
<el-option label="是" :value="1" />
|
||||
<el-option v-for="dict in sys_yesno" :key="dict.value" :label="dict.label" :value="dict.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
@@ -892,7 +891,7 @@ const getOperateContent = (record: any) => {
|
||||
const infoStatus = record.infoStatus;
|
||||
// console.log(infoStatus === '3');
|
||||
if (infoStatus === '3') {
|
||||
return '信息填写提交(待提交)';
|
||||
return '信息填写提交(待审核)';
|
||||
} else if (infoStatus === '4' || infoStatus === '5') {
|
||||
return record.remark || '无备注信息';
|
||||
} else {
|
||||
@@ -939,7 +938,7 @@ const getRegionTree = async () => {
|
||||
/** 获取宿舍楼栋树形数据 */
|
||||
const getDormBuildingTree = async () => {
|
||||
try {
|
||||
const res = await request.get('/xgss/dormBuildingTree/list');
|
||||
const res = await request.get('/xgss/dormBuildingTree/listAll');
|
||||
if (res && res.data) {
|
||||
// 处理数据,为楼栋名称添加性别标识
|
||||
const processTreeData = (nodes: any[]): any[] => {
|
||||
|
||||
@@ -947,7 +947,7 @@ const getRegionTree = async () => {
|
||||
/** 获取宿舍楼栋树形数据 */
|
||||
const getDormBuildingTree = async () => {
|
||||
try {
|
||||
const res = await request.get('/xgss/dormBuildingTree/list');
|
||||
const res = await request.get('/xgss/dormBuildingTree/listAll');
|
||||
if (res && res.data) {
|
||||
// 处理数据,为楼栋名称添加性别标识
|
||||
const processTreeData = (nodes: any[]): any[] => {
|
||||
|
||||
@@ -975,7 +975,7 @@ const getRegionTree = async () => {
|
||||
/** 获取宿舍楼栋树形数据 */
|
||||
const getDormBuildingTree = async () => {
|
||||
try {
|
||||
const res = await request.get('/xgss/dormBuildingTree/list');
|
||||
const res = await request.get('/xgss/dormBuildingTree/listAll');
|
||||
if (res && res.data) {
|
||||
// 处理数据,为楼栋名称添加性别标识
|
||||
const processTreeData = (nodes: any[]): any[] => {
|
||||
|
||||
@@ -21,6 +21,7 @@ web/src/views/xg/xsjcxx/stuRoster/
|
||||
### 1. 主界面 (index.vue)
|
||||
|
||||
#### 布局设计
|
||||
|
||||
- **左右布局模式**:左侧固定宽高的部门树,右侧内容区域
|
||||
- **部门树功能**:
|
||||
- 支持纵向和横向滚动条
|
||||
@@ -29,17 +30,20 @@ web/src/views/xg/xsjcxx/stuRoster/
|
||||
- 清空选择功能
|
||||
|
||||
#### 检索功能
|
||||
|
||||
- **基础检索**:学号、学生姓名、手机号码、状态四个检索条件
|
||||
- **高级检索**:弹出专门弹窗配置检索条件
|
||||
- **检索按钮**:搜索、重置、高级检索
|
||||
|
||||
#### 数据展示
|
||||
|
||||
- **双视图模式**:列表视图和卡片视图
|
||||
- **列表视图**:标准表格展示,支持列显示控制
|
||||
- **卡片视图**:卡片式布局,每行4个卡片
|
||||
- **分页功能**:支持分页显示
|
||||
|
||||
#### 操作功能
|
||||
|
||||
- **导出数据**:支持数据导出功能
|
||||
- **查看详情**:查看学生完整信息
|
||||
- **列控制**:通过right-toolbar控制列显示
|
||||
@@ -47,19 +51,23 @@ web/src/views/xg/xsjcxx/stuRoster/
|
||||
### 2. 高级检索弹窗 (AdvancedSearchDialog.vue)
|
||||
|
||||
#### 检索字段覆盖
|
||||
|
||||
支持 `xg_student_msg` 表中所有字段的检索功能:
|
||||
|
||||
**基础信息**
|
||||
|
||||
- 身份证号、英文姓名、曾用名、性别
|
||||
- 出生日期(时间范围)、民族、籍贯、政治面貌
|
||||
- 户籍所在地、家庭地址、现住址、邮政编码
|
||||
- 电子邮箱、QQ号码、微信号、婚姻状况
|
||||
|
||||
**学籍信息**
|
||||
|
||||
- 入学年份、入学日期(时间范围)、录取类别
|
||||
- 学制、学历层次、培养方式、年级、户口类型
|
||||
|
||||
**家庭信息**
|
||||
|
||||
- 父亲信息:姓名、电话、工作单位
|
||||
- 母亲信息:姓名、电话、工作单位
|
||||
- 监护人信息:姓名、电话、关系
|
||||
@@ -67,19 +75,23 @@ web/src/views/xg/xsjcxx/stuRoster/
|
||||
- 紧急联系人信息
|
||||
|
||||
**特殊标识**
|
||||
|
||||
- 贫困生、少数民族、残疾、孤儿、留守儿童、独生子女、低保、助学贷款等标识
|
||||
- 宗教信仰、生源地区
|
||||
|
||||
**健康信息**
|
||||
|
||||
- 身高、体重(数值范围)
|
||||
- 血型、健康状况、特殊体质、病史、过敏史
|
||||
- 残疾类型、残疾等级
|
||||
- 特长爱好、获奖情况、违纪记录、社会活动
|
||||
|
||||
**时间范围**
|
||||
|
||||
- 创建时间、更新时间(时间范围)
|
||||
|
||||
#### 检索特性
|
||||
|
||||
- **文本检索**:采用like模式
|
||||
- **下拉选择**:支持多选
|
||||
- **时间范围**:支持开始时间到结束时间的跨度检索
|
||||
@@ -89,9 +101,11 @@ web/src/views/xg/xsjcxx/stuRoster/
|
||||
### 3. 学生详情弹窗 (StudentDetailDialog.vue)
|
||||
|
||||
#### Tab页设计
|
||||
|
||||
参考 `stuMsgDetailDialog.vue` 结构,分为5个Tab页:
|
||||
|
||||
**基础信息**
|
||||
|
||||
- 学生照片和基本信息布局
|
||||
- 学号、身份证号、姓名、英文姓名、曾用名、性别
|
||||
- 出生日期、民族、籍贯、政治面貌
|
||||
@@ -100,12 +114,14 @@ web/src/views/xg/xsjcxx/stuRoster/
|
||||
- 生源地区、毕业院校、户口类型
|
||||
|
||||
**学籍信息**
|
||||
|
||||
- 学籍状态、入学年份、入学日期、录取类别
|
||||
- 学制、学历层次、培养方式、年级、是否住校
|
||||
- 学院、专业、班级、辅导员、班主任
|
||||
- 宿舍楼栋、宿舍房间号、银行卡号、开户银行
|
||||
|
||||
**家庭信息**
|
||||
|
||||
- 父亲信息:姓名、电话、工作单位
|
||||
- 母亲信息:姓名、电话、工作单位
|
||||
- 监护人信息:姓名、电话、关系
|
||||
@@ -113,15 +129,18 @@ web/src/views/xg/xsjcxx/stuRoster/
|
||||
- 紧急联系人信息
|
||||
|
||||
**特殊标识**
|
||||
|
||||
- 各种特殊标识的状态展示(使用Tag标签)
|
||||
- 助学贷款金额、残疾类型、残疾等级
|
||||
|
||||
**健康信息**
|
||||
|
||||
- 身高、体重、血型
|
||||
- 健康状况、特殊体质或疾病、病史、过敏史
|
||||
- 特长爱好、获奖情况、违纪记录、社会活动
|
||||
|
||||
#### 展示特性
|
||||
|
||||
- **只读模式**:所有字段都是禁用状态,仅用于查看
|
||||
- **状态标签**:使用不同颜色的Tag标签展示状态
|
||||
- **照片展示**:学生照片展示
|
||||
@@ -130,6 +149,7 @@ web/src/views/xg/xsjcxx/stuRoster/
|
||||
### 4. 测试页面 (test.vue)
|
||||
|
||||
#### 测试功能
|
||||
|
||||
- **基础功能测试**:查询条件输入和查询功能
|
||||
- **模拟数据展示**:表格形式展示模拟数据
|
||||
- **弹窗测试**:高级检索和详情弹窗的测试
|
||||
@@ -138,7 +158,9 @@ web/src/views/xg/xsjcxx/stuRoster/
|
||||
## 技术实现
|
||||
|
||||
### 数据模型
|
||||
|
||||
基于 `xg_student_msg` 表结构,包含:
|
||||
|
||||
- 基础信息字段(学号、姓名、性别等)
|
||||
- 学籍信息字段(状态、入学年份等)
|
||||
- 家庭信息字段(父母信息、经济状况等)
|
||||
@@ -146,19 +168,23 @@ web/src/views/xg/xsjcxx/stuRoster/
|
||||
- 健康信息字段(身高、体重、病史等)
|
||||
|
||||
### 组件设计
|
||||
|
||||
- **主界面组件**:负责整体布局和主要功能
|
||||
- **高级检索组件**:独立的弹窗组件,负责复杂检索
|
||||
- **详情展示组件**:独立的弹窗组件,负责信息展示
|
||||
- **测试组件**:用于功能测试和验证
|
||||
|
||||
### 状态管理
|
||||
|
||||
- **查询参数**:基础查询参数和高级查询参数
|
||||
- **视图状态**:列表视图和卡片视图切换
|
||||
- **弹窗状态**:各种弹窗的显示控制
|
||||
- **选择状态**:部门树的多选状态
|
||||
|
||||
### 模拟数据
|
||||
|
||||
目前使用模拟数据进行展示,包含:
|
||||
|
||||
- 3个示例学生数据
|
||||
- 完整的字段信息
|
||||
- 不同的状态值
|
||||
@@ -166,20 +192,24 @@ web/src/views/xg/xsjcxx/stuRoster/
|
||||
## 待完善功能
|
||||
|
||||
### API对接
|
||||
|
||||
- 需要对接真实的学生信息API
|
||||
- 实现数据的增删改查功能
|
||||
- 实现导出功能
|
||||
|
||||
### 权限控制
|
||||
|
||||
- 根据用户权限控制功能访问
|
||||
- 实现数据权限过滤
|
||||
|
||||
### 性能优化
|
||||
|
||||
- 大数据量时的分页优化
|
||||
- 检索性能优化
|
||||
- 组件懒加载
|
||||
|
||||
### 用户体验
|
||||
|
||||
- 更多的交互动画
|
||||
- 响应式布局优化
|
||||
- 移动端适配
|
||||
@@ -199,4 +229,4 @@ web/src/views/xg/xsjcxx/stuRoster/
|
||||
- 遵循Vue 3 Composition API规范
|
||||
- 使用TypeScript进行类型检查
|
||||
- 遵循Element Plus设计规范
|
||||
- 组件化开发,保持代码可维护性
|
||||
- 组件化开发,保持代码可维护性
|
||||
@@ -54,13 +54,7 @@
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="出生日期" prop="birthDate">
|
||||
<el-date-picker
|
||||
v-model="form.birthDate"
|
||||
type="date"
|
||||
placeholder="请选择出生日期"
|
||||
value-format="YYYY-MM-DD"
|
||||
disabled
|
||||
/>
|
||||
<el-date-picker v-model="form.birthDate" type="date" placeholder="请选择出生日期" value-format="YYYY-MM-DD" disabled />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
@@ -197,13 +191,7 @@
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="入学日期" prop="enrollmentDate">
|
||||
<el-date-picker
|
||||
v-model="form.enrollmentDate"
|
||||
type="date"
|
||||
placeholder="请选择入学日期"
|
||||
value-format="YYYY-MM-DD"
|
||||
disabled
|
||||
/>
|
||||
<el-date-picker v-model="form.enrollmentDate" type="date" placeholder="请选择入学日期" value-format="YYYY-MM-DD" disabled />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
@@ -837,11 +825,10 @@ const loadStudentData = async (studentId: number) => {
|
||||
dialogLoading.value = true;
|
||||
try {
|
||||
// 模拟API调用
|
||||
await new Promise(resolve => setTimeout(resolve, 500));
|
||||
await new Promise((resolve) => setTimeout(resolve, 500));
|
||||
|
||||
// 使用模拟数据
|
||||
Object.assign(form, mockStudentData);
|
||||
|
||||
} catch (error) {
|
||||
console.error('加载学生数据失败:', error);
|
||||
} finally {
|
||||
@@ -850,18 +837,24 @@ const loadStudentData = async (studentId: number) => {
|
||||
};
|
||||
|
||||
// 监听studentId变化
|
||||
watch(() => props.studentId, (newId) => {
|
||||
if (newId && dialogVisible.value) {
|
||||
loadStudentData(newId);
|
||||
watch(
|
||||
() => props.studentId,
|
||||
(newId) => {
|
||||
if (newId && dialogVisible.value) {
|
||||
loadStudentData(newId);
|
||||
}
|
||||
}
|
||||
});
|
||||
);
|
||||
|
||||
// 监听弹窗显示状态
|
||||
watch(() => props.visible, (visible) => {
|
||||
if (visible && props.studentId) {
|
||||
loadStudentData(props.studentId);
|
||||
watch(
|
||||
() => props.visible,
|
||||
(visible) => {
|
||||
if (visible && props.studentId) {
|
||||
loadStudentData(props.studentId);
|
||||
}
|
||||
}
|
||||
});
|
||||
);
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -243,7 +243,7 @@ import ApprovalButton from '@/components/Process/approvalButton.vue';
|
||||
import { AxiosResponse } from 'axios';
|
||||
import { StartProcessBo } from '@/api/workflow/workflowCommon/types';
|
||||
import { addStudentLeave, getStudentLeave, updateStudentLeave } from '@/api/workflow/studentLeave/studentLeave';
|
||||
import { getStudentMsg } from '@/api/xg/xsjcxx/studentMsg/studentMsg';
|
||||
import { getStudentMsg, getClassTeacherId, getCollegeLeaveApprovers } from '@/api/xg/xsjcxx/studentMsg/studentMsg';
|
||||
import { startWorkFlow } from '@/api/workflow/task';
|
||||
import { StudentLeaveForm, StudentLeaveVO } from '@/api/workflow/studentLeave/studentLeave/types';
|
||||
import { StudentMsgVO } from '@/api/xg/xsjcxx/studentMsg/studentMsg/types';
|
||||
@@ -386,31 +386,31 @@ const flowCodeOptions = [
|
||||
{
|
||||
value: 'hzdyjs-qj',
|
||||
label: '学生请假流程-杭州第一技师'
|
||||
},
|
||||
{
|
||||
value: 'leave1',
|
||||
label: '请假申请-普通'
|
||||
},
|
||||
{
|
||||
value: 'leave2',
|
||||
label: '请假申请-排他网关'
|
||||
},
|
||||
{
|
||||
value: 'leave3',
|
||||
label: '请假申请-并行网关'
|
||||
},
|
||||
{
|
||||
value: 'leave4',
|
||||
label: '请假申请-会签'
|
||||
},
|
||||
{
|
||||
value: 'leave5',
|
||||
label: '请假申请-并行会签网关'
|
||||
},
|
||||
{
|
||||
value: 'leave6',
|
||||
label: '请假申请-排他并行会签'
|
||||
}
|
||||
// {
|
||||
// value: 'leave1',
|
||||
// label: '请假申请-普通'
|
||||
// },
|
||||
// {
|
||||
// value: 'leave2',
|
||||
// label: '请假申请-排他网关'
|
||||
// },
|
||||
// {
|
||||
// value: 'leave3',
|
||||
// label: '请假申请-并行网关'
|
||||
// },
|
||||
// {
|
||||
// value: 'leave4',
|
||||
// label: '请假申请-会签'
|
||||
// },
|
||||
// {
|
||||
// value: 'leave5',
|
||||
// label: '请假申请-并行会签网关'
|
||||
// },
|
||||
// {
|
||||
// value: 'leave6',
|
||||
// label: '请假申请-排他并行会签'
|
||||
// }
|
||||
];
|
||||
|
||||
// 上传相关配置
|
||||
@@ -623,73 +623,75 @@ const handleLeaveTypeChange = (value: string) => {
|
||||
form.leaveTypeCode = leaveType.leaveTypeCode;
|
||||
|
||||
// 1. 处理销假状态逻辑
|
||||
if (leaveType.needCancelForNonLeave === '0') {
|
||||
// 当 need_cancel_for_non_leave 为 0-否 时
|
||||
if (form.isLeaveSchool === '0') {
|
||||
// 当 isLeaveSchool = 0 时,cancelStatus = 3 (无需销假)
|
||||
form.cancelStatus = '3';
|
||||
form.needCancel = '0';
|
||||
} else {
|
||||
// 其他时候,cancelStatus = 0 (未销假)
|
||||
form.cancelStatus = '0';
|
||||
form.needCancel = '1';
|
||||
}
|
||||
} else {
|
||||
// 当 need_cancel_for_non_leave 为 1-是 时,保持原有逻辑
|
||||
form.cancelStatus = '0';
|
||||
form.needCancel = '1';
|
||||
}
|
||||
// if (leaveType.needCancelForNonLeave === '0') {
|
||||
// // 当 need_cancel_for_non_leave 为 0-否 时
|
||||
// if (form.isLeaveSchool === '0') {
|
||||
// // 当 isLeaveSchool = 0 时,cancelStatus = 3 (无需销假)
|
||||
// form.cancelStatus = '3';
|
||||
// form.needCancel = '0';
|
||||
// } else {
|
||||
// // 其他时候,cancelStatus = 0 (未销假)
|
||||
// form.cancelStatus = '0';
|
||||
// form.needCancel = '1';
|
||||
// }
|
||||
// } else {
|
||||
// // 当 need_cancel_for_non_leave 为 1-是 时,保持原有逻辑
|
||||
// form.cancelStatus = '0';
|
||||
// form.needCancel = '1';
|
||||
// }
|
||||
|
||||
// 2. 处理是否可以提前请假的逻辑
|
||||
if (leaveType.canApplyInAdvance === '0') {
|
||||
// 不可以提前请假,开始时间不能选择今天之后的日期(只能选择今天之前的日期)
|
||||
// 这里我们通过日期选择器的 disabledDate 来控制
|
||||
// 在模板中已经设置了相应的限制
|
||||
}
|
||||
// if (leaveType.canApplyInAdvance === '0') {
|
||||
// // 不可以提前请假,开始时间不能选择今天之后的日期(只能选择今天之前的日期)
|
||||
// // 这里我们通过日期选择器的 disabledDate 来控制
|
||||
// // 在模板中已经设置了相应的限制
|
||||
// }
|
||||
|
||||
// 3. 处理最大申请时长的逻辑
|
||||
if (leaveType.maxApplyDuration && leaveType.maxApplyDuration > 0) {
|
||||
// 有最大申请时长限制,通过日期选择器的 disabledDate 来控制
|
||||
// 在模板中已经设置了相应的限制
|
||||
}
|
||||
// if (leaveType.maxApplyDuration && leaveType.maxApplyDuration > 0) {
|
||||
// // 有最大申请时长限制,通过日期选择器的 disabledDate 来控制
|
||||
// // 在模板中已经设置了相应的限制
|
||||
// }
|
||||
}
|
||||
} else {
|
||||
// 清空选择时,同时清空类型编码和相关状态
|
||||
form.leaveTypeCode = '';
|
||||
form.cancelStatus = '0';
|
||||
form.needCancel = '1';
|
||||
}
|
||||
};
|
||||
|
||||
// 开始时间禁用逻辑
|
||||
const disabledStartDate = (time: Date) => {
|
||||
if (!currentLeaveType.value) return false;
|
||||
// if (!currentLeaveType.value) return false;
|
||||
|
||||
const today = new Date();
|
||||
today.setHours(0, 0, 0, 0);
|
||||
// const today = new Date();
|
||||
// today.setHours(0, 0, 0, 0);
|
||||
|
||||
// 处理是否可以提前请假的逻辑
|
||||
if (currentLeaveType.value.canApplyInAdvance === '0') {
|
||||
// 不可以提前请假,开始时间不能选择今天之后的日期(只能选择今天之前的日期)
|
||||
if (time.getTime() > today.getTime()) {
|
||||
return true; // 禁用今天之后的日期
|
||||
}
|
||||
}
|
||||
// // 处理是否可以提前请假的逻辑
|
||||
// if (currentLeaveType.value.canApplyInAdvance === '0') {
|
||||
// // 不可以提前请假,开始时间不能选择今天之后的日期(只能选择今天之前的日期)
|
||||
// if (time.getTime() > today.getTime()) {
|
||||
// return true; // 禁用今天之后的日期
|
||||
// }
|
||||
// }
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
// 结束时间禁用逻辑
|
||||
const disabledEndDate = (time: Date) => {
|
||||
if (!currentLeaveType.value) return false;
|
||||
// if (!currentLeaveType.value) return false;
|
||||
|
||||
// 结束时间不受 can_apply_in_advance 限制,可以选择任何日期
|
||||
// 但需要确保结束时间不能早于开始时间
|
||||
if (form.startTime) {
|
||||
const startDate = new Date(form.startTime);
|
||||
if (time.getTime() <= startDate.getTime()) {
|
||||
return true; // 禁用早于或等于开始时间的日期
|
||||
}
|
||||
}
|
||||
// // 结束时间不受 can_apply_in_advance 限制,可以选择任何日期
|
||||
// // 但需要确保结束时间不能早于开始时间
|
||||
// if (form.startTime) {
|
||||
// const startDate = new Date(form.startTime);
|
||||
// // 修改逻辑:允许选择与开始时间同一天及之后的日期
|
||||
// if (time.getTime() < startDate.getTime()) {
|
||||
// return true; // 只禁用早于开始时间的日期,允许同一天
|
||||
// }
|
||||
// }
|
||||
|
||||
return false;
|
||||
};
|
||||
@@ -705,17 +707,17 @@ const calculateLeaveDuration = () => {
|
||||
const totalDays = diffInMilliseconds / (1000 * 60 * 60 * 24);
|
||||
|
||||
// 处理最大申请时长的逻辑
|
||||
if (currentLeaveType.value && currentLeaveType.value.maxApplyDuration && currentLeaveType.value.maxApplyDuration > 0) {
|
||||
console.log('totalDays', totalDays);
|
||||
console.log('currentLeaveType.value.maxApplyDuration', currentLeaveType.value.maxApplyDuration);
|
||||
if (totalDays > currentLeaveType.value.maxApplyDuration) {
|
||||
ElMessage.warning(`请假时长不能超过${currentLeaveType.value.maxApplyDuration}天`);
|
||||
// 清空结束时间
|
||||
form.endTime = '';
|
||||
form.leaveDuration = '';
|
||||
return;
|
||||
}
|
||||
}
|
||||
// if (currentLeaveType.value && currentLeaveType.value.maxApplyDuration && currentLeaveType.value.maxApplyDuration > 0) {
|
||||
// console.log('totalDays', totalDays);
|
||||
// console.log('currentLeaveType.value.maxApplyDuration', currentLeaveType.value.maxApplyDuration);
|
||||
// if (totalDays > currentLeaveType.value.maxApplyDuration) {
|
||||
// ElMessage.warning(`请假时长不能超过${currentLeaveType.value.maxApplyDuration}天`);
|
||||
// // 清空结束时间
|
||||
// form.endTime = '';
|
||||
// form.leaveDuration = '';
|
||||
// return;
|
||||
// }
|
||||
// }
|
||||
|
||||
// 格式化请假时长显示,保留2位小数
|
||||
form.leaveDuration = `${totalDays.toFixed(2)}`;
|
||||
@@ -766,6 +768,42 @@ const submitFlow = async () => {
|
||||
// 启动工作流
|
||||
const handleStartWorkFlow = async (data: StudentLeaveForm) => {
|
||||
try {
|
||||
// 1. 调用接口获取班主任ID
|
||||
let classTeacherId: number | null = null;
|
||||
try {
|
||||
const classTeacherResponse = await getClassTeacherId(data.studentId);
|
||||
if (classTeacherResponse && classTeacherResponse.data) {
|
||||
classTeacherId = classTeacherResponse.data;
|
||||
console.log('获取到班主任ID:', classTeacherId);
|
||||
} else {
|
||||
console.warn('获取班主任ID失败');
|
||||
ElMessage.error('获取班主任信息失败,请稍后重试');
|
||||
return; // 停止后续操作
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('调用获取班主任ID接口失败:', error);
|
||||
ElMessage.error('获取班主任信息失败,请稍后重试');
|
||||
return; // 停止后续操作
|
||||
}
|
||||
|
||||
// 2. 调用接口获取学院请假审批人列表
|
||||
let collegeApprovers: number[] = [];
|
||||
try {
|
||||
const collegeApproversResponse = await getCollegeLeaveApprovers(data.studentId);
|
||||
if (collegeApproversResponse && collegeApproversResponse.data) {
|
||||
collegeApprovers = collegeApproversResponse.data;
|
||||
console.log('获取到学院请假审批人列表:', collegeApprovers);
|
||||
} else {
|
||||
console.warn('获取学院请假审批人列表失败');
|
||||
ElMessage.error('获取学院审批人信息失败,请稍后重试');
|
||||
return; // 停止后续操作
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('调用获取学院请假审批人接口失败:', error);
|
||||
ElMessage.error('获取学院审批人信息失败,请稍后重试');
|
||||
return; // 停止后续操作
|
||||
}
|
||||
|
||||
submitFormData.value.flowCode = flowCode.value;
|
||||
submitFormData.value.businessId = data.id;
|
||||
// 流程变量
|
||||
@@ -773,9 +811,12 @@ const handleStartWorkFlow = async (data: StudentLeaveForm) => {
|
||||
// hzdyjs-qj 使用的流程变量
|
||||
qjts: data.leaveDuration,
|
||||
// leave2/6 使用的流程变量
|
||||
leaveDays: data.leaveDuration,
|
||||
// leaveDays: data.leaveDuration,
|
||||
// leave4/5 使用的流程变量
|
||||
userList: ['1', '3', '4']
|
||||
// userList: ['1', '3', '4'],
|
||||
// 新增的流程变量
|
||||
bzrspr: classTeacherId,
|
||||
ejxyspr: collegeApprovers
|
||||
};
|
||||
submitFormData.value.variables = taskVariables.value;
|
||||
const resp = await startWorkFlow(submitFormData.value);
|
||||
@@ -811,6 +852,51 @@ const submitForm = (status: string) => {
|
||||
ElMessage.error('请假时间不能为空');
|
||||
return;
|
||||
}
|
||||
|
||||
// ====== 新增:请假类型相关校验逻辑 ======
|
||||
// 1. 默认需要销假
|
||||
form.needCancel = '1';
|
||||
form.cancelStatus = '0';
|
||||
|
||||
// 获取当前选中的请假类型配置
|
||||
const leaveType = currentLeaveType.value;
|
||||
if (!leaveType) {
|
||||
ElMessage.error('请先选择请假类型');
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. 校验 needCancelForNonLeave(非离校请假是否需要销假)
|
||||
// needCancelForNonLeave = '0' 且 isLeaveSchool = '0' 时,不需要销假
|
||||
if (leaveType.needCancelForNonLeave === '0' && form.isLeaveSchool === '0') {
|
||||
form.needCancel = '0';
|
||||
form.cancelStatus = '3';
|
||||
// 业务上如有特殊cancelStatus要求可补充
|
||||
}
|
||||
|
||||
// 3. 校验 canApplyInAdvance(是否可以提前请假)
|
||||
if (leaveType.canApplyInAdvance === '0') {
|
||||
// 只能选择今天及以前的日期
|
||||
const now = new Date();
|
||||
now.setHours(23, 59, 59, 999);
|
||||
const start = new Date(form.startTime);
|
||||
if (start.getTime() > now.getTime()) {
|
||||
ElMessage.error('该请假类型不允许提前请假,开始时间不能大于今天!');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 4. 校验 maxApplyDuration(最大申请时长,天)
|
||||
if (leaveType.maxApplyDuration && leaveType.maxApplyDuration > 0) {
|
||||
const start = new Date(form.startTime);
|
||||
const end = new Date(form.endTime);
|
||||
const diffDays = (end.getTime() - start.getTime()) / (1000 * 60 * 60 * 24);
|
||||
if (diffDays > leaveType.maxApplyDuration) {
|
||||
ElMessage.error(`请假时长不能超过${leaveType.maxApplyDuration}天!`);
|
||||
return;
|
||||
}
|
||||
}
|
||||
// ====== end ======
|
||||
|
||||
console.log('status111:', status);
|
||||
studentLeaveFormRef.value?.validate(async (valid: boolean) => {
|
||||
if (valid) {
|
||||
@@ -890,20 +976,21 @@ const cancel = () => {
|
||||
};
|
||||
|
||||
// 监听 isLeaveSchool 字段变化,更新销假状态
|
||||
watch(
|
||||
() => form.isLeaveSchool,
|
||||
(newValue) => {
|
||||
if (currentLeaveType.value && currentLeaveType.value.needCancelForNonLeave === '0') {
|
||||
if (newValue === '0') {
|
||||
// 当 isLeaveSchool = 0 时,cancelStatus = 3 (无需销假)
|
||||
form.cancelStatus = '3';
|
||||
} else {
|
||||
// 其他时候,cancelStatus = 0 (未销假)
|
||||
form.cancelStatus = '0';
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
// watch(
|
||||
// () => form.isLeaveSchool,
|
||||
// (newValue) => {
|
||||
// console.log('newValue:', newValue);
|
||||
// // if (currentLeaveType.value && currentLeaveType.value.needCancelForNonLeave === '0') {
|
||||
// // if (newValue === '0') {
|
||||
// // // 当 isLeaveSchool = 0 时,cancelStatus = 3 (无需销假)
|
||||
// // form.cancelStatus = '3';
|
||||
// // } else {
|
||||
// // // 其他时候,cancelStatus = 0 (未销假)
|
||||
// // form.cancelStatus = '0';
|
||||
// // }
|
||||
// // }
|
||||
// }
|
||||
// );
|
||||
|
||||
// 根据id获取学生请假信息
|
||||
const getStudentLeaveInfo = async (id: string | number) => {
|
||||
@@ -1331,7 +1418,8 @@ onMounted(() => {
|
||||
/* 附件上传容器样式 */
|
||||
.attachment-upload-container {
|
||||
width: 100%;
|
||||
max-height: 300px;
|
||||
max-height: calc(100vh - 650px);
|
||||
min-height: 200px;
|
||||
overflow-y: auto;
|
||||
padding-right: 8px;
|
||||
border: 1px solid #e4e7ed;
|
||||
@@ -1342,7 +1430,7 @@ onMounted(() => {
|
||||
/* 附件上传区域内容样式 */
|
||||
.attachment-upload-container .upload-demo {
|
||||
padding: 12px;
|
||||
min-height: 150px;
|
||||
min-height: 180px;
|
||||
}
|
||||
|
||||
.attachment-upload-container .upload-demo .el-upload {
|
||||
|
||||
@@ -172,17 +172,17 @@
|
||||
|
||||
<el-table v-loading="loading" border :data="studentLeaveList" @selection-change="handleSelectionChange">
|
||||
<el-table-column type="selection" width="55" align="center" />
|
||||
<el-table-column label="主键ID" align="center" prop="id" v-if="true" />
|
||||
<el-table-column label="主键ID" align="center" prop="id" v-if="false" />
|
||||
<!-- <el-table-column label="删除标志" align="center" prop="deleted" /> -->
|
||||
<!-- <el-table-column label="学生ID" align="center" prop="studentId" /> -->
|
||||
<el-table-column label="学生姓名" align="center" prop="studentName" />
|
||||
<el-table-column label="学号" align="center" prop="studentNo" />
|
||||
<!-- <el-table-column label="班级ID" align="center" prop="classId" /> -->
|
||||
<el-table-column label="班级名称" align="center" prop="className" />
|
||||
<!-- <el-table-column label="专业ID" align="center" prop="majorId" /> -->
|
||||
<el-table-column label="专业名称" align="center" prop="majorName" />
|
||||
<!-- <el-table-column label="学院ID" align="center" prop="collegeId" /> -->
|
||||
<el-table-column label="学院名称" align="center" prop="collegeName" />
|
||||
<!-- <el-table-column label="专业ID" align="center" prop="majorId" /> -->
|
||||
<el-table-column label="专业名称" align="center" prop="majorName" />
|
||||
<!-- <el-table-column label="班级ID" align="center" prop="classId" /> -->
|
||||
<el-table-column label="班级名称" align="center" prop="className" />
|
||||
<el-table-column label="学生姓名" align="center" prop="studentName" />
|
||||
<el-table-column label="学号" align="center" prop="studentNo" />
|
||||
<!-- <el-table-column label="请假类型编码" align="center" prop="leaveTypeCode" /> -->
|
||||
<el-table-column label="请假类型名称" align="center" prop="leaveTypeName" />
|
||||
<el-table-column label="开始时间" align="center" prop="startTime" width="180">
|
||||
@@ -210,9 +210,18 @@
|
||||
<el-table-column label="紧急联系电话" align="center" prop="emergencyPhone" />
|
||||
<el-table-column label="请假原因" align="center" prop="leaveReason" />
|
||||
<!-- <el-table-column label="请假时地理位置" align="center" prop="leaveLocation" /> -->
|
||||
<el-table-column label="附件OSS文件ID" align="center" prop="attachmentOssIds" width="280" />
|
||||
<!-- <el-table-column label="审批状态" align="center" prop="approvalStatus" />
|
||||
<el-table-column label="审批意见" align="center" prop="approvalOpinion" />
|
||||
<!-- <el-table-column label="附件OSS文件ID" align="center" prop="attachmentOssIds" width="280" /> -->
|
||||
<el-table-column label="审批状态" align="center" prop="approvalStatus">
|
||||
<template #default="scope">
|
||||
<dict-tag :options="student_leave_approval_status" :value="scope.row.approvalStatus" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="销假状态" align="center" prop="cancelStatus">
|
||||
<template #default="scope">
|
||||
<dict-tag :options="xg_xsqj_cancel_status" :value="scope.row.cancelStatus" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<!--<el-table-column label="审批意见" align="center" prop="approvalOpinion" />
|
||||
<el-table-column label="审批时间" align="center" prop="approvalTime" width="180">
|
||||
<template #default="scope">
|
||||
<span>{{ parseTime(scope.row.approvalTime, '{y}-{m}-{d}') }}</span>
|
||||
@@ -229,12 +238,13 @@
|
||||
<el-table-column label="销假地理位置" align="center" prop="cancelLocation" />
|
||||
<el-table-column label="销假照片OSS文件ID" align="center" prop="cancelPhotoOssIds" />
|
||||
<el-table-column label="销假备注" align="center" prop="cancelRemark" /> -->
|
||||
<el-table-column label="操作" align="center" width="100px" class-name="small-padding fixed-width" fixed="right">
|
||||
<el-table-column label="操作" align="center" width="150px" class-name="small-padding fixed-width" fixed="right">
|
||||
<template #default="scope">
|
||||
<el-tooltip content="修改" placement="top">
|
||||
<el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['studentLeave:studentLeave:edit']"></el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="删除" placement="top">
|
||||
<!-- 删除按钮:仅草稿状态可见 -->
|
||||
<el-tooltip content="删除" placement="top" v-if="scope.row.approvalStatus === 'draft'">
|
||||
<el-button
|
||||
link
|
||||
type="danger"
|
||||
@@ -243,6 +253,16 @@
|
||||
v-hasPermi="['studentLeave:studentLeave:remove']"
|
||||
></el-button>
|
||||
</el-tooltip>
|
||||
<!-- 销假按钮:仅已完成状态且需要销假时可见 -->
|
||||
<el-tooltip content="销假" placement="top" v-if="scope.row.approvalStatus === 'finish' && scope.row.needCancel === '1'">
|
||||
<el-button
|
||||
link
|
||||
type="success"
|
||||
icon="CircleCheck"
|
||||
@click="handleCancelLeave(scope.row)"
|
||||
v-hasPermi="['studentLeave:studentLeave:cancel']"
|
||||
></el-button>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
@@ -278,7 +298,9 @@ interface PageData<T, Q> {
|
||||
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
||||
|
||||
// 加载字典数据
|
||||
const { sys_yesno } = toRefs<any>(proxy?.useDict('sys_yesno'));
|
||||
const { sys_yesno, student_leave_approval_status, xg_xsqj_cancel_status } = toRefs<any>(
|
||||
proxy?.useDict('sys_yesno', 'student_leave_approval_status', 'xg_xsqj_cancel_status')
|
||||
);
|
||||
|
||||
const studentLeaveList = ref<StudentLeaveVO[]>([]);
|
||||
const buttonLoading = ref(false);
|
||||
@@ -384,7 +406,7 @@ const data = reactive<PageData<StudentLeaveForm, StudentLeaveQuery>>({
|
||||
isLeaveSchool: [{ required: true, message: '是否离校:0-否,1-是不能为空', trigger: 'blur' }],
|
||||
needCancel: [{ required: true, message: '是否需要销假:0-否,1-是不能为空', trigger: 'blur' }],
|
||||
leaveReason: [{ required: true, message: '请假原因不能为空', trigger: 'blur' }],
|
||||
approvalStatus: [{ required: true, message: '审批状态:0-待审批,1-已通过,2-已拒绝,3-已取消不能为空', trigger: 'change' }],
|
||||
approvalStatus: [{ required: true, message: '审批状态不能为空', trigger: 'change' }],
|
||||
cancelStatus: [{ required: true, message: '销假状态:0-未销假,1-已销假,2-逾期未销假不能为空', trigger: 'change' }]
|
||||
}
|
||||
});
|
||||
@@ -496,6 +518,41 @@ const handleDelete = async (row?: StudentLeaveVO) => {
|
||||
await getList();
|
||||
};
|
||||
|
||||
/** 销假按钮操作 */
|
||||
const handleCancelLeave = async (row: StudentLeaveVO) => {
|
||||
await proxy?.$modal.confirm('是否确认销假学生请假申请?').finally(() => (loading.value = false));
|
||||
|
||||
// TODO: 调用销假API
|
||||
// await cancelStudentLeave(row.id);
|
||||
// reset();
|
||||
const _id = row?.id || ids.value[0];
|
||||
|
||||
// 根据销假状态设置type参数
|
||||
// 当销假状态cancel_status = '0' 或者 '3'时,type = 'add',其他都是 'update'
|
||||
const type = row.cancelStatus === '0' || row.cancelStatus === '3' ? 'add' : 'update';
|
||||
|
||||
// const res = await getStudentLeave(_id);
|
||||
// Object.assign(form.value, res.data);
|
||||
// // 传递type参数,标识为修改操作
|
||||
// form.value.type = 'update';
|
||||
// form.value.id = _id;
|
||||
// dialog.visible = true;
|
||||
// dialog.title = '修改学生请假';
|
||||
// proxy.$tab.closePage(proxy.$route);
|
||||
console.log('type:', type);
|
||||
|
||||
proxy.$router.push({
|
||||
path: `/xg/xsjcxx/xsqj/cancelLeaveEdit/index`,
|
||||
query: {
|
||||
id: _id,
|
||||
type: type
|
||||
}
|
||||
});
|
||||
|
||||
// proxy?.$modal.msgSuccess('销假成功');
|
||||
// await getList();
|
||||
};
|
||||
|
||||
/** 导出按钮操作 */
|
||||
const handleExport = () => {
|
||||
proxy?.download(
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -33,36 +33,21 @@
|
||||
<el-col :span="6">
|
||||
<el-form-item label="院系" prop="collegeName">
|
||||
<el-select v-model="queryParams.collegeName" placeholder="请选择院系" clearable style="width: 100%" @change="handleCollegeChange">
|
||||
<el-option
|
||||
v-for="college in collegeOptions"
|
||||
:key="college.name"
|
||||
:label="college.name"
|
||||
:value="college.name"
|
||||
/>
|
||||
<el-option v-for="college in collegeOptions" :key="college.name" :label="college.name" :value="college.name" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="专业" prop="majorName">
|
||||
<el-select v-model="queryParams.majorName" placeholder="请选择专业" clearable style="width: 100%" @change="handleMajorChange">
|
||||
<el-option
|
||||
v-for="major in majorOptions"
|
||||
:key="major.name"
|
||||
:label="major.name"
|
||||
:value="major.name"
|
||||
/>
|
||||
<el-option v-for="major in majorOptions" :key="major.name" :label="major.name" :value="major.name" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="班级" prop="className">
|
||||
<el-select v-model="queryParams.className" placeholder="请选择班级" clearable style="width: 100%">
|
||||
<el-option
|
||||
v-for="classItem in classOptions"
|
||||
:key="classItem.name"
|
||||
:label="classItem.name"
|
||||
:value="classItem.name"
|
||||
/>
|
||||
<el-option v-for="classItem in classOptions" :key="classItem.name" :label="classItem.name" :value="classItem.name" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
@@ -143,7 +128,7 @@
|
||||
v-hasPermi="['xljk:consultationappointment:query']"
|
||||
></el-button>
|
||||
</el-tooltip>
|
||||
|
||||
|
||||
<!-- 待咨询状态可记录咨询结果 -->
|
||||
<el-tooltip content="记录结果" placement="top" v-if="scope.row.status === 0">
|
||||
<el-button
|
||||
@@ -185,7 +170,7 @@
|
||||
<el-tag v-else type="info">未知状态({{ form.status }})</el-tag>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="创建时间">{{ parseTime(form.createTime) }}</el-descriptions-item>
|
||||
|
||||
|
||||
<!-- 只有在已到访状态时才显示咨询结果相关字段 -->
|
||||
<template v-if="form.status == 1">
|
||||
<el-descriptions-item label="实际到访时间">
|
||||
@@ -202,7 +187,7 @@
|
||||
</el-descriptions-item>
|
||||
</template>
|
||||
</el-descriptions>
|
||||
|
||||
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="open = false">关 闭</el-button>
|
||||
@@ -247,20 +232,10 @@
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="咨询内容" prop="consultationContent">
|
||||
<el-input
|
||||
v-model="recordForm.consultationContent"
|
||||
type="textarea"
|
||||
placeholder="请输入咨询内容"
|
||||
:rows="4"
|
||||
/>
|
||||
<el-input v-model="recordForm.consultationContent" type="textarea" placeholder="请输入咨询内容" :rows="4" />
|
||||
</el-form-item>
|
||||
<el-form-item label="咨询师建议" prop="counselorSuggestion">
|
||||
<el-input
|
||||
v-model="recordForm.counselorSuggestion"
|
||||
type="textarea"
|
||||
placeholder="请输入咨询师建议"
|
||||
:rows="4"
|
||||
/>
|
||||
<el-input v-model="recordForm.counselorSuggestion" type="textarea" placeholder="请输入咨询师建议" :rows="4" />
|
||||
</el-form-item>
|
||||
</template>
|
||||
</el-form>
|
||||
@@ -276,8 +251,25 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, onMounted, computed, watch } from 'vue';
|
||||
import { listAppointment, getAppointment, delAppointment, updateAppointment, recordConsultation, getCounselorOptions, getCollegeOptions, getMajorOptions, getClassOptions } from '@/api/xljk/consultation';
|
||||
import { XljkConsultationAppointmentVO, XljkConsultationAppointmentForm, CounselorOption, CollegeOption, MajorOption, ClassOption } from '@/api/xljk/consultation/types';
|
||||
import {
|
||||
listAppointment,
|
||||
getAppointment,
|
||||
delAppointment,
|
||||
updateAppointment,
|
||||
recordConsultation,
|
||||
getCounselorOptions,
|
||||
getCollegeOptions,
|
||||
getMajorOptions,
|
||||
getClassOptions
|
||||
} from '@/api/xljk/consultation';
|
||||
import {
|
||||
XljkConsultationAppointmentVO,
|
||||
XljkConsultationAppointmentForm,
|
||||
CounselorOption,
|
||||
CollegeOption,
|
||||
MajorOption,
|
||||
ClassOption
|
||||
} from '@/api/xljk/consultation/types';
|
||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||
import { parseTime } from '@/utils/ruoyi';
|
||||
import { download } from '@/utils/request';
|
||||
@@ -364,20 +356,25 @@ const recordRules = ref({
|
||||
});
|
||||
|
||||
// 监听状态变化,动态调整表单验证规则
|
||||
watch(() => recordForm.status, (newStatus) => {
|
||||
if (newStatus === 3) { // 未到访
|
||||
recordRules.value = {
|
||||
status: [{ required: true, message: '请选择咨询结果', trigger: 'change' }]
|
||||
};
|
||||
} else { // 已到访
|
||||
recordRules.value = {
|
||||
status: [{ required: true, message: '请选择咨询结果', trigger: 'change' }],
|
||||
actualVisitTime: [{ required: true, message: '请选择实际到访时间', trigger: 'change' }],
|
||||
communicationDuration: [{ required: true, message: '请输入沟通时长', trigger: 'blur' }],
|
||||
consultationContent: [{ required: true, message: '请输入咨询内容', trigger: 'blur' }]
|
||||
};
|
||||
watch(
|
||||
() => recordForm.status,
|
||||
(newStatus) => {
|
||||
if (newStatus === 3) {
|
||||
// 未到访
|
||||
recordRules.value = {
|
||||
status: [{ required: true, message: '请选择咨询结果', trigger: 'change' }]
|
||||
};
|
||||
} else {
|
||||
// 已到访
|
||||
recordRules.value = {
|
||||
status: [{ required: true, message: '请选择咨询结果', trigger: 'change' }],
|
||||
actualVisitTime: [{ required: true, message: '请选择实际到访时间', trigger: 'change' }],
|
||||
communicationDuration: [{ required: true, message: '请输入沟通时长', trigger: 'blur' }],
|
||||
consultationContent: [{ required: true, message: '请输入咨询内容', trigger: 'blur' }]
|
||||
};
|
||||
}
|
||||
}
|
||||
});
|
||||
);
|
||||
|
||||
const queryFormRef = ref();
|
||||
const recordFormRef = ref();
|
||||
@@ -392,11 +389,11 @@ const getList = async () => {
|
||||
queryParams.params.beginAppointmentDate = dateRange.value[0];
|
||||
queryParams.params.endAppointmentDate = dateRange.value[1];
|
||||
}
|
||||
|
||||
|
||||
const res = await listAppointment(queryParams);
|
||||
appointmentList.value = res.rows || [];
|
||||
total.value = res.total || 0;
|
||||
|
||||
|
||||
// 添加日志输出
|
||||
console.log('appointment_status字典数据:', appointment_status.value);
|
||||
console.log('appointmentList数据:', appointmentList.value);
|
||||
@@ -467,9 +464,9 @@ const resetQuery = () => {
|
||||
/** 多选框选中数据 */
|
||||
const handleSelectionChange = (selection: XljkConsultationAppointmentVO[]) => {
|
||||
ids.value = selection.map((item) => item.id);
|
||||
|
||||
|
||||
// 判断是否有非待咨询状态
|
||||
const hasNonPending = selection.some(item => item.status !== 0);
|
||||
const hasNonPending = selection.some((item) => item.status !== 0);
|
||||
multiple.value = !selection.length || hasNonPending;
|
||||
};
|
||||
|
||||
@@ -478,34 +475,34 @@ const handleView = async (row: XljkConsultationAppointmentVO) => {
|
||||
try {
|
||||
console.log('查看预约详情,行数据:', row);
|
||||
console.log('行数据status类型:', typeof row.status, '值:', row.status);
|
||||
|
||||
|
||||
const res = await getAppointment(row.id);
|
||||
console.log('获取预约详情API响应:', res);
|
||||
|
||||
|
||||
// 清空表单,确保没有残留数据
|
||||
Object.keys(form).forEach(key => {
|
||||
Object.keys(form).forEach((key) => {
|
||||
form[key] = undefined;
|
||||
});
|
||||
|
||||
|
||||
// 检查API响应格式
|
||||
if (res.code === 200 && res.data) {
|
||||
// 将API响应数据复制到表单
|
||||
Object.assign(form, res.data);
|
||||
|
||||
|
||||
// 确保状态是数值类型
|
||||
console.log('API返回的status类型:', typeof form.status, '值:', form.status);
|
||||
|
||||
|
||||
if (form.status === null || form.status === undefined) {
|
||||
// 如果API返回的status为空,使用行数据的status
|
||||
form.status = row.status;
|
||||
console.log('使用行数据的status:', form.status);
|
||||
}
|
||||
|
||||
|
||||
if (typeof form.status === 'string') {
|
||||
form.status = parseInt(form.status);
|
||||
console.log('转换后的status:', form.status);
|
||||
}
|
||||
|
||||
|
||||
// 检查实际到访时间
|
||||
console.log('实际到访时间:', form.actualVisitTime);
|
||||
if (form.status === 1 && !form.actualVisitTime) {
|
||||
@@ -517,9 +514,9 @@ const handleView = async (row: XljkConsultationAppointmentVO) => {
|
||||
console.warn('行数据也没有实际到访时间');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
console.log('复制后的表单数据:', form);
|
||||
|
||||
|
||||
// 如果是已到访状态,确保咨询结果相关字段有值
|
||||
if (form.status === 1) {
|
||||
console.log('已到访状态,咨询结果相关字段:', {
|
||||
@@ -533,27 +530,27 @@ const handleView = async (row: XljkConsultationAppointmentVO) => {
|
||||
console.warn('API响应格式不正确或无数据:', res);
|
||||
// 使用行数据填充表单
|
||||
Object.assign(form, row);
|
||||
|
||||
|
||||
// 确保状态是数值类型
|
||||
console.log('使用行数据,status类型:', typeof form.status, '值:', form.status);
|
||||
|
||||
|
||||
if (typeof form.status === 'string') {
|
||||
form.status = parseInt(form.status);
|
||||
console.log('转换后的status:', form.status);
|
||||
}
|
||||
|
||||
|
||||
console.log('使用行数据填充表单:', form);
|
||||
}
|
||||
|
||||
|
||||
open.value = true;
|
||||
title.value = '查看预约详情';
|
||||
} catch (error) {
|
||||
console.error('获取预约详情失败', error);
|
||||
ElMessage.error('获取预约详情失败');
|
||||
|
||||
|
||||
// 出错时也使用行数据填充
|
||||
Object.assign(form, row);
|
||||
|
||||
|
||||
// 确保状态是数值类型
|
||||
if (typeof form.status === 'string') {
|
||||
form.status = parseInt(form.status);
|
||||
@@ -567,7 +564,7 @@ const handleRecord = async (row: XljkConsultationAppointmentVO) => {
|
||||
console.log('记录咨询结果,行数据:', row);
|
||||
const res = await getAppointment(row.id);
|
||||
console.log('获取预约详情API响应:', res);
|
||||
|
||||
|
||||
// 重置记录表单
|
||||
const defaultValues = {
|
||||
id: undefined,
|
||||
@@ -583,9 +580,9 @@ const handleRecord = async (row: XljkConsultationAppointmentVO) => {
|
||||
consultationContent: '',
|
||||
counselorSuggestion: ''
|
||||
};
|
||||
|
||||
|
||||
Object.assign(recordForm, defaultValues);
|
||||
|
||||
|
||||
// 检查API响应格式
|
||||
if (res.code === 200 && res.data) {
|
||||
// 填充API返回的数据
|
||||
@@ -609,7 +606,7 @@ const handleRecord = async (row: XljkConsultationAppointmentVO) => {
|
||||
recordForm.reason = row.reason;
|
||||
recordForm.studentName = row.studentName;
|
||||
}
|
||||
|
||||
|
||||
console.log('填充后的记录表单:', recordForm);
|
||||
recordDialogVisible.value = true;
|
||||
} catch (error) {
|
||||
@@ -621,19 +618,19 @@ const handleRecord = async (row: XljkConsultationAppointmentVO) => {
|
||||
/** 提交记录表单 */
|
||||
const submitRecordForm = async () => {
|
||||
if (!recordFormRef.value) return;
|
||||
|
||||
|
||||
await recordFormRef.value.validate(async (valid: boolean) => {
|
||||
if (!valid) return;
|
||||
|
||||
|
||||
try {
|
||||
submitLoading.value = true;
|
||||
|
||||
|
||||
// 构建提交数据
|
||||
const submitData: any = {
|
||||
id: recordForm.id,
|
||||
status: recordForm.status
|
||||
};
|
||||
|
||||
|
||||
// 如果是已到访状态,添加其他字段
|
||||
if (recordForm.status === 1) {
|
||||
// 确保日期格式正确
|
||||
@@ -646,14 +643,14 @@ const submitRecordForm = async () => {
|
||||
const hours = String(recordForm.actualVisitTime.getHours()).padStart(2, '0');
|
||||
const minutes = String(recordForm.actualVisitTime.getMinutes()).padStart(2, '0');
|
||||
const seconds = String(recordForm.actualVisitTime.getSeconds()).padStart(2, '0');
|
||||
|
||||
|
||||
submitData.actualVisitTime = `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
|
||||
} else {
|
||||
submitData.actualVisitTime = recordForm.actualVisitTime;
|
||||
}
|
||||
console.log('提交的实际到访时间:', submitData.actualVisitTime);
|
||||
}
|
||||
|
||||
|
||||
submitData.communicationDuration = recordForm.communicationDuration;
|
||||
submitData.consultationContent = recordForm.consultationContent;
|
||||
submitData.counselorSuggestion = recordForm.counselorSuggestion;
|
||||
@@ -664,7 +661,7 @@ const submitRecordForm = async () => {
|
||||
submitData.consultationContent = '';
|
||||
submitData.counselorSuggestion = '';
|
||||
}
|
||||
|
||||
|
||||
await recordConsultation(recordForm.id as number, submitData);
|
||||
ElMessage.success('记录成功');
|
||||
recordDialogVisible.value = false;
|
||||
@@ -681,11 +678,11 @@ const submitRecordForm = async () => {
|
||||
/** 删除按钮操作 */
|
||||
const handleDelete = async (row?: XljkConsultationAppointmentVO) => {
|
||||
const _ids = row?.id || ids.value;
|
||||
|
||||
|
||||
// 检查是否有非待咨询状态的记录
|
||||
if (!row) {
|
||||
const selectedRecords = appointmentList.value.filter(item => ids.value.includes(item.id));
|
||||
if (selectedRecords.some(item => item.status !== 0)) {
|
||||
const selectedRecords = appointmentList.value.filter((item) => ids.value.includes(item.id));
|
||||
if (selectedRecords.some((item) => item.status !== 0)) {
|
||||
ElMessage.error('只能删除待咨询状态的预约记录');
|
||||
return;
|
||||
}
|
||||
@@ -693,18 +690,16 @@ const handleDelete = async (row?: XljkConsultationAppointmentVO) => {
|
||||
ElMessage.error('只能删除待咨询状态的预约记录');
|
||||
return;
|
||||
}
|
||||
|
||||
const confirmMessage = row
|
||||
? `是否确认删除该预约记录?`
|
||||
: `是否确认删除选中的${ids.value.length}条数据?`;
|
||||
|
||||
|
||||
const confirmMessage = row ? `是否确认删除该预约记录?` : `是否确认删除选中的${ids.value.length}条数据?`;
|
||||
|
||||
try {
|
||||
await ElMessageBox.confirm(confirmMessage, '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
});
|
||||
|
||||
|
||||
await delAppointment(_ids);
|
||||
ElMessage.success('删除成功');
|
||||
getList();
|
||||
@@ -718,29 +713,29 @@ const handleExport = async () => {
|
||||
// 构建查询参数,处理日期范围和params对象
|
||||
const exportParams = { ...queryParams };
|
||||
delete exportParams.params; // 移除params对象
|
||||
|
||||
|
||||
// 将日期范围添加到参数中
|
||||
if (dateRange.value && dateRange.value.length === 2) {
|
||||
exportParams.beginAppointmentDate = dateRange.value[0];
|
||||
exportParams.endAppointmentDate = dateRange.value[1];
|
||||
}
|
||||
|
||||
|
||||
// 移除undefined值
|
||||
Object.keys(exportParams).forEach(key => {
|
||||
Object.keys(exportParams).forEach((key) => {
|
||||
if (exportParams[key] === undefined) {
|
||||
delete exportParams[key];
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// 构建URL参数
|
||||
const params = new URLSearchParams();
|
||||
Object.keys(exportParams).forEach(key => {
|
||||
Object.keys(exportParams).forEach((key) => {
|
||||
params.append(key, exportParams[key]);
|
||||
});
|
||||
|
||||
|
||||
// 获取token
|
||||
const token = getToken();
|
||||
|
||||
|
||||
try {
|
||||
// 使用fetch API发送请求,正确设置Authorization头
|
||||
const response = await fetch(`http://localhost:8080/api/xljk/consultation/appointment/list/export?${params.toString()}`, {
|
||||
@@ -750,11 +745,11 @@ const handleExport = async () => {
|
||||
'clientid': import.meta.env.VITE_APP_CLIENT_ID || 'e5cd7e4891bf95d1d19206ce24a7b32e'
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`导出失败: ${response.status} ${response.statusText}`);
|
||||
}
|
||||
|
||||
|
||||
// 获取文件名
|
||||
const contentDisposition = response.headers.get('Content-Disposition');
|
||||
let filename = '预约记录.xlsx';
|
||||
@@ -764,7 +759,7 @@ const handleExport = async () => {
|
||||
filename = filenameMatch[1].replace(/['"]/g, '');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 下载文件
|
||||
const blob = await response.blob();
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
@@ -776,7 +771,6 @@ const handleExport = async () => {
|
||||
a.click();
|
||||
window.URL.revokeObjectURL(url);
|
||||
document.body.removeChild(a);
|
||||
|
||||
} catch (error) {
|
||||
console.error('导出失败:', error);
|
||||
ElMessage.error(`导出失败: ${error.message}`);
|
||||
@@ -790,7 +784,7 @@ const handleCollegeChange = async (collegeName: string) => {
|
||||
queryParams.className = undefined;
|
||||
majorOptions.value = [];
|
||||
classOptions.value = [];
|
||||
|
||||
|
||||
if (collegeName) {
|
||||
// 获取该院系下的专业
|
||||
try {
|
||||
@@ -810,7 +804,7 @@ const handleMajorChange = async (majorName: string) => {
|
||||
// 清空班级选择
|
||||
queryParams.className = undefined;
|
||||
classOptions.value = [];
|
||||
|
||||
|
||||
if (majorName && queryParams.collegeName) {
|
||||
// 获取专业下的班级
|
||||
try {
|
||||
@@ -900,4 +894,4 @@ onMounted(() => {
|
||||
.no-wrap-label :deep(.el-form-item__label) {
|
||||
white-space: nowrap;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
@@ -19,7 +19,7 @@
|
||||
</el-radio-group>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- 周视图 -->
|
||||
<div v-if="currentView === 'week'" class="week-grid">
|
||||
<!-- 表头:显示日期和星期 -->
|
||||
@@ -27,32 +27,23 @@
|
||||
<div class="time-label-cell">
|
||||
<span>时段</span>
|
||||
</div>
|
||||
<div
|
||||
v-for="day in weekDays"
|
||||
:key="day.date"
|
||||
class="day-cell"
|
||||
:class="{ 'today': day.isToday }"
|
||||
>
|
||||
<div v-for="day in weekDays" :key="day.date" class="day-cell" :class="{ 'today': day.isToday }">
|
||||
<div class="day-number">{{ day.dayNumber }}</div>
|
||||
<div class="day-name">{{ day.dayName }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- 表格内容:显示全天和时间段 -->
|
||||
<div class="week-body">
|
||||
<div class="time-row">
|
||||
<div class="time-label-cell">
|
||||
<span>全天</span>
|
||||
</div>
|
||||
<div
|
||||
v-for="day in weekDays"
|
||||
:key="day.date"
|
||||
class="day-cell"
|
||||
>
|
||||
<div v-for="day in weekDays" :key="day.date" class="day-cell">
|
||||
<!-- 这里可以放全天事件,目前为空 -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- 时间段行 -->
|
||||
<div class="time-slots-container">
|
||||
<!-- 上午时段 -->
|
||||
@@ -60,16 +51,12 @@
|
||||
<div class="time-label-cell">
|
||||
<span>上午</span>
|
||||
</div>
|
||||
<div
|
||||
v-for="day in weekDays"
|
||||
:key="day.date"
|
||||
class="day-cell"
|
||||
>
|
||||
<div
|
||||
v-if="getMorningSlot(day.date)"
|
||||
<div v-for="day in weekDays" :key="day.date" class="day-cell">
|
||||
<div
|
||||
v-if="getMorningSlot(day.date)"
|
||||
class="time-slot-card morning"
|
||||
:class="{
|
||||
'fully-booked': getMorningSlot(day.date).allBooked || getMorningSlot(day.date).isExpired,
|
||||
'fully-booked': getMorningSlot(day.date).allBooked || getMorningSlot(day.date).isExpired
|
||||
}"
|
||||
@click="handleSlotClick(getMorningSlot(day.date))"
|
||||
>
|
||||
@@ -77,22 +64,18 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- 下午时段 -->
|
||||
<div class="time-row">
|
||||
<div class="time-label-cell">
|
||||
<span>下午</span>
|
||||
</div>
|
||||
<div
|
||||
v-for="day in weekDays"
|
||||
:key="day.date"
|
||||
class="day-cell"
|
||||
>
|
||||
<div
|
||||
v-if="getAfternoonSlot(day.date)"
|
||||
<div v-for="day in weekDays" :key="day.date" class="day-cell">
|
||||
<div
|
||||
v-if="getAfternoonSlot(day.date)"
|
||||
class="time-slot-card afternoon"
|
||||
:class="{
|
||||
'fully-booked': getAfternoonSlot(day.date).allBooked || getAfternoonSlot(day.date).isExpired,
|
||||
'fully-booked': getAfternoonSlot(day.date).allBooked || getAfternoonSlot(day.date).isExpired
|
||||
}"
|
||||
@click="handleSlotClick(getAfternoonSlot(day.date))"
|
||||
>
|
||||
@@ -103,7 +86,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- 月视图 -->
|
||||
<div v-else class="month-grid">
|
||||
<!-- 表头:星期几 -->
|
||||
@@ -112,35 +95,35 @@
|
||||
{{ day }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- 日期单元格 -->
|
||||
<div class="month-body">
|
||||
<div v-for="week in monthDays" :key="week[0].date" class="month-row">
|
||||
<div
|
||||
v-for="day in week"
|
||||
:key="day.date"
|
||||
<div
|
||||
v-for="day in week"
|
||||
:key="day.date"
|
||||
class="month-cell"
|
||||
:class="{
|
||||
'today': day.isToday,
|
||||
'other-month': day.isOtherMonth
|
||||
:class="{
|
||||
'today': day.isToday,
|
||||
'other-month': day.isOtherMonth
|
||||
}"
|
||||
@click="day.isOtherMonth ? null : handleDateClick(day.date)"
|
||||
>
|
||||
<div class="month-day-number">{{ day.dayNumber }}</div>
|
||||
|
||||
|
||||
<!-- 上午时段 -->
|
||||
<div
|
||||
v-if="getMorningSlot(day.date)"
|
||||
<div
|
||||
v-if="getMorningSlot(day.date)"
|
||||
class="month-slot morning"
|
||||
:class="{ 'fully-booked': getMorningSlot(day.date).allBooked || getMorningSlot(day.date).isExpired }"
|
||||
@click.stop="handleSlotClick(getMorningSlot(day.date))"
|
||||
>
|
||||
上午 {{ getMorningSlot(day.date).startTime }}-{{ getMorningSlot(day.date).endTime }}
|
||||
</div>
|
||||
|
||||
|
||||
<!-- 下午时段 -->
|
||||
<div
|
||||
v-if="getAfternoonSlot(day.date)"
|
||||
<div
|
||||
v-if="getAfternoonSlot(day.date)"
|
||||
class="month-slot afternoon"
|
||||
:class="{ 'fully-booked': getAfternoonSlot(day.date).allBooked || getAfternoonSlot(day.date).isExpired }"
|
||||
@click.stop="handleSlotClick(getAfternoonSlot(day.date))"
|
||||
@@ -191,7 +174,7 @@ onMounted(() => {
|
||||
function goToday() {
|
||||
const currentDate = new Date();
|
||||
today.value = currentDate;
|
||||
|
||||
|
||||
if (currentView.value === 'week') {
|
||||
// 计算本周的周一
|
||||
const day = currentDate.getDay() || 7; // 周日是0,转换为7
|
||||
@@ -201,7 +184,7 @@ function goToday() {
|
||||
// 计算本月的1号
|
||||
periodStart.value = new Date(currentDate.getFullYear(), currentDate.getMonth(), 1);
|
||||
}
|
||||
|
||||
|
||||
// 触发日期范围变化事件
|
||||
emitDateRangeChange();
|
||||
}
|
||||
@@ -243,10 +226,10 @@ function handleViewChange(view: string) {
|
||||
// 计算当前月的1号
|
||||
periodStart.value = new Date(today.value.getFullYear(), today.value.getMonth(), 1);
|
||||
}
|
||||
|
||||
|
||||
// 触发视图变化事件
|
||||
emit('viewChange', view);
|
||||
|
||||
|
||||
// 触发日期范围变化事件
|
||||
emitDateRangeChange();
|
||||
}
|
||||
@@ -260,7 +243,7 @@ function handleDateClick(date: string) {
|
||||
// 触发日期范围变化事件
|
||||
function emitDateRangeChange() {
|
||||
let startDate, endDate;
|
||||
|
||||
|
||||
if (currentView.value === 'week') {
|
||||
// 周视图:周一到周日
|
||||
startDate = formatDate(periodStart.value);
|
||||
@@ -270,62 +253,62 @@ function emitDateRangeChange() {
|
||||
// 计算本月第一天
|
||||
const year = periodStart.value.getFullYear();
|
||||
const month = periodStart.value.getMonth();
|
||||
|
||||
|
||||
// 本月第一天
|
||||
const firstDay = new Date(year, month, 1);
|
||||
// 本月第一天是星期几(0-6,0是星期日)
|
||||
let firstDayOfWeek = firstDay.getDay() || 7; // 转换为1-7,1是星期一
|
||||
|
||||
|
||||
// 计算本月第一周的第一天(可能是上个月的日期)
|
||||
const firstDayOfCalendar = new Date(firstDay);
|
||||
firstDayOfCalendar.setDate(firstDayOfCalendar.getDate() - (firstDayOfWeek - 1));
|
||||
|
||||
|
||||
// 计算本月最后一天
|
||||
const lastDay = new Date(year, month + 1, 0);
|
||||
// 本月最后一天是星期几(0-6,0是星期日)
|
||||
let lastDayOfWeek = lastDay.getDay() || 7; // 转换为1-7,1是星期一
|
||||
|
||||
|
||||
// 计算本月最后一周的最后一天(可能是下个月的日期)
|
||||
const lastDayOfCalendar = new Date(lastDay);
|
||||
lastDayOfCalendar.setDate(lastDayOfCalendar.getDate() + (7 - lastDayOfWeek));
|
||||
|
||||
|
||||
// 确保我们覆盖整个日历视图中显示的所有日期,包括6行的完整数据
|
||||
// 这意味着我们至少需要包含42天的数据(6行 x 7天)
|
||||
const minEndDate = new Date(firstDayOfCalendar);
|
||||
minEndDate.setDate(minEndDate.getDate() + 41); // 41 = 6*7-1
|
||||
|
||||
|
||||
// 使用日历的第一天和至少覆盖6周的结束日期(或最后一周的最后一天,取较大者)
|
||||
startDate = formatDate(firstDayOfCalendar);
|
||||
|
||||
|
||||
// 确保结束日期至少覆盖6行
|
||||
if (lastDayOfCalendar < minEndDate) {
|
||||
endDate = formatDate(minEndDate);
|
||||
} else {
|
||||
endDate = formatDate(lastDayOfCalendar);
|
||||
endDate = formatDate(lastDayOfCalendar);
|
||||
}
|
||||
|
||||
|
||||
console.log(`日历日期范围:${startDate} 到 ${endDate},确保覆盖所有6行`);
|
||||
}
|
||||
|
||||
|
||||
emit('dateRangeChange', { startDate, endDate });
|
||||
}
|
||||
|
||||
// 处理时段点击
|
||||
function handleSlotClick(slot: AvailableTimeSlot | undefined) {
|
||||
if (!slot) return;
|
||||
|
||||
|
||||
// 如果该时段已过期,则不允许预约
|
||||
if (slot.isExpired) {
|
||||
ElMessage.warning('该时段已过期,请选择其他时段');
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// 如果该时段全部约满,则不允许预约
|
||||
if (slot.allBooked) {
|
||||
ElMessage.warning('该时段已全部约满,请选择其他时段');
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
emit('slotClick', slot);
|
||||
}
|
||||
|
||||
@@ -348,12 +331,12 @@ const periodTitle = computed(() => {
|
||||
const weekDays = computed(() => {
|
||||
const days = [];
|
||||
const dayNames = ['周一', '周二', '周三', '周四', '周五', '周六', '周日'];
|
||||
|
||||
|
||||
for (let i = 0; i < 7; i++) {
|
||||
const date = new Date(periodStart.value.getTime() + i * 24 * 60 * 60 * 1000);
|
||||
const formattedDate = formatDate(date);
|
||||
const isToday = isSameDay(date, today.value);
|
||||
|
||||
|
||||
days.push({
|
||||
date: formattedDate,
|
||||
dayNumber: date.getDate(), // 日期数字
|
||||
@@ -361,7 +344,7 @@ const weekDays = computed(() => {
|
||||
isToday
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
return days;
|
||||
});
|
||||
|
||||
@@ -370,32 +353,32 @@ const monthDays = computed(() => {
|
||||
const weeks = [];
|
||||
const year = periodStart.value.getFullYear();
|
||||
const month = periodStart.value.getMonth();
|
||||
|
||||
|
||||
// 本月第一天
|
||||
const firstDay = new Date(year, month, 1);
|
||||
// 本月第一天是星期几(0-6,0是星期日)
|
||||
let firstDayOfWeek = firstDay.getDay() || 7; // 转换为1-7,1是星期一
|
||||
|
||||
|
||||
// 计算本月第一周的第一天(可能是上个月的日期)
|
||||
const firstDayOfCalendar = new Date(firstDay);
|
||||
firstDayOfCalendar.setDate(firstDayOfCalendar.getDate() - (firstDayOfWeek - 1));
|
||||
|
||||
|
||||
// 生成6周的日期(确保能显示完整月份)
|
||||
for (let week = 0; week < 6; week++) {
|
||||
const weekDays = [];
|
||||
|
||||
|
||||
for (let day = 0; day < 7; day++) {
|
||||
const date = new Date(firstDayOfCalendar);
|
||||
date.setDate(date.getDate() + week * 7 + day);
|
||||
|
||||
|
||||
const formattedDate = formatDate(date);
|
||||
const isToday = isSameDay(date, today.value);
|
||||
const isOtherMonth = date.getMonth() !== month;
|
||||
|
||||
|
||||
// 调试信息,查看是否有对应的时段数据
|
||||
const morningSlot = getMorningSlot(formattedDate);
|
||||
const afternoonSlot = getAfternoonSlot(formattedDate);
|
||||
|
||||
|
||||
// 如果是最后一行,添加额外日志
|
||||
if (week === 5) {
|
||||
console.log(`日期 ${formattedDate} (${date.toLocaleDateString()}) 数据状态:`, {
|
||||
@@ -404,7 +387,7 @@ const monthDays = computed(() => {
|
||||
isOtherMonth
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
weekDays.push({
|
||||
date: formattedDate,
|
||||
dayNumber: date.getDate(),
|
||||
@@ -412,18 +395,16 @@ const monthDays = computed(() => {
|
||||
isOtherMonth
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
weeks.push(weekDays);
|
||||
}
|
||||
|
||||
|
||||
return weeks;
|
||||
});
|
||||
|
||||
// 判断两个日期是否是同一天
|
||||
function isSameDay(date1: Date, date2: Date) {
|
||||
return date1.getFullYear() === date2.getFullYear() &&
|
||||
date1.getMonth() === date2.getMonth() &&
|
||||
date1.getDate() === date2.getDate();
|
||||
return date1.getFullYear() === date2.getFullYear() && date1.getMonth() === date2.getMonth() && date1.getDate() === date2.getDate();
|
||||
}
|
||||
|
||||
// 格式化日期为 yyyy-MM-dd 格式
|
||||
@@ -433,22 +414,21 @@ function formatDate(date: Date) {
|
||||
|
||||
// 获取指定日期的上午时段
|
||||
function getMorningSlot(dateStr: string) {
|
||||
return props.availableSlots.find(slot =>
|
||||
slot.date === dateStr && slot.timeSlot === '0'
|
||||
);
|
||||
return props.availableSlots.find((slot) => slot.date === dateStr && slot.timeSlot === '0');
|
||||
}
|
||||
|
||||
// 获取指定日期的下午时段
|
||||
function getAfternoonSlot(dateStr: string) {
|
||||
return props.availableSlots.find(slot =>
|
||||
slot.date === dateStr && slot.timeSlot === '1'
|
||||
);
|
||||
return props.availableSlots.find((slot) => slot.date === dateStr && slot.timeSlot === '1');
|
||||
}
|
||||
|
||||
// 监听视图变化
|
||||
watch(() => props.defaultView, (newView) => {
|
||||
currentView.value = newView;
|
||||
});
|
||||
watch(
|
||||
() => props.defaultView,
|
||||
(newView) => {
|
||||
currentView.value = newView;
|
||||
}
|
||||
);
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@@ -564,15 +544,15 @@ watch(() => props.defaultView, (newView) => {
|
||||
}
|
||||
|
||||
.time-slot-card.morning {
|
||||
background-color: #409EFF;
|
||||
background-color: #409eff;
|
||||
}
|
||||
|
||||
.time-slot-card.afternoon {
|
||||
background-color: #67C23A;
|
||||
background-color: #67c23a;
|
||||
}
|
||||
|
||||
.time-slot-card.fully-booked {
|
||||
background-color: #C0C0C0;
|
||||
background-color: #c0c0c0;
|
||||
cursor: not-allowed;
|
||||
opacity: 0.7;
|
||||
}
|
||||
@@ -658,16 +638,16 @@ watch(() => props.defaultView, (newView) => {
|
||||
}
|
||||
|
||||
.month-slot.morning {
|
||||
background-color: #409EFF;
|
||||
background-color: #409eff;
|
||||
}
|
||||
|
||||
.month-slot.afternoon {
|
||||
background-color: #67C23A;
|
||||
background-color: #67c23a;
|
||||
}
|
||||
|
||||
.month-slot.fully-booked {
|
||||
background-color: #C0C0C0;
|
||||
background-color: #c0c0c0;
|
||||
cursor: not-allowed;
|
||||
opacity: 0.7;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
@@ -12,14 +12,11 @@ content = content.replace(
|
||||
);
|
||||
|
||||
// 修改submitAppointmentForm函数
|
||||
content = content.replace(
|
||||
"function submitAppointmentForm() {",
|
||||
"async function submitAppointmentForm() {"
|
||||
);
|
||||
content = content.replace('function submitAppointmentForm() {', 'async function submitAppointmentForm() {');
|
||||
|
||||
// 添加预约限制检查
|
||||
content = content.replace(
|
||||
"async function submitAppointmentForm() {",
|
||||
'async function submitAppointmentForm() {',
|
||||
`async function submitAppointmentForm() {
|
||||
// 表单验证
|
||||
if (!form.value.studentId) {
|
||||
@@ -41,7 +38,8 @@ content = content.replace(
|
||||
} catch (error) {
|
||||
console.error('检查预约限制失败', error);
|
||||
}
|
||||
`);
|
||||
`
|
||||
);
|
||||
|
||||
// 写回文件
|
||||
fs.writeFileSync(filePath, content);
|
||||
|
||||
@@ -12,14 +12,11 @@ content = content.replace(
|
||||
);
|
||||
|
||||
// 修改submitAppointmentForm函数
|
||||
content = content.replace(
|
||||
"function submitAppointmentForm() {",
|
||||
"async function submitAppointmentForm() {"
|
||||
);
|
||||
content = content.replace('function submitAppointmentForm() {', 'async function submitAppointmentForm() {');
|
||||
|
||||
// 添加预约限制检查
|
||||
content = content.replace(
|
||||
"function submitAppointmentForm() {",
|
||||
'function submitAppointmentForm() {',
|
||||
`async function submitAppointmentForm() {
|
||||
// 表单验证
|
||||
if (!form.value.studentId) {
|
||||
@@ -41,7 +38,8 @@ content = content.replace(
|
||||
} catch (error) {
|
||||
console.error('检查预约限制失败', error);
|
||||
}
|
||||
`);
|
||||
`
|
||||
);
|
||||
|
||||
// 写回文件
|
||||
fs.writeFileSync(filePath, content);
|
||||
|
||||
@@ -51,9 +51,9 @@
|
||||
<el-col :span="24">
|
||||
<el-form :model="sqlForm" label-width="120px">
|
||||
<el-form-item label="SQL语句">
|
||||
<el-input
|
||||
v-model="sqlForm.sql"
|
||||
type="textarea"
|
||||
<el-input
|
||||
v-model="sqlForm.sql"
|
||||
type="textarea"
|
||||
:rows="3"
|
||||
placeholder="SELECT * FROM xljk_consultation_appointment WHERE student_id = ?"
|
||||
></el-input>
|
||||
@@ -102,7 +102,7 @@ async function getStudentInfo() {
|
||||
try {
|
||||
const res = await getStudentByNo(form.studentNo);
|
||||
studentInfo.value = res.data;
|
||||
|
||||
|
||||
if (res.data && res.data.id) {
|
||||
appointmentForm.studentId = res.data.id;
|
||||
ElMessage.success('查询成功,已自动填充学生ID');
|
||||
@@ -121,11 +121,11 @@ async function getAppointments() {
|
||||
ElMessage.warning('请先输入学生ID');
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
const res = await getMyAppointments(appointmentForm.studentId);
|
||||
appointmentList.value = res.data || [];
|
||||
|
||||
|
||||
if (appointmentList.value.length === 0) {
|
||||
ElMessage.info('未找到该学生的预约记录');
|
||||
} else {
|
||||
@@ -143,14 +143,14 @@ async function executeSql() {
|
||||
ElMessage.warning('请输入SQL语句');
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
const res = await request({
|
||||
url: '/api/xljk/consultation/debug/execute-sql',
|
||||
method: 'post',
|
||||
data: { sql: sqlForm.sql }
|
||||
});
|
||||
|
||||
|
||||
sqlResult.value = res.data;
|
||||
ElMessage.success('SQL执行成功');
|
||||
} catch (error) {
|
||||
@@ -184,4 +184,4 @@ pre {
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
@@ -17,8 +17,8 @@
|
||||
<el-col :span="24">
|
||||
<div class="calendar-container">
|
||||
<!-- 使用WeekView组件,支持月视图和周视图切换 -->
|
||||
<WeekView
|
||||
:available-slots="availableSlots"
|
||||
<WeekView
|
||||
:available-slots="availableSlots"
|
||||
:default-view="calendarView"
|
||||
@date-range-change="handleDateRangeChange"
|
||||
@slot-click="handleSlotClick"
|
||||
@@ -30,19 +30,8 @@
|
||||
</el-card>
|
||||
|
||||
<!-- 预约表单弹窗 -->
|
||||
<el-dialog
|
||||
v-model="appointmentFormVisible"
|
||||
title="预约咨询"
|
||||
width="500px"
|
||||
append-to-body
|
||||
destroy-on-close
|
||||
>
|
||||
<el-form
|
||||
ref="appointmentFormRef"
|
||||
:model="appointmentForm"
|
||||
:rules="appointmentRules"
|
||||
label-width="100px"
|
||||
>
|
||||
<el-dialog v-model="appointmentFormVisible" title="预约咨询" width="500px" append-to-body destroy-on-close>
|
||||
<el-form ref="appointmentFormRef" :model="appointmentForm" :rules="appointmentRules" label-width="100px">
|
||||
<el-form-item label="咨询师" prop="counselorId">
|
||||
<el-input v-model="counselorDisplayName" disabled />
|
||||
</el-form-item>
|
||||
@@ -68,12 +57,7 @@
|
||||
<el-input v-model="studentName" disabled />
|
||||
</el-form-item>
|
||||
<el-form-item label="咨询原因" prop="reason">
|
||||
<el-input
|
||||
v-model="appointmentForm.reason"
|
||||
type="textarea"
|
||||
placeholder="请简要描述咨询原因"
|
||||
:rows="4"
|
||||
/>
|
||||
<el-input v-model="appointmentForm.reason" type="textarea" placeholder="请简要描述咨询原因" :rows="4" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
@@ -85,13 +69,7 @@
|
||||
</el-dialog>
|
||||
|
||||
<!-- 我的预约列表弹窗 -->
|
||||
<el-dialog
|
||||
v-model="myAppointmentsVisible"
|
||||
title="我的预约记录"
|
||||
width="800px"
|
||||
append-to-body
|
||||
destroy-on-close
|
||||
>
|
||||
<el-dialog v-model="myAppointmentsVisible" title="我的预约记录" width="800px" append-to-body destroy-on-close>
|
||||
<el-table :data="myAppointmentsList" border style="width: 100%">
|
||||
<el-table-column label="预约日期" prop="appointmentDate" width="120" />
|
||||
<el-table-column label="预约时段" prop="appointmentDetailTime" width="120" />
|
||||
@@ -100,12 +78,8 @@
|
||||
<el-table-column label="状态" prop="status" width="100">
|
||||
<template #default="scope">
|
||||
<!-- 添加调试信息 -->
|
||||
<div style="display: none;">
|
||||
状态值: {{ scope.row.status }}
|
||||
类型: {{ typeof scope.row.status }}
|
||||
字典数据: {{ appointment_status }}
|
||||
</div>
|
||||
|
||||
<div style="display: none">状态值: {{ scope.row.status }} 类型: {{ typeof scope.row.status }} 字典数据: {{ appointment_status }}</div>
|
||||
|
||||
<!-- 根据预约记录管理页面统一样式 -->
|
||||
<el-tag v-if="scope.row.status === 0 || scope.row.status === '0'" type="primary">待咨询</el-tag>
|
||||
<el-tag v-else-if="scope.row.status === 1 || scope.row.status === '1'" type="success">已到访</el-tag>
|
||||
@@ -117,22 +91,13 @@
|
||||
<el-table-column label="操作" width="120" align="center">
|
||||
<template #default="scope">
|
||||
<!-- 添加调试信息 -->
|
||||
<div style="display: none;">
|
||||
状态值: {{ scope.row.status }}
|
||||
类型: {{ typeof scope.row.status }}
|
||||
条件结果: {{ scope.row.status === 0 || scope.row.status === '0' }}
|
||||
<div style="display: none">
|
||||
状态值: {{ scope.row.status }} 类型: {{ typeof scope.row.status }} 条件结果: {{ scope.row.status === 0 || scope.row.status === '0' }}
|
||||
</div>
|
||||
|
||||
|
||||
<!-- 使用更直接的方式显示取消按钮 -->
|
||||
<template v-if="scope.row.status === 0 || scope.row.status === '0'">
|
||||
<el-button
|
||||
link
|
||||
type="danger"
|
||||
size="small"
|
||||
@click="cancelAppointment(scope.row)"
|
||||
>
|
||||
取消预约
|
||||
</el-button>
|
||||
<el-button link type="danger" size="small" @click="cancelAppointment(scope.row)"> 取消预约 </el-button>
|
||||
</template>
|
||||
<span v-else>--</span>
|
||||
</template>
|
||||
@@ -150,7 +115,15 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, onMounted, computed, watch } from 'vue';
|
||||
import { getAvailableTimeSlots, submitAppointment, getMyAppointments, updateAppointment, getStudentByNo, getCounselorOptions, getAvailableDetailSlots } from '@/api/xljk/consultation';
|
||||
import {
|
||||
getAvailableTimeSlots,
|
||||
submitAppointment,
|
||||
getMyAppointments,
|
||||
updateAppointment,
|
||||
getStudentByNo,
|
||||
getCounselorOptions,
|
||||
getAvailableDetailSlots
|
||||
} from '@/api/xljk/consultation';
|
||||
import { AvailableTimeSlot, XljkConsultationAppointmentForm, CounselorOption, TimeSlotDetail } from '@/api/xljk/consultation/types';
|
||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||
import { parseTime } from '@/utils/ruoyi';
|
||||
@@ -227,17 +200,17 @@ const counselorDisplayName = computed(() => {
|
||||
});
|
||||
|
||||
// 处理日期范围变化
|
||||
function handleDateRangeChange({ startDate, endDate }: { startDate: string, endDate: string }) {
|
||||
function handleDateRangeChange({ startDate, endDate }: { startDate: string; endDate: string }) {
|
||||
console.log(`日期范围变化:${startDate} 到 ${endDate},立即加载数据`);
|
||||
|
||||
|
||||
// 确保日期范围足够大,覆盖整个日历视图
|
||||
const startDateObj = new Date(startDate.replace(/-/g, '/'));
|
||||
const endDateObj = new Date(endDate.replace(/-/g, '/'));
|
||||
|
||||
|
||||
// 计算日期差(天数)
|
||||
const diffTime = endDateObj.getTime() - startDateObj.getTime();
|
||||
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
|
||||
|
||||
|
||||
// 如果日期范围小于42天(6周),则扩展结束日期
|
||||
if (diffDays < 42) {
|
||||
const expandedEndDate = new Date(startDateObj);
|
||||
@@ -245,7 +218,7 @@ function handleDateRangeChange({ startDate, endDate }: { startDate: string, endD
|
||||
endDate = parseTime(expandedEndDate, '{y}-{m}-{d}');
|
||||
console.log(`日期范围不足42天,扩展至:${startDate} 到 ${endDate}`);
|
||||
}
|
||||
|
||||
|
||||
loadAvailableSlots(startDate, endDate);
|
||||
}
|
||||
|
||||
@@ -260,7 +233,7 @@ async function loadAvailableSlots(startDate: string, endDate: string) {
|
||||
const res = await getAvailableTimeSlots(startDate, endDate);
|
||||
if (res.code === 200) {
|
||||
availableSlots.value = res.data || [];
|
||||
|
||||
|
||||
// 如果有可用时段但没有咨询师姓名,尝试加载咨询师信息
|
||||
if (availableSlots.value.length > 0 && !availableSlots.value[0].counselorName) {
|
||||
try {
|
||||
@@ -268,7 +241,7 @@ async function loadAvailableSlots(startDate: string, endDate: string) {
|
||||
if (counselorRes.code === 200 && counselorRes.data) {
|
||||
const counselors = counselorRes.data;
|
||||
// 为每个时段添加咨询师姓名
|
||||
availableSlots.value = availableSlots.value.map(slot => {
|
||||
availableSlots.value = availableSlots.value.map((slot) => {
|
||||
const counselor = counselors.find((c: CounselorOption) => c.id === slot.counselorId);
|
||||
if (counselor) {
|
||||
return { ...slot, counselorName: counselor.name };
|
||||
@@ -290,7 +263,7 @@ async function loadAvailableSlots(startDate: string, endDate: string) {
|
||||
// 处理时段点击
|
||||
async function handleSlotClick(slot: AvailableTimeSlot) {
|
||||
selectedSlot.value = slot;
|
||||
|
||||
|
||||
// 如果没有咨询师姓名,尝试获取咨询师信息
|
||||
if (!slot.counselorName && slot.counselorId) {
|
||||
try {
|
||||
@@ -306,7 +279,7 @@ async function handleSlotClick(slot: AvailableTimeSlot) {
|
||||
console.error('获取咨询师信息失败', error);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 填充预约表单
|
||||
appointmentForm.counselorId = slot.counselorId;
|
||||
appointmentForm.scheduleId = slot.scheduleId;
|
||||
@@ -314,10 +287,10 @@ async function handleSlotClick(slot: AvailableTimeSlot) {
|
||||
appointmentForm.appointmentTime = `${slot.startTime}-${slot.endTime}`;
|
||||
appointmentForm.appointmentDetailTime = ''; // 清空详细时段,等待用户选择
|
||||
appointmentForm.reason = ''; // 清空咨询原因,避免带出上次的内容
|
||||
|
||||
|
||||
// 加载详细时段
|
||||
await loadDetailTimeSlots(slot.date, slot.timeSlot);
|
||||
|
||||
|
||||
// 如果已登录且有用户ID,初始化学生信息
|
||||
if (userId.value) {
|
||||
// 初始设置为当前用户ID
|
||||
@@ -325,7 +298,7 @@ async function handleSlotClick(slot: AvailableTimeSlot) {
|
||||
studentName.value = nickname.value || '';
|
||||
studentNo.value = nickname.value || '';
|
||||
}
|
||||
|
||||
|
||||
appointmentFormVisible.value = true;
|
||||
}
|
||||
|
||||
@@ -350,16 +323,16 @@ async function loadDetailTimeSlots(date: string, timeSlot: string) {
|
||||
function getTimeSlotStyle(slot: TimeSlotDetail) {
|
||||
if (slot.isBooked) {
|
||||
// 已预约:灰色+删除线
|
||||
return {
|
||||
color: '#999',
|
||||
textDecoration: 'line-through',
|
||||
backgroundColor: '#f5f7fa'
|
||||
return {
|
||||
color: '#999',
|
||||
textDecoration: 'line-through',
|
||||
backgroundColor: '#f5f7fa'
|
||||
};
|
||||
} else if (slot.isExpired) {
|
||||
// 已过期:灰色,无删除线
|
||||
return {
|
||||
color: '#999',
|
||||
backgroundColor: '#f5f7fa'
|
||||
return {
|
||||
color: '#999',
|
||||
backgroundColor: '#f5f7fa'
|
||||
};
|
||||
} else {
|
||||
// 可预约:正常样式
|
||||
@@ -376,12 +349,12 @@ function getStatusText(status: string | number) {
|
||||
'2': '已取消',
|
||||
'3': '未到访'
|
||||
};
|
||||
|
||||
|
||||
// 将数值类型转为字符串
|
||||
if (typeof status === 'number') {
|
||||
status = status.toString();
|
||||
}
|
||||
|
||||
|
||||
return statusMap[status] || `未知(${status})`;
|
||||
}
|
||||
|
||||
@@ -394,12 +367,12 @@ function getStatusClass(status: string | number) {
|
||||
'2': 'text-info',
|
||||
'3': 'text-danger'
|
||||
};
|
||||
|
||||
|
||||
// 将数值类型转为字符串
|
||||
if (typeof status === 'number') {
|
||||
status = status.toString();
|
||||
}
|
||||
|
||||
|
||||
return classMap[status] || '';
|
||||
}
|
||||
|
||||
@@ -407,35 +380,35 @@ function getStatusClass(status: string | number) {
|
||||
function submitAppointmentForm() {
|
||||
appointmentFormRef.value?.validate(async (valid: boolean) => {
|
||||
if (!valid) return;
|
||||
|
||||
|
||||
if (!studentId.value) {
|
||||
ElMessage.warning('请先输入有效的学号');
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (!appointmentForm.appointmentDetailTime) {
|
||||
ElMessage.warning('请选择具体预约时段');
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
submitLoading.value = true;
|
||||
|
||||
|
||||
// 构建提交数据
|
||||
const submitData: XljkConsultationAppointmentForm = {
|
||||
...appointmentForm,
|
||||
studentId: studentId.value, // 不进行Number转换,保持原始值
|
||||
studentNo: studentNo.value // 添加学号
|
||||
};
|
||||
|
||||
|
||||
console.log('提交预约的studentId:', submitData.studentId);
|
||||
|
||||
|
||||
const res = await submitAppointment(submitData);
|
||||
if (res.code === 200) {
|
||||
ElMessage.success('预约成功');
|
||||
appointmentFormVisible.value = false;
|
||||
refreshCalendar(); // 刷新日历
|
||||
|
||||
|
||||
// 预约成功后,自动打开"我的预约"弹窗
|
||||
setTimeout(() => {
|
||||
viewMyAppointments();
|
||||
@@ -458,34 +431,34 @@ async function viewMyAppointments() {
|
||||
ElMessage.warning('请先登录');
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (!studentId.value) {
|
||||
ElMessage.warning('请先在学号输入框中输入学号并查询学生信息,或刷新页面让系统自动获取');
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
console.log('查询预约记录的studentId:', studentId.value, '类型:', typeof studentId.value);
|
||||
|
||||
|
||||
// 添加一个时间戳参数,强制绕过浏览器缓存
|
||||
const timestamp = new Date().getTime();
|
||||
console.log(`获取预约记录,添加时间戳${timestamp}强制刷新数据`);
|
||||
|
||||
|
||||
// 尝试多种类型转换,确保能正确查询到记录
|
||||
let id = studentId.value;
|
||||
|
||||
|
||||
// 尝试使用原始studentId查询,强制刷新数据
|
||||
console.log('尝试使用原始studentId查询:', id);
|
||||
let res = await getMyAppointments(id, true); // 强制刷新数据
|
||||
console.log('查询结果:', res);
|
||||
|
||||
|
||||
if (res.code === 200 && res.data && res.data.length > 0) {
|
||||
// 处理数据,确保状态字段是数值类型
|
||||
myAppointmentsList.value = res.data.map((item: any) => {
|
||||
console.log('原始记录项:', item);
|
||||
|
||||
|
||||
// 注意:移除了本地状态优先的逻辑,始终使用后端最新状态
|
||||
|
||||
|
||||
// 如果status是undefined或null,设置默认值为0
|
||||
if (item.status === undefined || item.status === null) {
|
||||
item.status = 0; // 默认为待咨询
|
||||
@@ -499,31 +472,31 @@ async function viewMyAppointments() {
|
||||
console.log('状态从字符串转换为数值:', statusNum);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
console.log('处理后的预约记录项:', item.id, '状态:', item.status, '类型:', typeof item.status);
|
||||
return item;
|
||||
});
|
||||
|
||||
|
||||
console.log('预约记录数据:', myAppointmentsList.value);
|
||||
console.log('字典数据:', appointment_status.value);
|
||||
myAppointmentsVisible.value = true;
|
||||
} else {
|
||||
console.log('原始ID查询无结果,尝试转换为数字类型');
|
||||
|
||||
|
||||
// 尝试转换为数字类型
|
||||
const numericId = Number(id);
|
||||
if (!isNaN(numericId)) {
|
||||
console.log('尝试使用数字类型studentId查询:', numericId);
|
||||
res = await getMyAppointments(numericId, true); // 强制刷新数据
|
||||
console.log('数字类型查询结果:', res);
|
||||
|
||||
|
||||
if (res.code === 200) {
|
||||
// 处理数据,确保状态字段是数值类型
|
||||
myAppointmentsList.value = (res.data || []).map((item: any) => {
|
||||
console.log('原始记录项:', item);
|
||||
|
||||
|
||||
// 注意:移除了本地状态优先的逻辑,始终使用后端最新状态
|
||||
|
||||
|
||||
// 如果status是undefined或null,设置默认值为0
|
||||
if (item.status === undefined || item.status === null) {
|
||||
item.status = 0; // 默认为待咨询
|
||||
@@ -537,20 +510,20 @@ async function viewMyAppointments() {
|
||||
console.log('状态从字符串转换为数值:', statusNum);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
console.log('处理后的预约记录项:', item.id, '状态:', item.status, '类型:', typeof item.status);
|
||||
return item;
|
||||
});
|
||||
|
||||
|
||||
console.log('预约记录数据:', myAppointmentsList.value);
|
||||
console.log('字典数据:', appointment_status.value);
|
||||
myAppointmentsVisible.value = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 无论是否找到记录,都显示弹窗
|
||||
myAppointmentsVisible.value = true;
|
||||
|
||||
|
||||
// 如果没有预约记录,显示提示
|
||||
if (myAppointmentsList.value.length === 0) {
|
||||
ElMessage.info('暂无预约记录');
|
||||
@@ -570,32 +543,32 @@ async function cancelAppointment(row: any) {
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
});
|
||||
|
||||
|
||||
// 记录原始行数据,用于调试
|
||||
console.log('取消预约的原始行数据:', row);
|
||||
|
||||
|
||||
const updateData = {
|
||||
id: row.id,
|
||||
status: 2 // 已取消,使用数值类型
|
||||
};
|
||||
|
||||
|
||||
console.log('发送取消预约请求:', updateData);
|
||||
|
||||
|
||||
const res = await updateAppointment(updateData);
|
||||
console.log('取消预约响应:', res);
|
||||
|
||||
|
||||
if (res.code === 200) {
|
||||
ElMessage.success('取消预约成功');
|
||||
|
||||
|
||||
// 直接更新本地数据,避免重新请求
|
||||
if (myAppointmentsList.value && myAppointmentsList.value.length > 0) {
|
||||
const index = myAppointmentsList.value.findIndex(item => item.id === row.id);
|
||||
const index = myAppointmentsList.value.findIndex((item) => item.id === row.id);
|
||||
if (index !== -1) {
|
||||
myAppointmentsList.value[index].status = 2;
|
||||
console.log('本地数据已更新,新状态:', myAppointmentsList.value[index].status);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 不再调用viewMyAppointments(),避免重新请求导致状态被覆盖
|
||||
// 只刷新日历,不刷新预约列表
|
||||
refreshCalendar();
|
||||
@@ -618,7 +591,7 @@ async function initCounselorInfo() {
|
||||
const counselors = res.data;
|
||||
// 存储咨询师信息,以备后用
|
||||
if (availableSlots.value.length > 0) {
|
||||
availableSlots.value = availableSlots.value.map(slot => {
|
||||
availableSlots.value = availableSlots.value.map((slot) => {
|
||||
const counselor = counselors.find((c: CounselorOption) => c.id === slot.counselorId);
|
||||
if (counselor) {
|
||||
return { ...slot, counselorName: counselor.name };
|
||||
@@ -638,7 +611,7 @@ async function refreshMyAppointments() {
|
||||
ElMessage.warning('无法获取学生ID');
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
refreshLoading.value = true;
|
||||
try {
|
||||
// 强制刷新数据
|
||||
@@ -657,7 +630,7 @@ async function refreshMyAppointments() {
|
||||
item.status = statusNum;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return item;
|
||||
});
|
||||
ElMessage.success('刷新成功');
|
||||
@@ -678,27 +651,27 @@ function refreshCalendar() {
|
||||
const now = new Date();
|
||||
// 获取当前月的第一天
|
||||
const firstDayOfMonth = new Date(now.getFullYear(), now.getMonth(), 1);
|
||||
|
||||
|
||||
// 获取当前月第一天所在周的周一(以确保完整覆盖当前月份)
|
||||
const firstDayOfWeek = firstDayOfMonth.getDay() || 7; // 转换为1-7,1是周一
|
||||
const firstDayOfCalendar = new Date(firstDayOfMonth);
|
||||
firstDayOfCalendar.setDate(firstDayOfCalendar.getDate() - (firstDayOfWeek - 1));
|
||||
|
||||
|
||||
// 计算显示6周需要的结束日期(从日历第一天开始算的42天后)
|
||||
const sixWeeksLater = new Date(firstDayOfCalendar);
|
||||
sixWeeksLater.setDate(sixWeeksLater.getDate() + 42); // 显示6周
|
||||
|
||||
|
||||
// 获取30天前的日期,确保能看到历史记录
|
||||
const thirtyDaysBefore = new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000);
|
||||
|
||||
|
||||
// 使用较早的日期作为开始日期
|
||||
const startDate = parseTime(thirtyDaysBefore < firstDayOfCalendar ? thirtyDaysBefore : firstDayOfCalendar, '{y}-{m}-{d}');
|
||||
|
||||
|
||||
// 结束日期取下月底和sixWeeksLater的较大者,确保完整覆盖日历视图
|
||||
const nextMonthEnd = new Date(now.getFullYear(), now.getMonth() + 2, 0); // 下个月的最后一天
|
||||
const endDateValue = new Date(Math.max(sixWeeksLater.getTime(), nextMonthEnd.getTime(), now.getTime() + 30 * 24 * 60 * 60 * 1000));
|
||||
const endDate = parseTime(endDateValue, '{y}-{m}-{d}');
|
||||
|
||||
|
||||
console.log(`刷新日历,日期范围:${startDate} 到 ${endDate},确保包含完整的6周显示`);
|
||||
loadAvailableSlots(startDate, endDate);
|
||||
}
|
||||
@@ -706,17 +679,17 @@ function refreshCalendar() {
|
||||
// 学号输入框失去焦点时查询学生信息
|
||||
async function handleStudentNoBlur() {
|
||||
if (!studentNo.value) return;
|
||||
|
||||
|
||||
try {
|
||||
console.log('查询学生信息,学号:', studentNo.value);
|
||||
const res = await getStudentByNo(studentNo.value);
|
||||
console.log('查询学生信息结果:', res);
|
||||
|
||||
|
||||
if (res.code === 200 && res.data) {
|
||||
studentId.value = res.data.id;
|
||||
studentName.value = res.data.name || '';
|
||||
console.log('获取到学生ID:', studentId.value, '类型:', typeof studentId.value);
|
||||
|
||||
|
||||
// 确保studentId是数字类型
|
||||
if (typeof studentId.value === 'string') {
|
||||
studentId.value = Number(studentId.value);
|
||||
@@ -737,10 +710,10 @@ async function handleStudentNoBlur() {
|
||||
onMounted(async () => {
|
||||
// 初始化咨询师信息
|
||||
initCounselorInfo();
|
||||
|
||||
|
||||
// 刷新日历,确保初次加载就包含完整的数据
|
||||
refreshCalendar();
|
||||
|
||||
|
||||
// 额外请求一次下个月的数据
|
||||
const now = new Date();
|
||||
const nextMonth = new Date(now.getFullYear(), now.getMonth() + 1, 1);
|
||||
@@ -749,23 +722,23 @@ onMounted(async () => {
|
||||
const endDate = parseTime(nextMonthEnd, '{y}-{m}-{d}');
|
||||
console.log(`额外请求下个月数据:${startDate} 到 ${endDate}`);
|
||||
loadAvailableSlots(startDate, endDate);
|
||||
|
||||
|
||||
// 自动获取学生信息(如果用户已登录且用户名为学号格式)
|
||||
if (userId.value && nickname.value) {
|
||||
console.log('当前用户信息:', { userId: userId.value, nickname: nickname.value });
|
||||
|
||||
|
||||
// 尝试直接使用nickname作为学号查询
|
||||
try {
|
||||
console.log('尝试使用nickname查询学生信息:', nickname.value);
|
||||
const res = await getStudentByNo(nickname.value);
|
||||
console.log('查询学生信息结果:', res);
|
||||
|
||||
|
||||
if (res.code === 200 && res.data) {
|
||||
studentId.value = res.data.id;
|
||||
studentName.value = res.data.name || '';
|
||||
studentNo.value = res.data.studentNo || nickname.value;
|
||||
console.log('自动获取学生信息成功:', res.data);
|
||||
|
||||
|
||||
// 查询是否有预约记录
|
||||
try {
|
||||
const appointmentRes = await getMyAppointments(studentId.value);
|
||||
@@ -775,13 +748,13 @@ onMounted(async () => {
|
||||
}
|
||||
} else {
|
||||
console.log('用户可能不是学生或学号格式不正确,尝试其他方式');
|
||||
|
||||
|
||||
// 尝试特定格式处理,例如"2025WRJ010林伟"格式可能需要提取前面的学号部分
|
||||
const studentNoMatch = nickname.value.match(/^(\d{4}[A-Za-z]{3}\d{3})/);
|
||||
if (studentNoMatch && studentNoMatch[1]) {
|
||||
const extractedStudentNo = studentNoMatch[1];
|
||||
console.log('尝试提取学号:', extractedStudentNo);
|
||||
|
||||
|
||||
try {
|
||||
const res2 = await getStudentByNo(extractedStudentNo);
|
||||
if (res2.code === 200 && res2.data) {
|
||||
@@ -795,7 +768,7 @@ onMounted(async () => {
|
||||
console.error('使用提取的学号查询失败:', e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 如果上述方法都失败,直接使用userId作为studentId(临时解决方案)
|
||||
studentId.value = userId.value;
|
||||
studentName.value = nickname.value || '';
|
||||
@@ -847,11 +820,11 @@ onMounted(async () => {
|
||||
|
||||
/* 状态文本样式 */
|
||||
.text-warning {
|
||||
color: #E6A23C;
|
||||
color: #e6a23c;
|
||||
}
|
||||
|
||||
.text-success {
|
||||
color: #67C23A;
|
||||
color: #67c23a;
|
||||
}
|
||||
|
||||
.text-info {
|
||||
@@ -859,6 +832,6 @@ onMounted(async () => {
|
||||
}
|
||||
|
||||
.text-danger {
|
||||
color: #F56C6C;
|
||||
color: #f56c6c;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
@@ -8,4 +8,4 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
// 主入口文件,用于路由跳转
|
||||
</script>
|
||||
</script>
|
||||
@@ -18,12 +18,7 @@
|
||||
<el-col :span="6">
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-select v-model="queryParams.status" placeholder="请选择状态" style="width: 100%" popper-class="noshow-status-popper">
|
||||
<el-option
|
||||
v-for="dict in limitationStatusOptions"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
<el-option v-for="dict in limitationStatusOptions" :key="dict.value" :label="dict.label" :value="dict.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
@@ -107,7 +102,7 @@
|
||||
v-hasPermi="['xljk:consultationnoshow:query']"
|
||||
></el-button>
|
||||
</el-tooltip>
|
||||
|
||||
|
||||
<!-- 生效中状态可解除限制 -->
|
||||
<el-tooltip content="解除限制" placement="top" v-if="scope.row.status === '0'">
|
||||
<el-button
|
||||
@@ -135,25 +130,27 @@
|
||||
<!-- 第一行:学生姓名、学号 -->
|
||||
<el-descriptions-item label="学生姓名">{{ form.studentName || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="学号">{{ form.studentNo || '-' }}</el-descriptions-item>
|
||||
|
||||
|
||||
<!-- 第二行:院系、专业 -->
|
||||
<el-descriptions-item label="院系">{{ form.collegeName || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="专业">{{ form.majorName || '-' }}</el-descriptions-item>
|
||||
|
||||
|
||||
<!-- 第三行:班级、咨询师 -->
|
||||
<el-descriptions-item label="班级">{{ form.className || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="咨询师">{{ form.counselorName || '-' }}</el-descriptions-item>
|
||||
|
||||
|
||||
<!-- 第四行:预约日期、预约时段 -->
|
||||
<el-descriptions-item label="预约日期">{{ form.appointmentDate ? parseTime(form.appointmentDate, '{y}-{m}-{d}') : '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="预约日期">{{
|
||||
form.appointmentDate ? parseTime(form.appointmentDate, '{y}-{m}-{d}') : '-'
|
||||
}}</el-descriptions-item>
|
||||
<el-descriptions-item label="预约时段">{{ form.appointmentDetailTime || '-' }}</el-descriptions-item>
|
||||
|
||||
|
||||
<!-- 第五行:咨询原因(占两列) -->
|
||||
<el-descriptions-item label="咨询原因" :span="2">{{ form.reason || '-' }}</el-descriptions-item>
|
||||
|
||||
|
||||
<!-- 第六行:未到访日期(占两列) -->
|
||||
<el-descriptions-item label="未到访日期" :span="2">{{ parseTime(form.noShowDate, '{y}-{m}-{d}') || '-' }}</el-descriptions-item>
|
||||
|
||||
|
||||
<!-- 最后一行:限制到期日、状态 -->
|
||||
<el-descriptions-item label="限制到期日">{{ form.expireDate ? parseTime(form.expireDate, '{y}-{m}-{d}') : '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="状态">
|
||||
@@ -166,12 +163,9 @@
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="viewOpen = false">关 闭</el-button>
|
||||
<el-button
|
||||
v-if="form.status === '0'"
|
||||
type="primary"
|
||||
@click="handleRelease(form)"
|
||||
v-hasPermi="['xljk:consultationnoshow:release']"
|
||||
>解除限制</el-button>
|
||||
<el-button v-if="form.status === '0'" type="primary" @click="handleRelease(form)" v-hasPermi="['xljk:consultationnoshow:release']"
|
||||
>解除限制</el-button
|
||||
>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
@@ -206,7 +200,7 @@ const queryParams = reactive<NoShowQuery>({
|
||||
pageSize: 10,
|
||||
studentName: undefined,
|
||||
studentNo: undefined,
|
||||
status: '0', // 默认查询"生效中"状态
|
||||
status: '0', // 默认查询"生效中"状态
|
||||
params: {
|
||||
startDate: undefined,
|
||||
endDate: undefined
|
||||
@@ -236,7 +230,7 @@ const form = reactive<NoShowVO>({
|
||||
|
||||
// 限制状态选项
|
||||
const limitationStatusOptions = ref([
|
||||
{ value: 'all', label: '全部' }, // 使用'all'代替空字符串
|
||||
{ value: 'all', label: '全部' }, // 使用'all'代替空字符串
|
||||
{ value: '0', label: '生效中' },
|
||||
{ value: '1', label: '已过期' },
|
||||
{ value: '2', label: '已解除' }
|
||||
@@ -247,12 +241,12 @@ const queryFormRef = ref();
|
||||
/** 查询未到访记录列表 */
|
||||
function getList() {
|
||||
loading.value = true;
|
||||
|
||||
|
||||
// 确保params对象存在
|
||||
if (!queryParams.params) {
|
||||
queryParams.params = {};
|
||||
}
|
||||
|
||||
|
||||
// 处理日期范围
|
||||
if (dateRange.value) {
|
||||
queryParams.params.startDate = dateRange.value[0];
|
||||
@@ -261,56 +255,58 @@ function getList() {
|
||||
queryParams.params.startDate = undefined;
|
||||
queryParams.params.endDate = undefined;
|
||||
}
|
||||
|
||||
|
||||
// 添加排序参数,实现倒序排列,按未到访日期排序
|
||||
queryParams.orderByColumn = 'noShowDate';
|
||||
queryParams.isAsc = 'desc';
|
||||
|
||||
|
||||
// 创建一个新的查询参数对象,避免修改原始对象
|
||||
const query = { ...queryParams };
|
||||
|
||||
listNoShow(query).then(response => {
|
||||
if (response.code === 200) {
|
||||
// 记录响应数据格式以便调试
|
||||
console.log('API响应数据:', response);
|
||||
|
||||
// 处理不同的数据结构可能性
|
||||
if (response.rows) {
|
||||
// 直接包含rows的情况
|
||||
noShowList.value = response.rows || [];
|
||||
total.value = response.total || 0;
|
||||
} else if (response.data) {
|
||||
if (Array.isArray(response.data)) {
|
||||
// 数据是数组的情况
|
||||
noShowList.value = response.data;
|
||||
total.value = response.data.length;
|
||||
} else if (response.data.rows) {
|
||||
// 标准分页格式
|
||||
noShowList.value = response.data.rows || [];
|
||||
total.value = response.data.total || 0;
|
||||
listNoShow(query)
|
||||
.then((response) => {
|
||||
if (response.code === 200) {
|
||||
// 记录响应数据格式以便调试
|
||||
console.log('API响应数据:', response);
|
||||
|
||||
// 处理不同的数据结构可能性
|
||||
if (response.rows) {
|
||||
// 直接包含rows的情况
|
||||
noShowList.value = response.rows || [];
|
||||
total.value = response.total || 0;
|
||||
} else if (response.data) {
|
||||
if (Array.isArray(response.data)) {
|
||||
// 数据是数组的情况
|
||||
noShowList.value = response.data;
|
||||
total.value = response.data.length;
|
||||
} else if (response.data.rows) {
|
||||
// 标准分页格式
|
||||
noShowList.value = response.data.rows || [];
|
||||
total.value = response.data.total || 0;
|
||||
} else {
|
||||
// 其他情况,可能data本身就是数据
|
||||
noShowList.value = [].concat(response.data || []);
|
||||
total.value = [].concat(response.data || []).length;
|
||||
}
|
||||
} else {
|
||||
// 其他情况,可能data本身就是数据
|
||||
noShowList.value = [].concat(response.data || []);
|
||||
total.value = [].concat(response.data || []).length;
|
||||
// 没有找到数据
|
||||
noShowList.value = [];
|
||||
total.value = 0;
|
||||
}
|
||||
} else {
|
||||
// 没有找到数据
|
||||
ElMessage.error(response.msg || '获取未到访记录列表失败');
|
||||
noShowList.value = [];
|
||||
total.value = 0;
|
||||
}
|
||||
} else {
|
||||
ElMessage.error(response.msg || '获取未到访记录列表失败');
|
||||
loading.value = false;
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('获取未到访记录列表失败', error);
|
||||
ElMessage.error('获取未到访记录列表失败,请稍后重试');
|
||||
noShowList.value = [];
|
||||
total.value = 0;
|
||||
}
|
||||
loading.value = false;
|
||||
}).catch(error => {
|
||||
console.error('获取未到访记录列表失败', error);
|
||||
ElMessage.error('获取未到访记录列表失败,请稍后重试');
|
||||
noShowList.value = [];
|
||||
total.value = 0;
|
||||
loading.value = false;
|
||||
});
|
||||
loading.value = false;
|
||||
});
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
@@ -323,14 +319,14 @@ function handleQuery() {
|
||||
function resetQuery() {
|
||||
dateRange.value = undefined;
|
||||
queryFormRef.value?.resetFields();
|
||||
|
||||
|
||||
// 确保params对象存在并重置日期范围
|
||||
if (!queryParams.params) {
|
||||
queryParams.params = {};
|
||||
}
|
||||
queryParams.params.startDate = undefined;
|
||||
queryParams.params.endDate = undefined;
|
||||
|
||||
|
||||
// 确保重置后状态仍为"生效中"
|
||||
queryParams.status = '0';
|
||||
handleQuery();
|
||||
@@ -340,29 +336,31 @@ function resetQuery() {
|
||||
function handleView(row: NoShowVO) {
|
||||
const id = row.id;
|
||||
loading.value = true;
|
||||
getNoShow(id).then(response => {
|
||||
if (response.code === 200) {
|
||||
console.log('获取详情响应:', response);
|
||||
// 处理不同的数据结构可能性
|
||||
if (response.data) {
|
||||
Object.assign(form, response.data);
|
||||
getNoShow(id)
|
||||
.then((response) => {
|
||||
if (response.code === 200) {
|
||||
console.log('获取详情响应:', response);
|
||||
// 处理不同的数据结构可能性
|
||||
if (response.data) {
|
||||
Object.assign(form, response.data);
|
||||
} else {
|
||||
// 可能直接返回了数据对象
|
||||
const data = { ...response };
|
||||
delete data.code;
|
||||
delete data.msg;
|
||||
Object.assign(form, data);
|
||||
}
|
||||
viewOpen.value = true;
|
||||
} else {
|
||||
// 可能直接返回了数据对象
|
||||
const data = {...response};
|
||||
delete data.code;
|
||||
delete data.msg;
|
||||
Object.assign(form, data);
|
||||
ElMessage.error(response.msg || '获取未到访记录详情失败');
|
||||
}
|
||||
viewOpen.value = true;
|
||||
} else {
|
||||
ElMessage.error(response.msg || '获取未到访记录详情失败');
|
||||
}
|
||||
loading.value = false;
|
||||
}).catch(error => {
|
||||
console.error('获取未到访记录详情失败', error);
|
||||
ElMessage.error('获取未到访记录详情失败,请稍后重试');
|
||||
loading.value = false;
|
||||
});
|
||||
loading.value = false;
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('获取未到访记录详情失败', error);
|
||||
ElMessage.error('获取未到访记录详情失败,请稍后重试');
|
||||
loading.value = false;
|
||||
});
|
||||
}
|
||||
|
||||
/** 解除限制按钮操作 */
|
||||
@@ -372,25 +370,29 @@ function handleRelease(row: NoShowVO) {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
loading.value = true;
|
||||
releaseNoShowLimitation(id).then(response => {
|
||||
if (response.code === 200) {
|
||||
ElMessage.success('解除限制成功');
|
||||
getList();
|
||||
if (viewOpen.value) {
|
||||
viewOpen.value = false;
|
||||
}
|
||||
} else {
|
||||
ElMessage.error(response.msg || '解除限制失败');
|
||||
}
|
||||
loading.value = false;
|
||||
}).catch(error => {
|
||||
console.error('解除限制失败', error);
|
||||
ElMessage.error('解除限制失败,请稍后重试');
|
||||
loading.value = false;
|
||||
});
|
||||
}).catch(() => {});
|
||||
})
|
||||
.then(() => {
|
||||
loading.value = true;
|
||||
releaseNoShowLimitation(id)
|
||||
.then((response) => {
|
||||
if (response.code === 200) {
|
||||
ElMessage.success('解除限制成功');
|
||||
getList();
|
||||
if (viewOpen.value) {
|
||||
viewOpen.value = false;
|
||||
}
|
||||
} else {
|
||||
ElMessage.error(response.msg || '解除限制失败');
|
||||
}
|
||||
loading.value = false;
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('解除限制失败', error);
|
||||
ElMessage.error('解除限制失败,请稍后重试');
|
||||
loading.value = false;
|
||||
});
|
||||
})
|
||||
.catch(() => {});
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
@@ -443,4 +445,4 @@ onMounted(() => {
|
||||
justify-content: flex-end;
|
||||
gap: 10px;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
@@ -8,12 +8,7 @@
|
||||
<el-col :span="6">
|
||||
<el-form-item label="咨询师" prop="counselorId">
|
||||
<el-select v-model="queryParams.counselorId" placeholder="请选择咨询师" clearable style="width: 100%">
|
||||
<el-option
|
||||
v-for="item in counselorOptions"
|
||||
:key="item.id"
|
||||
:label="item.name"
|
||||
:value="item.id"
|
||||
/>
|
||||
<el-option v-for="item in counselorOptions" :key="item.id" :label="item.name" :value="item.id" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
@@ -138,12 +133,7 @@
|
||||
<el-form ref="scheduleFormRef" :model="form" :rules="rules" label-width="100px">
|
||||
<el-form-item label="咨询师" prop="counselorId">
|
||||
<el-select v-model="form.counselorId" placeholder="请选择咨询师" style="width: 100%">
|
||||
<el-option
|
||||
v-for="item in counselorOptions"
|
||||
:key="item.id"
|
||||
:label="item.name"
|
||||
:value="item.id"
|
||||
/>
|
||||
<el-option v-for="item in counselorOptions" :key="item.id" :label="item.name" :value="item.id" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="排班日期" prop="dateRange" v-if="!form.id">
|
||||
@@ -158,15 +148,9 @@
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="排班日期" prop="scheduleDate" v-else>
|
||||
<el-date-picker
|
||||
v-model="form.scheduleDate"
|
||||
type="date"
|
||||
placeholder="选择日期"
|
||||
value-format="YYYY-MM-DD"
|
||||
style="width: 100%"
|
||||
/>
|
||||
<el-date-picker v-model="form.scheduleDate" type="date" placeholder="选择日期" value-format="YYYY-MM-DD" style="width: 100%" />
|
||||
</el-form-item>
|
||||
|
||||
|
||||
<!-- 上午时段设置 -->
|
||||
<el-divider content-position="left">上午时段</el-divider>
|
||||
<el-form-item label="启用上午" prop="morningEnabled">
|
||||
@@ -194,7 +178,7 @@
|
||||
/>
|
||||
</el-form-item>
|
||||
</template>
|
||||
|
||||
|
||||
<!-- 下午时段设置 -->
|
||||
<el-divider content-position="left">下午时段</el-divider>
|
||||
<el-form-item label="启用下午" prop="afternoonEnabled">
|
||||
@@ -222,7 +206,7 @@
|
||||
/>
|
||||
</el-form-item>
|
||||
</template>
|
||||
|
||||
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-radio-group v-model="form.status">
|
||||
<el-radio label="0">正常</el-radio>
|
||||
@@ -243,7 +227,12 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, onMounted, computed, watch } from 'vue';
|
||||
import { listSchedule, getSchedule, delSchedule, addSchedule, updateSchedule, getCounselorOptions } from '@/api/xljk/consultation';
|
||||
import { XljkConsultationScheduleVO, XljkConsultationScheduleForm, CounselorOption, XljkConsultationScheduleSummaryVO } from '@/api/xljk/consultation/types';
|
||||
import {
|
||||
XljkConsultationScheduleVO,
|
||||
XljkConsultationScheduleForm,
|
||||
CounselorOption,
|
||||
XljkConsultationScheduleSummaryVO
|
||||
} from '@/api/xljk/consultation/types';
|
||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||
import { parseTime } from '@/utils/ruoyi';
|
||||
|
||||
@@ -393,27 +382,27 @@ const reset = () => {
|
||||
form.startTime = '';
|
||||
form.endTime = '';
|
||||
form.status = '0';
|
||||
|
||||
|
||||
startTimeTemp.value = null;
|
||||
endTimeTemp.value = null;
|
||||
|
||||
|
||||
// 设置默认时间
|
||||
const morningStart = new Date();
|
||||
morningStart.setHours(8, 30, 0, 0);
|
||||
morningStartTimeTemp.value = morningStart;
|
||||
|
||||
|
||||
const morningEnd = new Date();
|
||||
morningEnd.setHours(12, 0, 0, 0);
|
||||
morningEndTimeTemp.value = morningEnd;
|
||||
|
||||
|
||||
const afternoonStart = new Date();
|
||||
afternoonStart.setHours(14, 30, 0, 0);
|
||||
afternoonStartTimeTemp.value = afternoonStart;
|
||||
|
||||
|
||||
const afternoonEnd = new Date();
|
||||
afternoonEnd.setHours(17, 0, 0, 0);
|
||||
afternoonEndTimeTemp.value = afternoonEnd;
|
||||
|
||||
|
||||
scheduleFormRef.value?.resetFields();
|
||||
};
|
||||
|
||||
@@ -451,20 +440,20 @@ const handleUpdate = async (row?: XljkConsultationScheduleSummaryVO) => {
|
||||
try {
|
||||
const res = await getSchedule(_id);
|
||||
Object.assign(form, res.data);
|
||||
|
||||
|
||||
// 设置时间选择器的值
|
||||
if (form.startTime) {
|
||||
const [hours, minutes] = form.startTime.split(':').map(Number);
|
||||
startTimeTemp.value = new Date();
|
||||
startTimeTemp.value.setHours(hours, minutes, 0, 0);
|
||||
}
|
||||
|
||||
|
||||
if (form.endTime) {
|
||||
const [hours, minutes] = form.endTime.split(':').map(Number);
|
||||
endTimeTemp.value = new Date();
|
||||
endTimeTemp.value.setHours(hours, minutes, 0, 0);
|
||||
}
|
||||
|
||||
|
||||
open.value = true;
|
||||
title.value = '修改预约时段';
|
||||
} catch (error) {
|
||||
@@ -475,10 +464,10 @@ const handleUpdate = async (row?: XljkConsultationScheduleSummaryVO) => {
|
||||
/** 提交按钮 */
|
||||
const submitForm = async () => {
|
||||
if (!scheduleFormRef.value) return;
|
||||
|
||||
|
||||
await scheduleFormRef.value.validate(async (valid: boolean) => {
|
||||
if (!valid) return;
|
||||
|
||||
|
||||
try {
|
||||
if (form.id) {
|
||||
// 修改单个预约时段
|
||||
@@ -498,21 +487,21 @@ const submitForm = async () => {
|
||||
ElMessage.warning('请选择排班日期范围');
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
const startDate = new Date(form.dateRange[0]);
|
||||
const endDate = new Date(form.dateRange[1]);
|
||||
|
||||
|
||||
if (startDate > endDate) {
|
||||
ElMessage.warning('开始日期不能大于结束日期');
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// 检查是否至少启用了一个时段
|
||||
if (!form.morningEnabled && !form.afternoonEnabled) {
|
||||
ElMessage.warning('请至少启用一个时段(上午或下午)');
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// 批量创建预约时段的请求数据
|
||||
const batchCreateData = {
|
||||
counselorId: form.counselorId,
|
||||
@@ -527,7 +516,7 @@ const submitForm = async () => {
|
||||
status: form.status,
|
||||
skipWeekends: true // 自动跳过周末
|
||||
};
|
||||
|
||||
|
||||
// 调用批量创建API
|
||||
const res = await addSchedule(batchCreateData);
|
||||
ElMessage.success(`批量创建成功,共创建${res.data || 0}条预约时段`);
|
||||
@@ -543,17 +532,17 @@ const submitForm = async () => {
|
||||
/** 删除按钮操作 */
|
||||
const handleDelete = async (row?: XljkConsultationScheduleSummaryVO) => {
|
||||
const _ids = row?.id || ids.value;
|
||||
const confirmMessage = row
|
||||
const confirmMessage = row
|
||||
? `是否确认删除该预约时段?${row.scheduleCount > 1 ? `(包含${row.scheduleCount}条记录)` : ''}`
|
||||
: `是否确认删除选中的${ids.value.length}条数据?`;
|
||||
|
||||
|
||||
try {
|
||||
await ElMessageBox.confirm(confirmMessage, '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
});
|
||||
|
||||
|
||||
await delSchedule(_ids);
|
||||
ElMessage.success('删除成功');
|
||||
getList();
|
||||
@@ -674,4 +663,4 @@ onMounted(() => {
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
@@ -11,23 +11,12 @@
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="测评名称" prop="evaluationName">
|
||||
<el-input
|
||||
v-model="form.evaluationName"
|
||||
placeholder="请输入测评名称"
|
||||
style="width: 100%"
|
||||
maxlength="100"
|
||||
show-word-limit
|
||||
/>
|
||||
<el-input v-model="form.evaluationName" placeholder="请输入测评名称" style="width: 100%" maxlength="100" show-word-limit />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="学期" prop="semester">
|
||||
<el-input
|
||||
v-model="form.semester"
|
||||
placeholder="请输入学期,如:2024上学期"
|
||||
style="width: 100%"
|
||||
maxlength="50"
|
||||
/>
|
||||
<el-input v-model="form.semester" placeholder="请输入学期,如:2024上学期" style="width: 100%" maxlength="50" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
@@ -35,38 +24,28 @@
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="院系" prop="departmentName">
|
||||
<el-select
|
||||
v-model="form.departmentName"
|
||||
placeholder="请选择院系"
|
||||
style="width: 100%"
|
||||
<el-select
|
||||
v-model="form.departmentName"
|
||||
placeholder="请选择院系"
|
||||
style="width: 100%"
|
||||
@change="handleCollegeChange"
|
||||
:loading="collegeLoading"
|
||||
>
|
||||
<el-option
|
||||
v-for="college in collegeOptions"
|
||||
:key="college.id"
|
||||
:label="college.name"
|
||||
:value="college.name"
|
||||
/>
|
||||
<el-option v-for="college in collegeOptions" :key="college.id" :label="college.name" :value="college.name" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="专业" prop="majorName">
|
||||
<el-select
|
||||
v-model="form.majorName"
|
||||
placeholder="请选择专业"
|
||||
style="width: 100%"
|
||||
<el-select
|
||||
v-model="form.majorName"
|
||||
placeholder="请选择专业"
|
||||
style="width: 100%"
|
||||
@change="handleMajorChange"
|
||||
:disabled="!form.departmentName"
|
||||
:loading="majorLoading"
|
||||
>
|
||||
<el-option
|
||||
v-for="major in majorOptions"
|
||||
:key="major.id"
|
||||
:label="major.name"
|
||||
:value="major.name"
|
||||
/>
|
||||
<el-option v-for="major in majorOptions" :key="major.id" :label="major.name" :value="major.name" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
@@ -75,31 +54,25 @@
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="班级" prop="className">
|
||||
<el-select
|
||||
v-model="form.className"
|
||||
placeholder="请选择班级"
|
||||
<el-select
|
||||
v-model="form.className"
|
||||
placeholder="请选择班级"
|
||||
style="width: 100%"
|
||||
@change="handleClassChange"
|
||||
:disabled="!form.majorName"
|
||||
:loading="classLoading"
|
||||
>
|
||||
<el-option
|
||||
v-for="cls in classOptions"
|
||||
:key="cls.id"
|
||||
:label="cls.name"
|
||||
:value="cls.name"
|
||||
:data-id="cls.id"
|
||||
/>
|
||||
<el-option v-for="cls in classOptions" :key="cls.id" :label="cls.name" :value="cls.name" :data-id="cls.id" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="备注" prop="remark">
|
||||
<el-input
|
||||
v-model="form.remark"
|
||||
type="textarea"
|
||||
placeholder="请输入备注信息"
|
||||
style="width: 100%"
|
||||
<el-input
|
||||
v-model="form.remark"
|
||||
type="textarea"
|
||||
placeholder="请输入备注信息"
|
||||
style="width: 100%"
|
||||
:rows="1"
|
||||
maxlength="500"
|
||||
show-word-limit
|
||||
@@ -116,7 +89,7 @@
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
|
||||
|
||||
<el-card shadow="hover" class="mb-[10px]" v-if="form.className">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
@@ -126,16 +99,10 @@
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
<div v-loading="studentsLoading">
|
||||
<el-alert
|
||||
v-if="studentList.length === 0 && !studentsLoading"
|
||||
title="未找到该班级的学生信息"
|
||||
type="warning"
|
||||
show-icon
|
||||
:closable="false"
|
||||
/>
|
||||
|
||||
<el-alert v-if="studentList.length === 0 && !studentsLoading" title="未找到该班级的学生信息" type="warning" show-icon :closable="false" />
|
||||
|
||||
<!-- 批量操作区域 -->
|
||||
<div class="batch-actions" v-if="studentList.length > 0">
|
||||
<span class="batch-label">批量设置心理状态:</span>
|
||||
@@ -147,24 +114,13 @@
|
||||
<el-button type="primary" size="small" @click="applyBatchMentalStatus" :disabled="selectedStudents.length === 0">
|
||||
应用到所选学生
|
||||
</el-button>
|
||||
<el-button type="info" size="small" @click="applyBatchMentalStatusToAll">
|
||||
应用到全部学生
|
||||
</el-button>
|
||||
<el-button type="warning" size="small" @click="resetAllMentalStatus">
|
||||
重置所有状态
|
||||
</el-button>
|
||||
<el-button type="info" size="small" @click="applyBatchMentalStatusToAll"> 应用到全部学生 </el-button>
|
||||
<el-button type="warning" size="small" @click="resetAllMentalStatus"> 重置所有状态 </el-button>
|
||||
<span class="batch-hint">已选择 {{ selectedStudents.length }} 名学生</span>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- 学生列表表格 -->
|
||||
<el-table
|
||||
v-if="studentList.length > 0"
|
||||
:data="studentList"
|
||||
border
|
||||
stripe
|
||||
style="width: 100%"
|
||||
@selection-change="handleSelectionChange"
|
||||
>
|
||||
<el-table v-if="studentList.length > 0" :data="studentList" border stripe style="width: 100%" @selection-change="handleSelectionChange">
|
||||
<el-table-column type="selection" width="55" />
|
||||
<el-table-column label="学号" align="center" prop="studentNo" width="120" />
|
||||
<el-table-column label="姓名" align="center" prop="studentName" width="100" />
|
||||
@@ -181,59 +137,49 @@
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="联系电话" align="center" prop="phone" width="120" />
|
||||
|
||||
|
||||
<!-- 心理状态选择 -->
|
||||
<el-table-column label="心理状态" align="center" width="180">
|
||||
<template #default="scope">
|
||||
<el-select
|
||||
v-model="scope.row.mentalStatus"
|
||||
placeholder="请选择心理状态"
|
||||
@change="handleMentalStatusChange(scope.row)"
|
||||
>
|
||||
<el-select v-model="scope.row.mentalStatus" placeholder="请选择心理状态" @change="handleMentalStatusChange(scope.row)">
|
||||
<el-option label="健康" value="1" />
|
||||
<el-option label="良好" value="2" />
|
||||
<el-option label="重点关注" value="3" />
|
||||
</el-select>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
|
||||
<!-- 分数输入,仅当心理状态为"重点关注"时显示 -->
|
||||
<el-table-column label="分数" align="center" width="120">
|
||||
<template #default="scope">
|
||||
<el-input-number
|
||||
v-if="scope.row.mentalStatus === '3'"
|
||||
v-model="scope.row.score"
|
||||
:min="0"
|
||||
:max="100"
|
||||
size="small"
|
||||
/>
|
||||
<el-input-number v-if="scope.row.mentalStatus === '3'" v-model="scope.row.score" :min="0" :max="100" size="small" />
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
|
||||
<!-- 备注 -->
|
||||
<el-table-column label="备注" align="center">
|
||||
<template #default="scope">
|
||||
<el-input
|
||||
v-model="scope.row.remark"
|
||||
type="text"
|
||||
placeholder="备注信息"
|
||||
size="small"
|
||||
/>
|
||||
<el-input v-model="scope.row.remark" type="text" placeholder="备注信息" size="small" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<div class="student-count" v-if="studentList.length > 0">
|
||||
共 {{ studentList.length }} 名学生
|
||||
</div>
|
||||
|
||||
<div class="student-count" v-if="studentList.length > 0">共 {{ studentList.length }} 名学生</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup name="XljkEvaluationAdd" lang="ts">
|
||||
import { addEvaluation, getCollegeOptions, getMajorOptions, getClassOptions, getStudentsByClass, batchAddEvaluationDetails } from '@/api/xljk/evaluation';
|
||||
import {
|
||||
addEvaluation,
|
||||
getCollegeOptions,
|
||||
getMajorOptions,
|
||||
getClassOptions,
|
||||
getStudentsByClass,
|
||||
batchAddEvaluationDetails
|
||||
} from '@/api/xljk/evaluation';
|
||||
import { XljkEvaluationForm, StudentInfo, CollegeOption, MajorOption, ClassOption } from '@/api/xljk/evaluation/types';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||
@@ -256,7 +202,7 @@ const generateCurrentSemester = () => {
|
||||
const now = new Date();
|
||||
const year = now.getFullYear();
|
||||
const month = now.getMonth() + 1; // 月份从0开始
|
||||
|
||||
|
||||
// 上半年(1-7月)为下学期,下半年(8-12月)为上学期
|
||||
if (month >= 8) {
|
||||
return `${year}上学期`;
|
||||
@@ -284,7 +230,7 @@ const form = ref<XljkEvaluationForm>({
|
||||
const generateEvaluationName = () => {
|
||||
const semester = form.value.semester || '';
|
||||
const className = form.value.className || '';
|
||||
|
||||
|
||||
if (semester && className) {
|
||||
return `${semester}${className}心理健康测评`;
|
||||
}
|
||||
@@ -292,11 +238,14 @@ const generateEvaluationName = () => {
|
||||
};
|
||||
|
||||
// 监听学期变化,自动更新测评名称
|
||||
watch(() => form.value.semester, (newSemester) => {
|
||||
if (newSemester && form.value.className) {
|
||||
form.value.evaluationName = generateEvaluationName();
|
||||
watch(
|
||||
() => form.value.semester,
|
||||
(newSemester) => {
|
||||
if (newSemester && form.value.className) {
|
||||
form.value.evaluationName = generateEvaluationName();
|
||||
}
|
||||
}
|
||||
});
|
||||
);
|
||||
|
||||
// 表单验证规则
|
||||
const rules = {
|
||||
@@ -320,12 +269,12 @@ const getColleges = async () => {
|
||||
formLoading.value = true;
|
||||
const res = await getCollegeOptions();
|
||||
collegeOptions.value = res.data || [];
|
||||
|
||||
|
||||
// 如果获取到院系选项,自动选择第一个
|
||||
if (collegeOptions.value.length > 0) {
|
||||
form.value.departmentName = collegeOptions.value[0].name;
|
||||
form.value.departmentId = collegeOptions.value[0].id;
|
||||
|
||||
|
||||
// 获取对应的专业选项
|
||||
await getMajors(form.value.departmentName);
|
||||
}
|
||||
@@ -344,12 +293,12 @@ const getMajors = async (collegeName: string) => {
|
||||
formLoading.value = true;
|
||||
const res = await getMajorOptions(collegeName);
|
||||
majorOptions.value = res.data || [];
|
||||
|
||||
|
||||
// 如果获取到专业选项,自动选择第一个
|
||||
if (majorOptions.value.length > 0) {
|
||||
form.value.majorName = majorOptions.value[0].name;
|
||||
form.value.majorId = majorOptions.value[0].id;
|
||||
|
||||
|
||||
// 获取对应的班级选项
|
||||
await getClasses(form.value.departmentName, form.value.majorName);
|
||||
}
|
||||
@@ -368,12 +317,12 @@ const getClasses = async (collegeName: string, majorName: string) => {
|
||||
formLoading.value = true;
|
||||
const res = await getClassOptions(collegeName, majorName);
|
||||
classOptions.value = res.data || [];
|
||||
|
||||
|
||||
// 如果获取到班级选项,自动选择第一个
|
||||
if (classOptions.value.length > 0) {
|
||||
form.value.className = classOptions.value[0].name;
|
||||
form.value.classId = classOptions.value[0].id;
|
||||
|
||||
|
||||
// 获取班级学生列表
|
||||
await handleClassChange(form.value.className);
|
||||
}
|
||||
@@ -393,7 +342,7 @@ const handleCollegeChange = async (collegeName: string) => {
|
||||
majorOptions.value = [];
|
||||
classOptions.value = [];
|
||||
studentList.value = [];
|
||||
|
||||
|
||||
if (collegeName) {
|
||||
// 获取该院系下的专业
|
||||
await getMajors(collegeName);
|
||||
@@ -406,7 +355,7 @@ const handleMajorChange = async (majorName: string) => {
|
||||
form.value.className = undefined;
|
||||
classOptions.value = [];
|
||||
studentList.value = [];
|
||||
|
||||
|
||||
if (majorName && form.value.departmentName) {
|
||||
// 获取专业下的班级
|
||||
await getClasses(form.value.departmentName, majorName);
|
||||
@@ -419,22 +368,22 @@ const handleClassChange = async (className: string) => {
|
||||
studentList.value = [];
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// 设置班级ID
|
||||
const selectedClass = classOptions.value.find(c => c.name === className);
|
||||
const selectedClass = classOptions.value.find((c) => c.name === className);
|
||||
if (selectedClass) {
|
||||
form.value.classId = selectedClass.id;
|
||||
}
|
||||
|
||||
|
||||
// 自动生成测评名称
|
||||
form.value.evaluationName = generateEvaluationName();
|
||||
|
||||
|
||||
// 获取班级学生列表
|
||||
try {
|
||||
studentsLoading.value = true;
|
||||
const res = await getStudentsByClass(form.value.departmentName, form.value.majorName, className);
|
||||
// 设置默认的心理状态为"健康"
|
||||
studentList.value = (res.data || []).map(student => ({
|
||||
studentList.value = (res.data || []).map((student) => ({
|
||||
...student,
|
||||
mentalStatus: '1', // 默认设置为"健康"
|
||||
remark: '' // 初始化备注为空字符串
|
||||
@@ -460,13 +409,13 @@ const handleMentalStatusChange = (student: StudentInfo) => {
|
||||
|
||||
/** 统计重点关注学生数量 */
|
||||
const countFocusStudents = () => {
|
||||
return studentList.value.filter(student => student.mentalStatus === '3').length;
|
||||
return studentList.value.filter((student) => student.mentalStatus === '3').length;
|
||||
};
|
||||
|
||||
/** 批量设置心理状态 */
|
||||
const applyBatchMentalStatus = () => {
|
||||
const newStatus = batchMentalStatus.value;
|
||||
selectedStudents.value.forEach(student => {
|
||||
selectedStudents.value.forEach((student) => {
|
||||
student.mentalStatus = newStatus;
|
||||
// 如果心理状态是"重点关注",设置默认分数
|
||||
if (newStatus === '3') {
|
||||
@@ -480,7 +429,7 @@ const applyBatchMentalStatus = () => {
|
||||
/** 批量设置心理状态到全部学生 */
|
||||
const applyBatchMentalStatusToAll = () => {
|
||||
const newStatus = batchMentalStatus.value;
|
||||
studentList.value.forEach(student => {
|
||||
studentList.value.forEach((student) => {
|
||||
student.mentalStatus = newStatus;
|
||||
// 如果心理状态是"重点关注",设置默认分数
|
||||
if (newStatus === '3') {
|
||||
@@ -497,19 +446,21 @@ const resetAllMentalStatus = () => {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
studentList.value.forEach(student => {
|
||||
student.mentalStatus = '1'; // 重置为"健康"
|
||||
student.score = undefined; // 清空分数
|
||||
student.remark = ''; // 清空备注
|
||||
})
|
||||
.then(() => {
|
||||
studentList.value.forEach((student) => {
|
||||
student.mentalStatus = '1'; // 重置为"健康"
|
||||
student.score = undefined; // 清空分数
|
||||
student.remark = ''; // 清空备注
|
||||
});
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: '已重置所有学生的心理状态'
|
||||
});
|
||||
})
|
||||
.catch(() => {
|
||||
// 取消操作
|
||||
});
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: '已重置所有学生的心理状态'
|
||||
});
|
||||
}).catch(() => {
|
||||
// 取消操作
|
||||
});
|
||||
};
|
||||
|
||||
/** 处理表格多选 */
|
||||
@@ -530,49 +481,49 @@ const submitForm = () => {
|
||||
submitLoading.value = false;
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (!form.value.majorName && form.value.majorId) {
|
||||
// 找不到majorName但有majorId的情况处理
|
||||
proxy?.$modal.msgError('专业名称不能为空');
|
||||
submitLoading.value = false;
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (!form.value.className && form.value.classId) {
|
||||
// 找不到className但有classId的情况处理
|
||||
proxy?.$modal.msgError('班级名称不能为空');
|
||||
submitLoading.value = false;
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// 确保ID字段有值,使用从选项中获取的ID
|
||||
if (form.value.departmentName) {
|
||||
const selectedCollege = collegeOptions.value.find(c => c.name === form.value.departmentName);
|
||||
const selectedCollege = collegeOptions.value.find((c) => c.name === form.value.departmentName);
|
||||
if (selectedCollege) {
|
||||
form.value.departmentId = selectedCollege.id;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (form.value.majorName) {
|
||||
const selectedMajor = majorOptions.value.find(m => m.name === form.value.majorName);
|
||||
const selectedMajor = majorOptions.value.find((m) => m.name === form.value.majorName);
|
||||
if (selectedMajor) {
|
||||
form.value.majorId = selectedMajor.id;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 班级ID已在handleClassChange中设置
|
||||
|
||||
|
||||
// 先创建测评
|
||||
const res = await addEvaluation(form.value);
|
||||
const evaluationId = res.data;
|
||||
|
||||
|
||||
if (evaluationId) {
|
||||
// 统计学生人数和重点关注学生数量
|
||||
const studentCount = studentList.value.length;
|
||||
const focusCount = countFocusStudents();
|
||||
|
||||
|
||||
// 准备学生测评明细数据
|
||||
const detailList = studentList.value.map(student => {
|
||||
const detailList = studentList.value.map((student) => {
|
||||
return {
|
||||
evaluationId,
|
||||
studentId: student.studentId,
|
||||
@@ -586,13 +537,13 @@ const submitForm = () => {
|
||||
remark: student.remark || '' // 确保备注有默认值
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
// 批量创建测评明细
|
||||
await batchAddEvaluationDetails(detailList);
|
||||
|
||||
|
||||
// 更新测评主表的学生数量和重点关注学生数量
|
||||
// 注:这部分逻辑可能需要在后端自动处理
|
||||
|
||||
|
||||
proxy?.$modal.msgSuccess('新增成功');
|
||||
router.replace('/xljk/evaluation');
|
||||
} else {
|
||||
@@ -679,4 +630,4 @@ onMounted(() => {
|
||||
color: #909399;
|
||||
margin-left: 10px;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
@@ -10,7 +10,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
<el-descriptions :column="3" border>
|
||||
<el-descriptions-item label="测评名称">{{ evaluationInfo.evaluationName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="专业">{{ evaluationInfo.majorName }}</el-descriptions-item>
|
||||
@@ -24,7 +24,7 @@
|
||||
<el-descriptions-item label="备注" :span="2">{{ evaluationInfo.remark || '无' }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</el-card>
|
||||
|
||||
|
||||
<!-- 统计信息卡片 -->
|
||||
<el-card shadow="hover" class="mb-[10px] statistics-card" v-loading="statisticsLoading">
|
||||
<template #header>
|
||||
@@ -32,7 +32,7 @@
|
||||
<span class="header-title">测评统计</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
<div class="statistics-container">
|
||||
<div class="statistics-row">
|
||||
<div class="statistic-item">
|
||||
@@ -42,7 +42,7 @@
|
||||
</template>
|
||||
</el-statistic>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="statistic-item">
|
||||
<el-statistic title="健康学生" :value="statistics.healthyCount">
|
||||
<template #suffix>
|
||||
@@ -50,7 +50,7 @@
|
||||
</template>
|
||||
</el-statistic>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="statistic-item">
|
||||
<el-statistic title="良好学生" :value="statistics.goodCount">
|
||||
<template #suffix>
|
||||
@@ -58,23 +58,23 @@
|
||||
</template>
|
||||
</el-statistic>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="statistic-item">
|
||||
<el-statistic title="重点关注" :value="statistics.focusCount" value-style="color: #f56c6c;">
|
||||
<template #suffix>
|
||||
<span class="suffix-label" style="color: #f56c6c;">人 ({{ formatPercent(statistics.focusPercent) }})</span>
|
||||
<span class="suffix-label" style="color: #f56c6c">人 ({{ formatPercent(statistics.focusPercent) }})</span>
|
||||
</template>
|
||||
</el-statistic>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- 图表展示 -->
|
||||
<div class="chart-container" v-if="statistics.totalStudents > 0">
|
||||
<div ref="pieChartRef" class="pie-chart"></div>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
|
||||
<!-- 学生测评结果表格 -->
|
||||
<el-card shadow="hover" class="mb-[10px]">
|
||||
<template #header>
|
||||
@@ -87,7 +87,7 @@
|
||||
<el-radio-button label="2">良好</el-radio-button>
|
||||
<el-radio-button label="3">重点关注</el-radio-button>
|
||||
</el-radio-group>
|
||||
|
||||
|
||||
<el-input
|
||||
v-model="searchKeyword"
|
||||
placeholder="搜索学号/姓名"
|
||||
@@ -100,14 +100,12 @@
|
||||
<el-icon class="el-input__icon" @click="handleSearch"><Search /></el-icon>
|
||||
</template>
|
||||
</el-input>
|
||||
|
||||
<el-button type="info" plain size="small" style="margin-left: 10px" @click="resetFilter">
|
||||
清除筛选
|
||||
</el-button>
|
||||
|
||||
<el-button type="info" plain size="small" style="margin-left: 10px" @click="resetFilter"> 清除筛选 </el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
border
|
||||
@@ -126,12 +124,8 @@
|
||||
<el-table-column label="联系电话" align="center" prop="phone" width="120" />
|
||||
<el-table-column label="心理状态" align="center" prop="mentalStatus" width="100">
|
||||
<template #default="scope">
|
||||
<el-tag
|
||||
:type="scope.row.mentalStatus === '1' ? 'success' :
|
||||
scope.row.mentalStatus === '2' ? 'warning' : 'danger'"
|
||||
>
|
||||
{{ scope.row.mentalStatus === '1' ? '健康' :
|
||||
scope.row.mentalStatus === '2' ? '良好' : '重点关注' }}
|
||||
<el-tag :type="scope.row.mentalStatus === '1' ? 'success' : scope.row.mentalStatus === '2' ? 'warning' : 'danger'">
|
||||
{{ scope.row.mentalStatus === '1' ? '健康' : scope.row.mentalStatus === '2' ? '良好' : '重点关注' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
@@ -143,7 +137,7 @@
|
||||
</el-table-column>
|
||||
<el-table-column label="备注" align="left" prop="remark" min-width="200" show-overflow-tooltip />
|
||||
</el-table>
|
||||
|
||||
|
||||
<div class="pagination-container">
|
||||
<div class="filter-info">
|
||||
<span v-if="filterStatus">当前筛选:{{ filterStatus === '1' ? '健康' : filterStatus === '2' ? '良好' : '重点关注' }}</span>
|
||||
@@ -239,7 +233,7 @@ const getStatistics = async () => {
|
||||
statisticsLoading.value = true;
|
||||
const res = await getEvaluationStatistics(evaluationId.value);
|
||||
statistics.value = res.data;
|
||||
|
||||
|
||||
// 初始化饼图
|
||||
initPieChart();
|
||||
} catch (error) {
|
||||
@@ -259,39 +253,40 @@ const getDetailList = async () => {
|
||||
studentNo: queryParams.value.studentNo,
|
||||
studentName: queryParams.value.studentName
|
||||
});
|
||||
|
||||
|
||||
// 使用getEvaluationDetailsByEvaluationId获取所有数据
|
||||
const res = await getEvaluationDetailsByEvaluationId(evaluationId.value);
|
||||
if (res.data) {
|
||||
// 存储原始数据
|
||||
const allDetails = res.data;
|
||||
console.log('获取到的原始数据:', allDetails.length, '条');
|
||||
|
||||
|
||||
// 根据过滤条件筛选数据
|
||||
let filteredData = [...allDetails];
|
||||
|
||||
|
||||
// 按心理状态筛选
|
||||
if (filterStatus.value) {
|
||||
filteredData = filteredData.filter(item => item.mentalStatus === filterStatus.value);
|
||||
filteredData = filteredData.filter((item) => item.mentalStatus === filterStatus.value);
|
||||
console.log('按心理状态筛选后:', filteredData.length, '条');
|
||||
}
|
||||
|
||||
|
||||
// 按学号或姓名搜索
|
||||
if (searchKeyword.value) {
|
||||
const keyword = searchKeyword.value.toLowerCase();
|
||||
filteredData = filteredData.filter(item =>
|
||||
(item.studentNo && item.studentNo.toLowerCase().includes(keyword)) ||
|
||||
(item.studentName && item.studentName.toLowerCase().includes(keyword))
|
||||
filteredData = filteredData.filter(
|
||||
(item) =>
|
||||
(item.studentNo && item.studentNo.toLowerCase().includes(keyword)) ||
|
||||
(item.studentName && item.studentName.toLowerCase().includes(keyword))
|
||||
);
|
||||
console.log('按关键词筛选后:', filteredData.length, '条');
|
||||
}
|
||||
|
||||
|
||||
// 分页处理
|
||||
const pageSize = queryParams.value.pageSize;
|
||||
const pageNum = queryParams.value.pageNum;
|
||||
const startIndex = (pageNum - 1) * pageSize;
|
||||
const endIndex = startIndex + pageSize;
|
||||
|
||||
|
||||
// 设置总数和当前页数据
|
||||
total.value = filteredData.length;
|
||||
detailList.value = filteredData.slice(startIndex, endIndex);
|
||||
@@ -312,15 +307,15 @@ const getDetailList = async () => {
|
||||
/** 初始化饼图 */
|
||||
const initPieChart = () => {
|
||||
if (!pieChartRef.value) return;
|
||||
|
||||
|
||||
// 销毁之前的图表实例
|
||||
if (pieChart) {
|
||||
pieChart.dispose();
|
||||
}
|
||||
|
||||
|
||||
// 创建新的图表实例
|
||||
pieChart = echarts.init(pieChartRef.value);
|
||||
|
||||
|
||||
const option = {
|
||||
title: {
|
||||
text: '心理状态分布',
|
||||
@@ -383,9 +378,9 @@ const initPieChart = () => {
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
|
||||
pieChart.setOption(option);
|
||||
|
||||
|
||||
// 监听窗口大小变化,重新调整图表大小
|
||||
window.addEventListener('resize', () => {
|
||||
pieChart?.resize();
|
||||
@@ -448,7 +443,7 @@ onBeforeUnmount(() => {
|
||||
pieChart.dispose();
|
||||
pieChart = null;
|
||||
}
|
||||
|
||||
|
||||
// 移除事件监听
|
||||
window.removeEventListener('resize', () => {
|
||||
pieChart?.resize();
|
||||
@@ -561,10 +556,10 @@ onBeforeUnmount(() => {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
|
||||
.pie-chart {
|
||||
width: 100%;
|
||||
height: 300px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
@@ -9,45 +9,27 @@
|
||||
|
||||
<el-form ref="formRef" :model="form" :rules="rules" label-width="120px">
|
||||
<el-form-item label="测评名称" prop="evaluationName">
|
||||
<el-input
|
||||
v-model="form.evaluationName"
|
||||
placeholder="请输入测评名称"
|
||||
style="width: 500px"
|
||||
maxlength="100"
|
||||
show-word-limit
|
||||
/>
|
||||
<el-input v-model="form.evaluationName" placeholder="请输入测评名称" style="width: 500px" maxlength="100" show-word-limit />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="专业" prop="majorId">
|
||||
<el-input
|
||||
v-model="form.majorName"
|
||||
disabled
|
||||
style="width: 500px"
|
||||
/>
|
||||
<el-input v-model="form.majorName" disabled style="width: 500px" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="班级" prop="classId">
|
||||
<el-input
|
||||
v-model="form.className"
|
||||
disabled
|
||||
style="width: 500px"
|
||||
/>
|
||||
<el-input v-model="form.className" disabled style="width: 500px" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="学期" prop="semester">
|
||||
<el-input
|
||||
v-model="form.semester"
|
||||
disabled
|
||||
style="width: 500px"
|
||||
/>
|
||||
<el-input v-model="form.semester" disabled style="width: 500px" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="备注" prop="remark">
|
||||
<el-input
|
||||
v-model="form.remark"
|
||||
type="textarea"
|
||||
placeholder="请输入备注信息"
|
||||
style="width: 500px"
|
||||
<el-input
|
||||
v-model="form.remark"
|
||||
type="textarea"
|
||||
placeholder="请输入备注信息"
|
||||
style="width: 500px"
|
||||
:rows="4"
|
||||
maxlength="500"
|
||||
show-word-limit
|
||||
@@ -62,7 +44,7 @@
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
|
||||
|
||||
<el-card shadow="hover" class="mb-[10px]">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
@@ -72,16 +54,10 @@
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
<div v-loading="studentsLoading">
|
||||
<el-alert
|
||||
v-if="studentList.length === 0 && !studentsLoading"
|
||||
title="未找到该班级的学生信息"
|
||||
type="warning"
|
||||
show-icon
|
||||
:closable="false"
|
||||
/>
|
||||
|
||||
<el-alert v-if="studentList.length === 0 && !studentsLoading" title="未找到该班级的学生信息" type="warning" show-icon :closable="false" />
|
||||
|
||||
<!-- 批量操作区域 -->
|
||||
<div class="batch-actions" v-if="studentList.length > 0">
|
||||
<span class="batch-label">批量设置心理状态:</span>
|
||||
@@ -93,23 +69,12 @@
|
||||
<el-button type="primary" size="small" @click="applyBatchMentalStatus" :disabled="selectedStudents.length === 0">
|
||||
应用到所选学生
|
||||
</el-button>
|
||||
<el-button type="info" size="small" @click="applyBatchMentalStatusToAll">
|
||||
应用到全部学生
|
||||
</el-button>
|
||||
<el-button type="warning" size="small" @click="resetAllMentalStatus">
|
||||
重置所有状态
|
||||
</el-button>
|
||||
<el-button type="info" size="small" @click="applyBatchMentalStatusToAll"> 应用到全部学生 </el-button>
|
||||
<el-button type="warning" size="small" @click="resetAllMentalStatus"> 重置所有状态 </el-button>
|
||||
<span class="batch-hint">已选择 {{ selectedStudents.length }} 名学生</span>
|
||||
</div>
|
||||
|
||||
<el-table
|
||||
v-if="studentList.length > 0"
|
||||
:data="studentList"
|
||||
border
|
||||
stripe
|
||||
style="width: 100%"
|
||||
@selection-change="handleSelectionChange"
|
||||
>
|
||||
|
||||
<el-table v-if="studentList.length > 0" :data="studentList" border stripe style="width: 100%" @selection-change="handleSelectionChange">
|
||||
<el-table-column type="selection" width="55" />
|
||||
<el-table-column label="学号" align="center" prop="studentNo" width="120" />
|
||||
<el-table-column label="姓名" align="center" prop="studentName" width="100" />
|
||||
@@ -124,59 +89,48 @@
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="联系电话" align="center" prop="phone" width="120" />
|
||||
|
||||
|
||||
<!-- 心理状态选择 -->
|
||||
<el-table-column label="心理状态" align="center" width="180">
|
||||
<template #default="scope">
|
||||
<el-select
|
||||
v-model="scope.row.mentalStatus"
|
||||
placeholder="请选择心理状态"
|
||||
@change="handleMentalStatusChange(scope.row)"
|
||||
>
|
||||
<el-select v-model="scope.row.mentalStatus" placeholder="请选择心理状态" @change="handleMentalStatusChange(scope.row)">
|
||||
<el-option label="健康" value="1" />
|
||||
<el-option label="良好" value="2" />
|
||||
<el-option label="重点关注" value="3" />
|
||||
</el-select>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
|
||||
<!-- 分数输入,仅当心理状态为"重点关注"时显示 -->
|
||||
<el-table-column label="分数" align="center" width="120">
|
||||
<template #default="scope">
|
||||
<el-input-number
|
||||
v-if="scope.row.mentalStatus === '3'"
|
||||
v-model="scope.row.score"
|
||||
:min="0"
|
||||
:max="100"
|
||||
size="small"
|
||||
/>
|
||||
<el-input-number v-if="scope.row.mentalStatus === '3'" v-model="scope.row.score" :min="0" :max="100" size="small" />
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
|
||||
<!-- 备注 -->
|
||||
<el-table-column label="备注" align="center">
|
||||
<template #default="scope">
|
||||
<el-input
|
||||
v-model="scope.row.remark"
|
||||
type="text"
|
||||
placeholder="备注信息"
|
||||
size="small"
|
||||
/>
|
||||
<el-input v-model="scope.row.remark" type="text" placeholder="备注信息" size="small" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<div class="student-count" v-if="studentList.length > 0">
|
||||
共 {{ studentList.length }} 名学生
|
||||
</div>
|
||||
|
||||
<div class="student-count" v-if="studentList.length > 0">共 {{ studentList.length }} 名学生</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup name="XljkEvaluationEdit" lang="ts">
|
||||
import { getEvaluation, updateEvaluation, getStudentsByClass, getEvaluationDetailsByEvaluationId, batchUpdateMentalStatus } from '@/api/xljk/evaluation';
|
||||
import {
|
||||
getEvaluation,
|
||||
updateEvaluation,
|
||||
getStudentsByClass,
|
||||
getEvaluationDetailsByEvaluationId,
|
||||
batchUpdateMentalStatus
|
||||
} from '@/api/xljk/evaluation';
|
||||
import { XljkEvaluationForm, StudentInfo, XljkEvaluationDetailForm } from '@/api/xljk/evaluation/types';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||
@@ -222,7 +176,7 @@ const getEvaluationInfo = async () => {
|
||||
try {
|
||||
const res = await getEvaluation(evaluationId.value);
|
||||
form.value = res.data;
|
||||
|
||||
|
||||
// 检查状态是否为草稿
|
||||
if (form.value.status !== '0') {
|
||||
ElMessage.error('只能编辑草稿状态的测评');
|
||||
@@ -250,15 +204,15 @@ const getStudents = async () => {
|
||||
console.log('获取学生列表的参数:', {
|
||||
className: form.value.className
|
||||
});
|
||||
|
||||
|
||||
// 只使用班级名称作为参数,忽略院系和专业,因为数据可能不匹配
|
||||
const res = await getStudentsByClass(null, null, form.value.className);
|
||||
|
||||
|
||||
// 打印返回结果
|
||||
console.log('获取学生列表的结果:', res);
|
||||
|
||||
|
||||
// 设置默认的心理状态为"健康"
|
||||
studentList.value = (res.data || []).map(student => ({
|
||||
studentList.value = (res.data || []).map((student) => ({
|
||||
...student,
|
||||
mentalStatus: '1', // 默认设置为"健康"
|
||||
remark: '' // 初始化备注为空字符串
|
||||
@@ -276,11 +230,11 @@ const getEvaluationDetails = async () => {
|
||||
try {
|
||||
const res = await getEvaluationDetailsByEvaluationId(evaluationId.value);
|
||||
detailList.value = res.data || [];
|
||||
|
||||
|
||||
// 将测评明细数据合并到学生列表中
|
||||
if (detailList.value.length > 0) {
|
||||
studentList.value.forEach(student => {
|
||||
const detail = detailList.value.find(d => d.studentId === student.studentId);
|
||||
studentList.value.forEach((student) => {
|
||||
const detail = detailList.value.find((d) => d.studentId === student.studentId);
|
||||
if (detail) {
|
||||
student.mentalStatus = detail.mentalStatus;
|
||||
student.score = detail.score;
|
||||
@@ -308,7 +262,7 @@ const handleMentalStatusChange = (student: StudentInfo) => {
|
||||
|
||||
/** 统计重点关注学生数量 */
|
||||
const countFocusStudents = () => {
|
||||
return studentList.value.filter(student => student.mentalStatus === '3').length;
|
||||
return studentList.value.filter((student) => student.mentalStatus === '3').length;
|
||||
};
|
||||
|
||||
/** 批量设置心理状态 */
|
||||
@@ -325,7 +279,7 @@ const applyBatchMentalStatus = async () => {
|
||||
return;
|
||||
}
|
||||
const status = batchMentalStatus.value;
|
||||
const detailsToUpdate = selectedStudents.value.map(student => {
|
||||
const detailsToUpdate = selectedStudents.value.map((student) => {
|
||||
return {
|
||||
id: student.detailId,
|
||||
evaluationId: form.value.id,
|
||||
@@ -349,7 +303,7 @@ const applyBatchMentalStatus = async () => {
|
||||
|
||||
const applyBatchMentalStatusToAll = async () => {
|
||||
const status = batchMentalStatus.value;
|
||||
const detailsToUpdate = studentList.value.map(student => {
|
||||
const detailsToUpdate = studentList.value.map((student) => {
|
||||
return {
|
||||
id: student.detailId,
|
||||
evaluationId: form.value.id,
|
||||
@@ -378,8 +332,8 @@ const resetAllMentalStatus = async () => {
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
});
|
||||
|
||||
const detailsToUpdate = studentList.value.map(student => {
|
||||
|
||||
const detailsToUpdate = studentList.value.map((student) => {
|
||||
return {
|
||||
id: student.detailId,
|
||||
evaluationId: form.value.id,
|
||||
@@ -412,9 +366,9 @@ const submitForm = () => {
|
||||
try {
|
||||
// 更新测评基本信息
|
||||
await updateEvaluation(form.value);
|
||||
|
||||
|
||||
// 准备学生测评明细数据
|
||||
const detailsToUpdate = studentList.value.map(student => {
|
||||
const detailsToUpdate = studentList.value.map((student) => {
|
||||
return {
|
||||
id: student.detailId, // 已有记录的ID
|
||||
evaluationId: form.value.id, // 保持ID为原始类型,不做Number转换
|
||||
@@ -429,10 +383,10 @@ const submitForm = () => {
|
||||
remark: student.remark
|
||||
} as XljkEvaluationDetailForm;
|
||||
});
|
||||
|
||||
|
||||
// 批量更新学生心理状态
|
||||
await batchUpdateMentalStatus(detailsToUpdate);
|
||||
|
||||
|
||||
ElMessage.success('修改成功');
|
||||
router.replace('/xljk/evaluation');
|
||||
} catch (error) {
|
||||
@@ -504,4 +458,4 @@ onMounted(() => {
|
||||
color: #909399;
|
||||
margin-left: 10px;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
@@ -21,7 +21,7 @@
|
||||
<el-descriptions-item label="学生人数">{{ detailList.length }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</el-card>
|
||||
|
||||
|
||||
<el-card shadow="hover" v-loading="loading">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
@@ -30,7 +30,7 @@
|
||||
<span class="fill-stats">
|
||||
已填写:<el-tag type="success">{{ filledCount }}</el-tag> / {{ detailList.length }}
|
||||
</span>
|
||||
|
||||
|
||||
<el-input
|
||||
v-model="searchKeyword"
|
||||
placeholder="搜索学号/姓名"
|
||||
@@ -46,15 +46,8 @@
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<el-table
|
||||
ref="tableRef"
|
||||
:data="filteredDetailList"
|
||||
border
|
||||
stripe
|
||||
:max-height="tableMaxHeight"
|
||||
:header-cell-style="{ background: '#f5f7fa' }"
|
||||
>
|
||||
|
||||
<el-table ref="tableRef" :data="filteredDetailList" border stripe :max-height="tableMaxHeight" :header-cell-style="{ background: '#f5f7fa' }">
|
||||
<el-table-column label="学号" prop="studentNo" width="120" align="center" fixed />
|
||||
<el-table-column label="姓名" prop="studentName" width="100" align="center" fixed />
|
||||
<el-table-column label="性别" prop="gender" width="60" align="center">
|
||||
@@ -63,7 +56,7 @@
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="联系电话" prop="phone" width="120" align="center" />
|
||||
|
||||
|
||||
<el-table-column label="心理状态" width="200" align="center">
|
||||
<template #default="scope">
|
||||
<el-radio-group v-model="scope.row.mentalStatus" @change="handleStatusChange(scope.row)">
|
||||
@@ -73,34 +66,28 @@
|
||||
</el-radio-group>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
|
||||
<el-table-column label="分数" width="150" align="center">
|
||||
<template #default="scope">
|
||||
<el-input-number
|
||||
<el-input-number
|
||||
v-model="scope.row.score"
|
||||
:disabled="scope.row.mentalStatus !== '3'"
|
||||
:min="1"
|
||||
:max="100"
|
||||
:min="1"
|
||||
:max="100"
|
||||
size="small"
|
||||
placeholder="分数"
|
||||
@change="handleScoreChange(scope.row)"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
|
||||
<el-table-column label="备注" align="center">
|
||||
<template #default="scope">
|
||||
<el-input
|
||||
v-model="scope.row.remark"
|
||||
type="textarea"
|
||||
:rows="1"
|
||||
placeholder="请输入备注信息"
|
||||
@blur="handleRemarkChange(scope.row)"
|
||||
/>
|
||||
<el-input v-model="scope.row.remark" type="textarea" :rows="1" placeholder="请输入备注信息" @blur="handleRemarkChange(scope.row)" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
|
||||
<div class="fixed-bottom-bar" v-if="detailList.length > 0">
|
||||
<el-button type="primary" size="large" @click="saveEvaluation" :loading="saveLoading">保存测评结果</el-button>
|
||||
</div>
|
||||
@@ -138,7 +125,7 @@ const getEvaluationInfo = async () => {
|
||||
try {
|
||||
const res = await getEvaluation(evaluationId.value);
|
||||
evaluationInfo.value = res.data;
|
||||
|
||||
|
||||
// 检查状态是否为"已发布"
|
||||
if (evaluationInfo.value.status !== '1') {
|
||||
proxy?.$modal.msgError('只能填写"已发布"状态的测评');
|
||||
@@ -171,7 +158,7 @@ const getDetailList = async () => {
|
||||
|
||||
/** 已填写的人数 */
|
||||
const filledCount = computed(() => {
|
||||
return detailList.value.filter(item => item.mentalStatus).length;
|
||||
return detailList.value.filter((item) => item.mentalStatus).length;
|
||||
});
|
||||
|
||||
/** 返回列表页 */
|
||||
@@ -204,19 +191,18 @@ const searchStudents = () => {
|
||||
filteredDetailList.value = [...detailList.value];
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
const keyword = searchKeyword.value.toLowerCase();
|
||||
filteredDetailList.value = detailList.value.filter(item => {
|
||||
return item.studentNo?.toLowerCase().includes(keyword) ||
|
||||
item.studentName?.toLowerCase().includes(keyword);
|
||||
filteredDetailList.value = detailList.value.filter((item) => {
|
||||
return item.studentNo?.toLowerCase().includes(keyword) || item.studentName?.toLowerCase().includes(keyword);
|
||||
});
|
||||
};
|
||||
|
||||
/** 保存测评结果 */
|
||||
const saveEvaluation = async () => {
|
||||
// 检查是否有未填写的记录
|
||||
const unfilledRecords = detailList.value.filter(item => !item.mentalStatus);
|
||||
|
||||
const unfilledRecords = detailList.value.filter((item) => !item.mentalStatus);
|
||||
|
||||
// 如果有未填写的记录,询问是否继续
|
||||
if (unfilledRecords.length > 0) {
|
||||
try {
|
||||
@@ -225,26 +211,24 @@ const saveEvaluation = async () => {
|
||||
return; // 用户取消保存
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 检查重点关注学生是否都有分数
|
||||
const noScoreItems = detailList.value.filter(item =>
|
||||
item.mentalStatus === '3' && (!item.score || item.score <= 0)
|
||||
);
|
||||
|
||||
const noScoreItems = detailList.value.filter((item) => item.mentalStatus === '3' && (!item.score || item.score <= 0));
|
||||
|
||||
if (noScoreItems.length > 0) {
|
||||
proxy?.$modal.msgError(`有 ${noScoreItems.length} 名重点关注学生未填写分数,请填写`);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// 准备提交数据
|
||||
const updateData: XljkEvaluationDetailForm[] = detailList.value.map(item => ({
|
||||
const updateData: XljkEvaluationDetailForm[] = detailList.value.map((item) => ({
|
||||
id: item.id,
|
||||
evaluationId: Number(evaluationId.value),
|
||||
mentalStatus: item.mentalStatus,
|
||||
score: item.score,
|
||||
remark: item.remark
|
||||
}));
|
||||
|
||||
|
||||
// 提交数据
|
||||
try {
|
||||
saveLoading.value = true;
|
||||
@@ -264,7 +248,7 @@ const setTableHeight = () => {
|
||||
const headerHeight = 160; // 估计上方卡片和间距的高度
|
||||
const footerHeight = 80; // 底部按钮区域高度
|
||||
const padding = 40; // 额外的边距
|
||||
|
||||
|
||||
tableMaxHeight.value = windowHeight - headerHeight - footerHeight - padding;
|
||||
};
|
||||
|
||||
@@ -277,7 +261,7 @@ onMounted(() => {
|
||||
getEvaluationInfo();
|
||||
getDetailList();
|
||||
setTableHeight();
|
||||
|
||||
|
||||
// 添加窗口大小变化监听
|
||||
window.addEventListener('resize', handleResize);
|
||||
});
|
||||
@@ -340,4 +324,4 @@ onBeforeUnmount(() => {
|
||||
.fixed-bottom-bar .el-button {
|
||||
min-width: 150px;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
@@ -12,37 +12,28 @@
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="院系" prop="departmentName">
|
||||
<el-select v-model="queryParams.departmentName" placeholder="请选择院系" clearable style="width: 100%" @change="handleDepartmentChange">
|
||||
<el-option
|
||||
v-for="dept in departmentOptions"
|
||||
:key="dept.name"
|
||||
:label="dept.name"
|
||||
:value="dept.name"
|
||||
/>
|
||||
<el-select
|
||||
v-model="queryParams.departmentName"
|
||||
placeholder="请选择院系"
|
||||
clearable
|
||||
style="width: 100%"
|
||||
@change="handleDepartmentChange"
|
||||
>
|
||||
<el-option v-for="dept in departmentOptions" :key="dept.name" :label="dept.name" :value="dept.name" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="专业" prop="majorName">
|
||||
<el-select v-model="queryParams.majorName" placeholder="请选择专业" clearable style="width: 100%" @change="handleMajorChange">
|
||||
<el-option
|
||||
v-for="major in majorOptions"
|
||||
:key="major.name"
|
||||
:label="major.name"
|
||||
:value="major.name"
|
||||
/>
|
||||
<el-option v-for="major in majorOptions" :key="major.name" :label="major.name" :value="major.name" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="班级" prop="className">
|
||||
<el-select v-model="queryParams.className" placeholder="请选择班级" clearable style="width: 100%">
|
||||
<el-option
|
||||
v-for="cls in classOptions"
|
||||
:key="cls.name"
|
||||
:label="cls.name"
|
||||
:value="cls.name"
|
||||
/>
|
||||
<el-option v-for="cls in classOptions" :key="cls.name" :label="cls.name" :value="cls.name" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
@@ -51,12 +42,7 @@
|
||||
<el-col :span="6">
|
||||
<el-form-item label="学期" prop="semester">
|
||||
<el-select v-model="queryParams.semester" placeholder="请选择学期" clearable style="width: 100%">
|
||||
<el-option
|
||||
v-for="semester in semesterOptions"
|
||||
:key="semester.value"
|
||||
:label="semester.label"
|
||||
:value="semester.value"
|
||||
/>
|
||||
<el-option v-for="semester in semesterOptions" :key="semester.value" :label="semester.label" :value="semester.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
@@ -142,7 +128,7 @@
|
||||
v-hasPermi="['xljk:evaluation:query']"
|
||||
></el-button>
|
||||
</el-tooltip>
|
||||
|
||||
|
||||
<!-- 仅草稿状态可修改 -->
|
||||
<el-tooltip content="修改" placement="top" v-if="scope.row.status === '0'">
|
||||
<el-button
|
||||
@@ -155,7 +141,7 @@
|
||||
v-hasPermi="['xljk:evaluation:edit']"
|
||||
></el-button>
|
||||
</el-tooltip>
|
||||
|
||||
|
||||
<!-- 仅草稿状态可删除 -->
|
||||
<el-tooltip content="删除" placement="top" v-if="scope.row.status === '0'">
|
||||
<el-button
|
||||
@@ -168,7 +154,7 @@
|
||||
v-hasPermi="['xljk:evaluation:remove']"
|
||||
></el-button>
|
||||
</el-tooltip>
|
||||
|
||||
|
||||
<!-- 草稿状态可提交 -->
|
||||
<el-tooltip content="提交" placement="top" v-if="scope.row.status === '0'">
|
||||
<el-button
|
||||
@@ -193,8 +179,27 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { listEvaluation, getEvaluation, delEvaluation, addEvaluation, updateEvaluation, completeEvaluation, getCollegeOptions, getMajorOptions, getClassOptions, getSemesterOptions } from '@/api/xljk/evaluation';
|
||||
import { XljkEvaluationVO, XljkEvaluationForm, XljkEvaluationQuery, CollegeOption, MajorOption, ClassOption, SemesterOption } from '@/api/xljk/evaluation/types';
|
||||
import {
|
||||
listEvaluation,
|
||||
getEvaluation,
|
||||
delEvaluation,
|
||||
addEvaluation,
|
||||
updateEvaluation,
|
||||
completeEvaluation,
|
||||
getCollegeOptions,
|
||||
getMajorOptions,
|
||||
getClassOptions,
|
||||
getSemesterOptions
|
||||
} from '@/api/xljk/evaluation';
|
||||
import {
|
||||
XljkEvaluationVO,
|
||||
XljkEvaluationForm,
|
||||
XljkEvaluationQuery,
|
||||
CollegeOption,
|
||||
MajorOption,
|
||||
ClassOption,
|
||||
SemesterOption
|
||||
} from '@/api/xljk/evaluation/types';
|
||||
|
||||
// 基本变量
|
||||
const { proxy } = getCurrentInstance() as any;
|
||||
@@ -277,7 +282,7 @@ const handleDepartmentChange = async (collegeName: string) => {
|
||||
queryParams.value.className = undefined;
|
||||
majorOptions.value = [];
|
||||
classOptions.value = [];
|
||||
|
||||
|
||||
if (collegeName) {
|
||||
// 获取该院系下的专业
|
||||
await getMajors(collegeName);
|
||||
@@ -291,7 +296,7 @@ const handleDepartmentChange = async (collegeName: string) => {
|
||||
const handleMajorChange = async (majorName: string) => {
|
||||
queryParams.value.className = undefined; // 清空班级选择
|
||||
classOptions.value = [];
|
||||
|
||||
|
||||
if (queryParams.value.departmentName && majorName) {
|
||||
// 获取该院系和专业下的班级
|
||||
await getClasses(queryParams.value.departmentName, majorName);
|
||||
@@ -327,9 +332,9 @@ const resetQuery = () => {
|
||||
/** 多选框选中数据 */
|
||||
const handleSelectionChange = (selection: XljkEvaluationVO[]) => {
|
||||
ids.value = selection.map((item) => item.id);
|
||||
|
||||
|
||||
// 判断是否有非草稿状态
|
||||
const hasNonDraft = selection.some(item => item.status !== '0');
|
||||
const hasNonDraft = selection.some((item) => item.status !== '0');
|
||||
single.value = selection.length !== 1 || hasNonDraft;
|
||||
multiple.value = !selection.length || hasNonDraft;
|
||||
};
|
||||
@@ -350,16 +355,14 @@ const handleUpdate = (row?: XljkEvaluationVO) => {
|
||||
proxy?.$router.push({ path: `/xljk/evaluation/edit/${id}` });
|
||||
};
|
||||
|
||||
|
||||
|
||||
/** 删除按钮操作 */
|
||||
const handleDelete = async (row?: XljkEvaluationVO) => {
|
||||
const _ids = row?.id || ids.value;
|
||||
|
||||
|
||||
// 检查是否有非草稿状态的记录
|
||||
if (!row) {
|
||||
const selectedRecords = evaluationList.value.filter(item => ids.value.includes(item.id));
|
||||
if (selectedRecords.some(item => item.status !== '0')) {
|
||||
const selectedRecords = evaluationList.value.filter((item) => ids.value.includes(item.id));
|
||||
if (selectedRecords.some((item) => item.status !== '0')) {
|
||||
proxy?.$modal.msgError('只能删除草稿状态的测评');
|
||||
return;
|
||||
}
|
||||
@@ -367,11 +370,9 @@ const handleDelete = async (row?: XljkEvaluationVO) => {
|
||||
proxy?.$modal.msgError('只能删除草稿状态的测评');
|
||||
return;
|
||||
}
|
||||
|
||||
const confirmMessage = row
|
||||
? `是否确认删除测评"${row.evaluationName}"?`
|
||||
: `是否确认删除选中的${ids.value.length}条数据?`;
|
||||
|
||||
|
||||
const confirmMessage = row ? `是否确认删除测评"${row.evaluationName}"?` : `是否确认删除选中的${ids.value.length}条数据?`;
|
||||
|
||||
await proxy?.$modal.confirm(confirmMessage);
|
||||
try {
|
||||
loading.value = true;
|
||||
@@ -459,4 +460,4 @@ onActivated(() => {
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
@@ -71,6 +71,7 @@
|
||||
## 📱 移动端支持
|
||||
|
||||
部分页面支持移动端访问:
|
||||
|
||||
- 学生在线报名
|
||||
- 报名状态查询
|
||||
- 录取结果查询
|
||||
@@ -86,16 +87,19 @@
|
||||
## 🚀 快速开始
|
||||
|
||||
### 管理端页面开发
|
||||
|
||||
1. 参考 `enrollment/index.vue` 的代码结构
|
||||
2. 使用标准的CRUD操作模板
|
||||
3. 配置相应的权限控制
|
||||
|
||||
### 学生端页面开发
|
||||
|
||||
1. 参考 `enrollment/apply.vue` 的表单设计
|
||||
2. 注重用户体验和表单验证
|
||||
3. 提供清晰的操作指引
|
||||
|
||||
### 路由配置
|
||||
|
||||
1. 在项目路由中添加相应配置
|
||||
2. 设置正确的权限和菜单信息
|
||||
3. 确保面包屑导航正确显示
|
||||
@@ -1,11 +1,5 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
v-model="dialogVisible"
|
||||
title="批量录取处理"
|
||||
width="800px"
|
||||
class="batch-admission-dialog"
|
||||
:before-close="handleClose"
|
||||
>
|
||||
<el-dialog v-model="dialogVisible" title="批量录取处理" width="800px" class="batch-admission-dialog" :before-close="handleClose">
|
||||
<div class="admission-container">
|
||||
<!-- 选择的学生列表 -->
|
||||
<el-card class="student-list-card" shadow="never">
|
||||
@@ -15,13 +9,7 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<el-table
|
||||
:data="selectedStudents"
|
||||
border
|
||||
height="250px"
|
||||
style="width: 100%"
|
||||
v-loading="loading"
|
||||
>
|
||||
<el-table :data="selectedStudents" border height="250px" style="width: 100%" v-loading="loading">
|
||||
<el-table-column label="序号" type="index" width="60" align="center" />
|
||||
<el-table-column label="学生姓名" prop="studentName" width="100" />
|
||||
<el-table-column label="身份证号" prop="idCard" width="180" />
|
||||
@@ -46,32 +34,16 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<el-form
|
||||
ref="settingFormRef"
|
||||
:model="settingForm"
|
||||
:rules="settingRules"
|
||||
label-width="120px"
|
||||
>
|
||||
<el-form ref="settingFormRef" :model="settingForm" :rules="settingRules" label-width="120px">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="批次前缀" prop="prefix">
|
||||
<el-input
|
||||
v-model="settingForm.prefix"
|
||||
placeholder="例如:HDY2025-"
|
||||
clearable
|
||||
@change="handlePrefixChange"
|
||||
/>
|
||||
<el-input v-model="settingForm.prefix" placeholder="例如:HDY2025-" clearable @change="handlePrefixChange" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="起始编号" prop="startNumber">
|
||||
<el-input-number
|
||||
v-model="settingForm.startNumber"
|
||||
:min="1"
|
||||
:max="9999"
|
||||
controls-position="right"
|
||||
@change="handleStartNumberChange"
|
||||
/>
|
||||
<el-input-number v-model="settingForm.startNumber" :min="1" :max="9999" controls-position="right" @change="handleStartNumberChange" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
@@ -79,23 +51,12 @@
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="数字位数" prop="digitLength">
|
||||
<el-input-number
|
||||
v-model="settingForm.digitLength"
|
||||
:min="1"
|
||||
:max="6"
|
||||
controls-position="right"
|
||||
@change="handleDigitLengthChange"
|
||||
/>
|
||||
<el-input-number v-model="settingForm.digitLength" :min="1" :max="6" controls-position="right" @change="handleDigitLengthChange" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="自动生成" prop="autoGenerate">
|
||||
<el-switch
|
||||
v-model="settingForm.autoGenerate"
|
||||
active-text="启用"
|
||||
inactive-text="禁用"
|
||||
@change="handleAutoGenerateChange"
|
||||
/>
|
||||
<el-switch v-model="settingForm.autoGenerate" active-text="启用" inactive-text="禁用" @change="handleAutoGenerateChange" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
@@ -125,17 +86,9 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<el-form
|
||||
ref="paymentFormRef"
|
||||
:model="paymentForm"
|
||||
label-width="120px"
|
||||
>
|
||||
<el-form ref="paymentFormRef" :model="paymentForm" label-width="120px">
|
||||
<el-form-item label="批量确认缴费">
|
||||
<el-switch
|
||||
v-model="paymentForm.confirmPayment"
|
||||
active-text="全部已缴费"
|
||||
inactive-text="保持原状态"
|
||||
/>
|
||||
<el-switch v-model="paymentForm.confirmPayment" active-text="全部已缴费" inactive-text="保持原状态" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
@@ -154,50 +107,50 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, computed, toRefs, watch, onMounted } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { admissionProcess, confirmPayment } from '@/api/zsjy/enrollmentProcess'
|
||||
import type { EnrollmentProcessVO } from '@/api/zsjy/enrollmentProcess'
|
||||
import { ref, reactive, computed, toRefs, watch, onMounted } from 'vue';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { admissionProcess, confirmPayment } from '@/api/zsjy/enrollmentProcess';
|
||||
import type { EnrollmentProcessVO } from '@/api/zsjy/enrollmentProcess';
|
||||
|
||||
interface Props {
|
||||
visible: boolean
|
||||
selectedStudents: EnrollmentProcessVO[]
|
||||
visible: boolean;
|
||||
selectedStudents: EnrollmentProcessVO[];
|
||||
}
|
||||
|
||||
interface Emits {
|
||||
(e: 'update:visible', value: boolean): void
|
||||
(e: 'success'): void
|
||||
(e: 'update:visible', value: boolean): void;
|
||||
(e: 'success'): void;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
visible: false,
|
||||
selectedStudents: () => []
|
||||
})
|
||||
});
|
||||
|
||||
const emit = defineEmits<Emits>()
|
||||
const emit = defineEmits<Emits>();
|
||||
|
||||
// 对话框可见性
|
||||
const dialogVisible = computed({
|
||||
get: () => props.visible,
|
||||
set: (value: boolean) => {
|
||||
emit('update:visible', value)
|
||||
emit('update:visible', value);
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
// 表单引用和加载状态
|
||||
const settingFormRef = ref<ElFormInstance>()
|
||||
const paymentFormRef = ref<ElFormInstance>()
|
||||
const loading = ref(false)
|
||||
const submitLoading = ref(false)
|
||||
const settingFormRef = ref<ElFormInstance>();
|
||||
const paymentFormRef = ref<ElFormInstance>();
|
||||
const loading = ref(false);
|
||||
const submitLoading = ref(false);
|
||||
|
||||
// 学生数据,添加临时字段
|
||||
const selectedStudents = computed(() => {
|
||||
return props.selectedStudents.map(student => ({
|
||||
return props.selectedStudents.map((student) => ({
|
||||
...student,
|
||||
tempNoticeNumber: student.noticeNumber || '',
|
||||
isAutoGenerated: false
|
||||
}))
|
||||
})
|
||||
}));
|
||||
});
|
||||
|
||||
// 通知书设置表单
|
||||
const settingForm = reactive({
|
||||
@@ -205,133 +158,132 @@ const settingForm = reactive({
|
||||
startNumber: 1001,
|
||||
digitLength: 4,
|
||||
autoGenerate: true
|
||||
})
|
||||
});
|
||||
|
||||
// 预览示例
|
||||
const previewNoticeNumber = computed(() => {
|
||||
const prefix = settingForm.prefix || ''
|
||||
const number = settingForm.startNumber.toString().padStart(settingForm.digitLength, '0')
|
||||
return `${prefix}${number}`
|
||||
})
|
||||
const prefix = settingForm.prefix || '';
|
||||
const number = settingForm.startNumber.toString().padStart(settingForm.digitLength, '0');
|
||||
return `${prefix}${number}`;
|
||||
});
|
||||
|
||||
// 缴费确认表单
|
||||
const paymentForm = reactive({
|
||||
confirmPayment: false
|
||||
})
|
||||
});
|
||||
|
||||
// 表单验证规则
|
||||
const settingRules = {
|
||||
startNumber: [
|
||||
{ required: true, message: '请输入起始编号', trigger: 'blur' }
|
||||
],
|
||||
digitLength: [
|
||||
{ required: true, message: '请选择数字位数', trigger: 'blur' }
|
||||
]
|
||||
}
|
||||
startNumber: [{ required: true, message: '请输入起始编号', trigger: 'blur' }],
|
||||
digitLength: [{ required: true, message: '请选择数字位数', trigger: 'blur' }]
|
||||
};
|
||||
|
||||
// 监听对话框打开,初始化表单
|
||||
watch(() => props.visible, (newVal) => {
|
||||
if (newVal) {
|
||||
initForm()
|
||||
watch(
|
||||
() => props.visible,
|
||||
(newVal) => {
|
||||
if (newVal) {
|
||||
initForm();
|
||||
}
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
/** 初始化表单 */
|
||||
const initForm = () => {
|
||||
settingForm.prefix = 'HDY2025-'
|
||||
settingForm.startNumber = 1001
|
||||
settingForm.digitLength = 4
|
||||
settingForm.autoGenerate = true
|
||||
paymentForm.confirmPayment = false
|
||||
settingForm.prefix = 'HDY2025-';
|
||||
settingForm.startNumber = 1001;
|
||||
settingForm.digitLength = 4;
|
||||
settingForm.autoGenerate = true;
|
||||
paymentForm.confirmPayment = false;
|
||||
|
||||
// 如果第一个学生已有通知书号,尝试提取前缀和起始编号
|
||||
if (props.selectedStudents.length > 0 && props.selectedStudents[0].noticeNumber) {
|
||||
const noticeNumber = props.selectedStudents[0].noticeNumber
|
||||
const match = noticeNumber.match(/^([^\d]*)(\d+)$/)
|
||||
const noticeNumber = props.selectedStudents[0].noticeNumber;
|
||||
const match = noticeNumber.match(/^([^\d]*)(\d+)$/);
|
||||
if (match) {
|
||||
settingForm.prefix = match[1] || 'HDY2025-'
|
||||
const number = parseInt(match[2])
|
||||
settingForm.startNumber = isNaN(number) ? 1001 : number
|
||||
settingForm.digitLength = match[2].length
|
||||
settingForm.prefix = match[1] || 'HDY2025-';
|
||||
const number = parseInt(match[2]);
|
||||
settingForm.startNumber = isNaN(number) ? 1001 : number;
|
||||
settingForm.digitLength = match[2].length;
|
||||
}
|
||||
}
|
||||
|
||||
// 延迟一下再生成通知书号,确保组件已渲染完成
|
||||
setTimeout(() => {
|
||||
if (settingForm.autoGenerate) {
|
||||
generateNoticeNumbers()
|
||||
generateNoticeNumbers();
|
||||
}
|
||||
}, 300)
|
||||
}
|
||||
}, 300);
|
||||
};
|
||||
|
||||
/** 生成录取通知书号 */
|
||||
const generateNoticeNumbers = () => {
|
||||
const { prefix, startNumber, digitLength } = settingForm
|
||||
const { prefix, startNumber, digitLength } = settingForm;
|
||||
|
||||
selectedStudents.value.forEach((student, index) => {
|
||||
const number = (startNumber + index).toString().padStart(digitLength, '0')
|
||||
student.tempNoticeNumber = `${prefix}${number}`
|
||||
student.isAutoGenerated = true
|
||||
})
|
||||
}
|
||||
const number = (startNumber + index).toString().padStart(digitLength, '0');
|
||||
student.tempNoticeNumber = `${prefix}${number}`;
|
||||
student.isAutoGenerated = true;
|
||||
});
|
||||
};
|
||||
|
||||
/** 清空通知书号 */
|
||||
const clearNoticeNumbers = () => {
|
||||
selectedStudents.value.forEach(student => {
|
||||
student.tempNoticeNumber = ''
|
||||
student.isAutoGenerated = false
|
||||
})
|
||||
}
|
||||
selectedStudents.value.forEach((student) => {
|
||||
student.tempNoticeNumber = '';
|
||||
student.isAutoGenerated = false;
|
||||
});
|
||||
};
|
||||
|
||||
/** 前缀变更 */
|
||||
const handlePrefixChange = () => {
|
||||
if (settingForm.autoGenerate) {
|
||||
generateNoticeNumbers()
|
||||
generateNoticeNumbers();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/** 起始编号变更 */
|
||||
const handleStartNumberChange = () => {
|
||||
if (settingForm.autoGenerate) {
|
||||
generateNoticeNumbers()
|
||||
generateNoticeNumbers();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/** 数字位数变更 */
|
||||
const handleDigitLengthChange = () => {
|
||||
if (settingForm.autoGenerate) {
|
||||
generateNoticeNumbers()
|
||||
generateNoticeNumbers();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/** 自动生成开关变更 */
|
||||
const handleAutoGenerateChange = (val: boolean) => {
|
||||
if (val) {
|
||||
generateNoticeNumbers()
|
||||
generateNoticeNumbers();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/** 手动修改通知书号 */
|
||||
const handleNoticeNumberChange = (row: any, index: number) => {
|
||||
// 标记为手动修改
|
||||
row.isAutoGenerated = false
|
||||
}
|
||||
row.isAutoGenerated = false;
|
||||
};
|
||||
|
||||
/** 提交处理 */
|
||||
const handleSubmit = async () => {
|
||||
if (!settingFormRef.value) return
|
||||
if (!settingFormRef.value) return;
|
||||
|
||||
try {
|
||||
await settingFormRef.value.validate()
|
||||
await settingFormRef.value.validate();
|
||||
|
||||
// 检查是否有未填写通知书号的学生
|
||||
const invalidStudents = selectedStudents.value.filter(s => !s.tempNoticeNumber)
|
||||
const invalidStudents = selectedStudents.value.filter((s) => !s.tempNoticeNumber);
|
||||
if (invalidStudents.length > 0) {
|
||||
ElMessage.warning(`有${invalidStudents.length}名学生未填写录取通知书号码`)
|
||||
return
|
||||
ElMessage.warning(`有${invalidStudents.length}名学生未填写录取通知书号码`);
|
||||
return;
|
||||
}
|
||||
|
||||
submitLoading.value = true
|
||||
submitLoading.value = true;
|
||||
|
||||
// 处理所有选中的学生
|
||||
const promises = selectedStudents.value.map(async (student) => {
|
||||
@@ -341,33 +293,33 @@ const handleSubmit = async () => {
|
||||
admissionResult: '1', // 处理通过
|
||||
noticeNumber: student.tempNoticeNumber,
|
||||
admissionOpinion: '批量录取处理'
|
||||
})
|
||||
});
|
||||
|
||||
// 如果需要确认缴费
|
||||
if (paymentForm.confirmPayment && student.paymentStatus !== '1') {
|
||||
await confirmPayment(student.id as number)
|
||||
await confirmPayment(student.id as number);
|
||||
}
|
||||
|
||||
return student.id
|
||||
})
|
||||
return student.id;
|
||||
});
|
||||
|
||||
await Promise.all(promises)
|
||||
await Promise.all(promises);
|
||||
|
||||
ElMessage.success('批量录取处理成功')
|
||||
emit('success')
|
||||
handleClose()
|
||||
ElMessage.success('批量录取处理成功');
|
||||
emit('success');
|
||||
handleClose();
|
||||
} catch (error) {
|
||||
console.error('批量处理提交失败:', error)
|
||||
ElMessage.error('批量处理提交失败')
|
||||
console.error('批量处理提交失败:', error);
|
||||
ElMessage.error('批量处理提交失败');
|
||||
} finally {
|
||||
submitLoading.value = false
|
||||
submitLoading.value = false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/** 关闭对话框 */
|
||||
const handleClose = () => {
|
||||
dialogVisible.value = false
|
||||
}
|
||||
dialogVisible.value = false;
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -6,35 +6,18 @@
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="6">
|
||||
<el-form-item label="学生姓名" prop="studentName">
|
||||
<el-input
|
||||
v-model="queryParams.studentName"
|
||||
placeholder="请输入学生姓名"
|
||||
clearable
|
||||
style="width: 100%"
|
||||
@keyup.enter="handleQuery"
|
||||
/>
|
||||
<el-input v-model="queryParams.studentName" placeholder="请输入学生姓名" clearable style="width: 100%" @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="身份证号" prop="idCard">
|
||||
<el-input
|
||||
v-model="queryParams.idCard"
|
||||
placeholder="请输入身份证号"
|
||||
clearable
|
||||
style="width: 100%"
|
||||
@keyup.enter="handleQuery"
|
||||
/>
|
||||
<el-input v-model="queryParams.idCard" placeholder="请输入身份证号" clearable style="width: 100%" @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="缴费状态" prop="paymentStatus">
|
||||
<el-select v-model="queryParams.paymentStatus" placeholder="请选择缴费状态" clearable style="width: 100%">
|
||||
<el-option
|
||||
v-for="dict in zsjy_payment_status"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
<el-option v-for="dict in zsjy_payment_status" :key="dict.value" :label="dict.label" :value="dict.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
@@ -99,23 +82,8 @@
|
||||
<div class="card-header">
|
||||
<span class="toolbar-title">新生录取管理</span>
|
||||
<div class="toolbar-actions">
|
||||
<el-button
|
||||
v-hasPermi="['zsjy:admission:export']"
|
||||
type="warning"
|
||||
plain
|
||||
icon="Download"
|
||||
@click="handleExport"
|
||||
>
|
||||
导出
|
||||
</el-button>
|
||||
<el-button
|
||||
type="info"
|
||||
plain
|
||||
icon="Refresh"
|
||||
@click="handleRefreshStats"
|
||||
>
|
||||
刷新统计
|
||||
</el-button>
|
||||
<el-button v-hasPermi="['zsjy:admission:export']" type="warning" plain icon="Download" @click="handleExport"> 导出 </el-button>
|
||||
<el-button type="info" plain icon="Refresh" @click="handleRefreshStats"> 刷新统计 </el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -135,9 +103,9 @@
|
||||
<el-table-column type="selection" width="55" align="center" />
|
||||
<el-table-column label="学生信息" align="center" width="200">
|
||||
<template #default="scope">
|
||||
<div style="text-align: left;">
|
||||
<div style="font-weight: 600; color: #303133;">{{ scope.row.studentName }}</div>
|
||||
<div style="font-size: 12px; color: #909399; margin-top: 2px;">{{ scope.row.idCard }}</div>
|
||||
<div style="text-align: left">
|
||||
<div style="font-weight: 600; color: #303133">{{ scope.row.studentName }}</div>
|
||||
<div style="font-size: 12px; color: #909399; margin-top: 2px">{{ scope.row.idCard }}</div>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
@@ -161,22 +129,10 @@
|
||||
<template #default="scope">
|
||||
<div class="action-buttons">
|
||||
<el-tooltip content="查看详情" placement="top">
|
||||
<el-button
|
||||
v-hasPermi="['zsjy:admission:query']"
|
||||
link
|
||||
type="primary"
|
||||
icon="View"
|
||||
@click="handleDetail(scope.row)"
|
||||
/>
|
||||
<el-button v-hasPermi="['zsjy:admission:query']" link type="primary" icon="View" @click="handleDetail(scope.row)" />
|
||||
</el-tooltip>
|
||||
<el-tooltip content="录取处理" placement="top">
|
||||
<el-button
|
||||
v-hasPermi="['zsjy:admission:process']"
|
||||
link
|
||||
type="warning"
|
||||
icon="Edit"
|
||||
@click="handleAdmissionProcess(scope.row)"
|
||||
/>
|
||||
<el-button v-hasPermi="['zsjy:admission:process']" link type="warning" icon="Edit" @click="handleAdmissionProcess(scope.row)" />
|
||||
</el-tooltip>
|
||||
<el-tooltip content="确认缴费" placement="top">
|
||||
<el-button
|
||||
@@ -196,54 +152,39 @@
|
||||
|
||||
<!-- 分页 -->
|
||||
<div class="pagination-container">
|
||||
<pagination
|
||||
v-show="total > 0"
|
||||
v-model:page="queryParams.pageNum"
|
||||
v-model:limit="queryParams.pageSize"
|
||||
:total="total"
|
||||
@pagination="getList"
|
||||
/>
|
||||
<pagination v-show="total > 0" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" :total="total" @pagination="getList" />
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- 流程详情对话框 -->
|
||||
<ProcessDetailDialog
|
||||
v-model:visible="detailDialogVisible"
|
||||
:process-data="currentProcess"
|
||||
/>
|
||||
<ProcessDetailDialog v-model:visible="detailDialogVisible" :process-data="currentProcess" />
|
||||
|
||||
<!-- 批量录取处理对话框 -->
|
||||
<BatchAdmissionDialog
|
||||
v-model:visible="batchAdmissionDialogVisible"
|
||||
:selected-students="selectedStudents"
|
||||
@success="handleDialogSuccess"
|
||||
/>
|
||||
<BatchAdmissionDialog v-model:visible="batchAdmissionDialogVisible" :selected-students="selectedStudents" @success="handleDialogSuccess" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup name="AdmissionManagement" lang="ts">
|
||||
import { reactive, ref, toRefs, onMounted } from 'vue'
|
||||
import { reactive, ref, toRefs, onMounted } from 'vue';
|
||||
import {
|
||||
listEnrollmentProcess,
|
||||
getEnrollmentProcess,
|
||||
getProcessStatistics,
|
||||
confirmPayment,
|
||||
exportEnrollmentProcess
|
||||
} from '@/api/zsjy/enrollmentProcess'
|
||||
import type { EnrollmentProcessVO, EnrollmentProcessQuery } from '@/api/zsjy/enrollmentProcess'
|
||||
import { ElMessageBox, ElMessage } from 'element-plus'
|
||||
import { Clock, Select, CircleCheck } from '@element-plus/icons-vue'
|
||||
import ProcessDetailDialog from '../enrollmentProcess/components/ProcessDetailDialog.vue'
|
||||
import BatchAdmissionDialog from './components/BatchAdmissionDialog.vue'
|
||||
} from '@/api/zsjy/enrollmentProcess';
|
||||
import type { EnrollmentProcessVO, EnrollmentProcessQuery } from '@/api/zsjy/enrollmentProcess';
|
||||
import { ElMessageBox, ElMessage } from 'element-plus';
|
||||
import { Clock, Select, CircleCheck } from '@element-plus/icons-vue';
|
||||
import ProcessDetailDialog from '../enrollmentProcess/components/ProcessDetailDialog.vue';
|
||||
import BatchAdmissionDialog from './components/BatchAdmissionDialog.vue';
|
||||
|
||||
const { proxy } = getCurrentInstance() as ComponentInternalInstance
|
||||
const { zsjy_enrollment_status, zsjy_payment_status } = toRefs<any>(
|
||||
proxy?.useDict('zsjy_enrollment_status', 'zsjy_payment_status')
|
||||
)
|
||||
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
||||
const { zsjy_enrollment_status, zsjy_payment_status } = toRefs<any>(proxy?.useDict('zsjy_enrollment_status', 'zsjy_payment_status'));
|
||||
|
||||
// 页面数据
|
||||
const data = reactive<{
|
||||
queryParams: EnrollmentProcessQuery
|
||||
queryParams: EnrollmentProcessQuery;
|
||||
}>({
|
||||
queryParams: {
|
||||
pageNum: 1,
|
||||
@@ -254,46 +195,46 @@ const data = reactive<{
|
||||
paymentStatus: undefined,
|
||||
params: {}
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
const { queryParams } = toRefs(data)
|
||||
const { queryParams } = toRefs(data);
|
||||
|
||||
// 页面状态
|
||||
const queryFormRef = ref<ElFormInstance>()
|
||||
const processTableRef = ref<ElTableInstance>()
|
||||
const loading = ref(true)
|
||||
const ids = ref<Array<string | number>>([])
|
||||
const total = ref(0)
|
||||
const processList = ref<EnrollmentProcessVO[]>([])
|
||||
const selectedStudents = ref<EnrollmentProcessVO[]>([])
|
||||
const queryFormRef = ref<ElFormInstance>();
|
||||
const processTableRef = ref<ElTableInstance>();
|
||||
const loading = ref(true);
|
||||
const ids = ref<Array<string | number>>([]);
|
||||
const total = ref(0);
|
||||
const processList = ref<EnrollmentProcessVO[]>([]);
|
||||
const selectedStudents = ref<EnrollmentProcessVO[]>([]);
|
||||
|
||||
// 统计数据
|
||||
const statistics = ref({
|
||||
total: 0,
|
||||
pending: 0,
|
||||
completed: 0
|
||||
})
|
||||
});
|
||||
|
||||
// 对话框状态
|
||||
const detailDialogVisible = ref(false)
|
||||
const batchAdmissionDialogVisible = ref(false)
|
||||
const currentProcess = ref<Partial<EnrollmentProcessVO>>({})
|
||||
const detailDialogVisible = ref(false);
|
||||
const batchAdmissionDialogVisible = ref(false);
|
||||
const currentProcess = ref<Partial<EnrollmentProcessVO>>({});
|
||||
|
||||
/** 查询录取处理中的学生列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
loading.value = true;
|
||||
try {
|
||||
// 确保查询条件中包含currentStatus=2(录取处理中)
|
||||
const params = { ...queryParams.value, currentStatus: '2' }
|
||||
const res = await listEnrollmentProcess(params)
|
||||
processList.value = res.rows || []
|
||||
total.value = res.total || 0
|
||||
const params = { ...queryParams.value, currentStatus: '2' };
|
||||
const res = await listEnrollmentProcess(params);
|
||||
processList.value = res.rows || [];
|
||||
total.value = res.total || 0;
|
||||
} catch (error) {
|
||||
console.error('获取列表失败:', error)
|
||||
console.error('获取列表失败:', error);
|
||||
|
||||
// 如果是404错误(接口不存在),使用模拟数据
|
||||
if (error?.response?.status === 404 || error?.message?.includes('404')) {
|
||||
console.warn('后端接口不存在,使用模拟数据')
|
||||
console.warn('后端接口不存在,使用模拟数据');
|
||||
// 创建模拟数据
|
||||
processList.value = [
|
||||
{
|
||||
@@ -322,83 +263,83 @@ const getList = async () => {
|
||||
isCompleted: 0,
|
||||
createTime: '2025-07-20 10:28:18'
|
||||
}
|
||||
]
|
||||
total.value = 2
|
||||
proxy?.$modal.msgWarning('后端接口暂未实现,显示模拟数据')
|
||||
];
|
||||
total.value = 2;
|
||||
proxy?.$modal.msgWarning('后端接口暂未实现,显示模拟数据');
|
||||
} else {
|
||||
// 其他错误,设置空数据
|
||||
processList.value = []
|
||||
total.value = 0
|
||||
processList.value = [];
|
||||
total.value = 0;
|
||||
}
|
||||
} finally {
|
||||
loading.value = false
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/** 获取统计数据 */
|
||||
const getStatistics = async () => {
|
||||
try {
|
||||
// 基于当前流程列表计算统计数据
|
||||
const stats = processList.value || []
|
||||
const stats = processList.value || [];
|
||||
const paymentCounts = stats.reduce((acc: any, item: any) => {
|
||||
const status = item.paymentStatus || '0'
|
||||
acc[status] = (acc[status] || 0) + 1
|
||||
return acc
|
||||
}, {})
|
||||
const status = item.paymentStatus || '0';
|
||||
acc[status] = (acc[status] || 0) + 1;
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
statistics.value = {
|
||||
total: stats.length,
|
||||
pending: paymentCounts['0'] || 0, // 未缴费
|
||||
completed: paymentCounts['1'] || 0 // 已缴费
|
||||
}
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('获取统计数据失败:', error)
|
||||
console.error('获取统计数据失败:', error);
|
||||
// 设置默认值
|
||||
statistics.value = {
|
||||
total: 0,
|
||||
pending: 0,
|
||||
completed: 0
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = async () => {
|
||||
queryParams.value.pageNum = 1
|
||||
queryParams.value.pageNum = 1;
|
||||
try {
|
||||
await getList()
|
||||
await getStatistics()
|
||||
await getList();
|
||||
await getStatistics();
|
||||
} catch (error) {
|
||||
console.error('搜索失败:', error)
|
||||
console.error('搜索失败:', error);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/** 重置按钮操作 */
|
||||
const resetQuery = () => {
|
||||
queryFormRef.value?.resetFields()
|
||||
handleQuery()
|
||||
}
|
||||
queryFormRef.value?.resetFields();
|
||||
handleQuery();
|
||||
};
|
||||
|
||||
/** 多选框选中数据 */
|
||||
const handleSelectionChange = (selection: EnrollmentProcessVO[]) => {
|
||||
ids.value = selection.map(item => item.id)
|
||||
selectedStudents.value = selection
|
||||
}
|
||||
ids.value = selection.map((item) => item.id);
|
||||
selectedStudents.value = selection;
|
||||
};
|
||||
|
||||
/** 查看详情 */
|
||||
const handleDetail = (row: EnrollmentProcessVO) => {
|
||||
currentProcess.value = row
|
||||
detailDialogVisible.value = true
|
||||
}
|
||||
currentProcess.value = row;
|
||||
detailDialogVisible.value = true;
|
||||
};
|
||||
|
||||
/** 录取处理 */
|
||||
const handleAdmissionProcess = () => {
|
||||
if (selectedStudents.value.length === 0) {
|
||||
ElMessage.warning('请选择需要处理的学生')
|
||||
return
|
||||
ElMessage.warning('请选择需要处理的学生');
|
||||
return;
|
||||
}
|
||||
batchAdmissionDialogVisible.value = true
|
||||
}
|
||||
batchAdmissionDialogVisible.value = true;
|
||||
};
|
||||
|
||||
/** 缴费确认 */
|
||||
const handleConfirmPayment = async (row: EnrollmentProcessVO) => {
|
||||
@@ -407,50 +348,54 @@ const handleConfirmPayment = async (row: EnrollmentProcessVO) => {
|
||||
confirmButtonText: '确认',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
})
|
||||
});
|
||||
|
||||
await confirmPayment(row.id as number)
|
||||
ElMessage.success('缴费确认成功')
|
||||
await getList()
|
||||
await getStatistics()
|
||||
await confirmPayment(row.id as number);
|
||||
ElMessage.success('缴费确认成功');
|
||||
await getList();
|
||||
await getStatistics();
|
||||
} catch (error) {
|
||||
if (error !== 'cancel') {
|
||||
ElMessage.error('缴费确认失败')
|
||||
ElMessage.error('缴费确认失败');
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/** 导出按钮操作 */
|
||||
const handleExport = () => {
|
||||
proxy?.download('/zsjy/enrollment/process/export', {
|
||||
...queryParams.value,
|
||||
currentStatus: '2'
|
||||
}, `admission_students_${new Date().getTime()}.xlsx`)
|
||||
}
|
||||
proxy?.download(
|
||||
'/zsjy/enrollment/process/export',
|
||||
{
|
||||
...queryParams.value,
|
||||
currentStatus: '2'
|
||||
},
|
||||
`admission_students_${new Date().getTime()}.xlsx`
|
||||
);
|
||||
};
|
||||
|
||||
/** 刷新统计数据 */
|
||||
const handleRefreshStats = async () => {
|
||||
await getStatistics()
|
||||
ElMessage.success('统计数据已刷新')
|
||||
}
|
||||
await getStatistics();
|
||||
ElMessage.success('统计数据已刷新');
|
||||
};
|
||||
|
||||
/** 对话框成功回调 */
|
||||
const handleDialogSuccess = async () => {
|
||||
await getList()
|
||||
await getStatistics()
|
||||
}
|
||||
await getList();
|
||||
await getStatistics();
|
||||
};
|
||||
|
||||
// 初始化
|
||||
onMounted(async () => {
|
||||
try {
|
||||
// 先获取列表数据,再基于列表数据计算统计
|
||||
await getList()
|
||||
await getStatistics()
|
||||
await getList();
|
||||
await getStatistics();
|
||||
} catch (error) {
|
||||
console.error('页面初始化失败:', error)
|
||||
proxy?.$modal.msgError('页面初始化失败,请刷新页面重试')
|
||||
console.error('页面初始化失败:', error);
|
||||
proxy?.$modal.msgError('页面初始化失败,请刷新页面重试');
|
||||
}
|
||||
})
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -26,7 +26,12 @@
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="性别" prop="gender" required>
|
||||
<el-select v-model="applyForm.gender" placeholder="请选择性别" style="width: 100%; font-size: 14px; font-weight: bold;" :key="applyForm.gender">
|
||||
<el-select
|
||||
v-model="applyForm.gender"
|
||||
placeholder="请选择性别"
|
||||
style="width: 100%; font-size: 14px; font-weight: bold"
|
||||
:key="applyForm.gender"
|
||||
>
|
||||
<el-option v-for="dict in zsjy_gender" :key="dict.value" :label="dict.label" :value="dict.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
@@ -35,13 +40,7 @@
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="8">
|
||||
<el-form-item label="出生日期" prop="birthDate">
|
||||
<el-date-picker
|
||||
v-model="applyForm.birthDate"
|
||||
type="date"
|
||||
placeholder="选择出生日期"
|
||||
value-format="YYYY-MM-DD"
|
||||
style="width: 100%"
|
||||
/>
|
||||
<el-date-picker v-model="applyForm.birthDate" type="date" placeholder="选择出生日期" value-format="YYYY-MM-DD" style="width: 100%" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
@@ -283,7 +282,7 @@ const { zsjy_gender, sys_yes_no, zsjy_major, zsjy_uniform_size } = toRefs<any>(
|
||||
const applyFormRef = ref<InstanceType<typeof ElForm>>();
|
||||
const submitLoading = ref(false);
|
||||
const queryLoading = ref(false);
|
||||
const majorOptions = ref<Array<{ value: string, label: string, majorCode: string }>>([]);
|
||||
const majorOptions = ref<Array<{ value: string; label: string; majorCode: string }>>([]);
|
||||
|
||||
const statusDialog = reactive({
|
||||
visible: false
|
||||
@@ -342,9 +341,7 @@ const rules = {
|
||||
],
|
||||
phone2: [{ pattern: /^1[3-9]\d{9}$/, message: '手机号码格式不正确', trigger: 'blur' }],
|
||||
appliedMajor: [{ required: true, message: '报考专业不能为空', trigger: 'change' }],
|
||||
totalScoreFull: [
|
||||
{ type: 'number', min: 100, max: 1000, message: '中考满分应在100-1000之间', trigger: 'blur' }
|
||||
],
|
||||
totalScoreFull: [{ type: 'number', min: 100, max: 1000, message: '中考满分应在100-1000之间', trigger: 'blur' }],
|
||||
totalScore: [{ type: 'number', min: 0, message: '中考成绩不能小于0', trigger: 'blur' }],
|
||||
chineseScore: [{ type: 'number', min: 0, message: '语文成绩不能小于0', trigger: 'blur' }],
|
||||
mathScore: [{ type: 'number', min: 0, message: '数学成绩不能小于0', trigger: 'blur' }],
|
||||
@@ -388,7 +385,7 @@ const onIdCardChange = () => {
|
||||
|
||||
/** 专业选择变化处理 */
|
||||
const onMajorChange = (selectedMajor: string) => {
|
||||
const major = majorOptions.value.find(item => item.label === selectedMajor);
|
||||
const major = majorOptions.value.find((item) => item.label === selectedMajor);
|
||||
if (major) {
|
||||
applyForm.majorCode = major.majorCode;
|
||||
}
|
||||
@@ -465,7 +462,7 @@ const submitApplication = () => {
|
||||
/** 重置表单 */
|
||||
const resetForm = () => {
|
||||
applyFormRef.value?.resetFields();
|
||||
Object.keys(applyForm).forEach(key => {
|
||||
Object.keys(applyForm).forEach((key) => {
|
||||
applyForm[key] = undefined;
|
||||
});
|
||||
};
|
||||
|
||||
@@ -35,24 +35,14 @@
|
||||
<el-col :span="6">
|
||||
<el-form-item label="所属学院" prop="collegeId">
|
||||
<el-select v-model="queryParams.collegeId" placeholder="请选择学院" clearable class="w-100" @change="onSearchCollegeChange">
|
||||
<el-option
|
||||
v-for="college in collegeOptions"
|
||||
:key="college.deptId"
|
||||
:label="college.deptName"
|
||||
:value="college.deptCategory"
|
||||
/>
|
||||
<el-option v-for="college in collegeOptions" :key="college.deptId" :label="college.deptName" :value="college.deptCategory" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="报考专业" prop="appliedMajor">
|
||||
<el-select v-model="queryParams.appliedMajor" placeholder="请选择报考专业" clearable class="w-100">
|
||||
<el-option
|
||||
v-for="major in searchMajorOptions"
|
||||
:key="major.value"
|
||||
:label="major.label"
|
||||
:value="major.value"
|
||||
/>
|
||||
<el-option v-for="major in searchMajorOptions" :key="major.value" :label="major.label" :value="major.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
@@ -82,7 +72,6 @@
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
</el-row>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="18" class="search-btn-container">
|
||||
@@ -108,10 +97,16 @@
|
||||
</template>
|
||||
<div class="button-group">
|
||||
<el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['zsjy:enrollment:info:add']">新增</el-button>
|
||||
<el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['zsjy:enrollment:info:edit']">修改</el-button>
|
||||
<el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['zsjy:enrollment:info:remove']">删除</el-button>
|
||||
<el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['zsjy:enrollment:info:edit']"
|
||||
>修改</el-button
|
||||
>
|
||||
<el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['zsjy:enrollment:info:remove']"
|
||||
>删除</el-button
|
||||
>
|
||||
<el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['zsjy:enrollment:info:export']">导出</el-button>
|
||||
<el-button type="success" plain icon="Check" :disabled="multiple" @click="handleBatchAudit()" v-hasPermi="['zsjy:enrollment:info:edit']">批量审核</el-button>
|
||||
<el-button type="success" plain icon="Check" :disabled="multiple" @click="handleBatchAudit()" v-hasPermi="['zsjy:enrollment:info:edit']"
|
||||
>批量审核</el-button
|
||||
>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
@@ -127,7 +122,7 @@
|
||||
<template #default="scope">
|
||||
<!-- 直接显示性别文字 -->
|
||||
<span class="gender-text-direct">
|
||||
{{ scope.row.gender === 'M' ? '男' : (scope.row.gender === 'F' ? '女' : (getGenderDisplay(scope.row.gender, scope.row.idCard))) }}
|
||||
{{ scope.row.gender === 'M' ? '男' : scope.row.gender === 'F' ? '女' : getGenderDisplay(scope.row.gender, scope.row.idCard) }}
|
||||
</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
@@ -160,7 +155,13 @@
|
||||
<el-button link type="success" icon="Check" @click="handleAudit(scope.row)" v-hasPermi="['zsjy:enrollment:info:edit']"></el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="删除" placement="top">
|
||||
<el-button link type="danger" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['zsjy:enrollment:info:remove']"></el-button>
|
||||
<el-button
|
||||
link
|
||||
type="danger"
|
||||
icon="Delete"
|
||||
@click="handleDelete(scope.row)"
|
||||
v-hasPermi="['zsjy:enrollment:info:remove']"
|
||||
></el-button>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</template>
|
||||
@@ -193,7 +194,8 @@
|
||||
placeholder="请输入身份证号"
|
||||
@input="onIdCardChange"
|
||||
@change="onIdCardChange"
|
||||
@blur="onIdCardChange" />
|
||||
@blur="onIdCardChange"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
@@ -201,8 +203,9 @@
|
||||
<el-select
|
||||
v-model="form.gender"
|
||||
placeholder="请选择性别"
|
||||
style="width: 100%; font-size: 14px; font-weight: bold;"
|
||||
:key="`gender-select-${form.gender || 'empty'}`">
|
||||
style="width: 100%; font-size: 14px; font-weight: bold"
|
||||
:key="`gender-select-${form.gender || 'empty'}`"
|
||||
>
|
||||
<el-option label="男" value="M" />
|
||||
<el-option label="女" value="F" />
|
||||
</el-select>
|
||||
@@ -212,13 +215,7 @@
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="8">
|
||||
<el-form-item label="出生日期" prop="birthDate">
|
||||
<el-date-picker
|
||||
v-model="form.birthDate"
|
||||
type="date"
|
||||
placeholder="选择出生日期"
|
||||
value-format="YYYY-MM-DD"
|
||||
style="width: 100%"
|
||||
/>
|
||||
<el-date-picker v-model="form.birthDate" type="date" placeholder="选择出生日期" value-format="YYYY-MM-DD" style="width: 100%" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
@@ -334,13 +331,13 @@
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="所属学院" prop="collegeName">
|
||||
<el-select v-model="(form as EnrollmentInfoFormExtended).selectCollegeId" placeholder="请选择所属学院" style="width: 100%" @change="onCollegeChange">
|
||||
<el-option
|
||||
v-for="college in collegeOptions"
|
||||
:key="college.deptId"
|
||||
:label="college.deptName"
|
||||
:value="college.deptId"
|
||||
/>
|
||||
<el-select
|
||||
v-model="(form as EnrollmentInfoFormExtended).selectCollegeId"
|
||||
placeholder="请选择所属学院"
|
||||
style="width: 100%"
|
||||
@change="onCollegeChange"
|
||||
>
|
||||
<el-option v-for="college in collegeOptions" :key="college.deptId" :label="college.deptName" :value="college.deptId" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
@@ -354,12 +351,7 @@
|
||||
<el-col :span="12">
|
||||
<el-form-item label="报考专业" prop="appliedMajor">
|
||||
<el-select v-model="form.appliedMajor" placeholder="请选择报考专业" style="width: 100%" @change="onMajorChange">
|
||||
<el-option
|
||||
v-for="major in majorOptions"
|
||||
:key="major.value"
|
||||
:label="major.label"
|
||||
:value="major.value"
|
||||
/>
|
||||
<el-option v-for="major in majorOptions" :key="major.value" :label="major.label" :value="major.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
@@ -431,11 +423,11 @@
|
||||
<span class="view-text">{{ viewForm.idCard || '-' }}</span>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-col :span="8">
|
||||
<el-form-item label="性别">
|
||||
<!-- 直接使用字符串显示性别,完全跳过dict-tag组件 -->
|
||||
<span class="gender-text-direct">
|
||||
{{ viewForm.gender === 'M' ? '男' : (viewForm.gender === 'F' ? '女' : (getGenderDisplay(viewForm.gender, viewForm.idCard))) }}
|
||||
<span class="gender-text-direct">
|
||||
{{ viewForm.gender === 'M' ? '男' : viewForm.gender === 'F' ? '女' : getGenderDisplay(viewForm.gender, viewForm.idCard) }}
|
||||
</span>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
@@ -633,7 +625,13 @@
|
||||
<el-descriptions-item label="性别">
|
||||
<!-- 直接显示性别文字 -->
|
||||
<span class="gender-text-direct">
|
||||
{{ currentStudent.gender === 'M' ? '男' : (currentStudent.gender === 'F' ? '女' : (getGenderDisplay(currentStudent.gender, currentStudent.idCard))) }}
|
||||
{{
|
||||
currentStudent.gender === 'M'
|
||||
? '男'
|
||||
: currentStudent.gender === 'F'
|
||||
? '女'
|
||||
: getGenderDisplay(currentStudent.gender, currentStudent.idCard)
|
||||
}}
|
||||
</span>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="出生日期">
|
||||
@@ -665,7 +663,9 @@
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="报考信息">
|
||||
<el-descriptions :column="3" border>
|
||||
<el-descriptions-item label="所属学院">{{ currentStudent.collegeName || getCollegeName(currentStudent.collegeId) || '' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="所属学院">{{
|
||||
currentStudent.collegeName || getCollegeName(currentStudent.collegeId) || ''
|
||||
}}</el-descriptions-item>
|
||||
<el-descriptions-item label="学院代码">{{ currentStudent.collegeId || '' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="报考专业">{{ currentStudent.appliedMajor || '' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="专业代码">{{ currentStudent.majorCode || '' }}</el-descriptions-item>
|
||||
@@ -686,25 +686,20 @@
|
||||
|
||||
<!-- 批量审核时的提示和学生列表 -->
|
||||
<div v-if="isBatchAudit" class="batch-audit-info">
|
||||
<el-alert
|
||||
title="批量审核"
|
||||
type="warning"
|
||||
:closable="false">
|
||||
<p>您正在对 <strong>{{ ids.length }}</strong> 条学生信息进行批量审核操作</p>
|
||||
<el-alert title="批量审核" type="warning" :closable="false">
|
||||
<p>
|
||||
您正在对 <strong>{{ ids.length }}</strong> 条学生信息进行批量审核操作
|
||||
</p>
|
||||
<p v-if="selectedStudents.length < ids.length">
|
||||
<i class="el-icon-warning"></i> 注意:只能显示 {{ selectedStudents.length }} 条学生信息,有 {{ ids.length - selectedStudents.length }} 条信息未能加载
|
||||
<i class="el-icon-warning"></i> 注意:只能显示 {{ selectedStudents.length }} 条学生信息,有
|
||||
{{ ids.length - selectedStudents.length }} 条信息未能加载
|
||||
</p>
|
||||
</el-alert>
|
||||
|
||||
<!-- 批量审核的学生列表 -->
|
||||
<div class="student-list-section">
|
||||
<h4>已选择的学生列表:</h4>
|
||||
<el-table
|
||||
:data="selectedStudents"
|
||||
stripe
|
||||
style="width: 100%"
|
||||
max-height="400"
|
||||
:loading="batchLoading">
|
||||
<el-table :data="selectedStudents" stripe style="width: 100%" max-height="400" :loading="batchLoading">
|
||||
<el-table-column label="序号" type="index" width="60" />
|
||||
<el-table-column label="姓名" prop="name" min-width="80" />
|
||||
<el-table-column label="身份证号" prop="idCard" min-width="150" />
|
||||
@@ -751,7 +746,13 @@
|
||||
<el-descriptions-item label="性别">
|
||||
<!-- 直接显示性别文字 -->
|
||||
<span class="gender-text-direct">
|
||||
{{ detailStudent.gender === 'M' ? '男' : (detailStudent.gender === 'F' ? '女' : (getGenderDisplay(detailStudent.gender, detailStudent.idCard))) }}
|
||||
{{
|
||||
detailStudent.gender === 'M'
|
||||
? '男'
|
||||
: detailStudent.gender === 'F'
|
||||
? '女'
|
||||
: getGenderDisplay(detailStudent.gender, detailStudent.idCard)
|
||||
}}
|
||||
</span>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="出生日期">
|
||||
@@ -811,7 +812,16 @@
|
||||
</template>
|
||||
|
||||
<script setup name="EnrollmentInfo" lang="ts">
|
||||
import { addEnrollmentInfo, delEnrollmentInfo, exportEnrollmentInfo, getEnrollmentInfo, getEnrollmentInfoOptions, listEnrollmentInfo, updateEnrollmentInfo, updateEnrollmentStatus } from '@/api/zsjy/enrollment';
|
||||
import {
|
||||
addEnrollmentInfo,
|
||||
delEnrollmentInfo,
|
||||
exportEnrollmentInfo,
|
||||
getEnrollmentInfo,
|
||||
getEnrollmentInfoOptions,
|
||||
listEnrollmentInfo,
|
||||
updateEnrollmentInfo,
|
||||
updateEnrollmentStatus
|
||||
} from '@/api/zsjy/enrollment';
|
||||
import { createProcessFromEnrollment } from '@/api/zsjy/enrollmentProcess';
|
||||
import { EnrollmentInfoVO, EnrollmentInfoQuery, EnrollmentInfoForm } from '@/api/zsjy/enrollment/types';
|
||||
import { ComponentInternalInstance, getCurrentInstance, reactive, ref, toRefs, onMounted, watch, computed, nextTick } from 'vue';
|
||||
@@ -821,12 +831,12 @@ import { DeptVO } from '@/api/system/dept/types';
|
||||
|
||||
// 状态常量
|
||||
const ENROLLMENT_STATUS = {
|
||||
PENDING: '0', // 待审核
|
||||
AUDITING: '1', // 信息审核中
|
||||
PROCESSING: '2', // 录取处理中
|
||||
CONFIRMING: '3', // 待最终确认
|
||||
COMPLETED: '4', // 录取完成
|
||||
REJECTED: '9' // 已驳回
|
||||
PENDING: '0', // 待审核
|
||||
AUDITING: '1', // 信息审核中
|
||||
PROCESSING: '2', // 录取处理中
|
||||
CONFIRMING: '3', // 待最终确认
|
||||
COMPLETED: '4', // 录取完成
|
||||
REJECTED: '9' // 已驳回
|
||||
};
|
||||
|
||||
interface DialogOption {
|
||||
@@ -854,9 +864,9 @@ const ids = ref<Array<string | number>>([]);
|
||||
const single = ref(true);
|
||||
const multiple = ref(true);
|
||||
const total = ref(0);
|
||||
const majorOptions = ref<Array<{ value: string, label: string, majorCode: string }>>([]);
|
||||
const majorOptions = ref<Array<{ value: string; label: string; majorCode: string }>>([]);
|
||||
// 搜索区域的专业选项
|
||||
const searchMajorOptions = ref<Array<{ value: string, label: string }>>([]);
|
||||
const searchMajorOptions = ref<Array<{ value: string; label: string }>>([]);
|
||||
|
||||
// 批量审核分页相关
|
||||
const batchCurrentPage = ref(1);
|
||||
@@ -872,7 +882,7 @@ const paginatedStudents = computed(() => {
|
||||
// 性别显示转换计算属性
|
||||
const genderDisplay = computed(() => {
|
||||
if (!form.value.gender) return '';
|
||||
const genderOption = zsjy_gender.value?.find(item => item.value === form.value.gender);
|
||||
const genderOption = zsjy_gender.value?.find((item) => item.value === form.value.gender);
|
||||
return genderOption ? genderOption.label : form.value.gender;
|
||||
});
|
||||
|
||||
@@ -1009,7 +1019,7 @@ const initFormData: EnrollmentInfoFormExtended = {
|
||||
peScore: undefined,
|
||||
appliedMajor: undefined,
|
||||
majorCode: undefined,
|
||||
collegeId: undefined, // 学院代码
|
||||
collegeId: undefined, // 学院代码
|
||||
collegeName: undefined, // 学院名称
|
||||
selectCollegeId: undefined, // 临时字段:用于下拉框选择
|
||||
uniformSize: undefined,
|
||||
@@ -1051,9 +1061,7 @@ const data = reactive<PageData<EnrollmentInfoForm, EnrollmentInfoQuery>>({
|
||||
],
|
||||
phone2: [{ pattern: /^1[3-9]\d{9}$/, message: '手机号码格式不正确', trigger: 'blur' }],
|
||||
appliedMajor: [{ required: true, message: '报考专业不能为空', trigger: 'change' }],
|
||||
totalScoreFull: [
|
||||
{ type: 'number', min: 100, max: 1000, message: '中考满分应在100-1000之间', trigger: 'blur' }
|
||||
],
|
||||
totalScoreFull: [{ type: 'number', min: 100, max: 1000, message: '中考满分应在100-1000之间', trigger: 'blur' }],
|
||||
totalScore: [{ type: 'number', min: 0, message: '中考成绩不能小于0', trigger: 'blur' }],
|
||||
chineseScore: [{ type: 'number', min: 0, message: '语文成绩不能小于0', trigger: 'blur' }],
|
||||
mathScore: [{ type: 'number', min: 0, message: '数学成绩不能小于0', trigger: 'blur' }],
|
||||
@@ -1111,7 +1119,7 @@ const onIdCardChange = () => {
|
||||
|
||||
/** 专业选择变化处理 */
|
||||
const onMajorChange = (selectedMajorId: string) => {
|
||||
const major = majorOptions.value.find(item => item.value === selectedMajorId);
|
||||
const major = majorOptions.value.find((item) => item.value === selectedMajorId);
|
||||
if (major) {
|
||||
// 将专业名称和专业代码分别赋值到对应字段
|
||||
form.value.appliedMajor = major.label; // 专业名称
|
||||
@@ -1127,19 +1135,19 @@ const onMajorChange = (selectedMajorId: string) => {
|
||||
const onCollegeChange = (selectedCollegeId: string) => {
|
||||
if (selectedCollegeId) {
|
||||
// 根据选择的学院ID找到学院对象
|
||||
const college = collegeOptions.value.find(item => item.deptId == selectedCollegeId);
|
||||
const college = collegeOptions.value.find((item) => item.deptId == selectedCollegeId);
|
||||
if (college) {
|
||||
// 正确设置:
|
||||
// - collegeId (学院代码): 使用部门的类别编码
|
||||
// - collegeName (学院名称): 使用部门名称
|
||||
// - selectCollegeId: 保存部门ID用于关联
|
||||
form.value.collegeId = college.deptCategory || ''; // 学院代码
|
||||
form.value.collegeName = college.deptName; // 学院名称
|
||||
form.value.collegeName = college.deptName; // 学院名称
|
||||
(form.value as EnrollmentInfoFormExtended).selectCollegeId = selectedCollegeId; // 使用正确类型
|
||||
|
||||
// 更新专业选项
|
||||
const majors = majorOptionsByCollege.value[selectedCollegeId] || [];
|
||||
majorOptions.value = majors.map(major => ({
|
||||
majorOptions.value = majors.map((major) => ({
|
||||
value: major.deptId.toString(),
|
||||
label: major.deptName,
|
||||
majorCode: major.deptCategory || ''
|
||||
@@ -1180,26 +1188,24 @@ const onSearchCollegeChange = (selectedCollegeId: string) => {
|
||||
|
||||
// 首先从字典中获取
|
||||
if (zsjy_major.value) {
|
||||
const filteredMajors = zsjy_major.value.filter(
|
||||
item => item.cssClass === selectedCollegeId
|
||||
);
|
||||
majors.push(...filteredMajors.map(m => ({ value: m.value, label: m.label })));
|
||||
const filteredMajors = zsjy_major.value.filter((item) => item.cssClass === selectedCollegeId);
|
||||
majors.push(...filteredMajors.map((m) => ({ value: m.value, label: m.label })));
|
||||
}
|
||||
|
||||
// 然后从部门数据中获取
|
||||
const college = collegeOptions.value.find(c => c.deptCategory === selectedCollegeId);
|
||||
const college = collegeOptions.value.find((c) => c.deptCategory === selectedCollegeId);
|
||||
if (college && majorOptionsByCollege.value[college.deptId]) {
|
||||
const deptMajors = majorOptionsByCollege.value[college.deptId];
|
||||
majors.push(...deptMajors.map(m => ({
|
||||
value: m.deptName,
|
||||
label: m.deptName
|
||||
})));
|
||||
majors.push(
|
||||
...deptMajors.map((m) => ({
|
||||
value: m.deptName,
|
||||
label: m.deptName
|
||||
}))
|
||||
);
|
||||
}
|
||||
|
||||
// 去除可能的重复项
|
||||
const uniqueMajors = majors.filter((item, index, self) =>
|
||||
index === self.findIndex(m => m.value === item.value)
|
||||
);
|
||||
const uniqueMajors = majors.filter((item, index, self) => index === self.findIndex((m) => m.value === item.value));
|
||||
|
||||
// 更新专业选项
|
||||
searchMajorOptions.value = uniqueMajors;
|
||||
@@ -1218,18 +1224,18 @@ const UNIFORM_SIZES = [
|
||||
{ value: '175/94A', label: '175/94A(身高175cm)', height: '175cm' },
|
||||
{ value: '180/96A', label: '180/96A(身高180cm)', height: '180cm' },
|
||||
{ value: '185/98A', label: '185/98A(身高185cm)', height: '185cm' },
|
||||
{ value: '190/100A', label: '190/100A(身高190cm)', height: '190cm' },
|
||||
{ value: '190/100A', label: '190/100A(身高190cm)', height: '190cm' }
|
||||
];
|
||||
|
||||
/** 根据学院代码查找学院名称 */
|
||||
const getCollegeName = (code: string | undefined) => {
|
||||
if (!code) return '-';
|
||||
// 首先查找zsjy_college字典
|
||||
const collegeDict = zsjy_college.value?.find(item => item.value === code);
|
||||
const collegeDict = zsjy_college.value?.find((item) => item.value === code);
|
||||
if (collegeDict) return collegeDict.label;
|
||||
|
||||
// 如果在字典中没找到,尝试在部门数据中查找
|
||||
const college = collegeOptions.value.find(c => c.deptCategory === code);
|
||||
const college = collegeOptions.value.find((c) => c.deptCategory === code);
|
||||
return college ? college.deptName : code;
|
||||
};
|
||||
|
||||
@@ -1249,7 +1255,7 @@ const getCollegeNameEnhanced = (student: any) => {
|
||||
// 2. 如果只有学院代码,尝试查找对应名称
|
||||
if (student.collegeId) {
|
||||
// 2.1 先从字典中查找
|
||||
const collegeDict = zsjy_college.value?.find(item => item.value === student.collegeId);
|
||||
const collegeDict = zsjy_college.value?.find((item) => item.value === student.collegeId);
|
||||
if (collegeDict) {
|
||||
console.log('从字典获取到学院名称:', collegeDict.label);
|
||||
return collegeDict.label;
|
||||
@@ -1257,7 +1263,7 @@ const getCollegeNameEnhanced = (student: any) => {
|
||||
|
||||
// 2.2 再从部门数据中查找
|
||||
if (collegeOptions.value && collegeOptions.value.length > 0) {
|
||||
const college = collegeOptions.value.find(c => c.deptCategory === student.collegeId);
|
||||
const college = collegeOptions.value.find((c) => c.deptCategory === student.collegeId);
|
||||
if (college) {
|
||||
console.log('从部门数据获取到学院名称:', college.deptName);
|
||||
return college.deptName;
|
||||
@@ -1275,7 +1281,7 @@ const getCollegeNameEnhanced = (student: any) => {
|
||||
/** 获取校服尺码显示名称 */
|
||||
const getUniformSizeLabel = (value: string | undefined) => {
|
||||
if (!value) return '-';
|
||||
const size = UNIFORM_SIZES.find(item => item.value === value);
|
||||
const size = UNIFORM_SIZES.find((item) => item.value === value);
|
||||
return size ? size.label : value;
|
||||
};
|
||||
|
||||
@@ -1396,12 +1402,10 @@ const handleUpdate = async (row?: EnrollmentInfoVO) => {
|
||||
await getCollegeAndMajorData();
|
||||
}
|
||||
|
||||
// 如果有学院名称或学院代码,加载该学院下的专业
|
||||
// 如果有学院名称或学院代码,加载该学院下的专业
|
||||
if (form.value.collegeName || form.value.collegeId) {
|
||||
// 根据学院代码或名称找到对应学院
|
||||
const college = collegeOptions.value.find(c =>
|
||||
c.deptCategory === form.value.collegeId || c.deptName === form.value.collegeName
|
||||
);
|
||||
const college = collegeOptions.value.find((c) => c.deptCategory === form.value.collegeId || c.deptName === form.value.collegeName);
|
||||
|
||||
if (college) {
|
||||
// 记住原始值,以便后面恢复
|
||||
@@ -1415,15 +1419,15 @@ const handleUpdate = async (row?: EnrollmentInfoVO) => {
|
||||
|
||||
// 加载该学院的专业
|
||||
const majors = majorOptionsByCollege.value[college.deptId] || [];
|
||||
majorOptions.value = majors.map(major => ({
|
||||
majorOptions.value = majors.map((major) => ({
|
||||
value: major.deptId.toString(),
|
||||
label: major.deptName,
|
||||
majorCode: major.deptCategory || ''
|
||||
}));
|
||||
|
||||
// 正确设置学院信息
|
||||
form.value.collegeId = college.deptCategory || ''; // 学院代码
|
||||
form.value.collegeName = college.deptName; // 学院名称
|
||||
form.value.collegeId = college.deptCategory || ''; // 学院代码
|
||||
form.value.collegeName = college.deptName; // 学院名称
|
||||
|
||||
console.log('编辑表单 - 学院信息:', {
|
||||
deptId: college.deptId,
|
||||
@@ -1437,12 +1441,12 @@ const handleUpdate = async (row?: EnrollmentInfoVO) => {
|
||||
|
||||
// 首先通过已有的专业名称查找
|
||||
if (originalAppliedMajor) {
|
||||
selectedMajor = majorOptions.value.find(m => m.label === originalAppliedMajor);
|
||||
selectedMajor = majorOptions.value.find((m) => m.label === originalAppliedMajor);
|
||||
}
|
||||
|
||||
// 如果没找到,再通过专业代码查找
|
||||
if (!selectedMajor && originalMajorCode) {
|
||||
selectedMajor = majorOptions.value.find(m => m.majorCode === originalMajorCode);
|
||||
selectedMajor = majorOptions.value.find((m) => m.majorCode === originalMajorCode);
|
||||
}
|
||||
|
||||
if (selectedMajor) {
|
||||
@@ -1468,7 +1472,7 @@ const handleView = async (row: EnrollmentInfoVO) => {
|
||||
console.log('查看按钮点击,ID:', row.id);
|
||||
|
||||
// 清空表单,避免数据污染
|
||||
Object.keys(viewForm).forEach(key => {
|
||||
Object.keys(viewForm).forEach((key) => {
|
||||
// @ts-ignore
|
||||
viewForm[key] = undefined;
|
||||
});
|
||||
@@ -1482,8 +1486,8 @@ const handleView = async (row: EnrollmentInfoVO) => {
|
||||
|
||||
console.log('API返回数据:', res.data);
|
||||
|
||||
// 先备份并打印原始数据,方便调试
|
||||
const originalData = {...res.data};
|
||||
// 先备份并打印原始数据,方便调试
|
||||
const originalData = { ...res.data };
|
||||
console.log('原始API返回数据:', originalData);
|
||||
|
||||
// 直接将API返回的数据赋值给表单,不进行任何处理
|
||||
@@ -1515,7 +1519,7 @@ const handleView = async (row: EnrollmentInfoVO) => {
|
||||
// 在后台尝试加载学院数据
|
||||
if (collegeOptions.value.length === 0) {
|
||||
console.log('学院数据未加载,正在加载...');
|
||||
getCollegeAndMajorData().catch(err => {
|
||||
getCollegeAndMajorData().catch((err) => {
|
||||
console.warn('加载学院数据失败,但不影响显示:', err);
|
||||
});
|
||||
}
|
||||
@@ -1523,7 +1527,7 @@ const handleView = async (row: EnrollmentInfoVO) => {
|
||||
// 在后台尝试加载字典数据
|
||||
if (!zsjy_college.value || zsjy_college.value.length === 0) {
|
||||
console.log('学院字典未加载,正在加载...');
|
||||
proxy?.getDicts("zsjy_college").catch(err => {
|
||||
proxy?.getDicts('zsjy_college').catch((err) => {
|
||||
console.warn('加载学院字典失败,但不影响显示:', err);
|
||||
});
|
||||
}
|
||||
@@ -1631,7 +1635,7 @@ const handleBatchAudit = async () => {
|
||||
} catch (error) {
|
||||
if (retryCount < maxRetries) {
|
||||
console.log(`获取学生信息失败,ID: ${id},正在重试第${retryCount + 1}次...`);
|
||||
await new Promise(resolve => setTimeout(resolve, 500)); // 等待500ms后重试
|
||||
await new Promise((resolve) => setTimeout(resolve, 500)); // 等待500ms后重试
|
||||
return getStudentInfoWithRetry(id, retryCount + 1);
|
||||
}
|
||||
console.error(`获取学生信息失败,ID: ${id},已重试${maxRetries}次`, error);
|
||||
@@ -1640,13 +1644,11 @@ const handleBatchAudit = async () => {
|
||||
};
|
||||
|
||||
// 使用带重试功能的函数获取所有学生信息
|
||||
const requests = ids.value.map(id => getStudentInfoWithRetry(id));
|
||||
const requests = ids.value.map((id) => getStudentInfoWithRetry(id));
|
||||
const results = await Promise.all(requests);
|
||||
|
||||
// 过滤掉请求失败的结果
|
||||
selectedStudents.value = results
|
||||
.filter(res => res !== null && res.data)
|
||||
.map(res => res.data);
|
||||
selectedStudents.value = results.filter((res) => res !== null && res.data).map((res) => res.data);
|
||||
|
||||
// 验证是否获取到了所有学生
|
||||
if (selectedStudents.value.length < ids.value.length) {
|
||||
@@ -1686,9 +1688,10 @@ const submitAudit = async () => {
|
||||
buttonLoading.value = true;
|
||||
try {
|
||||
// 根据审核结果设置状态值
|
||||
const status = auditForm.result === 'pass'
|
||||
? ENROLLMENT_STATUS.PROCESSING // 审核通过,设为"录取处理中"
|
||||
: ENROLLMENT_STATUS.REJECTED; // 审核不通过,设为"已驳回"
|
||||
const status =
|
||||
auditForm.result === 'pass'
|
||||
? ENROLLMENT_STATUS.PROCESSING // 审核通过,设为"录取处理中"
|
||||
: ENROLLMENT_STATUS.REJECTED; // 审核不通过,设为"已驳回"
|
||||
|
||||
if (auditForm.id) {
|
||||
// 单个审核
|
||||
@@ -1725,7 +1728,7 @@ const showRawData = () => {
|
||||
// 1. 基本信息
|
||||
info += '【基本信息】\n';
|
||||
info += `- 姓名: ${viewForm.name || '-'}\n`;
|
||||
info += `- 性别: ${viewForm.gender === 'M' ? '男' : (viewForm.gender === 'F' ? '女' : '-')}\n`;
|
||||
info += `- 性别: ${viewForm.gender === 'M' ? '男' : viewForm.gender === 'F' ? '女' : '-'}\n`;
|
||||
|
||||
// 2. 学院信息
|
||||
info += '\n【学院专业信息】\n';
|
||||
@@ -1767,38 +1770,51 @@ const viewStudentDetail = async (row: EnrollmentInfoVO) => {
|
||||
};
|
||||
|
||||
// 监听对话框关闭事件,重置状态
|
||||
watch(() => auditDialog.visible, (val) => {
|
||||
if (!val) {
|
||||
// 对话框关闭时重置状态
|
||||
auditForm.id = undefined;
|
||||
auditForm.result = 'pass';
|
||||
auditForm.remark = '';
|
||||
isBatchAudit.value = false;
|
||||
selectedStudents.value = [];
|
||||
watch(
|
||||
() => auditDialog.visible,
|
||||
(val) => {
|
||||
if (!val) {
|
||||
// 对话框关闭时重置状态
|
||||
auditForm.id = undefined;
|
||||
auditForm.result = 'pass';
|
||||
auditForm.remark = '';
|
||||
isBatchAudit.value = false;
|
||||
selectedStudents.value = [];
|
||||
}
|
||||
}
|
||||
});
|
||||
);
|
||||
|
||||
// 监听详情对话框关闭事件,重置状态
|
||||
watch(() => detailDialog.visible, (val) => {
|
||||
if (!val) {
|
||||
// 清空详情学生数据
|
||||
Object.keys(detailStudent).forEach(key => {
|
||||
if (key !== 'id') {
|
||||
detailStudent[key] = key === 'name' || key === 'idCard' || key === 'gender' ||
|
||||
key === 'phone1' || key === 'examNumber' || key === 'examRegion' ||
|
||||
key === 'appliedMajor' ? '' : undefined;
|
||||
} else {
|
||||
detailStudent[key] = undefined;
|
||||
}
|
||||
});
|
||||
watch(
|
||||
() => detailDialog.visible,
|
||||
(val) => {
|
||||
if (!val) {
|
||||
// 清空详情学生数据
|
||||
Object.keys(detailStudent).forEach((key) => {
|
||||
if (key !== 'id') {
|
||||
detailStudent[key] =
|
||||
key === 'name' ||
|
||||
key === 'idCard' ||
|
||||
key === 'gender' ||
|
||||
key === 'phone1' ||
|
||||
key === 'examNumber' ||
|
||||
key === 'examRegion' ||
|
||||
key === 'appliedMajor'
|
||||
? ''
|
||||
: undefined;
|
||||
} else {
|
||||
detailStudent[key] = undefined;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
);
|
||||
|
||||
// 已在顶部引入,此处不需要重复导入
|
||||
|
||||
// 修改 data 部分添加学院和专业数据存储
|
||||
const collegeOptions = ref<DeptVO[]>([]);
|
||||
const majorOptionsByCollege = ref<{[key: string]: DeptVO[]}>({});
|
||||
const majorOptionsByCollege = ref<{ [key: string]: DeptVO[] }>({});
|
||||
|
||||
// 添加获取部门数据的方法
|
||||
/** 获取学院和专业数据 */
|
||||
@@ -1809,7 +1825,7 @@ const getCollegeAndMajorData = async () => {
|
||||
// 强制刷新字典数据
|
||||
console.log('加载学院字典数据...');
|
||||
try {
|
||||
const dictResponse = await proxy?.getDicts("zsjy_college");
|
||||
const dictResponse = await proxy?.getDicts('zsjy_college');
|
||||
console.log('学院字典加载结果:', dictResponse);
|
||||
} catch (dictError) {
|
||||
console.error('加载字典数据出错:', dictError);
|
||||
@@ -1826,11 +1842,14 @@ const getCollegeAndMajorData = async () => {
|
||||
});
|
||||
|
||||
collegeOptions.value = colleges;
|
||||
console.log('获取到的学院数据:', colleges.map(c => ({
|
||||
deptId: c.deptId,
|
||||
deptName: c.deptName,
|
||||
deptCategory: c.deptCategory
|
||||
})));
|
||||
console.log(
|
||||
'获取到的学院数据:',
|
||||
colleges.map((c) => ({
|
||||
deptId: c.deptId,
|
||||
deptName: c.deptName,
|
||||
deptCategory: c.deptCategory
|
||||
}))
|
||||
);
|
||||
|
||||
// 如果没有找到学院数据,尝试其他可能的过滤条件
|
||||
if (colleges.length === 0) {
|
||||
@@ -1843,11 +1862,14 @@ const getCollegeAndMajorData = async () => {
|
||||
|
||||
if (possibleColleges.length > 0) {
|
||||
collegeOptions.value = possibleColleges;
|
||||
console.log('找到可能的学院:', possibleColleges.map(c => ({
|
||||
deptId: c.deptId,
|
||||
deptName: c.deptName,
|
||||
deptCategory: c.deptCategory
|
||||
})));
|
||||
console.log(
|
||||
'找到可能的学院:',
|
||||
possibleColleges.map((c) => ({
|
||||
deptId: c.deptId,
|
||||
deptName: c.deptName,
|
||||
deptCategory: c.deptCategory
|
||||
}))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1861,9 +1883,10 @@ const getCollegeAndMajorData = async () => {
|
||||
// 按照上下级关系组织专业
|
||||
colleges.forEach((college: DeptVO) => {
|
||||
// 找出该学院下的所有专业
|
||||
const majors = allMajors.filter((dept: DeptVO) =>
|
||||
// 使用上下级关系或其他关联关系确定专业归属哪个学院
|
||||
dept.parentId === college.deptId
|
||||
const majors = allMajors.filter(
|
||||
(dept: DeptVO) =>
|
||||
// 使用上下级关系或其他关联关系确定专业归属哪个学院
|
||||
dept.parentId === college.deptId
|
||||
);
|
||||
|
||||
majorOptionsByCollege.value[college.deptId] = majors;
|
||||
@@ -1875,7 +1898,7 @@ const getCollegeAndMajorData = async () => {
|
||||
// 如果当前有学生信息且缺少学院名称,尝试找到匹配
|
||||
if (viewForm.collegeId && !viewForm.collegeName) {
|
||||
// 查找学院名称
|
||||
const college = collegeOptions.value.find(c => c.deptCategory === viewForm.collegeId);
|
||||
const college = collegeOptions.value.find((c) => c.deptCategory === viewForm.collegeId);
|
||||
if (college) {
|
||||
console.log(`根据学院代码 ${viewForm.collegeId} 找到学院名称: ${college.deptName}`);
|
||||
viewForm.collegeName = college.deptName;
|
||||
|
||||
@@ -95,10 +95,11 @@ web/src/api/zsjy/enrollmentProcess.ts
|
||||
```
|
||||
|
||||
主要接口:
|
||||
|
||||
- `listEnrollmentProcess`:获取流程状态列表
|
||||
- `listMyAuditProcess`:获取待我审核列表
|
||||
- `getEnrollmentProcess`:获取流程状态详情
|
||||
- `infoAudit`:执行信息审核
|
||||
- `admissionProcess`:执行录取处理
|
||||
- `finalAudit`:执行最终确认
|
||||
- `exportEnrollmentProcess`:导出流程状态数据
|
||||
- `exportEnrollmentProcess`:导出流程状态数据
|
||||
@@ -1,11 +1,5 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
v-model="dialogVisible"
|
||||
title="录取处理"
|
||||
width="650px"
|
||||
class="admission-dialog"
|
||||
:before-close="handleClose"
|
||||
>
|
||||
<el-dialog v-model="dialogVisible" title="录取处理" width="650px" class="admission-dialog" :before-close="handleClose">
|
||||
<div class="admission-container">
|
||||
<!-- 学生信息展示 -->
|
||||
<el-card class="student-info-card" shadow="never">
|
||||
@@ -37,13 +31,7 @@
|
||||
<span class="section-title">录取处理</span>
|
||||
</div>
|
||||
</template>
|
||||
<el-form
|
||||
ref="processFormRef"
|
||||
:model="processForm"
|
||||
:rules="processRules"
|
||||
label-width="120px"
|
||||
class="process-form"
|
||||
>
|
||||
<el-form ref="processFormRef" :model="processForm" :rules="processRules" label-width="120px" class="process-form">
|
||||
<el-form-item label="处理结果" prop="processResult">
|
||||
<el-radio-group v-model="processForm.processResult" class="process-result-group">
|
||||
<el-radio value="1" class="process-radio process-pass">
|
||||
@@ -84,12 +72,7 @@
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="handleClose">取消</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
:loading="submitLoading"
|
||||
@click="handleSubmit"
|
||||
:disabled="!processForm.processResult"
|
||||
>
|
||||
<el-button type="primary" :loading="submitLoading" @click="handleSubmit" :disabled="!processForm.processResult">
|
||||
<el-icon><Check /></el-icon>
|
||||
提交处理
|
||||
</el-button>
|
||||
@@ -99,115 +82,115 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, computed, toRefs, watch } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { admissionProcess } from '@/api/zsjy/enrollmentProcess'
|
||||
import type { EnrollmentProcessVO } from '@/api/zsjy/enrollmentProcess'
|
||||
import { ref, reactive, computed, toRefs, watch } from 'vue';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { admissionProcess } from '@/api/zsjy/enrollmentProcess';
|
||||
import type { EnrollmentProcessVO } from '@/api/zsjy/enrollmentProcess';
|
||||
|
||||
interface Props {
|
||||
visible: boolean
|
||||
processData: Partial<EnrollmentProcessVO>
|
||||
visible: boolean;
|
||||
processData: Partial<EnrollmentProcessVO>;
|
||||
}
|
||||
|
||||
interface Emits {
|
||||
(e: 'update:visible', value: boolean): void
|
||||
(e: 'success'): void
|
||||
(e: 'update:visible', value: boolean): void;
|
||||
(e: 'success'): void;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
visible: false,
|
||||
processData: () => ({})
|
||||
})
|
||||
});
|
||||
|
||||
const emit = defineEmits<Emits>()
|
||||
const emit = defineEmits<Emits>();
|
||||
|
||||
const { proxy } = getCurrentInstance() as ComponentInternalInstance
|
||||
const { zsjy_enrollment_status, zsjy_process_status } = toRefs<any>(
|
||||
proxy?.useDict('zsjy_enrollment_status', 'zsjy_process_status')
|
||||
)
|
||||
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
||||
const { zsjy_enrollment_status, zsjy_process_status } = toRefs<any>(proxy?.useDict('zsjy_enrollment_status', 'zsjy_process_status'));
|
||||
|
||||
// 对话框可见性
|
||||
const dialogVisible = computed({
|
||||
get: () => props.visible,
|
||||
set: (value: boolean) => {
|
||||
emit('update:visible', value)
|
||||
emit('update:visible', value);
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
// 表单引用
|
||||
const processFormRef = ref<ElFormInstance>()
|
||||
const submitLoading = ref(false)
|
||||
const processFormRef = ref<ElFormInstance>();
|
||||
const submitLoading = ref(false);
|
||||
|
||||
// 处理表单
|
||||
const processForm = reactive({
|
||||
processResult: '',
|
||||
noticeNumber: '',
|
||||
processOpinion: ''
|
||||
})
|
||||
});
|
||||
|
||||
// 表单验证规则
|
||||
const processRules = {
|
||||
processResult: [
|
||||
{ required: true, message: '请选择处理结果', trigger: 'change' }
|
||||
],
|
||||
noticeNumber: [
|
||||
{ required: true, message: '请输入录取通知书号码', trigger: 'blur' }
|
||||
]
|
||||
}
|
||||
processResult: [{ required: true, message: '请选择处理结果', trigger: 'change' }],
|
||||
noticeNumber: [{ required: true, message: '请输入录取通知书号码', trigger: 'blur' }]
|
||||
};
|
||||
|
||||
// 监听对话框打开,重置表单
|
||||
watch(() => props.visible, (newVal) => {
|
||||
if (newVal) {
|
||||
resetForm()
|
||||
watch(
|
||||
() => props.visible,
|
||||
(newVal) => {
|
||||
if (newVal) {
|
||||
resetForm();
|
||||
}
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
// 监听处理结果变化,清空通知书号码
|
||||
watch(() => processForm.processResult, (newVal) => {
|
||||
if (newVal !== '1') {
|
||||
processForm.noticeNumber = ''
|
||||
watch(
|
||||
() => processForm.processResult,
|
||||
(newVal) => {
|
||||
if (newVal !== '1') {
|
||||
processForm.noticeNumber = '';
|
||||
}
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
/** 重置表单 */
|
||||
const resetForm = () => {
|
||||
processForm.processResult = ''
|
||||
processForm.noticeNumber = ''
|
||||
processForm.processOpinion = ''
|
||||
processFormRef.value?.clearValidate()
|
||||
}
|
||||
processForm.processResult = '';
|
||||
processForm.noticeNumber = '';
|
||||
processForm.processOpinion = '';
|
||||
processFormRef.value?.clearValidate();
|
||||
};
|
||||
|
||||
/** 提交处理 */
|
||||
const handleSubmit = async () => {
|
||||
if (!processFormRef.value) return
|
||||
if (!processFormRef.value) return;
|
||||
|
||||
try {
|
||||
await processFormRef.value.validate()
|
||||
await processFormRef.value.validate();
|
||||
|
||||
submitLoading.value = true
|
||||
submitLoading.value = true;
|
||||
|
||||
await admissionProcess({
|
||||
processId: props.processData.id as number,
|
||||
admissionResult: processForm.processResult,
|
||||
admissionOpinion: processForm.processOpinion || undefined,
|
||||
noticeNumber: processForm.processResult === '1' ? processForm.noticeNumber : undefined
|
||||
})
|
||||
});
|
||||
|
||||
ElMessage.success('处理提交成功')
|
||||
emit('success')
|
||||
handleClose()
|
||||
ElMessage.success('处理提交成功');
|
||||
emit('success');
|
||||
handleClose();
|
||||
} catch (error) {
|
||||
console.error('处理提交失败:', error)
|
||||
ElMessage.error('处理提交失败')
|
||||
console.error('处理提交失败:', error);
|
||||
ElMessage.error('处理提交失败');
|
||||
} finally {
|
||||
submitLoading.value = false
|
||||
submitLoading.value = false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/** 关闭对话框 */
|
||||
const handleClose = () => {
|
||||
dialogVisible.value = false
|
||||
}
|
||||
dialogVisible.value = false;
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -21,66 +21,69 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, computed, watch } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { finalAudit } from '@/api/zsjy/enrollmentProcess'
|
||||
import type { EnrollmentProcessVO } from '@/api/zsjy/enrollmentProcess'
|
||||
import { ref, reactive, computed, watch } from 'vue';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { finalAudit } from '@/api/zsjy/enrollmentProcess';
|
||||
import type { EnrollmentProcessVO } from '@/api/zsjy/enrollmentProcess';
|
||||
|
||||
interface Props {
|
||||
visible: boolean
|
||||
processData: Partial<EnrollmentProcessVO>
|
||||
visible: boolean;
|
||||
processData: Partial<EnrollmentProcessVO>;
|
||||
}
|
||||
|
||||
const props = defineProps<Props>()
|
||||
const emit = defineEmits(['update:visible', 'success'])
|
||||
const props = defineProps<Props>();
|
||||
const emit = defineEmits(['update:visible', 'success']);
|
||||
|
||||
const dialogVisible = computed({
|
||||
get: () => props.visible,
|
||||
set: (value: boolean) => emit('update:visible', value)
|
||||
})
|
||||
});
|
||||
|
||||
const formRef = ref()
|
||||
const loading = ref(false)
|
||||
const formRef = ref();
|
||||
const loading = ref(false);
|
||||
const form = reactive({
|
||||
auditResult: '',
|
||||
auditOpinion: ''
|
||||
})
|
||||
});
|
||||
|
||||
const rules = {
|
||||
auditResult: [{ required: true, message: '请选择审核结果', trigger: 'change' }]
|
||||
}
|
||||
};
|
||||
|
||||
watch(() => props.visible, (newVal) => {
|
||||
if (newVal) {
|
||||
form.auditResult = ''
|
||||
form.auditOpinion = ''
|
||||
formRef.value?.clearValidate()
|
||||
watch(
|
||||
() => props.visible,
|
||||
(newVal) => {
|
||||
if (newVal) {
|
||||
form.auditResult = '';
|
||||
form.auditOpinion = '';
|
||||
formRef.value?.clearValidate();
|
||||
}
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
const handleSubmit = async () => {
|
||||
if (!formRef.value) return
|
||||
if (!formRef.value) return;
|
||||
try {
|
||||
await formRef.value.validate()
|
||||
loading.value = true
|
||||
await formRef.value.validate();
|
||||
loading.value = true;
|
||||
await finalAudit({
|
||||
id: props.processData.id as number,
|
||||
auditResult: form.auditResult,
|
||||
auditOpinion: form.auditOpinion
|
||||
})
|
||||
ElMessage.success('审核提交成功')
|
||||
emit('success')
|
||||
handleClose()
|
||||
});
|
||||
ElMessage.success('审核提交成功');
|
||||
emit('success');
|
||||
handleClose();
|
||||
} catch (error) {
|
||||
ElMessage.error('审核提交失败')
|
||||
ElMessage.error('审核提交失败');
|
||||
} finally {
|
||||
loading.value = false
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
dialogVisible.value = false
|
||||
}
|
||||
dialogVisible.value = false;
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@@ -101,4 +104,4 @@ const handleClose = () => {
|
||||
justify-content: flex-end;
|
||||
gap: 12px;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
@@ -1,24 +1,12 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
v-model="dialogVisible"
|
||||
title="信息审核"
|
||||
width="1000px"
|
||||
class="audit-dialog"
|
||||
:before-close="handleClose"
|
||||
>
|
||||
<el-dialog v-model="dialogVisible" title="信息审核" width="1000px" class="audit-dialog" :before-close="handleClose">
|
||||
<div class="audit-container">
|
||||
<!-- 学生基本信息展示 -->
|
||||
<el-card class="student-info-card" shadow="never">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span class="section-title">学生基本信息</span>
|
||||
<el-button
|
||||
type="primary"
|
||||
plain
|
||||
size="small"
|
||||
@click="handleViewDetails"
|
||||
:loading="detailLoading"
|
||||
>
|
||||
<el-button type="primary" plain size="small" @click="handleViewDetails" :loading="detailLoading">
|
||||
<el-icon><View /></el-icon>
|
||||
查看完整信息
|
||||
</el-button>
|
||||
@@ -45,17 +33,13 @@
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span class="section-title">学生详细填报信息</span>
|
||||
<el-button
|
||||
type="text"
|
||||
size="small"
|
||||
@click="showDetails = false"
|
||||
>
|
||||
<el-button type="text" size="small" @click="showDetails = false">
|
||||
<el-icon><ArrowUp /></el-icon>
|
||||
收起
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
<div v-loading="detailLoading" class="details-content">
|
||||
<!-- 基本信息 -->
|
||||
<div class="info-section">
|
||||
@@ -89,7 +73,7 @@
|
||||
<span class="detail-value">{{ studentDetails.healthStatus || '-' }}</span>
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
|
||||
|
||||
<el-descriptions :column="1" border size="small" class="address-desc">
|
||||
<el-descriptions-item label="家庭住址">
|
||||
<span class="detail-value">{{ studentDetails.homeAddress || '-' }}</span>
|
||||
@@ -178,13 +162,7 @@
|
||||
<span class="section-title">审核操作</span>
|
||||
</div>
|
||||
</template>
|
||||
<el-form
|
||||
ref="auditFormRef"
|
||||
:model="auditForm"
|
||||
:rules="auditRules"
|
||||
label-width="100px"
|
||||
class="audit-form"
|
||||
>
|
||||
<el-form ref="auditFormRef" :model="auditForm" :rules="auditRules" label-width="100px" class="audit-form">
|
||||
<el-form-item label="审核结果" prop="auditResult">
|
||||
<el-radio-group v-model="auditForm.auditResult" class="audit-result-group">
|
||||
<el-radio value="1" class="audit-radio audit-pass">
|
||||
@@ -215,12 +193,7 @@
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="handleClose">取消</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
:loading="submitLoading"
|
||||
@click="handleSubmit"
|
||||
:disabled="!auditForm.auditResult"
|
||||
>
|
||||
<el-button type="primary" :loading="submitLoading" @click="handleSubmit" :disabled="!auditForm.auditResult">
|
||||
<el-icon><Check /></el-icon>
|
||||
提交审核
|
||||
</el-button>
|
||||
@@ -230,89 +203,87 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, computed, toRefs, watch, getCurrentInstance } from 'vue'
|
||||
import { ElMessage, type FormInstance } from 'element-plus'
|
||||
import { infoAudit } from '@/api/zsjy/enrollmentProcess'
|
||||
import { getEnrollmentInfo } from '@/api/zsjy/enrollment'
|
||||
import type { EnrollmentProcessVO } from '@/api/zsjy/enrollmentProcess'
|
||||
import type { EnrollmentInfoVO } from '@/api/zsjy/enrollment/types'
|
||||
import { parseTime } from '@/utils/ruoyi'
|
||||
import { View, ArrowUp, Check, CircleCheck, CircleClose } from '@element-plus/icons-vue'
|
||||
import { ref, reactive, computed, toRefs, watch, getCurrentInstance } from 'vue';
|
||||
import { ElMessage, type FormInstance } from 'element-plus';
|
||||
import { infoAudit } from '@/api/zsjy/enrollmentProcess';
|
||||
import { getEnrollmentInfo } from '@/api/zsjy/enrollment';
|
||||
import type { EnrollmentProcessVO } from '@/api/zsjy/enrollmentProcess';
|
||||
import type { EnrollmentInfoVO } from '@/api/zsjy/enrollment/types';
|
||||
import { parseTime } from '@/utils/ruoyi';
|
||||
import { View, ArrowUp, Check, CircleCheck, CircleClose } from '@element-plus/icons-vue';
|
||||
|
||||
interface Props {
|
||||
visible: boolean
|
||||
processData: Partial<EnrollmentProcessVO>
|
||||
visible: boolean;
|
||||
processData: Partial<EnrollmentProcessVO>;
|
||||
}
|
||||
|
||||
interface Emits {
|
||||
(e: 'update:visible', value: boolean): void
|
||||
(e: 'success'): void
|
||||
(e: 'update:visible', value: boolean): void;
|
||||
(e: 'success'): void;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
visible: false,
|
||||
processData: () => ({})
|
||||
})
|
||||
});
|
||||
|
||||
const emit = defineEmits<Emits>()
|
||||
const emit = defineEmits<Emits>();
|
||||
|
||||
const { proxy } = getCurrentInstance() as ComponentInternalInstance
|
||||
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
||||
const { zsjy_enrollment_status, zsjy_audit_status, zsjy_gender, sys_yes_no } = toRefs<any>(
|
||||
proxy?.useDict('zsjy_enrollment_status', 'zsjy_audit_status', 'zsjy_gender', 'sys_yes_no')
|
||||
)
|
||||
);
|
||||
|
||||
// 对话框可见性
|
||||
const dialogVisible = computed({
|
||||
get: () => props.visible,
|
||||
set: (value: boolean) => {
|
||||
emit('update:visible', value)
|
||||
emit('update:visible', value);
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
// 表单引用
|
||||
const auditFormRef = ref<FormInstance>()
|
||||
const submitLoading = ref(false)
|
||||
const detailLoading = ref(false)
|
||||
const showDetails = ref(false)
|
||||
const auditFormRef = ref<FormInstance>();
|
||||
const submitLoading = ref(false);
|
||||
const detailLoading = ref(false);
|
||||
const showDetails = ref(false);
|
||||
|
||||
// 学生详细信息
|
||||
const studentDetails = ref<Partial<EnrollmentInfoVO>>({})
|
||||
const studentDetails = ref<Partial<EnrollmentInfoVO>>({});
|
||||
|
||||
// 审核表单
|
||||
const auditForm = reactive({
|
||||
auditResult: '',
|
||||
auditOpinion: ''
|
||||
})
|
||||
});
|
||||
|
||||
// 表单验证规则
|
||||
const auditRules = {
|
||||
auditResult: [
|
||||
{ required: true, message: '请选择审核结果', trigger: 'change' }
|
||||
]
|
||||
}
|
||||
auditResult: [{ required: true, message: '请选择审核结果', trigger: 'change' }]
|
||||
};
|
||||
|
||||
// 时间格式化方法
|
||||
const formatTime = (time: string | Date) => {
|
||||
return parseTime(time, '{y}-{m}-{d} {h}:{i}:{s}')
|
||||
}
|
||||
return parseTime(time, '{y}-{m}-{d} {h}:{i}:{s}');
|
||||
};
|
||||
|
||||
// 查看学生详细信息
|
||||
const handleViewDetails = async () => {
|
||||
if (!props.processData.enrollmentId) {
|
||||
ElMessage.warning('缺少招生信息ID,无法获取详细信息')
|
||||
return
|
||||
ElMessage.warning('缺少招生信息ID,无法获取详细信息');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
detailLoading.value = true
|
||||
const res = await getEnrollmentInfo(props.processData.enrollmentId)
|
||||
studentDetails.value = res.data || {}
|
||||
showDetails.value = true
|
||||
detailLoading.value = true;
|
||||
const res = await getEnrollmentInfo(props.processData.enrollmentId);
|
||||
studentDetails.value = res.data || {};
|
||||
showDetails.value = true;
|
||||
} catch (error: any) {
|
||||
console.error('获取学生详细信息失败:', error)
|
||||
console.error('获取学生详细信息失败:', error);
|
||||
// 如果接口不存在,使用模拟数据
|
||||
if (error?.response?.status === 404) {
|
||||
ElMessage.warning('后端接口暂未实现,显示模拟数据')
|
||||
ElMessage.warning('后端接口暂未实现,显示模拟数据');
|
||||
// 基于流程数据创建模拟的详细信息
|
||||
studentDetails.value = {
|
||||
id: Number(props.processData.enrollmentId),
|
||||
@@ -340,64 +311,67 @@ const handleViewDetails = async () => {
|
||||
specialties: '擅长计算机编程,获得过市级编程竞赛二等奖',
|
||||
awards: '2024年杭州市中学生编程竞赛二等奖\n2023年校级三好学生',
|
||||
applicationTime: '2025-01-20 14:30:00'
|
||||
}
|
||||
showDetails.value = true
|
||||
};
|
||||
showDetails.value = true;
|
||||
} else {
|
||||
ElMessage.error('获取学生详细信息失败,请重试')
|
||||
ElMessage.error('获取学生详细信息失败,请重试');
|
||||
}
|
||||
} finally {
|
||||
detailLoading.value = false
|
||||
detailLoading.value = false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 监听对话框打开,重置表单
|
||||
watch(() => props.visible, (newVal) => {
|
||||
if (newVal) {
|
||||
resetForm()
|
||||
} else {
|
||||
// 关闭时重置详细信息显示状态
|
||||
showDetails.value = false
|
||||
studentDetails.value = {}
|
||||
watch(
|
||||
() => props.visible,
|
||||
(newVal) => {
|
||||
if (newVal) {
|
||||
resetForm();
|
||||
} else {
|
||||
// 关闭时重置详细信息显示状态
|
||||
showDetails.value = false;
|
||||
studentDetails.value = {};
|
||||
}
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
/** 重置表单 */
|
||||
const resetForm = () => {
|
||||
auditForm.auditResult = ''
|
||||
auditForm.auditOpinion = ''
|
||||
auditFormRef.value?.clearValidate()
|
||||
}
|
||||
auditForm.auditResult = '';
|
||||
auditForm.auditOpinion = '';
|
||||
auditFormRef.value?.clearValidate();
|
||||
};
|
||||
|
||||
/** 提交审核 */
|
||||
const handleSubmit = async () => {
|
||||
if (!auditFormRef.value) return
|
||||
|
||||
if (!auditFormRef.value) return;
|
||||
|
||||
try {
|
||||
await auditFormRef.value.validate()
|
||||
|
||||
submitLoading.value = true
|
||||
|
||||
await auditFormRef.value.validate();
|
||||
|
||||
submitLoading.value = true;
|
||||
|
||||
await infoAudit({
|
||||
id: props.processData.id as number,
|
||||
auditResult: auditForm.auditResult,
|
||||
auditOpinion: auditForm.auditOpinion || undefined
|
||||
})
|
||||
|
||||
ElMessage.success('审核提交成功')
|
||||
emit('success')
|
||||
handleClose()
|
||||
});
|
||||
|
||||
ElMessage.success('审核提交成功');
|
||||
emit('success');
|
||||
handleClose();
|
||||
} catch (error) {
|
||||
console.error('审核提交失败:', error)
|
||||
ElMessage.error('审核提交失败')
|
||||
console.error('审核提交失败:', error);
|
||||
ElMessage.error('审核提交失败');
|
||||
} finally {
|
||||
submitLoading.value = false
|
||||
submitLoading.value = false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/** 关闭对话框 */
|
||||
const handleClose = () => {
|
||||
dialogVisible.value = false
|
||||
}
|
||||
dialogVisible.value = false;
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@@ -770,4 +744,4 @@ const handleClose = () => {
|
||||
.details-content::-webkit-scrollbar-thumb:hover {
|
||||
background: #94a3b8;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
@@ -1,11 +1,5 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
v-model="dialogVisible"
|
||||
title="流程详情"
|
||||
width="900px"
|
||||
class="process-detail-dialog"
|
||||
:before-close="handleClose"
|
||||
>
|
||||
<el-dialog v-model="dialogVisible" title="流程详情" width="900px" class="process-detail-dialog" :before-close="handleClose">
|
||||
<div class="process-detail-container">
|
||||
<!-- 基本信息 -->
|
||||
<el-card class="detail-card" shadow="never">
|
||||
@@ -158,51 +152,50 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, toRefs } from 'vue'
|
||||
import type { EnrollmentProcessVO } from '@/api/zsjy/enrollmentProcess'
|
||||
import { ref, computed, toRefs } from 'vue';
|
||||
import type { EnrollmentProcessVO } from '@/api/zsjy/enrollmentProcess';
|
||||
|
||||
interface Props {
|
||||
visible: boolean
|
||||
processData: Partial<EnrollmentProcessVO>
|
||||
visible: boolean;
|
||||
processData: Partial<EnrollmentProcessVO>;
|
||||
}
|
||||
|
||||
interface Emits {
|
||||
(e: 'update:visible', value: boolean): void
|
||||
(e: 'update:visible', value: boolean): void;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
visible: false,
|
||||
processData: () => ({})
|
||||
})
|
||||
});
|
||||
|
||||
const emit = defineEmits<Emits>()
|
||||
const emit = defineEmits<Emits>();
|
||||
|
||||
const { proxy } = getCurrentInstance() as ComponentInternalInstance
|
||||
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
||||
const { zsjy_enrollment_status, zsjy_audit_status, zsjy_process_status, zsjy_payment_status, sys_yes_no } = toRefs<any>(
|
||||
proxy?.useDict('zsjy_enrollment_status', 'zsjy_audit_status', 'zsjy_process_status', 'zsjy_payment_status', 'sys_yes_no')
|
||||
)
|
||||
);
|
||||
|
||||
// 对话框可见性
|
||||
const dialogVisible = computed({
|
||||
get: () => props.visible,
|
||||
set: (value: boolean) => {
|
||||
emit('update:visible', value)
|
||||
emit('update:visible', value);
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
/** 关闭对话框 */
|
||||
const handleClose = () => {
|
||||
dialogVisible.value = false
|
||||
}
|
||||
dialogVisible.value = false;
|
||||
};
|
||||
|
||||
const isCompletedTag = computed(() => {
|
||||
return String(processData.value.isCompleted) === '1' ? 'success' : 'info'
|
||||
})
|
||||
return String(processData.value.isCompleted) === '1' ? 'success' : 'info';
|
||||
});
|
||||
|
||||
const isCompletedText = computed(() => {
|
||||
return String(processData.value.isCompleted) === '1' ? '已完成' : '未完成'
|
||||
})
|
||||
|
||||
return String(processData.value.isCompleted) === '1' ? '已完成' : '未完成';
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -45,27 +45,27 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, computed, watch } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { addEnrollmentProcess, updateEnrollmentProcess } from '@/api/zsjy/enrollmentProcess'
|
||||
import type { EnrollmentProcessVO } from '@/api/zsjy/enrollmentProcess'
|
||||
import { ref, reactive, computed, watch } from 'vue';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { addEnrollmentProcess, updateEnrollmentProcess } from '@/api/zsjy/enrollmentProcess';
|
||||
import type { EnrollmentProcessVO } from '@/api/zsjy/enrollmentProcess';
|
||||
|
||||
interface Props {
|
||||
visible: boolean
|
||||
processData: Partial<EnrollmentProcessVO>
|
||||
isEdit: boolean
|
||||
visible: boolean;
|
||||
processData: Partial<EnrollmentProcessVO>;
|
||||
isEdit: boolean;
|
||||
}
|
||||
|
||||
const props = defineProps<Props>()
|
||||
const emit = defineEmits(['update:visible', 'success'])
|
||||
const props = defineProps<Props>();
|
||||
const emit = defineEmits(['update:visible', 'success']);
|
||||
|
||||
const dialogVisible = computed({
|
||||
get: () => props.visible,
|
||||
set: (value: boolean) => emit('update:visible', value)
|
||||
})
|
||||
});
|
||||
|
||||
const formRef = ref()
|
||||
const loading = ref(false)
|
||||
const formRef = ref();
|
||||
const loading = ref(false);
|
||||
const form = reactive({
|
||||
id: undefined,
|
||||
studentName: '',
|
||||
@@ -73,60 +73,63 @@ const form = reactive({
|
||||
currentStatus: '0',
|
||||
currentHandlerName: '',
|
||||
remark: ''
|
||||
})
|
||||
});
|
||||
|
||||
const rules = {
|
||||
studentName: [{ required: true, message: '请输入学生姓名', trigger: 'blur' }],
|
||||
idCard: [{ required: true, message: '请输入身份证号', trigger: 'blur' }],
|
||||
currentStatus: [{ required: true, message: '请选择当前状态', trigger: 'change' }]
|
||||
}
|
||||
};
|
||||
|
||||
watch(() => props.visible, (newVal) => {
|
||||
if (newVal) {
|
||||
if (props.isEdit && props.processData) {
|
||||
Object.assign(form, props.processData)
|
||||
} else {
|
||||
resetForm()
|
||||
watch(
|
||||
() => props.visible,
|
||||
(newVal) => {
|
||||
if (newVal) {
|
||||
if (props.isEdit && props.processData) {
|
||||
Object.assign(form, props.processData);
|
||||
} else {
|
||||
resetForm();
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
const resetForm = () => {
|
||||
form.id = undefined
|
||||
form.studentName = ''
|
||||
form.idCard = ''
|
||||
form.currentStatus = '0'
|
||||
form.currentHandlerName = ''
|
||||
form.remark = ''
|
||||
formRef.value?.clearValidate()
|
||||
}
|
||||
form.id = undefined;
|
||||
form.studentName = '';
|
||||
form.idCard = '';
|
||||
form.currentStatus = '0';
|
||||
form.currentHandlerName = '';
|
||||
form.remark = '';
|
||||
formRef.value?.clearValidate();
|
||||
};
|
||||
|
||||
const handleSubmit = async () => {
|
||||
if (!formRef.value) return
|
||||
if (!formRef.value) return;
|
||||
try {
|
||||
await formRef.value.validate()
|
||||
loading.value = true
|
||||
|
||||
await formRef.value.validate();
|
||||
loading.value = true;
|
||||
|
||||
if (props.isEdit) {
|
||||
await updateEnrollmentProcess(form)
|
||||
ElMessage.success('更新成功')
|
||||
await updateEnrollmentProcess(form);
|
||||
ElMessage.success('更新成功');
|
||||
} else {
|
||||
await addEnrollmentProcess(form)
|
||||
ElMessage.success('创建成功')
|
||||
await addEnrollmentProcess(form);
|
||||
ElMessage.success('创建成功');
|
||||
}
|
||||
|
||||
emit('success')
|
||||
handleClose()
|
||||
|
||||
emit('success');
|
||||
handleClose();
|
||||
} catch (error) {
|
||||
ElMessage.error(props.isEdit ? '更新失败' : '创建失败')
|
||||
ElMessage.error(props.isEdit ? '更新失败' : '创建失败');
|
||||
} finally {
|
||||
loading.value = false
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
dialogVisible.value = false
|
||||
}
|
||||
dialogVisible.value = false;
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@@ -147,4 +150,4 @@ const handleClose = () => {
|
||||
justify-content: flex-end;
|
||||
gap: 12px;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
@@ -15,63 +15,66 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, computed, watch } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { rejectProcess } from '@/api/zsjy/enrollmentProcess'
|
||||
import type { EnrollmentProcessVO } from '@/api/zsjy/enrollmentProcess'
|
||||
import { ref, reactive, computed, watch } from 'vue';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { rejectProcess } from '@/api/zsjy/enrollmentProcess';
|
||||
import type { EnrollmentProcessVO } from '@/api/zsjy/enrollmentProcess';
|
||||
|
||||
interface Props {
|
||||
visible: boolean
|
||||
processData: Partial<EnrollmentProcessVO>
|
||||
visible: boolean;
|
||||
processData: Partial<EnrollmentProcessVO>;
|
||||
}
|
||||
|
||||
const props = defineProps<Props>()
|
||||
const emit = defineEmits(['update:visible', 'success'])
|
||||
const props = defineProps<Props>();
|
||||
const emit = defineEmits(['update:visible', 'success']);
|
||||
|
||||
const dialogVisible = computed({
|
||||
get: () => props.visible,
|
||||
set: (value: boolean) => emit('update:visible', value)
|
||||
})
|
||||
});
|
||||
|
||||
const formRef = ref()
|
||||
const loading = ref(false)
|
||||
const formRef = ref();
|
||||
const loading = ref(false);
|
||||
const form = reactive({
|
||||
rejectReason: ''
|
||||
})
|
||||
});
|
||||
|
||||
const rules = {
|
||||
rejectReason: [{ required: true, message: '请输入驳回原因', trigger: 'blur' }]
|
||||
}
|
||||
};
|
||||
|
||||
watch(() => props.visible, (newVal) => {
|
||||
if (newVal) {
|
||||
form.rejectReason = ''
|
||||
formRef.value?.clearValidate()
|
||||
watch(
|
||||
() => props.visible,
|
||||
(newVal) => {
|
||||
if (newVal) {
|
||||
form.rejectReason = '';
|
||||
formRef.value?.clearValidate();
|
||||
}
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
const handleSubmit = async () => {
|
||||
if (!formRef.value) return
|
||||
if (!formRef.value) return;
|
||||
try {
|
||||
await formRef.value.validate()
|
||||
loading.value = true
|
||||
await formRef.value.validate();
|
||||
loading.value = true;
|
||||
await rejectProcess({
|
||||
id: props.processData.id as number,
|
||||
rejectReason: form.rejectReason
|
||||
})
|
||||
ElMessage.success('流程已驳回')
|
||||
emit('success')
|
||||
handleClose()
|
||||
});
|
||||
ElMessage.success('流程已驳回');
|
||||
emit('success');
|
||||
handleClose();
|
||||
} catch (error) {
|
||||
ElMessage.error('驳回失败')
|
||||
ElMessage.error('驳回失败');
|
||||
} finally {
|
||||
loading.value = false
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
dialogVisible.value = false
|
||||
}
|
||||
dialogVisible.value = false;
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@@ -92,4 +95,4 @@ const handleClose = () => {
|
||||
justify-content: flex-end;
|
||||
gap: 12px;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
@@ -2,28 +2,16 @@
|
||||
<div class="page-container">
|
||||
<!-- 搜索区域 -->
|
||||
<el-card class="search-card">
|
||||
<el-form ref="queryFormRef" :model="queryParams" label-width="80px">
|
||||
<el-form ref="queryFormRef" :model="queryParams" label-width="80px">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="6">
|
||||
<el-form-item label="学生姓名" prop="studentName">
|
||||
<el-input
|
||||
v-model="queryParams.studentName"
|
||||
placeholder="请输入学生姓名"
|
||||
clearable
|
||||
style="width: 100%"
|
||||
@keyup.enter="handleQuery"
|
||||
/>
|
||||
<el-input v-model="queryParams.studentName" placeholder="请输入学生姓名" clearable style="width: 100%" @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="身份证号" prop="idCard">
|
||||
<el-input
|
||||
v-model="queryParams.idCard"
|
||||
placeholder="请输入身份证号"
|
||||
clearable
|
||||
style="width: 100%"
|
||||
@keyup.enter="handleQuery"
|
||||
/>
|
||||
<el-input v-model="queryParams.idCard" placeholder="请输入身份证号" clearable style="width: 100%" @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
@@ -85,23 +73,8 @@
|
||||
<div class="card-header">
|
||||
<span class="toolbar-title">录取处理管理</span>
|
||||
<div class="toolbar-actions">
|
||||
<el-button
|
||||
v-hasPermi="['zsjy:enrollmentProcess:export']"
|
||||
type="warning"
|
||||
plain
|
||||
icon="Download"
|
||||
@click="handleExport"
|
||||
>
|
||||
导出
|
||||
</el-button>
|
||||
<el-button
|
||||
type="info"
|
||||
plain
|
||||
icon="Refresh"
|
||||
@click="handleRefreshStats"
|
||||
>
|
||||
刷新统计
|
||||
</el-button>
|
||||
<el-button v-hasPermi="['zsjy:enrollmentProcess:export']" type="warning" plain icon="Download" @click="handleExport"> 导出 </el-button>
|
||||
<el-button type="info" plain icon="Refresh" @click="handleRefreshStats"> 刷新统计 </el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -123,9 +96,9 @@
|
||||
<el-table-column type="selection" width="55" align="center" />
|
||||
<el-table-column label="学生信息" align="center" min-width="180">
|
||||
<template #default="scope">
|
||||
<div style="text-align: left;">
|
||||
<div style="font-weight: 600; color: #303133;">{{ scope.row.studentName }}</div>
|
||||
<div style="font-size: 12px; color: #909399; margin-top: 2px;">{{ scope.row.idCard }}</div>
|
||||
<div style="text-align: left">
|
||||
<div style="font-weight: 600; color: #303133">{{ scope.row.studentName }}</div>
|
||||
<div style="font-size: 12px; color: #909399; margin-top: 2px">{{ scope.row.idCard }}</div>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
@@ -149,7 +122,9 @@
|
||||
</el-table-column>
|
||||
<el-table-column label="完成状态" align="center" min-width="90">
|
||||
<template #default="scope">
|
||||
<el-tag v-if="scope.row.is_completed === 1 || scope.row.is_completed === '1' || scope.row.is_completed === 'Y'" type="success">是</el-tag>
|
||||
<el-tag v-if="scope.row.is_completed === 1 || scope.row.is_completed === '1' || scope.row.is_completed === 'Y'" type="success"
|
||||
>是</el-tag
|
||||
>
|
||||
<el-tag v-else type="info">否</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
@@ -162,13 +137,7 @@
|
||||
<template #default="scope">
|
||||
<div class="action-buttons">
|
||||
<el-tooltip content="查看详情" placement="top">
|
||||
<el-button
|
||||
v-hasPermi="['zsjy:enrollmentProcess:query']"
|
||||
link
|
||||
type="primary"
|
||||
icon="View"
|
||||
@click="handleDetail(scope.row)"
|
||||
/>
|
||||
<el-button v-hasPermi="['zsjy:enrollmentProcess:query']" link type="primary" icon="View" @click="handleDetail(scope.row)" />
|
||||
</el-tooltip>
|
||||
<el-tooltip content="缴费确认" placement="top">
|
||||
<el-button
|
||||
@@ -180,8 +149,10 @@
|
||||
@click="handlePaymentConfirm(scope.row)"
|
||||
/>
|
||||
</el-tooltip>
|
||||
<el-tooltip :content="(scope.row.payment_status !== '1' && scope.row.paymentStatus !== '1') ?
|
||||
'请先完成缴费确认' : '录取处理'" placement="top">
|
||||
<el-tooltip
|
||||
:content="scope.row.payment_status !== '1' && scope.row.paymentStatus !== '1' ? '请先完成缴费确认' : '录取处理'"
|
||||
placement="top"
|
||||
>
|
||||
<el-button
|
||||
v-hasPermi="['zsjy:enrollmentProcess:process']"
|
||||
link
|
||||
@@ -197,21 +168,17 @@
|
||||
link
|
||||
type="info"
|
||||
icon="CircleCheck"
|
||||
:disabled="(scope.row.payment_status !== '1' && scope.row.paymentStatus !== '1') ||
|
||||
(scope.row.admissionStatus !== '1' && scope.row.admission_status !== '1') ||
|
||||
scope.row.is_completed === 1 ||
|
||||
scope.row.is_completed === 'Y'"
|
||||
:disabled="
|
||||
(scope.row.payment_status !== '1' && scope.row.paymentStatus !== '1') ||
|
||||
(scope.row.admissionStatus !== '1' && scope.row.admission_status !== '1') ||
|
||||
scope.row.is_completed === 1 ||
|
||||
scope.row.is_completed === 'Y'
|
||||
"
|
||||
@click="handleComplete(scope.row)"
|
||||
/>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="删除" placement="top">
|
||||
<el-button
|
||||
v-hasPermi="['zsjy:enrollmentProcess:remove']"
|
||||
link
|
||||
type="danger"
|
||||
icon="Delete"
|
||||
@click="handleDelete(scope.row)"
|
||||
/>
|
||||
<el-button v-hasPermi="['zsjy:enrollmentProcess:remove']" link type="danger" icon="Delete" @click="handleDelete(scope.row)" />
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</template>
|
||||
@@ -221,48 +188,26 @@
|
||||
|
||||
<!-- 分页 -->
|
||||
<div class="pagination-container">
|
||||
<pagination
|
||||
v-show="total > 0"
|
||||
v-model:page="queryParams.pageNum"
|
||||
v-model:limit="queryParams.pageSize"
|
||||
:total="total"
|
||||
@pagination="getList"
|
||||
/>
|
||||
<pagination v-show="total > 0" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" :total="total" @pagination="getList" />
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- 流程详情对话框 -->
|
||||
<ProcessDetailDialog
|
||||
v-model:visible="detailDialogVisible"
|
||||
:process-data="currentProcess"
|
||||
/>
|
||||
<ProcessDetailDialog v-model:visible="detailDialogVisible" :process-data="currentProcess" />
|
||||
|
||||
<!-- 录取处理对话框 -->
|
||||
<AdmissionProcessDialog
|
||||
v-model:visible="admissionProcessDialogVisible"
|
||||
:process-data="currentProcess"
|
||||
@success="handleDialogSuccess"
|
||||
/>
|
||||
<AdmissionProcessDialog v-model:visible="admissionProcessDialogVisible" :process-data="currentProcess" @success="handleDialogSuccess" />
|
||||
|
||||
<!-- 驳回流程对话框 -->
|
||||
<RejectProcessDialog
|
||||
v-model:visible="rejectDialogVisible"
|
||||
:process-data="currentProcess"
|
||||
@success="handleDialogSuccess"
|
||||
/>
|
||||
<RejectProcessDialog v-model:visible="rejectDialogVisible" :process-data="currentProcess" @success="handleDialogSuccess" />
|
||||
|
||||
<!-- 新增/编辑对话框 -->
|
||||
<ProcessFormDialog
|
||||
v-model:visible="formDialogVisible"
|
||||
:process-data="currentProcess"
|
||||
:is-edit="isEdit"
|
||||
@success="handleDialogSuccess"
|
||||
/>
|
||||
<ProcessFormDialog v-model:visible="formDialogVisible" :process-data="currentProcess" :is-edit="isEdit" @success="handleDialogSuccess" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup name="EnrollmentProcess" lang="ts">
|
||||
import { reactive, ref, toRefs, onMounted, nextTick } from 'vue'
|
||||
import { reactive, ref, toRefs, onMounted, nextTick } from 'vue';
|
||||
import {
|
||||
listEnrollmentProcess,
|
||||
getEnrollmentProcess,
|
||||
@@ -273,26 +218,26 @@ import {
|
||||
rejectProcess,
|
||||
resetProcess,
|
||||
exportEnrollmentProcess
|
||||
} from '@/api/zsjy/enrollmentProcess'
|
||||
import type { EnrollmentProcessVO, EnrollmentProcessQuery } from '@/api/zsjy/enrollmentProcess'
|
||||
import { ElMessageBox, ElMessage } from 'element-plus'
|
||||
import { Clock, Select, CircleCheck, Warning } from '@element-plus/icons-vue'
|
||||
import ProcessDetailDialog from './components/ProcessDetailDialog.vue'
|
||||
import AdmissionProcessDialog from './components/AdmissionProcessDialog.vue'
|
||||
import RejectProcessDialog from './components/RejectProcessDialog.vue'
|
||||
import ProcessFormDialog from './components/ProcessFormDialog.vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
} from '@/api/zsjy/enrollmentProcess';
|
||||
import type { EnrollmentProcessVO, EnrollmentProcessQuery } from '@/api/zsjy/enrollmentProcess';
|
||||
import { ElMessageBox, ElMessage } from 'element-plus';
|
||||
import { Clock, Select, CircleCheck, Warning } from '@element-plus/icons-vue';
|
||||
import ProcessDetailDialog from './components/ProcessDetailDialog.vue';
|
||||
import AdmissionProcessDialog from './components/AdmissionProcessDialog.vue';
|
||||
import RejectProcessDialog from './components/RejectProcessDialog.vue';
|
||||
import ProcessFormDialog from './components/ProcessFormDialog.vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
|
||||
const { proxy } = getCurrentInstance() as ComponentInternalInstance
|
||||
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
||||
const { zsjy_enrollment_status, zsjy_audit_status, zsjy_process_status, zsjy_payment_status, sys_yes_no } = toRefs<any>(
|
||||
proxy?.useDict('zsjy_enrollment_status', 'zsjy_audit_status', 'zsjy_process_status', 'zsjy_payment_status', 'sys_yes_no')
|
||||
)
|
||||
);
|
||||
|
||||
// 页面数据
|
||||
const data = reactive<{
|
||||
form: Partial<EnrollmentProcessVO>
|
||||
queryParams: EnrollmentProcessQuery
|
||||
rules: any
|
||||
form: Partial<EnrollmentProcessVO>;
|
||||
queryParams: EnrollmentProcessQuery;
|
||||
rules: any;
|
||||
}>({
|
||||
form: {},
|
||||
queryParams: {
|
||||
@@ -305,65 +250,65 @@ const data = reactive<{
|
||||
params: {}
|
||||
},
|
||||
rules: {}
|
||||
})
|
||||
});
|
||||
|
||||
const { queryParams, form, rules } = toRefs(data)
|
||||
const { queryParams, form, rules } = toRefs(data);
|
||||
|
||||
// 页面状态
|
||||
const queryFormRef = ref<ElFormInstance>()
|
||||
const processTableRef = ref<ElTableInstance>()
|
||||
const loading = ref(true)
|
||||
const ids = ref<Array<string | number>>([])
|
||||
const single = ref(true)
|
||||
const multiple = ref(true)
|
||||
const total = ref(0)
|
||||
const processList = ref<EnrollmentProcessVO[]>([])
|
||||
const queryFormRef = ref<ElFormInstance>();
|
||||
const processTableRef = ref<ElTableInstance>();
|
||||
const loading = ref(true);
|
||||
const ids = ref<Array<string | number>>([]);
|
||||
const single = ref(true);
|
||||
const multiple = ref(true);
|
||||
const total = ref(0);
|
||||
const processList = ref<EnrollmentProcessVO[]>([]);
|
||||
|
||||
/** 多选框选中数据 */
|
||||
const handleSelectionChange = (selection: EnrollmentProcessVO[]) => {
|
||||
ids.value = selection.map(item => item.id)
|
||||
single.value = selection.length !== 1
|
||||
multiple.value = selection.length === 0
|
||||
}
|
||||
ids.value = selection.map((item) => item.id);
|
||||
single.value = selection.length !== 1;
|
||||
multiple.value = selection.length === 0;
|
||||
};
|
||||
|
||||
// 统计数据
|
||||
const statistics = ref({
|
||||
total: 0,
|
||||
paymentConfirmed: 0,
|
||||
admissionProcessed: 0
|
||||
})
|
||||
});
|
||||
|
||||
// 对话框状态
|
||||
const detailDialogVisible = ref(false)
|
||||
const admissionProcessDialogVisible = ref(false)
|
||||
const rejectDialogVisible = ref(false)
|
||||
const formDialogVisible = ref(false)
|
||||
const isEdit = ref(false)
|
||||
const currentProcess = ref<Partial<EnrollmentProcessVO>>({})
|
||||
const detailDialogVisible = ref(false);
|
||||
const admissionProcessDialogVisible = ref(false);
|
||||
const rejectDialogVisible = ref(false);
|
||||
const formDialogVisible = ref(false);
|
||||
const isEdit = ref(false);
|
||||
const currentProcess = ref<Partial<EnrollmentProcessVO>>({});
|
||||
|
||||
/** 查询流程列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
loading.value = true;
|
||||
try {
|
||||
// 设置查询参数,只查询录取处理中且未完成的数据
|
||||
queryParams.value.currentStatus = '2' // 只查询录取处理中的数据
|
||||
queryParams.value.isCompleted = 0 // 只查询未完成的数据
|
||||
queryParams.value.currentStatus = '2'; // 只查询录取处理中的数据
|
||||
queryParams.value.isCompleted = 0; // 只查询未完成的数据
|
||||
|
||||
const res = await listEnrollmentProcess(queryParams.value)
|
||||
const res = await listEnrollmentProcess(queryParams.value);
|
||||
|
||||
// 对返回的数据进行处理,确保字段名一致
|
||||
const processedList = (res.rows || [])
|
||||
// 过滤掉已完成录取的数据
|
||||
.filter(item => {
|
||||
.filter((item) => {
|
||||
const isCompleted = item.isCompleted || item.is_completed || 0;
|
||||
// 只保留未完成录取的数据
|
||||
return isCompleted === 0 || isCompleted === '0';
|
||||
})
|
||||
.map(item => {
|
||||
.map((item) => {
|
||||
// 添加日志用于调试(首行数据)
|
||||
if (item === res.rows[0]) {
|
||||
console.log('首行数据字段:', Object.keys(item))
|
||||
console.log('首行数据内容:', JSON.stringify(item))
|
||||
console.log('首行数据字段:', Object.keys(item));
|
||||
console.log('首行数据内容:', JSON.stringify(item));
|
||||
}
|
||||
|
||||
// 字段名映射处理
|
||||
@@ -378,17 +323,17 @@ const getList = async () => {
|
||||
notice_number: item.notice_number || item.noticeNumber,
|
||||
admissionStatus: item.admissionStatus || item.admission_status,
|
||||
admission_status: item.admission_status || item.admissionStatus
|
||||
}
|
||||
})
|
||||
};
|
||||
});
|
||||
|
||||
processList.value = processedList
|
||||
total.value = processedList.length // 更新总数为过滤后的数量
|
||||
processList.value = processedList;
|
||||
total.value = processedList.length; // 更新总数为过滤后的数量
|
||||
} catch (error) {
|
||||
console.error('获取流程列表失败:', error)
|
||||
console.error('获取流程列表失败:', error);
|
||||
|
||||
// 如果是404错误(接口不存在),使用模拟数据
|
||||
if (error?.response?.status === 404 || error?.message?.includes('404')) {
|
||||
console.warn('后端接口不存在,使用模拟数据')
|
||||
console.warn('后端接口不存在,使用模拟数据');
|
||||
// 创建模拟的流程数据,只包含未完成录取的数据
|
||||
processList.value = [
|
||||
{
|
||||
@@ -430,19 +375,19 @@ const getList = async () => {
|
||||
isCompleted: 0, // 未完成
|
||||
createTime: '2025-07-20 10:28:45'
|
||||
}
|
||||
]
|
||||
total.value = 3
|
||||
proxy?.$modal.msgWarning('后端接口暂未实现,显示模拟数据')
|
||||
];
|
||||
total.value = 3;
|
||||
proxy?.$modal.msgWarning('后端接口暂未实现,显示模拟数据');
|
||||
} else {
|
||||
// 其他错误,设置空数据并抛出错误
|
||||
processList.value = []
|
||||
total.value = 0
|
||||
throw error
|
||||
processList.value = [];
|
||||
total.value = 0;
|
||||
throw error;
|
||||
}
|
||||
} finally {
|
||||
loading.value = false
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/** 获取统计数据 */
|
||||
const getStatistics = async () => {
|
||||
@@ -452,116 +397,116 @@ const getStatistics = async () => {
|
||||
// const stats = res.data || []
|
||||
|
||||
// 基于当前流程列表计算统计数据(只包含录取处理中的数据)
|
||||
const stats = processList.value || []
|
||||
const stats = processList.value || [];
|
||||
|
||||
// 计算已缴费人数和已处理人数
|
||||
let paymentConfirmed = 0
|
||||
let admissionProcessed = 0
|
||||
let paymentConfirmed = 0;
|
||||
let admissionProcessed = 0;
|
||||
|
||||
stats.forEach(item => {
|
||||
stats.forEach((item) => {
|
||||
// 计算已缴费人数
|
||||
if (item.paymentStatus === '1' || item.payment_status === '1') {
|
||||
paymentConfirmed++
|
||||
paymentConfirmed++;
|
||||
}
|
||||
|
||||
// 计算已处理人数(录取状态为已处理)
|
||||
if (item.admissionStatus === '1' || item.admission_status === '1') {
|
||||
admissionProcessed++
|
||||
admissionProcessed++;
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
// 更新统计数据
|
||||
statistics.value = {
|
||||
total: stats.length,
|
||||
paymentConfirmed,
|
||||
admissionProcessed
|
||||
}
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('获取统计数据失败:', error)
|
||||
console.error('获取统计数据失败:', error);
|
||||
// 设置默认值
|
||||
statistics.value = {
|
||||
total: 0,
|
||||
paymentConfirmed: 0,
|
||||
admissionProcessed: 0
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = async () => {
|
||||
queryParams.value.pageNum = 1
|
||||
queryParams.value.pageNum = 1;
|
||||
try {
|
||||
await getList()
|
||||
await getList();
|
||||
// 搜索后更新统计数据
|
||||
await getStatistics()
|
||||
await getStatistics();
|
||||
} catch (error) {
|
||||
console.error('搜索失败:', error)
|
||||
console.error('搜索失败:', error);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/** 重置按钮操作 */
|
||||
const resetQuery = () => {
|
||||
queryFormRef.value?.resetFields()
|
||||
handleQuery()
|
||||
}
|
||||
queryFormRef.value?.resetFields();
|
||||
handleQuery();
|
||||
};
|
||||
|
||||
/** 新增按钮操作 */
|
||||
const handleAdd = () => {
|
||||
currentProcess.value = {}
|
||||
isEdit.value = false
|
||||
formDialogVisible.value = true
|
||||
}
|
||||
currentProcess.value = {};
|
||||
isEdit.value = false;
|
||||
formDialogVisible.value = true;
|
||||
};
|
||||
|
||||
/** 修改按钮操作 */
|
||||
const handleUpdate = async (row?: EnrollmentProcessVO) => {
|
||||
const processId = row?.id || ids.value[0]
|
||||
const processId = row?.id || ids.value[0];
|
||||
try {
|
||||
const res = await getEnrollmentProcess(processId)
|
||||
currentProcess.value = res.data
|
||||
isEdit.value = true
|
||||
formDialogVisible.value = true
|
||||
const res = await getEnrollmentProcess(processId);
|
||||
currentProcess.value = res.data;
|
||||
isEdit.value = true;
|
||||
formDialogVisible.value = true;
|
||||
} catch (error) {
|
||||
ElMessage.error('获取流程信息失败')
|
||||
ElMessage.error('获取流程信息失败');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/** 查看详情 */
|
||||
const handleDetail = (row: EnrollmentProcessVO) => {
|
||||
currentProcess.value = row
|
||||
detailDialogVisible.value = true
|
||||
}
|
||||
currentProcess.value = row;
|
||||
detailDialogVisible.value = true;
|
||||
};
|
||||
|
||||
/** 录取处理 */
|
||||
const handleAdmissionProcess = (row: EnrollmentProcessVO) => {
|
||||
// 验证缴费状态
|
||||
if ((row.payment_status !== '1' && row.paymentStatus !== '1')) {
|
||||
if (row.payment_status !== '1' && row.paymentStatus !== '1') {
|
||||
ElMessage.warning('请先完成缴费确认,再进行录取处理');
|
||||
return;
|
||||
}
|
||||
|
||||
currentProcess.value = row;
|
||||
admissionProcessDialogVisible.value = true;
|
||||
}
|
||||
};
|
||||
|
||||
/** 缴费确认按钮点击处理 */
|
||||
const handlePaymentConfirm = async (row: EnrollmentProcessVO) => {
|
||||
try {
|
||||
await handleConfirmPayment(row)
|
||||
await handleConfirmPayment(row);
|
||||
} catch (error) {
|
||||
console.error('缴费确认失败:', error)
|
||||
console.error('缴费确认失败:', error);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/** 完成录取按钮点击处理 */
|
||||
const handleComplete = async (row: EnrollmentProcessVO) => {
|
||||
// 验证缴费状态
|
||||
if ((row.payment_status !== '1' && row.paymentStatus !== '1')) {
|
||||
if (row.payment_status !== '1' && row.paymentStatus !== '1') {
|
||||
ElMessage.warning('请先完成缴费确认,再进行完成录取操作');
|
||||
return;
|
||||
}
|
||||
|
||||
// 验证录取处理状态
|
||||
if ((row.admissionStatus !== '1' && row.admission_status !== '1')) {
|
||||
if (row.admissionStatus !== '1' && row.admission_status !== '1') {
|
||||
ElMessage.warning('请先完成录取处理,再进行完成录取操作');
|
||||
return;
|
||||
}
|
||||
@@ -577,18 +522,18 @@ const handleComplete = async (row: EnrollmentProcessVO) => {
|
||||
confirmButtonText: '确认',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
})
|
||||
});
|
||||
|
||||
await completeProcess(row.id as number)
|
||||
ElMessage.success('录取完成')
|
||||
await getList()
|
||||
await getStatistics()
|
||||
await completeProcess(row.id as number);
|
||||
ElMessage.success('录取完成');
|
||||
await getList();
|
||||
await getStatistics();
|
||||
} catch (error) {
|
||||
if (error !== 'cancel') {
|
||||
ElMessage.error('完成录取失败')
|
||||
ElMessage.error('完成录取失败');
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/** 缴费确认 */
|
||||
const handleConfirmPayment = async (row: EnrollmentProcessVO) => {
|
||||
@@ -597,18 +542,18 @@ const handleConfirmPayment = async (row: EnrollmentProcessVO) => {
|
||||
confirmButtonText: '确认',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
})
|
||||
});
|
||||
|
||||
await confirmPayment(row.id as number)
|
||||
ElMessage.success('缴费确认成功')
|
||||
await getList()
|
||||
await getStatistics()
|
||||
await confirmPayment(row.id as number);
|
||||
ElMessage.success('缴费确认成功');
|
||||
await getList();
|
||||
await getStatistics();
|
||||
} catch (error) {
|
||||
if (error !== 'cancel') {
|
||||
ElMessage.error('缴费确认失败')
|
||||
ElMessage.error('缴费确认失败');
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/** 完成录取 */
|
||||
const handleCompleteEnrollment = async (row: EnrollmentProcessVO) => {
|
||||
@@ -617,18 +562,18 @@ const handleCompleteEnrollment = async (row: EnrollmentProcessVO) => {
|
||||
confirmButtonText: '确认',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
})
|
||||
});
|
||||
|
||||
await completeProcess(row.id as number)
|
||||
ElMessage.success('录取完成')
|
||||
await getList()
|
||||
await getStatistics()
|
||||
await completeProcess(row.id as number);
|
||||
ElMessage.success('录取完成');
|
||||
await getList();
|
||||
await getStatistics();
|
||||
} catch (error) {
|
||||
if (error !== 'cancel') {
|
||||
ElMessage.error('完成录取失败')
|
||||
ElMessage.error('完成录取失败');
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/** 重置流程 */
|
||||
const handleResetProcess = async (row: EnrollmentProcessVO) => {
|
||||
@@ -637,58 +582,62 @@ const handleResetProcess = async (row: EnrollmentProcessVO) => {
|
||||
confirmButtonText: '确认重置',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
})
|
||||
});
|
||||
|
||||
await resetProcess(row.id as number)
|
||||
ElMessage.success('流程重置成功')
|
||||
await getList()
|
||||
await getStatistics()
|
||||
await resetProcess(row.id as number);
|
||||
ElMessage.success('流程重置成功');
|
||||
await getList();
|
||||
await getStatistics();
|
||||
} catch (error) {
|
||||
if (error !== 'cancel') {
|
||||
ElMessage.error('流程重置失败')
|
||||
ElMessage.error('流程重置失败');
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/** 删除按钮操作 */
|
||||
const handleDelete = async (row?: EnrollmentProcessVO) => {
|
||||
const processIds = row?.id ? [row.id] : ids.value
|
||||
const processIds = row?.id ? [row.id] : ids.value;
|
||||
try {
|
||||
await ElMessageBox.confirm('是否确认删除选中的流程数据?', '系统提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
})
|
||||
});
|
||||
|
||||
await delEnrollmentProcess(processIds)
|
||||
ElMessage.success('删除成功')
|
||||
await getList()
|
||||
await getStatistics()
|
||||
await delEnrollmentProcess(processIds);
|
||||
ElMessage.success('删除成功');
|
||||
await getList();
|
||||
await getStatistics();
|
||||
} catch (error) {
|
||||
if (error !== 'cancel') {
|
||||
ElMessage.error('删除失败')
|
||||
ElMessage.error('删除失败');
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/** 导出按钮操作 */
|
||||
const handleExport = () => {
|
||||
proxy?.download('/zsjy/enrollmentProcess/export', {
|
||||
...queryParams.value
|
||||
}, `enrollment_process_${new Date().getTime()}.xlsx`)
|
||||
}
|
||||
proxy?.download(
|
||||
'/zsjy/enrollmentProcess/export',
|
||||
{
|
||||
...queryParams.value
|
||||
},
|
||||
`enrollment_process_${new Date().getTime()}.xlsx`
|
||||
);
|
||||
};
|
||||
|
||||
/** 刷新统计数据 */
|
||||
const handleRefreshStats = async () => {
|
||||
await getStatistics()
|
||||
ElMessage.success('统计数据已刷新')
|
||||
}
|
||||
await getStatistics();
|
||||
ElMessage.success('统计数据已刷新');
|
||||
};
|
||||
|
||||
/** 对话框成功回调 */
|
||||
const handleDialogSuccess = async () => {
|
||||
await getList()
|
||||
await getStatistics()
|
||||
}
|
||||
await getList();
|
||||
await getStatistics();
|
||||
};
|
||||
|
||||
/** 获取完成录取按钮的提示内容 */
|
||||
const getCompleteButtonTooltip = (row: any): string => {
|
||||
@@ -705,29 +654,29 @@ const getCompleteButtonTooltip = (row: any): string => {
|
||||
}
|
||||
|
||||
return '完成录取';
|
||||
}
|
||||
};
|
||||
|
||||
/** 判断是否已缴费 */
|
||||
const isPaymentConfirmed = (row: any): boolean => {
|
||||
return row.payment_status === '1' || row.paymentStatus === '1';
|
||||
}
|
||||
};
|
||||
|
||||
/** 判断是否已处理录取 */
|
||||
const isAdmissionProcessed = (row: any): boolean => {
|
||||
return row.admissionStatus === '1' || row.admission_status === '1';
|
||||
}
|
||||
};
|
||||
|
||||
/** 判断是否已完成 */
|
||||
const isCompleted = (row: any): boolean => {
|
||||
return row.is_completed === 1 || row.is_completed === '1' || row.is_completed === 'Y';
|
||||
}
|
||||
};
|
||||
|
||||
/** 初始化 */
|
||||
onMounted(async () => {
|
||||
try {
|
||||
// 设置默认查询参数
|
||||
queryParams.value.currentStatus = '2' // 录取处理中
|
||||
queryParams.value.isCompleted = 0 // 未完成
|
||||
queryParams.value.currentStatus = '2'; // 录取处理中
|
||||
queryParams.value.isCompleted = 0; // 未完成
|
||||
|
||||
// 处理路由参数
|
||||
const route = useRoute();
|
||||
@@ -753,7 +702,7 @@ onMounted(async () => {
|
||||
console.error('页面初始化失败:', error);
|
||||
proxy?.$modal.msgError('页面初始化失败,请刷新页面重试');
|
||||
}
|
||||
})
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -60,13 +60,7 @@
|
||||
</template>
|
||||
|
||||
<!-- 表格区域 -->
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
border
|
||||
:data="processList"
|
||||
@selection-change="handleSelectionChange"
|
||||
@row-dblclick="handleRowClick"
|
||||
>
|
||||
<el-table v-loading="loading" border :data="processList" @selection-change="handleSelectionChange" @row-dblclick="handleRowClick">
|
||||
<el-table-column type="selection" width="55" align="center" />
|
||||
<el-table-column label="学生姓名" align="center" prop="studentName" />
|
||||
<el-table-column label="身份证号" align="center" prop="idCard" :show-overflow-tooltip="true" />
|
||||
@@ -97,13 +91,7 @@
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" align="center" width="180" class-name="small-padding fixed-width">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
type="primary"
|
||||
link
|
||||
icon="View"
|
||||
@click="handleView(scope.row)"
|
||||
v-hasPermi="['zsjy:enrollment:process:query']"
|
||||
>查看</el-button>
|
||||
<el-button type="primary" link icon="View" @click="handleView(scope.row)" v-hasPermi="['zsjy:enrollment:process:query']">查看</el-button>
|
||||
<el-button
|
||||
v-if="canAudit(scope.row)"
|
||||
type="success"
|
||||
@@ -111,7 +99,8 @@
|
||||
icon="Check"
|
||||
@click="handleApprove(scope.row)"
|
||||
v-hasPermi="['zsjy:enrollment:process:audit']"
|
||||
>通过</el-button>
|
||||
>通过</el-button
|
||||
>
|
||||
<el-button
|
||||
v-if="canAudit(scope.row)"
|
||||
type="danger"
|
||||
@@ -119,33 +108,21 @@
|
||||
icon="Close"
|
||||
@click="handleReject(scope.row)"
|
||||
v-hasPermi="['zsjy:enrollment:process:audit']"
|
||||
>驳回</el-button>
|
||||
>驳回</el-button
|
||||
>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<!-- 分页区域 -->
|
||||
<pagination
|
||||
v-if="total > 0"
|
||||
:total="total"
|
||||
v-model:page="queryParams.pageNum"
|
||||
v-model:limit="queryParams.pageSize"
|
||||
@pagination="getList"
|
||||
/>
|
||||
<pagination v-if="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
|
||||
</el-card>
|
||||
|
||||
<!-- 审核对话框 -->
|
||||
<el-dialog :title="auditDialogTitle" v-model="auditDialogVisible" width="500px" append-to-body>
|
||||
<el-form ref="auditFormRef" :model="auditForm" :rules="auditRules" label-width="100px">
|
||||
<el-form-item label="审核意见" prop="auditOpinion">
|
||||
<el-input
|
||||
v-model="auditForm.auditOpinion"
|
||||
type="textarea"
|
||||
placeholder="请输入审核意见"
|
||||
:rows="4"
|
||||
maxlength="200"
|
||||
show-word-limit
|
||||
/>
|
||||
<el-input v-model="auditForm.auditOpinion" type="textarea" placeholder="请输入审核意见" :rows="4" maxlength="200" show-word-limit />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
@@ -189,7 +166,7 @@ const total = ref(0);
|
||||
// 录取流程状态表格数据
|
||||
const processList = ref<EnrollmentProcessVO[]>([]);
|
||||
// 弹出层标题
|
||||
const auditDialogTitle = ref("");
|
||||
const auditDialogTitle = ref('');
|
||||
// 是否显示弹出层
|
||||
const auditDialogVisible = ref(false);
|
||||
// 当前操作类型
|
||||
@@ -206,7 +183,7 @@ const auditForm = reactive({
|
||||
|
||||
// 审核表单校验规则
|
||||
const auditRules = {
|
||||
auditOpinion: [{ required: true, message: "请输入审核意见", trigger: "blur" }]
|
||||
auditOpinion: [{ required: true, message: '请输入审核意见', trigger: 'blur' }]
|
||||
};
|
||||
|
||||
// 查询参数
|
||||
@@ -225,7 +202,7 @@ const auditFormRef = ref();
|
||||
/** 查询录取流程状态列表 */
|
||||
function getList() {
|
||||
loading.value = true;
|
||||
listMyAuditProcess(queryParams).then(response => {
|
||||
listMyAuditProcess(queryParams).then((response) => {
|
||||
processList.value = response.rows || [];
|
||||
total.value = response.total || 0;
|
||||
loading.value = false;
|
||||
@@ -246,7 +223,7 @@ function resetQuery() {
|
||||
|
||||
/** 多选框选中数据 */
|
||||
function handleSelectionChange(selection: EnrollmentProcessVO[]) {
|
||||
ids.value = selection.map(item => item.id);
|
||||
ids.value = selection.map((item) => item.id);
|
||||
if (selection.length === 1) {
|
||||
currentRow.value = selection[0];
|
||||
} else {
|
||||
@@ -271,19 +248,23 @@ function handleView(row?: EnrollmentProcessVO) {
|
||||
|
||||
/** 导出按钮操作 */
|
||||
function handleExport() {
|
||||
ElMessageBox.confirm('是否确认导出所有待我审核的数据项?', "警告", {
|
||||
confirmButtonText: "确定",
|
||||
cancelButtonText: "取消",
|
||||
type: "warning"
|
||||
}).then(() => {
|
||||
loading.value = true;
|
||||
exportEnrollmentProcess(queryParams).then(response => {
|
||||
download(response.msg, '待我审核列表', 'xlsx');
|
||||
loading.value = false;
|
||||
}).catch(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
}).catch(() => {});
|
||||
ElMessageBox.confirm('是否确认导出所有待我审核的数据项?', '警告', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
})
|
||||
.then(() => {
|
||||
loading.value = true;
|
||||
exportEnrollmentProcess(queryParams)
|
||||
.then((response) => {
|
||||
download(response.msg, '待我审核列表', 'xlsx');
|
||||
loading.value = false;
|
||||
})
|
||||
.catch(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
})
|
||||
.catch(() => {});
|
||||
}
|
||||
|
||||
/** 判断是否可以审核 */
|
||||
|
||||
@@ -1,11 +1,5 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
v-model="dialogVisible"
|
||||
title="学生报名详情"
|
||||
width="1000px"
|
||||
class="student-detail-dialog"
|
||||
:close-on-click-modal="false"
|
||||
>
|
||||
<el-dialog v-model="dialogVisible" title="学生报名详情" width="1000px" class="student-detail-dialog" :close-on-click-modal="false">
|
||||
<div class="detail-container">
|
||||
<!-- 基本信息 -->
|
||||
<el-card class="detail-card" shadow="never">
|
||||
@@ -22,7 +16,9 @@
|
||||
<el-descriptions-item label="联系电话">{{ studentData.phone1 || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="备用电话">{{ studentData.phone2 || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="民族">{{ studentData.nation || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="是否住宿">{{ studentData.isBoarding === 1 || studentData.isBoarding === '1' ? '是' : '否' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="是否住宿">{{
|
||||
studentData.isBoarding === 1 || studentData.isBoarding === '1' ? '是' : '否'
|
||||
}}</el-descriptions-item>
|
||||
<el-descriptions-item label="健康状况">{{ studentData.healthStatus || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="家庭住址" :span="3">{{ studentData.homeAddress || '-' }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
@@ -141,12 +137,13 @@ import type { EnrollmentProcessVO } from '@/api/zsjy/enrollmentProcess';
|
||||
import type { EnrollmentInfoVO } from '@/api/zsjy/enrollment/types';
|
||||
|
||||
// 合并类型,处理可能存在的字段
|
||||
type StudentDataType = Partial<EnrollmentProcessVO> & Partial<EnrollmentInfoVO> & {
|
||||
// 额外可能存在的字段
|
||||
studentName?: string;
|
||||
majorName?: string;
|
||||
province?: string;
|
||||
}
|
||||
type StudentDataType = Partial<EnrollmentProcessVO> &
|
||||
Partial<EnrollmentInfoVO> & {
|
||||
// 额外可能存在的字段
|
||||
studentName?: string;
|
||||
majorName?: string;
|
||||
province?: string;
|
||||
};
|
||||
|
||||
const props = defineProps({
|
||||
visible: {
|
||||
@@ -210,13 +207,20 @@ const formatTime = (time: string | Date | undefined) => {
|
||||
const getStatusType = (status: string | undefined) => {
|
||||
if (!status) return 'info';
|
||||
switch (status) {
|
||||
case '0': return 'info'; // 待审核
|
||||
case '1': return 'warning'; // 信息审核中
|
||||
case '2': return 'warning'; // 录取处理中
|
||||
case '3': return 'warning'; // 待最终确认
|
||||
case '4': return 'success'; // 录取完成
|
||||
case '9': return 'danger'; // 已驳回
|
||||
default: return 'info';
|
||||
case '0':
|
||||
return 'info'; // 待审核
|
||||
case '1':
|
||||
return 'warning'; // 信息审核中
|
||||
case '2':
|
||||
return 'warning'; // 录取处理中
|
||||
case '3':
|
||||
return 'warning'; // 待最终确认
|
||||
case '4':
|
||||
return 'success'; // 录取完成
|
||||
case '9':
|
||||
return 'danger'; // 已驳回
|
||||
default:
|
||||
return 'info';
|
||||
}
|
||||
};
|
||||
|
||||
@@ -224,13 +228,20 @@ const getStatusType = (status: string | undefined) => {
|
||||
const getStatusText = (status: string | undefined) => {
|
||||
if (!status) return '未知状态';
|
||||
switch (status) {
|
||||
case '0': return '待审核';
|
||||
case '1': return '信息审核中';
|
||||
case '2': return '录取处理中';
|
||||
case '3': return '待最终确认';
|
||||
case '4': return '录取完成';
|
||||
case '9': return '已驳回';
|
||||
default: return '未知状态';
|
||||
case '0':
|
||||
return '待审核';
|
||||
case '1':
|
||||
return '信息审核中';
|
||||
case '2':
|
||||
return '录取处理中';
|
||||
case '3':
|
||||
return '待最终确认';
|
||||
case '4':
|
||||
return '录取完成';
|
||||
case '9':
|
||||
return '已驳回';
|
||||
default:
|
||||
return '未知状态';
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="6">
|
||||
<el-form-item label="学生姓名" prop="studentName">
|
||||
<el-input v-model="queryParams.studentName" placeholder="请输入学生姓名" clearable @keyup.enter="handleQuery" />
|
||||
<el-input v-model="queryParams.studentName" placeholder="请输入学生姓名" clearable @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
@@ -20,24 +20,14 @@
|
||||
<el-col :span="6">
|
||||
<el-form-item label="所属学院" prop="collegeId">
|
||||
<el-select v-model="queryParams.params.collegeId" placeholder="请选择学院" clearable class="w-100" @change="onSearchCollegeChange">
|
||||
<el-option
|
||||
v-for="college in collegeOptions"
|
||||
:key="college.deptId"
|
||||
:label="college.deptName"
|
||||
:value="college.deptCategory"
|
||||
/>
|
||||
<el-option v-for="college in collegeOptions" :key="college.deptId" :label="college.deptName" :value="college.deptCategory" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="专业" prop="majorId">
|
||||
<el-select v-model="queryParams.params.majorId" placeholder="请选择专业" clearable class="w-100">
|
||||
<el-option
|
||||
v-for="major in searchMajorOptions"
|
||||
:key="major.value"
|
||||
:label="major.label"
|
||||
:value="major.value"
|
||||
/>
|
||||
<el-option v-for="major in searchMajorOptions" :key="major.value" :label="major.label" :value="major.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
@@ -47,14 +37,14 @@
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="6">
|
||||
<el-form-item label="性别" prop="gender">
|
||||
<el-select v-model="queryParams.params.gender" placeholder="请选择性别" clearable >
|
||||
<el-select v-model="queryParams.params.gender" placeholder="请选择性别" clearable>
|
||||
<el-option v-for="option in genderOptions" :key="option.code" :label="option.name" :value="option.code" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="当前状态" prop="currentStatus">
|
||||
<el-select v-model="queryParams.currentStatus" placeholder="请选择状态" clearable >
|
||||
<el-select v-model="queryParams.currentStatus" placeholder="请选择状态" clearable>
|
||||
<el-option v-for="option in statusOptions" :key="option.code" :label="option.name" :value="option.code" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
@@ -80,7 +70,16 @@
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="报名日期" prop="dateRange">
|
||||
<el-date-picker v-model="dateRange" type="daterange" range-separator="至" start-placeholder="开始日期" end-placeholder="结束日期" style="width: 380px" value-format="YYYY-MM-DD" @change="handleDateRangeChange" />
|
||||
<el-date-picker
|
||||
v-model="dateRange"
|
||||
type="daterange"
|
||||
range-separator="至"
|
||||
start-placeholder="开始日期"
|
||||
end-placeholder="结束日期"
|
||||
style="width: 380px"
|
||||
value-format="YYYY-MM-DD"
|
||||
@change="handleDateRangeChange"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
@@ -159,26 +158,23 @@
|
||||
</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>
|
||||
|
||||
<!-- 学生详情对话框 -->
|
||||
<StudentDetailDialog
|
||||
v-model:visible="detailDialogVisible"
|
||||
:student-data="currentStudent"
|
||||
/>
|
||||
<StudentDetailDialog v-model:visible="detailDialogVisible" :student-data="currentStudent" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, onMounted, getCurrentInstance } from 'vue';
|
||||
import { listEnrollmentProcess, exportEnrollmentProcess, EnrollmentProcessQuery, EnrollmentProcessVO, getEnrollmentProcess } from '@/api/zsjy/enrollmentProcess';
|
||||
import {
|
||||
listEnrollmentProcess,
|
||||
exportEnrollmentProcess,
|
||||
EnrollmentProcessQuery,
|
||||
EnrollmentProcessVO,
|
||||
getEnrollmentProcess
|
||||
} from '@/api/zsjy/enrollmentProcess';
|
||||
import { listDept } from '@/api/system/dept';
|
||||
import type { DeptVO } from '@/api/system/dept/types';
|
||||
import StudentDetailDialog from './components/StudentDetailDialog.vue';
|
||||
@@ -219,7 +215,7 @@ const dateRange = ref<[Date, Date]>();
|
||||
|
||||
// 学院和专业选项
|
||||
const collegeOptions = ref<DeptVO[]>([]);
|
||||
const majorOptionsByCollege = ref<{[key: string]: DeptVO[]}>({});
|
||||
const majorOptionsByCollege = ref<{ [key: string]: DeptVO[] }>({});
|
||||
const searchMajorOptions = ref<any[]>([]);
|
||||
|
||||
// 所有专业数据(包含学院信息)
|
||||
@@ -255,26 +251,24 @@ const onSearchCollegeChange = (selectedCollegeId: string) => {
|
||||
|
||||
// 首先从字典中获取
|
||||
if (zsjy_major?.value) {
|
||||
const filteredMajors = zsjy_major.value.filter(
|
||||
item => item.cssClass === selectedCollegeId
|
||||
);
|
||||
majors.push(...filteredMajors.map(m => ({ value: m.value, label: m.label })));
|
||||
const filteredMajors = zsjy_major.value.filter((item) => item.cssClass === selectedCollegeId);
|
||||
majors.push(...filteredMajors.map((m) => ({ value: m.value, label: m.label })));
|
||||
}
|
||||
|
||||
// 然后从部门数据中获取
|
||||
const college = collegeOptions.value.find(c => c.deptCategory === selectedCollegeId);
|
||||
const college = collegeOptions.value.find((c) => c.deptCategory === selectedCollegeId);
|
||||
if (college && majorOptionsByCollege.value[college.deptId]) {
|
||||
const deptMajors = majorOptionsByCollege.value[college.deptId];
|
||||
majors.push(...deptMajors.map(m => ({
|
||||
value: m.deptName,
|
||||
label: m.deptName
|
||||
})));
|
||||
majors.push(
|
||||
...deptMajors.map((m) => ({
|
||||
value: m.deptName,
|
||||
label: m.deptName
|
||||
}))
|
||||
);
|
||||
}
|
||||
|
||||
// 去除可能的重复项
|
||||
const uniqueMajors = majors.filter((item, index, self) =>
|
||||
index === self.findIndex(m => m.value === item.value)
|
||||
);
|
||||
const uniqueMajors = majors.filter((item, index, self) => index === self.findIndex((m) => m.value === item.value));
|
||||
|
||||
// 更新专业选项
|
||||
searchMajorOptions.value = uniqueMajors;
|
||||
@@ -289,7 +283,7 @@ const getCollegeAndMajorData = async () => {
|
||||
// 强制刷新字典数据
|
||||
console.log('加载学院字典数据...');
|
||||
try {
|
||||
const dictResponse = await proxy?.getDicts("zsjy_college");
|
||||
const dictResponse = await proxy?.getDicts('zsjy_college');
|
||||
console.log('学院字典加载结果:', dictResponse);
|
||||
} catch (dictError) {
|
||||
console.error('加载字典数据出错:', dictError);
|
||||
@@ -306,11 +300,14 @@ const getCollegeAndMajorData = async () => {
|
||||
});
|
||||
|
||||
collegeOptions.value = colleges;
|
||||
console.log('获取到的学院数据:', colleges.map(c => ({
|
||||
deptId: c.deptId,
|
||||
deptName: c.deptName,
|
||||
deptCategory: c.deptCategory
|
||||
})));
|
||||
console.log(
|
||||
'获取到的学院数据:',
|
||||
colleges.map((c) => ({
|
||||
deptId: c.deptId,
|
||||
deptName: c.deptName,
|
||||
deptCategory: c.deptCategory
|
||||
}))
|
||||
);
|
||||
|
||||
// 如果没有找到学院数据,尝试其他可能的过滤条件
|
||||
if (colleges.length === 0) {
|
||||
@@ -323,11 +320,14 @@ const getCollegeAndMajorData = async () => {
|
||||
|
||||
if (possibleColleges.length > 0) {
|
||||
collegeOptions.value = possibleColleges;
|
||||
console.log('找到可能的学院:', possibleColleges.map(c => ({
|
||||
deptId: c.deptId,
|
||||
deptName: c.deptName,
|
||||
deptCategory: c.deptCategory
|
||||
})));
|
||||
console.log(
|
||||
'找到可能的学院:',
|
||||
possibleColleges.map((c) => ({
|
||||
deptId: c.deptId,
|
||||
deptName: c.deptName,
|
||||
deptCategory: c.deptCategory
|
||||
}))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -341,9 +341,10 @@ const getCollegeAndMajorData = async () => {
|
||||
// 按照上下级关系组织专业
|
||||
colleges.forEach((college: DeptVO) => {
|
||||
// 找出该学院下的所有专业
|
||||
const majors = allMajors.filter((dept: DeptVO) =>
|
||||
// 使用上下级关系或其他关联关系确定专业归属哪个学院
|
||||
dept.parentId === college.deptId
|
||||
const majors = allMajors.filter(
|
||||
(dept: DeptVO) =>
|
||||
// 使用上下级关系或其他关联关系确定专业归属哪个学院
|
||||
dept.parentId === college.deptId
|
||||
);
|
||||
|
||||
majorOptionsByCollege.value[college.deptId] = majors;
|
||||
@@ -572,26 +573,40 @@ const formatIdCard = (row: any) => {
|
||||
// 获取状态类型
|
||||
const getStatusType = (status: string) => {
|
||||
switch (status) {
|
||||
case '0': return 'info'; // 待审核
|
||||
case '1': return 'warning'; // 信息审核中
|
||||
case '2': return 'warning'; // 录取处理中
|
||||
case '3': return 'warning'; // 待最终确认
|
||||
case '4': return 'success'; // 录取完成
|
||||
case '9': return 'danger'; // 已驳回
|
||||
default: return 'info';
|
||||
case '0':
|
||||
return 'info'; // 待审核
|
||||
case '1':
|
||||
return 'warning'; // 信息审核中
|
||||
case '2':
|
||||
return 'warning'; // 录取处理中
|
||||
case '3':
|
||||
return 'warning'; // 待最终确认
|
||||
case '4':
|
||||
return 'success'; // 录取完成
|
||||
case '9':
|
||||
return 'danger'; // 已驳回
|
||||
default:
|
||||
return 'info';
|
||||
}
|
||||
};
|
||||
|
||||
// 获取状态文本
|
||||
const getStatusText = (status: string) => {
|
||||
switch (status) {
|
||||
case '0': return '待审核';
|
||||
case '1': return '信息审核中';
|
||||
case '2': return '录取处理中';
|
||||
case '3': return '待最终确认';
|
||||
case '4': return '录取完成';
|
||||
case '9': return '已驳回';
|
||||
default: return '未知状态';
|
||||
case '0':
|
||||
return '待审核';
|
||||
case '1':
|
||||
return '信息审核中';
|
||||
case '2':
|
||||
return '录取处理中';
|
||||
case '3':
|
||||
return '待最终确认';
|
||||
case '4':
|
||||
return '录取完成';
|
||||
case '9':
|
||||
return '已驳回';
|
||||
default:
|
||||
return '未知状态';
|
||||
}
|
||||
};
|
||||
|
||||
@@ -611,7 +626,7 @@ const getCollegeName = (row: any): string => {
|
||||
let collegeId = row.collegeId || row.college_id;
|
||||
|
||||
if (collegeId) {
|
||||
const collegeMap: {[key: string]: string} = {
|
||||
const collegeMap: { [key: string]: string } = {
|
||||
'CS': '计算机科学与技术学院',
|
||||
'EE': '电子信息工程学院',
|
||||
'ME': '机械工程学院',
|
||||
@@ -634,14 +649,14 @@ const getCollegeNameFromCode = (code: string): string => {
|
||||
|
||||
// 从字典获取
|
||||
if (zsjy_college?.value) {
|
||||
const collegeDict = zsjy_college.value.find(dict => dict.value === code);
|
||||
const collegeDict = zsjy_college.value.find((dict) => dict.value === code);
|
||||
if (collegeDict) {
|
||||
return collegeDict.label;
|
||||
}
|
||||
}
|
||||
|
||||
// 从硬编码映射获取
|
||||
const collegeMap: {[key: string]: string} = {
|
||||
const collegeMap: { [key: string]: string } = {
|
||||
'CS': '计算机科学与技术学院',
|
||||
'EE': '电子信息工程学院',
|
||||
'ME': '机械工程学院',
|
||||
|
||||
@@ -203,12 +203,7 @@
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="24">
|
||||
<el-form-item label="主营业务" prop="mainBusiness">
|
||||
<el-input
|
||||
v-model="form.mainBusiness"
|
||||
type="textarea"
|
||||
placeholder="请输入主营业务"
|
||||
:autosize="{ minRows: 3, maxRows: 5 }"
|
||||
/>
|
||||
<el-input v-model="form.mainBusiness" type="textarea" placeholder="请输入主营业务" :autosize="{ minRows: 3, maxRows: 5 }" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
@@ -216,12 +211,7 @@
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="24">
|
||||
<el-form-item label="备注" prop="remark">
|
||||
<el-input
|
||||
v-model="form.remark"
|
||||
type="textarea"
|
||||
placeholder="请输入备注信息"
|
||||
:autosize="{ minRows: 2, maxRows: 4 }"
|
||||
/>
|
||||
<el-input v-model="form.remark" type="textarea" placeholder="请输入备注信息" :autosize="{ minRows: 2, maxRows: 4 }" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
@@ -265,27 +255,27 @@
|
||||
</template>
|
||||
|
||||
<script setup name="Enterprise" lang="ts">
|
||||
import { listEnterprise, getEnterprise, delEnterprise, addEnterprise, updateEnterprise } from '@/api/zsjy/enterprise'
|
||||
import { EnterpriseVO, EnterpriseForm, EnterpriseQuery } from '@/api/zsjy/types'
|
||||
import { listEnterprise, getEnterprise, delEnterprise, addEnterprise, updateEnterprise } from '@/api/zsjy/enterprise';
|
||||
import { EnterpriseVO, EnterpriseForm, EnterpriseQuery } from '@/api/zsjy/types';
|
||||
|
||||
const { proxy } = getCurrentInstance() as ComponentInternalInstance
|
||||
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
||||
|
||||
const enterpriseList = ref<EnterpriseVO[]>([])
|
||||
const buttonLoading = ref(false)
|
||||
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 enterpriseList = ref<EnterpriseVO[]>([]);
|
||||
const buttonLoading = ref(false);
|
||||
const loading = ref(true);
|
||||
const showSearch = ref(true);
|
||||
const ids = ref<Array<string | number>>([]);
|
||||
const single = ref(true);
|
||||
const multiple = ref(true);
|
||||
const total = ref(0);
|
||||
|
||||
const queryFormRef = ref<ElFormInstance>()
|
||||
const enterpriseFormRef = ref<ElFormInstance>()
|
||||
const queryFormRef = ref<ElFormInstance>();
|
||||
const enterpriseFormRef = ref<ElFormInstance>();
|
||||
|
||||
// 字典数据
|
||||
const { sys_normal_disable } = toRefs<any>(proxy?.useDict('sys_normal_disable'))
|
||||
const { zsjy_enterprise_industry } = toRefs<any>(proxy?.useDict('zsjy_enterprise_industry'))
|
||||
const { zsjy_enterprise_scale } = toRefs<any>(proxy?.useDict('zsjy_enterprise_scale'))
|
||||
const { sys_normal_disable } = toRefs<any>(proxy?.useDict('sys_normal_disable'));
|
||||
const { zsjy_enterprise_industry } = toRefs<any>(proxy?.useDict('zsjy_enterprise_industry'));
|
||||
const { zsjy_enterprise_scale } = toRefs<any>(proxy?.useDict('zsjy_enterprise_scale'));
|
||||
|
||||
const initFormData: EnterpriseForm = {
|
||||
id: undefined,
|
||||
@@ -299,7 +289,7 @@ const initFormData: EnterpriseForm = {
|
||||
address: '',
|
||||
status: '0',
|
||||
remark: ''
|
||||
}
|
||||
};
|
||||
|
||||
const data = reactive<PageData<EnterpriseForm, EnterpriseQuery>>({
|
||||
form: { ...initFormData },
|
||||
@@ -328,112 +318,112 @@ const data = reactive<PageData<EnterpriseForm, EnterpriseQuery>>({
|
||||
address: [{ required: true, message: '企业地址不能为空', trigger: 'blur' }],
|
||||
status: [{ required: true, message: '状态不能为空', trigger: 'change' }]
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
const { queryParams, form, rules } = toRefs(data)
|
||||
const { queryParams, form, rules } = toRefs(data);
|
||||
|
||||
// 对话框相关
|
||||
const dialog = reactive<DialogOption>({
|
||||
visible: false,
|
||||
title: ''
|
||||
})
|
||||
});
|
||||
|
||||
const viewDialog = reactive<DialogOption>({
|
||||
visible: false,
|
||||
title: ''
|
||||
})
|
||||
});
|
||||
|
||||
const viewForm = ref<EnterpriseVO>({} as EnterpriseVO)
|
||||
const viewForm = ref<EnterpriseVO>({} as EnterpriseVO);
|
||||
|
||||
/** 查询企业信息列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
const res = await listEnterprise(queryParams.value)
|
||||
enterpriseList.value = res.rows
|
||||
total.value = res.total
|
||||
loading.value = false
|
||||
}
|
||||
loading.value = true;
|
||||
const res = await listEnterprise(queryParams.value);
|
||||
enterpriseList.value = res.rows;
|
||||
total.value = res.total;
|
||||
loading.value = false;
|
||||
};
|
||||
|
||||
/** 取消按钮 */
|
||||
const cancel = () => {
|
||||
reset()
|
||||
dialog.visible = false
|
||||
}
|
||||
reset();
|
||||
dialog.visible = false;
|
||||
};
|
||||
|
||||
/** 表单重置 */
|
||||
const reset = () => {
|
||||
form.value = { ...initFormData }
|
||||
enterpriseFormRef.value?.resetFields()
|
||||
}
|
||||
form.value = { ...initFormData };
|
||||
enterpriseFormRef.value?.resetFields();
|
||||
};
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
queryParams.value.pageNum = 1
|
||||
getList()
|
||||
}
|
||||
queryParams.value.pageNum = 1;
|
||||
getList();
|
||||
};
|
||||
|
||||
/** 重置按钮操作 */
|
||||
const resetQuery = () => {
|
||||
queryFormRef.value?.resetFields()
|
||||
handleQuery()
|
||||
}
|
||||
queryFormRef.value?.resetFields();
|
||||
handleQuery();
|
||||
};
|
||||
|
||||
/** 多选框选中数据 */
|
||||
const handleSelectionChange = (selection: EnterpriseVO[]) => {
|
||||
ids.value = selection.map(item => item.id)
|
||||
single.value = selection.length !== 1
|
||||
multiple.value = !selection.length
|
||||
}
|
||||
ids.value = selection.map((item) => item.id);
|
||||
single.value = selection.length !== 1;
|
||||
multiple.value = !selection.length;
|
||||
};
|
||||
|
||||
/** 新增按钮操作 */
|
||||
const handleAdd = () => {
|
||||
reset()
|
||||
dialog.visible = true
|
||||
dialog.title = '添加企业信息'
|
||||
}
|
||||
reset();
|
||||
dialog.visible = true;
|
||||
dialog.title = '添加企业信息';
|
||||
};
|
||||
|
||||
/** 修改按钮操作 */
|
||||
const handleUpdate = async (row?: EnterpriseVO) => {
|
||||
reset()
|
||||
const id = row?.id || ids.value[0]
|
||||
const res = await getEnterprise(id)
|
||||
Object.assign(form.value, res.data)
|
||||
dialog.visible = true
|
||||
dialog.title = '修改企业信息'
|
||||
}
|
||||
reset();
|
||||
const id = row?.id || ids.value[0];
|
||||
const res = await getEnterprise(id);
|
||||
Object.assign(form.value, res.data);
|
||||
dialog.visible = true;
|
||||
dialog.title = '修改企业信息';
|
||||
};
|
||||
|
||||
/** 查看详情 */
|
||||
const handleView = async (row: EnterpriseVO) => {
|
||||
const res = await getEnterprise(row.id)
|
||||
viewForm.value = res.data
|
||||
viewDialog.visible = true
|
||||
}
|
||||
const res = await getEnterprise(row.id);
|
||||
viewForm.value = res.data;
|
||||
viewDialog.visible = true;
|
||||
};
|
||||
|
||||
/** 提交按钮 */
|
||||
const submitForm = () => {
|
||||
enterpriseFormRef.value?.validate(async (valid: boolean) => {
|
||||
if (valid) {
|
||||
buttonLoading.value = true
|
||||
buttonLoading.value = true;
|
||||
if (form.value.id) {
|
||||
await updateEnterprise(form.value).finally(() => (buttonLoading.value = false))
|
||||
await updateEnterprise(form.value).finally(() => (buttonLoading.value = false));
|
||||
} else {
|
||||
await addEnterprise(form.value).finally(() => (buttonLoading.value = false))
|
||||
await addEnterprise(form.value).finally(() => (buttonLoading.value = false));
|
||||
}
|
||||
proxy?.$modal.msgSuccess('操作成功')
|
||||
dialog.visible = false
|
||||
await getList()
|
||||
proxy?.$modal.msgSuccess('操作成功');
|
||||
dialog.visible = false;
|
||||
await getList();
|
||||
}
|
||||
})
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/** 删除按钮操作 */
|
||||
const handleDelete = async (row?: EnterpriseVO) => {
|
||||
const idList = row?.id || ids.value
|
||||
await proxy?.$modal.confirm('是否确认删除企业信息编号为"' + idList + '"的数据项?')
|
||||
await delEnterprise(idList)
|
||||
proxy?.$modal.msgSuccess('删除成功')
|
||||
await getList()
|
||||
}
|
||||
const idList = row?.id || ids.value;
|
||||
await proxy?.$modal.confirm('是否确认删除企业信息编号为"' + idList + '"的数据项?');
|
||||
await delEnterprise(idList);
|
||||
proxy?.$modal.msgSuccess('删除成功');
|
||||
await getList();
|
||||
};
|
||||
|
||||
/** 导出按钮操作 */
|
||||
const handleExport = () => {
|
||||
@@ -443,12 +433,12 @@ const handleExport = () => {
|
||||
...queryParams.value
|
||||
},
|
||||
`enterprise_${new Date().getTime()}.xlsx`
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
getList()
|
||||
})
|
||||
getList();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
@@ -13,13 +13,7 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<el-form
|
||||
ref="formRef"
|
||||
:model="form"
|
||||
:rules="rules"
|
||||
label-width="120px"
|
||||
class="graduate-form"
|
||||
>
|
||||
<el-form ref="formRef" :model="form" :rules="rules" label-width="120px" class="graduate-form">
|
||||
<!-- 基本信息 -->
|
||||
<el-card class="form-section" shadow="never">
|
||||
<template #header>
|
||||
@@ -70,13 +64,7 @@
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="8">
|
||||
<el-form-item label="出生日期" prop="birthDate">
|
||||
<el-date-picker
|
||||
v-model="form.birthDate"
|
||||
type="date"
|
||||
placeholder="请选择出生日期"
|
||||
value-format="YYYY-MM-DD"
|
||||
style="width: 100%"
|
||||
/>
|
||||
<el-date-picker v-model="form.birthDate" type="date" placeholder="请选择出生日期" value-format="YYYY-MM-DD" style="width: 100%" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
@@ -148,24 +136,12 @@
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="8">
|
||||
<el-form-item label="入学年份" prop="enrollmentYear">
|
||||
<el-date-picker
|
||||
v-model="form.enrollmentYear"
|
||||
type="year"
|
||||
placeholder="请选择入学年份"
|
||||
value-format="YYYY"
|
||||
style="width: 100%"
|
||||
/>
|
||||
<el-date-picker v-model="form.enrollmentYear" type="year" placeholder="请选择入学年份" value-format="YYYY" style="width: 100%" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="毕业年份" prop="graduationYear">
|
||||
<el-date-picker
|
||||
v-model="form.graduationYear"
|
||||
type="year"
|
||||
placeholder="请选择毕业年份"
|
||||
value-format="YYYY"
|
||||
style="width: 100%"
|
||||
/>
|
||||
<el-date-picker v-model="form.graduationYear" type="year" placeholder="请选择毕业年份" value-format="YYYY" style="width: 100%" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
@@ -293,14 +269,7 @@
|
||||
<el-row>
|
||||
<el-col :span="24">
|
||||
<el-form-item label="特长技能" prop="skills">
|
||||
<el-input
|
||||
v-model="form.skills"
|
||||
type="textarea"
|
||||
:rows="3"
|
||||
placeholder="请输入特长技能"
|
||||
maxlength="1000"
|
||||
show-word-limit
|
||||
/>
|
||||
<el-input v-model="form.skills" type="textarea" :rows="3" placeholder="请输入特长技能" maxlength="1000" show-word-limit />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
@@ -308,14 +277,7 @@
|
||||
<el-row>
|
||||
<el-col :span="24">
|
||||
<el-form-item label="获奖情况" prop="awards">
|
||||
<el-input
|
||||
v-model="form.awards"
|
||||
type="textarea"
|
||||
:rows="3"
|
||||
placeholder="请输入获奖情况"
|
||||
maxlength="1000"
|
||||
show-word-limit
|
||||
/>
|
||||
<el-input v-model="form.awards" type="textarea" :rows="3" placeholder="请输入获奖情况" maxlength="1000" show-word-limit />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
@@ -323,14 +285,7 @@
|
||||
<el-row>
|
||||
<el-col :span="24">
|
||||
<el-form-item label="工作意向" prop="jobIntention">
|
||||
<el-input
|
||||
v-model="form.jobIntention"
|
||||
type="textarea"
|
||||
:rows="3"
|
||||
placeholder="请输入工作意向"
|
||||
maxlength="1000"
|
||||
show-word-limit
|
||||
/>
|
||||
<el-input v-model="form.jobIntention" type="textarea" :rows="3" placeholder="请输入工作意向" maxlength="1000" show-word-limit />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
@@ -338,14 +293,7 @@
|
||||
<el-row>
|
||||
<el-col :span="24">
|
||||
<el-form-item label="备注信息" prop="remark">
|
||||
<el-input
|
||||
v-model="form.remark"
|
||||
type="textarea"
|
||||
:rows="3"
|
||||
placeholder="请输入备注信息"
|
||||
maxlength="500"
|
||||
show-word-limit
|
||||
/>
|
||||
<el-input v-model="form.remark" type="textarea" :rows="3" placeholder="请输入备注信息" maxlength="500" show-word-limit />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
@@ -358,13 +306,7 @@
|
||||
<script setup name="GraduateInfoForm" lang="ts">
|
||||
import { ref, reactive, computed, onMounted, getCurrentInstance } from 'vue';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import {
|
||||
getGraduateInfo,
|
||||
addGraduateInfo,
|
||||
updateGraduateInfo,
|
||||
checkStudentNoUnique,
|
||||
checkIdCardUnique
|
||||
} from '@/api/zsjy/graduateInfo';
|
||||
import { getGraduateInfo, addGraduateInfo, updateGraduateInfo, checkStudentNoUnique, checkIdCardUnique } from '@/api/zsjy/graduateInfo';
|
||||
import { GraduateInfoForm } from '@/api/zsjy/types';
|
||||
import { getDicts } from '@/api/system/dict/data';
|
||||
import { listDept } from '@/api/system/dept';
|
||||
@@ -383,18 +325,18 @@ const zsjy_graduate_salary_range = ref([]);
|
||||
|
||||
// 学院和专业数据(参考招生信息管理页面)
|
||||
const collegeOptions = ref<DeptVO[]>([]);
|
||||
const majorOptionsByCollege = ref<{[key: string]: DeptVO[]}>({});
|
||||
const majorOptions = ref<Array<{ value: string, label: string, majorCode: string }>>([]);
|
||||
const majorOptionsByCollege = ref<{ [key: string]: DeptVO[] }>({});
|
||||
const majorOptions = ref<Array<{ value: string; label: string; majorCode: string }>>([]);
|
||||
|
||||
// 加载字典数据的函数
|
||||
const loadDictData = async () => {
|
||||
try {
|
||||
console.log('开始加载字典数据...');
|
||||
|
||||
|
||||
// 性别字典
|
||||
const sexResp = await getDicts('sys_user_sex');
|
||||
if (sexResp?.data) {
|
||||
sys_user_sex.value = sexResp.data.map(item => ({
|
||||
sys_user_sex.value = sexResp.data.map((item) => ({
|
||||
label: item.dictLabel,
|
||||
value: item.dictValue
|
||||
}));
|
||||
@@ -404,7 +346,7 @@ const loadDictData = async () => {
|
||||
// 学历层次
|
||||
const eduResp = await getDicts('zsjy_education_level');
|
||||
if (eduResp?.data) {
|
||||
zsjy_education_level.value = eduResp.data.map(item => ({
|
||||
zsjy_education_level.value = eduResp.data.map((item) => ({
|
||||
label: item.dictLabel,
|
||||
value: item.dictValue
|
||||
}));
|
||||
@@ -414,7 +356,7 @@ const loadDictData = async () => {
|
||||
// 就业状态
|
||||
const empResp = await getDicts('zsjy_graduate_employment_status');
|
||||
if (empResp?.data) {
|
||||
zsjy_graduate_employment_status.value = empResp.data.map(item => ({
|
||||
zsjy_graduate_employment_status.value = empResp.data.map((item) => ({
|
||||
label: item.dictLabel,
|
||||
value: item.dictValue
|
||||
}));
|
||||
@@ -424,7 +366,7 @@ const loadDictData = async () => {
|
||||
// 单位性质
|
||||
const compResp = await getDicts('zsjy_graduate_company_type');
|
||||
if (compResp?.data) {
|
||||
zsjy_graduate_company_type.value = compResp.data.map(item => ({
|
||||
zsjy_graduate_company_type.value = compResp.data.map((item) => ({
|
||||
label: item.dictLabel,
|
||||
value: item.dictValue
|
||||
}));
|
||||
@@ -434,7 +376,7 @@ const loadDictData = async () => {
|
||||
// 月薪范围
|
||||
const salaryResp = await getDicts('zsjy_graduate_salary_range');
|
||||
if (salaryResp?.data) {
|
||||
zsjy_graduate_salary_range.value = salaryResp.data.map(item => ({
|
||||
zsjy_graduate_salary_range.value = salaryResp.data.map((item) => ({
|
||||
label: item.dictLabel,
|
||||
value: item.dictValue
|
||||
}));
|
||||
@@ -462,11 +404,14 @@ const getCollegeAndMajorData = async () => {
|
||||
});
|
||||
|
||||
collegeOptions.value = colleges;
|
||||
console.log('获取到的学院数据:', colleges.map(c => ({
|
||||
deptId: c.deptId,
|
||||
deptName: c.deptName,
|
||||
deptCategory: c.deptCategory
|
||||
})));
|
||||
console.log(
|
||||
'获取到的学院数据:',
|
||||
colleges.map((c) => ({
|
||||
deptId: c.deptId,
|
||||
deptName: c.deptName,
|
||||
deptCategory: c.deptCategory
|
||||
}))
|
||||
);
|
||||
|
||||
// 如果没有找到学院数据,尝试其他可能的过滤条件
|
||||
if (colleges.length === 0) {
|
||||
@@ -477,11 +422,14 @@ const getCollegeAndMajorData = async () => {
|
||||
|
||||
if (possibleColleges.length > 0) {
|
||||
collegeOptions.value = possibleColleges;
|
||||
console.log('找到可能的学院:', possibleColleges.map(c => ({
|
||||
deptId: c.deptId,
|
||||
deptName: c.deptName,
|
||||
deptCategory: c.deptCategory
|
||||
})));
|
||||
console.log(
|
||||
'找到可能的学院:',
|
||||
possibleColleges.map((c) => ({
|
||||
deptId: c.deptId,
|
||||
deptName: c.deptName,
|
||||
deptCategory: c.deptCategory
|
||||
}))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -495,9 +443,7 @@ const getCollegeAndMajorData = async () => {
|
||||
// 按照上下级关系组织专业
|
||||
colleges.forEach((college: DeptVO) => {
|
||||
// 找出该学院下的所有专业
|
||||
const majors = allMajors.filter((dept: DeptVO) =>
|
||||
dept.parentId === college.deptId
|
||||
);
|
||||
const majors = allMajors.filter((dept: DeptVO) => dept.parentId === college.deptId);
|
||||
|
||||
majorOptionsByCollege.value[college.deptId] = majors;
|
||||
if (majors.length > 0) {
|
||||
@@ -533,24 +479,24 @@ const submitLoading = ref(false);
|
||||
// 判断是否为编辑模式
|
||||
const isEdit = computed(() => !!route.params.id);
|
||||
|
||||
// 表单数据
|
||||
const form = reactive<GraduateInfoForm>({
|
||||
id: undefined,
|
||||
studentNo: '',
|
||||
name: '',
|
||||
gender: '',
|
||||
idCard: '',
|
||||
nation: '',
|
||||
politicalStatus: '',
|
||||
birthDate: '',
|
||||
enrollmentYear: undefined,
|
||||
graduationYear: undefined,
|
||||
collegeId: undefined,
|
||||
collegeName: '',
|
||||
majorId: undefined,
|
||||
majorName: '',
|
||||
classId: 1, // 临时设置默认班级ID,避免验证失败
|
||||
className: '',
|
||||
// 表单数据
|
||||
const form = reactive<GraduateInfoForm>({
|
||||
id: undefined,
|
||||
studentNo: '',
|
||||
name: '',
|
||||
gender: '',
|
||||
idCard: '',
|
||||
nation: '',
|
||||
politicalStatus: '',
|
||||
birthDate: '',
|
||||
enrollmentYear: undefined,
|
||||
graduationYear: undefined,
|
||||
collegeId: undefined,
|
||||
collegeName: '',
|
||||
majorId: undefined,
|
||||
majorName: '',
|
||||
classId: 1, // 临时设置默认班级ID,避免验证失败
|
||||
className: '',
|
||||
educationLevel: '',
|
||||
trainingMode: '',
|
||||
phoneNumber: '',
|
||||
@@ -591,9 +537,9 @@ const showFurtherStudyFields = computed(() => {
|
||||
const handleMajorChange = (value) => {
|
||||
console.log('专业选择变化:', value);
|
||||
form.majorId = value;
|
||||
|
||||
|
||||
// 查找对应的专业名称
|
||||
const selectedMajor = majorOptions.value.find(item => parseInt(item.value) === value);
|
||||
const selectedMajor = majorOptions.value.find((item) => parseInt(item.value) === value);
|
||||
if (selectedMajor) {
|
||||
form.majorName = selectedMajor.label;
|
||||
console.log('设置专业名称:', selectedMajor.label);
|
||||
@@ -615,10 +561,10 @@ const handleClassNameChange = () => {
|
||||
/** 处理学院选择变化(参考招生信息管理页面实现) */
|
||||
const handleCollegeChange = (selectedCollegeId: string) => {
|
||||
console.log('学院选择变化:', selectedCollegeId);
|
||||
|
||||
|
||||
if (selectedCollegeId) {
|
||||
// 根据选择的学院ID找到学院对象
|
||||
const college = collegeOptions.value.find(item => item.deptId == selectedCollegeId);
|
||||
const college = collegeOptions.value.find((item) => item.deptId == selectedCollegeId);
|
||||
if (college) {
|
||||
// 正确设置:
|
||||
// - collegeId: 使用部门ID作为学院ID
|
||||
@@ -628,7 +574,7 @@ const handleCollegeChange = (selectedCollegeId: string) => {
|
||||
|
||||
// 更新专业选项
|
||||
const majors = majorOptionsByCollege.value[selectedCollegeId] || [];
|
||||
majorOptions.value = majors.map(major => ({
|
||||
majorOptions.value = majors.map((major) => ({
|
||||
value: major.deptId.toString(),
|
||||
label: major.deptName,
|
||||
majorCode: major.deptCategory || ''
|
||||
@@ -671,14 +617,14 @@ const getGraduateDetail = async () => {
|
||||
// 设置学院和专业
|
||||
if (data.collegeId) {
|
||||
form.collegeId = data.collegeId;
|
||||
form.collegeName = collegeOptions.value.find(item => item.deptId === data.collegeId)?.deptName || '';
|
||||
form.collegeName = collegeOptions.value.find((item) => item.deptId === data.collegeId)?.deptName || '';
|
||||
console.log('设置学院名称:', form.collegeName);
|
||||
|
||||
// 根据选中的学院加载专业
|
||||
if (data.collegeId) {
|
||||
const majors = majorOptionsByCollege.value[data.collegeId];
|
||||
if (majors && majors.length > 0) {
|
||||
majorOptions.value = majors.map(item => ({
|
||||
majorOptions.value = majors.map((item) => ({
|
||||
label: item.deptName,
|
||||
value: item.deptId,
|
||||
majorCode: item.deptCode // 假设部门编码就是专业编码
|
||||
@@ -779,43 +725,31 @@ const rules = reactive({
|
||||
{ required: true, message: '姓名不能为空', trigger: 'blur' },
|
||||
{ min: 1, max: 30, message: '姓名长度在 1 到 30 个字符', trigger: 'blur' }
|
||||
],
|
||||
gender: [
|
||||
{ required: true, message: '性别不能为空', trigger: 'change' }
|
||||
],
|
||||
gender: [{ required: true, message: '性别不能为空', trigger: 'change' }],
|
||||
idCard: [
|
||||
{ required: true, message: '身份证号不能为空', trigger: 'blur' },
|
||||
{ pattern: /^[1-9]\d{5}(18|19|20)\d{2}((0[1-9])|(1[0-2]))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$/, message: '身份证号格式不正确', trigger: 'blur' },
|
||||
{
|
||||
pattern: /^[1-9]\d{5}(18|19|20)\d{2}((0[1-9])|(1[0-2]))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$/,
|
||||
message: '身份证号格式不正确',
|
||||
trigger: 'blur'
|
||||
},
|
||||
{ validator: validateIdCard, trigger: 'blur' }
|
||||
],
|
||||
enrollmentYear: [
|
||||
{ required: true, message: '入学年份不能为空', trigger: 'change' }
|
||||
],
|
||||
enrollmentYear: [{ required: true, message: '入学年份不能为空', trigger: 'change' }],
|
||||
graduationYear: [
|
||||
{ required: true, message: '毕业年份不能为空', trigger: 'change' },
|
||||
{ validator: validateGraduationYear, trigger: 'change' }
|
||||
],
|
||||
collegeId: [
|
||||
{ required: true, message: '学院名称不能为空', trigger: 'change' }
|
||||
],
|
||||
majorId: [
|
||||
{ required: true, message: '专业不能为空', trigger: 'change' }
|
||||
],
|
||||
className: [
|
||||
{ required: true, message: '班级名称不能为空', trigger: 'blur' }
|
||||
],
|
||||
educationLevel: [
|
||||
{ required: true, message: '学历层次不能为空', trigger: 'change' }
|
||||
],
|
||||
collegeId: [{ required: true, message: '学院名称不能为空', trigger: 'change' }],
|
||||
majorId: [{ required: true, message: '专业不能为空', trigger: 'change' }],
|
||||
className: [{ required: true, message: '班级名称不能为空', trigger: 'blur' }],
|
||||
educationLevel: [{ required: true, message: '学历层次不能为空', trigger: 'change' }],
|
||||
phoneNumber: [
|
||||
{ required: true, message: '手机号码不能为空', trigger: 'blur' },
|
||||
{ pattern: /^1[3-9]\d{9}$/, message: '手机号码格式不正确', trigger: 'blur' }
|
||||
],
|
||||
email: [
|
||||
{ type: 'email', message: '邮箱格式不正确', trigger: 'blur' }
|
||||
],
|
||||
employmentStatus: [
|
||||
{ required: true, message: '就业状态不能为空', trigger: 'change' }
|
||||
]
|
||||
email: [{ type: 'email', message: '邮箱格式不正确', trigger: 'blur' }],
|
||||
employmentStatus: [{ required: true, message: '就业状态不能为空', trigger: 'change' }]
|
||||
});
|
||||
|
||||
/** 提交表单 */
|
||||
@@ -827,7 +761,7 @@ const submitForm = async () => {
|
||||
try {
|
||||
// 处理年份字段
|
||||
const submitData = { ...form };
|
||||
|
||||
|
||||
// 🔍 详细的字段检查 - 帮助定位问题
|
||||
console.log('=== 提交前字段检查 ===');
|
||||
console.log('studentNo:', submitData.studentNo, typeof submitData.studentNo);
|
||||
@@ -843,7 +777,7 @@ const submitForm = async () => {
|
||||
console.log('phoneNumber:', submitData.phoneNumber, typeof submitData.phoneNumber);
|
||||
console.log('employmentStatus:', submitData.employmentStatus, typeof submitData.employmentStatus);
|
||||
console.log('========================');
|
||||
|
||||
|
||||
if (submitData.enrollmentYear) {
|
||||
submitData.enrollmentYear = parseInt(submitData.enrollmentYear.toString());
|
||||
}
|
||||
@@ -942,4 +876,4 @@ onMounted(() => {
|
||||
border-bottom: 1px solid #e8e8e8;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
@@ -311,13 +311,7 @@
|
||||
</el-table>
|
||||
</div>
|
||||
|
||||
<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>
|
||||
|
||||
<!-- 详情弹窗 -->
|
||||
@@ -539,14 +533,7 @@
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="审核意见" prop="auditRemark">
|
||||
<el-input
|
||||
v-model="auditForm.auditRemark"
|
||||
type="textarea"
|
||||
:rows="4"
|
||||
placeholder="请输入审核意见(选填)"
|
||||
maxlength="500"
|
||||
show-word-limit
|
||||
/>
|
||||
<el-input v-model="auditForm.auditRemark" type="textarea" :rows="4" placeholder="请输入审核意见(选填)" maxlength="500" show-word-limit />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
@@ -629,34 +616,29 @@ const getList = async () => {
|
||||
|
||||
try {
|
||||
const response = await listGraduateInfo(queryParams);
|
||||
|
||||
|
||||
// 检查响应数据格式并设置列表数据
|
||||
if (response && response.rows !== undefined) {
|
||||
// 处理 TableDataInfo 格式的数据 (直接从response获取)
|
||||
graduateInfoList.value = response.rows || [];
|
||||
total.value = response.total || 0;
|
||||
}
|
||||
else if (response && response.data && response.data.rows !== undefined) {
|
||||
} else if (response && response.data && response.data.rows !== undefined) {
|
||||
// 处理嵌套在data中的 TableDataInfo 格式
|
||||
graduateInfoList.value = response.data.rows || [];
|
||||
total.value = response.data.total || 0;
|
||||
}
|
||||
else if (response && Array.isArray(response)) {
|
||||
} else if (response && Array.isArray(response)) {
|
||||
// 处理直接返回的数组格式
|
||||
graduateInfoList.value = response;
|
||||
total.value = response.length;
|
||||
}
|
||||
else if (response && response.data && Array.isArray(response.data)) {
|
||||
} else if (response && response.data && Array.isArray(response.data)) {
|
||||
// 处理嵌套的数组格式
|
||||
graduateInfoList.value = response.data;
|
||||
total.value = response.data.length;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
console.error('未知的响应数据格式:', response);
|
||||
graduateInfoList.value = [];
|
||||
total.value = 0;
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('查询毕业生信息列表失败:', error);
|
||||
graduateInfoList.value = [];
|
||||
@@ -670,10 +652,7 @@ const getList = async () => {
|
||||
/** 获取统计数据 */
|
||||
const getStatistics = async () => {
|
||||
try {
|
||||
const [statsResponse, pendingResponse] = await Promise.all([
|
||||
getOverallStatistics(),
|
||||
getPendingAuditCount()
|
||||
]);
|
||||
const [statsResponse, pendingResponse] = await Promise.all([getOverallStatistics(), getPendingAuditCount()]);
|
||||
statistics.value = statsResponse.data;
|
||||
pendingAuditCount.value = pendingResponse.data;
|
||||
} catch (error) {
|
||||
@@ -697,7 +676,7 @@ const resetQuery = () => {
|
||||
|
||||
/** 多选框选中数据 */
|
||||
const handleSelectionChange = (selection: GraduateInfoVO[]) => {
|
||||
ids.value = selection.map(item => item.id!);
|
||||
ids.value = selection.map((item) => item.id!);
|
||||
single.value = selection.length !== 1;
|
||||
multiple.value = !selection.length;
|
||||
};
|
||||
|
||||
Loaded 100 of 1503 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user