心理健康2.0
This commit is contained in:
1 parent
067957256d
commit
3b492479b5
63 files changed
+10278
-642
No files matched your search
@@ -0,0 +1,140 @@
|
||||
import request from '@/utils/request'
|
||||
import { AxiosPromise } from 'axios'
|
||||
import { StglAuditorVO, StglAuditorForm, StglAuditorQuery } from './types'
|
||||
|
||||
/**
|
||||
* 查询社团管理审核人配置列表
|
||||
* @param query
|
||||
* @returns {*}
|
||||
*/
|
||||
|
||||
export const listStglAuditor = (query?: StglAuditorQuery): AxiosPromise<StglAuditorVO[]> => {
|
||||
return request({
|
||||
url: '/stgl/auditor/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询社团管理审核人配置详细
|
||||
* @param id
|
||||
*/
|
||||
export const getStglAuditor = (id: string | number): AxiosPromise<StglAuditorVO> => {
|
||||
return request({
|
||||
url: '/stgl/auditor/' + id,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增社团管理审核人配置
|
||||
* @param data
|
||||
*/
|
||||
export const addStglAuditor = (data: StglAuditorForm) => {
|
||||
return request({
|
||||
url: '/stgl/auditor',
|
||||
method: 'post',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改社团管理审核人配置
|
||||
* @param data
|
||||
*/
|
||||
export const updateStglAuditor = (data: StglAuditorForm) => {
|
||||
return request({
|
||||
url: '/stgl/auditor',
|
||||
method: 'put',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除社团管理审核人配置
|
||||
* @param id
|
||||
*/
|
||||
export const delStglAuditor = (id: string | number | Array<string | number>) => {
|
||||
return request({
|
||||
url: '/stgl/auditor/' + id,
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出社团管理审核人配置
|
||||
* @param query
|
||||
* @returns {*}
|
||||
*/
|
||||
export const exportStglAuditor = (query?: StglAuditorQuery) => {
|
||||
return request({
|
||||
url: '/stgl/auditor/export',
|
||||
method: 'post',
|
||||
data: query,
|
||||
responseType: 'blob'
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量添加审核人
|
||||
* @param userIds 用户ID列表
|
||||
* @param remark 备注
|
||||
*/
|
||||
export const batchAddAuditors = (userIds: number[], remark?: string) => {
|
||||
return request({
|
||||
url: '/stgl/auditor/batchAdd',
|
||||
method: 'post',
|
||||
data: {
|
||||
userIds,
|
||||
remark
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询我配置的审核人列表
|
||||
* @param query
|
||||
*/
|
||||
export const listMyConfigAuditors = (query?: StglAuditorQuery): AxiosPromise<StglAuditorVO[]> => {
|
||||
return request({
|
||||
url: '/stgl/auditor/myConfig',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量启用审核人
|
||||
* @param ids
|
||||
*/
|
||||
export const enableAuditors = (ids: number[]) => {
|
||||
return request({
|
||||
url: '/stgl/auditor/enable',
|
||||
method: 'post',
|
||||
data: ids
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量停用审核人
|
||||
* @param ids
|
||||
*/
|
||||
export const disableAuditors = (ids: number[]) => {
|
||||
return request({
|
||||
url: '/stgl/auditor/disable',
|
||||
method: 'post',
|
||||
data: ids
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据部门ID获取用户列表
|
||||
* @param deptId
|
||||
*/
|
||||
export const listUserByDeptId = (deptId: string | number) => {
|
||||
return request({
|
||||
url: '/stgl/auditor/users/' + deptId,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,184 @@
|
||||
import { PageQuery } from '@/api/types'
|
||||
|
||||
export interface StglAuditorVO {
|
||||
/**
|
||||
* 主键ID
|
||||
*/
|
||||
id: string | number;
|
||||
|
||||
/**
|
||||
* 用户ID
|
||||
*/
|
||||
userId: number;
|
||||
|
||||
/**
|
||||
* 用户账号
|
||||
*/
|
||||
userName: string;
|
||||
|
||||
/**
|
||||
* 用户昵称
|
||||
*/
|
||||
nickName: string;
|
||||
|
||||
/**
|
||||
* 部门ID
|
||||
*/
|
||||
deptId: number;
|
||||
|
||||
/**
|
||||
* 部门名称
|
||||
*/
|
||||
deptName: string;
|
||||
|
||||
/**
|
||||
* 联系电话
|
||||
*/
|
||||
phoneNumber: string;
|
||||
|
||||
/**
|
||||
* 邮箱地址
|
||||
*/
|
||||
email: string;
|
||||
|
||||
/**
|
||||
* 状态(1正常 0停用)
|
||||
*/
|
||||
status: string;
|
||||
|
||||
/**
|
||||
* 备注说明
|
||||
*/
|
||||
remark: string;
|
||||
|
||||
/**
|
||||
* 配置人ID
|
||||
*/
|
||||
configBy: number;
|
||||
|
||||
/**
|
||||
* 配置人姓名
|
||||
*/
|
||||
configByName: string;
|
||||
|
||||
/**
|
||||
* 创建者
|
||||
*/
|
||||
createBy: number;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
createTime: string;
|
||||
|
||||
/**
|
||||
* 更新者
|
||||
*/
|
||||
updateBy: number;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
updateTime: string;
|
||||
}
|
||||
|
||||
export interface StglAuditorForm {
|
||||
/**
|
||||
* 主键ID
|
||||
*/
|
||||
id?: string | number;
|
||||
|
||||
/**
|
||||
* 用户ID
|
||||
*/
|
||||
userId?: number;
|
||||
|
||||
/**
|
||||
* 用户账号
|
||||
*/
|
||||
userName?: string;
|
||||
|
||||
/**
|
||||
* 用户昵称
|
||||
*/
|
||||
nickName?: string;
|
||||
|
||||
/**
|
||||
* 部门ID
|
||||
*/
|
||||
deptId?: number;
|
||||
|
||||
/**
|
||||
* 部门名称
|
||||
*/
|
||||
deptName?: string;
|
||||
|
||||
/**
|
||||
* 联系电话
|
||||
*/
|
||||
phoneNumber?: string;
|
||||
|
||||
/**
|
||||
* 邮箱地址
|
||||
*/
|
||||
email?: string;
|
||||
|
||||
/**
|
||||
* 状态(1正常 0停用)
|
||||
*/
|
||||
status?: string;
|
||||
|
||||
/**
|
||||
* 备注说明
|
||||
*/
|
||||
remark?: string;
|
||||
|
||||
/**
|
||||
* 配置人ID
|
||||
*/
|
||||
configBy?: number;
|
||||
|
||||
/**
|
||||
* 配置人姓名
|
||||
*/
|
||||
configByName?: string;
|
||||
}
|
||||
|
||||
export interface StglAuditorQuery extends PageQuery {
|
||||
|
||||
/**
|
||||
* 用户ID
|
||||
*/
|
||||
userId?: number;
|
||||
|
||||
/**
|
||||
* 用户账号
|
||||
*/
|
||||
userName?: string;
|
||||
|
||||
/**
|
||||
* 用户昵称
|
||||
*/
|
||||
nickName?: string;
|
||||
|
||||
/**
|
||||
* 部门ID
|
||||
*/
|
||||
deptId?: number;
|
||||
|
||||
/**
|
||||
* 状态(1正常 0停用)
|
||||
*/
|
||||
status?: string;
|
||||
|
||||
/**
|
||||
* 配置人ID
|
||||
*/
|
||||
configBy?: number;
|
||||
|
||||
/**
|
||||
* 日期范围参数
|
||||
*/
|
||||
'params[beginCreateTime]'?: string;
|
||||
'params[endCreateTime]'?: string;
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
import request from '@/utils/request';
|
||||
import { AxiosPromise } from 'axios';
|
||||
import { StglAnnualReviewAuditVO, StglAnnualReviewAuditForm, StglAnnualReviewAuditQuery } from './type';
|
||||
|
||||
/**
|
||||
* 查询社团年审审批列表
|
||||
* @param query
|
||||
* @returns {*}
|
||||
*/
|
||||
export const listStglAnnualReviewAudit = (query?: StglAnnualReviewAuditQuery): AxiosPromise<StglAnnualReviewAuditVO[]> => {
|
||||
return request({
|
||||
url: '/stgl/annualReviewAudit/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 查询社团年审审批详细
|
||||
* @param id
|
||||
*/
|
||||
export const getStglAnnualReviewAudit = (id: string | number): AxiosPromise<StglAnnualReviewAuditVO> => {
|
||||
return request({
|
||||
url: '/stgl/annualReviewAudit/' + id,
|
||||
method: 'get'
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 根据年审记录ID查询审批记录
|
||||
* @param annualReviewId
|
||||
*/
|
||||
export const getAnnualReviewAuditsByReviewId = (annualReviewId: string | number): AxiosPromise<StglAnnualReviewAuditVO[]> => {
|
||||
return request({
|
||||
url: '/stgl/annualReviewAudit/byAnnualReview/' + annualReviewId,
|
||||
method: 'get'
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 根据审批人ID查询待审批记录
|
||||
* @param approverId
|
||||
*/
|
||||
export const getPendingAuditsByApproverId = (approverId: string | number): AxiosPromise<StglAnnualReviewAuditVO[]> => {
|
||||
return request({
|
||||
url: '/stgl/annualReviewAudit/pending/' + approverId,
|
||||
method: 'get'
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 新增社团年审审批
|
||||
* @param data
|
||||
*/
|
||||
export const addStglAnnualReviewAudit = (data: StglAnnualReviewAuditForm) => {
|
||||
return request({
|
||||
url: '/stgl/annualReviewAudit',
|
||||
method: 'post',
|
||||
data: data
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 修改社团年审审批
|
||||
* @param data
|
||||
*/
|
||||
export const updateStglAnnualReviewAudit = (data: StglAnnualReviewAuditForm) => {
|
||||
return request({
|
||||
url: '/stgl/annualReviewAudit',
|
||||
method: 'put',
|
||||
data: data
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 删除社团年审审批
|
||||
* @param id
|
||||
*/
|
||||
export const delStglAnnualReviewAudit = (id: string | number | Array<string | number>) => {
|
||||
return request({
|
||||
url: '/stgl/annualReviewAudit/' + id,
|
||||
method: 'delete'
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 审批年审记录
|
||||
* @param auditId 审批记录ID
|
||||
* @param approveResult 审批结果
|
||||
* @param approveOpinion 审批意见
|
||||
* @param nextAuditors 下一步审核人列表(可选)
|
||||
*/
|
||||
export const approveAnnualReview = (
|
||||
auditId: string | number,
|
||||
approveResult: string,
|
||||
approveOpinion: string,
|
||||
nextAuditors?: Array<{userId: string | number; userName: string; nickName: string}>
|
||||
) => {
|
||||
return request({
|
||||
url: '/stgl/annualReviewAudit/approve',
|
||||
method: 'post',
|
||||
data: {
|
||||
auditId,
|
||||
approveResult,
|
||||
approveOpinion,
|
||||
nextAuditors: nextAuditors || []
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,182 @@
|
||||
export interface StglAnnualReviewAuditVO {
|
||||
/**
|
||||
* 主键ID
|
||||
*/
|
||||
id: string | number;
|
||||
|
||||
/**
|
||||
* 年审记录ID
|
||||
*/
|
||||
annualReviewId: string | number;
|
||||
|
||||
/**
|
||||
* 节点代码(advisor_approve/college_approve/youth_league_approve)
|
||||
*/
|
||||
nodeCode: string;
|
||||
|
||||
/**
|
||||
* 节点名称(指导老师审批/学院审批/校团委审批)
|
||||
*/
|
||||
nodeName: string;
|
||||
|
||||
/**
|
||||
* 节点顺序(1.1,1.2,2,3)
|
||||
*/
|
||||
nodeOrder: number;
|
||||
|
||||
/**
|
||||
* 节点状态(pending/waiting/approved)
|
||||
*/
|
||||
nodeStatus: string;
|
||||
|
||||
/**
|
||||
* 审批人ID
|
||||
*/
|
||||
approverId: string | number;
|
||||
|
||||
/**
|
||||
* 审批人姓名
|
||||
*/
|
||||
approverName: string;
|
||||
|
||||
/**
|
||||
* 审批结果(通过/不通过)
|
||||
*/
|
||||
approveResult: string;
|
||||
|
||||
/**
|
||||
* 审批意见
|
||||
*/
|
||||
approveOpinion: string;
|
||||
|
||||
/**
|
||||
* 审批时间
|
||||
*/
|
||||
approveTime: string;
|
||||
|
||||
/**
|
||||
* 创建者
|
||||
*/
|
||||
createBy: string | number;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
createTime: string;
|
||||
|
||||
/**
|
||||
* 更新者
|
||||
*/
|
||||
updateBy: string | number;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
updateTime: string;
|
||||
}
|
||||
|
||||
export interface StglAnnualReviewAuditForm {
|
||||
/**
|
||||
* 主键ID
|
||||
*/
|
||||
id?: string | number;
|
||||
|
||||
/**
|
||||
* 年审记录ID
|
||||
*/
|
||||
annualReviewId?: string | number;
|
||||
|
||||
/**
|
||||
* 节点代码(advisor_approve/college_approve/youth_league_approve)
|
||||
*/
|
||||
nodeCode?: string;
|
||||
|
||||
/**
|
||||
* 节点名称(指导老师审批/学院审批/校团委审批)
|
||||
*/
|
||||
nodeName?: string;
|
||||
|
||||
/**
|
||||
* 节点顺序(1.1,1.2,2,3)
|
||||
*/
|
||||
nodeOrder?: number;
|
||||
|
||||
/**
|
||||
* 节点状态(pending/waiting/approved)
|
||||
*/
|
||||
nodeStatus?: string;
|
||||
|
||||
/**
|
||||
* 审批人ID
|
||||
*/
|
||||
approverId?: string | number;
|
||||
|
||||
/**
|
||||
* 审批人姓名
|
||||
*/
|
||||
approverName?: string;
|
||||
|
||||
/**
|
||||
* 审批结果(通过/不通过)
|
||||
*/
|
||||
approveResult?: string;
|
||||
|
||||
/**
|
||||
* 审批意见
|
||||
*/
|
||||
approveOpinion?: string;
|
||||
|
||||
/**
|
||||
* 审批时间
|
||||
*/
|
||||
approveTime?: string;
|
||||
}
|
||||
|
||||
export interface StglAnnualReviewAuditQuery extends PageQuery {
|
||||
/**
|
||||
* 年审记录ID
|
||||
*/
|
||||
annualReviewId?: string | number;
|
||||
|
||||
/**
|
||||
* 节点代码
|
||||
*/
|
||||
nodeCode?: string;
|
||||
|
||||
/**
|
||||
* 节点状态
|
||||
*/
|
||||
nodeStatus?: string;
|
||||
|
||||
/**
|
||||
* 审批人ID
|
||||
*/
|
||||
approverId?: string | number;
|
||||
|
||||
/**
|
||||
* 审批结果
|
||||
*/
|
||||
approveResult?: string;
|
||||
|
||||
/**
|
||||
* 审批时间范围
|
||||
*/
|
||||
params?: any;
|
||||
}
|
||||
|
||||
/**
|
||||
* 审批节点状态选项
|
||||
*/
|
||||
export const AUDIT_NODE_STATUS_OPTIONS = [
|
||||
{ label: '待审批', value: 'pending' },
|
||||
{ label: '等待中', value: 'waiting' },
|
||||
{ label: '已审批', value: 'approved' }
|
||||
];
|
||||
|
||||
/**
|
||||
* 审批结果选项
|
||||
*/
|
||||
export const APPROVE_RESULT_OPTIONS = [
|
||||
{ label: '通过', value: '通过' },
|
||||
{ label: '不通过', value: '不通过' }
|
||||
];
|
||||
@@ -4,18 +4,23 @@ import {
|
||||
XljkConsultationScheduleVO,
|
||||
XljkConsultationScheduleForm,
|
||||
XljkConsultationScheduleQuery,
|
||||
XljkConsultationScheduleSummaryVO,
|
||||
XljkConsultationAppointmentVO,
|
||||
XljkConsultationAppointmentForm,
|
||||
XljkConsultationAppointmentQuery,
|
||||
AvailableTimeSlot,
|
||||
TimeSlotDetail,
|
||||
CounselorOption
|
||||
CounselorOption,
|
||||
ScheduleWithStatus,
|
||||
XljkConsulterVO,
|
||||
XljkConsulterForm,
|
||||
XljkConsulterQuery
|
||||
} from './types';
|
||||
|
||||
// ===== 预约时段管理 API =====
|
||||
|
||||
// 查询预约时段列表
|
||||
export const listXljkSchedule = (query: XljkConsultationScheduleQuery): AxiosPromise<XljkConsultationScheduleVO[]> => {
|
||||
export const listXljkSchedule = (query: XljkConsultationScheduleQuery): AxiosPromise<XljkConsultationScheduleSummaryVO[]> => {
|
||||
return request({
|
||||
url: '/xljk/consultation/schedule/list',
|
||||
method: 'get',
|
||||
@@ -102,6 +107,15 @@ export const getXljkAvailableTimeSlots = (startDate: string, endDate: string): A
|
||||
});
|
||||
};
|
||||
|
||||
// 获取日历视图的排班数据(带预约状态)
|
||||
export const getXljkCalendarSchedules = (startDate: string, endDate: string): AxiosPromise<ScheduleWithStatus[]> => {
|
||||
return request({
|
||||
url: '/xljk/consultation/appointment/calendar-schedules',
|
||||
method: 'get',
|
||||
params: { startDate, endDate }
|
||||
});
|
||||
};
|
||||
|
||||
// 获取可用的详细时段列表(30分钟一个时段)
|
||||
export const getXljkAvailableDetailSlots = (date: string, timeSlot: string): AxiosPromise<TimeSlotDetail[]> => {
|
||||
return request({
|
||||
@@ -213,3 +227,140 @@ export const getXljkConsultationClassOptions = (collegeName?: string, majorName?
|
||||
params: { collegeName, majorName }
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 查询心理咨询师配置列表
|
||||
* @param query
|
||||
* @returns {*}
|
||||
*/
|
||||
|
||||
export const listXljkConsulter = (query?: XljkConsulterQuery): AxiosPromise<XljkConsulterVO[]> => {
|
||||
return request({
|
||||
url: '/xljk/consulter/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询心理咨询师配置详细
|
||||
* @param id
|
||||
*/
|
||||
export const getXljkConsulter = (id: string | number): AxiosPromise<XljkConsulterVO> => {
|
||||
return request({
|
||||
url: '/xljk/consulter/' + id,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增心理咨询师配置
|
||||
* @param data
|
||||
*/
|
||||
export const addXljkConsulter = (data: XljkConsulterForm) => {
|
||||
return request({
|
||||
url: '/xljk/consulter',
|
||||
method: 'post',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改心理咨询师配置
|
||||
* @param data
|
||||
*/
|
||||
export const updateXljkConsulter = (data: XljkConsulterForm) => {
|
||||
return request({
|
||||
url: '/xljk/consulter',
|
||||
method: 'put',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除心理咨询师配置
|
||||
* @param id
|
||||
*/
|
||||
export const delXljkConsulter = (id: string | number | Array<string | number>) => {
|
||||
return request({
|
||||
url: '/xljk/consulter/' + id,
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出心理咨询师配置
|
||||
* @param query
|
||||
* @returns {*}
|
||||
*/
|
||||
export const exportXljkConsulter = (query?: XljkConsulterQuery) => {
|
||||
return request({
|
||||
url: '/xljk/consulter/export',
|
||||
method: 'post',
|
||||
data: query,
|
||||
responseType: 'blob'
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量添加咨询师
|
||||
* @param userIds 用户ID列表
|
||||
* @param remark 备注
|
||||
*/
|
||||
export const batchAddConsulters = (userIds: number[], remark?: string) => {
|
||||
return request({
|
||||
url: '/xljk/consulter/batchAdd',
|
||||
method: 'post',
|
||||
data: {
|
||||
userIds,
|
||||
remark
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询我配置的咨询师列表
|
||||
* @param query
|
||||
*/
|
||||
export const listMyConfigConsulters = (query?: XljkConsulterQuery): AxiosPromise<XljkConsulterVO[]> => {
|
||||
return request({
|
||||
url: '/xljk/consulter/myConfig',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量启用咨询师
|
||||
* @param ids
|
||||
*/
|
||||
export const enableConsulters = (ids: number[]) => {
|
||||
return request({
|
||||
url: '/xljk/consulter/enable',
|
||||
method: 'post',
|
||||
data: ids
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量停用咨询师
|
||||
* @param ids
|
||||
*/
|
||||
export const disableConsulters = (ids: number[]) => {
|
||||
return request({
|
||||
url: '/xljk/consulter/disable',
|
||||
method: 'post',
|
||||
data: ids
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据部门ID获取用户列表
|
||||
* @param deptId
|
||||
*/
|
||||
export const listUserByDeptId = (deptId: string | number) => {
|
||||
return request({
|
||||
url: '/xljk/consulter/users/' + deptId,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
import { PageQuery } from '@/api/types'
|
||||
|
||||
export interface XljkConsultationScheduleVO {
|
||||
/**
|
||||
* 主键ID
|
||||
@@ -14,6 +16,11 @@ export interface XljkConsultationScheduleVO {
|
||||
*/
|
||||
counselorName?: string;
|
||||
|
||||
/**
|
||||
* 办公室
|
||||
*/
|
||||
office?: string;
|
||||
|
||||
/**
|
||||
* 排班日期
|
||||
*/
|
||||
@@ -71,6 +78,11 @@ export interface XljkConsultationScheduleForm extends BaseEntity {
|
||||
*/
|
||||
counselorId?: number;
|
||||
|
||||
/**
|
||||
* 办公室
|
||||
*/
|
||||
office?: string;
|
||||
|
||||
/**
|
||||
* 排班日期
|
||||
*/
|
||||
@@ -103,6 +115,11 @@ export interface XljkConsultationScheduleQuery extends PageQuery {
|
||||
*/
|
||||
counselorId?: number;
|
||||
|
||||
/**
|
||||
* 办公室
|
||||
*/
|
||||
office?: string;
|
||||
|
||||
/**
|
||||
* 排班日期开始
|
||||
*/
|
||||
@@ -130,6 +147,10 @@ export interface XljkConsultationScheduleQuery extends PageQuery {
|
||||
}
|
||||
|
||||
export interface XljkConsultationAppointmentVO extends BaseEntity {
|
||||
/**
|
||||
* 主键ID
|
||||
*/
|
||||
id?: string | number;
|
||||
/**
|
||||
* 学生ID
|
||||
*/
|
||||
@@ -163,13 +184,13 @@ export interface XljkConsultationAppointmentVO extends BaseEntity {
|
||||
*/
|
||||
reason?: string;
|
||||
/**
|
||||
* 状态(0待咨询 1已完成 2已取消 3未到访)
|
||||
* 咨询主题
|
||||
*/
|
||||
status?: number;
|
||||
consultTheme?: string;
|
||||
/**
|
||||
* 实际到访时间
|
||||
* 状态(0待咨询 1已完成 2已取消 3未到访 4待审核 5已驳回)
|
||||
*/
|
||||
actualVisitTime?: any;
|
||||
status?: number | string;
|
||||
/**
|
||||
* 沟通时长(分钟)
|
||||
*/
|
||||
@@ -186,6 +207,26 @@ export interface XljkConsultationAppointmentVO extends BaseEntity {
|
||||
* 学生姓名
|
||||
*/
|
||||
studentName?: string;
|
||||
/**
|
||||
* 心理病史(0-是,1-否)
|
||||
*/
|
||||
psychologicalHistory?: number;
|
||||
/**
|
||||
* 心理病史详情
|
||||
*/
|
||||
psychologicalHistoryDetail?: string;
|
||||
/**
|
||||
* 基本情况
|
||||
*/
|
||||
basicSituation?: string;
|
||||
/**
|
||||
* 重大生活事件
|
||||
*/
|
||||
majorLifeEvents?: string;
|
||||
/**
|
||||
* 是否需要转介(0-是,1-否)
|
||||
*/
|
||||
needReferral?: number;
|
||||
/**
|
||||
* 班级
|
||||
*/
|
||||
@@ -194,6 +235,10 @@ export interface XljkConsultationAppointmentVO extends BaseEntity {
|
||||
* 咨询师姓名
|
||||
*/
|
||||
counselorName?: string;
|
||||
/**
|
||||
* 办公室
|
||||
*/
|
||||
office?: string;
|
||||
/**
|
||||
* 院系
|
||||
*/
|
||||
@@ -202,6 +247,10 @@ export interface XljkConsultationAppointmentVO extends BaseEntity {
|
||||
* 专业
|
||||
*/
|
||||
majorName?: string;
|
||||
/**
|
||||
* 本学期到访次数
|
||||
*/
|
||||
semesterVisitCount?: number;
|
||||
}
|
||||
|
||||
export interface XljkConsultationAppointmentForm {
|
||||
@@ -242,13 +291,13 @@ export interface XljkConsultationAppointmentForm {
|
||||
*/
|
||||
reason?: string;
|
||||
/**
|
||||
* 状态(0待咨询 1已完成 2已取消 3未到访)
|
||||
* 咨询主题
|
||||
*/
|
||||
status?: number;
|
||||
consultTheme?: string;
|
||||
/**
|
||||
* 实际到访时间
|
||||
* 状态(0待咨询 1已完成 2已取消 3未到访 4待审核 5已驳回)
|
||||
*/
|
||||
actualVisitTime?: any;
|
||||
status?: number | string;
|
||||
/**
|
||||
* 沟通时长(分钟)
|
||||
*/
|
||||
@@ -261,6 +310,30 @@ export interface XljkConsultationAppointmentForm {
|
||||
* 咨询师建议
|
||||
*/
|
||||
counselorSuggestion?: string;
|
||||
/**
|
||||
* 学生姓名
|
||||
*/
|
||||
studentName?: string;
|
||||
/**
|
||||
* 心理病史(0-是,1-否)
|
||||
*/
|
||||
psychologicalHistory?: number;
|
||||
/**
|
||||
* 心理病史详情
|
||||
*/
|
||||
psychologicalHistoryDetail?: string;
|
||||
/**
|
||||
* 基本情况
|
||||
*/
|
||||
basicSituation?: string;
|
||||
/**
|
||||
* 重大生活事件
|
||||
*/
|
||||
majorLifeEvents?: string;
|
||||
/**
|
||||
* 是否需要转介(0-是,1-否)
|
||||
*/
|
||||
needReferral?: number;
|
||||
}
|
||||
|
||||
export interface XljkConsultationAppointmentQuery extends PageQuery {
|
||||
@@ -289,6 +362,11 @@ export interface XljkConsultationAppointmentQuery extends PageQuery {
|
||||
*/
|
||||
majorName?: string;
|
||||
|
||||
/**
|
||||
* 班级
|
||||
*/
|
||||
className?: string;
|
||||
|
||||
/**
|
||||
* 咨询师ID
|
||||
*/
|
||||
@@ -312,7 +390,11 @@ export interface XljkConsultationAppointmentQuery extends PageQuery {
|
||||
/**
|
||||
* 日期范围参数
|
||||
*/
|
||||
params?: any;
|
||||
params?: {
|
||||
beginAppointmentDate?: string;
|
||||
endAppointmentDate?: string;
|
||||
[key: string]: any;
|
||||
};
|
||||
}
|
||||
|
||||
export interface AvailableTimeSlot {
|
||||
@@ -331,6 +413,11 @@ export interface AvailableTimeSlot {
|
||||
*/
|
||||
counselorName?: string;
|
||||
|
||||
/**
|
||||
* 办公室
|
||||
*/
|
||||
office?: string;
|
||||
|
||||
/**
|
||||
* 日期
|
||||
*/
|
||||
@@ -419,6 +506,11 @@ export interface CounselorOption {
|
||||
* 咨询师姓名
|
||||
*/
|
||||
name: string;
|
||||
|
||||
/**
|
||||
* 办公室
|
||||
*/
|
||||
office: string;
|
||||
}
|
||||
|
||||
// 预约时段汇总视图对象
|
||||
@@ -433,6 +525,7 @@ export interface XljkConsultationScheduleSummaryVO {
|
||||
deleted: number;
|
||||
counselorId: number;
|
||||
counselorName: string;
|
||||
office: string;
|
||||
dateRange: string;
|
||||
morningEnabled: boolean;
|
||||
morningTime: string;
|
||||
@@ -602,3 +695,291 @@ export interface NoShowVO {
|
||||
*/
|
||||
updateTime?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 带预约状态的排班数据
|
||||
*/
|
||||
export interface ScheduleWithStatus {
|
||||
/**
|
||||
* 排班ID
|
||||
*/
|
||||
scheduleId: number;
|
||||
|
||||
/**
|
||||
* 咨询师ID
|
||||
*/
|
||||
counselorId: number;
|
||||
|
||||
/**
|
||||
* 咨询师姓名
|
||||
*/
|
||||
counselorName?: string;
|
||||
|
||||
/**
|
||||
* 办公室
|
||||
*/
|
||||
office?: string;
|
||||
|
||||
/**
|
||||
* 排班日期
|
||||
*/
|
||||
date: string;
|
||||
|
||||
/**
|
||||
* 时段类型(0上午 1中午 2下午)
|
||||
*/
|
||||
timeSlot: string;
|
||||
|
||||
/**
|
||||
* 开始时间(HH:mm)
|
||||
*/
|
||||
startTime: string;
|
||||
|
||||
/**
|
||||
* 结束时间(HH:mm)
|
||||
*/
|
||||
endTime: string;
|
||||
|
||||
/**
|
||||
* 时段显示文本(如:上午 08:30-12:00)
|
||||
*/
|
||||
timeDisplay: string;
|
||||
|
||||
/**
|
||||
* 是否已预约满
|
||||
*/
|
||||
isFullyBooked: boolean;
|
||||
|
||||
/**
|
||||
* 是否已过期
|
||||
*/
|
||||
isExpired: boolean;
|
||||
|
||||
/**
|
||||
* 可预约数量
|
||||
*/
|
||||
availableSlots: number;
|
||||
|
||||
/**
|
||||
* 总预约数量
|
||||
*/
|
||||
totalSlots: number;
|
||||
|
||||
/**
|
||||
* 创建者
|
||||
*/
|
||||
createBy?: number;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
createTime?: string;
|
||||
|
||||
/**
|
||||
* 更新者
|
||||
*/
|
||||
updateBy?: number;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
updateTime?: string;
|
||||
}
|
||||
|
||||
export interface XljkConsulterVO {
|
||||
/**
|
||||
* 主键ID
|
||||
*/
|
||||
id: string | number;
|
||||
|
||||
/**
|
||||
* 用户ID
|
||||
*/
|
||||
userId: number;
|
||||
|
||||
/**
|
||||
* 用户账号
|
||||
*/
|
||||
userName: string;
|
||||
|
||||
/**
|
||||
* 用户昵称
|
||||
*/
|
||||
nickName: string;
|
||||
|
||||
/**
|
||||
* 部门ID
|
||||
*/
|
||||
deptId: number;
|
||||
|
||||
/**
|
||||
* 部门名称
|
||||
*/
|
||||
deptName: string;
|
||||
|
||||
/**
|
||||
* 办公室
|
||||
*/
|
||||
office?: string;
|
||||
|
||||
/**
|
||||
* 联系电话
|
||||
*/
|
||||
phoneNumber: string;
|
||||
|
||||
/**
|
||||
* 邮箱地址
|
||||
*/
|
||||
email: string;
|
||||
|
||||
/**
|
||||
* 状态(1正常 0停用)
|
||||
*/
|
||||
status: string;
|
||||
|
||||
/**
|
||||
* 备注说明
|
||||
*/
|
||||
remark: string;
|
||||
|
||||
/**
|
||||
* 配置人ID
|
||||
*/
|
||||
configBy: number;
|
||||
|
||||
/**
|
||||
* 配置人姓名
|
||||
*/
|
||||
configByName: string;
|
||||
|
||||
/**
|
||||
* 创建者
|
||||
*/
|
||||
createBy: number;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
createTime: string;
|
||||
|
||||
/**
|
||||
* 更新者
|
||||
*/
|
||||
updateBy: number;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
updateTime: string;
|
||||
}
|
||||
|
||||
export interface XljkConsulterForm {
|
||||
/**
|
||||
* 主键ID
|
||||
*/
|
||||
id?: string | number;
|
||||
|
||||
/**
|
||||
* 用户ID
|
||||
*/
|
||||
userId?: number;
|
||||
|
||||
/**
|
||||
* 用户账号
|
||||
*/
|
||||
userName?: string;
|
||||
|
||||
/**
|
||||
* 用户昵称
|
||||
*/
|
||||
nickName?: string;
|
||||
|
||||
/**
|
||||
* 部门ID
|
||||
*/
|
||||
deptId?: number;
|
||||
|
||||
/**
|
||||
* 部门名称
|
||||
*/
|
||||
deptName?: string;
|
||||
|
||||
/**
|
||||
* 办公室
|
||||
*/
|
||||
office?: string;
|
||||
|
||||
/**
|
||||
* 联系电话
|
||||
*/
|
||||
phoneNumber?: string;
|
||||
|
||||
/**
|
||||
* 邮箱地址
|
||||
*/
|
||||
email?: string;
|
||||
|
||||
/**
|
||||
* 状态(1正常 0停用)
|
||||
*/
|
||||
status?: string;
|
||||
|
||||
/**
|
||||
* 备注说明
|
||||
*/
|
||||
remark?: string;
|
||||
|
||||
/**
|
||||
* 配置人ID
|
||||
*/
|
||||
configBy?: number;
|
||||
|
||||
/**
|
||||
* 配置人姓名
|
||||
*/
|
||||
configByName?: string;
|
||||
}
|
||||
|
||||
export interface XljkConsulterQuery extends PageQuery {
|
||||
|
||||
/**
|
||||
* 用户ID
|
||||
*/
|
||||
userId?: number;
|
||||
|
||||
/**
|
||||
* 用户账号
|
||||
*/
|
||||
userName?: string;
|
||||
|
||||
/**
|
||||
* 用户昵称
|
||||
*/
|
||||
nickName?: string;
|
||||
|
||||
/**
|
||||
* 部门ID
|
||||
*/
|
||||
deptId?: number;
|
||||
|
||||
/**
|
||||
* 状态(1正常 0停用)
|
||||
*/
|
||||
status?: string;
|
||||
|
||||
/**
|
||||
* 配置人ID
|
||||
*/
|
||||
configBy?: number;
|
||||
|
||||
/**
|
||||
* 办公室
|
||||
*/
|
||||
office?: string;
|
||||
|
||||
/**
|
||||
* 日期范围参数
|
||||
*/
|
||||
'params[beginCreateTime]'?: string;
|
||||
'params[endCreateTime]'?: string;
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,350 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
title="年审审批"
|
||||
v-model="visible"
|
||||
width="600px"
|
||||
append-to-body
|
||||
:before-close="handleClose"
|
||||
>
|
||||
<el-form ref="formRef" :model="form" :rules="rules" label-width="100px">
|
||||
<el-form-item label="社团名称:">
|
||||
<span>{{ auditInfo?.clubName || '加载中...' }}</span>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="年审年度:">
|
||||
<span>{{ auditInfo?.reviewYear || '加载中...' }}年</span>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="审批节点:">
|
||||
<span>{{ auditRecord?.nodeName || '加载中...' }}</span>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="审批结果:" prop="approveResult">
|
||||
<el-radio-group v-model="form.approveResult" @change="handleApproveResultChange">
|
||||
<el-radio value="通过">通过</el-radio>
|
||||
<el-radio value="不通过">不通过</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
|
||||
|
||||
|
||||
<el-form-item label="审批意见:" prop="approveOpinion">
|
||||
<el-input
|
||||
v-model="form.approveOpinion"
|
||||
type="textarea"
|
||||
:rows="4"
|
||||
placeholder="请填写审批意见"
|
||||
maxlength="500"
|
||||
show-word-limit
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="handleClose">取消</el-button>
|
||||
<el-button type="primary" @click="handleSubmit" :loading="loading">确定</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 审批流程选择对话框 -->
|
||||
<ApprovalProcessDialog
|
||||
v-model:visible="showApprovalProcessDialog"
|
||||
:next-node-name="getNextNodeName()"
|
||||
@confirm="handleApprovalProcessConfirm"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { approveAnnualReview, getStglAnnualReviewAudit, getAnnualReviewAuditsByReviewId } from '@/api/stgl/club/annualReviewAudit';
|
||||
import { getStglClubAnnualReview } from '@/api/stgl/club/annualReview';
|
||||
import ApprovalProcessDialog from './auditorSelector/ApprovalProcessDialog.vue';
|
||||
|
||||
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
||||
|
||||
const emit = defineEmits<{
|
||||
success: [];
|
||||
}>();
|
||||
|
||||
const visible = ref(false);
|
||||
const loading = ref(false);
|
||||
const formRef = ref<ElFormInstance>();
|
||||
|
||||
const form = ref({
|
||||
auditId: '',
|
||||
approveResult: '',
|
||||
approveOpinion: ''
|
||||
});
|
||||
|
||||
const auditInfo = ref<any>(null);
|
||||
const auditRecord = ref<any>(null);
|
||||
const allAuditRecords = ref<any[]>([]);
|
||||
const showApprovalProcessDialog = ref(false);
|
||||
|
||||
const rules = reactive<ElFormRules>({
|
||||
approveResult: [
|
||||
{ required: true, message: '请选择审批结果', trigger: 'change' }
|
||||
],
|
||||
approveOpinion: [
|
||||
{ required: true, message: '请填写审批意见', trigger: 'blur' },
|
||||
{ max: 500, message: '审批意见不能超过500个字符', trigger: 'blur' }
|
||||
]
|
||||
});
|
||||
|
||||
/** 打开弹窗 */
|
||||
const open = async (auditId: string | number, annualReviewId: string | number) => {
|
||||
visible.value = true;
|
||||
form.value.auditId = auditId.toString();
|
||||
|
||||
// 获取年审记录信息用于显示
|
||||
try {
|
||||
const res = await getStglClubAnnualReview(annualReviewId);
|
||||
auditInfo.value = res.data;
|
||||
} catch (error) {
|
||||
console.error('获取年审记录信息失败:', error);
|
||||
}
|
||||
|
||||
// 获取审批记录信息用于显示节点名称
|
||||
try {
|
||||
const auditRes = await getStglAnnualReviewAudit(auditId);
|
||||
auditRecord.value = auditRes.data;
|
||||
} catch (error) {
|
||||
console.error('获取审批记录信息失败:', error);
|
||||
}
|
||||
|
||||
// 获取当前年审的所有审批记录,用于判断是否是本环节最后一个节点
|
||||
try {
|
||||
const allAuditsRes = await getAnnualReviewAuditsByReviewId(annualReviewId);
|
||||
allAuditRecords.value = allAuditsRes.data || [];
|
||||
} catch (error) {
|
||||
console.error('获取所有审批记录失败:', error);
|
||||
}
|
||||
|
||||
// 重置表单
|
||||
resetForm();
|
||||
};
|
||||
|
||||
/** 重置表单 */
|
||||
const resetForm = () => {
|
||||
form.value.approveResult = '';
|
||||
form.value.approveOpinion = '';
|
||||
formRef.value?.clearValidate();
|
||||
};
|
||||
|
||||
/** 审批结果变化处理 */
|
||||
const handleApproveResultChange = (value: string) => {
|
||||
// 审批结果变化时的处理逻辑(如需要)
|
||||
};
|
||||
|
||||
|
||||
|
||||
/** 关闭弹窗 */
|
||||
const handleClose = () => {
|
||||
visible.value = false;
|
||||
resetForm();
|
||||
auditInfo.value = null;
|
||||
auditRecord.value = null;
|
||||
allAuditRecords.value = [];
|
||||
};
|
||||
|
||||
/** 提交审批 */
|
||||
const handleSubmit = async () => {
|
||||
if (!formRef.value) return;
|
||||
|
||||
const valid = await formRef.value.validate();
|
||||
if (!valid) return;
|
||||
|
||||
// 如果是校团委审批的最后一个节点,直接结束流程,不弹出审批流程选择对话框
|
||||
if (form.value.approveResult === '通过' && isSchoolCommitteeLastNode()) {
|
||||
console.log('校团委最后节点,直接结束流程');
|
||||
await submitApproval();
|
||||
return;
|
||||
}
|
||||
|
||||
// 如果审批通过,且当前节点是本环节的最后一个节点,弹出审批流程选择对话框
|
||||
if (form.value.approveResult === '通过' && isLastNodeInCurrentPhase()) {
|
||||
showApprovalProcessDialog.value = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// 直接提交审批(审批不通过或不是本环节最后一个节点的情况)
|
||||
await submitApproval();
|
||||
};
|
||||
|
||||
/** 判断当前节点是否是本环节的最后一个节点 */
|
||||
const isLastNodeInCurrentPhase = () => {
|
||||
if (!auditRecord.value || !allAuditRecords.value.length) {
|
||||
console.log('判断是否是最后节点: 数据不足', {
|
||||
hasAuditRecord: !!auditRecord.value,
|
||||
allAuditRecordsLength: allAuditRecords.value.length
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
const currentNodeCode = auditRecord.value.nodeCode;
|
||||
const currentNodeOrder = Number(auditRecord.value.nodeOrder);
|
||||
|
||||
console.log('当前审批记录详情:', {
|
||||
currentNodeCode,
|
||||
currentNodeOrder,
|
||||
currentNodeOrderType: typeof auditRecord.value.nodeOrder,
|
||||
currentNodeOrderRaw: auditRecord.value.nodeOrder,
|
||||
auditRecordKeys: Object.keys(auditRecord.value)
|
||||
});
|
||||
|
||||
// 获取同一环节的所有节点
|
||||
const samePhaseNodes = allAuditRecords.value.filter(record =>
|
||||
record.nodeCode === currentNodeCode
|
||||
);
|
||||
|
||||
console.log('所有审批记录:', allAuditRecords.value.map(record => ({
|
||||
id: record.id,
|
||||
nodeCode: record.nodeCode,
|
||||
nodeName: record.nodeName,
|
||||
nodeOrder: record.nodeOrder,
|
||||
nodeOrderType: typeof record.nodeOrder,
|
||||
approverName: record.approverName,
|
||||
nodeStatus: record.nodeStatus
|
||||
})));
|
||||
|
||||
console.log('同环节节点:', samePhaseNodes.map(n => ({
|
||||
nodeOrder: n.nodeOrder,
|
||||
nodeOrderType: typeof n.nodeOrder,
|
||||
approverName: n.approverName,
|
||||
nodeStatus: n.nodeStatus
|
||||
})));
|
||||
|
||||
if (samePhaseNodes.length === 0) {
|
||||
console.log('警告: 没有找到同环节的节点');
|
||||
return false;
|
||||
}
|
||||
|
||||
// 找出同一环节中最大的nodeOrder,使用Number转换确保数值比较
|
||||
const nodeOrders = samePhaseNodes.map(node => Number(node.nodeOrder));
|
||||
const maxNodeOrder = Math.max(...nodeOrders);
|
||||
|
||||
console.log('判断是否是最后节点:', {
|
||||
currentNodeCode,
|
||||
currentNodeOrder,
|
||||
maxNodeOrder,
|
||||
nodeOrders,
|
||||
isLast: currentNodeOrder === maxNodeOrder,
|
||||
comparison: `${currentNodeOrder} === ${maxNodeOrder}`
|
||||
});
|
||||
|
||||
// 当前节点的nodeOrder等于同环节最大nodeOrder时,表示是最后一个节点
|
||||
return currentNodeOrder === maxNodeOrder;
|
||||
};
|
||||
|
||||
/** 判断当前是否是校团委审批的最后一个节点 */
|
||||
const isSchoolCommitteeLastNode = () => {
|
||||
if (!auditRecord.value) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const currentNodeName = auditRecord.value.nodeName;
|
||||
const currentNodeOrder = Number(auditRecord.value.nodeOrder);
|
||||
|
||||
// 检查是否是校团委审批节点且node_order是3.X格式
|
||||
const isSchoolCommitteeNode = currentNodeName === '校团委审批';
|
||||
const isNodeOrder3X = currentNodeOrder >= 3 && currentNodeOrder < 4;
|
||||
|
||||
console.log('校团委最后节点判断:', {
|
||||
currentNodeName,
|
||||
currentNodeOrder,
|
||||
isSchoolCommitteeNode,
|
||||
isNodeOrder3X,
|
||||
isLastInPhase: isLastNodeInCurrentPhase()
|
||||
});
|
||||
|
||||
// 校团委审批 + node_order=3.X + 是本环节最后一个节点
|
||||
return isSchoolCommitteeNode && isNodeOrder3X && isLastNodeInCurrentPhase();
|
||||
};
|
||||
|
||||
/** 获取下一步节点名称 */
|
||||
const getNextNodeName = () => {
|
||||
// 根据当前节点确定下一步节点
|
||||
const currentNodeName = auditRecord.value?.nodeName;
|
||||
|
||||
if (currentNodeName === '指导老师审批') {
|
||||
return '二级学院审批';
|
||||
} else if (currentNodeName === '二级学院审批') {
|
||||
return '校团委审批';
|
||||
} else if (currentNodeName === '校团委审批') {
|
||||
return '完成审批';
|
||||
}
|
||||
|
||||
// 默认返回二级学院审批
|
||||
return '二级学院审批';
|
||||
};
|
||||
|
||||
/** 实际提交审批 */
|
||||
const submitApproval = async (approvalProcessData?: any) => {
|
||||
loading.value = true;
|
||||
try {
|
||||
// 判断是否是校团委最后节点
|
||||
const isSchoolCommitteeFinalNode = isSchoolCommitteeLastNode();
|
||||
|
||||
// 准备审批数据
|
||||
const approveData = {
|
||||
auditId: form.value.auditId,
|
||||
approveResult: form.value.approveResult,
|
||||
approveOpinion: form.value.approveOpinion,
|
||||
// 校团委最后节点时不设置下一步审核人,表示流程结束
|
||||
// 其他情况:只有审批通过且是本环节最后一个节点时,才包含下一步审核人信息
|
||||
nextAuditors: isSchoolCommitteeFinalNode ? [] :
|
||||
(form.value.approveResult === '通过' && isLastNodeInCurrentPhase()) ?
|
||||
(approvalProcessData?.auditors || []).map((auditor: any) => ({
|
||||
userId: auditor.userId,
|
||||
userName: auditor.userName,
|
||||
nickName: auditor.nickName
|
||||
})) : []
|
||||
};
|
||||
|
||||
console.log('提交审批数据:', {
|
||||
审批结果: approveData.approveResult,
|
||||
是否最后节点: isLastNodeInCurrentPhase(),
|
||||
是否校团委最后节点: isSchoolCommitteeFinalNode,
|
||||
下一步审核人数量: approveData.nextAuditors.length,
|
||||
流程状态: isSchoolCommitteeFinalNode ? '流程结束' : '继续流转'
|
||||
});
|
||||
|
||||
await approveAnnualReview(
|
||||
approveData.auditId,
|
||||
approveData.approveResult,
|
||||
approveData.approveOpinion,
|
||||
approveData.nextAuditors
|
||||
);
|
||||
|
||||
if (isSchoolCommitteeFinalNode) {
|
||||
proxy?.$modal.msgSuccess('审批成功,年审流程已完成!');
|
||||
} else {
|
||||
proxy?.$modal.msgSuccess('审批成功');
|
||||
}
|
||||
|
||||
handleClose();
|
||||
emit('success');
|
||||
} catch (error) {
|
||||
console.error('审批失败:', error);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
/** 审批流程确认处理 */
|
||||
const handleApprovalProcessConfirm = async (approvalProcessData: any) => {
|
||||
console.log('审批流程确认,数据:', approvalProcessData);
|
||||
await submitApproval(approvalProcessData);
|
||||
};
|
||||
|
||||
defineExpose({
|
||||
open
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.dialog-footer {
|
||||
text-align: right;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,384 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
title="选择审批流程"
|
||||
v-model="visible"
|
||||
width="70%"
|
||||
append-to-body
|
||||
destroy-on-close
|
||||
:close-on-click-modal="false"
|
||||
:close-on-press-escape="false"
|
||||
>
|
||||
<div class="approval-dialog-content">
|
||||
<el-alert
|
||||
:title="`请选择${props.nextNodeName}审核人`"
|
||||
description="社团年审审批提交后将按照您选择的审批人员顺序进行审批,请仔细选择审核人员。"
|
||||
type="info"
|
||||
:closable="false"
|
||||
show-icon
|
||||
style="margin-bottom: 20px"
|
||||
/>
|
||||
|
||||
<!-- 下一步审批节点提示 -->
|
||||
<div class="next-node-info">
|
||||
<div class="node-label">
|
||||
<el-icon><Document /></el-icon>
|
||||
下一步审批节点:{{ props.nextNodeName }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 当前已选择的审核人显示 -->
|
||||
<div class="current-auditors" v-if="currentAuditors.length > 0">
|
||||
<div class="section-title">
|
||||
<el-icon><User /></el-icon>
|
||||
当前已选择的审核人 ({{ currentAuditors.length }} 人)
|
||||
</div>
|
||||
<div class="auditor-tags">
|
||||
<el-tag
|
||||
v-for="auditor in currentAuditors"
|
||||
:key="auditor.userId"
|
||||
type="info"
|
||||
style="margin: 2px 5px 2px 0;"
|
||||
>
|
||||
{{ auditor.nickName }}({{ auditor.userName }})
|
||||
</el-tag>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 审核人选择区域 -->
|
||||
<div class="auditor-selection-section">
|
||||
<div class="custom-process-config">
|
||||
<el-form-item label="选择审核人" required>
|
||||
<div class="auditor-selector">
|
||||
<div class="selector-header">
|
||||
<el-button type="primary" icon="Plus" @click="openAuditorSelector">添加审核人</el-button>
|
||||
<el-text v-if="selectedAuditors.length > 0" type="info" size="small">
|
||||
已选择 {{ selectedAuditors.length }} 个审核人,将按添加顺序进行审批
|
||||
</el-text>
|
||||
</div>
|
||||
|
||||
<!-- 已选择的审核人列表 -->
|
||||
<div v-if="selectedAuditors.length > 0" class="selected-auditors">
|
||||
<div class="auditor-flow">
|
||||
<div v-for="(auditor, index) in selectedAuditors" :key="auditor.userId" class="auditor-node">
|
||||
<div class="node-content">
|
||||
<span class="node-index">{{ index + 1 }}</span>
|
||||
<div class="auditor-info">
|
||||
<div class="auditor-name">{{ auditor.nickName || auditor.userName }}</div>
|
||||
<div class="auditor-dept">{{ auditor.deptName || '未知部门' }}</div>
|
||||
</div>
|
||||
<el-button type="danger" icon="Delete" size="small" circle @click="removeAuditor(auditor)" />
|
||||
</div>
|
||||
<div v-if="index < selectedAuditors.length - 1" class="flow-arrow">
|
||||
<el-icon><ArrowRight /></el-icon>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 流程预览说明 -->
|
||||
<div class="flow-description">
|
||||
<el-alert type="info" :closable="false" show-icon>
|
||||
<template #title>
|
||||
<span>审批流程预览:{{ props.nextNodeName }}将按上述顺序进行逐级审批,每个节点审批通过后自动流转到下一节点</span>
|
||||
</template>
|
||||
</el-alert>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 空状态提示 -->
|
||||
<div v-else class="empty-state">
|
||||
<el-empty description="暂未选择审核人">
|
||||
<el-button type="primary" @click="openAuditorSelector">选择审核人</el-button>
|
||||
</el-empty>
|
||||
</div>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="handleCancel">取消</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
@click="handleConfirm"
|
||||
:loading="submitting"
|
||||
:disabled="selectedAuditors.length === 0"
|
||||
>
|
||||
<el-icon><Check /></el-icon>
|
||||
确认提交 ({{ selectedAuditors.length }} 个审核人)
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 审核人选择器 -->
|
||||
<AuditorSelector
|
||||
ref="auditorSelectorRef"
|
||||
@confirm="handleAuditorsSelected"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, watch } from 'vue';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { Check, User, UserFilled, Document, Plus, ArrowRight, Delete } from '@element-plus/icons-vue';
|
||||
import AuditorSelector from './index.vue';
|
||||
|
||||
interface Props {
|
||||
visible: boolean;
|
||||
currentAuditors?: any[]; // 当前已选择的审核人
|
||||
nextNodeName?: string; // 下一步审批节点名称
|
||||
}
|
||||
|
||||
interface Emits {
|
||||
(e: 'update:visible', value: boolean): void;
|
||||
(e: 'confirm', approvalData: any): void;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
currentAuditors: () => [],
|
||||
nextNodeName: '二级学院审批'
|
||||
});
|
||||
const emit = defineEmits<Emits>();
|
||||
|
||||
const visible = ref(props.visible);
|
||||
const submitting = ref(false);
|
||||
const selectedAuditors = ref<any[]>([]);
|
||||
const currentAuditors = ref<any[]>([]);
|
||||
const auditorSelectorRef = ref<InstanceType<typeof AuditorSelector>>();
|
||||
|
||||
// 监听visible变化
|
||||
watch(() => props.visible, (newVal) => {
|
||||
visible.value = newVal;
|
||||
if (newVal) {
|
||||
// 初始化当前审核人和选择的审核人
|
||||
currentAuditors.value = [...(props.currentAuditors || [])];
|
||||
selectedAuditors.value = [...(props.currentAuditors || [])];
|
||||
}
|
||||
});
|
||||
|
||||
// 监听内部visible变化
|
||||
watch(visible, (newVal) => {
|
||||
emit('update:visible', newVal);
|
||||
});
|
||||
|
||||
// 打开审核人选择器
|
||||
const openAuditorSelector = () => {
|
||||
auditorSelectorRef.value?.open(selectedAuditors.value);
|
||||
};
|
||||
|
||||
// 审核人选择确认
|
||||
const handleAuditorsSelected = (auditors: any[]) => {
|
||||
selectedAuditors.value = [...auditors];
|
||||
};
|
||||
|
||||
// 移除审核人
|
||||
const removeAuditor = (auditor: any) => {
|
||||
const index = selectedAuditors.value.findIndex(item => item.userId === auditor.userId);
|
||||
if (index > -1) {
|
||||
selectedAuditors.value.splice(index, 1);
|
||||
}
|
||||
};
|
||||
|
||||
// 取消操作
|
||||
const handleCancel = () => {
|
||||
visible.value = false;
|
||||
ElMessage.info('已取消提交');
|
||||
};
|
||||
|
||||
// 确认提交
|
||||
const handleConfirm = async () => {
|
||||
try {
|
||||
if (selectedAuditors.value.length === 0) {
|
||||
ElMessage.warning('请至少选择一个审核人');
|
||||
return;
|
||||
}
|
||||
|
||||
submitting.value = true;
|
||||
|
||||
// 构建审批流程数据
|
||||
const approvalData = {
|
||||
auditorIds: selectedAuditors.value.map(auditor => auditor.userId),
|
||||
processName: '年审审批流程',
|
||||
auditors: selectedAuditors.value
|
||||
};
|
||||
|
||||
console.log('确认审批流程,数据:', approvalData);
|
||||
|
||||
// 触发确认事件
|
||||
emit('confirm', approvalData);
|
||||
|
||||
visible.value = false;
|
||||
ElMessage.success('审批流程配置成功');
|
||||
} catch (error) {
|
||||
console.error('确认审批流程失败:', error);
|
||||
ElMessage.error('确认审批流程失败');
|
||||
} finally {
|
||||
submitting.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 暴露方法供父组件调用
|
||||
defineExpose({
|
||||
open: (preSelectedAuditors?: any[]) => {
|
||||
visible.value = true;
|
||||
currentAuditors.value = [...(preSelectedAuditors || [])];
|
||||
selectedAuditors.value = [...(preSelectedAuditors || [])];
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.approval-dialog-content {
|
||||
max-height: 500px;
|
||||
}
|
||||
|
||||
.dialog-footer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.next-node-info {
|
||||
margin-bottom: 20px;
|
||||
padding: 12px 16px;
|
||||
background-color: #f0f9ff;
|
||||
border: 1px solid #d1ecf1;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.node-label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-weight: 500;
|
||||
color: #0c63e4;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.current-auditors {
|
||||
margin-bottom: 20px;
|
||||
padding: 15px;
|
||||
border: 1px solid #e4e7ed;
|
||||
border-radius: 6px;
|
||||
background-color: #fafafa;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 10px;
|
||||
font-weight: 500;
|
||||
color: #303133;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.auditor-tags {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.auditor-selection-section {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.custom-process-config {
|
||||
padding: 24px;
|
||||
border: 1px solid #e4e7ed;
|
||||
border-radius: 8px;
|
||||
background-color: #fafbfc;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.auditor-selector {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.selector-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.selected-auditors {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.auditor-flow {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.auditor-node {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.node-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 12px 16px;
|
||||
background: white;
|
||||
border: 1px solid #e4e7ed;
|
||||
border-radius: 6px;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.node-index {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
background: #409eff;
|
||||
color: white;
|
||||
border-radius: 50%;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.auditor-info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.auditor-name {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #2c3e50;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.auditor-dept {
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.flow-arrow {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: #409eff;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.flow-description {
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
text-align: center;
|
||||
padding: 40px 20px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,396 @@
|
||||
<template>
|
||||
<div class="auditor-selector">
|
||||
<el-dialog
|
||||
v-model="visible"
|
||||
title="选择审核人"
|
||||
width="900px"
|
||||
append-to-body
|
||||
:before-close="handleClose"
|
||||
>
|
||||
<div class="auditor-select-content">
|
||||
<!-- 搜索区域 -->
|
||||
<div class="search-section" style="margin-bottom: 20px !important;">
|
||||
<el-input v-model="searchKeyword" placeholder="搜索审核人姓名或部门" clearable @input="handleSearch" style="width: 300px">
|
||||
<template #prefix>
|
||||
<el-icon><Search /></el-icon>
|
||||
</template>
|
||||
</el-input>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<!-- 审核人表格 -->
|
||||
<el-table
|
||||
ref="auditorTableRef"
|
||||
:data="tableData"
|
||||
@selection-change="handleSelectionChange"
|
||||
max-height="400"
|
||||
empty-text="暂无可用审核人"
|
||||
style="margin-top: 20px !important;"
|
||||
>
|
||||
<el-table-column type="selection" width="55" :selectable="isSelectable" header-align="center" align="center" />
|
||||
<el-table-column label="序号" type="index" width="60" header-align="center" align="center" />
|
||||
<el-table-column label="姓名" prop="nickName" width="130" header-align="center" align="center" />
|
||||
<el-table-column label="部门" prop="deptName" header-align="center" align="center" />
|
||||
<el-table-column label="联系电话" prop="phoneNumber" width="130" header-align="center" />
|
||||
<el-table-column label="邮箱" prop="email" show-overflow-tooltip header-align="center" />
|
||||
<el-table-column label="状态" prop="status" width="80" header-align="center" align="center">
|
||||
<template #default="scope">
|
||||
<el-tag :type="scope.row.status === '1' ? 'success' : 'danger'" size="small">
|
||||
{{ scope.row.status === '1' ? '正常' : '停用' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<div
|
||||
class="dialog-footer"
|
||||
style="display: flex !important; justify-content: space-between !important; align-items: center !important; width: 100% !important; margin: 0 !important;"
|
||||
>
|
||||
<span class="selected-count-text" style="color: #909399; font-size: 12px;"> 已选择 {{ selectedUsers.length }} 个审核人 </span>
|
||||
<div class="footer-buttons" style="display: flex !important; gap: 12px !important;">
|
||||
<el-button @click="handleClose">取消</el-button>
|
||||
<el-button type="primary" @click="handleConfirm" :disabled="selectedUsers.length === 0"> 确定添加 </el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, watch, onMounted, nextTick, computed, getCurrentInstance } from 'vue';
|
||||
import type { ComponentInternalInstance } from 'vue';
|
||||
import { ElMessage, ElMessageBox, ElTable } from 'element-plus';
|
||||
import { Search } from '@element-plus/icons-vue';
|
||||
import { listStglAuditor } from '@/api/stgl/auditor';
|
||||
import { listDept } from '@/api/system/dept';
|
||||
import { StglAuditorVO, StglAuditorQuery } from '@/api/stgl/auditor/types';
|
||||
import { DeptVO } from '@/api/system/dept/types';
|
||||
|
||||
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
||||
const { sys_normal_disable } = proxy?.useDict('sys_normal_disable') || {};
|
||||
const auditorTableRef = ref();
|
||||
|
||||
const emit = defineEmits<{
|
||||
confirm: [users: StglAuditorVO[]];
|
||||
}>();
|
||||
|
||||
// 响应式数据
|
||||
const visible = ref(false);
|
||||
const loading = ref(false);
|
||||
const userList = ref<StglAuditorVO[]>([]);
|
||||
const filteredUserList = ref<StglAuditorVO[]>([]);
|
||||
const selectedUsers = ref<StglAuditorVO[]>([]);
|
||||
const total = ref(0);
|
||||
const deptOptions = ref<any[]>([]);
|
||||
const searchKeyword = ref('');
|
||||
|
||||
// 计算表格数据
|
||||
const tableData = computed(() => {
|
||||
// 确保返回的数组总是有效的
|
||||
if (searchKeyword.value && filteredUserList.value) {
|
||||
return filteredUserList.value;
|
||||
}
|
||||
return userList.value || [];
|
||||
});
|
||||
|
||||
// 查询参数
|
||||
const queryParams = ref<StglAuditorQuery>({
|
||||
pageNum: 1,
|
||||
pageSize: 500, // 增加页面大小以显示所有审核人
|
||||
nickName: '',
|
||||
userName: '',
|
||||
deptId: undefined,
|
||||
status: '1' // 只查询正常状态的审核人
|
||||
});
|
||||
|
||||
// 初始查询参数的备份,用于重置
|
||||
const defaultQuery: StglAuditorQuery = {
|
||||
pageNum: 1,
|
||||
pageSize: 500, // 增加页面大小以显示所有审核人
|
||||
nickName: '',
|
||||
userName: '',
|
||||
deptId: undefined,
|
||||
status: '1'
|
||||
};
|
||||
|
||||
/** 打开选择器 */
|
||||
const open = (preSelected: StglAuditorVO[] = []) => {
|
||||
visible.value = true;
|
||||
selectedUsers.value = [...preSelected];
|
||||
// 重置搜索状态
|
||||
searchKeyword.value = '';
|
||||
filteredUserList.value = [];
|
||||
getDeptTree();
|
||||
getList();
|
||||
};
|
||||
|
||||
/** 获取部门树 */
|
||||
const getDeptTree = async () => {
|
||||
try {
|
||||
const response = await listDept();
|
||||
deptOptions.value = handleTree(response.data, 'id', 'parentId');
|
||||
} catch (error) {
|
||||
console.error('获取部门树失败:', error);
|
||||
}
|
||||
};
|
||||
|
||||
/** 获取审核人列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true;
|
||||
try {
|
||||
const response = await listStglAuditor(queryParams.value);
|
||||
userList.value = response.rows;
|
||||
filteredUserList.value = response.rows;
|
||||
total.value = response.total;
|
||||
|
||||
// 如果有搜索关键词,执行搜索
|
||||
if (searchKeyword.value) {
|
||||
handleSearch();
|
||||
}
|
||||
|
||||
// 设置表格选中状态
|
||||
nextTick(() => {
|
||||
setTableSelection();
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('获取审核人列表失败:', error);
|
||||
userList.value = [];
|
||||
filteredUserList.value = [];
|
||||
total.value = 0;
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
/** 设置表格选中状态 */
|
||||
const setTableSelection = () => {
|
||||
if (!auditorTableRef.value) return;
|
||||
|
||||
tableData.value.forEach(user => {
|
||||
const isSelected = selectedUsers.value.some(selected =>
|
||||
selected.userId === user.userId
|
||||
);
|
||||
auditorTableRef.value.toggleRowSelection(user, isSelected);
|
||||
});
|
||||
};
|
||||
|
||||
/** 判断行是否可选 */
|
||||
const isSelectable = (row: StglAuditorVO) => {
|
||||
return row.status === '1'; // 只有正常状态的审核人可选
|
||||
};
|
||||
|
||||
/** 表格选择变化 */
|
||||
const handleSelectionChange = (selection: StglAuditorVO[]) => {
|
||||
// 处理当前显示数据的选择
|
||||
const currentPageUserIds = tableData.value.map(user => user.userId);
|
||||
|
||||
// 移除当前页原有的选择
|
||||
selectedUsers.value = selectedUsers.value.filter(user =>
|
||||
!currentPageUserIds.includes(user.userId)
|
||||
);
|
||||
|
||||
// 添加当前页新的选择
|
||||
selectedUsers.value.push(...selection);
|
||||
};
|
||||
|
||||
/** 移除选中审核人 */
|
||||
const removeUser = (user: StglAuditorVO) => {
|
||||
selectedUsers.value = selectedUsers.value.filter(selected =>
|
||||
selected.userId !== user.userId
|
||||
);
|
||||
|
||||
// 如果当前页包含该用户,需要取消表格选中
|
||||
if (auditorTableRef.value) {
|
||||
const currentUser = tableData.value.find(u => u.userId === user.userId);
|
||||
if (currentUser) {
|
||||
auditorTableRef.value.toggleRowSelection(currentUser, false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/** 清空所有选择 */
|
||||
const clearSelected = () => {
|
||||
selectedUsers.value = [];
|
||||
if (auditorTableRef.value) {
|
||||
auditorTableRef.value.clearSelection();
|
||||
}
|
||||
};
|
||||
|
||||
/** 搜索处理 */
|
||||
const handleSearch = () => {
|
||||
if (!searchKeyword.value) {
|
||||
filteredUserList.value = userList.value;
|
||||
return;
|
||||
}
|
||||
|
||||
const keyword = searchKeyword.value.toLowerCase();
|
||||
filteredUserList.value = userList.value.filter(
|
||||
(user) =>
|
||||
(user.nickName && user.nickName.toLowerCase().includes(keyword)) ||
|
||||
(user.userName && user.userName.toLowerCase().includes(keyword)) ||
|
||||
(user.deptName && user.deptName.toLowerCase().includes(keyword))
|
||||
);
|
||||
};
|
||||
|
||||
/** 搜索 */
|
||||
const handleQuery = () => {
|
||||
queryParams.value.pageNum = 1;
|
||||
getList();
|
||||
};
|
||||
|
||||
/** 重置搜索 */
|
||||
const resetQuery = () => {
|
||||
Object.assign(queryParams.value, defaultQuery);
|
||||
handleQuery();
|
||||
};
|
||||
|
||||
/** 关闭弹窗 */
|
||||
const handleClose = () => {
|
||||
visible.value = false;
|
||||
selectedUsers.value = [];
|
||||
userList.value = [];
|
||||
filteredUserList.value = [];
|
||||
searchKeyword.value = '';
|
||||
Object.assign(queryParams.value, defaultQuery);
|
||||
};
|
||||
|
||||
/** 确认选择 */
|
||||
const handleConfirm = () => {
|
||||
emit('confirm', selectedUsers.value);
|
||||
visible.value = false;
|
||||
};
|
||||
|
||||
/** 处理树形数据 */
|
||||
const handleTree = (data: any[], id: string, parentId: string, children = 'children') => {
|
||||
const config = { id, parentId, children };
|
||||
const childrenListMap: any = {};
|
||||
const nodeIds: any = {};
|
||||
const tree = [];
|
||||
|
||||
for (const d of data) {
|
||||
const parentIdValue = d[config.parentId];
|
||||
if (childrenListMap[parentIdValue] == null) {
|
||||
childrenListMap[parentIdValue] = [];
|
||||
}
|
||||
nodeIds[d[config.id]] = d;
|
||||
childrenListMap[parentIdValue].push(d);
|
||||
}
|
||||
|
||||
for (const d of data) {
|
||||
const parentIdValue = d[config.parentId];
|
||||
if (nodeIds[parentIdValue] == null) {
|
||||
tree.push(d);
|
||||
}
|
||||
}
|
||||
|
||||
for (const t of tree) {
|
||||
adaptToChildrenArray(t);
|
||||
}
|
||||
|
||||
function adaptToChildrenArray(o: any) {
|
||||
if (childrenListMap[o[config.id]] !== null) {
|
||||
o[config.children] = childrenListMap[o[config.id]];
|
||||
}
|
||||
if (o[config.children]) {
|
||||
for (const c of o[config.children]) {
|
||||
adaptToChildrenArray(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return tree.map(item => ({
|
||||
id: item.id,
|
||||
label: item.deptName,
|
||||
children: item.children ? item.children.map((child: any) => convertToTreeNode(child)) : []
|
||||
}));
|
||||
};
|
||||
|
||||
const convertToTreeNode = (item: any): any => ({
|
||||
id: item.id,
|
||||
label: item.deptName,
|
||||
children: item.children ? item.children.map((child: any) => convertToTreeNode(child)) : []
|
||||
});
|
||||
|
||||
defineExpose({
|
||||
open
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.auditor-selector {
|
||||
.auditor-select-content {
|
||||
max-height: 500px;
|
||||
}
|
||||
|
||||
.search-section {
|
||||
margin-top: 20px !important;
|
||||
margin-bottom: 20px !important;
|
||||
}
|
||||
|
||||
|
||||
|
||||
:deep(.el-table) {
|
||||
margin-top: 20px !important;
|
||||
}
|
||||
|
||||
.dialog-footer {
|
||||
display: flex !important;
|
||||
justify-content: space-between !important;
|
||||
align-items: center !important;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.footer-buttons {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/* Element Plus Dialog Footer 样式覆盖 - 使用最高优先级的选择器 */
|
||||
.auditor-selector :deep(.el-dialog__footer) {
|
||||
padding: 15px 20px !important;
|
||||
text-align: left !important;
|
||||
display: block !important;
|
||||
}
|
||||
|
||||
.auditor-selector :deep(.el-dialog__footer .dialog-footer) {
|
||||
display: flex !important;
|
||||
justify-content: space-between !important;
|
||||
align-items: center !important;
|
||||
width: 100% !important;
|
||||
margin: 0 !important;
|
||||
padding: 0 !important;
|
||||
box-sizing: border-box !important;
|
||||
}
|
||||
|
||||
/* 确保按钮容器样式正确 */
|
||||
.auditor-selector .dialog-footer .footer-buttons {
|
||||
display: flex !important;
|
||||
gap: 12px !important;
|
||||
margin-left: auto !important;
|
||||
flex-shrink: 0 !important;
|
||||
}
|
||||
|
||||
/* 确保文本不会撑开容器 */
|
||||
.auditor-selector .dialog-footer .selected-count-text {
|
||||
flex-shrink: 0 !important;
|
||||
white-space: nowrap !important;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* 确保搜索框和表格间距的全局样式 */
|
||||
.auditor-selector .auditor-select-content .search-section {
|
||||
margin-bottom: 20px !important;
|
||||
}
|
||||
|
||||
.auditor-selector .auditor-select-content .el-table {
|
||||
margin-top: 20px !important;
|
||||
}
|
||||
</style>
|
||||
@@ -124,8 +124,27 @@
|
||||
<!-- 指导老师意见 -->
|
||||
<div class="opinion-section">
|
||||
<div class="opinion-title">指导老师意见</div>
|
||||
<div class="opinion-content">
|
||||
{{ annualReview.advisorOpinion || '暂无意见' }}
|
||||
<div v-if="advisorAudits.length > 0" class="audit-records">
|
||||
<div v-for="audit in advisorAudits" :key="audit.id" class="audit-record">
|
||||
<div class="audit-header">
|
||||
<span class="approver-name">{{ audit.approverName }}</span>
|
||||
<span class="audit-status" :class="getAuditStatusClass(audit.nodeStatus)">
|
||||
{{ getAuditStatusText(audit.nodeStatus) }}
|
||||
</span>
|
||||
<span v-if="audit.approveTime" class="audit-time">
|
||||
{{ parseTime(audit.approveTime, '{y}-{m}-{d} {h}:{i}:{s}') }}
|
||||
</span>
|
||||
</div>
|
||||
<div v-if="audit.approveOpinion" class="audit-opinion">
|
||||
{{ audit.approveOpinion }}
|
||||
</div>
|
||||
<div v-else class="audit-opinion pending">
|
||||
待审批
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="opinion-content">
|
||||
暂无审批记录
|
||||
</div>
|
||||
<div class="opinion-signature">签名(公章)</div>
|
||||
</div>
|
||||
@@ -133,8 +152,27 @@
|
||||
<!-- 二级学院意见 -->
|
||||
<div class="opinion-section">
|
||||
<div class="opinion-title">二级学院意见</div>
|
||||
<div class="opinion-content">
|
||||
{{ annualReview.collegeOpinion || '暂无意见' }}
|
||||
<div v-if="collegeAudits.length > 0" class="audit-records">
|
||||
<div v-for="audit in collegeAudits" :key="audit.id" class="audit-record">
|
||||
<div class="audit-header">
|
||||
<span class="approver-name">{{ audit.approverName }}</span>
|
||||
<span class="audit-status" :class="getAuditStatusClass(audit.nodeStatus)">
|
||||
{{ getAuditStatusText(audit.nodeStatus) }}
|
||||
</span>
|
||||
<span v-if="audit.approveTime" class="audit-time">
|
||||
{{ parseTime(audit.approveTime, '{y}-{m}-{d} {h}:{i}:{s}') }}
|
||||
</span>
|
||||
</div>
|
||||
<div v-if="audit.approveOpinion" class="audit-opinion">
|
||||
{{ audit.approveOpinion }}
|
||||
</div>
|
||||
<div v-else class="audit-opinion pending">
|
||||
待审批
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="opinion-content">
|
||||
暂无审批记录
|
||||
</div>
|
||||
<div class="opinion-signature">签名(公章)</div>
|
||||
</div>
|
||||
@@ -142,8 +180,27 @@
|
||||
<!-- 校团委意见 -->
|
||||
<div class="opinion-section">
|
||||
<div class="opinion-title">校团委意见</div>
|
||||
<div class="opinion-content">
|
||||
{{ annualReview.youthLeagueOpinion || '暂无意见' }}
|
||||
<div v-if="youthLeagueAudits.length > 0" class="audit-records">
|
||||
<div v-for="audit in youthLeagueAudits" :key="audit.id" class="audit-record">
|
||||
<div class="audit-header">
|
||||
<span class="approver-name">{{ audit.approverName }}</span>
|
||||
<span class="audit-status" :class="getAuditStatusClass(audit.nodeStatus)">
|
||||
{{ getAuditStatusText(audit.nodeStatus) }}
|
||||
</span>
|
||||
<span v-if="audit.approveTime" class="audit-time">
|
||||
{{ parseTime(audit.approveTime, '{y}-{m}-{d} {h}:{i}:{s}') }}
|
||||
</span>
|
||||
</div>
|
||||
<div v-if="audit.approveOpinion" class="audit-opinion">
|
||||
{{ audit.approveOpinion }}
|
||||
</div>
|
||||
<div v-else class="audit-opinion pending">
|
||||
待审批
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="opinion-content">
|
||||
暂无审批记录
|
||||
</div>
|
||||
<div class="opinion-signature">签名(公章)</div>
|
||||
</div>
|
||||
@@ -158,7 +215,9 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { getStglClubAnnualReview, getAnnualReviewActivities } from '@/api/stgl/club/annualReview';
|
||||
import { getAnnualReviewAuditsByReviewId } from '@/api/stgl/club/annualReviewAudit';
|
||||
import { StglClubAnnualReviewVO } from '@/api/stgl/club/annualReview/type';
|
||||
import { StglAnnualReviewAuditVO } from '@/api/stgl/club/annualReviewAudit/type';
|
||||
|
||||
// 扩展的年审VO接口(包含JSON字段)
|
||||
interface ExtendedAnnualReviewVO extends StglClubAnnualReviewVO {
|
||||
@@ -180,6 +239,8 @@ const annualReview = ref<ExtendedAnnualReviewVO>();
|
||||
const memberList = ref<any[]>([]);
|
||||
// 活动列表
|
||||
const activityList = ref<any[]>([]);
|
||||
// 审批记录列表
|
||||
const auditList = ref<StglAnnualReviewAuditVO[]>([]);
|
||||
|
||||
// 年份范围计算
|
||||
const yearRange = computed(() => {
|
||||
@@ -208,6 +269,41 @@ const memberCountText = computed(() => {
|
||||
return `${annualReview.value?.memberCount || 0}人`;
|
||||
});
|
||||
|
||||
// 指导老师审批记录
|
||||
const advisorAudits = computed(() => {
|
||||
return auditList.value.filter(audit => audit.nodeCode === 'advisor_approve');
|
||||
});
|
||||
|
||||
// 二级学院审批记录
|
||||
const collegeAudits = computed(() => {
|
||||
return auditList.value.filter(audit => audit.nodeCode === 'college_approve');
|
||||
});
|
||||
|
||||
// 校团委审批记录
|
||||
const youthLeagueAudits = computed(() => {
|
||||
return auditList.value.filter(audit => audit.nodeCode === 'youth_league_approve');
|
||||
});
|
||||
|
||||
/** 获取审批状态样式类 */
|
||||
const getAuditStatusClass = (status: string) => {
|
||||
const statusClasses: { [key: string]: string } = {
|
||||
'pending': 'status-pending',
|
||||
'waiting': 'status-waiting',
|
||||
'approved': 'status-approved'
|
||||
};
|
||||
return statusClasses[status] || '';
|
||||
};
|
||||
|
||||
/** 获取审批状态文本 */
|
||||
const getAuditStatusText = (status: string) => {
|
||||
const statusTexts: { [key: string]: string } = {
|
||||
'pending': '待审批',
|
||||
'waiting': '等待中',
|
||||
'approved': '已审批'
|
||||
};
|
||||
return statusTexts[status] || status;
|
||||
};
|
||||
|
||||
/** 格式化活动时间显示 */
|
||||
const formatActivityTime = (startTime: string, endTime?: string): string => {
|
||||
if (!startTime) return '';
|
||||
@@ -302,6 +398,19 @@ const getDetail = async () => {
|
||||
memberList.value = [];
|
||||
}
|
||||
|
||||
// 获取审批记录
|
||||
try {
|
||||
const auditRes = await getAnnualReviewAuditsByReviewId(props.annualReviewId);
|
||||
if (auditRes && auditRes.data && Array.isArray(auditRes.data)) {
|
||||
auditList.value = auditRes.data;
|
||||
} else {
|
||||
auditList.value = [];
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('获取审批记录失败:', error);
|
||||
auditList.value = [];
|
||||
}
|
||||
|
||||
// 从stgl_club_annual_review_activity表中获取年审记录对应的活动数据
|
||||
try {
|
||||
const activitiesRes = await getAnnualReviewActivities(props.annualReviewId);
|
||||
@@ -524,4 +633,79 @@ watch(
|
||||
line-height: 1.4;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
/* 审批记录样式 */
|
||||
.audit-records {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.audit-record {
|
||||
margin-bottom: 15px;
|
||||
padding: 12px;
|
||||
border: 1px solid #e4e7ed;
|
||||
border-radius: 4px;
|
||||
background: #fafafa;
|
||||
}
|
||||
|
||||
.audit-record:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.audit-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 8px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.approver-name {
|
||||
font-weight: bold;
|
||||
color: #303133;
|
||||
margin-right: 15px;
|
||||
}
|
||||
|
||||
.audit-status {
|
||||
padding: 2px 8px;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
margin-right: 15px;
|
||||
}
|
||||
|
||||
.status-pending {
|
||||
background: #fdf6ec;
|
||||
color: #e6a23c;
|
||||
border: 1px solid #f5dab1;
|
||||
}
|
||||
|
||||
.status-waiting {
|
||||
background: #f4f4f5;
|
||||
color: #909399;
|
||||
border: 1px solid #d3d4d6;
|
||||
}
|
||||
|
||||
.status-approved {
|
||||
background: #f0f9ff;
|
||||
color: #67c23a;
|
||||
border: 1px solid #b3d8ff;
|
||||
}
|
||||
|
||||
.audit-time {
|
||||
color: #909399;
|
||||
font-size: 12px;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.audit-opinion {
|
||||
line-height: 1.6;
|
||||
color: #606266;
|
||||
background: #fff;
|
||||
padding: 8px;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #e4e7ed;
|
||||
}
|
||||
|
||||
.audit-opinion.pending {
|
||||
color: #909399;
|
||||
font-style: italic;
|
||||
}
|
||||
</style>
|
||||
@@ -459,9 +459,24 @@ const loadClubInfo = async (clubId: string | number) => {
|
||||
try {
|
||||
const advisors = JSON.parse(clubData.advisorsJson);
|
||||
if (Array.isArray(advisors) && advisors.length > 0) {
|
||||
const firstAdvisor = advisors[0];
|
||||
advisorName = firstAdvisor.name || firstAdvisor.teacher?.nickName || '';
|
||||
advisorDept = firstAdvisor.dept || firstAdvisor.teacher?.deptName || '';
|
||||
// 处理多个指导老师的情况
|
||||
const advisorNames = [];
|
||||
const advisorDepts = [];
|
||||
|
||||
advisors.forEach(advisor => {
|
||||
const name = advisor.name || advisor.teacher?.nickName || '';
|
||||
const dept = advisor.dept || advisor.teacher?.deptName || '';
|
||||
|
||||
if (name) {
|
||||
advisorNames.push(name);
|
||||
}
|
||||
if (dept && !advisorDepts.includes(dept)) {
|
||||
advisorDepts.push(dept);
|
||||
}
|
||||
});
|
||||
|
||||
advisorName = advisorNames.join('、');
|
||||
advisorDept = advisorDepts.join('、');
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('解析指导老师JSON数据失败:', error);
|
||||
|
||||
@@ -57,7 +57,7 @@
|
||||
</el-row>
|
||||
</template>
|
||||
|
||||
<el-table v-loading="loading" border :data="annualReviewList" @selection-change="handleSelectionChange" @row-dblclick="handleView">
|
||||
<el-table v-loading="loading" border :data="annualReviewList" :key="tableKey" @selection-change="handleSelectionChange" @row-dblclick="handleView">
|
||||
<el-table-column type="selection" width="55" align="center" />
|
||||
<el-table-column label="社团名称" align="center" prop="clubName" min-width="150" />
|
||||
<el-table-column label="成立日期" align="center" prop="establishDate" width="120">
|
||||
@@ -80,11 +80,11 @@
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="状态" align="center" prop="status" width="100">
|
||||
<template #default="scope">
|
||||
<el-tag :type="getStatusTagType(scope.row.status) as any">{{ getStatusText(scope.row.status) }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="状态" align="center" prop="status" width="120">
|
||||
<template #default="scope">
|
||||
<el-tag :type="getStatusTagType(scope.row.status) as any">{{ getStatusText(scope.row.status) }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="年审时间" align="center" prop="createTime" width="180">
|
||||
<template #default="scope">
|
||||
<span>{{ parseTime(scope.row.createTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
|
||||
@@ -104,7 +104,10 @@
|
||||
<el-tooltip content="撤回" placement="top" v-if="scope.row.status === '1'">
|
||||
<el-button link type="warning" icon="RefreshLeft" @click="handleWithdraw(scope.row)" v-hasPermi="['stgl:clubAnnualReview:withdraw']"></el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="删除" placement="top" v-if="scope.row.status === '0'">
|
||||
<el-tooltip content="审批" placement="top" v-if="(scope.row.status === '1' || scope.row.status === '2') && canApprove(scope.row)">
|
||||
<el-button link type="success" icon="Check" @click="handleApprove(scope.row)" v-hasPermi="['stgl:annualReviewAudit:approve']"></el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="删除" placement="top" v-if="scope.row.status === '0' || scope.row.status === '4'">
|
||||
<el-button link type="danger" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['stgl:clubAnnualReview:remove']"></el-button>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
@@ -129,10 +132,15 @@
|
||||
<el-dialog title="年审记录详情" v-model="detailDialog.visible" width="1000px" append-to-body>
|
||||
<annual-review-detail :annual-review-id="detailDialog.annualReviewId" />
|
||||
</el-dialog>
|
||||
|
||||
<!-- 审批对话框 -->
|
||||
<approve-dialog ref="approveDialogRef" @success="handleApproveSuccess" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup name="StglClubAnnualReview" lang="ts">
|
||||
import { ref, reactive, toRefs, nextTick, onMounted, getCurrentInstance } from 'vue';
|
||||
import type { ComponentInternalInstance } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import {
|
||||
listStglClubAnnualReview,
|
||||
@@ -143,11 +151,16 @@ import {
|
||||
exportStglClubAnnualReview
|
||||
} from '@/api/stgl/club/annualReview';
|
||||
import { StglClubAnnualReviewVO, StglClubAnnualReviewQuery, ANNUAL_REVIEW_STATUS_OPTIONS } from '@/api/stgl/club/annualReview/type';
|
||||
import { getPendingAuditsByApproverId } from '@/api/stgl/club/annualReviewAudit';
|
||||
import { StglAnnualReviewAuditVO } from '@/api/stgl/club/annualReviewAudit/type';
|
||||
import { useUserStore } from '@/store/modules/user';
|
||||
import AnnualReviewForm from './form.vue';
|
||||
import AnnualReviewDetail from './detail.vue';
|
||||
import ApproveDialog from './approveDialog.vue';
|
||||
|
||||
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
||||
const route = useRoute();
|
||||
const userStore = useUserStore();
|
||||
const { stgl_club_type } = toRefs(proxy?.useDict('stgl_club_type') as any);
|
||||
|
||||
const annualReviewList = ref<StglClubAnnualReviewVO[]>([]);
|
||||
@@ -157,9 +170,16 @@ const ids = ref<Array<string | number>>([]);
|
||||
const single = ref(true);
|
||||
const multiple = ref(true);
|
||||
const total = ref(0);
|
||||
const tableKey = ref(0); // 用于强制重新渲染表格
|
||||
|
||||
// 审批权限相关状态
|
||||
const pendingAudits = ref<StglAnnualReviewAuditVO[]>([]);
|
||||
const pendingAuditsLoading = ref(false);
|
||||
const pendingAuditsCache = ref<Map<number, boolean>>(new Map());
|
||||
|
||||
const queryFormRef = ref<ElFormInstance>();
|
||||
const annualReviewFormRef = ref<InstanceType<typeof AnnualReviewForm>>();
|
||||
const approveDialogRef = ref<InstanceType<typeof ApproveDialog>>();
|
||||
|
||||
// 状态选项
|
||||
const statusOptions = ref(ANNUAL_REVIEW_STATUS_OPTIONS);
|
||||
@@ -214,10 +234,36 @@ const { queryParams, dialog, detailDialog } = toRefs(data);
|
||||
/** 查询社团年审记录列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true;
|
||||
const res = await listStglClubAnnualReview(queryParams.value);
|
||||
annualReviewList.value = res.rows;
|
||||
total.value = res.total;
|
||||
loading.value = false;
|
||||
try {
|
||||
// 并行加载年审记录和待审批记录
|
||||
const [annualReviewRes] = await Promise.all([
|
||||
listStglClubAnnualReview(queryParams.value),
|
||||
loadPendingAudits()
|
||||
]);
|
||||
|
||||
// 确保数据更新
|
||||
if (annualReviewRes && annualReviewRes.rows) {
|
||||
annualReviewList.value = annualReviewRes.rows;
|
||||
total.value = annualReviewRes.total;
|
||||
} else {
|
||||
console.warn('获取的年审记录数据格式异常:', annualReviewRes);
|
||||
annualReviewList.value = [];
|
||||
total.value = 0;
|
||||
}
|
||||
|
||||
// 强制触发 Vue 的响应式更新
|
||||
nextTick(() => {
|
||||
console.log('年审记录列表已更新,记录数:', annualReviewList.value.length);
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('加载数据失败:', error);
|
||||
proxy?.$modal.msgError('加载数据失败');
|
||||
// 重置数据
|
||||
annualReviewList.value = [];
|
||||
total.value = 0;
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
@@ -279,20 +325,51 @@ const handleView = (row: StglClubAnnualReviewVO) => {
|
||||
const handleSubmit = async (row: StglClubAnnualReviewVO) => {
|
||||
try {
|
||||
await proxy?.$modal.confirm(`是否确认提交年审记录"${row.clubName}"?`);
|
||||
|
||||
// 调用提交接口
|
||||
await submitStglClubAnnualReview(row.id);
|
||||
await getList();
|
||||
|
||||
// API调用成功后,立即更新本地状态,避免被getList覆盖
|
||||
await updateRowStatus(row.id, '1');
|
||||
|
||||
proxy?.$modal.msgSuccess('提交成功');
|
||||
} catch {}
|
||||
|
||||
// 延迟重新获取数据,确保状态已经在界面上显示
|
||||
setTimeout(async () => {
|
||||
await getList();
|
||||
}, 100);
|
||||
|
||||
} catch (error) {
|
||||
console.error('提交失败:', error);
|
||||
// 提交失败时才需要恢复状态,因为我们现在是先调用API再更新状态
|
||||
}
|
||||
};
|
||||
|
||||
/** 撤回按钮操作 */
|
||||
const handleWithdraw = async (row: StglClubAnnualReviewVO) => {
|
||||
try {
|
||||
await proxy?.$modal.confirm(`是否确认撤回年审记录"${row.clubName}"?`);
|
||||
|
||||
// 调用撤回接口
|
||||
await withdrawStglClubAnnualReview(row.id);
|
||||
await getList();
|
||||
|
||||
// API调用成功后,立即更新本地状态,避免被getList覆盖
|
||||
await updateRowStatus(row.id, '0');
|
||||
|
||||
proxy?.$modal.msgSuccess('撤回成功');
|
||||
} catch {}
|
||||
|
||||
// 延迟重新获取数据,确保状态已经在界面上显示
|
||||
setTimeout(async () => {
|
||||
await Promise.all([
|
||||
getList(),
|
||||
loadPendingAudits()
|
||||
]);
|
||||
}, 100);
|
||||
|
||||
} catch (error) {
|
||||
console.error('撤回失败:', error);
|
||||
// 撤回失败时才需要恢复状态,因为我们现在是先调用API再更新状态
|
||||
}
|
||||
};
|
||||
|
||||
/** 删除按钮操作 */
|
||||
@@ -322,6 +399,30 @@ const handleFormSuccess = () => {
|
||||
getList();
|
||||
};
|
||||
|
||||
/** 更新行状态的工具函数 */
|
||||
const updateRowStatus = async (rowId: string | number, newStatus: string) => {
|
||||
const rowIndex = annualReviewList.value.findIndex(item => item.id === rowId);
|
||||
if (rowIndex !== -1) {
|
||||
// 创建新对象确保Vue能检测到变化
|
||||
const updatedRow = {
|
||||
...annualReviewList.value[rowIndex],
|
||||
status: newStatus
|
||||
};
|
||||
// 使用splice替换整个对象,触发响应式更新
|
||||
annualReviewList.value.splice(rowIndex, 1, updatedRow);
|
||||
|
||||
// 强制触发DOM更新和表格重新渲染
|
||||
tableKey.value++;
|
||||
await nextTick();
|
||||
|
||||
console.log(`状态已同步更新: ${updatedRow.status}`);
|
||||
console.log('当前数组状态:', annualReviewList.value.map(item => ({ id: item.id, status: item.status, clubName: item.clubName })));
|
||||
|
||||
return updatedRow;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
/** 获取状态标签类型 */
|
||||
const getStatusTagType = (status: string) => {
|
||||
const tagTypes: { [key: string]: string } = {
|
||||
@@ -336,8 +437,114 @@ const getStatusTagType = (status: string) => {
|
||||
|
||||
/** 获取状态文本 */
|
||||
const getStatusText = (status: string) => {
|
||||
// 本地状态映射,确保显示正确
|
||||
const statusMap: { [key: string]: string } = {
|
||||
'0': '草稿',
|
||||
'1': '已提交',
|
||||
'2': '审核中',
|
||||
'3': '审核通过',
|
||||
'4': '审核不通过'
|
||||
};
|
||||
|
||||
// 优先使用本地映射
|
||||
if (statusMap[status]) {
|
||||
const result = statusMap[status];
|
||||
console.log(`getStatusText: 状态=${status}, 返回文本="${result}"`);
|
||||
return result;
|
||||
}
|
||||
|
||||
// fallback到原来的逻辑
|
||||
const option = statusOptions.value.find(item => item.value === status);
|
||||
return option?.label || status;
|
||||
const result = option?.label || status;
|
||||
console.log(`getStatusText: 状态=${status}, fallback返回="${result}"`);
|
||||
return result;
|
||||
};
|
||||
|
||||
/** 获取当前用户的待审批记录 */
|
||||
const loadPendingAudits = async () => {
|
||||
if (!userStore.userId || pendingAuditsLoading.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
pendingAuditsLoading.value = true;
|
||||
const response = await getPendingAuditsByApproverId(userStore.userId);
|
||||
|
||||
// 处理响应数据,确保 data 是数组
|
||||
// 后端返回的是 R<List<StglAnnualReviewAudit>>,R.data 可能为 null 或 []
|
||||
let data = [];
|
||||
if (response && response.data !== undefined) {
|
||||
// 如果response.data存在但为null,使用空数组
|
||||
data = response.data || [];
|
||||
} else if (response && response.rows !== undefined) {
|
||||
// 有些API返回格式可能是分页格式
|
||||
data = response.rows || [];
|
||||
}
|
||||
|
||||
pendingAudits.value = data;
|
||||
|
||||
// 更新缓存
|
||||
pendingAuditsCache.value.clear();
|
||||
if (Array.isArray(data)) {
|
||||
data.forEach((audit: StglAnnualReviewAuditVO) => {
|
||||
if (audit.annualReviewId) {
|
||||
pendingAuditsCache.value.set(Number(audit.annualReviewId), true);
|
||||
}
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取待审批记录失败:', error);
|
||||
// 重置数据
|
||||
pendingAudits.value = [];
|
||||
pendingAuditsCache.value.clear();
|
||||
// 不显示错误提示,避免影响页面正常使用
|
||||
} finally {
|
||||
pendingAuditsLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
/** 判断当前用户是否可以审批该年审记录 */
|
||||
const canApprove = (row: StglClubAnnualReviewVO) => {
|
||||
// 检查当前用户是否登录
|
||||
if (!userStore.userId) {
|
||||
console.log('canApprove: 用户未登录');
|
||||
return false;
|
||||
}
|
||||
|
||||
// 检查年审记录状态,支持"已提交"和"审核中"状态
|
||||
if (row.status !== '1' && row.status !== '2') {
|
||||
console.log(`canApprove: 状态不符合 ${row.status}`);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 检查缓存中是否有该年审记录的审批权限
|
||||
const hasPermission = pendingAuditsCache.value.has(Number(row.id));
|
||||
console.log(`canApprove: 年审ID=${row.id}, 当前用户=${userStore.userId}, 缓存权限=${hasPermission}, 缓存内容=`, Array.from(pendingAuditsCache.value.keys()));
|
||||
return hasPermission;
|
||||
};
|
||||
|
||||
/** 审批按钮操作 */
|
||||
const handleApprove = async (row: StglClubAnnualReviewVO) => {
|
||||
// 从待审批记录中找到对应的审批ID
|
||||
const audit = pendingAudits.value.find(audit =>
|
||||
Number(audit.annualReviewId) === Number(row.id)
|
||||
);
|
||||
|
||||
if (!audit) {
|
||||
proxy?.$modal.msgError('未找到对应的审批记录');
|
||||
return;
|
||||
}
|
||||
|
||||
approveDialogRef.value?.open(audit.id, row.id);
|
||||
};
|
||||
|
||||
/** 审批成功回调 */
|
||||
const handleApproveSuccess = async () => {
|
||||
// 审批完成后,需要同时刷新年审记录和待审批记录
|
||||
await Promise.all([
|
||||
getList(),
|
||||
loadPendingAudits()
|
||||
]);
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
|
||||
@@ -1278,9 +1278,9 @@ watch(
|
||||
() => form.value.advisors,
|
||||
(advisors: AdvisorVO[]) => {
|
||||
// 防止递归更新
|
||||
if (syncingAdvisors.value) return;
|
||||
if (syncLock.value) return;
|
||||
|
||||
syncingAdvisors.value = true;
|
||||
syncLock.value = true;
|
||||
nextTick(() => {
|
||||
try {
|
||||
// 为了兼容旧版本,同步第一个指导老师的信息到旧字段
|
||||
@@ -1297,7 +1297,7 @@ watch(
|
||||
form.value.advisorEmail = undefined;
|
||||
}
|
||||
} finally {
|
||||
syncingAdvisors.value = false;
|
||||
syncLock.value = false;
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
@@ -33,21 +33,21 @@
|
||||
<el-col :span="6">
|
||||
<el-form-item label="院系" prop="collegeName">
|
||||
<el-select v-model="queryParams.collegeName" placeholder="请选择院系" clearable style="width: 100%" @change="handleCollegeChange">
|
||||
<el-option v-for="college in collegeOptions" :key="college.name" :label="college.name" :value="college.name" />
|
||||
<el-option v-for="college in collegeOptions" :key="college.value" :label="college.label" :value="college.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="专业" prop="majorName">
|
||||
<el-select v-model="queryParams.majorName" placeholder="请选择专业" clearable style="width: 100%" @change="handleMajorChange">
|
||||
<el-option v-for="major in majorOptions" :key="major.name" :label="major.name" :value="major.name" />
|
||||
<el-option v-for="major in majorOptions" :key="major.value" :label="major.label" :value="major.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="班级" prop="className">
|
||||
<el-select v-model="queryParams.className" placeholder="请选择班级" clearable style="width: 100%">
|
||||
<el-option v-for="classItem in classOptions" :key="classItem.name" :label="classItem.name" :value="classItem.name" />
|
||||
<el-option v-for="classItem in classOptions" :key="classItem.value" :label="classItem.label" :value="classItem.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
@@ -106,10 +106,28 @@
|
||||
<el-tag v-else-if="scope.row.status === 1" type="success">已到访</el-tag>
|
||||
<el-tag v-else-if="scope.row.status === 2" type="danger">已取消</el-tag>
|
||||
<el-tag v-else-if="scope.row.status === 3" type="warning">未到访</el-tag>
|
||||
<el-tag v-else-if="scope.row.status === 4" type="info">待审核</el-tag>
|
||||
<el-tag v-else-if="scope.row.status === 5" type="danger">已驳回</el-tag>
|
||||
<el-tag v-else type="info">未知状态</el-tag>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="是否需要转介" align="center" prop="needReferral" width="120">
|
||||
<template #default="scope">
|
||||
<div v-if="scope.row.status === 1">
|
||||
<el-tag v-if="scope.row.needReferral === 2" type="warning">是</el-tag>
|
||||
<el-tag v-else-if="scope.row.needReferral === 1" type="success">否</el-tag>
|
||||
<span v-else>-</span>
|
||||
</div>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="本学期到访次数" align="center" prop="semesterVisitCount" width="120">
|
||||
<template #default="scope">
|
||||
<el-tag v-if="scope.row.semesterVisitCount > 0" type="info">{{ scope.row.semesterVisitCount }}次</el-tag>
|
||||
<span v-else>0次</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="创建时间" align="center" prop="createTime" width="150">
|
||||
<template #default="scope">
|
||||
<span>{{ parseTime(scope.row.createTime) }}</span>
|
||||
@@ -129,6 +147,31 @@
|
||||
></el-button>
|
||||
</el-tooltip>
|
||||
|
||||
<!-- 待审核状态可审核通过或拒绝 -->
|
||||
<el-tooltip content="审核通过" placement="top" v-if="scope.row.status === 4">
|
||||
<el-button
|
||||
link
|
||||
type="success"
|
||||
icon="Check"
|
||||
size="default"
|
||||
style="font-size: 16px; margin-right: 8px"
|
||||
@click="handleApprove(scope.row)"
|
||||
v-hasPermi="['xljk:consultationappointment:edit']"
|
||||
></el-button>
|
||||
</el-tooltip>
|
||||
|
||||
<el-tooltip content="审核拒绝" placement="top" v-if="scope.row.status === 4">
|
||||
<el-button
|
||||
link
|
||||
type="danger"
|
||||
icon="Close"
|
||||
size="default"
|
||||
style="font-size: 16px; margin-right: 8px"
|
||||
@click="handleReject(scope.row)"
|
||||
v-hasPermi="['xljk:consultationappointment:edit']"
|
||||
></el-button>
|
||||
</el-tooltip>
|
||||
|
||||
<!-- 待咨询状态可记录咨询结果 -->
|
||||
<el-tooltip content="记录结果" placement="top" v-if="scope.row.status === 0">
|
||||
<el-button
|
||||
@@ -161,29 +204,45 @@
|
||||
<el-descriptions-item label="咨询师">{{ form.counselorName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="预约日期">{{ parseTime(form.appointmentDate, '{y}-{m}-{d}') }}</el-descriptions-item>
|
||||
<el-descriptions-item label="预约时段">{{ form.appointmentDetailTime }}</el-descriptions-item>
|
||||
<el-descriptions-item label="咨询原因" :span="2">{{ form.reason }}</el-descriptions-item>
|
||||
<el-descriptions-item label="咨询主题">{{ form.consultTheme || '未选择' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="咨询原因">{{ form.reason }}</el-descriptions-item>
|
||||
<el-descriptions-item label="状态">
|
||||
<el-tag v-if="form.status === 0" type="primary">待咨询</el-tag>
|
||||
<el-tag v-else-if="form.status === 1" type="success">已到访</el-tag>
|
||||
<el-tag v-else-if="form.status === 2" type="danger">已取消</el-tag>
|
||||
<el-tag v-else-if="form.status === 3" type="warning">未到访</el-tag>
|
||||
<el-tag v-else-if="form.status === 4" type="info">待审核</el-tag>
|
||||
<el-tag v-else-if="form.status === 5" type="danger">已驳回</el-tag>
|
||||
<el-tag v-else type="info">未知状态({{ form.status }})</el-tag>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="创建时间">{{ parseTime(form.createTime) }}</el-descriptions-item>
|
||||
|
||||
<!-- 只有在已到访状态时才显示咨询结果相关字段 -->
|
||||
<template v-if="form.status == 1">
|
||||
<el-descriptions-item label="实际到访时间">
|
||||
{{ form.actualVisitTime ? parseTime(form.actualVisitTime, '{y}-{m}-{d} {h}:{i}:{s}') : '未记录' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="沟通时长">
|
||||
{{ form.communicationDuration ? form.communicationDuration + ' 分钟' : '未记录' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="咨询内容" :span="2">
|
||||
<el-descriptions-item label="心理病史">
|
||||
<el-tag v-if="form.psychologicalHistory === 2" type="warning">是</el-tag>
|
||||
<el-tag v-else-if="form.psychologicalHistory === 1" type="success">否</el-tag>
|
||||
<span v-else>未记录</span>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="心理病史详情" :span="2" v-if="form.psychologicalHistory === 2">
|
||||
{{ form.psychologicalHistoryDetail || '未记录' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="基本情况" :span="2">
|
||||
{{ form.basicSituation || '未记录' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="重大生活事件" :span="2">
|
||||
{{ form.majorLifeEvents || '未记录' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="本次咨询过程" :span="2">
|
||||
{{ form.consultationContent || '未记录' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="咨询师建议" :span="2">
|
||||
{{ form.counselorSuggestion || '未记录' }}
|
||||
<el-descriptions-item label="是否需要转介">
|
||||
<el-tag v-if="form.needReferral === 2" type="warning">是</el-tag>
|
||||
<el-tag v-else-if="form.needReferral === 1" type="success">否</el-tag>
|
||||
<span v-else>未记录</span>
|
||||
</el-descriptions-item>
|
||||
</template>
|
||||
</el-descriptions>
|
||||
@@ -196,8 +255,8 @@
|
||||
</el-dialog>
|
||||
|
||||
<!-- 记录咨询结果对话框 -->
|
||||
<el-dialog title="记录咨询结果" v-model="recordDialogVisible" width="600px" append-to-body>
|
||||
<el-form ref="recordFormRef" :model="recordForm" :rules="recordRules" label-width="100px">
|
||||
<el-dialog title="记录咨询结果" v-model="recordDialogVisible" width="800px" append-to-body>
|
||||
<el-form ref="recordFormRef" :model="recordForm" :rules="recordRules" label-width="120px">
|
||||
<el-form-item label="学生姓名">
|
||||
<el-input v-model="recordForm.studentName" disabled />
|
||||
</el-form-item>
|
||||
@@ -215,27 +274,35 @@
|
||||
</el-form-item>
|
||||
<!-- 只有在已到访状态时才显示以下字段 -->
|
||||
<template v-if="recordForm.status === 1">
|
||||
<el-form-item label="实际到访时间" prop="actualVisitTime" class="no-wrap-label">
|
||||
<el-date-picker
|
||||
v-model="recordForm.actualVisitTime"
|
||||
type="datetime"
|
||||
placeholder="选择日期时间"
|
||||
style="width: 100%"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
format="YYYY-MM-DD HH:mm:ss"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="沟通时长" prop="communicationDuration">
|
||||
<div class="flex-row align-center">
|
||||
<el-input-number v-model="recordForm.communicationDuration" :min="1" :max="180" />
|
||||
<span class="ml-2">分钟</span>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="咨询内容" prop="consultationContent">
|
||||
<el-input v-model="recordForm.consultationContent" type="textarea" placeholder="请输入咨询内容" :rows="4" />
|
||||
<el-form-item label="心理病史" prop="psychologicalHistory">
|
||||
<el-radio-group v-model="recordForm.psychologicalHistory" @change="handlePsychologicalHistoryChange">
|
||||
<el-radio :value="2">是</el-radio>
|
||||
<el-radio :value="1">否</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="咨询师建议" prop="counselorSuggestion">
|
||||
<el-input v-model="recordForm.counselorSuggestion" type="textarea" placeholder="请输入咨询师建议" :rows="4" />
|
||||
<el-form-item label="心理病史详情" prop="psychologicalHistoryDetail" v-if="recordForm.psychologicalHistory === 2">
|
||||
<el-input v-model="recordForm.psychologicalHistoryDetail" type="textarea" placeholder="请详细描述心理病史" :rows="3" />
|
||||
</el-form-item>
|
||||
<el-form-item label="基本情况" prop="basicSituation">
|
||||
<el-input v-model="recordForm.basicSituation" type="textarea" placeholder="请输入基本情况" :rows="4" />
|
||||
</el-form-item>
|
||||
<el-form-item label="重大生活事件" prop="majorLifeEvents">
|
||||
<el-input v-model="recordForm.majorLifeEvents" type="textarea" placeholder="请输入重大生活事件(可选)" :rows="3" />
|
||||
</el-form-item>
|
||||
<el-form-item label="本次咨询过程" prop="consultationContent">
|
||||
<el-input v-model="recordForm.consultationContent" type="textarea" placeholder="请输入本次咨询过程" :rows="4" />
|
||||
</el-form-item>
|
||||
<el-form-item label="是否需要转介" prop="needReferral">
|
||||
<el-radio-group v-model="recordForm.needReferral" @change="handleReferralChange">
|
||||
<el-radio :value="2">是</el-radio>
|
||||
<el-radio :value="1">否</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</template>
|
||||
</el-form>
|
||||
@@ -268,7 +335,8 @@ import {
|
||||
CounselorOption,
|
||||
CollegeOption,
|
||||
MajorOption,
|
||||
ClassOption
|
||||
ClassOption,
|
||||
XljkConsultationAppointmentQuery
|
||||
} from '@/api/xljk/consultation/types';
|
||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||
import { parseTime } from '@/utils/ruoyi';
|
||||
@@ -281,7 +349,9 @@ const appointment_status = ref([
|
||||
{ label: '待咨询', value: 0 },
|
||||
{ label: '已到访', value: 1 },
|
||||
{ label: '已取消', value: 2 },
|
||||
{ label: '未到访', value: 3 }
|
||||
{ label: '未到访', value: 3 },
|
||||
{ label: '待审核', value: 4 },
|
||||
{ label: '已驳回', value: 5 }
|
||||
]);
|
||||
|
||||
// 遮罩层
|
||||
@@ -304,19 +374,17 @@ const open = ref(false);
|
||||
const dateRange = ref<string[]>([]);
|
||||
// 咨询师选项
|
||||
const counselorOptions = ref<CounselorOption[]>([]);
|
||||
// 院系选项
|
||||
const collegeOptions = ref<CollegeOption[]>([]);
|
||||
// 专业选项
|
||||
const majorOptions = ref<MajorOption[]>([]);
|
||||
// 班级选项
|
||||
const classOptions = ref<ClassOption[]>([]);
|
||||
// 修复院系、专业、班级选项的类型定义
|
||||
const collegeOptions = ref<Array<{ value: string; label: string }>>([]);
|
||||
const majorOptions = ref<Array<{ value: string; label: string }>>([]);
|
||||
const classOptions = ref<Array<{ value: string; label: string }>>([]);
|
||||
// 是否显示记录对话框
|
||||
const recordDialogVisible = ref(false);
|
||||
// 提交按钮加载状态
|
||||
const submitLoading = ref(false);
|
||||
|
||||
// 查询参数
|
||||
const queryParams = reactive({
|
||||
// 查询参数的类型定义和字段修复
|
||||
const queryParams = reactive<XljkConsultationAppointmentQuery>({
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
studentName: undefined,
|
||||
@@ -339,20 +407,28 @@ const recordForm = reactive<XljkConsultationAppointmentForm>({
|
||||
scheduleId: undefined,
|
||||
appointmentDate: '',
|
||||
appointmentTime: '',
|
||||
appointmentDetailTime: '',
|
||||
reason: '',
|
||||
status: 1, // 默认已到访,使用数值类型
|
||||
actualVisitTime: undefined,
|
||||
communicationDuration: 30, // 默认30分钟
|
||||
consultationContent: '',
|
||||
counselorSuggestion: ''
|
||||
counselorSuggestion: '',
|
||||
studentName: '',
|
||||
psychologicalHistory: 1, // 默认否
|
||||
psychologicalHistoryDetail: '',
|
||||
basicSituation: '',
|
||||
majorLifeEvents: '',
|
||||
needReferral: 1 // 默认不需要转介
|
||||
});
|
||||
|
||||
// 记录表单校验
|
||||
const recordRules = ref({
|
||||
// 修复表单验证规则的类型定义
|
||||
const recordRules = ref<any>({
|
||||
status: [{ required: true, message: '请选择咨询结果', trigger: 'change' }],
|
||||
actualVisitTime: [{ required: true, message: '请选择实际到访时间', trigger: 'change' }],
|
||||
communicationDuration: [{ required: true, message: '请输入沟通时长', trigger: 'blur' }],
|
||||
consultationContent: [{ required: true, message: '请输入咨询内容', trigger: 'blur' }]
|
||||
psychologicalHistory: [{ required: true, message: '请选择心理病史', trigger: 'change' }],
|
||||
basicSituation: [{ required: true, message: '请输入基本情况', trigger: 'blur' }],
|
||||
consultationContent: [{ required: true, message: '请输入本次咨询过程', trigger: 'blur' }],
|
||||
needReferral: [{ required: true, message: '请选择是否需要转介', trigger: 'change' }]
|
||||
});
|
||||
|
||||
// 监听状态变化,动态调整表单验证规则
|
||||
@@ -368,9 +444,11 @@ watch(
|
||||
// 已到访
|
||||
recordRules.value = {
|
||||
status: [{ required: true, message: '请选择咨询结果', trigger: 'change' }],
|
||||
actualVisitTime: [{ required: true, message: '请选择实际到访时间', trigger: 'change' }],
|
||||
communicationDuration: [{ required: true, message: '请输入沟通时长', trigger: 'blur' }],
|
||||
consultationContent: [{ required: true, message: '请输入咨询内容', trigger: 'blur' }]
|
||||
psychologicalHistory: [{ required: true, message: '请选择心理病史', trigger: 'change' }],
|
||||
basicSituation: [{ required: true, message: '请输入基本情况', trigger: 'blur' }],
|
||||
consultationContent: [{ required: true, message: '请输入本次咨询过程', trigger: 'blur' }],
|
||||
needReferral: [{ required: true, message: '请选择是否需要转介', trigger: 'change' }]
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -384,7 +462,9 @@ const getList = async () => {
|
||||
loading.value = true;
|
||||
try {
|
||||
// 处理日期范围
|
||||
queryParams.params = {};
|
||||
if (!queryParams.params) {
|
||||
queryParams.params = {};
|
||||
}
|
||||
if (dateRange.value && dateRange.value.length === 2) {
|
||||
queryParams.params.beginAppointmentDate = dateRange.value[0];
|
||||
queryParams.params.endAppointmentDate = dateRange.value[1];
|
||||
@@ -503,24 +583,13 @@ const handleView = async (row: XljkConsultationAppointmentVO) => {
|
||||
console.log('转换后的status:', form.status);
|
||||
}
|
||||
|
||||
// 检查实际到访时间
|
||||
console.log('实际到访时间:', form.actualVisitTime);
|
||||
if (form.status === 1 && !form.actualVisitTime) {
|
||||
console.warn('已到访状态但实际到访时间为空,尝试使用行数据');
|
||||
if (row.actualVisitTime) {
|
||||
form.actualVisitTime = row.actualVisitTime;
|
||||
console.log('使用行数据的实际到访时间:', form.actualVisitTime);
|
||||
} else {
|
||||
console.warn('行数据也没有实际到访时间');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
console.log('复制后的表单数据:', form);
|
||||
|
||||
// 如果是已到访状态,确保咨询结果相关字段有值
|
||||
if (form.status === 1) {
|
||||
console.log('已到访状态,咨询结果相关字段:', {
|
||||
actualVisitTime: form.actualVisitTime,
|
||||
communicationDuration: form.communicationDuration,
|
||||
consultationContent: form.consultationContent,
|
||||
counselorSuggestion: form.counselorSuggestion
|
||||
@@ -558,6 +627,58 @@ const handleView = async (row: XljkConsultationAppointmentVO) => {
|
||||
}
|
||||
};
|
||||
|
||||
/** 审核通过操作 */
|
||||
const handleApprove = async (row: XljkConsultationAppointmentVO) => {
|
||||
try {
|
||||
await ElMessageBox.confirm('确认审核通过该预约申请?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
});
|
||||
|
||||
// 构建更新数据
|
||||
const updateData = {
|
||||
id: row.id,
|
||||
status: "0" // 审核通过,状态改为待咨询
|
||||
};
|
||||
|
||||
await updateXljkAppointment(updateData);
|
||||
ElMessage.success('审核通过成功');
|
||||
getList();
|
||||
} catch (error) {
|
||||
if (error !== 'cancel') {
|
||||
console.error('审核通过失败', error);
|
||||
ElMessage.error('审核通过失败');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/** 审核拒绝操作 */
|
||||
const handleReject = async (row: XljkConsultationAppointmentVO) => {
|
||||
try {
|
||||
await ElMessageBox.confirm('确认拒绝该预约申请?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
});
|
||||
|
||||
// 构建更新数据
|
||||
const updateData = {
|
||||
id: row.id,
|
||||
status: "5" // 拒绝,状态改为已驳回
|
||||
};
|
||||
|
||||
await updateXljkAppointment(updateData);
|
||||
ElMessage.success('审核拒绝成功');
|
||||
getList();
|
||||
} catch (error) {
|
||||
if (error !== 'cancel') {
|
||||
console.error('审核拒绝失败', error);
|
||||
ElMessage.error('审核拒绝失败');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/** 记录咨询结果操作 */
|
||||
const handleRecord = async (row: XljkConsultationAppointmentVO) => {
|
||||
try {
|
||||
@@ -575,10 +696,15 @@ const handleRecord = async (row: XljkConsultationAppointmentVO) => {
|
||||
appointmentDetailTime: '',
|
||||
reason: '',
|
||||
status: 1, // 默认已完成
|
||||
actualVisitTime: new Date(),
|
||||
communicationDuration: 30,
|
||||
consultationContent: '',
|
||||
counselorSuggestion: ''
|
||||
counselorSuggestion: '',
|
||||
studentName: '',
|
||||
psychologicalHistory: 1, // 默认否(1=否,2=是)
|
||||
psychologicalHistoryDetail: '',
|
||||
basicSituation: '',
|
||||
majorLifeEvents: '',
|
||||
needReferral: 1 // 默认不需要转介(1=不需要,2=需要)
|
||||
};
|
||||
|
||||
Object.assign(recordForm, defaultValues);
|
||||
@@ -594,6 +720,16 @@ const handleRecord = async (row: XljkConsultationAppointmentVO) => {
|
||||
recordForm.appointmentDetailTime = res.data.appointmentDetailTime;
|
||||
recordForm.reason = res.data.reason;
|
||||
recordForm.studentName = res.data.studentName;
|
||||
|
||||
// 如果API返回了已有的咨询记录数据,保持原有值
|
||||
if (res.data.needReferral !== undefined && res.data.needReferral !== null) {
|
||||
recordForm.needReferral = res.data.needReferral;
|
||||
console.log('从API获取的needReferral值:', res.data.needReferral, res.data.needReferral === 2 ? '是(需要转介)' : res.data.needReferral === 1 ? '否(不需要转介)' : '未知状态');
|
||||
}
|
||||
if (res.data.psychologicalHistory !== undefined && res.data.psychologicalHistory !== null) {
|
||||
recordForm.psychologicalHistory = res.data.psychologicalHistory;
|
||||
console.log('从API获取的psychologicalHistory值:', res.data.psychologicalHistory, res.data.psychologicalHistory === 2 ? '是(有病史)' : res.data.psychologicalHistory === 1 ? '否(无病史)' : '未知状态');
|
||||
}
|
||||
} else {
|
||||
console.warn('API响应格式不正确或无数据:', res);
|
||||
// 使用行数据填充
|
||||
@@ -608,6 +744,7 @@ const handleRecord = async (row: XljkConsultationAppointmentVO) => {
|
||||
}
|
||||
|
||||
console.log('填充后的记录表单:', recordForm);
|
||||
console.log('最终needReferral值:', recordForm.needReferral, recordForm.needReferral === 2 ? '是(需要转介)' : recordForm.needReferral === 1 ? '否(不需要转介)' : '未知状态');
|
||||
recordDialogVisible.value = true;
|
||||
} catch (error) {
|
||||
console.error('获取预约详情失败', error);
|
||||
@@ -625,6 +762,8 @@ const submitRecordForm = async () => {
|
||||
try {
|
||||
submitLoading.value = true;
|
||||
|
||||
console.log('提交前的recordForm数据:', recordForm);
|
||||
|
||||
// 构建提交数据
|
||||
const submitData: any = {
|
||||
id: recordForm.id,
|
||||
@@ -633,39 +772,59 @@ const submitRecordForm = async () => {
|
||||
|
||||
// 如果是已到访状态,添加其他字段
|
||||
if (recordForm.status === 1) {
|
||||
// 确保日期格式正确
|
||||
if (recordForm.actualVisitTime) {
|
||||
// 如果是Date对象,转换为ISO字符串格式
|
||||
if (recordForm.actualVisitTime instanceof Date) {
|
||||
const year = recordForm.actualVisitTime.getFullYear();
|
||||
const month = String(recordForm.actualVisitTime.getMonth() + 1).padStart(2, '0');
|
||||
const day = String(recordForm.actualVisitTime.getDate()).padStart(2, '0');
|
||||
const hours = String(recordForm.actualVisitTime.getHours()).padStart(2, '0');
|
||||
const minutes = String(recordForm.actualVisitTime.getMinutes()).padStart(2, '0');
|
||||
const seconds = String(recordForm.actualVisitTime.getSeconds()).padStart(2, '0');
|
||||
|
||||
submitData.actualVisitTime = `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
|
||||
} else {
|
||||
submitData.actualVisitTime = recordForm.actualVisitTime;
|
||||
}
|
||||
console.log('提交的实际到访时间:', submitData.actualVisitTime);
|
||||
}
|
||||
|
||||
submitData.communicationDuration = recordForm.communicationDuration;
|
||||
submitData.consultationContent = recordForm.consultationContent;
|
||||
submitData.counselorSuggestion = recordForm.counselorSuggestion;
|
||||
submitData.counselorSuggestion = recordForm.counselorSuggestion || '';
|
||||
submitData.psychologicalHistory = recordForm.psychologicalHistory;
|
||||
submitData.psychologicalHistoryDetail = recordForm.psychologicalHistoryDetail || '';
|
||||
submitData.basicSituation = recordForm.basicSituation;
|
||||
submitData.majorLifeEvents = recordForm.majorLifeEvents || '';
|
||||
submitData.needReferral = recordForm.needReferral;
|
||||
|
||||
console.log('已到访状态,提交的数据字段:');
|
||||
console.log('- psychologicalHistoryDetail:', submitData.psychologicalHistoryDetail, '类型:', typeof submitData.psychologicalHistoryDetail, '长度:', submitData.psychologicalHistoryDetail?.length);
|
||||
console.log('- basicSituation:', submitData.basicSituation, '类型:', typeof submitData.basicSituation, '长度:', submitData.basicSituation?.length);
|
||||
console.log('- majorLifeEvents:', submitData.majorLifeEvents, '类型:', typeof submitData.majorLifeEvents, '长度:', submitData.majorLifeEvents?.length);
|
||||
console.log('- needReferral:', submitData.needReferral, '类型:', typeof submitData.needReferral);
|
||||
console.log('- psychologicalHistory:', submitData.psychologicalHistory, '类型:', typeof submitData.psychologicalHistory);
|
||||
} else {
|
||||
// 未到访状态,设置默认值
|
||||
submitData.actualVisitTime = null;
|
||||
submitData.communicationDuration = 0;
|
||||
submitData.consultationContent = '';
|
||||
submitData.counselorSuggestion = '';
|
||||
submitData.psychologicalHistory = null;
|
||||
submitData.psychologicalHistoryDetail = '';
|
||||
submitData.basicSituation = '';
|
||||
submitData.majorLifeEvents = '';
|
||||
submitData.needReferral = null;
|
||||
}
|
||||
|
||||
await recordXljkConsultation(recordForm.id as number, submitData);
|
||||
console.log('最终提交的数据:', submitData);
|
||||
|
||||
const response = await recordXljkConsultation(recordForm.id as number, submitData);
|
||||
console.log('API响应:', response);
|
||||
|
||||
ElMessage.success('记录成功');
|
||||
recordDialogVisible.value = false;
|
||||
getList();
|
||||
|
||||
// 重新查询列表,并验证数据是否保存成功
|
||||
await getList();
|
||||
|
||||
// 验证数据保存情况
|
||||
try {
|
||||
const verifyRes = await getXljkAppointment(recordForm.id as number);
|
||||
console.log('验证保存结果,获取的数据:', verifyRes.data);
|
||||
if (verifyRes.code === 200 && verifyRes.data) {
|
||||
console.log('验证字段保存情况:');
|
||||
console.log('- psychologicalHistoryDetail 保存值:', verifyRes.data.psychologicalHistoryDetail);
|
||||
console.log('- basicSituation 保存值:', verifyRes.data.basicSituation);
|
||||
console.log('- majorLifeEvents 保存值:', verifyRes.data.majorLifeEvents);
|
||||
console.log('- needReferral 保存值:', verifyRes.data.needReferral);
|
||||
console.log('- psychologicalHistory 保存值:', verifyRes.data.psychologicalHistory);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('验证保存结果失败:', error);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('提交记录失败', error);
|
||||
ElMessage.error('提交记录失败');
|
||||
@@ -710,8 +869,8 @@ const handleDelete = async (row?: XljkConsultationAppointmentVO) => {
|
||||
|
||||
/** 导出按钮操作 */
|
||||
const handleExport = async () => {
|
||||
// 构建查询参数,处理日期范围和params对象
|
||||
const exportParams = { ...queryParams };
|
||||
// 构建查询参数,处理日期范围
|
||||
const exportParams: any = { ...queryParams };
|
||||
delete exportParams.params; // 移除params对象
|
||||
|
||||
// 将日期范围添加到参数中
|
||||
@@ -771,7 +930,7 @@ const handleExport = async () => {
|
||||
a.click();
|
||||
window.URL.revokeObjectURL(url);
|
||||
document.body.removeChild(a);
|
||||
} catch (error) {
|
||||
} catch (error: any) {
|
||||
console.error('导出失败:', error);
|
||||
ElMessage.error(`导出失败: ${error.message}`);
|
||||
}
|
||||
@@ -788,7 +947,7 @@ const handleCollegeChange = async (collegeName: string) => {
|
||||
if (collegeName) {
|
||||
// 获取该院系下的专业
|
||||
try {
|
||||
const res = await getMajorOptions(collegeName);
|
||||
const res = await getXljkConsultationMajorOptions(collegeName);
|
||||
majorOptions.value = res.data || [];
|
||||
} catch (error) {
|
||||
console.error('获取专业选项失败', error);
|
||||
@@ -808,7 +967,7 @@ const handleMajorChange = async (majorName: string) => {
|
||||
if (majorName && queryParams.collegeName) {
|
||||
// 获取专业下的班级
|
||||
try {
|
||||
const res = await getClassOptions(queryParams.collegeName, majorName);
|
||||
const res = await getXljkConsultationClassOptions(queryParams.collegeName, majorName);
|
||||
classOptions.value = res.data || [];
|
||||
} catch (error) {
|
||||
console.error('获取班级选项失败', error);
|
||||
@@ -816,7 +975,7 @@ const handleMajorChange = async (majorName: string) => {
|
||||
} else if (queryParams.collegeName) {
|
||||
// 如果清空专业但有院系,则获取院系下所有班级
|
||||
try {
|
||||
const res = await getClassOptions(queryParams.collegeName);
|
||||
const res = await getXljkConsultationClassOptions(queryParams.collegeName);
|
||||
classOptions.value = res.data || [];
|
||||
} catch (error) {
|
||||
console.error('获取班级选项失败', error);
|
||||
@@ -827,6 +986,18 @@ const handleMajorChange = async (majorName: string) => {
|
||||
}
|
||||
};
|
||||
|
||||
/** 转介选择变化时的调试日志 */
|
||||
const handleReferralChange = (value: number) => {
|
||||
console.log('转介字段值变化:', value, value === 2 ? '是(需要转介)' : value === 1 ? '否(不需要转介)' : '未知状态');
|
||||
console.log('当前recordForm.needReferral:', recordForm.needReferral);
|
||||
};
|
||||
|
||||
/** 心理病史选择变化时的调试日志 */
|
||||
const handlePsychologicalHistoryChange = (value: number) => {
|
||||
console.log('心理病史字段值变化:', value, value === 2 ? '是(有心理病史)' : value === 1 ? '否(无心理病史)' : '未知状态');
|
||||
console.log('当前recordForm.psychologicalHistory:', recordForm.psychologicalHistory);
|
||||
};
|
||||
|
||||
// 初始化
|
||||
onMounted(() => {
|
||||
console.log('初始化appointment_status字典数据:', appointment_status.value);
|
||||
|
||||
@@ -33,56 +33,75 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 表格内容:显示全天和时间段 -->
|
||||
<!-- 表格内容:显示时间段 -->
|
||||
<div class="week-body">
|
||||
<div class="time-row">
|
||||
<div class="time-label-cell">
|
||||
<span>全天</span>
|
||||
</div>
|
||||
<div v-for="day in weekDays" :key="day.date" class="day-cell">
|
||||
<!-- 这里可以放全天事件,目前为空 -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 时间段行 -->
|
||||
<div class="time-slots-container">
|
||||
<!-- 上午时段 -->
|
||||
<div class="time-row">
|
||||
<div class="time-label-cell">
|
||||
<span>上午</span>
|
||||
</div>
|
||||
<div v-for="day in weekDays" :key="day.date" class="day-cell">
|
||||
<div
|
||||
v-if="getMorningSlot(day.date)"
|
||||
class="time-slot-card morning"
|
||||
:class="{
|
||||
'fully-booked': getMorningSlot(day.date).allBooked || getMorningSlot(day.date).isExpired
|
||||
}"
|
||||
@click="handleSlotClick(getMorningSlot(day.date))"
|
||||
>
|
||||
{{ getMorningSlot(day.date).startTime }}-{{ getMorningSlot(day.date).endTime }}
|
||||
<!-- 动态显示所有排班时段 -->
|
||||
<template v-for="timeSlotType in getUniqueTimeSlots()" :key="timeSlotType">
|
||||
<div class="time-row">
|
||||
<div class="time-label-cell">
|
||||
<span>{{ getTimeSlotLabel(timeSlotType) }}</span>
|
||||
</div>
|
||||
<div v-for="day in weekDays" :key="day.date" class="day-cell">
|
||||
<div
|
||||
v-for="schedule in getSchedulesForDateAndTimeSlot(day.date, timeSlotType)"
|
||||
:key="`${schedule.scheduleId}-${schedule.startTime}`"
|
||||
class="time-slot-card"
|
||||
:class="{
|
||||
'morning': schedule.timeSlot === '0',
|
||||
'noon': schedule.timeSlot === '1',
|
||||
'afternoon': schedule.timeSlot === '2',
|
||||
'fully-booked': schedule.isFullyBooked || schedule.isExpired
|
||||
}"
|
||||
@click="handleScheduleClickEvent(schedule)"
|
||||
>
|
||||
{{ schedule.startTime }}-{{ schedule.endTime }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 下午时段 -->
|
||||
<div class="time-row">
|
||||
<div class="time-label-cell">
|
||||
<span>下午</span>
|
||||
</div>
|
||||
<div v-for="day in weekDays" :key="day.date" class="day-cell">
|
||||
<div
|
||||
v-if="getAfternoonSlot(day.date)"
|
||||
class="time-slot-card afternoon"
|
||||
:class="{
|
||||
'fully-booked': getAfternoonSlot(day.date).allBooked || getAfternoonSlot(day.date).isExpired
|
||||
}"
|
||||
@click="handleSlotClick(getAfternoonSlot(day.date))"
|
||||
>
|
||||
{{ getAfternoonSlot(day.date).startTime }}-{{ getAfternoonSlot(day.date).endTime }}
|
||||
<!-- 兼容性:如果没有新排班数据,继续显示原有的中午/下午时段 -->
|
||||
<template v-if="getUniqueTimeSlots().length === 0">
|
||||
<!-- 中午时段 -->
|
||||
<div class="time-row">
|
||||
<div class="time-label-cell">
|
||||
<span>中午</span>
|
||||
</div>
|
||||
<div v-for="day in weekDays" :key="day.date" class="day-cell">
|
||||
<div
|
||||
v-if="getMorningSlot(day.date)"
|
||||
class="time-slot-card morning"
|
||||
:class="{
|
||||
'fully-booked': getMorningSlot(day.date).allBooked || getMorningSlot(day.date).isExpired
|
||||
}"
|
||||
@click="handleSlotClick(getMorningSlot(day.date))"
|
||||
>
|
||||
{{ getMorningSlot(day.date).startTime }}-{{ getMorningSlot(day.date).endTime }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 下午时段 -->
|
||||
<div class="time-row">
|
||||
<div class="time-label-cell">
|
||||
<span>下午</span>
|
||||
</div>
|
||||
<div v-for="day in weekDays" :key="day.date" class="day-cell">
|
||||
<div
|
||||
v-if="getAfternoonSlot(day.date)"
|
||||
class="time-slot-card afternoon"
|
||||
:class="{
|
||||
'fully-booked': getAfternoonSlot(day.date).allBooked || getAfternoonSlot(day.date).isExpired
|
||||
}"
|
||||
@click="handleSlotClick(getAfternoonSlot(day.date))"
|
||||
>
|
||||
{{ getAfternoonSlot(day.date).startTime }}-{{ getAfternoonSlot(day.date).endTime }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -111,25 +130,44 @@
|
||||
>
|
||||
<div class="month-day-number">{{ day.dayNumber }}</div>
|
||||
|
||||
<!-- 上午时段 -->
|
||||
<!-- 显示该日期的所有排班 -->
|
||||
<div
|
||||
v-if="getMorningSlot(day.date)"
|
||||
class="month-slot morning"
|
||||
:class="{ 'fully-booked': getMorningSlot(day.date).allBooked || getMorningSlot(day.date).isExpired }"
|
||||
@click.stop="handleSlotClick(getMorningSlot(day.date))"
|
||||
v-for="schedule in getSchedulesForDate(day.date)"
|
||||
:key="`${schedule.scheduleId}-${schedule.startTime}`"
|
||||
class="month-slot"
|
||||
:class="{
|
||||
'morning': schedule.timeSlot === '0',
|
||||
'noon': schedule.timeSlot === '1',
|
||||
'afternoon': schedule.timeSlot === '2',
|
||||
'fully-booked': schedule.isFullyBooked || schedule.isExpired
|
||||
}"
|
||||
@click.stop="handleScheduleClickEvent(schedule)"
|
||||
>
|
||||
上午 {{ getMorningSlot(day.date).startTime }}-{{ getMorningSlot(day.date).endTime }}
|
||||
{{ schedule.timeDisplay }}
|
||||
</div>
|
||||
|
||||
<!-- 下午时段 -->
|
||||
<div
|
||||
v-if="getAfternoonSlot(day.date)"
|
||||
class="month-slot afternoon"
|
||||
:class="{ 'fully-booked': getAfternoonSlot(day.date).allBooked || getAfternoonSlot(day.date).isExpired }"
|
||||
@click.stop="handleSlotClick(getAfternoonSlot(day.date))"
|
||||
>
|
||||
下午 {{ getAfternoonSlot(day.date).startTime }}-{{ getAfternoonSlot(day.date).endTime }}
|
||||
</div>
|
||||
<!-- 兼容性:如果没有新排班数据,继续显示原有的中午/下午时段 -->
|
||||
<template v-if="getSchedulesForDate(day.date).length === 0">
|
||||
<!-- 中午时段 -->
|
||||
<div
|
||||
v-if="getMorningSlot(day.date)"
|
||||
class="month-slot morning"
|
||||
:class="{ 'fully-booked': getMorningSlot(day.date).allBooked || getMorningSlot(day.date).isExpired }"
|
||||
@click.stop="handleSlotClick(getMorningSlot(day.date))"
|
||||
>
|
||||
中午 {{ getMorningSlot(day.date).startTime }}-{{ getMorningSlot(day.date).endTime }}
|
||||
</div>
|
||||
|
||||
<!-- 下午时段 -->
|
||||
<div
|
||||
v-if="getAfternoonSlot(day.date)"
|
||||
class="month-slot afternoon"
|
||||
:class="{ 'fully-booked': getAfternoonSlot(day.date).allBooked || getAfternoonSlot(day.date).isExpired }"
|
||||
@click.stop="handleSlotClick(getAfternoonSlot(day.date))"
|
||||
>
|
||||
下午 {{ getAfternoonSlot(day.date).startTime }}-{{ getAfternoonSlot(day.date).endTime }}
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -142,7 +180,7 @@ import { ref, computed, onMounted, watch } from 'vue';
|
||||
import { parseTime } from '@/utils/ruoyi';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { ArrowLeft, ArrowRight } from '@element-plus/icons-vue';
|
||||
import { AvailableTimeSlot } from '@/api/xljk/consultation/types';
|
||||
import { AvailableTimeSlot, ScheduleWithStatus } from '@/api/xljk/consultation/types';
|
||||
|
||||
// 定义组件的Props
|
||||
const props = defineProps({
|
||||
@@ -150,6 +188,10 @@ const props = defineProps({
|
||||
type: Array as () => AvailableTimeSlot[],
|
||||
default: () => []
|
||||
},
|
||||
calendarSchedules: {
|
||||
type: Array as () => ScheduleWithStatus[],
|
||||
default: () => []
|
||||
},
|
||||
defaultView: {
|
||||
type: String,
|
||||
default: 'week',
|
||||
@@ -158,7 +200,7 @@ const props = defineProps({
|
||||
});
|
||||
|
||||
// 定义组件的事件
|
||||
const emit = defineEmits(['dateRangeChange', 'slotClick', 'viewChange']);
|
||||
const emit = defineEmits(['dateRangeChange', 'slotClick', 'scheduleClick', 'viewChange']);
|
||||
|
||||
// 当前日期和周期的开始日期
|
||||
const today = ref(new Date());
|
||||
@@ -382,7 +424,7 @@ const monthDays = computed(() => {
|
||||
// 如果是最后一行,添加额外日志
|
||||
if (week === 5) {
|
||||
console.log(`日期 ${formattedDate} (${date.toLocaleDateString()}) 数据状态:`, {
|
||||
上午时段: morningSlot ? '有数据' : '无数据',
|
||||
中午时段: morningSlot ? '有数据' : '无数据',
|
||||
下午时段: afternoonSlot ? '有数据' : '无数据',
|
||||
isOtherMonth
|
||||
});
|
||||
@@ -412,7 +454,7 @@ function formatDate(date: Date) {
|
||||
return parseTime(date, '{y}-{m}-{d}');
|
||||
}
|
||||
|
||||
// 获取指定日期的上午时段
|
||||
// 获取指定日期的中午时段
|
||||
function getMorningSlot(dateStr: string) {
|
||||
return props.availableSlots.find((slot) => slot.date === dateStr && slot.timeSlot === '0');
|
||||
}
|
||||
@@ -422,6 +464,76 @@ function getAfternoonSlot(dateStr: string) {
|
||||
return props.availableSlots.find((slot) => slot.date === dateStr && slot.timeSlot === '1');
|
||||
}
|
||||
|
||||
// 获取指定日期的所有排班
|
||||
function getSchedulesForDate(dateStr: string) {
|
||||
return props.calendarSchedules.filter(schedule => schedule.date === dateStr)
|
||||
.sort((a, b) => {
|
||||
// 先按时段类型排序
|
||||
const timeSlotCompare = a.timeSlot.localeCompare(b.timeSlot);
|
||||
if (timeSlotCompare !== 0) return timeSlotCompare;
|
||||
// 再按开始时间排序
|
||||
return a.startTime.localeCompare(b.startTime);
|
||||
});
|
||||
}
|
||||
|
||||
// 处理排班点击
|
||||
function handleScheduleClickEvent(schedule: ScheduleWithStatus) {
|
||||
if (!schedule) return;
|
||||
|
||||
// 如果该时段已过期,则不允许预约
|
||||
if (schedule.isExpired) {
|
||||
ElMessage.warning('该时段已过期,请选择其他时段');
|
||||
return;
|
||||
}
|
||||
|
||||
// 如果该时段全部约满,则不允许预约
|
||||
if (schedule.isFullyBooked) {
|
||||
ElMessage.warning('该时段已全部约满,请选择其他时段');
|
||||
return;
|
||||
}
|
||||
|
||||
emit('scheduleClick', schedule);
|
||||
}
|
||||
|
||||
// 获取所有唯一的时段类型
|
||||
function getUniqueTimeSlots() {
|
||||
const timeSlots = new Set<string>();
|
||||
props.calendarSchedules.forEach(schedule => {
|
||||
timeSlots.add(schedule.timeSlot);
|
||||
});
|
||||
return Array.from(timeSlots).sort();
|
||||
}
|
||||
|
||||
// 获取时段类型标签
|
||||
function getTimeSlotLabel(timeSlot: string) {
|
||||
switch (timeSlot) {
|
||||
case '0': return '中午';
|
||||
case '1': return '下午';
|
||||
case '2': return '下午';
|
||||
default: return '时段' + timeSlot;
|
||||
}
|
||||
}
|
||||
|
||||
// 获取指定日期和时段类型的排班
|
||||
function getSchedulesForDateAndTimeSlot(dateStr: string, timeSlot: string) {
|
||||
const result = props.calendarSchedules
|
||||
.filter(schedule => schedule.date === dateStr && schedule.timeSlot === timeSlot)
|
||||
.sort((a, b) => a.startTime.localeCompare(b.startTime));
|
||||
|
||||
// 特别调试9月18日time_slot=0的情况
|
||||
if (dateStr === '2025-09-18' && timeSlot === '0') {
|
||||
console.log('🔍 WeekView调试 - getSchedulesForDateAndTimeSlot:', {
|
||||
dateStr,
|
||||
timeSlot,
|
||||
totalSchedules: props.calendarSchedules.length,
|
||||
filteredResult: result,
|
||||
resultCount: result.length
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// 监听视图变化
|
||||
watch(
|
||||
() => props.defaultView,
|
||||
@@ -551,12 +663,18 @@ watch(
|
||||
background-color: #67c23a;
|
||||
}
|
||||
|
||||
.time-slot-card.noon {
|
||||
background-color: #e6a23c;
|
||||
}
|
||||
|
||||
.time-slot-card.fully-booked {
|
||||
background-color: #c0c0c0;
|
||||
cursor: not-allowed;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* 月视图样式 */
|
||||
.month-grid {
|
||||
flex: 1;
|
||||
@@ -645,9 +763,15 @@ watch(
|
||||
background-color: #67c23a;
|
||||
}
|
||||
|
||||
.month-slot.noon {
|
||||
background-color: #e6a23c;
|
||||
}
|
||||
|
||||
.month-slot.fully-booked {
|
||||
background-color: #c0c0c0;
|
||||
cursor: not-allowed;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
|
||||
</style>
|
||||
@@ -19,9 +19,11 @@
|
||||
<!-- 使用WeekView组件,支持月视图和周视图切换 -->
|
||||
<WeekView
|
||||
:available-slots="availableSlots"
|
||||
:calendar-schedules="calendarSchedules"
|
||||
:default-view="calendarView"
|
||||
@date-range-change="handleDateRangeChange"
|
||||
@slot-click="handleSlotClick"
|
||||
@schedule-click="handleScheduleClick"
|
||||
@view-change="handleViewChange"
|
||||
/>
|
||||
</div>
|
||||
@@ -35,27 +37,31 @@
|
||||
<el-form-item label="咨询师" prop="counselorId">
|
||||
<el-input v-model="counselorDisplayName" disabled />
|
||||
</el-form-item>
|
||||
<el-form-item label="办公室" prop="office">
|
||||
<el-input v-model="selectedOffice" disabled />
|
||||
</el-form-item>
|
||||
<el-form-item label="预约日期" prop="appointmentDate">
|
||||
<el-input v-model="formattedDate" disabled />
|
||||
</el-form-item>
|
||||
<el-form-item label="姓名" prop="studentName">
|
||||
<el-input v-model="studentName" disabled />
|
||||
</el-form-item>
|
||||
<el-form-item label="预约时段" prop="appointmentDetailTime">
|
||||
<el-select v-model="appointmentForm.appointmentDetailTime" placeholder="请选择具体预约时段" style="width: 100%">
|
||||
<el-input v-model="appointmentForm.appointmentDetailTime" disabled />
|
||||
</el-form-item>
|
||||
<el-form-item label="咨询主题" prop="consultTheme">
|
||||
<el-select v-model="appointmentForm.consultTheme" placeholder="请选择咨询主题*" style="width: 100%">
|
||||
<el-option
|
||||
v-for="slot in availableDetailSlots"
|
||||
:key="slot.timeSlot"
|
||||
:label="slot.timeSlot"
|
||||
:value="slot.timeSlot"
|
||||
:disabled="slot.isBooked || slot.isExpired"
|
||||
:style="getTimeSlotStyle(slot)"
|
||||
v-for="theme in xljk_consult_theme"
|
||||
:key="theme.value"
|
||||
:label="theme.label"
|
||||
:value="theme.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="学号" prop="studentNo" v-if="!hasStudentInfo">
|
||||
<el-input v-model="studentNo" placeholder="请输入学号" @blur="handleStudentNoBlur" />
|
||||
</el-form-item>
|
||||
<el-form-item label="姓名" prop="studentName">
|
||||
<el-input v-model="studentName" disabled />
|
||||
</el-form-item>
|
||||
<el-form-item label="咨询原因" prop="reason">
|
||||
<el-input v-model="appointmentForm.reason" type="textarea" placeholder="请简要描述咨询原因" :rows="4" />
|
||||
</el-form-item>
|
||||
@@ -69,13 +75,14 @@
|
||||
</el-dialog>
|
||||
|
||||
<!-- 我的预约列表弹窗 -->
|
||||
<el-dialog v-model="myAppointmentsVisible" title="我的预约记录" width="800px" append-to-body destroy-on-close>
|
||||
<el-table :data="myAppointmentsList" border style="width: 100%">
|
||||
<el-table-column label="预约日期" prop="appointmentDate" width="120" />
|
||||
<el-table-column label="预约时段" prop="appointmentDetailTime" width="120" />
|
||||
<el-table-column label="咨询师" prop="counselorName" width="100" />
|
||||
<el-table-column label="咨询原因" prop="reason" show-overflow-tooltip />
|
||||
<el-table-column label="状态" prop="status" width="100">
|
||||
<el-dialog v-model="myAppointmentsVisible" title="我的预约记录" width="1200px" append-to-body destroy-on-close>
|
||||
<el-table :data="myAppointmentsList" border style="width: 100%" header-align="center">
|
||||
<el-table-column label="预约日期" prop="appointmentDate" width="120" align="center" />
|
||||
<el-table-column label="预约时段" prop="appointmentDetailTime" width="140" align="center" />
|
||||
<el-table-column label="咨询师" prop="counselorName" width="100" align="center" />
|
||||
<el-table-column label="办公室" prop="office" width="200" show-overflow-tooltip header-align="center" />
|
||||
<el-table-column label="咨询原因" prop="reason" min-width="200" show-overflow-tooltip header-align="center" />
|
||||
<el-table-column label="状态" prop="status" width="100" align="center">
|
||||
<template #default="scope">
|
||||
<!-- 添加调试信息 -->
|
||||
<div style="display: none">状态值: {{ scope.row.status }} 类型: {{ typeof scope.row.status }} 字典数据: {{ appointment_status }}</div>
|
||||
@@ -85,6 +92,8 @@
|
||||
<el-tag v-else-if="scope.row.status === 1 || scope.row.status === '1'" type="success">已到访</el-tag>
|
||||
<el-tag v-else-if="scope.row.status === 2 || scope.row.status === '2'" type="danger">已取消</el-tag>
|
||||
<el-tag v-else-if="scope.row.status === 3 || scope.row.status === '3'" type="warning">未到访</el-tag>
|
||||
<el-tag v-else-if="scope.row.status === 4 || scope.row.status === '4'" type="info">待审核</el-tag>
|
||||
<el-tag v-else-if="scope.row.status === 5 || scope.row.status === '5'" type="danger">已驳回</el-tag>
|
||||
<el-tag v-else type="info">未知({{ scope.row.status }})</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
@@ -96,7 +105,7 @@
|
||||
</div>
|
||||
|
||||
<!-- 使用更直接的方式显示取消按钮 -->
|
||||
<template v-if="scope.row.status === 0 || scope.row.status === '0'">
|
||||
<template v-if="scope.row.status === 0 || scope.row.status === '0' || scope.row.status === 4 || scope.row.status === '4'">
|
||||
<el-button link type="danger" size="small" @click="cancelAppointment(scope.row)"> 取消预约 </el-button>
|
||||
</template>
|
||||
<span v-else>--</span>
|
||||
@@ -117,6 +126,7 @@
|
||||
import { ref, reactive, onMounted, computed, watch } from 'vue';
|
||||
import {
|
||||
getXljkAvailableTimeSlots,
|
||||
getXljkCalendarSchedules,
|
||||
submitXljkAppointment,
|
||||
getXljkMyAppointments,
|
||||
updateXljkAppointment,
|
||||
@@ -124,7 +134,7 @@ import {
|
||||
getXljkCounselorOptions,
|
||||
getXljkAvailableDetailSlots
|
||||
} from '@/api/xljk/consultation';
|
||||
import { AvailableTimeSlot, XljkConsultationAppointmentForm, CounselorOption, TimeSlotDetail } from '@/api/xljk/consultation/types';
|
||||
import { AvailableTimeSlot, XljkConsultationAppointmentForm, CounselorOption, TimeSlotDetail, ScheduleWithStatus } from '@/api/xljk/consultation/types';
|
||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||
import { parseTime } from '@/utils/ruoyi';
|
||||
import { useUserStore } from '@/store/modules/user';
|
||||
@@ -134,12 +144,17 @@ const { proxy } = getCurrentInstance() as any;
|
||||
// 不再使用后端字典数据
|
||||
// const { appointment_status } = toRefs(proxy?.useDict('appointment_status'));
|
||||
|
||||
// 获取咨询主题字典数据
|
||||
const { xljk_consult_theme } = toRefs<any>(proxy?.useDict('xljk_consult_theme'));
|
||||
|
||||
// 本地定义状态字典(与预约记录管理页面保持一致)
|
||||
const appointment_status = ref([
|
||||
{ label: '待咨询', value: 0, listClass: 'primary' },
|
||||
{ label: '已到访', value: 1, listClass: 'success' },
|
||||
{ label: '已取消', value: 2, listClass: 'danger' },
|
||||
{ label: '未到访', value: 3, listClass: 'warning' }
|
||||
{ label: '未到访', value: 3, listClass: 'warning' },
|
||||
{ label: '待审核', value: 4, listClass: 'info' },
|
||||
{ label: '已驳回', value: 5, listClass: 'danger' }
|
||||
]);
|
||||
|
||||
// 用户信息
|
||||
@@ -150,6 +165,7 @@ const hasStudentInfo = computed(() => Boolean(studentId.value));
|
||||
|
||||
// 日历相关
|
||||
const availableSlots = ref<AvailableTimeSlot[]>([]);
|
||||
const calendarSchedules = ref<ScheduleWithStatus[]>([]);
|
||||
const calendarView = ref('month'); // 默认使用月视图
|
||||
|
||||
// 预约表单相关
|
||||
@@ -161,6 +177,7 @@ const studentNo = ref('');
|
||||
const studentName = ref('');
|
||||
const studentId = ref<string | number | null>(null);
|
||||
const availableDetailSlots = ref<TimeSlotDetail[]>([]);
|
||||
const selectedOffice = ref(''); // 添加办公室字段
|
||||
|
||||
const appointmentForm = reactive<XljkConsultationAppointmentForm>({
|
||||
counselorId: undefined,
|
||||
@@ -168,6 +185,7 @@ const appointmentForm = reactive<XljkConsultationAppointmentForm>({
|
||||
appointmentDate: '',
|
||||
appointmentTime: '',
|
||||
appointmentDetailTime: '',
|
||||
consultTheme: '', // 咨询主题
|
||||
reason: '',
|
||||
status: 0 // 默认待咨询状态,使用数值类型
|
||||
});
|
||||
@@ -175,7 +193,7 @@ const appointmentForm = reactive<XljkConsultationAppointmentForm>({
|
||||
const appointmentRules = {
|
||||
reason: [{ required: true, message: '请输入咨询原因', trigger: 'blur' }],
|
||||
studentNo: [{ required: true, message: '请输入学号', trigger: 'blur' }],
|
||||
appointmentDetailTime: [{ required: true, message: '请选择具体预约时段', trigger: 'change' }]
|
||||
consultTheme: [{ required: true, message: '请选择咨询主题', trigger: 'change' }]
|
||||
};
|
||||
|
||||
// 我的预约相关
|
||||
@@ -196,7 +214,13 @@ const formattedTimeSlot = computed(() => {
|
||||
|
||||
// 计算属性:咨询师名称
|
||||
const counselorDisplayName = computed(() => {
|
||||
return selectedSlot.value?.counselorName || '正在加载...';
|
||||
if (selectedSlot.value?.counselorName) {
|
||||
return selectedSlot.value.counselorName;
|
||||
}
|
||||
if (selectedSlot.value?.counselorId) {
|
||||
return '正在加载...';
|
||||
}
|
||||
return '请选择咨询师';
|
||||
});
|
||||
|
||||
// 处理日期范围变化
|
||||
@@ -230,27 +254,52 @@ function handleViewChange(view: string) {
|
||||
// 加载可用预约时段
|
||||
async function loadAvailableSlots(startDate: string, endDate: string) {
|
||||
try {
|
||||
// 加载新的排班数据
|
||||
const scheduleRes = await getXljkCalendarSchedules(startDate, endDate);
|
||||
|
||||
if (scheduleRes.code === 200) {
|
||||
calendarSchedules.value = scheduleRes.data || [];
|
||||
}
|
||||
|
||||
// 为了兼容现有逻辑,同时保留原有的时段数据加载
|
||||
const res = await getXljkAvailableTimeSlots(startDate, endDate);
|
||||
if (res.code === 200) {
|
||||
availableSlots.value = res.data || [];
|
||||
|
||||
// 如果有可用时段但没有咨询师姓名,尝试加载咨询师信息
|
||||
if (availableSlots.value.length > 0 && !availableSlots.value[0].counselorName) {
|
||||
try {
|
||||
const counselorRes = await getXljkCounselorOptions();
|
||||
if (counselorRes.code === 200 && counselorRes.data) {
|
||||
const counselors = counselorRes.data;
|
||||
// 为每个时段添加咨询师姓名
|
||||
availableSlots.value = availableSlots.value.map((slot) => {
|
||||
const counselor = counselors.find((c: CounselorOption) => c.id === slot.counselorId);
|
||||
if (counselor) {
|
||||
return { ...slot, counselorName: counselor.name };
|
||||
}
|
||||
return slot;
|
||||
});
|
||||
// 如果有可用时段,检查并补充咨询师姓名
|
||||
if (availableSlots.value.length > 0) {
|
||||
// 检查是否有时段缺少咨询师姓名或办公室信息
|
||||
const hasMissingCounselorInfo = availableSlots.value.some(slot =>
|
||||
(!slot.counselorName || !slot.office) && slot.counselorId
|
||||
);
|
||||
|
||||
if (hasMissingCounselorInfo) {
|
||||
try {
|
||||
console.log('检测到缺少咨询师信息,开始获取咨询师详细信息...');
|
||||
const counselorRes = await getXljkCounselorOptions();
|
||||
if (counselorRes.code === 200 && counselorRes.data) {
|
||||
const counselors = counselorRes.data;
|
||||
console.log('获取到咨询师列表:', counselors);
|
||||
// 为每个时段添加咨询师姓名和办公室信息
|
||||
availableSlots.value = availableSlots.value.map((slot) => {
|
||||
if ((!slot.counselorName || !slot.office) && slot.counselorId) {
|
||||
const counselor = counselors.find((c: CounselorOption) => c.id === slot.counselorId);
|
||||
if (counselor) {
|
||||
const updatedSlot = {
|
||||
...slot,
|
||||
counselorName: slot.counselorName || counselor.name,
|
||||
office: slot.office || counselor.office
|
||||
};
|
||||
console.log('更新时段信息:', updatedSlot);
|
||||
return updatedSlot;
|
||||
}
|
||||
}
|
||||
return slot;
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取咨询师信息失败', error);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取咨询师信息失败', error);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -260,19 +309,103 @@ async function loadAvailableSlots(startDate: string, endDate: string) {
|
||||
}
|
||||
}
|
||||
|
||||
// 处理新的排班点击
|
||||
async function handleScheduleClick(schedule: ScheduleWithStatus) {
|
||||
console.log('点击排班:', schedule);
|
||||
|
||||
// 如果该时段已过期,则不允许预约
|
||||
if (schedule.isExpired) {
|
||||
ElMessage.warning('该时段已过期,请选择其他时段');
|
||||
return;
|
||||
}
|
||||
|
||||
// 如果该时段全部约满,则不允许预约
|
||||
if (schedule.isFullyBooked) {
|
||||
ElMessage.warning('该时段已全部约满,请选择其他时段');
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取咨询师名称和办公室信息
|
||||
let counselorName = schedule.counselorName || '';
|
||||
let office = schedule.office || '';
|
||||
|
||||
// 如果缺少咨询师姓名或办公室信息,则去获取完整的咨询师信息
|
||||
if ((!counselorName || !office) && schedule.counselorId) {
|
||||
try {
|
||||
console.log('获取咨询师详细信息,counselorId:', schedule.counselorId);
|
||||
const res = await getXljkCounselorOptions();
|
||||
if (res.code === 200 && res.data) {
|
||||
const counselor = res.data.find((item: CounselorOption) => item.id === schedule.counselorId);
|
||||
if (counselor) {
|
||||
counselorName = counselorName || counselor.name;
|
||||
office = office || counselor.office || '';
|
||||
console.log('获取到咨询师信息:', { name: counselor.name, office: counselor.office });
|
||||
} else {
|
||||
console.log('未找到对应的咨询师信息, counselorId:', schedule.counselorId);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取咨询师信息失败', error);
|
||||
}
|
||||
}
|
||||
|
||||
console.log('最终的办公室信息:', office);
|
||||
|
||||
// 填充预约表单 - 直接使用排班数据,不需要再选择详细时段
|
||||
appointmentForm.counselorId = schedule.counselorId;
|
||||
appointmentForm.scheduleId = schedule.scheduleId;
|
||||
appointmentForm.appointmentDate = schedule.date;
|
||||
appointmentForm.appointmentTime = `${schedule.startTime}-${schedule.endTime}`;
|
||||
appointmentForm.appointmentDetailTime = `${schedule.startTime}-${schedule.endTime}`; // 直接使用完整时段
|
||||
appointmentForm.consultTheme = ''; // 清空咨询主题,等待用户选择
|
||||
appointmentForm.reason = ''; // 清空咨询原因,避免带出上次的内容
|
||||
|
||||
// 设置选中的时段信息以便显示
|
||||
selectedSlot.value = {
|
||||
scheduleId: schedule.scheduleId,
|
||||
counselorId: schedule.counselorId,
|
||||
counselorName: counselorName,
|
||||
office: office,
|
||||
date: schedule.date,
|
||||
timeSlot: schedule.timeSlot,
|
||||
startTime: schedule.startTime,
|
||||
endTime: schedule.endTime,
|
||||
allBooked: schedule.isFullyBooked,
|
||||
isExpired: schedule.isExpired
|
||||
} as AvailableTimeSlot;
|
||||
|
||||
// 设置办公室信息
|
||||
selectedOffice.value = office;
|
||||
|
||||
// 如果已登录且有用户ID,初始化学生信息
|
||||
if (userId.value) {
|
||||
// 初始设置为当前用户ID
|
||||
studentId.value = userId.value;
|
||||
studentName.value = nickname.value || '';
|
||||
studentNo.value = nickname.value || '';
|
||||
}
|
||||
|
||||
appointmentFormVisible.value = true;
|
||||
}
|
||||
|
||||
// 处理时段点击
|
||||
async function handleSlotClick(slot: AvailableTimeSlot) {
|
||||
selectedSlot.value = slot;
|
||||
|
||||
// 如果没有咨询师姓名,尝试获取咨询师信息
|
||||
if (!slot.counselorName && slot.counselorId) {
|
||||
// 如果没有咨询师姓名或办公室信息,尝试获取咨询师信息
|
||||
if ((!slot.counselorName || !slot.office) && slot.counselorId) {
|
||||
try {
|
||||
console.log('获取咨询师详细信息,counselorId:', slot.counselorId);
|
||||
const res = await getXljkCounselorOptions();
|
||||
if (res.code === 200 && res.data) {
|
||||
const counselor = res.data.find((item: CounselorOption) => item.id === slot.counselorId);
|
||||
if (counselor) {
|
||||
slot.counselorName = counselor.name;
|
||||
slot.counselorName = slot.counselorName || counselor.name;
|
||||
slot.office = slot.office || counselor.office || '';
|
||||
selectedSlot.value = { ...slot };
|
||||
console.log('获取到咨询师信息:', { name: counselor.name, office: counselor.office });
|
||||
} else {
|
||||
console.log('未找到对应的咨询师信息, counselorId:', slot.counselorId);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
@@ -280,12 +413,16 @@ async function handleSlotClick(slot: AvailableTimeSlot) {
|
||||
}
|
||||
}
|
||||
|
||||
// 设置办公室信息
|
||||
selectedOffice.value = slot.office || '';
|
||||
|
||||
// 填充预约表单
|
||||
appointmentForm.counselorId = slot.counselorId;
|
||||
appointmentForm.scheduleId = slot.scheduleId;
|
||||
appointmentForm.appointmentDate = slot.date;
|
||||
appointmentForm.appointmentTime = `${slot.startTime}-${slot.endTime}`;
|
||||
appointmentForm.appointmentDetailTime = ''; // 清空详细时段,等待用户选择
|
||||
appointmentForm.consultTheme = ''; // 清空咨询主题,等待用户选择
|
||||
appointmentForm.reason = ''; // 清空咨询原因,避免带出上次的内容
|
||||
|
||||
// 加载详细时段
|
||||
@@ -387,7 +524,12 @@ function submitAppointmentForm() {
|
||||
}
|
||||
|
||||
if (!appointmentForm.appointmentDetailTime) {
|
||||
ElMessage.warning('请选择具体预约时段');
|
||||
ElMessage.warning('预约时段信息缺失,请重新点击时段');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!appointmentForm.consultTheme) {
|
||||
ElMessage.warning('请选择咨询主题');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -452,12 +594,36 @@ async function viewMyAppointments() {
|
||||
let res = await getXljkMyAppointments(id, true); // 强制刷新数据
|
||||
console.log('查询结果:', res);
|
||||
|
||||
// 获取咨询师信息用于补充办公室字段
|
||||
let counselors: CounselorOption[] = [];
|
||||
try {
|
||||
const counselorRes = await getXljkCounselorOptions();
|
||||
if (counselorRes.code === 200 && counselorRes.data) {
|
||||
counselors = counselorRes.data;
|
||||
console.log('获取到咨询师列表:', counselors);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取咨询师信息失败', error);
|
||||
}
|
||||
|
||||
if (res.code === 200 && res.data && res.data.length > 0) {
|
||||
// 处理数据,确保状态字段是数值类型
|
||||
// 处理数据,确保状态字段是数值类型,并补充办公室信息
|
||||
myAppointmentsList.value = res.data.map((item: any) => {
|
||||
console.log('原始记录项:', item);
|
||||
|
||||
// 注意:移除了本地状态优先的逻辑,始终使用后端最新状态
|
||||
|
||||
// 补充办公室信息
|
||||
if (item.counselorId && counselors.length > 0) {
|
||||
const counselor = counselors.find((c: CounselorOption) => c.id === item.counselorId);
|
||||
if (counselor) {
|
||||
item.office = counselor.office || '';
|
||||
item.counselorName = item.counselorName || counselor.name || '';
|
||||
console.log('补充咨询师信息:', { counselorId: item.counselorId, name: counselor.name, office: counselor.office });
|
||||
} else {
|
||||
console.log('未找到对应的咨询师信息, counselorId:', item.counselorId);
|
||||
}
|
||||
}
|
||||
|
||||
// 如果status是undefined或null,设置默认值为0
|
||||
if (item.status === undefined || item.status === null) {
|
||||
@@ -473,7 +639,7 @@ async function viewMyAppointments() {
|
||||
}
|
||||
}
|
||||
|
||||
console.log('处理后的预约记录项:', item.id, '状态:', item.status, '类型:', typeof item.status);
|
||||
console.log('处理后的预约记录项:', item.id, '状态:', item.status, '办公室:', item.office, '类型:', typeof item.status);
|
||||
return item;
|
||||
});
|
||||
|
||||
@@ -491,11 +657,23 @@ async function viewMyAppointments() {
|
||||
console.log('数字类型查询结果:', res);
|
||||
|
||||
if (res.code === 200) {
|
||||
// 处理数据,确保状态字段是数值类型
|
||||
// 处理数据,确保状态字段是数值类型,并补充办公室信息
|
||||
myAppointmentsList.value = (res.data || []).map((item: any) => {
|
||||
console.log('原始记录项:', item);
|
||||
|
||||
// 注意:移除了本地状态优先的逻辑,始终使用后端最新状态
|
||||
|
||||
// 补充办公室信息
|
||||
if (item.counselorId && counselors.length > 0) {
|
||||
const counselor = counselors.find((c: CounselorOption) => c.id === item.counselorId);
|
||||
if (counselor) {
|
||||
item.office = counselor.office || '';
|
||||
item.counselorName = item.counselorName || counselor.name || '';
|
||||
console.log('补充咨询师信息:', { counselorId: item.counselorId, name: counselor.name, office: counselor.office });
|
||||
} else {
|
||||
console.log('未找到对应的咨询师信息, counselorId:', item.counselorId);
|
||||
}
|
||||
}
|
||||
|
||||
// 如果status是undefined或null,设置默认值为0
|
||||
if (item.status === undefined || item.status === null) {
|
||||
@@ -511,7 +689,7 @@ async function viewMyAppointments() {
|
||||
}
|
||||
}
|
||||
|
||||
console.log('处理后的预约记录项:', item.id, '状态:', item.status, '类型:', typeof item.status);
|
||||
console.log('处理后的预约记录项:', item.id, '状态:', item.status, '办公室:', item.office, '类型:', typeof item.status);
|
||||
return item;
|
||||
});
|
||||
|
||||
@@ -594,7 +772,7 @@ async function initCounselorInfo() {
|
||||
availableSlots.value = availableSlots.value.map((slot) => {
|
||||
const counselor = counselors.find((c: CounselorOption) => c.id === slot.counselorId);
|
||||
if (counselor) {
|
||||
return { ...slot, counselorName: counselor.name };
|
||||
return { ...slot, counselorName: counselor.name, office: counselor.office };
|
||||
}
|
||||
return slot;
|
||||
});
|
||||
@@ -616,9 +794,34 @@ async function refreshMyAppointments() {
|
||||
try {
|
||||
// 强制刷新数据
|
||||
const res = await getXljkMyAppointments(studentId.value, true);
|
||||
|
||||
// 获取咨询师信息用于补充办公室字段
|
||||
let counselors: CounselorOption[] = [];
|
||||
try {
|
||||
const counselorRes = await getXljkCounselorOptions();
|
||||
if (counselorRes.code === 200 && counselorRes.data) {
|
||||
counselors = counselorRes.data;
|
||||
console.log('刷新时获取到咨询师列表:', counselors);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取咨询师信息失败', error);
|
||||
}
|
||||
|
||||
if (res.code === 200) {
|
||||
// 处理数据,确保状态字段是数值类型
|
||||
// 处理数据,确保状态字段是数值类型,并补充办公室信息
|
||||
myAppointmentsList.value = (res.data || []).map((item: any) => {
|
||||
// 补充办公室信息
|
||||
if (item.counselorId && counselors.length > 0) {
|
||||
const counselor = counselors.find((c: CounselorOption) => c.id === item.counselorId);
|
||||
if (counselor) {
|
||||
item.office = counselor.office || '';
|
||||
item.counselorName = item.counselorName || counselor.name || '';
|
||||
console.log('刷新时补充咨询师信息:', { counselorId: item.counselorId, name: counselor.name, office: counselor.office });
|
||||
} else {
|
||||
console.log('刷新时未找到对应的咨询师信息, counselorId:', item.counselorId);
|
||||
}
|
||||
}
|
||||
|
||||
// 如果status是undefined或null,设置默认值为0
|
||||
if (item.status === undefined || item.status === null) {
|
||||
item.status = 0; // 默认为待咨询
|
||||
@@ -631,6 +834,7 @@ async function refreshMyAppointments() {
|
||||
}
|
||||
}
|
||||
|
||||
console.log('刷新后的预约记录项:', item.id, '状态:', item.status, '办公室:', item.office);
|
||||
return item;
|
||||
});
|
||||
ElMessage.success('刷新成功');
|
||||
|
||||
@@ -12,6 +12,11 @@
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="办公室" prop="office">
|
||||
<el-input v-model="queryParams.office" placeholder="请输入办公室" clearable style="width: 100%" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="排班日期" prop="dateRange">
|
||||
<el-date-picker
|
||||
@@ -28,7 +33,7 @@
|
||||
<el-col :span="6">
|
||||
<el-form-item label="时段类型" prop="timeSlot">
|
||||
<el-select v-model="queryParams.timeSlot" placeholder="请选择时段类型" clearable style="width: 100%">
|
||||
<el-option label="上午" value="0" />
|
||||
<el-option label="中午" value="0" />
|
||||
<el-option label="下午" value="1" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
@@ -75,8 +80,9 @@
|
||||
<el-table v-loading="loading" border :data="scheduleList" @selection-change="handleSelectionChange">
|
||||
<el-table-column type="selection" width="55" align="center" />
|
||||
<el-table-column label="咨询师" align="center" prop="counselorName" min-width="100" show-overflow-tooltip />
|
||||
<el-table-column label="办公室" align="center" prop="office" min-width="120" show-overflow-tooltip />
|
||||
<el-table-column label="日期范围" align="center" prop="dateRange" min-width="180" />
|
||||
<el-table-column label="上午时段" align="center" min-width="120">
|
||||
<el-table-column label="中午时段" align="center" min-width="120">
|
||||
<template #default="scope">
|
||||
<span v-if="scope.row.morningEnabled">{{ scope.row.morningTime }}</span>
|
||||
<span v-else>-</span>
|
||||
@@ -129,13 +135,16 @@
|
||||
</el-card>
|
||||
|
||||
<!-- 添加或修改预约时段对话框 -->
|
||||
<el-dialog :title="title" v-model="open" width="500px" append-to-body>
|
||||
<el-dialog :title="title" v-model="open" width="800px" append-to-body>
|
||||
<el-form ref="scheduleFormRef" :model="form" :rules="rules" label-width="100px">
|
||||
<el-form-item label="咨询师" prop="counselorId">
|
||||
<el-select v-model="form.counselorId" placeholder="请选择咨询师" style="width: 100%">
|
||||
<el-select v-model="form.counselorId" placeholder="请选择咨询师" style="width: 100%" @change="handleCounselorChange">
|
||||
<el-option v-for="item in counselorOptions" :key="item.id" :label="item.name" :value="item.id" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="办公室" prop="office">
|
||||
<el-input v-model="form.office" placeholder="请输入办公室" style="width: 100%" />
|
||||
</el-form-item>
|
||||
<el-form-item label="排班日期" prop="dateRange" v-if="!form.id">
|
||||
<el-date-picker
|
||||
v-model="form.dateRange"
|
||||
@@ -151,60 +160,92 @@
|
||||
<el-date-picker v-model="form.scheduleDate" type="date" placeholder="选择日期" value-format="YYYY-MM-DD" style="width: 100%" />
|
||||
</el-form-item>
|
||||
|
||||
<!-- 上午时段设置 -->
|
||||
<el-divider content-position="left">上午时段</el-divider>
|
||||
<el-form-item label="启用上午" prop="morningEnabled">
|
||||
<el-switch v-model="form.morningEnabled" />
|
||||
</el-form-item>
|
||||
<template v-if="form.morningEnabled">
|
||||
<el-form-item label="开始时间" prop="morningStartTime">
|
||||
<el-time-picker
|
||||
v-model="morningStartTimeTemp"
|
||||
format="HH:mm"
|
||||
placeholder="选择时间"
|
||||
style="width: 100%"
|
||||
:picker-options="morningTimePickerOptions"
|
||||
@change="handleMorningStartTimeChange"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="结束时间" prop="morningEndTime">
|
||||
<el-time-picker
|
||||
v-model="morningEndTimeTemp"
|
||||
format="HH:mm"
|
||||
placeholder="选择时间"
|
||||
style="width: 100%"
|
||||
:picker-options="morningTimePickerOptions"
|
||||
@change="handleMorningEndTimeChange"
|
||||
/>
|
||||
</el-form-item>
|
||||
<!-- 中午时段设置 -->
|
||||
<el-divider content-position="left">中午时段</el-divider>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="启用中午" prop="noonEnabled">
|
||||
<el-switch v-model="form.noonEnabled" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12" class="text-right">
|
||||
<el-button v-if="form.noonEnabled" type="primary" link icon="Plus" class="add-time-slot-btn" @click="addNoonTimeSlot">增加时段</el-button>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<template v-if="form.noonEnabled">
|
||||
<div v-for="(slot, index) in form.noonTimeSlots" :key="`noon-${index}`" class="time-slot-row">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="10">
|
||||
<el-form-item :label="index === 0 ? '开始时间' : ''" :prop="`noonTimeSlots.${index}.startTime`">
|
||||
<el-time-picker
|
||||
v-model="slot.startTimeTemp"
|
||||
format="HH:mm"
|
||||
placeholder="选择时间"
|
||||
style="width: 100%"
|
||||
@change="(time) => handleNoonStartTimeChange(time, index)"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="10">
|
||||
<el-form-item :label="index === 0 ? '结束时间' : ''" :prop="`noonTimeSlots.${index}.endTime`">
|
||||
<el-time-picker
|
||||
v-model="slot.endTimeTemp"
|
||||
format="HH:mm"
|
||||
placeholder="选择时间"
|
||||
style="width: 100%"
|
||||
@change="(time) => handleNoonEndTimeChange(time, index)"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="4" class="time-slot-actions">
|
||||
<el-button v-if="index > 0" type="danger" link icon="Delete" class="delete-time-slot-btn" @click="removeNoonTimeSlot(index)"></el-button>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 下午时段设置 -->
|
||||
<el-divider content-position="left">下午时段</el-divider>
|
||||
<el-form-item label="启用下午" prop="afternoonEnabled">
|
||||
<el-switch v-model="form.afternoonEnabled" />
|
||||
</el-form-item>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="启用下午" prop="afternoonEnabled">
|
||||
<el-switch v-model="form.afternoonEnabled" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12" class="text-right">
|
||||
<el-button v-if="form.afternoonEnabled" type="primary" link icon="Plus" class="add-time-slot-btn" @click="addAfternoonTimeSlot">增加时段</el-button>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<template v-if="form.afternoonEnabled">
|
||||
<el-form-item label="开始时间" prop="afternoonStartTime">
|
||||
<el-time-picker
|
||||
v-model="afternoonStartTimeTemp"
|
||||
format="HH:mm"
|
||||
placeholder="选择时间"
|
||||
style="width: 100%"
|
||||
:picker-options="afternoonTimePickerOptions"
|
||||
@change="handleAfternoonStartTimeChange"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="结束时间" prop="afternoonEndTime">
|
||||
<el-time-picker
|
||||
v-model="afternoonEndTimeTemp"
|
||||
format="HH:mm"
|
||||
placeholder="选择时间"
|
||||
style="width: 100%"
|
||||
:picker-options="afternoonTimePickerOptions"
|
||||
@change="handleAfternoonEndTimeChange"
|
||||
/>
|
||||
</el-form-item>
|
||||
<div v-for="(slot, index) in form.afternoonTimeSlots" :key="`afternoon-${index}`" class="time-slot-row">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="10">
|
||||
<el-form-item :label="index === 0 ? '开始时间' : ''" :prop="`afternoonTimeSlots.${index}.startTime`">
|
||||
<el-time-picker
|
||||
v-model="slot.startTimeTemp"
|
||||
format="HH:mm"
|
||||
placeholder="选择时间"
|
||||
style="width: 100%"
|
||||
@change="(time) => handleAfternoonStartTimeChange(time, index)"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="10">
|
||||
<el-form-item :label="index === 0 ? '结束时间' : ''" :prop="`afternoonTimeSlots.${index}.endTime`">
|
||||
<el-time-picker
|
||||
v-model="slot.endTimeTemp"
|
||||
format="HH:mm"
|
||||
placeholder="选择时间"
|
||||
style="width: 100%"
|
||||
@change="(time) => handleAfternoonEndTimeChange(time, index)"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="4" class="time-slot-actions">
|
||||
<el-button v-if="index > 0" type="danger" link icon="Delete" class="delete-time-slot-btn" @click="removeAfternoonTimeSlot(index)"></el-button>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<el-form-item label="状态" prop="status">
|
||||
@@ -268,34 +309,13 @@ const open = ref(false);
|
||||
const dateRange = ref<string[]>([]);
|
||||
// 咨询师选项
|
||||
const counselorOptions = ref<CounselorOption[]>([]);
|
||||
// 时间选择器临时变量
|
||||
const startTimeTemp = ref<Date | null>(null);
|
||||
const endTimeTemp = ref<Date | null>(null);
|
||||
const morningStartTimeTemp = ref<Date | null>(null);
|
||||
const morningEndTimeTemp = ref<Date | null>(null);
|
||||
const afternoonStartTimeTemp = ref<Date | null>(null);
|
||||
const afternoonEndTimeTemp = ref<Date | null>(null);
|
||||
|
||||
// 时间选择器配置
|
||||
const morningTimePickerOptions = {
|
||||
start: '08:00',
|
||||
step: '00:30',
|
||||
end: '12:00',
|
||||
format: 'HH:mm'
|
||||
};
|
||||
|
||||
const afternoonTimePickerOptions = {
|
||||
start: '14:00',
|
||||
step: '00:30',
|
||||
end: '17:00',
|
||||
format: 'HH:mm'
|
||||
};
|
||||
|
||||
// 查询参数
|
||||
const queryParams = reactive({
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
counselorId: undefined,
|
||||
office: undefined,
|
||||
timeSlot: undefined,
|
||||
status: undefined,
|
||||
params: {} as any
|
||||
@@ -305,23 +325,50 @@ const queryParams = reactive({
|
||||
const form = reactive<any>({
|
||||
id: undefined,
|
||||
counselorId: undefined,
|
||||
office: '',
|
||||
scheduleDate: '',
|
||||
dateRange: [],
|
||||
morningEnabled: true,
|
||||
morningStartTime: '08:30',
|
||||
morningEndTime: '12:00',
|
||||
noonEnabled: true,
|
||||
noonTimeSlots: [
|
||||
{
|
||||
startTime: '12:00',
|
||||
endTime: '12:40',
|
||||
startTimeTemp: (() => {
|
||||
const time = new Date();
|
||||
time.setHours(12, 0, 0, 0);
|
||||
return time;
|
||||
})(),
|
||||
endTimeTemp: (() => {
|
||||
const time = new Date();
|
||||
time.setHours(12, 40, 0, 0);
|
||||
return time;
|
||||
})()
|
||||
}
|
||||
],
|
||||
afternoonEnabled: true,
|
||||
afternoonStartTime: '14:30',
|
||||
afternoonEndTime: '17:00',
|
||||
timeSlot: '0',
|
||||
startTime: '',
|
||||
endTime: '',
|
||||
afternoonTimeSlots: [
|
||||
{
|
||||
startTime: '17:00',
|
||||
endTime: '17:40',
|
||||
startTimeTemp: (() => {
|
||||
const time = new Date();
|
||||
time.setHours(17, 0, 0, 0);
|
||||
return time;
|
||||
})(),
|
||||
endTimeTemp: (() => {
|
||||
const time = new Date();
|
||||
time.setHours(17, 40, 0, 0);
|
||||
return time;
|
||||
})()
|
||||
}
|
||||
],
|
||||
status: '0'
|
||||
});
|
||||
|
||||
// 表单校验
|
||||
const rules = {
|
||||
counselorId: [{ required: true, message: '咨询师不能为空', trigger: 'change' }],
|
||||
office: [{ required: true, message: '办公室不能为空', trigger: 'blur' }],
|
||||
scheduleDate: [{ required: false, message: '排班日期不能为空', trigger: 'change' }],
|
||||
dateRange: [{ required: true, message: '排班日期范围不能为空', trigger: 'change' }],
|
||||
status: [{ required: true, message: '状态不能为空', trigger: 'change' }]
|
||||
@@ -377,38 +424,42 @@ const cancel = () => {
|
||||
const reset = () => {
|
||||
form.id = undefined;
|
||||
form.counselorId = undefined;
|
||||
form.office = '';
|
||||
form.scheduleDate = '';
|
||||
form.dateRange = [];
|
||||
form.morningEnabled = true;
|
||||
form.noonEnabled = true;
|
||||
form.afternoonEnabled = true;
|
||||
form.morningStartTime = '08:30';
|
||||
form.morningEndTime = '12:00';
|
||||
form.afternoonStartTime = '14:30';
|
||||
form.afternoonEndTime = '17:00';
|
||||
form.timeSlot = '0';
|
||||
form.startTime = '';
|
||||
form.endTime = '';
|
||||
form.status = '0';
|
||||
|
||||
startTimeTemp.value = null;
|
||||
endTimeTemp.value = null;
|
||||
// 设置中午时段默认值
|
||||
const noonStartTime = new Date();
|
||||
noonStartTime.setHours(12, 0, 0, 0);
|
||||
const noonEndTime = new Date();
|
||||
noonEndTime.setHours(12, 40, 0, 0);
|
||||
|
||||
// 设置默认时间
|
||||
const morningStart = new Date();
|
||||
morningStart.setHours(8, 30, 0, 0);
|
||||
morningStartTimeTemp.value = morningStart;
|
||||
form.noonTimeSlots = [
|
||||
{
|
||||
startTime: '12:00',
|
||||
endTime: '12:40',
|
||||
startTimeTemp: noonStartTime,
|
||||
endTimeTemp: noonEndTime
|
||||
}
|
||||
];
|
||||
|
||||
const morningEnd = new Date();
|
||||
morningEnd.setHours(12, 0, 0, 0);
|
||||
morningEndTimeTemp.value = morningEnd;
|
||||
// 设置下午时段默认值
|
||||
const afternoonStartTime = new Date();
|
||||
afternoonStartTime.setHours(17, 0, 0, 0);
|
||||
const afternoonEndTime = new Date();
|
||||
afternoonEndTime.setHours(17, 40, 0, 0);
|
||||
|
||||
const afternoonStart = new Date();
|
||||
afternoonStart.setHours(14, 30, 0, 0);
|
||||
afternoonStartTimeTemp.value = afternoonStart;
|
||||
|
||||
const afternoonEnd = new Date();
|
||||
afternoonEnd.setHours(17, 0, 0, 0);
|
||||
afternoonEndTimeTemp.value = afternoonEnd;
|
||||
form.afternoonTimeSlots = [
|
||||
{
|
||||
startTime: '17:00',
|
||||
endTime: '17:40',
|
||||
startTimeTemp: afternoonStartTime,
|
||||
endTimeTemp: afternoonEndTime
|
||||
}
|
||||
];
|
||||
|
||||
scheduleFormRef.value?.resetFields();
|
||||
};
|
||||
@@ -441,7 +492,7 @@ const handleAdd = () => {
|
||||
};
|
||||
|
||||
/** 查看按钮操作 */
|
||||
const handleView = async (row: XljkConsultationScheduleVO) => {
|
||||
const handleView = async (row: XljkConsultationScheduleSummaryVO) => {
|
||||
try {
|
||||
const res = await getXljkSchedule(row.id);
|
||||
if (res.code === 200 && res.data) {
|
||||
@@ -455,7 +506,7 @@ const handleView = async (row: XljkConsultationScheduleVO) => {
|
||||
};
|
||||
|
||||
/** 修改按钮操作 */
|
||||
const handleUpdate = async (row?: XljkConsultationScheduleVO) => {
|
||||
const handleUpdate = async (row?: XljkConsultationScheduleSummaryVO) => {
|
||||
if (row && row.id) {
|
||||
try {
|
||||
// 修复:将字符串状态正确转换为数字进行比较
|
||||
@@ -491,6 +542,7 @@ const submitForm = async () => {
|
||||
await updateXljkSchedule({
|
||||
id: form.id,
|
||||
counselorId: form.counselorId,
|
||||
office: form.office,
|
||||
scheduleDate: form.scheduleDate,
|
||||
timeSlot: form.timeSlot,
|
||||
startTime: form.startTime,
|
||||
@@ -514,26 +566,67 @@ const submitForm = async () => {
|
||||
}
|
||||
|
||||
// 检查是否至少启用了一个时段
|
||||
if (!form.morningEnabled && !form.afternoonEnabled) {
|
||||
ElMessage.warning('请至少启用一个时段(上午或下午)');
|
||||
if (!form.noonEnabled && !form.afternoonEnabled) {
|
||||
ElMessage.warning('请至少启用一个时段(中午或下午)');
|
||||
return;
|
||||
}
|
||||
|
||||
// 确保时间数据同步 - 从时间选择器同步到字符串字段
|
||||
if (form.noonEnabled) {
|
||||
form.noonTimeSlots.forEach((slot, index) => {
|
||||
if (slot.startTimeTemp && !slot.startTime) {
|
||||
const hours = slot.startTimeTemp.getHours().toString().padStart(2, '0');
|
||||
const minutes = slot.startTimeTemp.getMinutes().toString().padStart(2, '0');
|
||||
slot.startTime = `${hours}:${minutes}`;
|
||||
}
|
||||
if (slot.endTimeTemp && !slot.endTime) {
|
||||
const hours = slot.endTimeTemp.getHours().toString().padStart(2, '0');
|
||||
const minutes = slot.endTimeTemp.getMinutes().toString().padStart(2, '0');
|
||||
slot.endTime = `${hours}:${minutes}`;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (form.afternoonEnabled) {
|
||||
form.afternoonTimeSlots.forEach((slot, index) => {
|
||||
if (slot.startTimeTemp && !slot.startTime) {
|
||||
const hours = slot.startTimeTemp.getHours().toString().padStart(2, '0');
|
||||
const minutes = slot.startTimeTemp.getMinutes().toString().padStart(2, '0');
|
||||
slot.startTime = `${hours}:${minutes}`;
|
||||
}
|
||||
if (slot.endTimeTemp && !slot.endTime) {
|
||||
const hours = slot.endTimeTemp.getHours().toString().padStart(2, '0');
|
||||
const minutes = slot.endTimeTemp.getMinutes().toString().padStart(2, '0');
|
||||
slot.endTime = `${hours}:${minutes}`;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 批量创建预约时段的请求数据
|
||||
const batchCreateData = {
|
||||
counselorId: form.counselorId,
|
||||
office: form.office,
|
||||
startDate: form.dateRange[0],
|
||||
endDate: form.dateRange[1],
|
||||
morningEnabled: form.morningEnabled,
|
||||
morningStartTime: form.morningStartTime,
|
||||
morningEndTime: form.morningEndTime,
|
||||
noonEnabled: form.noonEnabled,
|
||||
noonTimeSlots: form.noonTimeSlots.map(slot => ({
|
||||
startTime: slot.startTime,
|
||||
endTime: slot.endTime
|
||||
})),
|
||||
afternoonEnabled: form.afternoonEnabled,
|
||||
afternoonStartTime: form.afternoonStartTime,
|
||||
afternoonEndTime: form.afternoonEndTime,
|
||||
afternoonTimeSlots: form.afternoonTimeSlots.map(slot => ({
|
||||
startTime: slot.startTime,
|
||||
endTime: slot.endTime
|
||||
})),
|
||||
status: form.status,
|
||||
skipWeekends: true // 自动跳过周末
|
||||
};
|
||||
|
||||
// 调试日志 - 确认时间数据
|
||||
console.log('提交的批量创建数据:', batchCreateData);
|
||||
console.log('中午时段数据:', form.noonTimeSlots);
|
||||
console.log('下午时段数据:', form.afternoonTimeSlots);
|
||||
|
||||
// 调用批量创建API
|
||||
const res = await addXljkSchedule(batchCreateData);
|
||||
if (res.code === 200) {
|
||||
@@ -551,7 +644,7 @@ const submitForm = async () => {
|
||||
};
|
||||
|
||||
/** 删除按钮操作 */
|
||||
const handleDelete = async (row?: XljkConsultationScheduleVO) => {
|
||||
const handleDelete = async (row?: XljkConsultationScheduleSummaryVO) => {
|
||||
const _ids = row?.id || ids.value;
|
||||
const confirmMessage = row
|
||||
? `是否确认删除该预约时段?${row.scheduleCount > 1 ? `(包含${row.scheduleCount}条记录)` : ''}`
|
||||
@@ -594,47 +687,147 @@ const handleEndTimeChange = (time: Date) => {
|
||||
}
|
||||
};
|
||||
|
||||
/** 处理上午开始时间变化 */
|
||||
const handleMorningStartTimeChange = (time: Date) => {
|
||||
/** 处理中午开始时间变化 */
|
||||
const handleNoonStartTimeChange = (time: Date, index: number) => {
|
||||
if (time) {
|
||||
const hours = time.getHours().toString().padStart(2, '0');
|
||||
const minutes = time.getMinutes().toString().padStart(2, '0');
|
||||
form.morningStartTime = `${hours}:${minutes}`;
|
||||
const timeString = `${hours}:${minutes}`;
|
||||
form.noonTimeSlots[index].startTime = timeString;
|
||||
form.noonTimeSlots[index].startTimeTemp = time;
|
||||
} else {
|
||||
form.morningStartTime = '';
|
||||
form.noonTimeSlots[index].startTime = '';
|
||||
form.noonTimeSlots[index].startTimeTemp = null;
|
||||
}
|
||||
};
|
||||
|
||||
/** 处理上午结束时间变化 */
|
||||
const handleMorningEndTimeChange = (time: Date) => {
|
||||
/** 处理中午结束时间变化 */
|
||||
const handleNoonEndTimeChange = (time: Date, index: number) => {
|
||||
if (time) {
|
||||
const hours = time.getHours().toString().padStart(2, '0');
|
||||
const minutes = time.getMinutes().toString().padStart(2, '0');
|
||||
form.morningEndTime = `${hours}:${minutes}`;
|
||||
const timeString = `${hours}:${minutes}`;
|
||||
form.noonTimeSlots[index].endTime = timeString;
|
||||
form.noonTimeSlots[index].endTimeTemp = time;
|
||||
} else {
|
||||
form.morningEndTime = '';
|
||||
form.noonTimeSlots[index].endTime = '';
|
||||
form.noonTimeSlots[index].endTimeTemp = null;
|
||||
}
|
||||
};
|
||||
|
||||
/** 新增中午时段 */
|
||||
const addNoonTimeSlot = () => {
|
||||
const lastSlot = form.noonTimeSlots[form.noonTimeSlots.length - 1];
|
||||
let startTime = '12:00';
|
||||
let endTime = '12:40';
|
||||
|
||||
if (lastSlot && lastSlot.endTime) {
|
||||
startTime = lastSlot.endTime;
|
||||
// 计算结束时间(开始时间+40分钟)
|
||||
const [hours, minutes] = startTime.split(':').map(Number);
|
||||
const startDate = new Date();
|
||||
startDate.setHours(hours, minutes, 0, 0);
|
||||
const endDate = new Date(startDate.getTime() + 40 * 60 * 1000); // 加40分钟
|
||||
endTime = `${endDate.getHours().toString().padStart(2, '0')}:${endDate.getMinutes().toString().padStart(2, '0')}`;
|
||||
}
|
||||
|
||||
// 创建对应的Date对象用于时间选择器
|
||||
const startTimeDate = new Date();
|
||||
const [startHours, startMinutes] = startTime.split(':').map(Number);
|
||||
startTimeDate.setHours(startHours, startMinutes, 0, 0);
|
||||
|
||||
const endTimeDate = new Date();
|
||||
const [endHours, endMinutes] = endTime.split(':').map(Number);
|
||||
endTimeDate.setHours(endHours, endMinutes, 0, 0);
|
||||
|
||||
form.noonTimeSlots.push({
|
||||
startTime,
|
||||
endTime,
|
||||
startTimeTemp: startTimeDate,
|
||||
endTimeTemp: endTimeDate
|
||||
});
|
||||
};
|
||||
|
||||
/** 移除中午时段 */
|
||||
const removeNoonTimeSlot = (index: number) => {
|
||||
form.noonTimeSlots.splice(index, 1);
|
||||
};
|
||||
|
||||
/** 处理下午开始时间变化 */
|
||||
const handleAfternoonStartTimeChange = (time: Date) => {
|
||||
const handleAfternoonStartTimeChange = (time: Date, index: number) => {
|
||||
if (time) {
|
||||
const hours = time.getHours().toString().padStart(2, '0');
|
||||
const minutes = time.getMinutes().toString().padStart(2, '0');
|
||||
form.afternoonStartTime = `${hours}:${minutes}`;
|
||||
const timeString = `${hours}:${minutes}`;
|
||||
form.afternoonTimeSlots[index].startTime = timeString;
|
||||
form.afternoonTimeSlots[index].startTimeTemp = time;
|
||||
} else {
|
||||
form.afternoonStartTime = '';
|
||||
form.afternoonTimeSlots[index].startTime = '';
|
||||
form.afternoonTimeSlots[index].startTimeTemp = null;
|
||||
}
|
||||
};
|
||||
|
||||
/** 处理下午结束时间变化 */
|
||||
const handleAfternoonEndTimeChange = (time: Date) => {
|
||||
const handleAfternoonEndTimeChange = (time: Date, index: number) => {
|
||||
if (time) {
|
||||
const hours = time.getHours().toString().padStart(2, '0');
|
||||
const minutes = time.getMinutes().toString().padStart(2, '0');
|
||||
form.afternoonEndTime = `${hours}:${minutes}`;
|
||||
const timeString = `${hours}:${minutes}`;
|
||||
form.afternoonTimeSlots[index].endTime = timeString;
|
||||
form.afternoonTimeSlots[index].endTimeTemp = time;
|
||||
} else {
|
||||
form.afternoonEndTime = '';
|
||||
form.afternoonTimeSlots[index].endTime = '';
|
||||
form.afternoonTimeSlots[index].endTimeTemp = null;
|
||||
}
|
||||
};
|
||||
|
||||
/** 新增下午时段 */
|
||||
const addAfternoonTimeSlot = () => {
|
||||
const lastSlot = form.afternoonTimeSlots[form.afternoonTimeSlots.length - 1];
|
||||
let startTime = '17:00';
|
||||
let endTime = '17:40';
|
||||
|
||||
if (lastSlot && lastSlot.endTime) {
|
||||
startTime = lastSlot.endTime;
|
||||
// 计算结束时间(开始时间+40分钟)
|
||||
const [hours, minutes] = startTime.split(':').map(Number);
|
||||
const startDate = new Date();
|
||||
startDate.setHours(hours, minutes, 0, 0);
|
||||
const endDate = new Date(startDate.getTime() + 40 * 60 * 1000); // 加40分钟
|
||||
endTime = `${endDate.getHours().toString().padStart(2, '0')}:${endDate.getMinutes().toString().padStart(2, '0')}`;
|
||||
}
|
||||
|
||||
// 创建对应的Date对象用于时间选择器
|
||||
const startTimeDate = new Date();
|
||||
const [startHours, startMinutes] = startTime.split(':').map(Number);
|
||||
startTimeDate.setHours(startHours, startMinutes, 0, 0);
|
||||
|
||||
const endTimeDate = new Date();
|
||||
const [endHours, endMinutes] = endTime.split(':').map(Number);
|
||||
endTimeDate.setHours(endHours, endMinutes, 0, 0);
|
||||
|
||||
form.afternoonTimeSlots.push({
|
||||
startTime,
|
||||
endTime,
|
||||
startTimeTemp: startTimeDate,
|
||||
endTimeTemp: endTimeDate
|
||||
});
|
||||
};
|
||||
|
||||
/** 移除下午时段 */
|
||||
const removeAfternoonTimeSlot = (index: number) => {
|
||||
form.afternoonTimeSlots.splice(index, 1);
|
||||
};
|
||||
|
||||
/** 处理咨询师变化 */
|
||||
const handleCounselorChange = (counselorId: number) => {
|
||||
if (counselorId) {
|
||||
const selectedCounselor = counselorOptions.value.find(counselor => counselor.id === counselorId);
|
||||
if (selectedCounselor && selectedCounselor.office) {
|
||||
form.office = selectedCounselor.office;
|
||||
}
|
||||
} else {
|
||||
form.office = '';
|
||||
}
|
||||
};
|
||||
|
||||
@@ -684,4 +877,140 @@ onMounted(() => {
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* 时间槽行样式 */
|
||||
.time-slot-row {
|
||||
margin-bottom: 10px;
|
||||
padding: 10px;
|
||||
background-color: #f5f7fa;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
/* 时间槽操作按钮样式 */
|
||||
.time-slot-actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
padding-top: 8px;
|
||||
}
|
||||
|
||||
/* 右对齐样式 */
|
||||
.text-right {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
/* 新增时段按钮样式 */
|
||||
.add-time-slot-btn {
|
||||
border: none !important;
|
||||
box-shadow: none !important;
|
||||
background-color: transparent !important;
|
||||
color: #409eff !important;
|
||||
padding: 8px 12px;
|
||||
font-size: 14px;
|
||||
font-weight: normal;
|
||||
transition: color 0.3s ease;
|
||||
}
|
||||
|
||||
.add-time-slot-btn:hover {
|
||||
color: #66b1ff !important;
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
.add-time-slot-btn:focus {
|
||||
border: none !important;
|
||||
box-shadow: none !important;
|
||||
background-color: transparent !important;
|
||||
outline: none !important;
|
||||
}
|
||||
|
||||
.add-time-slot-btn:active {
|
||||
border: none !important;
|
||||
box-shadow: none !important;
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
/* 更强的选择器覆盖 Element Plus 默认样式 */
|
||||
.el-button.add-time-slot-btn.el-button--primary.is-link {
|
||||
border: none !important;
|
||||
box-shadow: none !important;
|
||||
background-color: transparent !important;
|
||||
color: #409eff !important;
|
||||
}
|
||||
|
||||
.el-button.add-time-slot-btn.el-button--primary.is-link:hover {
|
||||
border: none !important;
|
||||
box-shadow: none !important;
|
||||
background-color: transparent !important;
|
||||
color: #66b1ff !important;
|
||||
}
|
||||
|
||||
.el-button.add-time-slot-btn.el-button--primary.is-link:focus {
|
||||
border: none !important;
|
||||
box-shadow: none !important;
|
||||
background-color: transparent !important;
|
||||
outline: none !important;
|
||||
}
|
||||
|
||||
.el-button.add-time-slot-btn.el-button--primary.is-link:active {
|
||||
border: none !important;
|
||||
box-shadow: none !important;
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
/* 删除时段按钮样式 */
|
||||
.delete-time-slot-btn {
|
||||
border: none !important;
|
||||
box-shadow: none !important;
|
||||
background-color: transparent !important;
|
||||
color: #f56c6c !important;
|
||||
padding: 8px;
|
||||
font-size: 16px;
|
||||
transition: color 0.3s ease;
|
||||
}
|
||||
|
||||
.delete-time-slot-btn:hover {
|
||||
color: #f78989 !important;
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
.delete-time-slot-btn:focus {
|
||||
border: none !important;
|
||||
box-shadow: none !important;
|
||||
background-color: transparent !important;
|
||||
outline: none !important;
|
||||
}
|
||||
|
||||
.delete-time-slot-btn:active {
|
||||
border: none !important;
|
||||
box-shadow: none !important;
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
/* 更强的选择器覆盖 Element Plus 默认样式 */
|
||||
.el-button.delete-time-slot-btn.el-button--danger.is-link {
|
||||
border: none !important;
|
||||
box-shadow: none !important;
|
||||
background-color: transparent !important;
|
||||
color: #f56c6c !important;
|
||||
}
|
||||
|
||||
.el-button.delete-time-slot-btn.el-button--danger.is-link:hover {
|
||||
border: none !important;
|
||||
box-shadow: none !important;
|
||||
background-color: transparent !important;
|
||||
color: #f78989 !important;
|
||||
}
|
||||
|
||||
.el-button.delete-time-slot-btn.el-button--danger.is-link:focus {
|
||||
border: none !important;
|
||||
box-shadow: none !important;
|
||||
background-color: transparent !important;
|
||||
outline: none !important;
|
||||
}
|
||||
|
||||
.el-button.delete-time-slot-btn.el-button--danger.is-link:active {
|
||||
border: none !important;
|
||||
box-shadow: none !important;
|
||||
background-color: transparent !important;
|
||||
}
|
||||
</style>
|
||||
File diff suppressed because it is too large.
Load diff
Reference in new issue
Block a user