- 在nuxt.config.ts中新增图片本地化配置,支持将API返回的远程图片下载到本地。 - 更新package.json,新增生成静态站点并本地化图片的命令。 - 新增README-image-localization.md文档,详细说明图片本地化功能的使用方法和注意事项。 - 在TheHeader和TheFooter组件中优化产品分类和网站链接的展示逻辑,提升用户体验。 - 更新多个页面的内容和样式,确保多语言支持的一致性。 - 新增图片下载和本地化的脚本,确保静态站点完全脱离原始服务器运行。master
@@ -0,0 +1,86 @@ | |||
# API图片本地化功能 | |||
此功能用于将API返回的远程图片链接下载到本地并替换成本地路径,方便生成的静态站点完全脱离原始服务器。 | |||
## 功能概述 | |||
1. 在Nuxt静态站点生成完成后,处理所有API响应JSON文件 | |||
2. 自动识别数据中的图片URL字段并下载图片到本地 | |||
3. 替换JSON数据中的远程URL为本地路径 | |||
4. 确保静态生成的网站能完全离线运行,所有图片资源都在本地 | |||
## 使用方法 | |||
### 基本使用 | |||
直接使用以下命令生成静态站点并本地化图片: | |||
```bash | |||
npm run generate:localize | |||
``` | |||
这个命令会: | |||
1. 运行标准的`nuxt generate`生成静态站点 | |||
2. 运行图片本地化脚本处理API数据中的图片URL | |||
### 只处理图片 | |||
如果你已经生成了静态站点,只需要处理图片: | |||
```bash | |||
npm run localize-images | |||
``` | |||
## 自定义配置 | |||
### 支持的图片字段 | |||
默认情况下,系统会查找以下字段名称中的图片URL: | |||
- image | |||
- imageUrl | |||
- thumbnail | |||
- cover | |||
- avatar | |||
- photo | |||
- src | |||
如需自定义,可以在`scripts/localize-images.mjs`中修改`localizeImages`函数的默认参数。 | |||
### 图片保存位置 | |||
默认情况下,下载的图片会保存到`public/images/remote`目录,网站访问路径为`/images/remote/[图片文件名]`。 | |||
## 工作原理 | |||
1. Nuxt先正常执行静态生成过程,将API数据缓存到`.output/server/api`目录下 | |||
2. 脚本`scripts/localize-images.mjs`在生成完成后执行,扫描并处理缓存的JSON文件 | |||
3. 识别并下载所有图片到`public/images/remote`目录 | |||
4. 更新JSON响应文件,将远程图片URL替换为本地路径 | |||
## 注意事项 | |||
1. 此方法不需要修改源代码,完全在构建后处理 | |||
2. 下载的图片使用URL的MD5哈希作为文件名,确保相同URL的图片只下载一次 | |||
3. 如果图片下载失败,会保留原始URL,不会破坏网站功能 | |||
4. 图片本地化过程会在控制台输出详细日志 | |||
## 常见问题 | |||
### 生成后图片仍然是远程链接 | |||
检查以下几点: | |||
1. 确认使用了`npm run generate:localize`而不是`npm run generate` | |||
2. 检查控制台输出中是否有错误信息 | |||
3. 检查`public/images/remote`目录是否已创建并包含图片 | |||
### 图片下载失败 | |||
可能的原因: | |||
1. 网络问题,无法访问原始图片URL | |||
2. 原始URL返回错误,如404或403 | |||
3. 目标目录没有写入权限 | |||
解决方法: | |||
1. 检查网络连接和图片URL是否可访问 | |||
2. 确保目标目录有写入权限 | |||
3. 手动修复失败的图片URL |
@@ -35,14 +35,16 @@ body { | |||
text-rendering: optimizeLegibility; | |||
font-feature-settings: "palt"; | |||
letter-spacing: 0.02em; | |||
font-weight: 400; | |||
font-style: normal; | |||
} | |||
/* 仅在日语环境下应用日文字体 */ | |||
html[lang="ja"] body { | |||
font-family: 'M PLUS 1p', sans-serif !important; | |||
font-family: 'Noto Sans JP', 'M PLUS 1p', sans-serif !important; | |||
} | |||
html[lang="en"] body { | |||
font-family: 'Montserrat', sans-serif !important; | |||
font-family: 'Montserrat', 'Noto Sans JP', sans-serif !important; | |||
} |
@@ -19,15 +19,15 @@ | |||
</p> | |||
</div> | |||
<!-- 快捷链接 --> | |||
<!-- 产品分类链接 --> | |||
<div class="hidden lg:block"> | |||
<h3 | |||
class="w-36 justify-start text-zinc-300 text-xl font-normal leading-snug mb-4" | |||
> | |||
{{ $t("common.home") }} | |||
{{ $t("common.footer.productsLinks.title") }} | |||
</h3> | |||
<ul class="space-y-4"> | |||
<li v-for="item in menuItems" :key="item.path"> | |||
<li v-for="item in menuProductsItems" :key="item.path"> | |||
<NuxtLink | |||
:to="item.path" | |||
class="text-zinc-500 text-sm font-normal hover:text-white transition" | |||
@@ -37,15 +37,15 @@ | |||
</li> | |||
</ul> | |||
</div> | |||
<!-- 快捷链接 --> | |||
<!-- 网站快捷链接 --> | |||
<div class="hidden lg:block"> | |||
<h3 | |||
class="w-36 justify-start text-zinc-300 text-xl font-normal leading-snug mb-4" | |||
> | |||
{{ $t("common.home") }} | |||
{{ $t("common.footer.websiteLinks.title") }} | |||
</h3> | |||
<ul class="space-y-4"> | |||
<li v-for="item in menuItems" :key="item.path"> | |||
<li v-for="item in menuWebsiteItems" :key="item.path"> | |||
<NuxtLink | |||
:to="item.path" | |||
class="text-zinc-500 text-sm font-normal hover:text-white transition" | |||
@@ -55,15 +55,15 @@ | |||
</li> | |||
</ul> | |||
</div> | |||
<!-- 快捷链接 --> | |||
<!-- 网站快捷链接 --> | |||
<div class="hidden lg:block"> | |||
<h3 | |||
class="w-36 justify-start text-zinc-300 text-xl font-normal leading-snug mb-4" | |||
> | |||
{{ $t("common.home") }} | |||
{{ $t("common.footer.quickLinks.title") }} | |||
</h3> | |||
<ul class="space-y-4"> | |||
<li v-for="item in menuItems" :key="item.path"> | |||
<li v-for="item in menuHomeItems" :key="item.path"> | |||
<NuxtLink | |||
:to="item.path" | |||
class="text-zinc-500 text-sm font-normal hover:text-white transition" | |||
@@ -83,12 +83,72 @@ | |||
* 页脚组件 | |||
* 包含网站导航、联系信息和版权信息 | |||
*/ | |||
import { useI18n } from "vue-i18n"; | |||
const { locale } = useI18n(); | |||
const config = useRuntimeConfig(); | |||
const defaultLocale = config.public.i18n?.defaultLocale || "en"; | |||
// 获取产品分类数据 | |||
const { data: categoryResponse } = useFetch(`/api/products/category?lang=${locale.value}`, { | |||
key: `category-${locale.value}` | |||
}); | |||
const productCategories = computed(() => { | |||
if (!categoryResponse.value?.data) return []; | |||
return categoryResponse.value.data; | |||
}); | |||
// 导航菜单项 | |||
const menuItems = [ | |||
{ label: "common.home", path: "/" }, | |||
{ label: "common.products", path: "/products" }, | |||
{ label: "common.faq", path: "/faq" }, | |||
{ label: "common.about", path: "/about" }, | |||
{ label: "common.contact", path: "/contact" }, | |||
]; | |||
const menuProductsItems = computed(() => { | |||
// 构建路径前缀 | |||
const prefix = locale.value === defaultLocale ? "" : `/${locale.value}`; | |||
// 使用API获取的产品分类数据 | |||
return productCategories.value.map((category: any) => ({ | |||
label: category.title, | |||
path: `${prefix}/products?category=${category.id}` | |||
})); | |||
}); | |||
const menuWebsiteItems = computed(() => [ | |||
{ | |||
label: "common.footer.websiteLinks.home", | |||
path: locale.value === defaultLocale ? "/" : `/${locale.value}`, | |||
}, | |||
{ | |||
label: "common.footer.websiteLinks.products", | |||
path: | |||
locale.value === defaultLocale | |||
? "/products" | |||
: `/${locale.value}/products`, | |||
}, | |||
{ | |||
label: "common.footer.websiteLinks.faq", | |||
path: locale.value === defaultLocale ? "/faq" : `/${locale.value}/faq`, | |||
}, | |||
{ | |||
label: "common.footer.websiteLinks.about", | |||
path: locale.value === defaultLocale ? "/about" : `/${locale.value}/about`, | |||
}, | |||
{ | |||
label: "common.footer.websiteLinks.contact", | |||
path: | |||
locale.value === defaultLocale ? "/contact" : `/${locale.value}/contact`, | |||
}, | |||
]); | |||
const menuHomeItems = computed(() => [ | |||
// 这里可以根据需要添加首页快捷链接 | |||
{ | |||
label: "common.footer.quickLinks.support", | |||
path: locale.value === defaultLocale ? "/support" : `/${locale.value}/support`, | |||
}, | |||
{ | |||
label: "common.footer.quickLinks.privacy", | |||
path: locale.value === defaultLocale ? "/privacy" : `/${locale.value}/privacy`, | |||
}, | |||
{ | |||
label: "common.footer.quickLinks.terms", | |||
path: locale.value === defaultLocale ? "/terms" : `/${locale.value}/terms`, | |||
} | |||
]); | |||
</script> |
@@ -307,6 +307,30 @@ let leaveTimeout: ReturnType<typeof setTimeout> | null = null; // Timeout for mo | |||
// 添加热门关键字 | |||
const hotKeywords = ref(["SSD", "SD", "DDR4"]); | |||
// 获取产品分类数据 | |||
const { data: categoryResponse } = useFetch(`/api/products/category?lang=${locale.value}`, { | |||
key: `category-${locale.value}` | |||
}); | |||
const productCategories = computed(() => { | |||
if (!categoryResponse.value?.data) return []; | |||
return categoryResponse.value.data.map((category: any) => ({ | |||
label: category.title, | |||
path: `/products?category=${category.id}` | |||
})); | |||
}); | |||
// 获取产品用途数据 | |||
const { data: usageResponse } = useFetch(`/api/products/usage?lang=${locale.value}`, { | |||
key: `usage-${locale.value}` | |||
}); | |||
const productUsages = computed(() => { | |||
if (!usageResponse.value?.data) return []; | |||
return usageResponse.value.data.map((usage: any) => ({ | |||
label: usage.name, | |||
path: `/products?usage=${usage.id}` | |||
})); | |||
}); | |||
// 使用 computed 来定义 homePath,根据是否为默认语言调整路径 | |||
const homePath = computed(() => { | |||
// 如果是默认语言,路径为根路径 '/' | |||
@@ -330,25 +354,17 @@ const menuItems = computed(() => { | |||
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` }, | |||
], | |||
items: productCategories.value.map((category: any) => ({ | |||
...category, | |||
path: `${prefix}${category.path}` | |||
})) | |||
}, | |||
{ | |||
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`, | |||
}, | |||
], | |||
items: productUsages.value.map((usage: any) => ({ | |||
...usage, | |||
path: `${prefix}${usage.path}` | |||
})) | |||
}, | |||
], | |||
}, |
@@ -11,14 +11,52 @@ export default { | |||
hotKeywords: "Hot Keywords", | |||
productCategories: "Product Categories", | |||
byUsage: "By Usage", | |||
footer: { | |||
productsLinks: { | |||
title: "Products", | |||
}, | |||
websiteLinks: { | |||
title: "Website", | |||
home: "Home", | |||
products: "Products", | |||
faq: "FAQ", | |||
about: "About Us", | |||
contact: "Contact", | |||
}, | |||
quickLinks: { | |||
title: "Quick Links", | |||
}, | |||
}, | |||
}, | |||
home: { | |||
title: "Welcome to Hanye Website", | |||
description: "Providing high-quality products and services.", | |||
title: "Hanye Website", | |||
description: | |||
"Providing high-quality products and services, including memory and SD, SSD, microSD related products, and professional technical support.", | |||
keywords: | |||
"Hanye, memory, storage, products, services, SD, SSD, microSD, technical support", | |||
learnMore: "Learn More", | |||
carousel: { | |||
one: { | |||
title: "New Technology", | |||
description: "3D NAND TLC", | |||
description2: "Flash-based,", | |||
description3: "High reliability and durability", | |||
description4: "Enjoy stress-free gaming experience", | |||
description5: "The computer startup time is dramatically faster!", | |||
}, | |||
}, | |||
}, | |||
products: { | |||
title: "Our Products", | |||
title: "Products", | |||
description: "Our products, product usage, and purchase help.", | |||
keywords: "Hanye, products, product information, technical support", | |||
product_list: "Product List", | |||
product_list_description: | |||
"Excellent products are the result of 10 years of experience and continuous innovation design.", | |||
product_categories_title: "Categories", | |||
product_categories_description: "Select products by type.", | |||
product_categories_usage: "By Usage", | |||
product_categories_usage_description: "Select products by usage.", | |||
viewDetails: "View Details", | |||
consultation: | |||
"Feel free to inquire about product consultations and quotes.", | |||
@@ -36,98 +74,70 @@ export default { | |||
"Diversifying products and relentlessly pursuing new creativity.", | |||
strong_point: "Our Strengths", | |||
strong_point_title: "Our Strengths / Why Choose Us", | |||
view_details: "View Details", | |||
}, | |||
faq: { | |||
title: "Frequently Asked Questions", | |||
description: | |||
"Frequently asked questions, product usage, and purchase help.", | |||
keywords: | |||
"Hanye, frequently asked questions, product information, technical support", | |||
searchPlaceholder: "Search questions", | |||
category: "Category", | |||
noResults: "No results found", | |||
clearSearch: "Clear search", | |||
}, | |||
about: { | |||
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.", | |||
}, | |||
description: | |||
"Introduction to Hanye's company overview, business philosophy, and corporate information.", | |||
keywords: | |||
"Hanye, company overview, business philosophy, corporate information", | |||
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", | |||
title: "Overview", | |||
companyInfo: "Company Info", | |||
established: "Established", | |||
ceo: "CEO", | |||
employees: "Employees", | |||
location: "Location", | |||
philosophy: "Philosophy", | |||
contact: "Contact", | |||
email: "Email", | |||
tel: "Tel", | |||
fax: "Fax", | |||
businessHours: "Business Hours", | |||
businessActivities: "Business Activities", | |||
companyName: "Company Name", | |||
englishName: "English Name", | |||
}, | |||
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.", | |||
companyInfo: { | |||
name: "Hanye", | |||
companyName: "Hanye", | |||
englishName: "Hanye Technology Co., Ltd.", | |||
description: | |||
"Hanye was founded in 2003 and is headquartered in Shenyang, China. Since its inception, we have grown into a comprehensive enterprise that develops, manufactures, and sells memory and related products.", | |||
established: "2003", | |||
ceo: "ZHENG XIAO DONG", | |||
employees: "30", | |||
location: | |||
"803, NO.6, AiTe, 90-6# SanHao Street, Heping District, ShenYang, China", | |||
philosophy: | |||
"We are committed to providing customers with more comfortable and secure digital lives, focusing on sustainable development, and contributing to society as a growth-oriented enterprise.", | |||
businessHours: "Mon - Fri: 9:00 AM - 6:00 PM", | |||
businessActivities: | |||
"Development, production, and sales of flash memory, development, manufacturing, and sales of SSD products, and other related businesses.", | |||
}, | |||
}, | |||
contact: { | |||
title: "Contact Us", | |||
description: "Contact us for more product information and support.", | |||
keywords: "Hanye, contact us, product information, technical support", | |||
name: "Name", | |||
email: "Email Address", | |||
email: "Email", | |||
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", | |||
}, | |||
captcha: "Captcha", | |||
refreshCaptcha: "Refresh Captcha", | |||
submit: "Submit", | |||
submitting: "Submitting...", | |||
}, | |||
}; |
@@ -11,14 +11,50 @@ export default { | |||
hotKeywords: "人気のキーワード", | |||
productCategories: "製品カテゴリー", | |||
byUsage: "用途で選ぶ", | |||
footer: { | |||
productsLinks: { | |||
title: "製品", | |||
}, | |||
websiteLinks: { | |||
title: "ウェブサイト", | |||
home: "ホーム", | |||
products: "製品", | |||
faq: "よくある質問", | |||
about: "会社概要", | |||
contact: "お問い合わせ", | |||
}, | |||
quickLinks: { | |||
title: "クイックリンク", | |||
}, | |||
}, | |||
}, | |||
home: { | |||
title: "Hanye ウェブサイトへようこそ", | |||
description: "高品質の製品とサービスを提供しています", | |||
keywords: "Hanye, メモリ, ストレージ, 製品, サービス, SD, SSD, microSD", | |||
learnMore: "詳細を見る", | |||
carousel: { | |||
one: { | |||
title: "新技術", | |||
description: "3D NAND TLC", | |||
description2: "フラッシュ採用、", | |||
description3: "信頼性が高く耐久性に優\nれている", | |||
description4: "ストレスのないゲーム体験をお楽しみください", | |||
description5: "パソコンの起動時間が劇的に速くなった!", | |||
}, | |||
}, | |||
}, | |||
products: { | |||
title: "当社の製品", | |||
description: "当社の製品、製品の使用方法や購入に関する助けを得ることができます。", | |||
keywords: "Hanye, 製品, 製品情報, 技術サポート", | |||
product_list: "製品一覧", | |||
product_list_description: | |||
"優れた製品は、10年以上の経験と継続的な革新デザインの結果です。", | |||
product_categories_title: "製品カテゴリー", | |||
product_categories_description: "製品の種類で選ぶ。", | |||
product_categories_usage: "用途で選ぶ", | |||
product_categories_usage_description: "製品の用途で選ぶ。", | |||
viewDetails: "詳細を見る", | |||
consultation: "製品に関するご相談、お見積もりはお気軽にどうぞ", | |||
consultation_button: "お問い合わせ", | |||
@@ -33,89 +69,64 @@ export default { | |||
develop_description: "製品の多様化を図り、新たな創意への飽くなき挑戦", | |||
strong_point: "当社の強み", | |||
strong_point_title: "当社の強み/選ばれる理由", | |||
view_details: "詳細を見る", | |||
}, | |||
faq: { | |||
title: "よくある質問", | |||
description: "よくある質問、製品の使用方法や購入に関する助けを得ることができます。", | |||
keywords: "Hanye, よくある質問, 製品情報, 技術サポート", | |||
searchPlaceholder: "質問を検索", | |||
category: "カテゴリー", | |||
noResults: "該当する質問はありません", | |||
clearSearch: "検索をクリア", | |||
}, | |||
about: { | |||
title: "当社について", | |||
meta: { | |||
title: "当社について - Hanye", | |||
description: "Hanyeの会社情報、沿革、事業内容をご覧ください。メモリ及び関連製品の開発・製造・販売に取り組んでいます。" | |||
}, | |||
intro: { | |||
title: "当社について", | |||
paragraph1: "Hanye は中国瀋陽に運営本部をおき 2003年に設立されました。", | |||
paragraph2: "創業から現在に至るまで、メモリ(記憶媒体) 及び 関連製品の開発・製造・販売を統べる総合企業に成長いたしました。", | |||
paragraph3: "また万全なアフターサービス体制も構築し全力でサポートいたします。お客様に信頼いただけるパートナーを目指し 一層の努力を重ねてまいります。", | |||
}, | |||
title: "当社について - Hanye", | |||
description: "Hanyeの会社概要、経営理念、企業情報をご紹介します。", | |||
keywords: "Hanye, 会社概要, 経営理念, 企業情報", | |||
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: "その関連事業", | |||
companyInfo: "会社情報", | |||
established: "設立", | |||
ceo: "代表取締役", | |||
employees: "従業員数", | |||
location: "所在地", | |||
philosophy: "経営理念", | |||
contact: "お問い合わせ", | |||
email: "メールアドレス", | |||
tel: "電話番号", | |||
fax: "FAX番号", | |||
businessHours: "営業時間", | |||
businessActivities: "事業内容", | |||
companyName: "会社名", | |||
englishName: "英文名", | |||
}, | |||
companyInfo: { | |||
name: "Hanye", | |||
companyName: "Hanye", | |||
englishName: "Hanye Technology Co., Ltd.", | |||
description: "Hanye は中国瀋陽に運営本部をおき2003年に設立されました。\n創業から現在に至るまで、メモリ(記憶媒体) 及び 関連製品の開発・製造・販売を統べる総合企業に成長いたしました。\nまた万全なアフターサービス体制も構築し全力でサポートいたします。\nお客様に信頼いただけるパートナーを目指し 一層の努力を重ねてまいります。", | |||
established: "2003年", | |||
ceo: "ZHENG XIAO DONG", | |||
employees: "30人", | |||
location: "803, NO.6, AiTe, 90-6# SanHao Street, Heping District, ShenYang, China", | |||
philosophy: | |||
"私たちは、革新的な技術と優れた品質で、お客様のデジタルライフをより快適に、より安全にすることを目指しています。持続可能な開発と環境への配慮を重視し、社会に貢献する企業として成長し続けます。", | |||
businessHours: "月曜日 - 金曜日 9:00-18:00", | |||
businessActivities: | |||
"閃存の開発、生産、販売, SSD製品の開発、製造、販売, その他の関連業務", | |||
}, | |||
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: "お問い合わせ", | |||
description: "Hanyeにお問い合わせください。", | |||
keywords: "Hanye, お問い合わせ, 製品情報, 技術サポート", | |||
name: "お名前", | |||
email: "メールアドレス", | |||
message: "メッセージ", | |||
captcha: "検証コード", | |||
refreshCaptcha: "検証コードを更新", | |||
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", | |||
}, | |||
submitting: "送信中...", | |||
}, | |||
}; |
@@ -11,14 +11,51 @@ export default { | |||
hotKeywords: "热门搜索", | |||
productCategories: "产品分类", | |||
byUsage: "按用途", | |||
footer: { | |||
productsLinks: { | |||
title: "产品", | |||
}, | |||
websiteLinks: { | |||
title: "网站", | |||
home: "首页", | |||
products: "产品", | |||
faq: "常见问题", | |||
about: "关于我们", | |||
contact: "联系我们", | |||
}, | |||
quickLinks: { | |||
title: "快捷链接", | |||
}, | |||
}, | |||
}, | |||
home: { | |||
title: "欢迎来到Hanye官网", | |||
description: "我们提供高质量的产品和服务", | |||
title: "Hanye 官网", | |||
description: | |||
"我们提供高质量的产品和服务,包括内存及SD,SSD,microSD相关产品, 并提供专业的技术支持", | |||
keywords: "Hanye, 内存, 存储, 产品, 服务, SD, SSD, microSD, 技术支持", | |||
learnMore: "了解更多", | |||
carousel: { | |||
one: { | |||
title: "新科技", | |||
description: "3D NAND TLC", | |||
description2: "Flash-based,", | |||
description3: "高可靠性、高耐用性", | |||
description4: "享受无压力的游戏体验", | |||
description5: "电脑启动时间大幅提升!", | |||
}, | |||
}, | |||
}, | |||
products: { | |||
title: "我们的产品", | |||
title: "产品", | |||
description: "我们的产品,帮助您更好地了解Hanye的产品和服务。", | |||
keywords: "Hanye, 产品, 服务, SD, SSD, microSD, 技术支持", | |||
product_list: "产品列表", | |||
product_list_description: | |||
"卓越的产品是基于我们10多年经验的技术和持续创新设计相结合的结果。", | |||
product_categories_title: "产品分类", | |||
product_categories_description: "根据产品类型选择产品。", | |||
product_categories_usage: "按用途", | |||
product_categories_usage_description: "根据产品用途选择产品。", | |||
viewDetails: "查看详情", | |||
consultation: "欢迎进行产品咨询,我们将在第一时间回复您", | |||
consultation_button: "联系我们", | |||
@@ -32,100 +69,65 @@ export default { | |||
develop_description: "不断开发和制造产品,提供创新解决方案", | |||
strong_point: "我们的优势", | |||
strong_point_title: "我们的优势/选择我们的理由", | |||
view_details: "查看详情", | |||
}, | |||
faq: { | |||
title: "常见问题", | |||
description: "常见问题,帮助您更好地了解Hanye的产品和服务。", | |||
keywords: "Hanye, 常见问题, 产品信息, 技术支持", | |||
searchPlaceholder: "搜索问题", | |||
category: "分类", | |||
clearSearch: "清除搜索", | |||
noResults: "没有找到相关问题", | |||
}, | |||
about: { | |||
title: "关于我们", | |||
meta: { | |||
title: "关于我们 - Hanye", | |||
description: "了解 Hanye 的公司信息、发展历程和业务范围。我们致力于内存及相关产品的开发、制造和销售。" | |||
}, | |||
intro: { | |||
title: "公司简介", | |||
paragraph1: "Hanye 成立于2003年,运营总部位于中国沈阳。", | |||
paragraph2: "自创立至今,我们已成长为集内存(存储介质)及相关产品的研发、制造、销售于一体的综合性企业。", | |||
paragraph3: "我们建立了完善的售后服务体系,竭诚为您提供支持。我们致力于成为客户信赖的合作伙伴,并将为此付出更多努力。", | |||
}, | |||
description: "介绍Hanye的公司概况、经营理念、企业信息", | |||
keywords: "Hanye, 公司概况, 经营理念, 企业信息", | |||
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: "及相关业务", | |||
title: "公司概况", | |||
companyInfo: "公司信息", | |||
established: "成立时间", | |||
ceo: "CEO", | |||
employees: "员工人数", | |||
location: "所在地", | |||
philosophy: "经营理念", | |||
contact: "联系方式", | |||
email: "邮箱", | |||
tel: "电话", | |||
fax: "传真", | |||
businessHours: "营业时间", | |||
businessActivities: "经营内容", | |||
companyName: "公司名称", | |||
englishName: "英文名称", | |||
}, | |||
companyInfo: { | |||
name: "Hanye", | |||
companyName: "Hanye", | |||
englishName: "Hanye Technology Co., Ltd.", | |||
description: | |||
"Hanye 成立于2003年,运营总部设在中国沈阳。\n创业至今,我司已成长为一家集开发、制造、销售内存(存储媒体)及相关产品于一体的综合性企业。\n我司还将构筑完善的售后服务体系,全力提供技术支持。\n以成为客户信赖的合作伙伴为目标而更加努力。", | |||
established: "2003年", | |||
ceo: "ZHENG XIAO DONG", | |||
employees: "30人", | |||
location: "中国辽宁省沈阳市和平区三好街90-6号艾特国际大厦803室", | |||
philosophy: | |||
"我们致力于为客户提供更舒适、更安全的数字生活,专注于可持续发展,并作为成长型企业为社会做出贡献。", | |||
businessHours: "周一至周五 9:00-18:00 (节假日除外)", | |||
businessActivities: | |||
"闪存的开发、生产和销售,开发、制造和销售SSD产品,其相关业务", | |||
}, | |||
contact: { | |||
title: "联系方式", | |||
emailLabel: "电子邮箱", | |||
emailValue: "hanye#hanye.cn", | |||
hoursLabel: "营业时间", | |||
hoursValue1: "工作日 9:00 - 18:00", | |||
hoursValue2: "周六、周日及法定节假日休息", | |||
phoneLabel: "电话", | |||
phoneValue: "86)024-8399-0696", | |||
phoneNote: "如需来访,请先电话联系。", | |||
} | |||
}, | |||
contact: { | |||
title: "联系我们", | |||
description: "联系我们,获取更多产品信息和支持。", | |||
keywords: "Hanye, 联系我们, 产品信息, 技术支持", | |||
name: "姓名", | |||
email: "邮箱", | |||
message: "消息", | |||
captcha: "验证码", | |||
refreshCaptcha: "点击刷新验证码", | |||
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 (节假日除外)", | |||
}, | |||
submitting: "正在提交...", | |||
}, | |||
}; |
@@ -48,9 +48,20 @@ export default defineNuxtConfig({ | |||
crawlLinks: true, | |||
routes: ["/"], | |||
}, | |||
// 添加图片本地化配置 | |||
publicAssets: [ | |||
{ | |||
dir: 'public', | |||
baseURL: '/' | |||
}, | |||
{ | |||
dir: 'public/images/remote', | |||
baseURL: '/images/remote' | |||
} | |||
] | |||
}, | |||
devServer: { | |||
host: "0.0.0.0", | |||
}, | |||
} | |||
}); |
@@ -6,6 +6,8 @@ | |||
"build": "nuxt build", | |||
"dev": "nuxt dev", | |||
"generate": "nuxt generate", | |||
"generate:localize": "nuxt generate && node scripts/localize-images.mjs", | |||
"localize-images": "node scripts/localize-images.mjs", | |||
"preview": "nuxt preview", | |||
"postinstall": "nuxt prepare" | |||
}, |
@@ -16,88 +16,170 @@ | |||
<nuxt-link | |||
to="/" | |||
class="justify-start text-white/60 text-base font-normal hover:text-white transition-colors duration-300" | |||
>ホーム</nuxt-link | |||
>{{ $t("common.home") }}</nuxt-link | |||
> | |||
<span class="text-white/60 text-base font-normal px-2"> / </span> | |||
<span class="text-white text-base font-normal">会社概要</span> | |||
<span class="text-white text-base font-normal">{{ | |||
$t("about.overview.title") | |||
}}</span> | |||
</div> | |||
</div> | |||
<!-- 顶部大标题 --> | |||
<div class="flex flex-col items-center justify-center px-2 mb-10"> | |||
<h1 class="text-white text-5xl font-bold mb-4 tracking-tight text-center"> | |||
{{ companyInfo.name }} | |||
<h1 | |||
class="text-white text-5xl font-bold mb-4 tracking-tight text-center" | |||
> | |||
{{ $t("about.companyInfo.name") }} | |||
</h1> | |||
<div class="text-stone-400 text-xl leading-relaxed text-center max-w-2xl"> | |||
{{ companyInfo.description }} | |||
<div | |||
class="text-stone-400 text-xl leading-relaxed text-center max-w-2xl break-words whitespace-pre-wrap" | |||
> | |||
{{ $t("about.companyInfo.description") }} | |||
</div> | |||
</div> | |||
<!-- 横向三栏分区卡片 --> | |||
<div class="w-full flex flex-col lg:flex-row gap-8 justify-center items-stretch max-w-screen-2xl mx-auto px-2 mb-20"> | |||
<div | |||
class="w-full flex flex-col lg:flex-row gap-8 justify-center items-stretch max-w-screen-2xl mx-auto px-2 mb-20" | |||
> | |||
<!-- 公司信息卡片 --> | |||
<div class="flex-1 bg-zinc-900/95 backdrop-blur-sm rounded-xl p-8 border border-zinc-800/50 shadow-lg flex flex-col gap-6 min-w-[260px]"> | |||
<h2 class="text-white text-2xl font-semibold mb-2 tracking-tight relative"> | |||
会社情報 | |||
<span class="absolute -bottom-2 left-0 w-12 h-1 bg-gradient-to-r from-blue-500 to-blue-300 rounded-full"></span> | |||
<div | |||
class="flex-1 bg-zinc-900/95 backdrop-blur-sm rounded-xl p-8 border border-zinc-800/50 shadow-lg flex flex-col gap-6 min-w-[260px]" | |||
> | |||
<h2 | |||
class="text-white text-2xl font-semibold mb-2 tracking-tight relative" | |||
> | |||
{{ $t("about.overview.companyInfo") }} | |||
<span | |||
class="absolute -bottom-2 left-0 w-12 h-1 bg-gradient-to-r from-blue-500 to-blue-300 rounded-full" | |||
></span> | |||
</h2> | |||
<div class="flex flex-col gap-3"> | |||
<div class="flex flex-col gap-1"> | |||
<span class="text-stone-400 text-base">設立</span> | |||
<span class="text-white text-lg font-bold">{{ companyInfo.established }}</span> | |||
<span class="text-stone-400 text-base">{{ | |||
$t("about.overview.companyName") | |||
}}</span> | |||
<span class="text-white text-lg font-bold">{{ | |||
$t("about.companyInfo.name") | |||
}}</span> | |||
</div> | |||
<div class="flex flex-col gap-1"> | |||
<span class="text-stone-400 text-base">{{ | |||
$t("about.overview.englishName") | |||
}}</span> | |||
<span class="text-white text-lg font-bold">{{ | |||
$t("about.companyInfo.englishName") | |||
}}</span> | |||
</div> | |||
<div class="flex flex-col gap-1"> | |||
<span class="text-stone-400 text-base">{{ | |||
$t("about.overview.established") | |||
}}</span> | |||
<span class="text-white text-lg font-bold">{{ | |||
$t("about.companyInfo.established") | |||
}}</span> | |||
</div> | |||
<div class="flex flex-col gap-1"> | |||
<span class="text-stone-400 text-base">代表者</span> | |||
<span class="text-white text-lg font-bold">{{ companyInfo.ceo }}</span> | |||
<span class="text-stone-400 text-base">{{ | |||
$t("about.overview.ceo") | |||
}}</span> | |||
<span class="text-white text-lg font-bold">{{ | |||
$t("about.companyInfo.ceo") | |||
}}</span> | |||
</div> | |||
<div class="flex flex-col gap-1"> | |||
<span class="text-stone-400 text-base">従業員数</span> | |||
<span class="text-white text-lg font-bold">{{ companyInfo.employees }}</span> | |||
<span class="text-stone-400 text-base">{{ | |||
$t("about.overview.employees") | |||
}}</span> | |||
<span class="text-white text-lg font-bold">{{ | |||
$t("about.companyInfo.employees") | |||
}}</span> | |||
</div> | |||
<div class="flex flex-col gap-1"> | |||
<span class="text-stone-400 text-base">所在地</span> | |||
<span class="text-white text-lg font-bold">{{ companyInfo.location }}</span> | |||
<span class="text-stone-400 text-base">{{ | |||
$t("about.overview.location") | |||
}}</span> | |||
<span class="text-white text-lg font-bold">{{ | |||
$t("about.companyInfo.location") | |||
}}</span> | |||
</div> | |||
</div> | |||
</div> | |||
<!-- 经营理念卡片 --> | |||
<div class="flex-1 bg-zinc-900/95 backdrop-blur-sm rounded-xl p-8 border border-zinc-800/50 shadow-lg flex flex-col gap-6 min-w-[260px]"> | |||
<h2 class="text-white text-2xl font-semibold mb-2 tracking-tight relative"> | |||
経営理念 | |||
<span class="absolute -bottom-2 left-0 w-12 h-1 bg-gradient-to-r from-blue-500 to-blue-300 rounded-full"></span> | |||
<div | |||
class="flex-1 bg-zinc-900/95 backdrop-blur-sm rounded-xl p-8 border border-zinc-800/50 shadow-lg flex flex-col gap-6 min-w-[260px]" | |||
> | |||
<h2 | |||
class="text-white text-2xl font-semibold mb-2 tracking-tight relative" | |||
> | |||
{{ $t("about.overview.philosophy") }} | |||
<span | |||
class="absolute -bottom-2 left-0 w-12 h-1 bg-gradient-to-r from-blue-500 to-blue-300 rounded-full" | |||
></span> | |||
</h2> | |||
<div class="text-stone-400 text-lg leading-relaxed"> | |||
{{ companyInfo.philosophy }} | |||
{{ $t("about.companyInfo.philosophy") }} | |||
</div> | |||
</div> | |||
<!-- 联系方式卡片 --> | |||
<div class="flex-1 bg-zinc-900/95 backdrop-blur-sm rounded-xl p-8 border border-zinc-800/50 shadow-lg flex flex-col gap-6 min-w-[260px]"> | |||
<h2 class="text-white text-2xl font-semibold mb-2 tracking-tight relative"> | |||
お問い合わせ | |||
<span class="absolute -bottom-2 left-0 w-12 h-1 bg-gradient-to-r from-blue-500 to-blue-300 rounded-full"></span> | |||
<div | |||
class="flex-1 bg-zinc-900/95 backdrop-blur-sm rounded-xl p-8 border border-zinc-800/50 shadow-lg flex flex-col gap-6 min-w-[260px]" | |||
> | |||
<h2 | |||
class="text-white text-2xl font-semibold mb-2 tracking-tight relative" | |||
> | |||
{{ $t("about.overview.contact") }} | |||
<span | |||
class="absolute -bottom-2 left-0 w-12 h-1 bg-gradient-to-r from-blue-500 to-blue-300 rounded-full" | |||
></span> | |||
</h2> | |||
<div class="flex flex-col gap-3"> | |||
<div class="flex flex-col gap-1"> | |||
<span class="text-stone-400 text-base">E-mail</span> | |||
<a :href="'mailto:' + companyInfo.email" class="text-white text-lg font-bold hover:text-blue-400 transition-colors">{{ companyInfo.email }}</a> | |||
<span class="text-stone-400 text-base">{{ | |||
$t("about.overview.email") | |||
}}</span> | |||
<a | |||
:href="'mailto:' + companyInfo.email" | |||
class="text-white text-lg font-bold hover:text-blue-400 transition-colors" | |||
>{{ companyInfo.email }}</a | |||
> | |||
</div> | |||
<div class="flex flex-col gap-1"> | |||
<span class="text-stone-400 text-base">TEL</span> | |||
<a :href="'tel:' + companyInfo.tel.replace(/[^0-9]/g, '')" class="text-white text-lg font-bold hover:text-blue-400 transition-colors">{{ companyInfo.tel }}</a> | |||
<span class="text-stone-400 text-base">{{ | |||
$t("about.overview.tel") | |||
}}</span> | |||
<a | |||
:href="'tel:' + companyInfo.tel.replace(/[^0-9]/g, '')" | |||
class="text-white text-lg font-bold hover:text-blue-400 transition-colors" | |||
>{{ companyInfo.tel }}</a | |||
> | |||
</div> | |||
<div class="flex flex-col gap-1"> | |||
<span class="text-stone-400 text-base">FAX</span> | |||
<span class="text-white text-lg font-bold">{{ companyInfo.fax }}</span> | |||
<span class="text-stone-400 text-base">{{ | |||
$t("about.overview.fax") | |||
}}</span> | |||
<span class="text-white text-lg font-bold">{{ | |||
companyInfo.fax | |||
}}</span> | |||
</div> | |||
<div class="flex flex-col gap-1"> | |||
<span class="text-stone-400 text-base">営業時間</span> | |||
<span class="text-white text-lg font-bold">{{ companyInfo.businessHours }}</span> | |||
<span class="text-stone-400 text-base">{{ | |||
$t("about.overview.businessHours") | |||
}}</span> | |||
<span class="text-white text-lg font-bold">{{ | |||
$t("about.companyInfo.businessHours") | |||
}}</span> | |||
</div> | |||
</div> | |||
<div class="flex flex-col gap-1 mt-2"> | |||
<span class="text-stone-400 text-base">事業内容</span> | |||
<span class="text-stone-400 text-base">{{ | |||
$t("about.overview.businessActivities") | |||
}}</span> | |||
<div class="text-white text-base font-bold space-y-1"> | |||
<p v-for="(item, idx) in companyInfo.businessActivities" :key="idx">{{ item }}</p> | |||
<p> | |||
{{ $t("about.companyInfo.businessActivities") }} | |||
</p> | |||
</div> | |||
</div> | |||
</div> | |||
@@ -113,53 +195,34 @@ | |||
* 展示公司基本信息、理念等 | |||
*/ | |||
import { useErrorHandler } from "~/composables/useErrorHandler"; | |||
import companyImage from "@/assets/images/product-banner.webp"; | |||
import { useI18n } from "vue-i18n"; | |||
const { t, locale } = useI18n(); | |||
// 公司信息接口定义 | |||
interface CompanyInfo { | |||
name: string; | |||
englishName: string; | |||
description: string; | |||
established: string; | |||
ceo: string; | |||
employees: string; | |||
location: string; | |||
philosophy: string; | |||
email: string; | |||
tel: string; | |||
fax: string; | |||
businessHours: string; | |||
businessActivities: string[]; | |||
} | |||
const { error, isLoading, wrapAsync } = useErrorHandler(); | |||
const companyInfo = ref<CompanyInfo>({ | |||
name: "Hanye", | |||
englishName: "Hanye Technology Co., Ltd.", | |||
description: "Hanye成立于2003年,运营总部设在中国沈阳。創業から現在に至るまで、メモリ(記憶媒体) 及び 関連製品の開発・製造・販売を統べる総合企業に成長いたしました。我司还将构筑完善的售后服务体系,全力提供技术支持。以成为客户信赖的合作伙伴为目标而更加努力。", | |||
established: "2003年", | |||
ceo: "ZHENG XIAO DONG", | |||
employees: "30人", | |||
location: "803, NO.6, AiTe, 90-6# SanHao Street, Heping District, ShenYang, China", | |||
philosophy: "私たちは、革新的な技術と優れた品質で、お客様のデジタルライフをより快適に、より安全にすることを目指しています。持続可能な開発と環境への配慮を重視し、社会に貢献する企業として成長し続けます。", | |||
email: "hanye@hanye.cn", | |||
tel: "86)024-8399-0696", | |||
fax: "86)024-8399-0696", | |||
businessHours: "平日 9:00-18:00 / 土日祝日休み", | |||
businessActivities: [ | |||
"闪存的开发、生产和销售", | |||
"开发、制造和销售SSD产品", | |||
"其相关业务" | |||
] | |||
tel: "86)024-8399-0696", | |||
fax: "86)024-8399-0696", | |||
}); | |||
// SEO优化 | |||
useHead({ | |||
title: "会社概要 - Hanye", | |||
title: t("about.title"), | |||
meta: [ | |||
{ | |||
name: "description", | |||
content: "Hanyeの会社概要、経営理念、企業情報をご紹介します。", | |||
content: t("about.description"), | |||
}, | |||
{ | |||
name: "keywords", | |||
content: t("about.keywords"), | |||
}, | |||
], | |||
}); | |||
@@ -179,7 +242,8 @@ useHead({ | |||
/* 动画效果 */ | |||
@keyframes float { | |||
0%, 100% { | |||
0%, | |||
100% { | |||
transform: translateY(0) scale(1); | |||
opacity: 0.9; | |||
} | |||
@@ -246,7 +310,8 @@ useHead({ | |||
} | |||
@keyframes pulse { | |||
0%, 100% { | |||
0%, | |||
100% { | |||
opacity: 1; | |||
filter: drop-shadow(0 0 2px rgba(59, 130, 246, 0.6)); | |||
} |
@@ -14,12 +14,12 @@ | |||
<nuxt-link | |||
to="/" | |||
class="justify-start text-white/60 text-base font-normal" | |||
>ホーム</nuxt-link | |||
>{{ $t("common.home") }}</nuxt-link | |||
> | |||
<span class="text-white/60 text-base font-normal px-2"> / </span> | |||
<nuxt-link to="/contact" class="text-white text-base font-normal" | |||
>お問い合わせ</nuxt-link | |||
> | |||
<nuxt-link to="/contact" class="text-white text-base font-normal">{{ | |||
$t("contact.title") | |||
}}</nuxt-link> | |||
</div> | |||
</div> | |||
<div | |||
@@ -32,7 +32,7 @@ | |||
class="bg-zinc-900 rounded-lg p-8 transition-all duration-300 hover:bg-zinc-800 hover:shadow-lg" | |||
> | |||
<div class="text-white text-3xl font-medium mb-6"> | |||
お問い合わせフォーム | |||
{{ $t("contact.title") }} | |||
</div> | |||
<form | |||
@submit.prevent="handleSubmit" | |||
@@ -44,14 +44,14 @@ | |||
type="text" | |||
id="name" | |||
class="peer block w-full appearance-none bg-transparent border-0 border-b-2 px-1 pt-5 pb-2 text-base text-gray-100 focus:outline-none focus:ring-0 placeholder-transparent transition-colors duration-300 focus:border-b-[3px] border-gray-600 focus:border-blue-500" | |||
placeholder="お名前を入力してください" | |||
:placeholder="$t('contact.name')" | |||
required | |||
/> | |||
<label | |||
for="name" | |||
class="absolute left-1 top-5 origin-[0] -translate-y-4 scale-75 transform text-sm duration-300 peer-placeholder-shown:translate-y-0 peer-placeholder-shown:scale-100 peer-focus:-translate-y-4 peer-focus:scale-75 peer-focus:font-medium text-gray-400 peer-focus:text-blue-400" | |||
> | |||
お名前 | |||
{{ $t("contact.name") }} | |||
</label> | |||
</div> | |||
@@ -61,14 +61,14 @@ | |||
type="email" | |||
id="email" | |||
class="peer block w-full appearance-none bg-transparent border-0 border-b-2 px-1 pt-5 pb-2 text-base text-gray-100 focus:outline-none focus:ring-0 placeholder-transparent transition-colors duration-300 focus:border-b-[3px] border-gray-600 focus:border-blue-500" | |||
placeholder="メールアドレスを入力してください" | |||
:placeholder="$t('contact.email')" | |||
required | |||
/> | |||
<label | |||
for="email" | |||
class="absolute left-1 top-5 origin-[0] -translate-y-4 scale-75 transform text-sm duration-300 peer-placeholder-shown:translate-y-0 peer-placeholder-shown:scale-100 peer-focus:-translate-y-4 peer-focus:scale-75 peer-focus:font-medium text-gray-400 peer-focus:text-blue-400" | |||
> | |||
メールアドレス | |||
{{ $t("contact.email") }} | |||
</label> | |||
</div> | |||
@@ -77,14 +77,14 @@ | |||
v-model="form.message" | |||
id="message" | |||
class="peer block w-full appearance-none bg-transparent border-0 border-b-2 px-1 pt-5 pb-2 text-base text-gray-100 focus:outline-none focus:ring-0 placeholder-transparent h-36 resize-none transition-colors duration-300 focus:border-b-[3px] border-gray-600 focus:border-blue-500" | |||
placeholder="お問い合わせ内容を入力してください" | |||
:placeholder="$t('contact.message')" | |||
required | |||
></textarea> | |||
<label | |||
for="message" | |||
class="absolute left-1 top-5 origin-[0] -translate-y-4 scale-75 transform text-sm duration-300 peer-placeholder-shown:translate-y-0 peer-placeholder-shown:scale-100 peer-focus:-translate-y-4 peer-focus:scale-75 peer-focus:font-medium text-gray-400 peer-focus:text-blue-400" | |||
> | |||
お問い合わせ内容 | |||
{{ $t("contact.message") }} | |||
</label> | |||
</div> | |||
@@ -102,7 +102,7 @@ | |||
? 'border-red-500 focus:border-red-500' | |||
: 'border-gray-600 focus:border-blue-500', | |||
]" | |||
placeholder="验证码" | |||
:placeholder="$t('contact.captcha')" | |||
required | |||
autocomplete="off" | |||
aria-describedby="captcha-error" | |||
@@ -117,22 +117,22 @@ | |||
: 'text-gray-400 peer-focus:text-blue-400', | |||
]" | |||
> | |||
验证码 | |||
{{ $t("contact.captcha") }} | |||
</label> | |||
</div> | |||
<div | |||
class="flex-shrink-0 cursor-pointer select-none rounded-md overflow-hidden transition-all duration-200 ease-in-out hover:shadow-md active:scale-100" | |||
v-html="captcha.captchaSvg.value" | |||
@click="captcha.generateCaptcha()" | |||
title="刷新验证码" | |||
:title="$t('contact.refreshCaptcha')" | |||
style="line-height: 0" | |||
></div> | |||
<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="刷新验证码" | |||
title="刷新验证码" | |||
:aria-label="$t('contact.refreshCaptcha')" | |||
:title="$t('contact.refreshCaptcha')" | |||
> | |||
<svg | |||
xmlns="http://www.w3.org/2000/svg" | |||
@@ -164,7 +164,11 @@ | |||
class="bg-gradient-to-r from-blue-700 to-blue-400 text-white text-base font-normal py-4 px-8 rounded-lg transition-all duration-300 hover:scale-105 hover:shadow-lg" | |||
:disabled="isSubmitting" | |||
> | |||
{{ isSubmitting ? "送信中..." : "送信する" }} | |||
{{ | |||
isSubmitting | |||
? $t("contact.submitting") | |||
: $t("contact.submit") | |||
}} | |||
</button> | |||
</form> | |||
</div> | |||
@@ -175,39 +179,39 @@ | |||
class="bg-zinc-900 rounded-lg p-8 transition-all duration-300 hover:bg-zinc-800 hover:shadow-lg" | |||
> | |||
<div class="text-white text-3xl font-medium mb-6"> | |||
会社情報 | |||
{{ $t("about.companyInfo.name") }} | |||
</div> | |||
<div class="flex flex-col gap-4"> | |||
<div class="flex items-center gap-4"> | |||
<div class="text-white/60 text-base font-normal"> | |||
会社名 | |||
{{ $t("about.overview.companyName") }} | |||
</div> | |||
<div class="text-white text-base font-normal"> | |||
株式会社ハニエ | |||
{{ $t("about.companyInfo.companyName") }} | |||
</div> | |||
</div> | |||
<div class="flex items-center gap-4"> | |||
<div class="text-white/60 text-base font-normal"> | |||
所在地 | |||
{{ $t("about.overview.location") }} | |||
</div> | |||
<div class="text-white text-base font-normal"> | |||
〒123-4567 東京都渋谷区神宮前1-1-1 | |||
{{ $t("about.companyInfo.location") }} | |||
</div> | |||
</div> | |||
<div class="flex items-center gap-4"> | |||
<div class="text-white/60 text-base font-normal"> | |||
電話番号 | |||
{{ $t("about.overview.tel") }} | |||
</div> | |||
<div class="text-white text-base font-normal"> | |||
03-1234-5678 | |||
86)024-8399-0696 | |||
</div> | |||
</div> | |||
<div class="flex items-center gap-4"> | |||
<div class="text-white/60 text-base font-normal"> | |||
メールアドレス | |||
{{ $t("about.overview.email") }} | |||
</div> | |||
<div class="text-white text-base font-normal"> | |||
info@hanye.co.jp | |||
hanye@hanye.cn | |||
</div> | |||
</div> | |||
</div> | |||
@@ -217,31 +221,10 @@ | |||
class="bg-zinc-900 rounded-lg p-8 transition-all duration-300 hover:bg-zinc-800 hover:shadow-lg" | |||
> | |||
<div class="text-white text-3xl font-medium mb-6"> | |||
営業時間 | |||
{{ $t("about.overview.businessHours") }} | |||
</div> | |||
<div class="flex flex-col gap-4"> | |||
<div class="flex items-center gap-4"> | |||
<div class="text-white/60 text-base font-normal"> | |||
平日 | |||
</div> | |||
<div class="text-white text-base font-normal"> | |||
9:00 - 18:00 | |||
</div> | |||
</div> | |||
<div class="flex items-center gap-4"> | |||
<div class="text-white/60 text-base font-normal"> | |||
土曜日 | |||
</div> | |||
<div class="text-white text-base font-normal"> | |||
9:00 - 17:00 | |||
</div> | |||
</div> | |||
<div class="flex items-center gap-4"> | |||
<div class="text-white/60 text-base font-normal"> | |||
日曜日・祝日 | |||
</div> | |||
<div class="text-white text-base font-normal">休業</div> | |||
</div> | |||
<div class="text-white text-base font-normal"> | |||
{{ $t("about.companyInfo.businessHours") }} | |||
</div> | |||
</div> | |||
</div> | |||
@@ -260,6 +243,7 @@ | |||
*/ | |||
import { useErrorHandler } from "~/composables/useErrorHandler"; | |||
import { useCaptcha } from "~/composables/useCaptcha"; | |||
const { t, locale } = useI18n(); | |||
const { error, isLoading, wrapAsync } = useErrorHandler(); | |||
const captcha = useCaptcha(); | |||
@@ -303,16 +287,16 @@ async function handleSubmit() { | |||
// SEO优化 | |||
useHead({ | |||
title: "联系我们 - Hanye", | |||
title: t("contact.title") + " - Hanye", | |||
meta: [ | |||
{ | |||
name: "description", | |||
content: "联系我们,获取更多产品信息和支持。", | |||
content: t("contact.description"), | |||
}, | |||
{ | |||
name: "keywords", | |||
content: t("contact.keywords"), | |||
}, | |||
], | |||
}); | |||
</script> | |||
<style scoped> | |||
/* 移除之前的 focus 样式,因为现在使用 border-b 样式 */ | |||
</style> |
@@ -14,11 +14,11 @@ | |||
<nuxt-link | |||
to="/" | |||
class="justify-start text-white/60 text-base font-normal" | |||
>ホーム</nuxt-link | |||
>{{ $t("common.home") }}</nuxt-link | |||
> | |||
<span class="text-white/60 text-base font-normal px-2"> / </span> | |||
<nuxt-link to="/faq" class="text-white text-base font-normal" | |||
>よくある質問</nuxt-link | |||
>{{ $t("faq.title") }}</nuxt-link | |||
> | |||
</div> | |||
</div> | |||
@@ -30,7 +30,9 @@ | |||
<!-- 左侧分类导航 --> | |||
<div class="col-span-1 md:col-span-2"> | |||
<div class="flex flex-col gap-4"> | |||
<div class="text-white text-3xl font-medium">カテゴリー</div> | |||
<div class="text-white text-3xl font-medium"> | |||
{{ $t("faq.category") }} | |||
</div> | |||
<div class="flex flex-col gap-4 w-fit"> | |||
<div | |||
v-for="category in categories" | |||
@@ -57,14 +59,14 @@ | |||
v-model="searchTerm" | |||
ref="searchInputRef" | |||
type="search" | |||
placeholder="キーワードで検索..." | |||
:placeholder="$t('faq.searchPlaceholder')" | |||
class="block w-full appearance-none rounded-lg border border-gray-600 bg-zinc-800/70 px-4 py-3 text-base text-gray-100 placeholder-gray-400 shadow-inner transition duration-200 focus:border-blue-500 focus:outline-none focus:ring-2 focus:ring-blue-500/50 focus:ring-offset-2 focus:ring-offset-zinc-900" | |||
/> | |||
<button | |||
v-if="searchTerm" | |||
@click="clearSearch" | |||
class="absolute right-2 top-1/2 -translate-y-1/2 flex items-center justify-center w-8 h-8 rounded-full bg-zinc-700/80 hover:bg-blue-500/90 text-gray-300 hover:text-white transition-all duration-200 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500" | |||
aria-label="検索キーワードをクリア" | |||
:aria-label="$t('faq.clearSearch')" | |||
tabindex="0" | |||
type="button" | |||
> | |||
@@ -118,7 +120,7 @@ | |||
v-if="filteredFaqs.length === 0" | |||
class="text-center text-gray-400 py-8" | |||
> | |||
該当する質問が見つかりません。 | |||
{{ $t("faq.noResults") }} | |||
</div> | |||
</div> | |||
</div> | |||
@@ -139,6 +141,8 @@ import { useErrorHandler } from "~/composables/useErrorHandler"; | |||
const { error, isLoading, wrapAsync } = useErrorHandler(); | |||
const { t, locale } = useI18n(); | |||
// FAQ数据 | |||
interface FAQ { | |||
id: number; | |||
@@ -299,11 +303,15 @@ function clearSearch() { | |||
// SEO优化 | |||
useHead({ | |||
title: "常见问题 - Hanye", | |||
title: t("faq.title") + " - Hanye", | |||
meta: [ | |||
{ | |||
name: "description", | |||
content: "浏览常见问题,获取产品使用和购买相关的帮助。", | |||
content: t("faq.description"), | |||
}, | |||
{ | |||
name: "keywords", | |||
content: t("faq.keywords"), | |||
}, | |||
], | |||
}); |
@@ -8,11 +8,11 @@ | |||
:space-between="30" | |||
:loop="true" | |||
:pagination="{ el: '.swiper-pagination-1', clickable: true }" | |||
:autoplay="{ | |||
delay: 5000, | |||
:autoplay="{ | |||
delay: 5000, | |||
disableOnInteraction: false, | |||
pauseOnMouseEnter: true, | |||
waitForTransition: true | |||
waitForTransition: true, | |||
}" | |||
effect="creative" | |||
:creativeEffect="{ | |||
@@ -34,18 +34,65 @@ | |||
:parallax="true" | |||
class="h-[320px] sm:h-[320px] md:h-[768px] lg:h-[900px] swiper-container-1" | |||
> | |||
<SwiperSlide v-for="item in carouselList" :key="item.id"> | |||
<nuxt-link :to="item.link"> | |||
<div | |||
class="max-w-screen-2xl mx-auto h-full" | |||
:style="{ | |||
backgroundImage: `url(${item.image})`, | |||
backgroundSize: 'cover', | |||
backgroundPosition: 'center', | |||
backgroundRepeat: 'no-repeat', | |||
}" | |||
></div> | |||
</nuxt-link> | |||
<SwiperSlide> | |||
<div | |||
class="max-w-screen-2xl mx-auto h-full relative" | |||
:style="{ | |||
backgroundImage: `url(${homeA1Webp})`, | |||
backgroundSize: 'cover', | |||
backgroundPosition: 'center', | |||
backgroundRepeat: 'no-repeat', | |||
}" | |||
> | |||
<div class="w-full h-full flex-col justify-center hidden md:flex relative z-10"> | |||
<div | |||
class="rounded border border-white w-11 h-6 leading-none justify-center flex items-center text-white text-sm font-normal" | |||
> | |||
SSD | |||
</div> | |||
<div class="justify-center"> | |||
<span class="text-white text-6xl font-normal leading-[78px]">{{ | |||
$t("home.carousel.one.title") | |||
}}</span> | |||
<span class="text-white text-6xl font-normal leading-[78px]">{{ | |||
$t("home.carousel.one.description") | |||
}}</span> | |||
<br /> | |||
<span class="text-white text-6xl font-normal leading-[78px]">{{ | |||
$t("home.carousel.one.description2") | |||
}}</span | |||
><br /> | |||
<span | |||
class="text-cyan-400 text-6xl font-normal leading-[78px]" | |||
>{{ $t("home.carousel.one.description3") }}</span | |||
> | |||
</div> | |||
<div class="flex flex-col gap-2 mt-4"> | |||
<div | |||
class="flex items-center gap-2 text-stone-50 text-xl font-normal" | |||
> | |||
<div class="w-2 h-2 bg-white rounded-full"></div> | |||
{{ $t("home.carousel.one.description4") }} | |||
</div> | |||
<div | |||
class="flex items-center gap-2 text-stone-50 text-xl font-normal" | |||
> | |||
<div class="w-2 h-2 bg-white rounded-full"></div> | |||
{{ $t("home.carousel.one.description5") }} | |||
</div> | |||
</div> | |||
<div | |||
class="w-36 h-14 mt-12 flex items-center justify-center bg-[#35F1FF] rounded-lg hover:bg-[#35F1FF]/80 transition-colors duration-300" | |||
> | |||
<nuxt-link | |||
to="/products/1" | |||
class="w-full h-full !flex items-center justify-center text-zinc-900" | |||
> | |||
{{ $t("products.view_details") }} | |||
</nuxt-link> | |||
</div> | |||
</div> | |||
</div> | |||
</SwiperSlide> | |||
<div class="max-w-screen-2xl mx-auto relative"> | |||
<div | |||
@@ -77,26 +124,35 @@ | |||
:key="usage.id" | |||
class="cursor-pointer select-none px-4 sm:px-7 py-2 sm:py-3 rounded-full border border-zinc-700 text-white transition-all duration-300 relative group" | |||
:class="{ | |||
'bg-cyan-400/10 border-cyan-400 text-cyan-400': activeIndex === index, | |||
'hover:border-zinc-600': activeIndex !== index | |||
'bg-cyan-400/10 border-cyan-400 text-cyan-400': | |||
activeIndex === index, | |||
'hover:border-zinc-600': activeIndex !== index, | |||
}" | |||
@click="handleUsageClick(usage.id)" | |||
> | |||
<div class="text-center text-xs sm:text-sm font-normal leading-tight md:text-base transition-colors duration-300 relative z-10"> | |||
<div | |||
class="text-center text-xs sm:text-sm font-normal leading-tight md:text-base transition-colors duration-300 relative z-10" | |||
> | |||
{{ usage.name }} | |||
</div> | |||
<div class="absolute inset-0 rounded-full bg-cyan-400/20 scale-0 transition-transform duration-300 group-hover:scale-100"></div> | |||
<div | |||
class="absolute inset-0 rounded-full bg-cyan-400/20 scale-0 transition-transform duration-300 group-hover:scale-100" | |||
></div> | |||
</div> | |||
<div class="flex items-center justify-center gap-4 ml-auto"> | |||
<div | |||
class="swiper-button-prev-2 bg-zinc-700 w-8 h-8 sm:w-10 sm:h-10 rounded-full flex items-center justify-center cursor-pointer hover:bg-zinc-600 transition-colors duration-200" | |||
> | |||
<i class="icon-arrow-left text-zinc-300 text-xs sm:text-sm font-normal"></i> | |||
<i | |||
class="icon-arrow-left text-zinc-300 text-xs sm:text-sm font-normal" | |||
></i> | |||
</div> | |||
<div | |||
class="swiper-button-next-2 bg-zinc-700 w-8 h-8 sm:w-10 sm:h-10 rounded-full flex items-center justify-center cursor-pointer hover:bg-zinc-600 transition-colors duration-200" | |||
> | |||
<i class="icon-arrow-right text-zinc-300 text-xs sm:text-sm font-normal"></i> | |||
<i | |||
class="icon-arrow-right text-zinc-300 text-xs sm:text-sm font-normal" | |||
></i> | |||
</div> | |||
</div> | |||
</div> | |||
@@ -131,10 +187,7 @@ | |||
class="w-full sm:w-1/2 md:w-1/3 lg:w-1/4" | |||
> | |||
<div class="w-full h-full p-2"> | |||
<nuxt-link | |||
:to="product.link" | |||
class="block w-full h-full" | |||
> | |||
<nuxt-link :to="product.link" class="block w-full h-full"> | |||
<div | |||
class="w-full h-full bg-zinc-900 rounded-2xl p-4 flex flex-col items-center justify-start relative overflow-hidden group hover:bg-zinc-800 transition-all duration-300 hover:shadow-lg hover:shadow-cyan-400/10" | |||
> | |||
@@ -174,7 +227,7 @@ | |||
<div | |||
class="px-4 py-2 bg-cyan-400/20 backdrop-blur-sm rounded-full text-white text-sm font-medium border border-cyan-400/30 transform transition-transform duration-300 group-hover:scale-105" | |||
> | |||
查看详情 | |||
{{ $t("products.view_details") }} | |||
</div> | |||
</div> | |||
</div> | |||
@@ -182,7 +235,8 @@ | |||
<div | |||
class="w-full mt-4 min-h-[80px] transition-all duration-300 transform" | |||
:class="{ | |||
'opacity-0 translate-y-4': !isImageLoaded[product.id], | |||
'opacity-0 translate-y-4': | |||
!isImageLoaded[product.id], | |||
'opacity-100 translate-y-0': | |||
isImageLoaded[product.id], | |||
}" | |||
@@ -232,13 +286,17 @@ | |||
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 group hover:border-cyan-400/30 transition-all duration-300" | |||
> | |||
<div class="col-span-1 flex flex-col gap-4"> | |||
<div class="flex flex-col gap-2 opacity-80 group-hover:opacity-100 transition-opacity duration-300"> | |||
<div | |||
class="flex flex-col gap-2 opacity-80 group-hover:opacity-100 transition-opacity duration-300" | |||
> | |||
<div | |||
v-for="feature in category.features" | |||
:key="feature" | |||
class="text-white text-sm md:text-base font-normal leading-tight flex gap-2 items-center group-hover:text-cyan-400 transition-colors duration-300" | |||
> | |||
<i class="icon-star text-sm group-hover:scale-110 transition-transform duration-300"></i> | |||
<i | |||
class="icon-star text-sm group-hover:scale-110 transition-transform duration-300" | |||
></i> | |||
<span>{{ feature }}</span> | |||
</div> | |||
</div> | |||
@@ -252,13 +310,17 @@ | |||
</div> | |||
</div> | |||
</div> | |||
<div class="w-32 h-32 md:w-44 md:h-44 relative overflow-hidden rounded-lg"> | |||
<div | |||
class="w-32 h-32 md:w-44 md:h-44 relative overflow-hidden rounded-lg" | |||
> | |||
<img | |||
:src="category.image" | |||
:alt="category.title" | |||
class="w-full h-full object-contain transition-transform duration-500 group-hover:scale-110" | |||
/> | |||
<div class="absolute inset-0 bg-gradient-to-t from-black/60 via-black/20 to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-300"></div> | |||
<div | |||
class="absolute inset-0 bg-gradient-to-t from-black/60 via-black/20 to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-300" | |||
></div> | |||
</div> | |||
</nuxt-link> | |||
</div> | |||
@@ -470,8 +532,8 @@ 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(); | |||
@@ -504,14 +566,6 @@ const { data: categoryData, error: categoryError } = await useFetch( | |||
); | |||
const categoryList = ref(categoryData.value?.data || []); | |||
// 调试信息 | |||
console.log("Carousel Data:", carouselData.value); | |||
console.log("Carousel Error:", carouselError.value); | |||
console.log("Usage Data:", usageData.value); | |||
console.log("Usage Error:", usageError.value); | |||
console.log("Category Data:", categoryData.value); | |||
console.log("Category Error:", categoryError.value); | |||
interface Product { | |||
id: number; | |||
title: string; | |||
@@ -543,7 +597,9 @@ const typedUsageList = computed(() => { | |||
// 计算当前激活的索引 | |||
const activeIndex = computed(() => { | |||
return typedUsageList.value.findIndex(item => item.id === activeUsageId.value); | |||
return typedUsageList.value.findIndex( | |||
(item) => item.id === activeUsageId.value | |||
); | |||
}); | |||
const activeProducts = computed(() => { | |||
@@ -629,11 +685,15 @@ const typedCategoryList = computed(() => { | |||
// SEO优化 | |||
useHead({ | |||
title: "Hanye - 首页", | |||
title: t("home.title") + " - Hanye", | |||
meta: [ | |||
{ | |||
name: "description", | |||
content: "基于 Nuxt3 的静态网站脚手架,支持多语言(中文、英文、日文)。", | |||
content: t("home.description"), | |||
}, | |||
{ | |||
name: "keywords", | |||
content: t("home.keywords"), | |||
}, | |||
], | |||
}); | |||
@@ -661,19 +721,19 @@ useHead({ | |||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); | |||
position: relative; | |||
overflow: hidden; | |||
&:hover { | |||
opacity: 1; | |||
transform: translateY(-2px); | |||
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.2); | |||
&::before { | |||
opacity: 1; | |||
} | |||
} | |||
&::before { | |||
content: ''; | |||
content: ""; | |||
position: absolute; | |||
inset: 0; | |||
background: linear-gradient(45deg, rgba(6, 182, 212, 0.1), transparent); | |||
@@ -797,7 +857,7 @@ useHead({ | |||
// 添加轮播图遮罩效果 | |||
.swiper-slide { | |||
&::before { | |||
content: ''; | |||
content: ""; | |||
position: absolute; | |||
top: 0; | |||
left: 0; | |||
@@ -836,7 +896,7 @@ useHead({ | |||
text-decoration: none; | |||
display: block; | |||
height: 100%; | |||
&:hover { | |||
text-decoration: none; | |||
} | |||
@@ -846,7 +906,7 @@ useHead({ | |||
z-index: 1; | |||
cursor: pointer; | |||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); | |||
&:hover { | |||
transform: translateY(-2px); | |||
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.2); |
@@ -18,12 +18,12 @@ | |||
<div | |||
class="justify-start text-white text-2xl font-normal md:text-4xl lg:text-6xl" | |||
> | |||
製品一覧 | |||
{{ $t("products.product_list") }} | |||
</div> | |||
<div | |||
class="text-white text-sm lg:text-lg font-normal leading-loose" | |||
> | |||
卓越した製品は、実績に裏打ちされた優れた技術と、<br />継続的な革新デザインとの融合により、生み出されます。 | |||
{{ $t("products.product_list_description") }} | |||
</div> | |||
</div> | |||
</div> | |||
@@ -38,11 +38,13 @@ | |||
<nuxt-link | |||
to="/" | |||
class="justify-start text-white/60 text-base font-normal" | |||
>ホーム</nuxt-link | |||
>{{ $t("common.home") }}</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 | |||
<nuxt-link | |||
to="/products" | |||
class="text-white text-base font-normal" | |||
>{{ $t("products.product_list") }}</nuxt-link | |||
> | |||
</div> | |||
</div> | |||
@@ -55,7 +57,9 @@ | |||
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="text-white text-3xl font-medium"> | |||
{{ $t("products.product_categories_title") }} | |||
</div> | |||
<div class="flex flex-col gap-4 w-fit"> | |||
<div | |||
v-for="category in categories" | |||
@@ -63,8 +67,9 @@ | |||
@click="handleCategoryFilter(category)" | |||
class="opacity-80 justify-start text-white text-base font-normal cursor-pointer hover:opacity-100 transition-all duration-300 px-4 py-2 rounded-lg inline-block" | |||
:class="{ | |||
'opacity-100 font-bold bg-gradient-to-r from-blue-700 to-blue-400 text-white border-0 shadow-lg scale-105 transition-all duration-300': selectedCategory === category, | |||
'hover:bg-zinc-800/50': selectedCategory !== category | |||
'opacity-100 font-bold bg-gradient-to-r from-blue-700 to-blue-400 text-white border-0 shadow-lg scale-105 transition-all duration-300': | |||
selectedCategory === category, | |||
'hover:bg-zinc-800/50': selectedCategory !== category, | |||
}" | |||
> | |||
{{ category }} | |||
@@ -73,7 +78,9 @@ | |||
</div> | |||
<div class="flex flex-col gap-4"> | |||
<div class="text-white text-3xl font-medium">用途分類</div> | |||
<div class="text-white text-3xl font-medium"> | |||
{{ $t("products.product_categories_usage") }} | |||
</div> | |||
<div class="flex flex-col gap-4 w-fit"> | |||
<div | |||
v-for="usage in usages" | |||
@@ -81,8 +88,9 @@ | |||
@click="handleUsageFilter(usage)" | |||
class="opacity-80 justify-start text-white text-base font-normal cursor-pointer hover:opacity-100 transition-all duration-300 px-4 py-2 rounded-lg inline-block" | |||
:class="{ | |||
'opacity-100 font-bold bg-gradient-to-r from-blue-700 to-blue-400 text-white border-0 shadow-lg scale-105 transition-all duration-300': selectedUsage === usage, | |||
'hover:bg-zinc-800/50': selectedUsage !== usage | |||
'opacity-100 font-bold bg-gradient-to-r from-blue-700 to-blue-400 text-white border-0 shadow-lg scale-105 transition-all duration-300': | |||
selectedUsage === usage, | |||
'hover:bg-zinc-800/50': selectedUsage !== usage, | |||
}" | |||
> | |||
{{ usage }} | |||
@@ -94,13 +102,25 @@ | |||
<div class="col-span-1 md:col-span-8"> | |||
<div class="flex flex-col gap-16"> | |||
<template v-for="category in categories" :key="category"> | |||
<div v-if="filteredProducts.filter(p => p.category === category).length > 0" class="flex flex-col gap-4"> | |||
<div | |||
v-if=" | |||
filteredProducts.filter((p) => p.category === category) | |||
.length > 0 | |||
" | |||
class="flex flex-col gap-4" | |||
> | |||
<div class="w-full text-white text-4xl font-normal mb-4"> | |||
{{ category }} | |||
</div> | |||
<transition-group name="fade" tag="div" class="w-full grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4"> | |||
<transition-group | |||
name="fade" | |||
tag="div" | |||
class="w-full grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4" | |||
> | |||
<nuxt-link | |||
v-for="product in filteredProducts.filter(p => p.category === category)" | |||
v-for="product in filteredProducts.filter( | |||
(p) => p.category === category | |||
)" | |||
:key="product.id" | |||
:to="`/products/${product.id}`" | |||
class="bg-zinc-900 rounded-lg transition-all duration-300 hover:bg-zinc-800 hover:scale-105 hover:shadow-lg hover:ring-2 hover:ring-blue-400" | |||
@@ -231,10 +251,12 @@ useHead({ | |||
</script> | |||
<style scoped> | |||
.fade-enter-active, .fade-leave-active { | |||
.fade-enter-active, | |||
.fade-leave-active { | |||
transition: opacity 0.3s; | |||
} | |||
.fade-enter-from, .fade-leave-to { | |||
.fade-enter-from, | |||
.fade-leave-to { | |||
opacity: 0; | |||
} | |||
</style> |
@@ -0,0 +1,238 @@ | |||
#!/usr/bin/env node | |||
/** | |||
* 图片本地化处理脚本 | |||
* 在静态站点生成后运行,将API返回的图片下载到本地 | |||
*/ | |||
import fs from 'fs' | |||
import path from 'path' | |||
import { createHash } from 'crypto' | |||
import { fileURLToPath } from 'url' | |||
// 获取当前文件的目录 | |||
const __filename = fileURLToPath(import.meta.url) | |||
const __dirname = path.dirname(__filename) | |||
const PROJECT_ROOT = path.resolve(__dirname, '..') | |||
/** | |||
* 下载并本地化图片 | |||
* @param imageUrl 原始图片URL | |||
* @param basePath 保存图片的基础路径 | |||
* @returns 本地化后的图片路径 | |||
*/ | |||
async function localizeImage(imageUrl, basePath = 'public/images/remote') { | |||
// 检查URL是否有效 | |||
if (!imageUrl || !imageUrl.startsWith('http')) { | |||
return imageUrl | |||
} | |||
try { | |||
// 创建保存目录 | |||
const fullBasePath = path.resolve(PROJECT_ROOT, basePath) | |||
if (!fs.existsSync(fullBasePath)) { | |||
fs.mkdirSync(fullBasePath, { recursive: true }) | |||
} | |||
// 生成唯一文件名(使用URL的MD5哈希) | |||
const urlHash = createHash('md5').update(imageUrl).digest('hex') | |||
const fileExt = path.extname(new URL(imageUrl).pathname) || '.jpg' | |||
const fileName = `${urlHash}${fileExt}` | |||
const filePath = path.join(fullBasePath, fileName) | |||
// 如果文件已存在,直接返回路径 | |||
if (fs.existsSync(filePath)) { | |||
return `/images/remote/${fileName}` | |||
} | |||
// 下载图片 | |||
const response = await fetch(imageUrl) | |||
if (!response.ok) { | |||
throw new Error(`下载图片失败: ${response.status} ${response.statusText}`) | |||
} | |||
const buffer = Buffer.from(await response.arrayBuffer()) | |||
fs.writeFileSync(filePath, buffer) | |||
console.log(`✅ 下载图片成功: ${imageUrl} -> /images/remote/${fileName}`) | |||
// 返回本地URL(相对于public目录) | |||
return `/images/remote/${fileName}` | |||
} catch (error) { | |||
console.error(`❌ 本地化图片失败 ${imageUrl}:`, error) | |||
return imageUrl // 失败时返回原始URL | |||
} | |||
} | |||
/** | |||
* 递归处理对象中的所有图片URL | |||
* @param data 需要处理的数据对象或数组 | |||
* @param imageFields 指定哪些字段包含图片URL | |||
* @returns 处理后的数据 | |||
*/ | |||
async function localizeImages( | |||
data, | |||
imageFields = ['image', 'imageUrl', 'thumbnail', 'cover', 'avatar', 'photo', 'src'] | |||
) { | |||
if (!data) return data | |||
// 处理数组 | |||
if (Array.isArray(data)) { | |||
return Promise.all(data.map(item => localizeImages(item, imageFields))) | |||
} | |||
// 处理对象 | |||
if (typeof data === 'object') { | |||
const result = { ...data } | |||
// 处理所有键 | |||
for (const [key, value] of Object.entries(result)) { | |||
// 如果是图片字段且值是字符串,本地化图片 | |||
if (imageFields.includes(key) && typeof value === 'string') { | |||
result[key] = await localizeImage(value) | |||
} | |||
// 递归处理嵌套对象或数组 | |||
else if (typeof value === 'object' && value !== null) { | |||
result[key] = await localizeImages(value, imageFields) | |||
} | |||
} | |||
return result | |||
} | |||
return data | |||
} | |||
/** | |||
* 从JSON文件提取图片URL并下载到本地 | |||
* @param jsonFilePath JSON文件路径 | |||
* @param imageFields 包含图片URL的字段名称数组 | |||
*/ | |||
async function extractAndDownloadImages( | |||
jsonFilePath, | |||
imageFields = ['image', 'imageUrl', 'thumbnail', 'cover', 'avatar', 'photo', 'src'] | |||
) { | |||
// 检查文件是否存在 | |||
if (!fs.existsSync(jsonFilePath)) { | |||
console.error(`文件不存在: ${jsonFilePath}`) | |||
return | |||
} | |||
try { | |||
// 读取JSON文件 | |||
const jsonData = JSON.parse(fs.readFileSync(jsonFilePath, 'utf-8')) | |||
// 递归查找所有图片URL | |||
const imageUrls = new Set() | |||
// 递归函数查找所有图片URL | |||
function findImageUrls(obj) { | |||
if (!obj) return | |||
if (Array.isArray(obj)) { | |||
obj.forEach(item => findImageUrls(item)) | |||
return | |||
} | |||
if (typeof obj === 'object') { | |||
for (const [key, value] of Object.entries(obj)) { | |||
if (imageFields.includes(key) && typeof value === 'string' && value.startsWith('http')) { | |||
imageUrls.add(value) | |||
} else if (typeof value === 'object' && value !== null) { | |||
findImageUrls(value) | |||
} | |||
} | |||
} | |||
} | |||
findImageUrls(jsonData) | |||
// 下载所有图片 | |||
console.log(`📋 在 ${jsonFilePath} 中找到 ${imageUrls.size} 个图片URL`) | |||
let downloadedCount = 0 | |||
for (const url of imageUrls) { | |||
try { | |||
const localUrl = await localizeImage(url) | |||
if (localUrl !== url) { | |||
downloadedCount++ | |||
} | |||
} catch (error) { | |||
console.error(`❌ 下载失败 ${url}:`, error) | |||
} | |||
} | |||
console.log(`📊 成功下载 ${downloadedCount}/${imageUrls.size} 个图片`) | |||
// 将本地化的图片URL写回到JSON文件 | |||
try { | |||
const localizedData = await localizeImages(jsonData) | |||
fs.writeFileSync(jsonFilePath, JSON.stringify(localizedData, null, 2)) | |||
console.log(`💾 已更新图片URL至本地路径: ${jsonFilePath}`) | |||
} catch (error) { | |||
console.error(`❌ 无法更新JSON文件 ${jsonFilePath}:`, error) | |||
} | |||
} catch (error) { | |||
console.error(`❌ 处理文件 ${jsonFilePath} 时出错:`, error) | |||
} | |||
} | |||
/** | |||
* 处理API响应文件中的图片 | |||
* @param outputDir 输出目录路径(通常是.output目录) | |||
*/ | |||
async function processApiResponseImages(outputDir = '.output') { | |||
console.log('🚀 开始处理API响应文件中的图片...') | |||
// 检查输出目录是否存在 | |||
if (!fs.existsSync(outputDir)) { | |||
console.error(`❌ 输出目录不存在: ${outputDir}`) | |||
return | |||
} | |||
const serverDir = path.join(outputDir, 'server/api') | |||
if (!fs.existsSync(serverDir)) { | |||
console.error(`❌ 服务器API目录不存在: ${serverDir}`) | |||
return | |||
} | |||
console.log(`🔍 扫描API响应文件: ${serverDir}`) | |||
// 递归函数查找所有JSON文件 | |||
async function processDirectory(dir) { | |||
const entries = fs.readdirSync(dir, { withFileTypes: true }) | |||
for (const entry of entries) { | |||
const fullPath = path.join(dir, entry.name) | |||
if (entry.isDirectory()) { | |||
await processDirectory(fullPath) | |||
} else if (entry.name.endsWith('.json')) { | |||
console.log(`📄 处理JSON文件: ${fullPath}`) | |||
await extractAndDownloadImages(fullPath) | |||
} | |||
} | |||
} | |||
await processDirectory(serverDir) | |||
console.log('✅ 所有API响应文件处理完成') | |||
} | |||
// 确保脚本能单独运行 | |||
async function main() { | |||
console.log('===== 🖼️ 开始图片本地化处理 =====') | |||
try { | |||
// 默认的输出目录是.output | |||
const outputDir = process.argv[2] || '.output' | |||
await processApiResponseImages(outputDir) | |||
console.log('===== ✅ 图片本地化处理完成 =====') | |||
process.exit(0) | |||
} catch (error) { | |||
console.error('❌ 图片本地化处理失败:', error) | |||
process.exit(1) | |||
} | |||
} | |||
main() |
@@ -1,14 +1,14 @@ | |||
/** | |||
* 首页轮播图数据接口 | |||
* @returns 轮播图数据列表 | |||
* | |||
* | |||
* 替换真实接口说明: | |||
* 1. 替换真实接口时,需要修改以下内容: | |||
* - 将模拟数据替换为真实接口调用 | |||
* - 添加错误处理 | |||
* - 添加接口参数处理 | |||
* - 添加数据转换逻辑 | |||
* | |||
* | |||
* 2. 真实接口示例: | |||
* const response = await $fetch('https://api.example.com/carousel', { | |||
* method: 'GET', | |||
@@ -21,7 +21,7 @@ | |||
* limit: 10 | |||
* } | |||
* }) | |||
* | |||
* | |||
* 3. 错误处理示例: | |||
* try { | |||
* const response = await $fetch('...') | |||
@@ -37,7 +37,7 @@ | |||
* message: '获取轮播图数据失败' | |||
* } | |||
* } | |||
* | |||
* | |||
* 4. 数据转换示例: | |||
* const transformedData = response.data.map(item => ({ | |||
* id: item.id, | |||
@@ -45,7 +45,7 @@ | |||
* image: item.imageUrl, | |||
* link: `/products/${item.productId}` | |||
* })) | |||
* | |||
* | |||
* 5. 接口参数处理示例: | |||
* const query = getQuery(event) | |||
* const page = Number(query.page) || 1 | |||
@@ -56,27 +56,44 @@ export default defineEventHandler(async () => { | |||
const carouselList = [ | |||
{ | |||
id: 1, | |||
title: '轮播图1', | |||
image: 'https://picsum.photos/1920/1080?random=1', | |||
link: '/products/1' | |||
image: "", | |||
description: ` | |||
<div class="rounded border border-white w-11 h-6 leading-none justify-center flex items-center text-white text-sm font-normal">SSD</div> | |||
<div class="justify-center"> | |||
<span class="text-white text-6xl font-normal leading-[78px]">新技術</span> | |||
<span class="text-white text-6xl font-normal leading-[78px]">3D NAND TLC</span> | |||
<br /> | |||
<span class="text-white text-6xl font-normal leading-[78px]">フラッシュ採用、 </span><br /> | |||
<span class="text-cyan-400 text-6xl font-normal leading-[78px]">信頼性が高く耐久性に優<br />れている</span> | |||
</div> | |||
<div class="flex flex-col gap-2 mt-4"> | |||
<div class="flex items-center gap-2 text-stone-50 text-xl font-normal"> | |||
<div class="w-2 h-2 bg-white rounded-full"></div> | |||
ストレスのないゲーム体験をお楽しみください | |||
</div> | |||
<div class="flex items-center gap-2 text-stone-50 text-xl font-normal"> | |||
<div class="w-2 h-2 bg-white rounded-full"></div> | |||
パソコンの起動時間が劇的に速くなった! | |||
</div> | |||
</div> | |||
`, | |||
link: "/products/1", | |||
}, | |||
{ | |||
id: 2, | |||
title: '轮播图2', | |||
image: 'https://picsum.photos/1920/1080?random=2', | |||
link: '/products/2' | |||
image: "", | |||
link: "/products/2", | |||
}, | |||
{ | |||
id: 3, | |||
title: '轮播图3', | |||
image: 'https://picsum.photos/1920/1080?random=3', | |||
link: '/products/3' | |||
} | |||
] | |||
image: "", | |||
link: "/products/3", | |||
}, | |||
]; | |||
return { | |||
code: 200, | |||
data: carouselList, | |||
message: '获取轮播图数据成功' | |||
} | |||
}) | |||
message: "获取轮播图数据成功", | |||
}; | |||
}); |
@@ -28,6 +28,7 @@ export default defineEventHandler(async (event) => { | |||
"https://picsum.photos/400/400?random=12", | |||
"https://picsum.photos/400/400?random=13", | |||
], | |||
summary:'摘要', | |||
description: "高性能2.5インチSSD、読み書き速度が速く、信頼性が高い。最新のNANDフラッシュ技術を採用し、高速なデータ転送と安定した性能を実現。PCの起動時間を大幅に短縮し、アプリケーションの読み込みを高速化。耐久性に優れ、長時間の使用にも耐えられる設計。", | |||
}, | |||
{ |
@@ -1,4 +1,8 @@ | |||
export default defineEventHandler(async (event) => { | |||
// 获取查询参数中的语言设置 | |||
const query = getQuery(event); | |||
const lang = query.lang || 'ja'; // 默认使用日语 | |||
// 模拟数据 | |||
const mockData = { | |||
code: 200, | |||
@@ -6,55 +10,55 @@ export default defineEventHandler(async (event) => { | |||
data: [ | |||
{ | |||
id: 1, | |||
title: "PC高速化", | |||
description: "2.5-inch SSD & M.2 SSD", | |||
title: lang === 'zh' ? "PC高速化" : "PC高速化", | |||
description: lang === 'zh' ? "2.5寸SSD和M.2 SSD" : "2.5-inch SSD & M.2 SSD", | |||
features: [ | |||
"PC高速化", | |||
"起動・読込 高速" | |||
lang === 'zh' ? "电脑加速" : "PC高速化", | |||
lang === 'zh' ? "启动和加载速度快" : "起動・読込 高速" | |||
], | |||
image: "https://picsum.photos/seed/ssd/400/400", | |||
link: "/products" | |||
}, | |||
{ | |||
id: 2, | |||
title: "データ保存", | |||
description: "HDD & SSD", | |||
title: lang === 'zh' ? "数据存储" : "データ保存", | |||
description: lang === 'zh' ? "HDD和SSD" : "HDD & SSD", | |||
features: [ | |||
"大容量保存", | |||
"データバックアップ" | |||
lang === 'zh' ? "大容量存储" : "大容量保存", | |||
lang === 'zh' ? "数据备份" : "データバックアップ" | |||
], | |||
image: "https://picsum.photos/seed/hdd/400/400", | |||
link: "/products" | |||
}, | |||
{ | |||
id: 3, | |||
title: "メモリ拡張", | |||
description: "DDR4 & DDR5", | |||
title: lang === 'zh' ? "内存扩展" : "メモリ拡張", | |||
description: lang === 'zh' ? "DDR4和DDR5" : "DDR4 & DDR5", | |||
features: [ | |||
"メモリ増設", | |||
"パフォーマンス向上" | |||
lang === 'zh' ? "内存升级" : "メモリ増設", | |||
lang === 'zh' ? "性能提升" : "パフォーマンス向上" | |||
], | |||
image: "https://picsum.photos/seed/ram/400/400", | |||
link: "/products" | |||
}, | |||
{ | |||
id: 4, | |||
title: "周辺機器", | |||
description: "USB & Thunderbolt", | |||
title: lang === 'zh' ? "外围设备" : "周辺機器", | |||
description: lang === 'zh' ? "USB和雷电接口" : "USB & Thunderbolt", | |||
features: [ | |||
"高速転送", | |||
"多機能接続" | |||
lang === 'zh' ? "高速传输" : "高速転送", | |||
lang === 'zh' ? "多功能连接" : "多機能接続" | |||
], | |||
image: "https://picsum.photos/seed/usb/400/400", | |||
link: "/products" | |||
}, | |||
{ | |||
id: 5, | |||
title: "冷却システム", | |||
description: "CPU & GPU Cooler", | |||
title: lang === 'zh' ? "散热系统" : "冷却システム", | |||
description: lang === 'zh' ? "CPU和GPU散热器" : "CPU & GPU Cooler", | |||
features: [ | |||
"効率的冷却", | |||
"静音設計" | |||
lang === 'zh' ? "高效散热" : "効率的冷却", | |||
lang === 'zh' ? "静音设计" : "静音設計" | |||
], | |||
image: "https://picsum.photos/seed/cooler/400/400", | |||
link: "/products" |
@@ -2,12 +2,16 @@ | |||
* 按用途产品展示接口 | |||
* @returns 按用途分类的产品数据 | |||
*/ | |||
export default defineEventHandler(async () => { | |||
export default defineEventHandler(async (event) => { | |||
// 获取查询参数中的语言设置 | |||
const query = getQuery(event); | |||
const lang = query.lang || 'ja'; // 默认使用日语 | |||
// 模拟数据 | |||
const usageList = [ | |||
{ | |||
id: 1, | |||
name: '外付けストレージ化', | |||
name: lang === 'zh' ? '外接存储转换' : '外付けストレージ化', | |||
products: [ | |||
{ | |||
id: 1, | |||
@@ -48,7 +52,7 @@ export default defineEventHandler(async () => { | |||
}, | |||
{ | |||
id: 2, | |||
name: 'PC高速化', | |||
name: lang === 'zh' ? 'PC加速' : 'PC高速化', | |||
products: [ | |||
{ | |||
id: 4, | |||
@@ -75,7 +79,7 @@ export default defineEventHandler(async () => { | |||
}, | |||
{ | |||
id: 3, | |||
name: 'データバックアップ', | |||
name: lang === 'zh' ? '数据备份' : 'データバックアップ', | |||
products: [ | |||
{ | |||
id: 7, | |||
@@ -105,6 +109,6 @@ export default defineEventHandler(async () => { | |||
return { | |||
code: 200, | |||
data: usageList, | |||
message: '获取按用途产品数据成功' | |||
message: lang === 'zh' ? '获取按用途产品数据成功' : '用途別製品データの取得に成功しました' | |||
} | |||
}) |
@@ -0,0 +1,169 @@ | |||
import fs from 'fs' | |||
import path from 'path' | |||
import { localizeImage } from './image-localizer' | |||
/** | |||
* 从JSON文件提取图片URL并下载到本地 | |||
* @param jsonFilePath JSON文件路径 | |||
* @param imageFields 包含图片URL的字段名称数组 | |||
*/ | |||
async function extractAndDownloadImages( | |||
jsonFilePath: string, | |||
imageFields = ['image', 'imageUrl', 'thumbnail', 'cover', 'avatar', 'photo', 'src'] | |||
) { | |||
// 检查文件是否存在 | |||
if (!fs.existsSync(jsonFilePath)) { | |||
console.error(`文件不存在: ${jsonFilePath}`) | |||
return | |||
} | |||
try { | |||
// 读取JSON文件 | |||
const jsonData = JSON.parse(fs.readFileSync(jsonFilePath, 'utf-8')) | |||
// 递归查找所有图片URL | |||
const imageUrls = new Set<string>() | |||
// 递归函数查找所有图片URL | |||
function findImageUrls(obj: any) { | |||
if (!obj) return | |||
if (Array.isArray(obj)) { | |||
obj.forEach(item => findImageUrls(item)) | |||
return | |||
} | |||
if (typeof obj === 'object') { | |||
for (const [key, value] of Object.entries(obj)) { | |||
if (imageFields.includes(key) && typeof value === 'string' && value.startsWith('http')) { | |||
imageUrls.add(value) | |||
} else if (typeof value === 'object' && value !== null) { | |||
findImageUrls(value) | |||
} | |||
} | |||
} | |||
} | |||
findImageUrls(jsonData) | |||
// 下载所有图片 | |||
console.log(`在 ${jsonFilePath} 中找到 ${imageUrls.size} 个图片URL`) | |||
let downloadedCount = 0 | |||
for (const url of imageUrls) { | |||
try { | |||
const localUrl = await localizeImage(url) | |||
if (localUrl !== url) { | |||
downloadedCount++ | |||
console.log(`已下载: ${url} -> ${localUrl}`) | |||
} | |||
} catch (error) { | |||
console.error(`下载失败 ${url}:`, error) | |||
} | |||
} | |||
console.log(`成功下载 ${downloadedCount}/${imageUrls.size} 个图片`) | |||
// 将本地化的图片URL写回到JSON文件 | |||
try { | |||
const localizedData = await localizeImages(jsonData) | |||
fs.writeFileSync(jsonFilePath, JSON.stringify(localizedData, null, 2)) | |||
console.log(`已更新图片URL至本地路径: ${jsonFilePath}`) | |||
} catch (error) { | |||
console.error(`无法更新JSON文件 ${jsonFilePath}:`, error) | |||
} | |||
} catch (error) { | |||
console.error(`处理文件 ${jsonFilePath} 时出错:`, error) | |||
} | |||
} | |||
/** | |||
* 递归处理对象中的所有图片URL | |||
* 此函数是image-localizer.ts中同名函数的复制,以避免在命令行运行时的循环依赖问题 | |||
*/ | |||
async function localizeImages( | |||
data: any, | |||
imageFields = ['image', 'imageUrl', 'thumbnail', 'cover', 'avatar', 'photo', 'src'] | |||
): Promise<any> { | |||
if (!data) return data | |||
// 处理数组 | |||
if (Array.isArray(data)) { | |||
return Promise.all(data.map(item => localizeImages(item, imageFields))) | |||
} | |||
// 处理对象 | |||
if (typeof data === 'object') { | |||
const result = { ...data } | |||
// 处理所有键 | |||
for (const [key, value] of Object.entries(result)) { | |||
// 如果是图片字段且值是字符串,本地化图片 | |||
if (imageFields.includes(key) && typeof value === 'string') { | |||
result[key] = await localizeImage(value) | |||
} | |||
// 递归处理嵌套对象或数组 | |||
else if (typeof value === 'object' && value !== null) { | |||
result[key] = await localizeImages(value, imageFields) | |||
} | |||
} | |||
return result | |||
} | |||
return data | |||
} | |||
/** | |||
* 处理API响应文件中的图片 | |||
* @param outputDir 输出目录路径(通常是.output目录) | |||
*/ | |||
export async function processApiResponseImages(outputDir = '.output') { | |||
console.log('开始处理API响应文件中的图片...') | |||
// 检查输出目录是否存在 | |||
if (!fs.existsSync(outputDir)) { | |||
console.error(`输出目录不存在: ${outputDir}`) | |||
return | |||
} | |||
const serverDir = path.join(outputDir, 'server/api') | |||
if (!fs.existsSync(serverDir)) { | |||
console.error(`服务器API目录不存在: ${serverDir}`) | |||
return | |||
} | |||
console.log(`扫描API响应文件: ${serverDir}`) | |||
// 递归函数查找所有JSON文件 | |||
async function processDirectory(dir: string) { | |||
const entries = fs.readdirSync(dir, { withFileTypes: true }) | |||
for (const entry of entries) { | |||
const fullPath = path.join(dir, entry.name) | |||
if (entry.isDirectory()) { | |||
await processDirectory(fullPath) | |||
} else if (entry.name.endsWith('.json')) { | |||
console.log(`处理JSON文件: ${fullPath}`) | |||
await extractAndDownloadImages(fullPath) | |||
} | |||
} | |||
} | |||
await processDirectory(serverDir) | |||
console.log('所有API响应文件处理完成') | |||
} | |||
// 允许通过命令行直接运行 | |||
// 兼容 ESM 和 CommonJS | |||
if (typeof require !== 'undefined' && require.main === module) { | |||
const outputDir = process.argv[2] || '.output' | |||
processApiResponseImages(outputDir) | |||
.then(() => console.log('图片本地化处理完成')) | |||
.catch(error => console.error('图片本地化处理失败:', error)) | |||
} | |||
// 导出主函数,供脚本调用 | |||
export default processApiResponseImages |
@@ -0,0 +1,89 @@ | |||
import fs from 'fs' | |||
import path from 'path' | |||
import { createHash } from 'crypto' | |||
/** | |||
* 下载并本地化图片 | |||
* @param imageUrl 原始图片URL | |||
* @param basePath 保存图片的基础路径 | |||
* @returns 本地化后的图片路径 | |||
*/ | |||
export async function localizeImage(imageUrl: string, basePath = 'public/images/remote'): Promise<string> { | |||
// 检查URL是否有效 | |||
if (!imageUrl || !imageUrl.startsWith('http')) { | |||
return imageUrl | |||
} | |||
try { | |||
// 创建保存目录 | |||
const fullBasePath = path.resolve(process.cwd(), basePath) | |||
if (!fs.existsSync(fullBasePath)) { | |||
fs.mkdirSync(fullBasePath, { recursive: true }) | |||
} | |||
// 生成唯一文件名(使用URL的MD5哈希) | |||
const urlHash = createHash('md5').update(imageUrl).digest('hex') | |||
const fileExt = path.extname(new URL(imageUrl).pathname) || '.jpg' | |||
const fileName = `${urlHash}${fileExt}` | |||
const filePath = path.join(fullBasePath, fileName) | |||
// 如果文件已存在,直接返回路径 | |||
if (fs.existsSync(filePath)) { | |||
return `/images/remote/${fileName}` | |||
} | |||
// 下载图片 | |||
const response = await fetch(imageUrl) | |||
if (!response.ok) { | |||
throw new Error(`下载图片失败: ${response.status} ${response.statusText}`) | |||
} | |||
const buffer = Buffer.from(await response.arrayBuffer()) | |||
fs.writeFileSync(filePath, buffer) | |||
// 返回本地URL(相对于public目录) | |||
return `/images/remote/${fileName}` | |||
} catch (error) { | |||
console.error(`本地化图片失败 ${imageUrl}:`, error) | |||
return imageUrl // 失败时返回原始URL | |||
} | |||
} | |||
/** | |||
* 递归处理对象中的所有图片URL | |||
* @param data 需要处理的数据对象或数组 | |||
* @param imageFields 指定哪些字段包含图片URL | |||
* @returns 处理后的数据 | |||
*/ | |||
export async function localizeImages( | |||
data: any, | |||
imageFields = ['image', 'imageUrl', 'thumbnail', 'cover', 'avatar', 'photo', 'src'] | |||
): Promise<any> { | |||
if (!data) return data | |||
// 处理数组 | |||
if (Array.isArray(data)) { | |||
return Promise.all(data.map(item => localizeImages(item, imageFields))) | |||
} | |||
// 处理对象 | |||
if (typeof data === 'object') { | |||
const result = { ...data } | |||
// 处理所有键 | |||
for (const [key, value] of Object.entries(result)) { | |||
// 如果是图片字段且值是字符串,本地化图片 | |||
if (imageFields.includes(key) && typeof value === 'string') { | |||
result[key] = await localizeImage(value) | |||
} | |||
// 递归处理嵌套对象或数组 | |||
else if (typeof value === 'object' && value !== null) { | |||
result[key] = await localizeImages(value, imageFields) | |||
} | |||
} | |||
return result | |||
} | |||
return data | |||
} |