修改打包报错问题

This commit is contained in:
2026-01-16 18:24:09 +08:00
parent f8b618a13a
commit ba961c8bce
52 changed files with 13777 additions and 1828 deletions

View File

@@ -69,7 +69,7 @@ import { ref, reactive, onMounted } from 'vue'
import { list } from '/@/api/recruit/recruitstudentplangroup'
import { getTabStaticDataList as getTabStaticDataListApi } from '/@/api/recruit/recruitstudentsignup'
// @ts-ignore
import global from '@/components/tools/commondict'
import global from '@/components/tools/commondict.vue'
import { useTable } from '/@/hooks/table'
// 表格引用

View File

@@ -103,7 +103,7 @@ import { useMessage } from '/@/hooks/message'
import { getDataStatistics } from '/@/api/recruit/newstucheckin'
import { list } from '/@/api/recruit/recruitstudentplangroup'
import { getDeptList } from '/@/api/basic/basicclass'
import { getClasslist } from '/@/api/stuwork/stupunlish'
import { queryAllClass } from '/@/api/basic/basicclass'
import axios from 'axios'
// 使用 Pinia store
@@ -173,7 +173,7 @@ const getDeptData = async () => {
// 查找所有班级
const getClassData = async () => {
try {
const data = await getClasslist()
const data = await queryAllClass()
classData.value = data.data || []
} catch (error) {
console.error('获取班级列表失败', error)

View File

@@ -103,7 +103,7 @@ import { useMessage } from '/@/hooks/message'
import { getDataStatistics } from '/@/api/recruit/newstucheckin'
import { list } from '/@/api/recruit/recruitstudentplangroup'
import { getDeptList } from '/@/api/basic/basicclass'
import { getClasslist } from '/@/api/stuwork/stupunlish'
import { queryAllClass } from '/@/api/basic/basicclass'
import axios from 'axios'
// 使用 Pinia store
@@ -173,7 +173,7 @@ const getDeptData = async () => {
// 查找所有班级
const getClassData = async () => {
try {
const data = await getClasslist()
const data = await queryAllClass()
classData.value = data.data || []
} catch (error) {
console.error('获取班级列表失败', error)

View File

@@ -134,7 +134,7 @@ import { getMNStuList, delMNObj } from '@/api/recruit/recruitImitateAdjustBatch'
import { listPlanByCondition as planMajor } from "@/api/recruit/recruitstudentplan"
import { getTypeValue } from "@/api/admin/dict"
// @ts-ignore
import global from '@/components/tools/commondict'
import global from '@/components/tools/commondict.vue'
const AddMNStu = defineAsyncComponent(() => import('./addMNStu.vue'))

View File

@@ -74,7 +74,7 @@ import { getDeptList } from "@/api/basic/basicclass"
import { dormApplyAnalysis } from "@/api/recruit/recruitstudentsignup"
import { list } from '@/api/recruit/recruitstudentplangroup'
// @ts-ignore
import global from '@/components/tools/commondict'
import global from '@/components/tools/commondict.vue'
// 响应式数据
const deptList = ref<any[]>([])

View File

@@ -445,7 +445,7 @@ import {
oneStuNo,
changeClassInfo, getMajorClass
} from '@/api/recruit/recruitstudentsignup'
import global from '@/components/tools/commondict'
import global from '@/components/tools/commondict.vue'
import { getClassListByRole, getDeptList, queryAllClassByInfo } from "@/api/basic/basicclass"
import {listPlanByCondition as planMajor} from "@/api/recruit/recruitstudentplan"
import { getTypeValue } from "@/api/admin/dict"

View File

@@ -22,7 +22,7 @@
import { ref, reactive } from 'vue'
import { useMessage } from '/@/hooks/message'
// @ts-ignore
import global from "@/components/tools/commondict"
import global from "@/components/tools/commondict.vue"
import { interview } from "@/api/recruit/recruitstudentsignup"
// 消息提示 hooks

View File

@@ -269,7 +269,7 @@ import { list } from '/@/api/recruit/recruitstudentplangroup'
import { fetchListStuDorm, yjOut, setFw, delFw, yjSend } from '/@/api/recruit/recruitstudentsignup'
import { getDeptList } from '/@/api/basic/basicclass'
// @ts-ignore
import global from '@/components/tools/commondict'
import global from '@/components/tools/commondict.vue'
const DormFW = defineAsyncComponent(() => import('./dormFW.vue'))
const ShowMap = defineAsyncComponent(() => import('./showMap.vue'))

View File

@@ -1,160 +0,0 @@
<!--
- Copyright (c) 2018-2025, cyweb All rights reserved.
-
- Redistribution and use in source and binary forms, with or without
- modification, are permitted provided that the following conditions are met:
-
- Redistributions of source code must retain the above copyright notice,
- this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright
- notice, this list of conditions and the following disclaimer in the
- documentation and/or other materials provided with the distribution.
- Neither the name of the pig4cloud.com developer nor the names of its
- contributors may be used to endorse or promote products derived from
- this software without specific prior written permission.
-
-->
<template>
<div class="execution">
<basic-container>
<avue-crud ref="crud"
:page="page"
:data="tableData"
:table-loading="tableLoading"
:option="tableOption"
@on-load="getList"
@search-change="handleFilter"
@refresh-change="refreshChange"
@row-update="handleUpdate"
@row-save="handleSave"
@row-del="rowDel">
</avue-crud>
</basic-container>
</div>
</template>
<script>
import {addObj, delObj, fetchList, putObj} from '@/api/recruit/recruitstudentsignupturnover'
import {tableOption} from '@/const/crud/recruit/recruitstudentsignupturnover'
import {mapGetters} from 'vuex'
export default {
name: 'recruitstudentsignupturnover',
data() {
return {
tableData: [],
page: {
total: 0, // 总页数
currentPage: 1, // 当前页数
pageSize: 10 // 每页显示多少条
},
tableLoading: false,
tableOption: tableOption,
params:{}
}
},
created() {
},
mounted: function() { },
computed: {
...mapGetters(['permissions'])
},
methods: {
getList(page) {
this.tableLoading = true
fetchList(Object.assign({
current: page.currentPage,
size: page.pageSize
}, this.params)).then(response => {
this.tableData = response.data.records
this.page.total = response.data.total
this.tableLoading = false
})
},
/**
* @title 打开新增窗口
* @detail 调用crud的handleadd方法即可
*
**/
handleAdd: function() {
this.$refs.crud.rowAdd()
},
handleEdit(row, index) {
this.$refs.crud.rowEdit(row, index)
},
handleDel(row, index) {
this.$refs.crud.rowDel(row, index)
},
rowDel: function(row, index) {
var _this = this
this.$confirm('是否确认删除ID为' + row.id, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(function() {
return delObj(row.id)
}).then(data => {
_this.tableData.splice(index, 1)
_this.$message({
showClose: true,
message: '删除成功',
type: 'success'
})
this.getList(this.page)
}).catch(function(err) { })
},
/**
* @title 数据更新
* @param row 为当前的数据
* @param index 为当前更新数据的行数
* @param done 为表单关闭函数
*
**/
handleUpdate: function(row, index, done) {
putObj(row).then(data => {
this.tableData.splice(index, 1, Object.assign({}, row))
this.$message({
showClose: true,
message: '修改成功',
type: 'success'
})
done()
this.getList(this.page)
})
},
/**
* @title 数据添加
* @param row 为当前的数据
* @param done 为表单关闭函数
*
**/
handleSave: function(row, done) {
addObj(row).then(data => {
this.tableData.push(Object.assign({}, row))
this.$message({
showClose: true,
message: '添加成功',
type: 'success'
})
done()
this.getList(this.page)
})
},
/**
* 刷新回调
*/
refreshChange() {
this.getList(this.page)
},
handleFilter(param){
this.params = param;
this.page.currentPage = 1;
this.getList(this.page);
}
}
}
</script>
<style lang="scss" scoped>
</style>

View File

@@ -0,0 +1,279 @@
<template>
<div class="layout-padding">
<div class="layout-padding-auto layout-padding-view">
<!-- 搜索表单 -->
<el-row v-show="showSearch">
<el-form :model="searchForm" ref="searchFormRef" :inline="true" @keyup.enter="handleSearch">
<el-form-item label="班级" prop="classCode">
<el-select
v-model="searchForm.classCode"
placeholder="请选择班级"
clearable
filterable
style="width: 200px">
<el-option
v-for="item in classList"
:key="item.classCode"
:label="item.classNo"
:value="item.classCode">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="点名类型" prop="orderType">
<el-select
v-model="searchForm.orderType"
placeholder="请选择点名类型"
clearable
style="width: 200px">
<el-option
v-for="item in orderTypeList"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" plain icon="Search" @click="handleSearch">查询</el-button>
<el-button icon="Refresh" @click="handleReset">重置</el-button>
</el-form-item>
</el-form>
</el-row>
<!-- 操作按钮 -->
<el-row>
<div class="mb8" style="width: 100%">
<right-toolbar
v-model:showSearch="showSearch"
class="ml10 mr20"
style="float: right;"
@queryTable="getDataList">
</right-toolbar>
</div>
</el-row>
<!-- 表格 -->
<el-table
:data="dataList"
v-loading="loading"
border
:cell-style="tableStyle.cellStyle"
:header-cell-style="tableStyle.headerCellStyle">
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="stuNo" label="学号" show-overflow-tooltip align="center" />
<el-table-column prop="realName" label="姓名" show-overflow-tooltip align="center" />
<el-table-column prop="phone" label="联系电话" show-overflow-tooltip align="center" />
<el-table-column prop="parentPhoneA" label="家长联系电话1" show-overflow-tooltip align="center" />
<el-table-column prop="parentPhoneB" label="家长联系电话2" show-overflow-tooltip align="center" />
<el-table-column prop="stuStatus" label="学生状态" show-overflow-tooltip align="center">
<template #default="scope">
<span>{{ formatStudentStatus(scope.row.stuStatus) }}</span>
</template>
</el-table-column>
<el-table-column prop="attendanceType" label="考勤类型" show-overflow-tooltip align="center">
<template #default="scope">
<span>{{ formatAttendanceType(scope.row.attendanceType) }}</span>
</template>
</el-table-column>
<el-table-column prop="isRoom" label="是否住宿" show-overflow-tooltip align="center">
<template #default="scope">
<span>{{ formatYesNo(scope.row.isRoom) }}</span>
</template>
</el-table-column>
<el-table-column prop="roomNo" label="宿舍号" show-overflow-tooltip align="center" />
<el-table-column prop="isDeviceIn" label="是否扫脸" show-overflow-tooltip align="center">
<template #default="scope">
<el-tag :type="scope.row.isDeviceIn === '1' ? 'success' : 'danger'">
{{ scope.row.isDeviceIn === '1' ? '是' : '否' }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" width="100" align="center" fixed="right">
<template #default="scope">
<el-button
icon="Edit"
text
type="primary"
@click="handleEdit(scope.row)">
编辑
</el-button>
</template>
</el-table-column>
</el-table>
</div>
</div>
</template>
<script setup lang="ts" name="ClassAttendance">
import { reactive, ref, onMounted } from 'vue'
import { fetchList, queryMyClassList } from '/@/api/stuwork/classattendance'
import { getDicts } from '/@/api/admin/dict'
import { useMessage } from '/@/hooks/message'
// 定义变量内容
const searchFormRef = ref()
const showSearch = ref(true)
const loading = ref(false)
const dataList = ref<any[]>([])
const classList = ref<any[]>([])
const orderTypeList = ref<any[]>([])
const studentStatusList = ref<any[]>([])
const attendanceTypeList = ref<any[]>([])
// 搜索表单
const searchForm = reactive({
classCode: '',
orderType: ''
})
// 表格样式
const tableStyle = {
cellStyle: { textAlign: 'center' },
headerCellStyle: {
textAlign: 'center',
background: 'var(--el-table-row-hover-bg-color)',
color: 'var(--el-text-color-primary)',
},
}
// 格式化学生状态
const formatStudentStatus = (value: string) => {
if (!value) return '-'
const item = studentStatusList.value.find((item: any) => item.value === value)
return item ? item.label : value
}
// 格式化考勤类型
const formatAttendanceType = (value: string) => {
if (!value) return '-'
const item = attendanceTypeList.value.find((item: any) => item.value === value)
return item ? item.label : value
}
// 格式化是否
const formatYesNo = (value: string | null) => {
if (value === null || value === undefined || value === '') return '-'
return value === '1' || value === 'true' ? '是' : '否'
}
// 查询
const handleSearch = () => {
getDataList()
}
// 重置
const handleReset = () => {
searchFormRef.value?.resetFields()
searchForm.classCode = ''
searchForm.orderType = ''
getDataList()
}
// 编辑
const handleEdit = (row: any) => {
// TODO: 实现编辑功能
useMessage().info('编辑功能待实现')
}
// 获取数据列表
const getDataList = async () => {
loading.value = true
try {
const params: any = {
classCode: searchForm.classCode || undefined,
orderType: searchForm.orderType || undefined
}
const res = await fetchList(params)
if (res.data && Array.isArray(res.data)) {
dataList.value = res.data
} else {
dataList.value = []
}
} catch (err: any) {
console.error('获取数据列表失败', err)
useMessage().error(err.msg || '获取数据列表失败')
dataList.value = []
} finally {
loading.value = false
}
}
// 获取班级列表
const getClassListData = async () => {
try {
const res = await queryMyClassList()
if (res.data && Array.isArray(res.data)) {
classList.value = res.data
} else {
classList.value = []
}
} catch (err) {
console.error('获取班级列表失败', err)
classList.value = []
}
}
// 获取点名类型字典
const getOrderTypeDict = async () => {
try {
// 根据业务逻辑,点名类型可能是固定的几个值
// 这里先使用常见的值,如果不对可以根据实际情况调整
orderTypeList.value = [
{ label: '早读', value: '1' },
{ label: '上午', value: '2' },
{ label: '下午', value: '3' },
{ label: '晚自习', value: '4' }
]
} catch (err) {
console.error('获取点名类型失败', err)
orderTypeList.value = []
}
}
// 获取学生状态字典
const getStudentStatusDict = async () => {
try {
const res = await getDicts('student_status')
if (res.data && Array.isArray(res.data)) {
studentStatusList.value = res.data.map((item: any) => ({
label: item.label || item.dictLabel || item.name,
value: item.value || item.dictValue || item.code
}))
} else {
studentStatusList.value = []
}
} catch (err) {
console.error('获取学生状态字典失败', err)
studentStatusList.value = []
}
}
// 获取考勤类型字典
const getAttendanceTypeDict = async () => {
try {
const res = await getDicts('attend_type')
if (res.data && Array.isArray(res.data)) {
attendanceTypeList.value = res.data.map((item: any) => ({
label: item.label || item.dictLabel || item.name,
value: item.value || item.dictValue || item.code
}))
} else {
attendanceTypeList.value = []
}
} catch (err) {
console.error('获取考勤类型字典失败', err)
attendanceTypeList.value = []
}
}
// 初始化
onMounted(() => {
getClassListData()
getOrderTypeDict()
getStudentStatusDict()
getAttendanceTypeDict()
// 不自动加载数据,需要先选择班级和点名类型
})
</script>

View File

@@ -0,0 +1,223 @@
<template>
<el-dialog
title="查看详情"
v-model="visible"
:close-on-click-modal="false"
draggable
width="900px">
<el-descriptions :column="2" border v-loading="loading" v-if="detailData">
<el-descriptions-item label="学年">{{ detailData.schoolYear || '-' }}</el-descriptions-item>
<el-descriptions-item label="学期">
<span>{{ formatSchoolTerm(detailData.schoolTerm) }}</span>
</el-descriptions-item>
<el-descriptions-item label="学院">{{ detailData.deptName || '-' }}</el-descriptions-item>
<el-descriptions-item label="班号">{{ detailData.classNo || '-' }}</el-descriptions-item>
<el-descriptions-item label="班主任">{{ detailData.teacherRealName || '-' }}</el-descriptions-item>
<el-descriptions-item label="人数">{{ detailData.num || '-' }}</el-descriptions-item>
<el-descriptions-item label="请假开始时间">
<span>{{ detailData.startTime ? formatDateTime(detailData.startTime) : '-' }}</span>
</el-descriptions-item>
<el-descriptions-item label="请假结束时间">
<span>{{ detailData.endTime ? formatDateTime(detailData.endTime) : '-' }}</span>
</el-descriptions-item>
<el-descriptions-item label="请假类型">
<span>{{ formatLeaveType(detailData.leaveType) }}</span>
</el-descriptions-item>
<el-descriptions-item label="是否住宿">
<span>{{ formatYesNo(detailData.stayDorm) }}</span>
</el-descriptions-item>
<el-descriptions-item label="时段请假">
<span>{{ formatYesNo(detailData.isSegment) }}</span>
</el-descriptions-item>
<el-descriptions-item label="请假时段" :span="2">{{ detailData.segmentString || '-' }}</el-descriptions-item>
<el-descriptions-item label="请假校门">
<span>{{ formatSchoolDoor(detailData.schoolDoor) }}</span>
</el-descriptions-item>
<el-descriptions-item label="基础部审核">
<span>{{ formatAuditType(detailData.deptAudit) }}</span>
</el-descriptions-item>
<el-descriptions-item label="学工处审批">
<span>{{ formatAuditType(detailData.schoolAudit) }}</span>
</el-descriptions-item>
<el-descriptions-item label="驳回原因" :span="2">{{ detailData.rejectReason || '-' }}</el-descriptions-item>
<el-descriptions-item label="请假事由" :span="2">{{ detailData.reason || '-' }}</el-descriptions-item>
</el-descriptions>
<template #footer>
<span class="dialog-footer">
<el-button @click="visible = false"> </el-button>
</span>
</template>
</el-dialog>
</template>
<script setup lang="ts" name="ClassLeaveApplyDetailDialog">
import { ref, onMounted } from 'vue'
import { getDetail } from '/@/api/stuwork/classleaveapply'
import { getDicts } from '/@/api/admin/dict'
// 定义变量内容
const visible = ref(false)
const loading = ref(false)
const detailData = ref<any>(null)
const schoolTermList = ref<any[]>([])
const leaveTypeList = ref<any[]>([])
const yesNoList = ref<any[]>([])
const auditTypeList = ref<any[]>([])
const schoolDoorList = ref<any[]>([])
// 校门列表
const schoolDoorListData = [
{ label: '校门东', value: '0' },
{ label: '校门南', value: '1' },
{ label: '校门西', value: '2' }
]
// 格式化学期
const formatSchoolTerm = (value: string | number) => {
if (value === null || value === undefined || value === '') {
return '-'
}
const dictItem = schoolTermList.value.find(item => item.value == value)
return dictItem ? dictItem.label : value
}
// 格式化日期时间
const formatDateTime = (dateTime: string) => {
if (!dateTime) return '-'
if (dateTime.includes(' ')) {
return dateTime.split('.')[0]
}
return dateTime
}
// 格式化请假类型
const formatLeaveType = (value: string) => {
if (!value) return '-'
const item = leaveTypeList.value.find((item: any) => item.value === value)
return item ? item.label : value
}
// 格式化是否
const formatYesNo = (value: string) => {
if (value === null || value === undefined || value === '') {
return '-'
}
const dictItem = yesNoList.value.find(item => item.value == value)
return dictItem ? dictItem.label : value
}
// 格式化校门
const formatSchoolDoor = (value: string) => {
if (!value) return '-'
const item = schoolDoorList.value.find((item: any) => item.value === value)
return item ? item.label : value
}
// 格式化审核类型
const formatAuditType = (value: string) => {
if (value === null || value === undefined || value === '') {
return '-'
}
const dictItem = auditTypeList.value.find(item => item.value == value)
return dictItem ? dictItem.label : value
}
// 打开弹窗
const openDialog = async (id: string) => {
visible.value = true
detailData.value = null
loading.value = true
try {
const res = await getDetail(id)
if (res.data) {
detailData.value = res.data
}
} catch (err) {
console.error('获取详情失败', err)
} finally {
loading.value = false
}
}
// 获取学期字典
const getSchoolTermDict = async () => {
try {
const res = await getDicts('school_term')
if (res.data) {
schoolTermList.value = Array.isArray(res.data) ? res.data.map((item: any) => ({
label: item.label || item.dictLabel || item.name,
value: item.value || item.dictValue || item.code
})) : []
}
} catch (err) {
console.error('获取学期字典失败', err)
schoolTermList.value = []
}
}
// 获取请假类型字典
const getLeaveTypeDict = async () => {
try {
const res = await getDicts('leave_type')
if (res.data) {
leaveTypeList.value = Array.isArray(res.data) ? res.data.map((item: any) => ({
label: item.label || item.dictLabel || item.name,
value: item.value || item.dictValue || item.code
})) : []
}
} catch (err) {
console.error('获取请假类型字典失败', err)
leaveTypeList.value = []
}
}
// 获取是否字典
const getYesNoDict = async () => {
try {
const res = await getDicts('yes_no')
if (res.data) {
yesNoList.value = Array.isArray(res.data) ? res.data.map((item: any) => ({
label: item.label || item.dictLabel || item.name,
value: item.value || item.dictValue || item.code
})) : []
}
} catch (err) {
console.error('获取是否字典失败', err)
yesNoList.value = []
}
}
// 获取审核类型字典
const getAuditTypeDict = async () => {
try {
const res = await getDicts('class_audit_type')
if (res.data) {
auditTypeList.value = Array.isArray(res.data) ? res.data.map((item: any) => ({
label: item.label || item.dictLabel || item.name,
value: item.value || item.dictValue || item.code
})) : []
}
} catch (err) {
console.error('获取审核类型字典失败', err)
auditTypeList.value = []
}
}
// 初始化
onMounted(() => {
schoolDoorList.value = schoolDoorListData
getSchoolTermDict()
getLeaveTypeDict()
getYesNoDict()
getAuditTypeDict()
})
// 暴露方法
defineExpose({
openDialog
})
</script>
<style scoped lang="scss">
</style>

View File

@@ -0,0 +1,471 @@
<template>
<el-dialog
title="批量新增"
v-model="visible"
:close-on-click-modal="false"
draggable
width="900px">
<el-form
ref="dataFormRef"
:model="form"
:rules="dataRules"
label-width="120px"
v-loading="loading">
<el-row :gutter="24">
<el-col :span="12" class="mb20">
<el-form-item label="学年" prop="schoolYear">
<el-select
v-model="form.schoolYear"
placeholder="请选择学年"
clearable
filterable
style="width: 100%">
<el-option
v-for="item in schoolYearList"
:key="item.year"
:label="item.year"
:value="item.year">
</el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="学期" prop="schoolTerm">
<el-select
v-model="form.schoolTerm"
placeholder="请选择学期"
clearable
style="width: 100%">
<el-option
v-for="item in schoolTermList"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="班号" prop="classCode">
<el-select
v-model="form.classCode"
placeholder="请选择班号"
clearable
filterable
style="width: 100%">
<el-option
v-for="item in classList"
:key="item.classCode"
:label="item.classNo"
:value="item.classCode">
</el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="请假类型" prop="leaveType">
<el-select
v-model="form.leaveType"
placeholder="请选择请假类型"
clearable
style="width: 100%">
<el-option
v-for="item in leaveTypeList"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="24" class="mb20">
<el-form-item label="请假时间" prop="leaveTime">
<el-date-picker
v-model="form.leaveTime"
type="datetimerange"
range-separator=""
start-placeholder="开始时间"
end-placeholder="结束时间"
format="YYYY-MM-DD HH:mm"
value-format="YYYY-MM-DD HH:mm:ss"
style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="是否住宿" prop="stayDorm">
<el-select
v-model="form.stayDorm"
placeholder="请选择是否住宿"
clearable
style="width: 100%">
<el-option
v-for="item in yesNoList"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="时段请假" prop="isSegment">
<el-switch
v-model="form.isSegment"
active-value="1"
inactive-value="0"
active-text=""
inactive-text=""
@change="handleSegmentChange" />
</el-form-item>
</el-col>
<template v-if="form.isSegment === '1'">
<el-col :span="12" class="mb20">
<el-form-item label="时段开始时间" prop="segmentStartTime">
<el-time-picker
v-model="form.segmentStartTime"
placeholder="请选择时段开始时间"
format="HH:mm"
value-format="HH:mm"
style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="时段结束时间" prop="segmentEndTime">
<el-time-picker
v-model="form.segmentEndTime"
placeholder="请选择时段结束时间"
format="HH:mm"
value-format="HH:mm"
style="width: 100%" />
</el-form-item>
</el-col>
</template>
<el-col :span="12" class="mb20">
<el-form-item label="校门" prop="schoolDoor">
<el-select
v-model="form.schoolDoor"
placeholder="请选择校门"
clearable
style="width: 100%">
<el-option
v-for="item in schoolDoorList"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="24" class="mb20">
<el-form-item label="请假事由" prop="reason">
<el-input
v-model="form.reason"
type="textarea"
:rows="4"
placeholder="请输入请假事由" />
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="visible = false"> </el-button>
<el-button type="primary" @click="onSubmit" :disabled="loading"> </el-button>
</span>
</template>
</el-dialog>
</template>
<script setup lang="ts" name="ClassLeaveApplyFormDialog">
import { ref, reactive, nextTick, onMounted, computed } from 'vue'
import { useMessage } from '/@/hooks/message'
import { addObj } from '/@/api/stuwork/classleaveapply'
import { queryAllSchoolYear } from '/@/api/basic/basicyear'
import { getDicts } from '/@/api/admin/dict'
import { getClassListByRole } from '/@/api/basic/basicclass'
const emit = defineEmits(['refresh'])
// 定义变量内容
const dataFormRef = ref()
const visible = ref(false)
const loading = ref(false)
const schoolYearList = ref<any[]>([])
const schoolTermList = ref<any[]>([])
const classList = ref<any[]>([])
const leaveTypeList = ref<any[]>([])
const yesNoList = ref<any[]>([])
const schoolDoorList = ref<any[]>([])
// 校门列表
const schoolDoorListData = [
{ label: '校门东', value: '0' },
{ label: '校门南', value: '1' },
{ label: '校门西', value: '2' }
]
// 提交表单数据
const form = reactive({
schoolYear: '',
schoolTerm: '',
classCode: '',
leaveTime: null as [string, string] | null,
leaveType: '',
reason: '',
stayDorm: '',
isSegment: '0',
segmentStartTime: '',
segmentEndTime: '',
schoolDoor: ''
})
// 定义校验规则
const dataRules = {
schoolYear: [
{ required: true, message: '请选择学年', trigger: 'change' }
],
schoolTerm: [
{ required: true, message: '请选择学期', trigger: 'change' }
],
classCode: [
{ required: true, message: '请选择班号', trigger: 'change' }
],
leaveTime: [
{ required: true, message: '请选择请假时间', trigger: 'change' }
],
leaveType: [
{ required: true, message: '请选择请假类型', trigger: 'change' }
],
reason: [
{ required: true, message: '请输入请假事由', trigger: 'blur' }
],
segmentStartTime: [
{
validator: (rule: any, value: any, callback: any) => {
if (form.isSegment === '1' && !value) {
callback(new Error('请选择时段开始时间'))
} else {
callback()
}
},
trigger: 'change'
}
],
segmentEndTime: [
{
validator: (rule: any, value: any, callback: any) => {
if (form.isSegment === '1' && !value) {
callback(new Error('请选择时段结束时间'))
} else {
callback()
}
},
trigger: 'change'
}
]
}
// 打开弹窗
const openDialog = () => {
visible.value = true
resetForm()
nextTick(() => {
dataFormRef.value?.resetFields()
})
}
// 重置表单
const resetForm = () => {
form.schoolYear = ''
form.schoolTerm = ''
form.classCode = ''
form.leaveTime = null
form.leaveType = ''
form.reason = ''
form.stayDorm = ''
form.isSegment = '0'
form.segmentStartTime = ''
form.segmentEndTime = ''
form.schoolDoor = ''
}
// 时段请假变化
const handleSegmentChange = () => {
if (form.isSegment !== '1') {
form.segmentStartTime = ''
form.segmentEndTime = ''
}
}
// 提交表单
const onSubmit = async () => {
if (!dataFormRef.value) return
await dataFormRef.value.validate(async (valid: boolean) => {
if (!valid) return
// 验证请假时间范围
if (!form.leaveTime || form.leaveTime.length !== 2) {
useMessage().error('请选择请假时间范围')
return
}
const [startTime, endTime] = form.leaveTime
const start = new Date(startTime)
const end = new Date(endTime)
if (end <= start) {
useMessage().error('请假结束时间必须大于开始时间')
return
}
// 验证时段时间(如果开启了时段请假)
if (form.isSegment === '1') {
if (!form.segmentStartTime || !form.segmentEndTime) {
useMessage().error('请选择时段开始时间和结束时间')
return
}
// 简单的时段验证(可以更复杂)
if (form.segmentEndTime <= form.segmentStartTime) {
useMessage().error('时段结束时间必须大于开始时间')
return
}
}
loading.value = true
try {
// 构建时段JSON如果开启了时段请假
let segmentJson = ''
if (form.isSegment === '1' && form.segmentStartTime && form.segmentEndTime) {
segmentJson = JSON.stringify({
startTime: form.segmentStartTime,
endTime: form.segmentEndTime
})
}
await addObj({
schoolYear: form.schoolYear,
schoolTerm: form.schoolTerm,
classCode: form.classCode,
startTime: startTime,
endTime: endTime,
leaveType: form.leaveType,
reason: form.reason,
stayDorm: form.stayDorm,
isSegment: form.isSegment,
segmentJson: segmentJson,
schoolDoor: form.schoolDoor
})
useMessage().success('新增成功')
visible.value = false
emit('refresh')
} catch (err: any) {
useMessage().error(err.msg || '新增失败')
} finally {
loading.value = false
}
})
}
// 获取学年列表
const getSchoolYearListData = async () => {
try {
const res = await queryAllSchoolYear()
if (res.data) {
schoolYearList.value = Array.isArray(res.data) ? res.data : []
}
} catch (err) {
console.error('获取学年列表失败', err)
schoolYearList.value = []
}
}
// 获取学期字典
const getSchoolTermDict = async () => {
try {
const res = await getDicts('school_term')
if (res.data) {
schoolTermList.value = Array.isArray(res.data) ? res.data.map((item: any) => ({
label: item.label || item.dictLabel || item.name,
value: item.value || item.dictValue || item.code
})) : []
}
} catch (err) {
console.error('获取学期字典失败', err)
schoolTermList.value = []
}
}
// 获取班级列表
const getClassListData = async () => {
try {
const res = await getClassListByRole()
if (res.data) {
classList.value = Array.isArray(res.data) ? res.data : []
}
} catch (err) {
console.error('获取班级列表失败', err)
classList.value = []
}
}
// 获取请假类型字典
const getLeaveTypeDict = async () => {
try {
const res = await getDicts('leave_type')
if (res.data) {
leaveTypeList.value = Array.isArray(res.data) ? res.data.map((item: any) => ({
label: item.label || item.dictLabel || item.name,
value: item.value || item.dictValue || item.code
})) : []
}
} catch (err) {
console.error('获取请假类型字典失败', err)
leaveTypeList.value = []
}
}
// 获取是否字典
const getYesNoDict = async () => {
try {
const res = await getDicts('yes_no')
if (res.data) {
yesNoList.value = Array.isArray(res.data) ? res.data.map((item: any) => ({
label: item.label || item.dictLabel || item.name,
value: item.value || item.dictValue || item.code
})) : []
}
} catch (err) {
console.error('获取是否字典失败', err)
yesNoList.value = []
}
}
// 初始化
onMounted(() => {
schoolDoorList.value = schoolDoorListData
getSchoolYearListData()
getSchoolTermDict()
getClassListData()
getLeaveTypeDict()
getYesNoDict()
})
// 暴露方法
defineExpose({
openDialog
})
</script>
<style scoped lang="scss">
</style>

View File

@@ -0,0 +1,504 @@
<template>
<div class="layout-padding">
<div class="layout-padding-auto layout-padding-view">
<!-- 搜索表单 -->
<el-row v-show="showSearch">
<el-form :model="searchForm" ref="searchFormRef" :inline="true" @keyup.enter="handleSearch">
<el-form-item label="学年" prop="schoolYear">
<el-select
v-model="searchForm.schoolYear"
placeholder="请选择学年"
clearable
filterable
style="width: 200px">
<el-option
v-for="item in schoolYearList"
:key="item.year"
:label="item.year"
:value="item.year">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="学期" prop="schoolTerm">
<el-select
v-model="searchForm.schoolTerm"
placeholder="请选择学期"
clearable
style="width: 200px">
<el-option
v-for="item in schoolTermList"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="学院" prop="deptCode">
<el-select
v-model="searchForm.deptCode"
placeholder="请选择学院"
clearable
filterable
style="width: 200px">
<el-option
v-for="item in deptList"
:key="item.deptCode"
:label="item.deptName"
:value="item.deptCode">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="班号" prop="classCode">
<el-select
v-model="searchForm.classCode"
placeholder="请选择班号"
clearable
filterable
style="width: 200px">
<el-option
v-for="item in classList"
:key="item.classCode"
:label="item.classNo"
:value="item.classCode">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="请假类型" prop="leaveType">
<el-select
v-model="searchForm.leaveType"
placeholder="请选择请假类型"
clearable
style="width: 200px">
<el-option
v-for="item in leaveTypeList"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="校门" prop="schoolDoor">
<el-select
v-model="searchForm.schoolDoor"
placeholder="请选择校门"
clearable
style="width: 200px">
<el-option
v-for="item in schoolDoorList"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" plain icon="Search" @click="handleSearch">查询</el-button>
<el-button icon="Refresh" @click="handleReset">重置</el-button>
</el-form-item>
</el-form>
</el-row>
<!-- 操作按钮 -->
<el-row>
<div class="mb8" style="width: 100%">
<el-button
icon="Plus"
type="primary"
class="ml10"
@click="formDialogRef.openDialog()">
批量新增
</el-button>
<el-button
icon="Delete"
type="danger"
class="ml10"
@click="handleDeleteByClass">
整班删除
</el-button>
<right-toolbar
v-model:showSearch="showSearch"
class="ml10 mr20"
style="float: right;"
@queryTable="getDataList">
</right-toolbar>
</div>
</el-row>
<!-- 表格 -->
<el-table
:data="state.dataList"
v-loading="state.loading"
border
:cell-style="tableStyle.cellStyle"
:header-cell-style="tableStyle.headerCellStyle">
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="schoolYear" label="学年" show-overflow-tooltip align="center" />
<el-table-column prop="schoolTerm" label="学期" show-overflow-tooltip align="center">
<template #default="scope">
<span>{{ formatSchoolTerm(scope.row.schoolTerm) }}</span>
</template>
</el-table-column>
<el-table-column prop="deptName" label="学院" show-overflow-tooltip align="center" />
<el-table-column prop="classNo" label="班号" show-overflow-tooltip align="center" />
<el-table-column prop="teacherRealName" label="班主任" show-overflow-tooltip align="center" />
<el-table-column prop="num" label="人数" show-overflow-tooltip align="center" />
<el-table-column prop="startTime" label="请假开始时间" show-overflow-tooltip align="center" width="180">
<template #default="scope">
<span>{{ scope.row.startTime ? formatDateTime(scope.row.startTime) : '-' }}</span>
</template>
</el-table-column>
<el-table-column prop="endTime" label="请假结束时间" show-overflow-tooltip align="center" width="180">
<template #default="scope">
<span>{{ scope.row.endTime ? formatDateTime(scope.row.endTime) : '-' }}</span>
</template>
</el-table-column>
<el-table-column prop="leaveType" label="请假类型" show-overflow-tooltip align="center">
<template #default="scope">
<span>{{ formatLeaveType(scope.row.leaveType) }}</span>
</template>
</el-table-column>
<el-table-column prop="reason" label="请假事由" show-overflow-tooltip align="center" min-width="150" />
<el-table-column prop="stayDorm" label="是否住宿" show-overflow-tooltip align="center">
<template #default="scope">
<span>{{ formatYesNo(scope.row.stayDorm) }}</span>
</template>
</el-table-column>
<el-table-column prop="isSegment" label="时段请假" show-overflow-tooltip align="center">
<template #default="scope">
<span>{{ formatYesNo(scope.row.isSegment) }}</span>
</template>
</el-table-column>
<el-table-column prop="segmentString" label="请假时段" show-overflow-tooltip align="center" min-width="150" />
<el-table-column prop="schoolDoor" label="请假校门" show-overflow-tooltip align="center">
<template #default="scope">
<span>{{ formatSchoolDoor(scope.row.schoolDoor) }}</span>
</template>
</el-table-column>
<el-table-column prop="deptAudit" label="基础部审核" show-overflow-tooltip align="center">
<template #default="scope">
<span>{{ formatAuditType(scope.row.deptAudit) }}</span>
</template>
</el-table-column>
<el-table-column prop="schoolAudit" label="学工处审批" show-overflow-tooltip align="center">
<template #default="scope">
<span>{{ formatAuditType(scope.row.schoolAudit) }}</span>
</template>
</el-table-column>
<el-table-column prop="rejectReason" label="驳回原因" show-overflow-tooltip align="center" min-width="150" />
<el-table-column label="操作" width="150" align="center" fixed="right">
<template #default="scope">
<el-button
icon="View"
text
type="primary"
@click="handleView(scope.row)">
查看
</el-button>
<el-button
icon="Delete"
text
type="danger"
@click="handleDelete(scope.row)">
删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<pagination
@size-change="sizeChangeHandle"
@current-change="currentChangeHandle"
v-bind="state.pagination" />
</div>
<!-- 批量新增表单弹窗 -->
<form-dialog ref="formDialogRef" @refresh="getDataList" />
<!-- 查看详情弹窗 -->
<detail-dialog ref="detailDialogRef" />
</div>
</template>
<script setup lang="ts" name="ClassLeaveApply">
import { reactive, ref, onMounted } from 'vue'
import { BasicTableProps, useTable } from "/@/hooks/table";
import { fetchList, delObj, deleteByClass } from "/@/api/stuwork/classleaveapply";
import { queryAllSchoolYear } from "/@/api/basic/basicyear";
import { getDicts } from "/@/api/admin/dict";
import { getDeptListByLevelTwo } from "/@/api/basic/basicdept";
import { getClassListByRole } from "/@/api/basic/basicclass";
import { useMessage, useMessageBox } from "/@/hooks/message";
import FormDialog from './form.vue'
import DetailDialog from './detail.vue'
// 定义变量内容
const searchFormRef = ref()
const showSearch = ref(true)
const schoolYearList = ref<any[]>([])
const schoolTermList = ref<any[]>([])
const deptList = ref<any[]>([])
const classList = ref<any[]>([])
const leaveTypeList = ref<any[]>([])
const yesNoList = ref<any[]>([])
const auditTypeList = ref<any[]>([])
const schoolDoorList = ref<any[]>([])
const formDialogRef = ref()
const detailDialogRef = ref()
// 校门列表
const schoolDoorListData = [
{ label: '校门东', value: '0' },
{ label: '校门南', value: '1' },
{ label: '校门西', value: '2' }
]
// 搜索表单
const searchForm = reactive({
schoolYear: '',
schoolTerm: '',
deptCode: '',
classCode: '',
leaveType: '',
schoolDoor: ''
})
// 配置 useTable
const state: BasicTableProps = reactive<BasicTableProps>({
queryForm: searchForm,
pageList: fetchList,
props: {
item: 'records',
totalCount: 'total'
},
createdIsNeed: true
})
// table hook
const {
getDataList,
currentChangeHandle,
sizeChangeHandle,
tableStyle
} = useTable(state)
// 格式化学期
const formatSchoolTerm = (value: string | number) => {
if (value === null || value === undefined || value === '') {
return '-'
}
const dictItem = schoolTermList.value.find(item => item.value == value)
return dictItem ? dictItem.label : value
}
// 格式化日期时间
const formatDateTime = (dateTime: string) => {
if (!dateTime) return '-'
if (dateTime.includes(' ')) {
return dateTime.split('.')[0]
}
return dateTime
}
// 格式化请假类型
const formatLeaveType = (value: string) => {
if (!value) return '-'
const item = leaveTypeList.value.find((item: any) => item.value === value)
return item ? item.label : value
}
// 格式化是否
const formatYesNo = (value: string) => {
if (value === null || value === undefined || value === '') {
return '-'
}
const dictItem = yesNoList.value.find(item => item.value == value)
return dictItem ? dictItem.label : value
}
// 格式化校门
const formatSchoolDoor = (value: string) => {
if (!value) return '-'
const item = schoolDoorList.value.find((item: any) => item.value === value)
return item ? item.label : value
}
// 格式化审核类型
const formatAuditType = (value: string) => {
if (value === null || value === undefined || value === '') {
return '-'
}
const dictItem = auditTypeList.value.find(item => item.value == value)
return dictItem ? dictItem.label : value
}
// 查询
const handleSearch = () => {
getDataList()
}
// 重置
const handleReset = () => {
searchFormRef.value?.resetFields()
getDataList()
}
// 查看
const handleView = (row: any) => {
detailDialogRef.value.openDialog(row.id)
}
// 删除
const handleDelete = async (row: any) => {
const { confirm } = useMessageBox()
try {
await confirm('确定要删除该批量请假记录吗?')
await delObj([row.id])
useMessage().success('删除成功')
getDataList()
} catch (err: any) {
if (err !== 'cancel') {
useMessage().error(err.msg || '删除失败')
}
}
}
// 整班删除
const handleDeleteByClass = async () => {
const { confirm, prompt } = useMessageBox()
try {
const { value } = await prompt('请输入要删除的班级ID')
if (!value) {
useMessage().warning('请输入班级ID')
return
}
await confirm('确定要删除该班级的所有请假记录吗?')
await deleteByClass(value)
useMessage().success('删除成功')
getDataList()
} catch (err: any) {
if (err !== 'cancel') {
useMessage().error(err.msg || '删除失败')
}
}
}
// 获取学年列表
const getSchoolYearListData = async () => {
try {
const res = await queryAllSchoolYear()
if (res.data) {
schoolYearList.value = Array.isArray(res.data) ? res.data : []
}
} catch (err) {
console.error('获取学年列表失败', err)
schoolYearList.value = []
}
}
// 获取学期字典
const getSchoolTermDict = async () => {
try {
const res = await getDicts('school_term')
if (res.data) {
schoolTermList.value = Array.isArray(res.data) ? res.data.map((item: any) => ({
label: item.label || item.dictLabel || item.name,
value: item.value || item.dictValue || item.code
})) : []
}
} catch (err) {
console.error('获取学期字典失败', err)
schoolTermList.value = []
}
}
// 获取系部列表
const getDeptListData = async () => {
try {
const res = await getDeptListByLevelTwo()
if (res.data) {
deptList.value = Array.isArray(res.data) ? res.data : []
}
} catch (err) {
console.error('获取系部列表失败', err)
deptList.value = []
}
}
// 获取班级列表
const getClassListData = async () => {
try {
const res = await getClassListByRole()
if (res.data) {
classList.value = Array.isArray(res.data) ? res.data : []
}
} catch (err) {
console.error('获取班级列表失败', err)
classList.value = []
}
}
// 获取请假类型字典
const getLeaveTypeDict = async () => {
try {
const res = await getDicts('leave_type')
if (res.data) {
leaveTypeList.value = Array.isArray(res.data) ? res.data.map((item: any) => ({
label: item.label || item.dictLabel || item.name,
value: item.value || item.dictValue || item.code
})) : []
}
} catch (err) {
console.error('获取请假类型字典失败', err)
leaveTypeList.value = []
}
}
// 获取是否字典
const getYesNoDict = async () => {
try {
const res = await getDicts('yes_no')
if (res.data) {
yesNoList.value = Array.isArray(res.data) ? res.data.map((item: any) => ({
label: item.label || item.dictLabel || item.name,
value: item.value || item.dictValue || item.code
})) : []
}
} catch (err) {
console.error('获取是否字典失败', err)
yesNoList.value = []
}
}
// 获取审核类型字典
const getAuditTypeDict = async () => {
try {
const res = await getDicts('class_audit_type')
if (res.data) {
auditTypeList.value = Array.isArray(res.data) ? res.data.map((item: any) => ({
label: item.label || item.dictLabel || item.name,
value: item.value || item.dictValue || item.code
})) : []
}
} catch (err) {
console.error('获取审核类型字典失败', err)
auditTypeList.value = []
}
}
// 初始化
onMounted(() => {
schoolDoorList.value = schoolDoorListData
getSchoolYearListData()
getSchoolTermDict()
getDeptListData()
getClassListData()
getLeaveTypeDict()
getYesNoDict()
getAuditTypeDict()
})
</script>
<style scoped lang="scss">
</style>

View File

@@ -0,0 +1,223 @@
<template>
<el-dialog
title="教室安排"
v-model="visible"
:width="600"
:close-on-click-modal="false"
draggable>
<el-form
ref="dataFormRef"
:model="form"
:rules="dataRules"
label-width="120px"
v-loading="loading">
<el-row :gutter="24">
<el-col :span="24" class="mb20">
<el-form-item label="楼号" prop="buildingNo">
<el-select
v-model="form.buildingNo"
placeholder="请选择楼号"
clearable
filterable
style="width: 100%">
<el-option
v-for="item in buildingList"
:key="item.buildingNo"
:label="item.buildingNo"
:value="item.buildingNo">
</el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="24" class="mb20">
<el-form-item label="教室位置" prop="position">
<el-select
v-model="form.position"
placeholder="请选择教室位置"
clearable
filterable
style="width: 100%">
<el-option
v-for="item in classroomList"
:key="item.position"
:label="item.position"
:value="item.position">
</el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="24" class="mb20">
<el-form-item label="班级" prop="classCode">
<el-select
v-model="form.classCode"
placeholder="请选择班级"
clearable
filterable
style="width: 100%">
<el-option
v-for="item in classList"
:key="item.classCode"
:label="item.classNo"
:value="item.classCode">
</el-option>
</el-select>
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="visible = false"> </el-button>
<el-button type="primary" @click="onSubmit" :disabled="loading"> </el-button>
</span>
</template>
</el-dialog>
</template>
<script setup lang="ts" name="ClassroomBaseArrangeDialog">
import { ref, reactive, nextTick, onMounted } from 'vue'
import { useMessage } from '/@/hooks/message'
import { editObj } from '/@/api/stuwork/classroombase'
import { queryAllClass } from '/@/api/basic/basicclass'
import { getClassRoomList, getBuildingList } from '/@/api/stuwork/teachclassroom'
const emit = defineEmits(['refresh'])
// 定义变量内容
const dataFormRef = ref()
const visible = ref(false)
const loading = ref(false)
const classList = ref<any[]>([])
const classroomList = ref<any[]>([])
const buildingList = ref<any[]>([])
// 提交表单数据
const form = reactive({
id: '',
buildingNo: '',
classCode: '',
position: ''
})
// 定义校验规则
const dataRules = {
buildingNo: [
{ required: true, message: '请选择楼号', trigger: 'change' }
],
position: [
{ required: true, message: '请选择教室位置', trigger: 'change' }
],
classCode: [
{ required: true, message: '请选择班级', trigger: 'change' }
]
}
// 打开弹窗
const openDialog = async (row: any) => {
visible.value = true
resetForm()
// 填充现有数据
if (row) {
form.id = row.id || ''
form.buildingNo = row.buildingNo || ''
form.classCode = row.classCode || ''
form.position = row.position || ''
}
nextTick(() => {
dataFormRef.value?.clearValidate()
})
}
// 重置表单
const resetForm = () => {
form.id = ''
form.buildingNo = ''
form.classCode = ''
form.position = ''
}
// 提交表单
const onSubmit = async () => {
if (!dataFormRef.value) return
await dataFormRef.value.validate(async (valid: boolean) => {
if (!valid) return
loading.value = true
try {
await editObj({
id: form.id,
buildingNo: form.buildingNo,
classCode: form.classCode,
position: form.position
})
useMessage().success('教室安排成功')
visible.value = false
emit('refresh')
} catch (err: any) {
useMessage().error(err.msg || '教室安排失败')
} finally {
loading.value = false
}
})
}
// 获取班级列表
const getClassListData = async () => {
try {
const res = await queryAllClass()
if (res.data) {
classList.value = Array.isArray(res.data) ? res.data : []
}
} catch (err) {
console.error('获取班级列表失败', err)
classList.value = []
}
}
// 获取教室列表
const getClassroomListData = async () => {
try {
const res = await getClassRoomList()
if (res.data) {
classroomList.value = Array.isArray(res.data) ? res.data : []
}
} catch (err) {
console.error('获取教室列表失败', err)
classroomList.value = []
}
}
// 获取楼号列表
const getBuildingListData = async () => {
try {
const res = await getBuildingList()
if (res.data) {
buildingList.value = Array.isArray(res.data) ? res.data : []
}
} catch (err) {
console.error('获取楼号列表失败', err)
buildingList.value = []
}
}
// 初始化
onMounted(() => {
getClassListData()
getClassroomListData()
getBuildingListData()
})
// 暴露方法
defineExpose({
openDialog
})
</script>
<style scoped lang="scss">
</style>

View File

@@ -0,0 +1,413 @@
<template>
<div class="layout-padding">
<div class="layout-padding-auto layout-padding-view">
<!-- 搜索表单 -->
<el-row v-show="showSearch">
<el-form :model="searchForm" ref="searchFormRef" :inline="true" @keyup.enter="handleSearch">
<el-form-item label="学院" prop="deptName">
<el-select
v-model="searchForm.deptName"
placeholder="请选择学院"
clearable
filterable
style="width: 200px">
<el-option
v-for="item in deptList"
:key="item.deptCode"
:label="item.deptName"
:value="item.deptName">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="班号" prop="classCode">
<el-select
v-model="searchForm.classCode"
placeholder="请选择班号"
clearable
filterable
style="width: 200px">
<el-option
v-for="item in classList"
:key="item.classCode"
:label="item.classNo"
:value="item.classCode">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="班级状态" prop="classStatus">
<el-select
v-model="searchForm.classStatus"
placeholder="请选择班级状态"
clearable
style="width: 200px">
<el-option
v-for="item in classStatusList"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="教室位置" prop="position">
<el-input
v-model="searchForm.position"
placeholder="请输入教室位置"
clearable
style="width: 200px" />
</el-form-item>
<el-form-item>
<el-button type="primary" plain icon="Search" @click="handleSearch">查询</el-button>
<el-button icon="Refresh" @click="handleReset">重置</el-button>
</el-form-item>
</el-form>
</el-row>
<!-- 操作按钮 -->
<el-row>
<div class="mb8" style="width: 100%">
<el-button
icon="Download"
type="success"
class="ml10"
@click="handleExport">
导出
</el-button>
<el-button
icon="Refresh"
type="primary"
class="ml10"
@click="handleSync">
同步教室安排
</el-button>
<right-toolbar
v-model:showSearch="showSearch"
class="ml10 mr20"
style="float: right;"
@queryTable="getDataList">
</right-toolbar>
</div>
</el-row>
<!-- 表格 -->
<el-table
:data="state.dataList"
v-loading="state.loading"
border
row-key="id"
:cell-style="tableStyle.cellStyle"
:header-cell-style="tableStyle.headerCellStyle">
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="buildingNo" label="楼号" show-overflow-tooltip align="center" />
<el-table-column prop="deptName" label="学院" show-overflow-tooltip align="center" />
<el-table-column prop="classStatus" label="班级状态" show-overflow-tooltip align="center">
<template #default="scope">
<span>{{ formatClassStatus(scope.row.classStatus) }}</span>
</template>
</el-table-column>
<el-table-column prop="position" label="教室位置" show-overflow-tooltip align="center" />
<el-table-column prop="stuNum" label="人数" show-overflow-tooltip align="center" />
<el-table-column prop="teacherRealName" label="班主任" show-overflow-tooltip align="center" />
<el-table-column prop="platformType" label="讲台类型" show-overflow-tooltip align="center">
<template #default="scope">
<span>{{ formatDict(scope.row.platformType, platformTypeList) }}</span>
</template>
</el-table-column>
<el-table-column prop="tyType" label="投影类型" show-overflow-tooltip align="center">
<template #default="scope">
<span>{{ formatDict(scope.row.tyType, tyTypeList) }}</span>
</template>
</el-table-column>
<el-table-column prop="tvType" label="电视机" show-overflow-tooltip align="center">
<template #default="scope">
<span>{{ formatDict(scope.row.tvType, tvTypeList) }}</span>
</template>
</el-table-column>
<el-table-column prop="chairCnt" label="方凳数量" show-overflow-tooltip align="center" />
<el-table-column prop="tableCnt" label="课桌数量" show-overflow-tooltip align="center" />
<el-table-column prop="password" label="锁密码" show-overflow-tooltip align="center" />
<el-table-column label="操作" width="120" align="center" fixed="right">
<template #default="scope">
<el-button
icon="Setting"
text
type="primary"
@click="handleArrange(scope.row)">
教室安排
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<pagination
@size-change="sizeChangeHandle"
@current-change="currentChangeHandle"
v-bind="state.pagination" />
</div>
<!-- 教室安排表单弹窗 -->
<arrange-dialog ref="arrangeDialogRef" @refresh="getDataList" />
</div>
</template>
<script setup lang="ts" name="ClassroomBase">
import { reactive, ref, onMounted } from 'vue'
import { BasicTableProps, useTable } from "/@/hooks/table";
import { fetchList, exportData, syncClassroomArrangement } from "/@/api/stuwork/classroombase";
import { getDeptListByLevelTwo } from "/@/api/basic/basicdept";
import { queryAllClass } from "/@/api/basic/basicclass";
import { getDicts } from "/@/api/admin/dict";
import { useMessage, useMessageBox } from "/@/hooks/message";
import ArrangeDialog from './arrange.vue'
// 定义变量内容
const searchFormRef = ref()
const showSearch = ref(true)
const deptList = ref<any[]>([])
const classList = ref<any[]>([])
const classStatusList = ref<any[]>([])
const platformTypeList = ref<any[]>([])
const tyTypeList = ref<any[]>([])
const tvTypeList = ref<any[]>([])
const arrangeDialogRef = ref()
// 班级状态列表
const classStatusListData = [
{ label: '在校', value: '0' },
{ label: '顶岗', value: '1' },
{ label: '更岗', value: '2' },
{ label: '离校', value: '3' }
]
// 搜索表单
const searchForm = reactive({
deptName: '',
classCode: '',
classStatus: '',
position: ''
})
// 配置 useTable
const state: BasicTableProps = reactive<BasicTableProps>({
queryForm: searchForm,
pageList: async (queryParams: any) => {
try {
const response = await fetchList(queryParams)
// 处理 ipage 数据结构
if (response && response.data) {
if (response.data.ipage) {
// 如果 ipage 是包含 records 和 total 的对象
if (Array.isArray(response.data.ipage)) {
// ipage 是数组
return {
...response,
data: {
records: response.data.ipage || [],
total: response.data.total || response.data.ipage.length || 0
}
}
} else {
// ipage 是对象,包含 records 和 total
return {
...response,
data: {
records: response.data.ipage.records || [],
total: response.data.ipage.total || 0
}
}
}
}
// 如果没有 ipage尝试直接使用 data
return {
...response,
data: {
records: response.data.records || response.data || [],
total: response.data.total || 0
}
}
}
// 如果 response 结构不对,返回空数据
return {
...response,
data: {
records: [],
total: 0
}
}
} catch (err: any) {
// 确保即使出错也返回正确的数据结构
console.error('获取数据失败', err)
throw err
}
},
props: {
item: 'records',
totalCount: 'total'
},
createdIsNeed: true
})
// table hook
const {
getDataList,
currentChangeHandle,
sizeChangeHandle,
tableStyle
} = useTable(state)
// 格式化班级状态
const formatClassStatus = (value: string) => {
if (value === null || value === undefined || value === '') {
return '-'
}
const dictItem = classStatusList.value.find(item => item.value == value)
return dictItem ? dictItem.label : value
}
// 格式化字典
const formatDict = (value: string, dictList: any[]) => {
if (!value) return '-'
const item = dictList.find((item: any) => item.value === value)
return item ? item.label : value
}
// 查询
const handleSearch = () => {
getDataList()
}
// 重置
const handleReset = () => {
searchFormRef.value?.resetFields()
getDataList()
}
// 导出
const handleExport = async () => {
try {
const res = await exportData(searchForm)
// 创建下载链接
const blob = new Blob([res.data], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' })
const url = window.URL.createObjectURL(blob)
const elink = document.createElement('a')
elink.style.display = 'none'
elink.href = url
elink.setAttribute('download', `教室安排及公物管理_${new Date().getTime()}.xlsx`)
document.body.appendChild(elink)
elink.click()
URL.revokeObjectURL(elink.href)
document.body.removeChild(elink)
useMessage().success('导出成功')
} catch (err: any) {
useMessage().error(err.msg || '导出失败')
}
}
// 同步教室安排
const handleSync = async () => {
const { confirm } = useMessageBox()
try {
await confirm('确定要同步教室安排吗?')
await syncClassroomArrangement()
useMessage().success('同步成功')
getDataList()
} catch (err: any) {
if (err !== 'cancel') {
useMessage().error(err.msg || '同步失败')
}
}
}
// 教室安排
const handleArrange = (row: any) => {
arrangeDialogRef.value?.openDialog(row)
}
// 获取系部列表
const getDeptListData = async () => {
try {
const res = await getDeptListByLevelTwo()
if (res.data) {
deptList.value = Array.isArray(res.data) ? res.data : []
}
} catch (err) {
console.error('获取系部列表失败', err)
deptList.value = []
}
}
// 获取班级列表
const getClassListData = async () => {
try {
const res = await queryAllClass()
if (res.data) {
classList.value = Array.isArray(res.data) ? res.data : []
}
} catch (err) {
console.error('获取班级列表失败', err)
classList.value = []
}
}
// 获取讲台类型字典
const getPlatformTypeDict = async () => {
try {
const res = await getDicts('platform_type')
if (res.data) {
platformTypeList.value = Array.isArray(res.data) ? res.data.map((item: any) => ({
label: item.label || item.dictLabel || item.name,
value: item.value || item.dictValue || item.code
})) : []
}
} catch (err) {
console.error('获取讲台类型字典失败', err)
platformTypeList.value = []
}
}
// 获取投影类型字典
const getTyTypeDict = async () => {
try {
const res = await getDicts('ty_type')
if (res.data) {
tyTypeList.value = Array.isArray(res.data) ? res.data.map((item: any) => ({
label: item.label || item.dictLabel || item.name,
value: item.value || item.dictValue || item.code
})) : []
}
} catch (err) {
console.error('获取投影类型字典失败', err)
tyTypeList.value = []
}
}
// 获取电视机类型字典
const getTvTypeDict = async () => {
try {
const res = await getDicts('tv_type')
if (res.data) {
tvTypeList.value = Array.isArray(res.data) ? res.data.map((item: any) => ({
label: item.label || item.dictLabel || item.name,
value: item.value || item.dictValue || item.code
})) : []
}
} catch (err) {
console.error('获取电视机类型字典失败', err)
tvTypeList.value = []
}
}
// 初始化
onMounted(() => {
classStatusList.value = classStatusListData
getDeptListData()
getClassListData()
getPlatformTypeDict()
getTyTypeDict()
getTvTypeDict()
})
</script>
<style scoped lang="scss">
</style>

View File

@@ -0,0 +1,199 @@
<template>
<el-dialog
:title="form.id ? '编辑' : '新增'"
v-model="visible"
:close-on-click-modal="false"
draggable
width="600px">
<el-form
ref="dataFormRef"
:model="form"
:rules="dataRules"
label-width="100px"
v-loading="loading">
<el-form-item label="留宿类型" prop="liveType">
<el-select
v-model="form.liveType"
placeholder="请选择留宿类型"
clearable
style="width: 100%">
<el-option
v-for="item in liveTypeList"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="留宿日期" prop="liveDates">
<el-date-picker
v-model="form.liveDates"
type="dates"
placeholder="选择留宿日期(可多选)"
format="YYYY-MM-DD"
value-format="YYYY-MM-DD"
style="width: 100%" />
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="visible = false"> </el-button>
<el-button type="primary" @click="onSubmit" :disabled="loading"> </el-button>
</span>
</template>
</el-dialog>
</template>
<script setup lang="ts" name="DormLiveApplyFormDialog">
import { ref, reactive, nextTick, onMounted } from 'vue'
import { useMessage } from '/@/hooks/message'
import { addObj, editObj, getDetail } from '/@/api/stuwork/dormliveapply'
import { getDicts } from '/@/api/admin/dict'
const emit = defineEmits(['refresh'])
// 定义变量内容
const dataFormRef = ref()
const visible = ref(false)
const loading = ref(false)
const operType = ref('add') // add 或 edit
const liveTypeList = ref<any[]>([])
// 提交表单数据
const form = reactive({
id: '',
liveType: '',
liveDates: [] as string[]
})
// 定义校验规则
const dataRules = {
liveType: [
{ required: true, message: '请选择留宿类型', trigger: 'change' }
],
liveDates: [
{ required: true, message: '请选择留宿日期', trigger: 'change', type: 'array', min: 1 }
]
}
// 打开弹窗
const openDialog = async (type: string = 'add', row?: any) => {
visible.value = true
operType.value = type
// 重置表单数据
nextTick(() => {
dataFormRef.value?.resetFields()
form.id = ''
form.liveType = ''
form.liveDates = []
// 编辑时填充数据
if (type === 'edit' && row) {
form.id = row.id
form.liveType = row.liveType || ''
// 获取详情以获取liveDates
if (row.id) {
getDetailData(row.id)
}
}
})
}
// 获取详情
const getDetailData = async (id: string) => {
try {
loading.value = true
const res = await getDetail(id)
if (res.data && res.data.records && res.data.records.length > 0) {
const detail = res.data.records[0]
form.liveType = detail.liveType || ''
// 处理留宿日期,可能是字符串或数组
if (detail.liveDates) {
if (Array.isArray(detail.liveDates)) {
form.liveDates = detail.liveDates
} else if (typeof detail.liveDates === 'string') {
// 如果是逗号分隔的字符串,转换为数组
form.liveDates = detail.liveDates.split(',').filter((d: string) => d.trim())
}
} else if (detail.liveDate) {
// 如果只有单个日期,转换为数组
form.liveDates = [detail.liveDate]
}
}
} catch (err) {
console.error('获取详情失败', err)
} finally {
loading.value = false
}
}
// 提交表单
const onSubmit = async () => {
if (!dataFormRef.value) return
await dataFormRef.value.validate(async (valid: boolean) => {
if (!valid) return
if (!form.liveDates || form.liveDates.length === 0) {
useMessage().error('请至少选择一个留宿日期')
return
}
loading.value = true
try {
const submitData: any = {
liveType: form.liveType,
liveDates: form.liveDates
}
// 编辑时需要包含id
if (operType.value === 'edit') {
submitData.id = form.id
}
if (operType.value === 'add') {
await addObj(submitData)
useMessage().success('新增成功')
} else {
await editObj(submitData)
useMessage().success('编辑成功')
}
visible.value = false
emit('refresh')
} catch (err: any) {
useMessage().error(err.msg || (operType.value === 'add' ? '新增失败' : '编辑失败'))
} finally {
loading.value = false
}
})
}
// 获取留宿类型字典
const getLiveTypeDict = async () => {
try {
const res = await getDicts('live_type')
if (res.data) {
liveTypeList.value = Array.isArray(res.data) ? res.data.map((item: any) => ({
label: item.label || item.dictLabel || item.name,
value: item.value || item.dictValue || item.code
})) : []
}
} catch (err) {
console.error('获取留宿类型字典失败', err)
liveTypeList.value = []
}
}
// 初始化
onMounted(() => {
getLiveTypeDict()
})
// 暴露方法给父组件
defineExpose({
openDialog
})
</script>

View File

@@ -0,0 +1,407 @@
<template>
<div class="layout-padding">
<div class="layout-padding-auto layout-padding-view">
<!-- 搜索表单 -->
<el-row v-show="showSearch">
<el-form :model="searchForm" ref="searchFormRef" :inline="true" @keyup.enter="handleSearch">
<el-form-item label="学年" prop="schoolYear">
<el-select
v-model="searchForm.schoolYear"
placeholder="请选择学年"
clearable
filterable
style="width: 200px">
<el-option
v-for="item in schoolYearList"
:key="item.year"
:label="item.year"
:value="item.year">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="学期" prop="schoolTerm">
<el-select
v-model="searchForm.schoolTerm"
placeholder="请选择学期"
clearable
style="width: 200px">
<el-option
v-for="item in schoolTermList"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="楼号" prop="buildingNo">
<el-select
v-model="searchForm.buildingNo"
placeholder="请选择楼号"
clearable
filterable
style="width: 200px">
<el-option
v-for="item in buildingList"
:key="item.buildingNo"
:label="item.buildingNo"
:value="item.buildingNo">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="开始时间" prop="startTime">
<el-date-picker
v-model="searchForm.startTime"
type="date"
placeholder="选择开始时间"
format="YYYY-MM-DD"
value-format="YYYY-MM-DD"
clearable
style="width: 200px" />
</el-form-item>
<el-form-item label="结束时间" prop="endTime">
<el-date-picker
v-model="searchForm.endTime"
type="date"
placeholder="选择结束时间"
format="YYYY-MM-DD"
value-format="YYYY-MM-DD"
clearable
style="width: 200px" />
</el-form-item>
<el-form-item label="班级" prop="classCode">
<el-select
v-model="searchForm.classCode"
placeholder="请选择班级"
clearable
filterable
style="width: 200px">
<el-option
v-for="item in classList"
:key="item.classCode"
:label="item.classNo"
:value="item.classCode">
</el-option>
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" plain icon="Search" @click="handleSearch">查询</el-button>
<el-button icon="Refresh" @click="handleReset">重置</el-button>
</el-form-item>
</el-form>
</el-row>
<!-- 操作按钮 -->
<el-row>
<div class="mb8" style="width: 100%">
<el-button
icon="FolderAdd"
type="primary"
class="ml10"
@click="formDialogRef.openDialog()">
</el-button>
<el-button
icon="Download"
type="success"
class="ml10"
@click="handleExport">
</el-button>
<right-toolbar
v-model:showSearch="showSearch"
class="ml10 mr20"
style="float: right;"
@queryTable="getDataList">
</right-toolbar>
</div>
</el-row>
<!-- 表格 -->
<el-table
:data="state.dataList"
v-loading="state.loading"
border
:cell-style="tableStyle.cellStyle"
:header-cell-style="tableStyle.headerCellStyle">
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="schoolYear" label="学年" show-overflow-tooltip align="center" />
<el-table-column prop="schoolTerm" label="学期" show-overflow-tooltip align="center">
<template #default="scope">
<span>{{ formatSchoolTerm(scope.row.schoolTerm) }}</span>
</template>
</el-table-column>
<el-table-column prop="deptName" label="学院" show-overflow-tooltip align="center" />
<el-table-column prop="classNo" label="班级" show-overflow-tooltip align="center" />
<el-table-column prop="realName" label="姓名" show-overflow-tooltip align="center" />
<el-table-column prop="roomNo" label="宿舍号" show-overflow-tooltip align="center" />
<el-table-column prop="liveType" label="留宿类型" show-overflow-tooltip align="center">
<template #default="scope">
<span>{{ formatLiveType(scope.row.liveType) }}</span>
</template>
</el-table-column>
<el-table-column prop="liveDate" label="留宿日期" show-overflow-tooltip align="center" />
<el-table-column prop="auditStatus" label="审核状态" show-overflow-tooltip align="center">
<template #default="scope">
<span>{{ formatAuditStatus(scope.row.auditStatus) }}</span>
</template>
</el-table-column>
<el-table-column label="操作" width="150" align="center" fixed="right">
<template #default="scope">
<el-button
icon="Edit"
text
type="primary"
@click="handleEdit(scope.row)">
编辑
</el-button>
<el-button
icon="Delete"
text
type="danger"
@click="handleDelete(scope.row)">
删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<pagination
@size-change="sizeChangeHandle"
@current-change="currentChangeHandle"
v-bind="state.pagination" />
</div>
<!-- 新增/编辑表单弹窗 -->
<form-dialog ref="formDialogRef" @refresh="getDataList" />
</div>
</template>
<script setup lang="ts" name="DormLiveApply">
import { reactive, ref, onMounted } from 'vue'
import { BasicTableProps, useTable } from "/@/hooks/table";
import { fetchList, delObj, exportData } from "/@/api/stuwork/dormliveapply";
import { queryAllSchoolYear } from "/@/api/basic/basicyear";
import { getDicts } from "/@/api/admin/dict";
import { getBuildingList } from "/@/api/stuwork/dormbuilding";
import { getClassListByRole } from "/@/api/basic/basicclass";
import { useMessage, useMessageBox } from "/@/hooks/message";
import FormDialog from './form.vue'
// 定义变量内容
const searchFormRef = ref()
const showSearch = ref(true)
const schoolYearList = ref<any[]>([])
const schoolTermList = ref<any[]>([])
const buildingList = ref<any[]>([])
const classList = ref<any[]>([])
const liveTypeList = ref<any[]>([])
const auditStatusList = ref<any[]>([])
const formDialogRef = ref()
// 搜索表单
const searchForm = reactive({
schoolYear: '',
schoolTerm: '',
buildingNo: '',
startTime: '',
endTime: '',
classCode: ''
})
// 配置 useTable
const state: BasicTableProps = reactive<BasicTableProps>({
queryForm: searchForm,
pageList: fetchList,
props: {
item: 'records',
totalCount: 'total'
},
createdIsNeed: true
})
// table hook
const {
getDataList,
currentChangeHandle,
sizeChangeHandle,
tableStyle
} = useTable(state)
// 格式化学期
const formatSchoolTerm = (value: string | number) => {
if (value === null || value === undefined || value === '') {
return '-'
}
const dictItem = schoolTermList.value.find(item => item.value == value)
return dictItem ? dictItem.label : value
}
// 格式化留宿类型
const formatLiveType = (value: string) => {
if (!value) return '-'
const item = liveTypeList.value.find((item: any) => item.value === value)
return item ? item.label : value
}
// 格式化审核状态
const formatAuditStatus = (value: string) => {
if (!value) return '-'
const item = auditStatusList.value.find((item: any) => item.value === value)
return item ? item.label : value
}
// 查询
const handleSearch = () => {
getDataList()
}
// 重置
const handleReset = () => {
searchFormRef.value?.resetFields()
searchForm.schoolYear = ''
searchForm.schoolTerm = ''
searchForm.buildingNo = ''
searchForm.startTime = ''
searchForm.endTime = ''
searchForm.classCode = ''
getDataList()
}
// 编辑
const handleEdit = (row: any) => {
formDialogRef.value?.openDialog('edit', row)
}
// 删除
const handleDelete = async (row: any) => {
try {
await useMessageBox().confirm('确定要删除这条记录吗?')
await delObj([row.id])
useMessage().success('删除成功')
getDataList()
} catch (err: any) {
if (err !== 'cancel') {
useMessage().error(err.msg || '删除失败')
}
}
}
// 导出
const handleExport = async () => {
try {
const res = await exportData(searchForm)
// 处理返回的文件流
const blob = new Blob([res.data])
const elink = document.createElement('a')
elink.download = '留宿申请.xlsx'
elink.style.display = 'none'
elink.href = URL.createObjectURL(blob)
document.body.appendChild(elink)
elink.click()
URL.revokeObjectURL(elink.href)
document.body.removeChild(elink)
useMessage().success('导出成功')
} catch (err: any) {
useMessage().error(err.msg || '导出失败')
}
}
// 获取学年列表
const getSchoolYearList = async () => {
try {
const res = await queryAllSchoolYear()
if (res.data) {
schoolYearList.value = Array.isArray(res.data) ? res.data : []
}
} catch (err) {
console.error('获取学年列表失败', err)
schoolYearList.value = []
}
}
// 获取学期字典
const getSchoolTermDict = async () => {
try {
const res = await getDicts('school_term')
if (res.data) {
schoolTermList.value = Array.isArray(res.data) ? res.data.map((item: any) => ({
label: item.label || item.dictLabel || item.name,
value: item.value || item.dictValue || item.code
})) : []
}
} catch (err) {
console.error('获取学期字典失败', err)
schoolTermList.value = []
}
}
// 获取楼号列表
const getBuildingListData = async () => {
try {
const res = await getBuildingList()
if (res.data) {
buildingList.value = Array.isArray(res.data) ? res.data : []
}
} catch (err) {
console.error('获取楼号列表失败', err)
buildingList.value = []
}
}
// 获取班级列表
const getClassListData = async () => {
try {
const res = await getClassListByRole()
if (res.data) {
classList.value = Array.isArray(res.data) ? res.data : []
}
} catch (err) {
console.error('获取班级列表失败', err)
classList.value = []
}
}
// 获取留宿类型字典
const getLiveTypeDict = async () => {
try {
const res = await getDicts('live_type')
if (res.data) {
liveTypeList.value = Array.isArray(res.data) ? res.data.map((item: any) => ({
label: item.label || item.dictLabel || item.name,
value: item.value || item.dictValue || item.code
})) : []
}
} catch (err) {
console.error('获取留宿类型字典失败', err)
liveTypeList.value = []
}
}
// 获取审核状态字典
const getAuditStatusDict = async () => {
try {
const res = await getDicts('dorm_live_audit_status')
if (res.data) {
auditStatusList.value = Array.isArray(res.data) ? res.data.map((item: any) => ({
label: item.label || item.dictLabel || item.name,
value: item.value || item.dictValue || item.code
})) : []
}
} catch (err) {
console.error('获取审核状态字典失败', err)
auditStatusList.value = []
}
}
// 初始化
onMounted(() => {
getSchoolYearList()
getSchoolTermDict()
getBuildingListData()
getClassListData()
getLiveTypeDict()
getAuditStatusDict()
})
</script>

View File

@@ -0,0 +1,516 @@
<template>
<el-dialog
:title="form.id ? '编辑' : '新增'"
v-model="visible"
:close-on-click-modal="false"
draggable
width="800px">
<el-form
ref="dataFormRef"
:model="form"
:rules="dataRules"
label-width="120px"
v-loading="loading">
<el-row :gutter="24">
<el-col :span="12" class="mb20">
<el-form-item label="开始时间" prop="startTime">
<el-date-picker
v-model="form.startTime"
type="datetime"
placeholder="选择开始时间"
format="YYYY-MM-DD HH:mm:ss"
value-format="YYYY-MM-DD HH:mm:ss"
style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="结束时间" prop="endTime">
<el-date-picker
v-model="form.endTime"
type="datetime"
placeholder="选择结束时间"
format="YYYY-MM-DD HH:mm:ss"
value-format="YYYY-MM-DD HH:mm:ss"
style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="班级" prop="classCode">
<el-select
v-model="form.classCode"
placeholder="请选择班级"
clearable
filterable
style="width: 100%"
@change="handleClassChange">
<el-option
v-for="item in classList"
:key="item.classCode"
:label="item.classNo"
:value="item.classCode">
</el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="选择学生" prop="selectedStudents">
<el-input
v-model="selectedStudentsText"
placeholder="请选择学生"
readonly
style="width: 100%"
@click="openStudentDialog">
<template #suffix>
<el-button
icon="Search"
circle
@click.stop="openStudentDialog" />
</template>
</el-input>
</el-form-item>
</el-col>
<el-col :span="24" class="mb20">
<el-form-item label="请假事由" prop="reason">
<el-input
v-model="form.reason"
type="textarea"
:rows="4"
placeholder="请输入请假事由" />
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="visible = false"> </el-button>
<el-button type="primary" @click="onSubmit" :disabled="loading"> </el-button>
</span>
</template>
</el-dialog>
<!-- 学生选择弹窗 -->
<el-dialog
title="选择学生"
v-model="studentDialogVisible"
:close-on-click-modal="false"
draggable
width="900px">
<!-- 搜索表单 -->
<el-form :model="studentSearchForm" :inline="true" @keyup.enter="handleStudentSearch">
<el-form-item label="班号" prop="classNo">
<el-input
v-model="studentSearchForm.classNo"
placeholder="请输入班号"
clearable
style="width: 200px" />
</el-form-item>
<el-form-item label="学号" prop="stuNo">
<el-input
v-model="studentSearchForm.stuNo"
placeholder="请输入学号"
clearable
style="width: 200px" />
</el-form-item>
<el-form-item label="姓名" prop="realName">
<el-input
v-model="studentSearchForm.realName"
placeholder="请输入姓名"
clearable
style="width: 200px" />
</el-form-item>
<el-form-item>
<el-button type="primary" plain icon="Search" @click="handleStudentSearch">查询</el-button>
<el-button icon="Refresh" @click="handleStudentReset">重置</el-button>
</el-form-item>
</el-form>
<!-- 学生表格 -->
<el-table
ref="studentTableRef"
:data="studentTableData"
v-loading="studentTableLoading"
border
@selection-change="handleStudentSelectionChange"
style="margin-top: 20px; max-height: 400px; overflow-y: auto;">
<el-table-column type="selection" width="55" align="center" />
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="classNo" label="班号" show-overflow-tooltip align="center" />
<el-table-column prop="stuNo" label="学号" show-overflow-tooltip align="center" />
<el-table-column prop="realName" label="姓名" show-overflow-tooltip align="center" />
</el-table>
<!-- 分页 -->
<pagination
@size-change="studentSizeChangeHandle"
@current-change="studentCurrentChangeHandle"
v-bind="studentPagination"
style="margin-top: 20px" />
<template #footer>
<span class="dialog-footer">
<el-button @click="studentDialogVisible = false"> </el-button>
<el-button type="primary" @click="confirmStudentSelection"> </el-button>
</span>
</template>
</el-dialog>
</template>
<script setup lang="ts" name="StuInnerLeaveApplyGroupFormDialog">
import { ref, reactive, nextTick, onMounted, computed } from 'vue'
import { useMessage } from '/@/hooks/message'
import { addObj, editObj, getDetail } from '/@/api/stuwork/stuinnerleaveapplygroup'
import { getClassListByRole } from '/@/api/basic/basicclass'
import { fetchList as getStudentList } from '/@/api/basic/basicstudent'
const emit = defineEmits(['refresh'])
// 定义变量内容
const dataFormRef = ref()
const studentTableRef = ref()
const visible = ref(false)
const loading = ref(false)
const operType = ref('add') // add 或 edit
const classList = ref<any[]>([])
// 学生选择弹窗相关
const studentDialogVisible = ref(false)
const studentTableLoading = ref(false)
const studentTableData = ref<any[]>([])
const selectedStudentRows = ref<any[]>([]) // 表格中选中的行
const studentSearchForm = reactive({
classNo: '',
stuNo: '',
realName: ''
})
const studentPagination = reactive({
total: 0,
pageSize: 10,
currentPage: 1
})
// 已选学生文本显示
const selectedStudentsText = computed(() => {
if (!form.selectedStudents || form.selectedStudents.length === 0) {
return ''
}
return `已选择 ${form.selectedStudents.length} 名学生`
})
// 提交表单数据
const form = reactive({
id: '',
startTime: '',
endTime: '',
classCode: '',
selectedStudents: [] as string[], // 选中的学号数组
reason: '',
stuList: [] as any[]
})
// 定义校验规则
const dataRules = {
startTime: [
{ required: true, message: '请选择开始时间', trigger: 'change' }
],
endTime: [
{ required: true, message: '请选择结束时间', trigger: 'change' }
],
classCode: [
{ required: true, message: '请选择班级', trigger: 'change' }
],
selectedStudents: [
{ required: true, message: '请至少选择一个学生', trigger: 'change', type: 'array', min: 1 }
],
reason: [
{ required: true, message: '请输入请假事由', trigger: 'blur' }
]
}
// 打开学生选择弹窗
const openStudentDialog = () => {
if (!form.classCode) {
useMessage().warning('请先选择班级')
return
}
studentDialogVisible.value = true
// 重置搜索表单,但保留班级代码
studentSearchForm.classNo = ''
studentSearchForm.stuNo = ''
studentSearchForm.realName = ''
// 加载学生列表
loadStudentTableData()
}
// 加载学生表格数据
const loadStudentTableData = async () => {
studentTableLoading.value = true
try {
const params: any = {
current: studentPagination.currentPage,
size: studentPagination.pageSize,
classCode: form.classCode
}
// 添加搜索条件
if (studentSearchForm.classNo) {
params.classNo = studentSearchForm.classNo
}
if (studentSearchForm.stuNo) {
params.stuNo = studentSearchForm.stuNo
}
if (studentSearchForm.realName) {
params.realName = studentSearchForm.realName
}
const res = await getStudentList(params)
if (res.data) {
studentTableData.value = res.data.records || []
studentPagination.total = res.data.total || 0
// 如果有已选中的学生,自动勾选
nextTick(() => {
if (form.selectedStudents && form.selectedStudents.length > 0 && studentTableRef.value) {
studentTableData.value.forEach((row: any) => {
if (form.selectedStudents.includes(row.stuNo)) {
studentTableRef.value.toggleRowSelection(row, true)
}
})
}
})
} else {
studentTableData.value = []
studentPagination.total = 0
}
} catch (err) {
console.error('获取学生列表失败', err)
studentTableData.value = []
studentPagination.total = 0
} finally {
studentTableLoading.value = false
}
}
// 学生搜索
const handleStudentSearch = () => {
studentPagination.currentPage = 1
loadStudentTableData()
}
// 学生搜索重置
const handleStudentReset = () => {
studentSearchForm.classNo = ''
studentSearchForm.stuNo = ''
studentSearchForm.realName = ''
studentPagination.currentPage = 1
loadStudentTableData()
}
// 学生表格选择变化
const handleStudentSelectionChange = (selection: any[]) => {
selectedStudentRows.value = selection
}
// 学生分页大小变化
const studentSizeChangeHandle = (size: number) => {
studentPagination.pageSize = size
studentPagination.currentPage = 1
loadStudentTableData()
}
// 学生分页页码变化
const studentCurrentChangeHandle = (page: number) => {
studentPagination.currentPage = page
loadStudentTableData()
}
// 确认学生选择
const confirmStudentSelection = () => {
if (selectedStudentRows.value.length === 0) {
useMessage().warning('请至少选择一个学生')
return
}
// 更新选中的学号数组
form.selectedStudents = selectedStudentRows.value.map((row: any) => row.stuNo)
studentDialogVisible.value = false
useMessage().success(`已选择 ${form.selectedStudents.length} 名学生`)
}
// 打开弹窗
const openDialog = async (type: string = 'add', row?: any) => {
visible.value = true
operType.value = type
// 重置表单数据
nextTick(() => {
dataFormRef.value?.resetFields()
form.id = ''
form.startTime = ''
form.endTime = ''
form.classCode = ''
form.selectedStudents = []
form.reason = ''
form.stuList = []
selectedStudentRows.value = []
// 编辑时填充数据
if (type === 'edit' && row) {
form.id = row.id
form.startTime = row.startTime || ''
form.endTime = row.endTime || ''
form.reason = row.reason || ''
// 获取详情以获取stuList和classCode
if (row.id) {
getDetailData(row.id)
}
}
})
}
// 获取详情
const getDetailData = async (id: string) => {
try {
loading.value = true
const res = await getDetail(id)
if (res.data && Array.isArray(res.data) && res.data.length > 0) {
const detail = res.data[0]
form.stuList = detail.stuList || []
// 如果有学生列表,提取学号和班级代码
if (form.stuList.length > 0) {
// 获取第一个学生的班级代码
const firstStudent = form.stuList[0]
if (firstStudent.classCode) {
form.classCode = firstStudent.classCode
// 设置选中的学生
form.selectedStudents = form.stuList.map((stu: any) => stu.stuNo)
}
}
}
} catch (err) {
console.error('获取详情失败', err)
} finally {
loading.value = false
}
}
// 提交表单
const onSubmit = async () => {
if (!dataFormRef.value) return
await dataFormRef.value.validate(async (valid: boolean) => {
if (!valid) return
// 验证结束时间必须大于开始时间
if (form.startTime && form.endTime) {
const start = new Date(form.startTime)
const end = new Date(form.endTime)
if (end <= start) {
useMessage().error('结束时间必须大于开始时间')
return
}
}
// 构建stuList数组
const buildStuList = async () => {
if (!form.selectedStudents || form.selectedStudents.length === 0) {
return []
}
// 需要重新获取学生信息来构建stuList
try {
const params: any = {
current: 1,
size: 1000,
classCode: form.classCode
}
const res = await getStudentList(params)
const allStudents = res.data?.records || []
return form.selectedStudents.map((stuNo: string) => {
const student = allStudents.find((s: any) => s.stuNo === stuNo)
if (student) {
return {
stuNo: student.stuNo,
realName: student.realName,
classCode: form.classCode,
status: '0', // 审核状态默认0
inOut: student.inOut || '1' // 是否允许离校默认1
}
}
return null
}).filter((item: any) => item !== null)
} catch (err) {
console.error('获取学生信息失败', err)
return []
}
}
loading.value = true
try {
const stuList = await buildStuList()
if (stuList.length === 0) {
useMessage().error('请至少选择一个学生')
loading.value = false
return
}
const submitData: any = {
startTime: form.startTime,
endTime: form.endTime,
reason: form.reason,
stuList: stuList
}
// 编辑时需要包含id
if (operType.value === 'edit') {
submitData.id = form.id
}
if (operType.value === 'add') {
await addObj(submitData)
useMessage().success('新增成功')
} else {
await editObj(submitData)
useMessage().success('编辑成功')
}
visible.value = false
emit('refresh')
} catch (err: any) {
useMessage().error(err.msg || (operType.value === 'add' ? '新增失败' : '编辑失败'))
} finally {
loading.value = false
}
})
}
// 获取班级列表
const getClassListData = async () => {
try {
const res = await getClassListByRole()
if (res.data) {
classList.value = Array.isArray(res.data) ? res.data : []
}
} catch (err) {
console.error('获取班级列表失败', err)
classList.value = []
}
}
// 初始化
onMounted(() => {
getClassListData()
})
// 暴露方法给父组件
defineExpose({
openDialog
})
</script>

View File

@@ -0,0 +1,196 @@
<template>
<div class="layout-padding">
<div class="layout-padding-auto layout-padding-view">
<!-- 搜索表单 -->
<el-row v-show="showSearch">
<el-form :model="searchForm" ref="searchFormRef" :inline="true" @keyup.enter="handleSearch">
<el-form-item label="发起部门" prop="deptCode">
<el-select
v-model="searchForm.deptCode"
placeholder="请选择发起部门"
clearable
filterable
style="width: 200px">
<el-option
v-for="item in deptList"
:key="item.deptCode"
:label="item.deptName"
:value="item.deptCode">
</el-option>
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" plain icon="Search" @click="handleSearch">查询</el-button>
<el-button icon="Refresh" @click="handleReset">重置</el-button>
</el-form-item>
</el-form>
</el-row>
<!-- 操作按钮 -->
<el-row>
<div class="mb8" style="width: 100%">
<el-button
icon="FolderAdd"
type="primary"
class="ml10"
@click="formDialogRef.openDialog()">
</el-button>
<right-toolbar
v-model:showSearch="showSearch"
class="ml10 mr20"
style="float: right;"
@queryTable="getDataList">
</right-toolbar>
</div>
</el-row>
<!-- 表格 -->
<el-table
:data="state.dataList"
v-loading="state.loading"
border
:cell-style="tableStyle.cellStyle"
:header-cell-style="tableStyle.headerCellStyle">
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="startTime" label="开始时间" show-overflow-tooltip align="center" width="180">
<template #default="scope">
<span>{{ scope.row.startTime ? formatDateTime(scope.row.startTime) : '-' }}</span>
</template>
</el-table-column>
<el-table-column prop="endTime" label="结束时间" show-overflow-tooltip align="center" width="180">
<template #default="scope">
<span>{{ scope.row.endTime ? formatDateTime(scope.row.endTime) : '-' }}</span>
</template>
</el-table-column>
<el-table-column prop="reason" label="请假事由" show-overflow-tooltip align="center" />
<el-table-column prop="deptName" label="发起部门" show-overflow-tooltip align="center" />
<el-table-column label="操作" width="150" align="center" fixed="right">
<template #default="scope">
<el-button
icon="Edit"
text
type="primary"
@click="handleEdit(scope.row)">
编辑
</el-button>
<el-button
icon="Delete"
text
type="danger"
@click="handleDelete(scope.row)">
删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<pagination
@size-change="sizeChangeHandle"
@current-change="currentChangeHandle"
v-bind="state.pagination" />
</div>
<!-- 新增/编辑表单弹窗 -->
<form-dialog ref="formDialogRef" @refresh="getDataList" />
</div>
</template>
<script setup lang="ts" name="StuInnerLeaveApplyGroup">
import { reactive, ref, onMounted } from 'vue'
import { BasicTableProps, useTable } from "/@/hooks/table";
import { fetchList, delObj } from "/@/api/stuwork/stuinnerleaveapplygroup";
import { getDeptListByLevelTwo } from "/@/api/basic/basicdept";
import { useMessage, useMessageBox } from "/@/hooks/message";
import FormDialog from './form.vue'
// 定义变量内容
const searchFormRef = ref()
const showSearch = ref(true)
const deptList = ref<any[]>([])
const formDialogRef = ref()
// 搜索表单
const searchForm = reactive({
deptCode: ''
})
// 配置 useTable
const state: BasicTableProps = reactive<BasicTableProps>({
queryForm: searchForm,
pageList: fetchList,
props: {
item: 'records',
totalCount: 'total'
},
createdIsNeed: true
})
// table hook
const {
getDataList,
currentChangeHandle,
sizeChangeHandle,
tableStyle
} = useTable(state)
// 格式化日期时间
const formatDateTime = (dateTime: string) => {
if (!dateTime) return '-'
// 如果包含时间部分,只显示日期部分
if (dateTime.includes(' ')) {
return dateTime.split(' ')[0]
}
return dateTime
}
// 查询
const handleSearch = () => {
getDataList()
}
// 重置
const handleReset = () => {
searchFormRef.value?.resetFields()
searchForm.deptCode = ''
getDataList()
}
// 编辑
const handleEdit = (row: any) => {
formDialogRef.value?.openDialog('edit', row)
}
// 删除
const handleDelete = async (row: any) => {
try {
await useMessageBox().confirm('确定要删除这条记录吗?')
await delObj([row.id])
useMessage().success('删除成功')
getDataList()
} catch (err: any) {
if (err !== 'cancel') {
useMessage().error(err.msg || '删除失败')
}
}
}
// 获取部门列表
const getDeptListData = async () => {
try {
const res = await getDeptListByLevelTwo()
if (res.data) {
deptList.value = Array.isArray(res.data) ? res.data : []
}
} catch (err) {
console.error('获取部门列表失败', err)
deptList.value = []
}
}
// 初始化
onMounted(() => {
getDeptListData()
})
</script>

View File

@@ -0,0 +1,327 @@
<template>
<el-dialog
title="新增"
v-model="visible"
:close-on-click-modal="false"
draggable
width="800px">
<el-form
ref="dataFormRef"
:model="form"
:rules="dataRules"
label-width="120px"
v-loading="loading">
<el-row :gutter="24">
<el-col :span="12" class="mb20">
<el-form-item label="班级" prop="classCode">
<el-select
v-model="form.classCode"
placeholder="请选择班级"
clearable
filterable
style="width: 100%"
@change="handleClassChange">
<el-option
v-for="item in classList"
:key="item.classCode"
:label="item.classNo"
:value="item.classCode">
</el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="学号" prop="stuNo">
<el-select
v-model="form.stuNo"
placeholder="请选择学生"
clearable
filterable
style="width: 100%"
:disabled="!form.classCode"
@change="handleStudentChange">
<el-option
v-for="item in studentList"
:key="item.stuNo"
:label="`${item.realName} (${item.stuNo})`"
:value="item.stuNo">
</el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="24" class="mb20">
<el-form-item label="请假时间" prop="leaveTime">
<el-date-picker
v-model="form.leaveTime"
type="datetimerange"
range-separator=""
start-placeholder="开始时间"
end-placeholder="结束时间"
format="YYYY-MM-DD HH:mm"
value-format="YYYY-MM-DD HH:mm:ss"
style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="请假类型" prop="leaveType">
<el-select
v-model="form.leaveType"
placeholder="请选择请假类型"
clearable
style="width: 100%">
<el-option
v-for="item in leaveTypeList"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="是否住宿" prop="stayDorm">
<el-switch
v-model="form.stayDorm"
active-value="1"
inactive-value="0"
active-text=""
inactive-text="" />
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="是否发热" prop="isFever">
<el-switch
v-model="form.isFever"
active-value="1"
inactive-value="0"
active-text=""
inactive-text="" />
</el-form-item>
</el-col>
<el-col :span="24" class="mb20">
<el-form-item label="请假事由" prop="reason">
<el-input
v-model="form.reason"
type="textarea"
:rows="4"
placeholder="请输入请假事由" />
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="visible = false"> </el-button>
<el-button type="primary" @click="onSubmit" :disabled="loading"> </el-button>
</span>
</template>
</el-dialog>
</template>
<script setup lang="ts" name="StuLeaveApplyFormDialog">
import { ref, reactive, nextTick, onMounted } from 'vue'
import { useMessage } from '/@/hooks/message'
import { addObj } from '/@/api/stuwork/stuleaveapply'
import { getDicts } from '/@/api/admin/dict'
import { getClassListByRole } from '/@/api/basic/basicclass'
import { queryStudentListByClass } from '/@/api/basic/basicstudent'
const emit = defineEmits(['refresh'])
// 定义变量内容
const dataFormRef = ref()
const visible = ref(false)
const loading = ref(false)
const classList = ref<any[]>([])
const studentList = ref<any[]>([])
const leaveTypeList = ref<any[]>([])
const yesNoList = ref<any[]>([])
// 提交表单数据
const form = reactive({
classCode: '',
stuNo: '',
leaveTime: null as [string, string] | null,
leaveType: '',
reason: '',
stayDorm: '',
isFever: ''
})
// 定义校验规则
const dataRules = {
classCode: [
{ required: true, message: '请选择班级', trigger: 'change' }
],
stuNo: [
{ required: true, message: '请选择学生', trigger: 'change' }
],
leaveTime: [
{ required: true, message: '请选择请假时间', trigger: 'change', type: 'array', min: 2 }
],
leaveType: [
{ required: true, message: '请选择请假类型', trigger: 'change' }
],
reason: [
{ required: true, message: '请输入请假事由', trigger: 'blur' }
],
stayDorm: [
{ required: true, message: '请选择是否住宿', trigger: 'change' }
],
isFever: [
{ required: true, message: '请选择是否发热', trigger: 'change' }
]
}
// 班级变化时获取学生列表
const handleClassChange = async (classCode: string) => {
form.stuNo = ''
studentList.value = []
if (!classCode) {
return
}
try {
const res = await queryStudentListByClass({ classCode, current: 1, size: 10000 })
if (res.data && Array.isArray(res.data.records)) {
studentList.value = res.data.records
} else {
studentList.value = []
}
} catch (err) {
console.error('获取学生列表失败', err)
studentList.value = []
}
}
// 学生变化
const handleStudentChange = (stuNo: string) => {
// 可以在这里处理学生变化后的逻辑
}
// 打开弹窗
const openDialog = async () => {
visible.value = true
// 重置表单数据
nextTick(() => {
dataFormRef.value?.resetFields()
form.classCode = ''
form.stuNo = ''
form.leaveTime = null
form.leaveType = ''
form.reason = ''
form.stayDorm = '0'
form.isFever = '0'
studentList.value = []
})
}
// 提交表单
const onSubmit = async () => {
if (!dataFormRef.value) return
await dataFormRef.value.validate(async (valid: boolean) => {
if (!valid) return
// 验证请假时间范围
if (!form.leaveTime || form.leaveTime.length !== 2) {
useMessage().error('请选择请假时间范围')
return
}
const [startTime, endTime] = form.leaveTime
const start = new Date(startTime)
const end = new Date(endTime)
if (end <= start) {
useMessage().error('请假结束时间必须大于开始时间')
return
}
loading.value = true
try {
await addObj({
classCode: form.classCode,
stuNo: form.stuNo,
startTime: startTime,
endTime: endTime,
leaveType: form.leaveType,
reason: form.reason,
stayDorm: form.stayDorm,
isFever: form.isFever
})
useMessage().success('新增成功')
visible.value = false
emit('refresh')
} catch (err: any) {
useMessage().error(err.msg || '新增失败')
} finally {
loading.value = false
}
})
}
// 获取班级列表
const getClassListData = async () => {
try {
const res = await getClassListByRole()
if (res.data) {
classList.value = Array.isArray(res.data) ? res.data : []
}
} catch (err) {
console.error('获取班级列表失败', err)
classList.value = []
}
}
// 获取请假类型字典
const getLeaveTypeDict = async () => {
try {
const res = await getDicts('leave_type')
if (res.data) {
leaveTypeList.value = Array.isArray(res.data) ? res.data.map((item: any) => ({
label: item.label || item.dictLabel || item.name,
value: item.value || item.dictValue || item.code
})) : []
}
} catch (err) {
console.error('获取请假类型字典失败', err)
leaveTypeList.value = []
}
}
// 获取是否字典
const getYesNoDict = async () => {
try {
const res = await getDicts('yes_no')
if (res.data) {
yesNoList.value = Array.isArray(res.data) ? res.data.map((item: any) => ({
label: item.label || item.dictLabel || item.name,
value: item.value || item.dictValue || item.code
})) : []
}
} catch (err) {
console.error('获取是否字典失败', err)
yesNoList.value = []
}
}
// 初始化
onMounted(() => {
getClassListData()
getLeaveTypeDict()
getYesNoDict()
})
// 暴露方法给父组件
defineExpose({
openDialog
})
</script>

View File

@@ -0,0 +1,537 @@
<template>
<div class="layout-padding">
<div class="layout-padding-auto layout-padding-view">
<!-- 搜索表单 -->
<el-row v-show="showSearch">
<el-form :model="searchForm" ref="searchFormRef" :inline="true" @keyup.enter="handleSearch">
<el-form-item label="学年" prop="schoolYear">
<el-select
v-model="searchForm.schoolYear"
placeholder="请选择学年"
clearable
filterable
style="width: 200px">
<el-option
v-for="item in schoolYearList"
:key="item.year"
:label="item.year"
:value="item.year">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="学期" prop="schoolTerm">
<el-select
v-model="searchForm.schoolTerm"
placeholder="请选择学期"
clearable
style="width: 200px">
<el-option
v-for="item in schoolTermList"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="学院" prop="deptCode">
<el-select
v-model="searchForm.deptCode"
placeholder="请选择学院"
clearable
filterable
style="width: 200px">
<el-option
v-for="item in deptList"
:key="item.deptCode"
:label="item.deptName"
:value="item.deptCode">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="班级" prop="classCode">
<el-select
v-model="searchForm.classCode"
placeholder="请选择班级"
clearable
filterable
style="width: 200px">
<el-option
v-for="item in classList"
:key="item.classCode"
:label="item.classNo"
:value="item.classCode">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="姓名" prop="realName">
<el-input
v-model="searchForm.realName"
placeholder="请输入姓名"
clearable
style="width: 200px" />
</el-form-item>
<el-form-item label="请假类型" prop="leaveType">
<el-select
v-model="searchForm.leaveType"
placeholder="请选择请假类型"
clearable
style="width: 200px">
<el-option
v-for="item in leaveTypeList"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="住宿类型" prop="goOrStay">
<el-select
v-model="searchForm.goOrStay"
placeholder="请选择住宿类型"
clearable
style="width: 200px">
<el-option label="走读生" value="0" />
<el-option label="住宿生" value="1" />
</el-select>
</el-form-item>
<el-form-item label="是否住宿" prop="stayDorm">
<el-select
v-model="searchForm.stayDorm"
placeholder="请选择是否住宿"
clearable
style="width: 200px">
<el-option
v-for="item in yesNoList"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="班主任审核" prop="classAudit">
<el-select
v-model="searchForm.classAudit"
placeholder="请选择班主任审核"
clearable
style="width: 200px">
<el-option
v-for="item in auditTypeList"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="学生科审核" prop="deptAudit">
<el-select
v-model="searchForm.deptAudit"
placeholder="请选择学生科审核"
clearable
style="width: 200px">
<el-option
v-for="item in auditTypeList"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="学工处审批" prop="schoolAudit">
<el-select
v-model="searchForm.schoolAudit"
placeholder="请选择学工处审批"
clearable
style="width: 200px">
<el-option
v-for="item in auditTypeList"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" plain icon="Search" @click="handleSearch">查询</el-button>
<el-button icon="Refresh" @click="handleReset">重置</el-button>
</el-form-item>
</el-form>
</el-row>
<!-- 操作按钮 -->
<el-row>
<div class="mb8" style="width: 100%">
<el-button
icon="FolderAdd"
type="primary"
class="ml10"
@click="formDialogRef.openDialog()">
</el-button>
<el-button
icon="Download"
type="success"
class="ml10"
@click="handleExport">
</el-button>
<right-toolbar
v-model:showSearch="showSearch"
class="ml10 mr20"
style="float: right;"
@queryTable="getDataList">
</right-toolbar>
</div>
</el-row>
<!-- 表格 -->
<el-table
:data="state.dataList"
v-loading="state.loading"
border
:cell-style="tableStyle.cellStyle"
:header-cell-style="tableStyle.headerCellStyle">
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="schoolYear" label="学年" show-overflow-tooltip align="center" />
<el-table-column prop="schoolTerm" label="学期" show-overflow-tooltip align="center">
<template #default="scope">
<span>{{ formatSchoolTerm(scope.row.schoolTerm) }}</span>
</template>
</el-table-column>
<el-table-column prop="deptName" label="学院" show-overflow-tooltip align="center" />
<el-table-column prop="realName" label="姓名" show-overflow-tooltip align="center" />
<el-table-column prop="startTime" label="请假开始时间" show-overflow-tooltip align="center" width="180">
<template #default="scope">
<span>{{ scope.row.startTime ? formatDateTime(scope.row.startTime) : '-' }}</span>
</template>
</el-table-column>
<el-table-column prop="endTime" label="请假结束时间" show-overflow-tooltip align="center" width="180">
<template #default="scope">
<span>{{ scope.row.endTime ? formatDateTime(scope.row.endTime) : '-' }}</span>
</template>
</el-table-column>
<el-table-column prop="leaveType" label="请假类型" show-overflow-tooltip align="center">
<template #default="scope">
<span>{{ formatLeaveType(scope.row.leaveType) }}</span>
</template>
</el-table-column>
<el-table-column prop="reason" label="请假事由" show-overflow-tooltip align="center" min-width="150" />
<el-table-column prop="stayDorm" label="是否住宿" show-overflow-tooltip align="center">
<template #default="scope">
<span>{{ formatYesNo(scope.row.stayDorm) }}</span>
</template>
</el-table-column>
<el-table-column prop="isFever" label="是否发热" show-overflow-tooltip align="center">
<template #default="scope">
<span>{{ formatYesNo(scope.row.isFever) }}</span>
</template>
</el-table-column>
<el-table-column prop="classAudit" label="班主任审核" show-overflow-tooltip align="center">
<template #default="scope">
<span>{{ formatAuditType(scope.row.classAudit) }}</span>
</template>
</el-table-column>
<el-table-column prop="deptAudit" label="学生科审核" show-overflow-tooltip align="center">
<template #default="scope">
<span>{{ formatAuditType(scope.row.deptAudit) }}</span>
</template>
</el-table-column>
<el-table-column prop="schoolAudit" label="学工处审批" show-overflow-tooltip align="center">
<template #default="scope">
<span>{{ formatAuditType(scope.row.schoolAudit) }}</span>
</template>
</el-table-column>
<el-table-column prop="remarks" label="备注" show-overflow-tooltip align="center" min-width="150" />
<el-table-column prop="rejectReason" label="驳回原因" show-overflow-tooltip align="center" min-width="150" />
<el-table-column label="操作" width="100" align="center" fixed="right">
<template #default="scope">
<el-button
icon="CircleClose"
text
type="warning"
@click="handleCancel(scope.row)">
撤销
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<pagination
@size-change="sizeChangeHandle"
@current-change="currentChangeHandle"
v-bind="state.pagination" />
</div>
<!-- 新增表单弹窗 -->
<form-dialog ref="formDialogRef" @refresh="getDataList" />
</div>
</template>
<script setup lang="ts" name="StuLeaveApply">
import { reactive, ref, onMounted } from 'vue'
import { BasicTableProps, useTable } from "/@/hooks/table";
import { fetchList, exportData, cancelObj } from "/@/api/stuwork/stuleaveapply";
import { queryAllSchoolYear } from "/@/api/basic/basicyear";
import { getDicts } from "/@/api/admin/dict";
import { getDeptListByLevelTwo } from "/@/api/basic/basicdept";
import { getClassListByRole } from "/@/api/basic/basicclass";
import { useMessage, useMessageBox } from "/@/hooks/message";
import FormDialog from './form.vue'
// 定义变量内容
const searchFormRef = ref()
const showSearch = ref(true)
const schoolYearList = ref<any[]>([])
const schoolTermList = ref<any[]>([])
const deptList = ref<any[]>([])
const classList = ref<any[]>([])
const leaveTypeList = ref<any[]>([])
const yesNoList = ref<any[]>([])
const auditTypeList = ref<any[]>([])
const formDialogRef = ref()
// 搜索表单
const searchForm = reactive({
schoolYear: '',
schoolTerm: '',
deptCode: '',
classCode: '',
realName: '',
leaveType: '',
goOrStay: '',
stayDorm: '',
classAudit: '',
deptAudit: '',
schoolAudit: ''
})
// 配置 useTable
const state: BasicTableProps = reactive<BasicTableProps>({
queryForm: searchForm,
pageList: fetchList,
props: {
item: 'records',
totalCount: 'total'
},
createdIsNeed: true
})
// table hook
const {
getDataList,
currentChangeHandle,
sizeChangeHandle,
tableStyle
} = useTable(state)
// 格式化学期
const formatSchoolTerm = (value: string | number) => {
if (value === null || value === undefined || value === '') {
return '-'
}
const dictItem = schoolTermList.value.find(item => item.value == value)
return dictItem ? dictItem.label : value
}
// 格式化日期时间
const formatDateTime = (dateTime: string) => {
if (!dateTime) return '-'
// 如果包含时间部分,只显示日期和时间
if (dateTime.includes(' ')) {
return dateTime.split('.')[0] // 移除毫秒部分
}
return dateTime
}
// 格式化请假类型
const formatLeaveType = (value: string) => {
if (!value) return '-'
const item = leaveTypeList.value.find((item: any) => item.value === value)
return item ? item.label : value
}
// 格式化是否
const formatYesNo = (value: string | null) => {
if (value === null || value === undefined || value === '') return '-'
const item = yesNoList.value.find((item: any) => item.value === value)
return item ? item.label : (value === '1' || value === 'true' ? '是' : '否')
}
// 格式化审核类型
const formatAuditType = (value: string) => {
if (!value) return '-'
const item = auditTypeList.value.find((item: any) => item.value === value)
return item ? item.label : value
}
// 查询
const handleSearch = () => {
getDataList()
}
// 重置
const handleReset = () => {
searchFormRef.value?.resetFields()
searchForm.schoolYear = ''
searchForm.schoolTerm = ''
searchForm.deptCode = ''
searchForm.classCode = ''
searchForm.realName = ''
searchForm.leaveType = ''
searchForm.goOrStay = ''
searchForm.stayDorm = ''
searchForm.classAudit = ''
searchForm.deptAudit = ''
searchForm.schoolAudit = ''
getDataList()
}
// 撤销
const handleCancel = async (row: any) => {
try {
await useMessageBox().confirm('确定要撤销这条请假记录吗?')
await cancelObj(row.id)
useMessage().success('撤销成功')
getDataList()
} catch (err: any) {
if (err !== 'cancel') {
useMessage().error(err.msg || '撤销失败')
}
}
}
// 导出
const handleExport = async () => {
try {
const res = await exportData(searchForm)
// 处理返回的文件流
const blob = new Blob([res.data])
const elink = document.createElement('a')
elink.download = '学生请假.xlsx'
elink.style.display = 'none'
elink.href = URL.createObjectURL(blob)
document.body.appendChild(elink)
elink.click()
URL.revokeObjectURL(elink.href)
document.body.removeChild(elink)
useMessage().success('导出成功')
} catch (err: any) {
useMessage().error(err.msg || '导出失败')
}
}
// 获取学年列表
const getSchoolYearList = async () => {
try {
const res = await queryAllSchoolYear()
if (res.data) {
schoolYearList.value = Array.isArray(res.data) ? res.data : []
}
} catch (err) {
console.error('获取学年列表失败', err)
schoolYearList.value = []
}
}
// 获取学期字典
const getSchoolTermDict = async () => {
try {
const res = await getDicts('school_term')
if (res.data) {
schoolTermList.value = Array.isArray(res.data) ? res.data.map((item: any) => ({
label: item.label || item.dictLabel || item.name,
value: item.value || item.dictValue || item.code
})) : []
}
} catch (err) {
console.error('获取学期字典失败', err)
schoolTermList.value = []
}
}
// 获取学院列表
const getDeptListData = async () => {
try {
const res = await getDeptListByLevelTwo()
if (res.data) {
deptList.value = Array.isArray(res.data) ? res.data : []
}
} catch (err) {
console.error('获取学院列表失败', err)
deptList.value = []
}
}
// 获取班级列表
const getClassListData = async () => {
try {
const res = await getClassListByRole()
if (res.data) {
classList.value = Array.isArray(res.data) ? res.data : []
}
} catch (err) {
console.error('获取班级列表失败', err)
classList.value = []
}
}
// 获取请假类型字典
const getLeaveTypeDict = async () => {
try {
const res = await getDicts('leave_type')
if (res.data) {
leaveTypeList.value = Array.isArray(res.data) ? res.data.map((item: any) => ({
label: item.label || item.dictLabel || item.name,
value: item.value || item.dictValue || item.code
})) : []
}
} catch (err) {
console.error('获取请假类型字典失败', err)
leaveTypeList.value = []
}
}
// 获取是否字典
const getYesNoDict = async () => {
try {
const res = await getDicts('yes_no')
if (res.data) {
yesNoList.value = Array.isArray(res.data) ? res.data.map((item: any) => ({
label: item.label || item.dictLabel || item.name,
value: item.value || item.dictValue || item.code
})) : []
}
} catch (err) {
console.error('获取是否字典失败', err)
yesNoList.value = []
}
}
// 获取审核类型字典
const getAuditTypeDict = async () => {
try {
const res = await getDicts('class_audit_type')
if (res.data) {
auditTypeList.value = Array.isArray(res.data) ? res.data.map((item: any) => ({
label: item.label || item.dictLabel || item.name,
value: item.value || item.dictValue || item.code
})) : []
}
} catch (err) {
console.error('获取审核类型字典失败', err)
auditTypeList.value = []
}
}
// 初始化
onMounted(() => {
getSchoolYearList()
getSchoolTermDict()
getDeptListData()
getClassListData()
getLeaveTypeDict()
getYesNoDict()
getAuditTypeDict()
})
</script>

View File

@@ -0,0 +1,386 @@
<template>
<el-dialog
:title="form.id ? '编辑' : '新增'"
v-model="visible"
:close-on-click-modal="false"
draggable
width="800px">
<el-form
ref="dataFormRef"
:model="form"
:rules="dataRules"
label-width="120px"
v-loading="loading">
<el-row :gutter="24">
<el-col :span="12" class="mb20">
<el-form-item label="班级" prop="classCode">
<el-select
v-model="form.classCode"
placeholder="请选择班级"
clearable
filterable
style="width: 100%"
@change="handleClassChange">
<el-option
v-for="item in classList"
:key="item.classCode"
:label="item.classNo"
:value="item.classCode">
</el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="学号" prop="stuNo">
<el-select
v-model="form.stuNo"
placeholder="请选择学生"
clearable
filterable
style="width: 100%"
:disabled="!form.classCode"
@change="handleStudentChange">
<el-option
v-for="item in studentList"
:key="item.stuNo"
:label="`${item.realName} (${item.stuNo})`"
:value="item.stuNo">
</el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="处分时间" prop="punlishStartDate">
<el-date-picker
v-model="form.punlishStartDate"
type="date"
placeholder="选择处分时间"
format="YYYY-MM-DD"
value-format="YYYY-MM-DD HH:mm:ss"
style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="到期日" prop="punlishEndDate">
<el-date-picker
v-model="form.punlishEndDate"
type="date"
placeholder="选择到期日"
format="YYYY-MM-DD"
value-format="YYYY-MM-DD HH:mm:ss"
style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="处分级别" prop="punlishLevel">
<el-select
v-model="form.punlishLevel"
placeholder="请选择处分级别"
clearable
style="width: 100%">
<el-option
v-for="item in punlishLevelList"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="24" class="mb20">
<el-form-item label="处分内容" prop="punlishContent">
<el-input
v-model="form.punlishContent"
type="textarea"
:rows="4"
placeholder="请输入处分内容" />
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="visible = false"> </el-button>
<el-button type="primary" @click="onSubmit" :disabled="loading"> </el-button>
</span>
</template>
</el-dialog>
</template>
<script setup lang="ts" name="StuPunlishFormDialog">
import { ref, reactive, nextTick, onMounted, watch } from 'vue'
import { useMessage } from '/@/hooks/message'
import { addObj, editObj, getDetail } from '/@/api/stuwork/stupunlish'
import { queryAllSchoolYear } from '/@/api/basic/basicyear'
import { getDicts } from '/@/api/admin/dict'
import { getClassListByRole } from '/@/api/basic/basicclass'
import { queryStudentListByClass } from '/@/api/basic/basicstudent'
const emit = defineEmits(['refresh'])
// 定义变量内容
const dataFormRef = ref()
const visible = ref(false)
const loading = ref(false)
const operType = ref('add') // add 或 edit
const schoolYearList = ref<any[]>([])
const schoolTermList = ref<any[]>([])
const classList = ref<any[]>([])
const studentList = ref<any[]>([])
const punlishLevelList = ref<any[]>([])
const publishStatusList = ref<any[]>([])
// 提交表单数据
const form = reactive({
id: '',
schoolYear: '',
schoolTerm: '',
classCode: '',
stuNo: '',
punlishStartDate: '',
punlishEndDate: '',
punlishLevel: '',
punlishContent: '',
publishStatus: '',
attachment: ''
})
// 定义校验规则
const dataRules = {
classCode: [
{ required: true, message: '请选择班级', trigger: 'change' }
],
stuNo: [
{ required: true, message: '请选择学生', trigger: 'change' }
],
punlishStartDate: [
{ required: true, message: '请选择处分时间', trigger: 'change' }
],
punlishEndDate: [
{ required: true, message: '请选择到期日', trigger: 'change' }
],
punlishLevel: [
{ required: true, message: '请选择处分级别', trigger: 'change' }
],
punlishContent: [
{ required: true, message: '请输入处分内容', trigger: 'blur' }
]
}
// 班级变化时获取学生列表
const handleClassChange = async (classCode: string) => {
form.stuNo = ''
studentList.value = []
if (!classCode) {
return
}
try {
const res = await queryStudentListByClass({ classCode, current: 1, size: 10000 })
if (res.data && Array.isArray(res.data.records)) {
studentList.value = res.data.records
} else {
studentList.value = []
}
} catch (err) {
console.error('获取学生列表失败', err)
studentList.value = []
}
}
// 学生变化
const handleStudentChange = (stuNo: string) => {
// 可以在这里处理学生变化后的逻辑
}
// 打开弹窗
const openDialog = async (type: string = 'add', row?: any) => {
visible.value = true
operType.value = type
// 重置表单数据
nextTick(() => {
dataFormRef.value?.resetFields()
form.id = ''
form.schoolYear = ''
form.schoolTerm = ''
form.classCode = ''
form.stuNo = ''
form.punlishStartDate = ''
form.punlishEndDate = ''
form.punlishLevel = ''
form.punlishContent = ''
form.publishStatus = ''
form.attachment = ''
studentList.value = []
// 编辑时填充数据
if (type === 'edit' && row) {
form.id = row.id
form.schoolYear = row.schoolYear || ''
form.schoolTerm = row.schoolTerm || ''
form.classCode = row.classCode || ''
form.stuNo = row.stuNo || ''
form.punlishStartDate = row.punlishStartDate || ''
form.punlishEndDate = row.punlishEndDate || ''
form.punlishLevel = row.punlishLevel || ''
form.punlishContent = row.punlishContent || ''
form.publishStatus = row.publishStatus || ''
form.attachment = row.attachment || ''
// 如果有班级代码,加载学生列表
if (form.classCode) {
handleClassChange(form.classCode)
}
}
})
}
// 提交表单
const onSubmit = async () => {
if (!dataFormRef.value) return
await dataFormRef.value.validate(async (valid: boolean) => {
if (!valid) return
// 验证到期日必须大于处分时间
if (form.punlishStartDate && form.punlishEndDate) {
const start = new Date(form.punlishStartDate)
const end = new Date(form.punlishEndDate)
if (end <= start) {
useMessage().error('到期日必须大于处分时间')
return
}
}
loading.value = true
try {
const submitData: any = {
classCode: form.classCode,
stuNo: form.stuNo,
punlishStartDate: form.punlishStartDate,
punlishEndDate: form.punlishEndDate,
punlishLevel: form.punlishLevel,
punlishContent: form.punlishContent,
publishStatus: form.publishStatus || '1',
attachment: form.attachment || ''
}
// 编辑时需要包含id和学年学期
if (operType.value === 'edit') {
submitData.id = form.id
submitData.schoolYear = form.schoolYear
submitData.schoolTerm = form.schoolTerm
}
if (operType.value === 'add') {
await addObj(submitData)
useMessage().success('新增成功')
} else {
await editObj(submitData)
useMessage().success('编辑成功')
}
visible.value = false
emit('refresh')
} catch (err: any) {
useMessage().error(err.msg || (operType.value === 'add' ? '新增失败' : '编辑失败'))
} finally {
loading.value = false
}
})
}
// 获取学年列表
const getSchoolYearList = async () => {
try {
const res = await queryAllSchoolYear()
if (res.data) {
schoolYearList.value = Array.isArray(res.data) ? res.data : []
}
} catch (err) {
console.error('获取学年列表失败', err)
schoolYearList.value = []
}
}
// 获取学期字典
const getSchoolTermDict = async () => {
try {
const res = await getDicts('school_term')
if (res.data) {
schoolTermList.value = Array.isArray(res.data) ? res.data.map((item: any) => ({
label: item.label || item.dictLabel || item.name,
value: item.value || item.dictValue || item.code
})) : []
}
} catch (err) {
console.error('获取学期字典失败', err)
schoolTermList.value = []
}
}
// 获取班级列表
const getClassListData = async () => {
try {
const res = await getClassListByRole()
if (res.data) {
classList.value = Array.isArray(res.data) ? res.data : []
}
} catch (err) {
console.error('获取班级列表失败', err)
classList.value = []
}
}
// 获取处分级别字典
const getPunlishLevelDict = async () => {
try {
const res = await getDicts('punlish_level')
if (res.data) {
punlishLevelList.value = Array.isArray(res.data) ? res.data.map((item: any) => ({
label: item.label || item.dictLabel || item.name,
value: item.value || item.dictValue || item.code
})) : []
}
} catch (err) {
console.error('获取处分级别字典失败', err)
punlishLevelList.value = []
}
}
// 获取处分状态字典
const getPublishStatusDict = async () => {
try {
const res = await getDicts('publish_status')
if (res.data) {
publishStatusList.value = Array.isArray(res.data) ? res.data.map((item: any) => ({
label: item.label || item.dictLabel || item.name,
value: item.value || item.dictValue || item.code
})) : []
}
} catch (err) {
console.error('获取处分状态字典失败', err)
publishStatusList.value = []
}
}
// 初始化
onMounted(() => {
getSchoolYearList()
getSchoolTermDict()
getClassListData()
getPunlishLevelDict()
getPublishStatusDict()
})
// 暴露方法给父组件
defineExpose({
openDialog
})
</script>

View File

@@ -0,0 +1,517 @@
<template>
<div class="layout-padding">
<div class="layout-padding-auto layout-padding-view">
<!-- 搜索表单 -->
<el-row v-show="showSearch">
<el-form :model="searchForm" ref="searchFormRef" :inline="true" @keyup.enter="handleSearch">
<el-form-item label="学年" prop="schoolYear">
<el-select
v-model="searchForm.schoolYear"
placeholder="请选择学年"
clearable
filterable
style="width: 200px">
<el-option
v-for="item in schoolYearList"
:key="item.year"
:label="item.year"
:value="item.year">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="学期" prop="schoolTerm">
<el-select
v-model="searchForm.schoolTerm"
placeholder="请选择学期"
clearable
style="width: 200px">
<el-option
v-for="item in schoolTermList"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="学院" prop="deptCode">
<el-select
v-model="searchForm.deptCode"
placeholder="请选择学院"
clearable
filterable
style="width: 200px">
<el-option
v-for="item in deptList"
:key="item.deptCode"
:label="item.deptName"
:value="item.deptCode">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="班级" prop="classCode">
<el-select
v-model="searchForm.classCode"
placeholder="请选择班级"
clearable
filterable
style="width: 200px">
<el-option
v-for="item in classList"
:key="item.classCode"
:label="item.classNo"
:value="item.classCode">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="姓名" prop="realName">
<el-input
v-model="searchForm.realName"
placeholder="请输入姓名"
clearable
style="width: 200px" />
</el-form-item>
<el-form-item label="学号" prop="stuNo">
<el-input
v-model="searchForm.stuNo"
placeholder="请输入学号"
clearable
style="width: 200px" />
</el-form-item>
<el-form-item label="处分月份" prop="punlishMonth">
<el-date-picker
v-model="searchForm.punlishMonth"
type="month"
placeholder="选择处分月份"
format="YYYY-MM"
value-format="YYYY-MM"
clearable
style="width: 200px" />
</el-form-item>
<el-form-item label="处分级别" prop="punlishLevel">
<el-select
v-model="searchForm.punlishLevel"
placeholder="请选择处分级别"
clearable
style="width: 200px">
<el-option
v-for="item in punlishLevelList"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" plain icon="Search" @click="handleSearch">查询</el-button>
<el-button icon="Refresh" @click="handleReset">重置</el-button>
</el-form-item>
</el-form>
</el-row>
<!-- 操作按钮 -->
<el-row>
<div class="mb8" style="width: 100%">
<el-button
icon="FolderAdd"
type="primary"
class="ml10"
@click="formDialogRef.openDialog()">
</el-button>
<el-button
icon="Download"
type="success"
class="ml10"
@click="handleExport">
</el-button>
<right-toolbar
v-model:showSearch="showSearch"
class="ml10 mr20"
style="float: right;"
@queryTable="getDataList">
</right-toolbar>
</div>
</el-row>
<!-- 表格 -->
<el-table
:data="state.dataList"
v-loading="state.loading"
border
:cell-style="tableStyle.cellStyle"
:header-cell-style="tableStyle.headerCellStyle">
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="schoolYear" label="学年" show-overflow-tooltip align="center" />
<el-table-column prop="schoolTerm" label="学期" show-overflow-tooltip align="center">
<template #default="scope">
<span>{{ formatSchoolTerm(scope.row.schoolTerm) }}</span>
</template>
</el-table-column>
<el-table-column prop="deptName" label="学院" show-overflow-tooltip align="center" />
<el-table-column prop="classNo" label="班级" show-overflow-tooltip align="center" />
<el-table-column prop="stuRealName" label="姓名" show-overflow-tooltip align="center" />
<el-table-column prop="stuNo" label="学号" show-overflow-tooltip align="center" />
<el-table-column prop="punlishStartDate" label="处分时间" show-overflow-tooltip align="center" width="180">
<template #default="scope">
<span>{{ scope.row.punlishStartDate ? formatDate(scope.row.punlishStartDate) : '-' }}</span>
</template>
</el-table-column>
<el-table-column prop="punlishEndDate" label="到期日" show-overflow-tooltip align="center" width="180">
<template #default="scope">
<span>{{ scope.row.punlishEndDate ? formatDate(scope.row.punlishEndDate) : '-' }}</span>
</template>
</el-table-column>
<el-table-column prop="punlishLevel" label="处分级别" show-overflow-tooltip align="center">
<template #default="scope">
<span>{{ formatPunlishLevel(scope.row.punlishLevel) }}</span>
</template>
</el-table-column>
<el-table-column prop="punlishContent" label="处分内容" show-overflow-tooltip align="center" min-width="150" />
<el-table-column prop="publishStatus" label="处分状态" show-overflow-tooltip align="center">
<template #default="scope">
<span>{{ formatPublishStatus(scope.row.publishStatus) }}</span>
</template>
</el-table-column>
<el-table-column label="操作" width="250" align="center" fixed="right">
<template #default="scope">
<el-button
icon="Document"
text
type="primary"
@click="handleReport(scope.row)">
思想汇报
</el-button>
<el-button
icon="CircleClose"
text
type="warning"
@click="handleCancel(scope.row)">
撤销处分
</el-button>
<el-button
icon="Edit"
text
type="primary"
@click="handleEdit(scope.row)">
编辑
</el-button>
<el-button
icon="Delete"
text
type="danger"
@click="handleDelete(scope.row)">
删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<pagination
@size-change="sizeChangeHandle"
@current-change="currentChangeHandle"
v-bind="state.pagination" />
</div>
<!-- 新增/编辑表单弹窗 -->
<form-dialog ref="formDialogRef" @refresh="getDataList" />
</div>
</template>
<script setup lang="ts" name="StuPunlish">
import { reactive, ref, onMounted } from 'vue'
import { BasicTableProps, useTable } from "/@/hooks/table";
import { fetchList, delObj, exportData, revokePunishment, getDetail } from "/@/api/stuwork/stupunlish";
import { queryAllSchoolYear } from "/@/api/basic/basicyear";
import { getDicts } from "/@/api/admin/dict";
import { getDeptListByLevelTwo } from "/@/api/basic/basicdept";
import { getClassListByRole } from "/@/api/basic/basicclass";
import { useMessage, useMessageBox } from "/@/hooks/message";
import FormDialog from './form.vue'
// 定义变量内容
const searchFormRef = ref()
const showSearch = ref(true)
const schoolYearList = ref<any[]>([])
const schoolTermList = ref<any[]>([])
const deptList = ref<any[]>([])
const classList = ref<any[]>([])
const punlishLevelList = ref<any[]>([])
const publishStatusList = ref<any[]>([])
const formDialogRef = ref()
// 搜索表单
const searchForm = reactive({
schoolYear: '',
schoolTerm: '',
deptCode: '',
classCode: '',
realName: '',
stuNo: '',
punlishMonth: '',
punlishLevel: '',
punlishMonthArray: [] as string[]
})
// 配置 useTable
const state: BasicTableProps = reactive<BasicTableProps>({
queryForm: searchForm,
pageList: async (params: any) => {
// 处理处分月份参数API需要数组格式
const queryParams = { ...params }
if (queryParams.punlishMonth) {
queryParams.punlishMonth = [queryParams.punlishMonth]
} else {
delete queryParams.punlishMonth
}
delete queryParams.punlishMonthArray
return await fetchList(queryParams)
},
props: {
item: 'records',
totalCount: 'total'
},
createdIsNeed: true
})
// table hook
const {
getDataList,
currentChangeHandle,
sizeChangeHandle,
tableStyle
} = useTable(state)
// 格式化学期
const formatSchoolTerm = (value: string | number) => {
if (value === null || value === undefined || value === '') {
return '-'
}
const dictItem = schoolTermList.value.find(item => item.value == value)
return dictItem ? dictItem.label : value
}
// 格式化日期
const formatDate = (dateStr: string) => {
if (!dateStr) return '-'
// 如果包含时间部分,只显示日期部分
if (dateStr.includes(' ')) {
return dateStr.split(' ')[0]
}
return dateStr
}
// 格式化处分级别
const formatPunlishLevel = (value: string) => {
if (!value) return '-'
const item = punlishLevelList.value.find((item: any) => item.value === value)
return item ? item.label : value
}
// 格式化处分状态
const formatPublishStatus = (value: string) => {
if (!value) return '-'
const item = publishStatusList.value.find((item: any) => item.value === value)
return item ? item.label : value
}
// 查询
const handleSearch = () => {
getDataList()
}
// 重置
const handleReset = () => {
searchFormRef.value?.resetFields()
searchForm.schoolYear = ''
searchForm.schoolTerm = ''
searchForm.deptCode = ''
searchForm.classCode = ''
searchForm.realName = ''
searchForm.stuNo = ''
searchForm.punlishMonth = ''
searchForm.punlishLevel = ''
getDataList()
}
// 编辑
const handleEdit = (row: any) => {
formDialogRef.value?.openDialog('edit', row)
}
// 删除
const handleDelete = async (row: any) => {
try {
await useMessageBox().confirm('确定要删除这条记录吗?')
await delObj([row.id])
useMessage().success('删除成功')
getDataList()
} catch (err: any) {
if (err !== 'cancel') {
useMessage().error(err.msg || '删除失败')
}
}
}
// 思想汇报
const handleReport = (row: any) => {
useMessage().info('思想汇报功能待实现')
// TODO: 实现思想汇报功能
}
// 撤销处分
const handleCancel = async (row: any) => {
const { confirm } = useMessageBox()
try {
await confirm('确定要撤销该处分吗?')
// 获取完整的处分信息
const res = await getDetail(row.id)
if (!res.data) {
useMessage().error('获取处分信息失败')
return
}
// 更新publishStatus为"0"来撤销处分
const updateData = {
...res.data,
publishStatus: '0'
}
await revokePunishment(updateData)
useMessage().success('撤销处分成功')
getDataList()
} catch (err: any) {
if (err !== 'cancel') {
useMessage().error(err.msg || '撤销处分失败')
}
}
}
// 导出
const handleExport = async () => {
try {
const params: any = { ...searchForm }
// 处理处分月份参数API需要数组格式
if (params.punlishMonth) {
params.punlishMonth = [params.punlishMonth]
} else {
delete params.punlishMonth
}
delete params.punlishMonthArray
const res = await exportData(params)
// 处理返回的文件流
const blob = new Blob([res.data])
const elink = document.createElement('a')
elink.download = '学生违纪.xlsx'
elink.style.display = 'none'
elink.href = URL.createObjectURL(blob)
document.body.appendChild(elink)
elink.click()
URL.revokeObjectURL(elink.href)
document.body.removeChild(elink)
useMessage().success('导出成功')
} catch (err: any) {
useMessage().error(err.msg || '导出失败')
}
}
// 获取学年列表
const getSchoolYearList = async () => {
try {
const res = await queryAllSchoolYear()
if (res.data) {
schoolYearList.value = Array.isArray(res.data) ? res.data : []
}
} catch (err) {
console.error('获取学年列表失败', err)
schoolYearList.value = []
}
}
// 获取学期字典
const getSchoolTermDict = async () => {
try {
const res = await getDicts('school_term')
if (res.data) {
schoolTermList.value = Array.isArray(res.data) ? res.data.map((item: any) => ({
label: item.label || item.dictLabel || item.name,
value: item.value || item.dictValue || item.code
})) : []
}
} catch (err) {
console.error('获取学期字典失败', err)
schoolTermList.value = []
}
}
// 获取学院列表
const getDeptListData = async () => {
try {
const res = await getDeptListByLevelTwo()
if (res.data) {
deptList.value = Array.isArray(res.data) ? res.data : []
}
} catch (err) {
console.error('获取学院列表失败', err)
deptList.value = []
}
}
// 获取班级列表
const getClassListData = async () => {
try {
const res = await getClassListByRole()
if (res.data) {
classList.value = Array.isArray(res.data) ? res.data : []
}
} catch (err) {
console.error('获取班级列表失败', err)
classList.value = []
}
}
// 获取处分级别字典
const getPunlishLevelDict = async () => {
try {
const res = await getDicts('punlish_level')
if (res.data) {
punlishLevelList.value = Array.isArray(res.data) ? res.data.map((item: any) => ({
label: item.label || item.dictLabel || item.name,
value: item.value || item.dictValue || item.code
})) : []
}
} catch (err) {
console.error('获取处分级别字典失败', err)
punlishLevelList.value = []
}
}
// 获取处分状态字典
const getPublishStatusDict = async () => {
try {
const res = await getDicts('publish_status')
if (res.data) {
publishStatusList.value = Array.isArray(res.data) ? res.data.map((item: any) => ({
label: item.label || item.dictLabel || item.name,
value: item.value || item.dictValue || item.code
})) : []
}
} catch (err) {
console.error('获取处分状态字典失败', err)
publishStatusList.value = []
}
}
// 初始化
onMounted(() => {
getSchoolYearList()
getSchoolTermDict()
getDeptListData()
getClassListData()
getPunlishLevelDict()
getPublishStatusDict()
})
</script>

View File

@@ -0,0 +1,338 @@
<template>
<el-dialog
title="新增"
v-model="visible"
:close-on-click-modal="false"
draggable
width="800px">
<el-form
ref="dataFormRef"
:model="form"
:rules="dataRules"
label-width="120px"
v-loading="loading">
<el-row :gutter="24">
<el-col :span="12" class="mb20">
<el-form-item label="系部代码" prop="deptCode">
<el-select
v-model="form.deptCode"
placeholder="请选择系部"
clearable
filterable
style="width: 100%"
@change="handleDeptChange">
<el-option
v-for="item in deptList"
:key="item.deptCode"
:label="item.deptName"
:value="item.deptCode">
</el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="班级代码" prop="classCode">
<el-select
v-model="form.classCode"
placeholder="请选择班级"
clearable
filterable
style="width: 100%"
@change="handleClassChange">
<el-option
v-for="item in classList"
:key="item.classCode"
:label="item.classNo"
:value="item.classCode">
</el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="学号" prop="stuNo">
<el-select
v-model="form.stuNo"
placeholder="请选择学生"
clearable
filterable
style="width: 100%"
:disabled="!form.classCode"
@change="handleStudentChange">
<el-option
v-for="item in studentList"
:key="item.stuNo"
:label="`${item.realName} (${item.stuNo})`"
:value="item.stuNo">
</el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="姓名" prop="realName">
<el-input
v-model="form.realName"
placeholder="自动填充"
disabled
style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="请假开始时间" prop="startTime">
<el-date-picker
v-model="form.startTime"
type="datetime"
placeholder="请选择开始时间"
format="YYYY-MM-DD HH:mm"
value-format="YYYY-MM-DD HH:mm:ss"
style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="请假结束时间" prop="endTime">
<el-date-picker
v-model="form.endTime"
type="datetime"
placeholder="请选择结束时间"
format="YYYY-MM-DD HH:mm"
value-format="YYYY-MM-DD HH:mm:ss"
style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="24" class="mb20">
<el-form-item label="请假事由" prop="reason">
<el-input
v-model="form.reason"
type="textarea"
:rows="4"
placeholder="请输入请假事由" />
</el-form-item>
</el-col>
<el-col :span="24" class="mb20">
<el-form-item label="备注" prop="remarks">
<el-input
v-model="form.remarks"
type="textarea"
:rows="3"
placeholder="请输入备注" />
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="visible = false"> </el-button>
<el-button type="primary" @click="onSubmit" :disabled="loading"> </el-button>
</span>
</template>
</el-dialog>
</template>
<script setup lang="ts" name="StuTemLeaveApplyFormDialog">
import { ref, reactive, nextTick, onMounted } from 'vue'
import { useMessage } from '/@/hooks/message'
import { addObj } from '/@/api/stuwork/stutemleaveapply'
import { getDeptListByLevelTwo } from '/@/api/basic/basicdept'
import { list as getClassList } from '/@/api/basic/basicclass'
import { queryStudentListByClass } from '/@/api/basic/basicstudent'
const emit = defineEmits(['refresh'])
// 定义变量内容
const dataFormRef = ref()
const visible = ref(false)
const loading = ref(false)
const deptList = ref<any[]>([])
const classList = ref<any[]>([])
const studentList = ref<any[]>([])
// 提交表单数据
const form = reactive({
deptCode: '',
classCode: '',
stuNo: '',
realName: '',
startTime: '',
endTime: '',
reason: '',
remarks: ''
})
// 定义校验规则
const dataRules = {
deptCode: [
{ required: true, message: '请选择系部代码', trigger: 'change' }
],
classCode: [
{ required: true, message: '请选择班级代码', trigger: 'change' }
],
stuNo: [
{ required: true, message: '请选择学号', trigger: 'change' }
],
startTime: [
{ required: true, message: '请选择请假开始时间', trigger: 'change' }
],
endTime: [
{ required: true, message: '请选择请假结束时间', trigger: 'change' }
],
reason: [
{ required: true, message: '请输入请假事由', trigger: 'blur' }
]
}
// 打开弹窗
const openDialog = () => {
visible.value = true
resetForm()
nextTick(() => {
dataFormRef.value?.resetFields()
})
}
// 重置表单
const resetForm = () => {
form.deptCode = ''
form.classCode = ''
form.stuNo = ''
form.realName = ''
form.startTime = ''
form.endTime = ''
form.reason = ''
form.remarks = ''
studentList.value = []
}
// 系部变化
const handleDeptChange = () => {
form.classCode = ''
form.stuNo = ''
form.realName = ''
studentList.value = []
// 根据系部筛选班级
if (form.deptCode) {
classList.value = classList.value.filter((item: any) => item.deptCode === form.deptCode)
} else {
getClassListData()
}
}
// 班级变化
const handleClassChange = async () => {
form.stuNo = ''
form.realName = ''
studentList.value = []
if (form.classCode) {
await getStudentListData()
}
}
// 学生变化
const handleStudentChange = () => {
const student = studentList.value.find((item: any) => item.stuNo === form.stuNo)
if (student) {
form.realName = student.realName || ''
} else {
form.realName = ''
}
}
// 提交表单
const onSubmit = async () => {
if (!dataFormRef.value) return
await dataFormRef.value.validate(async (valid: boolean) => {
if (!valid) return
// 验证时间
if (form.startTime && form.endTime) {
const start = new Date(form.startTime)
const end = new Date(form.endTime)
if (end <= start) {
useMessage().error('请假结束时间必须大于开始时间')
return
}
}
loading.value = true
try {
await addObj({
deptCode: form.deptCode,
classCode: form.classCode,
stuNo: form.stuNo,
startTime: form.startTime,
endTime: form.endTime,
reason: form.reason,
remarks: form.remarks
})
useMessage().success('新增成功')
visible.value = false
emit('refresh')
} catch (err: any) {
useMessage().error(err.msg || '新增失败')
} finally {
loading.value = false
}
})
}
// 获取系部列表
const getDeptListData = async () => {
try {
const res = await getDeptListByLevelTwo()
if (res.data) {
deptList.value = Array.isArray(res.data) ? res.data : []
}
} catch (err) {
console.error('获取系部列表失败', err)
deptList.value = []
}
}
// 获取班级列表
const getClassListData = async () => {
try {
const res = await getClassList()
if (res.data) {
classList.value = Array.isArray(res.data) ? res.data : []
}
} catch (err) {
console.error('获取班级列表失败', err)
classList.value = []
}
}
// 获取学生列表
const getStudentListData = async () => {
if (!form.classCode) return
try {
const res = await queryStudentListByClass(form.classCode)
if (res.data) {
studentList.value = Array.isArray(res.data) ? res.data : []
}
} catch (err) {
console.error('获取学生列表失败', err)
studentList.value = []
}
}
// 初始化
onMounted(() => {
getDeptListData()
getClassListData()
})
// 暴露方法
defineExpose({
openDialog
})
</script>
<style scoped lang="scss">
</style>

View File

@@ -0,0 +1,268 @@
<template>
<div class="layout-padding">
<div class="layout-padding-auto layout-padding-view">
<!-- 搜索表单 -->
<el-row v-show="showSearch">
<el-form :model="searchForm" ref="searchFormRef" :inline="true" @keyup.enter="handleSearch">
<el-form-item label="系部代码" prop="deptCode">
<el-select
v-model="searchForm.deptCode"
placeholder="请选择系部"
clearable
filterable
style="width: 200px">
<el-option
v-for="item in deptList"
:key="item.deptCode"
:label="item.deptName"
:value="item.deptCode">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="班级代码" prop="classCode">
<el-select
v-model="searchForm.classCode"
placeholder="请选择班级"
clearable
filterable
style="width: 200px">
<el-option
v-for="item in classList"
:key="item.classCode"
:label="item.classNo"
:value="item.classCode">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="学号" prop="stuNo">
<el-input
v-model="searchForm.stuNo"
placeholder="请输入学号"
clearable
style="width: 200px" />
</el-form-item>
<el-form-item>
<el-button type="primary" plain icon="Search" @click="handleSearch">查询</el-button>
<el-button icon="Refresh" @click="handleReset">重置</el-button>
</el-form-item>
</el-form>
</el-row>
<!-- 操作按钮 -->
<el-row>
<div class="mb8" style="width: 100%">
<el-button
icon="Plus"
type="primary"
class="ml10"
@click="formDialogRef.openDialog()">
新增
</el-button>
<right-toolbar
v-model:showSearch="showSearch"
class="ml10 mr20"
style="float: right;"
@queryTable="getDataList">
</right-toolbar>
</div>
</el-row>
<!-- 表格 -->
<el-table
:data="state.dataList"
v-loading="state.loading"
border
:cell-style="tableStyle.cellStyle"
:header-cell-style="tableStyle.headerCellStyle">
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="schoolYear" label="学年" show-overflow-tooltip align="center" />
<el-table-column prop="schoolTerm" label="学期" show-overflow-tooltip align="center">
<template #default="scope">
<span>{{ formatSchoolTerm(scope.row.schoolTerm) }}</span>
</template>
</el-table-column>
<el-table-column prop="deptCode" label="系部代码" show-overflow-tooltip align="center" />
<el-table-column prop="classCode" label="班级代码" show-overflow-tooltip align="center" />
<el-table-column prop="stuNo" label="学号" show-overflow-tooltip align="center" />
<el-table-column prop="realName" label="姓名" show-overflow-tooltip align="center" />
<el-table-column prop="startTime" label="请假开始时间" show-overflow-tooltip align="center" width="180">
<template #default="scope">
<span>{{ scope.row.startTime ? formatDateTime(scope.row.startTime) : '-' }}</span>
</template>
</el-table-column>
<el-table-column prop="endTime" label="请假结束时间" show-overflow-tooltip align="center" width="180">
<template #default="scope">
<span>{{ scope.row.endTime ? formatDateTime(scope.row.endTime) : '-' }}</span>
</template>
</el-table-column>
<el-table-column prop="reason" label="请假事由" show-overflow-tooltip align="center" min-width="150" />
<el-table-column prop="classTeach" label="班主任" show-overflow-tooltip align="center" />
<el-table-column prop="stuPhote" label="联系方式" show-overflow-tooltip align="center" />
<el-table-column prop="remarks" label="备注" show-overflow-tooltip align="center" min-width="150" />
<el-table-column label="操作" width="100" align="center" fixed="right">
<template #default="scope">
<el-button
icon="Delete"
text
type="danger"
@click="handleDelete(scope.row)">
删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<pagination
@size-change="sizeChangeHandle"
@current-change="currentChangeHandle"
v-bind="state.pagination" />
</div>
<!-- 新增表单弹窗 -->
<form-dialog ref="formDialogRef" @refresh="getDataList" />
</div>
</template>
<script setup lang="ts" name="StuTemLeaveApply">
import { reactive, ref, onMounted } from 'vue'
import { BasicTableProps, useTable } from "/@/hooks/table";
import { fetchList, delObj } from "/@/api/stuwork/stutemleaveapply";
import { getDicts } from "/@/api/admin/dict";
import { getDeptListByLevelTwo } from "/@/api/basic/basicdept";
import { getClassListByRole } from "/@/api/basic/basicclass";
import { useMessage, useMessageBox } from "/@/hooks/message";
import FormDialog from './form.vue'
// 定义变量内容
const searchFormRef = ref()
const showSearch = ref(true)
const deptList = ref<any[]>([])
const classList = ref<any[]>([])
const schoolTermList = ref<any[]>([])
const formDialogRef = ref()
// 搜索表单
const searchForm = reactive({
deptCode: '',
classCode: '',
stuNo: ''
})
// 配置 useTable
const state: BasicTableProps = reactive<BasicTableProps>({
queryForm: searchForm,
pageList: fetchList,
props: {
item: 'records',
totalCount: 'total'
},
createdIsNeed: true
})
// table hook
const {
getDataList,
currentChangeHandle,
sizeChangeHandle,
tableStyle
} = useTable(state)
// 格式化学期
const formatSchoolTerm = (value: string | number) => {
if (value === null || value === undefined || value === '') {
return '-'
}
const dictItem = schoolTermList.value.find(item => item.value == value)
return dictItem ? dictItem.label : value
}
// 格式化日期时间
const formatDateTime = (dateTime: string) => {
if (!dateTime) return '-'
// 如果包含时间部分,只显示日期和时间
if (dateTime.includes(' ')) {
return dateTime.split('.')[0] // 移除毫秒部分
}
return dateTime
}
// 查询
const handleSearch = () => {
getDataList()
}
// 重置
const handleReset = () => {
searchFormRef.value?.resetFields()
getDataList()
}
// 删除
const handleDelete = async (row: any) => {
const { confirm } = useMessageBox()
try {
await confirm('确定要删除该临时请假记录吗?')
await delObj([row.id])
useMessage().success('删除成功')
getDataList()
} catch (err: any) {
if (err !== 'cancel') {
useMessage().error(err.msg || '删除失败')
}
}
}
// 获取系部列表
const getDeptListData = async () => {
try {
const res = await getDeptListByLevelTwo()
if (res.data) {
deptList.value = Array.isArray(res.data) ? res.data : []
}
} catch (err) {
console.error('获取系部列表失败', err)
deptList.value = []
}
}
// 获取班级列表
const getClassListData = async () => {
try {
const res = await getClassListByRole()
if (res.data) {
classList.value = Array.isArray(res.data) ? res.data : []
}
} catch (err) {
console.error('获取班级列表失败', err)
classList.value = []
}
}
// 获取学期字典
const getSchoolTermDict = async () => {
try {
const res = await getDicts('school_term')
if (res.data) {
schoolTermList.value = Array.isArray(res.data) ? res.data.map((item: any) => ({
label: item.label || item.dictLabel || item.name,
value: item.value || item.dictValue || item.code
})) : []
}
} catch (err) {
console.error('获取学期字典失败', err)
schoolTermList.value = []
}
}
// 初始化
onMounted(() => {
getDeptListData()
getClassListData()
getSchoolTermDict()
})
</script>
<style scoped lang="scss">
</style>

View File

@@ -0,0 +1,624 @@
<template>
<el-dialog
:title="form.id ? '编辑' : '批量新增异动'"
v-model="visible"
:close-on-click-modal="false"
draggable
width="900px">
<el-form
ref="dataFormRef"
:model="form"
:rules="dataRules"
label-width="120px"
v-loading="loading">
<el-row :gutter="24">
<el-col :span="12" class="mb20">
<el-form-item label="学年" prop="schoolYear">
<el-select
v-model="form.schoolYear"
placeholder="请选择学年"
clearable
filterable
style="width: 100%">
<el-option
v-for="item in schoolYearList"
:key="item.year"
:label="item.year"
:value="item.year">
</el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="学期" prop="schoolTerm">
<el-select
v-model="form.schoolTerm"
placeholder="请选择学期"
clearable
style="width: 100%">
<el-option
v-for="item in schoolTermList"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="原班级" prop="oldClassCode">
<el-select
v-model="form.oldClassCode"
placeholder="请选择原班级"
clearable
filterable
style="width: 100%"
@change="handleOldClassChange">
<el-option
v-for="item in classList"
:key="item.classCode"
:label="item.classNo"
:value="item.classCode">
</el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="现班级" prop="newClassCode">
<el-select
v-model="form.newClassCode"
placeholder="请选择现班级"
clearable
filterable
style="width: 100%">
<el-option
v-for="item in classList"
:key="item.classCode"
:label="item.classNo"
:value="item.classCode">
</el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="异动类型" prop="turnoverType">
<el-select
v-model="form.turnoverType"
placeholder="请选择异动类型"
clearable
style="width: 100%">
<el-option
v-for="item in turnoverTypeList"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="转制类型" prop="turnYear">
<el-select
v-model="form.turnYear"
placeholder="请选择转制类型"
clearable
style="width: 100%">
<el-option
v-for="item in turnYearList"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="异动时间" prop="turnoverDate">
<el-date-picker
v-model="form.turnoverDate"
type="datetime"
placeholder="选择异动时间"
format="YYYY-MM-DD HH:mm:ss"
value-format="YYYY-MM-DD HH:mm:ss"
style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="选择学生" prop="selectedStudents">
<el-input
v-model="selectedStudentsText"
placeholder="请选择学生"
readonly
style="width: 100%"
@click="openStudentDialog">
<template #suffix>
<el-button
icon="Search"
circle
@click.stop="openStudentDialog" />
</template>
</el-input>
<span v-if="form.selectedStudents.length > 0" class="ml5" style="color: #409eff;">
已选 {{ form.selectedStudents.length }}
</span>
</el-form-item>
</el-col>
<el-col :span="24" class="mb20">
<el-form-item label="异动原因" prop="remarks">
<el-input
v-model="form.remarks"
type="textarea"
:rows="4"
placeholder="请输入异动原因" />
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="visible = false"> </el-button>
<el-button type="primary" @click="onSubmit" :disabled="loading"> </el-button>
</span>
</template>
</el-dialog>
<!-- 学生选择弹窗 -->
<el-dialog
title="选择学生"
v-model="studentDialogVisible"
:close-on-click-modal="false"
draggable
width="900px">
<!-- 搜索表单 -->
<el-form :model="studentSearchForm" :inline="true" @keyup.enter="handleStudentSearch">
<el-form-item label="班号" prop="classNo">
<el-input
v-model="studentSearchForm.classNo"
placeholder="请输入班号"
clearable
style="width: 200px" />
</el-form-item>
<el-form-item label="学号" prop="stuNo">
<el-input
v-model="studentSearchForm.stuNo"
placeholder="请输入学号"
clearable
style="width: 200px" />
</el-form-item>
<el-form-item label="姓名" prop="realName">
<el-input
v-model="studentSearchForm.realName"
placeholder="请输入姓名"
clearable
style="width: 200px" />
</el-form-item>
<el-form-item>
<el-button type="primary" plain icon="Search" @click="handleStudentSearch">查询</el-button>
<el-button icon="Refresh" @click="handleStudentReset">重置</el-button>
</el-form-item>
</el-form>
<!-- 学生表格 -->
<el-table
ref="studentTableRef"
:data="studentTableData"
v-loading="studentTableLoading"
border
@selection-change="handleStudentSelectionChange"
style="margin-top: 20px; max-height: 400px; overflow-y: auto;">
<el-table-column type="selection" width="55" align="center" />
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="classNo" label="班号" show-overflow-tooltip align="center" />
<el-table-column prop="stuNo" label="学号" show-overflow-tooltip align="center" />
<el-table-column prop="realName" label="姓名" show-overflow-tooltip align="center" />
</el-table>
<!-- 分页 -->
<pagination
@size-change="studentSizeChangeHandle"
@current-change="studentCurrentChangeHandle"
v-bind="studentPagination"
style="margin-top: 20px" />
<template #footer>
<span class="dialog-footer">
<el-button @click="studentDialogVisible = false"> </el-button>
<el-button type="primary" @click="confirmStudentSelection"> </el-button>
</span>
</template>
</el-dialog>
</template>
<script setup lang="ts" name="StuTurnoverFormDialog">
import { ref, reactive, nextTick, onMounted, computed } from 'vue'
import { useMessage } from '/@/hooks/message'
import { addObj, editObj, getDetail } from '/@/api/stuwork/stuturnover'
import { queryAllSchoolYear } from '/@/api/basic/basicyear'
import { getDicts } from '/@/api/admin/dict'
import { list as getClassList } from '/@/api/basic/basicclass'
import { fetchList as getStudentList } from '/@/api/basic/basicstudent'
const emit = defineEmits(['refresh'])
// 定义变量内容
const dataFormRef = ref()
const studentTableRef = ref()
const visible = ref(false)
const loading = ref(false)
const operType = ref('add') // add 或 edit
const schoolYearList = ref<any[]>([])
const schoolTermList = ref<any[]>([])
const classList = ref<any[]>([])
const turnoverTypeList = ref<any[]>([])
const turnYearList = ref<any[]>([])
// 学生选择弹窗相关
const studentDialogVisible = ref(false)
const studentTableLoading = ref(false)
const studentTableData = ref<any[]>([])
const selectedStudentRows = ref<any[]>([]) // 表格中选中的行
const studentSearchForm = reactive({
classCode: '',
classNo: '',
stuNo: '',
realName: ''
})
const studentPagination = reactive({
total: 0,
pageSize: 10,
currentPage: 1
})
// 提交表单数据
const form = reactive({
id: '',
schoolYear: '',
schoolTerm: '',
oldClassCode: '',
newClassCode: '',
turnoverType: '',
turnYear: '',
turnoverDate: '',
remarks: '',
selectedStudents: [] as any[] // 选中的学生对象数组
})
// 已选学生文本显示
const selectedStudentsText = computed(() => {
if (!form.selectedStudents || form.selectedStudents.length === 0) {
return ''
}
return `已选择 ${form.selectedStudents.length} 名学生`
})
// 定义校验规则
const dataRules = {
schoolYear: [
{ required: true, message: '请选择学年', trigger: 'change' }
],
schoolTerm: [
{ required: true, message: '请选择学期', trigger: 'change' }
],
oldClassCode: [
{ required: true, message: '请选择原班级', trigger: 'change' }
],
newClassCode: [
{ required: true, message: '请选择现班级', trigger: 'change' }
],
turnoverType: [
{ required: true, message: '请选择异动类型', trigger: 'change' }
],
turnoverDate: [
{ required: true, message: '请选择异动时间', trigger: 'change' }
],
selectedStudents: [
{ required: true, message: '请至少选择一个学生', trigger: 'change', type: 'array', min: 1 }
]
}
// 原班级变化时,更新学生搜索条件
const handleOldClassChange = () => {
studentSearchForm.classCode = form.oldClassCode
// 清空已选学生
form.selectedStudents = []
}
// 打开学生选择弹窗
const openStudentDialog = () => {
if (!form.oldClassCode) {
useMessage().warning('请先选择原班级')
return
}
studentDialogVisible.value = true
studentSearchForm.classCode = form.oldClassCode
studentSearchForm.classNo = ''
studentSearchForm.stuNo = ''
studentSearchForm.realName = ''
selectedStudentRows.value = []
studentPagination.currentPage = 1
handleStudentSearch()
}
// 学生搜索
const handleStudentSearch = async () => {
studentTableLoading.value = true
try {
const params: any = {
current: studentPagination.currentPage,
size: studentPagination.pageSize,
classCode: studentSearchForm.classCode || undefined,
stuNo: studentSearchForm.stuNo || undefined,
realName: studentSearchForm.realName || undefined
}
const res = await getStudentList(params)
if (res.data && res.data.records) {
studentTableData.value = res.data.records
studentPagination.total = res.data.total || 0
} else {
studentTableData.value = []
studentPagination.total = 0
}
// 恢复之前选中的学生
nextTick(() => {
if (form.selectedStudents.length > 0) {
studentTableData.value.forEach((row: any) => {
const isSelected = form.selectedStudents.some((s: any) => s.stuNo === row.stuNo)
if (isSelected) {
studentTableRef.value?.toggleRowSelection(row, true)
}
})
}
})
} catch (err) {
console.error('获取学生列表失败', err)
studentTableData.value = []
} finally {
studentTableLoading.value = false
}
}
// 学生搜索重置
const handleStudentReset = () => {
studentSearchForm.classNo = ''
studentSearchForm.stuNo = ''
studentSearchForm.realName = ''
handleStudentSearch()
}
// 学生表格选择变化
const handleStudentSelectionChange = (selection: any[]) => {
selectedStudentRows.value = selection
}
// 确认学生选择
const confirmStudentSelection = () => {
if (selectedStudentRows.value.length === 0) {
useMessage().warning('请至少选择一个学生')
return
}
// 合并已选学生(避免重复)
const newStudents = selectedStudentRows.value.map((row: any) => ({
stuNo: row.stuNo,
realName: row.realName
}))
// 合并到已选列表,去重
const existingStuNos = form.selectedStudents.map((s: any) => s.stuNo)
newStudents.forEach((student: any) => {
if (!existingStuNos.includes(student.stuNo)) {
form.selectedStudents.push(student)
}
})
studentDialogVisible.value = false
useMessage().success(`已选择 ${form.selectedStudents.length} 名学生`)
}
// 学生分页变化
const studentSizeChangeHandle = (size: number) => {
studentPagination.pageSize = size
studentPagination.currentPage = 1
handleStudentSearch()
}
const studentCurrentChangeHandle = (current: number) => {
studentPagination.currentPage = current
handleStudentSearch()
}
// 打开弹窗
const openDialog = async (type: string = 'add', row?: any) => {
visible.value = true
operType.value = type
// 重置表单数据
nextTick(() => {
dataFormRef.value?.resetFields()
form.id = ''
form.schoolYear = ''
form.schoolTerm = ''
form.oldClassCode = ''
form.newClassCode = ''
form.turnoverType = ''
form.turnYear = ''
form.turnoverDate = ''
form.remarks = ''
form.selectedStudents = []
// 编辑时填充数据
if (type === 'edit' && row) {
form.id = row.id
form.schoolYear = row.schoolYear || ''
form.schoolTerm = row.schoolTerm || ''
form.oldClassCode = row.oldClassCode || ''
form.newClassCode = row.newClassCode || ''
form.turnoverType = row.turnoverType || ''
form.turnYear = row.turnYear || ''
form.turnoverDate = row.turnoverDate || ''
form.remarks = row.remarks || ''
// 编辑时,学生列表只有当前学生
if (row.stuNo && row.realName) {
form.selectedStudents = [{
stuNo: row.stuNo,
realName: row.realName
}]
}
}
})
}
// 提交表单
const onSubmit = async () => {
if (!dataFormRef.value) return
await dataFormRef.value.validate(async (valid: boolean) => {
if (!valid) return
if (!form.selectedStudents || form.selectedStudents.length === 0) {
useMessage().error('请至少选择一个学生')
return
}
loading.value = true
try {
const submitData: any = {
schoolYear: form.schoolYear,
schoolTerm: form.schoolTerm,
oldClassCode: form.oldClassCode,
newClassCode: form.newClassCode,
turnoverType: form.turnoverType,
turnYear: form.turnYear || '',
turnoverDate: form.turnoverDate,
remarks: form.remarks || '',
stuList: form.selectedStudents.map((s: any) => ({
stuNo: s.stuNo,
realName: s.realName
}))
}
// 编辑时需要包含id和单个学生信息
if (operType.value === 'edit') {
submitData.id = form.id
submitData.stuNo = form.selectedStudents[0]?.stuNo
submitData.realName = form.selectedStudents[0]?.realName
// 编辑时不需要stuList
delete submitData.stuList
}
if (operType.value === 'add') {
await addObj(submitData)
useMessage().success('批量新增成功')
} else {
await editObj(submitData)
useMessage().success('编辑成功')
}
visible.value = false
emit('refresh')
} catch (err: any) {
useMessage().error(err.msg || (operType.value === 'add' ? '批量新增失败' : '编辑失败'))
} finally {
loading.value = false
}
})
}
// 获取学年列表
const getSchoolYearList = async () => {
try {
const res = await queryAllSchoolYear()
if (res.data) {
schoolYearList.value = Array.isArray(res.data) ? res.data : []
}
} catch (err) {
console.error('获取学年列表失败', err)
schoolYearList.value = []
}
}
// 获取学期字典
const getSchoolTermDict = async () => {
try {
const res = await getDicts('school_term')
if (res.data) {
schoolTermList.value = Array.isArray(res.data) ? res.data.map((item: any) => ({
label: item.label || item.dictLabel || item.name,
value: item.value || item.dictValue || item.code
})) : []
}
} catch (err) {
console.error('获取学期字典失败', err)
schoolTermList.value = []
}
}
// 获取班级列表
const getClassListData = async () => {
try {
const res = await getClassList()
if (res.data) {
classList.value = Array.isArray(res.data) ? res.data : []
}
} catch (err) {
console.error('获取班级列表失败', err)
classList.value = []
}
}
// 获取异动类型字典
const getTurnoverTypeDict = async () => {
try {
const res = await getDicts('turnover_type')
if (res.data) {
turnoverTypeList.value = Array.isArray(res.data) ? res.data.map((item: any) => ({
label: item.label || item.dictLabel || item.name,
value: item.value || item.dictValue || item.code
})) : []
}
} catch (err) {
console.error('获取异动类型字典失败', err)
turnoverTypeList.value = []
}
}
// 获取转制类型字典
const getTurnYearDict = async () => {
try {
const res = await getDicts('turn_year_type')
if (res.data) {
turnYearList.value = Array.isArray(res.data) ? res.data.map((item: any) => ({
label: item.label || item.dictLabel || item.name,
value: item.value || item.dictValue || item.code
})) : []
}
} catch (err) {
console.error('获取转制类型字典失败', err)
turnYearList.value = []
}
}
// 初始化
onMounted(() => {
getSchoolYearList()
getSchoolTermDict()
getClassListData()
getTurnoverTypeDict()
getTurnYearDict()
})
// 暴露方法给父组件
defineExpose({
openDialog
})
</script>

View File

@@ -0,0 +1,433 @@
<template>
<div class="layout-padding">
<div class="layout-padding-auto layout-padding-view">
<!-- 搜索表单 -->
<el-row v-show="showSearch">
<el-form :model="searchForm" ref="searchFormRef" :inline="true" @keyup.enter="handleSearch">
<el-form-item label="学年" prop="schoolYear">
<el-select
v-model="searchForm.schoolYear"
placeholder="请选择学年"
clearable
filterable
style="width: 200px">
<el-option
v-for="item in schoolYearList"
:key="item.year"
:label="item.year"
:value="item.year">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="学期" prop="schoolTerm">
<el-select
v-model="searchForm.schoolTerm"
placeholder="请选择学期"
clearable
style="width: 200px">
<el-option
v-for="item in schoolTermList"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="学院" prop="deptCode">
<el-select
v-model="searchForm.deptCode"
placeholder="请选择学院"
clearable
filterable
style="width: 200px">
<el-option
v-for="item in deptList"
:key="item.deptCode"
:label="item.deptName"
:value="item.deptCode">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="原班级" prop="oldClassCode">
<el-select
v-model="searchForm.oldClassCode"
placeholder="请选择原班级"
clearable
filterable
style="width: 200px">
<el-option
v-for="item in classList"
:key="item.classCode"
:label="item.classNo"
:value="item.classCode">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="学号" prop="stuNo">
<el-input
v-model="searchForm.stuNo"
placeholder="请输入学号"
clearable
style="width: 200px" />
</el-form-item>
<el-form-item label="姓名" prop="realName">
<el-input
v-model="searchForm.realName"
placeholder="请输入姓名"
clearable
style="width: 200px" />
</el-form-item>
<el-form-item label="异动类型" prop="turnoverType">
<el-select
v-model="searchForm.turnoverType"
placeholder="请选择异动类型"
clearable
style="width: 200px">
<el-option
v-for="item in turnoverTypeList"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" plain icon="Search" @click="handleSearch">查询</el-button>
<el-button icon="Refresh" @click="handleReset">重置</el-button>
</el-form-item>
</el-form>
</el-row>
<!-- 操作按钮 -->
<el-row>
<div class="mb8" style="width: 100%">
<el-button
icon="FolderAdd"
type="primary"
class="ml10"
@click="formDialogRef.openDialog()">
批量新增异动
</el-button>
<el-button
icon="Download"
type="success"
class="ml10"
@click="handleExport">
</el-button>
<right-toolbar
v-model:showSearch="showSearch"
class="ml10 mr20"
style="float: right;"
@queryTable="getDataList">
</right-toolbar>
</div>
</el-row>
<!-- 表格 -->
<el-table
:data="state.dataList"
v-loading="state.loading"
border
:cell-style="tableStyle.cellStyle"
:header-cell-style="tableStyle.headerCellStyle">
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="schoolYear" label="学年" show-overflow-tooltip align="center" />
<el-table-column prop="schoolTerm" label="学期" show-overflow-tooltip align="center">
<template #default="scope">
<span>{{ formatSchoolTerm(scope.row.schoolTerm) }}</span>
</template>
</el-table-column>
<el-table-column prop="deptName" label="学院" show-overflow-tooltip align="center" />
<el-table-column prop="oldClassNo" label="原班级" show-overflow-tooltip align="center" />
<el-table-column prop="newClassNo" label="现班级" show-overflow-tooltip align="center" />
<el-table-column prop="stuNo" label="学号" show-overflow-tooltip align="center" />
<el-table-column prop="realName" label="姓名" show-overflow-tooltip align="center" />
<el-table-column prop="turnoverDate" label="异动时间" show-overflow-tooltip align="center" width="180">
<template #default="scope">
<span>{{ scope.row.turnoverDate ? formatDate(scope.row.turnoverDate) : '-' }}</span>
</template>
</el-table-column>
<el-table-column prop="turnoverType" label="异动类型" show-overflow-tooltip align="center">
<template #default="scope">
<span>{{ formatTurnoverType(scope.row.turnoverType) }}</span>
</template>
</el-table-column>
<el-table-column prop="turnYear" label="转制类型" show-overflow-tooltip align="center">
<template #default="scope">
<span>{{ formatTurnYear(scope.row.turnYear) }}</span>
</template>
</el-table-column>
<el-table-column prop="remarks" label="异动原因" show-overflow-tooltip align="center" min-width="150" />
<el-table-column label="操作" width="150" align="center" fixed="right">
<template #default="scope">
<el-button
icon="Edit"
text
type="primary"
@click="handleEdit(scope.row)">
编辑
</el-button>
<el-button
icon="Delete"
text
type="danger"
@click="handleDelete(scope.row)">
删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<pagination
@size-change="sizeChangeHandle"
@current-change="currentChangeHandle"
v-bind="state.pagination" />
</div>
<!-- 批量新增/编辑表单弹窗 -->
<form-dialog ref="formDialogRef" @refresh="getDataList" />
</div>
</template>
<script setup lang="ts" name="StuTurnover">
import { reactive, ref, onMounted } from 'vue'
import { BasicTableProps, useTable } from "/@/hooks/table";
import { fetchList, delObj, exportData } from "/@/api/stuwork/stuturnover";
import { queryAllSchoolYear } from "/@/api/basic/basicyear";
import { getDicts } from "/@/api/admin/dict";
import { getDeptListByLevelTwo } from "/@/api/basic/basicdept";
import { list as getClassList } from "/@/api/basic/basicclass";
import { useMessage, useMessageBox } from "/@/hooks/message";
import FormDialog from './form.vue'
// 定义变量内容
const searchFormRef = ref()
const showSearch = ref(true)
const schoolYearList = ref<any[]>([])
const schoolTermList = ref<any[]>([])
const deptList = ref<any[]>([])
const classList = ref<any[]>([])
const turnoverTypeList = ref<any[]>([])
const turnYearList = ref<any[]>([])
const formDialogRef = ref()
// 搜索表单
const searchForm = reactive({
schoolYear: '',
schoolTerm: '',
deptCode: '',
oldClassCode: '',
stuNo: '',
realName: '',
turnoverType: ''
})
// 配置 useTable
const state: BasicTableProps = reactive<BasicTableProps>({
queryForm: searchForm,
pageList: fetchList,
props: {
item: 'records',
totalCount: 'total'
},
createdIsNeed: true
})
// table hook
const {
getDataList,
currentChangeHandle,
sizeChangeHandle,
tableStyle
} = useTable(state)
// 格式化学期
const formatSchoolTerm = (value: string | number) => {
if (value === null || value === undefined || value === '') {
return '-'
}
const dictItem = schoolTermList.value.find(item => item.value == value)
return dictItem ? dictItem.label : value
}
// 格式化日期
const formatDate = (dateStr: string) => {
if (!dateStr) return '-'
// 如果包含时间部分,只显示日期部分
if (dateStr.includes(' ')) {
return dateStr.split(' ')[0]
}
return dateStr
}
// 格式化异动类型
const formatTurnoverType = (value: string) => {
if (!value) return '-'
const item = turnoverTypeList.value.find((item: any) => item.value === value)
return item ? item.label : value
}
// 格式化转制类型
const formatTurnYear = (value: string) => {
if (!value) return '-'
const item = turnYearList.value.find((item: any) => item.value === value)
return item ? item.label : value
}
// 查询
const handleSearch = () => {
getDataList()
}
// 重置
const handleReset = () => {
searchFormRef.value?.resetFields()
searchForm.schoolYear = ''
searchForm.schoolTerm = ''
searchForm.deptCode = ''
searchForm.oldClassCode = ''
searchForm.stuNo = ''
searchForm.realName = ''
searchForm.turnoverType = ''
getDataList()
}
// 编辑
const handleEdit = (row: any) => {
formDialogRef.value?.openDialog('edit', row)
}
// 删除
const handleDelete = async (row: any) => {
try {
await useMessageBox().confirm('确定要删除这条记录吗?')
await delObj([row.id])
useMessage().success('删除成功')
getDataList()
} catch (err: any) {
if (err !== 'cancel') {
useMessage().error(err.msg || '删除失败')
}
}
}
// 导出
const handleExport = async () => {
try {
const res = await exportData(searchForm)
// 处理返回的文件流
const blob = new Blob([res.data])
const elink = document.createElement('a')
elink.download = '学籍异动.xlsx'
elink.style.display = 'none'
elink.href = URL.createObjectURL(blob)
document.body.appendChild(elink)
elink.click()
URL.revokeObjectURL(elink.href)
document.body.removeChild(elink)
useMessage().success('导出成功')
} catch (err: any) {
useMessage().error(err.msg || '导出失败')
}
}
// 获取学年列表
const getSchoolYearList = async () => {
try {
const res = await queryAllSchoolYear()
if (res.data) {
schoolYearList.value = Array.isArray(res.data) ? res.data : []
}
} catch (err) {
console.error('获取学年列表失败', err)
schoolYearList.value = []
}
}
// 获取学期字典
const getSchoolTermDict = async () => {
try {
const res = await getDicts('school_term')
if (res.data) {
schoolTermList.value = Array.isArray(res.data) ? res.data.map((item: any) => ({
label: item.label || item.dictLabel || item.name,
value: item.value || item.dictValue || item.code
})) : []
}
} catch (err) {
console.error('获取学期字典失败', err)
schoolTermList.value = []
}
}
// 获取学院列表
const getDeptListData = async () => {
try {
const res = await getDeptListByLevelTwo()
if (res.data) {
deptList.value = Array.isArray(res.data) ? res.data : []
}
} catch (err) {
console.error('获取学院列表失败', err)
deptList.value = []
}
}
// 获取班级列表
const getClassListData = async () => {
try {
const res = await getClassList()
if (res.data) {
classList.value = Array.isArray(res.data) ? res.data : []
}
} catch (err) {
console.error('获取班级列表失败', err)
classList.value = []
}
}
// 获取异动类型字典
const getTurnoverTypeDict = async () => {
try {
const res = await getDicts('turnover_type')
if (res.data) {
turnoverTypeList.value = Array.isArray(res.data) ? res.data.map((item: any) => ({
label: item.label || item.dictLabel || item.name,
value: item.value || item.dictValue || item.code
})) : []
}
} catch (err) {
console.error('获取异动类型字典失败', err)
turnoverTypeList.value = []
}
}
// 获取转制类型字典
const getTurnYearDict = async () => {
try {
const res = await getDicts('turn_year_type')
if (res.data) {
turnYearList.value = Array.isArray(res.data) ? res.data.map((item: any) => ({
label: item.label || item.dictLabel || item.name,
value: item.value || item.dictValue || item.code
})) : []
}
} catch (err) {
console.error('获取转制类型字典失败', err)
turnYearList.value = []
}
}
// 初始化
onMounted(() => {
getSchoolYearList()
getSchoolTermDict()
getDeptListData()
getClassListData()
getTurnoverTypeDict()
getTurnYearDict()
})
</script>

View File

@@ -0,0 +1,128 @@
<template>
<el-dialog
:title="form.id ? '编辑' : '新增'"
v-model="visible"
:width="600"
:close-on-click-modal="false"
draggable>
<el-form
ref="dataFormRef"
:model="form"
:rules="dataRules"
label-width="100px"
v-loading="loading">
<el-form-item label="楼号" prop="buildingNo">
<el-input-number
v-model="form.buildingNo"
:min="1"
placeholder="请输入楼号"
style="width: 100%" />
</el-form-item>
<el-form-item label="备注" prop="remarks">
<el-input
v-model="form.remarks"
type="textarea"
:rows="4"
placeholder="请输入备注" />
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="visible = false"> </el-button>
<el-button type="primary" @click="onSubmit" :disabled="loading"> </el-button>
</span>
</template>
</el-dialog>
</template>
<script setup lang="ts" name="TeachBuildingFormDialog">
import { ref, reactive, nextTick } from 'vue'
import { useMessage } from '/@/hooks/message'
import { addObj, editObj, getDetail } from '/@/api/stuwork/teachbuilding'
const emit = defineEmits(['refresh'])
// 定义变量内容
const dataFormRef = ref()
const visible = ref(false)
const loading = ref(false)
const operType = ref('add') // add 或 edit
// 提交表单数据
const form = reactive({
id: '',
buildingNo: undefined as number | undefined,
remarks: ''
})
// 定义校验规则
const dataRules = {
buildingNo: [
{ required: true, message: '请输入楼号', trigger: 'blur' },
{ type: 'number', min: 1, message: '楼号必须大于0', trigger: 'blur' }
]
}
// 打开弹窗
const openDialog = (type: string = 'add', row?: any) => {
visible.value = true
operType.value = type
// 重置表单数据
nextTick(() => {
dataFormRef.value?.resetFields()
form.id = ''
form.buildingNo = undefined
form.remarks = ''
// 编辑时填充数据
if (type === 'edit' && row) {
form.id = row.id
form.buildingNo = row.buildingNo
form.remarks = row.remarks || ''
}
})
}
// 提交表单
const onSubmit = async () => {
if (!dataFormRef.value) return
await dataFormRef.value.validate(async (valid: boolean) => {
if (!valid) return
loading.value = true
try {
if (operType.value === 'add') {
await addObj({
buildingNo: form.buildingNo,
remarks: form.remarks
})
useMessage().success('新增成功')
} else {
await editObj({
id: form.id,
buildingNo: form.buildingNo,
remarks: form.remarks
})
useMessage().success('编辑成功')
}
visible.value = false
emit('refresh')
} catch (err: any) {
useMessage().error(err.msg || (operType.value === 'add' ? '新增失败' : '编辑失败'))
} finally {
loading.value = false
}
})
}
// 暴露方法
defineExpose({
openDialog
})
</script>
<style scoped lang="scss">
</style>

View File

@@ -0,0 +1,116 @@
<template>
<div class="layout-padding">
<div class="layout-padding-auto layout-padding-view">
<!-- 操作按钮 -->
<el-row>
<div class="mb8" style="width: 100%">
<el-button
icon="Plus"
type="primary"
class="ml10"
@click="formDialogRef.openDialog()">
新增
</el-button>
<right-toolbar
class="ml10 mr20"
style="float: right;"
@queryTable="getDataList">
</right-toolbar>
</div>
</el-row>
<!-- 表格 -->
<el-table
:data="state.dataList"
v-loading="state.loading"
border
:cell-style="tableStyle.cellStyle"
:header-cell-style="tableStyle.headerCellStyle">
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="buildingNo" label="楼号" show-overflow-tooltip align="center" />
<el-table-column prop="remarks" label="备注" show-overflow-tooltip align="center" min-width="200" />
<el-table-column label="操作" width="150" align="center" fixed="right">
<template #default="scope">
<el-button
icon="Edit"
text
type="primary"
@click="handleEdit(scope.row)">
编辑
</el-button>
<el-button
icon="Delete"
text
type="danger"
@click="handleDelete(scope.row)">
删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<pagination
@size-change="sizeChangeHandle"
@current-change="currentChangeHandle"
v-bind="state.pagination" />
</div>
<!-- 新增/编辑表单弹窗 -->
<form-dialog ref="formDialogRef" @refresh="getDataList" />
</div>
</template>
<script setup lang="ts" name="TeachBuilding">
import { reactive, ref, onMounted } from 'vue'
import { BasicTableProps, useTable } from "/@/hooks/table";
import { fetchList, delObj } from "/@/api/stuwork/teachbuilding";
import { useMessage, useMessageBox } from "/@/hooks/message";
import FormDialog from './form.vue'
// 定义变量内容
const formDialogRef = ref()
// 配置 useTable
const state: BasicTableProps = reactive<BasicTableProps>({
queryForm: {},
pageList: fetchList,
props: {
item: 'records',
totalCount: 'total'
},
createdIsNeed: true
})
// table hook
const {
getDataList,
currentChangeHandle,
sizeChangeHandle,
tableStyle
} = useTable(state)
// 编辑
const handleEdit = (row: any) => {
formDialogRef.value?.openDialog('edit', row)
}
// 删除
const handleDelete = async (row: any) => {
const { confirm } = useMessageBox()
try {
await confirm('确定要删除该教学楼吗?')
await delObj([row.id])
useMessage().success('删除成功')
getDataList()
} catch (err: any) {
if (err !== 'cancel') {
useMessage().error(err.msg || '删除失败')
}
}
}
</script>
<style scoped lang="scss">
</style>

View File

@@ -0,0 +1,150 @@
<template>
<el-dialog
title="批量设置"
v-model="visible"
:width="600"
:close-on-click-modal="false"
draggable>
<el-form
ref="dataFormRef"
:model="form"
:rules="dataRules"
label-width="100px"
v-loading="loading">
<el-alert
title="提示"
type="info"
:closable="false"
style="margin-bottom: 20px;">
<template #default>
<div>已选择 {{ selectedCount }} 条教室记录请选择要设置的二级学院</div>
</template>
</el-alert>
<el-form-item label="二级学院" prop="deptCode">
<el-select
v-model="form.deptCode"
placeholder="请选择二级学院"
clearable
filterable
style="width: 100%">
<el-option
v-for="item in deptList"
:key="item.deptCode"
:label="item.deptName"
:value="item.deptCode">
</el-option>
</el-select>
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="visible = false"> </el-button>
<el-button type="primary" @click="onSubmit" :disabled="loading"> </el-button>
</span>
</template>
</el-dialog>
</template>
<script setup lang="ts" name="TeachClassroomBatchDialog">
import { ref, reactive, nextTick, onMounted, computed } from 'vue'
import { useMessage } from '/@/hooks/message'
import { batchSet } from '/@/api/stuwork/teachclassroom'
import { getDeptListByLevelTwo } from '/@/api/basic/basicdept'
const emit = defineEmits(['refresh'])
// 定义变量内容
const dataFormRef = ref()
const visible = ref(false)
const loading = ref(false)
const deptList = ref<any[]>([])
const selectedRows = ref<any[]>([])
// 提交表单数据
const form = reactive({
deptCode: ''
})
// 选中数量
const selectedCount = computed(() => selectedRows.value.length)
// 定义校验规则
const dataRules = {
deptCode: [
{ required: true, message: '请选择二级学院', trigger: 'change' }
]
}
// 打开弹窗
const openDialog = (rows: any[]) => {
selectedRows.value = rows
visible.value = true
resetForm()
nextTick(() => {
dataFormRef.value?.resetFields()
})
}
// 重置表单
const resetForm = () => {
form.deptCode = ''
}
// 提交表单
const onSubmit = async () => {
if (!dataFormRef.value) return
await dataFormRef.value.validate(async (valid: boolean) => {
if (!valid) return
if (selectedRows.value.length === 0) {
useMessage().warning('请先选择要设置的教室')
return
}
loading.value = true
try {
const ids = selectedRows.value.map((row: any) => row.id)
await batchSet({
ids: ids,
deptCode: form.deptCode
})
useMessage().success('批量设置成功')
visible.value = false
selectedRows.value = []
emit('refresh')
} catch (err: any) {
useMessage().error(err.msg || '批量设置失败')
} finally {
loading.value = false
}
})
}
// 获取系部列表
const getDeptListData = async () => {
try {
const res = await getDeptListByLevelTwo()
if (res.data) {
deptList.value = Array.isArray(res.data) ? res.data : []
}
} catch (err) {
console.error('获取系部列表失败', err)
deptList.value = []
}
}
// 初始化
onMounted(() => {
getDeptListData()
})
// 暴露方法
defineExpose({
openDialog
})
</script>
<style scoped lang="scss">
</style>

View File

@@ -0,0 +1,195 @@
<template>
<el-dialog
:title="form.id ? '编辑' : '新增'"
v-model="visible"
:width="600"
:close-on-click-modal="false"
draggable>
<el-form
ref="dataFormRef"
:model="form"
:rules="dataRules"
label-width="100px"
v-loading="loading">
<el-form-item label="学院" prop="deptCode">
<el-select
v-model="form.deptCode"
placeholder="请选择学院"
clearable
filterable
style="width: 100%">
<el-option
v-for="item in deptList"
:key="item.deptCode"
:label="item.deptName"
:value="item.deptCode">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="楼号" prop="buildingNo">
<el-select
v-model="form.buildingNo"
placeholder="请选择楼号"
clearable
filterable
style="width: 100%">
<el-option
v-for="item in buildingList"
:key="item.buildingNo"
:label="item.buildingNo"
:value="item.buildingNo">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="教室位置" prop="position">
<el-input
v-model="form.position"
placeholder="请输入教室位置" />
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="visible = false"> </el-button>
<el-button type="primary" @click="onSubmit" :disabled="loading"> </el-button>
</span>
</template>
</el-dialog>
</template>
<script setup lang="ts" name="TeachClassroomFormDialog">
import { ref, reactive, nextTick, onMounted } from 'vue'
import { useMessage } from '/@/hooks/message'
import { addObj, editObj } from '/@/api/stuwork/teachclassroom'
import { getDeptListByLevelTwo } from '/@/api/basic/basicdept'
import { getBuildingList } from '/@/api/stuwork/teachbuilding'
const emit = defineEmits(['refresh'])
// 定义变量内容
const dataFormRef = ref()
const visible = ref(false)
const loading = ref(false)
const deptList = ref<any[]>([])
const buildingList = ref<any[]>([])
const operType = ref('add') // add 或 edit
// 提交表单数据
const form = reactive({
id: '',
deptCode: '',
buildingNo: '',
position: ''
})
// 定义校验规则
const dataRules = {
deptCode: [
{ required: true, message: '请选择学院', trigger: 'change' }
],
buildingNo: [
{ required: true, message: '请选择楼号', trigger: 'change' }
],
position: [
{ required: true, message: '请输入教室位置', trigger: 'blur' }
]
}
// 打开弹窗
const openDialog = (type: string = 'add', row?: any) => {
visible.value = true
operType.value = type
// 重置表单数据
nextTick(() => {
dataFormRef.value?.resetFields()
form.id = ''
form.deptCode = ''
form.buildingNo = ''
form.position = ''
// 编辑时填充数据
if (type === 'edit' && row) {
form.id = row.id
form.deptCode = row.deptCode || ''
form.buildingNo = row.buildingNo || ''
form.position = row.position || ''
}
})
}
// 提交表单
const onSubmit = async () => {
if (!dataFormRef.value) return
await dataFormRef.value.validate(async (valid: boolean) => {
if (!valid) return
loading.value = true
try {
if (operType.value === 'add') {
await addObj({
deptCode: form.deptCode,
buildingNo: form.buildingNo,
position: form.position
})
useMessage().success('新增成功')
} else {
await editObj({
id: form.id,
deptCode: form.deptCode,
buildingNo: form.buildingNo,
position: form.position
})
useMessage().success('编辑成功')
}
visible.value = false
emit('refresh')
} catch (err: any) {
useMessage().error(err.msg || (operType.value === 'add' ? '新增失败' : '编辑失败'))
} finally {
loading.value = false
}
})
}
// 获取系部列表
const getDeptListData = async () => {
try {
const res = await getDeptListByLevelTwo()
if (res.data) {
deptList.value = Array.isArray(res.data) ? res.data : []
}
} catch (err) {
console.error('获取系部列表失败', err)
deptList.value = []
}
}
// 获取楼号列表
const getBuildingListData = async () => {
try {
const res = await getBuildingList()
if (res.data) {
buildingList.value = Array.isArray(res.data) ? res.data : []
}
} catch (err) {
console.error('获取楼号列表失败', err)
buildingList.value = []
}
}
// 初始化
onMounted(() => {
getDeptListData()
getBuildingListData()
})
// 暴露方法
defineExpose({
openDialog
})
</script>
<style scoped lang="scss">
</style>

View File

@@ -0,0 +1,244 @@
<template>
<div class="layout-padding">
<div class="layout-padding-auto layout-padding-view">
<!-- 搜索表单 -->
<el-row v-show="showSearch">
<el-form :model="searchForm" ref="searchFormRef" :inline="true" @keyup.enter="handleSearch">
<el-form-item label="学院" prop="deptCode">
<el-select
v-model="searchForm.deptCode"
placeholder="请选择学院"
clearable
filterable
style="width: 200px">
<el-option
v-for="item in deptList"
:key="item.deptCode"
:label="item.deptName"
:value="item.deptCode">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="楼号" prop="buildingNo">
<el-select
v-model="searchForm.buildingNo"
placeholder="请选择楼号"
clearable
filterable
style="width: 200px">
<el-option
v-for="item in buildingList"
:key="item.buildingNo"
:label="item.buildingNo"
:value="item.buildingNo">
</el-option>
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" plain icon="Search" @click="handleSearch">查询</el-button>
<el-button icon="Refresh" @click="handleReset">重置</el-button>
</el-form-item>
</el-form>
</el-row>
<!-- 操作按钮 -->
<el-row>
<div class="mb8" style="width: 100%">
<el-button
icon="Plus"
type="primary"
class="ml10"
@click="formDialogRef.openDialog()">
新增
</el-button>
<el-button
icon="Setting"
type="success"
class="ml10"
:disabled="selectedRows.length === 0"
@click="handleBatchSet">
批量设置
</el-button>
<right-toolbar
v-model:showSearch="showSearch"
class="ml10 mr20"
style="float: right;"
@queryTable="getDataList">
</right-toolbar>
</div>
</el-row>
<!-- 表格 -->
<el-table
:data="state.dataList"
v-loading="state.loading"
border
row-key="id"
:cell-style="tableStyle.cellStyle"
:header-cell-style="tableStyle.headerCellStyle"
@selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="deptName" label="学院" show-overflow-tooltip align="center" />
<el-table-column prop="buildingNo" label="楼号" show-overflow-tooltip align="center" />
<el-table-column prop="position" label="教室位置" show-overflow-tooltip align="center" />
<el-table-column label="操作" width="150" align="center" fixed="right">
<template #default="scope">
<el-button
icon="Edit"
text
type="primary"
@click="handleEdit(scope.row)">
编辑
</el-button>
<el-button
icon="Delete"
text
type="danger"
@click="handleDelete(scope.row)">
删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<pagination
@size-change="sizeChangeHandle"
@current-change="currentChangeHandle"
v-bind="state.pagination" />
</div>
<!-- 新增/编辑表单弹窗 -->
<form-dialog ref="formDialogRef" @refresh="getDataList" />
<!-- 批量设置弹窗 -->
<batch-dialog ref="batchDialogRef" @refresh="getDataList" />
</div>
</template>
<script setup lang="ts" name="TeachClassroom">
import { reactive, ref, onMounted } from 'vue'
import { BasicTableProps, useTable } from "/@/hooks/table";
import { fetchList, delObj } from "/@/api/stuwork/teachclassroom";
import { getDeptListByLevelTwo } from "/@/api/basic/basicdept";
import { getBuildingList } from "/@/api/stuwork/teachbuilding";
import { useMessage, useMessageBox } from "/@/hooks/message";
import FormDialog from './form.vue'
import BatchDialog from './batch.vue'
// 定义变量内容
const searchFormRef = ref()
const showSearch = ref(true)
const deptList = ref<any[]>([])
const buildingList = ref<any[]>([])
const formDialogRef = ref()
const batchDialogRef = ref()
const selectedRows = ref<any[]>([])
// 搜索表单
const searchForm = reactive({
deptCode: '',
buildingNo: ''
})
// 配置 useTable
const state: BasicTableProps = reactive<BasicTableProps>({
queryForm: searchForm,
pageList: fetchList,
props: {
item: 'records',
totalCount: 'total'
},
createdIsNeed: true
})
// table hook
const {
getDataList,
currentChangeHandle,
sizeChangeHandle,
tableStyle
} = useTable(state)
// 查询
const handleSearch = () => {
getDataList()
}
// 重置
const handleReset = () => {
searchFormRef.value?.resetFields()
getDataList()
}
// 编辑
const handleEdit = (row: any) => {
formDialogRef.value?.openDialog('edit', row)
}
// 删除
const handleDelete = async (row: any) => {
const { confirm } = useMessageBox()
try {
await confirm('确定要删除该教室吗?')
await delObj([row.id])
useMessage().success('删除成功')
getDataList()
} catch (err: any) {
if (err !== 'cancel') {
useMessage().error(err.msg || '删除失败')
}
}
}
// 选择变化
const handleSelectionChange = (selection: any[]) => {
selectedRows.value = selection
}
// 批量设置
const handleBatchSet = () => {
if (selectedRows.value.length === 0) {
useMessage().warning('请先选择要设置的教室')
return
}
batchDialogRef.value?.openDialog(selectedRows.value)
}
// 获取系部列表
const getDeptListData = async () => {
try {
const res = await getDeptListByLevelTwo()
if (res.data) {
deptList.value = Array.isArray(res.data) ? res.data : []
}
} catch (err) {
console.error('获取系部列表失败', err)
deptList.value = []
}
}
// 获取楼号列表
const getBuildingListData = async () => {
try {
const res = await getBuildingList()
if (res.data) {
buildingList.value = Array.isArray(res.data) ? res.data : []
}
} catch (err) {
console.error('获取楼号列表失败', err)
buildingList.value = []
}
}
// 初始化
onMounted(() => {
getDeptListData()
getBuildingListData()
})
</script>
<style scoped lang="scss">
</style>