// 对订阅弹窗状态进行控制,包括订阅次数控制、订阅弹窗、订阅结果
|
// 订阅频控接入步骤:
|
// 1. 定义页面对应订阅弹窗type,补充到subscribeFields列表中,用于初次接入小程序清空订阅次数
|
// 2. 在页面中引入subscribe.ts,调用addSubscribeCount方法,传入type、模板id列表、频控次数limit,返回是否可以继续订阅
|
// 3. (若弹窗内全部模板同意时不需要继续弹窗)调用Taro.requestSubscribeMessage订阅消息,在订阅成功后success函数中调用checkSubscribeStatus方法,传入type、模板id列表、订阅结果result、频控次数limit,用于检查订阅勾选状态
|
|
import Taro from "@tarojs/taro";
|
import { checkOtp } from "./tools";
|
import { saveUserSubscribeMessage } from "../robotaxi/api/subscribe";
|
import {
|
OPERATION_COUPON_SUBSCRIBE_CONFIG,
|
ORDER_SUCCESS_SUBSCRIBE_CONFIG,
|
SHARE_TRIP_SUBSCRIBE_CONFIG,
|
SubscribeSceneConfig
|
} from "../robotaxi/constants/subscribe";
|
|
// 需要频控的订阅字段,用于清空订阅次数
|
const subscribeFields = [
|
"STATION_DETAIL",
|
"PRICE_INFO",
|
"STATION_LIST",
|
"VOUCHER_ENTRY",
|
];
|
const ORDER_SUCCESS_CACHE_EXPIRE_MS = 10 * 60 * 1000; // 下单成功订阅缓存有效期(毫秒)
|
let isOrderSuccessSubscribeRequesting = false; // 下单成功订阅请求中标记,避免重复触发
|
let orderSuccessSubscribePromise: Promise<void> | null = null; // 下单成功订阅弹窗进行中的 Promise,结束后 resolve
|
let orderSuccessSubscribeResolve: (() => void) | null = null; // 上述 Promise 的 resolve 引用
|
let pendingOrderSuccessResult: Record<string, string> | null = null; // 点击阶段缓存的订阅返回结果
|
let pendingOrderSuccessAcceptedTemplateIds: string[] = []; // 点击阶段缓存的已同意模板
|
let pendingOrderSuccessCapturedAt = 0; // 点击阶段缓存写入时间戳
|
|
// 增加订阅次数并返回是否可以继续订阅
|
/**
|
* @param {string} type 订阅弹窗类型
|
* @param {string[]} tmplIds 模板id列表
|
* @param {number} limit 频控次数
|
* @param {boolean} needlogin 是否需要登录
|
* @return {boolean} 是否可以继续订阅
|
*/
|
export async function addSubscribeCount(type, tmplIds, limit = 3, needlogin = true) {
|
const isLogin = await checkOtp();
|
if (!isLogin && needlogin) return false;
|
const userInfo = Number(Taro.getStorageSync(type) || 0);
|
Taro.setStorageSync(type, userInfo + 1);
|
return new Promise((resolve, reject) => {
|
Taro.getSetting({
|
withSubscriptions: true,
|
success(res) {
|
if (res.subscriptionsSetting?.itemSettings) {
|
let allSetting = true;
|
tmplIds.forEach((item) => {
|
if (!res.subscriptionsSetting?.itemSettings[item]) {
|
allSetting = false;
|
}
|
});
|
if (allSetting) {
|
resolve(true);
|
} else {
|
resolve(userInfo < limit);
|
}
|
} else {
|
resolve(userInfo < limit);
|
}
|
},
|
});
|
});
|
}
|
|
// 订阅次数清空
|
export function clearSubscribeCount() {
|
subscribeFields.forEach((item) => {
|
Taro.setStorageSync(item, 0);
|
});
|
}
|
|
// 检查订阅勾选状态
|
export function checkSubscribeStatus(type, tmplIds, result, limit = 3) {
|
let allSetting = true;
|
tmplIds.forEach((item) => {
|
if (result[item] !== "accept") {
|
allSetting = false;
|
}
|
});
|
if (allSetting) {
|
Taro.setStorageSync(type, limit);
|
}
|
}
|
|
|
// 获取登录状态
|
export function getLoginStatus() {
|
checkOtp().then(otp => {
|
if (!otp) {
|
return false;
|
}else{
|
return true;
|
}
|
})
|
}
|
|
// 生成订阅频控 key
|
export function buildSubscribeType(sceneKey: string, bizId?: string | number) {
|
if (bizId === undefined || bizId === null || bizId === '') {
|
return sceneKey;
|
}
|
return `${sceneKey}_${bizId}`;
|
}
|
|
/**
|
* 从订阅结果中提取用户同意(accept)的模板 ID。
|
*/
|
function getAcceptedTemplateIds(tmplIds: string[], result: Record<string, string>) {
|
return tmplIds.filter((id) => result?.[id] === 'accept');
|
}
|
|
/**
|
* 用户同意订阅后上报后端(失败不影响主流程)。
|
*/
|
async function saveAcceptedSubscribeTemplates(templateIds: string[], orderId?: string | number) {
|
if (!Array.isArray(templateIds) || templateIds.length === 0) return;
|
try {
|
const loginRes = await Taro.login();
|
const jscode = loginRes?.code;
|
if (!jscode) return;
|
await saveUserSubscribeMessage({
|
templateIds,
|
jscode,
|
orderId: orderId || null
|
});
|
} catch (error) {
|
}
|
}
|
|
/**
|
* 清理下单成功订阅点击阶段缓存。
|
*/
|
function clearOrderSuccessSubscribeCache() {
|
pendingOrderSuccessResult = null;
|
pendingOrderSuccessAcceptedTemplateIds = [];
|
pendingOrderSuccessCapturedAt = 0;
|
}
|
|
/**
|
* 在用户点击手势中预拉起“下单成功”订阅(不依赖订单 ID)。
|
*/
|
export function requestOrderSuccessSubscribeOnTap() {
|
if (isOrderSuccessSubscribeRequesting) return;
|
isOrderSuccessSubscribeRequesting = true;
|
orderSuccessSubscribePromise = new Promise<void>((resolve) => {
|
orderSuccessSubscribeResolve = resolve;
|
});
|
Taro.requestSubscribeMessage({
|
tmplIds: ORDER_SUCCESS_SUBSCRIBE_CONFIG.tmplIds,
|
success: (result) => {
|
const normalizedResult = (result || {}) as Record<string, string>;
|
pendingOrderSuccessResult = normalizedResult;
|
pendingOrderSuccessAcceptedTemplateIds = getAcceptedTemplateIds(ORDER_SUCCESS_SUBSCRIBE_CONFIG.tmplIds, normalizedResult);
|
pendingOrderSuccessCapturedAt = Date.now();
|
},
|
fail: () => {
|
clearOrderSuccessSubscribeCache();
|
},
|
complete: () => {
|
isOrderSuccessSubscribeRequesting = false;
|
// 订阅弹窗已结束(用户已处理或未弹出),放行等待中的后续原生弹窗
|
orderSuccessSubscribeResolve?.();
|
orderSuccessSubscribeResolve = null;
|
orderSuccessSubscribePromise = null;
|
}
|
});
|
}
|
|
/**
|
* 等待“下单成功”订阅弹窗结束。
|
* 若当前没有进行中的订阅弹窗,立即 resolve;否则在订阅弹窗 complete 后 resolve。
|
* 用于让蓝牙系统授权等其它原生弹窗排在订阅弹窗之后,避免多个原生弹窗叠加。
|
*/
|
export function waitForOrderSuccessSubscribePopup(): Promise<void> {
|
return orderSuccessSubscribePromise || Promise.resolve();
|
}
|
|
/**
|
* 消费点击阶段缓存结果,并在拿到订单 ID 后补齐频控与后端上报。
|
*/
|
async function consumeOrderSuccessSubscribeCache(orderId: string) {
|
if (!orderId || !pendingOrderSuccessResult) return false;
|
const isExpired = Date.now() - pendingOrderSuccessCapturedAt > ORDER_SUCCESS_CACHE_EXPIRE_MS;
|
if (isExpired) {
|
clearOrderSuccessSubscribeCache();
|
return false;
|
}
|
const type = buildSubscribeType(ORDER_SUCCESS_SUBSCRIBE_CONFIG.sceneKey, orderId);
|
checkSubscribeStatus(type, ORDER_SUCCESS_SUBSCRIBE_CONFIG.tmplIds, pendingOrderSuccessResult, ORDER_SUCCESS_SUBSCRIBE_CONFIG.limit);
|
await saveAcceptedSubscribeTemplates(pendingOrderSuccessAcceptedTemplateIds);
|
clearOrderSuccessSubscribeCache();
|
return true;
|
}
|
|
/**
|
* 执行订阅弹窗请求。
|
* 用户主动点击场景下可直接调用,避免被本地频控缓存拦截。
|
*/
|
async function requestSubscribeMessageDirect(config: SubscribeSceneConfig, bizId?: string | number) {
|
const type = buildSubscribeType(config.sceneKey, bizId);
|
await new Promise<void>((resolve) => {
|
Taro.requestSubscribeMessage({
|
tmplIds: config.tmplIds,
|
success: (result) => {
|
checkSubscribeStatus(type, config.tmplIds, result, config.limit);
|
const acceptedTemplateIds = getAcceptedTemplateIds(config.tmplIds, result as Record<string, string>);
|
void saveAcceptedSubscribeTemplates(acceptedTemplateIds, bizId);
|
resolve();
|
},
|
fail: () => {
|
resolve();
|
},
|
complete: () => {
|
resolve();
|
}
|
});
|
});
|
}
|
|
// 执行通用订阅流程(失败/拒绝不影响主流程)
|
export async function runSubscribeScene(config: SubscribeSceneConfig, bizId?: string | number) {
|
try {
|
const type = buildSubscribeType(config.sceneKey, bizId);
|
const canSubscribe = await addSubscribeCount(type, config.tmplIds, config.limit, config.needLogin);
|
console.log('canSubscribe',canSubscribe);
|
|
if (!canSubscribe) return;
|
await new Promise<void>((resolve) => {
|
Taro.requestSubscribeMessage({
|
tmplIds: config.tmplIds,
|
success: (result) => {
|
checkSubscribeStatus(type, config.tmplIds, result, config.limit);
|
const acceptedTemplateIds = getAcceptedTemplateIds(config.tmplIds, result as Record<string, string>);
|
void saveAcceptedSubscribeTemplates(acceptedTemplateIds);
|
resolve();
|
},
|
fail: () => {
|
resolve();
|
},
|
complete: () => {
|
resolve();
|
}
|
});
|
});
|
} catch (error) {
|
}
|
}
|
|
// 下单成功后订阅消息
|
export async function subscribeAfterOrderSuccess(orderId: string) {
|
if (!orderId) return;
|
const consumed = await consumeOrderSuccessSubscribeCache(orderId);
|
if (consumed) return;
|
await runSubscribeScene(ORDER_SUCCESS_SUBSCRIBE_CONFIG, orderId);
|
}
|
|
// 用户主动点击“去开启”时,直接拉起订阅,不受频控缓存影响
|
export async function subscribeAfterOrderSuccessByTap(orderId: string) {
|
if (!orderId) return;
|
await requestSubscribeMessageDirect(ORDER_SUCCESS_SUBSCRIBE_CONFIG, orderId);
|
}
|
|
// 分享行程页主动点击订阅,只请求分享行程对应单模板
|
export async function subscribeShareTripByTap(orderId: string) {
|
if (!orderId) return;
|
await requestSubscribeMessageDirect(SHARE_TRIP_SUBSCRIBE_CONFIG, orderId);
|
}
|
|
// 运营活动 + 券效期 + 券到账三合一模板订阅
|
export async function subscribeOperationCouponGroup(bizId?: string | number) {
|
await runSubscribeScene(OPERATION_COUPON_SUBSCRIBE_CONFIG, bizId);
|
}
|