样式定制
Vue 3 Widget 提供了多层次的样式定制能力,从简单的类名覆盖到完全自定义渲染。
定制层次
| 层次 | 方式 | 适用场景 |
|---|---|---|
| 1 | CSS 类名覆盖 | 调整颜色、间距、字体等 |
| 2 | style 属性 | 单个组件的行内样式 |
| 3 | 自定义图标 (slots) | 替换内置图标 |
| 4 | scoped slots 渲染 | 完全自定义组件结构 |
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 | 禁用状态 |
更多类名
详见各组件文档的「样式定制」章节。
class 属性
每个组件都支持 class 属性,用于添加自定义类名:
<template>
<ControlPanel class="my-control-panel" />
</template>
<style>
.my-control-panel {
margin-bottom: 20px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
</style>
style 属性
使用 style 属性设置行内样式:
<template>
<ConversationList
:style="{
maxHeight: '400px',
borderRadius: '16px',
backgroundColor: 'rgba(255, 255, 255, 0.9)'
}"
/>
</template>
自定义图标 (Slots)
多个组件支持通过 slots 自定义图标:
ControlPanel 图标
<template>
<ControlPanel>
<template #muteIcon><MuteIcon /></template>
<template #unmuteIcon><UnmuteIcon /></template>
<template #hangupIcon><HangupIcon /></template>
<template #asrIcon><MicIcon /></template>
<template #keyboardIcon><KeyboardIcon /></template>
<template #sendIcon><SendIcon /></template>
</ControlPanel>
</template>
ConversationList 图标
<template>
<ConversationList>
<template #closeIcon><CloseIcon /></template>
</ConversationList>
</template>
RecommendedQuestions 图标
<template>
<RecommendedQuestions>
<template #arrowIcon><ArrowIcon /></template>
</RecommendedQuestions>
</template>
Scoped Slots 渲染
通过 scoped slots 可以完全自定义组件的渲染:
ConversationList
<template>
<ConversationList v-slot="{conversations, latestConversation}">
<div class="my-conversation-list">
<h3>对话历史</h3>
<div
v-for="conv in conversations"
:key="conv.id"
:class="['my-message', `my-message--${conv.type}`]"
>
<span class="speaker">
{{ conv.type === 'client' ? '我' : 'AI' }}
</span>
<p>{{ conv.text }}</p>
</div>
</div>
</ConversationList>
</template>
Conversation
<template>
<Conversation
:conversation="conv"
v-slot="{contents, fullText, type, isCompleted}"
>
<div :class="['custom-bubble', type]">
<p>{{ fullText }}</p>
<span v-if="!isCompleted" class="typing-indicator">...</span>
</div>
</Conversation>
</template>
Subtitle
<template>
<Subtitle v-slot="{speakerName, fullText, isExpanded, toggleExpand}">
<div class="custom-subtitle">
<strong>{{ speakerName }}:</strong>
<span>{{ fullText }}</span>
<button @click="toggleExpand">
{{ isExpanded ? '收起' : '展开' }}
</button>
</div>
</Subtitle>
</template>
Input
<template>
<Input v-slot="{value, setValue, handleSend, disabled, loading}">
<div class="custom-input">
<textarea
:value="value"
@input="e => setValue(e.target.value)"
:disabled="disabled"
placeholder="输入消息..."
/>
<button @click="handleSend" :disabled="loading || !value">
{{ loading ? '发送中...' : '发送' }}
</button>
</div>
</Input>
</template>
StartupScreen
<template>
<StartupScreen :show="showStartup" v-slot="{onStart, isActivating}">
<div class="custom-startup">
<img src="/logo.png" alt="Logo" />
<h1>欢迎使用数字员工</h1>
<button @click="onStart" :disabled="isActivating">
{{ isActivating ? '激活中...' : '开始对话' }}
</button>
</div>
</StartupScreen>
</template>
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. 使用 scoped 样式
Vue SFC 的 scoped 样式避免全局污染:
<template>
<ControlPanel class="my-control-panel" />
</template>
<style scoped>
.my-control-panel {
/* 样式 */
}
/* 深度选择器用于覆盖子组件样式 */
:deep(.apk-control-panel__input) {
border-color: #007bff;
}
</style>
2. 保持原有类名
覆盖样式时保留原有类名,确保响应式等功能正常:
<template>
<!-- ✅ 推荐 -->
<ControlPanel class="my-custom-class" />
<!-- ❌ 避免完全替换内部结构导致功能丢失 -->
</template>
3. 优先使用 CSS 覆盖
简单的样式调整优先使用 CSS 覆盖,而非 scoped slots 渲染:
/* ✅ 推荐:简单的样式调整 */
.apk-conversation--ai {
background: #f0f0f0;
}
<template>
<!-- ❌ 过度使用:简单样式不需要自定义渲染 -->
<Conversation :conversation="conv" v-slot="{fullText}">
<div style="background: #f0f0f0">{{ fullText }}</div>
</Conversation>
</template>
4. 渐进式定制
从最简单的方式开始,按需升级:
- 先尝试
class+ CSS - 不够用再尝试
style属性 - 需要结构变化时使用 scoped slots 渲染