“出国申请审批权限修改”
This commit is contained in:
commit
f907b9cd40
97 files changed
+18070
-2812
No files matched your search
@@ -0,0 +1,47 @@
|
||||
import request from '@/utils/request';
|
||||
import type { AxiosPromise } from 'axios';
|
||||
import type { CollegeVo, CollegeQuery } from './types';
|
||||
|
||||
// 导出类型
|
||||
export type { CollegeVo, MajorVo, CollegeQuery } from './types';
|
||||
|
||||
/**
|
||||
* 查询学院列表(用于二级学院,parent_id = 101)
|
||||
*/
|
||||
export function list(query?: any): AxiosPromise<CollegeVo[]> {
|
||||
return request({
|
||||
url: '/xg/college/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取学院和专业树形结构
|
||||
*/
|
||||
export function collegeTree(): AxiosPromise<CollegeVo[]> {
|
||||
return request({
|
||||
url: '/xg/college/tree',
|
||||
method: 'get'
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据学院ID获取专业列表
|
||||
*/
|
||||
export function listMajorsByCollegeId(collegeId: string | number): AxiosPromise<any[]> {
|
||||
return request({
|
||||
url: `/xg/college/majors/${collegeId}`,
|
||||
method: 'get'
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询学院详情
|
||||
*/
|
||||
export function getCollege(deptId: string | number): AxiosPromise<CollegeVo> {
|
||||
return request({
|
||||
url: `/xg/college/${deptId}`,
|
||||
method: 'get'
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/**
|
||||
* 学院信息对象
|
||||
*/
|
||||
export interface CollegeVo {
|
||||
deptId: number | string;
|
||||
deptName: string;
|
||||
parentId?: number | string;
|
||||
orderNum?: number;
|
||||
leader?: string;
|
||||
phone?: string;
|
||||
email?: string;
|
||||
status?: string;
|
||||
children?: CollegeVo[];
|
||||
}
|
||||
|
||||
/**
|
||||
* 专业信息对象
|
||||
*/
|
||||
export interface MajorVo {
|
||||
deptId: number | string;
|
||||
deptName: string;
|
||||
parentId: number | string;
|
||||
orderNum?: number;
|
||||
leader?: string;
|
||||
phone?: string;
|
||||
email?: string;
|
||||
status?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 学院查询参数
|
||||
*/
|
||||
export interface CollegeQuery {
|
||||
deptName?: string;
|
||||
status?: string;
|
||||
parentId?: number | string;
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
import request from '@/utils/request';
|
||||
|
||||
/**
|
||||
* 二级学院分管领导配置对象
|
||||
*/
|
||||
export interface CollegeLeaderBo {
|
||||
id?: number | string;
|
||||
collegeId?: number | string;
|
||||
collegeName?: string;
|
||||
leaderId?: number | string;
|
||||
leaderName?: string;
|
||||
remark?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 二级学院分管领导配置视图对象
|
||||
*/
|
||||
export interface CollegeLeaderVo {
|
||||
id: number | string;
|
||||
collegeId: number | string;
|
||||
collegeName: string;
|
||||
leaderId: number | string;
|
||||
leaderName: string;
|
||||
remark?: string;
|
||||
createTime?: string;
|
||||
updateTime?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询二级学院分管领导配置列表
|
||||
* @param query 查询参数
|
||||
* @returns 返回二级学院分管领导配置列表
|
||||
*/
|
||||
export function listCollegeLeader(query: any) {
|
||||
return request({
|
||||
url: '/xg/ejxyfgld/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据学院ID查询分管领导列表
|
||||
* @param collegeId 学院ID
|
||||
* @returns 返回分管领导列表
|
||||
*/
|
||||
export function listCollegeLeaderByCollege(collegeId: number | string) {
|
||||
return request({
|
||||
url: `/xg/ejxyfgld/listByCollege/${collegeId}`,
|
||||
method: 'get'
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取二级学院分管领导配置详情
|
||||
* @param id 主键ID
|
||||
* @returns 返回二级学院分管领导配置详情
|
||||
*/
|
||||
export function getCollegeLeader(id: number | string) {
|
||||
return request({
|
||||
url: `/xg/ejxyfgld/${id}`,
|
||||
method: 'get'
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增二级学院分管领导配置
|
||||
* @param data 二级学院分管领导配置信息
|
||||
* @returns 返回添加结果
|
||||
*/
|
||||
export function addCollegeLeader(data: CollegeLeaderBo) {
|
||||
return request({
|
||||
url: '/xg/ejxyfgld',
|
||||
method: 'post',
|
||||
data: data
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改二级学院分管领导配置
|
||||
* @param data 二级学院分管领导配置信息
|
||||
* @returns 返回修改结果
|
||||
*/
|
||||
export function updateCollegeLeader(data: CollegeLeaderBo) {
|
||||
return request({
|
||||
url: '/xg/ejxyfgld',
|
||||
method: 'put',
|
||||
data: data
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除二级学院分管领导配置
|
||||
* @param ids 主键ID数组
|
||||
* @returns 返回删除结果
|
||||
*/
|
||||
export function delCollegeLeader(ids: number | string | (number | string)[]) {
|
||||
return request({
|
||||
url: `/xg/ejxyfgld/${ids instanceof Array ? ids.join(',') : ids}`,
|
||||
method: 'delete'
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出二级学院分管领导配置
|
||||
* @param query 查询参数
|
||||
* @returns 返回导出结果
|
||||
*/
|
||||
export function exportCollegeLeader(query: any) {
|
||||
return request({
|
||||
url: '/xg/ejxyfgld/export',
|
||||
method: 'post',
|
||||
params: query,
|
||||
responseType: 'blob'
|
||||
});
|
||||
}
|
||||
|
||||
@@ -0,0 +1,269 @@
|
||||
import request from '@/utils/request';
|
||||
import { AxiosPromise } from 'axios';
|
||||
|
||||
/**
|
||||
* 学生动向信息管理(宿管员)API
|
||||
* 这些接口专门用于学生动向Tab页,会自动根据宿管员权限过滤数据
|
||||
*/
|
||||
|
||||
// ==================== 调宿退宿申请相关接口 ====================
|
||||
|
||||
export interface DormApplicationQuery {
|
||||
pageNum?: number;
|
||||
pageSize?: number;
|
||||
studentName?: string;
|
||||
studentNo?: string;
|
||||
collegeName?: string;
|
||||
majorName?: string;
|
||||
className?: string;
|
||||
applicationType?: string;
|
||||
applicationStatus?: string;
|
||||
applyTime?: string;
|
||||
params?: any;
|
||||
}
|
||||
|
||||
export interface DormApplicationVO {
|
||||
id: string | number;
|
||||
applicationName: string;
|
||||
applicationType: string;
|
||||
applicationStatus: string;
|
||||
studentId: string | number;
|
||||
studentNo: string;
|
||||
studentName: string;
|
||||
studentPhone: string;
|
||||
collegeId: string | number;
|
||||
collegeName: string;
|
||||
majorId: string | number;
|
||||
majorName: string;
|
||||
classId: string | number;
|
||||
className: string;
|
||||
currentCampusId: string | number;
|
||||
currentCampusName: string;
|
||||
currentBuildingId: string | number;
|
||||
currentBuildingName: string;
|
||||
currentRoomId: string | number;
|
||||
currentRoomNo: string;
|
||||
currentBedId: string | number;
|
||||
currentBedNo: string;
|
||||
targetCampusId?: string | number;
|
||||
targetCampusName?: string;
|
||||
targetBuildingId?: string | number;
|
||||
targetBuildingName?: string;
|
||||
targetRoomId?: string | number;
|
||||
targetRoomNo?: string;
|
||||
targetBedId?: string | number;
|
||||
targetBedNo?: string;
|
||||
reasonDetail: string;
|
||||
applyTime: string;
|
||||
attachmentOssIds?: string;
|
||||
remark?: string;
|
||||
createTime: string;
|
||||
updateTime: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询宿管员权限内的学生调宿退宿申请列表
|
||||
*/
|
||||
export function listDormApplication(query?: DormApplicationQuery): AxiosPromise<any> {
|
||||
return request({
|
||||
url: '/studentDynamics/dormApplicationList',
|
||||
method: 'get',
|
||||
params: query
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取调宿退宿申请详细信息
|
||||
*/
|
||||
export function getDormApplication(id: string | number): AxiosPromise<DormApplicationVO> {
|
||||
return request({
|
||||
url: '/studentDynamics/dormApplication/' + id,
|
||||
method: 'get'
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取调宿退宿申请详细信息(别名)
|
||||
*/
|
||||
export const getDormApplicationInfo = getDormApplication;
|
||||
|
||||
/**
|
||||
* 修改调宿退宿申请信息
|
||||
*/
|
||||
export function updateDormApplication(data: DormApplicationVO): AxiosPromise<any> {
|
||||
return request({
|
||||
url: '/studentDynamics/dormApplication',
|
||||
method: 'put',
|
||||
data: data
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除调宿退宿申请
|
||||
*/
|
||||
export function delDormApplication(id: string | number | Array<string | number>): AxiosPromise<any> {
|
||||
return request({
|
||||
url: '/studentDynamics/dormApplication/' + id,
|
||||
method: 'delete'
|
||||
});
|
||||
}
|
||||
|
||||
// ==================== 假期留宿相关接口 ====================
|
||||
|
||||
export interface HolidayStayQuery {
|
||||
pageNum?: number;
|
||||
pageSize?: number;
|
||||
studentName?: string;
|
||||
studentNo?: string;
|
||||
collegeName?: string;
|
||||
majorName?: string;
|
||||
className?: string;
|
||||
holidayType?: string;
|
||||
applyStartDate?: string;
|
||||
applyEndDate?: string;
|
||||
status?: string;
|
||||
params?: any;
|
||||
}
|
||||
|
||||
export interface HolidayStayVO {
|
||||
id: string | number;
|
||||
studentId: string | number;
|
||||
studentName: string;
|
||||
studentNo: string;
|
||||
classId: string | number;
|
||||
className: string;
|
||||
majorId: string | number;
|
||||
majorName: string;
|
||||
collegeId: string | number;
|
||||
collegeName: string;
|
||||
campusId: string | number;
|
||||
campusName: string;
|
||||
areaId?: string | number;
|
||||
areaName?: string;
|
||||
buildingId: string | number;
|
||||
buildingName: string;
|
||||
roomId: string | number;
|
||||
roomName: string;
|
||||
bedId: string | number;
|
||||
bedName: string;
|
||||
holidayType: string;
|
||||
applyStartDate: string;
|
||||
applyEndDate: string;
|
||||
stayReason: string;
|
||||
stayReasonType: string;
|
||||
studentPhone: string;
|
||||
emergencyContact: string;
|
||||
emergencyPhone: string;
|
||||
parentConsent: string;
|
||||
attachmentOssIds?: string;
|
||||
remark?: string;
|
||||
createTime: string;
|
||||
updateTime: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询宿管员权限内的学生假期留宿信息列表
|
||||
*/
|
||||
export function listHolidayStay(query?: HolidayStayQuery): AxiosPromise<any> {
|
||||
return request({
|
||||
url: '/studentDynamics/holidayStayList',
|
||||
method: 'get',
|
||||
params: query
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取假期留宿详细信息
|
||||
*/
|
||||
export function getHolidayStay(id: string | number): AxiosPromise<HolidayStayVO> {
|
||||
return request({
|
||||
url: '/studentDynamics/holidayStay/' + id,
|
||||
method: 'get'
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取假期留宿详细信息(别名)
|
||||
*/
|
||||
export const getHolidayStayInfo = getHolidayStay;
|
||||
|
||||
// ==================== 学生请假相关接口 ====================
|
||||
|
||||
export interface StudentLeaveQuery {
|
||||
pageNum?: number;
|
||||
pageSize?: number;
|
||||
studentName?: string;
|
||||
studentNo?: string;
|
||||
collegeName?: string;
|
||||
majorName?: string;
|
||||
className?: string;
|
||||
leaveTypeName?: string;
|
||||
approvalStatus?: string;
|
||||
startTime?: string;
|
||||
endTime?: string;
|
||||
params?: any;
|
||||
}
|
||||
|
||||
export interface StudentLeaveVO {
|
||||
id: string | number;
|
||||
studentId: string | number;
|
||||
studentName: string;
|
||||
studentNo: string;
|
||||
classId: string | number;
|
||||
className: string;
|
||||
majorId: string | number;
|
||||
majorName: string;
|
||||
collegeId: string | number;
|
||||
collegeName: string;
|
||||
leaveTypeCode: string;
|
||||
leaveTypeName: string;
|
||||
startTime: string;
|
||||
endTime: string;
|
||||
leaveDuration: number;
|
||||
isLeaveSchool: string;
|
||||
needCancel: string;
|
||||
emergencyContact: string;
|
||||
emergencyPhone: string;
|
||||
leaveReason: string;
|
||||
leaveLocation?: string;
|
||||
attachmentOssIds?: string;
|
||||
approvalStatus: string;
|
||||
approvalOpinion?: string;
|
||||
approvalTime?: string;
|
||||
approvalBy?: string | number;
|
||||
approvalByName?: string;
|
||||
cancelStatus?: string;
|
||||
cancelTime?: string;
|
||||
cancelLocation?: string;
|
||||
cancelPhotoOssIds?: string;
|
||||
cancelRemark?: string;
|
||||
createTime: string;
|
||||
updateTime: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询宿管员权限内的学生请假信息列表
|
||||
*/
|
||||
export function listStudentLeave(query?: StudentLeaveQuery): AxiosPromise<any> {
|
||||
return request({
|
||||
url: '/studentDynamics/studentLeaveList',
|
||||
method: 'get',
|
||||
params: query
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取学生请假详细信息
|
||||
*/
|
||||
export function getStudentLeave(id: string | number): AxiosPromise<StudentLeaveVO> {
|
||||
return request({
|
||||
url: '/studentDynamics/studentLeave/' + id,
|
||||
method: 'get'
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取学生请假详细信息(别名)
|
||||
*/
|
||||
export const getStudentLeaveInfo = getStudentLeave;
|
||||
|
||||
@@ -6,16 +6,17 @@ import { AxiosPromise } from 'axios';
|
||||
*/
|
||||
export interface XgSysUser {
|
||||
userId: number | string;
|
||||
userName: string;
|
||||
userName?: string;
|
||||
nickName: string;
|
||||
email: string;
|
||||
email?: string;
|
||||
phonenumber: string;
|
||||
sex: string;
|
||||
status: string;
|
||||
createTime: string;
|
||||
updateTime: string;
|
||||
deptId: number;
|
||||
deptName: string;
|
||||
sex?: string;
|
||||
status?: string;
|
||||
accountType?: string;
|
||||
createTime?: string;
|
||||
updateTime?: string;
|
||||
deptId?: number;
|
||||
deptName?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -27,3 +28,8 @@ export function getActiveUserList(): AxiosPromise<XgSysUser[]> {
|
||||
method: 'get'
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询有效用户列表(别名,用于兼容)
|
||||
*/
|
||||
export const activeList = getActiveUserList;
|
||||
@@ -106,3 +106,14 @@ export const getCollegeLeaveApprovers = (studentId: string | number): AxiosPromi
|
||||
method: 'get'
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 根据学生ID获取二级学院分管领导ID列表
|
||||
* @param studentId 学生ID
|
||||
*/
|
||||
export const getCollegeLeaders = (studentId: string | number): AxiosPromise<{ data: number[] }> => {
|
||||
return request({
|
||||
url: `/studentMsg/studentMsg/getCollegeLeaders/${studentId}`,
|
||||
method: 'get'
|
||||
});
|
||||
};
|
||||
@@ -33,6 +33,21 @@
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="message" label="审批意见" :show-overflow-tooltip="true" sortable align="center"></el-table-column>
|
||||
<el-table-column prop="signature" label="手写签名" width="150" align="center">
|
||||
<template #default="scope">
|
||||
<el-popover v-if="scope.row.signature" placement="left" :width="350" trigger="hover">
|
||||
<template #reference>
|
||||
<div class="signature-thumbnail">
|
||||
<img :src="scope.row.signature" alt="签名" class="signature-thumb-image" />
|
||||
</div>
|
||||
</template>
|
||||
<div class="signature-preview-large">
|
||||
<img :src="scope.row.signature" alt="签名" class="signature-large-image" />
|
||||
</div>
|
||||
</el-popover>
|
||||
<span v-else style="color: #909399; font-size: 12px">未签名</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="createTime" label="开始时间" width="160" :show-overflow-tooltip="true" sortable align="center"></el-table-column>
|
||||
<el-table-column prop="updateTime" label="结束时间" width="160" :show-overflow-tooltip="true" sortable align="center"></el-table-column>
|
||||
<el-table-column
|
||||
@@ -68,6 +83,7 @@
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref, getCurrentInstance, toRefs, type ComponentInternalInstance } from 'vue';
|
||||
import { flowHisTaskList } from '@/api/workflow/instance';
|
||||
import { propTypes } from '@/utils/propTypes';
|
||||
import { listByIds } from '@/api/system/oss';
|
||||
@@ -198,4 +214,48 @@ defineExpose({
|
||||
min-height: calc(100vh - 170px) !important;
|
||||
}
|
||||
}
|
||||
|
||||
// 列表中的签名缩略图容器
|
||||
.signature-thumbnail {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 4px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&:hover {
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
.signature-thumb-image {
|
||||
max-width: 120px;
|
||||
max-height: 50px;
|
||||
border: 1px solid #e4e7ed;
|
||||
border-radius: 4px;
|
||||
background: #ffffff;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||||
object-fit: contain;
|
||||
}
|
||||
}
|
||||
|
||||
// 鼠标悬停时的大图预览
|
||||
.signature-preview-large {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 16px;
|
||||
background: #f5f7fa;
|
||||
border-radius: 6px;
|
||||
|
||||
.signature-large-image {
|
||||
max-width: 100%;
|
||||
max-height: 200px;
|
||||
border: 2px solid #dcdfe6;
|
||||
border-radius: 4px;
|
||||
background: #ffffff;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
||||
object-fit: contain;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -31,6 +31,9 @@
|
||||
<el-form-item v-if="task.flowStatus === 'waiting'" label="审批意见">
|
||||
<el-input v-model="form.message" type="textarea" resize="none" />
|
||||
</el-form-item>
|
||||
<el-form-item v-if="task.flowStatus === 'waiting'" label="">
|
||||
<SignaturePad v-model="form.signature" :width="480" :height="180" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
@@ -107,6 +110,9 @@
|
||||
<el-form-item label="审批意见">
|
||||
<el-input v-model="backForm.message" type="textarea" resize="none" />
|
||||
</el-form-item>
|
||||
<el-form-item label="">
|
||||
<SignaturePad v-model="backForm.signature" :width="450" :height="180" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<div class="dialog-footer" style="float: right; padding-bottom: 20px">
|
||||
@@ -147,6 +153,7 @@ import {
|
||||
getNextNodeList
|
||||
} from '@/api/workflow/task';
|
||||
import UserSelect from '@/components/UserSelect';
|
||||
import SignaturePad from '@/components/SignaturePad/index.vue';
|
||||
|
||||
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
||||
import { UserVO } from '@/api/system/user/types';
|
||||
@@ -228,6 +235,7 @@ const deleteSignatureVisible = ref(false);
|
||||
const form = ref<Record<string, any>>({
|
||||
taskId: undefined,
|
||||
message: undefined,
|
||||
signature: undefined,
|
||||
assigneeMap: {},
|
||||
variables: {},
|
||||
messageType: ['1'],
|
||||
@@ -237,6 +245,7 @@ const backForm = ref<Record<string, any>>({
|
||||
taskId: undefined,
|
||||
nodeCode: undefined,
|
||||
message: undefined,
|
||||
signature: undefined,
|
||||
variables: {},
|
||||
messageType: ['1']
|
||||
});
|
||||
@@ -248,6 +257,7 @@ const openDialog = async (id?: string) => {
|
||||
form.value.fileId = undefined;
|
||||
taskId.value = id;
|
||||
form.value.message = undefined;
|
||||
form.value.signature = undefined;
|
||||
dialog.visible = true;
|
||||
loading.value = true;
|
||||
buttonDisabled.value = true;
|
||||
@@ -322,6 +332,7 @@ const handleCompleteTask = async () => {
|
||||
const handleBackProcessOpen = async () => {
|
||||
backForm.value = {};
|
||||
backForm.value.messageType = ['1'];
|
||||
backForm.value.signature = undefined;
|
||||
backVisible.value = true;
|
||||
backLoading.value = true;
|
||||
backButtonDisabled.value = true;
|
||||
|
||||
@@ -0,0 +1,278 @@
|
||||
<template>
|
||||
<div class="signature-pad-wrapper">
|
||||
<div class="signature-header">
|
||||
<span class="signature-title">手写签名</span>
|
||||
<el-button v-if="hasSignature" size="small" type="danger" :icon="Delete" @click="handleClear">清空</el-button>
|
||||
</div>
|
||||
<div class="signature-canvas-container">
|
||||
<canvas
|
||||
ref="canvasRef"
|
||||
class="signature-canvas"
|
||||
@mousedown="handleMouseDown"
|
||||
@mousemove="handleMouseMove"
|
||||
@mouseup="handleMouseUp"
|
||||
@mouseleave="handleMouseUp"
|
||||
@touchstart="handleTouchStart"
|
||||
@touchmove="handleTouchMove"
|
||||
@touchend="handleTouchEnd"
|
||||
></canvas>
|
||||
<div v-if="!hasSignature" class="signature-placeholder">请在此处签名</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, onUnmounted, watch, nextTick } from 'vue';
|
||||
import { Delete } from '@element-plus/icons-vue';
|
||||
|
||||
interface Props {
|
||||
modelValue?: string; // Base64签名数据
|
||||
width?: number;
|
||||
height?: number;
|
||||
lineWidth?: number;
|
||||
lineColor?: string;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
modelValue: '',
|
||||
width: 500,
|
||||
height: 200,
|
||||
lineWidth: 2,
|
||||
lineColor: '#000000'
|
||||
});
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:modelValue', value: string): void;
|
||||
}>();
|
||||
|
||||
const canvasRef = ref<HTMLCanvasElement>();
|
||||
const hasSignature = ref(false);
|
||||
const isDrawing = ref(false);
|
||||
let ctx: CanvasRenderingContext2D | null = null;
|
||||
let resizeObserver: ResizeObserver | null = null;
|
||||
|
||||
onMounted(() => {
|
||||
initCanvas();
|
||||
// 如果有初始值,加载到画布上
|
||||
if (props.modelValue) {
|
||||
loadSignature(props.modelValue);
|
||||
}
|
||||
|
||||
// 监听容器大小变化
|
||||
if (canvasRef.value?.parentElement) {
|
||||
resizeObserver = new ResizeObserver(() => {
|
||||
handleResize();
|
||||
});
|
||||
resizeObserver.observe(canvasRef.value.parentElement);
|
||||
}
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
if (resizeObserver) {
|
||||
resizeObserver.disconnect();
|
||||
}
|
||||
});
|
||||
|
||||
// 监听外部值变化
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
(newVal) => {
|
||||
if (newVal && !hasSignature.value) {
|
||||
loadSignature(newVal);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const initCanvas = () => {
|
||||
if (!canvasRef.value) return;
|
||||
|
||||
const canvas = canvasRef.value;
|
||||
const container = canvas.parentElement;
|
||||
|
||||
// 获取容器的实际宽度
|
||||
const containerWidth = container?.clientWidth || props.width;
|
||||
|
||||
// 设置画布的物理尺寸(实际像素)
|
||||
canvas.width = containerWidth;
|
||||
canvas.height = props.height;
|
||||
|
||||
// 同时设置CSS样式,确保显示尺寸与物理尺寸一致
|
||||
canvas.style.width = `${containerWidth}px`;
|
||||
canvas.style.height = `${props.height}px`;
|
||||
|
||||
ctx = canvas.getContext('2d');
|
||||
if (ctx) {
|
||||
ctx.strokeStyle = props.lineColor;
|
||||
ctx.lineWidth = props.lineWidth;
|
||||
ctx.lineCap = 'round';
|
||||
ctx.lineJoin = 'round';
|
||||
}
|
||||
};
|
||||
|
||||
// 处理容器大小变化
|
||||
const handleResize = () => {
|
||||
if (!canvasRef.value) return;
|
||||
|
||||
// 保存当前签名
|
||||
const currentSignature = hasSignature.value ? canvasRef.value.toDataURL('image/png') : '';
|
||||
|
||||
// 重新初始化画布
|
||||
initCanvas();
|
||||
|
||||
// 恢复签名
|
||||
if (currentSignature && hasSignature.value) {
|
||||
nextTick(() => {
|
||||
loadSignature(currentSignature);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const startDrawing = (x: number, y: number) => {
|
||||
if (!ctx || !canvasRef.value) return;
|
||||
|
||||
isDrawing.value = true;
|
||||
const rect = canvasRef.value.getBoundingClientRect();
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(x - rect.left, y - rect.top);
|
||||
};
|
||||
|
||||
const draw = (x: number, y: number) => {
|
||||
if (!isDrawing.value || !ctx || !canvasRef.value) return;
|
||||
|
||||
const rect = canvasRef.value.getBoundingClientRect();
|
||||
ctx.lineTo(x - rect.left, y - rect.top);
|
||||
ctx.stroke();
|
||||
hasSignature.value = true;
|
||||
};
|
||||
|
||||
const stopDrawing = () => {
|
||||
if (!isDrawing.value) return;
|
||||
|
||||
isDrawing.value = false;
|
||||
if (ctx) {
|
||||
ctx.closePath();
|
||||
}
|
||||
|
||||
// 导出签名数据
|
||||
if (hasSignature.value && canvasRef.value) {
|
||||
const signatureData = canvasRef.value.toDataURL('image/png');
|
||||
emit('update:modelValue', signatureData);
|
||||
}
|
||||
};
|
||||
|
||||
// 鼠标事件处理
|
||||
const handleMouseDown = (e: MouseEvent) => {
|
||||
startDrawing(e.clientX, e.clientY);
|
||||
};
|
||||
|
||||
const handleMouseMove = (e: MouseEvent) => {
|
||||
draw(e.clientX, e.clientY);
|
||||
};
|
||||
|
||||
const handleMouseUp = () => {
|
||||
stopDrawing();
|
||||
};
|
||||
|
||||
// 触摸事件处理(移动端支持)
|
||||
const handleTouchStart = (e: TouchEvent) => {
|
||||
e.preventDefault();
|
||||
const touch = e.touches[0];
|
||||
startDrawing(touch.clientX, touch.clientY);
|
||||
};
|
||||
|
||||
const handleTouchMove = (e: TouchEvent) => {
|
||||
e.preventDefault();
|
||||
const touch = e.touches[0];
|
||||
draw(touch.clientX, touch.clientY);
|
||||
};
|
||||
|
||||
const handleTouchEnd = (e: TouchEvent) => {
|
||||
e.preventDefault();
|
||||
stopDrawing();
|
||||
};
|
||||
|
||||
// 清空签名
|
||||
const handleClear = () => {
|
||||
if (!canvasRef.value || !ctx) return;
|
||||
|
||||
ctx.clearRect(0, 0, canvasRef.value.width, canvasRef.value.height);
|
||||
hasSignature.value = false;
|
||||
emit('update:modelValue', '');
|
||||
};
|
||||
|
||||
// 加载签名
|
||||
const loadSignature = (base64Data: string) => {
|
||||
if (!canvasRef.value || !ctx) return;
|
||||
|
||||
const img = new Image();
|
||||
img.onload = () => {
|
||||
ctx!.clearRect(0, 0, canvasRef.value!.width, canvasRef.value!.height);
|
||||
ctx!.drawImage(img, 0, 0);
|
||||
hasSignature.value = true;
|
||||
};
|
||||
img.src = base64Data;
|
||||
};
|
||||
|
||||
// 获取签名数据
|
||||
const getSignature = (): string => {
|
||||
if (!canvasRef.value || !hasSignature.value) return '';
|
||||
return canvasRef.value.toDataURL('image/png');
|
||||
};
|
||||
|
||||
// 对外暴露方法
|
||||
defineExpose({
|
||||
clear: handleClear,
|
||||
getSignature,
|
||||
hasSignature: () => hasSignature.value
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.signature-pad-wrapper {
|
||||
width: 100%;
|
||||
|
||||
.signature-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 8px;
|
||||
|
||||
.signature-title {
|
||||
font-size: 14px;
|
||||
color: #606266;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
|
||||
.signature-canvas-container {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
border: 1px solid #dcdfe6;
|
||||
border-radius: 4px;
|
||||
background-color: #ffffff;
|
||||
overflow: hidden;
|
||||
|
||||
&:hover {
|
||||
border-color: #c0c4cc;
|
||||
}
|
||||
|
||||
.signature-canvas {
|
||||
display: block;
|
||||
width: 100%;
|
||||
cursor: crosshair;
|
||||
touch-action: none;
|
||||
}
|
||||
|
||||
.signature-placeholder {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
color: #c0c4cc;
|
||||
font-size: 14px;
|
||||
pointer-events: none;
|
||||
user-select: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
+37
-25
@@ -47,7 +47,7 @@
|
||||
<img :src="menu.logoUrl" :alt="menu.title" class="menu-logo" />
|
||||
</div>
|
||||
<span class="action-title" :class="{ 'disabled': !menu.available }">{{ menu.title }}</span>
|
||||
<span class="action-desc" :class="{ 'disabled': !menu.available }">{{ menu.description }}</span>
|
||||
<!-- <span class="action-desc" :class="{ 'disabled': !menu.available }">{{ menu.description }}</span> -->
|
||||
<!-- 移除层级路径显示 -->
|
||||
<el-tag v-if="!menu.available" type="info" size="small" class="status-tag">开发中</el-tag>
|
||||
</div>
|
||||
@@ -946,24 +946,36 @@ onMounted(async () => {
|
||||
}
|
||||
|
||||
.menu-card-wrapper {
|
||||
flex: 0 0 calc(20% - 16px); // 每行5个,减去间隔
|
||||
min-width: 180px;
|
||||
flex: 0 0 calc((100% - 140px) / 8); // 每行8个,精确计算:(100% - 7个间隔*20px) / 8
|
||||
min-width: 100px;
|
||||
margin-bottom: 20px;
|
||||
|
||||
@media (max-width: 1600px) {
|
||||
flex: 0 0 calc((100% - 120px) / 7); // 中大屏幕每行7个
|
||||
}
|
||||
|
||||
@media (max-width: 1400px) {
|
||||
flex: 0 0 calc(25% - 15px); // 中等屏幕每行4个
|
||||
flex: 0 0 calc((100% - 100px) / 6); // 中等屏幕每行6个
|
||||
}
|
||||
|
||||
@media (max-width: 1200px) {
|
||||
flex: 0 0 calc(33.333% - 14px); // 小屏幕每行3个
|
||||
flex: 0 0 calc((100% - 80px) / 5); // 小屏幕每行5个
|
||||
}
|
||||
|
||||
@media (max-width: 992px) {
|
||||
flex: 0 0 calc((100% - 60px) / 4); // 平板每行4个
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
flex: 0 0 calc(50% - 10px); // 移动端每行2个
|
||||
flex: 0 0 calc((100% - 40px) / 3); // 移动端每行3个
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
flex: 0 0 calc((100% - 20px) / 2); // 小手机每行2个
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
flex: 0 0 100%; // 超小屏幕每行1个
|
||||
flex: 0 0 calc(100% - 20px); // 超小屏幕每行1个
|
||||
}
|
||||
}
|
||||
|
||||
@@ -972,7 +984,7 @@ onMounted(async () => {
|
||||
border: none;
|
||||
transition: all 0.3s ease;
|
||||
cursor: pointer;
|
||||
height: 140px;
|
||||
height: 110px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
@@ -995,7 +1007,7 @@ onMounted(async () => {
|
||||
|
||||
.menu-action {
|
||||
text-align: center;
|
||||
padding: 15px 10px;
|
||||
padding: 10px 8px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@@ -1011,15 +1023,15 @@ onMounted(async () => {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin-bottom: 12px;
|
||||
height: 48px;
|
||||
margin-bottom: 8px;
|
||||
height: 36px;
|
||||
}
|
||||
|
||||
.menu-logo {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
object-fit: contain;
|
||||
border-radius: 8px;
|
||||
border-radius: 6px;
|
||||
transition: all 0.3s ease;
|
||||
filter: drop-shadow(0 2px 8px rgba(0, 0, 0, 0.1));
|
||||
|
||||
@@ -1041,16 +1053,16 @@ onMounted(async () => {
|
||||
}
|
||||
|
||||
.action-title {
|
||||
font-size: 15px;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin-bottom: 6px;
|
||||
margin-bottom: 4px;
|
||||
display: block;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.action-desc {
|
||||
font-size: 12px;
|
||||
font-size: 11px;
|
||||
color: #666;
|
||||
display: block;
|
||||
line-height: 1.4;
|
||||
@@ -1140,29 +1152,29 @@ onMounted(async () => {
|
||||
}
|
||||
|
||||
.menu-action-card {
|
||||
height: 120px;
|
||||
height: 100px;
|
||||
}
|
||||
|
||||
.menu-action {
|
||||
padding: 10px 5px;
|
||||
padding: 8px 5px;
|
||||
}
|
||||
|
||||
.menu-logo-wrapper {
|
||||
height: 36px;
|
||||
margin-bottom: 8px;
|
||||
height: 30px;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.menu-logo {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
}
|
||||
|
||||
.action-title {
|
||||
font-size: 14px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.action-desc {
|
||||
font-size: 11px;
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.loading-state {
|
||||
|
||||
@@ -0,0 +1,322 @@
|
||||
<template>
|
||||
<div class="p-2">
|
||||
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
|
||||
<div v-show="showSearch" class="mb-[10px]">
|
||||
<el-card shadow="hover">
|
||||
<el-form ref="queryFormRef" :model="queryParams" label-width="100px" :inline="true">
|
||||
<el-form-item label="学院名称" prop="collegeName">
|
||||
<el-input v-model="queryParams.collegeName" placeholder="请输入学院名称" clearable @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
<el-form-item label="领导姓名" prop="leaderName">
|
||||
<el-input v-model="queryParams.leaderName" placeholder="请输入领导姓名" clearable @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
|
||||
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
</div>
|
||||
</transition>
|
||||
|
||||
<el-card shadow="never">
|
||||
<template #header>
|
||||
<el-row :gutter="10" class="mb8">
|
||||
<el-col :span="1.5">
|
||||
<el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['xg:ejxyfgld:add']">新增</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['xg:ejxyfgld:remove']"
|
||||
>删除</el-button
|
||||
>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['xg:ejxyfgld:export']">导出</el-button>
|
||||
</el-col>
|
||||
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
|
||||
</el-row>
|
||||
</template>
|
||||
|
||||
<el-table v-loading="loading" border :data="collegeLeaderList" @selection-change="handleSelectionChange">
|
||||
<el-table-column type="selection" width="55" align="center" />
|
||||
<el-table-column label="学院名称" align="center" prop="collegeName" />
|
||||
<el-table-column label="领导姓名" align="center" prop="leaderName" />
|
||||
<el-table-column label="备注" align="center" prop="remark" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="创建时间" align="center" prop="createTime" width="180" />
|
||||
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="180">
|
||||
<template #default="scope">
|
||||
<el-tooltip content="修改" placement="top">
|
||||
<el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['xg:ejxyfgld:edit']"></el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="删除" placement="top">
|
||||
<el-button link type="danger" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['xg:ejxyfgld:remove']"></el-button>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
|
||||
</el-card>
|
||||
|
||||
<!-- 添加或修改二级学院分管领导对话框 -->
|
||||
<el-dialog :title="dialog.title" v-model="dialog.visible" width="600px" append-to-body>
|
||||
<el-form ref="collegeLeaderFormRef" :model="form" :rules="rules" label-width="120px">
|
||||
<el-form-item label="二级学院" prop="collegeId">
|
||||
<el-select v-model="form.collegeId" placeholder="请选择二级学院" clearable filterable style="width: 100%" @change="handleCollegeChange">
|
||||
<el-option v-for="college in collegeList" :key="college.deptId" :label="college.deptName" :value="college.deptId" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="分管领导" prop="leaderId">
|
||||
<el-select v-model="form.leaderId" placeholder="请选择分管领导" clearable filterable style="width: 100%" @change="handleLeaderChange">
|
||||
<el-option v-for="user in teacherList" :key="user.userId" :label="`${user.nickName}(${user.userName})`" :value="user.userId" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="备注" prop="remark">
|
||||
<el-input v-model="form.remark" type="textarea" placeholder="请输入备注信息" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button :loading="buttonLoading" type="primary" @click="submitForm">确 定</el-button>
|
||||
<el-button @click="cancel">取 消</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup name="CollegeLeader" lang="ts">
|
||||
import { ref, reactive, toRefs, getCurrentInstance } from 'vue';
|
||||
import type { FormInstance } from 'element-plus';
|
||||
import {
|
||||
listCollegeLeader,
|
||||
getCollegeLeader,
|
||||
addCollegeLeader,
|
||||
updateCollegeLeader,
|
||||
delCollegeLeader,
|
||||
exportCollegeLeader,
|
||||
type CollegeLeaderBo,
|
||||
type CollegeLeaderVo
|
||||
} from '@/api/xg/ejxyfgld';
|
||||
import { list as listCollege } from '@/api/xg/college';
|
||||
import { activeList as listTeacher } from '@/api/xg/sysUser';
|
||||
|
||||
interface ComponentInternalInstance {
|
||||
proxy?: any;
|
||||
}
|
||||
|
||||
interface DialogOption {
|
||||
visible: boolean;
|
||||
title: string;
|
||||
}
|
||||
|
||||
interface QueryParams {
|
||||
pageNum: number;
|
||||
pageSize: number;
|
||||
collegeName?: string;
|
||||
leaderName?: string;
|
||||
}
|
||||
|
||||
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
||||
|
||||
const collegeLeaderList = ref<CollegeLeaderVo[]>([]);
|
||||
const buttonLoading = ref(false);
|
||||
const loading = ref(true);
|
||||
const showSearch = ref(true);
|
||||
const ids = ref<Array<string | number>>([]);
|
||||
const single = ref(true);
|
||||
const multiple = ref(true);
|
||||
const total = ref(0);
|
||||
|
||||
// 学院列表和教师列表
|
||||
const collegeList = ref<any[]>([]);
|
||||
const teacherList = ref<any[]>([]);
|
||||
|
||||
const queryFormRef = ref<FormInstance>();
|
||||
const collegeLeaderFormRef = ref<FormInstance>();
|
||||
|
||||
const dialog = reactive<DialogOption>({
|
||||
visible: false,
|
||||
title: ''
|
||||
});
|
||||
|
||||
const initFormData: CollegeLeaderBo = {
|
||||
id: undefined,
|
||||
collegeId: undefined,
|
||||
collegeName: undefined,
|
||||
leaderId: undefined,
|
||||
leaderName: undefined,
|
||||
remark: undefined
|
||||
};
|
||||
|
||||
const data = reactive({
|
||||
form: { ...initFormData },
|
||||
queryParams: {
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
collegeName: undefined,
|
||||
leaderName: undefined
|
||||
} as QueryParams,
|
||||
rules: {
|
||||
collegeId: [{ required: true, message: '二级学院不能为空', trigger: 'change' }],
|
||||
leaderId: [{ required: true, message: '分管领导不能为空', trigger: 'change' }]
|
||||
}
|
||||
});
|
||||
|
||||
const { queryParams, form, rules } = toRefs(data);
|
||||
|
||||
/** 查询二级学院分管领导列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true;
|
||||
try {
|
||||
const res = await listCollegeLeader(queryParams.value);
|
||||
collegeLeaderList.value = res.rows;
|
||||
total.value = res.total;
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
/** 加载学院列表 */
|
||||
const loadCollegeList = async () => {
|
||||
try {
|
||||
const res = await listCollege();
|
||||
collegeList.value = res.data || [];
|
||||
} catch (error) {
|
||||
console.error('加载学院列表失败:', error);
|
||||
}
|
||||
};
|
||||
|
||||
/** 加载教师列表 */
|
||||
const loadTeacherList = async () => {
|
||||
try {
|
||||
const res = await listTeacher();
|
||||
// 后端已经过滤出教职工(account_type = 1),直接使用
|
||||
teacherList.value = res.data || [];
|
||||
console.log('加载教师列表成功,共', teacherList.value.length, '条数据');
|
||||
} catch (error) {
|
||||
console.error('加载教师列表失败:', error);
|
||||
}
|
||||
};
|
||||
|
||||
/** 学院选择变更 */
|
||||
const handleCollegeChange = (val: any) => {
|
||||
const college = collegeList.value.find((c: any) => c.deptId === val);
|
||||
if (college) {
|
||||
form.value.collegeName = college.deptName;
|
||||
}
|
||||
};
|
||||
|
||||
/** 领导选择变更 */
|
||||
const handleLeaderChange = (val: any) => {
|
||||
const teacher = teacherList.value.find((t: any) => t.userId === val);
|
||||
if (teacher) {
|
||||
form.value.leaderName = teacher.nickName;
|
||||
}
|
||||
};
|
||||
|
||||
/** 取消按钮 */
|
||||
const cancel = () => {
|
||||
reset();
|
||||
dialog.visible = false;
|
||||
};
|
||||
|
||||
/** 表单重置 */
|
||||
const reset = () => {
|
||||
form.value = { ...initFormData };
|
||||
collegeLeaderFormRef.value?.resetFields();
|
||||
};
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
queryParams.value.pageNum = 1;
|
||||
getList();
|
||||
};
|
||||
|
||||
/** 重置按钮操作 */
|
||||
const resetQuery = () => {
|
||||
queryFormRef.value?.resetFields();
|
||||
handleQuery();
|
||||
};
|
||||
|
||||
/** 多选框选中数据 */
|
||||
const handleSelectionChange = (selection: CollegeLeaderVo[]) => {
|
||||
ids.value = selection.map((item) => item.id);
|
||||
single.value = selection.length !== 1;
|
||||
multiple.value = !selection.length;
|
||||
};
|
||||
|
||||
/** 新增按钮操作 */
|
||||
const handleAdd = async () => {
|
||||
reset();
|
||||
await loadCollegeList();
|
||||
await loadTeacherList();
|
||||
dialog.visible = true;
|
||||
dialog.title = '添加二级学院分管领导';
|
||||
};
|
||||
|
||||
/** 修改按钮操作 */
|
||||
const handleUpdate = async (row?: CollegeLeaderVo) => {
|
||||
reset();
|
||||
await loadCollegeList();
|
||||
await loadTeacherList();
|
||||
const id = row?.id || ids.value[0];
|
||||
const res = await getCollegeLeader(id);
|
||||
Object.assign(form.value, res.data);
|
||||
dialog.visible = true;
|
||||
dialog.title = '修改二级学院分管领导';
|
||||
};
|
||||
|
||||
/** 提交按钮 */
|
||||
const submitForm = () => {
|
||||
collegeLeaderFormRef.value?.validate(async (valid: boolean) => {
|
||||
if (valid) {
|
||||
buttonLoading.value = true;
|
||||
try {
|
||||
if (form.value.id) {
|
||||
await updateCollegeLeader(form.value);
|
||||
proxy?.$modal.msgSuccess('修改成功');
|
||||
} else {
|
||||
await addCollegeLeader(form.value);
|
||||
proxy?.$modal.msgSuccess('新增成功');
|
||||
}
|
||||
dialog.visible = false;
|
||||
await getList();
|
||||
} catch (error) {
|
||||
console.error('提交失败:', error);
|
||||
} finally {
|
||||
buttonLoading.value = false;
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/** 删除按钮操作 */
|
||||
const handleDelete = async (row?: CollegeLeaderVo) => {
|
||||
const deleteIds = row?.id ? [row.id] : ids.value;
|
||||
await proxy?.$modal.confirm('是否确认删除选中的配置?');
|
||||
try {
|
||||
await delCollegeLeader(deleteIds);
|
||||
await getList();
|
||||
proxy?.$modal.msgSuccess('删除成功');
|
||||
} catch (error) {
|
||||
console.error('删除失败:', error);
|
||||
}
|
||||
};
|
||||
|
||||
/** 导出按钮操作 */
|
||||
const handleExport = () => {
|
||||
proxy?.$download(
|
||||
'xg/ejxyfgld/export',
|
||||
{
|
||||
...queryParams.value
|
||||
},
|
||||
`二级学院分管领导_${new Date().getTime()}.xlsx`
|
||||
);
|
||||
};
|
||||
|
||||
// 初始化
|
||||
getList();
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss"></style>
|
||||
@@ -0,0 +1,256 @@
|
||||
<template>
|
||||
<el-dialog :title="dialogTitle" v-model="dialogOpen" width="800px" append-to-body @close="cancel">
|
||||
<el-form ref="formRef" :model="form" :rules="rules" label-width="120px" v-loading="loading">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="申请名称:">
|
||||
<span>{{ form.applicationName }}</span>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="申请类型:">
|
||||
<dict-tag :options="dorm_application_type" :value="form.applicationType" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="学生姓名:">
|
||||
<span>{{ form.studentName }}</span>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="学号:">
|
||||
<span>{{ form.studentNo }}</span>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="学院:">
|
||||
<span>{{ form.collegeName }}</span>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="专业:">
|
||||
<span>{{ form.majorName }}</span>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="班级:">
|
||||
<span>{{ form.className }}</span>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="联系电话:">
|
||||
<span>{{ form.studentPhone }}</span>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="24">
|
||||
<el-form-item label="当前宿舍:">
|
||||
<span>{{ form.currentCampusName }} {{ form.currentBuildingName }} {{ form.currentRoomNo }} {{ form.currentBedNo }}</span>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="20" v-if="form.applicationType === '1'">
|
||||
<el-col :span="24">
|
||||
<el-form-item label="目标宿舍:">
|
||||
<span>{{ form.targetCampusName }} {{ form.targetBuildingName }} {{ form.targetRoomNo }} {{ form.targetBedNo }}</span>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="申请时间:">
|
||||
<span>{{ form.applyTime }}</span>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="审批状态:">
|
||||
<dict-tag :options="student_leave_approval_status" :value="form.applicationStatus" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="24">
|
||||
<el-form-item label="申请原因:" prop="reasonDetail">
|
||||
<el-input
|
||||
v-if="isEditable"
|
||||
v-model="form.reasonDetail"
|
||||
type="textarea"
|
||||
:rows="3"
|
||||
placeholder="请输入申请原因"
|
||||
maxlength="500"
|
||||
show-word-limit
|
||||
/>
|
||||
<span v-else>{{ form.reasonDetail || '无' }}</span>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="24">
|
||||
<el-form-item label="备注信息:" prop="remark">
|
||||
<el-input
|
||||
v-if="isEditable"
|
||||
v-model="form.remark"
|
||||
type="textarea"
|
||||
:rows="2"
|
||||
placeholder="请输入备注信息"
|
||||
maxlength="200"
|
||||
show-word-limit
|
||||
/>
|
||||
<span v-else>{{ form.remark || '无' }}</span>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="cancel">{{ isEditable ? '取消' : '关闭' }}</el-button>
|
||||
<el-button v-if="isEditable" type="primary" @click="submitForm" :loading="submitLoading">保存</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="DormApplicationDetail">
|
||||
import { getCurrentInstance, ref, toRefs, computed, type ComponentInternalInstance } from 'vue';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { getDormApplicationInfo, updateDormApplication, type DormApplicationVO } from '@/api/xg/studentDynamics';
|
||||
|
||||
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
||||
const { dorm_application_type, student_leave_approval_status } = toRefs(
|
||||
proxy?.useDict('dorm_application_type', 'student_leave_approval_status') || {}
|
||||
);
|
||||
|
||||
const emit = defineEmits(['refresh']);
|
||||
|
||||
// 弹窗状态
|
||||
const dialogOpen = ref(false);
|
||||
const loading = ref(false);
|
||||
const submitLoading = ref(false);
|
||||
|
||||
// 表单数据
|
||||
const form = ref<DormApplicationVO>({} as DormApplicationVO);
|
||||
|
||||
// 表单引用
|
||||
const formRef = ref();
|
||||
|
||||
// 表单校验规则
|
||||
const rules = {
|
||||
reasonDetail: [{ required: true, message: '请输入申请原因', trigger: 'blur' }]
|
||||
};
|
||||
|
||||
/**
|
||||
* 是否可编辑
|
||||
* 草稿、已撤销、已退回状态可编辑
|
||||
*/
|
||||
const isEditable = computed(() => {
|
||||
return ['draft', 'cancel', 'back'].includes(form.value.applicationStatus || '');
|
||||
});
|
||||
|
||||
/**
|
||||
* 弹窗标题
|
||||
*/
|
||||
const dialogTitle = computed(() => {
|
||||
return isEditable.value ? '修改调宿/退宿申请' : '调宿/退宿申请详情';
|
||||
});
|
||||
|
||||
/**
|
||||
* 打开弹窗
|
||||
*/
|
||||
const openDialog = (id: number) => {
|
||||
reset();
|
||||
getDetail(id);
|
||||
dialogOpen.value = true;
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取详情数据
|
||||
*/
|
||||
const getDetail = async (id: number) => {
|
||||
loading.value = true;
|
||||
try {
|
||||
const response = await getDormApplicationInfo(id);
|
||||
form.value = response.data;
|
||||
} catch (error) {
|
||||
console.error('获取调宿退宿申请详情失败:', error);
|
||||
ElMessage.error('获取详情失败');
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 重置表单
|
||||
*/
|
||||
const reset = () => {
|
||||
form.value = {} as DormApplicationVO;
|
||||
formRef.value?.resetFields();
|
||||
};
|
||||
|
||||
/**
|
||||
* 提交表单
|
||||
*/
|
||||
const submitForm = async () => {
|
||||
if (!formRef.value) return;
|
||||
|
||||
await formRef.value.validate(async (valid: boolean) => {
|
||||
if (!valid) return;
|
||||
|
||||
submitLoading.value = true;
|
||||
try {
|
||||
await updateDormApplication(form.value);
|
||||
ElMessage.success('保存成功');
|
||||
dialogOpen.value = false;
|
||||
emit('refresh');
|
||||
} catch (error) {
|
||||
console.error('保存失败:', error);
|
||||
ElMessage.error('保存失败');
|
||||
} finally {
|
||||
submitLoading.value = false;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 取消操作
|
||||
*/
|
||||
const cancel = () => {
|
||||
dialogOpen.value = false;
|
||||
reset();
|
||||
};
|
||||
|
||||
// 暴露方法
|
||||
defineExpose({
|
||||
open: openDialog
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.dialog-footer {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
:deep(.el-form-item__label) {
|
||||
font-weight: 600;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
:deep(.el-form-item__content) {
|
||||
color: #303133;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,258 @@
|
||||
<template>
|
||||
<div class="page-container">
|
||||
<!-- 搜索表单 -->
|
||||
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
|
||||
<div v-show="showSearch" style="margin-bottom: 10px">
|
||||
<el-card shadow="hover">
|
||||
<el-form ref="queryFormRef" :model="queryParams" label-width="100px">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="6">
|
||||
<el-form-item label="学生姓名" prop="studentName">
|
||||
<el-input v-model="queryParams.studentName" placeholder="请输入学生姓名" clearable @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="学号" prop="studentNo">
|
||||
<el-input v-model="queryParams.studentNo" placeholder="请输入学号" clearable @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="学院名称" prop="collegeName">
|
||||
<el-input v-model="queryParams.collegeName" placeholder="请输入学院名称" clearable @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="专业名称" prop="majorName">
|
||||
<el-input v-model="queryParams.majorName" placeholder="请输入专业名称" clearable @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="6">
|
||||
<el-form-item label="班级名称" prop="className">
|
||||
<el-input v-model="queryParams.className" placeholder="请输入班级名称" clearable @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="申请类型" prop="applicationType">
|
||||
<el-select v-model="queryParams.applicationType" placeholder="请选择申请类型" clearable style="width: 100%">
|
||||
<el-option v-for="dict in dorm_application_type" :key="dict.value" :label="dict.label" :value="dict.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="申请时间" prop="applyTime">
|
||||
<el-date-picker
|
||||
clearable
|
||||
v-model="queryParams.applyTime"
|
||||
type="date"
|
||||
style="width: 100%"
|
||||
value-format="YYYY-MM-DD"
|
||||
placeholder="请选择申请时间"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="审批状态" prop="applicationStatus">
|
||||
<el-select v-model="queryParams.applicationStatus" placeholder="请选择审批状态" clearable style="width: 100%">
|
||||
<el-option v-for="dict in student_leave_approval_status" :key="dict.value" :label="dict.label" :value="dict.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-col :span="24" class="search-btn-container">
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
|
||||
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
</el-card>
|
||||
</div>
|
||||
</transition>
|
||||
|
||||
<!-- 数据表格 -->
|
||||
<el-card shadow="never">
|
||||
<template #header>
|
||||
<el-row :gutter="10" class="mb8">
|
||||
<el-col :span="1.5">
|
||||
<span class="card-title">调宿/退宿申请列表</span>
|
||||
</el-col>
|
||||
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
|
||||
</el-row>
|
||||
</template>
|
||||
|
||||
<el-table v-loading="loading" :data="dataList" @selection-change="handleSelectionChange">
|
||||
<el-table-column label="学生姓名" align="center" prop="studentName" />
|
||||
<el-table-column label="学号" align="center" prop="studentNo" />
|
||||
<el-table-column label="学院" align="center" prop="collegeName" show-overflow-tooltip />
|
||||
<el-table-column label="专业" align="center" prop="majorName" show-overflow-tooltip />
|
||||
<el-table-column label="班级" align="center" prop="className" show-overflow-tooltip />
|
||||
<el-table-column label="申请类型" align="center">
|
||||
<template #default="scope">
|
||||
<dict-tag :options="dorm_application_type" :value="scope.row.applicationType" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="当前宿舍" align="center" show-overflow-tooltip>
|
||||
<template #default="scope"> {{ scope.row.currentBuildingName }}{{ scope.row.currentRoomNo }}{{ scope.row.currentBedNo }} </template>
|
||||
</el-table-column>
|
||||
<el-table-column label="目标宿舍" align="center" show-overflow-tooltip>
|
||||
<template #default="scope">
|
||||
<span v-if="scope.row.applicationType === '1'">
|
||||
{{ scope.row.targetBuildingName }}{{ scope.row.targetRoomNo }}{{ scope.row.targetBedNo }}
|
||||
</span>
|
||||
<span v-else>—</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="审批状态" align="center">
|
||||
<template #default="scope">
|
||||
<dict-tag :options="student_leave_approval_status" :value="scope.row.applicationStatus" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="申请时间" align="center" prop="applyTime" width="180" />
|
||||
<el-table-column label="操作" align="center" width="150" fixed="right">
|
||||
<template #default="scope">
|
||||
<el-tooltip content="查看" placement="top">
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
icon="View"
|
||||
@click="handleView(scope.row)"
|
||||
v-hasPermi="['studentDynamics:dormApplication:query']"
|
||||
></el-button>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="DormApplicationTab">
|
||||
import { getCurrentInstance, ref, onMounted, toRefs, type ComponentInternalInstance } from 'vue';
|
||||
import { listDormApplication, type DormApplicationQuery } from '@/api/xg/studentDynamics';
|
||||
|
||||
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
||||
const { dorm_application_type, student_leave_approval_status } = toRefs(
|
||||
proxy?.useDict('dorm_application_type', 'student_leave_approval_status') || {}
|
||||
);
|
||||
|
||||
// 数据状态
|
||||
const loading = ref(true);
|
||||
const showSearch = ref(true);
|
||||
const dataList = ref([]);
|
||||
const total = ref(0);
|
||||
const ids = ref<Array<string | number>>([]);
|
||||
const single = ref(true);
|
||||
const multiple = ref(true);
|
||||
|
||||
// 查询参数
|
||||
const queryParams = ref<DormApplicationQuery & any>({
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
studentName: undefined,
|
||||
studentNo: undefined,
|
||||
collegeName: undefined,
|
||||
majorName: undefined,
|
||||
className: undefined,
|
||||
applicationType: undefined,
|
||||
applicationStatus: undefined,
|
||||
applyTime: undefined
|
||||
});
|
||||
|
||||
// 表单引用
|
||||
const queryFormRef = ref();
|
||||
|
||||
/**
|
||||
* 查询列表数据
|
||||
*/
|
||||
const getList = async () => {
|
||||
loading.value = true;
|
||||
try {
|
||||
const response = await listDormApplication(queryParams.value);
|
||||
dataList.value = response.rows;
|
||||
total.value = response.total;
|
||||
} catch (error) {
|
||||
console.error('获取调宿退宿申请列表失败:', error);
|
||||
proxy?.$modal.msgError('获取数据失败');
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 搜索按钮操作
|
||||
*/
|
||||
const handleQuery = () => {
|
||||
queryParams.value.pageNum = 1;
|
||||
getList();
|
||||
};
|
||||
|
||||
/**
|
||||
* 重置按钮操作
|
||||
*/
|
||||
const resetQuery = () => {
|
||||
queryFormRef.value?.resetFields();
|
||||
handleQuery();
|
||||
};
|
||||
|
||||
/**
|
||||
* 多选框选中数据
|
||||
*/
|
||||
const handleSelectionChange = (selection: any) => {
|
||||
ids.value = selection.map((item: any) => item.id);
|
||||
single.value = selection.length !== 1;
|
||||
multiple.value = !selection.length;
|
||||
};
|
||||
|
||||
/**
|
||||
* 查看详情
|
||||
*/
|
||||
const handleView = (row: any) => {
|
||||
// 跳转到调宿退宿申请详情页面
|
||||
proxy.$router.push({
|
||||
path: `/xg/xsjcxx/xssssq/dialog`,
|
||||
query: {
|
||||
id: row.id,
|
||||
type: 'update'
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 页面初始化
|
||||
onMounted(() => {
|
||||
getList();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 页面容器样式 */
|
||||
.page-container {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.mb8 {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
/* 搜索按钮容器样式 */
|
||||
.search-btn-container {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.search-btn-container .el-form-item {
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
|
||||
.card-title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,474 @@
|
||||
<template>
|
||||
<el-dialog title="假期留宿详情" v-model="dialogOpen" width="1000px" append-to-body>
|
||||
<el-form :model="form" label-width="120px" v-loading="loading">
|
||||
<el-row :gutter="20">
|
||||
<!-- 左列:学生基本信息 -->
|
||||
<el-col :span="12">
|
||||
<el-form-item label="学生姓名">
|
||||
<el-input v-model="form.studentName" disabled />
|
||||
</el-form-item>
|
||||
<el-form-item label="学号">
|
||||
<el-input v-model="form.studentNo" disabled />
|
||||
</el-form-item>
|
||||
<el-form-item label="学生联系电话">
|
||||
<el-input v-model="form.studentPhone" disabled />
|
||||
</el-form-item>
|
||||
<el-form-item label="学院名称">
|
||||
<el-input v-model="form.collegeName" disabled />
|
||||
</el-form-item>
|
||||
<el-form-item label="专业名称">
|
||||
<el-input v-model="form.majorName" disabled />
|
||||
</el-form-item>
|
||||
<el-form-item label="班级名称">
|
||||
<el-input v-model="form.className" disabled />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<!-- 右列:宿舍信息 -->
|
||||
<el-col :span="12">
|
||||
<el-form-item label="校区名称">
|
||||
<el-input v-model="form.campusName" disabled />
|
||||
</el-form-item>
|
||||
<el-form-item label="宿舍区名称">
|
||||
<el-input v-model="form.areaName" disabled />
|
||||
</el-form-item>
|
||||
<el-form-item label="宿舍楼名称">
|
||||
<el-input v-model="form.buildingName" disabled />
|
||||
</el-form-item>
|
||||
<el-form-item label="房间名称">
|
||||
<el-input v-model="form.roomName" disabled />
|
||||
</el-form-item>
|
||||
<el-form-item label="床位名称">
|
||||
<el-input v-model="form.bedName" disabled />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- 假期申请信息 -->
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="假期类型">
|
||||
<el-input v-model="holidayTypeLabel" disabled />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="留宿原因类型">
|
||||
<el-input v-model="stayReasonTypeLabel" disabled />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="留宿开始日期">
|
||||
<el-input v-model="form.applyStartDate" disabled />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="留宿结束日期">
|
||||
<el-input v-model="form.applyEndDate" disabled />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- 联系信息 -->
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="紧急联系人姓名">
|
||||
<el-input v-model="form.emergencyContact" disabled />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="紧急联系人电话">
|
||||
<el-input v-model="form.emergencyPhone" disabled />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="家长同意">
|
||||
<el-input v-model="parentConsentLabel" disabled />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- 留宿原因 -->
|
||||
<el-form-item label="留宿原因">
|
||||
<el-input v-model="form.stayReason" type="textarea" :rows="3" disabled />
|
||||
</el-form-item>
|
||||
|
||||
<!-- 附件显示 -->
|
||||
<el-form-item label="附件">
|
||||
<div class="attachment-view-container" v-if="attachmentList.length > 0">
|
||||
<div class="attachment-grid">
|
||||
<div v-for="file in attachmentList" :key="file.ossId" class="attachment-item">
|
||||
<!-- 缩略图区域 -->
|
||||
<div class="attachment-thumb">
|
||||
<image-preview v-if="isImageFile(file)" :src="file.ossId" class="thumb-image" :isOssId="true" />
|
||||
<div v-else class="file-placeholder">
|
||||
<el-icon><Document /></el-icon>
|
||||
<span class="file-ext">{{ getFileExt(file.name) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 文件名称 -->
|
||||
<div class="attachment-name" :title="file.name">
|
||||
{{ getShortFileName(file.name) }}
|
||||
</div>
|
||||
|
||||
<!-- 操作按钮 -->
|
||||
<div class="attachment-actions">
|
||||
<el-button type="success" size="small" @click="handleDownload(file)" title="下载">
|
||||
<el-icon><Download /></el-icon>
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="no-attachment">
|
||||
<el-empty description="暂无附件" :image-size="60" />
|
||||
</div>
|
||||
</el-form-item>
|
||||
|
||||
<!-- 备注信息 -->
|
||||
<el-form-item label="备注信息">
|
||||
<el-input v-model="form.remark" type="textarea" :rows="3" disabled />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<template #footer>
|
||||
<el-button @click="dialogOpen = false">关闭</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="HolidayStayDetail">
|
||||
import { ref, computed, toRefs, getCurrentInstance, type ComponentInternalInstance } from 'vue';
|
||||
import ElMessage from 'element-plus/es/components/message/index';
|
||||
import ElLoading from 'element-plus/es/components/loading/index';
|
||||
import { Document, Download } from '@element-plus/icons-vue';
|
||||
import { getHolidayStay, type HolidayStayVO } from '@/api/xg/studentDynamics';
|
||||
import ImagePreview from '@/components/ImagePreview/index.vue';
|
||||
import request from '@/utils/request';
|
||||
|
||||
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
||||
|
||||
// 字典数据
|
||||
const { xg_holiday_type, xg_stay_reason_type, sys_yesno } = toRefs<any>(proxy?.useDict('xg_holiday_type', 'xg_stay_reason_type', 'sys_yesno'));
|
||||
|
||||
// 对话框状态
|
||||
const dialogOpen = ref(false);
|
||||
const loading = ref(false);
|
||||
|
||||
// 表单数据
|
||||
const form = ref<Partial<HolidayStayVO>>({
|
||||
id: undefined,
|
||||
studentId: undefined,
|
||||
studentName: '',
|
||||
studentNo: '',
|
||||
classId: undefined,
|
||||
className: '',
|
||||
majorId: undefined,
|
||||
majorName: '',
|
||||
collegeId: undefined,
|
||||
collegeName: '',
|
||||
campusId: undefined,
|
||||
campusName: '',
|
||||
areaId: undefined,
|
||||
areaName: '',
|
||||
buildingId: undefined,
|
||||
buildingName: '',
|
||||
roomId: undefined,
|
||||
roomName: '',
|
||||
bedId: undefined,
|
||||
bedName: '',
|
||||
holidayType: '',
|
||||
applyStartDate: '',
|
||||
applyEndDate: '',
|
||||
stayReason: '',
|
||||
stayReasonType: '',
|
||||
studentPhone: '',
|
||||
emergencyContact: '',
|
||||
emergencyPhone: '',
|
||||
parentConsent: '',
|
||||
attachmentOssIds: '',
|
||||
remark: ''
|
||||
});
|
||||
|
||||
// 附件列表
|
||||
const attachmentList = ref<any[]>([]);
|
||||
|
||||
// 字典标签计算属性
|
||||
const holidayTypeLabel = computed(() => {
|
||||
if (!form.value.holidayType || !xg_holiday_type.value) return '';
|
||||
const dict = xg_holiday_type.value.find((item: any) => item.value === form.value.holidayType);
|
||||
return dict ? dict.label : form.value.holidayType;
|
||||
});
|
||||
|
||||
const stayReasonTypeLabel = computed(() => {
|
||||
if (!form.value.stayReasonType || !xg_stay_reason_type.value) return '';
|
||||
const dict = xg_stay_reason_type.value.find((item: any) => item.value === form.value.stayReasonType);
|
||||
return dict ? dict.label : form.value.stayReasonType;
|
||||
});
|
||||
|
||||
const parentConsentLabel = computed(() => {
|
||||
if (!form.value.parentConsent || !sys_yesno.value) return '';
|
||||
const dict = sys_yesno.value.find((item: any) => item.value === form.value.parentConsent);
|
||||
return dict ? dict.label : form.value.parentConsent;
|
||||
});
|
||||
|
||||
/**
|
||||
* 打开详情弹窗
|
||||
*/
|
||||
const open = async (id: string | number) => {
|
||||
loading.value = true;
|
||||
dialogOpen.value = true;
|
||||
|
||||
try {
|
||||
// 获取详情数据
|
||||
const response = await getHolidayStay(id);
|
||||
form.value = response.data;
|
||||
|
||||
// 处理附件数据
|
||||
await handleAttachmentData();
|
||||
} catch (error) {
|
||||
console.error('获取假期留宿详情失败:', error);
|
||||
ElMessage.error('获取详情失败');
|
||||
dialogOpen.value = false;
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 处理附件数据
|
||||
*/
|
||||
const handleAttachmentData = async () => {
|
||||
try {
|
||||
if (form.value.attachmentOssIds && form.value.attachmentOssIds.toString().trim()) {
|
||||
// 将逗号分隔的字符串转换为数组
|
||||
const ossIdsArray = form.value.attachmentOssIds
|
||||
.toString()
|
||||
.split(',')
|
||||
.map((id) => id.trim())
|
||||
.filter((id) => id);
|
||||
|
||||
// 调用接口获取附件信息
|
||||
const response = await request({
|
||||
url: `/resource/oss/listByIds/${ossIdsArray.join(',')}`,
|
||||
method: 'get'
|
||||
});
|
||||
|
||||
if (response.code === 200 && response.data) {
|
||||
// 将附件信息转换为显示需要的格式
|
||||
attachmentList.value = response.data.map((item: any) => ({
|
||||
ossId: item.ossId,
|
||||
name: item.originalName || '附件',
|
||||
url: item.url,
|
||||
type: item.contentType || ''
|
||||
}));
|
||||
} else {
|
||||
attachmentList.value = [];
|
||||
}
|
||||
} else {
|
||||
attachmentList.value = [];
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('处理附件数据失败:', error);
|
||||
attachmentList.value = [];
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 判断是否为图片文件
|
||||
*/
|
||||
const isImageFile = (file: any) => {
|
||||
const imageTypes = ['image/jpeg', 'image/png', 'image/gif', 'image/bmp', 'image/webp'];
|
||||
return imageTypes.includes(file.type) || /\.(jpg|jpeg|png|gif|bmp|webp)$/i.test(file.name);
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取文件扩展名
|
||||
*/
|
||||
const getFileExt = (fileName: string) => {
|
||||
const ext = fileName.split('.').pop()?.toUpperCase();
|
||||
return ext || 'FILE';
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取缩短的文件名
|
||||
*/
|
||||
const getShortFileName = (fileName: string) => {
|
||||
if (!fileName) return '';
|
||||
|
||||
const lastDotIndex = fileName.lastIndexOf('.');
|
||||
if (lastDotIndex === -1) {
|
||||
return fileName.length > 12 ? fileName.substring(0, 12) + '...' : fileName;
|
||||
}
|
||||
|
||||
const name = fileName.substring(0, lastDotIndex);
|
||||
const ext = fileName.substring(lastDotIndex);
|
||||
|
||||
if (name.length <= 8) {
|
||||
return fileName;
|
||||
}
|
||||
|
||||
return name.substring(0, 8) + '...' + ext;
|
||||
};
|
||||
|
||||
/**
|
||||
* 下载附件
|
||||
*/
|
||||
const handleDownload = async (file: any) => {
|
||||
try {
|
||||
const loading = ElLoading.service({
|
||||
text: '正在下载文件...',
|
||||
target: document.body
|
||||
});
|
||||
|
||||
const response = await request({
|
||||
url: '/resource/oss/download/' + file.ossId,
|
||||
method: 'get',
|
||||
responseType: 'blob',
|
||||
timeout: 300000
|
||||
});
|
||||
|
||||
// 验证响应数据
|
||||
if (!response) {
|
||||
throw new Error('响应数据为空');
|
||||
}
|
||||
|
||||
// 检查response的结构
|
||||
let blobData: Blob;
|
||||
if (response instanceof Blob) {
|
||||
blobData = response;
|
||||
} else if (response.data instanceof Blob) {
|
||||
blobData = response.data;
|
||||
} else {
|
||||
throw new Error('无效的响应数据格式');
|
||||
}
|
||||
|
||||
const url = window.URL.createObjectURL(blobData);
|
||||
const link = document.createElement('a');
|
||||
link.href = url;
|
||||
link.download = file.name;
|
||||
link.style.display = 'none';
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
window.URL.revokeObjectURL(url);
|
||||
loading?.close();
|
||||
ElMessage.success('下载成功');
|
||||
} catch (error) {
|
||||
console.error('下载文件失败:', error);
|
||||
ElMessage.error('下载文件失败,请稍后重试');
|
||||
}
|
||||
};
|
||||
|
||||
defineExpose({ open });
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 附件查看容器样式 */
|
||||
.attachment-view-container {
|
||||
width: 100%;
|
||||
padding: 12px;
|
||||
border: 1px solid #e4e7ed;
|
||||
border-radius: 6px;
|
||||
background: #fafafa;
|
||||
}
|
||||
|
||||
.attachment-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(5, 1fr);
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.attachment-item {
|
||||
background: #fff;
|
||||
border-radius: 6px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.attachment-item:hover {
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.attachment-thumb {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 110px;
|
||||
background: #f5f5f5;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.thumb-image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.file-placeholder {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #909399;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.file-placeholder .el-icon {
|
||||
font-size: 28px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.file-ext {
|
||||
font-size: 12px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.attachment-name {
|
||||
padding: 6px 8px;
|
||||
font-size: 11px;
|
||||
color: #303133;
|
||||
text-align: center;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
background: #fafafa;
|
||||
border-top: 1px solid #f0f0f0;
|
||||
line-height: 1.2;
|
||||
height: 32px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.attachment-actions {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 6px 8px;
|
||||
background: #fafafa;
|
||||
border-top: 1px solid #f0f0f0;
|
||||
height: 38px;
|
||||
}
|
||||
|
||||
.attachment-actions .el-button {
|
||||
padding: 4px 12px;
|
||||
font-size: 12px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.no-attachment {
|
||||
text-align: center;
|
||||
padding: 20px;
|
||||
color: #909399;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,263 @@
|
||||
<template>
|
||||
<div class="page-container">
|
||||
<!-- 搜索表单 -->
|
||||
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
|
||||
<div v-show="showSearch" class="mb-[10px]">
|
||||
<el-card shadow="hover">
|
||||
<el-form ref="queryFormRef" :model="queryParams" label-width="100px">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="6">
|
||||
<el-form-item label="学生姓名" prop="studentName">
|
||||
<el-input v-model="queryParams.studentName" placeholder="请输入学生姓名" clearable @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="学号" prop="studentNo">
|
||||
<el-input v-model="queryParams.studentNo" placeholder="请输入学号" clearable @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="学院名称" prop="collegeName">
|
||||
<el-input v-model="queryParams.collegeName" placeholder="请输入学院名称" clearable @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="专业名称" prop="majorName">
|
||||
<el-input v-model="queryParams.majorName" placeholder="请输入专业名称" clearable @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="6">
|
||||
<el-form-item label="班级名称" prop="className">
|
||||
<el-input v-model="queryParams.className" placeholder="请输入班级名称" clearable @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="假期类型" prop="holidayType">
|
||||
<el-select v-model="queryParams.holidayType" placeholder="请选择假期类型" clearable style="width: 100%">
|
||||
<el-option v-for="dict in xg_holiday_type" :key="dict.value" :label="dict.label" :value="dict.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="开始日期" prop="applyStartDate">
|
||||
<el-date-picker
|
||||
clearable
|
||||
v-model="queryParams.applyStartDate"
|
||||
type="date"
|
||||
style="width: 100%"
|
||||
value-format="YYYY-MM-DD"
|
||||
placeholder="请选择开始日期"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="结束日期" prop="applyEndDate">
|
||||
<el-date-picker
|
||||
clearable
|
||||
v-model="queryParams.applyEndDate"
|
||||
type="date"
|
||||
style="width: 100%"
|
||||
value-format="YYYY-MM-DD"
|
||||
placeholder="请选择结束日期"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-col :span="24" class="search-btn-container">
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
|
||||
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
</el-card>
|
||||
</div>
|
||||
</transition>
|
||||
|
||||
<!-- 数据表格 -->
|
||||
<el-card shadow="never">
|
||||
<template #header>
|
||||
<el-row :gutter="10" class="mb8">
|
||||
<el-col :span="1.5">
|
||||
<span class="card-title">假期留宿信息列表</span>
|
||||
</el-col>
|
||||
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
|
||||
</el-row>
|
||||
</template>
|
||||
|
||||
<el-table v-loading="loading" :data="dataList" @selection-change="handleSelectionChange">
|
||||
<el-table-column label="学生姓名" align="center" prop="studentName" />
|
||||
<el-table-column label="学号" align="center" prop="studentNo" />
|
||||
<el-table-column label="学院" align="center" prop="collegeName" show-overflow-tooltip />
|
||||
<el-table-column label="专业" align="center" prop="majorName" show-overflow-tooltip />
|
||||
<el-table-column label="班级" align="center" prop="className" show-overflow-tooltip />
|
||||
<el-table-column label="宿舍信息" align="center" show-overflow-tooltip>
|
||||
<template #default="scope"> {{ scope.row.buildingName }}{{ scope.row.roomName }} </template>
|
||||
</el-table-column>
|
||||
<el-table-column label="假期类型" align="center" prop="holidayType">
|
||||
<template #default="scope">
|
||||
<dict-tag :options="xg_holiday_type" :value="scope.row.holidayType" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="留宿时间" align="center" show-overflow-tooltip>
|
||||
<template #default="scope"> {{ scope.row.applyStartDate }} 至 {{ scope.row.applyEndDate }} </template>
|
||||
</el-table-column>
|
||||
<el-table-column label="留宿原因类型" align="center" prop="stayReasonType">
|
||||
<template #default="scope">
|
||||
<dict-tag :options="xg_stay_reason_type" :value="scope.row.stayReasonType" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="联系电话" align="center" prop="studentPhone" />
|
||||
<el-table-column label="紧急联系人" align="center" prop="emergencyContact" />
|
||||
<el-table-column label="操作" align="center" width="150" fixed="right">
|
||||
<template #default="scope">
|
||||
<el-tooltip content="查看" placement="top">
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
icon="View"
|
||||
@click="handleView(scope.row)"
|
||||
v-hasPermi="['studentDynamics:holidayStay:query']"
|
||||
></el-button>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
|
||||
</el-card>
|
||||
|
||||
<!-- 查看详情弹窗 -->
|
||||
<HolidayStayDetail ref="detailRef" @refresh="getList" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="HolidayStayTab">
|
||||
import { getCurrentInstance, ref, onMounted, toRefs } from 'vue';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { listHolidayStay, type HolidayStayQuery } from '@/api/xg/studentDynamics';
|
||||
import HolidayStayDetail from './HolidayStayDetail.vue';
|
||||
|
||||
const { proxy } = getCurrentInstance();
|
||||
|
||||
// 字典数据
|
||||
const { xg_holiday_type, xg_stay_reason_type } = toRefs(proxy?.useDict('xg_holiday_type', 'xg_stay_reason_type') || {});
|
||||
|
||||
// 数据状态
|
||||
const loading = ref(true);
|
||||
const showSearch = ref(true);
|
||||
const dataList = ref([]);
|
||||
const total = ref(0);
|
||||
const ids = ref<Array<string | number>>([]);
|
||||
const single = ref(true);
|
||||
const multiple = ref(true);
|
||||
|
||||
// 查询参数
|
||||
const queryParams = ref<HolidayStayQuery & any>({
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
studentName: undefined,
|
||||
studentNo: undefined,
|
||||
collegeName: undefined,
|
||||
majorName: undefined,
|
||||
className: undefined,
|
||||
holidayType: undefined,
|
||||
applyStartDate: undefined,
|
||||
applyEndDate: undefined,
|
||||
status: undefined
|
||||
});
|
||||
|
||||
// 表单引用
|
||||
const queryFormRef = ref();
|
||||
const detailRef = ref();
|
||||
|
||||
/**
|
||||
* 查询列表数据
|
||||
*/
|
||||
const getList = async () => {
|
||||
loading.value = true;
|
||||
try {
|
||||
const response = await listHolidayStay(queryParams.value);
|
||||
dataList.value = response.rows;
|
||||
total.value = response.total;
|
||||
} catch (error) {
|
||||
console.error('获取假期留宿信息列表失败:', error);
|
||||
ElMessage.error('获取数据失败');
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 搜索按钮操作
|
||||
*/
|
||||
const handleQuery = () => {
|
||||
queryParams.value.pageNum = 1;
|
||||
getList();
|
||||
};
|
||||
|
||||
/**
|
||||
* 重置按钮操作
|
||||
*/
|
||||
const resetQuery = () => {
|
||||
queryFormRef.value?.resetFields();
|
||||
handleQuery();
|
||||
};
|
||||
|
||||
/**
|
||||
* 多选框选中数据
|
||||
*/
|
||||
const handleSelectionChange = (selection: any) => {
|
||||
ids.value = selection.map((item: any) => item.id);
|
||||
single.value = selection.length !== 1;
|
||||
multiple.value = !selection.length;
|
||||
};
|
||||
|
||||
/**
|
||||
* 查看详情
|
||||
*/
|
||||
const handleView = (row: any) => {
|
||||
detailRef.value?.open(row.id);
|
||||
};
|
||||
|
||||
// 页面初始化
|
||||
onMounted(() => {
|
||||
getList();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 页面容器样式 */
|
||||
.page-container {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.mb-[10px] {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.mb8 {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
/* 搜索按钮容器样式 */
|
||||
.search-btn-container {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.search-btn-container .el-form-item {
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
|
||||
.card-title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,21 @@
|
||||
<template>
|
||||
<el-dialog title="学生请假详情" v-model="dialogOpen" width="800px" append-to-body>
|
||||
<div>学生请假详情组件待实现</div>
|
||||
<template #footer>
|
||||
<el-button @click="dialogOpen = false">关闭</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="StudentLeaveDetail">
|
||||
import { ref } from 'vue';
|
||||
|
||||
const dialogOpen = ref(false);
|
||||
|
||||
const open = (id: number) => {
|
||||
console.log('打开学生请假详情,ID:', id);
|
||||
dialogOpen.value = true;
|
||||
};
|
||||
|
||||
defineExpose({ open });
|
||||
</script>
|
||||
@@ -0,0 +1,271 @@
|
||||
<template>
|
||||
<div class="page-container">
|
||||
<!-- 搜索表单 -->
|
||||
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
|
||||
<div v-show="showSearch" style="margin-bottom: 10px">
|
||||
<el-card shadow="hover">
|
||||
<el-form ref="queryFormRef" :model="queryParams" label-width="100px">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="6">
|
||||
<el-form-item label="学生姓名" prop="studentName">
|
||||
<el-input v-model="queryParams.studentName" placeholder="请输入学生姓名" clearable @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="学号" prop="studentNo">
|
||||
<el-input v-model="queryParams.studentNo" placeholder="请输入学号" clearable @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="学院名称" prop="collegeName">
|
||||
<el-input v-model="queryParams.collegeName" placeholder="请输入学院名称" clearable @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="专业名称" prop="majorName">
|
||||
<el-input v-model="queryParams.majorName" placeholder="请输入专业名称" clearable @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="6">
|
||||
<el-form-item label="班级名称" prop="className">
|
||||
<el-input v-model="queryParams.className" placeholder="请输入班级名称" clearable @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="请假类型" prop="leaveTypeName">
|
||||
<el-input v-model="queryParams.leaveTypeName" placeholder="请输入请假类型名称" clearable @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="开始时间" prop="startTime">
|
||||
<el-date-picker
|
||||
clearable
|
||||
v-model="queryParams.startTime"
|
||||
type="date"
|
||||
style="width: 100%"
|
||||
value-format="YYYY-MM-DD"
|
||||
placeholder="请选择开始时间"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="结束时间" prop="endTime">
|
||||
<el-date-picker
|
||||
clearable
|
||||
v-model="queryParams.endTime"
|
||||
type="date"
|
||||
style="width: 100%"
|
||||
value-format="YYYY-MM-DD"
|
||||
placeholder="请选择结束时间"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="6">
|
||||
<el-form-item label="审批状态" prop="approvalStatus">
|
||||
<el-select v-model="queryParams.approvalStatus" placeholder="请选择审批状态" clearable style="width: 100%">
|
||||
<el-option v-for="dict in student_leave_approval_status" :key="dict.value" :label="dict.label" :value="dict.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-col :span="24" class="search-btn-container">
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
|
||||
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
</el-card>
|
||||
</div>
|
||||
</transition>
|
||||
|
||||
<!-- 数据表格 -->
|
||||
<el-card shadow="never">
|
||||
<template #header>
|
||||
<el-row :gutter="10" class="mb8">
|
||||
<el-col :span="1.5">
|
||||
<span class="card-title">学生请假信息列表</span>
|
||||
</el-col>
|
||||
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
|
||||
</el-row>
|
||||
</template>
|
||||
|
||||
<el-table v-loading="loading" :data="dataList" @selection-change="handleSelectionChange">
|
||||
<el-table-column label="学生姓名" align="center" prop="studentName" />
|
||||
<el-table-column label="学号" align="center" prop="studentNo" />
|
||||
<el-table-column label="学院" align="center" prop="collegeName" show-overflow-tooltip />
|
||||
<el-table-column label="专业" align="center" prop="majorName" show-overflow-tooltip />
|
||||
<el-table-column label="班级" align="center" prop="className" show-overflow-tooltip />
|
||||
<el-table-column label="请假类型" align="center" prop="leaveTypeName" />
|
||||
<el-table-column label="请假时间" align="center" show-overflow-tooltip>
|
||||
<template #default="scope"> {{ scope.row.startTime }} 至 {{ scope.row.endTime }} </template>
|
||||
</el-table-column>
|
||||
<el-table-column label="请假时长" align="center">
|
||||
<template #default="scope"> {{ scope.row.leaveDuration }}天 </template>
|
||||
</el-table-column>
|
||||
<el-table-column label="是否离校" align="center">
|
||||
<template #default="scope">
|
||||
<dict-tag :options="sys_yesno" :value="scope.row.isLeaveSchool" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="审批状态" align="center">
|
||||
<template #default="scope">
|
||||
<dict-tag :options="student_leave_approval_status" :value="scope.row.approvalStatus" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="销假状态" align="center">
|
||||
<template #default="scope">
|
||||
<dict-tag :options="xg_xsqj_cancel_status" :value="scope.row.cancelStatus" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" align="center" width="150" fixed="right">
|
||||
<template #default="scope">
|
||||
<el-tooltip content="查看" placement="top">
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
icon="View"
|
||||
@click="handleView(scope.row)"
|
||||
v-hasPermi="['studentDynamics:studentLeave:query']"
|
||||
></el-button>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="StudentLeaveTab">
|
||||
import { getCurrentInstance, ref, onMounted, toRefs } from 'vue';
|
||||
import { listStudentLeave, type StudentLeaveQuery } from '@/api/xg/studentDynamics';
|
||||
|
||||
const { proxy } = getCurrentInstance();
|
||||
const { sys_yesno, student_leave_approval_status, xg_xsqj_cancel_status } = toRefs(
|
||||
proxy?.useDict('sys_yesno', 'student_leave_approval_status', 'xg_xsqj_cancel_status') || {}
|
||||
);
|
||||
|
||||
// 数据状态
|
||||
const loading = ref(true);
|
||||
const showSearch = ref(true);
|
||||
const dataList = ref([]);
|
||||
const total = ref(0);
|
||||
const ids = ref<Array<string | number>>([]);
|
||||
const single = ref(true);
|
||||
const multiple = ref(true);
|
||||
|
||||
// 查询参数
|
||||
const queryParams = ref<StudentLeaveQuery & any>({
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
studentName: undefined,
|
||||
studentNo: undefined,
|
||||
collegeName: undefined,
|
||||
majorName: undefined,
|
||||
className: undefined,
|
||||
leaveTypeName: undefined,
|
||||
approvalStatus: undefined,
|
||||
startTime: undefined,
|
||||
endTime: undefined
|
||||
});
|
||||
|
||||
// 表单引用
|
||||
const queryFormRef = ref();
|
||||
|
||||
/**
|
||||
* 查询列表数据
|
||||
*/
|
||||
const getList = async () => {
|
||||
loading.value = true;
|
||||
try {
|
||||
const response = await listStudentLeave(queryParams.value);
|
||||
dataList.value = response.rows;
|
||||
total.value = response.total;
|
||||
} catch (error) {
|
||||
console.error('获取学生请假信息列表失败:', error);
|
||||
proxy?.$modal.msgError('获取数据失败');
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 搜索按钮操作
|
||||
*/
|
||||
const handleQuery = () => {
|
||||
queryParams.value.pageNum = 1;
|
||||
getList();
|
||||
};
|
||||
|
||||
/**
|
||||
* 重置按钮操作
|
||||
*/
|
||||
const resetQuery = () => {
|
||||
queryFormRef.value?.resetFields();
|
||||
handleQuery();
|
||||
};
|
||||
|
||||
/**
|
||||
* 多选框选中数据
|
||||
*/
|
||||
const handleSelectionChange = (selection: any) => {
|
||||
ids.value = selection.map((item: any) => item.id);
|
||||
single.value = selection.length !== 1;
|
||||
multiple.value = !selection.length;
|
||||
};
|
||||
|
||||
/**
|
||||
* 查看详情
|
||||
*/
|
||||
const handleView = (row: any) => {
|
||||
// 跳转到学生请假详情页面
|
||||
proxy.$router.push({
|
||||
path: `/xg/xsjcxx/xsqj/leaveEdit/index`,
|
||||
query: {
|
||||
id: row.id,
|
||||
type: 'update'
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 页面初始化
|
||||
onMounted(() => {
|
||||
getList();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 页面容器样式 */
|
||||
.page-container {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.mb8 {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
/* 搜索按钮容器样式 */
|
||||
.search-btn-container {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.search-btn-container .el-form-item {
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
|
||||
.card-title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,80 @@
|
||||
<template>
|
||||
<div class="p-2">
|
||||
<!-- Tab页组件 -->
|
||||
<el-tabs v-model="activeTab" @tab-click="handleTabClick" class="mb-4">
|
||||
<!-- 调宿/退宿信息Tab页 -->
|
||||
<el-tab-pane label="调宿/退宿信息" name="dormApplication">
|
||||
<DormApplicationTab v-if="activeTab === 'dormApplication'" ref="dormApplicationTabRef" />
|
||||
</el-tab-pane>
|
||||
|
||||
<!-- 假期留宿信息Tab页 -->
|
||||
<el-tab-pane label="假期留宿信息" name="holidayStay">
|
||||
<HolidayStayTab v-if="activeTab === 'holidayStay'" ref="holidayStayTabRef" />
|
||||
</el-tab-pane>
|
||||
|
||||
<!-- 请假信息Tab页 -->
|
||||
<el-tab-pane label="请假信息" name="studentLeave">
|
||||
<StudentLeaveTab v-if="activeTab === 'studentLeave'" ref="studentLeaveTabRef" />
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="StudentDynamics">
|
||||
import { ref, onMounted } from 'vue';
|
||||
import DormApplicationTab from './components/DormApplicationTab.vue';
|
||||
import HolidayStayTab from './components/HolidayStayTab.vue';
|
||||
import StudentLeaveTab from './components/StudentLeaveTab.vue';
|
||||
|
||||
// Tab页状态
|
||||
const activeTab = ref('dormApplication');
|
||||
|
||||
// 子组件引用
|
||||
const dormApplicationTabRef = ref();
|
||||
const holidayStayTabRef = ref();
|
||||
const studentLeaveTabRef = ref();
|
||||
|
||||
/**
|
||||
* Tab页切换处理
|
||||
*/
|
||||
const handleTabClick = (tab: any) => {
|
||||
console.log('切换到Tab页:', tab.name);
|
||||
// 可以在这里添加Tab切换的逻辑
|
||||
};
|
||||
|
||||
/**
|
||||
* 页面初始化
|
||||
*/
|
||||
onMounted(() => {
|
||||
console.log('学生动向信息管理页面已加载');
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.p-2 {
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.mb-4 {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
/* Tab页样式优化 */
|
||||
:deep(.el-tabs__header) {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
:deep(.el-tabs__item) {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
:deep(.el-tabs__item.is-active) {
|
||||
/* color: #409eff; */
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
:deep(.el-tabs__content) {
|
||||
padding: 0;
|
||||
}
|
||||
</style>
|
||||
@@ -183,7 +183,7 @@
|
||||
<el-table-column label="校区ID,关联xg_dorm_campus.id" align="center" prop="campusId" v-if="false" />
|
||||
<el-table-column label="校区" align="center" prop="campusName" />
|
||||
<el-table-column label="宿舍区ID,关联xg_dorm_area.id" align="center" prop="areaId" v-if="false" />
|
||||
<el-table-column label="宿舍区" align="center" prop="areaName" />
|
||||
<el-table-column label="宿舍区" align="center" prop="areaName" v-if="false" />
|
||||
<el-table-column label="宿舍楼ID,关联xg_dorm_building.id" align="center" prop="buildingId" v-if="false" />
|
||||
<el-table-column label="宿舍楼" align="center" prop="buildingName" />
|
||||
<el-table-column label="房间ID,关联xg_dorm_room.id" align="center" prop="roomId" v-if="false" />
|
||||
|
||||
@@ -180,7 +180,7 @@
|
||||
<el-table-column label="校区ID,关联xg_dorm_campus.id" align="center" prop="campusId" v-if="false" />
|
||||
<el-table-column label="校区" align="center" prop="campusName" />
|
||||
<el-table-column label="宿舍区ID,关联xg_dorm_area.id" align="center" prop="areaId" v-if="false" />
|
||||
<el-table-column label="宿舍区" align="center" prop="areaName" />
|
||||
<el-table-column label="宿舍区" align="center" prop="areaName" v-if="false" />
|
||||
<el-table-column label="宿舍楼ID,关联xg_dorm_building.id" align="center" prop="buildingId" v-if="false" />
|
||||
<el-table-column label="宿舍楼" align="center" prop="buildingName" />
|
||||
<el-table-column label="房间ID,关联xg_dorm_room.id" align="center" prop="roomId" v-if="false" />
|
||||
|
||||
@@ -307,7 +307,7 @@ import ApprovalButton from '@/components/Process/approvalButton.vue';
|
||||
import { AxiosResponse } from 'axios';
|
||||
import { StartProcessBo } from '@/api/workflow/workflowCommon/types';
|
||||
import { addStudentLeave, getStudentLeave, updateStudentLeave } from '@/api/workflow/studentLeave/studentLeave';
|
||||
import { getStudentMsg, getClassTeacherId, getCollegeLeaveApprovers } from '@/api/xg/xsjcxx/studentMsg/studentMsg';
|
||||
import { getStudentMsg, getClassTeacherId, getCollegeLeaveApprovers, getCollegeLeaders } from '@/api/xg/xsjcxx/studentMsg/studentMsg';
|
||||
import { startWorkFlow } from '@/api/workflow/task';
|
||||
import { StudentLeaveForm, StudentLeaveVO } from '@/api/workflow/studentLeave/studentLeave/types';
|
||||
import { StudentMsgVO } from '@/api/xg/xsjcxx/studentMsg/studentMsg/types';
|
||||
@@ -890,7 +890,7 @@ const handleStartWorkFlow = async (data: StudentLeaveForm) => {
|
||||
// 1. 调用接口获取班主任ID
|
||||
let classTeacherId: number | null = null;
|
||||
try {
|
||||
const classTeacherResponse = await getClassTeacherId(data.studentId);
|
||||
const classTeacherResponse: any = await getClassTeacherId(data.studentId);
|
||||
if (classTeacherResponse && classTeacherResponse.data) {
|
||||
classTeacherId = classTeacherResponse.data;
|
||||
console.log('获取到班主任ID:', classTeacherId);
|
||||
@@ -908,7 +908,7 @@ const handleStartWorkFlow = async (data: StudentLeaveForm) => {
|
||||
// 2. 调用接口获取学院请假审批人列表
|
||||
let collegeApprovers: number[] = [];
|
||||
try {
|
||||
const collegeApproversResponse = await getCollegeLeaveApprovers(data.studentId);
|
||||
const collegeApproversResponse: any = await getCollegeLeaveApprovers(data.studentId);
|
||||
if (collegeApproversResponse && collegeApproversResponse.data) {
|
||||
collegeApprovers = collegeApproversResponse.data;
|
||||
console.log('获取到学院请假审批人列表:', collegeApprovers);
|
||||
@@ -923,6 +923,24 @@ const handleStartWorkFlow = async (data: StudentLeaveForm) => {
|
||||
return; // 停止后续操作
|
||||
}
|
||||
|
||||
// 3. 调用接口获取二级学院分管领导ID列表
|
||||
let collegeLeaders: number[] = [];
|
||||
try {
|
||||
const collegeLeadersResponse: any = await getCollegeLeaders(data.studentId);
|
||||
if (collegeLeadersResponse && collegeLeadersResponse.data) {
|
||||
collegeLeaders = collegeLeadersResponse.data;
|
||||
console.log('获取到二级学院分管领导ID列表:', collegeLeaders);
|
||||
} else {
|
||||
console.warn('获取二级学院分管领导ID列表失败');
|
||||
ElMessage.error('获取二级学院分管领导信息失败,请稍后重试');
|
||||
return; // 停止后续操作
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('调用获取二级学院分管领导接口失败:', error);
|
||||
ElMessage.error('获取二级学院分管领导信息失败,请稍后重试');
|
||||
return; // 停止后续操作
|
||||
}
|
||||
|
||||
submitFormData.value.flowCode = flowCode.value;
|
||||
submitFormData.value.businessId = data.id;
|
||||
// 流程变量
|
||||
@@ -935,7 +953,8 @@ const handleStartWorkFlow = async (data: StudentLeaveForm) => {
|
||||
// userList: ['1', '3', '4'],
|
||||
// 新增的流程变量
|
||||
bzrspr: classTeacherId,
|
||||
ejxyspr: collegeApprovers
|
||||
ejxyspr: collegeApprovers,
|
||||
ejxyfgld: collegeLeaders
|
||||
};
|
||||
submitFormData.value.variables = taskVariables.value;
|
||||
const resp = await startWorkFlow(submitFormData.value);
|
||||
|
||||
Reference in new issue
Block a user