feat: 初始化 Figma 模板库 56 套展示站
- 56 套模板元数据(35 Figma 原生 + 21 非 Figma) - 静态展示站 (HTML/CSS/JS 无框架):格式筛选、lightbox、搜索 - 35 个 .fig 已真上传 Figma Drafts 云端 - iframe 实时投射(登录态可看私有 Drafts) - 部署:nginx:alpine + basic-auth (kang) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
173
web/app.js
Normal file
@@ -0,0 +1,173 @@
|
||||
let DATA = null;
|
||||
let currentFilter = 'all';
|
||||
let currentSearch = '';
|
||||
|
||||
async function load() {
|
||||
const res = await fetch('data.json?_=' + Date.now());
|
||||
DATA = await res.json();
|
||||
renderStats();
|
||||
render();
|
||||
document.getElementById('gen').textContent = '生成于 ' + DATA.generated_at;
|
||||
}
|
||||
|
||||
function renderStats() {
|
||||
const t = DATA.templates;
|
||||
const fig = t.filter(x => x.has_fig).length;
|
||||
const sk = t.filter(x => x.has_sketch).length;
|
||||
const xd = t.filter(x => x.has_xd).length;
|
||||
const psd = t.filter(x => x.has_psd).length;
|
||||
const imp = t.filter(x => x.figma_key).length;
|
||||
const el = document.getElementById('stats');
|
||||
el.innerHTML = `
|
||||
<div class="stat"><b>${t.length}</b>套总量</div>
|
||||
<div class="stat"><b>${fig}</b>Figma 原生</div>
|
||||
<div class="stat"><b>${sk}</b>含 Sketch</div>
|
||||
<div class="stat"><b>${xd}</b>含 XD</div>
|
||||
<div class="stat"><b>${psd}</b>含 PSD</div>
|
||||
<div class="stat"><b>${imp}</b>已入 Figma Drafts</div>
|
||||
`;
|
||||
const banner = document.getElementById('banner');
|
||||
if (DATA.imported_summary) {
|
||||
banner.innerHTML = DATA.imported_summary;
|
||||
banner.classList.add('show');
|
||||
}
|
||||
}
|
||||
|
||||
function matchFilter(t) {
|
||||
if (currentFilter === 'all') return true;
|
||||
if (currentFilter === 'fig') return t.has_fig;
|
||||
if (currentFilter === 'sketch') return t.has_sketch;
|
||||
if (currentFilter === 'xd') return t.has_xd;
|
||||
if (currentFilter === 'psd') return t.has_psd;
|
||||
if (currentFilter === 'imported') return !!t.figma_key;
|
||||
return true;
|
||||
}
|
||||
|
||||
function matchSearch(t) {
|
||||
if (!currentSearch) return true;
|
||||
return t.name.toLowerCase().includes(currentSearch) ||
|
||||
t.id.toLowerCase().includes(currentSearch);
|
||||
}
|
||||
|
||||
function render() {
|
||||
const grid = document.getElementById('grid');
|
||||
const empty = document.getElementById('empty');
|
||||
const list = DATA.templates.filter(t => matchFilter(t) && matchSearch(t));
|
||||
if (list.length === 0) {
|
||||
grid.innerHTML = '';
|
||||
empty.hidden = false;
|
||||
return;
|
||||
}
|
||||
empty.hidden = true;
|
||||
grid.innerHTML = list.map(t => `
|
||||
<article class="card" data-id="${t.id}">
|
||||
<div class="cover">
|
||||
${t.cover ? `<img src="${t.cover}" alt="${escapeHtml(t.name)}" loading="lazy">` : ''}
|
||||
<span class="id">${t.id}</span>
|
||||
${t.figma_key ? '<span class="imported">已入 Figma</span>' : ''}
|
||||
</div>
|
||||
<div class="body">
|
||||
<h3>${escapeHtml(t.name)}</h3>
|
||||
<div class="meta">
|
||||
<div class="badges">
|
||||
${t.has_fig ? `<span class="badge fig">FIG${t.fig_count>1?'×'+t.fig_count:''}</span>` : ''}
|
||||
${t.has_sketch ? `<span class="badge sketch">SKETCH${t.sketch_count>1?'×'+t.sketch_count:''}</span>` : ''}
|
||||
${t.has_xd ? `<span class="badge xd">XD${t.xd_count>1?'×'+t.xd_count:''}</span>` : ''}
|
||||
${t.has_psd ? `<span class="badge psd">PSD</span>` : ''}
|
||||
</div>
|
||||
<span>${t.archive_size_mb} MB</span>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
`).join('');
|
||||
grid.querySelectorAll('.card').forEach(c => {
|
||||
c.addEventListener('click', () => openModal(c.dataset.id));
|
||||
});
|
||||
}
|
||||
|
||||
function openModal(id) {
|
||||
const t = DATA.templates.find(x => x.id === id);
|
||||
if (!t) return;
|
||||
const body = document.getElementById('modalBody');
|
||||
const figmaSection = t.figma_key
|
||||
? `<iframe class="figma-embed" src="https://embed.figma.com/design/${t.figma_key}/?embed-host=kang&footer=false" allowfullscreen></iframe>`
|
||||
: '';
|
||||
body.innerHTML = `
|
||||
<h2>${escapeHtml(t.name)}</h2>
|
||||
<div class="sub2">
|
||||
<code>${t.id}</code>
|
||||
<span>${t.archive_size_mb} MB</span>
|
||||
${t.has_fig ? `<span class="badge fig">FIG×${t.fig_count}</span>` : ''}
|
||||
${t.has_sketch ? `<span class="badge sketch">SKETCH×${t.sketch_count}</span>` : ''}
|
||||
${t.has_xd ? `<span class="badge xd">XD×${t.xd_count}</span>` : ''}
|
||||
${t.has_psd ? `<span class="badge psd">PSD</span>` : ''}
|
||||
</div>
|
||||
<div class="actions">
|
||||
${t.figma_url
|
||||
? `<a class="btn" href="${t.figma_url}" target="_blank">在 Figma 打开 →</a>`
|
||||
: (t.has_fig
|
||||
? `<a class="btn" href="https://www.figma.com/files/recent" target="_blank">在 Figma Drafts 搜 "${escapeAttr(t.name.split(/[\s\-–—]/)[0])}" →</a>`
|
||||
: '')}
|
||||
<a class="btn ghost" href="${t.source_rel}" onclick="revealSource('${t.id}');return false;">在 Finder 中显示源包</a>
|
||||
<a class="btn ghost" href="javascript:void(0)" onclick="copyPath('${t.id}');">复制源文件路径</a>
|
||||
</div>
|
||||
${figmaSection}
|
||||
<div class="spec">
|
||||
<div class="k">源包</div><div class="v">source/${t.id}/${t.archive}</div>
|
||||
<div class="k">解压目录</div><div class="v">extracted/${t.id}/</div>
|
||||
${t.figma_key ? `<div class="k">Figma Key</div><div class="v">${t.figma_key}</div>` : ''}
|
||||
</div>
|
||||
<div class="gallery">
|
||||
${(t.gallery||[]).map(g => `<img src="${g}" loading="lazy">`).join('')}
|
||||
</div>
|
||||
`;
|
||||
document.getElementById('modal').hidden = false;
|
||||
document.body.style.overflow = 'hidden';
|
||||
}
|
||||
|
||||
function closeModal() {
|
||||
document.getElementById('modal').hidden = true;
|
||||
document.body.style.overflow = '';
|
||||
document.getElementById('modalBody').innerHTML = '';
|
||||
}
|
||||
|
||||
function revealSource(id) {
|
||||
// Dev-only helper: try opening local path in Finder via file://
|
||||
// Browsers block file:// from http:// — fall back to a helpful alert
|
||||
const path = `${location.pathname.replace(/\/[^/]*$/, '')}/../extracted/${id}`;
|
||||
alert(`本地路径:\n~/Projects/research/20260422-figma模板库/extracted/${id}/\n\n在终端跑:\nopen ~/Projects/research/20260422-figma模板库/extracted/${id}`);
|
||||
}
|
||||
|
||||
function escapeHtml(s) {
|
||||
return String(s).replace(/[&<>"']/g, c => ({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]));
|
||||
}
|
||||
function escapeAttr(s) {
|
||||
return String(s).replace(/"/g, '"');
|
||||
}
|
||||
function copyPath(id) {
|
||||
const t = DATA.templates.find(x => x.id === id);
|
||||
if (!t) return;
|
||||
const p = `~/Projects/research/20260422-figma模板库/extracted/${id}/`;
|
||||
navigator.clipboard.writeText(p).then(() => {
|
||||
alert('已复制:\n' + p);
|
||||
});
|
||||
}
|
||||
|
||||
document.getElementById('filters').addEventListener('click', e => {
|
||||
const btn = e.target.closest('.pill');
|
||||
if (!btn) return;
|
||||
document.querySelectorAll('.pill').forEach(p => p.classList.toggle('active', p === btn));
|
||||
currentFilter = btn.dataset.filter;
|
||||
render();
|
||||
});
|
||||
|
||||
document.getElementById('search').addEventListener('input', e => {
|
||||
currentSearch = e.target.value.trim().toLowerCase();
|
||||
render();
|
||||
});
|
||||
|
||||
document.getElementById('modalClose').addEventListener('click', closeModal);
|
||||
document.querySelector('.modal-bg').addEventListener('click', closeModal);
|
||||
document.addEventListener('keydown', e => { if (e.key === 'Escape') closeModal(); });
|
||||
|
||||
load();
|
||||
1291
web/data.json
Normal file
56
web/index.html
Normal file
@@ -0,0 +1,56 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<title>Figma 模板库 · 56 套精选</title>
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<header class="hero">
|
||||
<div class="wrap">
|
||||
<h1>Figma 模板库</h1>
|
||||
<p class="sub">56 套 Web UI Kit · 35 套 Figma 原生 · 本地源包 + 预览 + 可投射到个人账户</p>
|
||||
<div class="stats" id="stats"></div>
|
||||
<div class="banner" id="banner"></div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<nav class="toolbar">
|
||||
<div class="wrap">
|
||||
<div class="filters" id="filters">
|
||||
<button class="pill active" data-filter="all">全部</button>
|
||||
<button class="pill" data-filter="fig">Figma 原生</button>
|
||||
<button class="pill" data-filter="sketch">Sketch</button>
|
||||
<button class="pill" data-filter="xd">XD</button>
|
||||
<button class="pill" data-filter="psd">PSD</button>
|
||||
<button class="pill" data-filter="imported">已入 Figma</button>
|
||||
</div>
|
||||
<input type="search" id="search" placeholder="搜索模板名…">
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<main class="wrap">
|
||||
<div class="grid" id="grid"></div>
|
||||
<p class="empty" id="empty" hidden>没有匹配的模板</p>
|
||||
</main>
|
||||
|
||||
<div class="modal" id="modal" hidden>
|
||||
<div class="modal-bg"></div>
|
||||
<div class="modal-panel">
|
||||
<button class="modal-close" id="modalClose">✕</button>
|
||||
<div class="modal-body" id="modalBody"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<footer>
|
||||
<div class="wrap">
|
||||
<span>本地路径:<code>~/Projects/research/20260422-figma模板库/</code></span>
|
||||
<span id="gen"></span>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<script src="app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
BIN
web/previews/W1/预览 (1).jpg
Normal file
|
After Width: | Height: | Size: 245 KiB |
BIN
web/previews/W1/预览 (2).jpg
Normal file
|
After Width: | Height: | Size: 288 KiB |
BIN
web/previews/W1/预览 (3).jpg
Normal file
|
After Width: | Height: | Size: 225 KiB |
BIN
web/previews/W10/预览 (1).jpg
Normal file
|
After Width: | Height: | Size: 155 KiB |
BIN
web/previews/W10/预览 (2).jpg
Normal file
|
After Width: | Height: | Size: 256 KiB |
BIN
web/previews/W10/预览 (3).jpg
Normal file
|
After Width: | Height: | Size: 273 KiB |
BIN
web/previews/W11/预览 (1).jpg
Normal file
|
After Width: | Height: | Size: 292 KiB |
BIN
web/previews/W11/预览 (2).jpg
Normal file
|
After Width: | Height: | Size: 262 KiB |
BIN
web/previews/W11/预览 (3).jpg
Normal file
|
After Width: | Height: | Size: 155 KiB |
BIN
web/previews/W11/预览 (4).jpg
Normal file
|
After Width: | Height: | Size: 196 KiB |
BIN
web/previews/W11/预览 (5).jpg
Normal file
|
After Width: | Height: | Size: 104 KiB |
BIN
web/previews/W11/预览 (6).jpg
Normal file
|
After Width: | Height: | Size: 180 KiB |
BIN
web/previews/W11/预览 (7).jpg
Normal file
|
After Width: | Height: | Size: 117 KiB |
BIN
web/previews/W12/预览 (1).jpg
Normal file
|
After Width: | Height: | Size: 214 KiB |
BIN
web/previews/W12/预览 (2).jpg
Normal file
|
After Width: | Height: | Size: 187 KiB |
BIN
web/previews/W12/预览 (3).jpg
Normal file
|
After Width: | Height: | Size: 120 KiB |
BIN
web/previews/W12/预览 (4).jpg
Normal file
|
After Width: | Height: | Size: 170 KiB |
BIN
web/previews/W13/预览 (1).jpg
Normal file
|
After Width: | Height: | Size: 294 KiB |
BIN
web/previews/W13/预览 (2).jpg
Normal file
|
After Width: | Height: | Size: 275 KiB |
BIN
web/previews/W13/预览 (3).jpg
Normal file
|
After Width: | Height: | Size: 290 KiB |
BIN
web/previews/W13/预览 (4).jpg
Normal file
|
After Width: | Height: | Size: 158 KiB |
BIN
web/previews/W14/预览 (1).jpg
Normal file
|
After Width: | Height: | Size: 166 KiB |
BIN
web/previews/W14/预览 (2).jpg
Normal file
|
After Width: | Height: | Size: 170 KiB |
BIN
web/previews/W14/预览 (3).jpg
Normal file
|
After Width: | Height: | Size: 194 KiB |
BIN
web/previews/W15/预览 (1).jpg
Normal file
|
After Width: | Height: | Size: 124 KiB |
BIN
web/previews/W15/预览 (2).jpg
Normal file
|
After Width: | Height: | Size: 127 KiB |
BIN
web/previews/W15/预览 (3).jpg
Normal file
|
After Width: | Height: | Size: 113 KiB |
BIN
web/previews/W15/预览 (4).jpg
Normal file
|
After Width: | Height: | Size: 102 KiB |
BIN
web/previews/W15/预览 (5).jpg
Normal file
|
After Width: | Height: | Size: 114 KiB |
BIN
web/previews/W16/预览 (1).jpg
Normal file
|
After Width: | Height: | Size: 234 KiB |
BIN
web/previews/W16/预览 (2).jpg
Normal file
|
After Width: | Height: | Size: 216 KiB |
BIN
web/previews/W16/预览 (3).jpg
Normal file
|
After Width: | Height: | Size: 275 KiB |
BIN
web/previews/W16/预览 (4).jpg
Normal file
|
After Width: | Height: | Size: 152 KiB |
BIN
web/previews/W16/预览 (5).jpg
Normal file
|
After Width: | Height: | Size: 175 KiB |
BIN
web/previews/W17/预览 (1).jpg
Normal file
|
After Width: | Height: | Size: 226 KiB |
BIN
web/previews/W17/预览 (2).jpg
Normal file
|
After Width: | Height: | Size: 270 KiB |
BIN
web/previews/W17/预览 (3).jpg
Normal file
|
After Width: | Height: | Size: 254 KiB |
BIN
web/previews/W17/预览 (4).jpg
Normal file
|
After Width: | Height: | Size: 475 KiB |
BIN
web/previews/W18/预览 (1).jpg
Normal file
|
After Width: | Height: | Size: 259 KiB |
BIN
web/previews/W18/预览 (2).jpg
Normal file
|
After Width: | Height: | Size: 240 KiB |
BIN
web/previews/W18/预览 (3).jpg
Normal file
|
After Width: | Height: | Size: 297 KiB |
BIN
web/previews/W18/预览 (4).jpg
Normal file
|
After Width: | Height: | Size: 102 KiB |
BIN
web/previews/W18/预览 (5).jpg
Normal file
|
After Width: | Height: | Size: 150 KiB |
BIN
web/previews/W18/预览 (6).jpg
Normal file
|
After Width: | Height: | Size: 196 KiB |
BIN
web/previews/W18/预览 (7).jpg
Normal file
|
After Width: | Height: | Size: 195 KiB |
BIN
web/previews/W18/预览 (8).jpg
Normal file
|
After Width: | Height: | Size: 235 KiB |
BIN
web/previews/W19/预览 (1).jpg
Normal file
|
After Width: | Height: | Size: 270 KiB |
BIN
web/previews/W19/预览 (2).jpg
Normal file
|
After Width: | Height: | Size: 266 KiB |
BIN
web/previews/W19/预览 (3).jpg
Normal file
|
After Width: | Height: | Size: 290 KiB |
BIN
web/previews/W20/预览 (1).jpg
Normal file
|
After Width: | Height: | Size: 276 KiB |
BIN
web/previews/W20/预览 (2).jpg
Normal file
|
After Width: | Height: | Size: 342 KiB |
BIN
web/previews/W20/预览 (3).jpg
Normal file
|
After Width: | Height: | Size: 341 KiB |
BIN
web/previews/W20/预览 (4).jpg
Normal file
|
After Width: | Height: | Size: 332 KiB |
BIN
web/previews/W21/预览 (1).jpg
Normal file
|
After Width: | Height: | Size: 193 KiB |
BIN
web/previews/W21/预览 (2).jpg
Normal file
|
After Width: | Height: | Size: 225 KiB |
BIN
web/previews/W21/预览 (3).jpg
Normal file
|
After Width: | Height: | Size: 200 KiB |
BIN
web/previews/W22/预览 (1).jpg
Normal file
|
After Width: | Height: | Size: 250 KiB |
BIN
web/previews/W22/预览 (2).jpg
Normal file
|
After Width: | Height: | Size: 266 KiB |
BIN
web/previews/W22/预览 (3).jpg
Normal file
|
After Width: | Height: | Size: 317 KiB |
BIN
web/previews/W22/预览 (4).jpg
Normal file
|
After Width: | Height: | Size: 252 KiB |
BIN
web/previews/W22/预览 (5).jpg
Normal file
|
After Width: | Height: | Size: 248 KiB |
BIN
web/previews/W23/预览 (1).jpg
Normal file
|
After Width: | Height: | Size: 192 KiB |
BIN
web/previews/W23/预览 (2).jpg
Normal file
|
After Width: | Height: | Size: 189 KiB |
BIN
web/previews/W23/预览 (3).jpg
Normal file
|
After Width: | Height: | Size: 210 KiB |
BIN
web/previews/W24/预览 (1).jpg
Normal file
|
After Width: | Height: | Size: 332 KiB |
BIN
web/previews/W24/预览 (2).jpg
Normal file
|
After Width: | Height: | Size: 330 KiB |
BIN
web/previews/W24/预览 (3).jpg
Normal file
|
After Width: | Height: | Size: 143 KiB |
BIN
web/previews/W24/预览 (4).jpg
Normal file
|
After Width: | Height: | Size: 167 KiB |
BIN
web/previews/W24/预览 (5).jpg
Normal file
|
After Width: | Height: | Size: 162 KiB |
BIN
web/previews/W25/预览 (1).jpg
Normal file
|
After Width: | Height: | Size: 220 KiB |
BIN
web/previews/W25/预览 (2).jpg
Normal file
|
After Width: | Height: | Size: 221 KiB |
BIN
web/previews/W25/预览 (3).jpg
Normal file
|
After Width: | Height: | Size: 218 KiB |
BIN
web/previews/W25/预览 (4).jpg
Normal file
|
After Width: | Height: | Size: 200 KiB |
BIN
web/previews/W25/预览 (5).jpg
Normal file
|
After Width: | Height: | Size: 382 KiB |
BIN
web/previews/W25/预览 (6).jpg
Normal file
|
After Width: | Height: | Size: 252 KiB |
BIN
web/previews/W26/预览 (1).jpg
Normal file
|
After Width: | Height: | Size: 281 KiB |
BIN
web/previews/W26/预览 (2).jpg
Normal file
|
After Width: | Height: | Size: 306 KiB |
BIN
web/previews/W26/预览 (3).jpg
Normal file
|
After Width: | Height: | Size: 287 KiB |
BIN
web/previews/W26/预览 (4).jpg
Normal file
|
After Width: | Height: | Size: 295 KiB |
BIN
web/previews/W27/预览 (1).jpg
Normal file
|
After Width: | Height: | Size: 273 KiB |
BIN
web/previews/W27/预览 (2).jpg
Normal file
|
After Width: | Height: | Size: 200 KiB |
BIN
web/previews/W27/预览 (3).jpg
Normal file
|
After Width: | Height: | Size: 212 KiB |
BIN
web/previews/W28/预览 (1).jpg
Normal file
|
After Width: | Height: | Size: 292 KiB |
BIN
web/previews/W28/预览 (2).jpg
Normal file
|
After Width: | Height: | Size: 164 KiB |
BIN
web/previews/W28/预览 (3).jpg
Normal file
|
After Width: | Height: | Size: 157 KiB |
BIN
web/previews/W28/预览 (4).jpg
Normal file
|
After Width: | Height: | Size: 230 KiB |
BIN
web/previews/W28/预览 (5).jpg
Normal file
|
After Width: | Height: | Size: 250 KiB |
BIN
web/previews/W29/预览 (1).jpg
Normal file
|
After Width: | Height: | Size: 257 KiB |
BIN
web/previews/W29/预览 (2).jpg
Normal file
|
After Width: | Height: | Size: 287 KiB |
BIN
web/previews/W29/预览 (3).jpg
Normal file
|
After Width: | Height: | Size: 213 KiB |
BIN
web/previews/W29/预览 (4).jpg
Normal file
|
After Width: | Height: | Size: 254 KiB |
BIN
web/previews/W3/预览 (1).jpg
Normal file
|
After Width: | Height: | Size: 155 KiB |
BIN
web/previews/W3/预览 (2).jpg
Normal file
|
After Width: | Height: | Size: 125 KiB |
BIN
web/previews/W3/预览 (3).jpg
Normal file
|
After Width: | Height: | Size: 151 KiB |
BIN
web/previews/W30/预览 (1).jpg
Normal file
|
After Width: | Height: | Size: 140 KiB |
BIN
web/previews/W30/预览 (2).jpg
Normal file
|
After Width: | Height: | Size: 134 KiB |
BIN
web/previews/W30/预览 (3).jpg
Normal file
|
After Width: | Height: | Size: 199 KiB |