From c492469d706611f65536a60b2a3e76b265521562 Mon Sep 17 00:00:00 2001 From: yeuimu <2197651308@qq.com> Date: Fri, 28 Aug 2026 13:25:10 +0800 Subject: [PATCH] feat(admin): product families api client and types --- apps/admin/src/api/product-families.ts | 64 ++++++++ apps/admin/src/types/index.ts | 87 +++++++++++ plans/feature/product-family-admin-feature.md | 147 ++++++++++++++++++ 3 files changed, 298 insertions(+) create mode 100644 apps/admin/src/api/product-families.ts create mode 100644 plans/feature/product-family-admin-feature.md diff --git a/apps/admin/src/api/product-families.ts b/apps/admin/src/api/product-families.ts new file mode 100644 index 0000000..d97f009 --- /dev/null +++ b/apps/admin/src/api/product-families.ts @@ -0,0 +1,64 @@ +import request from './request' +import type { + AutoGroupPreviewGroup, + FamilyPriceOverrideRow, + PaginatedResult, + ProductFamily, +} from '@/types' + +export const productFamiliesApi = { + list: (params: { keyword?: string; page?: number; pageSize?: number }) => { + return request.get>('/product-families', { params }) + }, + + detail: (id: string) => { + return request.get(`/product-families/${id}`) + }, + + create: (data: { + familyName: string + familyCode?: string + familyImage?: string + originGoodIds?: string[] + primaryOriginGoodId?: string + }) => { + return request.post('/product-families', data) + }, + + patch: (id: string, data: Record) => { + return request.patch(`/product-families/${id}`, data) + }, + + autoGroup: (apply: boolean) => { + return request.post( + '/product-families/auto-group', + { apply }, + ) + }, + + updateMembers: (id: string, data: { addOriginGoodIds?: string[]; removeOriginGoodIds?: string[] }) => { + return request.post(`/product-families/${id}/members`, data) + }, + + createCustomMember: (id: string, data: Record) => { + return request.post(`/product-families/${id}/members/custom`, data) + }, + + recompute: (id: string) => { + return request.post(`/product-families/${id}/recompute`) + }, + + listOverrides: (id: string) => { + return request.get(`/product-families/${id}/price-overrides`) + }, + + putOverrides: (id: string, items: Array>) => { + return request.put(`/product-families/${id}/price-overrides`, { items }) + }, + + deleteOverrides: (id: string, cells: Array>) => { + return request.delete(`/product-families/${id}/price-overrides`, { + data: { cells }, + }) + }, +} diff --git a/apps/admin/src/types/index.ts b/apps/admin/src/types/index.ts index 512ac27..842b073 100644 --- a/apps/admin/src/types/index.ts +++ b/apps/admin/src/types/index.ts @@ -378,6 +378,10 @@ export interface OriginGoodsTreeNode { variantCount: number sizeRowCount: number packageRowCount: number + familyId: string | null + familyName: string | null + familyCode: string | null + familyStale: boolean | null } export interface OriginGoodsTreeCategoryNode { @@ -396,6 +400,89 @@ export interface OriginGoodsTreeResponse { configuredCount: number } +// Product Family (SPU layer) types +export interface ProductFamilyMember { + id: string + sdsGoodId: string + goodName: string + goodImage: string | null + source: 'SDS' | 'CUSTOM' + delisted: boolean + skuCode: string | null + logisticsLabel: string | null + craftLabel: string | null + warehouseLabel: string | null +} + +export interface FamilyPriceOverrideRow { + id: string + familyId: string + sizeId: string + colorId: string + craft: string + logistics: string + price: string + note: string | null + derivedPrice: string | null + diff: string | null +} + +export interface ProductFamilyPriceMatrix { + sizes: Array<{ key: string; name: string | null }> + colors: Array<{ key: string; name: string | null; hex: string | null; imageUrl: string | null }> + crafts: string[] + logistics: string[] + rows: Array<{ + sizeId: string + sizeName: string | null + colorId: string + colorName: string | null + craft: string + logistics: string + price: string + manual: boolean + sources: Array<{ sdsGoodId: string; sdsVariantId: string; price: string }> + }> +} + +export interface ProductFamily { + id: string + familyCode: string | null + familyName: string + familyImage: string | null + countryId: string | null + categoryId: string | null + primaryOriginGoodId: string | null + autoManaged: boolean + stale: boolean + detail: Record | null + sizeChart: { columns?: unknown; rows?: Array> } | null + packageSpecs: { columns?: unknown; rows?: Array> } | null + priceMatrix: ProductFamilyPriceMatrix | null + originGoods: ProductFamilyMember[] + priceOverrides: FamilyPriceOverrideRow[] + _count: { originGoods: number; priceOverrides: number } +} + +export interface AutoGroupPreviewGroup { + groupKey: string + familyName: string + familyCode: string | null + memberCount: number + sampleNames: string[] +} + +export interface CustomMemberVariantInput { + sku: string + sizeId?: string | null + sizeName?: string | null + colorId?: string | null + colorName?: string | null + colorHex?: string | null + imageUrl?: string | null + price: number +} + // Sync types export interface SyncLog { id: string diff --git a/plans/feature/product-family-admin-feature.md b/plans/feature/product-family-admin-feature.md new file mode 100644 index 0000000..556c4f7 --- /dev/null +++ b/plans/feature/product-family-admin-feature.md @@ -0,0 +1,147 @@ +# 产品族二期(admin 前端)实施计划 + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** admin 后台落地产品族管理界面:新增「产品族」标签页(列表/详情/成员管理/自定义成员/人工改价/自动成族),GoodsView 原产品树接入族信息。 + +**Architecture:** 新建 `views/product-family/` 目录按职责拆组件(不复制 GoodsView 巨型单文件模式);API 走新增 `src/api/product-families.ts`;GoodsView 仅做两处增量(兄弟勾选按族匹配、树节点族徽标)。依赖一期后端 `/product-families/*` 端点(已上线 develop)。 + +**Tech Stack:** Vue 3 `