WangEditor快速实现版
效果
案例代码
后端
Java
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;
}
}
前端
vue
<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">渲染HTML(XSS演示)</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>