下载:npm install --save-dev vue3-ace-editor
封装:
- components文件夹下新建aceBuilds文件夹
- 新建ace.config.js文件配置插件所需要的功能
- 新建aceEditor.vue文件初始化并封装插件
- 新建aceEditorFullScrenn.vue文件基于aceEditor.vue文件再次封装,实现弹框编辑
ace.config.js配置项
import ace from 'ace-builds';
import modeJsonUrl from 'ace-builds/src-noconflict/mode-json?url';
ace.config.setModuleUrl('ace/mode/json', modeJsonUrl);
import modeJavascriptUrl from 'ace-builds/src-noconflict/mode-javascript?url';
ace.config.setModuleUrl('ace/mode/javascript', modeJavascriptUrl);
import modeHtmlUrl from 'ace-builds/src-noconflict/mode-html?url';
ace.config.setModuleUrl('ace/mode/html', modeHtmlUrl);
import modeYamlUrl from 'ace-builds/src-noconflict/mode-yaml?url';
ace.config.setModuleUrl('ace/mode/yaml', modeYamlUrl);
import themeGithubUrl from 'ace-builds/src-noconflict/theme-github?url';
ace.config.setModuleUrl('ace/theme/github', themeGithubUrl);
import themeChromeUrl from 'ace-builds/src-noconflict/theme-chrome?url';
ace.config.setModuleUrl('ace/theme/chrome', themeChromeUrl);
import themeMonokaiUrl from 'ace-builds/src-noconflict/theme-monokai?url';
ace.config.setModuleUrl('ace/theme/monokai', themeMonokaiUrl);
import workerBaseUrl from 'ace-builds/src-noconflict/worker-base?url';
ace.config.setModuleUrl('ace/mode/base', workerBaseUrl);
import workerJsonUrl from 'ace-builds/src-noconflict/worker-json?url';
ace.config.setModuleUrl('ace/mode/json_worker', workerJsonUrl);
import workerJavascriptUrl from 'ace-builds/src-noconflict/worker-javascript?url';
ace.config.setModuleUrl('ace/mode/javascript_worker', workerJavascriptUrl);
import workerHtmlUrl from 'ace-builds/src-noconflict/worker-html?url';
ace.config.setModuleUrl('ace/mode/html_worker', workerHtmlUrl);
import workerYamlUrl from 'ace-builds/src-noconflict/worker-yaml?url';
ace.config.setModuleUrl('ace/mode/yaml_worker', workerYamlUrl);
import snippetsHtmlUrl from 'ace-builds/src-noconflict/snippets/html?url';
ace.config.setModuleUrl('ace/snippets/html', snippetsHtmlUrl);
import snippetsJsUrl from 'ace-builds/src-noconflict/snippets/javascript?url';
ace.config.setModuleUrl('ace/snippets/javascript', snippetsJsUrl);
import snippetsYamlUrl from 'ace-builds/src-noconflict/snippets/yaml?url';
ace.config.setModuleUrl('ace/snippets/javascript', snippetsYamlUrl);
import snippetsJsonUrl from 'ace-builds/src-noconflict/snippets/json?url';
ace.config.setModuleUrl('ace/snippets/json', snippetsJsonUrl);
import 'ace-builds/src-noconflict/ext-language_tools';
ace.require("ace/ext/language_tools");
aceEditor.vue封装代码编辑器
<template>
<div class="editor-container" :style="{ width: cW, height: cH }">
<slot></slot>
<div
ref="editorEle"
class="editAceWrap"
:class="className"
:style="{ width: cW, height: cH }"
></div>
</div>
</template>
<script setup name="VueAceEditor">
import ace from 'ace-builds';
import { computed, onBeforeUnmount, watch } from "vue";
const { proxy } = getCurrentInstance();
let props = defineProps({
lang: {
type: String,
default: "",
},
theme: {
type: String,
default: "monokai",
},
fontSize: {
type: Number,
default: 14,
},
content: {
type: String,
default: "",
},
width: {
type: [String, Number],
default: "100%",
},
height: {
type: [String, Number],
default: "100%",
},
className: {
type: String,
default: "vue-ace-editor",
},
options: {
type: Object,
default: () => ({
enableBasicAutocompletion: true,
enableLiveAutocompletion: true,
enableSnippets: true,
enableEmmet: true,
useElasticTabstops: true,
enableChromevoxEnhancements: true,
enableLinking: true,
spellcheck: true,
tabSize: 2
}),
},
wrap: {
type: String,
default: "free",
},
readonly: {
type: Boolean,
default: false,
},
showPrintMargin: {
type: Boolean,
default: false,
},
});
let editor = ref(null);
let session = ref(null);
const cH = computed(() => {
if (typeof props.height === "number") return props.height + "px";
if (typeof props.height === "string") return props.height;
return "";
});
let cW = computed(() => {
if (typeof props.width === "number") return props.width + "px";
if (typeof props.width === "string") return props.width;
return "";
});
let emits = defineEmits([
"init",
"onFocus",
"update:content",
"onBlur",
"onChange",
"onInput",
"onCopy",
"onPaste",
]);
watch(()=>props.fontSize,()=>{
setFontSize();
})
watch(()=>props.theme,()=>{
setTheme();
})
watch(()=>props.lang,()=>{
setMode();
})
watch(()=>props.height,()=>{
nextTick(() => {
resize();
});
})
watch(()=>props.width,()=>{
nextTick(() => {
resize();
});
})
watch(()=>props.content,()=>{
nextTick(() => {
setValue();
});
})
// watch: {
// fontSize() {
// this.setFontSize();
// },
// theme() {
// this.setTheme();
// },
// lang() {
// this.setMode();
// },
// height() {
// this.$nextTick(() => {
// this.resize();
// });
// },
// width() {
// this.$nextTick(() => {
// this.resize();
// });
// },
// content(val) {
// this.setValue();
// }
// },
onBeforeUnmount(() => {
editor.destroy();
editor.container.remove();
});
onMounted(() => {
init();
});
function init() {
editor = ace.edit(proxy.$refs.editorEle);
editor.$blockScrolling = Infinity;
session.value = editor.getSession();
emits("init", editor);
setMode();
setTheme();
setFontSize();
setOptions();
setOption();
setValue();
setReadonly();
setShowPrintMargin();
editor.on("focus", onFocus);
editor.on("blur", onBlur);
editor.on("copy", onCopy);
editor.on("paste", onPaste);
editor.on("change", onChange);
editor.on("input", onInput);
}
function setMode() {
session.value.setMode(`ace/mode/${props.lang}`);
session.value.setUseWrapMode(true);
return editor;
}
function setTheme() {
editor.setTheme(`ace/theme/${props.theme}`);
return editor;
}
function setFontSize() {
editor.setFontSize(props.fontSize);
return editor;
}
function setValue() {
session.value.setValue(props.content);
return editor;
}
function getValue() {
return editor.getValue();
}
function setOptions() {
editor.setOptions(props.options);
return editor;
}
function setOption() {
editor.setOption("wrap", props.wrap);
return editor;
}
function setReadonly() {
editor.setReadOnly(props.readonly);
return editor;
}
function setShowPrintMargin() {
editor.setShowPrintMargin(props.showPrintMargin);
return editor;
}
function setCompleteData(data) {
ace.acequire("ace/ext/language_tools").addCompleter({
getCompletions(editor, session, pos, prefix, callback) {
return callback(null, data);
},
});
}
function setAutocomplete() {
editor.execCommand("startAutocomplete");
return editor;
}
function getRowCol() {
return editor.selection.getCursor();
}
function onFocus() {
emits("onFocus", editor);
}
function onBlur() {
emits("update:content", getValue());
emits("onBlur", editor);
}
function onChange() {
emits("onChange", editor);
}
function onInput() {
emits("onInput", editor);
}
function onCopy() {
emits("onCopy", editor);
}
function onPaste() {
emits("onPaste", editor);
}
function resize() {
editor.resize();
return editor;
}
function setRange(startRow, startCol, endRow, endCol) {
const Range = ace.acequire("ace/range").Range;
return new Range(startRow, startCol, endRow, endCol);
}
/**
* @param(r) type:new Range()
*/
function getTextRange(r) {
return session.value.getTextRange(r);
}
function save(storageName) {
localStorage[storageName] = getValue();
session.value.getUndoManager().markClean();
return editor;
}
function undo() {
editor.undo();
return editor;
}
function redo() {
editor.redo();
return editor;
}
defineExpose({
session,
setValue,
getValue
})
</script>
<style scoped>
.editor-container {
position: relative;
}
.editAceWrap{
/* touch-action: none; */
}
:deep(.ace_scrollbar-inner){
background-color: #272822;
}
:deep(.ace_scrollbar-v){
background-color: #fff;
}
</style>
aceEditorFullScrenn.vue文件基于aceEditor.vue文件再次封装,实现弹框编辑
<template>
<el-row style="width: 100%">
<el-dialog
ref="dialog"
append-to-body
:close-on-click-modal="false"
draggable
width="900px"
:title="title"
v-model="isFullscreen"
top="8vh"
>
<vue-ace-editor
ref="aceEditor1"
v-bind="$attrs"
:content="innerValue"
:height="dialogBodyHeight"
:readonly="disabled"
@onBlur="aceEditorInput"
/>
<template #footer>
<span class="dialog-footer">
<el-button type="primary" @click="isFullscreen = false"
>关 闭</el-button
>
</span>
</template>
</el-dialog>
<el-col class="flex-sub">
<vue-ace-editor
ref="aceEditor2"
v-bind="$attrs"
:content="innerValue"
:readonly="disabled"
@onBlur="aceEditorInput"
/>
</el-col>
<el-col v-if="!noFullScreen" class="text-right full-screen-btn">
<el-button
icon="FullScreen"
type="text"
class="padding-0 large"
title="在新窗口显示"
@click="isFullscreen = true"
/>
</el-col>
</el-row>
</template>
<script setup name="FullScreenAceEditor">
import "./ace.config.js";
const { proxy } = getCurrentInstance();
let props = defineProps({
title: {
type: String,
default: "",
},
content: {
type: String,
default: "",
},
noFullScreen: {
type: Boolean,
default: false,
},
disabled: {
type: Boolean,
default: false,
},
});
let isFullscreen = ref(false);
let innerValue = ref("");
let dialogBodyHeight = ref(0);
let emits = defineEmits(["update:content"]);
let inputValue = ref("");
watch(
() => props.content,
(newValue) => {
nextTick(() => {
inputValue.value = newValue;
proxy.$refs.aceEditor2.session.setValue(newValue);
});
},
{ immediate: true }
);
watch(inputValue, (val) => {
emits("update:content", val);
});
watch(isFullscreen, (val) => {
if (val) {
nextTick(() => {
// const { height } = proxy.$refs.dialog.$el.getBoundingClientRect();
// if (height) {
// dialogBodyHeight.value = height - 80;
// }
dialogBodyHeight.value = 600;
proxy.$refs.aceEditor1.session.setValue(inputValue.value);
});
}
});
function aceEditorInput(editor) {
emits("update:content", editor.getValue());
}
function resize() {
proxy.$refs?.aceEditor1?.resize?.();
proxy.$refs?.aceEditor2?.resize?.();
}
</script>
<style lang="scss" scoped>
.full-screen-btn {
width: 30px;
position: absolute;
right: 10px;
top: -2px;
z-index: 2000;
}
</style>
使用:
import FullScreenAceEditor from "@/components/vue-ace/full-screen-ace-editor";
<full-screen-ace-editor
lang="json"
v-model:content="绑定值"
height="300px"
title="弹框标题"
/>