4
.env
4
.env
@@ -3,5 +3,5 @@ PUBLIC_MAXKB_URL=https://maxkb.xuyue.cc/chat/api/embed?protocol=https&host=maxkb
|
|||||||
PUBLIC_OJ_URL=http://localhost:8000
|
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=wss://signaling.xuyue.cc
|
PUBLIC_SIGNALING_URL=ws://10.13.114.114:8085
|
||||||
PUBLIC_WS_URL=wss://oj.xuyue.cc/ws
|
PUBLIC_WS_URL=ws://10.13.114.114:81/ws
|
||||||
17
src/App.vue
17
src/App.vue
@@ -2,7 +2,24 @@
|
|||||||
import { darkTheme, dateZhCN, zhCN } from "naive-ui"
|
import { darkTheme, dateZhCN, zhCN } from "naive-ui"
|
||||||
import "normalize.css"
|
import "normalize.css"
|
||||||
import "./index.css"
|
import "./index.css"
|
||||||
|
import { useConfigStore } from "shared/store/config"
|
||||||
|
import { useConfigUpdate } from "shared/composables/configUpdate"
|
||||||
|
import { useMaxKB } from "shared/composables/maxkb"
|
||||||
|
import { useUserStore } from "shared/store/user"
|
||||||
|
|
||||||
const isDark = useDark()
|
const isDark = useDark()
|
||||||
|
const configStore = useConfigStore()
|
||||||
|
const userStore = useUserStore()
|
||||||
|
|
||||||
|
// 初始化配置和实时更新
|
||||||
|
onMounted(() => {
|
||||||
|
configStore.getConfig()
|
||||||
|
userStore.getMyProfile()
|
||||||
|
})
|
||||||
|
|
||||||
|
// 使用配置更新和 MaxKB 功能
|
||||||
|
useConfigUpdate()
|
||||||
|
useMaxKB()
|
||||||
|
|
||||||
// 延迟加载 highlight.js,避免阻塞首屏
|
// 延迟加载 highlight.js,避免阻塞首屏
|
||||||
const hljsInstance = ref<any>(null)
|
const hljsInstance = ref<any>(null)
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
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 { Server } from "utils/types"
|
||||||
|
import { useConfigStore } from "shared/store/config"
|
||||||
|
import { useConfigWebSocket } from "shared/composables/websocket"
|
||||||
import {
|
import {
|
||||||
deleteJudgeServer,
|
deleteJudgeServer,
|
||||||
editWebsite,
|
editWebsite,
|
||||||
@@ -17,6 +19,8 @@ interface Testcase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const message = useMessage()
|
const message = useMessage()
|
||||||
|
const configStore = useConfigStore()
|
||||||
|
const { updateConfig } = useConfigWebSocket()
|
||||||
|
|
||||||
const testcaseColumns: DataTableColumn<Testcase>[] = [
|
const testcaseColumns: DataTableColumn<Testcase>[] = [
|
||||||
{ title: "测试用例 ID", key: "id" },
|
{ title: "测试用例 ID", key: "id" },
|
||||||
@@ -106,6 +110,7 @@ const websiteConfig = reactive({
|
|||||||
allow_register: true,
|
allow_register: true,
|
||||||
submission_list_show_all: true,
|
submission_list_show_all: true,
|
||||||
class_list: [],
|
class_list: [],
|
||||||
|
enable_maxkb: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
async function getWebsiteConfig() {
|
async function getWebsiteConfig() {
|
||||||
@@ -117,12 +122,18 @@ async function getWebsiteConfig() {
|
|||||||
websiteConfig.allow_register = res.data.allow_register
|
websiteConfig.allow_register = res.data.allow_register
|
||||||
websiteConfig.submission_list_show_all = res.data.submission_list_show_all
|
websiteConfig.submission_list_show_all = res.data.submission_list_show_all
|
||||||
websiteConfig.class_list = res.data.class_list
|
websiteConfig.class_list = res.data.class_list
|
||||||
|
websiteConfig.enable_maxkb = res.data.enable_maxkb
|
||||||
}
|
}
|
||||||
|
|
||||||
async function saveWebsiteConfig() {
|
async function saveWebsiteConfig() {
|
||||||
await editWebsite(websiteConfig)
|
await editWebsite(websiteConfig)
|
||||||
message.success("网站配置保存成功")
|
message.success("网站配置保存成功")
|
||||||
getWebsiteConfig()
|
getWebsiteConfig()
|
||||||
|
configStore.getConfig()
|
||||||
|
|
||||||
|
// 通过 WebSocket 广播配置变化,实现实时切换
|
||||||
|
updateConfig('enable_maxkb', websiteConfig.enable_maxkb)
|
||||||
|
updateConfig('submission_list_show_all', websiteConfig.submission_list_show_all)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function deleteTestcase(id?: string) {
|
async function deleteTestcase(id?: string) {
|
||||||
@@ -198,6 +209,10 @@ onMounted(() => {
|
|||||||
<span>显示所有提交</span>
|
<span>显示所有提交</span>
|
||||||
<n-switch v-model:value="websiteConfig.submission_list_show_all" />
|
<n-switch v-model:value="websiteConfig.submission_list_show_all" />
|
||||||
</n-flex>
|
</n-flex>
|
||||||
|
<n-flex align="center">
|
||||||
|
<span>启用右下角 AI 入口</span>
|
||||||
|
<n-switch v-model:value="websiteConfig.enable_maxkb" />
|
||||||
|
</n-flex>
|
||||||
</n-flex>
|
</n-flex>
|
||||||
</n-card>
|
</n-card>
|
||||||
<n-card class="box">
|
<n-card class="box">
|
||||||
|
|||||||
@@ -83,11 +83,6 @@ function renderIcon(icon: string) {
|
|||||||
return () => h(Icon, { icon, width: 24, height: 24 })
|
return () => h(Icon, { icon, width: 24, height: 24 })
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
userStore.getMyProfile()
|
|
||||||
configStore.getConfig()
|
|
||||||
})
|
|
||||||
|
|
||||||
const menus = computed<MenuOption[]>(() => [
|
const menus = computed<MenuOption[]>(() => [
|
||||||
{
|
{
|
||||||
label: () => h(RouterLink, { to: "/learn/01" }, { default: () => "自学" }),
|
label: () => h(RouterLink, { to: "/learn/01" }, { default: () => "自学" }),
|
||||||
|
|||||||
44
src/shared/composables/configUpdate.ts
Normal file
44
src/shared/composables/configUpdate.ts
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
import { useConfigStore } from "shared/store/config"
|
||||||
|
import { useConfigWebSocket, type ConfigUpdate } from "shared/composables/websocket"
|
||||||
|
|
||||||
|
export function useConfigUpdate() {
|
||||||
|
const configStore = useConfigStore()
|
||||||
|
|
||||||
|
// 处理 WebSocket 配置更新
|
||||||
|
const handleConfigUpdate = (data: ConfigUpdate) => {
|
||||||
|
console.log("收到配置更新:", data)
|
||||||
|
|
||||||
|
// 更新全局配置 - 使用响应式方式
|
||||||
|
if (data.key in configStore.config) {
|
||||||
|
// 直接修改 ref 的值来触发响应式更新
|
||||||
|
(configStore.config as any)[data.key] = data.value
|
||||||
|
console.log(`配置 ${data.key} 已更新为:`, data.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化 WebSocket - handler 会在 onMounted 时自动添加
|
||||||
|
const { connect, status } = useConfigWebSocket(handleConfigUpdate)
|
||||||
|
|
||||||
|
// 连接 WebSocket
|
||||||
|
onMounted(() => {
|
||||||
|
console.log("配置更新 WebSocket 正在连接...")
|
||||||
|
connect()
|
||||||
|
|
||||||
|
// 测试连接 - 发送心跳包
|
||||||
|
setTimeout(() => {
|
||||||
|
if (status.value === "connected") {
|
||||||
|
console.log("配置更新 WebSocket 连接成功,发送测试消息...")
|
||||||
|
// 这里可以发送一个测试消息
|
||||||
|
}
|
||||||
|
}, 2000)
|
||||||
|
})
|
||||||
|
|
||||||
|
// 监听连接状态
|
||||||
|
watch(status, (newStatus) => {
|
||||||
|
console.log("配置更新 WebSocket 状态:", newStatus)
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
connect
|
||||||
|
}
|
||||||
|
}
|
||||||
89
src/shared/composables/maxkb.ts
Normal file
89
src/shared/composables/maxkb.ts
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
import { useConfigStore } from "shared/store/config"
|
||||||
|
import { useConfigWebSocket, type ConfigUpdate } from "shared/composables/websocket"
|
||||||
|
|
||||||
|
export function useMaxKB() {
|
||||||
|
const configStore = useConfigStore()
|
||||||
|
const isLoaded = ref(false)
|
||||||
|
|
||||||
|
// 处理 WebSocket 配置更新 - 只处理 MaxKB 相关
|
||||||
|
const handleConfigUpdate = (data: ConfigUpdate) => {
|
||||||
|
console.log("MaxKB 收到配置更新:", data)
|
||||||
|
if (data.key === 'enable_maxkb') {
|
||||||
|
if (data.value) {
|
||||||
|
loadMaxKBScript()
|
||||||
|
} else {
|
||||||
|
removeMaxKBScript()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化 WebSocket
|
||||||
|
const { connect } = useConfigWebSocket(handleConfigUpdate)
|
||||||
|
|
||||||
|
const loadMaxKBScript = () => {
|
||||||
|
const { enable_maxkb } = configStore.config
|
||||||
|
|
||||||
|
if (!enable_maxkb) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const existingScript = document.querySelector(`script[src="${import.meta.env.PUBLIC_MAXKB_URL}"]`)
|
||||||
|
if (existingScript) {
|
||||||
|
isLoaded.value = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建并插入脚本标签
|
||||||
|
const script = document.createElement("script")
|
||||||
|
script.src = import.meta.env.PUBLIC_MAXKB_URL
|
||||||
|
script.async = true
|
||||||
|
script.defer = true
|
||||||
|
|
||||||
|
script.onload = () => {
|
||||||
|
isLoaded.value = true
|
||||||
|
console.log("MaxKB script loaded successfully")
|
||||||
|
}
|
||||||
|
|
||||||
|
script.onerror = () => {
|
||||||
|
console.error("Failed to load MaxKB script")
|
||||||
|
}
|
||||||
|
|
||||||
|
document.head.appendChild(script)
|
||||||
|
}
|
||||||
|
|
||||||
|
const removeMaxKBScript = () => {
|
||||||
|
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
|
||||||
|
onMounted(() => {
|
||||||
|
connect()
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => configStore.config.enable_maxkb,
|
||||||
|
(newValue) => {
|
||||||
|
if (newValue) {
|
||||||
|
loadMaxKBScript()
|
||||||
|
} else {
|
||||||
|
console.log("removeMaxKBScript")
|
||||||
|
removeMaxKBScript()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ immediate: true }
|
||||||
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
loadMaxKBScript,
|
||||||
|
removeMaxKBScript,
|
||||||
|
isLoaded: readonly(isLoaded)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -410,3 +410,63 @@ export function createWebSocketComposable<T extends WebSocketMessage>(
|
|||||||
removeHandler: (h: MessageHandler<T>) => ws.removeHandler(h),
|
removeHandler: (h: MessageHandler<T>) => ws.removeHandler(h),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 配置更新消息类型
|
||||||
|
*/
|
||||||
|
export interface ConfigUpdate extends WebSocketMessage {
|
||||||
|
type: "config_update"
|
||||||
|
key: string
|
||||||
|
value: any
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 配置 WebSocket 连接管理类
|
||||||
|
*/
|
||||||
|
class ConfigWebSocket extends BaseWebSocket<ConfigUpdate> {
|
||||||
|
constructor() {
|
||||||
|
super({
|
||||||
|
path: "config",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送配置更新
|
||||||
|
*/
|
||||||
|
updateConfig(key: string, value: any) {
|
||||||
|
this.send({
|
||||||
|
type: "config_update",
|
||||||
|
key,
|
||||||
|
value,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用于组件中使用配置 WebSocket 的 Composable
|
||||||
|
*/
|
||||||
|
export function useConfigWebSocket(handler?: MessageHandler<ConfigUpdate>) {
|
||||||
|
const ws = new ConfigWebSocket()
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
if (handler) {
|
||||||
|
ws.addHandler(handler)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
if (handler) {
|
||||||
|
ws.removeHandler(handler)
|
||||||
|
}
|
||||||
|
ws.disconnect()
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
connect: () => ws.connect(),
|
||||||
|
disconnect: () => ws.disconnect(),
|
||||||
|
updateConfig: (key: string, value: any) => ws.updateConfig(key, value),
|
||||||
|
status: ws.status,
|
||||||
|
addHandler: (h: MessageHandler<ConfigUpdate>) => ws.addHandler(h),
|
||||||
|
removeHandler: (h: MessageHandler<ConfigUpdate>) => ws.removeHandler(h),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ export const useConfigStore = defineStore("config", () => {
|
|||||||
submission_list_show_all: true,
|
submission_list_show_all: true,
|
||||||
allow_register: false,
|
allow_register: false,
|
||||||
class_list: [],
|
class_list: [],
|
||||||
|
enable_maxkb: true,
|
||||||
})
|
})
|
||||||
async function getConfig() {
|
async function getConfig() {
|
||||||
const res = await getWebsiteConfig()
|
const res = await getWebsiteConfig()
|
||||||
|
|||||||
@@ -340,6 +340,7 @@ export interface WebsiteConfig {
|
|||||||
allow_register: boolean
|
allow_register: boolean
|
||||||
submission_list_show_all: boolean
|
submission_list_show_all: boolean
|
||||||
class_list: string[] & never[]
|
class_list: string[] & never[]
|
||||||
|
enable_maxkb: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Server {
|
export interface Server {
|
||||||
|
|||||||
Reference in New Issue
Block a user