增加页面切换状态保存
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted } from 'vue';
|
||||
import { ref, computed, nextTick, onBeforeUnmount, onMounted, watch } from 'vue';
|
||||
import { usePromptStore } from '../stores/promptStore';
|
||||
import type { ExtendedPreset, PresetFolder, PresetType } from '../types';
|
||||
import NotificationToast from './NotificationToast.vue';
|
||||
@@ -9,6 +9,7 @@ import FolderSelector from './preset/FolderSelector.vue';
|
||||
import TypeSelector from './preset/TypeSelector.vue';
|
||||
|
||||
const store = usePromptStore();
|
||||
const PRESET_MANAGER_VIEW_STATE_KEY = 'preset-manager-view-state';
|
||||
|
||||
// State
|
||||
const activeTab = ref<'presets' | 'folders'>('presets'); // Kept for compatibility if needed, but UI will be unified
|
||||
@@ -16,6 +17,9 @@ const selectedType = ref<PresetType | 'all'>('all');
|
||||
const searchQuery = ref('');
|
||||
const selectedFolderId = ref<string | null>(null);
|
||||
const expandedFolderIds = ref<Set<string>>(new Set());
|
||||
const presetSidebarRef = ref<InstanceType<typeof PresetSidebar> | null>(null);
|
||||
const presetListRef = ref<InstanceType<typeof PresetList> | null>(null);
|
||||
const isRestoringViewState = ref(false);
|
||||
|
||||
// Dialog State
|
||||
const showMobileSidebar = ref(false);
|
||||
@@ -88,7 +92,7 @@ const filterOptions = computed<{ value: PresetType | 'all'; label: string }[]>((
|
||||
]);
|
||||
|
||||
const filteredPresets = computed(() => {
|
||||
let presets = store.extendedPresets || [];
|
||||
let presets = [...(store.extendedPresets || [])];
|
||||
|
||||
// Filter by Type
|
||||
if (selectedType.value !== 'all') {
|
||||
@@ -117,8 +121,19 @@ const filteredPresets = computed(() => {
|
||||
);
|
||||
}
|
||||
|
||||
return presets.sort((a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime());
|
||||
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 presetListResetKey = computed(() => JSON.stringify({
|
||||
selectedType: selectedType.value,
|
||||
searchQuery: searchQuery.value,
|
||||
selectedFolderId: selectedFolderId.value,
|
||||
}));
|
||||
|
||||
const flattenedFolders = computed(() => {
|
||||
type FlatItem = { id: string; name: string; label: string; level: number; presetCount: number; hasChildren: boolean };
|
||||
@@ -147,6 +162,62 @@ const allPresetsCount = computed(() => (store.extendedPresets || []).length);
|
||||
const uncategorizedCount = computed(() => (store.extendedPresets || []).filter(p => !p.folderId).length);
|
||||
const favoritesCount = computed(() => (store.extendedPresets || []).filter(p => p.isFavorite).length);
|
||||
|
||||
function persistPresetManagerViewState() {
|
||||
if (isRestoringViewState.value) return;
|
||||
const payload = {
|
||||
activeTab: activeTab.value,
|
||||
selectedType: selectedType.value,
|
||||
searchQuery: searchQuery.value,
|
||||
selectedFolderId: selectedFolderId.value,
|
||||
expandedFolderIds: Array.from(expandedFolderIds.value),
|
||||
sidebarScrollTop: presetSidebarRef.value?.contentRef?.scrollTop ?? 0,
|
||||
listScrollTop: presetListRef.value?.containerRef?.scrollTop ?? 0,
|
||||
currentPage: presetListRef.value?.getCurrentPage?.() ?? 1,
|
||||
};
|
||||
window.sessionStorage.setItem(PRESET_MANAGER_VIEW_STATE_KEY, JSON.stringify(payload));
|
||||
}
|
||||
|
||||
async function restorePresetManagerViewState() {
|
||||
const raw = window.sessionStorage.getItem(PRESET_MANAGER_VIEW_STATE_KEY);
|
||||
if (!raw) return;
|
||||
try {
|
||||
isRestoringViewState.value = true;
|
||||
const state = JSON.parse(raw) as {
|
||||
activeTab?: 'presets' | 'folders';
|
||||
selectedType?: PresetType | 'all';
|
||||
searchQuery?: string;
|
||||
selectedFolderId?: string | null;
|
||||
expandedFolderIds?: string[];
|
||||
sidebarScrollTop?: number;
|
||||
listScrollTop?: number;
|
||||
currentPage?: number;
|
||||
};
|
||||
if (state.activeTab === 'presets' || state.activeTab === 'folders') {
|
||||
activeTab.value = state.activeTab;
|
||||
}
|
||||
selectedType.value = state.selectedType ?? 'all';
|
||||
searchQuery.value = state.searchQuery ?? '';
|
||||
selectedFolderId.value = state.selectedFolderId ?? null;
|
||||
expandedFolderIds.value = new Set(state.expandedFolderIds ?? []);
|
||||
await nextTick();
|
||||
if (typeof state.currentPage === 'number') {
|
||||
presetListRef.value?.setCurrentPage?.(state.currentPage, false);
|
||||
}
|
||||
await nextTick();
|
||||
if (presetSidebarRef.value?.contentRef) {
|
||||
presetSidebarRef.value.contentRef.scrollTop = Math.max(0, state.sidebarScrollTop ?? 0);
|
||||
}
|
||||
if (presetListRef.value?.containerRef) {
|
||||
presetListRef.value.containerRef.scrollTop = Math.max(0, state.listScrollTop ?? 0);
|
||||
}
|
||||
} catch {
|
||||
// Ignore broken persisted state and continue with defaults.
|
||||
} finally {
|
||||
isRestoringViewState.value = false;
|
||||
persistPresetManagerViewState();
|
||||
}
|
||||
}
|
||||
|
||||
// Actions
|
||||
function handleFolderSelect(id: string | null) {
|
||||
selectedFolderId.value = id;
|
||||
@@ -278,6 +349,28 @@ function toggleFavorite(preset: ExtendedPreset) {
|
||||
}
|
||||
}
|
||||
|
||||
function handleReorderPresets(payload: { draggedId: string; targetId: string; side: 'before' | 'after' }) {
|
||||
const visibleIds = filteredPresets.value.map(preset => preset.id);
|
||||
const from = visibleIds.indexOf(payload.draggedId);
|
||||
const target = visibleIds.indexOf(payload.targetId);
|
||||
if (from === -1 || target === -1) return;
|
||||
const reordered = [...visibleIds];
|
||||
const [draggedId] = reordered.splice(from, 1);
|
||||
if (!draggedId) return;
|
||||
let insertAt = target;
|
||||
if (payload.side === 'after') {
|
||||
insertAt += from < target ? 0 : 1;
|
||||
} else if (from < target) {
|
||||
insertAt -= 1;
|
||||
}
|
||||
insertAt = Math.max(0, Math.min(insertAt, reordered.length));
|
||||
reordered.splice(insertAt, 0, draggedId);
|
||||
if (store.reorderExtendedPresets(reordered)) {
|
||||
persistPresetManagerViewState();
|
||||
showNotification('已调整预设顺序', 'success');
|
||||
}
|
||||
}
|
||||
|
||||
async function copyPresetContent(preset: ExtendedPreset) {
|
||||
try {
|
||||
await navigator.clipboard.writeText(preset.content);
|
||||
@@ -681,6 +774,26 @@ function closeFolderDialog() {
|
||||
|
||||
onMounted(() => {
|
||||
store.initializeExtendedPresets();
|
||||
nextTick(() => {
|
||||
restorePresetManagerViewState();
|
||||
});
|
||||
});
|
||||
|
||||
watch(
|
||||
() => [
|
||||
activeTab.value,
|
||||
selectedType.value,
|
||||
searchQuery.value,
|
||||
selectedFolderId.value,
|
||||
Array.from(expandedFolderIds.value).sort().join('|'),
|
||||
],
|
||||
() => {
|
||||
persistPresetManagerViewState();
|
||||
}
|
||||
);
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
persistPresetManagerViewState();
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -693,6 +806,7 @@ onMounted(() => {
|
||||
<div class="pm-sidebar" :class="{ 'mobile-open': showMobileSidebar }">
|
||||
<button v-if="showMobileSidebar" class="mobile-sidebar-close" @click="showMobileSidebar = false">×</button>
|
||||
<PresetSidebar
|
||||
ref="presetSidebarRef"
|
||||
:folder-tree="folderTree"
|
||||
:selected-folder-id="selectedFolderId"
|
||||
:expanded-ids="expandedFolderIds"
|
||||
@@ -706,6 +820,7 @@ onMounted(() => {
|
||||
@edit-folder="editFolder"
|
||||
@delete-folder="deleteFolder"
|
||||
@share-folder="handleShareFolder"
|
||||
@view-state-change="persistPresetManagerViewState"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -771,20 +886,24 @@ onMounted(() => {
|
||||
|
||||
<div class="pm-content-area">
|
||||
<PresetList
|
||||
ref="presetListRef"
|
||||
:presets="filteredPresets"
|
||||
:search-query="searchQuery"
|
||||
:reset-key="presetListResetKey"
|
||||
@apply="applyPreset"
|
||||
@edit="editPreset"
|
||||
@delete="deletePreset"
|
||||
@copy="copyPresetContent"
|
||||
@share="handleShare"
|
||||
@toggle-favorite="toggleFavorite"
|
||||
@reorder="handleReorderPresets"
|
||||
@view-state-change="persistPresetManagerViewState"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Create/Edit Preset Modal -->
|
||||
<div v-if="showCreateDialog" class="modal-overlay" @click.self="closePresetDialog">
|
||||
<div v-if="showCreateDialog" class="modal-overlay">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h3>{{ editingPreset ? '编辑预设' : '新建预设' }}</h3>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import { computed, nextTick, onMounted, onUnmounted, ref, watch } from 'vue';
|
||||
import { usePromptStore } from '../stores/promptStore';
|
||||
import type { PromptTag } from '../types';
|
||||
|
||||
@@ -17,19 +17,23 @@ const filteredTags = computed(() => store.filteredTags);
|
||||
const selectedLang = computed(() => store.selectedLang);
|
||||
|
||||
const PAGE_SIZE = 50;
|
||||
const QUICK_ADD_STATE_KEY = 'prompt-quick-add-view-state';
|
||||
const visibleCount = ref(PAGE_SIZE);
|
||||
const tagsContainer = ref<HTMLElement | null>(null);
|
||||
const draggedTagKey = ref<string | null>(null);
|
||||
const isRestoringState = ref(false);
|
||||
|
||||
const visibleTags = computed(() => {
|
||||
return filteredTags.value.slice(0, visibleCount.value);
|
||||
});
|
||||
|
||||
watch(() => filteredTags.value, () => {
|
||||
if (isRestoringState.value) return;
|
||||
visibleCount.value = PAGE_SIZE;
|
||||
if (tagsContainer.value) {
|
||||
tagsContainer.value.scrollTop = 0;
|
||||
}
|
||||
persistQuickAddState();
|
||||
});
|
||||
|
||||
function onScroll() {
|
||||
@@ -41,14 +45,17 @@ function onScroll() {
|
||||
visibleCount.value += PAGE_SIZE;
|
||||
}
|
||||
}
|
||||
persistQuickAddState();
|
||||
}
|
||||
|
||||
function selectCategory(index: number) {
|
||||
store.selectCategory(index);
|
||||
persistQuickAddState();
|
||||
}
|
||||
|
||||
function selectGroup(index: number) {
|
||||
store.selectGroup(index);
|
||||
persistQuickAddState();
|
||||
}
|
||||
|
||||
function onTagClick(tag: PromptTag) {
|
||||
@@ -75,6 +82,63 @@ function onTagDragEnd() {
|
||||
function displayTrans(tag: PromptTag) {
|
||||
return tag.translation?.[selectedLang.value] ?? tag.key;
|
||||
}
|
||||
|
||||
function persistQuickAddState() {
|
||||
const payload = {
|
||||
categoryIndex: store.selectedCategoryIndex,
|
||||
groupIndex: store.selectedGroupIndex,
|
||||
visibleCount: visibleCount.value,
|
||||
scrollTop: tagsContainer.value?.scrollTop ?? 0,
|
||||
};
|
||||
window.sessionStorage.setItem(QUICK_ADD_STATE_KEY, JSON.stringify(payload));
|
||||
}
|
||||
|
||||
async function restoreQuickAddState() {
|
||||
const raw = window.sessionStorage.getItem(QUICK_ADD_STATE_KEY);
|
||||
if (!raw) return;
|
||||
try {
|
||||
const state = JSON.parse(raw) as {
|
||||
categoryIndex?: number;
|
||||
groupIndex?: number;
|
||||
visibleCount?: number;
|
||||
scrollTop?: number;
|
||||
};
|
||||
isRestoringState.value = true;
|
||||
const categoryCount = categories.value.length;
|
||||
const nextCategoryIndex = Math.min(Math.max(state.categoryIndex ?? 0, 0), Math.max(categoryCount - 1, 0));
|
||||
store.selectCategory(nextCategoryIndex);
|
||||
const groupCount = currentCategory.value?.groups.length ?? 0;
|
||||
const nextGroupIndex = Math.min(Math.max(state.groupIndex ?? 0, 0), Math.max(groupCount - 1, 0));
|
||||
store.selectGroup(nextGroupIndex);
|
||||
visibleCount.value = Math.max(PAGE_SIZE, state.visibleCount ?? PAGE_SIZE);
|
||||
await nextTick();
|
||||
if (tagsContainer.value) {
|
||||
tagsContainer.value.scrollTop = Math.max(0, state.scrollTop ?? 0);
|
||||
}
|
||||
} catch {
|
||||
// Ignore broken persisted state and continue with defaults.
|
||||
} finally {
|
||||
isRestoringState.value = false;
|
||||
persistQuickAddState();
|
||||
}
|
||||
}
|
||||
|
||||
watch(
|
||||
() => [store.selectedCategoryIndex, store.selectedGroupIndex, visibleCount.value],
|
||||
() => {
|
||||
if (!isRestoringState.value) {
|
||||
persistQuickAddState();
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
onMounted(() => {
|
||||
restoreQuickAddState();
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
persistQuickAddState();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
@@ -8,6 +8,7 @@ import IconArrowRight from '../icons/IconArrowRight.vue';
|
||||
const props = defineProps<{
|
||||
presets: ExtendedPreset[];
|
||||
searchQuery: string;
|
||||
resetKey: string;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
@@ -17,12 +18,17 @@ const emit = defineEmits<{
|
||||
(e: 'copy', preset: ExtendedPreset): void;
|
||||
(e: 'share', preset: ExtendedPreset): void;
|
||||
(e: 'toggle-favorite', preset: ExtendedPreset): void;
|
||||
(e: 'reorder', payload: { draggedId: string; targetId: string; side: 'before' | 'after' }): void;
|
||||
(e: 'view-state-change'): void;
|
||||
}>();
|
||||
|
||||
// Pagination Logic
|
||||
const PAGE_SIZE = 24;
|
||||
const currentPage = ref(1);
|
||||
const containerRef = ref<HTMLElement | null>(null);
|
||||
const draggingPresetId = ref<string | null>(null);
|
||||
const overPresetId = ref<string | null>(null);
|
||||
const dropSide = ref<'before' | 'after' | null>(null);
|
||||
|
||||
const totalPages = computed(() => Math.ceil(props.presets.length / PAGE_SIZE));
|
||||
|
||||
@@ -32,11 +38,19 @@ const displayedPresets = computed(() => {
|
||||
return props.presets.slice(start, end);
|
||||
});
|
||||
|
||||
watch(() => props.presets, () => {
|
||||
watch(() => props.resetKey, () => {
|
||||
currentPage.value = 1;
|
||||
if (containerRef.value) {
|
||||
containerRef.value.scrollTop = 0;
|
||||
}
|
||||
emit('view-state-change');
|
||||
});
|
||||
|
||||
watch(() => props.presets.length, () => {
|
||||
const fallbackMax = totalPages.value || 1;
|
||||
if (currentPage.value > fallbackMax) {
|
||||
currentPage.value = fallbackMax;
|
||||
}
|
||||
});
|
||||
|
||||
function changePage(page: number) {
|
||||
@@ -45,6 +59,67 @@ function changePage(page: number) {
|
||||
if (containerRef.value) {
|
||||
containerRef.value.scrollTop = 0;
|
||||
}
|
||||
emit('view-state-change');
|
||||
}
|
||||
|
||||
function setCurrentPage(page: number, resetScroll = false) {
|
||||
const fallbackMax = totalPages.value || 1;
|
||||
currentPage.value = Math.min(Math.max(page, 1), fallbackMax);
|
||||
if (resetScroll && containerRef.value) {
|
||||
containerRef.value.scrollTop = 0;
|
||||
}
|
||||
}
|
||||
|
||||
function getCurrentPage() {
|
||||
return currentPage.value;
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
containerRef,
|
||||
setCurrentPage,
|
||||
getCurrentPage,
|
||||
});
|
||||
|
||||
function onDragStart(preset: ExtendedPreset, event: DragEvent) {
|
||||
draggingPresetId.value = preset.id;
|
||||
if (event.dataTransfer) {
|
||||
event.dataTransfer.effectAllowed = 'move';
|
||||
event.dataTransfer.setData('text/plain', preset.id);
|
||||
}
|
||||
}
|
||||
|
||||
function onDragOver(preset: ExtendedPreset, event: DragEvent) {
|
||||
if (!draggingPresetId.value || draggingPresetId.value === preset.id) return;
|
||||
event.preventDefault();
|
||||
const target = event.currentTarget as HTMLElement | null;
|
||||
if (!target) return;
|
||||
const rect = target.getBoundingClientRect();
|
||||
const side = event.clientY < rect.top + rect.height / 2 ? 'before' : 'after';
|
||||
overPresetId.value = preset.id;
|
||||
dropSide.value = side;
|
||||
if (event.dataTransfer) {
|
||||
event.dataTransfer.dropEffect = 'move';
|
||||
}
|
||||
}
|
||||
|
||||
function onDrop(preset: ExtendedPreset, event: DragEvent) {
|
||||
if (!draggingPresetId.value || draggingPresetId.value === preset.id || !dropSide.value) {
|
||||
cleanupDragState();
|
||||
return;
|
||||
}
|
||||
event.preventDefault();
|
||||
emit('reorder', {
|
||||
draggedId: draggingPresetId.value,
|
||||
targetId: preset.id,
|
||||
side: dropSide.value,
|
||||
});
|
||||
cleanupDragState();
|
||||
}
|
||||
|
||||
function cleanupDragState() {
|
||||
draggingPresetId.value = null;
|
||||
overPresetId.value = null;
|
||||
dropSide.value = null;
|
||||
}
|
||||
|
||||
function getTypeLabel(type: PresetType) {
|
||||
@@ -66,7 +141,7 @@ function formatDate(dateStr: string) {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="preset-list-container" ref="containerRef">
|
||||
<div class="preset-list-container" ref="containerRef" @scroll="emit('view-state-change')">
|
||||
<div v-if="presets.length === 0" class="empty-state">
|
||||
<div class="empty-icon">📭</div>
|
||||
<p class="empty-text">暂无预设</p>
|
||||
@@ -74,7 +149,14 @@ function formatDate(dateStr: string) {
|
||||
|
||||
<template v-else>
|
||||
<div class="preset-grid">
|
||||
<div v-for="preset in displayedPresets" :key="preset.id" class="preset-card nav-btn">
|
||||
<div v-for="preset in displayedPresets" :key="preset.id" class="preset-card nav-btn" draggable="true"
|
||||
:class="{
|
||||
dragging: draggingPresetId === preset.id,
|
||||
'insert-before': overPresetId === preset.id && dropSide === 'before' && draggingPresetId !== preset.id,
|
||||
'insert-after': overPresetId === preset.id && dropSide === 'after' && draggingPresetId !== preset.id
|
||||
}"
|
||||
@dragstart="onDragStart(preset, $event)" @dragover="onDragOver(preset, $event)"
|
||||
@drop="onDrop(preset, $event)" @dragend="cleanupDragState">
|
||||
<div class="card-header">
|
||||
<div class="preset-type" :title="getTypeLabel(preset.type)">
|
||||
<IconPresetType :type="preset.type" width="24" height="24" />
|
||||
@@ -221,6 +303,7 @@ function formatDate(dateStr: string) {
|
||||
transition: all 0.2s ease;
|
||||
position: relative;
|
||||
text-align: left;
|
||||
cursor: grab;
|
||||
}
|
||||
|
||||
.preset-card:hover {
|
||||
@@ -229,6 +312,19 @@ function formatDate(dateStr: string) {
|
||||
border-color: var(--color-border-hover);
|
||||
}
|
||||
|
||||
.preset-card.dragging {
|
||||
opacity: 0.55;
|
||||
cursor: grabbing;
|
||||
}
|
||||
|
||||
.preset-card.insert-before {
|
||||
border-top: 3px solid var(--color-accent);
|
||||
}
|
||||
|
||||
.preset-card.insert-after {
|
||||
border-bottom: 3px solid var(--color-accent);
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue';
|
||||
import { ref } from 'vue';
|
||||
import FolderTreeItem from './FolderTreeItem.vue';
|
||||
import type { PresetFolder } from '../../types';
|
||||
|
||||
@@ -25,11 +25,18 @@ const emit = defineEmits<{
|
||||
(e: 'edit-folder', folder: PresetFolder): void;
|
||||
(e: 'delete-folder', folder: PresetFolder): void;
|
||||
(e: 'share-folder', folder: PresetFolder): void;
|
||||
(e: 'view-state-change'): void;
|
||||
}>();
|
||||
|
||||
function selectFolder(id: string | null) {
|
||||
emit('update:selectedFolderId', id);
|
||||
}
|
||||
|
||||
const contentRef = ref<HTMLElement | null>(null);
|
||||
|
||||
defineExpose({
|
||||
contentRef,
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -45,7 +52,7 @@ function selectFolder(id: string | null) {
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="sidebar-content">
|
||||
<div class="sidebar-content" ref="contentRef" @scroll="emit('view-state-change')">
|
||||
<!-- 固定选项 -->
|
||||
<div class="system-folders">
|
||||
<div
|
||||
|
||||
@@ -833,6 +833,20 @@ export const usePromptStore = defineStore('promptStore', {
|
||||
};
|
||||
}
|
||||
|
||||
const normalized = [...this.extendedPresets].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();
|
||||
});
|
||||
let changed = false;
|
||||
normalized.forEach((preset, index) => {
|
||||
if (preset.sortOrder !== index) {
|
||||
preset.sortOrder = index;
|
||||
changed = true;
|
||||
}
|
||||
});
|
||||
|
||||
// 确保有默认文件夹
|
||||
if (this.presetFolders.length === 0) {
|
||||
const defaultFolder = this.createPresetFolder({
|
||||
@@ -845,15 +859,25 @@ export const usePromptStore = defineStore('promptStore', {
|
||||
if (this.presetManagement.settings) {
|
||||
this.presetManagement.settings.defaultFolder = defaultFolder.id;
|
||||
}
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (changed) {
|
||||
this.save();
|
||||
}
|
||||
},
|
||||
|
||||
createExtendedPreset(data: Omit<ExtendedPreset, 'id' | 'createdAt' | 'updatedAt'>) {
|
||||
const now = new Date().toISOString();
|
||||
const nextSortOrder = this.extendedPresets.reduce((max, preset) => {
|
||||
const value = typeof preset.sortOrder === 'number' ? preset.sortOrder : -1;
|
||||
return Math.max(max, value);
|
||||
}, -1) + 1;
|
||||
const preset: ExtendedPreset = {
|
||||
id: `preset_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`,
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
sortOrder: nextSortOrder,
|
||||
...data
|
||||
};
|
||||
|
||||
@@ -876,6 +900,41 @@ export const usePromptStore = defineStore('promptStore', {
|
||||
if (index === -1) return false;
|
||||
|
||||
this.extendedPresets.splice(index, 1);
|
||||
this.extendedPresets
|
||||
.sort((a, b) => (a.sortOrder ?? 0) - (b.sortOrder ?? 0))
|
||||
.forEach((preset, idx) => {
|
||||
preset.sortOrder = idx;
|
||||
});
|
||||
this.save();
|
||||
return true;
|
||||
},
|
||||
|
||||
reorderExtendedPresets(orderedIds: string[]) {
|
||||
if (!orderedIds.length) return false;
|
||||
const idSet = new Set(orderedIds);
|
||||
const ordered = [...this.extendedPresets].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 replacements = ordered.filter(preset => idSet.has(preset.id));
|
||||
if (replacements.length !== orderedIds.length) return false;
|
||||
const replacementMap = new Map(replacements.map(preset => [preset.id, preset]));
|
||||
const reorderedVisible = orderedIds
|
||||
.map(id => replacementMap.get(id))
|
||||
.filter((preset): preset is ExtendedPreset => !!preset);
|
||||
|
||||
let visibleIndex = 0;
|
||||
const merged = ordered.map(preset => {
|
||||
if (!idSet.has(preset.id)) return preset;
|
||||
const nextPreset = reorderedVisible[visibleIndex];
|
||||
visibleIndex += 1;
|
||||
return nextPreset ?? preset;
|
||||
});
|
||||
merged.forEach((preset, index2) => {
|
||||
preset.sortOrder = index2;
|
||||
});
|
||||
this.save();
|
||||
return true;
|
||||
},
|
||||
|
||||
@@ -84,6 +84,7 @@ export interface ExtendedPreset {
|
||||
folderId?: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
sortOrder?: number;
|
||||
isPublic?: boolean;
|
||||
author?: string;
|
||||
isFavorite?: boolean;
|
||||
|
||||
Reference in New Issue
Block a user