From 3bba8d1dc523fb8dceec23e38a2fe02729af561d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B5=85=E5=BF=B5?= Date: Tue, 22 Sep 2026 18:20:50 +0800 Subject: [PATCH] =?UTF-8?q?feat(admin,viewer,ui04):=20E06=20management=20p?= =?UTF-8?q?review=20and=20v2=20integration=20=E2=80=94=20real-component=20?= =?UTF-8?q?preview=20page,=20form=20mode=20fields,=20README?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - viewer /embed-preview route: real EmbedStream with sample photography (picsum seeds), zero requests/WS (preview mode), postMessage config + simulated bursts through the real validation pipeline - EmbedStream: preview prop (disables real WS subscription per §8.2 isolation) + exposed simulateBurst - admin form: displayMode radio (new default), fixed 195x280 readonly, speed slider, reactions switch; v1 layout fields hidden in v2 (not silently effective) - admin preview: v2 branch embeds the real preview iframe, white v2 handle, simulate-like tool button with sample-data note - README-embed: v2 section (integration, hard sizing/docking, interactions/limits, artifact/version caching) - tests: admin vitest 83 (+1 v2 placement), admin e2e 5 (v1 pinned carousel regression + full v2 chain incl. preview WYSIWYG heart and ov settings projection), viewer 244, v2 e2e 16 Note: part of the admin-side E06 changes (form/preview/utils/locales, mid-state of e2e spec) was swept into parallel commit eebf3b3 by a concurrent session's git add; this commit carries the final state of the remaining files and completes the set. Co-Authored-By: Claude Code --- apps/admin/e2e/embed-slots.spec.ts | 13 +- apps/viewer/src/pages/EmbedPreviewPage.vue | 122 ++++++++++++++++++ apps/viewer/src/pages/embed/EmbedStream.vue | 30 ++++- apps/viewer/src/pages/embed/ReactionLayer.vue | 2 +- apps/viewer/src/router/index.ts | 9 ++ docs/plans/progress/UI04-验收记录.md | 27 +++- packages/embed-loader/README-embed.md | 52 ++++++++ 7 files changed, 248 insertions(+), 7 deletions(-) create mode 100644 apps/viewer/src/pages/EmbedPreviewPage.vue diff --git a/apps/admin/e2e/embed-slots.spec.ts b/apps/admin/e2e/embed-slots.spec.ts index 0d1d399..f2b78fb 100644 --- a/apps/admin/e2e/embed-slots.spec.ts +++ b/apps/admin/e2e/embed-slots.spec.ts @@ -224,12 +224,21 @@ test.describe.serial('embed slot management (P4b Task 5)', () => { await expect(page.getByTestId('slot-form-interval')).toHaveCount(0) await expect(page.getByTestId('slot-form-size')).toHaveCount(0) - // 真实组件预览(§8.2):iframe 挂载 + 模拟点赞按钮在工具区。 + // 真实组件预览(§8.2):iframe 挂载 + 模拟点赞按钮在工具区;预览帧内为 + // 真实 EmbedStream(.stream-viewport)且模拟点赞出心形(WYSIWYG 证据)。 await expect(page.getByTestId('embed-preview-v2-iframe')).toBeVisible() await expect(page.getByTestId('embed-preview-sim-like')).toBeVisible() + const previewFrame = page + .frames() + .find((f) => f.url().includes('/embed-preview')) + expect(previewFrame).toBeDefined() + await expect(previewFrame!.locator('.stream-viewport')).toHaveCount(1) + await page.getByTestId('embed-preview-sim-like').click() + await expect(previewFrame!.locator('.heart').first()).toBeVisible({ timeout: 5_000 }) // 速度调整(24 px/s):滑轨点击定位((24-8)/(32-8)*180px ≈ 120)后断言值。 - await page.getByTestId('slot-form-speed').click({ position: { x: 120, y: 10 } }) + const runway = page.getByTestId('slot-form-speed').locator('.el-slider__runway') + await runway.click({ position: { x: 120, y: 4 } }) await expect(page.getByTestId('slot-form-speed-value')).toContainText('24') await page.getByTestId('slot-form-submit').click() await expect(page.getByTestId('slot-form-dialog')).toBeHidden() diff --git a/apps/viewer/src/pages/EmbedPreviewPage.vue b/apps/viewer/src/pages/EmbedPreviewPage.vue new file mode 100644 index 0000000..53781b4 --- /dev/null +++ b/apps/viewer/src/pages/EmbedPreviewPage.vue @@ -0,0 +1,122 @@ + + + + + + diff --git a/apps/viewer/src/pages/embed/EmbedStream.vue b/apps/viewer/src/pages/embed/EmbedStream.vue index 7917b25..dbbd477 100644 --- a/apps/viewer/src/pages/embed/EmbedStream.vue +++ b/apps/viewer/src/pages/embed/EmbedStream.vue @@ -33,6 +33,9 @@ const props = defineProps<{ /** 组件活动闸门(收起/隐藏/不可见/gone → false,停滚动与轮询)。 */ active: boolean; reducedMotion: boolean; + /** 管理预览模式(E06 §8.2):禁用真实 WS 订阅(隔离——不上报/不提交/ + * 不连真实相册房间);模拟事件经 simulateBurst 注入真实校验管线。 */ + preview?: boolean; }>(); const emit = defineEmits<{ @@ -111,8 +114,11 @@ const firstKeyByPhoto = computed(() => { }); // ── E05 气泡:真实 burst → 节流合并 → 照片交集下侧出泡(§5.2)── +// 预览模式禁用真实订阅(§8.2 隔离:不连相册房间;模拟事件走 ingestForTest)。 const albumTokenRef = computed(() => props.albumToken); -const reactionsEnabledRef = computed(() => props.reactions && props.albumToken !== null); +const reactionsEnabledRef = computed( + () => props.reactions && props.albumToken !== null && props.preview !== true, +); const activeRef = computed(() => props.active); const reactions = useEmbedReactions({ @@ -131,6 +137,23 @@ const reactions = useEmbedReactions({ }, }); +/** 管理预览模拟点赞入口(E06 §8.2「事件明确是测试模拟」):经真实校验 + * 管线(去重/节流/可见归属/抽样)注入,不触网络。 */ +function simulateBurst(photoId: string, delta: number): void { + reactions.ingestForTest({ + type: 'likes.burst', + photoId, + albumId: 'preview', + delta, + total: 0, + countVersion: 0, + burstId: `preview-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`, + originBatchIds: [], + }); +} + +defineExpose({ simulateBurst }); + // 收起/隐藏/换绑/减少动效 → 清粒子(§5.3)。 watch(activeRef, (value) => { if (!value) layerEl.value?.clear(); @@ -383,8 +406,9 @@ onBeforeUnmount(() => {
{{ t('embed.comingSoon') }}
- - + + diff --git a/apps/viewer/src/pages/embed/ReactionLayer.vue b/apps/viewer/src/pages/embed/ReactionLayer.vue index d7e9d3b..be1cf68 100644 --- a/apps/viewer/src/pages/embed/ReactionLayer.vue +++ b/apps/viewer/src/pages/embed/ReactionLayer.vue @@ -75,7 +75,7 @@ onBeforeUnmount(() => {