vue3+ts 富文本使用(WangEditor插件)

<template>
	<div :class="['editor-box', disabled ? 'editor-disabled' : '']">
		<Toolbar class="editor-toolbar" :editor="editorRef" :defaultConfig="toolbarConfig" :mode="mode" v-if="!hideToolBar" />
		<Editor
			:style="{ height }"
			class="editor-content'"
			v-model="valueHtml"
			:defaultConfig="editorConfig"
			:mode="mode"
			@on-created="handleCreated"
			@on-blur="handleBlur"
		/>
	</div>
</template>

<script setup lang="ts" name="WangEditor">
import { uploadFile as uploadFun } from "@/api/modules/upload";
import { nextTick, computed, shallowRef, onBeforeUnmount } from "vue";
import { IToolbarConfig, IEditorConfig } from "@wangeditor/editor";
import { Editor, Toolbar } from "@wangeditor/editor-for-vue";
import { ElMessage } from "element-plus";
// import { uploadVideo } from "@/api/modules/upload";
import "@wangeditor/editor/dist/css/style.css";

// 富文本 DOM 元素
const editorRef = shallowRef();

// 实列化编辑器
const handleCreated = (editor: any) => {
	editorRef.value = editor;
};

// 接收父组件参数,并设置默认值
interface RichEditorProps {
	value: string; // 富文本值 ==> 必传
	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: [
				//移除代码块
				"code"
			]
		};
	},
	editorConfig: () => {
		return {
			placeholder: "请输入内容...",
			MENU_CONF: {}
		};
	},
	height: "500px",
	mode: "default",
	hideToolBar: false,
	disabled: false
});

// 判断当前富文本编辑器是否禁用
if (props.disabled) 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: any) {
		// 防止富文本内容为空时,校验失败
		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("file", file);
		try {
			const { data } = await uploadFun(formData);
			insertFn("/api/dems-resource/file/downloadFileByFileId?fileId=" + data.id);
		} 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 uploadFun(formData);
			insertFn("/api/dems-resource/file/loadOnlineVideo?id=" + data.id);
		} catch (error) {
			console.log(error);
		}
	}
};

// 视频上传前判断
const uploadVideoValidate = (file: File) => {
	if (file.size / 1024 / 1024 > 100) {
		ElMessage.error("视频大小不能超过100M");
		return false;
	}
	return true;
};

// 编辑框失去焦点时触发
const handleBlur = () => {
	emit("check-validate");
};

// 组件销毁时,也及时销毁编辑器
onBeforeUnmount(() => {
	if (!editorRef.value) return;
	editorRef.value.destroy();
});

defineExpose({
	editor: editorRef
});
</script>

<style scoped lang="scss">
@import "./index.scss";
</style>

文件上传用到的接口

//上传文件
export const uploadFile = (params: FormData) => {
	return http.upload<Upload.ResFileList>(UPLOAD_FILE + `/file/uploadFile`, params, {
		headers: { "Content-Type": "multipart/form-data" }
	});
};

如果需要转义,封装组件如下

import { EVENT_PORT } from "@/api/config/servicePort";
import { MANAGE_PORT } from "@/api/config/servicePort";
//转义的数组,key是接口链接,value是要转义的字段。因为可能有多个富文本,所以是数组
const zyapi: any = {
	[`${EVENT_PORT}/接口名字/名字`]: ["content"],
};
//反转义的数组,key是接口链接,value是要转义的字段。因为可能有多个富文本,所以是数组
const fzyapi: any = {
	[`${EVENT_PORT}/接口名字/名字`]: ["content"],
};

/*1.用浏览器内部转换器实现html编码(转义)*/
const encode = (html: any) => {
	//1.首先动态创建一个容器标签元素,如DIV
	let temp: any = document.createElement("div");
	//2.然后将要转换的字符串设置为这个元素的innerText或者textContent
	temp.textContent != undefined ? (temp.textContent = html) : (temp.innerText = html);
	//3.最后返回这个元素的innerHTML,即得到经过HTML编码转换的字符串了
	let output = temp.innerHTML;
	temp = null;
	return output;
};

/*2.用浏览器内部转换器实现html解码(反转义)*/
const decode = (text: any) => {
	//1.首先动态创建一个容器标签元素,如DIV
	let temp: any = document.createElement("div");
	//2.然后将要转换的字符串设置为这个元素的innerHTML(ie,火狐,google都支持)
	temp.innerHTML = text;
	//3.最后返回这个元素的innerText或者textContent,即得到经过HTML解码的字符串了。
	let output = temp.innerText || temp.textContent;
	temp = null;
	return output;
};

//转义
export function encodeHtml(config: any) {
	//从zyapi获取要转义的字段
	let arr = zyapi[config.url] || [];
	for (let i = 0; i < arr.length; i++) {
		let sp = arr[i].split(".");
		if (sp.length == 2) {
			if (config.data[sp[0]]) {
				config.data[sp[0]][sp[1]] = encode(config.data[sp[0]][sp[1]]);
			}
		} else {
			config.data[arr[i]] = encode(config.data[arr[i]]);
		}
	}
}
//反转义
export function decodeHtml(data: any, url: any) {
	//从fzyapi获取要转义的字段
	let arr = fzyapi[url] || [];
	for (let i = 0; i < arr.length; i++) {
		let sp = arr[i].split(".");
		if (sp.length == 2) {
			if (data.data[sp[0]]) {
				data.data[sp[0]][sp[1]] = decode(data.data[sp[0]][sp[1]]);
			}
		} else {
			data.data[arr[i]] = decode(data.data[arr[i]]);
		}
	}
}

页面使用

引入组件

import wangEditor from "@/components/WangEditor/index.vue";

代码示例

					<wangEditor
							v-model:value="formInline.workorder!.eventDescription"
							:hideToolBar="hideToolBar"
							:disabled="isDisabled"
							@check-validate="validateFun"
							height="300px"
						></wangEditor>
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值