更新记录
1.0(2026-01-27)
- 支持流式输出的md格式
- 支持流式输出过程中自定义组件
平台兼容性
uni-app(4.85)
| Vue2 |
Vue3 |
Chrome |
Safari |
app-vue |
app-nvue |
Android |
iOS |
鸿蒙 |
| × |
√ |
√ |
√ |
√ |
- |
√ |
√ |
- |
| 微信小程序 |
支付宝小程序 |
抖音小程序 |
百度小程序 |
快手小程序 |
京东小程序 |
鸿蒙元服务 |
QQ小程序 |
飞书小程序 |
小红书小程序 |
快应用-华为 |
快应用-联盟 |
| - |
- |
- |
- |
- |
- |
- |
- |
- |
- |
- |
- |
介绍
这是一款解析markdown的流式输出插件,与市场上不同的是,它支持自定义组件的定义,现在支持vue3版本的android、ios、h5等版本。小程序等版本未测试...
属性
| 属性 名称 |
介绍 |
| customComponents |
自定义组件,当md字符串遇到该字符串时将转化为组件,推荐markRaw包裹避免渲染性能降低 |
| markdownContent |
md字符串 |
| typewriter |
是否打字机效果 |
| typewriterDelay |
打字机频率ms |
| typewriterStep |
打字机频率 xx字/ms |
子组件使用
我们为子组件设计了一套接收属性的方案,参考了标准的antdX的设计
| 属性 名称 |
介绍 |
| children |
标签中间的值,建议通常为json数据 |
| status |
状态,当加载标签开始即可为loading,当加载闭合标签时为done |
| 自定义标签属性 |
拓展,与后端约定 |
使用案例
<template>
<view class="content">
<DecongXMarkdown
:customComponents="customComponents"
:markdownContent="markdownContent"
:typewriter="true"
:typewriterDelay="20"
:typewriterStep="5"
/>
</view>
</template>
import { ref, onMounted, onUnmounted,markRaw } from 'vue'
import DecongXMarkdown from '@/uni_modules/components/decong-xmarkdown/decong-xmarkdown.vue'
import CustomLoading from './custom-loading.vue'
import CustomEcharts from './echarts/index.vue'
const markdownContent = ref('')
const customComponents = markRaw({
'custom-loading': CustomLoading,
'custom-echarts': CustomEcharts
})
// 模拟SSE数据
const sseData = [
'\n\n<custom-loading value="解析用户意图">{"data":"1","status2":"done","children2":"123","status3":"loading","children3":"456"}</custom-loading>',
'您好!我是您的**专业分析助手**。',
'\n\n由于您目前输入的信息过于简略,我暂时无法确定您想要查询或分析的具体内容。',
'\n\n为了能够精准地为您提供服务,请您补充以下关键信息:',
'\n\n1. **查询主体**:您想关注的对象是谁?',
'\n\n2. **时间范围**:您需要看哪个时段的数据?\n\n',
'自定义组件案例:\n\n',
'<custom-echarts type="line">{"xAxisData":[1,2,3,4,5],"yAxisData":[59.3,64.4,68.9,74.4,82.7,91.9,99.1,101.6,114.4,121]}</custom-echarts>',
]
let sseIndex = 0
let sseInterval: number | null = null
// 模拟SSE连接
const simulateSSE = () => {
sseInterval = setInterval(() => {
if (sseIndex < sseData.length) {
markdownContent.value += sseData[sseIndex]
sseIndex++
} else {
if (sseInterval) {
clearInterval(sseInterval)
sseInterval = null
}
}
}, 300) as unknown as number
}
onMounted(() => {
simulateSSE()
})
onUnmounted(() => {
if (sseInterval) {
clearInterval(sseInterval)
}
})
子组件案例
<template>
<view class="custom-loading">
<text v-if="status != 'done'"> loading...</text>
<view v-else>
<view> 自定义内容显示 </view>
<view> 属性值 :{{ value }}</view>
<view> 子属性值 :{{ children }}</view>
<view> 状态值 :{{ status }}</view>
</view>
</view>
</template>
<script setup lang="ts">
import { defineProps,watch } from 'vue'
const props = defineProps({
value: {
type: String,
default: ''
},
status: {
type: String,
default: 'loading'
},
children: {
type: String,
default: ''
}
})
watch(() => [props.children,props.status], ([newChildren,newStatus]:any) => {
console.log(newChildren,newStatus,'newChildren,newStatus')
})
</script>