fix: 修复一些问题
This commit is contained in:
parent
3f826a1a67
commit
9badd93bfe
@ -140,6 +140,7 @@ export const getPaperRecordDetail = (data: { recordId: string; reportFlag: numbe
|
|||||||
|
|
||||||
export interface JobReportVo {
|
export interface JobReportVo {
|
||||||
actualScore?: number;
|
actualScore?: number;
|
||||||
|
markScore?: number;
|
||||||
beginTime?: string;
|
beginTime?: string;
|
||||||
costTime?: number;
|
costTime?: number;
|
||||||
jobId?: number;
|
jobId?: number;
|
||||||
@ -179,17 +180,24 @@ export interface JobAnalyseVo {
|
|||||||
stuRankVoList?: StuRankVoItem[];
|
stuRankVoList?: StuRankVoItem[];
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 作业整体分析 */
|
/** 作业整体分析(家长端查看孩子报告时需传 userId) */
|
||||||
export const getJobAnalyse = (params: { recordId: string }) =>
|
export const getJobAnalyse = (params: { recordId: string; userId?: string }) =>
|
||||||
request<JobAnalyseVo>({
|
request<JobAnalyseVo>({
|
||||||
url: '/sc/job/getJobAnalyse',
|
url: '/sc/job/getJobAnalyse',
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
params,
|
params,
|
||||||
});
|
});
|
||||||
|
|
||||||
/** 知识点分析 */
|
export interface KnowledgeAnalyzeItem {
|
||||||
export const getKnowledgeAnalyse = (params: { recordId: string }) =>
|
knowledgeId?: string;
|
||||||
request<any[]>({
|
knowledgeName?: string;
|
||||||
|
answerScore?: number;
|
||||||
|
titleScore?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 知识点分析(家长端查看孩子报告时需传 userId) */
|
||||||
|
export const getKnowledgeAnalyse = (params: { recordId: string; userId?: string }) =>
|
||||||
|
request<KnowledgeAnalyzeItem[]>({
|
||||||
url: '/sc/job/statisticalKnowledge',
|
url: '/sc/job/statisticalKnowledge',
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
params,
|
params,
|
||||||
|
|||||||
@ -80,6 +80,7 @@ export const parentSmsLogin = (data: ParentSmsLoginParams) =>
|
|||||||
data: {
|
data: {
|
||||||
bindMaOpenid: true,
|
bindMaOpenid: true,
|
||||||
adminType: 16,
|
adminType: 16,
|
||||||
|
clientType: 'PARENT',
|
||||||
phone: data.phone,
|
phone: data.phone,
|
||||||
verifyCode: data.verifyCode,
|
verifyCode: data.verifyCode,
|
||||||
wxUser: {
|
wxUser: {
|
||||||
|
|||||||
@ -109,7 +109,7 @@ export interface KpointScoreItem {
|
|||||||
kpointId?: number;
|
kpointId?: number;
|
||||||
kpointName?: string;
|
kpointName?: string;
|
||||||
actualScore?: number;
|
actualScore?: number;
|
||||||
fullscore?: number;
|
fullScore?: number;
|
||||||
scoreRate?: number;
|
scoreRate?: number;
|
||||||
scoreRatePercent?: number;
|
scoreRatePercent?: number;
|
||||||
levelCode?: number;
|
levelCode?: number;
|
||||||
|
|||||||
@ -240,7 +240,7 @@ const levelKnowledge = computed<ScoreViewItem[]>(() => {
|
|||||||
return (level?.items || []).map((item) => ({
|
return (level?.items || []).map((item) => ({
|
||||||
kpointId: item.kpointId ?? 0,
|
kpointId: item.kpointId ?? 0,
|
||||||
kpointName: item.kpointName || '--',
|
kpointName: item.kpointName || '--',
|
||||||
fullScore: item.fullscore ?? 0,
|
fullScore: item.fullScore ?? 0,
|
||||||
score: item.actualScore ?? 0,
|
score: item.actualScore ?? 0,
|
||||||
scoreRatePercent: item.scoreRatePercent ?? 0,
|
scoreRatePercent: item.scoreRatePercent ?? 0,
|
||||||
}));
|
}));
|
||||||
|
|||||||
@ -43,24 +43,100 @@
|
|||||||
|
|
||||||
<template v-if="activeTab === 'analysis'">
|
<template v-if="activeTab === 'analysis'">
|
||||||
<ParentCard class="section-card" title="题型分布" subtitle="按题型拆解本次卷面构成">
|
<ParentCard class="section-card" title="题型分布" subtitle="按题型拆解本次卷面构成">
|
||||||
<view v-if="pageLoading && !analysisRows.length" class="answers-loading">
|
<view v-if="pageLoading && !channelAnalyzeList.length" class="answers-loading">
|
||||||
<xy-Empty type="loading" content="加载分析数据..." />
|
<xy-Empty type="loading" content="加载分析数据..." />
|
||||||
</view>
|
</view>
|
||||||
<view v-else-if="analysisRows.length" class="data-table">
|
<view v-else-if="channelAnalyzeList.length" class="chart-block">
|
||||||
<view class="table-row table-head">
|
<view class="pie-row">
|
||||||
<text>题型</text>
|
<view class="pie">
|
||||||
<text>题量</text>
|
<view class="pie-circle" :style="channelPieStyle"></view>
|
||||||
<text>占比</text>
|
<view class="pie-hole">
|
||||||
|
<text class="pie-hole-num">{{ totalQuestionCount }}</text>
|
||||||
|
<text class="pie-hole-label">题</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="pie-legend">
|
||||||
|
<view
|
||||||
|
v-for="(item, idx) in channelAnalyzeList"
|
||||||
|
:key="idx"
|
||||||
|
class="legend-item"
|
||||||
|
>
|
||||||
|
<view
|
||||||
|
class="legend-dot"
|
||||||
|
:style="{ background: channelPieColors[idx % channelPieColors.length] }"
|
||||||
|
></view>
|
||||||
|
<text class="legend-name">{{ item.chanelTypeName }}</text>
|
||||||
|
<text class="legend-count">{{ item.titleCount }}题</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<view v-for="row in analysisRows" :key="row.type" class="table-row channel-row">
|
<view class="data-table chart-table">
|
||||||
<text>{{ row.type }}</text>
|
<view class="table-row table-head channel-row">
|
||||||
<text class="strong">{{ row.count }}</text>
|
<text>题型</text>
|
||||||
<text class="rate-good">{{ row.rate }}</text>
|
<text>题量</text>
|
||||||
|
<text>占比</text>
|
||||||
|
</view>
|
||||||
|
<view v-for="row in analysisRows" :key="row.type" class="table-row channel-row">
|
||||||
|
<text>{{ row.type }}</text>
|
||||||
|
<text class="strong">{{ row.count }}</text>
|
||||||
|
<text class="rate-good">{{ row.rate }}</text>
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<xy-Empty v-else type="task" content="暂无题型分析数据" />
|
<xy-Empty v-else type="task" content="暂无题型分析数据" />
|
||||||
</ParentCard>
|
</ParentCard>
|
||||||
|
|
||||||
|
<ParentCard class="section-card" title="难度分布" subtitle="各难度档位题量占比">
|
||||||
|
<view v-if="pageLoading && !difficultyAnalyzeList.length" class="answers-loading">
|
||||||
|
<xy-Empty type="loading" content="加载分析数据..." />
|
||||||
|
</view>
|
||||||
|
<view v-else-if="difficultyAnalyzeList.length" class="chart-block">
|
||||||
|
<view class="pie-row">
|
||||||
|
<view class="pie">
|
||||||
|
<view class="pie-circle" :style="difficultyPieStyle"></view>
|
||||||
|
<view class="pie-hole">
|
||||||
|
<text class="pie-hole-num">{{ totalDifficultyCount }}</text>
|
||||||
|
<text class="pie-hole-label">题</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="pie-legend">
|
||||||
|
<view
|
||||||
|
v-for="(item, idx) in difficultyAnalyzeList"
|
||||||
|
:key="idx"
|
||||||
|
class="legend-item"
|
||||||
|
>
|
||||||
|
<view
|
||||||
|
class="legend-dot"
|
||||||
|
:style="{ background: getDifficultyColor(item.difficultyName) }"
|
||||||
|
></view>
|
||||||
|
<text class="legend-name">{{ item.difficultyName }}</text>
|
||||||
|
<text class="legend-count">{{ item.titleCount }}题</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="bars">
|
||||||
|
<view
|
||||||
|
v-for="(item, idx) in difficultyAnalyzeList"
|
||||||
|
:key="idx"
|
||||||
|
class="bar-row"
|
||||||
|
>
|
||||||
|
<view class="bar-name">{{ item.difficultyName }}</view>
|
||||||
|
<view class="bar-track">
|
||||||
|
<view
|
||||||
|
class="bar-fill"
|
||||||
|
:style="{
|
||||||
|
width: getDifficultyBarWidth(item.score) + '%',
|
||||||
|
background: getDifficultyColor(item.difficultyName),
|
||||||
|
}"
|
||||||
|
></view>
|
||||||
|
</view>
|
||||||
|
<view class="bar-value">{{ item.score }}<text class="bar-value-unit">分</text></view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<xy-Empty v-else type="task" content="暂无难度分析数据" />
|
||||||
|
</ParentCard>
|
||||||
|
|
||||||
<ParentCard class="section-card" title="难度得分" subtitle="关注中难题稳定性,决定后续练习重点">
|
<ParentCard class="section-card" title="难度得分" subtitle="关注中难题稳定性,决定后续练习重点">
|
||||||
<view v-if="pageLoading && !difficultyRows.length" class="answers-loading">
|
<view v-if="pageLoading && !difficultyRows.length" class="answers-loading">
|
||||||
<xy-Empty type="loading" content="加载分析数据..." />
|
<xy-Empty type="loading" content="加载分析数据..." />
|
||||||
@ -79,7 +155,7 @@
|
|||||||
<text :class="row.rateValue >= 80 ? 'rate-good' : 'rate-warn'">{{ row.rate }}</text>
|
<text :class="row.rateValue >= 80 ? 'rate-good' : 'rate-warn'">{{ row.rate }}</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<xy-Empty v-else type="task" content="暂无难度分析数据" />
|
<xy-Empty v-else type="task" content="暂无难度得分数据" />
|
||||||
</ParentCard>
|
</ParentCard>
|
||||||
|
|
||||||
<ParentCard
|
<ParentCard
|
||||||
@ -247,13 +323,21 @@
|
|||||||
|
|
||||||
<template v-if="activeTab === 'knowledge'">
|
<template v-if="activeTab === 'knowledge'">
|
||||||
<ParentCard class="section-card" title="错题知识点占比" subtitle="用于判断最近应优先补强的知识点">
|
<ParentCard class="section-card" title="错题知识点占比" subtitle="用于判断最近应优先补强的知识点">
|
||||||
<view class="knowledge-summary">
|
<view v-if="pageLoading && !knowledgeList.length" class="answers-loading">
|
||||||
|
<xy-Empty type="loading" content="加载知识点..." />
|
||||||
|
</view>
|
||||||
|
<view v-else-if="knowledgeRatios.length" class="knowledge-summary">
|
||||||
<view class="wrong-total">
|
<view class="wrong-total">
|
||||||
<text class="wrong-number">{{ wrongCount }}</text>
|
<text class="wrong-number">{{ knowledgeWeakCount }}</text>
|
||||||
<text class="wrong-label">错题数</text>
|
<text class="wrong-label">薄弱知识点</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="ratio-list">
|
<scroll-view
|
||||||
<view v-for="item in detail.knowledgeRatios" :key="item.name" class="ratio-row">
|
scroll-y
|
||||||
|
class="ratio-list"
|
||||||
|
:style="{ height: ratioListHeight }"
|
||||||
|
:show-scrollbar="false"
|
||||||
|
>
|
||||||
|
<view v-for="item in knowledgeRatios" :key="item.name" class="ratio-row">
|
||||||
<view class="ratio-meta">
|
<view class="ratio-meta">
|
||||||
<text>{{ item.name }}</text>
|
<text>{{ item.name }}</text>
|
||||||
<text>{{ item.percent }}%</text>
|
<text>{{ item.percent }}%</text>
|
||||||
@ -262,11 +346,16 @@
|
|||||||
<view class="ratio-fill" :class="item.tone" :style="{ width: `${item.percent}%` }"></view>
|
<view class="ratio-fill" :class="item.tone" :style="{ width: `${item.percent}%` }"></view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</scroll-view>
|
||||||
</view>
|
</view>
|
||||||
|
<xy-Empty v-else type="learn" content="暂无知识点占比数据" />
|
||||||
</ParentCard>
|
</ParentCard>
|
||||||
|
|
||||||
<ParentCard class="section-card" title="知识点得分">
|
<ParentCard class="section-card" title="知识点得分">
|
||||||
|
<view v-if="pageLoading && !knowledgeList.length" class="answers-loading">
|
||||||
|
<xy-Empty type="loading" content="加载知识点..." />
|
||||||
|
</view>
|
||||||
|
<template v-else-if="knowledgeList.length">
|
||||||
<view class="level-tabs">
|
<view class="level-tabs">
|
||||||
<view
|
<view
|
||||||
v-for="level in knowledgeTabs"
|
v-for="level in knowledgeTabs"
|
||||||
@ -287,10 +376,12 @@
|
|||||||
<view v-for="item in filteredKnowledgeScores" :key="item.name" class="table-row knowledge-row">
|
<view v-for="item in filteredKnowledgeScores" :key="item.name" class="table-row knowledge-row">
|
||||||
<text class="knowledge-name">{{ item.name }}</text>
|
<text class="knowledge-name">{{ item.name }}</text>
|
||||||
<text>{{ item.fullScore }}</text>
|
<text>{{ item.fullScore }}</text>
|
||||||
<text class="score-danger">{{ item.score }}[{{ knowledgeRate(item) }}%]</text>
|
<text :class="knowledgeScoreClass(item)">{{ item.score }}[{{ knowledgeRate(item) }}%]</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<xy-Empty v-if="!filteredKnowledgeScores.length" type="learn" content="当前分段暂无知识点" />
|
<xy-Empty v-if="!filteredKnowledgeScores.length" type="learn" content="当前分段暂无知识点" />
|
||||||
|
</template>
|
||||||
|
<xy-Empty v-else type="learn" content="暂无知识点数据" />
|
||||||
</ParentCard>
|
</ParentCard>
|
||||||
</template>
|
</template>
|
||||||
</view>
|
</view>
|
||||||
@ -308,7 +399,9 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, ref } from 'vue';
|
import { computed, ref } from 'vue';
|
||||||
import { onLoad } from '@dcloudio/uni-app';
|
import { onLoad } from '@dcloudio/uni-app';
|
||||||
import { getJobAnalyse, getPaperRecordDetail, type JobAnalyseVo } from '@/api/homework';
|
import { getJobAnalyse, getKnowledgeAnalyse, getPaperRecordDetail, type JobAnalyseVo, type KnowledgeAnalyzeItem } from '@/api/homework';
|
||||||
|
import { useParentStore } from '@/state/modules/parent';
|
||||||
|
import { storeToRefs } from 'pinia';
|
||||||
import ParentCard from '../components/ParentCard.vue';
|
import ParentCard from '../components/ParentCard.vue';
|
||||||
import ParentPage from '../components/ParentPage.vue';
|
import ParentPage from '../components/ParentPage.vue';
|
||||||
import {
|
import {
|
||||||
@ -317,6 +410,7 @@ import {
|
|||||||
type ReportQuestion,
|
type ReportQuestion,
|
||||||
type ReportQuestionSummary,
|
type ReportQuestionSummary,
|
||||||
type ReportScoreBlock,
|
type ReportScoreBlock,
|
||||||
|
type ReportKnowledgeRatio,
|
||||||
} from '../utils/data';
|
} from '../utils/data';
|
||||||
import {
|
import {
|
||||||
mapPaperQuestion,
|
mapPaperQuestion,
|
||||||
@ -342,7 +436,23 @@ const knowledgeTabs: { key: KnowledgeLevel; label: string }[] = [
|
|||||||
|
|
||||||
const REPORT_FLAG = 1;
|
const REPORT_FLAG = 1;
|
||||||
|
|
||||||
|
const parentStore = useParentStore();
|
||||||
|
const { activeChild } = storeToRefs(parentStore);
|
||||||
|
|
||||||
|
const channelPieColors = ['#2563eb', '#22c55e', '#f59e0b', '#ef4444', '#0ea5e9', '#a855f7', '#ec4899'];
|
||||||
|
const difficultyBaseColorMap: Record<string, string> = {
|
||||||
|
容易: '#22c55e',
|
||||||
|
较易: '#0ea5e9',
|
||||||
|
一般: '#f59e0b',
|
||||||
|
普通: '#f59e0b',
|
||||||
|
中等: '#f59e0b',
|
||||||
|
较难: '#f97316',
|
||||||
|
困难: '#ef4444',
|
||||||
|
};
|
||||||
|
const difficultyFallbackColors = ['#6366f1', '#14b8a6', '#f472b6', '#84cc16'];
|
||||||
|
|
||||||
const recordId = ref('');
|
const recordId = ref('');
|
||||||
|
const childUserId = ref('');
|
||||||
const reportName = ref('');
|
const reportName = ref('');
|
||||||
const reportSubject = ref('');
|
const reportSubject = ref('');
|
||||||
const reportCategory = ref('');
|
const reportCategory = ref('');
|
||||||
@ -360,9 +470,11 @@ const questionSummaries = ref<ReportQuestionSummary[]>([]);
|
|||||||
const questionDetailCache = ref<Record<number, ReportQuestion>>({});
|
const questionDetailCache = ref<Record<number, ReportQuestion>>({});
|
||||||
const paperDetail = ref<Record<string, unknown>>({});
|
const paperDetail = ref<Record<string, unknown>>({});
|
||||||
const jobAnalyse = ref<JobAnalyseVo>({});
|
const jobAnalyse = ref<JobAnalyseVo>({});
|
||||||
|
const knowledgeList = ref<KnowledgeAnalyzeItem[]>([]);
|
||||||
|
|
||||||
onLoad((query) => {
|
onLoad((query) => {
|
||||||
recordId.value = String(query?.id || '');
|
recordId.value = String(query?.id || '');
|
||||||
|
childUserId.value = String(query?.userId || '');
|
||||||
reportName.value = query?.name ? decodeURIComponent(String(query.name)) : '';
|
reportName.value = query?.name ? decodeURIComponent(String(query.name)) : '';
|
||||||
reportSubject.value = query?.subject ? decodeURIComponent(String(query.subject)) : '';
|
reportSubject.value = query?.subject ? decodeURIComponent(String(query.subject)) : '';
|
||||||
reportCategory.value = query?.category ? decodeURIComponent(String(query.category)) : '';
|
reportCategory.value = query?.category ? decodeURIComponent(String(query.category)) : '';
|
||||||
@ -387,9 +499,60 @@ const formatDifficultyScoreRate = (actual?: number, total?: number) => {
|
|||||||
return { text: `${Math.round(rate * 10) / 10}%`, value: rate };
|
return { text: `${Math.round(rate * 10) / 10}%`, value: rate };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const channelAnalyzeList = computed(() => jobAnalyse.value.paperChanelAnalyzeList || []);
|
||||||
|
const difficultyAnalyzeList = computed(() => jobAnalyse.value.paperDifficultyAnalyzeList || []);
|
||||||
|
|
||||||
|
const getDifficultyColor = (name?: string) => {
|
||||||
|
if (!name) return difficultyFallbackColors[0];
|
||||||
|
return difficultyBaseColorMap[name] || difficultyFallbackColors[name.length % difficultyFallbackColors.length];
|
||||||
|
};
|
||||||
|
|
||||||
|
const totalQuestionCount = computed(() =>
|
||||||
|
channelAnalyzeList.value.reduce((sum, item) => sum + (Number(item.titleCount) || 0), 0),
|
||||||
|
);
|
||||||
|
|
||||||
|
const totalDifficultyCount = computed(() =>
|
||||||
|
difficultyAnalyzeList.value.reduce((sum, item) => sum + (Number(item.titleCount) || 0), 0),
|
||||||
|
);
|
||||||
|
|
||||||
|
const buildPieStyle = (
|
||||||
|
list: { titleCount?: number }[],
|
||||||
|
colorFn: (idx: number) => string,
|
||||||
|
) => {
|
||||||
|
if (!list.length) return {};
|
||||||
|
const total = list.reduce((sum, item) => sum + (Number(item.titleCount) || 0), 0);
|
||||||
|
if (total <= 0) return {};
|
||||||
|
|
||||||
|
const parts: string[] = [];
|
||||||
|
let cur = 0;
|
||||||
|
list.forEach((item, idx) => {
|
||||||
|
const pct = ((Number(item.titleCount) || 0) / total) * 100;
|
||||||
|
const color = colorFn(idx);
|
||||||
|
const next = cur + pct;
|
||||||
|
parts.push(`${color} ${cur}% ${next}%`);
|
||||||
|
cur = next;
|
||||||
|
});
|
||||||
|
return { background: `conic-gradient(${parts.join(', ')})` };
|
||||||
|
};
|
||||||
|
|
||||||
|
const channelPieStyle = computed(() =>
|
||||||
|
buildPieStyle(channelAnalyzeList.value, (idx) => channelPieColors[idx % channelPieColors.length]),
|
||||||
|
);
|
||||||
|
|
||||||
|
const difficultyPieStyle = computed(() =>
|
||||||
|
buildPieStyle(difficultyAnalyzeList.value, (idx) =>
|
||||||
|
getDifficultyColor(difficultyAnalyzeList.value[idx]?.difficultyName),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
const getDifficultyBarWidth = (score?: number) => {
|
||||||
|
const max = Math.max(...difficultyAnalyzeList.value.map((item) => Number(item.score) || 0), 1);
|
||||||
|
return Math.round(((Number(score) || 0) / max) * 100);
|
||||||
|
};
|
||||||
|
|
||||||
const scoreBlocks = computed<ReportScoreBlock[]>(() => {
|
const scoreBlocks = computed<ReportScoreBlock[]>(() => {
|
||||||
const vo = jobAnalyse.value.jobReportVo || {};
|
const vo = jobAnalyse.value.jobReportVo || {};
|
||||||
const mark = Number(vo.actualScore ?? paperDetail.value.score ?? 0);
|
const mark = Number(vo.actualScore ?? vo.markScore ?? paperDetail.value.score ?? 0);
|
||||||
const total = Number(vo.totalScore ?? paperDetail.value.totalScore ?? 0);
|
const total = Number(vo.totalScore ?? paperDetail.value.totalScore ?? 0);
|
||||||
const rateFromScore =
|
const rateFromScore =
|
||||||
total > 0 ? `${Math.round((mark / total) * 1000) / 10}%` : reportScoreRate.value ? `${reportScoreRate.value}%` : '--';
|
total > 0 ? `${Math.round((mark / total) * 1000) / 10}%` : reportScoreRate.value ? `${reportScoreRate.value}%` : '--';
|
||||||
@ -418,7 +581,7 @@ const ranks = computed(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const analysisRows = computed(() => {
|
const analysisRows = computed(() => {
|
||||||
const list = jobAnalyse.value.paperChanelAnalyzeList || [];
|
const list = channelAnalyzeList.value;
|
||||||
const totalCount = list.reduce((sum, item) => sum + (Number(item.titleCount) || 0), 0);
|
const totalCount = list.reduce((sum, item) => sum + (Number(item.titleCount) || 0), 0);
|
||||||
return list.map((item) => {
|
return list.map((item) => {
|
||||||
const count = Number(item.titleCount) || 0;
|
const count = Number(item.titleCount) || 0;
|
||||||
@ -476,6 +639,52 @@ const detail = computed(() => ({
|
|||||||
ranks: ranks.value,
|
ranks: ranks.value,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
const knowledgeScores = computed<ReportKnowledgeScore[]>(() =>
|
||||||
|
knowledgeList.value.map((item) => ({
|
||||||
|
name: item.knowledgeName || '--',
|
||||||
|
fullScore: Number(item.titleScore) || 0,
|
||||||
|
score: Number(item.answerScore) || 0,
|
||||||
|
})),
|
||||||
|
);
|
||||||
|
|
||||||
|
const ratioTones: ReportKnowledgeRatio['tone'][] = ['blue', 'orange', 'green'];
|
||||||
|
|
||||||
|
const knowledgeRatios = computed<ReportKnowledgeRatio[]>(() => {
|
||||||
|
const lostItems = knowledgeList.value
|
||||||
|
.map((item) => ({
|
||||||
|
name: item.knowledgeName || '--',
|
||||||
|
lost: Math.max(0, (Number(item.titleScore) || 0) - (Number(item.answerScore) || 0)),
|
||||||
|
}))
|
||||||
|
.filter((item) => item.lost > 0)
|
||||||
|
.sort((a, b) => b.lost - a.lost);
|
||||||
|
|
||||||
|
const totalLost = lostItems.reduce((sum, item) => sum + item.lost, 0);
|
||||||
|
if (totalLost <= 0) return [];
|
||||||
|
|
||||||
|
return lostItems.map((item, idx) => ({
|
||||||
|
name: item.name,
|
||||||
|
percent: Math.round((item.lost / totalLost) * 100),
|
||||||
|
tone: ratioTones[idx % ratioTones.length],
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
const knowledgeWeakCount = computed(
|
||||||
|
() =>
|
||||||
|
knowledgeList.value.filter(
|
||||||
|
(item) => (Number(item.answerScore) || 0) < (Number(item.titleScore) || 0),
|
||||||
|
).length,
|
||||||
|
);
|
||||||
|
|
||||||
|
const RATIO_ROW_HEIGHT = 72;
|
||||||
|
const RATIO_LIST_MIN = 200;
|
||||||
|
const RATIO_LIST_MAX = 480;
|
||||||
|
|
||||||
|
const ratioListHeight = computed(() => {
|
||||||
|
const count = knowledgeRatios.value.length || 1;
|
||||||
|
const height = Math.min(Math.max(count * RATIO_ROW_HEIGHT, RATIO_LIST_MIN), RATIO_LIST_MAX);
|
||||||
|
return `${height}rpx`;
|
||||||
|
});
|
||||||
|
|
||||||
const selectedQuestionSummary = computed(
|
const selectedQuestionSummary = computed(
|
||||||
() => questionSummaries.value.find((item) => item.no === selectedNo.value) || null,
|
() => questionSummaries.value.find((item) => item.no === selectedNo.value) || null,
|
||||||
);
|
);
|
||||||
@ -556,13 +765,32 @@ const loadReportData = async () => {
|
|||||||
|
|
||||||
pageLoading.value = true;
|
pageLoading.value = true;
|
||||||
try {
|
try {
|
||||||
const [detailRes, analyseRes] = await Promise.all([
|
const analyseParams: { recordId: string; userId?: string } = {
|
||||||
getPaperRecordDetail({ recordId: recordId.value, reportFlag: REPORT_FLAG }),
|
recordId: recordId.value,
|
||||||
getJobAnalyse({ recordId: recordId.value }).catch(() => ({ data: {} })),
|
};
|
||||||
]);
|
const studentId = activeChild.value.id || childUserId.value;
|
||||||
|
if (studentId) {
|
||||||
|
analyseParams.userId = studentId;
|
||||||
|
}
|
||||||
|
|
||||||
|
const detailRes = await getPaperRecordDetail({ recordId: recordId.value, reportFlag: REPORT_FLAG });
|
||||||
paperDetail.value = (detailRes.data || {}) as Record<string, unknown>;
|
paperDetail.value = (detailRes.data || {}) as Record<string, unknown>;
|
||||||
jobAnalyse.value = analyseRes.data || {};
|
|
||||||
|
try {
|
||||||
|
const analyseRes = await getJobAnalyse(analyseParams);
|
||||||
|
jobAnalyse.value = analyseRes.data || {};
|
||||||
|
} catch (analyseErr) {
|
||||||
|
console.error('[parent-report-detail] analyse err', analyseErr);
|
||||||
|
jobAnalyse.value = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const knowledgeRes = await getKnowledgeAnalyse(analyseParams);
|
||||||
|
knowledgeList.value = Array.isArray(knowledgeRes.data) ? knowledgeRes.data : [];
|
||||||
|
} catch (knowledgeErr) {
|
||||||
|
console.error('[parent-report-detail] knowledge err', knowledgeErr);
|
||||||
|
knowledgeList.value = [];
|
||||||
|
}
|
||||||
|
|
||||||
const titleList = paperDetail.value.titleVoList || paperDetail.value.subjectTitleVoList;
|
const titleList = paperDetail.value.titleVoList || paperDetail.value.subjectTitleVoList;
|
||||||
rawTitleItems.value = Array.isArray(titleList) ? (titleList as Record<string, unknown>[]) : [];
|
rawTitleItems.value = Array.isArray(titleList) ? (titleList as Record<string, unknown>[]) : [];
|
||||||
@ -585,7 +813,17 @@ const loadReportData = async () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const knowledgeRate = (item: ReportKnowledgeScore) => Math.round((item.score / item.fullScore) * 100);
|
const knowledgeRate = (item: ReportKnowledgeScore) => {
|
||||||
|
if (!item.fullScore) return 0;
|
||||||
|
return Math.round((item.score / item.fullScore) * 100);
|
||||||
|
};
|
||||||
|
|
||||||
|
const knowledgeScoreClass = (item: ReportKnowledgeScore) => {
|
||||||
|
const rate = knowledgeRate(item);
|
||||||
|
if (rate >= 80) return 'rate-good';
|
||||||
|
if (rate >= 60) return 'rate-warn';
|
||||||
|
return 'score-danger';
|
||||||
|
};
|
||||||
|
|
||||||
const knowledgeLevelOf = (item: ReportKnowledgeScore): KnowledgeLevel => {
|
const knowledgeLevelOf = (item: ReportKnowledgeScore): KnowledgeLevel => {
|
||||||
const rate = knowledgeRate(item);
|
const rate = knowledgeRate(item);
|
||||||
@ -595,8 +833,8 @@ const knowledgeLevelOf = (item: ReportKnowledgeScore): KnowledgeLevel => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const filteredKnowledgeScores = computed(() => {
|
const filteredKnowledgeScores = computed(() => {
|
||||||
if (activeKnowledgeLevel.value === 'all') return detail.value.knowledgeScores;
|
if (activeKnowledgeLevel.value === 'all') return knowledgeScores.value;
|
||||||
return detail.value.knowledgeScores.filter((item) => knowledgeLevelOf(item) === activeKnowledgeLevel.value);
|
return knowledgeScores.value.filter((item) => knowledgeLevelOf(item) === activeKnowledgeLevel.value);
|
||||||
});
|
});
|
||||||
|
|
||||||
const previewStemImage = (index: number) => {
|
const previewStemImage = (index: number) => {
|
||||||
@ -772,6 +1010,144 @@ const goHome = () => {
|
|||||||
border-radius: 26rpx;
|
border-radius: 26rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.chart-block {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 28rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-table {
|
||||||
|
margin-top: 4rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pie-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 24rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pie {
|
||||||
|
position: relative;
|
||||||
|
width: 200rpx;
|
||||||
|
height: 200rpx;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pie-circle {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
border-radius: 50%;
|
||||||
|
box-shadow: 0 8rpx 16rpx rgba(37, 99, 235, 0.12);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pie-hole {
|
||||||
|
position: absolute;
|
||||||
|
inset: 28rpx;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: #ffffff;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
box-shadow:
|
||||||
|
inset 0 4rpx 8rpx rgba(0, 0, 0, 0.06),
|
||||||
|
0 2rpx 4rpx rgba(0, 0, 0, 0.04);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pie-hole-num {
|
||||||
|
font-size: 44rpx;
|
||||||
|
font-weight: 900;
|
||||||
|
color: #0f172a;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pie-hole-label {
|
||||||
|
font-size: 20rpx;
|
||||||
|
color: #94a3b8;
|
||||||
|
margin-top: 4rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pie-legend {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 12rpx;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-name {
|
||||||
|
flex: 1;
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #475569;
|
||||||
|
font-weight: 500;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-count {
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #2563eb;
|
||||||
|
font-weight: 800;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bars {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bar-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 16rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bar-name {
|
||||||
|
width: 88rpx;
|
||||||
|
flex-shrink: 0;
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #475569;
|
||||||
|
font-weight: 700;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bar-track {
|
||||||
|
flex: 1;
|
||||||
|
height: 24rpx;
|
||||||
|
border-radius: 999rpx;
|
||||||
|
background: #f1f5f9;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bar-fill {
|
||||||
|
height: 100%;
|
||||||
|
border-radius: 999rpx;
|
||||||
|
transition: width 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bar-value {
|
||||||
|
width: 88rpx;
|
||||||
|
flex-shrink: 0;
|
||||||
|
font-size: 26rpx;
|
||||||
|
font-weight: 900;
|
||||||
|
color: #0f172a;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bar-value-unit {
|
||||||
|
font-size: 20rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #94a3b8;
|
||||||
|
}
|
||||||
|
|
||||||
.table-row.channel-row {
|
.table-row.channel-row {
|
||||||
grid-template-columns: repeat(3, 1fr);
|
grid-template-columns: repeat(3, 1fr);
|
||||||
}
|
}
|
||||||
@ -1057,7 +1433,7 @@ const goHome = () => {
|
|||||||
|
|
||||||
.knowledge-summary {
|
.knowledge-summary {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: flex-start;
|
||||||
gap: 28rpx;
|
gap: 28rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1088,12 +1464,18 @@ const goHome = () => {
|
|||||||
.ratio-list {
|
.ratio-list {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
|
min-height: 200rpx;
|
||||||
|
max-height: 480rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ratio-row + .ratio-row {
|
.ratio-row + .ratio-row {
|
||||||
margin-top: 18rpx;
|
margin-top: 18rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ratio-row:last-child {
|
||||||
|
padding-bottom: 4rpx;
|
||||||
|
}
|
||||||
|
|
||||||
.ratio-meta {
|
.ratio-meta {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
|||||||
@ -217,8 +217,9 @@ const subjectClass = (subject: string) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const openReport = (item: ReportCard) => {
|
const openReport = (item: ReportCard) => {
|
||||||
|
const userId = activeChild.value.id ? `&userId=${activeChild.value.id}` : '';
|
||||||
uni.navigateTo({
|
uni.navigateTo({
|
||||||
url: `/pages/parent/reports/detail?id=${item.id}&name=${encodeURIComponent(item.title)}&subject=${encodeURIComponent(item.subject)}&category=${encodeURIComponent(item.category)}&date=${item.date}&score=${item.score}`,
|
url: `/pages/parent/reports/detail?id=${item.id}&name=${encodeURIComponent(item.title)}&subject=${encodeURIComponent(item.subject)}&category=${encodeURIComponent(item.category)}&date=${item.date}&score=${item.score}${userId}`,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -83,7 +83,7 @@ const skipFirstOnShow = ref(true);
|
|||||||
const totalWrong = computed(() => {
|
const totalWrong = computed(() => {
|
||||||
const subjects = summaryData.value?.subjects || [];
|
const subjects = summaryData.value?.subjects || [];
|
||||||
if (subjects.length) {
|
if (subjects.length) {
|
||||||
return subjects.reduce((sum, item) => sum + (item.totalCount || 0), 0);
|
return subjects.reduce((sum, item) => sum + (Number(item.totalCount) || 0), 0);
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
});
|
});
|
||||||
@ -94,10 +94,10 @@ const subjectStats = computed(() => {
|
|||||||
...buildSubjectStatRow(
|
...buildSubjectStatRow(
|
||||||
item.subjectName || '其他',
|
item.subjectName || '其他',
|
||||||
{
|
{
|
||||||
total: item.totalCount || 0,
|
total: Number(item.totalCount) || 0,
|
||||||
newCount: item.newCount || 0,
|
newCount: Number(item.newCount) || 0,
|
||||||
reviewCount: item.reviewCount || 0,
|
reviewCount: Number(item.reviewCount) || 0,
|
||||||
masteredCount: item.masteredCount || 0,
|
masteredCount: Number(item.masteredCount) || 0,
|
||||||
},
|
},
|
||||||
index,
|
index,
|
||||||
),
|
),
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user