vue 后台管理系统富文本组件(一)tinymce

简介

富文本组件作为后台管理系统的最重要的基础组件之一,一定要选择坑比较少的富文本厂家。这里使用的是好看又坑少的 tinymce。

主要依赖说明 (先安装,步骤略)

1
2
3
4
5
6
{
"axios": "^0.18.0",
"element-ui": "2.11.1",
"vue": "^2.6.10",
"vue-router": "^3.0.1"
}

tinymce:5.0.8 这里直接采用下载源码的方式,npm 下载极大的影响打包速度,也可以使用 cdn

tinymce 官网下载地址

正文

1.下载 tinymce 源码放在 vue-cli 3 生成的 public 文件夹下的 static 文件夹下 ,如图
在这里插入图片描述

需要语言包的话,单独下载 zh_CN.js 文件放在 tinymce_5.0.8 文件夹下的 lang 文件夹下

zh_CN.js 官网下载地址

2.在入口 html 文件中导入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
<meta name="renderer" content="webkit" />
<meta
name="viewport"
content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"
/>
<meta name="description" content="<%= webpackConfig.name %>" />
<meta name="robots" content="noindex,nofollow,noarchive" />
<title>title</title>
</head>
<body>
<script src="<%= BASE_URL %>static/tinymce_5.0.8/tinymce.min.js"></script>
<div id="app"></div>
</body>
</html>

3.组件 Tinymce

文件目录

在这里插入图片描述

src/components/Tinymce/index.vue

1
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
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
<template>
<div
class="tinymce-container editor-container"
:class="{fullscreen:fullscreen}"
>
<textarea :id="tinymceId" class="tinymce-textarea" />
<div class="editor-custom-btn-container">
<multiple-upload class="editor-upload-btn" @success="imageSuccess" />
</div>
</div>
</template>

<script>
import axios from "axios";

// MultipleUpload组件见 https://blog.csdn.net/qq_39953537/article/details/100039094

import MultipleUpload from "@/components/MultipleUpload";
import plugins from "./plugins"; // 见下文
import toolbar from "./toolbar"; // 见下文

// 上传html片段接口根据自己项目更换
import { uploadHtml } from "@/api/upload";
export default {
name: "Tinymce",
components: {
MultipleUpload
},
props: {
// 默认填充到富文本的html文件
html: {
type: String,
default: ""
},
toolbar: {
type: Array,
default() {
return [];
}
},
menubar: {
type: Boolean,
default: false
},
height: {
type: Number,
default: 400
}
},
data() {
return {
hasChange: false,
hasInit: false,
tinymceId: "vue-tinymce-" + +new Date(),
fullscreen: false,
value: "",
editorContent: ""
};
},
watch: {
value(val) {
this.$nextTick(() =>
window.tinymce.get(this.tinymceId).setContent(val || "")
);
},
html(val) {
if (this.isUrl) {
this.loadUrl(val);
}
}
},
created() {
if (this.html && this.html.startsWith("http")) {
this.loadUrl(this.html);
} else {
this.value = this.html + "";
this.editorContent = this.html + "";
}
},
mounted() {
this.initTinymce();
},
activated() {
this.initTinymce();
},
deactivated() {
this.destroyTinymce();
},
destroyed() {
this.destroyTinymce();
},
methods: {
initTinymce() {
window.tinymce.init({
fontsize_formats: "12px 14px 16px 18px 20px 24px 36px",
language: "zh_CN",
language_url: "/static/tinymce_5.0.8/langs/zh_CN.js",
selector: `#${this.tinymceId}`,
height: this.height,
body_class: "panel-body ",
object_resizing: true,
toolbar: this.toolbar.length > 0 ? this.toolbar : toolbar,
menubar: this.menubar,
plugins: plugins,
end_container_on_empty_block: true,
powerpaste_word_import: "clean",
code_dialog_height: 450,
code_dialog_width: 1000,
advlist_bullet_styles: "square",
advlist_number_styles: "default",
default_link_target: "_blank",
link_title: false,
init_instance_callback: editor => {
if (this.value) {
editor.setContent(this.value);
}
this.hasInit = true;
editor.on("NodeChange Change KeyUp SetContent", () => {
this.hasChange = true;
this.$emit("input", editor.getContent());
this.editorContent = editor.getContent();
});
},
setup(editor) {
editor.on("FullscreenStateChanged", e => {
this.fullscreen = e.state;
});
}
});
},
destroyTinymce() {
if (window.tinymce.get(this.tinymceId)) {
window.tinymce.get(this.tinymceId).destroy();
}
},
loadUrl(url) {
if (url && url.length > 0) {
axios
.get(url)
.then(response => {
// 处理HTML显示
this.value = response.data;
this.editorContent = response.data;
this.$emit("subLoadUrlToHtml", response.data);
this.$emit("input", response.data);
})
.catch(() => {
this.value = "服务器数据加载失败,请重试!";
});
}
},
// 设置编辑器内容
setContent(value) {
window.tinymce.get(this.tinymceId).setContent(value);
},
// 获取编辑器内容
getContent() {
window.tinymce.get(this.tinymceId).getContent();
},
// 图片上传成功后填充到富文本编辑器
async imageSuccess(urlList) {
try {
let imageTemplateList = "";
urlList.forEach(item => {
const image = `<img style="max-width:100%;" src="${item}">`;
imageTemplateList = imageTemplateList + image;
});
window.tinymce.get(this.tinymceId).insertContent(imageTemplateList);
this.$message({
message: "上传成功!",
type: "success"
});
} catch (error) {
console.log(error);
this.$message({
message: error,
type: "error"
});
}
},
// 编辑器内容上传到cos,调用返回url
async content2Url() {
try {
const res = await uploadHtml(this.editorContent);
return res;
} catch (error) {
this.$message({
message: error.data.message,
type: "error"
});
}
}
}
};
</script>

<style lang="scss">
#tinymce {
background-color: blue;
p {
margin: 0;
}
}
.tinymce-container {
position: relative;
}
.tinymce-container >>> .mce-fullscreen {
z-index: 10000;
}
.tinymce-textarea {
visibility: hidden;
z-index: -1;
}
.editor-custom-btn-container {
position: absolute;
right: 4px;
top: 4px;
/*z-index: 2005;*/
}
.fullscreen .editor-custom-btn-container {
z-index: 10000;
position: fixed;
}
.editor-upload-btn {
display: inline-block;
}
// 隐藏底部logo栏
.mce-edit-area + .mce-statusbar {
opacity: 0;
height: 0;
}
</style>

src/components/Tinymce/plugins.js

1
2
3
4
5
const plugins = [
"advlist anchor autolink autosave code codesample directionality emoticons fullscreen hr image imagetools importcss insertdatetime link lists media nonbreaking noneditable pagebreak preview print save searchreplace spellchecker tabfocus table template textpattern visualblocks visualchars wordcount paste"
];

export default plugins;

src/components/Tinymce/toolbar.js

1
2
3
4
5
const toolbar = [
"formatselect fontsizeselect forecolor backcolor bold italic underline strikethrough alignleft aligncenter alignright outdent indent removeformat hr undo redo"
];

export default toolbar;

4.使用

1
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
<template>
<div>
<tinymce ref="tinymce" :height="500" :html="html" @input="getContent" />
</div>
</template>

<script>
import Tinymce from "@/components/Tinymce";
export default {
name: "GoodsForm",
components: {
Tinymce
},
data() {
return {
html:
"https://ebusiness-1255313385.cosbj.myqcloud.com/image/20190823/center2019082304054532.html",
content: ""
};
},
methods: {
// 获取编辑器内容
getContent(content) {
this.content = content;
},
// 编辑器内容转换成在线url
async getcontent2Url() {
try {
const htmlUrl = await this.$refs.tinymce.content2Url();
return htmlUrl;
} catch (error) {
console.log(error);
}
}
}
};
</script>

5.使用效果
在这里插入图片描述

参考链接

1.https://github.com/PanJiaChen/vue-element-admin/blob/master/src/components/Tinymce/index.vue

2.tinymce 中文文档