用来用去还是用回了ueditor-Vue富文本编辑器二次扩展
ztj100 2024-12-19 17:58 54 浏览 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
大家可以前往下载
相关推荐
- sharding-jdbc实现`分库分表`与`读写分离`
-
一、前言本文将基于以下环境整合...
- 三分钟了解mysql中主键、外键、非空、唯一、默认约束是什么
-
在数据库中,数据表是数据库中最重要、最基本的操作对象,是数据存储的基本单位。数据表被定义为列的集合,数据在表中是按照行和列的格式来存储的。每一行代表一条唯一的记录,每一列代表记录中的一个域。...
- MySQL8行级锁_mysql如何加行级锁
-
MySQL8行级锁版本:8.0.34基本概念...
- mysql使用小技巧_mysql使用入门
-
1、MySQL中有许多很实用的函数,好好利用它们可以省去很多时间:group_concat()将取到的值用逗号连接,可以这么用:selectgroup_concat(distinctid)fr...
- MySQL/MariaDB中如何支持全部的Unicode?
-
永远不要在MySQL中使用utf8,并且始终使用utf8mb4。utf8mb4介绍MySQL/MariaDB中,utf8字符集并不是对Unicode的真正实现,即不是真正的UTF-8编码,因...
- 聊聊 MySQL Server 可执行注释,你懂了吗?
-
前言MySQLServer当前支持如下3种注释风格:...
- MySQL系列-源码编译安装(v5.7.34)
-
一、系统环境要求...
- MySQL的锁就锁住我啦!与腾讯大佬的技术交谈,是我小看它了
-
对酒当歌,人生几何!朝朝暮暮,唯有己脱。苦苦寻觅找工作之间,殊不知今日之事乃我心之痛,难道是我不配拥有工作嘛。自面试后他所谓的等待都过去一段时日,可惜在下京东上的小金库都要见低啦。每每想到不由心中一...
- MySQL字符问题_mysql中字符串的位置
-
中文写入乱码问题:我输入的中文编码是urf8的,建的库是urf8的,但是插入mysql总是乱码,一堆"???????????????????????"我用的是ibatis,终于找到原因了,我是这么解决...
- 深圳尚学堂:mysql基本sql语句大全(三)
-
数据开发-经典1.按姓氏笔画排序:Select*FromTableNameOrderByCustomerNameCollateChinese_PRC_Stroke_ci_as//从少...
- MySQL进行行级锁的?一会next-key锁,一会间隙锁,一会记录锁?
-
大家好,是不是很多人都对MySQL加行级锁的规则搞的迷迷糊糊,一会是next-key锁,一会是间隙锁,一会又是记录锁。坦白说,确实还挺复杂的,但是好在我找点了点规律,也知道如何如何用命令分析加...
- 一文讲清怎么利用Python Django实现Excel数据表的导入导出功能
-
摘要:Python作为一门简单易学且功能强大的编程语言,广受程序员、数据分析师和AI工程师的青睐。本文系统讲解了如何使用Python的Django框架结合openpyxl库实现Excel...
- 用DataX实现两个MySQL实例间的数据同步
-
DataXDataX使用Java实现。如果可以实现数据库实例之间准实时的...
- MySQL数据库知识_mysql数据库基础知识
-
MySQL是一种关系型数据库管理系统;那废话不多说,直接上自己以前学习整理文档:查看数据库命令:(1).查看存储过程状态:showprocedurestatus;(2).显示系统变量:show...
- 如何为MySQL中的JSON字段设置索引
-
背景MySQL在2015年中发布的5.7.8版本中首次引入了JSON数据类型。自此,它成了一种逃离严格列定义的方式,可以存储各种形状和大小的JSON文档,例如审计日志、配置信息、第三方数据包、用户自定...
你 发表评论:
欢迎- 一周热门
-
-
MySQL中这14个小玩意,让人眼前一亮!
-
旗舰机新标杆 OPPO Find X2系列正式发布 售价5499元起
-
【VueTorrent】一款吊炸天的qBittorrent主题,人人都可用
-
面试官:使用int类型做加减操作,是线程安全吗
-
C++编程知识:ToString()字符串转换你用正确了吗?
-
【Spring Boot】WebSocket 的 6 种集成方式
-
PyTorch 深度学习实战(26):多目标强化学习Multi-Objective RL
-
pytorch中的 scatter_()函数使用和详解
-
与 Java 17 相比,Java 21 究竟有多快?
-
基于TensorRT_LLM的大模型推理加速与OpenAI兼容服务优化
-
- 最近发表
- 标签列表
-
- idea eval reset (50)
- vue dispatch (70)
- update canceled (42)
- order by asc (53)
- spring gateway (67)
- 简单代码编程 贪吃蛇 (40)
- transforms.resize (33)
- redisson trylock (35)
- 卸载node (35)
- np.reshape (33)
- torch.arange (34)
- npm 源 (35)
- vue3 deep (35)
- win10 ssh (35)
- vue foreach (34)
- idea设置编码为utf8 (35)
- vue 数组添加元素 (34)
- std find (34)
- tablefield注解用途 (35)
- python str转json (34)
- java websocket客户端 (34)
- tensor.view (34)
- java jackson (34)
- vmware17pro最新密钥 (34)
- mysql单表最大数据量 (35)