From 021be8f22b5128b4c5f4c3ef7bf1ebc14c417978 Mon Sep 17 00:00:00 2001 From: kjqwer <2990346238@qq.com> Date: Sat, 9 May 2026 11:01:53 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E9=A2=84=E8=AE=BE=E6=8E=92?= =?UTF-8?q?=E5=BA=8F=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/PresetManager.vue | 175 ++++++++++++++++++++++++++- src/components/preset/PresetList.vue | 19 ++- 2 files changed, 187 insertions(+), 7 deletions(-) diff --git a/src/components/PresetManager.vue b/src/components/PresetManager.vue index 9a77425..93934ae 100644 --- a/src/components/PresetManager.vue +++ b/src/components/PresetManager.vue @@ -17,6 +17,8 @@ const selectedType = ref('all'); const searchQuery = ref(''); const selectedFolderId = ref(null); const expandedFolderIds = ref>(new Set()); +const sortField = ref<'custom' | 'name' | 'updatedAt'>('custom'); +const sortDirection = ref<'asc' | 'desc'>('asc'); const presetSidebarRef = ref | null>(null); const presetListRef = ref | null>(null); const isRestoringViewState = ref(false); @@ -91,6 +93,24 @@ const filterOptions = computed<{ value: PresetType | 'all'; label: string }[]>(( ...presetTypes ]); +const sortOptions = [ + { value: 'custom', label: '自定义排序' }, + { value: 'name', label: '按名称' }, + { value: 'updatedAt', label: '按时间' } +] as const; + +const isCustomSort = computed(() => sortField.value === 'custom'); + +const sortDirectionLabel = computed(() => { + if (sortField.value === 'name') { + return sortDirection.value === 'asc' ? '名称 A-Z' : '名称 Z-A'; + } + if (sortField.value === 'updatedAt') { + return sortDirection.value === 'asc' ? '时间旧到新' : '时间新到旧'; + } + return sortDirection.value === 'asc' ? '正序' : '倒序'; +}); + const filteredPresets = computed(() => { let presets = [...(store.extendedPresets || [])]; @@ -121,18 +141,43 @@ const filteredPresets = computed(() => { ); } - return presets.sort((a, b) => { - const ao = typeof a.sortOrder === 'number' ? a.sortOrder : Number.POSITIVE_INFINITY; - const bo = typeof b.sortOrder === 'number' ? b.sortOrder : Number.POSITIVE_INFINITY; - if (ao !== bo) return ao - bo; - return new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime(); + const sorted = [...presets]; + if (sortField.value === 'custom') { + sorted.sort((a, b) => { + const ao = typeof a.sortOrder === 'number' ? a.sortOrder : Number.POSITIVE_INFINITY; + const bo = typeof b.sortOrder === 'number' ? b.sortOrder : Number.POSITIVE_INFINITY; + if (ao !== bo) return sortDirection.value === 'asc' ? ao - bo : bo - ao; + const timeDiff = new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime(); + return sortDirection.value === 'asc' ? timeDiff : -timeDiff; + }); + return sorted; + } + if (sortField.value === 'name') { + sorted.sort((a, b) => { + const compare = a.name.localeCompare(b.name, 'zh-CN', { numeric: true, sensitivity: 'base' }); + if (compare !== 0) { + return sortDirection.value === 'asc' ? compare : -compare; + } + return new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime(); + }); + return sorted; + } + sorted.sort((a, b) => { + const compare = new Date(a.updatedAt).getTime() - new Date(b.updatedAt).getTime(); + if (compare !== 0) { + return sortDirection.value === 'asc' ? compare : -compare; + } + return a.name.localeCompare(b.name, 'zh-CN', { numeric: true, sensitivity: 'base' }); }); + return sorted; }); const presetListResetKey = computed(() => JSON.stringify({ selectedType: selectedType.value, searchQuery: searchQuery.value, selectedFolderId: selectedFolderId.value, + sortField: sortField.value, + sortDirection: sortDirection.value, })); const flattenedFolders = computed(() => { @@ -170,6 +215,8 @@ function persistPresetManagerViewState() { searchQuery: searchQuery.value, selectedFolderId: selectedFolderId.value, expandedFolderIds: Array.from(expandedFolderIds.value), + sortField: sortField.value, + sortDirection: sortDirection.value, sidebarScrollTop: presetSidebarRef.value?.contentRef?.scrollTop ?? 0, listScrollTop: presetListRef.value?.containerRef?.scrollTop ?? 0, currentPage: presetListRef.value?.getCurrentPage?.() ?? 1, @@ -188,6 +235,8 @@ async function restorePresetManagerViewState() { searchQuery?: string; selectedFolderId?: string | null; expandedFolderIds?: string[]; + sortField?: 'custom' | 'name' | 'updatedAt'; + sortDirection?: 'asc' | 'desc'; sidebarScrollTop?: number; listScrollTop?: number; currentPage?: number; @@ -199,6 +248,8 @@ async function restorePresetManagerViewState() { searchQuery.value = state.searchQuery ?? ''; selectedFolderId.value = state.selectedFolderId ?? null; expandedFolderIds.value = new Set(state.expandedFolderIds ?? []); + sortField.value = state.sortField ?? 'custom'; + sortDirection.value = state.sortDirection ?? 'asc'; await nextTick(); if (typeof state.currentPage === 'number') { presetListRef.value?.setCurrentPage?.(state.currentPage, false); @@ -229,6 +280,14 @@ function handleToggleExpand(id: string) { expandedFolderIds.value = set; } +function setSortField(field: 'custom' | 'name' | 'updatedAt') { + sortField.value = field; +} + +function toggleSortDirection() { + sortDirection.value = sortDirection.value === 'asc' ? 'desc' : 'asc'; +} + function createFolder(parentId?: string) { resetFolderForm(); if (parentId) { @@ -350,6 +409,10 @@ function toggleFavorite(preset: ExtendedPreset) { } function handleReorderPresets(payload: { draggedId: string; targetId: string; side: 'before' | 'after' }) { + if (!isCustomSort.value) { + showNotification('请先切换到自定义排序后再拖拽', 'info'); + return; + } const visibleIds = filteredPresets.value.map(preset => preset.id); const from = visibleIds.indexOf(payload.draggedId); const target = visibleIds.indexOf(payload.targetId); @@ -785,6 +848,8 @@ watch( selectedType.value, searchQuery.value, selectedFolderId.value, + sortField.value, + sortDirection.value, Array.from(expandedFolderIds.value).sort().join('|'), ], () => { @@ -848,6 +913,34 @@ onBeforeUnmount(() => { v-model="selectedType" :options="filterOptions" /> +
+
+ +
+ +
@@ -885,11 +978,15 @@ onBeforeUnmount(() => {
+
+ {{ isCustomSort ? '当前为自定义排序,可直接拖拽卡片调整顺序。' : '当前为只读排序视图,切回自定义排序后可继续拖拽。' }} +
{ .filter-group { display: flex; gap: 0.5rem; + align-items: center; + flex-wrap: wrap; } .type-select { @@ -1237,6 +1336,66 @@ onBeforeUnmount(() => { color: var(--color-text-primary); } +.sort-controls { + display: flex; + align-items: center; + gap: 0.75rem; + flex-wrap: wrap; +} + +.sort-field-group { + display: inline-flex; + align-items: center; + gap: 0.375rem; + padding: 0.25rem; + border: 1px solid var(--color-border); + border-radius: var(--radius-md); + background-color: var(--color-bg-primary); +} + +.sort-chip { + padding: 0.45rem 0.75rem; + border: none; + border-radius: calc(var(--radius-md) - 4px); + background: transparent; + color: var(--color-text-secondary); + cursor: pointer; + transition: all 0.2s ease; + font-size: 0.875rem; + white-space: nowrap; +} + +.sort-chip:hover { + background-color: var(--color-bg-secondary); + color: var(--color-text-primary); +} + +.sort-chip.active { + background-color: var(--color-accent); + color: white; + box-shadow: var(--shadow-sm); +} + +.sort-direction-btn { + display: inline-flex; + align-items: center; + gap: 0.5rem; + padding: 0.5rem 0.75rem; + border: 1px solid var(--color-border); + border-radius: var(--radius-md); + background-color: var(--color-bg-primary); + color: var(--color-text-secondary); + cursor: pointer; + transition: all 0.2s ease; + white-space: nowrap; +} + +.sort-direction-btn:hover { + border-color: var(--color-border-hover); + background-color: var(--color-bg-secondary); + color: var(--color-text-primary); +} + .action-group { margin-left: auto; display: flex; @@ -1257,6 +1416,12 @@ onBeforeUnmount(() => { position: relative; } +.sort-hint { + padding: 0.625rem 1rem 0; + font-size: 0.8125rem; + color: var(--color-text-tertiary); +} + /* Buttons */ .btn-primary { display: flex; diff --git a/src/components/preset/PresetList.vue b/src/components/preset/PresetList.vue index 5ea2d37..6a01189 100644 --- a/src/components/preset/PresetList.vue +++ b/src/components/preset/PresetList.vue @@ -9,6 +9,7 @@ const props = defineProps<{ presets: ExtendedPreset[]; searchQuery: string; resetKey: string; + enableReorder?: boolean; }>(); const emit = defineEmits<{ @@ -81,6 +82,10 @@ defineExpose({ }); function onDragStart(preset: ExtendedPreset, event: DragEvent) { + if (!props.enableReorder) { + event.preventDefault(); + return; + } draggingPresetId.value = preset.id; if (event.dataTransfer) { event.dataTransfer.effectAllowed = 'move'; @@ -89,6 +94,7 @@ function onDragStart(preset: ExtendedPreset, event: DragEvent) { } function onDragOver(preset: ExtendedPreset, event: DragEvent) { + if (!props.enableReorder) return; if (!draggingPresetId.value || draggingPresetId.value === preset.id) return; event.preventDefault(); const target = event.currentTarget as HTMLElement | null; @@ -103,6 +109,10 @@ function onDragOver(preset: ExtendedPreset, event: DragEvent) { } function onDrop(preset: ExtendedPreset, event: DragEvent) { + if (!props.enableReorder) { + cleanupDragState(); + return; + } if (!draggingPresetId.value || draggingPresetId.value === preset.id || !dropSide.value) { cleanupDragState(); return; @@ -209,11 +219,12 @@ function formatDate(dateStr: string) {
-