龙空技术网

一款好用的富文本编辑器「wangeditor」运用(附源码+视频讲解)

小王八NPC 93

前言:

如今我们对“wangeditor图片上传”大约比较讲究,我们都想要了解一些“wangeditor图片上传”的相关知识。那么小编也在网上汇集了一些关于“wangeditor图片上传””的相关文章,希望看官们能喜欢,我们快快来学习一下吧!

给大家分享一个好用的富文本编辑器

项目功能介绍

前后端分离

前端 vue3+typescript+wangEditor+axios

后端 springboot+mybatis-plus+swagger

项目精简 不引入过多框架

1. 自定义图片上传

2. 自定义视频上传

(wangEditor 有默认配置 但要返回response body有格式要求 故自己编写后端自定义实现)

3. 后端 对于html的处理 转义安全字符 解义

4. 文章的保存

5. 文章的查询

资源介绍

swagger接口文档

编辑器功能展示

项目目录讲解

前端

后端

部分代码展示

前端 富文本编辑器页面App.vue

<script setup lang="ts">import "@wangeditor/editor/dist/css/style.css"; // 引入 cssimport {  onBeforeUnmount,  ref,  shallowRef,  onMounted,  reactive} from "vue";import { Editor, Toolbar } from "@wangeditor/editor-for-vue";import { IEditorConfig } from "@wangeditor/core";import { uploadPic, deleteFile, uploadVideo, toSaveArticleAndFile ,toQueryArticleApi} from "./request/api";import { resourceUrl } from "./common/path";import {  IWangEPic,  IWangEVid,  IRichData,  IToSaveAricle,  IReQueryArticle} from "./pageTs/index";//图片 视频 类型声明const richData = reactive(new IRichData());const saveArticleData=reactive(new IToSaveAricle());// 编辑器实例,必须用 shallowRefconst editorRef = shallowRef();// 内容 HTMLconst valueHtml = ref("");// 模拟 axios 异步获取内容onMounted(() => {  setTimeout(() => {    valueHtml.value = "<p>大大帅将军 小小怪下士</p>";  }, 1500);});//编辑器初始化const toolbarConfig = {};const editorConfig: Partial<IEditorConfig> = { MENU_CONF: {} };// 编辑器创建完毕时的回调函数。const handleCreated = (editor: any) => {  editorRef.value = editor; // 记录 editor 实例,重要!};//图片类型定义type InsertPicType = (url: string) => void;//图片上传editorConfig.MENU_CONF!["uploadImage"] = {  // 自定义上传 InsertFnType  async customUpload(richPic: File, insertFn: InsertPicType) {    //图片上传接口调用    uploadPic(richPic).then((res) => {      console.log(res.data);      //返回给编辑器 图片地址      insertFn(resourceUrl + res.data);      //上传成功后 记录图片地址      richData.preFileList.push(res.data);    });  },};//上传视频 url 视频地址 poster 视频展示图片地址type InsertVidType = (url: string, poster: string) => void;editorConfig.MENU_CONF!["uploadVideo"] = {  // 自定义上传  async customUpload(file: File, insertFn: InsertVidType) {    //视频上传接口调用    uploadVideo(file).then((res) => {      console.log(res.data);      //返回给编辑器 图片地址      insertFn(resourceUrl + res.data, "/src/assets/bg.png");      //上传成功后 记录视频地址      richData.preFileList.push(res.data);    });  },};// 组件销毁时,也及时销毁编辑器onBeforeUnmount(() => {  const editor = editorRef.value;  if (editor == null) return;  editor.destroy();});//保存文章const toSaveArcitle = () => {  const editor = editorRef.value;  // 1.获取最后保存的文章图片 视频 list数组  editor.getElemsByType("image").forEach((item: IWangEPic) => {    //排除掉外部资源    if(item.src.indexOf(resourceUrl) !=-1){      richData.articleFileUrl.push(item.src);    }  });  editor.getElemsByType("video").forEach((item: IWangEVid) => {    if(item.src.indexOf(resourceUrl) !=-1){      richData.articleFileUrl.push(item.src);    }  });  //2.对于全部图片 视频 对比 获取已删除图片  richData.preFileList.forEach((item) => {    //articleFileList 数组展示的是图片完全路径    //preFileList 保存的是图片部分路径    //所以要通过添加resourceUrl常量进行对比    if (richData.articleFileUrl.indexOf(resourceUrl + item) == -1) {      //保存到需删除的数组中      richData.deleteFileList.push(item);    }else{      //保存需要存入数据库的数组      saveArticleData.articleFileUrl.push(item);    }  });  //3.调后台接口 删除图片 视频  deleteFile(richData.deleteFileList).catch((err) => {    console.log(err.msg);  });  //4.调后台接口保存文章  //参数赋值  saveArticleData.articleName=richData.articleName;  saveArticleData.articleAuthor=richData.articleAuthor;  saveArticleData.articleContent=valueHtml.value;  toSaveArticleAndFile(saveArticleData).then(res=>{    console.log("保存文章成功");    //保存文章后 所有数据清空    valueHtml.value="";    richData.articleAuthor="";    richData.articleName="";  })};//文章查询const queryArticle = reactive(new IReQueryArticle());const toQueryArticle=()=>{  toQueryArticleApi(1566944471915032576n).then(res=>{    console.log(res.data);    queryArticle.articleAuthor=res.data.articleAuthor;    queryArticle.articleContent=res.data.articleContent;    queryArticle.articleId=res.data.articleId;    queryArticle.gmtUpdate=res.data.gmtUpdate;    queryArticle.articleName=res.data.articleName;  })}</script><template>  <div>    文章名称:<input      class="demoInput"      type="text"      v-model="richData.articleName"      placeholder="请输入文章名称"    />  </div>  <div>    文章作者:<input      class="demoInput"      type="text"      v-model="richData.articleAuthor"      placeholder="请输入文章作者"    />  </div>  <div style="border: 1px solid #ccc">    <Toolbar      style="border-bottom: 1px solid #ccc"      :editor="editorRef"      :defaultConfig="toolbarConfig"      :mode="richData.model"    />    <Editor      style="height: 550px; overflow-y: hidden"      v-model="valueHtml"      :defaultConfig="editorConfig"      :mode="richData.model"      @onCreated="handleCreated"    />  </div>  <button @click="toSaveArcitle">保存</button>  <div>==========================================</div>  <button @click="toQueryArticle">查询文章</button>  <div v-if="queryArticle!=null">    <h1>{{queryArticle.articleName}}</h1>  <h5>{{queryArticle.articleAuthor}}</h5>  <h6>{{queryArticle.gmtUpdate}}</h6>  <div  v-html="queryArticle.articleContent">    </div>  </div></template><style lang="scss" scoped>.demoInput {  outline-style: none;  border: 1px solid #ccc;  border-radius: 3px;  padding: 13px 14px;  width: 320px;  font-size: 14px;  font-weight: 320;  margin: 20px;  font-family: "Microsoft soft";}.demoInput:focus {  border-color: #66afe9;  outline: 0;  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075),    0 0 8px rgba(102, 175, 233, 0.6);  box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075),    0 0 8px rgba(102, 175, 233, 0.6);}</style>

后端 文章查询保存 serviceImpl

/** * <p> *  服务实现类 * </p> * * @author 小王八 * @since 2022-09-05 */@Servicepublic class ArticleServiceImpl extends ServiceImpl<ArticleMapper, Article> implements ArticleService {    @Autowired    private ArticleMapper articleMapper;    @Autowired    private ArticleFileService articleFileService;    @Override    @Transactional(rollbackFor = Exception.class)    public String toSaveArticle(ToSaveArticle toSaveArticle) {        //1.生成文章id 插入图片article_id        long articleId = IdUtil.getSnowflakeNextId();        //2.文章内容转换        //过滤HTML文本,防止XSS攻击 可用 会清理掉html元素标签 只留下文本        //String articleContent = HtmlUtil.filter(toSaveArticle.getArticleContent());        //html=>安全字符        String escape = HtmlUtil.escape(toSaveArticle.getArticleContent());        //3.文章入数据库        save(new Article()                .setPkId(articleId)                .setArticleName(toSaveArticle.getArticleName())                .setArticleAuthor(toSaveArticle.getArticleAuthor())                .setArticleContent(escape)        );        //4.文章资源入数据库        if (ObjectUtil.isNotEmpty(toSaveArticle.getArticleFileUrl())){            articleFileService.toSaveFile(toSaveArticle.getArticleFileUrl(),articleId);        }        return "文章保存成功";    }    @Override    public ReShowArticle toShowArticle(Long articleId) {        return ReShowArticle.toReShow(getById(articleId));    }

功能演示 源码分享

关于功能的动态详细展示

我专门录制的一期B站视频 作为讲解

具体源码

放在视频简介(gitee 前后端地址都有)

【一款好用的富文本编辑器 wangEditor 前后端 vue3+springboot】

B站视频链接

制作不易 还望大家三连支持

标签: #wangeditor图片上传