chore: 入库生产基线(2026-10-26 工作树快照)
- 含首页壳/容器页/授权页/分享图/首页配置等此前未入库的在途改动 - 新增未跟踪的 pages/index/mall.vue(第 5 个 tabBar 页,pages.json 已引用) - 排除本地备份文件(*.bak* / *.origin-bak) Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -31,8 +31,9 @@ const BOX_API_BASE = 'https://mp.yizhezhe.com';
|
||||
// 与「宫格展示」用的 /api/mp/apps?isPublished=1 刻意解耦:上架状态只决定宫格显隐,
|
||||
// 不决定转发图 —— 反查只看 URL 前缀(接口只回 url+shareImageUrl,长前缀优先)。
|
||||
const APPS_API = BOX_API_BASE + '/api/mp/public/share-images';
|
||||
const APPS_CACHE_KEY = 'share_apps_index_v2'; // 反查表本地缓存 { t, items }
|
||||
// (v2:数据源从上架表换成含下架的反查表,bump key 让旧缓存立即失效)
|
||||
const APPS_CACHE_KEY = 'share_apps_index_v3'; // 反查表本地缓存 { t, items }
|
||||
// (v2:数据源从上架表换成含下架的反查表;v3:每行新增 shareTitle)
|
||||
// bump key 让旧缓存立即失效,别等 1h 才生效
|
||||
const APP_IMG_CACHE_PREFIX = 'web_view_share_img:'; // 单个应用分享图缓存(按 URL 基){ t, url }
|
||||
const APPS_CACHE_TTL = 60 * 60 * 1000; // 应用表缓存 1 小时
|
||||
const APP_IMG_CACHE_TTL = 24 * 60 * 60 * 1000; // 单应用分享图缓存 24 小时(后台改图最迟一天生效)
|
||||
@@ -41,17 +42,20 @@ export default {
|
||||
data() {
|
||||
return {
|
||||
// ---------- 分享(H5 无法自己拉起分享面板,只能由承载 web-view 的页面转发) ----------
|
||||
shareTitle: '', // H5 postMessage(action:'share') 自定义标题
|
||||
shareTitle: '', // 分享标题(H5 postMessage 上报的优先,其次 box_app 反查)
|
||||
shareTitleSource: '', // 当前标题来源:'box'(box_app 反查)| 'h5'(H5 postMessage 覆盖)
|
||||
shareSummary: '', // 分享摘要(部分场景用)
|
||||
shareHref: '', // H5 指定的分享链接(兜底;深链优先 webViewUrl)
|
||||
shareImageUrl: '', // 好友转发卡片图(网络 https)
|
||||
shareTimelineImage: '', // 朋友圈卡片图(必须本地路径,见 _prepareTimelineImage)
|
||||
shareImageSource: '', // 当前分享图来源:'box'(box_app 反查)| 'h5'(H5 postMessage 覆盖)
|
||||
shareImageBase: '', // 当前分享图所属的 H5 URL 基(切换应用时用它丢弃上一张)
|
||||
shareApps: [], // box_app 应用表(url → shareImageUrl),供同步匹配
|
||||
shareImageBase: '', // 当前分享数据所属的 H5 URL 基(切换应用时用它丢弃上一应用的图/标题)
|
||||
shareApps: [], // box_app 反查表(url → shareImageUrl / shareTitle),供同步匹配
|
||||
shareAppsLoaded: false, // 应用表是否已就绪
|
||||
shareAppsLoading: false,// 应用表请求中(防并发重入)
|
||||
shareAppsLoading: false,// 反查表请求中(防并发重入)
|
||||
_shareAppsCbs: [], // 请求中的回调队列(请求结束统一回调)
|
||||
_shareFetched: false, // 本页是否已做过一次异步兜底反查
|
||||
_shareTargetUrl: '', // 最近一次要分享的 H5 地址(反查表就绪后按它补解析)
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
@@ -66,6 +70,7 @@ export default {
|
||||
this.shareTimelineImage = '';
|
||||
this.shareImageSource = '';
|
||||
this.shareTitle = '';
|
||||
this.shareTitleSource = '';
|
||||
this.shareSummary = '';
|
||||
this.shareHref = '';
|
||||
},
|
||||
@@ -88,14 +93,18 @@ export default {
|
||||
},
|
||||
|
||||
/**
|
||||
* 分享前的统一解析(两个宿主页的 onShareAppMessage 都调它):
|
||||
* 分享前的统一解析(两个宿主页的 onShareAppMessage / onShareTimeline 都调它):
|
||||
* ① 应用切换检测 → 换应用先清空;
|
||||
* ② 每次都按"当前 H5 地址"重新反查 box_app 的 shareImageUrl
|
||||
* (不能因为 shareImageUrl 已有值就跳过 —— 那可能是上一个应用的图);
|
||||
* ③ 当前图若来自本应用 H5 的 postMessage(source='h5'),保留不覆盖。
|
||||
* ② 每次都按"当前 H5 地址"重新反查 box_app 的分享图 + 分享标题
|
||||
* (不能因为已有值就跳过 —— 那可能是上一个应用的);
|
||||
* ③ H5 自己上报过标题/图(source='h5')的,保留不覆盖。
|
||||
*/
|
||||
resolveShareCard(targetUrl) {
|
||||
// 进入本次解析时的归属基:换应用时 syncShareCardTarget 会清掉标题,
|
||||
// 用它区分「同一应用的标题」与「上一个应用遗留的标题」
|
||||
const baseAtEntry = this.shareImageBase;
|
||||
this.syncShareCardTarget(targetUrl);
|
||||
// ---- 分享图 ----
|
||||
const fromBox = this._lookupShareImage(targetUrl);
|
||||
if (fromBox) {
|
||||
if (this.shareImageSource !== 'h5') this._applyShareImage(fromBox, 'box', targetUrl);
|
||||
@@ -105,23 +114,65 @@ export default {
|
||||
this.shareImageSource = '';
|
||||
this.shareTimelineImage = '';
|
||||
}
|
||||
// ---- 分享标题(box_app 反查,服务端已把「后台分享标题为空」回退成应用名) ----
|
||||
const boxTitle = this._matchAppShareTitle(targetUrl);
|
||||
if (boxTitle) {
|
||||
if (this.shareTitleSource !== 'h5') {
|
||||
this.shareTitle = boxTitle;
|
||||
this.shareTitleSource = 'box';
|
||||
}
|
||||
} else if (this.shareTitleSource === 'box' && this.shareImageBase && this.shareImageBase !== baseAtEntry) {
|
||||
// 换到了反查表里查不到的应用,而现有标题是上一个应用反查来的 → 丢弃。
|
||||
// ⚠️ 同一地址不清:太阳码场景的标题来自 /api/mp/apps(不在反查表里也能有标题)。
|
||||
this.shareTitle = '';
|
||||
this.shareTitleSource = '';
|
||||
}
|
||||
if (!this.shareAppsLoaded) this._loadShareApps();
|
||||
},
|
||||
|
||||
/* 在内存应用表里按 URL 前缀匹配 shareImageUrl(同步,分享那一刻就能出图) */
|
||||
_matchAppShareImage(targetUrl) {
|
||||
const app = this._matchShareApp(targetUrl);
|
||||
return (app && app.shareImageUrl) || '';
|
||||
},
|
||||
|
||||
/* 在内存应用表里按 URL 前缀匹配分享标题(同步;服务端已回退应用名,空串表示没查到) */
|
||||
_matchAppShareTitle(targetUrl) {
|
||||
const app = this._matchShareApp(targetUrl);
|
||||
return (app && app.shareTitle) || '';
|
||||
},
|
||||
|
||||
/**
|
||||
* 用「已经拿到的应用对象」直接设标题(太阳码 scene 路径:/api/mp/apps 一次就带回
|
||||
* shareTitle / name,不必再等反查表)。
|
||||
* 后台没配 shareTitle 时回退应用名称 —— 与反查表服务端同口径。
|
||||
* @param app 应用对象(含 shareTitle / name / url)
|
||||
*/
|
||||
_applyShareTitleFromApp(app) {
|
||||
if (!app) return;
|
||||
const t = app.shareTitle || app.name || '';
|
||||
if (!t) return;
|
||||
this.shareTitle = t;
|
||||
this.shareTitleSource = 'box';
|
||||
},
|
||||
|
||||
/**
|
||||
* 反查表里按 URL 前缀找当前应用(长前缀优先 —— 服务端已按 url 长度倒序下发,
|
||||
* 防「/a 是 /a/b 的前缀」时张冠李戴)。图与标题共用同一条记录,避免两次匹配漂移。
|
||||
*/
|
||||
_matchShareApp(targetUrl) {
|
||||
const base = this._shareUrlBase(targetUrl);
|
||||
if (!base) return '';
|
||||
if (!base) return null;
|
||||
const apps = this.shareApps || [];
|
||||
for (let i = 0; i < apps.length; i++) {
|
||||
const app = apps[i];
|
||||
if (!app || !app.url || !app.shareImageUrl) continue;
|
||||
if (!app || !app.url) continue;
|
||||
const appBase = this._shareUrlBase(app.url);
|
||||
if (!appBase) continue;
|
||||
// 如 https://mp.yizhezhe.com/taidong == https://mp.yizhezhe.com/taidong/
|
||||
if (base === appBase || base.indexOf(appBase + '/') === 0) return app.shareImageUrl;
|
||||
if (base === appBase || base.indexOf(appBase + '/') === 0) return app;
|
||||
}
|
||||
return '';
|
||||
return null;
|
||||
},
|
||||
|
||||
/* 同步查图:优先内存应用表(onLoad 预热,1h 缓存),再退单应用缓存(24h TTL) */
|
||||
@@ -130,7 +181,11 @@ export default {
|
||||
if (!base) return '';
|
||||
// 1) 内存应用表:后台改图后最迟 1h 生效
|
||||
const fromList = this._matchAppShareImage(targetUrl);
|
||||
if (fromList) return fromList;
|
||||
if (fromList) {
|
||||
// 顺手写单应用缓存:冷启动时表还没回来也能立刻出图
|
||||
try { uni.setStorageSync(APP_IMG_CACHE_PREFIX + base, { t: Date.now(), url: fromList }); } catch (e) {}
|
||||
return fromList;
|
||||
}
|
||||
// 2) 单应用缓存兜底(应用表请求失败 / 冷启动还没回来时用)
|
||||
try {
|
||||
const cached = uni.getStorageSync(APP_IMG_CACHE_PREFIX + base);
|
||||
@@ -144,7 +199,7 @@ export default {
|
||||
return '';
|
||||
},
|
||||
|
||||
/* 拉全量已上架应用(带 1h 本地缓存);cb 可选,拉完(或命中缓存)后回调 */
|
||||
/* 拉 box_app 分享卡片反查表(url → 分享图/标题,带 1h 本地缓存);cb 可选,拉完(或命中缓存)后回调 */
|
||||
_loadShareApps(cb) {
|
||||
// 1) 本地缓存命中:冷启动也能同步出图
|
||||
try {
|
||||
@@ -157,9 +212,19 @@ export default {
|
||||
return;
|
||||
}
|
||||
} catch (e) {}
|
||||
// 2) 请求中:直接返回,避免并发重复拉(漏掉的 cb 由调用方自身的兜底路径覆盖)
|
||||
if (this.shareAppsLoading) return;
|
||||
// 2) 请求中:把回调排队,请求结束统一回调(避免并发重复拉,也别丢调用方)
|
||||
if (this.shareAppsLoading) {
|
||||
if (cb) this._shareAppsCbs.push(cb);
|
||||
return;
|
||||
}
|
||||
this.shareAppsLoading = true;
|
||||
if (cb) this._shareAppsCbs.push(cb);
|
||||
const finish = () => {
|
||||
this.shareAppsLoading = false;
|
||||
const cbs = this._shareAppsCbs || [];
|
||||
this._shareAppsCbs = [];
|
||||
cbs.forEach(fn => { try { fn(); } catch (e) {} });
|
||||
};
|
||||
uni.request({
|
||||
url: APPS_API,
|
||||
method: 'GET',
|
||||
@@ -170,35 +235,42 @@ export default {
|
||||
this.shareAppsLoaded = true;
|
||||
try { uni.setStorageSync(APPS_CACHE_KEY, { t: Date.now(), items: items }); } catch (e) {}
|
||||
}
|
||||
if (cb) cb();
|
||||
finish();
|
||||
},
|
||||
fail: (err) => {
|
||||
// 常见原因:mp.yizhezhe.com 未加入小程序后台 request 合法域名白名单
|
||||
console.warn('[shareCard] 应用表拉取失败(请检查 mp.yizhezhe.com 是否在 request 合法域名):', err);
|
||||
if (cb) cb();
|
||||
},
|
||||
complete: () => { this.shareAppsLoading = false; }
|
||||
console.warn('[shareCard] 反查表拉取失败(请检查 mp.yizhezhe.com 是否在 request 合法域名):', err);
|
||||
finish();
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/* 异步兜底反查(容器页 onLoad 用):同步没命中就拉应用表再匹配一次 */
|
||||
/* 异步兜底反查(容器页 onLoad 用):同步没命中就拉反查表再匹配一次 */
|
||||
_fetchShareImage(targetUrl) {
|
||||
if (!targetUrl) return;
|
||||
this.syncShareCardTarget(targetUrl); // 建立/校正"当前图属于哪个应用"
|
||||
this._shareTargetUrl = targetUrl; // 拉表回来后按它补解析分享图/标题
|
||||
this.syncShareCardTarget(targetUrl); // 建立/校正"当前数据属于哪个应用"
|
||||
// 反查表还没预热就先拉(标题/图都靠它;拉回来自己会补解析)
|
||||
if (!this.shareAppsLoaded) this._loadShareApps(() => this._syncShareCard(targetUrl));
|
||||
if (this.shareImageUrl) return; // H5 postMessage 已给图,不覆盖
|
||||
if (this._shareFetched) return; // 本页只兜底一次
|
||||
this._shareFetched = true;
|
||||
const hit = this._lookupShareImage(targetUrl);
|
||||
if (hit) { this._applyShareImage(hit, 'box', targetUrl); return; }
|
||||
// 表已就绪但没命中(或上面那次请求刚好失败)→ 再兜底一次,覆盖冷启动时序
|
||||
this._loadShareApps(() => {
|
||||
const matched = this._matchAppShareImage(targetUrl);
|
||||
if (!matched) return;
|
||||
try { uni.setStorageSync(APP_IMG_CACHE_PREFIX + this._shareUrlBase(targetUrl), { t: Date.now(), url: matched }); } catch (e) {}
|
||||
this._applyShareImage(matched, 'box', targetUrl);
|
||||
console.log('[shareCard] 分享图片(兜底): ' + matched);
|
||||
this._syncShareCard(targetUrl);
|
||||
if (this.shareImageUrl) console.log('[shareCard] 分享图片(兜底): ' + this.shareImageUrl);
|
||||
});
|
||||
},
|
||||
|
||||
/* 反查表就绪后补解析(onLoad 阶段表还没回来时用;已就绪则走 resolveShareCard) */
|
||||
_syncShareCard(targetUrl) {
|
||||
const url = targetUrl || this._shareTargetUrl;
|
||||
if (!url) return;
|
||||
this.resolveShareCard(url);
|
||||
},
|
||||
|
||||
/* 统一设置分享图:好友转发用网络图;朋友圈另转本地图(见下)
|
||||
@param source 'box'(box_app 反查,默认)| 'h5'(H5 postMessage 覆盖)
|
||||
@param forUrl 该图所属的 H5 地址(用于切换应用时判断是否过期) */
|
||||
|
||||
@@ -217,18 +217,24 @@ export default {
|
||||
// onShareTimeline(朋友圈)拿不到 webViewUrl 时唯一的同步手段,
|
||||
// 也修掉首页壳换应用后沿用上一个应用分享图的问题。
|
||||
const href = m.href || '';
|
||||
// 诊断日志(过滤关键字 enter-app):这条**不是实时**的 ——
|
||||
// 微信把 web-view 的 postMessage 攒到「后退 / 组件销毁 / 分享 / 复制链接」才派发,
|
||||
// 所以它出现的时间点通常晚于用户实际换应用;宫格换应用后宿主就是靠它才知道当前在哪。
|
||||
console.log('[enter-app] 宿主收到 H5 location 上报(延迟派发)href=' + href);
|
||||
// ⚠️ 顺序:先落 H5 上报的标题,再 resolveShareCard ——
|
||||
// 后者只在标题来源不是 'h5' 时才用 box_app 反查标题覆盖,顺序反了会白上报。
|
||||
if (m.title) { this.shareTitle = m.title; this.shareSummary = m.summary || ''; this.shareTitleSource = 'h5'; }
|
||||
if (href) {
|
||||
this.shareHref = href;
|
||||
if (typeof this.resolveShareCard === 'function') this.resolveShareCard(href);
|
||||
}
|
||||
if (m.title) { this.shareTitle = m.title; this.shareSummary = m.summary || ''; }
|
||||
// H5 给了图就以 H5 为准(仅当前应用;换应用会被 syncShareCardTarget 清掉)
|
||||
if (m.imageUrl) this._applyShareImage(m.imageUrl, 'h5', href || this.shareHref);
|
||||
break;
|
||||
}
|
||||
case 'share':
|
||||
// title / imageUrl / href 各自独立处理,单个字段缺失不影响其它
|
||||
if (m.title) { this.shareTitle = m.title; this.shareSummary = m.summary || ''; }
|
||||
if (m.title) { this.shareTitle = m.title; this.shareSummary = m.summary || ''; this.shareTitleSource = 'h5'; }
|
||||
if (m.href) this.shareHref = m.href;
|
||||
if (m.imageUrl) this._applyShareImage(m.imageUrl, 'h5', m.href);
|
||||
// 小程序无法用代码拉起分享面板,只能引导用户点右上角
|
||||
@@ -256,6 +262,7 @@ export default {
|
||||
case 'setTitle':
|
||||
if (m.title) {
|
||||
this.shareTitle = m.title;
|
||||
this.shareTitleSource = 'h5'; // H5 明确设的标题 → 后续反查不覆盖
|
||||
uni.setNavigationBarTitle({ title: m.title });
|
||||
}
|
||||
break;
|
||||
@@ -318,7 +325,7 @@ export default {
|
||||
uni.removeStorageSync('__AUTH_RESULT__');
|
||||
/* backUrl 优先:H5 发起授权时经 navigateTo query 带上的当前地址
|
||||
(确定性通道,不依赖 postMessage 派发时机);旧版 H5 没有 backUrl
|
||||
时回退到入参基址(shareHref || webUrl || MAMA_HOME)。 */
|
||||
时回退到入参基址(shareHref || webUrl || 生效首页地址)。 */
|
||||
const back = (res.backUrl && String(res.backUrl).split('#')[0]) || '';
|
||||
const base = back || baseUrl;
|
||||
if (!base) return '';
|
||||
@@ -336,7 +343,7 @@ export default {
|
||||
const openid = this._hostOpenid();
|
||||
if (!openid) return uni.showToast({ title: '未获得 openid,请先登录', icon: 'none' });
|
||||
/* 面板回传基址:requestProfile 消息带的 href 优先(确定性),没有才用调用处
|
||||
传入的 baseUrl(shareHref || webUrl || MAMA_HOME)。清掉已用值,
|
||||
传入的 baseUrl(shareHref || webUrl || 生效首页地址)。清掉已用值,
|
||||
避免下次面板在没有新消息 href 时复用旧地址。 */
|
||||
const base = (this._panelBaseUrl && String(this._panelBaseUrl).split('#')[0]) || baseUrl;
|
||||
this._panelBaseUrl = '';
|
||||
|
||||
Reference in New Issue
Block a user