Răsfoiți Sursa

Enhance app functionality and configuration: Added device info handling in app.js, integrated preload rules in app.json, and improved web-view component with payment features. Updated component usage across various pages and adjusted project settings for better performance.

徐福静0235668 5 luni în urmă
părinte
comite
25dc758e87

+ 91 - 1
README.md

@@ -57,4 +57,94 @@ npm run docs
 # 发布上线
 rsync -avPW dist/docs/ root@server.fmode.cn:/var/www/ng-fmode/wapp/
 scp -r dist/docs/* root@server.fmode.cn:/var/www/ng-fmode/wapp/
-```
+```
+
+## H5 嵌套小程序支付迁移(不改 nova-payment 组件)
+
+### H5 -> 小程序消息协议
+
+H5 在小程序 web-view 容器内,支付时发送:
+
+```js
+wx.miniProgram.postMessage({
+  data: {
+    type: 'requestMiniPay',
+    payload: {
+      tradeNo: 'ORDER202604090001',
+      price: 99.9,
+      orderId: 'parse_order_object_id',
+      orderType: 'shopgoods',
+      showType: 'all',
+      showBonus: false,
+      profileId: '',
+      // 可选:用于接收小程序支付结果(会刷新 web-view 到该地址)
+      callbackUrl: 'https://www.yuban.co/pay/result'
+    }
+  }
+});
+```
+
+### 小程序侧回传结果约定
+
+小程序内部统一生成 `miniPayResult` 结构:
+
+```js
+{
+  type: 'miniPayResult',
+  payload: {
+    success: true,
+    tradeNo: 'ORDER202604090001',
+    payType: 'wxpay',
+    cancelled: false,
+    errMsg: ''
+  }
+}
+```
+
+说明:
+- `web-view` 不支持小程序反向 `postMessage` 到 H5。
+- 若传了 `callbackUrl`,小程序会把结果拼接到 URL 查询参数并刷新到该地址。
+- 若不传 `callbackUrl`,建议 H5 按 `tradeNo` 轮询订单状态。
+
+### H5 运行时分流(不影响原 H5 支付)
+
+```js
+function isMiniProgramEnv() {
+  return window.__wxjs_environment === 'miniprogram';
+}
+
+function pay(order) {
+  if (isMiniProgramEnv() && window.wx?.miniProgram?.postMessage) {
+    wx.miniProgram.postMessage({
+      data: {
+        type: 'requestMiniPay',
+        payload: {
+          tradeNo: order.tradeNo,
+          price: order.price,
+          orderId: order.orderId,
+          orderType: order.orderType || 'shopgoods',
+          callbackUrl: order.callbackUrl || ''
+        }
+      }
+    });
+    return;
+  }
+
+  // 非小程序容器:保持原 H5 支付逻辑(chooseWXPay/WeixinJSBridge)
+  runLegacyH5Pay(order);
+}
+```
+
+## 代码质量平台排除建议(第三方依赖)
+
+当质量检查命中 `miniprogram_npm/@vant/weapp/**` 下的第三方文件时,建议在微信代码质量平台配置路径排除:
+
+- `miniprogram_npm/@vant/weapp/**`
+
+建议保留业务目录扫描:
+
+- `common-page/**`
+- `components/**`
+- `yuban/**`
+- `app.js`
+- `app.json`

+ 18 - 12
app.js

@@ -2,6 +2,7 @@
 let Nova = require("./utils/nova.js");
 const CONFIG = require("config.js");
 const request = require("./utils/request");
+require("exportToPlugin.js");
 const plugin = requirePlugin('fm-plugin')
 const { Parse } = plugin
 App({
@@ -59,24 +60,29 @@ App({
       this.globalData.company = extConfig.company
       this.globalData.appid = extConfig.wxappid
     }
-    let {
-      model,
-      platform,
-      statusBarHeight,
-      safeArea,
-      screenHeight
-    } = wx.getSystemInfoSync();
+    const deviceInfo = typeof wx.getDeviceInfo === 'function' ? wx.getDeviceInfo() : {};
+    const windowInfo = typeof wx.getWindowInfo === 'function' ? wx.getWindowInfo() : {};
+    const appBaseInfo = typeof wx.getAppBaseInfo === 'function' ? wx.getAppBaseInfo() : {};
+    const fallbackSystemInfo =
+      (!deviceInfo.model && !windowInfo.statusBarHeight && typeof wx.getSystemInfoSync === 'function')
+        ? wx.getSystemInfoSync()
+        : {};
+
+    const model = deviceInfo.model || fallbackSystemInfo.model || '';
+    const platform = deviceInfo.platform || fallbackSystemInfo.platform || '';
+    const statusBarHeight = windowInfo.statusBarHeight || fallbackSystemInfo.statusBarHeight || 0;
+    const safeArea = windowInfo.safeArea || fallbackSystemInfo.safeArea || null;
+    const screenHeight = windowInfo.screenHeight || fallbackSystemInfo.screenHeight || 0;
+
     this.globalData.platform = platform;
     this.globalData.statusBarHeight = statusBarHeight;
     this.globalData.safeArea = safeArea;
     this.globalData.screenHeight = screenHeight;
-    this.globalData.isIpx = model.includes("iPhone X");
+    this.globalData.isIpx = /iphone\s{0,}x/i.test(model);
 
-    let {
-      system
-    } = wx.getSystemInfoSync();
+    const system = appBaseInfo.system || fallbackSystemInfo.system || '';
     let headHeight;
-    if (/iphone\s{0,}x/i.test(model)) {
+    if (this.globalData.isIpx) {
       headHeight = 88;
     } else if (system.indexOf("Android") !== -1) {
       headHeight = 68;

+ 10 - 5
app.json

@@ -19,6 +19,14 @@
       ]
     }
   ],
+  "preloadRule": {
+    "yuban/pages/index/index": {
+      "packages": [
+        "common"
+      ],
+      "network": "all"
+    }
+  },
   "window": {
     "navigationStyle": "custom",
     "navigationBarTitleText": "",
@@ -27,6 +35,7 @@
     "backgroundColor": "#f6f5fa",
     "enablePullDownRefresh": false
   },
+  "lazyCodeLoading": "requiredComponents",
   "permission": {
     "scope.userLocation": {
       "desc": "你的位置信息将用于小程序位置接口的效果展示"
@@ -43,11 +52,7 @@
     "address": "/components/address/index",
     "upload": "/components/upload/index",
     "van-icon": "@vant/weapp/icon/index",
-    "van-button": "@vant/weapp/button/index",
-    "van-tabbar": "@vant/weapp/tabbar/index",
-    "van-tabbar-item": "@vant/weapp/tabbar-item/index",
-    "van-empty": "@vant/weapp/empty/index",
-    "van-loading": "@vant/weapp/loading/index"
+    "van-button": "@vant/weapp/button/index"
   },
   "sitemapLocation": "sitemap.json",
   "plugins": {

+ 1 - 3
common-page/pages/collect/index.json

@@ -1,9 +1,7 @@
 {
   "usingComponents": {
-    "van-stepper": "@vant/weapp/stepper/index",
     "van-icon": "@vant/weapp/icon/index",
     "van-swipe-cell": "@vant/weapp/swipe-cell/index",
-    "van-cell": "@vant/weapp/cell/index",
-    "van-cell-group": "@vant/weapp/cell-group/index"
+    "van-empty": "@vant/weapp/empty/index"
   }
 }

+ 4 - 1
common-page/pages/map-open/index.json

@@ -1,4 +1,7 @@
 {
   "navigationBarTitleText": "地图导航",
-  "usingComponents": {}
+  "usingComponents": {
+    "van-loading": "@vant/weapp/loading/index",
+    "van-empty": "@vant/weapp/empty/index"
+  }
 }

+ 2 - 1
common-page/pages/nova-express/index.json

@@ -1,5 +1,6 @@
 {
   "usingComponents": {
-    "van-steps": "@vant/weapp/steps/index"
+    "van-steps": "@vant/weapp/steps/index",
+    "van-empty": "@vant/weapp/empty/index"
   }
 }

+ 171 - 32
common-page/pages/web-view/index.js

@@ -1,6 +1,12 @@
 // common-page/pages/web-view/index.js
 const Parse = getApp().Parse;
 const company = getApp().globalData.company;
+const DEBUG_WEBVIEW = false;
+const debugLog = (...args) => {
+    if (DEBUG_WEBVIEW) {
+        console.log(...args);
+    }
+};
 
 Page({
     /**
@@ -9,6 +15,16 @@ Page({
     data: {
         path: "",
         currentTitle: "", // 当前标题
+        miniPayVisible: false,
+        miniPayPrice: 0,
+        miniPayTradeNo: "",
+        miniPayOrderId: "",
+        miniPayOrderType: "shopgoods",
+        miniPayShowType: "all",
+        miniPayShowBonus: false,
+        miniPayProfileId: "",
+        miniPayProcessing: false,
+        miniPayRequestContext: null,
     },
 
     // 标题轮询定时器
@@ -18,24 +34,30 @@ Page({
      * 生命周期函数--监听页面加载
      */
     onLoad: async function (options) {
-        console.log('===========================================');
-        console.log('======= web-view 页面加载 =======');
+        debugLog('======= web-view 页面加载 =======');
         
         // 1. 先检查用户登录状态
         const loginCheck = await this.checkUserLogin(options);
         if (!loginCheck) {
-            console.log('⚠️ 用户未登录或没有手机号,已跳转到授权页面');
-            console.log('===========================================');
+            debugLog('⚠️ 用户未登录或没有手机号,已跳转到授权页面');
             return; // 停止后续加载
         }
-        
-        console.log('✅ 用户已登录且有手机号,继续加载 web-view');
+        debugLog('✅ 用户已登录且有手机号,继续加载 web-view');
         
         // 2. 解码 URL
         let path = decodeURIComponent(options.path || '');
+        const isSkipAuth = options.skipAuth === 'true';
+        const isHomePath = /^https?:\/\/www\.yuban\.co\/home([/?#]|$)/.test(path);
+
+        // 引导页直达 H5 首页时走快速路径:
+        // 仅设置 web-view URL,避免额外参数拼接和 Parse 查询造成首开延迟。
+        if (isSkipAuth && isHomePath) {
+            this.setData({ path });
+            return;
+        }
 
-        console.log('原始 options.path:', options.path);
-        console.log('解码后的 path:', path);
+        debugLog('原始 options.path:', options.path);
+        debugLog('解码后的 path:', path);
 
         // 拼接额外参数(避免重复添加已存在的参数)
         let hasQuery = path.indexOf('?') !== -1;
@@ -85,9 +107,8 @@ Page({
             path = path + parsm;
         }
 
-        console.log('最终 web-view URL:', path);
-        console.log('URL 长度:', path.length);
-        console.log('===========================================');
+        debugLog('最终 web-view URL:', path);
+        debugLog('URL 长度:', path.length);
 
         this.setData({
             path: path
@@ -98,7 +119,7 @@ Page({
         const passedStoreId = options.storeId || '';
         // /home 场景下默认不需要门店标题:跳过 ShopStore 的 Parse 查询以提速。
         // 这里根据 H5 path 判断:例如 https://www.yuban.co/home?token=...&skipAuth=true...
-        const isHome = /^https?:\/\/www\.yuban\.co\/home([/?#]|$)/.test(path);
+        const isHome = isHomePath;
 
         if (passedStoreName) {
             this.setNavigationTitle(passedStoreName);
@@ -120,28 +141,26 @@ Page({
      */
     checkUserLogin: async function(options) {
         try {
-            console.log('===========================================');
-            console.log('🔍 开始检查用户登录状态...');
+            debugLog('🔍 开始检查用户登录状态...');
             
             // 检查是否有 skipAuth 参数(用于跳过登录检查)
             if (options.skipAuth === 'true') {
-                console.log('ℹ️ 检测到 skipAuth 参数,跳过登录检查');
-                console.log('===========================================');
+                debugLog('ℹ️ 检测到 skipAuth 参数,跳过登录检查');
                 return true;
             }
 
             // 检查是否是扫码进入
             const isScanEntry = options && (options.storeId && (options.scanCount || options.partnerId || options.userId || options.employeeId));
             if (isScanEntry) {
-                console.log('🚀 检测到扫码进入,准备强制登录以确保流量扣减');
+                debugLog('🚀 检测到扫码进入,准备强制登录以确保流量扣减');
             }
             
             // 1. 先调用 checkAuth 初始化用户(扫码进入时强制授权)
-            console.log('📱 调用 checkAuth 初始化用户...');
+            debugLog('📱 调用 checkAuth 初始化用户...');
             try {
                 // 如果是扫码进入,则强制授权;否则不强制(由后面逻辑决定)
                 await getApp().checkAuth(isScanEntry); 
-                console.log('✅ checkAuth 调用成功');
+                debugLog('✅ checkAuth 调用成功');
             } catch (err) {
                 console.warn('⚠️ checkAuth 调用失败:', err);
             }
@@ -151,36 +170,34 @@ Page({
             const hasMobile = currentUser?.get('mobile');
             const userLogin = wx.getStorageSync('userLogin');
             
-            console.log('📊 用户状态:');
-            console.log('   - 当前用户:', currentUser ? currentUser.id : '无');
-            console.log('   - 手机号:', hasMobile || '无');
-            console.log('   - userLogin 存储:', userLogin || '无');
+            debugLog('📊 用户状态:');
+            debugLog('   - 当前用户:', currentUser ? currentUser.id : '无');
+            debugLog('   - 手机号:', hasMobile || '无');
+            debugLog('   - userLogin 存储:', userLogin || '无');
             
             // 只有同时满足以下条件才认为已完整登录:
             // 1. Parse.User.current() 存在
             // 2. 用户有手机号
             // 3. userLogin 存储存在
             if (currentUser && hasMobile && userLogin) {
-                console.log('✅ 用户已完整登录');
-                console.log('===========================================');
+                debugLog('✅ 用户已完整登录');
                 return true;
             }
             
             // 用户未登录或没有手机号,跳转到授权页面
-            console.log('⚠️ 用户未完整登录,准备跳转到授权页面');
+            debugLog('⚠️ 用户未完整登录,准备跳转到授权页面');
             
             // 构建返回 URL(登录成功后返回当前页面)
             const currentPath = options.path || '';
             const returnUrl = encodeURIComponent(currentPath);
             
-            console.log('🔗 returnUrl:', returnUrl);
-            console.log('===========================================');
+            debugLog('🔗 returnUrl:', returnUrl);
             
             // 跳转到授权页面,并传递 returnUrl
             wx.redirectTo({
                 url: `/components/app-auth/index?returnUrl=${returnUrl}`,
                 success: () => {
-                    console.log('✅ redirectTo 到授权页面成功');
+                    debugLog('✅ redirectTo 到授权页面成功');
                 },
                 fail: (err) => {
                     console.error('❌ redirectTo 失败:', err);
@@ -189,7 +206,7 @@ Page({
                     wx.navigateTo({
                         url: `/components/app-auth/index?returnUrl=${returnUrl}`,
                         success: () => {
-                            console.log('✅ navigateTo 到授权页面成功');
+                            debugLog('✅ navigateTo 到授权页面成功');
                         },
                         fail: (err2) => {
                             console.error('❌ navigateTo 也失败:', err2);
@@ -198,7 +215,7 @@ Page({
                             wx.reLaunch({
                                 url: `/components/app-auth/index?returnUrl=${returnUrl}`,
                                 success: () => {
-                                    console.log('✅ reLaunch 到授权页面成功');
+                                    debugLog('✅ reLaunch 到授权页面成功');
                                 },
                                 fail: (err3) => {
                                     console.error('❌ 所有跳转方式都失败:', err3);
@@ -213,7 +230,7 @@ Page({
             
         } catch (err) {
             console.error('❌ 检查登录状态失败:', err);
-            console.log('===========================================');
+            debugLog('checkUserLogin error');
             
             // 出错时也跳转到授权页面
             const returnUrl = options.path ? encodeURIComponent(options.path) : '';
@@ -275,10 +292,16 @@ Page({
 
             // 找到最后一个标题更新消息
             let lastTitleMessage = null;
+            let lastPayMessage = null;
             for (let i = messages.length - 1; i >= 0; i--) {
                 const msg = messages[i];
                 if (msg.type === 'updateTitle' && msg.title) {
                     lastTitleMessage = msg;
+                }
+                if (!lastPayMessage && msg.type === 'requestMiniPay') {
+                    lastPayMessage = msg;
+                }
+                if (lastTitleMessage && lastPayMessage) {
                     break;
                 }
             }
@@ -287,11 +310,127 @@ Page({
             if (lastTitleMessage) {
                 this.setNavigationTitle(lastTitleMessage.title);
             }
+
+            if (lastPayMessage) {
+                this.handleMiniPayRequest(lastPayMessage.payload || {});
+            }
         } catch (error) {
             console.error('❌ 处理消息失败:', error);
         }
     },
 
+    normalizeMiniPayPayload(payload = {}) {
+        const price = Number(payload.price);
+        return {
+            tradeNo: payload.tradeNo || '',
+            price: Number.isFinite(price) ? price : 0,
+            orderId: payload.orderId || '',
+            orderType: payload.orderType || 'shopgoods',
+            showType: payload.showType || 'all',
+            showBonus: !!payload.showBonus,
+            profileId: payload.profileId || '',
+            scene: payload.scene || '',
+            bizId: payload.bizId || '',
+            callbackUrl: payload.callbackUrl || ''
+        };
+    },
+
+    handleMiniPayRequest(payload = {}) {
+        const parsed = this.normalizeMiniPayPayload(payload);
+        if (!parsed.tradeNo) {
+            wx.showToast({
+                title: '支付参数缺少tradeNo',
+                icon: 'none'
+            });
+            return;
+        }
+        if (parsed.price < 0) {
+            wx.showToast({
+                title: '支付金额无效',
+                icon: 'none'
+            });
+            return;
+        }
+        if (this.data.miniPayProcessing) {
+            wx.showToast({
+                title: '支付进行中,请稍后',
+                icon: 'none'
+            });
+            return;
+        }
+
+        this.setData({
+            miniPayVisible: false,
+            miniPayPrice: parsed.price,
+            miniPayTradeNo: parsed.tradeNo,
+            miniPayOrderId: parsed.orderId,
+            miniPayOrderType: parsed.orderType,
+            miniPayShowType: parsed.showType,
+            miniPayShowBonus: parsed.showBonus,
+            miniPayProfileId: parsed.profileId,
+            miniPayProcessing: true,
+            miniPayRequestContext: parsed
+        }, () => {
+            const paymentComp = this.selectComponent('#miniPayComponent');
+            if (!paymentComp || typeof paymentComp.pay !== 'function') {
+                this.setData({ miniPayProcessing: false });
+                wx.showToast({
+                    title: '支付组件未就绪',
+                    icon: 'none'
+                });
+                return;
+            }
+            paymentComp.pay();
+        });
+    },
+
+    onMiniPayResult(e) {
+        const detail = e.detail || {};
+        const payState = detail.params;
+        const result = {
+            type: 'miniPayResult',
+            payload: {
+                success: payState === 'ok',
+                tradeNo: detail.no || this.data.miniPayTradeNo,
+                payType: detail.type || 'wxpay',
+                cancelled: payState === 'Cancel the payment',
+                errMsg: payState && payState !== 'ok' && payState !== 'Cancel the payment'
+                    ? (typeof payState === 'string' ? payState : (payState.errMsg || ''))
+                    : ''
+            }
+        };
+
+        this.setData({
+            miniPayVisible: false,
+            miniPayProcessing: false
+        });
+        this.notifyH5MiniPayResult(result);
+    },
+
+    notifyH5MiniPayResult(result) {
+        const ctx = this.data.miniPayRequestContext || {};
+        console.log('miniPayResult:', result);
+
+        // web-view 不支持直接由小程序向 H5 反向 postMessage,
+        // 若 H5 传入 callbackUrl,则通过刷新 web-view URL 回传支付结果。
+        if (!ctx.callbackUrl) return;
+        try {
+            const separator = ctx.callbackUrl.includes('?') ? '&' : '?';
+            const query = [
+                'miniPayResult=1',
+                `success=${result.payload.success ? 1 : 0}`,
+                `tradeNo=${encodeURIComponent(result.payload.tradeNo || '')}`,
+                `payType=${encodeURIComponent(result.payload.payType || '')}`,
+                `cancelled=${result.payload.cancelled ? 1 : 0}`,
+                `errMsg=${encodeURIComponent(result.payload.errMsg || '')}`
+            ].join('&');
+            const callbackPath = `${ctx.callbackUrl}${separator}${query}`;
+            this.setData({ path: callbackPath });
+        } catch (error) {
+            console.error('❌ 回传支付结果失败:', error);
+        }
+    },
+
     /**
      * 设置导航栏标题(统一方法)
      */

+ 12 - 0
common-page/pages/web-view/index.wxml

@@ -1 +1,13 @@
 <web-view src="{{path}}" bindmessage="handleMessage"></web-view>
+<payment
+  id="miniPayComponent"
+  show="{{miniPayVisible}}"
+  price="{{miniPayPrice}}"
+  tradeNo="{{miniPayTradeNo}}"
+  orderId="{{miniPayOrderId}}"
+  orderType="{{miniPayOrderType}}"
+  showType="{{miniPayShowType}}"
+  showBonus="{{miniPayShowBonus}}"
+  profileId="{{miniPayProfileId}}"
+  bind:payResult="onMiniPayResult"
+/>

+ 3 - 2
exportToPlugin.js

@@ -6,12 +6,13 @@
  * @FilePath: \nova-wapp\exportToPlugin.js
  * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
  */
-const CONFIG = require("./config.js");
+const CONFIG = require("config.js");
+const configData = CONFIG.default || CONFIG;
 let {
   appid,
   company,
   rootPage,
-} = CONFIG.default
+} = configData
 module.exports = {
   appid,
   company,

+ 3 - 1
index.json

@@ -1,3 +1,5 @@
 {
-  "usingComponents": {}
+  "usingComponents": {
+    "van-loading": "@vant/weapp/loading/index"
+  }
 }

+ 15 - 2
project.config.json

@@ -35,7 +35,7 @@
     "minifyWXML": true,
     "showES6CompileOption": false,
     "useCompilerPlugins": false,
-    "ignoreUploadUnusedFiles": true,
+    "ignoreUploadUnusedFiles": false,
     "useApiHostProcess": false,
     "compileWorklet": false,
     "localPlugins": false,
@@ -68,9 +68,22 @@
       {
         "value": "package-lock.json",
         "type": "file"
+      },
+      {
+        "value": "miniprogram_npm/@vant/weapp/country/index.json",
+        "type": "file"
+      },
+      {
+        "value": "miniprogram_npm/@vant/weapp/country",
+        "type": "folder"
       }
     ],
-    "include": []
+    "include": [
+      {
+        "value": "exportToPlugin.js",
+        "type": "file"
+      }
+    ]
   },
   "appid": "wx56914f69b1563869",
   "libVersion": "3.6.6",

+ 32 - 21
yuban/components/home/index.js

@@ -6,11 +6,13 @@ Component({
 
   data: {
     loading: false,
+    webViewPath: '',
   },
 
   lifetimes: {
     attached: function () {
-      console.log('✅ 语伴首页加载完成');
+      this.preloadCommonSubpackage();
+      this.prepareWebViewPath();
     },
   },
 
@@ -24,29 +26,10 @@ Component({
       this.setData({ loading: true });
 
       try {
-        // 尝试从 Parse 获取 token(插件未授权时会是 null,不影响跳转)
-        let token = null;
-        try {
-          const app = getApp();
-          if (app && app.Parse) {
-            const currentUser = app.Parse.User.current();
-            token = currentUser ? currentUser.getSessionToken() : null;
-          }
-        } catch (e) {
-          console.warn('⚠️ Parse 不可用,以游客模式跳转 H5');
-        }
-
-        let h5Url = H5_BASE;
-        if (token) {
-          h5Url += `?token=${token}`;
-        }
-
-        const encodedUrl = encodeURIComponent(h5Url);
-        const webViewPath = `/common-page/pages/web-view/index?path=${encodedUrl}&skipAuth=true`;
+        const webViewPath = this.data.webViewPath || this.buildWebViewPath();
 
         wx.navigateTo({
           url: webViewPath,
-          success: () => console.log('✅ 跳转 H5 成功:', h5Url),
           fail: (err) => {
             console.error('❌ 跳转失败:', err);
             wx.showToast({ title: '跳转失败,请重试', icon: 'none' });
@@ -59,5 +42,33 @@ Component({
         wx.showToast({ title: '加载失败,请重试', icon: 'none' });
       }
     },
+
+    preloadCommonSubpackage() {
+      if (typeof wx.loadSubpackage !== 'function') return;
+      wx.loadSubpackage({
+        name: 'common'
+      });
+    },
+
+    prepareWebViewPath() {
+      const webViewPath = this.buildWebViewPath();
+      this.setData({ webViewPath });
+    },
+
+    buildWebViewPath() {
+      let token = null;
+      try {
+        const app = getApp();
+        if (app && app.Parse) {
+          const currentUser = app.Parse.User.current();
+          token = currentUser ? currentUser.getSessionToken() : null;
+        }
+      } catch (e) {
+        // Parse 不可用时直接游客模式进入 H5
+      }
+      const h5Url = token ? `${H5_BASE}?token=${token}` : H5_BASE;
+      const encodedUrl = encodeURIComponent(h5Url);
+      return `/common-page/pages/web-view/index?path=${encodedUrl}&skipAuth=true`;
+    }
   }
 })