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

帕奇式

唠叨

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

关于 vue-tinymce

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

过程

从tinymce开始

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

<!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引入。

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

# 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是加载出来了,但是样式和图标那些没了...好吧不折腾还是直接引入吧目前来说问题不大。(看看其他库都是直接引入,我不折腾算是对了)

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

# 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进行处理一些富文本数据。起步代码是这样的:

<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自管理问题。

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。

export default {
    ...
    watch:{
        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()就不会导致光标更新(当然还有其他方法,比如记录光标位置)。

const INIT = 0;
const INPUT = 1;
const CHANGED = 2;

export default {
    ...
    watch:{
        value(val){
            // 只在外部引起变化时才跟新编辑器
            if(this.status === CHANGED || this.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,一些细节不补充,建议看源码。以下是使用方法:

安装

$ npm i -D lpreterite/vue-tinymce

使用

# 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;"
            }
        }
    }
})

目录结构

dist/
- index.html
- main.js
- lang/
    -zh_CN.js
node_modules/
- tinymce/

遇到的问题

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

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

$ vue init webpack-simple my-product

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

使用方法:

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

一路回车之后会提示

   vue-cli · Generated "my-vue-component".

   To get started:
   
     cd my-vue-component
     npm install
     npm run dev 
     npm run hot.

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

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

阅读 24.8k

帕奇的手札
推敲、实验、搞破坏是每个孩子必经的阶段;)

设计和管理是毕生的课题👨‍💻

856 声望
66 粉丝
0 条评论

设计和管理是毕生的课题👨‍💻

856 声望
66 粉丝
文章目录
宣传栏