前端修复: - 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个页面全部渲染正常
472 lines
16 KiB
HTML
472 lines
16 KiB
HTML
<!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>
|