| .. | .. |
|---|
| 1 | +# 活动专区页面 `pages/zone/index` 逻辑说明 |
|---|
| 2 | + |
|---|
| 3 | +> 代码位置:`pages/zone/index.vue`(约 1670 行) |
|---|
| 4 | +> 主要子组件:`components/yueyangZone/index.vue`、`pages/goods_block/index.vue`、`components/home`、`components/popCoupon`、`components/popVideo`、`components/personalAndShareCoupon`、`components/task`、`components/popNewuser` |
|---|
| 5 | +> 所有接口请求前缀:`{HTTP_REQUEST_URL}/api/front/`(`utils/request.js`,`HTTP_REQUEST_URL` 来自 `config/app`) |
|---|
| 6 | + |
|---|
| 7 | +--- |
|---|
| 8 | + |
|---|
| 9 | +## 1. 页面定位 |
|---|
| 10 | + |
|---|
| 11 | +活动专区聚合页,根据后端返回的 `zoneType` 渲染三种完全不同的形态: |
|---|
| 12 | + |
|---|
| 13 | +| zoneType | 形态 | 关键特征 | |
|---|
| 14 | +|---|---|---| |
|---|
| 15 | +| `6` | 悦养豆兑换专区 | 交给 `<yueyangZone>` 组件渲染:悦养豆余额、券兑换卡片、兑换区选项卡 + 商品列表 | |
|---|
| 16 | +| `7` | 分区标签型专区 | 顶部横向标签吸顶 + 分区图/分区商品,标签与页面滚动双向联动 | |
|---|
| 17 | +| 其他(`3`、`8` 等) | 通用活动专区 | 头图 + 富文本/视频(zoneType 8)+ 工具栏(综合/价格/筛选)+ 瀑布流商品列表 + 上拉加载 | |
|---|
| 18 | + |
|---|
| 19 | +模板分支: |
|---|
| 20 | + |
|---|
| 21 | +```15:20:pages/zone/index.vue |
|---|
| 22 | + <yueyangZone ref=yueyangZoneRef v-if="isYueyangZone" :zoneInfo="info" :careBeanRate="careBeanRate" |
|---|
| 23 | + style="position: relative;z-index: 2;" @goLottery="goLottery" :relationKey="relationKey"></yueyangZone> |
|---|
| 24 | + <view v-if="isYueyangZone" class="detail-btn" @click="goDetail"> |
|---|
| 25 | + <text>消耗明细</text> |
|---|
| 26 | + </view> |
|---|
| 27 | + <view v-else> |
|---|
| 28 | +``` |
|---|
| 29 | + |
|---|
| 30 | +```1492:1498:pages/zone/index.vue |
|---|
| 31 | + if (res.data.zoneType == 6) { |
|---|
| 32 | + that.setYueyangZone() |
|---|
| 33 | + } else { |
|---|
| 34 | + if (that.info.zoneType == 7) { |
|---|
| 35 | + that.resetPartitionTabs(that.info.partitionTabList) |
|---|
| 36 | + that.homeTop = that.getHomeTop(0.4) |
|---|
| 37 | + } |
|---|
| 38 | +``` |
|---|
| 39 | + |
|---|
| 40 | +--- |
|---|
| 41 | + |
|---|
| 42 | +## 2. 入口参数 |
|---|
| 43 | + |
|---|
| 44 | +`/pages/zone/index?id=专区ID` |
|---|
| 45 | + |
|---|
| 46 | +| 参数 | 取值来源 | 作用 | |
|---|
| 47 | +|---|---|---| |
|---|
| 48 | +| `id` | URL / scene 解析结果 | 专区 ID,`getZoneDetail` 的主参数 | |
|---|
| 49 | +| `cateId` | URL / scene | 仅存入 `this.cateId`,当前未参与请求 | |
|---|
| 50 | +| `relationKey` | URL / scene / 好友券链接 | **仅悦养专区生效**,指定默认选中的兑换区标签 | |
|---|
| 51 | +| `shareToken` | URL / scene | 好友券分享 token,`friendCouponShareToken`,参与券包详情入参 | |
|---|
| 52 | +| `spread` | URL | 分销人 ID | |
|---|
| 53 | +| `scene` | 小程序码 | 走 scene 解析分支(`finishSceneParam` 事件 / `zone_scene` 缓存) | |
|---|
| 54 | +| `couponZone` | URL | 值为 `'false'` 时券包相关入参直接返回 `{}` | |
|---|
| 55 | + |
|---|
| 56 | +参数读取: |
|---|
| 57 | + |
|---|
| 58 | +```714:719:pages/zone/index.vue |
|---|
| 59 | + let options = currPage.options; |
|---|
| 60 | + this.relationKey = options.relationKey; |
|---|
| 61 | + this.id = options.id; |
|---|
| 62 | + this.cateId = options.cateId; |
|---|
| 63 | + this.friendCouponShareToken = options.shareToken |
|---|
| 64 | + this.urlOption = options |
|---|
| 65 | +``` |
|---|
| 66 | + |
|---|
| 67 | +scene 分支(`options.scene`): |
|---|
| 68 | +- 命中缓存 `zone_scene` + `zone_scene_param` → 直接 `JSON.parse` 取 `id / shareToken / cateId / relationKey`; |
|---|
| 69 | +- 未命中 → `uni.$once('finishSceneParam')` 等解析结果,并写缓存。 |
|---|
| 70 | + |
|---|
| 71 | +```728:772:pages/zone/index.vue |
|---|
| 72 | + if (options.scene) { |
|---|
| 73 | + if (this.$Cache.get('zone_scene') && this.$Cache.get('zone_scene') == options.scene && this.$Cache.get( |
|---|
| 74 | + 'zone_scene_param')) { |
|---|
| 75 | +``` |
|---|
| 76 | + |
|---|
| 77 | +--- |
|---|
| 78 | + |
|---|
| 79 | +## 3. 启动时序 |
|---|
| 80 | + |
|---|
| 81 | +``` |
|---|
| 82 | +onLoad |
|---|
| 83 | + ├─ uni.removeStorageSync('zoneTab') 清空悦养标签缓存 |
|---|
| 84 | + ├─ uni.getWindowInfo / getMenuButtonBoundingClientRect → 计算 marTop(header 高度) |
|---|
| 85 | + └─ 监听 careBeanRate、BELONG_READY 等 |
|---|
| 86 | +onShow |
|---|
| 87 | + ├─ 读取页面参数(id / relationKey / shareToken / cateId / scene) |
|---|
| 88 | + ├─ checkUnionId() → 等 unionId(UPDATE_LOGINSTATUS) |
|---|
| 89 | + │ └─ onGetUnion() → 已登录 onLoadFn();未登录 toLogin() + loginFlSuccess 回调 |
|---|
| 90 | + └─ 等待 careBeanRate(updateCareBeanRate)、belong(BELONG_READY) |
|---|
| 91 | + └─ belongInfoReadyFn → checkLoginFn(埋点 yy_pageView + hideLoading) |
|---|
| 92 | + |
|---|
| 93 | +onLoadFn |
|---|
| 94 | + ├─ handsCoupon() 检查可手动领取券(GET hands/coupon/check) |
|---|
| 95 | + └─ getZoneDetail() 专区详情(核心) |
|---|
| 96 | + ├─ updateZoneShareMenu() 分享菜单显隐 |
|---|
| 97 | + ├─ zoneType==6 → setYueyangZone() |
|---|
| 98 | + ├─ zoneType==7 → resetPartitionTabs(partitionTabList) |
|---|
| 99 | + ├─ 其他 → getGoodList() 分页商品 + validAlert() 倒计时 |
|---|
| 100 | + └─ $nextTick → hasSign() + couponPackVisit() |
|---|
| 101 | +``` |
|---|
| 102 | + |
|---|
| 103 | +```1114:1133:pages/zone/index.vue |
|---|
| 104 | + checkUnionId() { |
|---|
| 105 | + let that = this; |
|---|
| 106 | + if (that.$store.getters.unionId) { //初始化数据接口login请求完毕第一次login返回了unionId |
|---|
| 107 | + that.onGetUnion(); |
|---|
| 108 | + } else { |
|---|
| 109 | + uni.$once('UPDATE_LOGINSTATUS', that.onGetUnion); |
|---|
| 110 | + } |
|---|
| 111 | + }, |
|---|
| 112 | + onGetUnion() { |
|---|
| 113 | + let that = this; |
|---|
| 114 | + if (that.$store.getters.isLogin) { |
|---|
| 115 | + that.onLoadFn(); |
|---|
| 116 | + } else { |
|---|
| 117 | + toLogin(that); |
|---|
| 118 | + that.loginBackFn = function () { |
|---|
| 119 | + that.onLoadFn(); |
|---|
| 120 | + } |
|---|
| 121 | + uni.$once('loginFlSuccess', that.registerFn); |
|---|
| 122 | + } |
|---|
| 123 | + }, |
|---|
| 124 | +``` |
|---|
| 125 | + |
|---|
| 126 | +`onLoad` 会清掉悦养标签缓存: |
|---|
| 127 | + |
|---|
| 128 | +```658:659:pages/zone/index.vue |
|---|
| 129 | + onLoad(options) { |
|---|
| 130 | + uni.removeStorageSync('zoneTab') |
|---|
| 131 | +``` |
|---|
| 132 | + |
|---|
| 133 | +--- |
|---|
| 134 | + |
|---|
| 135 | +## 4. 三种形态的详细逻辑 |
|---|
| 136 | + |
|---|
| 137 | +### 4.1 悦养豆兑换专区(zoneType = 6) |
|---|
| 138 | + |
|---|
| 139 | +页面只做「取数 + 转发」,UI 与交互全在 `components/yueyangZone/index.vue`。 |
|---|
| 140 | + |
|---|
| 141 | +**页面侧:** |
|---|
| 142 | + |
|---|
| 143 | +```1207:1219:pages/zone/index.vue |
|---|
| 144 | + setYueyangZone() { |
|---|
| 145 | + // 悦养专区处理 |
|---|
| 146 | + let that = this |
|---|
| 147 | + this.isYueyangZone = true |
|---|
| 148 | + this.$nextTick(() => { |
|---|
| 149 | + that.getUserBean() |
|---|
| 150 | + // 获取专区优惠券 |
|---|
| 151 | + getZoneCoupon(that.info.id).then(res => { |
|---|
| 152 | + const couponData = res.data |
|---|
| 153 | + that.getCouponCount(couponData, 0) |
|---|
| 154 | + }) |
|---|
| 155 | + }) |
|---|
| 156 | + }, |
|---|
| 157 | +``` |
|---|
| 158 | + |
|---|
| 159 | +`getCouponCount` 递归逐张券查可用数量,遍历完后调用子组件 `resetCoupons`: |
|---|
| 160 | + |
|---|
| 161 | +```1190:1206:pages/zone/index.vue |
|---|
| 162 | + getCouponCount(couponData, index) { |
|---|
| 163 | + if (!couponData[index]) { |
|---|
| 164 | + this.$refs.yueyangZoneRef.resetCoupons(couponData) |
|---|
| 165 | + return |
|---|
| 166 | + } |
|---|
| 167 | + let that = this |
|---|
| 168 | + getZoneCouponCount({ |
|---|
| 169 | + couponId: couponData[index].id |
|---|
| 170 | + }).then(res => { |
|---|
| 171 | + let coupons = couponData |
|---|
| 172 | + coupons.splice(index, 1, { |
|---|
| 173 | + ...couponData[index], |
|---|
| 174 | + ...res.data |
|---|
| 175 | + }) |
|---|
| 176 | + that.getCouponCount(coupons, index + 1) |
|---|
| 177 | + }) |
|---|
| 178 | + }, |
|---|
| 179 | +``` |
|---|
| 180 | + |
|---|
| 181 | +**子组件侧:** |
|---|
| 182 | + |
|---|
| 183 | +- 悦养豆余额:`resetCareBean(res2.data.totalCareBeans)`,来自 `GET user/careBean`; |
|---|
| 184 | +- 券卡片:`couponList`(接口 `zone/coupon/list/{id}`),点击「立即兑换」→ `exchangeCoupon` → `POST zone/coupon/exchange`; |
|---|
| 185 | +- 兑换区选项卡:`couponProductTabs = JSON.parse(couponList[0].couponProductTabs)`,**标签数据来自第一张券的字段**; |
|---|
| 186 | +- 商品:`switchTab` 时按 `tab.productIds` 调 `GET products`(`ids` + `page=1&limit=100&zoneId`)。 |
|---|
| 187 | + |
|---|
| 188 | +**标签默认选中优先级:** |
|---|
| 189 | + |
|---|
| 190 | +```145:161:components/yueyangZone/index.vue |
|---|
| 191 | + const zoneTab = uni.getStorageSync('zoneTab') |
|---|
| 192 | + let index = 0 |
|---|
| 193 | + if (that.relationKey) { |
|---|
| 194 | + index = couponProductTabs.findIndex(item => item.relationKey == that.relationKey) |
|---|
| 195 | + } else if (zoneTab) { |
|---|
| 196 | + index = couponProductTabs.findIndex(item => item.relationKey == zoneTab) |
|---|
| 197 | + if (index == -1) { |
|---|
| 198 | + index = zoneTab |
|---|
| 199 | + } |
|---|
| 200 | + } else if(that.userInfo.level<3 && couponProductTabs[1]){ |
|---|
| 201 | + // 非臻享会员 |
|---|
| 202 | + index=1 |
|---|
| 203 | + } |
|---|
| 204 | + if (index == -1) { |
|---|
| 205 | + index = 0 |
|---|
| 206 | + } |
|---|
| 207 | + that.switchTab(couponProductTabs[index], index) |
|---|
| 208 | +``` |
|---|
| 209 | + |
|---|
| 210 | +1. URL `relationKey` 命中 → 该标签; |
|---|
| 211 | +2. 本地缓存 `zoneTab`(`uni.setStorageSync('zoneTab', tab.relationKey || index)`)→ 先按 `relationKey` 匹配,匹配不到把缓存值当索引; |
|---|
| 212 | +3. 非臻享会员(`userInfo.level < 3`)且存在第 2 个标签 → 索引 1; |
|---|
| 213 | +4. 兜底 → 索引 0。 |
|---|
| 214 | + |
|---|
| 215 | +> 注意:`onLoad` 会 `removeStorageSync('zoneTab')`,所以缓存只在「页面未销毁再次 onShow」时有效。 |
|---|
| 216 | + |
|---|
| 217 | +**可通过地址参数控制选中标签**:`?id=xx&relationKey=yyy`,值必须是 `couponProductTabs[].relationKey`(接口返回,代码无硬编码),匹配失败静默回退到第 0 个标签。 |
|---|
| 218 | + |
|---|
| 219 | +「消耗明细」按钮 → `/pages/consumption/index?zoneId=xx`(该页用 `GET care/bean/consume/record/list`)。 |
|---|
| 220 | + |
|---|
| 221 | +### 4.2 分区标签型专区(zoneType = 7) |
|---|
| 222 | + |
|---|
| 223 | +- 标签数据:`info.partitionTabList`,由 `resetPartitionTabs` 分成前 4 个(`tagList`)和剩余(`restList`,「更多」下拉展开): |
|---|
| 224 | + |
|---|
| 225 | +```1392:1398:pages/zone/index.vue |
|---|
| 226 | + resetPartitionTabs(partitionTabs) { |
|---|
| 227 | + let that = this |
|---|
| 228 | + that.partitionList = partitionTabs |
|---|
| 229 | + that.tagList = partitionTabs.slice(0, 4) |
|---|
| 230 | + that.restList = partitionTabs.slice(4) |
|---|
| 231 | + that.scheduleSetAllScrollTop() |
|---|
| 232 | + }, |
|---|
| 233 | +``` |
|---|
| 234 | + |
|---|
| 235 | +- 商品一次性拉取:把所有分区 `productIds` 合并后请求 `GET products`(`limit=100`),结果按 `id` 存入 `partitionProject` 映射,模板中 `v-if="partitionProject[gitem.id]"` 才渲染; |
|---|
| 236 | +- 标签 ↔ 滚动双向联动: |
|---|
| 237 | + - `setAllScrollTop()` 用 `createSelectorQuery` 量出每个 `#partition-item_i` 的绝对偏移存入 `allTop`; |
|---|
| 238 | + - `changeTag(idx)` → `scrollToTarget()` 滚到锚点,期间 `isScrollingByTag = true` 抑制反向联动; |
|---|
| 239 | + - `onPageScroll` → `checkScrollIndex()`(50ms 节流)→ `updateCurrentIndexByScroll()` 反算 `currentIndex`; |
|---|
| 240 | + - 分区图 `@load="onPartitionImageLoad"` 触发重新量高(图片撑高会改变锚点)。 |
|---|
| 241 | +- `currentIndex === -1` 表示「全部」,滚动到顶部。 |
|---|
| 242 | + |
|---|
| 243 | +### 4.3 通用活动专区(其他 zoneType) |
|---|
| 244 | + |
|---|
| 245 | +- 工具栏:`toolbarArr = [综合(salesOrder), 价格(priceOrder, asc/desc 可切换), 筛选(filter)]`,`changeToolbar` 重置分页后重新请求; |
|---|
| 246 | +- 筛选弹窗:点「筛选」→ `getAllCategory()` → `categoryList` → 多选 `selectMenu` / 清空 / 确定 `menuSubmit`(`where.cateIds = where.cid = 选中 id 串`); |
|---|
| 247 | +- 商品分页:`where = {page, limit: 20, cateIds, cid}`,`getGoodList()` 追加,`onReachBottom` 触底加载,`loadend` 终止; |
|---|
| 248 | +- 排序入参:`where[value] = sort`(如 `priceOrder: 'desc'`)。 |
|---|
| 249 | + |
|---|
| 250 | +--- |
|---|
| 251 | + |
|---|
| 252 | +## 5. 接口清单 |
|---|
| 253 | + |
|---|
| 254 | +请求封装:`api/store.js`、`api/user.js`,统一 `request.get/post`,大部分带 `noAuth: true`(游客可访问)。 |
|---|
| 255 | + |
|---|
| 256 | +| 调用方法 | HTTP + 路径(相对 `/api/front/`) | 定义位置 | 触发时机 | 关键用途 | |
|---|
| 257 | +|---|---|---|---|---| |
|---|
| 258 | +| `getZoneDetail(id, params)` | `GET store/zone/detail/{id}` | `api/store.js:213` | `onLoadFn`(核心入口) | 返回 `info`:专区名称/头图/`zoneType`/`shareSwitch`/`shareTitle`/`shareImage`/`expireTime`/`startTime`/`partitionTabList`/`couponPackStatus`/`mdayId`/`ruleTitleText`… | |
|---|
| 259 | +| `getProductslist(where)` | `GET products` | `api/store.js:94` | 通用专区商品分页;zoneType 7 按 ids 批量;悦养专区切标签 | 商品列表(`list` / `page` / `totalPage`) | |
|---|
| 260 | +| `getCategoryList()` | `GET category` | `api/store.js:84` | `onShow`、点「筛选」 | 筛选弹窗分类树 `categoryList` | |
|---|
| 261 | +| `checkSign({zoneId})` | `GET store/zone/check/sign` | `api/store.js:239` | `getZoneDetail` 后 | zoneType 3 判断是否已签到,未签到弹窗拦截 | |
|---|
| 262 | +| `getZoneCoupon(zoneId)` | `GET zone/coupon/list/{id}` | `api/store.js:278` | `setYueyangZone` | 悦养专区可兑换券列表(含 `couponProductTabs`) | |
|---|
| 263 | +| `getZoneCouponCount({couponId})` | `GET zone/coupon/user/count` | `api/store.js:284` | 券列表返回后逐张递归 | 每张券的 `usableCount` | |
|---|
| 264 | +| `couponExchange({couponId, zoneId})` | `POST zone/coupon/exchange` | `api/store.js:296` | 子组件点「立即兑换」→「确定」 | 悦养豆兑换券 | |
|---|
| 265 | +| `couponPackSharelink({activityId})` | `POST coupon-pack/share-link?activityId={id}` | `api/store.js:312` | 好友券分享(`onShareAppMessage` 分支) | 返回 `shareToken` / `path` / `shareCoverImage` | |
|---|
| 266 | +| `getUserBean()` | `GET user/careBean` | `api/user.js:10` | 悦养专区 `setYueyangZone` | `totalCareBeans` 悦养豆余额 | |
|---|
| 267 | +| `handsCoupon()` | `GET hands/coupon/check` | `api/user.js:494` | `onLoadFn` | 检查并自动领取手动导入券 | |
|---|
| 268 | + |
|---|
| 269 | +### 已导入但当前未使用的接口 |
|---|
| 270 | + |
|---|
| 271 | +| 方法 | 路径 | 说明 | |
|---|
| 272 | +|---|---|---| |
|---|
| 273 | +| `getZoneGoodList` | `GET store/zone/product/list/{id}` | 页面 import 了但从未调用,商品统一走 `getProductslist` | |
|---|
| 274 | +| `couponPackVisit` | `POST coupon-pack/visit` | 被页面同名方法 `couponPackVisit()`(1404 行)遮蔽,实际未发请求;券包状态完全取自信 `getZoneDetail` 的 `info.couponPackStatus` | |
|---|
| 275 | + |
|---|
| 276 | +--- |
|---|
| 277 | + |
|---|
| 278 | +## 6. 分享逻辑 |
|---|
| 279 | + |
|---|
| 280 | +- 判定:`isZoneShareEnabled()`,券包模式下优先用 `couponPackStatus.zoneShareSwitch`,否则用 `info.shareSwitch`,严格 `=== 1`: |
|---|
| 281 | + |
|---|
| 282 | +```875:893:pages/zone/index.vue |
|---|
| 283 | + isZoneShareEnabled() { |
|---|
| 284 | + if (this.info.couponPackStatus && this.info.couponPackStatus.issued) { |
|---|
| 285 | + return Number(this.info.couponPackStatus.zoneShareSwitch) === 1 |
|---|
| 286 | + } |
|---|
| 287 | + return Number(this.info.shareSwitch) === 1; |
|---|
| 288 | + }, |
|---|
| 289 | + updateZoneShareMenu() { |
|---|
| 290 | + // #ifdef MP |
|---|
| 291 | + if (this.id && !this.isZoneShareEnabled()) { |
|---|
| 292 | + wx.hideShareMenu({ |
|---|
| 293 | + menus: ['shareAppMessage'] |
|---|
| 294 | + }); |
|---|
| 295 | +``` |
|---|
| 296 | + |
|---|
| 297 | +- `updateZoneShareMenu` 调用点:`getZoneDetail` 成功、`catch`、`onShow`(`String(info.id) !== String(id)` 时先置 `shareSwitch = 0`)。 |
|---|
| 298 | +- `onShareAppMessage` 三条分支: |
|---|
| 299 | + 1. 好友券分享(`isCouponShare` 或 `couponType == 2 && canShareFriendCoupon()`)→ `buildCouponShareConfig()`(调分享链接接口,标题「好友专享优惠券」); |
|---|
| 300 | + 2. `!isZoneShareEnabled()` → `return {}`(不自定义内容); |
|---|
| 301 | + 3. 普通专区分享 → `path = /pages/zone/index?id=xx&spread=xx&couponZone=false` + 归属参数,标题 `info.shareTitle`,图 `info.shareImage`。 |
|---|
| 302 | +- 埋点:`yy_contentPageShare`、`yy_pageView`、`yy_marketPageClick`(`addTrack` 为全局 mixin)。 |
|---|
| 303 | + |
|---|
| 304 | +--- |
|---|
| 305 | + |
|---|
| 306 | +## 7. 券包展示(couponPackStatus) |
|---|
| 307 | + |
|---|
| 308 | +`getZoneDetail` 后 `couponPackVisit()` 解析 `info.couponPackStatus`: |
|---|
| 309 | + |
|---|
| 310 | +- `autoIssueOnZoneView == 1`(进专区自动发券): |
|---|
| 311 | + - 设置券图片(`friendCouponShortImage` / `selfCouponShortImage` / …,有默认值兜底)、`stockEmpty`、`couponBannerShow`; |
|---|
| 312 | + - `friendCoupon && selfCoupon` → `couponType = 2`(带分享的双券);`shareUnavailable == false && selfCoupon` → `couponType = 3`(不带分享); |
|---|
| 313 | + - `activityId = selfCoupon.activityId`(好友券分享用); |
|---|
| 314 | + - `productShareSwitch = couponPackStatus.productShareSwitch`; |
|---|
| 315 | + - 弹窗 `personalAndShareCouponRef.show(info, friendCouponShareToken)`。 |
|---|
| 316 | +- `autoIssueOnZoneView == 0`(接口送券展示模式):`couponDisplayShow = displayLayout != 'NONE'`; |
|---|
| 317 | + - `SINGLE_CENTER` → `couponType = 3`,`centerCoupon = displayCoupons[0]`; |
|---|
| 318 | + - `DOUBLE_SIDE` → `couponType = 2`,按 `position` 拆成 `leftCoupon` / `rightCoupon`。 |
|---|
| 319 | +- 券状态文案:`selfStatus`(0 已到账 / 1 已使用 / 2 已失效);`friendStatus` / `displayStatus`(`WAIT_USE` / `USED` / `EXPIRED` / `NOT_STARTED`);8.8 / 1.8 元券各有独立图片。 |
|---|
| 320 | +- 好友券分享按钮 `canShareFriendCoupon()`:`friendStatus` 非空且不在 `['RECEIVED','USED','EXPIRED']`;H5 端提示「请在微信小程序中分享给好友」。 |
|---|
| 321 | +- 弹窗关闭回调 `showOtherPop()` → 只弹一次新人弹窗 `popNewuserRef`。 |
|---|
| 322 | + |
|---|
| 323 | +--- |
|---|
| 324 | + |
|---|
| 325 | +## 8. 其他能力 |
|---|
| 326 | + |
|---|
| 327 | +| 能力 | 逻辑 | |
|---|
| 328 | +|---|---| |
|---|
| 329 | +| 倒计时 | `validAlert(info)` → `countdownTimer(time, 'start'/'end', token)`;未开始显示「后开启」,已开始显示「后结束」,用 `countdownToken` 作废旧定时器链;`expireTime` 为 `{digits, unit}` 数组 | |
|---|
| 330 | +| 签到校验 | `hasSign()`:zoneType 3 先 `checkSign`,未签到弹窗拦截;否则(且 `isFirst`)弹 `popVideoRef`(活动视频) | |
|---|
| 331 | +| 活动视频 | zoneType 8 且有 `uploadVideo`:`videoControls` 控制海报/播放,`bindZoneVideoPlay` 用 `createVideoContext` 播放 | |
|---|
| 332 | +| 富文本 | zoneType 8 且 `info.info` → `rich-text` | |
|---|
| 333 | +| 抽奖 | `info.isLottery && info.lotteryBanner` → `goLottery()`,跳 webview `${WEB_ACTIVITY_BASE}/acmall_lottery/index.html`(带 token/unionId/mdayId),未登录先登录 | |
|---|
| 334 | +| 规则说明 | `info.ruleTitleText && info.ruleContentText` → 点图打开 `personalAndShareCouponRuleRef` | |
|---|
| 335 | +| 视频弹窗关闭 | `handleClosePopVideo()` → 显示 `popCoupon` 且 `isFirst = false` | |
|---|
| 336 | +| 返回 | `returns()`:`uni.$emit('returnEvent')` + `navigateBack`;`returnShow` 控制返回按钮显隐(页面栈长度 > 1) | |
|---|
| 337 | +| 悦养豆汇率 | `careBeanRate` 来自 `store.getters.careBeanRate`(`updateCareBeanRate` 事件),透传给 `GoodBlock` | |
|---|
| 338 | + |
|---|
| 339 | +--- |
|---|
| 340 | + |
|---|
| 341 | +## 9. 关键状态字段速查 |
|---|
| 342 | + |
|---|
| 343 | +| 字段 | 含义 | |
|---|
| 344 | +|---|---| |
|---|
| 345 | +| `info` | 专区详情(`getZoneDetail` 全量) | |
|---|
| 346 | +| `isYueyangZone` | 是否悦养专区(zoneType 6) | |
|---|
| 347 | +| `zoneType` | 专区形态:6 悦养 / 7 分区标签 / 3 需签到 / 8 视频+富文本 | |
|---|
| 348 | +| `shareSwitch` / `couponPackStatus.zoneShareSwitch` | 专区分享开关(1 可分享) | |
|---|
| 349 | +| `productShareSwitch` | 传给商品卡的分享开关(先取 `res.data.shareSwitch`,券包模式下被 `couponPackStatus.productShareSwitch` 覆盖) | |
|---|
| 350 | +| `partitionList` / `partitionProject` / `currentIndex` / `allTop` | zoneType 7 的分区数据、商品映射、当前标签、锚点偏移 | |
|---|
| 351 | +| `where` / `goodList` / `loadend` / `activeTabIndex` | 通用专区分页条件、列表、是否加载完、当前排序 | |
|---|
| 352 | +| `couponType` / `couponBannerShow` / `couponDisplayShow` / `stockEmpty` | 券包展示形态控制 | |
|---|
| 353 | +| `friendStatus` / `selfStatus` / `activityId` / `friendCouponShareToken` | 好友券状态与分享凭证 | |
|---|
| 354 | +| `isCouponShare` | 本次分享是否为好友券分享 | |
|---|
| 355 | + |
|---|
| 356 | +--- |
|---|
| 357 | + |
|---|
| 358 | +## 10. 已知问题与注意点 |
|---|
| 359 | + |
|---|
| 360 | +1. `couponPackVisit` / `getZoneGoodList` 两个 import 未真正使用(前者被同名方法遮蔽),券包状态完全依赖 `getZoneDetail` 返回,若后端改为独立接口需补调用。 |
|---|
| 361 | +2. `productShareSwitch` 会被赋值两次(1505 行 `res.data.shareSwitch` → 1450 行券包模式覆盖),券包开启时以后者为准。 |
|---|
| 362 | +3. 悦养专区商品卡的 `share-switch` 用的是 `zoneInfo.shareSwitch`,而通用专区用的是 `productShareSwitch`,两者来源不同。 |
|---|
| 363 | +4. `activeTab`(子组件)初始值字符串 `'5'`,而 `switchTab` 写入数字索引,模板用 `==` 宽松比较,标签数 ≥ 6 时第 5 个标签会误高亮。 |
|---|
| 364 | +5. `zoneTab` 缓存同时可能存 `relationKey` 或索引两种语义,且为全局 key,跨专区复用时可能选中错误标签(`onLoad` 会清除一次,但未完全隔离)。 |
|---|
| 365 | +6. `relationKey` 为 `0` / `''` 时是 falsy,第一优先级会被跳过;`userInfo` 未就绪时 `userInfo.level < 3` 为 false,非会员兜底失效。 |
|---|
| 366 | +7. `couponProductTabs` 依赖 `couponList[0].couponProductTabs`,解析失败会走 `catch` 置空,整块标签栏与商品列表消失。 |
|---|
| 367 | +8. `cateId` 参数只赋值不使用;`getZoneDetail` 入参里 `where.zoneId` 被写了两次(1636/1637 行),实际以 `info.id` 为准。 |
|---|
| 368 | +9. zoneType 7 的锚点依赖图片加载完成(`onPartitionImageLoad`)与 `scheduleSetAllScrollTop` 的 150ms 防抖,图片较多的专区首屏联动可能有一帧偏差。 |
|---|