修复购物车秒杀商品合并.md 5.9 KB


name: 修复购物车秒杀商品合并 overview: 修复 mallApp 购物车/结算页把同规格「秒杀花束」与「普通花束」误合并成同一商品的问题,根因是商品去重/合并的 key 生成函数缺少活动类型字段。 todos:

  • id: fix-key content: 修改 cgProduct.js 的 getSelectRowKey,花束分支 key 加入 activityType status: pending
  • id: verify content: 在购物车/结算页手动验证秒杀行与普通行不再被误合并,并可正常下单 status: pending isProject: false ---

根因

mallApp 购物车与结算的“同一商品”判定分为两套逻辑,二者不一致:

  • 加购/删除定位用的是 _selectRowMatches(花束分支已考虑 activityType,能把秒杀行和普通行分开):

    _selectRowMatches(element, item) {
    if (!element || !item) return false;
    const eleProperty = this.getItemProperty(element);
    const itemProperty = this.getItemProperty(item);
    if (eleProperty !== itemProperty) return false;
    if (eleProperty === 0) {
        // 秒杀与普通购买同商品同规格也必须分行,避免不同计价规则的购物车行被合并
        if ((element.activityType || '') !== (item.activityType || '')) return false;
        return String(element.id) === String(item.id)
            && String(element.specGoodsId || 0) === String(item.specGoodsId || 0);
    }
    ...
    }
    
  • 勾选态 key / 合并去重 key 用的是 getSelectRowKey(花束分支没有activityType,是本次 bug 的根因):

    getSelectRowKey(item) {
    if (!item || item.id === undefined || item.id === null) return '';
    const property = this.getItemProperty(item);
    if (property === 0) {
        return `0_${item.id}_${item.specGoodsId || 0}`;
    }
    if (this._shouldMergeSelectByProductId()) {
        return `1_${item.id}`;
    }
    return `1_${item.id}_${item.classId || 0}`;
    },
    

getSelectRowKey 被两处消费:

  1. mallApp/src/mixins/cgProduct.js_mergeDuplicateSelectRows(第 273-296 行),当 pageType === 'cg'(mallApp 购物车/结算页均为 cg)时会在 _syncMergedSelectListIfNeeded(298-306 行)里被调用,合并 Vuex 中的 selectList
  2. mallApp/src/pages/billing/affirmMix.vueonShow(第 585-604 行左右),在拿到 selectList 后再次用同一个 key 做本地合并展示:

    onShow() {
    this.syncShopContext();
    this._syncMergedSelectListIfNeeded();
    const selectList = this.$util.copyObject(this.selectList);
    const mergedMap = {};
    const list = [];
    for (let i = 0; i < selectList.length; i++) {
        const item = selectList[i];
        const key = this.getSelectRowKey(item) || String(item.id);
        if (mergedMap[key]) {
            mergedMap[key].bigCount = Number(mergedMap[key].bigCount || 0) + Number(item.bigCount || 0);
            mergedMap[key].smallCount = Number(mergedMap[key].smallCount || 0) + Number(item.smallCount || 0);
        } else {
            mergedMap[key] = this.$util.copyObject(item);
            list.push(mergedMap[key]);
        }
    }
    this.list = list;
    ...
    

因为秒杀行(specGoodsId=2120, activityType='seckill', price=3)和普通行(specGoodsId=2120, activityType='', price=16)算出的 getSelectRowKey 都是 0_{goodsId}_2120,两行被合并成一行:数量相加,但价格/activityType 只保留了先出现的那一行,导致最终提交给后端 POST /order/create-mix-orderproduct 数组里少了一行、且这一行的活动标记可能是错的。

后端 app-mall/controllers/OrderController.phpactionCreateMixOrder(958 行起)是按 product 数组逐行独立处理和核价的(1110-1256 行),并不会对 goodsId+specGoodsId 做跨行去重,秒杀单独通过 activityType === 'seckill' 判断走 Redis 核价(1198-1239 行)。也就是说后端本来就支持同一规格同时存在秒杀行和普通行,问题完全在前端合并 key 缺字段。

修复方案

只需让 getSelectRowKey 的花束分支和 _selectRowMatches 保持一致,把 activityType 纳入 key:

文件:front-end/mallApp/src/mixins/cgProduct.js

getSelectRowKey(item) {
    if (!item || item.id === undefined || item.id === null) return '';
    const property = this.getItemProperty(item);
    if (property === 0) {
        return `0_${item.id}_${item.specGoodsId || 0}_${item.activityType || ''}`;
    }
    if (this._shouldMergeSelectByProductId()) {
        return `1_${item.id}`;
    }
    return `1_${item.id}_${item.classId || 0}`;
},

这一处改动会自动修复三个消费点:

  • mallApp/src/pages/home/cart.vue 第 371-373 行 getItemKeycheckedMap 勾选态 key,不再把秒杀行和普通行的勾选状态混在一起。
  • cgProduct.js_mergeDuplicateSelectRows / _syncMergedSelectListIfNeeded,Vuex 里的 selectList 合并不再误并。
  • affirmMix.vueonShow 合并循环(585-603 行),结算页展示与提交给 createMixOrderproduct 数组会分别保留秒杀行和普通行各自的价格、数量、activityType

不需要改动后端和 affirmGhs.vue(该页仅处理花材 property=1,本身不带活动字段,_selectRowMatches 分支不涉及秒杀问题)。

验证

  • 购物车里对同一花束同一规格分别以“秒杀价”和“普通价”各加入购物车,确认两行分别显示、可分别勾选/编辑数量。
  • 两行都勾选后点击「去结算」,确认 affirmMix.vue 展示两行(各自价格、各自「秒杀」标签),数量互不影响。
  • 提交下单,确认请求体 product 数组包含两条独立记录(一条带 activityType: 'seckill',一条不带),且后端下单成功、金额正确。