Skip to content

Latest commit

 

History

History
86 lines (65 loc) · 9.92 KB

frontend_development.md

File metadata and controls

86 lines (65 loc) · 9.92 KB

前端开发规范与拓展指南

定位现有实现请查 README.md 快速定位表,或 reference/frontend/(18 篇逐文件参考,含行号与 props 清单)。

1. 目录结构与文件定位

目录 内容 对应 reference 文档
src/pages/ 10 个页面(dashboard、account、char-select、characters、gacha-records、attendance、medals、todo、settings、developer) pages_*.md
src/layouts/ dashboard / default 布局 pages_core.md
src/components/ 弹窗、面板、侧栏、图表等大组件(约 29 个 tsx,含 network-settings.tsx) components_dialogs.md、components_settings_panels.md、components_sidebar_charts.md、components_game_action_panel.md
src/components/ui/ 37 个通用 UI 组件 + glass/ ui_components.md
src/components/cards/ 卡片系统:registry/、base/、card-container.tsx、_template/ 与 11 张卡片(含 system-resource、todo) cards_core.md 及各 cards_*.md
src/utils/ 19 个 IPC 封装与工具(account/config/proxy/cardConfig/dashboard/role/launcher/update/image/logger…) utils_services_a.md、utils_services_b.md
src/types/ TypeScript 类型 types_locales_styles.md
src/locales/{zh,en}/ 国际化资源 同上
src/styles/globals.css 全局样式、主题变量、玻璃拟态类 同上
src/stores/、src/hooks/、src/lib/、src/config/ 轻量状态、hooks、工具函数 entry_and_app.md

2. 开发规范

  • 状态管理: 优先使用 React Hooks (useState, useEffect);跨窗口/轻量全局状态用 src/stores/(inAppBrowser.ts、launcherMode.ts)。
  • 样式编写: 使用 Tailwind CSS 原子类 + src/components/ui/glass 的玻璃组件;深色模式通过 globals.css 的 .dark 类切换。
  • 国际化: 用户可见文本必须通过 useTranslation 从 locales/ 获取;卡片文案放各自的 locales/{zh,en}.json(合并进 card 命名空间)。
  • 日志记录: 使用 src/utils/logger.ts 的 logger / logInfo / logError / logDebug,禁止 console.log;日志级别由开发者模式下的 log_level 控制(关闭开发者模式时 DEBUG 自动屏蔽)。
  • IPC 调用: 页面/组件不直接散写 invoke,统一走 src/utils/*Service.ts 封装;需要新增命令时先在封装层加函数(含类型与错误兜底)。
  • 错误处理: 封装层统一 try/catch + logError,并明确失败时的返回值约定(空数组/false/null)。

3. 拓展位置与方法

3.1 新增仪表板卡片

卡片系统是约定式自动发现的,无需改核心代码(详见 card_development.md):

  1. 新建 src/components/cards/<card-name>/
  2. 添加 <card-name>.meta.json(id、name{zh,en}、description{zh,en}、icon、defaultSize、version、可选 allowMultiple)
  3. 添加 index.tsx 默认导出组件,props 用 BaseCardProps(roleId/cardId/settings/isEditMode)
  4. 添加 locales/{zh,en}.json,组件内用 t("card:key")
  5. 若有配置项,在 src/types/card-settings.ts 增加 settings 类型
  6. 重启开发服务器,卡片自动进入「添加卡片」对话框(getAvailableCards())

渲染由 src/pages/dashboard.tsx:422 的 CardContainer 统一负责(拖拽、编辑模式、右键菜单), 布局持久化走 src/utils/dashboardConfig.ts(配置键 dashboard_tabs),配置读写走 src/utils/cardConfigService.ts。

3.2 新增页面路由

  1. 在 src/pages/ 创建页面组件
  2. 在 src/App.tsx 用 React.lazy(() => import("@/pages/...")) 声明页面组件,再用 react-router-dom 的 <Route> 注册(页面加载态由 layouts/dashboard.tsx 的 <main> 内 Suspense 兜底,勿在路由内重复包裹)
  3. 如路由需可恢复,把路径加入 App.tsx 的 ROUTES 白名单
  4. 导航入口:src/components/dashboard-sidebar.tsx 或 navbar.tsx(入口与侧栏结构见 components_sidebar_charts.md)

打包:页面按路由懒加载;第三方库由 vite.config.ts 的 build.rolldownOptions.output.codeSplitting.groups 拆成 vendor-react/vendor-echarts/vendor-zrender/vendor-i18n/vendor-ui/vendor-dnd/vendor-tauri 等 chunk,避免单个 chunk 超过 Vite 的 500 kB 告警阈值。引入 echarts 时务必用 echarts/core + 按需 echarts.use(...),不要全量 import * as echarts from "echarts"。

⚠️ 改 codeSplitting.groups 必须保持 chunk 之间无回边。判定包归属时用「包根锚定」(/[\\/]node_modules[\\/](?:\.pnpm[\\/][^\\/]+[\\/])?react-dom[\\/]/),不要用宽泛的子串匹配 —— 后者会命中 @radix-ui/react-dom 等包,把 Radix 塞进 vendor-react,Rolldown 便会报告 Circular chunk: vendor-react -> vendor-ui -> vendor-react 并重排模块顶层声明,React 工厂变量在其模块求值前被写入,浏览器抛 Cannot set properties of undefined (setting 'Activity'),前端在 main.tsx 之前就崩溃、Splash 永远停在「正在启动…」。该故障的产物哈希与平台相关,只在 Linux 发行版稳定复现,Windows 上生成同一份配置的产物却正常,所以务必以 pnpm build 输出中是否出现 Circular chunk: 为准,出现即视为构建失败。

3.3 新增后端命令调用

  1. 在 src/utils/ 对应服务文件(或新建 xxxService.ts)中封装 invoke
  2. 定义请求参数与返回值的 TypeScript 类型(放 src/types/ 或服务文件内)
  3. try/catch 捕获异常并用 logger 记录,明确失败返回值
  4. 同步在 reference/ipc_index.md 与 reference/frontend/utils_services_*.md 补一行

3.4 新增通用 UI 组件

  • 放 src/components/ui/(不要放 ui/glass/,该目录只放 barrel index.ts),遵循现有组件的 props 风格(受控 isOpen/onOpenChange、className 合并用 cn)
  • 如需从 @/components/ui/glass 统一引入,在 ui/glass/index.ts 里追加一行再导出
  • 复用 glass/ 的视觉变量,支持深色模式
  • 玻璃模糊只能写在 ::before 层:globals.css 里 .glass-surface / .glass-surface-strong / .glass-control-surface / .glass-field / .account-glass-* 的底色与 backdrop-filter 都在 ::before(position:absolute; inset:0; z-index:-1)上,元素自身保持 background: transparent。原因是 Linux(WebKitGTK)在运行时改写 --glass-blur(设置页拖动「毛玻璃清晰度」)后,会把背景模糊层重新合成到元素内容之上,整张卡片连文字一起糊掉;模糊放进没有子内容的伪元素就不会波及内容。新增玻璃类请沿用同一模式,并把类名补进 globals.css 末尾 @layer components 的定位列表(放低优先级层,才不会盖掉 fixed/sticky/absolute/z-* 工具类)
  • 玻璃宿主与其祖先不得带 filter,也不得带 opacity < 1,入场动画不得保留终态:宿主/祖先一旦有 filter(含 brightness-* 这类工具类),或它的 opacity < 1(静态写死,或入场动画的淡入帧),或元素带着 fill-mode 为 both/forwards 的 animation,它就成为 backdrop root,其 ::before 的 backdrop-filter 再也采不到背后的壁纸/视频——玻璃立刻(或永久)退化成一块半透明色。这正是「悬停按钮变半透明」「右键菜单没有毛玻璃」的根因。落地做法:交互提亮用 globals.css 的 .lg-glass-interactive(filter 落在 ::before 上);入场动画只做 transform(位移/缩放),不做淡入,fill-mode 一律 backwards(只有退出动画才用 both);需要淡入的只有「既不是玻璃宿主也不是玻璃祖先」的层(如独立遮罩,改用 lg-backdrop-in 渐变背景色)。完整对照与实测数据见 types_locales_styles.md 的「backdrop root 约束」
  • 组件清单与 props 见 ui_components.md
  • 单选/分段语义优先用 GlassRadioGroup(<GlassRadioGroup value=… onValueChange=…> + <GlassRadioGroup.Item value=…>,见 ui/radio-group.tsx),不要用 Radix ToggleGroup 冒充单选组(其 type 只接受 single/multiple,传 horizontal/vertical 会踩类型陷阱)

4. 拓展流程示例:添加一个新的数据展示卡片

  1. 需求分析: 确定卡片展示的数据字段及来源(是否已有后端命令 → 查 ipc_index.md)
  2. 后端准备: 若无命令,按 backend_development.md 新增 command + service 方法
  3. 前端实现:
    • 复制 src/components/cards/_template/ 并重命名,写 meta.json + index.tsx + locales
    • 数据获取用 useCardData(src/components/cards/base/use-card-data.ts)
    • 需要用户配置时走 CardConfigService 并在 types/card-settings.ts 补类型
    • 「添加卡片」对话框(src/components/add-card-modal.tsx)会自动收录,无需改代码
  4. 测试验证: 运行项目,测试卡片添加、数据显示、拖拽排序、配置保存与卡片删除后的配置清理

5. 改代码后的文档同步

改动涉及新增/重命名文件、导出符号或 IPC 命令时,同步更新对应的 reference/*.md(格式见 README.md)。