百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术分类 > 正文

用来用去还是用回了ueditor-Vue富文本编辑器二次扩展

ztj100 2024-12-19 17:58 49 浏览 0 评论

用来用去还是用回了ueditor-Vue富文本编辑器二次扩展。我们使用用过UEditor、TinyMCE、CKEditor、wangEditor、Tiptap、Quill项目经历过多次富文本的编辑器的选型使用,发现现在新的富文本编辑总感觉还是没达到我们的要求,果然改回了ueditor。




UEditor 是由百度WEB前端研发部开发的一款所见即所得的开源富文本编辑器。它具有轻量、可定制、用户体验优秀等特点,并且基于MIT开源协议(也有说法是基于BSD协议),所有源代码在协议允许范围内可自由修改和使用。UEditor的推出,有效降低了网站开发者的开发成本,节约了他们在开发富文本编辑器方面所需的大量时间。

一、系统特点

分层架构设计:UEditor在设计上采用了经典的分层架构设计理念,包括核心层、命令插件层和UI层,以实现功能层次之间的轻度耦合。

核心层:提供编辑器底层的方法和概念,如DOM树操作、Selection、Range等。

命令插件层:所有的功能型实现都通过这一层中的命令和插件来完成,各个命令和插件之间基本互不耦合,方便定制与扩展。

UI层:与核心层和命令插件层几乎完全解耦,简单的配置即可为编辑器在界面上添加额外的UI元素和功能。

二、优点

体积小巧,性能优良:UEditor在保证功能全面的同时,尽量减少了体积,提升了性能。

使用简单:对于开发者来说,UEditor易于上手,可以快速集成到项目中。

多浏览器支持:支持Mozilla、MSIE、FireFox、Maxthon、Safari和Chrome等多种浏览器。

丰富完善的中文文档:为开发者提供了详尽的中文文档,方便学习和使用。

vue-ueditor-wrap 是一个Vue组件,它封装了UEditor(百度富文本编辑器)以便更容易地在Vue项目中集成和使用。UEditor是一个功能强大的富文本编辑器,但直接将其集成到Vue项目中可能会遇到一些兼容性和维护性的问题,而vue-ueditor-wrap正是为了解决这些问题而设计的。

如何使用 vue-ueditor-wrap

安装

首先,你需要通过npm或yarn来安装vue-ueditor-wrap。打开你的终端或命令提示符,然后运行以下命令之一:

npm install vue-ueditor-wrap --save

yarn add vue-ueditor-wrap

扩展组件

接下来,在你的Vue组件中引入vue-ueditor-wrap。

<template>
	<div style="border: 1px solid #ccc">
		<vue-ueditor-wrap
			v-model="data"
			:config="editorConfig"
			:editorDependencies="['ueditor.config.js', 'ueditor.all.js']"
			ref="editorRef"
		></vue-ueditor-wrap>
		<diy-storage ref="storageImg" style="display: none" @confirm="getAttachmentFileList"></diy-storage>
		<diy-storage
			ref="storageVideo"
			type="video"
			accept=".rm,.rmvb,.wmv,.avi,.mpg,.mpeg,.mp4"
			style="display: none"
			@confirm="getAttachmentVideoFileList"
		></diy-storage>
	</div>
</template>

<script setup name="DiyEditor">
import { ref } from 'vue';
import { VueUeditorWrap } from 'vue-ueditor-wrap';
import { useVModel } from '@vueuse/core';
import DiyStorage from '@/components/upload/storage.vue';
import { Session } from '@/utils/storage';
const editorRef = ref();
const storageImg = ref(null);
const storageVideo = ref(null);

const props = defineProps({
	modelValue: {
		type: String,
		default: '',
	},
	height: {
		type: Number,
		default: 300,
	},
});

const emit = defineEmits(['update:modelValue', 'handleBlur']);
const data = useVModel(props, 'modelValue', emit);

const serverHeaders = {
	Authorization: Session.get('token'),
};
const editorConfig = ref({
	debug: false,
	UEDITOR_HOME_URL: import.meta.env.MODE == 'development' ? '/public/ueditor/' : import.meta.env.VITE_BUILD_DIR + '/ueditor/',
	serverUrl: (process.env.NODE_ENV === 'production' ? '' : '/api') + '/sys/storage/upload',
	serverHeaders,
	// 编辑器不自动被内容撑高
	autoHeightEnabled: false,
	// 初始容器高度
	initialFrameHeight: props.height,
	// 初始容器宽度
	initialFrameWidth: '100%',
	toolbarCallback: function (cmd, editor) {
		editorRef.value = editor;
		if (cmd == 'insertimage') {
			storageImg.value.handleStorageDlg('', '上传图片');
			return true;
		} else if (cmd == 'insertvideo') {
			storageVideo.value.handleStorageDlg('', '上传视频');
			return true;
		}
	},
});

const getAttachmentFileList = (files) => {
	if (!files.length) {
		return;
	}
	files.forEach((element) => {
		editorRef.value?.execCommand('insertHtml', "<img src='" + element.url + "'/>");
	});
};

const getAttachmentVideoFileList = (files) => {
	if (!files.length) {
		return;
	}
	files.forEach((element) => {
		editorRef.value?.execCommand('insertHtml', "<video src='" + element.url + "'/>");
	});
};
</script>

通过使用vue-ueditor-wrap,你可以轻松地将UEditor集成到你的Vue项目中,从而为用户提供一个功能丰富的文本编辑体验。

<template>
	<div class="container">
		<div class="el-card is-always-shadow diygw-col-24">
			<div class="el-card__body">
				<div class="mb8">
					<el-button type="primary" plain @click="onOpenAddModule"> <SvgIcon name="ele-Plus" />新增 </el-button> <el-button type="danger" plain :disabled="multiple" @click="onTabelRowDel"> <SvgIcon name="ele-Delete" />删除 </el-button>
				</div>
				<el-table @selection-change="handleSelectionChange" v-loading="loading" :data="tableData" stripe border current-row-key="id" empty-text="没有数据" style="width: 100%">
					<el-table-column type="selection" width="55" align="center"></el-table-column>
					<el-table-column label="姓名" prop="name" align="center"> </el-table-column>
					<el-table-column label="性别" prop="sex" align="center"> </el-table-column>
					<el-table-column label="邮箱" prop="email" align="center"> </el-table-column>
					<el-table-column label="操作" align="center" fixed="right" width="180">
						<template #default="scope">
							<el-button type="primary" plain size="small" @click="onOpenEditModule(scope.row)"> <SvgIcon name="ele-Edit" />修改 </el-button> <el-button v-if="scope.row.id != 0" type="danger" plain size="small" @click="onTabelRowDel(scope.row)"> <SvgIcon name="ele-Delete" />删除 </el-button>
						</template>
					</el-table-column>
				</el-table>
				<!-- 分页设置-->
				<div v-show="total > 0"><el-divider></el-divider> <el-pagination background :total="total" :current-page="queryParams.pageNum" :page-size="queryParams.pageSize" layout="total, sizes, prev, pager, next, jumper" @size-change="handleSizeChange" @current-change="handleCurrentChange" /></div>
			</div>
			<!-- 添加或修改参数配置对话框 -->
			<el-dialog :fullscreen="isFullscreen" width="800px" v-model="isShowDialog" destroy-on-close title="CRUD表格" draggable center>
				<template #header="{ close, titleId, titleClass }">
					<h4 :id="titleId" :class="titleClass">CRUD表格</h4>
					<el-tooltip v-if="!isFullscreen" content="最大化" placement="bottom">
						<el-button class="diygw-max-icon el-dialog__headerbtn">
							<el-icon @click="isFullscreen = !isFullscreen">
								<FullScreen />
							</el-icon>
						</el-button>
					</el-tooltip>
					<el-tooltip v-if="isFullscreen" content="返回" placement="bottom">
						<el-button class="diygw-max-icon el-dialog__headerbtn">
							<el-icon @click="isFullscreen = !isFullscreen">
								<Remove />
							</el-icon>
						</el-button>
					</el-tooltip>
				</template>
				<el-form class="flex flex-direction-row flex-wrap" :model="editForm" :rules="editFormRules" ref="editFormRef" label-width="80px">
					<div class="flex diygw-col-24">
						<el-form-item prop="editor" class="diygw-el-rate" label="多行输入">
							<diy-editor v-model="editForm.editor"></diy-editor>
						</el-form-item>
					</div>
				</el-form>
				<template #footer>
					<div class="dialog-footer flex justify-center"><el-button @click="closeDialog"> 取 消 </el-button> <el-button type="primary" @click="onSubmit" :loading="saveloading"> 保 存 </el-button></div>
				</template>
			</el-dialog>
		</div>
		<div class="clearfix"></div>
	</div>
</template>

<script>
	import { useRouter, useRoute } from 'vue-router';
	import { storeToRefs } from 'pinia';
	import { useUserInfo } from '@/stores/userInfo';

	import { ElMessageBox, ElMessage } from 'element-plus';
	import { deepClone, changeRowToForm } from '@/utils/other';
	import { addData, updateData, delData, listData } from '@/api';

	import DiyEditor from '@/components/editor/index.vue';

	export default {
		components: {
			DiyEditor
		},
		data() {
			return {
				globalOption: {},
				userInfo: {},
				// 遮罩层
				loading: true,
				// 选中数组
				ids: [],
				// 非单个禁用
				single: true,
				// 非多个禁用
				multiple: true,
				// 弹出层标题
				title: '',
				// 总条数
				total: 0,
				tableData: [],
				// 是否显示弹出层
				isFullscreen: false,
				isShowDialog: false,
				saveloading: false,
				editFormInit: {},
				queryParamsInit: {},

				queryParams: {
					pageNum: 1,
					pageSize: 10
				},

				queryParamsRules: {},

				editForm: {
					id: undefined,
					editor: ''
				},

				editFormRules: {}
			};
		},
		methods: {
			getParamData(e, row) {
				row = row || {};
				let dataset = e.currentTarget ? e.currentTarget.dataset : e;
				if (row) {
					dataset = Object.assign(dataset, row);
				}
				return dataset;
			},
			navigateTo(e, row) {
				let dataset = this.getParamData(e, row);
				let { type } = dataset;
				if (type == 'page' || type == 'inner' || type == 'href') {
					this.router.push({
						path: dataset.url,
						query: dataset
					});
				} else if (typeof this[type] == 'function') {
					this[type](dataset);
				} else {
					ElMessage.error('待实现点击事件');
				}
			},
			async init() {}, // 打开弹窗
			openDialog(row) {
				if (row.id && row.id != undefined && row.id != 0) {
					this.editForm = changeRowToForm(row, this.editForm);
				} else {
					this.initForm();
				}
				this.isShowDialog = true;
				this.saveloading = false;
			},

			// 关闭弹窗
			closeDialog(row) {
				this.mittBus.emit('onEditAdmintableModule', row);
				this.isShowDialog = false;
			},

			// 保存
			onSubmit() {
				const formWrap = this.$refs.editFormRef;
				if (!formWrap) return;
				formWrap.validate((valid) => {
					if (valid) {
						this.saveloading = true;
						if (this.editForm.id != undefined && this.editForm.id != 0) {
							updateData('/sys/user', this.editForm)
								.then(() => {
									ElMessage.success('修改成功');
									this.saveloading = false;
									this.closeDialog(this.editForm); // 关闭弹窗
								})
								.finally(() => {
									this.saveloading = false;
								});
						} else {
							addData('/sys/user', this.editForm)
								.then(() => {
									ElMessage.success('新增成功');
									this.saveloading = false;
									this.closeDialog(this.editForm); // 关闭弹窗
								})
								.finally(() => {
									this.saveloading = false;
								});
						}
					} else {
						ElMessage.error('验证未通过!');
					}
				});
			},
			// 表单初始化,方法:`resetFields()` 无法使用
			initForm() {
				this.editForm = deepClone(this.editFormInit);
			},
			/** 查询CRUD表格列表 */
			handleQuery() {
				this.loading = true;
				listData('/sys/user', this.queryParams).then((response) => {
					this.tableData = response.rows;
					this.total = response.total;
					this.loading = false;
				});
			},
			/** 重置按钮操作 */
			resetQuery() {
				this.queryParams = deepClone(this.queryParamsInit);
				this.handleQuery();
			},

			// 打开新增CRUD表格弹窗
			onOpenAddModule(row) {
				row = [];
				this.title = '添加CRUD表格';
				this.openDialog(row);
			},
			// 打开编辑CRUD表格弹窗
			onOpenEditModule(row) {
				this.title = '修改CRUD表格';
				this.initForm();
				this.openDialog(row);
			},
			/** 删除按钮操作 */
			onTabelRowDel(row) {
				let thiz = this;
				const id = row.id || this.ids;
				ElMessageBox({
					message: '是否确认删除选中的CRUD表格?',
					title: '警告',
					showCancelButton: true,
					confirmButtonText: '确定',
					cancelButtonText: '取消',
					type: 'warning'
				}).then(function () {
					return delData('/sys/user', id).then(() => {
						thiz.handleQuery();
						ElMessage.success('删除成功');
					});
				});
			},
			// 多选框选中数据
			handleSelectionChange(selection) {
				this.ids = selection.map((item) => item.id);
				this.single = selection.length != 1;
				this.multiple = !selection.length;
			},

			//分页页面大小发生变化
			handleSizeChange(val) {
				this.queryParams.pageSize = val;
				this.handleQuery();
			},
			//当前页码发生变化
			handleCurrentChange(val) {
				this.queryParams.pageNum = val;
				this.handleQuery();
			}
		},
		async mounted() {
			this.router = useRouter();
			this.globalOption = useRoute().query;
			const stores = useUserInfo();
			const { userInfos } = storeToRefs(stores);
			this.userInfo = userInfos;
			await this.init();
			this.editFormInit = deepClone(this.editForm);
			this.queryParamsInit = deepClone(this.queryParams);
			this.handleQuery();
			this.mittBus.on('onEditAdmintableModule', () => {
				this.handleQuery();
			});
		},
		beforeUnmount() {
			this.mittBus.off('onEditAdmintableModule');
		}
	};
</script>

<style lang="scss" scoped>
	.container {
		font-size: 12px;
	}
</style>

扩展组件代码已开源至https://gitee.com/diygw/diygw-ui-admin

大家可以前往下载

相关推荐

SpringBoot整合SpringSecurity+JWT

作者|Sans_https://juejin.im/post/5da82f066fb9a04e2a73daec一.说明SpringSecurity是一个用于Java企业级应用程序的安全框架,主要包含...

「计算机毕设」一个精美的JAVA博客系统源码分享

前言大家好,我是程序员it分享师,今天给大家带来一个精美的博客系统源码!可以自己买一个便宜的云服务器,当自己的博客网站,记录一下自己学习的心得。开发技术博客系统源码基于SpringBoot,shiro...

springboot教务管理系统+微信小程序云开发附带源码

今天给大家分享的程序是基于springboot的管理,前端是小程序,系统非常的nice,不管是学习还是毕设都非常的靠谱。本系统主要分为pc端后台管理和微信小程序端,pc端有三个角色:管理员、学生、教师...

SpringBoot+LayUI后台管理系统开发脚手架

源码获取方式:关注,转发之后私信回复【源码】即可免费获取到!项目简介本项目本着避免重复造轮子的原则,建立一套快速开发JavaWEB项目(springboot-mini),能满足大部分后台管理系统基础开...

Spring Boot的Security安全控制——认识SpringSecurity!

SpringBoot的Security安全控制在Web项目开发中,安全控制是非常重要的,不同的人配置不同的权限,这样的系统才安全。最常见的权限框架有Shiro和SpringSecurity。Shi...

前同事2024年接私活已入百万,都是用这几个开源的SpringBoot项目

前言不得不佩服SpringBoot的生态如此强大,今天给大家推荐几款优秀的后台管理系统,小伙伴们再也不用从头到尾撸一个项目了。SmartAdmin...

值得学习的15 个优秀开源的 Spring Boot 学习项目

SpringBoot算是目前Java领域最火的技术栈了,除了书呢?当然就是开源项目了,今天整理15个开源领域非常不错的SpringBoot项目供大家学习,参考。高富帅的路上只能帮你到这里了,...

开发企业官网就用这个基于SpringBoot的CMS系统,真香

前言推荐这个项目是因为使用手册部署手册非常...

2021年超详细的java学习路线总结—纯干货分享

本文整理了java开发的学习路线和相关的学习资源,非常适合零基础入门java的同学,希望大家在学习的时候,能够节省时间。纯干货,良心推荐!第一阶段:Java基础...

jeecg-boot学习总结及使用心得(jeecgboot简单吗)

jeecg-boot学习总结及使用心得1.jeecg-boot是一个真正前后端分离的模版项目,便于二次开发,使用的都是较流行的新技术,后端技术主要有spring-boot2.x、shiro、Myb...

后勤集团原料管理系统springboot+Layui+MybatisPlus+Shiro源代码

本项目为前几天收费帮学妹做的一个项目,JavaEEJSP项目,在工作环境中基本使用不到,但是很多学校把这个当作编程入门的项目来做,故分享出本项目供初学者参考。一、项目描述后勤集团原料管理系统spr...

白卷开源SpringBoot+Vue的前后端分离入门项目

简介白卷是一个简单的前后端分离项目,主要采用Vue.js+SpringBoot技术栈开发。除了用作入门练习,作者还希望该项目可以作为一些常见Web项目的脚手架,帮助大家简化搭建网站的流程。...

Spring Security 自动踢掉前一个登录用户,一个配置搞定

登录成功后,自动踢掉前一个登录用户,松哥第一次见到这个功能,就是在扣扣里边见到的,当时觉得挺好玩的。自己做开发后,也遇到过一模一样的需求,正好最近的SpringSecurity系列正在连载,就结...

收藏起来!这款开源在线考试系统,我爱了

大家好,我是为广大程序员兄弟操碎了心的小编,每天推荐一个小工具/源码,装满你的收藏夹,每天分享一个小技巧,让你轻松节省开发效率,实现不加班不熬夜不掉头发,是我的目标!今天小编推荐一款基于Spr...

Shiro框架:认证和授权原理(shiro权限认证流程)

优质文章,及时送达前言Shiro作为解决权限问题的常用框架,常用于解决认证、授权、加密、会话管理等场景。本文将对Shiro的认证和授权原理进行介绍:Shiro可以做什么?、Shiro是由什么组成的?举...

取消回复欢迎 发表评论: