优化编辑器和前端内容页显示

This commit is contained in:
2026-01-28 14:36:40 +08:00
parent da378a01be
commit b714df5f43
3 changed files with 945 additions and 482 deletions
+380 -4
View File
@@ -5,15 +5,15 @@
// body 样式
body {
background-color: var(--el-bg-color-page);
color: var(--el-text-color-primary);
background-color: #f5f7fa;
color: #303133;
transition: background-color 0.3s ease, color 0.3s ease;
}
// container-box 样式
.container-box {
background-color: var(--el-bg-color);
border: 1px solid var(--el-border-color-lighter);
background-color: #ffffff;
border: 1px solid #ebeef5;
box-shadow: 0 2px 8px 0 rgba(0, 0, 0, 0.04);
border-radius: 8px;
padding: 24px;
@@ -48,6 +48,382 @@ body {
.toolbar-container{
border-bottom: 1px solid #dcdfe6 !important;
}
.editor-container {
background-color: #ffffff !important;
}
:deep(.w-e-text),
:deep(.w-e-text-container) {
background-color: transparent !important;
* {
color: #1a1a2e !important;
}
p {
color: #1a1a2e !important;
margin: 8px 0 !important;
line-height: 1.8 !important;
font-size: 14px !important;
text-indent: 0 !important;
}
span {
color: #1a1a2e !important;
font-size: 14px !important;
line-height: 1.8 !important;
}
strong, b {
font-weight: 600 !important;
color: #1a1a2e !important;
}
em, i {
font-style: italic !important;
}
u {
text-decoration: underline !important;
}
s, del {
text-decoration: line-through !important;
}
h1 {
color: #1a1a2e !important;
font-size: 28px !important;
font-weight: 600 !important;
margin: 16px 0 12px !important;
line-height: 1.4 !important;
}
h2 {
color: #1a1a2e !important;
font-size: 24px !important;
font-weight: 600 !important;
margin: 16px 0 12px !important;
line-height: 1.4 !important;
}
h3 {
color: #1a1a2e !important;
font-size: 20px !important;
font-weight: 600 !important;
margin: 16px 0 12px !important;
line-height: 1.4 !important;
}
h4 {
color: #1a1a2e !important;
font-size: 18px !important;
font-weight: 600 !important;
margin: 16px 0 12px !important;
line-height: 1.4 !important;
}
h5 {
color: #1a1a2e !important;
font-size: 16px !important;
font-weight: 600 !important;
margin: 16px 0 12px !important;
line-height: 1.4 !important;
}
h6 {
color: #1a1a2e !important;
font-size: 14px !important;
font-weight: 600 !important;
margin: 16px 0 12px !important;
line-height: 1.4 !important;
}
a {
color: #3973ff !important;
text-decoration: underline !important;
&:hover {
color: #3973ff !important;
opacity: 0.8 !important;
}
}
code {
background-color: #f5f7fa !important;
color: #1a1a2e !important;
border: 1px solid #e4e7ed !important;
border-radius: 3px !important;
padding: 2px 6px !important;
font-family: 'Consolas', 'Monaco', monospace !important;
font-size: 13px !important;
}
pre {
background-color: #f5f7fa !important;
border: 1px solid #e4e7ed !important;
border-radius: 4px !important;
color: #1a1a2e !important;
padding: 12px 16px !important;
margin: 12px 0 !important;
overflow-x: auto;
code {
background-color: transparent !important;
border: none !important;
padding: 0 !important;
color: #1a1a2e !important;
font-size: 13px !important;
line-height: 1.6 !important;
}
}
blockquote {
border-left: 4px solid #3973ff !important;
background-color: #f5f7fa !important;
color: #606266 !important;
padding: 8px 16px !important;
margin: 12px 0 !important;
}
table {
border-collapse: collapse !important;
border: 1px solid #e4e7ed !important;
width: 100% !important;
th, td {
border: 1px solid #e4e7ed !important;
background-color: #ffffff !important;
color: #1a1a2e !important;
padding: 8px 12px !important;
min-width: 60px;
}
th {
background-color: #f5f7fa !important;
font-weight: 600 !important;
}
}
ul {
list-style-type: disc !important;
color: #1a1a2e !important;
padding-left: 24px !important;
margin: 8px 0 !important;
}
ol {
list-style-type: decimal !important;
color: #1a1a2e !important;
padding-left: 24px !important;
margin: 8px 0 !important;
}
li {
color: #1a1a2e !important;
line-height: 1.8 !important;
margin: 4px 0 !important;
}
hr {
border-top: 1px solid #e4e7ed !important;
margin: 16px 0 !important;
}
img {
max-width: 100% !important;
border-radius: 4px !important;
margin: 8px 0 !important;
}
video {
max-width: 100% !important;
border-radius: 4px !important;
margin: 8px 0 !important;
}
.w-e-panel-tab-content {
color: #1a1a2e !important;
}
}
}
html.dark {
.wang-editor-wrapper {
border-color: #3d3d3d !important;
background-color: #1a1a1a !important;
.toolbar-container {
background-color: #2d2d2d !important;
border-color: #3d3d3d !important;
}
.editor-container {
background-color: #1a1a1a !important;
&::-webkit-scrollbar-thumb {
background: #4d4d4d !important;
}
&::-webkit-scrollbar-track {
background: #2d2d2d !important;
}
}
:deep(.w-e-text),
:deep(.w-e-text-container) {
background-color: transparent !important;
* {
color: #e0e0e0 !important;
}
p {
color: #e0e0e0 !important;
margin: 8px 0 !important;
line-height: 1.8 !important;
font-size: 14px !important;
text-indent: 0 !important;
}
span {
color: #e0e0e0 !important;
font-size: 14px !important;
line-height: 1.8 !important;
}
strong, b {
font-weight: 600 !important;
color: #e0e0e0 !important;
}
em, i {
font-style: italic !important;
}
u {
text-decoration: underline !important;
}
s, del {
text-decoration: line-through !important;
}
h1 {
color: #e0e0e0 !important;
font-size: 28px !important;
font-weight: 600 !important;
margin: 16px 0 12px !important;
line-height: 1.4 !important;
}
h2 {
color: #e0e0e0 !important;
font-size: 24px !important;
font-weight: 600 !important;
margin: 16px 0 12px !important;
line-height: 1.4 !important;
}
h3 {
color: #e0e0e0 !important;
font-size: 20px !important;
font-weight: 600 !important;
margin: 16px 0 12px !important;
line-height: 1.4 !important;
}
h4 {
color: #e0e0e0 !important;
font-size: 18px !important;
font-weight: 600 !important;
margin: 16px 0 12px !important;
line-height: 1.4 !important;
}
h5 {
color: #e0e0e0 !important;
font-size: 16px !important;
font-weight: 600 !important;
margin: 16px 0 12px !important;
line-height: 1.4 !important;
}
h6 {
color: #e0e0e0 !important;
font-size: 14px !important;
font-weight: 600 !important;
margin: 16px 0 12px !important;
line-height: 1.4 !important;
}
a {
color: #4f84ff !important;
text-decoration: underline !important;
&:hover {
color: #4f84ff !important;
opacity: 0.8 !important;
}
}
code {
background-color: #2d2d2d !important;
color: #e0e0e0 !important;
border-color: #3d3d3d !important;
}
pre {
background-color: #2d2d2d !important;
border-color: #3d3d3d !important;
color: #e0e0e0 !important;
code {
background-color: transparent !important;
border: none !important;
color: #e0e0e0 !important;
}
}
blockquote {
border-left-color: #4f84ff !important;
background-color: #2d2d2d !important;
color: #b0b0b0 !important;
}
table {
border-color: #3d3d3d !important;
th, td {
border-color: #3d3d3d !important;
background-color: #1a1a1a !important;
color: #e0e0e0 !important;
}
th {
background-color: #2d2d2d !important;
}
}
ul, ol, li {
color: #e0e0e0 !important;
}
hr {
border-top-color: #3d3d3d !important;
}
img, video {
max-width: 100% !important;
border-radius: 4px !important;
}
.w-e-panel-tab-content {
color: #e0e0e0 !important;
}
}
}
}
.el-form-item__label{
min-width: 80px !important;
+331 -122
View File
@@ -1,5 +1,10 @@
<template>
<el-drawer v-model="visible" :title="isEdit ? '编辑文章' : '新增文章'" size="60%" :before-close="handleBeforeClose">
<el-drawer
v-model="visible"
:title="isEdit ? '编辑文章' : '新增文章'"
size="60%"
:before-close="handleBeforeClose"
>
<el-form :model="form" :rules="rules" ref="formRef" label-width="80px">
<el-form-item label="标题" prop="title">
<el-input v-model="form.title" placeholder="请输入文章标题" />
@@ -25,37 +30,71 @@
</el-row>
<el-form-item label="简介" prop="desc">
<el-input v-model="form.desc" :rows="4" type="textarea" placeholder="请输入简介" />
<el-input
v-model="form.desc"
:rows="4"
type="textarea"
placeholder="请输入简介"
/>
</el-form-item>
<el-form-item label="封面图片" prop="image">
<div class="uploads">
<!-- 已有图片显示 -->
<div v-if="form.image && fileList.length === 0" class="existing-image">
<img :src="API_BASE_URL + form.image.replace(/\\\//g, '/')" alt="已有图片" />
<div
v-if="form.image && fileList.length === 0"
class="existing-image"
>
<img
:src="API_BASE_URL + form.image.replace(/\\\//g, '/')"
alt="已有图片"
/>
<div class="image-actions">
<el-button type="primary" size="small" @click="previewExistingImage">预览</el-button>
<el-button type="danger" size="small" @click="removeExistingImage">删除</el-button>
<el-button
type="primary"
size="small"
@click="previewExistingImage"
>预览</el-button
>
<el-button type="danger" size="small" @click="removeExistingImage"
>删除</el-button
>
</div>
</div>
<!-- 上传组件 -->
<el-upload v-model:file-list="fileList" :auto-upload="false" :before-upload="beforeImgUpload"
list-type="picture-card" :limit="1" :show-file-list="fileList.length > 0">
<el-upload
v-model:file-list="fileList"
:auto-upload="false"
:before-upload="beforeImgUpload"
list-type="picture-card"
:limit="1"
:show-file-list="fileList.length > 0"
>
<el-icon>
<Plus />
</el-icon>
<template #file="{ file }">
<div>
<img class="el-upload-list__item-thumbnail" :src="file.url" alt="" />
<img
class="el-upload-list__item-thumbnail"
:src="file.url"
alt=""
/>
<span class="el-upload-list__item-actions">
<span class="el-upload-list__item-preview" @click="handlePictureCardPreview(file)">
<span
class="el-upload-list__item-preview"
@click="handlePictureCardPreview(file)"
>
<el-icon>
<ZoomIn />
</el-icon>
</span>
<span class="el-upload-list__item-delete" @click="handleRemove(file)">
<span
class="el-upload-list__item-delete"
@click="handleRemove(file)"
>
<el-icon>
<Delete />
</el-icon>
@@ -66,9 +105,18 @@
</el-upload>
<el-drawer v-model="drawerVisible" width="60%" center>
<div style="display: flex; justify-content: center;align-items:center;">
<img :src="drawerImageUrl" alt="Preview Image"
style="max-width: 100%; max-height: 70vh; object-fit: contain;" />
<div
style="
display: flex;
justify-content: center;
align-items: center;
"
>
<img
:src="drawerImageUrl"
alt="Preview Image"
style="max-width: 100%; max-height: 70vh; object-fit: contain"
/>
</div>
</el-drawer>
@@ -102,36 +150,47 @@
<el-divider></el-divider>
<span class="drawer-footer">
<el-button @click="handleCancel">取消</el-button>
<el-button v-if="!isEdit" type="warning" @click="handleConfirm(0)" :loading="submitLoading">草稿</el-button>
<el-button type="primary" @click="handleConfirm(1)" :loading="submitLoading">提交</el-button>
<el-button
v-if="!isEdit"
type="warning"
@click="handleConfirm(0)"
:loading="submitLoading"
>草稿</el-button
>
<el-button
type="primary"
@click="handleConfirm(1)"
:loading="submitLoading"
>提交</el-button
>
</span>
</template>
</el-drawer>
</template>
<script lang="ts" setup>
import { ref, reactive, watch, nextTick, onMounted, computed } from 'vue';
import { ElMessage, ElUpload } from 'element-plus'
import WangEditor from '@/views/components/WangEditor.vue';
import { createArticle, editArticle, listCategories } from '@/api/article.js';
import { uploadFile } from '@/api/file.js';
import { ref, reactive, watch, nextTick, onMounted, computed } from "vue";
import { ElMessage, ElUpload } from "element-plus";
import WangEditor from "@/views/components/WangEditor.vue";
import { createArticle, editArticle, listCategories } from "@/api/article.js";
import { uploadFile } from "@/api/file.js";
const props = defineProps({
modelValue: {
type: Boolean,
default: false
default: false,
},
isEdit: {
type: Boolean,
default: false
default: false,
},
model: {
type: Object,
default: () => null
}
default: () => null,
},
});
const emit = defineEmits(['update:modelValue', 'saved']);
const emit = defineEmits(["update:modelValue", "saved"]);
const visible = ref(false);
const submitLoading = ref(false);
@@ -145,28 +204,31 @@ const handleBeforeClose = (done: () => void) => {
const cateOptions = ref([]);
const form = reactive({
title: '',
author: '云泽网',
cate: '',
content: '',
image: '',
desc: '',
title: "",
author: "云泽网",
cate: "",
content: "",
image: "",
desc: "",
is_trans: 0,
transurl: null
transurl: null,
});
const rules = {
title: [
{ required: true, message: '请输入文章标题', trigger: 'blur' },
{ min: 2, max: 200, message: '标题长度在 2 到 200 个字符', trigger: 'blur' }
{ required: true, message: "请输入文章标题", trigger: "blur" },
{
min: 2,
max: 200,
message: "标题长度在 2 200 个字符",
trigger: "blur",
},
],
author: [
{ required: true, message: '请输入作者', trigger: 'blur' },
{ max: 50, message: '作者姓名不能超过50个字符', trigger: 'blur' }
],
content: [
{ required: true, message: '请输入文章内容', trigger: 'blur' }
{ required: true, message: "请输入作者", trigger: "blur" },
{ max: 50, message: "作者姓名不能超过50个字符", trigger: "blur" },
],
content: [{ required: true, message: "请输入文章内容", trigger: "blur" }],
};
// 获取分类列表
@@ -179,88 +241,91 @@ async function fetchCategories() {
cateOptions.value = buildCategoryTree(categories);
}
} catch (error) {
console.error('获取分类失败:', error);
console.error("获取分类失败:", error);
}
}
// 构建分类树形结构
function buildCategoryTree(categories, parentCid = 0) {
return categories
.filter(cate => Number(cate.cid) === Number(parentCid))
.map(cate => {
.filter((cate) => Number(cate.cid) === Number(parentCid))
.map((cate) => {
const children = buildCategoryTree(categories, cate.id);
return {
...cate,
label: cate.name,
value: cate.id,
children: children.length > 0 ? children : undefined
children: children.length > 0 ? children : undefined,
};
});
}
// 上传相关
const fileList = ref<any[]>([])
const drawerVisible = ref(false)
const drawerImageUrl = ref('')
const API_BASE_URL = import.meta.env.VITE_API_BASE_URL
const fileList = ref<any[]>([]);
const drawerVisible = ref(false);
const drawerImageUrl = ref("");
const API_BASE_URL = import.meta.env.VITE_API_BASE_URL;
function beforeImgUpload(file: File) {
const isImage = file.type.startsWith('image/')
const isLt10M = file.size / 1024 / 1024 < 10
if (!isImage) ElMessage.error('仅支持图片格式')
if (!isLt10M) ElMessage.error('图片大小不能超过10MB')
return isImage && isLt10M
const isImage = file.type.startsWith("image/");
const isLt10M = file.size / 1024 / 1024 < 10;
if (!isImage) ElMessage.error("仅支持图片格式");
if (!isLt10M) ElMessage.error("图片大小不能超过10MB");
return isImage && isLt10M;
}
function handlePictureCardPreview(file: any) {
drawerImageUrl.value = file.url
drawerVisible.value = true
drawerImageUrl.value = file.url;
drawerVisible.value = true;
}
function handleRemove(file: any) {
fileList.value = []
form.image = ''
fileList.value = [];
form.image = "";
}
function removeExistingImage() {
form.image = ''
form.image = "";
}
function previewExistingImage() {
const imagePath = form.image.replace(/\\\//g, '/');
const imagePath = form.image.replace(/\\\//g, "/");
drawerImageUrl.value = API_BASE_URL + imagePath;
drawerVisible.value = true;
}
// 监听对话框显示状态
watch(() => props.modelValue, (newVal) => {
visible.value = newVal;
if (newVal) {
if (props.isEdit && props.model) {
// 编辑模式,填充表单数据
const modelData = props.model._raw || props.model;
nextTick(() => {
Object.assign(form, {
title: modelData.title || '',
author: modelData.author || '',
cate: modelData.cate || '',
content: modelData.content || '',
desc: modelData.desc || '',
is_trans: modelData.is_trans || 0,
transurl: modelData.transurl || null,
image: modelData.image || ''
watch(
() => props.modelValue,
(newVal) => {
visible.value = newVal;
if (newVal) {
if (props.isEdit && props.model) {
// 编辑模式,填充表单数据
const modelData = props.model._raw || props.model;
nextTick(() => {
Object.assign(form, {
title: modelData.title || "",
author: modelData.author || "",
cate: modelData.cate || "",
content: modelData.content || "",
desc: modelData.desc || "",
is_trans: modelData.is_trans || 0,
transurl: modelData.transurl || null,
image: modelData.image || "",
});
fileList.value = []; // 重置文件列表
});
fileList.value = []; // 重置文件列表
});
} else {
// 新增模式,重置表单
resetForm();
} else {
// 新增模式,重置表单
resetForm();
}
}
}
});
},
);
function handleCancel() {
emit('update:modelValue', false);
emit("update:modelValue", false);
resetForm();
}
@@ -274,16 +339,20 @@ async function handleConfirm(status = 1) {
// 如果有新选择的文件,先上传图片
if (fileList.value.length > 0 && fileList.value[0].raw) {
const uploadFormData = new FormData()
uploadFormData.append('file', fileList.value[0].raw)
uploadFormData.append('cate', 'article')
const uploadFormData = new FormData();
uploadFormData.append("file", fileList.value[0].raw);
uploadFormData.append("cate", "article");
const uploadRes = await uploadFile(uploadFormData);
// 200=新文件上传成功,201=文件已存在(使用已有文件的链接)
if ((uploadRes.code === 200 || uploadRes.code === 201) && uploadRes.data && uploadRes.data.url) {
if (
(uploadRes.code === 200 || uploadRes.code === 201) &&
uploadRes.data &&
uploadRes.data.url
) {
imageUrl = uploadRes.data.url;
} else {
ElMessage.error('图片上传失败' + (uploadRes.msg || '未知错误'));
ElMessage.error("图片上传失败" + (uploadRes.msg || "未知错误"));
return;
}
}
@@ -298,7 +367,7 @@ async function handleConfirm(status = 1) {
image: imageUrl,
is_trans: form.is_trans,
transurl: form.is_trans === 1 ? form.transurl : null,
status: status // 0为草稿,1为提交
status: status, // 0为草稿,1为提交
};
// 根据新增/编辑模式选择不同的API调用函数
@@ -311,38 +380,49 @@ async function handleConfirm(status = 1) {
const createArticleWithCheck = async (ignoreSimilarity = false) => {
const data = {
...submitData,
...(ignoreSimilarity && { ignore_similarity: 1 })
...(ignoreSimilarity && { ignore_similarity: 1 }),
};
return await createArticle(data);
};
res = await createArticleWithCheck();
resp = (res && typeof res.code !== 'undefined') ? res : (res && res.data ? res.data : res);
resp =
res && typeof res.code !== "undefined"
? res
: res && res.data
? res.data
: res;
// 检测到相似标题,显示确认对话框
if (resp && resp.code === 409) {
const similarArticles = resp.data?.similar_articles || [];
// 构建带样式的确认消息
const similarArticlesList = similarArticles.map((article: any) => `\n<div style="margin: 8px 0; padding: 8px; background: #f5f7fa; border-radius: 4px;">\n<div style="font-weight: bold; color: #303133;">${article.title}</div>\n<div style="color: #606266; font-size: 14px; margin-top: 4px;">相似度:${article.similarity}%</div>\n</div>\n`).join('');
const similarArticlesList = similarArticles
.map(
(article: any) =>
`\n<div style="margin: 8px 0; padding: 8px; background: #f5f7fa; border-radius: 4px;">\n<div style="font-weight: bold; color: #303133;">${article.title}</div>\n<div style="color: #606266; font-size: 14px; margin-top: 4px;">相似度:${article.similarity}%</div>\n</div>\n`,
)
.join("");
const confirmMessage = `\n<div>\n\n${similarArticlesList} \n<p style="margin-top: 16px; color: #303133;">是否继续创建?</p>\n</div>\n`;
// 使用Element Plus的confirm对话框
const { ElMessageBox } = await import('element-plus');
const { ElMessageBox } = await import("element-plus");
try {
await ElMessageBox.confirm(
confirmMessage,
'检测到相似标题',
{
confirmButtonText: '继续创建',
cancelButtonText: '取消',
type: 'warning',
dangerouslyUseHTMLString: true,
}
);
await ElMessageBox.confirm(confirmMessage, "检测到相似标题", {
confirmButtonText: "继续创建",
cancelButtonText: "取消",
type: "warning",
dangerouslyUseHTMLString: true,
});
// 用户确认继续,再次调用接口并忽略相似度检测
res = await createArticleWithCheck(true);
resp = (res && typeof res.code !== 'undefined') ? res : (res && res.data ? res.data : res);
resp =
res && typeof res.code !== "undefined"
? res
: res && res.data
? res.data
: res;
} catch (error) {
// 用户取消创建
return;
@@ -355,28 +435,57 @@ async function handleConfirm(status = 1) {
const articleId = modelData.id;
if (!articleId) {
ElMessage.error('文章ID不存在无法更新');
ElMessage.error("文章ID不存在无法更新");
return;
}
// 构建编辑请求数据,不包含文章ID(ID通过URL参数传递)
const editData = {
...submitData
...submitData,
};
res = await editArticle(articleId, editData);
resp = (res && typeof res.code !== 'undefined') ? res : (res && res.data ? res.data : res);
resp =
res && typeof res.code !== "undefined"
? res
: res && res.data
? res.data
: res;
}
if (resp && resp.code === 200 && (resp.message === 'success' || resp.msg === 'success')) {
ElMessage.success(status === 0 ? '保存草稿成功' : (props.isEdit ? '更新成功' : '创建成功'));
emit('update:modelValue', false);
emit('saved');
if (
resp &&
resp.code === 200 &&
(resp.message === "success" || resp.msg === "success")
) {
ElMessage.success(
status === 0
? "保存草稿成功"
: props.isEdit
? "更新成功"
: "创建成功",
);
emit("update:modelValue", false);
emit("saved");
} else {
ElMessage.error((resp && resp.message) || (resp && resp.msg) || (status === 0 ? '保存草稿失败' : (props.isEdit ? '更新失败' : '创建失败')));
ElMessage.error(
(resp && resp.message) ||
(resp && resp.msg) ||
(status === 0
? "保存草稿失败"
: props.isEdit
? "更新失败"
: "创建失败"),
);
}
} catch (error) {
ElMessage.error(status === 0 ? '保存草稿失败' : (props.isEdit ? '更新失败' : '创建失败'));
ElMessage.error(
status === 0
? "保存草稿失败"
: props.isEdit
? "更新失败"
: "创建失败",
);
} finally {
submitLoading.value = false;
}
@@ -393,14 +502,14 @@ function resetForm() {
fileList.value = [];
// 重置表单数据
Object.assign(form, {
title: '',
author: '云泽网',
cate: '',
content: '',
image: '',
desc: '',
title: "",
author: "云泽网",
cate: "",
content: "",
image: "",
desc: "",
is_trans: 0,
transurl: null
transurl: null,
});
}
@@ -408,12 +517,12 @@ function resetForm() {
defineExpose({
resetForm: () => {
formRef.value?.resetFields();
}
},
});
onMounted(() => {
fetchCategories();
})
});
</script>
<style lang="scss" scoped>
@@ -421,6 +530,106 @@ onMounted(() => {
border: 1px solid #dcdfe6;
border-radius: 4px;
overflow: hidden;
&:focus {
outline: none !important;
}
:deep(.w-e-text),
:deep(.w-e-text-container) {
p {
color: #1a1a2e !important;
margin: 0.5em 0 !important;
}
h1, h2, h3, h4, h5, h6 {
color: #1a1a2e !important;
font-weight: 600 !important;
}
a {
color: #3973ff !important;
text-decoration: underline !important;
&:hover {
color: #3973ff !important;
opacity: 0.8 !important;
}
}
code {
background-color: #f5f7fa !important;
color: #1a1a2e !important;
border: 1px solid #e4e7ed !important;
padding: 2px 6px !important;
border-radius: 3px !important;
font-family: 'Consolas', 'Monaco', monospace;
font-size: 13px;
}
pre {
background-color: #f5f7fa !important;
border: 1px solid #e4e7ed !important;
color: #1a1a2e !important;
border-radius: 4px !important;
padding: 12px 16px;
margin: 12px 0;
overflow-x: auto;
code {
background-color: transparent !important;
border: none !important;
padding: 0 !important;
}
}
blockquote {
border-left: 4px solid #3973ff !important;
background-color: #f5f7fa !important;
color: #606266 !important;
padding: 0.6em 1.2em !important;
margin: 1em 0 !important;
}
table {
border-collapse: collapse !important;
border: 1px solid #e4e7ed !important;
th, td {
border: 1px solid #e4e7ed !important;
background-color: #ffffff !important;
color: #1a1a2e !important;
padding: 8px 12px;
}
th {
background-color: #f5f7fa !important;
}
}
ul, ol {
color: #1a1a2e !important;
padding-left: 24px;
margin: 8px 0;
}
li {
color: #1a1a2e !important;
line-height: 1.8;
margin: 4px 0;
}
hr {
border-top: 1px solid #e4e7ed !important;
margin: 16px 0;
}
img {
max-width: 100%;
border-radius: 4px;
margin: 8px 0;
}
}
}
.drawer-footer {
+229 -351
View File
@@ -325,380 +325,258 @@ onBeforeUnmount(() => {
});
</script>
<style scoped lang="less">
<style lang="less">
.wang-editor-wrapper {
display: flex;
flex-direction: column;
height: 650px; // 设置固定高度
border: 1px solid var(--border-color);
height: 650px;
border: 1px solid #dcdfe6;
border-radius: 4px;
overflow: hidden;
background-color: var(--fill-color-blank);
background: #ffffff;
.toolbar-container {
.toolbar-container {--fill-color-light
position: sticky;
top: 0;
z-index: 10;
background-color: var(--fill-color-light);
border-bottom: 1px solid var(--border-color-lighter);
background: #f5f7fa;
border-bottom: 1px solid #dcdfe6;
flex-shrink: 0;
}
.editor-container {
flex: 1;
overflow-y: auto;
padding: 10px;
overflow-x: hidden;
padding: 0;
&::-webkit-scrollbar {
width: 6px;
height: 6px;
}
&::-webkit-scrollbar-thumb {
background: #c0c4cc;
border-radius: 3px;
}
&::-webkit-scrollbar-track {
background: #f5f7fa;
border-radius: 3px;
}
}
:deep(.w-e-text),
:deep(.w-e-text-container) {
p {
color: #1a1a2e !important;
margin: 8px 0 !important;
line-height: 1.8 !important;
}
h1, h2, h3, h4, h5, h6 {
color: #1a1a2e !important;
font-weight: 600 !important;
margin: 16px 0 8px !important;
}
a {
color: #3973ff !important;
text-decoration: underline !important;
}
code {
background: #f5f7fa !important;
color: #1a1a2e !important;
border: 1px solid #e4e7ed !important;
border-radius: 3px;
padding: 2px 6px;
font-family: 'Consolas', 'Monaco', monospace;
font-size: 13px;
}
pre {
background: #f5f7fa !important;
border: 1px solid #e4e7ed !important;
border-radius: 4px;
padding: 12px 16px;
margin: 12px 0;
overflow-x: auto;
color: #1a1a2e !important;
code {
background: transparent !important;
border: none !important;
padding: 0;
font-size: 13px;
line-height: 1.6;
color: #1a1a2e !important;
}
}
blockquote {
border-left: 4px solid #3973ff !important;
background: #f5f7fa !important;
color: #606266 !important;
padding: 8px 16px;
margin: 12px 0;
}
table {
border-collapse: collapse !important;
border: 1px solid #e4e7ed !important;
width: 100% !important;
margin: 12px 0;
th, td {
border: 1px solid #e4e7ed !important;
padding: 8px 12px !important;
min-width: 60px;
}
th {
background: #f5f7fa !important;
font-weight: 600;
}
}
ul, ol {
color: #1a1a2e !important;
padding-left: 24px !important;
margin: 8px 0;
}
li {
color: #1a1a2e !important;
line-height: 1.8 !important;
margin: 4px 0;
}
hr {
border-top: 1px solid #e4e7ed !important;
margin: 16px 0 !important;
}
img {
max-width: 100% !important;
border-radius: 4px !important;
margin: 8px 0;
}
}
}
html.dark {
.wang-editor-wrapper {
border-color: #3d3d3d;
background: #1a1a1a;
.toolbar-container {
background: #2d2d2d;
border-color: #3d3d3d;
}
.editor-container {
&::-webkit-scrollbar {
width: 6px;
height: 6px;
}
&::-webkit-scrollbar-thumb {
background: #4d4d4d;
border-radius: 3px;
}
&::-webkit-scrollbar-track {
background: #2d2d2d;
border-radius: 3px;
}
}
:deep(.w-e-text),
:deep(.w-e-text-container) {
background: transparent !important;
p, h1, h2, h3, h4, h5, h6 {
color: #e0e0e0 !important;
}
a {
color: #4f84ff !important;
text-decoration: underline !important;
}
code {
background: #2d2d2d !important;
color: #e0e0e0 !important;
border-color: #3d3d3d !important;
}
pre {
background: #2d2d2d !important;
border-color: #3d3d3d !important;
color: #e0e0e0 !important;
code {
background: transparent !important;
border: none !important;
color: #e0e0e0 !important;
}
}
blockquote {
border-color: #4f84ff !important;
background: #2d2d2d !important;
color: #b0b0b0 !important;
}
table {
border-color: #3d3d3d !important;
th, td {
border-color: #3d3d3d !important;
background: #1a1a1a !important;
color: #e0e0e0 !important;
}
th {
background: #2d2d2d !important;
}
}
ul, ol {
color: #e0e0e0 !important;
padding-left: 24px !important;
margin: 8px 0;
}
li {
color: #e0e0e0 !important;
line-height: 1.8 !important;
margin: 4px 0;
}
hr {
border-color: #3d3d3d !important;
}
img {
max-width: 100% !important;
border-radius: 4px !important;
margin: 8px 0;
}
}
}
}
.toolbar-container {
border-bottom: 1px solid var(--border-color-lighter);
background-color: var(--fill-color-light);
border-bottom: 1px solid #dcdfe6;
background-color: #f5f7fa;
transition: all 0.3s ease;
}
.editor-container {
min-height: 400px;
background-color: var(--fill-color-blank);
background-color: #ffffff;
transition: background-color 0.3s ease;
}
</style>
<style lang="less">
// 全局样式:WangEditor 编辑器主题适配
.wang-editor-wrapper {
// 工具栏样式
:deep(.w-e-toolbar) {
background-color: var(--fill-color-light) !important;
border-bottom-color: var(--border-color-lighter) !important;
border-bottom: 1px solid var(--border-color-lighter) !important;
// 工具栏按钮项
.w-e-bar-item {
button {
color: var(--text-color-primary) !important;
background-color: transparent !important;
border: none !important;
transition: all 0.2s ease !important;
&:hover:not(.disabled) {
background-color: var(--fill-color) !important;
color: var(--primary-color) !important;
}
&:active:not(.disabled) {
background-color: var(--fill-color-dark) !important;
}
&.active {
background-color: var(--fill-color-dark) !important;
color: var(--primary-color) !important;
}
&.disabled {
color: var(--text-color-disabled) !important;
cursor: not-allowed !important;
opacity: 0.5 !important;
}
}
// 按钮组分隔线
&::after {
background-color: var(--border-color-lighter) !important;
}
}
// 工具栏分割线
.w-e-bar-divider {
background-color: var(--border-color-lighter) !important;
}
}
// 编辑器文本容器
:deep(.w-e-text-container) {
background-color: var(--el-bg-color) !important;
color: var(--text-color-primary) !important;
border: none !important;
// 编辑器内容区域
.w-e-text {
color: var(--text-color-primary) !important;
background-color: transparent !important;
min-height: 400px !important;
// 编辑器焦点状态
&:focus {
outline: none !important;
}
// 编辑器内的段落
p {
color: var(--text-color-primary) !important;
margin: 0.5em 0 !important;
}
// 编辑器内的标题
h1,
h2,
h3,
h4,
h5,
h6 {
color: var(--text-color-primary) !important;
font-weight: 600 !important;
}
// 编辑器内的链接
a {
color: var(--primary-color) !important;
text-decoration: underline !important;
&:hover {
color: var(--primary-color) !important;
opacity: 0.8 !important;
}
}
// 编辑器内的代码
code {
background-color: var(--fill-color-light) !important;
color: var(--text-color-primary) !important;
border: 1px solid var(--border-color-lighter) !important;
padding: 2px 6px !important;
border-radius: 3px !important;
}
// 编辑器内的代码块
pre {
background-color: var(--fill-color-light) !important;
border: 1px solid var(--border-color-lighter) !important;
color: var(--text-color-primary) !important;
border-radius: 4px !important;
code {
background-color: transparent !important;
border: none !important;
padding: 0 !important;
}
}
// 编辑器内的引用
blockquote {
border-left: 4px solid var(--border-color) !important;
background-color: var(--fill-color-light) !important;
color: var(--text-color-primary) !important;
padding: 0.6em 1.2em !important;
margin: 1em 0 !important;
}
// 编辑器内的表格
table {
border-collapse: collapse !important;
border: 1px solid var(--border-color-lighter) !important;
th,
td {
border: 1px solid var(--border-color-lighter) !important;
background-color: var(--fill-color-blank) !important;
color: var(--text-color-primary) !important;
}
th {
background-color: var(--fill-color-light) !important;
}
}
// 编辑器内的列表
ul,
ol {
color: var(--text-color-primary) !important;
}
li {
color: var(--text-color-primary) !important;
}
}
// 占位符样式
.placeholder {
color: var(--text-color-placeholder) !important;
}
}
// 菜单下拉框
:deep(.w-e-drop-panel) {
background-color: var(--bg-color-overlay) !important;
border: 1px solid var(--border-color) !important;
box-shadow: var(--box-shadow) !important;
color: var(--text-color-primary) !important;
.w-e-list-item {
color: var(--text-color-primary) !important;
transition: all 0.2s ease !important;
&:hover {
background-color: var(--fill-color-light) !important;
color: var(--text-color-primary) !important;
}
&.selected {
background-color: var(--fill-color-light) !important;
color: var(--primary-color) !important;
}
&.disabled {
color: var(--text-color-disabled) !important;
cursor: not-allowed !important;
&:hover {
background-color: transparent !important;
}
}
}
// 分隔线
.w-e-drop-panel-divider {
background-color: var(--border-color-lighter) !important;
}
}
// 工具栏下拉菜单
:deep(.w-e-toolbar-menu) {
background-color: var(--bg-color-overlay) !important;
border: 1px solid var(--border-color) !important;
box-shadow: var(--box-shadow) !important;
.w-e-menu-item {
color: var(--text-color-primary) !important;
&:hover {
background-color: var(--fill-color-light) !important;
color: var(--text-color-primary) !important;
}
&.active {
background-color: var(--fill-color-light) !important;
color: var(--primary-color) !important;
}
}
}
// 模态框
:deep(.w-e-modal) {
background-color: var(--bg-color-overlay) !important;
border: 1px solid var(--border-color) !important;
box-shadow: var(--box-shadow) !important;
.w-e-modal-header {
border-bottom: 1px solid var(--border-color-lighter) !important;
color: var(--text-color-primary) !important;
}
.w-e-modal-body {
background-color: var(--bg-color-overlay) !important;
color: var(--text-color-primary) !important;
input,
textarea,
select {
background-color: var(--fill-color-blank) !important;
border-color: var(--border-color) !important;
color: var(--text-color-primary) !important;
&:focus {
border-color: var(--primary-color) !important;
}
}
}
.w-e-modal-footer {
border-top: 1px solid var(--border-color-lighter) !important;
button {
background-color: var(--fill-color-blank) !important;
border-color: var(--border-color) !important;
color: var(--text-color-primary) !important;
&:hover {
background-color: var(--fill-color-light) !important;
border-color: var(--primary-color) !important;
color: var(--primary-color) !important;
}
}
}
}
// 工具栏图标颜色
:deep(.w-e-bar-item svg),
:deep(.w-e-bar-item .w-e-icon) {
fill: var(--text-color-primary) !important;
color: var(--text-color-primary) !important;
transition:
fill 0.2s ease,
color 0.2s ease !important;
}
:deep(.w-e-bar-item:hover:not(.disabled) svg),
:deep(.w-e-bar-item:hover:not(.disabled) .w-e-icon),
:deep(.w-e-bar-item.active svg),
:deep(.w-e-bar-item.active .w-e-icon) {
fill: var(--primary-color) !important;
color: var(--primary-color) !important;
}
// 工具栏按钮组
:deep(.w-e-bar-divider) {
background-color: var(--border-color-lighter) !important;
}
// 编辑区域边框
&.focused {
border-color: var(--primary-color) !important;
box-shadow: 0 0 0 1px var(--primary-color) inset !important;
}
}
:deep(.w-e-text-container) {
background-color: var(--el-bg-color) !important;
}
// 深色主题额外适配
[data-theme="dark"] {
.wang-editor-wrapper {
background-color: var(--fill-color-darker) !important;
border-color: var(--border-color) !important;
:deep(.w-e-text-container) {
background-color: var(--fill-color-darker) !important;
}
:deep(.w-e-text-container .w-e-text) {
background-color: var(--fill-color-darker) !important;
color: var(--text-color-primary) !important;
// 编辑器内的图片边框
img {
border: 1px solid var(--border-color-lighter) !important;
border-radius: 4px !important;
}
// 编辑器内的水平线
hr {
border-top-color: var(--border-color-lighter) !important;
}
// 编辑器内的表格行交替颜色
table tr:nth-child(even) {
background-color: var(--fill-color-extra-light) !important;
}
}
}
}
// Fix z-index for full-screen/maximize mode
:deep(.w-e-fullscreen),
:deep(.w-e-fullscreen *),
:deep(.w-e-modal),
:deep(.w-e-drop-panel) {
z-index: 99999 !important;
}
</style>