@@ -19,6 +19,7 @@ function filterResult(result: Problem) {
|
|||||||
rate: getACRate(result.accepted_number, result.submission_number),
|
rate: getACRate(result.accepted_number, result.submission_number),
|
||||||
status: "",
|
status: "",
|
||||||
author: result.created_by.username,
|
author: result.created_by.username,
|
||||||
|
allow_flowchart: result.allow_flowchart,
|
||||||
}
|
}
|
||||||
if (result.my_status === null || result.my_status === undefined) {
|
if (result.my_status === null || result.my_status === undefined) {
|
||||||
newResult.status = "not_test"
|
newResult.status = "not_test"
|
||||||
@@ -270,7 +271,7 @@ export function getAIHeatmapData() {
|
|||||||
export function submitFlowchart(data: {
|
export function submitFlowchart(data: {
|
||||||
problem_id: number
|
problem_id: number
|
||||||
mermaid_code: string
|
mermaid_code: string
|
||||||
flowchart_data?: any
|
flowchart_data: any // 这个是压缩之后的,元数据太长了
|
||||||
}) {
|
}) {
|
||||||
return http.post("flowchart/submission", data)
|
return http.post("flowchart/submission", data)
|
||||||
}
|
}
|
||||||
@@ -295,3 +296,9 @@ export function retryFlowchartSubmission(submissionId: string) {
|
|||||||
submission_id: submissionId,
|
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>
|
<script lang="ts" setup>
|
||||||
import { useBreakpoints } from "shared/composables/breakpoints"
|
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 { isDesktop } = useBreakpoints()
|
||||||
const message = useMessage()
|
const message = useMessage()
|
||||||
|
const problemStore = useProblemStore()
|
||||||
|
const { problem } = toRefs(problemStore)
|
||||||
|
|
||||||
// 通过inject获取FlowchartEditor组件的引用
|
// 通过inject获取FlowchartEditor组件的引用
|
||||||
const flowchartEditorRef = inject<any>("flowchartEditorRef")
|
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格式
|
// 将流程图JSON数据转换为Mermaid格式
|
||||||
const convertToMermaid = (flowchartData: any) => {
|
const convertToMermaid = (flowchartData: any) => {
|
||||||
const { nodes, edges } = flowchartData
|
const { nodes, edges } = flowchartData
|
||||||
@@ -26,16 +122,18 @@ const convertToMermaid = (flowchartData: any) => {
|
|||||||
// 根据节点原始类型确定Mermaid语法
|
// 根据节点原始类型确定Mermaid语法
|
||||||
switch (originalType) {
|
switch (originalType) {
|
||||||
case "start":
|
case "start":
|
||||||
mermaid += ` ${nodeId}[${label}]\n`
|
mermaid += ` ${nodeId}((${label}))\n`
|
||||||
break
|
break
|
||||||
case "end":
|
case "end":
|
||||||
mermaid += ` ${nodeId}[${label}]\n`
|
mermaid += ` ${nodeId}((${label}))\n`
|
||||||
break
|
break
|
||||||
case "input":
|
case "input":
|
||||||
mermaid += ` ${nodeId}[${label}]\n`
|
// 输入框使用平行四边形
|
||||||
|
mermaid += ` ${nodeId}[/${label}/]\n`
|
||||||
break
|
break
|
||||||
case "output":
|
case "output":
|
||||||
mermaid += ` ${nodeId}[${label}]\n`
|
// 输出框使用平行四边形
|
||||||
|
mermaid += ` ${nodeId}[/${label}/]\n`
|
||||||
break
|
break
|
||||||
case "default":
|
case "default":
|
||||||
mermaid += ` ${nodeId}[${label}]\n`
|
mermaid += ` ${nodeId}[${label}]\n`
|
||||||
@@ -44,7 +142,8 @@ const convertToMermaid = (flowchartData: any) => {
|
|||||||
mermaid += ` ${nodeId}{${label}}\n`
|
mermaid += ` ${nodeId}{${label}}\n`
|
||||||
break
|
break
|
||||||
case "loop":
|
case "loop":
|
||||||
mermaid += ` ${nodeId}[${label}]\n`
|
// 循环使用菱形
|
||||||
|
mermaid += ` ${nodeId}{${label}}\n`
|
||||||
break
|
break
|
||||||
default:
|
default:
|
||||||
mermaid += ` ${nodeId}[${label}]\n`
|
mermaid += ` ${nodeId}[${label}]\n`
|
||||||
@@ -55,19 +154,57 @@ const convertToMermaid = (flowchartData: any) => {
|
|||||||
edges.forEach((edge: any) => {
|
edges.forEach((edge: any) => {
|
||||||
const source = edge.source
|
const source = edge.source
|
||||||
const target = edge.target
|
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`
|
mermaid += ` ${source} -->|${label}| ${target}\n`
|
||||||
} else {
|
} else {
|
||||||
mermaid += ` ${source} --> ${target}\n`
|
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
|
return mermaid
|
||||||
}
|
}
|
||||||
|
|
||||||
const submit = () => {
|
async function submit() {
|
||||||
if (!flowchartEditorRef?.value) return
|
if (!flowchartEditorRef?.value) return
|
||||||
// 获取流程图的JSON数据
|
// 获取流程图的JSON数据
|
||||||
const flowchartData = flowchartEditorRef.value.getFlowchartData()
|
const flowchartData = flowchartEditorRef.value.getFlowchartData()
|
||||||
@@ -76,26 +213,162 @@ const submit = () => {
|
|||||||
message.error("流程图节点或边不能为空")
|
message.error("流程图节点或边不能为空")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 打印JSON数据到控制台
|
|
||||||
console.log("流程图JSON数据:", JSON.stringify(flowchartData, null, 2))
|
|
||||||
|
|
||||||
// 转换为Mermaid格式
|
|
||||||
const mermaidCode = convertToMermaid(flowchartData)
|
const mermaidCode = convertToMermaid(flowchartData)
|
||||||
console.log("Mermaid代码:")
|
const compressed = utoa(JSON.stringify(flowchartData))
|
||||||
console.log(mermaidCode)
|
|
||||||
|
|
||||||
// 显示成功消息
|
loading.value = true
|
||||||
message.success("敬请期待,快了~")
|
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>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
<n-flex align="center">
|
||||||
<n-button
|
<n-button
|
||||||
:size="isDesktop ? 'medium' : 'small'"
|
:size="isDesktop ? 'medium' : 'small'"
|
||||||
type="primary"
|
type="primary"
|
||||||
|
:loading="loading"
|
||||||
|
:disabled="loading"
|
||||||
@click="submit"
|
@click="submit"
|
||||||
>
|
>
|
||||||
提交流程图
|
{{ loading ? "评分中..." : "提交流程图" }}
|
||||||
</n-button>
|
</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>
|
</template>
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import { useUserStore } from "shared/store/user"
|
|||||||
import { renderTableTitle } from "utils/renders"
|
import { renderTableTitle } from "utils/renders"
|
||||||
import ProblemStatus from "./components/ProblemStatus.vue"
|
import ProblemStatus from "./components/ProblemStatus.vue"
|
||||||
import AuthorSelect from "shared/components/AuthorSelect.vue"
|
import AuthorSelect from "shared/components/AuthorSelect.vue"
|
||||||
|
import ProblemListTitle from "./components/ProblemListTitle.vue"
|
||||||
|
|
||||||
interface Tag {
|
interface Tag {
|
||||||
id: number
|
id: number
|
||||||
@@ -146,6 +147,7 @@ const baseColumns: DataTableColumn<ProblemFiltered>[] = [
|
|||||||
title: renderTableTitle("题目", "streamline-emojis:watermelon-2"),
|
title: renderTableTitle("题目", "streamline-emojis:watermelon-2"),
|
||||||
key: "title",
|
key: "title",
|
||||||
minWidth: 200,
|
minWidth: 200,
|
||||||
|
render: (row) => h(ProblemListTitle, { problem: row }),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: renderTableTitle("难度", "streamline-emojis:lady-beetle"),
|
title: renderTableTitle("难度", "streamline-emojis:lady-beetle"),
|
||||||
|
|||||||
@@ -17,40 +17,11 @@ export const useProblemStore = defineStore("problem", () => {
|
|||||||
return problem.value?.languages ?? []
|
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 {
|
return {
|
||||||
// 状态
|
// 状态
|
||||||
problem,
|
problem,
|
||||||
|
|
||||||
// 计算属性
|
// 计算属性
|
||||||
languages,
|
languages,
|
||||||
|
|
||||||
// 操作
|
|
||||||
setProblem,
|
|
||||||
clearProblem,
|
|
||||||
updateProblem,
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,10 +1,7 @@
|
|||||||
import { nanoid } from "nanoid"
|
import { nanoid } from "nanoid"
|
||||||
import type { Ref } from 'vue'
|
import type { Ref } from "vue"
|
||||||
import type { Node, Edge } from '@vue-flow/core'
|
import type { Node, Edge } from "@vue-flow/core"
|
||||||
|
|
||||||
/**
|
|
||||||
* 简化的流程操作
|
|
||||||
*/
|
|
||||||
export function useFlowOperations(
|
export function useFlowOperations(
|
||||||
nodes: Ref<Node[]>,
|
nodes: Ref<Node[]>,
|
||||||
edges: Ref<Edge[]>,
|
edges: Ref<Edge[]>,
|
||||||
@@ -12,24 +9,77 @@ export function useFlowOperations(
|
|||||||
addEdges: (edges: Edge[]) => void,
|
addEdges: (edges: Edge[]) => void,
|
||||||
removeNodes: (nodeIds: string[]) => void,
|
removeNodes: (nodeIds: string[]) => void,
|
||||||
removeEdges: (edgeIds: 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 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 = {
|
const newEdge: Edge = {
|
||||||
id: `edge-${nanoid()}`,
|
id: `edge-${nanoid()}`,
|
||||||
source: params.source,
|
source: params.source,
|
||||||
target: params.target,
|
target: params.target,
|
||||||
sourceHandle: params.sourceHandle,
|
sourceHandle: params.sourceHandle,
|
||||||
targetHandle: params.targetHandle,
|
targetHandle: params.targetHandle,
|
||||||
type: 'default'
|
type: "default",
|
||||||
|
label: autoLabel,
|
||||||
}
|
}
|
||||||
|
|
||||||
addEdges([newEdge])
|
addEdges([newEdge])
|
||||||
saveState(nodes.value, edges.value)
|
saveState(nodes.value, edges.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 边点击删除
|
// 边点击处理 - 单击删除
|
||||||
const handleEdgeClick = (event: any) => {
|
const handleEdgeClick = (event: any) => {
|
||||||
removeEdges([event.edge.id])
|
removeEdges([event.edge.id])
|
||||||
saveState(nodes.value, edges.value)
|
saveState(nodes.value, edges.value)
|
||||||
@@ -38,11 +88,11 @@ export function useFlowOperations(
|
|||||||
// 节点删除
|
// 节点删除
|
||||||
const handleNodeDelete = (nodeId: string) => {
|
const handleNodeDelete = (nodeId: string) => {
|
||||||
// 删除相关边
|
// 删除相关边
|
||||||
const relatedEdges = edges.value.filter(edge =>
|
const relatedEdges = edges.value.filter(
|
||||||
edge.source === nodeId || edge.target === nodeId
|
(edge) => edge.source === nodeId || edge.target === nodeId,
|
||||||
)
|
)
|
||||||
if (relatedEdges.length > 0) {
|
if (relatedEdges.length > 0) {
|
||||||
removeEdges(relatedEdges.map(edge => edge.id))
|
removeEdges(relatedEdges.map((edge) => edge.id))
|
||||||
}
|
}
|
||||||
|
|
||||||
removeNodes([nodeId])
|
removeNodes([nodeId])
|
||||||
@@ -51,7 +101,7 @@ export function useFlowOperations(
|
|||||||
|
|
||||||
// 节点更新
|
// 节点更新
|
||||||
const handleNodeUpdate = (nodeId: string, newLabel: string) => {
|
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) {
|
if (nodeIndex !== -1) {
|
||||||
const oldNode = nodes.value[nodeIndex]
|
const oldNode = nodes.value[nodeIndex]
|
||||||
@@ -61,8 +111,8 @@ export function useFlowOperations(
|
|||||||
...oldNode,
|
...oldNode,
|
||||||
data: {
|
data: {
|
||||||
...oldNode.data,
|
...oldNode.data,
|
||||||
customLabel: newLabel
|
customLabel: newLabel,
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// 使用 Vue Flow 的更新方法
|
// 使用 Vue Flow 的更新方法
|
||||||
@@ -84,14 +134,14 @@ export function useFlowOperations(
|
|||||||
|
|
||||||
// 删除选中的节点和边
|
// 删除选中的节点和边
|
||||||
const deleteSelected = () => {
|
const deleteSelected = () => {
|
||||||
const selectedNodes = nodes.value.filter(node => (node as any).selected)
|
const selectedNodes = nodes.value.filter((node) => (node as any).selected)
|
||||||
const selectedEdges = edges.value.filter(edge => (edge as any).selected)
|
const selectedEdges = edges.value.filter((edge) => (edge as any).selected)
|
||||||
|
|
||||||
if (selectedNodes.length > 0) {
|
if (selectedNodes.length > 0) {
|
||||||
removeNodes(selectedNodes.map(node => node.id))
|
removeNodes(selectedNodes.map((node) => node.id))
|
||||||
}
|
}
|
||||||
if (selectedEdges.length > 0) {
|
if (selectedEdges.length > 0) {
|
||||||
removeEdges(selectedEdges.map(edge => edge.id))
|
removeEdges(selectedEdges.map((edge) => edge.id))
|
||||||
}
|
}
|
||||||
saveState(nodes.value, edges.value)
|
saveState(nodes.value, edges.value)
|
||||||
}
|
}
|
||||||
@@ -102,6 +152,6 @@ export function useFlowOperations(
|
|||||||
handleNodeDelete,
|
handleNodeDelete,
|
||||||
handleNodeUpdate,
|
handleNodeUpdate,
|
||||||
clearCanvas,
|
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
|
rate: string
|
||||||
status: "not_test" | "passed" | "failed"
|
status: "not_test" | "passed" | "failed"
|
||||||
author: string
|
author: string
|
||||||
|
allow_flowchart: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AdminProblemFiltered {
|
export interface AdminProblemFiltered {
|
||||||
|
|||||||
Reference in New Issue
Block a user