修复小程序推荐页系统分享直达作品

同步推荐页当前作品到小程序原生分享目标
保留小程序系统分享路径中的公开作品参数
补充小程序分享目标解析与前端消息发送测试
This commit is contained in:
kdletters
2026-06-11 22:30:23 +08:00
parent c5763fdf25
commit d78c11d5b7
6 changed files with 299 additions and 7 deletions

View File

@@ -12,6 +12,9 @@ const {
} = require('../../config'); } = require('../../config');
const { const {
appendHashParams, appendHashParams,
buildWebViewSharePath,
buildWebViewShareTimelineQuery,
resolveShareTargetFromWebViewMessage,
resolveWebViewUrlFromRuntimeConfig, resolveWebViewUrlFromRuntimeConfig,
} = require('./index.shared'); } = require('./index.shared');
@@ -23,7 +26,6 @@ const AUTH_RESULT_STORAGE_KEY = 'genarrative:mini-program-auth-result';
const AUTH_ACTION_LOGIN = 'login'; const AUTH_ACTION_LOGIN = 'login';
const PAY_RESULT_RECHECK_DELAY_MS = 120; const PAY_RESULT_RECHECK_DELAY_MS = 120;
const WEB_VIEW_SHARE_TITLE = '陶泥儿'; const WEB_VIEW_SHARE_TITLE = '陶泥儿';
const WEB_VIEW_SHARE_PATH = '/pages/web-view/index';
function showWebViewShareMenu() { function showWebViewShareMenu() {
if (typeof wx.showShareMenu !== 'function') { if (typeof wx.showShareMenu !== 'function') {
@@ -36,17 +38,25 @@ function showWebViewShareMenu() {
}); });
} }
function buildWebViewShareAppMessage() { function resolveNativeShareQuery(page) {
return (
(page && page._currentShareTarget) ||
(page && page._lastLaunchQuery) ||
{}
);
}
function buildWebViewShareAppMessage(query = {}) {
return { return {
title: WEB_VIEW_SHARE_TITLE, title: WEB_VIEW_SHARE_TITLE,
path: WEB_VIEW_SHARE_PATH, path: buildWebViewSharePath(query),
}; };
} }
function buildWebViewShareTimeline() { function buildWebViewShareTimeline(query = {}) {
return { return {
title: WEB_VIEW_SHARE_TITLE, title: WEB_VIEW_SHARE_TITLE,
query: '', query: buildWebViewShareTimelineQuery(query),
}; };
} }
@@ -669,15 +679,19 @@ Page({
}, },
handleWebViewMessage(event) { handleWebViewMessage(event) {
const shareTarget = resolveShareTargetFromWebViewMessage(event.detail);
if (shareTarget) {
this._currentShareTarget = shareTarget;
}
// 中文注释:支付和订阅消息都由独立 native 页面承接web-view 消息只保留调试输出。 // 中文注释:支付和订阅消息都由独立 native 页面承接web-view 消息只保留调试输出。
console.info('[web-view] message', event.detail); console.info('[web-view] message', event.detail);
}, },
onShareAppMessage() { onShareAppMessage() {
return buildWebViewShareAppMessage(); return buildWebViewShareAppMessage(resolveNativeShareQuery(this));
}, },
onShareTimeline() { onShareTimeline() {
return buildWebViewShareTimeline(); return buildWebViewShareTimeline(resolveNativeShareQuery(this));
}, },
}); });

View File

@@ -1,4 +1,6 @@
const ALLOWED_TARGET_PATHS = new Set(['/works/detail']); const ALLOWED_TARGET_PATHS = new Set(['/works/detail']);
const SHARE_TARGET_MESSAGE_TYPE = 'genarrative:share-target';
const WEB_VIEW_SHARE_PATH = '/pages/web-view/index';
function trimTrailingSlash(value) { function trimTrailingSlash(value) {
return String(value || '').trim().replace(/\/+$/u, ''); return String(value || '').trim().replace(/\/+$/u, '');
@@ -75,6 +77,60 @@ function resolveLaunchTargetQuery(query) {
}; };
} }
function buildWebViewSharePath(query = {}, basePath = WEB_VIEW_SHARE_PATH) {
const launchTarget = resolveLaunchTargetQuery(query);
if (!launchTarget.targetPath) {
return basePath;
}
return appendQuery(basePath, {
targetPath: launchTarget.targetPath,
work: launchTarget.work,
});
}
function buildWebViewShareTimelineQuery(query = {}) {
const launchTarget = resolveLaunchTargetQuery(query);
if (!launchTarget.targetPath) {
return '';
}
return new URLSearchParams({
targetPath: launchTarget.targetPath,
work: launchTarget.work,
}).toString();
}
function normalizeShareTargetMessageData(value) {
const message = value && value.data ? value.data : value;
if (!message || message.type !== SHARE_TARGET_MESSAGE_TYPE) {
return null;
}
const payload = message.payload || {};
const launchTarget = resolveLaunchTargetQuery(payload);
if (!launchTarget.targetPath) {
return null;
}
return {
...launchTarget,
title: String(payload.title || '').trim(),
};
}
function resolveShareTargetFromWebViewMessage(detail) {
const dataList = detail && Array.isArray(detail.data) ? detail.data : [];
for (let index = dataList.length - 1; index >= 0; index -= 1) {
const target = normalizeShareTargetMessageData(dataList[index]);
if (target) {
return target;
}
}
return normalizeShareTargetMessageData(detail);
}
function appendLaunchTargetToEntryUrl(entryUrl, query) { function appendLaunchTargetToEntryUrl(entryUrl, query) {
const launchTarget = resolveLaunchTargetQuery(query); const launchTarget = resolveLaunchTargetQuery(query);
if (!launchTarget.targetPath) { if (!launchTarget.targetPath) {
@@ -123,7 +179,10 @@ module.exports = {
appendHashParams, appendHashParams,
appendLaunchTargetToEntryUrl, appendLaunchTargetToEntryUrl,
appendQuery, appendQuery,
buildWebViewSharePath,
buildWebViewShareTimelineQuery,
normalizeTargetPath, normalizeTargetPath,
resolveShareTargetFromWebViewMessage,
resolveLaunchTargetQuery, resolveLaunchTargetQuery,
resolveWebViewUrlFromRuntimeConfig, resolveWebViewUrlFromRuntimeConfig,
}; };

View File

@@ -4,6 +4,9 @@ import webViewBridge from './index.shared.js';
const { const {
appendLaunchTargetToEntryUrl, appendLaunchTargetToEntryUrl,
buildWebViewSharePath,
buildWebViewShareTimelineQuery,
resolveShareTargetFromWebViewMessage,
resolveWebViewUrlFromRuntimeConfig, resolveWebViewUrlFromRuntimeConfig,
} = webViewBridge; } = webViewBridge;
@@ -53,4 +56,55 @@ describe('mini program web-view launch target', () => {
expect(url.pathname).toBe('/'); expect(url.pathname).toBe('/');
expect(url.searchParams.get('work')).toBeNull(); expect(url.searchParams.get('work')).toBeNull();
}); });
test('keeps public work params in native mini program share paths', () => {
const sharePath = buildWebViewSharePath({
targetPath: '/works/detail',
work: 'BB-12345678',
});
const url = new URL(sharePath, 'https://mini.test');
expect(url.pathname).toBe('/pages/web-view/index');
expect(url.searchParams.get('targetPath')).toBe('/works/detail');
expect(url.searchParams.get('work')).toBe('BB-12345678');
expect(
buildWebViewShareTimelineQuery({
targetPath: '/works/detail',
work: 'BB-12345678',
}),
).toBe('targetPath=%2Fworks%2Fdetail&work=BB-12345678');
});
test('reads the latest H5 recommended work share target from web-view messages', () => {
expect(
resolveShareTargetFromWebViewMessage({
data: [
{
data: {
type: 'genarrative:share-target',
payload: {
targetPath: '/works/detail',
work: 'PZ-0001',
title: '旧作品',
},
},
},
{
data: {
type: 'genarrative:share-target',
payload: {
targetPath: '/works/detail',
work: 'BB-12345678',
title: '汪汪声浪',
},
},
},
],
}),
).toEqual({
targetPath: '/works/detail',
work: 'BB-12345678',
title: '汪汪声浪',
});
});
}); });

View File

@@ -365,6 +365,7 @@ import {
updateVisualNovelWork, updateVisualNovelWork,
} from '../../services/visual-novel-works'; } from '../../services/visual-novel-works';
import { requestGenerationResultSubscribePermission } from '../../services/wechatMiniProgramSubscribe'; import { requestGenerationResultSubscribePermission } from '../../services/wechatMiniProgramSubscribe';
import { postWechatMiniProgramShareTarget } from '../../services/wechatMiniProgramShareTarget';
import { import {
woodenFishClient, woodenFishClient,
type WoodenFishGalleryCardResponse, type WoodenFishGalleryCardResponse,
@@ -741,6 +742,25 @@ function resolveRecommendEntryShareStage(
return 'work-detail'; return 'work-detail';
} }
function postRecommendEntryMiniProgramShareTarget(
entry: PlatformPublicGalleryCard | null | undefined,
) {
if (!entry) {
return false;
}
const publicWorkCode = resolvePlatformPublicWorkCode(entry)?.trim();
if (!publicWorkCode) {
return false;
}
return postWechatMiniProgramShareTarget({
targetPath: '/works/detail',
work: publicWorkCode,
title: entry.worldName,
});
}
function resolveUnsupportedPublicWorkActionMessage( function resolveUnsupportedPublicWorkActionMessage(
entry: PlatformPublicGalleryCard, entry: PlatformPublicGalleryCard,
actionLabel: string, actionLabel: string,
@@ -5250,6 +5270,7 @@ export function PlatformEntryFlowShellImpl({
return; return;
} }
postRecommendEntryMiniProgramShareTarget(entry);
openPublishShareModal({ openPublishShareModal({
title: entry.worldName, title: entry.worldName,
publicWorkCode, publicWorkCode,
@@ -16473,6 +16494,22 @@ export function PlatformEntryFlowShellImpl({
woodenFishRun, woodenFishRun,
}); });
useEffect(() => {
if (
selectionStage !== 'platform' ||
platformBootstrap.platformTab !== 'home' ||
!activeRecommendEntry
) {
return;
}
postRecommendEntryMiniProgramShareTarget(activeRecommendEntry);
}, [
activeRecommendEntry,
platformBootstrap.platformTab,
selectionStage,
]);
useEffect(() => { useEffect(() => {
if ( if (
isDesktopLayout || isDesktopLayout ||

View File

@@ -0,0 +1,67 @@
/* @vitest-environment jsdom */
import { afterEach, describe, expect, test, vi } from 'vitest';
import {
buildWechatMiniProgramShareTargetMessage,
postWechatMiniProgramShareTarget,
} from './wechatMiniProgramShareTarget';
afterEach(() => {
vi.restoreAllMocks();
Reflect.deleteProperty(window, 'wx');
window.history.replaceState(null, '', '/');
});
describe('wechatMiniProgramShareTarget', () => {
test('builds a compact share target message for mini program native share', () => {
expect(
buildWechatMiniProgramShareTargetMessage({
targetPath: '/works/detail',
work: ' BB-12345678 ',
title: ' 汪汪声浪 ',
}),
).toEqual({
type: 'genarrative:share-target',
payload: {
targetPath: '/works/detail',
work: 'BB-12345678',
title: '汪汪声浪',
},
});
});
test('posts the current recommended work to mini program web-view host', () => {
const postMessage = vi.fn();
window.history.replaceState(
null,
'',
'/?clientRuntime=wechat_mini_program',
);
window.wx = {
miniProgram: {
postMessage,
},
};
expect(
postWechatMiniProgramShareTarget({
targetPath: '/works/detail',
work: 'BB-12345678',
title: '汪汪声浪',
}),
).toBe(true);
expect(postMessage).toHaveBeenCalledWith({
data: {
type: 'genarrative:share-target',
payload: {
targetPath: '/works/detail',
work: 'BB-12345678',
title: '汪汪声浪',
},
},
});
});
});

View File

@@ -0,0 +1,61 @@
import { isWechatMiniProgramWebViewRuntime } from './authService';
const MESSAGE_TYPE = 'genarrative:share-target';
export type WechatMiniProgramShareTarget = {
targetPath: '/works/detail';
work: string;
title?: string | null;
};
function normalizeShareTarget(
target: WechatMiniProgramShareTarget | null | undefined,
) {
const work = target?.work?.trim() ?? '';
if (!work) {
return null;
}
return {
targetPath: '/works/detail' as const,
work,
title: target?.title?.trim() || undefined,
};
}
export function buildWechatMiniProgramShareTargetMessage(
target: WechatMiniProgramShareTarget | null | undefined,
) {
const normalizedTarget = normalizeShareTarget(target);
return normalizedTarget
? {
type: MESSAGE_TYPE,
payload: normalizedTarget,
}
: null;
}
export function postWechatMiniProgramShareTarget(
target: WechatMiniProgramShareTarget | null | undefined,
) {
if (
typeof window === 'undefined' ||
!isWechatMiniProgramWebViewRuntime() ||
typeof window.wx?.miniProgram?.postMessage !== 'function'
) {
return false;
}
const message = buildWechatMiniProgramShareTargetMessage(target);
if (!message) {
return false;
}
// 中文注释:微信 web-view 会在分享等时机把 postMessage 数据交给原生页,
// 小程序页据此把右上角系统分享指向当前推荐作品。
window.wx.miniProgram.postMessage({
data: message,
});
return true;
}