diff --git a/apps/viewer/package.json b/apps/viewer/package.json index 8d51510..d807ce8 100644 --- a/apps/viewer/package.json +++ b/apps/viewer/package.json @@ -15,15 +15,16 @@ }, "dependencies": { "@photo/contracts": "workspace:*", + "qrcode": "^1.5.4", "vue": "~3.4.0", "vue-i18n": "^9.14.0", - "vue-router": "~4.4.0", - "qrcode": "^1.5.4" + "vue-router": "~4.4.0" }, "devDependencies": { "@photo/db-cn": "workspace:*", "@photo/storage": "workspace:*", "@types/node": "^22.10.0", + "@types/qrcode": "^1.5.6", "@vitejs/plugin-vue": "^5.2.0", "@vue/test-utils": "^2.4.6", "dotenv": "^16.4.5", diff --git a/apps/viewer/src/components/PhotoLightbox.vue b/apps/viewer/src/components/PhotoLightbox.vue index 132d82d..b35043c 100644 --- a/apps/viewer/src/components/PhotoLightbox.vue +++ b/apps/viewer/src/components/PhotoLightbox.vue @@ -10,7 +10,7 @@ import PhotoActionBar from './viewer/PhotoActionBar.vue'; import HeartParticles from './viewer/HeartParticles.vue'; import InteractionNotice from './viewer/InteractionNotice.vue'; import ThumbnailStrip, { type ThumbnailItem } from './viewer/ThumbnailStrip.vue'; -import { shareOrCopy } from '../support/share'; +import ShareSheet from './viewer/ShareSheet.vue'; import { reducedMotion, toggleReducedMotion } from '../ui/prefs'; import { positionDisplay } from '../viewer/position'; import { MIN_SCALE, panBy, resetZoom, wheelFactor, zoomAt, type ZoomState } from '../viewer/zoom'; @@ -432,21 +432,12 @@ function onDownloadVariant(variant: DownloadVariant): void { // 可选中文本。分享目标为照片深链(含 lang 参数,ActivityPage 组装), // 不分享 OSS 签名 URL —— -const shareState = ref<'idle' | 'copied' | 'failed'>('idle'); +const shareSheetOpen = ref(false); const shareUrlText = ref(''); -let shareResetTimer: ReturnType | undefined; -async function shareCurrent(): Promise { +function shareCurrent(): void { shareUrlText.value = props.shareUrl ?? window.location.href; - const outcome = await shareOrCopy({ title: props.albumTitle ?? '', url: shareUrlText.value }); - shareState.value = outcome === 'copied' ? 'copied' : outcome === 'failed' ? 'failed' : 'idle'; - if (shareResetTimer !== undefined) clearTimeout(shareResetTimer); - // 失败时保留可选中文本(不自动消失),成功反馈数秒后收起。 - if (outcome === 'copied') { - shareResetTimer = setTimeout(() => { - shareState.value = 'idle'; - }, 3000); - } + shareSheetOpen.value = true; } // —— 顶栏「⋯」更多菜单(§5.1:必须有实际功能——减少动画开关) —— @@ -519,7 +510,6 @@ onBeforeUnmount(() => { window.removeEventListener('keydown', onKeydown); document.removeEventListener('pointerdown', onOuterPointerDown, true); document.body.style.overflow = savedBodyOverflow; - if (shareResetTimer !== undefined) clearTimeout(shareResetTimer); stopLocalLikeListener?.(); // 粒子订阅随大图卸载解除(useLikes 侧不残留) // 焦点归还触发元素(瀑布流单元格;深链直达时为 body——无可归还则不动)。 if (savedFocus !== null && savedFocus.isConnected) savedFocus.focus(); @@ -628,14 +618,16 @@ onBeforeUnmount(() => { 点击定位 = 缩略图条共用入口(序列未含该照片时忽略——不伪造定位) --> + + +

{{ t('album.loading') }}

- -

{ margin: 0; color: #d1d5db; } -.share-status { - margin: 0; - border-radius: var(--viewer-radius-pill, 20px); - background: rgba(22, 28, 36, 0.92); - color: #f3f4f6; - padding: 6px 16px; - font-size: 0.8125rem; - pointer-events: auto; - max-width: min(92%, 480px); -} -.share-url { - user-select: all; - word-break: break-all; - color: #9fc3f5; -} .download-failed { margin: 0; border-radius: var(--viewer-radius-pill, 20px); diff --git a/apps/viewer/src/components/album/AlbumHeader.vue b/apps/viewer/src/components/album/AlbumHeader.vue index 117c296..0d056e6 100644 --- a/apps/viewer/src/components/album/AlbumHeader.vue +++ b/apps/viewer/src/components/album/AlbumHeader.vue @@ -1,5 +1,5 @@