This commit is contained in:
2024-01-22 10:44:38 +08:00
parent d940c684ad
commit 230cf1bdeb
11 changed files with 369 additions and 370 deletions

View File

@@ -1,9 +1,9 @@
<script setup lang="ts"> <script setup lang="ts">
import { zhCN, dateZhCN, darkTheme } from "naive-ui" import { zhCN, dateZhCN, darkTheme } from "naive-ui"
import { useDark } from "@vueuse/core"
import Desktop from "./desktop/index.vue" import Desktop from "./desktop/index.vue"
import Mobile from "./mobile/index.vue" import Mobile from "./mobile/index.vue"
import { isDesktop, isMobile } from "./composables/breakpoints" import { isDesktop, isMobile } from "./composables/breakpoints"
import { useDark } from "@vueuse/core"
const isDark = useDark() const isDark = useDark()
</script> </script>
@@ -22,4 +22,3 @@ const isDark = useDark()
</n-message-provider> </n-message-provider>
</n-config-provider> </n-config-provider>
</template> </template>
./themes/breakpoints

View File

@@ -1,56 +1,56 @@
import axios from "axios" import axios from "axios"
import { Code } from "./types" import { Code } from "./types"
import { deadResults, languageToId } from "./templates" import { deadResults, languageToId } from "./templates"
function getChromeVersion() { function getChromeVersion() {
var raw = navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./) var raw = navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./)
return raw ? parseInt(raw[2], 10) : 0 return raw ? parseInt(raw[2], 10) : 0
} }
const isLowVersion = getChromeVersion() < 80 const isLowVersion = getChromeVersion() < 80
const protocol = isLowVersion ? "http" : "https" const protocol = isLowVersion ? "http" : "https"
function encode(string?: string) { function encode(string?: string) {
return btoa(String.fromCharCode(...new TextEncoder().encode(string ?? ""))) return btoa(String.fromCharCode(...new TextEncoder().encode(string ?? "")))
} }
function decode(bytes?: string) { function decode(bytes?: string) {
const latin = atob(bytes ?? "") const latin = atob(bytes ?? "")
return new TextDecoder("utf-8").decode( return new TextDecoder("utf-8").decode(
Uint8Array.from({ length: latin.length }, (_, index) => Uint8Array.from({ length: latin.length }, (_, index) =>
latin.charCodeAt(index), latin.charCodeAt(index),
), ),
) )
} }
const http = axios.create({ baseURL: `${protocol}://judge0api.xuyue.cc` }) const http = axios.create({ baseURL: `${protocol}://judge0api.xuyue.cc` })
export async function submit(code: Code, input: string) { export async function submit(code: Code, input: string) {
const encodedCode = encode(code.value) const encodedCode = encode(code.value)
if (encodedCode === deadResults[code.language].encoded) { if (encodedCode === deadResults[code.language].encoded) {
return deadResults[code.language].result return deadResults[code.language].result
} else { } else {
const id = languageToId[code.language] const id = languageToId[code.language]
let compilerOptions = "" let compilerOptions = ""
if (id === 50) compilerOptions = "-lm" // 解决 GCC 的链接问题 if (id === 50) compilerOptions = "-lm" // 解决 GCC 的链接问题
const payload = { const payload = {
source_code: encodedCode, source_code: encodedCode,
language_id: id, language_id: id,
stdin: encode(input), stdin: encode(input),
redirect_stderr_to_stdout: true, redirect_stderr_to_stdout: true,
compiler_options: compilerOptions, compiler_options: compilerOptions,
} }
const response = await http.post("/submissions", payload, { const response = await http.post("/submissions", payload, {
params: { base64_encoded: true, wait: true }, params: { base64_encoded: true, wait: true },
}) })
const data = response.data const data = response.data
return { return {
status: data.status && data.status.id, status: data.status && data.status.id,
output: [decode(data.compile_output), decode(data.stdout)] output: [decode(data.compile_output), decode(data.stdout)]
.join("\n") .join("\n")
.trim(), .trim(),
} }
} }
} }

View File

@@ -1,81 +1,81 @@
<script lang="ts" setup> <script lang="ts" setup>
import { computed, ref, watch } from "vue" import { computed, ref, watch } from "vue"
import { useDark } from "@vueuse/core" import { useDark } from "@vueuse/core"
import { Codemirror } from "vue-codemirror" import { Codemirror } from "vue-codemirror"
import { cpp } from "@codemirror/lang-cpp" import { cpp } from "@codemirror/lang-cpp"
import { python } from "@codemirror/lang-python" import { python } from "@codemirror/lang-python"
import { EditorView } from "@codemirror/view" import { EditorView } from "@codemirror/view"
import { LANGUAGE } from "../types" import { LANGUAGE } from "../types"
import { oneDark } from "../themes/oneDark" import { oneDark } from "../themes/oneDark"
import { smoothy } from "../themes/smoothy" import { smoothy } from "../themes/smoothy"
interface Props { interface Props {
label: string label: string
modelValue: string modelValue: string
language?: LANGUAGE language?: LANGUAGE
fontSize?: number fontSize?: number
readonly?: boolean readonly?: boolean
placeholder?: string placeholder?: string
} }
const props = withDefaults(defineProps<Props>(), { const props = withDefaults(defineProps<Props>(), {
language: "python", language: "python",
fontSize: 24, fontSize: 24,
readonly: false, readonly: false,
placeholder: "", placeholder: "",
}) })
const code = ref(props.modelValue) const code = ref(props.modelValue)
const isDark = useDark() const isDark = useDark()
const styleTheme = EditorView.baseTheme({ const styleTheme = EditorView.baseTheme({
"& .cm-scroller": { "& .cm-scroller": {
"font-family": "Consolas", "font-family": "Consolas",
}, },
"&.cm-editor.cm-focused": { "&.cm-editor.cm-focused": {
outline: "none", outline: "none",
}, },
}) })
const emit = defineEmits(["update:modelValue"]) const emit = defineEmits(["update:modelValue"])
watch( watch(
() => props.modelValue, () => props.modelValue,
(v) => { (v) => {
code.value = v code.value = v
}, },
) )
const lang = computed(() => { const lang = computed(() => {
if (props.language === "python") { if (props.language === "python") {
return python() return python()
} }
return cpp() return cpp()
}) })
function onChange(v: string) { function onChange(v: string) {
emit("update:modelValue", v) emit("update:modelValue", v)
} }
</script> </script>
<template> <template>
<div class="container"> <div class="container">
<div class="title">{{ label }}</div> <div class="title">{{ label }}</div>
<Codemirror <Codemirror
v-model="code" v-model="code"
indentWithTab indentWithTab
:extensions="[styleTheme, lang, isDark ? oneDark : smoothy]" :extensions="[styleTheme, lang, isDark ? oneDark : smoothy]"
:disabled="props.readonly" :disabled="props.readonly"
:tabSize="4" :tabSize="4"
:placeholder="props.placeholder" :placeholder="props.placeholder"
:style="{ height: '100%', fontSize: props.fontSize + 'px' }" :style="{ height: '100%', fontSize: props.fontSize + 'px' }"
@change="onChange" @change="onChange"
/> />
</div> </div>
</template> </template>
<style scoped> <style scoped>
.container { .container {
height: 100%; height: 100%;
} }
.title { .title {
padding: 12px 20px; padding: 12px 20px;
font-size: 16px; font-size: 16px;
} }
</style> </style>

View File

@@ -1,40 +1,40 @@
import { ref } from "vue" import { ref } from "vue"
import copyTextToClipboard from "copy-text-to-clipboard" import copyTextToClipboard from "copy-text-to-clipboard"
import { Code, LANGUAGE } from "../types" import { Code, LANGUAGE } from "../types"
import { sources } from "../templates" import { sources } from "../templates"
import { submit } from "../api" import { submit } from "../api"
export const code = ref<Code>({ export const code = ref<Code>({
value: sources["python"], value: sources["python"],
language: "python", language: "python",
}) })
export const input = ref("") export const input = ref("")
export const output = ref("") export const output = ref("")
export const loading = ref(false) export const loading = ref(false)
export function copy() { export function copy() {
copyTextToClipboard(code.value.value) copyTextToClipboard(code.value.value)
} }
export function reset() { export function reset() {
code.value.value = sources["python"] code.value.value = sources["python"]
output.value = "" output.value = ""
} }
export function changeLanguage(language: LANGUAGE) { export function changeLanguage(language: LANGUAGE) {
code.value.value = sources[language] code.value.value = sources[language]
output.value = "" output.value = ""
} }
export async function run() { export async function run() {
loading.value = true loading.value = true
const cleanCode = code.value.value.trim() const cleanCode = code.value.value.trim()
if (!cleanCode) return if (!cleanCode) return
output.value = "" output.value = ""
const result = await submit( const result = await submit(
{ value: cleanCode, language: code.value.language }, { value: cleanCode, language: code.value.language },
input.value.trim(), input.value.trim(),
) )
output.value = result.output || "" output.value = result.output || ""
loading.value = false loading.value = false
} }

View File

@@ -1,37 +1,37 @@
<template> <template>
<n-layout-content class="container"> <n-layout-content class="container">
<n-split direction="horizontal" :min="1 / 3" :max="4 / 5"> <n-split direction="horizontal" :min="1 / 3" :max="4 / 5">
<template #1> <template #1>
<CodeEditor <CodeEditor
label="代码区" label="代码区"
v-model="code.value" v-model="code.value"
:language="code.language" :language="code.language"
/> />
</template> </template>
<template #2> <template #2>
<n-split <n-split
direction="vertical" direction="vertical"
:default-size="1 / 3" :default-size="1 / 3"
:min="1 / 5" :min="1 / 5"
:max="3 / 5" :max="3 / 5"
> >
<template #1> <template #1>
<CodeEditor label="输入框" v-model="input" /> <CodeEditor label="输入框" v-model="input" />
</template> </template>
<template #2> <template #2>
<CodeEditor label="输出框" v-model="output" readonly /> <CodeEditor label="输出框" v-model="output" readonly />
</template> </template>
</n-split> </n-split>
</template> </template>
</n-split> </n-split>
</n-layout-content> </n-layout-content>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { code, input, output } from "../composables/code" import { code, input, output } from "../composables/code"
import CodeEditor from "../components/CodeEditor.vue" import CodeEditor from "../components/CodeEditor.vue"
</script> </script>
<style scoped> <style scoped>
.container { .container {
height: calc(100vh - 60px); height: calc(100vh - 60px);
} }
</style> </style>

View File

@@ -1,69 +1,69 @@
<script setup lang="ts"> <script setup lang="ts">
import { useMessage, type SelectOption } from "naive-ui" import { useMessage, type SelectOption } from "naive-ui"
import { useDark, useToggle } from "@vueuse/core" import { useDark, useToggle } from "@vueuse/core"
import Play from "../icons/Play.vue" import Play from "../icons/Play.vue"
import { import {
code, code,
copy, copy,
reset, reset,
run, run,
loading, loading,
changeLanguage, changeLanguage,
} from "../composables/code" } from "../composables/code"
const message = useMessage() const message = useMessage()
const isDark = useDark() const isDark = useDark()
const toggleDark = useToggle(isDark) const toggleDark = useToggle(isDark)
const languages: SelectOption[] = [ const languages: SelectOption[] = [
{ value: "python", label: "Python" }, { value: "python", label: "Python" },
{ value: "c", label: "C" }, { value: "c", label: "C" },
] ]
function copyAndNotify() { function copyAndNotify() {
copy() copy()
message.success("已经复制好了") message.success("已经复制好了")
} }
</script> </script>
<template> <template>
<n-layout-header bordered class="header"> <n-layout-header bordered class="header">
<n-flex justify="space-between" align="center"> <n-flex justify="space-between" align="center">
<div class="title">徐越的自测猫</div> <div class="title">徐越的自测猫</div>
<n-flex> <n-flex>
<n-button @click="toggleDark()"> <n-button @click="toggleDark()">
{{ isDark ? "浅色" : "深色" }} {{ isDark ? "浅色" : "深色" }}
</n-button> </n-button>
<n-button @click="reset">重置</n-button> <n-button @click="reset">重置</n-button>
<n-button @click="copyAndNotify">复制</n-button> <n-button @click="copyAndNotify">复制</n-button>
<n-select <n-select
class="select" class="select"
:options="languages" :options="languages"
v-model:value="code.language" v-model:value="code.language"
@update:value="changeLanguage" @update:value="changeLanguage"
/> />
<n-button type="primary" @click="run" :loading="loading"> <n-button type="primary" @click="run" :loading="loading">
<template #icon> <template #icon>
<n-icon> <n-icon>
<Play /> <Play />
</n-icon> </n-icon>
</template> </template>
运行 (F5) 运行 (F5)
</n-button> </n-button>
</n-flex> </n-flex>
</n-flex> </n-flex>
</n-layout-header> </n-layout-header>
</template> </template>
<style scoped> <style scoped>
.header { .header {
height: 60px; height: 60px;
padding: 12px; padding: 12px;
} }
.title { .title {
font-size: 20px; font-size: 20px;
} }
.select { .select {
width: 100px; width: 100px;
} }
</style> </style>

View File

@@ -1,8 +1,8 @@
<template> <template>
<Header /> <Header />
<Content /> <Content />
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import Header from "./Header.vue" import Header from "./Header.vue"
import Content from "./Content.vue" import Content from "./Content.vue"
</script> </script>

View File

@@ -1,15 +1,15 @@
<template> <template>
<svg <svg
viewBox="0 0 1024 1024" viewBox="0 0 1024 1024"
focusable="false" focusable="false"
data-icon="caret-right" data-icon="caret-right"
width="18px" width="18px"
height="18px" height="18px"
fill="currentColor" fill="currentColor"
aria-hidden="true" aria-hidden="true"
> >
<path <path
d="M715.8 493.5L335 165.1c-14.2-12.2-35-1.2-35 18.5v656.8c0 19.7 20.8 30.7 35 18.5l380.8-328.4c10.9-9.4 10.9-27.6 0-37z" d="M715.8 493.5L335 165.1c-14.2-12.2-35-1.2-35 18.5v656.8c0 19.7 20.8 30.7 35 18.5l380.8-328.4c10.9-9.4 10.9-27.6 0-37z"
></path> ></path>
</svg> </svg>
</template> </template>

View File

@@ -1 +1 @@
<template></template> <template></template>

View File

@@ -1,55 +1,55 @@
const cSource = const cSource =
'#include<stdio.h>\r\n\r\nint main()\r\n{\r\n printf("黄岩一职");\r\n return 0;\r\n}' '#include<stdio.h>\r\n\r\nint main()\r\n{\r\n printf("黄岩一职");\r\n return 0;\r\n}'
const cppSource = const cppSource =
'#include<iostream>\r\n\r\nusing namespace std;\r\n\r\nint main()\r\n{\r\n cout<<"黄岩一职"<<endl;\r\n return 0;\r\n}' '#include<iostream>\r\n\r\nusing namespace std;\r\n\r\nint main()\r\n{\r\n cout<<"黄岩一职"<<endl;\r\n return 0;\r\n}'
const pythonSource = 'print("黄岩一职")' const pythonSource = 'print("黄岩一职")'
const javaSource = const javaSource =
'public class Main {\r\n public static void main(String[] args) {\r\n System.out.println("黄岩一职");\r\n }\r\n}' 'public class Main {\r\n public static void main(String[] args) {\r\n System.out.println("黄岩一职");\r\n }\r\n}'
export const languageToId = { export const languageToId = {
c: 50, c: 50,
cpp: 54, cpp: 54,
java: 62, java: 62,
python: 71, python: 71,
} }
export const sources = { export const sources = {
c: cSource, c: cSource,
cpp: cppSource, cpp: cppSource,
java: javaSource, java: javaSource,
python: pythonSource, python: pythonSource,
} }
export const deadResults = { export const deadResults = {
c: { c: {
encoded: encoded:
"I2luY2x1ZGU8c3RkaW8uaD4NCg0KaW50IG1haW4oKQ0Kew0KICAgIHByaW50Zigi6buE5bKp5LiA6IGMIik7DQogICAgcmV0dXJuIDA7DQp9", "I2luY2x1ZGU8c3RkaW8uaD4NCg0KaW50IG1haW4oKQ0Kew0KICAgIHByaW50Zigi6buE5bKp5LiA6IGMIik7DQogICAgcmV0dXJuIDA7DQp9",
result: { result: {
status: 3, status: 3,
output: "黄岩一职", output: "黄岩一职",
}, },
}, },
cpp: { cpp: {
encoded: encoded:
"I2luY2x1ZGU8aW9zdHJlYW0+DQoNCnVzaW5nIG5hbWVzcGFjZSBzdGQ7DQoNCmludCBtYWluKCkNCnsNCiAgICBjb3V0PDwi6buE5bKp5LiA6IGMIjw8ZW5kbDsNCiAgICByZXR1cm4gMDsNCn0=", "I2luY2x1ZGU8aW9zdHJlYW0+DQoNCnVzaW5nIG5hbWVzcGFjZSBzdGQ7DQoNCmludCBtYWluKCkNCnsNCiAgICBjb3V0PDwi6buE5bKp5LiA6IGMIjw8ZW5kbDsNCiAgICByZXR1cm4gMDsNCn0=",
result: { result: {
status: 3, status: 3,
output: "黄岩一职", output: "黄岩一职",
}, },
}, },
python: { java: {
encoded: "cHJpbnQoIum7hOWyqeS4gOiBjCIp", encoded:
result: { "cHVibGljIGNsYXNzIE1haW4gew0KICAgIHB1YmxpYyBzdGF0aWMgdm9pZCBtYWluKFN0cmluZ1tdIGFyZ3MpIHsNCiAgICAgICAgU3lzdGVtLm91dC5wcmludGxuKCLpu4TlsqnkuIDogYwiKTsNCiAgICB9DQp9",
status: 3, result: {
output: "黄岩一职", status: 3,
}, output: "黄岩一职",
}, },
java: { },
encoded: python: {
"cHVibGljIGNsYXNzIE1haW4gew0KICAgIHB1YmxpYyBzdGF0aWMgdm9pZCBtYWluKFN0cmluZ1tdIGFyZ3MpIHsNCiAgICAgICAgU3lzdGVtLm91dC5wcmludGxuKCLpu4TlsqnkuIDogYwiKTsNCiAgICB9DQp9", encoded: "cHJpbnQoIum7hOWyqeS4gOiBjCIp",
result: { result: {
status: 3, status: 3,
output: "黄岩一职", output: "黄岩一职",
}, },
}, },
} }

View File

@@ -1,6 +1,6 @@
export type LANGUAGE = "c" | "cpp" | "python" | "java" export type LANGUAGE = "c" | "cpp" | "python" | "java"
export interface Code { export interface Code {
value: string value: string
language: LANGUAGE language: LANGUAGE
} }