这个项目封装了哪些"组件层级"
-
**基础 UI 组件层**:`src/components/ui/*`(`button`/`dialog`/`select`/`popover`...),偏"设计系统",稳定、复用广。
-
**业务通用组件层**:`src/components/shared/*`(`ConfirmDialog`、`ChatInputShell`、`StableUserAvatar`...),跨页面复用。
-
**页面/模块组件层**
-
**Dashboard**:`DashboardPage`、`DashboardTopBar`、`DashboardWorkspaceListPanel`
-
**Workspace(核心)**:`ChatPanel/*`、`CanvasPanel/*`、`FileSystem/*`、`PreviewPanel/*`、`SkillBuilder/*`
我觉得最"牛"的:**资产槽位系统(Asset Slot System)+ `AssetSlotPanel`**
它不是单纯 UI 封装。它是**配置驱动的"输入协议"**。解决一个很真实的问题:不同模型/工具对素材输入的**角色、数量、类型**都不一样。
-
**你要的"插槽"在这里是业务插槽**:首帧、尾帧、参考图、参考视频、音频等。
-
**你要的"通信"在这里是跨组件状态协作**:工具切换、素材引用、上传、交换、校验、发送。
关键点 1:把"工具差异"收敛成配置
-
**配置文件**:`src/components/Toolbox/constants/assetSlots.ts`
-
**UI 组件**:`src/components/Workspace/ChatPanel/AssetSlotPanel.tsx`(统一渲染/上传/交换/删除)
-
**状态层**:`src/components/Workspace/stores/uiStore.ts`(槽位化引用 + 扁平引用同步)
关键点 2:封装后如何保证 API 兼容性(这里是最能讲的)
这个项目用的是**双轨兼容**:
-
**新 API(槽位化)**:`slottedReferences: Record<slotId, nodeId[]>`
-
**旧 API(扁平)**:`referencedNodeIds: nodeId[]`
所有增删改都保证两者同步。旧逻辑不用改,也能继续跑;新逻辑逐步迁移即可。
最关键的一段就是 `uiStore.addReference`:在 tool 模式下优先进槽位;否则进扁平数组。
```251:302:c:\Users\派大鑫\Desktop\新建文件夹\loop\src\components\Workspace\stores\uiStore.ts
// ---------- 引用 ----------
referencedNodeIds: [],
slottedReferences: {},
addReference(refId) {
set((s) => {
const { activeToolId, interactionMode } = useToolStore.getState();
// ... 解析 refType ...
if (interactionMode === 'tool' && activeToolId && refType) {
const config = getToolAssetSlotConfig(activeToolId);
if (config) {
for (const slot of config.slots) {
if (slot.acceptType === refType) {
const current = s.slottedReferences[slot.slotId] || [];
if (current.length < slot.max && !current.includes(refId)) {
if (!s.slottedReferences[slot.slotId]) {
s.slottedReferences[slot.slotId] = [];
}
s.slottedReferences[slot.slotId].push(refId);
s.referencedNodeIds = Object.values(
s.slottedReferences
).flat();
return;
}
}
}
return;
}
}
if (!s.referencedNodeIds.includes(refId)) {
s.referencedNodeIds.push(refId);
}
});
},
```
**这就是"封装组件后 API 兼容性"的硬核答案**:
-
**不删旧接口**,而是用"兼容层"桥接。
-
**状态单一事实来源**在 store 内部维护,外部调用者感知最小。
面试官怎么讲(建议用 60 秒版本)
-
**问题**:不同 AI 工具对素材输入要求不同,原来会写很多 if/else,工具一多就爆炸;切换工具还会丢素材或校验混乱。
-
**做法**:做了"资产槽位系统"。用配置描述每个工具的输入槽位(类型/最小/最大/可交换)。UI 用一个通用组件渲染。store 负责分配、交换、迁移、校验。
-
**兼容**:保留旧的扁平引用 `referencedNodeIds`,新增槽位化 `slottedReferences`,在 store 内同步,保证旧链路不改也能跑。
-
**收益**:新增工具的素材输入适配从"改 UI + 改校验 + 改拼参"收敛为"加一份配置";减少重复代码与回归风险。
你说的"通信 + 插槽"对吗?
-
**对,但要区分两种"插槽"**:
-
**业务插槽**:上面这个资产槽位(首帧/尾帧/参考图)。这是本项目的核心亮点。
-
**React 插槽**:`children`/render props 这种组件组合方式。这里当然也用,但面试价值没"业务插槽系统"高。
-
**通信方式(你可以直接说)**:Zustand store 做"模块间总线"。
-
`toolStore` 提供当前工具与模式
-
`uiStore` 管引用与槽位
-
`AssetSlotPanel` 只做交互与展示,不自己写规则
简历可直接写的关键词与表述
-
**关键词**:React、TypeScript、Zustand、配置驱动(Config-driven UI)、Backward Compatibility、状态同步、模块解耦、可扩展输入协议、复杂表单/素材引用系统
-
**表述句式**(不编数字版):
-
"设计并落地配置驱动的资产槽位系统,将不同模型的素材输入差异抽象为统一协议(slotId/acceptType/min/max),并实现工具切换时的自动迁移与校验。"
-
"通过双轨兼容(slottedReferences + referencedNodeIds)构建兼容层,保障旧调用方无感升级,降低重构风险。"
如果你愿意,我也可以把这套"60 秒版本"再压缩成面试口播稿(更像真实对话)。