Files
school-developer/src/views/stuwork/stuassociation/index.vue
2026-03-02 18:18:06 +08:00

489 lines
15 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="deptCode">
<el-select
v-model="state.queryForm.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="teacherRealName">
<el-input
v-model="state.queryForm.teacherRealName"
placeholder="请输入指导老师姓名"
clearable
style="width: 200px" />
</el-form-item>
<el-form-item label="所属类别" prop="type">
<el-select
v-model="state.queryForm.type"
placeholder="请选择所属类别"
clearable
style="width: 200px">
<el-option
v-for="item in typeList"
:key="item.value"
:label="item.label"
:value="item.value">
</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="DataAnalysis"
type="warning"
class="ml10"
@click="handleStats">
社团统计
</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 === 'num'" #default="scope">
<el-tag v-if="scope.row.num !== undefined && scope.row.num !== null" size="small" type="success" effect="plain">
{{ scope.row.num }}
</el-tag>
<span v-else>-</span>
</template>
<!-- 成立时间列特殊模板 -->
<template v-else-if="col.prop === 'openTime'" #default="scope">
<span>{{ parseTime(scope.row.openTime, '{y}-{m}-{d}') }}</span>
</template>
<!-- 所属类别列特殊模板 -->
<template v-else-if="col.prop === 'type'" #default="scope">
<el-tag size="small" type="warning" effect="plain">
{{ formatType(scope.row.type) }}
</el-tag>
</template>
</el-table-column>
</template>
<el-table-column label="操作" width="200" align="center" fixed="right">
<template #header>
<el-icon><Setting /></el-icon>
<span style="margin-left: 4px">操作</span>
</template>
<template #default="scope">
<el-button
icon="User"
link
type="primary"
@click="handleViewMember(scope.row)">
查看成员
</el-button>
<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" />
<!-- 查看成员弹窗 -->
<member-dialog ref="memberDialogRef" />
<!-- 社团统计弹窗 -->
<el-dialog
title="社团统计"
v-model="statsDialogVisible"
:width="700"
:close-on-click-modal="false"
draggable>
<el-table
:data="statsData"
v-loading="statsLoading"
stripe
border
max-height="400">
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="deptName" label="学院名称" align="center" min-width="150" />
<el-table-column prop="associationCount" label="社团数量" align="center" width="120">
<template #default="scope">
<el-tag :type="scope.row.associationCount > 0 ? 'success' : 'info'" effect="plain">
{{ scope.row.associationCount }}
</el-tag>
</template>
</el-table-column>
</el-table>
<!-- 统计汇总 -->
<div v-if="statsData.length > 0" class="stats-summary">
<el-row :gutter="20">
<el-col :span="12">
<div class="summary-item">
<span class="label">学院总数</span>
<span class="value">{{ statsData.length }}</span>
</div>
</el-col>
<el-col :span="12">
<div class="summary-item">
<span class="label">社团总数</span>
<span class="value highlight">{{ totalAssociationCount }}</span>
</div>
</el-col>
</el-row>
</div>
<template #footer>
<el-button @click="statsDialogVisible = false"> </el-button>
</template>
</el-dialog>
</div>
</template>
<script setup lang="ts" name="StuAssociation">
import { reactive, ref, onMounted, computed, nextTick } from 'vue'
import { useRoute } from 'vue-router'
import { BasicTableProps, useTable } from "/@/hooks/table";
import { fetchList, delObj, getAssociationStats } from "/@/api/stuwork/stuassociation";
import { getDeptList } from "/@/api/basic/basicclass";
import { getDicts } from "/@/api/admin/dict";
import { useMessage, useMessageBox } from "/@/hooks/message";
import { parseTime } from "/@/utils/formatTime";
import TableColumnControl from '/@/components/TableColumnControl/index.vue'
import FormDialog from './form.vue'
import MemberDialog from './member.vue'
import {
List, Trophy, OfficeBuilding, User, UserFilled, Calendar,
Phone, Collection, Document, Files, Setting, Menu, Search, DataAnalysis
} from '@element-plus/icons-vue'
import { useTableColumnControl } from '/@/hooks/tableColumn'
// 定义变量内容
const route = useRoute()
const formDialogRef = ref()
const columnControlRef = ref<any>()
const memberDialogRef = ref()
const searchFormRef = ref()
const showSearch = ref(true)
const deptList = ref<any[]>([])
const typeList = ref<any[]>([])
// 社团统计相关
const statsDialogVisible = ref(false)
const statsLoading = ref(false)
const statsData = ref<any[]>([])
// 表格列配置
const tableColumns = [
{ prop: 'associationName', label: '社团名称', minWidth: 200 },
{ prop: 'deptName', label: '学院', minWidth: 150 },
{ prop: 'teacherRealName', label: '指导老师姓名', width: 120 },
{ prop: 'maintainer', label: '负责人', width: 100 },
{ prop: 'num', label: '人数', width: 80 },
{ prop: 'openTime', label: '成立时间', width: 120 },
{ prop: 'tel', label: '联系电话', width: 120 },
{ prop: 'type', label: '所属类别', width: 120 },
{ prop: 'applyNote', label: '成立申请', minWidth: 200 },
{ prop: 'ruleNote', label: '社团章程', minWidth: 200 }
]
// 列配置映射(用于图标显示)
const columnConfigMap: Record<string, { icon: any }> = {
associationName: { icon: Trophy },
deptName: { icon: OfficeBuilding },
teacherRealName: { icon: User },
maintainer: { icon: UserFilled },
num: { icon: UserFilled },
openTime: { icon: Calendar },
tel: { icon: Phone },
type: { icon: Collection },
applyNote: { icon: Document },
ruleNote: { icon: Files }
}
// 使用表格列控制hook
const {
visibleColumns,
visibleColumnsSorted,
checkColumnVisible,
handleColumnChange,
handleColumnOrderChange
} = useTableColumnControl(tableColumns, route.path)
// 表格样式
const tableStyle = {
cellStyle: { padding: '8px 0' },
headerCellStyle: { background: '#f5f7fa', color: '#606266', fontWeight: 'bold' }
}
// 配置 useTable
const state: BasicTableProps = reactive<BasicTableProps>({
queryForm: {
deptCode: '',
teacherRealName: '',
type: ''
},
pageList: fetchList,
props: {
item: 'records',
totalCount: 'total'
},
createdIsNeed: true // 页面加载时自动获取数据
})
// table hook
const {
getDataList,
currentChangeHandle,
sizeChangeHandle,
tableStyle: _tableStyle
} = useTable(state)
// 格式化类别
const formatType = (value: string | number) => {
if (value === null || value === undefined || value === '') {
return '-'
}
const dictItem = typeList.value.find(item => item.value == value)
return dictItem ? dictItem.label : value
}
// 重置
const handleReset = () => {
searchFormRef.value?.resetFields()
state.queryForm.deptCode = ''
state.queryForm.teacherRealName = ''
state.queryForm.type = ''
getDataList()
}
// 查看成员
const handleViewMember = (row: any) => {
memberDialogRef.value?.openDialog(row.id, row.associationName)
}
// 编辑
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 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 getTypeDict = async () => {
try {
const res = await getDicts('association_type')
if (res.data && Array.isArray(res.data)) {
typeList.value = res.data.map((item: any) => ({
label: item.label || item.dictLabel || item.name,
value: item.value || item.dictValue || item.code
}))
} else {
typeList.value = []
}
} catch (err) {
typeList.value = []
}
}
// 社团统计
const handleStats = async () => {
statsDialogVisible.value = true
statsLoading.value = true
statsData.value = []
try {
const res = await getAssociationStats()
if (res.data && Array.isArray(res.data)) {
statsData.value = res.data
} else {
statsData.value = []
}
} catch (err: any) {
useMessage().error(err.msg || '获取统计数据失败')
statsData.value = []
} finally {
statsLoading.value = false
}
}
// 社团总数计算
const totalAssociationCount = computed(() => {
return statsData.value.reduce((sum, item) => sum + (item.associationCount || 0), 0)
})
// 初始化
onMounted(() => {
getDeptListData()
getTypeDict()
nextTick(() => {
if (visibleColumns.value.length === 0) {
}
})
})
</script>
<style scoped lang="scss">
@import '/@/assets/styles/modern-page.scss';
.stats-summary {
margin-top: 15px;
padding: 15px;
background: #f5f7fa;
border-radius: 4px;
.summary-item {
text-align: center;
.label {
color: #909399;
font-size: 14px;
}
.value {
font-size: 24px;
font-weight: bold;
margin-left: 5px;
color: #409eff;
&.highlight {
color: #67c23a;
}
}
}
}
</style>