feat: 扫码进去小程序的几种方式兼容支持

This commit is contained in:
zoujing committed 2026-09-20 14:28:13 +08:00
1 parent 681ba3d8f1
commit a4c29569bf
3 files changed
+108 -5

No files matched your search

+17 -2
View File
@@ -1,12 +1,27 @@
<script setup>
import { onLaunch } from "@dcloudio/uni-app";
import { onLaunch, onShow } from "@dcloudio/uni-app";
import { useUserStore } from "@/store";
import userApi from "@/api/user";
import { handleEntry } from "@/utils/nav";
onLaunch(() => {
/**
* 启动/唤起参数 → 统一的入口对象。
* ⚠️ 只取 options.query:App 层的 options.scene 是「场景值」(数字),
* 小程序码真正的 scene 字符串在 options.query.scene 里,不能用前者覆盖后者。
*/
const entryParams = (options) => ({ ...((options && options.query) || {}) });
onLaunch((options) => {
useUserStore().restore();
// 本地若留有注销回执,查一次清理进度(无回执时不发请求),完成即丢弃
userApi.fetchErasureStatus();
// 扫码进入:冷启动参数在 onLaunch(页面 onLoad 也会带,双保险)
handleEntry(entryParams(options));
});
onShow((options) => {
// 后台被再次扫码唤起:页面 onLoad 不会重跑,参数只在这里
handleEntry(entryParams(options));
});
</script>
+35 -3
View File
@@ -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();
});
</script>
+56
View File
@@ -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}=([^&]*)`));