vue引入ueditor富文本编辑器并实现图片上传
之前在vue+element-ui项目中需要使用富文本编辑器,开始用的vue-quill-editor插件,后因项目需求,改用百度开源的UEditor,从引入项目到能够实现图片成功上传踩了很多坑,在此记录一下,以便后用。
# 下载UEditor源码
可以在UEditor官网下载UEditor源码,下载地址:http://ueditor.baidu.com/website/download.html,我使用的是官网最新[1.4.3.3 Jsp 版本]版本的包。
# 引入项目
下载后解压,将除了jsp的文件夹之外其余的文件和文件夹复制到vue项目中的static用于存放静态文件的目录下,因为我是前后端分离项目,后端使用的是spring boot,所以jsp目录下的文件肯定是不会放在前端的项目中的,包括config.json也放在后端用于解析。
接下来处理jsp文件夹中的文件,将jsp目录下的lib目中的ueditor.jar文件中的所有类全部拿出来,我使用的是JD-GUI反编译了一下,放到后端项目中,然后在controller层新建一个UeditorController.java的类
package yourpackage;
import yourpath.ActionEnter;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import java.io.UnsupportedEncodingException;
/**
* @program:
* @description: 处理请求
* @author: tiandonghui
* @create: 2020/4/6 13:10
**/
@RestController
@CrossOrigin
@RequestMapping("/ueditor")
public class UeditorController {
@RequestMapping(value = "/exec")
@ResponseBody
public String exec(HttpServletRequest request) throws UnsupportedEncodingException {
request.setCharacterEncoding("utf-8");
String rootPath = request.getRealPath("/");
return new ActionEnter( request, rootPath ).exec();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
jsp文件夹下的config.json文件放到yourpath/main/resources目录下,这里检查下config.json中是否存在中文注释,如果存在最好删掉,这对后面为了部署到服务器上时能够解析到config.json而对config.json进行的处理有影响,存在中文可能config.json解析出错,导致UEditor获取配置失败从而加载不出来,所以删除config.json中的中文注释,接下来修改ConfigManager.java类,如下:
package yourpackage;
import yourpath.ueditor.define.ActionMap;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.*;
import java.util.HashMap;
import java.util.Map;
/**
* @program:
* @description:
* @author: tiandonghui
* @create: 2020/4/6 13:14
**/
public class ConfigManager {
private final String rootPath;
private final String originalPath;
private final String contextPath;
private static final String configFileName = "config.json";
private String parentPath = null;
private JSONObject jsonConfig = null;
private final static String SCRAWL_FILE_NAME = "scrawl";
private final static String REMOTE_FILE_NAME = "remote";
private ConfigManager ( String rootPath, String contextPath, String uri ) throws FileNotFoundException, IOException {
rootPath = rootPath.replace( "\\", "/" );
this.rootPath = rootPath;
this.contextPath = contextPath;
this.originalPath = "src/main/resources/config.json";
this.initEnv();
}
/**
* 配置管理器构造工厂
* @param rootPath 服务器根路径
* @param contextPath 服务器所在项目路径
* @param uri 当前访问的uri
* @return 配置管理器实例或者null
*/
public static ConfigManager getInstance ( String rootPath, String contextPath, String uri ) {
try {
return new ConfigManager(rootPath, contextPath, uri);
} catch ( Exception e ) {
return null;
}
}
// 验证配置文件加载是否正确
public boolean valid () {
return this.jsonConfig != null;
}
public JSONObject getAllConfig () {
return this.jsonConfig;
}
public Map<String, Object> getConfig (int type ) throws JSONException {
Map<String, Object> conf = new HashMap<String, Object>();
String savePath = null;
String localSavePathPrefix = null;
switch ( type ) {
case ActionMap.UPLOAD_FILE:
conf.put( "isBase64", "false" );
conf.put( "maxSize", this.jsonConfig.getLong( "fileMaxSize" ) );
conf.put( "allowFiles", this.getArray( "fileAllowFiles" ) );
conf.put( "fieldName", this.jsonConfig.getString( "fileFieldName" ) );
savePath = this.jsonConfig.getString( "filePathFormat" );
break;
case ActionMap.UPLOAD_IMAGE:
conf.put( "isBase64", "false" );
conf.put( "maxSize", this.jsonConfig.getLong( "imageMaxSize" ) );
conf.put( "allowFiles", this.getArray( "imageAllowFiles" ) );
conf.put( "fieldName", this.jsonConfig.getString( "imageFieldName" ) );
savePath = this.jsonConfig.getString( "imagePathFormat" );
localSavePathPrefix = this.jsonConfig.getString("localSavePathPrefix");
break;
case ActionMap.UPLOAD_VIDEO:
conf.put( "maxSize", this.jsonConfig.getLong( "videoMaxSize" ) );
conf.put( "allowFiles", this.getArray( "videoAllowFiles" ) );
conf.put( "fieldName", this.jsonConfig.getString( "videoFieldName" ) );
savePath = this.jsonConfig.getString( "videoPathFormat" );
localSavePathPrefix = this.jsonConfig.getString("localSavePathPrefix");
break;
case ActionMap.UPLOAD_SCRAWL:
conf.put( "filename", ConfigManager.SCRAWL_FILE_NAME );
conf.put( "maxSize", this.jsonConfig.getLong( "scrawlMaxSize" ) );
conf.put( "fieldName", this.jsonConfig.getString( "scrawlFieldName" ) );
conf.put( "isBase64", "true" );
savePath = this.jsonConfig.getString( "scrawlPathFormat" );
break;
case ActionMap.CATCH_IMAGE:
conf.put( "filename", ConfigManager.REMOTE_FILE_NAME );
conf.put( "filter", this.getArray( "catcherLocalDomain" ) );
conf.put( "maxSize", this.jsonConfig.getLong( "catcherMaxSize" ) );
conf.put( "allowFiles", this.getArray( "catcherAllowFiles" ) );
conf.put( "fieldName", this.jsonConfig.getString( "catcherFieldName" ) + "[]" );
savePath = this.jsonConfig.getString( "catcherPathFormat" );
localSavePathPrefix = this.jsonConfig.getString("localSavePathPrefix");
break;
case ActionMap.LIST_IMAGE:
conf.put( "allowFiles", this.getArray( "imageManagerAllowFiles" ) );
conf.put( "dir", this.jsonConfig.getString( "imageManagerListPath" ) );
conf.put( "count", this.jsonConfig.getInt( "imageManagerListSize" ) );
break;
case ActionMap.LIST_FILE:
conf.put( "allowFiles", this.getArray( "fileManagerAllowFiles" ) );
conf.put( "dir", this.jsonConfig.getString( "fileManagerListPath" ) );
conf.put( "count", this.jsonConfig.getInt( "fileManagerListSize" ) );
break;
}
conf.put( "savePath", savePath );
conf.put( "localSavePathPrefix", localSavePathPrefix );
conf.put( "rootPath", this.rootPath );
return conf;
}
private void initEnv () throws FileNotFoundException, IOException {
String xmlString = "";
try {
// 为了在服务器上能够正确解析config.json,所以对config.json进行处理
StringBuffer content = new StringBuffer();
InputStream stream = getClass().getClassLoader().getResourceAsStream("config.json");
BufferedReader br = new BufferedReader(new InputStreamReader(stream));
String s = "";
while ((s = br.readLine()) != null) {
xmlString = xmlString + s;
}
br.close();
} catch (Exception e) {
throw new RuntimeException(e.getMessage());
}
try{
JSONObject jsonConfig = new JSONObject( xmlString );
this.jsonConfig = jsonConfig;
} catch ( Exception e ) {
this.jsonConfig = null;
}
}
private String getConfigPath () {
return this.parentPath + File.separator + ConfigManager.configFileName;
}
private String[] getArray ( String key ) throws JSONException {
JSONArray jsonArray = this.jsonConfig.getJSONArray( key );
String[] result = new String[ jsonArray.length() ];
for ( int i = 0, len = jsonArray.length(); i < len; i++ ) {
result[i] = jsonArray.getString( i );
}
return result;
}
private String readFile ( String path ) throws IOException {
StringBuilder builder = new StringBuilder();
try {
InputStreamReader reader = new InputStreamReader( new FileInputStream( path ), "UTF-8" );
BufferedReader bfReader = new BufferedReader( reader );
String tmpContent = null;
while ( ( tmpContent = bfReader.readLine() ) != null ) {
builder.append( tmpContent );
}
bfReader.close();
} catch ( UnsupportedEncodingException e ) {
// 忽略
}
return this.filter( builder.toString() );
}
// 过滤输入字符串, 剔除多行注释以及替换掉反斜杠
private String filter ( String input ) {
return input.replaceAll( "/\\*[\\s\\S]*?\\*/", "" );
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
接下来配置StorageManager.java文件
package yourpackage;
import yourpath.ueditor.define.AppInfo;
import yourpath.ueditor.define.BaseState;
import yourpath.ueditor.define.State;
import org.apache.commons.io.FileUtils;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.io.*;
@Component
@ConfigurationProperties(prefix="nginx")
public class StorageManager {
public static StorageManager storageManager;
public static final int BUFFER_SIZE = 8192;
private static String fileurl;
public static String getFileurl() {
return fileurl;
}
public static void setFileurl(String fileurl) {
StorageManager.fileurl = fileurl;
}
public static int getBufferSize() {
return BUFFER_SIZE;
}
@PostConstruct
public void init(){
storageManager = this;//工具类的实例赋值给fileUtils
storageManager.pubAttachService=this.pubAttachService;//会激活Spring对Dao的管理并赋给此类
System.out.println("工具类已经初始化了,被纳入spring管理");
}
public StorageManager() {
}
public static State saveBinaryFile(byte[] data, String path) {
File file = new File(path);
State state = valid(file);
if (!state.isSuccess()) {
return state;
}
try {
BufferedOutputStream bos = new BufferedOutputStream(
new FileOutputStream(file));
bos.write(data);
bos.flush();
bos.close();
} catch (IOException ioe) {
return new BaseState(false, AppInfo.IO_ERROR);
}
state = new BaseState(true, file.getAbsolutePath());
state.putInfo( "size", data.length );
state.putInfo( "title", file.getName() );
return state;
}
public static State saveFileByInputStream(MultipartFile file,String picName) {
State state = null;
File tmpFile = getTmpFile();
/**
* 此处调用文件上传服务,并获取返回结果返回
*/
//上传服务
// 上传结果成功true,失败false
boolean success = true;
//如果上传成功
if (success) {
state = new BaseState(true);
state.putInfo( "size", tmpFile.length() );
state.putInfo( "title", picName);//文件名填入此处
state.putInfo( "group", "");//所属group填入此处
state.putInfo( "url", "");//文件访问的url填入此处
tmpFile.delete();
}else{
state = new BaseState(false, 4);
tmpFile.delete();
}
return state;
}
private static File getTmpFile() {
File tmpDir = FileUtils.getTempDirectory();
String tmpFileName = (Math.random() * 10000 + "").replace(".", "");
return new File(tmpDir, tmpFileName);
}
private static State saveTmpFile(File tmpFile, String path) {
State state = null;
File targetFile = new File(path);
if (targetFile.canWrite()) {
return new BaseState(false, AppInfo.PERMISSION_DENIED);
}
try {
FileUtils.moveFile(tmpFile, targetFile);
} catch (IOException e) {
return new BaseState(false, AppInfo.IO_ERROR);
}
state = new BaseState(true);
state.putInfo( "size", targetFile.length() );
state.putInfo( "title", targetFile.getName() );
return state;
}
private static State valid(File file) {
File parentPath = file.getParentFile();
if ((!parentPath.exists()) && (!parentPath.mkdirs())) {
return new BaseState(false, AppInfo.FAILED_CREATE_FILE);
}
if (!parentPath.canWrite()) {
return new BaseState(false, AppInfo.PERMISSION_DENIED);
}
return new BaseState(true);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
接下来处理前端,修改ueditor.config.js文件的serverUrl为访问后端的url,即你的前缀 + "ueditor/exec"。在项目中正式引入,如果项目中有动态加载初始资源的init.js文件,可以直接在此引入。
// ueditor
window.SITE_CONFIG.cdnUrl + '/static/ueditor/ueditor.config.js',
window.SITE_CONFIG.cdnUrl + '/static/ueditor/ueditor.all.js',
window.SITE_CONFIG.cdnUrl + '/static/ueditor/jquery-2.2.3.min.js',
window.SITE_CONFIG.cdnUrl + '/static/ueditor/lang/zh-cn/zh-cn.js',
2
3
4
5
6
# 使用
如果没有可在main.js中引入,接下来新建一个ueditor.vue文件,我为了区分用途,命名为ue-for-resume.vue,这里可以建立多个文件,以解决同一页面同时实例化多个ueditor插件冲突的问题。
<template>
<div>
<script id="editorForResume" type="text/plain"></script>
</div>
</template>
<script>
export default {
name: "ueForResume",
data() {
return {
editor: null
};
},
props: {
value: "",
config: {}
},
mounted() {
const _this = this;
this.editor = window.UE.getEditor("editorForResume", this.config);
var editor = UE.getEditor('editorForResume');
editor.ready(function(){
editor.execCommand('lineheight', 1.75); //行间距
});
// 初始化UE
this.editor.addListener("ready", function() {
_this.editor.setContent(_this.value);
// 确保UE加载完成后,放入内容。
});
},
methods: {
getUEContent() {
// 获取内容方法
return this.editor.getContent();
},
getUEContentLength() {
// 获取内容长度
return this.editor.getContentTxt().length;
},
readyUe(){
this.editor.setContent('')
}
},
destroyed() {
this.editor.destroy();
}
};
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
至此大功告成,在你需要使用ueditor的页面引入ueditor
import Ueditor from '@/components/ue-for-resume.vue'
2
components中注册组件
components: {Ueditor}
2
需要使用的位置
<Ueditor :value="ueditor.value" :config="ueditor.config" ref="ueForResume"></Ueditor>
2