feat: 优化微信订阅消息体系并新增约课取消通知与开课前1小时提醒

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
richarjiang
2026-09-10 16:20:13 +08:00
parent d32f592e54
commit 3e049d2c1d
26 changed files with 1268 additions and 44 deletions

View File

@@ -6,9 +6,11 @@ import type {
SubscriptionMessageRequestItem,
SubscriptionMessageTemplate,
SubscriptionMessageTemplateConfig,
SubscriptionQuotaItem,
SubscriptionQuotasResponse,
UserProfileResponse,
} from '@mp-pilates/shared'
import { post } from './request'
import { get, post } from './request'
type TemplateResult = SubscriptionMessageRequestItem['result'] | 'tmplIds empty' | 'err' | 'undefined'
@@ -163,11 +165,22 @@ function normalizeSubscribeResults(
.filter((item): item is SubscriptionMessageRequestItem => item !== null)
}
export const SUBSCRIBE_BUNDLE_BOOKING: SubscriptionMessageScene[] = [
SubscriptionMessageScene.BOOKING_CREATED,
SubscriptionMessageScene.CLASS_REMINDER,
SubscriptionMessageScene.BOOKING_CANCELLED,
]
export const SUBSCRIBE_BUNDLE_CANCEL: SubscriptionMessageScene[] = [
SubscriptionMessageScene.BOOKING_CANCELLED,
SubscriptionMessageScene.CLASS_REMINDER,
]
/**
* 在当前调用栈同步调起 `uni.requestSubscribeMessage`。
* 在当前调用栈同步调起 `uni.requestSubscribeMessage`(支持多场景组合打包,最多 3 个模板)
* 微信要求授权框必须落在 tap / 支付 success 的同步栈里,因此这里不能先 `await`。
*/
export function requestSubscriptionMessage(scene: SubscriptionMessageScene): Promise<SubscriptionMessageRequestItem[]> {
export function requestSubscriptionBundle(scenes: SubscriptionMessageScene[]): Promise<SubscriptionMessageRequestItem[]> {
if (!isMpWeixin()) {
return Promise.resolve([])
}
@@ -177,15 +190,28 @@ export function requestSubscriptionMessage(scene: SubscriptionMessageScene): Pro
return Promise.reject(new Error('订阅消息模板尚未初始化,请重新进入页面后重试'))
}
const templates = getTemplatesByScene(config, scene)
// 按场景收集模板,去重 templateId微信单次最多支持 3 个模板
const templateMap = new Map<string, SubscriptionMessageTemplate>()
for (const scene of scenes) {
const list = getTemplatesByScene(config, scene)
for (const tpl of list) {
if (tpl.templateId && !templateMap.has(tpl.templateId)) {
templateMap.set(tpl.templateId, tpl)
}
if (templateMap.size >= 3) break
}
if (templateMap.size >= 3) break
}
const templates = Array.from(templateMap.values())
if (templates.length === 0) {
console.error('[subscribe] no templates matched scene', stringifyDebugPayload({ scene, config, debugContext: getSubscribeDebugContext() }))
console.error('[subscribe] no templates matched bundle', stringifyDebugPayload({ scenes, config, debugContext: getSubscribeDebugContext() }))
return Promise.resolve([])
}
const templateIds = templates.map((item) => item.templateId)
const debugContext = getSubscribeDebugContext()
console.log('[subscribe] requestSubscribeMessage:start', stringifyDebugPayload({ scene, templateIds, templates, debugContext }))
console.log('[subscribe] requestSubscriptionBundle:start', stringifyDebugPayload({ scenes, templateIds, templates, debugContext }))
return new Promise<SubscriptionMessageRequestItem[]>((resolve, reject) => {
uni.requestSubscribeMessage({
@@ -193,22 +219,78 @@ export function requestSubscriptionMessage(scene: SubscriptionMessageScene): Pro
success: (res) => {
const response = res as RequestSubscribeMessageSuccess
const requests = normalizeSubscribeResults(templates, response)
console.log('[subscribe] requestSubscribeMessage:success', stringifyDebugPayload({ scene, response, templateIds, debugContext }))
console.log('[subscribe] requestSubscribeMessage:normalized', stringifyDebugPayload({ scene, result: response, requests, templateIds, debugContext }))
console.log('[subscribe] requestSubscriptionBundle:success', stringifyDebugPayload({ scenes, response, templateIds, debugContext }))
console.log('[subscribe] requestSubscriptionBundle:normalized', stringifyDebugPayload({ scenes, result: response, requests, templateIds, debugContext }))
void reportResults(requests)
.then(() => resolve(requests))
.catch(reject)
},
fail: (err) => {
console.error('[subscribe] requestSubscribeMessage:fail', stringifyDebugPayload({ scene, error: err, templateIds, debugContext }))
reject(buildSubscribeError(err as RequestSubscribeMessageFail, scene, templateIds))
console.error('[subscribe] requestSubscriptionBundle:fail', stringifyDebugPayload({ scenes, error: err, templateIds, debugContext }))
reject(buildSubscribeError(err as RequestSubscribeMessageFail, scenes[0], templateIds))
},
})
})
}
export function requestSubscriptionMessage(scene: SubscriptionMessageScene): Promise<SubscriptionMessageRequestItem[]> {
return requestSubscriptionBundle([scene])
}
/**
* 约课三合一组合授权包含约课成功确认、开课前1小时提醒、课程取消通知
* 一次点击,三个场景额度同时 +1
*/
export function requestBookingBundleSubscriptionMessage(): Promise<SubscriptionMessageRequestItem[]> {
return requestSubscriptionBundle(SUBSCRIBE_BUNDLE_BOOKING)
}
/**
* 兼容原有命名,直接升级为约课三合一组合授权
*/
export function requestBookingCreatedSubscriptionMessage(): Promise<SubscriptionMessageRequestItem[]> {
return requestSubscriptionMessage(SubscriptionMessageScene.BOOKING_CREATED)
return requestBookingBundleSubscriptionMessage()
}
/**
* 取消预约时的组合授权:课程取消通知 + 下次上课提醒
*/
export function requestBookingCancelSubscriptionMessage(): Promise<SubscriptionMessageRequestItem[]> {
return requestSubscriptionBundle(SUBSCRIBE_BUNDLE_CANCEL)
}
/**
* 上课前1小时提醒单项授权
*/
export function requestClassReminderSubscriptionMessage(): Promise<SubscriptionMessageRequestItem[]> {
return requestSubscriptionMessage(SubscriptionMessageScene.CLASS_REMINDER)
}
/**
* 获取当前登录用户的各订阅场景额度水位
*/
export async function fetchUserSubscriptionQuotas(): Promise<SubscriptionQuotaItem[]> {
try {
const res = await get<SubscriptionQuotasResponse>('/user/subscription-messages/quotas')
return res.quotas || []
} catch (error) {
console.warn('[subscribe] fetchUserSubscriptionQuotas failed', error)
return []
}
}
/**
* 引导打开微信系统设置页,供用户恢复通知授权
*/
export function openSubscribeSettings(): Promise<boolean> {
return new Promise<boolean>((resolve) => {
uni.openSetting({
success: (res) => {
resolve(!!res.authSetting)
},
fail: () => resolve(false),
})
})
}
export async function requestAdminBookingSubscriptionCount(): Promise<UserProfileResponse | null> {