update
Some checks failed
Deploy / deploy (push) Has been cancelled

This commit is contained in:
2025-10-13 14:58:21 +08:00
parent eda470cb78
commit 854b1f0769
7 changed files with 555 additions and 330 deletions

View File

@@ -0,0 +1,179 @@
<script lang="ts" setup>
import { VueFlow } from "@vue-flow/core"
import { atou } from "utils/functions"
import "@vue-flow/core/dist/style.css"
import "@vue-flow/core/dist/theme-default.css"
interface EvaluationResult {
score?: number
grade?: string
feedback?: string
suggestions?: string
criteriaDetails?: any
}
interface Props {
evaluationResult: EvaluationResult | null
myFlowchartZippedStr: string
}
const props = defineProps<Props>()
const emit = defineEmits<{
close: []
loadToEditor: [data: any]
}>()
const showDetailModal = ref(false)
const nodes = ref<any[]>([])
const edges = ref<any[]>([])
// 根据分数获取标签类型
const getScoreType = (score: number) => {
if (score >= 90) return "success"
if (score >= 80) return "info"
if (score >= 70) return "warning"
return "error"
}
function openDetailModal() {
showDetailModal.value = true
if (props.myFlowchartZippedStr) {
const str = atou(props.myFlowchartZippedStr)
const json = JSON.parse(str)
nodes.value = json.nodes.map((node: any) => ({
...node,
position: node.position || { x: 0, y: 0 },
}))
edges.value = json.edges
}
}
function closeModal() {
showDetailModal.value = false
emit("close")
}
function loadToEditor() {
if (props.myFlowchartZippedStr) {
const str = atou(props.myFlowchartZippedStr)
const json = JSON.parse(str)
const processedData = {
nodes: json.nodes || [],
edges: json.edges || [],
}
emit("loadToEditor", processedData)
}
closeModal()
}
</script>
<template>
<div>
<!-- 评分结果显示 -->
<n-button
secondary
v-if="evaluationResult"
@click="openDetailModal"
:type="getScoreType(evaluationResult.score!)"
>
{{ evaluationResult.score }} {{ evaluationResult.grade }}
</n-button>
<!-- 详情弹框 -->
<n-modal
v-model:show="showDetailModal"
preset="card"
title="评分详情"
style="width: 1000px"
>
<n-grid :cols="5" :x-gap="16">
<n-gi :span="3">
<n-card title="大致缩略图">
<div class="flowchart">
<VueFlow
:nodes="nodes"
:edges="edges"
:fit-view-on-init="true"
:nodes-draggable="false"
:nodes-connectable="false"
:elements-selectable="false"
:zoom-on-scroll="false"
:pan-on-scroll="false"
:pan-on-drag="false"
:select-nodes-on-drag="false"
:delete-key-code="null"
:multi-selection-key-code="null"
/>
</div>
</n-card>
<n-flex style="margin-top: 16px" justify="center">
<n-button @click="loadToEditor" type="primary">
加载到流程图编辑器
</n-button>
</n-flex>
</n-gi>
<n-gi :span="2">
<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-gi>
</n-grid>
</n-modal>
</div>
</template>
<style scoped>
.flowchart {
height: 400px;
width: 100%;
border-radius: 4px;
overflow: hidden;
}
</style>

View File

@@ -1,96 +1,24 @@
<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 { useFlowchartSubmit } from "../composables/useFlowchartSubmit"
import { useProblemStore } from "oj/store/problem" import FlowchartEvaluationDisplay from "./FlowchartEvaluationDisplay.vue"
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 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 const {
grade?: string evaluationResult,
feedback?: string loading,
suggestions?: string submissionCount,
criteriaDetails?: any myFlowchartZippedStr,
} | null>(null) connect,
const submissionStatus = ref<{ disconnect,
status: string checkCurrentSubmissionStatus,
submission_id: string submitFlowchartData,
created_time?: string } = useFlowchartSubmit()
} | 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 并检查状态 // 组件挂载时连接 WebSocket 并检查状态
onMounted(() => { onMounted(() => {
@@ -103,191 +31,17 @@ onUnmounted(() => {
disconnect() disconnect()
}) })
// 将流程图JSON数据转换为Mermaid格式 // 提交函数
const convertToMermaid = (flowchartData: any) => {
const { nodes, edges } = flowchartData
if (!nodes || nodes.length === 0) {
return "graph TD\n A[空流程图]"
}
let mermaid = "graph TD\n"
// 处理节点 - 根据原始类型和自定义标签
nodes.forEach((node: any) => {
const nodeId = node.id
const label = node.data?.customLabel || node.data?.label || "节点"
const originalType = node.data?.originalType || node.type
// 根据节点原始类型确定Mermaid语法
switch (originalType) {
case "start":
mermaid += ` ${nodeId}((${label}))\n`
break
case "end":
mermaid += ` ${nodeId}((${label}))\n`
break
case "input":
// 输入框使用平行四边形
mermaid += ` ${nodeId}[/${label}/]\n`
break
case "output":
// 输出框使用平行四边形
mermaid += ` ${nodeId}[/${label}/]\n`
break
case "default":
mermaid += ` ${nodeId}[${label}]\n`
break
case "decision":
mermaid += ` ${nodeId}{${label}}\n`
break
case "loop":
// 循环使用菱形
mermaid += ` ${nodeId}{${label}}\n`
break
default:
mermaid += ` ${nodeId}[${label}]\n`
}
})
// 处理边
edges.forEach((edge: any) => {
const source = edge.source
const target = edge.target
const label = edge.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
}
async function submit() { async function submit() {
if (!flowchartEditorRef?.value) return await submitFlowchartData(flowchartEditorRef)
// 获取流程图的JSON数据
const flowchartData = flowchartEditorRef.value.getFlowchartData()
if (flowchartData.nodes.length === 0 || flowchartData.edges.length === 0) {
message.error("流程图节点或边不能为空")
return
}
const mermaidCode = convertToMermaid(flowchartData)
const compressed = utoa(JSON.stringify(flowchartData))
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) => { function handleLoadToEditor(data: any) {
if (score >= 90) return "success" if (flowchartEditorRef?.value) {
if (score >= 80) return "info" flowchartEditorRef.value.setFlowchartData(data)
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>
@@ -301,74 +55,18 @@ const formatTime = (timeString: string) => {
> >
{{ loading ? "评分中..." : "提交流程图" }} {{ loading ? "评分中..." : "提交流程图" }}
</n-button> </n-button>
<!-- 显示提交次数 --> <!-- 显示提交次数 -->
<n-button secondary v-if="submissionCount > 0" type="info"> <n-button secondary v-if="submissionCount > 0" type="info">
{{ submissionCount }} {{ submissionCount }}
</n-button> </n-button>
<!-- 评分结果显示 --> <!-- 评分结果显示组件 -->
<n-button <FlowchartEvaluationDisplay
secondary :evaluation-result="evaluationResult"
v-if="evaluationResult" :my-flowchart-zipped-str="myFlowchartZippedStr"
@click="showDetailModal = true" @close="() => {}"
:type="getScoreType(evaluationResult.score!)" @load-to-editor="handleLoadToEditor"
> />
{{ 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> </n-flex>
</template> </template>

View File

@@ -0,0 +1,106 @@
import { ref, watch } from 'vue'
import { useMessage } from 'naive-ui'
import {
useFlowchartWebSocket,
type FlowchartEvaluationUpdate,
} from 'shared/composables/websocket'
export interface EvaluationResult {
score?: number
grade?: string
feedback?: string
suggestions?: string
criteriaDetails?: any
}
export interface SubmissionStatus {
status: string
submission_id: string
created_time?: string
}
export function useFlowchartSubmission() {
const message = useMessage()
const evaluationResult = ref<EvaluationResult | null>(null)
const submissionStatus = ref<SubmissionStatus | null>(null)
const loading = ref(false)
// 处理 WebSocket 消息
const handleWebSocketMessage = (data: FlowchartEvaluationUpdate) => {
console.log("收到流程图评分更新:", data)
if (data.type === "flowchart_evaluation_completed") {
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 subscribeToSubmission = (submissionId: string) => {
console.log("开始订阅WebSocket更新")
subscribe(submissionId)
// 设置评分状态显示
submissionStatus.value = {
status: "processing",
submission_id: submissionId,
created_time: new Date().toISOString(),
}
}
// 清除结果
const clearResult = () => {
evaluationResult.value = null
submissionStatus.value = null
}
// 设置加载状态
const setLoading = (value: boolean) => {
loading.value = value
}
// 设置评估结果
const setEvaluationResult = (result: EvaluationResult) => {
evaluationResult.value = result
}
return {
evaluationResult,
submissionStatus,
loading,
connect,
disconnect,
subscribeToSubmission,
clearResult,
setLoading,
setEvaluationResult,
}
}

View File

@@ -0,0 +1,106 @@
import { ref } from 'vue'
import { useMessage } from 'naive-ui'
import { submitFlowchart, getCurrentProblemFlowchartSubmission } from 'oj/api'
import { useProblemStore } from 'oj/store/problem'
import { atou, utoa } from 'utils/functions'
import { useMermaidConverter } from './useMermaidConverter'
import { useFlowchartSubmission } from './useFlowchartSubmission'
export function useFlowchartSubmit() {
const message = useMessage()
const problemStore = useProblemStore()
const { problem } = toRefs(problemStore)
const { convertToMermaid } = useMermaidConverter()
const {
evaluationResult,
submissionStatus,
loading,
connect,
disconnect,
subscribeToSubmission,
clearResult,
setLoading,
setEvaluationResult,
} = useFlowchartSubmission()
// 提交次数
const submissionCount = ref(0)
// 存储流程图数据
const myFlowchartZippedStr = ref("")
// 检查当前问题的流程图提交状态
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) {
myFlowchartZippedStr.value = data.submission.flowchart_data.data
setEvaluationResult({
score: submission.ai_score,
grade: submission.ai_grade,
feedback: submission.ai_feedback,
suggestions: submission.ai_suggestions,
criteriaDetails: submission.ai_criteria_details,
})
}
}
// 提交流程图
const submitFlowchartData = async (flowchartEditorRef: any) => {
if (!flowchartEditorRef?.value) return
// 获取流程图的JSON数据
const flowchartData = flowchartEditorRef.value.getFlowchartData()
if (flowchartData.nodes.length === 0 || flowchartData.edges.length === 0) {
message.error("流程图节点或边不能为空")
return
}
const mermaidCode = convertToMermaid(flowchartData)
const compressed = utoa(JSON.stringify(flowchartData))
setLoading(true)
clearResult() // 清除之前的结果
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) {
subscribeToSubmission(submissionId)
}
message.success("流程图已提交,请耐心等待评分")
} catch (error) {
setLoading(false)
message.error("流程图提交失败")
console.error("提交流程图失败:", error)
}
}
return {
evaluationResult,
submissionStatus,
loading,
submissionCount,
myFlowchartZippedStr,
connect,
disconnect,
checkCurrentSubmissionStatus,
submitFlowchartData,
}
}

View File

@@ -0,0 +1,108 @@
/**
* 将流程图JSON数据转换为Mermaid格式
*/
export function useMermaidConverter() {
const convertToMermaid = (flowchartData: any) => {
const { nodes, edges } = flowchartData
if (!nodes || nodes.length === 0) {
return "graph TD\n A[空流程图]"
}
let mermaid = "graph TD\n"
// 处理节点 - 根据原始类型和自定义标签
nodes.forEach((node: any) => {
const nodeId = node.id
const label = node.data?.customLabel || node.data?.label || "节点"
const originalType = node.data?.originalType || node.type
// 根据节点原始类型确定Mermaid语法
switch (originalType) {
case "start":
mermaid += ` ${nodeId}((${label}))\n`
break
case "end":
mermaid += ` ${nodeId}((${label}))\n`
break
case "input":
// 输入框使用平行四边形
mermaid += ` ${nodeId}[/${label}/]\n`
break
case "output":
// 输出框使用平行四边形
mermaid += ` ${nodeId}[/${label}/]\n`
break
case "default":
mermaid += ` ${nodeId}[${label}]\n`
break
case "decision":
mermaid += ` ${nodeId}{${label}}\n`
break
case "loop":
// 循环使用菱形
mermaid += ` ${nodeId}{${label}}\n`
break
default:
mermaid += ` ${nodeId}[${label}]\n`
}
})
// 处理边
edges.forEach((edge: any) => {
const source = edge.source
const target = edge.target
const label = edge.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
}
return {
convertToMermaid,
}
}

View File

@@ -3,6 +3,7 @@
<template v-if="nodeType === 'start'"> <template v-if="nodeType === 'start'">
<Handle <Handle
type="source" type="source"
id="output"
:position="Position.Bottom" :position="Position.Bottom"
:style="getHandleStyle('#10b981', { bottom: '-10px' })" :style="getHandleStyle('#10b981', { bottom: '-10px' })"
/> />
@@ -12,6 +13,7 @@
<template v-else-if="nodeType === 'end'"> <template v-else-if="nodeType === 'end'">
<Handle <Handle
type="target" type="target"
id="input"
:position="Position.Top" :position="Position.Top"
:style="getHandleStyle('#ef4444', { top: '-10px' })" :style="getHandleStyle('#ef4444', { top: '-10px' })"
/> />
@@ -21,6 +23,7 @@
<template v-else-if="nodeType === 'decision'"> <template v-else-if="nodeType === 'decision'">
<Handle <Handle
type="target" type="target"
id="input"
:position="Position.Top" :position="Position.Top"
:style="getHandleStyle('#f59e0b', { top: '-16px' })" :style="getHandleStyle('#f59e0b', { top: '-16px' })"
/> />
@@ -122,11 +125,13 @@
<template v-else> <template v-else>
<Handle <Handle
type="target" type="target"
id="input"
:position="Position.Top" :position="Position.Top"
:style="getHandleStyle(nodeConfig.color, { top: '-10px' })" :style="getHandleStyle(nodeConfig.color, { top: '-10px' })"
/> />
<Handle <Handle
type="source" type="source"
id="output"
:position="Position.Bottom" :position="Position.Bottom"
:style="getHandleStyle(nodeConfig.color, { bottom: '-10px' })" :style="getHandleStyle(nodeConfig.color, { bottom: '-10px' })"
/> />

View File

@@ -122,6 +122,28 @@ onUnmounted(() => {
document.removeEventListener('keydown', handleKeyDown) document.removeEventListener('keydown', handleKeyDown)
}) })
// 加载外部数据到编辑器
const setFlowchartData = (data: { nodes: Node[], edges: Edge[] }) => {
if (data && data.nodes && data.edges) {
// 确保节点数据包含必要的位置信息
const processedNodes = data.nodes.map(node => ({
...node,
position: node.position || { x: 0, y: 0 }
}))
// 确保边数据包含必要的 handle 信息
const processedEdges = data.edges.map(edge => ({
...edge,
sourceHandle: edge.sourceHandle || null,
targetHandle: edge.targetHandle || null
}))
nodes.value = processedNodes
edges.value = processedEdges
saveState(nodes.value, edges.value)
}
}
// 暴露节点和边数据给父组件 // 暴露节点和边数据给父组件
defineExpose({ defineExpose({
nodes, nodes,
@@ -129,7 +151,8 @@ defineExpose({
getFlowchartData: () => ({ getFlowchartData: () => ({
nodes: nodes.value, nodes: nodes.value,
edges: edges.value edges: edges.value
}) }),
setFlowchartData
}) })
</script> </script>