- 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
11 KiB
11 KiB
产品中心 (Product Center) 设计规格
概述
为 InkReach 官网新增"产品中心"二级页面,包含三个子系统:
- NestJS 后端服务 — Prisma + PostgreSQL,RESTful API,SDS 定时同步
- Vue 3 Admin 后台 — Element Plus,商品/品类/国家/标签/位置管理
- 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 同步逻辑
- 品类同步: 调用
https://mapi.sdspod.com/category/tree/3,将返回的树形结构扁平化写入categories表 - 产品同步: 按 SDS 分类遍历产品,写入
origin_goods表 - 定时策略: 每小时自动同步一次 (
@Cron('0 * * * *')) - 增量策略: 对比现有记录,存在则跳过,不存在则插入
商品查询逻辑 (公开 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
数据流
- 页面加载 →
/api/backend/categories→ 渲染侧边栏 - 页面加载 →
/api/backend/countries→ 渲染国家标签栏 - 筛选/分页变化 →
/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 |