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:
@@ -65,10 +65,39 @@ export class SdsClientService {
|
||||
'https://mapi.sdspod.com';
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the SDS category tree of type 3 (products category).
|
||||
* Body matches the legacy inkpod client.
|
||||
*/
|
||||
private async request<T>(
|
||||
method: 'post' | 'get',
|
||||
url: string,
|
||||
body?: unknown,
|
||||
params?: Record<string, unknown>,
|
||||
): Promise<T> {
|
||||
const MAX_RETRIES = 3;
|
||||
let lastError: unknown;
|
||||
for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
|
||||
try {
|
||||
const config = {
|
||||
headers: POD_HEADERS,
|
||||
timeout: 30000,
|
||||
...(params ? { params } : {}),
|
||||
};
|
||||
const obs =
|
||||
method === 'post'
|
||||
? this.http.post<T>(url, body, config)
|
||||
: this.http.get<T>(url, config);
|
||||
const { data } = await firstValueFrom(obs);
|
||||
return data as T;
|
||||
} catch (err) {
|
||||
lastError = err;
|
||||
const msg = err instanceof Error ? err.message : String(err);
|
||||
if (attempt < MAX_RETRIES) {
|
||||
this.logger.warn(`SDS request attempt ${attempt}/${MAX_RETRIES} failed: ${msg}`);
|
||||
await new Promise((r) => setTimeout(r, 1000 * attempt));
|
||||
}
|
||||
}
|
||||
}
|
||||
throw lastError;
|
||||
}
|
||||
|
||||
async fetchCategoryTree(): Promise<SdsCategoryTreeNode[]> {
|
||||
const url = `${this.baseUrl}/category/tree/3`;
|
||||
const body = {
|
||||
@@ -76,27 +105,23 @@ export class SdsClientService {
|
||||
withPrivate: true,
|
||||
onlyHaveProduct: true,
|
||||
};
|
||||
const { data } = await firstValueFrom(
|
||||
this.http.post<SdsCategoryTreeNode[]>(url, body, { headers: POD_HEADERS }),
|
||||
);
|
||||
return Array.isArray(data) ? data : [];
|
||||
const data = await this.request<unknown>('post', url, body);
|
||||
if (!Array.isArray(data)) {
|
||||
throw new Error(`SDS category tree returned ${typeof data}, expected array`);
|
||||
}
|
||||
return data as SdsCategoryTreeNode[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches one page of products for a given SDS category.
|
||||
*/
|
||||
async fetchProductsPage(
|
||||
categoryId: string | number,
|
||||
page = 1,
|
||||
size = 50,
|
||||
): Promise<SdsProductsPage> {
|
||||
const url = `${this.baseUrl}/products/page`;
|
||||
const { data } = await firstValueFrom(
|
||||
this.http.get<SdsProductsPage>(url, {
|
||||
headers: POD_HEADERS,
|
||||
params: { categoryId, page, size },
|
||||
}),
|
||||
);
|
||||
return data ?? {};
|
||||
const data = await this.request<unknown>('get', url, undefined, { categoryId, page, size });
|
||||
if (!data || typeof data !== 'object') {
|
||||
throw new Error(`SDS products page returned ${typeof data}, expected object`);
|
||||
}
|
||||
return data as SdsProductsPage;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user