设置的实时变动
Some checks failed
Deploy / deploy (push) Has been cancelled

This commit is contained in:
2025-10-11 14:07:16 +08:00
parent b65bb3d53c
commit 76f8a03177
9 changed files with 118 additions and 105 deletions

2
.env
View File

@@ -4,4 +4,4 @@ PUBLIC_OJ_URL=http://localhost:8000
PUBLIC_CODE_URL=http://localhost:3000 PUBLIC_CODE_URL=http://localhost:3000
PUBLIC_JUDGE0_URL=https://judge0api.xuyue.cc PUBLIC_JUDGE0_URL=https://judge0api.xuyue.cc
PUBLIC_SIGNALING_URL=ws://10.13.114.114:8085 PUBLIC_SIGNALING_URL=ws://10.13.114.114:8085
PUBLIC_WS_URL=ws://10.13.114.114:81/ws PUBLIC_WS_URL=ws://localhost:8001/ws

View File

@@ -132,8 +132,11 @@ async function saveWebsiteConfig() {
configStore.getConfig() configStore.getConfig()
// 通过 WebSocket 广播配置变化,实现实时切换 // 通过 WebSocket 广播配置变化,实现实时切换
updateConfig('enable_maxkb', websiteConfig.enable_maxkb) updateConfig("enable_maxkb", websiteConfig.enable_maxkb)
updateConfig('submission_list_show_all', websiteConfig.submission_list_show_all) updateConfig(
"submission_list_show_all",
websiteConfig.submission_list_show_all,
)
} }
async function deleteTestcase(id?: string) { async function deleteTestcase(id?: string) {

View File

@@ -1,26 +1,28 @@
<template> <template>
<div align="center" style="display: inline-flex; margin: 0 10px;"> <div align="center" style="display: inline-flex; margin: 0 10px">
<img src="/S.png" alt="S Grade" v-if="props.grade === 'S'" /> <img src="/S.png" alt="S Grade" v-if="props.grade === 'S'" />
<img src="/A.png" alt="A Grade" v-if="props.grade === 'A'" /> <img src="/A.png" alt="A Grade" v-if="props.grade === 'A'" />
<img src="/B.png" alt="B Grade" v-if="props.grade === 'B'" /> <img src="/B.png" alt="B Grade" v-if="props.grade === 'B'" />
<img src="/C.png" alt="C Grade" v-if="props.grade === 'C'" /> <img src="/C.png" alt="C Grade" v-if="props.grade === 'C'" />
<n-tooltip trigger="hover"> <n-tooltip trigger="hover">
<template #trigger> <template #trigger>
<n-icon size="16" style="cursor: help;"> <n-icon size="16" style="cursor: help">
<svg viewBox="0 0 24 24" fill="currentColor"> <svg viewBox="0 0 24 24" fill="currentColor">
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 17h-2v-2h2v2zm2.07-7.75l-.9.92C13.45 12.9 13 13.5 13 15h-2v-.5c0-1.1.45-2.1 1.17-2.83l1.24-1.26c.37-.36.59-.86.59-1.41 0-1.1-.9-2-2-2s-2 .9-2 2H8c0-2.21 1.79-4 4-4s4 1.79 4 4c0 .88-.36 1.68-.93 2.25z"/> <path
d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 17h-2v-2h2v2zm2.07-7.75l-.9.92C13.45 12.9 13 13.5 13 15h-2v-.5c0-1.1.45-2.1 1.17-2.83l1.24-1.26c.37-.36.59-.86.59-1.41 0-1.1-.9-2-2-2s-2 .9-2 2H8c0-2.21 1.79-4 4-4s4 1.79 4 4c0 .88-.36 1.68-.93 2.25z"
/>
</svg> </svg>
</n-icon> </n-icon>
</template> </template>
<div style="max-width: 300px; line-height: 1.4;"> <div style="max-width: 300px; line-height: 1.4">
<div style="font-weight: bold; margin-bottom: 8px;">等级计算说明</div> <div style="font-weight: bold; margin-bottom: 8px">等级计算说明</div>
<div>使用加权平均方法计算综合等级</div> <div>使用加权平均方法计算综合等级</div>
<div> S级 = 4A级 = 3B级 = 2C级 = 1</div> <div> S级 = 4A级 = 3B级 = 2C级 = 1</div>
<div> 根据平均分数确定最终等级</div> <div> 根据平均分数确定最终等级</div>
<div> - S级3.5</div> <div>- S级3.5</div>
<div> - A级2.5-3.5</div> <div>- A级2.5-3.5</div>
<div> - B级1.5-2.5</div> <div>- B级1.5-2.5</div>
<div> - C级<1.5分</div> <div>- C级<1.5分</div>
</div> </div>
</n-tooltip> </n-tooltip>
</div> </div>

View File

@@ -108,11 +108,7 @@ watch(isMobile, (value) => {
</script> </script>
<template> <template>
<n-grid <n-grid v-if="problem" x-gap="16" :cols="screenModeStore.isBothMode ? 2 : 1">
v-if="problem"
x-gap="16"
:cols="screenModeStore.isBothMode ? 2 : 1"
>
<n-gi :span="isDesktop ? 1 : 2" v-if="shouldShowProblem"> <n-gi :span="isDesktop ? 1 : 2" v-if="shouldShowProblem">
<n-scrollbar v-if="isDesktop" style="max-height: calc(100vh - 92px)"> <n-scrollbar v-if="isDesktop" style="max-height: calc(100vh - 92px)">
<n-tabs v-model:value="currentTab" type="segment"> <n-tabs v-model:value="currentTab" type="segment">

View File

@@ -1,44 +1,28 @@
import { useConfigStore } from "shared/store/config" import { useConfigStore } from "shared/store/config"
import { useConfigWebSocket, type ConfigUpdate } from "shared/composables/websocket" import {
useConfigWebSocket,
type ConfigUpdate,
} from "shared/composables/websocket"
export function useConfigUpdate() { export function useConfigUpdate() {
const configStore = useConfigStore() const configStore = useConfigStore()
// 处理 WebSocket 配置更新 // 处理 WebSocket 配置更新
const handleConfigUpdate = (data: ConfigUpdate) => { const handleConfigUpdate = (data: ConfigUpdate) => {
console.log("收到配置更新:", data)
// 更新全局配置 - 使用响应式方式 // 更新全局配置 - 使用响应式方式
if (data.key in configStore.config) { if (data.key in configStore.config) {
// 直接修改 ref 的值来触发响应式更新 // 直接修改 ref 的值来触发响应式更新
(configStore.config as any)[data.key] = data.value ;(configStore.config as any)[data.key] = data.value
console.log(`配置 ${data.key} 已更新为:`, data.value)
} }
} }
// 初始化 WebSocket - handler 会在 onMounted 时自动添加 // 初始化 WebSocket - handler 会在 onMounted 时自动添加
const { connect, status } = useConfigWebSocket(handleConfigUpdate) const { connect } = useConfigWebSocket(handleConfigUpdate)
// 连接 WebSocket
onMounted(() => { onMounted(() => {
console.log("配置更新 WebSocket 正在连接...")
connect() connect()
// 测试连接 - 发送心跳包
setTimeout(() => {
if (status.value === "connected") {
console.log("配置更新 WebSocket 连接成功,发送测试消息...")
// 这里可以发送一个测试消息
}
}, 2000)
}) })
// 监听连接状态
watch(status, (newStatus) => {
console.log("配置更新 WebSocket 状态:", newStatus)
})
return { return {
connect connect,
} }
} }

View File

@@ -1,5 +1,8 @@
import { useConfigStore } from "shared/store/config" import { useConfigStore } from "shared/store/config"
import { useConfigWebSocket, type ConfigUpdate } from "shared/composables/websocket" import {
useConfigWebSocket,
type ConfigUpdate,
} from "shared/composables/websocket"
export function useMaxKB() { export function useMaxKB() {
const configStore = useConfigStore() const configStore = useConfigStore()
@@ -7,8 +10,7 @@ export function useMaxKB() {
// 处理 WebSocket 配置更新 - 只处理 MaxKB 相关 // 处理 WebSocket 配置更新 - 只处理 MaxKB 相关
const handleConfigUpdate = (data: ConfigUpdate) => { const handleConfigUpdate = (data: ConfigUpdate) => {
console.log("MaxKB 收到配置更新:", data) if (data.key === "enable_maxkb") {
if (data.key === 'enable_maxkb') {
if (data.value) { if (data.value) {
loadMaxKBScript() loadMaxKBScript()
} else { } else {
@@ -27,7 +29,9 @@ export function useMaxKB() {
return return
} }
const existingScript = document.querySelector(`script[src="${import.meta.env.PUBLIC_MAXKB_URL}"]`) const existingScript = document.querySelector(
`script[src="${import.meta.env.PUBLIC_MAXKB_URL}"]`,
)
if (existingScript) { if (existingScript) {
isLoaded.value = true isLoaded.value = true
return return
@@ -41,7 +45,6 @@ export function useMaxKB() {
script.onload = () => { script.onload = () => {
isLoaded.value = true isLoaded.value = true
console.log("MaxKB script loaded successfully")
} }
script.onerror = () => { script.onerror = () => {
@@ -52,15 +55,41 @@ export function useMaxKB() {
} }
const removeMaxKBScript = () => { const removeMaxKBScript = () => {
// 把 script 也删除
const script = document.querySelector(
`script[src="${import.meta.env.PUBLIC_MAXKB_URL}"]`,
)
if (script) {
script.remove()
}
// 等待DOM加载完成后删除所有id以"maxkb-"开头的元素
const removeMaxKBElements = () => {
// 查找所有id以"maxkb-"开头的元素
const elements = document.querySelectorAll('[id^="maxkb-"]')
elements.forEach((element) => {
element.remove()
})
}
// 如果DOM已经加载完成直接执行删除
if (document.readyState === "complete") {
removeMaxKBElements()
} else {
// 等待DOM加载完成
window.addEventListener("load", removeMaxKBElements, { once: true })
}
// 移除MaxKB脚本标签
const existingScript = document.querySelector(
`script[src="${import.meta.env.PUBLIC_MAXKB_URL}"]`,
)
if (existingScript) {
existingScript.remove()
}
// 重置加载状态
isLoaded.value = false isLoaded.value = false
const maxkbChatButton = document.getElementById("maxkb-chat-button")
const maxkbChatContainer = document.getElementById("maxkb-chat-container")
if (maxkbChatButton) {
maxkbChatButton.remove()
}
if (maxkbChatContainer) {
maxkbChatContainer.remove()
}
} }
// 连接 WebSocket // 连接 WebSocket
@@ -70,20 +99,19 @@ export function useMaxKB() {
watch( watch(
() => configStore.config.enable_maxkb, () => configStore.config.enable_maxkb,
(newValue) => { (enabled) => {
if (newValue) { if (enabled) {
loadMaxKBScript() loadMaxKBScript()
} else { } else {
console.log("removeMaxKBScript")
removeMaxKBScript() removeMaxKBScript()
} }
}, },
{ immediate: true } { immediate: true },
) )
return { return {
loadMaxKBScript, loadMaxKBScript,
removeMaxKBScript, removeMaxKBScript,
isLoaded: readonly(isLoaded) isLoaded: readonly(isLoaded),
} }
} }

View File

@@ -42,7 +42,8 @@ export const SYNC_MESSAGES = {
SYNC_ON: "断开同步", SYNC_ON: "断开同步",
SYNC_OFF: "开启同步", SYNC_OFF: "开启同步",
SYNCING_WITH: (name: string) => `🔗 与 ${name} 同步中`, SYNCING_WITH: (name: string) => `🔗 与 ${name} 同步中`,
STUDENT_LEFT: (name?: string) => name ? `💡 ${name}已离开` : "💡 可以关闭同步", STUDENT_LEFT: (name?: string) =>
name ? `💡 ${name}已离开` : "💡 可以关闭同步",
} as const } as const
// 类型定义 // 类型定义

View File

@@ -313,7 +313,6 @@ class SubmissionWebSocket extends BaseWebSocket<SubmissionUpdate> {
* 订阅特定提交的更新 * 订阅特定提交的更新
*/ */
subscribe(submissionId: string) { subscribe(submissionId: string) {
console.log(`[WebSocket] 发送订阅请求: submission_id=${submissionId}`)
const success = this.send({ const success = this.send({
type: "subscribe", type: "subscribe",
submission_id: submissionId, submission_id: submissionId,