修复拼图生成前订阅授权
新增小程序原生订阅消息授权页,在用户点击后请求生成结果通知授权。 拼图 compile_puzzle_draft 前等待授权页返回或跳过后再发起生成 action。 移除 web-view message 订阅授权路径,改用 storage/hash 回写订阅结果。 补充订阅授权测试、文档和团队踩坑记录。
This commit is contained in:
10
miniprogram/pages/subscribe-message/index.js
Normal file
10
miniprogram/pages/subscribe-message/index.js
Normal file
@@ -0,0 +1,10 @@
|
||||
/* global Page */
|
||||
|
||||
const { GENERATION_RESULT_SUBSCRIBE_TEMPLATE_ID } = require('../../config');
|
||||
const { createSubscribeMessagePage } = require('./index.shared');
|
||||
|
||||
Page(
|
||||
createSubscribeMessagePage(null, {
|
||||
templateId: GENERATION_RESULT_SUBSCRIBE_TEMPLATE_ID,
|
||||
}),
|
||||
);
|
||||
3
miniprogram/pages/subscribe-message/index.json
Normal file
3
miniprogram/pages/subscribe-message/index.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"navigationBarTitleText": "生成通知"
|
||||
}
|
||||
135
miniprogram/pages/subscribe-message/index.shared.js
Normal file
135
miniprogram/pages/subscribe-message/index.shared.js
Normal file
@@ -0,0 +1,135 @@
|
||||
/* global wx, getCurrentPages */
|
||||
|
||||
const SUBSCRIBE_RESULT_STORAGE_KEY = 'genarrative:wechat-subscribe-result';
|
||||
|
||||
function appendSubscribeResult(url, result) {
|
||||
const hashIndex = String(url || '').indexOf('#');
|
||||
const baseUrl =
|
||||
hashIndex >= 0 ? String(url).slice(0, hashIndex) : String(url || '');
|
||||
const rawHash = hashIndex >= 0 ? String(url).slice(hashIndex + 1) : '';
|
||||
const nextHash = rawHash
|
||||
.split('&')
|
||||
.filter((part) => part && !part.startsWith('wx_subscribe_result='))
|
||||
.concat(`wx_subscribe_result=${encodeURIComponent(result)}`)
|
||||
.join('&');
|
||||
return `${baseUrl}#${nextHash}`;
|
||||
}
|
||||
|
||||
function buildSubscribeResultValue(requestId, status, reason) {
|
||||
const segments = [requestId, status];
|
||||
if (reason) {
|
||||
segments.push(encodeURIComponent(reason));
|
||||
}
|
||||
return segments.join(':');
|
||||
}
|
||||
|
||||
function notifyPreviousWebView(requestId, status, reason) {
|
||||
const result = buildSubscribeResultValue(requestId, status, reason);
|
||||
wx.setStorageSync(SUBSCRIBE_RESULT_STORAGE_KEY, result);
|
||||
const pages = getCurrentPages();
|
||||
const previousPage = pages.length >= 2 ? pages[pages.length - 2] : null;
|
||||
if (previousPage && typeof previousPage.setData === 'function') {
|
||||
previousPage.setData({
|
||||
webViewUrl: appendSubscribeResult(previousPage.data.webViewUrl, result),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function resolveSubscribeStatus(result, templateId) {
|
||||
return result && result[templateId] === 'accept'
|
||||
? 'success'
|
||||
: 'skip';
|
||||
}
|
||||
|
||||
function createSubscribeMessagePage(pageContext, options = {}) {
|
||||
const templateId = String(options.templateId || '').trim();
|
||||
const notifyPageResult = (methodThis, status, reason) => {
|
||||
const page = pageContext ?? methodThis;
|
||||
const requestId = page.requestId || '';
|
||||
if (!requestId || page.hasNotifiedSubscribeResult) {
|
||||
return;
|
||||
}
|
||||
page.hasNotifiedSubscribeResult = true;
|
||||
notifyPreviousWebView(requestId, status, reason);
|
||||
};
|
||||
|
||||
return {
|
||||
data: {
|
||||
title: '接收生成结果通知',
|
||||
errorMessage: '',
|
||||
requesting: false,
|
||||
},
|
||||
|
||||
onLoad(query) {
|
||||
const page = pageContext ?? this;
|
||||
page.requestId = String(query.requestId || '');
|
||||
page.hasNotifiedSubscribeResult = false;
|
||||
},
|
||||
|
||||
notifyResult(status, reason) {
|
||||
notifyPageResult(this, status, reason);
|
||||
},
|
||||
|
||||
requestSubscribe() {
|
||||
const page = pageContext ?? this;
|
||||
const requestId = page.requestId || '';
|
||||
if (!requestId) {
|
||||
page.setData({
|
||||
errorMessage: '缺少订阅请求参数。',
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (!templateId) {
|
||||
notifyPageResult(this, 'skip', 'missing_template_id');
|
||||
wx.navigateBack();
|
||||
return;
|
||||
}
|
||||
if (typeof wx.requestSubscribeMessage !== 'function') {
|
||||
notifyPageResult(this, 'skip', 'unsupported');
|
||||
wx.navigateBack();
|
||||
return;
|
||||
}
|
||||
|
||||
page.setData({
|
||||
requesting: true,
|
||||
errorMessage: '',
|
||||
});
|
||||
wx.requestSubscribeMessage({
|
||||
tmplIds: [templateId],
|
||||
success(result) {
|
||||
notifyPageResult(
|
||||
page,
|
||||
resolveSubscribeStatus(result, templateId),
|
||||
'',
|
||||
);
|
||||
wx.navigateBack();
|
||||
},
|
||||
fail(error) {
|
||||
notifyPageResult(
|
||||
page,
|
||||
'skip',
|
||||
error && error.errMsg ? error.errMsg : 'failed',
|
||||
);
|
||||
wx.navigateBack();
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
handleSkip() {
|
||||
notifyPageResult(this, 'skip', 'user_skip');
|
||||
wx.navigateBack();
|
||||
},
|
||||
|
||||
onUnload() {
|
||||
notifyPageResult(this, 'skip', 'page_unload');
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
SUBSCRIBE_RESULT_STORAGE_KEY,
|
||||
appendSubscribeResult,
|
||||
buildSubscribeResultValue,
|
||||
createSubscribeMessagePage,
|
||||
resolveSubscribeStatus,
|
||||
};
|
||||
99
miniprogram/pages/subscribe-message/index.test.js
Normal file
99
miniprogram/pages/subscribe-message/index.test.js
Normal file
@@ -0,0 +1,99 @@
|
||||
import { beforeEach, describe, expect, test, vi } from 'vitest';
|
||||
|
||||
import subscribeMessageBridge from './index.shared.js';
|
||||
|
||||
const TEST_TEMPLATE_ID = 'm5z7BkkBhJGbcH0cdDeHaeRU2tViDEguP38XdrRRCdU';
|
||||
|
||||
const {
|
||||
SUBSCRIBE_RESULT_STORAGE_KEY,
|
||||
appendSubscribeResult,
|
||||
buildSubscribeResultValue,
|
||||
createSubscribeMessagePage,
|
||||
} = subscribeMessageBridge;
|
||||
|
||||
describe('subscribe-message mini program bridge', () => {
|
||||
beforeEach(() => {
|
||||
globalThis.wx = {
|
||||
requestSubscribeMessage: vi.fn(),
|
||||
setStorageSync: vi.fn(),
|
||||
navigateBack: vi.fn(),
|
||||
};
|
||||
globalThis.getCurrentPages = vi.fn(() => []);
|
||||
});
|
||||
|
||||
test('requests subscribe message and notifies previous web-view before returning', () => {
|
||||
const previousPage = {
|
||||
data: { webViewUrl: 'https://web.test/#tab=create' },
|
||||
setData: vi.fn(),
|
||||
};
|
||||
globalThis.getCurrentPages = vi.fn(() => [previousPage, {}]);
|
||||
globalThis.wx.requestSubscribeMessage.mockImplementationOnce((options) => {
|
||||
options.success?.({
|
||||
m5z7BkkBhJGbcH0cdDeHaeRU2tViDEguP38XdrRRCdU: 'accept',
|
||||
});
|
||||
});
|
||||
const page = createSubscribeMessagePage(
|
||||
{
|
||||
setData: vi.fn(),
|
||||
},
|
||||
{ templateId: TEST_TEMPLATE_ID },
|
||||
);
|
||||
page.onLoad({ requestId: 'request-1' });
|
||||
|
||||
page.requestSubscribe();
|
||||
|
||||
expect(globalThis.wx.requestSubscribeMessage).toHaveBeenCalledWith({
|
||||
tmplIds: [TEST_TEMPLATE_ID],
|
||||
success: expect.any(Function),
|
||||
fail: expect.any(Function),
|
||||
});
|
||||
expect(globalThis.wx.setStorageSync).toHaveBeenCalledWith(
|
||||
SUBSCRIBE_RESULT_STORAGE_KEY,
|
||||
'request-1:success',
|
||||
);
|
||||
expect(previousPage.setData).toHaveBeenCalledWith({
|
||||
webViewUrl:
|
||||
'https://web.test/#tab=create&wx_subscribe_result=request-1%3Asuccess',
|
||||
});
|
||||
expect(globalThis.wx.navigateBack).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('skip action notifies previous web-view', () => {
|
||||
const previousPage = {
|
||||
data: { webViewUrl: 'https://web.test/' },
|
||||
setData: vi.fn(),
|
||||
};
|
||||
globalThis.getCurrentPages = vi.fn(() => [previousPage, {}]);
|
||||
const page = createSubscribeMessagePage(
|
||||
{
|
||||
setData: vi.fn(),
|
||||
},
|
||||
{ templateId: TEST_TEMPLATE_ID },
|
||||
);
|
||||
page.onLoad({ requestId: 'request-skip' });
|
||||
|
||||
page.handleSkip();
|
||||
|
||||
expect(globalThis.wx.setStorageSync).toHaveBeenCalledWith(
|
||||
SUBSCRIBE_RESULT_STORAGE_KEY,
|
||||
'request-skip:skip:user_skip',
|
||||
);
|
||||
expect(previousPage.setData).toHaveBeenCalledWith({
|
||||
webViewUrl:
|
||||
'https://web.test/#wx_subscribe_result=request-skip%3Askip%3Auser_skip',
|
||||
});
|
||||
expect(globalThis.wx.navigateBack).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('appendSubscribeResult replaces stale subscribe hash', () => {
|
||||
expect(
|
||||
appendSubscribeResult(
|
||||
'https://web.test/#old=1&wx_subscribe_result=old',
|
||||
'req:skip',
|
||||
),
|
||||
).toBe('https://web.test/#old=1&wx_subscribe_result=req%3Askip');
|
||||
expect(buildSubscribeResultValue('req-1', 'skip', 'user_cancel')).toBe(
|
||||
'req-1:skip:user_cancel',
|
||||
);
|
||||
});
|
||||
});
|
||||
19
miniprogram/pages/subscribe-message/index.wxml
Normal file
19
miniprogram/pages/subscribe-message/index.wxml
Normal file
@@ -0,0 +1,19 @@
|
||||
<view class="subscribe-screen">
|
||||
<view class="subscribe-card">
|
||||
<view class="subscribe-title">{{title}}</view>
|
||||
<view wx:if="{{errorMessage}}" class="subscribe-text subscribe-text--danger">
|
||||
{{errorMessage}}
|
||||
</view>
|
||||
<button
|
||||
class="primary-button"
|
||||
loading="{{requesting}}"
|
||||
disabled="{{requesting}}"
|
||||
bindtap="requestSubscribe"
|
||||
>
|
||||
继续并接收通知
|
||||
</button>
|
||||
<button class="ghost-button" disabled="{{requesting}}" bindtap="handleSkip">
|
||||
仅继续生成
|
||||
</button>
|
||||
</view>
|
||||
</view>
|
||||
58
miniprogram/pages/subscribe-message/index.wxss
Normal file
58
miniprogram/pages/subscribe-message/index.wxss
Normal file
@@ -0,0 +1,58 @@
|
||||
.subscribe-screen {
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 48rpx;
|
||||
background: #0b0f14;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.subscribe-card {
|
||||
width: 100%;
|
||||
max-width: 560rpx;
|
||||
padding: 36rpx;
|
||||
border: 1rpx solid rgba(255, 255, 255, 0.14);
|
||||
border-radius: 12rpx;
|
||||
background: rgba(255, 255, 255, 0.06);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.subscribe-title {
|
||||
font-size: 34rpx;
|
||||
font-weight: 600;
|
||||
line-height: 1.35;
|
||||
color: #f5f7fb;
|
||||
}
|
||||
|
||||
.subscribe-text {
|
||||
margin-top: 16rpx;
|
||||
font-size: 26rpx;
|
||||
line-height: 1.55;
|
||||
color: rgba(245, 247, 251, 0.72);
|
||||
}
|
||||
|
||||
.subscribe-text--danger {
|
||||
color: #ffb4a9;
|
||||
}
|
||||
|
||||
.primary-button,
|
||||
.ghost-button {
|
||||
margin-top: 28rpx;
|
||||
width: 100%;
|
||||
border-radius: 8rpx;
|
||||
font-size: 26rpx;
|
||||
line-height: 2.6;
|
||||
}
|
||||
|
||||
.primary-button {
|
||||
background: #f5f7fb;
|
||||
color: #0b0f14;
|
||||
}
|
||||
|
||||
.ghost-button {
|
||||
margin-top: 20rpx;
|
||||
border: 1rpx solid rgba(255, 255, 255, 0.24);
|
||||
background: transparent;
|
||||
color: rgba(245, 247, 251, 0.86);
|
||||
}
|
||||
@@ -5,7 +5,6 @@ const {
|
||||
API_BASE_URL,
|
||||
DEV_API_BASE_URL,
|
||||
DEV_WEB_VIEW_ENTRY_URL,
|
||||
GENERATION_RESULT_SUBSCRIBE_TEMPLATE_ID,
|
||||
MINI_PROGRAM_APP_ID,
|
||||
MINI_PROGRAM_ENV,
|
||||
WEB_VIEW_ENTRY_URL,
|
||||
@@ -16,13 +15,12 @@ const MINI_PROGRAM_CLIENT_TYPE = 'mini_program';
|
||||
const MINI_PROGRAM_CLIENT_RUNTIME = 'wechat_mini_program';
|
||||
const CLIENT_INSTANCE_STORAGE_KEY = 'genarrative:mini-program-client-instance-id';
|
||||
const PAY_RESULT_STORAGE_KEY = 'genarrative:wechat-pay-result';
|
||||
const SUBSCRIBE_RESULT_STORAGE_KEY = 'genarrative:wechat-subscribe-result';
|
||||
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';
|
||||
const SUBSCRIBE_MESSAGE_TYPE = 'genarrative:request-subscribe-message';
|
||||
const GENERATION_RESULT_SUBSCRIBE_SCENE = 'generation-result';
|
||||
|
||||
function showWebViewShareMenu() {
|
||||
if (typeof wx.showShareMenu !== 'function') {
|
||||
@@ -418,36 +416,6 @@ function requestMiniProgramBindPhone(authToken, wechatPhoneCode, displayName) {
|
||||
});
|
||||
}
|
||||
|
||||
function requestGenerationResultSubscribeMessage() {
|
||||
return new Promise((resolve) => {
|
||||
if (!GENERATION_RESULT_SUBSCRIBE_TEMPLATE_ID) {
|
||||
resolve({ ok: false, reason: 'missing_template_id' });
|
||||
return;
|
||||
}
|
||||
if (typeof wx.requestSubscribeMessage !== 'function') {
|
||||
resolve({ ok: false, reason: 'unsupported' });
|
||||
return;
|
||||
}
|
||||
|
||||
wx.requestSubscribeMessage({
|
||||
tmplIds: [GENERATION_RESULT_SUBSCRIBE_TEMPLATE_ID],
|
||||
success(result) {
|
||||
resolve({
|
||||
ok: result[GENERATION_RESULT_SUBSCRIBE_TEMPLATE_ID] === 'accept',
|
||||
result,
|
||||
});
|
||||
},
|
||||
fail(error) {
|
||||
console.warn('[web-view] request subscribe message failed', error);
|
||||
resolve({
|
||||
ok: false,
|
||||
reason: error && error.errMsg ? error.errMsg : 'failed',
|
||||
});
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async function resolveAuthResult(displayName) {
|
||||
const code = await wxLogin();
|
||||
const response = await requestMiniProgramLogin(code, displayName);
|
||||
@@ -638,8 +606,10 @@ Page({
|
||||
}
|
||||
|
||||
this.consumePayResult();
|
||||
this.consumeSubscribeResult();
|
||||
setTimeout(() => {
|
||||
this.consumePayResult();
|
||||
this.consumeSubscribeResult();
|
||||
}, PAY_RESULT_RECHECK_DELAY_MS);
|
||||
},
|
||||
|
||||
@@ -655,6 +625,18 @@ Page({
|
||||
}
|
||||
},
|
||||
|
||||
consumeSubscribeResult() {
|
||||
const result = wx.getStorageSync(SUBSCRIBE_RESULT_STORAGE_KEY);
|
||||
if (result && this.data.webViewUrl) {
|
||||
wx.removeStorageSync(SUBSCRIBE_RESULT_STORAGE_KEY);
|
||||
this.setData({
|
||||
webViewUrl: appendHashParams(this.data.webViewUrl, {
|
||||
wx_subscribe_result: result,
|
||||
}),
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
async handleGetPhoneNumber(event) {
|
||||
if (!this.data.authResult || !this.data.authResult.token) {
|
||||
this.handleRetryLogin();
|
||||
@@ -745,23 +727,7 @@ Page({
|
||||
},
|
||||
|
||||
handleWebViewMessage(event) {
|
||||
const messages =
|
||||
event && event.detail && Array.isArray(event.detail.data)
|
||||
? event.detail.data
|
||||
: [];
|
||||
const shouldRequestSubscribe = messages.some((message) => {
|
||||
const payload = message && typeof message === 'object' ? message : {};
|
||||
return (
|
||||
payload.type === SUBSCRIBE_MESSAGE_TYPE &&
|
||||
payload.scene === GENERATION_RESULT_SUBSCRIBE_SCENE
|
||||
);
|
||||
});
|
||||
if (shouldRequestSubscribe) {
|
||||
void requestGenerationResultSubscribeMessage();
|
||||
return;
|
||||
}
|
||||
|
||||
// 中文注释:支付由独立 native 页面承接,其他 web-view 消息只保留调试输出。
|
||||
// 中文注释:支付和订阅消息都由独立 native 页面承接,web-view 消息只保留调试输出。
|
||||
console.info('[web-view] message', event.detail);
|
||||
},
|
||||
|
||||
|
||||
Reference in New Issue
Block a user