chore: adjust zip upload logical #90
This commit is contained in:
@@ -13,6 +13,8 @@ import { PATHS, FILE_RUNNERS } from '@/constants'
|
|||||||
import FileSidebar from './components/FileSidebar.vue'
|
import FileSidebar from './components/FileSidebar.vue'
|
||||||
import RunConfigDialog from './components/RunConfigDialog.vue'
|
import RunConfigDialog from './components/RunConfigDialog.vue'
|
||||||
import FileActionDialogs from './components/FileActionDialogs.vue'
|
import FileActionDialogs from './components/FileActionDialogs.vue'
|
||||||
|
import BaihuDialog from '@/components/ui/BaihuDialog.vue'
|
||||||
|
import { AlertCircle } from 'lucide-vue-next'
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
@@ -31,6 +33,12 @@ const isRefreshing = ref(false)
|
|||||||
const isEditMode = ref(false)
|
const isEditMode = ref(false)
|
||||||
const hasChanges = computed(() => fileContent.value !== originalContent.value)
|
const hasChanges = computed(() => fileContent.value !== originalContent.value)
|
||||||
|
|
||||||
|
const confirmLeave = ref({
|
||||||
|
show: false,
|
||||||
|
path: '',
|
||||||
|
onConfirm: () => {}
|
||||||
|
})
|
||||||
|
|
||||||
// Component Refs
|
// Component Refs
|
||||||
const dialogsRef = ref<InstanceType<typeof FileActionDialogs> | null>(null)
|
const dialogsRef = ref<InstanceType<typeof FileActionDialogs> | null>(null)
|
||||||
const terminalRef = ref<InstanceType<typeof XTerminal> | null>(null)
|
const terminalRef = ref<InstanceType<typeof XTerminal> | null>(null)
|
||||||
@@ -166,7 +174,17 @@ async function handleSelect(node: FileNode) {
|
|||||||
expandedDirs.value = new Set(expandedDirs.value)
|
expandedDirs.value = new Set(expandedDirs.value)
|
||||||
selectedFile.value = null
|
selectedFile.value = null
|
||||||
} else {
|
} else {
|
||||||
if (hasChanges.value && !confirm('当前文件有未保存的更改,是否放弃?')) return
|
if (hasChanges.value) {
|
||||||
|
confirmLeave.value = {
|
||||||
|
show: true,
|
||||||
|
path: node.path,
|
||||||
|
onConfirm: () => {
|
||||||
|
originalContent.value = fileContent.value // 假装保存或标记为已确认放弃
|
||||||
|
loadFile(node.path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
await loadFile(node.path)
|
await loadFile(node.path)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -528,5 +546,28 @@ onUnmounted(() => {
|
|||||||
</div>
|
</div>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
||||||
|
<BaihuDialog v-model:open="confirmLeave.show" title="未保存的更改" icon="AlertCircle" size="sm">
|
||||||
|
<div class="flex flex-col sm:flex-row items-center sm:items-start gap-4 p-1">
|
||||||
|
<div class="h-12 w-12 rounded-full bg-yellow-500/10 flex items-center justify-center shrink-0">
|
||||||
|
<AlertCircle class="h-6 w-6 text-yellow-500" />
|
||||||
|
</div>
|
||||||
|
<div class="flex-1 text-center sm:text-left">
|
||||||
|
<p class="text-sm text-foreground/90 leading-relaxed font-medium">文件尚未保存</p>
|
||||||
|
<p class="text-[13px] text-muted-foreground mt-1">
|
||||||
|
当前文件有未保存的更改。如果现在离开,您的更改将会丢失。
|
||||||
|
</p>
|
||||||
|
<div class="mt-3 px-2 py-1.5 bg-muted/50 rounded text-[11px] font-mono text-muted-foreground break-all border border-muted">
|
||||||
|
{{ confirmLeave.path }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<template #footer>
|
||||||
|
<div class="flex flex-col-reverse sm:flex-row gap-2 w-full sm:w-auto mt-2 sm:mt-0">
|
||||||
|
<Button variant="outline" @click="confirmLeave.show = false" class="w-full sm:w-24">继续编辑</Button>
|
||||||
|
<Button variant="destructive" @click="confirmLeave.show = false; confirmLeave.onConfirm()" class="w-full sm:w-auto px-6">放弃更改</Button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</BaihuDialog>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from 'vue'
|
import { ref, computed } from 'vue'
|
||||||
import { Button } from '@/components/ui/button'
|
import { Button } from '@/components/ui/button'
|
||||||
import { RefreshCw, FolderUp, Upload, Plus } from 'lucide-vue-next'
|
import { RefreshCw, FolderUp, Upload, Plus, AlertCircle } from 'lucide-vue-next'
|
||||||
import FileTreeNode from '@/components/FileTreeNode.vue'
|
import FileTreeNode from '@/components/FileTreeNode.vue'
|
||||||
|
import BaihuDialog from '@/components/ui/BaihuDialog.vue'
|
||||||
import { type FileNode } from '@/api'
|
import { type FileNode } from '@/api'
|
||||||
|
|
||||||
defineProps<{
|
const props = defineProps<{
|
||||||
fileTree: FileNode[]
|
fileTree: FileNode[]
|
||||||
expandedDirs: Set<string>
|
expandedDirs: Set<string>
|
||||||
selectedPath: string | null
|
selectedPath: string | null
|
||||||
@@ -29,6 +30,45 @@ const archiveInputRef = ref<HTMLInputElement | null>(null)
|
|||||||
const filesInputRef = ref<HTMLInputElement | null>(null)
|
const filesInputRef = ref<HTMLInputElement | null>(null)
|
||||||
const uploadTargetDir = ref('')
|
const uploadTargetDir = ref('')
|
||||||
|
|
||||||
|
// 确认框状态
|
||||||
|
const confirmUpload = ref<{
|
||||||
|
show: boolean
|
||||||
|
title: string
|
||||||
|
message: string
|
||||||
|
onConfirm: () => void
|
||||||
|
}>({
|
||||||
|
show: false,
|
||||||
|
title: '',
|
||||||
|
message: '',
|
||||||
|
onConfirm: () => {}
|
||||||
|
})
|
||||||
|
|
||||||
|
const currentTargetDir = computed(() => {
|
||||||
|
if (!props.selectedPath) return ''
|
||||||
|
|
||||||
|
// 深度优先搜索以确定选中的是目录还是文件
|
||||||
|
const findNode = (nodes: FileNode[], path: string): FileNode | null => {
|
||||||
|
for (const n of nodes) {
|
||||||
|
if (n.path === path) return n
|
||||||
|
if (n.children) {
|
||||||
|
const found = findNode(n.children, path)
|
||||||
|
if (found) return found
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const node = findNode(props.fileTree, props.selectedPath)
|
||||||
|
if (node && node.isDir) {
|
||||||
|
return node.path
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果选中的是文件,则返回它所在的目录
|
||||||
|
const parts = props.selectedPath.split('/')
|
||||||
|
parts.pop() // 移除文件名
|
||||||
|
return parts.join('/')
|
||||||
|
})
|
||||||
|
|
||||||
function triggerArchiveUpload(targetDir = '') {
|
function triggerArchiveUpload(targetDir = '') {
|
||||||
uploadTargetDir.value = targetDir
|
uploadTargetDir.value = targetDir
|
||||||
if (archiveInputRef.value) archiveInputRef.value.click()
|
if (archiveInputRef.value) archiveInputRef.value.click()
|
||||||
@@ -42,7 +82,17 @@ function triggerFilesUpload(targetDir = '') {
|
|||||||
function handleArchiveUpload(e: Event) {
|
function handleArchiveUpload(e: Event) {
|
||||||
const input = e.target as HTMLInputElement
|
const input = e.target as HTMLInputElement
|
||||||
const file = input.files?.[0]
|
const file = input.files?.[0]
|
||||||
if (file) emit('uploadArchive', file, uploadTargetDir.value)
|
if (file) {
|
||||||
|
const dirName = uploadTargetDir.value || '根目录'
|
||||||
|
confirmUpload.value = {
|
||||||
|
show: true,
|
||||||
|
title: '确认导入压缩包',
|
||||||
|
message: `即将把压缩包 [${file.name}] 导入到目录 [ ${dirName} ]。确认导入吗?`,
|
||||||
|
onConfirm: () => {
|
||||||
|
emit('uploadArchive', file, uploadTargetDir.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
input.value = ''
|
input.value = ''
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,14 +100,22 @@ function handleFilesUpload(e: Event) {
|
|||||||
const input = e.target as HTMLInputElement
|
const input = e.target as HTMLInputElement
|
||||||
const files = input.files
|
const files = input.files
|
||||||
if (files && files.length > 0) {
|
if (files && files.length > 0) {
|
||||||
const paths: string[] = []
|
const dirName = uploadTargetDir.value || '根目录'
|
||||||
for (let i = 0; i < files.length; i++) {
|
confirmUpload.value = {
|
||||||
const f = files.item(i)
|
show: true,
|
||||||
if (f) {
|
title: '确认上传文件',
|
||||||
paths.push((f as any).webkitRelativePath || f.name)
|
message: `即将把 ${files.length} 个文件/文件夹上传到目录 [ ${dirName} ]。确认上传吗?`,
|
||||||
}
|
onConfirm: () => {
|
||||||
|
const paths: string[] = []
|
||||||
|
for (let i = 0; i < files.length; i++) {
|
||||||
|
const f = files.item(i)
|
||||||
|
if (f) {
|
||||||
|
paths.push((f as any).webkitRelativePath || f.name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
emit('uploadFiles', files, paths, uploadTargetDir.value)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
emit('uploadFiles', files, paths, uploadTargetDir.value)
|
|
||||||
}
|
}
|
||||||
input.value = ''
|
input.value = ''
|
||||||
}
|
}
|
||||||
@@ -71,13 +129,13 @@ function handleFilesUpload(e: Event) {
|
|||||||
<Button variant="ghost" size="icon" class="h-6 w-6" @click="emit('refresh')" :disabled="isRefreshing" title="刷新">
|
<Button variant="ghost" size="icon" class="h-6 w-6" @click="emit('refresh')" :disabled="isRefreshing" title="刷新">
|
||||||
<RefreshCw class="h-3 w-3" :class="{ 'animate-spin': isRefreshing }" />
|
<RefreshCw class="h-3 w-3" :class="{ 'animate-spin': isRefreshing }" />
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant="ghost" size="icon" class="h-6 w-6" @click="triggerFilesUpload('')" title="上传文件/文件夹(放在根目录)">
|
<Button variant="ghost" size="icon" class="h-6 w-6" @click="triggerFilesUpload(currentTargetDir)" :title="currentTargetDir ? `上传到: ${currentTargetDir}` : '上传到根目录'">
|
||||||
<FolderUp class="h-3 w-3" />
|
<FolderUp class="h-3 w-3" />
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant="ghost" size="icon" class="h-6 w-6" @click="triggerArchiveUpload('')" title="导入压缩包(放在根目录)">
|
<Button variant="ghost" size="icon" class="h-6 w-6" @click="triggerArchiveUpload(currentTargetDir)" :title="currentTargetDir ? `导入压缩包到: ${currentTargetDir}` : '导入压缩包到根目录'">
|
||||||
<Upload class="h-3 w-3" />
|
<Upload class="h-3 w-3" />
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant="ghost" size="icon" class="h-6 w-6" @click="emit('create', '')" title="新建">
|
<Button variant="ghost" size="icon" class="h-6 w-6" @click="emit('create', currentTargetDir)" :title="currentTargetDir ? `在 ${currentTargetDir} 中新建` : '在根目录新建'">
|
||||||
<Plus class="h-3 w-3" />
|
<Plus class="h-3 w-3" />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
@@ -98,5 +156,26 @@ function handleFilesUpload(e: Event) {
|
|||||||
@rename="p => emit('rename', p)"
|
@rename="p => emit('rename', p)"
|
||||||
@duplicate="p => emit('duplicate', p)" />
|
@duplicate="p => emit('duplicate', p)" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- 上传确认对话框 -->
|
||||||
|
<BaihuDialog v-model:open="confirmUpload.show" :title="confirmUpload.title" icon="Upload" size="sm">
|
||||||
|
<div class="flex flex-col sm:flex-row items-center sm:items-start gap-4 p-1">
|
||||||
|
<div class="h-12 w-12 rounded-full bg-primary/10 flex items-center justify-center shrink-0">
|
||||||
|
<AlertCircle class="h-6 w-6 text-primary" />
|
||||||
|
</div>
|
||||||
|
<div class="flex-1 text-center sm:text-left">
|
||||||
|
<p class="text-sm text-foreground/90 leading-relaxed font-medium">确认操作?</p>
|
||||||
|
<p class="text-[13px] text-muted-foreground mt-1 break-all">
|
||||||
|
{{ confirmUpload.message }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<template #footer>
|
||||||
|
<div class="flex flex-col-reverse sm:flex-row gap-2 w-full sm:w-auto mt-2 sm:mt-0">
|
||||||
|
<Button variant="outline" @click="confirmUpload.show = false" class="w-full sm:w-24">取消</Button>
|
||||||
|
<Button @click="confirmUpload.show = false; confirmUpload.onConfirm()" class="w-full sm:w-auto px-6">立即开始</Button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</BaihuDialog>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
Reference in New Issue
Block a user