YingXingAI/components/markdown-viewer/markdown-viewer.vue

589 lines
20 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<view class="markdown-container">
<mp-html :content="renderedContent" :domain="domain" :lazy-load="true" :scroll-table="true" :selectable="true" />
</view>
</template>
<script>
import { marked } from 'marked';
import hljs from 'highlight.js';
import mpHtml from '@/node_modules/mp-html/dist/uni-app/components/mp-html/mp-html.vue';
// 自定义渲染器,修改列表的渲染方式
const renderer = new marked.Renderer();
// 重写粗体渲染方法,确保粗体文本不会导致不当换行
renderer.strong = function(text) {
// 确保文本周围没有不必要的空格
const trimmedText = text.trim();
return `<strong class="custom-strong" style="font-weight:900 !important;display:inline !important;white-space:normal !important;word-break:normal !important;">${trimmedText}</strong>`;
};
// 重写列表项渲染方法 - 使用特殊的包装来确保内容在一行
renderer.listitem = function(text) {
return `<li class="custom-list-item"><div class="list-item-wrapper" style="text-align:left !important;letter-spacing:normal !important;word-spacing:normal !important;text-justify:none !important;">${text}</div></li>`;
};
// 重写有序列表渲染方法
renderer.list = function(body, ordered) {
const type = ordered ? 'ol' : 'ul';
const className = ordered ? 'custom-ordered-list' : 'custom-unordered-list';
return `<${type} class="${className}">${body}</${type}>`;
};
// 重写段落渲染方法,确保换行正确
renderer.paragraph = function(text) {
return `<p class="custom-paragraph">${text}</p>`;
};
// 重写链接渲染方法,确保链接文本正常显示
renderer.link = function(href, title, text) {
return `<a href="${href}" title="${title || ''}" style="display:inline !important;white-space:normal !important;word-break:break-all !important;letter-spacing:normal !important;word-spacing:normal !important;text-align:left !important;">${text}</a>`;
};
// 配置marked
marked.use({
renderer: renderer,
highlight: function(code, lang) {
try {
return hljs.highlightAuto(code, [lang]).value;
} catch (e) {
return hljs.highlightAuto(code).value;
}
},
gfm: true,
breaks: true,
pedantic: false,
sanitize: false,
smartLists: true,
smartypants: false
});
export default {
name: 'MarkdownViewer',
components: {
mpHtml
},
props: {
// Markdown原始内容
content: {
type: String,
default: ''
},
// 主域名(用于链接拼接)
domain: {
type: String,
default: ''
},
// 自定义样式
customStyle: {
type: String,
default: ''
}
},
computed: {
// 将Markdown转换为HTML
renderedContent() {
if (!this.content) return '';
try {
// 添加自定义样式,确保内容自动换行和列表正确显示
const styleTag = `<style>
* {
max-width: 100% !important;
word-break: normal !important;
overflow-wrap: break-word !important;
box-sizing: border-box !important;
}
/* 段落样式修复 */
.custom-paragraph, p {
display: block !important;
white-space: normal !important;
margin: 10rpx 0 !important;
width: 100% !important;
line-height: 1.6 !important;
text-align: left !important; /* 改为左对齐,不要两端对齐 */
}
/* 解决粗体文本和普通文本换行问题 */
.custom-strong, strong, b {
display: inline !important;
font-weight: 900 !important;
white-space: normal !important;
word-break: normal !important;
overflow-wrap: break-word !important;
vertical-align: baseline !important;
position: static !important;
float: none !important;
margin: 0 !important;
padding: 0 !important;
}
/* 专门用于列表项内容包装的容器 */
.list-item-wrapper {
display: inline-block !important;
width: 100% !important;
white-space: normal !important;
/* 关键属性:防止内部元素分列 */
table-layout: fixed !important;
word-break: normal !important;
text-align: left !important;
text-justify: none !important;
letter-spacing: normal !important;
word-spacing: normal !important;
text-align-last: left !important;
}
/* 内联元素修复 */
span, a, em, strong, .custom-strong, b {
display: inline !important;
white-space: normal !important;
vertical-align: baseline !important;
float: none !important;
position: static !important;
width: auto !important;
height: auto !important;
/* 确保内部不会有奇怪的换行 */
word-break: keep-all !important;
}
/* 特别针对粗体文本 */
.custom-strong, strong, b {
font-weight: bold !important;
display: inline !important;
vertical-align: baseline !important;
position: static !important;
margin: 0 !important;
padding: 0 !important;
border: none !important;
float: none !important;
width: auto !important;
word-break: keep-all !important;
word-wrap: normal !important;
line-height: inherit !important;
color: inherit !important; /* 确保颜色正确继承 */
}
/* 有序列表样式 */
.custom-ordered-list, ol {
display: block !important;
counter-reset: item !important;
padding-left: 0 !important;
margin: 10rpx 0 !important;
width: 100% !important;
list-style-type: none !important;
}
/* 无序列表样式 */
.custom-unordered-list, ul {
display: block !important;
padding-left: 0 !important;
margin: 10rpx 0 !important;
width: 100% !important;
list-style-type: none !important;
}
/* 列表项共用样式 */
.custom-list-item, ol li, ul li {
display: flex !important;
padding-left: 0 !important;
margin-bottom: 8rpx !important;
width: 100% !important;
position: relative !important;
/* 确保内容在一行显示 */
flex-wrap: nowrap !important;
align-items: flex-start !important; /* 顶部对齐 */
}
/* 列表项内容样式 */
.custom-list-item > *:not(ol):not(ul),
ol li > *:not(ol):not(ul),
ul li > *:not(ol):not(ul) {
flex: 1 !important;
display: inline !important;
white-space: normal !important;
word-break: normal !important;
width: auto !important;
/* 确保文本不会被分列 */
column-count: 1 !important;
column-width: auto !important;
column-span: all !important;
text-align: left !important; /* 确保文本左对齐 */
justify-content: flex-start !important; /* 左对齐 */
text-justify: none !important; /* 禁用两端对齐 */
letter-spacing: normal !important; /* 正常字间距 */
word-spacing: normal !important; /* 正常词间距 */
}
/* 列表项内的p标签特殊处理 */
ol li p, ul li p, .custom-list-item p {
display: inline !important;
margin: 0 !important;
padding: 0 !important;
width: auto !important;
white-space: normal !important;
}
/* 有序列表项编号 */
.custom-ordered-list > .custom-list-item {
counter-increment: item !important;
}
.custom-ordered-list > .custom-list-item::before,
ol > li::before {
content: counter(item)"." !important;
min-width: 40rpx !important;
width: 40rpx !important;
font-weight: bold !important;
text-align: right !important;
margin-right: 8rpx !important;
padding-right: 0 !important;
flex-shrink: 0 !important;
align-self: flex-start !important; /* 顶部对齐 */
padding-top: 1rpx !important; /* 微调垂直位置 */
}
/* 无序列表项标记 */
.custom-unordered-list > .custom-list-item::before,
ul > li::before {
content: "-" !important;
min-width: 40rpx !important;
width: 40rpx !important;
font-weight: bold !important;
text-align: center !important;
margin-right: 8rpx !important;
padding-right: 0 !important;
flex-shrink: 0 !important;
align-self: flex-start !important; /* 顶部对齐 */
padding-top: 1rpx !important; /* 微调垂直位置 */
}
/* 代码块样式 */
pre, code {
white-space: pre-wrap !important;
word-wrap: break-word !important;
word-break: break-all !important;
overflow-wrap: break-word !important;
}
/* 禁用可能导致问题的CSS特性 */
* {
column-count: 1 !important;
column-gap: 0 !important;
column-rule: none !important;
column-span: all !important;
column-width: auto !important;
columns: auto !important;
}
${this.customStyle || ''}
</style>`;
// 对Markdown内容进行预处理修复可能导致问题的模式
let processedContent = this.content;
// 将Markdown内容分解为段落分别处理每个段落
const paragraphs = processedContent.split('\n\n');
const processedParagraphs = paragraphs.map(paragraph => {
// 处理一个段落内的粗体文本
return paragraph.replace(/\*\*([^*]+?)\*\*/g, (match, content) => {
// 返回无缝拼接的HTML粗体标签
return `<strong style="font-weight:900 !important;display:inline !important;">${content}</strong>`;
});
});
// 重新组合处理后的段落
processedContent = processedParagraphs.join('\n\n');
// 使用marked处理剩余的Markdown语法
let htmlContent = marked(processedContent);
// 后处理HTML确保任何遗漏的粗体标记也能被处理
htmlContent = htmlContent
// 处理任何遗漏的**标记
.replace(/\*\*([^*]+?)\*\*/g, (match, content) => {
return `<strong style="font-weight:900 !important;display:inline !important;">${content}</strong>`;
});
// 处理HTML确保所有文本元素都有正确的显示属性
let processedHtml = htmlContent;
// 将所有段落包装在特殊div中防止段落间的换行问题
processedHtml = processedHtml.replace(/<p([^>]*)>(.*?)<\/p>/gs, (match, attrs, content) => {
// 确保段落内容连续显示,不被换行打断
return `<p${attrs} style="white-space:normal !important;word-break:normal !important;overflow-wrap:break-word !important;">${content}</p>`;
});
// 确保强制使用行内样式
processedHtml = processedHtml.replace(
/<strong/g,
'<strong style="font-weight:900 !important;display:inline !important;white-space:normal !important;word-break:normal !important;overflow-wrap:break-word !important;color:inherit !important;"'
);
// 处理可能的HTML转义问题
processedHtml = processedHtml
.replace(/&lt;strong/g, '<strong')
.replace(/&lt;\/strong&gt;/g, '</strong>');
// 添加强制粗体样式覆盖
processedHtml = `
<style>
strong, .custom-strong, b {
font-weight: 900 !important;
display: inline !important;
color: inherit !important;
white-space: normal !important;
word-break: normal !important;
overflow-wrap: break-word !important;
}
p, div, span {
white-space: normal !important;
word-break: normal !important;
overflow-wrap: break-word !important;
text-align: left !important; /* 左对齐 */
text-justify: none !important; /* 禁用两端对齐 */
}
/* 关键样式:确保文本流不被打断 */
p strong, p b, strong, b {
display: inline !important;
white-space: normal !important;
}
/* 针对段落内部元素的处理 */
p > * {
display: inline !important;
}
/* 链接样式 */
a {
display: inline !important;
white-space: normal !important;
word-break: break-all !important;
letter-spacing: normal !important; /* 正常字间距 */
word-spacing: normal !important; /* 正常词间距 */
text-align: left !important; /* 左对齐 */
}
/* 禁用所有可能导致文本被拉伸的样式 */
* {
text-align-last: left !important;
text-justify: none !important;
text-align: left !important;
letter-spacing: normal !important;
word-spacing: normal !important;
}
</style>
` + processedHtml;
// 返回带样式的HTML
return styleTag + processedHtml;
} catch (error) {
console.error('Markdown渲染错误:', error);
return `<p style="color: red;">Markdown渲染错误: ${error.message}</p>`;
}
}
}
}
</script>
<style>
@import './highlight.css';
.markdown-container {
padding: 20rpx;
overflow-wrap: break-word;
word-wrap: break-word;
word-break: normal;
}
/* 全局样式 */
.markdown-container >>> * {
max-width: 100% !important;
white-space: normal !important;
word-break: normal !important;
overflow-wrap: break-word !important;
box-sizing: border-box !important;
}
/* 段落样式 */
.markdown-container >>> .custom-paragraph,
.markdown-container >>> p {
display: block !important;
/* margin: 10rpx 0 !important; */
width: 100% !important;
line-height: 1.6 !important;
text-align: left !important; /* 改为左对齐,不要两端对齐 */
}
/* 专门用于列表项内容包装的容器 */
.markdown-container >>> .list-item-wrapper {
display: inline-block !important;
width: 100% !important;
white-space: normal !important;
/* 关键属性:防止内部元素分列 */
table-layout: fixed !important;
word-break: normal !important;
text-align: left !important;
text-justify: none !important;
letter-spacing: normal !important;
word-spacing: normal !important;
text-align-last: left !important;
}
/* 内联元素修复 */
.markdown-container >>> span,
.markdown-container >>> a,
.markdown-container >>> em,
.markdown-container >>> strong,
.markdown-container >>> .custom-strong,
.markdown-container >>> b {
display: inline !important;
white-space: normal !important;
vertical-align: baseline !important;
float: none !important;
position: static !important;
width: auto !important;
height: auto !important;
/* 确保内部不会有奇怪的换行 */
/* 下面不注释的话会有奇怪的换行 */
/* word-break: keep-all !important; */
line-height: inherit !important;
}
/* 特别针对粗体文本 */
.markdown-container >>> .custom-strong,
.markdown-container >>> strong,
.markdown-container >>> b {
font-weight: 900 !important;
display: inline !important;
vertical-align: baseline !important;
position: static !important;
margin: 0 !important;
padding: 0 !important;
border: none !important;
float: none !important;
width: auto !important;
white-space: normal !important;
word-break: normal !important;
overflow-wrap: break-word !important;
word-wrap: normal !important;
line-height: inherit !important;
color: inherit !important; /* 确保颜色正确继承 */
font-size: inherit !important; /* 确保字体大小正确继承 */
font-family: inherit !important; /* 确保字体系列正确继承 */
text-decoration: none !important;
}
/* 有序列表样式 */
.markdown-container >>> .custom-ordered-list,
.markdown-container >>> ol {
display: block !important;
counter-reset: item !important;
padding-left: 0 !important;
margin: 10rpx 0 !important;
width: 100% !important;
list-style-type: none !important;
}
/* 无序列表样式 */
.markdown-container >>> .custom-unordered-list,
.markdown-container >>> ul {
display: block !important;
padding-left: 0 !important;
margin: 10rpx 0 !important;
width: 100% !important;
list-style-type: none !important;
}
/* 列表项共用样式 */
.markdown-container >>> .custom-list-item,
.markdown-container >>> ol li,
.markdown-container >>> ul li {
display: flex !important;
padding-left: 0 !important;
margin-bottom: 8rpx !important;
width: 100% !important;
position: relative !important;
/* 确保内容在一行显示 */
flex-wrap: nowrap !important;
align-items: flex-start !important; /* 顶部对齐 */
}
/* 列表项内容样式 */
.markdown-container >>> .custom-list-item > *:not(ol):not(ul),
.markdown-container >>> ol li > *:not(ol):not(ul),
.markdown-container >>> ul li > *:not(ol):not(ul) {
flex: 1 !important;
display: inline !important;
white-space: normal !important;
word-break: normal !important;
width: auto !important;
/* 确保文本不会被分列 */
column-count: 1 !important;
column-width: auto !important;
column-span: all !important;
text-align: left !important; /* 确保文本左对齐 */
justify-content: flex-start !important; /* 左对齐 */
text-justify: none !important; /* 禁用两端对齐 */
letter-spacing: normal !important; /* 正常字间距 */
word-spacing: normal !important; /* 正常词间距 */
}
/* 列表项内的p标签特殊处理 */
.markdown-container >>> ol li p,
.markdown-container >>> ul li p,
.markdown-container >>> .custom-list-item p {
display: inline !important;
margin: 0 !important;
padding: 0 !important;
width: auto !important;
white-space: normal !important;
}
/* 有序列表项编号 */
.markdown-container >>> .custom-ordered-list > .custom-list-item::before,
.markdown-container >>> ol > li::before {
counter-increment: item !important;
content: counter(item)"." !important;
min-width: 40rpx !important;
width: 40rpx !important;
font-weight: bold !important;
text-align: right !important;
margin-right: 8rpx !important;
padding-right: 0 !important;
flex-shrink: 0 !important;
align-self: flex-start !important; /* 顶部对齐 */
/* padding-top: 1rpx !important; 微调垂直位置 */
}
/* 无序列表项标记 */
.markdown-container >>> .custom-unordered-list > .custom-list-item::before,
.markdown-container >>> ul > li::before {
content: "-" !important;
min-width: 40rpx !important;
width: 40rpx !important;
font-weight: bold !important;
text-align: center !important;
margin-right: 8rpx !important;
padding-right: 0 !important;
flex-shrink: 0 !important;
align-self: flex-start !important; /* 顶部对齐 */
/* padding-top: 1rpx !important; 微调垂直位置 */
}
/* 禁用可能导致问题的CSS特性 */
.markdown-container >>> * {
column-count: 1 !important;
column-gap: 0 !important;
column-rule: none !important;
column-span: all !important;
column-width: auto !important;
columns: auto !important;
}
/* 代码块样式 */
.markdown-container >>> pre,
.markdown-container >>> code {
white-space: pre-wrap !important;
word-break: break-all !important;
overflow-wrap: break-word !important;
max-width: 100% !important;
}
</style>