|
@@ -0,0 +1,283 @@
|
|
|
+<template>
|
|
|
+ <div
|
|
|
+ class="rich-text-editor"
|
|
|
+ :style="{ border: border ? '1px solid #ccc' : '0px solid #ccc' }"
|
|
|
+ :key="JSON.stringify(readOnly)"
|
|
|
+ >
|
|
|
+ <Toolbar
|
|
|
+ v-show="showToolbar"
|
|
|
+ ref="toolbarRef"
|
|
|
+ class="rich-text-editor-toolbar"
|
|
|
+ :editor="editor"
|
|
|
+ :defaultConfig="toolbarConfig"
|
|
|
+ :mode="mode"
|
|
|
+ :style="toolbarConfig.style"
|
|
|
+ />
|
|
|
+ <Editor
|
|
|
+ ref="editorRef"
|
|
|
+ class="rich-text-editor-editor"
|
|
|
+ v-model="html"
|
|
|
+ :defaultConfig="editorConfig"
|
|
|
+ :mode="mode"
|
|
|
+ style="overflow-y: auto"
|
|
|
+ :style="editorConfig.style"
|
|
|
+ @onCreated="onCreated"
|
|
|
+ @onMaxLength="onMaxLength"
|
|
|
+ @onChange="onChange"
|
|
|
+ @customPaste="customPaste"
|
|
|
+ @customAlert="customAlert"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script>
|
|
|
+import { Editor, Toolbar } from "@wangeditor-next/editor-for-vue2";
|
|
|
+
|
|
|
+import {
|
|
|
+ initEditorConfig,
|
|
|
+ initToolbarConfig,
|
|
|
+ initUploadImage,
|
|
|
+} from "./initData";
|
|
|
+
|
|
|
+import "@wangeditor-next/editor/dist/css/style.css";
|
|
|
+
|
|
|
+export default {
|
|
|
+ components: { Editor, Toolbar },
|
|
|
+ props: {
|
|
|
+ value: {
|
|
|
+ type: String,
|
|
|
+ default: "",
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 主题
|
|
|
+ * default 默认 | simple 简约
|
|
|
+ * 工具栏的配置也会有所不同
|
|
|
+ */
|
|
|
+ mode: {
|
|
|
+ type: String,
|
|
|
+ default: "simple",
|
|
|
+ },
|
|
|
+
|
|
|
+ /** 是否显示边框 */
|
|
|
+ border: {
|
|
|
+ type: Boolean,
|
|
|
+ default: true,
|
|
|
+ },
|
|
|
+
|
|
|
+ /** 是否只读 */
|
|
|
+ readOnly: {
|
|
|
+ type: Boolean,
|
|
|
+ default: initToolbarConfig.readOnly,
|
|
|
+ },
|
|
|
+
|
|
|
+ placeholder: {
|
|
|
+ type: String,
|
|
|
+ default: "请输入内容...",
|
|
|
+ },
|
|
|
+
|
|
|
+ /** 是否显示工具栏 */
|
|
|
+ showToolbar: {
|
|
|
+ type: Boolean,
|
|
|
+ default: true,
|
|
|
+ },
|
|
|
+
|
|
|
+ /** 工具栏配置 */
|
|
|
+ toolbarConfig: {
|
|
|
+ type: Object,
|
|
|
+ default: () => initToolbarConfig,
|
|
|
+ },
|
|
|
+
|
|
|
+ /** 编辑器配置 */
|
|
|
+ defaultEditorConfig: {
|
|
|
+ type: Object,
|
|
|
+ default: () => initEditorConfig,
|
|
|
+ },
|
|
|
+ /** 自定义粘贴事件。可阻止编辑器的默认粘贴,实现自己的粘贴逻辑 */
|
|
|
+ customPaste: {
|
|
|
+ type: Function,
|
|
|
+ default(editor, event, callback) {
|
|
|
+ // console.log(editor.getConfig());
|
|
|
+ // console.log(editor.getConfig().MENU_CONF.uploadImage);
|
|
|
+ // // 阻止默认粘贴行为
|
|
|
+ // event.preventDefault()
|
|
|
+ // // 获取粘贴的HTML数据
|
|
|
+ // let clipboardData = event.clipboardData || window.clipboardData
|
|
|
+ // let pastedHtml = clipboardData.getData('text/html')
|
|
|
+ // if (pastedHtml) {
|
|
|
+ // // 处理粘贴内容,移除 <p><br></p> 标签
|
|
|
+ // pastedHtml = pastedHtml.replace(/<p><br><\/p>/g, '')
|
|
|
+ // // 插入处理后的内容到编辑器中
|
|
|
+ // editor.dangerouslyInsertHtml(pastedHtml)
|
|
|
+ // }
|
|
|
+ // callback(false)
|
|
|
+ },
|
|
|
+ },
|
|
|
+ /** 自定义编辑器 alert */
|
|
|
+ customAlert: {
|
|
|
+ type: Function,
|
|
|
+ default: (s, t) => {},
|
|
|
+ },
|
|
|
+ },
|
|
|
+ data() {
|
|
|
+ return {
|
|
|
+ editor: null,
|
|
|
+ html: "",
|
|
|
+ };
|
|
|
+ },
|
|
|
+ computed: {
|
|
|
+ editorConfig() {
|
|
|
+ // console.log(this.$props);
|
|
|
+ const config = this.$props.defaultEditorConfig;
|
|
|
+ const MENU_CONF = config.MENU_CONF;
|
|
|
+ const that = this;
|
|
|
+ // console.log({
|
|
|
+ // ...config,
|
|
|
+ // readOnly: Boolean(this.$props.readOnly),
|
|
|
+ // placeholder: this.$props.placeholder,
|
|
|
+ // MENU_CONF: {
|
|
|
+ // ...MENU_CONF,
|
|
|
+ // uploadImage: {
|
|
|
+ // onBeforeUpload(file) {
|
|
|
+ // const f = Object.values(file)[0];
|
|
|
+ // if (f.size > initUploadImage.base64LimitSize) {
|
|
|
+ // that.$message.error(initUploadImage.overflowErrMsg);
|
|
|
+ // }
|
|
|
+ // return false;
|
|
|
+ // },
|
|
|
+ // onError(file, err, res) {
|
|
|
+ // if (file.size > initUploadImage.base64LimitSize) {
|
|
|
+ // that.$message.error(initUploadImage.overflowErrMsg);
|
|
|
+ // }
|
|
|
+ // },
|
|
|
+ // ...MENU_CONF.uploadImage,
|
|
|
+ // },
|
|
|
+ // },
|
|
|
+ // });
|
|
|
+ return {
|
|
|
+ ...config,
|
|
|
+ readOnly: Boolean(this.$props.readOnly),
|
|
|
+ placeholder: this.$props.placeholder,
|
|
|
+ MENU_CONF: {
|
|
|
+ ...MENU_CONF,
|
|
|
+ uploadImage: {
|
|
|
+ /** 上传之前触发 */
|
|
|
+ onBeforeUpload(file) {
|
|
|
+ const f = Object.values(file)[0];
|
|
|
+ if (f.size > initUploadImage.maxFileSize) {
|
|
|
+ that.$message.error(initUploadImage.overflowErrMsg);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ },
|
|
|
+ /** 上传错误,或者触发 timeout 超时 */
|
|
|
+ onError(file, err, res) {
|
|
|
+ const f = file;
|
|
|
+ if (f.size > initUploadImage.maxFileSize) {
|
|
|
+ that.$message.error(initUploadImage.overflowErrMsg);
|
|
|
+ }
|
|
|
+ },
|
|
|
+ /** 上传失败 */
|
|
|
+ onFailed(file, res) {
|
|
|
+ that.$message.error(`${file.name} 上传失败`);
|
|
|
+ },
|
|
|
+ ...MENU_CONF.uploadImage,
|
|
|
+ },
|
|
|
+ },
|
|
|
+ };
|
|
|
+ },
|
|
|
+ },
|
|
|
+ watch: {
|
|
|
+ html: {
|
|
|
+ handler(v) {
|
|
|
+ // console.log(this.$props);
|
|
|
+ // console.log("v", v);
|
|
|
+ // doSomething
|
|
|
+ this.pasteTableLineBreaker();
|
|
|
+ },
|
|
|
+ deep: true,
|
|
|
+ },
|
|
|
+ value: {
|
|
|
+ handler(v) {
|
|
|
+ if (v != this.html) {
|
|
|
+ this.html = v || "";
|
|
|
+ }
|
|
|
+ },
|
|
|
+ deep: true,
|
|
|
+ immediate: true,
|
|
|
+ },
|
|
|
+ },
|
|
|
+ methods: {
|
|
|
+ /** 编辑器创建完毕时的回调函数 */
|
|
|
+ onCreated(editor) {
|
|
|
+ this.editor = Object.seal(editor); // 一定要用 Object.seal() ,否则会报错
|
|
|
+ this.$nextTick(() => {
|
|
|
+ // console.log(editor.getConfig())
|
|
|
+ // const toolbar = DomEditor.getToolbar(this.editor)
|
|
|
+ // const curToolbarConfig = toolbar.getConfig()
|
|
|
+ // console.log('【 curToolbarConfig 】-39', curToolbarConfig)
|
|
|
+ });
|
|
|
+ },
|
|
|
+ /** 编辑器内容、选区变化时的回调函数 */
|
|
|
+ onChange(editor) {
|
|
|
+ if (this.html != this.$props.value) {
|
|
|
+ // console.log(this.html);
|
|
|
+ this.$emit("input", this.html == "<p><br></p>" ? "" : this.html);
|
|
|
+ this.$emit("onChange", editor);
|
|
|
+ }
|
|
|
+ },
|
|
|
+ /**
|
|
|
+ * 编辑器销毁时的回调函数
|
|
|
+ * 调用 editor.destroy() 即可销毁编辑器,详见 API
|
|
|
+ */
|
|
|
+ onDestroyed(editor) {
|
|
|
+ this.$emit("onDestroy", editor);
|
|
|
+ },
|
|
|
+ /** 编辑器 focus 时的回调函数 */
|
|
|
+ onFocus(editor) {
|
|
|
+ this.$emit("onFocus", editor);
|
|
|
+ },
|
|
|
+ /** 编辑器 blur 时的回调函数 */
|
|
|
+ onBlur(editor) {
|
|
|
+ this.$emit("onBlur", editor);
|
|
|
+ },
|
|
|
+ /** 文本长度达到指定长度 */
|
|
|
+ onMaxLength(editor) {
|
|
|
+ this.$emit("onMaxLength", editor);
|
|
|
+ },
|
|
|
+
|
|
|
+ /** 处理粘贴表格的时候生成一个换行 */
|
|
|
+ pasteTableLineBreaker() {
|
|
|
+ if (/<p><br><\/p><table/g.test(this.html)) {
|
|
|
+ let filteredHtml = this.html.replace(/<p><br><\/p><table/g, "<table");
|
|
|
+ filteredHtml = filteredHtml.replace(
|
|
|
+ /<\/table><p><br><\/p>/g,
|
|
|
+ "</table>"
|
|
|
+ );
|
|
|
+ this.editor.clear();
|
|
|
+ this.editor.dangerouslyInsertHtml(filteredHtml);
|
|
|
+ this.onChange(this.editor);
|
|
|
+ }
|
|
|
+ },
|
|
|
+ },
|
|
|
+ beforeDestroy() {
|
|
|
+ const editor = this.editor;
|
|
|
+ if (editor == null) return;
|
|
|
+ editor.destroy(); // 组件销毁时,及时销毁编辑器
|
|
|
+ },
|
|
|
+};
|
|
|
+</script>
|
|
|
+<style lang="scss">
|
|
|
+.rich-text-editor {
|
|
|
+ display: flex;
|
|
|
+ width: 100%;
|
|
|
+ height: 400px;
|
|
|
+ flex-direction: column;
|
|
|
+
|
|
|
+ .rich-text-editor-toolbar {
|
|
|
+ border-bottom: 1px solid #ccc;
|
|
|
+ }
|
|
|
+ .rich-text-editor-editor {
|
|
|
+ flex: 1;
|
|
|
+ }
|
|
|
+}
|
|
|
+</style>
|