增加收藏和文件夹分享功能
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -86,6 +86,7 @@ export interface ExtendedPreset {
|
||||
updatedAt: string;
|
||||
isPublic?: boolean;
|
||||
author?: string;
|
||||
isFavorite?: boolean;
|
||||
}
|
||||
|
||||
// 预设文件夹
|
||||
|
||||
Reference in New Issue
Block a user