组件边界
判断组件应该放在 apps/admin、页面 modules、features,还是 packages/web/ui 和后台共享包
这一页说明 apps/admin 里组件应该放在哪里。旧文档里的“通用组件”清单不能直接搬过来,因为当前很多能力已经拆进 packages/web/ui/*、@skyroc/web-admin-layouts、@skyroc/web-admin-theme 和 @skyroc/web-admin-notification。
判断组件归属时先问两个问题:
- 这个组件是否知道
apps/admin的具体页面、路由、接口、权限、用户信息或资源。 - 这个组件是否可以在另一个后台应用中只通过 props / config 复用。
如果第一个问题是“是”,通常留在 apps/admin。如果第二个问题是“是”,再考虑沉淀到共享包。
当前实现位置
| 位置 | 职责 |
|---|---|
apps/admin/src/components | admin 应用壳组件,例如 SystemLogo、SkyrocAvatar。 |
apps/admin/src/pages/**/modules | 页面私有模块,例如首页图表、banner、新闻列表。 |
apps/admin/src/features/*/components | 某个应用能力内部的组件,例如认证头像、菜单 extra。 |
packages/web/ui/antd/src/components | Ant Design 组合组件,例如 ButtonIcon、ButtonLink、ErrorBoundary、ExceptionBase。 |
packages/web/ui/compose/src/components | 无状态组合组件,例如 SvgIcon、LazyAnimate、Portal、NumberTicker。 |
packages/web/admin-layouts/src | 后台布局壳、菜单、tabs、主题抽屉和 slot 能力。 |
packages/web/admin-notification/src | header 通知按钮、通知面板和通知 Provider。 |
放置规则
| 要做的事 | 推荐位置 | 原因 |
|---|---|---|
| 页面内部拆小块 | 当前页面目录的 modules | 只服务一个页面,不污染全局组件目录。 |
| 后台壳插槽组件 | apps/admin/src/components 或对应 feature | 知道 admin 的 logo、头像、用户信息或资源。 |
| 多个 admin 页面复用的业务组件 | apps/admin/src/features/{domain} | 仍然依赖当前应用业务约定。 |
| 通用按钮、错误页、图标封装 | packages/web/ui/antd 或 packages/web/ui/compose | 不应该知道 admin 的路由或接口。 |
| 菜单、tabs、layout 行为 | packages/web/admin-layouts | 属于后台壳基础能力。 |
| 主题、message、modal、notification holder | packages/web/admin-theme | 需要和主题、Ant Design Provider 绑定。 |
当前 admin 壳组件
apps/admin/src/pages/(admin)/layout.tsx 展示了当前最典型的组件边界:
<WebAdminLayout
footer={<AdminFooter />}
headerMiddleActions={<NotificationButton className="px-12px" />}
headerRightActions={<UserAvatar />}
logo={<SystemLogo className="text-32px text-primary" />}
logoTitle={t('system.title')}
/>这里的归属关系是:
| 组件 | 来源 | 归属判断 |
|---|---|---|
WebAdminLayout | @skyroc/web-admin-layouts | 通用后台 layout 壳。 |
NotificationButton | @skyroc/web-admin-notification | 通用通知入口,状态由通知 Provider 提供。 |
DarkModeContainer | @skyroc/web-ui-compose | 通用暗色适配容器。 |
SystemLogo | apps/admin/src/components | 使用当前应用的本地 logo 图标。 |
UserAvatar | apps/admin/src/features/auth/components | 读取当前用户、登录态和退出登录路由。 |
AdminFooter | 当前 layout 文件 | 只服务后台 layout,暂时不需要抽成全局组件。 |
UserAvatar 不能直接沉到 packages/web/ui,因为它依赖当前应用的 useAuth()、useUserInfoQuery()、路由跳转和退出登录策略。
页面 modules
首页当前把展示块拆在:
apps/admin/src/pages/(admin)/home/modules这种拆法适合页面私有 UI,例如 dashboard banner、图表、新闻卡片。它们可以知道当前页面的数据结构和布局细节。
当一个模块被多个页面复用时,先判断它是不是业务能力:
| 复用范围 | 建议 |
|---|---|
| 只在当前页面使用 | 留在页面 modules。 |
| 多个 admin 页面使用,但依赖当前业务接口或权限 | 移到 apps/admin/src/features/{domain}。 |
| 多个应用都能使用,且只靠 props 工作 | 考虑移到 packages/web/ui/*。 |
共享 UI 包怎么选
当前有三类常用 UI 包:
| 包 | 适合放什么 |
|---|---|
@skyroc/web-ui-antd | 基于 Ant Design 的组合组件,例如 ButtonIcon、ButtonLink、ErrorBoundary、ExceptionBase。 |
@skyroc/web-ui-compose | 不绑定业务的组合组件,例如 SvgIcon、LazyAnimate、Portal、NumberTicker、TypingAnimation。 |
@skyroc/web-ui | 更基础的 Web UI 能力,优先看现有包结构再扩展。 |
不要因为组件“看起来通用”就立刻放共享包。共享包组件需要更稳定的 props、测试、导出、文档和跨应用兼容性。
新增组件流程
新增组件时按这个顺序判断:
- 是否只服务一个页面:是就放页面
modules。 - 是否依赖当前应用业务数据、权限、路由或 storage:是就放
features或apps/admin/src/components。 - 是否只是某个后台壳 slot:优先放 app 侧,让
WebAdminLayout通过 props 接收。 - 是否可以跨应用复用:再放
packages/web/ui/*,并同步导出、测试和共享包文档。 - 是否改变 layout / menu / tabs 基础行为:再考虑
packages/web/admin-layouts。
最小示例
页面私有组件:
interface UserStatusTagProps {
/** 当前用户启用状态。 */
enabled: boolean;
}
const UserStatusTag = (props: UserStatusTagProps) => {
const { enabled } = props;
return <ATag color={enabled ? 'success' : 'default'}>{enabled ? '启用' : '停用'}</ATag>;
};如果它只出现在用户页面,放在 pages/(admin)/manage/user/modules。如果多个系统管理页面都要使用,并且状态语义属于当前 admin 业务,放到 features。只有当它变成纯 UI 标签、完全不关心业务枚举时,才考虑共享包。
常见误区
| 误区 | 正确做法 |
|---|---|
| 把旧文档的通用组件清单逐个搬进 admin docs | 先查当前源码。很多组件已属于 packages/web/ui/*。 |
页面组件一多就全部放 apps/admin/src/components | 页面私有模块放页面 modules,避免全局目录膨胀。 |
组件依赖 useAuth() 还想放 web-ui | 这类组件属于 admin 应用或 auth feature,不是无业务组件。 |
共享包组件直接读 import.meta.env | 共享包接收普通配置,应用侧负责读取 env。 |
| 沉淀共享包但不补导出和文档 | 共享包能力需要稳定导出、测试和 docs/web-kit-docs 同步。 |
相关链接
- 项目结构
- 布局系统
- 运行时 Provider
- 通知系统
docs/web-kit-docs/content/docs/materials.mdx