注:本篇文章的代码地址如下
本篇文章的目录:
- WangEditor 如何引入到项目中——前端
- 上传图片实现原理
- 图片上传接口实现——后端
- 总结
WangEditor 如何引入到项目中——前端
对于一个不熟悉的组件,如何最快的上手?一般来说,官网和文档是不二选择。官方网站如下:WangEditor
在上面的官网文档上,一共给出了 3 种安装方式:
- 下载源文件 js 引入
- 使用包管理工具引入(诸如 npm 之类)
- 使用在线的 js (CDN)
无论使用哪种方式,原理是一样的,都是加载 js 文件到项目中。本场 chat 使用 Vue.js 做前端开发,因此使用 npm 方式引入是最方便的。
引入方式很简单,安装 Node 环境之后在命令行工具里面输入下面的命令就行:
npm install -S wangeditor
/**
* -S 参数表示安装到本地
* 对于 Vue 项目会在 package.json 文件里自动更新依赖,
* 之后在别的电脑上运行电脑就无需再次手动安装,
* 执行 npm install 命令就可以跟随其他 package.json 中的依赖一起安装了
* /
安装之后如何初始化呢?先不去考虑代码实现,从思维上来讲就 2 个步骤:
- 对于 HTML 需要提供一个 DOM 节点
- 调用 WangEditor 组件的方法,跟上面的 DOM 节点关联
然后创建富文本编辑器就交给 WangEditor 的组件去实现,怎么实现的不用在意,它就像是一个黑盒子,我们给它需要的东西,它就会给我们想要的东西。
理解了思路,那么剩下的就简单了,核心部分代码如下:
/**
* 用了 Vue 的组件,下面需要关注的就是 <div ref="editorForm"/> 这一行
* 提供了一个 HTML 的 DOM 节点,对应上面的第一步
* /
<template>
<div class="editor">
<div ref="editorForm"/>
</div>
</template>
对于第二步,让我们思考一个问题,编辑器很可能会有多个地方调用对吧,那么独立成组件会更加合适吧。对应的使用场景大概像这样——富文本编辑器在子组件里免渲染,其他页面按需引入该组件,同时页面能实时设置和获取到富文本编辑器里面的内容。那么独立组件会面临几个问题:
- 页面传递内容参数给富文本编辑器组件
- 编辑器内容更新之后,引入富文本编辑器组件的页面能实时获取数据
这两个问题,本质上就是父子组件的传递参数问题,父组件到子组件用 props 属性,子组件到父组件用 $emit 函数。核心代码如下:
props: {
// 传递过来的编辑器内容参数,用于设置编辑器内容
content: {
type: String,
default: ''
}
},
data() {
return {
// 真正的编辑器里的内容
editorContent: '',
// 编辑器对象
editor: null
}
},
watch: {
// watch 表示监听 当父组件的内容变化时需要更新编辑器的内容
content() {
this.editor.txt.html(this.content)
}
},
/**
* 这里使用了 Vue 的 mounted 函数钩子,这属于 Vue 生命周期的一个阶段
*/
mounted() {
// 初始化
this.editor = new E(this.$refs.editorForm)
// 当编辑器内容变化的时候通知父组件
this.editor.customConfig.onchange = (html) => {
this.editorContent = html
// 通知父级控件方法,富组件可以通过 editorContent 事件去获取最新的编辑器内容
this.$emit('editorContent', html)
}
this.editor.create()
this.editor.txt.html(this.content)
}
以上就是子组件的核心代码部分,针对父组件部分就简单了,引入子组件,添加子组件定义的 editorContent 事件就行了。核心代码如下:
<template>
<div id="app">
<h1>WangEditor Demo</h1>
// 子组件将会在这里渲染出来 指定初始化的数据,以及监听数据更新的事件
<wang-editor :content="content" @editorContent="getEditorContent"/>
<h2>效果区域展示:</h2>
<div ref="preview" v-html="content"></div>
</div>
</template>
<script>
// 引入子组件
import WangEditor from '@/components/wang-editor'
export default {
name: 'app',
components: {
WangEditor
},
data() {
return {
// 编辑器内容
content: ''
}
},
methods: {
// 获取编辑器内容
getEditorContent(data) {
this.content = data
}
}
}
</script>
完成了这一步,最基本的编辑器就已经好了,效果如下:
上传图片实现原理
关于上传图片一般用 Base64 或者 form 表单上传,前者是把一张图片转换成 Base64 编码,然后用 img 标签的 src 属性中加上 data:image/jpeg;base64, 前缀交给浏览器渲染出来;后者是用 HTTP 方式上传到服务器上,然后通过 URI 访问图片资源。
使用 Base64 方式,图片不需要上传到服务器,当作字符串一样保存就行,但是字符串长度会很长很长;后者会占用服务器资源作存储,但返回给前端就是一串 URI 地址。
再回到 form 表单上传图片,既然是通过 HTTP 方式,那么肯定会涉及到后端。但不管怎么样都是从 HTTP 的请求体里面拿到图片,然后读取文件流,存储到服务器上,最后返回该图片可访问的 URI 资源。
对于 WangEditor 编辑器的上传图片,同样提供了 Base64 上传和接口上传图片两种方式,下面就是后端上传图片接口的实现。
图片上传接口实现——后端
上面已经提到过,后台提供上传图片接口,这个接口的主要作用就是从 HTTP 请求中获取文件流信息,然后保存到服务器上,同时返回文件的访问地址 URI。重点就是要处理好图片的存储和返回图片存储地址,废话不多说,直接上代码:
/**
* Created by Administrator on 2019/9/6.
* 上传图片 Controller 类
*/
@RestController
public class UploadImageController {
private static Logger logger = LoggerFactory.getLogger(UploadImageController.class);
/**
* 上传图片接口地址
* @param multipartHttpServletRequest
* @return
*/
@PostMapping(value = "/uploadImage")
public Object uploadImage(MultipartHttpServletRequest multipartHttpServletRequest){
// 图片存储路径
String path = "src/main/resources/static";
// 返回值
HashMap map = new HashMap();
List<String> data = new ArrayList<>();
// 取得request中的所有文件名
Iterator<String> iterator = multipartHttpServletRequest.getFileNames();
// 遍历
while (iterator.hasNext()) {
// 取得上传文件
MultipartFile multipartFile = multipartHttpServletRequest.getFile(iterator.next());
if (multipartFile != null) {
// 文件名
String fileName = multipartFile.getOriginalFilename();
// 获取文件拓展名
String extName = FileUtil.getExtName(fileName);
if (StringUtils.isEmpty(extName)) {
logger.error("文件后缀名称为空,文件可能有问题...");
map.put("errno", 1);
map.put("data", data);
return map;
}
// 保证 文件夹存
File fileDir = new File(path);
if (!fileDir.exists()){
fileDir.mkdirs();
}
File file = new File(fileDir, fileName);
// 拷贝文件流 到上面的文件
FileUtil.copyInputStreamToFile(multipartFile, file);
// 构建图片的可访问地址
String webUrl = multipartHttpServletRequest.getScheme()
+ "://" + multipartHttpServletRequest.getServerName()
+ ":" + multipartHttpServletRequest.getServerPort()
+ multipartHttpServletRequest.getContextPath();
String imageUrl = file.getPath().substring(path.length()).replaceAll("\\\\", "/");
logger.info("文件路径: {}", webUrl + imageUrl);
// 添加到数组中
data.add(webUrl + imageUrl);
}
}
// 返回前端需要的格式
map.put("errno", 0);
map.put("data", data);
return map;
}
}
上面的代码就提供了一个上传图片的接口地址,图片会存储在项目的 classpath 下面的 static 目录中,返回值的格式需要和 WangEditor 要求的格式保持一致。
与此同时,前端的富文本编辑器组件中也需要配置上传图片接口地址,如下:
总结
到了这里呢,本场 chat 就算是已经结束了,最后再梳理下整体的逻辑。
一开始遇到新的需求,需要用到不熟悉的组件,最好的开始就是官方文档,从官方文档其实可以看到很多东西。比如如何开始的步骤、提供的功能、设计思路等,然后需要结合自己的需求去做取舍和调整,对于一些苛刻的要求能否用提供的功能去间接实现等。
然后提供了基于 Vue 框架的 WangEditor 富文本编辑器前端部分的设计思路和代码实现,之后简单说明了两种上传图片的方式的优劣。
最后使用 Java 和 Spring Boot 完成了后端部分的图片上传接口,之后修改前端的配置就搞定了一个简单的案例。
注:代码地址如下