feat(分享图): 反查数据源换 /api/mp/public/share-images(含下架应用)—— mama-box/laixin 可配自定义转发图
- APPS_API 换 box_app 专用反查接口(不再被 isPublished=1 过滤卡住) - 缓存 key bump share_apps_index → v2(旧缓存只有上架应用,残留 1h) - shareCard.js 首次入库(此前为未跟踪文件);文档同步数据流与缓存说明
This commit is contained in:
139
docs/分享图片获取逻辑.md
139
docs/分享图片获取逻辑.md
@@ -13,11 +13,15 @@
|
||||
→ 存入 App.shareImageUrl = https://mp.yizhezhe.com/upload/picture/xxx.png
|
||||
|
||||
[box_app API]
|
||||
GET /api/mp/apps?identifier=xxx&isPublished=1
|
||||
→ 返回 { identifier, url, shareImageUrl, ... }
|
||||
GET /api/mp/apps?identifier=xxx&isPublished=1 (单个应用,太阳码场景)
|
||||
GET /api/mp/public/share-images (分享图反查表:所有配了图的应用,
|
||||
**不过滤上架状态**,2026-09-22 起)
|
||||
→ 返回 { url, shareImageUrl } 精简结构
|
||||
|
||||
[小程序 web_view(pages/annex/web_view/index.vue)]
|
||||
① 扫码/点击应用 → 获取 shareImageUrl → 本地缓存
|
||||
[小程序宿主页(两个,见第三节)]
|
||||
pages/annex/web_view/index.vue 容器页:扫码 / JumpPath / 分享落地
|
||||
pages/index/index.vue 首页壳:兜爸心选应用宫格 location.href 进入的 H5
|
||||
① 获取 shareImageUrl(网络图)→ 内存 + 本地缓存(mixins/shareCard.js)
|
||||
② 用户点 ··· 转发给好友 → 卡片图 = shareImageUrl(网络图,微信支持)
|
||||
③ 用户点 ··· 分享到朋友圈 → getImageInfo 转本地路径 → 卡片图 = 本地路径
|
||||
```
|
||||
@@ -34,15 +38,23 @@
|
||||
- 支持上传 `/api/upload`、编辑回填、单图(maxCount=1)
|
||||
- extra 提示:建议 5:4 比例,公网可访问
|
||||
|
||||
## 三、小程序端获取逻辑(web_view/index.vue)
|
||||
## 三、小程序端获取逻辑(两个宿主页共用 `mixins/shareCard.js`)
|
||||
|
||||
### 三种获取路径(按优先级)
|
||||
> 宿主页有两个,**分享卡片由谁产出取决于用户是从哪进来的**:
|
||||
> - `pages/annex/web_view/index.vue` —— 容器页:太阳码扫码 / 分享卡片落地 / JumpPath / 客服 / 登录跳转;
|
||||
> - `pages/index/index.vue` —— 首页壳:兜爸心选首页的应用宫格用 `location.href` 换 H5(见 `mama-box/js/app.js`
|
||||
> `jumpApp`),**小程序页面栈不变**,所以从这里进的应用(数胎动、孕检日历…)分享永远发生在首页壳。
|
||||
>
|
||||
> 反查 / 缓存 / 朋友圈本地图转换统一抽到 `mixins/shareCard.js`,两页行为一致。
|
||||
|
||||
### 四种获取路径(按优先级)
|
||||
|
||||
| 优先级 | 场景 | 方法 | 说明 |
|
||||
|--------|------|------|------|
|
||||
| ① 首选 | 太阳码扫码 | `_resolveAppScene` | 调 `mp/apps?identifier=xxx` 时**一次请求同时拿** H5 url + shareImageUrl,identifier 严格匹配 |
|
||||
| ② 兜底 | 点击应用(JumpPath 直传 URL) | `_fetchShareImage` | 按 URL 前缀反查 `mp/apps` 列表,命中写入本地缓存 |
|
||||
| ② 兜底 | 容器页点击应用(JumpPath 直传 URL) | `_fetchShareImage` | 按 URL 前缀反查 `mp/apps` 列表,命中写入本地缓存 |
|
||||
| ③ 覆盖 | H5 postMessage 自定义 | `handleMessage` 'share' | H5 `bridge.share({imageUrl})` 传了图才覆盖,为空保留 box_app 预取值 |
|
||||
| ④ 同步 | 首页壳(应用宫格进入任意 H5) | `_loadShareApps` + `resolveShareCard` | 首页壳 onLoad 预热**全量应用表**(内存 + 1h 本地缓存),`onShareAppMessage(options)` 里用 `options.webViewUrl` 同步匹配出图(首次转发就有图);**每次分享都重解析并检测应用切换**,避免沿用上一个应用的图 |
|
||||
|
||||
### ① 太阳码扫码(`_resolveAppScene`)
|
||||
|
||||
@@ -58,13 +70,13 @@
|
||||
|
||||
```
|
||||
点应用 → JumpPath 传完整 H5 URL(如 https://mp.yizhezhe.com/app_03/)
|
||||
→ 1. 查本地缓存 web_view_share_img:{originPath} → 命中直接复用
|
||||
→ 2. 未命中 → GET /api/mp/apps?isPublished=1&pageSize=100(拉全量已上架应用)
|
||||
→ 3. URL 前缀匹配:originPath === appBase || originPath.startsWith(appBase + '/')
|
||||
→ 4. 命中 → this._applyShareImage(shareImageUrl) + 写入缓存
|
||||
→ 1. 先同步查:内存应用表(若已预热)→ 单应用缓存 web_view_share_img:{urlBase}(24h TTL)
|
||||
→ 2. 未命中 → GET /api/mp/public/share-images(拉分享图反查表,**含未上架应用**,1h 本地缓存)
|
||||
→ 3. URL 前缀匹配:urlBase === appBase || urlBase.startsWith(appBase + '/')
|
||||
→ 4. 命中 → this._applyShareImage(shareImageUrl) + 写入单应用缓存
|
||||
```
|
||||
|
||||
页面加载时执行,用户点转发时图已就绪。`_shareFetched` 标志保证页面内只请求一次。
|
||||
页面加载时执行,用户点转发时图已就绪。`_shareFetched` 标志保证页面内只做一次异步兜底。
|
||||
|
||||
### ③ H5 postMessage 覆盖(`handleMessage`)
|
||||
|
||||
@@ -74,30 +86,119 @@ H5 bridge.share({ title, imageUrl }) → postMessage → handleMessage 'share'
|
||||
→ 为空则保留 box_app 预取的分享图片
|
||||
```
|
||||
|
||||
## 四、好友转发 vs 朋友圈(微信平台差异)
|
||||
### ④ 首页壳同步匹配(`pages/index/index.vue`,2026-09 补)
|
||||
|
||||
> 症状:从兜爸心选首页的应用宫格进入的 H5(如数胎动),点右上角 ··· 转发**没有**后台配置的分享图。
|
||||
|
||||
原因:宫格用 `location.href` 换 H5,小程序页面栈仍是首页壳 `pages/index/index`;而反查逻辑此前
|
||||
只写在容器页,首页壳的 `shareImageUrl` 只能靠 H5 postMessage,数胎动这类 H5 没有 `bridge.share`
|
||||
→ 卡片图为空 → 微信回退默认截图。
|
||||
|
||||
```
|
||||
onLoad → _loadShareApps() // 预热全量应用表:内存 shareApps + 本地缓存 share_apps_index(1h)
|
||||
点 ··· → onShareAppMessage(options)
|
||||
→ target = options.webViewUrl || this.shareHref || this.webUrl // 当前 web-view 真实地址
|
||||
→ this.resolveShareCard(target) // 见下:先做应用切换检测,再按当前地址反查
|
||||
→ imageUrl = this.shareImageUrl || ''
|
||||
```
|
||||
|
||||
`_lookupShareImage` 是**同步**的:先内存应用表,再单应用缓存。因此首次转发就能出图,
|
||||
不依赖 postMessage 的投递时机。后台改分享图后,最迟 1 小时(列表缓存 TTL)生效。
|
||||
朋友圈图仍走 `_prepareTimelineImage`(`getImageInfo` 转本地路径)。
|
||||
|
||||
> ⚠️ **跨应用残留(2026-09 修)**:症状是「A 应用转发能出图,切到 B 应用转发还是 A 的图」。
|
||||
> 根因是首页壳**不随 H5 换页重新 onLoad**(宫格用 `location.href`),而当时分享图取到后就一直
|
||||
> 留在 `shareImageUrl`,并且解析被 `if (!this.shareImageUrl)` 短路掉了 —— 于是上一个应用的图钉住不换。
|
||||
>
|
||||
> 修法(`resolveShareCard` / `syncShareCardTarget`,两个宿主页共用):
|
||||
> 1. `shareImageBase` 记录当前图属于哪个 H5 应用;分享前发现 `target` 换了应用 → **清空**
|
||||
> `shareImageUrl` / `shareTimelineImage` / `shareTitle` / `shareHref`;
|
||||
> 2. **每次都按当前 `target` 重新反查**,不再因为已有图就跳过;
|
||||
> 3. 当前图若来自本应用 H5 的 postMessage(`shareImageSource === 'h5'`)才保留优先,
|
||||
> 否则 box_app 配置图覆盖;
|
||||
> 4. 当前应用没有配置图 → 宁可留空回退微信默认截图,也不沿用上一个应用的图。
|
||||
>
|
||||
> 局限与正解:`onShareTimeline` 拿不到 `webViewUrl`,宿主无法自己判断当前应用。
|
||||
> 正解是 **H5 主动上报当前位置**:换页/路由切换时发
|
||||
> `wx.miniProgram.postMessage({ data: { action: 'location', href: location.href } })`,
|
||||
> 宿主 `webviewHost.handleHostMessage` 收到后更新 `shareHref` 并调 `resolveShareCard(href)`,
|
||||
> 朋友圈分享即拿到正确的落地页与分享图(共享库内置 `wxAuth.reportLocation()` / `wxAuth.watchLocation()`)。
|
||||
> H5 侧完整接入说明见 `box_app/docs/h5-auth-standard.md` 第四节。
|
||||
> 若 H5 未接入该上报,朋友圈分享只能以最后一次好友转发解析出的图为准。
|
||||
|
||||
## 四、分享路径(深链)—— 2026-09 修复
|
||||
|
||||
> 症状:在 web-view 的 H5 里点「分享给微信好友」,接收者点开卡片却落到**小程序首页**,
|
||||
> 而不是发送者当时所在的那个 H5 应用/页面。
|
||||
|
||||
### 根因
|
||||
|
||||
1. web-view 里的 H5 **无法用代码拉起分享面板**,卡片只能由承载 `<web-view>` 的**宿主小程序页**产出。
|
||||
2. H5 应用是从 mama-box(首页 web-view)的应用宫格用 `location.href` 换页进入的
|
||||
(`mama-box/js/app.js` → `jumpApp`),小程序页面栈**仍是首页** `pages/index/index`。
|
||||
3. `app_03` 的 `bridge.share()` 曾在 postMessage 后调 `wx.miniProgram.navigateBack({delta:1})`
|
||||
试图触发投递 —— 这会把 web-view 页弹掉(栈里只有 tabBar 根页时还会失败),
|
||||
转发随即被**别的页面**接管;首页回调又写死 `path: '/pages/index/index'`,
|
||||
于是接收者永远进首页。
|
||||
4. `onShareAppMessage` 忽略了微信回调参数 `options.webViewUrl`(当前 web-view 真实地址,
|
||||
含 H5 的 SPA 路由),H5 的 postMessage `href` 又因投递延迟不可靠 → 深链丢失。
|
||||
|
||||
### 修复
|
||||
|
||||
| 端 | 改动 |
|
||||
|----|------|
|
||||
| 小程序首页 `pages/index/index.vue` | `onShareAppMessage(options)` 用 `options.webViewUrl`(兜底 `shareHref` / `webUrl`)拼 `path: '/pages/annex/web_view/index?url=...'`;`onMessage` 支持 `action:'share'/'setTitle'`;补 `onShareTimeline` + `uni.showShareMenu`;`onLoad` 支持 `?url=` 落地(朋友圈转发经此转交容器) |
|
||||
| 小程序容器 `pages/annex/web_view/index.vue` | `onShareAppMessage(options)` 同样优先 `options.webViewUrl`;`onShareTimeline` 兜底 `shareHref \|\| url` |
|
||||
| 两页 `stripAuthParams` | 按 key 过滤(不再用会吃掉 `#hash` 的正则),剥 query 的 `openid/unionid/t/_v` 与 hash 的 `openid/unionid/phone/nickname/avatar` |
|
||||
| H5 `app_03/frontend/src/app/services/bridge.ts` | **删除 `navigateBack`**;`share()` 只发 postMessage 并返回是否在小程序内,页内提示用户点右上角「···」 |
|
||||
|
||||
> **接收者身份**:分享链接一律剥掉发送者身份,接收者经 `pages/annex/web_view` 容器
|
||||
> 按自己的 openid 重新注入(容器 `_applyIdentity`,query 传参)。
|
||||
|
||||
## 五、好友转发 vs 朋友圈(微信平台差异)
|
||||
|
||||
| 场景 | 图片要求 | 实现 |
|
||||
|------|---------|------|
|
||||
| **转发给好友** `onShareAppMessage` | 支持 https 网络图 | `imageUrl: this.shareImageUrl`(box_app 网络 URL 直接用) |
|
||||
| **分享到朋友圈** `onShareTimeline` | 仅本地路径/代码包路径,网络图静默忽略 | `_prepareTimelineImage` 用 `uni.getImageInfo` 转本地临时路径 `shareTimelineImage` |
|
||||
|
||||
## 五、关键前置条件(易踩坑)
|
||||
> ⚠️ `onShareTimeline` **没有回调参数**,拿不到 `webViewUrl`,只能兜底 `shareHref || url`;
|
||||
> 它也不能自定义 `path`(只重开当前页 + query),所以首页用 `?url=` 把目标转交 web_view 容器。
|
||||
|
||||
## 六、关键前置条件(易踩坑)
|
||||
|
||||
1. **box_app 已配置分享图**:管理后台 → 应用 → 上传"分享图片"
|
||||
2. **图片必须公网 https 可访问**(微信服务器要抓取)
|
||||
3. **mp.yizhezhe.com 必须在微信后台合法域名白名单**:
|
||||
- `request` 合法域名(拉取 mp/apps 接口)
|
||||
- `request` 合法域名(拉取 mp/apps 接口;首页壳预热应用表也用它)
|
||||
- `downloadFile` 合法域名(朋友圈转本地图 getImageInfo)
|
||||
- ⚠️ 未配置时:好友图可能正常(网络图),但**朋友圈图转换失败回退默认截图**,`_fetchShareImage` fail 分支会提示
|
||||
4. **分享图有缓存,改图不是立刻生效**:反查表缓存 `share_apps_index_v2` 1h,单应用分享图缓存 `web_view_share_img:*` 24h。
|
||||
真机验证新图时可先清缓存(开发者工具 → 清除数据缓存 / 重新编译)或等 TTL 过期。
|
||||
|
||||
## 六、相关代码位置
|
||||
> **反查表与上架状态解耦(2026-09-22)**:旧数据源 `/api/mp/apps?isPublished=1` 只回上架应用,
|
||||
> 导致 mama-box(未建上架记录)和 laixin(下架、经 mama-box 底 tab/来信卡的硬编码兜底 URL 进入)
|
||||
> 拿不到自定义转发图。现改用专用接口 `GET /api/mp/public/share-images`(box_app `mp_router.py`):
|
||||
> 返回**所有配置了分享图**的应用(含下架),只含 `url + shareImageUrl` 两字段,按 url 长度倒序
|
||||
> (长前缀优先命中,防撞车)。**给「不上宫格但要可进入」的应用配图 = 后台建/编辑记录、传分享图、
|
||||
> 保持下架**;宫格展示仍由 `/api/mp/apps?isPublished=1` 决定,互不影响。缓存 key 随之 bump 为
|
||||
> `share_apps_index_v2`(旧缓存里只有上架应用,不 bump 会残留 1h)。
|
||||
|
||||
## 七、相关代码位置
|
||||
|
||||
| 位置 | 文件 |
|
||||
|------|------|
|
||||
| box_app 模型字段 | `box_app/backend/app/models/app.py` |
|
||||
| box_app 服务透传 | `box_app/backend/app/services/app_serv.py` |
|
||||
| box_app 表单上传 | `box_app/frontend/src/pages/ProductManagement.jsx` |
|
||||
| **分享图公共逻辑(两个宿主页共用)** | `crmeb/mixins/shareCard.js` → `resolveShareCard` / `syncShareCardTarget` / `_loadShareApps` / `_lookupShareImage` / `_fetchShareImage` / `_applyShareImage` / `_prepareTimelineImage` |
|
||||
| **宿主页其它公共能力**(消息分发 / 授权面板 / 授权回传 / openid+unionid) | `crmeb/mixins/webviewHost.js` → `handleHostMessage` / `consumeAuthResult` / `submitHostAuth` |
|
||||
| 小程序扫码取图 | `crmeb/pages/annex/web_view/index.vue` → `_resolveAppScene` |
|
||||
| 小程序兜底反查 | 同上 → `_fetchShareImage` / `_applyShareImage` / `_prepareTimelineImage` |
|
||||
| 小程序分享回调 | 同上 → `onShareAppMessage` / `onShareTimeline` / `handleMessage` |
|
||||
| H5 传图 | `app_03/frontend/src/app/services/bridge.ts` → `share({imageUrl})` |
|
||||
| 小程序容器页(onLoad 兜底反查 / 转发回调) | 同上 → `_fetchShareImage`(mixin)/ `onShareAppMessage` / `onShareTimeline` / `@message="handleHostMessage"` |
|
||||
| 小程序首页壳(应用宫格进入的 H5) | `crmeb/pages/index/index.vue` → `onLoad` 预热应用表 + `onShareAppMessage` 同步匹配(带 `spid`)/ `onShareTimeline` / `@message="handleHostMessage"` |
|
||||
| 首页分享兜底(mama-box 等无 App 记录时) | 首页壳 `getShare()` → CRMEB 后台「分享设置」的 `title` / `img` |
|
||||
| 分享链接净化 | 两页的 `stripAuthParams` / `dropKeyValuePairs` |
|
||||
| H5 传图 / 触发分享 | `app_03/frontend/src/app/services/bridge.ts` → `share({title,href,imageUrl})` |
|
||||
|
||||
> 授权/消息这一侧的完整链路(两条:独立授权页 与 宿主页内嵌面板)见
|
||||
> `box_app/docs/h5-miniprogram-auth.md` 第 6 节。
|
||||
|
||||
234
mixins/shareCard.js
Normal file
234
mixins/shareCard.js
Normal file
@@ -0,0 +1,234 @@
|
||||
// +----------------------------------------------------------------------
|
||||
// | 分享卡片:box_app 后台配置的分享图 + H5 postMessage 覆盖
|
||||
// +----------------------------------------------------------------------
|
||||
// 背景(详见 crmeb/docs/分享图片获取逻辑.md):
|
||||
// · web-view 里的 H5 不能自己拉起分享面板,卡片只能由承载 <web-view> 的页面产出;
|
||||
// · 分享图有两个来源,优先级:
|
||||
// ① box_app App 表的 shareImageUrl(按当前 H5 的 URL 前缀反查)
|
||||
// ② H5 postMessage({action:'share', imageUrl})(有值才覆盖 ①)
|
||||
// · 有两个宿主页都需要这套逻辑:
|
||||
// pages/annex/web_view/index.vue —— 容器页(太阳码 / 分享落地 / JumpPath 进入)
|
||||
// pages/index/index.vue —— 首页壳(mama-box 应用宫格用 location.href 换 H5,
|
||||
// 小程序页面栈不变,所以这里的分享永远发生在首页壳)
|
||||
// 本 mixin 把「反查 + 缓存 + 朋友圈本地图转换」抽出来共用,避免两边逻辑漂移。
|
||||
//
|
||||
// 用法:
|
||||
// ① import shareCard from '@/mixins/shareCard.js' → mixins: [shareCard]
|
||||
// ② 页面 onLoad 调一次 this._loadShareApps()(预热全量应用表,分享时可同步命中)
|
||||
// ③ onShareAppMessage(options) / 分享前:
|
||||
// const target = (options && options.webViewUrl) || this.shareHref || this.webUrl;
|
||||
// this.resolveShareCard(target); // 应用切换检测 + 每次按当前地址重解析(同步出图)
|
||||
// return { ..., imageUrl: this.shareImageUrl || '' };
|
||||
//
|
||||
// ⚠️ 首页壳不会随 H5 应用切换重新 onLoad,所以:
|
||||
// · 必须缓存「全量应用表」,在分享那一刻用 options.webViewUrl 同步匹配;
|
||||
// · **不能把 shareImageUrl 当长期状态**:每次分享都要重新解析,并检测 target 是否换了应用,
|
||||
// 否则 B 应用会沿用 A 应用的图(resolveShareCard / syncShareCardTarget 就是干这个的)。
|
||||
|
||||
const BOX_API_BASE = 'https://mp.yizhezhe.com';
|
||||
// 分享图反查表:box_app 专用接口,**含未上架应用**(mama-box 没有上架记录、laixin
|
||||
// 已下架但仍经底 tab/来信卡进入,它们也要有自定义转发图)。
|
||||
// 与「宫格展示」用的 /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 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 小时(后台改图最迟一天生效)
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
// ---------- 分享(H5 无法自己拉起分享面板,只能由承载 web-view 的页面转发) ----------
|
||||
shareTitle: '', // H5 postMessage(action:'share') 自定义标题
|
||||
shareSummary: '', // 分享摘要(部分场景用)
|
||||
shareHref: '', // H5 指定的分享链接(兜底;深链优先 webViewUrl)
|
||||
shareImageUrl: '', // 好友转发卡片图(网络 https)
|
||||
shareTimelineImage: '', // 朋友圈卡片图(必须本地路径,见 _prepareTimelineImage)
|
||||
shareImageSource: '', // 当前分享图来源:'box'(box_app 反查)| 'h5'(H5 postMessage 覆盖)
|
||||
shareImageBase: '', // 当前分享图所属的 H5 URL 基(切换应用时用它丢弃上一张)
|
||||
shareApps: [], // box_app 应用表(url → shareImageUrl),供同步匹配
|
||||
shareAppsLoaded: false, // 应用表是否已就绪
|
||||
shareAppsLoading: false,// 应用表请求中(防并发重入)
|
||||
_shareFetched: false, // 本页是否已做过一次异步兜底反查
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
/* 当前 H5 URL 去掉 query / hash / 末尾斜杠,作为应用 url 的匹配基 */
|
||||
_shareUrlBase(url) {
|
||||
return String(url || '').split('#')[0].split('?')[0].replace(/\/+$/, '');
|
||||
},
|
||||
|
||||
/* 清空分享卡片状态(应用切换时调用,避免张冠李戴) */
|
||||
_clearShareCard() {
|
||||
this.shareImageUrl = '';
|
||||
this.shareTimelineImage = '';
|
||||
this.shareImageSource = '';
|
||||
this.shareTitle = '';
|
||||
this.shareSummary = '';
|
||||
this.shareHref = '';
|
||||
},
|
||||
|
||||
/**
|
||||
* 分享前调用:检测"当前要分享的 H5 是否换了一个应用"。
|
||||
* 首页壳用 location.href 在 H5 内部换应用,小程序页面不重新 onLoad ——
|
||||
* 若不检测,上一个应用的分享图/标题/href 会一直留着(本次线上问题的根因)。
|
||||
* @returns {string} 当前 target 的 URL 基
|
||||
*/
|
||||
syncShareCardTarget(targetUrl) {
|
||||
const base = this._shareUrlBase(targetUrl);
|
||||
if (!base) return '';
|
||||
if (this.shareImageBase && this.shareImageBase !== base) {
|
||||
// 换了应用:丢弃上一应用的分享数据
|
||||
this._clearShareCard();
|
||||
}
|
||||
this.shareImageBase = base;
|
||||
return base;
|
||||
},
|
||||
|
||||
/**
|
||||
* 分享前的统一解析(两个宿主页的 onShareAppMessage 都调它):
|
||||
* ① 应用切换检测 → 换应用先清空;
|
||||
* ② 每次都按"当前 H5 地址"重新反查 box_app 的 shareImageUrl
|
||||
* (不能因为 shareImageUrl 已有值就跳过 —— 那可能是上一个应用的图);
|
||||
* ③ 当前图若来自本应用 H5 的 postMessage(source='h5'),保留不覆盖。
|
||||
*/
|
||||
resolveShareCard(targetUrl) {
|
||||
this.syncShareCardTarget(targetUrl);
|
||||
const fromBox = this._lookupShareImage(targetUrl);
|
||||
if (fromBox) {
|
||||
if (this.shareImageSource !== 'h5') this._applyShareImage(fromBox, 'box', targetUrl);
|
||||
} else if (this.shareImageSource === 'box') {
|
||||
// 当前应用没有 box_app 配置图,而现有图是上一个应用反查来的 → 丢弃
|
||||
this.shareImageUrl = '';
|
||||
this.shareImageSource = '';
|
||||
this.shareTimelineImage = '';
|
||||
}
|
||||
if (!this.shareAppsLoaded) this._loadShareApps();
|
||||
},
|
||||
|
||||
/* 在内存应用表里按 URL 前缀匹配 shareImageUrl(同步,分享那一刻就能出图) */
|
||||
_matchAppShareImage(targetUrl) {
|
||||
const base = this._shareUrlBase(targetUrl);
|
||||
if (!base) return '';
|
||||
const apps = this.shareApps || [];
|
||||
for (let i = 0; i < apps.length; i++) {
|
||||
const app = apps[i];
|
||||
if (!app || !app.url || !app.shareImageUrl) 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;
|
||||
}
|
||||
return '';
|
||||
},
|
||||
|
||||
/* 同步查图:优先内存应用表(onLoad 预热,1h 缓存),再退单应用缓存(24h TTL) */
|
||||
_lookupShareImage(targetUrl) {
|
||||
const base = this._shareUrlBase(targetUrl);
|
||||
if (!base) return '';
|
||||
// 1) 内存应用表:后台改图后最迟 1h 生效
|
||||
const fromList = this._matchAppShareImage(targetUrl);
|
||||
if (fromList) return fromList;
|
||||
// 2) 单应用缓存兜底(应用表请求失败 / 冷启动还没回来时用)
|
||||
try {
|
||||
const cached = uni.getStorageSync(APP_IMG_CACHE_PREFIX + base);
|
||||
// 兼容旧格式(纯 URL 字符串,无 TTL)
|
||||
const rec = (cached && typeof cached === 'object') ? cached : { t: 0, url: cached };
|
||||
if (rec && rec.url && String(rec.url).indexOf('http') === 0 &&
|
||||
(!rec.t || (Date.now() - rec.t) < APP_IMG_CACHE_TTL)) {
|
||||
return rec.url;
|
||||
}
|
||||
} catch (e) {}
|
||||
return '';
|
||||
},
|
||||
|
||||
/* 拉全量已上架应用(带 1h 本地缓存);cb 可选,拉完(或命中缓存)后回调 */
|
||||
_loadShareApps(cb) {
|
||||
// 1) 本地缓存命中:冷启动也能同步出图
|
||||
try {
|
||||
const cached = uni.getStorageSync(APPS_CACHE_KEY);
|
||||
if (cached && cached.items && cached.items.length && cached.t &&
|
||||
(Date.now() - cached.t) < APPS_CACHE_TTL) {
|
||||
this.shareApps = cached.items;
|
||||
this.shareAppsLoaded = true;
|
||||
if (cb) cb();
|
||||
return;
|
||||
}
|
||||
} catch (e) {}
|
||||
// 2) 请求中:直接返回,避免并发重复拉(漏掉的 cb 由调用方自身的兜底路径覆盖)
|
||||
if (this.shareAppsLoading) return;
|
||||
this.shareAppsLoading = true;
|
||||
uni.request({
|
||||
url: APPS_API,
|
||||
method: 'GET',
|
||||
success: (res) => {
|
||||
const items = (res.data && res.data.result && res.data.result.items) || [];
|
||||
if (items.length) {
|
||||
this.shareApps = items;
|
||||
this.shareAppsLoaded = true;
|
||||
try { uni.setStorageSync(APPS_CACHE_KEY, { t: Date.now(), items: items }); } catch (e) {}
|
||||
}
|
||||
if (cb) cb();
|
||||
},
|
||||
fail: (err) => {
|
||||
// 常见原因:mp.yizhezhe.com 未加入小程序后台 request 合法域名白名单
|
||||
console.warn('[shareCard] 应用表拉取失败(请检查 mp.yizhezhe.com 是否在 request 合法域名):', err);
|
||||
if (cb) cb();
|
||||
},
|
||||
complete: () => { this.shareAppsLoading = false; }
|
||||
});
|
||||
},
|
||||
|
||||
/* 异步兜底反查(容器页 onLoad 用):同步没命中就拉应用表再匹配一次 */
|
||||
_fetchShareImage(targetUrl) {
|
||||
if (!targetUrl) return;
|
||||
this.syncShareCardTarget(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);
|
||||
});
|
||||
},
|
||||
|
||||
/* 统一设置分享图:好友转发用网络图;朋友圈另转本地图(见下)
|
||||
@param source 'box'(box_app 反查,默认)| 'h5'(H5 postMessage 覆盖)
|
||||
@param forUrl 该图所属的 H5 地址(用于切换应用时判断是否过期) */
|
||||
_applyShareImage(url, source, forUrl) {
|
||||
if (!url) return;
|
||||
if (forUrl) this.shareImageBase = this._shareUrlBase(forUrl);
|
||||
if (url === this.shareImageUrl) {
|
||||
if (source) this.shareImageSource = source;
|
||||
return; // 同一张图不重复处理(H5 可能重复 postMessage)
|
||||
}
|
||||
this.shareImageUrl = url;
|
||||
this.shareImageSource = source || 'box';
|
||||
this._prepareTimelineImage(url);
|
||||
},
|
||||
|
||||
/* 朋友圈分享图必须是本地路径/代码包路径(网络图会被微信静默忽略)→ getImageInfo 转本地
|
||||
注意:getImageInfo 对网络图同样受 downloadFile 合法域名限制,域名未配时朋友圈无自定义图。 */
|
||||
_prepareTimelineImage(url) {
|
||||
if (!url) return;
|
||||
uni.getImageInfo({
|
||||
src: url,
|
||||
success: (res) => {
|
||||
this.shareTimelineImage = res.path || '';
|
||||
console.log('[shareCard] 朋友圈分享图已转本地:', this.shareTimelineImage);
|
||||
},
|
||||
fail: (err) => {
|
||||
console.warn('[shareCard] 朋友圈分享图转本地失败(网络图/域名问题,回退默认截图):', err);
|
||||
this.shareTimelineImage = '';
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user