refactor(chat): 优化聊天消息编辑组件代码结构
- 移除不必要的代码注释和冗余逻辑 - 简化表单验证函数参数传递 - 删除重复的类型定义和无用变量 - 统一事件处理函数命名风格 - 清理未使用的插件引用和样式文件 - 优化条件判断语句提高可读性
This commit is contained in:
@@ -12,7 +12,9 @@
|
||||
@ok="save"
|
||||
>
|
||||
<template #extra>
|
||||
<a-button type="primary" style="margin-right: 8px" @click="save">保存</a-button>
|
||||
<a-button type="primary" style="margin-right: 8px" @click="save"
|
||||
>保存</a-button
|
||||
>
|
||||
</template>
|
||||
<a-form
|
||||
ref="formRef"
|
||||
@@ -60,6 +62,13 @@
|
||||
/>
|
||||
</div>
|
||||
</a-form-item>
|
||||
<a-form-item label="关键词" name="tags">
|
||||
<a-select
|
||||
v-model:value="form.tags"
|
||||
mode="tags"
|
||||
placeholder="按回车分隔"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="内容详情" name="content">
|
||||
<!-- 富文本编辑器 -->
|
||||
<div v-if="editor == 1">
|
||||
@@ -72,7 +81,8 @@
|
||||
placeholder="支持直接粘贴或拖拽图片,也可点击工具栏图片按钮从文件库选择"
|
||||
/>
|
||||
<div class="file-selector-tip">
|
||||
💡 提示:工具栏"图片"按钮从图片库选择,"上传"按钮快速上传图片;"视频"按钮从视频库选择,"上传视频"按钮快速上传视频;"一键排版"按钮自动优化文章格式;"首行缩进"按钮切换段落缩进
|
||||
💡
|
||||
提示:工具栏"图片"按钮从图片库选择,"上传"按钮快速上传图片;"视频"按钮从视频库选择,"上传视频"按钮快速上传视频;"一键排版"按钮自动优化文章格式;"首行缩进"按钮切换段落缩进
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -84,7 +94,7 @@
|
||||
type="primary"
|
||||
size="small"
|
||||
@click="openMarkdownImageSelector"
|
||||
style="margin-right: 8px;"
|
||||
style="margin-right: 8px"
|
||||
>
|
||||
📷 从图片库选择
|
||||
</a-button>
|
||||
@@ -92,7 +102,7 @@
|
||||
type="default"
|
||||
size="small"
|
||||
@click="openMarkdownVideoSelector"
|
||||
style="margin-right: 8px;"
|
||||
style="margin-right: 8px"
|
||||
>
|
||||
🎬 从视频库选择
|
||||
</a-button>
|
||||
@@ -107,7 +117,8 @@
|
||||
:onUploadImg="onMarkdownUploadImg"
|
||||
/>
|
||||
<div class="file-selector-tip">
|
||||
💡 提示:支持Markdown语法,可以使用工具栏按钮从文件库选择图片/视频,也可以直接拖拽上传文件
|
||||
💡
|
||||
提示:支持Markdown语法,可以使用工具栏按钮从文件库选择图片/视频,也可以直接拖拽上传文件
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -143,34 +154,61 @@
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
<a-divider class="py-4 mb-3" style="height: 20px" />
|
||||
<a-form-item label="关键词" name="tags">
|
||||
<a-select
|
||||
v-model:value="form.tags"
|
||||
mode="tags"
|
||||
placeholder="按回车分隔"
|
||||
></a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label="描述" name="overview">
|
||||
<a-textarea
|
||||
:rows="3"
|
||||
show-count
|
||||
placeholder="请输入描述"
|
||||
v-model:value="form.overview"
|
||||
<a-form-item label="上传视频" name="video">
|
||||
<div class="video-upload-container">
|
||||
<!-- 视频预览 -->
|
||||
<div v-if="form.video" class="video-preview">
|
||||
<video
|
||||
:src="form.video"
|
||||
controls
|
||||
style="max-width: 100%; max-height: 300px; border-radius: 8px"
|
||||
></video>
|
||||
<a-button
|
||||
type="link"
|
||||
danger
|
||||
@click="removeVideo"
|
||||
style="margin-top: 8px"
|
||||
>
|
||||
删除视频
|
||||
</a-button>
|
||||
</div>
|
||||
|
||||
<!-- 上传按钮 -->
|
||||
<div v-else>
|
||||
<a-upload
|
||||
:before-upload="beforeVideoUpload"
|
||||
:custom-request="handleVideoUpload"
|
||||
:show-upload-list="false"
|
||||
accept="video/*"
|
||||
>
|
||||
<a-button type="primary">
|
||||
<upload-outlined /> 选择视频文件
|
||||
</a-button>
|
||||
</a-upload>
|
||||
<div class="upload-tip"> 支持上传视频文件,大小不超过200MB </div>
|
||||
</div>
|
||||
|
||||
<!-- 上传进度 -->
|
||||
<div v-if="videoUploading" class="upload-progress">
|
||||
<a-progress
|
||||
:percent="uploadProgress"
|
||||
:status="uploadProgress === 100 ? 'success' : 'active'"
|
||||
/>
|
||||
<div class="progress-text">
|
||||
{{ uploadProgressText }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a-form-item>
|
||||
<a-form-item label="自定义链接" name="pdfUrl">
|
||||
<a-form-item label="PDF链接" name="pdfUrl" extra="用于PDF文件预览">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="https://"
|
||||
placeholder="https://oss.xxx.cn/xxx.pdf"
|
||||
v-model:value="form.pdfUrl"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="编号" name="code">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="code"
|
||||
v-model:value="form.code"
|
||||
/>
|
||||
<a-form-item label="文章编号" name="code" extra="用于getByCode查询">
|
||||
<a-input allow-clear placeholder="code" v-model:value="form.code" />
|
||||
</a-form-item>
|
||||
<a-form-item label="文章来源" name="source">
|
||||
<source-select
|
||||
@@ -179,6 +217,14 @@
|
||||
:placeholder="`文章来源`"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="产品概述" name="overview">
|
||||
<a-textarea
|
||||
:rows="3"
|
||||
show-count
|
||||
placeholder="请输入描述"
|
||||
v-model:value="form.overview"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
label="虚拟阅读量"
|
||||
name="virtualViews"
|
||||
@@ -255,13 +301,12 @@
|
||||
class="file-selector-modal"
|
||||
@done="onVideoSelected"
|
||||
/>
|
||||
|
||||
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive, watch } from 'vue';
|
||||
import { Form, message, Modal } from 'ant-design-vue';
|
||||
import { UploadOutlined } from '@ant-design/icons-vue';
|
||||
import { assignObject, htmlToText, uuid } from 'ele-admin-pro';
|
||||
import { addCmsArticle, updateCmsArticle } from '@/api/cms/cmsArticle';
|
||||
import { CmsArticle } from '@/api/cms/cmsArticle/model';
|
||||
@@ -281,6 +326,8 @@ import {CmsNavigation} from '@/api/cms/cmsNavigation/model';
|
||||
import SourceSelect from '@/views/cms/cmsArticle/dictionary/source-select.vue';
|
||||
import { useWebsiteSettingStore } from '@/store/modules/setting';
|
||||
import SelectData from '@/components/SelectFile/components/select-data.vue';
|
||||
import request from '@/utils/request';
|
||||
import { SERVER_API_URL } from '@/config/setting';
|
||||
|
||||
// 是否是修改
|
||||
const isUpdate = ref(false);
|
||||
@@ -328,6 +375,11 @@ const category = ref<string[]>([]);
|
||||
const password = ref();
|
||||
const lang = localStorage.getItem('i18n-lang');
|
||||
|
||||
// 视频上传相关状态
|
||||
const videoUploading = ref(false);
|
||||
const uploadProgress = ref(0);
|
||||
const uploadProgressText = ref('');
|
||||
|
||||
// 用户信息
|
||||
const form = reactive<CmsArticle>({
|
||||
articleId: undefined,
|
||||
@@ -442,7 +494,11 @@ const onEditorTypeChange = (e: any) => {
|
||||
}
|
||||
|
||||
// 如果当前有内容,提示用户确认切换
|
||||
if (content.value && content.value.trim() !== '' && content.value !== '<p><br></p>') {
|
||||
if (
|
||||
content.value &&
|
||||
content.value.trim() !== '' &&
|
||||
content.value !== '<p><br></p>'
|
||||
) {
|
||||
Modal.confirm({
|
||||
title: '🔄 切换编辑器类型',
|
||||
content: '切换编辑器类型可能会影响内容格式,是否继续?',
|
||||
@@ -509,7 +565,10 @@ const convertContentFormat = (fromType: number, toType: number) => {
|
||||
.replace(/<i[^>]*>(.*?)<\/i>/gi, '*$1*')
|
||||
.replace(/<code[^>]*>(.*?)<\/code>/gi, '`$1`')
|
||||
.replace(/<a[^>]*href="([^"]*)"[^>]*>(.*?)<\/a>/gi, '[$2]($1)')
|
||||
.replace(/<img[^>]*src="([^"]*)"[^>]*alt="([^"]*)"[^>]*>/gi, '')
|
||||
.replace(
|
||||
/<img[^>]*src="([^"]*)"[^>]*alt="([^"]*)"[^>]*>/gi,
|
||||
''
|
||||
)
|
||||
.replace(/<img[^>]*src="([^"]*)"[^>]*>/gi, '')
|
||||
.replace(/<p[^>]*>(.*?)<\/p>/gi, '$1\n\n')
|
||||
.replace(/<br[^>]*>/gi, '\n')
|
||||
@@ -532,7 +591,10 @@ const convertContentFormat = (fromType: number, toType: number) => {
|
||||
.replace(/\*(.*?)\*/g, '<em>$1</em>')
|
||||
.replace(/`(.*?)`/g, '<code>$1</code>')
|
||||
.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2">$1</a>')
|
||||
.replace(/!\[([^\]]*)\]\(([^)]+)\)/g, '<img src="$2" alt="$1" style="max-width: 100%;" />')
|
||||
.replace(
|
||||
/!\[([^\]]*)\]\(([^)]+)\)/g,
|
||||
'<img src="$2" alt="$1" style="max-width: 100%;" />'
|
||||
)
|
||||
.replace(/\n\n/g, '</p><p>')
|
||||
.replace(/\n/g, '<br>');
|
||||
|
||||
@@ -574,6 +636,104 @@ const onDeleteFile = (index: number) => {
|
||||
files.value.splice(index, 1);
|
||||
};
|
||||
|
||||
// 视频上传前的验证
|
||||
const beforeVideoUpload = (file: File) => {
|
||||
// 检查文件类型
|
||||
const isVideo = file.type.startsWith('video/');
|
||||
if (!isVideo) {
|
||||
message.error('只能上传视频文件!');
|
||||
return false;
|
||||
}
|
||||
|
||||
// 检查文件大小(200MB = 200 * 1024 * 1024 bytes)
|
||||
const maxSize = 200 * 1024 * 1024;
|
||||
if (file.size > maxSize) {
|
||||
message.error('视频文件大小不能超过 200MB!');
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
// 处理视频上传
|
||||
const handleVideoUpload = async (options: any) => {
|
||||
const { file } = options;
|
||||
|
||||
videoUploading.value = true;
|
||||
uploadProgress.value = 0;
|
||||
uploadProgressText.value = '准备上传...';
|
||||
|
||||
try {
|
||||
const formData = new FormData();
|
||||
formData.append('file', file);
|
||||
|
||||
// 使用 axios 上传以支持进度跟踪
|
||||
const res = await request.post<any>(
|
||||
SERVER_API_URL + '/oss/upload',
|
||||
formData,
|
||||
{
|
||||
onUploadProgress: (progressEvent: any) => {
|
||||
if (progressEvent.total) {
|
||||
const percentCompleted = Math.round(
|
||||
(progressEvent.loaded * 100) / progressEvent.total
|
||||
);
|
||||
uploadProgress.value = percentCompleted;
|
||||
|
||||
// 计算已上传和总大小
|
||||
const loadedMB = (progressEvent.loaded / (1024 * 1024)).toFixed(
|
||||
2
|
||||
);
|
||||
const totalMB = (progressEvent.total / (1024 * 1024)).toFixed(2);
|
||||
uploadProgressText.value = `正在上传: ${loadedMB}MB / ${totalMB}MB`;
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
if (res.data.code === 0 && res.data.data) {
|
||||
// 获取上传后的URL,视频文件不添加图片处理参数
|
||||
let videoUrl =
|
||||
res.data.data.url || res.data.data.path || res.data.data.downloadUrl;
|
||||
|
||||
// 如果URL中包含图片处理参数,移除它(因为这是视频文件)
|
||||
if (videoUrl && videoUrl.includes('?x-oss-process=')) {
|
||||
videoUrl = videoUrl.split('?x-oss-process=')[0];
|
||||
}
|
||||
|
||||
form.video = videoUrl;
|
||||
message.success('视频上传成功!');
|
||||
uploadProgressText.value = '上传完成';
|
||||
} else {
|
||||
throw new Error(res.data.message || '上传失败');
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error('视频上传失败:', error);
|
||||
message.error('视频上传失败:' + (error.message || '未知错误'));
|
||||
uploadProgress.value = 0;
|
||||
} finally {
|
||||
// 延迟隐藏进度条,让用户看到完成状态
|
||||
setTimeout(() => {
|
||||
videoUploading.value = false;
|
||||
uploadProgress.value = 0;
|
||||
uploadProgressText.value = '';
|
||||
}, 1500);
|
||||
}
|
||||
};
|
||||
|
||||
// 删除视频
|
||||
const removeVideo = () => {
|
||||
Modal.confirm({
|
||||
title: '确认删除',
|
||||
content: '确定要删除这个视频吗?',
|
||||
okText: '确定',
|
||||
cancelText: '取消',
|
||||
onOk: () => {
|
||||
form.video = undefined;
|
||||
message.success('视频已删除');
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const editorRef = ref<InstanceType<typeof TinymceEditor> | null>(null);
|
||||
|
||||
// 文件库选择弹窗状态
|
||||
@@ -620,7 +780,10 @@ const markdownToolbars = [
|
||||
] as any;
|
||||
|
||||
// 📝 Markdown编辑器图片上传处理
|
||||
const onMarkdownUploadImg = async (files: File[], callback: (urls: string[]) => void) => {
|
||||
const onMarkdownUploadImg = async (
|
||||
files: File[],
|
||||
callback: (urls: string[]) => void
|
||||
) => {
|
||||
try {
|
||||
const uploadPromises = files.map(async (file) => {
|
||||
// 检查文件大小(限制为10MB)
|
||||
@@ -646,7 +809,7 @@ const onMarkdownUploadImg = async (files: File[], callback: (urls: string[]) =>
|
||||
});
|
||||
|
||||
const results = await Promise.all(uploadPromises);
|
||||
const successUrls = results.filter(url => url !== null) as string[];
|
||||
const successUrls = results.filter((url) => url !== null) as string[];
|
||||
|
||||
if (successUrls.length > 0) {
|
||||
callback(successUrls);
|
||||
@@ -696,9 +859,9 @@ const config = ref({
|
||||
paste_data_images: true,
|
||||
automatic_uploads: true,
|
||||
|
||||
// 自定义工具栏,移除默认的image和media按钮,添加自定义按钮
|
||||
// 自定义工具栏,移除默认的image和media按钮,添加自定义按钮 (视频、上传视频按钮 custom_video_selector quick_video_upload)(首行缩进 )
|
||||
toolbar: [
|
||||
'fullscreen preview code codesample emoticons custom_image_selector quick_upload custom_video_selector quick_video_upload auto_format toggle_indent',
|
||||
'fullscreen preview code codesample emoticons auto_format custom_image_selector quick_upload',
|
||||
'undo redo | forecolor backcolor',
|
||||
'bold italic underline strikethrough',
|
||||
'alignleft aligncenter alignright alignjustify',
|
||||
@@ -740,7 +903,8 @@ const config = ref({
|
||||
},
|
||||
|
||||
// 图片工具栏
|
||||
image_toolbar: 'alignleft aligncenter alignright | rotateleft rotateright | imageoptions',
|
||||
image_toolbar:
|
||||
'alignleft aligncenter alignright | rotateleft rotateright | imageoptions',
|
||||
|
||||
// 图片标题
|
||||
image_title: true,
|
||||
@@ -769,7 +933,9 @@ const config = ref({
|
||||
onAction: () => {
|
||||
// 打开文件库选择弹窗
|
||||
fileSelectCallback.value = (url: string) => {
|
||||
editor.insertContent(`<img src="${url}" alt="图片" style="max-width: 100%;" />`);
|
||||
editor.insertContent(
|
||||
`<img src="${url}" alt="图片" style="max-width: 100%;" />`
|
||||
);
|
||||
};
|
||||
showFileSelector.value = true;
|
||||
}
|
||||
@@ -798,7 +964,9 @@ const config = ref({
|
||||
.then((res) => {
|
||||
loadingMsg();
|
||||
const imageUrl = res.url || res.path;
|
||||
editor.insertContent(`<img src="${imageUrl}" alt="${file.name}" style="max-width: 100%;" />`);
|
||||
editor.insertContent(
|
||||
`<img src="${imageUrl}" alt="${file.name}" style="max-width: 100%;" />`
|
||||
);
|
||||
message.success('图片上传成功');
|
||||
})
|
||||
.catch((msg) => {
|
||||
@@ -874,7 +1042,7 @@ const config = ref({
|
||||
|
||||
// 添加一键排版按钮
|
||||
editor.ui.registry.addButton('auto_format', {
|
||||
text: '一键排版',
|
||||
text: '排版',
|
||||
icon: 'template',
|
||||
tooltip: '智能优化文章格式和排版',
|
||||
onAction: () => {
|
||||
@@ -885,7 +1053,6 @@ const config = ref({
|
||||
|
||||
// 添加段落首行缩进切换按钮
|
||||
editor.ui.registry.addButton('toggle_indent', {
|
||||
text: '首行缩进',
|
||||
icon: 'indent',
|
||||
tooltip: '切换段落首行缩进(适合中文排版)',
|
||||
onAction: () => {
|
||||
@@ -928,7 +1095,12 @@ const handleAutoFormat = (editor: any) => {
|
||||
try {
|
||||
// 1. 检查内容
|
||||
const content = editor.getContent();
|
||||
if (!content || content.trim() === '' || content === '<p><br></p>' || content === '<p></p>') {
|
||||
if (
|
||||
!content ||
|
||||
content.trim() === '' ||
|
||||
content === '<p><br></p>' ||
|
||||
content === '<p></p>'
|
||||
) {
|
||||
message.warning({
|
||||
content: '📝 请先输入一些内容,然后再使用一键排版功能',
|
||||
duration: 3
|
||||
@@ -958,7 +1130,6 @@ const handleAutoFormat = (editor: any) => {
|
||||
|
||||
// 5. 可选:显示优化统计
|
||||
showOptimizationStats(content, optimizedContent);
|
||||
|
||||
} catch (error) {
|
||||
loadingMsg();
|
||||
console.error('排版优化失败:', error);
|
||||
@@ -968,7 +1139,6 @@ const handleAutoFormat = (editor: any) => {
|
||||
});
|
||||
}
|
||||
}, 800); // 给用户一个良好的反馈体验
|
||||
|
||||
} catch (error) {
|
||||
console.error('一键排版功能错误:', error);
|
||||
message.error({
|
||||
@@ -979,7 +1149,10 @@ const handleAutoFormat = (editor: any) => {
|
||||
};
|
||||
|
||||
// 📊 显示优化统计信息
|
||||
const showOptimizationStats = (originalContent: string, optimizedContent: string) => {
|
||||
const showOptimizationStats = (
|
||||
originalContent: string,
|
||||
optimizedContent: string
|
||||
) => {
|
||||
const stats = analyzeOptimization(originalContent, optimizedContent);
|
||||
|
||||
if (stats.optimizations.length > 0) {
|
||||
@@ -995,19 +1168,31 @@ const analyzeOptimization = (original: string, optimized: string) => {
|
||||
const optimizations: string[] = [];
|
||||
|
||||
// 检查各种优化项目
|
||||
if ((optimized.match(/<h[1-6][^>]*style/g) || []).length > (original.match(/<h[1-6][^>]*style/g) || []).length) {
|
||||
if (
|
||||
(optimized.match(/<h[1-6][^>]*style/g) || []).length >
|
||||
(original.match(/<h[1-6][^>]*style/g) || []).length
|
||||
) {
|
||||
optimizations.push('标题样式');
|
||||
}
|
||||
|
||||
if ((optimized.match(/<p[^>]*style/g) || []).length > (original.match(/<p[^>]*style/g) || []).length) {
|
||||
if (
|
||||
(optimized.match(/<p[^>]*style/g) || []).length >
|
||||
(original.match(/<p[^>]*style/g) || []).length
|
||||
) {
|
||||
optimizations.push('段落格式');
|
||||
}
|
||||
|
||||
if ((optimized.match(/<img[^>]*style/g) || []).length > (original.match(/<img[^>]*style/g) || []).length) {
|
||||
if (
|
||||
(optimized.match(/<img[^>]*style/g) || []).length >
|
||||
(original.match(/<img[^>]*style/g) || []).length
|
||||
) {
|
||||
optimizations.push('图片布局');
|
||||
}
|
||||
|
||||
if ((optimized.match(/<ul[^>]*style|<ol[^>]*style/g) || []).length > (original.match(/<ul[^>]*style|<ol[^>]*style/g) || []).length) {
|
||||
if (
|
||||
(optimized.match(/<ul[^>]*style|<ol[^>]*style/g) || []).length >
|
||||
(original.match(/<ul[^>]*style|<ol[^>]*style/g) || []).length
|
||||
) {
|
||||
optimizations.push('列表格式');
|
||||
}
|
||||
|
||||
@@ -1019,12 +1204,30 @@ const smartFormatContent = (content: string): string => {
|
||||
let optimized = content;
|
||||
|
||||
// 1. 🏷️ 标题优化 - 让标题更有层次感
|
||||
optimized = optimized.replace(/<h1([^>]*)>/g, '<h1$1 style="font-size: 28px; font-weight: 700; margin: 24px 0 16px 0; line-height: 1.3; color: #1a1a1a; border-bottom: 2px solid #e8e8e8; padding-bottom: 10px;">');
|
||||
optimized = optimized.replace(/<h2([^>]*)>/g, '<h2$1 style="font-size: 24px; font-weight: 600; margin: 20px 0 14px 0; line-height: 1.4; color: #2c2c2c;">');
|
||||
optimized = optimized.replace(/<h3([^>]*)>/g, '<h3$1 style="font-size: 20px; font-weight: 600; margin: 18px 0 12px 0; line-height: 1.4; color: #3c3c3c;">');
|
||||
optimized = optimized.replace(/<h4([^>]*)>/g, '<h4$1 style="font-size: 16px; font-weight: 600; margin: 14px 0 8px 0; line-height: 1.4; color: #4c4c4c;">');
|
||||
optimized = optimized.replace(/<h5([^>]*)>/g, '<h5$1 style="font-size: 14px; font-weight: 600; margin: 12px 0 6px 0; line-height: 1.4; color: #5c5c5c;">');
|
||||
optimized = optimized.replace(/<h6([^>]*)>/g, '<h6$1 style="font-size: 13px; font-weight: 600; margin: 10px 0 5px 0; line-height: 1.4; color: #6c6c6c;">');
|
||||
optimized = optimized.replace(
|
||||
/<h1([^>]*)>/g,
|
||||
'<h1$1 style="font-size: 28px; font-weight: 700; margin: 24px 0 16px 0; line-height: 1.3; color: #1a1a1a; border-bottom: 2px solid #e8e8e8; padding-bottom: 10px;">'
|
||||
);
|
||||
optimized = optimized.replace(
|
||||
/<h2([^>]*)>/g,
|
||||
'<h2$1 style="font-size: 24px; font-weight: 600; margin: 20px 0 14px 0; line-height: 1.4; color: #2c2c2c;">'
|
||||
);
|
||||
optimized = optimized.replace(
|
||||
/<h3([^>]*)>/g,
|
||||
'<h3$1 style="font-size: 20px; font-weight: 600; margin: 18px 0 12px 0; line-height: 1.4; color: #3c3c3c;">'
|
||||
);
|
||||
optimized = optimized.replace(
|
||||
/<h4([^>]*)>/g,
|
||||
'<h4$1 style="font-size: 16px; font-weight: 600; margin: 14px 0 8px 0; line-height: 1.4; color: #4c4c4c;">'
|
||||
);
|
||||
optimized = optimized.replace(
|
||||
/<h5([^>]*)>/g,
|
||||
'<h5$1 style="font-size: 14px; font-weight: 600; margin: 12px 0 6px 0; line-height: 1.4; color: #5c5c5c;">'
|
||||
);
|
||||
optimized = optimized.replace(
|
||||
/<h6([^>]*)>/g,
|
||||
'<h6$1 style="font-size: 13px; font-weight: 600; margin: 10px 0 5px 0; line-height: 1.4; color: #6c6c6c;">'
|
||||
);
|
||||
|
||||
// 2. 📝 段落优化 - 让阅读更舒适
|
||||
optimized = optimized.replace(/<p([^>]*)>/g, (match, attrs) => {
|
||||
@@ -1038,33 +1241,68 @@ const smartFormatContent = (content: string): string => {
|
||||
optimized = optimized.replace(/<img([^>]*?)>/g, (match, attrs) => {
|
||||
if (!attrs.includes('style=')) {
|
||||
const hasAlt = attrs.includes('alt=');
|
||||
return `<img${attrs} style="max-width: 100%; height: auto; margin: 20px auto; display: block; border-radius: 8px; box-shadow: 0 4px 16px rgba(0,0,0,0.1);"${!hasAlt ? ' alt="图片"' : ''}>`;
|
||||
return `<img${attrs} style="max-width: 100%; height: auto; margin: 20px auto; display: block; border-radius: 8px; box-shadow: 0 4px 16px rgba(0,0,0,0.1);"${
|
||||
!hasAlt ? ' alt="图片"' : ''
|
||||
}>`;
|
||||
}
|
||||
return match;
|
||||
});
|
||||
|
||||
// 4. 📋 列表优化 - 让列表更清晰
|
||||
optimized = optimized.replace(/<ul([^>]*)>/g, '<ul$1 style="margin: 16px 0; padding-left: 24px; line-height: 1.6;">');
|
||||
optimized = optimized.replace(/<ol([^>]*)>/g, '<ol$1 style="margin: 16px 0; padding-left: 24px; line-height: 1.6;">');
|
||||
optimized = optimized.replace(/<li([^>]*)>/g, '<li$1 style="margin: 8px 0; color: #333;">');
|
||||
optimized = optimized.replace(
|
||||
/<ul([^>]*)>/g,
|
||||
'<ul$1 style="margin: 16px 0; padding-left: 24px; line-height: 1.6;">'
|
||||
);
|
||||
optimized = optimized.replace(
|
||||
/<ol([^>]*)>/g,
|
||||
'<ol$1 style="margin: 16px 0; padding-left: 24px; line-height: 1.6;">'
|
||||
);
|
||||
optimized = optimized.replace(
|
||||
/<li([^>]*)>/g,
|
||||
'<li$1 style="margin: 8px 0; color: #333;">'
|
||||
);
|
||||
|
||||
// 5. 💬 引用优化 - 让引用更突出
|
||||
optimized = optimized.replace(/<blockquote([^>]*)>/g, '<blockquote$1 style="margin: 20px 0; padding: 16px 20px; border-left: 4px solid #1890ff; background: linear-gradient(90deg, #f6f8fa 0%, #ffffff 100%); font-style: italic; color: #555;">');
|
||||
optimized = optimized.replace(
|
||||
/<blockquote([^>]*)>/g,
|
||||
'<blockquote$1 style="margin: 20px 0; padding: 16px 20px; border-left: 4px solid #1890ff; background: linear-gradient(90deg, #f6f8fa 0%, #ffffff 100%); font-style: italic; color: #555;">'
|
||||
);
|
||||
|
||||
// 6. 💻 代码优化 - 让代码更专业
|
||||
optimized = optimized.replace(/<code([^>]*)>/g, '<code$1 style="background-color: #f1f3f4; padding: 2px 6px; border-radius: 4px; font-family: \'Fira Code\', Consolas, Monaco, monospace; font-size: 0.9em; color: #d73a49;">');
|
||||
optimized = optimized.replace(/<pre([^>]*)>/g, '<pre$1 style="margin: 20px 0; padding: 20px; background-color: #f8f9fa; border: 1px solid #e9ecef; border-radius: 8px; overflow-x: auto; font-family: \'Fira Code\', Consolas, Monaco, monospace; font-size: 14px; line-height: 1.5;">');
|
||||
optimized = optimized.replace(
|
||||
/<code([^>]*)>/g,
|
||||
'<code$1 style="background-color: #f1f3f4; padding: 2px 6px; border-radius: 4px; font-family: \'Fira Code\', Consolas, Monaco, monospace; font-size: 0.9em; color: #d73a49;">'
|
||||
);
|
||||
optimized = optimized.replace(
|
||||
/<pre([^>]*)>/g,
|
||||
'<pre$1 style="margin: 20px 0; padding: 20px; background-color: #f8f9fa; border: 1px solid #e9ecef; border-radius: 8px; overflow-x: auto; font-family: \'Fira Code\', Consolas, Monaco, monospace; font-size: 14px; line-height: 1.5;">'
|
||||
);
|
||||
|
||||
// 7. 📊 表格优化 - 让表格更美观
|
||||
optimized = optimized.replace(/<table([^>]*)>/g, '<table$1 style="width: 100%; border-collapse: collapse; margin: 20px 0; box-shadow: 0 2px 8px rgba(0,0,0,0.1); border-radius: 8px; overflow: hidden;">');
|
||||
optimized = optimized.replace(/<th([^>]*)>/g, '<th$1 style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 12px; text-align: left; font-weight: 600;">');
|
||||
optimized = optimized.replace(/<td([^>]*)>/g, '<td$1 style="padding: 12px; border-bottom: 1px solid #eee; color: #333;">');
|
||||
optimized = optimized.replace(
|
||||
/<table([^>]*)>/g,
|
||||
'<table$1 style="width: 100%; border-collapse: collapse; margin: 20px 0; box-shadow: 0 2px 8px rgba(0,0,0,0.1); border-radius: 8px; overflow: hidden;">'
|
||||
);
|
||||
optimized = optimized.replace(
|
||||
/<th([^>]*)>/g,
|
||||
'<th$1 style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 12px; text-align: left; font-weight: 600;">'
|
||||
);
|
||||
optimized = optimized.replace(
|
||||
/<td([^>]*)>/g,
|
||||
'<td$1 style="padding: 12px; border-bottom: 1px solid #eee; color: #333;">'
|
||||
);
|
||||
|
||||
// 8. 🔗 链接优化 - 让链接更友好
|
||||
optimized = optimized.replace(/<a([^>]*)>/g, '<a$1 style="color: #1890ff; text-decoration: none; border-bottom: 1px solid transparent; transition: border-bottom 0.2s ease;" onmouseover="this.style.borderBottom=\'1px solid #1890ff\'" onmouseout="this.style.borderBottom=\'1px solid transparent\'">');
|
||||
optimized = optimized.replace(
|
||||
/<a([^>]*)>/g,
|
||||
'<a$1 style="color: #1890ff; text-decoration: none; border-bottom: 1px solid transparent; transition: border-bottom 0.2s ease;" onmouseover="this.style.borderBottom=\'1px solid #1890ff\'" onmouseout="this.style.borderBottom=\'1px solid transparent\'">'
|
||||
);
|
||||
|
||||
// 9. ➖ 分隔线优化 - 让分隔线更优雅
|
||||
optimized = optimized.replace(/<hr([^>]*)>/g, '<hr$1 style="border: none; height: 2px; background: linear-gradient(90deg, transparent, #e8e8e8, transparent); margin: 30px 0;">');
|
||||
optimized = optimized.replace(
|
||||
/<hr([^>]*)>/g,
|
||||
'<hr$1 style="border: none; height: 2px; background: linear-gradient(90deg, transparent, #e8e8e8, transparent); margin: 30px 0;">'
|
||||
);
|
||||
|
||||
// 10. 🧹 清理多余空白
|
||||
optimized = optimized.replace(/\s+/g, ' '); // 清理多余空格
|
||||
@@ -1079,7 +1317,12 @@ const toggleParagraphIndent = (editor: any) => {
|
||||
try {
|
||||
const content = editor.getContent();
|
||||
|
||||
if (!content || content.trim() === '' || content === '<p><br></p>' || content === '<p></p>') {
|
||||
if (
|
||||
!content ||
|
||||
content.trim() === '' ||
|
||||
content === '<p><br></p>' ||
|
||||
content === '<p></p>'
|
||||
) {
|
||||
message.warning({
|
||||
content: '📝 请先输入一些段落内容,然后再切换首行缩进',
|
||||
duration: 3
|
||||
@@ -1088,7 +1331,9 @@ const toggleParagraphIndent = (editor: any) => {
|
||||
}
|
||||
|
||||
// 检查当前是否有首行缩进
|
||||
const hasIndent = content.includes('text-indent: 2em') || content.includes('text-indent:2em');
|
||||
const hasIndent =
|
||||
content.includes('text-indent: 2em') ||
|
||||
content.includes('text-indent:2em');
|
||||
|
||||
let newContent: string;
|
||||
let actionText: string;
|
||||
@@ -1109,7 +1354,6 @@ const toggleParagraphIndent = (editor: any) => {
|
||||
content: `📐 ${actionText}`,
|
||||
duration: 3
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('首行缩进切换失败:', error);
|
||||
message.error({
|
||||
@@ -1130,7 +1374,10 @@ const addIndentToParagraphs = (content: string): string => {
|
||||
return match.replace(/text-indent:\s*[^;]+;?/g, 'text-indent: 2em;');
|
||||
} else {
|
||||
// 在现有 style 中添加 text-indent
|
||||
return match.replace(/style="([^"]*)"/, 'style="$1 text-indent: 2em;"');
|
||||
return match.replace(
|
||||
/style="([^"]*)"/,
|
||||
'style="$1 text-indent: 2em;"'
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// 添加新的 style 属性
|
||||
@@ -1224,8 +1471,7 @@ const save = () => {
|
||||
loading.value = false;
|
||||
});
|
||||
})
|
||||
.catch(() => {
|
||||
});
|
||||
.catch(() => {});
|
||||
};
|
||||
|
||||
watch(
|
||||
@@ -1551,4 +1797,39 @@ watch(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 视频上传容器样式
|
||||
.video-upload-container {
|
||||
.video-preview {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
|
||||
video {
|
||||
border: 1px solid #e8e8e8;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
}
|
||||
|
||||
.upload-tip {
|
||||
margin-top: 8px;
|
||||
color: #999;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.upload-progress {
|
||||
margin-top: 16px;
|
||||
padding: 16px;
|
||||
background: #f8f9fa;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #e8e8e8;
|
||||
|
||||
.progress-text {
|
||||
margin-top: 8px;
|
||||
text-align: center;
|
||||
color: #666;
|
||||
font-size: 13px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -92,8 +92,6 @@
|
||||
// 中文语言文件
|
||||
import zh_Hans from 'bytemd/locales/zh_Hans.json';
|
||||
import 'bytemd/dist/index.min.css';
|
||||
import highlight from '@bytemd/plugin-highlight-ssr';
|
||||
import 'highlight.js/styles/default.css';
|
||||
import { ShopMerchantAccount } from '@/api/shop/shopMerchantAccount/model';
|
||||
import { User } from '@/api/system/user/model';
|
||||
|
||||
@@ -173,7 +171,7 @@
|
||||
type: 'string',
|
||||
message: '请填写消息内容',
|
||||
trigger: 'blur',
|
||||
validator: async (_rule: RuleObject, value: string) => {
|
||||
validator: async (_rule: RuleObject, _: string) => {
|
||||
if (content.value == '') {
|
||||
return Promise.reject('请填写消息内容');
|
||||
}
|
||||
@@ -187,8 +185,7 @@
|
||||
const plugins = ref([
|
||||
gfm({
|
||||
locale: zh_HansGfm
|
||||
}),
|
||||
highlight()
|
||||
})
|
||||
]);
|
||||
|
||||
const onToUser = (item: User) => {
|
||||
@@ -199,7 +196,7 @@
|
||||
// form.toUserName = item.nickname;
|
||||
};
|
||||
|
||||
const chooseType = (item: any) => {
|
||||
const chooseType = (_: any) => {
|
||||
form.type = 'text';
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user