# 商品分类 — 前端技术方案（C 端 · shop-app）

> **依据：** 《商品分类功能需求.md》v1.0.1、《商品分类技术方案.md》v1.0  
> **关联：** 《商城首页功能需求.md》v1.0.2、《商城首页技术方案.md》v1.1  
> **范围：** 消费者 APP **`shop-app`** 全部分类页（A）、一级分类商品页（B）、二级商品列表页（C）；**不** 改后端、**不** 实现搜索执行与商品详情。  
> **实现状态：** 页面与 API 封装已落地，待与 `/api/category/**` 联调。

---

## 1. 技术栈与约定

| 项 | 说明 |
|----|------|
| 框架 | uni-app **Vue 3** + **uview-plus** |
| 请求 | `@/utils/request`（与 ruoyi-ui 响应 `code`/`msg`/`data` 一致；商品列表为 `rows`/`total`） |
| 鉴权 | 分类三接口 **`header: { isToken: false }`**（`@Anonymous`） |
| 开发代理 | `config/index.js` 中 `H5_USE_PROXY=true` 时走 `/dev-api`（见 `vite.config.js`） |
| 参考页面 | `pages/index/index.vue`（商城首页样式）、`components/mall/*`（搜索/商品卡片） |

---

## 2. 目录与分包

```text
shop-app/
├── pages/                    # 主包（Tab + 登录）
│   ├── index/index.vue
│   ├── category/index.vue    # 分类 Tab
│   ├── cart/index.vue
│   ├── mine/index.vue
│   └── login/index.vue
└── subpackage/               # 分包页面
    ├── category/level1.vue、goods-list.vue
    ├── goods/detail.vue、reviews.vue
    └── search/index.vue、result.vue
```

路径常量见 `utils/pageRoute.js`；`pages.json` 中 `subPackages[].root` 与上表 `subpackage/*` 一致。

---

## 3. 页面与路由

| 页面 | 需求代号 | 路径 | 入口 |
|------|----------|------|------|
| 全部分类（Tab） | **A** | `pages/category/index` | 底部 Tab「分类」；首页「更多」`switchTab` |
| 一级分类商品 | **B** | `subpackage/category/level1` | 首页点击一级类目 `navigateTo` + `level1Id` |
| 二级商品列表 | **C** | `subpackage/category/goods-list` | A 页点击二级 `navigateTo` + `categoryId` |

**`pages.json` 注册：**

```text
pages/category/index        → navigationBarTitleText: 分类
subpackage/category/level1       → 动态 setNavigationBarTitle(level1Name)
subpackage/category/goods-list   → 动态 setNavigationBarTitle(level2Name)
```

**路由参数：**

| 页面 | Query | 说明 |
|------|-------|------|
| B | `level1Id`, `level1Name` | 一级上下文 |
| C | `categoryId`, `level1Id`, `level1Name`, `level2Name` | 二级列表 + 面包屑展示 |

---

## 4. 文件清单

| 类型 | 路径 | 说明 |
|------|------|------|
| A 页 | `shop-app/pages/category/index.vue` | 左一级 / 右二级 |
| B 页 | `shop-app/subpackage/category/level1.vue` | 搜索 + Tab + 列表 |
| C 页 | `shop-app/subpackage/category/goods-list.vue` | 搜索 + 面包屑 + 列表 |
| 列表块 | `shop-app/components/category/GoodsListBlock.vue` | 排序 + 分页列表（B/C 共用） |
| 搜索入口 | `shop-app/components/mall/SearchEntry.vue` | 占位「搜索兽药、饲料、店铺」 |
| 商品网格 | `shop-app/components/mall/GoodsGrid.vue` | 双列卡片 |
| 排序条 | `shop-app/components/mall/SortToolbar.vue` | 四档 sortBy |
| API | `shop-app/api/category.js` | tree / level2-tabs / goods |
| 常量 | `shop-app/constants/categorySort.js` | `DEFAULT_SORT_BY`、`SORT_OPTIONS` |
| 映射 | `shop-app/utils/categoryDisplay.js` | 分类树、Tab、占位图、热门标 |
| 映射 | `shop-app/utils/goodsDisplay.js` | 商品卡片（与首页热销字段一致） |

---

## 5. 接口封装

**模块：** `shop-app/api/category.js`

| 方法 | HTTP | 路径 | 响应 |
|------|------|------|------|
| `getCategoryTree()` | GET | `/api/category/tree` | `AjaxResult` → `data: CategoryVisibleVO[]` |
| `getLevel2Tabs(level1Id)` | GET | `/api/category/{level1Id}/level2-tabs` | `data: CategoryVisibleChildVO[]` |
| `getCategoryGoods(params)` | GET | `/api/category/goods` | `TableDataInfo` → `rows`, `total` |

**`getCategoryGoods` Query：**

| 参数 | 默认 | 说明 |
|------|------|------|
| categoryId | 必填 | 平台 **二级** ID |
| sortBy | `sales_desc` | `sales_desc` / `sales_asc` / `price_desc` / `price_asc` |
| pageNum | 1 | 页码 |
| pageSize | 10 | 每页条数（组件可配） |

**错误处理：** 请求 `silent: true`；失败时列表区空态，**不** Toast 刷屏（对齐 GC14）。

---

## 6. 页面结构

### 6.1 A — 全部分类 `pages/category/index`

```text
全部分类（Tab）
├── SearchEntry（§3 搜索占位）
├── 加载中 / 空态（失败文案「分类加载失败」）
└── 左右分栏
    ├── 左 scroll-y：一级列表（选中高亮 + 左侧绿条）
    └── 右 scroll-y：二级宫格（图 + 名 + 可选「热」标）
        └── 点击二级 → navigateTo C 页
```

| 行为 | 实现 |
|------|------|
| 数据 | `onShow` → `GET /tree` |
| 默认一级 | `selectedIndex = 0`（排序已由后端保证） |
| 切换一级 | 仅刷新右侧 `children`，不跳转 |
| 空树 | `u-empty`「暂无分类」 |

### 6.2 B — 一级分类商品 `subpackage/category/level1`

```text
一级分类商品页
├── SearchEntry
├── 一级标题（level1Name）
├── 二级 Tab（scroll-x，失败/空态独立）
└── GoodsListBlock（当前 Tab 的 categoryId）
```

| 行为 | 实现 |
|------|------|
| 数据 | `onShow` → `GET /{level1Id}/level2-tabs` |
| 默认 Tab | 第一个二级 |
| 切换 Tab（C1） | `scrollTopKey++` → 子组件 watch 后 **列表回顶** 并 `reload` |
| 排序 | Tab 切换 **保留** `sortBy`（父组件 state 不变） |
| 商品点击 | 暂 Toast「商品详情开发中」 |

### 6.3 C — 二级列表 `subpackage/category/goods-list`

```text
二级商品列表页
├── SearchEntry
├── 面包屑：level1Name > level2Name
└── GoodsListBlock（categoryId）
```

| 行为 | 实现 |
|------|------|
| 无 Tab | 仅展示当前二级商品 |
| 返回 | 系统返回栈 → A 页 |

### 6.4 商品列表块 `GoodsListBlock`（B/C 共用 §7）

```text
GoodsListBlock
├── SortToolbar（四档排序）
└── scroll-view
    ├── GoodsGrid（双列）
    ├── 空态「该分类下暂无商品」
    └── 底部「加载中 / 没有更多了」
```

| 项 | 实现 |
|----|------|
| 默认排序 | `sales_desc`（GC9） |
| 分页 | 上拉 `scrolltolower` 加载下一页 |
| 卡片字段 | 主图、名称、售价、店铺名（GC10） |
| 图片 | `resolveFileUrl`；无图 `/static/logo.png` |

---

## 7. 与商城首页协作

| 首页操作 | 前端跳转 |
|----------|----------|
| 点击一级类目 | `navigateTo` `/subpackage/category/level1?level1Id=&level1Name=` |
| 点击「更多」 | `switchTab` `/pages/category/index` |

**勿混用接口：** 首页横向导航用 `/api/home/categories`（仅一级）；分类模块用 `/api/category/**`。

---

## 8. 业务规则映射（前端）

| 规则 | 前端实现 |
|------|----------|
| GC1 | 仅消费 `/api/category` 平台树 |
| GC3 / GC-S2 | `SearchEntry` 固定占位文案 |
| GC4 | A → C 跳转带 `categoryId` |
| GC5 / C1 | B Tab + `scrollTopKey` 回顶重载 |
| GC5a | C 面包屑 `level1 > level2` |
| GC6 | 依赖后端 Facade 过滤空壳一级 |
| GC9 | `SortToolbar` 四档 `sortBy` |
| GC10 | `GoodsGrid` 字段 |
| GC12 / GC13 | 不做可购校验；列表不展示库存/销量 |
| GC14 | tree / tabs / goods **独立加载**与空态 |
| GC15 | 无编辑能力 |

---

## 9. 联调检查清单

| # | 项 |
|---|-----|
| 1 | `BASE_API` / H5 代理可访问 `/api/category/tree` |
| 2 | A 页左列一级、右列二级与后台 `show_flag` 一致 |
| 3 | B 页 Tab 数量与 `level2-tabs` 一致，切换后 `goods` 的 `categoryId` 变化 |
| 4 | 排序切换后 `sortBy`  query 正确 |
| 5 | 分页：`total` 大于 `pageSize` 时可上拉加载 |
| 6 | 首页一级 → B、首页更多 → A、A 二级 → C 链路通畅 |

---

## 10. 待建设（非本方案）

| 项 | 说明 |
|----|------|
| 搜索输入页 / 结果 | GC-S1 |
| 商品详情 | 列表点击跳转 |
| 深度链接校验 | 不可见分类友好提示 |

---

## 11. 修订记录

| 版本 | 说明 |
|------|------|
| **v1.0** | 首版：A/B/C 三页、公共组件、API 封装；对齐功能需求 v1.0.1、后端技术方案 v1.0 |

---

*文档版本：v1.0 · 工程目录 `shop-app` · 关联《商品分类功能需求.md》v1.0.1、《商品分类技术方案.md》v1.0*
