189 lines
6.0 KiB
Vue
189 lines
6.0 KiB
Vue
<template>
|
||
<div :class="['editor-box', self_disabled ? 'editor-disabled' : '']">
|
||
<Toolbar v-if="!hideToolBar" class="editor-toolbar" :editor="editorRef" :default-config="toolbarConfig" :mode="mode" />
|
||
<Editor
|
||
v-model="valueHtml"
|
||
class="editor-content"
|
||
:style="{ height }"
|
||
:mode="mode"
|
||
:default-config="editorConfig"
|
||
@on-created="handleCreated"
|
||
@on-blur="handleBlur"
|
||
/>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup lang="ts" name="WangEditor">
|
||
import { nextTick, computed, inject, shallowRef, onBeforeUnmount } from "vue";
|
||
import { IToolbarConfig, IEditorConfig } from "@wangeditor/editor";
|
||
import { Editor, Toolbar } from "@wangeditor/editor-for-vue";
|
||
import { uploadImg, uploadVideo } from "@/api/modules/upload";
|
||
import "@wangeditor/editor/dist/css/style.css";
|
||
import { formContextKey, formItemContextKey } from "element-plus";
|
||
// import { cloneDeep } from "lodash-es";
|
||
// 富文本 DOM 元素
|
||
const editorRef = shallowRef();
|
||
|
||
// 实列化编辑器
|
||
const handleCreated = (editor: any) => {
|
||
editorRef.value = editor;
|
||
};
|
||
|
||
console.log(editorRef.value, "=editorRef.value=");
|
||
const $router = useRouter();
|
||
const routerValueName: string = $router.currentRoute.value.name as string;
|
||
const routerObj: any = {
|
||
articleEditIndex: "article",
|
||
productEditIndex: "product",
|
||
bannerListIndex: "banner",
|
||
downloadListIndex: "download",
|
||
videoListIndex: "video",
|
||
QAListIndex: "QA"
|
||
};
|
||
const routerName = ref(routerObj[routerValueName]);
|
||
// 接收父组件参数,并设置默认值
|
||
interface RichEditorProps {
|
||
value: any; // 富文本值 ==> 必传
|
||
toolbarConfig?: Partial<IToolbarConfig>; // 工具栏配置 ==> 非必传(默认为空)
|
||
editorConfig?: Partial<IEditorConfig>; // 编辑器配置 ==> 非必传(默认为空)
|
||
height?: string; // 富文本高度 ==> 非必传(默认为 500px)
|
||
mode?: "default" | "simple"; // 富文本模式 ==> 非必传(默认为 default)
|
||
hideToolBar?: boolean; // 是否隐藏工具栏 ==> 非必传(默认为false)
|
||
disabled?: boolean; // 是否禁用编辑器 ==> 非必传(默认为false)
|
||
}
|
||
const props = withDefaults(defineProps<RichEditorProps>(), {
|
||
toolbarConfig: () => {
|
||
return {
|
||
excludeKeys: []
|
||
};
|
||
},
|
||
|
||
editorConfig: () => {
|
||
return {
|
||
placeholder: "请输入内容...",
|
||
MENU_CONF: {}
|
||
// parseElemHtml: (elemHtml: string, elem: HTMLElement, editor: any) => {
|
||
// console.log(editor, "========editor=========");
|
||
// console.log(elem.tagName, "=elem.tagName=");
|
||
// if (elem.tagName === "SPAN" && elem.querySelector("img")) {
|
||
// // 确保 span 元素可以正确显示 img
|
||
// elem.style.display = "inline-block";
|
||
// }
|
||
// return elemHtml;
|
||
// }
|
||
};
|
||
},
|
||
|
||
height: "500px",
|
||
mode: "default",
|
||
hideToolBar: false,
|
||
disabled: false
|
||
});
|
||
|
||
// 获取 el-form 组件上下文
|
||
const formContext = inject(formContextKey, void 0);
|
||
// 获取 el-form-item 组件上下文
|
||
const formItemContext = inject(formItemContextKey, void 0);
|
||
// 判断是否禁用上传和删除
|
||
const self_disabled = computed(() => {
|
||
return props.disabled || formContext?.disabled;
|
||
});
|
||
|
||
// 判断当前富文本编辑器是否禁用
|
||
if (self_disabled.value) nextTick(() => editorRef.value.disable());
|
||
|
||
// 富文本的内容监听,触发父组件改变,实现双向数据绑定
|
||
type EmitProps = {
|
||
(e: "update:value", val: string): void;
|
||
(e: "check-validate"): void;
|
||
};
|
||
|
||
const emit = defineEmits<EmitProps>();
|
||
|
||
const valueHtml = computed({
|
||
get() {
|
||
return props.value;
|
||
},
|
||
set(val: string) {
|
||
// 防止富文本内容为空时,校验失败
|
||
if (editorRef.value.isEmpty()) val = "";
|
||
emit("update:value", val);
|
||
}
|
||
});
|
||
|
||
/**
|
||
* @description 图片自定义上传
|
||
* @param file 上传的文件
|
||
* @param insertFn 上传成功后的回调函数(插入到富文本编辑器中)
|
||
* */
|
||
type InsertFnTypeImg = (url: string, alt?: string, href?: string) => void;
|
||
props.editorConfig.MENU_CONF!["uploadImage"] = {
|
||
async customUpload(file: File, insertFn: InsertFnTypeImg) {
|
||
if (!uploadImgValidate(file)) return;
|
||
let formData = new FormData();
|
||
formData.append("image", file);
|
||
|
||
try {
|
||
const result = await uploadImg(formData, routerName.value);
|
||
if (result?.code === 0) {
|
||
const { data } = result;
|
||
insertFn(import.meta.env.VITE_APP_API_BASE_UPLOAD_URL + data.path);
|
||
}
|
||
} catch (error) {
|
||
console.log(error);
|
||
}
|
||
}
|
||
};
|
||
|
||
// 图片上传前判断
|
||
const uploadImgValidate = (file: File): boolean => {
|
||
console.log(file);
|
||
return true;
|
||
};
|
||
|
||
/**
|
||
* @description 视频自定义上传
|
||
* @param file 上传的文件
|
||
* @param insertFn 上传成功后的回调函数(插入到富文本编辑器中)
|
||
* */
|
||
type InsertFnTypeVideo = (url: string, poster?: string) => void;
|
||
props.editorConfig.MENU_CONF!["uploadVideo"] = {
|
||
async customUpload(file: File, insertFn: InsertFnTypeVideo) {
|
||
if (!uploadVideoValidate(file)) return;
|
||
let formData = new FormData();
|
||
formData.append("file", file);
|
||
try {
|
||
const { data } = await uploadVideo(formData);
|
||
insertFn(data.fileUrl);
|
||
} catch (error) {
|
||
console.log(error);
|
||
}
|
||
}
|
||
};
|
||
|
||
// 视频上传前判断
|
||
const uploadVideoValidate = (file: File): boolean => {
|
||
console.log(file);
|
||
return true;
|
||
};
|
||
|
||
// 编辑框失去焦点时触发
|
||
const handleBlur = () => {
|
||
formItemContext?.prop && formContext?.validateField([formItemContext.prop as string]);
|
||
};
|
||
|
||
// 组件销毁时,也及时销毁编辑器
|
||
onBeforeUnmount(() => {
|
||
if (!editorRef.value) return;
|
||
editorRef.value.destroy();
|
||
});
|
||
|
||
defineExpose({
|
||
editor: editorRef
|
||
});
|
||
</script>
|
||
|
||
<style scoped lang="scss">
|
||
@import "./index.scss";
|
||
</style>
|