社团统计及团员统计

This commit is contained in:
yaojian
2026-03-02 18:18:06 +08:00
parent 3f06c5bea0
commit ba15afef9b
6 changed files with 436 additions and 27 deletions

View File

@@ -59,3 +59,13 @@ export const delObj = (ids: string[]) => {
}); });
}; };
/**
* 社团统计 - 根据学院统计社团数量
*/
export const getAssociationStats = () => {
return request({
url: '/stuwork/stuassociation/stats',
method: 'get'
});
};

View File

@@ -23,3 +23,51 @@ export const fetchList = (query?: any) => {
} }
}) })
} }
/**
* 学分确认
* @param graduYear 毕业年份
*/
export const confirmScore = (graduYear: string) => {
return request({
url: '/stuwork/stugraducheck/confirmScore',
method: 'get',
params: { graduYear }
})
}
/**
* 操行考核确认
* @param graduYear 毕业年份
*/
export const confirmConduct = (graduYear: string) => {
return request({
url: '/stuwork/stugraducheck/confirmConduct',
method: 'get',
params: { graduYear }
})
}
/**
* 违纪确认
* @param graduYear 毕业年份
*/
export const confirmPunish = (graduYear: string) => {
return request({
url: '/stuwork/stugraducheck/confirmPunish',
method: 'get',
params: { graduYear }
})
}
/**
* 等级工确认
* @param graduYear 毕业年份
*/
export const confirmSkill = (graduYear: string) => {
return request({
url: '/stuwork/stugraducheck/confirmSkill',
method: 'get',
params: { graduYear }
})
}

View File

@@ -89,3 +89,13 @@ export const exportExcel = (query?: any) => {
}); });
}; };
/**
* 获取团员统计信息(全校总数+各学院统计)
*/
export const getStatistics = () => {
return request({
url: '/stuwork/stuunionleague/statistics',
method: 'get'
});
};

View File

@@ -70,6 +70,13 @@
@click="formDialogRef.openDialog()"> @click="formDialogRef.openDialog()">
新增 新增
</el-button> </el-button>
<el-button
icon="DataAnalysis"
type="warning"
class="ml10"
@click="handleStats">
社团统计
</el-button>
<right-toolbar <right-toolbar
v-model:showSearch="showSearch" v-model:showSearch="showSearch"
class="ml10" class="ml10"
@@ -190,6 +197,51 @@
<!-- 查看成员弹窗 --> <!-- 查看成员弹窗 -->
<member-dialog ref="memberDialogRef" /> <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> </div>
</template> </template>
@@ -197,7 +249,7 @@
import { reactive, ref, onMounted, computed, nextTick } from 'vue' import { reactive, ref, onMounted, computed, nextTick } from 'vue'
import { useRoute } from 'vue-router' import { useRoute } from 'vue-router'
import { BasicTableProps, useTable } from "/@/hooks/table"; import { BasicTableProps, useTable } from "/@/hooks/table";
import { fetchList, delObj } from "/@/api/stuwork/stuassociation"; import { fetchList, delObj, getAssociationStats } from "/@/api/stuwork/stuassociation";
import { getDeptList } from "/@/api/basic/basicclass"; import { getDeptList } from "/@/api/basic/basicclass";
import { getDicts } from "/@/api/admin/dict"; import { getDicts } from "/@/api/admin/dict";
import { useMessage, useMessageBox } from "/@/hooks/message"; import { useMessage, useMessageBox } from "/@/hooks/message";
@@ -207,7 +259,7 @@ import FormDialog from './form.vue'
import MemberDialog from './member.vue' import MemberDialog from './member.vue'
import { import {
List, Trophy, OfficeBuilding, User, UserFilled, Calendar, List, Trophy, OfficeBuilding, User, UserFilled, Calendar,
Phone, Collection, Document, Files, Setting, Menu, Search Phone, Collection, Document, Files, Setting, Menu, Search, DataAnalysis
} from '@element-plus/icons-vue' } from '@element-plus/icons-vue'
import { useTableColumnControl } from '/@/hooks/tableColumn' import { useTableColumnControl } from '/@/hooks/tableColumn'
@@ -222,6 +274,11 @@ const showSearch = ref(true)
const deptList = ref<any[]>([]) const deptList = ref<any[]>([])
const typeList = ref<any[]>([]) const typeList = ref<any[]>([])
// 社团统计相关
const statsDialogVisible = ref(false)
const statsLoading = ref(false)
const statsData = ref<any[]>([])
// 表格列配置 // 表格列配置
const tableColumns = [ const tableColumns = [
{ prop: 'associationName', label: '社团名称', minWidth: 200 }, { prop: 'associationName', label: '社团名称', minWidth: 200 },
@@ -362,6 +419,31 @@ const getTypeDict = async () => {
} }
} }
// 社团统计
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(() => { onMounted(() => {
getDeptListData() getDeptListData()
@@ -375,5 +457,32 @@ onMounted(() => {
<style scoped lang="scss"> <style scoped lang="scss">
@import '/@/assets/styles/modern-page.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> </style>

View File

@@ -103,6 +103,10 @@
毕业学生名单 毕业学生名单
</span> </span>
<div class="header-actions"> <div class="header-actions">
<el-button type="primary" icon="DataAnalysis" @click="handleConfirmScore">学分确认</el-button>
<el-button type="success" icon="DataAnalysis" @click="handleConfirmConduct">操行确认</el-button>
<el-button type="warning" icon="DataAnalysis" @click="handleConfirmPunish">违纪确认</el-button>
<el-button type="info" icon="DataAnalysis" @click="handleConfirmSkill">等级工确认</el-button>
<right-toolbar <right-toolbar
v-model:showSearch="showSearch" v-model:showSearch="showSearch"
class="ml10" class="ml10"
@@ -181,6 +185,61 @@
</div> </div>
</el-card> </el-card>
</div> </div>
<!-- 确认结果对话框 -->
<el-dialog
v-model="confirmDialogVisible"
:title="confirmTypeLabel"
width="800px"
destroy-on-close>
<div v-loading="confirmLoading">
<!-- 统计信息 -->
<el-row :gutter="20" class="confirm-stats">
<el-col :span="6">
<el-statistic title="总人数" :value="confirmResult.totalCount || 0">
<template #suffix>
<span style="font-size: 12px; color: #909399;"></span>
</template>
</el-statistic>
</el-col>
<el-col :span="6">
<el-statistic title="合格人数" :value="confirmResult.qualifiedCount || 0">
<template #suffix>
<span style="font-size: 12px; color: #67c23a;"></span>
</template>
</el-statistic>
</el-col>
<el-col :span="6">
<el-statistic title="不合格人数" :value="confirmResult.unqualifiedCount || 0">
<template #suffix>
<span style="font-size: 12px; color: #f56c6c;"></span>
</template>
</el-statistic>
</el-col>
</el-row>
<el-divider content-position="left">不合格学生列表</el-divider>
<!-- 不合格学生表格 -->
<el-table
:data="confirmResult.unqualifiedList || []"
stripe
max-height="350px"
:cell-style="tableStyle.cellStyle"
:header-cell-style="tableStyle.headerCellStyle">
<el-table-column type="index" label="序号" width="70" align="center" />
<el-table-column prop="stuNo" label="学号" align="center" width="120" />
<el-table-column prop="realName" label="姓名" align="center" width="100" />
<el-table-column prop="classNo" label="班号" align="center" width="120" />
<el-table-column prop="reason" label="不合格原因" align="center" min-width="150" />
<el-table-column prop="detail" label="详情" align="center" min-width="150" show-overflow-tooltip />
</el-table>
</div>
<template #footer>
<el-button @click="confirmDialogVisible = false">关闭</el-button>
</template>
</el-dialog>
</div> </div>
</template> </template>
@@ -188,7 +247,7 @@
import { reactive, ref, computed, onMounted } from 'vue' import { reactive, ref, computed, onMounted } from 'vue'
import { useRoute } from 'vue-router' import { useRoute } from 'vue-router'
import { BasicTableProps, useTable } from '/@/hooks/table' import { BasicTableProps, useTable } from '/@/hooks/table'
import { fetchList } from '/@/api/stuwork/stugraducheck' import { fetchList, confirmScore, confirmConduct, confirmPunish, confirmSkill } from '/@/api/stuwork/stugraducheck'
import { getDeptList } from '/@/api/basic/basicclass' import { getDeptList } from '/@/api/basic/basicclass'
import TableColumnControl from '/@/components/TableColumnControl/index.vue' import TableColumnControl from '/@/components/TableColumnControl/index.vue'
import { import {
@@ -198,9 +257,11 @@ import {
Document, Document,
Menu, Menu,
Search, Search,
Grid Grid,
DataAnalysis
} from '@element-plus/icons-vue' } from '@element-plus/icons-vue'
import { useTableColumnControl } from '/@/hooks/tableColumn' import { useTableColumnControl } from '/@/hooks/tableColumn'
import { useMessage } from '/@/hooks/message'
const route = useRoute() const route = useRoute()
const searchFormRef = ref() const searchFormRef = ref()
@@ -208,6 +269,26 @@ const columnControlRef = ref<any>()
const showSearch = ref(true) const showSearch = ref(true)
const deptList = ref<any[]>([]) const deptList = ref<any[]>([])
// 确认对话框相关变量
const confirmDialogVisible = ref(false)
const confirmLoading = ref(false)
const confirmResult = ref<any>({
confirmType: '',
totalCount: 0,
qualifiedCount: 0,
unqualifiedCount: 0,
unqualifiedList: []
})
const confirmTypeLabel = computed(() => {
const map: Record<string, string> = {
score: '学分确认',
conduct: '操行确认',
punish: '违纪确认',
skill: '等级工确认'
}
return map[confirmResult.value.confirmType] || '确认结果'
})
// 毕业年份:当前年前后各 5 年 // 毕业年份:当前年前后各 5 年
const graduYearOptions = computed(() => { const graduYearOptions = computed(() => {
const y = new Date().getFullYear() const y = new Date().getFullYear()
@@ -299,6 +380,86 @@ const loadDeptList = async () => {
} }
} }
// 学分确认
const handleConfirmScore = async () => {
if (!searchForm.graduYear) {
useMessage().warning('请先选择毕业年份')
return
}
confirmLoading.value = true
confirmDialogVisible.value = true
try {
const res = await confirmScore(searchForm.graduYear)
if (res.data) {
confirmResult.value = res.data
}
} catch (err: any) {
useMessage().error(err.msg || '学分确认失败')
} finally {
confirmLoading.value = false
}
}
// 操行确认
const handleConfirmConduct = async () => {
if (!searchForm.graduYear) {
useMessage().warning('请先选择毕业年份')
return
}
confirmLoading.value = true
confirmDialogVisible.value = true
try {
const res = await confirmConduct(searchForm.graduYear)
if (res.data) {
confirmResult.value = res.data
}
} catch (err: any) {
useMessage().error(err.msg || '操行确认失败')
} finally {
confirmLoading.value = false
}
}
// 违纪确认
const handleConfirmPunish = async () => {
if (!searchForm.graduYear) {
useMessage().warning('请先选择毕业年份')
return
}
confirmLoading.value = true
confirmDialogVisible.value = true
try {
const res = await confirmPunish(searchForm.graduYear)
if (res.data) {
confirmResult.value = res.data
}
} catch (err: any) {
useMessage().error(err.msg || '违纪确认失败')
} finally {
confirmLoading.value = false
}
}
// 等级工确认
const handleConfirmSkill = async () => {
if (!searchForm.graduYear) {
useMessage().warning('请先选择毕业年份')
return
}
confirmLoading.value = true
confirmDialogVisible.value = true
try {
const res = await confirmSkill(searchForm.graduYear)
if (res.data) {
confirmResult.value = res.data
}
} catch (err: any) {
useMessage().error(err.msg || '等级工确认失败')
} finally {
confirmLoading.value = false
}
}
onMounted(() => { onMounted(() => {
loadDeptList() loadDeptList()
}) })

View File

@@ -78,6 +78,12 @@
学生团组织列表 学生团组织列表
</span> </span>
<div class="header-actions"> <div class="header-actions">
<el-button
icon="DataAnalysis"
type="info"
@click="handleStatistics">
团员统计
</el-button>
<el-button <el-button
icon="Plus" icon="Plus"
type="primary" type="primary"
@@ -235,6 +241,47 @@
</span> </span>
</template> </template>
</el-dialog> </el-dialog>
<!-- 统计对话框 -->
<el-dialog
v-model="statisticsDialogVisible"
title="团员统计"
width="700px"
destroy-on-close>
<!-- 全校总数 -->
<div class="statistics-total">
<el-statistic title="全校团员总数" :value="statisticsData.totalCount || 0">
<template #suffix>
<span style="font-size: 14px; color: #909399;"></span>
</template>
</el-statistic>
</div>
<el-divider content-position="left">各学院团员统计</el-divider>
<!-- 各学院统计表格 -->
<el-table
:data="statisticsData.deptList || []"
v-loading="statisticsLoading"
stripe
max-height="400px"
:cell-style="tableStyle.cellStyle"
:header-cell-style="tableStyle.headerCellStyle">
<el-table-column type="index" label="序号" width="70" align="center" />
<el-table-column prop="deptName" label="学院名称" align="center" min-width="150" />
<el-table-column prop="count" label="团员人数" align="center" width="120">
<template #default="{ row }">
<el-tag size="small" type="success" effect="plain" round>
{{ row.count }}
</el-tag>
</template>
</el-table-column>
</el-table>
<template #footer>
<el-button @click="statisticsDialogVisible = false">关闭</el-button>
</template>
</el-dialog>
</div> </div>
</template> </template>
@@ -242,13 +289,13 @@
import { reactive, ref, onMounted, computed, nextTick } from 'vue' import { reactive, ref, onMounted, computed, nextTick } from 'vue'
import { useRoute } from 'vue-router' import { useRoute } from 'vue-router'
import { BasicTableProps, useTable } from "/@/hooks/table"; import { BasicTableProps, useTable } from "/@/hooks/table";
import { fetchList, delObj, importExcel, exportExcel } from "/@/api/stuwork/stuunionleague"; import { fetchList, delObj, importExcel, exportExcel, getStatistics } from "/@/api/stuwork/stuunionleague";
import { getClassListByRole } from "/@/api/basic/basicclass"; import { getClassListByRole } from "/@/api/basic/basicclass";
import { getGradeList } from "/@/api/basic/basicclass"; import { getGradeList } from "/@/api/basic/basicclass";
import { useMessage, useMessageBox } from "/@/hooks/message"; import { useMessage, useMessageBox } from "/@/hooks/message";
import { parseTime } from "/@/utils/formatTime"; import { parseTime } from "/@/utils/formatTime";
import TableColumnControl from '/@/components/TableColumnControl/index.vue' 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 { UploadFilled, List, OfficeBuilding, Grid, CreditCard, Avatar, Phone, Calendar, Postcard, Briefcase, Setting, Menu, Search, Document, DataAnalysis } from '@element-plus/icons-vue'
import { useTableColumnControl } from '/@/hooks/tableColumn' import { useTableColumnControl } from '/@/hooks/tableColumn'
import type { UploadFile, UploadFiles } from 'element-plus'; import type { UploadFile, UploadFiles } from 'element-plus';
@@ -267,6 +314,14 @@ const importDialogVisible = ref(false)
const importLoading = ref(false) const importLoading = ref(false)
const fileList = ref<UploadFile[]>([]) const fileList = ref<UploadFile[]>([])
// 统计相关变量
const statisticsDialogVisible = ref(false)
const statisticsLoading = ref(false)
const statisticsData = ref<any>({
totalCount: 0,
deptList: []
})
// 表格列配置 // 表格列配置
const tableColumns = [ const tableColumns = [
{ prop: 'deptName', label: '学院名称', minWidth: 150 }, { prop: 'deptName', label: '学院名称', minWidth: 150 },
@@ -452,6 +507,22 @@ const getGradeListData = async () => {
} }
} }
// 打开统计对话框
const handleStatistics = async () => {
statisticsDialogVisible.value = true
statisticsLoading.value = true
try {
const res = await getStatistics()
if (res.data) {
statisticsData.value = res.data
}
} catch (err: any) {
useMessage().error(err.msg || '获取统计数据失败')
} finally {
statisticsLoading.value = false
}
}
// ??? // ???
onMounted(() => { onMounted(() => {
getClassListData() getClassListData()