# 店铺主页 — 前端技术方案（C 端 · shop-app）

> **依据：** 《店铺主页功能需求.md》v1.0、《店铺主页技术方案.md》v1.0（仅作接口对照，**本文档独立维护**）  
> **关联：** 《搜索页前端技术方案》、《商品分类前端技术方案》、《商品详情内页前端技术方案》  
> **范围：** C 端 **店铺介绍、关注、店内分类浏览、店内搜索、进商品详情**；**不** 改后端、**不** 实现全站搜索/购物车/我的关注列表页。  
> **实现状态：** 页面与 API 封装已落地，待与 `/api/shop/**` 联调。

---

## 1. 技术栈与约定

| 项 | 说明 |
|----|------|
| 框架 | uni-app **Vue 3** + **uview-plus** |
| 请求 | `@/utils/request`；读接口 `header: { isToken: false }`；关注写接口 **须 Token** |
| 商品列表响应 | **`TableDataInfo`**：`rows` + `total`（与分类/搜索一致） |
| 分类 VO | 与平台分类相同 `CategoryVisibleVO`；商品筛选参数名为 **`shopCategoryId`**（二级 ID） |
| 排序 | 后端固定 **销量降序**；前端 **不提供** sortBy Tab |
| 虚拟 Tab | 一级导航首项 **「全部商品」** 由前端常量注入，不调分类接口 |
| 参考组件 | `GoodsGrid`、`category/level1` Tab 样式、`search/index` 搜索栏 |

---

## 2. 页面与路由

| 页面 | 需求代号 | 路径 | 入口 |
|------|----------|------|------|
| 店铺主页 | **A** | `subpackage/shop/index` | 搜索页店铺 Tab、详情「进店」、带 `shopId` 链入 |
| 店内搜索 | **B+C** | `subpackage/shop/search` | A 页「搜索本店商品」；B 输入 + C 结果 **同页** |

**`pages.json` 分包：** `pkg-shop` → `root: subpackage/shop`

| 页面 | navigationBarTitleText |
|------|------------------------|
| index | 店铺（加载成功后改为店名） |
| search | 搜本店商品 |

**Query：**

| 页面 | 参数 | 说明 |
|------|------|------|
| A | `shopId`（必填） | 缺失 → 提示并返回 |
| B+C | `shopId`（必填） | 同上 |
| B+C | `keyword`（可选） | 预填并自动发起搜索 |

**路径常量：** `utils/pageRoute.js` → `PAGE_SHOP_HOME`、`PAGE_SHOP_SEARCH`

**跳转工具：** `utils/shopNav.js`

| 方法 | 行为 |
|------|------|
| `goShopHome(shopId)` | `navigateTo` A 页 |
| `goShopSearch(shopId, keyword?)` | `navigateTo` 店内搜索页 |

---

## 3. 文件清单

| 类型 | 路径 | 说明 |
|------|------|------|
| A 页 | `shop-app/subpackage/shop/index.vue` | 介绍区 + 关注 + 一二级 Tab + 商品列表 |
| 店内搜索 | `shop-app/subpackage/shop/search.vue` | 输入 + 结果列表（同页） |
| 商品列表块 | `shop-app/components/shop/ShopGoodsBlock.vue` | 分页、`GoodsGrid`、重试 |
| 店铺 API | `shop-app/api/shop.js` | `/api/shop/**` |
| 展示映射 | `shop-app/utils/shopDisplay.js` | 介绍区、分类树、搜索店卡片 |
| 导航 | `shop-app/utils/shopNav.js` | 进店跳转 |
| 常量 | `shop-app/constants/shop.js` | 营业态、全部 Tab、分页、占位文案 |

**已改入口（联调）：**

| 文件 | 改动 |
|------|------|
| `subpackage/search/result.vue` | 店铺 Tab `@item-click` → `goShopHome` |
| `subpackage/goods/detail.vue` | `onEnterShop` → `goShopHome(shopId)` |

---

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

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

| 方法 | HTTP | 路径 | 鉴权 | 页面 |
|------|------|------|------|------|
| `getShopProfile` | GET | `/{shopId}` | 匿名（Token 可选） | A |
| `getShopCategories` | GET | `/{shopId}/categories` | 匿名 | A |
| `getShopLevel2Tabs` | GET | `/{shopId}/categories/{level1Id}/level2-tabs` | 匿名 | A |
| `getShopGoods` | GET | `/{shopId}/goods` | 匿名 | A / 店内搜索 |
| `followShop` | POST | `/{shopId}/follow` | **须 Token** | A |
| `unfollowShop` | DELETE | `/{shopId}/follow` | **须 Token** | A |

### 4.1 商品列表 Query

| 场景 | 参数 |
|------|------|
| 全部商品 | 仅 `pageNum`、`pageSize` |
| 某二级分类 | `shopCategoryId` |
| 店内搜索 | `keyword`（**不传** `shopCategoryId`） |

空关键词：前端 **不请求**；后端 trim 后空白会报错。

### 4.2 关注交互

| 规则 | 前端实现 |
|------|----------|
| SH-F1 未登录 | `ensureApiToken()` → 跳转登录 |
| SH-F2/F3 | 成功后本地更新 `followed`、`fansCount ±1` |
| 失败 | request Toast；**不** 改按钮态 |

---

## 5. 页面逻辑说明

### 5.1 A. 店铺主页 `index.vue`

```text
onLoad(shopId)
  → loadProfile（失败：整页错误 + 重试/返回）
  → loadCategories（一级树，不含「全部」）
  → 默认「全部商品」→ ShopGoodsBlock 无 shopCategoryId

切换一级（非全部）
  → loadLevel2Tabs
  → 有二级：展示二级 Tab，传 shopCategoryId
  → 无二级：不请求商品，空态「该分类下暂无商品」

点击商品 → goGoodsDetail(goodsId)
点击搜索条 → goShopSearch(shopId)
```

| 展示 | 说明 |
|------|------|
| 停业 `shopStatus=1` | 名称旁 **「休息中」** 标签；列表仍展示（SH8） |
| 评分 | `rating` 为 null 时不展示评分行 |
| 粉丝 | 始终展示数字（无统计为 0） |
| 描述 | 两行截断展示 |

### 5.2 店内搜索 `search.vue`

| 规则 | 实现 |
|------|------|
| SH-S2 空关键词 | Toast，不展示结果区 |
| SH-S3/S4 | `ShopGoodsBlock` 带 `keyword` |
| 无匹配 | `emptyText="暂无相关"` |
| 回车 | `@confirm` 等同点搜索 |

### 5.3 `ShopGoodsBlock.vue`

- 监听 `shopId`、`shopCategoryId`、`keyword`、`scrollTopKey`、`enabled`
- 上拉 `scrolltolower` 加载更多
- `loadFailed` 时显示「点击重试」
- 卡片字段经 `mapGoodsCardList`：主图、名称、售价、店名

---

## 6. 数据映射（`shopDisplay.js`）

| 函数 | 用途 |
|------|------|
| `mapShopProfile` | 介绍区：头像 URL、`isClosed`、`showRating`、`followed` |
| `mapShopCategoryTree` | 复用 `mapCategoryTree` |
| `mapShopLevel2Tabs` | 复用 `mapLevel2Tabs` |
| `sortShopTabsWithHot` | `hot_flag=1` 靠前 |
| `mapShopCard` | 全站搜索店铺 Tab（已有） |

---

## 7. 异常与边界（前端）

| 情形 | 行为 |
|------|------|
| shopId 缺失 | Toast + `smartNavigateBack` |
| 店铺 404 | `pageError` + 重试 / 返回 |
| 分类/列表失败 | 分类树空；列表块内重试 |
| 一级无二级 | `goodsBlockEnabled=false`，专用空态文案 |
| 浏览 vs 下单 | 本模块不加购；停业/四条件在 **详情页** 拦截 |

---

## 8. 联调检查清单

- [ ] `GET /api/shop/{id}` 返回介绍字段；登录后 `followed` 正确
- [ ] `GET .../categories` 仅可见分类；「全部」下商品分页
- [ ] 选二级 `shopCategoryId` 筛选正确
- [ ] `GET .../goods?keyword=` 仅本店出售中、销量序
- [ ] `POST/DELETE .../follow` 粉丝数与按钮态一致
- [ ] 搜索页店铺卡片、详情「进店」跳转 A 页
- [ ] 已删店 404 文案与返回

---

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

| 项 | 说明 |
|------|------|
| 全站搜索 / 搜索历史 | 搜索分包 |
| 店内按价格排序 | 需求未要求 |
| 我的关注列表页 | 我的服务另册 |
| 电话一键拨打、分享 | 可选增强 |

---

## 10. 修订记录

| 版本 | 说明 |
|------|------|
| **v1.0** | 首版：A 主页 + 店内搜索同页、API/组件/入口联调说明 |

---

*文档版本：v1.0 · 不修改《店铺主页技术方案.md》（后端方案）。*
