feat(deploy): production deployment setup and fixes
- Debian-based api image (bookworm-slim), docker/debian mirrors, prisma binaryTargets for openssl 3.0 - nginx: admin SPA under /admin, TLS via acme.sh (ZeroSSL) + auto-renewal cron, http->https redirect - prisma: add origin_goods.delisted migration, sync missing schema (good_image/tag_font_color/good_tags), fix users.createdAt Timestamptz - api: CORS wildcard reflection, helmet CORP cross-origin, price backfill in persistProductDetail, categoryIcon ancestor fallback, mediaByColor per-color gallery in public goods detail - admin: /admin base path (vite + router) - import-data.mjs: udt_name casting, serial sequence advance fix
This commit is contained in:
@@ -1,361 +1,361 @@
|
||||
# 产品中心 (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 |
|
||||
# 产品中心 (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 |
|
||||
|
||||
@@ -1,92 +1,92 @@
|
||||
# Goods Management Redesign
|
||||
|
||||
## Date: 2026-06-21
|
||||
|
||||
## Problem
|
||||
|
||||
1. **Origin Good name not displayed**: GoodsView doesn't show the origin product name/image/price
|
||||
2. **Single tag per good**: Current schema only allows one tag per good via `goods.tag_id` FK
|
||||
3. **Easy to miss unconfigured origin products**: Flat paginated table with no visibility into which origin products have been configured vs not
|
||||
|
||||
## Solution
|
||||
|
||||
### 1. Multi-Tag Support (GoodTag Junction Table)
|
||||
|
||||
**Schema change:**
|
||||
- New `good_tags` junction table: `(good_id, tag_id)` composite PK
|
||||
- Migrate existing `goods.tag_id` data into `good_tags`
|
||||
- Set `goods.tag_id` to nullable (will be dropped in a future migration)
|
||||
|
||||
**Backend:**
|
||||
- `CreateGoodDto`: `tagIds: number[]` (replaces `tagId`)
|
||||
- `UpdateGoodDto`: `tagIds?: number[]` (replaces `tagId`)
|
||||
- `BatchCreateGoodDto`: `tagIds?: number[]` (replaces `tagId`)
|
||||
- `QueryGoodDto`: `tagId` filter queries `tags.some` instead of direct FK
|
||||
- `GoodsService.create/update`: manage `good_tags` records in transaction
|
||||
- `GOOD_INCLUDE`: include `tags: { include: { tag: true } }`
|
||||
|
||||
### 2. Origin Goods Tree API
|
||||
|
||||
New endpoint: `GET /origin-goods/tree`
|
||||
|
||||
Returns origin goods grouped by SDS category, with configuration status:
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"sdsCategoryId": "4185",
|
||||
"categoryName": "(包邮)180g纯棉T恤-单面印花",
|
||||
"originGoods": [
|
||||
{
|
||||
"id": "123",
|
||||
"goodName": "产品A",
|
||||
"goodImage": "https://...",
|
||||
"goodPrice": "12.50",
|
||||
"sdsGoodId": "p-123",
|
||||
"configuredCount": 2,
|
||||
"configuredCountries": ["美国", "英国"]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
Origin goods without `sdsCategoryId` are grouped under "未分类".
|
||||
|
||||
### 3. Admin UI: Dual Tree Layout
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────────────────┐
|
||||
│ [搜索原产品] [210/439 已配置] │
|
||||
├───────────────────┬─────────────────────────────────────┤
|
||||
│ 首页分类树 │ 原产品分类树 │
|
||||
│ │ │
|
||||
│ ▼ 美国 │ ▼ (包邮)180g纯棉T恤 │
|
||||
│ ▼ T恤 │ ✓ 产品A [美国,英国] │
|
||||
│ ▼ 卫衣 │ ✗ 产品B [未配置] │
|
||||
│ ▼ 英国 │ ▼ 未分类 │
|
||||
│ ▼ 韩国 │ ✗ 产品C [未配置] │
|
||||
├───────────────────┴─────────────────────────────────────┤
|
||||
│ 详情/操作区域 │
|
||||
│ - 选左侧分类: 该分类下所有 goods 表格 │
|
||||
│ - 选右侧原产品: 该原产品的配置详情 + 快捷配置按钮 │
|
||||
│ - 多标签选择 (el-select multiple) │
|
||||
└──────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**Key interactions:**
|
||||
- Left tree click: filter goods by selected category
|
||||
- Right tree click: show origin good detail + all its goods configs
|
||||
- Right tree badges: ✓ configured / ✗ unconfigured
|
||||
- Quick configure: select country+category+tags, click origin goods to batch create
|
||||
- Statistics bar: configured/total count
|
||||
|
||||
### 4. Data Migration
|
||||
|
||||
```sql
|
||||
-- Step 1: Create good_tags table
|
||||
-- Step 2: Copy existing tag_id data
|
||||
INSERT INTO good_tags (good_id, tag_id, created_at)
|
||||
SELECT good_id, tag_id, NOW() FROM goods WHERE tag_id IS NOT NULL;
|
||||
-- Step 3: Good.tag_id becomes nullable (already nullable)
|
||||
```
|
||||
# Goods Management Redesign
|
||||
|
||||
## Date: 2026-06-21
|
||||
|
||||
## Problem
|
||||
|
||||
1. **Origin Good name not displayed**: GoodsView doesn't show the origin product name/image/price
|
||||
2. **Single tag per good**: Current schema only allows one tag per good via `goods.tag_id` FK
|
||||
3. **Easy to miss unconfigured origin products**: Flat paginated table with no visibility into which origin products have been configured vs not
|
||||
|
||||
## Solution
|
||||
|
||||
### 1. Multi-Tag Support (GoodTag Junction Table)
|
||||
|
||||
**Schema change:**
|
||||
- New `good_tags` junction table: `(good_id, tag_id)` composite PK
|
||||
- Migrate existing `goods.tag_id` data into `good_tags`
|
||||
- Set `goods.tag_id` to nullable (will be dropped in a future migration)
|
||||
|
||||
**Backend:**
|
||||
- `CreateGoodDto`: `tagIds: number[]` (replaces `tagId`)
|
||||
- `UpdateGoodDto`: `tagIds?: number[]` (replaces `tagId`)
|
||||
- `BatchCreateGoodDto`: `tagIds?: number[]` (replaces `tagId`)
|
||||
- `QueryGoodDto`: `tagId` filter queries `tags.some` instead of direct FK
|
||||
- `GoodsService.create/update`: manage `good_tags` records in transaction
|
||||
- `GOOD_INCLUDE`: include `tags: { include: { tag: true } }`
|
||||
|
||||
### 2. Origin Goods Tree API
|
||||
|
||||
New endpoint: `GET /origin-goods/tree`
|
||||
|
||||
Returns origin goods grouped by SDS category, with configuration status:
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"sdsCategoryId": "4185",
|
||||
"categoryName": "(包邮)180g纯棉T恤-单面印花",
|
||||
"originGoods": [
|
||||
{
|
||||
"id": "123",
|
||||
"goodName": "产品A",
|
||||
"goodImage": "https://...",
|
||||
"goodPrice": "12.50",
|
||||
"sdsGoodId": "p-123",
|
||||
"configuredCount": 2,
|
||||
"configuredCountries": ["美国", "英国"]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
Origin goods without `sdsCategoryId` are grouped under "未分类".
|
||||
|
||||
### 3. Admin UI: Dual Tree Layout
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────────────────┐
|
||||
│ [搜索原产品] [210/439 已配置] │
|
||||
├───────────────────┬─────────────────────────────────────┤
|
||||
│ 首页分类树 │ 原产品分类树 │
|
||||
│ │ │
|
||||
│ ▼ 美国 │ ▼ (包邮)180g纯棉T恤 │
|
||||
│ ▼ T恤 │ ✓ 产品A [美国,英国] │
|
||||
│ ▼ 卫衣 │ ✗ 产品B [未配置] │
|
||||
│ ▼ 英国 │ ▼ 未分类 │
|
||||
│ ▼ 韩国 │ ✗ 产品C [未配置] │
|
||||
├───────────────────┴─────────────────────────────────────┤
|
||||
│ 详情/操作区域 │
|
||||
│ - 选左侧分类: 该分类下所有 goods 表格 │
|
||||
│ - 选右侧原产品: 该原产品的配置详情 + 快捷配置按钮 │
|
||||
│ - 多标签选择 (el-select multiple) │
|
||||
└──────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**Key interactions:**
|
||||
- Left tree click: filter goods by selected category
|
||||
- Right tree click: show origin good detail + all its goods configs
|
||||
- Right tree badges: ✓ configured / ✗ unconfigured
|
||||
- Quick configure: select country+category+tags, click origin goods to batch create
|
||||
- Statistics bar: configured/total count
|
||||
|
||||
### 4. Data Migration
|
||||
|
||||
```sql
|
||||
-- Step 1: Create good_tags table
|
||||
-- Step 2: Copy existing tag_id data
|
||||
INSERT INTO good_tags (good_id, tag_id, created_at)
|
||||
SELECT good_id, tag_id, NOW() FROM goods WHERE tag_id IS NOT NULL;
|
||||
-- Step 3: Good.tag_id becomes nullable (already nullable)
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user