Files
Nexus/frontend/src/components/files/FilesList.vue
T
Your Name 6cb591212d
Nexus CI/CD / test (push) Waiting to run
Nexus CI/CD / deploy (push) Blocked by required conditions
Nexus Pre-commit Checks / quick-check (push) Waiting to run
refactor(files): P2 split UI into components with page context inject
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-02 05:14:13 +08:00

235 lines
7.5 KiB
Vue

<script setup lang="ts">
import { useFilesPageContext } from '@/composables/files/filesPageContext'
const p = useFilesPageContext()
</script>
<template>
<v-card
elevation="0"
border
rounded="lg"
:class="{ 'files-drop-active': p.dropActive }"
@dragenter.prevent="p.dropActive = true"
@dragover.prevent="p.dropActive = true"
@dragleave.prevent="p.dropActive = false"
@drop.prevent="p.onDropFiles"
>
<v-data-table
v-model="p.selectedFiles"
:items="p.displayedFiles"
:headers="p.fileHeaders"
:loading="p.loading"
show-select
return-object
hover
density="comfortable"
item-value="name"
:row-props="p.fileRowProps"
>
<template #item.name="{ item }">
<div class="d-flex align-center ga-2 min-w-0">
<v-icon :color="p.entryIconColor(item)" size="20">
{{ p.entryIcon(item) }}
</v-icon>
<v-icon
v-if="p.isEntryLikelyUnreadable(item, p.sshUserForFiles)"
size="18"
color="warning"
title="当前 SSH 用户可能无法读取"
>
mdi-shield-lock
</v-icon>
<div class="min-w-0">
<span :class="{ 'font-weight-medium': item.type === 'directory' }">{{ item.name }}</span>
<div
v-if="item.type === 'symlink' && item.link_target"
class="text-caption text-medium-emphasis text-truncate"
>
{{ item.link_target }}
</div>
</div>
</div>
</template>
<template #item.owner="{ item }">
<v-tooltip :text="p.permissionTooltip(item)" location="top">
<template #activator="{ props: tipProps }">
<v-chip
v-bind="tipProps"
size="small"
variant="tonal"
:color="p.ownerChipColor(item.owner || '')"
class="text-none"
>
{{ p.formatOwnerChip(item) }}
</v-chip>
</template>
</v-tooltip>
</template>
<template #item.size="{ item }">
<span class="text-medium-emphasis">
{{ item.type === 'directory' ? '—' : p.formatSize(item.size) }}
</span>
</template>
<template #item.modified="{ item }">
<span class="text-medium-emphasis">{{ item.modified || '—' }}</span>
</template>
<template #item.actions="{ item }">
<div class="d-flex ga-1">
<v-btn
v-if="p.isRegularFile(item)"
variant="text"
size="x-small"
density="compact"
color="primary"
@click.stop="p.editFile(item)"
>
编辑
</v-btn>
<v-btn
v-if="p.isRegularFile(item)"
variant="text"
size="x-small"
density="compact"
@click.stop="p.previewFile(item)"
>
查看
</v-btn>
<v-btn
v-if="p.isRegularFile(item)"
variant="text"
size="x-small"
density="compact"
@click.stop="p.downloadFile(item)"
>
下载
</v-btn>
<v-btn
variant="text"
size="x-small"
color="error"
density="compact"
@click.stop="p.confirmDeleteFile(item)"
>
删除
</v-btn>
</div>
</template>
<template #no-data>
<div v-if="p.pageReady && !p.loading" class="text-center text-medium-emphasis py-8">
<v-icon size="40" class="mb-2">mdi-folder-open-outline</v-icon>
<div>{{ p.emptyHint }}</div>
<div v-if="p.selectedServer" class="text-caption mt-2 text-disabled">
单击目录进入 · 双击文件编辑 · Ctrl+A 全选 · Ctrl+C/V 复制粘贴
</div>
</div>
</template>
</v-data-table>
</v-card>
<v-menu v-model="p.showContextMenu" :target="[p.contextX, p.contextY]" location="bottom start">
<v-list density="compact" nav>
<v-list-item
v-if="p.contextFile && p.contextFile.type === 'directory'"
prepend-icon="mdi-folder-open"
@click="p.contextAction('open')"
>
<v-list-item-title>打开</v-list-item-title>
</v-list-item>
<v-list-item
v-if="p.contextFile && p.isRegularFile(p.contextFile)"
prepend-icon="mdi-pencil"
@click="p.contextAction('edit')"
>
<v-list-item-title>编辑</v-list-item-title>
</v-list-item>
<v-list-item
v-if="p.contextFile && p.isRegularFile(p.contextFile)"
prepend-icon="mdi-eye"
@click="p.contextAction('preview')"
>
<v-list-item-title>查看</v-list-item-title>
</v-list-item>
<v-list-item
v-if="p.contextFile && p.isRegularFile(p.contextFile)"
prepend-icon="mdi-download"
@click="p.contextAction('download')"
>
<v-list-item-title>下载</v-list-item-title>
</v-list-item>
<v-list-item
v-if="p.contextFile"
prepend-icon="mdi-content-copy"
@click="p.contextAction('copy')"
>
<v-list-item-title>复制 (Ctrl+C)</v-list-item-title>
</v-list-item>
<v-list-item
v-if="p.contextFile"
prepend-icon="mdi-content-cut"
@click="p.contextAction('cut')"
>
<v-list-item-title>剪切 (Ctrl+X)</v-list-item-title>
</v-list-item>
<v-list-item
prepend-icon="mdi-content-paste"
:disabled="!p.canPasteHere"
@click="p.contextAction('paste')"
>
<v-list-item-title>粘贴到当前目录 (Ctrl+V)</v-list-item-title>
</v-list-item>
<v-list-item
v-if="p.contextFile?.type === 'directory' && p.canPasteHere"
prepend-icon="mdi-folder-arrow-down"
@click="p.contextAction('pasteInto')"
>
<v-list-item-title>粘贴到{{ p.contextFile.name }}</v-list-item-title>
</v-list-item>
<v-list-item prepend-icon="mdi-link-variant" @click="p.contextAction('copyPath')">
<v-list-item-title>复制路径</v-list-item-title>
</v-list-item>
<v-list-item prepend-icon="mdi-console" @click="p.contextAction('terminal')">
<v-list-item-title>终端</v-list-item-title>
</v-list-item>
<v-divider />
<v-list-item
v-if="p.contextFile && (p.isRegularFile(p.contextFile) || p.contextFile.type === 'directory')"
prepend-icon="mdi-zip-box"
@click="p.contextAction('compress')"
>
<v-list-item-title>压缩</v-list-item-title>
</v-list-item>
<v-list-item
v-if="p.contextFile && p.isArchiveName(p.contextFile.name)"
prepend-icon="mdi-package-up"
@click="p.contextAction('decompress')"
>
<v-list-item-title>解压</v-list-item-title>
</v-list-item>
<v-divider />
<v-list-item prepend-icon="mdi-file-rename-box" @click="p.contextAction('rename')">
<v-list-item-title>重命名</v-list-item-title>
</v-list-item>
<v-list-item prepend-icon="mdi-lock" @click="p.contextAction('chmod')">
<v-list-item-title>修改权限</v-list-item-title>
</v-list-item>
<v-divider />
<v-list-item prepend-icon="mdi-delete" @click="p.contextAction('delete')">
<v-list-item-title class="text-error">删除</v-list-item-title>
</v-list-item>
</v-list>
</v-menu>
</template>
<style scoped>
.files-drop-active {
outline: 2px dashed rgb(var(--v-theme-primary));
outline-offset: -2px;
}
</style>