feat: fix agent logger display
This commit is contained in:
@@ -78,7 +78,7 @@ async function handleSelect(node: FileNode) {
|
||||
selectedPath.value = node.path
|
||||
// 更新 URL 使用 query 参数
|
||||
router.replace({ name: 'editor', query: { file: node.path } })
|
||||
|
||||
|
||||
if (node.isDir) {
|
||||
if (expandedDirs.value.has(node.path)) {
|
||||
expandedDirs.value.delete(node.path)
|
||||
@@ -176,24 +176,24 @@ async function deleteItem() {
|
||||
|
||||
async function runScript() {
|
||||
if (!selectedFile.value) return
|
||||
|
||||
|
||||
// 获取文件所在目录和文件名
|
||||
const parts = selectedFile.value.split('/')
|
||||
const fileName = parts.pop() || selectedFile.value
|
||||
const dirPath = parts.length > 0 ? parts.join('/') : ''
|
||||
|
||||
|
||||
// 根据文件扩展名确定运行命令
|
||||
const ext = fileName.split('.').pop()?.toLowerCase() || ''
|
||||
const runner = FILE_RUNNERS[ext]
|
||||
const cmd = runner ? `${runner} ${fileName}` : `./${fileName}`
|
||||
|
||||
|
||||
// 构建完整命令
|
||||
if (dirPath) {
|
||||
runCommand.value = `cd ${PATHS.SCRIPTS_DIR}/${dirPath} && ${cmd}`
|
||||
} else {
|
||||
runCommand.value = `cd ${PATHS.SCRIPTS_DIR} && ${cmd}`
|
||||
}
|
||||
|
||||
|
||||
showTerminalDialog.value = true
|
||||
// 等待 DOM 更新后初始化终端,增加延迟确保 Dialog 完全渲染
|
||||
await nextTick()
|
||||
@@ -210,6 +210,21 @@ function closeTerminal() {
|
||||
}, 300)
|
||||
}
|
||||
|
||||
async function handleDownload(path: string) {
|
||||
try {
|
||||
const url = api.files.download(path)
|
||||
const a = document.createElement('a')
|
||||
a.href = url
|
||||
a.download = path.split('/').pop() || 'file'
|
||||
document.body.appendChild(a)
|
||||
a.click()
|
||||
document.body.removeChild(a)
|
||||
toast.success('已发起下载')
|
||||
} catch (error: any) {
|
||||
toast.error('下载出错: ' + (error.message || '未知错误'))
|
||||
}
|
||||
}
|
||||
|
||||
async function handleMove(oldPath: string, newPath: string) {
|
||||
try {
|
||||
await api.files.rename(oldPath, newPath)
|
||||
@@ -362,24 +377,17 @@ onUnmounted(() => {
|
||||
<Plus class="h-3 w-3" />
|
||||
</Button>
|
||||
</div>
|
||||
<input ref="archiveInputRef" type="file" accept=".zip,.tar,.gz,.tgz" class="hidden" @change="handleArchiveUpload" />
|
||||
<input ref="archiveInputRef" type="file" accept=".zip,.tar,.gz,.tgz" class="hidden"
|
||||
@change="handleArchiveUpload" />
|
||||
<input ref="filesInputRef" type="file" multiple class="hidden" @change="handleFilesUpload" />
|
||||
</div>
|
||||
<div class="flex-1 overflow-auto p-1">
|
||||
<div v-if="fileTree.length === 0" class="text-xs text-muted-foreground text-center py-4">
|
||||
暂无文件
|
||||
</div>
|
||||
<FileTreeNode
|
||||
v-for="node in fileTree"
|
||||
:key="node.path"
|
||||
:node="node"
|
||||
:expanded-dirs="expandedDirs"
|
||||
:selected-path="selectedPath"
|
||||
@select="handleSelect"
|
||||
@delete="confirmDelete"
|
||||
@create="handleCreate"
|
||||
@move="handleMove"
|
||||
/>
|
||||
<FileTreeNode v-for="node in fileTree" :key="node.path" :node="node" :expanded-dirs="expandedDirs"
|
||||
:selected-path="selectedPath" @select="handleSelect" @delete="confirmDelete" @create="handleCreate"
|
||||
@download-file="handleDownload" @move="handleMove" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -391,11 +399,13 @@ onUnmounted(() => {
|
||||
<span v-if="hasChanges" class="text-orange-500 ml-1">●</span>
|
||||
</span>
|
||||
<div v-if="selectedFile" class="flex gap-1 shrink-0">
|
||||
<Button v-if="!isEditMode" variant="ghost" size="sm" class="h-6 text-xs gap-1 px-2" @click="isEditMode = true">
|
||||
<Button v-if="!isEditMode" variant="ghost" size="sm" class="h-6 text-xs gap-1 px-2"
|
||||
@click="isEditMode = true">
|
||||
<Pencil class="h-3 w-3" /> <span class="hidden sm:inline">编辑</span>
|
||||
</Button>
|
||||
<template v-else>
|
||||
<Button variant="ghost" size="sm" class="h-6 text-xs gap-1 px-2" @click="isEditMode = false; fileContent = originalContent">
|
||||
<Button variant="ghost" size="sm" class="h-6 text-xs gap-1 px-2"
|
||||
@click="isEditMode = false; fileContent = originalContent">
|
||||
<Eye class="h-3 w-3" /> <span class="hidden sm:inline">查看</span>
|
||||
</Button>
|
||||
<Button variant="ghost" size="sm" class="h-6 text-xs gap-1 px-2" :disabled="!hasChanges" @click="saveFile">
|
||||
@@ -408,12 +418,8 @@ onUnmounted(() => {
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<vue-monaco-editor
|
||||
v-if="selectedFile"
|
||||
v-model:value="fileContent"
|
||||
:language="getLanguage(selectedFile)"
|
||||
theme="vs-dark"
|
||||
:options="{
|
||||
<vue-monaco-editor v-if="selectedFile" v-model:value="fileContent" :language="getLanguage(selectedFile)"
|
||||
theme="vs-dark" :options="{
|
||||
minimap: { enabled: false },
|
||||
fontSize: editorFontSize,
|
||||
lineNumbers: 'on',
|
||||
@@ -429,9 +435,7 @@ onUnmounted(() => {
|
||||
insertSpaces: true,
|
||||
readOnly: !isEditMode,
|
||||
domReadOnly: !isEditMode
|
||||
}"
|
||||
@mount="handleEditorMount"
|
||||
/>
|
||||
}" @mount="handleEditorMount" />
|
||||
<div v-else class="h-full flex items-center justify-center text-muted-foreground text-sm">
|
||||
<span class="lg:hidden">从上方选择文件开始编辑</span>
|
||||
<span class="hidden lg:inline">从左侧选择文件开始编辑</span>
|
||||
@@ -439,7 +443,7 @@ onUnmounted(() => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
<!-- 新建对话框 -->
|
||||
<Dialog v-model:open="showCreateDialog">
|
||||
@@ -482,28 +486,28 @@ onUnmounted(() => {
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel class="h-7 text-xs">取消</AlertDialogCancel>
|
||||
<AlertDialogAction class="h-7 text-xs bg-destructive text-white hover:bg-destructive/90" @click="deleteItem">删除</AlertDialogAction>
|
||||
<AlertDialogAction class="h-7 text-xs bg-destructive text-white hover:bg-destructive/90" @click="deleteItem">
|
||||
删除
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
|
||||
<!-- 终端弹窗 -->
|
||||
<Dialog v-model:open="showTerminalDialog">
|
||||
<DialogContent class="w-[calc(100%-2rem)] sm:max-w-3xl h-[60vh] sm:h-[70vh] flex flex-col p-0 overflow-hidden !bg-[#1e1e1e] border-[#3c3c3c]" :show-close-button="false">
|
||||
<DialogContent
|
||||
class="w-[calc(100%-2rem)] sm:max-w-3xl h-[60vh] sm:h-[70vh] flex flex-col p-0 overflow-hidden !bg-[#1e1e1e] border-[#3c3c3c]"
|
||||
:show-close-button="false">
|
||||
<div class="flex items-center justify-between px-3 sm:px-4 py-2 border-b border-[#3c3c3c]">
|
||||
<span class="text-xs sm:text-sm font-medium text-gray-300">运行脚本</span>
|
||||
<Button variant="ghost" size="icon" class="h-6 w-6 text-gray-400 hover:text-white hover:bg-white/10" @click="closeTerminal">
|
||||
<Button variant="ghost" size="icon" class="h-6 w-6 text-gray-400 hover:text-white hover:bg-white/10"
|
||||
@click="closeTerminal">
|
||||
<X class="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
<div class="flex-1 overflow-hidden">
|
||||
<XTerminal
|
||||
v-if="showTerminalDialog"
|
||||
ref="terminalRef"
|
||||
:font-size="isSmallScreen ? 12 : 13"
|
||||
:initial-command="runCommand"
|
||||
:auto-connect="false"
|
||||
/>
|
||||
<XTerminal v-if="showTerminalDialog" ref="terminalRef" :font-size="isSmallScreen ? 12 : 13"
|
||||
:initial-command="runCommand" :auto-connect="false" />
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
@@ -511,5 +515,4 @@ onUnmounted(() => {
|
||||
</template>
|
||||
|
||||
|
||||
<style scoped>
|
||||
</style>
|
||||
<style scoped></style>
|
||||
|
||||
@@ -82,7 +82,7 @@ async function loadTree() {
|
||||
loading.value = true
|
||||
try {
|
||||
fileTree.value = await api.files.tree()
|
||||
|
||||
|
||||
// 仅在首次加载时从 URL 恢复状态
|
||||
if (expandedDirs.value.size === 0 && selectedFile.value === null && selectedDir.value === null) {
|
||||
// 从 URL 恢复展开的目录
|
||||
@@ -90,14 +90,14 @@ async function loadTree() {
|
||||
if (dirsParam && typeof dirsParam === 'string') {
|
||||
dirsParam.split(',').forEach(dir => expandedDirs.value.add(dir))
|
||||
}
|
||||
|
||||
|
||||
// 从 URL 恢复选中的文件夹
|
||||
const dirParam = route.query.dir
|
||||
if (dirParam && typeof dirParam === 'string') {
|
||||
selectedDir.value = dirParam
|
||||
expandedDirs.value.add(dirParam)
|
||||
}
|
||||
|
||||
|
||||
// 从 URL 加载文件
|
||||
const fileParam = route.query.file
|
||||
if (fileParam && typeof fileParam === 'string') {
|
||||
@@ -144,13 +144,13 @@ async function handleSelect(node: FileNode) {
|
||||
toggleDir(node.path)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
if (hasChanges.value) {
|
||||
pendingNode.value = node
|
||||
showUnsavedDialog.value = true
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
await selectFile(node)
|
||||
}
|
||||
|
||||
@@ -251,6 +251,22 @@ async function handleDelete() {
|
||||
deletePath.value = null
|
||||
}
|
||||
|
||||
async function handleDownload(path: string) {
|
||||
try {
|
||||
const url = api.files.download(path)
|
||||
// 使用后端直接下载接口
|
||||
const a = document.createElement('a')
|
||||
a.href = url
|
||||
a.download = path.split('/').pop() || 'file'
|
||||
document.body.appendChild(a)
|
||||
a.click()
|
||||
document.body.removeChild(a)
|
||||
toast.success('已发起下载')
|
||||
} catch (error: any) {
|
||||
toast.error('下载出错: ' + (error.message || '未知错误'))
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(loadTree)
|
||||
</script>
|
||||
|
||||
@@ -272,20 +288,14 @@ onMounted(loadTree)
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="flex-1 overflow-auto p-1">
|
||||
<div v-if="fileTree.length === 0" class="text-xs text-muted-foreground text-center py-4">
|
||||
暂无文件
|
||||
</div>
|
||||
<FileTreeNode
|
||||
v-for="node in fileTree"
|
||||
:key="node.path"
|
||||
:node="node"
|
||||
:expanded-dirs="expandedDirs"
|
||||
:selected-path="selectedFile || selectedDir"
|
||||
@select="handleSelect"
|
||||
@delete="confirmDeleteFile"
|
||||
/>
|
||||
<FileTreeNode v-for="node in fileTree" :key="node.path" :node="node" :expanded-dirs="expandedDirs"
|
||||
:selected-path="selectedFile || selectedDir" @select="handleSelect" @delete="confirmDeleteFile"
|
||||
@download-file="handleDownload" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -296,24 +306,15 @@ onMounted(loadTree)
|
||||
<span class="text-xs font-medium truncate">{{ selectedFile || '未选择文件' }}</span>
|
||||
<span v-if="hasChanges" class="text-xs text-orange-500 shrink-0">● 未保存</span>
|
||||
</div>
|
||||
<Button
|
||||
v-if="selectedFile"
|
||||
size="sm"
|
||||
class="h-6 text-xs gap-1 shrink-0"
|
||||
:disabled="!hasChanges || saving"
|
||||
@click="saveFile"
|
||||
>
|
||||
<Button v-if="selectedFile" size="sm" class="h-6 text-xs gap-1 shrink-0" :disabled="!hasChanges || saving"
|
||||
@click="saveFile">
|
||||
<Save class="h-3 w-3" />
|
||||
<span class="hidden sm:inline">{{ saving ? '保存中...' : '保存' }}</span>
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="flex-1">
|
||||
<VueMonacoEditor
|
||||
v-if="selectedFile"
|
||||
v-model:value="fileContent"
|
||||
:language="editorLanguage"
|
||||
theme="vs-dark"
|
||||
<VueMonacoEditor v-if="selectedFile" v-model:value="fileContent" :language="editorLanguage" theme="vs-dark"
|
||||
:options="{
|
||||
minimap: { enabled: false },
|
||||
fontSize: 13,
|
||||
@@ -322,10 +323,7 @@ onMounted(loadTree)
|
||||
automaticLayout: true,
|
||||
tabSize: 2,
|
||||
wordWrap: 'on'
|
||||
}"
|
||||
style="height: 100%"
|
||||
@mount="handleEditorMount"
|
||||
/>
|
||||
}" style="height: 100%" @mount="handleEditorMount" />
|
||||
<div v-else class="h-full flex items-center justify-center text-muted-foreground text-sm">
|
||||
选择一个文件开始编辑
|
||||
</div>
|
||||
@@ -344,12 +342,8 @@ onMounted(loadTree)
|
||||
<div v-if="selectedDir" class="text-xs text-muted-foreground">
|
||||
位置: {{ selectedDir }}/
|
||||
</div>
|
||||
<Input
|
||||
v-model="createName"
|
||||
class="h-8 text-xs"
|
||||
:placeholder="createType === 'file' ? 'example.js' : 'folder-name'"
|
||||
@keyup.enter="createItem"
|
||||
/>
|
||||
<Input v-model="createName" class="h-8 text-xs"
|
||||
:placeholder="createType === 'file' ? 'example.js' : 'folder-name'" @keyup.enter="createItem" />
|
||||
<div v-if="createName" class="text-xs text-muted-foreground">
|
||||
完整路径: {{ createFullPath }}
|
||||
</div>
|
||||
@@ -369,7 +363,8 @@ onMounted(loadTree)
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel class="h-7 text-xs">取消</AlertDialogCancel>
|
||||
<AlertDialogAction class="h-7 text-xs bg-destructive text-white hover:bg-destructive/90" @click="handleDelete">删除</AlertDialogAction>
|
||||
<AlertDialogAction class="h-7 text-xs bg-destructive text-white hover:bg-destructive/90"
|
||||
@click="handleDelete">删除</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
|
||||
Reference in New Issue
Block a user