Files
nj/docs/opendesign/screens/desktop/discover.html
iven b320641d9c
Some checks failed
Main Merge / backend (push) Has been cancelled
Main Merge / frontend (push) Has been cancelled
fix(app): 全链路验证修复 — 编译错误/CORS/迁移/启动脚本
前端修复:
- calendar_page: 移除不存在的 JournalEntry.content getter
- responsive_scaffold: 移除不存在的 notchThickness 参数
- splash_page: SingleTickerProvider → TickerProvider (多 AnimationController)
- profile_page: UserRoleType.name → .code (修复运行时崩溃)
- 导入缺失的 user.dart

后端修复:
- class_service: generate_class_code 取 UUID 后6位(随机部分)避免碰撞
- diary_role_seed: 移除不存在的 id 列,使用复合主键 ON CONFLICT

基础设施:
- config/default.toml: CORS 改为通配符(开发模式)
- scripts/dev.sh: 统一启动脚本(自动清理端口)
- docs/opendesign/: Open Design 设计规格 HTML 原型稿

验证结果: flutter analyze 0 error, cargo test 77/77 通过, 17个页面全部渲染正常
2026-06-02 01:03:58 +08:00

649 lines
22 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=1440, height=900, initial-scale=1">
<title>暖记 — 桌面端发现</title>
<link rel="stylesheet" href="../css/tokens.css">
<link rel="stylesheet" href="../css/components.css">
<link rel="stylesheet" href="shared.css">
<style>
body {
width: 1440px;
height: 900px;
overflow: hidden;
background: var(--bg);
font-family: var(--font-body);
}
.main-content { height: 900px; padding-top: 32px; }
.content-inner {
padding: var(--space-6) var(--space-10);
max-width: none;
}
/* Search bar */
.search-bar {
display: flex;
align-items: center;
gap: var(--space-3);
background: var(--surface);
border-radius: var(--radius-pill);
padding: var(--space-3) var(--space-5);
margin-bottom: var(--space-6);
border: 1px solid var(--border-soft);
box-shadow: var(--elev-soft);
transition: border-color var(--motion-fast) var(--ease-standard);
}
.search-bar:focus-within {
border-color: var(--accent);
}
.search-bar svg {
width: 20px;
height: 20px;
color: var(--muted);
flex-shrink: 0;
}
.search-bar input {
flex: 1;
border: none;
background: none;
font-family: var(--font-body);
font-size: var(--text-md);
color: var(--fg);
outline: none;
}
.search-bar input::placeholder {
color: var(--meta);
}
/* Hero inspiration card */
.inspiration-hero {
background: linear-gradient(135deg, var(--accent) 0%, var(--tertiary) 100%);
border-radius: var(--radius-lg);
padding: var(--space-8) var(--space-10);
margin-bottom: var(--space-6);
position: relative;
overflow: hidden;
cursor: pointer;
display: flex;
align-items: center;
gap: var(--space-8);
transition: transform var(--motion-fast) var(--ease-bounce);
}
.inspiration-hero:hover { transform: translateY(-2px); }
.inspiration-hero::before {
content: '';
position: absolute;
top: -50px;
right: -50px;
width: 200px;
height: 200px;
border-radius: 50%;
background: rgba(255,255,255,0.12);
}
.inspiration-hero::after {
content: '';
position: absolute;
bottom: -30px;
left: 60px;
width: 120px;
height: 120px;
border-radius: 50%;
background: rgba(255,255,255,0.08);
}
.inspiration-label {
font-size: var(--text-xs);
color: rgba(255,255,255,0.7);
font-weight: 500;
margin-bottom: var(--space-2);
text-transform: uppercase;
letter-spacing: 0.5px;
}
.inspiration-thumb {
width: 120px;
height: 120px;
border-radius: var(--radius-md);
background: rgba(255,255,255,0.2);
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: center;
font-size: 52px;
backdrop-filter: blur(4px);
}
.inspiration-info {
flex: 1;
min-width: 0;
position: relative;
z-index: 1;
}
.inspiration-title {
font-family: var(--font-display);
font-size: var(--text-2xl);
font-weight: 700;
color: white;
margin-bottom: var(--space-2);
}
.inspiration-desc {
font-size: var(--text-base);
color: rgba(255,255,255,0.8);
margin-bottom: var(--space-3);
max-width: 500px;
}
.inspiration-author {
font-size: var(--text-sm);
color: rgba(255,255,255,0.7);
}
.inspiration-action {
display: inline-flex;
align-items: center;
gap: var(--space-2);
padding: var(--space-3) var(--space-5);
background: white;
color: var(--accent);
border: none;
border-radius: var(--radius-pill);
font-family: var(--font-display);
font-size: var(--text-base);
font-weight: 600;
cursor: pointer;
transition: all var(--motion-fast) var(--ease-bounce);
flex-shrink: 0;
position: relative;
z-index: 1;
}
.inspiration-action:hover { transform: scale(1.05); }
.inspiration-action:focus-visible { box-shadow: var(--focus-ring); outline: none; }
.inspiration-action svg { width: 18px; height: 18px; }
/* Three-column grid */
.discover-grid {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
gap: var(--space-6);
}
/* Section heading */
.section-heading {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: var(--space-4);
}
.section-heading h3 {
font-family: var(--font-display);
font-size: var(--text-lg);
font-weight: 700;
}
.section-heading .more {
font-size: var(--text-sm);
color: var(--accent);
cursor: pointer;
background: none;
border: none;
font-weight: 500;
min-height: 44px;
display: inline-flex;
align-items: center;
}
.section-heading .more:focus-visible {
box-shadow: var(--focus-ring);
outline: none;
border-radius: 4px;
}
/* Topics */
.topics-scroll {
display: flex;
flex-wrap: wrap;
gap: var(--space-2);
}
.topic-chip {
display: inline-flex;
align-items: center;
gap: var(--space-1);
padding: var(--space-2) var(--space-4);
border-radius: var(--radius-pill);
font-size: var(--text-sm);
font-weight: 500;
white-space: nowrap;
cursor: pointer;
border: 1px solid var(--border);
background: var(--surface);
color: var(--fg-2);
transition: all var(--motion-fast) var(--ease-standard);
box-shadow: var(--elev-soft);
}
.topic-chip:hover {
border-color: var(--accent);
color: var(--accent);
}
.topic-chip:focus-visible {
box-shadow: var(--focus-ring);
outline: none;
}
.topic-chip.active {
background: var(--accent);
color: var(--accent-on);
border-color: var(--accent);
}
.topic-chip .emoji {
font-size: 16px;
}
/* Templates grid */
.templates-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: var(--space-3);
}
.template-card {
background: var(--surface);
border-radius: var(--radius-md);
overflow: hidden;
box-shadow: var(--elev-soft);
border: 1px solid var(--border-soft);
cursor: pointer;
transition: transform var(--motion-fast) var(--ease-standard);
}
.template-card:hover { transform: translateY(-2px); }
.template-card:active { transform: scale(0.97); }
.template-thumb {
height: 80px;
display: flex;
align-items: center;
justify-content: center;
font-size: 28px;
}
.template-info {
padding: var(--space-3);
}
.template-name {
font-family: var(--font-display);
font-size: var(--text-sm);
font-weight: 600;
color: var(--fg);
margin-bottom: var(--space-1);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.template-users {
font-size: var(--text-xs);
color: var(--muted);
display: flex;
align-items: center;
gap: 4px;
}
.template-users svg {
width: 12px;
height: 12px;
}
/* Expert diary cards */
.experts-list {
display: flex;
flex-direction: column;
gap: var(--space-3);
}
.expert-card {
background: var(--surface);
border-radius: var(--radius-md);
padding: var(--space-4);
box-shadow: var(--elev-soft);
border: 1px solid var(--border-soft);
display: flex;
gap: var(--space-3);
cursor: pointer;
transition: transform var(--motion-fast) var(--ease-standard);
}
.expert-card:hover { transform: translateY(-2px); }
.expert-card:active { transform: scale(0.98); }
.expert-avatar {
width: 48px;
height: 48px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 22px;
flex-shrink: 0;
}
.expert-info {
flex: 1;
min-width: 0;
}
.expert-name {
font-size: var(--text-xs);
color: var(--muted);
margin-bottom: 2px;
}
.expert-title {
font-family: var(--font-display);
font-size: var(--text-base);
font-weight: 600;
color: var(--fg);
margin-bottom: 4px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.expert-preview {
font-size: var(--text-sm);
color: var(--muted);
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
line-height: 1.5;
}
.expert-likes {
display: flex;
align-items: center;
gap: 4px;
font-size: var(--text-xs);
color: var(--muted);
flex-shrink: 0;
margin-top: 4px;
}
.expert-likes svg {
width: 14px;
height: 14px;
color: var(--rose);
}
/* Empty state */
.empty-state {
display: none;
flex-direction: column;
align-items: center;
justify-content: center;
padding: var(--space-10) var(--space-5);
text-align: center;
}
.empty-state.visible {
display: flex;
}
.empty-state .icon {
font-size: 48px;
margin-bottom: var(--space-4);
opacity: 0.6;
}
.empty-state .text {
font-family: var(--font-display);
font-size: var(--text-md);
font-weight: 600;
color: var(--fg-2);
margin-bottom: var(--space-2);
}
.empty-state .subtext {
font-size: var(--text-sm);
color: var(--muted);
}
/* Animations */
@keyframes fadeIn {
from { opacity: 0; transform: translateY(12px); }
to { opacity: 1; transform: translateY(0); }
}
.anim-fade { animation: fadeIn 0.5s var(--ease-standard) both; }
@media (prefers-reduced-motion: reduce) {
.inspiration-hero,
.template-card,
.expert-card,
.topic-chip,
.inspiration-action {
transition: none;
}
.anim-fade { animation: none; }
}
</style>
</head>
<body>
<!-- Status bar -->
<div class="desktop-statusbar">
<div class="traffic-lights">
<div class="dot" style="background:#FF5F57"></div>
<div class="dot" style="background:#FEBC2E"></div>
<div class="dot" style="background:#28C840"></div>
</div>
<span>暖记 Warm Notes</span>
<span style="font-variant-numeric:tabular-nums">14:32</span>
</div>
<!-- Sidebar -->
<nav class="sidebar" aria-label="侧边导航">
<div class="sidebar-brand">
<div class="sidebar-logo">📖</div>
<div>
<div class="sidebar-brand-text">暖记</div>
<div class="sidebar-brand-sub">Warm Notes</div>
</div>
</div>
<div class="sidebar-nav">
<button class="sidebar-nav-item" aria-label="首页">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M3 9l9-7 9 7v11a2 2 0 01-2 2H5a2 2 0 01-2-2z"/><polyline points="9 22 9 12 15 12 15 22"/></svg>
首页
</button>
<button class="sidebar-nav-item" aria-label="日历">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><rect x="3" y="4" width="18" height="18" rx="2"/><line x1="16" y1="2" x2="16" y2="6"/><line x1="8" y1="2" x2="8" y2="6"/><line x1="3" y1="10" x2="21" y2="10"/></svg>
日历
</button>
<button class="sidebar-nav-item" aria-label="心情追踪">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M20.84 4.61a5.5 5.5 0 00-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 00-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 000-7.78z"/></svg>
心情追踪
</button>
<button class="sidebar-nav-item" aria-label="贴纸库">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><rect x="3" y="3" width="18" height="18" rx="3"/><circle cx="9" cy="10" r="1.5" fill="currentColor"/><circle cx="15" cy="10" r="1.5" fill="currentColor"/><path d="M9 15c.8.8 2.2 1.2 3 1.2s2.2-.4 3-1.2"/></svg>
贴纸库
</button>
<button class="sidebar-nav-item" aria-label="模板">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><rect x="3" y="3" width="7" height="7" rx="1"/><rect x="14" y="3" width="7" height="7" rx="1"/><rect x="3" y="14" width="7" height="7" rx="1"/><rect x="14" y="14" width="7" height="7" rx="1"/></svg>
模板
</button>
<button class="sidebar-nav-item active" aria-label="发现">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>
发现
</button>
</div>
<button class="sidebar-write-btn" aria-label="写日记">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"><path d="M12 5v14M5 12h14"/></svg>
写日记
</button>
<div class="sidebar-footer">
<div class="sidebar-avatar">🐱</div>
<div>
<div class="sidebar-user-name">小暖</div>
<div class="sidebar-user-streak">连续记录 12 天</div>
</div>
</div>
</nav>
<!-- Main Content -->
<div class="main-content">
<!-- Top bar -->
<div class="desktop-topbar">
<div class="desktop-topbar-title">发现</div>
<div class="desktop-topbar-actions">
<button class="topbar-action-btn" aria-label="搜索">
<svg viewBox="0 0 18 18" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"><circle cx="8" cy="8" r="6"/><line x1="12.5" y1="12.5" x2="16" y2="16"/></svg>
</button>
<button class="topbar-action-btn" aria-label="筛选">
<svg viewBox="0 0 18 18" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"><path d="M4 6h10M4 9h10M4 12h7"/></svg>
</button>
</div>
</div>
<div class="content-inner">
<!-- Search bar -->
<div class="search-bar anim-fade" data-od-id="discover-search">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true">
<circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/>
</svg>
<input type="text" placeholder="搜索日记、模板、话题..." aria-label="搜索日记、模板、话题">
</div>
<!-- Empty state -->
<div class="empty-state" data-od-id="discover-empty" role="status">
<span class="icon" aria-hidden="true">🔍</span>
<div class="text">没有找到相关内容</div>
<div class="subtext">换个关键词试试吧</div>
</div>
<!-- Hero inspiration -->
<section data-od-id="discover-inspiration" aria-label="每日灵感" class="anim-fade" style="animation-delay:0.1s">
<div class="inspiration-hero" role="article" aria-label="今日灵感推荐:旅行手账排版技巧">
<div class="inspiration-thumb" aria-hidden="true">🗺️</div>
<div class="inspiration-info">
<div class="inspiration-label">今日推荐</div>
<div class="inspiration-title">旅行手账排版技巧</div>
<div class="inspiration-desc">学习如何用简单的素材和排版技巧,让你的旅行手账充满故事感。从照片布局到文字装饰,一步步教你做出精美的旅行记录。</div>
<div class="inspiration-author">by 手账达人小林</div>
</div>
<button class="inspiration-action" aria-label="查看详情">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M5 12h14M12 5l7 7-7 7"/></svg>
查看详情
</button>
</div>
</section>
<!-- Three-column grid -->
<div class="discover-grid anim-fade" style="animation-delay:0.2s">
<!-- Column 1: Hot topics -->
<section data-od-id="discover-topics" aria-label="热门话题">
<div class="section-heading">
<h3>热门话题</h3>
<button class="more" aria-label="查看更多话题">更多</button>
</div>
<div class="topics-scroll" role="list">
<button class="topic-chip active" role="listitem" aria-label="话题:期末备考">
<span class="emoji" aria-hidden="true">📚</span>#期末备考
</button>
<button class="topic-chip" role="listitem" aria-label="话题:读书笔记">
<span class="emoji" aria-hidden="true">📖</span>#读书笔记
</button>
<button class="topic-chip" role="listitem" aria-label="话题:旅行手账">
<span class="emoji" aria-hidden="true">✈️</span>#旅行手账
</button>
<button class="topic-chip" role="listitem" aria-label="话题:美食记录">
<span class="emoji" aria-hidden="true">🍜</span>#美食记录
</button>
<button class="topic-chip" role="listitem" aria-label="话题:校园生活">
<span class="emoji" aria-hidden="true">🎓</span>#校园生活
</button>
<button class="topic-chip" role="listitem" aria-label="话题:自我成长">
<span class="emoji" aria-hidden="true">🌱</span>#自我成长
</button>
</div>
</section>
<!-- Column 2: Featured templates -->
<section data-od-id="discover-templates" aria-label="精选模板">
<div class="section-heading">
<h3>精选模板</h3>
<button class="more" aria-label="查看更多模板">更多</button>
</div>
<div class="templates-grid">
<div class="template-card" role="article" aria-label="模板:考试周复习计划">
<div class="template-thumb" style="background: var(--tertiary-soft)" aria-hidden="true">📝</div>
<div class="template-info">
<div class="template-name">考试周复习计划</div>
<div class="template-users">
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"><path d="M10 3a3 3 0 11-6 0 3 3 0 016 0zM2 14v-1a4 4 0 014-4h4a4 4 0 014 4v1"/></svg>
2.3k 人使用
</div>
</div>
</div>
<div class="template-card" role="article" aria-label="模板:读书笔记手账">
<div class="template-thumb" style="background: var(--secondary-soft)" aria-hidden="true">📖</div>
<div class="template-info">
<div class="template-name">读书笔记手账</div>
<div class="template-users">
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"><path d="M10 3a3 3 0 11-6 0 3 3 0 016 0zM2 14v-1a4 4 0 014-4h4a4 4 0 014 4v1"/></svg>
1.8k 人使用
</div>
</div>
</div>
<div class="template-card" role="article" aria-label="模板:月度心情打卡">
<div class="template-thumb" style="background: var(--rose-soft)" aria-hidden="true">💫</div>
<div class="template-info">
<div class="template-name">月度心情打卡</div>
<div class="template-users">
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"><path d="M10 3a3 3 0 11-6 0 3 3 0 016 0zM2 14v-1a4 4 0 014-4h4a4 4 0 014 4v1"/></svg>
3.1k 人使用
</div>
</div>
</div>
<div class="template-card" role="article" aria-label="模板:旅行日记模板">
<div class="template-thumb" style="background: var(--surface-warm)" aria-hidden="true">🗺️</div>
<div class="template-info">
<div class="template-name">旅行日记模板</div>
<div class="template-users">
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"><path d="M10 3a3 3 0 11-6 0 3 3 0 016 0zM2 14v-1a4 4 0 014-4h4a4 4 0 014 4v1"/></svg>
1.5k 人使用
</div>
</div>
</div>
</div>
</section>
<!-- Column 3: Expert diaries -->
<section data-od-id="discover-experts" aria-label="达人日记">
<div class="section-heading">
<h3>达人日记</h3>
<button class="more" aria-label="查看更多达人日记">更多</button>
</div>
<div class="experts-list">
<div class="expert-card" role="article" aria-label="达人日记我的大学生活第100天">
<div class="expert-avatar" style="background: var(--tertiary-soft)" aria-hidden="true">🌸</div>
<div class="expert-info">
<div class="expert-name">小暖</div>
<div class="expert-title">我的大学生活第100天</div>
<div class="expert-preview">不知不觉大学生活已经过了100天了从最初的迷茫到现在的适应记录下这段珍贵的时光...</div>
</div>
<div class="expert-likes">
<svg viewBox="0 0 16 16" fill="currentColor"><path d="M8 14s-5.5-3.5-5.5-7.5C2.5 3.5 4 2 5.75 2 6.86 2 7.86 2.56 8 3.5 8.14 2.56 9.14 2 10.25 2 12 2 13.5 3.5 13.5 6.5 13.5 10.5 8 14 8 14z"/></svg>
328
</div>
</div>
<div class="expert-card" role="article" aria-label="达人日记考研倒计时30天手账">
<div class="expert-avatar" style="background: var(--secondary-soft)" aria-hidden="true">✏️</div>
<div class="expert-info">
<div class="expert-name">手账少女</div>
<div class="expert-title">考研倒计时30天手账</div>
<div class="expert-preview">最后冲刺阶段,用手账记录每一天的复习进度和心情变化,给自己加油打气...</div>
</div>
<div class="expert-likes">
<svg viewBox="0 0 16 16" fill="currentColor"><path d="M8 14s-5.5-3.5-5.5-7.5C2.5 3.5 4 2 5.75 2 6.86 2 7.86 2.56 8 3.5 8.14 2.56 9.14 2 10.25 2 12 2 13.5 3.5 13.5 6.5 13.5 10.5 8 14 8 14z"/></svg>
512
</div>
</div>
<div class="expert-card" role="article" aria-label="达人日记:秋日校园手账记录">
<div class="expert-avatar" style="background: var(--rose-soft)" aria-hidden="true">🍂</div>
<div class="expert-info">
<div class="expert-name">阿月</div>
<div class="expert-title">秋日校园手账记录</div>
<div class="expert-preview">银杏叶黄了,校园美得像画一样。收集了几片落叶夹在手账里,记录这个温柔的秋天...</div>
</div>
<div class="expert-likes">
<svg viewBox="0 0 16 16" fill="currentColor"><path d="M8 14s-5.5-3.5-5.5-7.5C2.5 3.5 4 2 5.75 2 6.86 2 7.86 2.56 8 3.5 8.14 2.56 9.14 2 10.25 2 12 2 13.5 3.5 13.5 6.5 13.5 10.5 8 14 8 14z"/></svg>
276
</div>
</div>
</div>
</section>
</div>
</div>
</div>
<script src="../../js/theme-switcher.js"></script>
</body>
</html>