Files
school-developer/src/views/stuwork/classroomhygienemonthly/index.vue
2026-01-22 13:38:10 +08:00

719 lines
21 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="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-input
v-model="searchForm.schoolTerm"
placeholder="请输入学期"
clearable
style="width: 200px" />
</el-form-item>
<el-form-item label="月份" prop="month">
<el-date-picker
v-model="searchForm.month"
type="month"
placeholder="请选择月份"
format="YYYY-MM"
value-format="YYYY-MM"
clearable
style="width: 200px" />
</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 filteredClassList"
: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="Upload"
type="primary"
class="ml10"
@click="handleImport">
导入
</el-button>
<el-button
icon="DocumentChecked"
type="success"
class="ml10"
@click="handleCheck">
考核
</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
ref="tableRef"
:data="state.dataList"
v-loading="state.loading"
border
:cell-style="tableStyle.cellStyle"
:header-cell-style="tableStyle.headerCellStyle"
@sort-change="sortChangeHandle">
<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">
<el-table-column
v-if="checkColumnVisible(col.prop || '')"
:prop="col.prop"
:label="col.label"
show-overflow-tooltip>
<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 === 'schoolTerm'" #default="scope">
<el-tag size="small" type="primary" effect="plain">
{{ formatSchoolTerm(scope.row.schoolTerm) }}
</el-tag>
</template>
<!-- 评分列特殊模板 -->
<template v-else-if="col.prop === 'score'" #default="scope">
<el-tag v-if="scope.row.score !== undefined && scope.row.score !== null" size="small" :type="scope.row.score >= 80 ? 'success' : scope.row.score >= 60 ? 'warning' : 'danger'" effect="plain">
{{ scope.row.score }}
</el-tag>
<span v-else>-</span>
</template>
<!-- 月份列特殊模板 -->
<template v-else-if="col.prop === 'month'" #default="scope">
<el-tag v-if="scope.row.month" size="small" type="info" effect="plain">
{{ scope.row.month }}
</el-tag>
<span v-else>-</span>
</template>
</el-table-column>
</template>
<el-table-column label="操作" width="150" align="center" fixed="right">
<template #header>
<el-icon><Setting /></el-icon>
<span style="margin-left: 4px">操作</span>
</template>
<template #default="scope">
<el-button
icon="Delete"
text
type="danger"
@click="handleDelete([scope.row.id])">
删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<pagination
@size-change="sizeChangeHandle"
@current-change="currentChangeHandle"
v-bind="state.pagination" />
</div>
<!-- 导入对话框 -->
<upload-excel
ref="uploadExcelRef"
:title="'导入教室月卫生'"
:url="'/stuwork/classroomhygienemonthly/importData'"
:temp-url="templateUrl"
@refreshDataList="getDataList" />
<!-- 考核对话框 -->
<el-dialog
v-model="checkDialogVisible"
title="教室卫生考核"
:close-on-click-modal="false"
draggable
width="500px">
<el-form
ref="checkFormRef"
:model="checkForm"
label-width="100px">
<el-form-item label="学年" prop="schoolYear">
<el-select
v-model="checkForm.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-form-item label="学期" prop="schoolTerm">
<el-input
v-model="checkForm.schoolTerm"
placeholder="请输入学期"
clearable />
</el-form-item>
<el-form-item label="月份" prop="month">
<el-date-picker
v-model="checkForm.month"
type="month"
placeholder="请选择月份"
format="YYYY-MM"
value-format="YYYY-MM"
clearable
style="width: 100%" />
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="checkDialogVisible = false"> </el-button>
<el-button type="primary" @click="confirmCheck"> </el-button>
</span>
</template>
</el-dialog>
</div>
</template>
<script setup lang="ts" name="ClassRoomHygieneMonthly">
import { ref, reactive, defineAsyncComponent, computed, onMounted } from 'vue'
import { useRoute } from 'vue-router'
import { BasicTableProps, useTable } from "/@/hooks/table";
import { fetchList, delObjs, checkClassRoomHygieneMonthly } from "/@/api/stuwork/classroomhygienemonthly";
import { useMessage, useMessageBox } from "/@/hooks/message";
import { queryAllSchoolYear } from '/@/api/basic/basicyear'
import { getDeptList } from '/@/api/basic/basicclass'
import { getClassListByRole } from '/@/api/basic/basicclass'
import { getDicts } from '/@/api/admin/dict'
import TableColumnControl from '/@/components/TableColumnControl/index.vue'
import type { TableInstance } from 'element-plus'
// 引入组件
const UploadExcel = defineAsyncComponent(() => import('/@/components/Upload/Excel.vue'));
import { List, Calendar, Clock, OfficeBuilding, Grid, UserFilled, Location, DataAnalysis, Document, Setting, Menu } from '@element-plus/icons-vue'
// 定义变量内容
const route = useRoute()
const searchFormRef = ref()
const uploadExcelRef = ref()
const checkFormRef = ref()
const tableRef = ref<TableInstance>()
const columnControlRef = ref<any>()
// 搜索变量
const showSearch = ref(true)
const schoolYearList = ref<any[]>([])
const schoolTermList = ref<any[]>([])
const deptList = ref<any[]>([])
const classList = ref<any[]>([])
const checkDialogVisible = ref(false)
// 模板文件URL
const templateUrl = ref('assets/file/教室月卫生导入模板.xlsx')
// 表格列配置
const tableColumns = [
{ prop: 'schoolYear', label: '学年' },
{ prop: 'schoolTerm', label: '学期' },
{ prop: 'deptName', label: '学院' },
{ prop: 'classNo', label: '班号' },
{ prop: 'classMasterName', label: '班主任' },
{ prop: 'buildingNo', label: '教学楼号' },
{ prop: 'position', label: '教室号' },
{ prop: 'score', label: '评分' },
{ prop: 'note', label: '检查记录' },
{ prop: 'month', label: '月份' },
{ prop: '操作', label: '操作', alwaysShow: true, fixed: 'right' as const }
]
// 当前显示的列(从 localStorage 读取或默认全部显示)
const visibleColumns = ref<string[]>([])
// 列排序顺序
const columnOrder = ref<string[]>([])
// 立即从 localStorage 加载配置(在组件创建时)
const loadSavedConfig = () => {
// 根据路由生成 storageKey
const routePath = route.path.replace(/^\//, '').replace(/\//g, '-')
const storageKey = `table-columns-${routePath}`
const saved = localStorage.getItem(storageKey)
if (saved) {
try {
const savedColumns = JSON.parse(saved)
const validColumns = tableColumns
.filter(col => !col.alwaysShow && !col.fixed)
.map(col => col.prop || col.label)
const filteredSaved = savedColumns.filter((col: string) => validColumns.includes(col))
// 使用保存的列配置(即使数量少于所有列,也使用保存的配置)
if (filteredSaved.length > 0) {
visibleColumns.value = filteredSaved
} else {
// 如果保存的配置为空或无效,使用所有列
visibleColumns.value = validColumns
}
} catch (e) {
visibleColumns.value = tableColumns
.filter(col => !col.alwaysShow && !col.fixed)
.map(col => col.prop || col.label)
}
} else {
visibleColumns.value = tableColumns
.filter(col => !col.alwaysShow && !col.fixed)
.map(col => col.prop || col.label)
}
// 加载列排序配置
const orderKey = `${storageKey}-order`
const savedOrder = localStorage.getItem(orderKey)
if (savedOrder) {
try {
const parsedOrder = JSON.parse(savedOrder)
const validColumns = tableColumns
.filter(col => !col.alwaysShow && !col.fixed)
.map(col => col.prop || col.label)
columnOrder.value = parsedOrder.filter((key: string) => validColumns.includes(key))
// 添加新列到排序列表末尾
validColumns.forEach(key => {
if (!columnOrder.value.includes(key)) {
columnOrder.value.push(key)
}
})
} catch (e) {
columnOrder.value = tableColumns
.filter(col => !col.alwaysShow && !col.fixed)
.map(col => col.prop || col.label)
}
} else {
columnOrder.value = tableColumns
.filter(col => !col.alwaysShow && !col.fixed)
.map(col => col.prop || col.label)
}
}
// 立即加载保存的配置
loadSavedConfig()
// 初始化可见列(已废弃,使用 loadSavedConfig 代替)
const initVisibleColumns = () => {
// 配置已在组件创建时通过 loadSavedConfig() 加载
// 这里只做兼容性处理,不重复加载
}
// 列显示控制函数
const checkColumnVisible = (prop: string): boolean => {
// 如果 visibleColumns 还没初始化,默认显示所有列
if (visibleColumns.value.length === 0) {
return true
}
// 检查 prop 是否在可见列列表中
const isVisible = visibleColumns.value.includes(prop)
return isVisible
}
// 监听列变化
const handleColumnChange = (columns: string[]) => {
visibleColumns.value = columns
// 根据路由生成 storageKey
const routePath = route.path.replace(/^\//, '').replace(/\//g, '-')
const storageKey = `table-columns-${routePath}`
// 只保存可选择的列(排除固定列和 alwaysShow 列)
const selectableColumns = columns.filter(col => {
const column = tableColumns.find(c => (c.prop || c.label) === col)
return column && !column.alwaysShow && !column.fixed
})
// 保存到 localStorage
localStorage.setItem(storageKey, JSON.stringify(selectableColumns))
}
// 监听列排序变化
const handleColumnOrderChange = (order: string[]) => {
columnOrder.value = order
// 根据路由生成 storageKey
const routePath = route.path.replace(/^\//, '').replace(/\//g, '-')
const storageKey = `table-columns-${routePath}-order`
// 保存排序顺序到 localStorage
localStorage.setItem(storageKey, JSON.stringify(order))
}
// 初始化列排序顺序(已废弃,使用 loadSavedConfig 代替)
const initColumnOrder = () => {
// 配置已在组件创建时通过 loadSavedConfig() 加载
// 这里只做兼容性处理,不重复加载
}
// 获取排序后的列配置
const sortedTableColumns = computed(() => {
// 获取所有可排序的列
const allSortableColumns = tableColumns.filter(col => !col.alwaysShow && !col.fixed)
if (columnOrder.value.length === 0) {
return allSortableColumns
}
const orderedColumns: typeof tableColumns = []
const unorderedColumns: typeof tableColumns = []
// 先按照保存的顺序添加列
columnOrder.value.forEach(key => {
const col = tableColumns.find(c => {
const colKey = c.prop || c.label
return colKey === key && !c.alwaysShow && !c.fixed
})
if (col) {
orderedColumns.push(col)
}
})
// 添加未在排序列表中的列(新增的列)
allSortableColumns.forEach(col => {
const key = col.prop || col.label
if (!columnOrder.value.includes(key)) {
unorderedColumns.push(col)
}
})
return [...orderedColumns, ...unorderedColumns]
})
// 列配置映射,包含每个列的渲染信息
const columnConfigMap: Record<string, any> = {
schoolYear: {
prop: 'schoolYear',
label: '学年',
icon: Calendar,
template: null
},
schoolTerm: {
prop: 'schoolTerm',
label: '学期',
icon: Clock,
template: 'schoolTerm'
},
deptName: {
prop: 'deptName',
label: '学院',
icon: OfficeBuilding,
template: null
},
classNo: {
prop: 'classNo',
label: '班号',
icon: Grid,
template: null
},
classMasterName: {
prop: 'classMasterName',
label: '班主任',
icon: UserFilled,
template: null
},
buildingNo: {
prop: 'buildingNo',
label: '教学楼号',
icon: OfficeBuilding,
template: null
},
position: {
prop: 'position',
label: '教室号',
icon: Location,
template: null
},
score: {
prop: 'score',
label: '评分',
icon: DataAnalysis,
template: 'score'
},
note: {
prop: 'note',
label: '检查记录',
icon: Document,
template: null
},
month: {
prop: 'month',
label: '月份',
icon: Calendar,
template: 'month'
}
}
// 初始化函数会在 onMounted 中调用,确保 DOM 已准备好
// 注意visibleColumns 和 columnOrder 需要在组件挂载前初始化,以便传递给 TableColumnControl
// 考核表单
const checkForm = reactive({
schoolYear: '',
schoolTerm: '',
month: ''
})
// 搜索表单
const searchForm = reactive({
schoolYear: '',
schoolTerm: '',
month: '',
deptCode: '',
classCode: ''
})
// 根据学院筛选班级列表
const filteredClassList = computed(() => {
if (!searchForm.deptCode) {
return classList.value
}
return classList.value.filter((item: any) => item.deptCode === searchForm.deptCode)
})
// 配置 useTable
const state: BasicTableProps = reactive<BasicTableProps>({
queryForm: searchForm,
pageList: fetchList,
props: {
item: 'records',
totalCount: 'total'
}
})
// table hook
const {
getDataList,
currentChangeHandle,
sizeChangeHandle,
sortChangeHandle,
tableStyle
} = useTable(state)
// 查询
const handleSearch = () => {
getDataList()
}
// 重置
const handleReset = () => {
searchFormRef.value?.resetFields()
searchForm.schoolYear = ''
searchForm.schoolTerm = ''
searchForm.month = ''
searchForm.deptCode = ''
searchForm.classCode = ''
getDataList()
}
// 导入
const handleImport = () => {
(uploadExcelRef.value as any)?.show()
}
// 考核
const handleCheck = () => {
checkDialogVisible.value = true
Object.assign(checkForm, {
schoolYear: '',
schoolTerm: '',
month: ''
})
}
// 确认考核
const confirmCheck = async () => {
if (!checkForm.schoolYear || !checkForm.schoolTerm || !checkForm.month) {
useMessage().warning('请填写完整的考核信息')
return
}
// 格式化月份显示2026-01
const monthDisplay = checkForm.month
try {
await useMessageBox().confirm(
`是否确认对${monthDisplay}月进行考核?\n1如果当前指定年月份已经有考核数据则会做覆盖处理\n2考核对象为所有有评分的班级`
)
} catch {
return
}
try {
await checkClassRoomHygieneMonthly({
schoolYear: checkForm.schoolYear,
schoolTerm: checkForm.schoolTerm,
month: checkForm.month
})
useMessage().success('考核成功')
checkDialogVisible.value = false
getDataList()
} catch (err: any) {
useMessage().error(err.msg || '考核失败')
}
}
// 删除操作
const handleDelete = async (ids: string[]) => {
try {
await useMessageBox().confirm('此操作将永久删除')
} catch {
return
}
try {
await delObjs(ids)
getDataList()
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) {
schoolYearList.value = []
}
}
// 获取学院列表
const getDeptListData = async () => {
try {
const res = await getDeptList()
if (res.data) {
deptList.value = Array.isArray(res.data) ? res.data : []
}
} catch (err) {
deptList.value = []
}
}
// 获取班号列表
const getClassListData = async () => {
try {
const res = await getClassListByRole()
if (res.data) {
classList.value = Array.isArray(res.data) ? res.data : []
}
} catch (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) {
schoolTermList.value = []
}
}
// 格式化学期
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
}
// 初始化
onMounted(() => {
getSchoolYearList()
getDeptListData()
getClassListData()
getSchoolTermDict()
// 配置已在组件创建时通过 loadSavedConfig() 加载
// 确保配置已同步到 TableColumnControl 组件
nextTick(() => {
// 确保 visibleColumns 已正确加载
if (visibleColumns.value.length === 0) {
// 如果 visibleColumns 为空,重新加载配置
loadSavedConfig()
}
// visibleColumns 已经通过 v-model 绑定到 TableColumnControl应该会自动同步
})
})
</script>