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

520 lines
17 KiB
HTML

<!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;
}
/* Global focus styles */
button:focus-visible, a:focus-visible, [role="tab"]:focus-visible {
box-shadow: 0 0 0 3px var(--focus-ring);
outline: none;
}
.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; }
/* Greeting header */
.greeting {
margin-bottom: var(--space-5);
display: flex;
align-items: flex-start;
justify-content: space-between;
}
.greeting-left {
flex: 1;
}
.greeting-date {
font-size: var(--text-sm);
color: var(--muted);
font-weight: 500;
margin-bottom: var(--space-1);
}
.greeting-text {
font-family: var(--font-display);
font-size: var(--text-2xl);
font-weight: 700;
color: var(--fg);
}
.greeting-text span { color: var(--accent); }
.greeting-search-btn {
width: 44px;
height: 44px;
border-radius: var(--radius-pill);
border: none;
background: var(--surface);
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
color: var(--fg-2);
box-shadow: var(--elev-soft);
border: 1px solid var(--border-soft);
transition: all var(--motion-fast);
flex-shrink: 0;
margin-top: var(--space-3);
}
.greeting-search-btn:hover { color: var(--accent); border-color: var(--accent); }
.greeting-search-btn svg { width: 20px; height: 20px; }
/* Mood quick-select */
.mood-section {
background: var(--surface);
border-radius: var(--radius-md);
padding: var(--space-4) var(--space-5);
margin-bottom: var(--space-5);
box-shadow: var(--elev-soft);
border: 1px solid var(--border-soft);
}
.mood-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: var(--space-3);
}
.mood-header span {
font-family: var(--font-handwritten);
font-size: var(--text-md);
font-weight: 600;
color: var(--fg-2);
}
.mood-header .weather {
font-size: var(--text-sm);
color: var(--muted);
display: flex;
align-items: center;
gap: 4px;
}
.mood-options {
display: flex;
justify-content: space-between;
}
.mood-opt {
display: flex;
flex-direction: column;
align-items: center;
gap: 4px;
background: none;
border: none;
cursor: pointer;
padding: var(--space-2);
border-radius: var(--radius-sm);
min-width: 44px;
min-height: 44px;
transition: all var(--motion-fast) var(--ease-bounce);
}
.mood-opt:hover { background: var(--surface-warm); }
.mood-opt.selected { background: var(--surface-warm); }
.mood-opt .face { font-size: 28px; }
.mood-opt .label { font-size: 11px; color: var(--muted); }
.mood-opt.selected .label { color: var(--accent); font-weight: 600; }
/* Today's journal entry card */
.today-card {
background: linear-gradient(135deg, var(--accent) 0%, var(--tertiary) 100%);
border-radius: var(--radius-lg);
padding: var(--space-6);
margin-bottom: var(--space-5);
position: relative;
overflow: hidden;
cursor: pointer;
transition: transform var(--motion-fast) var(--ease-bounce);
}
.today-card:hover { transform: translateY(-2px); }
.today-card:active { transform: scale(0.98); }
.today-card::before {
content: '';
position: absolute;
top: -30px;
right: -30px;
width: 120px;
height: 120px;
border-radius: 50%;
background: rgba(255,255,255,0.12);
}
.today-card::after {
content: '';
position: absolute;
bottom: -20px;
left: 40px;
width: 80px;
height: 80px;
border-radius: 50%;
background: rgba(255,255,255,0.08);
}
.today-card .label {
font-family: var(--font-handwritten);
font-size: var(--text-sm);
color: rgba(255,248,240,0.85);
font-weight: 600;
margin-bottom: var(--space-2);
}
.today-card .title {
font-family: var(--font-display);
font-size: var(--text-xl);
font-weight: 700;
color: var(--accent-on);
margin-bottom: var(--space-2);
}
.today-card .prompt {
font-size: var(--text-sm);
color: rgba(255,248,240,0.75);
}
.today-card .write-btn {
position: absolute;
bottom: var(--space-5);
right: var(--space-5);
width: 48px;
height: 48px;
border-radius: 50%;
background: white;
border: none;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
cursor: pointer;
transition: transform var(--motion-fast) var(--ease-bounce);
}
.today-card .write-btn:hover { transform: scale(1.1); }
.today-card .write-btn svg { width: 22px; height: 22px; color: var(--accent); }
/* Recent entries */
.recent-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: var(--space-4);
}
.recent-header h3 {
font-family: var(--font-display);
font-size: var(--text-lg);
font-weight: 700;
}
.recent-header a {
font-size: var(--text-sm);
color: var(--accent);
text-decoration: none;
font-weight: 500;
min-height: 44px;
display: inline-flex;
align-items: center;
}
.entry-card {
background: var(--surface);
border-radius: var(--radius-md);
padding: var(--space-4);
margin-bottom: var(--space-3);
box-shadow: var(--elev-soft);
border: 1px solid var(--border-soft);
display: flex;
gap: var(--space-4);
cursor: pointer;
transition: transform var(--motion-fast) var(--ease-standard);
}
.entry-card:hover { transform: translateY(-1px); }
.entry-card:active { transform: scale(0.98); }
.entry-preview {
width: 72px;
height: 72px;
border-radius: var(--radius-sm);
background: var(--surface-warm);
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: center;
font-size: 28px;
overflow: hidden;
}
.entry-info {
flex: 1;
min-width: 0;
}
.entry-date {
font-size: var(--text-xs);
color: var(--muted);
margin-bottom: 4px;
}
.entry-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;
}
.entry-excerpt {
font-size: var(--text-sm);
color: var(--muted);
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
.entry-mood {
font-size: 16px;
flex-shrink: 0;
margin-top: 2px;
}
/* Writing streak */
.streak-badge {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 6px 14px;
background: var(--tertiary-soft);
border-radius: var(--radius-pill);
font-size: var(--text-sm);
font-weight: 600;
color: #B8860B;
margin-bottom: var(--space-5);
}
.streak-badge svg { width: 16px; height: 16px; }
/* Quick stats row */
.quick-stats {
display: flex;
gap: var(--space-3);
margin-bottom: var(--space-5);
}
.stat-card {
flex: 1;
background: var(--surface);
border-radius: var(--radius-md);
padding: var(--space-4);
text-align: center;
box-shadow: var(--elev-soft);
border: 1px solid var(--border-soft);
}
.stat-card .num {
font-family: var(--font-display);
font-size: var(--text-2xl);
font-weight: 700;
color: var(--fg);
}
.stat-card .num.accent { color: var(--accent); }
.stat-card .num.green { color: var(--secondary); }
.stat-card .label {
font-size: var(--text-xs);
color: var(--muted);
margin-top: 4px;
}
/* Empty state */
.empty-state {
display: none; /* Shown via JS when no entries exist */
flex-direction: column;
align-items: center;
justify-content: center;
padding: var(--space-10) var(--space-5);
text-align: center;
}
.empty-state .empty-circle {
width: 80px;
height: 80px;
border-radius: 50%;
border: 2px dashed var(--border);
display: flex;
align-items: center;
justify-content: center;
margin-bottom: var(--space-4);
font-size: 32px;
opacity: 0.6;
}
.empty-state .empty-text {
font-size: var(--text-base);
color: var(--muted);
font-weight: 500;
margin-bottom: var(--space-2);
}
.empty-state .empty-hint {
font-size: var(--text-sm);
color: var(--meta);
}
</style>
</head>
<body>
<div class="content-area">
<!-- Greeting -->
<div class="greeting anim-fade">
<div class="greeting-left">
<div class="greeting-date">2026年5月31日 · 星期日</div>
<div class="greeting-text">下午好,<span>小暖</span></div>
</div>
<a href="search.html" class="greeting-search-btn" aria-label="搜索" title="搜索日记">
<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>
</a>
</div>
<!-- Writing streak -->
<div class="streak-badge anim-fade" style="animation-delay: 0.1s">
<svg viewBox="0 0 16 16" fill="#B8860B" aria-hidden="true"><path d="M8 1l2 5h5l-4 3 1.5 5L8 11 3.5 14 5 9 1 6h5z"/></svg>
连续记录 12 天
</div>
<!-- Mood check - standardized 5 moods: happy, calm, sad, angry, thinking -->
<div class="mood-section anim-fade" style="animation-delay: 0.15s">
<div class="mood-header">
<span>今天心情如何?</span>
<div class="weather">
<svg width="16" height="16" viewBox="0 0 16 16" fill="var(--tertiary)" aria-hidden="true">
<circle cx="8" cy="8" r="5"/><path d="M8 1v2M8 13v2M1 8h2M13 8h2M3.05 3.05l1.41 1.41M11.54 11.54l1.41 1.41M3.05 12.95l1.41-1.41M11.54 4.46l1.41-1.41" stroke="var(--tertiary)" stroke-width="1.5" stroke-linecap="round"/>
</svg>
晴 26°
</div>
</div>
<div class="mood-options">
<button class="mood-opt selected" aria-label="开心"><span class="face">😊</span><span class="label">开心</span></button>
<button class="mood-opt" aria-label="平静"><span class="face">😐</span><span class="label">平静</span></button>
<button class="mood-opt" aria-label="难过"><span class="face">😢</span><span class="label">难过</span></button>
<button class="mood-opt" aria-label="生气"><span class="face">😡</span><span class="label">生气</span></button>
<button class="mood-opt" aria-label="思考"><span class="face">🤔</span><span class="label">思考</span></button>
</div>
</div>
<!-- Today's journal -->
<div class="today-card anim-slide" style="animation-delay: 0.2s">
<div class="label">今天的日记</div>
<div class="title">写点什么吧...</div>
<div class="prompt">记录一个温暖的瞬间,或者今天发生的小事</div>
<button class="write-btn" aria-label="开始写日记">
<svg viewBox="0 0 22 22" fill="none" aria-hidden="true">
<path d="M11 5v12M5 11h12" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"/>
</svg>
</button>
</div>
<!-- Quick stats -->
<div class="quick-stats anim-fade" style="animation-delay: 0.25s">
<div class="stat-card">
<div class="num accent">28</div>
<div class="label">本月日记</div>
</div>
<div class="stat-card">
<div class="num green">12</div>
<div class="label">连续天数</div>
</div>
<div class="stat-card">
<div class="num">156</div>
<div class="label">总日记数</div>
</div>
</div>
<!-- Recent entries -->
<div class="recent-header anim-fade" style="animation-delay: 0.3s">
<h3>最近记录</h3>
<a href="#" aria-label="查看全部日记">查看全部</a>
</div>
<div class="entry-card anim-fade" style="animation-delay: 0.35s">
<div class="entry-preview">🌸</div>
<div class="entry-info">
<div class="entry-date">5月30日 · 周六</div>
<div class="entry-title">图书馆的午后</div>
<div class="entry-excerpt">今天在图书馆自习,窗外的阳光洒进来,暖暖的。复习了高数第三章...</div>
</div>
<div class="entry-mood">😊</div>
</div>
<div class="entry-card anim-fade" style="animation-delay: 0.4s">
<div class="entry-preview" style="background: var(--secondary-soft)">🍃</div>
<div class="entry-info">
<div class="entry-date">5月29日 · 周五</div>
<div class="entry-title">和舍友的火锅局</div>
<div class="entry-excerpt">考完试和舍友们去吃了火锅庆祝,大家都好开心,聊了很多有趣的事...</div>
</div>
<div class="entry-mood">😊</div>
</div>
<div class="entry-card anim-fade" style="animation-delay: 0.45s">
<div class="entry-preview" style="background: var(--tertiary-soft)">📝</div>
<div class="entry-info">
<div class="entry-date">5月28日 · 周四</div>
<div class="entry-title">期末考试第一天</div>
<div class="entry-excerpt">终于考完了英语,感觉发挥还可以。明天继续加油!给自己打个气...</div>
</div>
<div class="entry-mood">😐</div>
</div>
<div class="entry-card anim-fade" style="animation-delay: 0.5s">
<div class="entry-preview" style="background: var(--rose-soft)">🌙</div>
<div class="entry-info">
<div class="entry-date">5月27日 · 周三</div>
<div class="entry-title">夜跑打卡</div>
<div class="entry-excerpt">晚上去操场跑了三圈,吹着晚风特别舒服。回来洗了个热水澡...</div>
</div>
<div class="entry-mood">😐</div>
</div>
<!-- Empty state (shown when no entries exist, hidden by default) -->
<div class="empty-state" id="emptyEntries">
<div class="empty-circle">📝</div>
<div class="empty-text">还没有日记</div>
<div class="empty-hint">点击上方按钮,开始记录你的第一篇日记吧</div>
</div>
<div style="height: var(--space-6)"></div>
</div>
<!-- Bottom Tab Bar -->
<nav class="tab-bar" role="tablist">
<button class="tab-item active" role="tab" aria-label="首页" aria-selected="true">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true"><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" role="tab" aria-label="日历" aria-selected="false">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true"><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" role="tab" aria-label="写日记" aria-selected="false" style="position:relative">
<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="var(--accent-on)" stroke-width="2.5" stroke-linecap="round" style="width:22px;height:22px" aria-hidden="true"><path d="M12 5v14M5 12h14"/></svg>
</div>
<span style="margin-top:2px">写日记</span>
</button>
<button class="tab-item" role="tab" aria-label="发现" aria-selected="false">
<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>
<span>发现</span>
</button>
<button class="tab-item" role="tab" aria-label="我的" aria-selected="false">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true"><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" aria-hidden="true"></div>
<div class="home-indicator" style="position:absolute;bottom:8px;left:50%;transform:translateX(-50%);z-index:101" aria-hidden="true"></div>
<script src="../js/theme-switcher.js"></script>
</body>
</html>