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

472 lines
16 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
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=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>