feat(workspace): 重构工作区组件,优化路径选择和面包屑导航功能
Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
parent
9ff925e4df
commit
6fcfb090de
@ -2,22 +2,34 @@
|
|||||||
<section class="workspace-file-list">
|
<section class="workspace-file-list">
|
||||||
<div class="file-list-header">
|
<div class="file-list-header">
|
||||||
<div class="path-line">
|
<div class="path-line">
|
||||||
<button
|
<a-breadcrumb class="path-breadcrumb">
|
||||||
type="button"
|
<a-breadcrumb-item v-for="item in breadcrumbItems" :key="item.path">
|
||||||
class="path-action"
|
<button
|
||||||
:disabled="currentPath === '/'"
|
type="button"
|
||||||
aria-label="返回上级目录"
|
class="breadcrumb-action"
|
||||||
@click="$emit('go-parent')"
|
:class="{ current: item.path === currentPath }"
|
||||||
>
|
:disabled="item.path === currentPath"
|
||||||
<ChevronLeft :size="16" />
|
:title="item.path"
|
||||||
</button>
|
@click="$emit('select-path', item.path)"
|
||||||
<span class="path-text" :title="currentPath">{{ currentPath }}</span>
|
>
|
||||||
|
{{ item.name }}
|
||||||
|
</button>
|
||||||
|
</a-breadcrumb-item>
|
||||||
|
</a-breadcrumb>
|
||||||
</div>
|
</div>
|
||||||
<div class="list-actions">
|
<div class="list-actions">
|
||||||
<span class="entry-count">{{ entries.length }} 项</span>
|
<span class="entry-count">{{ entries.length }} 项</span>
|
||||||
<a-button size="small" :type="selectionMode ? 'primary' : 'default'" @click="toggleSelectionMode">
|
<a-tooltip title="多选">
|
||||||
多选
|
<a-button
|
||||||
</a-button>
|
size="small"
|
||||||
|
class="lucide-icon-btn"
|
||||||
|
:type="selectionMode ? 'primary' : 'default'"
|
||||||
|
aria-label="多选"
|
||||||
|
@click="toggleSelectionMode"
|
||||||
|
>
|
||||||
|
<ListChecks :size="14" />
|
||||||
|
</a-button>
|
||||||
|
</a-tooltip>
|
||||||
<a-button
|
<a-button
|
||||||
v-if="selectionMode"
|
v-if="selectionMode"
|
||||||
size="small"
|
size="small"
|
||||||
@ -122,7 +134,7 @@
|
|||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
import { ChevronLeft, Download, Folder, MoreHorizontal, Trash2 } from 'lucide-vue-next'
|
import { Download, Folder, ListChecks, MoreHorizontal, Trash2 } from 'lucide-vue-next'
|
||||||
import { formatFileSize, formatRelativeTime, getFileIcon, getFileIconColor } from '@/utils/file_utils'
|
import { formatFileSize, formatRelativeTime, getFileIcon, getFileIconColor } from '@/utils/file_utils'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
@ -137,7 +149,7 @@ const props = defineProps({
|
|||||||
|
|
||||||
const emit = defineEmits([
|
const emit = defineEmits([
|
||||||
'select-entry',
|
'select-entry',
|
||||||
'go-parent',
|
'select-path',
|
||||||
'update:selectedPaths',
|
'update:selectedPaths',
|
||||||
'update:selectionMode',
|
'update:selectionMode',
|
||||||
'delete-selected',
|
'delete-selected',
|
||||||
@ -148,6 +160,23 @@ const emit = defineEmits([
|
|||||||
const selectedPathSet = computed(() => new Set(props.selectedPaths))
|
const selectedPathSet = computed(() => new Set(props.selectedPaths))
|
||||||
const deletingPathSet = computed(() => new Set(props.deletingPaths))
|
const deletingPathSet = computed(() => new Set(props.deletingPaths))
|
||||||
const entryPaths = computed(() => props.entries.map((entry) => entry.path))
|
const entryPaths = computed(() => props.entries.map((entry) => entry.path))
|
||||||
|
const breadcrumbItems = computed(() => {
|
||||||
|
const normalizedPath = props.currentPath || '/'
|
||||||
|
if (normalizedPath === '/') {
|
||||||
|
return [{ name: '工作区', path: '/' }]
|
||||||
|
}
|
||||||
|
|
||||||
|
const segments = normalizedPath.split('/').filter(Boolean)
|
||||||
|
return segments.reduce(
|
||||||
|
(items, segment) => {
|
||||||
|
const parentPath = items[items.length - 1].path
|
||||||
|
const path = parentPath === '/' ? `/${segment}` : `${parentPath}/${segment}`
|
||||||
|
items.push({ name: segment, path })
|
||||||
|
return items
|
||||||
|
},
|
||||||
|
[{ name: '工作区', path: '/' }]
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
const allSelected = computed(() => {
|
const allSelected = computed(() => {
|
||||||
return entryPaths.value.length > 0 && entryPaths.value.every((path) => selectedPathSet.value.has(path))
|
return entryPaths.value.length > 0 && entryPaths.value.every((path) => selectedPathSet.value.has(path))
|
||||||
@ -213,37 +242,34 @@ const toggleEntrySelection = (path, checked) => {
|
|||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.path-action {
|
.path-breadcrumb {
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
width: 30px;
|
|
||||||
height: 30px;
|
|
||||||
border: 1px solid var(--gray-150);
|
|
||||||
border-radius: 8px;
|
|
||||||
background: var(--gray-0);
|
|
||||||
color: var(--gray-600);
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
&:disabled {
|
|
||||||
color: var(--gray-300);
|
|
||||||
cursor: not-allowed;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover:not(:disabled) {
|
|
||||||
background: var(--main-20);
|
|
||||||
color: var(--main-color);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.path-text {
|
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
color: var(--gray-900);
|
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
font-weight: 600;
|
}
|
||||||
|
|
||||||
|
.breadcrumb-action {
|
||||||
|
max-width: 180px;
|
||||||
|
padding: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
border: 0;
|
||||||
|
background: transparent;
|
||||||
|
color: var(--main-800);
|
||||||
|
cursor: pointer;
|
||||||
|
font: inherit;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
font-weight: 400;
|
||||||
|
|
||||||
|
&:hover:not(:disabled) {
|
||||||
|
color: var(--main-600);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.current,
|
||||||
|
&:disabled {
|
||||||
|
color: var(--gray-900);
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.entry-count {
|
.entry-count {
|
||||||
|
|||||||
@ -5,10 +5,12 @@
|
|||||||
:file="file"
|
:file="file"
|
||||||
:file-path="filePath"
|
:file-path="filePath"
|
||||||
:show-download="false"
|
:show-download="false"
|
||||||
|
:show-close="true"
|
||||||
:show-fullscreen="true"
|
:show-fullscreen="true"
|
||||||
:full-height="true"
|
:full-height="true"
|
||||||
container-class="workspace-preview-container"
|
container-class="workspace-preview-container"
|
||||||
content-class="workspace-preview-content"
|
content-class="workspace-preview-content"
|
||||||
|
@close="$emit('close')"
|
||||||
/>
|
/>
|
||||||
<div v-else-if="loading" class="preview-state">
|
<div v-else-if="loading" class="preview-state">
|
||||||
<a-spin />
|
<a-spin />
|
||||||
@ -31,6 +33,8 @@ defineProps({
|
|||||||
filePath: { type: String, default: '' },
|
filePath: { type: String, default: '' },
|
||||||
loading: { type: Boolean, default: false }
|
loading: { type: Boolean, default: false }
|
||||||
})
|
})
|
||||||
|
|
||||||
|
defineEmits(['close'])
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="less">
|
<style scoped lang="less">
|
||||||
|
|||||||
@ -104,8 +104,9 @@ defineEmits(['select-personal', 'select-database', 'select-path'])
|
|||||||
border: 1px solid transparent;
|
border: 1px solid transparent;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
color: var(--gray-700);
|
color: var(--gray-600);
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition:
|
transition:
|
||||||
@ -118,13 +119,13 @@ defineEmits(['select-personal', 'select-database', 'select-path'])
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover:not(:disabled),
|
&:hover:not(:disabled),
|
||||||
&.active {
|
&.active {
|
||||||
border-color: transparent;
|
border-color: transparent;
|
||||||
background: var(--main-20);
|
color: var(--main-600);
|
||||||
color: var(--main-color);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&.secondary {
|
&.secondary {
|
||||||
|
|||||||
@ -59,7 +59,7 @@
|
|||||||
:selection-mode="selectionMode"
|
:selection-mode="selectionMode"
|
||||||
:loading="loadingTree"
|
:loading="loadingTree"
|
||||||
@select-entry="handleSelectEntry"
|
@select-entry="handleSelectEntry"
|
||||||
@go-parent="goParentDirectory"
|
@select-path="selectWorkspacePath"
|
||||||
@update:selected-paths="selectedPaths = $event"
|
@update:selected-paths="selectedPaths = $event"
|
||||||
@update:selection-mode="handleSelectionModeChange"
|
@update:selection-mode="handleSelectionModeChange"
|
||||||
@delete-selected="confirmDeleteEntries(selectedEntries)"
|
@delete-selected="confirmDeleteEntries(selectedEntries)"
|
||||||
@ -79,6 +79,7 @@
|
|||||||
:file="previewFile"
|
:file="previewFile"
|
||||||
:file-path="selectedEntry?.path || ''"
|
:file-path="selectedEntry?.path || ''"
|
||||||
:loading="loadingPreview"
|
:loading="loadingPreview"
|
||||||
|
@close="closePreview"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -338,15 +339,6 @@ const closePreview = () => {
|
|||||||
revokePreviewObjectUrl()
|
revokePreviewObjectUrl()
|
||||||
}
|
}
|
||||||
|
|
||||||
const goParentDirectory = async () => {
|
|
||||||
if (currentPath.value === '/') return
|
|
||||||
const trimmed = currentPath.value.replace(/\/$/, '')
|
|
||||||
const parent = trimmed.slice(0, trimmed.lastIndexOf('/')) || '/'
|
|
||||||
closePreview()
|
|
||||||
clearWorkspaceSelection()
|
|
||||||
await loadWorkspaceEntries(parent)
|
|
||||||
}
|
|
||||||
|
|
||||||
const openCreateDirectoryModal = () => {
|
const openCreateDirectoryModal = () => {
|
||||||
if (activeSourceKey.value !== 'personal') return
|
if (activeSourceKey.value !== 'personal') return
|
||||||
newDirectoryName.value = ''
|
newDirectoryName.value = ''
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user