2025-04-22 10:22:33 +08:00
|
|
|
|
<template>
|
|
|
|
|
|
<BasicLayout :show-nav-bar="true" :nav-bar-props="{ title: '成绩分析' }">
|
2025-07-30 01:02:14 +08:00
|
|
|
|
<view class="cjfx-page">
|
|
|
|
|
|
<!-- 1. 班级选择器 -->
|
|
|
|
|
|
<CjBjPicker v-model="xzbjId" @change="onBjChange" />
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 2. 考试场次选择器 -->
|
|
|
|
|
|
<CjKsccPicker v-model="xzksccId" :bj-id="xzbjId" @change="onKsccChange" />
|
|
|
|
|
|
|
|
|
|
|
|
<view v-if="xzbjId && xzksccId">
|
|
|
|
|
|
<!-- 3. 科目选择Tabs (当选择了考试场次后显示) -->
|
|
|
|
|
|
<view class="km-xz-section" v-if="xzksccId && ksccKmLb.length > 0">
|
|
|
|
|
|
<view class="km-xz-tabs">
|
|
|
|
|
|
<scroll-view
|
|
|
|
|
|
scroll-x="true"
|
|
|
|
|
|
class="scroll-view-h"
|
|
|
|
|
|
:show-scrollbar="false"
|
2025-04-22 10:22:33 +08:00
|
|
|
|
>
|
2025-07-30 01:02:14 +08:00
|
|
|
|
<view
|
|
|
|
|
|
class="tab-item"
|
|
|
|
|
|
v-for="km in ksccKmLb"
|
|
|
|
|
|
:key="km.id"
|
|
|
|
|
|
:class="{ active: xzkmId === km.id }"
|
|
|
|
|
|
@click="selectKm(km.id)"
|
2025-04-22 10:22:33 +08:00
|
|
|
|
>
|
2025-07-30 01:02:14 +08:00
|
|
|
|
<text>{{ km.kmmc }}</text>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</scroll-view>
|
2025-04-22 10:22:33 +08:00
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
2025-07-30 01:02:14 +08:00
|
|
|
|
|
|
|
|
|
|
<view v-if="xzkmId">
|
|
|
|
|
|
<!-- 4. 本班学生成绩列表 -->
|
|
|
|
|
|
<CjBjXsList :xs-cj-lb="xsCjLb" />
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 5. 班级统计概览 -->
|
|
|
|
|
|
<CjBjTjGl :tjgl-data="tjglData" />
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 6. 本班与年级平均分对比分析图表 -->
|
2025-07-30 13:45:52 +08:00
|
|
|
|
<CjBjNjPjf
|
|
|
|
|
|
v-model:selected-km-id="xzkmId"
|
2025-08-23 01:35:23 +08:00
|
|
|
|
:bj-pj-list="bjFxList"
|
2025-07-30 13:45:52 +08:00
|
|
|
|
:current-bj-id="xzbjId"
|
|
|
|
|
|
/>
|
2025-07-30 01:02:14 +08:00
|
|
|
|
|
|
|
|
|
|
<!-- 7. 总分分数段图表 -->
|
2025-07-30 12:53:33 +08:00
|
|
|
|
<CjZfFs :dj-rs-list="djRsList" />
|
2025-07-30 01:02:14 +08:00
|
|
|
|
|
|
|
|
|
|
<!-- 8. 总分等级段图表 -->
|
2025-07-30 12:53:33 +08:00
|
|
|
|
<CjZfDj :dj-rs-list="djRsList" />
|
2025-04-22 10:22:33 +08:00
|
|
|
|
</view>
|
2025-07-30 01:02:14 +08:00
|
|
|
|
</view>
|
2025-04-22 10:22:33 +08:00
|
|
|
|
</view>
|
|
|
|
|
|
</BasicLayout>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<script lang="ts" setup>
|
2025-07-30 01:02:14 +08:00
|
|
|
|
import { jsdBjKscjKmApi, ksccKmFindByKsccIdApi } from "@/api/base/server";
|
|
|
|
|
|
import { onMounted, ref } from "vue";
|
|
|
|
|
|
import CjBjNjPjf from "./components/CjBjNjPjf.vue";
|
|
|
|
|
|
import CjBjPicker from "./components/CjBjPicker.vue";
|
|
|
|
|
|
import CjBjTjGl from "./components/CjBjTjGl.vue";
|
|
|
|
|
|
import CjBjXsList from "./components/CjBjXsList.vue";
|
|
|
|
|
|
import CjKsccPicker from "./components/CjKsccPicker.vue";
|
|
|
|
|
|
import CjZfDj from "./components/CjZfDj.vue";
|
|
|
|
|
|
import CjZfFs from "./components/CjZfFs.vue";
|
|
|
|
|
|
|
|
|
|
|
|
// --- Emits ---
|
|
|
|
|
|
const emit = defineEmits(["kmDataChange"]);
|
2025-04-22 10:22:33 +08:00
|
|
|
|
|
|
|
|
|
|
// --- Interfaces ---
|
2025-07-30 01:02:14 +08:00
|
|
|
|
interface KsccKm {
|
|
|
|
|
|
id: string | number;
|
|
|
|
|
|
kmmc: string;
|
|
|
|
|
|
kmfs: number;
|
|
|
|
|
|
}
|
|
|
|
|
|
interface XsCj {
|
|
|
|
|
|
id: string | number;
|
|
|
|
|
|
name: string;
|
|
|
|
|
|
score: number;
|
|
|
|
|
|
bjpm: number;
|
|
|
|
|
|
}
|
|
|
|
|
|
interface TjglData {
|
|
|
|
|
|
bjrs: number;
|
|
|
|
|
|
njpjzf: number;
|
|
|
|
|
|
bjpjf: number;
|
|
|
|
|
|
njzgf: number;
|
|
|
|
|
|
ysl: number;
|
|
|
|
|
|
bjzgf: number;
|
|
|
|
|
|
ysrs: number;
|
|
|
|
|
|
jgl: number;
|
|
|
|
|
|
jgrs: number;
|
|
|
|
|
|
}
|
|
|
|
|
|
interface FsDuan {
|
|
|
|
|
|
name: string;
|
|
|
|
|
|
value: number;
|
|
|
|
|
|
color: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
interface DjDuan {
|
|
|
|
|
|
name: string;
|
|
|
|
|
|
value: number;
|
|
|
|
|
|
color: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
interface BjPjInfo {
|
|
|
|
|
|
bjId: string;
|
|
|
|
|
|
bjmc: string;
|
|
|
|
|
|
njmc: string;
|
|
|
|
|
|
fs: number;
|
|
|
|
|
|
}
|
2025-07-30 12:53:33 +08:00
|
|
|
|
interface DjRsInfo {
|
|
|
|
|
|
dj: string;
|
|
|
|
|
|
rs: number;
|
|
|
|
|
|
zgf: string;
|
|
|
|
|
|
zdf: string;
|
|
|
|
|
|
}
|
2025-04-22 10:22:33 +08:00
|
|
|
|
|
|
|
|
|
|
// --- Static Mock Data ---
|
2025-07-30 12:53:33 +08:00
|
|
|
|
// 这些静态数据现在不再需要,因为组件直接使用 djRsList
|
2025-04-22 10:22:33 +08:00
|
|
|
|
|
|
|
|
|
|
// --- State ---
|
2025-07-30 01:02:14 +08:00
|
|
|
|
const xzbjId = ref<string | number | null>(null);
|
|
|
|
|
|
const xzksccId = ref<string | number | null>(null);
|
|
|
|
|
|
const xzkmId = ref<string | number | null>(null);
|
|
|
|
|
|
const ksccKmLb = ref<KsccKm[]>([]);
|
|
|
|
|
|
const xsCjLb = ref<XsCj[]>([]);
|
|
|
|
|
|
const tjglData = ref<TjglData>({
|
|
|
|
|
|
bjrs: 0,
|
|
|
|
|
|
njpjzf: 0,
|
|
|
|
|
|
bjpjf: 0,
|
|
|
|
|
|
njzgf: 0,
|
|
|
|
|
|
ysl: 0,
|
|
|
|
|
|
bjzgf: 0,
|
|
|
|
|
|
ysrs: 0,
|
|
|
|
|
|
jgl: 0,
|
|
|
|
|
|
jgrs: 0,
|
|
|
|
|
|
});
|
2025-08-23 01:35:23 +08:00
|
|
|
|
const bjFxList = ref<BjPjInfo[]>([]);
|
2025-07-30 12:53:33 +08:00
|
|
|
|
const djRsList = ref<DjRsInfo[]>([]);
|
2025-07-30 01:02:14 +08:00
|
|
|
|
|
|
|
|
|
|
// --- API Functions ---
|
|
|
|
|
|
const getKsccKmLb = async (ksccId: string | number) => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const res = await ksccKmFindByKsccIdApi({
|
|
|
|
|
|
ksccId,
|
|
|
|
|
|
});
|
|
|
|
|
|
if (res && res.resultCode === 1 && res.result) {
|
|
|
|
|
|
ksccKmLb.value = res.result.map((km: any) => ({
|
|
|
|
|
|
id: km.id,
|
|
|
|
|
|
kmmc: km.kmmc,
|
|
|
|
|
|
kmfs: 100, // 默认分数,实际应该从后端获取
|
|
|
|
|
|
}));
|
|
|
|
|
|
console.log("获取科目列表成功:", ksccKmLb.value);
|
|
|
|
|
|
|
|
|
|
|
|
// 默认选择第一个科目
|
|
|
|
|
|
if (ksccKmLb.value.length > 0) {
|
|
|
|
|
|
xzkmId.value = ksccKmLb.value[0].id;
|
|
|
|
|
|
// 获取该科目的成绩数据
|
|
|
|
|
|
await getBjKscjKmData(xzbjId.value!, ksccId, ksccKmLb.value[0].id);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error("获取科目列表失败:", error);
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
2025-04-22 10:22:33 +08:00
|
|
|
|
|
2025-07-30 01:02:14 +08:00
|
|
|
|
const getBjKscjKmData = async (
|
|
|
|
|
|
bjId: string | number,
|
|
|
|
|
|
ksccId: string | number,
|
|
|
|
|
|
kmId: string | number
|
|
|
|
|
|
) => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const res = await jsdBjKscjKmApi({ bjId, ksccId, kmId });
|
|
|
|
|
|
if (res && res.resultCode === 1 && res.result) {
|
|
|
|
|
|
// 处理学生成绩数据
|
|
|
|
|
|
const ksccKscjList = res.result.ksccKscjList || [];
|
|
|
|
|
|
|
|
|
|
|
|
xsCjLb.value = ksccKscjList.map((cj: any) => ({
|
|
|
|
|
|
id: cj.xsId,
|
|
|
|
|
|
name: cj.xsxm || cj.xm || "未知学生",
|
|
|
|
|
|
score: cj.ksfs || 0,
|
|
|
|
|
|
bjpm: cj.bjpm || 0, // 使用后端返回的班级排名
|
|
|
|
|
|
}));
|
2025-07-30 12:53:33 +08:00
|
|
|
|
console.log("学生成绩列表:", xsCjLb.value);
|
2025-07-30 01:02:14 +08:00
|
|
|
|
|
|
|
|
|
|
// 处理统计数据
|
|
|
|
|
|
tjglData.value.bjrs = res.result.bjRs || 0;
|
|
|
|
|
|
tjglData.value.bjpjf = res.result.bjPjf || 0;
|
|
|
|
|
|
tjglData.value.njpjzf = res.result.njPjf || 0;
|
|
|
|
|
|
tjglData.value.bjzgf = res.result.bjZgf || 0;
|
|
|
|
|
|
tjglData.value.njzgf = res.result.njZgf || 0;
|
|
|
|
|
|
tjglData.value.ysrs = res.result.ysRs || 0;
|
|
|
|
|
|
tjglData.value.jgrs = res.result.jgRs || 0;
|
|
|
|
|
|
tjglData.value.ysl = res.result.ysl || 0;
|
|
|
|
|
|
tjglData.value.jgl = res.result.jgl || 0;
|
|
|
|
|
|
|
|
|
|
|
|
// 处理班级平均分列表数据,传递给CjBjNjPjf组件
|
2025-08-23 01:35:23 +08:00
|
|
|
|
bjFxList.value = res.result.bjFxList || [];
|
2025-07-30 12:53:33 +08:00
|
|
|
|
|
|
|
|
|
|
// 处理等级段数据列表,传递给CjZfFs和CjZfDj组件
|
|
|
|
|
|
djRsList.value = res.result.djRsList || [];
|
|
|
|
|
|
|
2025-07-30 01:02:14 +08:00
|
|
|
|
console.log("获取班级科目成绩数据成功:", res.result);
|
2025-07-30 12:53:33 +08:00
|
|
|
|
console.log("等级人数列表:", djRsList.value);
|
2025-07-30 01:02:14 +08:00
|
|
|
|
}
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error("获取班级科目成绩数据失败:", error);
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
2025-04-22 10:22:33 +08:00
|
|
|
|
|
|
|
|
|
|
// --- Event Handlers ---
|
2025-07-30 01:02:14 +08:00
|
|
|
|
const onBjChange = async (bjId: string | number | null, bjInfo: any | null) => {
|
|
|
|
|
|
xzbjId.value = bjId;
|
|
|
|
|
|
xzksccId.value = null; // 重置考试场次选择
|
|
|
|
|
|
xzkmId.value = null; // 重置科目选择
|
|
|
|
|
|
ksccKmLb.value = []; // 清空科目列表
|
2025-04-22 10:22:33 +08:00
|
|
|
|
|
2025-07-30 01:02:14 +08:00
|
|
|
|
console.log(`切换到班级: ${bjId}, 班级名称: ${bjInfo?.name}`);
|
2025-04-22 10:22:33 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
2025-07-30 01:02:14 +08:00
|
|
|
|
const onKsccChange = async (
|
|
|
|
|
|
ksccId: string | number | null,
|
|
|
|
|
|
ksccInfo: any | null
|
2025-04-22 10:22:33 +08:00
|
|
|
|
) => {
|
2025-07-30 01:02:14 +08:00
|
|
|
|
xzksccId.value = ksccId;
|
|
|
|
|
|
xzkmId.value = null; // 重置科目选择
|
2025-04-22 10:22:33 +08:00
|
|
|
|
|
2025-07-30 01:02:14 +08:00
|
|
|
|
console.log(`切换到考试场次: ${ksccId}, 考试名称: ${ksccInfo?.name}`);
|
2025-04-22 10:22:33 +08:00
|
|
|
|
|
2025-07-30 01:02:14 +08:00
|
|
|
|
if (ksccId) {
|
|
|
|
|
|
// 获取该考试场次的科目列表
|
|
|
|
|
|
await getKsccKmLb(ksccId);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
ksccKmLb.value = [];
|
2025-04-22 10:22:33 +08:00
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2025-07-30 01:02:14 +08:00
|
|
|
|
const selectKm = async (kmId: string | number) => {
|
|
|
|
|
|
xzkmId.value = kmId;
|
|
|
|
|
|
console.log(`选中科目: ${kmId}`);
|
|
|
|
|
|
|
|
|
|
|
|
// 获取该科目的详细数据
|
|
|
|
|
|
if (xzbjId.value && xzksccId.value) {
|
|
|
|
|
|
await getBjKscjKmData(xzbjId.value, xzksccId.value, kmId);
|
2025-04-22 10:22:33 +08:00
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2025-07-30 01:02:14 +08:00
|
|
|
|
const onKmChange = (kmId: string | number | null, km: any) => {
|
|
|
|
|
|
console.log(`科目变化: ${kmId}, 科目信息:`, km);
|
|
|
|
|
|
// 处理科目变化逻辑
|
2025-04-22 10:22:33 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
2025-07-30 01:02:14 +08:00
|
|
|
|
// --- Lifecycle ---
|
|
|
|
|
|
onMounted(async () => {
|
|
|
|
|
|
console.log("成绩分析页面初始化完成");
|
2025-04-22 10:22:33 +08:00
|
|
|
|
});
|
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<style scoped lang="scss">
|
2025-07-30 01:02:14 +08:00
|
|
|
|
.cjfx-page {
|
2025-04-22 10:22:33 +08:00
|
|
|
|
background-color: #f4f5f7;
|
|
|
|
|
|
min-height: calc(100vh - var(--window-top));
|
|
|
|
|
|
padding-bottom: 20rpx;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-07-30 01:02:14 +08:00
|
|
|
|
.km-xz-section {
|
2025-04-22 10:22:33 +08:00
|
|
|
|
background-color: #ffffff;
|
2025-07-30 01:02:14 +08:00
|
|
|
|
padding: 0;
|
2025-04-22 10:22:33 +08:00
|
|
|
|
|
2025-07-30 01:02:14 +08:00
|
|
|
|
.km-xz-tabs {
|
|
|
|
|
|
padding: 0;
|
2025-04-22 10:22:33 +08:00
|
|
|
|
|
2025-07-30 01:02:14 +08:00
|
|
|
|
.scroll-view-h {
|
|
|
|
|
|
white-space: nowrap;
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
height: 80rpx;
|
|
|
|
|
|
line-height: 80rpx;
|
|
|
|
|
|
border-bottom: 1rpx solid #f0f0f0;
|
2025-04-22 10:22:33 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-07-30 01:02:14 +08:00
|
|
|
|
.tab-item {
|
|
|
|
|
|
display: inline-block;
|
|
|
|
|
|
padding: 0 30rpx;
|
|
|
|
|
|
font-size: 28rpx;
|
|
|
|
|
|
color: #666;
|
|
|
|
|
|
position: relative;
|
2025-04-22 10:22:33 +08:00
|
|
|
|
text-align: center;
|
2025-07-30 01:02:14 +08:00
|
|
|
|
height: 100%;
|
|
|
|
|
|
|
|
|
|
|
|
&.active {
|
|
|
|
|
|
color: #447ade;
|
|
|
|
|
|
font-weight: bold;
|
|
|
|
|
|
|
|
|
|
|
|
&::after {
|
|
|
|
|
|
content: "";
|
|
|
|
|
|
position: absolute;
|
|
|
|
|
|
bottom: 0;
|
|
|
|
|
|
left: 50%;
|
|
|
|
|
|
transform: translateX(-50%);
|
|
|
|
|
|
width: 40%;
|
|
|
|
|
|
height: 6rpx;
|
|
|
|
|
|
background-color: #447ade;
|
|
|
|
|
|
border-radius: 3rpx;
|
2025-04-22 10:22:33 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-07-30 12:53:33 +08:00
|
|
|
|
|
|
|
|
|
|
.debug-info {
|
|
|
|
|
|
background-color: #f0f0f0;
|
|
|
|
|
|
padding: 20rpx;
|
|
|
|
|
|
margin-top: 20rpx;
|
|
|
|
|
|
border-radius: 10rpx;
|
|
|
|
|
|
|
|
|
|
|
|
.debug-title {
|
|
|
|
|
|
font-size: 32rpx;
|
|
|
|
|
|
font-weight: bold;
|
|
|
|
|
|
margin-bottom: 10rpx;
|
|
|
|
|
|
text-align: center;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.debug-item {
|
|
|
|
|
|
font-size: 28rpx;
|
|
|
|
|
|
color: #333;
|
|
|
|
|
|
margin-bottom: 5rpx;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-04-22 10:22:33 +08:00
|
|
|
|
</style>
|