Vite 6 + React 18 + TypeScript(strict)+ Ant Design 5 +
@ant-design/pro-components+ Zustand 5 + Axios + SCSS 的后台管理平台。
BrowserRouter SPA,包含多级路由菜单、登录 / 退出与登录态校验;日期库为 Day.js(已配置中文 locale)。
npm install # 安装依赖
npm run dev # 启动开发服务器(http://localhost:5173)
npm run typecheck # TypeScript 类型检查
npm run build # 类型检查 + 生产构建
npm run preview # 预览生产构建
改动代码后至少执行 npm run typecheck;涉及构建配置或样式时再执行 npm run build。
src/
├── api/ # request.ts(axios 封装)、auth.ts、authHeaders.ts
├── components/ # 通用组件(AntdAppBridge 等)
├── layouts/ # BasicLayout:侧边栏、头部、内容区、面包屑
├── pages/ # 页面,按业务域分目录(页面目录结构见「三、3」)
├── router/ # routeConfig.tsx(路由 + 菜单唯一数据源)、menu.tsx、index.tsx
├── store/ # Zustand 登录态
├── styles/ # 全局 SCSS、通用页面样式
├── types/ # 全局类型(PageResult 等)
├── utils/ # 工具(feedback.ts 全局错误提示)
└── main.tsx # ConfigProvider + AntdApp + BrowserRouter
routeConfig.tsx 是唯一数据源,配置 path、title、icon、element 后侧边栏与面包屑自动生效。支持 2-3 级嵌套;分组路由(无 element)通过 <Outlet /> 渲染并默认跳转到第一个子页面;未匹配路由显示 404。lazy(() => import('@/pages/...')) 注册,BasicLayout 的内容区已提供 <Suspense> 边界;新增页面不要用静态 import,避免所有页面打进入口 chunk。src/store/auth.ts 持久化 token 与 user;src/api/auth.ts 调用 POST /auth/tenant-login({ phone, password, tenantCode })。未登录访问受保护页面跳转 /login,登录成功后返回原地址。--app-primary、--app-bg、--app-border、--app-text、--app-text-secondary;通用类 .page-header、.page-toolbar、.filter-card、.table-card、.text-muted;Ant Design 主题在 main.tsx 的 ConfigProvider 统一配置。组件、工具函数、Hook、事件处理函数都使用箭头函数,不新增 function 声明。
const UserList = () => {
const handleSubmit = async () => {
// ...
};
return <Table />;
};
export default UserList;
写功能前先找现成能力,不要重复造轮子:
BasicLayout、登录校验 RequireAuth、提示与确认框使用 AntdApp.useApp() 的 message / modal(**不要**用 message.success() / Modal.confirm() 静态方法)。ProFormSelect / ProFormTreeSelect / ProFormCascader,不要用原生控件包 Form.Item;静态枚举不受此限制。远程搜索用 request(配合 debounceTime),已有数据通过 options / fieldProps 传入,校验规则直接写在组件的 rules 上。// ✅ 选项来自接口
<ProFormSelect
name="leaderId"
label="负责人"
showSearch
debounceTime={300}
request={async ({ keyWords }) => getLeaderOptions({ keyword: keyWords })}
/>
// ❌ 原生控件包在 Form.Item 中
<Form.Item name="leaderId" label="负责人">
<Select options={leaderOptions} />
</Form.Item>
src/components/ 下新增。variant="borderless",不用 bordered={false})。components/,超长页面拆分components/,文件名使用大驼峰;组件私有 .scss 与组件同目录,由组件自身引入。props;接口调用仍放在页面目录的 service.ts,由页面或组件按需调用;不要跨页面引用其他页面目录下的组件。index.tsx)超过 300 行必须拆分,按功能块抽取到 components/;超过 300 行的复杂组件同样按功能拆分,不要在同一个文件里堆叠多个组件。src/pages/system/users/
├── index.tsx # 页面组件(尽量精简)
├── columns.tsx # 表格列定义
├── types.ts # 页面类型
├── service.ts # 接口调用
├── utils.ts # 按需
├── Users.scss # 页面样式
└── components/
├── UserFormDrawer.tsx
└── UserDetailDrawer.tsx
新增页面统一使用 /new-page skill(.claude/skills/new-page/):它会选择表格页 / 非表格页分支并整体套用模板,再完成代码规范、路由注册、文档更新与类型检查。不要手工复制模板或另起方式搭脚手架。
/new-page 根据 <接口文档路径>,添加一个【页面名】页面,放到【分组名】目录下
仅改动现有页面(如调整某一列)等不涉及脚手架的场景才直接改代码。新页面必须在 src/router/routeConfig.tsx 用完整路径注册(path、title、icon、element,需要分组时使用 children),页面组件使用 lazy(() => import(...)) 懒加载,完成后执行 npm run typecheck。
以下四条为强制要求:
src/api/request.ts:统一通过其导出的 request 发起请求,不要在页面、组件或 service.ts 中直接使用 axios、fetch 或其他请求库。interface:在 service.ts 中通过 request<TReq, TRes> 显式标注泛型,禁止使用 any 或省略类型。types.ts(跨页面复用的放到 src/api/ 对应业务域文件)。<业务>Request,响应为 <业务>Response,响应对应接口返回 data 的结构(后端为 Result<T> 时取 data 部分)。type TenantRoleListRequest = Record<string, never>。```ts
// types.ts
export interface TenantRoleToggleStatusRequest {
id: number;
}
export interface TenantRoleToggleStatusResponse {
id: number;
status: RoleStatus;
}
// service.ts
export const toggleRoleStatus = ({ id }: TenantRoleToggleStatusRequest) =>
request<TenantRoleToggleStatusRequest, TenantRoleToggleStatusResponse>({
url: /tenant/roles/${id}/toggle-status,
method: 'PUT',
headers: getAuthHeaders(),
});
```
.then 链式处理,禁止 await + try/catch:成功逻辑写在 .then,失败逻辑写在 .catch(错误已由 request 统一提示时可留空),收尾逻辑写在 .finally。```ts
// ✅
getOrgList({ page: 1, size: 20 })
.then((data) => {
setDataSource(data.records);
})
.catch(() => {
// 错误已由 request 统一提示
})
.finally(() => {
setLoading(false);
});
// ❌
try {
const data = await getOrgList({ page: 1, size: 20 });
setDataSource(data.records);
} catch {
// ...
} finally {
setLoading(false);
}
```
service.ts,页面与组件通过它调用,不要在组件内直接写请求实现。src/api/ 下(如 src/api/auth.ts)。补充说明:
request<TReq, TRes>(config, options):泛型分别表示请求参数 / 请求体与响应 data 的类型;默认只返回 data,parse: false 返回完整 AxiosResponse;失败抛出 ApiError(含 status、data、response、config);默认合并相同并发请求(merge: false 关闭);showError: false 关闭自动提示;signal 取消当前调用。src/api/auth.ts 调用真实登录接口,登录成功后由 src/store/auth.ts 写入登录态;全局登录态只放在 src/store/auth.ts,页面局部状态使用 useState / useMemo。style。.user-cell__name)。aria-label。scroll={{ x: ... }},左右布局使用栅格或 flex 换行。index.html;构建产物在 dist/。/proxy-api 转发到 VITE_API_PROXY_TARGET(未配置时取 VITE_API_BASE_URL),浏览器端不会跨域;生产环境需后端开启 CORS,或将前端与接口部署在同一域名下由反向代理转发。vite.config.ts 的 manualChunks 将 React、antd 核心、其余依赖拆成长缓存 chunk,ProComponents 及业务页面依赖由 Rollup 按需拆分为异步 chunk。新增页面时保持懒加载即可,无需再调整拆包配置。