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,471 @@
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=1024, height=768, initial-scale=1">
<title>暖记 — 平板端搜索</title>
<link rel="stylesheet" href="../css/tokens.css">
<link rel="stylesheet" href="../css/components.css">
<link rel="stylesheet" href="shared.css">
<style>
body {
width: 1024px;
height: 768px;
overflow: hidden;
background: var(--bg);
font-family: var(--font-body);
}
.search-layout {
display: flex;
height: 768px;
}
/* Full-screen search without sidebar */
.search-main {
flex: 1;
display: flex;
flex-direction: column;
overflow: hidden;
}
/* Search bar */
.search-bar {
height: 64px;
display: flex;
align-items: center;
gap: var(--space-4);
padding: 0 var(--space-8);
background: var(--surface);
border-bottom: 1px solid var(--border-soft);
flex-shrink: 0;
}
.search-back-btn {
width: 38px;
height: 38px;
border-radius: var(--radius-pill);
border: none;
background: none;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
color: var(--fg);
transition: background var(--motion-fast);
}
.search-back-btn:hover { background: var(--surface-warm); }
.search-back-btn svg { width: 20px; height: 20px; }
.search-input-wrap {
flex: 1;
max-width: 600px;
position: relative;
display: flex;
align-items: center;
}
.search-input-wrap svg {
position: absolute;
left: var(--space-4);
width: 18px;
height: 18px;
color: var(--muted);
pointer-events: none;
}
.search-input {
width: 100%;
height: 44px;
border: none;
border-radius: var(--radius-pill);
background: var(--surface-warm);
padding: 0 var(--space-4) 0 44px;
font-size: var(--text-base);
color: var(--fg);
outline: none;
font-family: var(--font-body);
}
.search-input::placeholder { color: var(--meta); }
.search-cancel-btn {
border: none;
background: none;
color: var(--accent);
font-size: var(--text-base);
font-weight: 600;
cursor: pointer;
min-height: 44px;
display: flex;
align-items: center;
}
.search-cancel-btn:hover { opacity: 0.7; }
/* Scrollable content */
.search-content {
flex: 1;
overflow-y: auto;
padding: var(--space-6) var(--space-8);
scrollbar-width: thin;
scrollbar-color: var(--border) transparent;
max-width: 900px;
margin: 0 auto;
width: 100%;
}
.search-content::-webkit-scrollbar { width: 4px; }
.search-content::-webkit-scrollbar-thumb { background: var(--border); border-radius: 2px; }
/* Sections */
.search-section { margin-bottom: var(--space-6); }
.search-section-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: var(--space-3);
}
.search-section-title {
font-family: var(--font-display);
font-size: var(--text-lg);
font-weight: 700;
color: var(--fg);
}
.search-section-action {
font-size: var(--text-sm);
color: var(--accent);
background: none;
border: none;
cursor: pointer;
font-weight: 500;
min-height: 44px;
display: inline-flex;
align-items: center;
}
.search-tags { display: flex; flex-wrap: wrap; gap: var(--space-2); }
.search-tag {
padding: 8px 18px;
border-radius: var(--radius-pill);
background: var(--surface-warm);
color: var(--fg-2);
font-size: var(--text-sm);
font-weight: 500;
border: 1px solid transparent;
cursor: pointer;
min-height: 44px;
display: inline-flex;
align-items: center;
transition: all var(--motion-fast);
}
.search-tag:hover { border-color: var(--accent); color: var(--accent); }
/* Result tabs */
.result-tabs {
display: flex;
gap: var(--space-1);
margin-bottom: var(--space-5);
border-bottom: 1px solid var(--border-soft);
padding-bottom: 2px;
}
.result-tab {
padding: var(--space-3) var(--space-5);
background: none;
border: none;
font-size: var(--text-sm);
font-weight: 600;
color: var(--muted);
cursor: pointer;
min-height: 44px;
position: relative;
transition: color var(--motion-fast);
}
.result-tab.active { color: var(--accent); }
.result-tab.active::after {
content: '';
position: absolute;
bottom: -3px;
left: var(--space-5);
right: var(--space-5);
height: 3px;
border-radius: 2px;
background: var(--accent);
}
/* Two-column results */
.result-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: var(--space-4);
}
.result-card {
background: var(--surface);
border-radius: var(--radius-md);
padding: var(--space-4);
box-shadow: var(--elev-soft);
border: 1px solid var(--border-soft);
cursor: pointer;
transition: transform var(--motion-fast) var(--ease-standard);
}
.result-card:hover { transform: translateY(-1px); }
.result-card-top {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 4px;
}
.result-card-title {
font-family: var(--font-display);
font-size: var(--text-base);
font-weight: 600;
color: var(--fg);
}
.result-card-title mark {
background: var(--tertiary-soft);
color: var(--accent);
border-radius: 2px;
padding: 0 2px;
}
.result-card-date {
font-size: var(--text-xs);
color: var(--muted);
white-space: nowrap;
margin-left: var(--space-3);
}
.result-card-excerpt {
font-size: var(--text-sm);
color: var(--muted);
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
line-height: 1.5;
}
.result-card-excerpt mark {
background: var(--tertiary-soft);
color: var(--accent);
border-radius: 2px;
padding: 0 2px;
}
.result-card-tags { display: flex; gap: var(--space-1); margin-top: var(--space-2); }
.result-card-tag {
font-size: var(--text-xs);
color: var(--accent);
background: var(--surface-warm);
padding: 2px 8px;
border-radius: var(--radius-pill);
}
.result-template-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: var(--space-4);
}
.result-template-card {
background: var(--surface);
border-radius: var(--radius-md);
padding: var(--space-5);
box-shadow: var(--elev-soft);
border: 1px solid var(--border-soft);
text-align: center;
cursor: pointer;
transition: all var(--motion-fast);
}
.result-template-card:hover { border-color: var(--accent); transform: translateY(-2px); }
.result-template-icon { font-size: 36px; margin-bottom: var(--space-2); }
.result-template-name {
font-family: var(--font-display);
font-size: var(--text-sm);
font-weight: 600;
color: var(--fg);
margin-bottom: 4px;
}
.result-template-name mark {
background: var(--tertiary-soft);
color: var(--accent);
border-radius: 2px;
padding: 0 2px;
}
.result-template-desc { font-size: var(--text-xs); color: var(--muted); }
</style>
</head>
<body>
<div class="search-layout">
<div class="search-main">
<div class="search-bar">
<button class="search-back-btn" aria-label="返回">
<svg viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M15 18l-6-6 6-6"/></svg>
</button>
<div class="search-input-wrap">
<svg viewBox="0 0 22 22" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true"><circle cx="11" cy="11" r="7"/><line x1="16" y1="16" x2="20" y2="20"/></svg>
<input class="search-input" type="text" id="searchInput" placeholder="搜索日记、模板、标签..." autocomplete="off" aria-label="搜索" autofocus>
</div>
<button class="search-cancel-btn" aria-label="取消搜索">取消</button>
</div>
<div class="search-content">
<!-- Before-search state -->
<div id="searchBefore">
<div class="search-section">
<div class="search-section-header">
<span class="search-section-title">最近搜索</span>
<button class="search-section-action" aria-label="清除搜索历史">清除</button>
</div>
<div class="search-tags">
<button class="search-tag" aria-label="搜索 期末考试">期末考试</button>
<button class="search-tag" aria-label="搜索 读书笔记">读书笔记</button>
<button class="search-tag" aria-label="搜索 旅行计划">旅行计划</button>
<button class="search-tag" aria-label="搜索 心情">心情</button>
</div>
</div>
<div class="search-section">
<div class="search-section-header">
<span class="search-section-title">热门搜索</span>
</div>
<div class="search-tags">
<button class="search-tag" aria-label="搜索 期末">#期末</button>
<button class="search-tag" aria-label="搜索 读书">#读书</button>
<button class="search-tag" aria-label="搜索 心情">#心情</button>
<button class="search-tag" aria-label="搜索 旅行">#旅行</button>
<button class="search-tag" aria-label="搜索 美食">#美食</button>
<button class="search-tag" aria-label="搜索 学习">#学习</button>
<button class="search-tag" aria-label="搜索 运动">#运动</button>
<button class="search-tag" aria-label="搜索 灵感">#灵感</button>
</div>
</div>
</div>
<!-- Search results -->
<div id="searchResults" style="display:none">
<div class="result-tabs" role="tablist">
<button class="result-tab active" role="tab" aria-label="全部结果" aria-selected="true">全部</button>
<button class="result-tab" role="tab" aria-label="日记结果" aria-selected="false">日记</button>
<button class="result-tab" role="tab" aria-label="模板结果" aria-selected="false">模板</button>
<button class="result-tab" role="tab" aria-label="标签结果" aria-selected="false">标签</button>
</div>
<div id="resultDiary">
<div class="result-grid">
<div class="result-card" aria-label="打开日记 期末考试周复习计划">
<div class="result-card-top">
<span class="result-card-title">期末<mark>考试</mark>周复习计划</span>
<span class="result-card-date">2025年12月15日</span>
</div>
<div class="result-card-excerpt">今天开始了为期两周的期末<mark>考试</mark>复习,列了详细的计划表,每一科都安排了时间...</div>
<div class="result-card-tags">
<span class="result-card-tag">#学习</span>
<span class="result-card-tag">#期末</span>
</div>
</div>
<div class="result-card" aria-label="打开日记 考研倒计时30天">
<div class="result-card-top">
<span class="result-card-title">考研倒计时30天</span>
<span class="result-card-date">2025年11月25日</span>
</div>
<div class="result-card-excerpt">距离考研还有30天今天复习了英语阅读和政治感觉<mark>考试</mark>越来越近了...</div>
<div class="result-card-tags">
<span class="result-card-tag">#学习</span>
<span class="result-card-tag">#考研</span>
</div>
</div>
<div class="result-card" aria-label="打开日记 考试后的放松手账">
<div class="result-card-top">
<span class="result-card-title"><mark>考试</mark>后的放松手账</span>
<span class="result-card-date">2025年6月20日</span>
</div>
<div class="result-card-excerpt">终于考完试了!用手账记录了<mark>考试</mark>结束那一刻的心情,画了一幅小小的庆祝插画...</div>
<div class="result-card-tags">
<span class="result-card-tag">#心情</span>
<span class="result-card-tag">#手账</span>
</div>
</div>
<div class="result-card" aria-label="打开日记 图书馆的午后">
<div class="result-card-top">
<span class="result-card-title">图书馆的午后</span>
<span class="result-card-date">2025年5月30日</span>
</div>
<div class="result-card-excerpt">今天在图书馆自习,窗外的阳光洒进来,暖暖的。复习了高数第三章...</div>
<div class="result-card-tags">
<span class="result-card-tag">#日常</span>
<span class="result-card-tag">#学习</span>
</div>
</div>
</div>
</div>
<div id="resultTemplates" style="display:none">
<div class="result-template-grid">
<div class="result-template-card" aria-label="使用模板 考试复习计划">
<div class="result-template-icon">📝</div>
<div class="result-template-name"><mark>考试</mark>复习计划</div>
<div class="result-template-desc">适合期末<mark>考试</mark>周使用</div>
</div>
<div class="result-template-card" aria-label="使用模板 学习打卡">
<div class="result-template-icon">📚</div>
<div class="result-template-name">学习打卡</div>
<div class="result-template-desc">每日学习记录模板</div>
</div>
<div class="result-template-card" aria-label="使用模板 成绩分析">
<div class="result-template-icon">📊</div>
<div class="result-template-name">成绩分析</div>
<div class="result-template-desc"><mark>考试</mark>成绩复盘模板</div>
</div>
<div class="result-template-card" aria-label="使用模板 目标追踪">
<div class="result-template-icon">🎯</div>
<div class="result-template-name">目标追踪</div>
<div class="result-template-desc">学期目标追踪模板</div>
</div>
</div>
</div>
<div id="resultTags" style="display:none">
<div class="search-tags" style="gap: var(--space-3);">
<button class="search-tag" style="padding: 12px 24px; font-size: var(--text-base);" aria-label="搜索标签 考试">#考试 (3篇)</button>
<button class="search-tag" style="padding: 12px 24px; font-size: var(--text-base);" aria-label="搜索标签 考研">#考研 (2篇)</button>
<button class="search-tag" style="padding: 12px 24px; font-size: var(--text-base);" aria-label="搜索标签 考试周">#考试周 (1篇)</button>
<button class="search-tag" style="padding: 12px 24px; font-size: var(--text-base);" aria-label="搜索标签 复习">#复习 (5篇)</button>
</div>
</div>
</div>
</div>
</div>
</div>
<script>
var input = document.getElementById('searchInput');
var beforeEl = document.getElementById('searchBefore');
var resultsEl = document.getElementById('searchResults');
var resultTabs = document.querySelectorAll('.result-tab');
var panels = ['resultDiary', 'resultTemplates', 'resultTags'];
input.addEventListener('input', function() {
if (this.value.trim().length > 0) {
beforeEl.style.display = 'none';
resultsEl.style.display = '';
} else {
beforeEl.style.display = '';
resultsEl.style.display = 'none';
}
});
document.querySelectorAll('.search-tag').forEach(function(tag) {
tag.addEventListener('click', function() {
input.value = this.textContent.trim();
input.dispatchEvent(new Event('input'));
});
});
resultTabs.forEach(function(tab, idx) {
tab.addEventListener('click', function() {
resultTabs.forEach(function(t) { t.classList.remove('active'); t.setAttribute('aria-selected', 'false'); });
tab.classList.add('active');
tab.setAttribute('aria-selected', 'true');
panels.forEach(function(p, i) {
document.getElementById(p).style.display = i === idx ? '' : 'none';
});
});
});
input.focus();
</script>
<script src="../../js/theme-switcher.js"></script>
</body>
</html>