更新记录

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>

隐私、权限声明

1. 本插件需要申请的系统权限列表:

2. 本插件采集的数据、发送的服务器地址、以及数据用途说明:

3. 本插件是否包含广告,如包含需详细说明广告表达方式、展示频率: