diff --git a/frontend/src/pages/home/index.less b/frontend/src/pages/home/index.less
index 695b029..8557be2 100644
--- a/frontend/src/pages/home/index.less
+++ b/frontend/src/pages/home/index.less
@@ -5,13 +5,14 @@
.home-container {
position: relative;
- min-height: calc(100vh - 160px);
+ flex: 1;
+ height: 100%;
padding: clamp(24px, 4vw, 40px) clamp(24px, 5vw, 60px);
background: linear-gradient(135deg, #ffffff 0%, #f1f5f9 100%);
color: @home-text-main;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
- overflow-x: hidden; // 允许垂直滚动
- margin: -24px;
+ overflow-x: hidden;
+ overflow-y: auto;
z-index: 1;
display: flex;
flex-direction: column;
@@ -76,12 +77,36 @@
margin-bottom: 56px !important;
color: @home-text-main !important;
letter-spacing: -0.02em; // Tighter tracking
+ display: flex;
+ align-items: center;
+ flex-wrap: wrap;
+
+ .home-title-accent-wrapper {
+ position: relative;
+ display: inline-flex;
+ flex-direction: column;
+ height: 1.2em; /* fixed height for scroller */
+ overflow: hidden;
+ vertical-align: bottom;
+ }
+
+ .home-title-accent-scroller {
+ display: flex;
+ flex-direction: column;
+ transition: transform 0.6s cubic-bezier(0.22, 1, 0.36, 1);
+ }
.home-title-accent {
+ display: flex;
+ align-items: center;
+ height: 1.2em; /* strictly matched to wrapper height */
+ line-height: 1.2em;
+ white-space: nowrap;
background: linear-gradient(90deg, #4f46e5, #7c3aed, #2563eb); // Deeper, more elegant gradient
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
+ flex-shrink: 0;
}
}
@@ -91,20 +116,37 @@
flex-wrap: wrap;
@media (max-width: 768px) {
- gap: 32px;
+ gap: 24px;
+ flex-direction: column;
+ align-items: stretch;
+
+ .home-action-item {
+ max-width: 100%;
+ }
}
}
.home-action-item {
+ position: relative;
display: flex;
flex-direction: column;
align-items: flex-start;
cursor: pointer;
- max-width: 240px;
+ width: 100%;
+ max-width: 280px;
+ flex: 1;
+ min-width: 240px;
+ padding: 32px 24px;
+ border-radius: 20px;
+ background: var(--action-bg-gradient);
+ transition: transform 0.4s cubic-bezier(0.34, 1.56, 0.64, 1), box-shadow 0.4s ease;
&:hover {
+ transform: translateY(-6px);
+ box-shadow: 0 20px 40px -10px var(--action-shadow);
+
.home-action-icon-wrapper {
- transform: translateY(-6px);
+ transform: translateY(-4px) scale(1.05);
}
.home-action-icon {
box-shadow:
@@ -116,27 +158,47 @@
opacity: 0.8;
transform: translateY(14px) scale(0.9);
}
- .home-action-title {
- color: @home-primary !important;
- }
}
&--violet {
--action-color: #7c3aed;
- --action-bg: rgba(124, 58, 237, 0.15);
+ --action-bg: #8b5cf6;
+ --action-bg-gradient: linear-gradient(135deg, rgba(237, 233, 254, 0.8) 0%, rgba(2ede, 233, 254, 0.4) 100%);
+ --action-shadow: rgba(124, 58, 237, 0.15);
+ --action-badge-bg: linear-gradient(90deg, #8b5cf6, #a78bfa);
+ --action-icon-bg: linear-gradient(135deg, #7c3aed, #a78bfa);
+ background: linear-gradient(180deg, #f3f0ff 0%, #fdfcff 100%);
}
&--cyan {
--action-color: #0284c7;
- --action-bg: rgba(2, 132, 199, 0.15);
+ --action-bg: #0ea5e9;
+ --action-bg-gradient: linear-gradient(135deg, rgba(224, 242, 254, 0.8) 0%, rgba(224, 242, 254, 0.4) 100%);
+ --action-shadow: rgba(2, 132, 199, 0.15);
+ --action-badge-bg: linear-gradient(90deg, #0ea5e9, #38bdf8);
+ --action-icon-bg: linear-gradient(135deg, #0284c7, #38bdf8);
+ background: linear-gradient(180deg, #e0f2fe 0%, #f0f9ff 100%);
+ }
+
+ .home-action-badge {
+ position: absolute;
+ top: 0;
+ right: 0;
+ padding: 6px 16px;
+ background: var(--action-badge-bg);
+ color: white;
+ font-size: 13px;
+ font-weight: 600;
+ border-radius: 0 20px 0 20px;
+ z-index: 2;
}
.home-action-icon-wrapper {
position: relative;
- width: 64px;
- height: 64px;
- margin-bottom: 24px;
- transition: transform 0.4s cubic-bezier(0.34, 1.56, 0.64, 1); // Springy bounce
+ width: 72px;
+ height: 72px;
+ margin-bottom: 32px;
+ transition: transform 0.4s cubic-bezier(0.34, 1.56, 0.64, 1);
}
.home-action-icon {
@@ -147,19 +209,40 @@
display: flex;
align-items: center;
justify-content: center;
- font-size: 28px;
- color: var(--action-color);
- background: linear-gradient(135deg, #ffffff 0%, #f8fafc 100%);
- border-radius: 20px;
- border: 1px solid rgba(255, 255, 255, 0.8);
- // Apple-like nested shadow
+ font-size: 32px;
+ color: white;
+ background: var(--action-icon-bg);
+ border-radius: 16px;
+ border: 1px solid rgba(255, 255, 255, 0.2);
box-shadow:
- 0 4px 6px -1px rgba(0, 0, 0, 0.05),
- 0 2px 4px -2px rgba(0, 0, 0, 0.05),
- inset 0 1px 0 rgba(255,255,255,1);
+ 0 8px 16px -4px var(--action-shadow),
+ inset 0 2px 4px rgba(255, 255, 255, 0.3);
transition: box-shadow 0.4s ease;
}
+ .home-action-icon-circle {
+ position: absolute;
+ bottom: -8px;
+ right: -12px;
+ width: 32px;
+ height: 32px;
+ background: white;
+ border-radius: 50%;
+ z-index: 3;
+ box-shadow: 0 4px 12px rgba(0,0,0,0.1);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+
+ &::after {
+ content: '';
+ width: 12px;
+ height: 12px;
+ background: var(--action-color);
+ border-radius: 50%;
+ }
+ }
+
.home-action-icon-glow {
position: absolute;
top: 50%;
@@ -176,23 +259,23 @@
}
.home-action-title {
- font-size: 20px !important;
+ font-size: 24px !important;
font-weight: 700 !important;
- margin-bottom: 12px !important;
- color: @home-text-main !important;
+ margin-bottom: 16px !important;
+ color: #1e1e38 !important;
transition: color 0.3s ease;
}
.home-action-desc {
display: flex;
flex-direction: column;
- gap: 4px;
+ gap: 8px;
}
.home-action-line {
- font-size: 14px;
- color: @home-text-gray;
- line-height: 1.6;
+ font-size: 15px;
+ color: #5a5a72;
+ line-height: 1.5;
}
}
@@ -232,93 +315,136 @@
}
.home-recent-card {
+ position: relative;
display: flex;
- background: rgba(255, 255, 255, 0.7); // Glassmorphism base
- backdrop-filter: blur(20px);
- border-radius: 16px;
- padding: 16px;
+ flex-direction: column;
+ min-height: 140px;
+ padding: 20px 24px 20px;
cursor: pointer;
- transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
- // Sophisticated inner and outer shadows
- box-shadow:
- 0 4px 6px -1px rgba(0, 0, 0, 0.02),
- 0 2px 4px -2px rgba(0, 0, 0, 0.02),
- inset 0 1px 0 rgba(255, 255, 255, 0.6);
- border: 1px solid rgba(255, 255, 255, 0.8);
+ overflow: hidden;
+ border-radius: 20px;
+ border: none;
+ background: linear-gradient(180deg, #f9f8fe 0%, #f3f2fa 100%);
+ box-shadow: 0 8px 28px rgba(113, 107, 151, 0.08);
+ transition: transform 0.28s ease, box-shadow 0.28s ease, border-color 0.28s ease;
&:hover {
transform: translateY(-4px);
- background: rgba(255, 255, 255, 0.95);
- border-color: rgba(99, 102, 241, 0.15);
- box-shadow:
- 0 20px 25px -5px rgba(0, 0, 0, 0.05),
- 0 8px 10px -6px rgba(0, 0, 0, 0.01),
- inset 0 1px 0 rgba(255, 255, 255, 0.8);
+ box-shadow: 0 14px 34px rgba(113, 107, 151, 0.12);
- .home-recent-card-action {
- color: @home-primary;
- transform: translateX(0);
- opacity: 1;
+ .home-recent-card-icon {
+ transform: translateX(2px);
+ box-shadow: 0 10px 22px rgba(118, 105, 255, 0.12);
}
}
- .home-recent-card-thumbnail {
- width: 100px;
- height: 72px;
- background: linear-gradient(135deg, #e0e7ff 0%, #dbeafe 100%);
- border-radius: 10px;
+ &:focus-visible {
+ outline: 3px solid rgba(126, 105, 255, 0.22);
+ outline-offset: 2px;
+ }
+
+ &::before {
+ content: "";
+ position: absolute;
+ inset: auto -92px -136px auto;
+ width: 200px;
+ height: 200px;
+ border-radius: 50%;
+ background: radial-gradient(circle, rgba(133, 115, 255, 0.1) 0%, rgba(133, 115, 255, 0) 70%);
+ pointer-events: none;
+ }
+
+ .home-recent-card-dot {
+ position: absolute;
+ top: 10px;
+ right: 10px;
+ width: 14px;
+ height: 14px;
+ border-radius: 50%;
+ background: linear-gradient(180deg, #ff8f8f 0%, #f56f6f 100%);
+ box-shadow: 0 0 0 4px rgba(249, 248, 254, 0.96);
+ z-index: 2;
+ }
+
+ .home-recent-card-head {
+ display: flex;
+ align-items: flex-start;
+ justify-content: space-between;
+ gap: 16px;
+ margin-bottom: 12px;
+ }
+
+ .home-recent-card-icon {
+ width: 40px;
+ height: 40px;
display: flex;
align-items: center;
justify-content: center;
- margin-right: 16px;
flex-shrink: 0;
- border: 1px solid rgba(255, 255, 255, 0.5);
+ border-radius: 12px;
+ background: #efedf8;
+ color: #8a80ff;
+ box-shadow: none;
+ transition: transform 0.28s ease, box-shadow 0.28s ease;
.home-recent-card-play-icon {
- font-size: 24px;
- color: #fff;
- opacity: 0.9;
- filter: drop-shadow(0 2px 4px rgba(0,0,0,0.1));
+ font-size: 18px;
}
}
- .home-recent-card-content {
+ .home-recent-card-tags {
display: flex;
- flex-direction: column;
- justify-content: center;
- flex: 1;
- min-width: 0;
+ flex-wrap: wrap;
+ gap: 8px 10px;
+ margin-bottom: auto;
+ padding-right: 12px;
+ }
+
+ .home-recent-card-tag {
+ margin: 0;
+ padding: 4px 10px;
+ border-radius: 6px;
+ background: #eceaf7;
+ color: #6f66f0;
+ font-size: 13px;
+ font-weight: 500;
+ line-height: 1.2;
}
.home-recent-card-title {
- font-size: 15px !important;
- font-weight: 600 !important;
- margin-bottom: 8px !important;
- color: @home-text-main !important;
- white-space: nowrap;
+ margin: 0 !important;
+ color: #2d2c59 !important;
+ font-size: 17px !important;
+ line-height: 1.4 !important;
+ font-weight: 700 !important;
+ display: -webkit-box;
overflow: hidden;
- text-overflow: ellipsis;
+ -webkit-line-clamp: 2;
+ -webkit-box-orient: vertical;
}
.home-recent-card-footer {
display: flex;
justify-content: space-between;
align-items: center;
+ margin-top: 18px;
+ gap: 12px;
+ color: #7d7d9e;
font-size: 13px;
}
.home-recent-card-duration {
- color: @home-text-gray;
font-family: SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace;
- font-size: 12px;
+ font-size: 13px;
+ font-weight: 500;
+ color: #7d7d9e;
}
- .home-recent-card-action {
- color: transparent;
+ .home-recent-card-time {
+ font-size: 13px;
font-weight: 500;
- transition: all 0.3s ease;
- transform: translateX(-4px);
- opacity: 0;
+ color: #7d7d9e;
+ white-space: nowrap;
}
}
diff --git a/frontend/src/pages/home/index.tsx b/frontend/src/pages/home/index.tsx
index 5121672..9a083f7 100644
--- a/frontend/src/pages/home/index.tsx
+++ b/frontend/src/pages/home/index.tsx
@@ -2,7 +2,7 @@ import { useEffect, useMemo, useState } from "react";
import {
AudioOutlined,
ArrowRightOutlined,
- PlayCircleOutlined,
+ VideoCameraOutlined,
VideoCameraAddOutlined
} from "@ant-design/icons";
import { Button, Empty, Skeleton, Tag, Typography } from "antd";
@@ -20,6 +20,7 @@ type QuickEntry = {
icon: React.ReactNode;
description: string[];
accent: string;
+ badge: string;
onClick: () => void;
};
@@ -31,6 +32,8 @@ type RecentCard = {
tags: string[];
};
+const RECENT_CARD_READ_STORAGE_KEY = "home_recent_card_read_ids";
+
const fallbackRecentCards: RecentCard[] = [
{
id: "sample-1",
@@ -65,7 +68,7 @@ function buildRecentCards(tasks: MeetingVO[]): RecentCard[] {
title: task.title,
duration: `0${index + 1}:${10 + index * 12}`,
time: dayjs(task.meetingTime || task.createdAt).format("YYYY-MM-DD HH:mm"),
- tags: task.tags?.split(",").filter(Boolean).slice(0, 5) || ["转写", "总结", "纪要"]
+ tags: task.tags?.split(",").filter(Boolean).slice(0, 5) || []
}));
}
@@ -73,12 +76,39 @@ export default function HomePage() {
const navigate = useNavigate();
const [recentTasks, setRecentTasks] = useState
([]);
const [loading, setLoading] = useState(true);
+ const [readCardIds, setReadCardIds] = useState(() => {
+ if (typeof window === "undefined") {
+ return [];
+ }
+
+ try {
+ const rawValue = window.localStorage.getItem(RECENT_CARD_READ_STORAGE_KEY);
+ if (!rawValue) {
+ return [];
+ }
+ const parsed = JSON.parse(rawValue);
+ return Array.isArray(parsed) ? parsed.map(String) : [];
+ } catch {
+ return [];
+ }
+ });
+
+ const [wordIndex, setWordIndex] = useState(0);
+ const ROTATING_WORDS = useMemo(() => ["都有迹可循", "都能被听见", "都值得记录", "都清晰可见"], []);
+
+ useEffect(() => {
+ const interval = setInterval(() => {
+ setWordIndex((prev) => (prev + 1) % ROTATING_WORDS.length);
+ }, 3000);
+ return () => clearInterval(interval);
+ }, [ROTATING_WORDS.length]);
useEffect(() => {
const fetchRecentTasks = async () => {
try {
const response = await getRecentTasks();
- setRecentTasks(response.data.data || []);
+ const payload: any = (response as any).data || response;
+ setRecentTasks(payload?.data || payload || []);
} catch (error) {
console.error("Home recent tasks load failed", error);
} finally {
@@ -89,6 +119,14 @@ export default function HomePage() {
void fetchRecentTasks();
}, []);
+ useEffect(() => {
+ if (typeof window === "undefined") {
+ return;
+ }
+
+ window.localStorage.setItem(RECENT_CARD_READ_STORAGE_KEY, JSON.stringify(readCardIds));
+ }, [readCardIds]);
+
const quickEntries = useMemo(
() => [
{
@@ -96,13 +134,15 @@ export default function HomePage() {
icon: ,
description: ["实时语音转文字", "同步翻译,智能总结要点"],
accent: "violet",
- onClick: () => navigate("/meeting-live-create")
+ badge: "会议神器",
+ onClick: () => navigate("/meetings?action=create&type=realtime")
},
{
title: "上传音视频",
icon: ,
description: ["音视频转文字", "区分发言人,一键导出"],
accent: "cyan",
+ badge: "iMeeting",
onClick: () => navigate("/meetings?create=true")
}
],
@@ -111,6 +151,15 @@ export default function HomePage() {
const recentCards = useMemo(() => buildRecentCards(recentTasks), [recentTasks]);
+ const handleRecentCardClick = (card: RecentCard) => {
+ const cardId = String(card.id);
+ setReadCardIds((prev) => (prev.includes(cardId) ? prev : [...prev, cardId]));
+
+ if (typeof card.id === "number") {
+ navigate(`/meetings/${card.id}`);
+ }
+ };
+
return (
{/* Massive Abstract Background Sphere matching the design/img.png */}
@@ -122,7 +171,19 @@ export default function HomePage() {