Files
nj/docs/opendesign/screens/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

556 lines
19 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=390, height=844, initial-scale=1, viewport-fit=cover">
<title>暖记 — 发现</title>
<link href="https://fonts.googleapis.com/css2?family=Caveat:wght@400;600;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="../css/tokens.css">
<link rel="stylesheet" href="../css/components.css">
<style>
body {
width: 390px;
height: 844px;
overflow: hidden;
background: var(--bg);
position: relative;
}
.content-area {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: calc(var(--tab-height) + var(--safe-bottom));
overflow-y: auto;
padding: 0 var(--space-5);
padding-top: calc(var(--safe-top) + var(--space-2));
scrollbar-width: none;
}
.content-area::-webkit-scrollbar { display: 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-4);
margin-bottom: var(--space-5);
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-base);
color: var(--fg);
outline: none;
}
.search-bar input::placeholder {
color: var(--meta);
}
/* Daily inspiration card */
.inspiration-card {
background: linear-gradient(135deg, var(--accent) 0%, var(--tertiary) 100%);
border-radius: var(--radius-lg);
padding: var(--space-5);
margin-bottom: var(--space-5);
position: relative;
overflow: hidden;
cursor: pointer;
transition: transform var(--motion-fast) var(--ease-bounce);
}
.inspiration-card:hover { transform: translateY(-2px); }
.inspiration-card:active { transform: scale(0.98); }
.inspiration-card::before {
content: '';
position: absolute;
top: -30px;
right: -30px;
width: 120px;
height: 120px;
border-radius: 50%;
background: rgba(255,255,255,0.12);
}
.inspiration-card::after {
content: '';
position: absolute;
bottom: -20px;
left: 30px;
width: 80px;
height: 80px;
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-body {
display: flex;
gap: var(--space-4);
align-items: center;
}
.inspiration-thumb {
width: 80px;
height: 80px;
border-radius: var(--radius-sm);
background: rgba(255,255,255,0.2);
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: center;
font-size: 36px;
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-lg);
font-weight: 700;
color: white;
margin-bottom: var(--space-1);
}
.inspiration-author {
font-size: var(--text-sm);
color: rgba(255,255,255,0.75);
}
/* Topics section */
.topics-section {
margin-bottom: var(--space-5);
}
.topics-scroll {
display: flex;
gap: var(--space-2);
overflow-x: auto;
scrollbar-width: none;
padding-bottom: var(--space-2);
}
.topics-scroll::-webkit-scrollbar { display: none; }
.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);
min-height: var(--touch-min);
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);
margin-bottom: var(--space-5);
}
.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: 96px;
display: flex;
align-items: center;
justify-content: center;
font-size: 32px;
}
.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;
}
/* Experts diary list */
.experts-list {
display: flex;
flex-direction: column;
gap: var(--space-3);
margin-bottom: var(--space-6);
}
.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(-1px); }
.expert-card:active { transform: scale(0.98); }
.expert-avatar {
width: 44px;
height: 44px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 20px;
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: 2px;
}
.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);
}
/* Reduced motion */
@media (prefers-reduced-motion: reduce) {
.inspiration-card,
.template-card,
.expert-card,
.topic-chip {
transition: none;
}
.anim-fade,
.anim-slide {
animation: none;
}
}
</style>
</head>
<body>
<div class="content-area">
<!-- 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 (shown when search has no results) -->
<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>
<!-- Discover content -->
<div id="discover-content">
<!-- Daily inspiration -->
<section data-od-id="discover-inspiration" class="anim-fade" style="animation-delay: 0.1s">
<div class="section-heading">
<h3>每日灵感</h3>
<button class="more" aria-label="查看更多灵感">更多</button>
</div>
<div class="inspiration-card" role="article" aria-label="今日灵感推荐:旅行手账排版技巧">
<div class="inspiration-label">今日推荐</div>
<div class="inspiration-body">
<div class="inspiration-thumb" aria-hidden="true">🗺️</div>
<div class="inspiration-info">
<div class="inspiration-title">旅行手账排版技巧</div>
<div class="inspiration-author">by 手账达人小林</div>
</div>
</div>
</div>
</section>
<!-- Hot topics -->
<section class="topics-section" data-od-id="discover-topics" aria-label="热门话题">
<div class="section-heading anim-fade" style="animation-delay: 0.15s">
<h3>热门话题</h3>
<button class="more" aria-label="查看更多话题">更多</button>
</div>
<div class="topics-scroll anim-fade" style="animation-delay: 0.2s" 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>
<!-- Featured templates -->
<section data-od-id="discover-templates" aria-label="精选模板">
<div class="section-heading anim-fade" style="animation-delay: 0.25s">
<h3>精选模板</h3>
<button class="more" aria-label="查看更多模板">更多</button>
</div>
<div class="templates-grid anim-fade" style="animation-delay: 0.3s">
<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>
<!-- Expert diaries -->
<section data-od-id="discover-experts" aria-label="达人日记">
<div class="section-heading anim-fade" style="animation-delay: 0.35s">
<h3>达人日记</h3>
<button class="more" aria-label="查看更多达人日记">更多</button>
</div>
<div class="experts-list anim-fade" style="animation-delay: 0.4s">
<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 style="height: var(--space-6)"></div>
</div>
</div>
<!-- Bottom Tab Bar -->
<nav class="tab-bar" aria-label="底部导航">
<button class="tab-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>
<span>首页</span>
</button>
<button class="tab-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>
<span>日历</span>
</button>
<button class="tab-item" style="position:relative" aria-label="写日记">
<div style="width:44px;height:44px;border-radius:50%;background:var(--accent);display:flex;align-items:center;justify-content:center;margin-top:-16px;box-shadow:0 4px 12px var(--shadow-accent)">
<svg viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2.5" stroke-linecap="round" style="width:22px;height:22px"><path d="M12 5v14M5 12h14"/></svg>
</div>
<span style="margin-top:2px">写日记</span>
</button>
<button class="tab-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>
<span>发现</span>
</button>
<button class="tab-item" aria-label="我的">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M20 21v-2a4 4 0 00-4-4H8a4 4 0 00-4 4v2"/><circle cx="12" cy="7" r="4"/></svg>
<span>我的</span>
</button>
</nav>
<div class="dynamic-island"></div>
<div class="home-indicator" style="position:absolute;bottom:8px;left:50%;transform:translateX(-50%);z-index:101"></div>
<script src="../js/theme-switcher.js"></script>
</body>
</html>