Files
inkreach-official-website/docs/references/structs.md
T

291 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# InkReach Product Center — Monorepo Structure
> pnpm workspaces + Turborepo 统一管理三个子项目与共享包。所有子项目位于 `apps/`,共享代码位于 `packages/`。
## 根目录
```
inkreach-official/
├── apps/
│ ├── api/ # NestJS 后端 (package: @inkreach/api)
│ ├── admin/ # Vue 3 后台 (package: @inkreach/admin)
│ └── website/ # Nuxt 4 官网 (package: @inkreach/website)
├── packages/
│ ├── tsconfig/ # 共享 TypeScript 配置 (base / api / vue)
│ └── shared-types/ # 共享类型定义(PaginatedResult, BackendEnvelope, 实体接口)
├── .agents/ # AI Agent 共享技能
├── docs/ # 跨子项目文档
│ ├── dev/ # 设计文档(数据库表设计、PRD、原型图)
│ ├── references/
│ │ └── structs.md # 本文件
│ └── superpowers/specs/ # superpowers 规格说明
├── plans/ # 跨子项目计划
├── package.json # 根 workspace 配置(scripts + devDependencies
├── pnpm-workspace.yaml # pnpm workspace 定义(apps/*, packages/*
├── turbo.json # Turborepo 任务流水线
├── .prettierrc # 统一代码格式化
├── .gitignore # 全局忽略规则
├── .env # 根级环境变量(DATABASE_URL 等)
├── AGENTS.md # 根级 AI Agent 开发规范
└── README.md
```
## 端口与子项目
| 子项目 | 包名 | 端口 | 启动命令 | 说明 |
|--------|------|------|----------|------|
| `apps/api` | `@inkreach/api` | 3001 | `pnpm --filter @inkreach/api start:dev` | NestJS + Prisma + PostgreSQL 后端 APISwagger 文档 `/api/docs` |
| `apps/admin` | `@inkreach/admin` | 5173 | `pnpm --filter @inkreach/admin dev` | Vue 3 + Element Plus 后台管理;通过 Vite proxy `/api → :3001` |
| `apps/website` | `@inkreach/website` | 3000 | `pnpm --filter @inkreach/website dev` | Nuxt 4 官网;通过 Nitro `server/api/backend/*` 代理 `:3001` |
启动顺序:先启动 `apps/api`,再启动另两个。
## 子项目 1apps/api(后端,@inkreach/api
```
apps/api/
├── prisma/
│ ├── schema.prisma # 数据模型(OriginGood/Country/Category/Tag/Position/Good/User/SyncLog
│ └── migrations/ # Prisma migrate 历史
├── src/
│ ├── main.ts # 入口:CORS、ValidationPipe、Swagger、BigInt JSON 序列化
│ ├── app.module.ts # 根模块,聚合所有业务模块
│ ├── prisma/ # PrismaService 封装
│ ├── auth/ # JWT 认证:register / login / JwtStrategy / JwtAuthGuard
│ ├── countries/ # 国家 CRUD(受 JWT 保护)
│ ├── categories/ # 品类 CRUD(受 JWT 保护,自引用树)
│ ├── tags/ # 标签 CRUD(受 JWT 保护)
│ ├── tag-groups/ # 标签分组 CRUD(受 JWT 保护,含批量排序)
│ ├── positions/ # 坑位 CRUD(受 JWT 保护)
│ ├── origin-goods/ # SDS 原始商品快照(只读分页)
│ ├── goods/ # 商品 CRUD + 批量优先级 + 批量创建
│ ├── sync/ # SDS 同步:分类 / 商品 / 同步日志
│ ├── public/ # 公开 API:分类树 / 国家 / 商品分页 / 商品详情
│ └── common/ # 全局装饰器 / 过滤器 / 拦截器
│ ├── decorators/current-user.decorator.ts
│ ├── filters/http-exception.filter.ts
│ └── interceptors/transform.interceptor.ts
├── test/ # e2e 测试
├── dist/ # 构建产物
├── nest-cli.json
├── tsconfig.json / tsconfig.build.json
├── jest.config.js
└── package.json
```
### 数据模型(Prisma
| 模型 | 说明 |
|------|------|
| `OriginGood` | SDS 原始商品缓存,关联 `sds_good_id`(唯一) |
| `Country` | 国家,关联 goods / positions |
| `Category` | 自引用树形品类,可选 `sds_category_id` |
| `Tag` | 标签,含 `tagColor``tagFontColor``tagGroupId``sortOrder``timing` |
| `TagGroup` | 标签分组(含 `groupName``groupColor``groupIcon``sortOrder`),删除分组时组内 tag 的 `tagGroupId` 通过 `onDelete: SetNull` 自动置空 |
| `Position` | 坑位:`(country, category)` 维度,关联多个 goods |
| `Good` | 商品:`originGood × country × category × tag? × position?`,含 `goodPriority` |
| `GoodOriginGood` | 副源关联中间表(多对一):一个 Good 可关联多个副源 OriginGood;主源走 `goods.origin_good_id` 不入表。用于把名称相同但工厂/仓库不同的多个原产品合并为一个商品展示 |
| `User` | 后台用户(bcrypt 哈希) |
| `SyncLog` | 同步任务日志,含 `SyncType`CATEGORIES / PRODUCTS)和 `SyncStatus` |
### 关键设计
- **所有主键为 `BigInt`**,路由解析后 `BigInt(id)` 处理,序列化时通过 `BigInt.prototype.toJSON` 转为字符串。
- **全局 `TransformInterceptor`**:把响应包装为 `{ data: T, success: true }`,前端读取 `response.data`
- **全局 `ValidationPipe`**`whitelist + transform + forbidNonWhitelisted`
- **全局 `HttpExceptionFilter`**:统一错误响应形态。
- **CORS 白名单**`http://localhost:5173`admin)和 `http://localhost:3000`website)。
- **JWT**:所有 `/goods /categories /countries /tags /positions /origin-goods /sync/*` 路由受 `JwtAuthGuard` 保护;`/public/*``/auth/*` 公开。
### API 路由
| 前缀 | 说明 | 鉴权 |
|------|------|------|
| `/auth/register` `POST` | 注册后台用户 | 公开 |
| `/auth/login` `POST` | 登录获取 JWT | 公开 |
| `/public/categories` `GET` | 公开品类树(仅含已挂商品的品类) | 公开 |
| `/public/countries` `GET` | 公开国家列表(仅含已挂商品的国家) | 公开 |
| `/public/tags` `GET` | 公开标签列表(带 `group` 字段,按 group 排序) | 公开 |
| `/public/tag-groups` `GET` | 公开标签分组列表 | 公开 |
| `/public/goods` `GET` | 分页商品(支持 `countryId/categoryId/tagIds(逗号分隔)/keyword/page/pageSize``tagIds` 为 AND 关系) | 公开 |
| `/public/goods/:id` `GET` | 商品详情 | 公开 |
| `/categories` `/tags` `/tag-groups` `/countries` `/positions` | 后台 CRUD | JWT |
| `/tags/sort` `PATCH` | 批量更新 tag 排序和分组归属 | JWT |
| `/tag-groups/sort` `PATCH` | 批量更新分组排序 | JWT |
| `/origin-goods` `GET` | SDS 原始商品快照分页 | JWT |
| `/goods` | 后台商品 CRUD + `POST /goods/batch` + `PATCH /goods/batch-priority` | JWT |
| `/sync/categories` `POST` | 手动触发分类同步 | JWT |
| `/sync/products` `POST` | 手动触发商品同步 | JWT |
| `/sync/status` `GET` | 最近同步日志(`?limit=20` | JWT |
| `/api/docs` | Swagger UI | 公开 |
## 子项目 2apps/admin(后台,@inkreach/admin
```
apps/admin/
├── public/ # 静态资源
├── src/
│ ├── main.ts # 入口:Pinia + Vue Router + ElementPlus
│ ├── App.vue
│ ├── style.css # 全局样式(含品牌色变量)
│ ├── api/ # 按业务模块拆分的 API 客户端
│ │ ├── request.ts # axios 实例 + JWT 拦截 + 全局错误处理
│ │ ├── auth.ts # /auth/login, /auth/me, /auth/logout
│ │ ├── goods.ts # 商品 CRUD + 批量
│ │ ├── categories.ts # 品类 CRUD
│ │ ├── countries.ts # 国家 CRUD
│ │ ├── tags.ts # 标签 CRUD(含批量排序)
│ │ ├── tag-groups.ts # 标签分组 CRUD
│ │ ├── positions.ts # 坑位 CRUD
│ │ ├── origin-goods.ts # 原始商品快照
│ │ └── sync.ts # 同步触发 + 日志
│ ├── layouts/DefaultLayout.vue # 侧边栏 + 顶部条 + 用户菜单
│ ├── router/index.ts # 路由 + 登录守卫
│ ├── stores/
│ │ ├── auth.ts # 登录态 + token + user(持久化到 localStorage
│ │ └── app.ts # 侧边栏折叠
│ ├── types/index.ts # 共享类型
│ ├── views/
│ │ ├── login/LoginView.vue # 登录
│ │ ├── goods/GoodsView.vue
│ │ ├── categories/CategoriesView.vue
│ │ ├── countries/CountriesView.vue
│ │ ├── tags/TagsView.vue
│ │ ├── positions/PositionsView.vue
│ │ └── sync/SyncView.vue
│ ├── auto-imports.d.ts # 自动生成的自动导入类型
│ └── components.d.ts # 自动生成的组件类型
├── index.html
├── vite.config.ts # Vite + ElementPlus 自动导入 + /api → :3001 代理
├── tsconfig.json / tsconfig.app.json / tsconfig.node.json
└── package.json
```
### 路由
| 路径 | 视图 | 鉴权 |
|------|------|------|
| `/login` | LoginView | 公开 |
| `/goods` | GoodsView(默认页) | JWT |
| `/categories` | CategoriesView | JWT |
| `/countries` | CountriesView | JWT |
| `/tags` | TagsView | JWT |
| `/positions` | PositionsView | JWT |
| `/sync` | SyncView | JWT |
| `/:pathMatch(.*)*` | 重定向到 `/goods` | — |
### 关键设计
- **Vite 代理**`/api/*` 代理到 `http://localhost:3001`rewrite 去掉 `/api` 前缀。
- **axios 拦截器**:请求注入 `Authorization: Bearer <token>`;响应直接返回 `response.data`401 自动登出跳转。
- **ElementPlus 自动导入**:通过 `unplugin-auto-import` + `unplugin-vue-components` + `ElementPlusResolver`
- **路由守卫**:未登录访问受保护路由跳 `/login`;已登录访问 `/login``/`
- **Pinia 持久化**`auth` store 主动读写 `localStorage``token` + `user`)。
## 子项目 3apps/website(官网,@inkreach/website
```
apps/website/
├── app/
│ ├── app.vue # 根容器:<NuxtPage />
│ ├── pages/
│ │ ├── index.vue # 首页
│ │ └── product-center.vue # 产品中心(侧边栏 + 国家/筛选 + 网格 + 分页)
│ ├── assets/css/tailwind.css # Tailwind v4 主题(@theme 定义颜色与动画)
│ ├── composables/
│ │ ├── useNavData.ts # 导航数据(选品推荐/解决方案)
│ │ ├── usePodProducts.ts # 首页 POD 产品(SDS
│ │ └── useProductCenter.ts # 产品中心数据(调用 /api/backend/*
│ └── components/
│ ├── AppHeader.vue / AppFooter.vue
│ ├── nav/ # 导航下拉面板
│ │ ├── NavMegaMenu.vue
│ │ └── NavColumnMenu.vue
│ ├── product/ # 产品中心专用组件
│ │ ├── ProductSidebar.vue # 树形品类侧边栏
│ │ ├── ProductCountryFilter.vue # 国家 pill 筛选
│ │ ├── ProductFilterBar.vue # 搜索输入 + 按钮
│ │ ├── ProductCard.vue # 商品卡片
│ │ ├── ProductCardSkeleton.vue # 骨架占位
│ │ ├── ProductGrid.vue # 网格容器
│ │ └── ProductPagination.vue # 分页器
│ ├── HeroBanner.vue
│ ├── TrustSection.vue
│ ├── StepProcess.vue
│ ├── PodProducts.vue
│ ├── FeatureCards.vue
│ ├── WhyInkReach.vue
│ ├── CompanyProfile.vue
│ ├── CustomerCases.vue
│ └── CtaBanner.vue
├── server/ # Nitro 后端
│ ├── api/
│ │ ├── pod/ # 原有 SDS POD API 代理
│ │ └── backend/ # NestJS 后端代理(同源 + Nitro 缓存 60s
│ │ ├── categories.get.ts # → GET :3001/public/categories
│ │ ├── countries.get.ts # → GET :3001/public/countries
│ │ ├── tags.get.ts # → GET :3001/public/tags
│ │ ├── tag-groups.get.ts # → GET :3001/public/tag-groups
│ │ ├── goods.get.ts # → GET :3001/public/goods
│ │ └── goods/[id].get.ts # → GET :3001/public/goods/:id
│ └── utils/pod-api.ts
├── public/ # 静态资源
├── plans/feature/ # 历史功能计划
├── docs/
│ ├── references/structs.md # 子项目级结构文档
│ └── superpowers/specs/
├── nuxt.config.ts # runtimeConfig.public.backendUrl
├── .env # NUXT_PUBLIC_BACKEND_URL=http://localhost:3001
├── AGENTS.md # 子项目 AI Agent 规范
├── README.md
└── package.json
```
> 子项目内部的页面、组件、composable、代理路由、组件响应式断点等详细信息见 `apps/website/docs/references/structs.md`。
## 数据库
- `apps/api/public/product-center/`:产品中心 Figma 国家旗帜与一级品类图标,由 `/assets/product-center/*` 对外提供。
- `apps/api/prisma/configure-product-center-icons.ts`:按名称幂等写入 `countryIcon/categoryIcon`,不创建业务记录。
## 产品中心设计还原(2026-07-16
- `apps/website/app/components/product/ProductCenterHeader.vue`Figma 产品中心专用 80px 导航栏。
- `apps/website/app/components/product/ProductSidebar.vue`:240px 分类树及移动端抽屉内容。
- `apps/website/app/components/product/ProductCountryFilter.vue`:国家胶囊筛选。
- `apps/website/app/components/product/ProductTagFilter.vue`:物流与工艺分组筛选。
- `apps/website/app/components/product/ProductCard.vue`270 x 382 桌面商品卡片。
- `apps/website/app/components/product/ProductPagination.vue`:总数、页码、每页数量和跳转。
- `apps/website/test/useProductCenter.test.ts`:默认筛选名称映射测试。
- `docs/references/product-center.md`:产品中心使用与验证说明。
- `skills/inkreach-official-website/SKILL.md`Agent 使用说明。
## 官网首页设计还原(2026-07-16
- `apps/website/app/pages/index.vue`:官网首页组合入口。
- `apps/website/app/components/HeroBanner.vue``AppFooter.vue`:首页分区组件。
- `apps/website/public/case-*.png`Figma 用户案例商品图。
- `docs/references/homepage.md`:首页结构、尺寸基准和验证说明。
- `apps/website/test/HomepageNavigationCarousel.test.ts`:首页当前页 CTA、自动轮播、主视觉图片和临时导航隐藏的行为测试。
## 数据库
- `apps/api/src/public/public.service.ts`:官网公共商品序列化边界;响应中的商品 `id` 使用 `originGood.sdsGoodId`,不暴露本地 `goods.good_id`
- PostgreSQL 14+Prisma 5.x
- 连接配置在根 `.env``DATABASE_URL`
- 所有 `TIMESTAMPTZ` 列:`@db.Timestamptz(6)`
- 所有主键:`BigInt @default(autoincrement())`
- 表名与列名通过 `@map` / `@@map` 映射为 `snake_case`
- 迁移位于 `apps/api/prisma/migrations/`
## 环境变量总览
| 变量 | 位置 | 用途 | 默认 |
|------|------|------|------|
| `DATABASE_URL` | 根 `.env` | PostgreSQL 连接串 | — |
| `JWT_SECRET` | `apps/api` | JWT 签名密钥 | — |
| `PORT` | `apps/api` | 后端端口 | `3001` |
| `SDS_API_*` | `apps/api` | 同步上游 SDS 接口凭据 | — |
| `VITE_API_BASE` | `apps/admin` | axios baseURL | `/api` |
| `NUXT_PUBLIC_BACKEND_URL` | `apps/website` | NestJS 后端地址 | `http://localhost:3001` |