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

331 lines
13 KiB
HTML
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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;
}
/* 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>