浏览代码

feat: 新增路由加载指示器和返回顶部按钮组件

本次提交主要进行了以下修改:
1. 在 `app.vue` 中引入了 `RouteLoader` 组件,以提升路由切换时的用户体验。
2. 在 `components` 目录下新增了 `RouteLoader.vue` 组件,负责显示路由加载进度条。
3. 新增了 `BackToTop.vue` 组件,提供返回顶部的功能,增强页面导航体验。
4. 更新了 `TheFooter.vue` 和 `TheHeader.vue`,分别引入返回顶部按钮和调整了样式以匹配新的设计。
5. 在 `nuxt.config.ts` 中添加了页面过渡效果配置,提升页面切换的流畅性。
6. 更新了样式文件,调整了颜色变量和过渡效果,确保一致性。

这些改动旨在提升用户体验和页面的可用性。
master
lizhuang 1 个月前
父节点
当前提交
4bc8642379

+ 1
- 0
app.vue 查看文件

@@ -1,5 +1,6 @@
<template>
<div>
<RouteLoader />
<NuxtLayout>
<NuxtPage />
</NuxtLayout>

+ 33
- 1
assets/css/styles.css 查看文件

@@ -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;
}

+ 60
- 0
components/BackToTop.vue 查看文件

@@ -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>

+ 58
- 28
components/LanguageSwitcher.vue 查看文件

@@ -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>

+ 98
- 0
components/RouteLoader.vue 查看文件

@@ -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>

+ 2
- 0
components/TheFooter.vue 查看文件

@@ -75,6 +75,8 @@
</div>
</div>
</div>
<!-- 返回顶部按钮 -->
<BackToTop />
</footer>
</template>


+ 5
- 5
components/TheHeader.vue 查看文件

@@ -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>

+ 1
- 0
i18n/locales/en.ts 查看文件

@@ -14,6 +14,7 @@ export default {
clear: "Clear",
noResults: "No results found",
searching: "Searching...",
backToTop: "Back to Top",
breadcrumb: {
home: "Home",
privacy: "Privacy Policy",

+ 1
- 0
i18n/locales/ja.ts 查看文件

@@ -14,6 +14,7 @@ export default {
clear: "クリア",
noResults: "関連する結果はありません",
searching: "検索中...",
backToTop: "トップに戻る",
breadcrumb: {
home: "ホーム",
privacy: "プライバシーポリシー",

+ 1
- 0
i18n/locales/zh.ts 查看文件

@@ -14,6 +14,7 @@ export default {
clear: "清除",
noResults: "没有找到相关结果",
searching: "搜索中...",
backToTop: "返回顶部",
footer: {
productsLinks: {
title: "产品",

+ 18
- 6
nuxt.config.ts 查看文件

@@ -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",
});

+ 19
- 19
pages/about.vue 查看文件

@@ -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">

+ 11
- 11
pages/contact.vue 查看文件

@@ -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"
>
{{

+ 97
- 15
pages/faq.vue 查看文件

@@ -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>

+ 1
- 76
pages/index.vue 查看文件

@@ -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 {

+ 20
- 20
pages/products/[id].vue 查看文件

@@ -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;
}
}

+ 20
- 19
pages/products/index.vue 查看文件

@@ -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 {

正在加载...
取消
保存