班主任考核学期统计

This commit is contained in:
yaojian
2026-03-05 15:37:17 +08:00
parent 700c2e6a83
commit f63ce726c1
3 changed files with 614 additions and 0 deletions

View File

@@ -0,0 +1,499 @@
<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="searchForm" ref="searchFormRef" :inline="true" @keyup.enter="handleSearch" class="search-form">
<el-form-item label="学年" prop="schoolYear">
<el-select
v-model="searchForm.schoolYear"
placeholder="请选择学年"
clearable
filterable
style="width: 150px">
<el-option
v-for="year in schoolYearList"
:key="year"
:label="year"
:value="year" />
</el-select>
</el-form-item>
<el-form-item label="学期" prop="schoolTerm">
<el-select
v-model="searchForm.schoolTerm"
placeholder="请选择学期"
clearable
style="width: 120px">
<el-option label="第一学期" value="1" />
<el-option label="第二学期" value="2" />
</el-select>
</el-form-item>
<el-form-item label="班主任" prop="classMasterName">
<el-input
v-model="searchForm.classMasterName"
placeholder="请输入班主任姓名"
clearable
style="width: 150px" />
</el-form-item>
<el-form-item label="部门" prop="deptCode">
<el-input
v-model="searchForm.deptCode"
placeholder="请输入部门编码"
clearable
style="width: 150px" />
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleSearch">查询</el-button>
<el-button icon="Reset" @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"><DataAnalysis /></el-icon>
班主任考核学期汇总
</span>
<div class="header-actions">
<el-button type="primary" icon="Refresh" @click="handleGenerate">生成汇总</el-button>
<el-button type="warning" icon="Sort" @click="handleRecalculate">重新排名</el-button>
<el-button type="success" icon="Download" @click="handleExport">导出</el-button>
<right-toolbar
v-model:showSearch="showSearch"
class="ml10"
@queryTable="getDataList" />
</div>
</div>
</template>
<!-- 待处理申诉提醒 -->
<el-alert
v-if="pendingAppealCount > 0"
:title="`当前有 ${pendingAppealCount} 条待处理申诉`"
type="warning"
show-icon
class="pending-alert"
@click="showPendingAppeals" />
<!-- 表格 -->
<el-table
:data="dataList"
v-loading="loading"
stripe
:cell-style="tableStyle.cellStyle"
:header-cell-style="tableStyle.headerCellStyle"
class="modern-table">
<el-table-column type="index" label="排名" width="80" align="center">
<template #default="{ row }">
<el-tag v-if="row.ranking <= 3" :type="getRankTagType(row.ranking)" effect="dark">
{{ row.ranking }}
</el-tag>
<span v-else>{{ row.ranking }}</span>
</template>
</el-table-column>
<el-table-column prop="schoolYear" label="学年" width="100" align="center" />
<el-table-column prop="schoolTerm" label="学期" width="100" align="center">
<template #default="{ row }">
{{ row.schoolTerm === '1' ? '第一学期' : '第二学期' }}
</template>
</el-table-column>
<el-table-column prop="classMasterName" label="班主任" width="120" align="center">
<template #default="{ row }">
<div>
<div>{{ row.classMasterName }}</div>
<div class="text-xs text-gray-500">{{ row.classMasterCode }}</div>
</div>
</template>
</el-table-column>
<el-table-column prop="deptName" label="部门" min-width="150" />
<el-table-column prop="totalScore" label="总得分" width="120" align="center">
<template #default="{ row }">
<span :class="getScoreClass(row.totalScore)">
{{ row.totalScore }}
</span>
</template>
</el-table-column>
<el-table-column prop="appealCount" label="申诉统计" width="120" align="center">
<template #default="{ row }">
<div>
<span>: {{ row.appealCount || 0 }} </span>
<div v-if="row.pendingAppealCount > 0" class="text-warning">
待处理: {{ row.pendingAppealCount }}
</div>
</div>
</template>
</el-table-column>
<el-table-column prop="status" label="状态" width="100" align="center">
<template #default="{ row }">
<el-tag :type="row.status === '1' ? 'success' : 'info'">
{{ row.status === '1' ? '已完成' : '未完成' }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="summaryTime" label="汇总时间" width="160" align="center">
<template #default="{ row }">
{{ formatDateTime(row.summaryTime) }}
</template>
</el-table-column>
<el-table-column label="操作" width="150" align="center" fixed="right">
<template #default="{ row }">
<el-button icon="View" link type="primary" @click="handleView(row)">详情</el-button>
<el-button
v-if="row.pendingAppealCount > 0"
icon="Bell"
link
type="warning"
@click="handleAppeals(row)">
申诉
</el-button>
</template>
</el-table-column>
<template #empty>
<el-empty description="暂无数据" :image-size="120" />
</template>
</el-table>
<!-- 分页 -->
<el-pagination
v-model:current-page="page.currentPage"
v-model:page-size="page.pageSize"
:page-sizes="[10, 20, 50, 100]"
:total="page.total"
layout="total, sizes, prev, pager, next, jumper"
class="pagination"
@size-change="handleSizeChange"
@current-change="handleCurrentChange" />
</el-card>
</div>
<!-- 生成汇总弹窗 -->
<el-dialog
v-model="generateDialogVisible"
title="生成学期汇总"
width="400px"
:close-on-click-modal="false">
<el-form :model="generateForm" ref="generateFormRef" :rules="generateRules" label-width="80px">
<el-form-item label="学年" prop="schoolYear">
<el-select
v-model="generateForm.schoolYear"
placeholder="请选择学年"
filterable
style="width: 100%">
<el-option
v-for="year in schoolYearList"
:key="year"
:label="year"
:value="year" />
</el-select>
</el-form-item>
<el-form-item label="学期" prop="schoolTerm">
<el-select v-model="generateForm.schoolTerm" placeholder="请选择学期" style="width: 100%">
<el-option label="第一学期" value="1" />
<el-option label="第二学期" value="2" />
</el-select>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="generateDialogVisible = false">取消</el-button>
<el-button type="primary" @click="submitGenerate" :loading="generateLoading">确定</el-button>
</template>
</el-dialog>
<!-- 详情弹窗 -->
<el-dialog
v-model="detailDialogVisible"
title="班主任考核详情"
width="700px"
:close-on-click-modal="false">
<el-descriptions :column="2" border>
<el-descriptions-item label="学年">{{ detailData.schoolYear }}</el-descriptions-item>
<el-descriptions-item label="学期">{{ detailData.schoolTerm === '1' ? '第一学期' : '第二学期' }}</el-descriptions-item>
<el-descriptions-item label="班主任">{{ detailData.classMasterName }}</el-descriptions-item>
<el-descriptions-item label="工号">{{ detailData.classMasterCode }}</el-descriptions-item>
<el-descriptions-item label="部门">{{ detailData.deptName }}</el-descriptions-item>
<el-descriptions-item label="班级">{{ detailData.className }}</el-descriptions-item>
<el-descriptions-item label="总得分">
<span :class="getScoreClass(detailData.totalScore)">{{ detailData.totalScore }}</span>
</el-descriptions-item>
<el-descriptions-item label="排名">
<el-tag v-if="detailData.ranking <= 3" :type="getRankTagType(detailData.ranking)" effect="dark">
{{ detailData.ranking }}
</el-tag>
<span v-else> {{ detailData.ranking }} </span>
</el-descriptions-item>
<el-descriptions-item label="申诉次数">{{ detailData.appealCount || 0 }} </el-descriptions-item>
<el-descriptions-item label="待处理申诉">
<el-tag v-if="detailData.pendingAppealCount > 0" type="warning">{{ detailData.pendingAppealCount }} </el-tag>
<span v-else>0 </span>
</el-descriptions-item>
<el-descriptions-item label="汇总时间" :span="2">{{ formatDateTime(detailData.summaryTime) }}</el-descriptions-item>
</el-descriptions>
</el-dialog>
</div>
</template>
<script setup lang="ts" name="ClassMasterEvaluationSummary">
import { reactive, ref, onMounted, computed } from 'vue'
import { fetchList, generateSummary, recalculateRanking, exportSummary } from '/@/api/stuwork/classmasterevaluationsummary'
import {useMessage, useMessageBox} from '/@/hooks/message'
import { Search, DataAnalysis, Refresh, Sort, Download, View, Bell } from '@element-plus/icons-vue'
// 定义变量内容
const searchFormRef = ref()
const generateFormRef = ref()
const showSearch = ref(true)
const loading = ref(false)
const generateLoading = ref(false)
const dataList = ref<any[]>([])
const generateDialogVisible = ref(false)
const detailDialogVisible = ref(false)
const detailData = ref<any>({})
const pendingAppealCount = ref(0)
// 分页
const page = reactive({
currentPage: 1,
pageSize: 20,
total: 0
})
// 搜索表单
const searchForm = reactive({
schoolYear: '',
schoolTerm: '',
classMasterName: '',
deptCode: ''
})
// 生成汇总表单
const generateForm = reactive({
schoolYear: '',
schoolTerm: ''
})
// 生成汇总表单校验规则
const generateRules = {
schoolYear: [{ required: true, message: '请选择学年', trigger: 'change' }],
schoolTerm: [{ required: true, message: '请选择学期', trigger: 'change' }]
}
// 表格样式
const tableStyle = {
cellStyle: { textAlign: 'center' },
headerCellStyle: {
textAlign: 'center',
background: 'var(--el-table-row-hover-bg-color)',
color: 'var(--el-text-color-primary)'
}
}
// 学年列表
const schoolYearList = computed(() => {
const currentYear = new Date().getFullYear()
const list = []
for (let i = 0; i < 5; i++) {
list.push(`${currentYear - i}-${currentYear - i + 1}`)
}
return list
})
// 格式化日期时间
const formatDateTime = (dateTime: string) => {
if (!dateTime) return '-'
return dateTime.replace('T', ' ').substring(0, 19)
}
// 获取排名标签类型
const getRankTagType = (ranking: number) => {
if (ranking === 1) return 'danger'
if (ranking === 2) return 'warning'
if (ranking === 3) return 'success'
return 'info'
}
// 获取分数样式
const getScoreClass = (score: number) => {
if (score >= 90) return 'text-success font-bold'
if (score >= 80) return 'text-primary'
if (score >= 60) return 'text-warning'
return 'text-danger'
}
// 查询
const handleSearch = () => {
page.currentPage = 1
getDataList()
}
// 重置
const handleReset = () => {
searchFormRef.value?.resetFields()
getDataList()
}
// 分页
const handleSizeChange = (val: number) => {
page.pageSize = val
getDataList()
}
const handleCurrentChange = (val: number) => {
page.currentPage = val
getDataList()
}
// 获取数据列表
const getDataList = async () => {
loading.value = true
try {
const res = await fetchList({
current: page.currentPage,
size: page.pageSize,
...searchForm
})
if (res.data && res.data.records) {
dataList.value = res.data.records
page.total = res.data.total
// 计算待处理申诉总数
pendingAppealCount.value = dataList.value.reduce((sum, item) => sum + (item.pendingAppealCount || 0), 0)
} else {
dataList.value = []
page.total = 0
pendingAppealCount.value = 0
}
} catch (err: any) {
useMessage().error(err.msg || '获取数据失败')
} finally {
loading.value = false
}
}
// 生成汇总
const handleGenerate = () => {
generateForm.schoolYear = ''
generateForm.schoolTerm = ''
generateDialogVisible.value = true
}
// 提交生成汇总
const submitGenerate = async () => {
try {
await generateFormRef.value?.validate()
generateLoading.value = true
await generateSummary(generateForm)
useMessage().success('汇总生成成功')
generateDialogVisible.value = false
getDataList()
} catch (err: any) {
if (err !== false) {
useMessage().error(err.msg || '生成失败')
}
} finally {
generateLoading.value = false
}
}
// 重新计算排名
const handleRecalculate = async () => {
if (!searchForm.schoolYear || !searchForm.schoolTerm) {
useMessage().warning('请先选择学年学期')
return
}
try {
await useMessageBox().confirm('确定要重新计算排名吗?')
await recalculateRanking({
schoolYear: searchForm.schoolYear,
schoolTerm: searchForm.schoolTerm
})
useMessage().success('排名重新计算成功')
getDataList()
} catch (err: any) {
if (err !== 'cancel') {
useMessage().error(err.msg || '操作失败')
}
}
}
// 导出
const handleExport = async () => {
try {
const res = await exportSummary(searchForm)
// TODO: 实现导出逻辑
useMessage().success('导出成功')
} catch (err: any) {
useMessage().error(err.msg || '导出失败')
}
}
// 查看详情
const handleView = (row: any) => {
detailData.value = row
detailDialogVisible.value = true
}
// 查看申诉
const handleAppeals = (row: any) => {
// TODO: 跳转到申诉列表页面
useMessage().info('跳转到申诉列表')
}
// 显示待处理申诉
const showPendingAppeals = () => {
// TODO: 显示待处理申诉弹窗
useMessage().info('显示待处理申诉列表')
}
// 初始化
onMounted(() => {
getDataList()
})
</script>
<style scoped lang="scss">
@import '/@/assets/styles/modern-page.scss';
.pending-alert {
margin-bottom: 16px;
cursor: pointer;
}
.text-warning {
color: var(--el-color-warning);
font-size: 12px;
}
.text-success {
color: var(--el-color-success);
}
.text-primary {
color: var(--el-color-primary);
}
.text-danger {
color: var(--el-color-danger);
}
.font-bold {
font-weight: bold;
}
.text-xs {
font-size: 12px;
}
.text-gray-500 {
color: #999;
}
</style>