增加收藏和文件夹分享功能

This commit is contained in:
2025-12-12 16:08:30 +08:00
parent f41e9757bf
commit b444ea5cfd
5 changed files with 102 additions and 10 deletions
+60 -10
View File
@@ -90,7 +90,9 @@ const filteredPresets = computed(() => {
}
// Filter by Folder
if (selectedFolderId.value) {
if (selectedFolderId.value === 'favorites') {
presets = presets.filter(p => p.isFavorite);
} else if (selectedFolderId.value) {
presets = presets.filter(p => p.folderId === selectedFolderId.value);
} else if (selectedFolderId.value === '') {
// Uncategorized
@@ -137,6 +139,7 @@ const flattenedFolders = computed(() => {
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);
// Actions
function handleFolderSelect(id: string | null) {
@@ -262,6 +265,13 @@ function deletePreset(preset: ExtendedPreset) {
}
}
function toggleFavorite(preset: ExtendedPreset) {
store.updateExtendedPreset(preset.id, { isFavorite: !preset.isFavorite });
if (!preset.isFavorite) {
showNotification(`已添加到收藏`, 'success');
}
}
async function copyPresetContent(preset: ExtendedPreset) {
try {
await navigator.clipboard.writeText(preset.content);
@@ -327,9 +337,11 @@ const shareLoading = ref(false);
const shareResultCode = ref('');
const shareImportCode = ref('');
const shareSinglePreset = ref<ExtendedPreset | null>(null);
const shareFolder = ref<PresetFolder | null>(null);
function openShareDialog(preset?: ExtendedPreset) {
function openShareDialog(preset?: ExtendedPreset, folder?: PresetFolder) {
shareSinglePreset.value = preset || null;
shareFolder.value = folder || null;
shareTab.value = 'create';
shareResultCode.value = '';
shareImportCode.value = '';
@@ -340,6 +352,10 @@ function handleShare(preset: ExtendedPreset) {
openShareDialog(preset);
}
function handleShareFolder(folder: PresetFolder) {
openShareDialog(undefined, folder);
}
async function generateShareCode() {
shareLoading.value = true;
try {
@@ -349,6 +365,33 @@ async function generateShareCode() {
if (shareSinglePreset.value) {
data = shareSinglePreset.value;
type = 'single';
} else if (shareFolder.value) {
// Find all presets in this folder and its subfolders?
// User likely just wants this folder and its direct contents, OR the whole tree.
// Simplest is recursive export of folder + subfolders + presets inside them.
const folderIds = new Set<string>();
const foldersToExport: PresetFolder[] = [];
const collectFolders = (id: string) => {
const folder = store.presetFolders.find(f => f.id === id);
if (folder) {
folderIds.add(id);
foldersToExport.push(folder);
// Find subfolders
store.presetFolders.filter(f => f.parentId === id).forEach(f => collectFolders(f.id));
}
};
collectFolders(shareFolder.value.id);
const presetsToExport = store.extendedPresets.filter(p => p.folderId && folderIds.has(p.folderId));
data = {
presetFolders: foldersToExport,
extendedPresets: presetsToExport
};
type = 'folder';
} else {
const jsonString = store.exportPresetsToJson();
try {
@@ -594,6 +637,7 @@ function closeShareDialog() {
shareResultCode.value = '';
shareImportCode.value = '';
shareSinglePreset.value = null;
shareFolder.value = null;
}
// Helpers
@@ -648,12 +692,14 @@ onMounted(() => {
:expanded-ids="expandedFolderIds"
:all-count="allPresetsCount"
:uncategorized-count="uncategorizedCount"
:favorites-count="favoritesCount"
@update:selected-folder-id="handleFolderSelect"
@toggle-expand="handleToggleExpand"
@create-folder="createFolder()"
@create-sub-folder="createFolder"
@edit-folder="editFolder"
@delete-folder="deleteFolder"
@share-folder="handleShareFolder"
/>
</div>
@@ -721,14 +767,15 @@ onMounted(() => {
<div class="pm-content-area">
<PresetList
:presets="filteredPresets"
:search-query="searchQuery"
@apply="applyPreset"
@edit="editPreset"
@delete="deletePreset"
@copy="copyPresetContent"
@share="handleShare"
/>
:presets="filteredPresets"
:search-query="searchQuery"
@apply="applyPreset"
@edit="editPreset"
@delete="deletePreset"
@copy="copyPresetContent"
@share="handleShare"
@toggle-favorite="toggleFavorite"
/>
</div>
</div>
@@ -867,6 +914,9 @@ onMounted(() => {
<p v-if="shareSinglePreset">
正在分享预设: <strong>{{ shareSinglePreset.name }}</strong>
</p>
<p v-else-if="shareFolder">
正在分享文件夹: <strong>{{ shareFolder.name }}</strong>
</p>
<p v-else>
正在分享: <strong>所有预设数据</strong>
</p>
+11
View File
@@ -21,6 +21,7 @@ const emit = defineEmits<{
(e: 'create-sub', parentId: string): void;
(e: 'edit', folder: PresetFolder): void;
(e: 'delete', folder: PresetFolder): void;
(e: 'share', folder: PresetFolder): void;
}>();
const isExpanded = computed(() => props.expandedIds.has(props.folder.id));
@@ -85,6 +86,15 @@ const showActions = ref(false);
<!-- 操作按钮组 (悬停显示) -->
<div class="folder-actions" v-show="showActions || isSelected">
<button @click.stop="emit('share', folder)" title="分享文件夹">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<circle cx="18" cy="5" r="3"/>
<circle cx="6" cy="12" r="3"/>
<circle cx="18" cy="19" r="3"/>
<line x1="8.59" y1="13.51" x2="15.42" y2="17.49"/>
<line x1="15.41" y1="6.51" x2="8.59" y2="10.49"/>
</svg>
</button>
<button @click.stop="emit('create-sub', folder.id)" title="新建子文件夹">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M12 5v14M5 12h14"/>
@@ -118,6 +128,7 @@ const showActions = ref(false);
@create-sub="emit('create-sub', $event)"
@edit="emit('edit', $event)"
@delete="emit('delete', $event)"
@share="emit('share', $event)"
/>
</div>
</div>
+17
View File
@@ -13,6 +13,7 @@ const emit = defineEmits<{
(e: 'delete', preset: ExtendedPreset): void;
(e: 'copy', preset: ExtendedPreset): void;
(e: 'share', preset: ExtendedPreset): void;
(e: 'toggle-favorite', preset: ExtendedPreset): void;
}>();
function getTypeIcon(type: PresetType) {
@@ -61,6 +62,11 @@ function formatDate(dateStr: string) {
</div>
<h4 class="preset-name" :title="preset.name">{{ preset.name }}</h4>
<div class="preset-actions">
<button @click="emit('toggle-favorite', preset)" class="action-btn" :class="{ 'is-favorite': preset.isFavorite }" title="收藏">
<svg width="14" height="14" viewBox="0 0 24 24" :fill="preset.isFavorite ? 'currentColor' : 'none'" stroke="currentColor" stroke-width="2">
<path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z"></path>
</svg>
</button>
<button @click="emit('apply', preset)" class="action-btn apply-btn" title="应用预设">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<polyline points="20,6 9,17 4,12"/>
@@ -240,6 +246,17 @@ function formatDate(dateStr: string) {
border-color: var(--color-accent);
}
.is-favorite {
color: #ef4444;
border-color: rgba(239, 68, 68, 0.3);
background-color: rgba(239, 68, 68, 0.1);
}
.is-favorite:hover {
background-color: rgba(239, 68, 68, 0.2);
color: #ef4444;
}
/* Dropdown menu implementation */
.dropdown-menu {
position: relative;
+13
View File
@@ -14,6 +14,7 @@ const props = defineProps<{
expandedIds: Set<string>;
allCount: number;
uncategorizedCount: number;
favoritesCount: number;
}>();
const emit = defineEmits<{
@@ -23,6 +24,7 @@ const emit = defineEmits<{
(e: 'create-sub-folder', parentId: string): void;
(e: 'edit-folder', folder: PresetFolder): void;
(e: 'delete-folder', folder: PresetFolder): void;
(e: 'share-folder', folder: PresetFolder): void;
}>();
function selectFolder(id: string | null) {
@@ -55,6 +57,16 @@ function selectFolder(id: string | null) {
<span class="item-name">所有预设</span>
<span class="item-count">{{ allCount }}</span>
</div>
<div
class="sidebar-item"
:class="{ active: selectedFolderId === 'favorites' }"
@click="selectFolder('favorites')"
>
<span class="item-icon"></span>
<span class="item-name">我的收藏</span>
<span class="item-count">{{ favoritesCount }}</span>
</div>
<div
class="sidebar-item"
@@ -83,6 +95,7 @@ function selectFolder(id: string | null) {
@create-sub="emit('create-sub-folder', $event)"
@edit="emit('edit-folder', $event)"
@delete="emit('delete-folder', $event)"
@share="emit('share-folder', $event)"
/>
</div>
</div>