Files
inkreach-official-website/docs/references/structs.md
T
yeuimu 0d2a954153 deploy(single-stack): 单栈收敛——v2 并入 deploy compose,v1 退役
- docker-compose.yml 重写为单栈 4 容器(admin 边缘 + v2-api/v2-admin/v2-postgres),
  v2 数据卷 deploy-v2_pgdata/uploads 以 external 引用,数据零迁移
- nginx:/uploads/ /assets/ 上游改 v2-api(product-center 覆盖块随之冗余移除),
  / 跳转 /v2/admin/;docker-compose.v2.yml 删除(血统已并入 develop)
- v1 api+postgres 已停用未删,终档/快照/回滚手册见 deploy/backups/consolidation-*/
- 切换验证:v2 库切换前后行数完全一致;/v2-api /public /v2/admin /v2/h5
  /assets 全路由冒烟 + 公网端到端通过
2026-09-03 02:09:24 +08:00

22 KiB
Raw Blame History

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,再启动另两个。

生产部署(单栈,2026-09-03 收敛后)

  • 唯一部署源:本仓库 develop/opt/inkreach),deploy/docker-compose.yml 单 compose 编排 4 容器:admin(边缘 nginx80/443 + 证书 + 全站路由)、v2-apiv2-adminv2-postgres(数据卷 deploy-v2_pgdata external 引用,v2 数据未迁移)。
  • 域名路由:/v2/admin/SPA)、/v2-api/(小程序/后台 API,去前缀转发)、/public/ H5 同源 API)、/uploads//assets/ 均由 v2-api 服务;/ 302 到 /v2/admin/
  • v1(旧 api+postgres,曾支撑 /public/ 与旧后台)已退役:切换时容器停用未删, 库终档与全部配置快照及回滚手册见 deploy/backups/consolidation-*/RESTORE.md
  • 原 v2 独立栈(docker-compose.v2.yml/opt/inkreach-v2 检出)已并入单栈并删除, 其血统(refactor/v2)已完全合入 develop;后续所有开发只走 develop。

子项目 1apps/api(后端,@inkreach/api

apps/api/
├── prisma/
│   ├── schema.prisma              # 数据模型(OriginGood/ProductFamily/FamilyPriceOverride/Country/Category/Tag/Position/Good/User/SyncLog
│   ├── migrations/                # Prisma migrate 历史
│   ├── backfill-product-families.ts  # 产品族回填脚本(解析列→自动建族→全量重算,幂等)
│   ├── fix-pure-sku-good-names.ts    # 商品名存量修复脚本(剥型号限定词/纯款号补描述,幂等,dry-run 默认)
│   └── fix-category-parens.ts        # 分类名去括号存量修复脚本(整段删除括号段,幂等,dry-run 默认)
├── 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 原始商品快照(只读分页 + 配置状态树 + 链接级标签人工接管)
│   ├── product-families/          # 产品族(SPU 层):CRUD / auto-group(并入已有族优先,familyNameKey 族语义键)/ attachToMatchingFamily(单链接自动归族)/ consolidateFragments(碎片族合并)/ 成员管理 / 自定义成员 / 价格覆盖 / 重算 / 按链接名称派生标签(auto-tag-rules,含 热转印→烫画 别名)
│   ├── goods/                     # 商品 CRUD + 批量优先级 + 批量创建 + 展示名规范化(品名 SKU,剥 ASCII 型号限定词,纯款号按 SDS 分类名补描述)
│   ├── 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(唯一);含四个链接名解析列(skuCode/logisticsLabel/craftLabel/warehouseLabel,格式 国家(物流)品名-SKU-工艺[-仓库])与 familyId 族归属
OriginGoodDetail SDS /products/{id} 详情缓存(文本字段 + sizeChart/packageSpecs/options/media JSON
OriginGoodVariant SDS 子 SKU 缓存(尺码 × 颜色 × 价格),价格矩阵推导的数据源
ProductFamily 产品族(SPU 层):同 SDS 分类(产品模型)的多条链接合并为一族;物化并集尺码表/包装规则与五维价格矩阵(priceMatrix JSON);autoManaged=false 为人工锁定(重算只置 stale);primaryOriginGoodId 主链接(无 FK,服务层维护)
FamilyPriceOverride 人工按格改价:(familyId, sizeId, colorId, craft, logistics) 唯一 → price;独立于推导矩阵,重算永不覆盖
Country 国家,关联 goods / positions
Category 自引用树形品类,可选 sds_category_id
Tag 标签,含 tagColortagFontColortagGroupIdsortOrdertiming
TagGroup 标签分组(含 groupNamegroupColorgroupIconsortOrder),删除分组时组内 tag 的 tagGroupId 通过 onDelete: SetNull 自动置空
Position 坑位:(country, category) 维度,关联多个 goods
Good 商品:originGood × country × category × tag? × position?,含 goodPriorityfamilyId 为派生数据(主链接所属族,创建时自动填充,族成员变更时联动)
GoodOriginGood 副源关联中间表(多对一):一个 Good 可关联多个副源 OriginGood;主源走 goods.origin_good_id 不入表。用于把名称相同但工厂/仓库不同的多个原产品合并为一个商品展示
User 后台用户(bcrypt 哈希)
SyncLog 同步任务日志,含 SyncTypeCATEGORIES / PRODUCTS)和 SyncStatus

关键设计

  • 所有主键为 BigInt,路由解析后 BigInt(id) 处理,序列化时通过 BigInt.prototype.toJSON 转为字符串。
  • 全局 TransformInterceptor:把响应包装为 { data: T, success: true },前端读取 response.data
  • 全局 ValidationPipewhitelist + transform + forbidNonWhitelisted
  • 全局 HttpExceptionFilter:统一错误响应形态。
  • CORS 白名单http://localhost:5173admin)和 http://localhost:3000website)。
  • JWT:所有 /goods /categories /countries /tags /positions /origin-goods /product-families /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 分页商品(族化契约:一族一条goodId=族IDprice=族起价;无族商品不返回;支持 countryId/categoryId/tags(JSON)/keyword/minPrice/maxPrice/sort/page/pageSize)。默认排序 = 款序树:国家(countries.sort_order) → 款所属二级(categories.sort_order) → 款(categories.sort_order) → good_priority;商品经 origin_goods.sds_category_id 定位到款(新树三级节点=合并后的款,顺序值由 排序表.md 经回填脚本写入),同一款下多条 Good 聚在一起、款内按优先级分先后;sort=PRICE_ASC/PRICE_DESC/NEWEST 不受影响 公开
/public/goods/:id GET 款级详情,:id = 族 ID(唯一公开键,SDS 链接 ID 404);公共字段取代表 Good,variants = 全体族成员 旧副源(去重),sizeChart/packageSpecs = 族并集;默认输出 family 块(并集尺码表/包装 + 严格五维价格矩阵 尺码×颜色×印花数量×工艺×物流 + 族起价);PUBLIC_DETAIL_FROM_FAMILY=false 应急回退 公开
/categories /tags /tag-groups /countries /positions 后台 CRUD JWT
/countries/sort PATCH 批量保存国家拖拽排序(items=[{id,sortOrder}] 全量提交;公开/后台国家列表均按 sortOrder 排序) JWT
/tags/sort PATCH 批量更新 tag 排序和分组归属 JWT
/tag-groups/sort PATCH 批量更新分组排序 JWT
/origin-goods GET SDS 原始商品快照分页 JWT
/origin-goods/tree GET 配置状态树(叶子含 familyId/familyName/familyCode/familyStale JWT
/origin-goods/:id/tags GET/PUT/DELETE 链接级标签:查(含 manual 标记)/ 人工接管全量替换 / 恢复按名称自动派生;写入后镜像到名下商品 JWT
/product-families GET/POST 产品族分页列表(keyword 匹配名称/编码)/ 建族(可直挂成员) JWT
/product-families/organize POST 整理原产品库(显式人工动作):回填解析列 → 派生标签(人工接管不动)→ 自动建族(并入已有同款族优先)→ 碎片族合并 → 全量重算;返回 familiesMerged/fragmentsConsolidated/fragmentsManualReviewCLI 等价 pnpm --filter @inkreach/api organize JWT
/product-families/auto-group POST 自动成族:按 SDS 分类(产品模型)聚合无族链接,无分类回退族语义名称键(familyNameKey,物流/工艺不分族);已有同款 autoManaged 族则并入(不建 -2 碎片族),同款仅人工锁定族则跳过;{apply:false} 预览含 action: create/merge/skip{apply:true} 落库并重算(幂等) JWT
/product-families/:id GET/PATCH 族详情(成员+覆盖)/ 编辑 canonical 字段、autoManaged、主链接 JWT
/product-families/:id/recompute POST 手动重算并集与价格矩阵 JWT
/product-families/:id/members POST 成员增删 {addOriginGoodIds, removeOriginGoodIds};移除主链接后 primary 落到剩余成员 JWT
/product-families/:id/members/custom POST 族内创建自定义成员(人工商品:物流/工艺归因必填 + 变体价格 + 可选尺码表/包装) JWT
/product-families/:id/price-overrides GET/PUT/DELETE 人工改价:查(含推导价对照与差额)/ 批量 upsert / 按格删除恢复推导价;格子五键 sizeId+colorId+printCount+craft+logistics 必须存在于族矩阵选项 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        # 原始商品快照
│   │   ├── product-families.ts    # 产品族 CRUD / auto-group / 成员 / 覆盖 / 重算
│   │   └── 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    # 商品配置主页面(左右树 + 筛选 + 全局列表)
│   │   ├── goods/components/      # 弹窗/弹层组件:GoodsEditDialog(编辑+族成员标签/改价)、
│   │   │                          #   GoodsConfigDialog(配置合并)、CustomGoodDialog、TagFilterPopover
│   │   ├── 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:3001rewrite 去掉 /api 前缀。
  • axios 拦截器:请求注入 Authorization: Bearer <token>;响应直接返回 response.data401 自动登出跳转。
  • ElementPlus 自动导入:通过 unplugin-auto-import + unplugin-vue-components + ElementPlusResolver
  • 路由守卫:未登录访问受保护路由跳 /login;已登录访问 /login/
  • Pinia 持久化auth store 主动读写 localStoragetoken + 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.vueFigma 产品中心专用 80px 导航栏。
  • apps/website/app/components/product/ProductSidebar.vue240px 分类树及移动端抽屉内容。
  • apps/website/app/components/product/ProductCountryFilter.vue:国家胶囊筛选。
  • apps/website/app/components/product/ProductTagFilter.vue:物流与工艺分组筛选。
  • apps/website/app/components/product/ProductCard.vue270 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.mdAgent 使用说明。

官网首页设计还原(2026-07-16)

  • apps/website/app/pages/index.vue:官网首页组合入口。
  • apps/website/app/components/HeroBanner.vueAppFooter.vue:首页分区组件。
  • apps/website/public/case-*.pngFigma 用户案例商品图。
  • 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

  • 连接配置在根 .envDATABASE_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