使用vue2-ace-editor实现可选择的代码编辑器

最近在琢磨前端,因项目中需要在页面上编辑代码,所以需要写一个代码编辑器供用户使用。找了几个编辑器相关的组件,对比了下感觉还是vue2-ace-editor用着舒服,写了demo供大家参考。

由于我的项目使用的是vue2,二开鹅厂的bk-ci(蓝盾),因此基础组件使用的是bk组件。大家可以把选择框、按钮这些替换成自己使用的组件(如element-ui等)

组件已经封装好可以直接到手使用,支持更换主题、切换语言、缩进、搜索和替换(编辑器内Ctrl+F)、复制,json美化等...(后期加了字体大小选择和全屏功能,拉到底部查看代码)

效果图:

首先,到项目中安装插件:

npm install --save-dev vue2-ace-editor

安装成功后,引用组件,可在main.js中全局引用:

import Editor from 'vue2-ace-editor'
Vue.component('editor', Editor)

或直接在代码中局部引用(如我这里直接代码中引用):

<template>
    <div>
        <bk-card title="代码编辑器">
            <div style="display: flex;  width: 100%; margin-right: 20px; margin-bottom: 10px;">
                
                <bk-select
                    :disabled="false"
                    placeholder="请选择主题"
                    search-placeholder="搜索主题"
                    v-model="valueTheme"
                    style="width: 150px; margin-right: 20px;"
                    @change="selectTheme"
                    searchable>
                    <bk-option v-for="option in listTheme"
                        :key="option"
                        :id="option"
                        :name="option">
                    </bk-option>
                </bk-select>

                <bk-select
                    :disabled="false"
                    placeholder="请选择语言"
                    search-placeholder="搜索语言"
                    v-model="valueCodeLang"
                    style="width: 150px; margin-right: 20px;"
                    @change="selectLang"
                    :font-size="large"
                    searchable>
                    <bk-option v-for="option in listCodeLang"
                        :key="option"
                        :id="option"
                        :name="option">
                    </bk-option>
                </bk-select>

                <bk-button theme="primary" icon="script-file" class="mr10" @click="copyCode()"> 复制代码</bk-button>
                <bk-button v-if="valueCodeLang === 'json'" theme="primary" icon="eye" class="mr10" @click="formatCode()"> 美化代码</bk-button>

            </div>

            <editor
                ref="aceEditor"
                v-model="content"
                @init="editorInit"
                width="100%"
                height="400px"
                :lang="lang"
                :theme="theme"
                :options="{
                    enableBasicAutocompletion: true,
                    enableSnippets: true,
                    enableLiveAutocompletion: true,
                    tabSize: 6, fontSize: 14,
                    readOnly: readOnly,//设置是否只读
                    showPrintMargin: false //去除编辑器里的竖线
                }"
            ></editor>
        </bk-card>
        
    </div>
</template>

<script>
    export default {
        components: {
            editor: require('vue2-ace-editor')
        },
        props: {
            // 是否只读
            readOnly: {
                type: Boolean,
                default: false
            },
            // 要展示的代码
            codeData: {
                type: String,
                default: ''
            },
            // 默认的主题
            valueTheme: {
                type: String,
                default: 'dracula'
            },
            // 默认的语言
            valueCodeLang: {
                type: String,
                default: 'json'
            }
        },
        data () {
            return {
                listTheme: [
                    'dracula',
                    'chrome',
                    'chaos',
                    'clouds',
                    'clouds_midnight',
                    'xcode',
                    'monokai',
                    'ambiance',
                    'dreamweaver',
                    'eclipse',
                    'github',
                    'idle_fingers'
                ],
                listCodeLang: [
                    'json',
                    'yaml',
                    'xml',
                    'java',
                    'text',
                    'javascript',
                    'scheme',
                    'lua',
                    'mysql',
                    'perl',
                    'powershell',
                    'python',
                    'ruby',
                    'sql',
                    'hjson',
                    'ini'
                ],
            
                content: '',
                theme: '',
                lang: ''
            }
        },
        mounted () {
            // 初始化编辑器
            this.editorInit()
            // 初始化主题、语言
            this.theme = this.valueTheme
            this.lang = this.valueCodeLang
            // 若传输代码,则展示代码
            if (this.codeData) {
                console.log(this.codeData)
                this.$refs.aceEditor.editor.setValue(this.codeData)
            }
        },
        methods: {
            selectTheme (newValue, oldValue) {
                if (newValue) {
                    this.theme = newValue
                }
            },
            selectLang (newValue, oldValue) {
                if (newValue) {
                    this.lang = newValue
                }
            },
            editorInit () { // 初始化
                require('brace/ext/language_tools')
                require('brace/ext/beautify')
                require('brace/ext/error_marker')
                require('brace/ext/searchbox')
                require('brace/ext/split')

                // 循坏加载语言,通过点击按钮切换
                for (let s = 0; s < this.listCodeLang.length; s++) {
                    require('brace/snippets/' + this.listCodeLang[s])
                }
                for (let j = 0; j < this.listCodeLang.length; j++) {
                    require('brace/mode/' + this.listCodeLang[j])
                }

                // 循坏加载主题,通过点击按钮切换
                for (let i = 0; i < this.listTheme.length; i++) {
                    require('brace/theme/' + this.listTheme[i])
                }
            },

            copyCode () {
                const code = this.$refs.aceEditor.editor.getValue()
                
                // 复制到剪切板
                if (navigator.clipboard) {
                    navigator.clipboard.writeText(code)
                    // 复制成功 给提示 此处省略
                } else {
                    // 复制失败 给提示 此处省略
                    alert('您的浏览器不支持自动复制,请手动复制')
                }
            },

            formatCode () {
                const string = JSON.stringify(JSON.parse(this.$refs.aceEditor.editor.getValue()), null, 2)
                this.$refs.aceEditor.editor.setValue(string)
            }
        
            // getValue () { // 获取编辑器中的值
            //     console.log('编辑器中第一个换行符的位置:' + this.$refs.aceEditor.editor.getValue().indexOf('\n'))
            // }
        }
    }
</script>

<style>

</style>

开发中遇到关于 'Unexpected token '<'' 的报错的话,要先加载 “brace/snippets/” 再加载 “brace/mode/” 。顺序不对就会报错!


由于这个组件用到其他弹框等页面时,编辑器会显的有点小,不方便看代码修改代码,因此加了一个全屏显示的功能,效果图如下:

注意点:全屏后由于页面z-index会被设为最大值,所以下拉框会被覆盖,点击无法正常显示,若使用的是element,vue2(element-ui)的select组件可以用 popper-append-to-body='false' 解决
vue3(element plus)的select组件可以用 teleported='false' 解决。可以根据是否全屏来更改值。若改不了,全屏时就把这几个下拉框隐藏掉。

遇到一个小bug,假如当前字体大小是20,全屏后字体会自动变为12。全屏时20,退出全屏一样会变为12,至今未找到原因。。。因此做此下策:大小改变时,就直接把字体大小定为12

代码如下:

<template>
    <div ref="screenFull">
        <bk-card title="代码编辑器" :show-head="false">
            <div style="display: flex; width: 100%; margin-right: 20px; margin-bottom: 10px;">
                <el-select
                    v-model="valueTheme"
                    :value-key="'valueTheme'"
                    placeholder="选择主题"
                    style="width: 130px; margin-right: 20px;"
                    filterable="true"
                    clearable="true"
                    @change="selectTheme"
                    :popper-append-to-body="false"
                >
                    <el-option
                        v-for="item in listTheme"
                        :key="item"
                        :label="item"
                        :value="item">
                    </el-option>
                </el-select>

                <el-select
                    v-model="valueCodeLang"
                    :value-key="'valueCodeLang'"
                    placeholder="选择语言"
                    style="width: 120px; margin-right: 20px;"
                    filterable="true"
                    clearable="true"
                    @change="selectLang"
                    :popper-append-to-body="false"
                >
                    <el-option
                        v-for="item in listCodeLang"
                        :key="item"
                        :label="item"
                        :value="item">
                    </el-option>
                </el-select>

                <el-select
                    v-model="valueFontSize"
                    :value-key="'valueFontSize'"
                    placeholder="字体大小"
                    style="width: 100px; margin-right: 20px;"
                    filterable="true"
                    clearable="true"
                    @change="selectFontSize"
                    :popper-append-to-body="false"
                >
                    <el-option
                        v-for="item in listFontSize"
                        :key="item"
                        :label="item"
                        :value="item">
                    </el-option>
                </el-select>

                <bk-button theme="primary" icon="script-file" class="mr10" @click="copyCode()"> 复制</bk-button>
                <bk-button v-if="valueCodeLang === 'json'" theme="primary" icon="eye" class="mr10" @click="formatCode()"> 美化</bk-button>

                <bk-button theme="primary" :icon=" fullScreen ? 'un-full-screen' : 'full-screen' " class="mr10" @click="screen()"> {{ fullScreen ? '退出全屏' : '全屏' }}</bk-button>
                
            </div>
            <div style="display: flex; overflow: auto;">
                <editor
                    ref="aceEditor"
                    v-model="content"
                    @init="editorInit"
                    width="100%"
                    style="min-height:300px;"
                    :height="editorHight"
                    :lang="lang"
                    :theme="theme"
                    :options="{
                        enableBasicAutocompletion: true,
                        enableSnippets: true,
                        enableLiveAutocompletion: true,
                        tabSize: 4, fontSize: fontSize,
                        readOnly: readOnly,//设置是否只读
                        showPrintMargin: false //去除编辑器里的竖线
                    }"
                ></editor>
            </div>
            
        </bk-card>
        
    </div>
</template>

<script>
    export default {
        components: {
            editor: require('vue2-ace-editor')
        },
        props: {
            // 编辑框高度
            editorHight: {
                type: Number,
                default: 300
            },
            // 是否只读
            readOnly: {
                type: Boolean,
                default: false
            },
            // 要展示的代码
            codeData: {
                type: String,
                default: ''
            },
            // 默认的主题
            valueTheme: {
                type: String,
                default: 'clouds',
                validator: function (value) { // 自定义验证函数
                    if (this.listTheme.includes(value)) {
                        return value
                    } else {
                        return 'clouds'
                    }
                }
            },
            // 默认的语言
            valueCodeLang: {
                type: String,
                default: 'json',
                validator: function (value) { // 自定义验证函数
                    if (this.listCodeLang.includes(value)) {
                        return value
                    } else {
                        return 'text'
                    }
                }
            },
            // 默认的语言
            valueFontSize: {
                type: Number,
                default: 12
            }
        },
        data () {
            return {
                listTheme: [
                    'clouds',
                    'clouds_midnight',
                    'dracula',
                    'chrome',
                    'chaos',
                    'xcode',
                    'monokai',
                    'ambiance',
                    'dreamweaver',
                    'eclipse',
                    'github',
                    'idle_fingers'
                ],
                listCodeLang: [
                    'json',
                    'yaml',
                    'xml',
                    'java',
                    'text',
                    'javascript',
                    'scheme',
                    'lua',
                    'mysql',
                    'perl',
                    'powershell',
                    'python',
                    'ruby',
                    'sql',
                    'hjson',
                    'ini'
                ],

                listFontSize: [
                    10, 12, 14, 16, 20
                ],
            
                content: '',
                theme: '',
                lang: '',
                fontSize: 12,
                tmpFontSize: 12,

                fullScreen: false,
                unFullEditorHight: 0, // 非全屏状态下的编辑器高度
                appendToBody: true
            }
        },
        created () {
            // 初始化主题、语言、大小
            this.theme = this.valueTheme
            this.lang = this.valueCodeLang
            this.fontSize = this.tmpFontSize = this.valueFontSize
        },
        mounted () {
            // 初始化编辑器
            this.editorInit()
            this.unFullEditorHight = this.editorHight // 缓存浏览器高度

            // 若传输代码,则展示代码
            if (this.codeData) {
                console.log(this.codeData)
                this.$refs.aceEditor.editor.setValue(this.codeData)
            }

            // ESC按键事件无法监听,监听窗口变化
            window.onresize = () => {
                this.fullScreen = this.checkFull()
            }
        },

        methods: {
            selectTheme (newValue) {
                if (newValue) {
                    this.theme = newValue
                }
            },
            selectLang (newValue) {
                if (newValue) {
                    this.lang = newValue
                }
            },
            selectFontSize (newValue) {
                if (newValue) {
                    this.fontSize = newValue
                    this.tmpFontSize = newValue
                }
            },
            editorInit () { // 初始化
                require('brace/ext/language_tools')
                require('brace/ext/beautify')
                require('brace/ext/error_marker')
                require('brace/ext/searchbox')
                require('brace/ext/split')

                // 循坏加载语言,通过点击按钮切换
                for (let s = 0; s < this.listCodeLang.length; s++) {
                    require('brace/snippets/' + this.listCodeLang[s])
                }
                for (let j = 0; j < this.listCodeLang.length; j++) {
                    require('brace/mode/' + this.listCodeLang[j])
                }

                // 循坏加载主题,通过点击按钮切换
                for (let i = 0; i < this.listTheme.length; i++) {
                    require('brace/theme/' + this.listTheme[i])
                }
            },

            copyCode () {
                const code = this.$refs.aceEditor.editor.getValue()
                let message, theme
                // 复制到剪切板
                if (navigator.clipboard) {
                    navigator.clipboard.writeText(code)
                    
                    message = '复制成功'
                    theme = 'success'
                } else {
                    message = '您的浏览器不支持自动复制,请手动复制'
                    theme = 'error'
                }

                this.$bkMessage({
                    message,
                    theme
                })
            },

            formatCode () {
                const code = this.$refs.aceEditor.editor.getValue()
                console.log(code)
                if (code !== '') {
                    const string = JSON.stringify(JSON.parse(code), null, 2)
                    this.$refs.aceEditor.editor.setValue(string)
                }
            },
        
            // getValue () {
            // 获取编辑器中的值
            //     console.log('第一个换行符的位置:' + this.$refs.aceEditor.editor.getValue().indexOf('\n'))
            // }

            checkFull () { // 用来监听编辑器大小是否变化
                // 火狐浏览器
                const isFull
                    = document.mozFullScreen
                        || document.fullScreen
                        // 谷歌浏览器及Webkit内核浏览器
                        || document.webkitIsFullScreen
                        || document.webkitRequestFullScreen
                        || document.mozRequestFullScreen
                        || document.msFullscreenEnabled

                console.log(isFull)
                console.log(!!isFull)
                if (!isFull) { // false时 :非全屏
                    this.editorHight = this.unFullEditorHight
                }

                return !!isFull
            },
            screen () {
                this.valueFontSize = this.fontSize = 12 // 大小变更后初始化文字大小(不定义也会变为12 组件bug)
                const element = this.$refs.screenFull

                if (this.fullScreen) {
                    this.appendToBody = true
                    this.editorHight = this.unFullEditorHight
                    // 关闭全屏
                    if (document.exitFullscreen) {
                        document.exitFullscreen()
                    } else if (document.webkitCancelFullScreen) {
                        document.webkitCancelFullScreen()
                    } else if (document.mozCancelFullScreen) {
                        document.mozCancelFullScreen()
                    } else if (document.msExitFullscreen) {
                        document.msExitFullscreen()
                    }
                } else {
                    this.appendToBody = false
                    // 全屏
                    this.editorHight = window.screen.availHeight - 50
                    if (element.requestFullscreen) {
                        element.requestFullscreen()
                    } else if (element.webkitRequestFullScreen) {
                        element.webkitRequestFullScreen()
                    } else if (element.mozRequestFullScreen) {
                        element.mozRequestFullScreen()
                    } else if (element.msRequestFullscreen) {
                        // IE11
                        element.msRequestFullscreen()
                    }
                }

                this.fullScreen = !this.fullScreen

                // setTimeout(() => {
                //     console.log(this.tmpFontSize)
                //     this.fontSize = this.tmpFontSize
                // }, 200)
            }
            
        }
    }
</script>

<style>

</style>

  • 11
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值