# 01 — 框架选型与技术栈 > 参考:vue-vben-admin(★★★★★ Vue 标准标杆)、ant-design-pro(★★★★★ React 标准标杆)、soybean-admin(★★★★☆ 强烈推荐)、vue-pure-admin(★★★★☆ 推荐);对照:gin-vue-admin(★★☆☆☆ Gin 全栈) --- ## 一、选型的第一性问题:单栈还是双栈 ### 1.1 标杆项目的单栈现实 | 项目 | 框架 | 为什么不双栈 | |------|------|--------------| | ant-design-pro | React 19 | 生态深度绑定 Umi Max 运行时(约定式路由、运行时配置、插件体系) | | gin-vue-admin | Vue 3 | Element Plus + 国内管理端生态,Go 后端配套脚手架默认 Vue(★★☆ 仅对照) | | vue-vben-admin | Vue 3 | 用 monorepo + adapter 层支持 5 个 UI 库,但**框架始终只有 Vue** | | soybean-admin | Vue 3 | 文件路由 + 生成物策略,规范优先(★★★★☆ 强烈推荐) | | vue-pure-admin | Vue 3 | 单包直出、轻量实用,官方精简版 thin(★★★★☆ 推荐) | > **结论**:标杆项目全部单栈。多 UI 库共存靠的是「适配器层」(vben 的 `effects/*` adapter),而不是多框架。**框架级双栈的成本(请求层、状态、权限、构建、测试全部 ×2)只有迁移期才值得付。** > > 参考优先级:**Vue 架构看 vben、规范写法看 soybean、同栈 EP 实操看 pure、React 看 adp**;gva 仅作 Gin 全栈视角补充,不作前端代码质量的首选参考。 ### 1.2 双栈只在迁移期 ``` ┌─────────────────────────────────────┐ │ 后端 REST API(Gin / FastAPI ...) │ │ 单一契约:/api/* │ └───────────┬─────────────┬───────────┘ │ │ ┌───────────▼────┐ ┌─────▼──────────────┐ │ 主端(功能源) │ │ 副端(迁移参照) │ │ 承接全部新功能 │ │ 只对齐、不抢先 │ │ 进入产物 │ │ 迁移完成即退役 │ └────────────────┘ └────────────────────┘ ``` 迁移期三条纪律(**必须**): 1. **功能以主端为准**:副端只做「对齐」同步,不抢先实现。 2. **契约以两端共同名为准**:token key、主题 key、模块开关键、权限码、响应格式——迁移开始前就统一命名(推荐命名空间 `.auth.token` / `.theme.mode`),而不是迁移后再改。 3. **不共享代码,只共享约定**:两端各自实现同一份「契约文档」(见 08 章),不引入 monorepo 这种为双栈兜底的重型方案。 ### 1.3 何时值得上 monorepo(vben 模式) vben 用 pnpm workspace + Turbo 支撑 5 个 UI 库 app + 30+ 框架包,代价是: - `pnpm-workspace.yaml` 维护 300+ 依赖 catalog,`overrides` 强制统一版本 - 循环依赖检查(`check:circular`)、依赖合规检查(`check:dep`)成为必跑任务 - 构建配置收敛为内部包 `internal/vite-config`,app 配置才能薄到 15 行 **判断标准**:当「框架层代码」(布局引擎、请求客户端、权限系统)需要被 ≥3 个应用复用时,才值得付 monorepo 成本。普通单产品工程(1-2 个前端)——**不值得**,独立 Vite 工程 + 统一 Makefile 编排即可。soybean 的「轻 workspace」(单应用 + 8 个工具包)是中间形态,适合工具链需要沉淀的团队。 ### 1.4 何时允许偏离默认栈 **必须**:新项目 / 新模块使用本章默认栈(见 00 章「默认技术栈」);存量代码**不因「不合推荐」而大规模重写**——渐进迁移:新文件执行新标准,旧文件随功能迭代就近升级。 **允许偏离**的场景(需在 PR / ADR 中说明理由):组件库确缺关键能力(虚拟表格、复杂甘特等);性能热点需要更轻方案;与后端生态强绑定(如脚手架生成的默认产物)。 **禁止**:同一工程内并存两套 UI 库、两套状态库、两种 CSS 方案——偏离只允许发生在「整工程」粒度。 --- ## 二、UI 组件库选型 ### 2.1 三种流派对照 | 维度 | Element Plus(本标准 Vue 主推;gva 补充) | Ant Design + ProComponents(adp) | shadcn/ui(React 品牌差异化) | |------|--------------------------|-----------------------------------|-----------------------------| | 形态 | 传统全量组件库 | 组件库 + 中后台增强层 | 源码复制型(Radix + Tailwind) | | 中后台生产力 | 高(表格/表单/弹层齐全) | **极高**(ProTable/ProForm 声明式 CRUD) | 中(原子件全,业务件自建) | | 主题定制 | CSS 变量 + SCSS 双通道 | Design Token(configProvider) | **完全可控**(Token 就是你的 CSS 变量) | | 暗色模式 | `dark/css-vars.css` 一行接入 | `data-theme="dark"` + token | `.dark` class 天然支持 | | TS 支持 | 一般(类型宽松) | 好 | 极好(TSX 源码进仓库) | | 体积 | 全量较大,靠 unplugin 按需 | 较大 | 用多少复制多少 | | 适合谁 | Vue 主栈、快速交付 | React 中后台重 CRUD | React、要品牌差异化 | ### 2.2 选型决策树 ``` 需要管理端全家桶、快速交付? ├─ Vue 栈 ──────────▶ Element Plus(+ unplugin 按需引入) ├─ React 栈、CRUD 为主 ▶ Ant Design + ProComponents └─ React 栈、要品牌感 ─▶ shadcn/ui + Tailwind 双栈迁移期? └─▶ 主端选全量组件库保效率(EP), 副端选 shadcn 保可读性 —— 常见组合 ``` 两个 ★★★★☆ 参考的选型启示:soybean-admin(Naive UI + UnoCSS)证明「hook 优先 + 组件薄壳」也能撑起完整中后台;vue-pure-admin 证明了 **Tailwind v4 `@theme` 把 `--el-*` 变量桥接为原子色 token** 是 EP 工程引入原子类的最短路径(详见 06 章)。 ### 2.3 图标体系(易被忽视的选型) - **glob 注册模式(gva,补充)**:`import.meta.glob` 自动收集 `assets/icons/**/*.svg` 注册为全局组件,菜单存 icon 字符串即可 `` 渲染;插件图标加 `插件名-` 前缀防冲突。适合「菜单图标由后端配置下发」的场景。 - **路由声明模式(adp)**:路由 `icon: 'crown'` 字符串 + 图标映射表,与路由集中声明天然契合。 - **按需编译模式(pure/soybean)**:unplugin-icons 虚拟模块(`import Upload from '~icons/ri/upload-line'`)+ Iconify 离线/在线双模式。 - **通用规则**:**图标名是数据(存在菜单表里),渲染是前端映射**——不要把图标组件硬编码进菜单模板。 --- ## 三、构建工具与运行时 ### 3.1 Vite 是唯一现实选择(2026) | 能力 | 说明 | 标杆用法 | |------|------|----------| | 按需引入 | `unplugin-auto-import` + `unplugin-vue-components` | EP 组件零 import(vben/soybean 系均如此) | | `import.meta.glob` | 动态路由的组件映射基石 | gva/vben/pure 的 backend 菜单模式 | | env 分层 | `.env.development` / `.env.production` + `import.meta.env` | 全部项目;vben 另分 `VITE_GLOB_*` 运行时可改(见 08 章) | | proxy | dev 反代 `/api` 到后端端口,避免 CORS 配置漂移 | `'/api' → 后端端口`(含 `ws: true` 供 WebSocket) | | 构建指纹 | `define` 注入版本/commit hash | adp:`__APP_VERSION__` + git rev-parse 兜底 | **必须**按需引入,**禁止**全量 `app.use(组件库)` + 全量 CSS(gva 全量引入是反面教材,首屏体积大)。 ### 3.2 路由库 - Vue:`vue-router@4`,`createWebHistory`(hash 模式是历史包袱,新工程用 history) - React:`react-router@7`,用**路由配置数组 + `` 声明式**或 `createBrowserRouter`;**禁止**用 `Object.entries().map` + 三元链选页面渲染 ### 3.3 HTTP 客户端 | 库 | 使用方 | 要点 | |----|--------|------| | axios | vben / soybean / pure / gva | 拦截器、取消(AbortController)生态成熟 | | umi-request / axios | adp | 错误契约挂载在 request config 上(skipErrorHandler 等) | | fetch | — | 零依赖,但拦截器/超时/取消需自建,**不推荐**作为管理端基础 | > **必须**统一 axios:拦截器结构、错误对象形态在全员脑中保持一致,排障不用切换心智模型。 --- ## 四、TypeScript 采用策略 ### 4.1 三档策略 | 档位 | 适用 | 做法 | 标杆 | |------|------|------|------| | **纯 JS + JSDoc** | 快速交付的存量 Vue 主端 | 关键模块(request/permission/stores)写 JSDoc 类型注释 | gva | | **渐进 TS** | 存量 JS 工程演进 | 新文件 `.ts`,老文件不动;`allowJs: true`;先建 `typings/` 写 `.d.ts` | soybean 路线 | | **TS strict** | 新工程(**默认**) | `strict: true`,类型即文档 | adp、vben、soybean、pure | **新项目必须 TS strict**;存量工程按上表渐进,但接口函数**不允许**「既无类型注释也无类型定义」。 ### 4.2 类型放哪里(adp/soybean 范式) - 全局契约类型集中声明:`declare namespace API { interface RuleListItem {...} }`,服务层函数签名 `request(...)` 收口 - 页面私有类型随页面 co-location(`data.d.ts` / `utils/types.ts`) - 路由 meta 用 `vue-router.d.ts` 模块扩充强类型化(vben 30+ 字段全 JSDoc;soybean 17 字段同款) - soybean 补充:全局类型按 namespace 组织(`typings/` 下 Api/App/Storage/Env 四区,storage 与 env 均有**键级类型**,i18n key 递归派生)——**JS 工程渐进 TS 的最低成本路径**:先建 `typings/` 写 `.d.ts`,不动业务代码 --- ## 五、状态管理选型 ### 5.1 先分类再选库 ``` 状态三类: ├─ 客户端全局状态(token、用户信息、菜单、UI 偏好)→ Pinia / Zustand ├─ 服务端状态(列表、详情、缓存失效) → TanStack Query / ProTable request / 自研 usePagedQuery └─ URL 状态(筛选、分页、Tab) → 路由 query,永远不要复制进 store ``` ### 5.2 三种落法 | 落法 | 出处 | 说明 | |------|------|------| | adp 三层 | initialState(useModel)+ React Query + ProTable request | 服务端状态交给 Query:`queryKey` 按资源命名,mutation 成功后 `invalidateQueries` | | vben 分域 store | Pinia setup 风格,按域拆 modules(gva 同构,补充参考) | token/user/router/app/dictionary 各一,持久化手动分层 | | pure StoreHook | `useXxxStoreHook()` 包装 store + pinia 实例 | 在 setup 外(拦截器、守卫、工具函数)也能安全取 store | | soybean 模块化 | 每模块一目录 `index.ts`(状态编排)+ `shared.ts`(可单测纯函数) | 纯逻辑与响应式状态强制分离 | **经验值**:列表页优先「组件内管理 + 请求取消」(AbortController + 版本号方案,见 03 章),只有跨页面共享的数据才进 store。**不要为了「规范」把每个列表都塞进全局 store。** --- ## 六、技术栈对照总表 | 维度 | ant-design-pro | vue-vben-admin | soybean-admin | vue-pure-admin | gin-vue-admin | 本标准推荐值 | | ---- | -------------- | -------------- | ------------- | -------------- | ------------- | ------------------ | | 框架 | React 19 | Vue 3 | Vue 3.5 | Vue 3.5 | Vue 3 | Vue 3 或 React(单栈) | | 语言 | TS strict | TS strict | TS strict(namespace 组织) | TS(strict 关闭) | JS | 新项目 TS strict;存量渐进 | | UI 库 | antd v6 + Pro | antd-vue/EP/naive 适配 | Naive UI + UnoCSS | Element Plus + TW v4 | Element Plus | Vue: EP;React: antd 或 shadcn | | 构建 | Umi Max(utoopack) | Vite + Turbo monorepo | Vite(轻 workspace) | Vite 8/rolldown 单包 | Vite | Vite + pnpm | | 状态 | useModel + RQ | Pinia + 持久化插件 | Pinia(index+shared) | Pinia(StoreHook 模式) | Pinia 分域 | Pinia(Vue)/ Zustand+RQ(React) | | 请求 | umi-request + 错误契约 | RequestClient 类 | 五钩子工厂 + flat 返回 | PureHttp 类(196 行) | axios 单例 | axios 封装(见 03 章) | | 样式 | Tailwind v4 + antd-style | HSL Token + TW v4 | ThemeSetting 三端同步 | EP↔TW v4 变量桥接 | SCSS + UnoCSS + EP 变量 | 语义 Token 单源(见 06 章) | | 规范 | Biome + commitlint | oxlint + eslint + lefthook | soybeanjs/eslint-config + sa CLI | eslint + stylelint + husky | ESLint flat + Prettier | ESLint flat + Prettier + commitlint | | 测试 | Vitest + Playwright | Vitest(内核组件全覆盖) | —(未见体系化) | —(未见体系化) | 无(短板) | Vitest + Playwright(见 08 章) | --- ## 七、AI 检查清单(对比目标工程逐项核对) **P0(结构性的,立即改)** 1. 工程是否单栈?若双栈并存,是否有明确的「主端/副端 + 退役计划」与统一契约命名? 2. 是否存在同工程两套 UI 库 / 两套状态库 / 两种 CSS 方案? 3. 组件库是否按需引入(unplugin 或手动)?全量 `app.use()` 即不合格 4. 包管理器是否 pnpm?`preinstall: only-allow pnpm` 是否配置? **P1(质量项,排期改)** 5. 新文件是否 TS / 存量是否补 JSDoc?接口函数是否存在「无类型也无注释」 6. 路由渲染是否声明式(禁止 entries+三元链)? 7. HTTP 客户端是否统一 axios? 8. 状态三分法是否被遵守(URL 状态不进 store、列表不进全局 store)? **P2(演进项)** 9. 是否需要 `typings/` 目录承接全局契约类型(soybean 四区范式)? 10. 构建指纹(版本/commit 注入)是否配置? --- > 下一篇:[02-项目架构与目录结构](02-项目架构与目录结构.md) —— 目录是架构的最后陈述。