Files
school-developer/src/views/stuwork/stuunionleague/index.vue
2026-02-06 00:04:33 +08:00

469 lines
14 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="modern-page-container">
<div class="page-wrapper">
<!-- 查询条件 -->
<el-card v-show="showSearch" class="search-card" shadow="never">
<template #header>
<div class="card-header">
<span class="card-title">
<el-icon class="title-icon"><Search /></el-icon>
查询条件
</span>
</div>
</template>
<el-form :model="state.queryForm" ref="searchFormRef" :inline="true" @keyup.enter="getDataList" class="search-form">
<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 label="班级编号" prop="classNo">
<el-input
v-model="state.queryForm.classNo"
placeholder="请输入班级编号"
clearable
style="width: 200px" />
</el-form-item>
<el-form-item label="姓名" prop="realName">
<el-input
v-model="state.queryForm.realName"
placeholder="请输入姓名"
clearable
style="width: 200px" />
</el-form-item>
<el-form-item label="序列号" prop="serNo">
<el-input
v-model="state.queryForm.serNo"
placeholder="请输入序列号"
clearable
style="width: 200px" />
</el-form-item>
<el-form-item label="入学年份" prop="grade">
<el-select
v-model="state.queryForm.grade"
placeholder="请选择入学年份"
clearable
style="width: 200px">
<el-option
v-for="item in gradeList"
:key="item.grade"
:label="item.grade"
:value="item.grade">
</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-card>
<!-- 数据列表 -->
<el-card class="content-card" shadow="never">
<template #header>
<div class="card-header">
<span class="card-title">
<el-icon class="title-icon"><Document /></el-icon>
学生团组织列表
</span>
<div class="header-actions">
<el-button
icon="Plus"
type="primary"
@click="formDialogRef.openDialog()">
新增
</el-button>
<el-button
icon="Upload"
type="success"
class="ml10"
@click="handleImport">
导入
</el-button>
<el-button
icon="Download"
type="warning"
class="ml10"
@click="handleExport">
导出
</el-button>
<right-toolbar
v-model:showSearch="showSearch"
class="ml10"
@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>
</div>
</template>
<!-- 表格 -->
<el-table
:data="state.dataList"
v-loading="state.loading"
stripe
:cell-style="tableStyle.cellStyle"
:header-cell-style="tableStyle.headerCellStyle"
class="modern-table">
<el-table-column type="index" label="序号" width="70" align="center">
<template #header>
<el-icon><List /></el-icon>
</template>
<template #default="{ $index }">
{{ $index + 1 + ((state.pagination?.current || 1) - 1) * (state.pagination?.size || 10) }}
</template>
</el-table-column>
<template v-for="col in visibleColumnsSorted" :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"
:min-width="col.minWidth"
:width="col.width">
<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 === 'enterTime'" #default="scope">
<span>{{ parseTime(scope.row.enterTime, '{y}-{m}-{d}') }}</span>
</template>
<!-- 职务格式化 -->
<template v-else-if="col.prop === 'position'" #default="scope">
<el-tag v-if="scope.row.position" size="small" type="primary" effect="plain">
{{ scope.row.position }}
</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="Edit"
link
type="primary"
@click="handleEdit(scope.row)">
编辑
</el-button>
<el-button
icon="Delete"
link
type="danger"
@click="handleDelete(scope.row)">
删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<div class="pagination-wrapper">
<pagination
@size-change="sizeChangeHandle"
@current-change="currentChangeHandle"
v-bind="state.pagination" />
</div>
</el-card>
</div>
<!-- 新增/编辑对话框 -->
<form-dialog ref="formDialogRef" @refresh="getDataList" />
<!-- 导入对话框 -->
<el-dialog
title="导入数据"
v-model="importDialogVisible"
:close-on-click-modal="false"
width="500px">
<el-upload
ref="uploadRef"
:auto-upload="false"
:limit="1"
:on-exceed="handleExceed"
:on-change="handleFileChange"
:file-list="fileList"
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" :loading="importLoading">确认</el-button>
</span>
</template>
</el-dialog>
</div>
</template>
<script setup lang="ts" name="StuUnionLeague">
import { reactive, ref, onMounted, computed, nextTick } from 'vue'
import { useRoute } from 'vue-router'
import { BasicTableProps, useTable } from "/@/hooks/table";
import { fetchList, delObj, importExcel, exportExcel } from "/@/api/stuwork/stuunionleague";
import { getClassListByRole } from "/@/api/basic/basicclass";
import { getGradeList } from "/@/api/basic/basicclass";
import { useMessage, useMessageBox } from "/@/hooks/message";
import { parseTime } from "/@/utils/formatTime";
import TableColumnControl from '/@/components/TableColumnControl/index.vue'
import { UploadFilled, List, OfficeBuilding, Grid, CreditCard, Avatar, Phone, Calendar, Postcard, Briefcase, Setting, Menu, Search, Document } from '@element-plus/icons-vue'
import { useTableColumnControl } from '/@/hooks/tableColumn'
import type { UploadFile, UploadFiles } from 'element-plus';
import FormDialog from './form.vue'
// 定义变量
const route = useRoute()
const formDialogRef = ref()
const columnControlRef = ref<any>()
const searchFormRef = ref()
const uploadRef = ref()
const showSearch = ref(true)
const classList = ref<any[]>([])
const gradeList = ref<any[]>([])
const importDialogVisible = ref(false)
const importLoading = ref(false)
const fileList = ref<UploadFile[]>([])
// 表格列配置
const tableColumns = [
{ prop: 'deptName', label: '学院名称', minWidth: 150 },
{ prop: 'classNo', label: '班级', width: 120 },
{ prop: 'stuNo', label: '学号', width: 120 },
{ prop: 'realName', label: '姓名', width: 100 },
{ prop: 'phone', label: '手机号', width: 120 },
{ prop: 'enterTime', label: '入团时间', width: 120 },
{ prop: 'serNo', label: '序列号', width: 120 },
{ prop: 'position', label: '职务名称', width: 120 }
]
// 列配置映射
const columnConfigMap: Record<string, { icon: any }> = {
deptName: { icon: OfficeBuilding },
classNo: { icon: Grid },
stuNo: { icon: CreditCard },
realName: { icon: Avatar },
phone: { icon: Phone },
enterTime: { icon: Calendar },
serNo: { icon: Postcard },
position: { icon: Briefcase }
}
// 表格列控制hook
const {
visibleColumns,
visibleColumnsSorted,
checkColumnVisible,
handleColumnChange,
handleColumnOrderChange
} = useTableColumnControl(tableColumns)
// 表格样式
const tableStyle = {
cellStyle: { padding: '8px 0' },
headerCellStyle: { background: '#f5f7fa', color: '#606266', fontWeight: 'bold' }
}
// 使用 useTable
const state: BasicTableProps = reactive<BasicTableProps>({
queryForm: {
classCode: '',
classNo: '',
realName: '',
serNo: '',
grade: ''
},
pageList: fetchList,
props: {
item: 'records',
totalCount: 'total'
},
createdIsNeed: true
})
// table hook
const {
getDataList,
currentChangeHandle,
sizeChangeHandle,
tableStyle: _tableStyle
} = useTable(state)
// 重置
const handleReset = () => {
searchFormRef.value?.resetFields()
state.queryForm.classCode = ''
state.queryForm.classNo = ''
state.queryForm.realName = ''
state.queryForm.serNo = ''
state.queryForm.grade = ''
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 handleImport = () => {
importDialogVisible.value = true
fileList.value = []
}
// 文件变化
const handleFileChange = (file: UploadFile, files: UploadFiles) => {
fileList.value = [file]
}
// 文件超出限制
const handleExceed = () => {
useMessage().warning('文件数量超出限制')
}
// 提交导入
const handleImportSubmit = async () => {
if (fileList.value.length === 0) {
useMessage().warning('请先选择要上传的文件')
return
}
const file = fileList.value[0].raw
if (!file) {
useMessage().warning('文件无效')
return
}
importLoading.value = true
try {
await importExcel(file as File)
useMessage().success('导入成功')
importDialogVisible.value = false
fileList.value = []
getDataList()
} catch (err: any) {
useMessage().error(err.msg || '导入失败')
} finally {
importLoading.value = false
}
}
// 导出
const handleExport = async () => {
try {
const res = await exportExcel(state.queryForm)
const blob = new Blob([res.data as BlobPart])
const url = window.URL.createObjectURL(blob)
const link = document.createElement('a')
link.href = url
link.download = `学生团组织_${parseTime(new Date(), '{y}{m}{d}{h}{i}{s}')}.xlsx`
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
window.URL.revokeObjectURL(url)
useMessage().success('导出成功')
} catch (err: any) {
useMessage().error(err.msg || '导出失败')
}
}
// 获取班级列表
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 = []
}
}
// 获取入学年份列表
const getGradeListData = async () => {
try {
const res = await getGradeList()
if (res.data && Array.isArray(res.data)) {
gradeList.value = res.data
} else {
gradeList.value = []
}
} catch (err) {
gradeList.value = []
}
}
// ???
onMounted(() => {
getClassListData()
getGradeListData()
nextTick(() => {
if (visibleColumns.value.length === 0) {
}
})
})
</script>
<style scoped lang="scss">
@import '/@/assets/styles/modern-page.scss';
</style>