优化: 收藏栏宽度可调与操作菜单收敛
- 侧边栏宽度拖拽调整180-420并持久化 - 收藏操作收敛为三点菜单加右键菜单,移除悬停按钮组 - 右键与三点菜单互斥,打开其一收起另一 - 置顶项背景改清晰浅蓝
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<transition name="slide">
|
||||
<div v-show="config.visible" class="sidebar">
|
||||
<div v-show="config.visible" class="sidebar" :style="{ width: width + 'px' }">
|
||||
<template v-for="section in sectionOrder" :key="section">
|
||||
<!-- 服务器区块 -->
|
||||
<div v-if="section === 'server' && showServer" class="sidebar-section">
|
||||
@@ -54,9 +54,15 @@
|
||||
</div>
|
||||
<div class="section-content-wrap" :class="{ collapsed: favCollapsed }">
|
||||
<div class="section-content fav-content">
|
||||
<div
|
||||
<a-dropdown
|
||||
v-for="(fav, index) in config.favoriteFiles"
|
||||
:key="fav.path"
|
||||
trigger="contextMenu"
|
||||
align-point
|
||||
:popup-visible="favCtxMenuIndex === index"
|
||||
@popup-visible-change="(v: boolean) => onFavCtxVisible(index, v)"
|
||||
>
|
||||
<div
|
||||
class="sidebar-item"
|
||||
:class="{
|
||||
'sidebar-item-pinned': fav.pinnedAt,
|
||||
@@ -78,41 +84,49 @@
|
||||
@drop="handleDrop($event, index)"
|
||||
@dragend="handleDragEnd"
|
||||
>
|
||||
<span class="sidebar-item-icon">{{ getFileIcon(fav) }}</span>
|
||||
<span class="sidebar-item-icon">{{ getFileIcon({ is_dir: fav.isDir, name: fav.name }) }}</span>
|
||||
<span class="sidebar-item-name" :title="favoriteTooltip(fav)">{{ favoriteName(fav) }}</span>
|
||||
<a-button
|
||||
type="text"
|
||||
size="mini"
|
||||
@click.stop="handleTogglePin(fav)"
|
||||
class="sidebar-item-pin"
|
||||
:class="{ 'is-pinned': fav.pinnedAt }"
|
||||
<a-dropdown
|
||||
trigger="click"
|
||||
:popup-visible="favMenuIndex === index"
|
||||
@popup-visible-change="(v: boolean) => onFavMenuVisible(index, v)"
|
||||
>
|
||||
<a-button type="text" size="mini" title="更多操作" class="sidebar-item-more" @click.stop>
|
||||
<template #icon>
|
||||
<icon-pushpin :style="{ opacity: fav.pinnedAt ? 1 : 0.4 }" />
|
||||
<icon-more />
|
||||
</template>
|
||||
</a-button>
|
||||
<a-button
|
||||
type="text"
|
||||
size="mini"
|
||||
title="重命名收藏"
|
||||
@click.stop="openRename(fav)"
|
||||
class="sidebar-item-rename"
|
||||
>
|
||||
<template #icon>
|
||||
<icon-edit />
|
||||
<template #content>
|
||||
<a-doption @click="openRename(fav)">
|
||||
<template #icon><icon-edit /></template>
|
||||
重命名
|
||||
</a-doption>
|
||||
<a-doption @click="handleTogglePin(fav)">
|
||||
<template #icon><icon-pushpin /></template>
|
||||
{{ fav.pinnedAt ? '取消置顶' : '置顶' }}
|
||||
</a-doption>
|
||||
<a-doption class="danger" @click="handleRemoveFavorite(fav)">
|
||||
<template #icon><icon-close /></template>
|
||||
删除
|
||||
</a-doption>
|
||||
</template>
|
||||
</a-button>
|
||||
<a-button
|
||||
type="text"
|
||||
size="mini"
|
||||
@click.stop="handleRemoveFavorite(fav)"
|
||||
class="sidebar-item-remove"
|
||||
>
|
||||
<template #icon>
|
||||
<icon-close />
|
||||
</template>
|
||||
</a-button>
|
||||
</a-dropdown>
|
||||
</div>
|
||||
<template #content>
|
||||
<a-doption @click="openRename(fav)">
|
||||
<template #icon><icon-edit /></template>
|
||||
重命名
|
||||
</a-doption>
|
||||
<a-doption @click="handleTogglePin(fav)">
|
||||
<template #icon><icon-pushpin /></template>
|
||||
{{ fav.pinnedAt ? '取消置顶' : '置顶' }}
|
||||
</a-doption>
|
||||
<a-doption class="danger" @click="handleRemoveFavorite(fav)">
|
||||
<template #icon><icon-close /></template>
|
||||
删除
|
||||
</a-doption>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
<div v-if="config.favoriteFiles.length === 0" class="sidebar-empty">
|
||||
<icon-star />
|
||||
<span>暂无收藏</span>
|
||||
@@ -158,6 +172,7 @@
|
||||
:placeholder="renameOriginal"
|
||||
:max-length="255"
|
||||
allow-clear
|
||||
@keydown.enter.prevent="confirmRename"
|
||||
/>
|
||||
</a-modal>
|
||||
|
||||
@@ -200,7 +215,7 @@ import type { SidebarConfig, FavoriteFile } from '@/types/file-system'
|
||||
import type { SystemInfo } from '@/api/connection-manager'
|
||||
import { connectionManager } from '@/api/connection-manager'
|
||||
import { useConfigStore } from '@/stores/config'
|
||||
import { IconStar, IconClose, IconPushpin, IconEdit, IconDown, IconRight, IconStorage, IconComputer, IconDesktop, IconPlus, IconCheckCircle } from '@arco-design/web-vue/es/icon'
|
||||
import { IconStar, IconClose, IconPushpin, IconEdit, IconMore, IconDown, IconRight, IconStorage, IconComputer, IconDesktop, IconPlus, IconCheckCircle } from '@arco-design/web-vue/es/icon'
|
||||
import { getFileIcon } from '@/utils/fileUtils'
|
||||
|
||||
const configStore = useConfigStore()
|
||||
@@ -208,6 +223,7 @@ const configStore = useConfigStore()
|
||||
// Props
|
||||
interface Props {
|
||||
config: SidebarConfig
|
||||
width: number
|
||||
}
|
||||
|
||||
const props = defineProps<Props>()
|
||||
@@ -399,6 +415,20 @@ const handleTogglePin = (file: FavoriteFile) => {
|
||||
emit('togglePin', file.path)
|
||||
}
|
||||
|
||||
// 收藏项双菜单受控互斥:右键菜单与「...」点击菜单,一方展开强制收起另一方
|
||||
const favMenuIndex = ref(-1)
|
||||
const favCtxMenuIndex = ref(-1)
|
||||
|
||||
function onFavCtxVisible(index: number, visible: boolean) {
|
||||
favCtxMenuIndex.value = visible ? index : -1
|
||||
if (visible) favMenuIndex.value = -1
|
||||
}
|
||||
|
||||
function onFavMenuVisible(index: number, visible: boolean) {
|
||||
favMenuIndex.value = visible ? index : -1
|
||||
if (visible) favCtxMenuIndex.value = -1
|
||||
}
|
||||
|
||||
// 重命名收藏弹窗(预填展示名,placeholder 显示原名)
|
||||
const renameVisible = ref(false)
|
||||
const renameValue = ref('')
|
||||
@@ -429,6 +459,11 @@ const handleRenameOk = (done: (closed?: boolean) => void) => {
|
||||
done()
|
||||
}
|
||||
|
||||
// 输入框回车与点击确定走同一提交逻辑
|
||||
const confirmRename = () => {
|
||||
handleRenameOk(() => { renameVisible.value = false })
|
||||
}
|
||||
|
||||
// 展示名优先,未设置回退原名
|
||||
const favoriteName = (fav: FavoriteFile): string => fav.displayName || fav.name
|
||||
|
||||
@@ -891,17 +926,8 @@ function handleDelete(p: { id: string; name: string }) {
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.sidebar-item-remove {
|
||||
opacity: 0;
|
||||
transition: opacity 0.2s;
|
||||
}
|
||||
|
||||
.sidebar-item:hover .sidebar-item-remove {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.sidebar-item-pinned {
|
||||
background: var(--color-fill-1);
|
||||
background: var(--arcoblue-1);
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
@@ -917,21 +943,13 @@ function handleDelete(p: { id: string; name: string }) {
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.sidebar-item-pin {
|
||||
/* 「...」操作菜单按钮,悬停收藏项时显示 */
|
||||
.sidebar-item-more {
|
||||
opacity: 0;
|
||||
transition: opacity 0.2s;
|
||||
}
|
||||
|
||||
.sidebar-item:hover .sidebar-item-pin {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.sidebar-item-rename {
|
||||
opacity: 0;
|
||||
transition: opacity 0.2s;
|
||||
}
|
||||
|
||||
.sidebar-item:hover .sidebar-item-rename {
|
||||
.sidebar-item:hover .sidebar-item-more {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
@@ -979,6 +997,17 @@ function handleDelete(p: { id: string; name: string }) {
|
||||
}
|
||||
</style>
|
||||
|
||||
<!-- 非 scoped:Arco 下拉菜单挂载在 body 下,删除项标红需全局样式 -->
|
||||
<style>
|
||||
.arco-dropdown-option.danger {
|
||||
color: rgb(var(--danger-6));
|
||||
}
|
||||
|
||||
.arco-dropdown-option.danger:hover {
|
||||
background: rgb(var(--danger-1));
|
||||
}
|
||||
</style>
|
||||
|
||||
<!-- 非 scoped:Teleport 到 body 的面板样式 -->
|
||||
<style>
|
||||
/* 弹出面板公共 */
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
<template>
|
||||
<div class="file-system-container">
|
||||
<!-- 主内容区:左侧边栏 + 右侧工作区 -->
|
||||
<div class="main-content">
|
||||
<div ref="mainContentRef" class="main-content">
|
||||
<!-- 左侧收藏夹侧边栏(全高) -->
|
||||
<Sidebar
|
||||
v-if="showSidebar"
|
||||
:config="sidebarConfig"
|
||||
:width="sidebarWidth"
|
||||
@open-favorite="handleOpenFavorite"
|
||||
@remove-favorite="handleRemoveFavorite"
|
||||
@toggle-pin="handleToggleFavPin"
|
||||
@@ -20,6 +21,9 @@
|
||||
@server-select="handleConnectionChanged"
|
||||
/>
|
||||
|
||||
<!-- 侧边栏拖宽把手 -->
|
||||
<div v-if="showSidebar" class="sidebar-resizer" @mousedown="handleSidebarResizeWithOverlay"></div>
|
||||
|
||||
<!-- 右侧工作区:面包屑工具栏 + 文件列表/编辑器 -->
|
||||
<div class="workspace-area">
|
||||
<!-- 面包屑导航工具栏 -->
|
||||
@@ -272,6 +276,58 @@ const savePanelWidth = (width: { left: number; right: number }) => {
|
||||
const panelWidth = ref(restorePanelWidth())
|
||||
const workspaceRef = ref<HTMLElement | null>(null)
|
||||
|
||||
// 侧边栏宽度(拖拽调整,localStorage 持久化)
|
||||
const restoreSidebarWidth = (): number => {
|
||||
try {
|
||||
const saved = localStorage.getItem(STORAGE_KEYS.FILESYSTEM.SIDEBAR_WIDTH)
|
||||
if (saved) {
|
||||
const width = Number(saved)
|
||||
if (Number.isFinite(width)) {
|
||||
return Math.min(DEFAULTS.MAX_SIDEBAR_WIDTH, Math.max(DEFAULTS.MIN_SIDEBAR_WIDTH, width))
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('恢复侧边栏宽度失败:', error)
|
||||
}
|
||||
return DEFAULTS.SIDEBAR_WIDTH
|
||||
}
|
||||
|
||||
const saveSidebarWidth = (width: number) => {
|
||||
try {
|
||||
localStorage.setItem(STORAGE_KEYS.FILESYSTEM.SIDEBAR_WIDTH, String(width))
|
||||
} catch (error) {
|
||||
console.error('保存侧边栏宽度失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
const sidebarWidth = ref(restoreSidebarWidth())
|
||||
const mainContentRef = ref<HTMLElement | null>(null)
|
||||
|
||||
const handleSidebarHorizontalResize = createResizeHandler(
|
||||
() => mainContentRef.value,
|
||||
() => sidebarWidth.value,
|
||||
{
|
||||
direction: 'horizontal',
|
||||
outputMode: 'pixels',
|
||||
minPercent: 0,
|
||||
maxPercent: 100,
|
||||
minPixels: DEFAULTS.MIN_SIDEBAR_WIDTH,
|
||||
onResize: (px) => {
|
||||
sidebarWidth.value = Math.min(DEFAULTS.MAX_SIDEBAR_WIDTH, Math.max(DEFAULTS.MIN_SIDEBAR_WIDTH, px))
|
||||
},
|
||||
onResizeEnd: () => {
|
||||
isResizing.value = false
|
||||
saveSidebarWidth(sidebarWidth.value)
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
// mousedown 时复用工作区遮罩,防止拖拽经过编辑器 iframe 时丢事件
|
||||
const handleSidebarResizeWithOverlay = (e: MouseEvent) => {
|
||||
isResizing.value = true
|
||||
handleSidebarHorizontalResize(e)
|
||||
}
|
||||
|
||||
// 系统路径(使用 composable)
|
||||
const { commonPaths, systemPaths, loadCommonPaths } = useCommonPaths()
|
||||
|
||||
@@ -2094,6 +2150,19 @@ watch(() => themeStore.isDark, async () => {
|
||||
background: var(--color-primary-light-1);
|
||||
}
|
||||
|
||||
/* 侧边栏拖宽把手(与文件列表区分隔条同款) */
|
||||
.sidebar-resizer {
|
||||
width: 3px;
|
||||
background: var(--color-border);
|
||||
cursor: col-resize;
|
||||
transition: background 0.2s;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.sidebar-resizer:hover {
|
||||
background: var(--color-primary-light-1);
|
||||
}
|
||||
|
||||
.resize-overlay {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
|
||||
@@ -23,6 +23,7 @@ export const STORAGE_KEYS = {
|
||||
PATH_HISTORY: 'app-filesystem-path-history',
|
||||
FILE_CONTENT_HEIGHT: 'app-filesystem-file-content-height',
|
||||
PANEL_WIDTH: 'app-filesystem-panel-width',
|
||||
SIDEBAR_WIDTH: 'app-filesystem-sidebar-width',
|
||||
SIDEBAR_VISIBLE: 'app-filesystem-sidebar-visible',
|
||||
FAVORITE_FILES: 'app-filesystem-favorite-files',
|
||||
EDIT_MODE: 'app-filesystem-edit-mode', // HTML/Markdown 编辑模式状态
|
||||
@@ -334,8 +335,12 @@ export const DEFAULTS = {
|
||||
MAX_PANEL_WIDTH: 80,
|
||||
DEFAULT_PANEL_WIDTH: 50,
|
||||
|
||||
// 侧边栏宽度(px)
|
||||
// 侧边栏默认宽度(px)
|
||||
SIDEBAR_WIDTH: 220,
|
||||
|
||||
// 侧边栏宽度范围(px)
|
||||
MIN_SIDEBAR_WIDTH: 180,
|
||||
MAX_SIDEBAR_WIDTH: 420,
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user