From cc32a3d3622f15030b1cd40654c4547f73f8d23c Mon Sep 17 00:00:00 2001 From: root Date: Fri, 16 Oct 2026 22:35:28 +0800 Subject: [PATCH] chore: tidy repo - DESIGN.md & scoring rules into docs/, icon generators into tools/, drop one-off junk, refresh app README --- .gitignore | 44 +++++-------- README.md | 5 +- app/README.md | 62 ++++--------------- DESIGN.md => docs/DESIGN.md | 0 .../scoring-rules-cqccst-2026-2027.json | 0 tools/gen_icons.py | 41 ++++++++++++ tools/gen_pwa_icons.py | 34 ++++++++++ 7 files changed, 106 insertions(+), 80 deletions(-) rename DESIGN.md => docs/DESIGN.md (100%) rename rules/重庆城市科技学院-计网学院-2026-2027.json => docs/examples/scoring-rules-cqccst-2026-2027.json (100%) create mode 100644 tools/gen_icons.py create mode 100644 tools/gen_pwa_icons.py diff --git a/.gitignore b/.gitignore index 8224cda..e77782b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,45 +1,35 @@ # ---- 依赖与构建产物 ---- node_modules/ dist/ +src-tauri/target/ +src-tauri/gen/ # ---- 运行时数据(本地库 / 附件 / 服务端数据)---- .data/ data/ -# ---- 测试运行时数据 ---- -.regr/ -.factory*/ -.wire-*/ -.builtin-test/ -build-log.txt -.builtin-test/ -*.tmp.log -.factory-test/ +# ---- 测试与调试临时目录 ---- +.tmp-test/ .seed-dbg/ -.seed-*/ +.dbg-*/ +.regr/ .regression/ .regression-s/ -.dbg-x/ -.grade-test/ .regression-test/ +.builtin-test/ +.builtin-test/ +.factory*/ +.factory-test/ +.seed-*/ +.wire-*/ +.grade-test/ sync-server/.data-*/ + +# ---- 工具与本地二进制 ---- tools/pnpm.exe tools/pnpm.zip -%SystemDrive%/ - -# ---- Rust ---- -src-tauri/target/ -src-tauri/gen/ - -# ---- 自包含 pnpm:保留 pnpm.cjs,忽略解包目录 ---- tools/pnpm-tgz/ -# ---- 临时脚本与产物 ---- -gen_*.py -parse_docx.py -check_yaml.py -docx_content.txt +# ---- 日志 ---- *.log - -# 集成测试临时目录 -.tmp-test/ +build-log.txt diff --git a/README.md b/README.md index e9c16cf..8bb8b94 100644 --- a/README.md +++ b/README.md @@ -123,7 +123,8 @@ CI:push 自动跑 check + 集成测试(见 `.gitea/workflows/ci.yml`,需 ├─ scripts/ # check / test-integration / smoke ├─ .gitea/workflows/ # CI ├─ .githooks/ # pre-push 钩子 -└─ docs/api.md # HTTP API 参考 +├─ docs/ # api.md(HTTP API)· DESIGN.md(设计底稿)· examples/ +└─ .gitea/ .githooks/ # CI workflow 与 pre-push 钩子 ``` ## 环境变量(sync-server) @@ -140,7 +141,7 @@ CI:push 自动跑 check + 集成测试(见 `.gitea/workflows/ci.yml`,需 ## 文档 - [HTTP API 参考](docs/api.md) -- [设计方案(v0.5 底稿)](DESIGN.md) —— 项目的原始设计思想;现产品已演进为云优先多用户架构 +- [设计方案(v0.5 底稿)](docs/DESIGN.md) —— 项目的原始设计思想;现产品已演进为云优先多用户架构 ## Roadmap diff --git a/app/README.md b/app/README.md index 916ac5a..92f9f7b 100644 --- a/app/README.md +++ b/app/README.md @@ -1,56 +1,16 @@ -# 足迹 Footprints +# 足迹 Footprints — 应用目录 -个人留痕系统 · 桌面应用(Tauri 2 + React 19 + SQLite)+ 移动端 PWA。 -设计文档见仓库根目录 `DESIGN.md`;同步服务端见 `sync-server/`。 +React 19 前端 + Tauri 2 桌面壳 + 共用 Node 命令层。 +**云优先架构**:业务数据全部存服务器(`../sync-server`),浏览器 / PWA / 桌面三端共享同一命令契约。 -## 目录结构 +## 结构 - app/ - src/ React 前端(时间线 / 记录 / 统计 / 类型 / 字典) - src-tauri/ Tauri 2 桌面壳 + Rust 后端(rusqlite,26 个 IPC 命令 + 同步地基) - public/ PWA 清单 / Service Worker / 图标 - server/dev-bridge.mjs 开发桥接:HTTP 暴露与 Tauri IPC 相同的命令契约 - server/db.mjs 桥接与种子脚本共用的数据层(node:sqlite) - server/seed.mjs 演示数据(仅当库为空时写入) - schema.sql 数据库 schema(Rust 与 Node 共用同一份,v2 含同步元数据) - tools/pnpm.cjs 自包含 pnpm(本机无全局 npm/pnpm 时使用) - sync-server/ 自建同步服务(Docker 部署,协议已冻结) + src/ React 前端(时间线 / 记录 / 成绩 / 统计 / 类型 / 字典 / 管理) + public/ PWA:sw.js(v3,接口类请求绝不缓存)/ manifest / 图标 + src-tauri/ Tauri 2 桌面壳(Windows NSIS 安装包构建) + server/ db.mjs 命令层(37 个命令,Web 与桌面共用)+ seed.mjs(本地演示数据) + schema.sql 数据库 schema(Rust 与 Node 共用同一份,v2 含同步元数据) -## 浏览器预览(无需 Rust) +图标再生成:`python ../tools/gen_icons.py`(桌面)与 `python ../tools/gen_pwa_icons.py`(PWA)。 - node ../tools/pnpm.cjs bridge # 终端 1:数据桥接(含 dist 静态托管) - node ../tools/pnpm.cjs dev # 终端 2:前端热更(http://localhost:5173) - -或构建后直接访问桥接托管页: - - node ../tools/pnpm.cjs build # tsc 类型检查 + vite 打包(含 PWA 资源) - node server/seed.mjs # 可选:写入演示数据 - -## 桌面应用打包(Windows) - -前置:Rust stable-msvc(scripts/setup-rust.ps1 一键安装,MSVC Build Tools 需已具备)。 - - node ../tools/pnpm.cjs build # 先产出 dist - node ../tools/pnpm.cjs tauri build - -产物:`src-tauri/target/release/bundle/nsis/*-setup.exe`(NSIS 安装包)。 -开发模式:`node ../tools/pnpm.cjs tauri dev`。 - -## 移动端(PWA) - -前端已做响应式与 PWA 改造(manifest + Service Worker + 图标): - -- 桌面壳内运行时自动跳过 SW 注册 -- 浏览器/手机访问托管页时自动注册 SW,可"添加到主屏幕"离线壳启动 -- 同步服务部署后,手机 PWA 直连同一账号数据 - -原生 Android 壳(Tauri mobile)为可选后续项,需要 Android SDK/NDK;iOS 构建需 macOS。 - -## 命令契约 - -前端统一经 `src/lib/api.ts` 调用:浏览器模式走 `POST /api/`(开发桥接), -桌面模式走 Tauri `invoke(command, { payload })`。两端实现同一组命令, -含多端同步地基:每行携带 uuid/hlc/origin,删除为墓碑,写入进 sync_oplog。 - -数据与附件位置:桌面版在系统应用数据目录(`%APPDATA%/com.footprints.app`), -开发预览在 `app/.data/`。备份 = 复制该目录。 +文档:[`../docs/DESIGN.md`](../docs/DESIGN.md)(设计底稿)· [`../docs/api.md`](../docs/api.md)(HTTP API)· [`../sync-server/`](../sync-server/)(服务器)。 diff --git a/DESIGN.md b/docs/DESIGN.md similarity index 100% rename from DESIGN.md rename to docs/DESIGN.md diff --git a/rules/重庆城市科技学院-计网学院-2026-2027.json b/docs/examples/scoring-rules-cqccst-2026-2027.json similarity index 100% rename from rules/重庆城市科技学院-计网学院-2026-2027.json rename to docs/examples/scoring-rules-cqccst-2026-2027.json diff --git a/tools/gen_icons.py b/tools/gen_icons.py new file mode 100644 index 0000000..47f8a9a --- /dev/null +++ b/tools/gen_icons.py @@ -0,0 +1,41 @@ + +# -*- coding: utf-8 -*- +# 生成 Footprints 应用图标:品牌蓝圆角方块 + 白色抽象足迹 +from PIL import Image, ImageDraw +import os + +OUT = os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', 'app', 'src-tauri', 'icons') +os.makedirs(OUT, exist_ok=True) + +BRAND = (48, 86, 211) +BRAND_DARK = (35, 62, 160) + +def make(size): + ss = size * 4 # 超采样抗锯齿 + img = Image.new('RGBA', (ss, ss), (0, 0, 0, 0)) + d = ImageDraw.Draw(img) + radius = ss // 5 + # 背景圆角方块(上浅下深的纵向渐变) + grad = Image.new('RGBA', (ss, ss), (0, 0, 0, 0)) + gd = ImageDraw.Draw(grad) + for y in range(ss): + t = y / ss + c = tuple(int(BRAND[i] + (BRAND_DARK[i] - BRAND[i]) * t) for i in range(3)) + (255,) + gd.line([(0, y), (ss, y)], fill=c) + mask = Image.new('L', (ss, ss), 0) + md = ImageDraw.Draw(mask) + md.rounded_rectangle([0, 0, ss - 1, ss - 1], radius=radius, fill=255) + img.paste(grad, (0, 0), mask) + # 抽象足迹:前掌大椭圆 + 后跟小椭圆 + white = (255, 255, 255, 240) + d.ellipse([ss*0.24, ss*0.16, ss*0.60, ss*0.62], fill=white) # 前掌 + d.ellipse([ss*0.56, ss*0.58, ss*0.86, ss*0.84], fill=(255, 255, 255, 200)) # 后跟 + return img.resize((size, size), Image.LANCZOS) + +for name, size in [('32x32.png', 32), ('128x128.png', 128), ('128x128@2x.png', 256), ('icon.png', 512)]: + make(size).save(os.path.join(OUT, name)) + +ico_sizes = [16, 24, 32, 48, 64, 128, 256] +imgs = [make(s) for s in ico_sizes] +imgs[-1].save(os.path.join(OUT, 'icon.ico'), format='ICO', sizes=[(s, s) for s in ico_sizes]) +print('icons generated:', os.listdir(OUT)) diff --git a/tools/gen_pwa_icons.py b/tools/gen_pwa_icons.py new file mode 100644 index 0000000..72c4cca --- /dev/null +++ b/tools/gen_pwa_icons.py @@ -0,0 +1,34 @@ + +# -*- coding: utf-8 -*- +from PIL import Image, ImageDraw +import os + +OUT = os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', 'app', 'public', 'icons') +os.makedirs(OUT, exist_ok=True) +BRAND = (48, 86, 211) +BRAND_DARK = (35, 62, 160) + +def make(size, safe=1.0): + ss = size * 4 + img = Image.new('RGBA', (ss, ss), (0, 0, 0, 0)) + grad = Image.new('RGBA', (ss, ss), (0, 0, 0, 0)) + gd = ImageDraw.Draw(grad) + for y in range(ss): + t = y / ss + c = tuple(int(BRAND[i] + (BRAND_DARK[i] - BRAND[i]) * t) for i in range(3)) + (255,) + gd.line([(0, y), (ss, y)], fill=c) + mask = Image.new('L', (ss, ss), 0) + ImageDraw.Draw(mask).rounded_rectangle([0, 0, ss - 1, ss - 1], radius=ss // 5, fill=255) + img.paste(grad, (0, 0), mask) + d = ImageDraw.Draw(img) + white = (255, 255, 255, 240) + # safe 缩放:maskable 时内容收缩到中心 70% 区域 + cx, cy, k = ss / 2, ss / 2, safe + d.ellipse([cx - 0.18*ss*k, cy - 0.34*ss*k, cx + 0.18*ss*k, cy + 0.10*ss*k], fill=white) + d.ellipse([cx + 0.03*ss*k, cy + 0.08*ss*k, cx + 0.33*ss*k, cy + 0.34*ss*k], fill=(255, 255, 255, 200)) + return img.resize((size, size), Image.LANCZOS) + +make(192).save(os.path.join(OUT, 'pwa-192.png')) +make(512).save(os.path.join(OUT, 'pwa-512.png')) +make(512, safe=0.7).save(os.path.join(OUT, 'maskable-512.png')) +print('pwa icons:', os.listdir(OUT))