Files
xianyan/docs/design-preview/wallpaper-gallery-design.html
Developer b5157c19f4 feat: 新增壁纸图库组件和编辑器功能优化
- 新增壁纸图库相关组件(WallpaperGalleryView/WallpaperSearchBar等)
- 优化编辑器主题服务和系统UI管理
- 新增虚线边框和拖拽描边风格支持
- 完善今日诗词服务和阅读报告功能
- 修复多个UI问题和空指针异常
- 更新依赖库版本和SVG资源
- 优化交互动画和状态管理
- 补充文档和API测试脚本
2026-05-05 05:03:33 +08:00

1263 lines
49 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=device-width, initial-scale=1.0">
<title>闲言APP — 壁纸公共组件设计方案</title>
<style>
:root {
--primary: #007AFF;
--primary-light: #4CD964;
--bg: #000000;
--bg-card: #1C1C1E;
--bg-elevated: #2C2C2E;
--bg-glass: rgba(28,28,30,0.72);
--text: #FFFFFF;
--text-secondary: rgba(255,255,255,0.6);
--text-hint: rgba(255,255,255,0.35);
--border: rgba(255,255,255,0.08);
--border-active: rgba(0,122,255,0.5);
--radius-sm: 8px;
--radius-md: 12px;
--radius-lg: 16px;
--radius-xl: 20px;
--shadow: 0 8px 32px rgba(0,0,0,0.4);
--font: -apple-system, BlinkMacSystemFont, 'SF Pro Display', 'SF Pro Text', system-ui, sans-serif;
}
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: var(--font);
background: var(--bg);
color: var(--text);
min-height: 100vh;
overflow-x: hidden;
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 24px 16px;
}
/* ─── Header ─── */
.page-header {
text-align: center;
padding: 40px 0 32px;
}
.page-header h1 {
font-size: 28px;
font-weight: 700;
letter-spacing: -0.5px;
background: linear-gradient(135deg, #007AFF, #5AC8FA, #4CD964);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.page-header p {
color: var(--text-secondary);
font-size: 14px;
margin-top: 8px;
}
/* ─── Section ─── */
.section {
margin-bottom: 48px;
}
.section-title {
font-size: 20px;
font-weight: 700;
margin-bottom: 16px;
display: flex;
align-items: center;
gap: 8px;
}
.section-desc {
color: var(--text-secondary);
font-size: 13px;
margin-bottom: 20px;
line-height: 1.6;
}
/* ─── Approach Cards ─── */
.approach-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
gap: 16px;
}
.approach-card {
background: var(--bg-card);
border-radius: var(--radius-lg);
border: 1px solid var(--border);
padding: 20px;
transition: all 0.3s;
position: relative;
}
.approach-card.recommended {
border-color: var(--primary);
box-shadow: 0 0 20px rgba(0,122,255,0.15);
}
.approach-card .badge {
position: absolute;
top: -8px;
right: 16px;
background: var(--primary);
color: white;
font-size: 11px;
font-weight: 600;
padding: 2px 10px;
border-radius: 10px;
}
.approach-card h3 {
font-size: 16px;
font-weight: 600;
margin-bottom: 8px;
}
.approach-card .pros, .approach-card .cons {
font-size: 12px;
line-height: 1.8;
margin-top: 8px;
}
.approach-card .pros span { color: #4CD964; }
.approach-card .cons span { color: #FF9500; }
/* ─── Phone Mockup ─── */
.phone-frame {
width: 375px;
max-width: 100%;
margin: 0 auto;
background: var(--bg);
border-radius: 40px;
border: 3px solid #333;
overflow: hidden;
box-shadow: var(--shadow), 0 0 0 1px rgba(255,255,255,0.05);
position: relative;
}
.phone-notch {
width: 120px;
height: 28px;
background: #000;
border-radius: 0 0 16px 16px;
margin: 0 auto;
position: relative;
z-index: 10;
}
.phone-content {
height: 680px;
overflow-y: auto;
overflow-x: hidden;
scrollbar-width: none;
}
.phone-content::-webkit-scrollbar { display: none; }
/* ─── Wallpaper Gallery Component ─── */
.gallery-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 8px 16px 4px;
}
.gallery-header h2 {
font-size: 17px;
font-weight: 700;
}
.gallery-close {
width: 28px;
height: 28px;
border-radius: 14px;
background: rgba(255,255,255,0.1);
display: flex;
align-items: center;
justify-content: center;
font-size: 12px;
cursor: pointer;
}
/* Source Tabs */
.source-tabs {
display: flex;
gap: 6px;
padding: 8px 16px;
overflow-x: auto;
scrollbar-width: none;
}
.source-tabs::-webkit-scrollbar { display: none; }
.source-tab {
flex-shrink: 0;
padding: 6px 12px;
border-radius: 999px;
font-size: 12px;
font-weight: 500;
background: rgba(255,255,255,0.06);
border: 1px solid transparent;
cursor: pointer;
transition: all 0.2s;
white-space: nowrap;
display: flex;
align-items: center;
gap: 4px;
}
.source-tab.active {
background: rgba(0,122,255,0.15);
border-color: rgba(0,122,255,0.4);
color: #4DA3FF;
}
.source-tab .speed {
font-size: 9px;
opacity: 0.5;
}
/* Category Tabs */
.category-tabs {
display: flex;
gap: 6px;
padding: 4px 16px 8px;
overflow-x: auto;
scrollbar-width: none;
}
.category-tabs::-webkit-scrollbar { display: none; }
.category-tab {
flex-shrink: 0;
padding: 4px 10px;
border-radius: 999px;
font-size: 11px;
font-weight: 500;
background: transparent;
border: 1px solid rgba(255,255,255,0.06);
cursor: pointer;
transition: all 0.2s;
white-space: nowrap;
}
.category-tab.active {
background: rgba(0,122,255,0.08);
border-color: rgba(0,122,255,0.3);
color: #4DA3FF;
}
/* Search Bar */
.search-bar {
margin: 4px 16px 10px;
padding: 8px 12px;
background: rgba(255,255,255,0.06);
border-radius: 10px;
font-size: 13px;
color: var(--text-hint);
display: flex;
align-items: center;
gap: 6px;
}
/* Masonry Grid */
.masonry {
columns: 2;
column-gap: 8px;
padding: 0 12px 16px;
}
.masonry-item {
break-inside: avoid;
margin-bottom: 8px;
border-radius: var(--radius-md);
overflow: hidden;
position: relative;
cursor: pointer;
transition: transform 0.2s;
}
.masonry-item:hover { transform: scale(1.02); }
.masonry-item img {
width: 100%;
display: block;
border-radius: var(--radius-md);
}
.masonry-item .overlay {
position: absolute;
bottom: 0;
left: 0;
right: 0;
padding: 8px;
background: linear-gradient(transparent, rgba(0,0,0,0.7));
border-radius: 0 0 var(--radius-md) var(--radius-md);
}
.masonry-item .overlay .title {
font-size: 10px;
font-weight: 500;
color: rgba(255,255,255,0.85);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.masonry-item .overlay .meta {
font-size: 8px;
color: rgba(255,255,255,0.5);
margin-top: 2px;
display: flex;
gap: 6px;
}
.masonry-item .select-check {
position: absolute;
top: 6px;
right: 6px;
width: 20px;
height: 20px;
border-radius: 10px;
background: var(--primary);
display: none;
align-items: center;
justify-content: center;
font-size: 10px;
}
.masonry-item.selected .select-check { display: flex; }
.masonry-item.selected { outline: 2px solid var(--primary); outline-offset: -2px; }
/* Apply Button */
.apply-bar {
padding: 10px 16px;
background: rgba(28,28,30,0.9);
backdrop-filter: blur(20px);
border-top: 1px solid var(--border);
}
.apply-btn {
width: 100%;
padding: 12px;
border-radius: var(--radius-md);
background: var(--primary);
color: white;
font-size: 15px;
font-weight: 600;
border: none;
cursor: pointer;
text-align: center;
}
.apply-btn:disabled {
opacity: 0.4;
cursor: default;
}
/* ─── Drag Border Demo ─── */
.drag-demo-area {
display: flex;
gap: 32px;
justify-content: center;
flex-wrap: wrap;
}
.drag-demo-box {
width: 200px;
height: 260px;
background: var(--bg-card);
border-radius: var(--radius-lg);
display: flex;
align-items: center;
justify-content: center;
position: relative;
overflow: hidden;
}
.drag-demo-box .content {
width: 120px;
height: 80px;
background: linear-gradient(135deg, #667eea, #764ba2);
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-size: 13px;
font-weight: 600;
position: relative;
}
/* Dashed border style */
.drag-demo-box.dashed .content {
outline: 2px dashed rgba(0,122,255,0.8);
outline-offset: 4px;
}
.drag-demo-box.dashed .content::before,
.drag-demo-box.dashed .content::after {
content: '';
position: absolute;
width: 10px;
height: 10px;
background: white;
border: 2px solid var(--primary);
border-radius: 2px;
}
.drag-demo-box.dashed .content::before { top: -9px; left: -9px; }
.drag-demo-box.dashed .content::after { top: -9px; right: -9px; }
.drag-demo-box.dashed .corner-bl,
.drag-demo-box.dashed .corner-br {
position: absolute;
width: 10px;
height: 10px;
background: white;
border: 2px solid var(--primary);
border-radius: 2px;
}
.drag-demo-box.dashed .corner-bl { bottom: -9px; left: -9px; }
.drag-demo-box.dashed .corner-br { bottom: -9px; right: -9px; }
/* Glow border style */
.drag-demo-box.glow .content {
outline: 2px solid rgba(0,122,255,0.6);
outline-offset: 4px;
box-shadow: 0 0 12px rgba(0,122,255,0.4), 0 0 24px rgba(0,122,255,0.2), inset 0 0 8px rgba(0,122,255,0.1);
animation: glow-pulse 2s ease-in-out infinite;
}
@keyframes glow-pulse {
0%, 100% { box-shadow: 0 0 12px rgba(0,122,255,0.4), 0 0 24px rgba(0,122,255,0.2); }
50% { box-shadow: 0 0 18px rgba(0,122,255,0.6), 0 0 36px rgba(0,122,255,0.3); }
}
.drag-label {
text-align: center;
margin-top: 12px;
font-size: 13px;
font-weight: 500;
color: var(--text-secondary);
}
/* ─── Rich Text Demo ─── */
.richtext-demo {
background: var(--bg-card);
border-radius: var(--radius-lg);
border: 1px solid var(--border);
overflow: hidden;
max-width: 600px;
margin: 0 auto;
}
.richtext-toolbar {
display: flex;
gap: 2px;
padding: 8px 12px;
background: rgba(255,255,255,0.03);
border-bottom: 1px solid var(--border);
flex-wrap: wrap;
}
.richtext-toolbar .tool-btn {
width: 32px;
height: 32px;
border-radius: 6px;
background: transparent;
border: none;
color: var(--text-secondary);
font-size: 14px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.15s;
}
.richtext-toolbar .tool-btn:hover { background: rgba(255,255,255,0.08); }
.richtext-toolbar .tool-btn.active { background: rgba(0,122,255,0.2); color: #4DA3FF; }
.richtext-toolbar .divider {
width: 1px;
height: 20px;
background: var(--border);
margin: 6px 4px;
}
.richtext-canvas {
padding: 20px;
min-height: 200px;
}
.richtext-canvas h2 {
font-size: 20px;
font-weight: 700;
margin-bottom: 8px;
}
.richtext-canvas p {
font-size: 14px;
line-height: 1.7;
color: var(--text-secondary);
margin-bottom: 8px;
}
.richtext-canvas .bold { font-weight: 700; color: var(--text); }
.richtext-canvas .italic { font-style: italic; }
.richtext-canvas .underline { text-decoration: underline; }
.richtext-canvas .strikethrough { text-decoration: line-through; }
.richtext-canvas blockquote {
border-left: 3px solid var(--primary);
padding-left: 12px;
margin: 8px 0;
color: var(--text-secondary);
font-style: italic;
}
.richtext-canvas ul {
padding-left: 20px;
margin: 8px 0;
}
.richtext-canvas li {
font-size: 14px;
line-height: 1.7;
color: var(--text-secondary);
}
.richtext-canvas code {
background: rgba(255,255,255,0.06);
padding: 2px 6px;
border-radius: 4px;
font-family: 'SF Mono', Menlo, monospace;
font-size: 12px;
color: #FF9500;
}
/* ─── Icon Mapping Table ─── */
.icon-table {
width: 100%;
border-collapse: collapse;
font-size: 13px;
}
.icon-table th {
text-align: left;
padding: 10px 12px;
background: rgba(255,255,255,0.03);
border-bottom: 1px solid var(--border);
font-weight: 600;
font-size: 12px;
color: var(--text-secondary);
}
.icon-table td {
padding: 8px 12px;
border-bottom: 1px solid var(--border);
vertical-align: middle;
}
.icon-table tr:hover td { background: rgba(255,255,255,0.02); }
.icon-table .icon-preview {
display: flex;
align-items: center;
gap: 8px;
}
.icon-table .icon-preview .cupertino {
color: var(--primary);
font-size: 16px;
}
.icon-table .icon-preview .fallback {
font-size: 12px;
color: var(--text-hint);
}
.icon-table .tag {
display: inline-block;
padding: 1px 6px;
border-radius: 4px;
font-size: 10px;
font-weight: 600;
}
.icon-table .tag.cupertino { background: rgba(0,122,255,0.15); color: #4DA3FF; }
.icon-table .tag.svg { background: rgba(255,149,0,0.15); color: #FF9500; }
.icon-table .tag.emoji { background: rgba(255,59,48,0.15); color: #FF3B30; }
/* ─── Library Table ─── */
.lib-table {
width: 100%;
border-collapse: collapse;
font-size: 13px;
}
.lib-table th {
text-align: left;
padding: 10px 12px;
background: rgba(255,255,255,0.03);
border-bottom: 1px solid var(--border);
font-weight: 600;
font-size: 12px;
color: var(--text-secondary);
}
.lib-table td {
padding: 8px 12px;
border-bottom: 1px solid var(--border);
}
.lib-table .priority {
display: inline-block;
padding: 1px 8px;
border-radius: 4px;
font-size: 10px;
font-weight: 600;
}
.lib-table .priority.p1 { background: rgba(255,59,48,0.15); color: #FF3B30; }
.lib-table .priority.p2 { background: rgba(255,149,0,0.15); color: #FF9500; }
.lib-table .priority.p3 { background: rgba(52,199,89,0.15); color: #34C759; }
/* ─── Two-column layout for comparison ─── */
.two-col {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 24px;
}
@media (max-width: 768px) {
.two-col { grid-template-columns: 1fr; }
.approach-grid { grid-template-columns: 1fr; }
.drag-demo-area { flex-direction: column; align-items: center; }
}
/* ─── Tab Switch ─── */
.tab-switch {
display: flex;
background: var(--bg-card);
border-radius: 10px;
padding: 3px;
margin-bottom: 20px;
max-width: 400px;
}
.tab-switch .tab {
flex: 1;
padding: 8px 16px;
border-radius: 8px;
font-size: 13px;
font-weight: 500;
text-align: center;
cursor: pointer;
transition: all 0.2s;
color: var(--text-secondary);
}
.tab-switch .tab.active {
background: var(--primary);
color: white;
}
/* ─── Loaded indicator ─── */
.loaded-badge {
position: absolute;
top: 6px;
left: 6px;
background: rgba(52,199,89,0.9);
color: white;
font-size: 8px;
font-weight: 600;
padding: 1px 5px;
border-radius: 4px;
}
/* ─── Skeleton ─── */
.skeleton {
background: linear-gradient(90deg, rgba(255,255,255,0.04) 25%, rgba(255,255,255,0.08) 50%, rgba(255,255,255,0.04) 75%);
background-size: 200% 100%;
animation: shimmer 1.5s infinite;
border-radius: var(--radius-md);
}
@keyframes shimmer {
0% { background-position: 200% 0; }
100% { background-position: -200% 0; }
}
</style>
</head>
<body>
<div class="container">
<!-- Header -->
<div class="page-header">
<h1>🎨 壁纸公共组件 + 编辑器增强 设计方案</h1>
<p>闲言APP v3.8.0 · 2026-05-04 · 5项需求整合设计</p>
</div>
<!-- ═══════════════════════════════════════════ -->
<!-- Section 1: 方案对比 -->
<!-- ═══════════════════════════════════════════ -->
<div class="section">
<div class="section-title">📐 壁纸组件提取 — 三种方案对比</div>
<div class="section-desc">
当前问题:编辑器 WallpaperPanel 和灵感页 TemplateGalleryPage 代码重复、风格不一致、图片不显示。<br>
目标:提取公共组件,统一风格,修复 bug两个场景复用。
</div>
<div class="approach-grid">
<!-- Approach 1 -->
<div class="approach-card">
<h3>🔧 方案A: 渐进增强</h3>
<p style="font-size:12px;color:var(--text-secondary);">保留现有两个组件,逐步抽取公共方法到 Service/Provider 层</p>
<div class="pros"><span></span> 风险最低,改动最小</div>
<div class="cons"><span>⚠️</span> UI风格仍不统一两套维护成本</div>
<div class="cons"><span>⚠️</span> 没有根本解决设计不一致问题</div>
</div>
<!-- Approach 2 (Recommended) -->
<div class="approach-card recommended">
<span class="badge">推荐</span>
<h3>🏗️ 方案B: 全量提取公共组件</h3>
<p style="font-size:12px;color:var(--text-secondary);">新建 WallpaperGalleryView 公共组件,替换两处旧代码,统一设计</p>
<div class="pros"><span></span> 单一数据源,风格统一</div>
<div class="pros"><span></span> 瀑布流+已加载优先+分类"全部"</div>
<div class="pros"><span></span> 编辑器抽屉/全屏页面两种模式</div>
<div class="cons"><span>⚠️</span> 工作量稍大,需同时改两处调用</div>
</div>
<!-- Approach 3 -->
<div class="approach-card">
<h3>🧩 方案C: 组合式小组件</h3>
<p style="font-size:12px;color:var(--text-secondary);">拆分为 SourceBar/CategoryBar/MasonryGrid 等原子组件,按需组合</p>
<div class="pros"><span></span> 最大灵活性,可自由组合</div>
<div class="cons"><span>⚠️</span> 组件数量多,复杂度高</div>
<div class="cons"><span>⚠️</span> 过度设计当前只有2个场景</div>
</div>
</div>
</div>
<!-- ═══════════════════════════════════════════ -->
<!-- Section 2: 壁纸公共组件 UI 原型 -->
<!-- ═══════════════════════════════════════════ -->
<div class="section">
<div class="section-title">🖼️ 壁纸公共组件 — UI原型</div>
<div class="section-desc">
方案B详细设计顶栏横向滑动源+分类+搜索)+ 全局瀑布流 + 已加载优先 + 分类"全部"<br>
两种使用模式:①编辑器抽屉面板(选择后应用为图层)②灵感页全屏页面(浏览+预览+下载)
</div>
<div class="tab-switch" id="modeSwitch">
<div class="tab active" onclick="switchMode('drawer')">📱 编辑器抽屉模式</div>
<div class="tab" onclick="switchMode('fullscreen')">🖥️ 灵感页全屏模式</div>
</div>
<div class="two-col">
<!-- Phone Mockup -->
<div>
<div class="phone-frame" id="phoneFrame">
<div class="phone-notch"></div>
<div class="phone-content" id="phoneContent">
<!-- Drawer Mode -->
<div id="drawerMode">
<div class="gallery-header">
<h2>🖼 在线壁纸</h2>
<div class="gallery-close"></div>
</div>
<!-- Source Tabs -->
<div class="source-tabs">
<div class="source-tab active">📷 Unsplash <span class="speed">138ms</span></div>
<div class="source-tab">🎞 Pexels <span class="speed">136ms</span></div>
<div class="source-tab">🌍 必应 <span class="speed">245ms</span></div>
<div class="source-tab">🚀 NASA <span class="speed">133ms</span></div>
<div class="source-tab">🔍 Pixabay <span class="speed">140ms</span></div>
<div class="source-tab">🏙 WallSt <span class="speed">140ms</span></div>
<div class="source-tab">🎨 动漫 <span class="speed">2.4s</span></div>
<div class="source-tab">🌸 二次元 <span class="speed">2.8s</span></div>
</div>
<!-- Category Tabs -->
<div class="category-tabs">
<div class="category-tab active">🌐 全部</div>
<div class="category-tab">🌿 自然</div>
<div class="category-tab">🐾 动物</div>
<div class="category-tab">🏛 建筑</div>
<div class="category-tab">💻 科技</div>
<div class="category-tab">🎨 抽象</div>
<div class="category-tab">👤 人物</div>
<div class="category-tab">✈️ 旅行</div>
<div class="category-tab">🍜 美食</div>
<div class="category-tab">🌸 动漫</div>
</div>
<!-- Search -->
<div class="search-bar">🔍 搜索壁纸…</div>
<!-- Masonry Grid -->
<div class="masonry">
<div class="masonry-item selected">
<div class="loaded-badge">已缓存</div>
<img src="https://images.unsplash.com/photo-1506744038136-46273834b3fb?w=300&h=400&fit=crop" alt="" loading="lazy">
<div class="overlay">
<div class="title">Mountain Lake</div>
<div class="meta"><span>3840×2160</span><span>👁 12.3k</span></div>
</div>
<div class="select-check"></div>
</div>
<div class="masonry-item">
<img src="https://images.unsplash.com/photo-1470071459604-3b5ec3a7fe05?w=300&h=200&fit=crop" alt="" loading="lazy">
<div class="overlay">
<div class="title">Foggy Forest</div>
<div class="meta"><span>2560×1440</span></div>
</div>
</div>
<div class="masonry-item">
<div class="loaded-badge">已缓存</div>
<img src="https://images.unsplash.com/photo-1441974231531-c6227db76b6e?w=300&h=350&fit=crop" alt="" loading="lazy">
<div class="overlay">
<div class="title">Sunlight Forest</div>
<div class="meta"><span>1920×1080</span><span>👁 8.5k</span></div>
</div>
</div>
<div class="masonry-item">
<img src="https://images.unsplash.com/photo-1507525428034-b723cf961d3e?w=300&h=250&fit=crop" alt="" loading="lazy">
<div class="overlay">
<div class="title">Tropical Beach</div>
<div class="meta"><span>4096×2160</span></div>
</div>
</div>
<div class="masonry-item">
<img src="https://images.unsplash.com/photo-1519681393784-d120267933ba?w=300&h=450&fit=crop" alt="" loading="lazy">
<div class="overlay">
<div class="title">Starry Mountain</div>
<div class="meta"><span>3840×2160</span><span>👁 25.1k</span></div>
</div>
</div>
<div class="masonry-item">
<div class="loaded-badge">已缓存</div>
<img src="https://images.unsplash.com/photo-1501785888041-af3ef285b470?w=300&h=280&fit=crop" alt="" loading="lazy">
<div class="overlay">
<div class="title">Golden Sunset</div>
<div class="meta"><span>2560×1440</span></div>
</div>
</div>
<div class="masonry-item">
<div class="skeleton" style="height:180px;"></div>
</div>
<div class="masonry-item">
<div class="skeleton" style="height:220px;"></div>
</div>
</div>
<!-- Apply Button -->
<div class="apply-bar">
<button class="apply-btn">🖼 设为背景图层</button>
</div>
</div>
<!-- Fullscreen Mode (hidden by default) -->
<div id="fullscreenMode" style="display:none;">
<div class="gallery-header" style="padding-top:4px;">
<h2>🎨 壁纸模板</h2>
<div style="display:flex;gap:8px;align-items:center;">
<div class="gallery-close" style="background:rgba(255,255,255,0.06);">🔍</div>
</div>
</div>
<!-- Source Tabs -->
<div class="source-tabs">
<div class="source-tab active">📷 Unsplash <span class="speed">138ms</span></div>
<div class="source-tab">🎞 Pexels <span class="speed">136ms</span></div>
<div class="source-tab">🌍 必应 <span class="speed">245ms</span></div>
<div class="source-tab">🚀 NASA <span class="speed">133ms</span></div>
<div class="source-tab">🔍 Pixabay <span class="speed">140ms</span></div>
<div class="source-tab">🏙 WallSt <span class="speed">140ms</span></div>
<div class="source-tab">🖼 360 <span class="speed">264ms</span></div>
<div class="source-tab">🏔 精选 <span class="speed">345ms</span></div>
<div class="source-tab">🎲 多多 <span class="speed">2.1s</span></div>
<div class="source-tab">🔮 增强必应 <span class="speed">371ms</span></div>
<div class="source-tab">🎨 动漫 <span class="speed">2.4s</span></div>
<div class="source-tab">🌸 二次元 <span class="speed">2.8s</span></div>
</div>
<!-- Category Tabs -->
<div class="category-tabs">
<div class="category-tab active">🌐 全部</div>
<div class="category-tab">🌿 自然</div>
<div class="category-tab">🐾 动物</div>
<div class="category-tab">🏛 建筑</div>
<div class="category-tab">💻 科技</div>
<div class="category-tab">🎨 抽象</div>
<div class="category-tab">👤 人物</div>
<div class="category-tab">✈️ 旅行</div>
<div class="category-tab">🍜 美食</div>
<div class="category-tab">🌸 动漫</div>
</div>
<!-- Masonry Grid (fullscreen has more items) -->
<div class="masonry">
<div class="masonry-item">
<div class="loaded-badge">已缓存</div>
<img src="https://images.unsplash.com/photo-1506744038136-46273834b3fb?w=300&h=400&fit=crop" alt="" loading="lazy">
<div class="overlay">
<div class="title">Mountain Lake</div>
<div class="meta"><span>3840×2160</span><span>👁 12.3k</span><span>❤ 892</span></div>
</div>
</div>
<div class="masonry-item">
<img src="https://images.unsplash.com/photo-1470071459604-3b5ec3a7fe05?w=300&h=200&fit=crop" alt="" loading="lazy">
<div class="overlay">
<div class="title">Foggy Forest</div>
<div class="meta"><span>2560×1440</span><span>❤ 456</span></div>
</div>
</div>
<div class="masonry-item">
<div class="loaded-badge">已缓存</div>
<img src="https://images.unsplash.com/photo-1441974231531-c6227db76b6e?w=300&h=350&fit=crop" alt="" loading="lazy">
<div class="overlay">
<div class="title">Sunlight Forest</div>
<div class="meta"><span>1920×1080</span><span>👁 8.5k</span></div>
</div>
</div>
<div class="masonry-item">
<img src="https://images.unsplash.com/photo-1507525428034-b723cf961d3e?w=300&h=250&fit=crop" alt="" loading="lazy">
<div class="overlay">
<div class="title">Tropical Beach</div>
<div class="meta"><span>4096×2160</span><span>❤ 1.2k</span></div>
</div>
</div>
<div class="masonry-item">
<img src="https://images.unsplash.com/photo-1519681393784-d120267933ba?w=300&h=450&fit=crop" alt="" loading="lazy">
<div class="overlay">
<div class="title">Starry Mountain</div>
<div class="meta"><span>3840×2160</span><span>👁 25.1k</span></div>
</div>
</div>
<div class="masonry-item">
<div class="loaded-badge">已缓存</div>
<img src="https://images.unsplash.com/photo-1501785888041-af3ef285b470?w=300&h=280&fit=crop" alt="" loading="lazy">
<div class="overlay">
<div class="title">Golden Sunset</div>
<div class="meta"><span>2560×1440</span><span>❤ 723</span></div>
</div>
</div>
<div class="masonry-item">
<img src="https://images.unsplash.com/photo-1472214103451-9374bd1c798e?w=300&h=220&fit=crop" alt="" loading="lazy">
<div class="overlay">
<div class="title">Green Hills</div>
<div class="meta"><span>1920×1200</span></div>
</div>
</div>
<div class="masonry-item">
<img src="https://images.unsplash.com/photo-1505144808419-1957a94ca61e?w=300&h=380&fit=crop" alt="" loading="lazy">
<div class="overlay">
<div class="title">Ocean Waves</div>
<div class="meta"><span>3840×2160</span><span>👁 15.7k</span></div>
</div>
</div>
</div>
</div>
</div>
</div>
<p style="text-align:center;color:var(--text-hint);font-size:11px;margin-top:12px;">👆 点击顶部标签切换 编辑器抽屉 / 灵感页全屏 两种模式</p>
</div>
<!-- Design Spec -->
<div>
<h3 style="font-size:16px;font-weight:600;margin-bottom:16px;">📋 公共组件设计规格</h3>
<div style="background:var(--bg-card);border-radius:var(--radius-lg);border:1px solid var(--border);padding:16px;margin-bottom:16px;">
<h4 style="font-size:13px;font-weight:600;margin-bottom:8px;">🏗️ 组件架构</h4>
<pre style="font-size:11px;line-height:1.8;color:var(--text-secondary);font-family:'SF Mono',Menlo,monospace;">
WallpaperGalleryView (公共组件)
├── WallpaperGalleryMode enum
│ ├── drawer → 编辑器抽屉面板
│ └── fullscreen → 灵感页全屏
├── WallpaperSourceBar → 源切换横滑
├── WallpaperCategoryBar → 分类横滑(含"全部")
├── WallpaperSearchBar → 搜索栏
├── WallpaperMasonryGrid → 瀑布流网格
│ ├── 已加载优先排序
│ ├── CachedNetworkImage
│ └── 选择态/预览态
├── WallpaperApplyBar → 应用按钮(仅drawer)
└── WallpaperPreviewSheet → 大图预览(仅fullscreen)</pre>
</div>
<div style="background:var(--bg-card);border-radius:var(--radius-lg);border:1px solid var(--border);padding:16px;margin-bottom:16px;">
<h4 style="font-size:13px;font-weight:600;margin-bottom:8px;">🐛 图片不显示 Bug 修复</h4>
<div style="font-size:12px;line-height:1.8;color:var(--text-secondary);">
<p><span style="color:#FF3B30;">根因:</span> API返回的 <code>thumbnail_url</code> / <code>preview_url</code> 可能为空字符串</p>
<p><span style="color:#4CD964;">修复:</span> 三级回退策略 thumbnailUrl → previewUrl → imageUrl</p>
<p><span style="color:#4CD964;">修复:</span> 空URL时显示渐变占位+重试按钮</p>
<p><span style="color:#4CD964;">修复:</span> CachedNetworkImage 添加 http headers (User-Agent)</p>
</div>
</div>
<div style="background:var(--bg-card);border-radius:var(--radius-lg);border:1px solid var(--border);padding:16px;margin-bottom:16px;">
<h4 style="font-size:13px;font-weight:600;margin-bottom:8px;">⚡ 已加载优先策略</h4>
<div style="font-size:12px;line-height:1.8;color:var(--text-secondary);">
<p>1. 切换源/分类时,已缓存的图片排到前面</p>
<p>2. 使用 <code>CachedNetworkImage</code> 的缓存管理器检测本地缓存</p>
<p>3. 已缓存项显示绿色"已缓存"标记</p>
<p>4. 未缓存项显示骨架屏 shimmer 动画</p>
</div>
</div>
<div style="background:var(--bg-card);border-radius:var(--radius-lg);border:1px solid var(--border);padding:16px;">
<h4 style="font-size:13px;font-weight:600;margin-bottom:8px;">🌐 分类增加"全部"</h4>
<div style="font-size:12px;line-height:1.8;color:var(--text-secondary);">
<p>WallpaperCategory 枚举首位增加 <code>all('all', '🌐', '全部')</code></p>
<p>默认选中"全部",不传 category 参数给 API</p>
<p>⚠️ 已有 all 枚举值,需确认 API 传参逻辑</p>
</div>
</div>
</div>
</div>
</div>
<!-- ═══════════════════════════════════════════ -->
<!-- Section 3: 拖拽描边效果 -->
<!-- ═══════════════════════════════════════════ -->
<div class="section">
<div class="section-title">✏️ 拖拽描边效果 — 两种风格</div>
<div class="section-desc">
拖拽可编辑视图组件(文本/贴纸/表情包等)时显示描边,松手后取消。<br>
编辑器顶部工具栏增加设置按钮,点击弹出 Sheet 切换风格。
</div>
<div class="drag-demo-area">
<div>
<div class="drag-demo-box dashed">
<div class="content">
文本层
<div class="corner-bl"></div>
<div class="corner-br"></div>
</div>
</div>
<div class="drag-label">虚线 + 控制点</div>
</div>
<div>
<div class="drag-demo-box glow">
<div class="content">贴纸层</div>
</div>
<div class="drag-label">发光边框</div>
</div>
</div>
<div style="background:var(--bg-card);border-radius:var(--radius-lg);border:1px solid var(--border);padding:16px;margin-top:24px;max-width:600px;margin-left:auto;margin-right:auto;">
<h4 style="font-size:13px;font-weight:600;margin-bottom:8px;">⚙️ 设置入口</h4>
<div style="font-size:12px;line-height:1.8;color:var(--text-secondary);">
<p>📍 位置:编辑器顶部导航栏,导出按钮左侧</p>
<p>🔘 图标CupertinoIcons.settings (齿轮)</p>
<p>📋 弹出StupidSimpleGlassSheet 设置面板</p>
<p>🔄 选项:描边风格切换 (虚线+控制点 / 发光边框)</p>
<p>💾 持久化SharedPreferences 存储用户偏好</p>
</div>
</div>
</div>
<!-- ═══════════════════════════════════════════ -->
<!-- Section 4: 富文本编辑器 -->
<!-- ═══════════════════════════════════════════ -->
<div class="section">
<div class="section-title">📝 富文本编辑器 — 中级排版</div>
<div class="section-desc">
点击底部"文字"按钮时,显示富文本编辑面板,实时渲染到编辑区画布。<br>
不与编辑器已有功能重复(已有:颜色/对齐/字号),新增:粗体/斜体/下划线/删除线/列表/引用/代码块。
</div>
<div class="richtext-demo">
<div class="richtext-toolbar">
<button class="tool-btn active" title="粗体"><b>B</b></button>
<button class="tool-btn" title="斜体"><i>I</i></button>
<button class="tool-btn" title="下划线"><u>U</u></button>
<button class="tool-btn" title="删除线"><s>S</s></button>
<div class="divider"></div>
<button class="tool-btn" title="有序列表">1.</button>
<button class="tool-btn" title="无序列表"></button>
<button class="tool-btn" title="引用"></button>
<button class="tool-btn" title="代码块">&lt;/&gt;</button>
<div class="divider"></div>
<button class="tool-btn" title="字号">Aa</button>
<button class="tool-btn" title="颜色">🎨</button>
<button class="tool-btn" title="对齐"></button>
</div>
<div class="richtext-canvas">
<h2>示例标题</h2>
<p>这是一段<span class="bold">粗体文字</span>,包含<span class="italic">斜体</span><span class="underline">下划线</span><span class="strikethrough">删除线</span>效果。</p>
<blockquote>引用块:生活不止眼前的苟且,还有诗和远方。</blockquote>
<ul>
<li>列表项一:晨光熹微</li>
<li>列表项二:暮色苍茫</li>
<li>列表项三:<code>代码片段</code> 也可以嵌入</li>
</ul>
</div>
</div>
<div style="background:var(--bg-card);border-radius:var(--radius-lg);border:1px solid var(--border);padding:16px;margin-top:20px;max-width:600px;margin-left:auto;margin-right:auto;">
<h4 style="font-size:13px;font-weight:600;margin-bottom:8px;">📚 推荐库选择</h4>
<div style="font-size:12px;line-height:2;color:var(--text-secondary);">
<p><span style="color:#4CD964;">✅ 推荐:</span> <code>flutter_quill</code> (^11.0) — 最成熟的Flutter富文本库</p>
<p>· 支持粗体/斜体/下划线/删除线/列表/引用/代码块</p>
<p>· 自定义工具栏可与编辑器UI融合</p>
<p>· Delta格式可序列化为JSON</p>
<p>· 导出为HTML/Markdown</p>
<p><span style="color:#FF9500;">备选:</span> <code>appflowy_editor</code> — 功能更全但更重,适合文档编辑</p>
</div>
</div>
</div>
<!-- ═══════════════════════════════════════════ -->
<!-- Section 5: Emoji → Icon 映射表 -->
<!-- ═══════════════════════════════════════════ -->
<div class="section">
<div class="section-title">🔄 Emoji → Icon 全量替换映射</div>
<div class="section-desc">
优先级CupertinoIcons → SVG → 自绘 → Emoji(兜底) · 每按钮仅一个icon
</div>
<div style="overflow-x:auto;">
<table class="icon-table">
<thead>
<tr>
<th>原始Emoji</th>
<th>原标签</th>
<th>替换方案</th>
<th>来源</th>
</tr>
</thead>
<tbody>
<tr><td>🖌️</td><td>编辑Tab</td><td><div class="icon-preview"><span class="cupertino">🖌</span> paintbrush</div></td><td><span class="tag cupertino">Cupertino</span></td></tr>
<tr><td></td><td>效果Tab</td><td><div class="icon-preview"><span class="cupertino"></span> sparkles</div></td><td><span class="tag cupertino">Cupertino</span></td></tr>
<tr><td>📦</td><td>内容Tab</td><td><div class="icon-preview"><span class="cupertino"></span> square_grid_2x2</div></td><td><span class="tag cupertino">Cupertino</span></td></tr>
<tr><td>📐</td><td>画布Tab</td><td><div class="icon-preview"><span class="cupertino"></span> triangle_ruler</div></td><td><span class="tag svg">SVG</span></td></tr>
<tr><td>🖌️</td><td>画笔</td><td><div class="icon-preview"><span class="cupertino">🖌</span> paintbrush</div></td><td><span class="tag cupertino">Cupertino</span></td></tr>
<tr><td>📝</td><td>文字</td><td><div class="icon-preview"><span class="cupertino">T</span> textformat</div></td><td><span class="tag cupertino">Cupertino</span></td></tr>
<tr><td>✂️</td><td>裁剪</td><td><div class="icon-preview"><span class="cupertino"></span> scissors</div></td><td><span class="tag cupertino">Cupertino</span></td></tr>
<tr><td>🎨</td><td>色调</td><td><div class="icon-preview"><span class="cupertino">🎨</span> paintpalette</div></td><td><span class="tag cupertino">Cupertino</span></td></tr>
<tr><td>🔍</td><td>滤镜</td><td><div class="icon-preview"><span class="cupertino"></span> circle_grid_3x3</div></td><td><span class="tag cupertino">Cupertino</span></td></tr>
<tr><td>🌫️</td><td>模糊</td><td><div class="icon-preview"><span class="cupertino"></span> circle_dashed</div></td><td><span class="tag svg">SVG</span></td></tr>
<tr><td>😀</td><td>表情</td><td><div class="icon-preview"><span class="cupertino"></span> smiley</div></td><td><span class="tag cupertino">Cupertino</span></td></tr>
<tr><td>🎭</td><td>贴纸</td><td><div class="icon-preview"><span class="cupertino">🎭</span> stickers</div></td><td><span class="tag cupertino">Cupertino</span></td></tr>
<tr><td>🌈</td><td>渐变</td><td><div class="icon-preview"><span class="cupertino"></span> gradient</div></td><td><span class="tag svg">SVG</span></td></tr>
<tr><td>💧</td><td>水印</td><td><div class="icon-preview"><span class="cupertino">💧</span> drop</div></td><td><span class="tag cupertino">Cupertino</span></td></tr>
<tr><td>🖼️</td><td>壁纸</td><td><div class="icon-preview"><span class="cupertino">🖼</span> photo</div></td><td><span class="tag cupertino">Cupertino</span></td></tr>
<tr><td>💬</td><td>一言</td><td><div class="icon-preview"><span class="cupertino">💬</span> chat_bubble</div></td><td><span class="tag cupertino">Cupertino</span></td></tr>
<tr><td>📝</td><td>草稿</td><td><div class="icon-preview"><span class="cupertino">📋</span> doc_text</div></td><td><span class="tag cupertino">Cupertino</span></td></tr>
<tr><td>🖼️</td><td>模板</td><td><div class="icon-preview"><span class="cupertino"></span> square_grid_2x2</div></td><td><span class="tag cupertino">Cupertino</span></td></tr>
<tr><td>🎯</td><td>取色</td><td><div class="icon-preview"><span class="cupertino"></span> eyedropper</div></td><td><span class="tag cupertino">Cupertino</span></td></tr>
<tr><td>📥</td><td>导入</td><td><div class="icon-preview"><span class="cupertino"></span> arrow_down_doc</div></td><td><span class="tag cupertino">Cupertino</span></td></tr>
<tr><td>🧰</td><td>更多</td><td><div class="icon-preview"><span class="cupertino"></span> ellipsis</div></td><td><span class="tag cupertino">Cupertino</span></td></tr>
<tr><td></td><td>Shader</td><td><div class="icon-preview"><span class="cupertino"></span> sparkles</div></td><td><span class="tag cupertino">Cupertino</span></td></tr>
<tr><td></td><td>历史</td><td><div class="icon-preview"><span class="cupertino"></span> clock</div></td><td><span class="tag cupertino">Cupertino</span></td></tr>
<tr><td>📚</td><td>图层</td><td><div class="icon-preview"><span class="cupertino"></span> list_bullet</div></td><td><span class="tag cupertino">Cupertino</span></td></tr>
<tr><td>↩️</td><td>撤销</td><td><div class="icon-preview"><span class="cupertino"></span> arrow_uturn_left</div></td><td><span class="tag cupertino">Cupertino</span></td></tr>
<tr><td>↪️</td><td>重做</td><td><div class="icon-preview"><span class="cupertino"></span> arrow_uturn_right</div></td><td><span class="tag cupertino">Cupertino</span></td></tr>
</tbody>
</table>
</div>
</div>
<!-- ═══════════════════════════════════════════ -->
<!-- Section 6: 库推荐 -->
<!-- ═══════════════════════════════════════════ -->
<div class="section">
<div class="section-title">📦 可扩展功能 & 库推荐</div>
<div class="section-desc">
基于现有库分析,推荐新增库和可扩展功能方向。
</div>
<div style="overflow-x:auto;">
<table class="lib-table">
<thead>
<tr>
<th></th>
<th>用途</th>
<th>对应功能</th>
<th>优先级</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>flutter_quill</code></td>
<td>富文本编辑器</td>
<td>编辑器文字面板 — 粗体/斜体/列表/引用/代码</td>
<td><span class="priority p1">P1</span></td>
</tr>
<tr>
<td><code>flutter_staggered_grid_view</code></td>
<td>瀑布流布局</td>
<td>壁纸公共组件 — 瀑布流网格</td>
<td><span class="priority p1">P1</span></td>
</tr>
<tr>
<td><code>visibility_detector</code></td>
<td>可见性检测</td>
<td>壁纸列表懒加载 + 已加载优先排序</td>
<td><span class="priority p1">P1</span></td>
</tr>
<tr>
<td><code>escapable</code> / 自绘</td>
<td>键盘快捷键</td>
<td>编辑器快捷键支持 (Ctrl+Z撤销等)</td>
<td><span class="priority p2">P2</span></td>
</tr>
<tr>
<td><code>super_clipboard</code></td>
<td>剪贴板增强</td>
<td>复制/粘贴图片到编辑器</td>
<td><span class="priority p2">P2</span></td>
</tr>
<tr>
<td><code>reorderable_grid_view</code></td>
<td>可排序网格</td>
<td>图层拖拽排序可视化</td>
<td><span class="priority p2">P2</span></td>
</tr>
<tr>
<td><code>dotted_border</code></td>
<td>虚线边框</td>
<td>拖拽描边 — 虚线风格</td>
<td><span class="priority p2">P2</span></td>
</tr>
<tr>
<td><code>auto_size_text</code></td>
<td>自适应文字</td>
<td>编辑器文字层自动缩放适配</td>
<td><span class="priority p3">P3</span></td>
</tr>
<tr>
<td><code>flutter_colorpicker</code></td>
<td>颜色选择器</td>
<td>编辑器取色器增强 (已有flex_color_picker可替代)</td>
<td><span class="priority p3">P3</span></td>
</tr>
</tbody>
</table>
</div>
<div style="background:var(--bg-card);border-radius:var(--radius-lg);border:1px solid var(--border);padding:16px;margin-top:20px;">
<h4 style="font-size:13px;font-weight:600;margin-bottom:8px;">🔮 基于现有库的可扩展功能</h4>
<div style="font-size:12px;line-height:2;color:var(--text-secondary);">
<p>· <code>flutter_svg</code> → SVG贴纸编辑器 (缩放/旋转/颜色替换)</p>
<p>· <code>lottie</code> → Lottie贴纸参数控制 (速度/方向/颜色)</p>
<p>· <code>flutter_3d_controller</code> → 3D贴纸姿态预设库</p>
<p>· <code>adaptive_palette</code> → 主题色自动应用到文字/边框/阴影</p>
<p>· <code>flutter_shaders_ui</code> → Shader参数实时调节面板</p>
<p>· <code>pro_image_editor</code> → 自定义Layer交互 (旋转手柄/缩放边框)</p>
<p>· <code>cached_network_image</code> → 离线壁纸缓存管理 + 缓存大小显示</p>
<p>· <code>flutter_animate</code> → 图层入场/退场动画预设</p>
</div>
</div>
</div>
<!-- ═══════════════════════════════════════════ -->
<!-- Footer -->
<!-- ═══════════════════════════════════════════ -->
<div style="text-align:center;padding:40px 0 20px;color:var(--text-hint);font-size:12px;">
<p>闲言APP · 编辑器增强设计方案 · 2026-05-04</p>
<p style="margin-top:4px;">Layout First · Design System First · Plan Before Code</p>
</div>
</div>
<script>
function switchMode(mode) {
const tabs = document.querySelectorAll('#modeSwitch .tab');
tabs.forEach(t => t.classList.remove('active'));
const drawer = document.getElementById('drawerMode');
const fullscreen = document.getElementById('fullscreenMode');
if (mode === 'drawer') {
tabs[0].classList.add('active');
drawer.style.display = '';
fullscreen.style.display = 'none';
} else {
tabs[1].classList.add('active');
drawer.style.display = 'none';
fullscreen.style.display = '';
}
}
// Interactive source/category tabs
document.querySelectorAll('.source-tab').forEach(tab => {
tab.addEventListener('click', function() {
this.parentElement.querySelectorAll('.source-tab').forEach(t => t.classList.remove('active'));
this.classList.add('active');
});
});
document.querySelectorAll('.category-tab').forEach(tab => {
tab.addEventListener('click', function() {
this.parentElement.querySelectorAll('.category-tab').forEach(t => t.classList.remove('active'));
this.classList.add('active');
});
});
// Interactive masonry items (select/deselect)
document.querySelectorAll('.masonry-item').forEach(item => {
item.addEventListener('click', function() {
const parent = this.closest('.masonry');
parent.querySelectorAll('.masonry-item').forEach(i => i.classList.remove('selected'));
this.classList.add('selected');
const applyBtn = this.closest('#phoneContent').querySelector('.apply-btn');
if (applyBtn) applyBtn.disabled = false;
});
});
</script>
</body>
</html>