使用 Ant design X vue 组件库怎么解决无法正确展示 MarkDown 格式字符串的问题?

1 天前
tiRolin  tiRolin

是这样的,我毕设搞了一个 AI 对话,但是呢,这个 AI 对话返回的字符串是 markdown 格式,但是我前端无法正确解析,最后字符串格式就会变成下面这样

这里显然没有正确解析出样式来,我试了很多方法都没能解决这个问题,各个 ai 问了个遍了,然后去网上找了好多方法,还是没搞定,下面是我这个页面的源码,有没有大佬帮我看看到底该怎么解决这个问题啊?小弟我感激不尽啊

<template>
  <div class="layout">
    <div class="menu">
      <RouterLink to="/" class="re-home">返回首页</RouterLink>
      <!-- Logo -->
      <div class="logo">
        <img :src="logo" draggable="false" alt="logo" class="logo-img" />
        <span class="logo-span">柑橘智能问答</span>
      </div>
    </div>
    <div class="chat">
      <!-- 消息列表 -->
      <Bubble.List
        :items="messages"
        class="messages"
        style="flex: 1"
        :messageRender="renderMarkdown"
        :roles="{
          ai: {
            placement: 'start',
            typing: { step: 5, interval: 20 },
            styles: {
              content: {
                borderRadius: '16px'
              }
            }
          },
          local: {
            placement: 'end',
            variant: 'shadow'
          }
        }"
      />

      <!-- 输入框 -->
      <Sender
        :value="content"
        class="sender"
        @submit="onSubmit"
        @update:value="(val) => (content = val)"
      />
    </div>
  </div>
</template>

<script setup>
import { ref, h } from 'vue'
import { Conversations, Prompts, Sender, Bubble } from 'ant-design-x-vue'
import { Edit, Delete } from '@element-plus/icons-vue'
import reqAIChat from '@/api/chat/index'
import { message } from 'ant-design-vue'
import { ElMessage } from 'element-plus'
import logo from '@/assets/images/logo.png'
import { marked } from 'marked'
import hljs from 'highlight.js'
import 'highlight.js/styles/github.css'
marked.setOptions({
  highlight: (code) => hljs.highlightAuto(code).value,
  breaks: true,
  gfm: true
})

marked.use({
  renderer: {
    link(href, title, text) {
      return `<a href="${href}" target="_blank" rel="noopener">${text}</a>`
    }
  }
})
const renderMarkdown = (content, item) => {
  if (item?.isMarkdown) {
    return h('Typography', { class: 'markdown-container' }, [
      h('div', {
        innerHTML: marked.parse(content), // Vue 3 直接注入 HTML
        class: 'markdown-body'
      })
    ])
  }
  return content // 普通文本直接返回
}
const content = ref('')

const messages = ref([
  {
    key: '1',
    content:
      '## Markdown 测试\n[链接]( https://x.ant.design)1. **橙( Orange )**:包括甜橙和酸橙,甜橙中又分为脐橙、瓦伦西亚橙等。2. **柑橘( Mandarin )**:也称为蜜橘,包括各种小型、易剥皮的柑橘。3. **柚子( Pomelo )**:也称为文旦,是柑橘类中最大的一种。4. **柠檬( Lemon )**:以其酸味和香气闻名。5. **青柠( Lime )**:比柠檬小,酸味较轻。6. **葡萄柚( Grapefruit )**:大小和形状类似葡萄,味道可以是甜的也可以是酸的。',
    role: 'ai',
    variant: 'primary',
    isMarkdown: true
  }
])
const activeKey = ref('0')
const conversationsItems = ref(
  Array.from({ length: 2 }).map((_, index) => ({
    key: `item${index + 1}`,
    label: `未命名对话 ${index + 1}`
  }))
)
const onSubmit = async (nextContent) => {
  if (!nextContent) return
  messages.value.push({
    key: `${messages.value.length + 1}`,
    content: nextContent,
    role: 'local',
    variant: 'shadow'
  })
  content.value = ''
  try {
    const res = await reqAIChat({ message: nextContent })
    if (!res) {
      ElMessage({
        message: '请求失败',
        type: 'error'
      })
      return
    }

    messages.value.push({
      key: `${messages.value.length + 1}`,
      content: res,
      role: 'ai',
      variant: 'primary',
      isMarkdown: true
    })
  } catch (error) {
    ElMessage({
      message: '请求出错',
      type: 'error'
    })
  }
}
const onAddConversation = () => {
  conversationsItems.value.push({
    key: `${conversationsItems.value.length + 1}`,
    label: `未命名对话${conversationsItems.value.length + 1}`
  })
  activeKey.value = `${conversationsItems.value.length + 1}`
}

const menuConfig = (conversation) => {
  return {
    items: [
      {
        label: '编辑',
        key: 'edit',
        icon: () => h(Edit)
      },
      {
        label: '删除',
        key: 'delete',
        icon: () => h(Delete),
        danger: true
      }
    ],
    onClick: (menuInfo) => {
      message.info(`点击 ${conversation.key} - ${menuInfo.key}`)
    }
  }
}
</script>

<style scoped>
.markdown-body {
  padding: 12px 16px;
  line-height: 1.7;

  /* 必须继承字体 */
  font-family: inherit;

  /* 代码块样式 */
  pre {
    padding: 12px;
    border-radius: 8px;
    background: #f6f8fa !important;
  }

  code {
    font-family: 'SFMono-Regular', Consolas, monospace;
  }
}
.markdown-body {
  line-height: 1.6;
  color: #333;
}

.re-home {
  display: inline-block;
  padding: 10px 20px;
  margin: 12px;
  background: #ffa500;
  color: white;
  text-decoration: none;
  border-radius: 4px;
  font-size: 14px;
  font-weight: bold;
  text-align: center;
  transition: background-color 0.3s ease;
}
.layout {
  width: 100%;
  min-width: 1000px;
  height: 722px;
  display: flex;
  background: #fff;
  font-family: AlibabaPuHuiTi, sans-serif;
}

.menu {
  padding: 24px 0;
  /* background: #fff; */
  width: 280px;
  height: 100%;
  display: flex;
  flex-direction: column;
  background: rgba(245, 245, 245, 0.5);
}

.logo {
  display: flex;
  height: 72px;
  align-items: center;
  justify-content: start;
  padding: 0 24px;
}

.logo-img {
  width: 24px;
  height: 24px;
}

.logo-span {
  margin: 0 8px;
  font-weight: bold;
  color: #333;
  font-size: 16px;
}

.add-btn {
  background: #1677ff0f;
  border: 1px solid #1677ff34;
  width: calc(100% - 24px);
  cursor: pointer;
  margin: 0 12px 24px 12px;
  font-size: 14px;
  height: 32px;
  padding: 4px 15px;
  border-radius: 6px;
}
.add-btn:hover {
  color: #69b1ff;
}

.chat {
  height: 100%;
  width: 100%;
  max-width: 700px;
  margin: 0 auto;
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  padding: 24px;
  gap: 16px;
}
</style>

这里 content 里已经有一些写死的内容了,那是我用来测试的,就是想着如果可以解析了那么这些写死的内容是会被正确解析上去的,但是我看了好久都没搞懂该怎么解决这个问题,所以这个写死的内容也一直留着了

585 次点击
所在节点    Vue.js
4 条回复
learnshare
1 天前
跟所用的 UI 框架无关,需要将 Markdown 渲染为 HTML
https://github.com/markdown-it/markdown-it
learnshare
1 天前
很久以前的代码可以参考,但相关 API 有点不同
https://github.com/LearnShare/markdown.css/blob/master/index.js#L27
Linho1219
1 天前
翻了文档,Bubble.List 应该是不支持 messageRender 属性的,只有 Bubble 支持。试了一下,把 .List 去掉,content 传入字符串就能正常渲染了。所以可以考虑用 v-for 和 Bubble 一起来实现。

文档: https://antd-design-x-vue.netlify.app/component/bubble#api

多看文档、在能够复现问题的前提下将项目文件裁剪到最小!出问题的就是 Bubble ,带了一箩筐页面和样式什么的,发出来这么长。把不影响的部分先删掉也方便你自己定位问题。
都毕设了,我想你应该是有能力独立解决这个问题的。我之前都没碰过这个 ant design X ,花一会时间也找到了
shintendo
1 天前
楼上+1 ,提问时尽量找最小复现,在这个过程中很可能自己就定位到问题了

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/1119152

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX