Files
school-developer/src/views/stuwork/stuconduct/indexTerm.vue
2026-03-11 17:51:17 +08:00

465 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="queryForm" ref="searchFormRef" :inline="true" @keyup.enter="getDataList" class="search-form">
<el-form-item label="学年" prop="schoolYear">
<el-select
v-model="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="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="classCode">
<el-select
v-model="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>
<el-button type="primary" plain icon="Search" @click="getDataList">查询</el-button>
<el-button icon="Refresh" @click="handleReset">重置</el-button>
<el-button type="warning" icon="Bell" @click="handleSendWarning" :loading="warningLoading">发送预警</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>
</template>
<!-- 统计表格 -->
<el-table
:data="statisticsData"
v-loading="loading"
stripe
style="width: 100%; margin-bottom: 20px"
:cell-style="tableStyle.cellStyle"
:header-cell-style="tableStyle.headerCellStyle"
>
<el-table-column prop="label" label="" width="120" align="center" fixed="left" />
<el-table-column prop="classNo" label="班级" min-width="150" align="center" />
<el-table-column prop="excellent" label="优秀" min-width="100" align="center" />
<el-table-column prop="good" label="良好" min-width="100" align="center" />
<el-table-column prop="pass" label="及格" min-width="100" align="center" />
<el-table-column prop="fail" label="不及格" min-width="100" align="center" />
</el-table>
<!-- 学生列表表格 -->
<el-table
:data="studentList"
v-loading="loading"
stripe
style="width: 100%"
: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="学号" min-width="120" show-overflow-tooltip align="center" />
<el-table-column prop="realName" label="姓名" min-width="100" show-overflow-tooltip align="center" />
<!-- 各月份分数 -->
<el-table-column v-for="(month, index) in monthColumns" :key="index" :label="month.label" min-width="70" align="center">
<template #default="scope">
<span>{{ formatScore(scope.row[month.prop]) }}</span>
</template>
</el-table-column>
<!-- 学期总评 -->
<el-table-column prop="scoreOneTerm" label="学期总评" min-width="100" align="center">
<template #default="scope">
<el-tag :type="getScoreType(scope.row.scoreOneTerm)" size="small" effect="plain">
{{ formatScore(scope.row.scoreOneTerm) }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" width="100" align="center" fixed="right">
<template #default="scope">
<el-button icon="View" link type="primary" @click="handleView(scope.row)">查看</el-button>
</template>
</el-table-column>
</el-table>
</el-card>
</div>
<!-- 查看详情弹窗 -->
<el-dialog v-model="viewDialogVisible" title="学期操行考核详情" width="900px" destroy-on-close @close="viewDetailList = []">
<div v-if="viewRow" class="view-summary">
<el-descriptions :column="3" border size="small">
<el-descriptions-item label="学号">{{ viewRow.stuNo }}</el-descriptions-item>
<el-descriptions-item label="姓名">{{ viewRow.realName }}</el-descriptions-item>
<el-descriptions-item label="学期总评">
<el-tag :type="getScoreType(viewRow.scoreOneTerm)" size="small">
{{ formatScore(viewRow.scoreOneTerm) }}
</el-tag>
</el-descriptions-item>
</el-descriptions>
</div>
<div class="view-detail-title">考核记录</div>
<el-table
:data="viewDetailList"
v-loading="viewLoading"
stripe
size="small"
max-height="400"
:cell-style="tableStyle.cellStyle"
:header-cell-style="tableStyle.headerCellStyle">
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="recordDate" label="考核日期" width="120" align="center" show-overflow-tooltip />
<el-table-column prop="conductType" label="类型" width="80" align="center">
<template #default="scope">
<el-tag :type="scope.row.conductType === '1' ? 'success' : 'danger'" size="small">
{{ scope.row.conductType === '1' ? '加分' : scope.row.conductType === '0' ? '扣分' : scope.row.conductType || '-' }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="score" label="分数" width="80" align="center">
<template #default="scope">
{{ scope.row.score != null && scope.row.score !== undefined ? Number(scope.row.score) : '-' }}
</template>
</el-table-column>
<el-table-column prop="description" label="情况记录" min-width="150" show-overflow-tooltip />
<el-table-column prop="remarks" label="备注" min-width="120" show-overflow-tooltip />
</el-table>
<template v-if="viewDetailList.length === 0 && !viewLoading">
<el-empty description="暂无考核记录" :image-size="80" />
</template>
</el-dialog>
</div>
</template>
<script setup lang="ts" name="StuConductTerm">
import { reactive, ref, onMounted, computed } from 'vue'
import { getStuConductTerm, sendConductWarning, queryDataByStuNo } from "/@/api/stuwork/stuconduct";
import { getClassListByRole } from "/@/api/basic/basicclass";
import { queryAllSchoolYear } from "/@/api/basic/basicyear";
import { getDicts } from "/@/api/admin/dict";
import { useMessage, useMessageBox } from "/@/hooks/message";
import { Search, Document } from '@element-plus/icons-vue';
// 表格样式
const tableStyle = {
cellStyle: { padding: '8px 0', textAlign: 'center' },
headerCellStyle: { background: '#f5f7fa', color: '#606266', fontWeight: 'bold', textAlign: 'center' },
};
// 定义变量内容
const searchFormRef = ref()
const showSearch = ref(true)
const loading = ref(false)
const warningLoading = ref(false)
const schoolYearList = ref<any[]>([])
const schoolTermList = ref<any[]>([])
const classList = ref<any[]>([])
const studentList = ref<any[]>([])
const viewDialogVisible = ref(false)
const viewLoading = ref(false)
const viewRow = ref<any>(null)
const viewDetailList = ref<any[]>([])
// 查询表单
const queryForm = reactive({
schoolYear: '',
schoolTerm: '',
classCode: '',
});
// 根据学期动态生成月份列
const monthColumns = computed(() => {
// 第一学期9月、10月、11月、12月、1月
// 第二学期2月、3月、4月、5月、6月
if (queryForm.schoolTerm === '1') {
return [
{ label: '9月', prop: 'scoreOneMonth' },
{ label: '10月', prop: 'scoreTwoMonth' },
{ label: '11月', prop: 'scoreThreeMonth' },
{ label: '12月', prop: 'scoreFourMonth' },
{ label: '1月', prop: 'scoreFiveMonth' },
];
} else if (queryForm.schoolTerm === '2') {
return [
{ label: '2月', prop: 'scoreSixMonth' },
{ label: '3月', prop: 'scoreSevenMonth' },
{ label: '4月', prop: 'scoreEightMonth' },
{ label: '5月', prop: 'scoreNineMonth' },
{ label: '6月', prop: 'scoreTenMonth' },
];
}
// 默认返回第一学期
return [
{ label: '9月', prop: 'scoreOneMonth' },
{ label: '10月', prop: 'scoreTwoMonth' },
{ label: '11月', prop: 'scoreThreeMonth' },
{ label: '12月', prop: 'scoreFourMonth' },
{ label: '1月', prop: 'scoreFiveMonth' },
];
});
// 格式化分数
const formatScore = (score: any) => {
if (score === null || score === undefined || score === '') return '-';
return Number(score).toFixed(2);
};
// 根据分数获取标签类型
const getScoreType = (score: any) => {
if (score === null || score === undefined) return 'info';
const num = Number(score);
if (num >= 90) return 'success';
if (num >= 80) return 'primary';
if (num >= 60) return 'warning';
return 'danger';
};
// 统计表格数据
const statisticsData = computed(() => {
if (studentList.value.length === 0) {
return [];
}
let excellent = 0;
let good = 0;
let pass = 0;
let fail = 0;
const total = studentList.value.length;
studentList.value.forEach((student: any) => {
const score = student.scoreOneTerm;
if (score !== null && score !== undefined) {
if (Number(score) >= 90) {
excellent++;
} else if (Number(score) >= 80) {
good++;
} else if (Number(score) >= 60) {
pass++;
} else {
fail++;
}
}
});
const excellentRate = total > 0 ? ((excellent / total) * 100).toFixed(2) + '%' : '0%';
const goodRate = total > 0 ? ((good / total) * 100).toFixed(2) + '%' : '0%';
const passRate = total > 0 ? ((pass / total) * 100).toFixed(2) + '%' : '0%';
const failRate = total > 0 ? ((fail / total) * 100).toFixed(2) + '%' : '0%';
const excellentGoodRate = total > 0 ? (((excellent + good) / total) * 100).toFixed(2) + '%' : '0%';
const classNo = studentList.value.length > 0 ? studentList.value[0].classNo || '-' : '-';
return [
{ label: '人数', classNo, excellent, good, pass, fail },
{ label: '比率', classNo, excellent: excellentRate, good: goodRate, pass: passRate, fail: failRate },
{ label: '优良率', classNo, excellent: excellentGoodRate, good: '-', pass: '-', fail: '-' },
{ label: '备注', classNo, excellent: '-', good: '-', pass: '-', fail: '-' },
];
});
// 获取数据列表
const getDataList = async () => {
if (!queryForm.schoolYear || !queryForm.schoolTerm || !queryForm.classCode) {
useMessage().warning('请选择学年、学期和班级');
return;
}
loading.value = true;
try {
const res = await getStuConductTerm({
schoolYear: queryForm.schoolYear,
schoolTerm: queryForm.schoolTerm,
classCode: queryForm.classCode,
});
if (res.data && Array.isArray(res.data)) {
// 从返回数据中提取学生列表
const tempList: any[] = [];
res.data.forEach((item: any) => {
if (item.basicStudentVOList && Array.isArray(item.basicStudentVOList)) {
item.basicStudentVOList.forEach((student: any) => {
tempList.push({
...student,
classNo: item.classNo,
classCode: item.classCode,
});
});
}
});
studentList.value = tempList;
} else {
studentList.value = [];
}
} catch (_err) {
studentList.value = [];
} finally {
loading.value = false;
}
};
// 格式化学期显示
const formatSchoolTerm = (value: string | number) => {
if (value === null || value === undefined || value === '') return '-';
const dictItem = schoolTermList.value.find((item: any) => item.value == value);
return dictItem ? dictItem.label : value;
};
// 重置
const handleReset = () => {
searchFormRef.value?.resetFields();
queryForm.schoolYear = '';
queryForm.schoolTerm = '';
queryForm.classCode = '';
studentList.value = [];
};
// 查看详情
const handleView = async (row: any) => {
if (!queryForm.schoolYear || !row.stuNo) {
useMessage().warning('缺少学年或学号');
return;
}
viewRow.value = row;
viewDialogVisible.value = true;
viewDetailList.value = [];
viewLoading.value = true;
try {
const res = await queryDataByStuNo({
schoolYear: queryForm.schoolYear,
stuNo: row.stuNo,
});
const list = Array.isArray(res.data) ? res.data : [];
// 筛选当前学期的记录
const term = queryForm.schoolTerm;
viewDetailList.value = term ? list.filter((r: any) => String(r.schoolTerm) === String(term)) : list;
} catch (_err) {
viewDetailList.value = [];
} finally {
viewLoading.value = false;
}
};
// 发送学期操行考核预警
const handleSendWarning = async () => {
if (!queryForm.schoolYear || !queryForm.schoolTerm) {
useMessage().warning('请先选择学年 and 学期');
return;
}
const { confirm } = useMessageBox();
try {
await confirm(
`确定要发送${queryForm.schoolYear}学年第${queryForm.schoolTerm === '1' ? '一' : '二'}学期操行考核预警吗将向班主任推送不及格学生低于60分的预警通知。`
);
warningLoading.value = true;
const res = await sendConductWarning(queryForm.schoolYear, queryForm.schoolTerm);
useMessage().success(res.msg || '预警通知发送成功');
} catch (err: any) {
if (err !== 'cancel') {
useMessage().error(err.msg || '预警通知发送失败');
}
} finally {
warningLoading.value = false;
}
};
// 获取学年列表
const getSchoolYearList = async () => {
try {
const res = await queryAllSchoolYear();
schoolYearList.value = res.data && Array.isArray(res.data) ? res.data : [];
} 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 = [];
}
};
// 获取班级列表
const getClassListData = async () => {
try {
const res = await getClassListByRole();
classList.value = res.data && Array.isArray(res.data) ? res.data : [];
} catch (err) {
classList.value = [];
}
};
// 初始化
onMounted(() => {
getSchoolYearList();
getSchoolTermDict();
getClassListData();
});
</script>
<style scoped lang="scss">
@import '/@/assets/styles/modern-page.scss';
.view-summary {
margin-bottom: 16px;
}
.view-detail-title {
margin-bottom: 8px;
font-weight: 600;
color: #303133;
}
</style>