| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608 |
- <template>
- <!-- 成绩查询 - 成绩单 -->
- <ReportModule
- ref="reportModuleRef"
- :showTitle="false"
- :showDescribe="false"
- :showPrintBtn="true"
- tableOrChart="table"
- :currentPage="state.pageInfo.pageNum"
- :pageSize="state.pageInfo.pageSize"
- :pageSizes="[50, 100]"
- :total="state.pageInfo.total"
- @ChangePageSize="ChangePageSize"
- @ChangeCurrentPage="ChangeCurrentPage"
- @ExportExcel="ExportExcel"
- @PrintPdf="PrintPdf"
- >
- <template #title_left>
- <el-input
- v-model="state.keyWord"
- clearable
- style="width: 200px"
- placeholder="请输入学号或姓名"
- class="input_with"
- @input="HandleSearch"
- >
- <template #append>
- <el-button :icon="Search" @click="HandleSearch" />
- </template>
- </el-input>
- <span class="count_item"
- >应考:{{ state.tableCount.examStudentCount }}人</span
- >
- <span class="count_item">实考:{{ state.tableCount.normalCount }}人</span>
- <span class="count_item orange"
- >缺考:{{ state.tableCount.missExamCount }}人</span
- >
- </template>
- <template #title_right>
- <el-checkbox-group v-model="state.checkList" class="checkbox_group">
- <el-checkbox
- v-if="state.groupDataTitleData.length"
- label="显示分组"
- value="group"
- />
- <el-checkbox
- v-if="state.scoreDataTitleData.length"
- label="显示小题"
- value="question"
- />
- </el-checkbox-group>
- </template>
- <template #module_table_chart>
- <el-table
- :data="state.tableData"
- border
- stripe
- ref="tableRef"
- :height="state.tableHeight"
- v-loading="state.tableLoading"
- :element-loading-text="state.loadingText"
- :element-loading-spinner="loadingSvgIcon"
- element-loading-background="#ffffff"
- >
- <el-table-column
- type="index"
- :index="GetIndexNumber"
- align="center"
- width="70"
- label="序号"
- :fixed="state.checkList.length ? 'left' : null"
- ></el-table-column>
- <el-table-column
- v-for="item in state.staticHeaderData"
- :key="item.prop"
- :prop="item.prop"
- :label="item.label"
- min-width="80"
- :fixed="state.checkList.length ? 'left' : null"
- >
- <template #default="scope">
- <span
- v-if="
- scope.row.score != '缺考' &&
- scope.row.score != '违纪' &&
- item.prop === 'studentUserName'
- "
- @click="OpenStudentPaper(scope.row)"
- style="cursor: pointer; color: #2e64fa"
- >
- {{ scope.row[item.prop] }}
- </span>
- <template v-else>
- {{ scope.row?.[item.prop] || "-" }}
- </template>
- </template>
- </el-table-column>
- <template v-if="state.checkList.includes('group')">
- <el-table-column
- v-for="(item, index) in state.groupDataTitleData"
- :key="item.prop"
- :prop="item.prop"
- :label="item.label"
- min-width="100"
- >
- <template #header>
- <template v-if="item.label.length > 6">
- <el-tooltip effect="dark" :content="item.label" placement="top">
- <span>{{ item.label }}</span>
- </el-tooltip>
- </template>
- <template v-else>
- {{ item.label }}
- </template>
- </template>
- <template #default="scope">{{
- scope.row?.dynamicsData?.groupData?.[index] || "-"
- }}</template>
- </el-table-column>
- </template>
- <template v-if="state.checkList.includes('question')">
- <el-table-column
- v-for="(item, index) in state.scoreDataTitleData"
- :key="item.prop"
- :prop="item.prop"
- :label="item.label"
- min-width="100"
- >
- <template #default="scope">{{
- scope.row?.dynamicsData?.scoreData?.[index] || "-"
- }}</template>
- </el-table-column>
- </template>
- </el-table>
- </template>
- </ReportModule>
- <StudentPaper
- :modelValue="state.showStudentPaperDialog"
- :paperInfo="state.paperInfo"
- :pageTitle="state.paperTitle"
- @updateModelValue="UpdateModelValue"
- ></StudentPaper>
- <ScorePdf ref="scorePdfRef"></ScorePdf>
- <div class="page_dialog padding_0">
- <el-dialog
- title="选择班级"
- center
- v-model="showClassPop"
- width="480px"
- >
- <div class="dialog_center padding_20 page_search">
- <div class="select_check_content score_class_popup">
- <el-checkbox
- :indeterminate="isIndeterminate"
- v-model="checkAll"
- @change="HandleCheckAllChange"
- >全部</el-checkbox
- >
- <el-checkbox-group
- v-model="selectClassList"
- @change="HandleCheckedClassChange"
- >
- <el-checkbox
- :value="item.classIdCode"
- v-for="item in classList"
- :title="item.className"
- >{{ TruncateMiddleText(item.className, 5, 3, 1) }}</el-checkbox
- >
- </el-checkbox-group>
- </div>
- </div>
- <div slot="footer" class="el-dialog__footer dialog-footer">
- <el-button size="small" @click="showClassPop = false">取 消</el-button>
- <el-button
- size="small"
- type="primary"
- @click="ConfirmEnter()"
- :loading="buttonLoading"
- >确 定</el-button
- >
- </div>
- </el-dialog>
- </div>
- </template>
- <script lang="ts" setup>
- import ReportModule from "@/components/ReportModule.vue";
- import StudentPaper from "@/components/StudentPaper.vue"; //学生答题卡组件
- import ScorePdf from "@/components/ScorePdf.vue";
- import {
- studentTranscriptTitle,
- queryJointStudentStatistics,
- studentTranscript,
- studentTranscriptExcel,
- } from "@/api/analysis";
- import { useAnalysisStore } from "@/store/analysis";
- import { downloadExcel } from "@/utils/exportExcel";
- import { Search } from "@element-plus/icons-vue";
- import { loadingSvgIcon, truncateMiddleText } from "@/utils/common";
- import { ElMessage } from "element-plus";
- import {
- nextTick,
- ref,
- onMounted,
- onUnmounted,
- reactive,
- computed,
- watch,
- } from "vue";
- import { throttle } from "lodash";
- interface TableColumn {
- prop: string;
- label: string;
- display?: boolean;
- [key: string]: any;
- }
- interface TableCount {
- examStudentCount: number | string;
- normalCount: number | string;
- missExamCount: number | string;
- }
- interface PageInfo {
- pageSize: number;
- pageNum: number;
- total: number;
- }
- interface State {
- keyWord: string;
- checkList: string[];
- tableHeight: Number;
- tableData: any[];
- staticHeaderData: TableColumn[];
- groupDataTitleData: TableColumn[];
- scoreDataTitleData: TableColumn[];
- tableCount: TableCount;
- pageInfo: PageInfo;
- tableLoading: Boolean;
- loadingText: String;
- paperInfo: {
- examPaperId: string; //考试科目id
- platformNumber: string; //学籍号平台号
- questionId: string; //题目id
- };
- paperTitle: String; //学生试卷标题
- showStudentPaperDialog: Boolean; //是否显示学生答题卡弹窗
- }
- const analysisStore = useAnalysisStore();
- const getExamName = computed(() => {
- return analysisStore.analysisExamInfo.examName || "";
- });
- const state = reactive<State>({
- keyWord: "",
- checkList: [],
- tableHeight: 1000, //表格高度
- tableData: [],
- staticHeaderData: [],
- groupDataTitleData: [],
- scoreDataTitleData: [],
- tableCount: {
- examStudentCount: 0,
- normalCount: 0,
- missExamCount: 0,
- },
- pageInfo: {
- pageSize: 50,
- pageNum: 1,
- total: 0,
- },
- tableLoading: true,
- loadingText: "加载中,请稍后...",
- paperInfo: {
- examPaperId: "", //考试科目id
- platformNumber: "", //学籍号平台号
- questionId: "", //题目id
- }, //学生试卷信息
- paperTitle: "", //学生试卷标题
- showStudentPaperDialog: false, //是否显示学生答题卡弹窗
- });
- const tableRef = ref<any>(null);
- const reportModuleRef = ref<any>(null);
- const scorePdfRef = ref<any>(null);//Pdf组件
- const showClassPop = ref<boolean>(false); //选择班级弹框
- const isIndeterminate = ref<boolean>(false); //是否班级全选半选
- const checkAll = ref<boolean>(false); //班级全选
- const selectClassList = ref<string[]>([]); //选中的班级
- const buttonLoading = ref<boolean>(false); //按钮loading
- const classList = computed(() => {//班级列表
- const classNameList = analysisStore.filtersData.find((item: any) => item.type === "className")?.list || [];
- return classNameList.filter((item: any) => item.classLevel == 2)
- });
- /** 获取表头 */
- const GetStudentTranscriptTitle = async () => {
- if (!analysisStore.filterObject) return;
- try {
- const res = await studentTranscriptTitle({
- ...analysisStore.filterObject,
- topicControl: 0,
- });
- if (res.code === 200) {
- const staticHeaderData = res.data?.title?.staticHeaderData || [];
- state.staticHeaderData = staticHeaderData.filter(
- (item: { display: any }) => item.display,
- );
- state.groupDataTitleData =
- res.data?.title?.dynamicsHeaderData?.groupDataTitle || [];
- state.scoreDataTitleData =
- res.data?.title?.dynamicsHeaderData?.scoreDataTitle || [];
- let checkList = [];
- if (state.groupDataTitleData.length) {
- checkList.push("group");
- }
- if (state.scoreDataTitleData.length) {
- checkList.push("question");
- }
- state.checkList = checkList.length ? [checkList[0]] : [];
- }
- } catch (err) {
- console.error("获取表头失败:", err);
- }
- };
- /** 获取顶部统计 */
- const GetTableCount = async () => {
- if (!analysisStore.filterObject) return;
- try {
- const res = await queryJointStudentStatistics({
- ...analysisStore.filterObject,
- topicControl: 0,
- queryStr: state.keyWord,
- });
- if (res.code === 200) {
- state.tableCount = res.data || {};
- }
- } catch (err) {
- console.error("获取统计数据失败:", err);
- }
- };
- /** 获取表格数据 */
- const GetTableData = async (isInit: boolean = false) => {
- if (!analysisStore.filterObject) return;
- try {
- state.tableLoading = true;
- const res = await studentTranscript({
- ...analysisStore.filterObject,
- topicControl: 0,
- queryStr: state.keyWord,
- pageParam: {
- pageNum: state.pageInfo.pageNum,
- pageSize: state.pageInfo.pageSize,
- },
- });
- if (res.code === 200) {
- state.tableData = res.data?.listData || [];
- state.pageInfo.total = Number(res.data?.total || 0);
- }
- } catch (err) {
- console.error("获取表格数据失败:", err);
- } finally {
- state.tableLoading = false;
- // 数据加载完成后重置滚动
- nextTick(() => {
- TableScrollTop();
- if (isInit) {
- SetTableHeight();
- }
- });
- }
- };
- //获取序号
- const GetIndexNumber = (index: number) => {
- let indexCount =
- (state.pageInfo.pageNum - 1) * state.pageInfo.pageSize + index + 1;
- return indexCount;
- };
- // 当前页码事件
- const ChangeCurrentPage = (val: number) => {
- state.pageInfo.pageNum = val;
- GetTableData();
- };
- // 每页显示个数事件
- const ChangePageSize = (val: number) => {
- state.pageInfo.pageSize = val;
- state.pageInfo.pageNum = 1;
- GetTableData();
- };
- // 导出Excel
- const ExportExcel = () => {
- // 1. 设置加载状态
- reportModuleRef.value?.SetExportLoading?.(true);
- // 2. 参数
- const params = {
- ...analysisStore.filterObject,
- topicControl: 0,
- topicControlName: "成绩单",
- queryStr: state.keyWord,
- pageParam: {
- pageNum: state.pageInfo.pageNum,
- pageSize: state.pageInfo.pageSize,
- },
- staticHeaderData: state.staticHeaderData,
- dynamicsHeaderDataMap: {},
- };
- if (state.checkList.includes("group")) {
- params.dynamicsHeaderDataMap.groupDataTitle = state.groupDataTitleData;
- }
- if (state.checkList.includes("question")) {
- params.dynamicsHeaderDataMap.scoreDataTitle = state.scoreDataTitleData;
- }
- // 3. 调用通用下载方法,并在完成后重置加载状态
- downloadExcel(studentTranscriptExcel, params).finally(() => {
- reportModuleRef.value?.SetExportLoading?.(false);
- });
- };
- // 打印PDF
- const PrintPdf = () => {
- selectClassList.value = [];//清空选择的数据
- isIndeterminate.value = false;//默认不半选
- buttonLoading.value = false;
- if(analysisStore?.filterObject?.classLevel===0){//年级
- selectClassList.value = classList.value.map((item: any) => item.classIdCode);
- checkAll.value = true;//默认全选
- showClassPop.value = true;//显示弹框
- }else{
- checkAll.value = false;//默认不全选
- showClassPop.value = false;//不显示弹框
- reportModuleRef.value?.SetPrintLoading?.(true);
- const classIdCode = analysisStore?.filterObject?.classIdCode;
- selectClassList.value = classIdCode? [classIdCode] : [];
- scorePdfRef.value?.LoadAndExportPdf(state.keyWord, selectClassList.value)
- .finally(() => {
- reportModuleRef.value?.SetPrintLoading?.(false);
- });//加载并导出PDF
- }
- };
- //班级全选
- const HandleCheckAllChange = () => {
- if (checkAll.value) {
- selectClassList.value = classList.value.map((item: any) => item.classIdCode);
- } else {
- selectClassList.value = [];
- }
- };
- //班级选择改变事件
- const HandleCheckedClassChange = () => {
- if (selectClassList.value.length > 0) {
- checkAll.value = true;
- if (selectClassList.value.length == classList.value.length) {
- isIndeterminate.value = false;
- } else {
- isIndeterminate.value = true;
- }
- } else {
- checkAll.value = false;
- isIndeterminate.value = false;
- }
- };
- const TruncateMiddleText = (
- text: string,
- num: number,
- startStr: number,
- endStr: number,
- ) => {
- return truncateMiddleText(text, num, startStr, endStr);
- };
- //确定选择班级
- const ConfirmEnter = () => {
- if (selectClassList.value.length == 0) {
- ElMessage.warning("请至少选择一个班级!");
- return;
- }
- buttonLoading.value = true;
- setTimeout(() => {
- showClassPop.value = false;//先关闭弹框,不等PDF生成完成
- }, 500);
- scorePdfRef.value?.LoadAndExportPdf(state.keyWord, selectClassList.value)//加载并导出PDF
- .then(() => {
- ElMessage.success("成绩单已在新页面打开,请去下载或打印");
- })
- .catch((err) => {
- console.error("导出成绩单PDF失败:", err);
- ElMessage.error("导出PDF失败,请重试");
- })
- .finally(() => {
- buttonLoading.value = false;
- });
- };
- //打开答题卡弹窗
- const OpenStudentPaper = (row, prop) => {
- state.paperInfo = {
- examPaperId: analysisStore.filterObject.subjectId, //考试科目id
- platformNumber: row.studentRegistrationCode, //学籍号平台号
- questionId: "", //题目id
- };
- state.paperTitle = `${getExamName.value}-${analysisStore.filterObject.subjectName}-${row.className}-${row.studentUserName}`; //学生姓名
- state.showStudentPaperDialog = true;
- };
- //更新弹窗状态
- const UpdateModelValue = (val: boolean) => {
- state.showStudentPaperDialog = val;
- };
- // 搜索事件
- const HandleSearch = () => {
- state.pageInfo.pageNum = 1;
- GetTableData();
- };
- // 表格内容滚动置顶
- const TableScrollTop = () => {
- const el = tableRef.value?.$el;
- if (!el) return;
- // 在下一帧执行,确保 DOM 已更新
- requestAnimationFrame(() => {
- // 尝试多种选择器,兼容不同版本
- const scrollWrap =
- el.querySelector(".el-scrollbar__wrap") ||
- el.querySelector(".el-table__body-wrapper") ||
- el.querySelector(".el-table__fixed-body-wrapper");
- if (scrollWrap) {
- scrollWrap.scrollTop = 0;
- scrollWrap.scrollLeft = 0;
- } else {
- // 若仍未找到,延迟再试一次(表格可能尚未完全渲染)
- setTimeout(() => {
- const retry =
- el.querySelector(".el-scrollbar__wrap") ||
- el.querySelector(".el-table__body-wrapper");
- if (retry) {
- retry.scrollTop = 0;
- retry.scrollLeft = 0;
- }
- }, 200);
- }
- });
- };
- // 设置表格高度
- const SetTableHeight = () => {
- const container = document.getElementsByClassName("report_content")?.[0];
- if (container) {
- state.tableHeight = container.clientHeight - 152;
- }
- };
- // 窗口大小改变事件
- const HandleResize = throttle(() => {
- SetTableHeight();
- }, 200);
- // 初始化
- const PageInit = () => {
- state.pageInfo.pageNum = 1;
- state.checkList = [];
- GetStudentTranscriptTitle();
- GetTableCount();
- GetTableData(true);
- };
- // 监听筛选条件
- watch(
- () => analysisStore.filterObject,
- async () => {
- PageInit();
- },
- { deep: true },
- );
- onMounted(() => {
- window.addEventListener("resize", HandleResize);
- PageInit();
- });
- onUnmounted(() => {
- // 移除监听 防止内存泄漏
- window.removeEventListener("resize", HandleResize);
- });
- </script>
- <style lang="scss" scoped>
- .input_with {
- margin-right: 10px;
- }
- .count_item {
- font-size: 16px;
- font-weight: 400;
- color: #333;
- line-height: 24px;
- margin-left: 10px;
- &.orange {
- color: #fb9f34;
- }
- }
- .checkbox_group {
- :deep(.el-checkbox) {
- margin-right: 10px;
- &:first-child {
- margin-right: 20px;
- }
- }
- }
- </style>
|