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:
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user