From 21ba9b7c429375ddd5b2de5e3d7e555b86b4bf02 Mon Sep 17 00:00:00 2001 From: Council Date: Tue, 30 Jun 2026 18:37:10 +0800 Subject: [PATCH] =?UTF-8?q?fix(vr-ticket-wallet):=20=E4=BF=AE=E5=A4=8D=20i?= =?UTF-8?q?OS=20=E7=9C=9F=E6=9C=BA=20evaluate=20=E7=99=BD=E5=B1=8F=20+=20S?= =?UTF-8?q?wiper=20=E5=9B=9E=E5=BC=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. ticket-wallet.vue:移除顶层 getApp() 调用,修复 iOS 独立分包 evaluate 早于 App.vue#onLaunch 导致的 'o.functions' 错误 (与 verify/index.vue、records/index.vue 同病同治) 2. ticket-qr-popup:swiper 改用内部 internalIndex 状态, 避免父组件倒计时 re-render 时 prop 重设导致 iOS 回弹 (订单详情页弹出有此问题,票夹页面无此问题) --- ...-weixin-real-device-white-screen-report.md | 106 +++++++++++++++++- .../components/ticket-qr-popup/index.vue | 20 +++- .../vr-ticket-wallet/ticket-wallet.vue | 35 +++++- 3 files changed, 154 insertions(+), 7 deletions(-) diff --git a/docs/ios-weixin-real-device-white-screen-report.md b/docs/ios-weixin-real-device-white-screen-report.md index 53d004aa..9b538e68 100644 --- a/docs/ios-weixin-real-device-white-screen-report.md +++ b/docs/ios-weixin-real-device-white-screen-report.md @@ -2,7 +2,7 @@ > **日期**:2026-06-30 > **作者**:Antigravity (AI) + 用户 (bigemon) -> **状态**:✅ **首页白屏已解决**、✅ **扫码核销页空白页已解决**、✅ **核销记录页塌陷已解决**、✅ **iconfont 渲染异常已解决** +> **状态**:✅ **首页白屏已解决**、✅ **扫码核销页空白页已解决**、✅ **核销记录页塌陷已解决**、✅ **iconfont 渲染异常已解决**、✅ **核销码弹窗 Swiper 回弹已解决** > **相关文件**:`pages.json`、`pages/diy/components/diy/activity.vue`、`pages/plugins/vr-ticket-wallet/verify/index.vue`、`pages/plugins/vr-ticket-wallet/verify/index.css`、`pages/plugins/vr-ticket-wallet/records/index.vue`、`pages/plugins/vr-ticket-wallet/records/records.css`、`components/iconfont/iconfont.vue`、`docs/err.log` > **配套文档**:[`docs/ios-weixin-real-device-uniapp-lessons.md`](file:///Users/bigemon/WorkSpace/vr-shopxo-uniapp/docs/ios-weixin-real-device-uniapp-lessons.md) — 跨问题的方法论、调试手册、避坑指南 @@ -693,3 +693,107 @@ grep -n "const app = getApp()" pages/diy/components/diy/*.vue | `` 元素 | uni-app 跨平台组件,编译为小程序的 ``,**原生不支持 flex** | | `@font-face` 渲染 | iOS WKWebView 对 `rpx` 在 `@font-face` 内的换算有 bug | | `1em` 单位 | 相对当前 `font-size` 的长度,**不是父容器宽度** | + +--- + +## 十二、Bug #4:订单详情核销码弹窗 Swiper 回弹 + +> **报告日期**:2026-06-30 +> **报告人**:Antigravity (AI) + 用户 (bigemon) +> **状态**:✅ **已解决** + +### 12.1 问题描述 + +**环境**:iOS 微信真机(iPhone) +**触发路径**:用户中心 → 订单列表 → 订单详情 → 点击"查看核销码" → 弹出 `ticket-qr-popup` 弹窗 + +**现象**: + +- 弹窗正常打开,第一张二维码正常显示 +- 尝试向右滑动到第二张(或第三张)时,**手指松开后立即回弹到第一张** +- **票夹页面**(`ticket-wallet.vue`)打开的弹窗**无此问题** + +### 12.2 根因分析 + +`ticket-qr-popup` 组件的 swiper **直接绑定到 prop `currentIndex`**: + +```html + +``` + +订单详情页有 `startQrCountdown()` 定时器,**每秒执行一次**更新 `currentExpiresIn`: + +```js +this.qrRefreshTimer = setInterval(function() { + self.currentExpiresIn--; // ← 触发父组件 re-render +}, 1000); +``` + +每次 `currentExpiresIn` 更新都触发 `setData`,导致整个页面 re-render。uni-app 会将 prop `currentIndex` 的当前值重新"设回" swiper。在 iOS 小程序环境中,这种 re-render 触发的 `:current` 重新赋值会**打断 swiper 动画状态**,导致滑动结束后回弹到上一次 prop 值(0)。 + +**为什么票夹页面没问题?** 票夹页面组件树更小,re-render 对 swiper 的干扰更小。 + +### 12.3 修复方案 + +**修改文件**:`pages/plugins/vr-ticket-wallet/components/ticket-qr-popup/index.vue` + +核心思路:让 swiper 使用**内部 data** `internalIndex` 管理索引,`watch` 监听 prop 变化,`onSwiperChange` 立即更新内部状态,避免父组件 re-render 干扰。 + +**改动 1**:新增 `data()` 和 `watch` + +```js +data() { + return { + // 内部 swiper 索引:避免父组件 re-render 时 prop 重新设值导致回弹 + internalIndex: this.currentIndex, + }; +}, +watch: { + currentIndex(newVal) { + this.internalIndex = newVal; + }, +}, +``` + +**改动 2**:swiper `:current` 改用 `internalIndex` + +```html + +``` + +**改动 3**:`onSwiperChange` 立即更新内部索引 + +```js +onSwiperChange: function(e) { + // 立即更新内部索引,避免等待父组件 round-trip 期间 swiper 被重置 + this.internalIndex = e.detail.current; + this.$emit('change', e.detail.current); +}, +``` + +**改动 4**:indicator-dots 和 `currentTicket` 改用 `internalIndex` + +```html +:class="{ 'dot-active': tIdx === internalIndex }" +``` + +```js +currentTicket: function() { + return this.tickets[this.internalIndex] || null; +}, +``` + +### 12.4 修复效果 + +| 场景 | 修复前 | 修复后 | +|------|--------|--------| +| 订单详情 → 查看核销码 → 滑动 | ❌ 回弹到第一张 | ✅ 正常滑动 | +| 票夹页面 → 查看核销码 → 滑动 | ✅ 正常 | ✅ 正常 | + +### 12.5 经验总结 + +**iOS swiper + 父组件高频 re-render 的组合问题**: + +1. **不要在父组件中对带绑定 `:current` 的 prop 进行高频更新**(如定时器、动画帧) +2. **最佳实践**:子组件负责管理自身的 swiper 状态,用 `watch` 监听 prop 变化,滑动事件自行处理 +3. **类似场景**:倒计时组件、实时数据刷新组件内嵌 swiper/tabs 时,均应采用组件内部状态隔离模式 diff --git a/pages/plugins/vr-ticket-wallet/components/ticket-qr-popup/index.vue b/pages/plugins/vr-ticket-wallet/components/ticket-qr-popup/index.vue index 645c4a55..5bd5496e 100644 --- a/pages/plugins/vr-ticket-wallet/components/ticket-qr-popup/index.vue +++ b/pages/plugins/vr-ticket-wallet/components/ticket-qr-popup/index.vue @@ -16,7 +16,7 @@ class="qr-swiper" :indicator-dots="false" :autoplay="false" - :current="currentIndex" + :current="internalIndex" @change="onSwiperChange" > @@ -126,14 +126,28 @@ export default { }, }, }, + data() { + return { + // 内部 swiper 索引:避免父组件 re-render(如倒计时)时 prop 重新设值 + // 导致 iOS swiper 回弹到第一张 + internalIndex: this.currentIndex, + }; + }, + watch: { + currentIndex(newVal) { + this.internalIndex = newVal; + }, + }, computed: { currentTicket: function() { - return this.tickets[this.currentIndex] || null; + return this.tickets[this.internalIndex] || null; }, }, methods: { // Swiper 滑动回调 onSwiperChange: function(e) { + // 立即更新内部索引,避免等待父组件 round-trip 期间 swiper 被重置 + this.internalIndex = e.detail.current; this.$emit('change', e.detail.current); }, diff --git a/pages/plugins/vr-ticket-wallet/ticket-wallet.vue b/pages/plugins/vr-ticket-wallet/ticket-wallet.vue index e1157611..fb307106 100644 --- a/pages/plugins/vr-ticket-wallet/ticket-wallet.vue +++ b/pages/plugins/vr-ticket-wallet/ticket-wallet.vue @@ -272,7 +272,15 @@ import TicketQrPopup from './components/ticket-qr-popup/index.vue'; import componentCommon from '@/components/common/common'; import componentQuickNav from '@/components/quick-nav/quick-nav'; -const app = getApp(); +// ────────────────────────────────────────────────────────────────────────── +// [iOS 真机白屏修复 · 方案 A] 2026-06-30 +// 根因:vr-ticket-wallet 是独立 subpackage。iOS 微信真机的 subpackage +// evaluate 阶段可能早于 App.vue#onLaunch,导致顶层 getApp() 拿到的 +// globalData 未填充,从而