fix(app): 全链路验证修复 — 编译错误/CORS/迁移/启动脚本
Some checks failed
Main Merge / backend (push) Has been cancelled
Main Merge / frontend (push) Has been cancelled

前端修复:
- 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个页面全部渲染正常
This commit is contained in:
iven
2026-06-02 01:03:58 +08:00
parent 749ef55b89
commit b320641d9c
56 changed files with 20696 additions and 239 deletions

View File

@@ -0,0 +1,431 @@
<!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: 0;
overflow-y: auto;
padding: 0 var(--space-5);
padding-top: calc(var(--safe-top) + var(--space-2));
padding-bottom: var(--safe-bottom);
scrollbar-width: none;
}
.content-area::-webkit-scrollbar { display: none; }
.top-bar {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: var(--space-4);
}
.top-bar h2 {
font-family: var(--font-display);
font-size: var(--text-2xl);
font-weight: 700;
}
.back-btn {
min-width: 44px;
min-height: 44px;
border-radius: 50%;
border: none;
background: var(--surface);
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
color: var(--fg);
box-shadow: var(--elev-soft);
}
.back-btn svg { width: 20px; height: 20px; }
/* Search */
.search-box {
display: flex;
align-items: center;
gap: var(--space-3);
background: var(--surface);
border-radius: var(--radius-pill);
padding: var(--space-3) var(--space-5);
margin-bottom: var(--space-5);
box-shadow: var(--elev-soft);
border: 1px solid var(--border-soft);
}
.search-box svg { width: 20px; height: 20px; color: var(--muted); flex-shrink: 0; }
.search-box input {
flex: 1;
border: none;
outline: none;
font-size: var(--text-base);
background: transparent;
color: var(--fg);
font-family: var(--font-body);
}
.search-box input::placeholder { color: var(--meta); }
/* Category tabs */
.cat-tabs {
display: flex;
gap: var(--space-2);
margin-bottom: var(--space-5);
overflow-x: auto;
scrollbar-width: none;
padding-bottom: 4px;
}
.cat-tabs::-webkit-scrollbar { display: none; }
.cat-tab {
padding: 8px 18px;
min-height: 44px;
border-radius: var(--radius-pill);
border: 1.5px solid var(--border);
background: var(--surface);
font-size: var(--text-sm);
font-weight: 500;
color: var(--fg-2);
cursor: pointer;
white-space: nowrap;
transition: all var(--motion-fast);
}
.cat-tab.active {
background: var(--accent);
color: var(--accent-on);
border-color: var(--accent);
}
.cat-tab:hover:not(.active) { border-color: var(--accent); color: var(--accent); }
/* Featured pack */
.featured-pack {
background: linear-gradient(135deg, var(--secondary-soft), var(--secondary-soft));
border-radius: var(--radius-lg);
padding: var(--space-5);
margin-bottom: var(--space-5);
display: flex;
gap: var(--space-4);
align-items: center;
cursor: pointer;
transition: transform var(--motion-fast);
}
.featured-pack:hover { transform: translateY(-2px); }
.featured-icon {
width: 72px;
height: 72px;
border-radius: var(--radius-md);
background: white;
display: flex;
align-items: center;
justify-content: center;
font-size: 36px;
box-shadow: var(--elev-soft);
flex-shrink: 0;
}
.featured-info h4 {
font-family: var(--font-display);
font-size: var(--text-lg);
font-weight: 700;
margin-bottom: 4px;
}
.featured-info p {
font-size: var(--text-sm);
color: var(--muted);
margin-bottom: var(--space-2);
}
.featured-info .tag {
display: inline-block;
padding: 3px 10px;
background: var(--secondary);
color: var(--accent-on);
border-radius: var(--radius-pill);
font-size: 11px;
font-weight: 600;
}
/* Sticker pack grid */
.pack-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: var(--space-3);
margin-bottom: var(--space-5);
}
.pack-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: all var(--motion-fast);
}
.pack-card:hover { transform: translateY(-2px); box-shadow: var(--elev-medium); }
.pack-preview {
height: 100px;
display: flex;
align-items: center;
justify-content: center;
font-size: 36px;
gap: 8px;
}
.pack-info {
padding: var(--space-3) var(--space-4);
}
.pack-info h5 {
font-family: var(--font-display);
font-size: var(--text-sm);
font-weight: 600;
margin-bottom: 2px;
}
.pack-info .count {
font-size: var(--text-xs);
color: var(--muted);
}
.pack-info .price {
font-size: var(--text-xs);
font-weight: 600;
color: var(--accent);
margin-top: 4px;
}
.pack-info .free {
color: var(--secondary);
}
/* Individual stickers grid */
.section-head {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: var(--space-4);
}
.section-head h3 {
font-family: var(--font-display);
font-size: var(--text-lg);
font-weight: 700;
}
.section-head a {
font-size: var(--text-sm);
color: var(--accent);
text-decoration: none;
font-weight: 500;
min-height: 44px;
display: inline-flex;
align-items: center;
}
.sticker-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: var(--space-3);
margin-bottom: var(--space-5);
}
.sticker-cell {
aspect-ratio: 1;
background: var(--surface);
border-radius: var(--radius-md);
display: flex;
align-items: center;
justify-content: center;
font-size: 32px;
cursor: pointer;
transition: all var(--motion-fast) var(--ease-bounce);
box-shadow: var(--elev-soft);
border: 1px solid var(--border-soft);
position: relative;
min-height: 44px;
}
.sticker-cell:hover { transform: scale(1.08); border-color: var(--accent); }
.sticker-cell:active { transform: scale(0.95); }
.sticker-cell .fav {
position: absolute;
top: 4px;
right: 4px;
width: 18px;
height: 18px;
border-radius: 50%;
background: var(--surface);
display: flex;
align-items: center;
justify-content: center;
opacity: 0;
transition: opacity var(--motion-fast);
min-width: 44px;
min-height: 44px;
padding: 13px;
box-sizing: content-box;
top: -8px;
right: -8px;
}
.sticker-cell:hover .fav { opacity: 1; }
.sticker-cell .fav svg { width: 10px; height: 10px; color: var(--rose); }
.sticker-cell .fav.active svg { fill: var(--rose); }
/* Empty state for empty favorites */
.empty-state {
display: none; /* Shown via JS when favorites are empty */
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">
<!-- Top bar -->
<div class="top-bar anim-fade">
<button class="back-btn" aria-label="返回">
<svg viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true"><path d="M13 17l-6-6 6-6"/></svg>
</button>
<h2>贴纸素材</h2>
<div style="width:44px"></div>
</div>
<!-- Search -->
<div class="search-box anim-fade" style="animation-delay: 0.05s">
<svg viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true"><circle cx="9" cy="9" r="6"/><path d="M14 14l4 4"/></svg>
<input type="text" placeholder="搜索贴纸、素材包..." aria-label="搜索贴纸">
</div>
<!-- Categories -->
<div class="cat-tabs anim-fade" style="animation-delay: 0.1s">
<button class="cat-tab active" aria-label="推荐分类">推荐</button>
<button class="cat-tab" aria-label="可爱分类">可爱</button>
<button class="cat-tab" aria-label="植物分类">植物</button>
<button class="cat-tab" aria-label="手绘分类">手绘</button>
<button class="cat-tab" aria-label="校园分类">校园</button>
<button class="cat-tab" aria-label="节日分类">节日</button>
<button class="cat-tab" aria-label="文字分类">文字</button>
<button class="cat-tab" aria-label="和纸胶带分类">和纸胶带</button>
</div>
<!-- Featured pack -->
<div class="featured-pack anim-slide" style="animation-delay: 0.15s">
<div class="featured-icon">🌸</div>
<div class="featured-info">
<h4>春日花园系列</h4>
<p>32款手绘花卉贴纸让你的日记春意盎然</p>
<span class="tag">限时免费</span>
</div>
</div>
<!-- Pack grid -->
<div class="section-head anim-fade" style="animation-delay: 0.2s">
<h3>热门素材包</h3>
<a href="#" aria-label="查看全部素材包">查看全部</a>
</div>
<div class="pack-grid anim-fade" style="animation-delay: 0.25s">
<div class="pack-card">
<div class="pack-preview" style="background: var(--tertiary-soft)">☕📚</div>
<div class="pack-info">
<h5>学习日常</h5>
<div class="count">24款贴纸</div>
<div class="price free">免费</div>
</div>
</div>
<div class="pack-card">
<div class="pack-preview" style="background: var(--secondary-soft)">🌿🍃</div>
<div class="pack-info">
<h5>清新植物</h5>
<div class="count">36款贴纸</div>
<div class="price">¥6</div>
</div>
</div>
<div class="pack-card">
<div class="pack-preview" style="background: var(--rose-soft)">🎀💕</div>
<div class="pack-info">
<h5>甜蜜少女</h5>
<div class="count">28款贴纸</div>
<div class="price">¥3</div>
</div>
</div>
<div class="pack-card">
<div class="pack-preview" style="background: var(--secondary-soft)">🦋✨</div>
<div class="pack-info">
<h5>梦幻星空</h5>
<div class="count">20款贴纸</div>
<div class="price free">免费</div>
</div>
</div>
</div>
<!-- Individual stickers -->
<div class="section-head anim-fade" style="animation-delay: 0.3s">
<h3>精选贴纸</h3>
<a href="#" aria-label="查看收藏夹">收藏夹</a>
</div>
<div class="sticker-grid anim-fade" style="animation-delay: 0.35s">
<div class="sticker-cell" aria-label="樱花贴纸">🌸<div class="fav"><svg viewBox="0 0 12 12" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M6 10s-4-2.5-4-5a2 2 0 014 0 2 2 0 014 0c0 2.5-4 5-4 5z"/></svg></div></div>
<div class="sticker-cell" aria-label="星星贴纸"><div class="fav"><svg viewBox="0 0 12 12" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M6 10s-4-2.5-4-5a2 2 0 014 0 2 2 0 014 0c0 2.5-4 5-4 5z"/></svg></div></div>
<div class="sticker-cell" aria-label="树叶贴纸">🌿<div class="fav active"><svg viewBox="0 0 12 12" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M6 10s-4-2.5-4-5a2 2 0 014 0 2 2 0 014 0c0 2.5-4 5-4 5z"/></svg></div></div>
<div class="sticker-cell" aria-label="太阳贴纸">☀️<div class="fav"><svg viewBox="0 0 12 12" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M6 10s-4-2.5-4-5a2 2 0 014 0 2 2 0 014 0c0 2.5-4 5-4 5z"/></svg></div></div>
<div class="sticker-cell" aria-label="蛋糕贴纸">🍰<div class="fav"><svg viewBox="0 0 12 12" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M6 10s-4-2.5-4-5a2 2 0 014 0 2 2 0 014 0c0 2.5-4 5-4 5z"/></svg></div></div>
<div class="sticker-cell" aria-label="音乐贴纸">🎵<div class="fav"><svg viewBox="0 0 12 12" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M6 10s-4-2.5-4-5a2 2 0 014 0 2 2 0 014 0c0 2.5-4 5-4 5z"/></svg></div></div>
<div class="sticker-cell" aria-label="书本贴纸">📚<div class="fav"><svg viewBox="0 0 12 12" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M6 10s-4-2.5-4-5a2 2 0 014 0 2 2 0 014 0c0 2.5-4 5-4 5z"/></svg></div></div>
<div class="sticker-cell" aria-label="月亮贴纸">🌙<div class="fav active"><svg viewBox="0 0 12 12" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M6 10s-4-2.5-4-5a2 2 0 014 0 2 2 0 014 0c0 2.5-4 5-4 5z"/></svg></div></div>
<div class="sticker-cell" aria-label="蝴蝶结贴纸">🎀<div class="fav"><svg viewBox="0 0 12 12" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M6 10s-4-2.5-4-5a2 2 0 014 0 2 2 0 014 0c0 2.5-4 5-4 5z"/></svg></div></div>
<div class="sticker-cell" aria-label="枫叶贴纸">🍂<div class="fav"><svg viewBox="0 0 12 12" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M6 10s-4-2.5-4-5a2 2 0 014 0 2 2 0 014 0c0 2.5-4 5-4 5z"/></svg></div></div>
<div class="sticker-cell" aria-label="咖啡贴纸"><div class="fav"><svg viewBox="0 0 12 12" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M6 10s-4-2.5-4-5a2 2 0 014 0 2 2 0 014 0c0 2.5-4 5-4 5z"/></svg></div></div>
<div class="sticker-cell" aria-label="蝴蝶贴纸">🦋<div class="fav"><svg viewBox="0 0 12 12" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M6 10s-4-2.5-4-5a2 2 0 014 0 2 2 0 014 0c0 2.5-4 5-4 5z"/></svg></div></div>
</div>
<!-- Empty state for empty favorites (hidden by default) -->
<div class="empty-state" id="emptyFavorites">
<div class="empty-circle">💜</div>
<div class="empty-text">收藏夹是空的</div>
<div class="empty-hint">长按贴纸可添加到收藏夹</div>
</div>
<div style="height: var(--space-8)"></div>
</div>
<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>