- Two-line good nodes with country + tag chips, hover tooltip
- Resizable dual-tree with fixed-height panels (no node overlap)
- Filter bar: search, country filter, tag filter (multi-select with rename)
- Mode switch (品类/国家) pushed to right via spacer
- Inline create country/tag in edit & config modals via quick-create buttons
- Right tree node height auto-fix for locate highlight
- Filter dropdown styles in global scope for teleported popper
- Backend: multi-tag (GoodTag junction), goodImage column, origin goods tree API
- Admin response interceptor unwraps {data, success} envelope
- Simplified sidebar to single 商品管理 entry with tabs
- SyncView simplified to product sync only
149 lines
4.2 KiB
TypeScript
149 lines
4.2 KiB
TypeScript
import { ApiProperty } from '@nestjs/swagger';
|
|
import type { Good as PrismaGood } from '@prisma/client';
|
|
|
|
export interface GoodRelations {
|
|
country?: { id: bigint; countryName: string; countryIcon: string | null } | null;
|
|
category?: { id: bigint; categoryName: string; categoryIcon: string | null } | null;
|
|
tag?: { id: bigint; tagName: string; tagColor: string | null } | null;
|
|
position?: { id: bigint; indexVal: number } | null;
|
|
originGood?: {
|
|
id: bigint;
|
|
sdsGoodId: string;
|
|
goodName: string | null;
|
|
goodImage: string | null;
|
|
goodPrice: unknown;
|
|
} | null;
|
|
goodTags?: { tag: { id: bigint; tagName: string; tagColor: string | null } }[];
|
|
}
|
|
|
|
export class GoodDto {
|
|
@ApiProperty()
|
|
id!: string;
|
|
|
|
@ApiProperty()
|
|
goodName!: string;
|
|
|
|
@ApiProperty({ nullable: true })
|
|
goodImage!: string | null;
|
|
|
|
@ApiProperty()
|
|
goodPriority!: number;
|
|
|
|
@ApiProperty()
|
|
countryId!: string;
|
|
|
|
@ApiProperty()
|
|
categoryId!: string;
|
|
|
|
@ApiProperty({ nullable: true })
|
|
tagId!: string | null;
|
|
|
|
@ApiProperty({ nullable: true })
|
|
positionId!: string | null;
|
|
|
|
@ApiProperty()
|
|
originGoodId!: string;
|
|
|
|
@ApiProperty()
|
|
createdAt!: string;
|
|
|
|
@ApiProperty()
|
|
updatedAt!: string;
|
|
|
|
@ApiProperty({ required: false, nullable: true })
|
|
country?: { id: string; countryName: string; countryIcon: string | null } | null;
|
|
|
|
@ApiProperty({ required: false, nullable: true })
|
|
category?: { id: string; categoryName: string; categoryIcon: string | null } | null;
|
|
|
|
@ApiProperty({ required: false, nullable: true })
|
|
tag?: { id: string; tagName: string; tagColor: string | null } | null;
|
|
|
|
@ApiProperty({ required: false, type: Array })
|
|
tags!: Array<{ id: string; tagName: string; tagColor: string | null }>;
|
|
|
|
@ApiProperty({ required: false, nullable: true })
|
|
position?: { id: string; indexVal: number } | null;
|
|
|
|
@ApiProperty({ required: false, nullable: true })
|
|
originGood?: {
|
|
id: string;
|
|
sdsGoodId: string;
|
|
goodName: string | null;
|
|
goodImage: string | null;
|
|
goodPrice: string | null;
|
|
} | null;
|
|
|
|
static from(
|
|
good: PrismaGood,
|
|
rel: GoodRelations = {},
|
|
): GoodDto {
|
|
return {
|
|
id: good.id.toString(),
|
|
goodName: good.goodName,
|
|
goodImage: good.goodImage,
|
|
goodPriority: good.goodPriority,
|
|
countryId: good.countryId.toString(),
|
|
categoryId: good.categoryId.toString(),
|
|
tagId: good.tagId === null || good.tagId === undefined ? null : good.tagId.toString(),
|
|
positionId: good.positionId === null || good.positionId === undefined ? null : good.positionId.toString(),
|
|
originGoodId: good.originGoodId.toString(),
|
|
createdAt: good.createdAt.toISOString(),
|
|
updatedAt: good.updatedAt.toISOString(),
|
|
country: rel.country
|
|
? {
|
|
id: rel.country.id.toString(),
|
|
countryName: rel.country.countryName,
|
|
countryIcon: rel.country.countryIcon,
|
|
}
|
|
: null,
|
|
category: rel.category
|
|
? {
|
|
id: rel.category.id.toString(),
|
|
categoryName: rel.category.categoryName,
|
|
categoryIcon: rel.category.categoryIcon,
|
|
}
|
|
: null,
|
|
tag: rel.tag
|
|
? {
|
|
id: rel.tag.id.toString(),
|
|
tagName: rel.tag.tagName,
|
|
tagColor: rel.tag.tagColor,
|
|
}
|
|
: null,
|
|
tags: rel.goodTags
|
|
? rel.goodTags.map((gt) => ({
|
|
id: gt.tag.id.toString(),
|
|
tagName: gt.tag.tagName,
|
|
tagColor: gt.tag.tagColor,
|
|
}))
|
|
: [],
|
|
position: rel.position
|
|
? {
|
|
id: rel.position.id.toString(),
|
|
indexVal: rel.position.indexVal,
|
|
}
|
|
: null,
|
|
originGood: rel.originGood
|
|
? {
|
|
id: rel.originGood.id.toString(),
|
|
sdsGoodId: rel.originGood.sdsGoodId,
|
|
goodName: rel.originGood.goodName,
|
|
goodImage: rel.originGood.goodImage,
|
|
goodPrice:
|
|
rel.originGood.goodPrice === null ||
|
|
rel.originGood.goodPrice === undefined
|
|
? null
|
|
: (rel.originGood.goodPrice as { toString(): string }).toString(),
|
|
}
|
|
: null,
|
|
};
|
|
}
|
|
}
|
|
|
|
export interface PaginatedGoods {
|
|
items: GoodDto[];
|
|
total: number;
|
|
page: number;
|
|
pageSize: number;
|
|
} |