样式定制
React Widget 提供了多层次的样式定制能力,从简单的类名覆盖到完全自定义渲染。
定制层次
| 层次 | 方式 | 适用场景 |
|---|---|---|
| 1 | CSS 类名覆盖 | 调整颜色、间距、字体等 |
| 2 | style 属性 | 单个组件的行内样式 |
| 3 | 自定义图标 | 替换内置图标 |
| 4 | children 渲染 | 完全自定义组件结构 |
CSS 类名覆盖
命名规范
所有组件使用 BEM 命名规范,前缀为 apk-(AaaS Pilot Kit):
.apk-{组件名} /* 块 */
.apk-{组件名}__{元素名} /* 元素 */
.apk-{组件名}--{修饰符} /* 修饰符 */
示例
/* 修改控制面板背景 */
.apk-control-panel {
background: rgba(0, 0, 0, 0.8);
}
/* 修改输入框样式 */
.apk-control-panel__input {
border: 2px solid #007bff;
border-radius: 25px;
}
/* 修改移动端按钮大小 */
.apk-control-panel--mobile .apk-control-panel__mic-btn {
width: 60px;
height: 60px;
}
组件类名速查
PilotKit
| 类名 | 说明 |
|---|---|
.apk-pilotkit | 根容器 |
.apk-pilotkit__content | 内容区域 |
ConversationList
| 类名 | 说明 |
|---|---|
.apk-conversation-list | 根容器 |
.apk-conversation-list--mobile | 移动端样式 |
.apk-conversation-list--desktop | 桌面端样式 |
.apk-conversation-list__inner | 内层滚动容器 |
.apk-conversation-list__follow-button | 滚动跟随按钮 |
Conversation
| 类名 | 说明 |
|---|---|
.apk-conversation | 根容器 |
.apk-conversation--client | 用户消息 |
.apk-conversation--ai | AI 消息 |
.apk-conversation__content-text | 文字内容 |
ControlPanel
| 类名 | 说明 |
|---|---|
.apk-control-panel | 根容器 |
.apk-control-panel--mobile | 移动端样式 |
.apk-control-panel__inner | 内层容器 |
.apk-control-panel__mode-btn | 模式切换按钮 |
.apk-control-panel__input | 输入框 |
.apk-control-panel__send-btn | 发送按钮 |
.apk-control-panel__mic-btn | 麦克风按钮 |
.apk-control-panel__hangup-btn | 挂断按钮 |
Input
| 类名 | 说明 |
|---|---|
.apk-input | 根容器 |
.apk-input--mobile | 移动端样式 |
.apk-input__field | 输入框 |
.apk-input__send-icon | 发送图标 |
.apk-input--error | 错误状态 |
.apk-input--disabled | 禁用状态 |
更多类名
详见各组件文档的「样式定制」章节。
className 属性
每个组件都支持 className 属性,用于添加自定义类名:
<ControlPanel className="my-control-panel" />
.my-control-panel {
margin-bottom: 20px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
style 属性
使用 style 属性设置行内样式:
<ConversationList
style={{
maxHeight: 400,
borderRadius: 16,
backgroundColor: 'rgba(255, 255, 255, 0.9)'
}}
/>
自定义图标
多个组件支持自定义图标:
ControlPanel 图标
<ControlPanel
muteIcon={<MuteIcon />}
unmuteIcon={<UnmuteIcon />}
hangupIcon={<HangupIcon />}
asrIcon={<MicIcon />}
keyboardIcon={<KeyboardIcon />}
sendIcon={<SendIcon />}
/>
ConversationList 图标
<ConversationList
closeIcon={<CloseIcon />}
/>
RecommendedQuestions 图标
<RecommendedQuestions
arrowIcon={<ArrowIcon />}
/>
children 渲染
通过 children 函数可以完全自定义组件的渲染:
ConversationList
<ConversationList>
{({conversations, latestConversation}) => (
<div className="my-conversation-list">
<h3>对话历史</h3>
{conversations.map(conv => (
<div
key={conv.id}
className={`my-message my-message--${conv.type}`}
>
<span className="speaker">
{conv.type === 'client' ? '我' : 'AI'}
</span>
<p>{conv.text}</p>
</div>
))}
</div>
)}
</ConversationList>
Conversation
<Conversation conversation={conv}>
{({contents, fullText, type, isCompleted}) => (
<div className={`custom-bubble ${type}`}>
<p>{fullText}</p>
{!isCompleted && <span className="typing-indicator">...</span>}
</div>
)}
</Conversation>
Subtitle
<Subtitle>
{({speakerName, fullText, isExpanded, toggleExpand}) => (
<div className="custom-subtitle">
<strong>{speakerName}:</strong>
<span>{fullText}</span>
<button onClick={toggleExpand}>
{isExpanded ? '收起' : '展开'}
</button>
</div>
)}
</Subtitle>
Input
<Input>
{({value, setValue, handleSend, disabled, loading}) => (
<div className="custom-input">
<textarea
value={value}
onChange={e => setValue(e.target.value)}
disabled={disabled}
placeholder="输入消息..."
/>
<button onClick={handleSend} disabled={loading || !value}>
{loading ? '发送中...' : '发送'}
</button>
</div>
)}
</Input>
StartupScreen
<StartupScreen show={showStartup}>
{({onStart, isActivating}) => (
<div className="custom-startup">
<img src="/logo.png" alt="Logo" />
<h1>欢迎使用数字员工</h1>
<button onClick={onStart} disabled={isActivating}>
{isActivating ? '激活中...' : '开始对话'}
</button>
</div>
)}
</StartupScreen>
CSS 变量(规划中)
提示
CSS 变量支持正在规划中,未来版本将提供更便捷的主题定制方式。
预期的变量示例:
:root {
--apk-primary-color: #007bff;
--apk-background-color: rgba(255, 255, 255, 0.9);
--apk-text-color: #333;
--apk-border-radius: 20px;
--apk-font-family: -apple-system, BlinkMacSystemFont, sans-serif;
}
主题示例
深色主题
/* 深色主题 */
.dark-theme .apk-conversation-list {
background: rgba(30, 30, 30, 0.95);
}
.dark-theme .apk-conversation--ai {
background: rgba(50, 50, 50, 0.9);
color: #fff;
}
.dark-theme .apk-conversation--client {
background: #007bff;
color: #fff;
}
.dark-theme .apk-control-panel {
background: rgba(40, 40, 40, 0.9);
}
.dark-theme .apk-control-panel__input {
background: rgba(60, 60, 60, 0.9);
color: #fff;
border-color: rgba(100, 100, 100, 0.5);
}
圆角主题
/* 圆角主题 */
.rounded-theme .apk-conversation {
border-radius: 24px;
}
.rounded-theme .apk-control-panel__input {
border-radius: 30px;
}
.rounded-theme .apk-control-panel__mic-btn,
.rounded-theme .apk-control-panel__send-btn {
border-radius: 50%;
}
企业主题
/* 企业品牌主题 */
.corporate-theme .apk-conversation--ai {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: #fff;
}
.corporate-theme .apk-control-panel__mic-btn {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
}
.corporate-theme .apk-subtitle {
border-left: 3px solid #667eea;
padding-left: 12px;
}
最佳实践
1. 使用 CSS 模块或 scoped 样式
避免全局样式污染:
// styles.module.css
.myControlPanel {
/* 样式 */
}
// Component.tsx
import styles from './styles.module.css';
<ControlPanel className={styles.myControlPanel} />
2. 保持原有类名
覆盖样式时保留原有类名,确保响应式等功能正常:
// ✅ 推荐
<ControlPanel className="my-custom-class" />
// ❌ 避免完全替换内部结构导致功能丢失
3. 优先使用 CSS 覆盖
简单的样式调整优先使用 CSS 覆盖,而非 children 渲染:
/* ✅ 推荐:简单的样式调整 */
.apk-conversation--ai {
background: #f0f0f0;
}
// ❌ 过度使用:简单样式不需要自定义渲染
<Conversation conversation={conv}>
{({fullText}) => (
<div style={{background: '#f0f0f0'}}>{fullText}</div>
)}
</Conversation>
4. 渐进式定制
从最简单的方式开始,按需升级:
- 先尝试
className+ CSS - 不够用再尝试
style属性 - 需要结构变化时使用
children渲染