瀏覽代碼

feat: 更新图标和页面内容,增强用户体验

- 在icomoon中新增了两个图标:enter和star,并更新了相关的SVG和CSS文件。
- 更新了demo.html文件,增加了新图标的展示。
- 新增了多个图片资源,包括home-b-1.webp、product-banner.webp和video.webp,丰富了页面内容。
- 在LanguageSwitcher组件中优化了下拉菜单的交互逻辑,增加了鼠标悬停效果。
- 更新了TheHeader和TheFooter组件的样式,提升了页面的视觉效果。
- 在关于我们、联系我们和产品页面中进行了内容更新,增加了SEO优化。
- 新增FAQ页面,提供常见问题解答,提升用户支持。
- 在index.vue和products/index.vue中优化了产品展示逻辑,改善了用户体验。
master
lizhuang 1 月之前
父節點
當前提交
a7c1fdacc5

+ 29
- 1
assets/icomoon/demo.html 查看文件

@@ -9,10 +9,38 @@
<link rel="stylesheet" href="style.css"></head>
<body>
<div class="bgc1 clearfix">
<h1 class="mhmm mvm"><span class="fgc1">Font Name:</span> icomoon <small class="fgc1">(Glyphs:&nbsp;8)</small></h1>
<h1 class="mhmm mvm"><span class="fgc1">Font Name:</span> icomoon <small class="fgc1">(Glyphs:&nbsp;10)</small></h1>
</div>
<div class="clearfix mhl ptl">
<h1 class="mvm mtn fgc1">Grid Size: Unknown</h1>
<div class="glyph fs1">
<div class="clearfix bshadow0 pbs">
<span class="icon-enter"></span>
<span class="mls"> icon-enter</span>
</div>
<fieldset class="fs0 size1of1 clearfix hidden-false">
<input type="text" readonly value="e909" class="unit size1of2" />
<input type="text" maxlength="1" readonly value="&#xe909;" class="unitRight size1of2 talign-right" />
</fieldset>
<div class="fs0 bshadow0 clearfix hidden-true">
<span class="unit pvs fgc1">liga: </span>
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs1">
<div class="clearfix bshadow0 pbs">
<span class="icon-star"></span>
<span class="mls"> icon-star</span>
</div>
<fieldset class="fs0 size1of1 clearfix hidden-false">
<input type="text" readonly value="e908" class="unit size1of2" />
<input type="text" maxlength="1" readonly value="&#xe908;" class="unitRight size1of2 talign-right" />
</fieldset>
<div class="fs0 bshadow0 clearfix hidden-true">
<span class="unit pvs fgc1">liga: </span>
<input type="text" readonly value="" class="liga unitRight" />
</div>
</div>
<div class="glyph fs1">
<div class="clearfix bshadow0 pbs">
<span class="icon-h1"></span>

二進制
assets/icomoon/fonts/icomoon.eot 查看文件


+ 2
- 0
assets/icomoon/fonts/icomoon.svg 查看文件

@@ -15,4 +15,6 @@
<glyph unicode="&#xe905;" glyph-name="h2" horiz-adv-x="1044" d="M671.059 752.461c-7.629 0.006-14.948-3.017-20.352-8.405l-76.928-76.928h-215.593c-7.639 0-14.964-3.035-20.366-8.436-5.402-5.4-8.434-12.725-8.434-20.364s3.033-14.964 8.434-20.364c5.402-5.402 12.727-8.436 20.366-8.436h215.593l76.928-76.885c5.396-5.404 12.717-8.442 20.352-8.448h113.792c7.639 0 14.964 3.035 20.366 8.434 5.4 5.402 8.434 12.727 8.434 20.366 0 7.637-3.035 14.962-8.434 20.364-5.402 5.402-12.727 8.436-20.366 8.436h-101.845l-56.533 56.533 56.533 56.533h101.845c7.639 0 14.964 3.035 20.366 8.436 5.4 5.402 8.434 12.727 8.434 20.364 0 7.639-3.035 14.964-8.434 20.366-5.402 5.402-12.727 8.434-20.366 8.434h-113.792zM215.692 638.352c0.006 14.029 3.454 27.843 10.045 40.225 6.591 12.384 16.122 22.961 27.756 30.799 11.634 7.84 25.015 12.702 38.969 14.161 13.952 1.457 28.050-0.534 41.053-5.797s24.515-13.641 33.524-24.393c9.009-10.754 15.24-23.556 18.146-37.28 2.905-13.726 2.395-27.953-1.485-41.435s-11.010-25.803-20.766-35.885l98.987-210.261h308.777c31.616 0 57.259-25.6 57.259-57.259v-99.541c0-7.639-3.035-14.964-8.436-20.366-5.4-5.4-12.725-8.434-20.364-8.434h-540.459c-7.639 0-14.962 3.035-20.364 8.434-5.402 5.402-8.436 12.727-8.436 20.366v99.541c0 31.616 25.643 57.259 57.259 57.259h111.061l-86.953 184.745c-12.008-1.396-24.174-0.236-35.7 3.405-11.528 3.641-22.154 9.679-31.181 17.719-9.027 8.038-16.25 17.896-21.195 28.928-4.947 11.030-7.501 22.981-7.499 35.068zM301.367 666.47c-7.452 0-14.598-2.96-19.868-8.229-5.268-5.27-8.229-12.416-8.229-19.868s2.962-14.598 8.229-19.866c5.27-5.27 12.416-8.229 19.868-8.229s14.598 2.96 19.868 8.229c5.268 5.268 8.227 12.414 8.227 19.866s-2.96 14.598-8.227 19.868c-5.27 5.27-12.416 8.229-19.868 8.229zM443.106 310.886h-155.607v-70.4h482.859v70.4h-327.253z" />
<glyph unicode="&#xe906;" glyph-name="h3" horiz-adv-x="1044" d="M506.72 812.567c4.59 2.701 9.821 4.126 15.145 4.126 5.327 0 10.557-1.425 15.147-4.126l288-169.386c4.488-2.641 8.206-6.408 10.787-10.927 2.582-4.521 3.938-9.637 3.933-14.844v-338.816c0.006-5.205-1.351-10.323-3.933-14.842-2.582-4.521-6.3-8.288-10.787-10.929l-288-169.385c-4.59-2.702-9.821-4.126-15.147-4.126s-10.555 1.424-15.145 4.126l-288 169.385c-4.488 2.641-8.206 6.408-10.787 10.929-2.582 4.519-3.938 9.637-3.933 14.842v338.773c-0.006 5.207 1.351 10.323 3.933 14.844s6.3 8.287 10.787 10.927l288 169.43zM263.733 600.3v-304.64l258.133-151.895 258.135 151.851v304.683l-258.135 151.893-258.133-151.893zM657.347 579.499c7.92 0 15.518-3.147 21.118-8.747 5.602-5.6 8.749-13.198 8.749-21.118v-203.264c0-3.923-0.772-7.808-2.272-11.431-1.501-3.625-3.7-6.918-6.475-9.693-2.773-2.773-6.065-4.974-9.689-6.475-3.625-1.501-7.509-2.274-11.431-2.274s-7.806 0.774-11.431 2.274c-3.623 1.501-6.916 3.702-9.689 6.475-2.775 2.775-4.974 6.067-6.475 9.693-1.501 3.623-2.272 7.509-2.272 11.431v203.264c0 7.92 3.147 15.518 8.747 21.118 5.602 5.6 13.2 8.747 21.12 8.747zM521.838 511.746c7.92 0 15.518-3.147 21.118-8.749 5.6-5.6 8.747-13.198 8.747-21.118v-135.509c0-7.922-3.147-15.518-8.747-21.12-5.6-5.6-13.198-8.747-21.118-8.747-7.922 0-15.52 3.147-21.12 8.747-5.6 5.602-8.747 13.198-8.747 21.12v135.509c0 7.92 3.147 15.518 8.747 21.118 5.6 5.602 13.198 8.749 21.12 8.749zM416.195 414.123c-0.002 7.92-3.149 15.516-8.749 21.116s-13.198 8.745-21.118 8.745c-7.92 0-15.516-3.145-21.116-8.745s-8.749-13.196-8.749-21.116v-67.753c-0.002-3.923 0.77-7.808 2.271-11.431 1.501-3.625 3.7-6.918 6.475-9.693 2.773-2.773 6.065-4.974 9.691-6.475 3.623-1.501 7.507-2.274 11.429-2.274s7.808 0.774 11.431 2.274c3.623 1.501 6.916 3.702 9.691 6.475 2.773 2.775 4.972 6.067 6.473 9.693 1.501 3.623 2.272 7.509 2.272 11.431v67.753z" />
<glyph unicode="&#xe907;" glyph-name="arrow-left" horiz-adv-x="1182" d="M1113.038 501.613h-946.074l362.27 344.949c18.969 18.063 19.704 48.073 1.637 67.037-18.044 18.94-48.063 19.699-67.056 1.636l-413.72-393.971c-17.904-17.92-27.79-41.72-27.79-67.061 0-25.316 9.886-49.139 28.625-67.842l412.909-393.167c9.176-8.743 20.937-13.084 32.698-13.084 12.52 0 25.039 4.931 34.358 14.714 18.068 18.968 17.333 48.955-1.636 67.017l-363.782 344.953h947.563c26.178 0 47.423 21.239 47.423 47.409s-21.246 47.41-47.423 47.41z" />
<glyph unicode="&#xe908;" glyph-name="star" d="M497.388 957.076c12.138-3.498 23.19-10.018 32.122-18.95s15.452-19.984 18.95-32.122l78.4-272.533 272.536-78.4c15.565-4.481 29.261-13.903 38.997-26.848 9.745-12.943 15.019-28.705 15.019-44.908s-5.274-31.963-15.019-44.907c-9.737-12.944-23.433-22.368-38.997-26.848l-272.536-78.4-78.4-272.532c-4.481-15.573-13.903-29.261-26.848-39.006s-28.705-15.010-44.908-15.010c-16.201 0-31.963 5.265-44.907 15.010s-22.367 23.433-26.848 39.006l-78.4 272.532-272.533 78.4c-15.57 4.48-29.26 13.903-39.004 26.848s-15.014 28.705-15.014 44.907c0 16.202 5.27 31.964 15.014 44.908s23.434 22.367 39.004 26.848l272.533 78.4 78.4 272.533c2.715 9.424 7.26 18.22 13.375 25.887s13.681 14.054 22.264 18.797c8.584 4.743 18.018 7.748 27.763 8.844s19.612 0.262 29.035-2.456zM476.631 831.487l-63.989-222.731-13.74-47.562-270.442-77.952 270.442-77.728 13.74-47.562 63.989-222.805 64.138 222.805 13.739 47.562 47.563 13.739 222.805 64.064-270.368 77.803-13.739 47.563-64.138 222.805zM476.631 831.487l-63.989-222.731-13.74-47.562-270.442-77.952 270.442-77.728 13.74-47.562 63.989-222.805 64.138 222.805 13.739 47.562 47.563 13.739 222.805 64.064-270.368 77.803-13.739 47.563-64.138 222.805z" />
<glyph unicode="&#xe909;" glyph-name="enter" d="M810.667 704c-23.564 0-42.667-19.103-42.667-42.667v-170.667c0-23.564-19.103-42.667-42.667-42.667v0h-409.173l55.467 55.040c7.753 7.753 12.548 18.463 12.548 30.293 0 23.661-19.181 42.841-42.841 42.841-11.83 0-22.541-4.795-30.293-12.548v0l-128-128c-3.773-3.937-6.808-8.616-8.859-13.791l-0.101-0.289c-2.024-4.793-3.201-10.366-3.201-16.213s1.176-11.42 3.305-16.495l-0.105 0.281c2.152-5.464 5.187-10.143 8.974-14.095l-0.014 0.015 128-128c7.733-7.795 18.45-12.621 30.293-12.621s22.56 4.826 30.291 12.618l0.003 0.003c7.795 7.733 12.621 18.45 12.621 30.293s-4.826 22.56-12.618 30.291l-0.003 0.003-55.467 55.040h409.173c70.692 0 128 57.308 128 128v0 170.667c0 23.564-19.103 42.667-42.667 42.667v0z" />
</font></defs></svg>

二進制
assets/icomoon/fonts/icomoon.ttf 查看文件


二進制
assets/icomoon/fonts/icomoon.woff 查看文件


+ 1
- 1
assets/icomoon/selection.json
文件差異過大導致無法顯示
查看文件


+ 11
- 5
assets/icomoon/style.css 查看文件

@@ -1,10 +1,10 @@
@font-face {
font-family: 'icomoon';
src: url('fonts/icomoon.eot?ezztx0');
src: url('fonts/icomoon.eot?ezztx0#iefix') format('embedded-opentype'),
url('fonts/icomoon.ttf?ezztx0') format('truetype'),
url('fonts/icomoon.woff?ezztx0') format('woff'),
url('fonts/icomoon.svg?ezztx0#icomoon') format('svg');
src: url('fonts/icomoon.eot?7g9lqp');
src: url('fonts/icomoon.eot?7g9lqp#iefix') format('embedded-opentype'),
url('fonts/icomoon.ttf?7g9lqp') format('truetype'),
url('fonts/icomoon.woff?7g9lqp') format('woff'),
url('fonts/icomoon.svg?7g9lqp#icomoon') format('svg');
font-weight: normal;
font-style: normal;
font-display: block;
@@ -25,6 +25,12 @@
-moz-osx-font-smoothing: grayscale;
}

.icon-enter:before {
content: "\e909";
}
.icon-star:before {
content: "\e908";
}
.icon-h1:before {
content: "\e904";
}

二進制
assets/images/home-b-1.webp 查看文件


二進制
assets/images/product-banner.webp 查看文件


二進制
assets/videos/video.webp 查看文件


+ 46
- 51
components/LanguageSwitcher.vue 查看文件

@@ -1,45 +1,37 @@
<template>
<div class="relative inline-block text-left " ref="dropdownContainerRef">
<div class="relative inline-block text-left" ref="dropdownContainerRef" @mouseleave="handleMouseLeave">
<div
@click="toggleDropdown"
class="flex justify-center items-center gap-2 text-white opacity-80 text-sm"
@mouseenter="handleMouseEnter"
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"></i>
{{ currentLocaleName || "Language" }}
<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"
>
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="3" d="M19 9l-7 7-7-7" />
</svg>
</div>

<transition
enter-active-class="transition ease-out duration-100"
enter-from-class="transform opacity-0 scale-95"
enter-to-class="transform opacity-100 scale-100"
leave-active-class="transition ease-in duration-75"
leave-from-class="transform opacity-100 scale-100"
leave-to-class="transform opacity-0 scale-95"
>
<transition name="fade-down">
<div
v-if="isDropdownOpen"
class="origin-top-right absolute right-0 mt-2 w-36 rounded-md shadow-lg bg-[var(--color-bg)] ring-1 ring-black ring-opacity-5 focus:outline-none z-10"
role="menu"
aria-orientation="vertical"
aria-labelledby="options-menu"
@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"
>
<div class="py-1" role="none">
<a
v-for="locale in availableLocales"
:key="locale.code"
href="#"
@click.prevent="selectLanguage(locale.code)"
:class="[
'block px-4 py-2 text-sm opacity-80',
currentLocale === locale.code
? 'text-white opacity-100 font-bold'
: 'text-white hover: opacity-100',
]"
role="menuitem"
>
{{ locale.name }}
</a>
</div>
<ul class="space-y-1">
<li v-for="locale in availableLocales" :key="locale.code">
<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' : '' ]"
>
{{ locale.name }}
</button>
</li>
</ul>
</div>
</transition>
</div>
@@ -52,7 +44,6 @@
*/
import { ref, computed } from "vue";
import { useI18n } from "#imports"; // 修正 useI18n 导入
import { onClickOutside } from "@vueuse/core"; // 导入 onClickOutside

// 定义语言代码的类型,应该与 i18n 配置中的一致
type LocaleCode = "zh" | "en" | "ja"; // 你需要根据你的 i18n 配置更新这个类型
@@ -60,7 +51,8 @@ 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
const dropdownContainerRef = ref(null); // 保留 ref,虽然 onClickOutside 移除了,但未来可能有用
let leaveTimeout: ReturnType<typeof setTimeout> | null = null; // Timeout for mouseleave delay

// 可用语言列表
const availableLocales = computed(() => {
@@ -72,19 +64,12 @@ const availableLocales = computed(() => {

// 当前选中语言的名称
const currentLocaleName = computed(() => {
const current = availableLocales.value.find(
(l) => l.code === currentLocale.value
const current = availableLocales.value.find((l: { code: string; name: string }) =>
l.code === locale.value
);
return current ? current.name : "";
});

/**
* 切换下拉菜单的显示/隐藏状态
*/
function toggleDropdown() {
isDropdownOpen.value = !isDropdownOpen.value;
}

/**
* 选择语言并关闭下拉菜单
* @param {string} langCode - 选择的语言代码
@@ -99,13 +84,23 @@ async function selectLanguage(langCode: string) {
// 这里可以添加用户反馈,例如显示一个错误提示
}
}
isDropdownOpen.value = false; // 关闭下拉菜单
handleMouseLeave(); // 使用 handleMouseLeave 关闭
}

// 点击外部关闭下拉菜单
onClickOutside(dropdownContainerRef, () => {
if (isDropdownOpen.value) {
isDropdownOpen.value = false;
// --- Dropdown Logic (like Products dropdown) ---
function handleMouseEnter() {
if (leaveTimeout) {
clearTimeout(leaveTimeout);
leaveTimeout = null;
}
});
isDropdownOpen.value = true;
}

function handleMouseLeave() {
// Delay closing the dropdown slightly
leaveTimeout = setTimeout(() => {
isDropdownOpen.value = false;
}, 150); // 150ms delay
}
// --- End Dropdown Logic ---
</script>

+ 1
- 1
components/TheFooter.vue 查看文件

@@ -8,7 +8,7 @@
>
<!-- Logo & 描述 -->
<div
class="flex flex-col lg:items-start :lg:justify-start sm:items-center text-center lg:text-left"
class="flex flex-col lg:items-start :lg:justify-start sm:items-center text-center lg:text-left col-span-2 lg:col-span-1"
>
<h3 class="mb-4">
<i class="icon-brand text-white text-2xl"></i>

+ 360
- 37
components/TheHeader.vue 查看文件

@@ -1,37 +1,122 @@
<template>
<!-- Header -->
<header class="fixed top-0 z-50 w-full bg-slate-900/70 backdrop-blur-[50px]">
<div class="max-w-screen-2xl mx-auto px-4 sm:px-6 lg:px-10">
<div class="h-[55px] flex justify-between items-center sm:h-[72px]">
<div class="flex justify-start items-center gap-12 lg:gap-24">
<nuxt-link :to="homePath" class="mt-[5px] flex-shrink-0">
<i class="icon-brand text-white text-1xl sm:text-2xl"></i>
<nuxt-link :to="homePath" class="brand-link mt-[5px] flex-shrink-0">
<i
class="icon-brand text-white text-1xl sm:text-2xl block transition-[transform,filter] duration-500 ease-in-out"
></i>
</nuxt-link>
<!-- Desktop Menu -->
<nav class="hidden md:flex justify-start items-start gap-7 lg:gap-14">
<nuxt-link
v-for="item in menuItems"
:key="item.path"
class="justify-start text-white text-sm opacity-80 hover:opacity-100 transition-opacity"
:to="item.path"
:class="{
'text-white font-bold opacity-100': $route.path === item.path,
}"
>
{{ $t(item.label) }}
</nuxt-link>
<template v-for="item in menuItems" :key="item.label">
<!-- Regular Link -->
<nuxt-link
v-if="!item.isDropdown"
class="main-nav-link relative inline-block justify-start text-white text-sm opacity-80 hover:opacity-100 transition-opacity py-2 px-3 rounded-md"
:to="item.path"
:class="[
$route.path === item.path
? 'font-bold opacity-100 bg-white/15'
: '',
]"
>
{{ $t(item.label) }}
</nuxt-link>

<!-- Dropdown Container -->
<div
v-else-if="item.isDropdown"
class="relative"
@mouseleave="handleMouseLeave"
>
<!-- Dropdown Trigger -->
<div
@mouseenter="handleMouseEnter(item.label)"
class="justify-start text-white text-sm opacity-80 hover:opacity-100 transition-opacity cursor-pointer flex items-center gap-1 py-2"
:class="{
'text-white font-bold opacity-100':
openDropdown === item.label ||
$route.path.startsWith(item.pathPrefix),
}"
>
<span>{{ $t(item.label) }}</span>
<!-- Dropdown Arrow -->
<svg
class="h-3 w-3 text-white/60"
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"
/>
</svg>
</div>

<!-- Dropdown Panel -->
<transition name="fade-down">
<div
v-if="item.isDropdown && openDropdown === item.label"
@mouseenter="handleMouseEnter(item.label)"
class="absolute left-0 top-full mt-1 w-max min-w-[450px] bg-slate-800/90 backdrop-blur-md rounded-lg shadow-xl p-4 z-10 grid grid-cols-2 gap-4"
>
<div
v-for="(section, index) in item.children"
:key="index"
class="bg-black/10 p-4 rounded-md"
>
<h3
class="text-sm font-semibold text-gray-300 uppercase tracking-wider mb-4 flex items-center gap-2"
>
<i
:class="[
index === 0 ? 'icon-tag' : 'icon-target',
'text-gray-400',
]"
></i>
<span>{{ $t(section.title) }}</span>
</h3>
<ul class="space-y-3">
<li v-for="link in section.items" :key="link.path">
<nuxt-link
:to="link.path"
@click="handleMouseLeave"
class="block text-base text-gray-200 hover:text-white hover:bg-white/10 transition-all duration-150 rounded px-2 py-1"
:class="{
'text-white font-bold bg-white/5':
$route.path === link.path,
}"
>
{{ $t(link.label) }}
</nuxt-link>
</li>
</ul>
</div>
</div>
</transition>
</div>
</template>
</nav>
</div>

<div class="flex justify-start items-center gap-4 md:gap-6">
<!-- Search -->
<div
class="w-auto h-8 relative items-center opacity-40 rounded-2xl border border-color-[rgba(255,255,255,0.4)] pr-4 hover:opacity-100 transition-opacity duration-300 hidden md:flex"
@click="openSearch"
class="w-auto h-8 relative items-center opacity-40 rounded-2xl pr-4 hover:opacity-100 transition-opacity duration-300 hidden md:flex cursor-pointer"
style="border: 0.5px solid rgba(255, 255, 255, 0.4)"
>
<button
<span
class="flex items-center justify-center w-8 h-8 opacity-80 hover:opacity-100 text-white"
>
<i class="icon-search text-sm"></i>
</button>
</span>
<span
class="hidden lg:inline-block ml-1 text-white text-sm opacity-80"
>
@@ -98,29 +183,134 @@
id="mobile-menu"
>
<div class="px-2 pt-2 pb-3 space-y-1 sm:px-3">
<nuxt-link
v-for="item in menuItems"
:key="item.path"
:to="item.path"
@click="closeMobileMenu"
class="block px-3 py-2 rounded-md text-base font-medium"
:class="[
$route.path === item.path
? 'bg-gray-900 text-white'
: 'text-gray-300 hover:bg-gray-700 hover:text-white',
]"
>{{ $t(item.label) }}</nuxt-link
>
<template v-for="item in menuItems" :key="item.label">
<!-- Mobile Regular Link -->
<nuxt-link
v-if="!item.isDropdown"
:to="item.path"
@click="closeMobileMenu"
class="block px-3 py-2 rounded-md text-base font-medium"
:class="[
$route.path === item.path
? 'bg-gray-900 text-white'
: 'text-gray-300 hover:bg-gray-700 hover:text-white',
]"
>
{{ $t(item.label) }}
</nuxt-link>

<!-- Mobile Dropdown Section -->
<div v-else class="mt-2">
<h3
class="px-3 pt-2 pb-1 text-sm font-semibold text-gray-400 uppercase tracking-wider"
>
{{ $t(item.label) }}
</h3>
<div
v-for="section in item.children"
:key="section.title"
class="mt-1"
>
<!-- Optional: Section title for mobile? -->
<!-- <h4 class="px-3 pt-1 text-xs font-medium text-gray-500">{{ $t(section.title) }}</h4> -->
<nuxt-link
v-for="link in section.items"
:key="link.path"
:to="link.path"
@click="closeMobileMenu"
class="block pl-6 pr-3 py-2 rounded-md text-base font-medium text-gray-300 hover:bg-gray-700 hover:text-white"
:class="{
'bg-gray-900 text-white': $route.path === link.path,
}"
>
{{ $t(link.label) }}
</nuxt-link>
</div>
</div>
</template>
</div>
</div>
</transition>
</header>

<!-- Search Layer (Moved outside header) -->
<transition name="search-fade-scale">
<div
v-if="isSearchOpen"
class="fixed inset-0 z-[60] bg-black/80 backdrop-blur-md flex items-start justify-center pt-20"
@click.self="closeSearch"
>
<div
class="bg-gradient-to-br from-slate-800 to-slate-900 p-8 rounded-lg relative w-full max-w-2xl mx-4 search-modal-content shadow-xl"
>
<button
@click="closeSearch"
class="absolute top-3 right-3 text-gray-500 hover:text-white hover:bg-white/10 rounded-full p-2 transition-colors"
>
<svg
class="h-5 w-5"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M6 18L18 6M6 6l12 12"
/>
</svg>
</button>
<h2 class="text-white text-xl mb-6">{{ $t("common.search") }}</h2>

<!-- Input with Icon -->
<div class="relative mb-6">
<span class="absolute inset-y-0 left-0 flex items-center pl-3">
<i class="icon-search text-gray-400 text-sm"></i>
</span>
<input
ref="searchInputRef"
type="text"
: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"
/>
<!-- Enter Icon -->
<span
class="absolute inset-y-0 right-0 flex items-center pr-3 cursor-pointer group"
>
<i
class="icon-enter text-gray-400 group-hover:text-blue-400 transition-colors"
></i>
</span>
</div>

<!-- Search results could go here -->
<!-- Hot Keywords Section -->
<div class="mt-6">
<h3 class="text-gray-400 text-sm mb-3">
{{ $t("common.hotKeywords") || "热门搜索" }}
</h3>
<div class="flex flex-wrap gap-3">
<button
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"
>
{{ keyword }}
</button>
</div>
</div>
</div>
</div>
</transition>
</template>

<script setup lang="ts">
import { ref, computed } from "vue";
import { ref, computed, watch, nextTick } from "#imports";
import { useI18n } from "vue-i18n";
import { useRuntimeConfig } from '#app'; // 导入 useRuntimeConfig

/**
* 页面头部组件
@@ -129,27 +319,61 @@ import { useRuntimeConfig } from '#app'; // 导入 useRuntimeConfig
const { t, locale } = useI18n();
const config = useRuntimeConfig();
// 从运行时配置获取默认语言,如果未配置则默认为 'en'
const defaultLocale = config.public.i18n?.defaultLocale || 'en';
const defaultLocale = config.public.i18n?.defaultLocale || "en";
const mobileMenuOpen = ref(false);
const isSearchOpen = ref(false);
const searchInputRef = ref<HTMLInputElement | null>(null);
const openDropdown = ref<string | null>(null);
let leaveTimeout: ReturnType<typeof setTimeout> | null = null; // Timeout for mouseleave delay

// 添加热门关键字
const hotKeywords = ref(["SSD", "SD", "DDR4"]);

// 使用 computed 来定义 homePath,根据是否为默认语言调整路径
const homePath = computed(() => {
// 如果是默认语言,路径为根路径 '/'
// 否则,路径为 '/<locale>/'
return locale.value === defaultLocale ? '/' : `/${locale.value}/`;
return locale.value === defaultLocale ? "/" : `/${locale.value}/`;
});


// 使用 computed 来定义 menuItems,根据是否为默认语言调整路径
const menuItems = computed(() => {
// 判断当前是否为默认语言
const isDefaultLocale = locale.value === defaultLocale;
// 如果是默认语言,路径前缀为空字符串,否则为 '/<locale>'
const prefix = isDefaultLocale ? '' : `/${locale.value}`;
const prefix = isDefaultLocale ? "" : `/${locale.value}`;
return [
// 首页路径特殊处理:默认语言为 '/', 其他语言为 '/<locale>/'
{ label: "common.home", path: isDefaultLocale ? '/' : `${prefix}/` },
{ label: "common.products", path: `${prefix}/products` },
{ label: "common.home", path: isDefaultLocale ? "/" : `${prefix}/` },
{
label: "common.products",
isDropdown: true,
pathPrefix: `${prefix}/products`,
children: [
{
title: "common.productCategories",
items: [
{ label: "SSD", path: `${prefix}/products?category=ssd` },
{ label: "DRAM", path: `${prefix}/products?category=dram` },
{ label: "NAND", path: `${prefix}/products?category=nand` },
],
},
{
title: "common.byUsage",
items: [
{
label: "Enterprise",
path: `${prefix}/products?usage=enterprise`,
},
{ label: "Consumer", path: `${prefix}/products?usage=consumer` },
{
label: "Industrial",
path: `${prefix}/products?usage=industrial`,
},
],
},
],
},
{ label: "common.faq", path: `${prefix}/faq` },
{ label: "common.about", path: `${prefix}/about` },
{ label: "common.contact", path: `${prefix}/contact` },
@@ -169,6 +393,57 @@ function toggleMobileMenu() {
function closeMobileMenu() {
mobileMenuOpen.value = false;
}

/**
* 打开搜索层
*/
function openSearch() {
isSearchOpen.value = true;
}

/**
* 关闭搜索层
*/
function closeSearch() {
isSearchOpen.value = false;
}

/**
* 搜索热门关键字 (示例)
* @param keyword
*/
function searchHotKeyword(keyword: string) {
console.log("Searching for hot keyword:", keyword);
// 可以在这里实现填充输入框或直接执行搜索的逻辑
// 例如: searchInputValue.value = keyword;
closeSearch(); // 点击后可以关闭搜索层
}

// 监听搜索层状态,打开时自动聚焦输入框
watch(isSearchOpen, (newValue: boolean) => {
if (newValue) {
nextTick(() => {
searchInputRef.value?.focus();
});
}
});

// --- Dropdown Logic ---
function handleMouseEnter(label: string) {
if (leaveTimeout) {
clearTimeout(leaveTimeout);
leaveTimeout = null;
}
openDropdown.value = label;
}

function handleMouseLeave() {
// Delay closing the dropdown slightly
leaveTimeout = setTimeout(() => {
openDropdown.value = null;
}, 150); // 150ms delay
}
// --- End Dropdown Logic ---
</script>

<style lang="scss" scoped>
@@ -176,6 +451,21 @@ header {
user-select: none;
}

/* Brand icon hover effect */
.brand-link {
&:hover {
.icon-brand {
transform: scale(1.05);
filter: drop-shadow(0 0 6px rgba(255, 255, 255, 0.6));
}
}
}

/* Base style for icon to apply will-change */
.icon-brand {
will-change: transform, filter; /* Hint for browser optimization */
}

/* Transition for mobile menu */
.slide-fade-enter-active {
transition: all 0.3s ease-out;
@@ -191,6 +481,39 @@ header {
opacity: 0;
}

/* Transition for dropdown */
.fade-down-enter-active,
.fade-down-leave-active {
transition: all 0.2s ease-out;
}

.fade-down-enter-from,
.fade-down-leave-to {
opacity: 0;
transform: translateY(-10px);
}

/* Transition for search overlay */
.search-fade-scale-enter-active,
.search-fade-scale-leave-active {
transition: opacity 0.3s ease-out;
}

.search-fade-scale-enter-from,
.search-fade-scale-leave-to {
opacity: 0;
}

/* 为搜索框内容添加独立的、稍延迟的动画 */
.search-modal-content {
transition: transform 0.3s ease-out 0.05s;
}

.search-fade-scale-enter-from .search-modal-content,
.search-fade-scale-leave-to .search-modal-content {
transform: scale(0.95) translateY(10px);
}

/* Keep the sticky header consistent */
.sticky {
position: sticky;

+ 136
- 0
composables/useCaptcha.ts 查看文件

@@ -0,0 +1,136 @@
import { ref, computed } from 'vue';

/**
* 生成随机字符串
* @param length 字符串长度
* @returns 随机字符串
*/
function generateRandomString(length: number): string {
const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
let result = '';
for (let i = 0; i < length; i++) {
result += characters.charAt(Math.floor(Math.random() * characters.length));
}
return result;
}

/**
* 为验证码文本生成扭曲的 SVG
* @param text 验证码文本
* @returns SVG 字符串
*/
function generateCaptchaSvg(text: string): string {
const width = 150;
const height = 50;
const fontSize = 30;
const letters = text.split('');

// 应用于整个 SVG 的滤镜,增加噪点和轻微扭曲
const filter = `
<filter id="captchaNoise">
<feTurbulence baseFrequency="0.7 0.8" numOctaves="3" seed="${Math.random() * 100}" stitchTiles="stitch" type="fractalNoise" result="turbulence"/>
<feDisplacementMap in="SourceGraphic" in2="turbulence" scale="3" xChannelSelector="R" yChannelSelector="G" result="displacement"/>
<feGaussianBlur in="displacement" stdDeviation="0.5" />
</filter>
`;

// 为每个字母生成 <text> 元素,并应用随机变换
const textElements = letters.map((char, index) => {
const x = (width / text.length) * (index + 0.5);
const y = height / 2 + fontSize / 3; // 基线调整
const rotate = Math.random() * 30 - 15; // -15 到 15 度旋转
const skewX = Math.random() * 10 - 5; // 轻微倾斜
const fill = `hsl(${Math.random() * 360}, 70%, 70%)`; // 随机 HSL 颜色

return `<text
x="${x}"
y="${y}"
font-size="${fontSize}"
font-family="Arial, sans-serif"
font-weight="bold"
text-anchor="middle"
fill="${fill}"
transform="rotate(${rotate} ${x} ${y}) skewX(${skewX})"
style="letter-spacing: 1px;"
>${char}</text>`;
}).join('');

// 添加几条干扰线
let lines = '';
for (let i = 0; i < 3; i++) {
lines += `<line
x1="${Math.random() * width * 0.8}"
y1="${Math.random() * height}"
x2="${Math.random() * width * 0.8 + width * 0.2}"
y2="${Math.random() * height}"
stroke="rgba(180, 180, 200, 0.3)"
stroke-width="2"
/>`;
}

return `
<svg
xmlns="http://www.w3.org/2000/svg"
width="${width}"
height="${height}"
viewBox="0 0 ${width} ${height}"
style="background-color: rgba(55, 65, 81, 0.3); border-radius: 6px; border: 1px solid rgba(107, 114, 128, 0.5);"
>
<defs>
${filter}
</defs>
<g style="filter: url(#captchaNoise);">
${textElements}
${lines}
</g>
</svg>
`;
}


export function useCaptcha(length: number = 5) {
const captchaText = ref('');
const userInput = ref('');
const captchaSvg = ref('');
const error = ref('');

/**
* 生成新的验证码
*/
const generateCaptcha = () => {
const newText = generateRandomString(length);
captchaText.value = newText;
captchaSvg.value = generateCaptchaSvg(newText);
userInput.value = ''; // 清空用户输入
error.value = ''; // 清除错误
};

/**
* 验证用户输入
* @returns 是否验证通过
*/
const validateCaptcha = (): boolean => {
if (!userInput.value) {
error.value = '请输入验证码';
return false;
}
if (userInput.value.toLowerCase() !== captchaText.value.toLowerCase()) {
error.value = '验证码错误';
generateCaptcha(); // 错误后自动刷新
return false;
}
error.value = '';
return true;
};

// 初始化生成第一个验证码
generateCaptcha();

return {
userInput,
captchaSvg,
error,
generateCaptcha,
validateCaptcha,
};
}

+ 121
- 37
i18n/locales/en.ts 查看文件

@@ -1,49 +1,133 @@
export default {
common: {
home: 'Home',
products: 'Products',
faq: 'FAQ',
about: 'About Us',
contact: 'Contact',
search: 'Search',
language: 'Language'
home: "Home",
products: "Products",
faq: "FAQ",
about: "About Us",
contact: "Contact",
search: "Search",
language: "Language",
searchPlaceholder: "Search products, FAQ, etc.",
hotKeywords: "Hot Keywords",
productCategories: "Product Categories",
byUsage: "By Usage",
},
home: {
title: 'Welcome to Hanye Website',
description: 'We provide high quality products and services',
learnMore: 'Learn More'
title: "Welcome to Hanye Website",
description: "Providing high-quality products and services.",
learnMore: "Learn More",
},
products: {
title: 'Our Products',
viewDetails: 'View Details',
consultation: 'Product consultation, quotation is welcome',
consultation_button: 'Contact Us',
usage:'Storage by Use',
usage_title:'Choose storage by use case.',
support:'Support',
support_description:'10+ years of professional tech support.',
development:'Own R&D and Manufacturing',
development_description:'We develop, manufacture, and sell memory products online.',
develop:'Innovation',
develop_description:'Developing innovative solutions continuously.',
strong_point:'Our strengths',
strong_point_title:'Our strengths/Why choose us',
title: "Our Products",
viewDetails: "View Details",
consultation:
"Feel free to inquire about product consultations and quotes.",
consultation_button: "Contact Us",
usage: "Choose storage by usage",
usage_title: "Select storage products based on your needs.",
support: "Support",
support_description:
"Expert technical support from our team with over 10 years of experience.",
development: "Independent Development, Manufacturing, and Sales",
development_description:
"Independent development, manufacturing, and online sales of memory and related products.",
develop: "Towards Continuous Development",
develop_description:
"Diversifying products and relentlessly pursuing new creativity.",
strong_point: "Our Strengths",
strong_point_title: "Our Strengths / Why Choose Us",
},
faq: {
title: 'Frequently Asked Questions',
searchPlaceholder: 'Search questions'
title: "Frequently Asked Questions",
searchPlaceholder: "Search questions",
},
about: {
title: 'About Us',
history: 'Company History',
team: 'Our Team',
values: 'Our Values'
title: "About Us",
meta: {
title: "About Us - Hanye",
description:
"Learn about Hanye's company information, history, and business scope. We are dedicated to the development, manufacturing, and sales of memory and related products.",
},
intro: {
title: "About Hanye",
paragraph1:
"Hanye was established in 2003 with its operational headquarters in Shenyang, China.",
paragraph2:
"From its founding to the present, we have grown into a comprehensive enterprise integrating the development, manufacturing, and sales of memory (storage media) and related products.",
paragraph3:
"We have also established a complete after-sales service system to provide full support. We strive to be a trusted partner for our customers and will continue to make further efforts.",
},
overview: {
title: "Company Overview",
companyNameLabel: "Company Name",
companyNameValue: "Hanye",
englishNameLabel: "English Name",
englishNameValue: "Hanye Technology Co., Ltd.",
ceoLabel: "CEO / Representative Director",
ceoValue: "ZHENG XIAO DONG",
employeesLabel: "Number of Employees",
employeesValue: "30",
addressLabel: "Address",
addressValue:
"803, NO.6, AiTe, 90-6# SanHao Street, Heping District, ShenYang, China",
telLabel: "TEL",
telValue: "86)024-8399-0696",
faxLabel: "FAX",
faxValue: "86)024-8399-0696",
businessLabel: "Business Activities",
businessValue1:
"Development, manufacturing, and sales of flash memory products",
businessValue2: "Development, manufacturing, and sales of SSD products",
businessValue3: "Related businesses",
},
contact: {
title: "Contact Information",
emailLabel: "E-mail",
emailValue: "hanye#hanye.cn",
hoursLabel: "Business Hours",
hoursValue1: "Weekdays 9:00 - 18:00",
hoursValue2: "Closed on Saturdays, Sundays, and holidays",
phoneLabel: "Phone",
phoneValue: "86)024-8399-0696",
phoneNote: "Please call for inquiries regarding visits.",
},
},
contact: {
title: 'Contact Us',
name: 'Name',
email: 'Email',
message: 'Message',
submit: 'Submit'
}
}
title: "Contact Us",
name: "Name",
email: "Email Address",
message: "Message",
submit: "Send Message",
form: {
title: "Leave us a message",
nameLabel: "Name",
emailLabel: "Email Address",
messageLabel: "Message",
captchaLabel: "Captcha",
submitLabel: "Send Message",
successMessage: "Message sent successfully. We will contact you soon.",
submitLoading: "Sending...",
captchaRefresh: "Refresh Captcha",
captchaRequired: "Please enter the captcha code",
captchaIncorrect: "Incorrect captcha code",
nameRequired: "Please enter your name",
emailRequired: "Please enter your email address",
emailInvalid: "Please enter a valid email address",
messageRequired: "Please enter your message",
},
info: {
title: "Contact Info",
description: "Leave us a message, and we will get back to you shortly.",
addressLabel: "Address",
addressValue1: "803, NO.6, AiTe, 90-6# SanHao Street,",
addressValue2: "Heping District, ShenYang, China",
phoneLabel: "Phone",
phoneValue: "86)024-8399-0696",
emailLabel: "Email",
emailValue: "info#hanye.com",
hoursLabel: "Business Hours",
hoursValue1: "Mon - Fri: 9:00 AM - 6:00 PM",
hoursValue2: "Sat - Sun: Closed",
},
},
};

+ 109
- 38
i18n/locales/ja.ts 查看文件

@@ -1,50 +1,121 @@
export default {
common: {
home: 'ホーム',
products: '製品',
faq: 'よくある質問',
about: '会社概要',
contact: 'お問い合わせ',
search: '検索',
language: '言語'
home: "ホーム",
products: "製品",
faq: "よくある質問",
about: "会社概要",
contact: "お問い合わせ",
search: "検索",
language: "言語",
searchPlaceholder: "製品、FAQ などを検索",
hotKeywords: "人気のキーワード",
productCategories: "製品カテゴリー",
byUsage: "用途で選ぶ",
},
home: {
title: 'Hanye ウェブサイトへようこそ',
description: '高品質の製品とサービスを提供しています',
learnMore: '詳細を見る'
title: "Hanye ウェブサイトへようこそ",
description: "高品質の製品とサービスを提供しています",
learnMore: "詳細を見る",
},
products: {
title: '当社の製品',
viewDetails: '詳細を見る',
consultation: '製品に関するご相談、お見積もりはお気軽にどうぞ',
consultation_button: 'お問い合わせ',
usage:'使い方でストレージを選ぼう',
usage_title:'用途に応じてストレージ製品を選ぶ。',
support:'サポート',
support_description:'10年以上のキャリアを誇るチームが専門技術でサポート',
development:'独自に開発・製造、販売',
development_description:'メモリ及び関連製品の独自開発、製造・オンラインショップでの販売',
develop:'たゆまぬ発展へ',
develop_description:'製品の多様化を図り、新たな創意への飽くなき挑戦',
strong_point:'当社の強み',
strong_point_title:'当社の強み/選ばれる理由',
title: "当社の製品",
viewDetails: "詳細を見る",
consultation: "製品に関するご相談、お見積もりはお気軽にどうぞ",
consultation_button: "お問い合わせ",
usage: "使い方でストレージを選ぼう",
usage_title: "用途に応じてストレージ製品を選ぶ。",
support: "サポート",
support_description: "10年以上のキャリアを誇るチームが専門技術でサポート",
development: "独自に開発・製造、販売",
development_description:
"メモリ及び関連製品の独自開発、製造・オンラインショップでの販売",
develop: "たゆまぬ発展へ",
develop_description: "製品の多様化を図り、新たな創意への飽くなき挑戦",
strong_point: "当社の強み",
strong_point_title: "当社の強み/選ばれる理由",
},
faq: {
title: 'よくある質問',
searchPlaceholder: '質問を検索'
title: "よくある質問",
searchPlaceholder: "質問を検索",
},
about: {
title: '会社概要',
history: '会社の歴史',
team: 'チームメンバー',
values: '企業価値'
title: "当社について",
meta: {
title: "当社について - Hanye",
description: "Hanyeの会社情報、沿革、事業内容をご覧ください。メモリ及び関連製品の開発・製造・販売に取り組んでいます。"
},
intro: {
title: "当社について",
paragraph1: "Hanye は中国瀋陽に運営本部をおき 2003年に設立されました。",
paragraph2: "創業から現在に至るまで、メモリ(記憶媒体) 及び 関連製品の開発・製造・販売を統べる総合企業に成長いたしました。",
paragraph3: "また万全なアフターサービス体制も構築し全力でサポートいたします。お客様に信頼いただけるパートナーを目指し 一層の努力を重ねてまいります。",
},
overview: {
title: "会社概要",
companyNameLabel: "会社名",
companyNameValue: "Hanye",
englishNameLabel: "英文社名",
englishNameValue: "Hanye Technology Co., Ltd.",
ceoLabel: "代表取締役",
ceoValue: "ZHENG XIAO DONG",
employeesLabel: "従業員数",
employeesValue: "30名",
addressLabel: "所在地",
addressValue: "803, NO.6, AiTe, 90-6# SanHao Street, Heping District, ShenYang, China",
telLabel: "TEL",
telValue: "86)024-8399-0696",
faxLabel: "FAX",
faxValue: "86)024-8399-0696",
businessLabel: "事業内容",
businessValue1: "フラッシュメモリーの開発・製造・販売",
businessValue2: "SSD製品の開発・製造・販売",
businessValue3: "その関連事業",
},
contact: {
title: "連絡先",
emailLabel: "E-mail",
emailValue: "hanye#hanye.cn",
hoursLabel: "営業時間",
hoursValue1: "平日9:00~18:00",
hoursValue2: "土日祝定休",
phoneLabel: "電話",
phoneValue: "86)024-8399-0696",
phoneNote: "ご来社の場合はお電話でお問い合わせください",
}
},
contact: {
title: 'お問い合わせ',
name: 'お名前',
email: 'メールアドレス',
message: 'メッセージ',
submit: '送信'
}
}
title: "お問い合わせ",
name: "お名前",
email: "メールアドレス",
message: "メッセージ",
submit: "送信",
form: {
title: "メッセージを残してください",
nameLabel: "お名前",
emailLabel: "メールアドレス",
messageLabel: "メッセージ",
captchaLabel: "検証コード",
submitLabel: "送信",
successMessage: "メッセージを送信しました。すぐに連絡いたします。",
submitLoading: "送信中...",
captchaRefresh: "検証コードを更新",
captchaRequired: "検証コードを入力してください",
captchaIncorrect: "検証コードが間違っています",
nameRequired: "お名前を入力してください",
},
info: {
title: "お問い合わせ",
description: "メッセージを残してください。すぐに連絡いたします。",
addressLabel: "住所",
addressValue1: "803, NO.6, AiTe, 90-6# SanHao Street, Heping District, ShenYang, China",
addressValue2: "",
phoneLabel: "電話番号",
phoneValue: "86)024-8399-0696",
emailLabel: "メールアドレス",
emailValue: "info#hanye.com",
hoursLabel: "営業時間",
hoursValue1: "月曜日 - 金曜日 9:00-18:00",
hoursValue2: "土曜日 - 日曜日 9:00-18:00",
},
},
};

+ 119
- 37
i18n/locales/zh.ts 查看文件

@@ -1,49 +1,131 @@
export default {
common: {
home: '首页',
products: '产品',
faq: '常见问题',
about: '关于我们',
contact: '联系我们',
search: '搜索',
language: '语言'
home: "首页",
products: "产品",
faq: "常见问题",
about: "关于我们",
contact: "联系我们",
search: "搜索",
language: "语言",
searchPlaceholder: "搜索关键字, 产品、FAQ 等",
hotKeywords: "热门搜索",
productCategories: "产品分类",
byUsage: "按用途",
},
home: {
title: '欢迎来到Hanye官网',
description: '我们提供高质量的产品和服务',
learnMore: '了解更多'
title: "欢迎来到Hanye官网",
description: "我们提供高质量的产品和服务",
learnMore: "了解更多",
},
products: {
title: '我们的产品',
viewDetails: '查看详情',
consultation: '欢迎进行产品咨询,我们将在第一时间回复您',
consultation_button: '联系我们',
usage:'使用方法选择存储',
usage_title:'根据用途选择存储产品。',
support:'支持',
support_description:'拥有10年以上经验的团队提供专业技术支持',
development:'独立开发和制造',
development_description:'独立开发和制造内存及相关产品,并在网上商店销售',
develop:'不断发展',
develop_description:'不断开发和制造产品,提供创新解决方案',
strong_point:'我们的优势',
strong_point_title:'我们的优势/选择我们的理由',
title: "我们的产品",
viewDetails: "查看详情",
consultation: "欢迎进行产品咨询,我们将在第一时间回复您",
consultation_button: "联系我们",
usage: "使用方法选择存储",
usage_title: "根据用途选择存储产品。",
support: "支持",
support_description: "拥有10年以上经验的团队提供专业技术支持",
development: "独立开发和制造",
development_description: "独立开发和制造内存及相关产品,并在网上商店销售",
develop: "不断发展",
develop_description: "不断开发和制造产品,提供创新解决方案",
strong_point: "我们的优势",
strong_point_title: "我们的优势/选择我们的理由",
},
faq: {
title: '常见问题',
searchPlaceholder: '搜索问题'
title: "常见问题",
searchPlaceholder: "搜索问题",
},
about: {
title: '关于我们',
history: '公司历史',
team: '团队成员',
values: '企业价值观'
title: "关于我们",
meta: {
title: "关于我们 - Hanye",
description: "了解 Hanye 的公司信息、发展历程和业务范围。我们致力于内存及相关产品的开发、制造和销售。"
},
intro: {
title: "公司简介",
paragraph1: "Hanye 成立于2003年,运营总部位于中国沈阳。",
paragraph2: "自创立至今,我们已成长为集内存(存储介质)及相关产品的研发、制造、销售于一体的综合性企业。",
paragraph3: "我们建立了完善的售后服务体系,竭诚为您提供支持。我们致力于成为客户信赖的合作伙伴,并将为此付出更多努力。",
},
overview: {
title: "公司概要",
companyNameLabel: "公司名称",
companyNameValue: "Hanye",
englishNameLabel: "英文名称",
englishNameValue: "Hanye Technology Co., Ltd.",
ceoLabel: "法人代表",
ceoValue: "郑晓东", // Translated name if appropriate, otherwise keep original
employeesLabel: "员工人数",
employeesValue: "30人",
addressLabel: "地址",
addressValue: "中国辽宁省沈阳市和平区三好街90-6号艾特国际6号楼803室", // More detailed Chinese address
telLabel: "电话",
telValue: "86)024-8399-0696",
faxLabel: "传真",
faxValue: "86)024-8399-0696",
businessLabel: "业务范围",
businessValue1: "闪存产品的开发、制造、销售",
businessValue2: "SSD产品的开发、制造、销售",
businessValue3: "及相关业务",
},
contact: {
title: "联系方式",
emailLabel: "电子邮箱",
emailValue: "hanye#hanye.cn",
hoursLabel: "营业时间",
hoursValue1: "工作日 9:00 - 18:00",
hoursValue2: "周六、周日及法定节假日休息",
phoneLabel: "电话",
phoneValue: "86)024-8399-0696",
phoneNote: "如需来访,请先电话联系。",
}
},
contact: {
title: '联系我们',
name: '姓名',
email: '邮箱',
message: '消息',
submit: '提交'
}
}
title: "联系我们",
name: "姓名",
email: "邮箱",
message: "消息",
submit: "提交",
form: {
title: "给我们留言",
nameLabel: "姓名",
emailLabel: "邮箱",
messageLabel: "消息",
captchaLabel: "验证码",
submitLabel: "提交",
successMessage: "消息已成功发送,我们会尽快与您联系。",
submitLoading: "正在发送...",
captchaRefresh: "点击刷新验证码",
captchaRequired: "请输入验证码",
captchaIncorrect: "验证码不正确",
nameRequired: "请输入您的姓名",
emailRequired: "请输入您的邮箱",
messageRequired: "请输入您的消息",
emailInvalid: "请输入有效的邮箱地址",
},
validation: {
nameRequired: "请输入您的姓名",
emailRequired: "请输入您的邮箱",
messageRequired: "请输入您的消息",
emailInvalid: "请输入有效的邮箱地址",
},
captcha: {
required: "请输入验证码",
incorrect: "验证码不正确",
},
info: {
title: "联系我们",
description: "欢迎给我们留言,我们将在第一时间回复您",
addressLabel: "地址",
addressValue1: "803, NO.6, AiTe, 90-6# SanHao Street, Heping District, ShenYang, China",
addressValue2: "中国辽宁省沈阳市和平区三好街90-6号艾特国际大厦803室",
phoneLabel: "电话",
emailLabel: "邮箱",
hoursLabel: "工作时间",
hoursValue1: "周一至周五 9:00-18:00 (节假日除外)",
hoursValue2: "周六至周日 9:00-18:00 (节假日除外)",
},
},
};

+ 226
- 68
pages/about.vue 查看文件

@@ -1,88 +1,246 @@
<template>
<div class="py-8">
<div class="container-custom">
<h1 class="text-3xl font-bold mb-8">{{ $t('about.title') }}</h1>
<div class="bg-white border border-gray-200 rounded-lg overflow-hidden">
<div class="p-8">
<section class="mb-12">
<h2 class="text-2xl font-semibold mb-4">{{ $t('about.history') }}</h2>
<p class="text-gray-700 mb-4">
汉业科技成立于2020年,是一家专注于提供高质量产品和服务的科技公司。
从创立之初,我们就致力于通过创新技术解决客户面临的挑战,为客户创造价值。
</p>
<p class="text-gray-700">
经过多年的发展,我们已成为行业内备受认可的品牌,拥有众多忠实客户。
我们的产品覆盖了多个领域,为客户提供了全方位的解决方案。
</p>
</section>
<section class="mb-12">
<h2 class="text-2xl font-semibold mb-4">{{ $t('about.values') }}</h2>
<div class="grid grid-cols-1 md:grid-cols-3 gap-6">
<div class="bg-blue-50 p-6 rounded-lg">
<h3 class="text-xl font-medium mb-2 text-blue-700">创新精神</h3>
<p class="text-gray-700">
我们不断探索新技术、新方法,为客户提供创新的解决方案。
</p>
</div>
<div class="bg-green-50 p-6 rounded-lg">
<h3 class="text-xl font-medium mb-2 text-green-700">专业品质</h3>
<p class="text-gray-700">
我们坚持高标准,确保每一个产品都经过严格的质量控制。
</p>
<div class="w-full h-[55px] sm:h-[72px]"></div>
<div
class="max-w-full px-4 py-16 md:px-8 lg:px-10 bg-gray-900 text-gray-300 min-h-screen relative overflow-hidden"
>
<!-- Subtle Pattern Background -->
<div class="absolute inset-0 opacity-[0.03] pointer-events-none"
style="background-image: linear-gradient(45deg, #fff 12%, transparent 12.5%, transparent 87%, #fff 87.5%, #fff),
linear-gradient(-45deg, #fff 12%, transparent 12.5%, transparent 87%, #fff 87.5%, #fff);
background-size: 8px 8px;">
</div>
<!-- Content Wrapper -->
<div class="relative max-w-screen-xl mx-auto">
<h1 class="text-4xl md:text-6xl mb-12 text-center font-normal text-white">
{{ $t("about.title") }}
</h1>

<div class="space-y-12">
<!-- Introduction Section -->
<section
class="relative bg-gray-800/80 border border-gray-700 rounded-xl overflow-hidden shadow-xl backdrop-blur-sm p-6 sm:p-8 lg:p-10"
>
<div class="mb-8">
<h2
class="text-2xl font-semibold text-gray-100 sm:text-3xl inline-block"
>
{{ $t("about.intro.title") }}
</h2>
<div
class="h-1 w-20 bg-gradient-to-r from-blue-500 to-purple-500 rounded-full mt-2"
></div>
</div>
<div class="space-y-4 text-lg text-gray-300 leading-relaxed">
<p>{{ $t("about.intro.paragraph1") }}</p>
<p>{{ $t("about.intro.paragraph2") }}</p>
<p>{{ $t("about.intro.paragraph3") }}</p>
</div>
</section>

<!-- Company Overview Section -->
<section
class="relative bg-gray-800/80 border border-gray-700 rounded-xl overflow-hidden shadow-xl backdrop-blur-sm p-6 sm:p-8 lg:p-10"
>
<div class="mb-8">
<h2
class="text-2xl font-semibold text-gray-100 sm:text-3xl inline-block"
>
{{ $t("about.overview.title") }}
</h2>
<div
class="h-1 w-20 bg-gradient-to-r from-blue-500 to-purple-500 rounded-full mt-2"
></div>
</div>
<dl
class="grid grid-cols-1 gap-x-6 gap-y-8 sm:grid-cols-2 lg:grid-cols-3"
>
<div class="sm:col-span-1">
<dt class="text-base font-semibold text-gray-100">
{{ $t("about.overview.companyNameLabel") }}
</dt>
<dd class="mt-1 text-base text-gray-400">
{{ $t("about.overview.companyNameValue") }}
</dd>
</div>
<div class="sm:col-span-1">
<dt class="text-base font-semibold text-gray-100">
{{ $t("about.overview.englishNameLabel") }}
</dt>
<dd class="mt-1 text-base text-gray-400">
{{ $t("about.overview.englishNameValue") }}
</dd>
</div>
<div class="sm:col-span-1">
<dt class="text-base font-semibold text-gray-100">
{{ $t("about.overview.ceoLabel") }}
</dt>
<dd class="mt-1 text-base text-gray-400">
{{ $t("about.overview.ceoValue") }}
</dd>
</div>
<div class="sm:col-span-1">
<dt class="text-base font-semibold text-gray-100">
{{ $t("about.overview.employeesLabel") }}
</dt>
<dd class="mt-1 text-base text-gray-400">
{{ $t("about.overview.employeesValue") }}
</dd>
</div>
<div class="sm:col-span-2">
<dt class="text-base font-semibold text-gray-100">
{{ $t("about.overview.addressLabel") }}
</dt>
<dd class="mt-1 text-base text-gray-400">
{{ $t("about.overview.addressValue") }}
</dd>
</div>
<div class="sm:col-span-1">
<dt class="text-base font-semibold text-gray-100">
{{ $t("about.overview.telLabel") }}
</dt>
<dd class="mt-1 text-base text-gray-400">
{{ $t("about.overview.telValue") }}
</dd>
</div>
<div class="sm:col-span-1">
<dt class="text-base font-semibold text-gray-100">
{{ $t("about.overview.faxLabel") }}
</dt>
<dd class="mt-1 text-base text-gray-400">
{{ $t("about.overview.faxValue") }}
</dd>
</div>
<div class="sm:col-span-2 lg:col-span-3">
<dt class="text-base font-semibold text-gray-100">
{{ $t("about.overview.businessLabel") }}
</dt>
<dd class="mt-1 text-base text-gray-400">
{{ $t("about.overview.businessValue1") }}<br />
{{ $t("about.overview.businessValue2") }}<br />
{{ $t("about.overview.businessValue3") }}
</dd>
</div>
</dl>
</section>

<!-- Contact Info Section -->
<section
class="relative bg-gray-800/80 border border-gray-700 rounded-xl overflow-hidden shadow-xl backdrop-blur-sm p-6 sm:p-8 lg:p-10"
>
<div class="mb-8">
<h2
class="text-2xl font-semibold text-gray-100 sm:text-3xl inline-block"
>
{{ $t("about.contact.title") }}
</h2>
<div
class="h-1 w-20 bg-gradient-to-r from-blue-500 to-purple-500 rounded-full mt-2"
></div>
</div>
<div class="grid grid-cols-1 gap-8 md:grid-cols-2 lg:grid-cols-3">
<div class="flex items-start space-x-4">
<div
class="flex-shrink-0 h-10 w-10 flex items-center justify-center bg-gradient-to-br from-blue-500 to-purple-600 text-white rounded-lg shadow-md"
>
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-5 w-5"
viewBox="0 0 20 20"
fill="currentColor"
>
<path
d="M2.003 5.884L10 9.882l7.997-3.998A2 2 0 0016 4H4a2 2 0 00-1.997 1.884z"
/>
<path
d="M18 8.118l-8 4-8-4V14a2 2 0 002 2h12a2 2 0 002-2V8.118z"
/>
</svg>
</div>
<div class="bg-yellow-50 p-6 rounded-lg">
<h3 class="text-xl font-medium mb-2 text-yellow-700">客户至上</h3>
<p class="text-gray-700">
客户的满意是我们最大的追求,我们致力于超越客户期望。
<div>
<h3 class="text-base font-medium text-gray-100">
{{ $t("about.contact.emailLabel") }}
</h3>
<p class="mt-1 text-base text-gray-400">
{{ $t("about.contact.emailValue") }}
</p>
</div>
</div>
</section>
<section>
<h2 class="text-2xl font-semibold mb-4">{{ $t('about.team') }}</h2>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
<div class="text-center">
<div class="h-40 w-40 bg-gray-200 rounded-full mx-auto mb-4"></div>
<h3 class="text-xl font-medium">张三</h3>
<p class="text-gray-600">创始人 & CEO</p>
<div class="flex items-start space-x-4">
<div
class="flex-shrink-0 h-10 w-10 flex items-center justify-center bg-gradient-to-br from-blue-500 to-purple-600 text-white rounded-lg shadow-md"
>
<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="M10 18a8 8 0 100-16 8 8 0 000 16zm1-12a1 1 0 10-2 0v4a1 1 0 00.293.707l2.828 2.829a1 1 0 101.415-1.415L11 9.586V6z"
clip-rule="evenodd"
/>
</svg>
</div>
<div class="text-center">
<div class="h-40 w-40 bg-gray-200 rounded-full mx-auto mb-4"></div>
<h3 class="text-xl font-medium">李四</h3>
<p class="text-gray-600">技术总监</p>
<div>
<h3 class="text-base font-medium text-gray-100">
{{ $t("about.contact.hoursLabel") }}
</h3>
<p class="mt-1 text-base text-gray-400">
{{ $t("about.contact.hoursValue1") }}
</p>
<p class="mt-1 text-base text-gray-400">
{{ $t("about.contact.hoursValue2") }}
</p>
</div>
<div class="text-center">
<div class="h-40 w-40 bg-gray-200 rounded-full mx-auto mb-4"></div>
<h3 class="text-xl font-medium">王五</h3>
<p class="text-gray-600">产品经理</p>
</div>
<div class="flex items-start space-x-4">
<div
class="flex-shrink-0 h-10 w-10 flex items-center justify-center bg-gradient-to-br from-blue-500 to-purple-600 text-white rounded-lg shadow-md"
>
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-5 w-5"
viewBox="0 0 20 20"
fill="currentColor"
>
<path
d="M2 3a1 1 0 011-1h2.153a1 1 0 01.986.836l.74 4.435a1 1 0 01-.54 1.06l-1.548.773a11.037 11.037 0 006.105 6.105l.774-1.548a1 1 0 011.059-.54l4.435.74a1 1 0 01.836.986V17a1 1 0 01-1 1h-2C7.82 18 2 12.18 2 5V3z"
/>
</svg>
</div>
<div class="text-center">
<div class="h-40 w-40 bg-gray-200 rounded-full mx-auto mb-4"></div>
<h3 class="text-xl font-medium">赵六</h3>
<p class="text-gray-600">营销总监</p>
<div>
<h3 class="text-base font-medium text-gray-100">
{{ $t("about.contact.phoneLabel") }}
</h3>
<p class="mt-1 text-base text-gray-400">
{{ $t("about.contact.phoneValue") }}
</p>
<p class="mt-1 text-sm text-gray-500">
{{ $t("about.contact.phoneNote") }}
</p>
</div>
</div>
</section>
</div>
</div>
</section>
</div>
</div>
</div>
</template>

<script setup lang="ts">
/**
* 关于我们页面
* 展示公司历史、价值观和团队成员
*/
import { useI18n } from "vue-i18n";

const { t } = useI18n();

// SEO优化
// SEO
useHead({
title: '关于我们 - Hanye',
title: t("about.meta.title"),
meta: [
{ name: 'description', content: '了解我们的公司历史、企业价值观和团队成员。汉业科技致力于提供高质量的产品和服务。' }
]
{
name: "description",
content: t("about.meta.description"),
},
],
});
</script>
</script>

+ 391
- 123
pages/contact.vue 查看文件

@@ -1,133 +1,387 @@
<template>
<div class="py-8">
<div class="container-custom">
<h1 class="text-3xl font-bold mb-8">{{ $t('contact.title') }}</h1>
<div class="grid grid-cols-1 lg:grid-cols-2 gap-8">
<div class="w-full h-[55px] sm:h-[72px]"></div>
<div
class="max-w-full px-4 py-16 md:px-8 lg:px-10 bg-gradient-to-br from-gray-900 via-gray-900 to-black text-gray-300 min-h-screen"
>
<div class="max-w-screen-xl mx-auto">
<h1 class="text-4xl md:text-6xl mb-12 text-center font-normal text-white">
{{ $t("contact.title") }}
</h1>

<div class="grid grid-cols-1 gap-10 lg:grid-cols-2 lg:gap-16">
<!-- 联系表单 -->
<div class="bg-white border border-gray-200 rounded-lg overflow-hidden">
<div class="p-8">
<h2 class="text-2xl font-semibold mb-6">给我们留言</h2>
<div
class="relative bg-gray-800/70 border border-gray-700 rounded-xl overflow-hidden shadow-2xl backdrop-blur-sm transition-all duration-300 ease-in-out hover:shadow-blue-500/30 hover:border-blue-500/50 group"
>
<div
class="absolute inset-0 bg-gradient-to-r from-transparent via-blue-900/10 to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-500"
></div>
<div class="relative p-6 sm:p-8 lg:p-10">
<div class="mb-8">
<h2
class="text-2xl font-semibold text-gray-100 sm:text-3xl inline-block"
>
{{ $t("contact.form.title") }}
</h2>
<div
class="h-1 w-20 bg-gradient-to-r from-blue-500 to-purple-500 rounded-full mt-2"
></div>
</div>

<ErrorBoundary :error="error">
<form @submit.prevent="submitForm">
<div class="mb-4">
<label for="name" class="block text-gray-700 font-medium mb-2">{{ $t('contact.name') }}</label>
<input
type="text"
id="name"
v-model="formData.name"
class="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
:class="{ 'border-red-500': formErrors.name }"
<form @submit.prevent="submitForm" class="space-y-10">
<div class="relative">
<input
type="text"
id="name"
v-model="formData.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]"
:class="[
formErrors.name
? 'border-red-500 focus:border-red-500'
: 'border-gray-600 focus:border-blue-500',
]"
:placeholder="$t('contact.name')"
required
:aria-invalid="formErrors.name ? 'true' : 'false'"
aria-describedby="name-error"
/>
<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"
:class="[
formErrors.name
? 'text-red-400 peer-focus:text-red-400'
: 'text-gray-400 peer-focus:text-blue-400',
]"
>{{ $t("contact.name") }}</label
>
<p v-if="formErrors.name" class="mt-1 text-sm text-red-600">{{ formErrors.name }}</p>
<p
v-if="formErrors.name"
id="name-error"
class="mt-1.5 text-xs text-red-400 sm:text-sm"
>
{{ formErrors.name }}
</p>
</div>
<div class="mb-4">
<label for="email" class="block text-gray-700 font-medium mb-2">{{ $t('contact.email') }}</label>
<input
type="email"
id="email"
v-model="formData.email"
class="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
:class="{ 'border-red-500': formErrors.email }"

<div class="relative">
<input
type="email"
id="email"
v-model="formData.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]"
:class="[
formErrors.email
? 'border-red-500 focus:border-red-500'
: 'border-gray-600 focus:border-blue-500',
]"
:placeholder="$t('contact.email')"
required
:aria-invalid="formErrors.email ? 'true' : 'false'"
aria-describedby="email-error"
/>
<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"
:class="[
formErrors.email
? 'text-red-400 peer-focus:text-red-400'
: 'text-gray-400 peer-focus:text-blue-400',
]"
>{{ $t("contact.email") }}</label
>
<p v-if="formErrors.email" class="mt-1 text-sm text-red-600">{{ formErrors.email }}</p>
<p
v-if="formErrors.email"
id="email-error"
class="mt-1.5 text-xs text-red-400 sm:text-sm"
>
{{ formErrors.email }}
</p>
</div>
<div class="mb-6">
<label for="message" class="block text-gray-700 font-medium mb-2">{{ $t('contact.message') }}</label>
<textarea
id="message"
v-model="formData.message"
class="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 h-32"
:class="{ 'border-red-500': formErrors.message }"

<div class="relative">
<textarea
id="message"
v-model="formData.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]"
:class="[
formErrors.message
? 'border-red-500 focus:border-red-500'
: 'border-gray-600 focus:border-blue-500',
]"
:placeholder="$t('contact.message')"
required
rows="5"
:aria-invalid="formErrors.message ? 'true' : 'false'"
aria-describedby="message-error"
></textarea>
<p v-if="formErrors.message" class="mt-1 text-sm text-red-600">{{ formErrors.message }}</p>
<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"
:class="[
formErrors.message
? 'text-red-400 peer-focus:text-red-400'
: 'text-gray-400 peer-focus:text-blue-400',
]"
>{{ $t("contact.message") }}</label
>
<p
v-if="formErrors.message"
id="message-error"
class="mt-1.5 text-xs text-red-400 sm:text-sm"
>
{{ formErrors.message }}
</p>
</div>

<!-- Captcha Section -->
<div class="relative pt-2">
<div class="flex items-center space-x-3">
<!-- Captcha Input -->
<div class="flex-grow relative">
<input
type="text"
id="captcha"
v-model="captcha.userInput.value"
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]"
:class="[
captcha.error.value
? 'border-red-500 focus:border-red-500'
: 'border-gray-600 focus:border-blue-500',
]"
:placeholder="$t('contact.form.captchaLabel')"
required
autocomplete="off"
aria-describedby="captcha-error"
:aria-invalid="captcha.error.value ? 'true' : 'false'"
/>
<label
for="captcha"
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"
:class="[
captcha.error.value
? 'text-red-400 peer-focus:text-red-400'
: 'text-gray-400 peer-focus:text-blue-400',
]"
>{{ $t("contact.form.captchaLabel") }}</label
>
</div>
<!-- Captcha Image/SVG -->
<div
class="flex-shrink-0 cursor-pointer rounded-md overflow-hidden transition-all duration-200 ease-in-out hover:scale-105 hover:shadow-md active:scale-100"
v-html="captcha.captchaSvg.value"
@click="captcha.generateCaptcha()"
:title="$t('contact.form.captchaRefresh')"
style="line-height: 0"
></div>
<!-- Refresh Button -->
<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"
:aria-label="$t('contact.form.captchaRefresh')"
:title="$t('contact.form.captchaRefresh')"
>
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-5 w-5"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
stroke-width="2"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"
/>
</svg>
</button>
</div>
<p
v-if="captcha.error.value"
id="captcha-error"
class="mt-1.5 text-xs text-red-400 sm:text-sm"
>
{{
captcha.error.value === "请输入验证码"
? $t("contact.validation.captchaRequired")
: $t("contact.validation.captchaIncorrect")
}}
</p>
</div>
<div>
<button
type="submit"
class="btn btn-primary w-full"
<div class="pt-6">
<button
type="submit"
class="w-full bg-gradient-to-r from-blue-600 to-purple-600 text-white font-semibold py-3.5 px-6 rounded-lg shadow-lg hover:shadow-xl hover:from-blue-500 hover:to-purple-500 transform hover:-translate-y-0.5 transition-all duration-300 ease-in-out disabled:opacity-50 disabled:cursor-not-allowed disabled:transform-none disabled:shadow-md text-base tracking-wide focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-purple-500 focus-visible:ring-offset-gray-900"
:disabled="isLoading"
>
<span v-if="isLoading" class="flex items-center justify-center">
<span class="animate-spin h-4 w-4 border-2 border-white rounded-full border-t-transparent mr-2"></span>
提交中...
<span
v-if="isLoading"
class="flex items-center justify-center"
>
<span
class="animate-spin h-5 w-5 border-2 border-white rounded-full border-t-transparent mr-2.5"
></span>
{{ $t("contact.form.submitLoading") }}
</span>
<span v-else>{{ $t('contact.submit') }}</span>
<span v-else>{{ $t("contact.submit") }}</span>
</button>
</div>
<div v-if="submitSuccess" class="mt-4 p-3 bg-green-50 text-green-800 rounded-md">
消息已成功发送,我们会尽快与您联系。

<div
v-if="submitSuccess"
class="mt-6 p-4 bg-green-600/20 text-green-300 rounded-lg border border-green-500/50 text-sm flex items-center space-x-2"
role="alert"
>
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-5 w-5 flex-shrink-0"
viewBox="0 0 20 20"
fill="currentColor"
>
<path
fill-rule="evenodd"
d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z"
clip-rule="evenodd"
/>
</svg>
<span>{{ $t("contact.form.successMessage") }}</span>
</div>
</form>
</ErrorBoundary>
</div>
</div>
<!-- 联系信息 -->
<div class="bg-white border border-gray-200 rounded-lg overflow-hidden">
<div class="p-8">
<h2 class="text-2xl font-semibold mb-6">联系方式</h2>
<div class="space-y-6">
<div class="flex items-start">
<div class="flex-shrink-0 h-10 w-10 flex items-center justify-center bg-blue-100 text-blue-600 rounded-full">
<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="M5.05 4.05a7 7 0 119.9 9.9L10 18.9l-4.95-4.95a7 7 0 010-9.9zM10 11a2 2 0 100-4 2 2 0 000 4z" clip-rule="evenodd" />
<div
class="relative bg-gray-800/70 border border-gray-700 rounded-xl overflow-hidden shadow-2xl backdrop-blur-sm transition-all duration-300 ease-in-out hover:shadow-purple-500/30 hover:border-purple-500/50 group"
>
<div
class="absolute inset-0 bg-gradient-to-r from-transparent via-purple-900/10 to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-500"
></div>
<div class="relative p-6 sm:p-8 lg:p-10">
<div class="mb-8">
<h2
class="text-2xl font-semibold text-gray-100 sm:text-3xl inline-block"
>
{{ $t("contact.info.title") }}
</h2>
<div
class="h-1 w-20 bg-gradient-to-r from-blue-500 to-purple-500 rounded-full mt-2"
></div>
</div>

<div class="space-y-8">
<div class="flex items-center">
<div
class="flex-shrink-0 h-12 w-12 flex items-center justify-center bg-gradient-to-br from-blue-500 to-purple-600 text-white rounded-full shadow-lg"
>
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-6 w-6"
viewBox="0 0 20 20"
fill="currentColor"
aria-hidden="true"
>
<path
fill-rule="evenodd"
d="M5.05 4.05a7 7 0 119.9 9.9L10 18.9l-4.95-4.95a7 7 0 010-9.9zM10 11a2 2 0 100-4 2 2 0 000 4z"
clip-rule="evenodd"
/>
</svg>
</div>
<div class="ml-4">
<h3 class="text-lg font-medium text-gray-900">地址</h3>
<p class="mt-1 text-gray-600">
中国上海市浦东新区张江高科技园区<br>
科技大道123号
<h3 class="text-lg font-medium text-gray-100">
{{ $t("contact.info.addressLabel") }}
</h3>
<p class="mt-1 text-base text-gray-400">
{{ $t("contact.info.addressValue1") }}<br />
{{ $t("contact.info.addressValue2") }}
</p>
</div>
</div>
<div class="flex items-start">
<div class="flex-shrink-0 h-10 w-10 flex items-center justify-center bg-blue-100 text-blue-600 rounded-full">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
<path d="M2 3a1 1 0 011-1h2.153a1 1 0 01.986.836l.74 4.435a1 1 0 01-.54 1.06l-1.548.773a11.037 11.037 0 006.105 6.105l.774-1.548a1 1 0 011.059-.54l4.435.74a1 1 0 01.836.986V17a1 1 0 01-1 1h-2C7.82 18 2 12.18 2 5V3z" />

<div class="flex items-center">
<div
class="flex-shrink-0 h-12 w-12 flex items-center justify-center bg-gradient-to-br from-blue-500 to-purple-600 text-white rounded-full shadow-lg"
>
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-6 w-6"
viewBox="0 0 20 20"
fill="currentColor"
aria-hidden="true"
>
<path
d="M2 3a1 1 0 011-1h2.153a1 1 0 01.986.836l.74 4.435a1 1 0 01-.54 1.06l-1.548.773a11.037 11.037 0 006.105 6.105l.774-1.548a1 1 0 011.059-.54l4.435.74a1 1 0 01.836.986V17a1 1 0 01-1 1h-2C7.82 18 2 12.18 2 5V3z"
/>
</svg>
</div>
<div class="ml-4">
<h3 class="text-lg font-medium text-gray-900">电话</h3>
<p class="mt-1 text-gray-600">+86 123 456 7890</p>
<h3 class="text-lg font-medium text-gray-100">
{{ $t("contact.info.phoneLabel") }}
</h3>
<p class="mt-1 text-base text-gray-400">+86 123 456 7890</p>
</div>
</div>
<div class="flex items-start">
<div class="flex-shrink-0 h-10 w-10 flex items-center justify-center bg-blue-100 text-blue-600 rounded-full">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
<path d="M2.003 5.884L10 9.882l7.997-3.998A2 2 0 0016 4H4a2 2 0 00-1.997 1.884z" />
<path d="M18 8.118l-8 4-8-4V14a2 2 0 002 2h12a2 2 0 002-2V8.118z" />

<div class="flex items-center">
<div
class="flex-shrink-0 h-12 w-12 flex items-center justify-center bg-gradient-to-br from-blue-500 to-purple-600 text-white rounded-full shadow-lg"
>
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-6 w-6"
viewBox="0 0 20 20"
fill="currentColor"
aria-hidden="true"
>
<path
d="M2.003 5.884L10 9.882l7.997-3.998A2 2 0 0016 4H4a2 2 0 00-1.997 1.884z"
/>
<path
d="M18 8.118l-8 4-8-4V14a2 2 0 002 2h12a2 2 0 002-2V8.118z"
/>
</svg>
</div>
<div class="ml-4">
<h3 class="text-lg font-medium text-gray-900">邮箱</h3>
<p class="mt-1 text-gray-600">contact@example.com</p>
<h3 class="text-lg font-medium text-gray-100">
{{ $t("contact.info.emailLabel") }}
</h3>
<p class="mt-1 text-base text-gray-400">
contact@example.com
</p>
</div>
</div>
<div class="flex items-start">
<div class="flex-shrink-0 h-10 w-10 flex items-center justify-center bg-blue-100 text-blue-600 rounded-full">
<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="M10 18a8 8 0 100-16 8 8 0 000 16zm1-12a1 1 0 10-2 0v4a1 1 0 00.293.707l2.828 2.829a1 1 0 101.415-1.415L11 9.586V6z" clip-rule="evenodd" />

<div class="flex items-center">
<div
class="flex-shrink-0 h-12 w-12 flex items-center justify-center bg-gradient-to-br from-blue-500 to-purple-600 text-white rounded-full shadow-lg"
>
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-6 w-6"
viewBox="0 0 20 20"
fill="currentColor"
aria-hidden="true"
>
<path
fill-rule="evenodd"
d="M10 18a8 8 0 100-16 8 8 0 000 16zm1-12a1 1 0 10-2 0v4a1 1 0 00.293.707l2.828 2.829a1 1 0 101.415-1.415L11 9.586V6z"
clip-rule="evenodd"
/>
</svg>
</div>
<div class="ml-4">
<h3 class="text-lg font-medium text-gray-900">工作时间</h3>
<p class="mt-1 text-gray-600">
周一至周五: 9:00 - 18:00<br>
周六、周日: 休息
<h3 class="text-lg font-medium text-gray-100">
{{ $t("contact.info.hoursLabel") }}
</h3>
<p class="mt-1 text-base text-gray-400">
{{ $t("contact.info.hoursValue1") }}<br />
{{ $t("contact.info.hoursValue2") }}
</p>
</div>
</div>
@@ -144,24 +398,27 @@
* 联系我们页面
* 提供联系表单和联系信息
*/
import { ref, reactive } from 'vue';
import { useErrorHandler } from '~/composables/useErrorHandler';
import { useErrorHandler } from "~/composables/useErrorHandler";
import { useCaptcha } from "~/composables/useCaptcha";
import { useI18n } from "vue-i18n";

const { error, isLoading, wrapAsync } = useErrorHandler();
const captcha = useCaptcha();
const submitSuccess = ref(false);
const { t } = useI18n();

// 表单数据
const formData = reactive({
name: '',
email: '',
message: ''
name: "",
email: "",
message: "",
});

// 表单错误
const formErrors = reactive({
name: '',
email: '',
message: ''
name: "",
email: "",
message: "",
});

/**
@@ -170,33 +427,33 @@ const formErrors = reactive({
*/
function validateForm(): boolean {
let isValid = true;
// 重置错误
formErrors.name = '';
formErrors.email = '';
formErrors.message = '';
formErrors.name = "";
formErrors.email = "";
formErrors.message = "";
// 验证姓名
if (!formData.name.trim()) {
formErrors.name = '请输入您的姓名';
formErrors.name = t("contact.validation.nameRequired");
isValid = false;
}
// 验证邮箱
if (!formData.email.trim()) {
formErrors.email = '请输入您的邮箱';
formErrors.email = t("contact.validation.emailRequired");
isValid = false;
} else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(formData.email)) {
formErrors.email = '请输入有效的邮箱地址';
formErrors.email = t("contact.validation.emailInvalid");
isValid = false;
}
// 验证消息
if (!formData.message.trim()) {
formErrors.message = '请输入您的消息';
formErrors.message = t("contact.validation.messageRequired");
isValid = false;
}
return isValid;
}

@@ -206,34 +463,45 @@ function validateForm(): boolean {
async function submitForm() {
// 重置成功状态
submitSuccess.value = false;
// 验证表单
// 验证表单(姓名、邮箱、消息)
if (!validateForm()) {
return;
}

// 验证验证码
if (!captcha.validateCaptcha()) {
return;
}

// 提交表单数据
await wrapAsync(async () => {
// 模拟API请求
await new Promise(resolve => setTimeout(resolve, 1000));
console.log("Form Data:", formData);
console.log("Captcha Validated!");
await new Promise((resolve) => setTimeout(resolve, 1500));

// 模拟成功响应
submitSuccess.value = true;
// 清空表单
formData.name = '';
formData.email = '';
formData.message = '';

// 清空表单和验证码
formData.name = "";
formData.email = "";
formData.message = "";
captcha.generateCaptcha(); // 成功后也刷新验证码

return true;
});
}

// SEO优化
useHead({
title: '联系我们 - Hanye',
title: t("contact.meta.title"),
meta: [
{ name: 'description', content: '联系我们获取更多信息或咨询服务。我们期待收到您的留言。' }
]
{
name: "description",
content: t("contact.meta.description"),
},
],
});
</script>
</script>

+ 220
- 0
pages/faq.vue 查看文件

@@ -0,0 +1,220 @@
<template>
<div class="w-full h-[55px] sm:h-[72px]"></div>
<div
class="max-w-full px-4 py-16 md:px-8 lg:px-10 bg-gradient-to-br from-gray-900 via-gray-900 to-black text-gray-300 min-h-screen"
>
<div class="max-w-screen-xl mx-auto">
<h1 class="text-4xl md:text-6xl mb-12 text-center font-normal text-white">
{{ $t("faq.title") }}
</h1>

<!-- Search Bar -->
<div class="mb-12 max-w-xl mx-auto">
<div class="relative">
<input
type="search"
v-model="searchTerm"
:placeholder="$t('faq.searchPlaceholder')"
class="block w-full appearance-none rounded-lg border border-gray-600 bg-gray-700/50 px-4 py-3 pl-10 pr-4 text-base text-gray-100 placeholder-gray-400 shadow-inner transition duration-200 ease-in-out focus:border-blue-500 focus:outline-none focus:ring-2 focus:ring-blue-500/50 focus:ring-offset-2 focus:ring-offset-gray-900"
/>
<div
class="pointer-events-none absolute inset-y-0 left-0 flex items-center pl-3"
>
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-5 w-5 text-gray-400"
viewBox="0 0 20 20"
fill="currentColor"
>
<path
fill-rule="evenodd"
d="M8 4a4 4 0 100 8 4 4 0 000-8zM2 8a6 6 0 1110.89 3.476l4.817 4.817a1 1 0 01-1.414 1.414l-4.816-4.816A6 6 0 012 8z"
clip-rule="evenodd"
/>
</svg>
</div>
</div>
</div>

<!-- FAQ Accordion -->
<div class="space-y-5">
<div
v-for="faq in filteredFaqs"
:key="faq.id"
class="relative border rounded-xl overflow-hidden shadow-lg backdrop-blur-sm transition-all duration-300 ease-in-out group"
:class="[
openAccordionIds.has(faq.id)
? 'bg-gray-750/70 border-blue-500/60 shadow-blue-500/20'
: 'bg-gray-800/60 border-gray-700 hover:border-blue-500/40 hover:bg-gray-750/70',
]"
>
<button
@click="toggleAccordion(faq.id)"
class="flex w-full items-center justify-between px-6 py-5 text-left text-lg font-medium text-gray-100 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2 focus-visible:ring-offset-gray-800 transition-colors duration-200"
:class="{ 'bg-gray-700/50': openAccordionIds.has(faq.id) }"
:aria-expanded="openAccordionIds.has(faq.id)"
:aria-controls="`faq-answer-${faq.id}`"
>
<span class="pr-4">{{ faq.question }}</span>
<!-- Plus/Minus Icon -->
<div
class="relative h-6 w-6 flex-shrink-0 text-blue-400 group-hover:text-blue-300"
>
<svg
xmlns="http://www.w3.org/2000/svg"
class="absolute h-6 w-6 transition-opacity duration-300 ease-in-out"
:class="
openAccordionIds.has(faq.id) ? 'opacity-0' : 'opacity-100'
"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
stroke-width="2"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M12 4v16m8-8H4"
/>
</svg>
<svg
xmlns="http://www.w3.org/2000/svg"
class="absolute h-6 w-6 transition-opacity duration-300 ease-in-out"
:class="
openAccordionIds.has(faq.id) ? 'opacity-100' : 'opacity-0'
"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
stroke-width="2"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M20 12H4"
/>
</svg>
</div>
</button>
<transition
enter-active-class="transition-[grid-template-rows,opacity] ease-in-out duration-300"
enter-from-class="grid-template-rows-[0fr] opacity-0"
enter-to-class="grid-template-rows-[1fr] opacity-100"
leave-active-class="transition-[grid-template-rows,opacity] ease-in-out duration-300"
leave-from-class="grid-template-rows-[1fr] opacity-100"
leave-to-class="grid-template-rows-[0fr] opacity-0"
>
<div
v-show="openAccordionIds.has(faq.id)"
:id="`faq-answer-${faq.id}`"
class="grid overflow-hidden"
role="region"
>
<div class="overflow-hidden">
<div
class="px-6 pt-4 pb-8 text-base text-gray-300 leading-relaxed"
>
<div
class="prose prose-invert max-w-none prose-p:text-gray-300 prose-a:text-blue-400 hover:prose-a:text-blue-300"
>
<p v-html="faq.answer"></p>
</div>
</div>
</div>
</div>
</transition>
</div>

<div
v-if="filteredFaqs.length === 0"
class="text-center text-gray-400 py-8"
>
{{ $t("faq.noResults") }}
</div>
</div>
</div>
</div>
</template>

<script setup lang="ts">
import { ref, computed } from "vue";
import { useI18n } from "vue-i18n";

const { t } = useI18n();
const searchTerm = ref("");
const openAccordionIds = ref<Set<string>>(new Set());

// Define FAQ item type with actual strings
interface FaqItem {
id: string;
question: string;
answer: string;
}

// Placeholder FAQ data with actual strings (replace with your real data)
const faqs = ref<FaqItem[]>([
{
id: "faq-1",
question: "如何购买 Hanye 产品?", // Example Question 1
answer:
"您可以通过我们的官方在线商店或授权的零售商处购买 Hanye 产品。我们建议您在官方渠道购买以确保正品和售后服务。", // Example Answer 1
},
{
id: "faq-2",
question: "产品保修期是多久?",
answer:
"不同产品的保修期可能不同,请参考具体产品的说明页面或联系我们的客服获取详细信息。通常固态硬盘提供3-5年保修,内存条提供终身保固。",
},
{
id: "faq-3",
question: "如何申请售后服务?",
answer:
"如果您需要售后服务,请准备好您的购买凭证,并通过我们的官方网站提交售后申请或直接联系客服中心。",
},
{
id: "faq-4",
question: "Hanye SSD 是否兼容我的电脑?",
answer:
"Hanye SSD 兼容大多数台式机和笔记本电脑。请确认您的设备支持相应的接口(如 SATA 或 NVMe)和规格。具体兼容性列表请参考产品页面。",
},
{
id: "faq-5",
question: "忘记密码怎么办?",
answer:
'如果是指 Hanye 相关的在线服务账户密码,请使用"忘记密码"功能进行重置。如果是加密 U 盘或 SSD 的密码,很抱歉,为了数据安全,我们无法提供密码破解服务。',
},
]);

// Filter FAQs based on actual string content
const filteredFaqs = computed(() => {
if (!searchTerm.value) {
return faqs.value;
}
const lowerSearchTerm = searchTerm.value.toLowerCase();
return faqs.value.filter(
(faq: FaqItem) =>
faq.question.toLowerCase().includes(lowerSearchTerm) ||
faq.answer.toLowerCase().includes(lowerSearchTerm)
);
});

// Toggle accordion item (modified for Set)
const toggleAccordion = (id: string) => {
if (openAccordionIds.value.has(id)) {
openAccordionIds.value.delete(id);
} else {
openAccordionIds.value.add(id);
}
};

// SEO (Still uses i18n)
useHead({
title: t("faq.meta.title"),
meta: [
{
name: "description",
content: t("faq.meta.description"),
},
],
});
</script>

+ 338
- 70
pages/index.vue 查看文件

@@ -53,16 +53,16 @@
</Swiper>
</section>

<!-- 分类产品展示 -->
<section class="max-w-full mb-12 md:mb-32">
<!-- 按用途产品展示 -->
<section class="max-w-full mb-12 md:mb-32 xl:px-2 lg:px-2 md:px-4 px-4">
<div class="max-w-screen-2xl mx-auto relative">
<div
class="justify-center text-cyan-400 text-base font-normal leading-tight mb-4 px-4 md:px-0"
class="justify-center text-cyan-400 text-base font-normal leading-tight mb-4"
>
{{ $t("products.usage") }}
</div>
<div
class="justify-center text-white font-normal mb-8 px-4 md:px-0 md:mb-16 text-xl sm:text-2xl md:text-4xl lg:text-6xl"
class="justify-center text-white font-normal mb-8 md:mb-16 text-xl sm:text-2xl md:text-4xl lg:text-6xl"
>
{{ $t("products.usage_title") }}
</div>
@@ -70,7 +70,7 @@
<div
class="max-w-screen-2xl mx-auto relative overflow-x-auto whitespace-nowrap scrollbar-hide scroll-smooth"
>
<div class="w-full mb-8 inline-flex items-center gap-4 px-4 md:px-0">
<div class="w-full mb-8 inline-flex items-center gap-4">
<div
class="cursor-pointer select-none px-7 py-3 rounded-full outline outline-1 outline-offset-[-1px] outline-cyan-400 inline-flex justify-center items-center gap-2.5"
>
@@ -116,26 +116,27 @@
</div>
</div>
</div>
<div
class="w-full relative pl-[calc((100vw-1536px)/2)] 2xl:pl-[calc((100vw-1536px)/2)] lg:pl-[0] md:pl-[0] sm:pl-[0] max-w-full"
>
<div class="max-w-screen-2xl mx-auto">
<div class="w-full">
<Swiper
:modules="[Navigation]"
:spaceBetween="30"
slidesPerView="auto"
:grid="{
fill: 'column',
rows: 1,
:slidesPerView="4"
:breakpoints="{
320: { slidesPerView: 2, spaceBetween: 10 },
640: { slidesPerView: 3, spaceBetween: 20 },
1024: { slidesPerView: 4, spaceBetween: 20 },
1280: { slidesPerView: 4, spaceBetween: 30 },
1536: { slidesPerView: 4, spaceBetween: 30 },
}"
:navigation="{
prevEl: '.swiper-button-prev-2',
nextEl: '.swiper-button-next-2',
}"
class="h-32 sm:h-32 md:h-64 lg:h-96 max-w-full"
class="max-w-full"
>
<SwiperSlide
class="!w-32 sm:!w-32 md:!w-64 lg:!w-96 !h-32 sm:!h-32 md:!h-64 lg:!h-96"
class="w-32 sm:w-32 md:w-64 lg:w-96 h-32 sm:h-32 md:h-64 lg:h-96"
>
<div
class="w-full h-full [background:#181818] rounded-2xl px-2 md:px-10 flex flex-col items-center justify-center"
@@ -146,19 +147,19 @@
class="w-full h-full object-contain"
/>
<div
class="text-center text-white text-base font-bold leading-tight mb-4 hidden md:block"
class="text-center text-white text-xs sm:text-sm md:text-base font-bold leading-tight mb-2 md:mb-4"
>
Hanye Q60-2TST3
</div>
<div
class="text-center justify-center text-white text-xs font-normal mb-8 leading-tight hidden md:block opacity-80"
class="text-center justify-center text-white text-[10px] sm:text-xs font-normal mb-4 md:mb-8 leading-tight opacity-80"
>
2TB SSD UP TO 550MB/s
</div>
</div>
</SwiperSlide>
<SwiperSlide
class="!w-32 sm:!w-32 md:!w-64 lg:!w-96 !h-32 sm:!h-32 md:!h-64 lg:!h-96"
class="w-32 sm:w-32 md:w-64 lg:w-96 h-32 sm:h-32 md:h-64 lg:h-96"
>
<div
class="w-full h-full [background:#181818] rounded-2xl px-2 md:px-10 flex flex-col items-center justify-center"
@@ -169,19 +170,19 @@
class="w-full h-full object-contain"
/>
<div
class="text-center text-white text-base font-bold leading-tight mb-4 hidden md:block"
class="text-center text-white text-xs sm:text-sm md:text-base font-bold leading-tight mb-2 md:mb-4"
>
Hanye Q60-2TST3
</div>
<div
class="text-center justify-center text-white text-xs font-normal mb-8 leading-tight hidden md:block opacity-80"
class="text-center justify-center text-white text-[10px] sm:text-xs font-normal mb-4 md:mb-8 leading-tight opacity-80"
>
2TB SSD UP TO 550MB/s
</div>
</div>
</SwiperSlide>
<SwiperSlide
class="!w-32 sm:!w-32 md:!w-64 lg:!w-96 !h-32 sm:!h-32 md:!h-64 lg:!h-96"
class="w-32 sm:w-32 md:w-64 lg:w-96 h-32 sm:h-32 md:h-64 lg:h-96"
>
<div
class="w-full h-full [background:#181818] rounded-2xl px-2 md:px-10 flex flex-col items-center justify-center"
@@ -192,19 +193,19 @@
class="w-full h-full object-contain"
/>
<div
class="text-center text-white text-base font-bold leading-tight mb-4 hidden md:block"
class="text-center text-white text-xs sm:text-sm md:text-base font-bold leading-tight mb-2 md:mb-4"
>
Hanye Q60-2TST3
</div>
<div
class="text-center justify-center text-white text-xs font-normal mb-8 leading-tight hidden md:block opacity-80"
class="text-center justify-center text-white text-[10px] sm:text-xs font-normal mb-4 md:mb-8 leading-tight opacity-80"
>
2TB SSD UP TO 550MB/s
</div>
</div>
</SwiperSlide>
<SwiperSlide
class="!w-32 sm:!w-32 md:!w-64 lg:!w-96 !h-32 sm:!h-32 md:!h-64 lg:!h-96"
class="w-32 sm:w-32 md:w-64 lg:w-96 h-32 sm:h-32 md:h-64 lg:h-96"
>
<div
class="w-full h-full [background:#181818] rounded-2xl px-2 md:px-10 flex flex-col items-center justify-center"
@@ -215,19 +216,19 @@
class="w-full h-full object-contain"
/>
<div
class="text-center text-white text-base font-bold leading-tight mb-4 hidden md:block"
class="text-center text-white text-xs sm:text-sm md:text-base font-bold leading-tight mb-2 md:mb-4"
>
Hanye Q60-2TST3
</div>
<div
class="text-center justify-center text-white text-xs font-normal mb-8 leading-tight hidden md:block opacity-80"
class="text-center justify-center text-white text-[10px] sm:text-xs font-normal mb-4 md:mb-8 leading-tight opacity-80"
>
2TB SSD UP TO 550MB/s
</div>
</div>
</SwiperSlide>
<SwiperSlide
class="!w-32 sm:!w-32 md:!w-64 lg:!w-96 !h-32 sm:!h-32 md:!h-64 lg:!h-96"
class="w-32 sm:w-32 md:w-64 lg:w-96 h-32 sm:h-32 md:h-64 lg:h-96"
>
<div
class="w-full h-full [background:#181818] rounded-2xl px-2 md:px-10 flex flex-col items-center justify-center"
@@ -238,12 +239,12 @@
class="w-full h-full object-contain"
/>
<div
class="text-center text-white text-base font-bold leading-tight mb-4 hidden md:block"
class="text-center text-white text-xs sm:text-sm md:text-base font-bold leading-tight mb-2 md:mb-4"
>
Hanye Q60-2TST3
</div>
<div
class="text-center justify-center text-white text-xs font-normal mb-8 leading-tight hidden md:block opacity-80"
class="text-center justify-center text-white text-[10px] sm:text-xs font-normal mb-4 md:mb-8 leading-tight opacity-80"
>
2TB SSD UP TO 550MB/s
</div>
@@ -254,20 +255,258 @@
</div>
</section>

<!-- 按用途产品展示 -->
<!-- 按分类产品展示 -->
<section class="max-w-full mb-12 md:mb-32 xl:px-2 lg:px-2 md:px-4 px-4">
<div class="max-w-screen-2xl mx-auto relative">
<div
class="justify-center text-cyan-400 text-base font-normal leading-tight mb-4"
>
Hanye独自に開発・製造、販売
</div>
<div
class="justify-center text-white font-normal mb-8 md:mb-16 text-xl sm:text-2xl md:text-4xl lg:text-6xl"
>
製品カテゴリー
</div>
</div>
<div class="max-w-screen-2xl mx-auto">
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
<nuxt-link
to="/products"
class="bg-zinc-950/10 backdrop-blur-[50px] border border-white/10 rounded-lg flex gap-8 p-4 sm:p-8 justify-between category-item"
>
<div class="col-span-1 flex flex-col gap-4">
<div class="flex flex-col gap-2 opacity-80">
<div
class="text-white text-sm md:text-base font-normal leading-tight flex gap-2 items-center"
>
<i class="icon-star text-sm"></i>
<span>PC高速化</span>
</div>
<div
class="text-white text-sm md:text-base font-normal leading-tight flex gap-2 items-center"
>
<i class="icon-star text-sm"></i>
<span>起動・読込 高速</span>
</div>
</div>
<div
class="p-2 sm:p-4 mt-auto bg-zinc-500/20 rounded-lg outline outline-1 outline-offset-[-1px] outline-white/10 backdrop-blur-xl inline-flex justify-center items-center gap-3 overflow-hidden"
>
<div
class="justify-start text-neutral-200 text-xs md:text-sm font-medium uppercase leading-relaxed"
>
2.5-inch SSD & M.2 SSD
</div>
</div>
</div>
<div class="w-32 h-32 md:w-44 md:h-44">
<img
:src="product"
alt="h1"
class="w-full h-full object-contain"
/>
</div>
</nuxt-link>
<nuxt-link
to="/products"
class="bg-zinc-950/10 backdrop-blur-[50px] border border-white/10 rounded-lg flex gap-8 p-4 sm:p-8 justify-between category-item"
>
<div class="col-span-1 flex flex-col gap-4">
<div class="flex flex-col gap-2 opacity-80">
<div
class="text-white text-sm md:text-base font-normal leading-tight flex gap-2 items-center"
>
<i class="icon-star text-sm"></i>
<span>PC高速化</span>
</div>
<div
class="text-white text-sm md:text-base font-normal leading-tight flex gap-2 items-center"
>
<i class="icon-star text-sm"></i>
<span>起動・読込 高速</span>
</div>
</div>
<div
class="p-2 sm:p-4 mt-auto bg-zinc-500/20 rounded-lg outline outline-1 outline-offset-[-1px] outline-white/10 backdrop-blur-xl inline-flex justify-center items-center gap-3 overflow-hidden"
>
<div
class="justify-start text-neutral-200 text-xs md:text-sm font-medium uppercase leading-relaxed"
>
2.5-inch SSD & M.2 SSD
</div>
</div>
</div>
<div class="w-32 h-32 md:w-44 md:h-44">
<img
:src="product"
alt="h1"
class="w-full h-full object-contain"
/>
</div>
</nuxt-link>
<nuxt-link
to="/products"
class="bg-zinc-950/10 backdrop-blur-[50px] border border-white/10 rounded-lg flex gap-8 p-4 sm:p-8 justify-between category-item"
>
<div class="col-span-1 flex flex-col gap-4">
<div class="flex flex-col gap-2 opacity-80">
<div
class="text-white text-sm md:text-base font-normal leading-tight flex gap-2 items-center"
>
<i class="icon-star text-sm"></i>
<span>PC高速化</span>
</div>
<div
class="text-white text-sm md:text-base font-normal leading-tight flex gap-2 items-center"
>
<i class="icon-star text-sm"></i>
<span>起動・読込 高速</span>
</div>
</div>
<div
class="p-2 sm:p-4 mt-auto bg-zinc-500/20 rounded-lg outline outline-1 outline-offset-[-1px] outline-white/10 backdrop-blur-xl inline-flex justify-center items-center gap-3 overflow-hidden"
>
<div
class="justify-start text-neutral-200 text-xs md:text-sm font-medium uppercase leading-relaxed"
>
2.5-inch SSD & M.2 SSD
</div>
</div>
</div>
<div class="w-32 h-32 md:w-44 md:h-44">
<img
:src="product"
alt="h1"
class="w-full h-full object-contain"
/>
</div>
</nuxt-link>
<nuxt-link
to="/products"
class="bg-zinc-950/10 backdrop-blur-[50px] border border-white/10 rounded-lg flex gap-8 p-4 sm:p-8 justify-between category-item"
>
<div class="col-span-1 flex flex-col gap-4">
<div class="flex flex-col gap-2 opacity-80">
<div
class="text-white text-sm md:text-base font-normal leading-tight flex gap-2 items-center"
>
<i class="icon-star text-sm"></i>
<span>PC高速化</span>
</div>
<div
class="text-white text-sm md:text-base font-normal leading-tight flex gap-2 items-center"
>
<i class="icon-star text-sm"></i>
<span>起動・読込 高速</span>
</div>
</div>
<div
class="p-2 sm:p-4 mt-auto bg-zinc-500/20 rounded-lg outline outline-1 outline-offset-[-1px] outline-white/10 backdrop-blur-xl inline-flex justify-center items-center gap-3 overflow-hidden"
>
<div
class="justify-start text-neutral-200 text-xs md:text-sm font-medium uppercase leading-relaxed"
>
2.5-inch SSD & M.2 SSD
</div>
</div>
</div>
<div class="w-32 h-32 md:w-44 md:h-44">
<img
:src="product"
alt="h1"
class="w-full h-full object-contain"
/>
</div>
</nuxt-link>
<nuxt-link
to="/products"
class="bg-zinc-950/10 backdrop-blur-[50px] border border-white/10 rounded-lg flex gap-8 p-4 sm:p-8 justify-between category-item"
>
<div class="col-span-1 flex flex-col gap-4">
<div class="flex flex-col gap-2 opacity-80">
<div
class="text-white text-sm md:text-base font-normal leading-tight flex gap-2 items-center"
>
<i class="icon-star text-sm"></i>
<span>PC高速化</span>
</div>
<div
class="text-white text-sm md:text-base font-normal leading-tight flex gap-2 items-center"
>
<i class="icon-star text-sm"></i>
<span>起動・読込 高速</span>
</div>
</div>
<div
class="p-2 sm:p-4 mt-auto bg-zinc-500/20 rounded-lg outline outline-1 outline-offset-[-1px] outline-white/10 backdrop-blur-xl inline-flex justify-center items-center gap-3 overflow-hidden"
>
<div
class="justify-start text-neutral-200 text-xs md:text-sm font-medium uppercase leading-relaxed"
>
2.5-inch SSD & M.2 SSD
</div>
</div>
</div>
<div class="w-32 h-32 md:w-44 md:h-44">
<img
:src="product"
alt="h1"
class="w-full h-full object-contain"
/>
</div>
</nuxt-link>
<nuxt-link
to="/products"
class="bg-zinc-950/10 backdrop-blur-[50px] border border-white/10 rounded-lg flex gap-8 p-4 sm:p-8 justify-between category-item"
>
<div class="col-span-1 flex flex-col gap-4">
<div class="flex flex-col gap-2 opacity-80">
<div
class="text-white text-sm md:text-base font-normal leading-tight flex gap-2 items-center"
>
<i class="icon-star text-sm"></i>
<span>PC高速化</span>
</div>
<div
class="text-white text-sm md:text-base font-normal leading-tight flex gap-2 items-center"
>
<i class="icon-star text-sm"></i>
<span>起動・読込 高速</span>
</div>
</div>
<div
class="p-2 sm:p-4 mt-auto bg-zinc-500/20 rounded-lg outline outline-1 outline-offset-[-1px] outline-white/10 backdrop-blur-xl inline-flex justify-center items-center gap-3 overflow-hidden"
>
<div
class="justify-start text-neutral-200 text-xs md:text-sm font-medium uppercase leading-relaxed"
>
2.5-inch SSD & M.2 SSD
</div>
</div>
</div>
<div class="w-32 h-32 md:w-44 md:h-44">
<img
:src="product"
alt="h1"
class="w-full h-full object-contain"
/>
</div>
</nuxt-link>
</div>
</div>
</section>

<!-- Tag description -->
<section class="max-w-full mb-12 md:mb-32">
<!-- 产品核心展示 -->
<section class="max-w-full mb-12 md:mb-32 xl:px-2 lg:px-2 md:px-4 px-4">
<div
class="max-w-screen-2xl mx-auto grid grid-cols-1 justify-items-center gap-4 sm:grid-cols-2 md:grid-cols-3"
class="max-w-screen-2xl mx-auto grid grid-cols-1 lg:grid-cols-3 md:grid-cols-2 sm:grid-cols-1 gap-4"
>
<div class="inline-flex justify-start items-center gap-5 px-4">
<div class="inline-flex justify-start items-center gap-5">
<div
class="w-16 h-16 relative bg-gradient-to-b from-neutral-600 to-slate-400 rounded-xl border-b-[1.50px] border-neutral-700 overflow-hidden"
class="w-12 h-12 md:w-16 md:h-16 relative bg-gradient-to-b from-neutral-600 to-slate-400 rounded-xl border-b-[1.50px] border-neutral-700 overflow-hidden"
>
<div
class="w-12 h-12 left-[9.50px] top-[9.50px] absolute overflow-hidden flex items-center justify-center"
>
<div class="w-full h-full flex items-center justify-center">
<i class="icon-h1 text-white text-5xl"></i>
</div>
</div>
@@ -284,13 +523,11 @@
</div>
</div>
</div>
<div class="inline-flex justify-start items-center gap-5 px-4">
<div class="inline-flex justify-start items-center gap-5">
<div
class="w-16 h-16 relative bg-gradient-to-b from-neutral-600 to-slate-400 rounded-xl border-b-[1.50px] border-neutral-700 overflow-hidden"
class="w-12 h-12 md:w-16 md:h-16 relative bg-gradient-to-b from-neutral-600 to-slate-400 rounded-xl border-b-[1.50px] border-neutral-700 overflow-hidden"
>
<div
class="w-12 h-12 left-[9.50px] top-[9.50px] absolute overflow-hidden flex items-center justify-center"
>
<div class="w-full h-full flex items-center justify-center">
<i class="icon-h2 text-white text-5xl"></i>
</div>
</div>
@@ -307,13 +544,11 @@
</div>
</div>
</div>
<div class="inline-flex justify-start items-center gap-5 px-4">
<div class="inline-flex justify-start items-center gap-5">
<div
class="w-16 h-16 relative bg-gradient-to-b from-neutral-600 to-slate-400 rounded-xl border-b-[1.50px] border-neutral-700 overflow-hidden"
class="w-12 h-12 md:w-16 md:h-16 relative bg-gradient-to-b from-neutral-600 to-slate-400 rounded-xl border-b-[1.50px] border-neutral-700 overflow-hidden"
>
<div
class="w-12 h-12 left-[9.50px] top-[9.50px] absolute overflow-hidden flex items-center justify-center"
>
<div class="w-full h-full flex items-center justify-center">
<i class="icon-h3 text-white text-5xl"></i>
</div>
</div>
@@ -334,15 +569,15 @@
</section>

<!-- 当社の強み -->
<section class="max-w-full mb-0 md:mb-28">
<section class="max-w-full mb-12 md:mb-32 xl:px-2 lg:px-2 md:px-4 px-4">
<div class="max-w-screen-2xl mx-auto relative">
<div
class="justify-center text-cyan-400 text-base font-normal leading-tight mb-4 px-4 md:px-0"
class="justify-center text-cyan-400 text-base font-normal leading-tight mb-4"
>
{{ $t("products.strong_point") }}
</div>
<div
class="justify-center text-white font-normal mb-8 px-4 md:px-0 md:mb-16 text-xl sm:text-2xl md:text-4xl lg:text-6xl"
class="justify-center text-white font-normal mb-8 md:mb-16 text-xl sm:text-2xl md:text-4xl lg:text-6xl"
>
{{ $t("products.strong_point_title") }}
</div>
@@ -363,10 +598,8 @@
</div>
</div>
</div>
<div class="overflow-hidden w-full">
<div
class="w-full relative pl-[calc((100vw-1536px)/2)] 2xl:pl-[calc((100vw-1536px)/2)] lg:pl-[0] md:pl-[0] sm:pl-[0] max-w-full"
>
<div class="max-w-screen-2xl mx-auto">
<div class="w-full relative max-w-full">
<Swiper
:modules="[Navigation, Pagination]"
slides-per-view="auto"
@@ -380,7 +613,7 @@
>
<SwiperSlide class="!max-w-screen-2xl !w-full">
<div
class="w-full h-full flex items-center px-4 md:px-20"
class="w-full h-full flex items-center px-0 md:px-20"
:style="{
backgroundImage: `url(${homeC1Webp})`,
backgroundSize: 'cover',
@@ -389,7 +622,7 @@
}"
>
<div
class="w-[100%] md:w-[50%] bg-white/5 rounded-2xl backdrop-blur-[50px] px-8 py-12 flex flex-col gap-8 border border-white/10"
class="w-full lg:w-[50%] h-full md:h-auto bg-white/5 rounded-0 md:rounded-2xl backdrop-blur-[50px] px-4 py-8 md:py-12 md:px-8 flex flex-col gap-8 border border-white/10"
>
<div
class="opacity-90 justify-start text-white text-2xl font-normal md:text-4xl"
@@ -397,7 +630,7 @@
一貫体制による高品質と安定供給
</div>
<div
class="opacity-70 justify-start text-white text-lg font-normal leading-relaxed hidden sm:block"
class="opacity-70 justify-start text-white text-base md:text-lg font-normal leading-relaxed"
>
「企画・開発から製造、品質管理、販売、オンラインショップ運営まで自社で完結。ISO認証取得の工場で生産された信頼性の高い製品を、安定してお届けします。」
</div>
@@ -413,28 +646,39 @@
</section>

<!-- 产品咨询 -->
<section
class="max-w-full h-[240px] md:h-[480px] bg-black/80 hidden md:block"
>
<section class="max-w-full h-[240px] md:h-[480px] bg-black/80 md:block">
<div class="h-full relative">
<h1
class="text-center justify-start text-white font-normal absolute top-1/3 left-4 right-4 -translate-x-0 md:left-1/2 md:-translate-x-1/2 md:right-auto text-xl sm:text-2xl md:text-3xl"
>
{{ $t("products.consultation") }}
</h1>
<div
class="w-40 h-11 bg-zinc-300/10 rounded-lg outline outline-1 flex items-center justify-center gap-2 outline-white/20 backdrop-blur-[10px] absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/5"
class="absolute top-0 left-0 w-full h-full flex flex-col gap-6 items-center justify-center z-10"
>
<span class="text-white text-sm font-normal">{{
$t("products.consultation_button")
}}</span>
<i class="icon-arrow-right text-white text-sm font-normal"></i>
<h1
class="text-center justify-start text-white font-normal text-xl sm:text-2xl md:text-3xl px-2"
>
{{ $t("products.consultation") }}
</h1>
<nuxt-link
:to="locale === defaultLocale ? '/contact' : `/${locale}/contact`"
class="w-32 h-10 md:w-40 md:h-11 bg-zinc-300/10 rounded-lg outline outline-1 flex items-center justify-center gap-2 outline-white/20 backdrop-blur-[10px] cursor-pointer hover:bg-zinc-300/20 transition-colors duration-200"
>
<span class="text-xs md:text-sm font-normal">{{
$t("products.consultation_button")
}}</span>
<i class="icon-arrow-right text-sm font-normal"></i>
</nuxt-link>
</div>
<img
v-if="isMobile"
:src="videoWebp"
alt="video"
class="w-full h-full object-cover opacity-20"
/>
<video
v-else
:src="videoSrc"
autoplay
muted
loop
:poster="videoWebp"
class="w-full h-full object-cover opacity-20"
></video>
</div>
@@ -454,13 +698,26 @@ import "swiper/css";
import "swiper/css/navigation";
import "swiper/css/pagination";

import { useBreakpoints, breakpointsTailwind } from "@vueuse/core";
import { useI18n } from "vue-i18n";
import video from "@/assets/videos/video.mp4";
import videoWebp from "@/assets/videos/video.webp";
import homeA1Webp from "@/assets/images/home-a-1.webp";
import homeC1Webp from "@/assets/images/home-c-1.webp";
import product from "@/assets/images/product.png";

const { t, locale } = useI18n();
const config = useRuntimeConfig();
// 从运行时配置获取默认语言,如果未配置则默认为 'en'
const defaultLocale = config.public.i18n?.defaultLocale || "en";

const videoSrc = ref(video);

// Define breakpoints
const breakpoints = useBreakpoints(breakpointsTailwind);
// Check if the device is mobile (smaller than md)
const isMobile = breakpoints.smaller("md");

/**
* 网站首页
* 展示网站主要内容和精选产品
@@ -509,4 +766,15 @@ useHead({
:deep(.swiper-pagination-bullet-active) {
background-color: var(--color-text); /* Example color */
}
.category-item {
background-image: url("@/assets/images/home-b-1.webp");
background-size: cover;
background-position: center;
background-repeat: no-repeat;
opacity: 0.8;
transition: opacity 0.3s ease;
&:hover {
opacity: 1;
}
}
</style>

+ 238
- 45
pages/products/index.vue 查看文件

@@ -1,31 +1,221 @@
<template>
<div class="py-8">
<div class="container-custom">
<h1 class="text-3xl font-bold mb-8">{{ $t('products.title') }}</h1>
<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"></div>
</div>
<div v-else class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
<div v-for="product in products" :key="product.id" class="bg-white border border-gray-200 rounded-lg overflow-hidden shadow-sm hover:shadow-md transition-shadow">
<div class="h-48 bg-gray-100 flex items-center justify-center">
<!-- 产品图片占位符 -->
<span class="text-gray-400">{{ product.title }}</span>
<div>
<div class="w-full h-[55px] sm:h-[72px]"></div>
<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"
></div>
</div>

<div v-else>
<div class="w-full mb-12 relative">
<div class="absolute top-0 left-0 w-full h-full z-10">
<div
class="max-w-screen-2xl mx-auto h-full flex flex-col justify-center gap-4 p-4"
>
<div
class="justify-start text-white text-2xl font-normal md:text-4xl lg:text-6xl"
>
製品一覧
</div>
<div
class="text-white text-sm lg:text-lg font-normal leading-loose"
>
卓越した製品は、実績に裏打ちされた優れた技術と、<br />継続的な革新デザインとの融合により、生み出されます。
</div>
</div>
<div class="p-6">
<h2 class="text-xl font-semibold mb-2">{{ product.title }}</h2>
<p class="text-gray-600 mb-4">{{ product.description }}</p>
<NuxtLink :to="`/products/${product.id}`" class="text-blue-600 hover:text-blue-800 font-medium">
{{ $t('products.viewDetails') }}
</NuxtLink>
</div>
<img
:src="banner"
alt="products-banner"
class="w-full object-cover h-60 lg:h-full"
/>
</div>
<div class="max-w-full mb-6 xl:px-2 lg:px-2 md:px-4 px-4">
<div class="max-w-screen-2xl mx-auto">
<nuxt-link
to="/"
class="justify-start text-white/60 text-base font-normal"
>ホーム</nuxt-link
>
<span class="text-white/60 text-base font-normal px-2"> / </span>
<nuxt-link to="/products" class="text-white text-base font-normal"
>製品一覧</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"
>
<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="col-span-1 md:col-span-2 flex flex-col gap-16 mb-8 md:mb-0"
>
<div class="flex flex-col gap-4">
<div class="text-white text-3xl font-medium">
製品カテゴリー
</div>
<div class="flex flex-col gap-4">
<div
class="opacity-80 justify-start text-white text-base font-normal"
>
PCメモリ
</div>
<div
class="opacity-80 justify-start text-white text-base font-normal"
>
PCメモリ
</div>
</div>
</div>

<div class="flex flex-col gap-4">
<div class="text-white text-3xl font-medium">
製品カテゴリー
</div>
<div class="flex flex-col gap-4">
<div
class="opacity-80 justify-start text-white text-base font-normal"
>
PCメモリ
</div>
<div
class="opacity-80 justify-start text-white text-base font-normal"
>
PCメモリ
</div>
</div>
</div>
</div>

<div class="col-span-1 md:col-span-8">
<div class="flex flex-col gap-16">
<div class="flex flex-col gap-4">
<div class="w-full text-white text-4xl font-normal mb-4">
2.5-inch SSD
</div>
<div
class="w-full grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4"
>
<div class="bg-zinc-900 rounded-lg">
<div class="w-full p-8">
<img
src="https://placehold.co/400x400"
alt=""
class="w-full h-full object-cover rounded-lg mb-4"
/>
<div
class="text-center justify-start text-white text-xl font-normal"
>
Hanye Q60-256GST3
</div>
<div
class="text-center justify-start text-stone-400 text-base font-normal leading-normal"
>
256GB / 512GB / 1TB / 2TB
</div>
</div>
</div>
<div class="bg-zinc-900 rounded-lg"></div>
<div class="bg-zinc-900 rounded-lg"></div>
<div class="bg-zinc-900 rounded-lg"></div>
</div>
</div>
<div class="flex flex-col gap-4">
<div class="w-full text-white text-4xl font-normal mb-4">
2.5-inch SSD
</div>
<div
class="w-full grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4"
>
<div class="bg-zinc-900 rounded-lg">
<div class="w-full p-8">
<img
src="https://placehold.co/400x400"
alt=""
class="w-full h-full object-cover rounded-lg mb-4"
/>
<div
class="text-center justify-start text-white text-xl font-normal"
>
Hanye Q60-256GST3
</div>
<div
class="text-center justify-start text-stone-400 text-base font-normal leading-normal"
>
256GB / 512GB / 1TB / 2TB
</div>
</div>
</div>
<div class="bg-zinc-900 rounded-lg">
<div class="w-full p-8">
<img
src="https://placehold.co/400x400"
alt=""
class="w-full h-full object-cover rounded-lg mb-4"
/>
<div
class="text-center justify-start text-white text-xl font-normal"
>
Hanye Q60-256GST3
</div>
<div
class="text-center justify-start text-stone-400 text-base font-normal leading-normal"
>
256GB / 512GB / 1TB / 2TB
</div>
</div>
</div>
<div class="bg-zinc-900 rounded-lg">
<div class="w-full p-8">
<img
src="https://placehold.co/400x400"
alt=""
class="w-full h-full object-cover rounded-lg mb-4"
/>
<div
class="text-center justify-start text-white text-xl font-normal"
>
Hanye Q60-256GST3
</div>
<div
class="text-center justify-start text-stone-400 text-base font-normal leading-normal"
>
256GB / 512GB / 1TB / 2TB
</div>
</div>
</div>
<div class="bg-zinc-900 rounded-lg">
<div class="w-full p-8">
<img
src="https://placehold.co/400x400"
alt=""
class="w-full h-full object-cover rounded-lg mb-4"
/>
<div
class="text-center justify-start text-white text-xl font-normal"
>
Hanye Q60-256GST3
</div>
<div
class="text-center justify-start text-stone-400 text-base font-normal leading-normal"
>
256GB / 512GB / 1TB / 2TB
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</ErrorBoundary>
</div>
</div>
</ErrorBoundary>
</div>
</template>

@@ -34,8 +224,8 @@
* 产品列表页面
* 展示所有产品
*/
import { ref, onMounted } from 'vue';
import { useErrorHandler } from '~/composables/useErrorHandler';
import { useErrorHandler } from "~/composables/useErrorHandler";
import banner from "@/assets/images/product-banner.webp";

// 产品接口定义
interface Product {
@@ -53,42 +243,42 @@ const products = ref<Product[]>([]);
async function loadProducts() {
await wrapAsync(async () => {
// 模拟API请求延迟
await new Promise(resolve => setTimeout(resolve, 500));
await new Promise((resolve) => setTimeout(resolve, 500));
// 模拟数据,实际项目中应从API获取
products.value = [
{
id: 1,
title: '产品一',
description: '这是产品一的详细描述,介绍产品特点和用途。'
title: "产品一",
description: "这是产品一的详细描述,介绍产品特点和用途。",
},
{
id: 2,
title: '产品二',
description: '这是产品二的详细描述,介绍产品特点和用途。'
title: "产品二",
description: "这是产品二的详细描述,介绍产品特点和用途。",
},
{
id: 3,
title: '产品三',
description: '这是产品三的详细描述,介绍产品特点和用途。'
title: "产品三",
description: "这是产品三的详细描述,介绍产品特点和用途。",
},
{
id: 4,
title: '产品四',
description: '这是产品四的详细描述,介绍产品特点和用途。'
title: "产品四",
description: "这是产品四的详细描述,介绍产品特点和用途。",
},
{
id: 5,
title: '产品五',
description: '这是产品五的详细描述,介绍产品特点和用途。'
title: "产品五",
description: "这是产品五的详细描述,介绍产品特点和用途。",
},
{
id: 6,
title: '产品六',
description: '这是产品六的详细描述,介绍产品特点和用途。'
}
title: "产品六",
description: "这是产品六的详细描述,介绍产品特点和用途。",
},
];
return products.value;
});
}
@@ -100,9 +290,12 @@ onMounted(() => {

// SEO优化
useHead({
title: '产品列表 - Hanye',
title: "产品列表 - Hanye",
meta: [
{ name: 'description', content: '浏览我们的产品列表,找到适合您的解决方案。' }
]
{
name: "description",
content: "浏览我们的产品列表,找到适合您的解决方案。",
},
],
});
</script>
</script>

Loading…
取消
儲存