本次提交主要进行了以下修改: 1. 在 `app.vue` 中引入了 `RouteLoader` 组件,以提升路由切换时的用户体验。 2. 在 `components` 目录下新增了 `RouteLoader.vue` 组件,负责显示路由加载进度条。 3. 新增了 `BackToTop.vue` 组件,提供返回顶部的功能,增强页面导航体验。 4. 更新了 `TheFooter.vue` 和 `TheHeader.vue`,分别引入返回顶部按钮和调整了样式以匹配新的设计。 5. 在 `nuxt.config.ts` 中添加了页面过渡效果配置,提升页面切换的流畅性。 6. 更新了样式文件,调整了颜色变量和过渡效果,确保一致性。 这些改动旨在提升用户体验和页面的可用性。master
@@ -1,5 +1,6 @@ | |||
<template> | |||
<div> | |||
<RouteLoader /> | |||
<NuxtLayout> | |||
<NuxtPage /> | |||
</NuxtLayout> |
@@ -53,7 +53,7 @@ | |||
/* 颜色变量 */ | |||
--color-primary: #3b82f6; | |||
--color-secondary: #10b981; | |||
--color-accent: #f59e0b; | |||
--color-accent: #22d3ee; | |||
--color-danger: #ef4444; | |||
--color-success: #10b981; | |||
--color-warning: #f59e0b; | |||
@@ -295,4 +295,36 @@ html[lang="ja"] body { | |||
.prose details[open] summary { | |||
margin-bottom: 0.5rem; | |||
} | |||
/* 页面过渡动画 */ | |||
.page-enter-active, | |||
.page-leave-active { | |||
transition: all 0.3s; | |||
} | |||
.page-enter-from, | |||
.page-leave-to { | |||
opacity: 0; | |||
transform: translateY(10px); | |||
} | |||
/* 加载指示器样式 */ | |||
.route-loader { | |||
position: fixed; | |||
top: 0; | |||
left: 0; | |||
height: 3px; | |||
background-color: var(--color-accent); | |||
z-index: 9999; | |||
transition: width 0.2s ease; | |||
} | |||
.route-loader-enter-active, | |||
.route-loader-leave-active { | |||
transition: opacity 0.3s; | |||
} | |||
.route-loader-enter-from, | |||
.route-loader-leave-to { | |||
opacity: 0; | |||
} |
@@ -0,0 +1,60 @@ | |||
<template> | |||
<div | |||
v-show="showBackToTop" | |||
@click="scrollToTop" | |||
class="fixed right-5 bottom-5 bg-stone-800 hover:bg-stone-700 text-white w-10 h-10 rounded-full flex items-center justify-center cursor-pointer transition-all duration-300 z-50 shadow-lg" | |||
:aria-label="t('common.backToTop')" | |||
> | |||
<svg | |||
xmlns="http://www.w3.org/2000/svg" | |||
class="h-5 w-5" | |||
viewBox="0 0 20 20" | |||
fill="currentColor" | |||
> | |||
<path | |||
fill-rule="evenodd" | |||
d="M14.707 12.707a1 1 0 01-1.414 0L10 9.414l-3.293 3.293a1 1 0 01-1.414-1.414l4-4a1 1 0 011.414 0l4 4a1 1 0 010 1.414z" | |||
clip-rule="evenodd" | |||
/> | |||
</svg> | |||
</div> | |||
</template> | |||
<script setup lang="ts"> | |||
/** | |||
* 返回顶部按钮组件 | |||
* 当页面滚动超过一定距离时显示,点击后平滑滚动回顶部 | |||
*/ | |||
import { useI18n } from 'vue-i18n'; | |||
const { t } = useI18n(); | |||
const showBackToTop = ref(false); | |||
const scrollThreshold = 300; // 显示按钮的滚动阈值 | |||
/** | |||
* 监听滚动事件,控制按钮显示 | |||
*/ | |||
function checkScroll() { | |||
showBackToTop.value = window.scrollY > scrollThreshold; | |||
} | |||
/** | |||
* 滚动到页面顶部 | |||
*/ | |||
function scrollToTop() { | |||
window.scrollTo({ | |||
top: 0, | |||
behavior: "smooth", | |||
}); | |||
} | |||
// 生命周期钩子 | |||
onMounted(() => { | |||
window.addEventListener("scroll", checkScroll); | |||
checkScroll(); // 初始检查 | |||
}); | |||
onUnmounted(() => { | |||
window.removeEventListener("scroll", checkScroll); | |||
}); | |||
</script> |
@@ -1,24 +1,30 @@ | |||
<template> | |||
<div class="relative inline-block text-left" ref="dropdownContainerRef" @mouseleave="handleMouseLeave"> | |||
<div class="relative inline-block text-left" ref="dropdownContainerRef"> | |||
<div | |||
@mouseenter="handleMouseEnter" | |||
@click="toggleDropdown" | |||
class="flex items-center gap-1 text-white opacity-80 text-sm hover:opacity-100 cursor-pointer py-2 transition-opacity" | |||
> | |||
<i class="icon-i18n mr-1"></i> | |||
<span>{{ currentLocaleName || "Language" }}</span> | |||
<svg | |||
class="h-3 w-3 text-white/60 transition-transform duration-200" | |||
:class="{'rotate-180': isDropdownOpen}" | |||
fill="none" viewBox="0 0 24 24" stroke="currentColor" | |||
:class="{ 'rotate-180': isDropdownOpen }" | |||
fill="none" | |||
viewBox="0 0 24 24" | |||
stroke="currentColor" | |||
> | |||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="3" d="M19 9l-7 7-7-7" /> | |||
<path | |||
stroke-linecap="round" | |||
stroke-linejoin="round" | |||
stroke-width="3" | |||
d="M19 9l-7 7-7-7" | |||
/> | |||
</svg> | |||
</div> | |||
<transition name="fade-down"> | |||
<div | |||
v-if="isDropdownOpen" | |||
@mouseenter="handleMouseEnter" | |||
class="absolute right-0 top-full mt-1 w-max min-w-[120px] bg-slate-800/90 backdrop-blur-md rounded-lg shadow-xl p-2 z-10" | |||
> | |||
<ul class="space-y-1"> | |||
@@ -26,7 +32,11 @@ | |||
<button | |||
@click="selectLanguage(locale.code)" | |||
class="block w-full text-left text-base text-gray-200 hover:text-white hover:bg-white/10 transition-all duration-150 rounded px-3 py-1.5" | |||
:class="[ locale.code === currentLocale ? 'font-bold opacity-100 bg-white/15' : '' ]" | |||
:class="[ | |||
locale.code === currentLocale | |||
? 'font-bold opacity-100 bg-white/15' | |||
: '', | |||
]" | |||
> | |||
{{ locale.name }} | |||
</button> | |||
@@ -40,10 +50,10 @@ | |||
<script setup lang="ts"> | |||
/** | |||
* 语言切换组件 - 下拉菜单样式 | |||
* 支持切换配置的语言 | |||
* 支持切换配置的语言,同时支持移动端和桌面端 | |||
*/ | |||
import { ref, computed } from "vue"; | |||
import { useI18n } from "#imports"; // 修正 useI18n 导入 | |||
import { ref, computed, onMounted, onBeforeUnmount } from "vue"; | |||
import { useI18n } from "#imports"; | |||
// 定义语言代码的类型,应该与 i18n 配置中的一致 | |||
type LocaleCode = "zh" | "en" | "ja"; // 你需要根据你的 i18n 配置更新这个类型 | |||
@@ -51,8 +61,7 @@ type LocaleCode = "zh" | "en" | "ja"; // 你需要根据你的 i18n 配置更新 | |||
const { locale, locales, setLocale } = useI18n(); | |||
const currentLocale = computed(() => locale.value); | |||
const isDropdownOpen = ref(false); | |||
const dropdownContainerRef = ref(null); // 保留 ref,虽然 onClickOutside 移除了,但未来可能有用 | |||
let leaveTimeout: ReturnType<typeof setTimeout> | null = null; // Timeout for mouseleave delay | |||
const dropdownContainerRef = ref(null); | |||
// 可用语言列表 | |||
const availableLocales = computed(() => { | |||
@@ -64,8 +73,8 @@ const availableLocales = computed(() => { | |||
// 当前选中语言的名称 | |||
const currentLocaleName = computed(() => { | |||
const current = availableLocales.value.find((l: { code: string; name: string }) => | |||
l.code === locale.value | |||
const current = availableLocales.value.find( | |||
(l: { code: string; name: string }) => l.code === locale.value | |||
); | |||
return current ? current.name : ""; | |||
}); | |||
@@ -85,23 +94,44 @@ async function selectLanguage(langCode: string) { | |||
// 这里可以添加用户反馈,例如显示一个错误提示 | |||
} | |||
} | |||
handleMouseLeave(); // 使用 handleMouseLeave 关闭 | |||
closeDropdown(); | |||
} | |||
// --- Dropdown Logic (like Products dropdown) --- | |||
function handleMouseEnter() { | |||
if (leaveTimeout) { | |||
clearTimeout(leaveTimeout); | |||
leaveTimeout = null; | |||
} | |||
isDropdownOpen.value = true; | |||
/** | |||
* 切换下拉菜单的显示状态 | |||
*/ | |||
function toggleDropdown() { | |||
isDropdownOpen.value = !isDropdownOpen.value; | |||
} | |||
/** | |||
* 关闭下拉菜单 | |||
*/ | |||
function closeDropdown() { | |||
isDropdownOpen.value = false; | |||
} | |||
function handleMouseLeave() { | |||
// Delay closing the dropdown slightly | |||
leaveTimeout = setTimeout(() => { | |||
isDropdownOpen.value = false; | |||
}, 150); // 150ms delay | |||
/** | |||
* 处理点击外部区域时关闭下拉菜单 | |||
*/ | |||
function handleClickOutside(event: MouseEvent) { | |||
if ( | |||
dropdownContainerRef.value && | |||
!(dropdownContainerRef.value as HTMLElement).contains( | |||
event.target as Node | |||
) && | |||
isDropdownOpen.value | |||
) { | |||
closeDropdown(); | |||
} | |||
} | |||
// --- End Dropdown Logic --- | |||
// 监听全局点击事件,用于关闭下拉菜单 | |||
onMounted(() => { | |||
document.addEventListener("click", handleClickOutside); | |||
}); | |||
onBeforeUnmount(() => { | |||
document.removeEventListener("click", handleClickOutside); | |||
}); | |||
</script> |
@@ -0,0 +1,98 @@ | |||
<template> | |||
<Transition name="route-loader"> | |||
<div v-if="isLoading" class="route-loader" :style="{ width: `${progress}%` }"></div> | |||
</Transition> | |||
</template> | |||
<script setup lang="ts"> | |||
/** | |||
* 路由加载指示器组件 | |||
* 在路由切换时显示顶部进度条,提升用户体验 | |||
*/ | |||
import { ref, onMounted, onUnmounted } from 'vue'; | |||
import { useRouter } from 'vue-router'; | |||
// 加载状态和进度 | |||
const isLoading = ref(false); | |||
const progress = ref(0); | |||
const timer = ref<NodeJS.Timeout | null>(null); | |||
const progressTimer = ref<NodeJS.Timeout | null>(null); | |||
// 获取路由器实例 | |||
const router = useRouter(); | |||
/** | |||
* 开始加载动画 | |||
* 显示进度条并逐步增加进度 | |||
*/ | |||
function startLoading() { | |||
// 重置并显示加载条 | |||
isLoading.value = true; | |||
progress.value = 0; | |||
// 清除之前可能存在的计时器 | |||
if (progressTimer.value) clearInterval(progressTimer.value); | |||
// 创建随机增长的进度条,最多到90% | |||
progressTimer.value = setInterval(() => { | |||
// 根据当前进度计算下一步增长量,进度越高增长越慢 | |||
const remaining = 100 - progress.value; | |||
const increment = remaining * 0.1 * Math.random(); | |||
// 增加进度,但确保不超过90% | |||
progress.value = Math.min(progress.value + increment, 90); | |||
}, 300); | |||
} | |||
/** | |||
* 完成加载动画 | |||
* 将进度迅速增加到100%,然后隐藏进度条 | |||
*/ | |||
function completeLoading() { | |||
// 清除进度增长定时器 | |||
if (progressTimer.value) { | |||
clearInterval(progressTimer.value); | |||
progressTimer.value = null; | |||
} | |||
// 完成加载动画,设置100% | |||
progress.value = 100; | |||
// 设置延时以显示完成状态 | |||
if (timer.value) clearTimeout(timer.value); | |||
timer.value = setTimeout(() => { | |||
isLoading.value = false; | |||
progress.value = 0; | |||
}, 300); | |||
} | |||
/** | |||
* 监听路由事件 | |||
* 在路由切换开始和结束时触发相应的动画 | |||
*/ | |||
onMounted(() => { | |||
// 路由开始切换 | |||
router.beforeEach(() => { | |||
startLoading(); | |||
return true; | |||
}); | |||
// 路由切换完成 | |||
router.afterEach(() => { | |||
completeLoading(); | |||
}); | |||
// 路由切换出错 | |||
router.onError(() => { | |||
completeLoading(); | |||
}); | |||
}); | |||
/** | |||
* 组件卸载时清理计时器 | |||
*/ | |||
onUnmounted(() => { | |||
if (timer.value) clearTimeout(timer.value); | |||
if (progressTimer.value) clearInterval(progressTimer.value); | |||
}); | |||
</script> |
@@ -75,6 +75,8 @@ | |||
</div> | |||
</div> | |||
</div> | |||
<!-- 返回顶部按钮 --> | |||
<BackToTop /> | |||
</footer> | |||
</template> | |||
@@ -253,7 +253,7 @@ | |||
:placeholder=" | |||
t('common.searchPlaceholder') || 'Enter search term...' | |||
" | |||
class="w-full p-3 pl-10 pr-10 rounded bg-slate-700 text-white border border-slate-600 focus:outline-none focus:border-blue-500 focus:ring-1 focus:ring-blue-500" | |||
class="w-full p-3 pl-10 pr-10 rounded bg-slate-700 text-white border border-slate-600 focus:outline-none focus:border-cyan-500 focus:ring-1 focus:ring-cyan-500" | |||
@input="handleSearch" | |||
/> | |||
<!-- Clear Icon --> | |||
@@ -263,7 +263,7 @@ | |||
@click="clearSearch" | |||
> | |||
<button | |||
class="flex items-center justify-center w-8 h-8 rounded-full bg-zinc-700/80 hover:bg-blue-500/90 text-gray-300 hover:text-white transition-all duration-200 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500" | |||
class="flex items-center justify-center w-8 h-8 rounded-full bg-zinc-700/80 hover:bg-cyan-500/90 text-gray-300 hover:text-white transition-all duration-200 focus:outline-none focus-visible:ring-2 focus-visible:ring-cyan-500" | |||
:aria-label="t('common.clear')" | |||
tabindex="0" | |||
type="button" | |||
@@ -295,7 +295,7 @@ | |||
</div> | |||
<div | |||
v-if="searchResults.length > 0" | |||
class="mt-4 max-h-[60vh] overflow-y-auto pr-2 space-y-4 search-results" | |||
class="mt-4 max-h-[50vh] overflow-y-auto pr-2 space-y-4 search-results" | |||
> | |||
<div | |||
v-for="result in searchResults" | |||
@@ -308,7 +308,7 @@ | |||
@click="closeSearch" | |||
> | |||
<div | |||
class="text-white font-medium mb-1 group-hover:text-blue-400 transition-colors" | |||
class="text-white font-medium mb-1 group-hover:text-cyan-400 transition-colors" | |||
v-html="highlightText(result.title, searchQuery)" | |||
></div> | |||
<div | |||
@@ -339,7 +339,7 @@ | |||
v-for="keyword in hotKeywords" | |||
:key="keyword" | |||
@click="searchHotKeyword(keyword)" | |||
class="px-4 py-1.5 bg-slate-700 text-white/80 rounded-full text-sm hover:bg-blue-600 hover:text-white transition-colors duration-200" | |||
class="px-4 py-1.5 bg-slate-700 text-white/80 rounded-full text-sm hover:bg-cyan-600 hover:text-white transition-colors duration-200" | |||
> | |||
{{ keyword }} | |||
</button> |
@@ -14,6 +14,7 @@ export default { | |||
clear: "Clear", | |||
noResults: "No results found", | |||
searching: "Searching...", | |||
backToTop: "Back to Top", | |||
breadcrumb: { | |||
home: "Home", | |||
privacy: "Privacy Policy", |
@@ -14,6 +14,7 @@ export default { | |||
clear: "クリア", | |||
noResults: "関連する結果はありません", | |||
searching: "検索中...", | |||
backToTop: "トップに戻る", | |||
breadcrumb: { | |||
home: "ホーム", | |||
privacy: "プライバシーポリシー", |
@@ -14,6 +14,7 @@ export default { | |||
clear: "清除", | |||
noResults: "没有找到相关结果", | |||
searching: "搜索中...", | |||
backToTop: "返回顶部", | |||
footer: { | |||
productsLinks: { | |||
title: "产品", |
@@ -109,16 +109,28 @@ export default defineNuxtConfig({ | |||
renderJsonPayloads: false, | |||
}, | |||
devServer: { | |||
host: "0.0.0.0", | |||
}, | |||
compatibilityDate: "2025-05-07", | |||
// 页面过渡效果配置 | |||
app: { | |||
head: { | |||
charset: "utf-8", | |||
viewport: "width=device-width, initial-scale=1", | |||
}, | |||
pageTransition: { | |||
name: "page", | |||
mode: "out-in", | |||
}, | |||
}, | |||
// 路由配置 | |||
router: { | |||
options: { | |||
linkActiveClass: "active", | |||
}, | |||
}, | |||
devServer: { | |||
host: "0.0.0.0", | |||
}, | |||
compatibilityDate: "2025-05-07", | |||
}); |
@@ -5,7 +5,7 @@ | |||
<div v-if="isLoading" class="flex justify-center py-12"> | |||
<!-- 加载中 --> | |||
<div | |||
class="animate-spin h-8 w-8 border-4 border-blue-500 rounded-full border-t-transparent" | |||
class="animate-spin h-8 w-8 border-4 border-cyan-400 rounded-full border-t-transparent" | |||
></div> | |||
</div> | |||
@@ -33,7 +33,7 @@ | |||
{{ t("about.companyInfo.name") }} | |||
</h1> | |||
<div | |||
class="text-stone-400 text-xl leading-relaxed text-center max-w-2xl break-words whitespace-pre-wrap" | |||
class="text-[#71717A] text-xl leading-relaxed text-center max-w-2xl break-words whitespace-pre-wrap" | |||
> | |||
{{ t("about.companyInfo.description") }} | |||
</div> | |||
@@ -52,12 +52,12 @@ | |||
> | |||
{{ t("about.overview.companyInfo") }} | |||
<span | |||
class="absolute -bottom-2 left-0 w-12 h-1 bg-gradient-to-r from-blue-500 to-blue-300 rounded-full" | |||
class="absolute -bottom-2 left-0 w-12 h-1 bg-cyan-400 rounded-full" | |||
></span> | |||
</h2> | |||
<div class="flex flex-col gap-3"> | |||
<div class="flex flex-col gap-1"> | |||
<span class="text-stone-400 text-base">{{ | |||
<span class="text-[#71717A] text-base">{{ | |||
t("about.overview.companyName") | |||
}}</span> | |||
<span class="text-white text-lg font-bold">{{ | |||
@@ -65,7 +65,7 @@ | |||
}}</span> | |||
</div> | |||
<div class="flex flex-col gap-1"> | |||
<span class="text-stone-400 text-base">{{ | |||
<span class="text-[#71717A] text-base">{{ | |||
t("about.overview.englishName") | |||
}}</span> | |||
<span class="text-white text-lg font-bold">{{ | |||
@@ -73,7 +73,7 @@ | |||
}}</span> | |||
</div> | |||
<div class="flex flex-col gap-1"> | |||
<span class="text-stone-400 text-base">{{ | |||
<span class="text-[#71717A] text-base">{{ | |||
t("about.overview.established") | |||
}}</span> | |||
<span class="text-white text-lg font-bold">{{ | |||
@@ -81,7 +81,7 @@ | |||
}}</span> | |||
</div> | |||
<div class="flex flex-col gap-1"> | |||
<span class="text-stone-400 text-base">{{ | |||
<span class="text-[#71717A] text-base">{{ | |||
t("about.overview.ceo") | |||
}}</span> | |||
<span class="text-white text-lg font-bold">{{ | |||
@@ -89,7 +89,7 @@ | |||
}}</span> | |||
</div> | |||
<div class="flex flex-col gap-1"> | |||
<span class="text-stone-400 text-base">{{ | |||
<span class="text-[#71717A] text-base">{{ | |||
t("about.overview.employees") | |||
}}</span> | |||
<span class="text-white text-lg font-bold">{{ | |||
@@ -97,7 +97,7 @@ | |||
}}</span> | |||
</div> | |||
<div class="flex flex-col gap-1"> | |||
<span class="text-stone-400 text-base">{{ | |||
<span class="text-[#71717A] text-base">{{ | |||
t("about.overview.location") | |||
}}</span> | |||
<span class="text-white text-lg font-bold">{{ | |||
@@ -115,10 +115,10 @@ | |||
> | |||
{{ t("about.overview.philosophy") }} | |||
<span | |||
class="absolute -bottom-2 left-0 w-12 h-1 bg-gradient-to-r from-blue-500 to-blue-300 rounded-full" | |||
class="absolute -bottom-2 left-0 w-12 h-1 bg-cyan-400 rounded-full" | |||
></span> | |||
</h2> | |||
<div class="text-stone-400 text-lg leading-relaxed"> | |||
<div class="text-[#71717A] text-lg leading-relaxed"> | |||
{{ t("about.companyInfo.philosophy") }} | |||
</div> | |||
</div> | |||
@@ -131,32 +131,32 @@ | |||
> | |||
{{ t("about.overview.contact") }} | |||
<span | |||
class="absolute -bottom-2 left-0 w-12 h-1 bg-gradient-to-r from-blue-500 to-blue-300 rounded-full" | |||
class="absolute -bottom-2 left-0 w-12 h-1 bg-cyan-400 rounded-full" | |||
></span> | |||
</h2> | |||
<div class="flex flex-col gap-3"> | |||
<div class="flex flex-col gap-1"> | |||
<span class="text-stone-400 text-base">{{ | |||
<span class="text-[#71717A] text-base">{{ | |||
t("about.overview.email") | |||
}}</span> | |||
<a | |||
:href="'mailto:' + companyInfo.email" | |||
class="text-white text-lg font-bold hover:text-blue-400 transition-colors" | |||
class="text-white text-lg font-bold hover:text-cyan-400 transition-colors" | |||
>{{ companyInfo.email }}</a | |||
> | |||
</div> | |||
<div class="flex flex-col gap-1"> | |||
<span class="text-stone-400 text-base">{{ | |||
<span class="text-[#71717A] text-base">{{ | |||
t("about.overview.tel") | |||
}}</span> | |||
<a | |||
:href="'tel:' + companyInfo.tel.replace(/[^0-9]/g, '')" | |||
class="text-white text-lg font-bold hover:text-blue-400 transition-colors" | |||
class="text-white text-lg font-bold hover:text-cyan-400 transition-colors" | |||
>{{ companyInfo.tel }}</a | |||
> | |||
</div> | |||
<div class="flex flex-col gap-1"> | |||
<span class="text-stone-400 text-base">{{ | |||
<span class="text-[#71717A] text-base">{{ | |||
t("about.overview.fax") | |||
}}</span> | |||
<span class="text-white text-lg font-bold">{{ | |||
@@ -164,7 +164,7 @@ | |||
}}</span> | |||
</div> | |||
<div class="flex flex-col gap-1"> | |||
<span class="text-stone-400 text-base">{{ | |||
<span class="text-[#71717A] text-base">{{ | |||
t("about.overview.businessHours") | |||
}}</span> | |||
<span class="text-white text-lg font-bold">{{ | |||
@@ -173,7 +173,7 @@ | |||
</div> | |||
</div> | |||
<div class="flex flex-col gap-1 mt-2"> | |||
<span class="text-stone-400 text-base">{{ | |||
<span class="text-[#71717A] text-base">{{ | |||
t("about.overview.businessActivities") | |||
}}</span> | |||
<div class="text-white text-base font-bold space-y-1"> |
@@ -4,7 +4,7 @@ | |||
<ErrorBoundary :error="error"> | |||
<div v-if="isLoading" class="flex justify-center py-12"> | |||
<div | |||
class="animate-spin h-8 w-8 border-4 border-blue-500 rounded-full border-t-transparent" | |||
class="animate-spin h-8 w-8 border-4 border-cyan-400 rounded-full border-t-transparent" | |||
></div> | |||
</div> | |||
@@ -45,13 +45,13 @@ | |||
v-model="form.name" | |||
type="text" | |||
id="name" | |||
class="peer block w-full appearance-none bg-transparent border-0 border-b-2 px-1 pt-5 pb-2 text-base text-gray-100 focus:outline-none focus:ring-0 placeholder-transparent transition-colors duration-300 focus:border-b-[3px] border-gray-600 focus:border-blue-500" | |||
class="peer block w-full appearance-none bg-transparent border-0 border-b-2 px-1 pt-5 pb-2 text-base text-gray-100 focus:outline-none focus:ring-0 placeholder-transparent transition-colors duration-300 focus:border-b-[3px] border-gray-600 focus:border-cyan-400" | |||
:placeholder="t('contact.name')" | |||
required | |||
/> | |||
<label | |||
for="name" | |||
class="absolute left-1 top-5 origin-[0] -translate-y-4 scale-75 transform text-sm duration-300 peer-placeholder-shown:translate-y-0 peer-placeholder-shown:scale-100 peer-focus:-translate-y-4 peer-focus:scale-75 peer-focus:font-medium text-gray-400 peer-focus:text-blue-400" | |||
class="absolute left-1 top-5 origin-[0] -translate-y-4 scale-75 transform text-sm duration-300 peer-placeholder-shown:translate-y-0 peer-placeholder-shown:scale-100 peer-focus:-translate-y-4 peer-focus:scale-75 peer-focus:font-medium text-gray-400 peer-focus:text-cyan-400" | |||
> | |||
{{ t("contact.name") }} | |||
</label> | |||
@@ -62,13 +62,13 @@ | |||
v-model="form.email" | |||
type="email" | |||
id="email" | |||
class="peer block w-full appearance-none bg-transparent border-0 border-b-2 px-1 pt-5 pb-2 text-base text-gray-100 focus:outline-none focus:ring-0 placeholder-transparent transition-colors duration-300 focus:border-b-[3px] border-gray-600 focus:border-blue-500" | |||
class="peer block w-full appearance-none bg-transparent border-0 border-b-2 px-1 pt-5 pb-2 text-base text-gray-100 focus:outline-none focus:ring-0 placeholder-transparent transition-colors duration-300 focus:border-b-[3px] border-gray-600 focus:border-cyan-400" | |||
:placeholder="t('contact.email')" | |||
required | |||
/> | |||
<label | |||
for="email" | |||
class="absolute left-1 top-5 origin-[0] -translate-y-4 scale-75 transform text-sm duration-300 peer-placeholder-shown:translate-y-0 peer-placeholder-shown:scale-100 peer-focus:-translate-y-4 peer-focus:scale-75 peer-focus:font-medium text-gray-400 peer-focus:text-blue-400" | |||
class="absolute left-1 top-5 origin-[0] -translate-y-4 scale-75 transform text-sm duration-300 peer-placeholder-shown:translate-y-0 peer-placeholder-shown:scale-100 peer-focus:-translate-y-4 peer-focus:scale-75 peer-focus:font-medium text-gray-400 peer-focus:text-cyan-400" | |||
> | |||
{{ t("contact.email") }} | |||
</label> | |||
@@ -78,13 +78,13 @@ | |||
<textarea | |||
v-model="form.message" | |||
id="message" | |||
class="peer block w-full appearance-none bg-transparent border-0 border-b-2 px-1 pt-5 pb-2 text-base text-gray-100 focus:outline-none focus:ring-0 placeholder-transparent h-36 resize-none transition-colors duration-300 focus:border-b-[3px] border-gray-600 focus:border-blue-500" | |||
class="peer block w-full appearance-none bg-transparent border-0 border-b-2 px-1 pt-5 pb-2 text-base text-gray-100 focus:outline-none focus:ring-0 placeholder-transparent h-36 resize-none transition-colors duration-300 focus:border-b-[3px] border-gray-600 focus:border-cyan-400" | |||
:placeholder="t('contact.message')" | |||
required | |||
></textarea> | |||
<label | |||
for="message" | |||
class="absolute left-1 top-5 origin-[0] -translate-y-4 scale-75 transform text-sm duration-300 peer-placeholder-shown:translate-y-0 peer-placeholder-shown:scale-100 peer-focus:-translate-y-4 peer-focus:scale-75 peer-focus:font-medium text-gray-400 peer-focus:text-blue-400" | |||
class="absolute left-1 top-5 origin-[0] -translate-y-4 scale-75 transform text-sm duration-300 peer-placeholder-shown:translate-y-0 peer-placeholder-shown:scale-100 peer-focus:-translate-y-4 peer-focus:scale-75 peer-focus:font-medium text-gray-400 peer-focus:text-cyan-400" | |||
> | |||
{{ t("contact.message") }} | |||
</label> | |||
@@ -102,7 +102,7 @@ | |||
:class="[ | |||
captcha.error.value | |||
? 'border-red-500 focus:border-red-500' | |||
: 'border-gray-600 focus:border-blue-500', | |||
: 'border-gray-600 focus:border-cyan-400', | |||
]" | |||
:placeholder="t('contact.captcha')" | |||
required | |||
@@ -116,7 +116,7 @@ | |||
:class="[ | |||
captcha.error.value | |||
? 'text-red-400 peer-focus:text-red-400' | |||
: 'text-gray-400 peer-focus:text-blue-400', | |||
: 'text-gray-400 peer-focus:text-cyan-400', | |||
]" | |||
> | |||
{{ t("contact.captcha") }} | |||
@@ -132,7 +132,7 @@ | |||
<button | |||
type="button" | |||
@click="captcha.generateCaptcha()" | |||
class="flex-shrink-0 p-2 text-gray-500 hover:text-blue-400 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2 focus-visible:ring-offset-gray-800 rounded-full hover:bg-gray-700/50 transition-all duration-200 ease-in-out" | |||
class="flex-shrink-0 p-2 text-gray-500 hover:text-cyan-400 focus:outline-none focus-visible:ring-2 focus-visible:ring-cyan-400 focus-visible:ring-offset-2 focus-visible:ring-offset-gray-800 rounded-full hover:bg-gray-700/50 transition-all duration-200 ease-in-out" | |||
:aria-label="t('contact.refreshCaptcha')" | |||
:title="t('contact.refreshCaptcha')" | |||
> | |||
@@ -163,7 +163,7 @@ | |||
<button | |||
type="submit" | |||
class="bg-gradient-to-r from-blue-700 to-blue-400 text-white text-base font-normal py-4 px-8 rounded-lg transition-all duration-300 hover:scale-105 hover:shadow-lg" | |||
class="bg-cyan-400 text-zinc-900 relative text-base font-normal py-4 px-8 rounded-lg transition-all duration-300 hover:shadow-lg hover:bg-cyan-500" | |||
:disabled="isSubmitting" | |||
> | |||
{{ |
@@ -9,40 +9,62 @@ | |||
</div> | |||
<div v-else> | |||
<div class="max-w-full xl:px-2 lg:px-2 md:px-4 px-4 mt-6 mb-20"> | |||
<div class="max-w-full xl:px-2 lg:px-2 md:px-4 px-4 mt-6 mb-12"> | |||
<div class="max-w-screen-2xl mx-auto"> | |||
<nuxt-link | |||
:to="`${homepagePath}/`" | |||
class="justify-start text-white/60 text-base font-normal" | |||
class="justify-start text-white/60 text-sm md:text-base font-normal hover:text-white transition-colors duration-300" | |||
>{{ t("common.home") }}</nuxt-link | |||
> | |||
<span class="text-white/60 text-base font-normal px-2"> / </span> | |||
<span class="text-white/60 text-sm md:text-base font-normal px-2"> | |||
/ | |||
</span> | |||
<nuxt-link | |||
:to="`${homepagePath}/faq`" | |||
class="text-white text-base font-normal" | |||
class="text-white text-sm md:text-base font-normal" | |||
>{{ t("faq.title") }}</nuxt-link | |||
> | |||
</div> | |||
</div> | |||
<div | |||
class="max-w-full mb-12 md:mb-20 lg:mb-32 xl:px-2 lg:px-2 md:px-4 px-4" | |||
class="max-w-full mb-12 md:mb-20 lg:mb-32 xl:mb-20 xl:px-8 lg:px-6 md:px-4 px-4" | |||
> | |||
<div class="max-w-screen-2xl mx-auto"> | |||
<div class="w-full grid grid-cols-1 md:grid-cols-10 gap-8 md:gap-2"> | |||
<div class="w-full grid grid-cols-1 md:grid-cols-12 gap-8 md:gap-4"> | |||
<!-- 左侧分类导航 --> | |||
<div class="col-span-1 md:col-span-2"> | |||
<div class="flex flex-col gap-4"> | |||
<div class="text-white text-3xl font-medium"> | |||
{{ t("faq.category") }} | |||
<div class="col-span-1 md:col-span-3 flex flex-col gap-4 sm:gap-6 md:gap-8 lg:gap-10 xl:gap-12 2xl:gap-16 mb-4 sm:mb-6 md:mb-8 lg:mb-10 xl:mb-12 2xl:mb-16 pr-4"> | |||
<div class="flex flex-col gap-2 sm:gap-3 md:gap-4 lg:gap-5 xl:gap-6"> | |||
<div class="flex justify-between items-center"> | |||
<div class="text-white text-base sm:text-lg md:text-xl lg:text-2xl xl:text-3xl font-medium"> | |||
{{ t("faq.category") }} | |||
</div> | |||
<button | |||
v-if="selectedCategory !== categoriesList[0]" | |||
@click="handleCategoryFilter(categoriesList[0])" | |||
class="flex items-center gap-1 px-1.5 py-0.5 sm:px-2 sm:py-1 md:px-2.5 md:py-1.5 lg:px-3 lg:py-2 text-xs sm:text-sm md:text-base text-white/60 hover:text-white bg-zinc-800/50 hover:bg-zinc-700/50 rounded-lg transition-all duration-300 active:scale-95" | |||
> | |||
<svg | |||
xmlns="http://www.w3.org/2000/svg" | |||
class="h-2.5 w-2.5 sm:h-3 sm:w-3 md:h-4 md:w-4" | |||
viewBox="0 0 20 20" | |||
fill="currentColor" | |||
> | |||
<path | |||
fill-rule="evenodd" | |||
d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" | |||
clip-rule="evenodd" | |||
/> | |||
</svg> | |||
</button> | |||
</div> | |||
<div class="flex flex-col gap-4 w-fit"> | |||
<div class="flex flex-row md:flex-col gap-1.5 sm:gap-2 md:gap-2.5 lg:gap-3 xl:gap-4 w-full md:w-fit overflow-x-auto md:overflow-x-visible pb-2 md:pb-0 whitespace-nowrap md:whitespace-normal"> | |||
<div | |||
v-for="category in categoriesList" | |||
:key="category" | |||
@click="handleCategoryFilter(category)" | |||
class="opacity-80 justify-start text-white text-base font-normal cursor-pointer hover:opacity-100 transition-all duration-300 px-4 py-2 rounded-lg inline-block" | |||
class="select-none text-white text-xs sm:text-sm md:text-base lg:text-lg font-normal cursor-pointer hover:opacity-100 transition-all duration-300 px-2 py-1 sm:px-2.5 sm:py-1.5 md:px-3 md:py-2 lg:px-4 lg:py-2.5 rounded-lg inline-block active:scale-95 whitespace-nowrap" | |||
:class="{ | |||
'opacity-100 font-bold bg-gradient-to-r from-blue-700 to-blue-400 text-white border-0 shadow-lg scale-105 transition-all duration-300': | |||
'font-bold bg-cyan-400 text-zinc-900 border-0 shadow-lg scale-105 transition-all duration-300': | |||
selectedCategory === category, | |||
'hover:bg-zinc-800/50': selectedCategory !== category, | |||
}" | |||
@@ -52,8 +74,9 @@ | |||
</div> | |||
</div> | |||
</div> | |||
<!-- 右侧FAQ列表 --> | |||
<div class="col-span-1 md:col-span-8"> | |||
<div class="col-span-1 md:col-span-9"> | |||
<!-- 搜索框 --> | |||
<div class="mb-8 relative"> | |||
<input | |||
@@ -400,10 +423,69 @@ useHead({ | |||
/* 添加过渡动画 */ | |||
.fade-enter-active, | |||
.fade-leave-active { | |||
transition: opacity 0.3s; | |||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); | |||
} | |||
.fade-enter-from, | |||
.fade-leave-to { | |||
opacity: 0; | |||
transform: translateY(20px); | |||
} | |||
/* 分类悬停效果 */ | |||
.active\:scale-95:active { | |||
transform: scale(0.95); | |||
} | |||
/* 优化横向滚动 */ | |||
.overflow-x-auto { | |||
-webkit-overflow-scrolling: touch; | |||
scrollbar-width: none; /* Firefox */ | |||
-ms-overflow-style: none; /* IE and Edge */ | |||
} | |||
.overflow-x-auto::-webkit-scrollbar { | |||
display: none; /* Chrome, Safari, Opera */ | |||
} | |||
/* 清除按钮动画 */ | |||
button { | |||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); | |||
} | |||
button:hover { | |||
transform: translateY(-2px); | |||
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), | |||
0 2px 4px -1px rgba(0, 0, 0, 0.06); | |||
} | |||
button:active { | |||
transform: translateY(0); | |||
} | |||
/* FAQ项目悬停效果 */ | |||
.bg-zinc-900 { | |||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); | |||
} | |||
.bg-zinc-900:hover { | |||
transform: translateY(-4px); | |||
} | |||
/* 响应式调整 */ | |||
@media (max-width: 768px) { | |||
button:hover { | |||
transform: translateY(-1px); | |||
} | |||
.bg-zinc-900:hover { | |||
transform: translateY(-2px); | |||
} | |||
} | |||
@media (min-width: 1024px) { | |||
.bg-zinc-900:hover { | |||
transform: translateY(-4px); | |||
} | |||
} | |||
</style> |
@@ -97,64 +97,6 @@ | |||
</div> | |||
</div> | |||
</SwiperSlide> | |||
<SwiperSlide> | |||
<div | |||
class="max-w-screen-2xl mx-auto h-full relative" | |||
:style="{ | |||
backgroundImage: `url(${homeA2Webp})`, | |||
backgroundSize: 'cover', | |||
backgroundPosition: 'center', | |||
backgroundRepeat: 'no-repeat', | |||
}" | |||
> | |||
<div | |||
class="w-full h-full flex-col justify-center hidden md:flex relative z-10" | |||
> | |||
<div class="justify-center"> | |||
<span class="text-white text-6xl font-normal leading-[78px]">{{ | |||
t("home.carousel.two.title") | |||
}}</span> | |||
<span class="text-white text-6xl font-normal leading-[78px]">{{ | |||
t("home.carousel.two.description") | |||
}}</span> | |||
<br /> | |||
<span class="text-white text-6xl font-normal leading-[78px]">{{ | |||
t("home.carousel.two.description2") | |||
}}</span | |||
><br /> | |||
<span | |||
class="text-cyan-400 text-6xl font-normal leading-[78px]" | |||
>{{ t("home.carousel.two.description3") }}</span | |||
> | |||
</div> | |||
<div class="flex flex-col gap-2 mt-4"> | |||
<div | |||
class="flex items-center gap-2 text-stone-50 text-xl font-normal" | |||
> | |||
<div class="w-2 h-2 bg-white rounded-full"></div> | |||
{{ t("home.carousel.two.description4") }} | |||
</div> | |||
<div | |||
class="flex items-center gap-2 text-stone-50 text-xl font-normal" | |||
> | |||
<div class="w-2 h-2 bg-white rounded-full"></div> | |||
{{ t("home.carousel.two.description5") }} | |||
</div> | |||
</div> | |||
<div | |||
class="w-36 h-14 mt-12 flex items-center justify-center bg-[#35F1FF] rounded-lg hover:bg-[#35F1FF]/80 transition-colors duration-300" | |||
> | |||
<nuxt-link | |||
:to="`${homepagePath}/products`" | |||
class="w-full h-full !flex items-center justify-center text-zinc-900" | |||
> | |||
{{ t("products.view_details") }} | |||
</nuxt-link> | |||
</div> | |||
</div> | |||
</div> | |||
</SwiperSlide> | |||
<SwiperSlide v-if="!isMobile"> | |||
<div class="max-w-screen-2xl mx-auto h-full relative"> | |||
<video | |||
@@ -242,7 +184,7 @@ | |||
:key="usage.id" | |||
class="cursor-pointer select-none px-4 sm:px-7 py-2 sm:py-3 rounded-full border border-zinc-700 text-white transition-all duration-300 relative group" | |||
:class="{ | |||
'bg-gradient-to-r from-blue-700 to-blue-400 text-white border-zinc-900 pointer-events-none': | |||
'bg-cyan-400 border-zinc-900 pointer-events-none text-zinc-900': | |||
activeIndex === index, | |||
'hover:border-zinc-600': activeIndex !== index, | |||
}" | |||
@@ -1094,23 +1036,6 @@ useHead({ | |||
} | |||
} | |||
// 添加轮播图遮罩效果 | |||
.swiper-slide { | |||
&::before { | |||
content: ""; | |||
position: absolute; | |||
top: 0; | |||
left: 0; | |||
right: 0; | |||
bottom: 0; | |||
background: linear-gradient( | |||
to bottom, | |||
rgba(0, 0, 0, 0.2) 0%, | |||
rgba(0, 0, 0, 0.4) 100% | |||
); | |||
z-index: 1; | |||
} | |||
} | |||
// 优化图片加载动画 | |||
@keyframes fadeIn { |
@@ -5,7 +5,7 @@ | |||
<div v-if="isLoading" class="flex justify-center py-12"> | |||
<!-- 加载中 --> | |||
<div | |||
class="animate-spin h-8 w-8 border-4 border-blue-500 rounded-full border-t-transparent" | |||
class="animate-spin h-8 w-8 border-4 border-cyan-400 rounded-full border-t-transparent" | |||
></div> | |||
</div> | |||
@@ -103,7 +103,7 @@ | |||
class="absolute inset-0 flex items-center justify-center bg-zinc-800/50 z-10" | |||
> | |||
<div | |||
class="animate-spin h-8 w-8 border-4 border-blue-500 rounded-full border-t-transparent" | |||
class="animate-spin h-8 w-8 border-4 border-cyan-400 rounded-full border-t-transparent" | |||
></div> | |||
</div> | |||
@@ -118,7 +118,7 @@ | |||
}}</span> | |||
<button | |||
@click.stop="retryLoadSlideImage(slideIndex)" | |||
class="px-4 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600 transition-colors duration-300" | |||
class="px-4 py-2 bg-cyan-400 text-white rounded-lg hover:bg-cyan-600 transition-colors duration-300" | |||
> | |||
{{ t("products.retry") }} | |||
</button> | |||
@@ -134,7 +134,7 @@ | |||
class="absolute inset-0 flex items-center justify-center bg-zinc-900/80 z-30" | |||
> | |||
<div | |||
class="animate-spin h-12 w-12 border-4 border-blue-500 rounded-full border-t-transparent" | |||
class="animate-spin h-12 w-12 border-4 border-cyan-400 rounded-full border-t-transparent" | |||
></div> | |||
</div> | |||
</div> | |||
@@ -167,9 +167,9 @@ | |||
<div | |||
class="w-full h-full rounded-lg overflow-hidden thumbnail-fixed-size" | |||
:class="{ | |||
'ring-2 ring-blue-500 ring-offset-2 ring-offset-zinc-900': | |||
'ring-2 ring-cyan-400 ring-offset-2 ring-offset-zinc-900': | |||
currentSlideIndex === index, | |||
'hover:ring-1 hover:ring-blue-500/50 hover:ring-offset-1 hover:ring-offset-zinc-900': | |||
'hover:ring-1 hover:ring-cyan-400/50 hover:ring-offset-1 hover:ring-offset-zinc-900': | |||
currentSlideIndex !== index, | |||
'opacity-50': | |||
isSlideThumbnailLoading[index] || | |||
@@ -182,7 +182,7 @@ | |||
class="absolute inset-0 flex items-center justify-center bg-zinc-800/50 rounded-lg z-10" | |||
> | |||
<div | |||
class="animate-spin h-4 w-4 border-2 border-blue-500 rounded-full border-t-transparent" | |||
class="animate-spin h-4 w-4 border-2 border-cyan-400 rounded-full border-t-transparent" | |||
></div> | |||
</div> | |||
@@ -224,7 +224,7 @@ | |||
}}</span> | |||
<button | |||
@click.stop="retryLoadSlideImage(index)" | |||
class="px-2 py-1 bg-blue-500 text-white text-xs rounded hover:bg-blue-600 transition-colors duration-300" | |||
class="px-2 py-1 bg-cyan-400 text-white text-xs rounded hover:bg-cyan-600 transition-colors duration-300" | |||
> | |||
{{ t("products.retry") }} | |||
</button> | |||
@@ -236,7 +236,7 @@ | |||
<!-- 缩略图导航按钮 --> | |||
<button | |||
class="swiper-thumb-prev absolute top-1/2 left-2 z-10 w-8 h-8 flex items-center justify-center bg-black/50 hover:bg-blue-500 rounded-full transform -translate-y-1/2 transition-all duration-300" | |||
class="swiper-thumb-prev absolute top-1/2 left-2 z-10 w-8 h-8 flex items-center justify-center bg-black/50 hover:bg-cyan-400 rounded-full transform -translate-y-1/2 transition-all duration-300" | |||
> | |||
<svg | |||
xmlns="http://www.w3.org/2000/svg" | |||
@@ -254,7 +254,7 @@ | |||
</svg> | |||
</button> | |||
<button | |||
class="swiper-thumb-next absolute top-1/2 right-2 z-10 w-8 h-8 flex items-center justify-center bg-black/50 hover:bg-blue-500 rounded-full transform -translate-y-1/2 transition-all duration-300" | |||
class="swiper-thumb-next absolute top-1/2 right-2 z-10 w-8 h-8 flex items-center justify-center bg-black/50 hover:bg-cyan-400 rounded-full transform -translate-y-1/2 transition-all duration-300" | |||
> | |||
<svg | |||
xmlns="http://www.w3.org/2000/svg" | |||
@@ -281,7 +281,7 @@ | |||
<h1 class="text-white text-3xl font-medium mb-4"> | |||
{{ product.title || product.name }} | |||
</h1> | |||
<div class="text-stone-400 text-lg leading-relaxed"> | |||
<div class="text-[#71717A] text-lg leading-relaxed"> | |||
{{ product.summary }} | |||
</div> | |||
</div> | |||
@@ -292,7 +292,7 @@ | |||
<div | |||
class="flex justify-between items-center py-2 border-b border-zinc-800" | |||
> | |||
<span class="text-stone-400">{{ | |||
<span class="text-[#71717A]">{{ | |||
t("products.categoryTitle") | |||
}}</span> | |||
<span class="text-white font-medium">{{ | |||
@@ -302,7 +302,7 @@ | |||
<div | |||
class="flex justify-between items-center py-2 border-b border-zinc-800" | |||
> | |||
<span class="text-stone-400">{{ | |||
<span class="text-[#71717A]">{{ | |||
t("products.usageTitle") | |||
}}</span> | |||
<span class="text-white font-medium">{{ | |||
@@ -313,7 +313,7 @@ | |||
v-if="product.capacities && product.capacities.length > 0" | |||
class="flex justify-between items-center py-2" | |||
> | |||
<span class="text-stone-400">{{ | |||
<span class="text-[#71717A]">{{ | |||
t("products.capacitiesTitle") | |||
}}</span> | |||
<span class="text-white font-medium">{{ | |||
@@ -332,7 +332,7 @@ | |||
{{ t("products.productDescription") }} | |||
</h2> | |||
<div | |||
class="text-stone-400 leading-relaxed space-y-4 prose prose-invert max-w-none" | |||
class="text-[#71717A] leading-relaxed space-y-4 prose prose-invert max-w-none" | |||
> | |||
{{ product.description }} | |||
</div> | |||
@@ -340,7 +340,7 @@ | |||
<div class="bg-zinc-900 rounded-lg p-6"> | |||
<div | |||
class="text-stone-400 leading-relaxed space-y-4 prose prose-invert max-w-none" | |||
class="text-[#71717A] leading-relaxed space-y-4 prose prose-invert max-w-none" | |||
> | |||
<ContentRenderer :value="product.content" /> | |||
</div> | |||
@@ -380,7 +380,7 @@ | |||
> | |||
{{ relatedProduct.title || relatedProduct.name }} | |||
</h3> | |||
<p class="text-stone-400 text-sm line-clamp-2"> | |||
<p class="text-[#71717A] text-sm line-clamp-2"> | |||
{{ relatedProduct.summary }} | |||
</p> | |||
</div> | |||
@@ -811,12 +811,12 @@ useHead(() => ({ | |||
:deep(.swiper-pagination-bullet-active) { | |||
opacity: 1; | |||
background-color: theme("colors.blue.500"); | |||
background-color: theme("colors.cyan.400"); | |||
} | |||
:deep(.swiper-button-next), | |||
:deep(.swiper-button-prev) { | |||
color: theme("colors.blue.500"); | |||
color: theme("colors.cyan.500"); | |||
background-color: rgba(0, 0, 0, 0.3); | |||
width: 36px; | |||
height: 36px; | |||
@@ -828,7 +828,7 @@ useHead(() => ({ | |||
} | |||
&:hover { | |||
background-color: theme("colors.blue.500"); | |||
background-color: theme("colors.cyan.500"); | |||
color: white; | |||
} | |||
} |
@@ -5,7 +5,7 @@ | |||
<div v-if="isLoading" class="flex justify-center py-8 md:py-12"> | |||
<!-- 加载中 --> | |||
<div | |||
class="animate-spin h-6 w-6 md:h-8 md:w-8 border-4 border-blue-500 rounded-full border-t-transparent" | |||
class="animate-spin h-6 w-6 md:h-8 md:w-8 border-4 border-cyan-500 rounded-full border-t-transparent" | |||
></div> | |||
</div> | |||
@@ -57,7 +57,9 @@ | |||
> | |||
<div class="max-w-screen-2xl mx-auto"> | |||
<div class="w-full grid grid-cols-1 md:grid-cols-12 gap-4"> | |||
<div class="sticky top-24 col-span-1 md:col-span-3 flex flex-col gap-4 sm:gap-6 md:gap-8 lg:gap-10 xl:gap-12 2xl:gap-16 mb-4 sm:mb-6 md:mb-8 lg:mb-10 xl:mb-12 2xl:mb-16 pr-4"> | |||
<div | |||
class="sticky top-24 col-span-1 md:col-span-3 flex flex-col gap-4 sm:gap-6 md:gap-8 lg:gap-10 xl:gap-12 2xl:gap-16 mb-4 sm:mb-6 md:mb-8 lg:mb-10 xl:mb-12 2xl:mb-16 pr-4" | |||
> | |||
<div | |||
class="flex flex-col gap-2 sm:gap-3 md:gap-4 lg:gap-5 xl:gap-6" | |||
> | |||
@@ -93,9 +95,9 @@ | |||
v-for="category in categories" | |||
:key="category" | |||
@click="handleCategoryFilter(category)" | |||
class="opacity-80 select-none text-white text-xs sm:text-sm md:text-base lg:text-lg font-normal cursor-pointer hover:opacity-100 transition-all duration-300 px-2 py-1 sm:px-2.5 sm:py-1.5 md:px-3 md:py-2 lg:px-4 lg:py-2.5 rounded-lg inline-block active:scale-95 whitespace-nowrap" | |||
class="select-none text-white text-xs sm:text-sm md:text-base lg:text-lg font-normal cursor-pointer hover:opacity-100 transition-all duration-300 px-2 py-1 sm:px-2.5 sm:py-1.5 md:px-3 md:py-2 lg:px-4 lg:py-2.5 rounded-lg inline-block active:scale-95 whitespace-nowrap" | |||
:class="{ | |||
'opacity-100 font-bold bg-gradient-to-r from-blue-700 to-blue-400 text-white border-0 shadow-lg scale-105 transition-all duration-300': | |||
'font-bold bg-cyan-400 text-zinc-900 border-0 shadow-lg scale-105 transition-all duration-300': | |||
selectedCategory === category, | |||
'hover:bg-zinc-800/50': selectedCategory !== category, | |||
}" | |||
@@ -142,7 +144,7 @@ | |||
@click="handleUsageFilter(usage)" | |||
class="opacity-80 select-none text-white text-xs sm:text-sm md:text-base lg:text-lg font-normal cursor-pointer hover:opacity-100 transition-all duration-300 px-2 py-1 sm:px-2.5 sm:py-1.5 md:px-3 md:py-2 lg:px-4 lg:py-2.5 rounded-lg inline-block active:scale-95" | |||
:class="{ | |||
'opacity-100 font-bold bg-gradient-to-r from-blue-700 to-blue-400 text-white border-0 shadow-lg scale-105 transition-all duration-300': | |||
'opacity-100 font-bold bg-cyan-400 text-white border-0 shadow-lg scale-105 transition-all duration-300': | |||
selectedUsage === usage, | |||
'hover:bg-zinc-800/50': selectedUsage !== usage, | |||
}" | |||
@@ -151,7 +153,6 @@ | |||
</div> | |||
</div> | |||
</div> | |||
</div> | |||
<div class="col-span-1 md:col-span-9"> | |||
<div class="flex flex-col gap-8 md:gap-12 lg:gap-16"> | |||
@@ -196,7 +197,7 @@ | |||
<div class="relative"> | |||
<!-- 系列标题背景 --> | |||
<div | |||
class="absolute inset-0 bg-gradient-to-r from-blue-900/20 to-blue-500/20 rounded-lg transform -skew-x-6" | |||
class="absolute inset-0 bg-gradient-to-r from-cyan-900/20 to-cyan-500/20 rounded-lg transform -skew-x-6" | |||
></div> | |||
<!-- 系列标题内容 --> | |||
@@ -205,7 +206,7 @@ | |||
> | |||
<!-- 装饰线 --> | |||
<div | |||
class="w-1 h-6 md:h-8 bg-gradient-to-b from-blue-500 to-blue-300 rounded-full" | |||
class="w-1 h-6 md:h-8 bg-cyan-400 text-zinc-900 rounded-full" | |||
></div> | |||
<!-- 系列名称 --> | |||
@@ -217,7 +218,7 @@ | |||
<!-- 产品数量标签 --> | |||
<div | |||
class="ml-auto px-2 py-0.5 md:px-3 md:py-1 bg-blue-500/20 rounded-full text-blue-300 text-xs md:text-sm" | |||
class="ml-auto px-2 py-0.5 md:px-3 md:py-1 bg-cyan-500/20 rounded-full text-cyan-300 text-xs md:text-sm" | |||
> | |||
{{ seriesProducts.length }} | |||
{{ t("products.product_count") }} | |||
@@ -244,7 +245,7 @@ | |||
})" | |||
:key="product.id" | |||
:to="`${homepagePath}/products/${product.name}`" | |||
class="group bg-zinc-900 rounded-lg transition-all duration-300 hover:bg-zinc-800 hover:scale-105 hover:shadow-lg hover:ring-2 hover:ring-blue-400 relative overflow-hidden" | |||
class="group bg-zinc-900 rounded-lg transition-all duration-300 hover:bg-zinc-800 hover:scale-105 hover:shadow-lg hover:ring-2 hover:ring-cyan-400 relative overflow-hidden" | |||
> | |||
<div class="w-full p-4 md:p-6 lg:p-8"> | |||
<div | |||
@@ -264,7 +265,7 @@ | |||
class="absolute inset-0 flex items-center justify-center bg-zinc-800/50 rounded-lg" | |||
> | |||
<div | |||
class="animate-spin h-6 w-6 md:h-8 md:w-8 border-4 border-blue-500 rounded-full border-t-transparent" | |||
class="animate-spin h-6 w-6 md:h-8 md:w-8 border-4 border-cyan-500 rounded-full border-t-transparent" | |||
></div> | |||
</div> | |||
<div | |||
@@ -297,7 +298,7 @@ | |||
{{ product.name }} | |||
</div> | |||
<div | |||
class="text-center text-stone-400 text-sm md:text-base font-normal leading-normal" | |||
class="text-center text-[#71717A] text-sm md:text-base font-normal leading-normal" | |||
> | |||
{{ product.capacities.join(" / ") }} | |||
</div> | |||
@@ -352,7 +353,7 @@ | |||
})" | |||
:key="product.id" | |||
:to="`${homepagePath}/products/${product.name}`" | |||
class="group bg-zinc-900 rounded-lg transition-all duration-300 hover:bg-zinc-800 hover:scale-105 hover:shadow-lg hover:ring-2 hover:ring-blue-400 relative overflow-hidden" | |||
class="group bg-zinc-900 rounded-lg transition-all duration-300 hover:bg-zinc-800 hover:shadow-lg hover:ring-2 hover:ring-cyan-400 relative overflow-hidden" | |||
> | |||
<div class="w-full p-4 md:p-6 lg:p-8"> | |||
<div | |||
@@ -372,7 +373,7 @@ | |||
class="absolute inset-0 flex items-center justify-center bg-zinc-800/50 rounded-lg" | |||
> | |||
<div | |||
class="animate-spin h-6 w-6 md:h-8 md:w-8 border-4 border-blue-500 rounded-full border-t-transparent" | |||
class="animate-spin h-6 w-6 md:h-8 md:w-8 border-4 border-cyan-500 rounded-full border-t-transparent" | |||
></div> | |||
</div> | |||
<div | |||
@@ -405,7 +406,7 @@ | |||
{{ product.name }} | |||
</div> | |||
<div | |||
class="text-center text-stone-400 text-sm md:text-base font-normal leading-normal" | |||
class="text-center text-[#71717A] text-sm md:text-base font-normal leading-normal" | |||
> | |||
{{ product.capacities.join(" / ") }} | |||
</div> | |||
@@ -813,13 +814,13 @@ onBeforeUnmount(() => { | |||
/* 系列标题悬停效果 */ | |||
.relative:hover .bg-gradient-to-r { | |||
@apply from-blue-900/30 to-blue-500/30; | |||
@apply from-cyan-900/30 to-cyan-500/30; | |||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); | |||
} | |||
/* 产品数量标签悬停效果 */ | |||
.relative:hover .bg-blue-500\/20 { | |||
@apply bg-blue-500/30; | |||
.relative:hover .bg-cyan-500\/20 { | |||
@apply bg-cyan-500/30; | |||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); | |||
} | |||
@@ -836,7 +837,7 @@ onBeforeUnmount(() => { | |||
/* 添加过渡动画 */ | |||
.bg-primary { | |||
@apply bg-blue-600; | |||
@apply bg-cyan-600; | |||
} | |||
button { |