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

506 lines
16 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="roomNo">
<el-input
v-model="state.queryForm.roomNo"
placeholder="请输入宿舍号"
clearable
style="width: 200px" />
</el-form-item>
<el-form-item label="奖项名称" prop="ruleName">
<el-input
v-model="state.queryForm.ruleName"
placeholder="请输入奖项名称"
clearable
style="width: 200px" />
</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="RewardDorm">
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/rewarddorm";
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, House, Trophy, EditPen, Setting, Menu } from '@element-plus/icons-vue'
import { getTableConfigFromLocal, saveTableConfigToLocal, updateUserTableConfig } from '/@/api/admin/usertable'
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 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: 'roomNo', label: '宿舍号', icon: House },
{ prop: 'ruleName', label: '奖项名称', icon: Trophy, minWidth: 150 },
{ prop: 'remarks', label: '备注', icon: EditPen, minWidth: 200 }
]
// 当前显示的列
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)
}
}
// 列变化处理
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
})
// 保存到本地统一存储
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 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: '',
roomNo: '',
ruleName: ''
},
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 handleReset = () => {
searchFormRef.value?.resetFields()
state.queryForm.schoolYear = ''
state.queryForm.schoolTerm = ''
state.queryForm.roomNo = ''
state.queryForm.ruleName = ''
getDataList()
}
// 导入
const handleImport = () => {
importDialogVisible.value = true
importFile.value = null
uploadRef.value?.clearFiles()
}
// 下载模板
const handleDownloadTemplate = async () => {
try {
const fileName = '文明宿舍导入模板.xlsx'
// 使用动态导入获取文件URL从 views/stuwork/rewarddorm 到 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 = []
}
}
// 初始化
loadSavedConfig()
onMounted(() => {
getSchoolYearList()
getSchoolTermDict()
})
</script>
<style scoped lang="scss">
</style>