Vue.js简单集成ACE代码编辑器
你有一个 textarea 输入框,你妄想用它来输入代码,结果到头来发现太难受了,一没高亮,二没提示,三没法精细化控制。此时此刻不得不考虑集成一款代码编辑器来替换掉弱鸡的 textarea 了,这里我使用 ACE 编辑器。说点废话啊,关于使用 Vue 集成 ACE,踩坑无数,搜到的资料比较零碎,大部分集中在引入方式和是否经过 webpack 打包有关。这一块我搜到的资料基本没有派上用场,他喵的都是
你有一个 textarea 输入框,你妄想用它来输入代码,结果到头来发现太难受了,一没高亮,二没提示,三没法精细化控制。此时此刻不得不考虑集成一款代码编辑器来替换掉弱鸡的 textarea 了,这里我使用 ACE 编辑器。
说点废话啊,关于使用 Vue 集成 ACE,踩坑无数,搜到的资料比较零碎,大部分集中在引入方式和是否经过 webpack
打包有关。这一块我搜到的资料基本没有派上用场,他喵的都是我自己摸索出来的。而且我不想用别人封装过的(例如:vue2-ace-editor
或者 brace),我就是想自己摸索摸索。
一、安装
npm install ace-builds --save-dev
安装完的效果,可以看到有 4 个版本 ACE,分别是 src、src-min、src-min-noconflict、src-noconflict,这里统一使用最后一个,即 src-noconflict 版本:
二、集成
在 Vue 项目的预编译入口文件 main.js 中引入 ACE 模块:
import ace from 'ace-builds'
Vue.use(ace)
创建一个 Vue 组件,将来只需引用该组件即可正常使用 ACE 编辑器了。先看一下 HTML 代码段:
<template>
<div class="ace-container">
<!-- 官方文档中使用 id,这里禁止使用,在后期打包后容易出现问题,使用 ref 或者 DOM 就行 -->
<div class="ace-editor" ref="ace"></div>
</div>
</template>
还需要在 mounted 钩子中初始化好这个 ACE 编辑器:
<script>
import ace from 'ace-builds'
import 'ace-builds/webpack-resolver' // 在 webpack 环境中使用必须要导入
import 'ace-builds/src-noconflict/theme-monokai' // 默认设置的主题
import 'ace-builds/src-noconflict/mode-javascript' // 默认设置的语言模式
export default {
mounted () {
this.aceEditor = ace.edit(this.$refs.ace, {
maxLines: 20, // 最大行数,超过会自动出现滚动条
minLines: 10, // 最小行数,还未到最大行数时,编辑器会自动伸缩大小
fontSize: 14, // 编辑器内字体大小
theme: this.themePath, // 默认设置的主题
mode: this.modePath, // 默认设置的语言模式
tabSize: 4 // 制表符设置为 4 个空格大小
})
},
data () {
return {
aceEditor: null,
themePath: 'ace/theme/monokai', // 不导入 webpack-resolver,该模块路径会报错
modePath: 'ace/mode/javascript' // 同上
}
}
}
</script>
关于导入“webpack-resolver”,ACE 官方是这么说的:
其实这样子算是把 ACE 组件写好了,只是是最简单的版本,那怎么用呢?我这里使用全局导入。我在项目中的 components 目录中创建一个文件“install.js”,该文件专门用来管理安装自定义的组件。
// 这个就是自己写的 ACE 组件
import ACE from './packages/dynamic-form/custom-ace.vue'
export default {
install: function (Vue, options) {
// 引号中的字符串就是之后的组件标签名,即 <ace></ace>
Vue.component('ace', ACE)
}
}
在 main.js 中挂载到全局:
import ace from 'ace-builds'
import componentsInstall from './components/install'
Vue.use(ace)
Vue.use(componentsInstall)
在需要用到的地方就可以这么写:
<ace></ace>
看看效果如何:
三、进阶
我在 ACE 官网中发现了内置小书签的编辑器,传送门。当点击右下角的小书签时,会弹出一个蒙层,可以选择各种各样的属性配置,比如选择编辑器的语言模式,主题,是否换行,字体大小等等。如何使用的,这里我给一个别人写好的链接:传送门。不过我这里情况不太一样,不能使用这种方式,前面那个人的方式其实就是官方的,运行时动态创建一个
<template>
<div class="ace-container">
<div class="ace-editor" ref="ace"></div>
<!-- toggleConfigPanel:打开/关闭蒙层 -->
<div class="bookmarklet" @click="toggleConfigPanel"></div>
</div>
</template>
CSS 样式:
<style lang='scss' scoped>
.ace-container {
position: relative;
.bookmarklet {
position: absolute;
right: 0;
bottom: 0;
width: 20px;
height: 20px;
z-index: 2;
cursor: pointer;
border-width: 9px;
border-style: solid;
border-color: lightblue gray gray rgb(206, 173, 230);
border-image: initial;
}
}
</style>
看下效果,你会发现和官方的是一模一样的,哈哈,CSS 样式后面 4 个我就是从官方那里抄过来的,当然是一样的了:
这个小书签的点击事件“toggleConfigPanel”,记得先定义一个变量 toggle,用来表示打开/关闭:
toggleConfigPanel () {
this.toggle = !this.toggle
}
接下来写一个自定义蒙层,继续扩展 HTML 代码,蒙层中放置两个属性,切换语言模式和是否换行,大家可以自行扩展:
<template>
<div class="ace-container">
<div class="ace-editor" ref="ace"></div>
<div class="config-panel" v-show="toggle">
<div>
<div class="item">
<label class="title">语言</label>
<el-select class="value" v-model="modePath" @change="handleModelPathChange" size="mini" value-key="name">
<el-option v-for="mode in modeArray"
:key="mode.name"
:label="mode.name"
:value="mode.path">
</el-option>
</el-select>
</div>
<div class="item">
<label class="title">换行</label>
<el-select class="value" v-model="wrap" @change="handleWrapChange" size="mini" value-key="name">
<el-option v-for="wrap in wrapArray"
:key="wrap.name"
:label="wrap.name"
:value="wrap.value">
</el-option>
</el-select>
</div>
</div>
</div>
<!-- toggleConfigPanel:打开/关闭蒙层 -->
<div class="bookmarklet" @click="toggleConfigPanel"></div>
</div>
</template>
config-panel 的 CSS 代码:
.config-panel {
position: absolute;
right: 0;
bottom: 0;
width: 50%;
height: 100%;
overflow: scroll;
box-shadow: grey -5px 2px 3px;
background-color: rgba(255, 255, 255, 0.5);
z-index: 1;
.item {
margin: 10px auto;
text-align: center;
.title {
color: white;
margin: 0 10px;
font-size: 14px;
}
}
}
语言模式和换行需要事先定义好数组,再去 data 函数中定义变量:
const wrapArray = [{
name: '开启',
value: true
}, {
name: '关闭',
value: false
}]
const modeArray = [{
name: 'JavaScript',
path: 'ace/mode/javascript'
}, {
name: 'HTML',
path: 'ace/mode/html'
}, {
name: 'CSS',
path: 'ace/mode/css'
}, {
name: 'SCSS',
path: 'ace/mode/scss'
}, {
name: 'Json',
path: 'ace/mode/json'
}, {
name: 'Java',
path: 'ace/mode/java'
}, {
name: 'Text',
path: 'ace/mode/text'
}]
实现 change 事件,使得修改属性生效:
handleModelPathChange (modelPath) {
this.aceEditor.getSession().setMode(modelPath)
},
handleWrapChange (wrap) {
this.aceEditor.getSession().setUseWrapMode(wrap)
}
看下效果,当切换不同语言的时候,高亮规则将自动修改:
完成上述代码之后,你不禁会想,既然能输入代码了,能不能加上自动提示?是可以的,官方支持了。
上文中支持了 7 种语言,具体能支持什么语言,在你项目的 “node_modules” → “ace-builds” → “src-conflict” → 所有以 “mode-” 开头的 js 文件都写了,可以自行对语言数组进行扩展。题外话,所有以 “theme-” 开头的表示当前能用的主题,“ext-” 开头的表示一些扩展工具。在 “src-noconflict” 中有一个名字叫做 “snippets” 的目录,该目录定义所有语言的自动提示规则,这就是我们想要的。
在代码中导入 7 种语言的自动提示规则:
import 'ace-builds/src-noconflict/snippets/javascript'
import 'ace-builds/src-noconflict/snippets/html'
import 'ace-builds/src-noconflict/snippets/css'
import 'ace-builds/src-noconflict/snippets/scss'
import 'ace-builds/src-noconflict/snippets/json'
import 'ace-builds/src-noconflict/snippets/java'
import 'ace-builds/src-noconflict/snippets/text'
在 mounted 钩子中添加激活自动提示的代码:
this.aceEditor.setOptions({
enableSnippets: true,
enableLiveAutocompletion: true,
enableBasicAutocompletion: true
})
出乎你想象,到这里就可以了,我们看下效果:
上文也说了,我们在需要用到的地方使用 标签就可以展示该编辑器了,但是光展示没有用啊,我们要获取到其中的值,接下来就所说如何实时获取值。
首先我们在 mounted 钩子中添加绑定事件的代码:
// 绑定一个 change 事件,调用 change 方法
this.aceEditor.getSession().on('change', this.change)
change 方法:
change () {
this.$emit('input', this.aceEditor.getSession().getValue())
}
修改 标签如下所示,记得先去定义一个 content 变量:
<ace v-model="content"></ace>
自定义的 ACE 组件内部就可以利用 props 来获取上文的 content 值:
props: {
value: String
}
修改 mounted 钩子中初始化 ACE 编辑器的方法:
// 外界的 content 值到这里就是 value 值,默认必须为 '',不得为 null、undefined,否则编辑器报错
this.aceEditor = ace.edit(this.$refs.ace, {
maxLines: 20,
minLines: 10,
fontSize: 14,
value: this.value ? this.value : '',
theme: this.themePath,
mode: this.modePath,
wrap: this.wrap,
tabSize: 4
})
好了,到此为止就都完成了,既能高亮、又能自动提示、还能获取并派发值,一个能满足最基本使用的编辑器就集成好了,如果想深入,官方的 API 文档中详细着呢,大家自行研究。如果大家不想看上面那些跳来跳去的代码,我把自定义的 ACE 组件代码完整贴出来。
四、完整代码加粗样式
<template>
<div class="ace-container">
<div class="ace-editor" ref="ace"></div>
<div class="config-panel" v-show="toggle">
<div>
<div class="item">
<label class="title">语言</label>
<el-select class="value" v-model="modePath" @change="handleModelPathChange" size="mini" value-key="name">
<el-option v-for="mode in modeArray"
:key="mode.name"
:label="mode.name"
:value="mode.path">
</el-option>
</el-select>
</div>
<div class="item">
<label class="title">换行</label>
<el-select class="value" v-model="wrap" @change="handleWrapChange" size="mini" value-key="name">
<el-option v-for="wrap in wrapArray"
:key="wrap.name"
:label="wrap.name"
:value="wrap.value">
</el-option>
</el-select>
</div>
</div>
</div>
<div class="bookmarklet" @click="toggleConfigPanel"></div>
</div>
</template>
<script>
import ace from 'ace-builds'
import 'ace-builds/src-noconflict/snippets/javascript'
import 'ace-builds/src-noconflict/snippets/html'
import 'ace-builds/src-noconflict/snippets/css'
import 'ace-builds/src-noconflict/snippets/scss'
import 'ace-builds/src-noconflict/snippets/json'
import 'ace-builds/src-noconflict/snippets/java'
import 'ace-builds/src-noconflict/snippets/text'
import 'ace-builds/webpack-resolver'
import 'ace-builds/src-noconflict/ext-language_tools'
import 'ace-builds/src-noconflict/theme-monokai'
import 'ace-builds/src-noconflict/mode-javascript'
// const themeArray = [{
// name: 'monokai',
// path: 'ace/theme/monokai'
// }]
const wrapArray = [{
name: '开启',
value: true
}, {
name: '关闭',
value: false
}]
const modeArray = [{
name: 'JavaScript',
path: 'ace/mode/javascript'
}, {
name: 'HTML',
path: 'ace/mode/html'
}, {
name: 'CSS',
path: 'ace/mode/css'
}, {
name: 'SCSS',
path: 'ace/mode/scss'
}, {
name: 'Json',
path: 'ace/mode/json'
}, {
name: 'Java',
path: 'ace/mode/java'
}, {
name: 'Text',
path: 'ace/mode/text'
}]
export default {
props: {
value: String
},
mounted () {
this.aceEditor = ace.edit(this.$refs.ace, {
maxLines: 20,
minLines: 10,
fontSize: 14,
value: this.value ? this.value : '',
theme: this.themePath,
mode: this.modePath,
wrap: this.wrap,
tabSize: 4
})
// 激活自动提示
this.aceEditor.setOptions({
enableSnippets: true,
enableLiveAutocompletion: true,
enableBasicAutocompletion: true
})
this.aceEditor.getSession().on('change', this.change)
},
data () {
return {
aceEditor: null,
toggle: false,
wrap: true,
themePath: 'ace/theme/monokai',
modePath: 'ace/mode/javascript',
modeArray: modeArray,
wrapArray: wrapArray
}
},
methods: {
toggleConfigPanel () {
this.toggle = !this.toggle
},
change () {
this.$emit('input', this.aceEditor.getSession().getValue())
},
handleModelPathChange (modelPath) {
this.aceEditor.getSession().setMode(modelPath)
},
handleWrapChange (wrap) {
this.aceEditor.getSession().setUseWrapMode(wrap)
}
}
}
</script>
<style lang='scss' scoped>
.ace-container {
position: relative;
.config-panel {
position: absolute;
right: 0;
bottom: 0;
width: 50%;
height: 100%;
overflow: scroll;
box-shadow: grey -5px 2px 3px;
background-color: rgba(255, 255, 255, 0.5);
z-index: 1;
.item {
margin: 10px auto;
text-align: center;
.title {
color: white;
margin: 0 10px;
font-size: 14px;
}
}
}
.bookmarklet {
position: absolute;
right: 0;
bottom: 0;
width: 20px;
height: 20px;
z-index: 2;
cursor: pointer;
border-width: 9px;
border-style: solid;
border-color: lightblue gray gray rgb(206, 173, 230);
border-image: initial;
}
}
</style>
更多推荐
所有评论(0)