706 lines
21 KiB
Vue
706 lines
21 KiB
Vue
<template>
|
||
<ParentPage active="reports" :show-tab="false" :use-scroll-view="false">
|
||
<ParentFilterRow :items="filterItems" @change="onFilterChange" />
|
||
|
||
<view class="module-section">
|
||
<ParentCard title="错题知识点占比" subtitle="按错题占比识别最近需要优先补强的知识点">
|
||
<view v-if="pageLoading" class="loading-wrap">
|
||
<xy-Empty type="loading" content="加载中..." />
|
||
</view>
|
||
<scroll-view
|
||
v-else-if="wrongRatioList.length"
|
||
class="ratio-scroll"
|
||
scroll-y
|
||
:show-scrollbar="false"
|
||
:style="ratioScrollStyle"
|
||
>
|
||
<view class="ratio-layout">
|
||
<view class="ratio-main">
|
||
<view class="ratio-total">
|
||
<text class="ratio-number">{{ mainWrongPercent }}%</text>
|
||
<text class="ratio-label">最高错题占比</text>
|
||
</view>
|
||
<view
|
||
v-for="item in wrongRatioList"
|
||
:key="`bar-${item.kpointId}`"
|
||
class="ratio-bar"
|
||
>
|
||
<view class="ratio-fill" :class="item.tone" :style="{ width: `${item.ratioPercent}%` }"></view>
|
||
</view>
|
||
</view>
|
||
<view class="legend-list">
|
||
<view v-for="item in wrongRatioList" :key="item.kpointId" class="legend-item">
|
||
<view class="legend-dot" :class="item.tone"></view>
|
||
<text class="legend-name ellipsis">{{ item.kpointName }}</text>
|
||
<text class="legend-percent">{{ formatPercent(item.ratioPercent) }}%</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</scroll-view>
|
||
<xy-Empty v-else type="task" :content="emptyTip" />
|
||
</ParentCard>
|
||
</view>
|
||
|
||
<view class="module-section">
|
||
<ParentCard title="知识点得分" subtitle="可切换得分等级,快速定位当前薄弱段">
|
||
<view class="level-tabs">
|
||
<view
|
||
v-for="level in scoreLevels"
|
||
:key="level.key"
|
||
class="level-tab"
|
||
:class="{ active: activeLevel === level.key }"
|
||
@click="activeLevel = level.key"
|
||
>
|
||
{{ level.label }}
|
||
</view>
|
||
</view>
|
||
|
||
<view v-if="pageLoading" class="loading-wrap">
|
||
<xy-Empty type="loading" content="加载中..." />
|
||
</view>
|
||
<view v-else-if="levelKnowledge.length" class="score-table-wrap">
|
||
<view class="score-row table-head">
|
||
<text>知识点</text>
|
||
<text>满分</text>
|
||
<text>得分</text>
|
||
</view>
|
||
<scroll-view class="score-table-scroll" scroll-y :show-scrollbar="false" :style="scoreTableScrollStyle">
|
||
<view v-for="item in levelKnowledge" :key="`${item.kpointId}-${activeLevel}`" class="score-row">
|
||
<text class="knowledge-name ellipsis">{{ item.kpointName }}</text>
|
||
<text>{{ item.fullScore }}</text>
|
||
<text class="score-value">{{ item.score }}<text class="score-rate">({{ formatPercent(item.scoreRatePercent) }}%)</text></text>
|
||
</view>
|
||
</scroll-view>
|
||
</view>
|
||
<xy-Empty v-else type="learn" content="当前等级暂无知识点" />
|
||
</ParentCard>
|
||
</view>
|
||
|
||
<view class="module-section">
|
||
<ParentCard title="得分能力" subtitle="综合最近多次练习得分,观察能力变化和稳定性">
|
||
<view v-if="pageLoading" class="loading-wrap">
|
||
<xy-Empty type="loading" content="加载中..." />
|
||
</view>
|
||
<template v-else-if="abilityHasContent">
|
||
<view v-if="abilityHasChartData" class="ucharts-wrap">
|
||
<qiun-data-charts
|
||
class="ability-chart"
|
||
type="line"
|
||
canvasId="parentAbilityTrendCanvas2dLineChart"
|
||
background="rgba(255,255,255,0)"
|
||
:canvas2d="false"
|
||
:in-scroll-view="false"
|
||
:ontouch="false"
|
||
:disable-scroll="true"
|
||
:reload="chartReload"
|
||
:reshow="chartReshow"
|
||
:opts="abilityChartOpts"
|
||
:chartData="abilityChartData"
|
||
/>
|
||
</view>
|
||
|
||
<view class="summary-grid">
|
||
<view v-for="item in abilitySummaryList" :key="item.label" class="summary-item">
|
||
<text class="summary-label">{{ item.label }}</text>
|
||
<text class="summary-value">{{ item.value }}</text>
|
||
<text class="summary-desc">{{ item.desc }}</text>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
<xy-Empty v-else type="learn" :content="abilityEmptyTip" />
|
||
</ParentCard>
|
||
</view>
|
||
</ParentPage>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { computed, nextTick, ref, watch } from 'vue';
|
||
import { onShow } from '@dcloudio/uni-app';
|
||
import { storeToRefs } from 'pinia';
|
||
import {
|
||
getKpointScore,
|
||
getScoreAbility,
|
||
getWrongKpointRatio,
|
||
type KpointScoreLevel,
|
||
type ScoreAbilityPoint,
|
||
type ScoreAbilityResult,
|
||
type WrongKpointRatioItem,
|
||
} from '@/api/parent';
|
||
import { useParentStore } from '@/state/modules/parent';
|
||
import { useUserStore } from '@/state/modules/user';
|
||
import { formatTime } from '@/utils/format';
|
||
import { SUBJECT_PICKER_OPTIONS } from '@/utils/subject';
|
||
import ParentCard from '../components/ParentCard.vue';
|
||
import ParentFilterRow from '../components/ParentFilterRow.vue';
|
||
import ParentPage from '../components/ParentPage.vue';
|
||
|
||
const categoryOptions = ['全部类型', '周作业', '月考'];
|
||
|
||
type ScoreLevel = 'weak' | 'pass' | 'good' | 'excellent';
|
||
type RatioTone = 'blue' | 'orange' | 'green' | 'rose';
|
||
|
||
interface WrongRatioViewItem {
|
||
kpointId: number;
|
||
kpointName: string;
|
||
ratioPercent: number;
|
||
tone: RatioTone;
|
||
}
|
||
|
||
interface ScoreViewItem {
|
||
kpointId: number;
|
||
kpointName: string;
|
||
fullScore: number;
|
||
score: number;
|
||
scoreRatePercent: number;
|
||
}
|
||
|
||
interface AbilityTrendViewItem {
|
||
date: string;
|
||
score: number;
|
||
}
|
||
|
||
interface AbilitySummaryViewItem {
|
||
label: string;
|
||
value: string;
|
||
desc: string;
|
||
}
|
||
|
||
const RATIO_TONES: RatioTone[] = ['blue', 'orange', 'green', 'rose'];
|
||
|
||
/** 列表区域高度区间(rpx) */
|
||
const MODULE_SCROLL_MIN_RPX = 280;
|
||
const MODULE_SCROLL_MAX_RPX = 520;
|
||
const SCORE_ROW_RPX = 76;
|
||
|
||
const clampModuleScrollHeight = (contentRpx: number) => {
|
||
const heightRpx = Math.max(MODULE_SCROLL_MIN_RPX, Math.min(MODULE_SCROLL_MAX_RPX, contentRpx));
|
||
return `${heightRpx}rpx`;
|
||
};
|
||
|
||
const LEVEL_CODE_MAP: Record<ScoreLevel, number> = {
|
||
excellent: 1,
|
||
good: 2,
|
||
pass: 3,
|
||
weak: 4,
|
||
};
|
||
|
||
const userStore = useUserStore();
|
||
const parentStore = useParentStore();
|
||
const { activeChild } = storeToRefs(parentStore);
|
||
|
||
const subjectIndex = ref(0);
|
||
const categoryIndex = ref(0);
|
||
const activeLevel = ref<ScoreLevel>('weak');
|
||
const pageLoading = ref(false);
|
||
|
||
const wrongRatioList = ref<WrongRatioViewItem[]>([]);
|
||
const scoreLevelsData = ref<KpointScoreLevel[]>([]);
|
||
const abilityTrendList = ref<AbilityTrendViewItem[]>([]);
|
||
const abilitySummaryList = ref<AbilitySummaryViewItem[]>([]);
|
||
const abilityHasContent = ref(false);
|
||
const abilityHasChartData = ref(false);
|
||
|
||
const filterItems = computed(() => [
|
||
{ key: 'subject', range: SUBJECT_PICKER_OPTIONS.map((item) => item.label), value: subjectIndex.value },
|
||
{ key: 'category', range: categoryOptions, value: categoryIndex.value },
|
||
]);
|
||
|
||
const emptyTip = computed(() => {
|
||
if (!activeChild.value.id) return '请先绑定并选择孩子';
|
||
return '暂无符合条件的知识点数据';
|
||
});
|
||
|
||
const abilityEmptyTip = computed(() => {
|
||
if (!activeChild.value.id) return '请先绑定并选择孩子';
|
||
return '暂无得分能力数据';
|
||
});
|
||
|
||
const mainWrongPercent = computed(() => {
|
||
if (!wrongRatioList.value.length) return 0;
|
||
return Math.max(...wrongRatioList.value.map((item) => item.ratioPercent));
|
||
});
|
||
|
||
const scoreLevels: { key: ScoreLevel; label: string }[] = [
|
||
{ key: 'weak', label: '薄弱' },
|
||
{ key: 'pass', label: '合格' },
|
||
{ key: 'good', label: '良好' },
|
||
{ key: 'excellent', label: '优秀' },
|
||
];
|
||
|
||
const ratioScrollStyle = computed(() => {
|
||
const count = wrongRatioList.value.length;
|
||
const mainHeight = 116 + count * 30;
|
||
const legendHeight = count * 46;
|
||
return { height: clampModuleScrollHeight(Math.max(mainHeight, legendHeight)) };
|
||
});
|
||
|
||
const levelKnowledge = computed<ScoreViewItem[]>(() => {
|
||
const levelCode = LEVEL_CODE_MAP[activeLevel.value];
|
||
const level = scoreLevelsData.value.find((item) => item.levelCode === levelCode);
|
||
return (level?.items || []).map((item) => ({
|
||
kpointId: item.kpointId ?? 0,
|
||
kpointName: item.kpointName || '--',
|
||
fullScore: item.fullScore ?? 0,
|
||
score: item.actualScore ?? 0,
|
||
scoreRatePercent: item.scoreRatePercent ?? 0,
|
||
}));
|
||
});
|
||
|
||
const scoreTableScrollStyle = computed(() => {
|
||
const rows = levelKnowledge.value.length;
|
||
return { height: clampModuleScrollHeight(rows * SCORE_ROW_RPX) };
|
||
});
|
||
|
||
const formatPercent = (value?: number) => {
|
||
const num = Number(value);
|
||
if (!Number.isFinite(num)) return '0';
|
||
return Number.isInteger(num) ? String(num) : num.toFixed(1);
|
||
};
|
||
|
||
const mapWrongRatioItems = (items: WrongKpointRatioItem[] = []): WrongRatioViewItem[] =>
|
||
items.map((item, index) => ({
|
||
kpointId: item.kpointId ?? index,
|
||
kpointName: item.kpointName || '--',
|
||
ratioPercent: item.ratioPercent ?? 0,
|
||
tone: RATIO_TONES[index % RATIO_TONES.length],
|
||
}));
|
||
|
||
const formatAbilityDate = (value?: string) => {
|
||
if (!value) return '--';
|
||
const formatted = formatTime(value, 'MM-DD');
|
||
return formatted || value;
|
||
};
|
||
|
||
const normalizeAbilityScore = (item: ScoreAbilityPoint) => {
|
||
if (!item.hasData) return 0;
|
||
const score = Number(item.scoreAbility);
|
||
return Number.isFinite(score) ? score : 0;
|
||
};
|
||
|
||
const mapAbilityTrendItems = (points: ScoreAbilityPoint[] = []): AbilityTrendViewItem[] =>
|
||
points.map((item) => ({
|
||
date: formatAbilityDate(item.statDate),
|
||
score: normalizeAbilityScore(item),
|
||
}));
|
||
|
||
const hasAbilityChartData = (points: ScoreAbilityPoint[] = []) => points.length > 0;
|
||
|
||
const mapAbilitySummary = (data?: ScoreAbilityResult): AbilitySummaryViewItem[] => {
|
||
const overallScore = data?.overallScoreAbility;
|
||
const overallDesc =
|
||
overallScore != null
|
||
? `综合得分能力 ${formatPercent(overallScore)} 分`
|
||
: '暂无评估数据';
|
||
const periodDesc =
|
||
data?.startDate && data?.endDate
|
||
? `统计周期 ${formatAbilityDate(data.startDate)} ~ ${formatAbilityDate(data.endDate)}`
|
||
: '暂无稳定性分析';
|
||
|
||
return [
|
||
{
|
||
label: '得分能力综合评估',
|
||
value: data?.assessmentName || '--',
|
||
desc: overallDesc,
|
||
},
|
||
{
|
||
label: '得分能力稳定性',
|
||
value: data?.stabilityName || '--',
|
||
desc: periodDesc,
|
||
},
|
||
];
|
||
};
|
||
|
||
const getSelectedSubjectId = () => SUBJECT_PICKER_OPTIONS[subjectIndex.value]?.value;
|
||
|
||
const fetchAnalysisData = async () => {
|
||
const userId = activeChild.value.id;
|
||
const subjectId = getSelectedSubjectId();
|
||
|
||
if (!userId || !subjectId) {
|
||
wrongRatioList.value = [];
|
||
scoreLevelsData.value = [];
|
||
abilityTrendList.value = [];
|
||
abilitySummaryList.value = [];
|
||
abilityHasContent.value = false;
|
||
abilityHasChartData.value = false;
|
||
return;
|
||
}
|
||
|
||
pageLoading.value = true;
|
||
try {
|
||
const params = { subjectId, userId };
|
||
const [ratioRes, scoreRes, abilityRes] = await Promise.all([
|
||
getWrongKpointRatio(params),
|
||
getKpointScore(params),
|
||
getScoreAbility(params),
|
||
]);
|
||
wrongRatioList.value = mapWrongRatioItems(ratioRes.data?.items);
|
||
scoreLevelsData.value = scoreRes.data?.levels || [];
|
||
abilityTrendList.value = mapAbilityTrendItems(abilityRes.data?.points);
|
||
abilitySummaryList.value = mapAbilitySummary(abilityRes.data);
|
||
abilityHasChartData.value = hasAbilityChartData(abilityRes.data?.points);
|
||
abilityHasContent.value = abilityHasChartData.value || !!abilityRes.data?.assessmentName;
|
||
} catch (err) {
|
||
console.error('[parent-analysis] fetch err', err);
|
||
wrongRatioList.value = [];
|
||
scoreLevelsData.value = [];
|
||
abilityTrendList.value = [];
|
||
abilitySummaryList.value = [];
|
||
abilityHasContent.value = false;
|
||
abilityHasChartData.value = false;
|
||
} finally {
|
||
pageLoading.value = false;
|
||
await refreshChart();
|
||
}
|
||
};
|
||
|
||
const onFilterChange = (payload: { key: string; value: number }) => {
|
||
if (payload.key === 'subject') {
|
||
subjectIndex.value = payload.value;
|
||
return;
|
||
}
|
||
if (payload.key === 'category') {
|
||
categoryIndex.value = payload.value;
|
||
}
|
||
};
|
||
|
||
watch([subjectIndex, categoryIndex, () => activeChild.value.id], () => {
|
||
fetchAnalysisData();
|
||
});
|
||
|
||
const buildAbilityChartData = () => ({
|
||
categories: abilityTrendList.value.map((item) => item.date),
|
||
series: [
|
||
{
|
||
name: '得分能力',
|
||
data: abilityTrendList.value.map((item) => item.score),
|
||
color: '#0891b2',
|
||
},
|
||
],
|
||
});
|
||
|
||
const abilityChartData = ref<Record<string, unknown>>({ categories: [], series: [] });
|
||
const chartReshow = ref(false);
|
||
const chartReload = ref(false);
|
||
|
||
const refreshChart = async () => {
|
||
if (!abilityHasChartData.value) {
|
||
abilityChartData.value = { categories: [], series: [] };
|
||
return;
|
||
}
|
||
abilityChartData.value = JSON.parse(JSON.stringify(buildAbilityChartData()));
|
||
await nextTick();
|
||
setTimeout(() => {
|
||
chartReload.value = !chartReload.value;
|
||
chartReshow.value = !chartReshow.value;
|
||
}, 320);
|
||
};
|
||
|
||
const abilityChartMin = computed(() => {
|
||
if (!abilityTrendList.value.length) return 0;
|
||
const minScore = Math.min(...abilityTrendList.value.map((item) => item.score));
|
||
return Math.max(0, Math.floor(minScore / 10) * 10 - 10);
|
||
});
|
||
|
||
onShow(async () => {
|
||
try {
|
||
if (!userStore.userInfo.id) {
|
||
await userStore.fetchParentUserInfo();
|
||
}
|
||
await parentStore.fetchBindChildren(userStore.userInfo.id);
|
||
} catch {
|
||
/* ignore */
|
||
}
|
||
await fetchAnalysisData();
|
||
});
|
||
|
||
const abilityChartOpts = computed(() => {
|
||
const pointCount = abilityTrendList.value.length || 30;
|
||
const labelCount = Math.min(6, Math.max(4, Math.ceil(pointCount / 5)));
|
||
|
||
return {
|
||
color: ['#0891b2'],
|
||
padding: [18, 12, 8, 12],
|
||
enableScroll: false,
|
||
dataLabel: false,
|
||
legend: { show: false },
|
||
xAxis: {
|
||
disableGrid: false,
|
||
gridColor: '#eef2f7',
|
||
fontColor: '#64748b',
|
||
fontSize: 10,
|
||
boundaryGap: 'justify',
|
||
labelCount,
|
||
},
|
||
yAxis: {
|
||
gridType: 'dash',
|
||
dashLength: 2,
|
||
gridColor: '#eef2f7',
|
||
data: [
|
||
{
|
||
min: abilityChartMin.value,
|
||
max: 100,
|
||
splitNumber: 4,
|
||
fontColor: '#94a3b8',
|
||
fontSize: 10,
|
||
},
|
||
],
|
||
},
|
||
extra: {
|
||
line: {
|
||
type: 'curve',
|
||
width: 3,
|
||
activeType: 'hollow',
|
||
},
|
||
markLine: {
|
||
type: 'dash',
|
||
dashLength: 6,
|
||
data: [
|
||
{ value: 85, lineColor: 'rgba(244, 63, 94, 0.32)', showLabel: true, labelText: '优秀' },
|
||
{ value: 70, lineColor: 'rgba(244, 63, 94, 0.28)', showLabel: true, labelText: '良好' },
|
||
{ value: 60, lineColor: 'rgba(244, 63, 94, 0.22)', showLabel: true, labelText: '一般' },
|
||
],
|
||
},
|
||
},
|
||
};
|
||
});
|
||
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
.module-section {
|
||
margin-top: 44rpx;
|
||
width: 100%;
|
||
max-width: 100%;
|
||
overflow-x: hidden;
|
||
}
|
||
|
||
.filter-row + .module-section {
|
||
margin-top: 36rpx;
|
||
}
|
||
|
||
.loading-wrap {
|
||
padding: 24rpx 0;
|
||
}
|
||
|
||
.ratio-scroll {
|
||
min-height: 280rpx;
|
||
max-height: 520rpx;
|
||
}
|
||
|
||
.ratio-layout {
|
||
display: flex;
|
||
align-items: flex-start;
|
||
gap: 28rpx;
|
||
width: 100%;
|
||
max-width: 100%;
|
||
}
|
||
|
||
.ratio-main {
|
||
width: 232rpx;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.ratio-total {
|
||
min-height: 116rpx;
|
||
border-radius: 28rpx;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
background: linear-gradient(180deg, #ecfeff 0%, #ffffff 100%);
|
||
box-shadow: inset 0 -2rpx 0 rgba(34, 211, 238, 0.24);
|
||
}
|
||
|
||
.ratio-number {
|
||
color: #164e63;
|
||
font-size: 42rpx;
|
||
font-weight: 900;
|
||
}
|
||
|
||
.ratio-label {
|
||
margin-top: 2rpx;
|
||
color: #64748b;
|
||
font-size: 20rpx;
|
||
}
|
||
|
||
.ratio-bar {
|
||
height: 16rpx;
|
||
margin-top: 14rpx;
|
||
border-radius: 999rpx;
|
||
overflow: hidden;
|
||
background: #e2e8f0;
|
||
}
|
||
|
||
.ratio-fill {
|
||
height: 100%;
|
||
border-radius: 999rpx;
|
||
|
||
&.blue { background: #3b82f6; }
|
||
&.orange { background: #f97316; }
|
||
&.green { background: #22c55e; }
|
||
&.rose { background: #f43f5e; }
|
||
}
|
||
|
||
.legend-list {
|
||
flex: 1;
|
||
min-width: 0;
|
||
}
|
||
|
||
.legend-item {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12rpx;
|
||
min-height: 46rpx;
|
||
color: #164e63;
|
||
font-size: 24rpx;
|
||
font-weight: 700;
|
||
}
|
||
|
||
.legend-dot {
|
||
width: 16rpx;
|
||
height: 16rpx;
|
||
border-radius: 50%;
|
||
flex-shrink: 0;
|
||
|
||
&.blue { background: #3b82f6; }
|
||
&.orange { background: #f97316; }
|
||
&.green { background: #22c55e; }
|
||
&.rose { background: #f43f5e; }
|
||
}
|
||
|
||
.legend-name {
|
||
flex: 1;
|
||
}
|
||
|
||
.legend-percent {
|
||
color: #0891b2;
|
||
}
|
||
|
||
.level-tabs {
|
||
display: grid;
|
||
grid-template-columns: repeat(4, 1fr);
|
||
gap: 14rpx;
|
||
margin-bottom: 20rpx;
|
||
}
|
||
|
||
.level-tab {
|
||
height: 64rpx;
|
||
border: 2rpx solid #e2e8f0;
|
||
border-radius: 22rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
color: #475569;
|
||
background: #ffffff;
|
||
font-size: 24rpx;
|
||
font-weight: 800;
|
||
|
||
&.active {
|
||
border-color: #0891b2;
|
||
color: #ffffff;
|
||
background: linear-gradient(180deg, #22d3ee 0%, #0891b2 100%);
|
||
box-shadow: 0 10rpx 20rpx rgba(8, 145, 178, 0.2);
|
||
}
|
||
}
|
||
|
||
.score-table-wrap {
|
||
border: 2rpx solid #e2e8f0;
|
||
border-radius: 24rpx;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.score-table-scroll {
|
||
min-height: 280rpx;
|
||
max-height: 520rpx;
|
||
}
|
||
|
||
.score-row {
|
||
display: grid;
|
||
grid-template-columns: minmax(0, 1fr) 112rpx 150rpx;
|
||
align-items: center;
|
||
min-height: 76rpx;
|
||
padding: 0 22rpx;
|
||
color: #164e63;
|
||
font-size: 24rpx;
|
||
text-align: center;
|
||
border-top: 2rpx solid #f1f5f9;
|
||
|
||
&:first-child {
|
||
border-top: 0;
|
||
}
|
||
|
||
> text,
|
||
> .knowledge-name {
|
||
min-width: 0;
|
||
}
|
||
}
|
||
|
||
.table-head {
|
||
color: #475569;
|
||
background: #f8fafc;
|
||
font-weight: 900;
|
||
}
|
||
|
||
.knowledge-name {
|
||
text-align: left;
|
||
}
|
||
|
||
.score-value {
|
||
color: #ea580c;
|
||
font-weight: 900;
|
||
}
|
||
|
||
.score-rate {
|
||
margin-left: 2rpx;
|
||
font-size: 22rpx;
|
||
font-weight: 700;
|
||
}
|
||
|
||
.ucharts-wrap {
|
||
width: 100%;
|
||
max-width: 100%;
|
||
height: 360rpx;
|
||
margin-top: 4rpx;
|
||
overflow: hidden;
|
||
position: relative;
|
||
}
|
||
|
||
.ability-chart {
|
||
display: block;
|
||
width: 100% !important;
|
||
max-width: 100%;
|
||
height: 100%;
|
||
}
|
||
|
||
.summary-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(2, 1fr);
|
||
gap: 16rpx;
|
||
margin-top: 26rpx;
|
||
}
|
||
|
||
.summary-item {
|
||
padding: 18rpx;
|
||
border-radius: 24rpx;
|
||
background: #f8fafc;
|
||
text-align: center;
|
||
}
|
||
|
||
.summary-label,
|
||
.summary-desc {
|
||
display: block;
|
||
color: #64748b;
|
||
font-size: 22rpx;
|
||
line-height: 1.45;
|
||
}
|
||
|
||
.summary-value {
|
||
display: block;
|
||
margin: 8rpx 0;
|
||
color: #0891b2;
|
||
font-size: 32rpx;
|
||
font-weight: 900;
|
||
}
|
||
</style>
|