This commit is contained in:
@@ -19,11 +19,13 @@ interface Props {
|
|||||||
nextLine?: number
|
nextLine?: number
|
||||||
currentLineText?: string
|
currentLineText?: string
|
||||||
nextLineText?: string
|
nextLineText?: string
|
||||||
|
height?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = withDefaults(defineProps<Props>(), {
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
language: "python",
|
language: "python",
|
||||||
fontSize: 24,
|
fontSize: 24,
|
||||||
|
height: 600,
|
||||||
})
|
})
|
||||||
|
|
||||||
const code = ref(props.modelValue)
|
const code = ref(props.modelValue)
|
||||||
@@ -123,7 +125,6 @@ const highlightField = StateField.define<DecorationSet>({
|
|||||||
const styleTheme = EditorView.baseTheme({
|
const styleTheme = EditorView.baseTheme({
|
||||||
"& .cm-scroller": {
|
"& .cm-scroller": {
|
||||||
"font-family": "Monaco",
|
"font-family": "Monaco",
|
||||||
height: "calc(100vh - 120px)",
|
|
||||||
},
|
},
|
||||||
"&.cm-editor.cm-focused": {
|
"&.cm-editor.cm-focused": {
|
||||||
outline: "none",
|
outline: "none",
|
||||||
@@ -254,10 +255,12 @@ watch(
|
|||||||
<Codemirror
|
<Codemirror
|
||||||
v-model="code"
|
v-model="code"
|
||||||
indentWithTab
|
indentWithTab
|
||||||
|
disabled
|
||||||
:extensions="[styleTheme, lang, highlightField, isDark ? oneDark : smoothy]"
|
:extensions="[styleTheme, lang, highlightField, isDark ? oneDark : smoothy]"
|
||||||
:tabSize="4"
|
:tabSize="4"
|
||||||
:style="{
|
:style="{
|
||||||
fontSize: props.fontSize + 'px',
|
fontSize: props.fontSize + 'px',
|
||||||
|
height: props.height + 'px',
|
||||||
}"
|
}"
|
||||||
@change="onChange"
|
@change="onChange"
|
||||||
@ready="onReady"
|
@ready="onReady"
|
||||||
|
|||||||
@@ -1,28 +1,127 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed } from "vue"
|
// Vue 核心
|
||||||
|
import { ref, computed, watch } from "vue"
|
||||||
|
|
||||||
|
// 第三方库
|
||||||
|
import copyTextToClipboard from "copy-text-to-clipboard"
|
||||||
|
import { useMessage } from "naive-ui"
|
||||||
import { Icon } from "@iconify/vue"
|
import { Icon } from "@iconify/vue"
|
||||||
|
import { useIntervalFn } from "@vueuse/core"
|
||||||
|
|
||||||
interface Props {
|
// 组件
|
||||||
visible: boolean
|
import DebugEditor from "./DebugEditor.vue"
|
||||||
variables?: Record<string, any>
|
|
||||||
output?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
const props = withDefaults(defineProps<Props>(), {
|
// 组合式函数和类型
|
||||||
visible: false,
|
import { code, input, reset, size, output, status } from "../composables/code"
|
||||||
variables: () => ({}),
|
import { Status } from "../types"
|
||||||
output: "",
|
import { debug } from "../api"
|
||||||
|
|
||||||
|
// ==================== Props 和 Emits ====================
|
||||||
|
const props = defineProps<{
|
||||||
|
initialDebugData?: any
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
close: []
|
||||||
|
}>()
|
||||||
|
|
||||||
|
// ==================== 响应式状态 ====================
|
||||||
|
const message = useMessage()
|
||||||
|
|
||||||
|
// 调试状态
|
||||||
|
const debugData = ref<any>(props.initialDebugData || null)
|
||||||
|
const currentStep = ref(0)
|
||||||
|
|
||||||
|
// 自动运行状态
|
||||||
|
const isAutoRunning = ref(false)
|
||||||
|
|
||||||
|
const {
|
||||||
|
pause: pauseAutoRun,
|
||||||
|
resume: resumeAutoRun,
|
||||||
|
isActive: isAutoRunActive,
|
||||||
|
} = useIntervalFn(
|
||||||
|
() => {
|
||||||
|
if (currentStep.value < debugData.value.trace.length - 1) {
|
||||||
|
currentStep.value++
|
||||||
|
|
||||||
|
// 如果遇到输入步骤,暂停自动运行
|
||||||
|
if (debugData.value.trace[currentStep.value]?.event === "raw_input") {
|
||||||
|
pauseAutoRun()
|
||||||
|
isAutoRunning.value = false
|
||||||
|
message.info("程序正在等待输入,自动运行已暂停")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 到达最后一步,停止自动运行
|
||||||
|
pauseAutoRun()
|
||||||
|
isAutoRunning.value = false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
500,
|
||||||
|
{ immediate: false },
|
||||||
|
)
|
||||||
|
|
||||||
|
// ==================== 监听器 ====================
|
||||||
|
// 监听 props 变化
|
||||||
|
watch(
|
||||||
|
() => props.initialDebugData,
|
||||||
|
(newData) => {
|
||||||
|
if (newData) {
|
||||||
|
debugData.value = newData
|
||||||
|
currentStep.value = 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ immediate: true },
|
||||||
|
)
|
||||||
|
|
||||||
|
// ==================== 计算属性 ====================
|
||||||
|
// 调试行号相关
|
||||||
|
const currentLine = computed(() => {
|
||||||
|
if (
|
||||||
|
debugData.value &&
|
||||||
|
debugData.value.trace &&
|
||||||
|
debugData.value.trace[currentStep.value]
|
||||||
|
) {
|
||||||
|
const line = debugData.value.trace[currentStep.value].line
|
||||||
|
console.log(`Step ${currentStep.value}: currentLine = ${line}`)
|
||||||
|
return line && line > 0 ? line : undefined
|
||||||
|
}
|
||||||
|
return undefined
|
||||||
})
|
})
|
||||||
|
|
||||||
const emit = defineEmits(["close"])
|
const nextLine = computed(() => {
|
||||||
|
if (
|
||||||
|
debugData.value &&
|
||||||
|
debugData.value.trace &&
|
||||||
|
debugData.value.trace[currentStep.value + 1]
|
||||||
|
) {
|
||||||
|
const line = debugData.value.trace[currentStep.value + 1].line
|
||||||
|
console.log(`Step ${currentStep.value}: nextLine = ${line}`)
|
||||||
|
return line && line > 0 && line !== currentLine.value ? line : undefined
|
||||||
|
}
|
||||||
|
console.log(`Step ${currentStep.value}: nextLine = undefined (no next step)`)
|
||||||
|
return undefined
|
||||||
|
})
|
||||||
|
|
||||||
|
// 调试信息相关
|
||||||
|
const currentVariables = computed(() => {
|
||||||
|
if (
|
||||||
|
debugData.value &&
|
||||||
|
debugData.value.trace &&
|
||||||
|
debugData.value.trace[currentStep.value]
|
||||||
|
) {
|
||||||
|
return debugData.value.trace[currentStep.value].globals || {}
|
||||||
|
}
|
||||||
|
return {}
|
||||||
|
})
|
||||||
|
|
||||||
// 格式化变量显示
|
// 格式化变量显示
|
||||||
const formattedVariables = computed(() => {
|
const formattedVariables = computed(() => {
|
||||||
if (!props.variables || Object.keys(props.variables).length === 0) {
|
const variables = currentVariables.value
|
||||||
|
if (!variables || Object.keys(variables).length === 0) {
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
|
|
||||||
return Object.entries(props.variables).map(([key, value]) => {
|
return Object.entries(variables).map(([key, value]) => {
|
||||||
// 处理特殊类型
|
// 处理特殊类型
|
||||||
let displayValue = ""
|
let displayValue = ""
|
||||||
let displayType = typeof value
|
let displayType = typeof value
|
||||||
@@ -49,25 +148,303 @@ const formattedVariables = computed(() => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
// 格式化输出显示
|
|
||||||
const formattedOutput = computed(() => {
|
|
||||||
if (!props.output) return ""
|
|
||||||
return props.output
|
|
||||||
})
|
|
||||||
|
|
||||||
// 计算输出行数
|
// 计算输出行数
|
||||||
const outputLines = computed(() => {
|
const outputLines = computed(() => {
|
||||||
if (!props.output) return 0
|
const output = currentOutput.value
|
||||||
return props.output.split("\n").filter((line) => line !== "").length
|
if (!output) return 0
|
||||||
|
return output.split("\n").filter((line) => line !== "").length
|
||||||
})
|
})
|
||||||
|
|
||||||
function closePanel() {
|
const currentLineText = computed(() => {
|
||||||
emit("close")
|
if (
|
||||||
|
debugData.value &&
|
||||||
|
debugData.value.trace &&
|
||||||
|
debugData.value.trace[currentStep.value]
|
||||||
|
) {
|
||||||
|
const step = debugData.value.trace[currentStep.value]
|
||||||
|
const isLastStep = currentStep.value === debugData.value.trace.length - 1
|
||||||
|
const eventText = isLastStep ? "" : getEventText(step.event)
|
||||||
|
const stepText = isLastStep
|
||||||
|
? "最后一步"
|
||||||
|
: `当前第${currentStep.value + 1}步`
|
||||||
|
return `${stepText}${eventText}`
|
||||||
|
}
|
||||||
|
return undefined
|
||||||
|
})
|
||||||
|
|
||||||
|
const nextLineText = computed(() => {
|
||||||
|
if (
|
||||||
|
debugData.value &&
|
||||||
|
debugData.value.trace &&
|
||||||
|
debugData.value.trace[currentStep.value + 1]
|
||||||
|
) {
|
||||||
|
const step = debugData.value.trace[currentStep.value + 1]
|
||||||
|
const isNextLastStep =
|
||||||
|
currentStep.value + 1 === debugData.value.trace.length - 1
|
||||||
|
const eventText = isNextLastStep ? "" : getEventText(step.event)
|
||||||
|
const stepText = isNextLastStep ? "最后一步" : `下一步`
|
||||||
|
return `${stepText}${eventText}`
|
||||||
|
}
|
||||||
|
return undefined
|
||||||
|
})
|
||||||
|
|
||||||
|
// ==================== 工具函数 ====================
|
||||||
|
/**
|
||||||
|
* 获取事件类型的中文描述
|
||||||
|
*/
|
||||||
|
function getEventText(event: string): string {
|
||||||
|
switch (event) {
|
||||||
|
case "step_line":
|
||||||
|
return "" // 普通执行不显示额外文字
|
||||||
|
case "call":
|
||||||
|
return "(调用函数)"
|
||||||
|
case "return":
|
||||||
|
return "(函数返回)"
|
||||||
|
case "exception":
|
||||||
|
return "(异常)"
|
||||||
|
case "uncaught_exception":
|
||||||
|
return "(异常)"
|
||||||
|
case "raw_input":
|
||||||
|
return "(等待输入)"
|
||||||
|
default:
|
||||||
|
return event || ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 输出相关
|
||||||
|
const currentOutput = computed(() => {
|
||||||
|
if (
|
||||||
|
debugData.value &&
|
||||||
|
debugData.value.trace &&
|
||||||
|
debugData.value.trace.length > 0
|
||||||
|
) {
|
||||||
|
let outputText = ""
|
||||||
|
|
||||||
|
for (let i = 0; i <= currentStep.value; i++) {
|
||||||
|
const step = debugData.value.trace[i]
|
||||||
|
if (step) {
|
||||||
|
if (step.event === "exception" || step.event === "uncaught_exception") {
|
||||||
|
if (step.exception_msg) {
|
||||||
|
outputText = step.exception_msg
|
||||||
|
}
|
||||||
|
} else if (step.stdout) {
|
||||||
|
outputText = step.stdout
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
outputText = outputText.trimEnd()
|
||||||
|
|
||||||
|
const hasException = debugData.value.trace.some(
|
||||||
|
(step: any) =>
|
||||||
|
step.event === "exception" || step.event === "uncaught_exception",
|
||||||
|
)
|
||||||
|
status.value = hasException ? Status.RuntimeError : Status.Accepted
|
||||||
|
|
||||||
|
output.value = outputText
|
||||||
|
return outputText
|
||||||
|
}
|
||||||
|
return output.value || ""
|
||||||
|
})
|
||||||
|
|
||||||
|
// ==================== 主要功能函数 ====================
|
||||||
|
/**
|
||||||
|
* 复制代码到剪贴板
|
||||||
|
*/
|
||||||
|
function copy() {
|
||||||
|
copyTextToClipboard(code.value)
|
||||||
|
message.success("已经复制好了")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 当 debugData 更新时,初始化相关状态
|
||||||
|
watch(
|
||||||
|
() => debugData.value,
|
||||||
|
(newData) => {
|
||||||
|
if (newData) {
|
||||||
|
currentStep.value = 0
|
||||||
|
|
||||||
|
// 检查步骤数量并显示提醒
|
||||||
|
if (newData.trace && newData.trace.length > 5000) {
|
||||||
|
message.warning(`超过 5000 步,请优化代码或减少循环次数`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 显示前几个 trace 条目的行号
|
||||||
|
if (newData.trace) {
|
||||||
|
console.log("First few trace entries:")
|
||||||
|
newData.trace.slice(0, 5).forEach((entry: any, index: number) => {
|
||||||
|
console.log(
|
||||||
|
` Step ${index}: line ${entry.line}, event: ${entry.event}`,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ immediate: true },
|
||||||
|
)
|
||||||
|
|
||||||
|
// ==================== 调试控制函数 ====================
|
||||||
|
/**
|
||||||
|
* 跳转到第一步
|
||||||
|
*/
|
||||||
|
function firstStep() {
|
||||||
|
if (debugData.value && debugData.value.trace) {
|
||||||
|
currentStep.value = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 上一步
|
||||||
|
*/
|
||||||
|
function prevStep() {
|
||||||
|
if (debugData.value && debugData.value.trace && currentStep.value > 0) {
|
||||||
|
currentStep.value--
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 下一步
|
||||||
|
*/
|
||||||
|
function nextStep() {
|
||||||
|
if (
|
||||||
|
debugData.value &&
|
||||||
|
debugData.value.trace &&
|
||||||
|
currentStep.value < debugData.value.trace.length - 1
|
||||||
|
) {
|
||||||
|
currentStep.value++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 跳转到最后一步
|
||||||
|
*/
|
||||||
|
function lastStep() {
|
||||||
|
if (debugData.value && debugData.value.trace) {
|
||||||
|
currentStep.value = debugData.value.trace.length - 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== UI控制函数 ====================
|
||||||
|
/**
|
||||||
|
* 自动运行/暂停
|
||||||
|
*/
|
||||||
|
function autoRun() {
|
||||||
|
if (!debugData.value || !debugData.value.trace) return
|
||||||
|
|
||||||
|
if (isAutoRunActive.value) {
|
||||||
|
// 停止自动运行
|
||||||
|
pauseAutoRun()
|
||||||
|
isAutoRunning.value = false
|
||||||
|
} else {
|
||||||
|
// 开始自动运行
|
||||||
|
isAutoRunning.value = true
|
||||||
|
resumeAutoRun()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<n-card v-if="visible" class="floating-panel" :bordered="true" size="small">
|
<n-flex>
|
||||||
|
<!-- 左侧:分为上中下三层 -->
|
||||||
|
<n-flex vertical style="flex: 1">
|
||||||
|
<DebugEditor
|
||||||
|
v-model="code.value"
|
||||||
|
:font-size="size"
|
||||||
|
:language="code.language"
|
||||||
|
:current-line="currentLine"
|
||||||
|
:next-line="nextLine"
|
||||||
|
:current-line-text="currentLineText"
|
||||||
|
:next-line-text="nextLineText"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- 中:调试控制按钮 -->
|
||||||
|
<n-flex align="center" justify="center" style="margin-top: 20px">
|
||||||
|
<!-- 步骤控制 -->
|
||||||
|
<n-tooltip>
|
||||||
|
<template #trigger>
|
||||||
|
<n-button @click="firstStep">
|
||||||
|
<template #icon>
|
||||||
|
<Icon icon="material-symbols:skip-previous" />
|
||||||
|
</template>
|
||||||
|
</n-button>
|
||||||
|
</template>
|
||||||
|
第一步
|
||||||
|
</n-tooltip>
|
||||||
|
|
||||||
|
<n-tooltip>
|
||||||
|
<template #trigger>
|
||||||
|
<n-button type="primary" @click="prevStep">
|
||||||
|
<template #icon>
|
||||||
|
<Icon icon="tabler:chevron-left" />
|
||||||
|
</template>
|
||||||
|
</n-button>
|
||||||
|
</template>
|
||||||
|
上一步
|
||||||
|
</n-tooltip>
|
||||||
|
|
||||||
|
<n-tooltip>
|
||||||
|
<template #trigger>
|
||||||
|
<n-button
|
||||||
|
:type="isAutoRunning ? 'warning' : 'info'"
|
||||||
|
@click="autoRun"
|
||||||
|
>
|
||||||
|
<template #icon>
|
||||||
|
<Icon
|
||||||
|
:icon="
|
||||||
|
isAutoRunning
|
||||||
|
? 'material-symbols:pause'
|
||||||
|
: 'material-symbols:play-arrow'
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</n-button>
|
||||||
|
</template>
|
||||||
|
{{ isAutoRunning ? "暂停自动运行" : "开始自动运行" }}
|
||||||
|
</n-tooltip>
|
||||||
|
|
||||||
|
<n-tooltip>
|
||||||
|
<template #trigger>
|
||||||
|
<n-button type="error" @click="nextStep">
|
||||||
|
<template #icon>
|
||||||
|
<Icon icon="tabler:chevron-right" />
|
||||||
|
</template>
|
||||||
|
</n-button>
|
||||||
|
</template>
|
||||||
|
下一步
|
||||||
|
</n-tooltip>
|
||||||
|
|
||||||
|
<n-tooltip>
|
||||||
|
<template #trigger>
|
||||||
|
<n-button @click="lastStep">
|
||||||
|
<template #icon>
|
||||||
|
<Icon icon="material-symbols:skip-next" />
|
||||||
|
</template>
|
||||||
|
</n-button>
|
||||||
|
</template>
|
||||||
|
最后一步
|
||||||
|
</n-tooltip>
|
||||||
|
</n-flex>
|
||||||
|
|
||||||
|
<!-- 下:调试进度条 -->
|
||||||
|
<n-flex v-if="debugData && debugData.trace" vertical>
|
||||||
|
<n-slider
|
||||||
|
v-model:value="currentStep"
|
||||||
|
:min="0"
|
||||||
|
:max="debugData.trace.length - 1"
|
||||||
|
:step="1"
|
||||||
|
:tooltip="false"
|
||||||
|
/>
|
||||||
|
<!-- 步骤信息 -->
|
||||||
|
<n-text
|
||||||
|
depth="3"
|
||||||
|
style="text-align: center; font-size: 12px; white-space: nowrap"
|
||||||
|
>
|
||||||
|
步骤: {{ currentStep + 1 }} / {{ debugData.trace.length }}
|
||||||
|
</n-text>
|
||||||
|
</n-flex>
|
||||||
|
</n-flex>
|
||||||
|
|
||||||
|
<!-- 右侧:调试信息面板 -->
|
||||||
|
<n-card :bordered="true" size="small" style="width: 350px">
|
||||||
<template #header>
|
<template #header>
|
||||||
<n-flex justify="space-between" align="center">
|
<n-flex justify="space-between" align="center">
|
||||||
<n-flex align="center">
|
<n-flex align="center">
|
||||||
@@ -76,21 +453,12 @@ function closePanel() {
|
|||||||
</n-icon>
|
</n-icon>
|
||||||
<n-text strong>调试信息</n-text>
|
<n-text strong>调试信息</n-text>
|
||||||
</n-flex>
|
</n-flex>
|
||||||
<n-button quaternary circle size="small" @click="closePanel">
|
|
||||||
<template #icon>
|
|
||||||
<n-icon>
|
|
||||||
<Icon icon="mdi:close" :width="16" :height="16" />
|
|
||||||
</n-icon>
|
|
||||||
</template>
|
|
||||||
</n-button>
|
|
||||||
</n-flex>
|
</n-flex>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<n-space vertical :size="16" class="panel-content">
|
|
||||||
<!-- 变量部分 -->
|
<!-- 变量部分 -->
|
||||||
<n-collapse :default-expanded-names="['variables']">
|
<n-collapse :default-expanded-names="['variables']">
|
||||||
<n-collapse-item title="变量" name="variables">
|
<n-collapse-item name="variables">
|
||||||
<n-scrollbar style="max-height: 260px">
|
|
||||||
<template #header>
|
<template #header>
|
||||||
<n-flex align="center">
|
<n-flex align="center">
|
||||||
<n-icon>
|
<n-icon>
|
||||||
@@ -99,9 +467,14 @@ function closePanel() {
|
|||||||
<n-text>变量</n-text>
|
<n-text>变量</n-text>
|
||||||
</n-flex>
|
</n-flex>
|
||||||
</template>
|
</template>
|
||||||
<div v-if="formattedVariables.length === 0">
|
<n-scrollbar style="max-height: 260px">
|
||||||
<n-text type="info" class="no-variables-text">暂无变量</n-text>
|
<n-space
|
||||||
</div>
|
v-if="formattedVariables.length === 0"
|
||||||
|
vertical
|
||||||
|
style="padding: 20px; text-align: center"
|
||||||
|
>
|
||||||
|
<n-text type="info">暂无变量</n-text>
|
||||||
|
</n-space>
|
||||||
<n-space v-else vertical>
|
<n-space v-else vertical>
|
||||||
<n-card
|
<n-card
|
||||||
v-for="variable in formattedVariables"
|
v-for="variable in formattedVariables"
|
||||||
@@ -109,17 +482,22 @@ function closePanel() {
|
|||||||
size="small"
|
size="small"
|
||||||
:bordered="true"
|
:bordered="true"
|
||||||
>
|
>
|
||||||
<n-flex
|
<n-space vertical :size="8">
|
||||||
justify="space-between"
|
<n-flex justify="space-between" align="center">
|
||||||
align="center"
|
<n-text strong type="primary">{{ variable.name }}</n-text>
|
||||||
class="variable-header"
|
|
||||||
>
|
|
||||||
<n-text strong :type="'primary'">{{ variable.name }}</n-text>
|
|
||||||
<n-tag size="small" type="info">{{ variable.type }}</n-tag>
|
<n-tag size="small" type="info">{{ variable.type }}</n-tag>
|
||||||
</n-flex>
|
</n-flex>
|
||||||
<n-text code class="variable-value">
|
<n-text
|
||||||
|
code
|
||||||
|
style="
|
||||||
|
font-size: 12px;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
word-break: break-all;
|
||||||
|
"
|
||||||
|
>
|
||||||
{{ variable.value }}
|
{{ variable.value }}
|
||||||
</n-text>
|
</n-text>
|
||||||
|
</n-space>
|
||||||
</n-card>
|
</n-card>
|
||||||
</n-space>
|
</n-space>
|
||||||
</n-scrollbar>
|
</n-scrollbar>
|
||||||
@@ -127,8 +505,8 @@ function closePanel() {
|
|||||||
</n-collapse>
|
</n-collapse>
|
||||||
|
|
||||||
<!-- 输出部分 -->
|
<!-- 输出部分 -->
|
||||||
<n-collapse v-if="formattedOutput" :default-expanded-names="['output']">
|
<n-collapse v-if="currentOutput" :default-expanded-names="['output']">
|
||||||
<n-collapse-item :title="`输出 (${outputLines} 行)`" name="output">
|
<n-collapse-item name="output">
|
||||||
<template #header>
|
<template #header>
|
||||||
<n-flex align="center">
|
<n-flex align="center">
|
||||||
<n-icon>
|
<n-icon>
|
||||||
@@ -139,50 +517,22 @@ function closePanel() {
|
|||||||
</template>
|
</template>
|
||||||
<n-card size="small" :bordered="true">
|
<n-card size="small" :bordered="true">
|
||||||
<n-scrollbar style="max-height: 300px">
|
<n-scrollbar style="max-height: 300px">
|
||||||
<n-text code class="output-text">
|
<n-text
|
||||||
{{ formattedOutput }}
|
code
|
||||||
|
style="
|
||||||
|
font-size: 12px;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
word-break: break-all;
|
||||||
|
display: block;
|
||||||
|
"
|
||||||
|
>
|
||||||
|
{{ currentOutput }}
|
||||||
</n-text>
|
</n-text>
|
||||||
</n-scrollbar>
|
</n-scrollbar>
|
||||||
</n-card>
|
</n-card>
|
||||||
</n-collapse-item>
|
</n-collapse-item>
|
||||||
</n-collapse>
|
</n-collapse>
|
||||||
</n-space>
|
|
||||||
</n-card>
|
</n-card>
|
||||||
|
</n-flex>
|
||||||
</template>
|
</template>
|
||||||
|
<style scoped></style>
|
||||||
<style scoped>
|
|
||||||
.floating-panel {
|
|
||||||
width: 300px;
|
|
||||||
position: absolute;
|
|
||||||
top: 20px;
|
|
||||||
right: 120px;
|
|
||||||
z-index: 100;
|
|
||||||
}
|
|
||||||
|
|
||||||
.output-text {
|
|
||||||
font-size: 12px;
|
|
||||||
white-space: pre-wrap;
|
|
||||||
word-break: break-all;
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.panel-content {
|
|
||||||
padding: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.no-variables-text {
|
|
||||||
text-align: center;
|
|
||||||
display: block;
|
|
||||||
padding: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.variable-header {
|
|
||||||
margin-bottom: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.variable-value {
|
|
||||||
font-size: 12px;
|
|
||||||
white-space: pre-wrap;
|
|
||||||
word-break: break-all;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|||||||
@@ -1,21 +1,64 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import { ref } from "vue"
|
||||||
import copyTextToClipboard from "copy-text-to-clipboard"
|
import copyTextToClipboard from "copy-text-to-clipboard"
|
||||||
import { useMessage } from "naive-ui"
|
import { useMessage } from "naive-ui"
|
||||||
import CodeEditor from "../components/CodeEditor.vue"
|
import CodeEditor from "../components/CodeEditor.vue"
|
||||||
|
import DebugPanel from "../components/DebugPanel.vue"
|
||||||
import { code, input, reset, size } from "../composables/code"
|
import { code, input, reset, size } from "../composables/code"
|
||||||
import { debug } from "../api"
|
import { debug } from "../api"
|
||||||
|
|
||||||
const message = useMessage()
|
const message = useMessage()
|
||||||
|
|
||||||
|
const showDebugModal = ref(false)
|
||||||
|
const debugData = ref<any>(null)
|
||||||
|
|
||||||
|
function closeDebug() {
|
||||||
|
showDebugModal.value = false
|
||||||
|
debugData.value = null
|
||||||
|
}
|
||||||
|
|
||||||
function copy() {
|
function copy() {
|
||||||
copyTextToClipboard(code.value)
|
copyTextToClipboard(code.value)
|
||||||
message.success("已经复制好了")
|
message.success("已经复制好了")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查调试数据是否需要输入但用户没有提供足够的输入
|
||||||
|
*/
|
||||||
|
function needsInputButNotProvided(debugData: any, providedInputs: string[]): boolean {
|
||||||
|
if (!debugData?.trace || debugData.trace.length === 0) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
const lastStep = debugData.trace[debugData.trace.length - 1]
|
||||||
|
// 如果最后一步是 raw_input,说明程序在等待输入,用户提供的输入不足
|
||||||
|
if (lastStep.event === "raw_input") {
|
||||||
|
// 统计 trace 中所有的 raw_input 事件数量(程序需要的输入数量)
|
||||||
|
const requiredInputCount = debugData.trace.filter(
|
||||||
|
(step: any) => step.event === "raw_input"
|
||||||
|
).length
|
||||||
|
|
||||||
|
// 如果用户提供的输入数量不足,返回 true
|
||||||
|
return providedInputs.length < requiredInputCount
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
async function handleDebug() {
|
async function handleDebug() {
|
||||||
const inputs = input.value ? input.value.split("\n") : []
|
const inputs = input.value
|
||||||
|
? input.value.split("\n").filter((line) => line.trim() !== "")
|
||||||
|
: []
|
||||||
const res = await debug(code.value, inputs)
|
const res = await debug(code.value, inputs)
|
||||||
console.log(res.data)
|
debugData.value = res.data
|
||||||
|
|
||||||
|
// 检查是否需要输入但用户没有提供足够的输入
|
||||||
|
if (needsInputButNotProvided(res.data, inputs)) {
|
||||||
|
message.warning("程序需要输入,请在输入框输入内容后重新点击调试按钮")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
showDebugModal.value = true
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -30,6 +73,28 @@ async function handleDebug() {
|
|||||||
<template #actions>
|
<template #actions>
|
||||||
<n-button quaternary type="primary" @click="copy">复制</n-button>
|
<n-button quaternary type="primary" @click="copy">复制</n-button>
|
||||||
<n-button quaternary @click="reset">清空</n-button>
|
<n-button quaternary @click="reset">清空</n-button>
|
||||||
|
<n-button
|
||||||
|
v-if="code.language === 'python'"
|
||||||
|
quaternary
|
||||||
|
type="error"
|
||||||
|
:disabled="!code.value"
|
||||||
|
@click="handleDebug"
|
||||||
|
>
|
||||||
|
调试
|
||||||
|
</n-button>
|
||||||
</template>
|
</template>
|
||||||
</CodeEditor>
|
</CodeEditor>
|
||||||
|
|
||||||
|
<n-modal
|
||||||
|
v-model:show="showDebugModal"
|
||||||
|
preset="card"
|
||||||
|
title="调试"
|
||||||
|
size="large"
|
||||||
|
:mask-closable="false"
|
||||||
|
:auto-focus="false"
|
||||||
|
@close="closeDebug"
|
||||||
|
style="width: 80vw; max-width: 1000px;"
|
||||||
|
>
|
||||||
|
<DebugPanel :initial-debug-data="debugData" @close="closeDebug" />
|
||||||
|
</n-modal>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { code } from "../composables/code"
|
import { code } from "../composables/code"
|
||||||
import CodeSection from "./CodeSection.vue"
|
import CodeSection from "./CodeSection.vue"
|
||||||
import DebugSection from "./DebugSection.vue"
|
|
||||||
import InputSection from "./InputSection.vue"
|
import InputSection from "./InputSection.vue"
|
||||||
import OutputSection from "./OutputSection.vue"
|
import OutputSection from "./OutputSection.vue"
|
||||||
import TurtleSection from "./TurtleSection.vue"
|
import TurtleSection from "./TurtleSection.vue"
|
||||||
@@ -11,9 +10,7 @@ import TurtleSection from "./TurtleSection.vue"
|
|||||||
<n-layout-content class="container">
|
<n-layout-content class="container">
|
||||||
<n-split direction="horizontal" :min="1 / 3" :max="4 / 5">
|
<n-split direction="horizontal" :min="1 / 3" :max="4 / 5">
|
||||||
<template #1>
|
<template #1>
|
||||||
<component
|
<CodeSection />
|
||||||
:is="code.language === 'python' ? DebugSection : CodeSection"
|
|
||||||
/>
|
|
||||||
</template>
|
</template>
|
||||||
<template #2>
|
<template #2>
|
||||||
<n-split
|
<n-split
|
||||||
|
|||||||
@@ -1,536 +0,0 @@
|
|||||||
<script lang="ts" setup>
|
|
||||||
// Vue 核心
|
|
||||||
import { ref, computed, watch } from "vue"
|
|
||||||
|
|
||||||
// 第三方库
|
|
||||||
import copyTextToClipboard from "copy-text-to-clipboard"
|
|
||||||
import { useMessage } from "naive-ui"
|
|
||||||
import { Icon } from "@iconify/vue"
|
|
||||||
import { useIntervalFn } from "@vueuse/core"
|
|
||||||
|
|
||||||
// 组件
|
|
||||||
import DebugEditor from "../components/DebugEditor.vue"
|
|
||||||
import DebugPanel from "../components/DebugPanel.vue"
|
|
||||||
|
|
||||||
// 组合式函数和类型
|
|
||||||
import { code, input, reset, size, output, status } from "../composables/code"
|
|
||||||
import { Status } from "../types"
|
|
||||||
import { debug } from "../api"
|
|
||||||
|
|
||||||
// ==================== 响应式状态 ====================
|
|
||||||
const message = useMessage()
|
|
||||||
|
|
||||||
// 调试状态
|
|
||||||
const showDebug = ref(false)
|
|
||||||
const debugData = ref<any>(null)
|
|
||||||
const currentStep = ref(0)
|
|
||||||
|
|
||||||
// UI 状态
|
|
||||||
const showFloatingPanel = ref(false)
|
|
||||||
|
|
||||||
// 自动运行状态
|
|
||||||
const isAutoRunning = ref(false)
|
|
||||||
|
|
||||||
const {
|
|
||||||
pause: pauseAutoRun,
|
|
||||||
resume: resumeAutoRun,
|
|
||||||
isActive: isAutoRunActive,
|
|
||||||
} = useIntervalFn(
|
|
||||||
() => {
|
|
||||||
if (currentStep.value < debugData.value.trace.length - 1) {
|
|
||||||
currentStep.value++
|
|
||||||
|
|
||||||
// 如果遇到输入步骤,暂停自动运行
|
|
||||||
if (debugData.value.trace[currentStep.value]?.event === "raw_input") {
|
|
||||||
pauseAutoRun()
|
|
||||||
isAutoRunning.value = false
|
|
||||||
message.info("程序正在等待输入,自动运行已暂停")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// 到达最后一步,停止自动运行
|
|
||||||
pauseAutoRun()
|
|
||||||
isAutoRunning.value = false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
500,
|
|
||||||
{ immediate: false },
|
|
||||||
)
|
|
||||||
|
|
||||||
// 调试状态快照(用于检测代码/输入变化)
|
|
||||||
let debugStartCode = ""
|
|
||||||
let debugStartInput = ""
|
|
||||||
|
|
||||||
// ==================== 监听器 ====================
|
|
||||||
// 监听代码变化
|
|
||||||
watch(
|
|
||||||
() => code.value,
|
|
||||||
(newCode) => {
|
|
||||||
if (showDebug.value && newCode !== debugStartCode) {
|
|
||||||
message.warning("代码已修改,请重新点击调试按钮")
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
// 监听输入变化
|
|
||||||
watch(
|
|
||||||
() => input.value,
|
|
||||||
(newInput) => {
|
|
||||||
if (showDebug.value && newInput !== debugStartInput) {
|
|
||||||
message.warning("输入已修改,请重新点击调试按钮")
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
// ==================== 计算属性 ====================
|
|
||||||
// 调试行号相关
|
|
||||||
const currentLine = computed(() => {
|
|
||||||
if (
|
|
||||||
debugData.value &&
|
|
||||||
debugData.value.trace &&
|
|
||||||
debugData.value.trace[currentStep.value]
|
|
||||||
) {
|
|
||||||
const line = debugData.value.trace[currentStep.value].line
|
|
||||||
console.log(`Step ${currentStep.value}: currentLine = ${line}`)
|
|
||||||
return line && line > 0 ? line : undefined
|
|
||||||
}
|
|
||||||
return undefined
|
|
||||||
})
|
|
||||||
|
|
||||||
const nextLine = computed(() => {
|
|
||||||
if (
|
|
||||||
debugData.value &&
|
|
||||||
debugData.value.trace &&
|
|
||||||
debugData.value.trace[currentStep.value + 1]
|
|
||||||
) {
|
|
||||||
const line = debugData.value.trace[currentStep.value + 1].line
|
|
||||||
console.log(`Step ${currentStep.value}: nextLine = ${line}`)
|
|
||||||
return line && line > 0 && line !== currentLine.value ? line : undefined
|
|
||||||
}
|
|
||||||
console.log(`Step ${currentStep.value}: nextLine = undefined (no next step)`)
|
|
||||||
return undefined
|
|
||||||
})
|
|
||||||
|
|
||||||
// 调试信息相关
|
|
||||||
const currentVariables = computed(() => {
|
|
||||||
if (
|
|
||||||
debugData.value &&
|
|
||||||
debugData.value.trace &&
|
|
||||||
debugData.value.trace[currentStep.value]
|
|
||||||
) {
|
|
||||||
return debugData.value.trace[currentStep.value].globals || {}
|
|
||||||
}
|
|
||||||
return {}
|
|
||||||
})
|
|
||||||
|
|
||||||
const currentLineText = computed(() => {
|
|
||||||
if (
|
|
||||||
debugData.value &&
|
|
||||||
debugData.value.trace &&
|
|
||||||
debugData.value.trace[currentStep.value]
|
|
||||||
) {
|
|
||||||
const step = debugData.value.trace[currentStep.value]
|
|
||||||
const isLastStep = currentStep.value === debugData.value.trace.length - 1
|
|
||||||
const eventText = isLastStep ? "" : getEventText(step.event)
|
|
||||||
const stepText = isLastStep
|
|
||||||
? "最后一步"
|
|
||||||
: `当前第${currentStep.value + 1}步`
|
|
||||||
return `${stepText}${eventText}`
|
|
||||||
}
|
|
||||||
return undefined
|
|
||||||
})
|
|
||||||
|
|
||||||
const nextLineText = computed(() => {
|
|
||||||
if (
|
|
||||||
debugData.value &&
|
|
||||||
debugData.value.trace &&
|
|
||||||
debugData.value.trace[currentStep.value + 1]
|
|
||||||
) {
|
|
||||||
const step = debugData.value.trace[currentStep.value + 1]
|
|
||||||
const isNextLastStep =
|
|
||||||
currentStep.value + 1 === debugData.value.trace.length - 1
|
|
||||||
const eventText = isNextLastStep ? "" : getEventText(step.event)
|
|
||||||
const stepText = isNextLastStep ? "最后一步" : `下一步`
|
|
||||||
return `${stepText}${eventText}`
|
|
||||||
}
|
|
||||||
return undefined
|
|
||||||
})
|
|
||||||
|
|
||||||
// ==================== 工具函数 ====================
|
|
||||||
/**
|
|
||||||
* 获取事件类型的中文描述
|
|
||||||
*/
|
|
||||||
function getEventText(event: string): string {
|
|
||||||
switch (event) {
|
|
||||||
case "step_line":
|
|
||||||
return "" // 普通执行不显示额外文字
|
|
||||||
case "call":
|
|
||||||
return "(调用函数)"
|
|
||||||
case "return":
|
|
||||||
return "(函数返回)"
|
|
||||||
case "exception":
|
|
||||||
return "(异常)"
|
|
||||||
case "uncaught_exception":
|
|
||||||
return "(异常)"
|
|
||||||
case "raw_input":
|
|
||||||
return "(等待输入)"
|
|
||||||
default:
|
|
||||||
return event || ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 输出相关
|
|
||||||
const currentOutput = computed(() => {
|
|
||||||
if (
|
|
||||||
debugData.value &&
|
|
||||||
debugData.value.trace &&
|
|
||||||
debugData.value.trace.length > 0
|
|
||||||
) {
|
|
||||||
let outputText = ""
|
|
||||||
|
|
||||||
for (let i = 0; i <= currentStep.value; i++) {
|
|
||||||
const step = debugData.value.trace[i]
|
|
||||||
if (step) {
|
|
||||||
if (step.event === "exception" || step.event === "uncaught_exception") {
|
|
||||||
if (step.exception_msg) {
|
|
||||||
outputText = step.exception_msg
|
|
||||||
}
|
|
||||||
} else if (step.stdout) {
|
|
||||||
outputText = step.stdout
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
outputText = outputText.trimEnd()
|
|
||||||
|
|
||||||
const hasException = debugData.value.trace.some(
|
|
||||||
(step: any) =>
|
|
||||||
step.event === "exception" || step.event === "uncaught_exception",
|
|
||||||
)
|
|
||||||
status.value = hasException ? Status.RuntimeError : Status.Accepted
|
|
||||||
|
|
||||||
output.value = outputText
|
|
||||||
return outputText
|
|
||||||
}
|
|
||||||
return output.value || ""
|
|
||||||
})
|
|
||||||
|
|
||||||
// ==================== 主要功能函数 ====================
|
|
||||||
/**
|
|
||||||
* 复制代码到剪贴板
|
|
||||||
*/
|
|
||||||
function copy() {
|
|
||||||
copyTextToClipboard(code.value)
|
|
||||||
message.success("已经复制好了")
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 开始调试
|
|
||||||
*/
|
|
||||||
async function handleDebug() {
|
|
||||||
showDebug.value = true
|
|
||||||
showFloatingPanel.value = true
|
|
||||||
// 保存调试开始时的代码和输入状态
|
|
||||||
debugStartCode = code.value
|
|
||||||
debugStartInput = input.value
|
|
||||||
|
|
||||||
const inputs = input.value ? input.value.split("\n") : []
|
|
||||||
const res = await debug(code.value, inputs)
|
|
||||||
debugData.value = res.data
|
|
||||||
currentStep.value = 0
|
|
||||||
|
|
||||||
// 检查步骤数量并显示提醒
|
|
||||||
if (res.data.trace && res.data.trace.length > 5000) {
|
|
||||||
message.warning(`超过 5000 步,请优化代码或减少循环次数`)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查最后一步是否为 raw_input 事件
|
|
||||||
if (res.data.trace && res.data.trace.length > 0) {
|
|
||||||
const lastStep = res.data.trace[res.data.trace.length - 1]
|
|
||||||
if (lastStep.event === "raw_input") {
|
|
||||||
message.info("程序正在等待输入,请在输入框输入内容后重新点击调试按钮")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 显示前几个 trace 条目的行号
|
|
||||||
if (res.data.trace) {
|
|
||||||
console.log("First few trace entries:")
|
|
||||||
res.data.trace.slice(0, 5).forEach((entry: any, index: number) => {
|
|
||||||
console.log(` Step ${index}: line ${entry.line}, event: ${entry.event}`)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ==================== 调试控制函数 ====================
|
|
||||||
/**
|
|
||||||
* 跳转到第一步
|
|
||||||
*/
|
|
||||||
function firstStep() {
|
|
||||||
if (debugData.value && debugData.value.trace) {
|
|
||||||
currentStep.value = 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 上一步
|
|
||||||
*/
|
|
||||||
function prevStep() {
|
|
||||||
if (debugData.value && debugData.value.trace && currentStep.value > 0) {
|
|
||||||
currentStep.value--
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 下一步
|
|
||||||
*/
|
|
||||||
function nextStep() {
|
|
||||||
if (
|
|
||||||
debugData.value &&
|
|
||||||
debugData.value.trace &&
|
|
||||||
currentStep.value < debugData.value.trace.length - 1
|
|
||||||
) {
|
|
||||||
currentStep.value++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 跳转到最后一步
|
|
||||||
*/
|
|
||||||
function lastStep() {
|
|
||||||
if (debugData.value && debugData.value.trace) {
|
|
||||||
currentStep.value = debugData.value.trace.length - 1
|
|
||||||
|
|
||||||
// 如果最后一步是 raw_input,显示提醒
|
|
||||||
const lastStep = debugData.value.trace[debugData.value.trace.length - 1]
|
|
||||||
if (lastStep.event === "raw_input") {
|
|
||||||
message.info("程序正在等待输入,请在输入区域输入内容后重新调试")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ==================== UI控制函数 ====================
|
|
||||||
/**
|
|
||||||
* 关闭浮动面板
|
|
||||||
*/
|
|
||||||
function closeFloatingPanel() {
|
|
||||||
showFloatingPanel.value = false
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 关闭调试模式
|
|
||||||
*/
|
|
||||||
function closeDebug() {
|
|
||||||
showDebug.value = false
|
|
||||||
showFloatingPanel.value = false
|
|
||||||
debugData.value = null
|
|
||||||
currentStep.value = 0
|
|
||||||
|
|
||||||
// 清除保存的调试状态
|
|
||||||
debugStartCode = ""
|
|
||||||
debugStartInput = ""
|
|
||||||
|
|
||||||
// 停止自动运行
|
|
||||||
pauseAutoRun()
|
|
||||||
isAutoRunning.value = false
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 自动运行/暂停
|
|
||||||
*/
|
|
||||||
function autoRun() {
|
|
||||||
if (!debugData.value || !debugData.value.trace) return
|
|
||||||
|
|
||||||
if (isAutoRunActive.value) {
|
|
||||||
// 停止自动运行
|
|
||||||
pauseAutoRun()
|
|
||||||
isAutoRunning.value = false
|
|
||||||
} else {
|
|
||||||
// 开始自动运行
|
|
||||||
isAutoRunning.value = true
|
|
||||||
resumeAutoRun()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<!-- 头部工具栏 -->
|
|
||||||
<n-flex align="center" class="header">
|
|
||||||
<!-- 标题和基础操作 -->
|
|
||||||
<Icon icon="streamline-emojis:lemon" :width="24" :height="24" />
|
|
||||||
<span class="title">代码区</span>
|
|
||||||
|
|
||||||
<n-button quaternary type="primary" @click="copy">复制</n-button>
|
|
||||||
<n-button quaternary @click="reset">清空</n-button>
|
|
||||||
<n-button
|
|
||||||
quaternary
|
|
||||||
type="error"
|
|
||||||
:disabled="!code.value"
|
|
||||||
@click="handleDebug"
|
|
||||||
>
|
|
||||||
调试
|
|
||||||
</n-button>
|
|
||||||
|
|
||||||
<!-- 调试控制按钮 -->
|
|
||||||
<template v-if="showDebug">
|
|
||||||
<!-- 步骤控制 -->
|
|
||||||
<n-tooltip>
|
|
||||||
<template #trigger>
|
|
||||||
<n-button text @click="firstStep">
|
|
||||||
<template #icon>
|
|
||||||
<Icon icon="material-symbols:skip-previous" />
|
|
||||||
</template>
|
|
||||||
</n-button>
|
|
||||||
</template>
|
|
||||||
第一步
|
|
||||||
</n-tooltip>
|
|
||||||
|
|
||||||
<n-tooltip>
|
|
||||||
<template #trigger>
|
|
||||||
<n-button text type="primary" @click="prevStep">
|
|
||||||
<template #icon>
|
|
||||||
<Icon icon="tabler:chevron-left" />
|
|
||||||
</template>
|
|
||||||
</n-button>
|
|
||||||
</template>
|
|
||||||
上一步
|
|
||||||
</n-tooltip>
|
|
||||||
|
|
||||||
<n-tooltip>
|
|
||||||
<template #trigger>
|
|
||||||
<n-button
|
|
||||||
:type="isAutoRunning ? 'warning' : 'info'"
|
|
||||||
text
|
|
||||||
@click="autoRun"
|
|
||||||
>
|
|
||||||
<template #icon>
|
|
||||||
<Icon
|
|
||||||
:icon="
|
|
||||||
isAutoRunning
|
|
||||||
? 'material-symbols:pause'
|
|
||||||
: 'material-symbols:play-arrow'
|
|
||||||
"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
</n-button>
|
|
||||||
</template>
|
|
||||||
{{ isAutoRunning ? "暂停自动运行" : "开始自动运行" }}
|
|
||||||
</n-tooltip>
|
|
||||||
|
|
||||||
<n-tooltip>
|
|
||||||
<template #trigger>
|
|
||||||
<n-button type="error" text @click="nextStep">
|
|
||||||
<template #icon>
|
|
||||||
<Icon icon="tabler:chevron-right" />
|
|
||||||
</template>
|
|
||||||
</n-button>
|
|
||||||
</template>
|
|
||||||
下一步
|
|
||||||
</n-tooltip>
|
|
||||||
|
|
||||||
<n-tooltip>
|
|
||||||
<template #trigger>
|
|
||||||
<n-button text @click="lastStep">
|
|
||||||
<template #icon>
|
|
||||||
<Icon icon="material-symbols:skip-next" />
|
|
||||||
</template>
|
|
||||||
</n-button>
|
|
||||||
</template>
|
|
||||||
最后一步
|
|
||||||
</n-tooltip>
|
|
||||||
|
|
||||||
<!-- 面板控制 -->
|
|
||||||
<n-button
|
|
||||||
quaternary
|
|
||||||
type="info"
|
|
||||||
@click="showFloatingPanel = !showFloatingPanel"
|
|
||||||
>
|
|
||||||
{{ showFloatingPanel ? "隐藏面板" : "显示面板" }}
|
|
||||||
</n-button>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<!-- 调试进度条 -->
|
|
||||||
<n-flex
|
|
||||||
v-if="showDebug && debugData && debugData.trace"
|
|
||||||
align="center"
|
|
||||||
class="progress-section"
|
|
||||||
>
|
|
||||||
<n-slider
|
|
||||||
v-model:value="currentStep"
|
|
||||||
:min="0"
|
|
||||||
:max="debugData.trace.length - 1"
|
|
||||||
:step="1"
|
|
||||||
:tooltip="false"
|
|
||||||
class="debug-progress"
|
|
||||||
/>
|
|
||||||
<span class="progress-text">
|
|
||||||
步骤: {{ currentStep + 1 }} / {{ debugData.trace.length }}
|
|
||||||
</span>
|
|
||||||
</n-flex>
|
|
||||||
|
|
||||||
<!-- 关闭调试 -->
|
|
||||||
<n-tooltip v-if="showDebug">
|
|
||||||
<template #trigger>
|
|
||||||
<n-button text type="error" @click="closeDebug">
|
|
||||||
<template #icon>
|
|
||||||
<Icon icon="tabler:x" />
|
|
||||||
</template>
|
|
||||||
</n-button>
|
|
||||||
</template>
|
|
||||||
关闭调试
|
|
||||||
</n-tooltip>
|
|
||||||
</n-flex>
|
|
||||||
|
|
||||||
<!-- 主要内容区域 -->
|
|
||||||
<div class="debug-container">
|
|
||||||
<!-- 代码编辑器 -->
|
|
||||||
<DebugEditor
|
|
||||||
v-model="code.value"
|
|
||||||
:font-size="size"
|
|
||||||
:language="code.language"
|
|
||||||
:current-line="currentLine"
|
|
||||||
:next-line="nextLine"
|
|
||||||
:current-line-text="currentLineText"
|
|
||||||
:next-line-text="nextLineText"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<!-- 调试面板 -->
|
|
||||||
<DebugPanel
|
|
||||||
:visible="showFloatingPanel"
|
|
||||||
:variables="currentVariables"
|
|
||||||
:output="currentOutput"
|
|
||||||
@close="closeFloatingPanel"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<style scoped>
|
|
||||||
/* ==================== 头部样式 ==================== */
|
|
||||||
.header {
|
|
||||||
padding: 12px 20px;
|
|
||||||
height: 60px;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
.title {
|
|
||||||
font-size: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ==================== 主容器样式 ==================== */
|
|
||||||
.debug-container {
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ==================== 进度条样式 ==================== */
|
|
||||||
.progress-section {
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.debug-progress {
|
|
||||||
flex: 1;
|
|
||||||
max-width: 200px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.progress-text {
|
|
||||||
font-size: 12px;
|
|
||||||
color: var(--n-text-color-disabled);
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -8,7 +8,7 @@ import {
|
|||||||
getAIAnalysis,
|
getAIAnalysis,
|
||||||
showAnalysis,
|
showAnalysis,
|
||||||
} from "../composables/analysis"
|
} from "../composables/analysis"
|
||||||
import AnalysisPanel from "./AnalysisPanel.vue"
|
import AnalysisPanel from "../components/AnalysisPanel.vue"
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|||||||
Reference in New Issue
Block a user