新增:收藏夹置顶功能
This commit is contained in:
@@ -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;
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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) => {
|
||||||
// 拖拽开始
|
// 拖拽开始
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,6 +29,8 @@ export interface FileItem {
|
|||||||
export interface FavoriteFile extends FileItem {
|
export interface FavoriteFile extends FileItem {
|
||||||
/** 添加时间(时间戳) */
|
/** 添加时间(时间戳) */
|
||||||
addedAt: number
|
addedAt: number
|
||||||
|
/** 置顶时间(时间戳),undefined 表示未置顶 */
|
||||||
|
pinnedAt?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user