|
|
@@ -1,18 +1,35 @@
|
|
|
<template>
|
|
|
- <ReportModule :titleList="['1、分数段图']" tableOrChart="chart" :showPrintBtn="false" :showExportBtn="false">
|
|
|
+ <ReportAssistant
|
|
|
+ :titleList="['1、分数段图']"
|
|
|
+ tableOrChart="chart"
|
|
|
+ :showPrintBtn="false"
|
|
|
+ :showExportBtn="false"
|
|
|
+ >
|
|
|
<template #title_right>
|
|
|
- <div :class="['right_item', { item_active: state.sectionScore == item }]" v-for="item in state.sortRangeScore"
|
|
|
- :key="item" @click="TagClick(item)">
|
|
|
+ <div
|
|
|
+ :class="['right_item', { item_active: state.sectionScore == item }]"
|
|
|
+ v-for="item in state.sortRangeScore"
|
|
|
+ :key="item"
|
|
|
+ @click="TagClick(item)"
|
|
|
+ >
|
|
|
{{ item }}分段
|
|
|
</div>
|
|
|
<div class="right_set">
|
|
|
<span>设置分数段</span>
|
|
|
- <el-input v-model="state.sectionScore" maxlength="3" style="width: 54px" @input="HandleInput"
|
|
|
- @change="BlurSectionScore" />
|
|
|
+ <el-input
|
|
|
+ v-model="state.sectionScore"
|
|
|
+ maxlength="3"
|
|
|
+ style="width: 54px"
|
|
|
+ @input="HandleInput"
|
|
|
+ @change="BlurSectionScore"
|
|
|
+ />
|
|
|
<span>分/段</span>
|
|
|
</div>
|
|
|
<div class="right_radio">
|
|
|
- <el-select v-model="state.radioRangeScore" v-if="analysisStore?.filterObject?.classLevel != 2">
|
|
|
+ <el-select
|
|
|
+ v-model="state.radioRangeScore"
|
|
|
+ v-if="analysisStore?.filterObject?.classLevel != 2"
|
|
|
+ >
|
|
|
<el-option :value="0" label="按年级"></el-option>
|
|
|
<el-option :value="1" label="按班级"></el-option>
|
|
|
</el-select>
|
|
|
@@ -21,23 +38,40 @@
|
|
|
<template #module_table_chart>
|
|
|
<template v-if="state.scoreSegmentData.datay.length">
|
|
|
<template v-if="state.radioRangeScore == 0">
|
|
|
- <BarLineChart :datax="state.scoreSegmentData.datax" :datay="state.scoreSegmentData.datay"
|
|
|
- :fullScore="state.scoreSegmentData.fullScore" :markLine="state.scoreSegmentData.markLine"
|
|
|
- :color="state.scoreSegmentData.color" :title="state.scoreSegmentData.title"
|
|
|
- :tooltipData="state.scoreSegmentData.tooltipData" :unit="state.scoreSegmentData.unit"
|
|
|
- :tooltipTitle="state.scoreSegmentData.tooltipTitle">
|
|
|
+ <BarLineChart
|
|
|
+ :datax="state.scoreSegmentData.datax"
|
|
|
+ :datay="state.scoreSegmentData.datay"
|
|
|
+ :fullScore="state.scoreSegmentData.fullScore"
|
|
|
+ :markLine="state.scoreSegmentData.markLine"
|
|
|
+ :color="state.scoreSegmentData.color"
|
|
|
+ :title="state.scoreSegmentData.title"
|
|
|
+ :tooltipData="state.scoreSegmentData.tooltipData"
|
|
|
+ :unit="state.scoreSegmentData.unit"
|
|
|
+ :tooltipTitle="state.scoreSegmentData.tooltipTitle"
|
|
|
+ >
|
|
|
</BarLineChart>
|
|
|
</template>
|
|
|
<!-- 按班级 -->
|
|
|
<template v-if="state.radioRangeScore == 1">
|
|
|
- <LineBarChart :datax="state.scoreSegmentClassData.datax" :datay="state.scoreSegmentClassData.datay"
|
|
|
- :showBackground="false" :legendList="state.scoreSegmentClassData.legendList"
|
|
|
- :title="state.scoreSegmentClassData.title" :tooltipData="state.scoreSegmentClassData.tooltipData"
|
|
|
- :hideOverlap="true"></LineBarChart>
|
|
|
+ <LineBarChart
|
|
|
+ :datax="state.scoreSegmentClassData.datax"
|
|
|
+ :datay="state.scoreSegmentClassData.datay"
|
|
|
+ :showBackground="false"
|
|
|
+ :legendList="state.scoreSegmentClassData.legendList"
|
|
|
+ :title="state.scoreSegmentClassData.title"
|
|
|
+ :tooltipData="state.scoreSegmentClassData.tooltipData"
|
|
|
+ :hideOverlap="true"
|
|
|
+ ></LineBarChart>
|
|
|
</template>
|
|
|
</template>
|
|
|
- <div v-else class="no_content_data" v-loading="state.tableLoading" :element-loading-text="state.loadingText"
|
|
|
- element-loading-spinner="el-icon-loading" element-loading-background="#ffffff">
|
|
|
+ <div
|
|
|
+ v-else
|
|
|
+ class="no_content_data"
|
|
|
+ v-loading="state.tableLoading"
|
|
|
+ :element-loading-text="state.loadingText"
|
|
|
+ element-loading-spinner="el-icon-loading"
|
|
|
+ element-loading-background="#ffffff"
|
|
|
+ >
|
|
|
<span>暂无数据</span>
|
|
|
</div>
|
|
|
</template>
|
|
|
@@ -50,22 +84,57 @@
|
|
|
区分度是衡量试题对不同水平考生的区分能力,通常用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、分数段表']" tableOrChart="table" :showPrintBtn="false" :showDescribe="false"
|
|
|
- :currentPage="state.scoreSegmentData.pageNum" :pageSize="state.scoreSegmentData.pageSize"
|
|
|
- :total="state.scoreSegmentData.total" @update:pageSize="handleSizeChange" @update:currentPage="handleCurrentChange">
|
|
|
+ </ReportAssistant>
|
|
|
+ <ReportAssistant
|
|
|
+ ref="reportModuleRef"
|
|
|
+ :titleList="['2、分数段表']"
|
|
|
+ tableOrChart="table"
|
|
|
+ :showPrintBtn="false"
|
|
|
+ :showDescribe="false"
|
|
|
+ :currentPage="state.scoreSegmentData.pageNum"
|
|
|
+ :pageSize="state.scoreSegmentData.pageSize"
|
|
|
+ :total="state.scoreSegmentData.total"
|
|
|
+ @ChangePageSize="ChangePageSize"
|
|
|
+ @ChangeCurrentPage="ChangeCurrentPage"
|
|
|
+ @ExportExcel="ExportExcel"
|
|
|
+ >
|
|
|
<template #module_table_chart>
|
|
|
- <el-table :data="scoreRangeTableData" border>
|
|
|
- <template v-for="(header, headerIndex) in state.scoreSegmentData.headerData">
|
|
|
- <el-table-column align="center" :label="header.name" v-if="header.child && header.child.length"
|
|
|
- :key="`child_${headerIndex}`">
|
|
|
- <el-table-column v-for="(child, childIndex) in header.child" align="center" :label="child.value"
|
|
|
- :prop="child.prop" :key="`${headerIndex}_${childIndex}`" show-overflow-tooltip>
|
|
|
+ <el-table :data="scoreRangeTableData" border stripe>
|
|
|
+ <template
|
|
|
+ v-for="(header, headerIndex) in state.scoreSegmentData.headerData"
|
|
|
+ >
|
|
|
+ <el-table-column
|
|
|
+ align="center"
|
|
|
+ :label="header.name"
|
|
|
+ v-if="header.child && header.child.length"
|
|
|
+ :key="`child_${headerIndex}`"
|
|
|
+ >
|
|
|
+ <el-table-column
|
|
|
+ v-for="(child, childIndex) in header.child"
|
|
|
+ align="center"
|
|
|
+ :label="child.value"
|
|
|
+ :prop="child.prop"
|
|
|
+ :key="`${headerIndex}_${childIndex}`"
|
|
|
+ show-overflow-tooltip
|
|
|
+ >
|
|
|
<template #default="scope">
|
|
|
- <template v-for="(detailItem, detailKey) in scope.row.detailList">
|
|
|
- <span v-if="header.name == detailItem.schoolName" :key="`${headerIndex}_${childIndex}_${detailKey}`">
|
|
|
- <span :class="child.prop == 'doubleOnlineNum' ? 'table_row_blue' : ''"
|
|
|
- v-if="child.prop == 'doubleOnlineNum' && detailItem[child.prop] != 0 && detailItem[child.prop] != '-'">
|
|
|
+ <template
|
|
|
+ v-for="(detailItem, detailKey) in scope.row.detailList"
|
|
|
+ >
|
|
|
+ <span
|
|
|
+ v-if="header.name == detailItem.schoolName"
|
|
|
+ :key="`${headerIndex}_${childIndex}_${detailKey}`"
|
|
|
+ >
|
|
|
+ <span
|
|
|
+ :class="
|
|
|
+ child.prop == 'doubleOnlineNum' ? 'table_row_blue' : ''
|
|
|
+ "
|
|
|
+ v-if="
|
|
|
+ child.prop == 'doubleOnlineNum' &&
|
|
|
+ detailItem[child.prop] != 0 &&
|
|
|
+ detailItem[child.prop] != '-'
|
|
|
+ "
|
|
|
+ >
|
|
|
{{ detailItem[child.prop] }}
|
|
|
</span>
|
|
|
<span v-else>
|
|
|
@@ -76,8 +145,16 @@
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
</el-table-column>
|
|
|
- <el-table-column v-else align="center" width="100" :label="header.name" :prop="header.prop" :key="headerIndex"
|
|
|
- fixed="left" show-overflow-tooltip>
|
|
|
+ <el-table-column
|
|
|
+ v-else
|
|
|
+ align="center"
|
|
|
+ width="100"
|
|
|
+ :label="header.name"
|
|
|
+ :prop="header.prop"
|
|
|
+ :key="headerIndex"
|
|
|
+ fixed="left"
|
|
|
+ show-overflow-tooltip
|
|
|
+ >
|
|
|
<template #default="scope">
|
|
|
{{ scope.row[header.prop] }}
|
|
|
</template>
|
|
|
@@ -85,19 +162,141 @@
|
|
|
</template>
|
|
|
</el-table>
|
|
|
</template>
|
|
|
- </ReportModule>
|
|
|
+ </ReportAssistant>
|
|
|
</template>
|
|
|
+
|
|
|
<script lang="ts" setup>
|
|
|
-import ReportModule from "@/components/ReportModule.vue";
|
|
|
-import BarLineChart from "@/components/echarts/barLineChart.vue";//柱状图折线图组件
|
|
|
-import LineBarChart from "@/components/echarts/lineBarChart.vue";//折线图柱状图组件
|
|
|
+import ReportAssistant from "@/components/ReportModule.vue";
|
|
|
+import BarLineChart from "@/components/echarts/barLineChart.vue"; //柱状图折线图组件
|
|
|
+import LineBarChart from "@/components/echarts/lineBarChart.vue"; //折线图柱状图组件
|
|
|
import { useAnalysisStore } from "@/store/analysis";
|
|
|
-import { onMounted, reactive, computed, watch } from "vue";
|
|
|
-import { scoreSegment } from "@/api/analysis";
|
|
|
+import { onMounted, reactive, computed, watch, ref } from "vue";
|
|
|
+import { scoreSegment, publicExport } from "@/api/analysis";
|
|
|
+import { downloadExcel, GetExcelFileName } from "@/utils/exportExcel";
|
|
|
+
|
|
|
+// --- 类型定义 ---
|
|
|
+
|
|
|
+interface MarkLineItem {
|
|
|
+ name: string;
|
|
|
+ value?: number;
|
|
|
+ color?: string;
|
|
|
+ xAxis?: number;
|
|
|
+}
|
|
|
+
|
|
|
+interface TooltipItem {
|
|
|
+ name: string;
|
|
|
+ value: string;
|
|
|
+}
|
|
|
+
|
|
|
+interface TableHeaderChild {
|
|
|
+ value: string;
|
|
|
+ prop: string;
|
|
|
+}
|
|
|
+
|
|
|
+interface TableHeader {
|
|
|
+ name: string;
|
|
|
+ prop?: string;
|
|
|
+ child?: TableHeaderChild[];
|
|
|
+}
|
|
|
+
|
|
|
+interface TableRowDetail {
|
|
|
+ schoolName: string;
|
|
|
+ [key: string]: any;
|
|
|
+}
|
|
|
+
|
|
|
+interface TableRow {
|
|
|
+ detailList: TableRowDetail[];
|
|
|
+ [key: string]: any;
|
|
|
+}
|
|
|
+
|
|
|
+interface ScoreSegmentData {
|
|
|
+ exportLoading: boolean;
|
|
|
+ datax: string[];
|
|
|
+ datay: number[][];
|
|
|
+ fullScore: number;
|
|
|
+ markLine: MarkLineItem[];
|
|
|
+ tooltipData: TooltipItem[];
|
|
|
+ title: string[];
|
|
|
+ color: string[];
|
|
|
+ unit: string;
|
|
|
+ tooltipTitle: string;
|
|
|
+ tableData: TableRow[];
|
|
|
+ headerData: TableHeader[];
|
|
|
+ pageSize: number;
|
|
|
+ total: number;
|
|
|
+ pageNum: number;
|
|
|
+}
|
|
|
+
|
|
|
+interface ScoreSegmentClassData {
|
|
|
+ datax: string[];
|
|
|
+ datay: number[][];
|
|
|
+ title: string[];
|
|
|
+ legendList: string[];
|
|
|
+ tooltipData: string[][];
|
|
|
+}
|
|
|
+
|
|
|
+interface State {
|
|
|
+ sectionScore: string;
|
|
|
+ sortRangeScore: string[];
|
|
|
+ radioRangeScore: number;
|
|
|
+ scoreSegmentData: ScoreSegmentData;
|
|
|
+ tableLoading: boolean;
|
|
|
+ loadingText: string;
|
|
|
+ scoreSegmentClassData: ScoreSegmentClassData;
|
|
|
+}
|
|
|
+
|
|
|
+// API 返回数据的简易接口定义 (根据实际后端返回调整)
|
|
|
+interface ApiChartDataTotalItem {
|
|
|
+ name: string;
|
|
|
+ doubleOnlineNum: number;
|
|
|
+ doubleOnlineRate: string;
|
|
|
+ studentUserName: string[];
|
|
|
+}
|
|
|
+
|
|
|
+interface ApiChartDataSingleItem {
|
|
|
+ name: string;
|
|
|
+ detailList: {
|
|
|
+ doubleOnlineNum: number;
|
|
|
+ doubleOnlineRate: string;
|
|
|
+ }[];
|
|
|
+}
|
|
|
+
|
|
|
+interface ApiChartData {
|
|
|
+ groupSchoolData: ApiChartDataTotalItem[];
|
|
|
+ oneSchoolData: ApiChartDataSingleItem[];
|
|
|
+ average: string;
|
|
|
+ standard: string;
|
|
|
+ twoStandard: string;
|
|
|
+ negativeOneStandard: string;
|
|
|
+ negativeTwoStandard: string;
|
|
|
+ fullScore: string;
|
|
|
+}
|
|
|
+
|
|
|
+interface ApiResponse {
|
|
|
+ code: number;
|
|
|
+ data?: {
|
|
|
+ chartData: ApiChartData;
|
|
|
+ rowData: TableRow[];
|
|
|
+ titleData: TableHeader[];
|
|
|
+ };
|
|
|
+}
|
|
|
+
|
|
|
+// --- 业务逻辑 ---
|
|
|
+
|
|
|
const analysisStore = useAnalysisStore();
|
|
|
-const state = reactive({
|
|
|
- sectionScore: '', // 设置分数段输入框
|
|
|
- sortRangeScore: ['5', '10'], // 设置分数段
|
|
|
+const reportModuleRef = ref<any>(null);
|
|
|
+const getExamName = computed(() => {
|
|
|
+ return analysisStore.analysisExamInfo.examName || "";
|
|
|
+});
|
|
|
+const scoreRangeTableData = computed(() => {
|
|
|
+ const { tableData, pageSize, pageNum } = state.scoreSegmentData;
|
|
|
+ const start = (pageNum - 1) * pageSize;
|
|
|
+ const end = start + pageSize;
|
|
|
+ return tableData.slice(start, end);
|
|
|
+});
|
|
|
+const state = reactive<State>({
|
|
|
+ sectionScore: "", // 设置分数段输入框
|
|
|
+ sortRangeScore: ["5", "10"], // 设置分数段
|
|
|
radioRangeScore: 0, // 0按年级, 1按班级
|
|
|
scoreSegmentData: {
|
|
|
exportLoading: false,
|
|
|
@@ -123,208 +322,299 @@ const state = reactive({
|
|
|
datay: [],
|
|
|
title: [],
|
|
|
legendList: [],
|
|
|
- tooltipData: [],//悬浮弹窗的数据
|
|
|
- }//分数段 按班级分析数据
|
|
|
+ tooltipData: [], //悬浮弹窗的数据
|
|
|
+ }, //分数段 按班级分析数据
|
|
|
});
|
|
|
-const scoreRangeTableData = computed(() => {
|
|
|
- const { tableData, pageSize, pageNum } = state.scoreSegmentData;
|
|
|
- const start = (pageNum - 1) * pageSize;
|
|
|
- const end = start + pageSize;
|
|
|
- return tableData.slice(start, end);
|
|
|
-})
|
|
|
//分数段
|
|
|
const GetScoreSegment = async () => {
|
|
|
state.tableLoading = true;
|
|
|
- const res = await scoreSegment({
|
|
|
- ...analysisStore.filterObject,
|
|
|
- scoreSegmentNum: state.sectionScore,
|
|
|
- });
|
|
|
- if (res.code === 200 && res.data && res.data.rowData && res.data.rowData.length) {
|
|
|
- let chartData = res.data.chartData
|
|
|
- let chartDataTotal = res.data.chartData.groupSchoolData // 联校/年级数据
|
|
|
- let chartDataSingle = res.data.chartData.oneSchoolData // 单校/班级数据
|
|
|
-
|
|
|
- let datax = []; // 分数段x轴数据
|
|
|
- let datay = []; // 联校/年级分数段y轴数据
|
|
|
- let count = []; // 联校/年级数据
|
|
|
- let tooltipData = []; // 联校/年级悬浮弹窗数据
|
|
|
- let markLine = []; // 辅助线数据
|
|
|
-
|
|
|
- let classTooltipData = []; //班级悬浮弹窗数据
|
|
|
- let classDatay = []; //分数段按班级y轴数据
|
|
|
- let classTitle = []; //分数段按班级图例数据
|
|
|
-
|
|
|
- let average = parseFloat(res.data.chartData.average); // 平均分
|
|
|
- let averageIndex = 0;//平均分的索引
|
|
|
- let standard = parseFloat(res.data.chartData.standard);//标准差
|
|
|
- let standardIndex = 0;//标准差索引
|
|
|
- let twoStandard = parseFloat(res.data.chartData.twoStandard);//2倍标准差
|
|
|
- let twoStandardIndex = 0;//2倍标准差索引
|
|
|
- let negativeOneStandard = parseFloat(res.data.chartData.negativeOneStandard);//-1倍标准差
|
|
|
- let negativeOneStandardIndex = 0;//-1倍标准差索引
|
|
|
- let negativeTwoStandard = parseFloat(res.data.chartData.negativeTwoStandard);//-2倍标准差
|
|
|
- let negativeTwoStandardIndex = 0;//-2倍标准差索引
|
|
|
-
|
|
|
- chartDataTotal.forEach((item, index) => {
|
|
|
- let list = item.name.split('-');
|
|
|
- let num1 = parseInt(list[0].replace(/[\[\]()]/g, ''));
|
|
|
- let num2 = parseInt(list[1].replace(/[\[\]()]/g, ''));
|
|
|
-
|
|
|
- if (num2 < average && average < num1) { // 平均分
|
|
|
- averageIndex = index
|
|
|
- }
|
|
|
- if (num2 < standard && standard < num1) { // 标准差
|
|
|
- standardIndex = index
|
|
|
- }
|
|
|
- if (num2 < twoStandard && twoStandard < num1) { // 2倍标准差
|
|
|
- twoStandardIndex = index
|
|
|
- }
|
|
|
- if (num2 < negativeOneStandard && negativeOneStandard < num1) { // -1倍标准差
|
|
|
- negativeOneStandardIndex = index
|
|
|
- }
|
|
|
- if (num2 < negativeTwoStandard && negativeTwoStandard < num1) { // -2倍标准差
|
|
|
- negativeTwoStandardIndex = index
|
|
|
- }
|
|
|
+ try {
|
|
|
+ const res = (await scoreSegment({
|
|
|
+ ...analysisStore.filterObject,
|
|
|
+ scoreSegmentNum: state.sectionScore,
|
|
|
+ })) as unknown as ApiResponse;
|
|
|
|
|
|
- datax.push(item.name)
|
|
|
- count.push(item.doubleOnlineNum)
|
|
|
- tooltipData.push({
|
|
|
- name: '',
|
|
|
- value: `${item.doubleOnlineNum}人,占比${item.doubleOnlineRate} ${item.studentUserName.length ? `(${item.studentUserName.join('、')})` : ''}`
|
|
|
- })
|
|
|
- });
|
|
|
- datay.push(count, count)
|
|
|
+ if (
|
|
|
+ res.code === 200 &&
|
|
|
+ res.data &&
|
|
|
+ res.data.rowData &&
|
|
|
+ res.data.rowData.length
|
|
|
+ ) {
|
|
|
+ let chartData = res.data.chartData;
|
|
|
+ let chartDataTotal = res.data.chartData.groupSchoolData; // 联校/年级数据
|
|
|
+ let chartDataSingle = res.data.chartData.oneSchoolData; // 单校/班级数据
|
|
|
|
|
|
- markLine.push({
|
|
|
- name: '平均分',
|
|
|
- value: average,
|
|
|
- color: '#FAC858',
|
|
|
- xAxis: averageIndex,
|
|
|
- });
|
|
|
- markLine.push({
|
|
|
- name: '标准差',
|
|
|
- color: '#3BA272',
|
|
|
- value: standard,
|
|
|
- xAxis: standardIndex,
|
|
|
- });
|
|
|
+ let datax: string[] = []; // 分数段x轴数据
|
|
|
+ let datay: number[][] = []; // 联校/年级分数段y轴数据
|
|
|
+ let count: number[] = []; // 联校/年级数据
|
|
|
+ let tooltipData: TooltipItem[] = []; // 联校/年级悬浮弹窗数据
|
|
|
+ let markLine: MarkLineItem[] = []; // 辅助线数据
|
|
|
|
|
|
- markLine.push({
|
|
|
- name: '-标准差',
|
|
|
- color: '#EE6666',
|
|
|
- value: negativeOneStandard,
|
|
|
- xAxis: negativeOneStandardIndex,
|
|
|
- });
|
|
|
- markLine.push({
|
|
|
- name: '-2倍标准差',
|
|
|
- color: '#EE6666',
|
|
|
- value: negativeTwoStandard,
|
|
|
- xAxis: negativeTwoStandardIndex,
|
|
|
- });
|
|
|
- markLine.push({
|
|
|
- name: '2倍标准差',
|
|
|
- color: '#3BA272',
|
|
|
- value: twoStandard,
|
|
|
- xAxis: twoStandardIndex,
|
|
|
- });
|
|
|
- //判断人数是否可点击
|
|
|
- const rowData = res.data.rowData || [];
|
|
|
- state.scoreSegmentData = {
|
|
|
- datax: datax,
|
|
|
- datay: datay,
|
|
|
- fullScore: parseFloat(chartData.fullScore),
|
|
|
- markLine: markLine,//辅助线数据
|
|
|
- tooltipData: tooltipData,//悬浮弹窗数据
|
|
|
- title: ["年级", "1班"],
|
|
|
- color: ["#995FB3", "#5470C6"],
|
|
|
- unit: '人',
|
|
|
- tooltipTitle: '及格率',
|
|
|
- tableData: rowData,
|
|
|
- headerData: res.data.titleData || [],
|
|
|
- pageSize: 10,//每页显示数据
|
|
|
- total: rowData.length,//总数
|
|
|
- pageNum: 1,//当前页
|
|
|
- };//分数段图数据
|
|
|
-
|
|
|
- // 单校/班级图表数据处理
|
|
|
- chartDataSingle.forEach(item => {
|
|
|
- classTitle.push(item.name)
|
|
|
- let singleItem = []
|
|
|
- let tootlipItem = []
|
|
|
- item.detailList.forEach(scoreItem => {
|
|
|
- singleItem.push(scoreItem.doubleOnlineNum)
|
|
|
- tootlipItem.push(`${scoreItem.doubleOnlineNum}人,占比${scoreItem.doubleOnlineRate}`);
|
|
|
- })
|
|
|
- classDatay.push(singleItem)
|
|
|
- classTooltipData.push(tootlipItem)
|
|
|
- })
|
|
|
- state.scoreSegmentClassData = {
|
|
|
- datax: datax,
|
|
|
- datay: classDatay,
|
|
|
- title: classTitle,//不会修改原数组
|
|
|
- legendList: classTitle.slice(0, 3),//默认显示全部
|
|
|
- tooltipData: classTooltipData,
|
|
|
- };
|
|
|
- } else {
|
|
|
- state.scoreSegmentData = {
|
|
|
- datax: [],
|
|
|
- datay: [],
|
|
|
- fullScore: 100,//满分值
|
|
|
- markLine: [],//辅助线
|
|
|
- tooltipData: [],//悬浮弹窗的数据
|
|
|
- title: ["年级", "年级"],
|
|
|
- color: ["#995FB3", "#5470C6"],
|
|
|
- unit: '人',
|
|
|
- tooltipTitle: '及格率',
|
|
|
- tableData: [],
|
|
|
- headerData: [],
|
|
|
- pageSize: 10,//每页显示数据
|
|
|
- total: 0,//总数
|
|
|
- pageNum: 1,//当前页
|
|
|
- };//分数段图数据
|
|
|
-
|
|
|
- state.scoreSegmentClassData = {
|
|
|
- datax: [],
|
|
|
- datay: [],
|
|
|
- title: [],
|
|
|
- legendList: [],
|
|
|
- tooltipData: [],//悬浮弹窗的数据
|
|
|
- };//分数段 按班级分析数据
|
|
|
+ let classTooltipData: string[][] = []; //班级悬浮弹窗数据
|
|
|
+ let classDatay: number[][] = []; //分数段按班级y轴数据
|
|
|
+ let classTitle: string[] = []; //分数段按班级图例数据
|
|
|
+
|
|
|
+ let average = parseFloat(res.data.chartData.average); // 平均分
|
|
|
+ let averageIndex = 0; //平均分的索引
|
|
|
+ let standard = parseFloat(res.data.chartData.standard); //标准差
|
|
|
+ let standardIndex = 0; //标准差索引
|
|
|
+ let twoStandard = parseFloat(res.data.chartData.twoStandard); //2倍标准差
|
|
|
+ let twoStandardIndex = 0; //2倍标准差索引
|
|
|
+ let negativeOneStandard = parseFloat(
|
|
|
+ res.data.chartData.negativeOneStandard,
|
|
|
+ ); //-1倍标准差
|
|
|
+ let negativeOneStandardIndex = 0; //-1倍标准差索引
|
|
|
+ let negativeTwoStandard = parseFloat(
|
|
|
+ res.data.chartData.negativeTwoStandard,
|
|
|
+ ); //-2倍标准差
|
|
|
+ let negativeTwoStandardIndex = 0; //-2倍标准差索引
|
|
|
+
|
|
|
+ chartDataTotal.forEach((item: ApiChartDataTotalItem, index: number) => {
|
|
|
+ let list = item.name.split("-");
|
|
|
+ let num1 = parseInt(list[0].replace(/[\[\]()]/g, ""));
|
|
|
+ let num2 = parseInt(list[1].replace(/[\[\]()]/g, ""));
|
|
|
+
|
|
|
+ if (num2 < average && average < num1) {
|
|
|
+ // 平均分
|
|
|
+ averageIndex = index;
|
|
|
+ }
|
|
|
+ if (num2 < standard && standard < num1) {
|
|
|
+ // 标准差
|
|
|
+ standardIndex = index;
|
|
|
+ }
|
|
|
+ if (num2 < twoStandard && twoStandard < num1) {
|
|
|
+ // 2倍标准差
|
|
|
+ twoStandardIndex = index;
|
|
|
+ }
|
|
|
+ if (num2 < negativeOneStandard && negativeOneStandard < num1) {
|
|
|
+ // -1倍标准差
|
|
|
+ negativeOneStandardIndex = index;
|
|
|
+ }
|
|
|
+ if (num2 < negativeTwoStandard && negativeTwoStandard < num1) {
|
|
|
+ // -2倍标准差
|
|
|
+ negativeTwoStandardIndex = index;
|
|
|
+ }
|
|
|
+
|
|
|
+ datax.push(item.name);
|
|
|
+ count.push(item.doubleOnlineNum);
|
|
|
+ tooltipData.push({
|
|
|
+ name: "",
|
|
|
+ value: `${item.doubleOnlineNum}人,占比${item.doubleOnlineRate} ${item.studentUserName.length ? `(${item.studentUserName.join("、")})` : ""}`,
|
|
|
+ });
|
|
|
+ });
|
|
|
+ datay.push(count, count);
|
|
|
+
|
|
|
+ markLine.push({
|
|
|
+ name: "平均分",
|
|
|
+ value: average,
|
|
|
+ color: "#FAC858",
|
|
|
+ xAxis: averageIndex,
|
|
|
+ });
|
|
|
+ markLine.push({
|
|
|
+ name: "标准差",
|
|
|
+ color: "#3BA272",
|
|
|
+ value: standard,
|
|
|
+ xAxis: standardIndex,
|
|
|
+ });
|
|
|
+
|
|
|
+ markLine.push({
|
|
|
+ name: "-标准差",
|
|
|
+ color: "#EE6666",
|
|
|
+ value: negativeOneStandard,
|
|
|
+ xAxis: negativeOneStandardIndex,
|
|
|
+ });
|
|
|
+ markLine.push({
|
|
|
+ name: "-2倍标准差",
|
|
|
+ color: "#EE6666",
|
|
|
+ value: negativeTwoStandard,
|
|
|
+ xAxis: negativeTwoStandardIndex,
|
|
|
+ });
|
|
|
+ markLine.push({
|
|
|
+ name: "2倍标准差",
|
|
|
+ color: "#3BA272",
|
|
|
+ value: twoStandard,
|
|
|
+ xAxis: twoStandardIndex,
|
|
|
+ });
|
|
|
+
|
|
|
+ //判断人数是否可点击
|
|
|
+ const rowData = res.data.rowData || [];
|
|
|
+
|
|
|
+ // 修复点:确保赋值对象包含所有必需属性,特别是 exportLoading
|
|
|
+ state.scoreSegmentData = {
|
|
|
+ exportLoading: false,
|
|
|
+ datax: datax,
|
|
|
+ datay: datay,
|
|
|
+ fullScore: parseFloat(chartData.fullScore),
|
|
|
+ markLine: markLine, //辅助线数据
|
|
|
+ tooltipData: tooltipData, //悬浮弹窗数据
|
|
|
+ title: ["年级", "1班"],
|
|
|
+ color: ["#995FB3", "#5470C6"],
|
|
|
+ unit: "人",
|
|
|
+ tooltipTitle: "及格率",
|
|
|
+ tableData: rowData,
|
|
|
+ headerData: res.data.titleData || [],
|
|
|
+ pageSize: 10, //每页显示数据
|
|
|
+ total: rowData.length, //总数
|
|
|
+ pageNum: 1, //当前页
|
|
|
+ }; //分数段图数据
|
|
|
+
|
|
|
+ // 单校/班级图表数据处理
|
|
|
+ chartDataSingle.forEach((item: ApiChartDataSingleItem) => {
|
|
|
+ classTitle.push(item.name);
|
|
|
+ let singleItem: number[] = [];
|
|
|
+ let tootlipItem: string[] = [];
|
|
|
+ item.detailList.forEach((scoreItem) => {
|
|
|
+ singleItem.push(scoreItem.doubleOnlineNum);
|
|
|
+ tootlipItem.push(
|
|
|
+ `${scoreItem.doubleOnlineNum}人,占比${scoreItem.doubleOnlineRate}`,
|
|
|
+ );
|
|
|
+ });
|
|
|
+ classDatay.push(singleItem);
|
|
|
+ classTooltipData.push(tootlipItem);
|
|
|
+ });
|
|
|
+
|
|
|
+ state.scoreSegmentClassData = {
|
|
|
+ datax: datax,
|
|
|
+ datay: classDatay,
|
|
|
+ title: classTitle, //不会修改原数组
|
|
|
+ legendList: classTitle.slice(0, 3), //默认显示全部
|
|
|
+ tooltipData: classTooltipData,
|
|
|
+ };
|
|
|
+ } else {
|
|
|
+ // 修复点:确保赋值对象包含所有必需属性,特别是 exportLoading
|
|
|
+ state.scoreSegmentData = {
|
|
|
+ exportLoading: false,
|
|
|
+ datax: [],
|
|
|
+ datay: [],
|
|
|
+ fullScore: 100, //满分值
|
|
|
+ markLine: [], //辅助线
|
|
|
+ tooltipData: [], //悬浮弹窗的数据
|
|
|
+ title: ["年级", "年级"],
|
|
|
+ color: ["#995FB3", "#5470C6"],
|
|
|
+ unit: "人",
|
|
|
+ tooltipTitle: "及格率",
|
|
|
+ tableData: [],
|
|
|
+ headerData: [],
|
|
|
+ pageSize: 10, //每页显示数据
|
|
|
+ total: 0, //总数
|
|
|
+ pageNum: 1, //当前页
|
|
|
+ }; //分数段图数据
|
|
|
+
|
|
|
+ state.scoreSegmentClassData = {
|
|
|
+ datax: [],
|
|
|
+ datay: [],
|
|
|
+ title: [],
|
|
|
+ legendList: [],
|
|
|
+ tooltipData: [], //悬浮弹窗的数据
|
|
|
+ }; //分数段 按班级分析数据
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error("获取分数段数据失败:", error);
|
|
|
+ } finally {
|
|
|
+ state.tableLoading = false;
|
|
|
}
|
|
|
- state.tableLoading = false;
|
|
|
};
|
|
|
+
|
|
|
//分数段切换
|
|
|
-const TagClick = (item) => {
|
|
|
+const TagClick = (item: string) => {
|
|
|
state.sectionScore = item;
|
|
|
- GetScoreSegment();//获取分数段数据
|
|
|
-}
|
|
|
+ GetScoreSegment(); //获取分数段数据
|
|
|
+};
|
|
|
+
|
|
|
const HandleInput = (value: string) => {
|
|
|
- state.sectionScore = value.replace(/[^\d]/g, '');
|
|
|
-}
|
|
|
+ state.sectionScore = value.replace(/[^\d]/g, "");
|
|
|
+};
|
|
|
+
|
|
|
// 设置分数段失去焦点
|
|
|
const BlurSectionScore = (value: string) => {
|
|
|
- if (value == '0') {
|
|
|
- state.sectionScore = '';
|
|
|
+ if (value == "0") {
|
|
|
+ state.sectionScore = "";
|
|
|
}
|
|
|
- GetScoreSegment();//获取分数段数据
|
|
|
-}
|
|
|
+ GetScoreSegment(); //获取分数段数据
|
|
|
+};
|
|
|
+
|
|
|
// 分页
|
|
|
-const handleCurrentChange = (val: number) => {
|
|
|
+const ChangeCurrentPage = (val: number) => {
|
|
|
state.scoreSegmentData.pageNum = val;
|
|
|
-}
|
|
|
+};
|
|
|
|
|
|
-const handleSizeChange = (val: number) => {
|
|
|
+const ChangePageSize = (val: number) => {
|
|
|
state.scoreSegmentData.pageSize = val;
|
|
|
state.scoreSegmentData.pageNum = 1;
|
|
|
-}
|
|
|
+};
|
|
|
+// 导出Excel
|
|
|
+const ExportExcel = () => {
|
|
|
+ // 1. 设置加载状态
|
|
|
+ reportModuleRef.value?.SetExportLoading?.(true);
|
|
|
+ // 2. 参数
|
|
|
+ const examName = getExamName.value;
|
|
|
+ let staticHeaderData: { label: string; prop: string | undefined; display: boolean; }[] = [],
|
|
|
+ dynamicsHeaderData: { label: string; prop: string | undefined; display: boolean; }[] = [],
|
|
|
+ childHeaderData: { label: string; prop: string; display: boolean; }[] = [],
|
|
|
+ dataList: any[][] = [];
|
|
|
+ state.scoreSegmentData.headerData.forEach((item) => {
|
|
|
+ if (item.child && item.child.length > 0) {
|
|
|
+ dynamicsHeaderData.push({
|
|
|
+ label: item.name,
|
|
|
+ prop: item.prop,
|
|
|
+ display: true,
|
|
|
+ });
|
|
|
+ if (childHeaderData.length == 0) {
|
|
|
+ item.child.forEach((child) => {
|
|
|
+ childHeaderData.push({
|
|
|
+ label: child.value,
|
|
|
+ prop: child.prop,
|
|
|
+ display: true,
|
|
|
+ });
|
|
|
+ });
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ staticHeaderData.push({
|
|
|
+ label: item.name,
|
|
|
+ prop: item.prop,
|
|
|
+ display: true,
|
|
|
+ });
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ state.scoreSegmentData.tableData.forEach((item) => {
|
|
|
+ const rowData: any[] = [];
|
|
|
+ staticHeaderData.forEach((header) => {
|
|
|
+ rowData.push(item[header.prop]);
|
|
|
+ });
|
|
|
+ if (item.detailList && item?.detailList.length > 0) {
|
|
|
+ item.detailList.forEach((detail) => {
|
|
|
+ childHeaderData.forEach((child) => {
|
|
|
+ rowData.push(detail[child.prop]);
|
|
|
+ });
|
|
|
+ });
|
|
|
+ }
|
|
|
+ dataList.push(rowData);
|
|
|
+ });
|
|
|
+ const params = {
|
|
|
+ fileName: `${GetExcelFileName(examName, analysisStore.filterObject, "分数段表")}`,
|
|
|
+ examName: examName, //考试名称
|
|
|
+ sheetName: "分数段表", //sheet页名称
|
|
|
+ staticHeaderData: staticHeaderData, //静态表头
|
|
|
+ dynamicsHeaderData: dynamicsHeaderData, //动态表头的一级表头
|
|
|
+ childHeaderData: childHeaderData, //二级表头
|
|
|
+ dataList: dataList, //数据
|
|
|
+ };
|
|
|
+ // 3. 调用通用下载方法,并在完成后重置加载状态
|
|
|
+ downloadExcel(publicExport, params).finally(() => {
|
|
|
+ reportModuleRef.value?.SetExportLoading?.(false);
|
|
|
+ });
|
|
|
+};
|
|
|
// 初始化
|
|
|
const pageInit = () => {
|
|
|
GetScoreSegment();
|
|
|
};
|
|
|
+
|
|
|
// 监听筛选条件
|
|
|
watch(
|
|
|
() => analysisStore.filterObject,
|
|
|
async () => {
|
|
|
- state.sectionScore = '';
|
|
|
+ state.sectionScore = "";
|
|
|
pageInit();
|
|
|
},
|
|
|
{ deep: true },
|