| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709 |
- <template>
- <ReportModule
- :showHeader="false"
- :showTitle="false"
- :showDescribe="false"
- tableOrChart="qita"
- :showPrintBtn="false"
- :showExportBtn="false"
- >
- <template #module_qita>
- <div class="card_container">
- <div
- class="paper_card"
- v-for="(item, index) in state.paperItems"
- :key="index"
- :style="{ background: item.background }"
- >
- <div
- class="background-image"
- :style="{ backgroundImage: `url(${item.backgroundImage})` }"
- ></div>
- <div class="statistic" :style="{ color: item.color }">
- {{ item.label }}
- </div>
- <div class="value" :style="{ color: item.color }">
- {{ item.value }}
- </div>
- </div>
- </div>
- </template>
- </ReportModule>
- <ReportModule
- :titleList="['1、难度与区分度对比图']"
- tableOrChart="chart"
- :showPrintBtn="false"
- :showExportBtn="false"
- >
- <template #module_table_chart>
- <LineChart
- v-if="state.lineChartData?.datax?.length"
- :datax="state.lineChartData.datax"
- :datay="state.lineChartData.datay"
- :showCheckBox="false"
- :extraText="false"
- :showMarkPoint="true"
- :showMarkLine="true"
- :gridRight="40"
- :isSetMarkNumber="true"
- :markNumber="state.lineChartData.markNumber"
- :title="state.lineChartData.title"
- :legendList="state.lineChartData.title"
- ></LineChart>
- <div
- v-else
- class="module_chart no_content_data"
- v-loading="state.dataLoading"
- :element-loading-text="state.loadingText"
- :element-loading-spinner="loadingSvgIcon"
- element-loading-background="#ffffff"
- >
- <span>暂无数据</span>
- </div>
- </template>
- <template #module_describe>
- 说明:难度指试题的难易程度,通常用P值表示。计算方式为P=X/M(P为难度,X为试题平均得分,M为试题满分)。P值在0到1之间,值越大表示试题越简单,试题通常分为容易题(P≥0.7)、中等题(0.4至0.7之间)和难题(P≤0.4)。
- 区分度是衡量试题对不同水平考生的区分能力,通常用D值表示。通过计算高分组和低分组在某一试题上的通过率之差,得到试题区分度。区分度高的试题能将不同水平的考生区分开来,水平高的学生得高分,水平低的学生得低分。D值的取值范围介于-1至1之间,D值越高,区分的效果越好。D≥0.4表明此题的区分度很好,属于优秀;0.3≤D<0.4表明此题的区分度较好,属于良好;0.2≤D<0.4表明此题的区分度一般;D<0.2表明此题的区分度较低。
- 分析试题的难度和区分度可以确保试题的质量和有效性,从而更好地评估学生的知识掌握程度和区分不同水平的学生。
- </template>
- </ReportModule>
- <ReportModule
- :titleList="['2、难度分布']"
- :showDescribe="false"
- tableOrChart="qita"
- :showPrintBtn="false"
- :showExportBtn="false"
- >
- <template #module_qita>
- <div class="content_left">
- <PieChart
- v-if="state.difficultyChart.data?.length"
- height="340"
- :data="state.difficultyChart.data"
- :refresh="state.difficultyChart.refresh"
- @refresh="GetFirstPieRefresh"
- />
- <div
- v-else
- class="module_chart no_content_data"
- v-loading="state.dataLoading"
- :element-loading-text="state.loadingText"
- :element-loading-spinner="loadingSvgIcon"
- element-loading-background="#ffffff"
- >
- <span>暂无数据</span>
- </div>
- </div>
- <div class="content_right table_42">
- <el-table
- border
- :data="state.difficultyChart.tableData"
- stripe
- align="center"
- row-key="name"
- >
- <el-table-column
- prop="name"
- label="分类"
- align="center"
- width="100"
- show-overflow-tooltip
- v-if="state.difficultyChart.tableData.length"
- />
- <el-table-column
- prop="value"
- label="难度系数"
- align="center"
- width="120"
- show-overflow-tooltip
- v-if="state.difficultyChart.tableData.length"
- />
- <el-table-column
- prop="score"
- label="满分"
- align="center"
- width="80"
- show-overflow-tooltip
- v-if="state.difficultyChart.tableData.length"
- />
- <el-table-column
- prop="scoreRate"
- label="分数占比"
- align="center"
- width="90"
- show-overflow-tooltip
- v-if="state.difficultyChart.tableData.length"
- >
- <template #default="scope">
- <span>{{ scope.row.scoreRate }}%</span>
- </template>
- </el-table-column>
- <el-table-column
- :prop="item.prop"
- :label="item.label"
- align="center"
- show-overflow-tooltip
- v-for="(item, index) in state.discriminationChart.headerData"
- :key="index"
- >
- <template #default="scope">
- {{ scope.row[item.prop] || "-" }}
- </template>
- </el-table-column>
- </el-table>
- </div>
- </template>
- </ReportModule>
- <ReportModule
- :titleList="['3、区分度分布']"
- :showDescribe="false"
- tableOrChart="qita"
- :showPrintBtn="false"
- :showExportBtn="false"
- >
- <template #module_qita>
- <div class="content_left">
- <PieChart
- v-if="state.discriminationChart.data?.length"
- :color="['#5470C6', '#3BA272', '#FAC858', '#EE6666']"
- height="340"
- :data="state.discriminationChart.data"
- :refresh="state.discriminationChart.refresh"
- @refresh="GetSecondPieRefresh"
- />
- <div
- v-else
- class="module_chart no_content_data"
- v-loading="state.dataLoading"
- :element-loading-text="state.loadingText"
- :element-loading-spinner="loadingSvgIcon"
- element-loading-background="#ffffff"
- >
- <span>暂无数据</span>
- </div>
- </div>
- <div class="content_right table_42">
- <el-table
- border
- :data="state.discriminationChart.tableData"
- stripe
- align="center"
- row-key="name"
- >
- <el-table-column
- prop="name"
- label="分类"
- align="center"
- width="70"
- v-if="state.discriminationChart.tableData.length"
- />
- <el-table-column
- prop="value"
- label="区分度"
- align="center"
- width="110"
- show-overflow-tooltip
- v-if="state.discriminationChart.tableData.length"
- />
- <el-table-column
- prop="score"
- label="满分"
- align="center"
- width="80"
- show-overflow-tooltip
- v-if="state.discriminationChart.tableData.length"
- />
- <el-table-column
- prop="scoreRate"
- label="分数占比"
- align="center"
- width="100"
- show-overflow-tooltip
- v-if="state.discriminationChart.tableData.length"
- >
- <template #default="scope">
- <span>{{ scope.row.scoreRate }}%</span>
- </template>
- </el-table-column>
- <el-table-column
- :prop="item.prop"
- :label="item.label"
- align="center"
- show-overflow-tooltip
- v-for="(item, index) in state.discriminationChart.headerData"
- :key="index"
- >
- <template #default="scope">
- {{ scope.row[item.prop] || "-" }}
- </template>
- </el-table-column>
- </el-table>
- </div>
- </template>
- </ReportModule>
- <ReportModule
- ref="reportModuleRef"
- :showTitle="true"
- :titleList="['4、命题分析表']"
- :showDescribe="false"
- tableOrChart="table"
- :showPrintBtn="false"
- :showExportBtn="true"
- :currentPage="state.questionData.currentPage"
- :pageSize="state.questionData.pageSize"
- :total="state.questionData.total"
- @ChangePageSize="ChangePageSize"
- @ChangeCurrentPage="ChangeCurrentPage"
- @ExportExcel="ExportExcel"
- >
- <template #module_table_chart>
- <el-table
- :data="questionTable"
- border
- stripe
- align="center"
- row-key="showCode"
- >
- <el-table-column
- prop="questionCode"
- label="题号"
- min-width="120"
- align="center"
- show-overflow-tooltip
- >
- <template #default="scope">
- <span v-if="scope.row.showCode == 0">全卷</span>
- <span v-else>{{ scope.row.questionCode }}</span>
- </template>
- </el-table-column>
- <el-table-column
- prop="questionTypeName"
- label="题型"
- min-width="120"
- align="center"
- show-overflow-tooltip
- />
- <el-table-column
- prop="studentCount"
- label="实考人数"
- min-width="100"
- align="center"
- show-overflow-tooltip
- />
- <el-table-column
- prop="fullMark"
- label="满分"
- min-width="100"
- align="center"
- show-overflow-tooltip
- />
- <el-table-column
- prop="difficulty"
- label="难度"
- min-width="100"
- align="center"
- show-overflow-tooltip
- />
- <el-table-column
- prop="discrimination"
- label="区分度"
- min-width="100"
- align="center"
- show-overflow-tooltip
- />
- <el-table-column
- prop="averageScore"
- label="平均分"
- min-width="100"
- align="center"
- show-overflow-tooltip
- />
- <el-table-column
- prop="maxScore"
- label="最高分"
- min-width="100"
- align="center"
- show-overflow-tooltip
- />
- <el-table-column
- prop="minScore"
- label="最低分"
- min-width="100"
- align="center"
- show-overflow-tooltip
- />
- <el-table-column
- prop="scoreRate"
- label="得分率"
- min-width="100"
- align="center"
- show-overflow-tooltip
- >
- <template #default="scope"> {{ scope.row.scoreRate }}% </template>
- </el-table-column>
- <el-table-column
- prop="standardDeviation"
- label="标准差"
- min-width="100"
- align="center"
- show-overflow-tooltip
- />
- </el-table>
- </template>
- </ReportModule>
- </template>
- <script lang="ts" setup>
- import { reactive, onMounted, computed, ref, watch } from "vue";
- import ReportModule from "@/components/ReportModule.vue";
- import LineChart from "@/components/echarts/lineChart.vue";
- import PieChart from "@/components/echarts/pieChart.vue";
- import paperDifficultyImg from "@/assets/icon/Paper_difficulty.png";
- import difficultyRatioImg from "@/assets/icon/difficulty_ratio.png";
- import testPaperDistinctionImg from "@/assets/icon/test_paper_distinction.png";
- import discriminationRatioImg from "@/assets/icon/discrimination_ratio.png";
- import testPaperReliabilityImg from "@/assets/icon/test_paper_reliability.png";
- import { useAnalysisStore } from "@/store/analysis";
- import { propositionAnalysis, publicExport } from "@/api/analysis";
- import {
- downloadExcel,
- GetExcelFileName,
- propositionAnalysisStaticHeaderData,
- } from "@/utils/exportExcel";
- import {loadingSvgIcon} from "@/utils/common";
- const analysisStore = useAnalysisStore();
- const reportModuleRef = ref<any>(null);
- const getExamName = computed(() => {
- return analysisStore.analysisExamInfo.examName || "";
- });
- interface PaperItem {
- label: string;
- value: string | number;
- background: string;
- backgroundImage: string;
- color: string;
- }
- const state = reactive({
- paperItems: [
- {
- label: "试卷难度",
- value: "",
- background: "linear-gradient(99.67deg, #B4D2FF 0%, #F1F7FF 100%)",
- backgroundImage: paperDifficultyImg,
- color: "#15325E",
- },
- {
- label: "难度比例(难中易)",
- value: "",
- background: "linear-gradient(99.67deg, #FFCAE1 0%, #FFF3F8 100%)",
- backgroundImage: difficultyRatioImg,
- color: "#5d1638",
- },
- {
- label: "试卷区分度",
- value: "",
- background: "linear-gradient(99.67deg, #D2C6FF 0%, #F7F1FF 100%)",
- backgroundImage: testPaperDistinctionImg,
- color: "#41155e",
- },
- {
- label: "区分度比例(优良中差)",
- value: "",
- background: "linear-gradient(99.67deg, #F7E595 0%, #FFFBED 100%)",
- backgroundImage: discriminationRatioImg,
- color: "#7a581d",
- },
- {
- label: "试卷信度",
- value: "",
- background: "linear-gradient(99.67deg, #BCF5C9 0%, #ECFFF5 100%)",
- backgroundImage: testPaperReliabilityImg,
- color: "#0f4c2e",
- },
- ] as PaperItem[],
- keyWord: "",
- checkList: [] as string[],
- lineChartData: {
- datax: [] as string[],
- datay: [[], []] as number[][], // 修复: 显式指定为 number[][],防止推断为 never[][]
- title: ["难度", "区分度"],
- colors: [] as string[],
- markNumber: [] as number[],
- },
- difficultyChart: {
- data: [] as any[],
- refresh: false,
- tableData: [] as any[],
- },
- discriminationChart: {
- data: [] as any[],
- refresh: false,
- tableData: [] as any[],
- headerData: [] as any[],
- },
- questionData: {
- totalTableData: [] as any[],
- tableData: [] as any[],
- pageSize: 10,
- total: 0,
- currentPage: 1,
- },
- exportLoading: false,
- dataLoading: false,
- loadingText: "加载中,请稍后...",
- });
- const questionTable = computed(() => {
- const start = (state.questionData.currentPage - 1) * state.questionData.pageSize;
- const end = start + state.questionData.pageSize;
- return state.questionData.tableData.slice(start, end);
- });
- // 修复: 添加参数类型注解 isRefresh: boolean
- const PageInit = async (isRefresh: boolean) => {
- if (isRefresh) {
- state.difficultyChart.refresh = true;
- state.discriminationChart.refresh = true;
- }
- state.dataLoading = true;
- try {
- let { code, data } = await propositionAnalysis(analysisStore.filterObject);
- state.questionData.currentPage = 1;
- if (code == 200 && data) {
- const { difficultyList, discriminationList, paperInfo, questionInfoList } = data;
-
- const difficultyChart = difficultyList || [];
-
- // 修复: 将三元表达式赋值改为标准的 if-else,避免 TS 语法警告
- const discriminationChart = (discriminationList || []).map((item: any) => {
- if (item.name === "及格") {
- item.name = "一般";
- } else if (item.name === "低分") {
- item.name = "较低";
- }
- return item;
- });
- state.questionData.totalTableData = questionInfoList || [];
- state.questionData.tableData = questionInfoList || [];
- const length = state.questionData.totalTableData.length;
-
- // 修复: 使用可选链 ?. 和空值合并 ?? 防止 paperInfo 为空时报错
- state.paperItems[0].value = paperInfo?.paperDifficulty ?? "";
- state.paperItems[1].value = paperInfo?.difficultyRate ?? "";
- state.paperItems[2].value = paperInfo?.paperDiscrimination ?? "";
- state.paperItems[3].value = paperInfo?.discriminationRate ?? "";
- state.paperItems[4].value = paperInfo?.paperReliability ?? "";
-
- state.lineChartData.datax = [];
- state.lineChartData.datay = [[], []] as number[][]; // 修复: 重置时保持类型断言
- const lastItem = state.questionData.totalTableData[length - 1];
- // 修复: 使用可选链防止 length 为 0 时越界报错
- state.lineChartData.markNumber = [
- lastItem?.difficulty ?? 0,
- lastItem?.discrimination ?? 0,
- ];
-
- state.questionData.totalTableData.forEach((item, index) => {
- if (index != length - 1) {
- state.lineChartData.datax.push(item.questionCode);
- state.lineChartData.datay[0].push(item.difficulty);
- state.lineChartData.datay[1].push(item.discrimination);
- }
- });
-
- state.difficultyChart.data = difficultyChart.map((item: any) => {
- return { name: item.name, value: item.scoreRate };
- });
- state.difficultyChart.tableData = difficultyChart;
- state.difficultyChart.headerData = data.headerList || [];
-
- state.discriminationChart.data = discriminationChart.map((item: any) => {
- return { name: item.name, value: item.scoreRate };
- });
- state.discriminationChart.headerData = data.headerList || [];
- state.discriminationChart.tableData = discriminationChart;
-
- state.questionData.total = length;
- } else {
- state.lineChartData.datax = [];
- state.lineChartData.datay = [[], []] as number[][];
- state.lineChartData.title = ["难度", "区分度"];
- state.lineChartData.colors = [];
- state.lineChartData.markNumber = [];
- state.paperItems.map((item) => {
- item.value = "";
- });
- state.difficultyChart.data = [];
- state.difficultyChart.tableData = [];
- state.difficultyChart.headerData = [];
- state.discriminationChart.data = [];
- state.discriminationChart.headerData = [];
- state.discriminationChart.tableData = [];
- state.questionData.totalTableData = [];
- state.questionData.tableData = [];
- }
- } finally {
- state.dataLoading = false;
- }
- };
- // 修复: 添加参数类型注解
- const GetFirstPieRefresh = (value: boolean) => {
- state.difficultyChart.refresh = value;
- };
- const GetSecondPieRefresh = (value: boolean) => {
- state.discriminationChart.refresh = value;
- };
- // 修复: 添加参数类型注解
- const ChangeCurrentPage = (val: number) => {
- state.questionData.currentPage = val;
- };
- const ChangePageSize = (val: number) => {
- state.questionData.pageSize = val;
- state.questionData.currentPage = 1;
- };
- const ExportExcel = () => {
- reportModuleRef.value?.SetExportLoading?.(true);
- const examName = getExamName.value;
- const staticHeaderData = propositionAnalysisStaticHeaderData();
- let dataList: any[] = [];
-
- state.questionData.tableData.forEach((row: any) => {
- const rowData = staticHeaderData.map((header: any) => {
- if (header.prop == "questionCode") {
- return row.showCode == 0 ? "全卷" : row.questionCode;
- } else if (header.prop == "scoreRate") {
- return `${row.scoreRate}%`;
- } else {
- return row[header.prop];
- }
- });
- dataList.push(rowData);
- });
-
- let params = {
- fileName: `${GetExcelFileName(examName, analysisStore.filterObject, "命题分析表")}`,
- examName: examName,
- sheetName: "命题分析表",
- staticHeaderData: staticHeaderData,
- dynamicsHeaderData: [],
- childHeaderData: [],
- dataList: dataList,
- };
-
- downloadExcel(publicExport, params).finally(() => {
- reportModuleRef.value?.SetExportLoading?.(false);
- });
- };
- watch(
- () => analysisStore.filterObject,
- async () => {
- PageInit(true);
- },
- { deep: true },
- );
- onMounted(() => {
- PageInit(false);
- });
- </script>
- <style lang="scss" scoped>
- .card_container {
- width: 100%;
- display: flex;
- flex-wrap: wrap;
- /* 允许换行 */
- /* 设置元素之间间隔 */
- gap: 10px;
- padding: 10px;
- box-sizing: border-box;
- .paper_card {
- // flex: 0 1 calc((100% - 240px) / 5);
- flex-grow: 1; /* 使每个div平分可用空间 */
- flex-basis: 0; /* 初始基础大小为0 */
- min-width: 180px;
- // max-width: 320px;
- // width: 238px;
- /* 每个卡片占据 18% 宽度,确保能在一行显示五个,考虑间隔 */
- // flex: 0 0 16.5%; /* 每个卡片占据 18% 宽度,确保能在一行显示五个,考虑间隔 */
- // border: 2px dashed #ccc;
- border-radius: 10px;
- padding: 20px 16px;
- text-align: center;
- color: #fff;
- /* 文字颜色可以根据背景调整 */
- background-size: cover;
- background-position: center;
- height: 100px;
- /* 可调整高度 */
- // margin-bottom: 20px; /* 卡片底部间距 */
- position: relative;
- }
- .background-image {
- position: absolute;
- top: 0;
- right: 0;
- /* 背景图片宽度 */
- width: 100%;
- height: 100px;
- background-size: contain;
- background-repeat: no-repeat;
- background-position: center right;
- z-index: 1;
- /* 确保背景图片在文本之下 */
- border-radius: 10px;
- }
- .statistic,
- .value {
- position: relative;
- z-index: 2;
- /* 确保文本在背景图片之上 */
- }
- .statistic {
- font-size: 16px;
- font-weight: 400;
- line-height: 22px;
- text-align: left;
- z-index: 2;
- }
- .value {
- font-size: 26px;
- font-weight: 600;
- line-height: 42px;
- text-align: left;
- margin-top: 5px;
- z-index: 2;
- }
- }
- .module_qita {
- align-items: center;
- .content_left {
- width: 40%;
- }
- .content_right {
- width: 60%;
- display: flex;
- align-items: center;
- &.table_42{
- :deep(.el-table){
- border-radius: 8px;
- .el-table__cell{
- height: 52px;
- }
- }
- }
- }
- }
- </style>
|