# 购物车 — 前端技术方案（C 端 · shop-app）

> **依据：** 《购物车功能需求.md》v1.0、《购物车技术方案.md》v1.0（仅作接口对照，**本文档独立维护**）  
> **关联：** 《商品详情内页前端技术方案》、《确认订单页（多商品）前端技术方案》v1.0、《店铺主页前端技术方案》  
> **范围：** C 端 **购物车 Tab 列表**、详情 **加购**、勾选/改量/删除/清理失效、**同店去结算**；确认订单 **多商品页** 见另册，本模块负责跳转与 prepare 门禁。  
> **实现状态：** Tab 页、组件、API、`checkout-cart` 跳转链路 **已落地**。

---

## 1. 技术栈与约定

| 项 | 说明 |
|----|------|
| 框架 | uni-app **Vue 3** + **uview-plus** |
| 请求 | `@/utils/request`；列表 `AjaxResult.data` 为 **`items[]` + `checkedSummary`** |
| 鉴权 | **全部** `/api/cart/**` 须会员 Token |
| 页面位置 | **主包 Tab** `pages/cart/index`（与 `pages.json` tabBar 一致） |
| 规格 v1 | 统一规格；加购 `specKey` 不传，`specText` 由详情拼接或「默认」 |
| 防抖 | 改量/勾选/去结算使用 `useActionGuard` |
| 参考 | `subpackage/order/checkout-cart.vue`（去结算下游）、`pages/mine` 未登录态 |

---

## 2. 页面与路由

| 页面 | 需求代号 | 路径 | 入口 |
|------|----------|------|------|
| 购物车列表 | **A** | `pages/cart/index` | 底部 Tab「购物车」；详情加购后可自行切 Tab |
| 确认订单（多商品） | — | `subpackage/order/checkout-cart` | 本页 **去结算**（另册维护） |

**未登录：** 空态 +「去登录」→ `PAGE_LOGIN`（CT0）

**路径常量：**

| 常量 | 路径 |
|------|------|
| `PAGE_CART` | `/pages/cart/index` |
| `PAGE_CHECKOUT_CART` | `/subpackage/order/checkout-cart` |

**预加载：** `pages.json` → `preloadRule` 为 `pages/cart/index` 预加载 `pkg-order`。

---

## 3. 文件清单

| 类型 | 路径 | 说明 |
|------|------|------|
| Tab 页 | `shop-app/pages/cart/index.vue` | 分组列表、工具栏、底栏全选/合计/去结算 |
| 店组 | `shop-app/components/cart/CartShopGroup.vue` | 店头全选、进店、休息中标签 |
| 商品行 | `shop-app/components/cart/CartItemRow.vue` | 勾选、数量、小计、失效标签、删除、进详情 |
| 购物车 API | `shop-app/api/cart.js` | `/api/cart/**` |
| 展示 | `shop-app/utils/cartDisplay.js` | 扁平 `items` 按店分组、失效文案、勾选统计 |
| 结算导航 | `shop-app/utils/checkoutNav.js` | `goCartCheckout(cartItemIds)` |
| 规格展示 | `shop-app/utils/cartSpec.js` | `parseCartSpecText`（`§` 分隔） |
| 常量 | `shop-app/constants/cart.js` | 失效类型、空态、跨店提示 |
| 常量 | `shop-app/constants/checkout.js` | `CART_CHECKOUT_IDS_KEY` |

**已改上游：**

| 文件 | 改动 |
|------|------|
| `subpackage/goods/detail.vue` | `doAddCart` → `POST /api/cart/items` |

---

## 4. 接口封装（`api/cart.js`）

**基路径：** `/api/cart`

| 方法 | HTTP | 路径 | 场景 |
|------|------|------|------|
| `getCartList` | GET | `/api/cart` | Tab onShow 拉列表 |
| `addCartItem` | POST | `/api/cart/items` | 详情加购 |
| `updateCartQuantity` | PUT | `/api/cart/items/{id}/quantity` | +/- 改量 |
| `updateCartChecked` | PUT | `/api/cart/items/checked` | 行/店/全选 |
| `removeCartItem` | DELETE | `/api/cart/items/{id}` | 单行删除 |
| `removeCartItems` | DELETE | `/api/cart/items` | 删除选中 |
| `cleanInvalidCart` | DELETE | `/api/cart/invalid` | 清理失效 |
| `prepareCartCheckout` | POST | `/api/cart/checkout/prepare` | 去结算门禁 |

### 4.1 列表响应 `CartListVO`

| 字段 | 说明 |
|------|------|
| `items[]` | 扁平购物车行（含 `shopId/shopName/shopStatus`）；排序 `cart_item_id DESC` |
| `checkedSummary` | 已勾选且 `purchasable` 行合计 |

前端 **`mapCartList`** 将 `items[]` **按 `shopId` 分组** 为 UI 用 `groups[]`（后端不分组）。

### 4.2 加购 Body（详情）

| 字段 | 说明 |
|------|------|
| goodsId | 必填 |
| quantity | 必填 |
| specText | 规格快照；无则「默认」 |

### 4.3 批量勾选 Body

```json
{
  "items": [
    { "cartItemId": 1, "checked": true }
  ]
}
```

### 4.4 去结算 Body

```json
{ "cartItemIds": [1, 2] }
```

成功后再跳转确认订单页；**不在购物车页缓存 prepare 结果**（确认页走 `POST /api/checkout/preview`）。

---

## 5. 列表页结构（`pages/cart/index.vue`）

```text
购物车 Tab
├── 未登录：空态 + 去登录
├── 加载中 / 加载失败（重试）
├── 空购物车：「购物车是空的」+ 去逛逛（首页 Tab）
└── 有数据
    ├── 顶栏工具条
    │   ├── 清理失效商品（有失效行时显示）
    │   └── 删除选中（有勾选时显示）
    ├── scroll-view · CartShopGroup × N
    └── 底栏
        ├── 全选（仅可购行）
        ├── 合计 ¥checkedSummary.checkedAmountText
        └── 去结算(N)
```

---

## 6. 列表页逻辑

```text
onShow
  → 无 Token：访客空态
  → 有 Token：GET /api/cart → mapCartList（items 按店分组）

店组头勾选
  → 该店全部 purchasable 行 checked 同步 → PUT checked → 刷新

行勾选 / 底栏全选
  → PUT checked → 刷新（含 checkedSummary）

数量 +/-
  → PUT quantity → 刷新（库存不足后端 400）

删除单行
  → 二次确认 → DELETE 单行 → 刷新

删除选中
  → 二次确认（展示件数）→ DELETE 批量 → 刷新

清理失效
  → countInvalidCartItems → 二次确认（展示件数）→ DELETE /invalid → Toast removedCount

去结算
  → 无勾选可购行：提示
  → 跨店勾选：Toast「请选择同一店铺的商品结算」
  → POST prepare 成功 → goCartCheckout(ids)
  → prepare 失败：request 已 Toast，不跳转
```

| 需求规则 | 实现 |
|----------|------|
| CT2 按店分组 | `groupCartItemsByShop` |
| 失效不可勾选 | `purchasable=false` 置灰勾选 |
| CT-S1 不跨店结算 | `getCheckedShopIds.size > 1` 拦截 |
| CT7 清理失效 | 顶栏按钮 + 件数确认 |
| 缺货/下架进详情 | 点击主图/名称 `goGoodsDetail` |
| 进店 | 点击店名 `goShopHome` |
| 行小计 | `subtotalText` 展示 |

### 6.1 底栏合计

使用接口 **`checkedSummary`**（仅 **已勾选且可购** 行），与后端口径一致。

### 6.2 失效标签

`invalidType` → `getCartInvalidLabel`（`cartDisplay.js`）：

| invalidType | 默认文案 |
|-------------|----------|
| OUT_OF_STOCK | 缺货 |
| OFF_SHELF | 已下架 |
| SHOP_CLOSED | 休息中 |
| CATEGORY_HIDDEN | 不可购买 |
| GOODS_DELETED | 已失效 |
| SHOP_DELETED | 店铺失效 |

优先使用后端 `invalidMsg`。

---

## 7. 组件说明

### 7.1 `CartShopGroup.vue`

- 店头：勾选（仅当有可购行）、店名、停业「休息中」、进店箭头
- 子组件 `CartItemRow` 列表

### 7.2 `CartItemRow.vue`

- 可购：勾选、+/- 改量、删除
- 失效：勾选禁用、数量只读、失效标签、仍可删、可点进详情
- 展示：主图、名称、规格列表、单价、**小计**

---

## 8. 数据映射（`cartDisplay.js`）

| 函数 | 用途 |
|------|------|
| `mapCartList` | `items[]` → `groups[]` + `checkedSummary` |
| `groupCartItemsByShop` | 内部：按 `shopId` 分组，保持行序 |
| `hasInvalidCartItems` | 是否显示「清理失效」 |
| `countInvalidCartItems` | 清理前展示件数 |
| `getCheckedPurchasableIds` | 去结算 / prepare 入参 |
| `getCheckedShopIds` | 跨店校验 |
| `getAllPurchasableItems` | 全选逻辑 |
| `getCartInvalidLabel` | 失效标签文案 |

**行模型要点：** `displayPic`、`priceText`、`subtotalText`、`specList`、`invalidLabel`、`purchasable`

---

## 9. 与确认订单（多商品）边界

| 项 | 购物车侧 |
|----|----------|
| prepare | `POST /api/cart/checkout/prepare` 门禁（四条件、同店） |
| 跳转 | `goCartCheckout` → `checkout-cart?cartItemIds=` + storage 兜底 |
| 预览/提交 | **不在本页**；见《确认订单页（多商品）前端技术方案》 |
| 支付成功删行 | **后端** 处理；返回 Tab 刷新可见 |

```text
去结算
  → prepareCartCheckout(ids)
  → goCartCheckout(ids)
       → setStorage CART_CHECKOUT_IDS_KEY
       → navigateTo PAGE_CHECKOUT_CART
```

---

## 10. 联调检查清单

- [ ] 未登录 Tab 仅引导登录
- [ ] 详情加购后 Tab 刷新可见，同规格数量累加
- [ ] 列表 `items` 正确按店分组展示
- [ ] 失效行不可勾选、标签与进店/进详情可用
- [ ] 改量超过库存 → 400「库存不足」
- [ ] 跨店勾选去结算 → 前端拦截文案
- [ ] 同店 prepare 成功 → 跳转 `checkout-cart`
- [ ] 清理失效展示件数；删除选中二次确认
- [ ] 停业店可展示，prepare 拦截停业

---

## 11. 非本期（前端不实现）

| 项 | 说明 |
|------|------|
| 列表/搜索快捷加购 | 仅详情加购 |
| 跨店一键多单 | CT-S1 不支持 |
| 优惠券、凑单 | 未要求 |
| 失效自动删除 | 用户触发「清理失效」 |

---

## 12. 修订记录

| 版本 | 说明 |
|------|------|
| **v1.1** | 对齐需求 v1.0：**items 前端按店分组**、清理失效件数确认、行小计、去结算跳转 `checkout-cart`；更新与多商品确认页边界 |
| **v1.0** | 首版：Tab 购物车、详情加购、同店 prepare |

---

*文档版本：v1.1 · 不修改《购物车技术方案.md》（后端方案）。*
