项目:xiaoye-admin-react | 版本:v1.0 | 生成日期:2026-10-06 | 依据:源码分析
xiaoye-admin-react 是一个基于 Vite + React 19 + Tailwind CSS v4 + Ant Design 6 的企业级后台管理系统脚手架(package.json 中描述为 "Xiaoye Admin React - Vite + React + Tailwind CSS + Ant Design 后台管理框架")。它不是某个具体业务系统的残片,而是一套面向二次开发的后台框架:内置了登录鉴权、动态路由与菜单权限、按钮级权限、多套布局、主题定制、国际化、请求层封装、MSW 本地 Mock、OpenAPI 代码生成以及一整套演示业务页面。开发者拿到代码后,替换掉 Mock 数据、接上真实后端,即可在其上快速搭建自己的管理系统。
项目由原 Hooks-Admin-Pro-master(Xiaoye-Admin)全量移植而来(见 README.md 第七节),原项目使用自研组件库 @xiaoye-react/ui,本项目统一改为 Ant Design 6 + Tailwind CSS,并且全程不使用 Less——所有样式文件均为原生 .css,原子化样式由 Tailwind CSS v4 承担,组件样式由 antd 的 CSS-in-JS 运行时承担(README.md 开篇明确声明,vite.config.ts 末尾注释再次强调)。
目标用户分为三类:
nas-deploy/ 提供的 NAS + VPS + frp 内网穿透部署链路,以及 history 路由、PWA 的服务器配置要求。演示形态:项目以 pnpm build:mock 构建的"演示构建"(production + MSW 浏览器端 Mock)部署于 admin-react.zhangzhengyang.com(nas-deploy/README.md),无需真实后端即可完整演示登录(admin / 123456)、权限、CRUD 等全部页面。
这套"React 19 + antd 6 + Tailwind 4 + Vite 8"的组合并非流行词拼盘,每一项都服务于"脚手架"这一定位。React 19 提供 lazy/Suspense 的成熟语义与 useSyncExternalStore 等底层能力(后者被 OfflineBanner 用于订阅网络状态);antd 6 以 CSS-in-JS 运行时承担组件样式,配合 @ant-design/cssinjs 的 layer 能力解决了与原子化 CSS 的优先级冲突,使两套样式体系可以无 !important 共存;Tailwind v4 的 CSS-first 配置(@theme 设计令牌 + 官方 Vite 插件)消灭了传统 v3 的 tailwind.config.js 与 PostCSS 链,让"设计令牌"直接写在 src/styles/global.css 里并被工具类自动消费;Vite 8 则通过 import.meta.glob、define 注入、插件开关体系支撑了动态路由、构建信息注入与环境分化这三个框架级特性。选择 pnpm 除了速度与磁盘收益,更因其严格的 node_modules 结构形成了"依赖必须显式声明"的纪律(@ant-design/cssinjs 因此出现在 dependencies 中,尽管它也是 antd 的传递依赖)。理解这些"为什么"比记住"是什么"更重要:替换任何一项技术栈时,都要重新评估它对样式分层、动态路由与 Mock 体系的影响。
以下能力全部由源码实现,标注了对应的入口文件,均可在二次开发中直接复用。
| 能力域 | 能力说明 | 关键入口 |
|---|---|---|
| 动态路由 | import.meta.glob 扫描 src/views/** 懒加载,菜单 JSON/接口驱动注册路由 | src/routers/helper/ConvertRouter.tsx |
| 路由守卫 | 未登录跳登录(带 redirect 回跳)、已登录禁回登录页、document.title 国际化 | src/routers/helper/RouterGuard.tsx |
| 菜单权限 | 登录后拉取菜单树,生成侧边菜单/扁平路由/按钮权限三类数据 | src/hooks/usePermissions.ts、src/stores/modules/auth.ts |
| 按钮权限 | AuthButton 组件 + useAuthButton Hook,按页面 meta.key 匹配权限码 | src/components/AuthButton/index.tsx、src/hooks/useAuthButton.ts |
| 四套布局 | vertical / classic / transverse / columns 运行时切换 | src/layouts/index.tsx、src/layouts/Layout*.tsx |
| 多标签页 | 可拖拽排序、可关闭、affix 固定、与 KeepAlive 缓存联动 | src/layouts/components/Tabs/index.tsx、src/stores/modules/tabs.ts |
| 页面缓存 | 基于 display 切换的 KeepAlive,meta.isKeepAlive 控制缓存 | src/components/KeepAlive/index.tsx |
| 主题系统 | 主题色/暗黑/紧凑/圆角/灰度/色弱/水印实时切换,antd algorithm + CSS 变量双轨 | src/components/ThemeProvider/index.tsx、src/hooks/useTheme.ts |
| 国际化 | i18next 中/英双语,菜单/按钮/错误提示/antd locale/dayjs 全联动 | src/languages/index.ts、src/components/ThemeProvider/index.tsx |
| 请求层 | axios 封装:token 注入、全屏 loading、业务码分流(showType)、401 统一登出 | src/api/index.ts、src/api/helper/errorHandler.ts |
| 本地 Mock | MSW 2 浏览器端拦截,42 个接口与 src/api/modules/ 一一对应 | src/mocks/、public/mockServiceWorker.js |
| OpenAPI 代码生成 | pnpm openapi 从 openapi/oneapi.json 生成 src/services/api | scripts/openapi.mjs、src/api/openapiRequest.ts |
| 异常兜底 | ErrorBoundary(含 ChunkLoadError 发版自愈)、403/404/500 页、断网横幅 | src/components/ErrorBoundary/index.tsx、src/components/OfflineBanner/index.tsx |
| PWA | vite-plugin-pwa,navigateFallback 支持 history 深链离线刷新 | build/plugins.ts |
| 演示页面集 | 77 项菜单(authMenuList.json 实际解析口径,见第 4 节):form/list/feat/assembly/showcase/system 等约 60+ 业务页 | src/views/**、src/assets/json/authMenuList.json |
| 包 | 版本 | 用途与选型理由 |
|---|---|---|
react / react-dom | ^19.3.0 | 视图层,React 19 稳定版 |
antd | ^6.6.3 | 企业级组件库,替代原自研 @xiaoye-react/ui,降低维护成本 |
@ant-design/cssinjs | ^2.1.2 | 提供 StyleProvider layer,把 antd 样式注入 @layer antd 降权,是 antd 与 Tailwind 共存的关键(src/main.tsx) |
@ant-design/icons | ^6.3.4 | 图标库,版本与 antd 主版本对齐 |
react-router-dom | ^6.30.4 | 路由,使用 createBrowserRouter/createHashRouter + loader 传递路由元信息 |
zustand | ^5.0.14 | 状态管理,轻量、支持 persist 中间件做 localStorage 持久化 |
axios | ^1.18.1 | HTTP 客户端,拦截器体系承载鉴权与错误分流 |
i18next / react-i18next | ^26.3.6 / ^17.0.10 | 国际化 |
ahooks | ^3.9.7 | Hooks 工具集(如 useDebounceFn、useUpdateEffect) |
echarts | ^6.1.0 | 图表(src/components/Echarts/、src/views/assembly/echarts/) |
@dnd-kit/* | core/sortable/utilities | 多标签页拖拽排序、拖拽表格 |
dayjs | ^1.11.21 | 时间处理,随 antd 内置依赖并同步 locale |
md5 | ^2.3.0 | 登录/改密密码摘要传输(src/views/login/components/LoginForm.tsx) |
nprogress | ^0.2.0 | 顶部进度条(src/config/nprogress.ts) |
react-colorful | ^5.8.0 | 主题抽屉取色器 |
react-transition-group | ^4.4.5 | 进出场动画——声明但全仓零引用(无任何 import,仅 src/styles/index.css 注释提及),历史遗留、可移除(口径依据见《17-依赖全景》第 6 节) |
qs | ^6.15.3 | URL 参数序列化——声明但全仓零引用(仅 src/enums/httpEnum.ts 注释提及),历史遗留、可移除(口径依据见《17-依赖全景》第 6 节) |
| 包 | 版本 | 用途与选型理由 |
|---|---|---|
vite | ^8.3.0 | 构建工具与 dev server |
@vitejs/plugin-react | ^6.1.1 | JSX 转换与 Fast Refresh |
tailwindcss + @tailwindcss/vite | ^4.3.3 | 原子化 CSS。v4 采用 CSS-first 配置,无需 tailwind.config.js/postcss.config.js/autoprefixer |
typescript | ^5.9.3 | 类型系统。必须锁 5.9.x:typescript-eslint@8.70.0 的 peer 约束为 <6.1.0,TS 7(Go 版)暂不兼容(README.md 明确警告) |
msw | 2.15.0(精确锁定) | Service Worker 级 Mock;public/mockServiceWorker.js 与版本绑定,升级需重新 npx msw init public/ --save |
@umijs/openapi | ^1.14.1 | OpenAPI → TypeScript service 生成器 |
vitest + @vitest/coverage-v8 + happy-dom | ^5.0.0 / ^20.14.5 | 单元测试(DOM 环境用 happy-dom,独立 vitest.config.ts) |
@testing-library/react / jest-dom / user-event | ^16 / ^7 / ^14 | 组件测试 |
vite-plugin-checker | ^0.14.5 | dev 时把 TS 错误显示在浏览器浮层 |
vite-plugin-compression | ^0.5.1 | 按 VITE_BUILD_COMPRESS 产出 gzip/brotli |
vite-plugin-pwa | ^1.3.0 | PWA 与离线回退 |
rollup-plugin-visualizer | ^7.1.1 | VITE_REPORT=true 时产出 stats.html 体积分析 |
eslint 生态(^10)+ prettier(^3.9.6) | — | Flat config 代码规范,eslint-config-prettier 置于数组最后 |
less,Vite 机制上无法处理 .less。布局/原子样式归 Tailwind,组件样式归 antd CSS-in-JS,变量用原生 CSS 自定义属性与 @theme 设计令牌,三者覆盖 Less 全部场景(README.md 第四节)。@layer theme, base, antd, components, utilities;,配合 StyleProvider layer,使 Preflight 不破坏 antd、工具类无需 !important 即可覆盖 antd(详见 docs/tailwind-antd-style-conflict.md 与 src/styles/global.css)。@ant-design/cssinjs 虽是 antd 的传递依赖,也必须显式声明才能直接 import。src/api/modules/ 一一对应,且演示站可脱离后端独立运行。package.json engines),包管理器 pnpm >= 9。工程规范层面有三组约定需要二次开发者知悉。第一组是 ESLint Flat Config 的顺序敏感性(eslint.config.js):导出数组的排列依次为忽略目录(dist/node_modules/coverage 与生成物 src/services/**)、js.configs.recommended、typescript-eslint 推荐集、项目自定义 TS/TSX 规则、Node 脚本全局变量补充、纯类型文件的 no-explicit-any 豁免,最后一项必须是 eslint-config-prettier,用于关闭一切与格式化冲突的检查规则——这个"最后一项"是硬约束,插队会导致格式类误报。项目还有几个刻意的规则取舍(源码注释写明理由):TS 文件关闭 no-undef(TypeScript 自身已做未定义检查,重复检查产生误报);React Hooks 与 React Refresh 规则手动声明而非引用插件预设对象(插件预设的导出结构在大版本间会变,手动声明更稳);no-console 仅放行 warn 与 error。
第二组是 Prettier 与编辑器约定:.prettierrc.json 核心项为 printWidth: 120、双引号、带分号、trailingComma: "none"、arrowParens: "avoid";.prettierignore 排除生成物;.editorconfig 与 .vscode/ 提供基础编辑器对齐。
第三组是 TypeScript 工程结构:采用 project references 三文件结构——tsconfig.json 仅做引用聚合不含编译配置,tsconfig.app.json 管业务源码 src/(配置 @ 别名、strict、noUnusedLocals、noUnusedParameters、verbatimModuleSyntax),tsconfig.node.json 管构建脚本 vite.config.ts(需要 "types": ["node"])。verbatimModuleSyntax: true 与 ESLint 的 consistent-type-imports 规则配合,确保类型导入在编译后被完全擦除,这是业务代码中大量 import type 写法的来源。
xiaoye-admin-react/
├── build/ # Vite 构建辅助(被 vite.config.ts 引用)
│ ├── getEnv.ts # 环境变量解析 wrapperEnv(布尔/数字/JSON 自动转换)
│ ├── plugins.ts # 插件组装:checker / compression / PWA / visualizer
│ └── proxy.ts # 依据 VITE_PROXY 生成 dev server 代理
├── docs/ # 项目内技术文档(Tailwind 冲突机制、迁移指南、审计报告)
├── nas-deploy/ # NAS 演示站部署链路(详见 06-部署运行)
│ ├── README.md # 发布手册:端口、DNS、frp、HTTPS、验收
│ ├── docker-compose.yml # nginx:1.27-alpine 卷挂载纯静态站
│ ├── nginx-default.conf # 站点 nginx 配置(缓存策略 + SPA 回退)
│ ├── sync.sh # 一键发布脚本(构建→同步→原子切换→验证)
│ ├── frpc-add.toml # frp 隧道追加片段
│ └── vps-nginx-admin-react.conf # VPS 反代站点配置
├── openapi/
│ └── oneapi.json # OpenAPI 3.0.1 契约示例(与 MSW handler 对齐)
├── scripts/
│ └── openapi.mjs # @umijs/openapi 生成入口(pnpm openapi)
├── public/
│ ├── mockServiceWorker.js # MSW Service Worker(与 msw 版本绑定)
│ └── logo-*.png / favicon.svg# PWA 图标与站点图标
├── tests/
│ └── setupTests.ts # vitest 全局 polyfill(matchMedia/ResizeObserver 等)
├── src/
│ ├── main.tsx # 应用入口:开发态预置登录、MSW 启动、Provider 嵌套
│ ├── App.tsx # 注入 antd App 实例、ErrorBoundary、RefreshProvider
│ ├── api/ # 手写请求层
│ │ ├── index.ts # axios 实例 + 请求/响应拦截器
│ │ ├── openapiRequest.ts # OpenAPI 生成代码的 request 适配器
│ │ ├── config/servicePort.ts # 接口前缀(/hooks)
│ │ ├── helper/ # checkStatus / errorHandler / errorShowType
│ │ ├── interface/index.ts # 全部请求/响应类型(Result 信封、分页、业务实体)
│ │ └── modules/ # login / user / system / business 四域 42 个接口
│ ├── services/ # OpenAPI 生成产物(pnpm openapi 生成,禁止手改)
│ ├── assets/
│ │ ├── json/ # authMenuList.json(77 项菜单树)、authButtonList.json
│ │ ├── mock/ # Xiaoye-Admin.openapi.json(历史契约存档)
│ │ └── icons / images / iconfont
│ ├── components/ # 通用组件:AuthButton、Echarts、Error(403/404/500)、
│ │ # ErrorBoundary、Icon、KeepAlive、Lazy、Loading、
│ │ # OfflineBanner、QRCode、SvgIcon、SwitchDark、
│ │ # ThemeProvider、Tour、Transfer、日期时间封装等
│ ├── config/ # 全局常量:HOME_URL/LOGIN_URL/白名单、nprogress、proTable 分页
│ ├── context/ # Refresh(页面刷新 nonce)、RouteMeta(路由元信息注入)
│ ├── enums/httpEnum.ts # ResultEnum / RequestEnum / ContentTypeEnum
│ ├── hooks/ # usePermissions、useAuthButton、useTheme、useMessage、
│ │ # useClipboard、useCountUp、useEyeDropper
│ ├── languages/ # i18next 初始化 + zh/en 词表
│ ├── layouts/ # 四套布局 + Header/Menu/Tabs/Main/Footer/ThemeDrawer
│ ├── mocks/ # MSW:browser.ts、handlers.ts、modules/{auth,demo,system,business}
│ ├── pages/ # 空目录(.gitkeep),业务页面统一放 views/
│ ├── routers/ # 路由入口、静态路由、ConvertRouter、RouterGuard、类型
│ ├── stores/ # zustand:global(持久化)/user(持久化)/auth/tabs
│ ├── styles/ # global.css(@layer + @theme)、theme/{global,header,sider}.ts
│ ├── typings/ # global.d.ts(ViteEnv/__APP_INFO__)/window.d.ts($navigate)/utils.d.ts
│ ├── utils/ # menu、navigate、safeRedirect、themeTokens、color、is
│ └── views/ # 全部页面(懒加载扫描目标):about/assembly/auth/details/
│ # feat/form/home/link/list/login/menu/noLayout/result/
│ # showcase/system
├── .env / .env.development / .env.production / .env.test / .env.mock
├── eslint.config.js / .prettierrc.json
├── tsconfig.json / tsconfig.app.json / tsconfig.node.json # project references 结构
├── vite.config.ts / vitest.config.ts
└── package.json # engines: node >= 20.19.0说明:src/assets/json/authMenuList.json 经实际解析统计为 77 个菜单节点(14 个顶级),本文档集统一以 77 为准;README.md 写"80 项"、src/mocks/modules/auth.ts 与 src/hooks/usePermissions.ts 注释写"81 项/条",均系历史版本菜单的遗留口径,以源码 JSON 实际解析结果为准(依据与源码侧待修订项见《08-安全与权限设计》第 6 节、《13-技术债务与演进建议》D1)。
node -v # 需 >= 20.19.0
pnpm -v # 需 >= 9
pnpm install
pnpm dev # 启动开发服务器(端口取 .env 的 VITE_PORT=5173,自动打开浏览器)开发态有两个刻意设计(src/main.tsx):
xiaoye-user(dev-token + 演示账号)并跳过登录页;.env.development 中 VITE_DEV_AUTO_LOGIN=false 可关闭,以便完整测试登录/退出/redirect 回跳。enableMocking() 在渲染前 await,带 5 秒超时兜底与"SW 未接管页面自动整页刷新一次"的时序缺口修复。开发态菜单直接使用 src/assets/json/authMenuList.json(usePermissions 短路后端请求),按钮权限仍走 MSW 接口并带 5 秒重试窗口。
| 命令 | 说明 |
|---|---|
pnpm dev | 开发服务器 |
pnpm build | tsc -b 类型检查 + 生产构建 → dist/ |
pnpm build:mock | 演示构建(production + VITE_ENABLE_MOCK=true 保留 MSW) |
pnpm preview | 本地预览生产产物 |
pnpm typecheck / pnpm lint / pnpm lint:fix | 类型检查 / ESLint |
pnpm format / pnpm format:check | Prettier 格式化 / 校验 |
pnpm openapi | 从 openapi/oneapi.json 重新生成 src/services/api |
pnpm test / test:watch / test:coverage | vitest 单测 |
src/views/<your-module>/index.tsx 创建组件(目录约定为 index.tsx,因为动态路由按 /src/views + element + .tsx 解析,见 src/routers/helper/ConvertRouter.tsx)。{ path, element, meta } 节点加入 src/assets/json/authMenuList.json;接入真实后端后由菜单接口(GET /api/hooks/menu/list,mock 见 src/mocks/modules/auth.ts)下发。meta.key 用于菜单/标签/按钮权限的 i18n 与权限码定位。src/api/modules/<domain>.ts 增加函数并在 src/api/interface/index.ts 补类型;契约优先方式则更新 openapi/oneapi.json 后执行 pnpm openapi 重新生成(生成物 src/services/api 禁止手改,见 src/services/README.md)。src/mocks/modules/ 对应域文件中用 MSW http.get/post 注册 handler(与真实接口路径 /api/hooks/... 一致)。src/languages/modules/zh.ts 与 en.ts 中按命名空间补充。pnpm typecheck && pnpm lint && pnpm test,再 pnpm dev 目测。生产构建后刷新深链不 404 依赖服务器 try_files ... /index.html 回退(README.md 部署注意一节)。.env.development 的 VITE_PROXY 配置 [["/api", "http://your-backend"]](build/proxy.ts 会 rewrite 掉 /api 前缀并支持 https/ws);.env.production 的 VITE_API_URL 保持 /api 由 Nginx 反代(当前默认值即是);openapi/oneapi.json 替换为后端导出的 swagger/oneapi 文档并重新生成 services;src/hooks/usePermissions.ts 注释明确这是防权限绕过的安全设计)。这套框架里"开发态"与"生产态"的差异不止于接口地址,还涉及权限、Mock 与登录三条主线,二次开发时若不清楚差异会误判问题。
权限主线:usePermissions 用 import.meta.env.PROD 取反判断开发态(而不是 DEV,源码注释解释:以 --mode development/test 构建部署时 DEV 仍为 true,会误判为开发态)。开发态菜单直接取 src/assets/json/authMenuList.json 静态数据、零网络等待,仅按钮权限仍请求 MSW 接口(带 5 秒重试窗口)以保证按钮权限演示页有数据;生产态两个权限接口并行请求,菜单失败即清登录态跳登录,空菜单视为合法结果。因此"开发态看得到、生产态看不到的菜单"几乎都是后端菜单数据未下发所致,而不是前端路由注册问题。
Mock 主线:开发态恒定启动 MSW;演示构建(pnpm build:mock)靠 VITE_ENABLE_MOCK=true 在生产产物中保留 MSW;常规生产构建通过动态 import("@/mocks/browser") 让 MSW 代码不进入产物。MSW 启动有三重防护(5 秒超时、未接管请求告警、SW 接管缺口自愈刷新),任何一重触发失败都不会阻塞渲染。
登录主线:开发态默认预置 dev-token 与演示账号直接进主界面,想测完整登录流需把 .env.development 的 VITE_DEV_AUTO_LOGIN 改为 false 并重启 dev server;生产态永远从真实登录开始。另外开发态 usePermissions 不会触发菜单接口,意味着"菜单接口超时 30 秒挂起"这类问题只会在生产形态出现——这也是该短路设计诞生的原因(源码注释:避免 mock 不可达时请求挂起)。
以下条目均对应源码中真实存在的机制。其一,页面刷新后深链 404:history 路由要求服务器把未知路径回退 index.html(Nginx try_files $uri $uri/ /index.html;),dev server 与 pnpm preview 已内置,自建静态服务器必须补齐。其二,控制台出现"你所访问的资源不存在":典型的 MSW 未接管,首装 SW 的时序缺口已由 main.tsx 的自动刷新自愈,若仍出现检查 public/mockServiceWorker.js 是否与 msw 版本匹配。其三,切页白屏且控制台报 chunk 加载失败:发版后旧 chunk 已删除,ErrorBoundary 会给出"重试"按钮,恢复联网时自动整页刷新;开发中遇到多半是构建缓存问题。其四,登录后跳回登录页:检查菜单接口是否返回成功——生产态菜单失败会清 token,RouterGuard 随即拦截跳转,这是设计行为而非 Bug。其五,整页按钮全部消失:按钮权限接口最终失败会按"无权限"处理(usePermissions 的兜底分支),检查 /api/hooks/auth/buttons 的可达性,开发态该接口有 5 秒重试窗口仍失败才会触发。其六,构建警告 INEFFECTIVE_DYNAMIC_IMPORT 与 vendor-antd 超 1.5MB:均为已知且无害(README.md "已知构建提示"),前者源于 login 页被静态与动态双路导入,后者源于 antd 全量引入。