前端修复: - 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个页面全部渲染正常
331 lines
13 KiB
HTML
331 lines
13 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; }
|
||
|
||
/* Profile header */
|
||
.profile-header {
|
||
text-align: center; padding: var(--space-5) 0;
|
||
margin-bottom: var(--space-5);
|
||
}
|
||
.avatar {
|
||
width: 80px; height: 80px; border-radius: 50%;
|
||
background: linear-gradient(135deg, var(--accent), var(--tertiary));
|
||
margin: 0 auto var(--space-3);
|
||
display: flex; align-items: center; justify-content: center;
|
||
font-size: 36px;
|
||
box-shadow: 0 4px 16px var(--shadow-input-focus);
|
||
}
|
||
.username {
|
||
font-family: var(--font-display);
|
||
font-size: var(--text-xl); font-weight: 700;
|
||
margin-bottom: 4px;
|
||
}
|
||
.bio {
|
||
font-size: var(--text-sm); color: var(--muted);
|
||
}
|
||
|
||
/* Stats bar */
|
||
.stats-bar {
|
||
display: flex; background: var(--surface);
|
||
border-radius: var(--radius-md); padding: var(--space-4);
|
||
box-shadow: var(--elev-soft); border: 1px solid var(--border-soft);
|
||
margin-bottom: var(--space-5);
|
||
}
|
||
.stat-item {
|
||
flex: 1; text-align: center;
|
||
border-right: 1px solid var(--border-soft);
|
||
}
|
||
.stat-item:last-child { border-right: none; }
|
||
.stat-item .num {
|
||
font-family: var(--font-display);
|
||
font-size: var(--text-xl); font-weight: 700; color: var(--fg);
|
||
}
|
||
.stat-item .num.accent { color: var(--accent); }
|
||
.stat-item .num.green { color: var(--secondary); }
|
||
.stat-item .label {
|
||
font-size: var(--text-xs); color: var(--muted); margin-top: 2px;
|
||
}
|
||
|
||
/* Achievement badges */
|
||
.badge-section {
|
||
margin-bottom: var(--space-5);
|
||
}
|
||
.badge-section h3 {
|
||
font-family: var(--font-display);
|
||
font-size: var(--text-lg); font-weight: 700;
|
||
margin-bottom: var(--space-4);
|
||
}
|
||
.badge-row {
|
||
display: flex; gap: var(--space-3); overflow-x: auto;
|
||
padding-bottom: var(--space-2);
|
||
scrollbar-width: none;
|
||
}
|
||
.badge-row::-webkit-scrollbar { display: none; }
|
||
.badge-item {
|
||
flex-shrink: 0; width: 80px;
|
||
display: flex; flex-direction: column;
|
||
align-items: center; gap: 6px;
|
||
}
|
||
.badge-icon {
|
||
width: 56px; height: 56px; border-radius: 50%;
|
||
display: flex; align-items: center; justify-content: center;
|
||
font-size: 24px;
|
||
}
|
||
.badge-name {
|
||
font-size: 11px; color: var(--muted); text-align: center;
|
||
font-weight: 500;
|
||
}
|
||
.badge-item.earned .badge-name { color: var(--fg-2); }
|
||
|
||
/* Settings list */
|
||
.settings-section {
|
||
margin-bottom: var(--space-5);
|
||
}
|
||
.settings-section h3 {
|
||
font-family: var(--font-display);
|
||
font-size: var(--text-lg); font-weight: 700;
|
||
margin-bottom: var(--space-3);
|
||
}
|
||
.settings-card {
|
||
background: var(--surface);
|
||
border-radius: var(--radius-md);
|
||
box-shadow: var(--elev-soft);
|
||
border: 1px solid var(--border-soft);
|
||
overflow: hidden;
|
||
}
|
||
.setting-item {
|
||
display: flex; align-items: center;
|
||
padding: var(--space-4) var(--space-5);
|
||
border-bottom: 1px solid var(--border-soft);
|
||
cursor: pointer;
|
||
transition: background var(--motion-fast);
|
||
min-height: 44px;
|
||
}
|
||
.setting-item:last-child { border-bottom: none; }
|
||
.setting-item:hover { background: var(--surface-warm); }
|
||
.setting-item .icon {
|
||
width: 36px; height: 36px; border-radius: var(--radius-sm);
|
||
display: flex; align-items: center; justify-content: center;
|
||
margin-right: var(--space-3); font-size: 18px; flex-shrink: 0;
|
||
}
|
||
.setting-item .text {
|
||
flex: 1;
|
||
}
|
||
.setting-item .text .title {
|
||
font-size: var(--text-base); font-weight: 500; color: var(--fg);
|
||
}
|
||
.setting-item .text .sub {
|
||
font-size: var(--text-xs); color: var(--muted); margin-top: 1px;
|
||
}
|
||
.setting-item .arrow {
|
||
color: var(--meta);
|
||
min-width: 44px; min-height: 44px;
|
||
display: flex; align-items: center; justify-content: center;
|
||
}
|
||
.setting-item .arrow svg { width: 16px; height: 16px; }
|
||
|
||
/* Toggle */
|
||
.toggle {
|
||
width: 44px; height: 26px; border-radius: 13px;
|
||
background: var(--border); position: relative;
|
||
cursor: pointer; transition: background var(--motion-fast);
|
||
}
|
||
.toggle.on { background: var(--accent); }
|
||
.toggle::after {
|
||
content: ''; position: absolute;
|
||
top: 3px; left: 3px;
|
||
width: 20px; height: 20px; border-radius: 50%;
|
||
background: white; transition: transform var(--motion-fast);
|
||
box-shadow: 0 1px 3px rgba(0,0,0,0.15);
|
||
}
|
||
.toggle.on::after { transform: translateX(18px); }
|
||
</style>
|
||
</head>
|
||
<body>
|
||
|
||
<div class="content-area">
|
||
|
||
<!-- Profile -->
|
||
<div class="profile-header anim-fade">
|
||
<div class="avatar" aria-hidden="true">🐹</div>
|
||
<div class="username">小暖</div>
|
||
<div class="bio">每一天都值得被温柔记录</div>
|
||
</div>
|
||
|
||
<!-- Stats -->
|
||
<div class="stats-bar anim-fade" style="animation-delay: 0.1s">
|
||
<div class="stat-item">
|
||
<div class="num accent">156</div>
|
||
<div class="label">总日记</div>
|
||
</div>
|
||
<div class="stat-item">
|
||
<div class="num green">12</div>
|
||
<div class="label">连续天数</div>
|
||
</div>
|
||
<div class="stat-item">
|
||
<div class="num">28</div>
|
||
<div class="label">本月日记</div>
|
||
</div>
|
||
<div class="stat-item">
|
||
<div class="num">342</div>
|
||
<div class="label">使用贴纸</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Achievements -->
|
||
<div class="badge-section anim-fade" style="animation-delay: 0.15s">
|
||
<h3>成就徽章</h3>
|
||
<div class="badge-row">
|
||
<div class="badge-item earned">
|
||
<div class="badge-icon" style="background:var(--tertiary-soft)" aria-hidden="true">🔥</div>
|
||
<div class="badge-name">7天连续</div>
|
||
</div>
|
||
<div class="badge-item earned">
|
||
<div class="badge-icon" style="background:var(--secondary-soft)" aria-hidden="true">📝</div>
|
||
<div class="badge-name">百篇日记</div>
|
||
</div>
|
||
<div class="badge-item earned">
|
||
<div class="badge-icon" style="background:var(--rose-soft)" aria-hidden="true">🌸</div>
|
||
<div class="badge-name">贴纸达人</div>
|
||
</div>
|
||
<div class="badge-item">
|
||
<div class="badge-icon" style="background:var(--border-soft);opacity:0.5" aria-hidden="true">🏆</div>
|
||
<div class="badge-name">年度记录</div>
|
||
</div>
|
||
<div class="badge-item">
|
||
<div class="badge-icon" style="background:var(--border-soft);opacity:0.5" aria-hidden="true">⭐</div>
|
||
<div class="badge-name">30天连续</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Settings -->
|
||
<div class="settings-section anim-fade" style="animation-delay: 0.2s">
|
||
<h3>设置</h3>
|
||
<div class="settings-card">
|
||
<div class="setting-item">
|
||
<div class="icon" style="background:var(--tertiary-soft)" aria-hidden="true">🔔</div>
|
||
<div class="text">
|
||
<div class="title">日记提醒</div>
|
||
<div class="sub">每天 21:00 提醒写日记</div>
|
||
</div>
|
||
<div class="toggle on" role="switch" aria-label="日记提醒开关" aria-checked="true" tabindex="0"></div>
|
||
</div>
|
||
<div class="setting-item">
|
||
<div class="icon" style="background:var(--secondary-soft)" aria-hidden="true">🔒</div>
|
||
<div class="text">
|
||
<div class="title">隐私锁</div>
|
||
<div class="sub">Face ID 解锁查看日记</div>
|
||
</div>
|
||
<div class="arrow" aria-hidden="true"><svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M6 12l4-4-4-4"/></svg></div>
|
||
</div>
|
||
<div class="setting-item">
|
||
<div class="icon" style="background:var(--secondary-soft)" aria-hidden="true">☁️</div>
|
||
<div class="text">
|
||
<div class="title">云端同步</div>
|
||
<div class="sub">已同步 · 上次 2分钟前</div>
|
||
</div>
|
||
<div class="arrow" aria-hidden="true"><svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M6 12l4-4-4-4"/></svg></div>
|
||
</div>
|
||
<div class="setting-item">
|
||
<div class="icon" style="background:var(--rose-soft)" aria-hidden="true">🎨</div>
|
||
<div class="text">
|
||
<div class="title">主题外观</div>
|
||
<div class="sub">暖色系 · 默认字体</div>
|
||
</div>
|
||
<div class="arrow" aria-hidden="true"><svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M6 12l4-4-4-4"/></svg></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- More settings -->
|
||
<div class="settings-section anim-fade" style="animation-delay: 0.25s">
|
||
<div class="settings-card">
|
||
<div class="setting-item">
|
||
<div class="icon" style="background:var(--surface-warm)" aria-hidden="true">📦</div>
|
||
<div class="text">
|
||
<div class="title">导出数据</div>
|
||
</div>
|
||
<div class="arrow" aria-hidden="true"><svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M6 12l4-4-4-4"/></svg></div>
|
||
</div>
|
||
<div class="setting-item">
|
||
<div class="icon" style="background:var(--surface-warm)" aria-hidden="true">💬</div>
|
||
<div of="text">
|
||
<div class="title">意见反馈</div>
|
||
</div>
|
||
<div class="arrow" aria-hidden="true"><svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M6 12l4-4-4-4"/></svg></div>
|
||
</div>
|
||
<div class="setting-item">
|
||
<div class="icon" style="background:var(--surface-warm)" aria-hidden="true">ℹ️</div>
|
||
<div class="text">
|
||
<div class="title">关于暖记</div>
|
||
<div class="sub">版本 1.0.0</div>
|
||
</div>
|
||
<div class="arrow" aria-hidden="true"><svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M6 12l4-4-4-4"/></svg></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div style="height: var(--space-8)"></div>
|
||
</div>
|
||
|
||
<!-- Tab bar -->
|
||
<nav class="tab-bar" role="tablist">
|
||
<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="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 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="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>
|