插件工作台卡片功能升级方案
版本:v1.0 | 日期:2026-09-16 | 状态:规划中
一、现状分析
1.1 仪表盘页面架构
核心组件: WorkbenchHome.tsx(macOS 桌面小组件风格)
┌─────────────────────────────────────────────────────────────┐
│ 顶部栏:问候语 + 日期 + 场景模式切换(工作/生活/学习)+ 自定义按钮 │
├─────────────────────────────────────────────────────────────┤
│ 卡片墙 │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ 操作卡 │ │ 工具卡 │ │ 插件卡 │ │ ... │ │
│ │ 新建任务 │ │ 待办清单 │ │ 自定义 │ │ │ │
│ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │
├─────────────────────────────────────────────────────────────┤
│ 添加卡片弹层(CardLibrary) │
└─────────────────────────────────────────────────────────────┘1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
1.2 卡片类型
| 类型 | 说明 | 示例 |
|---|---|---|
action | 操作卡 - 固定功能入口 | 新建任务、打开文件夹、场景市场 |
tool | 工具卡 - 内置数据摘要 | 待办清单、周报、番茄钟 |
plugin | 插件卡 - 插件自定义数据 | 天气卡片、宏录制统计 |
1.3 现有插件卡片支持
已实现 ✅:
PluginWidgetCardContribution类型定义完整PluginCardPreview组件(自动刷新、手动刷新、IntersectionObserver)resolveCardMeta()支持解析插件卡片pluginUiRegistry.getWidgetCards()获取插件卡片- 刷新策略分层(core 15s / enhanced 60s)
缺失 ❌:
CardLibrary中没有显示插件卡片,只显示通用操作卡和当前模式工具卡- 用户无法从卡片库添加插件提供的卡片
二、升级方案
2.1 CardLibrary 增强
目标: 在添加卡片弹层中显示插件卡片,按插件分组展示
修改文件: aweeclaw-client/src/renderer/components/workbench/WorkbenchHome.tsx
CardLibrary 组件改动
typescript
// 新增:获取所有可用的插件卡片
const pluginCards = useMemo(() => {
const result: Array<{
pluginKey: string
cardId: string
label: string
labelZh: string
icon: string
modes?: SceneMode[]
}> = []
// 遍历所有已发现的插件
for (const [pluginKey, contribution] of pluginUiRegistry.discovered) {
if (!contribution.contributes.widgetCards?.length) continue
for (const wc of contribution.contributes.widgetCards) {
// 模式过滤
if (wc.modes?.length && !wc.modes.includes(mode)) continue
result.push({
pluginKey,
cardId: wc.id,
label: wc.label,
labelZh: wc.labelZh,
icon: wc.icon,
modes: wc.modes,
})
}
}
return result
}, [mode])1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
渲染插件卡片分组
typescript
{/* 插件卡片 */}
{pluginCards.length > 0 && (
<div className="mb-5">
<div className="flex items-center gap-2 mb-2.5">
<Blocks className="w-3.5 h-3.5 text-purple-500/70" strokeWidth={1.8} />
<span className="text-xs font-semibold text-text-secondary">
{isZh ? '插件卡片' : 'Plugin Cards'}
</span>
<div className="flex-1 h-px bg-border/30" />
</div>
<div className="grid grid-cols-2 sm:grid-cols-3 gap-2">
{pluginCards.map((card) => {
const Icon = getLucideIcon(card.icon) || Blocks
const idx = TOOL_COLORS.length > 0 ? Math.abs(hashCode(card.cardId)) % TOOL_COLORS.length : 0
return renderItem({
id: card.cardId,
icon: Icon,
titleZh: card.labelZh ?? card.label,
titleEn: card.label,
color: TOOL_COLORS[idx],
})
})}
</div>
</div>
)}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
2.2 插件发现机制优化
问题: 当前 pluginUiRegistry.discovered 是私有属性,CardLibrary 无法直接访问
方案 A: 添加公开方法 getAllWidgetCards(mode?: string)
typescript
// PluginUiRegistry.ts
getAllWidgetCards(mode?: SceneMode): Array<{
pluginKey: string
cardId: string
contribution: PluginWidgetCardContribution
}> {
const result = []
for (const [pluginKey, contribution] of this.discovered) {
if (!contribution.contributes.widgetCards?.length) continue
for (const wc of contribution.contributes.widgetCards) {
if (wc.modes?.length && mode && !wc.modes.includes(mode)) continue
result.push({
pluginKey,
cardId: wc.id,
contribution: wc,
})
}
}
return result
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
方案 B: 通过 Hook 订阅(推荐)
typescript
// usePluginExtensions.ts
export function usePluginWidgetCards(mode?: SceneMode) {
return useSyncExternalStore(
pluginUiRegistry.subscribe.bind(pluginUiRegistry),
() => pluginUiRegistry.getAllWidgetCards(mode),
)
}1
2
3
4
5
6
7
2
3
4
5
6
7
2.3 完整交互流程
用户点击 "+" 添加卡片
↓
打开 CardLibrary
↓
┌─────────────────────────────────────┐
│ 通用操作卡(ACTION_CARDS) │
│ ├─ 新建任务 │
│ ├─ 打开文件夹 │
│ └─ ... │
├─────────────────────────────────────┤
│ 工作工具(当前模式) │
│ ├─ 待办清单 │
│ ├─ 工作周报 │
│ └─ ... │
├─────────────────────────────────────┤
│ 插件卡片(新增) │
│ ├─ 天气卡片 (weather-widget) │
│ ├─ 宏统计 (ai-macro-recorder) │
│ └─ ... │
└─────────────────────────────────────┘
↓
用户选择插件卡片
↓
添加到 workbenchWidgets
↓
卡片墙显示 PluginCardPreview
↓
自动调用 getCardData 获取数据1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
三、实现清单
3.1 PluginUiRegistry 改动
| 改动 | 文件 | 说明 |
|---|---|---|
添加 getAllWidgetCards() 方法 | PluginUiRegistry.ts | 公开获取所有可用插件卡片 |
优化 ensureWidgetCardsLoaded() | PluginUiRegistry.ts | 确保 CardLibrary 打开时卡片已加载 |
3.2 WorkbenchHome 改动
| 改动 | 文件 | 说明 |
|---|---|---|
| CardLibrary 添加插件卡片分组 | WorkbenchHome.tsx | 渲染插件卡片列表 |
使用 usePluginWidgetCards Hook | WorkbenchHome.tsx | 响应式获取插件卡片 |
3.3 usePluginExtensions 改动
| 改动 | 文件 | 说明 |
|---|---|---|
添加 usePluginWidgetCards Hook | usePluginExtensions.ts | 订阅插件卡片变化 |
四、用户体验优化
4.1 空状态提示
当没有插件卡片时,显示引导:
typescript
{pluginCards.length === 0 && (
<div className="text-center py-4 text-[11px] text-text-muted">
<Blocks className="w-6 h-6 mx-auto mb-2 opacity-30" />
<p>{isZh ? '暂无插件卡片' : 'No plugin cards available'}</p>
<p className="mt-1">
{isZh ? '安装支持卡片的插件后将在此显示' : 'Install plugins with card support to see them here'}
</p>
</div>
)}1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
4.2 卡片来源标识
在 CardLibrary 中显示插件名称,便于用户识别来源:
typescript
<div className="text-[10px] text-text-muted mt-0.5">
{pluginKey}
</div>1
2
3
2
3
4.3 模式过滤提示
当插件卡片被模式过滤时,显示提示:
typescript
{filteredCount > 0 && (
<p className="text-[10px] text-text-muted mt-2">
{isZh
? `还有 ${filteredCount} 个卡片在其他模式下可用`
: `${filteredCount} more cards available in other modes`
}
</p>
)}1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
五、兼容性考虑
5.1 向后兼容
- 未声明
widgetCards的插件不受影响 - 已添加的插件卡片继续正常工作
- CardLibrary 新增分组不影响现有布局
5.2 性能优化
- 插件卡片列表通过
useMemo缓存 - 避免在 CardLibrary 打开时重复加载 ui.js
- 使用 IntersectionObserver 暂停不可见卡片刷新
六、测试用例
6.1 功能测试
| 测试项 | 预期结果 |
|---|---|
| 无插件时打开 CardLibrary | 不显示插件卡片分组 |
| 安装有 widgetCards 的插件 | CardLibrary 显示对应卡片 |
| 切换场景模式 | 只显示当前模式的插件卡片 |
| 添加插件卡片 | 卡片墙正确显示 PluginCardPreview |
| 移除插件卡片 | 卡片从卡片墙消失 |
6.2 边界测试
| 测试项 | 预期结果 |
|---|---|
| 插件卸载后 | 已添加的卡片自动移除或显示错误状态 |
| 插件更新后 | 卡片自动刷新 |
| 网络断开 | 卡片显示离线状态,不报错 |
七、后续扩展
7.1 卡片配置
支持插件卡片的用户自定义配置:
typescript
interface WidgetCardConfig {
/** 刷新间隔覆盖 */
refreshInterval?: number
/** 显示/隐藏标题 */
showTitle?: boolean
/** 自定义参数传递给 getCardData */
customParams?: Record<string, unknown>
}1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
7.2 卡片事件
支持卡片与宿主的事件通信:
typescript
interface WidgetCardEvent {
type: 'click' | 'hover' | 'action'
cardId: string
payload?: unknown
}
// 插件可通过 host 发送事件
host.emitEvent({ type: 'click', cardId: 'weather-widget:current' })1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
7.3 卡片联动
支持卡片之间的数据联动:
typescript
// 插件可以订阅其他卡片的数据变化
host.subscribeCard('todo-list:count', (data) => {
// 根据待办数量调整显示
})1
2
3
4
2
3
4
附录:相关文件
| 文件 | 说明 |
|---|---|
aweeclaw-client/src/renderer/components/workbench/WorkbenchHome.tsx | 仪表盘主组件 |
aweeclaw-client/src/renderer/plugins/PluginUiRegistry.ts | 插件 UI 注册表 |
aweeclaw-client/src/renderer/plugins/usePluginExtensions.ts | 插件扩展 Hook |
aweeclaw-client/src/renderer/plugins/types.ts | 插件类型定义 |
aweeclaw-client/src/shared/plugin-sdk/types.ts | 插件 SDK 类型 |

