feat(app,viewer): WeChat card share + 'Share to...' label + site og tags
- 按钮文字「分享到微信/其他应用」→「分享到...」(用户需求) - 微信卡片分享(fluwx 6):WECHAT_APPID 经 --dart-define 注入;已配置且 装微信 → WXWebpage 卡片(标题=相册名、描述=日期·地点、缩略图=封面压缩 240px PNG ≤32KB、点击进观众链接);未配置/未装 → 回退系统分享面板 - viewer index.html 加站点级 og 标签(微信/QQ 抓取链接预览用;每相册动态 og 需服务端注入,登记后续增强) - 397/397 全绿、analyze 0 Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
@@ -321,11 +321,24 @@ test.describe.serial('E04 interactions: pause, click lock, keyboard, destroy', (
|
||||
const target = inside.locator('a.stream-photo').first();
|
||||
const photoId = await target.getAttribute('data-photo-id');
|
||||
expect(photoId).toBeTruthy();
|
||||
const popupPromise = page.waitForEvent('popup', { timeout: 5000 });
|
||||
// 宿主内弹框(用户决策 2026-09-23):不新开标签,宿主页覆盖层 + 详情 iframe。
|
||||
let popupOpened = false;
|
||||
page.once('popup', () => {
|
||||
popupOpened = true;
|
||||
});
|
||||
await target.click();
|
||||
const popup = await popupPromise;
|
||||
expect(popup.url()).toContain(`/p/${photoId}`);
|
||||
await popup.close();
|
||||
const modalFrame = page.locator('iframe[title="PhotoLive photo detail"]');
|
||||
await modalFrame.waitFor({ timeout: 5000 });
|
||||
expect(popupOpened).toBe(false);
|
||||
await expect
|
||||
.poll(async () => {
|
||||
const f = page.frames().find((fr) => (fr.url().includes('/p/') ?? false));
|
||||
return f?.url() ?? '';
|
||||
})
|
||||
.toContain(`/p/${photoId}`);
|
||||
await page.screenshot({ path: `${OUT_ROOT}/actual/server/photo-modal.png` });
|
||||
await page.keyboard.press('Escape');
|
||||
await expect(modalFrame).toHaveCount(0);
|
||||
});
|
||||
|
||||
test('drag beyond 8px cancels click (§4.1)', async ({ page }) => {
|
||||
|
||||
@@ -5,6 +5,13 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<!-- 无 JS 时的兜底标题;运行时由 App.vue 按 i18n 覆写 -->
|
||||
<title>现场相册</title>
|
||||
<!-- 分享链接卡片的站点级 og 标签(UI06):微信/QQ 等抓取器不执行 JS,
|
||||
读源码生成链接预览——此处提供全站默认;每相册动态 og(标题/封面)
|
||||
需服务端按 token 注入,属后续增强。 -->
|
||||
<meta property="og:site_name" content="PhotoLive" />
|
||||
<meta property="og:title" content="现场相册 · PhotoLive" />
|
||||
<meta property="og:description" content="扫码或点击查看活动现场照片直播" />
|
||||
<meta property="og:type" content="website" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -25,7 +25,10 @@ export type EmbedUpMessage =
|
||||
| { photoliveEmbed: true; type: 'resize'; height: number }
|
||||
| { photoliveEmbed: true; type: 'collapse' }
|
||||
| { photoliveEmbed: true; type: 'config'; configVersion: number; settings: EmbedSlotSettings }
|
||||
| { photoliveEmbed: true; type: 'destroy' };
|
||||
| { photoliveEmbed: true; type: 'destroy' }
|
||||
/** 照片详情宿主内弹框(用户决策 2026-09-23):url 为观众端深链(同源
|
||||
* viewer 产物域,宿主 loader 校验后内嵌 iframe 弹框,不新开标签)。 */
|
||||
| { photoliveEmbed: true; type: 'open-photo'; url: string };
|
||||
|
||||
/** 宿主源(referrer 推导);取不到(被隐去/直接打开)为 null。 */
|
||||
export function embedParentOrigin(): string | null {
|
||||
|
||||
@@ -18,6 +18,7 @@ import { useRoute } from 'vue-router';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useEmbedFeed } from '../embed/useEmbedFeed';
|
||||
import { clampEmbedIntervalMs, EMBED_CAROUSEL_DEFAULT_MS } from '../embed/feed';
|
||||
import { postEmbedMessage } from '../embed/postMessage';
|
||||
import EmbedCarousel from './embed/EmbedCarousel.vue';
|
||||
import EmbedStream from './embed/EmbedStream.vue';
|
||||
|
||||
@@ -131,7 +132,13 @@ function openPhoto(photoId: string): void {
|
||||
if (albumToken === undefined) return;
|
||||
const url = new URL(`/a/${encodeURIComponent(albumToken)}/p/${encodeURIComponent(photoId)}`, window.location.origin);
|
||||
url.searchParams.set('lang', feed.resolvedLocale ?? 'zh-CN');
|
||||
// noopener:新标签页不持有 opener(sandbox allow-popups-to-escape-sandbox 配合)。
|
||||
// 宿主内弹框(用户决策 2026-09-23):有父窗(loader 嵌入)→ postMessage
|
||||
// 由宿主弹框内嵌详情页,不跳出当前网站;无父窗(直接打开/预览)→ 新标签
|
||||
// noopener(sandbox allow-popups-to-escape-sandbox 配合)。
|
||||
if (window.parent !== window) {
|
||||
postEmbedMessage({ photoliveEmbed: true, type: 'open-photo', url: url.toString() });
|
||||
return;
|
||||
}
|
||||
window.open(url, '_blank', 'noopener,noreferrer');
|
||||
}
|
||||
</script>
|
||||
@@ -143,6 +150,7 @@ function openPhoto(photoId: string): void {
|
||||
<EmbedStream
|
||||
:photos="feed.status === 'ready' ? feed.photos : []"
|
||||
:album-token="feed.album?.publicToken ?? null"
|
||||
:album-title="feed.album?.title ?? ''"
|
||||
:photo-url="streamPhotoUrl"
|
||||
:speed="streamSpeed"
|
||||
:reactions="streamReactions"
|
||||
|
||||
@@ -101,6 +101,7 @@ onBeforeUnmount(() => {
|
||||
ref="streamEl"
|
||||
:photos="SAMPLE_PHOTOS"
|
||||
album-token="preview"
|
||||
album-title="PhotoLive 相册 · 预览"
|
||||
:photo-url="previewPhotoUrl"
|
||||
:speed="speed"
|
||||
:reactions="reactions"
|
||||
|
||||
@@ -24,6 +24,8 @@ const props = defineProps<{
|
||||
photos: PublicEmbedPhoto[];
|
||||
/** 当前公开相册 token(气泡订阅目标;未解析为 null)。 */
|
||||
albumToken: string | null;
|
||||
/** 相册标题(顶部叠加展示,用户决策 2026-09-23)。 */
|
||||
albumTitle?: string;
|
||||
/** 观众深链构造器(真实 <a> href;点击经锁定目标受控打开,§4.1)。 */
|
||||
photoUrl: (photoId: string) => string;
|
||||
/** 自动上滚速度(CSS px/s)。 */
|
||||
@@ -402,6 +404,11 @@ onBeforeUnmount(() => {
|
||||
>
|
||||
</a>
|
||||
</div>
|
||||
<!-- 顶部相册标题(用户决策 2026-09-23):叠加在照片上(不占独立高度,
|
||||
照片窗仍是全屏照片带),左侧 12px、避让右侧 44px 收起区;渐变底 +
|
||||
text-shadow 保证可读;空标题(未解析)不渲染。 -->
|
||||
<p v-if="albumTitle !== ''" class="stream-title" aria-hidden="true">{{ albumTitle }}</p>
|
||||
|
||||
<!-- StateOverlay(§2.2):空相册紧凑空态(收起入口保持可用)。 -->
|
||||
<div v-if="photos.length === 0" class="stream-empty" role="note">
|
||||
{{ t('embed.comingSoon') }}
|
||||
@@ -486,6 +493,21 @@ onBeforeUnmount(() => {
|
||||
background: #f6f8fb;
|
||||
object-fit: cover;
|
||||
}
|
||||
.stream-title {
|
||||
position: absolute;
|
||||
top: 11px;
|
||||
left: 12px;
|
||||
right: 56px; /* 避让右上收起触区 */
|
||||
z-index: 2;
|
||||
margin: 0;
|
||||
font: 600 12px/1.3 system-ui, -apple-system, 'Segoe UI', 'PingFang SC', sans-serif;
|
||||
color: rgba(255, 255, 255, 0.94);
|
||||
text-shadow: 0 1px 3px rgba(9, 14, 20, 0.65);
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
pointer-events: none;
|
||||
}
|
||||
/* 收起入口(§2.4;效果示例 .collapse):44×44 触区右上、可视圆 24×24
|
||||
* 内缩 6px(#ffffffd1 底、− 字形 19px #647084)。 */
|
||||
.stream-collapse {
|
||||
|
||||
Reference in New Issue
Block a user