- 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
166 lines
4.2 KiB
TypeScript
166 lines
4.2 KiB
TypeScript
interface CategoryChild {
|
|
id: number;
|
|
name: string;
|
|
parentId: number;
|
|
parentName: string;
|
|
tag: string;
|
|
productNum: number;
|
|
children: CategoryChild[];
|
|
}
|
|
|
|
interface Category {
|
|
id: number;
|
|
name: string;
|
|
tag: string;
|
|
productNum: number;
|
|
children: CategoryChild[];
|
|
}
|
|
|
|
interface ProductItem {
|
|
id: number;
|
|
name: string;
|
|
psd_img_url: string;
|
|
thumbImgUrl: string;
|
|
currentPrice: number;
|
|
color_str: string[];
|
|
}
|
|
|
|
interface ProductPage {
|
|
totalCount: number;
|
|
items: ProductItem[];
|
|
page: number;
|
|
size: number;
|
|
}
|
|
|
|
interface CountryGroup {
|
|
id: number;
|
|
name: string;
|
|
products: CategoryChild[];
|
|
}
|
|
|
|
const COUNTRY_NAME_MAP: Record<string, string> = {
|
|
'美国工厂直发': '美国',
|
|
'日本本地工厂直发': '日本',
|
|
'墨西哥工厂本地直发': '墨西哥',
|
|
'巴西本地工厂直发': '巴西',
|
|
'中东本地工厂直发': '中东',
|
|
'欧洲波兰工厂直发': '波兰',
|
|
'欧洲西班牙工厂直发': '西班牙',
|
|
'欧洲德国工厂本地直发': '德国',
|
|
'欧洲意大利工厂直发': '意大利',
|
|
'英国本地直发': '英国',
|
|
'加拿大本地工厂直发': '加拿大',
|
|
'澳大利亚本地工厂直发': '澳大利亚',
|
|
'韩国本地直发': '韩国',
|
|
};
|
|
|
|
export function usePodProducts() {
|
|
const categories = ref<Category[]>([]);
|
|
const countryGroups = ref<CountryGroup[]>([]);
|
|
const activeCountryIdx = ref(0);
|
|
const productImages = ref<Map<number, string>>(new Map());
|
|
const loading = ref(true);
|
|
const loadingProducts = ref(false);
|
|
|
|
const countryPage = ref(0);
|
|
const visibleCountryCount = 6;
|
|
|
|
const fetchCategories = async () => {
|
|
try {
|
|
loading.value = true;
|
|
const data = await $fetch<Category[]>('/api/pod/categories', {
|
|
method: 'POST',
|
|
});
|
|
categories.value = data;
|
|
countryGroups.value = data.map((cat) => ({
|
|
id: cat.id,
|
|
name: COUNTRY_NAME_MAP[cat.name] ?? cat.name,
|
|
products: cat.children,
|
|
}));
|
|
if (countryGroups.value.length > 0) {
|
|
await fetchProductsForCountry(0);
|
|
}
|
|
} catch (error) {
|
|
console.error('Failed to fetch categories:', error);
|
|
} finally {
|
|
loading.value = false;
|
|
}
|
|
};
|
|
|
|
const fetchProductsForCountry = async (idx: number) => {
|
|
const group = countryGroups.value[idx];
|
|
if (!group) return;
|
|
|
|
loadingProducts.value = true;
|
|
try {
|
|
const children = group.products.slice(0, 10);
|
|
const promises = children.map((child) =>
|
|
$fetch<ProductPage>(`/api/pod/products/${child.id}`).then((res) => ({
|
|
id: child.id,
|
|
name: child.name,
|
|
image: res.items?.[0]?.psd_img_url ?? res.items?.[0]?.thumbImgUrl ?? '',
|
|
})).catch(() => ({
|
|
id: child.id,
|
|
name: child.name,
|
|
image: '',
|
|
})),
|
|
);
|
|
const results = await Promise.all(promises);
|
|
const newMap = new Map(productImages.value);
|
|
for (const r of results) {
|
|
newMap.set(r.id, r.image);
|
|
}
|
|
productImages.value = newMap;
|
|
} catch (error) {
|
|
console.error('Failed to fetch products:', error);
|
|
} finally {
|
|
loadingProducts.value = false;
|
|
}
|
|
};
|
|
|
|
const selectCountry = (idx: number) => {
|
|
activeCountryIdx.value = idx;
|
|
countryPage.value = Math.floor(idx / visibleCountryCount);
|
|
fetchProductsForCountry(idx);
|
|
};
|
|
|
|
const visibleCountries = computed(() => {
|
|
const start = countryPage.value * visibleCountryCount;
|
|
return countryGroups.value.slice(start, start + visibleCountryCount);
|
|
});
|
|
|
|
const canPrevCountry = computed(() => countryPage.value > 0);
|
|
const canNextCountry = computed(
|
|
() =>
|
|
(countryPage.value + 1) * visibleCountryCount <
|
|
countryGroups.value.length,
|
|
);
|
|
|
|
const currentProducts = computed(() => {
|
|
const group = countryGroups.value[activeCountryIdx.value];
|
|
if (!group) return [];
|
|
return group.products.slice(0, 10).map((p) => ({
|
|
id: p.id,
|
|
name: p.name,
|
|
image: productImages.value.get(p.id) ?? '',
|
|
}));
|
|
});
|
|
|
|
return {
|
|
categories,
|
|
countryGroups,
|
|
activeCountryIdx,
|
|
productImages,
|
|
loading,
|
|
loadingProducts,
|
|
countryPage,
|
|
visibleCountryCount,
|
|
visibleCountries,
|
|
canPrevCountry,
|
|
canNextCountry,
|
|
currentProducts,
|
|
fetchCategories,
|
|
selectCountry,
|
|
};
|
|
}
|