目录结构
text
admin-design/
├── docs/ # 文档站(VitePress):guide/ + components/
├── public/brand/ # 品牌资源,业务系统直接覆盖同名文件
├── src/
│ ├── app/ # 外壳接线:路由、布局、Providers、示例菜单
│ ├── components/ # 公共组件(外壳、页型零件、图表、设置面板)
│ │ └── ui/ # 基础控件(button/input/table/tabs/dialog…)
│ ├── hooks/ # 通用 hooks
│ ├── lib/ # 纯逻辑:主题偏好、i18n、树、分页、指标、表格偏好…
│ ├── pages/ # 标准页型示例:工作台、列表、详情、设置、通知、登录注册
│ │ └── design-system/ # 组件总览(按类型拆分)
│ ├── test/ # 全部测试:unit / components / e2e
│ └── index.css # 语义 Token 的唯一来源
├── .githooks/pre-commit # 提交前校验
└── .github/workflows/ci.yml # CI:check + e2e1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
判断标准
新增代码前先回答一个问题:这段代码服务谁?
| 代码性质 | 落点 |
|---|---|
| 能给任意业务复用的通用控件 | src/components/ui/ |
| 带后台语义但不绑定具体业务(表格、树、图表、设置面板、页型零件) | src/components/ |
| 纯函数 / 纯数据(无 React) | src/lib/ |
| 只服务某个页面 | src/pages/ |
| 外壳接线(路由、布局、Providers、菜单) | src/app/ |
需要 UI 状态时才用 Zustand,并且放在 lib/ 里;服务端数据不进 Zustand,也不写进 localStorage。
命名约定
- 文件名 kebab-case:
data-table.tsx、password-strength.tsx。 - 组件名 PascalCase,导出用命名导出(
export function DataTable),不使用默认导出。 - 基础控件沿用 shadcn 风格:
src/components/ui/button.tsx导出Button与buttonVariants。 - 测试文件与被测对象同名:
src/test/components/data-table.test.tsx对应src/components/data-table.tsx。 - 文档页与组件分类页一一对应:
docs/components/data.md↔/design-system/data。
新增一个页面要动哪几处
src/pages/xxx-page.tsx写页面(用PageHeader+ 页面零件组装)。src/App.tsx注册路由,懒加载(lazy(() => import(...)))。src/app/navigation.ts把路径登记到navigation,并在navigationGroups的paths里声明分组与顺序。- 文案进
src/lib/i18n.ts(中英同步),标题可以走pageTitles。 - 补测试:页面级交互放
src/test/e2e/,纯逻辑放src/test/unit/。
如果页面是后台菜单下发的(不需要在前端登记菜单),跳过第 3 步:页面放在 src/pages/ 下, 接口用组件键(例如 reports-page)引用它即可,见动态菜单与权限。
新增一个公共组件要动哪几处
模板要求"三件套",缺一不可:
- 组件本体:
src/components/(通用控件放ui/)。 - 组件总览示例:加到对应分类页(
src/pages/design-system/*.tsx),可交互。 - 至少一处真实落点:标准页型里真的用上,而不是只存在于总览。
- 测试:
src/test/components/或src/test/unit/,交互复杂再加 Playwright。 - 文档:在
docs/components/<分类>.md补一节(props 表 + 用法)。