修复小程序推荐页系统分享直达作品
同步推荐页当前作品到小程序原生分享目标 保留小程序系统分享路径中的公开作品参数 补充小程序分享目标解析与前端消息发送测试
This commit is contained in:
@@ -12,6 +12,9 @@ const {
|
||||
} = require('../../config');
|
||||
const {
|
||||
appendHashParams,
|
||||
buildWebViewSharePath,
|
||||
buildWebViewShareTimelineQuery,
|
||||
resolveShareTargetFromWebViewMessage,
|
||||
resolveWebViewUrlFromRuntimeConfig,
|
||||
} = require('./index.shared');
|
||||
|
||||
@@ -23,7 +26,6 @@ const AUTH_RESULT_STORAGE_KEY = 'genarrative:mini-program-auth-result';
|
||||
const AUTH_ACTION_LOGIN = 'login';
|
||||
const PAY_RESULT_RECHECK_DELAY_MS = 120;
|
||||
const WEB_VIEW_SHARE_TITLE = '陶泥儿';
|
||||
const WEB_VIEW_SHARE_PATH = '/pages/web-view/index';
|
||||
|
||||
function showWebViewShareMenu() {
|
||||
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 {
|
||||
title: WEB_VIEW_SHARE_TITLE,
|
||||
path: WEB_VIEW_SHARE_PATH,
|
||||
path: buildWebViewSharePath(query),
|
||||
};
|
||||
}
|
||||
|
||||
function buildWebViewShareTimeline() {
|
||||
function buildWebViewShareTimeline(query = {}) {
|
||||
return {
|
||||
title: WEB_VIEW_SHARE_TITLE,
|
||||
query: '',
|
||||
query: buildWebViewShareTimelineQuery(query),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -669,15 +679,19 @@ Page({
|
||||
},
|
||||
|
||||
handleWebViewMessage(event) {
|
||||
const shareTarget = resolveShareTargetFromWebViewMessage(event.detail);
|
||||
if (shareTarget) {
|
||||
this._currentShareTarget = shareTarget;
|
||||
}
|
||||
// 中文注释:支付和订阅消息都由独立 native 页面承接,web-view 消息只保留调试输出。
|
||||
console.info('[web-view] message', event.detail);
|
||||
},
|
||||
|
||||
onShareAppMessage() {
|
||||
return buildWebViewShareAppMessage();
|
||||
return buildWebViewShareAppMessage(resolveNativeShareQuery(this));
|
||||
},
|
||||
|
||||
onShareTimeline() {
|
||||
return buildWebViewShareTimeline();
|
||||
return buildWebViewShareTimeline(resolveNativeShareQuery(this));
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
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) {
|
||||
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) {
|
||||
const launchTarget = resolveLaunchTargetQuery(query);
|
||||
if (!launchTarget.targetPath) {
|
||||
@@ -123,7 +179,10 @@ module.exports = {
|
||||
appendHashParams,
|
||||
appendLaunchTargetToEntryUrl,
|
||||
appendQuery,
|
||||
buildWebViewSharePath,
|
||||
buildWebViewShareTimelineQuery,
|
||||
normalizeTargetPath,
|
||||
resolveShareTargetFromWebViewMessage,
|
||||
resolveLaunchTargetQuery,
|
||||
resolveWebViewUrlFromRuntimeConfig,
|
||||
};
|
||||
|
||||
@@ -4,6 +4,9 @@ import webViewBridge from './index.shared.js';
|
||||
|
||||
const {
|
||||
appendLaunchTargetToEntryUrl,
|
||||
buildWebViewSharePath,
|
||||
buildWebViewShareTimelineQuery,
|
||||
resolveShareTargetFromWebViewMessage,
|
||||
resolveWebViewUrlFromRuntimeConfig,
|
||||
} = webViewBridge;
|
||||
|
||||
@@ -53,4 +56,55 @@ describe('mini program web-view launch target', () => {
|
||||
expect(url.pathname).toBe('/');
|
||||
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: '汪汪声浪',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -365,6 +365,7 @@ import {
|
||||
updateVisualNovelWork,
|
||||
} from '../../services/visual-novel-works';
|
||||
import { requestGenerationResultSubscribePermission } from '../../services/wechatMiniProgramSubscribe';
|
||||
import { postWechatMiniProgramShareTarget } from '../../services/wechatMiniProgramShareTarget';
|
||||
import {
|
||||
woodenFishClient,
|
||||
type WoodenFishGalleryCardResponse,
|
||||
@@ -741,6 +742,25 @@ function resolveRecommendEntryShareStage(
|
||||
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(
|
||||
entry: PlatformPublicGalleryCard,
|
||||
actionLabel: string,
|
||||
@@ -5250,6 +5270,7 @@ export function PlatformEntryFlowShellImpl({
|
||||
return;
|
||||
}
|
||||
|
||||
postRecommendEntryMiniProgramShareTarget(entry);
|
||||
openPublishShareModal({
|
||||
title: entry.worldName,
|
||||
publicWorkCode,
|
||||
@@ -16473,6 +16494,22 @@ export function PlatformEntryFlowShellImpl({
|
||||
woodenFishRun,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
selectionStage !== 'platform' ||
|
||||
platformBootstrap.platformTab !== 'home' ||
|
||||
!activeRecommendEntry
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
postRecommendEntryMiniProgramShareTarget(activeRecommendEntry);
|
||||
}, [
|
||||
activeRecommendEntry,
|
||||
platformBootstrap.platformTab,
|
||||
selectionStage,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
isDesktopLayout ||
|
||||
|
||||
67
src/services/wechatMiniProgramShareTarget.test.ts
Normal file
67
src/services/wechatMiniProgramShareTarget.test.ts
Normal 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: '汪汪声浪',
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
61
src/services/wechatMiniProgramShareTarget.ts
Normal file
61
src/services/wechatMiniProgramShareTarget.ts
Normal 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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user