# FlashLink Home Web 基于 React + TypeScript + Vite 的 H5 前端项目。 ## 环境要求 - Node.js >= 22.12.0(React Router v7 framework 模式 + Vite 7 要求) - pnpm >= 10 > 依赖统一用 pnpm 命令管理(`pnpm add` / `pnpm add -D`),勿手改 package.json 版本号,否则会与 lockfile 不一致导致 `frozen-lockfile` 安装失败。 ## 快速开始 ```bash pnpm install --frozen-lockfile pnpm dev ``` 浏览器访问控制台输出的本地地址即可。 ## 渲染模式(CSR / SSG) 项目支持两种渲染模式,**同一份代码、构建时切换**(互斥,一次只出一种产物)。两种模式与 `--mode`(环境:localdev/development/test/production)**正交**,通过不同的 npm script 选择(脚本内部用 `APP_RENDER_MODE` 环境变量传给 `vite.config.ts`)。 | 模式 | 入口 | 路由 | i18n | 产物 | 适用 | | --- | --- | --- | --- | --- | --- | | **SSG**(默认) | `root.tsx` / `entry.client.tsx` | `/:lang/home`(路径前缀多语言) | `frameworkI18n.ts`(同步 init) | `dist/client/`(预渲染静态 HTML) | 需爬虫/SEO 可见内容,官网/落地页 | | **CSR** | `index.html` / `main.tsx` | `/home`(无语言前缀) | `i18n/index.ts`(LanguageDetector 异步) | `dist/`(空壳 SPA) | 纯单页应用,无预渲染需求 | > 两套入口、路由(`src/routes.ts` vs `src/router/`)、i18n 并存且**完全独立**,由 `APP_RENDER_MODE` 决定实际加载哪套。详见 [SSG 架构文档](./docs/SSG.md)。 ## 脚本说明 | 命令 | 说明 | | --- | --- | | `pnpm dev` | 本地开发(默认 = `dev:ssg`) | | `pnpm dev:ssg` / `pnpm dev:csr` | 指定模式本地开发(`.env.localdev`) | | `pnpm build` | 生产构建(默认 = `build:ssg:prod`) | | `pnpm build:ssg:{dev,test,prod}` | SSG 模式各环境构建 → `dist/client/` | | `pnpm build:csr:{dev,test,prod}` | CSR 模式各环境构建(先 tsc,再 vite build) → `dist/` | | `pnpm build:dev` / `build:test` / `build:prod` | 默认模式(SSG)各环境构建别名 | | `pnpm typecheck` | 生成路由类型并做 TS 类型检查(`react-router typegen && tsc`) | | `pnpm preview` | 预览 SSG 产物(= `preview:ssg`) | | `pnpm preview:ssg` | 用 sirv 静态伺服 `dist/client/`(端口 8848) | | `pnpm preview:csr` | 用 sirv 静态伺服 `dist/`(端口 8848) | | `pnpm lint` / `pnpm lint:fix` | ESLint 检查 / 自动修复 | | `pnpm stylelint` / `pnpm stylelint:fix` | 样式检查 / 自动修复 | ## 环境配置 通过不同 `.env` 文件区分环境,Vite 按 **mode** 加载: | 文件 | 何时加载 | | --- | --- | | `.env` | 始终加载(公共默认) | | `.env.localdev` | `pnpm dev`(`--mode localdev`) | | `.env.development` | `pnpm build:dev` | | `.env.production` | `pnpm build` / `pnpm build:prod` | | `.env.test` | `pnpm build:test` | | `.env.local` | 本地覆盖,除 test 外都会加载(一般不提交) | 需在环境文件中配置的变量见 `.env` 内注释或项目文档。 ## 技术栈 - **框架**:React 18、TypeScript - **构建**:Vite 7、pnpm - **渲染**:双模式(构建时切换)——SSG(React Router v7 framework 模式 + 静态预渲染,默认)/ CSR(传统单页应用)。详见 [渲染模式](#渲染模式csr--ssg) 与 [SSG 架构文档](./docs/SSG.md) - **UI**:Ant Design 5、Tailwind CSS、SCSS/Less - **路由**:React Router 7(SSG 用 `/:lang/...` 路径前缀多语言;CSR 用 `/home` 无前缀) - **状态**:基于 Context + Hooks 的 Model 方案(类 Umi max) - **国际化**:i18next(SSG 用 `src/i18n/frameworkI18n.ts` 同步 init;CSR 用 `src/i18n/index.ts` LanguageDetector 异步) - **请求**:Axios 封装(`src/utils/request`),请求体 brotli 压缩(brotli-wasm) - **图标**:@iconify/react + 自定义 SVG 转 Iconify 插件 - **规范**:ESLint、Prettier、Stylelint、Commitlint、Husky、lint-staged ## 项目结构 ``` ├── build/ # Vite 构建相关(插件、工具、分包/压缩/服务配置) │ └── config/ # 细粒度 Vite 配置模块(chunks、build、plugins、server、optimize) ├── public/ # 静态资源 ├── src/ │ ├── assets/ # 资源 │ │ └── iconify/ # 单色 single-color、多色 multi-color 图标 │ ├── components/ # 公共组件 │ ├── config/ # 应用与请求配置(含 request 拦截器、错误处理) │ ├── defines/ # 枚举与常量 │ ├── hooks/ # 公共 Hooks │ ├── i18n/ # 国际化配置 │ ├── layouts/ # 布局 │ ├── locales/ # 多语言文案(zh-CN、en-US、fa-IR) │ ├── models/ # 全局状态 Model │ ├── pages/ # 页面(按页面分目录,直接作为 route module) │ ├── routes/ # framework 路由用的包装组件(langLayout、rootRedirect 等) │ ├── services/ # API 接口(按模块分目录) │ ├── styles/ # 全局样式 │ ├── utils/ # 工具与 request 封装 │ ├── root.tsx # HTML 外壳 + Provider 层(取代 index.html/App.tsx) │ ├── routes.ts # framework 路由配置(含 /:lang 前缀) │ ├── entry.client.tsx # 客户端入口(hydrateRoot,取代 main.tsx) │ └── entry.server.tsx # 构建期预渲染入口 ├── types/ # 全局类型与 env 类型 ├── docs/SSG.md # SSG / framework 模式架构文档 ├── react-router.config.ts # framework 模式配置(ssr:false + prerender) ├── vite.config.ts └── package.json ``` ## 开发规范摘要 - 严格 TypeScript,遵循项目 ESLint/Prettier 配置 - 提交信息使用 Conventional Commits - 组件:函数组件 + Hooks;样式优先 Tailwind,必要时 SCSS/Less - 用户可见文案全部走 `locales`,禁止在代码中硬编码 - 常量与枚举放在 `src/defines/`,请求在 `src/services/` 按模块组织 - 新页面:在 `src/pages/` 下建目录、在 `src/routes.ts` 中注册路由、在 `locales` 补文案;若页面需预渲染,在 `react-router.config.ts` 的 `prerender()` 中加路径(详见 [SSG 架构文档](./docs/SSG.md)) ## 状态管理(Model) - Model 定义在 `src/models/`,通过 `createModel(hook, name)` 创建。 - 在组件中:`const user = userModel.useModel()`,使用返回的状态与方法。 - 根组件已通过 `autoImportModels` 注入所有 Model 的 Provider。 详见 `src/utils/model/` 与现有 `userModel`、`dialogModel`、`userConfigModel` 实现。 ## 图标 - 单色图标:放入 `src/assets/iconify/single-color/`,会转为 `currentColor`,可用 `className` 控制颜色。 - 多色图标:放入 `src/assets/iconify/multi-color/`,保留原色。 - 使用:`import icon from '@/assets/iconify/single-color/xxx.svg'`,再 ``(@iconify/react)。 ## 多语言 - 文案在 `src/locales/` 下按语言分目录(zh-CN、en-US、fa-IR),每语言含 common、components、menus、pages 等模块。 - 组件内:`const { t } = useTranslation();`,`t('key')` 或带命名空间的 key。 - 禁止在页面/组件中写死中文或英文字符串。 ## 请求与接口 - 请求封装:`src/utils/request`,基础配置与拦截器在 `src/config/request/`(认证、加解密、错误处理)。 - 接口按模块放在 `src/services/`,每模块可有 `typings.d.ts` + `index.ts`。 - 调用:`import { fetchXxx } from '@/services/xxx'`,错误由统一错误处理兜底,可选 `skipErrorHandler` 自行处理。 ## 本地存储 - `src/utils/localUtils`、`src/utils/sessionUtils` 提供 `createLocalTools` / `createSessionTools`,支持按 key/value 加密与命名空间。 - 通过环境变量配置加密密钥与存储命名空间,敏感数据建议开启加密选项。 ## 部署 两种渲染模式的产物目录与 nginx 配置**不同**,按实际构建的模式选择对应配置。多环境通过 `build:dev` / `build:test` / `build:prod` 与对应 `.env.*` 区分。 ### SSG 模式(默认) - 产物在 **`dist/client/`**(framework 模式产物结构)。`ssr:false` 为纯静态预渲染,**无需常驻 Node 服务**,对 CDN 加速零影响。 - 预渲染路径在 `react-router.config.ts` 的 `prerender()` 中配置;未预渲染的路径由 SPA fallback(`dist/client/index.html`)承载。 - nginx 需先尝试命中预渲染静态页,再回落 SPA fallback(详见 [SSG 架构文档](./docs/SSG.md)): ```nginx server { root /var/www/home-web/dist/client; location / { # 依次尝试:精确文件 → 目录 index(命中预渲染页) → flat 后缀 → 回落 SPA fallback try_files $uri $uri/ $uri.html /index.html; } # 带 hash 的静态资源长缓存 location /assets/ { expires 1y; add_header Cache-Control "public, immutable"; } } ``` ### CSR 模式 - 产物在 **`dist/`**,仅有一个空壳 `index.html`,无预渲染文件,即标准单页应用部署。 - nginx 只需最基础的 SPA fallback(无需 `$uri/`、`$uri.html`,产物里没有对应文件): ```nginx server { root /var/www/home-web/dist; location / { try_files $uri /index.html; } # 带 hash 的静态资源长缓存 location /assets/ { expires 1y; add_header Cache-Control "public, immutable"; } } ```