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

765 lines
26 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>`;
};
// 重写表格渲染方法,添加表格容器使其可滚动
renderer.table = function(header, body) {
return `<div class="table-container"><table class="custom-table">
<thead>${header}</thead>
<tbody>${body}</tbody>
</table></div>`;
};
// 重写表格单元格渲染方法
renderer.tablecell = function(content, flags) {
const type = flags.header ? 'th' : 'td';
const align = flags.align ? ` style="text-align:${flags.align}"` : '';
return `<${type}${align}>${content}</${type}>`;
};
// 配置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; /* 确保颜色正确继承 */
}
/* 专门修复mp-html组件生成的列表结构 */
div[data-v-7e5387f] {
display: inline !important;
}
/* 处理列表编号所在的容器 - 重要修复 */
div[data-v-7e5387f] {
display: inline !important;
white-space: nowrap !important; /* 防止编号和首个字符分开 */
}
/* 首行缩进优化(确保列表项不会被意外截断) */
div div {
display: inline !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;
}
/* 针对mp-html生成的列表标记容器 */
div[style*="undefined"] {
display: inline !important;
white-space: nowrap !important;
}
/* 对列表项第一个元素特殊处理 */
h4 {
display: inline !important;
margin: 0 !important;
padding: 0 !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;
// 处理表格,确保表格正确显示
processedHtml = processedHtml.replace(/<table([^>]*)>/g, (match, attrs) => {
return `<div class="table-container"><table${attrs || ''} class="custom-table">`;
});
processedHtml = processedHtml.replace(/<\/table>/g, '</table></div>');
// 将所有段落包装在特殊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; /* 左对齐 */
}
/* 表格样式 */
table {
border-collapse: collapse !important;
width: 100% !important;
margin: 16rpx 0 !important;
table-layout: fixed !important;
border: 1px solid #e0e0e0 !important;
overflow-x: auto !important;
}
th, td {
border: 1px solid #e0e0e0 !important;
padding: 12rpx !important;
text-align: left !important;
word-break: break-word !important;
white-space: normal !important;
font-size: 28rpx !important;
}
th {
background-color: #f5f5f5 !important;
font-weight: bold !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;
}
/* 专门修复mp-html组件生成的列表结构 */
.markdown-container >>> div[data-v-7e5387f] {
display: inline !important;
}
/* 处理列表编号所在的容器 - 重要修复 */
.markdown-container >>> div[data-v-7e5387f] {
display: inline !important;
white-space: nowrap !important; /* 防止编号和首个字符分开 */
}
/* 首行缩进优化(确保列表项不会被意外截断) */
.markdown-container >>> div div {
display: inline !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;
}
/* 针对mp-html生成的列表标记容器 */
.markdown-container >>> div[style*="undefined"] {
display: inline !important;
white-space: nowrap !important;
}
/* 对列表项第一个元素特殊处理 */
.markdown-container >>> h4 {
display: inline !important;
margin: 0 !important;
padding: 0 !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;
}
/* 表格样式 */
.markdown-container >>> table {
border-collapse: collapse !important;
width: 100% !important;
margin: 0 !important;
table-layout: fixed !important; /* 使用固定表格布局 */
border: 1px solid #e0e0e0 !important;
font-size: 24rpx !important; /* 稍微减小字体大小,适应移动端 */
overflow-x: visible !important; /* 移除溢出隐藏 */
}
.markdown-container >>> table th,
.markdown-container >>> table td {
border: 1px solid #e0e0e0 !important;
padding: 8rpx 10rpx !important; /* 减小内边距 */
text-align: left !important;
word-break: break-word !important; /* 确保长文本会自动换行 */
white-space: normal !important;
vertical-align: middle !important;
min-width: 80rpx !important; /* 设置最小宽度 */
max-width: none !important; /* 移除最大宽度限制 */
width: auto !important; /* 使列宽自动调整 */
overflow-wrap: break-word !important; /* 确保长单词会换行 */
}
.markdown-container >>> table th {
background-color: #f5f5f5 !important;
font-weight: bold !important;
color: #333333 !important;
}
.markdown-container >>> table tr:nth-child(even) {
background-color: #fafafa !important;
}
.markdown-container >>> table tr:hover {
background-color: #f0f0f0 !important;
}
/* 表格容器 - 移除水平滚动 */
.markdown-container >>> .table-container {
width: 100% !important;
overflow-x: visible !important; /* 移除水平滚动 */
margin: 16rpx 0 30rpx !important; /* 增加底部边距,与下方内容分开 */
display: block !important;
}
/* 表格整体显示 */
.markdown-container >>> .custom-table {
width: 100% !important;
table-layout: fixed !important; /* 固定布局 */
overflow: visible !important; /* 显示所有内容 */
}
/* 表格标题 */
.markdown-container >>> table caption {
font-weight: bold !important;
padding: 10rpx !important;
caption-side: top !important;
text-align: center !important;
color: #333333 !important;
}
/* 分隔线样式优化 */
.markdown-container >>> hr {
display: block !important;
height: 2rpx !important;
border: 0 !important;
border-top: 2rpx solid #e0e0e0 !important;
margin: 24rpx 0 !important; /* 增加上下边距 */
padding: 0 !important;
clear: both !important;
background-color: #e0e0e0 !important;
}
</style>