Vue项目快速整合WangEditor富文本编辑器
一、安装依赖
npm i wangeditor --save //富文本编辑器
npm install highlight.js -S //代码高亮
npm install dompurify vue-dompurify-html // 防xss 库
二、app.vue代码案例
已对接图片、视频接口 ,具体看如下代码案例 。
2.1、图片接口数据格式
{
"data": [
{
"alt": "图片文字说明",
"href": "跳转链接",
"url": "http://localhost:8080/uploads/1727435190_2.jpg"
}
],
"errno": 0
}
2.2、视频接口数据格式
{
"data": {
"url": "http://localhost:8080/uploads/1727435236_20240920_092347.mp4"
},
"errno": 0
}
<template>
<div id="app">
<h1>Hello WangEditor</h1>
<button @click="logEditorContent">输出编辑器内容</button>
<br>
<br>
<div ref="editorContainer" style="height: 300px; border: 1px solid #ccc;"></div>
</div>
</template>
<script>
import E from 'wangeditor';
import 'highlight.js/styles/github.css';
export default {
name: 'App',
data() {
return {
editor: null,
};
},
mounted() {
this.$nextTick(() => {
this.editor = new E(this.$refs.editorContainer);
// 配置图片上传的服务器接口
this.editor.config.uploadImgServer = 'http://localhost:8080/upload';
this.editor.config.uploadFileName = 'file';
// 配置视频上传的服务器接口
this.editor.config.uploadVideoServer = 'http://localhost:8080/upload-video';
this.editor.config.uploadVideoName = 'file';
this.editor.highlight = window.hljs;
// 图片上传的回调处理逻辑
this.editor.config.uploadImgHooks = {
customInsert: (insertImgFn, result) => {
if (result.errno === 0 && result.data && Array.isArray(result.data) && result.data.length > 0) {
const imageData = result.data[0];
if (imageData.url && typeof imageData.url === 'string' && imageData.url.trim() !== '') {
insertImgFn(imageData.url, imageData.alt, imageData.href);
}
}
},
};
// 视频上传的回调处理逻辑
this.editor.config.uploadVideoHooks = {
customInsert: (insertVideoFn, result) => {
if (result.errno === 0 && result.data && result.data.url) {
insertVideoFn(result.data.url);
} else {
console.error('视频上传失败');
}
},
};
this.editor.create();
});
},
beforeDestroy() {
if (this.editor) {
this.editor.destroy();
}
},
methods: {
logEditorContent() {
if (this.editor) {
const content = this.editor.txt.html(); // 获取编辑器中的HTML内容
console.log("编辑器内容:", content);
}
},
},
};
</script>
<style>
#app {
width: 80%;
margin: 20px auto;
}
.w-e-text-container {
min-height: 300px;
}
</style>
go后端接口案例,可直接复制用
package main
import (
"fmt"
"github.com/gin-gonic/gin"
"mime/multipart"
"net/http"
"path/filepath"
"strings"
"time"
)
func main() {
router := gin.Default()
// 跨域中间件
router.Use(CORSMiddleware())
// 静态文件服务,将 "./uploads" 目录公开
router.Static("/uploads", "./uploads")
// 图片上传接口
router.POST("/upload", uploadFile)
// 视频上传接口
router.POST("/upload-video", uploadVideo)
// 启动服务器,监听8080端口
router.Run(":8080")
}
// 上传文件处理(图片)
func uploadFile(c *gin.Context) {
uploadCommon(c, "image")
}
// 上传视频处理
func uploadVideo(c *gin.Context) {
uploadCommon(c, "video")
}
// uploadCommon 是通用的文件上传处理函数,接受图片或视频
func uploadCommon(c *gin.Context, fileTypeExpected string) {
file, err := c.FormFile("file")
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"errno": 1,
"data": gin.H{},
})
return
}
// 使用时间戳生成唯一文件名
filename := fmt.Sprintf("%d_%s", time.Now().Unix(), filepath.Base(file.Filename))
savePath := filepath.Join("./uploads", filename)
// 保存文件到指定路径
if err := c.SaveUploadedFile(file, savePath); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"errno": 1,
"data": gin.H{},
})
return
}
// 判断文件类型
fileType := getFileType(file)
if fileType == fileTypeExpected {
// 如果文件类型匹配(图片或视频),根据不同类型返回相应的 JSON 响应
if fileType == "image" {
c.JSON(http.StatusOK, gin.H{
"errno": 0,
"data": []gin.H{
{
"url": "http://localhost:8080/uploads/" + filename,
"alt": "图片文字说明",
"href": "跳转链接",
},
},
})
} else if fileType == "video" {
c.JSON(http.StatusOK, gin.H{
"errno": 0,
"data": gin.H{
"url": "http://localhost:8080/uploads/" + filename,
},
})
}
} else {
// 文件类型不匹配
c.JSON(http.StatusBadRequest, gin.H{
"errno": 1,
"data": gin.H{},
})
}
}
// getFileType 返回文件的 MIME 类型
func getFileType(file *multipart.FileHeader) string {
ext := strings.ToLower(filepath.Ext(file.Filename))
switch ext {
case ".jpg", ".jpeg", ".png", ".gif", ".bmp":
return "image"
case ".mp4", ".avi", ".mkv", ".mov":
return "video"
default:
return "other"
}
}
// CORSMiddleware 处理跨域请求的中间件
func CORSMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
c.Writer.Header().Set("Access-Control-Allow-Origin", "*")
c.Writer.Header().Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE")
c.Writer.Header().Set("Access-Control-Allow-Headers", "Origin, Content-Type, X-Auth-Token, Authorization")
// 处理预检请求
if c.Request.Method == "OPTIONS" {
c.AbortWithStatus(http.StatusNoContent)
return
}
c.Next()
}
}
三、预防xss代码案例
首先main.js文件装载
import DOMPurify from 'dompurify';
Vue.directive('dompurify-html', {
bind(el, binding) {
// 使用 DOMPurify 清理绑定的 HTML 内容
el.innerHTML = DOMPurify.sanitize(binding.value);
},
update(el, binding) {
// 每次数据更新时再次清理内容
el.innerHTML = DOMPurify.sanitize(binding.value);
}
});
<template>
<div id="app">
<h1>安全渲染 HTML 内容</h1>
<div v-dompurify-html="htmlContent"></div>
</div>
</template>
<script>
export default {
data() {
return {
// 动态 HTML 内容,可能来自用户输入或外部数据
htmlContent: '<p οnclick="alert(\'XSS\')">这是一些<strong>带有恶意代码</strong>的HTML</p>'
};
}
};
</script>