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:
@@ -1,28 +1,27 @@
|
||||
<script setup>
|
||||
const customerCases = ref([
|
||||
{ id: 1, platform: 'TEMU卖家', isPrimary: true, title: '复古水洗 经典重塑', description: '巡演灵感做旧款,粉丝自发应援首选', salesGrowth: '260%', monthlySales: '45W美元', image: 'https://picsum.photos/seed/case1/400/300' },
|
||||
{ id: 2, platform: 'TikTok卖家', isPrimary: false, title: '冠军荣耀 同款套装', description: '夺冠纪念球衣,球迷珍藏爆款', salesGrowth: '300%', monthlySales: '68W美元', image: 'https://picsum.photos/seed/case2/400/300' },
|
||||
{ id: 3, platform: 'SHEIN卖家', isPrimary: false, title: '潮流印花 全棉舒适', description: '自制设计印花图案,无尾货压力轻松售', salesGrowth: '180%', monthlySales: '22W美元', image: 'https://picsum.photos/seed/case3/400/300' },
|
||||
{ id: 4, platform: 'TEMU卖家', isPrimary: true, title: '复古水洗 经典重塑', description: '巡演灵感做旧款,粉丝自发应援首选', salesGrowth: '260%', monthlySales: '45W美元', image: 'https://picsum.photos/seed/case1/400/300' },
|
||||
{ id: 5, platform: 'TikTok卖家', isPrimary: false, title: '冠军荣耀 同款套装', description: '夺冠纪念球衣,球迷珍藏爆款', salesGrowth: '300%', monthlySales: '68W美元', image: 'https://picsum.photos/seed/case2/400/300' },
|
||||
{ id: 6, platform: 'SHEIN卖家', isPrimary: false, title: '潮流印花 全棉舒适', description: '自制设计印花图案,无尾货压力轻松售', salesGrowth: '180%', monthlySales: '22W美元', image: 'https://picsum.photos/seed/case3/400/300' },
|
||||
{ id: 1, platform: 'TEMU卖家', isPrimary: true, title: '复古水洗 经典重塑', description: '巡演灵感做旧款,粉丝自发应援首选', salesGrowth: '260%', monthlySales: '45W美元', image: '/case-temu-washed.png' },
|
||||
{ id: 2, platform: 'TikTok卖家', isPrimary: false, title: '冠军荣耀 同款套装', description: '夺冠纪念球衣,球迷珍藏爆款', salesGrowth: '300%', monthlySales: '68W美元', image: '/case-tiktok-kit.png' },
|
||||
{ id: 3, platform: 'SHEIN卖家', isPrimary: false, title: '潮流印花 全棉舒适', description: '自制设计印花图案,无尾货压力轻松售', salesGrowth: '180%', monthlySales: '22W美元', image: '/case-shein-print.png' },
|
||||
{ id: 4, platform: 'TEMU卖家', isPrimary: true, title: '天然彩棉 亲肤百搭', description: '达人联名系列,持续热卖无库存压力', salesGrowth: '210%', monthlySales: '38W美元', image: '/case-temu-cotton.png' },
|
||||
{ id: 5, platform: 'TikTok卖家', isPrimary: false, title: '日系简约 清凉无袖', description: '音乐节应援款,透气轻便人气高', salesGrowth: '240%', monthlySales: '32W美元', image: '/case-tiktok-sleeveless.png' },
|
||||
]);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="py-10 lg:py-16 bg-gray-50 overflow-hidden">
|
||||
<section class="cases-section py-10 lg:py-16 bg-gray-50 overflow-hidden">
|
||||
<div class="max-w-7xl mx-auto px-4 lg:px-6">
|
||||
<h2 class="text-[clamp(1.5rem,3vw,2.5rem)] font-bold text-gray-900 text-center mb-8 lg:mb-12">用户案例</h2>
|
||||
</div>
|
||||
|
||||
<div class="hidden lg:block overflow-hidden hover:[&>.marquee-track]:[animation-play-state:paused]">
|
||||
<div class="marquee-track flex gap-8 w-max animate-marquee">
|
||||
<div class="hidden lg:block overflow-hidden" aria-label="用户案例自动轮播">
|
||||
<div class="case-track flex gap-8 w-max">
|
||||
<div
|
||||
v-for="(testCase, idx) in [...customerCases, ...customerCases]"
|
||||
:key="'a-' + idx"
|
||||
class="flex-shrink-0 w-[350px] h-[420px] bg-white rounded-xl overflow-hidden"
|
||||
class="case-card relative flex-shrink-0 w-[350px] h-[420px] bg-white rounded-xl overflow-hidden"
|
||||
>
|
||||
<div class="relative h-48">
|
||||
<div class="case-media relative h-48">
|
||||
<span
|
||||
:class="['absolute top-4 left-4', testCase.isPrimary ? 'bg-primary' : 'bg-black', 'text-white px-3 py-1 rounded-full text-sm font-medium']"
|
||||
>
|
||||
@@ -30,28 +29,30 @@ const customerCases = ref([
|
||||
</span>
|
||||
<img :src="testCase.image" :alt="testCase.title" class="w-full h-full object-cover" />
|
||||
</div>
|
||||
<div class="p-6">
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<div>
|
||||
<div class="case-details p-6">
|
||||
<div class="case-metrics flex justify-between items-center mb-4">
|
||||
<div class="growth-metric">
|
||||
<p class="text-gray-500 text-sm">销量增长</p>
|
||||
<p class="text-2xl lg:text-3xl font-bold text-primary">{{ testCase.salesGrowth }}</p>
|
||||
</div>
|
||||
<div class="text-right">
|
||||
<div class="sales-metric text-right">
|
||||
<p class="text-gray-500 text-sm">月销售额</p>
|
||||
<p class="text-2xl lg:text-3xl font-bold text-primary">{{ testCase.monthlySales }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<h3 class="text-lg lg:text-xl font-bold text-gray-900 mb-2">{{ testCase.title }}</h3>
|
||||
<p class="text-gray-600 text-sm">{{ testCase.description }}</p>
|
||||
<div class="case-copy">
|
||||
<h3 class="text-lg lg:text-xl font-bold text-gray-900 mb-2">{{ testCase.title }}</h3>
|
||||
<p class="text-gray-600 text-sm">{{ testCase.description }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="lg:hidden overflow-x-auto pb-4 px-4">
|
||||
<div class="flex gap-4 w-max">
|
||||
<div class="lg:hidden overflow-hidden pb-4 px-4" aria-label="用户案例自动轮播">
|
||||
<div class="case-track-mobile flex gap-4 w-max">
|
||||
<div
|
||||
v-for="(testCase, idx) in customerCases"
|
||||
v-for="(testCase, idx) in [...customerCases, ...customerCases]"
|
||||
:key="'m-' + idx"
|
||||
class="flex-shrink-0 w-[280px] bg-white rounded-xl overflow-hidden"
|
||||
>
|
||||
@@ -82,3 +83,43 @@ const customerCases = ref([
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.case-track { animation: case-marquee 30s linear infinite; }
|
||||
.case-track-mobile { animation: case-marquee-mobile 30s linear infinite; }
|
||||
.case-track:hover,
|
||||
.case-track:focus-within,
|
||||
.case-track-mobile:hover,
|
||||
.case-track-mobile:focus-within { animation-play-state: paused; }
|
||||
|
||||
@keyframes case-marquee {
|
||||
to { transform: translateX(calc(-50% - 16px)); }
|
||||
}
|
||||
|
||||
@keyframes case-marquee-mobile {
|
||||
to { transform: translateX(calc(-50% - 8px)); }
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.case-track,
|
||||
.case-track-mobile { animation: none; }
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
.cases-section { height: 928px; padding-top: 54px; }
|
||||
.case-track { padding-left: 122px; }
|
||||
.case-card { width: 464px; height: 500px; }
|
||||
.case-media { height: 360px; background: #f8f9fb; }
|
||||
.case-media img { object-fit: contain; }
|
||||
.case-details { position: absolute; inset: 0; padding: 0; pointer-events: none; }
|
||||
.case-metrics { display: block; margin: 0; }
|
||||
.case-metrics > div { position: absolute; right: 16px; width: 240px; text-align: right; }
|
||||
.growth-metric { top: 148px; }
|
||||
.sales-metric { top: 253px; }
|
||||
.case-metrics p:first-child { margin-bottom: 2px; font-size: 16px; }
|
||||
.case-metrics p:last-child { font-size: 48px; line-height: 1.1; white-space: nowrap; }
|
||||
.case-copy { position: absolute; top: 392px; left: 0; width: 100%; padding: 0 24px; text-align: center; }
|
||||
.case-copy h3 { margin-bottom: 12px; font-size: 24px; }
|
||||
.case-copy p { font-size: 16px; white-space: nowrap; }
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user