目录

WangEditor快速实现版

WangEditor快速实现版

效果

https://i-blog.csdnimg.cn/direct/aa341fa2549e4107a89d77da33343a5f.png

后端

package com.diy.springboot.controller;

import cn.hutool.core.util.IdUtil;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiImplicitParam;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.io.IOException;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

@Api(tags = "富文本编辑器文件上传接口")
@RestController
@RequestMapping("/editor")
public class WangEditorUploadController {

    @Value("${files.upload.path}")
    private String fileUploadPath;

    @ApiOperation(value = "上传图片或视频", notes = "支持图片(jpg,jpeg,png,gif,bmp)和视频(mp4,avi,mkv,mov)格式")
    @ApiImplicitParam(name = "file", value = "文件", required = true, dataType = "MultipartFile")
    @PostMapping("/upload")
    public ResponseEntity<Map<String, Object>> upload(@RequestParam MultipartFile file) {
        if (file.isEmpty()) {
            return ResponseEntity.badRequest().body(createErrorResponse());
        }

        String originalFilename = file.getOriginalFilename();
        String fileType = getFileType(originalFilename);
        if ("other".equals(fileType)) {
            return ResponseEntity.badRequest().body(createErrorResponse());
        }

        try {
            String fileUUID = IdUtil.fastSimpleUUID() + originalFilename.substring(originalFilename.lastIndexOf("."));
            File uploadFile = new File(fileUploadPath + fileUUID);

            File parentFile = uploadFile.getParentFile();
            if (!parentFile.exists()) {
                parentFile.mkdirs();
            }

            file.transferTo(uploadFile);
            return ResponseEntity.ok(createImageResponse(fileUUID));
        } catch (IOException e) {
            return ResponseEntity.badRequest().body(createErrorResponse());
        }
    }

    @ApiOperation(value = "获取文件类型", hidden = true)
    private String getFileType(String filename) {
        String ext = filename.substring(filename.lastIndexOf(".")).toLowerCase();
        switch (ext) {
            case ".jpg":
            case ".jpeg":
            case ".png":
            case ".gif":
            case ".bmp":
                return "image";
            case ".mp4":
            case ".avi":
            case ".mkv":
            case ".mov":
                return "video";
            default:
                return "other";
        }
    }

    @ApiOperation(value = "创建错误响应", hidden = true)
    private Map<String, Object> createErrorResponse() {
        Map<String, Object> response = new HashMap<>();
        response.put("errno", 1);
        response.put("data", Collections.emptyMap());
        return response;
    }

    @ApiOperation(value = "创建图片响应", hidden = true)
    private Map<String, Object> createImageResponse(String filename) {
        Map<String, Object> response = new HashMap<>();
        response.put("errno", 0);

        Map<String, String> imageData = new HashMap<>();
        imageData.put("url", "http://localhost:9090/file/" + filename);
        response.put("data", imageData);

        return response;
    }
}

前端

<template>
  <div id="app">
    <h1>富文本编辑器与HTML渲染</h1>
    <el-row>
      <el-button type="primary" @click="logEditorContent">输出编辑器内容</el-button>
      <el-button type="success" @click="loadSampleContent">回显内容</el-button>
      <el-button type="warning" @click="renderUnsafeHtml">渲染HTMLXSS演示)</el-button>
    </el-row>
    <el-divider></el-divider>
    <div ref="editorContainer" style="border: 1px solid #ccc;"></div>
    <el-divider></el-divider>
    <h2>渲染的HTML内容</h2>
    <div v-html="renderedContent"></div>
  </div>
</template>

<script>
import E from 'wangeditor'

export default {
  name: 'App',
  data() {
    return {
      editor: null,
      renderedContent: '',
    }
  },
  mounted() {
    this.$nextTick(() => {
      this.editor = new E(this.$refs.editorContainer)

      // 配置图片上传的服务器接口
      this.editor.config.uploadImgServer = 'http://localhost:9090/editor/upload';
      this.editor.config.uploadFileName = 'file';

      // 配置视频上传的服务器接口
      this.editor.config.uploadVideoServer = 'http://localhost:9090/editor/upload';
      this.editor.config.uploadVideoName = 'file';

      this.editor.highlight = window.hljs;

      // 修改图片上传的回调处理逻辑,适配后端返回的数据结构
      this.editor.config.uploadImgHooks = {
        customInsert: (insertImgFn, result) => {
          if (result.errno === 0 && result.data && result.data.url) {
            // 直接使用data.url,不再假设data是数组
            insertImgFn(result.data.url);
          } else {
            console.error('图片上传失败', result);
          }
        },
      };

      // 视频上传的回调处理逻辑
      this.editor.config.uploadVideoHooks = {
        customInsert: (insertVideoFn, result) => {
          if (result.errno === 0 && result.data && result.data.url) {
            insertVideoFn(result.data.url);
          } else {
            console.error('视频上传失败', result);
          }
        },
      };

      this.editor.create()
    })
  },
  beforeDestroy() {
    if (this.editor) {
      this.editor.destroy()
    }
  },
  methods: {
    logEditorContent() {
      if (this.editor) {
        const content = this.editor.txt.html()
        console.log("编辑器内容:", content)
        this.renderedContent = content
      }
    },
    loadSampleContent() {
      if (this.editor) {
        const sampleContent = `
          <h2>欢迎使用 WangEditor</h2>
          <p>这是一个<strong>富文本编辑器</strong>示例。</p>
          <ul>
            <li>支持多种格式</li>
            <li>易于使用</li>
            <li>功能强大</li>
          </ul>
          <p>你可以在这里添加<span style="color: blue;">各种样式</span>的文本。</p>
          <blockquote>这是一个引用示例。</blockquote>
        `
        this.editor.txt.html(sampleContent)
        this.renderedContent = sampleContent
      }
    },
    renderUnsafeHtml() {
      const unsafeHtml = '<img src="x" onerror="alert(\'XSS 攻击演示\')">'
      this.editor.txt.html(unsafeHtml)
      this.renderedContent = unsafeHtml
    }
  },
}
</script>

<style>
#app {
  width: 80%;
  margin: 20px auto;
}

.w-e-text-container {
  height: 300px !important;
}
</style>