]*>(.*?)<\/code>/gi, '`$1`')
- .replace(/]*href="([^"]*)"[^>]*>(.*?)<\/a>/gi, '[$2]($1)')
- .replace(
- /
]*src="([^"]*)"[^>]*alt="([^"]*)"[^>]*>/gi,
- ''
- )
- .replace(/
]*src="([^"]*)"[^>]*>/gi, '')
- .replace(/]*>(.*?)<\/p>/gi, '$1\n\n')
- .replace(/
]*>/gi, '\n')
- .replace(/<[^>]+>/g, '') // 移除其他HTML标签
- .replace(/\n{3,}/g, '\n\n') // 清理多余换行
- .trim();
-
- content.value = markdownContent;
- message.info('已尝试将富文本内容转换为Markdown格式');
- } else if (fromType === 2 && toType === 1) {
- // Markdown转富文本(基本转换)
- let htmlContent = content.value
- .replace(/^# (.*$)/gim, '
$1
')
- .replace(/^## (.*$)/gim, '$1
')
- .replace(/^### (.*$)/gim, '$1
')
- .replace(/^#### (.*$)/gim, '$1
')
- .replace(/^##### (.*$)/gim, '$1
')
- .replace(/^###### (.*$)/gim, '$1
')
- .replace(/\*\*(.*?)\*\*/g, '$1')
- .replace(/\*(.*?)\*/g, '$1')
- .replace(/`(.*?)`/g, '$1')
- .replace(/\[([^\]]+)\]\(([^)]+)\)/g, '$1')
- .replace(
- /!\[([^\]]*)\]\(([^)]+)\)/g,
- '
'
- )
- .replace(/\n\n/g, '')
- .replace(/\n/g, '
');
-
- // 包装在段落标签中
- if (htmlContent && !htmlContent.startsWith('<')) {
- htmlContent = '
' + htmlContent + '
';
- }
-
- content.value = htmlContent;
- message.info('已尝试将Markdown内容转换为富文本格式');
- }
- } catch (error) {
- console.error('内容格式转换失败:', error);
- message.warning('内容格式转换失败,请手动调整内容格式');
- }
- };
-
- const onEditor = (value: any) => {
- editor.value = value.target.value;
- };
const onComments = () => {
if (form.comments == undefined) {
@@ -737,7 +564,16 @@
});
};
- const editorRef = ref | null>(null);
+ const editorRef = ref(null);
+
+ // markdown-it 实例:编辑回显时把老 Markdown 内容转成 HTML 喂给富文本编辑器
+ const md = markdownit();
+ const toEditorContent = (val: string): string => {
+ if (!val) return '';
+ // 已是 HTML(含标签)则原样返回,否则用 markdown-it 转 HTML
+ if (/<[a-z][\s\S]*>/i.test(val)) return val;
+ return md.render(val);
+ };
// 文件库选择弹窗状态
const showFileSelector = ref(false);
@@ -747,324 +583,6 @@
const showVideoSelector = ref(false);
const videoSelectCallback = ref<((url: string) => void) | null>(null);
- // 📝 Markdown编辑器配置
- const markdownToolbars = [
- 'bold',
- 'underline',
- 'italic',
- '-',
- 'title',
- 'strikeThrough',
- 'sub',
- 'sup',
- 'quote',
- 'unorderedList',
- 'orderedList',
- 'task',
- '-',
- 'codeRow',
- 'code',
- 'link',
- 'image',
- 'table',
- 'mermaid',
- 'katex',
- '-',
- 'revoke',
- 'next',
- 'save',
- '=',
- 'pageFullscreen',
- 'fullscreen',
- 'preview',
- 'previewOnly',
- 'htmlPreview',
- 'catalog'
- ] as any;
-
- // 📝 Markdown编辑器图片上传处理
- const onMarkdownUploadImg = async (
- files: File[],
- callback: (urls: string[]) => void
- ) => {
- try {
- const uploadPromises = files.map(async (file) => {
- // 检查文件大小(限制为10MB)
- if (file.size > 10 * 1024 * 1024) {
- message.error(`图片 ${file.name} 大小超过10MB,请选择更小的文件`);
- return null;
- }
-
- // 检查文件类型
- if (!file.type.startsWith('image/')) {
- message.error(`文件 ${file.name} 不是有效的图片格式`);
- return null;
- }
-
- try {
- const result = await uploadOss(file);
- return result.url || result.path;
- } catch (error) {
- console.error('图片上传失败:', error);
- message.error(`图片 ${file.name} 上传失败`);
- return null;
- }
- });
-
- const results = await Promise.all(uploadPromises);
- const successUrls = results.filter((url) => url !== null) as string[];
-
- if (successUrls.length > 0) {
- callback(successUrls);
- message.success(`成功上传 ${successUrls.length} 张图片`);
- }
- } catch (error) {
- console.error('批量上传失败:', error);
- message.error('图片上传失败,请重试');
- }
- };
-
- // 📝 Markdown编辑器图片选择器
- const openMarkdownImageSelector = () => {
- fileSelectCallback.value = (url: string) => {
- // 在当前光标位置插入Markdown图片语法
- const imageMarkdown = ``;
- insertMarkdownText(imageMarkdown);
- };
- showFileSelector.value = true;
- };
-
- // 📝 Markdown编辑器视频选择器
- const openMarkdownVideoSelector = () => {
- videoSelectCallback.value = (url: string) => {
- // 在当前光标位置插入Markdown视频语法(使用HTML标签)
- const videoMarkdown = ``;
- insertMarkdownText(videoMarkdown);
- };
- showVideoSelector.value = true;
- };
-
- // 📝 在Markdown编辑器中插入文本
- const insertMarkdownText = (text: string) => {
- // 简单的文本插入,在内容末尾添加
- if (content.value) {
- content.value += '\n\n' + text;
- } else {
- content.value = text;
- }
- };
-
- const config = ref({
- height: 420,
- paste_data_images: true,
- automatic_uploads: true,
-
- // 自定义工具栏,移除默认的image和media按钮,添加自定义按钮 (视频、上传视频按钮 custom_video_selector quick_video_upload)(首行缩进 )
- toolbar: [
- 'fullscreen preview code codesample emoticons auto_format custom_image_selector quick_upload',
- 'undo redo | forecolor backcolor',
- 'bold italic underline strikethrough',
- 'alignleft aligncenter alignright alignjustify',
- 'outdent indent | numlist bullist',
- 'formatselect fontselect fontsizeselect',
- 'link charmap anchor pagebreak | ltr rtl'
- ].join(' | '),
-
- // 图片上传处理器 - 支持拖拽和粘贴上传
- images_upload_handler: (blobInfo, success, error) => {
- const file = blobInfo.blob();
-
- // 检查文件大小(限制为10MB)
- if (file.size > 10 * 1024 * 1024) {
- error('图片大小不能超过10MB');
- return;
- }
-
- // 检查文件类型
- if (!file.type.startsWith('image/')) {
- error('只能上传图片文件');
- return;
- }
-
- // 显示上传进度提示
- const loadingMsg = message.loading('图片上传中...', 0);
-
- uploadOss(file)
- .then((res) => {
- loadingMsg();
- success(res.url || res.path);
- message.success('图片上传成功');
- })
- .catch((msg) => {
- loadingMsg();
- error(msg || '图片上传失败');
- message.error('图片上传失败:' + msg);
- });
- },
-
- // 图片工具栏
- image_toolbar:
- 'alignleft aligncenter alignright | rotateleft rotateright | imageoptions',
-
- // 图片标题
- image_title: true,
-
- // 图片描述
- image_description: true,
-
- // 图片尺寸
- image_dimensions: true,
-
- // 图片类别
- image_class_list: [
- { title: '无样式', value: '' },
- { title: '响应式图片', value: 'img-responsive' },
- { title: '圆角图片', value: 'img-rounded' },
- { title: '圆形图片', value: 'img-circle' }
- ],
-
- // 自定义按钮设置
- setup: (editor: any) => {
- // 添加自定义图片选择按钮
- editor.ui.registry.addButton('custom_image_selector', {
- text: '图片',
- icon: 'image',
- tooltip: '插入图片(从文件库选择或上传)',
- onAction: () => {
- // 打开文件库选择弹窗
- fileSelectCallback.value = (url: string) => {
- editor.insertContent(
- `
`
- );
- };
- showFileSelector.value = true;
- }
- });
-
- // 添加快速上传按钮
- editor.ui.registry.addButton('quick_upload', {
- text: '上传',
- icon: 'upload',
- tooltip: '快速上传图片',
- onAction: () => {
- const input = document.createElement('input');
- input.type = 'file';
- input.accept = 'image/*';
- input.onchange = (e: any) => {
- const file = e.target.files[0];
- if (file) {
- // 检查文件大小
- if (file.size > 10 * 1024 * 1024) {
- message.error('图片大小不能超过10MB');
- return;
- }
-
- const loadingMsg = message.loading('图片上传中...', 0);
- uploadOss(file)
- .then((res) => {
- loadingMsg();
- const imageUrl = res.url || res.path;
- editor.insertContent(
- `
`
- );
- message.success('图片上传成功');
- })
- .catch((msg) => {
- loadingMsg();
- message.error('图片上传失败:' + msg);
- });
- }
- };
- input.click();
- }
- });
-
- // 添加自定义视频选择按钮
- editor.ui.registry.addButton('custom_video_selector', {
- text: '视频',
- icon: 'embed',
- tooltip: '插入视频(从视频库选择)',
- onAction: () => {
- // 打开视频库选择弹窗
- videoSelectCallback.value = (url: string) => {
- // 插入视频标签,使用HTML5 video元素
- editor.insertContent(`
-
- `);
- };
- showVideoSelector.value = true;
- }
- });
-
- // 添加快速视频上传按钮
- editor.ui.registry.addButton('quick_video_upload', {
- text: '上传视频',
- icon: 'upload',
- tooltip: '快速上传视频',
- onAction: () => {
- const input = document.createElement('input');
- input.type = 'file';
- input.accept = 'video/*';
- input.onchange = (e: any) => {
- const file = e.target.files[0];
- if (file) {
- // 检查文件大小(限制为100MB)
- if (file.size > 100 * 1024 * 1024) {
- message.error('视频大小不能超过100MB');
- return;
- }
-
- const loadingMsg = message.loading('视频上传中...', 0);
- uploadOss(file)
- .then((res) => {
- loadingMsg();
- const videoUrl = res.path || res.downloadUrl;
- editor.insertContent(`
-
- `);
- message.success('视频上传成功');
- })
- .catch((msg) => {
- loadingMsg();
- message.error('视频上传失败:' + msg);
- });
- }
- };
- input.click();
- }
- });
-
- // 添加一键排版按钮
- editor.ui.registry.addButton('auto_format', {
- text: '排版',
- icon: 'template',
- tooltip: '智能优化文章格式和排版',
- onAction: () => {
- // 直接在这里处理排版,因为此时编辑器肯定已经初始化完成
- handleAutoFormat(editor);
- }
- });
-
- // 添加段落首行缩进切换按钮
- editor.ui.registry.addButton('toggle_indent', {
- icon: 'indent',
- tooltip: '切换段落首行缩进(适合中文排版)',
- onAction: () => {
- toggleParagraphIndent(editor);
- }
- });
- }
- });
-
// 从文件库选择图片的回调
const onFileSelected = (data: FileRecord) => {
if (fileSelectCallback.value) {
@@ -1093,11 +611,31 @@
showVideoSelector.value = false;
};
+ // 从图片库选择图片:插入到 WangEditor 当前光标处
+ const openImageSelector = () => {
+ fileSelectCallback.value = (url: string) => {
+ editorRef.value?.insertHtml(
+ `
`
+ );
+ };
+ showFileSelector.value = true;
+ };
+
+ // 从视频库选择视频:插入到 WangEditor 当前光标处
+ const openVideoSelector = () => {
+ videoSelectCallback.value = (url: string) => {
+ editorRef.value?.insertHtml(
+ ``
+ );
+ };
+ showVideoSelector.value = true;
+ };
+
// 🎨 智能一键排版 - 人性化设计
- const handleAutoFormat = (editor: any) => {
+ const handleAutoFormat = () => {
try {
// 1. 检查内容
- const content = editor.getContent();
+ const content = content.value;
if (
!content ||
content.trim() === '' ||
@@ -1121,7 +659,7 @@
setTimeout(() => {
try {
const optimizedContent = smartFormatContent(content);
- editor.setContent(optimizedContent);
+ content.value = optimizedContent;
loadingMsg();
@@ -1316,9 +854,9 @@
};
// 🔄 段落首行缩进切换功能
- const toggleParagraphIndent = (editor: any) => {
+ const toggleParagraphIndent = () => {
try {
- const content = editor.getContent();
+ const content = content.value;
if (
!content ||
@@ -1351,7 +889,7 @@
actionText = '已添加段落首行缩进';
}
- editor.setContent(newContent);
+ content.value = newContent;
message.success({
content: `📐 ${actionText}`,
@@ -1449,7 +987,7 @@
}
const formData = {
...form,
- editor: editor.value || 1,
+ editor: 1,
status: setting.setting?.articleReview ? 1 : 0,
content: content.value
};
@@ -1480,12 +1018,6 @@
watch(
() => props.visible,
(visible) => {
- if (localStorage.getItem('Editor')) {
- editor.value = Number(localStorage.getItem('Editor'));
- } else {
- editor.value = 1;
- }
-
if (visible) {
images.value = [];
category.value = [];
@@ -1499,7 +1031,7 @@
// 文章详情
assignObject(form, data);
if (data.content) {
- content.value = data.content;
+ content.value = toEditorContent(data.content);
}
if (!data.source) {
form.source = undefined;
@@ -1509,9 +1041,6 @@
} else {
form.tags = undefined;
}
- if (data.editor) {
- editor.value = data.editor;
- }
if (data.files) {
const arr = JSON.parse(data.files);
arr.map((url: string) => {
@@ -1561,26 +1090,6 @@
border-radius: 6px;
}
- // 编辑器内图片样式
- :deep(.mce-content-body) {
- img {
- max-width: 100%;
- height: auto;
-
- &.img-responsive {
- width: 100%;
- height: auto;
- }
-
- &.img-rounded {
- border-radius: 8px;
- }
-
- &.img-circle {
- border-radius: 50%;
- }
- }
- }
}
// 文件选择提示