/** * iOS 包 H5 迁移 — 本地模拟器(假数据 + 场景预设) * ============================================================ * 用途:后端 /api/mig/* 未就绪时,在本地/测试环境完整走通客户端全流程。 * 开关:isDebugEnv()(localhost / 192.* / *.usgamewin.com)下,URL 带 ?migsim=1 开启、 * ?migsim=0 关闭;状态明文存 localStorage(MIG_SIM / MIG_SIM_STATE),生产域名永远不生效。 * 原则:客户端逻辑全部真实,仅 service.js 的网络层被替换成本文件的假实现。 */ import { isDebugEnv } from "@utils/helpers"; const KEY_ON = "MIG_SIM"; const KEY_STATE = "MIG_SIM_STATE"; /** 场景预设:与《03-交互原型》10 屏一一对应(config 片段,叠加在 DEFAULT 上) */ export const SIM_PRESETS = { "new_soft_b1": { note: "新增T0-6·B1首充banner", track: "new", stage: "soft", state: "S0", vip: 1, show_e1: 0, show_e2: 0, block_days: 12 }, "soft_nopay": { note: "新增·软引导期但未首充(show_banner=false)·无banner", track: "new", stage: "soft", state: "S0", vip: 1, banner_flag: "off", show_e1: 0, show_e2: 0, block_days: 12 }, "new_soft_e1": { note: "新增T0-6·E1首提软弹", track: "new", stage: "soft", state: "S0", vip: 1, show_e1: 1, show_e2: 0, block_days: 12 }, "new_soft_e2": { note: "新增T0-6·E2三充次日软弹", track: "new", stage: "soft", state: "S0", vip: 1, show_e1: 0, show_e2: 1, block_days: 12 }, "cd_bar_only": { note: "存量D1-3·后端关强弹(show_strong=false)·仅倒计时条", track: "legacy", stage: "countdown", state: "S0", vip: 0, strong_flag: "off", block_days: 6 }, "cd_strong_day1": { note: "强弹首日·利益角度C-CD2", track: "legacy", stage: "countdown", state: "S0", vip: 1, strong_copy: "C-CD2", block_days: 3 }, "cd_strong": { note: "强弹次日起·缺省派生·损失规避C-CD2b", track: "legacy", stage: "countdown", state: "S0", vip: 1, strong_copy: "C-CD2b", block_days: 3 }, "cd_strong_forced": { note: "后端强开强弹(show_strong=true)", track: "legacy", stage: "countdown", state: "S0", vip: 1, strong_flag: "on", block_days: 3 }, "s1_retry": { note: "S1≥48h·retry变体C-CD2r", track: "new", stage: "countdown", state: "S1", vip: 1, strong_copy: "C-CD2r", block_days: 3 }, "s2_finish": { note: "S2·停强弹+Finish文案", track: "new", stage: "countdown", state: "S2", vip: 1, block_days: 3 }, "hard_block": { note: "硬拦截·3次Later", track: "legacy", stage: "block", state: "S0", vip: 1, later_remaining: 3, block_days: -1 }, "later_exhausted": { note: "Later用尽·锁大厅", track: "legacy", stage: "block", state: "S0", vip: 0, later_remaining: 0, block_days: -1 }, "migrated_s3": { note: "S3·全覆盖成功页", track: "new", stage: "countdown", state: "S3", migrated: 1, vip: 1, block_days: 3 }, "cfg_fail": { note: "config失败·无任何触点(无本地兜底)", track: "legacy", stage: "block", state: "S0", cfg_fail: 1, block_days: -1 }, "channel_disabled": { note: "渠道未开放(enabled=false)", channel_off: 1 } }; const DEFAULT_STATE = { preset: "new_soft_b1", track: "new", stage: "soft", state: "S0", migrated: 0, vip: 1, banner_flag: "auto", show_e1: 0, show_e2: 0, strong_flag: "auto", strong_copy: "C-CD2b", later_remaining: 3, freeze: 0, cfg_fail: 0, channel_off: 0, block_days: 12 }; /** URL ?migsim=1|0 开/关(仅 debug 环境);读一次即从地址栏清掉 */ export const armSimFromUrl = () => { if (!isDebugEnv()) return; try { const u = new URL(window.location.href); const v = u.searchParams.get("migsim"); if (v == null) return; if (v === "0") { localStorage.removeItem(KEY_ON); localStorage.removeItem(KEY_STATE); } else localStorage.setItem(KEY_ON, "1"); u.searchParams.delete("migsim"); window.history.replaceState(null, "", u.pathname + u.search + u.hash); } catch (e) { /* ignore */ } }; export const isSimOn = () => { try { return isDebugEnv() && localStorage.getItem(KEY_ON) === "1"; } catch (e) { return false; } }; /* ───────── 真机联调开关(与 sim 互补)───────── * migsim=1 :假数据、不出网络,只看 UI; * migdev=1 :走真实 /game/mig/config,面板可直接打后端 /game/mig/test/state 造状态(真链路)。 */ const KEY_DEV = "MIG_DEV"; /** URL ?migdev=1|0 开/关(仅 debug 环境);读一次即从地址栏清掉 */ export const armDevFromUrl = () => { if (!isDebugEnv()) return; try { const u = new URL(window.location.href); const v = u.searchParams.get("migdev"); if (v == null) return; if (v === "0") localStorage.removeItem(KEY_DEV); else localStorage.setItem(KEY_DEV, "1"); u.searchParams.delete("migdev"); window.history.replaceState(null, "", u.pathname + u.search + u.hash); } catch (e) { /* ignore */ } }; export const isDevOn = () => { try { return isDebugEnv() && localStorage.getItem(KEY_DEV) === "1"; } catch (e) { return false; } }; export const getSimState = () => { try { const raw = localStorage.getItem(KEY_STATE); return { ...DEFAULT_STATE, ...(raw ? JSON.parse(raw) : {}) }; } catch (e) { return { ...DEFAULT_STATE }; } }; export const setSimState = (patch = {}) => { const next = { ...getSimState(), ...patch }; try { localStorage.setItem(KEY_STATE, JSON.stringify(next)); } catch (e) { /* ignore */ } return next; }; export const applySimPreset = (name) => { const p = SIM_PRESETS[name]; if (!p) return getSimState(); const { note, ...fields } = p; // 预设为完整场景:未提字段回落 DEFAULT(而非残留上个场景) return setSimState({ ...DEFAULT_STATE, ...fields, preset: name }); }; /** * 由 sim 状态生成 /game/mig/config 假响应(结构对齐《IOS_H5_MIGRATION_API_CN》精简 config)。 * 只输出后端真实下发的字段:enabled/track/stage/migrated/mig_state/platform/platform_at/ * now/block_at/reward_pack_text/show_e1/show_e2/hard_block(+ 可选 strong_copy_id、show_strong、show_banner)。 * banner/countdown_bar 触点由客户端按 stage 自派生;强弹为兼容形式:后端可选下发 show_strong,缺省按 stage。 */ export const buildSimConfig = (s = getSimState()) => { if (s.channel_off) return { enabled: false }; // 渠道未开放迁移 const packText = s.vip ? `77 Free Bonus + ${global.DOLLAR || "£"}7 Cash` : "77 Free Bonus"; const now = Date.now(); const blockAt = new Date(now + Number(s.block_days) * 864e5 + 7 * 36e5).toISOString(); // 端类型 → platform 枚举(与后端一致:0 H5 / 1 iOS / 2 Android / 3 PWA);sim 下按 mig_state 粗映射 const platform = s.migrated ? 3 : (s.state === "S2" || s.state === "S1" ? 0 : 1); const conf = { enabled: true, track: s.track, stage: s.stage, migrated: !!s.migrated, mig_state: s.state, platform, platform_at: new Date(now).toISOString(), now: new Date(now).toISOString(), // 服务器当前时间:客户端倒计时以此校准(serverNow) block_at: blockAt, reward_pack_text: packText, show_e1: !!s.show_e1, show_e2: !!s.show_e2, hard_block: { enabled: s.stage === "block", later_total: 3, later_remaining: Number(s.later_remaining), later_scope: "session" } }; if (s.strong_copy && s.strong_copy !== "C-CD2b") conf.strong_copy_id = s.strong_copy; // 可选覆盖强弹文案 // 强弹开关:仅当模拟"后端显式下发"时才带 show_strong;"auto" 不下发,验证前端缺省按 stage 派生 if (s.strong_flag === "on") conf.show_strong = true; else if (s.strong_flag === "off") conf.show_strong = false; // B1 banner 开关(首充):同上,"auto" 不下发 → 前端缺省 stage=soft 即挂;"off" 模拟"未首充" if (s.banner_flag === "on") conf.show_banner = true; else if (s.banner_flag === "off") conf.show_banner = false; return conf; }; /** 假 POST /game/mig/later(用完对齐后端:code 429 + msg 数组 + data.later_remaining=0) */ export const simConsumeLater = () => { const s = getSimState(); if (Number(s.later_remaining) <= 0) { return { code: 429, msg: ["mig.later_exhausted", "All Later passes used"], data: { later_remaining: 0 } }; } const next = setSimState({ later_remaining: Number(s.later_remaining) - 1 }); return { code: 200, data: { later_remaining: next.later_remaining, play_pass: "SIM_PASS_" + Date.now(), scope: "session" } }; };