- Debian-based api image (bookworm-slim), docker/debian mirrors, prisma binaryTargets for openssl 3.0 - nginx: admin SPA under /admin, TLS via acme.sh (ZeroSSL) + auto-renewal cron, http->https redirect - prisma: add origin_goods.delisted migration, sync missing schema (good_image/tag_font_color/good_tags), fix users.createdAt Timestamptz - api: CORS wildcard reflection, helmet CORP cross-origin, price backfill in persistProductDetail, categoryIcon ancestor fallback, mediaByColor per-color gallery in public goods detail - admin: /admin base path (vite + router) - import-data.mjs: udt_name casting, serial sequence advance fix
166 lines
4.4 KiB
TypeScript
166 lines
4.4 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,
|
|
};
|
|
}
|