fix monaco.

This commit is contained in:
2023-01-12 09:36:13 +08:00
parent 75e6906c16
commit ab5cb8610b
20 changed files with 281 additions and 132 deletions

View File

@@ -1,9 +1,28 @@
<script setup lang="ts"> <script setup lang="ts">
import Md from "./step-1/index.md" import Md from "./step-1/index.md"
import Monaco from "../shared/monaco/index.vue"
const route = useRoute()
console.log(route.params.step)
const code = ref("")
function change(value: string) {
code.value = value
}
</script> </script>
<template> <template>
<Md /> <el-row>
<el-col :span="4"> </el-col>
<el-col :span="12">
<Md />
{{ code }}
</el-col>
<el-col :span="8">
<Monaco :value="code" @change="change" />
</el-col>
</el-row>
</template> </template>
<style scoped></style> <style scoped></style>

View File

@@ -8,7 +8,7 @@ import App from "./App.vue"
import storage from "./utils/storage" import storage from "./utils/storage"
import routes from "./routes" import routes from "./routes"
import { STORAGE_KEY } from "./utils/constants" import { STORAGE_KEY } from "./utils/constants"
import { useLoginStore } from "./shared/stores/login" import { useLoginStore } from "./shared/store/login"
const router = createRouter({ const router = createRouter({
history: createWebHistory(), history: createWebHistory(),

View File

@@ -1,37 +0,0 @@
<script setup lang="ts">
import { TabsPaneContext } from "element-plus"
import { Ref } from "vue"
import { Problem } from "../../../utils/types"
import { submissionExists } from "../../api"
import SubmitPanel from "./submit-panel.vue"
import TestcasePanel from "./testcase-panel.vue"
const tab = ref("test")
const submitPanelRef = ref<{ submit: Function }>()
const problem = inject<Ref<Problem>>("problem")
const [tried] = useToggle()
onMounted(() => {
checkIfTried()
})
async function checkIfTried() {
const res = await submissionExists(problem!.value.id)
tried.value = res.data
}
function onTab(pane: TabsPaneContext) {
if (pane.paneName === "submit") {
submitPanelRef && submitPanelRef.value!.submit()
}
}
</script>
<template>
<el-tabs type="border-card" @tab-click="onTab" v-model="tab">
<TestcasePanel />
<SubmitPanel ref="submitPanelRef" />
</el-tabs>
</template>
<style scoped></style>

View File

@@ -1,14 +1,13 @@
<script lang="ts" setup> <script lang="ts" setup>
import loader, { Monaco } from "@monaco-editor/loader" import { LANGUAGE_LABEL, SOURCES } from "../../../utils/constants"
import {
LANGUAGE_LABEL,
LANGUAGE_VALUE,
SOURCES,
} from "../../../utils/constants"
import { isMobile } from "../../../utils/breakpoints"
import { Problem } from "../../../utils/types" import { Problem } from "../../../utils/types"
import EditorExec from "./editor-exec.vue"
import { useCodeStore } from "../../stores/code" import { useCodeStore } from "../../stores/code"
import { submissionExists } from "../../api"
import { TabsPaneContext } from "element-plus"
import Monaco from "../../../shared/monaco/index.vue"
import SubmitPanel from "../components/submit-panel.vue"
import TestcasePanel from "../components/testcase-panel.vue"
interface Props { interface Props {
problem: Problem problem: Problem
@@ -16,63 +15,38 @@ interface Props {
const props = defineProps<Props>() const props = defineProps<Props>()
const { code, setLanguage, setValue } = useCodeStore() const { code } = useCodeStore()
setValue(SOURCES[props.problem.languages[0] || "C"])
setLanguage(props.problem.languages[0] || "C")
const monacoEditorRef = ref() code.language = props.problem.languages[0] || "C"
code.value = props.problem.template[code.language] || SOURCES[code.language]
let monaco: Monaco const tab = ref("test")
const submitPanelRef = ref<{ submit: Function }>()
const [tried] = useToggle()
onMounted(() => { watch(() => code.language, reset)
init()
})
onBeforeUnmount(() => {
monaco.editor.getModels().forEach((model) => model.dispose())
})
watch(
() => code.language,
() => {
if (monaco && monaco.editor) {
monaco.editor.setModelLanguage(
monaco.editor.getModels()[0],
LANGUAGE_VALUE[code.language]
)
reset()
}
}
)
function run() {}
function reset() { function reset() {
setValue(props.problem.template[code.language] || SOURCES[code.language]) code.value = props.problem.template[code.language] || SOURCES[code.language]
if (monaco && monaco.editor) {
monaco.editor.getModels()[0].setValue(code.value)
}
} }
async function init() { function change(value: string) {
setValue(props.problem.template[code.language] || SOURCES[code.language]) code.value = value
monaco = await loader.init() }
monaco.editor.create(monacoEditorRef.value, {
value: code.value, // 编辑器初始显示文字 onMounted(() => {
language: LANGUAGE_VALUE[code.language], checkIfTried()
theme: "vs", // 官方自带三种主题vs, hc-black, or vs-dark })
minimap: {
enabled: false, async function checkIfTried() {
}, const res = await submissionExists(props.problem.id)
lineNumbersMinChars: 3, tried.value = res.data
automaticLayout: true, // 自适应布局 }
tabSize: 4,
fontSize: isMobile.value ? 20 : 24, // 字体大小 function onTab(pane: TabsPaneContext) {
scrollBeyondLastLine: false, // 取消代码后面一大段空白 if (pane.paneName === "submit") {
}) submitPanelRef && submitPanelRef.value!.submit()
monaco.editor.getModels()[0].onDidChangeContent(() => { }
setValue(monaco.editor.getModels()[0].getValue())
})
} }
</script> </script>
@@ -93,11 +67,17 @@ async function init() {
<el-button @click="reset">重置</el-button> <el-button @click="reset">重置</el-button>
</el-form-item> </el-form-item>
</el-form> </el-form>
<section <Monaco
ref="monacoEditorRef" class="editor"
:class="isMobile ? 'editorMobile' : 'editor'" :language="code.language"
></section> :value="code.value"
<EditorExec /> height="calc(100vh - 621px)"
@change="change"
/>
<el-tabs type="border-card" @tab-click="onTab" v-model="tab">
<TestcasePanel />
<SubmitPanel ref="submitPanelRef" />
</el-tabs>
</template> </template>
<style scoped> <style scoped>
@@ -106,11 +86,6 @@ async function init() {
} }
.editor { .editor {
/* 141px+400 */ min-height: 200px;
height: calc(100vh - 541px);
}
.editorMobile {
height: calc(100vh - 612px);
} }
</style> </style>

View File

@@ -61,5 +61,3 @@ defineProps<Props>()
</el-descriptions-item> </el-descriptions-item>
</el-descriptions> </el-descriptions>
</template> </template>
<style scoped></style>

View File

@@ -230,7 +230,7 @@ defineExpose({ submit })
</template> </template>
<style scoped> <style scoped>
.panel { .panel {
height: 320px; height: 400px;
} }
.result { .result {

View File

@@ -1,13 +1,13 @@
<script setup lang="ts"></script> <script setup lang="ts"></script>
<template> <template>
<el-tab-pane label="测试 & 运行" name="test"> <el-tab-pane label="测试面板" name="test">
<div class="panel"></div> <div class="panel"></div>
</el-tab-pane> </el-tab-pane>
</template> </template>
<style scoped> <style scoped>
.panel { .panel {
height: 320px; height: 400px;
} }
</style> </style>

View File

@@ -1,5 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import { useUserStore } from "../../shared/stores/user" import { useUserStore } from "../../shared/store/user"
import { filterEmptyValue, getTagColor } from "../../utils/functions" import { filterEmptyValue, getTagColor } from "../../utils/functions"
import { isDesktop } from "../../utils/breakpoints" import { isDesktop } from "../../utils/breakpoints"
import { getProblemList, getProblemTagList, getRandomProblemID } from "../api" import { getProblemList, getProblemTagList, getRandomProblemID } from "../api"
@@ -77,7 +77,7 @@ function goProblem(row: any) {
watch(() => query.page, routePush) watch(() => query.page, routePush)
watch( watch(
() => query.tag || query.difficulty || query.limit, () => [query.tag, query.difficulty, query.limit],
() => { () => {
query.page = 1 query.page = 1
routePush() routePush()

View File

@@ -6,13 +6,5 @@ export const useCodeStore = defineStore("code", () => {
language: "C", language: "C",
}) })
function setValue(value: string) { return { code }
code.value = value
}
function setLanguage(language: LANGUAGE) {
code.language = language
}
return { code, setLanguage, setValue }
}) })

View File

@@ -7,7 +7,7 @@ const routes = [
component: Home, component: Home,
children: [ children: [
{ path: "", component: Problems }, { path: "", component: Problems },
{ path: "/learn", component: () => import("./learn/index.vue") }, { path: "/learn/:step*", component: () => import("./learn/index.vue") },
{ {
path: "problem/:problemID", path: "problem/:problemID",
component: () => import("./oj/problem/detail.vue"), component: () => import("./oj/problem/detail.vue"),

View File

@@ -1,7 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { useLoginStore } from "../stores/login" import { useLoginStore } from "../store/login"
import { useSignupStore } from "../../oj/stores/signup" import { useSignupStore } from "../../oj/stores/signup"
import { useUserStore } from "../stores/user" import { useUserStore } from "../store/user"
import { logout } from "../api" import { logout } from "../api"
const loginStore = useLoginStore() const loginStore = useLoginStore()
@@ -30,7 +30,7 @@ onMounted(userStore.getMyProfile)
<el-menu router mode="horizontal" :default-active="$route.path"> <el-menu router mode="horizontal" :default-active="$route.path">
<el-menu-item index="/learn">自学</el-menu-item> <el-menu-item index="/learn">自学</el-menu-item>
<el-menu-item index="/">题库</el-menu-item> <el-menu-item index="/">题库</el-menu-item>
<el-menu-item index="/contest"></el-menu-item> <el-menu-item index="/contest"></el-menu-item>
<el-menu-item index="/status">提交</el-menu-item> <el-menu-item index="/status">提交</el-menu-item>
<el-menu-item index="/rank">排名</el-menu-item> <el-menu-item index="/rank">排名</el-menu-item>
</el-menu> </el-menu>

107
src/shared/monaco/index.vue Normal file
View File

@@ -0,0 +1,107 @@
<script setup lang="ts">
import type * as Monaco from "monaco-editor"
import loader from "@monaco-editor/loader"
import { LANGUAGE_VALUE } from "../../utils/constants"
import { LANGUAGE } from "../../utils/types"
import { isMobile } from "../../utils/breakpoints"
interface Props {
value: string
language?: LANGUAGE
height?: string
fontSize?: number
class?: string
}
const props = withDefaults(defineProps<Props>(), {
language: "C",
height: "calc(100vh - 100px)",
fontSize: 20,
class: "",
})
const emit = defineEmits<{
(e: "change", value: string): void
}>()
const monacoEditorRef = ref()
let editor: Monaco.editor.IStandaloneCodeEditor | null
onMounted(async function () {
const monaco = await loader.init()
const model = monaco.editor.createModel(
props.value,
LANGUAGE_VALUE[props.language],
monaco.Uri.parse(`file:///root/${Date.now()}.${ext()}`)
)
editor = monaco.editor.create(monacoEditorRef.value, {
model,
theme: "vs-dark", // 官方自带三种主题vs, hc-black, or vs-dark
minimap: {
enabled: false,
},
lineNumbersMinChars: 3,
automaticLayout: true, // 自适应布局
tabSize: 4,
fontSize: isMobile.value ? 20 : 24, // 字体大小
})
model.onDidChangeContent(() => {
const value = model.getValue().toString()
emit("change", value)
})
editor.onKeyDown((e) => {
if ((e.ctrlKey || e.metaKey) && e.code === "KeyS") {
e.preventDefault()
}
if ((e.ctrlKey || e.metaKey) && e.code === "KeyR") {
e.preventDefault()
}
})
watchEffect(() => {
monaco.editor.setModelLanguage(model, LANGUAGE_VALUE[props.language])
})
watchEffect(() => {
if (props.value !== model.getValue()) {
console.log(666)
model.setValue(props.value)
}
})
})
onUnmounted(() => {
editor && editor.dispose()
})
function ext() {
switch (props.language) {
case "C":
return "c"
case "C++":
return "cpp"
case "Java":
return "java"
case "JavaScript":
return "js"
case "Python2":
return "py"
case "Python3":
return "py"
case "Golang":
return "go"
}
}
</script>
<template>
<div
ref="monacoEditorRef"
:class="props.class"
:style="{ height: props.height }"
></div>
</template>
<style scoped></style>

View File

@@ -2,8 +2,8 @@
import { FormInstance } from "element-plus" import { FormInstance } from "element-plus"
import { useSignupStore } from "../../oj/stores/signup" import { useSignupStore } from "../../oj/stores/signup"
import { login } from "../../shared/api" import { login } from "../../shared/api"
import { useLoginStore } from "../stores/login" import { useLoginStore } from "../store/login"
import { useUserStore } from "../stores/user" import { useUserStore } from "../store/user"
const loginStore = useLoginStore() const loginStore = useLoginStore()
const signupStore = useSignupStore() const signupStore = useSignupStore()

7
src/shims.d.ts vendored Normal file
View File

@@ -0,0 +1,7 @@
declare module "element-plus/dist/locale/zh-cn.mjs"
declare module "*.md" {
import type { ComponentOptions } from "vue"
const Component: ComponentOptions
export default Component
}

View File

@@ -189,6 +189,27 @@ export const DEAD_RESULTS = {
output: "黄岩一职", output: "黄岩一职",
}, },
}, },
Python2: {
encoded: "",
result: {
status: 3,
output: "黄岩一职",
},
},
Golang: {
encoded: "",
result: {
status: 3,
output: "黄岩一职",
},
},
JavaScript: {
encoded: "",
result: {
status: 3,
output: "黄岩一职",
},
},
} }
export const LANGUAGE_LABEL = { export const LANGUAGE_LABEL = {

View File

@@ -49,3 +49,13 @@ export function submissionTimeFormat(time: number) {
if (time === undefined) return "--" if (time === undefined) return "--"
return time + "ms" return time + "ms"
} }
export function debounce(fn: Function, n = 100) {
let handle: any
return (...args: any[]) => {
if (handle) clearTimeout(handle)
handle = setTimeout(() => {
fn(...args)
}, n)
}
}

59
src/utils/judge.ts Normal file
View File

@@ -0,0 +1,59 @@
import axios from "axios"
import { DEAD_RESULTS } from "./constants"
import { Code } from "./types"
const http = axios.create({ baseURL: "https://judge0api.hyyz.izhai.net" })
function encode(str: string) {
return btoa(unescape(encodeURIComponent(str || "")))
}
function decode(bytes: string) {
let escaped = escape(atob(bytes || ""))
try {
return decodeURIComponent(escaped)
} catch (e) {
return unescape(escaped)
}
}
export async function createTestSubmission(code: Code, input: string) {
const encodedCode = encode(code.value)
if (encodedCode === DEAD_RESULTS[code.language].encoded) {
return DEAD_RESULTS[code.language].result
} else {
const id = {
C: 50,
"C++": 54,
Java: 62,
Golang: 60,
JavaScript: 63,
Python2: 70,
Python3: 71,
}[code.language]
let compilerOptions = ""
if (id === 50) compilerOptions = "-lm" // 解决 GCC 的链接问题
const payload = {
source_code: encodedCode,
language_id: id,
stdin: encode(input),
redirect_stderr_to_stdout: true,
compiler_options: compilerOptions,
}
try {
const response = await http.post("/submissions", payload, {
params: { base64_encoded: true, wait: true },
})
const data = response.data
return {
status: data.status && data.status.id,
output: [decode(data.compile_output), decode(data.stdout)]
.join("\n")
.trim(),
}
} catch (e) {
console.error(e)
}
}
}

2
src/vite-env.d.ts vendored
View File

@@ -1,3 +1 @@
/// <reference types="vite/client" /> /// <reference types="vite/client" />
declare module "element-plus/dist/locale/zh-cn.mjs"
declare module "*.md"