项目:xiaoye-admin-react | 版本:v1.1 | 生成日期:2026-10-06 | 依据:源码分析
本文提供六个最高频扩展场景的标准作业程序(SOP),每篇按"前置阅读 → 步骤 → 验证 → 易错点"组织。框架的扩展哲学在《01-系统架构》第 6 节已立规约(约定优于配置、六条不可违反的纪律),本文是那些规约在具体场景中的落地动作。所有场景的验证基线命令一致:pnpm typecheck && pnpm lint && pnpm test,然后 pnpm dev 目测。
动手前先做一次"扩展路径选择":面对需求,先问它落在哪个事实上。改"能看到什么"→ 菜单数据(场景一);改"能调什么"→ 接口三处对齐(场景二);改"显示什么语言/主题/布局"→ global store 单源(场景三、四、七);改"数据从哪来"→ 环境与代理(场景五);改"状态放哪"→ store 归属判断(场景六)。从事实源改起、让派生层跟随,是六个场景共同的方法论;反过来,从消费端硬改(手写路由、组件里写死文案、绕过 store 改 DOM)都会制造《13-技术债务与演进建议》里那类口径漂移。以下场景按使用频率排序,前两个覆盖日常开发的大多数需求。
前置阅读:《02-页面设计》1(三条组织原则)、《05-核心流程》流程二。
src/views/<module>/index.tsx 创建组件。目录约定必须是 index.tsx——动态路由按 /src/views + element + .tsx 拼接解析(src/routers/helper/ConvertRouter.tsx),换文件名或嵌套深度都会落空。列表页直接复制 src/views/list/useProTable/index.tsx 范式(params + searchVersion + latestRequestId + 弹窗 CRUD),表单页参照 src/views/form/basic。src/assets/json/authMenuList.json,结构为 { path, element, meta },meta.key 全局唯一(它同时是 i18n 词条、标签栏、按钮权限的命名空间)、meta.icon 取 antd 图标名字符串。接入真实后端后由菜单接口下发(改动在后端/菜单管理页进行,前端 JSON 不再生效,见《03-接口设计》4.3)。src/languages/modules/zh.ts 与 en.ts 的 menu 命名空间成对添加 meta.key 对应的双语标题。防御式写法 t("menu." + key, { defaultValue: meta.title }) 保证忘补词条也不显示裸 key,但菜单/标签/面包屑/document.title 四处复用同一词条,补一条即四处生效,没有理由不补。src/assets/json/authButtonList.json 加 "<meta.key>": ["add", "edit", ...],页面内用 <AuthButton authority="add"> 或 useAuthButton() 消费;演示页范例 src/views/auth/button。meta.isKeepAlive: true;固定标签加 meta.isAffix: true;动态详情页加 meta.activeMenu 指向父页;全屏页加 meta.isFull: true;外链用 meta.isLink(不注册本地路由)。验证:dev 下菜单出现且路由可达;手输未登记路径落 404;语言切换标题联动;开缓存后切页状态保留。易错点:meta.key 与既有 key 重复(路由不冲突但权限与 i18n 串位);目录级节点忘了 redirect 指向首个子菜单(点击目录无反应)。另有一个只属于动态路由体系的检查:新页面组件的默认导出必须存在且是组件(glob 转 lazy() 的前提),命名导出会导致渲染出 Page Not Found 占位或空白——若页面路过了路由但渲染异常,先查导出形态再查业务代码。
前置阅读:《03-接口设计》2 与 5。
openapi/oneapi.json 按既有信封模式(ResultXxx)补路径与 schema,跑 pnpm openapi 重新生成 src/services/api(生成物禁止手改,页面 import api.<tag>.<fn>);手写优先——在 src/api/interface/index.ts 补类型(信封 ResultData<T> + 分页 ReqPage/ResPage<T>,编辑入参用 Omit<实体, 服务端字段> 派生),在 src/api/modules/<域>.ts 加函数,路径前缀用 PORT1(src/api/config/servicePort.ts,注释明确它是后端契约路径勿改名)。src/mocks/modules/ 对应域文件用 http.get/post 注册,路径与真实接口一致(/api/hooks/...);响应用 _shared.ts 的 ok(data)/fail(msg) 包装(注释强调必须与拦截器 code===200 判定一致);列表数据遵循"确定性生成"原则(demo 域 87 条用户是范本,索引派生保证分页可复现),id 用 nextId()。src/api/interface/index.ts 的类型同时被页面、API 模块与 handler 引用——改契约先改这里,让 mock 与真实接口天然共享类型;openapi 路径则同步维护 schema。验证:dev 下页面出数;改 handler 返回 fail 确认拦截器按 showType 弹错;onUnhandledRequest: "warn" 无漏 mock 告警。易错点:忘配 showType 导致默认按 ERROR_MESSAGE 弹错;分页响应漏 total 字段导致 ProTable 范式分页失效;三种对齐里漏掉 openapi(下次有人跑 pnpm openapi 不会覆盖你的手写模块,但契约文档开始说谎)⚠️ 待确认:团队约定二选一后应删除另一路径的示例,避免双轨漂移。
前置阅读:《05-核心流程》流程三。
src/languages/modules/ 新建 <locale>.ts(如 ja.ts),结构完全复制 zh.ts/en.ts 的命名空间树(约 17 个命名空间,menu/home/login/common/error/appError/...),逐条翻译。两份现有词表各约 990 行,这是本场景 90% 的工作量。src/languages/index.ts 的 resources 加 ja: { translation: ja }。src/stores/modules/global.ts 的 language 类型从 "zh" | "en" | null 扩为三值联合;src/utils/index.ts 的 getBrowserLang 补映射分支(如 ja 开头 → "ja")。src/components/ThemeProvider/index.tsx 的语言 effect 需补两处——dayjs.locale(...) 映射(ja)与 antd ConfigProvider locale 映射(引入 antd 的 jaJP);html[lang] 同步赋值。四件事(i18next/dayjs/antd/html lang)缺一会表现为"翻译变了但日期格式或组件内置文案没变"。验证:语言下拉出现新语言且四类联动全变;刷新后持久化保持(global store persist 自动覆盖)。易错点:词表命名空间漏枝导致该语言下回退显示默认语言(i18next 行为,不报错);antd locale 导错包(必须是 antd/locale/ja_JP)。
前置阅读:《07-模块详解》5(主题双轨制)、《05-核心流程》流程四。
src/stores/modules/global.ts 的 GlobalState 加字段与默认值(persist 自动纳入,无需 migrate——旧缓存缺字段由 zustand 合并默认值,仅当语义需要强制重置时才升 version)。src/components/ThemeProvider/index.tsx 的 useShallow 订阅与 themeConfig useMemo 中消费;CSS 变量轨——语义色进 src/utils/themeTokens.ts(遵守其头注释的三级分层纪律:原始 Token → 语义 Token → 组件 Token,禁止直接使用原始色值),静态/布局类变量进 src/styles/theme/{global,header,sider}.ts 的三态结构,由 useTheme 统一写入 :root。src/layouts/components/ThemeDrawer/index.tsx 加控件,只写 store(setGlobalState({ key, value })),不直接操作 DOM 或 antd。@theme 令牌(src/styles/global.css),或 CSS 变量 var(--xiaoye-*)。验证:切换即时生效、刷新保持、暗黑与四套布局下观感正确。易错点:绕过 store 直接改 DOM(刷新丢失);在 Tailwind 工具类里写死色值导致暗黑失效(用 dark: 变体或令牌);新增原始色未生成明暗两套阶梯。
前置阅读:《06-部署运行》8(六步迁移清单)、《03-接口设计》5。
核心动作收敛为五步:.env.development 配 VITE_PROXY = [["/api", "http://localhost:后端端口"]](build/proxy.ts 自动 rewrite 掉 /api 前缀、支持 ws、https 目标自动 secure: false);main.tsx 的 MSW 在 PROD 构建自动退出(VITE_ENABLE_MOCK !== "true" 短路),无需删代码;用后端 swagger/oneapi 覆盖 openapi/oneapi.json 后 pnpm openapi 重新生成 services;与后端核对三件事——请求头名 x-access-token(非标准 Authorization)、信封 {code,msg,data,showType?} 语义(401 必须走 code 401 或 HTTP 401)、菜单/按钮权限接口的数据结构与 RouteObjectType/权限码形态一致;生产 Nginx 配 /api 反代。安全红线:后端菜单接口失败时前端会清登录态跳登录(绝不回退静态菜单),联调时"接口不通就整站登出"是设计行为,不是 Bug,勿在前端"修复"它。易错点:开发态若后端不可达,菜单接口挂起到 30 秒超时才失败——先用 curl 验证代理连通再排查前端。
该场景还有一个常被漏掉的收尾动作:切换期保留 mock 的回退能力。建议的做法是给 VITE_ENABLE_MOCK 保留独立验证手段(例如临时在 .env.development 注释切换),因为接后端的头两周经常需要"后端还没好的页面继续用 mock 演示"——此时可以把尚未实现的后端接口的 handler 留在 MSW 里,已实现的走代理(MSW 的 onUnhandledRequest: "warn" 放行未拦截请求,天然支持这种混合形态)。等全部接口就绪后再整体关闭 mock,删除 handler 的动作放到生产切换前一次性完成,避免反复拉扯。
前置阅读:《04-数据模型》2 与 5。
useGlobalStore,会话身份进 useUserStore,权限进 useAuthStore,标签进 useTabsStore——不建第五个 store,页面局部状态不进全局。src/stores/interface/index.ts(注释强调"方法由 zustand 自动推导"),不另设类型文件。partialize 范围内;useUserStore 版本演进时必须同步写 migrate(容错旧结构、异常回退默认值、绝不抛错——抛错会导致登录态整体丢失,源码注释原文)。setGlobalState 模式),禁止组件直接 setState。验证:pnpm test 过(防 store 循环依赖——src/utils/menu.ts 因打破 utils ↔ stores 成环而独立成文件,新代码不得反向引入);刷新后持久化字段保持、旧缓存启动不报错。易错点:在 store 里存函数或组件引用(persist 序列化会丢弃);新增字段忘加进 partialize 白名单导致"改了但不持久"。
前置阅读:《01-系统架构》2.5、《07-模块详解》7。
src/layouts/ 新建 LayoutXxx.tsx(结构参照 LayoutVertical.tsx:aside/ header/ main 三区的组合方式可自定,但内容区必须挂 LayoutMain——它是窄屏折叠、Maximize、KeepAliveOutlet 与 Tabs 的宿主,绕开它等于绕开页面缓存与标签体系)。src/layouts/index.tsx 的布局映射表加入新组件,src/stores/modules/global.ts 的 layout 字段类型扩为五值联合;ThemeDrawer 的布局选择控件补选项(含预览缩略图,参照现有四项)。src/styles/theme/{sider,header}.ts 的三态 token(light/inverted/dark),不要为新布局另起色值;Menu 组件的 mode 由布局传入(vertical 用 inline、transverse 用 horizontal),确认新布局下菜单的展开/手风琴行为正常。易错点:把 Header/Menu 渲染在 LayoutMain 之外导致 KeepAliveOutlet 拿不到正确出口;布局切换时未受控的局部 state 残留(四套布局共享同一 LayoutIndex 实例,切换是组件替换而非整页刷新,副作用要挂在组件卸载上)。
无论哪个场景,收尾都过同一张单:pnpm typecheck && pnpm lint && pnpm format:check && pnpm test 全绿;pnpm dev 下手测主流程 + 刷新恢复 + 语言/暗黑两态;涉及接口的三处对齐(api ↔ MSW ↔ openapi)无漂移;涉及生产的(权限、redirect、登出)复核"从紧"语义未被放宽;pnpm build 通过且无新增构建告警。这六个场景覆盖了本框架 95% 的日常扩展需求,剩余长尾(新增布局、新增图表类型、换路由模式)的共同方法也一致:先找到该领域的"唯一事实源"(菜单数据、global store、契约文件),从事实源改起,让派生层自动跟随。