Chapter 01 · Frontend
前端架构
原则:性能、稳定性、用户体验优先;不同平台允许不同技术栈,但视觉与交互必须 100% 还原 design/ 设计系统——以 ui_kits/ React 原型为像素验收标准,以 colors_and_type.css 为令牌唯一真相源。
§1分端技术栈选型
| 端 | 技术栈 | 选型理由(性能 / 稳定 / UX) | 像素基准 |
|---|---|---|---|
| 移动 App iOS / Android |
Flutter + sing-box libbox(gomobile 桥接)iOS NetworkExtension / Android VpnService |
需要系统级隧道能力,Web 栈不可行;Flutter 自绘渲染保证两端像素一致,design/flutter/ 起步包(theme + widgets + main)拿来即跑。 |
ui_kits/mobile/ |
| iPad 客户端 | Flutter——与移动 App 同一工程,宽度 ≥900 自适应切换为侧栏分栏布局 | 不是第五套代码:复用移动端全部原子组件与内核层,仅布局开关;横屏 1180×820 基准,左侧栏导航(触控尺寸,行高 ≥48px),连接页双栏(左大连接键 / 右信息列),节点双列网格。 | ui_kits/tablet/ |
| 桌面客户端 Windows / macOS |
Flutter desktop + sing-box 子进程(TUN 模式) | 与移动端共享 ~90% UI 代码(连接键、节点列表、统计、账户全复用),920×600 固定窗 + 侧栏布局照搬原型;避免 Electron 的内存与启动开销。 | ui_kits/desktop/ |
| 官网 | Astro(SSG)→ Cloudflare Pages | 纯静态 = 极致加载性能 + 整站可秒级复制到任意备用域名(灾备关键,见 05 章);Astro 直接吃 JSX,ui_kits/website 组件近乎逐字迁移。 |
ui_kits/website/ |
| Web 用户中心 | Next.js(React,静态导出 + 客户端取数) | ui_kits/usercenter 本身是 React——组件直接复用是保真上限;静态导出后同样可多镜像部署,动态数据全走 API。 |
ui_kits/usercenter/ |
为什么不强行统一一个栈
客户端离不开系统级隧道与开机自启,必须是原生壳;Web 端的两个原型本身就是 React,重写成 Flutter Web 反而损失保真与首屏性能。五个产品面、四套实现(iPad 归入移动端工程的自适应断点),通过同一套设计令牌(CSS 变量 ↔
pangolin_theme.dart 一一对应)保证视觉同源——栈不同,像素相同。§2客户端分层架构(移动 + 桌面共享)
UI 层(共享 ~90%)flutter/widgets/:连接键三态 · 智能选择推荐卡 · 免费额度卡 · 节点列表 · 统计 · 账户;明暗双主题 + 中英单显;宽度 ≥900 自适应切侧栏分栏布局(iPad / 桌面同构)
状态层Riverpod:连接状态机(off / connecting / on)· 会话 · 节点目录(带 version)· 用量额度
服务层API client(域名池 + 重试退避 + 响应签名校验)· 节点目录缓存 · 订阅凭证存储(Keychain / Keystore)
内核层sing-box libbox(移动:gomobile AAR/XCFramework;桌面:子进程 + TUN)· URLTest 智能选线 · Kill-switch
平台壳iOS NetworkExtension · Android VpnService(前台服务)· macOS/Win TUN 设备 + 开机自启
UI 与内核之间仅通过状态层通信;内核崩溃不拖垮 UI,UI 重启不掉隧道
- 连接状态机是核心:UI 三态(off / connecting / on)严格对应内核事件,禁止 UI 侧"乐观显示已连接"。
- 智能选择(默认选中):sing-box URLTest 组在本地探测延迟自动选优,节点列表置顶推荐卡与原型一致。
- 免费版额度:本地倒计时仅作展示,权威额度以 API 为准(见 02 章
/v1/ads/unlock与 connect 校验)。 - 默认安全:Kill-switch 默认开启;DNS 走 DoH 防泄露;阻断 WebRTC 直连泄露(桌面端注入策略)。
§3100% 还原策略
3.1 令牌同源
| 端 | 令牌载体 | 同步方式 |
|---|---|---|
| 官网 / 用户中心 | colors_and_type.css 直接链入 | 原样引用,不复制不改写 |
| 移动 / 桌面 | flutter/pangolin_theme.dart | 与 CSS 一一对应的 Dart 镜像;改令牌必须两处同改(design/CLAUDE.md §6) |
3.2 像素基准与组件对照
每个界面动手前先打开对应 React 原型比对;以下关键组件逐一对照验收:
- 核心连接键:off 暖灰底 + 虚线轨道环 / connecting 旋转弧 / on 绿底满环 + 圆内计时 + 光晕(过渡只用
box-shadow/background-color,禁transition: all) - 智能选择推荐卡:常驻 accent-subtle 底 + clay 渐变 zap 图标 + 「推荐」胶囊,默认选中
- 免费额度卡:剩余分钟 + 进度条(≤3 分钟变 warning 色)+「看广告开始使用」→ 解锁后变绿
- 国家码块(2 字母,无 emoji 国旗)、状态胶囊(色点 + 文字,无 emoji)、Lucide 细线图标
- Tab 滑动切换:>60px 且横向位移明显大于纵向,200ms 方向感知滑入;子页不响应
- iPad 分栏布局(对照
ui_kits/tablet/):左侧栏导航行高 ≥48px 触控尺寸;连接页双栏(左大连接键 / 右额度卡→当前节点→实时速率);节点页置顶智能推荐卡 + 双列网格(同桌面)
3.3 提交前验收清单(每个界面,引用 design/CLAUDE.md §9)
- 颜色全部来自语义 token,无硬编码十六进制
- 明 / 暗两主题、中 / 英两语言(单显不并排)共四态验证
- 文案无铁律 13 红线词;套餐数字与 design/CLAUDE.md §7 一致
- App / 官网内无任何支付表单,购买只引导到外部渠道
§4客户端断网弹性
设计目标:API 全灭、域名全被污染时,客户端仍能连上节点。详见 06 章断网应对矩阵,此处为客户端侧实现。
| 机制 | 实现 | 覆盖的故障 |
|---|---|---|
| 节点目录缓存 | 每次成功拉取 /v1/nodes 后落盘(带 version 与时间戳);启动时先用缓存渲染并尝试连接,后台再刷新 | API 暂时不可达 |
| API 端点池 | 客户端内置:主域名 + 备用域名 N 个 + IP 直连兜底;按序故障转移,成功的端点置顶记忆 | 单个域名被墙 / 被污染 |
| DoH 解析 | 域名解析优先走 DoH(多个提供方),绕开本地污染 | DNS 污染 |
| 签名端点更新 | 定期从多镜像静态文件(Cloudflare Pages / GitHub 等)拉取 Ed25519 签名的端点列表,验签后合并进端点池 | 内置端点全部失效 |
| 紧急逃生配置 | 安装包内置 1–2 个应急节点参数(低速、仅够拉新目录),所有在线途径失效时启用 | 极端封锁 |
| 应急公告 | 客户端内公告位从签名静态 JSON 读取(多镜像),可引导用户更新或切换渠道 | 需要人工广播时 |
注意所有兜底参数(IP、应急节点、签名公钥)属于敏感资产:随版本轮换、按渠道分包(不同分发渠道内置不同 IP 子集),泄露一个渠道不烧全部。