From 04c6c49eaa246a3ba6dab5d47500d5a32d362bd9 Mon Sep 17 00:00:00 2001
From: yuetsh <517252939@qq.com>
Date: Sun, 5 Oct 2025 09:54:42 +0800
Subject: [PATCH] fix
---
src/oj/problem/components/ContestEditor.vue | 57 ++++++
src/oj/problem/components/Form.vue | 162 ++++++++-------
.../{Editor.vue => ProblemEditor.vue} | 188 +++++++++---------
src/oj/problem/detail.vue | 11 +-
src/shared/components/CodeEditor.vue | 81 +-------
src/shared/components/SyncCodeEditor.vue | 138 +++++++++++++
src/shared/composables/sync.ts | 29 +--
7 files changed, 394 insertions(+), 272 deletions(-)
create mode 100644 src/oj/problem/components/ContestEditor.vue
rename src/oj/problem/components/{Editor.vue => ProblemEditor.vue} (91%)
create mode 100644 src/shared/components/SyncCodeEditor.vue
diff --git a/src/oj/problem/components/ContestEditor.vue b/src/oj/problem/components/ContestEditor.vue
new file mode 100644
index 0000000..f48360f
--- /dev/null
+++ b/src/oj/problem/components/ContestEditor.vue
@@ -0,0 +1,57 @@
+
+
+
+
+
+
+
+
diff --git a/src/oj/problem/components/Form.vue b/src/oj/problem/components/Form.vue
index b850beb..f16aed8 100644
--- a/src/oj/problem/components/Form.vue
+++ b/src/oj/problem/components/Form.vue
@@ -16,13 +16,13 @@ interface Props {
storageKey: string
withTest?: boolean
otherUserInfo?: { name: string; isSuperAdmin: boolean }
- isSynced?: boolean
+ isConnected?: boolean // WebSocket 实际的连接状态(已建立/未建立)
hadConnection?: boolean
}
const props = withDefaults(defineProps(), {
withTest: false,
- isSynced: false,
+ isConnected: false,
hadConnection: false,
})
@@ -36,34 +36,40 @@ const route = useRoute()
const router = useRouter()
const userStore = useUserStore()
-const isSynced = ref(false)
+const syncEnabled = ref(false) // 用户点击按钮后的意图状态(想要开启/关闭)
const statisticPanel = ref(false)
+// 计算属性
+const isContestMode = computed(() => route.name === "contest problem")
+const buttonSize = computed(() => (isDesktop.value ? "medium" : "small"))
+const showSyncFeature = computed(
+ () => isDesktop.value && userStore.isAuthed && !isContestMode.value,
+)
+
const menu = computed(() => [
- { label: "去自测猫", key: "test", show: isMobile.value },
+ { label: "去自测猫", key: "goTestCat", show: isMobile.value },
{ label: "复制代码", key: "copy" },
{ label: "重置代码", key: "reset" },
])
-const languageOptions: DropdownOption[] = problem.value!.languages.map((it) => ({
- label: () =>
- h("div", { style: "display: flex; align-items: center;" }, [
- h("img", {
- src: `/${it}.svg`,
- style: { width: "16px", height: "16px", marginRight: "8px" },
- }),
- LANGUAGE_SHOW_VALUE[it],
- ]),
- value: it,
-}))
+const languageOptions: DropdownOption[] = problem.value!.languages.map(
+ (it) => ({
+ label: () =>
+ h("div", { style: "display: flex; align-items: center;" }, [
+ h("img", {
+ src: `/${it}.svg`,
+ style: { width: "16px", height: "16px", marginRight: "8px" },
+ }),
+ LANGUAGE_SHOW_VALUE[it],
+ ]),
+ value: it,
+ }),
+)
+// 代码操作相关
const copy = async () => {
const success = await copyToClipboard(code.value)
- if (success) {
- message.success("代码复制成功")
- } else {
- message.error("代码复制失败")
- }
+ message[success ? "success" : "error"](`代码复制${success ? "成功" : "失败"}`)
}
const reset = () => {
@@ -72,123 +78,129 @@ const reset = () => {
message.success("代码重置成功")
}
+const changeLanguage = (v: LANGUAGE) => {
+ storage.set(STORAGE_KEY.LANGUAGE, v)
+ emit("changeLanguage", v)
+}
+
+const runCode = async () => {
+ const res = await createTestSubmission(code, input.value)
+ output.value = res.output
+}
+
+// 导航相关
+const goTestCat = () => {
+ window.open(import.meta.env.PUBLIC_CODE_URL, "_blank")
+}
+
const goSubmissions = () => {
const name = route.params.contestID ? "contest submissions" : "submissions"
router.push({ name, query: { problem: problem.value!._id } })
}
const goEdit = () => {
- const baseUrl = "/admin/problem/edit/" + problem.value!.id
const url = problem.value!.contest
? `/admin/contest/${problem.value!.contest}/problem/edit/${problem.value!.id}`
- : baseUrl
+ : `/admin/problem/edit/${problem.value!.id}`
window.open(router.resolve(url).href, "_blank")
}
-const test = async () => {
- const res = await createTestSubmission(code, input.value)
- output.value = res.output
-}
-
+// 菜单处理
const handleMenuSelect = (key: string) => {
const actions: Record void> = {
reset,
copy,
- test: () => window.open(import.meta.env.PUBLIC_CODE_URL, "_blank"),
+ goTestCat,
}
actions[key]?.()
}
-const changeLanguage = (v: LANGUAGE) => {
- storage.set(STORAGE_KEY.LANGUAGE, v)
- emit("changeLanguage", v)
-}
-
+// 协同编辑相关
const toggleSync = () => {
- isSynced.value = !isSynced.value
- emit("toggleSync", isSynced.value)
-}
-
-const gotoTestCat = () => {
- window.open(import.meta.env.PUBLIC_CODE_URL, "_blank")
+ syncEnabled.value = !syncEnabled.value
+ emit("toggleSync", syncEnabled.value)
}
defineExpose({
resetSyncStatus: () => {
- isSynced.value = false
+ syncEnabled.value = false
},
})
+
-
+
重置代码
- 运行代码
+ 运行代码
-
+
提交信息
-
+
{{ isDesktop ? "统计信息" : "统计" }}
-
- 自测猫
-
+
+ 自测猫
+
- 操作
+ 操作
-
+
-
-
-
-
-
- 与 {{ otherUserInfo.name }} 同步中
-
-
- 学生已退出,可以关闭同步
-
+
+
+
+
+
+
+
+
+ 与 {{ otherUserInfo.name }} 同步中
+
+
+ 学生已退出,可以关闭同步
+
+
diff --git a/src/oj/problem/components/Editor.vue b/src/oj/problem/components/ProblemEditor.vue
similarity index 91%
rename from src/oj/problem/components/Editor.vue
rename to src/oj/problem/components/ProblemEditor.vue
index abd6d6e..c23a36b 100644
--- a/src/oj/problem/components/Editor.vue
+++ b/src/oj/problem/components/ProblemEditor.vue
@@ -1,94 +1,94 @@
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
diff --git a/src/oj/problem/detail.vue b/src/oj/problem/detail.vue
index 184b07e..e4144e7 100644
--- a/src/oj/problem/detail.vue
+++ b/src/oj/problem/detail.vue
@@ -9,7 +9,8 @@ import {
} from "~/shared/composables/switchScreen"
import { problem } from "../composables/problem"
-const Editor = defineAsyncComponent(() => import("./components/Editor.vue"))
+const ProblemEditor = defineAsyncComponent(() => import("./components/ProblemEditor.vue"))
+const ContestEditor = defineAsyncComponent(() => import("./components/ContestEditor.vue"))
const EditorWithTest = defineAsyncComponent(
() => import("./components/EditorWithTest.vue"),
)
@@ -54,6 +55,8 @@ const tabOptions = computed(() => {
const currentTab = ref("content")
+const shouldUseProblemEditor = computed(() => route.name === "problem")
+
watch(
[() => route.query.tab, () => tabOptions.value],
([rawTab]) => {
@@ -125,7 +128,8 @@ watch(isMobile, (value) => {
-
+
+
@@ -139,7 +143,8 @@ watch(isMobile, (value) => {
-
+
+
diff --git a/src/shared/components/CodeEditor.vue b/src/shared/components/CodeEditor.vue
index 8c397f8..41bf259 100644
--- a/src/shared/components/CodeEditor.vue
+++ b/src/shared/components/CodeEditor.vue
@@ -7,18 +7,8 @@ import type { Extension } from "@codemirror/state"
import { LANGUAGE } from "~/utils/types"
import { oneDark } from "../themes/oneDark"
import { smoothy } from "../themes/smoothy"
-import { useCodeSync } from "../composables/sync"
-import { isDesktop } from "../composables/breakpoints"
-
-interface EditorReadyPayload {
- view: EditorView
- state: any
- container: HTMLElement
-}
interface Props {
- sync?: boolean
- problem?: string
language?: LANGUAGE
fontSize?: number
height?: string
@@ -27,8 +17,6 @@ interface Props {
}
const props = withDefaults(defineProps(), {
- sync: false,
- problem: "",
language: "Python3",
fontSize: 20,
height: "100%",
@@ -39,13 +27,6 @@ const props = withDefaults(defineProps(), {
const { readonly, placeholder, height, fontSize } = toRefs(props)
const code = defineModel("value")
-const emit = defineEmits<{
- syncClosed: []
- syncStatusChange: [
- status: { otherUser?: { name: string; isSuperAdmin: boolean } },
- ]
-}>()
-
const isDark = useDark()
const styleTheme = EditorView.baseTheme({
@@ -64,66 +45,7 @@ const extensions = computed(() => [
styleTheme,
lang.value,
isDark.value ? oneDark : smoothy,
- getInitialExtension(),
])
-
-const { startSync, stopSync, getInitialExtension } = useCodeSync()
-const editorView = ref(null)
-let cleanupSync: (() => void) | null = null
-
-const cleanupSyncResources = () => {
- if (cleanupSync) {
- cleanupSync()
- cleanupSync = null
- }
- stopSync()
-}
-
-const initSync = async () => {
- if (!editorView.value || !props.problem || !isDesktop.value) return
-
- cleanupSyncResources()
-
- cleanupSync = await startSync({
- problemId: props.problem,
- editorView: editorView.value as EditorView,
- onStatusChange: (status) => {
- if (status.error === "超管已离开" && !status.connected) {
- emit("syncClosed")
- }
- emit("syncStatusChange", { otherUser: status.otherUser })
- },
- })
-}
-
-const handleEditorReady = (payload: EditorReadyPayload) => {
- editorView.value = payload.view as EditorView
- if (props.sync) {
- initSync()
- }
-}
-
-watch(
- () => props.sync,
- (shouldSync) => {
- if (shouldSync) {
- initSync()
- } else {
- cleanupSyncResources()
- }
- },
-)
-
-watch(
- () => props.problem,
- (newProblem, oldProblem) => {
- if (newProblem !== oldProblem && props.sync) {
- initSync()
- }
- },
-)
-
-onUnmounted(cleanupSyncResources)
@@ -135,6 +57,5 @@ onUnmounted(cleanupSyncResources)
:tab-size="4"
:placeholder="placeholder"
:style="{ height, fontSize: `${fontSize}px` }"
- @ready="handleEditorReady"
/>
-
+
\ No newline at end of file
diff --git a/src/shared/components/SyncCodeEditor.vue b/src/shared/components/SyncCodeEditor.vue
new file mode 100644
index 0000000..8f89948
--- /dev/null
+++ b/src/shared/components/SyncCodeEditor.vue
@@ -0,0 +1,138 @@
+
+
+
+
+
diff --git a/src/shared/composables/sync.ts b/src/shared/composables/sync.ts
index d514abc..1d23969 100644
--- a/src/shared/composables/sync.ts
+++ b/src/shared/composables/sync.ts
@@ -121,7 +121,7 @@ export function useCodeSync() {
},
onStatusChange,
)
- message.warning(`超管 ${superAdminInfo.name} 已退出`)
+ message.warning(`🎈 超管 ${superAdminInfo.name} 溜了,协同编辑已断开连接`)
stopSync()
}
}
@@ -150,7 +150,7 @@ export function useCodeSync() {
onStatusChange,
)
if (lastSyncState !== "error") {
- message.warning("协同编辑需要至少一个超级管理员")
+ message.warning("🎓 协同编辑需要一位超级管理员坐镇哦")
lastSyncState = "error"
}
} else if (canSync) {
@@ -159,13 +159,13 @@ export function useCodeSync() {
connected: true,
roomUsers,
canSync: true,
- message: "协同编辑已激活,可以开始协作!",
+ message: "🎉 协同编辑已激活,开始愉快的代码之旅吧!",
otherUser,
},
onStatusChange,
)
if (lastSyncState !== "active") {
- message.success("协同编辑已激活,可以开始协作!")
+ message.success("🎉 协同编辑已激活,开始愉快的代码之旅吧!")
lastSyncState = "active"
}
} else {
@@ -174,7 +174,7 @@ export function useCodeSync() {
connected: true,
roomUsers,
canSync: false,
- message: roomUsers === 1 ? "正在等待加入" : "等待超级管理员加入...",
+ message: roomUsers === 1 ? "👋 正在等待小伙伴加入..." : "🎓 等待超级管理员加入...",
otherUser,
},
onStatusChange,
@@ -213,17 +213,6 @@ export function useCodeSync() {
const { problemId, editorView, onStatusChange } = options
if (!userStore.isAuthed) {
- updateStatus(
- {
- connected: false,
- roomUsers: 0,
- canSync: false,
- message: "请先登录后再使用同步功能",
- error: "用户未登录",
- },
- onStatusChange,
- )
- message.error("请先登录后再使用同步功能")
return () => {}
}
@@ -260,7 +249,7 @@ export function useCodeSync() {
},
onStatusChange,
)
- message.warning("协同编辑连接已断开")
+ message.warning("📡 协同编辑连接断开了,可能网络不太稳定呢")
}
})
@@ -280,7 +269,7 @@ export function useCodeSync() {
onStatusChange,
)
message.warning(
- `房间人数已满(最多${SYNC_CONSTANTS.MAX_ROOM_USERS}人),已自动断开连接`,
+ `🚪 哎呀,房间已经坐满了(最多${SYNC_CONSTANTS.MAX_ROOM_USERS}人),已自动断开连接`,
)
stopSync()
return
@@ -349,13 +338,13 @@ export function useCodeSync() {
connected: true,
roomUsers: 1,
canSync: false,
- message: "正在等待加入",
+ message: "🚀 协同编辑已准备就绪,等待伙伴加入...",
},
onStatusChange,
)
message.info(
- userStore.isSuperAdmin ? "正在等待学生加入..." : "正在等待超管加入...",
+ userStore.isSuperAdmin ? "👀 正在等待学生加入房间..." : "🎯 正在等待超管加入房间...",
)
lastSyncState = "waiting"
}