Compare commits

..

10 Commits

Author SHA1 Message Date
7d3cb2f070 add test
Some checks failed
Deploy / deploy (push) Has been cancelled
2025-09-19 11:16:24 +08:00
b02d7b99a6 add test 2025-09-19 10:58:34 +08:00
7ee8bef20c update 2025-09-16 10:01:05 +08:00
d209eb600e add cpp 2025-09-08 08:35:16 +08:00
d34183c639 add CPP 2025-09-06 12:45:40 +08:00
e2b5224a62 update 2025-09-05 11:10:26 +08:00
84d798c01f use rsbuild instead of vite 2025-09-05 11:00:55 +08:00
80552924df add staging MaxKB link 2025-09-05 09:13:05 +08:00
ce0f7d5aa0 update 2025-08-30 15:17:27 +08:00
44be9336f1 update 2025-08-30 15:17:11 +08:00
26 changed files with 1594 additions and 1298 deletions

9
.env
View File

@@ -1,4 +1,5 @@
VITE_MAXKB_URL=https://maxkb.xuyue.cc/chat/api/embed?protocol=https&host=maxkb.xuyue.cc&token=2e801f7d6efdcc99 PUBLIC_ENV=dev
VITE_OJ_URL=http://localhost:8000 PUBLIC_MAXKB_URL=https://maxkb.xuyue.cc/chat/api/embed?protocol=https&host=maxkb.xuyue.cc&token=2e801f7d6efdcc99
VITE_CODE_URL=https://code.xuyue.cc PUBLIC_OJ_URL=http://localhost:8000
VITE_JUDGE0_URL=https://judge0api.xuyue.cc PUBLIC_CODE_URL=https://code.xuyue.cc
PUBLIC_JUDGE0_URL=https://judge0api.xuyue.cc

View File

@@ -1,4 +1,5 @@
VITE_MAXKB_URL=https://maxkb.xuyue.cc/chat/api/embed?protocol=https&host=maxkb.xuyue.cc&token=2e801f7d6efdcc99 PUBLIC_ENV=xuyue.cc
VITE_OJ_URL=https://oj.xuyue.cc PUBLIC_MAXKB_URL=https://maxkb.xuyue.cc/chat/api/embed?protocol=https&host=maxkb.xuyue.cc&token=2e801f7d6efdcc99
VITE_CODE_URL=https://code.xuyue.cc PUBLIC_OJ_URL=https://oj.xuyue.cc
VITE_JUDGE0_URL=https://judge0api.xuyue.cc PUBLIC_CODE_URL=https://code.xuyue.cc
PUBLIC_JUDGE0_URL=https://judge0api.xuyue.cc

View File

@@ -1,5 +1,6 @@
VITE_MAXKB_URL=https://maxkb.xuyue.cc/chat/api/embed?protocol=https&host=maxkb.xuyue.cc&token=2e801f7d6efdcc99 PUBLIC_ENV=school
VITE_OJ_URL=http://10.13.114.114:81 PUBLIC_MAXKB_URL=http://10.13.114.114:92/chat/api/embed?protocol=http&host=10.13.114.114:92&token=dd37457027c40b39
VITE_CODE_URL=http://10.13.114.114:82 PUBLIC_OJ_URL=http://10.13.114.114:81
VITE_JUDGE0_URL=http://10.13.114.114:8082 PUBLIC_CODE_URL=http://10.13.114.114:82
VITE_ICONIFY_URL=http://10.13.114.114:8098 PUBLIC_JUDGE0_URL=http://10.13.114.114:8082
PUBLIC_ICONIFY_URL=http://10.13.114.114:8098

6
.env.test Normal file
View File

@@ -0,0 +1,6 @@
PUBLIC_ENV=test
PUBLIC_MAXKB_URL=http://10.13.114.114:92/chat/api/embed?protocol=http&host=10.13.114.114:92&token=dd37457027c40b39
PUBLIC_OJ_URL=http://10.13.114.114:81
PUBLIC_CODE_URL=http://10.13.114.114:82
PUBLIC_JUDGE0_URL=http://10.13.114.114:8082
PUBLIC_ICONIFY_URL=http://10.13.114.114:8098

View File

@@ -12,11 +12,10 @@
<script <script
async async
defer defer
src="%VITE_MAXKB_URL%" src="<%= process.env.PUBLIC_MAXKB_URL %>"
></script> ></script>
</head> </head>
<body> <body>
<div id="app"></div> <div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body> </body>
</html> </html>

2542
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,46 +1,45 @@
{ {
"name": "oj-next", "name": "oj-next",
"version": "1.7.0", "version": "1.8.0",
"type": "module", "type": "module",
"scripts": { "scripts": {
"start": "vite", "start": "rsbuild dev",
"build": "vue-tsc && vite build", "build": "rsbuild build",
"build:staging": "vue-tsc && vite build --mode=staging", "build:staging": "rsbuild build --env-mode=staging",
"preview": "vite preview", "build:test": "rsbuild build --env-mode=test",
"fmt": "prettier --write src *.ts" "fmt": "prettier --write src *.ts"
}, },
"dependencies": { "dependencies": {
"@codemirror/lang-cpp": "^6.0.3", "@codemirror/lang-cpp": "^6.0.3",
"@codemirror/lang-python": "^6.2.1", "@codemirror/lang-python": "^6.2.1",
"@vueuse/core": "^13.7.0", "@vueuse/core": "^13.9.0",
"@wangeditor-next/editor": "^5.6.43", "@wangeditor-next/editor": "^5.6.45",
"@wangeditor-next/editor-for-vue": "^5.1.14", "@wangeditor-next/editor-for-vue": "^5.1.14",
"axios": "^1.11.0", "axios": "^1.12.2",
"canvas-confetti": "^1.9.3", "canvas-confetti": "^1.9.3",
"chart.js": "^4.5.0", "chart.js": "^4.5.0",
"codemirror": "^6.0.2", "codemirror": "^6.0.2",
"copy-text-to-clipboard": "^3.2.0", "copy-text-to-clipboard": "^3.2.1",
"date-fns": "^4.1.0", "date-fns": "^4.1.0",
"highlight.js": "^11.11.1", "highlight.js": "^11.11.1",
"md-editor-v3": "^5.8.4", "md-editor-v3": "^6.0.1",
"naive-ui": "^2.42.0", "naive-ui": "^2.43.1",
"normalize.css": "^8.0.1", "normalize.css": "^8.0.1",
"pinia": "^3.0.3", "pinia": "^3.0.3",
"vue": "^3.5.19", "vue": "^3.5.21",
"vue-chartjs": "^5.3.2", "vue-chartjs": "^5.3.2",
"vue-codemirror": "^6.1.1", "vue-codemirror": "^6.1.1",
"vue-router": "^4.5.1" "vue-router": "^4.5.1"
}, },
"devDependencies": { "devDependencies": {
"@iconify/vue": "^5.0.0", "@iconify/vue": "^5.0.0",
"@rsbuild/core": "^1.5.9",
"@rsbuild/plugin-vue": "^1.1.2",
"@types/canvas-confetti": "^1.9.0", "@types/canvas-confetti": "^1.9.0",
"@types/node": "^24.3.0", "@types/node": "^24.5.2",
"@vitejs/plugin-vue": "^6.0.1",
"prettier": "^3.6.2", "prettier": "^3.6.2",
"typescript": "^5.9.2", "typescript": "^5.9.2",
"unplugin-auto-import": "^20.0.0", "unplugin-auto-import": "^20.1.0",
"unplugin-vue-components": "^29.0.0", "unplugin-vue-components": "^29.0.0"
"vite": "^7.1.3",
"vue-tsc": "^3.0.6"
} }
} }

93
rsbuild.config.ts Normal file
View File

@@ -0,0 +1,93 @@
import { defineConfig, loadEnv } from "@rsbuild/core"
import { pluginVue } from "@rsbuild/plugin-vue"
import AutoImport from "unplugin-auto-import/rspack"
import Components from "unplugin-vue-components/rspack"
import { NaiveUiResolver } from "unplugin-vue-components/resolvers"
export default defineConfig(({ envMode }) => {
const { publicVars, rawPublicVars } = loadEnv({
cwd: process.cwd(),
mode: envMode,
})
const url = rawPublicVars["PUBLIC_OJ_URL"]
const proxyConfig = {
target: url,
headers: { Referer: url },
changeOrigin: true,
}
return {
plugins: [pluginVue()],
tools: {
rspack: {
plugins: [
AutoImport({
imports: [
"vue",
"vue-router",
"@vueuse/core",
"pinia",
{
"naive-ui": [
"useDialog",
"useMessage",
"useNotification",
"useLoadingBar",
],
},
{
from: "naive-ui",
imports: [
"DataTableColumn",
"FormRules",
"FormItemRule",
"SelectOption",
"UploadCustomRequestOptions",
"UploadFileInfo",
"MenuOption",
"DropdownDividerOption",
"DropdownOption",
],
type: true,
},
],
dts: "./src/auto-imports.d.ts",
}),
Components({
resolvers: [NaiveUiResolver()],
dts: "./src/components.d.ts",
}),
],
},
},
html: {
template: "./index.html",
},
source: {
entry: {
index: "./src/main.ts",
},
define: publicVars,
},
performance: {
chunkSplit: {
strategy: "split-by-module",
},
},
resolve: {
alias: {
"~": "./src",
utils: "./src/utils",
oj: "./src/oj",
admin: "./src/admin",
},
},
server: {
port: 5173,
proxy: {
"/api": proxyConfig,
"/public": proxyConfig,
},
},
}
})

View File

@@ -1,6 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import hljs from "highlight.js/lib/core" import hljs from "highlight.js/lib/core"
import c from "highlight.js/lib/languages/c" import c from "highlight.js/lib/languages/c"
import cpp from "highlight.js/lib/languages/cpp"
import python from "highlight.js/lib/languages/python" import python from "highlight.js/lib/languages/python"
import { darkTheme, dateZhCN, zhCN } from "naive-ui" import { darkTheme, dateZhCN, zhCN } from "naive-ui"
import "normalize.css" import "normalize.css"
@@ -8,6 +9,7 @@ import "./index.css"
hljs.registerLanguage("c", c) hljs.registerLanguage("c", c)
hljs.registerLanguage("python", python) hljs.registerLanguage("python", python)
hljs.registerLanguage("cpp", cpp)
const isDark = useDark() const isDark = useDark()
</script> </script>

View File

@@ -255,4 +255,4 @@ export function deleteTutorial(id: number) {
export function setTutorialVisibility(id: number, is_public: boolean) { export function setTutorialVisibility(id: number, is_public: boolean) {
return http.put("admin/tutorial/visibility", { id, is_public }) return http.put("admin/tutorial/visibility", { id, is_public })
} }

View File

@@ -105,8 +105,9 @@ const difficultyOptions: SelectOption[] = [
] ]
const languageOptions = [ const languageOptions = [
{ label: LANGUAGE_SHOW_VALUE["C"], value: "C" },
{ label: LANGUAGE_SHOW_VALUE["Python3"], value: "Python3" }, { label: LANGUAGE_SHOW_VALUE["Python3"], value: "Python3" },
{ label: LANGUAGE_SHOW_VALUE["C"], value: "C" },
{ label: LANGUAGE_SHOW_VALUE["C++"], value: "C++" },
] ]
const tagOptions = computed(() => const tagOptions = computed(() =>

View File

@@ -99,7 +99,7 @@ const abnormalServers = computed(() =>
) )
const websiteConfig = reactive({ const websiteConfig = reactive({
website_base_url: import.meta.env.VITE_OJ_URL, website_base_url: import.meta.env.PUBLIC_OJ_URL,
website_name: "判题狗", website_name: "判题狗",
website_name_shortcut: "判题狗", website_name_shortcut: "判题狗",
website_footer: "所有权归属于徐越,感谢青岛大学开源 OJ 系统,感谢开源社区", website_footer: "所有权归属于徐越,感谢青岛大学开源 OJ 系统,感谢开源社区",

View File

@@ -40,4 +40,4 @@ async function handleDelete() {
</n-popconfirm> </n-popconfirm>
</n-flex> </n-flex>
</template> </template>
<style scoped></style> <style scoped></style>

View File

@@ -87,7 +87,11 @@ onMounted(init)
/> />
</n-form-item> </n-form-item>
<n-form-item label="顺序"> <n-form-item label="顺序">
<n-input-number style="width: 100px" v-model:value="tutorial.order" :min="0" /> <n-input-number
style="width: 100px"
v-model:value="tutorial.order"
:min="0"
/>
</n-form-item> </n-form-item>
<n-form-item label="可见"> <n-form-item label="可见">
<n-switch v-model:value="tutorial.is_public" /> <n-switch v-model:value="tutorial.is_public" />

View File

@@ -102,7 +102,7 @@ async function onDeleteUsers(userIDs: DataTableRowKey[] | Ref<number[]>) {
async function onResetPassword(user: User) { async function onResetPassword(user: User) {
const res = await resetPassword(user.id) const res = await resetPassword(user.id)
message.success(`${user.username}】密码重置${res.data}`) message.success(`${user.username}密码重置成【${res.data}`)
users.value = users.value.map((it) => { users.value = users.value.map((it) => {
if (it.id === user.id && user.admin_type === USER_TYPE.REGULAR_USER) { if (it.id === user.id && user.admin_type === USER_TYPE.REGULAR_USER) {
it.raw_password = res.data it.raw_password = res.data

14
src/env.d.ts vendored Normal file
View File

@@ -0,0 +1,14 @@
/// <reference types="@rsbuild/core/types" />
interface ImportMetaEnv {
readonly PUBLIC_ENV: string
readonly PUBLIC_MAXKB_URL: string
readonly PUBLIC_OJ_URL: string
readonly PUBLIC_CODE_URL: string
readonly PUBLIC_JUDGE0_URL: string
readonly PUBLIC_ICONIFY_URL: string
}
interface ImportMeta {
readonly env: ImportMetaEnv
}

View File

@@ -34,8 +34,8 @@ app.use(router)
app.use(pinia) app.use(pinia)
app.mount("#app") app.mount("#app")
if (!!import.meta.env.VITE_ICONIFY_URL) { if (!!import.meta.env.PUBLIC_ICONIFY_URL) {
addAPIProvider("", { addAPIProvider("", {
resources: [import.meta.env.VITE_ICONIFY_URL], resources: [import.meta.env.PUBLIC_ICONIFY_URL],
}) })
} }

View File

@@ -239,4 +239,4 @@ export function getTutorial(id: number) {
export function getTutorials() { export function getTutorials() {
return http.get("tutorials") return http.get("tutorials")
} }

View File

@@ -92,7 +92,7 @@ async function select(key: string) {
copy() copy()
break break
case "test": case "test":
window.open(import.meta.env.VITE_CODE_URL, "_blank") window.open(import.meta.env.PUBLIC_CODE_URL, "_blank")
break break
} }
} }
@@ -103,7 +103,7 @@ function changeLanguage(v: LANGUAGE) {
} }
function gotoTestCat() { function gotoTestCat() {
const url = import.meta.env.VITE_CODE_URL const url = import.meta.env.PUBLIC_CODE_URL
window.open(url, "_blank") window.open(url, "_blank")
} }
</script> </script>

View File

@@ -15,6 +15,23 @@ const userStore = useUserStore()
const configStore = useConfigStore() const configStore = useConfigStore()
const route = useRoute() const route = useRoute()
const router = useRouter() const router = useRouter()
const envVersion = computed(() => {
if (import.meta.env.PUBLIC_ENV === "test") {
return "测试版"
} else if (import.meta.env.PUBLIC_ENV === "dev") {
return "开发版"
}
return ""
})
const showEnvVersion = computed(() => {
return (
import.meta.env.PUBLIC_ENV === "test" ||
import.meta.env.PUBLIC_ENV === "dev"
)
})
const active = computed(() => { const active = computed(() => {
const path = route.path.split("/")[1] || "problem" const path = route.path.split("/")[1] || "problem"
return !["user", "setting"].includes(path) ? path : "" return !["user", "setting"].includes(path) ? path : ""
@@ -131,6 +148,7 @@ function goHome() {
<n-flex align="center" class="title" @click="goHome"> <n-flex align="center" class="title" @click="goHome">
<Icon icon="streamline-emojis:dog" :height="30"></Icon> <Icon icon="streamline-emojis:dog" :height="30"></Icon>
<div>{{ configStore.config?.website_name }}</div> <div>{{ configStore.config?.website_name }}</div>
<div v-if="showEnvVersion">({{ envVersion }})</div>
</n-flex> </n-flex>
<div> <div>
<n-menu <n-menu

5
src/shims.d.ts vendored
View File

@@ -1,5 +0,0 @@
declare module "*.md" {
import type { ComponentOptions } from "vue"
const Component: ComponentOptions
export default Component
}

View File

@@ -2,7 +2,7 @@ import axios from "axios"
import { decode, encode } from "./functions" import { decode, encode } from "./functions"
import { Code } from "./types" import { Code } from "./types"
const http = axios.create({ baseURL: import.meta.env.VITE_JUDGE0_URL }) const http = axios.create({ baseURL: import.meta.env.PUBLIC_JUDGE0_URL })
export async function createTestSubmission(code: Code, input: string) { export async function createTestSubmission(code: Code, input: string) {
const encodedCode = encode(code.value) const encodedCode = encode(code.value)

View File

@@ -218,7 +218,6 @@ export interface Submission {
memory_cost?: number memory_cost?: number
} }
ip: string ip: string
// TODO: 这里不知道是什么
contest: null contest: null
problem: string problem: string
can_unshare: boolean can_unshare: boolean
@@ -384,7 +383,7 @@ export interface Tutorial {
is_public: boolean is_public: boolean
order: number order: number
type: "python" | "c" type: "python" | "c"
created_by?: User created_by?: User
updated_at?: Date updated_at?: Date
created_at?: Date created_at?: Date
} }

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

@@ -1,13 +0,0 @@
/// <reference types="vite/client" />
interface ImportMetaEnv {
readonly VITE_MAXKB_URL: string
readonly VITE_OJ_URL: string
readonly VITE_CODE_URL: string
readonly VITE_JUDGE0_URL: string
readonly VITE_ICONIFY_URL: string
}
interface ImportMeta {
readonly env: ImportMetaEnv
}

View File

@@ -5,5 +5,5 @@
"moduleResolution": "Node", "moduleResolution": "Node",
"allowSyntheticDefaultImports": true "allowSyntheticDefaultImports": true
}, },
"include": ["vite.config.ts"] "include": ["rsbuild.config.ts"]
} }

View File

@@ -1,96 +0,0 @@
import Vue from "@vitejs/plugin-vue"
import path from "path"
import AutoImport from "unplugin-auto-import/vite"
import { NaiveUiResolver } from "unplugin-vue-components/resolvers"
import Components from "unplugin-vue-components/vite"
import { defineConfig, loadEnv } from "vite"
export default defineConfig(({ mode }) => {
const env = loadEnv(mode, process.cwd())
const url = env.VITE_OJ_URL
const proxyConfig = {
target: url,
changeOrigin: true,
headers: { Referer: url },
}
return {
build: {
rollupOptions: {
output: {
manualChunks: {
chart: ["vue-chartjs", "chart.js"],
editor: [
"@wangeditor-next/editor",
"@wangeditor-next/editor-for-vue",
],
cm: [
"vue-codemirror",
"codemirror",
"@codemirror/lang-cpp",
"@codemirror/lang-python",
],
md: [
"md-editor-v3",
"md-editor-v3/lib/style.css",
"md-editor-v3/lib/preview.css",
],
},
},
},
},
resolve: {
alias: {
"~": path.resolve(__dirname, "./src"),
utils: path.resolve(__dirname, "./src/utils"),
oj: path.resolve(__dirname, "./src/oj"),
admin: path.resolve(__dirname, "./src/admin"),
},
},
plugins: [
Vue(),
AutoImport({
imports: [
"vue",
"vue-router",
"@vueuse/core",
"pinia",
{
"naive-ui": [
"useDialog",
"useMessage",
"useNotification",
"useLoadingBar",
],
},
{
from: "naive-ui",
imports: [
"DataTableColumn",
"FormRules",
"FormItemRule",
"SelectOption",
"UploadCustomRequestOptions",
"UploadFileInfo",
"MenuOption",
"DropdownDividerOption",
"DropdownOption",
],
type: true,
},
],
dts: "./src/auto-imports.d.ts",
}),
Components({
resolvers: [NaiveUiResolver()],
dts: "./src/components.d.ts",
}),
],
server: {
proxy: {
"/api": proxyConfig,
"/public": proxyConfig,
},
},
}
})