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>
10
.gitignore
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
# keep repo lean — source archives + extracted are local only
|
||||
source/
|
||||
extracted/
|
||||
.DS_Store
|
||||
__pycache__/
|
||||
*.pyc
|
||||
.playwright-mcp/
|
||||
|
||||
# generated manifests stay in repo so site works on fresh clone
|
||||
# (manifest.json, figma-files.json, figma-match-report.json, web/)
|
||||
1
.htpasswd
Normal file
@@ -0,0 +1 @@
|
||||
kang:$2y$10$J/3TiFUWxoSw4ahfmoQNmOPUarSDgf3gYFZzvheogHZbktACWFuxq
|
||||
91
.memory/status.md
Normal file
@@ -0,0 +1,91 @@
|
||||
# Figma 模板库 - 状态
|
||||
|
||||
> 2026-04-22 立项,从桌面搬迁 11GB "Figma模板网页官网首页PC电脑Web端UI界面Sketch Xd设计素材200套" 整理并展示
|
||||
|
||||
## 核心事实
|
||||
|
||||
- **"200 套" 名不副实**:实际 **56 个源包** + 202 张 A 预览索引缩略图
|
||||
- 格式分布:51×zip + 3×7z + 2×rar,展开后:
|
||||
- **含 `.fig`**:35 套(Figma 原生)
|
||||
- 只 `.sketch`:8 套
|
||||
- 只 `.xd`:7 套
|
||||
- 只 `.psd`:2 套
|
||||
- 混合无 fig:4 套
|
||||
|
||||
## 目录结构
|
||||
|
||||
```
|
||||
~/Projects/research/20260422-figma模板库/
|
||||
├── source/ 11G · 原始压缩包 + A 预览/ 202 张索引图
|
||||
├── extracted/ 全 56 套已解压(nested zip 也全递归解压了:W27/W39/W20 等)
|
||||
├── manifest.json 56 条元数据(name/格式/文件列表/大小)
|
||||
├── scripts/
|
||||
│ └── import-to-figma.sh slim/full 模式批量 open -a Figma
|
||||
├── web/ 展示站
|
||||
│ ├── index.html
|
||||
│ ├── styles.css
|
||||
│ ├── app.js
|
||||
│ ├── data.json 56 条含 cover/gallery/figma_key 字段(key 暂空)
|
||||
│ ├── thumbs/ 56 张 cover(900px/jpg82)6.1M
|
||||
│ └── previews/ 全部 galleries(1600px/jpg82)52M
|
||||
└── .memory/
|
||||
```
|
||||
|
||||
## 已写入 Figma Drafts(slim 模式 43 个)
|
||||
|
||||
- W1 测试 1 个(手动 open)+ 42 个 slim 批量
|
||||
- **W37 Daily UI**:30 天的 kit 只入了第 1 天("Day 03 - Videos Website Landing"),其他 29 天本地 `extracted/W37/` 保留
|
||||
- W5 Wiloa 全 4 变体(Hotel/Plant/Restaurant/Travel)
|
||||
- W56 Orabel 全 6 页(Home/About/Portfolio/Blog/Contact/Open Menu)
|
||||
- 其他 32 个 template 各 1 个 fig
|
||||
|
||||
## 非 Figma 原生的 21 套(已搁置)
|
||||
|
||||
只 Sketch:W11, W12, W18, W20, W30, W44(6)
|
||||
只 XD:W8, W19, W22, W23, W39, W49(6)
|
||||
只 PSD:W17(含 68 psd + 2 xd), W42(11 psd)
|
||||
混合无 fig:W4, W9, W15, W21, W27, W36, W50(7)
|
||||
|
||||
后续如需:
|
||||
- Sketch → Figma 桌面 File→Import 可批量(多选)
|
||||
- XD → 第三方转换器(convertxd 之类)或搁置
|
||||
- PSD → 非 Figma 生态
|
||||
|
||||
## 展示站
|
||||
|
||||
- 端口 **4010**(端口注册中心分的 4010-4019 块)
|
||||
- 启动:`cd web && python3 -m http.server 4010`
|
||||
- 功能:卡片网格 · 格式筛选(fig/sketch/xd/psd/imported)· 名称搜索 · lightbox modal · 本地源包路径复制 · Figma Drafts 跳转
|
||||
- 暗色调 / CSS variables / 无框架 / 静态
|
||||
|
||||
## 活·iframe 投射 ✅ 已完成
|
||||
|
||||
**重大踩坑**:`open -a Figma file.fig` 只在桌面版本地打开(Untitled tab),**不上传云端**。Drafts 云端 API 看不到。
|
||||
|
||||
**真·上传路径**:Figma 网页版 Drafts 页 → "+" → Import → "Import from computer"(接受 .fig)。Playwright `browser_file_upload` 可一次喂 42 个,Figma 串行处理 ~50s/个 → 约 60 分钟全部上云。
|
||||
|
||||
**fileKey 抓取(绕了好几圈)**:
|
||||
1. ❌ Figma REST API:无 "list drafts" 接口
|
||||
2. ❌ Figma Desktop `settings.json`:不实时更新
|
||||
3. ❌ AppleScript 读 Figma 桌面窗口:被系统拒
|
||||
4. ❌ Figma Plugin API:要已知 fileKey 才能进
|
||||
5. ❌ Chromium Cookie:macOS Keychain 加密
|
||||
6. ✅ **Figma 内部 API**:登录后调 `/api/folders/{drafts_folder_id}/paginated_files?...` 拿全 list(drafts_folder_id 从 `/api/user/state` 拿不到,但触发 Drafts UI 后从 network 抓到 `194902307`)
|
||||
7. 模糊匹配名称回填 → `scripts/match-and-update.py`(35/35 score 1.0)
|
||||
|
||||
**最终架构**:
|
||||
- 静态卡片缩略图(本地 56 张,离线可看)
|
||||
- 点开 modal → `<iframe src="https://embed.figma.com/design/{key}/?embed-host=kang&footer=false">` 实时渲染你 Figma Drafts 里的真实文件
|
||||
- "在 Figma 打开 →" 按钮跳转编辑模式
|
||||
- 35/35 fig 模板全部支持
|
||||
|
||||
iframe 嵌入只在你登录 Figma 的浏览器里能看(drafts 私有),符合个人用途。
|
||||
|
||||
## 部署(未做)
|
||||
|
||||
若要上 VPS(像 `styles.kang-kang.com` 那样):
|
||||
- Coolify dockerfile nginx:alpine 模式
|
||||
- DNS:`*.kang-kang.com` 泛解析已指 76.13.31.179
|
||||
- 建议域名:`figma-templates.kang-kang.com` 或 `figma.kang-kang.com`
|
||||
- 本地静态文件体积:web/ 合计 ~60MB(可接受)
|
||||
- 加 basic-auth(类似 styles 站)
|
||||
9
Dockerfile
Normal file
@@ -0,0 +1,9 @@
|
||||
FROM nginx:alpine
|
||||
|
||||
COPY nginx.conf /etc/nginx/nginx.conf
|
||||
COPY .htpasswd /etc/nginx/.htpasswd
|
||||
|
||||
COPY web/ /usr/share/nginx/html/
|
||||
|
||||
EXPOSE 80
|
||||
CMD ["nginx", "-g", "daemon off;"]
|
||||
39
README.md
Normal file
@@ -0,0 +1,39 @@
|
||||
# Figma 模板库
|
||||
|
||||
56 套 Web UI Kit 本地整理 + 静态展示站,35 套 Figma 原生已云端就位 Drafts。
|
||||
|
||||
## 结构
|
||||
|
||||
```
|
||||
├── source/ 11G · 原始 zip/7z/rar + 预览图(gitignored)
|
||||
├── extracted/ 全套解压(gitignored)
|
||||
├── manifest.json 56 条元数据
|
||||
├── figma-files.json 云端 fileKey 列表
|
||||
├── scripts/
|
||||
│ ├── import-to-figma.sh open -a Figma 批量(注意:不上云,仅本地)
|
||||
│ └── match-and-update.py 按名字模糊匹配 Figma fileKey 回填 data.json
|
||||
├── web/ 静态展示站
|
||||
│ ├── index.html / styles.css / app.js
|
||||
│ ├── data.json 前端数据
|
||||
│ ├── thumbs/ 6.1M 56 张 cover
|
||||
│ └── previews/ 52M gallery 压缩 JPG
|
||||
├── nginx.conf + .htpasswd + Dockerfile 部署用
|
||||
└── .memory/status.md 项目记忆
|
||||
```
|
||||
|
||||
## 本地跑
|
||||
|
||||
```bash
|
||||
cd web && python3 -m http.server 4010
|
||||
# → http://localhost:4010
|
||||
```
|
||||
|
||||
## 部署
|
||||
|
||||
`nginx:alpine` 容器 + basic-auth(kang / 见 credentials.md)。
|
||||
Coolify dockerfile 模式,Gitea 主仓 + GitHub 镜像。
|
||||
|
||||
## 关键教训
|
||||
|
||||
- **`open -a Figma file.fig` 不上传云端**,仅本地打开。真·上云走网页 Import 对话框(接受 .fig .sketch .pdf 等)
|
||||
- **fileKey 抓取**:Figma REST 无 list-drafts;用内部 API `/api/folders/{drafts_folder_id}/paginated_files` 需登录 cookie,Playwright MCP 实现
|
||||
1
figma-files.json
Normal file
@@ -0,0 +1 @@
|
||||
[{"key":"CLZepne3voPYx198q3wUHO","name":"Bright Kit"},{"key":"LA2daTQaigGkMUUNgPr6fp","name":"AKASHA-PAGEBUILDING-KIT"},{"key":"tLnL0G13yvkQtBZeQmPeq5","name":"06 Open Menu - Orabel Web UI Kit"},{"key":"bCrGhKu5PwOZh4F2EbyLk8","name":"05 Contact - Orabel Web UI Kit"},{"key":"Dc4uWhUeLCC6f2wiblXiBa","name":"04 Blog - Orabel Web UI Kit"},{"key":"BOnT7oN2pavJlDCdAO9Yih","name":"03 Portfolio - Orabel Web UI Kit"},{"key":"AEZMbGAy5IfnDAkFrbYwB6","name":"02 About - Orabel Web UI Kit"},{"key":"IoQBzPjHw51WjFKL52kJKN","name":"01 Home - Orabel Web UI Kit"},{"key":"IpdxPbiWuGZQpr3aNXsVpC","name":"Fecca"},{"key":"vcb4bLF79NTpybgXvAtkNM","name":"GETPAY - FINANCE TEMPLATES DESIGN"},{"key":"pMjAEiLKAFE57eaI4f4vva","name":"Aset - skill Shoot"},{"key":"U4YDJ7B0Sefem9r8LiEs8b","name":"Ui8_RealStatic_Static Mania"},{"key":"KtGIx2vrYNtApMiPYCTiiB","name":"Quicker Design system"},{"key":"3rhRDK0NC4oniMRGLNGnte","name":"WILOA - TRAVEL"},{"key":"1zIi1k19ngpoGixSB3upum","name":"WILOA - RESTAURANT"},{"key":"lv1hy5bS3Wx86slIXgorFn","name":"WILOA - PLANT"},{"key":"7C3QNOG6FhBRguT3nQ3dzf","name":"WILOA - HOTEL"},{"key":"df0oLsZta06a79s5iepJdF","name":"LANDINGKUY - Landing Page Design"},{"key":"Suvu5K5DSKPngkMKRb01MK","name":"Real Estate SaaS Kit & Dashboard (UI8)"},{"key":"sHmAQQxmvRTIxOg8OgQQFG","name":"3 template Agency"},{"key":"oZ7a2aFNZ21bg128ixnB3k","name":"Wiredunk_Wireframe"},{"key":"huDsMAoVThpYnGAU9j8WVB","name":"Modularity"},{"key":"4OTPxx5sgN8eFJ3joudoVH","name":"MasterFlow_UI_Kit"},{"key":"uAcopfnDXakv7l5Jr6sMqG","name":"iotask"},{"key":"fL1KqwIS3OmHCsUwhgJusG","name":"Figmaland- UI Kit (Startup)"},{"key":"PJHgFt29Lyo3JwMKxa5Tsm","name":"04_DailyUI_Video_Landing_Website"},{"key":"RNRNDiF6gFcTCi6wJncvgv","name":"JackCreative"},{"key":"BfBzvqu4VK53iSD5fbKNAo","name":"Intirior Architecture web ui kit"},{"key":"RKIYTIOCADVsASHNxZ5tr2","name":"Social Media Dashboard - UI Kit"},{"key":"qFC0pVEHe9Toc2aJ2MgO32","name":"DevAgency - Web Ui Kits"},{"key":"khWI1rrgwj5702NilEcha2","name":"Sydney Oasis Hotel Websites"},{"key":"5CZLD5lcxxRQNE1QWjqbmP","name":"UXFlow Web Kit"},{"key":"dvPWtG7KaEWr895iCI0Ioy","name":"LOOMI"},{"key":"5R9tevOwnk4iJzU3jEjg0q","name":"Landing UI Kit"},{"key":"oyHlh6eh7TjxYhCUbFTvBS","name":"Jobhuntly - Job Board & Portal UI Kit"},{"key":"G4mWQN0IWGkAKpD7Ga0bG3","name":"Core - Dashboard Builder"},{"key":"Sarco9c50gDttu37szmurE","name":"Webflew Agency UI Kit"},{"key":"x3g1FivshKeZ9PwccE7w3w","name":"Core - Dashboard Builder"},{"key":"fsufV596UmcNyM8yfCYlAr","name":"Eco -Ecommerce Analytics Admin Dashboard"},{"key":"s5KbowY6N6M4IkjHbgLjOc","name":"Figmaland-UI MAX ( ui8 )"},{"key":"ms5OYBa3fRlDwRmSLWWorY","name":"Online Shop UI Kit"},{"key":"pxsKzhDM8UL8oTSB4hUpOZ","name":"saascuy"},{"key":"EKp3ZDo2A3kDp88DMvFqte","name":"Havoc Agency UI Kit"}]
|
||||
282
figma-match-report.json
Normal file
@@ -0,0 +1,282 @@
|
||||
[
|
||||
{
|
||||
"W": "W1",
|
||||
"name": "Saascuy - Saas Landing Page UI KIT",
|
||||
"fig_stem": "saascuy",
|
||||
"matched": "saascuy",
|
||||
"key": "pxsKzhDM8UL8oTSB4hUpOZ",
|
||||
"score": 1.0
|
||||
},
|
||||
{
|
||||
"W": "W10",
|
||||
"name": "Havoc Agency UI Kit",
|
||||
"fig_stem": "Havoc Agency UI Kit",
|
||||
"matched": "Havoc Agency UI Kit",
|
||||
"key": "EKp3ZDo2A3kDp88DMvFqte",
|
||||
"score": 1.0
|
||||
},
|
||||
{
|
||||
"W": "W13",
|
||||
"name": "Premium Online Shop UI Kit & Dashboard",
|
||||
"fig_stem": "Online Shop UI Kit",
|
||||
"matched": "Online Shop UI Kit",
|
||||
"key": "ms5OYBa3fRlDwRmSLWWorY",
|
||||
"score": 1.0
|
||||
},
|
||||
{
|
||||
"W": "W14",
|
||||
"name": "Ui Max",
|
||||
"fig_stem": "Figmaland-UI MAX ( ui8 )",
|
||||
"matched": "Figmaland-UI MAX ( ui8 )",
|
||||
"key": "s5KbowY6N6M4IkjHbgLjOc",
|
||||
"score": 1.0
|
||||
},
|
||||
{
|
||||
"W": "W16",
|
||||
"name": "Eco -Ecommerce Analytics Admin Dashboard Kit",
|
||||
"fig_stem": "Eco -Ecommerce Analytics Admin Dashboard",
|
||||
"matched": "Eco -Ecommerce Analytics Admin Dashboard",
|
||||
"key": "fsufV596UmcNyM8yfCYlAr",
|
||||
"score": 1.0
|
||||
},
|
||||
{
|
||||
"W": "W2",
|
||||
"name": "Core – Dashboard Builder",
|
||||
"fig_stem": "Core - Dashboard Builder",
|
||||
"matched": "Core - Dashboard Builder",
|
||||
"key": "G4mWQN0IWGkAKpD7Ga0bG3",
|
||||
"score": 1.0
|
||||
},
|
||||
{
|
||||
"W": "W24",
|
||||
"name": "Webflew Agency Template UI Kit",
|
||||
"fig_stem": "Webflew Agency UI Kit",
|
||||
"matched": "Webflew Agency UI Kit",
|
||||
"key": "Sarco9c50gDttu37szmurE",
|
||||
"score": 1.0
|
||||
},
|
||||
{
|
||||
"W": "W25",
|
||||
"name": "Core – Dashboard Builder",
|
||||
"fig_stem": "Core - Dashboard Builder",
|
||||
"matched": "Core - Dashboard Builder",
|
||||
"key": "x3g1FivshKeZ9PwccE7w3w",
|
||||
"score": 1.0
|
||||
},
|
||||
{
|
||||
"W": "W26",
|
||||
"name": "Jobhuntly - Job Board & Portal UI Kit",
|
||||
"fig_stem": "Jobhuntly - Job Board & Portal UI Kit",
|
||||
"matched": "Jobhuntly - Job Board & Portal UI Kit",
|
||||
"key": "oyHlh6eh7TjxYhCUbFTvBS",
|
||||
"score": 1.0
|
||||
},
|
||||
{
|
||||
"W": "W28",
|
||||
"name": "Finity - Landing Page Template",
|
||||
"fig_stem": "Landing UI Kit",
|
||||
"matched": "Landing UI Kit",
|
||||
"key": "5R9tevOwnk4iJzU3jEjg0q",
|
||||
"score": 1.0
|
||||
},
|
||||
{
|
||||
"W": "W29",
|
||||
"name": "Loomi",
|
||||
"fig_stem": "LOOMI",
|
||||
"matched": "LOOMI",
|
||||
"key": "dvPWtG7KaEWr895iCI0Ioy",
|
||||
"score": 1.0
|
||||
},
|
||||
{
|
||||
"W": "W3",
|
||||
"name": "UXFlow Web Kit Design",
|
||||
"fig_stem": "UXFlow Web Kit",
|
||||
"matched": "UXFlow Web Kit",
|
||||
"key": "5CZLD5lcxxRQNE1QWjqbmP",
|
||||
"score": 1.0
|
||||
},
|
||||
{
|
||||
"W": "W31",
|
||||
"name": "Sydney Oasis Hotel - Websites Template",
|
||||
"fig_stem": "Sydney Oasis Hotel Websites",
|
||||
"matched": "Sydney Oasis Hotel Websites",
|
||||
"key": "khWI1rrgwj5702NilEcha2",
|
||||
"score": 1.0
|
||||
},
|
||||
{
|
||||
"W": "W32",
|
||||
"name": "DevAgency - Web Ui Kits",
|
||||
"fig_stem": "DevAgency - Web Ui Kits",
|
||||
"matched": "DevAgency - Web Ui Kits",
|
||||
"key": "qFC0pVEHe9Toc2aJ2MgO32",
|
||||
"score": 1.0
|
||||
},
|
||||
{
|
||||
"W": "W33",
|
||||
"name": "Insight - Dashboard UI Kit",
|
||||
"fig_stem": "Social Media Dashboard - UI Kit",
|
||||
"matched": "Social Media Dashboard - UI Kit",
|
||||
"key": "RKIYTIOCADVsASHNxZ5tr2",
|
||||
"score": 1.0
|
||||
},
|
||||
{
|
||||
"W": "W34",
|
||||
"name": "Intirior Architecture web ui kit",
|
||||
"fig_stem": "Intirior Architecture web ui kit",
|
||||
"matched": "Intirior Architecture web ui kit",
|
||||
"key": "BfBzvqu4VK53iSD5fbKNAo",
|
||||
"score": 1.0
|
||||
},
|
||||
{
|
||||
"W": "W35",
|
||||
"name": "JackCreative Personal Portfolio Website Design",
|
||||
"fig_stem": "JackCreative",
|
||||
"matched": "JackCreative",
|
||||
"key": "RNRNDiF6gFcTCi6wJncvgv",
|
||||
"score": 1.0
|
||||
},
|
||||
{
|
||||
"W": "W37",
|
||||
"name": "Daily UI Starter Pages - A ready-made UI Kits",
|
||||
"fig_stem": "04_DailyUI_Video_Landing_Website",
|
||||
"matched": "04_DailyUI_Video_Landing_Website",
|
||||
"key": "PJHgFt29Lyo3JwMKxa5Tsm",
|
||||
"score": 1.0
|
||||
},
|
||||
{
|
||||
"W": "W38",
|
||||
"name": "Figmaland - Startup Ui Kit",
|
||||
"fig_stem": "Figmaland- UI Kit (Startup)",
|
||||
"matched": "Figmaland- UI Kit (Startup)",
|
||||
"key": "fL1KqwIS3OmHCsUwhgJusG",
|
||||
"score": 1.0
|
||||
},
|
||||
{
|
||||
"W": "W40",
|
||||
"name": "IOTASK UI Kit",
|
||||
"fig_stem": "iotask",
|
||||
"matched": "iotask",
|
||||
"key": "uAcopfnDXakv7l5Jr6sMqG",
|
||||
"score": 1.0
|
||||
},
|
||||
{
|
||||
"W": "W41",
|
||||
"name": "Master Flow - Responsive Template for Figma",
|
||||
"fig_stem": "MasterFlow_UI_Kit",
|
||||
"matched": "MasterFlow_UI_Kit",
|
||||
"key": "4OTPxx5sgN8eFJ3joudoVH",
|
||||
"score": 1.0
|
||||
},
|
||||
{
|
||||
"W": "W43",
|
||||
"name": "Modularity Web Design System for Figma",
|
||||
"fig_stem": "Modularity",
|
||||
"matched": "Modularity",
|
||||
"key": "huDsMAoVThpYnGAU9j8WVB",
|
||||
"score": 1.0
|
||||
},
|
||||
{
|
||||
"W": "W45",
|
||||
"name": "Wiredunk - Landing Page Template for Multipurposes",
|
||||
"fig_stem": "Wiredunk_Wireframe",
|
||||
"matched": "Wiredunk_Wireframe",
|
||||
"key": "oZ7a2aFNZ21bg128ixnB3k",
|
||||
"score": 1.0
|
||||
},
|
||||
{
|
||||
"W": "W46",
|
||||
"name": "agency website template",
|
||||
"fig_stem": "3 template Agency",
|
||||
"matched": "3 template Agency",
|
||||
"key": "sHmAQQxmvRTIxOg8OgQQFG",
|
||||
"score": 1.0
|
||||
},
|
||||
{
|
||||
"W": "W47",
|
||||
"name": "Estatery - Real Estate SaaS Web UI Kit",
|
||||
"fig_stem": "Real Estate SaaS Kit & Dashboard (UI8)",
|
||||
"matched": "Real Estate SaaS Kit & Dashboard (UI8)",
|
||||
"key": "Suvu5K5DSKPngkMKRb01MK",
|
||||
"score": 1.0
|
||||
},
|
||||
{
|
||||
"W": "W48",
|
||||
"name": "LANDINGKUY - Landing Page Design Templates",
|
||||
"fig_stem": "LANDINGKUY - Landing Page Design",
|
||||
"matched": "LANDINGKUY - Landing Page Design",
|
||||
"key": "df0oLsZta06a79s5iepJdF",
|
||||
"score": 1.0
|
||||
},
|
||||
{
|
||||
"W": "W5",
|
||||
"name": "Wiloa 2.0 - Landing Page UI-Kit",
|
||||
"fig_stem": "WILOA - HOTEL",
|
||||
"matched": "WILOA - HOTEL",
|
||||
"key": "7C3QNOG6FhBRguT3nQ3dzf",
|
||||
"score": 1.0
|
||||
},
|
||||
{
|
||||
"W": "W51",
|
||||
"name": "Quickr Design System",
|
||||
"fig_stem": "Quicker Design system",
|
||||
"matched": "Quicker Design system",
|
||||
"key": "KtGIx2vrYNtApMiPYCTiiB",
|
||||
"score": 1.0
|
||||
},
|
||||
{
|
||||
"W": "W52",
|
||||
"name": "RealStatic - Real State Website Design",
|
||||
"fig_stem": "Ui8_RealStatic_Static Mania",
|
||||
"matched": "Ui8_RealStatic_Static Mania",
|
||||
"key": "U4YDJ7B0Sefem9r8LiEs8b",
|
||||
"score": 1.0
|
||||
},
|
||||
{
|
||||
"W": "W53",
|
||||
"name": "Skill Shoot - Online course website and responsive uikit",
|
||||
"fig_stem": "Aset - skill Shoot",
|
||||
"matched": "Aset - skill Shoot",
|
||||
"key": "pMjAEiLKAFE57eaI4f4vva",
|
||||
"score": 1.0
|
||||
},
|
||||
{
|
||||
"W": "W54",
|
||||
"name": "GetPay - 6 Unique Finance Landing Pages",
|
||||
"fig_stem": "GETPAY - FINANCE TEMPLATES DESIGN",
|
||||
"matched": "GETPAY - FINANCE TEMPLATES DESIGN",
|
||||
"key": "vcb4bLF79NTpybgXvAtkNM",
|
||||
"score": 1.0
|
||||
},
|
||||
{
|
||||
"W": "W55",
|
||||
"name": "Fecca - Landing Page UI Kit (Figma)",
|
||||
"fig_stem": "Fecca",
|
||||
"matched": "Fecca",
|
||||
"key": "IpdxPbiWuGZQpr3aNXsVpC",
|
||||
"score": 1.0
|
||||
},
|
||||
{
|
||||
"W": "W56",
|
||||
"name": "Orabel Web UI Kit",
|
||||
"fig_stem": "01 Home - Orabel Web UI Kit",
|
||||
"matched": "01 Home - Orabel Web UI Kit",
|
||||
"key": "IoQBzPjHw51WjFKL52kJKN",
|
||||
"score": 1.0
|
||||
},
|
||||
{
|
||||
"W": "W6",
|
||||
"name": "AKASHA Pages Building Kit",
|
||||
"fig_stem": "AKASHA-PAGEBUILDING-KIT",
|
||||
"matched": "AKASHA-PAGEBUILDING-KIT",
|
||||
"key": "LA2daTQaigGkMUUNgPr6fp",
|
||||
"score": 1.0
|
||||
},
|
||||
{
|
||||
"W": "W7",
|
||||
"name": "Bright Kit Web Layouts",
|
||||
"fig_stem": "Bright Kit",
|
||||
"matched": "Bright Kit",
|
||||
"key": "CLZepne3voPYx198q3wUHO",
|
||||
"score": 1.0
|
||||
}
|
||||
]
|
||||
1794
manifest.json
Normal file
39
nginx.conf
Normal file
@@ -0,0 +1,39 @@
|
||||
worker_processes auto;
|
||||
events { worker_connections 1024; }
|
||||
|
||||
http {
|
||||
include /etc/nginx/mime.types;
|
||||
default_type application/octet-stream;
|
||||
sendfile on;
|
||||
keepalive_timeout 65;
|
||||
gzip on;
|
||||
gzip_types text/plain text/css application/javascript application/json image/svg+xml;
|
||||
gzip_min_length 1024;
|
||||
|
||||
server {
|
||||
listen 80 default_server;
|
||||
server_name _;
|
||||
|
||||
root /usr/share/nginx/html;
|
||||
index index.html;
|
||||
|
||||
auth_basic "Figma 模板库";
|
||||
auth_basic_user_file /etc/nginx/.htpasswd;
|
||||
|
||||
# cache immutable assets
|
||||
location ~* \.(jpg|jpeg|png|gif|webp|svg|woff2?|ttf|eot)$ {
|
||||
expires 30d;
|
||||
add_header Cache-Control "public, immutable";
|
||||
}
|
||||
location ~* \.(css|js)$ {
|
||||
expires 7d;
|
||||
}
|
||||
location ~* \.json$ {
|
||||
add_header Cache-Control "no-cache";
|
||||
}
|
||||
|
||||
location / {
|
||||
try_files $uri $uri/ =404;
|
||||
}
|
||||
}
|
||||
}
|
||||
41
scripts/import-to-figma.sh
Executable file
@@ -0,0 +1,41 @@
|
||||
#!/usr/bin/env bash
|
||||
# Batch open .fig files in Figma desktop app (→ Drafts)
|
||||
# Mode: "all" (72 files) or "slim" (W37 only 1, rest all = 48 files)
|
||||
set -euo pipefail
|
||||
MODE="${1:-slim}"
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
cd "$ROOT"
|
||||
|
||||
python3 - "$MODE" <<'PY' > /tmp/fig-list.txt
|
||||
import json, sys, os
|
||||
mode = sys.argv[1]
|
||||
m = json.load(open("manifest.json"))
|
||||
already_opened = {"W1"} # already done in earlier test
|
||||
files = []
|
||||
for t in m["templates"]:
|
||||
if not t["fig"]: continue
|
||||
figs = list(t["fig"])
|
||||
if mode == "slim" and t["id"] == "W37":
|
||||
figs = figs[:1]
|
||||
for f in figs:
|
||||
path = f'extracted/{t["id"]}/{f}'
|
||||
if t["id"] in already_opened and figs.index(f) == 0 and t["id"] == "W1":
|
||||
continue # skip W1 first fig (already opened)
|
||||
files.append(path)
|
||||
print('\n'.join(files))
|
||||
PY
|
||||
|
||||
count=$(wc -l < /tmp/fig-list.txt | tr -d ' ')
|
||||
echo "Mode=$MODE Files to open: $count"
|
||||
echo ""
|
||||
|
||||
i=0
|
||||
while IFS= read -r f; do
|
||||
i=$((i+1))
|
||||
printf "[%2d/%d] %s\n" "$i" "$count" "$f"
|
||||
open -a Figma "$ROOT/$f"
|
||||
sleep 1.5
|
||||
done < /tmp/fig-list.txt
|
||||
|
||||
echo ""
|
||||
echo "Done. All $count .fig files sent to Figma."
|
||||
105
scripts/match-and-update.py
Executable file
@@ -0,0 +1,105 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Match Figma cloud Drafts files (from figma-files.json) back to W{N} entries in
|
||||
manifest.json by fuzzy name match, then update web/data.json with figma_key+url.
|
||||
"""
|
||||
import json, re, sys, difflib
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parent.parent
|
||||
|
||||
def normalize(s):
|
||||
s = s.lower()
|
||||
s = re.sub(r'[^a-z0-9]+', ' ', s).strip()
|
||||
return s
|
||||
|
||||
def best_match(target, candidates):
|
||||
"""Return (score, candidate) best match from candidates list of dicts with .name"""
|
||||
nt = normalize(target)
|
||||
best = (0, None)
|
||||
for c in candidates:
|
||||
nc = normalize(c['name'])
|
||||
# exact / prefix / contains / sequence
|
||||
if nt == nc: s = 1.0
|
||||
elif nt in nc or nc in nt: s = 0.85
|
||||
else: s = difflib.SequenceMatcher(None, nt, nc).ratio()
|
||||
if s > best[0]:
|
||||
best = (s, c)
|
||||
return best
|
||||
|
||||
def main():
|
||||
manifest = json.loads((ROOT/'manifest.json').read_text())
|
||||
figma_files_path = ROOT/'figma-files.json'
|
||||
if not figma_files_path.exists():
|
||||
print(f"missing {figma_files_path}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
figma_files = json.loads(figma_files_path.read_text()) # list of {key, name, ...}
|
||||
|
||||
# for each W template that has fig, find matching figma file
|
||||
matches = []
|
||||
used_keys = set()
|
||||
for t in manifest['templates']:
|
||||
if not t['fig']: continue
|
||||
# use the .fig file's stem as match target (closer than archive name)
|
||||
targets = [Path(f).stem for f in t['fig']]
|
||||
# add archive stem as backup
|
||||
if t.get('archive'):
|
||||
targets.append(Path(t['archive']).stem)
|
||||
targets.append(t['name'])
|
||||
# try each target
|
||||
best_overall = (0, None)
|
||||
for tgt in targets:
|
||||
score, cand = best_match(tgt, [f for f in figma_files if f['key'] not in used_keys])
|
||||
if score > best_overall[0]:
|
||||
best_overall = (score, cand)
|
||||
if score > 0.95:
|
||||
break
|
||||
score, cand = best_overall
|
||||
if cand and score >= 0.6:
|
||||
used_keys.add(cand['key'])
|
||||
matches.append({
|
||||
'W': t['id'], 'name': t['name'], 'fig_stem': Path(t['fig'][0]).stem if t['fig'] else None,
|
||||
'matched': cand['name'], 'key': cand['key'], 'score': round(score, 3)
|
||||
})
|
||||
else:
|
||||
matches.append({
|
||||
'W': t['id'], 'name': t['name'], 'fig_stem': Path(t['fig'][0]).stem if t['fig'] else None,
|
||||
'matched': None, 'best_score': round(best_overall[0], 3) if cand else 0,
|
||||
'best_candidate': cand['name'] if cand else None
|
||||
})
|
||||
|
||||
# Update web/data.json
|
||||
data_path = ROOT/'web'/'data.json'
|
||||
data = json.loads(data_path.read_text())
|
||||
by_W = {m['W']: m for m in matches if m.get('key')}
|
||||
for t in data['templates']:
|
||||
m = by_W.get(t['id'])
|
||||
if m:
|
||||
t['figma_key'] = m['key']
|
||||
t['figma_url'] = f"https://www.figma.com/file/{m['key']}"
|
||||
else:
|
||||
t['figma_key'] = None
|
||||
t['figma_url'] = None
|
||||
|
||||
# update banner with imported count
|
||||
imported = sum(1 for t in data['templates'] if t['figma_key'])
|
||||
data['imported_summary'] = (
|
||||
f"✅ <b>{imported} 个 Figma 原生文件</b>已云端就位在你的 "
|
||||
f"<a href='https://www.figma.com/files/team/1304178887825899477/drafts' target='_blank'>Figma Drafts</a>。"
|
||||
f"点卡片打开 modal 查看 iframe 实时投射 + 跳 Figma 编辑。"
|
||||
)
|
||||
data_path.write_text(json.dumps(data, ensure_ascii=False, indent=2))
|
||||
|
||||
# write match report
|
||||
report = ROOT/'figma-match-report.json'
|
||||
report.write_text(json.dumps(matches, ensure_ascii=False, indent=2))
|
||||
print(f"Matched {imported}/{sum(1 for t in manifest['templates'] if t['fig'])} fig templates")
|
||||
print(f"Report: {report.relative_to(ROOT)}")
|
||||
unmatched = [m for m in matches if not m.get('key')]
|
||||
if unmatched:
|
||||
print(f"\n⚠️ {len(unmatched)} unmatched:")
|
||||
for m in unmatched:
|
||||
print(f" {m['W']:4s} {m['name'][:50]:50s} best='{m.get('best_candidate','')[:40]}' score={m.get('best_score',0)}")
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
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 |