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

This commit is contained in:
2025-10-22 11:17:12 +08:00
parent 4306e555bb
commit bc2db54575
2 changed files with 203 additions and 83 deletions

View File

@@ -134,7 +134,7 @@ function closePanel() {
<n-collapse v-if="formattedOutput" :default-expanded-names="['output']"> <n-collapse v-if="formattedOutput" :default-expanded-names="['output']">
<n-collapse-item :title="`输出 (${outputLines} 行)`" name="output"> <n-collapse-item :title="`输出 (${outputLines} 行)`" name="output">
<template #header> <template #header>
<n-flex align="center" :gap="8"> <n-flex align="center">
<n-icon> <n-icon>
<Icon icon="mdi:console" :width="14" :height="14" /> <Icon icon="mdi:console" :width="14" :height="14" />
</n-icon> </n-icon>
@@ -170,7 +170,7 @@ function closePanel() {
font-size: 12px; font-size: 12px;
white-space: pre-wrap; white-space: pre-wrap;
word-break: break-all; word-break: break-all;
max-height: 200px; max-height: 400px;
overflow-y: auto; overflow-y: auto;
display: block; display: block;
} }

View File

@@ -1,22 +1,88 @@
<script lang="ts" setup> <script lang="ts" setup>
// Vue 核心
import { ref, computed, watch } 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 { Icon } from "@iconify/vue"
import { useIntervalFn } from "@vueuse/core"
// 组件
import DebugEditor from "../components/DebugEditor.vue" import DebugEditor from "../components/DebugEditor.vue"
import FloatingPanel from "../components/FloatingPanel.vue" import FloatingPanel from "../components/FloatingPanel.vue"
import { code, input, reset, size, output } from "../composables/code"
import { debug } from "../api"
import { ref, computed } from "vue"
import { Icon } from "@iconify/vue"
// 组合式函数和类型
import { code, input, reset, size, output, status } from "../composables/code"
import { Status } from "../types"
import { debug } from "../api"
// ==================== 响应式状态 ====================
const message = useMessage() const message = useMessage()
// 调试状态
const showDebug = ref(false) const showDebug = ref(false)
const debugData = ref<any>(null) const debugData = ref<any>(null)
const currentStep = ref(0) const currentStep = ref(0)
const showFloatingPanel = ref(false)
const isAutoRunning = ref(false)
const autoRunInterval = ref<number | null>(null)
// 计算当前行和下一步行 // 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(() => { const currentLine = computed(() => {
if ( if (
debugData.value && debugData.value &&
@@ -25,7 +91,6 @@ const currentLine = computed(() => {
) { ) {
const line = debugData.value.trace[currentStep.value].line const line = debugData.value.trace[currentStep.value].line
console.log(`Step ${currentStep.value}: currentLine = ${line}`) console.log(`Step ${currentStep.value}: currentLine = ${line}`)
// 确保行号有效
return line && line > 0 ? line : undefined return line && line > 0 ? line : undefined
} }
return undefined return undefined
@@ -39,14 +104,13 @@ const nextLine = computed(() => {
) { ) {
const line = debugData.value.trace[currentStep.value + 1].line const line = debugData.value.trace[currentStep.value + 1].line
console.log(`Step ${currentStep.value}: nextLine = ${line}`) console.log(`Step ${currentStep.value}: nextLine = ${line}`)
// 确保行号有效且与当前行不同
return line && line > 0 && line !== currentLine.value ? line : undefined return line && line > 0 && line !== currentLine.value ? line : undefined
} }
console.log(`Step ${currentStep.value}: nextLine = undefined (no next step)`) console.log(`Step ${currentStep.value}: nextLine = undefined (no next step)`)
return undefined return undefined
}) })
// 计算当前步骤的变量 // 调试信息相关
const currentVariables = computed(() => { const currentVariables = computed(() => {
if ( if (
debugData.value && debugData.value &&
@@ -58,7 +122,6 @@ const currentVariables = computed(() => {
return {} return {}
}) })
// 计算当前行文字
const currentLineText = computed(() => { const currentLineText = computed(() => {
if ( if (
debugData.value && debugData.value &&
@@ -76,7 +139,6 @@ const currentLineText = computed(() => {
return undefined return undefined
}) })
// 计算下一步行文字
const nextLineText = computed(() => { const nextLineText = computed(() => {
if ( if (
debugData.value && debugData.value &&
@@ -93,20 +155,10 @@ const nextLineText = computed(() => {
return undefined return undefined
}) })
// 检测是否为最后一步且事件为 raw_input // ==================== 工具函数 ====================
const isLastStepRawInput = computed(() => { /**
if ( * 获取事件类型的中文描述
debugData.value && */
debugData.value.trace &&
debugData.value.trace.length > 0
) {
const lastStep = debugData.value.trace[debugData.value.trace.length - 1]
return lastStep.event === "raw_input"
}
return false
})
// 获取事件类型的中文描述
function getEventText(event: string): string { function getEventText(event: string): string {
switch (event) { switch (event) {
case "step_line": case "step_line":
@@ -114,11 +166,11 @@ function getEventText(event: string): string {
case "call": case "call":
return "(调用函数)" return "(调用函数)"
case "return": case "return":
return "(函数返回)" // 更准确地表示函数返回 return "(函数返回)"
case "exception": case "exception":
return "(异常)" return "(异常)"
case "uncaught_exception": case "uncaught_exception":
return "(未捕获异常)" return "(异常)"
case "raw_input": case "raw_input":
return "(等待输入)" return "(等待输入)"
default: default:
@@ -126,25 +178,61 @@ function getEventText(event: string): string {
} }
} }
// 计算当前步骤的输出 // 输出相关
const currentOutput = computed(() => { const currentOutput = computed(() => {
if ( if (
debugData.value && debugData.value &&
debugData.value.trace && debugData.value.trace &&
debugData.value.trace[currentStep.value] debugData.value.trace.length > 0
) { ) {
return debugData.value.trace[currentStep.value].stdout || "" 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 || "" return output.value || ""
}) })
// ==================== 主要功能函数 ====================
/**
* 复制代码到剪贴板
*/
function copy() { function copy() {
copyTextToClipboard(code.value) copyTextToClipboard(code.value)
message.success("已经复制好了") message.success("已经复制好了")
} }
/**
* 开始调试
*/
async function handleDebug() { async function handleDebug() {
showDebug.value = true showDebug.value = true
showFloatingPanel.value = true
// 保存调试开始时的代码和输入状态
debugStartCode = code.value
debugStartInput = input.value
const inputs = input.value ? input.value.split("\n") : [] const inputs = input.value ? input.value.split("\n") : []
const res = await debug(code.value, inputs) const res = await debug(code.value, inputs)
debugData.value = res.data debugData.value = res.data
@@ -172,18 +260,28 @@ async function handleDebug() {
} }
} }
// ==================== 调试控制函数 ====================
/**
* 跳转到第一步
*/
function firstStep() { function firstStep() {
if (debugData.value && debugData.value.trace) { if (debugData.value && debugData.value.trace) {
currentStep.value = 0 currentStep.value = 0
} }
} }
/**
* 上一步
*/
function prevStep() { function prevStep() {
if (debugData.value && debugData.value.trace && currentStep.value > 0) { if (debugData.value && debugData.value.trace && currentStep.value > 0) {
currentStep.value-- currentStep.value--
} }
} }
/**
* 下一步
*/
function nextStep() { function nextStep() {
if ( if (
debugData.value && debugData.value &&
@@ -194,6 +292,9 @@ function nextStep() {
} }
} }
/**
* 跳转到最后一步
*/
function lastStep() { function lastStep() {
if (debugData.value && debugData.value.trace) { if (debugData.value && debugData.value.trace) {
currentStep.value = debugData.value.trace.length - 1 currentStep.value = debugData.value.trace.length - 1
@@ -206,57 +307,59 @@ function lastStep() {
} }
} }
// ==================== UI控制函数 ====================
/**
* 关闭浮动面板
*/
function closeFloatingPanel() { function closeFloatingPanel() {
showFloatingPanel.value = false showFloatingPanel.value = false
} }
/**
* 关闭调试模式
*/
function closeDebug() { function closeDebug() {
showDebug.value = false showDebug.value = false
showFloatingPanel.value = false showFloatingPanel.value = false
debugData.value = null debugData.value = null
currentStep.value = 0 currentStep.value = 0
// 清除保存的调试状态
debugStartCode = ""
debugStartInput = ""
// 停止自动运行 // 停止自动运行
if (autoRunInterval.value) { pauseAutoRun()
clearInterval(autoRunInterval.value)
autoRunInterval.value = null
}
isAutoRunning.value = false isAutoRunning.value = false
} }
/**
* 自动运行/暂停
*/
function autoRun() { function autoRun() {
if (!debugData.value || !debugData.value.trace) return if (!debugData.value || !debugData.value.trace) return
if (isAutoRunning.value) { if (isAutoRunActive.value) {
// 停止自动运行 // 停止自动运行
if (autoRunInterval.value) { pauseAutoRun()
clearInterval(autoRunInterval.value)
autoRunInterval.value = null
}
isAutoRunning.value = false isAutoRunning.value = false
} else { } else {
// 开始自动运行 // 开始自动运行
isAutoRunning.value = true isAutoRunning.value = true
autoRunInterval.value = setInterval(() => { resumeAutoRun()
if (currentStep.value < debugData.value.trace.length - 1) {
currentStep.value++
} else {
// 到达最后一步,停止自动运行
if (autoRunInterval.value) {
clearInterval(autoRunInterval.value)
autoRunInterval.value = null
}
isAutoRunning.value = false
}
}, 500) // 每500毫秒执行一步
} }
} }
</script> </script>
<template> <template>
<!-- 头部工具栏 -->
<n-flex align="center" class="header"> <n-flex align="center" class="header">
<Icon icon="streamline-emojis:lemon" :width="24" :height="24"></Icon> <!-- 标题和基础操作 -->
<Icon icon="streamline-emojis:lemon" :width="24" :height="24" />
<span class="title">代码区</span> <span class="title">代码区</span>
<n-button quaternary type="primary" @click="copy">复制</n-button>
<n-button quaternary @click="reset">清空</n-button>
<n-button <n-button
quaternary quaternary
type="error" type="error"
@@ -265,17 +368,21 @@ function autoRun() {
> >
调试 调试
</n-button> </n-button>
<!-- 调试控制按钮 -->
<template v-if="showDebug"> <template v-if="showDebug">
<!-- 步骤控制 -->
<n-tooltip> <n-tooltip>
<template #trigger> <template #trigger>
<n-button text @click="firstStep"> <n-button text @click="firstStep">
<template #icon> <template #icon>
<Icon icon="tabler:player-skip-back" /> <Icon icon="material-symbols:skip-previous" />
</template> </template>
</n-button> </n-button>
</template> </template>
第一步 第一步
</n-tooltip> </n-tooltip>
<n-tooltip> <n-tooltip>
<template #trigger> <template #trigger>
<n-button text type="primary" @click="prevStep"> <n-button text type="primary" @click="prevStep">
@@ -286,17 +393,20 @@ function autoRun() {
</template> </template>
上一步 上一步
</n-tooltip> </n-tooltip>
<n-tooltip> <n-tooltip>
<template #trigger> <template #trigger>
<n-button <n-button
:type="isAutoRunning ? 'warning' : 'success'" :type="isAutoRunning ? 'warning' : 'info'"
text text
@click="autoRun" @click="autoRun"
> >
<template #icon> <template #icon>
<Icon <Icon
:icon=" :icon="
isAutoRunning ? 'tabler:player-pause' : 'tabler:player-play' isAutoRunning
? 'material-symbols:pause'
: 'material-symbols:play-arrow'
" "
/> />
</template> </template>
@@ -304,6 +414,7 @@ function autoRun() {
</template> </template>
{{ isAutoRunning ? "暂停自动运行" : "开始自动运行" }} {{ isAutoRunning ? "暂停自动运行" : "开始自动运行" }}
</n-tooltip> </n-tooltip>
<n-tooltip> <n-tooltip>
<template #trigger> <template #trigger>
<n-button type="error" text @click="nextStep"> <n-button type="error" text @click="nextStep">
@@ -314,41 +425,32 @@ function autoRun() {
</template> </template>
下一步 下一步
</n-tooltip> </n-tooltip>
<n-tooltip> <n-tooltip>
<template #trigger> <template #trigger>
<n-button text @click="lastStep"> <n-button text @click="lastStep">
<template #icon> <template #icon>
<Icon icon="tabler:player-skip-forward" /> <Icon icon="material-symbols:skip-next" />
</template> </template>
</n-button> </n-button>
</template> </template>
最后一步 最后一步
</n-tooltip> </n-tooltip>
<n-tooltip>
<template #trigger> <!-- 面板控制 -->
<n-button text type="error" @click="closeDebug"> <n-button
<template #icon> quaternary
<Icon icon="tabler:x" /> type="info"
</template> @click="showFloatingPanel = !showFloatingPanel"
</n-button> >
</template> {{ showFloatingPanel ? "隐藏面板" : "显示面板" }}
关闭调试 </n-button>
</n-tooltip>
</template> </template>
<n-button quaternary type="primary" @click="copy">复制</n-button>
<n-button quaternary @click="reset">清空</n-button>
<n-button
v-if="showDebug"
quaternary
type="info"
@click="showFloatingPanel = !showFloatingPanel"
>
{{ showFloatingPanel ? "隐藏面板" : "显示面板" }}
</n-button>
<!-- 调试进度条 --> <!-- 调试进度条 -->
<n-flex <n-flex
align="center"
v-if="showDebug && debugData && debugData.trace" v-if="showDebug && debugData && debugData.trace"
align="center"
class="progress-section" class="progress-section"
> >
<n-slider <n-slider
@@ -363,9 +465,23 @@ function autoRun() {
步骤: {{ currentStep + 1 }} / {{ debugData.trace.length }} 步骤: {{ currentStep + 1 }} / {{ debugData.trace.length }}
</span> </span>
</n-flex> </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> </n-flex>
<!-- 主要内容区域 -->
<div class="debug-container"> <div class="debug-container">
<!-- 代码编辑器 -->
<DebugEditor <DebugEditor
v-model="code.value" v-model="code.value"
:font-size="size" :font-size="size"
@@ -376,7 +492,7 @@ function autoRun() {
:next-line-text="nextLineText" :next-line-text="nextLineText"
/> />
<!-- 浮动面板 --> <!-- 浮动调试面板 -->
<FloatingPanel <FloatingPanel
:visible="showFloatingPanel" :visible="showFloatingPanel"
:variables="currentVariables" :variables="currentVariables"
@@ -386,21 +502,25 @@ function autoRun() {
</div> </div>
</template> </template>
<style scoped> <style scoped>
/* ==================== 头部样式 ==================== */
.header { .header {
padding: 12px 20px; padding: 12px 20px;
height: 60px; height: 60px;
box-sizing: border-box; box-sizing: border-box;
} }
.title { .title {
font-size: 16px; font-size: 16px;
} }
/* ==================== 主容器样式 ==================== */
.debug-container { .debug-container {
position: relative; position: relative;
} }
/* ==================== 进度条样式 ==================== */
.progress-section { .progress-section {
flex: 1; flex: 1;
margin-left: 12px;
} }
.debug-progress { .debug-progress {