|
@@ -0,0 +1,395 @@
|
|
|
|
|
+#### ai 自行的发出指令:
|
|
|
|
|
+
|
|
|
|
|
+请在以下目录中做代码研究(只读,不要修改文件):
|
|
|
|
|
+- /Users/shishao/dnmp/www/front-end (前端 uni-app 多项目仓库,重点关注 mallApp 子项目)
|
|
|
|
|
+- /Users/shishao/dnmp/www/huahuibao (后端 Yii2 多应用仓库,重点关注 mallApp 对应的后端应用,可能是 app-mall)
|
|
|
|
|
+
|
|
|
|
|
+需要回答的问题:
|
|
|
|
|
+
|
|
|
|
|
+1. 找到 mallApp/src/pages/home/cart.vue 完整文件内容。重点关注:
|
|
|
|
|
+ - 购物车列表中商品的唯一标识逻辑(用什么字段判断“同一件商品”,是否用到 goodsId + specGoodsId,是否考虑秒杀 activityType / activityId / seckillId 等字段区分同一 specGoodsId 下的秒杀 vs 普通商品)。
|
|
|
|
|
+ - 点击“去结算”时,是如何组装数据、通过什么方式(路由参数/本地存储/store)传递给 billing/affirmMix.vue 页面的。
|
|
|
|
|
+ - 购物车中商品加入/合并(如加减数量、选中商品)时用什么 key 做 dedupe 或 grouping。
|
|
|
|
|
+
|
|
|
|
|
+2. 找到 mallApp/src/pages/billing/affirmMix.vue 完整文件内容。重点关注:
|
|
|
|
|
+ - 页面拿到商品列表后,如何做“商品合并”(可能是把多条购物车记录,按 goodsId+specGoodsId 等聚合成同一个商品条目,尤其是涉及到数量累加、价格计算的地方)。
|
|
|
|
|
+ - 找出具体做合并 (merge/groupBy/reduce) 的代码片段,列出用到的 key 字段。
|
|
|
|
|
+ - 这个合并逻辑为什么会把用户描述的第2个(秒跌商品,specGoodsId=2120, activityType=seckill, price=3)和第3个商品(普通商品, specGoodsId=2120, price=16)当作同一个商品合并,请具体分析原因(多半是 groupBy key 只用了 goodsId+specGoodsId,没有把 activityType/activityId/秒杀标识纳入 key)。
|
|
|
|
|
+ - 找出该合并逻辑相关的完整代码位置(文件路径+行号)。
|
|
|
|
|
+
|
|
|
|
|
+3. 在 mallApp 相关的 api 封装 (src/api/**) 中找到"提交订单"、"结算预览"相关接口,看看提交给后端的数据结构里,商品项是否带有 activityType/activityId/seckillId 等字段用以区分秒杀和普通商品。
|
|
|
|
|
+
|
|
|
|
|
+4. 在后端 huahuibao 仓库中,找到 app-mall (或类似目录) 中处理"订单结算/下单"逻辑的 Controller 及相关的业务类 (biz-*/*/classes),重点关注:
|
|
|
|
|
+ - 订单里商品的唯一性判断逻辑,是否使用 goodsId+specGoodsId,还是额外用了活动类型/活动id 来区分秒杀商品和普通商品。
|
|
|
|
|
+ - 查找关键词如 "specGoodsId", "activityType", "seckill", "秒杀" 等,找到相关 Model/Form/Service 类的字段定义和处理逻辑。
|
|
|
|
|
+ - 找出后端认为"同一商品"的判断字段有哪些,是否包含秒杀相关标识。
|
|
|
|
|
+
|
|
|
|
|
+5. 搜索整个 mallApp 目录下(不限于这两个文件)是否有其他地方用到类似的商品合并/去重逻辑(例如 utils 里的公共函数),这些函数被哪些页面复用。
|
|
|
|
|
+
|
|
|
|
|
+请把找到的关键代码片段(带文件路径和行号)、以及你的分析总结,完整返回给我。这非常重要,请尽量详细并给出具体代码内容,不要只给结论。
|
|
|
|
|
+
|
|
|
|
|
+#### Ai 回答输出
|
|
|
|
|
+以下是只读研究结果。核心结论先说:**购物车加购能区分秒杀/普通,但结算页合并 key 漏了 `activityType`,因此同 `goodsId + specGoodsId` 的秒杀与普通会被合成一行。**
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+## 1. `cart.vue`:唯一标识、去结算、加减合并
|
|
|
|
|
+
|
|
|
|
|
+文件:`/Users/shishao/dnmp/www/front-end/mallApp/src/pages/home/cart.vue`(约 1197 行;逻辑在 mixin `cgProduct`)
|
|
|
|
|
+
|
|
|
|
|
+### 1.1 商品唯一标识(真正逻辑在 mixin)
|
|
|
|
|
+
|
|
|
|
|
+购物车页本身不写 dedupe key,而是:
|
|
|
|
|
+
|
|
|
|
|
+```371:373:/Users/shishao/dnmp/www/front-end/mallApp/src/pages/home/cart.vue
|
|
|
|
|
+ getItemKey(item) {
|
|
|
|
|
+ return this.getSelectRowKey(item)
|
|
|
|
|
+ },
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+```514:518:/Users/shishao/dnmp/www/front-end/mallApp/src/pages/home/cart.vue
|
|
|
|
|
+ findCartRowIndex(list, item) {
|
|
|
|
|
+ if (!item || item.id === undefined || item.id === null) {
|
|
|
|
|
+ return -1
|
|
|
|
|
+ }
|
|
|
|
|
+ return list.findIndex((el) => this._selectRowMatches(el, item))
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+`cgProduct.js` 里两套逻辑**不一致**:
|
|
|
|
|
+
|
|
|
|
|
+**A. `getSelectRowKey`(勾选 map / 结算合并用)——不含 `activityType`:**
|
|
|
|
|
+
|
|
|
|
|
+```261:272:/Users/shishao/dnmp/www/front-end/mallApp/src/mixins/cgProduct.js
|
|
|
|
|
+ /** 购物车行唯一键:花束用 id+property+specGoodsId,花材用 id(+classId) */
|
|
|
|
|
+ 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}`;
|
|
|
|
|
+ },
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+花束 key = `0_{goodsId}_{specGoodsId}`,**没有** `activityType` / `activityId` / `seckillId`。
|
|
|
|
|
+
|
|
|
|
|
+**B. `_selectRowMatches`(加购/删除/加减定位用)——花束会比 `activityType`:**
|
|
|
|
|
+
|
|
|
|
|
+```307:322:/Users/shishao/dnmp/www/front-end/mallApp/src/mixins/cgProduct.js
|
|
|
|
|
+ _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);
|
|
|
|
|
+ }
|
|
|
|
|
+ if (this._shouldMergeSelectByProductId()) {
|
|
|
|
|
+ return String(element.id) === String(item.id);
|
|
|
|
|
+ }
|
|
|
|
|
+ return element.id == item.id && element.classId == item.classId;
|
|
|
|
|
+ },
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+加购时写入 `activityType: 'seckill'`:
|
|
|
|
|
+
|
|
|
|
|
+```468:474:/Users/shishao/dnmp/www/front-end/mallApp/src/mixins/cgProduct.js
|
|
|
|
|
+ // 秒杀商品:打上活动标记与原价,用于购物车/结算页展示划线价;
|
|
|
|
|
+ // 实际下单价格由后端独立核对活动配置核价,此处价格仅用于前端展示
|
|
|
|
|
+ if (goods.activityType === 'seckill') {
|
|
|
|
|
+ row.activityType = 'seckill';
|
|
|
|
|
+ row.activityEndTime = Number(goods.activityEndTime) || 0;
|
|
|
|
|
+ row.originPrice = Number(goods.originPrice) || 0;
|
|
|
|
|
+ }
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+**结论(购物车侧):**
|
|
|
|
|
+
|
|
|
|
|
+| 场景 | 判断字段 | 是否区分秒杀 vs 普通 |
|
|
|
|
|
+|------|----------|----------------------|
|
|
|
|
|
+| 加购合并 / 加减 / 删除 | `property` + `id` + `specGoodsId` + **`activityType`** | 是,可两行并存 |
|
|
|
|
|
+| 勾选 `checkedMap` key | `0_id_specGoodsId`(无 activityType) | **否**,同规格秒杀/普通共用勾选态 |
|
|
|
|
|
+| 仓库中未发现 `activityId` / `seckillId` 参与购物车行身份 | — | — |
|
|
|
|
|
+
|
|
|
|
|
+展示上会看 `activityType === 'seckill'` 打「秒杀」标(`cart.vue` 第 51 行)。
|
|
|
|
|
+
|
|
|
|
|
+### 1.2 「去结算」如何传给 `affirmMix`
|
|
|
|
|
+
|
|
|
|
|
+```660:706:/Users/shishao/dnmp/www/front-end/mallApp/src/pages/home/cart.vue
|
|
|
|
|
+ goCheckout() {
|
|
|
|
|
+ const list = this.checkedCheckoutList
|
|
|
|
|
+ ...
|
|
|
|
|
+ const checkoutUrl = '/pages/billing/affirmMix'
|
|
|
|
|
+ ...
|
|
|
|
|
+ const navigateAffirm = () => {
|
|
|
|
|
+ this.limitBuyWarnList = []
|
|
|
|
|
+ this.setSelectInfoByType({ type: this.pageType, info: list })
|
|
|
|
|
+ this.setLimitBuyInfoByType({ type: this.pageType, info: [] })
|
|
|
|
|
+ const realHdId = this.hdId || uni.getStorageSync('hdId') || 0
|
|
|
|
|
+ this.$util.pageTo({
|
|
|
|
|
+ url: checkoutUrl,
|
|
|
|
|
+ query: { account, hdId: realHdId }
|
|
|
|
|
+ })
|
|
|
|
|
+ }
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+传递方式:
|
|
|
|
|
+
|
|
|
|
|
+1. **Vuex**:`setSelectInfoByType({ type: 'cg', info: 勾选列表 })` —— 商品明细走 store,不是 URL。
|
|
|
|
|
+2. **路由 query**:仅 `account`、`hdId`。
|
|
|
|
|
+3. 限购时还会 `setLimitBuyInfoByType`;本地缓存 key 形如 `selectListcg_hd_{account}`(`persistCartToStorage` / `rememberProduct`)。
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+## 2. `affirmMix.vue`:合并逻辑与误合并原因
|
|
|
|
|
+
|
|
|
|
|
+文件:`/Users/shishao/dnmp/www/front-end/mallApp/src/pages/billing/affirmMix.vue`
|
|
|
|
|
+
|
|
|
|
|
+### 2.1 合并代码(根因位置)
|
|
|
|
|
+
|
|
|
|
|
+`onShow`:
|
|
|
|
|
+
|
|
|
|
|
+```585:603:/Users/shishao/dnmp/www/front-end/mallApp/src/pages/billing/affirmMix.vue
|
|
|
|
|
+ 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;
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+公共合并(会先改 Vuex):
|
|
|
|
|
+
|
|
|
|
|
+```273:305:/Users/shishao/dnmp/www/front-end/mallApp/src/mixins/cgProduct.js
|
|
|
|
|
+ _mergeDuplicateSelectRows(list) {
|
|
|
|
|
+ ...
|
|
|
|
|
+ const key = this.getSelectRowKey(ele) || String(ele.id);
|
|
|
|
|
+ if (!map.has(key)) {
|
|
|
|
|
+ ...
|
|
|
|
|
+ } else {
|
|
|
|
|
+ const prev = map.get(key);
|
|
|
|
|
+ prev.bigCount = Number(prev.bigCount) + (Number(ele.bigCount) || 0);
|
|
|
|
|
+ prev.smallCount = Number(prev.smallCount) + (Number(ele.smallCount) || 0);
|
|
|
|
|
+ }
|
|
|
|
|
+ ...
|
|
|
|
|
+ },
|
|
|
|
|
+ _syncMergedSelectListIfNeeded() {
|
|
|
|
|
+ if (!this._shouldMergeSelectByProductId()) return;
|
|
|
|
|
+ ...
|
|
|
|
|
+ const merged = this._mergeDuplicateSelectRows(list);
|
|
|
|
|
+ if (merged.length !== list.length) {
|
|
|
|
|
+ this.setSelectInfoByType({ type: this.pageType, info: merged });
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### 2.2 为什么秒杀(specGoodsId=2120, price=3) 和普通(specGoodsId=2120, price=16) 会被合成一件
|
|
|
|
|
+
|
|
|
|
|
+假设两行:
|
|
|
|
|
+
|
|
|
|
|
+- 秒杀:`id=主商品, specGoodsId=2120, activityType='seckill', price=3`
|
|
|
|
|
+- 普通:`id=主商品, specGoodsId=2120, activityType='' , price=16`
|
|
|
|
|
+
|
|
|
|
|
+两者 `getSelectRowKey` 都是:
|
|
|
|
|
+
|
|
|
|
|
+```text
|
|
|
|
|
+0_{主商品id}_2120
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+合并时:
|
|
|
|
|
+
|
|
|
|
|
+1. `_syncMergedSelectListIfNeeded()` 先按该 key 合并进 Vuex;
|
|
|
|
|
+2. `onShow` 再用同一 key 累加 `bigCount` / `smallCount`;
|
|
|
|
|
+3. **只保留先出现那一行的其它字段**(`price`、`activityType` 等),后一行数量加进去。
|
|
|
|
|
+
|
|
|
|
|
+因此会出现:
|
|
|
|
|
+
|
|
|
|
|
+- 数量相加;
|
|
|
|
|
+- 价格/秒杀标只剩第一条的;
|
|
|
|
|
+- UI 只剩一行。
|
|
|
|
|
+
|
|
|
|
|
+**根因确认:合并 key 只用了 `property + id + specGoodsId`,没有把 `activityType`(更没有 activityId)纳入。**
|
|
|
|
|
+加购侧 `_selectRowMatches` 已意识到要分行,但 `getSelectRowKey` / 结算合并未对齐。
|
|
|
|
|
+
|
|
|
|
|
+### 2.3 合并相关完整位置
|
|
|
|
|
+
|
|
|
|
|
+| 位置 | 路径 | 行号 |
|
|
|
|
|
+|------|------|------|
|
|
|
|
|
+| 结算页合并循环 | `mallApp/src/pages/billing/affirmMix.vue` | 588–603 |
|
|
|
|
|
+| 进入页先同步合并 Vuex | 同上 + `cgProduct._syncMergedSelectListIfNeeded` | affirmMix 588;mixin 298–305 |
|
|
|
|
|
+| key 定义 | `mallApp/src/mixins/cgProduct.js` | 262–272 |
|
|
|
|
|
+| Map 合并实现 | `mallApp/src/mixins/cgProduct.js` | 273–296 |
|
|
|
|
|
+| 加购可区分 activityType | `mallApp/src/mixins/cgProduct.js` | 307–317, 475–485 |
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+## 3. mallApp API:提交订单 / 结算预览
|
|
|
|
|
+
|
|
|
|
|
+`/Users/shishao/dnmp/www/front-end/mallApp/src/api/order/index.js`:
|
|
|
|
|
+
|
|
|
|
|
+```39:48:/Users/shishao/dnmp/www/front-end/mallApp/src/api/order/index.js
|
|
|
|
|
+export const createOrder = data => {
|
|
|
|
|
+ return https.post('/order/create-order', data)
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/** *
|
|
|
|
|
+ * 花束+花材合并结算下单(不影响 buy-item / create-order)
|
|
|
|
|
+ */
|
|
|
|
|
+export const createMixOrder = data => {
|
|
|
|
|
+ return https.post('/order/create-mix-order', data)
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+- **混合结算提交**:`createMixOrder` → `POST /order/create-mix-order`(affirmMix 使用)
|
|
|
|
|
+- **纯花材**:`buyItem` → `/order/buy-item`(affirmGhs)
|
|
|
|
|
+- **立即购买花束**:`createOrder` → `/order/create-order`
|
|
|
|
|
+- **未发现单独的「结算预览」接口**;affirmMix 金额在前端算,提交时后端再核价。相关辅助:`getLimitBuyInfo`、`orderRelate`、`allDeliveryQuotes`(跑腿报价)
|
|
|
|
|
+
|
|
|
|
|
+### affirmMix 提交结构(花束是否带活动字段)
|
|
|
|
|
+
|
|
|
|
|
+```1644:1668:/Users/shishao/dnmp/www/front-end/mallApp/src/pages/billing/affirmMix.vue
|
|
|
|
|
+ const product = (this.list || []).map((ele) => {
|
|
|
|
|
+ if (this.isBouquetItem(ele)) {
|
|
|
|
|
+ const saleId = Number(ele.specGoodsId) > 0 ? Number(ele.specGoodsId) : Number(ele.id);
|
|
|
|
|
+ const row = {
|
|
|
|
|
+ productId: saleId,
|
|
|
|
|
+ goodsId: Number(ele.goodsId || ele.id),
|
|
|
|
|
+ specGoodsId: Number(ele.specGoodsId) || 0,
|
|
|
|
|
+ num: Number(ele.bigCount) || 1,
|
|
|
|
|
+ unitType: 0,
|
|
|
|
|
+ property: 0
|
|
|
|
|
+ };
|
|
|
|
|
+ // 秒杀/团购打上活动标记,后端据此独立核对活动库存/限购/价格后再计价
|
|
|
|
|
+ if (ele.activityType === "seckill" || ele.activityType === "groupBuy") {
|
|
|
|
|
+ row.activityType = ele.activityType;
|
|
|
|
|
+ }
|
|
|
|
|
+ return row;
|
|
|
|
|
+ }
|
|
|
|
|
+ ...
|
|
|
|
|
+ return { productId: ele.id, num, itemId: ele.itemId, unitType, property: 1 };
|
|
|
|
|
+ });
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+要点:
|
|
|
|
|
+
|
|
|
|
|
+- 花束可带 `activityType: 'seckill' | 'groupBuy'`
|
|
|
|
|
+- **没有** `activityId` / `seckillId`
|
|
|
|
|
+- 花材行不带活动字段
|
|
|
|
|
+- **若前端已误合并**,只会提交一行;是否带 `activityType` 取决于合并后保留的是哪一行
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+## 4. 后端 app-mall:下单唯一性与秒杀
|
|
|
|
|
+
|
|
|
|
|
+入口:`/Users/shishao/dnmp/www/huahuibao/app-mall/controllers/OrderController.php` → `actionCreateMixOrder`(约 958 行起)
|
|
|
|
|
+
|
|
|
|
|
+### 4.1 处理方式
|
|
|
|
|
+
|
|
|
|
|
+按 `product` JSON **逐行**处理,**没有**用 `goodsId+specGoodsId` 再做跨行去重。
|
|
|
|
|
+
|
|
|
|
|
+花束分支(约 1149–1255):
|
|
|
|
|
+
|
|
|
|
|
+- 用 `goodsId` / `specGoodsId` / `productId` 解析销售单元 `saleGoodsId`
|
|
|
|
|
+- **仅当** `activityType === 'seckill'` 时走秒杀:
|
|
|
|
|
+
|
|
|
|
|
+```1198:1238:/Users/shishao/dnmp/www/huahuibao/app-mall/controllers/OrderController.php
|
|
|
|
|
+ // 秒杀商品:不信任前端传的价格,独立回查 Redis 秒杀配置核实活动状态/库存/限购后再核价
|
|
|
|
|
+ $seckillRow = null;
|
|
|
|
|
+ if (strval($element['activityType'] ?? '') === 'seckill') {
|
|
|
|
|
+ $seckillRow = HomePageModuleClass::getSeckillActiveRow($mainId, $saleGoodsId);
|
|
|
|
|
+ ...
|
|
|
|
|
+ }
|
|
|
|
|
+ ...
|
|
|
|
|
+ if ($seckillRow) {
|
|
|
|
|
+ $unitPrice = floatval($seckillRow['price'] ?? 0);
|
|
|
|
|
+ ...
|
|
|
|
|
+ } else {
|
|
|
|
|
+ $ret = \bizHd\goods\classes\GoodsClass::getFinalPrice(...);
|
|
|
|
|
+ ...
|
|
|
|
|
+ }
|
|
|
|
|
+ if ($seckillRow) {
|
|
|
|
|
+ HomePageModuleClass::reserveSeckillPurchase($mainId, $saleGoodsId, $hdCustomId, $goodsNum);
|
|
|
|
|
+ }
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+秒杀配置查找:`HomePageModuleClass::getSeckillActiveRow($mainId, $goodsId)` —— 按 **销售商品 id(规格 id)** 匹配,无单独 activityId:
|
|
|
|
|
+
|
|
|
|
|
+```309:323:/Users/shishao/dnmp/www/huahuibao/biz-hd/homePageConfig/classes/HomePageModuleClass.php
|
|
|
|
|
+ public static function getSeckillActiveRow($mainId, $goodsId)
|
|
|
|
|
+ {
|
|
|
|
|
+ $data = self::getSeckill($mainId, true);
|
|
|
|
|
+ ...
|
|
|
|
|
+ if (intval($item['goodsId'] ?? 0) === $goodsId) {
|
|
|
|
|
+ return $item;
|
|
|
|
|
+ }
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### 4.2 后端「同一商品」字段
|
|
|
|
|
+
|
|
|
|
|
+| 维度 | 字段 |
|
|
|
|
|
+|------|------|
|
|
|
|
|
+| 花材行 | `productId`(+ property=1) |
|
|
|
|
|
+| 花束行 | `goodsId` + `specGoodsId`/`productId` → `saleGoodsId` |
|
|
|
|
|
+| 秒杀 vs 普通 | **`activityType === 'seckill'`**(请求行级);无 activityId |
|
|
|
|
|
+| 秒杀库存/限购键 | `mainId` + `saleGoodsId`(规格商品 id) |
|
|
|
|
|
+
|
|
|
|
|
+后端本身允许同规格两行(一行带 seckill、一行不带);当前问题主要在**前端合并丢掉一行后再提交**。
|
|
|
|
|
+
|
|
|
|
|
+单品立即购买 `create-order` 同样用顶层 `activityType`(约 610 行),逻辑一致。
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+## 5. mallApp 其它合并/去重复用
|
|
|
|
|
+
|
|
|
|
|
+| 函数/逻辑 | 文件 | 被谁用 |
|
|
|
|
|
+|-----------|------|--------|
|
|
|
|
|
+| `getSelectRowKey` / `_mergeDuplicateSelectRows` / `_syncMergedSelectListIfNeeded` / `_selectRowMatches` | `src/mixins/cgProduct.js` | 混入 cart、affirmMix、affirmGhs、商品详情加购等 |
|
|
|
|
|
+| affirmMix `onShow` mergedMap | `pages/billing/affirmMix.vue` 588–603 | 仅混合结算 |
|
|
|
|
|
+| affirmGhs `onShow` | `pages/billing/affirmGhs.vue` 367–381 | **只用 `item.id`** 合并,更粗,主要面向花材 |
|
|
|
|
|
+| `addBouquetToCart` | mixin | `goods/detail.vue` → `onAddCartFromPopup` |
|
|
|
|
|
+
|
|
|
|
|
+未发现独立 utils 公共 `groupBy(goodsId, specGoodsId)`;合并集中在 **cgProduct mixin + 两个结算页 onShow**。
|
|
|
|
|
+
|
|
|
|
|
+仓库内**没有** `activityId` / `seckillId` 作为购物车/结算行 key;活动标识基本是字符串 `activityType: 'seckill'`。
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+## 总览(问题链路)
|
|
|
|
|
+
|
|
|
|
|
+```text
|
|
|
|
|
+加购 (_selectRowMatches 含 activityType)
|
|
|
|
|
+ → 购物车可显示两行(秒杀 ¥3 + 普通 ¥16)
|
|
|
|
|
+
|
|
|
|
|
+勾选 key = getSelectRowKey(无 activityType)
|
|
|
|
|
+ → 同规格两行共享 checkedMap
|
|
|
|
|
+
|
|
|
|
|
+去结算 → Vuex selectList + 路由 account/hdId
|
|
|
|
|
+
|
|
|
|
|
+affirmMix onShow
|
|
|
|
|
+ → _syncMergedSelectListIfNeeded + mergedMap
|
|
|
|
|
+ → key = 0_id_2120(无 activityType)
|
|
|
|
|
+ → 两行合成一行,数量累加,价格/活动标取第一行
|
|
|
|
|
+
|
|
|
|
|
+提交 createMixOrder
|
|
|
|
|
+ → 只剩一行 product;activityType 可能错/丢
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+**修复方向(仅分析,未改代码):** 让 `getSelectRowKey`(及 `_mergeDuplicateSelectRows` / affirmMix 合并)与 `_selectRowMatches` 对齐,花束 key 至少加入 `activityType`(例如 `0_${id}_${specGoodsId}_${activityType||''}`),勾选 map 同步调整。
|