fix(subscribe): 订阅消息未下发原因可见 + 小程序真实授权

- 后台测试推送:未就绪返回 mode=SIMULATED 明确原因 + 配置自检(模板ID/openid/appId/appSecret/总开关),不再把模拟显示为成功

- 占位符 tmpl_xxx 视为未配置(测试推送与订阅模板接口均过滤),微信错误码中文化(43101/47003/40037/40001 等)

- 小程序「平台消息与通知」点击时真实调用 wx.requestSubscribeMessage 并回显授权状态,标签不再恒为已开启

- 管理端拦截器错误提示改用 message;文档补订阅消息排查顺序;新增回归护栏(86 套件/631 用例全绿)
这个提交包含在:
xcbyy
2026-10-19 10:02:57 +08:00
父节点 0aed11cb01
当前提交 cacc478d71
共有 8 个文件被更改,包括 229 次插入 和 29 次删除

查看文件

@@ -49,3 +49,42 @@ export function requestSubscribeFor(scene) {
// 授权失败不影响主流程
}
}
/** 全部已配置的真实模板ID(过滤空值与 tmpl_ 占位符) */
function allTmplIds() {
if (!templates || templates.enablePush === false) return [];
return ['orderStatusTemplateId', 'activityJoinTemplateId', 'withdrawSuccessTemplateId', 'verifyCodeTemplateId']
.map((k) => templates[k])
.filter((x) => x && !String(x).startsWith('tmpl_'));
}
/**
* 主动请求全部订阅授权(用于「平台消息与通知」入口)。
* 注意:wx.requestSubscribeMessage 必须在用户点击手势中同步调用;本函数不 await 任何网络请求。
* 返回每项授权结果,便于界面给出真实提示(订阅消息为一次性订阅,每条授权仅可下发 1 条)。
*/
export function requestSubscribeAll() {
return new Promise((resolve) => {
try {
if (typeof uni.requestSubscribeMessage !== 'function') {
return resolve({ supported: false, tmplIds: [], accepted: [], rejected: [] });
}
const tmplIds = allTmplIds();
if (tmplIds.length === 0) {
return resolve({ supported: true, tmplIds: [], accepted: [], rejected: [], noTemplates: true });
}
uni.requestSubscribeMessage({
tmplIds,
success: (res) => {
const accepted = tmplIds.filter((id) => res && res[id] === 'accept');
const rejected = tmplIds.filter((id) => !res || res[id] !== 'accept');
resolve({ supported: true, tmplIds, accepted, rejected, raw: res });
},
fail: (err) => resolve({ supported: true, tmplIds, accepted: [], rejected: tmplIds, error: err }),
complete: () => {}
});
} catch (e) {
resolve({ supported: true, tmplIds: [], accepted: [], rejected: [], error: e });
}
});
}

查看文件

@@ -152,7 +152,7 @@
<view class="list-row btn-active" @click="openNotice">
<image class="row-svg" :src="ICONS.bell" mode="aspectFit" />
<text class="row-label">平台消息与通知</text>
<text class="row-sub-tag">{{ noticeSubscribe ? '提醒开启' : '已静音' }}</text>
<text class="row-sub-tag">{{ noticeSubscribe ? '已开启提醒' : '点击开启' }}</text>
<image class="row-arrow-svg" :src="ICONS.chevronRight" mode="aspectFit" />
</view>
<view class="list-row btn-active" @click="openMyReportsModal">
@@ -352,6 +352,7 @@ import { getCurrentThemeKey, setTheme, THEME_KEYS, applyThemeToNativeUI } from '
import { getCurrentRole, setCurrentRole, ROLE_TYPES, toggleRole as toggleUserRole, isExpertCertified, getExpertCertStatus, setExpertCertStatus, CERT_STATUS } from '@/common/role.js';
import { getNavBarInfo } from '@/common/nav.js';
import { getUserProfile, rechargeBalance, withdrawCommission, getUserFinanceRecords, updateUserProfile, uploadImage, getRechargeTiers, getPlatformConfigs, applyExpertCert, getUserWithdrawals, getFavorites, removeFavorite, getDistributionTeam, getDistributionRecords, getMyReports, bindInviteCode } from '@/common/api.js';
import { preloadSubscribeTemplates, requestSubscribeAll } from '@/common/subscribe.js';
const DEFAULT_GUEST_AVATAR = 'https://images.unsplash.com/photo-1535713875002-d1d0cf377fde?w=200';
const initialUserId = typeof uni !== 'undefined' ? (uni.getStorageSync('userId') || null) : null;
@@ -382,7 +383,7 @@ const openMyReportsModal = () => {
// 系统与隐私设置
const privacyHidePhone = ref(uni.getStorageSync('privacy_hide_phone') === '1');
const noticeSubscribe = ref(uni.getStorageSync('notice_subscribe') !== '0');
const noticeSubscribe = ref(uni.getStorageSync('notice_subscribe') === '1');
const cacheSizeText = ref('1.25 MB');
// 分销推广管理
@@ -628,6 +629,8 @@ onMounted(() => {
loadUserProfile();
loadPlatformConfigs();
loadMyReports();
// 预加载订阅消息模板ID,确保点击授权时无需等待网络(保留点击手势)
preloadSubscribeTemplates();
});
onShow(() => {
@@ -639,7 +642,7 @@ onShow(() => {
loadUserProfile();
loadPlatformConfigs();
privacyHidePhone.value = uni.getStorageSync('privacy_hide_phone') === '1';
noticeSubscribe.value = uni.getStorageSync('notice_subscribe') !== '0';
noticeSubscribe.value = uni.getStorageSync('notice_subscribe') === '1';
if (isLoggedIn.value) {
loadFavorites();
loadMyReports();
@@ -1163,6 +1166,9 @@ const contactService = () => {
};
const openNotice = () => {
// 同步触发微信订阅授权(必须保留点击手势,不能先 await 网络);再展示平台须知。
const subResult = typeof uni.requestSubscribeMessage === 'function' ? requestSubscribeAll() : null;
const notice = platformConfigs.value.safety_notice || {};
const title = notice.title || '平台官方公告与指引';
let content = '【安全履约须知】\n1. 为保障搭子与陪伴师人身与财产安全,线下约局须在合规公开商业街区进行。\n2. 现场碰面后请出示 4 位数字核销码进行签到确认。\n3. 平台提供 7x24 小时争议仲裁与资金托管保障服务。';
@@ -1177,6 +1183,20 @@ const openNotice = () => {
confirmText: '我知道了',
confirmColor: '#6366F1'
});
if (subResult) {
subResult.then((r) => {
if (!r || !r.supported) return;
if (r.noTemplates || !r.tmplIds || r.tmplIds.length === 0) {
uni.showToast({ title: '后台未配置订阅消息模板', icon: 'none' });
return;
}
const accepted = (r.accepted || []).length > 0;
noticeSubscribe.value = accepted;
uni.setStorageSync('notice_subscribe', accepted ? '1' : '0');
uni.showToast({ title: accepted ? '已开启消息提醒' : '未获得提醒授权', icon: 'none' });
});
}
};
const openFaq = () => {