From 0ee688582a255ccb50a7ec2d32613a16e0622952 Mon Sep 17 00:00:00 2001 From: kjqwer <2990346238@qq.com> Date: Wed, 3 Dec 2025 07:57:18 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E5=88=86=E4=BA=AB=E7=A0=81?= =?UTF-8?q?=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + src/components/PresetManager.vue | 276 +++++++++++++++++++++++++++ src/components/preset/PresetList.vue | 11 ++ 3 files changed, 288 insertions(+) diff --git a/.gitignore b/.gitignore index a547bf3..df122b4 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,7 @@ node_modules dist dist-ssr *.local +temp/* # Editor directories and files .vscode/* diff --git a/src/components/PresetManager.vue b/src/components/PresetManager.vue index 7eb7f69..171fa17 100644 --- a/src/components/PresetManager.vue +++ b/src/components/PresetManager.vue @@ -319,6 +319,126 @@ function importPresets(event: Event) { (event.target as HTMLInputElement).value = ''; } +// Share & Cloud +const showShareDialog = ref(false); +const shareTab = ref<'create' | 'import'>('create'); +const shareLoading = ref(false); +const shareResultCode = ref(''); +const shareImportCode = ref(''); +const shareSinglePreset = ref(null); + +function openShareDialog(preset?: ExtendedPreset) { + shareSinglePreset.value = preset || null; + shareTab.value = 'create'; + shareResultCode.value = ''; + shareImportCode.value = ''; + showShareDialog.value = true; +} + +function handleShare(preset: ExtendedPreset) { + openShareDialog(preset); +} + +async function generateShareCode() { + shareLoading.value = true; + try { + let data; + let type = 'all'; + + if (shareSinglePreset.value) { + data = shareSinglePreset.value; + type = 'single'; + } else { + const jsonString = store.exportPresetsToJson(); + try { + data = JSON.parse(jsonString); + } catch (e) { + data = {}; + } + } + + const response = await fetch('https://sywb.top/api/share/create', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ data, type }) + }); + + const result = await response.json(); + + if (result.success) { + shareResultCode.value = result.code; + showNotification('分享码生成成功', 'success'); + } else { + showNotification(result.error || '生成失败', 'error'); + } + } catch (error) { + console.error(error); + showNotification('网络错误,无法生成分享码', 'error'); + } finally { + shareLoading.value = false; + } +} + +async function importFromShareCode() { + if (!shareImportCode.value || shareImportCode.value.length !== 6) { + showNotification('请输入有效的6位分享码', 'error'); + return; + } + + shareLoading.value = true; + try { + const response = await fetch(`https://sywb.top/api/share/${shareImportCode.value}`); + const result = await response.json(); + + if (result.success) { + const data = result.data; + if (result.type === 'single') { + // Import single preset + const newPreset = { ...data }; + // Remove ID to create new + if (newPreset.id) delete newPreset.id; + + // Ensure name uniqueness or mark as imported + newPreset.name = newPreset.name + ' (Imported)'; + + store.createExtendedPreset(newPreset); + showNotification(`预设「${newPreset.name}」导入成功`, 'success'); + } else { + // Import all + const jsonString = JSON.stringify(data); + const success = store.importPresetsFromJson(jsonString); + if (success) { + showNotification('预设导入成功', 'success'); + } else { + showNotification('导入数据格式错误', 'error'); + } + } + closeShareDialog(); + } else { + showNotification(result.error || '分享码无效或已过期', 'error'); + } + } catch (error) { + console.error(error); + showNotification('网络错误,无法导入', 'error'); + } finally { + shareLoading.value = false; + } +} + +function copyShareCode() { + navigator.clipboard.writeText(shareResultCode.value); + showNotification('分享码已复制', 'success'); +} + +function closeShareDialog() { + showShareDialog.value = false; + shareResultCode.value = ''; + shareImportCode.value = ''; + shareSinglePreset.value = null; +} + // Helpers function resetPresetForm() { presetForm.value = { @@ -406,6 +526,11 @@ onMounted(() => {
+
@@ -542,6 +668,83 @@ onMounted(() => { + + + { border-color: var(--color-text-primary); box-shadow: 0 0 0 2px var(--color-bg-primary); } + +.share-modal { + max-width: 400px; +} + +.share-tabs { + display: flex; + border-bottom: 1px solid var(--color-border); + margin-bottom: 1rem; +} + +.share-tabs button { + flex: 1; + padding: 0.75rem; + background: none; + border: none; + border-bottom: 2px solid transparent; + color: var(--color-text-secondary); + cursor: pointer; + font-weight: 500; +} + +.share-tabs button.active { + color: var(--color-accent); + border-bottom-color: var(--color-accent); +} + +.share-info { + text-align: center; + margin-bottom: 1.5rem; + color: var(--color-text-primary); +} + +.share-info p { + margin: 0.5rem 0; +} + +.text-muted { + color: var(--color-text-tertiary); + font-size: 0.875rem; +} + +.share-result { + display: flex; + flex-direction: column; + gap: 1rem; + align-items: center; +} + +.code-display { + font-size: 2rem; + font-weight: 700; + letter-spacing: 0.25rem; + color: var(--color-accent); + padding: 1rem; + background-color: var(--color-bg-secondary); + border-radius: var(--radius-md); + width: 100%; + text-align: center; +} + +.code-input { + font-size: 1.5rem; + text-align: center; + letter-spacing: 0.25rem; + padding: 0.75rem; +} + +.full-width { + width: 100%; + justify-content: center; + padding: 0.75rem; +} diff --git a/src/components/preset/PresetList.vue b/src/components/preset/PresetList.vue index 6dfb436..77e5b96 100644 --- a/src/components/preset/PresetList.vue +++ b/src/components/preset/PresetList.vue @@ -12,6 +12,7 @@ const emit = defineEmits<{ (e: 'edit', preset: ExtendedPreset): void; (e: 'delete', preset: ExtendedPreset): void; (e: 'copy', preset: ExtendedPreset): void; + (e: 'share', preset: ExtendedPreset): void; }>(); function getTypeIcon(type: PresetType) { @@ -80,6 +81,16 @@ function formatDate(dateStr: string) {