diff --git a/src/App.vue b/src/App.vue index cc30817..417aa13 100644 --- a/src/App.vue +++ b/src/App.vue @@ -1,12 +1,27 @@ diff --git a/src/pages/index/index.vue b/src/pages/index/index.vue index 64a0b70..e77d2cf 100644 --- a/src/pages/index/index.vue +++ b/src/pages/index/index.vue @@ -2,11 +2,21 @@ /** * 主包壳页面:主页 / 权益 / 我的 三块面板同屏共存,底部 Tab 栏只渲染一次 * --------------------------------------------------------------------------- + * 扫码入口:二维码指向本页并带商家 id(`merchant_id` / `scene` / `q` 三种来源都认), + * 进来自动切到权益 Tab 再打开商家详情 —— 取值见 utils/nav.js 的 pickMerchantId / handleEntry。 */ -import { onMounted, ref } from "vue"; -import { onShow } from "@dcloudio/uni-app"; +import { nextTick, onMounted, ref } from "vue"; +import { onLoad, onShow } from "@dcloudio/uni-app"; import { TABS } from "@/constant"; -import { consumePendingTab, registerTabController, setTabQuery } from "@/utils/nav"; +import { + consumePendingMerchant, + consumePendingTab, + goDetail, + pickMerchantId, + registerTabController, + setPendingMerchant, + setTabQuery, +} from "@/utils/nav"; import HomePanel from "@/views/home/index.vue"; import BenefitPanel from "@/views/benefit/index.vue"; import MinePanel from "@/views/mine/index.vue"; @@ -32,17 +42,39 @@ const goTab = (key, query = "") => { showPanel(key); }; +/** Tab 控制器是否已就绪(onShow 可能早于 onMounted,未就绪时不消费扫码参数) */ +let shellReady = false; + +/** + * 扫码进店:先切权益 Tab 再打开商家详情。 + * 顺序不能反 —— 先切 Tab,「详情返回」才会落在权益页而不是主页。 + */ +const runEntry = () => { + if (!shellReady) return; + const id = consumePendingMerchant(); + if (!id) return; + goTab("benefit"); + nextTick(() => setTimeout(() => goDetail(id), 0)); +}; + +onLoad((options) => { + setPendingMerchant(pickMerchantId(options)); +}); + onMounted(() => { // 面板的 onMounted 早于本页 onMounted,此时 ref 已就绪 registerTabController({ go: goTab, getActive: () => active.value }); // 从子页 / 登录页切过来:nav 里可能压着待切 Tab(登录回跳、reLaunch 冷启) const pending = consumePendingTab(); goTab(pending?.key || active.value, pending?.query || ""); + shellReady = true; + runEntry(); }); onShow(() => { // 从板块列表 / 详情 / 登录页等返回:让当前面板按需刷新(各家内部有幂等守卫) showPanel(active.value); + runEntry(); }); diff --git a/src/utils/nav.js b/src/utils/nav.js index f20f5ef..8b6260f 100644 --- a/src/utils/nav.js +++ b/src/utils/nav.js @@ -28,6 +28,8 @@ export const registerTabController = (ctl) => { let tabQuery = ""; /** 壳尚未就绪时的待切 Tab */ let pendingTab = null; +/** 扫码 / 外链进入时待打开的商家 id */ +let pendingMerchant = ""; export const setTabQuery = (query) => { tabQuery = query || ""; @@ -47,6 +49,60 @@ export const consumePendingTab = () => { return pending; }; +/** 记下待打开的商家 id(空值不覆盖已有的) */ +export const setPendingMerchant = (id) => { + const value = String(id || "").trim(); + if (value) pendingMerchant = value; +}; + +/** 取出并清空待打开的商家 id */ +export const consumePendingMerchant = () => { + const value = pendingMerchant; + pendingMerchant = ""; + return value; +}; + +/** + * 从启动/唤起参数里取商家 id:兼容四种来源 + * - `merchant_id` / `merchantId` / `id`:页面 query 直传(小程序码 path、分享链接) + * - `scene`:小程序码 getUnlimited,值可能是 `merchant_id=xxx` 也可能是短码 + * - `q`:普通链接二维码,值是 URL 编码过的原始链接,解码后再取 query + */ +export const pickMerchantId = (options) => { + const o = options || {}; + const direct = o.merchant_id || o.merchantId || o.id || ""; + if (direct) return decodeURIComponent(String(direct)).trim(); + if (o.scene) { + const scene = decodeURIComponent(String(o.scene)); + return ( + pickParam(scene, "merchant_id") || + pickParam(scene, "merchantId") || + pickParam(scene, "id") || + scene.trim() + ); + } + if (o.q) { + const query = decodeURIComponent(String(o.q)).split("?")[1] || ""; + return pickParam(query, "merchant_id") || pickParam(query, "merchantId") || pickParam(query, "id"); + } + return ""; +}; + +/** + * 入口参数处理(App.onLaunch / App.onShow 调用):记下商家 id; + * 栈顶不是壳页时 reLaunch 回壳 —— 由壳消费 pending 后切权益 Tab 并打开详情。 + * 冷启动时页面栈还没建立(getCurrentPages 为空),此时只记 pending,不 reLaunch。 + */ +export const handleEntry = (options) => { + const id = pickMerchantId(options); + if (!id) return; + setPendingMerchant(id); + const pages = getCurrentPages(); + if (!pages.length) return; + const top = `/${pages[pages.length - 1].route}`; + if (top !== ROUTES.HOME) uni.reLaunch({ url: ROUTES.HOME }); +}; + /** query 串取参数 */ const pickParam = (query, key) => { const matched = (query || "").match(new RegExp(`(?:^|&)${key}=([^&]*)`));