Files
school-developer/src/views/stuwork/classroombase/index.vue
2026-01-26 11:07:16 +08:00

587 lines
18 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<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"
style="float: right;"
@queryTable="getDataList">
<TableColumnControl
ref="columnControlRef"
:columns="tableColumns"
v-model="visibleColumns"
trigger-type="default"
trigger-circle
@change="handleColumnChange"
@order-change="handleColumnOrderChange"
>
<template #trigger>
<el-tooltip class="item" effect="dark" content="列设置" placement="top">
<el-button circle style="margin-left: 0;">
<el-icon><Menu /></el-icon>
</el-button>
</el-tooltip>
</template>
</TableColumnControl>
</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">
<template #header>
<el-icon><List /></el-icon>
</template>
</el-table-column>
<template v-for="col in sortedTableColumns" :key="col.prop || col.label">
<el-table-column
v-if="checkColumnVisible(col.prop || '') && col.prop !== '操作'"
:prop="col.prop"
:label="col.label"
show-overflow-tooltip
align="center">
<template #header>
<el-icon><component :is="columnConfigMap[col.prop]?.icon || Calendar" /></el-icon>
<span style="margin-left: 4px">{{ col.label }}</span>
</template>
<!-- 班级状态列特殊模板 -->
<template v-if="col.prop === 'classStatus'" #default="scope">
<el-tag size="small" type="info" effect="plain">
{{ formatClassStatus(scope.row.classStatus) }}
</el-tag>
</template>
<!-- 人数列特殊模板 -->
<template v-else-if="col.prop === 'stuNum'" #default="scope">
<el-tag v-if="scope.row.stuNum" size="small" type="success" effect="plain">
{{ scope.row.stuNum }}
</el-tag>
<span v-else>-</span>
</template>
<!-- 讲台类型列特殊模板 -->
<template v-else-if="col.prop === 'platformType'" #default="scope">
<el-tag size="small" type="info" effect="plain">
{{ formatDict(scope.row.platformType, platformTypeList) }}
</el-tag>
</template>
<!-- 投影类型列特殊模板 -->
<template v-else-if="col.prop === 'tyType'" #default="scope">
<el-tag size="small" type="info" effect="plain">
{{ formatDict(scope.row.tyType, tyTypeList) }}
</el-tag>
</template>
<!-- 电视机列特殊模板 -->
<template v-else-if="col.prop === 'tvType'" #default="scope">
<el-tag size="small" type="info" effect="plain">
{{ formatDict(scope.row.tvType, tvTypeList) }}
</el-tag>
</template>
</el-table-column>
</template>
<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, computed, nextTick } from 'vue'
import { useRoute } from 'vue-router'
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 TableColumnControl from '/@/components/TableColumnControl/index.vue'
import ArrangeDialog from './arrange.vue'
import { List, OfficeBuilding, CircleCheck, Location, UserFilled, Collection, Setting, Menu, Calendar } from '@element-plus/icons-vue'
import { getTableConfigFromLocal, saveTableConfigToLocal, updateUserTableConfig } from '/@/api/admin/usertable'
// 定义变量内容
const route = useRoute()
const searchFormRef = ref()
const columnControlRef = ref<any>()
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 tableColumns = [
{ prop: 'buildingNo', label: '楼号' },
{ prop: 'deptName', label: '学院' },
{ prop: 'classStatus', label: '班级状态' },
{ prop: 'position', label: '教室位置' },
{ prop: 'stuNum', label: '人数' },
{ prop: 'teacherRealName', label: '班主任' },
{ prop: 'platformType', label: '讲台类型' },
{ prop: 'tyType', label: '投影类型' },
{ prop: 'tvType', label: '电视机' },
{ prop: 'chairCnt', label: '方凳数量' },
{ prop: 'tableCnt', label: '课桌数量' },
{ prop: 'password', label: '锁密码' }
]
// 列配置映射(用于图标)
const columnConfigMap: Record<string, { icon: any }> = {
buildingNo: { icon: OfficeBuilding },
deptName: { icon: OfficeBuilding },
classStatus: { icon: CircleCheck },
position: { icon: Location },
stuNum: { icon: UserFilled },
teacherRealName: { icon: UserFilled },
platformType: { icon: Collection },
tyType: { icon: Collection },
tvType: { icon: Collection },
chairCnt: { icon: Calendar },
tableCnt: { icon: Calendar },
password: { icon: Calendar }
}
// 当前显示的列
const visibleColumns = ref<string[]>([])
// 列排序顺序
const columnOrder = ref<string[]>([])
// 从本地统一存储加载配置
const loadSavedConfig = () => {
const routePath = route.path.replace(/^\//, '').replace(/\//g, '-')
const storageKey = `table-columns-${routePath}`
const savedConfig = getTableConfigFromLocal(storageKey)
if (savedConfig && savedConfig.visibleColumns) {
const validColumns = tableColumns.map(col => col.prop || col.label)
const filteredSaved = savedConfig.visibleColumns.filter((col: string) => validColumns.includes(col))
visibleColumns.value = filteredSaved.length > 0 ? filteredSaved : validColumns
} else {
visibleColumns.value = tableColumns.map(col => col.prop || col.label)
}
if (savedConfig && savedConfig.columnOrder) {
const validColumns = tableColumns.map(col => col.prop || col.label)
columnOrder.value = savedConfig.columnOrder.filter((key: string) => validColumns.includes(key))
validColumns.forEach(key => {
if (!columnOrder.value.includes(key)) {
columnOrder.value.push(key)
}
})
} else {
columnOrder.value = tableColumns.map(col => col.prop || col.label)
}
}
loadSavedConfig()
// 排序后的表格列
const sortedTableColumns = computed(() => {
const columns = tableColumns.filter(col => {
const key = col.prop || col.label
return visibleColumns.value.includes(key)
})
if (columnOrder.value.length > 0) {
const orderedColumns: typeof tableColumns = []
const unorderedColumns: typeof tableColumns = []
columnOrder.value.forEach(key => {
const col = columns.find(c => (c.prop || c.label) === key)
if (col) {
orderedColumns.push(col)
}
})
columns.forEach(col => {
const key = col.prop || col.label
if (!columnOrder.value.includes(key)) {
unorderedColumns.push(col)
}
})
return [...orderedColumns, ...unorderedColumns]
}
return columns
})
// 列显示控制函数
const checkColumnVisible = (prop: string): boolean => {
if (visibleColumns.value.length === 0) {
return true
}
return visibleColumns.value.includes(prop)
}
// 监听列变化
const handleColumnChange = (columns: string[]) => {
visibleColumns.value = columns
const routePath = route.path.replace(/^\//, '').replace(/\//g, '-')
const storageKey = `table-columns-${routePath}`
const selectableColumns = columns.filter(col => {
const column = tableColumns.find(c => (c.prop || c.label) === col)
return column && !column.alwaysShow && !column.fixed
})
// 保存到本地统一存储
saveTableConfigToLocal(storageKey, { visibleColumns: selectableColumns })
// 异步保存到后端
updateUserTableConfig(storageKey, { visibleColumns: selectableColumns }).catch(() => {})
}
// 监听列排序变化
const handleColumnOrderChange = (order: string[]) => {
columnOrder.value = order
const routePath = route.path.replace(/^\//, '').replace(/\//g, '-')
const storageKey = `table-columns-${routePath}`
// 保存到本地统一存储
saveTableConfigToLocal(storageKey, { columnOrder: order })
// 异步保存到后端
updateUserTableConfig(storageKey, { columnOrder: order }).catch(() => {})
}
// 班级状态列表
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) {
// 确保即使出错也返回正确的数据结构
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) {
deptList.value = []
}
}
// 获取班级列表
const getClassListData = async () => {
try {
const res = await queryAllClass()
if (res.data) {
classList.value = Array.isArray(res.data) ? res.data : []
}
} catch (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) {
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) {
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) {
tvTypeList.value = []
}
}
// 初始化
onMounted(() => {
classStatusList.value = classStatusListData
getDeptListData()
getClassListData()
getPlatformTypeDict()
getTyTypeDict()
getTvTypeDict()
nextTick(() => {
if (visibleColumns.value.length === 0) {
loadSavedConfig()
}
})
})
</script>
<style scoped lang="scss">
</style>