# 产品中心 (Product Center) 设计规格

## 概述

为 InkReach 官网新增"产品中心"二级页面，包含三个子系统：
1. **NestJS 后端服务** — Prisma + PostgreSQL，RESTful API，SDS 定时同步
2. **Vue 3 Admin 后台** — Element Plus，商品/品类/国家/标签/位置管理
3. **Nuxt 4 官网页面** — 产品中心二级页，带侧边栏分类、国家筛选、搜索、分页

## 架构

- NestJS 监听 `:3001`，提供 RESTful API
- Admin 直连 NestJS API（开发环境 Vite proxy 解决跨域）
- Website 通过 Nitro server route 代理 NestJS API

## 实施顺序

后端服务 → Admin 后台 → 官网产品中心页

---

## 1. NestJS 后端服务

### 技术栈

| 技术 | 版本 | 用途 |
|------|------|------|
| NestJS | 10+ | 后端框架 |
| Prisma | 6+ | ORM |
| PostgreSQL | 16+ | 数据库 |
| @nestjs/schedule | - | 定时同步 SDS |
| passport + jwt | - | JWT 认证 |
| class-validator | - | DTO 验证 |

### 项目结构

```
inkreach-official-nestjs/
├── prisma/
│   ├── schema.prisma
│   └── migrations/
├── src/
│   ├── main.ts
│   ├── app.module.ts
│   ├── auth/
│   │   ├── auth.module.ts
│   │   ├── auth.controller.ts
│   │   ├── auth.service.ts
│   │   ├── jwt.strategy.ts
│   │   └── dto/
│   ├── goods/
│   │   ├── goods.module.ts
│   │   ├── goods.controller.ts
│   │   ├── goods.service.ts
│   │   └── dto/
│   ├── categories/
│   ├── countries/
│   ├── tags/
│   ├── positions/
│   ├── origin-goods/
│   └── sync/
│       ├── sync.module.ts
│       ├── sync.service.ts
│       └── sync.controller.ts
├── test/
├── .env
├── nest-cli.json
├── tsconfig.json
└── package.json
```

### Prisma Schema

将 `docs/dev/database-table-design.md` 的 DDL 转换为 Prisma schema，6 个核心模型：`OriginGoods`, `Country`, `Category`, `Tag`, `Position`, `Good`。

关系映射：
- `Good` → `OriginGoods` (多对一, RESTRICT)
- `Good` → `Country` (多对一, RESTRICT)
- `Good` → `Category` (多对一, RESTRICT)
- `Good` → `Tag` (多对一, SET NULL)
- `Good` → `Position` (多对一, SET NULL)
- `Category` 自关联 (parent_category_id)
- `Position` → `Country` (多对一, CASCADE)
- `Position` → `Category` (多对一, CASCADE)

### API 设计

#### 认证

| 方法 | 路径 | 描述 |
|------|------|------|
| POST | `/auth/login` | 管理员登录，返回 JWT |
| POST | `/auth/register` | 注册（仅初始化用） |

#### 商品管理 (Admin, 需认证)

| 方法 | 路径 | 描述 |
|------|------|------|
| GET | `/goods` | 分页查询商品（支持 country/category/tag/position/keyword 筛选） |
| GET | `/goods/:id` | 商品详情 |
| POST | `/goods` | 创建商品 |
| PATCH | `/goods/:id` | 更新商品 |
| DELETE | `/goods/:id` | 删除商品 |
| PATCH | `/goods/batch-priority` | 批量更新优先级 |
| POST | `/goods/batch` | 批量创建商品（从 origin_goods 选取） |

#### 品类管理 (Admin)

| 方法 | 路径 | 描述 |
|------|------|------|
| GET | `/categories` | 获取品类树 |
| GET | `/categories/:id` | 品类详情 |
| POST | `/categories` | 创建品类 |
| PATCH | `/categories/:id` | 更新品类 |
| DELETE | `/categories/:id` | 删除品类 |

#### 国家管理 (Admin)

| 方法 | 路径 | 描述 |
|------|------|------|
| GET | `/countries` | 国家列表 |
| POST | `/countries` | 创建国家 |
| PATCH | `/countries/:id` | 更新国家 |
| DELETE | `/countries/:id` | 删除国家 |

#### 标签管理 (Admin)

| 方法 | 路径 | 描述 |
|------|------|------|
| GET | `/tags` | 标签列表 |
| POST | `/tags` | 创建标签 |
| PATCH | `/tags/:id` | 更新标签 |
| DELETE | `/tags/:id` | 删除标签 |

#### 位置管理 (Admin)

| 方法 | 路径 | 描述 |
|------|------|------|
| GET | `/positions` | 位置列表 |
| POST | `/positions` | 创建位置 |
| PATCH | `/positions/:id` | 更新位置 |
| DELETE | `/positions/:id` | 删除位置 |

#### 原商品管理 (Admin)

| 方法 | 路径 | 描述 |
|------|------|------|
| GET | `/origin-goods` | 原商品列表（来自 SDS 同步） |

#### 同步 (Admin)

| 方法 | 路径 | 描述 |
|------|------|------|
| POST | `/sync/categories` | 手动触发品类同步 |
| POST | `/sync/products` | 手动触发产品同步 |
| GET | `/sync/status` | 获取最近同步状态 |

#### 公开 API (Website, 无需认证)

| 方法 | 路径 | 描述 |
|------|------|------|
| GET | `/public/categories` | 品类树（仅含已配置商品的品类） |
| GET | `/public/countries` | 国家列表（仅含已配置商品的国家） |
| GET | `/public/goods` | 分页查询商品（支持 country/category/tag/keyword 筛选） |
| GET | `/public/goods/:id` | 商品详情 |

### SDS 同步逻辑

1. **品类同步**: 调用 `https://mapi.sdspod.com/category/tree/3`，将返回的树形结构扁平化写入 `categories` 表
2. **产品同步**: 按 SDS 分类遍历产品，写入 `origin_goods` 表
3. **定时策略**: 每小时自动同步一次 (`@Cron('0 * * * *')`)
4. **增量策略**: 对比现有记录，存在则跳过，不存在则插入

### 商品查询逻辑 (公开 API)

排序: `good_priority DESC → position.index_val ASC → created_at DESC`

筛选参数：
- `countryId` — 按国家筛选
- `categoryId` — 按品类筛选（含子品类）
- `tagId` — 按标签筛选
- `keyword` — 按商品名模糊搜索
- `page` / `pageSize` — 分页

---

## 2. Admin 后台

### 技术栈

| 技术 | 版本 | 用途 |
|------|------|------|
| Vue | 3.5+ | UI 框架 |
| Vite | 6+ | 构建工具 |
| Element Plus | 2.9+ | UI 组件库 |
| Vue Router | 4+ | 路由 |
| Pinia | 3+ | 状态管理 |
| Axios | 1.7+ | HTTP 客户端 |
| TypeScript | 5+ | 类型安全 |

### 项目结构

```
inkreach-official-admin/
├── src/
│   ├── main.ts
│   ├── App.vue
│   ├── router/index.ts
│   ├── stores/
│   │   ├── auth.ts
│   │   └── app.ts
│   ├── api/
│   │   ├── request.ts
│   │   ├── auth.ts
│   │   ├── goods.ts
│   │   ├── categories.ts
│   │   ├── countries.ts
│   │   ├── tags.ts
│   │   ├── positions.ts
│   │   ├── origin-goods.ts
│   │   └── sync.ts
│   ├── layouts/
│   │   └── DefaultLayout.vue
│   ├── views/
│   │   ├── login/LoginView.vue
│   │   ├── goods/GoodsView.vue
│   │   ├── categories/CategoriesView.vue
│   │   ├── countries/CountriesView.vue
│   │   ├── tags/TagsView.vue
│   │   ├── positions/PositionsView.vue
│   │   └── sync/SyncView.vue
│   ├── components/
│   └── types/index.ts
├── .env
├── vite.config.ts
├── tsconfig.json
└── package.json
```

### 核心页面功能

**商品管理 (GoodsView)**:
- 表格展示：商品名、国家、品类、标签、位置、优先级
- 筛选：按国家/品类/标签筛选
- 操作：新增、编辑、删除、调整优先级
- 批量添加：从 origin_goods 选择商品，批量配置国家/品类/标签/优先级

**品类管理 (CategoriesView)**:
- 树形表格展示层级关系
- 新增/编辑/删除品类

**同步管理 (SyncView)**:
- 显示最近同步时间、同步状态
- 手动触发品类同步/产品同步按钮

---

## 3. 官网产品中心页

### 页面结构

左右两栏布局：左侧固定宽度侧边栏（品类导航），右侧主内容区（国家筛选 + 筛选栏 + 商品网格 + 分页）。

### 组件拆分

```
app/pages/product-center.vue          # 页面入口
app/components/product/
├── ProductSidebar.vue                # 侧边栏品类导航
├── ProductCountryFilter.vue          # 国家标签栏
├── ProductFilterBar.vue              # 筛选下拉 + 已选标签 + 搜索
├── ProductGrid.vue                   # 商品卡片网格
├── ProductCard.vue                   # 单个商品卡片
├── ProductCardSkeleton.vue           # 骨架屏卡片
└── ProductPagination.vue             # 分页控件
app/composables/
└── useProductCenter.ts               # 产品中心数据逻辑
```

### 商品卡片设计

- 商品图片 (1:1 方形)
- 商品名 (单行截断)
- 标签行 (pill 样式，颜色体系：绿色=包邮，橙色=工艺，灰色=其他)
- 价格 (粗体，"¥XX.XX起")

### 侧边栏品类导航

- 顶级品类带图标 + 折叠箭头
- 展开子品类列表，左侧橙色竖条标识当前选中
- 选中子品类背景 `bg-inkreach-homepage-3` + 橙色文字

### 国家标签栏

- Pill 形状按钮，每个带国旗图标
- "全部" 为默认选中，橙色边框 + 橙色文字
- 未选中为灰色边框

### 搜索

- 分体式搜索框：左侧输入框 + 右侧橙色"搜索"按钮
- 支持按商品名搜索

### 分页

- 左侧："总计 N 个产品"
- 中间：页码按钮（方形）
- 右侧：每页条数下拉 + 跳转输入框

### 骨架屏

数据加载时显示骨架屏：
- 卡片区域显示 ProductCardSkeleton (4x3 网格)
- 分页隐藏
- 侧边栏和筛选区正常显示

### Nitro API 代理

```
server/api/backend/categories.get.ts  -> GET  :3001/public/categories
server/api/backend/countries.get.ts   -> GET  :3001/public/countries
server/api/backend/goods.get.ts       -> GET  :3001/public/goods
server/api/backend/goods/[id].get.ts  -> GET  :3001/public/goods/:id
```

### 数据流

1. 页面加载 → `/api/backend/categories` → 渲染侧边栏
2. 页面加载 → `/api/backend/countries` → 渲染国家标签栏
3. 筛选/分页变化 → `/api/backend/goods?countryId=&categoryId=&keyword=&page=&pageSize=` → 渲染商品网格

### 关联改动

- 首页 `PodProducts.vue` 的"更多产品"按钮改为 `<NuxtLink to="/product-center">`
- `AppHeader.vue` 的"产品中心"链接改为 `<NuxtLink to="/product-center">`

---

## 跨项目配置

### 环境变量

**inkreach-official-nestjs/.env**:
- DATABASE_URL=postgresql://postgres:yoyoki219765.@localhost:5432/inkreach-official
- JWT_SECRET=(generate-secure-secret)
- SDS_API_BASE=https://mapi.sdspod.com
- PORT=3001

**inkreach-official-admin/.env**:
- VITE_API_BASE=http://localhost:3001

**inkreach-official-website**:
- NUXT_PUBLIC_BACKEND_URL=http://localhost:3001

### 端口分配

| 项目 | 端口 |
|------|------|
| NestJS Backend | 3001 |
| Admin (Vite dev) | 5173 |
| Website (Nuxt dev) | 3000 |
