From 41819b6d9bdcad24c86297d79e6dc3e68429d29d Mon Sep 17 00:00:00 2001 From: yuetsh <517252939@qq.com> Date: Mon, 13 Oct 2025 12:31:12 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8F=90=E4=BA=A4=E6=B5=81=E7=A8=8B=E5=9B=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/oj/api.ts | 9 +- .../problem/components/ProblemListTitle.vue | 17 + src/oj/problem/components/SubmitFlowchart.vue | 321 ++++++++++++++++-- src/oj/problem/list.vue | 2 + src/oj/store/problem.ts | 29 -- .../FlowchartEditor/useFlowOperations.ts | 104 ++++-- src/shared/composables/websocket.ts | 69 ++++ src/utils/types.ts | 1 + 8 files changed, 471 insertions(+), 81 deletions(-) create mode 100644 src/oj/problem/components/ProblemListTitle.vue diff --git a/src/oj/api.ts b/src/oj/api.ts index 9c7d5ff..347a685 100644 --- a/src/oj/api.ts +++ b/src/oj/api.ts @@ -19,6 +19,7 @@ function filterResult(result: Problem) { rate: getACRate(result.accepted_number, result.submission_number), status: "", author: result.created_by.username, + allow_flowchart: result.allow_flowchart, } if (result.my_status === null || result.my_status === undefined) { newResult.status = "not_test" @@ -270,7 +271,7 @@ export function getAIHeatmapData() { export function submitFlowchart(data: { problem_id: number mermaid_code: string - flowchart_data?: any + flowchart_data: any // 这个是压缩之后的,元数据太长了 }) { return http.post("flowchart/submission", data) } @@ -295,3 +296,9 @@ export function retryFlowchartSubmission(submissionId: string) { submission_id: submissionId, }) } + +export function getCurrentProblemFlowchartSubmission(problemId: number) { + return http.get("flowchart/submission/current", { + params: { problem_id: problemId }, + }) +} diff --git a/src/oj/problem/components/ProblemListTitle.vue b/src/oj/problem/components/ProblemListTitle.vue new file mode 100644 index 0000000..0051043 --- /dev/null +++ b/src/oj/problem/components/ProblemListTitle.vue @@ -0,0 +1,17 @@ + + diff --git a/src/oj/problem/components/SubmitFlowchart.vue b/src/oj/problem/components/SubmitFlowchart.vue index 6321c5c..106a205 100644 --- a/src/oj/problem/components/SubmitFlowchart.vue +++ b/src/oj/problem/components/SubmitFlowchart.vue @@ -1,12 +1,108 @@ diff --git a/src/oj/problem/list.vue b/src/oj/problem/list.vue index 53d4d6d..e5fa38a 100644 --- a/src/oj/problem/list.vue +++ b/src/oj/problem/list.vue @@ -14,6 +14,7 @@ import { useUserStore } from "shared/store/user" import { renderTableTitle } from "utils/renders" import ProblemStatus from "./components/ProblemStatus.vue" import AuthorSelect from "shared/components/AuthorSelect.vue" +import ProblemListTitle from "./components/ProblemListTitle.vue" interface Tag { id: number @@ -146,6 +147,7 @@ const baseColumns: DataTableColumn[] = [ title: renderTableTitle("题目", "streamline-emojis:watermelon-2"), key: "title", minWidth: 200, + render: (row) => h(ProblemListTitle, { problem: row }), }, { title: renderTableTitle("难度", "streamline-emojis:lady-beetle"), diff --git a/src/oj/store/problem.ts b/src/oj/store/problem.ts index 00a5633..f33cc78 100644 --- a/src/oj/store/problem.ts +++ b/src/oj/store/problem.ts @@ -17,40 +17,11 @@ export const useProblemStore = defineStore("problem", () => { return problem.value?.languages ?? [] }) - // ==================== 操作 ==================== - /** - * 设置当前题目 - */ - function setProblem(newProblem: Problem | null) { - problem.value = newProblem - } - - /** - * 清空当前题目 - */ - function clearProblem() { - problem.value = null - } - - /** - * 更新题目的部分字段 - */ - function updateProblem(updates: Partial) { - if (problem.value) { - problem.value = { ...problem.value, ...updates } - } - } - return { // 状态 problem, // 计算属性 languages, - - // 操作 - setProblem, - clearProblem, - updateProblem, } }) diff --git a/src/shared/components/FlowchartEditor/useFlowOperations.ts b/src/shared/components/FlowchartEditor/useFlowOperations.ts index 05c157a..d5e6a1a 100644 --- a/src/shared/components/FlowchartEditor/useFlowOperations.ts +++ b/src/shared/components/FlowchartEditor/useFlowOperations.ts @@ -1,10 +1,7 @@ import { nanoid } from "nanoid" -import type { Ref } from 'vue' -import type { Node, Edge } from '@vue-flow/core' +import type { Ref } from "vue" +import type { Node, Edge } from "@vue-flow/core" -/** - * 简化的流程操作 - */ export function useFlowOperations( nodes: Ref, edges: Ref, @@ -12,24 +9,77 @@ export function useFlowOperations( addEdges: (edges: Edge[]) => void, removeNodes: (nodeIds: string[]) => void, removeEdges: (edgeIds: string[]) => void, - saveState: (nodes: Node[], edges: Edge[]) => void + saveState: (nodes: Node[], edges: Edge[]) => void, ) { + // 根据节点类型和handle自动推断标签 + const getAutoLabel = ( + sourceNode: any, + targetNode: any, + sourceHandle: string, + targetHandle: string, + ) => { + const sourceType = sourceNode?.data?.originalType || sourceNode?.type + const targetType = targetNode?.data?.originalType || targetNode?.type + + // 如果是判断节点 + if (sourceType === "decision") { + // 根据handle ID推断标签 + if (sourceHandle === "yes") { + return "是" + } else if (sourceHandle === "no") { + return "否" + } + } + + // 如果是循环节点 + if (sourceType === "loop") { + // 根据handle ID推断标签 + if (sourceHandle === "continue") { + return "继续" + } else if (sourceHandle === "exit") { + return "退出" + } + } + + // 如果是循环体回到循环节点 + if (targetType === "loop") { + if (targetHandle === "return") { + return "返回" + } + } + // 默认情况 + return "" + } + // 连接处理 const handleConnect = (params: any) => { + // 获取源节点和目标节点 + const sourceNode = nodes.value.find((node) => node.id === params.source) + const targetNode = nodes.value.find((node) => node.id === params.target) + + // 自动推断标签 + const autoLabel = getAutoLabel( + sourceNode, + targetNode, + params.sourceHandle, + params.targetHandle, + ) + const newEdge: Edge = { id: `edge-${nanoid()}`, source: params.source, target: params.target, sourceHandle: params.sourceHandle, targetHandle: params.targetHandle, - type: 'default' + type: "default", + label: autoLabel, } - + addEdges([newEdge]) saveState(nodes.value, edges.value) } - // 边点击删除 + // 边点击处理 - 单击删除 const handleEdgeClick = (event: any) => { removeEdges([event.edge.id]) saveState(nodes.value, edges.value) @@ -38,39 +88,39 @@ export function useFlowOperations( // 节点删除 const handleNodeDelete = (nodeId: string) => { // 删除相关边 - const relatedEdges = edges.value.filter(edge => - edge.source === nodeId || edge.target === nodeId + const relatedEdges = edges.value.filter( + (edge) => edge.source === nodeId || edge.target === nodeId, ) if (relatedEdges.length > 0) { - removeEdges(relatedEdges.map(edge => edge.id)) + removeEdges(relatedEdges.map((edge) => edge.id)) } - + removeNodes([nodeId]) saveState(nodes.value, edges.value) } // 节点更新 const handleNodeUpdate = (nodeId: string, newLabel: string) => { - const nodeIndex = nodes.value.findIndex(node => node.id === nodeId) - + const nodeIndex = nodes.value.findIndex((node) => node.id === nodeId) + if (nodeIndex !== -1) { const oldNode = nodes.value[nodeIndex] - + // 创建新的节点对象以确保响应式更新 const updatedNode = { ...oldNode, data: { ...oldNode.data, - customLabel: newLabel - } + customLabel: newLabel, + }, } - + // 使用 Vue Flow 的更新方法 nodes.value[nodeIndex] = updatedNode - + // 强制触发响应式更新 nodes.value = [...nodes.value] - + saveState(nodes.value, edges.value) } } @@ -84,14 +134,14 @@ export function useFlowOperations( // 删除选中的节点和边 const deleteSelected = () => { - const selectedNodes = nodes.value.filter(node => (node as any).selected) - const selectedEdges = edges.value.filter(edge => (edge as any).selected) - + const selectedNodes = nodes.value.filter((node) => (node as any).selected) + const selectedEdges = edges.value.filter((edge) => (edge as any).selected) + if (selectedNodes.length > 0) { - removeNodes(selectedNodes.map(node => node.id)) + removeNodes(selectedNodes.map((node) => node.id)) } if (selectedEdges.length > 0) { - removeEdges(selectedEdges.map(edge => edge.id)) + removeEdges(selectedEdges.map((edge) => edge.id)) } saveState(nodes.value, edges.value) } @@ -102,6 +152,6 @@ export function useFlowOperations( handleNodeDelete, handleNodeUpdate, clearCanvas, - deleteSelected + deleteSelected, } } diff --git a/src/shared/composables/websocket.ts b/src/shared/composables/websocket.ts index e54317b..ae7444b 100644 --- a/src/shared/composables/websocket.ts +++ b/src/shared/composables/websocket.ts @@ -410,6 +410,75 @@ export function createWebSocketComposable( } } +/** + * 流程图评分更新消息类型 + */ +export interface FlowchartEvaluationUpdate extends WebSocketMessage { + type: "flowchart_evaluation_completed" | "flowchart_evaluation_failed" | "flowchart_evaluation_update" + submission_id: string + score?: number + grade?: string + feedback?: string + error?: string +} + +/** + * 流程图 WebSocket 连接管理类 + */ +class FlowchartWebSocket extends BaseWebSocket { + constructor() { + super({ + path: "flowchart", // 使用专门的 flowchart WebSocket 路径 + }) + } + + /** + * 订阅特定流程图提交的更新 + */ + subscribe(submissionId: string) { + const success = this.send({ + type: "subscribe", + submission_id: submissionId, + }) + if (!success) { + console.error("[Flowchart WebSocket] 订阅失败: 连接未就绪") + } + } +} + +/** + * 用于组件中使用流程图 WebSocket 的 Composable + */ +export function useFlowchartWebSocket( + handler?: MessageHandler, +) { + const ws = new FlowchartWebSocket() + + // 如果提供了处理器,添加到实例中 + if (handler) { + ws.addHandler(handler) + } + + // 组件卸载时清理资源 + onUnmounted(() => { + if (handler) { + ws.removeHandler(handler) + } + ws.disconnect() + }) + + return { + connect: () => ws.connect(), + disconnect: () => ws.disconnect(), + subscribe: (submissionId: string) => ws.subscribe(submissionId), + scheduleDisconnect: (delay?: number) => ws.scheduleDisconnect(delay), + cancelScheduledDisconnect: () => ws.cancelScheduledDisconnect(), + status: ws.status, + addHandler: (h: MessageHandler) => ws.addHandler(h), + removeHandler: (h: MessageHandler) => ws.removeHandler(h), + } +} + /** * 配置更新消息类型 */ diff --git a/src/utils/types.ts b/src/utils/types.ts index 03b35e1..9c88b47 100644 --- a/src/utils/types.ts +++ b/src/utils/types.ts @@ -170,6 +170,7 @@ export interface ProblemFiltered { rate: string status: "not_test" | "passed" | "failed" author: string + allow_flowchart: boolean } export interface AdminProblemFiltered {