// 课程中心:全屏 web-view 加载线上课程列表 // // 注意:课程页部署在 www.muguaclub.cn,与首页 H5 的 m.muguaclub.cn 是不同主机名, // www 必须单独加入微信公众平台「开发设置 → 业务域名」并在该站点根目录放校验文件, // 否则真机打开会白屏。 // 登录态:课程页本体是公开的,但产品要求「未登录不进课程页」——未登录时切到「我的」引导登录, // 因此也不需要走 webview/redirect 换 cookie。 const auth = require('../../utils/auth'); const COURSE_URL = 'https://www.muguaclub.cn/courses/index.html'; // 「去上课」浮窗的目标小程序(第三方,appId 由业务方提供)。 // 只用 navigateToMiniProgram,不传 path → 落对方首页;若将来要直达某个页面, // 在这里补 path: '/pages/xxx/xxx'(以 / 开头)。 // 白名单:app.json 的 navigateToMiniProgramAppIdList 已于 2020-04-24 取消(配置里若还留着反而报 appJSON 无效), // 所以不需要任何平台/工程配置。但下面两条绕不过去: // ① 必须由用户点击触发(基础库 2.3.0 起),所以在 onShow 里自动调会报 // `navigateToMiniProgram:fail can only be invoked by user TAP gesture`; // ② 跳转前微信一定弹系统确认框,用户取消时 fail.errMsg 含 'cancel'。 const CLASS_MINI_APP_ID = 'wxa3589f1193170aa3'; Page({ data: { webviewUrl: '', webviewError: false, errorMsg: '', webviewKey: 0, // 「去上课」浮窗是否已挂载。**故意不跟 web-view 同帧渲染**,见 _scheduleFloat showFloat: false }, _webviewKey: 0, _redirecting: false, _jumping: false, _floatTimer: null, onShow() { this.ensureAccess(); }, // 未登录不进课程页,直接切到「我的」引导登录 ensureAccess() { if (auth.getToken()) { this.loadWebview(); return; } // token 可能还在 App.onLaunch 发起的登录流程里,复用同一个登录单例再判一次,避免冷启动误跳 return auth.ensureLogin() .then((token) => { if (token) { this.loadWebview(); return; } this.denyAccess(); }) .catch(() => { this.denyAccess(); }); }, // 清掉可能残留的 web-view(如退出登录后切回本 tab),再切到「我的」 denyAccess() { clearTimeout(this._floatTimer); if (this.data.webviewUrl || this.data.showFloat) { this.setData({ webviewUrl: '', webviewError: false, errorMsg: '', showFloat: false }); } if (this._redirecting) { return; } this._redirecting = true; wx.switchTab({ url: '/pages/profile/profile', complete: () => { this._redirecting = false; } }); }, loadWebview() { // key 自增 + 时间戳:强制 web-view 销毁重建,避免切 tab 回来白屏或读到上一次的缓存 this._webviewKey += 1; this.setData({ webviewUrl: COURSE_URL + '?_t=' + Date.now(), webviewError: false, errorMsg: '', webviewKey: this._webviewKey, // 与上面的字段合并成同一次 setData,别拆开发(同同步树连发会撞 recursive update) showFloat: false }); this._scheduleFloat(); }, // 延迟一拍再把浮窗插进去。 // // 为什么不能跟 web-view 同帧渲染:原生组件按「后插入的盖住先插入的」决定层级, // 而 web-view 每次 onShow 都因 key 自增被销毁重建 —— 同帧渲染时重建后的 web-view // 反而压在浮窗上,**真机上是「浮窗不见了」**(安卓默认如此,iOS 多数机型不出现; // 开发者工具里则是完全看不到覆盖效果,无论怎么改都只能真机验证)。 // 等 web-view 挂载完(250ms)再 setData,浮窗就成了后插入的那一个。 _scheduleFloat() { clearTimeout(this._floatTimer); this._floatTimer = setTimeout(() => { // 定时器可能落在 webviewError / 已切走的场景里,必须再判一次 if (this.data.webviewUrl && !this.data.webviewError) { this.setData({ showFloat: true }); } }, 250); }, onWebviewError(e) { // 加载失败时把浮窗收掉(错误页上不叠浮窗),同时取消待触发的显示定时器 clearTimeout(this._floatTimer); this.setData({ webviewError: true, errorMsg: (e && e.detail && e.detail.errMsg) || '加载失败,请检查网络后重试', showFloat: false }); }, onRetry() { clearTimeout(this._floatTimer); this.setData({ webviewUrl: '', webviewError: false, errorMsg: '', showFloat: false }); setTimeout(() => { this.loadWebview(); }, 60); }, // ---- 去上课:跳转到第三方授课小程序 ---- onGoClass() { // 防重入:连点会连续弹系统确认框。complete 里复位(跳转成功时本页被挂起, // 用户从对方小程序返回会走 onShow → 重新加载课程页,这是本页既有行为,不在这里动) if (this._jumping) { return; } this._jumping = true; wx.navigateToMiniProgram({ appId: CLASS_MINI_APP_ID, fail: (err) => { const msg = (err && err.errMsg) || ''; // 用户点了取消,不是错误 if (msg.indexOf('cancel') >= 0) { return; } wx.showToast({ title: '打开失败,请稍后再试', icon: 'none' }); }, complete: () => { this._jumping = false; } }); }, // ---- 分享 ---- onShareAppMessage() { return { title: '明缘云道场 · 全部课程', path: '/pages/course/course' }; }, onShareTimeline() { return { title: '明缘云道场 · 全部课程', query: 'from=timeline' }; } });