diff --git a/packages/common b/packages/common
index 21e056075..d73d63dcb 160000
--- a/packages/common
+++ b/packages/common
@@ -1 +1 @@
-Subproject commit 21e0560751ba680188fc0f97aa8bf79835990084
+Subproject commit d73d63dcbf9202b83798ca84f09c89672e660b74
diff --git a/packages/pro-components/chat/chat-content/chat-content.json b/packages/pro-components/chat/chat-content/chat-content.json
index e4071fdd8..c7ef469b2 100644
--- a/packages/pro-components/chat/chat-content/chat-content.json
+++ b/packages/pro-components/chat/chat-content/chat-content.json
@@ -1,7 +1,12 @@
{
"component": true,
"styleIsolation": "apply-shared",
+ "componentGenerics": {
+ "tail-component": {
+ "default": "tdesign-miniprogram/chat-markdown/chat-markdown-tail/chat-markdown-tail"
+ }
+ },
"usingComponents": {
"t-chat-markdown": "tdesign-miniprogram/chat-markdown/chat-markdown"
}
-}
+}
\ No newline at end of file
diff --git a/packages/pro-components/chat/chat-content/chat-content.wxml b/packages/pro-components/chat/chat-content/chat-content.wxml
index a2463c9d1..18ddf2917 100644
--- a/packages/pro-components/chat/chat-content/chat-content.wxml
+++ b/packages/pro-components/chat/chat-content/chat-content.wxml
@@ -10,7 +10,13 @@
-
+
diff --git a/packages/pro-components/chat/chat-list/_example/docs/custom-tail/custom-tail.js b/packages/pro-components/chat/chat-list/_example/docs/custom-tail/custom-tail.js
new file mode 100644
index 000000000..b89373255
--- /dev/null
+++ b/packages/pro-components/chat/chat-list/_example/docs/custom-tail/custom-tail.js
@@ -0,0 +1,3 @@
+Component({
+ properties: {},
+});
diff --git a/packages/pro-components/chat/chat-list/_example/docs/custom-tail/custom-tail.json b/packages/pro-components/chat/chat-list/_example/docs/custom-tail/custom-tail.json
new file mode 100644
index 000000000..66165257f
--- /dev/null
+++ b/packages/pro-components/chat/chat-list/_example/docs/custom-tail/custom-tail.json
@@ -0,0 +1,4 @@
+{
+ "component": true,
+ "styleIsolation": "apply-shared"
+}
\ No newline at end of file
diff --git a/packages/pro-components/chat/chat-list/_example/docs/custom-tail/custom-tail.wxml b/packages/pro-components/chat/chat-list/_example/docs/custom-tail/custom-tail.wxml
new file mode 100644
index 000000000..48c6e1257
--- /dev/null
+++ b/packages/pro-components/chat/chat-list/_example/docs/custom-tail/custom-tail.wxml
@@ -0,0 +1,2 @@
+
+●
diff --git a/packages/pro-components/chat/chat-list/_example/docs/custom-tail/custom-tail.wxss b/packages/pro-components/chat/chat-list/_example/docs/custom-tail/custom-tail.wxss
new file mode 100644
index 000000000..7552813db
--- /dev/null
+++ b/packages/pro-components/chat/chat-list/_example/docs/custom-tail/custom-tail.wxss
@@ -0,0 +1,12 @@
+/* 自定义光标样式:蓝色渐变闪烁 */
+.custom-tail {
+ display: inline-block;
+ color: #0052d9;
+ font-weight: bold;
+ animation: custom-tail-blink 0.8s ease-in-out infinite;
+}
+
+@keyframes custom-tail-blink {
+ 0%, 100% { opacity: 1; }
+ 50% { opacity: 0.2; }
+}
diff --git a/packages/pro-components/chat/chat-list/_example/docs/index.js b/packages/pro-components/chat/chat-list/_example/docs/index.js
index 179cbcad6..ef39efe05 100644
--- a/packages/pro-components/chat/chat-list/_example/docs/index.js
+++ b/packages/pro-components/chat/chat-list/_example/docs/index.js
@@ -189,6 +189,7 @@ Component({
},
],
},
+ markdownProps: { streaming: { hasNextChunk: true, tail: true } },
chatId: getUniqueKey(),
};
@@ -208,6 +209,7 @@ Component({
complete() {
that.setData({
'chatList[0].message.status': 'complete',
+ 'chatList[0].markdownProps': {},
loading: false,
});
},
diff --git a/packages/pro-components/chat/chat-list/_example/docs/index.json b/packages/pro-components/chat/chat-list/_example/docs/index.json
index a56fa5337..7c409dc19 100644
--- a/packages/pro-components/chat/chat-list/_example/docs/index.json
+++ b/packages/pro-components/chat/chat-list/_example/docs/index.json
@@ -1,7 +1,13 @@
{
"component": true,
"styleIsolation": "shared",
+ "componentGenerics": {
+ "tail-component": {
+ "default": "./custom-tail/custom-tail"
+ }
+ },
"usingComponents": {
+ "custom-tail": "./custom-tail/custom-tail",
"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",
@@ -9,4 +15,4 @@
"t-chat-actionbar": "tdesign-miniprogram/chat-actionbar/chat-actionbar",
"t-toast": "tdesign-miniprogram/toast/toast"
}
-}
+}
\ No newline at end of file
diff --git a/packages/pro-components/chat/chat-list/_example/docs/index.wxml b/packages/pro-components/chat/chat-list/_example/docs/index.wxml
index 3a22faae8..04bcc526b 100644
--- a/packages/pro-components/chat/chat-list/_example/docs/index.wxml
+++ b/packages/pro-components/chat/chat-list/_example/docs/index.wxml
@@ -6,12 +6,26 @@
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'}}"
bind:message-longpress="showPopover"
>
+
+
+
+
+
+
+
+
diff --git a/packages/pro-components/chat/chat-markdown/_example/tail/custom-tail/custom-tail.js b/packages/pro-components/chat/chat-markdown/_example/tail/custom-tail/custom-tail.js
new file mode 100644
index 000000000..b89373255
--- /dev/null
+++ b/packages/pro-components/chat/chat-markdown/_example/tail/custom-tail/custom-tail.js
@@ -0,0 +1,3 @@
+Component({
+ properties: {},
+});
diff --git a/packages/pro-components/chat/chat-markdown/_example/tail/custom-tail/custom-tail.json b/packages/pro-components/chat/chat-markdown/_example/tail/custom-tail/custom-tail.json
new file mode 100644
index 000000000..66165257f
--- /dev/null
+++ b/packages/pro-components/chat/chat-markdown/_example/tail/custom-tail/custom-tail.json
@@ -0,0 +1,4 @@
+{
+ "component": true,
+ "styleIsolation": "apply-shared"
+}
\ No newline at end of file
diff --git a/packages/pro-components/chat/chat-markdown/_example/tail/custom-tail/custom-tail.wxml b/packages/pro-components/chat/chat-markdown/_example/tail/custom-tail/custom-tail.wxml
new file mode 100644
index 000000000..48c6e1257
--- /dev/null
+++ b/packages/pro-components/chat/chat-markdown/_example/tail/custom-tail/custom-tail.wxml
@@ -0,0 +1,2 @@
+
+●
diff --git a/packages/pro-components/chat/chat-markdown/_example/tail/custom-tail/custom-tail.wxss b/packages/pro-components/chat/chat-markdown/_example/tail/custom-tail/custom-tail.wxss
new file mode 100644
index 000000000..7552813db
--- /dev/null
+++ b/packages/pro-components/chat/chat-markdown/_example/tail/custom-tail/custom-tail.wxss
@@ -0,0 +1,12 @@
+/* 自定义光标样式:蓝色渐变闪烁 */
+.custom-tail {
+ display: inline-block;
+ color: #0052d9;
+ font-weight: bold;
+ animation: custom-tail-blink 0.8s ease-in-out infinite;
+}
+
+@keyframes custom-tail-blink {
+ 0%, 100% { opacity: 1; }
+ 50% { opacity: 0.2; }
+}
diff --git a/packages/pro-components/chat/chat-markdown/_example/tail/index.js b/packages/pro-components/chat/chat-markdown/_example/tail/index.js
new file mode 100644
index 000000000..81d738c84
--- /dev/null
+++ b/packages/pro-components/chat/chat-markdown/_example/tail/index.js
@@ -0,0 +1,45 @@
+import markdownData from '../base/mock2.js';
+
+const CHUNK_SIZE = 5;
+const INTERVAL_MS = 80;
+
+Page({
+ data: {
+ content: '',
+ streaming: { hasNextChunk: false, tail: true },
+ },
+
+ onLoad() {
+ this.startStreaming();
+ },
+
+ startStreaming() {
+ let index = 0;
+
+ this.setData({
+ content: '',
+ streaming: { hasNextChunk: true, tail: true },
+ });
+
+ const timer = setInterval(() => {
+ index += CHUNK_SIZE;
+ const isDone = index >= markdownData.length;
+ this.setData({
+ content: markdownData.slice(0, index),
+ streaming: { hasNextChunk: !isDone, tail: true },
+ });
+ if (isDone) clearInterval(timer);
+ }, INTERVAL_MS);
+ },
+
+ handleReplay() {
+ this.startStreaming();
+ },
+
+ handleNodeTap(e) {
+ const { node } = e.detail;
+ if (node && node.type === 'image') {
+ wx.previewImage({ urls: [node.href], current: node.href });
+ }
+ },
+});
diff --git a/packages/pro-components/chat/chat-markdown/_example/tail/index.json b/packages/pro-components/chat/chat-markdown/_example/tail/index.json
new file mode 100644
index 000000000..1f8a5342b
--- /dev/null
+++ b/packages/pro-components/chat/chat-markdown/_example/tail/index.json
@@ -0,0 +1,6 @@
+{
+ "usingComponents": {
+ "t-chat-markdown": "tdesign-miniprogram/chat-markdown/chat-markdown",
+ "custom-tail": "./custom-tail/custom-tail"
+ }
+}
\ No newline at end of file
diff --git a/packages/pro-components/chat/chat-markdown/_example/tail/index.wxml b/packages/pro-components/chat/chat-markdown/_example/tail/index.wxml
new file mode 100644
index 000000000..46e45bde3
--- /dev/null
+++ b/packages/pro-components/chat/chat-markdown/_example/tail/index.wxml
@@ -0,0 +1,18 @@
+
+
+
+ 默认光标
+
+
+
+
+ 自定义光标组件
+
+
+
+
diff --git a/packages/pro-components/chat/chat-markdown/_example/tail/index.wxss b/packages/pro-components/chat/chat-markdown/_example/tail/index.wxss
new file mode 100644
index 000000000..700cf8b15
--- /dev/null
+++ b/packages/pro-components/chat/chat-markdown/_example/tail/index.wxss
@@ -0,0 +1,4 @@
+.chat-example-block {
+ background-color: var(--td-bg-color-container);
+ padding: 32rpx;
+}
diff --git a/packages/pro-components/chat/chat-markdown/chat-markdown-node/chat-markdown-node.json b/packages/pro-components/chat/chat-markdown/chat-markdown-node/chat-markdown-node.json
index 984cca0aa..b83e0193d 100644
--- a/packages/pro-components/chat/chat-markdown/chat-markdown-node/chat-markdown-node.json
+++ b/packages/pro-components/chat/chat-markdown/chat-markdown-node/chat-markdown-node.json
@@ -1,9 +1,14 @@
{
"component": true,
"styleIsolation": "apply-shared",
+ "componentGenerics": {
+ "tail-component": {
+ "default": "../chat-markdown-tail/chat-markdown-tail"
+ }
+ },
"usingComponents": {
"chat-markdown-table": "../chat-markdown-table/chat-markdown-table",
"chat-markdown-code": "../chat-markdown-code/chat-markdown-code",
"chat-markdown-node": "./chat-markdown-node"
}
-}
+}
\ No newline at end of file
diff --git a/packages/pro-components/chat/chat-markdown/chat-markdown-node/chat-markdown-node.wxml b/packages/pro-components/chat/chat-markdown/chat-markdown-node/chat-markdown-node.wxml
index f3ce92f2f..023aefda0 100644
--- a/packages/pro-components/chat/chat-markdown/chat-markdown-node/chat-markdown-node.wxml
+++ b/packages/pro-components/chat/chat-markdown/chat-markdown-node/chat-markdown-node.wxml
@@ -2,7 +2,7 @@
-
+
@@ -15,7 +15,7 @@
-
+
{{''+li.text+''}}
@@ -24,7 +24,7 @@
-
+
@@ -37,7 +37,7 @@
+ >
@@ -53,15 +53,18 @@
bindtap="nodeClick"
>
-
+
+
+
+ {{''+item.raw+''}}
+
- {{''+item.raw+''}}
-
+
{{''+item.text+''}}
@@ -69,7 +72,7 @@
-
+
{{''+item.text+''}}
@@ -77,7 +80,7 @@
-
+
{{''+item.text+''}}
@@ -85,7 +88,7 @@
-
+
diff --git a/packages/pro-components/chat/chat-markdown/chat-markdown-tail/chat-markdown-tail.json b/packages/pro-components/chat/chat-markdown/chat-markdown-tail/chat-markdown-tail.json
new file mode 100644
index 000000000..66165257f
--- /dev/null
+++ b/packages/pro-components/chat/chat-markdown/chat-markdown-tail/chat-markdown-tail.json
@@ -0,0 +1,4 @@
+{
+ "component": true,
+ "styleIsolation": "apply-shared"
+}
\ No newline at end of file
diff --git a/packages/pro-components/chat/chat-markdown/chat-markdown-tail/chat-markdown-tail.ts b/packages/pro-components/chat/chat-markdown/chat-markdown-tail/chat-markdown-tail.ts
new file mode 100644
index 000000000..ea48b23a8
--- /dev/null
+++ b/packages/pro-components/chat/chat-markdown/chat-markdown-tail/chat-markdown-tail.ts
@@ -0,0 +1,22 @@
+import { SuperComponent, wxComponent, ComponentsOptionsType } from '../../../../components/common/src/index';
+import config from '../../../../components/common/config';
+
+const { prefix } = config;
+const name = `${prefix}-chat-markdown`;
+
+@wxComponent()
+export default class ChatMarkdownTail extends SuperComponent {
+ options: ComponentsOptionsType = {};
+
+ properties = {
+ /** 光标字符内容,由 chat-markdown 内部注入,默认 ▋ */
+ content: {
+ type: String,
+ value: '▋',
+ },
+ };
+
+ data = {
+ classPrefix: name,
+ };
+}
diff --git a/packages/pro-components/chat/chat-markdown/chat-markdown-tail/chat-markdown-tail.wxml b/packages/pro-components/chat/chat-markdown/chat-markdown-tail/chat-markdown-tail.wxml
new file mode 100644
index 000000000..f06047840
--- /dev/null
+++ b/packages/pro-components/chat/chat-markdown/chat-markdown-tail/chat-markdown-tail.wxml
@@ -0,0 +1 @@
+{{content}}
diff --git a/packages/pro-components/chat/chat-markdown/chat-markdown.json b/packages/pro-components/chat/chat-markdown/chat-markdown.json
index 640df53ef..5c6a3fa3f 100644
--- a/packages/pro-components/chat/chat-markdown/chat-markdown.json
+++ b/packages/pro-components/chat/chat-markdown/chat-markdown.json
@@ -1,7 +1,12 @@
{
"component": true,
"styleIsolation": "shared",
+ "componentGenerics": {
+ "tail-component": {
+ "default": "./chat-markdown-tail/chat-markdown-tail"
+ }
+ },
"usingComponents": {
"chat-markdown-node": "./chat-markdown-node/chat-markdown-node"
}
-}
+}
\ No newline at end of file
diff --git a/packages/pro-components/chat/chat-markdown/chat-markdown.less b/packages/pro-components/chat/chat-markdown/chat-markdown.less
index 8c8e8ec3f..7d2769427 100644
--- a/packages/pro-components/chat/chat-markdown/chat-markdown.less
+++ b/packages/pro-components/chat/chat-markdown/chat-markdown.less
@@ -189,4 +189,20 @@
border: 1rpx solid @component-border;
}
}
+
+ // 流式输出尾部光标
+ &-tail {
+ display: inline-block;
+ animation: chat-markdown-tail-blink 1s step-start infinite;
+ }
+}
+
+@keyframes chat-markdown-tail-blink {
+ 0%,
+ 100% {
+ opacity: 1;
+ }
+ 50% {
+ opacity: 0;
+ }
}
diff --git a/packages/pro-components/chat/chat-markdown/chat-markdown.ts b/packages/pro-components/chat/chat-markdown/chat-markdown.ts
index 3fce2f5c3..8b4bf3b62 100644
--- a/packages/pro-components/chat/chat-markdown/chat-markdown.ts
+++ b/packages/pro-components/chat/chat-markdown/chat-markdown.ts
@@ -7,6 +7,55 @@ import { TdChatMarkdownProps } from './type';
const { prefix } = config;
const name = `${prefix}-chat-markdown`;
+const DEFAULT_TAIL_CONTENT = '▋';
+
+/** 解析 tail 参数,返回光标字符;不需要显示时返回 null */
+function resolveTailContent(tail?: boolean | { content?: string }): string | null {
+ if (!tail) return null;
+ if (typeof tail === 'boolean') return DEFAULT_TAIL_CONTENT;
+ return tail.content || DEFAULT_TAIL_CONTENT;
+}
+
+/**
+ * 将列表项的子 tokens 展平,供 injectTailToTokens 递归使用。
+ * marked 的 list token 结构:list.items[].tokens(而非 list.tokens)
+ */
+function flatListItems(items: any[]): any[] {
+ return items.reduce((result: any[], item: any) => {
+ if (item.tokens?.length) result.push(...item.tokens);
+ return result;
+ }, []);
+}
+
+/**
+ * 从后往前遍历 token 树,找到最后一个非空 text 叶子节点,打上 isTail 标记。
+ * - 有子节点(tokens / items)时优先递归
+ * - 末尾是 code / table / image 等非 text 节点时静默跳过,不注入
+ * @returns 是否成功注入
+ */
+function injectTailToTokens(tokens: any[], tailChar: string): boolean {
+ for (let i = tokens.length - 1; i >= 0; i -= 1) {
+ const token = tokens[i];
+ // 优先递归子节点
+ let children: any[] | null = null;
+ if (token.tokens?.length) {
+ children = token.tokens;
+ } else if (token.items?.length) {
+ children = flatListItems(token.items);
+ }
+ if (children?.length) {
+ if (injectTailToTokens(children, tailChar)) return true;
+ }
+ // 叶子文本节点且内容非空
+ if (token.type === 'text' && (token.text || token.raw)?.trim()) {
+ token.isTail = true;
+ token.tailContent = tailChar;
+ return true;
+ }
+ }
+ return false;
+}
+
export interface ChatMarkdownProps extends TdChatMarkdownProps {}
@wxComponent()
@@ -28,6 +77,10 @@ export default class ChatMarkdown extends SuperComponent {
content: function (markdown: string) {
this.parseMarkdown(markdown);
},
+ // streaming 变化时重新解析(如 hasNextChunk 从 true 变 false,光标消失)
+ streaming: function () {
+ this.parseMarkdown(this.data.content);
+ },
};
methods = {
@@ -37,6 +90,13 @@ export default class ChatMarkdown extends SuperComponent {
const lexer = new Lexer(this.data.options);
const tokens = lexer.lex(markdown);
+ // 尾部光标注入
+ const { streaming } = this.data;
+ const tailChar = resolveTailContent(streaming?.tail);
+ if (streaming?.hasNextChunk && tailChar) {
+ injectTailToTokens(tokens, tailChar);
+ }
+
this.setData({ nodes: tokens });
} catch (error) {
console.error('Markdown parsing error:', error);
diff --git a/packages/pro-components/chat/chat-markdown/chat-markdown.wxml b/packages/pro-components/chat/chat-markdown/chat-markdown.wxml
index 4f84d8603..3fa72f3e3 100644
--- a/packages/pro-components/chat/chat-markdown/chat-markdown.wxml
+++ b/packages/pro-components/chat/chat-markdown/chat-markdown.wxml
@@ -1,5 +1,5 @@
-
+
diff --git a/packages/pro-components/chat/chat-markdown/props.ts b/packages/pro-components/chat/chat-markdown/props.ts
index 5bcc8967f..e83ec446e 100644
--- a/packages/pro-components/chat/chat-markdown/props.ts
+++ b/packages/pro-components/chat/chat-markdown/props.ts
@@ -17,6 +17,11 @@ const props: TdChatMarkdownProps = {
type: Object,
value: { gfm: true, pedantic: false, breaks: true },
},
+ /** 流式输出配置 */
+ streaming: {
+ type: Object,
+ value: null,
+ },
};
export default props;
diff --git a/packages/pro-components/chat/chat-markdown/type.ts b/packages/pro-components/chat/chat-markdown/type.ts
index adf560d5a..5e59a07d0 100644
--- a/packages/pro-components/chat/chat-markdown/type.ts
+++ b/packages/pro-components/chat/chat-markdown/type.ts
@@ -22,6 +22,13 @@ export interface TdChatMarkdownProps {
type: ObjectConstructor;
value?: TdChatContentMDOptions;
};
+ /**
+ * 流式输出配置,控制尾部光标的显示与隐藏
+ */
+ streaming?: {
+ type: ObjectConstructor;
+ value?: TdChatMarkdownStreamingOption;
+ };
}
export interface TdChatContentMDOptions {
@@ -30,3 +37,15 @@ export interface TdChatContentMDOptions {
smartLists?: boolean;
breaks?: boolean;
}
+
+export interface TdChatMarkdownTailOption {
+ /** 自定义光标字符,默认 '▋' */
+ content?: string;
+}
+
+export interface TdChatMarkdownStreamingOption {
+ /** 是否还有后续内容块,false 时光标消失 */
+ hasNextChunk: boolean;
+ /** 尾部光标配置,true 使用默认光标 ▋,false/不传则不显示 */
+ tail?: boolean | TdChatMarkdownTailOption;
+}
diff --git a/packages/uniapp-pro-components/chat/chat-content/chat-content.vue b/packages/uniapp-pro-components/chat/chat-content/chat-content.vue
index fb4d79e09..89545afb7 100644
--- a/packages/uniapp-pro-components/chat/chat-content/chat-content.vue
+++ b/packages/uniapp-pro-components/chat/chat-content/chat-content.vue
@@ -16,6 +16,7 @@
diff --git a/packages/uniapp-pro-components/chat/chat-list/_example/docs/index.vue b/packages/uniapp-pro-components/chat/chat-list/_example/docs/index.vue
index 8c4204f6c..e1b391ac7 100644
--- a/packages/uniapp-pro-components/chat/chat-list/_example/docs/index.vue
+++ b/packages/uniapp-pro-components/chat/chat-list/_example/docs/index.vue
@@ -14,12 +14,28 @@
:avatar="item.avatar || ''"
:name="item.name || ''"
:datetime="item.datetime || ''"
- :content="item.message.content"
:role="item.message.role"
- :chat-content-props="chatContentProps"
:placement="item.message.role === 'user' ? 'right' : 'left'"
@message-longpress="showPopover"
>
+
+
+
+
+
+ {{ item.tailContent }}
+
diff --git a/packages/uniapp-pro-components/chat/chat-markdown/README.md b/packages/uniapp-pro-components/chat/chat-markdown/README.md
index 055c98ef0..7127c267f 100644
--- a/packages/uniapp-pro-components/chat/chat-markdown/README.md
+++ b/packages/uniapp-pro-components/chat/chat-markdown/README.md
@@ -43,6 +43,10 @@ import TChatMarkdown from '@tdesign/uniapp-chat/chat-markdown/chat-markdown.vue'
{{ refer }}
+### 05 流式输出光标
+
+{{ tail }}
+
## API
### ChatMarkdown Props
@@ -51,6 +55,7 @@ import TChatMarkdown from '@tdesign/uniapp-chat/chat-markdown/chat-markdown.vue'
-- | -- | -- | -- | --
custom-style | Object | - | 自定义样式 | N
content | String | - | 必需。markdown 内容文本 | Y
+streaming | Object | - | 流式输出配置,控制光标显示。TS 类型:`TdChatStreamingConfig` `interface TdChatStreamingConfig { hasNextChunk?: boolean; tail?: boolean \| { content?: string } }`。[详细类型定义](https://github.com/tencent/tdesign-miniprogram/blob/develop/packages/uniapp-pro-components/chat/chat-markdown/type.ts) | N
options | Object | { gfm: true, pedantic: false, breaks: true } | Markdown 解析器基础配置。TS 类型:`TdChatContentMDOptions ` `interface TdChatContentMDOptions {gfm?: boolean; pedantic?: boolean; smartLists?: boolean; breaks?: boolean}`。[详细类型定义](https://github.com/tencent/tdesign-miniprogram/blob/develop/packages/uniapp-pro-components/chat/chat-markdown/type.ts) | N
### ChatMarkdown Events
diff --git a/packages/uniapp-pro-components/chat/chat-markdown/_example/chat-markdown.vue b/packages/uniapp-pro-components/chat/chat-markdown/_example/chat-markdown.vue
index 95c1875c9..3386b69da 100644
--- a/packages/uniapp-pro-components/chat/chat-markdown/_example/chat-markdown.vue
+++ b/packages/uniapp-pro-components/chat/chat-markdown/_example/chat-markdown.vue
@@ -34,6 +34,9 @@
+
+
+
@@ -45,6 +48,7 @@ import CodeDemo from './code/index.vue';
import SheetDemo from './sheet/index.vue';
import UrlDemo from './url/index.vue';
import ReferDemo from './refer/index.vue';
+import TailDemo from './tail/index.vue';
export default {
@@ -55,6 +59,7 @@ export default {
SheetDemo,
UrlDemo,
ReferDemo,
+ TailDemo,
},
data() {
return {};
diff --git a/packages/uniapp-pro-components/chat/chat-markdown/_example/tail/index.vue b/packages/uniapp-pro-components/chat/chat-markdown/_example/tail/index.vue
new file mode 100644
index 000000000..c45d9efd0
--- /dev/null
+++ b/packages/uniapp-pro-components/chat/chat-markdown/_example/tail/index.vue
@@ -0,0 +1,68 @@
+
+
+
+
+
+
+
+
+
diff --git a/packages/uniapp-pro-components/chat/chat-markdown/chat-markdown.less b/packages/uniapp-pro-components/chat/chat-markdown/chat-markdown.less
index 09defedb2..e02148a87 100644
--- a/packages/uniapp-pro-components/chat/chat-markdown/chat-markdown.less
+++ b/packages/uniapp-pro-components/chat/chat-markdown/chat-markdown.less
@@ -150,6 +150,12 @@
border: 1rpx solid @component-border;
}
+ // 流式输出尾部光标
+ &-tail {
+ display: inline-block;
+ animation: chat-markdown-tail-blink 1s step-start infinite;
+ }
+
// 表格样式
.@{chat-markdown-table}__container {
display: table;
@@ -190,3 +196,14 @@
}
}
}
+
+@keyframes chat-markdown-tail-blink {
+ 0%,
+ 100% {
+ opacity: 1;
+ }
+
+ 50% {
+ opacity: 0;
+ }
+}
diff --git a/packages/uniapp-pro-components/chat/chat-markdown/chat-markdown.vue b/packages/uniapp-pro-components/chat/chat-markdown/chat-markdown.vue
index 7831cff1f..487cc8444 100644
--- a/packages/uniapp-pro-components/chat/chat-markdown/chat-markdown.vue
+++ b/packages/uniapp-pro-components/chat/chat-markdown/chat-markdown.vue
@@ -22,9 +22,44 @@ import props from './props';
import tools from '@tdesign/uniapp/common/utils.wxs';
import { uniComponent } from '@tdesign/uniapp/common/src/index';
-
const name = `${prefix}-chat-markdown`;
+const DEFAULT_TAIL_CONTENT = '▋';
+
+function resolveTailContent(tail) {
+ if (!tail) return null;
+ if (typeof tail === 'boolean') return DEFAULT_TAIL_CONTENT;
+ return tail.content || DEFAULT_TAIL_CONTENT;
+}
+
+function flatListItems(items) {
+ return items.reduce((result, item) => {
+ if (item.tokens?.length) result.push(...item.tokens);
+ return result;
+ }, []);
+}
+
+function injectTailToTokens(tokens, tailChar) {
+ for (let i = tokens.length - 1; i >= 0; i -= 1) {
+ const token = tokens[i];
+ let children = null;
+ if (token.tokens?.length) {
+ children = token.tokens;
+ } else if (token.items?.length) {
+ children = flatListItems(token.items);
+ }
+ if (children?.length) {
+ if (injectTailToTokens(children, tailChar)) return true;
+ }
+ if (token.type === 'text' && (token.text || token.raw)?.trim()) {
+ token.isTail = true;
+ token.tailContent = tailChar;
+ return true;
+ }
+ }
+ return false;
+}
+
export default {
components: {
chatMarkdownNode,
@@ -56,6 +91,12 @@ export default {
immediate: true,
deep: true,
},
+ streaming: {
+ handler() {
+ this.parseMarkdown(this.content);
+ },
+ deep: true,
+ },
},
methods: {
@@ -72,6 +113,12 @@ export default {
const tokens = lexer.lex(markdown);
+ // 尾部光标注入
+ const tailChar = resolveTailContent(this.streaming?.tail);
+ if (this.streaming?.hasNextChunk && tailChar) {
+ injectTailToTokens(tokens, tailChar);
+ }
+
this.nodes = tokens;
} catch (error) {
// 解析失败时,将原始文本作为普通文本显示
diff --git a/packages/uniapp-pro-components/chat/chat-markdown/props.ts b/packages/uniapp-pro-components/chat/chat-markdown/props.ts
index 4eaccdcdd..3723a0d1e 100644
--- a/packages/uniapp-pro-components/chat/chat-markdown/props.ts
+++ b/packages/uniapp-pro-components/chat/chat-markdown/props.ts
@@ -16,6 +16,11 @@ export default {
type: Object,
default: () => ({ gfm: true, pedantic: false, breaks: true }),
},
+ /** 流式输出配置 */
+ streaming: {
+ type: Object,
+ default: () => null,
+ },
/** 点击链接时触发 */
onClick: {
type: Function,
diff --git a/packages/uniapp-pro-components/chat/chat-markdown/type.ts b/packages/uniapp-pro-components/chat/chat-markdown/type.ts
index da3890d2b..d099eab6e 100644
--- a/packages/uniapp-pro-components/chat/chat-markdown/type.ts
+++ b/packages/uniapp-pro-components/chat/chat-markdown/type.ts
@@ -15,12 +15,23 @@ export interface TdChatMarkdownProps {
* @default { gfm: true, pedantic: false, breaks: true }
*/
options?: TdChatContentMDOptions;
+ /**
+ * 流式输出配置
+ */
+ streaming?: TdChatStreamingConfig;
/**
* 点击链接时触发
*/
onClick?: (context: TdMarkdownClickContext) => void;
}
+export interface TdChatStreamingConfig {
+ /** 是否还有下一个数据块,控制光标显隐 */
+ hasNextChunk?: boolean;
+ /** 尾部光标配置,true 使用默认光标 ▋,传对象可自定义光标字符 */
+ tail?: boolean | { content?: string };
+}
+
export interface TdChatContentMDOptions {
gfm?: boolean;
pedantic?: boolean;