修复小程序推荐页系统分享直达作品
同步推荐页当前作品到小程序原生分享目标 保留小程序系统分享路径中的公开作品参数 补充小程序分享目标解析与前端消息发送测试
This commit is contained in:
@@ -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));
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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: '汪汪声浪',
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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 ||
|
||||||
|
|||||||
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