feat: add rich text help article editor
This commit is contained in:
220
admin-web/src/components/RichTextEditor.vue
Normal file
220
admin-web/src/components/RichTextEditor.vue
Normal file
@@ -0,0 +1,220 @@
|
||||
<script setup lang="ts">
|
||||
import "@wangeditor/editor/dist/css/style.css";
|
||||
import { computed, onBeforeUnmount, ref, shallowRef, watch } from "vue";
|
||||
import { Editor, Toolbar } from "@wangeditor/editor-for-vue";
|
||||
import type { IDomEditor, IEditorConfig, IToolbarConfig } from "@wangeditor/editor";
|
||||
import { ElMessage } from "element-plus";
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
modelValue: string;
|
||||
disabled?: boolean;
|
||||
minHeight?: number;
|
||||
uploadImage?: (file: File) => Promise<string>;
|
||||
}>(), {
|
||||
minHeight: 560,
|
||||
});
|
||||
|
||||
const emit = defineEmits<{
|
||||
(event: "update:modelValue", value: string): void;
|
||||
}>();
|
||||
|
||||
const mode = "default";
|
||||
const editorRef = shallowRef<IDomEditor>();
|
||||
const valueHtml = ref(props.modelValue || "");
|
||||
|
||||
const editorHeight = computed(() => `${props.minHeight}px`);
|
||||
|
||||
const toolbarConfig: Partial<IToolbarConfig> = {
|
||||
toolbarKeys: [
|
||||
"header2",
|
||||
"header3",
|
||||
"header4",
|
||||
"|",
|
||||
"bold",
|
||||
"italic",
|
||||
"underline",
|
||||
"through",
|
||||
"blockquote",
|
||||
"|",
|
||||
"bulletedList",
|
||||
"numberedList",
|
||||
"|",
|
||||
"insertLink",
|
||||
{
|
||||
key: "group-image",
|
||||
title: "图片",
|
||||
menuKeys: ["insertImage", "uploadImage"],
|
||||
},
|
||||
"|",
|
||||
"clearStyle",
|
||||
"undo",
|
||||
"redo",
|
||||
],
|
||||
};
|
||||
|
||||
const editorConfig: Partial<IEditorConfig> = {
|
||||
placeholder: "请输入文章正文,可使用标题、列表、引用、链接和图片。",
|
||||
scroll: true,
|
||||
MENU_CONF: {
|
||||
uploadImage: {
|
||||
maxFileSize: 5 * 1024 * 1024,
|
||||
allowedFileTypes: ["image/*"],
|
||||
async customUpload(file: File, insertFn: (url: string, alt?: string, href?: string) => void) {
|
||||
if (!file.type.startsWith("image/")) {
|
||||
ElMessage.error("仅支持上传图片文件");
|
||||
return;
|
||||
}
|
||||
if (!props.uploadImage) {
|
||||
ElMessage.error("图片上传接口未配置");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const url = await props.uploadImage(file);
|
||||
insertFn(url, file.name);
|
||||
ElMessage.success("图片已插入");
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
ElMessage.error("图片上传失败");
|
||||
}
|
||||
},
|
||||
},
|
||||
insertLink: {
|
||||
checkLink: (_text: string, url: string) => {
|
||||
if (!url.trim()) {
|
||||
return "链接地址不能为空";
|
||||
}
|
||||
return true;
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
(value) => {
|
||||
if (value === valueHtml.value) {
|
||||
return;
|
||||
}
|
||||
valueHtml.value = value || "";
|
||||
},
|
||||
);
|
||||
|
||||
watch(valueHtml, (value) => {
|
||||
emit("update:modelValue", value);
|
||||
});
|
||||
|
||||
watch(
|
||||
() => props.disabled,
|
||||
(disabled) => {
|
||||
const editor = editorRef.value;
|
||||
if (!editor) {
|
||||
return;
|
||||
}
|
||||
if (disabled) {
|
||||
editor.disable();
|
||||
return;
|
||||
}
|
||||
editor.enable();
|
||||
},
|
||||
);
|
||||
|
||||
function handleCreated(editor: IDomEditor) {
|
||||
editorRef.value = editor;
|
||||
if (props.disabled) {
|
||||
editor.disable();
|
||||
}
|
||||
}
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
editorRef.value?.destroy();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="rich-text-editor" :class="{ 'rich-text-editor--disabled': disabled }">
|
||||
<Toolbar class="rich-text-editor__toolbar" :editor="editorRef" :default-config="toolbarConfig" :mode="mode" />
|
||||
<Editor
|
||||
v-model="valueHtml"
|
||||
class="rich-text-editor__body"
|
||||
:style="{ height: editorHeight }"
|
||||
:default-config="editorConfig"
|
||||
:mode="mode"
|
||||
@on-created="handleCreated"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.rich-text-editor {
|
||||
overflow: hidden;
|
||||
border: 1px solid var(--admin-border);
|
||||
border-radius: 8px;
|
||||
background: #fff;
|
||||
box-shadow: 0 14px 36px rgba(34, 28, 18, 0.06);
|
||||
--w-e-toolbar-bg-color: #fff;
|
||||
--w-e-toolbar-color: var(--admin-text-main);
|
||||
--w-e-toolbar-active-bg-color: rgba(200, 164, 93, 0.14);
|
||||
--w-e-toolbar-active-color: var(--admin-text-main);
|
||||
--w-e-toolbar-border-color: var(--admin-border);
|
||||
--w-e-textarea-bg-color: #fff;
|
||||
--w-e-textarea-color: var(--admin-text-main);
|
||||
--w-e-textarea-selected-border-color: rgba(200, 164, 93, 0.55);
|
||||
--w-e-textarea-slight-bg-color: #fbf7ef;
|
||||
--w-e-textarea-slight-color: #9aa0aa;
|
||||
--w-e-modal-button-bg-color: #fff;
|
||||
--w-e-modal-button-border-color: var(--admin-border);
|
||||
}
|
||||
|
||||
.rich-text-editor--disabled {
|
||||
opacity: 0.74;
|
||||
}
|
||||
|
||||
.rich-text-editor__toolbar {
|
||||
border-bottom: 1px solid var(--admin-border);
|
||||
background: linear-gradient(180deg, #fff 0%, #fbfbfc 100%);
|
||||
}
|
||||
|
||||
:deep(.w-e-toolbar) {
|
||||
padding: 7px 10px;
|
||||
}
|
||||
|
||||
:deep(.w-e-bar-item button) {
|
||||
border-radius: 6px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
:deep(.w-e-bar-item button:hover) {
|
||||
background: rgba(200, 164, 93, 0.14);
|
||||
}
|
||||
|
||||
:deep(.w-e-text-container) {
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
:deep(.w-e-text-placeholder) {
|
||||
color: #9aa0aa;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
:deep(.w-e-scroll) {
|
||||
padding: 24px 30px;
|
||||
}
|
||||
|
||||
:deep(.w-e-text-container [data-slate-editor]) {
|
||||
color: var(--admin-text-main);
|
||||
font-size: 16px;
|
||||
line-height: 1.9;
|
||||
}
|
||||
|
||||
:deep(.w-e-text-container [data-slate-editor] h2),
|
||||
:deep(.w-e-text-container [data-slate-editor] h3),
|
||||
:deep(.w-e-text-container [data-slate-editor] h4) {
|
||||
color: var(--admin-text);
|
||||
line-height: 1.45;
|
||||
}
|
||||
|
||||
:deep(.w-e-text-container [data-slate-editor] img) {
|
||||
border-radius: 8px;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user