Private
Public Access
1
0

新增:收藏夹置顶功能

This commit is contained in:
2026-03-06 22:03:34 +08:00
parent 1eaf61cf41
commit 5f94ccf13b
4 changed files with 115 additions and 3 deletions

View File

@@ -11,6 +11,9 @@
:key="fav.path" :key="fav.path"
class="sidebar-item" class="sidebar-item"
:class="{ :class="{
'sidebar-item-pinned': fav.pinnedAt,
'sidebar-item-pinned-first': index === firstPinnedIndex,
'sidebar-item-pinned-last': index === lastPinnedIndex,
'sidebar-item-dragging': config.draggingState.isDragging && config.draggingState.draggedIndex === index, 'sidebar-item-dragging': config.draggingState.isDragging && config.draggingState.draggedIndex === index,
'sidebar-item-drag-over': config.draggingState.isDragging && config.draggingState.draggedIndex !== index 'sidebar-item-drag-over': config.draggingState.isDragging && config.draggingState.draggedIndex !== index
}" }"
@@ -27,8 +30,19 @@
@drop="handleDrop($event, index)" @drop="handleDrop($event, index)"
@dragend="handleDragEnd" @dragend="handleDragEnd"
> >
<span class="sidebar-item-icon">{{ fav.isDir ? '📁' : '📄' }}</span> <span class="sidebar-item-icon">{{ getFileIcon(fav) }}</span>
<span class="sidebar-item-name" :title="fav.name">{{ fav.name }}</span> <span class="sidebar-item-name" :title="fav.name">{{ fav.name }}</span>
<a-button
type="text"
size="mini"
@click.stop="handleTogglePin(fav)"
class="sidebar-item-pin"
:class="{ 'is-pinned': fav.pinnedAt }"
>
<template #icon>
<icon-pushpin :style="{ opacity: fav.pinnedAt ? 1 : 0.4 }" />
</template>
</a-button>
<a-button <a-button
type="text" type="text"
size="mini" size="mini"
@@ -51,6 +65,7 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { computed } from 'vue'
import type { SidebarConfig, FavoriteFile } from '@/types/file-system' import type { SidebarConfig, FavoriteFile } from '@/types/file-system'
// Props // Props
@@ -60,10 +75,21 @@ interface Props {
const props = defineProps<Props>() const props = defineProps<Props>()
// 计算第一个和最后一个置顶项的索引
const pinnedIndices = computed(() => {
return props.config.favoriteFiles
.map((fav, index) => fav.pinnedAt ? index : -1)
.filter(i => i !== -1)
})
const firstPinnedIndex = computed(() => pinnedIndices.value[0] ?? -1)
const lastPinnedIndex = computed(() => pinnedIndices.value[pinnedIndices.value.length - 1] ?? -1)
// Emits // Emits
interface Emits { interface Emits {
(e: 'openFavorite', file: FavoriteFile): void (e: 'openFavorite', file: FavoriteFile): void
(e: 'removeFavorite', path: string): void (e: 'removeFavorite', path: string): void
(e: 'togglePin', path: string): void
(e: 'longPressStart', event: MouseEvent | TouchEvent, index: number): void (e: 'longPressStart', event: MouseEvent | TouchEvent, index: number): void
(e: 'longPressCancel'): void (e: 'longPressCancel'): void
(e: 'dragStart', event: DragEvent, index: number): void (e: 'dragStart', event: DragEvent, index: number): void
@@ -75,7 +101,8 @@ interface Emits {
const emit = defineEmits<Emits>() const emit = defineEmits<Emits>()
// 图标导入 // 图标导入
import { IconStar, IconClose } from '@arco-design/web-vue/es/icon' import { IconStar, IconClose, IconPushpin } from '@arco-design/web-vue/es/icon'
import { getFileIcon } from '@/utils/fileUtils'
// 事件处理 // 事件处理
const handleOpenFavorite = (file: FavoriteFile) => { const handleOpenFavorite = (file: FavoriteFile) => {
@@ -86,6 +113,10 @@ const handleRemoveFavorite = (file: FavoriteFile) => {
emit('removeFavorite', file.path) emit('removeFavorite', file.path)
} }
const handleTogglePin = (file: FavoriteFile) => {
emit('togglePin', file.path)
}
const handleLongPressStart = (event: MouseEvent | TouchEvent, index: number) => { const handleLongPressStart = (event: MouseEvent | TouchEvent, index: number) => {
emit('longPressStart', event, index) emit('longPressStart', event, index)
} }
@@ -200,6 +231,32 @@ const handleDragEnd = () => {
opacity: 1; opacity: 1;
} }
.sidebar-item-pinned {
background: var(--color-fill-1);
border-radius: 0;
}
.sidebar-item-pinned-first {
border-radius: 6px 6px 0 0;
}
.sidebar-item-pinned-last {
border-radius: 0 0 6px 6px;
}
.sidebar-item-pinned-first.sidebar-item-pinned-last {
border-radius: 6px;
}
.sidebar-item-pin {
opacity: 0;
transition: opacity 0.2s;
}
.sidebar-item:hover .sidebar-item-pin {
opacity: 1;
}
.sidebar-empty { .sidebar-empty {
display: flex; display: flex;
flex-direction: column; flex-direction: column;

View File

@@ -18,6 +18,21 @@ export function useFavorites() {
pressedIndex: -1 pressedIndex: -1
}) })
/**
* 排序收藏列表:置顶项在前(按 pinnedAt 降序),非置顶项按添加时间降序
*/
const sortFavorites = () => {
favorites.value.sort((a, b) => {
// 置顶项优先
if (a.pinnedAt && !b.pinnedAt) return -1
if (!a.pinnedAt && b.pinnedAt) return 1
// 都是置顶项,按置顶时间降序
if (a.pinnedAt && b.pinnedAt) return b.pinnedAt - a.pinnedAt
// 都不是置顶项,按添加时间降序(最新在前)
return b.addedAt - a.addedAt
})
}
/** /**
* 从 localStorage 加载收藏列表 * 从 localStorage 加载收藏列表
*/ */
@@ -32,6 +47,9 @@ export function useFavorites() {
...fav, ...fav,
isDir: fav.isDir ?? (fav as any).is_dir ?? false isDir: fav.isDir ?? (fav as any).is_dir ?? false
})) }))
// 排序
sortFavorites()
} }
} catch (error) { } catch (error) {
console.error('加载收藏列表失败:', error) console.error('加载收藏列表失败:', error)
@@ -108,6 +126,34 @@ export function useFavorites() {
return favorites.value.some(fav => normalizePath(fav.path) === normalizedPath) return favorites.value.some(fav => normalizePath(fav.path) === normalizedPath)
} }
/**
* 切换置顶状态
*/
const togglePin = (path: string) => {
const normalizedPath = normalizePath(path)
const fav = favorites.value.find(f => normalizePath(f.path) === normalizedPath)
if (fav) {
if (fav.pinnedAt) {
// 取消置顶
fav.pinnedAt = undefined
} else {
// 设置置顶
fav.pinnedAt = Date.now()
}
sortFavorites()
saveFavorites()
}
}
/**
* 检查是否已置顶
*/
const isPinned = (path: string): boolean => {
const normalizedPath = normalizePath(path)
const fav = favorites.value.find(f => normalizePath(f.path) === normalizedPath)
return !!fav?.pinnedAt
}
/** /**
* 长按开始 * 长按开始
*/ */
@@ -219,6 +265,8 @@ export function useFavorites() {
removeFavorite, removeFavorite,
toggleFavorite, toggleFavorite,
isFavorite, isFavorite,
togglePin,
isPinned,
// 拖拽方法 // 拖拽方法
onLongPressStart, onLongPressStart,

View File

@@ -21,6 +21,7 @@
:config="sidebarConfig" :config="sidebarConfig"
@open-favorite="handleOpenFavorite" @open-favorite="handleOpenFavorite"
@remove-favorite="handleRemoveFavorite" @remove-favorite="handleRemoveFavorite"
@toggle-pin="handleTogglePin"
@long-press-start="handleLongPressStart" @long-press-start="handleLongPressStart"
@long-press-cancel="handleLongPressCancel" @long-press-cancel="handleLongPressCancel"
@drag-start="handleDragStart" @drag-start="handleDragStart"
@@ -224,7 +225,7 @@ const fileOps = useFileOperations({
}) })
// 收藏夹 // 收藏夹
const { favorites, draggingState, toggleFavorite: toggleFav, removeFavorite: removeFav, isFavorite } = useFavorites() const { favorites, draggingState, toggleFavorite: toggleFav, removeFavorite: removeFav, isFavorite, togglePin } = useFavorites()
// 路径导航 // 路径导航
const { filePath, history, navigate, back, forward, onPathSelect, onPathEnter, browseDirectory, getParentPath } = const { filePath, history, navigate, back, forward, onPathSelect, onPathEnter, browseDirectory, getParentPath } =
@@ -457,6 +458,10 @@ const handleRemoveFavorite = (path: string) => {
removeFav(path) removeFav(path)
} }
const handleTogglePin = (path: string) => {
togglePin(path)
}
const handleLongPressStart = (event: MouseEvent | TouchEvent, index: number) => { const handleLongPressStart = (event: MouseEvent | TouchEvent, index: number) => {
// 拖拽开始 // 拖拽开始
} }

View File

@@ -29,6 +29,8 @@ export interface FileItem {
export interface FavoriteFile extends FileItem { export interface FavoriteFile extends FileItem {
/** 添加时间(时间戳) */ /** 添加时间(时间戳) */
addedAt: number addedAt: number
/** 置顶时间时间戳undefined 表示未置顶 */
pinnedAt?: number
} }
/** /**