分享我的发现、想法与心得

0%

前端手札——vue组件vue-tinymce开发经验分享

唠叨

最近公司在开发一个社交管理后台,看一遍线框图后发现需要富文本编辑器我便找会上两年开发的vue-tinymce组件,可惜的是组件支持还是vue1,所以这个组件需要升级支持vue2。然后有朋友问我为何不用现有的?因为看一圈回来发觉比较不靠谱的啊,全部都需要赋予id值(明明可以内部处理的为何要外部传入?),实在看不下去结果还是完善自己写的这个没多少收藏的库吧:)

关于 vue-tinymce

vue-tinymce 只是基于tinymce封装的vue组件,让用vue的同学能快速使用tinymce富文本编辑器。

过程

从tinymce开始

接下来分享一些开发过程中的一些问题,首先要学会初次化,我们先来看看tinymce的官方例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!DOCTYPE html>
<html>
<head>
<script src="https://cloud.tinymce.com/stable/tinymce.min.js"></script>
</head>
<body>
<textarea>Next, get a free TinyMCE Cloud API key!</textarea>
<script>
tinymce.init({
selector:'textarea'
//or
// target: document.querySelector('textarea')
});
</script>
</body>
</html>

能看出tinymce需要引入全局才能使用,就没其他方式了?于是我找了一下npmjs.org有的有的,可以用import引入。

于是不用想立马写个例子试试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# index.html
<!DOCTYPE html>
<html>
<body>
<textarea>Next, get a free TinyMCE Cloud API key!</textarea>
<script src="dist/main.js"></script>
</body>
</html>

# main.js
import tinymce form 'tinymce';
tinymce.init({
selector:'textarea'
//or
// target: document.querySelector('textarea')
});

结果发现tinymce是加载出来了,但是样式和图标那些没了…好吧不折腾还是直接引入吧目前来说问题不大。(看看其他库都是直接引入,我不折腾算是对了)

好了,第一步完成了,接下来第二步是获得/设定富文本内容,来看看以下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# main.js
tinymce.init({
selector: 'textarea',
// 获得editor,当有多个textarea实例时会多次调用setup
setup: (editor)=> {
// 初次化编辑器
editor.on('init', ()=>{
// 设置默认值
editor.setContent('<p>Default Value!</p>');
// 注册事件
editor.on('input change undo redo', ()=>{
// 获得编辑结果
console.log(editor.getContent());
});
});
}
})

上面这段是已总结怎样获得或设置富文本内容,tinymce知道怎样用就能开始写vue组件。

需要怎样的vue组件

作为组件配置当然可以自己设定的固需要setting的传入,可能也需要在初次化动手再自定义一些功能所以加上setup,再来是获得editor进行处理一些富文本数据。起步代码是这样的:

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
<template>
<textarea :id="id"></textarea>
</template>
<script>
export default {
props: ['setting','setup', 'value'],
data(){ return {id:'tinymce', editor:null}; }
mounted(){
const setting = {
...this.setting,
{
selector: `#${this.id}`,
setup: (editor)=> {
this.setup(editor);
this.editor = editor;
editor.on('init', ()=>{
editor.setContent(this.value);
editor.on('input change undo redo', ()=>{
this.value = editor.getContent();
});
});
}
}
};
tinymce.init(setting);
},
beforeDestroy: function(){
tinymce.remove(this.id);
}
}
</script>

自管理id

对比其他vue-tinymce组件都要传入id我感到很不解,因为根本没这个必要,所以接下来先解决id自管理问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
export default {
...
// 这里我用render写在template绑定:id一样可以
render(createElement){
return createElement('textarea', {
attrs: {
id: this.id
}
});
},
data(){
return {
//生成id
id: 'vue-tinymce-'+Date.now(),
}
}
...
}

支持v-model双向绑定

这个简单,只要传入字段(props)包含value,使用v-model就能从value获得绑定数据,然后当富文本编辑器数据跟新时使用$emit('input', value)方法便能通知变化跟新value。

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
export default {
...
watchs:{
value(val){
// 当传入值变化时跟新富文本内容
tinymce.get(this.id).setContent(val);
}
},
mounted(){
const setting = {
...this.setting,
{
selector: `#${this.id}`,
setup: (editor)=> {
this.setup(editor);
this.editor = editor;
editor.on('init', ()=>{
editor.setContent(this.value);
editor.on('input change undo redo', ()=>{
this.$emit('input', editor.getContent());
});
});
}
}
};
tinymce.init(setting);
}
...
}

到这里将近完成了,可惜这次问题静静地出现了:输入一个字光标就刷到最前面。接下来得解决这问题,思路我猜应该是editor的input事件触发$emit('input')然后进入watch调用了editor.setContent()方法后导致光标更新,这里解决办法是当前编辑不让触发editor.seContent()就不会导致光标更新(当然还有其他方法,比如记录光标位置)。

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
const INIT = 0;
const INPUT = 1;
const CHANGED = 2;

export default {
...
watchs:{
value(val){
// 只在外部引起变化时才跟新编辑器
if(this.status === CHANGED || selt.status === INIT) return this.status = INPUT;
tinymce.get(this.id).setContent(val);
}
},
mounted(){
const setting = {
...this.setting,
{
selector: `#${this.id}`,
setup: (editor)=> {
this.setup(editor);
this.editor = editor;
editor.on('init', ()=>{
editor.setContent(this.value);
editor.on('input change undo redo', ()=>{
// 只在用户输入导致事件相应时才更新value数据
if(this.status === INPUT || this.status === INIT) return this.status = CHANGED;
this.$emit('input', editor.getContent());
});
});
}
}
};
tinymce.init(setting);
}
...
}

当value从外部更新时才更新编辑器内容,编辑器触发的内容更新并不需要绕一圈回来再更新编辑器,这样便能解决光标问题。

结果

就在这vue-tinymce,一些细节不补充,建议看源码。以下是使用方法:

安装

1
$ npm i -D lpreterite/vue-tinymce

使用

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
# index.html
<div id="app">
<vue-tinymce
ref="tinymce"
v-model="content"
:setting="setting">
</vue-tinymce>
</div>
<!-- in last -->
<script src="node_modules/tinymce/tinymce.min.js"></script>

# main.js
import Vue from 'vue';
import VueTinymce from 'vue-tinymce.vue';

new Vue({
el: '#app',
data: function(){
return {
content: '<p>html content</p>',
setting: {
height: 200,
language_url: "langs/zh_CN.js",
block_formats: "Paragraph=p;Heading 1=h1;Heading 2=h2;Heading 3=h3;Heading 4=h4;Heading 5=h5;Heading 6=h6;"
}
}
}
})

目录结构

1
2
3
4
5
6
7
dist/
- index.html
- main.js
- lang/
-zh_CN.js
node_modules/
- tinymce/

遇到的问题

刚开始想写vue2组件我跑了一圈github也没发现比较好的例子,构建工具及配置直接能用的并没有,参考的倒是找到一些。webpack配置算是个麻烦事,想尽量简化工作就得动动脑子。

vue-cli是个好东西,能帮你快速创建项目,如想创建vue的单页项目可以这样使用:

1
$ vue init webpack-simple my-product

可是没见到有快速创建vue组件的项目,所以这里我写了一个lpreterite/vue-component-project提供给大家使用。

使用方法:

1
$ vue init lpreterite/vue-component-project my-vue-component

一路回车之后会提示
1
2
3
4
5
6
7
8
vue-cli · Generated "my-vue-component".

To get started:

cd my-vue-component
npm install
npm run dev
npm run hot.

项目就这样创建好来👌,剩下交给你们发挥。

这遍文章算是把手上这个大项目的副产品吧 :) 。希望日后有点时间继续分享其他在经验及一些大项目下的组件,欢迎评论和PR!