相信写OJ的小伙伴都会遇到一个问题,那就是在线编辑器的实现。到底要选哪个呢
其实开源的编辑器有挺多的比如Ace、CodeMirror、MonacoEditor、CodeFlask、EditArea。详细的可以看看这个Top 5: 用Javascript编写的最佳代码编辑器插件, Top 5: Best code editor plugins written in Javascript
我第一个用的是MonacoEditor,这东西对webpack最新版跟webpack会起冲突,当时搞了我一下午,我也不知道前一天晚上是怎么成功的。。。
接着我又翻到了一些较为详细的Ace使用文档,然后对比了一下发现与AcWing是一样的,hhh。这让我瞬间来了动力。记得当时是先用了基于vue二次开发的,但是文档都不是很清楚,所以后来就直接用官方的了。顺带提一下qdoj用的是CodeMirror。
话不多说我们先看看效果图吧(完全和AcWing一样,2333
我用的是Element-ui,其实这些实现起来不是特别难,但是对于我这个后端废物来说,还是花了我很多时间的,有一说一,前端还是挺麻烦的。
那接着就上代码吧
我就只给编辑器的代码好了,不然太多就显得一团糟,毕竟我还没把它们分出来,哈哈哈
vue2版本
// 首先html部分就这么一行,ref是vue选中标签需要给上的属性,看到的文档都说不要用id,说是会有问题,感兴趣的小伙伴也可以试试
<div ref="ace" class="ace"></div>
<script>
// 导入需要的包
import ace from 'ace-builds'
import 'ace-builds/webpack-resolver'; // 在 webpack 环境中使用必须要导入
//解决添加提示时控制台警告(提示必须引入的包)
import "ace-builds/src-noconflict/ext-language_tools"
import "ace-builds/src-noconflict/ext-emmet"
//语言提示
import 'ace-builds/src-noconflict/snippets/javascript'
import 'ace-builds/src-noconflict/snippets/c_cpp'
import 'ace-builds/src-noconflict/snippets/java'
import 'ace-builds/src-noconflict/snippets/golang'
import 'ace-builds/src-noconflict/snippets/python'
//输入类型
import 'ace-builds/src-noconflict/keybinding-emacs'
import 'ace-builds/src-noconflict/keybinding-vim'
import 'ace-builds/src-noconflict/keybinding-vscode'
//以下是在vue中使用,其余的自己根据文档更改
export default {
name: 'CodeEditor',
props: {
value: {
type: String,
required: true
}
},
data() {
return {
themeValue: 'ambiance',
aceEditor: null,
themePath: 'ace/theme/monokai', // 不导入 webpack-resolver,该模块路径会报错
modePath: 'ace/mode/c_cpp', // 同上
codeValue: this.value || ''
}
}
mounted() {
this.aceEditor = ace.edit(this.$refs.ace,{
maxLines: 1000, // 最大行数,超过会自动出现滚动条
minLines: 22, // 最小行数,还未到最大行数时,编辑器会自动伸缩大小
fontSize: 14, // 编辑器内字体大小
theme: this.themePath, // 默认设置的主题
mode: this.modePath, // 默认设置的语言模式
tabSize: 4, // 制表符设置为 4 个空格大小
readOnly: false, //只读
highlightActiveLine: true,
value: this.codeValue
});
this.aceEditor.setOptions({
enableSnippets: true,
enableLiveAutocompletion: true,
enableBasicAutocompletion: true
});
// 快捷键
// this.aceEditor.commands.addCommand({
// name: 'myCommand',
// bindKey: {win: 'Ctrl-M', mac: 'Command-M'},
// exec: function(editor) {
// //...
// },
// readOnly: true // false if this command should not apply in readOnly mode
// });
}
}
</script>
<style scoped lang="scss">
.ace {
position: relative !important;
border: 1px solid lightgray;
margin: auto;
height: auto;
width: 100%;
}
</style>
引用组件时记得绑定value
<CodeEditor v-bind:value="''"></CodeEditor>
想了一下还是给全部代码吧,因为我没有拆分,所以全部代码就是我截图的整个页面
<template>
<div class="Ace">
<div class="code_tool_bar">
<span style="position: relative;left: 10px; top: 16px; font-size: 18px;">写代码啦</span>
<el-button icon="el-icon-s-tools" @click="dialogTableVisible = true" style="float: right;margin: 10px;"></el-button>
<el-button icon="el-icon-refresh" @click="refresh_code" style="float: right; margin: 10px;"></el-button>
<el-select v-model="selectLanguageValue" @change="changSelectValue1" filterable style="float: right;margin: 10px;">
<el-option
v-for="item in languagesOptions"
:key="item.label"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
<el-dialog :visible.sync="dialogTableVisible"
:append-to-body="true"
top="40px"
width="39.194%"
:destroy-on-close="true"
:show-close="false"
custom-class="code-editor-config-dialog">
<el-card style="margin: -59px -20px 0 -20px;"
shadow="never">
<div slot="header" class="clearfix">
<span>代码编辑器设置</span>
<el-button style="float: right; padding: 3px 0" type="text" @click="dialogTableVisible = false">x</el-button>
</div>
<div class="row">
<el-row>
<el-col :span="16">
<div class="code-editor-option-title">主题</div>
<div class="code-editor-option-description">对白色界面感到厌倦了吗?可以尝试其他的背景和代码高亮风格。</div>
</el-col>
<el-col :span="8">
<el-select v-model="selectThemeValue"
@change="changSelectValue"
filterable>
<el-option
v-for="item in themesOptions"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</el-col>
</el-row>
<hr>
</div>
<div class="row">
<el-row>
<el-col :span="16">
<div class="code-editor-option-title">编辑类型</div>
<div class="code-editor-option-description">更喜欢Vim或者Emacs的输入方式吗?我们也为你提供了这些选项。</div>
</el-col>
<el-col :span="8">
<el-select v-model="selectEditorValue"
@change="setEditorMode"
filterable>
<el-option
v-for="item in editorOption"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</el-col>
</el-row>
<hr>
</div>
<div class="row">
<el-row>
<el-col :span="16">
<div class="code-editor-option-title">缩进长度</div>
<div class="code-editor-option-description">选择代码缩进的长度。默认是4个空格。</div>
</el-col>
<el-col :span="8">
<el-select v-model="selectTabValue"
@change="setTabSize"
filterable>
<el-option
v-for="item in tabOption"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</el-col>
</el-row>
<hr>
</div>
<div class="row">
<el-row>
<el-col :span="16">
<div class="code-editor-option-title">主题</div>
<div class="code-editor-option-description">对白色界面感到厌倦了吗?可以尝试其他的背景和代码高亮风格。</div>
</el-col>
<el-col :span="8">
<el-select v-model="selectThemeValue"
@change="changSelectValue"
filterable>
<el-option
v-for="item in themesOptions"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</el-col>
</el-row>
</div>
</el-card>
<el-button @click="dialogTableVisible = false" style="margin: 20px 480px -12px 480px">确 定</el-button>
</el-dialog>
</div>
<div ref="ace" class="ace"></div>
<el-button class="submitBtn" round type="success" ><i class="fa fa-cloud-upload" aria-hidden="true"></i>提交代码</el-button>
<el-button class="debuggerBtn" round @click="debuggerCode" ><i class="fa fa-play-circle-o" aria-hidden="true"></i>调试代码</el-button>
</div>
</template>
<script>
import ace from 'ace-builds'
import 'ace-builds/webpack-resolver'; // 在 webpack 环境中使用必须要导入
//解决添加提示时控制台警告(提示必须引入的包)
import "ace-builds/src-noconflict/ext-language_tools"
import "ace-builds/src-noconflict/ext-emmet"
//语言提示
import 'ace-builds/src-noconflict/snippets/javascript'
import 'ace-builds/src-noconflict/snippets/c_cpp'
import 'ace-builds/src-noconflict/snippets/java'
import 'ace-builds/src-noconflict/snippets/golang'
import 'ace-builds/src-noconflict/snippets/python'
//输入类型
import 'ace-builds/src-noconflict/keybinding-emacs'
import 'ace-builds/src-noconflict/keybinding-vim'
import 'ace-builds/src-noconflict/keybinding-vscode'
export default {
name: 'CodeEditor',
props: {
value: {
type: String,
required: true
}
},
methods: {
changSelectValue(value) {
this.aceEditor.setTheme(`ace/theme/${value}`);
},
refresh_code() {
this.aceEditor.session.setValue('');
},
changSelectValue1(value) {
this.aceEditor.session.setMode(`ace/mode/${value}`);
},
debuggerCode() {
let value = this.aceEditor.session.getValue();
console.log(value);
},
setTabSize(size) {
this.aceEditor.session.setTabSize(size);
},
setEditorMode(value) {
this.aceEditor.setKeyboardHandler(`ace/keyboard/${value}`);
}
},
data() {
return {
dialogTableVisible: false,
themeValue: 'ambiance',
aceEditor: null,
themePath: 'ace/theme/monokai', // 不导入 webpack-resolver,该模块路径会报错
modePath: 'ace/mode/c_cpp', // 同上
codeValue: this.value || '',
editorOption:[{
value: 'vscode',
label: 'Standard'
},{
value: 'vim',
label: 'Vim'
},{
value: 'emacs',
label: 'Emacs'
}],
selectEditorValue: 'Standard',
tabOption:[{
value: '2',
label: '2个空格'
},{
value: '4',
label: '4个空格'
},{
value: '6',
label: '6个空格'
}],
selectTabValue: '4个空格',
themesOptions: [{
value: 'crimson_editor',
label: 'CrimsonEditor'
},{
value: 'monokai',
label: 'Monokai'
},{
value: 'terminal',
label: 'Terminal'
},{
value: 'xcode',
label: 'Xcode'
}],
selectThemeValue: 'Monokai',
languagesOptions: [{
value: 'c_cpp',
label: 'C++'
},{
value: 'c_cpp',
label: 'C'
},{
value: 'java',
label: 'Java'
},{
value: 'golang',
label: 'Golang'
},{
value: 'python',
label: 'Python'
},{
value: 'javascript',
label: 'Javascript'
}],
selectLanguageValue: 'C++'
};
},
mounted() {
this.aceEditor = ace.edit(this.$refs.ace,{
maxLines: 1000, // 最大行数,超过会自动出现滚动条
minLines: 22, // 最小行数,还未到最大行数时,编辑器会自动伸缩大小
fontSize: 14, // 编辑器内字体大小
theme: this.themePath, // 默认设置的主题
mode: this.modePath, // 默认设置的语言模式
tabSize: 4, // 制表符设置为 4 个空格大小
readOnly: false, //只读
highlightActiveLine: true,
value: this.codeValue
});
this.aceEditor.setOptions({
enableSnippets: true,
enableLiveAutocompletion: true,
enableBasicAutocompletion: true
});
// 快捷键
// this.aceEditor.commands.addCommand({
// name: 'myCommand',
// bindKey: {win: 'Ctrl-M', mac: 'Command-M'},
// exec: function(editor) {
// //...
// },
// readOnly: true // false if this command should not apply in readOnly mode
// });
},
watch: {
value(newVal) {
console.log(newVal);
this.aceEditor.setValue(newVal);
}
}
};
</script>
<style scoped lang="scss">
.code_tool_bar {
height: 60px;
width: 100%;
background: #f8f9fa;
border: 1px solid #c2c7d0;
margin-bottom: 0;
}
.code-editor-option-title {
font-size: 17px;
margin-bottom: 10px;
}
.code-editor-option-description {
font-size: 13px;
color: grey;
}
hr {
background: none !important;
height: 1px !important;
border: 0 !important;
border-top: 1px solid #eee !important;
margin-top: 20px;
margin-bottom: 20px;
}
.ace {
position: relative !important;
border: 1px solid lightgray;
margin: auto;
height: auto;
width: 100%;
}
.debuggerBtn {
float: right;
margin: 13px 20px 0 0;
}
.submitBtn {
float: right;
margin: 13px 0 0 0;
}
</style>
vue3 + vite版本
子组件
<template>
<div ref="aceEditor" class="ace-editor"></div>
</template>
<script>
import { watch, ref, onMounted, onBeforeUnmount } from 'vue'
import ace from 'ace-builds'
//import 'ace-builds/webpack-resolver'; // 在 webpack 环境中使用必须要导入
import 'ace-builds/src-noconflict/theme-monokai'
// ace 检索框
import 'ace-builds/src-min-noconflict/ext-searchbox'
import workerJavascriptUrl from 'ace-builds/src-noconflict/worker-javascript?url'
import 'ace-builds/src-noconflict/mode-javascript'
import 'ace-builds/src-noconflict/mode-json'
import 'ace-builds/src-noconflict/snippets/javascript'
//代码完成
import 'ace-builds/src-noconflict/ext-language_tools'
export default {
name: 'AceEditor',
props: {
value: {
type: String,
default: '',
required: true,
},
// 编辑器主题色
theme: {
type: String,
default: 'monokai',
},
// 外部传入的语法类型
language: {
type: String,
default: 'javascript',
},
},
emits: ['update:value'],
setup(props, { emit }) {
let aceInstance = null
const aceEditor = ref(null)
const initEditor = () => {
if (aceInstance) {
//实例销毁
aceInstance.destroy()
}
ace.config.set('basePath', 'https://cdnjs.cloudflare.com/ajax/libs/ace/1.4.14/') // 貌似可有可无
ace.config.setModuleUrl('ace/mode/javascript_worker', workerJavascriptUrl)
aceInstance = ace.edit(aceEditor.value, {
maxLines: 1000, // 最大行数,超过会自动出现滚动条
minLines: 22, // 最小行数,还未到最大行数时,编辑器会自动伸缩大小
fontSize: 14, // 编辑器内字体大小
theme: 'ace/theme/' + (props.theme ? props.theme : 'monokai'), // 默认设置的主题
mode: 'ace/mode/' + (props.language ? props.language : 'javascript'), // 默认设置的语言模式
tabSize: 4, // 制表符设置为 4 个空格大小
readOnly: false, //只读
highlightActiveLine: true,
value: props.value,
showPrintMargin: true,
newLineMode: 'unix',
})
aceInstance.setOptions({
enableBasicAutocompletion: true, //
enableSnippets: true,
enableLiveAutocompletion: true, //输入内容时,弹出补全提示框,
})
aceInstance.on('change', () => {
if (emit) {
emit('update:value', aceInstance.getValue())
}
})
aceInstance.setShowPrintMargin(false) //分隔线,提示换行的
//快捷键
aceInstance.commands.addCommand({
name: 'formatter',
bindKey: { win: 'Ctrl-Shift-F', mac: 'Command-Shift-F' },
exec: () => emit('formatter', aceInstance),
})
aceInstance.setValue(props.value ? props.value : '')
}
watch(
() => props.value,
(newProps) => {
const position = aceInstance.getCursorPosition()
aceInstance.getSession().setValue(newProps)
aceInstance.clearSelection()
aceInstance.moveCursorToPosition(position)
},
)
onMounted(() => {
initEditor()
})
onBeforeUnmount(() => {
aceInstance.destroy()
})
return {
aceEditor,
}
},
}
</script>
<style scoped>
/*:deep(.ace_scrollbar)::-webkit-scrollbar {*/
/* height: 7px;*/
/* width: 7px;*/
/*}*/
/*:deep(.ace_scrollbar)::-webkit-scrollbar-track {*/
/* box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3);*/
/* background-color: #272822; !* Matches ace monokai *!*/
/* border-radius: 10px;*/
/*}*/
/*:deep(.ace_scrollbar)::-webkit-scrollbar-thumb {*/
/* background-color: darkgrey;*/
/* outline: 1px solid slategrey;*/
/* border-radius: 10px;*/
/*}*/
</style>
父组件
<template>
<AceEditor
v-model:value="editorContent.content"
:theme="aceEditor.theme"
:language="aceEditor.language"
@update:value="editorContent.content = $event"
></AceEditor>
</template>
<script>
import { ref, reactive, defineAsyncComponent, watch } from 'vue'
export default {
name: 'UserBotIndexView',
components: {
AceEditor: defineAsyncComponent(() => import('@/components/AceEditor.vue')),
},
setup() {
let editorContent = ref('')
const aceEditor = reactive({
content: '请输入Bot代码',
theme: 'monokai',
language: 'javascript',
})
watch(
() => aceEditor.content,
(newContent) => {
console.log(newContent)
},
)
return {
editorContent,
aceEditor,
}
},
}
</script>
<style scoped>
</style>
好了,在线代码编辑器的分享就告一段落了,因为是网页上的东西,安全是一个很重要的考虑因素,比如csrf亦或者是xss,在提交的时候前后端都应该去过滤一下代码。有空的话再介绍一下Ace实现的MarkDown编辑器吧(也是一样的哦~)
大佬请问如果想实现代码运行,有大概过程吗
前端提交代码后,后端去调用评测服务,然后根据评测服务返回的数据响应
后端的测评服务有没有推荐的,现在一点头绪都没有😭
看别人的项目怎么用的 多去了解
大大大大......大佬!
qq飞车玩多了是吧
大佬牛逼
菜狗一个
大佬牛逼
不不不,我是傻逼
后端用什么实现的?
Java,现在后端只是随便写写,前端还没写完
向大佬学习
哈哈哈,废物一个