前端修复: - 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个页面全部渲染正常
520 lines
17 KiB
HTML
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>
|