Merge branch 'master' of http://47.99.164.66:3000/hzdyjs-xg/all-project
This commit is contained in:
commit
75201841cd
243 files changed
+89463
-2524
No files matched your search
@@ -0,0 +1,46 @@
|
||||
import request from '@/utils/request';
|
||||
import { ClassMsgForm, ClassMsgQuery, ClassMsgVO } from '../types';
|
||||
import { AxiosPromise } from 'axios';
|
||||
|
||||
// 查询班级信息-新版列表
|
||||
export function listClassMsg(query: ClassMsgQuery) {
|
||||
return request({
|
||||
url: '/classMsg/classMsg/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
});
|
||||
}
|
||||
|
||||
// 查询班级信息-新版详细
|
||||
export function getClassMsg(id: string | number) {
|
||||
return request({
|
||||
url: `/classMsg/classMsg/${id}`,
|
||||
method: 'get'
|
||||
});
|
||||
}
|
||||
|
||||
// 新增班级信息-新版
|
||||
export function addClassMsg(data: ClassMsgForm) {
|
||||
return request({
|
||||
url: '/classMsg/classMsg',
|
||||
method: 'post',
|
||||
data: data
|
||||
});
|
||||
}
|
||||
|
||||
// 修改班级信息-新版
|
||||
export function updateClassMsg(data: ClassMsgForm) {
|
||||
return request({
|
||||
url: '/classMsg/classMsg',
|
||||
method: 'put',
|
||||
data: data
|
||||
});
|
||||
}
|
||||
|
||||
// 删除班级信息-新版
|
||||
export function delClassMsg(id: string | number) {
|
||||
return request({
|
||||
url: `/classMsg/classMsg/${id}`,
|
||||
method: 'delete'
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,336 @@
|
||||
export interface ClassMsgVO {
|
||||
/**
|
||||
* 主键ID(雪花ID)
|
||||
*/
|
||||
id: string | number;
|
||||
|
||||
/**
|
||||
* 学院代码
|
||||
*/
|
||||
collegeId: string | number;
|
||||
|
||||
/**
|
||||
* 专业代码
|
||||
*/
|
||||
majorId: string | number;
|
||||
|
||||
/**
|
||||
* 班级名称
|
||||
*/
|
||||
className: string;
|
||||
|
||||
/**
|
||||
* 班级类型:1-中级工 2-高级工 3-预备技师 4-技师 5-中高职一体
|
||||
*/
|
||||
classType: string;
|
||||
|
||||
/**
|
||||
* 年级
|
||||
*/
|
||||
grade: string;
|
||||
|
||||
/**
|
||||
* 入学年份
|
||||
*/
|
||||
enrollmentYear: number;
|
||||
|
||||
/**
|
||||
* 毕业年份
|
||||
*/
|
||||
graduationYear: number;
|
||||
|
||||
/**
|
||||
* 班级状态:1-正常,2-暂停,3-毕业,4-解散
|
||||
*/
|
||||
classStatus: string;
|
||||
|
||||
/**
|
||||
* 备注信息
|
||||
*/
|
||||
remark: string;
|
||||
|
||||
}
|
||||
|
||||
export interface ClassMsgForm extends BaseEntity {
|
||||
/**
|
||||
* 主键ID(雪花ID)
|
||||
*/
|
||||
id?: string | number;
|
||||
|
||||
/**
|
||||
* 学院代码
|
||||
*/
|
||||
collegeId?: string | number;
|
||||
|
||||
/**
|
||||
* 学院名称
|
||||
*/
|
||||
collegeName?: string;
|
||||
|
||||
/**
|
||||
* 专业代码
|
||||
*/
|
||||
majorId?: string | number;
|
||||
|
||||
/**
|
||||
* 专业名称
|
||||
*/
|
||||
majorName?: string;
|
||||
|
||||
/**
|
||||
* 班级代码
|
||||
*/
|
||||
classCode?: string;
|
||||
|
||||
/**
|
||||
* 班级名称
|
||||
*/
|
||||
className?: string;
|
||||
|
||||
/**
|
||||
* 班级类型:1-中级工 2-高级工 3-预备技师 4-技师 5-中高职一体
|
||||
*/
|
||||
classType?: string;
|
||||
|
||||
/**
|
||||
* 年级
|
||||
*/
|
||||
grade?: string;
|
||||
|
||||
/**
|
||||
* 入学年份
|
||||
*/
|
||||
enrollmentYear?: number;
|
||||
|
||||
/**
|
||||
* 毕业年份
|
||||
*/
|
||||
graduationYear?: number;
|
||||
|
||||
/**
|
||||
* 培养方式:全日制,在职,函授,夜大等
|
||||
*/
|
||||
trainingType?: string;
|
||||
|
||||
/**
|
||||
* 学位类型:学士,硕士,博士,专科
|
||||
*/
|
||||
degreeType?: string;
|
||||
|
||||
/**
|
||||
* 辅导员ID
|
||||
*/
|
||||
counselorId?: string | number;
|
||||
|
||||
/**
|
||||
* 辅导员姓名
|
||||
*/
|
||||
counselorName?: string;
|
||||
|
||||
/**
|
||||
* 班主任ID
|
||||
*/
|
||||
classTeacherId?: string | number;
|
||||
|
||||
/**
|
||||
* 班主任姓名
|
||||
*/
|
||||
classTeacherName?: string;
|
||||
|
||||
/**
|
||||
* 班级导师ID
|
||||
*/
|
||||
classAdvisorId?: string | number;
|
||||
|
||||
/**
|
||||
* 班级导师姓名
|
||||
*/
|
||||
classAdvisorName?: string;
|
||||
|
||||
/**
|
||||
* 班长ID
|
||||
*/
|
||||
classMonitorId?: string | number;
|
||||
|
||||
/**
|
||||
* 班长姓名
|
||||
*/
|
||||
classMonitorName?: string;
|
||||
|
||||
/**
|
||||
* 副班长ID
|
||||
*/
|
||||
deputyMonitorId?: string | number;
|
||||
|
||||
/**
|
||||
* 副班长姓名
|
||||
*/
|
||||
deputyMonitorName?: string;
|
||||
|
||||
/**
|
||||
* 班级状态:1-正常,2-暂停,3-毕业,4-解散
|
||||
*/
|
||||
classStatus?: string;
|
||||
|
||||
/**
|
||||
* 是否已毕业:0-未毕业,1-已毕业
|
||||
*/
|
||||
isGraduation?: number;
|
||||
|
||||
/**
|
||||
* 毕业日期
|
||||
*/
|
||||
graduationDate?: string;
|
||||
|
||||
/**
|
||||
* 计划容量
|
||||
*/
|
||||
plannedCapacity?: number;
|
||||
|
||||
/**
|
||||
* 当前学生人数
|
||||
*/
|
||||
currentStudentCount?: number;
|
||||
|
||||
/**
|
||||
* 最大学生人数
|
||||
*/
|
||||
maxStudentCount?: number;
|
||||
|
||||
/**
|
||||
* 男生人数
|
||||
*/
|
||||
maleCount?: number;
|
||||
|
||||
/**
|
||||
* 女生人数
|
||||
*/
|
||||
femaleCount?: number;
|
||||
|
||||
/**
|
||||
* 在籍人数
|
||||
*/
|
||||
activeCount?: number;
|
||||
|
||||
/**
|
||||
* 转入人数
|
||||
*/
|
||||
transferInCount?: number;
|
||||
|
||||
/**
|
||||
* 转出人数
|
||||
*/
|
||||
transferOutCount?: number;
|
||||
|
||||
/**
|
||||
* 退学人数
|
||||
*/
|
||||
dropoutCount?: number;
|
||||
|
||||
/**
|
||||
* 毕业人数
|
||||
*/
|
||||
graduateCount?: number;
|
||||
|
||||
/**
|
||||
* 班级办公室位置
|
||||
*/
|
||||
officeLocation?: string;
|
||||
|
||||
/**
|
||||
* 班级教室位置
|
||||
*/
|
||||
classLocation?: string;
|
||||
|
||||
/**
|
||||
* 联系电话
|
||||
*/
|
||||
contactPhone?: string;
|
||||
|
||||
/**
|
||||
* QQ群号
|
||||
*/
|
||||
qqGroup?: string;
|
||||
|
||||
/**
|
||||
* 微信群
|
||||
*/
|
||||
wechatGroup?: string;
|
||||
|
||||
/**
|
||||
* 班级邮箱
|
||||
*/
|
||||
email?: string;
|
||||
|
||||
/**
|
||||
* 课程安排(JSON格式)
|
||||
*/
|
||||
classSchedule?: string;
|
||||
|
||||
/**
|
||||
* 班级文化建设
|
||||
*/
|
||||
classCulture?: string;
|
||||
|
||||
/**
|
||||
* 班级规章制度
|
||||
*/
|
||||
classRules?: string;
|
||||
|
||||
/**
|
||||
* 班级描述
|
||||
*/
|
||||
classDescription?: string;
|
||||
|
||||
/**
|
||||
* 最后统计更新时间
|
||||
*/
|
||||
lastCountUpdate?: string;
|
||||
|
||||
/**
|
||||
* 备注信息
|
||||
*/
|
||||
remark?: string;
|
||||
|
||||
}
|
||||
|
||||
export interface ClassMsgQuery extends PageQuery {
|
||||
|
||||
/**
|
||||
* 学院代码
|
||||
*/
|
||||
collegeId?: string | number;
|
||||
|
||||
/**
|
||||
* 专业代码
|
||||
*/
|
||||
majorId?: string | number;
|
||||
|
||||
/**
|
||||
* 班级名称
|
||||
*/
|
||||
className?: string;
|
||||
|
||||
/**
|
||||
* 班级类型:1-中级工 2-高级工 3-预备技师 4-技师 5-中高职一体
|
||||
*/
|
||||
classType?: string;
|
||||
|
||||
/**
|
||||
* 年级
|
||||
*/
|
||||
grade?: string;
|
||||
|
||||
/**
|
||||
* 班级状态:1-正常,2-暂停,3-毕业,4-解散
|
||||
*/
|
||||
classStatus?: string;
|
||||
|
||||
/**
|
||||
* 日期范围参数
|
||||
*/
|
||||
params?: any;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
import request from '@/utils/request';
|
||||
import { SysDeptVo } from './types';
|
||||
|
||||
// 获取学院列表
|
||||
export function listColleges() {
|
||||
return request({
|
||||
url: '/xg/college/list',
|
||||
method: 'get'
|
||||
});
|
||||
}
|
||||
|
||||
// 获取学院和专业树形结构
|
||||
export function collegeTree() {
|
||||
return request({
|
||||
url: '/xg/college/tree',
|
||||
method: 'get'
|
||||
});
|
||||
}
|
||||
|
||||
// 根据学院ID获取专业列表
|
||||
export function listMajorsByCollegeId(collegeId: string | number) {
|
||||
return request({
|
||||
url: `/xg/college/majors/${collegeId}`,
|
||||
method: 'get'
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
/** 部门视图对象 */
|
||||
export interface SysDeptVo {
|
||||
/** 部门id */
|
||||
deptId: number;
|
||||
/** 父部门id */
|
||||
parentId: number;
|
||||
/** 祖级列表 */
|
||||
ancestors: string;
|
||||
/** 部门名称 */
|
||||
deptName: string;
|
||||
/** 显示顺序 */
|
||||
orderNum: number;
|
||||
/** 负责人 */
|
||||
leader: string;
|
||||
/** 联系电话 */
|
||||
phone: string;
|
||||
/** 邮箱 */
|
||||
email: string;
|
||||
/** 部门状态:0正常,1停用 */
|
||||
status: string;
|
||||
/** 删除标志 */
|
||||
delFlag: string;
|
||||
/** 创建者 */
|
||||
createBy: number;
|
||||
/** 创建时间 */
|
||||
createTime: string;
|
||||
/** 更新者 */
|
||||
updateBy: number;
|
||||
/** 更新时间 */
|
||||
updateTime: string;
|
||||
/** 子部门 */
|
||||
children: SysDeptVo[];
|
||||
}
|
||||
|
||||
/** 班级查询参数 */
|
||||
export interface ClassMsgQuery {
|
||||
/** 学院代码 */
|
||||
collegeId?: number | string;
|
||||
/** 专业代码 */
|
||||
majorId?: number | string;
|
||||
/** 班级名称 */
|
||||
className?: string;
|
||||
/** 班级类型 */
|
||||
classType?: string | number;
|
||||
/** 年级 */
|
||||
grade?: string;
|
||||
/** 班级状态 */
|
||||
classStatus?: string | number;
|
||||
/** 分页大小 */
|
||||
pageSize: number;
|
||||
/** 当前页码 */
|
||||
pageNum: number;
|
||||
/** 其他参数 */
|
||||
params: Record<string, any>;
|
||||
}
|
||||
|
||||
/** 班级详细信息 */
|
||||
export interface ClassMsgVO {
|
||||
/** 主键ID */
|
||||
id: string | number;
|
||||
/** 学院代码 */
|
||||
collegeId?: string | number;
|
||||
/** 学院名称 */
|
||||
collegeName?: string;
|
||||
/** 专业代码 */
|
||||
majorId?: string | number;
|
||||
/** 专业名称 */
|
||||
majorName?: string;
|
||||
/** 班级代码 */
|
||||
classCode?: string;
|
||||
/** 班级名称 */
|
||||
className?: string;
|
||||
/** 班级类型 */
|
||||
classType?: string | number;
|
||||
/** 年级 */
|
||||
grade?: string;
|
||||
/** 入学年份 */
|
||||
enrollmentYear?: string;
|
||||
/** 毕业年份 */
|
||||
graduationYear?: string;
|
||||
/** 培养类型 */
|
||||
trainingType?: string;
|
||||
/** 学位类型 */
|
||||
degreeType?: string;
|
||||
/** 辅导员ID */
|
||||
counselorId?: string | number;
|
||||
/** 辅导员姓名 */
|
||||
counselorName?: string;
|
||||
/** 班主任ID */
|
||||
classTeacherId?: string | number;
|
||||
/** 班主任姓名 */
|
||||
classTeacherName?: string;
|
||||
/** 班级导师ID */
|
||||
classAdvisorId?: string | number;
|
||||
/** 班级导师姓名 */
|
||||
classAdvisorName?: string;
|
||||
/** 班长ID */
|
||||
classMonitorId?: string | number;
|
||||
/** 班长姓名 */
|
||||
classMonitorName?: string;
|
||||
/** 副班长ID */
|
||||
deputyMonitorId?: string | number;
|
||||
/** 副班长姓名 */
|
||||
deputyMonitorName?: string;
|
||||
/** 班级状态 */
|
||||
classStatus?: string | number;
|
||||
/** 是否已毕业 */
|
||||
isGraduation?: string | number;
|
||||
/** 毕业日期 */
|
||||
graduationDate?: string;
|
||||
/** 计划容量 */
|
||||
plannedCapacity?: number;
|
||||
/** 当前学生人数 */
|
||||
currentStudentCount?: number;
|
||||
/** 最大学生人数 */
|
||||
maxStudentCount?: number;
|
||||
/** 男生人数 */
|
||||
maleCount?: number;
|
||||
/** 女生人数 */
|
||||
femaleCount?: number;
|
||||
/** 在籍人数 */
|
||||
activeCount?: number;
|
||||
/** 转入人数 */
|
||||
transferInCount?: number;
|
||||
/** 转出人数 */
|
||||
transferOutCount?: number;
|
||||
/** 退学人数 */
|
||||
dropoutCount?: number;
|
||||
/** 毕业人数 */
|
||||
graduateCount?: number;
|
||||
/** 办公室位置 */
|
||||
officeLocation?: string;
|
||||
/** 教室位置 */
|
||||
classLocation?: string;
|
||||
/** 联系电话 */
|
||||
contactPhone?: string;
|
||||
/** QQ群号 */
|
||||
qqGroup?: string;
|
||||
/** 微信群 */
|
||||
wechatGroup?: string;
|
||||
/** 班级邮箱 */
|
||||
email?: string;
|
||||
/** 课程安排 */
|
||||
classSchedule?: string;
|
||||
/** 班级文化建设 */
|
||||
classCulture?: string;
|
||||
/** 班级规章制度 */
|
||||
classRules?: string;
|
||||
/** 班级描述 */
|
||||
classDescription?: string;
|
||||
/** 统计更新时间 */
|
||||
lastCountUpdate?: string;
|
||||
/** 备注 */
|
||||
remark?: string;
|
||||
}
|
||||
|
||||
/** 班级表单数据 */
|
||||
export interface ClassMsgForm extends ClassMsgVO {
|
||||
// 表单额外字段
|
||||
}
|
||||
@@ -1,126 +0,0 @@
|
||||
export interface DormBuildingVO {
|
||||
/**
|
||||
* 主键ID
|
||||
*/
|
||||
id: string | number;
|
||||
|
||||
/**
|
||||
* 宿舍楼编码
|
||||
*/
|
||||
buildingCode: string;
|
||||
|
||||
/**
|
||||
* 宿舍楼名称
|
||||
*/
|
||||
buildingName: string;
|
||||
|
||||
/**
|
||||
* 所属宿舍区ID
|
||||
*/
|
||||
areaId: string | number;
|
||||
|
||||
/**
|
||||
* 所属校区ID
|
||||
*/
|
||||
campusId: string | number;
|
||||
|
||||
/**
|
||||
* 宿舍楼类型(1男生楼 2女生楼 3混合楼)
|
||||
*/
|
||||
buildingType: string;
|
||||
|
||||
/**
|
||||
* 宿舍楼描述
|
||||
*/
|
||||
buildingDescription: string;
|
||||
|
||||
/**
|
||||
* 排序号
|
||||
*/
|
||||
sortOrder: number;
|
||||
|
||||
}
|
||||
|
||||
export interface DormBuildingForm extends BaseEntity {
|
||||
/**
|
||||
* 主键ID
|
||||
*/
|
||||
id?: string | number;
|
||||
|
||||
/**
|
||||
* 宿舍楼编码
|
||||
*/
|
||||
buildingCode?: string;
|
||||
|
||||
/**
|
||||
* 宿舍楼名称
|
||||
*/
|
||||
buildingName?: string;
|
||||
|
||||
/**
|
||||
* 所属宿舍区ID
|
||||
*/
|
||||
areaId?: string | number;
|
||||
|
||||
/**
|
||||
* 所属校区ID
|
||||
*/
|
||||
campusId?: string | number;
|
||||
|
||||
/**
|
||||
* 宿舍楼类型(1男生楼 2女生楼 3混合楼)
|
||||
*/
|
||||
buildingType?: string;
|
||||
|
||||
/**
|
||||
* 宿舍楼描述
|
||||
*/
|
||||
buildingDescription?: string;
|
||||
|
||||
/**
|
||||
* 排序号
|
||||
*/
|
||||
sortOrder?: number;
|
||||
|
||||
}
|
||||
|
||||
export interface DormBuildingQuery extends PageQuery {
|
||||
|
||||
/**
|
||||
* 宿舍楼编码
|
||||
*/
|
||||
buildingCode?: string;
|
||||
|
||||
/**
|
||||
* 宿舍楼名称
|
||||
*/
|
||||
buildingName?: string;
|
||||
|
||||
/**
|
||||
* 所属宿舍区ID
|
||||
*/
|
||||
areaId?: string | number;
|
||||
|
||||
/**
|
||||
* 所属校区ID
|
||||
*/
|
||||
campusId?: string | number;
|
||||
|
||||
/**
|
||||
* 宿舍楼类型(1男生楼 2女生楼 3混合楼)
|
||||
*/
|
||||
buildingType?: string;
|
||||
|
||||
/**
|
||||
* 排序号
|
||||
*/
|
||||
sortOrder?: number;
|
||||
|
||||
/**
|
||||
* 日期范围参数
|
||||
*/
|
||||
params?: any;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import request from '@/utils/request'
|
||||
import { AxiosPromise } from 'axios'
|
||||
import { ClassInfoVO, ClassInfoQuery, ClassInfoForm, OptionData } from './classInfo/types'
|
||||
import { getToken } from '@/utils/auth'
|
||||
import { DeptTreeVO, DeptVO } from '@/api/system/dept/types'
|
||||
import { treeselect as deptTreeselect, listDeptExcludeChild } from '@/api/system/dept'
|
||||
|
||||
// 通用类型定义
|
||||
interface TableDataInfo<T> {
|
||||
@@ -256,14 +259,34 @@ export const getCollegeOptions = (): AxiosPromise<R<any[]>> => {
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取专业选项
|
||||
* 从系统部门获取学院选项(二级学院)
|
||||
*/
|
||||
export const getMajorOptions = (collegeCode?: string): AxiosPromise<R<any[]>> => {
|
||||
export const getDeptCollegeOptions = (): AxiosPromise<DeptTreeVO[]> => {
|
||||
console.log('直接调用部门树API,避免treeselect参数问题');
|
||||
return request({
|
||||
url: '/xg/studentInfo/options/majors',
|
||||
method: 'get',
|
||||
params: { collegeCode }
|
||||
})
|
||||
url: '/system/dept/treeselect',
|
||||
method: 'get'
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 从系统部门获取专业选项(学院的下级部门)
|
||||
*/
|
||||
export const getDeptMajorOptions = (deptId: string | number): AxiosPromise<DeptVO[]> => {
|
||||
// 确保deptId是有效的数字ID
|
||||
if (!deptId || isNaN(Number(deptId))) {
|
||||
console.error('Invalid deptId:', deptId);
|
||||
return Promise.reject(new Error('无效的部门ID'));
|
||||
}
|
||||
|
||||
// 直接调用接口,不使用封装的系统API
|
||||
const numericDeptId = Number(deptId);
|
||||
console.log('直接调用部门API,使用数字ID:', numericDeptId);
|
||||
|
||||
return request({
|
||||
url: '/system/dept/list/exclude/' + numericDeptId,
|
||||
method: 'get'
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -277,11 +300,17 @@ export const getCounselorOptions = (): AxiosPromise<R<any[]>> => {
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取班主任选项
|
||||
* 获取班主任选项列表
|
||||
*/
|
||||
export const getClassTeacherOptions = (): AxiosPromise<R<any[]>> => {
|
||||
export const getClassTeacherOptions = (): AxiosPromise<R<OptionData[]>> => {
|
||||
console.log('调用班主任API: /xg/classInfo/teacherOptions')
|
||||
return request({
|
||||
url: '/xg/studentInfo/options/classTeachers',
|
||||
method: 'get'
|
||||
url: '/xg/classInfo/teacherOptions',
|
||||
method: 'get',
|
||||
headers: {
|
||||
// 确保请求带有认证信息
|
||||
'Authorization': 'Bearer ' + getToken(),
|
||||
'clientid': import.meta.env.VITE_APP_CLIENT_ID
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import request from '@/utils/request'
|
||||
import { AxiosPromise } from 'axios'
|
||||
import { getToken } from '@/utils/auth'
|
||||
|
||||
// 学生信息数据类型
|
||||
export interface StudentInfo {
|
||||
@@ -398,9 +399,15 @@ export function getCounselorOptions(): AxiosPromise<OptionData[]> {
|
||||
|
||||
// 获取班主任选项
|
||||
export function getClassTeacherOptions(): AxiosPromise<OptionData[]> {
|
||||
console.log('调用班主任API: /xg/classInfo/teacherOptions')
|
||||
return request({
|
||||
url: '/xg/studentInfo/options/classTeachers',
|
||||
method: 'get'
|
||||
url: '/xg/classInfo/teacherOptions',
|
||||
method: 'get',
|
||||
headers: {
|
||||
// 确保请求带有认证信息
|
||||
'Authorization': 'Bearer ' + getToken(),
|
||||
'clientid': import.meta.env.VITE_APP_CLIENT_ID
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
import request from '@/utils/request';
|
||||
|
||||
/**
|
||||
* 班级班主任辅导员关联对象
|
||||
*/
|
||||
interface ClassTeacherCounselorRelBo {
|
||||
id?: number | string;
|
||||
classId?: number | string;
|
||||
teacherId?: number | string;
|
||||
teacherName?: string;
|
||||
counselorId?: number | string;
|
||||
counselorName?: string;
|
||||
relationType?: number; // 1:班主任 2:辅导员
|
||||
remark?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加班级班主任辅导员关联
|
||||
* @param data 班级班主任辅导员关联信息
|
||||
* @returns 返回添加结果
|
||||
*/
|
||||
export function addClassTeacherCounselorRel(data: ClassTeacherCounselorRelBo) {
|
||||
return request({
|
||||
url: '/teacherCounselorRel/classTeacherCounselorRel',
|
||||
method: 'post',
|
||||
data: data
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改班级班主任辅导员关联
|
||||
* @param data 班级班主任辅导员关联信息
|
||||
* @returns 返回修改结果
|
||||
*/
|
||||
export function updateClassTeacherCounselorRel(data: ClassTeacherCounselorRelBo) {
|
||||
return request({
|
||||
url: '/teacherCounselorRel/classTeacherCounselorRel',
|
||||
method: 'put',
|
||||
data: data
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询班级班主任辅导员关联列表
|
||||
* @param query 查询参数
|
||||
* @returns 返回班级班主任辅导员关联列表
|
||||
*/
|
||||
export function listClassTeacherCounselorRel(query: any) {
|
||||
return request({
|
||||
url: '/teacherCounselorRel/classTeacherCounselorRel/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取班级班主任辅导员关联详情
|
||||
* @param id 关联ID
|
||||
* @returns 返回班级班主任辅导员关联详情
|
||||
*/
|
||||
export function getClassTeacherCounselorRel(id: number | string) {
|
||||
return request({
|
||||
url: `/teacherCounselorRel/classTeacherCounselorRel/${id}`,
|
||||
method: 'get'
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除班级班主任辅导员关联
|
||||
* @param ids 关联ID数组
|
||||
* @returns 返回删除结果
|
||||
*/
|
||||
export function delClassTeacherCounselorRel(ids: number | string | (number | string)[]) {
|
||||
return request({
|
||||
url: `/teacherCounselorRel/classTeacherCounselorRel/${ids instanceof Array ? ids.join(',') : ids}`,
|
||||
method: 'delete'
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出班级班主任辅导员关联
|
||||
* @param query 查询参数
|
||||
* @returns 返回导出结果
|
||||
*/
|
||||
export function exportClassTeacherCounselorRel(query: any) {
|
||||
return request({
|
||||
url: '/teacherCounselorRel/classTeacherCounselorRel/export',
|
||||
method: 'post',
|
||||
params: query,
|
||||
responseType: 'blob'
|
||||
});
|
||||
}
|
||||
@@ -14,5 +14,8 @@ import request from '@/utils/request';
|
||||
// export * from './scholarship' // 奖助管理
|
||||
// export * from './activity' // 活动管理
|
||||
|
||||
// 导出学工用户模块API
|
||||
export * from './sysUser'
|
||||
|
||||
// 临时导出一个空对象,防止编译错误
|
||||
export default {};
|
||||
@@ -0,0 +1,29 @@
|
||||
import request from '@/utils/request';
|
||||
import { AxiosPromise } from 'axios';
|
||||
|
||||
/**
|
||||
* 用户信息对象
|
||||
*/
|
||||
export interface XgSysUser {
|
||||
userId: number | string;
|
||||
userName: string;
|
||||
nickName: string;
|
||||
email: string;
|
||||
phonenumber: string;
|
||||
sex: string;
|
||||
status: string;
|
||||
createTime: string;
|
||||
updateTime: string;
|
||||
deptId: number;
|
||||
deptName: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询有效用户列表
|
||||
*/
|
||||
export function getActiveUserList(): AxiosPromise<XgSysUser[]> {
|
||||
return request({
|
||||
url: '/xg/sysUser/activeList',
|
||||
method: 'get'
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
import request from '@/utils/request';
|
||||
import { AxiosPromise } from 'axios';
|
||||
import { DormBedVO, DormBedForm, DormBedQuery } from '@/api/xgss/dormBed/types';
|
||||
|
||||
/**
|
||||
* 查询宿舍床位信息列表
|
||||
* @param query
|
||||
* @returns {*}
|
||||
*/
|
||||
|
||||
export const listDormBed = (query?: DormBedQuery): AxiosPromise<DormBedVO[]> => {
|
||||
return request({
|
||||
url: '/dormbed/dormBed/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 查询宿舍床位信息详细
|
||||
* @param id
|
||||
*/
|
||||
export const getDormBed = (id: string | number): AxiosPromise<DormBedVO> => {
|
||||
return request({
|
||||
url: '/dormbed/dormBed/' + id,
|
||||
method: 'get'
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 新增宿舍床位信息
|
||||
* @param data
|
||||
*/
|
||||
export const addDormBed = (data: DormBedForm) => {
|
||||
return request({
|
||||
url: '/dormbed/dormBed',
|
||||
method: 'post',
|
||||
data: data
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 修改宿舍床位信息
|
||||
* @param data
|
||||
*/
|
||||
export const updateDormBed = (data: DormBedForm) => {
|
||||
return request({
|
||||
url: '/dormbed/dormBed',
|
||||
method: 'put',
|
||||
data: data
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 删除宿舍床位信息
|
||||
* @param id
|
||||
*/
|
||||
export const delDormBed = (id: string | number | Array<string | number>) => {
|
||||
return request({
|
||||
url: '/dormbed/dormBed/' + id,
|
||||
method: 'delete'
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,66 @@
|
||||
/**
|
||||
* 宿舍床位信息查询参数
|
||||
*/
|
||||
export interface DormBedQuery {
|
||||
/** 床位编号 */
|
||||
bedCode?: string;
|
||||
/** 床位名称 */
|
||||
bedName?: string;
|
||||
/** 所属房间ID */
|
||||
roomId?: string | number;
|
||||
/** 床位状态(0=空闲,1=已分配,2=维修中) */
|
||||
bedStatus?: string;
|
||||
/** 床位类型(1=下铺,2=上铺,3=中铺,4=独立床) */
|
||||
bedType?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 宿舍床位信息对象
|
||||
*/
|
||||
export interface DormBedVO {
|
||||
/** 床位ID */
|
||||
id: string | number;
|
||||
/** 床位编号 */
|
||||
bedCode: string;
|
||||
/** 床位名称 */
|
||||
bedName: string;
|
||||
/** 所属房间ID */
|
||||
roomId: string | number;
|
||||
/** 床位状态(0=空闲,1=已分配,2=维修中) */
|
||||
bedStatus: string;
|
||||
/** 床位类型(1=下铺,2=上铺,3=中铺,4=独立床) */
|
||||
bedType: string;
|
||||
/** 备注 */
|
||||
remark?: string;
|
||||
/** 创建者 */
|
||||
createBy: number;
|
||||
/** 创建时间 */
|
||||
createTime: Date;
|
||||
/** 更新者 */
|
||||
updateBy: number;
|
||||
/** 更新时间 */
|
||||
updateTime: Date;
|
||||
}
|
||||
|
||||
/**
|
||||
* 宿舍床位信息表单对象
|
||||
*/
|
||||
export interface DormBedForm {
|
||||
/** 床位ID */
|
||||
id: string | number;
|
||||
/** 床位编号 */
|
||||
bedCode: string;
|
||||
/** 床位名称 */
|
||||
bedName: string;
|
||||
/** 所属房间ID */
|
||||
roomId: string | number;
|
||||
/** 床位状态(0=空闲,1=已分配,2=维修中) */
|
||||
bedStatus: string;
|
||||
/** 床位类型(1=下铺,2=上铺,3=中铺,4=独立床) */
|
||||
bedType: string;
|
||||
/** 备注 */
|
||||
remark?: string;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
import request from '@/utils/request';
|
||||
import { AxiosPromise } from 'axios';
|
||||
import { DormRoomVO, DormRoomForm, DormRoomQuery } from '@/api/xgss/dormRoom/types';
|
||||
|
||||
/**
|
||||
* 查询宿舍房间信息列表
|
||||
* @param query
|
||||
* @returns {*}
|
||||
*/
|
||||
|
||||
export const listDormRoom = (query?: DormRoomQuery): AxiosPromise<DormRoomVO[]> => {
|
||||
return request({
|
||||
url: '/xgss/dormRoom/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 查询宿舍房间信息详细
|
||||
* @param id
|
||||
*/
|
||||
export const getDormRoom = (id: string | number): AxiosPromise<DormRoomVO> => {
|
||||
return request({
|
||||
url: '/xgss/dormRoom/' + id,
|
||||
method: 'get'
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 新增宿舍房间信息
|
||||
* @param data
|
||||
*/
|
||||
export const addDormRoom = (data: DormRoomForm) => {
|
||||
return request({
|
||||
url: '/xgss/dormRoom',
|
||||
method: 'post',
|
||||
data: data
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 修改宿舍房间信息
|
||||
* @param data
|
||||
*/
|
||||
export const updateDormRoom = (data: DormRoomForm) => {
|
||||
return request({
|
||||
url: '/xgss/dormRoom',
|
||||
method: 'put',
|
||||
data: data
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 删除宿舍房间信息
|
||||
* @param id
|
||||
*/
|
||||
export const delDormRoom = (id: string | number | Array<string | number>) => {
|
||||
return request({
|
||||
url: '/xgss/dormRoom/' + id,
|
||||
method: 'delete'
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,176 @@
|
||||
export interface DormRoomVO {
|
||||
/**
|
||||
* 主键ID
|
||||
*/
|
||||
id: string | number;
|
||||
|
||||
/**
|
||||
* 房间编号
|
||||
*/
|
||||
roomCode: string;
|
||||
|
||||
/**
|
||||
* 房间名称
|
||||
*/
|
||||
roomName: string;
|
||||
|
||||
/**
|
||||
* 所属宿舍楼ID
|
||||
*/
|
||||
buildingId: string | number;
|
||||
|
||||
/**
|
||||
* 楼层
|
||||
*/
|
||||
floorNum: number;
|
||||
|
||||
/**
|
||||
* 房间类型(1标准间 2 单人间 3 套间)
|
||||
*/
|
||||
roomType: string;
|
||||
|
||||
/**
|
||||
* 实际床位数
|
||||
*/
|
||||
actualBeds: number;
|
||||
|
||||
/**
|
||||
* 当前床位数
|
||||
*/
|
||||
currentBeds?: number;
|
||||
|
||||
/**
|
||||
* 房间面积(平方米)
|
||||
*/
|
||||
roomArea: number;
|
||||
|
||||
/**
|
||||
* 房间状态(0可用 1维修中 2停用)
|
||||
*/
|
||||
roomStatus: string;
|
||||
|
||||
/**
|
||||
* 删除标志(0代表存在 1代表删除)
|
||||
*/
|
||||
deleted: string;
|
||||
|
||||
/**
|
||||
* 备注
|
||||
*/
|
||||
remark: string;
|
||||
|
||||
}
|
||||
|
||||
export interface DormRoomForm extends BaseEntity {
|
||||
/**
|
||||
* 主键ID
|
||||
*/
|
||||
id?: string | number;
|
||||
|
||||
/**
|
||||
* 房间编号
|
||||
*/
|
||||
roomCode?: string;
|
||||
|
||||
/**
|
||||
* 房间名称
|
||||
*/
|
||||
roomName?: string;
|
||||
|
||||
/**
|
||||
* 所属宿舍楼ID
|
||||
*/
|
||||
buildingId?: string | number;
|
||||
|
||||
/**
|
||||
* 楼层
|
||||
*/
|
||||
floorNum?: number;
|
||||
|
||||
/**
|
||||
* 房间类型(1标准间 2 单人间 3 套间)
|
||||
*/
|
||||
roomType?: string;
|
||||
|
||||
/**
|
||||
* 实际床位数
|
||||
*/
|
||||
actualBeds?: number;
|
||||
|
||||
/**
|
||||
* 房间面积(平方米)
|
||||
*/
|
||||
roomArea?: number;
|
||||
|
||||
/**
|
||||
* 房间状态(0可用 1维修中 2停用)
|
||||
*/
|
||||
roomStatus?: string;
|
||||
|
||||
/**
|
||||
* 删除标志(0代表存在 1代表删除)
|
||||
*/
|
||||
deleted?: string;
|
||||
|
||||
/**
|
||||
* 备注
|
||||
*/
|
||||
remark?: string;
|
||||
|
||||
}
|
||||
|
||||
export interface DormRoomQuery extends PageQuery {
|
||||
|
||||
/**
|
||||
* 房间编号
|
||||
*/
|
||||
roomCode?: string;
|
||||
|
||||
/**
|
||||
* 房间名称
|
||||
*/
|
||||
roomName?: string;
|
||||
|
||||
/**
|
||||
* 所属宿舍楼ID
|
||||
*/
|
||||
buildingId?: string | number;
|
||||
|
||||
/**
|
||||
* 楼层
|
||||
*/
|
||||
floorNum?: number;
|
||||
|
||||
/**
|
||||
* 房间类型(1标准间 2 单人间 3 套间)
|
||||
*/
|
||||
roomType?: string;
|
||||
|
||||
/**
|
||||
* 实际床位数
|
||||
*/
|
||||
actualBeds?: number;
|
||||
|
||||
/**
|
||||
* 房间面积(平方米)
|
||||
*/
|
||||
roomArea?: number;
|
||||
|
||||
/**
|
||||
* 房间状态(0可用 1维修中 2停用)
|
||||
*/
|
||||
roomStatus?: string;
|
||||
|
||||
/**
|
||||
* 删除标志(0代表存在 1代表删除)
|
||||
*/
|
||||
deleted?: string;
|
||||
|
||||
/**
|
||||
* 日期范围参数
|
||||
*/
|
||||
params?: any;
|
||||
}
|
||||
|
||||
|
||||
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
+34
@@ -61,3 +61,37 @@ export const delDormManager = (id: string | number | Array<string | number>) =>
|
||||
method: 'delete'
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 分配管理员数据范围
|
||||
* @param managerId 管理员ID
|
||||
* @param authType 授权类型(1全校级 2校区级 3宿舍区级 4楼栋级)
|
||||
* @param authSourceIds 授权来源ID列表
|
||||
*/
|
||||
export const assignScope = (managerId: string | number, authType: number, authSourceIds: (string | number)[]) => {
|
||||
return request({
|
||||
url: '/glysqgx/dormManagerSource/assignScope',
|
||||
method: 'post',
|
||||
params: {
|
||||
managerId,
|
||||
authType
|
||||
},
|
||||
data: authSourceIds
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取管理员授权范围名称
|
||||
* @param managerId 管理员ID
|
||||
* @param managerType 管理员类型
|
||||
*/
|
||||
export const getManagerScopeNames = (managerId: string | number, managerType: number) => {
|
||||
return request({
|
||||
url: '/glysqgx/dormManagerSource/getScopeNames',
|
||||
method: 'get',
|
||||
params: {
|
||||
managerId,
|
||||
managerType
|
||||
}
|
||||
});
|
||||
};
|
||||
+5
@@ -49,6 +49,11 @@ export interface DormManagerVO {
|
||||
*/
|
||||
remark: string;
|
||||
|
||||
/**
|
||||
* 授权范围名称(逗号分隔的字符串)
|
||||
*/
|
||||
scopeNames?: string;
|
||||
|
||||
}
|
||||
|
||||
export interface DormManagerForm extends BaseEntity {
|
||||
+12
-1
@@ -1,6 +1,6 @@
|
||||
import request from '@/utils/request';
|
||||
import { AxiosPromise } from 'axios';
|
||||
import { DormBuildingVO, DormBuildingForm, DormBuildingQuery } from '@/api/sslxx/dormBuilding/types';
|
||||
import { DormBuildingVO, DormBuildingForm, DormBuildingQuery, BuildingTreeSelectVo } from '@/api/sslxx/dormBuilding/types';
|
||||
|
||||
/**
|
||||
* 查询宿舍楼信息列表
|
||||
@@ -83,3 +83,14 @@ export const listAreaTreeData = (campusId?: string | number): AxiosPromise<any>
|
||||
params: { campusId }
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取宿舍楼树形结构数据(校区-宿舍区-宿舍楼三级结构)
|
||||
* 校区和宿舍区节点不可选,只有宿舍楼节点可以选择
|
||||
*/
|
||||
export const getBuildingTree = (): AxiosPromise<BuildingTreeSelectVo[]> => {
|
||||
return request({
|
||||
url: '/sslxx/dormBuilding/buildingTree',
|
||||
method: 'get'
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,50 @@
|
||||
export interface DormBuildingVO {
|
||||
id: string | number;
|
||||
buildingCode: string;
|
||||
buildingName: string;
|
||||
areaId: string | number;
|
||||
campusId: string | number;
|
||||
buildingType: string;
|
||||
buildingDescription: string;
|
||||
sortOrder: number;
|
||||
createTime: string;
|
||||
createBy: string;
|
||||
updateTime: string;
|
||||
updateBy: string;
|
||||
remark: string;
|
||||
areaName?: string;
|
||||
campusName?: string;
|
||||
floors: number;
|
||||
}
|
||||
|
||||
export interface DormBuildingForm extends Omit<DormBuildingVO, 'createTime' | 'createBy' | 'updateTime' | 'updateBy' | 'areaName' | 'campusName'> {
|
||||
}
|
||||
|
||||
export interface DormBuildingQuery {
|
||||
buildingCode?: string;
|
||||
buildingName?: string;
|
||||
areaId?: string | number;
|
||||
campusId?: string | number;
|
||||
buildingType?: string;
|
||||
floors?: number;
|
||||
params?: Record<string, any>;
|
||||
}
|
||||
|
||||
/**
|
||||
* 宿舍楼树形结构节点
|
||||
*/
|
||||
export interface BuildingTreeSelectVo {
|
||||
id: number;
|
||||
label: string;
|
||||
nodeType: 'campus' | 'area' | 'building';
|
||||
disabled: boolean;
|
||||
children?: BuildingTreeSelectVo[];
|
||||
createBy?: number;
|
||||
createTime?: string;
|
||||
updateBy?: number;
|
||||
floors?: number;
|
||||
updateTime?: string;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import request from '@/utils/request';
|
||||
import { AxiosPromise } from 'axios';
|
||||
import { DormAreaVO, DormAreaForm, DormAreaQuery } from '@/api/ssqxx/dormArea/types';
|
||||
import { DormAreaVO, DormAreaForm, DormAreaQuery, TreeSelectVo } from '@/api/ssqxx/dormArea/types';
|
||||
|
||||
/**
|
||||
* 查询宿舍区信息列表
|
||||
@@ -61,3 +61,14 @@ export const delDormArea = (id: string | number | Array<string | number>) => {
|
||||
method: 'delete'
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取宿舍区树形结构数据
|
||||
* 以校区为根节点,宿舍区为子节点的树形结构
|
||||
*/
|
||||
export const getAreaTree = (): AxiosPromise<TreeSelectVo[]> => {
|
||||
return request({
|
||||
url: '/ssqxx/dormArea/tree',
|
||||
method: 'get'
|
||||
});
|
||||
};
|
||||
@@ -87,5 +87,21 @@ export interface DormAreaQuery extends PageQuery {
|
||||
params?: any;
|
||||
}
|
||||
|
||||
/**
|
||||
* 树形选择数据类型
|
||||
*/
|
||||
export interface TreeSelectVo {
|
||||
/** 节点ID */
|
||||
id: number;
|
||||
/** 节点名称 */
|
||||
label: string;
|
||||
/** 节点类型(campus:校区, area:宿舍区) */
|
||||
type: string;
|
||||
/** 是否禁用选择 */
|
||||
disabled: boolean;
|
||||
/** 子节点 */
|
||||
children?: TreeSelectVo[];
|
||||
}
|
||||
|
||||
|
||||
|
||||
File renamed without changes.
File renamed without changes.
@@ -0,0 +1,264 @@
|
||||
import request from '@/utils/request';
|
||||
import { DormQuery, RoomForm, BedForm, DormTreeNode, RoomInfo, BedInfo } from './types';
|
||||
|
||||
// API前缀
|
||||
const baseUrl = '/xsssfjgl';
|
||||
|
||||
/**
|
||||
* 获取宿舍楼树形结构
|
||||
*/
|
||||
export function getDormTree() {
|
||||
return request({
|
||||
url: '/xgss/dormBuildingTree/list',
|
||||
method: 'get'
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取房间列表
|
||||
* @param query 查询条件
|
||||
* @returns 房间列表
|
||||
*/
|
||||
export function listRooms(query: DormQuery) {
|
||||
return request({
|
||||
url: baseUrl + '/room/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取房间详情
|
||||
* @param roomId 房间ID
|
||||
* @returns 房间详情
|
||||
*/
|
||||
export function getRoom(roomId: string | number) {
|
||||
return request({
|
||||
url: baseUrl + '/room/' + roomId,
|
||||
method: 'get'
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增房间
|
||||
* @param data 房间信息
|
||||
* @returns 结果
|
||||
*/
|
||||
export function addRoom(data: RoomForm) {
|
||||
return request({
|
||||
url: baseUrl + '/room',
|
||||
method: 'post',
|
||||
data: data
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改房间
|
||||
* @param data 房间信息
|
||||
* @returns 结果
|
||||
*/
|
||||
export function updateRoom(data: RoomForm) {
|
||||
return request({
|
||||
url: baseUrl + '/room',
|
||||
method: 'put',
|
||||
data: data
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除房间
|
||||
* @param roomIds 房间ID字符串
|
||||
* @returns 结果
|
||||
*/
|
||||
export function delRoom(roomIds: string | number) {
|
||||
return request({
|
||||
url: baseUrl + '/room/' + roomIds,
|
||||
method: 'delete'
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取床位列表
|
||||
* @param roomId 房间ID
|
||||
* @returns 床位列表
|
||||
*/
|
||||
export function listBeds(roomId: string | number) {
|
||||
return request({
|
||||
url: baseUrl + '/bed/list',
|
||||
method: 'get',
|
||||
params: { roomId }
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增床位
|
||||
* @param data 床位信息
|
||||
* @returns 结果
|
||||
*/
|
||||
export function addBed(data: BedForm) {
|
||||
return request({
|
||||
url: baseUrl + '/bed',
|
||||
method: 'post',
|
||||
data: data
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改床位
|
||||
* @param data 床位信息
|
||||
* @returns 结果
|
||||
*/
|
||||
export function updateBed(data: BedForm) {
|
||||
return request({
|
||||
url: baseUrl + '/bed',
|
||||
method: 'put',
|
||||
data: data
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除床位
|
||||
* @param bedIds 床位ID字符串
|
||||
* @returns 结果
|
||||
*/
|
||||
export function delBed(bedIds: string | number) {
|
||||
return request({
|
||||
url: baseUrl + '/bed/' + bedIds,
|
||||
method: 'delete'
|
||||
});
|
||||
}
|
||||
|
||||
// 模拟数据 - 在API接口完成前使用
|
||||
// 生成宿舍树形结构的模拟数据
|
||||
export function getMockDormTree(): { data: DormTreeNode[] } {
|
||||
const mockData: DormTreeNode[] = [
|
||||
{
|
||||
id: 1,
|
||||
label: '东校区',
|
||||
type: 'campus',
|
||||
disabled: true,
|
||||
children: [
|
||||
{
|
||||
id: 11,
|
||||
label: '东区宿舍',
|
||||
type: 'area',
|
||||
disabled: true,
|
||||
children: [
|
||||
{
|
||||
id: 111,
|
||||
label: '东区1号楼',
|
||||
type: 'building',
|
||||
buildingFloors: 6,
|
||||
gender: '1' // 男生楼
|
||||
},
|
||||
{
|
||||
id: 112,
|
||||
label: '东区2号楼',
|
||||
type: 'building',
|
||||
buildingFloors: 8,
|
||||
gender: '2' // 女生楼
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
label: '西校区',
|
||||
type: 'campus',
|
||||
disabled: true,
|
||||
children: [
|
||||
{
|
||||
id: 21,
|
||||
label: '西区宿舍',
|
||||
type: 'area',
|
||||
disabled: true,
|
||||
children: [
|
||||
{
|
||||
id: 211,
|
||||
label: '西区1号楼',
|
||||
type: 'building',
|
||||
buildingFloors: 7,
|
||||
gender: '2' // 女生楼
|
||||
},
|
||||
{
|
||||
id: 212,
|
||||
label: '西区2号楼',
|
||||
type: 'building',
|
||||
buildingFloors: 5,
|
||||
gender: '1' // 男生楼
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
return { data: mockData };
|
||||
}
|
||||
|
||||
// 生成房间数据的模拟数据
|
||||
export function getMockRooms(buildingId: string | number, floorNumber?: number): { rows: RoomInfo[], total: number } {
|
||||
const rooms: RoomInfo[] = [];
|
||||
|
||||
// 根据buildingId判断楼层数
|
||||
let floors = 6;
|
||||
if (buildingId === 112) floors = 8;
|
||||
else if (buildingId === 211) floors = 7;
|
||||
else if (buildingId === 212) floors = 5;
|
||||
|
||||
// 如果指定了楼层,只生成该楼层的数据
|
||||
const startFloor = floorNumber || 1;
|
||||
const endFloor = floorNumber || floors;
|
||||
|
||||
for (let floor = startFloor; floor <= endFloor; floor++) {
|
||||
for (let i = 1; i <= 10; i++) {
|
||||
const roomNumber = `${floor}${i.toString().padStart(2, '0')}`;
|
||||
rooms.push({
|
||||
roomId: `${buildingId}${roomNumber}`,
|
||||
roomName: roomNumber,
|
||||
floorNumber: floor,
|
||||
buildingId: buildingId,
|
||||
capacity: 4,
|
||||
used: Math.floor(Math.random() * 5),
|
||||
roomType: '标准间',
|
||||
status: Math.random() > 0.9 ? '1' : '0',
|
||||
remark: '',
|
||||
createBy: 'admin',
|
||||
createTime: '2023-01-01 00:00:00',
|
||||
updateBy: 'admin',
|
||||
updateTime: '2023-01-01 00:00:00'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
rows: rooms.filter(room => !floorNumber || room.floorNumber === floorNumber),
|
||||
total: rooms.length
|
||||
};
|
||||
}
|
||||
|
||||
// 生成床位数据的模拟数据
|
||||
export function getMockBeds(roomId: string | number): { data: BedInfo[] } {
|
||||
const beds: BedInfo[] = [];
|
||||
const capacity = 4;
|
||||
|
||||
for (let i = 1; i <= capacity; i++) {
|
||||
const status = Math.random() > 0.6 ? '1' : '0';
|
||||
beds.push({
|
||||
bedId: `${roomId}-${i}`,
|
||||
bedName: `${i}号床`,
|
||||
roomId: roomId,
|
||||
status: status,
|
||||
studentId: status === '1' ? 10000 + Math.floor(Math.random() * 5000) : undefined,
|
||||
studentName: status === '1' ? `学生${Math.floor(Math.random() * 1000)}` : undefined,
|
||||
remark: '',
|
||||
createBy: 'admin',
|
||||
createTime: '2023-01-01 00:00:00',
|
||||
updateBy: 'admin',
|
||||
updateTime: '2023-01-01 00:00:00'
|
||||
});
|
||||
}
|
||||
|
||||
return { data: beds };
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
// 基础字段类型
|
||||
export interface BaseEntity {
|
||||
createBy: number | string;
|
||||
createTime: string;
|
||||
updateBy: number | string;
|
||||
updateTime: string;
|
||||
}
|
||||
|
||||
// 分页查询参数
|
||||
export interface PageQuery {
|
||||
pageNum: number;
|
||||
pageSize: number;
|
||||
}
|
||||
|
||||
// 树形数据节点类型
|
||||
export interface DormTreeNode {
|
||||
id: string | number;
|
||||
label: string;
|
||||
children?: DormTreeNode[];
|
||||
type: 'campus' | 'area' | 'building'; // 节点类型:校区、宿舍区、宿舍楼
|
||||
disabled?: boolean; // 是否禁用选择
|
||||
buildingFloors?: number; // 宿舍楼层数
|
||||
gender?: string; // 宿舍楼性别: 1-男生楼, 2-女生楼
|
||||
}
|
||||
|
||||
// 房间信息
|
||||
export interface RoomInfo extends BaseEntity {
|
||||
roomId: string | number;
|
||||
roomCode: string;
|
||||
roomName: string;
|
||||
floorNum: number;
|
||||
buildingId: string | number;
|
||||
capacity: number;
|
||||
used: number;
|
||||
roomArea: number;
|
||||
roomType: string;
|
||||
status: string; // 0-正常 1-维修中 2-停用
|
||||
remark: string;
|
||||
}
|
||||
|
||||
// 床位信息
|
||||
export interface BedInfo extends BaseEntity {
|
||||
bedId: string | number;
|
||||
bedName: string;
|
||||
roomId: string | number;
|
||||
status: string; // 0-空闲 1-已分配 2-维修中
|
||||
studentId?: string | number;
|
||||
studentName?: string;
|
||||
remark: string;
|
||||
}
|
||||
|
||||
// 查询参数
|
||||
export interface DormQuery extends PageQuery {
|
||||
buildingId?: string | number;
|
||||
roomName?: string;
|
||||
floorNum?: number;
|
||||
status?: string;
|
||||
}
|
||||
|
||||
// 房间表单
|
||||
export interface RoomForm {
|
||||
roomId?: string | number;
|
||||
roomCode: string;
|
||||
roomName: string;
|
||||
floorNum: number;
|
||||
buildingId: string | number;
|
||||
capacity: number;
|
||||
roomArea: number;
|
||||
roomType: string;
|
||||
status: string;
|
||||
remark?: string;
|
||||
}
|
||||
|
||||
// 床位表单
|
||||
export interface BedForm {
|
||||
id?: string | number;
|
||||
bedId?: string | number;
|
||||
bedCode: string;
|
||||
bedName: string;
|
||||
roomId: string | number;
|
||||
bedType: string;
|
||||
bedStatus: string;
|
||||
remark?: string;
|
||||
}
|
||||
File renamed without changes.
@@ -50,6 +50,23 @@ export const updateEnrollmentInfo = (data: EnrollmentInfoForm) => {
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 更新招生信息状态并同步到流程管理
|
||||
* @param id 招生信息ID(单个ID或ID数组)
|
||||
* @param status 新状态
|
||||
*/
|
||||
export const updateEnrollmentStatus = (id: string | number | Array<string | number>, status: string) => {
|
||||
const data = Array.isArray(id)
|
||||
? { ids: id, status } // 批量更新
|
||||
: { id, status }; // 单个更新
|
||||
|
||||
return request({
|
||||
url: '/zsjy/enrollment/info/updateStatus',
|
||||
method: 'post',
|
||||
data
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 删除招生报名信息
|
||||
* @param id
|
||||
@@ -121,4 +138,4 @@ export const queryMyEnrollmentStatus = (params: { idCard?: string, examNumber?:
|
||||
method: 'get',
|
||||
params: params
|
||||
});
|
||||
};
|
||||
};
|
||||
@@ -295,7 +295,16 @@ export interface EnrollmentProcessQuery extends PageQuery {
|
||||
// 查询录取流程状态列表
|
||||
export const listEnrollmentProcess = (query: EnrollmentProcessQuery): AxiosPromise<EnrollmentProcessVO[]> => {
|
||||
return request({
|
||||
url: '/zsjy/enrollmentProcess/list',
|
||||
url: '/zsjy/enrollment/process/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
|
||||
// 查询我的待审核列表
|
||||
export const listMyAuditProcess = (query: EnrollmentProcessQuery): AxiosPromise<EnrollmentProcessVO[]> => {
|
||||
return request({
|
||||
url: '/zsjy/enrollment/process/myAudit',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
@@ -304,7 +313,7 @@ export const listEnrollmentProcess = (query: EnrollmentProcessQuery): AxiosPromi
|
||||
// 查询录取流程状态详细
|
||||
export const getEnrollmentProcess = (id: string | number): AxiosPromise<EnrollmentProcessVO> => {
|
||||
return request({
|
||||
url: '/zsjy/enrollmentProcess/' + id,
|
||||
url: '/zsjy/enrollment/process/' + id,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
@@ -312,16 +321,34 @@ export const getEnrollmentProcess = (id: string | number): AxiosPromise<Enrollme
|
||||
// 新增录取流程状态
|
||||
export const addEnrollmentProcess = (data: EnrollmentProcessForm) => {
|
||||
return request({
|
||||
url: '/zsjy/enrollmentProcess',
|
||||
url: '/zsjy/enrollment/process',
|
||||
method: 'post',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
// 从报名信息创建流程
|
||||
export const createProcessFromEnrollment = (enrollmentId: number) => {
|
||||
return request({
|
||||
url: '/zsjy/enrollment/process/createFromEnrollment',
|
||||
method: 'post',
|
||||
params: { enrollmentId }
|
||||
})
|
||||
}
|
||||
|
||||
// 同步流程状态
|
||||
export const syncProcessStatus = (id: string | number) => {
|
||||
return request({
|
||||
url: '/zsjy/enrollment/process/syncStatus',
|
||||
method: 'post',
|
||||
params: { id }
|
||||
})
|
||||
}
|
||||
|
||||
// 修改录取流程状态
|
||||
export const updateEnrollmentProcess = (data: EnrollmentProcessForm) => {
|
||||
return request({
|
||||
url: '/zsjy/enrollmentProcess',
|
||||
url: '/zsjy/enrollment/process',
|
||||
method: 'put',
|
||||
data: data
|
||||
})
|
||||
@@ -330,7 +357,7 @@ export const updateEnrollmentProcess = (data: EnrollmentProcessForm) => {
|
||||
// 删除录取流程状态
|
||||
export const delEnrollmentProcess = (id: string | number | Array<string | number>) => {
|
||||
return request({
|
||||
url: '/zsjy/enrollmentProcess/' + id,
|
||||
url: '/zsjy/enrollment/process/' + id,
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
||||
@@ -338,117 +365,130 @@ export const delEnrollmentProcess = (id: string | number | Array<string | number
|
||||
// 根据报名信息ID查询流程状态
|
||||
export const getProcessByEnrollmentId = (enrollmentId: number): AxiosPromise<EnrollmentProcessVO> => {
|
||||
return request({
|
||||
url: '/zsjy/enrollmentProcess/enrollment/' + enrollmentId,
|
||||
url: '/zsjy/enrollment/process/enrollment/' + enrollmentId,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
// 查询我的待处理流程
|
||||
export const getPendingProcessList = (): AxiosPromise<EnrollmentProcessVO[]> => {
|
||||
// 查询待处理的流程列表
|
||||
export const getPendingProcessList = (query: EnrollmentProcessQuery): AxiosPromise<EnrollmentProcessVO[]> => {
|
||||
return request({
|
||||
url: '/zsjy/enrollmentProcess/pending',
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
// 根据流程状态查询流程列表
|
||||
export const getProcessByStatus = (status: string): AxiosPromise<EnrollmentProcessVO[]> => {
|
||||
return request({
|
||||
url: '/zsjy/enrollmentProcess/status/' + status,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
// 统计各状态的流程数量
|
||||
export const getProcessStatistics = (): AxiosPromise<any[]> => {
|
||||
return request({
|
||||
url: '/zsjy/enrollmentProcess/statistics',
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
// 根据关键词搜索流程
|
||||
export const searchProcess = (keyword: string): AxiosPromise<EnrollmentProcessVO[]> => {
|
||||
return request({
|
||||
url: '/zsjy/enrollmentProcess/search',
|
||||
url: '/zsjy/enrollment/process/pending',
|
||||
method: 'get',
|
||||
params: { keyword }
|
||||
params: query
|
||||
})
|
||||
}
|
||||
|
||||
// 根据状态查询流程列表
|
||||
export const getProcessListByStatus = (status: string, query: EnrollmentProcessQuery): AxiosPromise<EnrollmentProcessVO[]> => {
|
||||
return request({
|
||||
url: '/zsjy/enrollment/process/status/' + status,
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
|
||||
// 获取流程统计数据
|
||||
export const getProcessStatistics = (): AxiosPromise<any> => {
|
||||
return request({
|
||||
url: '/zsjy/enrollment/process/statistics',
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
// 搜索流程
|
||||
export const searchProcess = (keyword: string, query: EnrollmentProcessQuery): AxiosPromise<EnrollmentProcessVO[]> => {
|
||||
return request({
|
||||
url: '/zsjy/enrollment/process/search',
|
||||
method: 'get',
|
||||
params: { keyword, ...query }
|
||||
})
|
||||
}
|
||||
|
||||
// 信息审核
|
||||
export const infoAudit = (data: { id: number; auditResult: string; auditOpinion?: string }) => {
|
||||
export const infoAudit = (data: {
|
||||
processId: number;
|
||||
auditResult: string;
|
||||
auditOpinion?: string;
|
||||
}) => {
|
||||
return request({
|
||||
url: '/zsjy/enrollmentProcess/infoAudit',
|
||||
url: '/zsjy/enrollment/process/infoAudit',
|
||||
method: 'post',
|
||||
data: data
|
||||
params: data
|
||||
})
|
||||
}
|
||||
|
||||
// 录取处理
|
||||
export const admissionProcess = (data: {
|
||||
id: number;
|
||||
processResult: string;
|
||||
processOpinion?: string;
|
||||
noticeNumber?: string
|
||||
export const admissionProcess = (data: {
|
||||
processId: number;
|
||||
admissionResult: string;
|
||||
admissionOpinion?: string;
|
||||
noticeNumber?: string;
|
||||
}) => {
|
||||
return request({
|
||||
url: '/zsjy/enrollmentProcess/admissionProcess',
|
||||
url: '/zsjy/enrollment/process/admissionProcess',
|
||||
method: 'post',
|
||||
data: data
|
||||
params: data
|
||||
})
|
||||
}
|
||||
|
||||
// 缴费确认
|
||||
export const confirmPayment = (id: number) => {
|
||||
// 确认缴费
|
||||
export const confirmPayment = (processId: number) => {
|
||||
return request({
|
||||
url: '/zsjy/enrollmentProcess/confirmPayment',
|
||||
url: '/zsjy/enrollment/process/confirmPayment',
|
||||
method: 'post',
|
||||
data: { id }
|
||||
params: { processId }
|
||||
})
|
||||
}
|
||||
|
||||
// 最终审核
|
||||
export const finalAudit = (data: { id: number; auditResult: string; auditOpinion?: string }) => {
|
||||
export const finalAudit = (data: {
|
||||
processId: number;
|
||||
finalResult: string;
|
||||
finalOpinion?: string;
|
||||
}) => {
|
||||
return request({
|
||||
url: '/zsjy/enrollmentProcess/finalAudit',
|
||||
url: '/zsjy/enrollment/process/finalConfirm',
|
||||
method: 'post',
|
||||
data: data
|
||||
params: data
|
||||
})
|
||||
}
|
||||
|
||||
// 完成录取
|
||||
export const completeEnrollment = (id: number) => {
|
||||
// 完成流程
|
||||
export const completeProcess = (processId: number) => {
|
||||
return request({
|
||||
url: '/zsjy/enrollmentProcess/complete',
|
||||
url: '/zsjy/enrollment/process/complete',
|
||||
method: 'post',
|
||||
data: { id }
|
||||
params: { processId }
|
||||
})
|
||||
}
|
||||
|
||||
// 驳回流程
|
||||
export const rejectProcess = (data: { id: number; rejectReason: string }) => {
|
||||
export const rejectProcess = (data: {
|
||||
processId: number;
|
||||
rejectReason: string;
|
||||
}) => {
|
||||
return request({
|
||||
url: '/zsjy/enrollmentProcess/reject',
|
||||
url: '/zsjy/enrollment/process/reject',
|
||||
method: 'post',
|
||||
data: data
|
||||
params: data
|
||||
})
|
||||
}
|
||||
|
||||
// 重置流程状态
|
||||
export const resetProcess = (id: number) => {
|
||||
// 重置流程
|
||||
export const resetProcess = (processId: number) => {
|
||||
return request({
|
||||
url: '/zsjy/enrollmentProcess/reset',
|
||||
url: '/zsjy/enrollment/process/reset',
|
||||
method: 'post',
|
||||
data: { id }
|
||||
params: { processId }
|
||||
})
|
||||
}
|
||||
|
||||
// 导出录取流程状态
|
||||
export const exportEnrollmentProcess = (query: EnrollmentProcessQuery) => {
|
||||
return request({
|
||||
url: '/zsjy/enrollmentProcess/export',
|
||||
url: '/zsjy/enrollment/process/export',
|
||||
method: 'post',
|
||||
data: query
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
import request from '@/utils/request';
|
||||
|
||||
/**
|
||||
* 获取统计概览数据
|
||||
*/
|
||||
export function getStatisticsOverview() {
|
||||
return request({
|
||||
url: '/dev-api/zsjy/statistics/overview',
|
||||
method: 'get'
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取专业分布统计
|
||||
*/
|
||||
export function getMajorDistribution() {
|
||||
return request({
|
||||
url: '/dev-api/zsjy/statistics/major',
|
||||
method: 'get'
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取地区分布统计
|
||||
*/
|
||||
export function getRegionDistribution() {
|
||||
return request({
|
||||
url: '/dev-api/zsjy/statistics/region',
|
||||
method: 'get'
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取性别比例统计
|
||||
*/
|
||||
export function getGenderRatio() {
|
||||
return request({
|
||||
url: '/dev-api/zsjy/statistics/gender',
|
||||
method: 'get'
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取报名趋势统计
|
||||
* @param {number} days 天数,默认为7天
|
||||
*/
|
||||
export function getEnrollmentTrend(days?: number) {
|
||||
return request({
|
||||
url: '/dev-api/zsjy/statistics/trend',
|
||||
method: 'get',
|
||||
params: { days }
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取状态分布统计
|
||||
*/
|
||||
export function getStatusDistribution() {
|
||||
return request({
|
||||
url: '/dev-api/zsjy/statistics/status',
|
||||
method: 'get'
|
||||
});
|
||||
}
|
||||
|
||||
// 导出接口类型定义
|
||||
export interface StatisticsOverview {
|
||||
totalEnrollments: number;
|
||||
todayEnrollments: number;
|
||||
paidCount: number;
|
||||
paymentRate: string;
|
||||
admittedCount: number;
|
||||
admissionRate: string;
|
||||
majorDistribution: Array<{[key: string]: any}>;
|
||||
regionDistribution: Array<{[key: string]: any}>;
|
||||
genderRatio: {[key: string]: number};
|
||||
weeklyTrend: Array<{[key: string]: any}>;
|
||||
statusDistribution: {[key: string]: number};
|
||||
}
|
||||
@@ -99,4 +99,8 @@ onMounted(() => {
|
||||
.show-btn {
|
||||
margin-left: 12px;
|
||||
}
|
||||
.top-right-btn {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
</style>
|
||||
+12
-18
@@ -1,20 +1,14 @@
|
||||
import * as ElementPlusIconsVue from '@element-plus/icons-vue';
|
||||
import { App } from 'vue';
|
||||
import { App } from 'vue'
|
||||
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
|
||||
|
||||
export default {
|
||||
install: (app: App) => {
|
||||
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
|
||||
app.component(key, component);
|
||||
}
|
||||
|
||||
// 确保Close图标被正确注册(用于Dialog关闭按钮)
|
||||
if (ElementPlusIconsVue.Close) {
|
||||
app.component('Close', ElementPlusIconsVue.Close);
|
||||
}
|
||||
|
||||
// 备用:如果Close不存在,使用CircleClose
|
||||
if (ElementPlusIconsVue.CircleClose && !ElementPlusIconsVue.Close) {
|
||||
app.component('Close', ElementPlusIconsVue.CircleClose);
|
||||
}
|
||||
export default function registerIcons(app: App) {
|
||||
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
|
||||
app.component(key, component)
|
||||
}
|
||||
};
|
||||
|
||||
// 移除重复的Close组件注册
|
||||
// app.component('Close', ElementPlusIconsVue.Close);
|
||||
|
||||
// 注册常用图标的别名
|
||||
app.component('CircleCloseIcon', ElementPlusIconsVue.CircleClose);
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -46,7 +46,7 @@
|
||||
:tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
|
||||
:default-expand-all="isExpandAll"
|
||||
>
|
||||
<el-table-column prop="deptName" label="部门名称" width="260"></el-table-column>
|
||||
<el-table-column prop="deptName" label="部门名称" width="400"></el-table-column>
|
||||
<el-table-column prop="deptCategory" align="center" label="类别编码" width="200"></el-table-column>
|
||||
<el-table-column prop="orderNum" align="center" label="排序" width="200"></el-table-column>
|
||||
<el-table-column prop="status" align="center" label="状态" width="100">
|
||||
|
||||
@@ -57,8 +57,15 @@
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="班主任" prop="classTeacherName">
|
||||
<el-input v-model="queryParams.classTeacherName" placeholder="请输入班主任姓名" clearable @keyup.enter="handleQuery" />
|
||||
<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-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
@@ -277,7 +284,7 @@
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="专业" prop="majorCode">
|
||||
<el-select v-model="form.majorCode" placeholder="请选择专业" style="width: 100%">
|
||||
<el-select v-model="form.majorCode" placeholder="请选择专业" style="width: 100%" @change="handleMajorChange">
|
||||
<el-option v-for="option in formMajorOptions" :key="option.value" :label="option.label" :value="option.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
@@ -325,7 +332,9 @@
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="班主任" prop="classTeacherName">
|
||||
<el-input v-model="form.classTeacherName" placeholder="请输入班主任姓名" maxlength="50" />
|
||||
<el-select v-model="form.classTeacherId" placeholder="请选择班主任" style="width: 100%" @change="handleClassTeacherChange">
|
||||
<el-option v-for="option in classTeacherOptions" :key="option.value" :label="option.label" :value="option.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
@@ -464,7 +473,9 @@ import {
|
||||
getMajorOptions,
|
||||
getCounselorOptions,
|
||||
getClassTeacherOptions,
|
||||
getClassStatistics // 新增统计接口导入
|
||||
getClassStatistics, // 新增统计接口导入
|
||||
getDeptCollegeOptions, // 新增从系统部门获取学院
|
||||
getDeptMajorOptions // 新增从系统部门获取专业
|
||||
} from '@/api/xg-xx/classInfo';
|
||||
import { ClassInfoVO, ClassInfoQuery, ClassInfoForm, ClassStatisticsData, OptionData } from '@/api/xg-xx/classInfo/types';
|
||||
|
||||
@@ -597,7 +608,7 @@ const data = reactive<PageData<ClassInfoForm, ClassInfoQuery>>({
|
||||
grade: undefined,
|
||||
collegeCode: undefined,
|
||||
majorCode: undefined,
|
||||
classTeacherName: undefined,
|
||||
classTeacherId: undefined,
|
||||
classStatus: undefined
|
||||
},
|
||||
rules: {
|
||||
@@ -670,30 +681,104 @@ const getList = async () => {
|
||||
/** 获取选项数据 */
|
||||
const getOptions = async () => {
|
||||
try {
|
||||
// 并行获取所有选项数据和统计数据
|
||||
const [collegeRes, counselorRes, classTeacherRes, statisticsRes] = await Promise.all([
|
||||
getCollegeOptions(),
|
||||
getCounselorOptions(),
|
||||
getClassTeacherOptions(),
|
||||
getClassStatistics() // 获取统计数据
|
||||
]);
|
||||
// 1. 先单独获取部门数据,彻底避免Promise.all可能的参数串扰
|
||||
console.log('单独获取部门数据');
|
||||
try {
|
||||
// 调用部门API获取学院数据
|
||||
const collegeRes = await getDeptCollegeOptions();
|
||||
console.log('学院API响应:', collegeRes);
|
||||
|
||||
// 设置选项数据
|
||||
collegeOptions.value = collegeRes.data || [];
|
||||
counselorOptions.value = counselorRes.data || [];
|
||||
classTeacherOptions.value = classTeacherRes.data || [];
|
||||
// 设置学院选项数据(将部门树转换为选项格式)
|
||||
if (collegeRes.data && Array.isArray(collegeRes.data)) {
|
||||
// 找到所有二级学院(一级部门的子部门)
|
||||
const rootDepts = collegeRes.data || [];
|
||||
const secondLevelDepts: OptionData[] = [];
|
||||
|
||||
// 设置统计数据
|
||||
if (statisticsRes.data) {
|
||||
statistics.value = {
|
||||
totalCount: statisticsRes.data.totalCount || 0,
|
||||
activeCount: statisticsRes.data.activeCount || 0,
|
||||
graduatedCount: statisticsRes.data.graduatedCount || 0,
|
||||
suspendedCount: statisticsRes.data.pausedCount || 0,
|
||||
dissolvedCount: 0, // 可以后续添加
|
||||
totalStudentCount: statisticsRes.data.totalStudents || 0,
|
||||
averageClassSize: statisticsRes.data.totalCount > 0 ? Math.round((statisticsRes.data.totalStudents || 0) / statisticsRes.data.totalCount) : 0
|
||||
};
|
||||
console.log('部门树根节点数量:', rootDepts.length);
|
||||
|
||||
// 处理每个一级部门
|
||||
rootDepts.forEach(rootDept => {
|
||||
console.log(`处理一级部门: ${rootDept.label}, ID: ${rootDept.id}`);
|
||||
// 处理一级部门下的二级学院
|
||||
if (rootDept.children && rootDept.children.length > 0) {
|
||||
rootDept.children.forEach(child => {
|
||||
// 确保ID是有效的数字
|
||||
const childId = child.id;
|
||||
if (childId && !isNaN(Number(childId))) {
|
||||
console.log(`添加二级学院: ${child.label}, ID: ${childId}, 数字ID: ${Number(childId)}`);
|
||||
secondLevelDepts.push({
|
||||
value: Number(childId), // 确保使用数字类型
|
||||
label: child.label,
|
||||
// 保存原始数据,方便后续使用
|
||||
deptId: Number(childId),
|
||||
parentId: Number(rootDept.id)
|
||||
});
|
||||
} else {
|
||||
console.warn(`跳过无效ID的学院: ${child.label}, ID: ${childId}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
collegeOptions.value = secondLevelDepts;
|
||||
console.log('设置学院选项:', collegeOptions.value);
|
||||
}
|
||||
} catch (collegeError) {
|
||||
console.error('获取学院数据失败:', collegeError);
|
||||
// 如果获取失败,使用空数组
|
||||
collegeOptions.value = [];
|
||||
}
|
||||
|
||||
// 2. 然后获取其他数据
|
||||
console.log('获取其他选项数据');
|
||||
try {
|
||||
// 获取班主任选项
|
||||
const counselorRes = await getClassTeacherOptions();
|
||||
console.log('班主任API响应:', counselorRes);
|
||||
|
||||
// 处理班主任数据
|
||||
if (counselorRes.data?.data) {
|
||||
// 标准成功响应
|
||||
console.log('标准成功响应,data字段类型:', typeof counselorRes.data.data);
|
||||
classTeacherOptions.value = counselorRes.data.data || [];
|
||||
} else if (Array.isArray(counselorRes.data)) {
|
||||
// 直接返回数组
|
||||
console.log('直接返回数组,长度:', counselorRes.data.length);
|
||||
classTeacherOptions.value = counselorRes.data;
|
||||
} else if (counselorRes.data && Array.isArray(counselorRes.data.rows)) {
|
||||
// 分页响应格式
|
||||
console.log('分页响应格式,rows字段长度:', counselorRes.data.rows.length);
|
||||
classTeacherOptions.value = counselorRes.data.rows;
|
||||
} else {
|
||||
// 其他格式,尝试直接使用
|
||||
console.log('其他格式,直接使用response.data');
|
||||
classTeacherOptions.value = counselorRes.data || [];
|
||||
}
|
||||
|
||||
console.log('设置班主任选项:', classTeacherOptions.value);
|
||||
} catch (counselorError) {
|
||||
console.error('获取班主任数据失败:', counselorError);
|
||||
classTeacherOptions.value = [];
|
||||
}
|
||||
|
||||
// 3. 获取统计数据
|
||||
try {
|
||||
const statisticsRes = await getClassStatistics();
|
||||
// 设置统计数据
|
||||
const statsData = statisticsRes.data?.data;
|
||||
if (statsData) {
|
||||
statistics.value = {
|
||||
totalCount: statsData.totalCount || 0,
|
||||
activeCount: statsData.activeCount || 0,
|
||||
graduatedCount: statsData.graduatedCount || 0,
|
||||
suspendedCount: statsData.pausedCount || 0,
|
||||
dissolvedCount: 0, // 可以后续添加
|
||||
totalStudentCount: statsData.totalStudents || 0,
|
||||
averageClassSize: statsData.totalCount > 0 ? Math.round((statsData.totalStudents || 0) / statsData.totalCount) : 0
|
||||
};
|
||||
}
|
||||
} catch (statsError) {
|
||||
console.error('获取统计数据失败:', statsError);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取选项数据失败:', error);
|
||||
@@ -702,38 +787,121 @@ const getOptions = async () => {
|
||||
};
|
||||
|
||||
/** 学院变更处理 */
|
||||
const handleCollegeChange = async (collegeCode: string) => {
|
||||
const handleCollegeChange = async (deptId: string | number) => {
|
||||
// 重置专业选择
|
||||
queryParams.value.majorCode = undefined;
|
||||
if (collegeCode) {
|
||||
|
||||
// 调试输出
|
||||
console.log('学院变更,部门ID:', deptId, '类型:', typeof deptId);
|
||||
|
||||
// 验证deptId是有效的数字
|
||||
if (deptId && !isNaN(Number(deptId))) {
|
||||
try {
|
||||
const res = await getMajorOptions(collegeCode);
|
||||
majorOptions.value = res.data || [];
|
||||
// 获取子部门作为专业选项
|
||||
const numericDeptId = Number(deptId);
|
||||
console.log('准备获取专业数据,使用数字ID:', numericDeptId);
|
||||
const res = await getDeptMajorOptions(numericDeptId);
|
||||
|
||||
// 处理响应数据
|
||||
if (res.data && Array.isArray(res.data)) {
|
||||
// 转换为选项格式
|
||||
const deptMajors = res.data.map(dept => {
|
||||
// 确保使用数字类型的ID
|
||||
const majorId = Number(dept.deptId);
|
||||
console.log(`专业选项: ${dept.deptName}, ID: ${majorId}`);
|
||||
|
||||
return {
|
||||
value: majorId,
|
||||
label: dept.deptName,
|
||||
deptId: majorId,
|
||||
parentId: numericDeptId
|
||||
};
|
||||
});
|
||||
|
||||
// 设置专业选项
|
||||
majorOptions.value = deptMajors;
|
||||
console.log('设置专业选项:', majorOptions.value);
|
||||
} else {
|
||||
console.warn('专业数据不是数组格式:', res.data);
|
||||
majorOptions.value = [];
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取专业选项失败:', error);
|
||||
majorOptions.value = [];
|
||||
proxy?.$modal.msgError('获取专业选项失败');
|
||||
}
|
||||
} else {
|
||||
console.warn('无效的学院/部门ID:', deptId);
|
||||
majorOptions.value = [];
|
||||
}
|
||||
};
|
||||
|
||||
/** 表单学院变更处理 */
|
||||
const handleFormCollegeChange = async (collegeCode: string) => {
|
||||
const handleFormCollegeChange = async (deptId: string | number) => {
|
||||
// 重置专业选择和名称
|
||||
form.value.majorCode = undefined;
|
||||
form.value.majorName = undefined;
|
||||
if (collegeCode) {
|
||||
|
||||
// 调试输出
|
||||
console.log('表单学院变更,部门ID:', deptId, '类型:', typeof deptId);
|
||||
|
||||
// 验证deptId是有效的数字
|
||||
if (deptId && !isNaN(Number(deptId))) {
|
||||
try {
|
||||
const res = await getMajorOptions(collegeCode);
|
||||
formMajorOptions.value = res.data || [];
|
||||
// 获取子部门作为专业选项
|
||||
const numericDeptId = Number(deptId);
|
||||
console.log('准备获取专业数据,使用数字ID:', numericDeptId);
|
||||
const res = await getDeptMajorOptions(numericDeptId);
|
||||
|
||||
// 处理响应数据
|
||||
if (res.data && Array.isArray(res.data)) {
|
||||
// 转换为选项格式
|
||||
const deptMajors = res.data.map(dept => {
|
||||
// 确保使用数字类型的ID
|
||||
const majorId = Number(dept.deptId);
|
||||
console.log(`表单专业选项: ${dept.deptName}, ID: ${majorId}`);
|
||||
|
||||
return {
|
||||
value: majorId,
|
||||
label: dept.deptName,
|
||||
deptId: majorId,
|
||||
parentId: numericDeptId
|
||||
};
|
||||
});
|
||||
|
||||
// 设置表单专业选项
|
||||
formMajorOptions.value = deptMajors;
|
||||
console.log('设置表单专业选项:', formMajorOptions.value);
|
||||
} else {
|
||||
console.warn('专业数据不是数组格式:', res.data);
|
||||
formMajorOptions.value = [];
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取专业选项失败:', error);
|
||||
console.error('获取表单专业选项失败:', error);
|
||||
formMajorOptions.value = [];
|
||||
proxy?.$modal.msgError('获取专业选项失败');
|
||||
}
|
||||
} else {
|
||||
console.warn('无效的学院/部门ID:', deptId);
|
||||
formMajorOptions.value = [];
|
||||
}
|
||||
};
|
||||
|
||||
/** 专业变更处理 */
|
||||
const handleMajorChange = (majorCode: string | number) => {
|
||||
const selectedMajor = formMajorOptions.value.find(option => option.value === Number(majorCode));
|
||||
form.value.majorName = selectedMajor ? selectedMajor.label : '';
|
||||
console.log('专业变更,选择专业:', majorCode, '专业名称:', form.value.majorName);
|
||||
};
|
||||
|
||||
// 添加班主任选择变更处理函数
|
||||
/** 班主任变更处理 */
|
||||
const handleClassTeacherChange = (teacherId: string | number) => {
|
||||
form.value.classTeacherId = teacherId;
|
||||
const selectedTeacher = classTeacherOptions.value.find(option => option.value === teacherId);
|
||||
form.value.classTeacherName = selectedTeacher ? selectedTeacher.label : '';
|
||||
};
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
queryParams.value.pageNum = 1;
|
||||
@@ -745,6 +913,7 @@ const resetQuery = () => {
|
||||
queryFormRef.value?.resetFields();
|
||||
majorOptions.value = [];
|
||||
handleQuery();
|
||||
getClassTeacherOptions(); // 重置后重新获取班主任选项
|
||||
};
|
||||
|
||||
/** 多选框选中数据 */
|
||||
@@ -882,6 +1051,8 @@ const cancel = () => {
|
||||
/** 表单重置 */
|
||||
const reset = () => {
|
||||
form.value = { ...initFormData };
|
||||
form.value.classTeacherId = undefined; // 确保重置班主任ID
|
||||
form.value.classTeacherName = undefined; // 确保重置班主任名称
|
||||
formMajorOptions.value = [];
|
||||
classInfoFormRef.value?.resetFields();
|
||||
};
|
||||
@@ -890,7 +1061,86 @@ const reset = () => {
|
||||
onMounted(() => {
|
||||
getList();
|
||||
getOptions();
|
||||
getClassTeacherList(); // 获取班主任列表
|
||||
});
|
||||
|
||||
/** 获取班主任选项 */
|
||||
const getClassTeacherList = async () => {
|
||||
console.log('开始获取班主任列表');
|
||||
try {
|
||||
const response = await getClassTeacherOptions();
|
||||
console.log('获取班主任列表成功,响应数据:', response);
|
||||
|
||||
// 解析响应数据
|
||||
if (response.data && typeof response.data === 'object') {
|
||||
console.log('响应数据类型:', Object.prototype.toString.call(response.data));
|
||||
|
||||
if (response.data.code === 200 && response.data.data) {
|
||||
// 标准成功响应
|
||||
console.log('标准成功响应,data字段:', JSON.stringify(response.data.data));
|
||||
classTeacherOptions.value = response.data.data || [];
|
||||
} else if (Array.isArray(response.data)) {
|
||||
// 直接返回数组
|
||||
console.log('直接返回数组,长度:', response.data.length);
|
||||
if (response.data.length > 0) {
|
||||
console.log('数组第一个元素:', JSON.stringify(response.data[0]));
|
||||
}
|
||||
classTeacherOptions.value = response.data;
|
||||
} else if (response.data && Array.isArray(response.data.rows)) {
|
||||
// 分页响应格式
|
||||
console.log('分页响应格式,rows字段:', JSON.stringify(response.data.rows));
|
||||
classTeacherOptions.value = response.data.rows;
|
||||
} else {
|
||||
// 其他格式,尝试直接使用
|
||||
console.log('其他格式,直接使用response.data');
|
||||
classTeacherOptions.value = response.data || [];
|
||||
}
|
||||
} else {
|
||||
console.log('响应数据为空或不是对象类型');
|
||||
classTeacherOptions.value = [];
|
||||
}
|
||||
|
||||
console.log('设置后的班主任选项:', JSON.stringify(classTeacherOptions.value));
|
||||
|
||||
// 检查每个选项的格式是否正确
|
||||
if (classTeacherOptions.value && classTeacherOptions.value.length > 0) {
|
||||
console.log('选项数量:', classTeacherOptions.value.length);
|
||||
classTeacherOptions.value.forEach((option, index) => {
|
||||
console.log(`选项[${index}]:`, JSON.stringify(option));
|
||||
if (!option.value || !option.label) {
|
||||
console.warn(`选项[${index}]格式不正确:`, option);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 如果没有获取到数据,给出提示
|
||||
if (!classTeacherOptions.value.length) {
|
||||
proxy?.$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 = '认证失败,请重新登录';
|
||||
} else if (status === 403) {
|
||||
console.error('权限不足(403),无法访问班主任数据');
|
||||
errorMessage = '权限不足,无法获取班主任列表';
|
||||
} else if (data && data.msg) {
|
||||
errorMessage = errorMessage + ': ' + data.msg;
|
||||
}
|
||||
}
|
||||
|
||||
classTeacherOptions.value = [];
|
||||
proxy?.$modal.msgError(errorMessage);
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -122,6 +122,16 @@
|
||||
<el-option label="毕业" value="5" />
|
||||
</el-select>
|
||||
</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-select>
|
||||
</el-form-item>
|
||||
<div class="search-btn-container">
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
|
||||
@@ -377,7 +387,8 @@ import {
|
||||
getStudent,
|
||||
delStudent,
|
||||
exportStudents,
|
||||
getStatisticsOverview
|
||||
getStatisticsOverview,
|
||||
getClassTeacherOptions
|
||||
} from '@/api/xg-xx/student'
|
||||
import {
|
||||
getCollegeOptions,
|
||||
@@ -435,7 +446,8 @@ export default {
|
||||
classCode: null,
|
||||
grade: null,
|
||||
studentStatus: null,
|
||||
infoStatus: null
|
||||
infoStatus: null,
|
||||
classTeacherId: null
|
||||
},
|
||||
// 表单参数
|
||||
form: {},
|
||||
@@ -452,6 +464,7 @@ export default {
|
||||
collegeOptions: [],
|
||||
majorOptions: [],
|
||||
classOptions: [],
|
||||
classTeacherOptions: [],
|
||||
// 弹窗显示状态
|
||||
detailVisible: false,
|
||||
editVisible: false,
|
||||
@@ -470,6 +483,7 @@ export default {
|
||||
this.getList()
|
||||
this.getStatistics()
|
||||
this.getCollegeList()
|
||||
this.getClassTeacherOptions() // 调用API获取班主任数据
|
||||
},
|
||||
methods: {
|
||||
/** 查询学生列表 */
|
||||
@@ -543,6 +557,113 @@ export default {
|
||||
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)
|
||||
})
|
||||
},
|
||||
/** 获取班级列表 */
|
||||
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)
|
||||
})
|
||||
},
|
||||
/** 获取班主任选项 */
|
||||
getClassTeacherOptions() {
|
||||
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.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 (!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)
|
||||
@@ -550,21 +671,12 @@ export default {
|
||||
this.queryParams.classCode = null
|
||||
this.majorOptions = []
|
||||
this.classOptions = []
|
||||
this.queryParams.classTeacherId = null // 清空班主任选择
|
||||
|
||||
if (collegeId) {
|
||||
console.log('学院变化,获取专业列表:', collegeId)
|
||||
// 根据学院ID获取专业列表
|
||||
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)
|
||||
})
|
||||
this.getMajorList(collegeId)
|
||||
}
|
||||
},
|
||||
/** 专业变化处理 */
|
||||
@@ -572,21 +684,12 @@ export default {
|
||||
console.log('专业变化,清空班级选择:', majorId)
|
||||
this.queryParams.classCode = null
|
||||
this.classOptions = []
|
||||
this.queryParams.classTeacherId = null // 清空班主任选择
|
||||
|
||||
if (majorId) {
|
||||
console.log('专业变化,获取班级列表:', majorId)
|
||||
// 根据专业ID获取班级列表
|
||||
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)
|
||||
})
|
||||
this.getClassList(majorId)
|
||||
}
|
||||
},
|
||||
/** 搜索按钮操作 */
|
||||
@@ -608,11 +711,13 @@ export default {
|
||||
classCode: null,
|
||||
grade: null,
|
||||
studentStatus: null,
|
||||
infoStatus: null
|
||||
infoStatus: null,
|
||||
classTeacherId: null
|
||||
}
|
||||
// 重置级联选择器
|
||||
this.majorOptions = []
|
||||
this.classOptions = []
|
||||
this.classTeacherOptions = [] // 重置班主任选项
|
||||
this.handleQuery()
|
||||
},
|
||||
/** 多选框选中数据 */
|
||||
|
||||
+22
-6
@@ -86,6 +86,7 @@
|
||||
<el-table-column label="宿舍楼名称" align="center" prop="buildingName" />
|
||||
<el-table-column label="所属校区" align="center" prop="campusName" />
|
||||
<el-table-column label="所属宿舍区" align="center" prop="areaName" />
|
||||
<el-table-column label="总楼层数" align="center" prop="floors" />
|
||||
<el-table-column label="宿舍楼类型" align="center" prop="buildingType">
|
||||
<template #default="scope">
|
||||
<dict-tag :options="dorm_type" :value="scope.row.buildingType" />
|
||||
@@ -128,7 +129,18 @@
|
||||
<el-input v-model="form.buildingCode" placeholder="请输入宿舍楼编码" />
|
||||
</el-form-item>
|
||||
<el-form-item label="宿舍楼名称" prop="buildingName">
|
||||
<el-input v-model="form.buildingName" placeholder="请输入宿舍楼名称" />
|
||||
<el-input v-model="form.buildingName" placeholder="请输入宿舍楼名称" class="fixed-width-input" />
|
||||
</el-form-item>
|
||||
<el-form-item label="总楼层数" prop="floors">
|
||||
<el-input-number
|
||||
v-model="form.floors"
|
||||
:min="1"
|
||||
:max="99"
|
||||
:step="1"
|
||||
:default-value="6"
|
||||
placeholder="请输入总楼层数"
|
||||
class="fixed-width-input"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="宿舍楼类型" prop="buildingType">
|
||||
<el-select v-model="form.buildingType" placeholder="请选择宿舍楼类型">
|
||||
@@ -161,10 +173,10 @@ import {
|
||||
updateDormBuilding,
|
||||
listCampusOptions,
|
||||
listAreaTreeData
|
||||
} from '@/api/sslxx/dormBuilding';
|
||||
import { DormBuildingVO, DormBuildingQuery, DormBuildingForm } from '@/api/sslxx/dormBuilding/types';
|
||||
import { DormCampusVO } from '@/api/xqxx/dormCampus/types';
|
||||
import { DormAreaVO } from '@/api/ssqxx/dormArea/types';
|
||||
} from '@/api/xgss/sslxx/dormBuilding';
|
||||
import { DormBuildingVO, DormBuildingQuery, DormBuildingForm } from '@/api/xgss/sslxx/dormBuilding/types';
|
||||
import { DormCampusVO } from '@/api/xgss/xqxx/dormCampus/types';
|
||||
import { DormAreaVO } from '@/api/xgss/ssqxx/dormArea/types';
|
||||
import { getCurrentInstance, ComponentInternalInstance, ref, reactive, toRefs, onMounted } from 'vue';
|
||||
|
||||
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
||||
@@ -198,7 +210,8 @@ const initFormData: DormBuildingForm = {
|
||||
campusId: undefined,
|
||||
buildingType: undefined,
|
||||
buildingDescription: undefined,
|
||||
sortOrder: undefined
|
||||
sortOrder: undefined,
|
||||
floors: 6 // 添加默认值为6
|
||||
};
|
||||
const data = reactive<PageData<DormBuildingForm, DormBuildingQuery>>({
|
||||
form: { ...initFormData },
|
||||
@@ -537,4 +550,7 @@ onMounted(() => {
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
}
|
||||
.fixed-width-input {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
+4
-4
@@ -112,10 +112,10 @@
|
||||
</template>
|
||||
|
||||
<script setup name="DormArea" lang="ts">
|
||||
import { listDormArea, getDormArea, delDormArea, addDormArea, updateDormArea } from '@/api/ssqxx/dormArea';
|
||||
import { DormAreaVO, DormAreaQuery, DormAreaForm } from '@/api/ssqxx/dormArea/types';
|
||||
import { listDormCampus } from '@/api/xqxx/dormCampus';
|
||||
import type { DormCampusVO } from '@/api/xqxx/dormCampus/types';
|
||||
import { listDormArea, getDormArea, delDormArea, addDormArea, updateDormArea } from '@/api/xgss/ssqxx/dormArea';
|
||||
import { DormAreaVO, DormAreaQuery, DormAreaForm } from '@/api/xgss/ssqxx/dormArea/types';
|
||||
import { listDormCampus } from '@/api/xgss/xqxx/dormCampus';
|
||||
import type { DormCampusVO } from '@/api/xgss/xqxx/dormCampus/types';
|
||||
|
||||
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
||||
|
||||
+2
-2
@@ -94,8 +94,8 @@
|
||||
</template>
|
||||
|
||||
<script setup name="DormCampus" lang="ts">
|
||||
import { listDormCampus, getDormCampus, delDormCampus, addDormCampus, updateDormCampus } from '@/api/xqxx/dormCampus';
|
||||
import { DormCampusVO, DormCampusQuery, DormCampusForm } from '@/api/xqxx/dormCampus/types';
|
||||
import { listDormCampus, getDormCampus, delDormCampus, addDormCampus, updateDormCampus } from '@/api/xgss/xqxx/dormCampus';
|
||||
import { DormCampusVO, DormCampusQuery, DormCampusForm } from '@/api/xgss/xqxx/dormCampus/types';
|
||||
|
||||
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
||||
|
||||
@@ -0,0 +1,145 @@
|
||||
<template>
|
||||
<el-dialog v-model="visible" :title="title" width="500px" append-to-body @close="closeDialog">
|
||||
<el-form ref="formRef" :model="form" :rules="rules" label-width="80px">
|
||||
<el-form-item label="床位编号" prop="bedCode">
|
||||
<el-input v-model="form.bedCode" placeholder="请输入床位编号" maxlength="20" />
|
||||
</el-form-item>
|
||||
<el-form-item label="床位名称" prop="bedName">
|
||||
<el-input v-model="form.bedName" placeholder="请输入床位名称" maxlength="20" />
|
||||
</el-form-item>
|
||||
<el-form-item label="床位状态" prop="bedStatus">
|
||||
<el-radio-group v-model="form.bedStatus">
|
||||
<el-radio label="0">空闲</el-radio>
|
||||
<el-radio label="1" disabled>已分配</el-radio>
|
||||
<el-radio label="2">维修中</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="床位类型" prop="bedType">
|
||||
<el-radio-group v-model="form.bedType">
|
||||
<el-radio label="1">下铺</el-radio>
|
||||
<el-radio label="2">上铺</el-radio>
|
||||
<!-- <el-radio label="3">中铺</el-radio> -->
|
||||
<!-- <el-radio label="4">独立床</el-radio> -->
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="备注" prop="remark">
|
||||
<el-input v-model="form.remark" type="textarea" placeholder="请输入备注内容" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button type="primary" @click="submitForm">确 定</el-button>
|
||||
<el-button @click="visible = false">取 消</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, watch } from 'vue';
|
||||
import type { FormInstance, FormRules } from 'element-plus';
|
||||
import { DormBedForm } from '@/api/xgss/dormBed/types';
|
||||
import { addDormBed, updateDormBed, getDormBed } from '@/api/xgss/dormBed';
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue: boolean;
|
||||
title: string;
|
||||
bedId?: string | number;
|
||||
roomId: string | number;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits(['update:modelValue', 'success']);
|
||||
|
||||
const formRef = ref<FormInstance>();
|
||||
const visible = ref(false);
|
||||
|
||||
// 表单数据
|
||||
const initFormData: DormBedForm = {
|
||||
id: '',
|
||||
bedCode: '',
|
||||
bedName: '',
|
||||
roomId: '',
|
||||
bedStatus: '0',
|
||||
bedType: '1',
|
||||
remark: ''
|
||||
};
|
||||
|
||||
const form = reactive<DormBedForm>({ ...initFormData });
|
||||
|
||||
// 表单校验规则
|
||||
const rules = reactive<FormRules>({
|
||||
bedCode: [{ required: true, message: '床位编号不能为空', trigger: 'blur' }],
|
||||
bedName: [{ required: true, message: '床位名称不能为空', trigger: 'blur' }],
|
||||
bedStatus: [{ required: true, message: '床位状态不能为空', trigger: 'change' }],
|
||||
bedType: [{ required: true, message: '床位类型不能为空', trigger: 'change' }]
|
||||
});
|
||||
|
||||
// 监听visible属性变化
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
(val) => {
|
||||
visible.value = val;
|
||||
if (val) {
|
||||
resetForm();
|
||||
// 设置表单初始值
|
||||
form.roomId = props.roomId;
|
||||
// 编辑模式,获取床位详情
|
||||
if (props.bedId) {
|
||||
console.log('开始获取床位详情, bedId:', props.bedId);
|
||||
getDormBed(props.bedId)
|
||||
.then((res) => {
|
||||
console.log('获取床位详情成功:', res.data);
|
||||
if (res.data) {
|
||||
Object.assign(form, res.data);
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('获取床位详情失败', error);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// 监听visible变化,同步回父组件
|
||||
watch(
|
||||
() => visible.value,
|
||||
(val) => {
|
||||
emit('update:modelValue', val);
|
||||
}
|
||||
);
|
||||
|
||||
// 关闭对话框
|
||||
const closeDialog = () => {
|
||||
visible.value = false;
|
||||
resetForm();
|
||||
};
|
||||
|
||||
// 重置表单
|
||||
const resetForm = () => {
|
||||
Object.assign(form, { ...initFormData });
|
||||
formRef.value?.resetFields();
|
||||
};
|
||||
|
||||
// 提交表单
|
||||
const submitForm = () => {
|
||||
formRef.value?.validate(async (valid) => {
|
||||
if (valid) {
|
||||
try {
|
||||
if (props.bedId) {
|
||||
// 修改
|
||||
form.id = props.bedId;
|
||||
await updateDormBed(form);
|
||||
} else {
|
||||
// 新增
|
||||
await addDormBed(form);
|
||||
}
|
||||
closeDialog();
|
||||
emit('success');
|
||||
} catch (error) {
|
||||
console.error('操作失败', error);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,165 @@
|
||||
<template>
|
||||
<el-dialog v-model="visible" :title="title" width="500px" append-to-body @close="closeDialog">
|
||||
<el-form ref="formRef" :model="form" :rules="rules" label-width="80px">
|
||||
<el-form-item label="楼层" prop="floorNum">
|
||||
<el-input v-model="form.floorNum" placeholder="请输入楼层" :disabled="true" />
|
||||
</el-form-item>
|
||||
<el-form-item label="房间编号" prop="roomCode">
|
||||
<el-input v-model="form.roomCode" placeholder="请输入房间编号" maxlength="20" />
|
||||
</el-form-item>
|
||||
<el-form-item label="房间名称" prop="roomName">
|
||||
<el-input v-model="form.roomName" placeholder="请输入房间名称" maxlength="20" />
|
||||
</el-form-item>
|
||||
<el-form-item label="房间类型" prop="roomType">
|
||||
<el-select v-model="form.roomType" placeholder="请选择房间类型" clearable style="width: 100%">
|
||||
<el-option label="标准间" value="1" />
|
||||
<el-option label="单人间" value="2" />
|
||||
<el-option label="套间" value="3" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="床位数量" prop="actualBeds">
|
||||
<el-input-number v-model="form.actualBeds" :min="1" :max="8" controls-position="right" style="width: 100%" />
|
||||
</el-form-item>
|
||||
<el-form-item label="房间面积" prop="roomArea">
|
||||
<el-input v-model="form.roomArea" placeholder="请输入房间面积" maxlength="20">
|
||||
<template #append>
|
||||
<span style="color: #000; background: transparent">平方米</span>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="房间状态" prop="roomStatus">
|
||||
<el-radio-group v-model="form.roomStatus">
|
||||
<el-radio label="0">正常</el-radio>
|
||||
<el-radio label="1">维修中</el-radio>
|
||||
<el-radio label="2">停用</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="备注" prop="remark">
|
||||
<el-input v-model="form.remark" type="textarea" placeholder="请输入备注内容" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button type="primary" @click="submitForm">确 定</el-button>
|
||||
<el-button @click="visible = false">取 消</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, watch } from 'vue';
|
||||
import type { FormInstance, FormRules } from 'element-plus';
|
||||
import { DormRoomForm, DormRoomVO } from '@/api/xgss/dormRoom/types';
|
||||
import { addDormRoom, updateDormRoom } from '@/api/xgss/dormRoom';
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue: boolean;
|
||||
title: string;
|
||||
roomId?: string | number;
|
||||
floorNum: number;
|
||||
buildingId: string | number;
|
||||
room?: DormRoomVO;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits(['update:modelValue', 'success']);
|
||||
|
||||
const formRef = ref<FormInstance>();
|
||||
const visible = ref(false);
|
||||
|
||||
// 表单数据
|
||||
const initFormData: DormRoomForm = {
|
||||
id: undefined,
|
||||
roomCode: '',
|
||||
roomName: '',
|
||||
floorNum: 1,
|
||||
buildingId: '',
|
||||
actualBeds: 8,
|
||||
roomArea: 0,
|
||||
roomType: '1',
|
||||
roomStatus: '0',
|
||||
remark: ''
|
||||
};
|
||||
|
||||
const form = reactive<DormRoomForm>({ ...initFormData });
|
||||
|
||||
// 表单校验规则
|
||||
const rules = reactive<FormRules>({
|
||||
roomCode: [{ required: true, message: '房间编号不能为空', trigger: 'blur' }],
|
||||
roomName: [{ required: true, message: '房间名称不能为空', trigger: 'blur' }],
|
||||
floorNum: [{ required: true, message: '楼层不能为空', trigger: 'blur' }],
|
||||
actualBeds: [{ required: true, message: '床位数量不能为空', trigger: 'change' }],
|
||||
roomType: [{ required: true, message: '房间类型不能为空', trigger: 'change' }],
|
||||
roomStatus: [{ required: true, message: '房间状态不能为空', trigger: 'change' }]
|
||||
});
|
||||
|
||||
// 监听visible属性变化
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
(val) => {
|
||||
visible.value = val;
|
||||
if (val) {
|
||||
// 完全重置表单,确保没有残留数据
|
||||
resetForm();
|
||||
|
||||
// 重新设置基本参数
|
||||
form.floorNum = props.floorNum;
|
||||
form.buildingId = props.buildingId;
|
||||
|
||||
// 如果是编辑模式且有room数据,填充表单数据
|
||||
if (props.room) {
|
||||
Object.keys(form).forEach((key) => {
|
||||
if (key in props.room) {
|
||||
form[key] = props.room[key];
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// 监听visible变化,同步回父组件
|
||||
watch(
|
||||
() => visible.value,
|
||||
(val) => {
|
||||
emit('update:modelValue', val);
|
||||
}
|
||||
);
|
||||
|
||||
// 关闭对话框
|
||||
const closeDialog = () => {
|
||||
visible.value = false;
|
||||
resetForm();
|
||||
};
|
||||
|
||||
// 重置表单
|
||||
const resetForm = () => {
|
||||
// 使用深拷贝确保完全重置
|
||||
Object.keys(form).forEach((key) => {
|
||||
form[key] = initFormData[key];
|
||||
});
|
||||
formRef.value?.resetFields();
|
||||
};
|
||||
|
||||
// 提交表单
|
||||
const submitForm = () => {
|
||||
formRef.value?.validate(async (valid) => {
|
||||
if (valid) {
|
||||
try {
|
||||
if (props.roomId) {
|
||||
// 修改
|
||||
form.id = props.roomId;
|
||||
await updateDormRoom(form);
|
||||
} else {
|
||||
// 新增
|
||||
await addDormRoom(form);
|
||||
}
|
||||
closeDialog();
|
||||
emit('success');
|
||||
} catch (error) {
|
||||
console.error('操作失败', error);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,864 @@
|
||||
<template>
|
||||
<div class="p-2">
|
||||
<div class="flex-layout">
|
||||
<!-- 宿舍树 -->
|
||||
<div class="tree-sidebar">
|
||||
<el-card shadow="hover">
|
||||
<el-input
|
||||
v-model="buildingName"
|
||||
prefix-icon="Search"
|
||||
placeholder="请输入宿舍楼名称"
|
||||
clearable
|
||||
@clear="watchBuildingName"
|
||||
@keyup.enter="watchBuildingName"
|
||||
>
|
||||
</el-input>
|
||||
<div class="tree-container">
|
||||
<el-tree
|
||||
ref="dormTreeRef"
|
||||
class="mt-2"
|
||||
node-key="id"
|
||||
:data="dormOptions"
|
||||
:props="{ label: 'label', children: 'children', disabled: 'disabled' }"
|
||||
:expand-on-click-node="false"
|
||||
:filter-node-method="filterNode"
|
||||
highlight-current
|
||||
default-expand-all
|
||||
@node-click="handleNodeClick"
|
||||
>
|
||||
<template #default="{ node, data }">
|
||||
<div class="custom-tree-node">
|
||||
<el-tooltip v-if="data.disabled" effect="dark" content="此节点不可选择,请选择具体的宿舍楼" placement="right">
|
||||
<span :class="{ 'disabled-node': data.disabled }">{{ node.label }}</span>
|
||||
</el-tooltip>
|
||||
<span v-else>
|
||||
<span v-if="data.type === 'building'" :class="data.buildingType === '1' ? 'male-building' : 'female-building'">
|
||||
{{ data.buildingType === '1' ? '(男)' : '(女)' }}
|
||||
</span>
|
||||
{{ node.label }}
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
</el-tree>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
|
||||
<div class="content-area">
|
||||
<transition :enter-active-class="animate.searchAnimate.enter" :leave-active-class="animate.searchAnimate.leave">
|
||||
<div v-show="showSearch" class="mb-[10px]">
|
||||
<el-card shadow="hover">
|
||||
<el-form ref="queryFormRef" :model="queryParams" inline>
|
||||
<el-form-item label="房间号" prop="roomName">
|
||||
<el-input v-model="queryParams.roomName" placeholder="请输入房间号" clearable @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
<el-form-item label="状态" prop="roomStatus">
|
||||
<el-select v-model="queryParams.roomStatus" placeholder="房间状态" clearable>
|
||||
<el-option label="正常" value="0" />
|
||||
<el-option label="维修中" value="1" />
|
||||
<el-option label="停用" value="2" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
|
||||
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
</div>
|
||||
</transition>
|
||||
|
||||
<el-card v-if="!selectedBuilding" shadow="hover" class="empty-tip">
|
||||
<div class="flex flex-col items-center justify-center h-[400px]">
|
||||
<el-icon class="text-gray-400 text-5xl mb-4"><InfoFilled /></el-icon>
|
||||
<p class="text-gray-500 text-lg">请在左侧选择宿舍楼</p>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<template v-else>
|
||||
<el-card shadow="hover" class="mb-4">
|
||||
<template #header>
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<span class="font-bold"
|
||||
>{{ selectedBuilding?.label }}
|
||||
<span :class="selectedBuilding?.buildingType === '1' ? 'male-building' : 'female-building'">
|
||||
{{ selectedBuilding?.buildingType === '1' ? '(男生楼)' : '(女生楼)' }}
|
||||
</span>
|
||||
- 房间管理</span
|
||||
>
|
||||
</el-col>
|
||||
<el-col :span="12" class="text-right">
|
||||
<right-toolbar
|
||||
:show-search="showSearch"
|
||||
@update:show-search="(val) => (showSearch = val)"
|
||||
@query-table="loadRoomData"
|
||||
></right-toolbar>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</template>
|
||||
|
||||
<!-- 楼层卡片 -->
|
||||
<div v-for="floor in floorList" :key="floor" class="mb-4">
|
||||
<el-divider>
|
||||
<el-tag type="success" effect="dark" class="mr-2">{{ floor }}层</el-tag>
|
||||
<el-button type="primary" icon="Plus" size="small" @click="handleAddAllRoom(floor)">自动添加房间</el-button>
|
||||
<el-button type="primary" icon="Plus" size="small" @click="handleAddRoom(floor)">添加房间</el-button>
|
||||
</el-divider>
|
||||
|
||||
<div class="room-container">
|
||||
<el-empty v-if="getRoomsByFloor(floor).length === 0" description="暂无房间数据" />
|
||||
<div v-else class="room-grid">
|
||||
<el-card
|
||||
v-for="room in getRoomsByFloor(floor)"
|
||||
:key="room.id"
|
||||
class="room-card"
|
||||
:class="{ 'room-disabled': room.roomStatus !== '0' }"
|
||||
>
|
||||
<template #header>
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="font-bold">{{ room.roomName }}</span>
|
||||
<div>
|
||||
<el-tag v-if="room.roomStatus === '0'" type="success" size="small">正常</el-tag>
|
||||
<el-tag v-else-if="room.roomStatus === '1'" type="warning" size="small">维修中</el-tag>
|
||||
<el-tag v-else type="danger" size="small">停用</el-tag>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<div class="room-info">
|
||||
<div class="mb-2">
|
||||
<span class="text-gray-500">类型:</span>{{ room.roomType === '1' ? '标准间' : room.roomType === '2' ? '单人间' : '套间' }}
|
||||
</div>
|
||||
<div class="mb-2"><span class="text-gray-500">床位:</span>{{ room.currentBeds || 0 }}/{{ room.actualBeds }}</div>
|
||||
<div class="flex justify-between mt-4">
|
||||
<div>
|
||||
<el-button type="primary" link size="small" @click="handleManageBeds(room)">床位管理</el-button>
|
||||
</div>
|
||||
<div>
|
||||
<el-button type="primary" link icon="Edit" @click="handleEditRoom(room)" />
|
||||
<el-button type="danger" link icon="Delete" @click="handleDeleteRoom(room)" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 房间管理对话框 -->
|
||||
<room-dialog
|
||||
v-model="roomDialog.visible"
|
||||
:title="roomDialog.title"
|
||||
:room-id="roomDialog.roomId"
|
||||
:floor-num="roomDialog.floorNum"
|
||||
:building-id="roomDialog.buildingId"
|
||||
:room="roomDialog.room"
|
||||
@success="handleRoomSuccess"
|
||||
/>
|
||||
|
||||
<!-- 床位管理对话框 -->
|
||||
<el-dialog v-model="bedManageDialog.visible" :title="`${bedManageDialog.roomName} - 床位管理`" width="800px">
|
||||
<div class="mb-4 text-right">
|
||||
<el-button v-if="!isBedLimitReached" type="primary" icon="Plus" size="small" @click="handleAddALlBed">自动添加床位</el-button>
|
||||
<el-button v-if="!isBedLimitReached" type="primary" icon="Plus" size="small" @click="handleAddBed">添加床位</el-button>
|
||||
<el-tag v-else type="info">已达到床位上限 ({{ bedList.length }}/{{ bedManageDialog.maxBeds }})</el-tag>
|
||||
<!-- <el-button type="success" icon="Check" size="small" @click="handleSaveBeds">保存</el-button> -->
|
||||
</div>
|
||||
<el-table v-loading="bedLoading" :data="bedList" border>
|
||||
<el-table-column label="床位编号" prop="bedCode" />
|
||||
<el-table-column label="床位名称" prop="bedName" />
|
||||
<el-table-column label="状态" align="center">
|
||||
<template #default="scope">
|
||||
<el-tag v-if="scope.row.bedStatus === '0'" type="success">空闲</el-tag>
|
||||
<el-tag v-else-if="scope.row.bedStatus === '1'" type="warning">已分配</el-tag>
|
||||
<el-tag v-else type="danger">维修中</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<!-- 在这里增加床位类型-->
|
||||
<el-table-column label="床位类型" prop="bedType">
|
||||
<template #default="scope">
|
||||
<el-tag v-if="scope.row.bedType === '1'" type="success">下铺</el-tag>
|
||||
<el-tag v-else-if="scope.row.bedType === '2'" type="warning">上铺</el-tag>
|
||||
<el-tag v-else-if="scope.row.bedType === '3'" type="danger">中铺</el-tag>
|
||||
<el-tag v-else type="info">独立床</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<!-- <el-table-column label="学生" prop="studentName" /> -->
|
||||
<el-table-column label="备注" prop="remark" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="操作" width="180" align="center">
|
||||
<template #default="scope">
|
||||
<el-button type="primary" link icon="Edit" @click="handleEditBed(scope.row)" />
|
||||
<el-button type="danger" link icon="Delete" @click="handleDeleteBed(scope.row)" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 床位表单对话框 -->
|
||||
<bed-dialog
|
||||
v-model="bedDialog.visible"
|
||||
:title="bedDialog.title"
|
||||
:bed-id="bedDialog.bedId"
|
||||
:room-id="bedDialog.roomId"
|
||||
@success="handleBedSuccess"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup name="DormRoomManagement" lang="ts">
|
||||
import { ref, computed, reactive, onMounted, nextTick, watch } from 'vue';
|
||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||
import { InfoFilled, Search } from '@element-plus/icons-vue';
|
||||
import { DormTreeNode, BedInfo, DormQuery, BedForm } from '@/api/xgss/xsssfjgl/types';
|
||||
import { DormRoomVO, DormRoomQuery } from '@/api/xgss/dormRoom/types';
|
||||
import { getDormTree } from '@/api/xgss/xsssfjgl';
|
||||
import { listDormBed, delDormBed, addDormBed } from '@/api/xgss/dormBed';
|
||||
import type { DormBedQuery } from '@/api/xgss/dormBed/types';
|
||||
import { listDormRoom, delDormRoom, addDormRoom } from '@/api/xgss/dormRoom';
|
||||
|
||||
// 引入子组件
|
||||
import RoomDialog from './components/RoomDialog.vue';
|
||||
import BedDialog from './components/BedDialog.vue';
|
||||
|
||||
// 组件状态
|
||||
const dormTreeRef = ref();
|
||||
const queryFormRef = ref();
|
||||
const buildingName = ref('');
|
||||
const showSearch = ref(true);
|
||||
const animate = reactive({
|
||||
searchAnimate: {
|
||||
enter: 'animate__animated animate__fadeInDown',
|
||||
leave: 'animate__animated animate__fadeOutUp'
|
||||
}
|
||||
});
|
||||
|
||||
// 判断床位是否已达到上限的计算属性
|
||||
const isBedLimitReached = computed(() => {
|
||||
return bedList.value.length >= bedManageDialog.maxBeds;
|
||||
});
|
||||
|
||||
// 宿舍树数据
|
||||
const dormOptions = ref<DormTreeNode[]>([]);
|
||||
const selectedBuilding = ref<DormTreeNode>();
|
||||
|
||||
// 房间数据
|
||||
const roomList = ref<DormRoomVO[]>([]);
|
||||
const floorList = computed(() => {
|
||||
if (!selectedBuilding.value?.buildingFloors) return [];
|
||||
return Array.from({ length: selectedBuilding.value.buildingFloors }, (_, i) => i + 1);
|
||||
});
|
||||
|
||||
// 查询参数
|
||||
const queryParams = reactive<DormRoomQuery>({
|
||||
pageNum: 1,
|
||||
pageSize: 10000,
|
||||
buildingId: undefined,
|
||||
roomName: '',
|
||||
roomStatus: undefined
|
||||
});
|
||||
|
||||
// 房间对话框
|
||||
const roomDialog = reactive({
|
||||
visible: false,
|
||||
title: '',
|
||||
roomId: undefined as string | number | undefined,
|
||||
floorNum: 1,
|
||||
buildingId: '' as string | number,
|
||||
room: undefined as DormRoomVO | undefined
|
||||
});
|
||||
|
||||
// 床位管理对话框
|
||||
const bedManageDialog = reactive({
|
||||
visible: false,
|
||||
roomId: '' as string | number,
|
||||
roomName: '',
|
||||
maxBeds: 0 // 预设的最大床位数
|
||||
});
|
||||
|
||||
// 床位表单对话框
|
||||
const bedDialog = reactive({
|
||||
visible: false,
|
||||
title: '',
|
||||
bedId: undefined as string | number | undefined,
|
||||
roomId: '' as string | number
|
||||
});
|
||||
|
||||
// 床位数据
|
||||
const bedList = ref<BedInfo[]>([]);
|
||||
const bedLoading = ref(false);
|
||||
|
||||
/** 通过条件过滤节点 */
|
||||
const filterNode = (value: string, data: DormTreeNode) => {
|
||||
if (!value) return true;
|
||||
return data.label.toLowerCase().indexOf(value.toLowerCase()) !== -1;
|
||||
};
|
||||
|
||||
/** 监听宿舍楼名称变化,筛选树 */
|
||||
const watchBuildingName = () => {
|
||||
dormTreeRef.value?.filter(buildingName.value);
|
||||
};
|
||||
|
||||
// 监听buildingName变化,实时筛选宿舍楼
|
||||
watch(buildingName, () => {
|
||||
watchBuildingName();
|
||||
});
|
||||
|
||||
/** 获取宿舍树数据 */
|
||||
const getDormTreeData = async () => {
|
||||
try {
|
||||
const res = await getDormTree();
|
||||
// 处理树节点,标记非宿舍楼节点为禁用
|
||||
const processTreeNodes = (nodes: DormTreeNode[]) => {
|
||||
return nodes.map((node) => {
|
||||
// 如果不是宿舍楼类型,则禁用该节点
|
||||
if (node.type !== 'building') {
|
||||
node.disabled = true;
|
||||
}
|
||||
|
||||
// 如果是宿舍楼但没有性别信息,随机分配性别(实际项目中应该由后端提供)
|
||||
if (node.type === 'building' && !node.gender) {
|
||||
// 这里使用随机分配,实际项目中应该从后端获取
|
||||
node.gender = Math.random() > 0.5 ? '1' : '2';
|
||||
}
|
||||
|
||||
// 递归处理子节点
|
||||
if (node.children && node.children.length > 0) {
|
||||
node.children = processTreeNodes(node.children);
|
||||
}
|
||||
return node;
|
||||
});
|
||||
};
|
||||
|
||||
dormOptions.value = processTreeNodes(res.data);
|
||||
} catch (error) {
|
||||
console.error('获取宿舍树失败', error);
|
||||
}
|
||||
};
|
||||
|
||||
/** 节点单击事件 */
|
||||
const handleNodeClick = (data: DormTreeNode) => {
|
||||
if (data.type !== 'building') {
|
||||
// 如果不是宿舍楼,不进行操作
|
||||
return;
|
||||
}
|
||||
|
||||
selectedBuilding.value = data;
|
||||
queryParams.buildingId = data.id;
|
||||
loadRoomData();
|
||||
};
|
||||
|
||||
/** 加载房间数据 */
|
||||
const loadRoomData = async () => {
|
||||
if (!queryParams.buildingId) return;
|
||||
|
||||
try {
|
||||
const res = await listDormRoom(queryParams);
|
||||
roomList.value = res?.rows;
|
||||
|
||||
// 加载每个房间的当前床位数量
|
||||
await Promise.all(
|
||||
roomList.value.map(async (room) => {
|
||||
try {
|
||||
const bedQuery: DormBedQuery = { roomId: room.id };
|
||||
const bedRes = await listDormBed(bedQuery);
|
||||
room.currentBeds = bedRes?.rows?.length || 0;
|
||||
} catch (error) {
|
||||
console.error('获取床位数量失败', error);
|
||||
room.currentBeds = 0;
|
||||
}
|
||||
})
|
||||
);
|
||||
} catch (error) {
|
||||
console.error('获取房间列表失败', error);
|
||||
}
|
||||
};
|
||||
|
||||
/** 根据楼层获取房间数据 */
|
||||
const getRoomsByFloor = (floor: number) => {
|
||||
return roomList.value.filter((room) => room.floorNum === floor);
|
||||
};
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
queryParams.pageNum = 1;
|
||||
loadRoomData();
|
||||
};
|
||||
|
||||
/** 重置按钮操作 */
|
||||
const resetQuery = () => {
|
||||
queryFormRef.value?.resetFields();
|
||||
queryParams.pageNum = 1;
|
||||
queryParams.roomName = '';
|
||||
queryParams.roomStatus = undefined;
|
||||
handleQuery();
|
||||
};
|
||||
|
||||
/** 批量自动添加房间 */
|
||||
const handleAddAllRoom = (floor: number) => {
|
||||
if (!selectedBuilding.value) return;
|
||||
|
||||
// 使用Element Plus的对话框提示用户输入寝室数量
|
||||
ElMessageBox.prompt('请输入要添加的寝室数量', '批量添加房间', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
inputPattern: /^[1-9]\d*$/,
|
||||
inputErrorMessage: '请输入正整数',
|
||||
inputValue: '10' // 默认值
|
||||
})
|
||||
.then(async ({ value }) => {
|
||||
const roomCount = parseInt(value);
|
||||
|
||||
try {
|
||||
// 先获取当前楼层的房间列表,确定起始房间号
|
||||
const floorRooms = getRoomsByFloor(floor);
|
||||
let startRoomNumber = 1;
|
||||
|
||||
// 如果已有房间,确定起始编号(避免房间号重复)
|
||||
if (floorRooms.length > 0) {
|
||||
// 提取现有房间号中的数字部分
|
||||
const existingRoomNumbers = floorRooms
|
||||
.map((room) => {
|
||||
// 假设房间名称格式为:楼层+编号,如"3201"
|
||||
const match = room.roomName.match(/\d+$/);
|
||||
return match ? parseInt(match[0].substring(floor.toString().length), 10) : 0;
|
||||
})
|
||||
.filter((num) => !isNaN(num));
|
||||
|
||||
// 找出最大编号并加1作为起始编号
|
||||
if (existingRoomNumbers.length > 0) {
|
||||
startRoomNumber = Math.max(...existingRoomNumbers) + 1;
|
||||
}
|
||||
}
|
||||
|
||||
// 显示加载提示
|
||||
ElMessage.info(`正在添加 ${roomCount} 个房间,请稍候...`);
|
||||
|
||||
// 批量添加房间
|
||||
for (let i = 0; i < roomCount; i++) {
|
||||
const roomNumber = startRoomNumber + i;
|
||||
// 房间号格式:楼栋首字符+楼层+编号(如A栋2层1号房间为A201)
|
||||
// 补零确保房间号长度一致
|
||||
const paddedRoomNumber = roomNumber.toString().padStart(2, '0');
|
||||
const buildingFirstChar = selectedBuilding.value.label.charAt(0);
|
||||
const roomCode = `${buildingFirstChar}${floor}${paddedRoomNumber}`;
|
||||
|
||||
const roomData = {
|
||||
buildingId: selectedBuilding.value.id,
|
||||
floorNum: floor,
|
||||
roomCode: `${roomCode}`,
|
||||
roomName: `${roomCode}`,
|
||||
roomType: '1', // 1: 标准间
|
||||
actualBeds: 6, // 默认6个床位
|
||||
roomArea: 0, // 默认面积为0
|
||||
roomStatus: '0' // 0: 正常
|
||||
};
|
||||
|
||||
// 调用添加房间API
|
||||
await addDormRoom(roomData);
|
||||
|
||||
// 添加延迟避免请求过快
|
||||
await new Promise((resolve) => setTimeout(resolve, 300));
|
||||
}
|
||||
|
||||
ElMessage.success(`成功添加 ${roomCount} 个房间`);
|
||||
// 重新加载房间数据
|
||||
loadRoomData();
|
||||
} catch (error) {
|
||||
console.error('批量添加房间失败', error);
|
||||
ElMessage.error('添加房间失败,请重试');
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
// 用户取消操作
|
||||
ElMessage.info('已取消添加房间');
|
||||
});
|
||||
};
|
||||
|
||||
/** 添加房间 */
|
||||
const handleAddRoom = (floor: number) => {
|
||||
if (!selectedBuilding.value) return;
|
||||
// console.log(floor);
|
||||
|
||||
// 完全重置房间对话框的状态
|
||||
roomDialog.title = '添加房间';
|
||||
roomDialog.roomId = undefined;
|
||||
roomDialog.floorNum = floor;
|
||||
roomDialog.buildingId = selectedBuilding.value.id;
|
||||
roomDialog.room = undefined; // 确保清除之前的房间数据
|
||||
|
||||
// 延迟显示对话框,确保状态已完全重置
|
||||
nextTick(() => {
|
||||
roomDialog.visible = true;
|
||||
});
|
||||
};
|
||||
|
||||
/** 编辑房间 */
|
||||
const handleEditRoom = (room: DormRoomVO) => {
|
||||
roomDialog.title = '编辑房间';
|
||||
roomDialog.roomId = room.id;
|
||||
roomDialog.floorNum = room.floorNum;
|
||||
roomDialog.buildingId = room.buildingId;
|
||||
roomDialog.room = room; // 将完整房间对象传递给对话框
|
||||
roomDialog.visible = true;
|
||||
};
|
||||
|
||||
/** 删除房间 */
|
||||
const handleDeleteRoom = (row: DormRoomVO) => {
|
||||
ElMessageBox.confirm(`确定要删除房间 ${row.roomName} 吗?`, '警告', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
})
|
||||
.then(async () => {
|
||||
try {
|
||||
await delDormRoom(row.id);
|
||||
ElMessage.success('删除成功');
|
||||
loadRoomData();
|
||||
} catch (error) {
|
||||
console.error('删除房间失败', error);
|
||||
}
|
||||
})
|
||||
.catch(() => {});
|
||||
};
|
||||
|
||||
/** 房间操作成功回调 */
|
||||
const handleRoomSuccess = () => {
|
||||
loadRoomData();
|
||||
};
|
||||
|
||||
/** 管理床位 */
|
||||
const handleManageBeds = async (room: DormRoomVO) => {
|
||||
bedManageDialog.roomId = room.id;
|
||||
bedManageDialog.roomName = room.roomName;
|
||||
bedManageDialog.maxBeds = room.actualBeds || 0; // 保存预设的床位数
|
||||
bedManageDialog.visible = true;
|
||||
|
||||
await loadBedData(room.id);
|
||||
};
|
||||
|
||||
/** 加载床位数据 */
|
||||
const loadBedData = async (roomId: string | number) => {
|
||||
bedLoading.value = true;
|
||||
try {
|
||||
const query: DormBedQuery = {
|
||||
roomId,
|
||||
pageNum: 1,
|
||||
pageSize: 100
|
||||
};
|
||||
const res = await listDormBed(query);
|
||||
bedList.value = res?.rows || [];
|
||||
} catch (error) {
|
||||
console.error('获取床位列表失败', error);
|
||||
} finally {
|
||||
bedLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
/** 添加床位 */
|
||||
const handleAddBed = () => {
|
||||
bedDialog.title = '添加床位';
|
||||
bedDialog.bedId = undefined;
|
||||
bedDialog.roomId = bedManageDialog.roomId;
|
||||
bedDialog.visible = true;
|
||||
};
|
||||
|
||||
/** 编辑床位 */
|
||||
const handleEditBed = (bed: BedInfo) => {
|
||||
bedDialog.title = '编辑床位';
|
||||
bedDialog.bedId = bed.id;
|
||||
bedDialog.roomId = bed.roomId;
|
||||
bedDialog.visible = true;
|
||||
};
|
||||
|
||||
/** 删除床位 */
|
||||
const handleDeleteBed = (row: BedInfo) => {
|
||||
ElMessageBox.confirm(`确定要删除床位 ${row.bedName} 吗?`, '警告', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
})
|
||||
.then(async () => {
|
||||
try {
|
||||
await delDormBed(row.id);
|
||||
ElMessage.success('删除成功');
|
||||
await loadBedData(bedManageDialog.roomId);
|
||||
// 更新房间的当前床位数
|
||||
if (selectedBuilding.value) {
|
||||
loadRoomData();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('删除床位失败', error);
|
||||
}
|
||||
})
|
||||
.catch(() => {});
|
||||
};
|
||||
|
||||
/** 床位操作成功回调 */
|
||||
const handleBedSuccess = async () => {
|
||||
await loadBedData(bedManageDialog.roomId);
|
||||
// 更新房间的当前床位数
|
||||
if (selectedBuilding.value) {
|
||||
loadRoomData();
|
||||
}
|
||||
};
|
||||
|
||||
/** 保存床位信息 */
|
||||
const handleSaveBeds = async () => {
|
||||
try {
|
||||
ElMessage.success('床位信息保存成功');
|
||||
await loadBedData(bedManageDialog.roomId);
|
||||
} catch (error) {
|
||||
console.error('保存床位信息失败', error);
|
||||
ElMessage.error('保存失败,请重试');
|
||||
}
|
||||
};
|
||||
|
||||
/** 自动添加床位 */
|
||||
const handleAddALlBed = async () => {
|
||||
// 计算需要添加的床位数量
|
||||
const maxBeds = bedManageDialog.maxBeds;
|
||||
const currentBeds = bedList.value.length;
|
||||
|
||||
if (currentBeds >= maxBeds) {
|
||||
ElMessage.warning(`已达到床位上限 (${currentBeds}/${maxBeds})`);
|
||||
return;
|
||||
}
|
||||
|
||||
// 计算要添加的床位数量,床位按成对添加(上铺和下铺)
|
||||
const needToAdd = maxBeds - currentBeds;
|
||||
const pairCount = Math.ceil(needToAdd / 2); // 需要添加的床位对数
|
||||
|
||||
try {
|
||||
bedLoading.value = true;
|
||||
|
||||
// 计算起始编号(基于已有床位计算)
|
||||
let startIndex = 1;
|
||||
if (bedList.value.length > 0) {
|
||||
// 尝试从现有床位编号中提取最大数字
|
||||
const existingNumbers = bedList.value
|
||||
.map((bed) => {
|
||||
// 假设bedName中包含数字,如"1上"中的1
|
||||
const match = bed.bedName.match(/^(\d+)/);
|
||||
return match ? parseInt(match[1], 10) : 0;
|
||||
})
|
||||
.filter((num) => !isNaN(num));
|
||||
|
||||
if (existingNumbers.length > 0) {
|
||||
startIndex = Math.max(...existingNumbers) + 1;
|
||||
}
|
||||
}
|
||||
|
||||
// 添加床位计数
|
||||
let addedCount = 0;
|
||||
|
||||
// 创建延迟函数
|
||||
const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
|
||||
|
||||
for (let i = 0; i < pairCount; i++) {
|
||||
const bedIndex = startIndex + i;
|
||||
|
||||
// 如果当前床位数 + 已添加的床位数 < 最大床位数,则添加上铺
|
||||
if (currentBeds + addedCount < maxBeds) {
|
||||
// 使用API需要的格式
|
||||
const upperBed = {
|
||||
id: '', // 新增时为空
|
||||
bedCode: `${addedCount + 1}`,
|
||||
bedName: `${addedCount + 1}`,
|
||||
roomId: bedManageDialog.roomId,
|
||||
bedStatus: '0', // 空闲
|
||||
bedType: '2', // 上铺
|
||||
remark: ''
|
||||
};
|
||||
await addDormBed(upperBed);
|
||||
addedCount++;
|
||||
|
||||
// 添加0.5秒延迟
|
||||
await delay(100);
|
||||
}
|
||||
|
||||
// 如果当前床位数 + 已添加的床位数 < 最大床位数,则添加下铺
|
||||
if (currentBeds + addedCount < maxBeds) {
|
||||
// 使用API需要的格式
|
||||
const lowerBed = {
|
||||
id: '', // 新增时为空
|
||||
bedCode: `${addedCount + 1}`,
|
||||
bedName: `${addedCount + 1}`,
|
||||
roomId: bedManageDialog.roomId,
|
||||
bedStatus: '0', // 空闲
|
||||
bedType: '1', // 下铺
|
||||
remark: ''
|
||||
};
|
||||
await addDormBed(lowerBed);
|
||||
addedCount++;
|
||||
}
|
||||
}
|
||||
|
||||
if (addedCount > 0) {
|
||||
ElMessage.success(`成功添加 ${addedCount} 个床位`);
|
||||
// 重新加载床位数据
|
||||
await loadBedData(bedManageDialog.roomId);
|
||||
// 更新房间的当前床位数
|
||||
if (selectedBuilding.value) {
|
||||
loadRoomData();
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('批量添加床位失败', error);
|
||||
ElMessage.error('添加床位失败,请重试');
|
||||
} finally {
|
||||
bedLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
getDormTreeData();
|
||||
});
|
||||
|
||||
// 确保树加载后才能过滤
|
||||
watch(
|
||||
dormOptions,
|
||||
() => {
|
||||
if (buildingName.value) {
|
||||
nextTick(() => {
|
||||
watchBuildingName();
|
||||
});
|
||||
}
|
||||
},
|
||||
{ deep: true }
|
||||
);
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.flex-layout {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.tree-sidebar {
|
||||
min-width: 320px;
|
||||
max-width: 320px;
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: calc(100vh - 120px); /* 调整高度以适应页面 */
|
||||
}
|
||||
|
||||
.tree-sidebar .el-card {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.tree-sidebar .el-card__body {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 15px;
|
||||
overflow: hidden; /* 防止卡片内容溢出 */
|
||||
}
|
||||
|
||||
.content-area {
|
||||
flex: 1;
|
||||
min-width: 0; /* 防止内容溢出 */
|
||||
}
|
||||
|
||||
.room-container {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.room-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.room-card {
|
||||
margin-bottom: 0;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.room-card:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.room-disabled {
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.room-info {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.empty-tip {
|
||||
min-height: 400px;
|
||||
}
|
||||
|
||||
.custom-tree-node {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding-right: 8px;
|
||||
white-space: nowrap; /* 防止节点内容换行 */
|
||||
overflow: visible; /* 确保长内容可见 */
|
||||
min-width: 200px; /* 设置最小宽度 */
|
||||
}
|
||||
|
||||
.disabled-node {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.male-building {
|
||||
color: var(--el-color-primary);
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.female-building {
|
||||
color: #ff69b4; /* 粉色 */
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.tree-container {
|
||||
overflow-y: auto; /* 纵向滚动条 */
|
||||
overflow-x: auto; /* 横向滚动条 */
|
||||
max-height: unset; /* 取消最大高度限制 */
|
||||
min-height: 0; /* 允许容器收缩 */
|
||||
height: 100%; /* 填满父容器 */
|
||||
flex: 1; /* 占用剩余空间 */
|
||||
border: 1px solid #ebeef5;
|
||||
border-radius: 4px;
|
||||
padding: 5px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.tree-container::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
}
|
||||
|
||||
.tree-container::-webkit-scrollbar-thumb {
|
||||
background-color: #c0c4cc;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.tree-container::-webkit-scrollbar-track {
|
||||
background-color: #f5f7fa;
|
||||
}
|
||||
|
||||
/* 当宿舍树节点太长时,确保有足够空间显示完整内容 */
|
||||
.tree-container .el-tree-node__content {
|
||||
min-width: fit-content;
|
||||
height: auto;
|
||||
min-height: 26px;
|
||||
padding: 4px 0;
|
||||
}
|
||||
|
||||
.tree-container .el-tree {
|
||||
min-width: 100%; /* 确保树至少占满容器宽度 */
|
||||
width: max-content; /* 允许内容扩展 */
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,150 @@
|
||||
<template>
|
||||
<el-dialog v-model="visible" :title="title" width="500px" append-to-body @close="closeDialog">
|
||||
<el-form ref="formRef" :model="form" :rules="rules" label-width="80px">
|
||||
<el-form-item label="床位编号" prop="bedCode">
|
||||
<el-input v-model="form.bedCode" placeholder="请输入床位编号" maxlength="20" />
|
||||
</el-form-item>
|
||||
<el-form-item label="床位名称" prop="bedName">
|
||||
<el-input v-model="form.bedName" placeholder="请输入床位名称" maxlength="20" />
|
||||
</el-form-item>
|
||||
<el-form-item label="床位状态" prop="bedStatus">
|
||||
<el-radio-group v-model="form.bedStatus">
|
||||
<el-radio label="0">空闲</el-radio>
|
||||
<el-radio label="1" disabled>已分配</el-radio>
|
||||
<el-radio label="2">维修中</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="床位类型" prop="bedType">
|
||||
<el-radio-group v-model="form.bedType">
|
||||
<el-radio label="1">下铺</el-radio>
|
||||
<el-radio label="2">上铺</el-radio>
|
||||
<el-radio label="3">中铺</el-radio>
|
||||
<el-radio label="4">独立床</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="备注" prop="remark">
|
||||
<el-input v-model="form.remark" type="textarea" placeholder="请输入备注内容" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button type="primary" @click="submitForm">确 定</el-button>
|
||||
<el-button @click="visible = false">取 消</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, watch } from 'vue';
|
||||
import type { FormInstance, FormRules } from 'element-plus';
|
||||
import { DormBedForm } from '@/api/xgss/dormBed/types';
|
||||
import { addDormBed, updateDormBed, getDormBed } from '@/api/xgss/dormBed';
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue: boolean;
|
||||
title: string;
|
||||
bedId?: string | number;
|
||||
roomId: string | number;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits(['update:modelValue', 'success']);
|
||||
|
||||
const formRef = ref<FormInstance>();
|
||||
const visible = ref(false);
|
||||
|
||||
// 表单数据
|
||||
const initFormData: DormBedForm = {
|
||||
id: '',
|
||||
bedCode: '',
|
||||
bedName: '',
|
||||
roomId: '',
|
||||
bedStatus: '0',
|
||||
bedType: '4',
|
||||
remark: ''
|
||||
};
|
||||
|
||||
const form = reactive<DormBedForm>({ ...initFormData });
|
||||
|
||||
// 表单校验规则
|
||||
const rules = reactive<FormRules>({
|
||||
bedCode: [{ required: true, message: '床位编号不能为空', trigger: 'blur' }],
|
||||
bedName: [{ required: true, message: '床位名称不能为空', trigger: 'blur' }],
|
||||
bedStatus: [{ required: true, message: '床位状态不能为空', trigger: 'change' }],
|
||||
bedType: [{ required: true, message: '床位类型不能为空', trigger: 'change' }]
|
||||
});
|
||||
|
||||
// 监听visible属性变化
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
(val) => {
|
||||
visible.value = val;
|
||||
if (val) {
|
||||
resetForm();
|
||||
// 设置表单初始值
|
||||
form.roomId = props.roomId;
|
||||
// 编辑模式,获取床位详情
|
||||
if (props.bedId) {
|
||||
console.log('开始获取床位详情, bedId:', props.bedId);
|
||||
getDormBed(props.bedId)
|
||||
.then((res) => {
|
||||
console.log('获取床位详情成功:', res.data);
|
||||
if (res.data) {
|
||||
Object.assign(form, res.data);
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('获取床位详情失败', error);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// 监听visible变化,同步回父组件
|
||||
watch(
|
||||
() => visible.value,
|
||||
(val) => {
|
||||
emit('update:modelValue', val);
|
||||
}
|
||||
);
|
||||
|
||||
// 关闭对话框
|
||||
const closeDialog = () => {
|
||||
visible.value = false;
|
||||
resetForm();
|
||||
};
|
||||
|
||||
// 重置表单
|
||||
const resetForm = () => {
|
||||
Object.assign(form, { ...initFormData });
|
||||
formRef.value?.resetFields();
|
||||
};
|
||||
|
||||
// 提交表单
|
||||
const submitForm = () => {
|
||||
formRef.value?.validate(async (valid) => {
|
||||
if (valid) {
|
||||
try {
|
||||
if (props.bedId) {
|
||||
// 修改
|
||||
form.id = props.bedId;
|
||||
await updateDormBed(form);
|
||||
} else {
|
||||
// 新增
|
||||
await addDormBed(form);
|
||||
}
|
||||
closeDialog();
|
||||
emit('success');
|
||||
} catch (error) {
|
||||
console.error('操作失败', error);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 添加默认导出以支持导入
|
||||
defineOptions({
|
||||
name: 'BedDialog'
|
||||
});
|
||||
</script>
|
||||
@@ -0,0 +1,171 @@
|
||||
<template>
|
||||
<el-dialog v-model="visible" :title="title" width="500px" append-to-body @close="closeDialog">
|
||||
<el-form ref="formRef" :model="form" :rules="rules" label-width="80px">
|
||||
<el-form-item label="楼层" prop="floorNum">
|
||||
<el-input v-model="form.floorNum" placeholder="请输入楼层" :disabled="true" />
|
||||
</el-form-item>
|
||||
<el-form-item label="房间编号" prop="roomCode">
|
||||
<el-input v-model="form.roomCode" placeholder="请输入房间编号" maxlength="20" />
|
||||
</el-form-item>
|
||||
<el-form-item label="房间名称" prop="roomName">
|
||||
<el-input v-model="form.roomName" placeholder="请输入房间名称" maxlength="20" />
|
||||
</el-form-item>
|
||||
<el-form-item label="房间类型" prop="roomType">
|
||||
<el-select v-model="form.roomType" placeholder="请选择房间类型" clearable style="width: 100%">
|
||||
<el-option label="标准间" value="1" />
|
||||
<el-option label="单人间" value="2" />
|
||||
<el-option label="套间" value="3" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="床位数量" prop="actualBeds">
|
||||
<el-input-number v-model="form.actualBeds" :min="1" :max="8" controls-position="right" style="width: 100%" />
|
||||
</el-form-item>
|
||||
<el-form-item label="房间面积" prop="roomArea">
|
||||
<el-input v-model="form.roomArea" placeholder="请输入房间面积" maxlength="20">
|
||||
<template #append>
|
||||
<span style="color: #000; background: transparent">平方米</span>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="房间状态" prop="roomStatus">
|
||||
<el-radio-group v-model="form.roomStatus">
|
||||
<el-radio label="0">正常</el-radio>
|
||||
<el-radio label="1">维修中</el-radio>
|
||||
<el-radio label="2">停用</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="备注" prop="remark">
|
||||
<el-input v-model="form.remark" type="textarea" placeholder="请输入备注内容" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button type="primary" @click="submitForm">确 定</el-button>
|
||||
<el-button @click="visible = false">取 消</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, watch } from 'vue';
|
||||
import type { FormInstance, FormRules } from 'element-plus';
|
||||
import { DormRoomForm, DormRoomVO } from '@/api/xgss/dormRoom/types';
|
||||
import { addDormRoom, updateDormRoom } from '@/api/xgss/dormRoom';
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue: boolean;
|
||||
title: string;
|
||||
roomId?: string | number;
|
||||
floorNum: number;
|
||||
buildingId: string | number;
|
||||
room?: DormRoomVO;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits(['update:modelValue', 'success']);
|
||||
|
||||
const formRef = ref<FormInstance>();
|
||||
const visible = ref(false);
|
||||
|
||||
// 表单数据
|
||||
const initFormData: DormRoomForm = {
|
||||
id: undefined,
|
||||
roomCode: '',
|
||||
roomName: '',
|
||||
floorNum: 1,
|
||||
buildingId: '',
|
||||
actualBeds: 4,
|
||||
roomArea: 0,
|
||||
roomType: '1',
|
||||
roomStatus: '0',
|
||||
remark: ''
|
||||
};
|
||||
|
||||
const form = reactive<DormRoomForm>({ ...initFormData });
|
||||
|
||||
// 表单校验规则
|
||||
const rules = reactive<FormRules>({
|
||||
roomCode: [{ required: true, message: '房间编号不能为空', trigger: 'blur' }],
|
||||
roomName: [{ required: true, message: '房间名称不能为空', trigger: 'blur' }],
|
||||
floorNum: [{ required: true, message: '楼层不能为空', trigger: 'blur' }],
|
||||
actualBeds: [{ required: true, message: '床位数量不能为空', trigger: 'change' }],
|
||||
roomType: [{ required: true, message: '房间类型不能为空', trigger: 'change' }],
|
||||
roomStatus: [{ required: true, message: '房间状态不能为空', trigger: 'change' }]
|
||||
});
|
||||
|
||||
// 监听visible属性变化
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
(val) => {
|
||||
visible.value = val;
|
||||
if (val) {
|
||||
// 完全重置表单,确保没有残留数据
|
||||
resetForm();
|
||||
|
||||
// 重新设置基本参数
|
||||
form.floorNum = props.floorNum;
|
||||
form.buildingId = props.buildingId;
|
||||
|
||||
// 如果是编辑模式且有room数据,填充表单数据
|
||||
if (props.room) {
|
||||
Object.keys(form).forEach((key) => {
|
||||
if (key in props.room) {
|
||||
form[key] = props.room[key];
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// 监听visible变化,同步回父组件
|
||||
watch(
|
||||
() => visible.value,
|
||||
(val) => {
|
||||
emit('update:modelValue', val);
|
||||
}
|
||||
);
|
||||
|
||||
// 关闭对话框
|
||||
const closeDialog = () => {
|
||||
visible.value = false;
|
||||
resetForm();
|
||||
};
|
||||
|
||||
// 重置表单
|
||||
const resetForm = () => {
|
||||
// 使用深拷贝确保完全重置
|
||||
Object.keys(form).forEach((key) => {
|
||||
form[key] = initFormData[key];
|
||||
});
|
||||
formRef.value?.resetFields();
|
||||
};
|
||||
|
||||
// 提交表单
|
||||
const submitForm = () => {
|
||||
formRef.value?.validate(async (valid) => {
|
||||
if (valid) {
|
||||
try {
|
||||
console.log(props.roomId);
|
||||
if (props.roomId) {
|
||||
// 修改
|
||||
form.id = props.roomId;
|
||||
await updateDormRoom(form);
|
||||
} else {
|
||||
// 新增
|
||||
await addDormRoom(form);
|
||||
}
|
||||
closeDialog();
|
||||
emit('success');
|
||||
} catch (error) {
|
||||
console.error('操作失败', error);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 添加默认导出以支持导入
|
||||
defineOptions({
|
||||
name: 'RoomDialog'
|
||||
});
|
||||
</script>
|
||||
File diff suppressed because it is too large.
Load diff
+2
-2
@@ -47,8 +47,8 @@
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref, reactive, computed, watch, onMounted } from 'vue';
|
||||
import { getTeacherList } from '@/api/xsssgly/dormManager';
|
||||
import { addDormManager, updateDormManager } from '@/api/ssglyxx/dormManager/index';
|
||||
import { getTeacherList } from '@/api/xgss/xsssgly/dormManager';
|
||||
import { addDormManager, updateDormManager } from '@/api/xgss/ssglyxx/dormManager/index';
|
||||
import { ElMessage } from 'element-plus';
|
||||
|
||||
export default defineComponent({
|
||||
File renamed without changes.
@@ -0,0 +1,440 @@
|
||||
<template>
|
||||
<el-dialog title="分配管理范围" v-model="dialogVisible" width="500px" append-to-body>
|
||||
<el-form ref="formRef" :model="form" label-width="100px">
|
||||
<el-form-item label="管理员姓名">
|
||||
<span>{{ admin?.name }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item label="管理范围" prop="scope">
|
||||
<!-- 校区类型使用原有的多选下拉框 -->
|
||||
<el-select
|
||||
v-if="type === 'campus'"
|
||||
v-model="form.scope"
|
||||
placeholder="请选择管理范围"
|
||||
style="width: 100%"
|
||||
clearable
|
||||
filterable
|
||||
multiple
|
||||
collapse-tags
|
||||
default-expand-all
|
||||
collapse-tags-tooltip
|
||||
>
|
||||
<el-option v-for="item in scopeOptions" :key="item.value" :label="item.label" :value="item.value" />
|
||||
</el-select>
|
||||
|
||||
<!-- 宿舍区类型使用树形选择器 -->
|
||||
<el-tree-select
|
||||
v-else-if="type === 'area'"
|
||||
v-model="form.scope"
|
||||
placeholder="请选择宿舍区"
|
||||
style="width: 100%"
|
||||
clearable
|
||||
filterable
|
||||
multiple
|
||||
collapse-tags
|
||||
collapse-tags-tooltip
|
||||
default-expand-all
|
||||
:data="treeData"
|
||||
:props="{
|
||||
children: 'children',
|
||||
label: 'label',
|
||||
disabled: 'disabled',
|
||||
value: 'id'
|
||||
}"
|
||||
check-strictly
|
||||
/>
|
||||
|
||||
<!-- 宿舍楼类型使用新的树形选择器 -->
|
||||
<el-tree-select
|
||||
v-else-if="type === 'building'"
|
||||
v-model="form.scope"
|
||||
placeholder="请选择宿舍楼"
|
||||
style="width: 100%"
|
||||
clearable
|
||||
filterable
|
||||
multiple
|
||||
collapse-tags
|
||||
collapse-tags-tooltip
|
||||
default-expand-all
|
||||
:data="buildingTreeData"
|
||||
:props="{
|
||||
children: 'children',
|
||||
label: 'label',
|
||||
disabled: 'disabled',
|
||||
value: 'id'
|
||||
}"
|
||||
check-strictly
|
||||
/>
|
||||
|
||||
<!-- 其他类型保持原有的单选下拉框 -->
|
||||
<el-select v-else v-model="form.scope" placeholder="请选择管理范围" style="width: 100%" clearable filterable>
|
||||
<el-option v-for="item in scopeOptions" :key="item.value" :label="item.label" :value="item.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button type="primary" @click="submitForm">确 定</el-button>
|
||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref, reactive, computed, watch, onMounted } from 'vue';
|
||||
import { listDormCampus } from '@/api/xgss/xqxx/dormCampus';
|
||||
import { getAreaTree } from '@/api/xgss/ssqxx/dormArea';
|
||||
import { getBuildingTree } from '@/api/xgss/sslxx/dormBuilding';
|
||||
import type { TreeSelectVo } from '@/api/xgss/ssqxx/dormArea/types';
|
||||
import type { BuildingTreeSelectVo } from '@/api/xgss/sslxx/dormBuilding/types';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'ScopeDialog',
|
||||
props: {
|
||||
modelValue: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
admin: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
emits: ['update:modelValue', 'submit'],
|
||||
setup(props, { emit }) {
|
||||
const dialogVisible = computed({
|
||||
get: () => props.modelValue,
|
||||
set: (val) => {
|
||||
emit('update:modelValue', val);
|
||||
}
|
||||
});
|
||||
|
||||
// 判断是否为校区类型的计算属性
|
||||
const isCampusType = computed(() => props.type === 'campus');
|
||||
// 判断是否为宿舍区类型的计算属性
|
||||
const isAreaType = computed(() => props.type === 'area');
|
||||
// 判断是否为宿舍楼类型的计算属性
|
||||
const isBuildingType = computed(() => props.type === 'building');
|
||||
|
||||
const formRef = ref();
|
||||
const form = reactive({
|
||||
scope: isCampusType.value || isAreaType.value || isBuildingType.value ? [] : ''
|
||||
});
|
||||
|
||||
// 下拉选项数据
|
||||
const scopeOptions = ref<any[]>([]);
|
||||
|
||||
// 宿舍区树形数据
|
||||
const treeData = ref<TreeSelectVo[]>([]);
|
||||
|
||||
// 宿舍楼树形数据
|
||||
const buildingTreeData = ref<BuildingTreeSelectVo[]>([]);
|
||||
|
||||
// 标记是否完成数据加载的状态
|
||||
const dataLoaded = ref(false);
|
||||
|
||||
// 处理scope字符串为数组的辅助函数(校区使用)
|
||||
const parseScopeString = (scopeString: string) => {
|
||||
if (!scopeString) return [];
|
||||
// 尝试按逗号分隔并提取ID (假设格式为: "东校区,西校区")
|
||||
return scopeString
|
||||
.split(',')
|
||||
.map((item) => {
|
||||
// 在校区选项中查找匹配的校区名称
|
||||
const option = scopeOptions.value.find((opt) => opt.label === item.trim());
|
||||
return option ? option.value : null;
|
||||
})
|
||||
.filter((id) => id !== null); // 过滤掉未找到的项
|
||||
};
|
||||
|
||||
// 处理宿舍区scope字符串为数组的辅助函数
|
||||
const parseAreaScopeString = (scopeString: string) => {
|
||||
if (!scopeString) return [];
|
||||
// 尝试按逗号分隔宿舍区名称
|
||||
const areaNames = scopeString.split(',').map((item) => item.trim());
|
||||
|
||||
// 在树形数据中查找匹配的宿舍区ID
|
||||
const findAreaIds = (nodes: TreeSelectVo[]): number[] => {
|
||||
if (!nodes || nodes.length === 0) return [];
|
||||
|
||||
let result: number[] = [];
|
||||
|
||||
for (const node of nodes) {
|
||||
// 如果节点标签匹配任何一个宿舍区名称,添加该节点ID
|
||||
if (areaNames.includes(node.label)) {
|
||||
result.push(node.id);
|
||||
}
|
||||
|
||||
// 如果有子节点,递归查找
|
||||
if (node.children && node.children.length > 0) {
|
||||
result = [...result, ...findAreaIds(node.children)];
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
return findAreaIds(treeData.value);
|
||||
};
|
||||
|
||||
// 处理宿舍楼scope字符串为数组的辅助函数
|
||||
const parseBuildingScopeString = (scopeString: string) => {
|
||||
if (!scopeString) return [];
|
||||
// 尝试按逗号分隔宿舍楼名称
|
||||
const buildingNames = scopeString.split(',').map((item) => item.trim());
|
||||
|
||||
// 在树形数据中查找匹配的宿舍楼ID
|
||||
const findBuildingIds = (nodes: BuildingTreeSelectVo[]): number[] => {
|
||||
if (!nodes || nodes.length === 0) return [];
|
||||
|
||||
let result: number[] = [];
|
||||
|
||||
for (const node of nodes) {
|
||||
// 如果是宿舍楼节点,且名称匹配,添加该节点ID
|
||||
if (node.nodeType === 'building' && buildingNames.includes(node.label)) {
|
||||
result.push(node.id);
|
||||
}
|
||||
|
||||
// 如果有子节点,递归查找
|
||||
if (node.children && node.children.length > 0) {
|
||||
result = [...result, ...findBuildingIds(node.children)];
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
return findBuildingIds(buildingTreeData.value);
|
||||
};
|
||||
|
||||
// 根据类型加载选项
|
||||
const loadScopeOptions = async () => {
|
||||
dataLoaded.value = false;
|
||||
if (isCampusType.value) {
|
||||
// 调用API获取校区列表
|
||||
try {
|
||||
const res = await listDormCampus();
|
||||
// 适配el-select格式
|
||||
console.log('校区数据加载成功:', res);
|
||||
scopeOptions.value = (res.rows || []).map((item: any) => ({
|
||||
value: item.id,
|
||||
label: item.campusName
|
||||
}));
|
||||
// 校区支持多选,初始化为数组
|
||||
form.scope = Array.isArray(form.scope) ? form.scope : [];
|
||||
dataLoaded.value = true;
|
||||
// 数据加载完成后,处理当前管理员的scope
|
||||
handleAdminScope();
|
||||
} catch (error) {
|
||||
console.error('加载校区数据失败:', error);
|
||||
dataLoaded.value = true;
|
||||
}
|
||||
} else if (isAreaType.value) {
|
||||
// 宿舍区类型时加载树形数据
|
||||
try {
|
||||
const res = await getAreaTree();
|
||||
if (res && res.data) {
|
||||
treeData.value = res.data;
|
||||
console.log('宿舍区树形数据加载成功:', treeData.value);
|
||||
}
|
||||
// 宿舍区使用树形选择,支持多选
|
||||
form.scope = Array.isArray(form.scope) ? form.scope : [];
|
||||
dataLoaded.value = true;
|
||||
// 数据加载完成后,处理当前管理员的scope
|
||||
handleAdminScope();
|
||||
} catch (error) {
|
||||
console.error('加载树形数据失败:', error);
|
||||
dataLoaded.value = true;
|
||||
}
|
||||
} else if (isBuildingType.value) {
|
||||
// 宿舍楼类型时加载树形数据
|
||||
try {
|
||||
const res = await getBuildingTree();
|
||||
if (res && res.data) {
|
||||
buildingTreeData.value = res.data;
|
||||
console.log('宿舍楼树形数据加载成功:', buildingTreeData.value);
|
||||
}
|
||||
// 宿舍楼使用树形选择,支持多选
|
||||
form.scope = Array.isArray(form.scope) ? form.scope : [];
|
||||
dataLoaded.value = true;
|
||||
// 数据加载完成后,处理当前管理员的scope
|
||||
handleAdminScope();
|
||||
} catch (error) {
|
||||
console.error('加载宿舍楼树形数据失败:', error);
|
||||
dataLoaded.value = true;
|
||||
}
|
||||
} else if (props.type === 'building') {
|
||||
// 这段代码已被上面的isBuildingType.value条件替代,保留此处以兼容旧代码
|
||||
// 将在下一次重构时移除
|
||||
dataLoaded.value = true;
|
||||
// 数据加载完成后,处理当前管理员的scope
|
||||
handleAdminScope();
|
||||
} else {
|
||||
scopeOptions.value = [];
|
||||
dataLoaded.value = true;
|
||||
// 数据加载完成后,处理当前管理员的scope
|
||||
handleAdminScope();
|
||||
}
|
||||
};
|
||||
|
||||
// 创建单独的函数处理admin的scope,确保在数据加载完成后执行
|
||||
const handleAdminScope = () => {
|
||||
const admin = props.admin;
|
||||
if (!admin || !dataLoaded.value) return;
|
||||
|
||||
// 根据类型决定如何设置scope值
|
||||
if (isCampusType.value) {
|
||||
// 校区类型 - 支持多选
|
||||
if (admin.scope) {
|
||||
// 判断scope的类型
|
||||
if (Array.isArray(admin.scope)) {
|
||||
// 如果已经是数组,直接使用
|
||||
form.scope = admin.scope;
|
||||
} else if (typeof admin.scope === 'string') {
|
||||
// 如果是字符串,需要解析
|
||||
form.scope = parseScopeString(admin.scope);
|
||||
} else {
|
||||
// 其他情况,初始化为空数组
|
||||
form.scope = [];
|
||||
}
|
||||
console.log('校区管理员回显数据:', form.scope);
|
||||
} else {
|
||||
form.scope = [];
|
||||
}
|
||||
} else if (isAreaType.value) {
|
||||
// 宿舍区类型 - 支持多选
|
||||
if (admin.scope) {
|
||||
if (Array.isArray(admin.scope)) {
|
||||
// 如果已经是数组,直接使用
|
||||
form.scope = admin.scope;
|
||||
} else if (typeof admin.scope === 'string') {
|
||||
// 如果是字符串,需要解析为树形选择器所需的ID数组
|
||||
form.scope = parseAreaScopeString(admin.scope);
|
||||
} else {
|
||||
form.scope = [];
|
||||
}
|
||||
console.log('宿舍区管理员回显数据:', form.scope);
|
||||
} else {
|
||||
form.scope = [];
|
||||
}
|
||||
} else if (isBuildingType.value) {
|
||||
// 宿舍楼类型 - 支持多选
|
||||
if (admin.scope) {
|
||||
if (Array.isArray(admin.scope)) {
|
||||
// 如果已经是数组,直接使用
|
||||
form.scope = admin.scope;
|
||||
} else if (typeof admin.scope === 'string') {
|
||||
// 如果是字符串,需要解析为树形选择器所需的ID数组
|
||||
form.scope = parseBuildingScopeString(admin.scope);
|
||||
} else {
|
||||
form.scope = [];
|
||||
}
|
||||
console.log('宿舍楼管理员回显数据:', form.scope);
|
||||
} else {
|
||||
form.scope = [];
|
||||
}
|
||||
} else {
|
||||
// 其他类型 - 单选
|
||||
form.scope = admin.scope || '';
|
||||
}
|
||||
};
|
||||
|
||||
// 监听type变化,动态加载选项
|
||||
watch(
|
||||
() => props.type,
|
||||
() => {
|
||||
loadScopeOptions();
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
// 监听对话框可见性,确保每次打开时都重新加载数据
|
||||
watch(
|
||||
() => dialogVisible.value,
|
||||
(newVal) => {
|
||||
if (newVal) {
|
||||
// 对话框打开时,确保重新加载数据
|
||||
loadScopeOptions();
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// 监听admin变化
|
||||
watch(
|
||||
() => props.admin,
|
||||
() => {
|
||||
// 如果数据已加载完成,则立即处理admin的scope
|
||||
if (dataLoaded.value) {
|
||||
handleAdminScope();
|
||||
}
|
||||
// 否则,会在loadScopeOptions完成后自动调用handleAdminScope
|
||||
},
|
||||
{ deep: true }
|
||||
);
|
||||
|
||||
const submitForm = () => {
|
||||
// 将form.scope转换为API所需的格式
|
||||
let result;
|
||||
if (Array.isArray(form.scope)) {
|
||||
// 对于多选,发送选中的ID数组
|
||||
result = {
|
||||
sourceIds: form.scope
|
||||
};
|
||||
} else {
|
||||
// 对于单选,将字符串ID包装在数组中
|
||||
result = {
|
||||
sourceIds: form.scope ? [form.scope] : []
|
||||
};
|
||||
}
|
||||
emit('submit', result);
|
||||
dialogVisible.value = false;
|
||||
};
|
||||
|
||||
// 在对话框打开时,重置数据状态,防止旧数据影响新的显示
|
||||
watch(
|
||||
() => dialogVisible.value,
|
||||
(newVal) => {
|
||||
if (newVal) {
|
||||
// 对话框打开时,先清空数据,确保重新加载
|
||||
scopeOptions.value = [];
|
||||
treeData.value = [];
|
||||
buildingTreeData.value = [];
|
||||
dataLoaded.value = false;
|
||||
|
||||
// 根据类型设置默认值
|
||||
if (isCampusType.value || isAreaType.value || isBuildingType.value) {
|
||||
form.scope = [];
|
||||
} else {
|
||||
form.scope = '';
|
||||
}
|
||||
|
||||
// 然后加载新数据
|
||||
loadScopeOptions();
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
return {
|
||||
dialogVisible,
|
||||
form,
|
||||
formRef,
|
||||
scopeOptions,
|
||||
treeData,
|
||||
buildingTreeData,
|
||||
submitForm,
|
||||
isCampusType,
|
||||
type: computed(() => props.type)
|
||||
};
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.dialog-footer {
|
||||
text-align: right;
|
||||
}
|
||||
</style>
|
||||
@@ -218,11 +218,17 @@
|
||||
import { ref, reactive, onMounted } from 'vue';
|
||||
import AdminDialog from './components/AdminDialog.vue';
|
||||
import ScopeDialog from './components/ScopeDialog.vue';
|
||||
import axios from 'axios';
|
||||
import { listDormManager } from '@/api/ssglyxx/dormManager';
|
||||
import { delDormManager } from '@/api/ssglyxx/dormManager';
|
||||
import request from '@/utils/request';
|
||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||
|
||||
// API路径
|
||||
const apiUrl = {
|
||||
list: '/ssglyxx/dormManager/list',
|
||||
delete: '/ssglyxx/dormManager/',
|
||||
assignScope: '/glysqgx/dormManagerSource/assignScope',
|
||||
getScopeNames: '/glysqgx/dormManagerSource/getScopeNames'
|
||||
};
|
||||
|
||||
const activeTab = ref('school');
|
||||
|
||||
// 各tab独立检索表单
|
||||
@@ -287,38 +293,80 @@ async function fetchAdmins(type: string) {
|
||||
Object.keys(params).forEach((key) => {
|
||||
if (params[key] === '' || params[key] === undefined) delete params[key];
|
||||
});
|
||||
// const res = await axios.get('/ssglyxx/dormManager/list', { params });
|
||||
const res = await listDormManager(params);
|
||||
// 兼容TableDataInfo和R结构
|
||||
let data = [];
|
||||
if (Array.isArray(res.data)) {
|
||||
data = res.data;
|
||||
} else if (res.rows) {
|
||||
data = res?.rows;
|
||||
}
|
||||
// console.log(data);
|
||||
const mappedData = data.map((item: any) => ({
|
||||
id: item.id,
|
||||
name: item.managerName, // 映射
|
||||
phone: item.phone,
|
||||
sex: typeof item.gender === 'number' ? item.gender : Number(item.gender), // gender转数字
|
||||
remark: item.remark
|
||||
// 其他字段按需添加
|
||||
}));
|
||||
// console.log(mappedData);
|
||||
switch (type) {
|
||||
case 'school':
|
||||
schoolAdmins.value = mappedData;
|
||||
break;
|
||||
case 'campus':
|
||||
campusAdmins.value = mappedData;
|
||||
break;
|
||||
case 'area':
|
||||
areaAdmins.value = mappedData;
|
||||
break;
|
||||
case 'building':
|
||||
buildingAdmins.value = mappedData;
|
||||
break;
|
||||
|
||||
try {
|
||||
// 获取管理员列表
|
||||
const res = await request({
|
||||
url: apiUrl.list,
|
||||
method: 'get',
|
||||
params
|
||||
});
|
||||
|
||||
// 兼容TableDataInfo和R结构
|
||||
let data = [];
|
||||
if (Array.isArray(res.data)) {
|
||||
data = res.data;
|
||||
} else if (res.rows) {
|
||||
data = res?.rows;
|
||||
}
|
||||
|
||||
// 预处理管理员数据
|
||||
const mappedData = await Promise.all(
|
||||
data.map(async (item: any) => {
|
||||
// 基础字段映射
|
||||
const manager = {
|
||||
id: item.id,
|
||||
name: item.managerName, // 映射
|
||||
phone: item.phone,
|
||||
sex: typeof item.gender === 'number' ? item.gender : Number(item.gender), // gender转数字
|
||||
remark: item.remark,
|
||||
scope: '' // 初始化scope字段
|
||||
};
|
||||
|
||||
// 非全校级管理员需要获取授权范围
|
||||
if (managerType > 1) {
|
||||
try {
|
||||
// 获取授权范围名称
|
||||
const scopeRes = await request({
|
||||
url: apiUrl.getScopeNames,
|
||||
method: 'get',
|
||||
params: {
|
||||
managerId: item.id,
|
||||
managerType: managerType
|
||||
}
|
||||
});
|
||||
|
||||
console.log(scopeRes.msg);
|
||||
if (scopeRes) {
|
||||
manager.scope = scopeRes.msg;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取管理员授权范围失败:', error);
|
||||
}
|
||||
}
|
||||
|
||||
return manager;
|
||||
})
|
||||
);
|
||||
|
||||
// 更新对应类型的管理员数据
|
||||
switch (type) {
|
||||
case 'school':
|
||||
schoolAdmins.value = mappedData;
|
||||
break;
|
||||
case 'campus':
|
||||
campusAdmins.value = mappedData;
|
||||
break;
|
||||
case 'area':
|
||||
areaAdmins.value = mappedData;
|
||||
break;
|
||||
case 'building':
|
||||
buildingAdmins.value = mappedData;
|
||||
break;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取管理员列表失败:', error);
|
||||
ElMessage.error('获取管理员列表失败');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -336,8 +384,6 @@ function onAdd(type: string) {
|
||||
dialogVisible.value = true;
|
||||
}
|
||||
function onEdit(type: string, admin: any) {
|
||||
console.log('onEdit=============');
|
||||
console.log(admin);
|
||||
dialogMode.value = 'edit';
|
||||
currentAdmin.value = { ...admin };
|
||||
activeTab.value = type; // 确保当前标签页与编辑的管理员类型一致
|
||||
@@ -352,7 +398,10 @@ async function onDelete(type: string, admin: any) {
|
||||
})
|
||||
.then(async () => {
|
||||
try {
|
||||
await delDormManager(admin.id);
|
||||
await request({
|
||||
url: apiUrl.delete + admin.id,
|
||||
method: 'delete'
|
||||
});
|
||||
ElMessage.success('删除成功');
|
||||
fetchAdmins(type); // 删除后刷新
|
||||
} catch (e) {
|
||||
@@ -369,23 +418,48 @@ function onScope(type: string, admin: any) {
|
||||
scopeDialogVisible.value = true;
|
||||
}
|
||||
function onDialogSubmit(admin: any) {
|
||||
// TODO: 新增/编辑时调用API,成功后刷新
|
||||
const list = getListByType(activeTab.value);
|
||||
if (dialogMode.value === 'add') {
|
||||
admin.id = Date.now();
|
||||
list.value.push(admin);
|
||||
} else {
|
||||
const idx = list.value.findIndex((a: any) => a.id === admin.id);
|
||||
if (idx !== -1) list.value[idx] = { ...admin };
|
||||
}
|
||||
// 修改后立即刷新列表数据
|
||||
fetchAdmins(activeTab.value);
|
||||
dialogVisible.value = false;
|
||||
}
|
||||
function onScopeSubmit(scope: string) {
|
||||
// TODO: 分配范围时调用API,成功后刷新
|
||||
const list = getListByType(scopeType.value);
|
||||
const idx = list.value.findIndex((a: any) => a.id === currentAdmin.value.id);
|
||||
if (idx !== -1) list.value[idx].scope = scope;
|
||||
scopeDialogVisible.value = false;
|
||||
async function onScopeSubmit(scope: any) {
|
||||
try {
|
||||
// 将scope类型转换为数字
|
||||
let authType = 1;
|
||||
switch (scopeType.value) {
|
||||
case 'school':
|
||||
authType = 1;
|
||||
break;
|
||||
case 'campus':
|
||||
authType = 2;
|
||||
break;
|
||||
case 'area':
|
||||
authType = 3;
|
||||
break;
|
||||
case 'building':
|
||||
authType = 4;
|
||||
break;
|
||||
}
|
||||
|
||||
// 调用API
|
||||
await request({
|
||||
url: apiUrl.assignScope,
|
||||
method: 'post',
|
||||
params: {
|
||||
managerId: currentAdmin.value.id,
|
||||
authType: authType
|
||||
},
|
||||
data: scope.sourceIds
|
||||
});
|
||||
|
||||
// 成功后刷新数据并提示
|
||||
ElMessage.success('管理范围分配成功');
|
||||
fetchAdmins(scopeType.value);
|
||||
scopeDialogVisible.value = false;
|
||||
} catch (error) {
|
||||
ElMessage.error('管理范围分配失败');
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
function getListByType(type: string) {
|
||||
switch (type) {
|
||||
@@ -1,153 +0,0 @@
|
||||
<template>
|
||||
<el-dialog title="分配管理范围" v-model="dialogVisible" width="500px" append-to-body>
|
||||
<el-form ref="formRef" :model="form" label-width="100px">
|
||||
<el-form-item label="管理员姓名">
|
||||
<span>{{ admin?.name }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item label="管理范围" prop="scope">
|
||||
<el-select
|
||||
v-model="form.scope"
|
||||
placeholder="请选择管理范围"
|
||||
style="width: 100%"
|
||||
clearable
|
||||
filterable
|
||||
:multiple="isCampusType"
|
||||
collapse-tags
|
||||
collapse-tags-tooltip
|
||||
>
|
||||
<el-option v-for="item in scopeOptions" :key="item.value" :label="item.label" :value="item.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button type="primary" @click="submitForm">确 定</el-button>
|
||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref, reactive, computed, watch, onMounted } from 'vue';
|
||||
import { listDormCampus } from '@/api/xqxx/dormCampus';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'ScopeDialog',
|
||||
props: {
|
||||
modelValue: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
admin: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
emits: ['update:modelValue', 'submit'],
|
||||
setup(props, { emit }) {
|
||||
const dialogVisible = computed({
|
||||
get: () => props.modelValue,
|
||||
set: (val) => {
|
||||
emit('update:modelValue', val);
|
||||
}
|
||||
});
|
||||
|
||||
// 判断是否为校区类型的计算属性
|
||||
const isCampusType = computed(() => props.type === 'campus');
|
||||
|
||||
const formRef = ref();
|
||||
const form = reactive({
|
||||
scope: isCampusType.value ? [] : ''
|
||||
});
|
||||
|
||||
// 选项数据
|
||||
const scopeOptions = ref<any[]>([]);
|
||||
|
||||
// 根据类型加载选项
|
||||
const loadScopeOptions = async () => {
|
||||
if (isCampusType.value) {
|
||||
// 调用API获取校区列表
|
||||
const res = await listDormCampus();
|
||||
// 适配el-select格式
|
||||
console.log(res);
|
||||
scopeOptions.value = (res.rows || []).map((item: any) => ({
|
||||
value: item.id,
|
||||
label: item.campusName
|
||||
}));
|
||||
// 校区支持多选,初始化为数组
|
||||
form.scope = Array.isArray(form.scope) ? form.scope : [];
|
||||
} else if (props.type === 'area') {
|
||||
scopeOptions.value = [
|
||||
{ value: '1号宿舍区', label: '1号宿舍区' },
|
||||
{ value: '2号宿舍区', label: '2号宿舍区' },
|
||||
{ value: '3号宿舍区', label: '3号宿舍区' }
|
||||
];
|
||||
// 非校区为单选,初始化为字符串
|
||||
form.scope = Array.isArray(form.scope) ? '' : form.scope;
|
||||
} else if (props.type === 'building') {
|
||||
scopeOptions.value = [
|
||||
{ value: '1号楼', label: '1号楼' },
|
||||
{ value: '2号楼', label: '2号楼' },
|
||||
{ value: '3号楼', label: '3号楼' },
|
||||
{ value: '4号楼', label: '4号楼' }
|
||||
];
|
||||
// 非校区为单选,初始化为字符串
|
||||
form.scope = Array.isArray(form.scope) ? '' : form.scope;
|
||||
} else {
|
||||
scopeOptions.value = [];
|
||||
}
|
||||
};
|
||||
|
||||
// 监听type变化,动态加载选项
|
||||
watch(
|
||||
() => props.type,
|
||||
() => {
|
||||
loadScopeOptions();
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
watch(
|
||||
() => props.admin,
|
||||
(newVal) => {
|
||||
if (newVal && newVal.scope) {
|
||||
// 根据类型决定如何设置scope值
|
||||
if (isCampusType.value) {
|
||||
// 校区支持多选,确保scope是数组
|
||||
form.scope = Array.isArray(newVal.scope) ? newVal.scope : [newVal.scope];
|
||||
} else {
|
||||
// 其他类型是单选
|
||||
form.scope = newVal.scope;
|
||||
}
|
||||
}
|
||||
},
|
||||
{ deep: true, immediate: true }
|
||||
);
|
||||
|
||||
const submitForm = () => {
|
||||
emit('submit', form.scope);
|
||||
dialogVisible.value = false;
|
||||
};
|
||||
|
||||
return {
|
||||
dialogVisible,
|
||||
form,
|
||||
formRef,
|
||||
scopeOptions,
|
||||
submitForm,
|
||||
isCampusType
|
||||
};
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.dialog-footer {
|
||||
text-align: right;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,490 @@
|
||||
<template>
|
||||
<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">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span class="section-title">已选择的学生 ({{ selectedStudents.length }}人)</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<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" />
|
||||
<el-table-column label="录取通知书号码">
|
||||
<template #default="scope">
|
||||
<el-input
|
||||
v-model="scope.row.tempNoticeNumber"
|
||||
placeholder="请输入录取通知书号码"
|
||||
:disabled="scope.row.isAutoGenerated"
|
||||
@change="handleNoticeNumberChange(scope.row, scope.$index)"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-card>
|
||||
|
||||
<!-- 录取通知书设置 -->
|
||||
<el-card class="notice-setting-card" shadow="never">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span class="section-title">录取通知书设置</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<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-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-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<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-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-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="generateNoticeNumbers">
|
||||
<el-icon><RefreshRight /></el-icon>
|
||||
批量生成通知书号
|
||||
</el-button>
|
||||
<el-button @click="clearNoticeNumbers">
|
||||
<el-icon><Delete /></el-icon>
|
||||
清空通知书号
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="预览示例">
|
||||
<el-tag type="info">{{ previewNoticeNumber }}</el-tag>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
|
||||
<!-- 批量缴费确认 -->
|
||||
<el-card class="payment-card" shadow="never">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span class="section-title">缴费确认</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<el-form
|
||||
ref="paymentFormRef"
|
||||
:model="paymentForm"
|
||||
label-width="120px"
|
||||
>
|
||||
<el-form-item label="批量确认缴费">
|
||||
<el-switch
|
||||
v-model="paymentForm.confirmPayment"
|
||||
active-text="全部已缴费"
|
||||
inactive-text="保持原状态"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="handleClose">取消</el-button>
|
||||
<el-button type="primary" :loading="submitLoading" @click="handleSubmit">
|
||||
<el-icon><Check /></el-icon>
|
||||
确认提交
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</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'
|
||||
|
||||
interface Props {
|
||||
visible: boolean
|
||||
selectedStudents: EnrollmentProcessVO[]
|
||||
}
|
||||
|
||||
interface Emits {
|
||||
(e: 'update:visible', value: boolean): void
|
||||
(e: 'success'): void
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
visible: false,
|
||||
selectedStudents: () => []
|
||||
})
|
||||
|
||||
const emit = defineEmits<Emits>()
|
||||
|
||||
// 对话框可见性
|
||||
const dialogVisible = computed({
|
||||
get: () => props.visible,
|
||||
set: (value: boolean) => {
|
||||
emit('update:visible', value)
|
||||
}
|
||||
})
|
||||
|
||||
// 表单引用和加载状态
|
||||
const settingFormRef = ref<ElFormInstance>()
|
||||
const paymentFormRef = ref<ElFormInstance>()
|
||||
const loading = ref(false)
|
||||
const submitLoading = ref(false)
|
||||
|
||||
// 学生数据,添加临时字段
|
||||
const selectedStudents = computed(() => {
|
||||
return props.selectedStudents.map(student => ({
|
||||
...student,
|
||||
tempNoticeNumber: student.noticeNumber || '',
|
||||
isAutoGenerated: false
|
||||
}))
|
||||
})
|
||||
|
||||
// 通知书设置表单
|
||||
const settingForm = reactive({
|
||||
prefix: 'HDY2025-',
|
||||
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 paymentForm = reactive({
|
||||
confirmPayment: false
|
||||
})
|
||||
|
||||
// 表单验证规则
|
||||
const settingRules = {
|
||||
startNumber: [
|
||||
{ required: true, message: '请输入起始编号', trigger: 'blur' }
|
||||
],
|
||||
digitLength: [
|
||||
{ required: true, message: '请选择数字位数', trigger: 'blur' }
|
||||
]
|
||||
}
|
||||
|
||||
// 监听对话框打开,初始化表单
|
||||
watch(() => props.visible, (newVal) => {
|
||||
if (newVal) {
|
||||
initForm()
|
||||
}
|
||||
})
|
||||
|
||||
/** 初始化表单 */
|
||||
const initForm = () => {
|
||||
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+)$/)
|
||||
if (match) {
|
||||
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()
|
||||
}
|
||||
}, 300)
|
||||
}
|
||||
|
||||
/** 生成录取通知书号 */
|
||||
const generateNoticeNumbers = () => {
|
||||
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 clearNoticeNumbers = () => {
|
||||
selectedStudents.value.forEach(student => {
|
||||
student.tempNoticeNumber = ''
|
||||
student.isAutoGenerated = false
|
||||
})
|
||||
}
|
||||
|
||||
/** 前缀变更 */
|
||||
const handlePrefixChange = () => {
|
||||
if (settingForm.autoGenerate) {
|
||||
generateNoticeNumbers()
|
||||
}
|
||||
}
|
||||
|
||||
/** 起始编号变更 */
|
||||
const handleStartNumberChange = () => {
|
||||
if (settingForm.autoGenerate) {
|
||||
generateNoticeNumbers()
|
||||
}
|
||||
}
|
||||
|
||||
/** 数字位数变更 */
|
||||
const handleDigitLengthChange = () => {
|
||||
if (settingForm.autoGenerate) {
|
||||
generateNoticeNumbers()
|
||||
}
|
||||
}
|
||||
|
||||
/** 自动生成开关变更 */
|
||||
const handleAutoGenerateChange = (val: boolean) => {
|
||||
if (val) {
|
||||
generateNoticeNumbers()
|
||||
}
|
||||
}
|
||||
|
||||
/** 手动修改通知书号 */
|
||||
const handleNoticeNumberChange = (row: any, index: number) => {
|
||||
// 标记为手动修改
|
||||
row.isAutoGenerated = false
|
||||
}
|
||||
|
||||
/** 提交处理 */
|
||||
const handleSubmit = async () => {
|
||||
if (!settingFormRef.value) return
|
||||
|
||||
try {
|
||||
await settingFormRef.value.validate()
|
||||
|
||||
// 检查是否有未填写通知书号的学生
|
||||
const invalidStudents = selectedStudents.value.filter(s => !s.tempNoticeNumber)
|
||||
if (invalidStudents.length > 0) {
|
||||
ElMessage.warning(`有${invalidStudents.length}名学生未填写录取通知书号码`)
|
||||
return
|
||||
}
|
||||
|
||||
submitLoading.value = true
|
||||
|
||||
// 处理所有选中的学生
|
||||
const promises = selectedStudents.value.map(async (student) => {
|
||||
// 录取处理
|
||||
await admissionProcess({
|
||||
processId: student.id as number,
|
||||
admissionResult: '1', // 处理通过
|
||||
noticeNumber: student.tempNoticeNumber,
|
||||
admissionOpinion: '批量录取处理'
|
||||
})
|
||||
|
||||
// 如果需要确认缴费
|
||||
if (paymentForm.confirmPayment && student.paymentStatus !== '1') {
|
||||
await confirmPayment(student.id as number)
|
||||
}
|
||||
|
||||
return student.id
|
||||
})
|
||||
|
||||
await Promise.all(promises)
|
||||
|
||||
ElMessage.success('批量录取处理成功')
|
||||
emit('success')
|
||||
handleClose()
|
||||
} catch (error) {
|
||||
console.error('批量处理提交失败:', error)
|
||||
ElMessage.error('批量处理提交失败')
|
||||
} finally {
|
||||
submitLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 关闭对话框 */
|
||||
const handleClose = () => {
|
||||
dialogVisible.value = false
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.batch-admission-dialog :deep(.el-dialog) {
|
||||
border-radius: 16px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.batch-admission-dialog :deep(.el-dialog__header) {
|
||||
background: linear-gradient(135deg, rgb(93, 135, 255) 0%, rgb(73, 190, 255) 100%);
|
||||
color: white;
|
||||
padding: 20px 24px;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.batch-admission-dialog :deep(.el-dialog__title) {
|
||||
color: white;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.batch-admission-dialog :deep(.el-dialog__headerbtn) {
|
||||
.el-dialog__close {
|
||||
color: white;
|
||||
font-size: 20px;
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&:hover {
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.batch-admission-dialog :deep(.el-dialog__body) {
|
||||
padding: 20px 24px;
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
.batch-admission-dialog :deep(.el-dialog__footer) {
|
||||
padding: 16px 24px 20px;
|
||||
background: #ffffff;
|
||||
border-top: 1px solid rgba(93, 135, 255, 0.1);
|
||||
}
|
||||
|
||||
.admission-container {
|
||||
max-height: 60vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.student-list-card,
|
||||
.notice-setting-card,
|
||||
.payment-card {
|
||||
margin-bottom: 16px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #e4e7ed;
|
||||
}
|
||||
|
||||
.student-list-card:last-child,
|
||||
.notice-setting-card:last-child,
|
||||
.payment-card:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.student-list-card :deep(.el-card__header),
|
||||
.notice-setting-card :deep(.el-card__header),
|
||||
.payment-card :deep(.el-card__header) {
|
||||
background: #f8f9fa;
|
||||
border-bottom: 1px solid #e4e7ed;
|
||||
padding: 12px 16px;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #1e293b;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.section-title::before {
|
||||
content: '';
|
||||
width: 3px;
|
||||
height: 16px;
|
||||
background: linear-gradient(135deg, rgb(93, 135, 255), rgb(73, 190, 255));
|
||||
border-radius: 2px;
|
||||
margin-right: 8px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
/* 对话框底部样式 */
|
||||
.dialog-footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
/* 滚动条样式 */
|
||||
.admission-container::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
|
||||
.admission-container::-webkit-scrollbar-track {
|
||||
background: #f1f1f1;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.admission-container::-webkit-scrollbar-thumb {
|
||||
background: #c1c1c1;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.admission-container::-webkit-scrollbar-thumb:hover {
|
||||
background: #a8a8a8;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,706 @@
|
||||
<template>
|
||||
<div class="page-container">
|
||||
<!-- 搜索区域 -->
|
||||
<el-card class="search-card">
|
||||
<el-form ref="queryFormRef" :model="queryParams" :inline="true" 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-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-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-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<div 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>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
</el-card>
|
||||
|
||||
<!-- 统计卡片 -->
|
||||
<el-row :gutter="16" class="stat-cards">
|
||||
<el-col :span="8">
|
||||
<el-card class="stat-card stat-card-primary" shadow="hover">
|
||||
<div class="stat-content">
|
||||
<div class="stat-icon">
|
||||
<el-icon><Document /></el-icon>
|
||||
</div>
|
||||
<div class="stat-info">
|
||||
<div class="stat-value">{{ statistics.total || 0 }}</div>
|
||||
<div class="stat-label">总录取数</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-card class="stat-card stat-card-warning" shadow="hover">
|
||||
<div class="stat-content">
|
||||
<div class="stat-icon">
|
||||
<el-icon><Clock /></el-icon>
|
||||
</div>
|
||||
<div class="stat-info">
|
||||
<div class="stat-value">{{ statistics.pending || 0 }}</div>
|
||||
<div class="stat-label">待处理</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-card class="stat-card stat-card-success" shadow="hover">
|
||||
<div class="stat-content">
|
||||
<div class="stat-icon">
|
||||
<el-icon><Select /></el-icon>
|
||||
</div>
|
||||
<div class="stat-info">
|
||||
<div class="stat-value">{{ statistics.completed || 0 }}</div>
|
||||
<div class="stat-label">已完成</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- 操作按钮区域 -->
|
||||
<el-card class="button-card">
|
||||
<template #header>
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</el-card>
|
||||
|
||||
<!-- 表格区域 -->
|
||||
<el-card class="table-card">
|
||||
<div class="table-container">
|
||||
<el-table
|
||||
ref="processTableRef"
|
||||
v-loading="loading"
|
||||
:data="processList"
|
||||
row-key="id"
|
||||
class="scroll-table"
|
||||
@selection-change="handleSelectionChange"
|
||||
>
|
||||
<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>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="当前状态" align="center" width="120">
|
||||
<template #default="scope">
|
||||
<dict-tag :options="zsjy_enrollment_status" :value="scope.row.currentStatus" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="缴费状态" align="center" width="100">
|
||||
<template #default="scope">
|
||||
<dict-tag :options="zsjy_payment_status" :value="scope.row.paymentStatus" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="录取通知书" align="center" width="150" prop="noticeNumber" show-overflow-tooltip />
|
||||
<el-table-column label="处理时间" align="center" width="180">
|
||||
<template #default="scope">
|
||||
<span>{{ parseTime(scope.row.admissionTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" align="center" width="220" fixed="right">
|
||||
<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-tooltip>
|
||||
<el-tooltip content="录取处理" placement="top">
|
||||
<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
|
||||
v-hasPermi="['zsjy:admission:payment']"
|
||||
link
|
||||
type="success"
|
||||
icon="CreditCard"
|
||||
:disabled="scope.row.paymentStatus === '1'"
|
||||
@click="handleConfirmPayment(scope.row)"
|
||||
/>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
|
||||
<!-- 分页 -->
|
||||
<div class="pagination-container">
|
||||
<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"
|
||||
/>
|
||||
|
||||
<!-- 批量录取处理对话框 -->
|
||||
<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 {
|
||||
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'
|
||||
|
||||
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: {
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
studentName: undefined,
|
||||
idCard: undefined,
|
||||
currentStatus: '2', // 默认只查询录取处理中状态
|
||||
paymentStatus: undefined,
|
||||
params: {}
|
||||
}
|
||||
})
|
||||
|
||||
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 statistics = ref({
|
||||
total: 0,
|
||||
pending: 0,
|
||||
completed: 0
|
||||
})
|
||||
|
||||
// 对话框状态
|
||||
const detailDialogVisible = ref(false)
|
||||
const batchAdmissionDialogVisible = ref(false)
|
||||
const currentProcess = ref<Partial<EnrollmentProcessVO>>({})
|
||||
|
||||
/** 查询录取处理中的学生列表 */
|
||||
const getList = async () => {
|
||||
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
|
||||
} catch (error) {
|
||||
console.error('获取列表失败:', error)
|
||||
|
||||
// 如果是404错误(接口不存在),使用模拟数据
|
||||
if (error?.response?.status === 404 || error?.message?.includes('404')) {
|
||||
console.warn('后端接口不存在,使用模拟数据')
|
||||
// 创建模拟数据
|
||||
processList.value = [
|
||||
{
|
||||
id: 1946510098494091300,
|
||||
enrollmentId: 1001,
|
||||
studentName: '张三',
|
||||
idCard: '330101200501010001',
|
||||
currentStatus: '2',
|
||||
infoAuditStatus: '1',
|
||||
admissionStatus: '0',
|
||||
finalAuditStatus: '0',
|
||||
paymentStatus: '0',
|
||||
isCompleted: 0,
|
||||
createTime: '2025-07-20 10:28:09'
|
||||
},
|
||||
{
|
||||
id: 1946510098494091301,
|
||||
enrollmentId: 1946510098494091266,
|
||||
studentName: '赵四',
|
||||
idCard: '340403201509091234',
|
||||
currentStatus: '2',
|
||||
infoAuditStatus: '1',
|
||||
admissionStatus: '0',
|
||||
finalAuditStatus: '0',
|
||||
paymentStatus: '0',
|
||||
isCompleted: 0,
|
||||
createTime: '2025-07-20 10:28:18'
|
||||
}
|
||||
]
|
||||
total.value = 2
|
||||
proxy?.$modal.msgWarning('后端接口暂未实现,显示模拟数据')
|
||||
} else {
|
||||
// 其他错误,设置空数据
|
||||
processList.value = []
|
||||
total.value = 0
|
||||
}
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 获取统计数据 */
|
||||
const getStatistics = async () => {
|
||||
try {
|
||||
// 基于当前流程列表计算统计数据
|
||||
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
|
||||
}, {})
|
||||
|
||||
statistics.value = {
|
||||
total: stats.length,
|
||||
pending: paymentCounts['0'] || 0, // 未缴费
|
||||
completed: paymentCounts['1'] || 0 // 已缴费
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取统计数据失败:', error)
|
||||
// 设置默认值
|
||||
statistics.value = {
|
||||
total: 0,
|
||||
pending: 0,
|
||||
completed: 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = async () => {
|
||||
queryParams.value.pageNum = 1
|
||||
try {
|
||||
await getList()
|
||||
await getStatistics()
|
||||
} catch (error) {
|
||||
console.error('搜索失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
/** 重置按钮操作 */
|
||||
const resetQuery = () => {
|
||||
queryFormRef.value?.resetFields()
|
||||
handleQuery()
|
||||
}
|
||||
|
||||
/** 多选框选中数据 */
|
||||
const handleSelectionChange = (selection: EnrollmentProcessVO[]) => {
|
||||
ids.value = selection.map(item => item.id)
|
||||
selectedStudents.value = selection
|
||||
}
|
||||
|
||||
/** 查看详情 */
|
||||
const handleDetail = (row: EnrollmentProcessVO) => {
|
||||
currentProcess.value = row
|
||||
detailDialogVisible.value = true
|
||||
}
|
||||
|
||||
/** 录取处理 */
|
||||
const handleAdmissionProcess = () => {
|
||||
if (selectedStudents.value.length === 0) {
|
||||
ElMessage.warning('请选择需要处理的学生')
|
||||
return
|
||||
}
|
||||
batchAdmissionDialogVisible.value = true
|
||||
}
|
||||
|
||||
/** 缴费确认 */
|
||||
const handleConfirmPayment = async (row: EnrollmentProcessVO) => {
|
||||
try {
|
||||
await ElMessageBox.confirm('确认该学生已完成缴费?', '缴费确认', {
|
||||
confirmButtonText: '确认',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
})
|
||||
|
||||
await confirmPayment(row.id as number)
|
||||
ElMessage.success('缴费确认成功')
|
||||
await getList()
|
||||
await getStatistics()
|
||||
} catch (error) {
|
||||
if (error !== 'cancel') {
|
||||
ElMessage.error('缴费确认失败')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** 导出按钮操作 */
|
||||
const handleExport = () => {
|
||||
proxy?.download('/zsjy/enrollment/process/export', {
|
||||
...queryParams.value,
|
||||
currentStatus: '2'
|
||||
}, `admission_students_${new Date().getTime()}.xlsx`)
|
||||
}
|
||||
|
||||
/** 刷新统计数据 */
|
||||
const handleRefreshStats = async () => {
|
||||
await getStatistics()
|
||||
ElMessage.success('统计数据已刷新')
|
||||
}
|
||||
|
||||
/** 对话框成功回调 */
|
||||
const handleDialogSuccess = async () => {
|
||||
await getList()
|
||||
await getStatistics()
|
||||
}
|
||||
|
||||
// 初始化
|
||||
onMounted(async () => {
|
||||
try {
|
||||
// 先获取列表数据,再基于列表数据计算统计
|
||||
await getList()
|
||||
await getStatistics()
|
||||
} catch (error) {
|
||||
console.error('页面初始化失败:', error)
|
||||
proxy?.$modal.msgError('页面初始化失败,请刷新页面重试')
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 页面容器样式 */
|
||||
.page-container {
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
/* 搜索卡片样式 */
|
||||
.search-card {
|
||||
margin-bottom: 10px;
|
||||
border-radius: 12px;
|
||||
border: 1px solid #e4e7ed;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.search-card :deep(.el-card__body) {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.search-btn-container {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.search-btn-container .el-form-item {
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
|
||||
/* 统计卡片样式 */
|
||||
.stat-cards {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
border-radius: 8px;
|
||||
transition: all 0.3s ease;
|
||||
height: 90px;
|
||||
}
|
||||
|
||||
.stat-card:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.stat-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 5px 12px;
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.stat-icon {
|
||||
font-size: 24px;
|
||||
margin-right: 12px;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.stat-info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
line-height: 1;
|
||||
margin-bottom: 3px;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 13px;
|
||||
opacity: 0.7;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.stat-card-primary {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.stat-card-success {
|
||||
background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.stat-card-warning {
|
||||
background: linear-gradient(135deg, #ff9a56 0%, #ffad56 100%);
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* 按钮卡片样式 */
|
||||
.button-card {
|
||||
margin-bottom: 10px;
|
||||
border-radius: 12px;
|
||||
border: 1px solid #e4e7ed;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.button-card :deep(.el-card__header) {
|
||||
border-bottom: 1px solid rgba(93, 135, 255, 0.1);
|
||||
background: linear-gradient(135deg, #fafbff 0%, #f8fafc 100%);
|
||||
padding: 16px 20px;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.toolbar-title {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #1e293b;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.toolbar-title::before {
|
||||
content: '';
|
||||
width: 4px;
|
||||
height: 20px;
|
||||
background: linear-gradient(135deg, rgb(93, 135, 255), rgb(73, 190, 255));
|
||||
border-radius: 2px;
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
.toolbar-actions {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
/* 表格卡片样式 */
|
||||
.table-card {
|
||||
margin-top: 0;
|
||||
border-radius: 12px;
|
||||
border: 1px solid #e4e7ed;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.table-card :deep(.el-card__body) {
|
||||
padding: 0px 20px 20px 20px !important;
|
||||
}
|
||||
|
||||
/* 表格容器 - 支持横向滚动 */
|
||||
.table-container {
|
||||
width: 100%;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
/* 横向滚动表格样式 */
|
||||
.scroll-table {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* 表格横向滚动条样式优化 */
|
||||
.scroll-table :deep(.el-table__body-wrapper) {
|
||||
overflow-x: auto !important; /* 自动显示横向滚动条 */
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.scroll-table :deep(.el-table__header-wrapper) {
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
/* 强制显示表格横向滚动条 - 适用于所有浏览器 */
|
||||
.scroll-table :deep(.el-table__body-wrapper) {
|
||||
scrollbar-width: thin; /* Firefox */
|
||||
scrollbar-color: #409eff #f0f0f0; /* Firefox - thumb track */
|
||||
}
|
||||
|
||||
/* Webkit 浏览器的横向滚动条样式 */
|
||||
.scroll-table :deep(.el-table__body-wrapper)::-webkit-scrollbar {
|
||||
height: 12px !important; /* 增加滚动条高度 */
|
||||
width: 12px !important;
|
||||
}
|
||||
|
||||
.scroll-table :deep(.el-table__body-wrapper)::-webkit-scrollbar-track {
|
||||
background: #f0f0f0 !important;
|
||||
border-radius: 6px !important;
|
||||
border: 1px solid #e4e7ed !important; /* 添加边框使其更明显 */
|
||||
}
|
||||
|
||||
.scroll-table :deep(.el-table__body-wrapper)::-webkit-scrollbar-thumb {
|
||||
background: #409eff !important; /* 使用主题色,更明显 */
|
||||
border-radius: 6px !important;
|
||||
border: 2px solid #f0f0f0 !important; /* 添加白色边框 */
|
||||
min-height: 20px !important; /* 确保最小高度 */
|
||||
}
|
||||
|
||||
.scroll-table :deep(.el-table__body-wrapper)::-webkit-scrollbar-thumb:hover {
|
||||
background: #337ecc !important; /* 悬停时更深的蓝色 */
|
||||
}
|
||||
|
||||
.scroll-table :deep(.el-table__body-wrapper)::-webkit-scrollbar-thumb:active {
|
||||
background: #2b6cb0 !important; /* 点击时的颜色 */
|
||||
}
|
||||
|
||||
/* 确保滚动条始终可见的额外设置 */
|
||||
.scroll-table :deep(.el-table__body-wrapper)::-webkit-scrollbar-corner {
|
||||
background: #f0f0f0;
|
||||
}
|
||||
|
||||
/* 固定列样式优化 */
|
||||
.scroll-table :deep(.el-table__fixed-right) {
|
||||
box-shadow: -2px 0 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.scroll-table :deep(.el-table__fixed-left) {
|
||||
box-shadow: 2px 0 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
/* 确保固定列在滚动时有正确的背景色 */
|
||||
.scroll-table :deep(.el-table__fixed-right .el-table__cell),
|
||||
.scroll-table :deep(.el-table__fixed-left .el-table__cell) {
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
/* 表格行悬停效果在固定列上的同步 */
|
||||
.scroll-table :deep(.el-table__row:hover .el-table__cell) {
|
||||
background-color: #f5f7fa !important;
|
||||
}
|
||||
|
||||
/* 分页容器样式 */
|
||||
.pagination-container {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
padding: 16px 20px 0 0 !important;
|
||||
background-color: #fff;
|
||||
margin-top: 0px !important;
|
||||
}
|
||||
|
||||
/* 操作按钮样式 */
|
||||
.action-buttons {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* 确保操作按钮宽度一致 */
|
||||
.action-buttons .el-button {
|
||||
width: 27px !important;
|
||||
height: 27px !important;
|
||||
padding: 0 !important;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
</style>
|
||||
@@ -1,7 +1,8 @@
|
||||
<template>
|
||||
<div class="p-2">
|
||||
<div class="page-container">
|
||||
<!-- 搜索区域 -->
|
||||
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
|
||||
<div class="search" v-show="showSearch">
|
||||
<el-card v-show="showSearch" class="search-card">
|
||||
<el-form :model="queryParams" ref="queryFormRef" :inline="true" label-width="68px">
|
||||
<el-form-item label="姓名" prop="name">
|
||||
<el-input v-model="queryParams.name" placeholder="请输入姓名" clearable style="width: 240px" @keyup.enter="handleQuery" />
|
||||
@@ -26,69 +27,96 @@
|
||||
<el-form-item label="中考地区" prop="examRegion">
|
||||
<el-input v-model="queryParams.examRegion" placeholder="请输入中考地区" clearable style="width: 240px" @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
|
||||
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
|
||||
<el-form-item label="报名状态" prop="enrollmentStatus">
|
||||
<el-select v-model="queryParams.enrollmentStatus" placeholder="请选择报名状态" clearable style="width: 240px">
|
||||
<el-option :value="ENROLLMENT_STATUS.PENDING" label="待审核" />
|
||||
<el-option :value="ENROLLMENT_STATUS.AUDITING" label="信息审核中" />
|
||||
<el-option :value="ENROLLMENT_STATUS.PROCESSING" label="录取处理中" />
|
||||
<el-option :value="ENROLLMENT_STATUS.CONFIRMING" label="待最终确认" />
|
||||
<el-option :value="ENROLLMENT_STATUS.COMPLETED" label="录取完成" />
|
||||
<el-option :value="ENROLLMENT_STATUS.REJECTED" label="已驳回" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<div 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>
|
||||
</div>
|
||||
</el-form>
|
||||
</div>
|
||||
</el-card>
|
||||
</transition>
|
||||
|
||||
<el-card shadow="never">
|
||||
<!-- 操作按钮区域 -->
|
||||
<el-card class="button-card">
|
||||
<template #header>
|
||||
<el-row :gutter="10" class="mb8">
|
||||
<el-col :span="1.5">
|
||||
<div class="card-header">
|
||||
<span class="toolbar-title">招生信息管理</span>
|
||||
<div class="toolbar-actions">
|
||||
<el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['zsjy:enrollment:info:add']">新增</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['zsjy:enrollment:info:edit']">修改</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['zsjy:enrollment:info:remove']">删除</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['zsjy:enrollment:info:export']">导出</el-button>
|
||||
</el-col>
|
||||
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
|
||||
</el-row>
|
||||
<el-button type="success" plain icon="Check" :disabled="multiple" @click="handleBatchAudit()" v-hasPermi="['zsjy:enrollment:info:edit']">批量审核</el-button>
|
||||
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</el-card>
|
||||
|
||||
<el-table v-loading="loading" :data="enrollmentInfoList" @selection-change="handleSelectionChange">
|
||||
<el-table-column type="selection" width="55" align="center" />
|
||||
<el-table-column label="主键ID" align="center" prop="id" v-if="false" />
|
||||
<el-table-column label="姓名" align="center" prop="name" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="身份证号" align="center" prop="idCard" :show-overflow-tooltip="true" width="180" />
|
||||
<el-table-column label="性别" align="center" prop="gender">
|
||||
<template #default="scope">
|
||||
<dict-tag :options="zsjy_gender" :value="scope.row.gender" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="联系电话" align="center" prop="phone1" :show-overflow-tooltip="true" width="130" />
|
||||
<el-table-column label="准考证号" align="center" prop="examNumber" :show-overflow-tooltip="true" width="130" />
|
||||
<el-table-column label="中考地区" align="center" prop="examRegion" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="中考成绩" align="center" prop="totalScore" width="100" />
|
||||
<el-table-column label="报考专业" align="center" prop="appliedMajor" :show-overflow-tooltip="true" width="120" />
|
||||
<el-table-column label="报名时间" align="center" prop="applicationTime" width="160">
|
||||
<template #default="scope">
|
||||
<span>{{ parseTime(scope.row.applicationTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="160">
|
||||
<template #default="scope">
|
||||
<el-tooltip content="查看" placement="top">
|
||||
<el-button link type="primary" icon="View" @click="handleView(scope.row)" v-hasPermi="['zsjy:enrollment:info:query']"></el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="修改" placement="top">
|
||||
<el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['zsjy:enrollment:info:edit']"></el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="删除" placement="top">
|
||||
<el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['zsjy:enrollment:info:remove']"></el-button>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!-- 表格区域 -->
|
||||
<el-card class="table-card">
|
||||
<div class="table-container">
|
||||
<el-table v-loading="loading" :data="enrollmentInfoList" class="scroll-table" @selection-change="handleSelectionChange">
|
||||
<el-table-column type="selection" width="55" align="center" />
|
||||
<el-table-column label="主键ID" align="center" prop="id" v-if="false" />
|
||||
<el-table-column label="姓名" align="center" prop="name" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="身份证号" align="center" prop="idCard" :show-overflow-tooltip="true" width="180" />
|
||||
<el-table-column label="性别" align="center" prop="gender">
|
||||
<template #default="scope">
|
||||
<dict-tag :options="zsjy_gender" :value="scope.row.gender" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="联系电话" align="center" prop="phone1" :show-overflow-tooltip="true" width="130" />
|
||||
<el-table-column label="准考证号" align="center" prop="examNumber" :show-overflow-tooltip="true" width="130" />
|
||||
<el-table-column label="中考地区" align="center" prop="examRegion" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="中考成绩" align="center" prop="totalScore" width="100" />
|
||||
<el-table-column label="报考专业" align="center" prop="appliedMajor" :show-overflow-tooltip="true" width="120" />
|
||||
<el-table-column label="报名状态" align="center" prop="enrollmentStatus" width="120">
|
||||
<template #default="scope">
|
||||
<dict-tag :options="zsjy_enrollment_status" :value="scope.row.enrollmentStatus" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="报名时间" align="center" prop="applicationTime" width="160">
|
||||
<template #default="scope">
|
||||
<span>{{ parseTime(scope.row.applicationTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" align="center" width="220" fixed="right">
|
||||
<template #default="scope">
|
||||
<div class="action-buttons">
|
||||
<el-tooltip content="查看" placement="top">
|
||||
<el-button link type="primary" icon="View" @click="handleView(scope.row)" v-hasPermi="['zsjy:enrollment:info:query']"></el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="修改" placement="top">
|
||||
<el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['zsjy:enrollment:info:edit']"></el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="审核" placement="top">
|
||||
<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-tooltip>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
|
||||
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
|
||||
<!-- 分页 -->
|
||||
<div class="pagination-container">
|
||||
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- 添加或修改招生报名信息对话框 -->
|
||||
@@ -105,7 +133,7 @@
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="身份证号" prop="idCard">
|
||||
<el-input v-model="form.idCard" placeholder="请输入身份证号" @change="onIdCardChange" />
|
||||
<el-input v-model="form.idCard" placeholder="请输入身份证号" @input="onIdCardChange" @change="onIdCardChange" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
@@ -472,15 +500,204 @@
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 审核对话框 -->
|
||||
<el-dialog :title="auditDialog.title" v-model="auditDialog.visible" width="800px" append-to-body>
|
||||
<!-- 学生信息展示部分,批量审核时隐藏 -->
|
||||
<div v-if="!isBatchAudit" class="student-info-section">
|
||||
<h3 class="section-title">学生基本信息</h3>
|
||||
<el-tabs type="card">
|
||||
<el-tab-pane label="基础信息">
|
||||
<el-descriptions :column="3" border>
|
||||
<el-descriptions-item label="姓名">{{ currentStudent.name || '' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="身份证号">{{ currentStudent.idCard || '' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="性别">
|
||||
<dict-tag :options="zsjy_gender" :value="currentStudent.gender || ''" />
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="出生日期">
|
||||
{{ parseTime(currentStudent.birthDate, '{y}-{m}-{d}') || '' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="民族">{{ currentStudent.nation || '' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="是否住校">
|
||||
<dict-tag :options="sys_yes_no" :value="currentStudent.isBoarding?.toString() || ''" />
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="联系电话">{{ currentStudent.phone1 || '' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="备用电话">{{ currentStudent.phone2 || '' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="家庭地址">{{ currentStudent.homeAddress || '' }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="中考信息">
|
||||
<el-descriptions :column="3" border>
|
||||
<el-descriptions-item label="初中毕业学校">{{ currentStudent.middleSchool || '' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="准考证号">{{ currentStudent.examNumber || '' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="中考地区">{{ currentStudent.examRegion || '' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="中考满分">{{ currentStudent.totalScoreFull || '' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="中考成绩">{{ currentStudent.totalScore || '' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="语文成绩">{{ currentStudent.chineseScore || '' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="数学成绩">{{ currentStudent.mathScore || '' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="英语成绩">{{ currentStudent.englishScore || '' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="科学成绩">{{ currentStudent.scienceScore || '' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="社会成绩">{{ currentStudent.socialScore || '' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="体育成绩">{{ currentStudent.peScore || '' }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="报考信息">
|
||||
<el-descriptions :column="3" border>
|
||||
<el-descriptions-item label="报考专业">{{ currentStudent.appliedMajor || '' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="专业代码">{{ currentStudent.majorCode || '' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="校服尺码">{{ currentStudent.uniformSize || '' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="健康状况">{{ currentStudent.healthStatus || '' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="特长爱好">{{ currentStudent.specialties || '' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="获奖情况">{{ currentStudent.awards || '' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="报名状态">
|
||||
<dict-tag :options="zsjy_enrollment_status" :value="currentStudent.enrollmentStatus || ''" />
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="报名时间">
|
||||
{{ parseTime(currentStudent.applicationTime, '{y}-{m}-{d} {h}:{i}:{s}') || '' }}
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</div>
|
||||
|
||||
<!-- 批量审核时的提示和学生列表 -->
|
||||
<div v-if="isBatchAudit" class="batch-audit-info">
|
||||
<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 }} 条信息未能加载
|
||||
</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-column label="序号" type="index" width="60" />
|
||||
<el-table-column label="姓名" prop="name" min-width="80" />
|
||||
<el-table-column label="身份证号" prop="idCard" min-width="150" />
|
||||
<el-table-column label="联系电话" prop="phone1" min-width="120" />
|
||||
<el-table-column label="中考成绩" prop="totalScore" min-width="80" />
|
||||
<el-table-column label="报考专业" prop="appliedMajor" min-width="120" />
|
||||
<el-table-column label="查看" align="center" width="80">
|
||||
<template #default="scope">
|
||||
<el-button link type="primary" icon="View" @click="viewStudentDetail(scope.row)"></el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 审核表单 -->
|
||||
<el-form ref="auditFormRef" :model="auditForm" label-width="100px" class="audit-form">
|
||||
<el-form-item label="审核结果" prop="result">
|
||||
<el-radio-group v-model="auditForm.result">
|
||||
<el-radio :value="'pass'" label="审核通过"></el-radio>
|
||||
<el-radio :value="'reject'" label="审核不通过"></el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="审核意见" prop="remark">
|
||||
<el-input v-model="auditForm.remark" type="textarea" placeholder="请输入审核意见" :rows="3" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="auditDialog.visible = false">取 消</el-button>
|
||||
<el-button type="primary" @click="submitAudit" :loading="buttonLoading">确 认</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 批量审核时查看学生详情的对话框 -->
|
||||
<el-dialog :title="'学生详细信息 - ' + (detailStudent?.name || '')" v-model="detailDialog.visible" width="800px" append-to-body>
|
||||
<div class="student-detail-section">
|
||||
<el-tabs type="card">
|
||||
<el-tab-pane label="基础信息">
|
||||
<el-descriptions :column="3" border>
|
||||
<el-descriptions-item label="姓名">{{ detailStudent.name || '' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="身份证号">{{ detailStudent.idCard || '' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="性别">
|
||||
<dict-tag :options="zsjy_gender" :value="detailStudent.gender || ''" />
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="出生日期">
|
||||
{{ parseTime(detailStudent.birthDate, '{y}-{m}-{d}') || '' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="民族">{{ detailStudent.nation || '' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="是否住校">
|
||||
<dict-tag :options="sys_yes_no" :value="detailStudent.isBoarding?.toString() || ''" />
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="联系电话">{{ detailStudent.phone1 || '' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="备用电话">{{ detailStudent.phone2 || '' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="家庭地址">{{ detailStudent.homeAddress || '' }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="中考信息">
|
||||
<el-descriptions :column="3" border>
|
||||
<el-descriptions-item label="初中毕业学校">{{ detailStudent.middleSchool || '' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="准考证号">{{ detailStudent.examNumber || '' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="中考地区">{{ detailStudent.examRegion || '' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="中考满分">{{ detailStudent.totalScoreFull || '' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="中考成绩">{{ detailStudent.totalScore || '' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="语文成绩">{{ detailStudent.chineseScore || '' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="数学成绩">{{ detailStudent.mathScore || '' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="英语成绩">{{ detailStudent.englishScore || '' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="科学成绩">{{ detailStudent.scienceScore || '' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="社会成绩">{{ detailStudent.socialScore || '' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="体育成绩">{{ detailStudent.peScore || '' }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="报考信息">
|
||||
<el-descriptions :column="3" border>
|
||||
<el-descriptions-item label="报考专业">{{ detailStudent.appliedMajor || '' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="专业代码">{{ detailStudent.majorCode || '' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="校服尺码">{{ detailStudent.uniformSize || '' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="健康状况">{{ detailStudent.healthStatus || '' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="特长爱好">{{ detailStudent.specialties || '' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="获奖情况">{{ detailStudent.awards || '' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="报名状态">
|
||||
<dict-tag :options="zsjy_enrollment_status" :value="detailStudent.enrollmentStatus || ''" />
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="报名时间">
|
||||
{{ parseTime(detailStudent.applicationTime, '{y}-{m}-{d} {h}:{i}:{s}') || '' }}
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</div>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="detailDialog.visible = false">关 闭</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup name="EnrollmentInfo" lang="ts">
|
||||
import { listEnrollmentInfo, getEnrollmentInfo, delEnrollmentInfo, addEnrollmentInfo, updateEnrollmentInfo } 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 } from 'vue';
|
||||
import { ComponentInternalInstance, getCurrentInstance, reactive, ref, toRefs, onMounted, watch, computed } from 'vue';
|
||||
import { FormInstance } from 'element-plus';
|
||||
|
||||
// 状态常量
|
||||
const ENROLLMENT_STATUS = {
|
||||
PENDING: '0', // 待审核
|
||||
AUDITING: '1', // 信息审核中
|
||||
PROCESSING: '2', // 录取处理中
|
||||
CONFIRMING: '3', // 待最终确认
|
||||
COMPLETED: '4', // 录取完成
|
||||
REJECTED: '9' // 已驳回
|
||||
};
|
||||
|
||||
interface DialogOption {
|
||||
visible: boolean;
|
||||
title: string;
|
||||
@@ -493,13 +710,14 @@ interface PageData<T, S> {
|
||||
}
|
||||
|
||||
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
||||
const { zsjy_gender, sys_yes_no, zsjy_major, zsjy_uniform_size } = toRefs<any>(
|
||||
proxy?.useDict('zsjy_gender', 'sys_yes_no', 'zsjy_major', 'zsjy_uniform_size')
|
||||
const { zsjy_gender, sys_yes_no, zsjy_major, zsjy_uniform_size, zsjy_enrollment_status } = toRefs<any>(
|
||||
proxy?.useDict('zsjy_gender', 'sys_yes_no', 'zsjy_major', 'zsjy_uniform_size', 'zsjy_enrollment_status')
|
||||
);
|
||||
|
||||
const enrollmentInfoList = ref<EnrollmentInfoVO[]>([]);
|
||||
const buttonLoading = ref(false);
|
||||
const loading = ref(true);
|
||||
const batchLoading = ref(false);
|
||||
const showSearch = ref(true);
|
||||
const ids = ref<Array<string | number>>([]);
|
||||
const single = ref(true);
|
||||
@@ -507,6 +725,34 @@ const multiple = ref(true);
|
||||
const total = ref(0);
|
||||
const majorOptions = ref<Array<{ value: string, label: string, majorCode: string }>>([]);
|
||||
|
||||
// 批量审核分页相关
|
||||
const batchCurrentPage = ref(1);
|
||||
const batchPageSize = ref(10);
|
||||
|
||||
// 计算当前页的学生列表
|
||||
const paginatedStudents = computed(() => {
|
||||
const startIndex = (batchCurrentPage.value - 1) * batchPageSize.value;
|
||||
const endIndex = startIndex + batchPageSize.value;
|
||||
return selectedStudents.value.slice(startIndex, endIndex);
|
||||
});
|
||||
|
||||
// 性别显示转换计算属性
|
||||
const genderDisplay = computed(() => {
|
||||
if (!form.value.gender) return '';
|
||||
const genderOption = zsjy_gender.value?.find(item => item.value === form.value.gender);
|
||||
return genderOption ? genderOption.label : form.value.gender;
|
||||
});
|
||||
|
||||
// 处理批量审核页面大小变化
|
||||
const handleBatchSizeChange = (val: number) => {
|
||||
batchPageSize.value = val;
|
||||
};
|
||||
|
||||
// 处理批量审核页码变化
|
||||
const handleBatchCurrentChange = (val: number) => {
|
||||
batchCurrentPage.value = val;
|
||||
};
|
||||
|
||||
const queryFormRef = ref<FormInstance>();
|
||||
const enrollmentInfoFormRef = ref<FormInstance>();
|
||||
|
||||
@@ -520,6 +766,42 @@ const viewDialog = reactive<DialogOption>({
|
||||
title: ''
|
||||
});
|
||||
|
||||
const auditDialog = reactive<DialogOption>({
|
||||
visible: false,
|
||||
title: ''
|
||||
});
|
||||
|
||||
const detailDialog = reactive<DialogOption>({
|
||||
visible: false,
|
||||
title: ''
|
||||
});
|
||||
|
||||
const auditForm = reactive({
|
||||
id: undefined as undefined | string | number,
|
||||
result: 'pass', // 审核结果,'pass' 或 'reject'
|
||||
remark: ''
|
||||
});
|
||||
|
||||
// 修复 currentStudent 的定义
|
||||
const currentStudent = reactive<Partial<EnrollmentInfoVO>>({
|
||||
id: undefined,
|
||||
name: '',
|
||||
idCard: '',
|
||||
gender: '',
|
||||
phone1: '',
|
||||
examNumber: '',
|
||||
examRegion: '',
|
||||
totalScore: undefined,
|
||||
appliedMajor: '',
|
||||
applicationTime: undefined
|
||||
});
|
||||
|
||||
// 批量审核标志
|
||||
const isBatchAudit = ref(false);
|
||||
|
||||
// 批量审核选中的学生列表
|
||||
const selectedStudents = ref<EnrollmentInfoVO[]>([]);
|
||||
|
||||
const viewForm = reactive<EnrollmentInfoForm>({
|
||||
id: undefined,
|
||||
name: undefined,
|
||||
@@ -550,6 +832,19 @@ const viewForm = reactive<EnrollmentInfoForm>({
|
||||
awards: undefined
|
||||
});
|
||||
|
||||
const detailStudent = reactive<Partial<EnrollmentInfoVO>>({
|
||||
id: undefined,
|
||||
name: '',
|
||||
idCard: '',
|
||||
gender: '',
|
||||
phone1: '',
|
||||
examNumber: '',
|
||||
examRegion: '',
|
||||
totalScore: undefined,
|
||||
appliedMajor: '',
|
||||
applicationTime: undefined
|
||||
});
|
||||
|
||||
const initFormData: EnrollmentInfoForm = {
|
||||
id: undefined,
|
||||
name: undefined,
|
||||
@@ -593,6 +888,7 @@ const data = reactive<PageData<EnrollmentInfoForm, EnrollmentInfoQuery>>({
|
||||
majorCode: undefined,
|
||||
phone1: undefined,
|
||||
examRegion: undefined,
|
||||
enrollmentStatus: ENROLLMENT_STATUS.AUDITING, // 默认只查询"信息审核中"状态的学生
|
||||
params: {}
|
||||
},
|
||||
rules: {
|
||||
@@ -631,15 +927,23 @@ const { queryParams, form, rules } = toRefs(data);
|
||||
const onIdCardChange = () => {
|
||||
const idCard = form.value.idCard;
|
||||
if (idCard && idCard.length === 18) {
|
||||
// 提取出生日期
|
||||
const year = idCard.substring(6, 10);
|
||||
const month = idCard.substring(10, 12);
|
||||
const day = idCard.substring(12, 14);
|
||||
form.value.birthDate = `${year}-${month}-${day}`;
|
||||
|
||||
// 提取性别(奇数为男M,偶数为女F)
|
||||
const genderCode = parseInt(idCard.substring(16, 17));
|
||||
form.value.gender = genderCode % 2 === 1 ? 'M' : 'F';
|
||||
try {
|
||||
// 提取出生日期
|
||||
const year = idCard.substring(6, 10);
|
||||
const month = idCard.substring(10, 12);
|
||||
const day = idCard.substring(12, 14);
|
||||
form.value.birthDate = `${year}-${month}-${day}`;
|
||||
|
||||
// 提取性别(奇数为男,偶数为女)
|
||||
const genderCode = parseInt(idCard.substring(16, 17));
|
||||
const genderValue = genderCode % 2 === 1 ? 'M' : 'F';
|
||||
|
||||
// 直接设置性别,无需额外检查
|
||||
form.value.gender = genderValue;
|
||||
console.log('设置性别为:', genderValue);
|
||||
} catch (error) {
|
||||
console.error('解析身份证信息时出错:', error);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -693,6 +997,26 @@ const getMajorOptions = () => {
|
||||
}
|
||||
};
|
||||
|
||||
/** 获取校服尺码选项 */
|
||||
const getUniformSizeOptions = () => {
|
||||
// 创建默认的校服尺码选项数组(如果字典数据不可用)
|
||||
const defaultUniformSizes = [
|
||||
{ value: 'XS', label: 'XS', remark: '加小号' },
|
||||
{ value: 'S', label: 'S', remark: '小号' },
|
||||
{ value: 'M', label: 'M', remark: '中号' },
|
||||
{ value: 'L', label: 'L', remark: '大号' },
|
||||
{ value: 'XL', label: 'XL', remark: '加大号' },
|
||||
{ value: 'XXL', label: 'XXL', remark: '加加大号' },
|
||||
{ value: 'XXXL', label: 'XXXL', remark: '加加加大号' },
|
||||
];
|
||||
|
||||
// 检查字典数据是否存在,如果不存在则使用默认值
|
||||
if (!zsjy_uniform_size.value || zsjy_uniform_size.value.length === 0) {
|
||||
console.warn('校服尺码字典数据不可用,使用默认值');
|
||||
zsjy_uniform_size.value = defaultUniformSizes;
|
||||
}
|
||||
};
|
||||
|
||||
/** 查询招生报名信息列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true;
|
||||
@@ -710,7 +1034,10 @@ const cancel = () => {
|
||||
|
||||
/** 表单重置 */
|
||||
const reset = () => {
|
||||
form.value = { ...initFormData };
|
||||
form.value = {
|
||||
...initFormData,
|
||||
enrollmentStatus: ENROLLMENT_STATUS.AUDITING // 默认设置为"信息审核中"状态
|
||||
};
|
||||
enrollmentInfoFormRef.value?.resetFields();
|
||||
};
|
||||
|
||||
@@ -723,6 +1050,8 @@ const handleQuery = () => {
|
||||
/** 重置按钮操作 */
|
||||
const resetQuery = () => {
|
||||
queryFormRef.value?.resetFields();
|
||||
// 确保重置后状态过滤条件依然生效
|
||||
queryParams.value.enrollmentStatus = ENROLLMENT_STATUS.AUDITING;
|
||||
handleQuery();
|
||||
};
|
||||
|
||||
@@ -763,26 +1092,25 @@ const submitForm = () => {
|
||||
enrollmentInfoFormRef.value?.validate(async (valid: boolean) => {
|
||||
if (valid) {
|
||||
buttonLoading.value = true;
|
||||
if (form.value.id) {
|
||||
await updateEnrollmentInfo(form.value)
|
||||
.then(() => {
|
||||
proxy?.$modal.msgSuccess('修改成功');
|
||||
dialog.visible = false;
|
||||
getList();
|
||||
})
|
||||
.finally(() => {
|
||||
buttonLoading.value = false;
|
||||
});
|
||||
} else {
|
||||
await addEnrollmentInfo(form.value)
|
||||
.then(() => {
|
||||
proxy?.$modal.msgSuccess('新增成功');
|
||||
dialog.visible = false;
|
||||
getList();
|
||||
})
|
||||
.finally(() => {
|
||||
buttonLoading.value = false;
|
||||
});
|
||||
try {
|
||||
// 如果是新增,设置默认状态为"信息审核中"
|
||||
if (!form.value.id) {
|
||||
form.value.enrollmentStatus = ENROLLMENT_STATUS.AUDITING;
|
||||
}
|
||||
|
||||
if (form.value.id) {
|
||||
await updateEnrollmentInfo(form.value);
|
||||
proxy?.$modal.msgSuccess('修改成功');
|
||||
} else {
|
||||
await addEnrollmentInfo(form.value);
|
||||
proxy?.$modal.msgSuccess('新增成功');
|
||||
}
|
||||
dialog.visible = false;
|
||||
getList();
|
||||
} catch (error) {
|
||||
proxy?.$modal.msgError(form.value.id ? '修改失败' : '新增失败');
|
||||
} finally {
|
||||
buttonLoading.value = false;
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -808,18 +1136,235 @@ const handleExport = () => {
|
||||
);
|
||||
};
|
||||
|
||||
/** 批量审核按钮操作 */
|
||||
const handleBatchAudit = async () => {
|
||||
if (ids.value.length === 0) {
|
||||
proxy?.$modal.msgError('请选择要审核的招生报名信息');
|
||||
return;
|
||||
}
|
||||
|
||||
// 加载选中的学生信息
|
||||
selectedStudents.value = [];
|
||||
buttonLoading.value = true;
|
||||
batchLoading.value = true;
|
||||
|
||||
try {
|
||||
// 使用Promise.all并行获取所有选中学生信息,更可靠
|
||||
const maxRetries = 3; // 最多重试3次
|
||||
|
||||
// 创建一个带重试功能的获取学生信息的函数
|
||||
const getStudentInfoWithRetry = async (id: string | number, retryCount = 0): Promise<any> => {
|
||||
try {
|
||||
return await getEnrollmentInfo(Number(id));
|
||||
} catch (error) {
|
||||
if (retryCount < maxRetries) {
|
||||
console.log(`获取学生信息失败,ID: ${id},正在重试第${retryCount + 1}次...`);
|
||||
await new Promise(resolve => setTimeout(resolve, 500)); // 等待500ms后重试
|
||||
return getStudentInfoWithRetry(id, retryCount + 1);
|
||||
}
|
||||
console.error(`获取学生信息失败,ID: ${id},已重试${maxRetries}次`, error);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
// 使用带重试功能的函数获取所有学生信息
|
||||
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);
|
||||
|
||||
// 验证是否获取到了所有学生
|
||||
if (selectedStudents.value.length < ids.value.length) {
|
||||
proxy?.$modal.msgWarning(`有${ids.value.length - selectedStudents.value.length}个学生信息获取失败,请重试或单独审核`);
|
||||
}
|
||||
|
||||
// 不直接赋值ids,以避免类型错误
|
||||
auditForm.id = undefined; // 批量审核时id为undefined,特殊处理
|
||||
auditForm.result = 'pass'; // 批量审核时默认设为审核通过
|
||||
auditForm.remark = '';
|
||||
auditDialog.title = '批量审核报名信息';
|
||||
isBatchAudit.value = true; // 设置为批量审核模式
|
||||
auditDialog.visible = true;
|
||||
} catch (error) {
|
||||
console.error('加载学生信息失败:', error);
|
||||
proxy?.$modal.msgError('加载学生信息失败,请稍后重试');
|
||||
} finally {
|
||||
buttonLoading.value = false;
|
||||
batchLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
/** 审核按钮操作 */
|
||||
const handleAudit = async (row: EnrollmentInfoVO) => {
|
||||
auditForm.id = row.id;
|
||||
// 设置当前学生信息
|
||||
Object.assign(currentStudent, row);
|
||||
auditForm.result = row.enrollmentStatus === ENROLLMENT_STATUS.REJECTED ? 'reject' : 'pass'; // 根据当前状态设置审核结果
|
||||
auditForm.remark = '';
|
||||
auditDialog.title = '审核报名信息';
|
||||
isBatchAudit.value = false; // 设置为单个审核模式
|
||||
auditDialog.visible = true;
|
||||
};
|
||||
|
||||
/** 提交审核 */
|
||||
const submitAudit = async () => {
|
||||
buttonLoading.value = true;
|
||||
try {
|
||||
// 根据审核结果设置状态值
|
||||
const status = auditForm.result === 'pass'
|
||||
? ENROLLMENT_STATUS.PROCESSING // 审核通过,设为"录取处理中"
|
||||
: ENROLLMENT_STATUS.REJECTED; // 审核不通过,设为"已驳回"
|
||||
|
||||
if (auditForm.id) {
|
||||
// 单个审核
|
||||
await updateEnrollmentStatus(auditForm.id, status);
|
||||
} else {
|
||||
// 批量审核 - 直接传递ids数组
|
||||
await updateEnrollmentStatus(ids.value, status);
|
||||
}
|
||||
|
||||
proxy?.$modal.msgSuccess('审核成功');
|
||||
auditDialog.visible = false;
|
||||
getList();
|
||||
} catch (error) {
|
||||
console.error('审核失败:', error);
|
||||
proxy?.$modal.msgError('审核失败,请稍后重试');
|
||||
} finally {
|
||||
buttonLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
/** 取消查看 */
|
||||
const cancelView = () => {
|
||||
viewDialog.visible = false;
|
||||
};
|
||||
|
||||
/** 查看流程按钮操作 */
|
||||
const handleViewProcess = (row: EnrollmentInfoVO) => {
|
||||
// 跳转到流程管理页面,并筛选该学生的流程
|
||||
const router = getCurrentInstance()?.appContext.app.config.globalProperties.$router;
|
||||
if (router) {
|
||||
router.push({
|
||||
path: '/zsjy/enrollmentProcess',
|
||||
query: {
|
||||
enrollmentId: row.id,
|
||||
studentName: row.name
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/** 查看学生详情按钮操作 */
|
||||
const viewStudentDetail = async (row: EnrollmentInfoVO) => {
|
||||
const res = await getEnrollmentInfo(row.id);
|
||||
Object.assign(detailStudent, res.data);
|
||||
detailDialog.visible = true;
|
||||
detailDialog.title = '学生详细信息 - ' + (detailStudent.name || '');
|
||||
};
|
||||
|
||||
// 监听对话框关闭事件,重置状态
|
||||
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;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
getList();
|
||||
getMajorOptions();
|
||||
getUniformSizeOptions(); // 添加校服尺码选项初始化
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.page-container {
|
||||
padding: 20px;
|
||||
background-color: #f5f7fa;
|
||||
}
|
||||
|
||||
.search-card {
|
||||
margin-bottom: 20px;
|
||||
padding: 20px;
|
||||
background-color: #fff;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.search-btn-container {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.button-card {
|
||||
margin-bottom: 20px;
|
||||
padding: 20px;
|
||||
background-color: #fff;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.toolbar-title {
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.toolbar-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.table-card {
|
||||
margin-bottom: 20px;
|
||||
padding: 20px;
|
||||
background-color: #fff;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.table-container {
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.scroll-table {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.pagination-container {
|
||||
text-align: right;
|
||||
padding-top: 20px;
|
||||
}
|
||||
|
||||
.form-section {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
@@ -845,4 +1390,64 @@ onMounted(() => {
|
||||
white-space: pre-wrap;
|
||||
word-break: break-all;
|
||||
}
|
||||
</style>
|
||||
|
||||
.action-buttons {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.student-info-section {
|
||||
margin-bottom: 20px;
|
||||
padding: 20px;
|
||||
background-color: #f5f7fa;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
color: #303133;
|
||||
margin-bottom: 15px;
|
||||
padding-bottom: 8px;
|
||||
border-bottom: 2px solid #409eff;
|
||||
}
|
||||
|
||||
.batch-audit-info {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.student-list-section {
|
||||
margin-top: 15px;
|
||||
padding: 15px;
|
||||
background-color: #f5f7fa;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.batch-pagination {
|
||||
margin-top: 15px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.audit-form {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.student-detail-section {
|
||||
margin-top: 20px;
|
||||
padding: 20px;
|
||||
background-color: #fff;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.selected-gender {
|
||||
margin-top: 5px;
|
||||
font-size: 12px;
|
||||
color: #67c23a;
|
||||
padding: 2px 5px;
|
||||
border-radius: 4px;
|
||||
background-color: #f0f9eb;
|
||||
display: inline-block;
|
||||
}
|
||||
</style>
|
||||
@@ -1,166 +1,104 @@
|
||||
# 录取流程状态管理模块
|
||||
# 录取流程管理模块
|
||||
|
||||
## 📋 模块说明
|
||||
## 功能概述
|
||||
|
||||
- **模块名称**: 录取流程状态管理 (Enrollment Process Management)
|
||||
- **模块简称**: enrollmentProcess
|
||||
- **对应后端**: backend/ruoyi-modules/ruoyi-zsjy/
|
||||
- **对应API**: web/src/api/zsjy/enrollmentProcess.ts
|
||||
录取流程管理模块用于管理学生从报名到最终录取的整个流程,包括信息审核、录取处理、最终确认等环节。该模块提供了完整的流程状态管理、审核操作和数据统计功能。
|
||||
|
||||
## 🎯 功能概述
|
||||
## 主要功能
|
||||
|
||||
录取流程状态管理模块提供完整的招生录取流程管理功能,包括:
|
||||
1. **流程状态管理**:查看和管理所有录取流程的状态,包括待审核、信息审核中、录取处理中、待最终确认、录取完成和已驳回等状态。
|
||||
2. **待我审核**:根据用户角色权限,显示需要当前用户审核的录取流程记录,支持信息审核、录取处理和最终确认等操作。
|
||||
3. **审核记录**:查看所有审核操作的历史记录,包括审核人、审核时间、审核意见等信息。
|
||||
|
||||
### 📊 统计概览
|
||||
- 总流程数统计
|
||||
- 待处理流程统计
|
||||
- 处理中流程统计
|
||||
- 已完成流程统计
|
||||
## 页面说明
|
||||
|
||||
### 🔍 查询功能
|
||||
- 学生姓名、身份证号模糊查询
|
||||
- 当前状态、审核状态筛选查询
|
||||
- 录取处理状态、最终审核状态查询
|
||||
- 完成状态筛选查询
|
||||
### 1. 流程状态列表页 (index.vue)
|
||||
|
||||
### 📝 流程管理
|
||||
- 流程信息查看、新增、修改、删除
|
||||
- 流程详情查看
|
||||
- 流程数据批量导出
|
||||
- 功能:查看和管理所有录取流程的状态
|
||||
- 权限:`zsjy:enrollment:process:list`
|
||||
- 操作:查看详情、审核、驳回、导出等
|
||||
|
||||
### 🔄 流程操作
|
||||
- **信息审核**: A老师进行学生报名信息审核
|
||||
- **录取处理**: B老师进行录取处理,生成通知书号码
|
||||
- **缴费确认**: 确认学生缴费状态
|
||||
- **最终审核**: 部门领导进行最终确认
|
||||
- **完成录取**: 完成整个录取流程
|
||||
- **驳回流程**: 在任意环节驳回流程
|
||||
- **重置流程**: 管理员重置流程状态
|
||||
### 2. 待我审核页面 (pending.vue)
|
||||
|
||||
### 📈 流程状态
|
||||
```
|
||||
待审核(0) → 信息审核中(1) → 录取处理中(2) → 待最终确认(3) → 录取完成(4)
|
||||
↓
|
||||
已驳回(9)
|
||||
```
|
||||
- 功能:根据用户角色权限,显示需要当前用户审核的录取流程记录
|
||||
- 权限:`zsjy:enrollment:process:pending`
|
||||
- 操作:
|
||||
- 查看详情:查看报名信息和流程状态详情
|
||||
- 审核通过:根据当前流程状态执行对应的审核通过操作
|
||||
- 审核驳回:根据当前流程状态执行对应的审核驳回操作
|
||||
- 导出:导出待审核数据
|
||||
|
||||
## 🎨 页面设计
|
||||
### 3. 我的审核记录页面 (myaudit.vue)
|
||||
|
||||
### 页面布局
|
||||
- **搜索区域**: 7个搜索条件,支持组合查询
|
||||
- **统计卡片**: 4个统计指标,渐变色彩设计
|
||||
- **表格区域**: 完整的流程信息展示
|
||||
- **操作按钮**: 查看、审核、处理、更多操作
|
||||
- 功能:查看当前用户的所有审核操作记录
|
||||
- 权限:`zsjy:enrollment:process:myaudit`
|
||||
- 操作:查看详情、导出等
|
||||
|
||||
### 对话框设计
|
||||
- **流程详情**: 完整展示流程各阶段信息
|
||||
- **信息审核**: 审核结果选择和意见填写
|
||||
- **录取处理**: 处理结果、通知书号码、处理意见
|
||||
- **最终审核**: 确认结果和审核意见
|
||||
- **驳回流程**: 驳回原因填写
|
||||
- **新增/编辑**: 基本流程信息管理
|
||||
## 数据字典
|
||||
|
||||
## 🔧 技术特性
|
||||
### 流程状态 (zsjy_enrollment_status)
|
||||
|
||||
### 前端技术
|
||||
- **Vue 3.5.13** + **TypeScript 5.8.3**
|
||||
- **Element Plus** UI组件库
|
||||
- **Composition API** 组合式API
|
||||
- **响应式设计** 适配多端
|
||||
- 0:待审核
|
||||
- 1:信息审核中
|
||||
- 2:录取处理中
|
||||
- 3:待最终确认
|
||||
- 4:录取完成
|
||||
- 9:已驳回
|
||||
|
||||
### 样式设计
|
||||
- **参考后勤管理模块** 保持风格一致
|
||||
- **渐变色卡片** 美观的统计展示
|
||||
- **悬停效果** 良好的交互体验
|
||||
- **操作按钮** 图标化设计
|
||||
### 审核状态 (zsjy_audit_status)
|
||||
|
||||
### 权限控制
|
||||
- 精细化权限控制,12个权限点
|
||||
- 不同角色不同操作权限
|
||||
- 按钮级权限控制
|
||||
- 0:待审核
|
||||
- 1:审核通过
|
||||
- 9:审核驳回
|
||||
|
||||
## 🔐 权限设计
|
||||
### 处理状态 (zsjy_process_status)
|
||||
|
||||
### 权限点列表
|
||||
- `zsjy:enrollmentProcess:list` - 查看流程列表
|
||||
- `zsjy:enrollmentProcess:query` - 查看流程详情
|
||||
- `zsjy:enrollmentProcess:add` - 新增流程
|
||||
- `zsjy:enrollmentProcess:edit` - 修改流程
|
||||
- `zsjy:enrollmentProcess:remove` - 删除流程
|
||||
- `zsjy:enrollmentProcess:export` - 导出流程
|
||||
- `zsjy:enrollmentProcess:audit` - 信息审核权限
|
||||
- `zsjy:enrollmentProcess:process` - 录取处理权限
|
||||
- `zsjy:enrollmentProcess:finalAudit` - 最终审核权限
|
||||
- `zsjy:enrollmentProcess:complete` - 完成录取权限
|
||||
- `zsjy:enrollmentProcess:reject` - 驳回流程权限
|
||||
- `zsjy:enrollmentProcess:reset` - 重置流程权限
|
||||
- 0:待处理
|
||||
- 1:处理完成
|
||||
- 9:处理驳回
|
||||
|
||||
### 角色权限建议
|
||||
```
|
||||
A老师(信息审核员):
|
||||
- list, query, audit
|
||||
## 权限说明
|
||||
|
||||
B老师(录取处理员):
|
||||
- list, query, process, confirmPayment
|
||||
- `zsjy:enrollment:process:list`:查看流程状态列表
|
||||
- `zsjy:enrollment:process:query`:查看流程状态详情
|
||||
- `zsjy:enrollment:process:audit`:执行审核操作
|
||||
- `zsjy:enrollment:process:pending`:查看待我审核列表
|
||||
- `zsjy:enrollment:process:myaudit`:查看我的审核记录
|
||||
- `zsjy:enrollment:process:export`:导出流程状态数据
|
||||
|
||||
部门领导:
|
||||
- list, query, finalAudit, complete
|
||||
## 角色权限配置
|
||||
|
||||
系统管理员:
|
||||
- 所有权限(包括 reset, reject)
|
||||
```
|
||||
- **信息审核员 (zsjy_info_auditor)**:负责信息审核环节,拥有 `zsjy:enrollment:process:audit` 权限
|
||||
- **录取处理员 (zsjy_admission_processor)**:负责录取处理环节,拥有 `zsjy:enrollment:process:audit` 权限
|
||||
- **最终确认员 (zsjy_final_confirmer)**:负责最终确认环节,拥有 `zsjy:enrollment:process:audit` 权限
|
||||
- **管理员 (admin)**:拥有所有权限,可以查看和操作所有流程
|
||||
|
||||
## 📁 文件结构
|
||||
## 开发说明
|
||||
|
||||
### 前端文件结构
|
||||
|
||||
```
|
||||
enrollmentProcess/
|
||||
├── index.vue # 主页面
|
||||
├── README.md # 说明文档
|
||||
└── components/ # 组件目录
|
||||
├── ProcessDetailDialog.vue # 流程详情对话框
|
||||
├── InfoAuditDialog.vue # 信息审核对话框
|
||||
├── AdmissionProcessDialog.vue # 录取处理对话框
|
||||
├── FinalAuditDialog.vue # 最终审核对话框
|
||||
├── RejectProcessDialog.vue # 驳回流程对话框
|
||||
└── ProcessFormDialog.vue # 新增/编辑对话框
|
||||
web/src/views/zsjy/enrollmentProcess/
|
||||
├── index.vue # 流程状态列表页
|
||||
├── pending.vue # 待我审核页面
|
||||
├── myaudit.vue # 我的审核记录页面
|
||||
├── detail.vue # 详情页面
|
||||
└── components/ # 组件目录
|
||||
├── AuditForm.vue # 审核表单组件
|
||||
└── ProcessTimeline.vue # 流程时间线组件
|
||||
```
|
||||
|
||||
## 🚀 使用说明
|
||||
### API接口
|
||||
|
||||
### 基本操作
|
||||
1. **查询流程**: 使用搜索条件筛选流程信息
|
||||
2. **查看详情**: 点击"查看"按钮查看完整流程信息
|
||||
3. **信息审核**: 对待审核状态的流程进行审核
|
||||
4. **录取处理**: 对审核通过的流程进行录取处理
|
||||
5. **最终审核**: 领导对处理完成的流程进行最终确认
|
||||
```
|
||||
web/src/api/zsjy/enrollmentProcess.ts
|
||||
```
|
||||
|
||||
### 流程操作
|
||||
1. **审核操作**: 选择审核结果(通过/驳回)并填写意见
|
||||
2. **处理操作**: 选择处理结果,填写通知书号码和处理意见
|
||||
3. **缴费确认**: 确认学生已完成缴费
|
||||
4. **完成录取**: 完成整个录取流程
|
||||
5. **驳回流程**: 在任意环节驳回流程到初始状态
|
||||
|
||||
### 高级功能
|
||||
1. **批量操作**: 选择多个流程进行批量删除
|
||||
2. **导出数据**: 根据查询条件导出Excel文件
|
||||
3. **统计刷新**: 手动刷新统计数据
|
||||
4. **流程重置**: 管理员重置流程状态(谨慎操作)
|
||||
|
||||
## 💡 注意事项
|
||||
|
||||
- 流程状态有严格的流转规则,不能跳跃式操作
|
||||
- 驳回的流程会回到初始状态,需要重新审核
|
||||
- 重置操作不可恢复,仅限管理员使用
|
||||
- 统计数据会实时更新,也可手动刷新
|
||||
- 导出功能会根据当前查询条件生成文件
|
||||
|
||||
## 🔗 相关模块
|
||||
|
||||
- 招生信息管理模块 (enrollment) - 报名信息管理
|
||||
- 系统管理模块 (system) - 用户权限管理
|
||||
- 工作流模块 (workflow) - 流程引擎支持
|
||||
|
||||
---
|
||||
|
||||
**🎉 录取流程状态管理模块开发完成!严格按照后勤管理模块的样式风格设计,提供完整的流程管理功能!**
|
||||
主要接口:
|
||||
- `listEnrollmentProcess`:获取流程状态列表
|
||||
- `listMyAuditProcess`:获取待我审核列表
|
||||
- `getEnrollmentProcess`:获取流程状态详情
|
||||
- `infoAudit`:执行信息审核
|
||||
- `admissionProcess`:执行录取处理
|
||||
- `finalAudit`:执行最终确认
|
||||
- `exportEnrollmentProcess`:导出流程状态数据
|
||||
@@ -180,19 +180,19 @@ const resetForm = () => {
|
||||
/** 提交处理 */
|
||||
const handleSubmit = async () => {
|
||||
if (!processFormRef.value) return
|
||||
|
||||
|
||||
try {
|
||||
await processFormRef.value.validate()
|
||||
|
||||
|
||||
submitLoading.value = true
|
||||
|
||||
|
||||
await admissionProcess({
|
||||
id: props.processData.id as number,
|
||||
processResult: processForm.processResult,
|
||||
processOpinion: processForm.processOpinion || undefined,
|
||||
noticeNumber: processForm.noticeNumber || undefined
|
||||
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()
|
||||
@@ -444,4 +444,4 @@ const handleClose = () => {
|
||||
background: #fff !important;
|
||||
color: #303133 !important;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
@@ -2,16 +2,26 @@
|
||||
<el-dialog
|
||||
v-model="dialogVisible"
|
||||
title="信息审核"
|
||||
width="600px"
|
||||
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>
|
||||
<span class="section-title">学生基本信息</span>
|
||||
<el-button
|
||||
type="primary"
|
||||
plain
|
||||
size="small"
|
||||
@click="handleViewDetails"
|
||||
:loading="detailLoading"
|
||||
>
|
||||
<el-icon><View /></el-icon>
|
||||
查看完整信息
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
<el-descriptions :column="2" border>
|
||||
@@ -30,6 +40,137 @@
|
||||
</el-descriptions>
|
||||
</el-card>
|
||||
|
||||
<!-- 学生详细信息展示(可展开/收起) -->
|
||||
<el-card v-if="showDetails" class="student-details-card" shadow="never">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span class="section-title">学生详细填报信息</span>
|
||||
<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">
|
||||
<h4 class="info-section-title">基本信息</h4>
|
||||
<el-descriptions :column="3" border size="small">
|
||||
<el-descriptions-item label="姓名">
|
||||
<span class="detail-value">{{ studentDetails.name || '-' }}</span>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="身份证号">
|
||||
<span class="detail-value">{{ studentDetails.idCard || '-' }}</span>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="性别">
|
||||
<dict-tag :options="zsjy_gender" :value="studentDetails.gender" />
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="出生日期">
|
||||
<span class="detail-value">{{ studentDetails.birthDate || '-' }}</span>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="联系电话">
|
||||
<span class="detail-value">{{ studentDetails.phone1 || '-' }}</span>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="备用电话">
|
||||
<span class="detail-value">{{ studentDetails.phone2 || '-' }}</span>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="民族">
|
||||
<span class="detail-value">{{ studentDetails.nation || '-' }}</span>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="是否住宿">
|
||||
<dict-tag :options="sys_yes_no" :value="studentDetails.isBoarding" />
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="健康状况">
|
||||
<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>
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</div>
|
||||
|
||||
<!-- 学业信息 -->
|
||||
<div class="info-section">
|
||||
<h4 class="info-section-title">学业信息</h4>
|
||||
<el-descriptions :column="3" border size="small">
|
||||
<el-descriptions-item label="毕业中学">
|
||||
<span class="detail-value">{{ studentDetails.middleSchool || '-' }}</span>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="准考证号">
|
||||
<span class="detail-value">{{ studentDetails.examNumber || '-' }}</span>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="中考地区">
|
||||
<span class="detail-value">{{ studentDetails.examRegion || '-' }}</span>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="中考满分">
|
||||
<span class="detail-value">{{ studentDetails.totalScoreFull || '-' }}</span>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="中考成绩">
|
||||
<span class="detail-value score-highlight">{{ studentDetails.totalScore || '-' }}</span>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="语文成绩">
|
||||
<span class="detail-value">{{ studentDetails.chineseScore || '-' }}</span>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="数学成绩">
|
||||
<span class="detail-value">{{ studentDetails.mathScore || '-' }}</span>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="英语成绩">
|
||||
<span class="detail-value">{{ studentDetails.englishScore || '-' }}</span>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="科学成绩">
|
||||
<span class="detail-value">{{ studentDetails.scienceScore || '-' }}</span>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="社会成绩">
|
||||
<span class="detail-value">{{ studentDetails.socialScore || '-' }}</span>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="体育成绩">
|
||||
<span class="detail-value">{{ studentDetails.peScore || '-' }}</span>
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</div>
|
||||
|
||||
<!-- 报考信息 -->
|
||||
<div class="info-section">
|
||||
<h4 class="info-section-title">报考信息</h4>
|
||||
<el-descriptions :column="2" border size="small">
|
||||
<el-descriptions-item label="报考专业">
|
||||
<span class="detail-value major-highlight">{{ studentDetails.appliedMajor || '-' }}</span>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="专业代码">
|
||||
<span class="detail-value">{{ studentDetails.majorCode || '-' }}</span>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="校服尺码">
|
||||
<span class="detail-value">{{ studentDetails.uniformSize || '-' }}</span>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="报名时间">
|
||||
<span class="detail-value">{{ formatTime(studentDetails.applicationTime) || '-' }}</span>
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</div>
|
||||
|
||||
<!-- 其他信息 -->
|
||||
<div class="info-section">
|
||||
<h4 class="info-section-title">其他信息</h4>
|
||||
<el-descriptions :column="1" border size="small">
|
||||
<el-descriptions-item label="特长爱好">
|
||||
<div class="detail-textarea">{{ studentDetails.specialties || '-' }}</div>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="获奖情况">
|
||||
<div class="detail-textarea">{{ studentDetails.awards || '-' }}</div>
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- 审核表单 -->
|
||||
<el-card class="audit-form-card" shadow="never">
|
||||
<template #header>
|
||||
@@ -89,10 +230,14 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, computed, toRefs, watch } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
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
|
||||
@@ -112,8 +257,8 @@ const props = withDefaults(defineProps<Props>(), {
|
||||
const emit = defineEmits<Emits>()
|
||||
|
||||
const { proxy } = getCurrentInstance() as ComponentInternalInstance
|
||||
const { zsjy_enrollment_status, zsjy_audit_status } = toRefs<any>(
|
||||
proxy?.useDict('zsjy_enrollment_status', 'zsjy_audit_status')
|
||||
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')
|
||||
)
|
||||
|
||||
// 对话框可见性
|
||||
@@ -125,8 +270,13 @@ const dialogVisible = computed({
|
||||
})
|
||||
|
||||
// 表单引用
|
||||
const auditFormRef = ref<ElFormInstance>()
|
||||
const auditFormRef = ref<FormInstance>()
|
||||
const submitLoading = ref(false)
|
||||
const detailLoading = ref(false)
|
||||
const showDetails = ref(false)
|
||||
|
||||
// 学生详细信息
|
||||
const studentDetails = ref<Partial<EnrollmentInfoVO>>({})
|
||||
|
||||
// 审核表单
|
||||
const auditForm = reactive({
|
||||
@@ -141,10 +291,73 @@ const auditRules = {
|
||||
]
|
||||
}
|
||||
|
||||
// 时间格式化方法
|
||||
const formatTime = (time: string | Date) => {
|
||||
return parseTime(time, '{y}-{m}-{d} {h}:{i}:{s}')
|
||||
}
|
||||
|
||||
// 查看学生详细信息
|
||||
const handleViewDetails = async () => {
|
||||
if (!props.processData.enrollmentId) {
|
||||
ElMessage.warning('缺少招生信息ID,无法获取详细信息')
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
detailLoading.value = true
|
||||
const res = await getEnrollmentInfo(props.processData.enrollmentId)
|
||||
studentDetails.value = res.data || {}
|
||||
showDetails.value = true
|
||||
} catch (error: any) {
|
||||
console.error('获取学生详细信息失败:', error)
|
||||
// 如果接口不存在,使用模拟数据
|
||||
if (error?.response?.status === 404) {
|
||||
ElMessage.warning('后端接口暂未实现,显示模拟数据')
|
||||
// 基于流程数据创建模拟的详细信息
|
||||
studentDetails.value = {
|
||||
id: Number(props.processData.enrollmentId),
|
||||
name: props.processData.studentName,
|
||||
idCard: props.processData.idCard,
|
||||
gender: props.processData.idCard ? (parseInt(props.processData.idCard.substring(16, 17)) % 2 === 1 ? 'M' : 'F') : 'M',
|
||||
phone1: '138****5678',
|
||||
nation: '汉族',
|
||||
isBoarding: 'Y',
|
||||
healthStatus: '健康',
|
||||
homeAddress: '浙江省杭州市西湖区某某街道某某小区',
|
||||
middleSchool: '杭州市某某中学',
|
||||
examNumber: '2025001234',
|
||||
examRegion: '杭州市',
|
||||
totalScoreFull: 600,
|
||||
totalScore: 520,
|
||||
chineseScore: 110,
|
||||
mathScore: 115,
|
||||
englishScore: 108,
|
||||
scienceScore: 95,
|
||||
socialScore: 92,
|
||||
appliedMajor: '计算机应用技术',
|
||||
majorCode: 'JSJYY001',
|
||||
uniformSize: 'L',
|
||||
specialties: '擅长计算机编程,获得过市级编程竞赛二等奖',
|
||||
awards: '2024年杭州市中学生编程竞赛二等奖\n2023年校级三好学生',
|
||||
applicationTime: '2025-01-20 14:30:00'
|
||||
}
|
||||
showDetails.value = true
|
||||
} else {
|
||||
ElMessage.error('获取学生详细信息失败,请重试')
|
||||
}
|
||||
} finally {
|
||||
detailLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 监听对话框打开,重置表单
|
||||
watch(() => props.visible, (newVal) => {
|
||||
if (newVal) {
|
||||
resetForm()
|
||||
} else {
|
||||
// 关闭时重置详细信息显示状态
|
||||
showDetails.value = false
|
||||
studentDetails.value = {}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -417,4 +630,144 @@ const handleClose = () => {
|
||||
background: #fff !important;
|
||||
color: #303133 !important;
|
||||
}
|
||||
|
||||
/* 学生信息卡片样式 */
|
||||
.student-info-card {
|
||||
margin-bottom: 20px;
|
||||
border-radius: 12px;
|
||||
border: 1px solid #e4e7ed;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.student-info-card :deep(.el-card__header) {
|
||||
background: linear-gradient(135deg, #fafbff 0%, #f8fafc 100%);
|
||||
border-bottom: 1px solid rgba(93, 135, 255, 0.1);
|
||||
padding: 16px 20px;
|
||||
}
|
||||
|
||||
/* 学生详细信息卡片样式 */
|
||||
.student-details-card {
|
||||
margin-bottom: 20px;
|
||||
border-radius: 12px;
|
||||
border: 1px solid #e4e7ed;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.student-details-card :deep(.el-card__header) {
|
||||
background: linear-gradient(135deg, #f0f9ff 0%, #e0f2fe 100%);
|
||||
border-bottom: 1px solid rgba(14, 165, 233, 0.1);
|
||||
padding: 16px 20px;
|
||||
}
|
||||
|
||||
/* 详细信息内容样式 */
|
||||
.details-content {
|
||||
max-height: 600px;
|
||||
overflow-y: auto;
|
||||
padding: 10px 0;
|
||||
}
|
||||
|
||||
.info-section {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.info-section:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.info-section-title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #1e293b;
|
||||
margin: 0 0 12px 0;
|
||||
padding-bottom: 8px;
|
||||
border-bottom: 2px solid #e2e8f0;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.info-section-title::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: -2px;
|
||||
left: 0;
|
||||
width: 60px;
|
||||
height: 2px;
|
||||
background: linear-gradient(135deg, #3b82f6, #1d4ed8);
|
||||
border-radius: 1px;
|
||||
}
|
||||
|
||||
/* 详细信息值样式 */
|
||||
.detail-value {
|
||||
color: #374151;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.score-highlight {
|
||||
color: #dc2626;
|
||||
font-weight: 600;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.major-highlight {
|
||||
color: #059669;
|
||||
font-weight: 600;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.detail-textarea {
|
||||
color: #374151;
|
||||
line-height: 1.6;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
max-height: 120px;
|
||||
overflow-y: auto;
|
||||
padding: 8px 0;
|
||||
}
|
||||
|
||||
/* 地址描述样式 */
|
||||
.address-desc {
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
/* 卡片头部样式 */
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #1e293b;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.section-title::before {
|
||||
content: '';
|
||||
width: 4px;
|
||||
height: 18px;
|
||||
background: linear-gradient(135deg, #3b82f6, #1d4ed8);
|
||||
border-radius: 2px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
/* 详细信息滚动条样式 */
|
||||
.details-content::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
|
||||
.details-content::-webkit-scrollbar-track {
|
||||
background: #f1f5f9;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.details-content::-webkit-scrollbar-thumb {
|
||||
background: #cbd5e1;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.details-content::-webkit-scrollbar-thumb:hover {
|
||||
background: #94a3b8;
|
||||
}
|
||||
</style>
|
||||
@@ -28,7 +28,12 @@
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="当前状态" prop="currentStatus">
|
||||
<el-select v-model="queryParams.currentStatus" placeholder="请选择当前状态" clearable style="width: 100%">
|
||||
<el-select
|
||||
v-model="queryParams.currentStatus"
|
||||
placeholder="请选择当前状态"
|
||||
clearable
|
||||
style="width: 240px"
|
||||
>
|
||||
<el-option
|
||||
v-for="dict in zsjy_enrollment_status"
|
||||
:key="dict.value"
|
||||
@@ -38,47 +43,14 @@
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="信息审核" prop="infoAuditStatus">
|
||||
<el-select v-model="queryParams.infoAuditStatus" placeholder="请选择信息审核状态" clearable style="width: 100%">
|
||||
<el-option
|
||||
v-for="dict in zsjy_audit_status"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="6">
|
||||
<el-form-item label="录取处理" prop="admissionStatus">
|
||||
<el-select v-model="queryParams.admissionStatus" placeholder="请选择录取处理状态" clearable style="width: 100%">
|
||||
<el-option
|
||||
v-for="dict in zsjy_process_status"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="最终审核" prop="finalAuditStatus">
|
||||
<el-select v-model="queryParams.finalAuditStatus" placeholder="请选择最终审核状态" clearable style="width: 100%">
|
||||
<el-option
|
||||
v-for="dict in zsjy_audit_status"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="完成状态" prop="isCompleted">
|
||||
<el-select v-model="queryParams.isCompleted" placeholder="请选择完成状态" clearable style="width: 100%">
|
||||
<el-select
|
||||
v-model="queryParams.isCompleted"
|
||||
placeholder="请选择完成状态"
|
||||
clearable
|
||||
style="width: 240px"
|
||||
>
|
||||
<el-option
|
||||
v-for="dict in sys_yes_no"
|
||||
:key="dict.value"
|
||||
@@ -88,15 +60,11 @@
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<div 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>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<div class="search-btn-container">
|
||||
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
|
||||
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
|
||||
</div>
|
||||
</el-form>
|
||||
</el-card>
|
||||
|
||||
@@ -132,7 +100,7 @@
|
||||
<el-card class="stat-card stat-card-info" shadow="hover">
|
||||
<div class="stat-content">
|
||||
<div class="stat-icon">
|
||||
<el-icon><Processing /></el-icon>
|
||||
<el-icon><Clock /></el-icon>
|
||||
</div>
|
||||
<div class="stat-info">
|
||||
<div class="stat-value">{{ statistics.processing || 0 }}</div>
|
||||
@@ -162,15 +130,6 @@
|
||||
<div class="card-header">
|
||||
<span class="toolbar-title">录取流程管理</span>
|
||||
<div class="toolbar-actions">
|
||||
<el-button
|
||||
v-hasPermi="['zsjy:enrollmentProcess:add']"
|
||||
type="primary"
|
||||
plain
|
||||
icon="Plus"
|
||||
@click="handleAdd"
|
||||
>
|
||||
新增流程
|
||||
</el-button>
|
||||
<el-button
|
||||
v-hasPermi="['zsjy:enrollmentProcess:export']"
|
||||
type="warning"
|
||||
@@ -219,30 +178,22 @@
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="当前处理人" align="center" width="120" prop="currentHandlerName" />
|
||||
<el-table-column label="信息审核" align="center" width="100">
|
||||
<template #default="scope">
|
||||
<dict-tag :options="zsjy_audit_status" :value="scope.row.infoAuditStatus" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="录取处理" align="center" width="100">
|
||||
<template #default="scope">
|
||||
<dict-tag :options="zsjy_process_status" :value="scope.row.admissionStatus" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="最终审核" align="center" width="100">
|
||||
<template #default="scope">
|
||||
<dict-tag :options="zsjy_audit_status" :value="scope.row.finalAuditStatus" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="缴费状态" align="center" width="100">
|
||||
<template #default="scope">
|
||||
<dict-tag :options="zsjy_payment_status" :value="scope.row.paymentStatus" />
|
||||
<el-tag v-if="scope.row.payment_status === '1' || scope.row.paymentStatus === '1'" type="success">已缴费</el-tag>
|
||||
<el-tag v-else-if="scope.row.payment_status === '0' || scope.row.paymentStatus === '0'" type="warning">未缴费</el-tag>
|
||||
<el-tag v-else type="info">未知</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="录取通知书" align="center" width="150" show-overflow-tooltip>
|
||||
<template #default="scope">
|
||||
<span>{{ scope.row.noticeNumber || scope.row.notice_number || '未生成' }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="录取通知书" align="center" width="150" prop="noticeNumber" show-overflow-tooltip />
|
||||
<el-table-column label="完成状态" align="center" width="100">
|
||||
<template #default="scope">
|
||||
<dict-tag :options="sys_yes_no" :value="scope.row.isCompleted" />
|
||||
<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>
|
||||
<el-table-column label="创建时间" align="center" width="180">
|
||||
@@ -262,64 +213,48 @@
|
||||
@click="handleDetail(scope.row)"
|
||||
/>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="信息审核" placement="top">
|
||||
<el-tooltip content="缴费确认" placement="top">
|
||||
<el-button
|
||||
v-hasPermi="['zsjy:enrollmentProcess:audit']"
|
||||
v-hasPermi="['zsjy:enrollmentProcess:payment']"
|
||||
link
|
||||
type="success"
|
||||
icon="Check"
|
||||
:disabled="scope.row.currentStatus !== '0' && scope.row.currentStatus !== '1'"
|
||||
@click="handleInfoAudit(scope.row)"
|
||||
icon="CreditCard"
|
||||
:disabled="scope.row.payment_status === '1' || scope.row.paymentStatus === '1'"
|
||||
@click="handlePaymentConfirm(scope.row)"
|
||||
/>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="录取处理" placement="top">
|
||||
<el-tooltip :content="(scope.row.payment_status !== '1' && scope.row.paymentStatus !== '1') ?
|
||||
'请先完成缴费确认' : '录取处理'" placement="top">
|
||||
<el-button
|
||||
v-hasPermi="['zsjy:enrollmentProcess:process']"
|
||||
link
|
||||
type="warning"
|
||||
icon="Edit"
|
||||
:disabled="scope.row.currentStatus !== '2'"
|
||||
:disabled="scope.row.payment_status !== '1' && scope.row.paymentStatus !== '1'"
|
||||
@click="handleAdmissionProcess(scope.row)"
|
||||
/>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="最终审核" placement="top">
|
||||
<el-tooltip :content="getCompleteButtonTooltip(scope.row)" placement="top">
|
||||
<el-button
|
||||
v-hasPermi="['zsjy:enrollmentProcess:finalAudit']"
|
||||
v-hasPermi="['zsjy:enrollmentProcess:complete']"
|
||||
link
|
||||
type="info"
|
||||
icon="Finished"
|
||||
:disabled="scope.row.currentStatus !== '3'"
|
||||
@click="handleFinalAudit(scope.row)"
|
||||
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'"
|
||||
@click="handleComplete(scope.row)"
|
||||
/>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="更多操作" placement="top">
|
||||
<el-dropdown @command="(command: string) => handleCommand(command, scope.row)">
|
||||
<el-button link type="primary" icon="More" />
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item command="payment" :disabled="scope.row.paymentStatus === '1'">
|
||||
<el-icon><CreditCard /></el-icon>
|
||||
缴费确认
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item command="complete" :disabled="scope.row.isCompleted === 1">
|
||||
<el-icon><CircleCheck /></el-icon>
|
||||
完成录取
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item command="reject" :disabled="scope.row.currentStatus === '9'">
|
||||
<el-icon><CircleClose /></el-icon>
|
||||
驳回流程
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item command="reset" v-hasPermi="['zsjy:enrollmentProcess:reset']">
|
||||
<el-icon><RefreshRight /></el-icon>
|
||||
重置流程
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item command="delete" v-hasPermi="['zsjy:enrollmentProcess:remove']">
|
||||
<el-icon><Delete /></el-icon>
|
||||
删除
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
<el-tooltip content="删除" placement="top">
|
||||
<el-button
|
||||
v-hasPermi="['zsjy:enrollmentProcess:remove']"
|
||||
link
|
||||
type="danger"
|
||||
icon="Delete"
|
||||
@click="handleDelete(scope.row)"
|
||||
/>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</template>
|
||||
@@ -345,13 +280,6 @@
|
||||
:process-data="currentProcess"
|
||||
/>
|
||||
|
||||
<!-- 信息审核对话框 -->
|
||||
<InfoAuditDialog
|
||||
v-model:visible="infoAuditDialogVisible"
|
||||
:process-data="currentProcess"
|
||||
@success="handleDialogSuccess"
|
||||
/>
|
||||
|
||||
<!-- 录取处理对话框 -->
|
||||
<AdmissionProcessDialog
|
||||
v-model:visible="admissionProcessDialogVisible"
|
||||
@@ -359,13 +287,6 @@
|
||||
@success="handleDialogSuccess"
|
||||
/>
|
||||
|
||||
<!-- 最终审核对话框 -->
|
||||
<FinalAuditDialog
|
||||
v-model:visible="finalAuditDialogVisible"
|
||||
:process-data="currentProcess"
|
||||
@success="handleDialogSuccess"
|
||||
/>
|
||||
|
||||
<!-- 驳回流程对话框 -->
|
||||
<RejectProcessDialog
|
||||
v-model:visible="rejectDialogVisible"
|
||||
@@ -391,19 +312,19 @@ import {
|
||||
delEnrollmentProcess,
|
||||
getProcessStatistics,
|
||||
confirmPayment,
|
||||
completeEnrollment,
|
||||
completeProcess,
|
||||
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 InfoAuditDialog from './components/InfoAuditDialog.vue'
|
||||
import AdmissionProcessDialog from './components/AdmissionProcessDialog.vue'
|
||||
import FinalAuditDialog from './components/FinalAuditDialog.vue'
|
||||
import RejectProcessDialog from './components/RejectProcessDialog.vue'
|
||||
import ProcessFormDialog from './components/ProcessFormDialog.vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
|
||||
const { proxy } = getCurrentInstance() as ComponentInternalInstance
|
||||
const { zsjy_enrollment_status, zsjy_audit_status, zsjy_process_status, zsjy_payment_status, sys_yes_no } = toRefs<any>(
|
||||
@@ -423,10 +344,7 @@ const data = reactive<{
|
||||
studentName: undefined,
|
||||
idCard: undefined,
|
||||
currentStatus: undefined,
|
||||
infoAuditStatus: undefined,
|
||||
admissionStatus: undefined,
|
||||
finalAuditStatus: undefined,
|
||||
isCompleted: undefined,
|
||||
isCompleted: 0,
|
||||
params: {}
|
||||
},
|
||||
rules: {}
|
||||
@@ -444,6 +362,13 @@ 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
|
||||
}
|
||||
|
||||
// 统计数据
|
||||
const statistics = ref({
|
||||
total: 0,
|
||||
@@ -454,9 +379,7 @@ const statistics = ref({
|
||||
|
||||
// 对话框状态
|
||||
const detailDialogVisible = ref(false)
|
||||
const infoAuditDialogVisible = ref(false)
|
||||
const admissionProcessDialogVisible = ref(false)
|
||||
const finalAuditDialogVisible = ref(false)
|
||||
const rejectDialogVisible = ref(false)
|
||||
const formDialogVisible = ref(false)
|
||||
const isEdit = ref(false)
|
||||
@@ -467,8 +390,71 @@ const getList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const res = await listEnrollmentProcess(queryParams.value)
|
||||
processList.value = res.rows
|
||||
total.value = res.total
|
||||
|
||||
// 对返回的数据进行处理,确保字段名一致
|
||||
const processedList = (res.rows || []).map(item => {
|
||||
// 添加日志用于调试(首行数据)
|
||||
if (item === res.rows[0]) {
|
||||
console.log('首行数据字段:', Object.keys(item))
|
||||
console.log('首行数据内容:', JSON.stringify(item))
|
||||
}
|
||||
|
||||
// 字段名映射处理
|
||||
return {
|
||||
...item,
|
||||
// 确保同时有驼峰式和下划线式的字段
|
||||
paymentStatus: item.paymentStatus || item.payment_status,
|
||||
payment_status: item.payment_status || item.paymentStatus,
|
||||
isCompleted: item.isCompleted || item.is_completed,
|
||||
is_completed: item.is_completed || item.isCompleted,
|
||||
noticeNumber: item.noticeNumber || item.notice_number,
|
||||
notice_number: item.notice_number || item.noticeNumber
|
||||
}
|
||||
})
|
||||
|
||||
processList.value = processedList
|
||||
total.value = res.total || 0
|
||||
} catch (error) {
|
||||
console.error('获取流程列表失败:', error)
|
||||
|
||||
// 如果是404错误(接口不存在),使用模拟数据
|
||||
if (error?.response?.status === 404 || error?.message?.includes('404')) {
|
||||
console.warn('后端接口不存在,使用模拟数据')
|
||||
// 创建模拟的流程数据
|
||||
processList.value = [
|
||||
{
|
||||
id: 1946510098494091300,
|
||||
enrollmentId: 1001,
|
||||
studentName: '张三',
|
||||
idCard: '330101200501010001',
|
||||
currentStatus: '0',
|
||||
infoAuditStatus: '0',
|
||||
admissionStatus: '0',
|
||||
finalAuditStatus: '0',
|
||||
isCompleted: 0,
|
||||
createTime: '2025-07-20 10:28:09'
|
||||
},
|
||||
{
|
||||
id: 1946510098494091301,
|
||||
enrollmentId: 1946510098494091266,
|
||||
studentName: '赵四',
|
||||
idCard: '340403201509091234',
|
||||
currentStatus: '0',
|
||||
infoAuditStatus: '0',
|
||||
admissionStatus: '0',
|
||||
finalAuditStatus: '0',
|
||||
isCompleted: 0,
|
||||
createTime: '2025-07-20 10:28:18'
|
||||
}
|
||||
]
|
||||
total.value = 2
|
||||
proxy?.$modal.msgWarning('后端接口暂未实现,显示模拟数据')
|
||||
} else {
|
||||
// 其他错误,设置空数据并抛出错误
|
||||
processList.value = []
|
||||
total.value = 0
|
||||
throw error
|
||||
}
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
@@ -477,23 +463,46 @@ const getList = async () => {
|
||||
/** 获取统计数据 */
|
||||
const getStatistics = async () => {
|
||||
try {
|
||||
const res = await getProcessStatistics()
|
||||
const stats = res.data || []
|
||||
// 暂时注释掉不存在的统计接口,改为基于流程列表计算统计
|
||||
// const res = await getProcessStatistics()
|
||||
// const stats = res.data || []
|
||||
|
||||
// 基于当前流程列表计算统计数据
|
||||
const stats = processList.value || []
|
||||
const statusCounts = stats.reduce((acc: any, item: any) => {
|
||||
const status = item.currentStatus
|
||||
acc[status] = (acc[status] || 0) + 1
|
||||
return acc
|
||||
}, {})
|
||||
|
||||
statistics.value = {
|
||||
total: stats.reduce((sum: number, item: any) => sum + Number(item.count), 0),
|
||||
pending: stats.find((item: any) => ['0', '1'].includes(item.status))?.count || 0,
|
||||
processing: stats.find((item: any) => ['2', '3'].includes(item.status))?.count || 0,
|
||||
completed: stats.find((item: any) => item.status === '4')?.count || 0
|
||||
total: stats.length,
|
||||
pending: (statusCounts['0'] || 0) + (statusCounts['1'] || 0), // 待审核 + 信息审核中
|
||||
processing: (statusCounts['2'] || 0) + (statusCounts['3'] || 0), // 录取处理中 + 待最终确认
|
||||
completed: statusCounts['4'] || 0 // 录取完成
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取统计数据失败:', error)
|
||||
// 设置默认值
|
||||
statistics.value = {
|
||||
total: 0,
|
||||
pending: 0,
|
||||
processing: 0,
|
||||
completed: 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
const handleQuery = async () => {
|
||||
queryParams.value.pageNum = 1
|
||||
getList()
|
||||
try {
|
||||
await getList()
|
||||
// 搜索后更新统计数据
|
||||
await getStatistics()
|
||||
} catch (error) {
|
||||
console.error('搜索失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
/** 重置按钮操作 */
|
||||
@@ -502,13 +511,6 @@ const resetQuery = () => {
|
||||
handleQuery()
|
||||
}
|
||||
|
||||
/** 多选框选中数据 */
|
||||
const handleSelectionChange = (selection: EnrollmentProcessVO[]) => {
|
||||
ids.value = selection.map(item => item.id)
|
||||
single.value = selection.length !== 1
|
||||
multiple.value = !selection.length
|
||||
}
|
||||
|
||||
/** 新增按钮操作 */
|
||||
const handleAdd = () => {
|
||||
currentProcess.value = {}
|
||||
@@ -535,44 +537,62 @@ const handleDetail = (row: EnrollmentProcessVO) => {
|
||||
detailDialogVisible.value = true
|
||||
}
|
||||
|
||||
/** 信息审核 */
|
||||
const handleInfoAudit = (row: EnrollmentProcessVO) => {
|
||||
currentProcess.value = row
|
||||
infoAuditDialogVisible.value = true
|
||||
}
|
||||
|
||||
/** 录取处理 */
|
||||
const handleAdmissionProcess = (row: EnrollmentProcessVO) => {
|
||||
currentProcess.value = row
|
||||
admissionProcessDialogVisible.value = true
|
||||
// 验证缴费状态
|
||||
if ((row.payment_status !== '1' && row.paymentStatus !== '1')) {
|
||||
ElMessage.warning('请先完成缴费确认,再进行录取处理');
|
||||
return;
|
||||
}
|
||||
|
||||
currentProcess.value = row;
|
||||
admissionProcessDialogVisible.value = true;
|
||||
}
|
||||
|
||||
/** 最终审核 */
|
||||
const handleFinalAudit = (row: EnrollmentProcessVO) => {
|
||||
currentProcess.value = row
|
||||
finalAuditDialogVisible.value = true
|
||||
/** 缴费确认按钮点击处理 */
|
||||
const handlePaymentConfirm = async (row: EnrollmentProcessVO) => {
|
||||
try {
|
||||
await handleConfirmPayment(row)
|
||||
} catch (error) {
|
||||
console.error('缴费确认失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
/** 更多操作命令处理 */
|
||||
const handleCommand = async (command: string, row: EnrollmentProcessVO) => {
|
||||
currentProcess.value = row
|
||||
|
||||
switch (command) {
|
||||
case 'payment':
|
||||
await handleConfirmPayment(row)
|
||||
break
|
||||
case 'complete':
|
||||
await handleCompleteEnrollment(row)
|
||||
break
|
||||
case 'reject':
|
||||
rejectDialogVisible.value = true
|
||||
break
|
||||
case 'reset':
|
||||
await handleResetProcess(row)
|
||||
break
|
||||
case 'delete':
|
||||
await handleDelete(row)
|
||||
break
|
||||
/** 完成录取按钮点击处理 */
|
||||
const handleComplete = async (row: EnrollmentProcessVO) => {
|
||||
// 验证缴费状态
|
||||
if ((row.payment_status !== '1' && row.paymentStatus !== '1')) {
|
||||
ElMessage.warning('请先完成缴费确认,再进行完成录取操作');
|
||||
return;
|
||||
}
|
||||
|
||||
// 验证录取处理状态
|
||||
if ((row.admissionStatus !== '1' && row.admission_status !== '1')) {
|
||||
ElMessage.warning('请先完成录取处理,再进行完成录取操作');
|
||||
return;
|
||||
}
|
||||
|
||||
// 验证是否已经完成
|
||||
if (row.is_completed === 1 || row.is_completed === 'Y') {
|
||||
ElMessage.warning('该录取流程已完成,无需重复操作');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await ElMessageBox.confirm('确认完成该学生的录取流程?', '完成录取', {
|
||||
confirmButtonText: '确认',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
})
|
||||
|
||||
await completeProcess(row.id as number)
|
||||
ElMessage.success('录取完成')
|
||||
await getList()
|
||||
await getStatistics()
|
||||
} catch (error) {
|
||||
if (error !== 'cancel') {
|
||||
ElMessage.error('完成录取失败')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -584,7 +604,7 @@ const handleConfirmPayment = async (row: EnrollmentProcessVO) => {
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
})
|
||||
|
||||
|
||||
await confirmPayment(row.id as number)
|
||||
ElMessage.success('缴费确认成功')
|
||||
await getList()
|
||||
@@ -604,8 +624,8 @@ const handleCompleteEnrollment = async (row: EnrollmentProcessVO) => {
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
})
|
||||
|
||||
await completeEnrollment(row.id as number)
|
||||
|
||||
await completeProcess(row.id as number)
|
||||
ElMessage.success('录取完成')
|
||||
await getList()
|
||||
await getStatistics()
|
||||
@@ -624,7 +644,7 @@ const handleResetProcess = async (row: EnrollmentProcessVO) => {
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
})
|
||||
|
||||
|
||||
await resetProcess(row.id as number)
|
||||
ElMessage.success('流程重置成功')
|
||||
await getList()
|
||||
@@ -645,7 +665,7 @@ const handleDelete = async (row?: EnrollmentProcessVO) => {
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
})
|
||||
|
||||
|
||||
await delEnrollmentProcess(processIds)
|
||||
ElMessage.success('删除成功')
|
||||
await getList()
|
||||
@@ -676,10 +696,50 @@ const handleDialogSuccess = async () => {
|
||||
await getStatistics()
|
||||
}
|
||||
|
||||
// 初始化
|
||||
/** 获取完成录取按钮的提示内容 */
|
||||
const getCompleteButtonTooltip = (row: any): string => {
|
||||
if (row.is_completed === 1 || row.is_completed === 'Y') {
|
||||
return '录取已完成,无需重复操作';
|
||||
}
|
||||
|
||||
if (row.payment_status !== '1' && row.paymentStatus !== '1') {
|
||||
return '请先完成缴费确认';
|
||||
}
|
||||
|
||||
if (row.admissionStatus !== '1' && row.admission_status !== '1') {
|
||||
return '请先完成录取处理';
|
||||
}
|
||||
|
||||
return '完成录取';
|
||||
}
|
||||
|
||||
/** 初始化 */
|
||||
onMounted(async () => {
|
||||
await getList()
|
||||
await getStatistics()
|
||||
try {
|
||||
// 处理路由参数
|
||||
const route = useRoute();
|
||||
if (route && route.query) {
|
||||
if (route.query.enrollmentId) {
|
||||
queryParams.value.enrollmentId = Number(route.query.enrollmentId);
|
||||
}
|
||||
if (route.query.studentName) {
|
||||
queryParams.value.studentName = route.query.studentName as string;
|
||||
}
|
||||
}
|
||||
|
||||
// 先获取列表数据,再基于列表数据计算统计
|
||||
try {
|
||||
await getList();
|
||||
// 列表数据获取成功后,计算统计数据
|
||||
await getStatistics();
|
||||
} catch (error) {
|
||||
console.error('获取数据失败:', error);
|
||||
proxy?.$modal.msgError('获取数据失败,请检查网络连接或联系管理员');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('页面初始化失败:', error);
|
||||
proxy?.$modal.msgError('页面初始化失败,请刷新页面重试');
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -822,6 +882,7 @@ onMounted(async () => {
|
||||
|
||||
/* 表格卡片样式 */
|
||||
.table-card {
|
||||
margin-top: 0;
|
||||
border-radius: 12px;
|
||||
border: 1px solid #e4e7ed;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||
@@ -831,15 +892,92 @@ onMounted(async () => {
|
||||
padding: 0px 20px 20px 20px !important;
|
||||
}
|
||||
|
||||
/* 表格样式 */
|
||||
.scroll-table :deep(.el-table__header-wrapper .el-table__header th) {
|
||||
background-color: #f8f9fa;
|
||||
color: #333;
|
||||
font-weight: 600;
|
||||
/* 表格容器 - 支持横向滚动 */
|
||||
.table-container {
|
||||
width: 100%;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.scroll-table :deep(.el-table__body-wrapper .el-table__row:hover) {
|
||||
background-color: #f5f7fa;
|
||||
/* 横向滚动表格样式 */
|
||||
.scroll-table {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* 表格横向滚动条样式优化 */
|
||||
.scroll-table :deep(.el-table__body-wrapper) {
|
||||
overflow-x: auto !important; /* 自动显示横向滚动条 */
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.scroll-table :deep(.el-table__header-wrapper) {
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
/* 强制显示表格横向滚动条 - 适用于所有浏览器 */
|
||||
.scroll-table :deep(.el-table__body-wrapper) {
|
||||
scrollbar-width: thin; /* Firefox */
|
||||
scrollbar-color: #409eff #f0f0f0; /* Firefox - thumb track */
|
||||
}
|
||||
|
||||
/* Webkit 浏览器的横向滚动条样式 */
|
||||
.scroll-table :deep(.el-table__body-wrapper)::-webkit-scrollbar {
|
||||
height: 12px !important; /* 增加滚动条高度 */
|
||||
width: 12px !important;
|
||||
}
|
||||
|
||||
.scroll-table :deep(.el-table__body-wrapper)::-webkit-scrollbar-track {
|
||||
background: #f0f0f0 !important;
|
||||
border-radius: 6px !important;
|
||||
border: 1px solid #e4e7ed !important; /* 添加边框使其更明显 */
|
||||
}
|
||||
|
||||
.scroll-table :deep(.el-table__body-wrapper)::-webkit-scrollbar-thumb {
|
||||
background: #409eff !important; /* 使用主题色,更明显 */
|
||||
border-radius: 6px !important;
|
||||
border: 2px solid #f0f0f0 !important; /* 添加白色边框 */
|
||||
min-height: 20px !important; /* 确保最小高度 */
|
||||
}
|
||||
|
||||
.scroll-table :deep(.el-table__body-wrapper)::-webkit-scrollbar-thumb:hover {
|
||||
background: #337ecc !important; /* 悬停时更深的蓝色 */
|
||||
}
|
||||
|
||||
.scroll-table :deep(.el-table__body-wrapper)::-webkit-scrollbar-thumb:active {
|
||||
background: #2b6cb0 !important; /* 点击时的颜色 */
|
||||
}
|
||||
|
||||
/* 确保滚动条始终可见的额外设置 */
|
||||
.scroll-table :deep(.el-table__body-wrapper)::-webkit-scrollbar-corner {
|
||||
background: #f0f0f0;
|
||||
}
|
||||
|
||||
/* 固定列样式优化 */
|
||||
.scroll-table :deep(.el-table__fixed-right) {
|
||||
box-shadow: -2px 0 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.scroll-table :deep(.el-table__fixed-left) {
|
||||
box-shadow: 2px 0 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
/* 确保固定列在滚动时有正确的背景色 */
|
||||
.scroll-table :deep(.el-table__fixed-right .el-table__cell),
|
||||
.scroll-table :deep(.el-table__fixed-left .el-table__cell) {
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
/* 表格行悬停效果在固定列上的同步 */
|
||||
.scroll-table :deep(.el-table__row:hover .el-table__cell) {
|
||||
background-color: #f5f7fa !important;
|
||||
}
|
||||
|
||||
/* 分页容器样式 */
|
||||
.pagination-container {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
padding: 16px 20px 0 0 !important;
|
||||
background-color: #fff;
|
||||
margin-top: 0px !important;
|
||||
}
|
||||
|
||||
/* 操作按钮样式 */
|
||||
@@ -851,6 +989,7 @@ onMounted(async () => {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* 确保操作按钮宽度一致 */
|
||||
.action-buttons .el-button {
|
||||
width: 27px !important;
|
||||
height: 27px !important;
|
||||
@@ -860,12 +999,40 @@ onMounted(async () => {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
/* 分页容器样式 */
|
||||
.pagination-container {
|
||||
/* 批量操作表单样式 */
|
||||
.form-tips {
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
line-height: 1.2;
|
||||
padding: 2px 0 0 0;
|
||||
}
|
||||
|
||||
/* 搜索区域样式优化 */
|
||||
.search-card {
|
||||
margin-bottom: 20px;
|
||||
padding: 10px;
|
||||
background-color: #fff;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.search-card :deep(.el-select) {
|
||||
width: 240px;
|
||||
}
|
||||
|
||||
.search-btn-container {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
padding: 16px 20px 0 0 !important;
|
||||
background-color: #fff;
|
||||
margin-top: 0px !important;
|
||||
margin-top: 10px;
|
||||
}
|
||||
</style>
|
||||
|
||||
.search-btn-container .el-form-item {
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
|
||||
/* 确保文本不会被截断 */
|
||||
:deep(.el-select-dropdown__item span) {
|
||||
white-space: normal !important;
|
||||
word-break: break-word !important;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,411 @@
|
||||
<template>
|
||||
<div class="page-container">
|
||||
<!-- 搜索区域 -->
|
||||
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
|
||||
<div ref="searchRef" v-show="showSearch" class="mb-[10px]">
|
||||
<el-card shadow="hover">
|
||||
<el-form ref="queryFormRef" :model="queryParams" label-width="100px">
|
||||
<!-- 第一行:搜索元素 -->
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="6">
|
||||
<el-form-item label="学生姓名" prop="studentName">
|
||||
<el-input v-model="queryParams.studentName" placeholder="请输入学生姓名" clearable @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="身份证号" prop="idCard">
|
||||
<el-input v-model="queryParams.idCard" placeholder="请输入身份证号" clearable @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- 操作按钮行 -->
|
||||
<el-row>
|
||||
<el-col :span="24" class="search-btn-container">
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
|
||||
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
</el-card>
|
||||
</div>
|
||||
</transition>
|
||||
|
||||
<!-- 表格操作区域 -->
|
||||
<el-card shadow="never" class="table-card">
|
||||
<template #header>
|
||||
<el-row ref="toolbarRef" :gutter="10" class="mb8">
|
||||
<el-col :span="1.5">
|
||||
<el-button type="success" plain icon="Check" :disabled="single" @click="handleApprove()" v-hasPermi="['zsjy:enrollment:process:audit']">
|
||||
通过
|
||||
</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button type="danger" plain icon="Close" :disabled="single" @click="handleReject()" v-hasPermi="['zsjy:enrollment:process:audit']">
|
||||
驳回
|
||||
</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button type="primary" plain icon="View" :disabled="single" @click="handleView()" v-hasPermi="['zsjy:enrollment:process:query']">
|
||||
查看
|
||||
</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button icon="Download" @click="handleExport" v-hasPermi="['zsjy:enrollment:process: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="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" />
|
||||
<el-table-column label="当前状态" align="center" prop="currentStatus">
|
||||
<template #default="scope">
|
||||
<dict-tag :options="zsjy_enrollment_status" :value="scope.row.currentStatus" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="信息审核状态" align="center" prop="infoAuditStatus">
|
||||
<template #default="scope">
|
||||
<dict-tag :options="zsjy_audit_status" :value="scope.row.infoAuditStatus" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="录取处理状态" align="center" prop="admissionStatus">
|
||||
<template #default="scope">
|
||||
<dict-tag :options="zsjy_process_status" :value="scope.row.admissionStatus" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="最终审核状态" align="center" prop="finalAuditStatus">
|
||||
<template #default="scope">
|
||||
<dict-tag :options="zsjy_audit_status" :value="scope.row.finalAuditStatus" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="创建时间" align="center" prop="createTime" width="180">
|
||||
<template #default="scope">
|
||||
<span>{{ parseTime(scope.row.createTime) }}</span>
|
||||
</template>
|
||||
</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
|
||||
v-if="canAudit(scope.row)"
|
||||
type="success"
|
||||
link
|
||||
icon="Check"
|
||||
@click="handleApprove(scope.row)"
|
||||
v-hasPermi="['zsjy:enrollment:process:audit']"
|
||||
>通过</el-button>
|
||||
<el-button
|
||||
v-if="canAudit(scope.row)"
|
||||
type="danger"
|
||||
link
|
||||
icon="Close"
|
||||
@click="handleReject(scope.row)"
|
||||
v-hasPermi="['zsjy:enrollment:process:audit']"
|
||||
>驳回</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"
|
||||
/>
|
||||
</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-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button type="primary" @click="submitAudit">确 定</el-button>
|
||||
<el-button @click="auditDialogVisible = false">取 消</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup name="PendingAudit" lang="ts">
|
||||
import { ref, reactive, onMounted, computed, getCurrentInstance, toRefs } from 'vue';
|
||||
import type { ComponentInternalInstance } from 'vue';
|
||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { listMyAuditProcess, infoAudit, admissionProcess, finalAudit, exportEnrollmentProcess } from '@/api/zsjy/enrollmentProcess';
|
||||
import { parseTime } from '@/utils/ruoyi';
|
||||
import { download } from '@/utils/request';
|
||||
import type { EnrollmentProcessVO, EnrollmentProcessQuery } from '@/api/zsjy/enrollmentProcess';
|
||||
|
||||
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
||||
// 字典数据
|
||||
const { zsjy_enrollment_status, zsjy_audit_status, zsjy_process_status, sys_yes_no } = toRefs<any>(
|
||||
proxy?.useDict('zsjy_enrollment_status', 'zsjy_audit_status', 'zsjy_process_status', 'sys_yes_no')
|
||||
);
|
||||
|
||||
// 遮罩层
|
||||
const loading = ref(true);
|
||||
// 选中数组
|
||||
const ids = ref<Array<string | number>>([]);
|
||||
// 非单个禁用
|
||||
const single = computed(() => ids.value.length !== 1);
|
||||
// 非多个禁用
|
||||
const multiple = computed(() => !ids.value.length);
|
||||
// 显示搜索条件
|
||||
const showSearch = ref(true);
|
||||
// 总条数
|
||||
const total = ref(0);
|
||||
// 录取流程状态表格数据
|
||||
const processList = ref<EnrollmentProcessVO[]>([]);
|
||||
// 弹出层标题
|
||||
const auditDialogTitle = ref("");
|
||||
// 是否显示弹出层
|
||||
const auditDialogVisible = ref(false);
|
||||
// 当前操作类型
|
||||
const actionType = ref('');
|
||||
// 当前选中的记录
|
||||
const currentRow = ref<EnrollmentProcessVO | null>(null);
|
||||
|
||||
// 审核表单
|
||||
const auditForm = reactive({
|
||||
processId: 0,
|
||||
auditResult: '',
|
||||
auditOpinion: ''
|
||||
});
|
||||
|
||||
// 审核表单校验规则
|
||||
const auditRules = {
|
||||
auditOpinion: [{ required: true, message: "请输入审核意见", trigger: "blur" }]
|
||||
};
|
||||
|
||||
// 查询参数
|
||||
const queryParams = reactive<EnrollmentProcessQuery>({
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
studentName: '',
|
||||
idCard: '',
|
||||
params: {}
|
||||
});
|
||||
|
||||
// 表单引用
|
||||
const queryFormRef = ref();
|
||||
const auditFormRef = ref();
|
||||
|
||||
/** 查询录取流程状态列表 */
|
||||
function getList() {
|
||||
loading.value = true;
|
||||
listMyAuditProcess(queryParams).then(response => {
|
||||
processList.value = response.rows || [];
|
||||
total.value = response.total || 0;
|
||||
loading.value = false;
|
||||
});
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
function handleQuery() {
|
||||
queryParams.pageNum = 1;
|
||||
getList();
|
||||
}
|
||||
|
||||
/** 重置按钮操作 */
|
||||
function resetQuery() {
|
||||
queryFormRef.value?.resetFields();
|
||||
handleQuery();
|
||||
}
|
||||
|
||||
/** 多选框选中数据 */
|
||||
function handleSelectionChange(selection: EnrollmentProcessVO[]) {
|
||||
ids.value = selection.map(item => item.id);
|
||||
if (selection.length === 1) {
|
||||
currentRow.value = selection[0];
|
||||
} else {
|
||||
currentRow.value = null;
|
||||
}
|
||||
}
|
||||
|
||||
/** 行点击事件 */
|
||||
function handleRowClick(row: EnrollmentProcessVO) {
|
||||
handleView(row);
|
||||
}
|
||||
|
||||
/** 查看按钮操作 */
|
||||
function handleView(row?: EnrollmentProcessVO) {
|
||||
const id = row?.id || (ids.value.length > 0 ? ids.value[0] : null);
|
||||
if (id) {
|
||||
// 跳转到详情页面
|
||||
const router = useRouter();
|
||||
router.push({ path: '/zsjy/enrollmentProcess/detail', query: { id: id.toString() } });
|
||||
}
|
||||
}
|
||||
|
||||
/** 导出按钮操作 */
|
||||
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(() => {});
|
||||
}
|
||||
|
||||
/** 判断是否可以审核 */
|
||||
function canAudit(row: EnrollmentProcessVO) {
|
||||
// 根据当前用户角色和流程状态判断是否可以审核
|
||||
// 这里简单实现,实际应该根据用户角色和流程状态判断
|
||||
return true;
|
||||
}
|
||||
|
||||
/** 通过按钮操作 */
|
||||
function handleApprove(row?: EnrollmentProcessVO) {
|
||||
const selectedRow = row || currentRow.value;
|
||||
if (!selectedRow) {
|
||||
ElMessage.warning('请选择要审核的记录');
|
||||
return;
|
||||
}
|
||||
|
||||
auditForm.processId = selectedRow.id as number;
|
||||
auditForm.auditResult = '1'; // 1-通过
|
||||
auditForm.auditOpinion = '';
|
||||
auditDialogTitle.value = '审核通过';
|
||||
actionType.value = 'approve';
|
||||
auditDialogVisible.value = true;
|
||||
}
|
||||
|
||||
/** 驳回按钮操作 */
|
||||
function handleReject(row?: EnrollmentProcessVO) {
|
||||
const selectedRow = row || currentRow.value;
|
||||
if (!selectedRow) {
|
||||
ElMessage.warning('请选择要审核的记录');
|
||||
return;
|
||||
}
|
||||
|
||||
auditForm.processId = selectedRow.id as number;
|
||||
auditForm.auditResult = '9'; // 9-驳回
|
||||
auditForm.auditOpinion = '';
|
||||
auditDialogTitle.value = '审核驳回';
|
||||
actionType.value = 'reject';
|
||||
auditDialogVisible.value = true;
|
||||
}
|
||||
|
||||
/** 提交审核 */
|
||||
function submitAudit() {
|
||||
auditFormRef.value?.validate((valid: boolean) => {
|
||||
if (valid) {
|
||||
const selectedRow = currentRow.value;
|
||||
if (!selectedRow) {
|
||||
ElMessage.warning('请选择要审核的记录');
|
||||
return;
|
||||
}
|
||||
|
||||
const params = {
|
||||
processId: auditForm.processId,
|
||||
auditOpinion: auditForm.auditOpinion
|
||||
};
|
||||
|
||||
// 根据当前流程状态和用户角色调用不同的审核API
|
||||
// 这里简单实现,实际应该根据用户角色和流程状态调用不同的API
|
||||
if (selectedRow.currentStatus === '0' || selectedRow.currentStatus === '1') {
|
||||
// 信息审核
|
||||
infoAudit({
|
||||
processId: auditForm.processId,
|
||||
auditResult: auditForm.auditResult,
|
||||
auditOpinion: auditForm.auditOpinion
|
||||
}).then(() => {
|
||||
ElMessage.success('审核操作成功');
|
||||
auditDialogVisible.value = false;
|
||||
getList();
|
||||
});
|
||||
} else if (selectedRow.currentStatus === '2') {
|
||||
// 录取处理
|
||||
admissionProcess({
|
||||
processId: auditForm.processId,
|
||||
admissionResult: auditForm.auditResult,
|
||||
admissionOpinion: auditForm.auditOpinion
|
||||
}).then(() => {
|
||||
ElMessage.success('审核操作成功');
|
||||
auditDialogVisible.value = false;
|
||||
getList();
|
||||
});
|
||||
} else if (selectedRow.currentStatus === '3') {
|
||||
// 最终确认
|
||||
finalAudit({
|
||||
processId: auditForm.processId,
|
||||
finalResult: auditForm.auditResult,
|
||||
finalOpinion: auditForm.auditOpinion
|
||||
}).then(() => {
|
||||
ElMessage.success('审核操作成功');
|
||||
auditDialogVisible.value = false;
|
||||
getList();
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getList();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.page-container {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.search-card {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.search-btn-container {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.table-card {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.dialog-footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,847 @@
|
||||
<template>
|
||||
<div class="p-2 dashboard-container">
|
||||
<!-- 头部统计卡片 -->
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="6">
|
||||
<el-card shadow="hover" class="stat-card">
|
||||
<div class="stat-card-content">
|
||||
<div class="stat-icon primary-icon">
|
||||
<el-icon><UserFilled /></el-icon>
|
||||
</div>
|
||||
<div class="stat-info">
|
||||
<div class="stat-value">{{ statisticsData?.totalEnrollments || 0 }}</div>
|
||||
<div class="stat-label">总报名人数</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-card shadow="hover" class="stat-card">
|
||||
<div class="stat-card-content">
|
||||
<div class="stat-icon success-icon">
|
||||
<el-icon><Calendar /></el-icon>
|
||||
</div>
|
||||
<div class="stat-info">
|
||||
<div class="stat-value">{{ statisticsData?.todayEnrollments || 0 }}</div>
|
||||
<div class="stat-label">今日报名人数</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-card shadow="hover" class="stat-card">
|
||||
<div class="stat-card-content">
|
||||
<div class="stat-icon warning-icon">
|
||||
<el-icon><Money /></el-icon>
|
||||
</div>
|
||||
<div class="stat-info">
|
||||
<div class="stat-value">{{ statisticsData?.paidCount || 0 }}</div>
|
||||
<div class="stat-label">已缴费人数</div>
|
||||
<div class="stat-sub-label">缴费率: {{ statisticsData?.paymentRate || '0%' }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-card shadow="hover" class="stat-card">
|
||||
<div class="stat-card-content">
|
||||
<div class="stat-icon info-icon">
|
||||
<el-icon><Medal /></el-icon>
|
||||
</div>
|
||||
<div class="stat-info">
|
||||
<div class="stat-value">{{ statisticsData?.admittedCount || 0 }}</div>
|
||||
<div class="stat-label">已录取人数</div>
|
||||
<div class="stat-sub-label">录取率: {{ statisticsData?.admissionRate || '0%' }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- 图表区域 -->
|
||||
<el-row :gutter="20" class="chart-row">
|
||||
<!-- 近7天报名趋势 -->
|
||||
<el-col :span="16">
|
||||
<el-card shadow="hover" class="chart-card">
|
||||
<template #header>
|
||||
<div class="chart-header">
|
||||
<span class="chart-title">近7天报名趋势</span>
|
||||
<el-radio-group v-model="trendDays" size="small" @change="handleTrendDaysChange">
|
||||
<el-radio-button label="7">7天</el-radio-button>
|
||||
<el-radio-button label="15">15天</el-radio-button>
|
||||
<el-radio-button label="30">30天</el-radio-button>
|
||||
</el-radio-group>
|
||||
</div>
|
||||
</template>
|
||||
<div class="chart-container" v-loading="trendLoading">
|
||||
<div id="trendChart" class="chart"></div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
|
||||
<!-- 性别比例 -->
|
||||
<el-col :span="8">
|
||||
<el-card shadow="hover" class="chart-card">
|
||||
<template #header>
|
||||
<div class="chart-header">
|
||||
<span class="chart-title">性别比例</span>
|
||||
</div>
|
||||
</template>
|
||||
<div class="chart-container" v-loading="genderLoading">
|
||||
<div id="genderChart" class="chart"></div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="20" class="chart-row">
|
||||
<!-- 专业分布 -->
|
||||
<el-col :span="12">
|
||||
<el-card shadow="hover" class="chart-card">
|
||||
<template #header>
|
||||
<div class="chart-header">
|
||||
<span class="chart-title">专业分布</span>
|
||||
</div>
|
||||
</template>
|
||||
<div class="chart-container" v-loading="majorLoading">
|
||||
<div id="majorChart" class="chart"></div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
|
||||
<!-- 地区分布 -->
|
||||
<el-col :span="12">
|
||||
<el-card shadow="hover" class="chart-card">
|
||||
<template #header>
|
||||
<div class="chart-header">
|
||||
<span class="chart-title">地区分布</span>
|
||||
</div>
|
||||
</template>
|
||||
<div class="chart-container" v-loading="regionLoading">
|
||||
<div id="regionChart" class="chart"></div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- 状态分布 -->
|
||||
<el-row>
|
||||
<el-col :span="24">
|
||||
<el-card shadow="hover" class="chart-card">
|
||||
<template #header>
|
||||
<div class="chart-header">
|
||||
<span class="chart-title">录取状态分布</span>
|
||||
</div>
|
||||
</template>
|
||||
<div class="chart-container" v-loading="statusLoading">
|
||||
<div id="statusChart" class="chart"></div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup name="EnrollmentStatistics" lang="ts">
|
||||
import { ref, onMounted, onUnmounted, nextTick } from 'vue';
|
||||
import { UserFilled, Calendar, Money, Medal } from '@element-plus/icons-vue';
|
||||
import * as echarts from 'echarts';
|
||||
import { getStatisticsOverview, getEnrollmentTrend, getGenderRatio, getMajorDistribution, getRegionDistribution, getStatusDistribution, StatisticsOverview } from '@/api/zsjy/statistics';
|
||||
import { ElMessage } from 'element-plus'; // Added ElMessage import
|
||||
|
||||
// 图表实例
|
||||
let trendChartInstance: echarts.ECharts | null = null;
|
||||
let genderChartInstance: echarts.ECharts | null = null;
|
||||
let majorChartInstance: echarts.ECharts | null = null;
|
||||
let regionChartInstance: echarts.ECharts | null = null;
|
||||
let statusChartInstance: echarts.ECharts | null = null;
|
||||
|
||||
// 数据加载状态
|
||||
const loading = ref(true);
|
||||
const trendLoading = ref(false);
|
||||
const genderLoading = ref(false);
|
||||
const majorLoading = ref(false);
|
||||
const regionLoading = ref(false);
|
||||
const statusLoading = ref(false);
|
||||
|
||||
// 统计数据
|
||||
const statisticsData = ref<StatisticsOverview | null>(null);
|
||||
|
||||
// 趋势图天数选择
|
||||
const trendDays = ref<number>(7);
|
||||
|
||||
// 获取统计概览数据
|
||||
const loadStatisticsData = async () => {
|
||||
loading.value = true;
|
||||
try {
|
||||
console.log('开始请求统计概览数据...');
|
||||
const res = await getStatisticsOverview();
|
||||
console.log('统计概览数据响应:', res);
|
||||
|
||||
// 检查响应数据结构
|
||||
if (!res || !res.data) {
|
||||
console.error('统计概览数据为空或格式不正确:', res);
|
||||
ElMessage.error('获取统计数据失败,请联系管理员');
|
||||
return;
|
||||
}
|
||||
|
||||
statisticsData.value = res.data;
|
||||
|
||||
// 加载所有图表
|
||||
loadAllCharts();
|
||||
} catch (error) {
|
||||
console.error('获取统计概览数据失败', error);
|
||||
ElMessage.error('获取统计数据失败,请联系管理员');
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 加载所有图表
|
||||
const loadAllCharts = () => {
|
||||
loadTrendChart();
|
||||
loadGenderChart();
|
||||
loadMajorChart();
|
||||
loadRegionChart();
|
||||
loadStatusChart();
|
||||
};
|
||||
|
||||
// 加载趋势图
|
||||
const loadTrendChart = async () => {
|
||||
trendLoading.value = true;
|
||||
try {
|
||||
console.log('开始请求报名趋势数据...');
|
||||
const res = await getEnrollmentTrend(trendDays.value);
|
||||
console.log('报名趋势数据响应:', res);
|
||||
|
||||
// 检查响应数据结构
|
||||
if (!res || !res.data) {
|
||||
console.error('报名趋势数据为空或格式不正确:', res);
|
||||
return;
|
||||
}
|
||||
|
||||
const trendData = res.data;
|
||||
|
||||
// 初始化趋势图
|
||||
if (!trendChartInstance) {
|
||||
const chartDom = document.getElementById('trendChart');
|
||||
if (!chartDom) {
|
||||
console.error('找不到趋势图DOM元素!');
|
||||
return;
|
||||
}
|
||||
trendChartInstance = echarts.init(chartDom);
|
||||
}
|
||||
|
||||
const dates: string[] = [];
|
||||
const counts: number[] = [];
|
||||
|
||||
if (Array.isArray(trendData)) {
|
||||
trendData.forEach(item => {
|
||||
dates.push(item.date as string);
|
||||
counts.push(item.count as number);
|
||||
});
|
||||
} else {
|
||||
console.error('趋势数据不是数组格式:', trendData);
|
||||
return;
|
||||
}
|
||||
|
||||
const option = {
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'shadow'
|
||||
}
|
||||
},
|
||||
grid: {
|
||||
left: '3%',
|
||||
right: '4%',
|
||||
bottom: '3%',
|
||||
containLabel: true
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: dates,
|
||||
axisLabel: {
|
||||
rotate: dates.length > 10 ? 45 : 0
|
||||
}
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
name: '报名人数',
|
||||
minInterval: 1
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '报名人数',
|
||||
type: 'line',
|
||||
smooth: true,
|
||||
data: counts,
|
||||
areaStyle: {
|
||||
opacity: 0.3,
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||
{ offset: 0, color: '#5d87ff' },
|
||||
{ offset: 1, color: 'rgba(93, 135, 255, 0.1)' }
|
||||
])
|
||||
},
|
||||
lineStyle: {
|
||||
width: 3,
|
||||
color: '#5d87ff'
|
||||
},
|
||||
itemStyle: {
|
||||
color: '#5d87ff'
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
trendChartInstance.setOption(option);
|
||||
} catch (error) {
|
||||
console.error('获取报名趋势数据失败', error);
|
||||
ElMessage.warning('获取报名趋势数据失败');
|
||||
} finally {
|
||||
trendLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 加载性别比例图
|
||||
const loadGenderChart = async () => {
|
||||
genderLoading.value = true;
|
||||
try {
|
||||
console.log('开始请求性别比例数据...');
|
||||
const res = await getGenderRatio();
|
||||
console.log('性别比例数据响应:', res);
|
||||
|
||||
// 检查响应数据结构
|
||||
if (!res || !res.data) {
|
||||
console.error('性别比例数据为空或格式不正确:', res);
|
||||
return;
|
||||
}
|
||||
|
||||
const genderData = res.data;
|
||||
|
||||
// 初始化性别比例图
|
||||
if (!genderChartInstance) {
|
||||
const chartDom = document.getElementById('genderChart');
|
||||
if (!chartDom) {
|
||||
console.error('找不到性别比例图DOM元素!');
|
||||
return;
|
||||
}
|
||||
genderChartInstance = echarts.init(chartDom);
|
||||
}
|
||||
|
||||
const pieData = [];
|
||||
let total = 0;
|
||||
|
||||
// 计算总数
|
||||
Object.keys(genderData).forEach(key => {
|
||||
total += genderData[key];
|
||||
});
|
||||
|
||||
// 转换数据格式
|
||||
Object.keys(genderData).forEach(key => {
|
||||
const name = key === 'M' ? '男' : key === 'F' ? '女' : '未知';
|
||||
const value = genderData[key];
|
||||
const percentage = total > 0 ? ((value / total) * 100).toFixed(1) : '0.0';
|
||||
|
||||
pieData.push({
|
||||
name: `${name}: ${percentage}%`,
|
||||
value
|
||||
});
|
||||
});
|
||||
|
||||
const option = {
|
||||
tooltip: {
|
||||
trigger: 'item',
|
||||
formatter: '{b}'
|
||||
},
|
||||
legend: {
|
||||
orient: 'vertical',
|
||||
right: '5%',
|
||||
top: 'center'
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '性别比例',
|
||||
type: 'pie',
|
||||
radius: ['40%', '70%'],
|
||||
avoidLabelOverlap: false,
|
||||
itemStyle: {
|
||||
borderRadius: 10,
|
||||
borderColor: '#fff',
|
||||
borderWidth: 2
|
||||
},
|
||||
label: {
|
||||
show: false
|
||||
},
|
||||
emphasis: {
|
||||
label: {
|
||||
show: true,
|
||||
fontSize: '12',
|
||||
fontWeight: 'bold'
|
||||
}
|
||||
},
|
||||
labelLine: {
|
||||
show: false
|
||||
},
|
||||
data: pieData,
|
||||
color: ['#5d87ff', '#ff5da0', '#909399']
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
genderChartInstance.setOption(option);
|
||||
} catch (error) {
|
||||
console.error('获取性别比例数据失败', error);
|
||||
ElMessage.warning('获取性别比例数据失败');
|
||||
} finally {
|
||||
genderLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 加载专业分布图
|
||||
const loadMajorChart = async () => {
|
||||
majorLoading.value = true;
|
||||
try {
|
||||
console.log('开始请求专业分布数据...');
|
||||
const res = await getMajorDistribution();
|
||||
console.log('专业分布数据响应:', res);
|
||||
|
||||
// 检查响应数据结构
|
||||
if (!res || !res.data) {
|
||||
console.error('专业分布数据为空或格式不正确:', res);
|
||||
return;
|
||||
}
|
||||
|
||||
const majorData = res.data;
|
||||
|
||||
// 初始化专业分布图
|
||||
if (!majorChartInstance) {
|
||||
const chartDom = document.getElementById('majorChart');
|
||||
if (!chartDom) {
|
||||
console.error('找不到专业分布图DOM元素!');
|
||||
return;
|
||||
}
|
||||
majorChartInstance = echarts.init(chartDom);
|
||||
}
|
||||
|
||||
const majorNames: string[] = [];
|
||||
const majorCounts: number[] = [];
|
||||
|
||||
if (Array.isArray(majorData)) {
|
||||
majorData.forEach(item => {
|
||||
majorNames.push(item.name as string);
|
||||
majorCounts.push(item.value as number);
|
||||
});
|
||||
} else {
|
||||
console.error('专业分布数据不是数组格式:', majorData);
|
||||
return;
|
||||
}
|
||||
|
||||
const option = {
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'shadow'
|
||||
}
|
||||
},
|
||||
grid: {
|
||||
left: '3%',
|
||||
right: '4%',
|
||||
bottom: '3%',
|
||||
containLabel: true
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: majorNames,
|
||||
axisLabel: {
|
||||
interval: 0,
|
||||
rotate: majorNames.length > 5 ? 45 : 0,
|
||||
textStyle: {
|
||||
fontSize: 12
|
||||
}
|
||||
}
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
name: '报名人数',
|
||||
minInterval: 1
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '报名人数',
|
||||
type: 'bar',
|
||||
data: majorCounts,
|
||||
itemStyle: {
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||
{ offset: 0, color: '#5d87ff' },
|
||||
{ offset: 1, color: '#83bff6' }
|
||||
])
|
||||
},
|
||||
emphasis: {
|
||||
itemStyle: {
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||
{ offset: 0, color: '#2378f7' },
|
||||
{ offset: 0.7, color: '#2378f7' },
|
||||
{ offset: 1, color: '#83bff6' }
|
||||
])
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
majorChartInstance.setOption(option);
|
||||
} catch (error) {
|
||||
console.error('获取专业分布数据失败', error);
|
||||
ElMessage.warning('获取专业分布数据失败');
|
||||
} finally {
|
||||
majorLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 加载地区分布图
|
||||
const loadRegionChart = async () => {
|
||||
regionLoading.value = true;
|
||||
try {
|
||||
console.log('开始请求地区分布数据...');
|
||||
const res = await getRegionDistribution();
|
||||
console.log('地区分布数据响应:', res);
|
||||
|
||||
// 检查响应数据结构
|
||||
if (!res || !res.data) {
|
||||
console.error('地区分布数据为空或格式不正确:', res);
|
||||
return;
|
||||
}
|
||||
|
||||
const regionData = res.data;
|
||||
|
||||
// 初始化地区分布图
|
||||
if (!regionChartInstance) {
|
||||
const chartDom = document.getElementById('regionChart');
|
||||
if (!chartDom) {
|
||||
console.error('找不到地区分布图DOM元素!');
|
||||
return;
|
||||
}
|
||||
regionChartInstance = echarts.init(chartDom);
|
||||
}
|
||||
|
||||
const pieData: { name: string; value: number }[] = [];
|
||||
|
||||
if (Array.isArray(regionData)) {
|
||||
regionData.forEach(item => {
|
||||
pieData.push({
|
||||
name: item.name as string,
|
||||
value: item.value as number
|
||||
});
|
||||
});
|
||||
} else {
|
||||
console.error('地区分布数据不是数组格式:', regionData);
|
||||
return;
|
||||
}
|
||||
|
||||
const option = {
|
||||
tooltip: {
|
||||
trigger: 'item',
|
||||
formatter: '{a} <br/>{b}: {c} ({d}%)'
|
||||
},
|
||||
legend: {
|
||||
type: 'scroll',
|
||||
orient: 'vertical',
|
||||
right: 10,
|
||||
top: 20,
|
||||
bottom: 20
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '地区分布',
|
||||
type: 'pie',
|
||||
radius: '55%',
|
||||
center: ['40%', '50%'],
|
||||
data: pieData,
|
||||
emphasis: {
|
||||
itemStyle: {
|
||||
shadowBlur: 10,
|
||||
shadowOffsetX: 0,
|
||||
shadowColor: 'rgba(0, 0, 0, 0.5)'
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
regionChartInstance.setOption(option);
|
||||
} catch (error) {
|
||||
console.error('获取地区分布数据失败', error);
|
||||
ElMessage.warning('获取地区分布数据失败');
|
||||
} finally {
|
||||
regionLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 加载状态分布图
|
||||
const loadStatusChart = async () => {
|
||||
statusLoading.value = true;
|
||||
try {
|
||||
console.log('开始请求状态分布数据...');
|
||||
const res = await getStatusDistribution();
|
||||
console.log('状态分布数据响应:', res);
|
||||
|
||||
// 检查响应数据结构
|
||||
if (!res || !res.data) {
|
||||
console.error('状态分布数据为空或格式不正确:', res);
|
||||
return;
|
||||
}
|
||||
|
||||
const statusData = res.data;
|
||||
|
||||
// 初始化状态分布图
|
||||
if (!statusChartInstance) {
|
||||
const chartDom = document.getElementById('statusChart');
|
||||
if (!chartDom) {
|
||||
console.error('找不到状态分布图DOM元素!');
|
||||
return;
|
||||
}
|
||||
statusChartInstance = echarts.init(chartDom);
|
||||
}
|
||||
|
||||
const statusMap: Record<string, string> = {
|
||||
'0': '未开始',
|
||||
'1': '信息审核中',
|
||||
'2': '缴费确认中',
|
||||
'3': '录取处理中',
|
||||
'4': '最终审核中',
|
||||
'5': '完成录取',
|
||||
'6': '退学',
|
||||
'7': '其他'
|
||||
};
|
||||
|
||||
// 转换为可视化数据
|
||||
const chartData = [];
|
||||
|
||||
// 如果是原始格式 (pending, infoAuditing, processing 等)
|
||||
if (statusData.pending !== undefined) {
|
||||
if (statusData.pending > 0) chartData.push({ name: '未开始', value: statusData.pending });
|
||||
if (statusData.infoAuditing > 0) chartData.push({ name: '信息审核中', value: statusData.infoAuditing });
|
||||
if (statusData.processing > 0) chartData.push({ name: '缴费确认中', value: statusData.processing });
|
||||
if (statusData.finalAuditing > 0) chartData.push({ name: '最终审核中', value: statusData.finalAuditing });
|
||||
if (statusData.completed > 0) chartData.push({ name: '完成录取', value: statusData.completed });
|
||||
if (statusData.rejected > 0) chartData.push({ name: '退学', value: statusData.rejected });
|
||||
}
|
||||
// 如果是数字键值对格式
|
||||
else {
|
||||
Object.keys(statusData).forEach(key => {
|
||||
chartData.push({
|
||||
name: statusMap[key] || `状态${key}`,
|
||||
value: statusData[key]
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
const option = {
|
||||
tooltip: {
|
||||
trigger: 'item',
|
||||
formatter: '{a} <br/>{b}: {c} ({d}%)'
|
||||
},
|
||||
legend: {
|
||||
orient: 'horizontal',
|
||||
bottom: 'bottom'
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '录取状态',
|
||||
type: 'pie',
|
||||
radius: ['30%', '50%'],
|
||||
center: ['50%', '40%'],
|
||||
avoidLabelOverlap: false,
|
||||
itemStyle: {
|
||||
borderRadius: 10,
|
||||
borderColor: '#fff',
|
||||
borderWidth: 2
|
||||
},
|
||||
label: {
|
||||
show: true,
|
||||
formatter: '{b}: {c} ({d}%)'
|
||||
},
|
||||
emphasis: {
|
||||
label: {
|
||||
show: true,
|
||||
fontSize: '12',
|
||||
fontWeight: 'bold'
|
||||
}
|
||||
},
|
||||
labelLine: {
|
||||
show: true
|
||||
},
|
||||
data: chartData
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
statusChartInstance.setOption(option);
|
||||
} catch (error) {
|
||||
console.error('获取状态分布数据失败', error);
|
||||
ElMessage.warning('获取状态分布数据失败');
|
||||
} finally {
|
||||
statusLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 处理趋势图天数变化
|
||||
const handleTrendDaysChange = () => {
|
||||
loadTrendChart();
|
||||
};
|
||||
|
||||
// 窗口大小变化时重新调整图表大小
|
||||
const handleResize = () => {
|
||||
trendChartInstance?.resize();
|
||||
genderChartInstance?.resize();
|
||||
majorChartInstance?.resize();
|
||||
regionChartInstance?.resize();
|
||||
statusChartInstance?.resize();
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
console.log('统计页面已挂载');
|
||||
try {
|
||||
// 加载后台真实数据
|
||||
loadStatisticsData();
|
||||
window.addEventListener('resize', handleResize);
|
||||
console.log('添加窗口resize事件监听');
|
||||
} catch (e) {
|
||||
console.error('页面初始化错误:', e);
|
||||
}
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
// 销毁图表实例
|
||||
trendChartInstance?.dispose();
|
||||
genderChartInstance?.dispose();
|
||||
majorChartInstance?.dispose();
|
||||
regionChartInstance?.dispose();
|
||||
statusChartInstance?.dispose();
|
||||
|
||||
// 移除事件监听
|
||||
window.removeEventListener('resize', handleResize);
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.dashboard-container {
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
height: 120px;
|
||||
margin-bottom: 20px;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.stat-card:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 12px 24px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.stat-card-content {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 20px;
|
||||
}
|
||||
|
||||
.stat-icon {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
border-radius: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-right: 16px;
|
||||
}
|
||||
|
||||
.stat-icon .el-icon {
|
||||
font-size: 28px;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.primary-icon {
|
||||
background: linear-gradient(135deg, #5d87ff 0%, #5d87ff 100%);
|
||||
}
|
||||
|
||||
.success-icon {
|
||||
background: linear-gradient(135deg, #13deab 0%, #13de95 100%);
|
||||
}
|
||||
|
||||
.warning-icon {
|
||||
background: linear-gradient(135deg, #ffae1f 0%, #ffae1f 100%);
|
||||
}
|
||||
|
||||
.info-icon {
|
||||
background: linear-gradient(135deg, #539bff 0%, #539bff 100%);
|
||||
}
|
||||
|
||||
.stat-info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
font-size: 28px;
|
||||
font-weight: 700;
|
||||
color: #303133;
|
||||
margin-bottom: 4px;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 14px;
|
||||
color: #909399;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.stat-sub-label {
|
||||
font-size: 12px;
|
||||
color: #67c23a;
|
||||
background-color: #f0f9eb;
|
||||
display: inline-block;
|
||||
padding: 2px 6px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.chart-row {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.chart-card {
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
height: 100%;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.chart-card:hover {
|
||||
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.chart-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.chart-title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.chart-container {
|
||||
height: 300px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.chart {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
Reference in new issue
Block a user