Files
school-developer/src/views/stuwork/stuconduct/indexTerm.vue
2026-03-09 12:03:19 +08:00

480 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="layout-padding">
<div class="layout-padding-auto layout-padding-view">
<!-- 搜索表单 -->
<el-row v-show="showSearch">
<el-form :model="queryForm" ref="searchFormRef" :inline="true" @keyup.enter="getDataList">
<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-row>
<!-- 统计表格 -->
<el-row style="margin-bottom: 20px">
<el-table
:data="statisticsData"
v-loading="loading"
border
style="width: 100%"
: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-row>
<!-- 学生列表表格 -->
<el-row>
<el-table
:data="studentList"
v-loading="loading"
border
:max-height="600"
:cell-style="tableStyle.cellStyle"
:header-cell-style="tableStyle.headerCellStyle"
>
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="stuNo" label="学号" show-overflow-tooltip align="center" />
<el-table-column prop="realName" label="姓名" show-overflow-tooltip align="center" />
<el-table-column prop="score" label="学期总评" show-overflow-tooltip align="center">
<template #default="scope">
<span>{{ scope.row.score !== null && scope.row.score !== undefined ? scope.row.score.toFixed(2) : '-' }}</span>
</template>
</el-table-column>
<el-table-column label="操作" width="150" align="center" fixed="right">
<template #default="scope">
<el-button icon="View" text type="primary" @click="handleView(scope.row)"> 查看 </el-button>
</template>
</el-table-column>
</el-table>
</el-row>
</div>
<!-- 查看详情弹窗接口queryDataByStuNo 通过学年学号查看详情按当前学期筛选 -->
<el-dialog
v-model="viewDialogVisible"
title="学期操行考核详情"
width="800px"
destroy-on-close
@close="viewDetailList = []">
<div v-if="viewRow" class="view-summary">
<el-descriptions :column="2" 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="学年">{{ queryForm.schoolYear }}</el-descriptions-item>
<el-descriptions-item label="学期">{{ formatSchoolTerm(queryForm.schoolTerm) }}</el-descriptions-item>
<el-descriptions-item label="学期总评" :span="2">
{{ viewRow.score != null && viewRow.score !== undefined ? Number(viewRow.score).toFixed(2) : '-' }}
</el-descriptions-item>
</el-descriptions>
</div>
<div class="view-detail-title">考核记录</div>
<el-table
:data="viewDetailList"
v-loading="viewLoading"
border
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="schoolTerm" label="学期" width="80" align="center" show-overflow-tooltip />
<el-table-column prop="recordDate" label="考核日期" width="110" 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="140" show-overflow-tooltip />
<el-table-column prop="remarks" label="备注" min-width="100" 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, queryDataByStuNo, sendConductWarning } 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";
// 表格样式 - 在组件内部定义,不从外部导入
const tableStyle = {
cellStyle: { padding: '8px 0' },
headerCellStyle: { background: '#f5f7fa', color: '#606266', fontWeight: 'bold' },
};
// 定义变量内容
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 statisticsData = computed(() => {
if (studentList.value.length === 0) {
return [];
}
// 计算各等级人数
// 优秀:>=90良好80-89及格60-79不及格<60
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.score;
if (score !== null && score !== undefined) {
if (score >= 90) {
excellent++;
} else if (score >= 80) {
good++;
} else if (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 excellentGoodCount = excellent + good;
const excellentGoodRate = total > 0 ? ((excellentGoodCount / total) * 100).toFixed(2) + '%' : '0%';
// 获取班级名称
const classNo = studentList.value.length > 0 ? studentList.value[0].classNo || '-' : '-';
return [
{
label: '人数',
classNo: classNo,
excellent: excellent,
good: good,
pass: pass,
fail: fail,
},
{
label: '比率',
classNo: classNo,
excellent: excellentRate,
good: goodRate,
pass: passRate,
fail: failRate,
},
{
label: '优良率',
classNo: classNo,
excellent: excellentGoodRate,
good: '-',
pass: '-',
fail: '-',
},
{
label: '备注',
classNo: 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)) {
// 处理返回的数据,提取学生列表
// 根据API文档返回的是StuConductTermVO数组
const tempList: any[] = [];
res.data.forEach((item: any) => {
// 如果返回的数据结构中有basicStudentVOList需要展开
if (item.basicStudentVOList && Array.isArray(item.basicStudentVOList) && item.basicStudentVOList.length > 0) {
item.basicStudentVOList.forEach((student: any) => {
tempList.push({
stuNo: student.stuNo || item.stuNo,
realName: student.realName || item.realName,
score: item.score, // 学期总评分数
classNo: item.classNo,
classCode: item.classCode,
});
});
} else {
// 直接使用item作为学生信息
tempList.push({
stuNo: item.stuNo,
realName: item.realName,
score: item.score,
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 = [];
};
// 查看详情接口GET /stuwork/stuconduct/queryDataByStuNo按当前学年+学号拉取后筛本学期记录)
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();
if (res.data && Array.isArray(res.data)) {
schoolYearList.value = res.data;
} else {
schoolYearList.value = [];
}
} 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();
if (res.data && Array.isArray(res.data)) {
classList.value = res.data;
} else {
classList.value = [];
}
} catch (err) {
classList.value = [];
}
};
// 初始化
onMounted(() => {
getSchoolYearList();
getSchoolTermDict();
getClassListData();
});
</script>
<style scoped lang="scss">
.layout-padding {
.layout-padding-auto {
.layout-padding-view {
.el-row {
margin-bottom: 20px;
}
}
}
}
.view-summary {
margin-bottom: 16px;
}
.view-detail-title {
margin-bottom: 8px;
font-weight: 600;
color: #303133;
}
// 确保页面可以滚动
.layout-padding {
height: 100%;
overflow-y: auto;
.layout-padding-auto {
height: 100%;
.layout-padding-view {
height: 100%;
overflow-y: auto;
}
}
}
</style>