UI 组件开发
编程式场景支持自定义 React 面板组件,实现丰富的用户界面。
组件注册
第一步:定义面板
在 getPlugin() 的 ui.panels 中声明面板:
typescript
getPlugin() {
return {
ui: {
layout: 'dashboard-centric',
panels: [
{
id: 'dashboard',
component: 'DashboardPanel',
region: 'primary',
defaultVisible: true,
resizable: true,
minWidth: 300,
maxWidth: 800,
},
{
id: 'settings',
component: 'SettingsPanel',
region: 'secondary',
defaultVisible: false,
resizable: true,
minWidth: 250,
maxWidth: 500,
},
{
id: 'chat',
component: 'ChatPanel',
region: 'auxiliary',
defaultVisible: true,
resizable: true,
minWidth: 300,
maxWidth: 600,
},
],
},
}
}面板区域
| 区域 | 说明 | 位置 |
|---|---|---|
primary | 主面板 | 中央区域 |
secondary | 次面板 | 左侧或右侧 |
auxiliary | 辅助面板 | 聊天面板 |
bottom | 底部面板 | 终端/输出 |
overlay | 浮层面板 | 弹窗覆盖 |
面板配置
| 字段 | 类型 | 说明 |
|---|---|---|
id | string | 面板唯一标识 |
component | string | 组件名(对应 getComponents 的 key) |
region | string | 面板区域 |
defaultVisible | boolean | 默认是否可见 |
resizable | boolean | 是否可调整大小 |
minWidth | number | 最小宽度(px) |
maxWidth | number | 最大宽度(px) |
minHeight | number | 最小高度(px) |
maxHeight | number | 最大高度(px) |
第二步:注册组件
在 getComponents() 中返回组件映射:
typescript
import { DashboardPanel } from './components/DashboardPanel.js'
import { SettingsPanel } from './components/SettingsPanel.js'
getComponents() {
return {
DashboardPanel,
SettingsPanel,
}
}组件开发
基础组件
tsx
import React from 'react'
export const DashboardPanel: React.FC = () => {
return (
<div className="dashboard-panel">
<h2>仪表盘</h2>
<p>欢迎使用场景仪表盘</p>
</div>
)
}使用共享依赖
组件可以使用客户端注入的共享依赖:
tsx
import React, { useState, useEffect } from 'react'
import { BarChart3, TrendingUp } from 'lucide-react'
interface StatsData {
total: number
completed: number
pending: number
}
export const StatsPanel: React.FC = () => {
const [stats, setStats] = useState<StatsData>({
total: 0,
completed: 0,
pending: 0,
})
useEffect(() => {
// 获取数据
loadStats()
}, [])
const loadStats = async () => {
// 通过场景数据通道获取数据
// 具体实现取决于场景的数据源
}
return (
<div className="stats-panel p-4">
<h3 className="text-lg font-semibold mb-4">统计数据</h3>
<div className="grid grid-cols-3 gap-4">
<div className="stat-card">
<BarChart3 className="w-6 h-6 text-blue-500" />
<span className="text-2xl font-bold">{stats.total}</span>
<span className="text-sm text-gray-500">总计</span>
</div>
<div className="stat-card">
<TrendingUp className="w-6 h-6 text-green-500" />
<span className="text-2xl font-bold">{stats.completed}</span>
<span className="text-sm text-gray-500">已完成</span>
</div>
<div className="stat-card">
<div className="w-6 h-6 text-yellow-500" />
<span className="text-2xl font-bold">{stats.pending}</span>
<span className="text-sm text-gray-500">待处理</span>
</div>
</div>
</div>
)
}数据通信
组件通过数据通道与场景通信:
tsx
import React, { useEffect } from 'react'
export const DataPanel: React.FC = () => {
useEffect(() => {
// 订阅数据通道
const handleData = (event: CustomEvent) => {
console.log('收到数据:', event.detail)
}
window.addEventListener('scenario:data-update', handleData as EventListener)
return () => {
window.removeEventListener('scenario:data-update', handleData as EventListener)
}
}, [])
return <div>Data Panel</div>
}样式
组件使用 TailwindCSS 或自定义 CSS:
tsx
// 使用 TailwindCSS
<div className="flex items-center justify-between p-4 bg-white rounded-lg shadow">
// 或使用 CSS Modules
import styles from './DashboardPanel.module.css'
<div className={styles.container}>仪表盘卡片
场景可以在仪表盘页面展示数据卡片,让用户快速查看关键信息。
声明卡片
在 getPlugin() 的 ui.widgetCards 中声明卡片:
typescript
getPlugin() {
return {
ui: {
layout: 'dashboard-centric',
widgetCards: [
{
id: 'code-reviewer:stats',
icon: 'BarChart3',
label: 'Review Stats',
labelZh: '审查统计',
description: 'Code review statistics',
descriptionZh: '代码审查统计数据',
tier: 'core',
previewComponent: 'StatsCard',
},
{
id: 'code-reviewer:recent',
icon: 'Clock',
label: 'Recent Reviews',
labelZh: '最近审查',
description: 'Recent code reviews',
descriptionZh: '最近的代码审查',
tier: 'enhanced',
previewComponent: 'RecentReviewsCard',
},
],
},
}
}卡片配置
| 字段 | 类型 | 说明 |
|---|---|---|
id | string | 卡片唯一标识,格式:<scenarioId>:<cardName> |
icon | string | lucide 图标名 |
label | string | 英文标题 |
labelZh | string | 中文标题 |
description | string | 英文描述(可选) |
descriptionZh | string | 中文描述(可选) |
tier | 'core' | 'enhanced' | 刷新优先级(可选,默认 'enhanced') |
previewComponent | string | 预览组件名(对应 getComponents() 的 key) |
刷新优先级
core:高频刷新(15秒),适用于核心数据卡片enhanced:低频刷新(60秒),适用于一般数据卡片
实现卡片组件
在 getComponents() 中注册卡片组件:
typescript
import { StatsCard } from './components/StatsCard.js'
import { RecentReviewsCard } from './components/RecentReviewsCard.js'
getComponents() {
return {
StatsCard,
RecentReviewsCard,
}
}卡片组件签名:
tsx
import React from 'react'
interface WidgetCardPreviewProps {
/** 卡片数据 */
data: Record<string, unknown> | null
/** 是否正在加载 */
loading: boolean
/** 手动刷新回调 */
onRefresh: () => void
}
export const StatsCard: React.FC<WidgetCardPreviewProps> = ({
data,
loading,
onRefresh,
}) => {
if (loading) {
return <div className="p-4 animate-pulse">加载中...</div>
}
if (!data) {
return (
<div className="p-4 text-center">
<p>暂无数据</p>
<button onClick={onRefresh}>刷新</button>
</div>
)
}
return (
<div className="p-4">
<h3>统计</h3>
<p>总计: {data.total}</p>
</div>
)
}提供卡片数据
实现 getCardData() 方法为卡片提供数据:
typescript
const module: ScenarioModule = {
// ... 其他配置
getCardData: async (cardId: string) => {
switch (cardId) {
case 'code-reviewer:stats':
return {
total: 156,
approved: 120,
rejected: 24,
pending: 12,
}
case 'code-reviewer:recent':
return {
reviews: [
{ id: 1, title: 'Fix login bug', status: 'approved' },
{ id: 2, title: 'Add user API', status: 'pending' },
],
}
default:
return null
}
},
}卡片生命周期
- 场景激活时,卡片自动注册到仪表盘
- 场景停用时,卡片自动从仪表盘移除
- 卡片数据根据
tier配置自动刷新 - 用户可以手动添加/移除场景卡片
最佳实践
- 组件轻量:保持组件简洁,避免复杂逻辑
- 响应式:使用 TailwindCSS 响应式类
- 性能:避免不必要的重渲染
- 错误边界:添加错误边界处理异常
- 加载状态:处理数据加载中的状态
- 空状态:处理无数据的情况

