Compare commits
5
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dd29f04664 | ||
|
|
5d418a2280 | ||
|
|
b4ac42b736 | ||
|
|
c227c1d506 | ||
|
|
38500671af |
@@ -4,7 +4,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
|
|||||||
|
|
||||||
## Project Overview
|
## Project Overview
|
||||||
|
|
||||||
**ojnext** is the frontend for an Online Judge platform. Built with Vue 3 + TypeScript using Rsbuild (Rust-based bundler), Naive UI component library, Pinia for state management, and Vue Router.
|
**ojnext** is the frontend for an Online Judge platform. Built with Vue 3 + TypeScript using Vite (Rolldown-based bundler), Naive UI component library, Pinia for state management, and Vue Router.
|
||||||
|
|
||||||
## Commands
|
## Commands
|
||||||
|
|
||||||
|
|||||||
+1
-8
@@ -9,16 +9,9 @@
|
|||||||
<script>
|
<script>
|
||||||
window.localStorage.setItem("maxkbMaskTip", true)
|
window.localStorage.setItem("maxkbMaskTip", true)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<% if (process.env.PUBLIC_MAXKB_URL) { %>
|
|
||||||
<script
|
|
||||||
async
|
|
||||||
defer
|
|
||||||
src="<%= process.env.PUBLIC_MAXKB_URL %>"
|
|
||||||
></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>
|
||||||
|
|||||||
Generated
+2845
-836
File diff suppressed because it is too large
Load Diff
+19
-17
@@ -1,18 +1,19 @@
|
|||||||
{
|
{
|
||||||
"name": "oj-next",
|
"name": "oj-next",
|
||||||
"version": "1.8.0",
|
"version": "1.9.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "rsbuild dev",
|
"start": "vite",
|
||||||
"build": "rsbuild build",
|
"build": "vite build",
|
||||||
"build:staging": "rsbuild build --env-mode=staging",
|
"build:staging": "vite build --mode staging",
|
||||||
"build:test": "rsbuild build --env-mode=test",
|
"build:test": "vite build --mode test",
|
||||||
"fmt": "prettier --write src *.ts"
|
"fmt": "prettier --write src *.ts"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@codemirror/autocomplete": "^6.20.3",
|
"@codemirror/autocomplete": "^6.20.3",
|
||||||
"@codemirror/lang-cpp": "^6.0.3",
|
"@codemirror/lang-cpp": "^6.0.3",
|
||||||
"@codemirror/lang-python": "^6.2.1",
|
"@codemirror/lang-python": "^6.2.1",
|
||||||
|
"@codemirror/lang-sql": "^6.10.0",
|
||||||
"@vue-flow/background": "^1.3.2",
|
"@vue-flow/background": "^1.3.2",
|
||||||
"@vue-flow/controls": "^1.1.3",
|
"@vue-flow/controls": "^1.1.3",
|
||||||
"@vue-flow/core": "^1.48.2",
|
"@vue-flow/core": "^1.48.2",
|
||||||
@@ -21,9 +22,9 @@
|
|||||||
"@vue-flow/node-toolbar": "^1.1.1",
|
"@vue-flow/node-toolbar": "^1.1.1",
|
||||||
"@vueuse/core": "^14.3.0",
|
"@vueuse/core": "^14.3.0",
|
||||||
"@vueuse/router": "^14.3.0",
|
"@vueuse/router": "^14.3.0",
|
||||||
"@wangeditor-next/editor": "^5.7.12",
|
"@wangeditor-next/editor": "^5.7.13",
|
||||||
"@wangeditor-next/editor-for-vue": "^5.1.14",
|
"@wangeditor-next/editor-for-vue": "^5.1.14",
|
||||||
"axios": "^1.18.0",
|
"axios": "^1.18.1",
|
||||||
"canvas-confetti": "^1.9.4",
|
"canvas-confetti": "^1.9.4",
|
||||||
"chart.js": "^4.5.1",
|
"chart.js": "^4.5.1",
|
||||||
"chartjs-chart-wordcloud": "^4.4.5",
|
"chartjs-chart-wordcloud": "^4.4.5",
|
||||||
@@ -33,15 +34,15 @@
|
|||||||
"date-fns": "^4.4.0",
|
"date-fns": "^4.4.0",
|
||||||
"fflate": "^0.8.3",
|
"fflate": "^0.8.3",
|
||||||
"highlight.js": "^11.11.1",
|
"highlight.js": "^11.11.1",
|
||||||
"md-editor-v3": "^6.5.1",
|
"md-editor-v3": "^6.5.3",
|
||||||
"mermaid": "^11.15.0",
|
"mermaid": "^11.16.0",
|
||||||
"mermaid-legacy": "npm:mermaid@^9.1.7",
|
"mermaid-legacy": "npm:mermaid@^9.4.3",
|
||||||
"naive-ui": "^2.44.1",
|
"naive-ui": "^2.44.1",
|
||||||
"nanoid": "^5.1.15",
|
"nanoid": "^5.1.16",
|
||||||
"normalize.css": "^8.0.1",
|
"normalize.css": "^8.0.1",
|
||||||
"pinia": "^3.0.4",
|
"pinia": "^3.0.4",
|
||||||
"skulpt": "^1.2.0",
|
"skulpt": "^1.2.0",
|
||||||
"vue": "^3.5.38",
|
"vue": "^3.5.39",
|
||||||
"vue-chartjs": "^5.3.3",
|
"vue-chartjs": "^5.3.3",
|
||||||
"vue-codemirror": "^6.1.1",
|
"vue-codemirror": "^6.1.1",
|
||||||
"vue-router": "^5.1.0",
|
"vue-router": "^5.1.0",
|
||||||
@@ -51,14 +52,15 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@iconify/vue": "^5.0.1",
|
"@iconify/vue": "^5.0.1",
|
||||||
"@rsbuild/core": "^1.7.5",
|
|
||||||
"@rsbuild/plugin-vue": "^1.2.9",
|
|
||||||
"@types/canvas-confetti": "^1.9.0",
|
"@types/canvas-confetti": "^1.9.0",
|
||||||
"@types/node": "^26.0.0",
|
"@types/node": "^26.1.0",
|
||||||
|
"@vitejs/plugin-legacy": "^8.1.0",
|
||||||
|
"@vitejs/plugin-vue": "^6.0.7",
|
||||||
"@vue/tsconfig": "^0.9.1",
|
"@vue/tsconfig": "^0.9.1",
|
||||||
"prettier": "^3.8.4",
|
"prettier": "^3.9.4",
|
||||||
"typescript": "^6.0.3",
|
"typescript": "^6.0.3",
|
||||||
"unplugin-auto-import": "^21.0.0",
|
"unplugin-auto-import": "^21.0.0",
|
||||||
"unplugin-vue-components": "^32.1.0"
|
"unplugin-vue-components": "^32.1.0",
|
||||||
|
"vite": "^8.1.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,104 +0,0 @@
|
|||||||
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"
|
|
||||||
|
|
||||||
const config: ReturnType<typeof defineConfig> = defineConfig(({ envMode }) => {
|
|
||||||
const { publicVars, rawPublicVars } = loadEnv({
|
|
||||||
cwd: process.cwd(),
|
|
||||||
mode: envMode,
|
|
||||||
})
|
|
||||||
|
|
||||||
const proxyConfig = {
|
|
||||||
target: rawPublicVars["PUBLIC_OJ_URL"],
|
|
||||||
changeOrigin: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
const wsProxyConfig = {
|
|
||||||
target: rawPublicVars["PUBLIC_WS_URL"],
|
|
||||||
ws: true,
|
|
||||||
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,
|
|
||||||
},
|
|
||||||
output: {
|
|
||||||
polyfill: "usage",
|
|
||||||
},
|
|
||||||
performance: {
|
|
||||||
chunkSplit: {
|
|
||||||
strategy: "split-by-module",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
resolve: {
|
|
||||||
alias: {
|
|
||||||
utils: "./src/utils",
|
|
||||||
oj: "./src/oj",
|
|
||||||
admin: "./src/admin",
|
|
||||||
shared: "./src/shared",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
server: {
|
|
||||||
port: 5173,
|
|
||||||
proxy: {
|
|
||||||
"/api": proxyConfig,
|
|
||||||
"/public": proxyConfig,
|
|
||||||
"/ws": wsProxyConfig,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
export default config
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import TextEditor from "shared/components/TextEditor.vue"
|
import TextEditor from "shared/components/TextEditor.vue"
|
||||||
import { AnnouncementEdit } from "utils/types"
|
import type { AnnouncementEdit } from "utils/types"
|
||||||
import { createAnnouncement, editAnnouncement, getAnnouncement } from "../api"
|
import { createAnnouncement, editAnnouncement, getAnnouncement } from "../api"
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
import { NSwitch } from "naive-ui"
|
import { NSwitch } from "naive-ui"
|
||||||
import Pagination from "shared/components/Pagination.vue"
|
import Pagination from "shared/components/Pagination.vue"
|
||||||
import { parseTime } from "utils/functions"
|
import { parseTime } from "utils/functions"
|
||||||
import { Announcement } from "utils/types"
|
import type { Announcement } from "utils/types"
|
||||||
import { editAnnouncement, getAnnouncementList } from "../api"
|
import { editAnnouncement, getAnnouncementList } from "../api"
|
||||||
import Actions from "./components/Actions.vue"
|
import Actions from "./components/Actions.vue"
|
||||||
|
|
||||||
|
|||||||
+5
-2
@@ -133,10 +133,13 @@ export async function uploadImage(file: File): Promise<string> {
|
|||||||
return res.success ? res.file_path : ""
|
return res.success ? res.file_path : ""
|
||||||
}
|
}
|
||||||
|
|
||||||
// 上传测试用例
|
// 上传测试用例;SQL 题的压缩包是 1.sql..N.sql(每个文件一个测试点的建表+数据脚本)
|
||||||
export function uploadTestcases(file: File) {
|
export function uploadTestcases(file: File, options: { sql?: boolean } = {}) {
|
||||||
const form = new window.FormData()
|
const form = new window.FormData()
|
||||||
form.append("file", file)
|
form.append("file", file)
|
||||||
|
if (options.sql) {
|
||||||
|
form.append("sql", "1")
|
||||||
|
}
|
||||||
return http.post<TestcaseUploadedReturns>("admin/test_case", form, {
|
return http.post<TestcaseUploadedReturns>("admin/test_case", form, {
|
||||||
headers: { "content-type": "multipart/form-data" },
|
headers: { "content-type": "multipart/form-data" },
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
import { NButton } from "naive-ui"
|
import { NButton } from "naive-ui"
|
||||||
import Pagination from "shared/components/Pagination.vue"
|
import Pagination from "shared/components/Pagination.vue"
|
||||||
import { parseTime } from "utils/functions"
|
import { parseTime } from "utils/functions"
|
||||||
import { Comment } from "utils/types"
|
import type { Comment } from "utils/types"
|
||||||
import { getCommentList } from "../api"
|
import { getCommentList } from "../api"
|
||||||
import CommentActions from "./components/CommentActions.vue"
|
import CommentActions from "./components/CommentActions.vue"
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { Contest } from "utils/types"
|
import type { Contest } from "utils/types"
|
||||||
import { cloneContest } from "../../api"
|
import { cloneContest } from "../../api"
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
|||||||
+139
-48
@@ -10,7 +10,13 @@ import {
|
|||||||
} from "utils/constants"
|
} from "utils/constants"
|
||||||
import download from "utils/download"
|
import download from "utils/download"
|
||||||
import { unique } from "utils/functions"
|
import { unique } from "utils/functions"
|
||||||
import type { BlankProblem, LANGUAGE, Tag, Testcase } from "utils/types"
|
import type {
|
||||||
|
BlankProblem,
|
||||||
|
LANGUAGE,
|
||||||
|
SQLConfig,
|
||||||
|
Tag,
|
||||||
|
Testcase,
|
||||||
|
} from "utils/types"
|
||||||
import {
|
import {
|
||||||
createContestProblem,
|
createContestProblem,
|
||||||
createProblem,
|
createProblem,
|
||||||
@@ -89,6 +95,7 @@ const problem = useLocalStorage<BlankProblem>(STORAGE_KEY.ADMIN_PROBLEM, {
|
|||||||
flowchart_hint: "",
|
flowchart_hint: "",
|
||||||
show_flowchart: false,
|
show_flowchart: false,
|
||||||
ast_rules: null as { [key: string]: any[] } | null,
|
ast_rules: null as { [key: string]: any[] } | null,
|
||||||
|
sql_config: null as SQLConfig | null,
|
||||||
})
|
})
|
||||||
|
|
||||||
// 从服务器来的tag列表
|
// 从服务器来的tag列表
|
||||||
@@ -171,8 +178,40 @@ const languageOptions = [
|
|||||||
{ 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" },
|
||||||
{ label: LANGUAGE_SHOW_VALUE["C++"], value: "C++" },
|
{ label: LANGUAGE_SHOW_VALUE["C++"], value: "C++" },
|
||||||
|
{ label: LANGUAGE_SHOW_VALUE["SQL"], value: "SQL" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
const isSQLProblem = computed(() => !!problem.value?.languages.includes("SQL"))
|
||||||
|
|
||||||
|
// SQL 题联动:SQL 必须是唯一语言(后端强校验),不需要预制代码,自动初始化 sql_config
|
||||||
|
watch(
|
||||||
|
() => problem.value?.languages,
|
||||||
|
(langs) => {
|
||||||
|
if (!langs) return
|
||||||
|
if (langs.includes("SQL")) {
|
||||||
|
if (langs.length > 1) {
|
||||||
|
problem.value.languages = ["SQL"]
|
||||||
|
return
|
||||||
|
}
|
||||||
|
needTemplate.value = false
|
||||||
|
if (!problem.value.sql_config) {
|
||||||
|
problem.value.sql_config = { mode: "query", order_sensitive: false }
|
||||||
|
}
|
||||||
|
currentActiveAnswer.value = "SQL"
|
||||||
|
// 代码规则检查基于 Python/C 的 AST 解析,对 SQL 没有意义,清空避免脏数据
|
||||||
|
if (problem.value.ast_rules) {
|
||||||
|
problem.value.ast_rules = null
|
||||||
|
}
|
||||||
|
// 流程图依赖 Python 答案生成,对 SQL 没有意义
|
||||||
|
problem.value.allow_flowchart = false
|
||||||
|
problem.value.show_flowchart = false
|
||||||
|
} else if (problem.value.sql_config) {
|
||||||
|
problem.value.sql_config = null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ immediate: true },
|
||||||
|
)
|
||||||
|
|
||||||
async function getProblemDetail() {
|
async function getProblemDetail() {
|
||||||
if (!props.problemID) {
|
if (!props.problemID) {
|
||||||
syncTagInputsFromProblemTags()
|
syncTagInputsFromProblemTags()
|
||||||
@@ -211,6 +250,7 @@ async function getProblemDetail() {
|
|||||||
problem.value.flowchart_hint = data.flowchart_hint ?? ""
|
problem.value.flowchart_hint = data.flowchart_hint ?? ""
|
||||||
problem.value.flowchart_data = data.flowchart_data
|
problem.value.flowchart_data = data.flowchart_data
|
||||||
problem.value.ast_rules = data.ast_rules ?? null
|
problem.value.ast_rules = data.ast_rules ?? null
|
||||||
|
problem.value.sql_config = data.sql_config ?? null
|
||||||
if (data.answers && data.answers.length) {
|
if (data.answers && data.answers.length) {
|
||||||
problem.value.answers = data.answers
|
problem.value.answers = data.answers
|
||||||
} else {
|
} else {
|
||||||
@@ -262,7 +302,7 @@ function resetTemplate(language: LANGUAGE) {
|
|||||||
|
|
||||||
async function handleUploadTestcases({ file }: UploadCustomRequestOptions) {
|
async function handleUploadTestcases({ file }: UploadCustomRequestOptions) {
|
||||||
try {
|
try {
|
||||||
const res = await uploadTestcases(file.file!)
|
const res = await uploadTestcases(file.file!, { sql: isSQLProblem.value })
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
if (res.error) {
|
if (res.error) {
|
||||||
message.error("上传测试用例失败")
|
message.error("上传测试用例失败")
|
||||||
@@ -332,6 +372,19 @@ async function validateProblem() {
|
|||||||
message.error("编程语言没有选择")
|
message.error("编程语言没有选择")
|
||||||
hasErrors = true
|
hasErrors = true
|
||||||
}
|
}
|
||||||
|
// SQL 题验证
|
||||||
|
else if (isSQLProblem.value && !problem.value.sql_config?.mode) {
|
||||||
|
message.error("SQL 题需要选择题型(查询题/增删改题)")
|
||||||
|
hasErrors = true
|
||||||
|
} else if (
|
||||||
|
isSQLProblem.value &&
|
||||||
|
!problem.value.answers.find(
|
||||||
|
(ans) => ans.language === "SQL" && ans.code.trim() !== "",
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
message.error("SQL 题必须填写标准答案(判题时用它生成期望结果)")
|
||||||
|
hasErrors = true
|
||||||
|
}
|
||||||
// 流程图验证
|
// 流程图验证
|
||||||
else if (problem.value.show_flowchart || problem.value.allow_flowchart) {
|
else if (problem.value.show_flowchart || problem.value.allow_flowchart) {
|
||||||
if (
|
if (
|
||||||
@@ -611,7 +664,7 @@ watch(
|
|||||||
</n-flex>
|
</n-flex>
|
||||||
</n-checkbox-group>
|
</n-checkbox-group>
|
||||||
</n-form-item>
|
</n-form-item>
|
||||||
<n-form-item>
|
<n-form-item v-if="!isSQLProblem">
|
||||||
<n-checkbox
|
<n-checkbox
|
||||||
v-model:checked="needTemplate"
|
v-model:checked="needTemplate"
|
||||||
label="预制代码(显示在编辑器中,帮助快速上手)"
|
label="预制代码(显示在编辑器中,帮助快速上手)"
|
||||||
@@ -630,10 +683,37 @@ watch(
|
|||||||
</n-form-item>
|
</n-form-item>
|
||||||
</n-form>
|
</n-form>
|
||||||
|
|
||||||
|
<n-form
|
||||||
|
v-if="isSQLProblem && problem.sql_config"
|
||||||
|
inline
|
||||||
|
label-placement="left"
|
||||||
|
>
|
||||||
|
<n-form-item label="SQL 题型">
|
||||||
|
<n-radio-group v-model:value="problem.sql_config.mode">
|
||||||
|
<n-radio-button value="query">查询题(比对查询结果)</n-radio-button>
|
||||||
|
<n-radio-button value="modify">
|
||||||
|
增删改题(比对执行后的表数据)
|
||||||
|
</n-radio-button>
|
||||||
|
</n-radio-group>
|
||||||
|
</n-form-item>
|
||||||
|
<n-form-item label="严格比对行顺序">
|
||||||
|
<n-switch v-model:value="problem.sql_config.order_sensitive" />
|
||||||
|
<n-text depth="3" style="margin-left: 12px">
|
||||||
|
题目要求 ORDER BY 时开启;关闭则按无序集合比对
|
||||||
|
</n-text>
|
||||||
|
</n-form-item>
|
||||||
|
</n-form>
|
||||||
|
|
||||||
<n-grid :cols="2" x-gap="20">
|
<n-grid :cols="2" x-gap="20">
|
||||||
<n-gi>
|
<n-gi>
|
||||||
<n-form>
|
<n-form>
|
||||||
<n-form-item label="本题参考答案(选填,用于 AI 分析,不会泄露)">
|
<n-form-item
|
||||||
|
:label="
|
||||||
|
isSQLProblem
|
||||||
|
? '标准答案(必填,判题依据:每个测试点会运行它生成期望结果)'
|
||||||
|
: '本题参考答案(选填,用于 AI 分析,不会泄露)'
|
||||||
|
"
|
||||||
|
>
|
||||||
<n-tabs
|
<n-tabs
|
||||||
type="segment"
|
type="segment"
|
||||||
default-value="Python3"
|
default-value="Python3"
|
||||||
@@ -681,7 +761,7 @@ watch(
|
|||||||
</n-gi>
|
</n-gi>
|
||||||
</n-grid>
|
</n-grid>
|
||||||
|
|
||||||
<n-grid :cols="2">
|
<n-grid v-if="!isSQLProblem" :cols="2">
|
||||||
<n-gi :span="1">
|
<n-gi :span="1">
|
||||||
<AstRulesEditor
|
<AstRulesEditor
|
||||||
v-model="problem.ast_rules!"
|
v-model="problem.ast_rules!"
|
||||||
@@ -695,7 +775,7 @@ watch(
|
|||||||
<h2 class="title">测试用例区域</h2>
|
<h2 class="title">测试用例区域</h2>
|
||||||
|
|
||||||
<n-flex align="center" style="margin-bottom: 12px">
|
<n-flex align="center" style="margin-bottom: 12px">
|
||||||
<div>
|
<div v-if="!isSQLProblem">
|
||||||
<n-button type="success" @click="showGeneratorModal = true">
|
<n-button type="success" @click="showGeneratorModal = true">
|
||||||
(新)直接生成
|
(新)直接生成
|
||||||
</n-button>
|
</n-button>
|
||||||
@@ -706,14 +786,23 @@ watch(
|
|||||||
accept=".zip"
|
accept=".zip"
|
||||||
:custom-request="handleUploadTestcases"
|
:custom-request="handleUploadTestcases"
|
||||||
>
|
>
|
||||||
<n-button type="info">(老)手动上传</n-button>
|
<n-button type="info">
|
||||||
|
{{ isSQLProblem ? "上传数据脚本压缩包" : "(老)手动上传" }}
|
||||||
|
</n-button>
|
||||||
</n-upload>
|
</n-upload>
|
||||||
</div>
|
</div>
|
||||||
<n-tooltip placement="right">
|
<n-tooltip placement="right" style="max-width: 320px; white-space: normal">
|
||||||
<template #trigger>
|
<template #trigger>
|
||||||
<n-button text>温馨提醒</n-button>
|
<n-button text>温馨提醒</n-button>
|
||||||
</template>
|
</template>
|
||||||
【测试用例】最好要有10个,要考虑边界情况,且不要跟【测试样例】一模一样
|
<template v-if="isSQLProblem">
|
||||||
|
压缩包内放 1.sql、2.sql…(每个文件是一个测试点的建表+插入数据脚本,
|
||||||
|
判题时对每个测试点分别运行标准答案和学生 SQL 比对结果)。 建议至少 2-3
|
||||||
|
个数据不同的测试点,防止学生硬编码答案
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
【测试用例】最好要有10个,要考虑边界情况,且不要跟【测试样例】一模一样
|
||||||
|
</template>
|
||||||
</n-tooltip>
|
</n-tooltip>
|
||||||
</n-flex>
|
</n-flex>
|
||||||
|
|
||||||
@@ -758,48 +847,50 @@ watch(
|
|||||||
/>
|
/>
|
||||||
</n-modal>
|
</n-modal>
|
||||||
|
|
||||||
<n-divider />
|
<template v-if="!isSQLProblem">
|
||||||
|
<n-divider />
|
||||||
|
|
||||||
<h2 class="title">流程图区域</h2>
|
<h2 class="title">流程图区域</h2>
|
||||||
|
|
||||||
<!-- 流程图相关设置 -->
|
<!-- 流程图相关设置 -->
|
||||||
<n-form inline label-placement="left" :show-feedback="false">
|
<n-form inline label-placement="left" :show-feedback="false">
|
||||||
<n-form-item label="根据上面的【Python答案】智能生成 Mermaid 代码">
|
<n-form-item label="根据上面的【Python答案】智能生成 Mermaid 代码">
|
||||||
<n-button
|
<n-button
|
||||||
type="primary"
|
type="primary"
|
||||||
size="small"
|
size="small"
|
||||||
:disabled="
|
:disabled="
|
||||||
!problem.answers.filter((a) => a.language === 'Python3')[0].code
|
!problem.answers.filter((a) => a.language === 'Python3')[0]?.code
|
||||||
.length
|
.length
|
||||||
"
|
"
|
||||||
:loading="isAIGenerating"
|
:loading="isAIGenerating"
|
||||||
@click="generateMermaid"
|
@click="generateMermaid"
|
||||||
>
|
>
|
||||||
AI 生成
|
AI 生成
|
||||||
</n-button>
|
</n-button>
|
||||||
</n-form-item>
|
</n-form-item>
|
||||||
<n-form-item label="允许提交流程图">
|
<n-form-item label="允许提交流程图">
|
||||||
<n-switch v-model:value="problem.allow_flowchart" />
|
<n-switch v-model:value="problem.allow_flowchart" />
|
||||||
</n-form-item>
|
</n-form-item>
|
||||||
<n-form-item label="显示标准流程图">
|
<n-form-item label="显示标准流程图">
|
||||||
<n-switch v-model:value="problem.show_flowchart" />
|
<n-switch v-model:value="problem.show_flowchart" />
|
||||||
</n-form-item>
|
</n-form-item>
|
||||||
</n-form>
|
</n-form>
|
||||||
|
|
||||||
<n-form>
|
<n-form>
|
||||||
<n-form-item>
|
<n-form-item>
|
||||||
<MermaidEditor
|
<MermaidEditor
|
||||||
v-model="problem.mermaid_code"
|
v-model="problem.mermaid_code"
|
||||||
@render-success="onMermaidRenderSuccess"
|
@render-success="onMermaidRenderSuccess"
|
||||||
/>
|
/>
|
||||||
</n-form-item>
|
</n-form-item>
|
||||||
<n-form-item label="流程图提示信息(选填)">
|
<n-form-item label="流程图提示信息(选填)">
|
||||||
<n-input
|
<n-input
|
||||||
v-model:value="problem.flowchart_hint"
|
v-model:value="problem.flowchart_hint"
|
||||||
placeholder="请输入流程图相关的提示信息,帮助学生理解题目要求"
|
placeholder="请输入流程图相关的提示信息,帮助学生理解题目要求"
|
||||||
/>
|
/>
|
||||||
</n-form-item>
|
</n-form-item>
|
||||||
</n-form>
|
</n-form>
|
||||||
|
</template>
|
||||||
<n-flex style="margin-bottom: 120px" align="center" justify="end">
|
<n-flex style="margin-bottom: 120px" align="center" justify="end">
|
||||||
<n-button type="primary" @click="submit">提交</n-button>
|
<n-button type="primary" @click="submit">提交</n-button>
|
||||||
</n-flex>
|
</n-flex>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { h } from "vue"
|
import { h } from "vue"
|
||||||
import { ProblemSetBadge } from "utils/types"
|
import type { ProblemSetBadge } from "utils/types"
|
||||||
import { NButton, NImage } from "naive-ui"
|
import { NButton, NImage } from "naive-ui"
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ProblemSetBadge } from "utils/types"
|
import type { ProblemSetBadge } from "utils/types"
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
show: boolean
|
show: boolean
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ProblemSetProblem } from "utils/types"
|
import type { ProblemSetProblem } from "utils/types"
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
show: boolean
|
show: boolean
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { h } from "vue"
|
import { h } from "vue"
|
||||||
import { NDataTable, NButton, NFlex } from "naive-ui"
|
import { NDataTable, NButton, NFlex } from "naive-ui"
|
||||||
import { ProblemSetProblem } from "utils/types"
|
import type { ProblemSetProblem } from "utils/types"
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
problems: ProblemSetProblem[]
|
problems: ProblemSetProblem[]
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { parseTime } from "utils/functions"
|
import { parseTime } from "utils/functions"
|
||||||
import { ProblemSet } from "utils/types"
|
import type { ProblemSet } from "utils/types"
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
problemSet: ProblemSet
|
problemSet: ProblemSet
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
import { h } from "vue"
|
import { h } from "vue"
|
||||||
import { NDataTable, NButton, NFlex } from "naive-ui"
|
import { NDataTable, NButton, NFlex } from "naive-ui"
|
||||||
import { parseTime } from "utils/functions"
|
import { parseTime } from "utils/functions"
|
||||||
import { ProblemSetProgress } from "utils/types"
|
import type { ProblemSetProgress } from "utils/types"
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
progress: ProblemSetProgress[]
|
progress: ProblemSetProgress[]
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { NTabPane, NTabs, NButton, NFlex } from "naive-ui"
|
import { NTabPane, NTabs, NButton, NFlex } from "naive-ui"
|
||||||
import {
|
import type {
|
||||||
ProblemSet,
|
ProblemSet,
|
||||||
ProblemSetProblem,
|
ProblemSetProblem,
|
||||||
ProblemSetBadge,
|
ProblemSetBadge,
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { CreateProblemSetData, EditProblemSetData } from "utils/types"
|
import type { CreateProblemSetData, EditProblemSetData } from "utils/types"
|
||||||
import { getProblemSetDetail, createProblemSet, editProblemSet } from "../api"
|
import { getProblemSetDetail, createProblemSet, editProblemSet } from "../api"
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { NButton, NTag } from "naive-ui"
|
import { NButton, NTag } from "naive-ui"
|
||||||
import { parseTime } from "utils/functions"
|
import { parseTime } from "utils/functions"
|
||||||
import { Server } from "utils/types"
|
import type { Server } from "utils/types"
|
||||||
import { useConfigStore } from "shared/store/config"
|
import { useConfigStore } from "shared/store/config"
|
||||||
import { useConfigWebSocket } from "shared/composables/websocket"
|
import { useConfigWebSocket } from "shared/composables/websocket"
|
||||||
import {
|
import {
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { getRank } from "oj/api"
|
|||||||
import Pagination from "shared/components/Pagination.vue"
|
import Pagination from "shared/components/Pagination.vue"
|
||||||
import { useUserStore } from "shared/store/user"
|
import { useUserStore } from "shared/store/user"
|
||||||
import { getACRate } from "utils/functions"
|
import { getACRate } from "utils/functions"
|
||||||
import { Rank } from "utils/types"
|
import type { Rank } from "utils/types"
|
||||||
import { getBaseInfo, randomUser10 } from "../api"
|
import { getBaseInfo, randomUser10 } from "../api"
|
||||||
|
|
||||||
const userCount = ref(0)
|
const userCount = ref(0)
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import {
|
import type {
|
||||||
Exercise,
|
Exercise,
|
||||||
ExerciseType,
|
ExerciseType,
|
||||||
ExerciseMcqData,
|
ExerciseMcqData,
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import CodeEditor from "shared/components/CodeEditor.vue"
|
import CodeEditor from "shared/components/CodeEditor.vue"
|
||||||
import MarkdownEditor from "shared/components/MarkdownEditor.vue"
|
import MarkdownEditor from "shared/components/MarkdownEditor.vue"
|
||||||
import { Tutorial } from "utils/types"
|
import type { Tutorial } from "utils/types"
|
||||||
import { createTutorial, getTutorial, updateTutorial } from "../api"
|
import { createTutorial, getTutorial, updateTutorial } from "../api"
|
||||||
import ExerciseManager from "./components/ExerciseManager.vue"
|
import ExerciseManager from "./components/ExerciseManager.vue"
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { NSwitch } from "naive-ui"
|
import { NSwitch } from "naive-ui"
|
||||||
import { parseTime } from "utils/functions"
|
import { parseTime } from "utils/functions"
|
||||||
import { Tutorial } from "utils/types"
|
import type { Tutorial } from "utils/types"
|
||||||
import { getTutorialList, setTutorialVisibility } from "../api"
|
import { getTutorialList, setTutorialVisibility } from "../api"
|
||||||
import Actions from "./components/Actions.vue"
|
import Actions from "./components/Actions.vue"
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { PROBLEM_PERMISSION, USER_TYPE } from "utils/constants"
|
import { PROBLEM_PERMISSION, USER_TYPE } from "utils/constants"
|
||||||
import { getUserRole } from "utils/functions"
|
import { getUserRole } from "utils/functions"
|
||||||
import { User } from "utils/types"
|
import type { User } from "utils/types"
|
||||||
import TextCopy from "shared/components/TextCopy.vue"
|
import TextCopy from "shared/components/TextCopy.vue"
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { DataTableRowKey, SelectOption } from "naive-ui"
|
|||||||
import Pagination from "shared/components/Pagination.vue"
|
import Pagination from "shared/components/Pagination.vue"
|
||||||
import { usePagination } from "shared/composables/pagination"
|
import { usePagination } from "shared/composables/pagination"
|
||||||
import { parseTime } from "utils/functions"
|
import { parseTime } from "utils/functions"
|
||||||
import { User } from "utils/types"
|
import type { User } from "utils/types"
|
||||||
import {
|
import {
|
||||||
deleteUsers,
|
deleteUsers,
|
||||||
editUser,
|
editUser,
|
||||||
|
|||||||
Vendored
+1
-9
@@ -1,4 +1,4 @@
|
|||||||
/// <reference types="@rsbuild/core/types" />
|
/// <reference types="vite/client" />
|
||||||
|
|
||||||
interface ImportMetaEnv {
|
interface ImportMetaEnv {
|
||||||
readonly PUBLIC_ENV: string
|
readonly PUBLIC_ENV: string
|
||||||
@@ -14,11 +14,3 @@ interface ImportMetaEnv {
|
|||||||
interface ImportMeta {
|
interface ImportMeta {
|
||||||
readonly env: ImportMetaEnv
|
readonly env: ImportMetaEnv
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Document {
|
|
||||||
startViewTransition?: (callback: () => void | Promise<void>) => {
|
|
||||||
ready: Promise<void>
|
|
||||||
finished: Promise<void>
|
|
||||||
updateCallbackDone: Promise<void>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -36,7 +36,7 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { NButton, NTooltip } from "naive-ui"
|
import { NButton, NTooltip } from "naive-ui"
|
||||||
import TagTitle from "./TagTitle.vue"
|
import TagTitle from "./TagTitle.vue"
|
||||||
import { FlowchartSummary, SolvedProblem } from "utils/types"
|
import type { FlowchartSummary, SolvedProblem } from "utils/types"
|
||||||
import { useAIStore } from "oj/store/ai"
|
import { useAIStore } from "oj/store/ai"
|
||||||
import { useBreakpoints } from "shared/composables/breakpoints"
|
import { useBreakpoints } from "shared/composables/breakpoints"
|
||||||
import { parseTime } from "utils/functions"
|
import { parseTime } from "utils/functions"
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import Pagination from "shared/components/Pagination.vue"
|
|||||||
import { useBreakpoints } from "shared/composables/breakpoints"
|
import { useBreakpoints } from "shared/composables/breakpoints"
|
||||||
import { parseTime } from "utils/functions"
|
import { parseTime } from "utils/functions"
|
||||||
import { renderTableTitle } from "utils/renders"
|
import { renderTableTitle } from "utils/renders"
|
||||||
import { Announcement } from "utils/types"
|
import type { Announcement } from "utils/types"
|
||||||
import TitleWithTag from "./components/TitleWithTag.vue"
|
import TitleWithTag from "./components/TitleWithTag.vue"
|
||||||
|
|
||||||
const total = ref(0)
|
const total = ref(0)
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ContestRank } from "utils/types"
|
import type { ContestRank } from "utils/types"
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
rank: ContestRank
|
rank: ContestRank
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import {
|
|||||||
Tooltip,
|
Tooltip,
|
||||||
Legend,
|
Legend,
|
||||||
} from "chart.js"
|
} from "chart.js"
|
||||||
import { ContestRank } from "utils/types"
|
import type { ContestRank } from "utils/types"
|
||||||
|
|
||||||
ChartJS.register(
|
ChartJS.register(
|
||||||
CategoryScale,
|
CategoryScale,
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ProblemFiltered } from "utils/types"
|
import type { ProblemFiltered } from "utils/types"
|
||||||
import ProblemStatus from "oj/problem/components/ProblemStatus.vue"
|
import ProblemStatus from "oj/problem/components/ProblemStatus.vue"
|
||||||
import { useContestStore } from "oj/store/contest"
|
import { useContestStore } from "oj/store/contest"
|
||||||
import { renderTableTitle } from "utils/renders"
|
import { renderTableTitle } from "utils/renders"
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { Exercise, ExerciseDebugData } from "utils/types"
|
import type { Exercise, ExerciseDebugData } from "utils/types"
|
||||||
import { highlightLines } from "../composables/useCodeHighlight"
|
import { highlightLines } from "../composables/useCodeHighlight"
|
||||||
import "./exercise-highlight.css"
|
import "./exercise-highlight.css"
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { Exercise, ExerciseGroupData } from "utils/types"
|
import type { Exercise, ExerciseGroupData } from "utils/types"
|
||||||
|
|
||||||
const props = defineProps<{ exercise: Exercise; lang?: string }>()
|
const props = defineProps<{ exercise: Exercise; lang?: string }>()
|
||||||
const data = computed(() => props.exercise.data as ExerciseGroupData)
|
const data = computed(() => props.exercise.data as ExerciseGroupData)
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { Exercise, ExerciseMatchData } from "utils/types"
|
import type { Exercise, ExerciseMatchData } from "utils/types"
|
||||||
|
|
||||||
const props = defineProps<{ exercise: Exercise; lang?: string }>()
|
const props = defineProps<{ exercise: Exercise; lang?: string }>()
|
||||||
const data = computed(() => props.exercise.data as ExerciseMatchData)
|
const data = computed(() => props.exercise.data as ExerciseMatchData)
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { Exercise, ExerciseMcqData } from "utils/types"
|
import type { Exercise, ExerciseMcqData } from "utils/types"
|
||||||
|
|
||||||
const props = defineProps<{ exercise: Exercise }>()
|
const props = defineProps<{ exercise: Exercise }>()
|
||||||
const data = computed(() => props.exercise.data as ExerciseMcqData)
|
const data = computed(() => props.exercise.data as ExerciseMcqData)
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { Exercise, ExercisePredictData } from "utils/types"
|
import type { Exercise, ExercisePredictData } from "utils/types"
|
||||||
import { highlight } from "../composables/useCodeHighlight"
|
import { highlight } from "../composables/useCodeHighlight"
|
||||||
import "./exercise-highlight.css"
|
import "./exercise-highlight.css"
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
import hljs from "highlight.js/lib/core"
|
import hljs from "highlight.js/lib/core"
|
||||||
import python from "highlight.js/lib/languages/python"
|
import python from "highlight.js/lib/languages/python"
|
||||||
import c from "highlight.js/lib/languages/c"
|
import c from "highlight.js/lib/languages/c"
|
||||||
import { Exercise, ExerciseSortData } from "utils/types"
|
import type { Exercise, ExerciseSortData } from "utils/types"
|
||||||
|
|
||||||
hljs.registerLanguage("python", python)
|
hljs.registerLanguage("python", python)
|
||||||
hljs.registerLanguage("c", c)
|
hljs.registerLanguage("c", c)
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { Exercise } from "utils/types"
|
import type { Exercise } from "utils/types"
|
||||||
|
|
||||||
const ExerciseMcq = defineAsyncComponent(() => import("./ExerciseMcq.vue"))
|
const ExerciseMcq = defineAsyncComponent(() => import("./ExerciseMcq.vue"))
|
||||||
const ExerciseSort = defineAsyncComponent(() => import("./ExerciseSort.vue"))
|
const ExerciseSort = defineAsyncComponent(() => import("./ExerciseSort.vue"))
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Exercise } from "utils/types"
|
import type { Exercise } from "utils/types"
|
||||||
|
|
||||||
type Segment =
|
type Segment =
|
||||||
| { type: "md"; content: string }
|
| { type: "md"; content: string }
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import CodeEditor from "shared/components/CodeEditor.vue"
|
|||||||
import { useBreakpoints } from "shared/composables/breakpoints"
|
import { useBreakpoints } from "shared/composables/breakpoints"
|
||||||
import { provideSyncStatus } from "oj/composables/syncStatus"
|
import { provideSyncStatus } from "oj/composables/syncStatus"
|
||||||
import storage from "utils/storage"
|
import storage from "utils/storage"
|
||||||
import { LANGUAGE } from "utils/types"
|
import type { LANGUAGE } from "utils/types"
|
||||||
import Form from "./Form.vue"
|
import Form from "./Form.vue"
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import {
|
|||||||
import { useBreakpoints } from "shared/composables/breakpoints"
|
import { useBreakpoints } from "shared/composables/breakpoints"
|
||||||
import { useUserStore } from "shared/store/user"
|
import { useUserStore } from "shared/store/user"
|
||||||
import storage from "utils/storage"
|
import storage from "utils/storage"
|
||||||
import { LANGUAGE } from "utils/types"
|
import type { LANGUAGE } from "utils/types"
|
||||||
import StatisticsPanel from "shared/components/StatisticsPanel.vue"
|
import StatisticsPanel from "shared/components/StatisticsPanel.vue"
|
||||||
import IconButton from "shared/components/IconButton.vue"
|
import IconButton from "shared/components/IconButton.vue"
|
||||||
import { Icon } from "@iconify/vue"
|
import { Icon } from "@iconify/vue"
|
||||||
@@ -140,7 +140,8 @@ defineExpose({
|
|||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
if (!languages.value.includes(codeStore.code.language)) {
|
if (!languages.value.includes(codeStore.code.language)) {
|
||||||
codeStore.code.language = "Python3"
|
// 回退到题目支持的第一种语言(如 SQL 题只有 "SQL",硬编码 Python3 会被后端拒绝)
|
||||||
|
codeStore.code.language = languages.value[0] ?? "Python3"
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
@@ -175,6 +176,7 @@ onMounted(() => {
|
|||||||
|
|
||||||
<template v-if="codeStore.code.language !== 'Flowchart'">
|
<template v-if="codeStore.code.language !== 'Flowchart'">
|
||||||
<IconButton
|
<IconButton
|
||||||
|
v-if="codeStore.code.language !== 'SQL'"
|
||||||
icon="streamline-ultimate-color:business-lucky-cat"
|
icon="streamline-ultimate-color:business-lucky-cat"
|
||||||
tip="自测猫"
|
tip="自测猫"
|
||||||
@click="goTestCat"
|
@click="goTestCat"
|
||||||
|
|||||||
@@ -130,6 +130,16 @@ onBeforeUnmount(() => {
|
|||||||
watch(isMobile, (value) => {
|
watch(isMobile, (value) => {
|
||||||
if (value) screenModeStore.resetScreenMode()
|
if (value) screenModeStore.resetScreenMode()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// SQL 题不支持"自测"模式(外部代码运行器无法执行 SQL),切到该屏时自动跳到下一模式
|
||||||
|
watch(
|
||||||
|
() => screenModeStore.isCodeOnlyMode,
|
||||||
|
(codeOnly) => {
|
||||||
|
if (codeOnly && problem.value?.languages.includes("SQL")) {
|
||||||
|
screenModeStore.switchScreenMode()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { Icon } from "@iconify/vue"
|
import { Icon } from "@iconify/vue"
|
||||||
import { ProblemSet, UserBadge as UserBadgeType } from "utils/types"
|
import type { ProblemSet, UserBadge as UserBadgeType } from "utils/types"
|
||||||
import UserBadge from "shared/components/UserBadge.vue"
|
import UserBadge from "shared/components/UserBadge.vue"
|
||||||
import { useUserStore } from "shared/store/user"
|
import { useUserStore } from "shared/store/user"
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { Icon } from "@iconify/vue"
|
import { Icon } from "@iconify/vue"
|
||||||
import { ProblemSetProblem } from "utils/types"
|
import type { ProblemSetProblem } from "utils/types"
|
||||||
import { DIFFICULTY } from "utils/constants"
|
import { DIFFICULTY } from "utils/constants"
|
||||||
import { getTagColor } from "utils/functions"
|
import { getTagColor } from "utils/functions"
|
||||||
import { useBreakpoints } from "shared/composables/breakpoints"
|
import { useBreakpoints } from "shared/composables/breakpoints"
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
import { h, computed, ref, onMounted, watch } from "vue"
|
import { h, computed, ref, onMounted, watch } from "vue"
|
||||||
import { watchDebounced } from "@vueuse/core"
|
import { watchDebounced } from "@vueuse/core"
|
||||||
import { parseTime } from "utils/functions"
|
import { parseTime } from "utils/functions"
|
||||||
import { ProblemSetProgress } from "utils/types"
|
import type { ProblemSetProgress } from "utils/types"
|
||||||
import { getProblemSetUserProgress } from "../../api"
|
import { getProblemSetUserProgress } from "../../api"
|
||||||
import { NFlex, NTag } from "naive-ui"
|
import { NFlex, NTag } from "naive-ui"
|
||||||
import { usePagination } from "shared/composables/pagination"
|
import { usePagination } from "shared/composables/pagination"
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import {
|
|||||||
joinProblemSet,
|
joinProblemSet,
|
||||||
getUserBadges,
|
getUserBadges,
|
||||||
} from "../api"
|
} from "../api"
|
||||||
import {
|
import type {
|
||||||
ProblemSet,
|
ProblemSet,
|
||||||
ProblemSetProblem,
|
ProblemSetProblem,
|
||||||
UserBadge as UserBadgeType,
|
UserBadge as UserBadgeType,
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import {
|
|||||||
Colors,
|
Colors,
|
||||||
} from "chart.js"
|
} from "chart.js"
|
||||||
import { ChartType } from "utils/constants"
|
import { ChartType } from "utils/constants"
|
||||||
import { Rank } from "utils/types"
|
import type { Rank } from "utils/types"
|
||||||
|
|
||||||
// 仅注册柱状图所需的 Chart.js 组件
|
// 仅注册柱状图所需的 Chart.js 组件
|
||||||
ChartJS.register(
|
ChartJS.register(
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
import { DetailsData, DurationData } from "utils/types"
|
import type { DetailsData, DurationData } from "utils/types"
|
||||||
import { consumeJSONEventStream } from "utils/stream"
|
import { consumeJSONEventStream } from "utils/stream"
|
||||||
import {
|
import {
|
||||||
getAIDetailData,
|
getAIDetailData,
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { defineStore } from "pinia"
|
import { defineStore } from "pinia"
|
||||||
import { STORAGE_KEY } from "utils/constants"
|
import { STORAGE_KEY } from "utils/constants"
|
||||||
import storage from "utils/storage"
|
import storage from "utils/storage"
|
||||||
import { Code, LANGUAGE } from "utils/types"
|
import type { Code, LANGUAGE } from "utils/types"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 代码编辑器状态管理 Store
|
* 代码编辑器状态管理 Store
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { formatISO, getTime, parseISO } from "date-fns"
|
|||||||
import { useUserStore } from "shared/store/user"
|
import { useUserStore } from "shared/store/user"
|
||||||
import { ContestStatus, ContestType } from "utils/constants"
|
import { ContestStatus, ContestType } from "utils/constants"
|
||||||
import { duration } from "utils/functions"
|
import { duration } from "utils/functions"
|
||||||
import { Contest, Problem } from "utils/types"
|
import type { Contest, Problem } from "utils/types"
|
||||||
import {
|
import {
|
||||||
checkContestPassword,
|
checkContestPassword,
|
||||||
getContest,
|
getContest,
|
||||||
|
|||||||
@@ -109,7 +109,7 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { Icon } from "@iconify/vue"
|
import { Icon } from "@iconify/vue"
|
||||||
import { FlowchartSubmission } from "utils/types"
|
import type { FlowchartSubmission } from "utils/types"
|
||||||
import { useMermaid } from "shared/composables/useMermaid"
|
import { useMermaid } from "shared/composables/useMermaid"
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
</n-text>
|
</n-text>
|
||||||
</template>
|
</template>
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { Grade } from "utils/types"
|
import type { Grade } from "utils/types"
|
||||||
defineProps<{
|
defineProps<{
|
||||||
score: number
|
score: number
|
||||||
grade: Grade
|
grade: Grade
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import {
|
|||||||
submissionTimeFormat,
|
submissionTimeFormat,
|
||||||
utoa,
|
utoa,
|
||||||
} from "utils/functions"
|
} from "utils/functions"
|
||||||
import { Submission } from "utils/types"
|
import type { Submission } from "utils/types"
|
||||||
import SubmissionResultTag from "shared/components/SubmissionResultTag.vue"
|
import SubmissionResultTag from "shared/components/SubmissionResultTag.vue"
|
||||||
import { useBreakpoints } from "shared/composables/breakpoints"
|
import { useBreakpoints } from "shared/composables/breakpoints"
|
||||||
import { useCodeStore } from "oj/store/code"
|
import { useCodeStore } from "oj/store/code"
|
||||||
@@ -127,7 +127,13 @@ onMounted(init)
|
|||||||
</n-flex>
|
</n-flex>
|
||||||
</n-alert>
|
</n-alert>
|
||||||
<n-flex :vertical="isDesktop" justify="center">
|
<n-flex :vertical="isDesktop" justify="center">
|
||||||
<n-button secondary @click="copyToCat">复制到自测猫</n-button>
|
<n-button
|
||||||
|
v-if="submission.language !== 'SQL'"
|
||||||
|
secondary
|
||||||
|
@click="copyToCat"
|
||||||
|
>
|
||||||
|
复制到自测猫
|
||||||
|
</n-button>
|
||||||
<n-button secondary @click="copyToProblem">复制回到题目</n-button>
|
<n-button secondary @click="copyToProblem">复制回到题目</n-button>
|
||||||
</n-flex>
|
</n-flex>
|
||||||
</n-flex>
|
</n-flex>
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ import { JUDGE_STATUS, LANGUAGE_FORMAT_VALUE } from "utils/constants"
|
|||||||
import Copy from "shared/components/Copy.vue"
|
import Copy from "shared/components/Copy.vue"
|
||||||
import Pagination from "shared/components/Pagination.vue"
|
import Pagination from "shared/components/Pagination.vue"
|
||||||
import { parseTime } from "utils/functions"
|
import { parseTime } from "utils/functions"
|
||||||
import { Message } from "utils/types"
|
import type { Message } from "utils/types"
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const messages = ref<Message[]>([])
|
const messages = ref<Message[]>([])
|
||||||
|
|||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
import http from "utils/http"
|
import http from "utils/http"
|
||||||
import { Profile, Tag } from "utils/types"
|
import type { Profile, Tag } from "utils/types"
|
||||||
|
|
||||||
export function login(data: { username: string; password: string }) {
|
export function login(data: { username: string; password: string }) {
|
||||||
return http.post("login", data)
|
return http.post("login", data)
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { Icon } from "@iconify/vue"
|
import { Icon } from "@iconify/vue"
|
||||||
import { ContestType } from "utils/constants"
|
import { ContestType } from "utils/constants"
|
||||||
import { Contest } from "utils/types"
|
import type { Contest } from "utils/types"
|
||||||
|
|
||||||
defineProps<{ contest: Contest }>()
|
defineProps<{ contest: Contest }>()
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ContestType } from "utils/constants"
|
import { ContestType } from "utils/constants"
|
||||||
import { Contest } from "utils/types"
|
import type { Contest } from "utils/types"
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
contest: Contest
|
contest: Contest
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { JUDGE_STATUS } from "utils/constants"
|
import { JUDGE_STATUS } from "utils/constants"
|
||||||
import { SUBMISSION_RESULT } from "utils/types"
|
import type { SUBMISSION_RESULT } from "utils/types"
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
result: SUBMISSION_RESULT
|
result: SUBMISSION_RESULT
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
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 { sql, SQLite } from "@codemirror/lang-sql"
|
||||||
import { bracketMatching } from "@codemirror/language"
|
import { bracketMatching } from "@codemirror/language"
|
||||||
import { EditorView } from "@codemirror/view"
|
import { EditorView } from "@codemirror/view"
|
||||||
import { Codemirror } from "vue-codemirror"
|
import { Codemirror } from "vue-codemirror"
|
||||||
@@ -57,6 +58,8 @@ const emit = defineEmits<{
|
|||||||
const { isDesktop } = useBreakpoints()
|
const { isDesktop } = useBreakpoints()
|
||||||
|
|
||||||
const langExtension = computed((): Extension => {
|
const langExtension = computed((): Extension => {
|
||||||
|
if (language === "SQL")
|
||||||
|
return sql({ dialect: SQLite, upperCaseKeywords: true })
|
||||||
return ["Python2", "Python3"].includes(language) ? python() : cpp()
|
return ["Python2", "Python3"].includes(language) ? python() : cpp()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -23,7 +23,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { UserBadge } from "utils/types"
|
import type { UserBadge } from "utils/types"
|
||||||
import { parseTime } from "utils/functions"
|
import { parseTime } from "utils/functions"
|
||||||
interface Props {
|
interface Props {
|
||||||
badge: UserBadge
|
badge: UserBadge
|
||||||
|
|||||||
@@ -27,6 +27,9 @@ export function enhanceCompletion(language: LANGUAGE): CompletionSource {
|
|||||||
const word = context.matchBefore(/\w+/)
|
const word = context.matchBefore(/\w+/)
|
||||||
if (!word && !context.explicit) return null
|
if (!word && !context.explicit) return null
|
||||||
|
|
||||||
|
// SQL 没有中文注释提示,关键字补全由 @codemirror/lang-sql 提供
|
||||||
|
if (language === "SQL") return null
|
||||||
|
|
||||||
const trulyLanguage = language.startsWith("Python") ? "python" : "c"
|
const trulyLanguage = language.startsWith("Python") ? "python" : "c"
|
||||||
const completions: Completion[] = (
|
const completions: Completion[] = (
|
||||||
chineseAnnotations[trulyLanguage] || []
|
chineseAnnotations[trulyLanguage] || []
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { getWebsiteConfig } from "oj/api"
|
import { getWebsiteConfig } from "oj/api"
|
||||||
import { WebsiteConfig } from "utils/types"
|
import type { WebsiteConfig } from "utils/types"
|
||||||
|
|
||||||
export const useConfigStore = defineStore("config", () => {
|
export const useConfigStore = defineStore("config", () => {
|
||||||
const config = ref<WebsiteConfig>({
|
const config = ref<WebsiteConfig>({
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { PROBLEM_PERMISSION, STORAGE_KEY, USER_TYPE } from "utils/constants"
|
import { PROBLEM_PERMISSION, STORAGE_KEY, USER_TYPE } from "utils/constants"
|
||||||
import storage from "utils/storage"
|
import storage from "utils/storage"
|
||||||
import { Profile, User } from "utils/types"
|
import type { Profile, User } from "utils/types"
|
||||||
import { getProfile } from "../api"
|
import { getProfile } from "../api"
|
||||||
import { useConfigStore } from "./config"
|
import { useConfigStore } from "./config"
|
||||||
|
|
||||||
|
|||||||
@@ -175,6 +175,7 @@ export const SOURCES = {
|
|||||||
JavaScript: "",
|
JavaScript: "",
|
||||||
Golang: "",
|
Golang: "",
|
||||||
Flowchart: "",
|
Flowchart: "",
|
||||||
|
SQL: "",
|
||||||
} as const
|
} as const
|
||||||
|
|
||||||
export const LANGUAGE_ID = {
|
export const LANGUAGE_ID = {
|
||||||
@@ -186,6 +187,7 @@ export const LANGUAGE_ID = {
|
|||||||
JavaScript: 0,
|
JavaScript: 0,
|
||||||
Golang: 0,
|
Golang: 0,
|
||||||
Flowchart: 0,
|
Flowchart: 0,
|
||||||
|
SQL: 0,
|
||||||
} as const
|
} as const
|
||||||
|
|
||||||
export const LANGUAGE_FORMAT_VALUE = {
|
export const LANGUAGE_FORMAT_VALUE = {
|
||||||
@@ -197,6 +199,7 @@ export const LANGUAGE_FORMAT_VALUE = {
|
|||||||
JavaScript: "javascript",
|
JavaScript: "javascript",
|
||||||
Golang: "go",
|
Golang: "go",
|
||||||
Flowchart: "flowchart",
|
Flowchart: "flowchart",
|
||||||
|
SQL: "sql",
|
||||||
} as const
|
} as const
|
||||||
|
|
||||||
export const LANGUAGE_SHOW_VALUE = {
|
export const LANGUAGE_SHOW_VALUE = {
|
||||||
@@ -208,6 +211,7 @@ export const LANGUAGE_SHOW_VALUE = {
|
|||||||
Python3: "Python",
|
Python3: "Python",
|
||||||
JavaScript: "JS",
|
JavaScript: "JS",
|
||||||
Golang: "Go",
|
Golang: "Go",
|
||||||
|
SQL: "SQL",
|
||||||
} as const
|
} as const
|
||||||
|
|
||||||
export const ICON_SET = {
|
export const ICON_SET = {
|
||||||
@@ -219,6 +223,7 @@ export const ICON_SET = {
|
|||||||
Java: "devicon:java",
|
Java: "devicon:java",
|
||||||
JavaScript: "devicon:javascript",
|
JavaScript: "devicon:javascript",
|
||||||
Golang: "devicon:go",
|
Golang: "devicon:go",
|
||||||
|
SQL: "devicon:sqlite",
|
||||||
} as const
|
} as const
|
||||||
|
|
||||||
const cTemplate = `//TEMPLATE BEGIN
|
const cTemplate = `//TEMPLATE BEGIN
|
||||||
@@ -256,6 +261,7 @@ export const CODE_TEMPLATES = {
|
|||||||
JavaScript: blankTemplate,
|
JavaScript: blankTemplate,
|
||||||
Golang: blankTemplate,
|
Golang: blankTemplate,
|
||||||
Flowchart: blankTemplate,
|
Flowchart: blankTemplate,
|
||||||
|
SQL: blankTemplate,
|
||||||
} as const
|
} as const
|
||||||
|
|
||||||
export enum ScreenMode {
|
export enum ScreenMode {
|
||||||
|
|||||||
@@ -65,6 +65,12 @@ export type LANGUAGE =
|
|||||||
| "JavaScript"
|
| "JavaScript"
|
||||||
| "Golang"
|
| "Golang"
|
||||||
| "Flowchart"
|
| "Flowchart"
|
||||||
|
| "SQL"
|
||||||
|
|
||||||
|
export interface SQLConfig {
|
||||||
|
mode: "query" | "modify"
|
||||||
|
order_sensitive: boolean
|
||||||
|
}
|
||||||
|
|
||||||
export type LANGUAGE_SHOW_LABEL =
|
export type LANGUAGE_SHOW_LABEL =
|
||||||
(typeof LANGUAGE_SHOW_VALUE)[keyof typeof LANGUAGE_SHOW_VALUE]
|
(typeof LANGUAGE_SHOW_VALUE)[keyof typeof LANGUAGE_SHOW_VALUE]
|
||||||
@@ -164,6 +170,9 @@ export interface Problem {
|
|||||||
}[]
|
}[]
|
||||||
} | null
|
} | null
|
||||||
has_ast_rules?: boolean
|
has_ast_rules?: boolean
|
||||||
|
|
||||||
|
// SQL 题配置(非 SQL 题为 null)
|
||||||
|
sql_config?: SQLConfig | null
|
||||||
}
|
}
|
||||||
|
|
||||||
export type AdminProblem = Problem & AlterProblem
|
export type AdminProblem = Problem & AlterProblem
|
||||||
|
|||||||
@@ -2,6 +2,9 @@
|
|||||||
"extends": "@vue/tsconfig/tsconfig.dom.json",
|
"extends": "@vue/tsconfig/tsconfig.dom.json",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
||||||
|
// Vite(oxc) 遵守 verbatimModuleSyntax,会保留未写 type 的纯类型导入导致构建报错;
|
||||||
|
// 关掉以让转换器自动消除类型导入(与原 Rsbuild 行为一致)
|
||||||
|
"verbatimModuleSyntax": false,
|
||||||
"paths": {
|
"paths": {
|
||||||
"utils/*": ["./src/utils/*"],
|
"utils/*": ["./src/utils/*"],
|
||||||
"oj/*": ["./src/oj/*"],
|
"oj/*": ["./src/oj/*"],
|
||||||
|
|||||||
+1
-1
@@ -20,5 +20,5 @@
|
|||||||
"noFallthroughCasesInSwitch": true,
|
"noFallthroughCasesInSwitch": true,
|
||||||
"noUncheckedSideEffectImports": true
|
"noUncheckedSideEffectImports": true
|
||||||
},
|
},
|
||||||
"include": ["rsbuild.config.ts"]
|
"include": ["vite.config.ts"]
|
||||||
}
|
}
|
||||||
|
|||||||
+112
@@ -0,0 +1,112 @@
|
|||||||
|
import { fileURLToPath, URL } from "node:url"
|
||||||
|
import { defineConfig, loadEnv, type Plugin } from "vite"
|
||||||
|
import vue from "@vitejs/plugin-vue"
|
||||||
|
import legacy from "@vitejs/plugin-legacy"
|
||||||
|
import AutoImport from "unplugin-auto-import/vite"
|
||||||
|
import Components from "unplugin-vue-components/vite"
|
||||||
|
import { NaiveUiResolver } from "unplugin-vue-components/resolvers"
|
||||||
|
|
||||||
|
// index.html 里按需注入 MaxKB 脚本(原 Rsbuild EJS 模板的等价实现)
|
||||||
|
function injectMaxkb(maxkbUrl: string | undefined): Plugin {
|
||||||
|
return {
|
||||||
|
name: "inject-maxkb",
|
||||||
|
transformIndexHtml(html) {
|
||||||
|
if (!maxkbUrl) return html
|
||||||
|
return {
|
||||||
|
html,
|
||||||
|
tags: [
|
||||||
|
{
|
||||||
|
tag: "script",
|
||||||
|
attrs: { async: true, defer: true, src: maxkbUrl },
|
||||||
|
injectTo: "head",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default defineConfig(({ mode }) => {
|
||||||
|
const env = loadEnv(mode, process.cwd(), "PUBLIC_")
|
||||||
|
|
||||||
|
const proxyConfig = {
|
||||||
|
target: env["PUBLIC_OJ_URL"],
|
||||||
|
changeOrigin: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
const wsProxyConfig = {
|
||||||
|
target: env["PUBLIC_WS_URL"],
|
||||||
|
ws: true,
|
||||||
|
changeOrigin: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
plugins: [
|
||||||
|
vue(),
|
||||||
|
// 机房存在 Chrome 91 等旧浏览器:不做 SystemJS 双构建,
|
||||||
|
// 只按使用情况给现代产物注入 core-js API polyfill(Array.at 等)
|
||||||
|
legacy({
|
||||||
|
renderLegacyChunks: false,
|
||||||
|
modernTargets: "chrome>=90",
|
||||||
|
modernPolyfills: true,
|
||||||
|
}),
|
||||||
|
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",
|
||||||
|
}),
|
||||||
|
injectMaxkb(env["PUBLIC_MAXKB_URL"]),
|
||||||
|
],
|
||||||
|
envPrefix: "PUBLIC_",
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
// mermaid-legacy (mermaid@9) 写死了 UMD 路径,新版 cytoscape 的 exports
|
||||||
|
// 不允许 import 条件访问它,转到 ESM 产物
|
||||||
|
"cytoscape/dist/cytoscape.umd.js": "cytoscape/dist/cytoscape.esm.mjs",
|
||||||
|
utils: fileURLToPath(new URL("./src/utils", import.meta.url)),
|
||||||
|
oj: fileURLToPath(new URL("./src/oj", import.meta.url)),
|
||||||
|
admin: fileURLToPath(new URL("./src/admin", import.meta.url)),
|
||||||
|
shared: fileURLToPath(new URL("./src/shared", import.meta.url)),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
server: {
|
||||||
|
port: 5173,
|
||||||
|
proxy: {
|
||||||
|
"/api": proxyConfig,
|
||||||
|
"/public": proxyConfig,
|
||||||
|
"/ws": wsProxyConfig,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user