@@ -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 },
|
||||
})
|
||||
}
|
||||
|
||||
17
src/oj/problem/components/ProblemListTitle.vue
Normal file
17
src/oj/problem/components/ProblemListTitle.vue
Normal file
@@ -0,0 +1,17 @@
|
||||
<script lang="ts" setup>
|
||||
import { ProblemFiltered } from "utils/types"
|
||||
import { Icon } from "@iconify/vue"
|
||||
|
||||
defineProps<{
|
||||
problem: ProblemFiltered
|
||||
}>()
|
||||
</script>
|
||||
<template>
|
||||
<n-flex align="center">
|
||||
<span>{{ problem.title }}</span>
|
||||
<Icon
|
||||
v-if="problem.allow_flowchart"
|
||||
icon="streamline-freehand-color:programming-flowchart"
|
||||
/>
|
||||
</n-flex>
|
||||
</template>
|
||||
@@ -1,12 +1,108 @@
|
||||
<script lang="ts" setup>
|
||||
import { useBreakpoints } from "shared/composables/breakpoints"
|
||||
import { submitFlowchart, getCurrentProblemFlowchartSubmission } from "oj/api"
|
||||
import { useProblemStore } from "oj/store/problem"
|
||||
import { utoa } from "utils/functions"
|
||||
import {
|
||||
useFlowchartWebSocket,
|
||||
type FlowchartEvaluationUpdate,
|
||||
} from "shared/composables/websocket"
|
||||
|
||||
const loading = ref(false)
|
||||
const { isDesktop } = useBreakpoints()
|
||||
const message = useMessage()
|
||||
const problemStore = useProblemStore()
|
||||
const { problem } = toRefs(problemStore)
|
||||
|
||||
// 通过inject获取FlowchartEditor组件的引用
|
||||
const flowchartEditorRef = inject<any>("flowchartEditorRef")
|
||||
|
||||
const evaluationResult = ref<{
|
||||
score?: number
|
||||
grade?: string
|
||||
feedback?: string
|
||||
suggestions?: string
|
||||
criteriaDetails?: any
|
||||
} | null>(null)
|
||||
const submissionStatus = ref<{
|
||||
status: string
|
||||
submission_id: string
|
||||
created_time?: string
|
||||
} | null>(null)
|
||||
|
||||
// 弹框状态
|
||||
const showDetailModal = ref(false)
|
||||
|
||||
// 提交次数
|
||||
const submissionCount = ref(0)
|
||||
|
||||
// 处理 WebSocket 消息
|
||||
const handleWebSocketMessage = (data: FlowchartEvaluationUpdate) => {
|
||||
console.log("收到流程图评分更新:", data)
|
||||
|
||||
if (data.type === "flowchart_evaluation_completed") {
|
||||
console.log("处理评分完成消息")
|
||||
loading.value = false
|
||||
evaluationResult.value = {
|
||||
score: data.score,
|
||||
grade: data.grade,
|
||||
feedback: data.feedback,
|
||||
}
|
||||
submissionStatus.value = null // 清除状态
|
||||
message.success(`流程图评分完成!得分: ${data.score}分 (${data.grade}级)`)
|
||||
} else if (data.type === "flowchart_evaluation_failed") {
|
||||
console.log("处理评分失败消息")
|
||||
loading.value = false
|
||||
submissionStatus.value = null // 清除状态
|
||||
message.error(`流程图评分失败: ${data.error}`)
|
||||
} else {
|
||||
console.log("未知的消息类型:", data.type)
|
||||
}
|
||||
}
|
||||
|
||||
// 创建 WebSocket 连接
|
||||
const { connect, disconnect, subscribe, status } = useFlowchartWebSocket(
|
||||
handleWebSocketMessage,
|
||||
)
|
||||
|
||||
// 监听WebSocket状态变化
|
||||
watch(
|
||||
status,
|
||||
(newStatus) => {
|
||||
console.log("WebSocket状态变化:", newStatus)
|
||||
},
|
||||
{ immediate: true },
|
||||
)
|
||||
|
||||
// 检查当前问题的流程图提交状态
|
||||
const checkCurrentSubmissionStatus = async () => {
|
||||
if (!problem.value?.id) return
|
||||
|
||||
const { data } = await getCurrentProblemFlowchartSubmission(problem.value.id)
|
||||
const submission = data.submission
|
||||
submissionCount.value = data.count
|
||||
if (submission && submission.status === 2) {
|
||||
evaluationResult.value = {
|
||||
score: submission.ai_score,
|
||||
grade: submission.ai_grade,
|
||||
feedback: submission.ai_feedback,
|
||||
suggestions: submission.ai_suggestions,
|
||||
criteriaDetails: submission.ai_criteria_details,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 组件挂载时连接 WebSocket 并检查状态
|
||||
onMounted(() => {
|
||||
connect()
|
||||
checkCurrentSubmissionStatus()
|
||||
})
|
||||
|
||||
// 组件卸载时断开连接
|
||||
onUnmounted(() => {
|
||||
disconnect()
|
||||
})
|
||||
|
||||
// 将流程图JSON数据转换为Mermaid格式
|
||||
const convertToMermaid = (flowchartData: any) => {
|
||||
const { nodes, edges } = flowchartData
|
||||
@@ -26,16 +122,18 @@ const convertToMermaid = (flowchartData: any) => {
|
||||
// 根据节点原始类型确定Mermaid语法
|
||||
switch (originalType) {
|
||||
case "start":
|
||||
mermaid += ` ${nodeId}[${label}]\n`
|
||||
mermaid += ` ${nodeId}((${label}))\n`
|
||||
break
|
||||
case "end":
|
||||
mermaid += ` ${nodeId}[${label}]\n`
|
||||
mermaid += ` ${nodeId}((${label}))\n`
|
||||
break
|
||||
case "input":
|
||||
mermaid += ` ${nodeId}[${label}]\n`
|
||||
// 输入框使用平行四边形
|
||||
mermaid += ` ${nodeId}[/${label}/]\n`
|
||||
break
|
||||
case "output":
|
||||
mermaid += ` ${nodeId}[${label}]\n`
|
||||
// 输出框使用平行四边形
|
||||
mermaid += ` ${nodeId}[/${label}/]\n`
|
||||
break
|
||||
case "default":
|
||||
mermaid += ` ${nodeId}[${label}]\n`
|
||||
@@ -44,7 +142,8 @@ const convertToMermaid = (flowchartData: any) => {
|
||||
mermaid += ` ${nodeId}{${label}}\n`
|
||||
break
|
||||
case "loop":
|
||||
mermaid += ` ${nodeId}[${label}]\n`
|
||||
// 循环使用菱形
|
||||
mermaid += ` ${nodeId}{${label}}\n`
|
||||
break
|
||||
default:
|
||||
mermaid += ` ${nodeId}[${label}]\n`
|
||||
@@ -55,19 +154,57 @@ const convertToMermaid = (flowchartData: any) => {
|
||||
edges.forEach((edge: any) => {
|
||||
const source = edge.source
|
||||
const target = edge.target
|
||||
const label = edge.label || edge.data?.label || ""
|
||||
const label = edge.label ?? ""
|
||||
|
||||
if (label) {
|
||||
if (label && label.trim() !== "") {
|
||||
mermaid += ` ${source} -->|${label}| ${target}\n`
|
||||
} else {
|
||||
mermaid += ` ${source} --> ${target}\n`
|
||||
}
|
||||
})
|
||||
|
||||
// 添加样式定义来区分不同类型的节点
|
||||
mermaid += "\n"
|
||||
mermaid +=
|
||||
" classDef startEnd fill:#e1f5fe,stroke:#01579b,stroke-width:2px\n"
|
||||
mermaid += " classDef input fill:#e3f2fd,stroke:#1976d2,stroke-width:2px\n"
|
||||
mermaid +=
|
||||
" classDef output fill:#f3e5f5,stroke:#7b1fa2,stroke-width:2px\n"
|
||||
mermaid +=
|
||||
" classDef process fill:#e8f5e8,stroke:#1b5e20,stroke-width:2px\n"
|
||||
mermaid +=
|
||||
" classDef decision fill:#fff3e0,stroke:#e65100,stroke-width:2px\n"
|
||||
mermaid += "\n"
|
||||
|
||||
// 为节点应用样式
|
||||
nodes.forEach((node: any) => {
|
||||
const nodeId = node.id
|
||||
const originalType = node.data?.originalType || node.type
|
||||
|
||||
switch (originalType) {
|
||||
case "start":
|
||||
case "end":
|
||||
mermaid += ` class ${nodeId} startEnd\n`
|
||||
break
|
||||
case "input":
|
||||
mermaid += ` class ${nodeId} input\n`
|
||||
break
|
||||
case "output":
|
||||
mermaid += ` class ${nodeId} output\n`
|
||||
break
|
||||
case "decision":
|
||||
case "loop":
|
||||
mermaid += ` class ${nodeId} decision\n`
|
||||
break
|
||||
default:
|
||||
mermaid += ` class ${nodeId} process\n`
|
||||
}
|
||||
})
|
||||
|
||||
return mermaid
|
||||
}
|
||||
|
||||
const submit = () => {
|
||||
async function submit() {
|
||||
if (!flowchartEditorRef?.value) return
|
||||
// 获取流程图的JSON数据
|
||||
const flowchartData = flowchartEditorRef.value.getFlowchartData()
|
||||
@@ -76,26 +213,162 @@ const submit = () => {
|
||||
message.error("流程图节点或边不能为空")
|
||||
return
|
||||
}
|
||||
|
||||
// 打印JSON数据到控制台
|
||||
console.log("流程图JSON数据:", JSON.stringify(flowchartData, null, 2))
|
||||
|
||||
// 转换为Mermaid格式
|
||||
const mermaidCode = convertToMermaid(flowchartData)
|
||||
console.log("Mermaid代码:")
|
||||
console.log(mermaidCode)
|
||||
const compressed = utoa(JSON.stringify(flowchartData))
|
||||
|
||||
// 显示成功消息
|
||||
message.success("敬请期待,快了~")
|
||||
loading.value = true
|
||||
evaluationResult.value = null // 清除之前的结果
|
||||
|
||||
try {
|
||||
const response = await submitFlowchart({
|
||||
problem_id: problem.value!.id,
|
||||
mermaid_code: mermaidCode,
|
||||
flowchart_data: {
|
||||
compressed: true,
|
||||
data: compressed,
|
||||
},
|
||||
})
|
||||
|
||||
// 获取提交ID并订阅更新
|
||||
const submissionId = response.data.submission_id
|
||||
|
||||
if (submissionId) {
|
||||
console.log("开始订阅WebSocket更新")
|
||||
subscribe(submissionId)
|
||||
|
||||
// 设置评分状态显示
|
||||
submissionStatus.value = {
|
||||
status: "processing",
|
||||
submission_id: submissionId,
|
||||
created_time: new Date().toISOString(),
|
||||
}
|
||||
}
|
||||
|
||||
message.success("流程图已提交,请耐心等待评分")
|
||||
} catch (error) {
|
||||
loading.value = false
|
||||
message.error("流程图提交失败")
|
||||
console.error("提交流程图失败:", error)
|
||||
}
|
||||
}
|
||||
|
||||
// 根据分数获取标签类型
|
||||
const getScoreType = (score: number) => {
|
||||
if (score >= 90) return "success"
|
||||
if (score >= 80) return "info"
|
||||
if (score >= 70) return "warning"
|
||||
return "error"
|
||||
}
|
||||
|
||||
// 根据等级获取标签类型
|
||||
const getGradeType = (grade: string) => {
|
||||
switch (grade) {
|
||||
case "S":
|
||||
return "success"
|
||||
case "A":
|
||||
return "info"
|
||||
case "B":
|
||||
return "warning"
|
||||
case "C":
|
||||
return "error"
|
||||
default:
|
||||
return "default"
|
||||
}
|
||||
}
|
||||
|
||||
// 格式化时间
|
||||
const formatTime = (timeString: string) => {
|
||||
const date = new Date(timeString)
|
||||
return date.toLocaleString("zh-CN", {
|
||||
year: "numeric",
|
||||
month: "2-digit",
|
||||
day: "2-digit",
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
second: "2-digit",
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<n-button
|
||||
:size="isDesktop ? 'medium' : 'small'"
|
||||
type="primary"
|
||||
@click="submit"
|
||||
>
|
||||
提交流程图
|
||||
</n-button>
|
||||
<n-flex align="center">
|
||||
<n-button
|
||||
:size="isDesktop ? 'medium' : 'small'"
|
||||
type="primary"
|
||||
:loading="loading"
|
||||
:disabled="loading"
|
||||
@click="submit"
|
||||
>
|
||||
{{ loading ? "评分中..." : "提交流程图" }}
|
||||
</n-button>
|
||||
<!-- 显示提交次数 -->
|
||||
<n-button secondary v-if="submissionCount > 0" type="info">
|
||||
{{ submissionCount }} 次
|
||||
</n-button>
|
||||
|
||||
<!-- 评分结果显示 -->
|
||||
<n-button
|
||||
secondary
|
||||
v-if="evaluationResult"
|
||||
@click="showDetailModal = true"
|
||||
:type="getScoreType(evaluationResult.score!)"
|
||||
>
|
||||
{{ evaluationResult.score }}分 {{ evaluationResult.grade }}级
|
||||
</n-button>
|
||||
|
||||
<!-- 详情弹框 -->
|
||||
<n-modal
|
||||
v-model:show="showDetailModal"
|
||||
preset="card"
|
||||
title="评分详情"
|
||||
style="width: 500px"
|
||||
>
|
||||
<!-- 反馈部分 -->
|
||||
<n-card
|
||||
v-if="evaluationResult?.feedback"
|
||||
size="small"
|
||||
title="AI反馈"
|
||||
style="margin-bottom: 16px"
|
||||
>
|
||||
<n-text>{{ evaluationResult.feedback }}</n-text>
|
||||
</n-card>
|
||||
|
||||
<!-- 建议部分 -->
|
||||
<n-card
|
||||
v-if="evaluationResult?.suggestions"
|
||||
size="small"
|
||||
title="改进建议"
|
||||
style="margin-bottom: 16px"
|
||||
>
|
||||
<n-text>{{ evaluationResult.suggestions }}</n-text>
|
||||
</n-card>
|
||||
|
||||
<!-- 详细评分部分 -->
|
||||
<n-card
|
||||
v-if="evaluationResult?.criteriaDetails"
|
||||
size="small"
|
||||
title="详细评分"
|
||||
>
|
||||
<div
|
||||
v-for="(detail, key) in evaluationResult.criteriaDetails"
|
||||
:key="key"
|
||||
style="margin-bottom: 12px"
|
||||
>
|
||||
<n-flex
|
||||
justify="space-between"
|
||||
align="center"
|
||||
style="margin-bottom: 4px"
|
||||
>
|
||||
<n-text strong>{{ key }}</n-text>
|
||||
<n-tag :type="getScoreType(detail.score || 0)" size="small" round>
|
||||
{{ detail.score || 0 }}分 / {{ detail.max }}分
|
||||
</n-tag>
|
||||
</n-flex>
|
||||
<n-text v-if="detail.comment" depth="3" style="font-size: 12px">
|
||||
{{ detail.comment }}
|
||||
</n-text>
|
||||
</div>
|
||||
</n-card>
|
||||
</n-modal>
|
||||
</n-flex>
|
||||
</template>
|
||||
|
||||
@@ -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<ProblemFiltered>[] = [
|
||||
title: renderTableTitle("题目", "streamline-emojis:watermelon-2"),
|
||||
key: "title",
|
||||
minWidth: 200,
|
||||
render: (row) => h(ProblemListTitle, { problem: row }),
|
||||
},
|
||||
{
|
||||
title: renderTableTitle("难度", "streamline-emojis:lady-beetle"),
|
||||
|
||||
@@ -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<Problem>) {
|
||||
if (problem.value) {
|
||||
problem.value = { ...problem.value, ...updates }
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
// 状态
|
||||
problem,
|
||||
|
||||
// 计算属性
|
||||
languages,
|
||||
|
||||
// 操作
|
||||
setProblem,
|
||||
clearProblem,
|
||||
updateProblem,
|
||||
}
|
||||
})
|
||||
|
||||
@@ -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<Node[]>,
|
||||
edges: Ref<Edge[]>,
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -410,6 +410,75 @@ export function createWebSocketComposable<T extends WebSocketMessage>(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 流程图评分更新消息类型
|
||||
*/
|
||||
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<FlowchartEvaluationUpdate> {
|
||||
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<FlowchartEvaluationUpdate>,
|
||||
) {
|
||||
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<FlowchartEvaluationUpdate>) => ws.addHandler(h),
|
||||
removeHandler: (h: MessageHandler<FlowchartEvaluationUpdate>) => ws.removeHandler(h),
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 配置更新消息类型
|
||||
*/
|
||||
|
||||
@@ -170,6 +170,7 @@ export interface ProblemFiltered {
|
||||
rate: string
|
||||
status: "not_test" | "passed" | "failed"
|
||||
author: string
|
||||
allow_flowchart: boolean
|
||||
}
|
||||
|
||||
export interface AdminProblemFiltered {
|
||||
|
||||
Reference in New Issue
Block a user