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
+32 -23
View File
@@ -2,7 +2,7 @@
import type { RecommendCategory, SolutionColumn, NavBanner } from '~/composables/useNavData';
const PORTAL_URL = 'https://inkpod.vip/portal/search';
const LOGIN_URL = 'https://inkpod.vip/user/login';
const SHOW_EXTENDED_NAV = false;
const mobileMenuOpen = ref(false);
const recommendMenuOpen = ref(false);
@@ -60,6 +60,10 @@ function closeMobileMenu(): void {
mobileSolutionOpen.value = false;
}
function goToLogin(): void {
window.location.href = 'https://inkpod.vip/user/login';
}
function toggleMobileRecommend(): void {
mobileRecommendOpen.value = !mobileRecommendOpen.value;
mobileSolutionOpen.value = false;
@@ -80,11 +84,11 @@ const isProductCenter = computed(() => route.path === '/product-center');
<template>
<header class="pl-6 md:pl-10 lg:pl-16 pr-6 md:pr-10 lg:pr-20 flex items-center h-20 fixed top-0 left-0 right-0 z-50 bg-white border-b border-gray-100">
<div>
<img src="/logo-橙@2x.png" alt="Inkreach" class="h-12 lg:h-14" />
</div>
<NuxtLink to="/" aria-label="返回首页">
<img src="/logo-unified.svg" alt="Inkreach" class="h-12 w-[83px] lg:h-[56px] lg:w-[97px] object-contain" />
</NuxtLink>
<nav class="hidden lg:flex gap-6 text-xl flex-1 ml-8 items-center h-20">
<nav class="desktop-nav hidden lg:flex gap-6 flex-1 ml-8 items-center h-20">
<NuxtLink to="/product-center"
class="transition-colors"
:class="isProductCenter
@@ -92,13 +96,11 @@ const isProductCenter = computed(() => route.path === '/product-center');
: 'text-gray-400 hover:text-primary hover:underline underline-offset-14 decoration-primary decoration-2'"
>产品中心</NuxtLink>
<a :href="PORTAL_URL" target="_blank"
class="transition-colors"
:class="isProductCenter
? 'text-gray-400'
: 'hover:text-primary hover:underline underline-offset-14 decoration-primary decoration-2'"
class="text-gray-400 hover:text-primary hover:underline underline-offset-14 decoration-primary decoration-2 transition-colors"
>在线设计</a>
<div
v-if="SHOW_EXTENDED_NAV"
class="relative h-full flex items-center"
@mouseenter="openRecommendMenu"
@mouseleave="closeRecommendMenu"
@@ -107,7 +109,7 @@ const isProductCenter = computed(() => route.path === '/product-center');
class="transition-colors flex items-center gap-1"
:class="[
recommendMenuOpen ? 'text-primary' : '',
isProductCenter && !recommendMenuOpen ? 'text-gray-400' : '',
!recommendMenuOpen ? 'text-gray-400' : '',
]"
>
<span class="hover:underline underline-offset-14 decoration-primary decoration-2">选品推荐</span>
@@ -122,6 +124,7 @@ const isProductCenter = computed(() => route.path === '/product-center');
</div>
<div
v-if="SHOW_EXTENDED_NAV"
class="relative h-full flex items-center"
@mouseenter="openSolutionMenu"
@mouseleave="closeSolutionMenu"
@@ -130,7 +133,7 @@ const isProductCenter = computed(() => route.path === '/product-center');
class="transition-colors flex items-center gap-1"
:class="[
solutionMenuOpen ? 'text-primary' : '',
isProductCenter && !solutionMenuOpen ? 'text-gray-400' : '',
!solutionMenuOpen ? 'text-gray-400' : '',
]"
>
<span class="hover:underline underline-offset-14 decoration-primary decoration-2">解决方案</span>
@@ -144,13 +147,13 @@ const isProductCenter = computed(() => route.path === '/product-center');
</div>
</nav>
<div class="hidden lg:flex gap-6 text-xl items-center">
<a :href="LOGIN_URL" target="_blank"
<div class="desktop-nav hidden lg:flex gap-6 items-center">
<button type="button" @click="goToLogin"
class="hover:text-primary hover:underline underline-offset-12 decoration-primary decoration-2 transition-colors"
>登录</a>
<a :href="LOGIN_URL" target="_blank"
>登录</button>
<button type="button" @click="goToLogin"
class="text-white rounded-lg px-6 py-1.5 self-center hover:shadow-lg transition-all duration-300 transform hover:-translate-y-1 pulse-animation bg-primary"
>立即开始</a>
>立即开始</button>
</div>
<button
@@ -178,7 +181,9 @@ const isProductCenter = computed(() => route.path === '/product-center');
class="fixed top-0 right-0 h-full w-80 bg-white shadow-2xl z-50 lg:hidden flex flex-col overflow-y-auto"
>
<div class="flex justify-between items-center p-6 pb-4">
<img src="/logo-橙.png" alt="Inkreach" class="h-8" />
<NuxtLink to="/" aria-label="返回首页" @click="closeMobileMenu">
<img src="/logo-unified.svg" alt="Inkreach" class="h-[56px] w-[97px] object-contain" />
</NuxtLink>
<button @click="closeMobileMenu" class="p-2" aria-label="Close menu">
<i class="fa-solid fa-xmark text-2xl"></i>
</button>
@@ -194,7 +199,8 @@ const isProductCenter = computed(() => route.path === '/product-center');
class="py-3 hover:text-primary transition-colors"
>在线设计</a>
<div>
<!-- Temporarily hidden until recommendation content is ready. -->
<div v-if="SHOW_EXTENDED_NAV">
<button
class="w-full py-3 flex items-center justify-between hover:text-primary transition-colors"
@click="toggleMobileRecommend"
@@ -244,7 +250,8 @@ const isProductCenter = computed(() => route.path === '/product-center');
</Transition>
</div>
<div>
<!-- Temporarily hidden until solution content is ready. -->
<div v-if="SHOW_EXTENDED_NAV">
<button
class="w-full py-3 flex items-center justify-between hover:text-primary transition-colors"
@click="toggleMobileSolution"
@@ -291,12 +298,12 @@ const isProductCenter = computed(() => route.path === '/product-center');
</nav>
<div class="flex flex-col gap-4 px-6 mt-auto pb-6 pt-4">
<a :href="LOGIN_URL" target="_blank"
<button type="button" @click="goToLogin"
class="py-2.5 text-lg hover:text-primary transition-colors text-center"
>登录</a>
<a :href="LOGIN_URL" target="_blank"
>登录</button>
<button type="button" @click="goToLogin"
class="text-white rounded-lg px-6 py-3 text-center hover:shadow-lg transition-all duration-300 pulse-animation bg-primary"
>立即开始</a>
>立即开始</button>
</div>
</div>
</Transition>
@@ -304,6 +311,8 @@ const isProductCenter = computed(() => route.path === '/product-center');
</template>
<style scoped>
.desktop-nav { font-size: 18px; }
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.3s ease;