fix(review): 优化复习滚动条展示方式

- 去除"复习滚动条"模块标题,改用右侧小字颜色图例
- 按 session 维度分组展示,一次学习的报告和残片合并为一个条目
- 移除类型标签(报告/残片),改用左边框颜色区分(绿色=有报告,黄色=仅残片)
- 展示内容从 50 字增加到 120 字
- 滚动动画从 80s 放慢到 150s
This commit is contained in:
2026-05-23 20:35:53 +08:00
parent c535548793
commit 70d347e06e
+123 -43
View File
@@ -6,31 +6,72 @@ import { getReviewFeed, type ReviewFeedItem } from "@/api/review";
const reviewItems = ref<ReviewFeedItem[]>([]); const reviewItems = ref<ReviewFeedItem[]>([]);
const isHovering = ref(false); const isHovering = ref(false);
const truncatedItems = computed(() => interface SessionGroup {
reviewItems.value.map((item) => ({ sessionNum: string;
...item, taskName: string;
display: taskNum: string;
item.content.length > 50 content: string;
? item.content.substring(0, 50) + "..." hasReport: boolean;
: item.content, fragmentCount: number;
})) navigateType: string;
); navigateId: number;
}
// 按 sessionNum 分组,一个会话展示为一个 chip
const sessionGroups = computed<SessionGroup[]>(() => {
const map = new Map<string, ReviewFeedItem[]>();
for (const item of reviewItems.value) {
const list = map.get(item.sessionNum) || [];
list.push(item);
map.set(item.sessionNum, list);
}
const groups: SessionGroup[] = [];
for (const [sessionNum, items] of map) {
const first = items[0];
const report = items.find(i => i.sourceType === "REPORT");
const fragments = items.filter(i => i.sourceType === "FRAGMENT");
const rawContent = report?.content || fragments.map(f => f.content).join("");
const content = rawContent.length > 120
? rawContent.substring(0, 120) + "..."
: rawContent;
groups.push({
sessionNum,
taskName: first.taskName,
taskNum: first.taskNum,
content,
hasReport: !!report,
fragmentCount: fragments.length,
navigateType: report ? "report" : "fragment",
navigateId: report ? report.id : (fragments[0]?.id ?? 0),
});
}
groups.sort((a, b) => {
const aItem = reviewItems.value.find(i => i.sessionNum === a.sessionNum);
const bItem = reviewItems.value.find(i => i.sessionNum === b.sessionNum);
return (bItem?.createdTime || "").localeCompare(aItem?.createdTime || "");
});
return groups;
});
// 复制一份实现无缝循环 // 复制一份实现无缝循环
const duplicatedItems = computed(() => [...truncatedItems.value, ...truncatedItems.value]); const duplicatedGroups = computed(() => [...sessionGroups.value, ...sessionGroups.value]);
const loadReviewFeed = async () => { const loadReviewFeed = async () => {
try { try {
const res = await getReviewFeed(30); const res = await getReviewFeed(100);
reviewItems.value = res?.data || []; reviewItems.value = res?.data || [];
} catch { } catch {
// feed 加载失败不影响页面主体功能 // feed 加载失败不影响页面主体功能
} }
}; };
const goToReviewDetail = (item: ReviewFeedItem) => { const goToReviewDetail = (group: SessionGroup) => {
const type = item.sourceType === "REPORT" ? "report" : "fragment"; router.push(`/review/detail/${group.navigateType}/${group.navigateId}`);
router.push(`/review/detail/${type}/${item.id}`);
}; };
const startStudy = () => { const startStudy = () => {
@@ -48,8 +89,13 @@ onMounted(() => {
<template> <template>
<section class="welcome-page"> <section class="welcome-page">
<div class="review-scroll-section surface-card" v-if="reviewItems.length > 0"> <div class="review-scroll-section surface-card" v-if="sessionGroups.length > 0">
<h3>复习滚动条</h3> <div class="review-scroll-header">
<span class="color-legend">
<span class="dot dot-report"></span> 有学习报告
<span class="dot dot-fragment"></span> 仅有学习残片
</span>
</div>
<div class="review-scroll-track"> <div class="review-scroll-track">
<div <div
class="review-scroll-content" class="review-scroll-content"
@@ -58,21 +104,14 @@ onMounted(() => {
@mouseleave="isHovering = false" @mouseleave="isHovering = false"
> >
<span <span
v-for="(item, index) in duplicatedItems" v-for="(group, index) in duplicatedGroups"
:key="index" :key="index"
class="review-chip" class="review-chip"
@click="goToReviewDetail(item)" :class="group.hasReport ? 'has-report' : 'fragments-only'"
@click="goToReviewDetail(group)"
> >
<el-tag <strong class="chip-task">{{ group.taskName }}</strong>
size="small" <span class="chip-content">{{ group.content }}</span>
:type="item.sourceType === 'REPORT' ? 'success' : 'warning'"
effect="plain"
disable-transitions
>
{{ item.sourceType === 'REPORT' ? '报告' : '残片' }}
</el-tag>
<strong>{{ item.taskName }}</strong>
<span>{{ item.display }}</span>
</span> </span>
</div> </div>
</div> </div>
@@ -158,14 +197,38 @@ onMounted(() => {
/* 复习滚动条区域 */ /* 复习滚动条区域 */
.review-scroll-section { .review-scroll-section {
padding: 20px 22px; padding: 14px 22px;
overflow: hidden; overflow: hidden;
} }
.review-scroll-section h3 { .review-scroll-header {
margin: 0 0 12px; display: flex;
color: var(--green-900); justify-content: flex-end;
font-size: 16px; margin-bottom: 10px;
}
.color-legend {
font-size: 12px;
color: var(--text-secondary);
display: inline-flex;
align-items: center;
gap: 12px;
}
.dot {
display: inline-block;
width: 8px;
height: 8px;
border-radius: 2px;
margin-right: 3px;
}
.dot-report {
background: var(--green-600);
}
.dot-fragment {
background: #e6a23c;
} }
.review-scroll-track { .review-scroll-track {
@@ -178,7 +241,7 @@ onMounted(() => {
display: inline-flex; display: inline-flex;
gap: 14px; gap: 14px;
white-space: nowrap; white-space: nowrap;
animation: review-ticker 80s linear infinite; animation: review-ticker 150s linear infinite;
} }
.review-scroll-content.paused { .review-scroll-content.paused {
@@ -197,33 +260,50 @@ onMounted(() => {
.review-chip { .review-chip {
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
gap: 8px; gap: 10px;
padding: 8px 14px; padding: 8px 16px;
background: var(--surface-strong); background: var(--surface-strong);
border: 1px solid var(--border-soft); border: 1px solid var(--border-soft);
border-radius: 24px; border-left: 3px solid transparent;
border-radius: 6px;
cursor: pointer; cursor: pointer;
transition: background 0.2s, box-shadow 0.2s; transition: background 0.2s, box-shadow 0.2s;
flex-shrink: 0; flex-shrink: 0;
user-select: none; user-select: none;
} }
.review-chip.has-report {
border-left-color: var(--green-600);
}
.review-chip.fragments-only {
border-left-color: #e6a23c;
}
.review-chip:hover { .review-chip:hover {
background: #e8f5e9;
box-shadow: var(--shadow-soft); box-shadow: var(--shadow-soft);
} }
.review-chip strong { .review-chip.has-report:hover {
color: var(--green-700); background: #e8f5e9;
font-size: 13px;
} }
.review-chip span { .review-chip.fragments-only:hover {
background: #fff8e1;
}
.chip-task {
color: var(--green-700);
font-size: 13px;
flex-shrink: 0;
}
.chip-content {
color: var(--text-secondary); color: var(--text-secondary);
font-size: 13px; font-size: 13px;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
max-width: 220px; max-width: 320px;
} }
.tips-board { .tips-board {