• wangeditor富文本编辑器使用(详细)


    1.下载安装

    yarn add wangeditor -S
    npm i wangeditor

    2.导入

    import E from ‘wangeditor’;

    3. 具体使用

    3.1 页面使用

    <editor  ref="editor"
             v-model="submitData.content"
             :img-sep="editorImgSep"
             :req-file-name="reqFileName"
             :upload-params="editorUploadParams"
             :custom-upload-img="customUploadImg"
             v-bind="ruleConfig.content.fieldProps" />
     import Editor from '@bus_comp/business/editor/index.vue';
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    3.2 富文本编辑器相关文件

    src/components/business/editor/

    3.2.1 custom_btns/full_screen.ts

    /**
     * @description: 全屏按钮
     */
    
    import E from 'wangeditor';
    let { BtnMenu } = E;
    export class FullScreen extends BtnMenu {
        constructor (editor) {
            let $elem = E.$(
                `<div class="w-e-menu">
                     <a class="_editor_btn_fullscreen" data-fullsreen-status="false">
                         <i class="w-e-icon-fullscreen"></i>
                     </a>
                 </div>`
            );
            super($elem, editor);
        }
     
        clickHandler () {
            let sirEditor = document.querySelector('#SirEditor');
            let classes = sirEditor?.className;
            if (!classes) {
                 sirEditor?.className = 'fullscreen-editor';
            } else {
                let classArr = classes.split(' ');
                if (!classArr.includes('fullscreen-editor')) {
                    classArr.push('fullscreen-editor');
                } else {
                    let findIndex = classArr.findIndex(item => item === 'fullscreen-editor');
                    classArr.splice(findIndex, 1);
                }
                 sirEditor?.className = classArr.join(' ');
            }
            let iel = document.querySelector('#SirEditor' + ' ._editor_btn_fullscreen i');
            let ielClasses = iel?.className;
            let ielClassesArr = ielClasses?.split(' ');
            if (ielClassesArr.includes('w-e-icon-fullscreen')) {
                 iel?.classList.remove('w-e-icon-fullscreen');
                 iel?.classList.add('w-e-icon-fullscreen_exit');
            } else {
                 iel?.classList.remove('w-e-icon-fullscreen_exit');
                 iel?.classList.add('w-e-icon-fullscreen');
            }
        }
     
        tryChangeActive () {
            this.active();
        }
    }
    
    • 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

    3.2.2 2. const.ts

    /**
     * @description: 富文本编辑器配置
     */
    
    
    type MENU_TYPE =
        | 'head'
        | 'bold'
        | 'fontSize'
        | 'fontName'
        | 'italic'
        | 'underline'
        | 'strikeThrough'
        | 'foreColor'
        | 'backColor'
        | 'link'
        | 'list'
        | 'justify'
        | 'quote'
        | 'emoticon'
        | 'image'
        | 'table'
        | 'video'
        | 'code'
        | 'undo'
        | 'fullscreen'
        | 'lineHeight'
        | 'indent'
        | 'splitLine'
        | 'todo'
        | 'redo';
    interface MenuTipItem {
        menuItem: MENU_TYPE; // 菜单的menu
        class?: string; // 实际对应的className 样式命名不一样按照menuItem...
        tip: string; // title悬浮提示
    }
    
    // 这里只为那些悬浮上去没有选择菜单的按钮加提示
    export const MENU_TIPS: MenuTipItem[] = [
        {
            menuItem: 'bold',
            tip: _('粗体')
        },
        {
            menuItem: 'italic',
            tip: _('斜体')
        },
        {
            menuItem: 'underline',
            tip: _('下划线')
        },
        {
            menuItem: 'strikeThrough',
            tip: _('删除线')
        },
        {
            menuItem: 'link',
            tip: _('插入链接')
        },
        {
            menuItem: 'justify',
            tip: _('对齐方式')
        },
        {
            menuItem: 'quote',
            class: 'quotes-left',
            tip: _('引用')
        },
        {
            menuItem: 'code',
            tip: _('插入代码')
        },
        {
            menuItem: 'image',
            tip: _('插入图片')
        },
        {
            menuItem: 'table',
            class: 'table2',
            tip: _('插入表格')
        },
        {
            menuItem: 'undo',
            tip: _('撤销')
        },
        {
            menuItem: 'redo',
            tip: _('重复')
        },
        {
            menuItem: 'fullscreen',
            tip: _('全屏')
        },
        {
            menuItem: 'indent',
            tip: _('缩进')
        },
        {
            menuItem: 'splitLine',
            tip: _('分割线')
        },
        {
            menuItem: 'todo',
            tip: _('待办事项')
        },
        {
            menuItem: 'lineHeight',
            tip: _('行高')
        }
    ];
    
    const FULLSCREEN_TIP = {
        isFullscreen: _('退出全屏'),
        notFullscreen: _('全屏')
    };
    export const VIEWER_TIP = {
        FULLSCREEN_TIP
    };
    
    /**
     * 文件大小单位
     */
    const BYTE = 1024;
    export const CN = {
        trillion: BYTE * BYTE // 1M
    };
    
    export const DEFAULT_FILE_SIZE = 200;
    
    
    
    • 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

    3.2.3 editor.vue

    <template>
        <div id="SirEditor"
             class="editor-box">
            <div :id="editorBar"
                 ref="toolbar"
                 class="toolbar"></div>
            <div ref="textArea"
                 class="text-area"
                 @mousewheel="_onDomMouseWheel">
                <div :id="editorElem"
                     class="editor-content"></div>
                <!--用来模拟placeholder-->
                <textarea
                    v-show="showTip"
                    ref="input"
                    v-model.trim="tip"
                    class="placeholder-tip form-control"
                    :placeholder="placeholder"
                    @blur="_onDomBlur"
                    @focus="_onDomFocus"></textarea>
            </div>
        </div>
    </template>
    
    <script>
    
    /**
     * @description: 富文本编辑器-组件
     */
    
    import { removeErrTip, showErrTip } from '../utils/index';
    import E from 'wangeditor';
    import { uuid } from '../utils/uuid';
    import lodashIsFunction from 'lodash/isFunction';
    import { MENU_TIPS, CN } from './const';
    import { FullScreen } from './custom_btns/full_screen';
    
    // import xss from 'xss';
    /* eslint-disable */
    export default {
        props: {
            richContent: {
                // 文本内容
                type: String,
                default: ''
            },
            placeholder: {
                type: String,
                default: _('请输入正文')
            },
            menus: {
                // 菜单配置,默认全部显示
                type: Array,
                default() {
                    return [];
                }
            },
            isBase64: {
                // 是否以Base64形式存储
                type: Boolean,
                default: false
            },
            customUploadImg: {
                // 是否完全自定义上传
                type: Function,
                default: null
            },
            callback: Function, // 自定义上传回调
    
            serverUrl: {
                // 上传情况的上传地址
                type: String,
                default: ''
            },
            beforeUpload: Function, // 插件方式上传图片之前的校验
    
            reqFileName: {
                // 上传图片请求图片名
                type: String,
                defaule: 'file'
            },
            acceptImgs: {
                // 富文本支持上传图片格式
                type: Array,
                default: () => ['png', 'jpg', 'jpeg', 'bmp']
            },
            uploadImgParams: {
                // 配置上传参数,默认会被添加到formdata中。
                type: Object,
                default() {
                    return {};
                }
            },
            imgSep: {
                type: String,
                default: 'file:'
            },
            uploadHeaders: {
                type: Object,
                default() {
                    return {};
                }
            },
            menuTooltipPosition: {
              type: String,
              default: 'down'
            },
            paramsWithUrl: {
                // 将参数拼接到 url 中
                type: Boolean,
                default: false
            },
            maxSize: {
                // 不传就是不限制大小
                type: Number,
                default: 0
            },
            timeout: {
                // 上传超时时间
                type: Number,
                default: 5000
            },
            maxNum: {
                // 最多上传几张
                type: Number,
                default: 0
            },
            showLinkImg: {
                // 是否显示“网络图片”tab,默认显示
                type: Boolean,
                default: false
            },
            blankText: {
                type: String,
                default: _('该输入项不允许为空')
            },
            clickFocus: {
                // 点击立即聚焦富文本,粘贴图片会需要
                type: Boolean,
                default: false
            },
            checkBase64: Function,
            showMenusTip: {
                type: Boolean,
                default: false
            },
            menusTips: {
                // 给工具栏的按钮增加title悬浮提示
                type: Array,
                default() {
                    return MENU_TIPS;
                }
            },
            preventScroll: {
                type: Boolean,
                default: false
            }
        },
    
        data() {
            return {
                editorBar: uuid(),
                editorElem: uuid(),
                tipTime: 2000, // 提示显示的时间
                editorContent: '', /
    
    • 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

    3.2.4 index.vue

    <template>
        <div class="sir-editor-wrapper"
             :class="prefixCls">
            <editor
                ref="sirEditor"
                class="sir-editor"
                :rich-content.sync="editorContent"
                :show-link-img="showLinkImg"
                :server-url="uploadUrl"
                :before-upload="beforeUpload"
                :upload-img-params="uploadParams"
                :custom-upload-img="customUploadImg"
                :req-file-name="reqFileName"
                show-menus-tip
                :menus="menus"
                :max-num="fileNumber"
                :img-sep="imgSep"
                :click-focus="true"
                :placeholder="placeholder"
                @update:richContent="handleChange" />
        </div>
    </template>
    
    <script lang="ts">
    
    /**
     * @description: 富文本编辑器
     */
    
    import { Component, Prop, Vue, Watch } from 'vue-property-decorator';
    import { API_PREFIX } from '@common/const/const';
    import { DEFAULT_FILE_SIZE, CN } from './const';
    
    import Editor from './editor.vue';
    
    @Component({
        name: 'SirEditor',
        components: {
            Editor
        },
        model: {
            prop: 'value',
            event: 'change'
        }
    })
    export default class SirEditor extends Vue {
        @Prop(String) private readonly value?: string;
    
        @Prop(String) private readonly placeholder?: string;
    
        @Prop(String) private readonly imgSep?: string;
    
        @Prop({ default: false }) private readonly showLinkImg?: boolean;
    
        @Prop({ default: () => ({}) }) private readonly uploadParams?: object;
    
        @Prop({ default: '' }) private readonly prefixCls?: string;
    
        @Prop(Number) private readonly fileSize?: number;
    
        @Prop(Number) private readonly fileNumber?: number;
    
        @Prop({
            type: String,
            default: 'file'
        }) 
        private readonly reqFileName?: number;
    
        @Prop({
            type: Function,
            default: null
        }) 
        private readonly customUploadImg?;
    
        /** 内容 */
        private editorContent?: string | null = null;
    
        /** 文件上传路径 */
        private uploadUrl?: string = API_PREFIX + '/attachments';
        private menus = [
            'head', // 标题
            'bold', // 粗体
            'fontSize', // 字号
            'fontName', // 字体
            'italic', // 斜体
            'underline', // 下划线
            'strikeThrough', // 删除线
            'foreColor', // 文字颜色
            'backColor', // 背景颜色
            'link', // 插入链接
            'list', // 列表
            'justify', // 对齐方式
            'image', // 插入图片
            'quote', // 引用
            // 'emoticon', // 表情
            'table', // 表格
            // 'video', // 插入视频
            // 'code', // 插入代码
            'undo', // 撤销
            'redo', // 重复
            'lineHeight', // 行高
            'indent', // 增加缩进/减少缩进
            'fullscreen', // 全屏
            'splitLine', // 分割线
            'todo' // 待办事项
        ]; // 富文本toolbar配置
        @Watch('value', { immediate: true })
        private handleValueChange (value: string) {
    
            // 防止在编辑时,触发watch
            if (value !== this.editorContent) {
                let sirEditor: any = this.$refs.sirEditor;
                if (sirEditor) {
                    sirEditor.setJsonValue(value);
                }
                this.editorContent = value || null;
            }
        }
    
        beforeUpload (xhr, editor, files) {
            const FILE_SIZE = this.fileSize || DEFAULT_FILE_SIZE;
            const IMG_MAX_SIZE = FILE_SIZE * CN.trillion; // 单张不超过5M
            let vm = this as any;
            let currentImgs = vm.getContentImgs();
            let curImgLength = currentImgs.length;
            if (this.fileNumber) {
                let canUploadCount = this.fileNumber - curImgLength;
    
                if (canUploadCount <= 0) {
                    
                    // 为0 不能再上传了
                    vm.$warn(_(`最多上传{0}个文件`, this.fileNumber), { autoHide: true });
                    return true;
                }
    
                // 截取可上传的总数
                if (files.length > canUploadCount) {
                    files = files.slice(0, canUploadCount);
                    vm.$warn(_(`最多上传{0}个文件`, this.fileNumber), { autoHide: true });
                }
            }
    
            if (files.some(fi
    
    • 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

    3.2.5 viewer.vue

    <!-- eslint-disable vue/no-v-html -->
    <template>
        <div class="sir-editor-viewer w-e-text">
            <div v-html="content"></div>
        </div>
    </template>
    <script>
    
    /**
     * @description: 富文本编辑器-viewer查看页面
     */
    
    import { VIEWER_TIP } from './const';
    
    export default {
        name: 'SirEditorViewer',
        props: {
            allowFullscreen: {
    
                // 是否允许全屏
                type: Boolean,
                default: true
            },
            toolbarDirection: {
    
                // 工具栏的位置,left or right
                type: String,
                default: 'right'
            },
            maxHeight: {
    
                // 最大高度
                type: String,
                default: '300px'
            },
            content: {
    
                // 文本内容
                type: String,
                default: ''
            }
        },
        data () {
            return {
                isFullscreen: false,
                VIEWER_TIP
            };
        },
        computed: {
            gettoolbarDirection () {
                return this.toolbarDirection === 'left' ? 'left' : 'right';
            },
            fullscreenText () {
                return this.isFullscreen ? VIEWER_TIP.FULLSCREEN_TIP.isFullscreen : VIEWER_TIP.FULLSCREEN_TIP.notFullscreen;
            }
        },
        methods: {
            toggleFullscreen () {
                this.isFullscreen = !this.isFullscreen;
            },
            onCopy () {
                this.$ok(_('复制成功'));
            },
            onError () {
                this.$fail(_('复制失败'));
            }
        }
    };
    </script>
    <style lang="less" scoped>
    /* stylelint-disable */
    @toolbar-height: 40px;
    
    .sir-editor-viewer {
        position: relative;
        display: inline-block;
        width: 100%;
        min-height: calc(@toolbar-height + 2px);
        padding: 0;
        cursor: pointer;
    
        &.fullscreen {
            position: fixed !important;
            top: 0 !important;
            left: 0 !important;
            z-index: 9999;
            display: flex;
            flex-direction: column;
            width: 100% !important;
            height: 100% !important;
            background-color: white;
    
            .content-box {
                max-height: 100% !important;
            }
    
            .toolbar-wrapper {
                position: static;
                display: block;
                border-bottom: 1px solid rgb(211, 211, 211);
    
                .toolbar-item {
                    background-color: transparent;
                }
            }
        }
    
        &:hover {
            &:not(.fullscreen) {
                .toolbar-wrapper {
                    display: block;
                }
            }
        }
    
        .toolbar-wrapper {
            position: absolute;
            top: 0;
            right: 0;
            left: 0;
            z-index: 100;
            display: none;
            height: @toolbar-height;
            line-height: @toolbar-height;
    
            &.left {
                .toolbar-item {
                    float: left;
                }
            }
    
            &.right {
                .toolbar-item {
                    float: right;
                }
            }
    
            .toolbar-item {
                width: @toolbar-height;
                height: 100%;
                color: rgb(129, 129, 129);
                text-align: center;
                background-color: #f6f6f6a8;
                transition: all 0.2s;
    
                &:hover {
                    color: #333;
                    background-color: #e4e4e4;
                }
            }
        }
    
        .content-box {
            padding: 6px 10px;
            overflow: auto;
        }
        /deep/ li {
            list-style: inherit;
        }
    }
    </style>
    
    
    • 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
  • 相关阅读:
    Ion Stoica:做成Spark和Ray两个明星项目的秘笈
    LAMP部署
    boomYouth
    【实验记录1】行人重识别
    Leetcode 2866. Beautiful Towers II
    AOP是什么? AOP底层原理、AOP的术语
    轻量级消息队列 Django-Q 轻度体验
    Linux——(第五章)用户管理
    Spark的任务调度
    Polygon L2扩容方案揭秘
  • 原文地址:https://blog.csdn.net/qq_44613011/article/details/125481183