feat(product-center): implement Figma-designed UI, upload module, and website tests

- Redesign website homepage & product-center per Figma (fonts, logos, hero/footer/customer cases)
- Add API upload module (multer) with static serving for uploads/public assets
- Add OriginGood.delisted flag and SDS request retry logic
- Add admin ImageUpload component and goods import/upload flows
- Add vitest suite for website components and composables (32 tests)
- Add skills, docs, plans and PRODUCT.md
This commit is contained in:
yeuimu
2026-08-20 14:32:03 +08:00
parent b5fc88f3fa
commit 79fabd85f7
107 changed files with 5364 additions and 2601 deletions
+14 -1
View File
@@ -193,6 +193,19 @@ describe('PublicService', () => {
expect(priorities).toEqual(sorted);
});
it('returns the SDS product id as the public product id', async () => {
const result = await service.getGoods({
page: 1,
pageSize: 1,
countryId: Number(countryId),
keyword: `Pub High ${stamp}`,
});
expect(result.items).toHaveLength(1);
expect(result.items[0].id).toBe(`pub-sds-${stamp}`);
expect(result.items[0].id).not.toBe(goodIds[0].toString());
});
it('getGood returns detail and 404 for unknown id', async () => {
const first = await service.getGoods({
page: 1,
@@ -201,7 +214,7 @@ describe('PublicService', () => {
keyword: `Pub `,
});
expect(first.items.length).toBe(1);
const detail = await service.getGood(BigInt(first.items[0].id));
const detail = await service.getGood(goodIds[0]);
expect(detail.id).toBe(first.items[0].id);
await expect(service.getGood(BigInt(99999999))).rejects.toBeInstanceOf(
+7 -4
View File
@@ -89,7 +89,9 @@ export class PublicService {
}
async getGoods(query: PublicQueryGoodDto): Promise<PublicPaginatedGoods> {
const where: Prisma.GoodWhereInput = {};
const where: Prisma.GoodWhereInput = {
originGood: { delisted: false },
};
if (query.countryId !== undefined) where.countryId = BigInt(query.countryId);
if (query.tagIds) {
const ids = query.tagIds
@@ -154,9 +156,10 @@ export class PublicService {
tag: { id: bigint; tagName: string; tagColor: string | null; tagFontColor: string | null; tagGroup: { id: bigint; groupName: string; sortOrder: number } | null } | null;
position: { id: bigint; indexVal: number } | null;
originGood: {
sdsGoodId: string;
goodImage: string | null;
goodPrice: { toString(): string } | null;
} | null;
};
goodTags: { tag: { id: bigint; tagName: string; tagColor: string | null; tagFontColor: string | null; tagGroup: { id: bigint; groupName: string; sortOrder: number } | null } }[];
createdAt: Date;
}): PublicGoodDto {
@@ -169,7 +172,7 @@ export class PublicService {
}
: null;
return {
id: good.id.toString(),
id: good.originGood.sdsGoodId,
goodName: good.goodName,
goodPriority: good.goodPriority,
country: {
@@ -250,4 +253,4 @@ export class PublicService {
}
return roots;
}
}
}