
Tdesign Miniprogram
- 8 installs
- 33 repo stars
- Updated April 26, 2026
- bighardperson/computer-science-skills-collection
TDesign Mini Program is a Claude Code skill for building WeChat mini program UIs with Tencent's TDesign component library of 60+ components.
About
TDesign Mini Program is a Claude Code skill for building WeChat mini program UIs with Tencent's TDesign component library. It documents installation, project configuration, theme and dark-mode setup, and the import paths for 60+ components across basic, navigation, input, and data-display categories, including chat components for AI interfaces. A developer uses it when building or styling a WeChat mini program with TDesign.
- TDesign WeChat Mini Program UI component library by Tencent
- 60+ components with dark mode, theme customization, and AI chat components
- Includes install, config, and per-component import-path reference
Tdesign Miniprogram by the numbers
- 8 all-time installs (skills.sh)
- Ranked #1,729 of 2,244 Frontend Development skills by installs in the Skillselion catalog
- Data as of Jul 30, 2026 (Skillselion catalog sync)
tdesign-miniprogram capabilities & compatibility
- Capabilities
- frontend · ui design
- Use cases
- frontend · ui design
- Pricing
- Free
What tdesign-miniprogram says it does
TDesign WeChat Mini Program UI component library by Tencent.
Provides 60+ high-quality components with dark mode support, theme customization, and more.
npm i tdesign-miniprogram -S --production
npx skills add https://github.com/bighardperson/computer-science-skills-collection --skill tdesign-miniprogramAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 8 |
|---|---|
| repo stars | ★ 33 |
| Last updated | April 26, 2026 |
| Repository | bighardperson/computer-science-skills-collection ↗ |
What it does
Build and style WeChat mini program interfaces using the TDesign component library.
Who is it for?
Wiring up and theming TDesign UI components in a WeChat mini program, including AI chat interfaces.
Skip if: Non-WeChat platforms or web apps not using the mini program TDesign library.
When should I use this skill?
You are building a WeChat mini program with TDesign components or configuring its theme/dark mode.
What you get
A correctly configured TDesign mini program with the right components imported and themed.
By the numbers
- 60+ components
- 6 basic components
- 8 navigation components
Files
TDesign Mini Program Skill
TDesign WeChat Mini Program component library, an enterprise-level design system by Tencent. Provides 60+ high-quality components with dark mode support, theme customization, and more.
When to Use This Skill
This skill should be triggered when:
- Developing WeChat Mini Programs with TDesign component library
- Using TDesign UI components (Button, Input, Dialog, etc.)
- Implementing interfaces following TDesign design specifications
- Configuring TDesign themes and style customization
- Building AI chat interfaces (using TDesign Chat components)
- Implementing dark mode adaptation
Quick Start
Installation
npm i tdesign-miniprogram -S --productionModify app.json
Remove "style": "v2" from app.json to avoid style conflicts.
Modify project.config.json
Add the following to the setting section of project.config.json:
{
"setting": {
"packNpmManually": true,
"packNpmRelationList": [
{
"packageJsonPath": "./package.json",
"miniprogramNpmDistDir": "./"
}
]
}
}Modify tsconfig.json (TypeScript projects)
{
"paths": {
"tdesign-miniprogram/*": [
"./miniprogram/miniprogram_npm/tdesign-miniprogram/*"
]
}
}After modifying project.config.json, build npm in WeChat DevTools: Tools - Build npmAfter successful build, check Compile JS to ES5Using Components
Import in page or component JSON file:
{
"usingComponents": {
"t-button": "tdesign-miniprogram/button/button"
}
}Use in WXML:
<t-button theme="primary">Button</t-button>Component Categories
Basic Components (6)
| Component | Description | Import Path |
|---|---|---|
| Button | Button | tdesign-miniprogram/button/button |
| Divider | Divider | tdesign-miniprogram/divider/divider |
| Fab | Floating Action Button | tdesign-miniprogram/fab/fab |
| Icon | Icon | tdesign-miniprogram/icon/icon |
| Layout | Layout | tdesign-miniprogram/row/row |
| Link | Link | tdesign-miniprogram/link/link |
Navigation Components (8)
| Component | Description | Import Path |
|---|---|---|
| BackTop | Back to Top | tdesign-miniprogram/back-top/back-top |
| Drawer | Drawer | tdesign-miniprogram/drawer/drawer |
| Indexes | Index List | tdesign-miniprogram/indexes/indexes |
| Navbar | Navigation Bar | tdesign-miniprogram/navbar/navbar |
| SideBar | Side Navigation | tdesign-miniprogram/side-bar/side-bar |
| Steps | Steps | tdesign-miniprogram/steps/steps |
| TabBar | Bottom Tab Bar | tdesign-miniprogram/tab-bar/tab-bar |
| Tabs | Tabs | tdesign-miniprogram/tabs/tabs |
Input Components (16)
| Component | Description | Import Path |
|---|---|---|
| Calendar | Calendar | tdesign-miniprogram/calendar/calendar |
| Cascader | Cascader | tdesign-miniprogram/cascader/cascader |
| CheckBox | Checkbox | tdesign-miniprogram/checkbox/checkbox |
| DateTimePicker | Date Time Picker | tdesign-miniprogram/date-time-picker/date-time-picker |
| Input | Input | tdesign-miniprogram/input/input |
| Picker | Picker | tdesign-miniprogram/picker/picker |
| Radio | Radio | tdesign-miniprogram/radio/radio |
| Rate | Rate | tdesign-miniprogram/rate/rate |
| Search | Search | tdesign-miniprogram/search/search |
| Slider | Slider | tdesign-miniprogram/slider/slider |
| Stepper | Stepper | tdesign-miniprogram/stepper/stepper |
| Switch | Switch | tdesign-miniprogram/switch/switch |
| Textarea | Textarea | tdesign-miniprogram/textarea/textarea |
| TreeSelect | Tree Select | tdesign-miniprogram/tree-select/tree-select |
| Upload | Upload | tdesign-miniprogram/upload/upload |
| Form | Form | tdesign-miniprogram/form/form |
Data Display Components (18)
| Component | Description | Import Path |
|---|---|---|
| Avatar | Avatar | tdesign-miniprogram/avatar/avatar |
| Badge | Badge | tdesign-miniprogram/badge/badge |
| Cell | Cell | tdesign-miniprogram/cell/cell |
| Collapse | Collapse | tdesign-miniprogram/collapse/collapse |
| CountDown | Countdown | tdesign-miniprogram/count-down/count-down |
| Empty | Empty State | tdesign-miniprogram/empty/empty |
| Footer | Footer | tdesign-miniprogram/footer/footer |
| Grid | Grid | tdesign-miniprogram/grid/grid |
| Image | Image | tdesign-miniprogram/image/image |
| ImageViewer | Image Viewer | tdesign-miniprogram/image-viewer/image-viewer |
| Progress | Progress | tdesign-miniprogram/progress/progress |
| Result | Result | tdesign-miniprogram/result/result |
| Skeleton | Skeleton | tdesign-miniprogram/skeleton/skeleton |
| Sticky | Sticky | tdesign-miniprogram/sticky/sticky |
| Swiper | Swiper | tdesign-miniprogram/swiper/swiper |
| Table | Table | tdesign-miniprogram/table/table |
| Tag | Tag | tdesign-miniprogram/tag/tag |
| List | List | tdesign-miniprogram/list/list |
Feedback Components (12)
| Component | Description | Import Path |
|---|---|---|
| ActionSheet | Action Sheet | tdesign-miniprogram/action-sheet/action-sheet |
| Dialog | Dialog | tdesign-miniprogram/dialog/dialog |
| DropdownMenu | Dropdown Menu | tdesign-miniprogram/dropdown-menu/dropdown-menu |
| Guide | Guide | tdesign-miniprogram/guide/guide |
| Loading | Loading | tdesign-miniprogram/loading/loading |
| Message | Message | tdesign-miniprogram/message/message |
| NoticeBar | Notice Bar | tdesign-miniprogram/notice-bar/notice-bar |
| Overlay | Overlay | tdesign-miniprogram/overlay/overlay |
| Popup | Popup | tdesign-miniprogram/popup/popup |
| PullDownRefresh | Pull Down Refresh | tdesign-miniprogram/pull-down-refresh/pull-down-refresh |
| SwipeCell | Swipe Cell | tdesign-miniprogram/swipe-cell/swipe-cell |
| Toast | Toast | tdesign-miniprogram/toast/toast |
AI Chat Components (9)
| Component | Description | Import Path |
|---|---|---|
| ChatList | Chat List | tdesign-miniprogram/chat-list/chat-list |
| ChatMessage | Chat Message | tdesign-miniprogram/chat-message/chat-message |
| ChatSender | Chat Sender | tdesign-miniprogram/chat-sender/chat-sender |
| ChatContent | Chat Content | tdesign-miniprogram/chat-content/chat-content |
| ChatActionbar | Chat Action Bar | tdesign-miniprogram/chat-actionbar/chat-actionbar |
| ChatLoading | Chat Loading | tdesign-miniprogram/chat-loading/chat-loading |
| ChatMarkdown | Chat Markdown | tdesign-miniprogram/chat-markdown/chat-markdown |
| ChatThinking | Chat Thinking | tdesign-miniprogram/chat-thinking/chat-thinking |
| Attachments | Attachments | tdesign-miniprogram/attachments/attachments |
Common Patterns
Button
<!-- Basic Buttons -->
<t-button theme="primary" size="large">Primary Button</t-button>
<t-button theme="light" size="large">Light Button</t-button>
<t-button size="large">Default Button</t-button>
<!-- Outline and Text Buttons -->
<t-button theme="primary" size="large" variant="outline"
>Outline Button</t-button
>
<t-button theme="primary" size="large" variant="text">Text Button</t-button>
<!-- Icon Button -->
<t-button
theme="primary"
icon="app"
content="Icon Button"
size="large"
></t-button>
<!-- Loading State -->
<t-button theme="primary" size="large" loading>Loading</t-button>
<!-- Disabled State -->
<t-button theme="primary" size="large" disabled>Disabled</t-button>
<!-- Block Button -->
<t-button theme="primary" size="large" block>Block Button</t-button>
<!-- Ghost Button (transparent background) -->
<t-button theme="primary" ghost size="large">Ghost Button</t-button>Input
{
"usingComponents": {
"t-input": "tdesign-miniprogram/input/input"
}
}<t-input
label="Label"
placeholder="Please enter"
value="{{value}}"
bind:change="onChange"
/>Dialog
{
"usingComponents": {
"t-dialog": "tdesign-miniprogram/dialog/dialog"
}
}<t-dialog
visible="{{visible}}"
title="Dialog Title"
content="Dialog content"
confirm-btn="Confirm"
cancel-btn="Cancel"
bind:confirm="onConfirm"
bind:cancel="onCancel"
/>Toast
import Toast from 'tdesign-miniprogram/toast/index';
Toast({
context: this,
selector: '#t-toast',
message: 'Toast message',
});AI Chat Interface
{
"usingComponents": {
"t-chat-list": "tdesign-miniprogram/chat-list/chat-list",
"t-chat-message": "tdesign-miniprogram/chat-message/chat-message",
"t-chat-sender": "tdesign-miniprogram/chat-sender/chat-sender"
}
}<t-chat-list layout="single">
<t-chat-message
avatar="{{item.avatar}}"
name="{{item.name}}"
content="{{item.content}}"
role="{{item.role}}"
/>
<view slot="footer">
<t-chat-sender bind:send="onSend" />
</view>
</t-chat-list>Component({
data: {
messages: [
{
role: 'user',
content: [{ type: 'text', data: 'Hello' }],
},
{
role: 'assistant',
content: [{ type: 'text', data: 'Hello! How can I help you?' }],
},
],
},
methods: {
onSend(e) {
const { value } = e.detail;
// Handle send message
},
},
});Style Customization
Method 1: Using Style Attribute
<t-button style="color: red">Custom Style</t-button>
<t-button custom-style="color: red">Custom Style</t-button>Method 2: Disable Style Isolation
Override styles directly in page:
.t-button--primary {
background-color: navy;
}In custom components, enable styleIsolation:
Component({
options: {
styleIsolation: 'shared',
},
});Method 3: External Style Classes
<t-button t-class="my-button-class">Button</t-button>.my-button-class {
background-color: navy !important;
}Method 4: CSS Variables
TDesign provides rich CSS variables for theme customization:
page {
--td-button-primary-bg-color: #0052d9;
--td-button-border-radius: 8rpx;
}Dark Mode
1. Modify app.json
{
"darkmode": true
}2. Import Design Token
In app.wxss:
@import 'miniprogram_npm/tdesign-miniprogram/common/style/theme/_index.wxss';3. Use CSS Variables
.text {
color: var(--td-text-color-secondary);
}4. Special Component Adaptation
For components wrapped in custom-tab-bar or root-portal, add .page class:
<view class="page">
<t-tab-bar />
</view>Reference Files
This skill includes comprehensive documentation in references/:
Basic Documentation
- miniprogram/getting-started.md - Quick start guide
- miniprogram/overview.md - Component overview
- miniprogram/custom-style.md - Style customization
- miniprogram/custom-theme.md - Theme customization
- miniprogram/dark-mode.md - Dark mode
Component Documentation (miniprogram/components/)
- button.md - Button
- input.md - Input
- dialog.md - Dialog
- form.md - Form
- ... more component docs
AI Chat Component Documentation (miniprogram-chat/)
- getting-started.md - Chat component quick start
- sse.md - SSE streaming
- components/chat-message.md - Chat message
- components/chat-sender.md - Chat sender
- components/chat-list.md - Chat list
- ... more chat component docs
Use Read tool to access specific reference files when detailed API information is needed.
Key Requirements
- Minimum base library version:
^2.12.0 - Build npm in WeChat DevTools required
- Remove
"style": "v2"fromapp.json
Resources
Notes
- This skill was automatically generated from TDesign official documentation
- Reference files preserve the structure and examples from source docs
- Some reference content remains in Chinese as per official documentation
{
"name": "TDesign 微信小程序组件",
"installedAt": 1776152040698,
"source": "marketplace",
"iconSource": "tdesign-miniprogram",
"version": "1.0.0"
}Attachments 文件附件
示例
引入
全局引入,在 miniprogram 根目录下的app.json中配置,局部引入,在需要引入的页面或组件的index.json中配置。
"usingComponents": {
"t-attachments": "tdesign-miniprogram/attachments/attachments"
}代码演示
Tips: 请确保开发者工具为打开状态。导入开发者工具后,依次执行:npm i > 构建npm包 > 勾选 "将JS编译成ES5"
01组件类型
图片类型
WXML (html):
<view class="chat-example">
<view class="chat-example-block">
<t-attachments items="{{items}}" bind:fileClick="onFileClick" bind:remove="onRemove" bind:add="onAdd" />
</view>
</view>
JS (javascript):
Page({
data: {
items: [
{
fileType: 'image',
name: 'sample-image.jpg',
url: 'https://tdesign.gtimg.com/site/avatar.jpg',
size: 1024,
status: 'success',
},
],
},
onFileClick(e) {
const { item } = e.detail;
console.log('点击文件:', item);
wx.showToast({
title: `点击了${item.name}`,
icon: 'none',
});
},
onRemove(e) {
const { item, index } = e.detail;
console.log('删除文件:', e, item, '索引:', index);
// 从列表中移除文件
const newItems = [...this.data.items];
newItems.splice(index, 1);
this.setData({
items: newItems,
});
wx.showToast({
title: '删除成功',
icon: 'success',
});
},
onAdd() {
console.log('点击添加按钮');
wx.showToast({
title: '点击了添加按钮',
icon: 'none',
});
// 模拟添加新文件
const newFile = {
fileType: 'txt',
name: `新文件${this.data.items.length + 1}.txt`,
url: 'https://example.com/newfile.txt',
size: 256,
status: 'success',
};
this.setData({
items: [...this.data.items, newFile],
});
},
});
CSS (css):
.chat-example-block {
padding: 32rpx;
background-color: var(--td-bg-color-container);
}
JSON (javascript):
{
"usingComponents": {
"t-attachments": "tdesign-miniprogram/attachments/attachments"
}
}
文件类型
WXML (html):
<view class="chat-example">
<!-- 有效的示例,呈现效果为 单行滚动 -->
<!-- <view class="chat-example-block">
<t-attachments items="{{items}}" bind:fileClick="onFileClick" bind:remove="onRemove" bind:add="onAdd" />
</view> -->
<view class="chat-example-block" wx:for="{{items}}" wx:for-item="item" wx:key="url">
<t-attachments items="{{[item]}}" bind:fileClick="onFileClick" bind:remove="onRemove" bind:add="onAdd" />
</view>
</view>
JS (javascript):
Page({
data: {
items: [
{
fileType: 'doc',
name: 'word-file.doc',
url: 'https://example.com/word-file.doc',
size: 222859,
status: 'success',
},
{
fileType: 'excel',
name: 'excel-file.xlsx',
url: 'https://example.com/excel-file.xlsx',
size: 222859,
status: 'success',
},
{
fileType: 'pdf',
name: 'pdf-file.pdf',
url: 'https://example.com/pdf-file.pdf',
size: 222859,
status: 'success',
},
{
fileType: 'ppt',
name: 'ppt-file.pptx',
url: 'https://example.com/ppt-file.pptx',
size: 222859,
status: 'success',
},
{
fileType: 'video',
name: 'video-file.mp4',
url: 'https://example.com/video-file.mp4',
size: 222859,
status: 'success',
},
{
fileType: 'file',
name: 'file',
url: 'https://example.com/audio-file.mp3',
size: 222859,
status: 'success',
},
],
},
onFileClick(e) {
const { item } = e.detail;
console.log('点击文件:', item);
wx.showToast({
title: `点击了${item.name}`,
icon: 'none',
});
},
onRemove(e) {
const { item, index } = e.detail;
console.log('删除文件:', e, item, '索引:', index);
// 从列表中移除文件
const newItems = [...this.data.items];
newItems.splice(index, 1);
this.setData({
items: newItems,
});
wx.showToast({
title: '删除成功',
icon: 'success',
});
},
onAdd() {
console.log('点击添加按钮');
wx.showToast({
title: '点击了添加按钮',
icon: 'none',
});
// 模拟添加新文件
const newFile = {
fileType: 'txt',
name: `新文件${this.data.items.length + 1}.txt`,
url: 'https://example.com/newfile.txt',
size: 256,
status: 'success',
};
this.setData({
items: [...this.data.items, newFile],
});
},
});
CSS (css):
.chat-example {
padding: 32rpx;
box-sizing: border-box;
background-color: var(--td-bg-color-container);
}
.chat-example-block {
display: inline-flex;
width: 336rpx;
margin-bottom: 26rpx;
}
JSON (javascript):
{
"styleIsolation": "isolated",
"usingComponents": {
"t-attachments": "tdesign-miniprogram/attachments/attachments"
}
}
02组件状态
图片类型加载状态
WXML (html):
<view class="chat-example">
<view class="chat-example-block">
<t-attachments items="{{items}}" bind:fileClick="onFileClick" bind:remove="onRemove" bind:add="onAdd" />
</view>
</view>
JS (javascript):
Page({
data: {
items: [
{
fileType: 'image',
name: 'sample-image.jpg',
url: 'https://tdesign.gtimg.com/site/avatar.jpg',
size: 1024,
status: 'pending',
},
],
},
onFileClick(e) {
const { item } = e.detail;
console.log('点击文件:', item);
wx.showToast({
title: `点击了${item.name}`,
icon: 'none',
});
},
onRemove(e) {
const { item, index } = e.detail;
console.log('删除文件:', e, item, '索引:', index);
// 从列表中移除文件
const newItems = [...this.data.items];
newItems.splice(index, 1);
this.setData({
items: newItems,
});
wx.showToast({
title: '删除成功',
icon: 'success',
});
},
onAdd() {
console.log('点击添加按钮');
wx.showToast({
title: '点击了添加按钮',
icon: 'none',
});
// 模拟添加新文件
const newFile = {
fileType: 'txt',
name: `新文件${this.data.items.length + 1}.txt`,
url: 'https://example.com/newfile.txt',
size: 256,
status: 'success',
};
this.setData({
items: [...this.data.items, newFile],
});
},
});
CSS (css):
.chat-example-block {
padding: 32rpx;
background-color: var(--td-bg-color-container);
}
JSON (javascript):
{
"usingComponents": {
"t-attachments": "tdesign-miniprogram/attachments/attachments"
}
}
文件类型加载状态
WXML (html):
<view class="chat-example">
<view class="chat-example-block">
<t-attachments items="{{items}}" bind:fileClick="onFileClick" bind:remove="onRemove" bind:add="onAdd" />
</view>
</view>
JS (javascript):
Page({
data: {
items: [
{
fileType: 'doc',
name: 'word-file.doc',
url: 'https://example.com/word-file.doc',
size: 222859,
status: 'pending',
},
],
},
onFileClick(e) {
const { item } = e.detail;
console.log('点击文件:', item);
wx.showToast({
title: `点击了${item.name}`,
icon: 'none',
});
},
onRemove(e) {
const { item, index } = e.detail;
console.log('删除文件:', e, item, '索引:', index);
// 从列表中移除文件
const newItems = [...this.data.items];
newItems.splice(index, 1);
this.setData({
items: newItems,
});
wx.showToast({
title: '删除成功',
icon: 'success',
});
},
onAdd() {
console.log('点击添加按钮');
wx.showToast({
title: '点击了添加按钮',
icon: 'none',
});
// 模拟添加新文件
const newFile = {
fileType: 'txt',
name: `新文件${this.data.items.length + 1}.txt`,
url: 'https://example.com/newfile.txt',
size: 256,
status: 'success',
};
this.setData({
items: [...this.data.items, newFile],
});
},
});
CSS (css):
.chat-example-block {
padding: 32rpx;
background-color: var(--td-bg-color-container);
}
JSON (javascript):
{
"usingComponents": {
"t-attachments": "tdesign-miniprogram/attachments/attachments"
}
}
API
AttachmentsProps
| 名称 | 类型 | 默认值 | 描述 | 必传 |
|---|---|---|---|---|
| style | Object | - | 样式 | N |
| custom-style | Object | - | 样式,一般用于开启虚拟化组件节点场景 | N |
| addable | Boolean | true | 【讨论中】是否显示添加按钮 | N |
| image-viewer | Boolean | true | 是否启用图片预览功能 | N |
| items | Array | [] | 必需。附件列表。TS 类型:FileItem[]`interface FileItem { fileType: 'image'\ | 'video'\ |
| removable | Boolean | true | 是否显示删除按钮 | N |
AttachmentsEvents
| 名称 | 参数 | 描述 |
|---|---|---|
| add | - | 点击添加按钮时触发 |
| file-click | (item: FileItem) | 点击文件时触发 |
| remove | (item: FileItem, index: number) | 点击删除按钮时触发 |
ChatActionbar 对话操作
示例
引入
全局引入,在 miniprogram 根目录下的app.json中配置,局部引入,在需要引入的页面或组件的index.json中配置。
"usingComponents": {
"t-chat-actionbar": "tdesign-miniprogram/chat-actionbar/chat-actionbar"
}代码演示
Tips: 请确保开发者工具为打开状态。导入开发者工具后,依次执行:npm i > 构建npm包 > 勾选 "将JS编译成ES5"
01组件类型
基础类型
WXML (html):
<t-toast id="t-toast" />
<view class="container">
<t-chat-actionbar content="{{content}}" bind:actions="handleAction" />
</view>
JS (javascript):
import Toast from 'tdesign-miniprogram/toast';
Component({
data: {
content: '这是一段可以被复制的内容,支持markdown格式。\n\n**粗体文本**\n*斜体文本*\n\n- 列表项1\n- 列表项2',
},
methods: {
handleAction(e) {
const { name, active, data } = e.detail;
let message = '';
switch (name) {
case 'replay':
message = '重新生成';
break;
case 'copy':
console.log(data);
message = '复制成功';
break;
case 'good':
message = active ? '点赞成功' : '取消点赞';
break;
case 'bad':
message = active ? '点踩成功' : '取消点踩';
break;
case 'share':
message = '分享功能';
break;
default:
message = `执行了${name}操作`;
}
Toast({
context: this,
selector: '#t-toast',
message,
theme: 'success',
});
},
},
});
CSS (css):
.container {
padding: 32rpx;
background-color: var(--td-bg-color-container);
}
.layout-btn {
margin: 16rpx 0;
padding: 12rpx 24rpx;
background-color: #0052d9;
color: #fff;
border: none;
border-radius: 8rpx;
font-size: 28rpx;
}
.demo-text {
padding: 16rpx 0;
}
.checkbox-group {
display: flex;
flex-wrap: wrap;
gap: 16rpx;
}
JSON (javascript):
{
"component": true,
"usingComponents": {
"t-chat-actionbar": "tdesign-miniprogram/chat-actionbar/chat-actionbar",
"t-toast": "tdesign-miniprogram/toast/toast"
}
}
02组件状态
手动初始化状态
WXML (html):
<t-toast id="t-toast" />
<view class="container">
<t-chat-actionbar content="{{content}}" bind:actions="handleAction" comment="{{comment}}" />
</view>
JS (javascript):
import Toast from 'tdesign-miniprogram/toast';
Page({
data: {
content: '这是一段可以被复制的内容,支持markdown格式。\n\n**粗体文本**\n*斜体文本*\n\n- 列表项1\n- 列表项2',
comment: 'good',
},
handleAction(e) {
const { name, active, data } = e.detail;
let message = '';
switch (name) {
case 'replay':
message = '重新生成';
break;
case 'copy':
console.log(data);
message = '复制成功';
break;
case 'good':
message = active ? '点赞成功' : '取消点赞';
break;
case 'bad':
message = active ? '点踩成功' : '取消点踩';
break;
case 'share':
message = '分享功能';
break;
default:
message = `执行了${name}操作`;
}
Toast({
context: this,
selector: '#t-toast',
message,
theme: 'success',
});
},
});
CSS (css):
.container {
padding: 32rpx;
background-color: var(--td-bg-color-container);
}
.layout-btn {
margin: 16rpx 0;
padding: 12rpx 24rpx;
background-color: #0052d9;
color: #fff;
border: none;
border-radius: 8rpx;
font-size: 28rpx;
}
.demo-text {
padding: 16rpx 0;
}
.checkbox-group {
display: flex;
flex-wrap: wrap;
gap: 16rpx;
}
JSON (javascript):
{
"usingComponents": {
"t-chat-actionbar": "tdesign-miniprogram/chat-actionbar/chat-actionbar",
"t-toast": "tdesign-miniprogram/toast/toast"
}
}
API
ChatActionbarProps
| 名称 | 类型 | 默认值 | 描述 | 必传 |
|---|---|---|---|---|
| style | Object | - | 样式 | N |
| custom-style | Object | - | 样式,一般用于开启虚拟化组件节点场景 | N |
| action-bar | Array | ['replay', 'copy', 'good', 'bad', 'share'] | 操作栏配置。TS 类型:`Array<'replay'\ | 'copy'\ |
| chat-id | String | - | 【实验】聊天消息的唯一标识 | N |
| comment | String | - | 评价内容 | N |
| content | String | - | 被复制的内容 | N |
| copy-mode | String | markdown | 【实验】复制内容的模式,可选 'markdown'(复制markdown原文)或 'text'(复制纯文本)。可选项:markdown/text | N |
| disabled | Boolean | false | 【讨论中】操作按钮是否可点击 | N |
| placement | String | start | 【实验】操作栏位置。可选项:start/end/space-around/space-between | N |
ChatActionbarEvents
| 名称 | 参数 | 描述 |
|---|---|---|
| actions | (detail: {name: string, active: boolean}) | 点击点赞,点踩,复制,分享,重新生成按钮时触发发 |
ChatContent 对话正文
示例
引入
全局引入,在 miniprogram 根目录下的app.json中配置,局部引入,在需要引入的页面或组件的index.json中配置。
"usingComponents": {
"t-chat-content": "tdesign-miniprogram/chat-content/chat-content"
}代码演示
Tips: 请确保开发者工具为打开状态。导入开发者工具后,依次执行:npm i > 构建npm包 > 勾选 "将JS编译成ES5"
01组件类型
- 对大模型返回的 markdown 数据自动渲染。markdown 会内置调用
t-chat-markdown渲染,同时可根据 role(user/assistant)切换样式。 - 用户发送的消息保持默认格式显示,纯文本会做 HTML 转义并用 rich-text 渲染;
WXML (html):
<view class="chat-example">
<view class="title">用户内容纯文本支持HTML转义</view>
<view class="chat-example-block">
<t-chat-content content="{{userContent}}" role="user"></t-chat-content>
</view>
</view>
<view class="chat-example">
<view class="title">助手内容(Markdown)</view>
<view class="chat-example-block">
<t-chat-content content="{{assistantContent}}" role="assistant"></t-chat-content>
</view>
</view>
JS (javascript):
// import { Lexer } from 'marked';
import markdownData from './mock.js';
// 创建Lexer实例时添加配置,禁用gfm规范中的缩进代码块
// const lexer = new Lexer({});
// const tokens = lexer.lex(markdownData);
Component({
data: {
userContent: {
type: 'text',
data: '这是用户发送的普通文本内容',
},
assistantContent: {
type: 'markdown',
data: markdownData,
},
},
methods: {},
});
CSS (css):
.chat-example {
margin-bottom: 32rpx;
}
.chat-example-block {
padding: 32rpx;
background-color: var(--td-bg-color-container);
}
.title {
padding: 0 32rpx 32rpx;
font-size: 28rpx;
line-height: 44rpx;
color: var(--bg-color-demo-desc);
}
JSON (javascript):
{
"component": true,
"styleIsolation": "shared",
"usingComponents": {
"t-chat-content": "tdesign-miniprogram/chat-content/chat-content"
}
}
MOCK (javascript):
const mockMarkdownData = `# Markdown功能测试 (H1标题)
## 基础语法测试 (H2标题)
### 列表测试
- 无序列表项1
- 无序列表项2
- 缩进列表项1(4个空格缩进)
- 缩进列表项2(4个空格缩进)
1. 有序列表项1
2. 有序列表项2
1. 缩进有序列表项1(4个空格缩进)
2. 缩进有序列表项2(4个空格缩进)
### 代码块测试
\`\`\`javascript
// JavaScript 代码块
function greet(name) {
console.log(\`Hello, \${name}!\`);
}
greet('Markdown');
\`\`\`
### 其他元素
> 引用文本块
> 多行引用内容
**加粗文字** _斜体文字_ ~~删除线~~
这是一个链接 [TDesign](https://tdesign.tencent.com)。
`;
export default mockMarkdownData;
API
ChatContentProps
| 名称 | 类型 | 默认值 | 描述 | 必传 |
|---|---|---|---|---|
| style | Object | - | 样式 | N |
| custom-style | Object | - | 样式,一般用于开启虚拟化组件节点场景 | N |
| content | Object | - | 必需。聊天内容对象。TS 类型:TdChatContentType `interface TdChatContentType { type: 'text' \ | 'markdown'; data: string; }`。详细类型定义 |
| markdown-props | Object | - | marked 解析器的配置选项。TS 类型:ChatMarkdownProps,ChatMarkdown API Documents。详细类型定义 | N |
| role | String | - | 必需。消息角色,用于区分用户和助手的消息样式 。可选项:user/assistant/system | Y |
| status | String | - | 正文状态。可选项:error / '' | N |
ChatList 对话列表
示例
引入
全局引入,在 miniprogram 根目录下的app.json中配置,局部引入,在需要引入的页面或组件的index.json中配置。
"usingComponents": {
"t-chat": "tdesign-miniprogram/chat-list/chat-list"
}代码演示
Tips: 请确保开发者工具为打开状态。导入开发者工具后,依次执行:npm i > 构建npm包 > 勾选 "将JS编译成ES5"
组件类型
基础使用
WXML (html):
<view class="chat-box" style="height: {{contentHeight}};">
<t-chat id="chatList" bindscroll="onScroll">
<block wx:for="{{chatList}}" wx:key="index">
<t-chat-message
avatar="{{item.avatar || ''}}"
name="{{item.name || ''}}"
datetime="{{item.datetime || ''}}"
content="{{item.content}}"
role="{{item.role}}"
placement="{{item.role === 'user' ? 'right' : 'left'}}"
status="{{item.status || ''}}"
>
<t-chat-actionbar
wx:if="{{chatIndex !== chatList.length - 1 && item.status === 'complete' && item.role === 'assistant'}}"
slot="actionbar"
placement="end"
bind:actions="handleAction"
/>
</t-chat-message>
</block>
<view slot="footer">
<t-chat-sender
value="{{value}}"
loading="{{loading}}"
disabled="{{disabled}}"
autoRiseWithKeyboard="{{true}}"
renderPresets="{{renderPresets}}"
bind:send="onSend"
bind:stop="onStop"
bind:focus="onFocus"
/>
</view>
</t-chat>
<!-- 内置虚拟列表优化性能仅在data属性中使用 -->
<!-- <t-chat id="chatList" bindscroll="onScroll" data="{{chatList}}"></t-chat> -->
</view>
<t-toast id="t-toast" />
JS (javascript):
import Toast from 'tdesign-miniprogram/toast';
import { getNavigationBarHeight } from '../../../utils/utils';
const mockData = `南极的自动提款机并没有一个特定的专属名称,但历史上确实有一台ATM机曾短暂存在于南极的**麦克默多站**(McMurdo Station)。这台ATM由美国**富兰克林国家银行**(Wells Fargo)于1998年安装,主要供驻扎在该站的科研人员使用。不过,由于南极的极端环境和极低的人口密度,这台ATM机并未长期运行,最终被移除。
**背景补充:**
- **麦克默多站**是美国在南极最大的科研基地,夏季人口可达约1,000人,冬季约200人。
- 该ATM机更多是作为一种象征性服务存在,实际使用频率极低,因为南极科考人员通常依靠预支资金或电子支付。
- 目前南极已无长期运行的ATM机,现代科考站更多依赖非现金交易方式。
南极作为非主权领土,其基础设施以科研和生活支持为主,商业金融服务非常有限。若有类似设施,通常是临时或实验性质的。`;
const sleep = (ms) => {
return new Promise((resolve) => setTimeout(resolve, ms));
};
const fetchStream = async (str, options) => {
const { success, complete, delay = 100 } = options;
const arr = str.split('');
for (let i = 0; i < arr.length; i += 1) {
// eslint-disable-next-line no-await-in-loop
await sleep(delay);
success(arr[i]);
}
complete();
};
Component({
options: {
styleIsolation: 'shared',
},
data: {
renderPresets: [{ name: 'send', type: 'icon' }],
chatList: [
{
avatar: 'https://tdesign.gtimg.com/site/chat-avatar.png',
role: 'assistant',
status: 'complete',
content: [
{
type: 'text',
data: '它叫 McMurdo Station ATM,是美国富国银行安装在南极洲最大科学中心麦克默多站的一台自动提款机。',
},
],
},
{
role: 'user',
content: [
{
type: 'text',
data: '牛顿第一定律是否适用于所有参考系?',
},
],
},
],
value: '', // 输入框的值
loading: false, // 加载状态
disabled: false, // 禁用状态
inputStyle: '', // 输入框样式
contentHeight: '100vh', // 内容高度
animation: 'dots',
},
methods: {
// 调用chatList的滚动到底部方法
scrollToBottom() {
const chatListComponent = this.selectComponent('#chatList');
if (chatListComponent && typeof chatListComponent.scrollToBottom === 'function') {
chatListComponent.scrollToBottom();
}
},
onScroll(e) {
console.log('监听滚动', e);
},
// 发送消息事件处理
onSend(e) {
const { value } = e.detail;
if (!value || value.trim() === '') return;
// 创建用户消息对象
const userMessage = {
role: 'user',
content: [
{
type: 'text',
data: value.trim(),
},
],
};
// 将用户消息插入到chatList的开头(因为reverse为true,所以用unshift)
this.setData({
chatList: [userMessage, ...this.data.chatList],
value: '', // 清空输入框
});
// 模拟助手回复(可选)
this.simulateAssistantReply(value.trim());
},
// 停止事件处理
onStop() {
this.setData({
loading: false,
});
},
// 聚焦事件处理
onFocus() {
console.log('输入框聚焦');
},
// 获取当前时间
getCurrentTime() {
const now = new Date();
const hours = now.getHours().toString().padStart(2, '0');
const minutes = now.getMinutes().toString().padStart(2, '0');
return `${hours}:${minutes}`;
},
// 模拟助手回复
simulateAssistantReply() {
this.setData({ loading: true});
// 请求中
const assistantMessage = {
role: 'assistant',
content: [
{
type: 'markdown',
data: '',
},
],
avatar: 'https://tdesign.gtimg.com/site/chat-avatar.png',
status: 'pending',
};
this.setData({
chatList: [assistantMessage, ...this.data.chatList],
});
const that = this;
wx.nextTick(() => {
fetchStream(mockData, {
success(result) {
// 生文中
that.data.chatList[0].status = 'streaming';
if (!that.data.loading) return;
that.data.chatList[0].content[0].data += result;
that.setData({
chatList: that.data.chatList,
});
},
complete() {
that.data.chatList[0].status = 'complete';
that.setData({
chatList: that.data.chatList,
});
that.setData({
loading: false,
});
},
});
});
},
handleAction(e) {
const { name, active, data } = e.detail;
let message = '';
switch (name) {
case 'replay':
message = '重新生成';
break;
case 'copy':
console.log(data);
message = '复制成功';
break;
case 'good':
message = active ? '点赞成功' : '取消点赞';
break;
case 'bad':
message = active ? '点踩成功' : '取消点踩';
break;
case 'share':
message = '分享功能';
break;
default:
message = `执行了${name}操作`;
}
Toast({
context: this,
selector: '#t-toast',
message,
theme: 'success',
});
},
},
lifetimes: {
attached: function () {
/**
* 计算内容区域高度
* 生成CSS calc表达式:calc(100vh - 96rpx - 导航高度 - 底部安全区域高度)
*/
try {
// 获取当前的导航栏高度和安全区域高度
const navigationBarHeight = getNavigationBarHeight() || 0;
// 生成CSS calc表达式字符串
const contentHeight = `calc(100vh - 96rpx - ${navigationBarHeight}px)`;
this.setData({
contentHeight: contentHeight,
});
console.log('内容区域高度CSS表达式:', contentHeight);
} catch (error) {
console.error('生成内容高度表达式失败:', error);
this.setData({
contentHeight: 'calc(100vh - 96rpx)',
});
}
},
},
});
CSS (css):
.chat-box {
padding-top: 32rpx;
box-sizing: border-box;
}
.t-chat__list {
padding: 0 0 0 32rpx;
box-sizing: border-box;
}
.t-chat-message {
padding: 0 32rpx;
}
JSON (javascript):
{
"component": true,
"styleIsolation": "shared",
"usingComponents": {
"t-chat-message": "tdesign-miniprogram/chat-message/chat-message",
"t-chat-content": "tdesign-miniprogram/chat-content/chat-content",
"t-chat": "tdesign-miniprogram/chat-list/chat-list",
"t-chat-sender": "tdesign-miniprogram/chat-sender/chat-sender",
"t-chat-actionbar": "tdesign-miniprogram/chat-actionbar/chat-actionbar",
"t-toast": "tdesign-miniprogram/toast/toast"
}
}
MOCK (javascript):
// file test
组合式用法
WXML (html):
<view class="chat-box" style="height: {{contentHeight}};">
<t-chat>
<block wx:for="{{chatList}}" wx:key="index">
<t-chat-message
avatar="{{item.avatar || ''}}"
name="{{item.name || ''}}"
datetime="{{item.datetime || ''}}"
content="{{item.message.content}}"
role="{{item.message.role}}"
placement="{{item.message.role === 'user' ? 'right' : 'left'}}"
>
<t-chat-actionbar
wx:if="{{chatIndex !== chatList.length - 1 && item.message.status === 'complete' && item.message.role === 'assistant'}}"
slot="actionbar"
placement="end"
bind:actions="handleAction"
/>
</t-chat-message>
</block>
<view slot="footer">
<t-chat-sender
value="{{value}}"
loading="{{loading}}"
disabled="{{disabled}}"
renderPresets="{{renderPresets}}"
bind:send="onSend"
bind:stop="onStop"
bind:focus="onFocus"
autoRiseWithKeyboard="{{true}}"
/>
</view>
</t-chat>
</view>
<t-toast id="t-toast" />
JS (javascript):
import Toast from 'tdesign-miniprogram/toast';
import { getNavigationBarHeight } from '../../../utils/utils';
const mockData = {
avatar: 'https://tdesign.gtimg.com/site/chat-avatar.png',
message: {
role: 'assistant',
content: [
{
type: 'thinking',
status: 'complete',
data: {
title: '思考中',
text: '',
},
},
],
},
};
const mockData1 =
'嗯,用户问的是南极的自动提款机叫什么名字。这个问题有点有趣,因为南极是一个极端寒冷的地方,而且大部分地区都是无人居住的科研站。\n';
const mockData2 =
'\n\n南极的自动提款机并没有一个特定的专属名称,但历史上确实有一台ATM机曾短暂存在于南极的**麦克默多站**(McMurdo Station)。这台ATM由美国**富兰克林国家银行**(Wells Fargo)于1998年安装,主要供驻扎在该站的科研人员使用。不过,由于南极的极端环境和极低的人口密度,这台ATM机并未长期运行,最终被移除。\n\n**背景补充:**\n- **麦克默多站**是美国在南极最大的科研基地,夏季人口可达约1,000人,冬季约200人。\n- 该ATM机更多是作为一种象征性服务存在,实际使用频率极低,因为南极科考人员通常依靠预支资金或电子支付。\n- 目前南极已无长期运行的ATM机,现代科考站更多依赖非现金交易方式。\n\n南极作为非主权领土,其基础设施以科研和生活支持为主,商业金融服务非常有限。若有类似设施,通常是临时或实验性质的。';
const sleep = (ms) => {
return new Promise((resolve) => setTimeout(resolve, ms));
};
const fetchStream = async (str, options) => {
const { success, complete, delay = 100 } = options;
const arr = str.split('');
for (let i = 0; i < arr.length; i += 1) {
// eslint-disable-next-line no-await-in-loop
await sleep(delay);
success(arr[i]);
}
complete();
};
Component({
properties: {
isActive: {
type: Boolean,
value: false,
observer: function (v) {
this.setData({
value: v ? '南极的自动提款机叫什么名字' : '', // 输入框的值
});
},
},
},
data: {
renderPresets: [
{
name: 'send',
type: 'icon',
},
],
chatList: [
{
avatar: 'https://tdesign.gtimg.com/site/chat-avatar.png',
message: {
status: 'complete',
role: 'assistant',
content: [
{
type: 'text',
data: '它叫 McMurdo Station ATM,是美国富国银行安装在南极洲最大科学中心麦克默多站的一台自动提款机。',
},
],
},
},
{
message: {
role: 'user',
content: [
{
type: 'text',
data: '牛顿第一定律是否适用于所有参考系?',
},
],
},
},
],
value: '',
loading: false, // 加载状态
disabled: false, // 禁用状态
inputStyle: '', // 动态样式
contentHeight: '100vh', // 内容高度
},
methods: {
// 发送消息事件处理
onSend(e) {
const { value } = e.detail;
if (!value || value.trim() === '') return;
// 创建用户消息对象
const userMessage = {
message: {
role: 'user',
content: [
{
type: 'text',
data: value.trim(),
},
],
},
};
// 将用户消息插入到chatList的开头(因为reverse为true,所以用unshift)
this.setData({
chatList: [userMessage, ...this.data.chatList],
value: '', // 清空输入框
});
// 模拟助手回复(可选)
this.simulateAssistantReply(value.trim());
},
// 停止事件处理
onStop() {
console.log('停止发送');
this.setData({
loading: false,
});
},
// 聚焦事件处理
onFocus() {
console.log('输入框聚焦');
},
// 获取当前时间
getCurrentTime() {
const now = new Date();
const hours = now.getHours().toString().padStart(2, '0');
const minutes = now.getMinutes().toString().padStart(2, '0');
return `${hours}:${minutes}`;
},
// 模拟助手回复
simulateAssistantReply() {
this.setData({ loading: true });
const assistantMessage = mockData;
this.setData({
chatList: [assistantMessage, ...this.data.chatList],
});
const that = this;
wx.nextTick(async () => {
await fetchStream(mockData1, {
success(result) {
if (!that.data.loading) return;
that.data.chatList[0].message.content[0].data.text += result;
that.setData({
chatList: that.data.chatList,
});
},
complete() {
that.data.chatList[0].message.content[0].data.title = '思考完成';
that.setData({
chatList: that.data.chatList,
});
},
});
if (!that.data.loading) return;
that.data.chatList[0].message.content.push({
type: 'markdown',
data: '',
});
that.setData({
chatList: that.data.chatList,
});
await fetchStream(mockData2, {
success(result) {
if (!that.data.loading) return;
that.data.chatList[0].message.content[1].data += result;
that.setData({
chatList: that.data.chatList,
});
},
complete() {
that.data.chatList[0].message.status = 'complete';
that.setData({
chatList: that.data.chatList,
});
that.setData({
loading: false,
});
},
});
});
},
handleAction(e) {
const { name, active, data } = e.detail;
let message = '';
switch (name) {
case 'replay':
message = '重新生成';
break;
case 'copy':
console.log(data);
message = '复制成功';
break;
case 'good':
message = active ? '点赞成功' : '取消点赞';
break;
case 'bad':
message = active ? '点踩成功' : '取消点踩';
break;
case 'share':
message = '分享功能';
break;
default:
message = `执行了${name}操作`;
}
Toast({
context: this,
selector: '#t-toast',
message,
theme: 'success',
});
},
},
lifetimes: {
attached: function () {
console.log('-----attached----');
/**
* 计算内容区域高度
* 生成CSS calc表达式:calc(100vh - 96rpx - 导航高度 - 底部安全区域高度)
*/
try {
// 获取当前的导航栏高度和安全区域高度
const navigationBarHeight = getNavigationBarHeight() || 0;
// 生成CSS calc表达式字符串
const contentHeight = `calc(100vh - 96rpx - ${navigationBarHeight}px)`;
this.setData({
contentHeight: contentHeight,
});
console.log('内容区域高度CSS表达式:', contentHeight);
} catch (error) {
console.error('生成内容高度表达式失败:', error);
this.setData({
contentHeight: 'calc(100vh - 96rpx)',
});
}
},
},
});
CSS (css):
.chat-box {
padding-top: 32rpx;
box-sizing: border-box;
}
.t-chat__list {
padding: 0 0 0 32rpx;
box-sizing: border-box;
}
.t-chat-message {
padding: 0 32rpx;
}
JSON (javascript):
{
"component": true,
"styleIsolation": "shared",
"usingComponents": {
"t-chat-message": "tdesign-miniprogram/chat-message/chat-message",
"t-chat-content": "tdesign-miniprogram/chat-content/chat-content",
"t-chat": "tdesign-miniprogram/chat-list/chat-list",
"t-chat-sender": "tdesign-miniprogram/chat-sender/chat-sender",
"t-chat-actionbar": "tdesign-miniprogram/chat-actionbar/chat-actionbar",
"t-toast": "tdesign-miniprogram/toast/toast"
}
}
自定义
t-chat-message支持content自定义,使用建议:渲染聊天消息统一用 t-chat-content;仅在需要“单独使用 Markdown 组件”或自定义管线时使用 t-chat-markdown。也支持别的 markdown 渲染组件,选择其他 markdown 渲染库由用户自行安装。
WXML (html):
<view class="chat-box chart-chat" style="height: {{contentHeight}};">
<t-chat>
<block wx:for="{{chatList}}" wx:key="index" wx:for-index="chatIndex">
<t-chat-message
class="{{item.message.role}}"
avatar="{{item.avatar || ''}}"
name="{{item.name || ''}}"
datetime="{{item.datetime || ''}}"
role="{{item.message.role || 'assistant'}}"
placement="{{item.message.role === 'user' ? 'right' : 'left'}}"
>
<view slot="content">
<block
wx:for="{{item.message.content}}"
wx:for-item="contentItem"
wx:for-index="contentIndex"
wx:key="contentIndex"
>
<t-chat-content
content="{{contentItem}}"
wx:if="{{contentItem.type === 'text' || contentItem.type === 'markdown'}}"
/>
<!-- 封装的图表组件见源码 -->
<chart-component wx:if="{{contentItem.type === 'chart'}}" el="normalLine" options="{{contentItem}}" />
</block>
</view>
<t-chat-actionbar
wx:if="{{chatIndex !== chatList.length - 1 && item.message.status === 'complete' && item.message.role === 'assistant'}}"
slot="actionbar"
placement="end"
bind:actions="handleAction"
/>
</t-chat-message>
</block>
<view slot="footer">
<t-chat-sender
value="{{value}}"
loading="{{loading}}"
disabled="{{disabled}}"
renderPresets="{{renderPresets}}"
bind:send="onSend"
bind:stop="onStop"
bind:focus="onFocus"
autoRiseWithKeyboard="{{true}}"
/>
</view>
</t-chat>
</view>
<t-toast id="t-toast" />
JS (javascript):
import Toast from 'tdesign-miniprogram/toast';
import { getNavigationBarHeight } from '../../../utils/utils';
const sleep = (ms) => {
return new Promise((resolve) => setTimeout(resolve, ms));
};
const fetchStream = async (str, options) => {
const { success, complete, delay = 100 } = options;
const arr = str.split('');
for (let i = 0; i < arr.length; i += 1) {
// eslint-disable-next-line no-await-in-loop
await sleep(delay);
success(arr[i]);
}
complete();
};
Component({
properties: {
isActive: {
type: Boolean,
value: false,
observer: function (v) {
this.setData({
value: v ? '南极的自动提款机叫什么名字' : '', // 输入框的值
});
},
},
},
options: {
styleIsolation: 'shared',
},
data: {
renderPresets: [
{
name: 'send',
type: 'icon',
},
],
chatList: [
{
avatar: 'https://tdesign.gtimg.com/site/chat-avatar.png',
message: {
role: 'assistant',
content: [
{
type: 'text',
data: '欢迎使用TDesign智能图表分析助手,请输入你的问题',
},
],
},
},
],
value: '', // 输入框的值
loading: false, // 加载状态
disabled: false, // 禁用状态
inputStyle: '', // 动态样式
contentHeight: '100vh', // 内容高度
},
methods: {
// 发送消息事件处理
onSend(e) {
const { value } = e.detail;
if (!value || value.trim() === '') return;
// 创建用户消息对象
const userMessage = {
message: {
role: 'user',
content: [
{
type: 'text',
data: value.trim(),
},
],
},
};
// 将用户消息插入到chatList的开头(因为reverse为true,所以用unshift)
this.setData({
chatList: [userMessage, ...this.data.chatList],
value: '', // 清空输入框
});
// 模拟助手回复(可选)
this.simulateAssistantReply(value.trim());
},
// 停止事件处理
onStop() {
console.log('停止发送');
this.setData({
loading: false,
});
},
// 聚焦事件处理
onFocus() {
console.log('输入框聚焦');
},
// 获取当前时间
getCurrentTime() {
const now = new Date();
const hours = now.getHours().toString().padStart(2, '0');
const minutes = now.getMinutes().toString().padStart(2, '0');
return `${hours}:${minutes}`;
},
// 模拟助手回复
simulateAssistantReply() {
this.setData({ loading: true });
const assistantMessage = {
avatar: 'https://tdesign.gtimg.com/site/chat-avatar.png',
message: {
role: 'assistant',
content: [
{
type: 'markdown',
data: '',
},
],
},
};
const list = [assistantMessage, ...this.data.chatList];
this.setData({
chatList: list,
});
const that = this;
wx.nextTick(async () => {
await fetchStream(
'今日上午北京道路车辆通行状况9:00的峰值(1320),可能显示早高峰拥堵最严重时段10:00后缓慢回落,可以得出如下折线图:',
{
success(result) {
if (!that.data.loading) return;
that.data.chatList[0].message.content[0].data += result;
that.setData({
chatList: that.data.chatList,
});
},
complete() {},
},
);
if (!that.data.loading) return;
that.data.chatList[0].message.content.push(
{
type: 'chart',
data: {
id: 8379.117942106575,
chartType: 'line',
options: {
xAxis: {
boundaryGap: true,
type: 'category',
data: ['0:00', '1:00', '2:00', '3:00', '4:00', '5:00', '6:00'],
},
yAxis: {
type: 'value',
},
series: [
{
data: [500, 401, 382, 433, 560, 630, 720],
type: 'line',
},
],
},
},
},
{
type: 'markdown',
data: '',
},
);
that.setData({
chatList: that.data.chatList,
});
await fetchStream(
'今日晚上北京道路车辆通行状况18:00的峰值(1322),可能显示早高峰拥堵最严重时段21:00后缓慢回落,可以得出如下折线图:',
{
success(result) {
if (!that.data.loading) return;
that.data.chatList[0].message.content[2].data += result;
that.setData({
chatList: that.data.chatList,
});
},
complete() {},
},
);
if (!that.data.loading) return;
that.data.chatList[0].message.content.push({
type: 'chart',
data: {
id: 9954.694158956194,
chartType: 'line',
options: {
xAxis: {
boundaryGap: true,
type: 'category',
data: ['0:00', '1:00', '2:00', '3:00', '4:00', '5:00', '6:00'],
},
yAxis: {
type: 'value',
},
series: [
{
data: [500, 401, 382, 433, 560, 630, 720],
type: 'line',
},
],
},
},
strategy: 'append',
status: 'complete',
});
that.data.chatList[0].message.status = 'complete';
that.setData({
chatList: that.data.chatList,
loading: false,
});
});
},
handleAction(e) {
const { name, active, data } = e.detail;
let message = '';
switch (name) {
case 'replay':
message = '重新生成';
break;
case 'copy':
console.log(data);
message = '复制成功';
break;
case 'good':
message = active ? '点赞成功' : '取消点赞';
break;
case 'bad':
message = active ? '点踩成功' : '取消点踩';
break;
case 'share':
message = '分享功能';
break;
default:
message = `执行了${name}操作`;
}
Toast({
context: this,
selector: '#t-toast',
message,
theme: 'success',
});
},
},
lifetimes: {
attached: function () {
/**
* 计算内容区域高度
* 生成CSS calc表达式:calc(100vh - 96rpx - 导航高度 - 底部安全区域高度)
*/
try {
// 获取当前的导航栏高度和安全区域高度
const navigationBarHeight = getNavigationBarHeight() || 0;
// 生成CSS calc表达式字符串
const contentHeight = `calc(100vh - 96rpx - ${navigationBarHeight}px)`;
this.setData({
contentHeight: contentHeight,
});
console.log('内容区域高度CSS表达式:', contentHeight);
} catch (error) {
console.error('生成内容高度表达式失败:', error);
this.setData({
contentHeight: 'calc(100vh - 96rpx)',
});
}
},
},
});
CSS (css):
.chat-box {
padding-top: 32rpx;
box-sizing: border-box;
}
.t-chat__list {
padding: 0 0 0 32rpx;
box-sizing: border-box;
}
.t-chat-message {
padding: 0 32rpx;
}
.chart-chat .assistant .t-chat__detail {
width: 100%;
}
JSON (javascript):
{
"component": true,
"styleIsolation": "shared",
"usingComponents": {
"t-toast": "tdesign-miniprogram/toast/toast",
"t-chat-message": "tdesign-miniprogram/chat-message/chat-message",
"t-chat-content": "tdesign-miniprogram/chat-content/chat-content",
"t-chat": "tdesign-miniprogram/chat-list/chat-list",
"t-chat-sender": "tdesign-miniprogram/chat-sender/chat-sender",
"t-chat-actionbar": "tdesign-miniprogram/chat-actionbar/chat-actionbar",
"chart-component": "../chart-component"
}
}
文案助手
WXML (html):
<view class="chat-box" style="height: {{contentHeight}};">
<t-chat>
<block wx:for="{{chatList}}" wx:key="index" wx:for-index="chatIndex">
<t-chat-message
avatar="{{item.avatar || ''}}"
name="{{item.name || ''}}"
datetime="{{item.datetime || ''}}"
content="{{item.message.content}}"
role="{{item.message.role}}"
chatContentProps="{{chatContentProps}}"
placement="{{item.message.role === 'user' ? 'right' : 'left'}}"
>
<t-chat-actionbar
wx:if="{{chatIndex !== chatList.length - 1 && item.message.status === 'complete' && item.message.role === 'assistant'}}"
slot="actionbar"
actionBar="{{customActionBar}}"
bind:actions="handleAction"
/>
</t-chat-message>
</block>
<view slot="footer">
<t-chat-sender
value="{{value}}"
loading="{{loading}}"
disabled="{{disabled}}"
bind:send="onSend"
fileList="{{fileList}}"
bind:stop="onStop"
bind:focus="onFocus"
bind:keyboardheightchange="onkeyboardheightchange"
bind:updateVisible="onUpdateVisible"
bind:fileDelete="onFileDelete"
bind:fileChange="onFileChange"
attachmentsProps="{{attachmentsProps}}"
renderPresets="{{renderPresets}}"
autoRiseWithKeyboard="{{true}}"
visible="{{visible}}"
/>
</view>
</t-chat>
</view>
<t-toast id="t-toast" />
JS (javascript):
import Toast from 'tdesign-miniprogram/toast';
import { getNavigationBarHeight } from '../../../utils/utils';
const mockData1 =
'🌼宝子们,春天来啦,这些户外郊游打卡地你必须知道👏\n\n🌟郊野公园\n这里有大片的草地和各种花卉,随便一拍都是大片既视感📷。还能放风筝、野餐,享受惬意的春日时光。\n\n🌳植物园\n各种珍稀植物汇聚于此,仿佛置身于绿色的海洋。漫步其中,感受大自然的神奇与美丽。\n\n💧湖边湿地\n湖水清澈,周围生态环境优越。能看到很多候鸟和水生植物,是亲近自然的好去处。\n\n宝子们,赶紧收拾行囊,去这些地方打卡吧😜。\n\n#春天郊游 #打卡目的地 #户外之旅 #春日美景';
const sleep = (ms) => {
return new Promise((resolve) => setTimeout(resolve, ms));
};
const fetchStream = async (str, options) => {
const { success, complete, delay = 100 } = options;
const arr = str.split('');
for (let i = 0; i < arr.length; i += 1) {
// eslint-disable-next-line no-await-in-loop
await sleep(delay);
success(arr[i]);
}
complete();
};
Component({
properties: {
isActive: {
type: Boolean,
value: false,
observer: function (v) {
// 延迟 30ms,避免 hidden 场景下, value 变更无法触发 textarea 的自动换行
// 代码片段(iOS 真机可复现):https://developers.weixin.qq.com/s/7UoAYgmr8G4k
setTimeout(() => {
this.setData({
value: v ? '根据所提供的材料总结一篇文章,推荐春天户外郊游打卡目的地,需要符合小红书平台写作风格' : '', // 输入框的值
});
}, 30);
},
},
},
data: {
customActionBar: ['copy', 'good', 'bad'],
chatList: [
{
avatar: 'https://tdesign.gtimg.com/site/chat-avatar.png',
message: {
role: 'assistant',
content: [
{
type: 'text',
data: '欢迎使用TDesign文案写作助手,可以先上传你需要参考的文件,输入你要撰写的主题~',
},
],
},
},
],
value: '', // 输入框的值
loading: false, // 加载状态
disabled: false, // 禁用状态
inputStyle: '', // 动态样式
attachmentsProps: {
items: [],
removable: true,
imageViewer: true,
addable: false,
},
renderPresets: [
{
name: 'upload',
presets: ['uploadCamera', 'uploadImage', 'uploadAttachment'],
status: '',
},
{
name: 'send',
type: 'icon',
},
],
fileList: [],
visible: false, // 是否显示选择文件面板
chatContentProps: {
thinking: { maxHeight: 100, collapsed: true },
},
contentHeight: '100vh', // 内容高度
},
methods: {
// 发送消息事件处理
onSend(e) {
const { value } = e.detail;
if (!value || value.trim() === '') return;
// 创建用户消息对象
const content = [
{
type: 'text',
data: value.trim(),
},
];
const attachments = this.data.attachmentsProps.items.map((item) => {
return {
...item,
status: 'success',
};
});
content.unshift({
type: 'attachment',
data: attachments,
});
this.setData({
attachmentsProps: {
...this.data.attachmentsProps,
items: [],
},
fileList: [],
});
const userMessage = {
message: {
role: 'user',
content,
},
};
// 将用户消息插入到chatList的开头(因为reverse为true,所以用unshift)
this.setData({
chatList: [userMessage, ...this.data.chatList],
value: '', // 清空输入框
});
// 模拟助手回复(可选)
this.simulateAssistantReply(value.trim());
},
// 停止事件处理
onStop() {
console.log('停止发送');
this.setData({
loading: false,
});
},
// 聚焦事件处理
onFocus() {
console.log('输入框聚焦');
},
// 打开选择文件界面
onUpdateVisible(e) {
const visible = e.detail;
console.log('上传面板显示状态:', visible);
this.setData({ visible });
},
onFileDelete() {
this.setData({
attachmentsProps: {
...this.data.attachmentsProps,
items: [],
},
});
},
onFileChange(e) {
const { files } = e.detail;
this.setData({ attachmentsProps: { ...this.data.attachmentsProps, items: files } });
this.setData({ fileList: files });
},
// 模拟助手回复
simulateAssistantReply() {
this.setData({ loading: true });
const assistantMessage = {
avatar: 'https://tdesign.gtimg.com/site/chat-avatar.png',
message: {
role: 'assistant',
content: [
{
type: 'markdown',
data: '',
},
],
},
};
this.setData({
chatList: [assistantMessage, ...this.data.chatList],
});
const that = this;
wx.nextTick(() => {
fetchStream(mockData1, {
success(result) {
if (!that.data.loading) return;
that.data.chatList[0].message.content[0].data += result;
that.setData({
chatList: that.data.chatList,
});
},
complete() {
that.data.chatList[0].message.status = 'complete';
that.setData({
chatList: that.data.chatList,
});
that.setData({
loading: false,
});
},
});
});
},
handleAction(e) {
const { name, active, data } = e.detail;
let message = '';
switch (name) {
case 'replay':
message = '重新生成';
break;
case 'copy':
console.log(data);
message = '复制成功';
break;
case 'good':
message = active ? '点赞成功' : '取消点赞';
break;
case 'bad':
message = active ? '点踩成功' : '取消点踩';
break;
case 'share':
message = '分享功能';
break;
default:
message = `执行了${name}操作`;
}
Toast({
context: this,
selector: '#t-toast',
message,
theme: 'success',
});
},
},
lifetimes: {
attached: function () {
/**
* 计算内容区域高度
* 生成CSS calc表达式:calc(100vh - 96rpx - 导航高度 - 底部安全区域高度)
*/
try {
// 获取当前的导航栏高度和安全区域高度
const navigationBarHeight = getNavigationBarHeight() || 0;
// 生成CSS calc表达式字符串
const contentHeight = `calc(100vh - 96rpx - ${navigationBarHeight}px)`;
this.setData({
contentHeight: contentHeight,
});
console.log('内容区域高度CSS表达式:', contentHeight);
} catch (error) {
console.error('生成内容高度表达式失败:', error);
this.setData({
contentHeight: 'calc(100vh - 96rpx)',
});
}
},
},
});
CSS (css):
.chat-box {
padding-top: 32rpx;
box-sizing: border-box;
}
.t-chat__list {
padding: 0 0 0 32rpx;
box-sizing: border-box;
}
.t-chat-message {
padding: 0 32rpx;
}
.preview {
padding: 16rpx;
display: flex;
justify-content: space-between;
border: 1px solid black;
}
JSON (javascript):
{
"component": true,
"styleIsolation": "shared",
"usingComponents": {
"t-chat-message": "tdesign-miniprogram/chat-message/chat-message",
"t-chat-content": "tdesign-miniprogram/chat-content/chat-content",
"t-chat": "tdesign-miniprogram/chat-list/chat-list",
"t-chat-sender": "tdesign-miniprogram/chat-sender/chat-sender",
"t-chat-actionbar": "tdesign-miniprogram/chat-actionbar/chat-actionbar",
"t-toast": "tdesign-miniprogram/toast/toast"
}
}
图像生成
WXML (html):
<view class="chat-box image-chat" style="height: {{contentHeight}};">
<t-chat>
<block wx:for="{{chatList}}" wx:key="index" wx:for-index="chatIndex">
<t-chat-message
avatar="{{item.avatar || ''}}"
name="{{item.name || ''}}"
datetime="{{item.datetime || ''}}"
role="{{item.message.role}}"
placement="{{item.message.role === 'user' ? 'right' : 'left'}}"
>
<view wx:if="{{item.message.role === 'user'}}" slot="content">
<block
wx:for="{{item.message.content}}"
wx:for-item="contentItem"
wx:for-index="contentIndex"
wx:key="contentIndex"
>
<t-chat-content
content="{{contentItem}}"
wx:if="{{contentItem.type === 'text' || contentItem.type === 'markdown'}}"
/>
</block>
</view>
<view wx:else style="width: 100%" slot="content">
<block
wx:for="{{item.message.content}}"
wx:for-item="contentItem"
wx:for-index="contentIndex"
wx:key="contentIndex"
>
<t-chat-content
content="{{contentItem}}"
wx:if="{{contentItem.type === 'text' || contentItem.type === 'markdown'}}"
/>
<view wx:else class="attachment-slide">
<t-attachments items="{{contentItem.data}}" inChat="{{true}}" removable="{{false}}" />
</view>
</block>
</view>
<t-chat-actionbar
wx:if="{{chatIndex !== chatList.length - 1 && item.message.status === 'complete' && item.message.role === 'assistant'}}"
slot="actionbar"
placement="end"
bind:actions="handleAction"
/>
</t-chat-message>
</block>
<view slot="footer">
<t-chat-sender
value="{{value}}"
loading="{{loading}}"
disabled="{{disabled}}"
renderPresets="{{renderPresets}}"
bind:send="onSend"
bind:stop="onStop"
bind:focus="onFocus"
autoRiseWithKeyboard="{{true}}"
/>
</view>
</t-chat>
</view>
<t-toast id="t-toast" />
JS (javascript):
import Toast from 'tdesign-miniprogram/toast';
import { getNavigationBarHeight } from '../../../utils/utils';
const sleep = (ms) => {
return new Promise((resolve) => setTimeout(resolve, ms));
};
const fetchStream = async (str, options) => {
const { success, complete, delay = 100 } = options;
const arr = str.split('');
for (let i = 0; i < arr.length; i += 1) {
// eslint-disable-next-line no-await-in-loop
await sleep(delay);
success(arr[i]);
}
complete();
};
Component({
properties: {
isActive: {
type: Boolean,
value: false,
observer: function (v) {
this.setData({
value: v ? '请为Tdesign设计三张品牌宣传图' : '', // 输入框的值
});
},
},
},
options: {
styleIsolation: 'shared',
},
data: {
renderPresets: [
{
name: 'send',
type: 'icon',
},
],
customActionBar: ['good', 'bad'],
chatList: [
{
avatar: 'https://tdesign.gtimg.com/site/chat-avatar.png',
message: {
role: 'assistant',
content: [
{
type: 'text',
data: '欢迎使用TDesign智能生图助手,请先写下你的创意,可以试试上传参考图哦~',
},
],
},
},
],
value: '', // 输入框的值
loading: false, // 加载状态
disabled: false, // 禁用状态
inputStyle: '', // 输入框动态样式
attachmentsProps: {
items: [],
removable: true,
imageViewer: true,
addable: false,
},
contentHeight: '100vh', // 内容高度
},
methods: {
// 发送消息事件处理
onSend(e) {
const { value } = e.detail;
if (!value || value.trim() === '') return;
// 创建用户消息对象
const userMessage = {
message: {
role: 'user',
content: [
{
type: 'text',
data: value.trim(),
},
],
},
};
// 将用户消息插入到chatList的开头(因为reverse为true,所以用unshift)
this.setData({
chatList: [userMessage, ...this.data.chatList],
value: '', // 清空输入框
});
// 模拟助手回复(可选)
this.simulateAssistantReply(value.trim());
},
// 停止事件处理
onStop() {
console.log('停止发送');
this.setData({
loading: false,
});
},
// 聚焦事件处理
onFocus() {
console.log('输入框聚焦');
},
// 打开选择文件界面
onUpdateVisible() {
const that = this;
wx.chooseMessageFile({
count: 1,
type: 'file',
success(res) {
const tempFile = res.tempFiles[0];
console.log('选择的文件信息:', tempFile);
const item = {
fileType: 'doc',
name: tempFile.name,
url: tempFile.path,
size: tempFile.size,
status: 'success',
};
that.setData({
attachmentsProps: {
...that.data.attachmentsProps,
items: [item],
},
});
},
fail(err) {
console.error('选择文件失败:', err);
},
});
},
// 模拟助手回复
simulateAssistantReply() {
this.setData({ loading: true });
const assistantMessage = {
avatar: 'https://tdesign.gtimg.com/site/chat-avatar.png',
message: {
role: 'assistant',
content: [
{
type: 'markdown',
data: '',
},
],
},
};
this.setData({
chatList: [assistantMessage, ...this.data.chatList],
});
const that = this;
wx.nextTick(async () => {
await fetchStream('接下来我将生成符合要求的图片', {
success(result) {
if (!that.data.loading) return;
that.data.chatList[0].message.content[0].data += result;
that.setData({
chatList: that.data.chatList,
});
},
complete() {},
});
if (!that.data.loading) return;
that.data.chatList[0].message.content.push({
type: 'imageview',
status: 'complete',
data: [
{
name: 'sample1.png',
url: 'https://tdesign.gtimg.com/site/square.png',
fileType: 'image',
status: 'success',
size: 1032,
width: 128,
height: 128,
},
{
name: 'sample2.png',
url: 'https://tdesign.gtimg.com/site/square.png',
fileType: 'image',
status: 'success',
size: 1032,
width: 128,
height: 128,
},
{
name: 'sample3.png',
url: 'https://tdesign.gtimg.com/site/square.png',
fileType: 'image',
status: 'success',
size: 1032,
width: 128,
height: 128,
},
{
name: 'sample4.png',
url: 'https://tdesign.gtimg.com/site/square.png',
fileType: 'image',
status: 'success',
size: 1032,
width: 128,
height: 128,
},
{
name: 'sample5.png',
url: 'https://tdesign.gtimg.com/site/square.png',
fileType: 'image',
status: 'success',
size: 1032,
width: 128,
height: 128,
},
],
});
that.data.chatList[0].message.status = 'complete';
that.setData({
chatList: that.data.chatList,
loading: false,
});
});
},
handleAction(e) {
const { name, active, data } = e.detail;
console.log('----', name);
let message = '';
switch (name) {
case 'replay':
message = '重新生成';
break;
case 'copy':
console.log(data);
message = '复制成功';
break;
case 'good':
message = active ? '点赞成功' : '取消点赞';
break;
case 'bad':
message = active ? '点踩成功' : '取消点踩';
break;
case 'share':
message = '分享功能';
break;
default:
message = `执行了${name}操作`;
}
Toast({
context: this,
selector: '#t-toast',
message,
theme: 'success',
});
},
},
lifetimes: {
attached: function () {
/**
* 计算内容区域高度
* 生成CSS calc表达式:calc(100vh - 96rpx - 导航高度 - 底部安全区域高度)
*/
try {
// 获取当前的导航栏高度和安全区域高度
const navigationBarHeight = getNavigationBarHeight() || 0;
// 生成CSS calc表达式字符串
const contentHeight = `calc(100vh - 96rpx - ${navigationBarHeight}px)`;
this.setData({
contentHeight: contentHeight,
});
console.log('内容区域高度CSS表达式:', contentHeight);
} catch (error) {
console.error('生成内容高度表达式失败:', error);
this.setData({
contentHeight: 'calc(100vh - 96rpx)',
});
}
},
},
});
CSS (css):
.chat-box {
padding-top: 32rpx;
box-sizing: border-box;
}
.image-chat .t-chat__list {
padding: 0 0 0 0;
box-sizing: border-box;
}
.t-chat-message {
padding: 0 32rpx;
}
.image-chat .t-chat__inner.assistant .t-chat__avatar {
padding-left: 32rpx;
}
.image-chat .assistant,
.image-chat .assistant .t-chat__detail {
width: 100%;
}
.attachment-slide {
height: 274rpx;
width: 100%;
}
.attachment-slide .t-attachments {
padding-right: 32rpx;
padding-top: 24rpx;
box-sizing: border-box;
position: fixed;
z-index: 2;
left: 0;
right: 0;
}
.attachment-slide .t-attachments .t-attachments__files:first-child {
padding-left: 120rpx;
}
.attachment-slide .t-attachments .t-attachments__files:last-child {
padding-right: 32rpx;
}
JSON (javascript):
{
"component": true,
"styleIsolation": "shared",
"usingComponents": {
"t-chat-message": "tdesign-miniprogram/chat-message/chat-message",
"t-chat-content": "tdesign-miniprogram/chat-content/chat-content",
"t-chat": "tdesign-miniprogram/chat-list/chat-list",
"t-chat-sender": "tdesign-miniprogram/chat-sender/chat-sender",
"t-chat-actionbar": "tdesign-miniprogram/chat-actionbar/chat-actionbar",
"t-attachments": "tdesign-miniprogram/attachments/attachments",
"t-toast": "tdesign-miniprogram/toast/toast"
}
}
任务规划
WXML (html):
<view class="chat-box" style="height: {{contentHeight}};">
<t-chat>
<block wx:for="{{chatList}}" wx:key="index" wx:for-index="chatIndex">
<t-chat-message
avatar="{{item.avatar || ''}}"
name="{{item.name || ''}}"
datetime="{{item.datetime || ''}}"
role="{{item.message.role}}"
placement="{{item.message.role === 'user' ? 'right' : 'left'}}"
>
<view slot="content">
<block
wx:for="{{item.message.content}}"
wx:for-item="contentItem"
wx:for-index="contentIndex"
wx:key="contentIndex"
>
<t-chat-content
content="{{contentItem}}"
wx:if="{{contentItem.type === 'text' || contentItem.type === 'markdown'}}"
/>
<view class="step" wx:if="{{contentItem.type === 'agent'}}">
<t-steps layout="vertical" current="{{contentItem.content.steps.length}}">
<t-step-item wx:for="{{contentItem.content.steps}}" wx:key="index" title="{{item.step}}">
<view slot="content" class="step-text-list">
<view
wx:for="{{item.tasks}}"
wx:key="index"
wx:for-item="taskItem"
class="step-text {{taskItem.type}}"
>
<t-icon
wx:if="{{taskItem.type === 'command'}}"
name="control-platform"
size="32rpx"
class="step-icon"
/>
{{taskItem.text}}
</view>
</view>
</t-step-item>
</t-steps>
</view>
</block>
</view>
<t-chat-actionbar
wx:if="{{chatIndex !== chatList.length - 1 && item.message.status === 'complete' && item.message.role === 'assistant'}}"
slot="actionbar"
placement="end"
bind:actions="handleAction"
/>
</t-chat-message>
</block>
<view slot="footer">
<t-chat-sender
value="{{value}}"
loading="{{loading}}"
disabled="{{disabled}}"
renderPresets="{{renderPresets}}"
bind:send="onSend"
bind:stop="onStop"
bind:focus="onFocus"
autoRiseWithKeyboard="{{true}}"
/>
</view>
</t-chat>
</view>
<t-toast id="t-toast" />
JS (javascript):
import Toast from 'tdesign-miniprogram/toast';
import { getNavigationBarHeight } from '../../../utils/utils';
const sleep = (ms) => {
return new Promise((resolve) => setTimeout(resolve, ms));
};
const fetchStream = async (str, options) => {
const { success, complete, delay = 100 } = options;
const arr = str.split('');
for (let i = 0; i < arr.length; i += 1) {
// eslint-disable-next-line no-await-in-loop
await sleep(delay);
success(arr[i]);
}
complete();
};
Component({
properties: {
contentHeight: {
type: String,
value: '100vh',
},
isActive: {
type: Boolean,
value: false,
observer: function (v) {
this.setData({
value: v ? '请帮我做一个5岁儿童生日聚会的规划' : '', // 输入框的值
});
},
},
},
options: {
styleIsolation: 'shared',
},
data: {
renderPresets: [
{
name: 'send',
type: 'icon',
},
],
chatList: [
{
avatar: 'https://tdesign.gtimg.com/site/chat-avatar.png',
message: {
role: 'assistant',
content: [
{
type: 'text',
data: '欢迎使用TDesign Agent家庭活动策划助手,请给我布置任务吧~',
},
],
},
},
],
value: '', // 输入框的值
loading: false, // 加载状态
disabled: false, // 禁用状态
inputStyle: '', // 输入框动态样式
},
methods: {
// 发送消息事件处理
onSend(e) {
const { value } = e.detail;
if (!value || value.trim() === '') return;
// 创建用户消息对象
const userMessage = {
message: {
role: 'user',
content: [
{
type: 'text',
data: value.trim(),
},
],
},
};
// 将用户消息插入到chatList的开头(因为reverse为true,所以用unshift)
this.setData({
chatList: [userMessage, ...this.data.chatList],
value: '', // 清空输入框
});
// 模拟助手回复(可选)
this.simulateAssistantReply(value.trim());
},
// 停止事件处理
onStop() {
console.log('停止发送');
this.setData({
loading: false,
});
},
// 聚焦事件处理
onFocus() {
console.log('输入框聚焦');
},
// 模拟助手回复
simulateAssistantReply() {
this.setData({ loading: true });
const assistantMessage = {
avatar: 'https://tdesign.gtimg.com/site/chat-avatar.png',
message: {
role: 'assistant',
content: [
{
type: 'markdown',
data: '',
},
],
},
};
this.setData({
chatList: [assistantMessage, ...this.data.chatList],
});
const that = this;
wx.nextTick(async () => {
await fetchStream('为5岁小朋友准备一场生日派对,我会根据要求准备合适方案,计划从以下几个步骤进行准备:', {
success(result) {
if (!that.data.loading) return;
that.data.chatList[0].message.content[0].data += result;
that.setData({
chatList: that.data.chatList,
});
},
complete() {},
});
if (!that.data.loading) return;
that.data.chatList[0].message.content.push({
type: 'agent',
id: 'task1',
content: {
text: '',
steps: [],
},
});
that.setData({
chatList: that.data.chatList,
});
await fetchStream('生日聚会规划任务已分解为3个执行阶段', {
success(result) {
if (!that.data.loading) return;
that.data.chatList[0].message.content[1].content.text += result;
that.setData({
chatList: that.data.chatList,
});
},
complete() {},
});
if (!that.data.loading) return;
that.data.chatList[0].message.content[1].content.steps.push({
step: '确定派对餐饮方案',
agent_id: 'a1',
tasks: [
{
type: 'command',
text: '',
},
],
status: 'finish',
});
that.setData({
chatList: that.data.chatList,
});
await fetchStream('调用智能搜索工具', {
success(result) {
if (!that.data.loading) return;
that.data.chatList[0].message.content[1].content.steps[0].tasks[0].text += result;
that.setData({
chatList: that.data.chatList,
});
},
complete() {},
});
if (!that.data.loading) return;
that.data.chatList[0].message.content[1].content.steps[0].tasks.push({
type: 'command',
text: '',
});
that.setData({
chatList: that.data.chatList,
});
await fetchStream('已筛选出3种高性价比菜单方案,开始进行营养匹配', {
success(result) {
if (!that.data.loading) return;
that.data.chatList[0].message.content[1].content.steps[0].tasks[1].text += result;
that.setData({
chatList: that.data.chatList,
});
},
complete() {},
});
if (!that.data.loading) return;
that.data.chatList[0].message.content[1].content.steps[0].tasks.push({
type: 'result',
text: '',
});
that.setData({
chatList: that.data.chatList,
});
await fetchStream('主菜是香草烤鸡(无麸质),准备耗时45分钟;', {
success(result) {
if (!that.data.loading) return;
that.data.chatList[0].message.content[1].content.steps[0].tasks[2].text += result;
that.setData({
chatList: that.data.chatList,
});
},
complete() {},
});
if (!that.data.loading) return;
that.data.chatList[0].message.content[1].content.steps.push({
step: '准备派对现场布置',
agent_id: 'a2',
tasks: [
{
type: 'command',
text: '',
},
],
status: 'finish',
});
that.setData({
chatList: that.data.chatList,
});
await fetchStream('调用智能搜索工具,搜索儿童派对用品清单', {
success(result) {
if (!that.data.loading) return;
that.data.chatList[0].message.content[1].content.steps[1].tasks[0].text += result;
that.setData({
chatList: that.data.chatList,
});
},
complete() {},
});
if (!that.data.loading) return;
that.data.chatList[0].message.content[1].content.steps[1].tasks.push({
type: 'result',
text: '',
});
that.setData({
chatList: that.data.chatList,
});
await fetchStream(
'推荐现场布置方案:餐具(一次性纸盘、刀叉套装)、杯子、纸巾、一次性桌布,装饰气球、横幅、礼帽等',
{
success(result) {
if (!that.data.loading) return;
that.data.chatList[0].message.content[1].content.steps[1].tasks[1].text += result;
that.setData({
chatList: that.data.chatList,
});
},
complete() {},
},
);
if (!that.data.loading) return;
that.data.chatList[0].message.content[1].content.steps.push({
step: '策划派对活动',
agent_id: 'a1',
tasks: [
{
type: 'command',
text: '',
},
],
status: 'finish',
});
that.setData({
chatList: that.data.chatList,
});
await fetchStream('搜索儿童派对游戏', {
success(result) {
if (!that.data.loading) return;
that.data.chatList[0].message.content[1].content.steps[2].tasks[0].text += result;
that.setData({
chatList: that.data.chatList,
});
},
complete() {},
});
if (!that.data.loading) return;
that.data.chatList[0].message.content[1].content.steps[2].tasks.push({
type: 'command',
text: '',
});
that.setData({
chatList: that.data.chatList,
});
await fetchStream('整理信息并进行合理性分析,安全性评估', {
success(result) {
if (!that.data.loading) return;
that.data.chatList[0].message.content[1].content.steps[2].tasks[1].text += result;
that.setData({
chatList: that.data.chatList,
});
},
complete() {},
});
if (!that.data.loading) return;
that.data.chatList[0].message.content[1].content.steps[2].tasks.push({
type: 'result',
text: '',
});
that.setData({
chatList: that.data.chatList,
});
await fetchStream(
'派对总时长建议控制在1.5小时,符合5岁儿童注意力持续时间,每位小朋友到达时可以在拍照区留影,可设置一个签到',
{
success(result) {
if (!that.data.loading) return;
that.data.chatList[0].message.content[1].content.steps[2].tasks[2].text += result;
that.setData({
chatList: that.data.chatList,
});
},
complete() {},
},
);
that.data.chatList[0].message.status = 'complete';
that.setData({
chatList: that.data.chatList,
});
that.setData({
loading: false,
});
});
},
handleAction(e) {
const { name, active, data } = e.detail;
let message = '';
switch (name) {
case 'replay':
message = '重新生成';
break;
case 'copy':
console.log(data);
message = '复制成功';
break;
case 'good':
message = active ? '点赞成功' : '取消点赞';
break;
case 'bad':
message = active ? '点踩成功' : '取消点踩';
break;
case 'share':
message = '分享功能';
break;
default:
message = `执行了${name}操作`;
}
Toast({
context: this,
selector: '#t-toast',
message,
theme: 'success',
});
},
},
lifetimes: {
attached: function () {
/**
* 计算内容区域高度
* 生成CSS calc表达式:calc(100vh - 96rpx - 导航高度 - 底部安全区域高度)
*/
try {
// 获取当前的导航栏高度和安全区域高度
const navigationBarHeight = getNavigationBarHeight() || 0;
// 生成CSS calc表达式字符串
const contentHeight = `calc(100vh - 96rpx - ${navigationBarHeight}px)`;
this.setData({
contentHeight: contentHeight,
});
console.log('内容区域高度CSS表达式:', contentHeight);
} catch (error) {
console.error('生成内容高度表达式失败:', error);
this.setData({
contentHeight: 'calc(100vh - 96rpx)',
});
}
},
},
});
CSS (css):
.chat-box {
padding-top: 32rpx;
box-sizing: border-box;
}
.t-chat__list {
padding: 0 0 0 32rpx;
box-sizing: border-box;
}
.t-chat-message {
padding: 0 32rpx;
}
.preview {
padding: 16rpx;
display: flex;
justify-content: space-between;
border: 1px solid var(--td-component-border);
}
.step {
padding-top: 24rpx;
}
.step-text-list {
display: flex;
flex-direction: column;
gap: 16rpx;
}
.step-text {
text-align: start;
}
.step-text.command {
padding: 16rpx;
border-radius: 16rpx;
background-color: var(--td-bg-color-secondarycontainer);
display: flex;
font-size: 28rpx;
line-height: 44rpx;
color: var(--td-text-color-secondary);
}
.step-text.result {
font-size: 28rpx;
line-height: 44rpx;
color: var(--td-text-color-primary);
}
.step-icon {
margin-right: 12rpx;
margin-top: 6rpx;
}
.t-steps-item__circle--finish {
background-color: transparent;
color: var(--td-text-color-primary);
border: 1px solid var(--td-text-color-primary);
width: 16px;
height: 16px;
}
.t-steps-item__circle--finish .t-icon {
font-size: 12px;
}
.t-steps-item__line--finish {
background-color: var(--td-component-border);
}
.t-steps-item__title--finish {
color: var(--td-text-color-primary);
font-weight: 600;
}
JSON (javascript):
{
"component": true,
"styleIsolation": "shared",
"usingComponents": {
"t-chat-message": "tdesign-miniprogram/chat-message/chat-message",
"t-chat-content": "tdesign-miniprogram/chat-content/chat-content",
"t-chat": "tdesign-miniprogram/chat-list/chat-list",
"t-chat-sender": "tdesign-miniprogram/chat-sender/chat-sender",
"t-chat-actionbar": "tdesign-miniprogram/chat-actionbar/chat-actionbar",
"t-steps": "tdesign-miniprogram/steps/steps",
"t-step-item": "tdesign-miniprogram/step-item/step-item",
"t-icon": "tdesign-miniprogram/icon/icon",
"t-toast": "tdesign-miniprogram/toast/toast"
}
}
API
ChatListProps
| 名称 | 类型 | 默认值 | 描述 | 必传 |
|---|---|---|---|---|
| style | Object | - | 样式 | N |
| custom-style | Object | - | 样式,一般用于开启虚拟化组件节点场景 | N |
| animation | String | skeleton | 动画效果,支持「渐变加载动画」,「闪烁加载动画」, 「骨架屏」三种。可选项:skeleton/moving/gradient/dot | N |
| data | Array | - | 对话列表的数据。TS 类型:Array<TdChatItemMeta> interface TdChatItemMeta { avatar?: string; name?:string; role?:string; datetime?: string; content?: string; status?: string }。详细类型定义 | N |
| layout | String | both | 对话布局形式,支持两侧对齐与左对齐。使用插槽自定义对话内容时不生效,得用t-chat-message的placement属性。可选项:both/single | N |
| reverse | Boolean | true | 是否表现为倒序 | N |
ChatListEvents
| 名称 | 参数 | 描述 |
|---|---|---|
| scroll | (detail: {scrollLeft, scrollTop, scrollHeight, scrollWidth, deltaX, deltaY}) | 滚动事件的回调 |
ChatListSlots
| 名称 | 描述 |
|---|---|
| actionbar | 自定义操作按钮的插槽 |
ChatLoading 对话加载
示例
引入
全局引入,在 miniprogram 根目录下的app.json中配置,局部引入,在需要引入的页面或组件的index.json中配置。
"usingComponents": {
"t-chat-loading": "tdesign-miniprogram/chat-loading/chat-loading"
}代码演示
Tips: 请确保开发者工具为打开状态。导入开发者工具后,依次执行:npm i > 构建npm包 > 勾选 "将JS编译成ES5"
01组件类型
支持多种加载中动效类型,包括 gradient、moving、dots
WXML (html):
<view class="chat-example">
<!-- <view class="chat-example-block">
<t-chat-loading animation="skeleton" />
</view> -->
<view class="chat-example-block">
<t-chat-loading animation="gradient" />
</view>
<view class="chat-example-block">
<t-chat-loading animation="moving" />
</view>
<view class="chat-example-block">
<t-chat-loading animation="dots" />
</view>
</view>
JS (javascript):
Component({
data: {
currentAnimation: 'skeleton',
loadingText: '',
animations: [
{ key: 'moving', text: '正在理解中...' },
{ key: 'gradient', text: '深度思考中...' },
{ key: 'circle', text: '加载中...' },
{ key: 'skeleton', text: '' },
],
},
onLoad() {
// 模拟动画切换
this.startAnimationCycle();
},
startAnimationCycle() {
let index = 0;
this.animationTimer = setInterval(() => {
const animation = this.data.animations[index];
this.setData({
currentAnimation: animation.key,
loadingText: animation.text,
});
index = (index + 1) % this.data.animations.length;
}, 2000); // 每2秒切换一次动画
},
onUnload() {
// 清理定时器
if (this.animationTimer) {
clearInterval(this.animationTimer);
}
},
// 手动切换动画类型
switchAnimation(e) {
const { animation } = e.currentTarget.dataset;
const animationData = this.data.animations.find((item) => item.key === animation);
if (animationData) {
this.setData({
currentAnimation: animationData.key,
loadingText: animationData.text,
});
}
},
});
CSS (css):
.chat-example {
background-color: var(--td-bg-color-container);
padding: 32rpx;
display: flex;
align-items: center;
gap: 48rpx;
}
JSON (javascript):
{
"usingComponents": {
"t-chat-loading": "tdesign-miniprogram/chat-loading/chat-loading",
"t-col": "tdesign-miniprogram/col/col",
"t-row": "tdesign-miniprogram/row/row"
}
}
带文案描述的加载组件
WXML (html):
<view class="chat-example">
<view class="chat-example-block">
<t-chat-loading animation="dots" text="加载中..." />
</view>
</view>
JS (javascript):
Component({
data: {},
});
CSS (css):
.chat-example {
background-color: var(--td-bg-color-container);
padding: 32rpx;
}
JSON (javascript):
{
"usingComponents": {
"t-chat-loading": "tdesign-miniprogram/chat-loading/chat-loading"
}
}
API
ChatLoadingProps
| 名称 | 类型 | 默认值 | 描述 | 必传 |
|---|---|---|---|---|
| style | Object | - | 样式 | N |
| custom-style | Object | - | 样式,一般用于开启虚拟化组件节点场景 | N |
| animation | String | moving | 加载的状态形式。可选项:skeleton/moving/gradient/dot | N |
| text | String | - | 加载过程展示的文字内容 | N |
ChatMarkdown Markdown内容
示例
引入
全局引入,在 miniprogram 根目录下的app.json中配置,局部引入,在需要引入的页面或组件的index.json中配置。
"usingComponents": {
"t-chat-markdown": "tdesign-miniprogram/chat-markdown/chat-markdown"
}代码演示
Tips: 请确保开发者工具为打开状态。导入开发者工具后,依次执行:npm i > 构建npm包 > 勾选 "将JS编译成ES5"
01基础Markdown样式
标题与文本
WXML (html):
<view class="chat-example-block">
<t-chat-markdown content="{{markdownContent}}" bind:click="handleLinkTap" />
</view>
JS (javascript):
import markdownData from './mock.js';
// 内置marked处理
Page({
data: {
markdownContent: markdownData,
},
handleLinkTap(e) {
// 打开链接
console.log('监听点击', e);
wx.navigateTo({
url: e.detail.node.href,
});
},
});
CSS (css):
.chat-example-block {
background-color: var(--td-bg-color-container);
padding: 32rpx;
}
JSON (javascript):
{
"usingComponents": {
"t-chat-markdown": "tdesign-miniprogram/chat-markdown/chat-markdown"
}
}
MOCK (javascript):
const mockMarkdownData = `
# 一级标题
## 二级标题
### 三级标题
#### 四级标题
##### 五级标题
###### 六级标题
正文
**加粗正文**
~~删除线~~
行内代码: \`console.log('Hello')\`
`;
export default mockMarkdownData;
列表
WXML (html):
<view class="chat-example-block">
<t-chat-markdown content="{{markdownContent}}" bind:click="handleLinkTap" />
</view>
JS (javascript):
import markdownData from './mock.js';
// 内置marked处理
Page({
data: {
markdownContent: markdownData,
},
handleLinkTap(e) {
// 打开链接
console.log('监听点击', e);
wx.navigateTo({
url: e.detail.node.href,
});
},
});
CSS (css):
.chat-example-block {
background-color: var(--td-bg-color-container);
padding: 32rpx;
}
JSON (javascript):
{
"usingComponents": {
"t-chat-markdown": "tdesign-miniprogram/chat-markdown/chat-markdown"
}
}
MOCK (javascript):
const mockMarkdownData = `
- 无序列表
- 无序列表
- 嵌套列表
- 嵌套列表
1. 有序列表
2. 有序列表
`;
export default mockMarkdownData;
02代码块与表格
代码块
WXML (html):
<view class="chat-example-block">
<t-chat-markdown content="{{markdownContent}}" bind:click="handleLinkTap" />
</view>
JS (javascript):
import markdownData from './mock.js';
// 内置marked处理
Page({
data: {
markdownContent: markdownData,
},
handleLinkTap(e) {
// 打开链接
console.log('监听点击', e);
wx.navigateTo({
url: e.detail.node.href,
});
},
});
CSS (css):
.chat-example-block {
background-color: var(--td-bg-color-container);
padding: 32rpx;
}
JSON (javascript):
{
"usingComponents": {
"t-chat-markdown": "tdesign-miniprogram/chat-markdown/chat-markdown"
}
}
MOCK (javascript):
const mockMarkdownData = `
\`\`\`TDesign-登录表单.jsx
import { Form, Input, Button, Message } from 'tdesign-react';
const LoginForm = () => { const [loading, setLoading] = useState(false);
\`\`\`
`;
export default mockMarkdownData;
表格
WXML (html):
<view class="chat-example-block">
<t-chat-markdown content="{{markdownContent}}" bind:click="handleLinkTap" />
</view>
JS (javascript):
import markdownData from './mock.js';
// 内置marked处理
Page({
data: {
markdownContent: markdownData,
},
handleLinkTap(e) {
// 打开链接
console.log('监听点击', e);
wx.navigateTo({
url: e.detail.node.href,
});
},
});
CSS (css):
.chat-example-block {
background-color: var(--td-bg-color-container);
padding: 32rpx;
}
JSON (javascript):
{
"usingComponents": {
"t-chat-markdown": "tdesign-miniprogram/chat-markdown/chat-markdown"
}
}
MOCK (javascript):
const mockMarkdownData = `
| 左对齐 | 居中对齐 | 右对齐 | 内容 |
| :--------- | :------: | -----: | ----- |
| 单元格 | 单元格 | 单元格 | 单元格 |
| 长文本示例| 长文本示例长文本示例长文本示例 | $100 | 文本内容 |
| 文本示例 | 文本内容 | $100 | 文本内容 |
`;
export default mockMarkdownData;
03图片与超链接
支持监听链接的点击事件
WXML (html):
<view class="chat-example-block">
<t-chat-markdown content="{{markdownContent}}" bind:click="handleLinkTap" />
</view>
JS (javascript):
import markdownData from './mock.js';
// 内置marked处理
Page({
data: {
markdownContent: markdownData,
},
handleLinkTap(e) {
// 打开链接
console.log('监听点击', e);
wx.navigateTo({
url: e.detail.node.href,
});
},
});
CSS (css):
.demo-container {
padding: 32rpx;
background-color: #fff;
}
.text {
margin-left: 10rpx;
margin-bottom: 32rpx;
}
JSON (javascript):
{
"usingComponents": {
"t-chat-markdown": "tdesign-miniprogram/chat-markdown/chat-markdown"
}
}
MOCK (javascript):
const mockMarkdownData = `

这是一个链接 [Markdown语法](https://markdown.com.cn)。
`;
export default mockMarkdownData;
04引用
WXML (html):
<view class="chat-example-block">
<t-chat-markdown content="{{markdownContent}}" bind:click="handleLinkTap" />
</view>
JS (javascript):
import markdownData from './mock.js';
// 内置marked处理
Page({
data: {
markdownContent: markdownData,
},
handleLinkTap(e) {
// 打开链接
console.log('监听点击', e);
wx.navigateTo({
url: e.detail.node.href,
});
},
});
CSS (css):
.chat-example-block {
background-color: var(--td-bg-color-container);
padding: 32rpx;
}
JSON (javascript):
{
"usingComponents": {
"t-chat-markdown": "tdesign-miniprogram/chat-markdown/chat-markdown"
}
}
MOCK (javascript):
const mockMarkdownData = `
下面是引用示例
> TDesign distills Tencent's years of design experience into professional design guideline, providing universal design solutions that assist product managers, designers, developers, and other roles in efficiently completing the design and development of enterprise-level products, while maintaining consistent design language and style to meet user experience requirements.
`;
export default mockMarkdownData;
API
ChatMarkdownProps
| 名称 | 类型 | 默认值 | 描述 | 必传 |
|---|---|---|---|---|
| style | Object | - | 样式 | N |
| custom-style | Object | - | 样式,一般用于开启虚拟化组件节点场景 | N |
| content | String | - | 必需。markdown 内容文本 | Y |
| options | Object | Markdown 解析器基础配置。TS 类型:TdChatContentMDOptions interface TdChatContentMDOptions {gfm?: boolean; pedantic?: boolean; smartLists?: boolean; breaks?: boolean}。详细类型定义 | N |
ChatMarkdownEvents
| 名称 | 参数 | 描述 |
|---|---|---|
| click | (detail: {detail:{event, node}, currentTarget, target}) | 点击链接时触发 |
ChatMessage 对话消息体
示例
引入
全局引入,在 miniprogram 根目录下的app.json中配置,局部引入,在需要引入的页面或组件的index.json中配置。
"usingComponents": {
"t-chat-message": "tdesign-miniprogram/chat-message/chat-message"
}代码演示
Tips: 请确保开发者工具为打开状态。导入开发者工具后,依次执行:npm i > 构建npm包 > 勾选 "将JS编译成ES5"
01组件类型
基础类型
WXML (html):
<view class="chat-example">
<view class="chat-example-block">
<t-chat-message content="{{userMessage.content}}" role="{{userMessage.role}}"></t-chat-message>
</view>
</view>
JS (javascript):
Component({
properties: {
userMessage: {
type: Object,
value: {
role: 'user',
content: [
{
type: 'text',
data: '牛顿第一定律是否适用于所有参考系?',
},
],
},
},
},
});
CSS (css):
.chat-example {
display: flex;
flex-direction: column;
gap: 32rpx;
}
.chat-example-block {
background-color: var(--td-bg-color-container);
padding: 32rpx 32rpx 0 32rpx;
}
JSON (javascript):
{
"component": true,
"usingComponents": {
"t-chat-message": "tdesign-miniprogram/chat-message/chat-message",
"t-chat-actionbar": "tdesign-miniprogram/chat-actionbar/chat-actionbar"
}
}
可配置昵称、头像、对齐方式
支持avatar,name插槽自定义
WXML (html):
<view class="chat-example">
<view class="chat-example-block">
<t-chat-message datetime="16:38" name="张三" content="{{message.content}}" role="{{message.role}}"></t-chat-message>
</view>
<view class="chat-example-block">
<t-chat-message
avatar="https://tdesign.gtimg.com/site/chat-avatar.png"
datetime="16:38"
name="TDesignAI"
content="{{message.content}}"
role="{{message.role}}"
></t-chat-message>
</view>
<view class="chat-example-block">
<t-chat-message
avatar="https://tdesign.gtimg.com/site/chat-avatar.png"
datetime="16:38"
name="TDesignAI"
content="{{message.content}}"
role="{{message.role}}"
placement="right"
></t-chat-message>
</view>
<view class="chat-example-block">
<t-chat-message content="{{message.content}}" role="{{message.role}}" datetime="16:38">
<view slot="name" class="name-block">
<image src="https://tdesign.gtimg.com/site/chat-avatar.png" />
<view>Canxuan</view>
</view>
</t-chat-message>
</view>
</view>
JS (javascript):
Component({
data: {
message: {
role: 'system',
content: [
{
type: 'text',
data: '牛顿第一定律是否适用于所有参考系?',
},
],
},
},
});
CSS (css):
.chat-example {
display: flex;
flex-direction: column;
gap: 32rpx;
}
.chat-example-block {
background-color: var(--td-bg-color-container);
padding: 32rpx 32rpx 0 32rpx;
}
.name-block {
display: flex;
align-items: center;
margin-right: 16rpx;
}
.name-block image {
margin-right: 16rpx;
width: 40rpx;
height: 40rpx;
}
JSON (javascript):
{
"component": true,
"usingComponents": {
"t-chat-message": "tdesign-miniprogram/chat-message/chat-message",
"t-chat-actionbar": "tdesign-miniprogram/chat-actionbar/chat-actionbar",
"t-divider": "tdesign-miniprogram/divider/divider"
}
}
配置消息属性
支持content插槽自定义, content插槽使用建议:渲染聊天消息统一用 t-chat-content;仅在需要“单独使用 Markdown 组件”时使用 t-chat-markdown。也支持别的 markdown 渲染组件,选择其他 markdown 渲染库由用户自行安装。
WXML (html):
<view class="chat-example">
<!-- 附件消息 -->
<view class="chat-example-block">
<t-chat-message
content="{{pic2.content}}"
role="{{pic2.role}}"
chatContentProps="{{chatContentProps}}"
></t-chat-message>
</view>
<view class="chat-example-block">
<t-chat-message
content="{{pic3.content}}"
role="{{pic3.role}}"
placement="right"
chatContentProps="{{chatContentProps}}"
></t-chat-message>
</view>
<view class="chat-example-block">
<t-chat-message
content="{{fileMessage.content}}"
role="{{fileMessage.role}}"
chatContentProps="{{chatContentProps}}"
></t-chat-message>
</view>
<view class="chat-example-block">
<t-chat-message
content="{{fileMessage.content}}"
role="{{fileMessage.role}}"
placement="right"
chatContentProps="{{chatContentProps}}"
></t-chat-message>
</view>
<!-- 思考过程消息 -->
<view class="chat-example-block">
<t-chat-message
content="{{aiMessage.content}}"
role="{{aiMessage.role}}"
status="{{aiMessage.status}}"
variant="text"
>
<t-chat-actionbar slot="actionbar" />
</t-chat-message>
</view>
</view>
JS (javascript):
Component({
data: {
aiMessage: {
role: 'assistant',
status: 'complete',
content: [
{
type: 'thinking',
data: {
title: '已完成思考(耗时3秒)',
text: '好的,我现在需要回答用户关于对比近3年当代偶像爱情剧并总结创作经验的问题\n查询网络信息中...\n根据网络搜索结果,成功案例包括《春色寄情人》《要久久爱》《你也有今天》等,但缺乏具体播放数据,需要结合行业报告总结共同特征。2022-2024年偶像爱情剧的创作经验主要集中在题材创新、现实元素融入、快节奏叙事等方面。结合行业报告和成功案例,总结出以下创作经验。',
},
},
{
type: 'text',
data: '不,牛顿第一定律并不适用于所有参考系。它只适用于惯性参考系。',
},
],
},
pic1: {
role: 'user',
name: '张三',
avatar: 'https://tdesign.gtimg.com/site/avatar.jpg',
content: [
{
type: 'attachment',
data: [
{
fileType: 'image',
name: 'avatar.jpg',
size: 234234,
url: 'https://tdesign.gtimg.com/demo/demo-image-1.png',
width: 1920, // 图片实际宽度
height: 1080, // 图片实际高度
},
],
},
{
type: 'text',
data: '分析以下内容,总结一篇广告策划方案',
},
],
},
pic3: {
role: 'user',
content: [
{
type: 'attachment',
data: [
{
fileType: 'image',
name: 'avatar.jpg',
size: 234234,
url: 'https://tdesign.gtimg.com/demo/demo-image-1.png',
width: 1920, // 图片实际宽度
height: 1080, // 图片实际高度
},
],
},
{
type: 'text',
data: '分析以下',
},
],
},
pic2: {
role: 'user',
content: [
{
type: 'attachment',
data: [
{
fileType: 'image',
name: 'avatar.jpg',
size: 234234,
url: 'https://tdesign.gtimg.com/demo/demo-image-1.png',
width: 1920, // 为了更好的适配不同尺寸图片建议传入宽高,不传也有兜底尺寸
height: 1080, // 为了更好的适配不同尺寸图片建议传入宽高,不传也有兜底尺寸
},
{
fileType: 'image',
name: 'avatar2.jpg',
size: 234234,
url: 'https://tdesign.gtimg.com/demo/demo-image-1.png',
width: 1920, // 图片实际宽度
height: 1080, // 图片实际高度
},
],
},
{
type: 'text',
data: '分析以下内容,总结一篇广告策划方案',
},
],
},
fileMessage: {
role: 'user',
content: [
{
type: 'attachment',
data: [
{
fileType: 'doc',
name: 'word-file.doc',
url: 'https://example.com/word-file.doc',
size: 222859,
status: 'success',
},
{
fileType: 'excel',
name: 'excel-file.xlsx',
url: 'https://example.com/excel-file.xlsx',
size: 222859,
status: 'success',
},
{
fileType: 'pdf',
name: 'pdf-file.pdf',
url: 'https://example.com/pdf-file.pdf',
size: 222859,
status: 'success',
},
{
fileType: 'ppt',
name: 'ppt-file.pptx',
url: 'https://example.com/ppt-file.pptx',
size: 222859,
status: 'success',
},
{
fileType: 'video',
name: 'video-file.mp4',
url: 'https://example.com/video-file.mp4',
size: 222859,
status: 'success',
},
{
fileType: 'file',
name: 'file',
url: 'https://example.com/audio-file.mp3',
size: 222859,
status: 'success',
},
],
},
{
type: 'text',
data: '不,牛顿第一定律并不适用于所有参考系。它只适用于惯性参考系。',
},
],
},
},
});
CSS (css):
.chat-example {
display: flex;
flex-direction: column;
gap: 32rpx;
}
.chat-example-block {
background-color: var(--td-bg-color-container);
padding: 32rpx 32rpx 0 32rpx;
}
JSON (javascript):
{
"component": true,
"usingComponents": {
"t-chat-message": "tdesign-miniprogram/chat-message/chat-message",
"t-chat-actionbar": "tdesign-miniprogram/chat-actionbar/chat-actionbar",
"t-avatar": "tdesign-miniprogram/avatar/avatar"
}
}02组件状态
加载状态
WXML (html):
<view class="chat-example">
<view class="chat-example-block">
<t-chat-message
avatar="https://tdesign.gtimg.com/site/chat-avatar.png"
content="{{message.content}}"
role="{{message.role}}"
animation="gradient"
status="{{message.status}}"
></t-chat-message>
</view>
<view class="chat-example-block">
<t-chat-message
avatar="https://tdesign.gtimg.com/site/chat-avatar.png"
content="{{message.content}}"
role="{{message.role}}"
animation="skeleton"
status="{{message.status}}"
></t-chat-message>
</view>
</view>
JS (javascript):
Component({
data: {
message: {
role: 'assistant',
status: 'pending',
content: [
{
type: 'text',
data: '牛顿第一定律并不适用于所有参考系,它只适用于惯性参考系。在质点不受外力作用时,能够判断出质点静止或作匀速直线运动的参考系一定是惯性参考系,因此只有在惯性参考系中牛顿第一定律才适用。',
},
],
},
},
});
CSS (css):
.chat-example {
display: flex;
flex-direction: column;
gap: 32rpx;
}
.chat-example-block {
background-color: var(--td-bg-color-container);
padding: 32rpx 32rpx 0 32rpx;
}
JSON (javascript):
{
"component": true,
"usingComponents": {
"t-chat-message": "tdesign-miniprogram/chat-message/chat-message",
"t-chat-actionbar": "tdesign-miniprogram/chat-actionbar/chat-actionbar"
}
}
出错状态
WXML (html):
<view class="chat-example">
<view class="chat-example-block">
<t-chat-message
avatar="https://tdesign.gtimg.com/site/chat-avatar.png"
content="{{message.content}}"
role="{{message.role}}"
status="error"
></t-chat-message>
</view>
</view>
JS (javascript):
Component({
data: {
message: {
role: 'assistant',
status: 'error',
content: [
{
type: 'text',
data: '´• •`!!!请求出错',
},
],
},
},
});
CSS (css):
.chat-example {
display: flex;
flex-direction: column;
gap: 32rpx;
}
.chat-example-block {
background-color: var(--td-bg-color-container);
padding: 32rpx 32rpx 0 32rpx;
}
JSON (javascript):
{
"component": true,
"usingComponents": {
"t-chat-message": "tdesign-miniprogram/chat-message/chat-message",
"t-chat-actionbar": "tdesign-miniprogram/chat-actionbar/chat-actionbar"
}
}
03组件样式
气泡样式
WXML (html):
<view class="chat-example">
<view class="chat-example-block"
><t-chat-message content="{{userMessage.content}}" role="{{userMessage.role}}" variant="text"></t-chat-message
></view>
<view class="chat-example-block"
><t-chat-message content="{{userMessage.content}}" role="{{userMessage.role}}" variant="base"></t-chat-message
></view>
<view class="chat-example-block"
><t-chat-message content="{{userMessage.content}}" role="{{userMessage.role}}" variant="outline"></t-chat-message
></view>
</view>
JS (javascript):
Component({
properties: {
userMessage: {
type: Object,
value: {
role: 'user',
content: [
{
type: 'text',
data: '牛顿第一定律是否适用于所有参考系?',
},
],
},
},
},
});
CSS (css):
.chat-example {
display: flex;
flex-direction: column;
gap: 32rpx;
}
.chat-example-block {
background-color: var(--td-bg-color-container);
padding: 32rpx 32rpx 0 32rpx;
}
JSON (javascript):
{
"component": true,
"styleIsolation": "shared",
"usingComponents": {
"t-chat-message": "tdesign-miniprogram/chat-message/chat-message",
"t-chat-actionbar": "tdesign-miniprogram/chat-actionbar/chat-actionbar"
}
}
API
ChatMessageProps
| 名称 | 类型 | 默认值 | 描述 | 必传 |
|---|---|---|---|---|
| style | Object | - | 样式 | N |
| custom-style | Object | - | 样式,一般用于开启虚拟化组件节点场景 | N |
| animation | String | skeleton | 动画效果。可选项:skeleton/moving/gradient/dots | N |
| avatar | String | - | 自定义的头像配置 | N |
| chat-content-props | Object | - | 聊天内容组件的属性。TS 类型:ChatContentProps,ChatContent API Documents。详细类型定义 | N |
| chat-id | String | - | 聊天消息的唯一标识 | N |
| content | Array | - | 消息内容,数组中的每一项为一个消息内容对象。TS 类型:ChatMessageContent[] `type ChatMessageContent = TextContent \ | MarkdownContent \ |
| datetime | String | - | 对话单元的时间配置 | N |
| name | String | - | 自定义的昵称 | N |
| placement | String | - | 消息显示位置。可选项:left/right | N |
| role | String | user | 消息角色。可选项:user/assistant/system | N |
| status | String | - | 消息状态。可选项:pending/streaming/complete/stop/error | N |
| variant | String | base | 气泡框样式,支持基础、线框、文字三种类型。可选项:base/outline/text | N |
ChatMessageEvents
| 名称 | 参数 | 描述 |
|---|---|---|
| longpress | (detail: { id: string }) | - |
ChatMessageSlots
| 名称 | 描述 |
|---|---|
| avatar | 自定义avatar显示内容 |
| content | 自定义消息内容 |
| name | 自定义name显示内容 |
ChatThinking 思考过程
示例
引入
全局引入,在 miniprogram 根目录下的app.json中配置,局部引入,在需要引入的页面或组件的index.json中配置。
"usingComponents": {
"t-chat-thinking": "tdesign-miniprogram/chat-thinking/chat-thinking"
}代码演示
Tips: 请确保开发者工具为打开状态。导入开发者工具后,依次执行:npm i > 构建npm包 > 勾选 "将JS编译成ES5"
01组件类型
支持多种加载动效类型,包括gradient、moving、dots
WXML (html):
<view class="chat-example-block">
<t-chat-thinking
content="{{content}}"
layout="block"
status="{{status}}"
animation="moving"
bind:expandChange="handleExpandChange"
/>
</view>
JS (javascript):
Component({
data: {
thinking: true,
fullText:
'嗯,用户问牛顿第一定律是不是适用于所有参考系。首先,我得先回忆一下牛顿第一定律的内容。牛顿第一定律,也就是惯性定律,说物体在没有外力作用时会保持静止或匀速直线运动。也就是说, 保持原来的运动状态。',
currentText: '',
isTyping: true,
content: {
text: '',
title: '思考过程',
},
typeSpeed: 50,
status: 'pending',
startTime: 0,
},
lifetimes: {
attached() {
this.setData({
startTime: Date.now(),
});
this.startTyping();
},
detached() {
if (this.typingTimer) {
clearTimeout(this.typingTimer);
}
},
},
methods: {
startTyping() {
const { fullText, typeSpeed } = this.data;
let currentIndex = 0;
const typeNextChar = () => {
if (currentIndex <= fullText.length) {
const currentText = fullText.substring(0, currentIndex);
// 检查是否已经完成打字
if (currentIndex === fullText.length) {
const endTime = Date.now();
const duration = Math.round((endTime - this.data.startTime) / 1000);
this.setData({
currentText,
content: {
text: currentText,
title: `已完成思考(耗时${duration}秒)`,
},
isTyping: false,
status: 'complete',
});
return; // 直接返回,不再继续执行
}
// 正常打字过程
this.setData({
currentText,
content: {
text: currentText,
title: '思考过程',
},
isTyping: currentIndex < fullText.length,
});
if (currentIndex < fullText.length) {
this.typingTimer = setTimeout(typeNextChar, typeSpeed);
}
currentIndex += 1;
}
};
typeNextChar();
},
replayTyping() {
if (this.typingTimer) {
clearTimeout(this.typingTimer);
}
this.setData({
currentText: '',
content: {
text: '',
title: '思考过程',
},
isTyping: true,
startTime: Date.now(),
});
this.startTyping();
},
onStop() {
console.log('停止思考');
this.setData({
thinking: false,
});
wx.showToast({
title: '已停止思考',
icon: 'success',
});
},
toggleThinking() {
this.setData({
thinking: !this.data.thinking,
});
},
resetThinking() {
this.setData({
thinking: true,
});
wx.showToast({
title: '已重置',
icon: 'success',
});
},
},
});
CSS (css):
.chat-example-block {
background-color: var(--td-bg-color-container);
padding: 32rpx;
}
JSON (javascript):
{
"usingComponents": {
"t-chat-thinking": "tdesign-miniprogram/chat-thinking/chat-thinking"
},
"component": true
}
02组件状态
思考中
WXML (html):
<view class="chat-example-block">
<t-chat-thinking content="{{content}}" layout="block" status="{{status}}" bind:collapsedChange="handleCollapsedChange" />
</view>
JS (javascript):
Component({
data: {
animation: 'moving',
content: {
text: '嗯,用户问牛顿第一定律是不是适用于所有参考系。首先,我得先回忆一下牛顿第一定律的内容。牛顿第一定律',
title: '思考中···',
},
status: 'pending',
},
methods: {
handleCollapsedChange(e) {
console.log('展开状态变化:', e.detail);
},
},
});
CSS (css):
.chat-example-block {
background-color: var(--td-bg-color-container);
padding: 32rpx;
}
JSON (javascript):
{
"usingComponents": {
"t-chat-thinking": "tdesign-miniprogram/chat-thinking/chat-thinking"
}
}
思考完成
WXML (html):
<view class="chat-example-block">
<t-chat-thinking content="{{content}}" layout="block" status="{{status}}" bind:collapsedChange="handleCollapsedChange" />
</view>
JS (javascript):
Component({
data: {
animation: 'moving',
status: 'complete',
content: {
text: '嗯,用户问牛顿第一定律是不是适用于所有参考系。首先,我得先回忆一下牛顿第一定律的内容。牛顿第一定律,也就是惯性定律,说物体在没有外力作用时会保持静止或匀速直线运动。也就是说, 保持原来的运动状态。',
title: '已深度思考(用时19秒)',
},
},
methods: {
handleCollapsedChange(e) {
console.log('展开状态变化:', e.detail);
},
},
});
CSS (css):
.chat-example-block {
background-color: var(--td-bg-color-container);
padding: 32rpx;
}
JSON (javascript):
{
"usingComponents": {
"t-chat-thinking": "tdesign-miniprogram/chat-thinking/chat-thinking",
"t-radio": "tdesign-miniprogram/radio/radio",
"t-radio-group": "tdesign-miniprogram/radio-group/radio-group"
}
}
03组件样式
支持通过layout来设置思考过程的布局方式
WXML (html):
<block>
<view class="chat-example-desc">block 样式</view>
<view class="chat-example-block">
<t-chat-thinking layout="block" content="{{content}}" status="{{status}}" animation="{{animation}}" />
</view>
<view class="chat-example-desc">border 样式</view>
<view class="chat-example-block">
<t-chat-thinking layout="border" content="{{content}}" status="{{status}}" animation="{{animation}}" />
</view>
</block>
JS (javascript):
Component({
data: {
animation: 'moving',
status: 'complete',
content: {
text: '嗯,用户问牛顿第一定律是不是适用于所有参考系。首先,我得先回忆一下牛顿第一定律的内容。牛顿第一定律,也就是惯性定律,说物体在没有外力作用时会保持静止或匀速直线运动。也就是说, 保持原来的运动状态。',
title: '已深度思考(用时19秒)',
},
},
});
CSS (css):
.chat-example-desc {
margin: var(--td-spacer-3) var(--td-spacer-2) var(--td-spacer-2);
font-size: var(--td-font-size-base);
white-space: pre-line;
color: var(--bg-color-demo-desc);
line-height: 22px;
}
.chat-example-desc:first-child {
margin-top: -8px;
}
.chat-example-block {
background-color: var(--td-bg-color-container);
padding: var(--td-spacer-2);
}
JSON (javascript):
{
"usingComponents": {
"t-chat-thinking": "tdesign-miniprogram/chat-thinking/chat-thinking"
}
}
API
ChatThinkingProps
| 名称 | 类型 | 默认值 | 描述 | 必传 |
|---|---|---|---|---|
| style | Object | - | 样式 | N |
| custom-style | Object | - | 样式,一般用于开启虚拟化组件节点场景 | N |
| animation | String | moving | 内容区域最大高度,超出会自动滚动。可选项:skeleton/moving/gradient/dot | N |
| collapsed | Boolean | false | 是否折叠 | N |
| content | Object | - | 必需。思考内容对象。TS 类型:{ text?: string; title?: string } | Y |
| layout | String | block | 布局方式。可选项:block/border | N |
| max-height | Number | - | 内容区域最大高度,超出会自动滚动 | N |
| status | String | pending | 必需。思考状态。可选项:complete/stop/error/pending | Y |
ChatThinkingEvents
| 名称 | 参数 | 描述 |
|---|---|---|
| collapsed-change | (value: Boolean) | 切换折叠面板时触发 |
Wechat MiniProgram For AI Chat
预览
小程序组件示例小程序,请使用微信扫码预览 ↓
使用之前
使用前,请确保你已经学习过微信官方的 小程序简易教程 和 自定义组件介绍。
安装
使用NPM
小程序已经支持使用 NPM 安装第三方包,详见 NPM 支持
npm i tdesign-miniprogram -S --production安装完之后,需要在微信开发者工具中对 npm 进行构建:工具 - 构建 npm。(构建时若出现NPM packages not found字样,请到project.config.json文件补充packNpmManually和packNpmRelationList配置项,具体见NPM 支持)
构建成功后勾选 将 JS 编译成 ES5修改app.json
将 app.json 中的 "style": "v2" 移除。
因为 该配置 表示启用新版组件样式,将会导致 TDesign 的组件样式错乱。
修改tsconfig.json
如果使用typescript开发,需要修改tsconfig.json指定paths
{
"paths": {
"tdesign-miniprogram/*":["./miniprogram/miniprogram_npm/tdesign-miniprogram/*"]
}
}使用组件
以聊天组件为例,只需要在 JSON 文件中引入聊天对应的自定义组件即可
{
"usingComponents": {
"t-chat": "tdesign-miniprogram/chat-list/chat-list",
"t-chat-message": "tdesign-miniprogram/chat-message/chat-message",
"t-chat-sender": "tdesign-miniprogram/chat-sender/chat-sender"
}
}接着就可以在 wxml 中直接使用组件
<t-chat layout="single">
<t-chat-message avatar="{{item.avatar}}" name="{{item.name}}" content="{{item.content}}" role="{{item.role}}" />
<view slot="footer">
<t-chat-sender bind:send="onSend" />
</view>
</t-chat>在开发者工具中预览
# 安装项目依赖
npm install
# 执行组件编译
npm run dev打开微信开发者工具,把_example目录添加进去就可以预览示例了。
基础库版本
最低基础库版本^2.6.5
什么是流式输出
简述
流式输出,也称为流式传输,指的是服务器持续地将数据推送到客户端,而不是一次性发送完毕。这种模式下,连接一旦建立,服务器就能实时地发送更新给客户端。
使用场景
流式输出的典型应用场景包括实时消息推送、股票行情更新、实时通知等,任何需要服务器向客户端实时传输数据的场合都可以使用。
与普通请求的区别
与传统的 HTTP 请求不同,普通请求是基于请求-响应模型,客户端发送请求后,服务器处理完毕即刻响应并关闭连接。流式输出则保持连接开放,允许服务器连续发送多个响应。
如何创建一个SSE
Python
在 Python 中,可以使用 fastAPI 框架来实现 Server-Sent Events。以下是一个示例:
1. 安装 FastAPI 和 Uvicorn 首先,确保你已经安装了 FastAPI 和 Uvicorn :
pip install fastapi uvicorn1. 创建 FastAPI 应用 接下来,创建一个 FastAPI 应用,并定义一个流式接口。我们将使用异步生成器来逐步生成数据,并使用 StreamingResponse 来流式发送数据给客户端。
import json
import asyncio
from fastapi import FastAPI
from sse_starlette.sse import EventSourceResponse
import uvicorn
from fastapi.middleware.cors import CORSMiddleware
app = FastAPI()
app.add_middleware(
CORSMiddleware,
allow_origins=['*'], # 设置允许跨域的域名列表,* 代表所有域名
allow_credentials=True,
allow_methods=['*'],
allow_headers=['*'],
)
async def event_generator():
count = 0
while True:
await asyncio.sleep(1)
count += 1
data = {"count": count}
yield json.dumps(data)
@app.get("/events")
async def get_events():
return EventSourceResponse(event_generator())
@app.post("/events")
async def post_events():
return EventSourceResponse(event_generator())
if __name__ == '__main__':
uvicorn.run(app, host='0.0.0.0', port=4000)1. 运行应用 保存上述代码到一个文件(例如main.py),然后运行应用:
python3 main.py1. 测试流式接口
- get接口
curl http://0.0.0.0:4000/events- post接口
curl -X POST "http://0.0.0.0:4000/events" -H "Content-Type: application/json"你应该会看到每秒钟输出一行数据,类似于:
data: {"count": 1}
data: {"count": 2}
data: {"count": 3}
data: {"count": 4}
data: {"count": 5}
...为什么大模型LLM需要使用SSE?
从某种意义上说,现阶段 LLM 模型采用 SSE 是历史遗留原因。
Transformer 前后内容是需要推理拼接的,且不说内容很多的时候,推理的时间会很长(还有 Max Token 的限制)。推理上下文的时候也是逐步推理生成的,因此默认就是流式输出进行包裹。如果哪天 AI 的速度可以不受这些内容的限制了,可能一次性返回是一个更好的交互。
TDesignAIChat如何接入SSE
对于流式请求来说,组件其实只关心一个内容,那就是返回的 String,下面是 hunyuan 的流式返回案例。
data: {"id":"7eced65bb3cb122d9f927563fc0e5673","created":1695218378,"choices":[{"delta":{"role":"assistant","content":"我是"}}],"usage":{"prompt_tokens":10,"completion_tokens":1,"total_tokens":11}}
data: {"id":"7eced65bb3cb122d9f927563fc0e5673","created":1695218378,"choices":[{"delta":{"role":"assistant","content":"由腾"}}],"usage":{"prompt_tokens":10,"completion_tokens":3,"total_tokens":13}}
data: {"id":"7eced65bb3cb122d9f927563fc0e5673","created":1695218378,"choices":[{"delta":{"role":"assistant","content":"讯公"}}],"usage":{"prompt_tokens":10,"completion_tokens":5,"total_tokens":15}}
data: {"id":"7eced65bb3cb122d9f927563fc0e5673","created":1695218378,"choices":[{"delta":{"role":"assistant","content":"司开"}}],"usage":{"prompt_tokens":10,"completion_tokens":7,"total_tokens":17}}
data: {"id":"7eced65bb3cb122d9f927563fc0e5673","created":1695218378,"choices":[{"delta":{"role":"assistant","content":"发的"}}],"usage":{"prompt_tokens":10,"completion_tokens":8,"total_tokens":18}}
data: {"id":"7eced65bb3cb122d9f927563fc0e5673","created":1695218378,"choices":[{"delta":{"role":"assistant","content":"大型"}}],"usage":{"prompt_tokens":10,"completion_tokens":9,"total_tokens":19}}
data: {"id":"7eced65bb3cb122d9f927563fc0e5673","created":1695218378,"choices":[{"delta":{"role":"assistant","content":"语言"}}],"usage":{"prompt_tokens":10,"completion_tokens":10,"total_tokens":20}}
data: {"id":"7eced65bb3cb122d9f927563fc0e5673","created":1695218378,"choices":[{"delta":{"role":"assistant","content":"模型"}}],"usage":{"prompt_tokens":10,"completion_tokens":11,"total_tokens":21}}下面是微信小程序解析SSE流式接口请求的代码片段示例:
const requestTask = wx.request({
url: 'https://your-api-url',
enableChunked: true,
success(res){
console.log('success:', res)
},
fail(err){
console.warn('fail:', err);
}
});
// 解析流式返回的数据
requestTask.onChunkReceived((chunk)=>console.log('chunk', chunk))BackTop 返回顶部
示例
引入
全局引入,在 miniprogram 根目录下的app.json中配置,局部引入,在需要引入的页面或组件的index.json中配置。
"usingComponents": {
"t-back-top": "tdesign-miniprogram/back-top/back-top",
}代码演示
Tips: 请确保开发者工具为打开状态。导入开发者工具后,依次执行:npm i > 构建npm包 > 勾选 "将JS编译成ES5"
基础返回顶部
WXML (html):
<t-back-top
theme="{{backTopTheme}}"
text="{{backTopText}}"
scroll-top="{{scrollTop}}"
bind:to-top="onToTop"
></t-back-top>
JS (javascript):
Component({
properties: {
scrollTop: { type: Number, value: 0 },
},
data: {
backTopTheme: 'round',
backTopText: '顶部',
},
methods: {
onToTop(e) {
this.triggerEvent('to-top', e);
},
},
});
CSS (css):
JSON (javascript):
{
"component": true,
"usingComponents": {
"t-back-top": "tdesign-miniprogram/back-top/back-top"
}
}
API
BackTopProps
| 名称 | 类型 | 默认值 | 描述 | 必传 |
|---|---|---|---|---|
| style | Object | - | 样式 | N |
| custom-style | Object | - | 样式,一般用于开启虚拟化组件节点场景 | N |
| fixed | Boolean | true | 是否绝对定位固定到屏幕右下方 | N |
| icon | String / Boolean / Object | true | 图标。值为false表示不显示图标。不传表示使用默认图标'backtop' | N |
| scroll-top | Number | 0 | 页面滚动距离 | N |
| text | String | '' | 文案 | N |
| theme | String | round | 预设的样式类型。可选项:round/half-round/round-dark/half-round-dark | N |
| visibility-height | Number | 200 | 滚动高度达到此参数值才出现 | N |
BackTopEvents
| 名称 | 参数 | 描述 |
|---|---|---|
| to-top | - | 点击触发 |
BackTopSlots
| 名称 | 描述 |
|---|---|
| - | 默认插槽,自定义内容区域内容 |
| icon | 自定义图标内容 |
BackTopExternalClasses
| 类名 | 描述 |
|---|---|
| t-class | 根节点样式类 |
| t-class-icon | 图标样式类 |
| t-class-text | 文本样式类 |
CSSVariables
组件提供了下列 CSS 变量,可用于自定义样式。
| 名称 | 默认值 | 描述 |
|---|---|---|
| --td-back-top-half-round-border-radius | @radius-round | - |
| --td-back-top-round-bg-color | @bg-color-container | - |
| --td-back-top-round-border-color | @component-border | - |
| --td-back-top-round-border-radius | @radius-circle | - |
| --td-back-top-round-color | @text-color-primary | - |
| --td-back-top-round-dark-bg-color | @gray-color-13 | - |
| --td-back-top-round-dark-color | @text-color-anti | - |
Overlay 遮罩层
示例
该组件于 0.10.0 版本上线,请留意版本。
引入
全局引入,在 miniprogram 根目录下的app.json中配置,局部引入,在需要引入的页面或组件的index.json中配置。
"usingComponents": {
"t-overlay": "tdesign-miniprogram/overlay/overlay"
}代码演示
Tips: 请确保开发者工具为打开状态。导入开发者工具后,依次执行:npm i > 构建npm包 > 勾选 "将JS编译成ES5"
基础使用
WXML (html):
<t-overlay visible="{{visible}}" duration="{{500}}" bind:click="handleOverlayClick" />
<t-button block size="large" theme="primary" variant="outline" bind:tap="handleClick">基础用法</t-button>
JS (javascript):
Component({
data: {
visible: false,
},
methods: {
handleClick() {
this.setData({ visible: true });
},
handleOverlayClick(e) {
this.setData({
visible: e.detail.visible,
});
},
},
});
CSS (css):
JSON (javascript):
{
"component": true,
"usingComponents": {
"t-overlay": "tdesign-miniprogram/overlay/overlay",
"t-button": "tdesign-miniprogram/button/button"
}
}
API
OverlayProps
| 名称 | 类型 | 默认值 | 描述 | 必传 |
|---|---|---|---|---|
| style | Object | - | 样式 | N |
| custom-style | Object | - | 样式,一般用于开启虚拟化组件节点场景 | N |
| background-color | String | - | 遮罩层的背景色 | N |
| duration | Number | 300 | 背景色过渡时间,单位毫秒 | N |
| prevent-scroll-through | Boolean | true | 防止滚动穿透,即不允许点击和滚动 | N |
| using-custom-navbar | Boolean | false | 是否使用了自定义导航栏 | N |
| visible | Boolean | false | 是否展示 | N |
| z-index | Number | 11000 | 遮罩层级 | N |
OverlayEvents
| 名称 | 参数 | 描述 |
|---|---|---|
| click | ({ visible: boolean }) | 点击遮罩时触发 |
OverlaySlots
| 名称 | 描述 |
|---|---|
| - | 默认插槽,自定义内容区域内容 |
CSSVariables
组件提供了下列 CSS 变量,可用于自定义样式。
| 名称 | 默认值 | 描述 |
|---|---|---|
| --td-overlay-bg-color | @mask-active | - |
| --td-overlay-transition-duration | 300ms | - |
Related skills
FAQ
How is it installed?
Via npm i tdesign-miniprogram, then removing style v2 from app.json and configuring packNpmManually in project.config.json before building npm in WeChat DevTools.
How many components does it cover?
60+ components across basic, navigation, input, and data-display categories, plus chat components for AI interfaces.