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

568 lines
17 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="state.queryForm" ref="searchFormRef" :inline="true" @keyup.enter="getDataList">
<el-form-item label="学年" prop="schoolYear">
<el-select
v-model="state.queryForm.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="state.queryForm.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="state.queryForm.deptCode"
placeholder="请选择学院"
clearable
filterable
style="width: 200px"
@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-form-item label="班级" prop="classCode">
<el-select
v-model="state.queryForm.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="getDataList">查询</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"
@click="formDialogRef.openDialog()">
新增
</el-button>
<el-button
icon="Upload"
type="primary"
class="ml10"
@click="handleImport">
导入
</el-button>
<right-toolbar
v-model:showSearch="showSearch"
class="ml10 mr20"
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
: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>
<el-table-column
v-for="col in visibleColumnsSorted"
:key="col.prop"
:prop="col.prop"
:label="col.label"
:width="col.width"
:min-width="col.minWidth"
:show-overflow-tooltip="col.showOverflowTooltip !== false"
:align="col.align || 'center'">
<template #header>
<el-icon v-if="col.icon"><component :is="col.icon" /></el-icon>
<span :style="{ marginLeft: col.icon ? '4px' : '0' }">{{ col.label }}</span>
</template>
<template #default="scope" v-if="col.prop === 'schoolTerm'">
<el-tag size="small" type="primary" effect="plain">
{{ formatSchoolTerm(scope.row.schoolTerm) }}
</el-tag>
</template>
<template #default="scope" v-else-if="col.prop === 'ruleName'">
<el-tag v-if="scope.row.ruleName" size="small" type="warning" effect="light">
{{ scope.row.ruleName }}
</el-tag>
<span v-else>-</span>
</template>
</el-table-column>
<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="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" />
<!-- 导入弹窗 -->
<el-dialog
title="导入文明班级"
v-model="importDialogVisible"
:width="500"
:close-on-click-modal="false"
draggable>
<div style="margin-bottom: 15px;">
<el-button
icon="Download"
type="success"
@click="handleDownloadTemplate">
下载模板
</el-button>
</div>
<el-upload
ref="uploadRef"
:auto-upload="false"
:on-change="handleFileChange"
:limit="1"
accept=".xlsx,.xls"
drag>
<el-icon class="el-icon--upload"><upload-filled /></el-icon>
<div class="el-upload__text">
将文件拖到此处<em>点击上传</em>
</div>
<template #tip>
<div class="el-upload__tip">
只能上传 xlsx/xls 文件
</div>
</template>
</el-upload>
<template #footer>
<span class="dialog-footer">
<el-button @click="importDialogVisible = false"> </el-button>
<el-button type="primary" @click="handleImportSubmit" :disabled="!importFile || importLoading"> </el-button>
</span>
</template>
</el-dialog>
</div>
</template>
<script setup lang="ts" name="RewardClass">
import { reactive, ref, onMounted, computed } from 'vue'
import { useRoute } from 'vue-router'
import { BasicTableProps, useTable } from "/@/hooks/table";
import { fetchList, delObj, importExcel } from "/@/api/stuwork/rewardclass";
import { getDeptList } from "/@/api/basic/basicclass";
import { getClassListByRole } from "/@/api/basic/basicclass";
import { queryAllSchoolYear } from "/@/api/basic/basicyear";
import { getDicts } from "/@/api/admin/dict";
import { useMessage, useMessageBox } from "/@/hooks/message";
import TableColumnControl from '/@/components/TableColumnControl/index.vue'
import { UploadFilled, List, Calendar, Clock, OfficeBuilding, Grid, UserFilled, Trophy, EditPen, Setting, Menu } from '@element-plus/icons-vue'
import FormDialog from './form.vue'
// 定义变量内容
const route = useRoute()
const formDialogRef = ref()
const uploadRef = ref()
const searchFormRef = ref()
const columnControlRef = ref()
const showSearch = ref(true)
const schoolYearList = ref<any[]>([])
const schoolTermList = ref<any[]>([])
const deptList = ref<any[]>([])
const classList = ref<any[]>([])
const importDialogVisible = ref(false)
const importFile = ref<File | null>(null)
const importLoading = ref(false)
// 表格列配置
const tableColumns = [
{ prop: 'schoolYear', label: '学年', icon: Calendar },
{ prop: 'schoolTerm', label: '学期', icon: Clock },
{ prop: 'deptName', label: '学院', icon: OfficeBuilding },
{ prop: 'classNo', label: '班级', icon: Grid },
{ prop: 'classMasterName', label: '班主任', icon: UserFilled },
{ prop: 'ruleName', label: '奖项名称', icon: Trophy, minWidth: 150 },
{ prop: 'remarks', label: '备注', icon: EditPen, minWidth: 200 }
]
// 当前显示的列
const visibleColumns = ref<string[]>([])
// 列排序顺序
const columnOrder = ref<string[]>([])
// 立即从 localStorage 加载配置
const loadSavedConfig = () => {
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.map(col => col.prop || col.label)
const filteredSaved = savedColumns.filter((col: string) => validColumns.includes(col))
visibleColumns.value = filteredSaved.length > 0 ? filteredSaved : validColumns
} catch (e) {
visibleColumns.value = tableColumns.map(col => col.prop || col.label)
}
} else {
visibleColumns.value = tableColumns.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.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.map(col => col.prop || col.label)
}
} else {
columnOrder.value = tableColumns.map(col => col.prop || col.label)
}
}
// 列变化处理
const handleColumnChange = (value: string[]) => {
visibleColumns.value = value
const routePath = route.path.replace(/^\//, '').replace(/\//g, '-')
const storageKey = `table-columns-${routePath}`
const selectableColumns = value.filter(col => {
const column = tableColumns.find(c => (c.prop || c.label) === col)
return column && !column.alwaysShow && !column.fixed
})
localStorage.setItem(storageKey, JSON.stringify(selectableColumns))
}
// 列排序变化处理
const handleColumnOrderChange = (order: string[]) => {
columnOrder.value = order
const routePath = route.path.replace(/^\//, '').replace(/\//g, '-')
const storageKey = `table-columns-${routePath}`
localStorage.setItem(`${storageKey}-order`, JSON.stringify(order))
}
// 排序后的表格列
const visibleColumnsSorted = computed(() => {
const columns = tableColumns.filter(col => {
const key = col.prop || col.label
return visibleColumns.value.includes(key)
})
if (columnOrder.value.length > 0) {
const orderedColumns: any[] = []
const unorderedColumns: any[] = []
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 tableStyle = {
cellStyle: { padding: '8px 0' },
headerCellStyle: { background: '#f5f7fa', color: '#606266', fontWeight: 'bold' }
}
// 配置 useTable
const state: BasicTableProps = reactive<BasicTableProps>({
queryForm: {
schoolYear: '',
schoolTerm: '',
deptCode: '',
classCode: ''
},
pageList: fetchList,
props: {
item: 'records',
totalCount: 'total'
},
createdIsNeed: true // 页面加载时自动获取数据
})
// table hook
const {
getDataList,
currentChangeHandle,
sizeChangeHandle,
tableStyle: _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 handleDeptChange = () => {
// 可以根据学院筛选班级,这里暂时不实现
}
// 重置
const handleReset = () => {
searchFormRef.value?.resetFields()
state.queryForm.schoolYear = ''
state.queryForm.schoolTerm = ''
state.queryForm.deptCode = ''
state.queryForm.classCode = ''
getDataList()
}
// 导入
const handleImport = () => {
importDialogVisible.value = true
importFile.value = null
uploadRef.value?.clearFiles()
}
// 下载模板
const handleDownloadTemplate = async () => {
try {
const fileName = '文明班级导入模板.xlsx'
// 使用动态导入获取文件URL从 views/stuwork/rewardclass 到 assets/file 的相对路径
const fileUrl = new URL(`../../../assets/file/${fileName}`, import.meta.url).href
const response = await fetch(fileUrl)
if (!response.ok) {
throw new Error('文件下载失败')
}
const blob = await response.blob()
const url = window.URL.createObjectURL(blob)
const link = document.createElement('a')
link.href = url
link.download = fileName
document.body.appendChild(link)
link.click()
window.URL.revokeObjectURL(url)
document.body.removeChild(link)
useMessage().success('模板下载成功')
} catch (error) {
useMessage().error('模板下载失败,请检查模板文件是否存在')
}
}
// 文件变化
const handleFileChange = (file: any) => {
importFile.value = file.raw
}
// 提交导入
const handleImportSubmit = async () => {
if (!importFile.value) {
useMessage().warning('请选择要导入的文件')
return
}
importLoading.value = true
try {
await importExcel(importFile.value)
useMessage().success('导入成功')
importDialogVisible.value = false
importFile.value = null
uploadRef.value?.clearFiles()
getDataList()
} catch (err: any) {
useMessage().error(err.msg || '导入失败')
} finally {
importLoading.value = false
}
}
// 编辑
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 getSchoolYearList = async () => {
try {
const res = await queryAllSchoolYear()
if (res.data && Array.isArray(res.data)) {
schoolYearList.value = res.data
} else {
schoolYearList.value = []
}
} catch (err) {
schoolYearList.value = []
}
}
// 获取学期字典
const getSchoolTermDict = async () => {
try {
const res = await getDicts('school_term')
if (res.data && Array.isArray(res.data)) {
schoolTermList.value = res.data.map((item: any) => ({
label: item.label || item.dictLabel || item.name,
value: item.value || item.dictValue || item.code
}))
} else {
schoolTermList.value = []
}
} catch (err) {
schoolTermList.value = []
}
}
// 获取学院列表
const getDeptListData = async () => {
try {
const res = await getDeptList()
if (res.data && Array.isArray(res.data)) {
deptList.value = res.data
} else {
deptList.value = []
}
} catch (err) {
deptList.value = []
}
}
// 获取班级列表
const getClassListData = async () => {
try {
const res = await getClassListByRole()
if (res.data && Array.isArray(res.data)) {
classList.value = res.data
} else {
classList.value = []
}
} catch (err) {
classList.value = []
}
}
// 初始化
loadSavedConfig()
onMounted(() => {
getSchoolYearList()
getSchoolTermDict()
getDeptListData()
getClassListData()
})
</script>
<style scoped lang="scss">
</style>