页面右侧聊天助手的ai输出结果加上md文档格式显示

This commit is contained in:
xuxin
2026-05-28 17:41:23 +08:00
parent 96071d0105
commit 0e6fde08c7
5 changed files with 170 additions and 2 deletions
+2
View File
@@ -13,12 +13,14 @@
"axios": "^1.13.6", "axios": "^1.13.6",
"element-plus": "^2.13.3", "element-plus": "^2.13.3",
"html2pdf.js": "^0.14.0", "html2pdf.js": "^0.14.0",
"markdown-it": "^14.2.0",
"sass": "^1.97.3", "sass": "^1.97.3",
"vue": "^3.5.25", "vue": "^3.5.25",
"vue-router": "^4.6.4", "vue-router": "^4.6.4",
"vuex": "^4.1.0" "vuex": "^4.1.0"
}, },
"devDependencies": { "devDependencies": {
"@types/markdown-it": "^14.1.2",
"@types/node": "^24.10.1", "@types/node": "^24.10.1",
"@vitejs/plugin-vue": "^6.0.2", "@vitejs/plugin-vue": "^6.0.2",
"@vue/tsconfig": "^0.8.1", "@vue/tsconfig": "^0.8.1",
+71
View File
@@ -20,6 +20,9 @@ importers:
html2pdf.js: html2pdf.js:
specifier: ^0.14.0 specifier: ^0.14.0
version: 0.14.0 version: 0.14.0
markdown-it:
specifier: ^14.2.0
version: 14.2.0
sass: sass:
specifier: ^1.97.3 specifier: ^1.97.3
version: 1.97.3 version: 1.97.3
@@ -33,6 +36,9 @@ importers:
specifier: ^4.1.0 specifier: ^4.1.0
version: 4.1.0(vue@3.5.29(typescript@5.9.3)) version: 4.1.0(vue@3.5.29(typescript@5.9.3))
devDependencies: devDependencies:
'@types/markdown-it':
specifier: ^14.1.2
version: 14.1.2
'@types/node': '@types/node':
specifier: ^24.10.1 specifier: ^24.10.1
version: 24.11.0 version: 24.11.0
@@ -487,12 +493,21 @@ packages:
'@types/estree@1.0.8': '@types/estree@1.0.8':
resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==}
'@types/linkify-it@5.0.0':
resolution: {integrity: sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==}
'@types/lodash-es@4.17.12': '@types/lodash-es@4.17.12':
resolution: {integrity: sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==} resolution: {integrity: sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==}
'@types/lodash@4.17.24': '@types/lodash@4.17.24':
resolution: {integrity: sha512-gIW7lQLZbue7lRSWEFql49QJJWThrTFFeIMJdp3eH4tKoxm1OvEPg02rm4wCCSHS0cL3/Fizimb35b7k8atwsQ==} resolution: {integrity: sha512-gIW7lQLZbue7lRSWEFql49QJJWThrTFFeIMJdp3eH4tKoxm1OvEPg02rm4wCCSHS0cL3/Fizimb35b7k8atwsQ==}
'@types/markdown-it@14.1.2':
resolution: {integrity: sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==}
'@types/mdurl@2.0.0':
resolution: {integrity: sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==}
'@types/node@24.11.0': '@types/node@24.11.0':
resolution: {integrity: sha512-fPxQqz4VTgPI/IQ+lj9r0h+fDR66bzoeMGHp8ASee+32OSGIkeASsoZuJixsQoVef1QJbeubcPBxKk22QVoWdw==} resolution: {integrity: sha512-fPxQqz4VTgPI/IQ+lj9r0h+fDR66bzoeMGHp8ASee+32OSGIkeASsoZuJixsQoVef1QJbeubcPBxKk22QVoWdw==}
@@ -587,6 +602,9 @@ packages:
alien-signals@3.1.2: alien-signals@3.1.2:
resolution: {integrity: sha512-d9dYqZTS90WLiU0I5c6DHj/HcKkF8ZyGN3G5x8wSbslulz70KOxaqCT0hQCo9KOyhVqzqGojvNdJXoTumZOtcw==} resolution: {integrity: sha512-d9dYqZTS90WLiU0I5c6DHj/HcKkF8ZyGN3G5x8wSbslulz70KOxaqCT0hQCo9KOyhVqzqGojvNdJXoTumZOtcw==}
argparse@2.0.1:
resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
async-validator@4.2.5: async-validator@4.2.5:
resolution: {integrity: sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg==} resolution: {integrity: sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg==}
@@ -658,6 +676,10 @@ packages:
peerDependencies: peerDependencies:
vue: ^3.3.0 vue: ^3.3.0
entities@4.5.0:
resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==}
engines: {node: '>=0.12'}
entities@7.0.1: entities@7.0.1:
resolution: {integrity: sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==} resolution: {integrity: sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==}
engines: {node: '>=0.12'} engines: {node: '>=0.12'}
@@ -783,6 +805,9 @@ packages:
jspdf@4.2.1: jspdf@4.2.1:
resolution: {integrity: sha512-YyAXyvnmjTbR4bHQRLzex3CuINCDlQnBqoSYyjJwTP2x9jDLuKDzy7aKUl0hgx3uhcl7xzg32agn5vlie6HIlQ==} resolution: {integrity: sha512-YyAXyvnmjTbR4bHQRLzex3CuINCDlQnBqoSYyjJwTP2x9jDLuKDzy7aKUl0hgx3uhcl7xzg32agn5vlie6HIlQ==}
linkify-it@5.0.1:
resolution: {integrity: sha512-wVoTjP4Q6R0NW5hiZkVJaFZPWgtXfoGF+6LucL3/FtiNjmcHhYjEr5f1Kqjirc1nBW07J/ZuRFumqr2oqccEWg==}
local-pkg@1.1.2: local-pkg@1.1.2:
resolution: {integrity: sha512-arhlxbFRmoQHl33a0Zkle/YWlmNwoyt6QNZEIJcqNbdrsix5Lvc4HyyI3EnwxTYlZYc32EbYrQ8SzEZ7dqgg9A==} resolution: {integrity: sha512-arhlxbFRmoQHl33a0Zkle/YWlmNwoyt6QNZEIJcqNbdrsix5Lvc4HyyI3EnwxTYlZYc32EbYrQ8SzEZ7dqgg9A==}
engines: {node: '>=14'} engines: {node: '>=14'}
@@ -803,10 +828,17 @@ packages:
magic-string@0.30.21: magic-string@0.30.21:
resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==}
markdown-it@14.2.0:
resolution: {integrity: sha512-1TGiQiJVRQ3NPmZH6sx5Cfnmg6GQm9jvC1ch4TK511NjSJvjzKLzn5pPfZRNZkRPZP0HqCioSndqH8v2nRaWVQ==}
hasBin: true
math-intrinsics@1.1.0: math-intrinsics@1.1.0:
resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
mdurl@2.0.0:
resolution: {integrity: sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==}
memoize-one@6.0.0: memoize-one@6.0.0:
resolution: {integrity: sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==} resolution: {integrity: sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==}
@@ -870,6 +902,10 @@ packages:
proxy-from-env@1.1.0: proxy-from-env@1.1.0:
resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==}
punycode.js@2.3.1:
resolution: {integrity: sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==}
engines: {node: '>=6'}
quansync@0.2.11: quansync@0.2.11:
resolution: {integrity: sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==} resolution: {integrity: sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==}
@@ -931,6 +967,9 @@ packages:
engines: {node: '>=14.17'} engines: {node: '>=14.17'}
hasBin: true hasBin: true
uc.micro@2.1.0:
resolution: {integrity: sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==}
ufo@1.6.3: ufo@1.6.3:
resolution: {integrity: sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==} resolution: {integrity: sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==}
@@ -1328,12 +1367,21 @@ snapshots:
'@types/estree@1.0.8': {} '@types/estree@1.0.8': {}
'@types/linkify-it@5.0.0': {}
'@types/lodash-es@4.17.12': '@types/lodash-es@4.17.12':
dependencies: dependencies:
'@types/lodash': 4.17.24 '@types/lodash': 4.17.24
'@types/lodash@4.17.24': {} '@types/lodash@4.17.24': {}
'@types/markdown-it@14.1.2':
dependencies:
'@types/linkify-it': 5.0.0
'@types/mdurl': 2.0.0
'@types/mdurl@2.0.0': {}
'@types/node@24.11.0': '@types/node@24.11.0':
dependencies: dependencies:
undici-types: 7.16.0 undici-types: 7.16.0
@@ -1460,6 +1508,8 @@ snapshots:
alien-signals@3.1.2: {} alien-signals@3.1.2: {}
argparse@2.0.1: {}
async-validator@4.2.5: {} async-validator@4.2.5: {}
asynckit@0.4.0: {} asynckit@0.4.0: {}
@@ -1553,6 +1603,8 @@ snapshots:
transitivePeerDependencies: transitivePeerDependencies:
- '@vue/composition-api' - '@vue/composition-api'
entities@4.5.0: {}
entities@7.0.1: {} entities@7.0.1: {}
es-define-property@1.0.1: {} es-define-property@1.0.1: {}
@@ -1702,6 +1754,10 @@ snapshots:
dompurify: 3.3.3 dompurify: 3.3.3
html2canvas: 1.4.1 html2canvas: 1.4.1
linkify-it@5.0.1:
dependencies:
uc.micro: 2.1.0
local-pkg@1.1.2: local-pkg@1.1.2:
dependencies: dependencies:
mlly: 1.8.0 mlly: 1.8.0
@@ -1722,8 +1778,19 @@ snapshots:
dependencies: dependencies:
'@jridgewell/sourcemap-codec': 1.5.5 '@jridgewell/sourcemap-codec': 1.5.5
markdown-it@14.2.0:
dependencies:
argparse: 2.0.1
entities: 4.5.0
linkify-it: 5.0.1
mdurl: 2.0.0
punycode.js: 2.3.1
uc.micro: 2.1.0
math-intrinsics@1.1.0: {} math-intrinsics@1.1.0: {}
mdurl@2.0.0: {}
memoize-one@6.0.0: {} memoize-one@6.0.0: {}
mime-db@1.52.0: {} mime-db@1.52.0: {}
@@ -1783,6 +1850,8 @@ snapshots:
proxy-from-env@1.1.0: {} proxy-from-env@1.1.0: {}
punycode.js@2.3.1: {}
quansync@0.2.11: {} quansync@0.2.11: {}
raf@3.4.1: raf@3.4.1:
@@ -1864,6 +1933,8 @@ snapshots:
typescript@5.9.3: {} typescript@5.9.3: {}
uc.micro@2.1.0: {}
ufo@1.6.3: {} ufo@1.6.3: {}
undici-types@7.16.0: {} undici-types@7.16.0: {}
+84
View File
@@ -94,6 +94,90 @@
font-size: 0.13rem; font-size: 0.13rem;
line-height: 1.6; line-height: 1.6;
max-width: 85%; max-width: 85%;
// ===== Markdown 渲染样式 =====
p {
margin: 0 0 0.08rem;
&:last-child { margin-bottom: 0; }
}
h1, h2, h3, h4, h5, h6 {
margin: 0.12rem 0 0.06rem;
font-weight: 600;
line-height: 1.4;
}
h1 { font-size: 0.18rem; }
h2 { font-size: 0.16rem; }
h3 { font-size: 0.14rem; }
ul, ol {
padding-left: 0.2rem;
margin: 0.06rem 0;
}
li {
margin-bottom: 0.04rem;
}
code {
background: #f1f5f9;
padding: 0.01rem 0.04rem;
border-radius: 0.03rem;
font-size: 0.12rem;
font-family: 'Courier New', monospace;
}
pre {
background: #1e293b;
color: #e2e8f0;
padding: 0.1rem 0.12rem;
border-radius: 0.06rem;
overflow-x: auto;
margin: 0.08rem 0;
code {
background: none;
padding: 0;
color: inherit;
font-size: 0.11rem;
}
}
blockquote {
border-left: 3px solid #cbd5e1;
padding-left: 0.1rem;
margin: 0.08rem 0;
color: #64748b;
}
a {
color: #2563eb;
text-decoration: underline;
}
table {
border-collapse: collapse;
margin: 0.08rem 0;
width: 100%;
font-size: 0.12rem;
th, td {
border: 1px solid #e5e7eb;
padding: 0.04rem 0.08rem;
text-align: left;
}
th {
background: #f8fafc;
font-weight: 600;
}
}
hr {
border: none;
border-top: 1px solid #e5e7eb;
margin: 0.1rem 0;
}
} }
&__msg--ai &__msg-bubble { &__msg--ai &__msg-bubble {
+12 -2
View File
@@ -74,12 +74,22 @@
import { ref, computed, watch, nextTick, onMounted } from 'vue' import { ref, computed, watch, nextTick, onMounted } from 'vue'
import { useRoute } from 'vue-router' import { useRoute } from 'vue-router'
import { useStore } from 'vuex' import { useStore } from 'vuex'
import markdownit from 'markdown-it'
import MemberDialog from '@/components/MemberDialog.vue' import MemberDialog from '@/components/MemberDialog.vue'
import AiThinkingIndicator from '@/components/tools/AiThinkingIndicator.vue' import AiThinkingIndicator from '@/components/tools/AiThinkingIndicator.vue'
import { sendNovaChat } from '@/utils/aiRequest' import { sendNovaChat } from '@/utils/aiRequest'
import type { NovaChatHistoryItem } from '@/utils/aiRequest' import type { NovaChatHistoryItem } from '@/utils/aiRequest'
import { fetchResumeList } from '@/api/resume' import { fetchResumeList } from '@/api/resume'
// ==================== Markdown 渲染实例 ====================
/** markdown-it 实例,用于将 AI 回复的 Markdown 文本渲染为 HTML */
const md = markdownit({
html: false, // 禁止原始 HTML(防 XSS
breaks: true, // 将换行符转为 <br>
linkify: true, // 自动识别链接
})
// ==================== Props ==================== // ==================== Props ====================
/** 组件属性 */ /** 组件属性 */
@@ -180,9 +190,9 @@ const userQuestions = computed(() => quickQuestions.value)
// ==================== 内容格式化 ==================== // ==================== 内容格式化 ====================
/** 将消息中的换行符转为 HTML 换行标签 */ /** 将消息内容通过 markdown-it 渲染为 HTML */
function formatContent(content: string): string { function formatContent(content: string): string {
return content.replace(/\n/g, '<br>') return md.render(content)
} }
// ==================== 滚动控制 ==================== // ==================== 滚动控制 ====================
+1
View File
@@ -9,6 +9,7 @@
"allowImportingTsExtensions": true, "allowImportingTsExtensions": true,
"resolveJsonModule": true, "resolveJsonModule": true,
"isolatedModules": true, "isolatedModules": true,
"allowSyntheticDefaultImports": true,
"noEmit": true, "noEmit": true,
"jsx": "preserve", "jsx": "preserve",
"strict": true, "strict": true,