shizhongqi 1 týždeň pred
rodič
commit
45939ec114

+ 0 - 0
AI/AI辅助编程IDE/Cursor/cursor 代理设置.md → AI/Cursor/cursor 代理设置.md


+ 24 - 0
AI/Cursor/skills/创建skill.md

@@ -0,0 +1,24 @@
+#### 需求
+/Users/shishao/.codex/skills/yii2-api-form-validation/SKILL.md@/Users/shishao/.codex/skills/yii2-api-form-validation/SKILL.md 
+请在 agents 下添加上如上的 skill,并且告知如何主动与被动触发它
+
+
+#### cursor 的输出
+![[Pasted image 20260722143430.png]]
+已完成。`yii2-api-form-validation` 已接入本仓库 Agent 体系,位置如下:
+
+1. **项目 skill**:`.cursor/skills/yii2-api-form-validation/`  
+   - `SKILL.md`  
+   - `agents/openai.yaml`(含 `policy.allow_implicit_invocation: true`)  
+   - `references/patterns.md`
+2. **`AGENTS.md`**:新增 Skills 说明  
+3. **个人 skill**:`~/.codex/skills/.../agents/openai.yaml` 已同步开启被动触发
+
+### 如何触发
+**主动触发(显式)**
+- 对话里写:`使用 $yii2-api-form-validation 给这个 Controller 补 Form 校验`
+- 或从 skill 列表选 “Yii2 API Form Validation”
+
+**被动触发(隐式)**
+- 不提 skill 名,但需求命中 description,例如「给 action 加校验 / 用 BaseForm / 替换 request get/post」
+- 依赖 `allow_implicit_invocation: true`;若设为 `false`,或 Cursor 侧加了 `disable-model-invocation: true`,则只会主动触发

+ 0 - 0
AI/AI辅助编程IDE/Cursor/最佳实践.md → AI/Cursor/最佳实践.md


+ 1 - 1
Home.md

@@ -57,7 +57,7 @@ sensitive: false
 
 - [[AI/Codex/选中代码 + 提需求]]
 - [[Context Engineering/ContextEngineering]]
-- [[AI/AI辅助编程IDE/Cursor/最佳实践]]
+- [[最佳实践]]
 - [[AI/AI辅助编程IDE/cursor_rules/yii2_cursor_rules]]
 
 ## 工具环境

BIN
attachments/Pasted image 20260722143430.png


+ 56 - 0
任务小笔记/26年/07-13:花掌柜与花卉宝改版/6-秒杀商品实现能展示与购买.md

@@ -0,0 +1,56 @@
+### 需求指令:
+mallApp/src/pages/home/index.vue 在店铺首页下,点击秒杀专区的商品,进入详情页时,要把秒杀商品的数据带过来,并且能够加入购物车进行下一步的合并下单
+
+## AI执行
+实现完成,改动跨了 mallApp 前端和 huahuibao 后端两个仓库。汇总如下:
+
+## 问题诊断
+
+之前点击首页「秒杀专区」的商品,只是带着 `id` 跳到普通商品详情页(`activitySection.vue` 的 `goDetail`),秒杀价、限购、活动截止时间等数据完全丢失;加入购物车后走「合并下单」(`affirmMix.vue` → `createMixOrder`),后端 `OrderController::actionCreateMixOrder` 对花束类商品**永远按商品原价重新核价**,从没读取过秒杀配置——秒杀专区实际上只是首页展示噱头,跟真实下单完全没有打通。
+
+## 前端改动(mallApp)
+
+- `components/home/activitySection.vue`:秒杀商品点击详情时,在跳转链接里附带 `activityType/activityPrice/activityLimit/activityStock/activityEndTime`。
+- `pages/goods/section-list.vue`(秒杀「更多」列表):同样补上活动参数(后端 `getSectionGoodsPage` 也加了 `startTime/endTime` 返回,见下)。
+- `pages/goods/detail.vue`:解析活动参数,展示秒杀价横条(秒杀价标签、倒计时、限购提示)、原价划线;加入购物车时把活动标记透传下去。
+- `mixins/cgProduct.js`(`addBouquetToCart`):购物车行打上 `activityType/activityEndTime/originPrice`,并让 `getCartItemOriginalUnitPrice`/`cartPriceShowStrike` 支持秒杀商品的划线价展示。
+- `pages/home/cart.vue`、`pages/billing/affirmMix.vue`:购物车、确认下单页展示「秒杀」标签和划线原价;`affirmMix` 提交合并下单时把 `activityType` 一起传给后端。
+
+## 后端改动(huahuibao)
+
+- `biz-hd/homePageConfig/classes/HomePageModuleClass.php`:新增 `getSeckillActiveRow`(校验活动是否在进行中、商品是否仍在秒杀配置里上架)、`getSeckillSoldCount`/`getSeckillCustomBoughtCount`(读取 Redis 计数)、`reserveSeckillPurchase`/`rollbackSeckillReservationSnapshot`/`clearSeckillReservationSnapshot`(预占秒杀名额+失败回滚,风格参照现有花材限购 `ProductClass::handleLimitBuy` 的写法)。
+- `app-mall/controllers/OrderController::actionCreateMixOrder`:花束行如果带 `activityType=seckill`,**不信任前端传的价格**,独立回查 Redis 秒杀配置核实活动状态、剩余库存、单人限购,通过后才用秒杀价核价并预占名额;失败/异常时通过既有的 `register_shutdown_function` 一并回滚。
+- `biz-hd/homePageConfig/classes/HomePageDisplayClass::getSectionGoodsPage`:秒杀/团购「更多」列表接口补充返回 `startTime/endTime`。
+
+这样价格闭环是安全的:前端展示的秒杀价只是"提示",真正决定下单金额、是否超出限购/库存的,是服务端独立查一遍 Redis 里的秒杀配置,客户端传的价格不会被信任。
+
+## 已知遗留范围(未改动)
+
+- 「立即购买」单件下单流程(`sel-popup.vue` 的下一步 → `pages/order/buy.vue` → `actionCreateOrder`)没有做同样的服务端核价,如果需要该入口也支持秒杀价,需要再补一次类似改动。按你的描述(加入购物车→合并下单)这次没有动它。
+- 团购专区(`groupBuy`)目前没有改,只做了秒杀。
+
+所有改动已跑过 PHP 语法检查(`php -l`)和 Vue 模板/脚本语法解析,均无报错;已运行 `npm run index` 和 `npm run validate` 刷新并校验本知识库索引。未做任何 `git add/commit`。
+
+
+### 需求指令2:
+「立即购买」单件下单流程(sel-popup.vue 的下一步 → pages/order/buy.vue → actionCreateOrder)没有做同样的服务端核价,请把该入口也支持秒杀价
+
+
+所有改动均已完成,验证通过。总结一下这次的改动:
+
+## 后端 `huahuibao/app-mall/controllers/OrderController.php` — `actionCreateOrder`
+
+完全复用 `actionCreateMixOrder` 已验证过的秒杀核价套路:
+
+1. **不信任前端价格**:当请求带 `activityType=seckill` 时,不再走 `GoodsClass::getFinalPrice`,而是调用 `HomePageModuleClass::getSeckillActiveRow` 从 Redis 独立回查该秒杀活动是否仍有效。
+2. **库存与限购校验**:用 `getSeckillSoldCount` / `getSeckillCustomBoughtCount` 核对秒杀库存和该用户(`$this->customId`,即 `$hd->customId`)的限购数量,超出则 `util::fail`。
+3. **价格覆盖**:校验通过后用 `$seckillRow['price']` 覆盖 `$unitPrice`,再进入原有下单逻辑(运费、红包、创建订单等不变)。
+4. **事务安全**:补上了 `register_shutdown_function`(原来 `actionCreateOrder` 没有)以及 `reserveSeckillPurchase` / `clearSeckillReservationSnapshot` / `rollbackSeckillReservationSnapshot`,确保秒杀名额预占与订单事务的提交/回滚/异常退出保持一致,不会出现"占了名额但没成单"的泄漏。
+
+## 前端
+
+- **`mallApp/src/pages/goods/components/sel-popup.vue`**(`goods/detail.vue` 使用的那个 sel-popup):`nextFn` 在跳转 `/pages/order/buy` 前,若当前商品处于秒杀(`info.activityType === 'seckill'`),会把秒杀价、原价、限购、结束时间重新写回 `goods`(防止选规格时把价格字段带回原价),并在 URL 上带上 `activityType=seckill`。
+- **`mallApp/src/pages/order/buy.vue`**:提交订单时,若 `option.activityType` 或缓存的 `goodsInfo.activityType` 为 `seckill`,在请求体里附加 `activityType: 'seckill'`,交给后端做真正的核价。
+- **`mallApp/src/pages/order/components/app-order-list2.vue`**(buy 页展示商品信息的组件):顺手加了"秒杀"标签和划线原价展示,和购物车/合并结算页的视觉风格保持一致。
+
+已用 `php -l` 和 `acorn` 分别检查了 PHP 与 Vue 组件脚本语法,并重新执行了 `npm run index` / `npm run validate` 刷新知识库索引,均无问题。

+ 390 - 0
任务小笔记/26年/07-13:花掌柜与花卉宝改版/7-团购实现.md

@@ -0,0 +1,390 @@
+@mallApp/src/pages/home/index.vue:47~53 点击里面的商品时,修改为跳转到一个“团购详情”页面,此页面样式如图。
+
+
+### 团购的整体实现介绍
+团购详情.png 只呈现了一个在客户点击团购商品时,展现给客户的团购入口页(即下面的:用户选择 > 红玫瑰99元 ),其它后端及前端的支持没有实现时,是无法实现团购功能的。下面对团购核心功能做个描述,好让 AI 有个实现脉络,然后据此展开把实现计划写得更具体。
+
+
+整个系统的核心其实只有三个对象:
+```
+活动(Activity) 
+    │
+    ├── 商品(Product)
+    │
+    └── 拼团(Group)
+            │
+            ├── 团长(发起人)
+            ├── 成员1
+            ├── 成员2
+            ├── 成员3
+            └── 成员4
+```
+团购活动创建在 @hdApp/src/admin/homePageConfig/groupBuy.vue 页面实现创建。当前,只支持一次只创建一个活动及运行一个活动(活动数据暂存到 redis,接下来要用 mysql 数据表持久化)。当前没有实现保存历史活动,此次实现时,补充按:活动日期时间 为区分id,创建出活动批次。对应下面的商品,如果商品有新增,但活动日期时间没变,就以活动日期时间为前缀,创建新活动商品记录(如果还是同样的商品,但把价格,也要创建新的商品记录,旧的不展示,只展现新的商品)。如果活动日期时间变动了,就会创建新的一批活动。如果活动日期时间重叠,直接取用历史活动,不新建了。
+
+例如:
+```
+七夕鲜花拼团活动
+    │
+    ├── 红玫瑰99元(5人成团)
+    ├── 百合79元(3人成团)
+    └── 向日葵59元(2人成团)
+```
+
+用户选择
+> 红玫瑰99元
+
+点击
+> 我要开团
+
+系统生成:
+```
+GroupID = 9527
+
+商品:红玫瑰
+团长:A
+人数:1/5
+状态:拼团中
+```
+
+随后分享的是:
+```
+https://xxxx/pages/group?id=9527
+```
+
+不是
+```
+product?id=88
+```
+
+---
+
+# 整个流程
+
+## 第一步:创建团
+
+用户A
+```
+商品详情
+
+红玫瑰99元
+
+【我要开团】
+```
+
+点击以后
+
+服务器(后端服务):
+```
+创建Group
+
+group_id=1001
+leader=A
+need_num=5
+current_num=1
+status=拼团中
+```
+
+然后A支付成功。
+
+支付成功以后:
+```
+Group
+1001
+
+人数
+1/5
+```
+
+---
+
+## 第二步:分享
+
+分享卡片:
+```
+A邀请你一起拼团
+
+还差4人成团
+
+立即参与
+```
+
+分享路径
+```javascript
+onShareAppMessage() {
+    return {
+        title:'还差4人成团',
+        path:'/pages/group/detail?id=1001'
+    }
+}
+```
+
+所有人打开都是:
+```
+GroupID=1001
+```
+而不是商品ID。
+
+---
+
+# 第三步:别人进入
+
+例如
+
+B打开
+
+页面请求
+```
+GET
+
+/group/detail?id=1001
+```
+
+服务器返回
+
+```
+Group
+1001
+
+商品:红玫瑰
+
+团长:A
+
+人数:1/5
+
+成员:A
+```
+
+页面展示
+```
+A □ □ □ □  还差4人
+
+【我要参团】
+```
+
+---
+
+# 第四步:B下单
+
+点击
+```
+我要参团
+```
+
+实际上就是
+```
+POST
+
+/order/create
+
+{
+    group_id:1001,
+    product_id:88
+}
+```
+
+注意:
+这里不是创建新团。
+
+而是:
+```
+加入已有团
+```
+
+服务器(后端):
+```
+创建订单
+
+order
+↓
+支付
+↓
+支付成功
+↓
+加入GroupMember
+```
+
+于是
+```
+Group
+1001
+
+人数
+2/5
+```
+
+---
+
+# 第五步:继续分享
+
+B也可以继续分享
+
+分享出去仍然是:
+```
+group?id=1001
+```
+
+不是新的Group。
+
+于是 C、D、E
+
+全部进入:
+```
+1001
+```
+
+人数:
+```
+1/5
+↓
+2/5
+↓
+3/5
+↓
+4/5
+↓
+5/5
+```
+
+---
+
+# 第六步:满团
+
+支付成功以后
+```
+current_num++
+
+if(current_num==need_num){
+	status=SUCCESS
+}
+```
+
+例如
+```
+need_num=5
+current_num=5
+```
+
+更新
+```
+status=SUCCESS
+```
+
+然后:
+
+通知所有人:
+```
+🎉 拼团成功
+
+等待商家发货
+```
+
+# 其它:不满团情况处理
+
+@hdApp/src/admin/homePageConfig/groupBuyGoodsEdit.vue 每个团购商品有分是否支持虚拟成团,以下按支持与不技术情况进行展开说明:
+1. 支持
+	开启了虚拟成团的,会填写一个虚拟成团时间(要搞循环任务),每隔一段时间时,触发一闪任务,把不满团的自动变动为满团让其团购成功。
+   
+2. 不支持
+	不支持虚拟成团的,则会在活动到期时,把对应开团的(或参团的)的订单标记为失败。
+
+# 后端流程
+
+创建团:
+```
+创建订单
+↓
+支付
+↓
+创建Group
+↓
+加入GroupMember
+↓
+人数=1
+```
+
+参团:
+```
+创建订单
+↓
+支付
+↓
+加入GroupMember
+↓
+人数+1
+↓
+检查是否满团
+↓
+满团
+↓
+通知(通知可以先不实现)
+```
+
+---
+
+# 前端页面
+
+页面脉络如下 
+```
+活动详情
+@mallApp/src/pages/home/index.vue:47~53 活动呈现
+```
+
+点击某商品,展示(按团购详情.png展示):
+```
+玫瑰
+
+99元
+
+5人成团
+
+【去开团】
+```
+↓
+```
+团购的结果 ----  现在只在订单中呈现情况
+```
+
+拼团情况展示:
+```
+已完成
+
+失败
+```
+---
+
+# 微信分享实现
+
+关键在于分享路径带上 `group_id`:
+
+```javascript
+onShareAppMessage() {
+    return {
+        title: `还差${this.group.need_num - this.group.current_num}人成团`,
+        imageUrl: this.group.product.cover,
+        path: `/pages/group/detail?id=${this.group.id}`
+    }
+}
+```
+
+好友打开:
+```
+group/detail?id=1001
+```
+
+即可进入同一个团。
+
+---
+
+# 支持增加的功能
+
+这是一个商用的鲜花商城,建议一开始就预留以下能力,而不要只做最基础的拼团:
+1. **自动退款**:拼团失败后自动触发退款流程(项目中有退款实现,请找出直接用上)。
+2. **一人成多团**:同一用户可以同时发起多个不同团,或参与多个团(处理好,同一个团不重叠同一客户)。
+3. **虚拟成团**:支持机器人补位(如有业务需求,需明确标识并遵守相关平台规则)。
+4. **阶梯拼团**:3 人团、5 人团、10 人团对应不同价格。
+5. **团排行榜**:展示热门拼团、即将满团(例如还差 1 人),提高转化率。
+6. **库存锁定策略**:建议在用户支付成功后占用库存,而不是开团时占用;若业务要求,也可采用预占库存并设置超时释放机制。
+
+---
+
+建议采用"拼多多式拼团"架构:订单(Order) 与 拼团(Group) 解耦,订单只记录自己属于哪个 `group_id`,而拼团人数完全由 `group_member` 维护。** 这种设计扩展性最好,后续增加**团长免单、阶梯团、抽奖团、老带新团、万人团**等玩法时,数据库结构基本无需调整,非常适合商用项目。

+ 395 - 0
任务小笔记/26年/07-13:花掌柜与花卉宝改版/7-购物车与结算页的商品去重/1-探索购物车合并与结算去重逻辑.md

@@ -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 同步调整。

+ 90 - 0
任务小笔记/26年/07-13:花掌柜与花卉宝改版/7-购物车与结算页的商品去重/主干.md

@@ -0,0 +1,90 @@
+### 需求指令:
+项目:mallApp
+问题:从 mallApp/src/pages/home/cart.vue  点击“去结算”跳转到 mallApp/src/pages/billing/affirmMix.vue 时,商品合并时出现问题。实际具体的情况是:[{id: 2116, goodsId: 2116, specGoodsId: "2121", property: 0, classId: 0, name: "多规格花束",…},…]
+0: {id: 2116, goodsId: 2116, specGoodsId: "2121", property: 0, classId: 0, name: "多规格花束",…}
+activityEndTime: 1784908800
+activityType: "seckill"
+bigCount: 1
+bigNum: 9999
+bigPrice: 5
+bigUnit: "份"
+classId: 0
+cover: "http://img.theflorist.cn/uploads/1101/37058/202607/14/f5920eb66a9bf6ca5d0737ad26460500.jpg?x-oss-process=image/resize,m_fill,h_230,w_230"
+freightType: "1"
+goodsId: 2116
+id: 2116
+itemName: "多规格花束"
+name: "多规格花束"
+originPrice: 36
+price: 5
+priceType: "1"
+property: 0
+ratio: 1
+smallCount: 0
+smallNum: 0
+smallPrice: 5
+smallUnit: "份"
+specGoodsId: "2121"
+specName: "规2"
+stock: 9999
+weight: "1"
+1: {id: 2116, goodsId: 2116, specGoodsId: "2120", property: 0, classId: 0, name: "多规格花束",…}
+activityEndTime: 1784908800
+activityType: "seckill"
+bigCount: 1
+bigNum: 9999
+bigPrice: 3
+bigUnit: "份"
+classId: 0
+cover: "http://img.theflorist.cn/uploads/1101/37058/202607/14/0f2d0439bee901ba13a9ca01757d5f57.jpg?x-oss-process=image/resize,m_fill,h_230,w_230"
+freightType: "1"
+goodsId: 2116
+id: 2116
+itemName: "多规格花束"
+name: "多规格花束"
+originPrice: 16
+price: 3
+priceType: "1"
+property: 0
+ratio: 1
+smallCount: 0
+smallNum: 0
+smallPrice: 3
+smallUnit: "份"
+specGoodsId: "2120"
+specName: "规1"
+stock: 9999
+weight: "1"
+2: {id: "2116", goodsId: "2116", specGoodsId: "2120", property: 0, classId: 0, name: "多规格花束",…}
+bigCount: 1
+bigNum: 9999
+bigPrice: 16
+bigUnit: "份"
+classId: 0
+cover: "http://img.theflorist.cn/uploads/1101/37058/202607/14/f5920eb66a9bf6ca5d0737ad26460500.jpg?x-oss-process=image/resize,m_fill,h_230,w_230"
+freightType: "1"
+goodsId: "2116"
+id: "2116"
+itemName: "多规格花束"
+name: "多规格花束"
+price: 16
+priceType: "1"
+property: 0
+ratio: 1
+smallCount: 0
+smallNum: 0
+smallPrice: 16
+smallUnit: "份"
+specGoodsId: "2120"
+specName: "规1"
+stock: 9999
+weight: "1"
+
+第2个与第3 个商品数据,在 mallApp/src/pages/billing/affirmMix.vue 被认为是同一种商品,而实际是第2个是秒杀商品(价格为3元),第3个是普通商品(价格为16),在 mallApp/src/pages/billing/affirmMix.vue 下组合商品时出现错误,请结合后端订单结算的逻辑及 mallApp/src/pages/home/cart.vue 判断唯一逻辑,修复这个问题,让最终结果能正常下单
+
+
+#### AI 执行
+1. 执行子任务  [[1-探索购物车合并与结算去重逻辑]]
+   
+#### AI 最终的计划输出
+[[修复购物车秒杀商品合并]]

+ 116 - 0
任务小笔记/26年/07-13:花掌柜与花卉宝改版/7-购物车与结算页的商品去重/修复购物车秒杀商品合并.md

@@ -0,0 +1,116 @@
+---
+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`,能把秒杀行和普通行分开):
+
+```307:322: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);
+    }
+    ...
+}
+```
+
+- **勾选态 key / 合并去重 key** 用的是 `getSelectRowKey`(花束分支**没有**带 `activityType`,是本次 bug 的根因):
+
+```261:272: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}`;
+    }
+    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.vue` 的 `onShow`(第 585-604 行左右),在拿到 `selectList` 后再次用同一个 key 做本地合并展示:
+
+```585:603: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;
+    ...
+```
+
+因为秒杀行(`specGoodsId=2120, activityType='seckill', price=3`)和普通行(`specGoodsId=2120, activityType='', price=16`)算出的 `getSelectRowKey` 都是 `0_{goodsId}_2120`,两行被合并成一行:数量相加,但价格/`activityType` 只保留了先出现的那一行,导致最终提交给后端 `POST /order/create-mix-order` 的 `product` 数组里少了一行、且这一行的活动标记可能是错的。
+
+后端 `app-mall/controllers/OrderController.php` 的 `actionCreateMixOrder`(958 行起)是按 `product` 数组逐行独立处理和核价的(1110-1256 行),并不会对 `goodsId+specGoodsId` 做跨行去重,秒杀单独通过 `activityType === 'seckill'` 判断走 Redis 核价(1198-1239 行)。也就是说**后端本来就支持同一规格同时存在秒杀行和普通行**,问题完全在前端合并 key 缺字段。
+
+## 修复方案
+
+只需让 `getSelectRowKey` 的花束分支和 `_selectRowMatches` 保持一致,把 `activityType` 纳入 key:
+
+文件:[front-end/mallApp/src/mixins/cgProduct.js](front-end/mallApp/src/mixins/cgProduct.js)
+
+```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 行 `getItemKey` → `checkedMap` 勾选态 key,不再把秒杀行和普通行的勾选状态混在一起。
+- `cgProduct.js` 的 `_mergeDuplicateSelectRows` / `_syncMergedSelectListIfNeeded`,Vuex 里的 `selectList` 合并不再误并。
+- `affirmMix.vue` 的 `onShow` 合并循环(585-603 行),结算页展示与提交给 `createMixOrder` 的 `product` 数组会分别保留秒杀行和普通行各自的价格、数量、`activityType`。
+
+不需要改动后端和 `affirmGhs.vue`(该页仅处理花材 `property=1`,本身不带活动字段,`_selectRowMatches` 分支不涉及秒杀问题)。
+
+## 验证
+
+- 购物车里对同一花束同一规格分别以“秒杀价”和“普通价”各加入购物车,确认两行分别显示、可分别勾选/编辑数量。
+- 两行都勾选后点击「去结算」,确认 `affirmMix.vue` 展示两行(各自价格、各自「秒杀」标签),数量互不影响。
+- 提交下单,确认请求体 `product` 数组包含两条独立记录(一条带 `activityType: 'seckill'`,一条不带),且后端下单成功、金额正确。

+ 31 - 29
任务小笔记/26年/07-13:花掌柜与花卉宝改版/问题.md

@@ -23,32 +23,34 @@
 2. 首页展示调整--展示方式按产品原先定义进行调整
 	1. hdApp/src/admin/homePageConfig/seckill.vue
 	2. hdApp/src/admin/homePageConfig/groupBuy.vue
-	   
-	   3. hdApp/src/admin/homePageConfig/hot.vue
-	   4. 
-
-	   4. hdApp/src/admin/homePageConfig/new.vue
-	   5. hdApp/src/admin/homePageConfig/pullGoods.vue
-	      
-	      
-
- hdApp/src/admin/homePageConfig/hot.vue
- **删除"商品展示数量"输入框及其提示文案**
-  **删除"排列规则"整块(`cols-section`)**,包括对应的 `onLayoutColsChange` 方法与 scss 样式
-  
-  
-项目:hdApp
-页面:hdApp/src/admin/homePageConfig/hot.vue 、hdApp/src/admin/homePageConfig/new.vue 与 hdApp/src/admin/homePageConfig/pullGoods.vue
-需求:
-1. 隐藏商品展示数量,按照下面2的规则进行自动设置展示数量
-2. 把 hdApp/src/admin/homePageConfig/hot.vue  页面下的“排列规则”删除掉(或隐藏),使用如下隐藏规则:
-	1. 当只添加了一个商品时,按1排1列来展示
-	2. 当只添加了两个商品时,按1排2列来展示
-	3. 当添加了三个及以上商品时,按1排3列来展示且把展示数量设置为3个。当添加商品数量超出3个时,那么mallApp下的店铺首页的热门推荐的右上角要显示“更多”链接。
-
-
- - hdApp/src/admin/homePageConfig/new.vue 页面也做以上同样的处理。
- - hdApp/src/admin/homePageConfig/pullGoods.vue 页面下的商品展示数量删除掉(或隐藏),“排列规则”删除掉(或隐藏),使用如下隐藏规则:
-	   1. 当只添加了一个商品时,按1排1列来展示
-	2. 当只添加了两个商品时,按1排2列来展示
-	3. 当添加了三个及以上商品时,还是按1排2列来展示(且把展示数量设置为全部x),且在mallApp下的店铺首页的下拉商品中把所有的商品都展示。由于全部商品都显示了,右上角没有“更多”链接。
+	3. hdApp/src/admin/homePageConfig/hot.vue
+	4. hdApp/src/admin/homePageConfig/new.vue
+	5. hdApp/src/admin/homePageConfig/pullGoods.vue
+
+3. 同一商品:如何区分是秒杀与原价,秒杀时效过了(就变为原价商品)。设置秒杀商品的价格不能高于等于原价。
+4. 公告来自公告设置的店铺公告
+5.  消息中心与客服务聊天--向产品区分消息中心与客服页面
+6. 首页 baner 的跳转链接有问题
+7. 团购详情页--及其流程走完
+
+#### 秒杀商品场景
+1. 同一商品:如何区分是秒杀与原商品---- 从下单到最后订单查询
+	- 如果实现了以上的,那是否可以有专门查询秒杀商品入口(或者查询历史订单中哪些是秒杀商品)
+	- 如果某一商品在加入购物车时是秒杀商品,但后面它的秒杀属性失效了要怎么处理?-- 1.直接不让下单(让其去除它)2.做提示然后按普通商品进行下单
+2. 要实现区分是秒杀与原商品,要在创建秒杀商品时,就记录好标识及必要信息 --- 让 AI 来结合项目选择最佳实现方案(直接从表设计到实现,全部一起来)
+
+
+唯一识别id:  时间 + 商品id(+多规格) + 价格  -------->  创建出秒杀商品id
+- 多规格,即几种规格,就要多几种秒杀商品id
+
+   
+#### 团购商品场景
+开篇提议:团购商品的实现细节会与“秒杀”有很类同的场景,可以结合秒杀一起考虑与覆盖场景
+
+1. 团购商品是不是只会出现团购专区,而不会与原商品不好区分?
+
+
+新增秒杀商品,如果是多规格的,为什么名称会变掉
+mallApp 店铺首页如何能点击秒杀商品时,进入到商品详情还是呈现秒杀商品信息
+
+

+ 2 - 0
开发工具/PHPSTORM 快捷键.md

@@ -16,6 +16,8 @@ sensitive: false
 | ----------------------- | ------------- | --------------------------- | ----- |
 | Ctrl + Alt + ←/→        | ⌘⌥←, ⌘⌥→      | 返回 / 前进到上次导航操作              | ★★★★☆ |
 | Ctrl+Shift+Backspace    | ⌘⇧Backspace   | 定位到最后编辑处                    | ★★★★★ |
+|                         | ⌘ + [         | 查看代码时实现“后退”                 | ★★★★★ |
+|                         | ⌘ + ]         | 查看代码时实现“前进”                 | ★★★★★ |
 | Ctrl + B / Ctrl + Click | ⌘B or ⌘ Click | 跳转到函数的声明处                   |       |
 | Ctrl + H                | ⌃H            | 浏览选定类的层次结构                  |       |
 | Ctrl + U                | ⌘U            | 转到 super-method/super-class |       |