fix(review): 优化复习滚动条展示方式
- 去除"复习滚动条"模块标题,改用右侧小字颜色图例 - 按 session 维度分组展示,一次学习的报告和残片合并为一个条目 - 移除类型标签(报告/残片),改用左边框颜色区分(绿色=有报告,黄色=仅残片) - 展示内容从 50 字增加到 120 字 - 滚动动画从 80s 放慢到 150s
This commit is contained in:
+123
-43
@@ -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 {
|
||||||
|
|||||||
Reference in New Issue
Block a user