本次提交主要进行了以下修改: 1. 新增隐私政策和客户支持页面,提供用户相关信息。 2. 更新了路由配置,确保新页面能够正确渲染。 3. 在国际化配置中添加了隐私政策和支持页面的翻译内容,提升多语言支持。 4. 优化了页面样式和结构,提升用户体验。 这些改动旨在丰富网站内容,增强用户对隐私和支持信息的获取。master
@@ -57,6 +57,14 @@ html[lang="en"] body { | |||
.prose { | |||
color: rgba(255, 255, 255, 0.6); | |||
line-height: 1.625; | |||
blockquote { | |||
border-left-color: var(--color-primary); | |||
p { | |||
margin: 0; | |||
} | |||
} | |||
} | |||
.prose h1 { | |||
@@ -196,7 +204,7 @@ html[lang="en"] body { | |||
.prose tr:nth-child(even) { | |||
background-color: rgba(39, 39, 42, 0.3); | |||
} | |||
.prose hr { |
@@ -22,7 +22,7 @@ | |||
<!-- 产品分类链接 --> | |||
<div class="hidden lg:block"> | |||
<h3 | |||
class="w-36 justify-start text-zinc-300 text-xl font-normal leading-snug mb-4" | |||
class="justify-start text-zinc-300 text-xl font-normal leading-snug mb-4" | |||
> | |||
{{ t("common.footer.productsLinks.title") }} | |||
</h3> | |||
@@ -40,7 +40,7 @@ | |||
<!-- 网站快捷链接 --> | |||
<div class="hidden lg:block"> | |||
<h3 | |||
class="w-36 justify-start text-zinc-300 text-xl font-normal leading-snug mb-4" | |||
class="justify-start text-zinc-300 text-xl font-normal leading-snug mb-4" | |||
> | |||
{{ t("common.footer.websiteLinks.title") }} | |||
</h3> | |||
@@ -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" | |||
class="justify-start text-zinc-300 text-xl font-normal leading-snug mb-4" | |||
> | |||
{{ t("common.footer.quickLinks.title") }} | |||
{{ t("common.footer.legalAndSupport.title") }} | |||
</h3> | |||
<ul class="space-y-4"> | |||
<li v-for="item in menuHomeItems" :key="item.path"> | |||
<li v-for="item in menuLegalAndSupportItems" :key="item.path"> | |||
<NuxtLink | |||
:to="item.path" | |||
class="text-zinc-500 text-sm font-normal hover:text-white transition" | |||
@@ -171,20 +171,19 @@ const menuWebsiteItems = computed(() => [ | |||
}, | |||
]); | |||
const menuHomeItems = computed(() => [ | |||
// 这里可以根据需要添加首页快捷链接 | |||
const menuLegalAndSupportItems = computed(() => [ | |||
{ | |||
label: "common.footer.quickLinks.support", | |||
label: "common.footer.legalAndSupport.support", | |||
path: | |||
locale.value === defaultLocale ? "/support" : `/${locale.value}/support`, | |||
}, | |||
{ | |||
label: "common.footer.quickLinks.privacy", | |||
label: "common.footer.legalAndSupport.privacy", | |||
path: | |||
locale.value === defaultLocale ? "/privacy" : `/${locale.value}/privacy`, | |||
}, | |||
{ | |||
label: "common.footer.quickLinks.terms", | |||
label: "common.footer.legalAndSupport.terms", | |||
path: locale.value === defaultLocale ? "/terms" : `/${locale.value}/terms`, | |||
}, | |||
]); |
@@ -50,16 +50,16 @@ | |||
<div | |||
v-if="item.isDropdown && openDropdown === item.label" | |||
@mouseenter="handleMouseEnter(item.label)" | |||
class="absolute left-0 top-full mt-4 w-max min-w-[700px] bg-slate-900 rounded-none border-none shadow-none p-0 z-10 grid grid-cols-2 gap-0 transition-all duration-300" | |||
class="absolute left-0 top-full mt-4 w-[300px] bg-slate-900 rounded-none border-none shadow-none p-0 z-10 gap-0 transition-all duration-300" | |||
> | |||
<div | |||
v-for="(section, index) in item.children" | |||
:key="index" | |||
class="bg-slate-900 rounded-none p-6 flex flex-col gap-1" | |||
> | |||
<h3 class="text-base font-medium text-white mb-2"> | |||
<!-- <h3 class="text-base font-medium text-white mb-2"> | |||
{{ t(section.title) }} | |||
</h3> | |||
</h3> --> | |||
<ul class="flex flex-col gap-1"> | |||
<li v-for="link in section.items" :key="link.path"> | |||
<nuxt-link | |||
@@ -492,13 +492,13 @@ const menuItems = computed(() => { | |||
path: `${prefix}${category.path}`, | |||
})), | |||
}, | |||
{ | |||
title: "common.byUsage", | |||
items: productUsages.value.map((usage: any) => ({ | |||
...usage, | |||
path: `${prefix}${usage.path}`, | |||
})), | |||
}, | |||
// { | |||
// title: "common.byUsage", | |||
// items: productUsages.value.map((usage: any) => ({ | |||
// ...usage, | |||
// path: `${prefix}${usage.path}`, | |||
// })), | |||
// }, | |||
], | |||
}, | |||
{ label: "common.faq", path: `${prefix}/faq` }, |
@@ -0,0 +1,79 @@ | |||
--- | |||
title: Privacy Policy | |||
--- | |||
# Privacy Policy | |||
> Last updated: 2025-05-12 | |||
## 1. Introduction | |||
Welcome to Hanye's Privacy Policy. This document explains how we collect, use, and protect your personal information when you use our website. | |||
## 2. Information We Collect | |||
### 2.1 Personal Information | |||
We collect the following personal information: | |||
- **Name**: Your full name | |||
- **Email address**: Your contact email | |||
- **Contact information**: Phone number and address | |||
- **Usage data**: How you interact with our website | |||
### 2.2 Non-Personal Information | |||
We also collect non-personal information: | |||
- **Browser type**: The web browser you use | |||
- **Device information**: Your device type and operating system | |||
- **IP address**: Your internet protocol address | |||
- **Cookies**: Small data files stored on your device | |||
## 3. How We Use Your Information | |||
We use your information to: | |||
- Provide and maintain our services | |||
- Improve user experience | |||
- Send important updates | |||
- Respond to your inquiries | |||
- Analyze website usage | |||
## 4. Data Protection | |||
We implement the following security measures: | |||
- **Encryption**: All data is encrypted in transit and at rest | |||
- **Secure servers**: Protected by industry-standard security | |||
- **Regular assessments**: Continuous security monitoring | |||
- **Access controls**: Strict access management | |||
## 5. Your Rights | |||
You have the following rights: | |||
- Access your personal data | |||
- Correct inaccurate data | |||
- Request data deletion | |||
- Object to data processing | |||
- Data portability | |||
## 6. Cookies | |||
We use cookies to: | |||
- Improve website functionality | |||
- Remember your preferences | |||
- Analyze website traffic | |||
- Enhance user experience | |||
## 7. Third-Party Services | |||
We may use third-party services that collect information. These services have their own privacy policies. | |||
## 8. Children's Privacy | |||
Our services are not intended for children under 13. We do not knowingly collect personal information from children. | |||
## 9. Changes to This Policy | |||
We may update this privacy policy periodically. We will notify you of any changes by posting the new policy on this page. | |||
## 10. Contact Us | |||
If you have questions about this privacy policy, please contact us: | |||
**Email:** [hanye@hanye.cn] |
@@ -0,0 +1,66 @@ | |||
--- | |||
title: Support | |||
--- | |||
# Customer Support | |||
> Last updated: 2025-05-12 | |||
## 1. About Support | |||
Welcome to Hanye's Customer Support page. This page provides support information about our products and services. | |||
## 2. Frequently Asked Questions | |||
### 2.1 Orders and Shipping | |||
- **How to track orders**: You can track your order status through the link in your confirmation email | |||
- **Shipping time**: Items are typically shipped within 3-5 business days | |||
- **International shipping**: We currently ship to many countries worldwide | |||
- **Return policy**: Items can be returned within 30 days of receipt | |||
### 2.2 About Products | |||
- **Product warranty**: All products come with a 1-year warranty | |||
- **Product details**: Detailed specifications can be found on each product page | |||
- **Compatibility**: Compatibility information is listed on the product detail pages | |||
## 3. Contact Methods | |||
You can contact our support team using the following methods: | |||
### 3.1 Email | |||
Send an email to support@hanye.cn and we'll respond within 24 hours. | |||
### 3.2 Phone | |||
Call us during business hours (Monday-Friday, 9:00-18:00 JST) at: | |||
+81-XX-XXXX-XXXX | |||
### 3.3 Live Chat | |||
Click on the chat icon at the bottom right of our website to chat with a support representative in real-time during business hours. | |||
## 4. Troubleshooting | |||
Solutions to common issues: | |||
### 4.1 If a Product Is Not Working Properly | |||
- Check if the power is properly connected | |||
- Verify that the firmware is updated to the latest version | |||
- Try restarting the product | |||
### 4.2 Account Issues | |||
- If you need to reset your password, click "Forgot Password" on the login page | |||
- To update account information, go to the "My Account" section after logging in | |||
## 5. Support Resources | |||
### 5.1 Manuals and Guides | |||
Product manuals and user guides can be downloaded from product pages. | |||
### 5.2 Video Tutorials | |||
Our YouTube channel features detailed video tutorials on how to use our products. | |||
### 5.3 Community Forum | |||
You can also utilize our community forum where users can exchange information. | |||
## 6. Feedback | |||
Your feedback is extremely important for improving our services. Please send your opinions and suggestions about our products and services to feedback@hanye.cn. |
@@ -0,0 +1,91 @@ | |||
--- | |||
title: Terms of Service | |||
--- | |||
# Terms of Service | |||
> Last updated: 2025-05-12 | |||
## 1. Introduction | |||
Thank you for using Hanye's website (the "Site"). These Terms of Service establish the conditions for using the Site. By using the Site, you are considered to have agreed to these terms. | |||
## 2. Use of Services | |||
### 2.1 Eligibility | |||
To use the services on the Site, you must meet the following conditions: | |||
- Be at least 13 years old | |||
- Have a valid email address | |||
- Agree to these Terms of Service | |||
### 2.2 Accounts | |||
Some services may require account creation. When creating an account: | |||
- Provide accurate and current information | |||
- Maintain the confidentiality of your password | |||
- Take responsibility for all activities on your account | |||
## 3. Intellectual Property Rights | |||
### 3.1 Ownership | |||
The Site and its content (text, images, logos, designs, etc.) belong to Hanye or its licensors and are protected by copyright laws and international treaties. | |||
### 3.2 Usage Restrictions | |||
The content on the Site may only be used for personal, non-commercial purposes. The following activities are prohibited: | |||
- Reproducing, modifying, or distributing content | |||
- Commercial use of content | |||
- Downloading or saving content without written permission from us | |||
## 4. Prohibited Activities | |||
The following activities are prohibited when using the Site: | |||
- Engaging in illegal or harmful activities | |||
- Harassing or threatening other users | |||
- Posting false or misleading information | |||
- Attempting to breach the Site's security | |||
- Distributing viruses or malware | |||
- Engaging in activities that interfere with the normal functioning of the Site | |||
## 5. Purchases and Payments | |||
### 5.1 Product Information | |||
The product information displayed on the Site is provided as accurately as possible, but errors may occur. Prices and availability may change without notice. | |||
### 5.2 Orders and Confirmation | |||
After you place an order, a confirmation email will be sent. However, we reserve the right to cancel orders in the following cases: | |||
- If the product is out of stock | |||
- If we cannot verify your payment information | |||
- If we suspect fraudulent activity | |||
### 5.3 Payments | |||
Payments must be made using the methods specified on the Site. You guarantee that the payment information you provide is accurate. | |||
## 6. Limitation of Liability | |||
The Site and its content are provided "as is," and we make no explicit or implicit guarantees regarding: | |||
- Uninterrupted service | |||
- Freedom from errors | |||
- Suitability for specific purposes | |||
- Complete security | |||
To the maximum extent permitted by law, we are not responsible for damages arising from: | |||
- Use or inability to use the Site | |||
- Unauthorized access by third parties | |||
- Loss or corruption of data | |||
## 7. Dispute Resolution | |||
### 7.1 Governing Law | |||
These Terms of Service are governed by and interpreted according to the laws of Japan. | |||
### 7.2 Dispute Resolution | |||
All disputes related to these Terms of Service shall be subject to the exclusive jurisdiction of the Tokyo District Court as the court of first instance. | |||
## 8. Changes to Terms | |||
We reserve the right to change these Terms of Service at any time. If changes are made, we will notify you by posting the new Terms on the Site. Your continued use of the Site after changes constitutes acceptance of the new Terms. | |||
## 9. Contact Us | |||
If you have questions about these Terms of Service, please contact us at: | |||
**Email:** [terms@hanye.cn] |
@@ -0,0 +1,79 @@ | |||
--- | |||
title: プライバシーポリシー | |||
--- | |||
# プライバシーポリシー | |||
> 最終更新日: 2025年5月12日 | |||
## 1. はじめに | |||
Hanyeのプライバシーポリシーへようこそ。このドキュメントでは、当ウェブサイトをご利用いただく際に、お客様の個人情報をどのように収集、使用、保護するかについて説明します。 | |||
## 2. 収集する情報 | |||
### 2.1 個人情報 | |||
以下の個人情報を収集します: | |||
- **氏名**:あなたの氏名 | |||
- **メールアドレス**:連絡先メール | |||
- **連絡先情報**:電話番号と住所 | |||
- **利用データ**:当ウェブサイトでのあなたの操作方法 | |||
### 2.2 非個人情報 | |||
また、以下の非個人情報も収集します: | |||
- **ブラウザの種類**:使用するウェブブラウザ | |||
- **デバイス情報**:デバイスの種類とオペレーティングシステム | |||
- **IPアドレス**:あなたのインターネットプロトコルアドレス | |||
- **クッキー**:お使いのデバイスに保存される小さなデータファイル | |||
## 3. 情報の使用方法 | |||
お客様の情報を以下の目的で使用します: | |||
- サービスの提供と維持 | |||
- ユーザー体験の向上 | |||
- 重要な更新情報の送信 | |||
- お問い合わせへの対応 | |||
- ウェブサイト利用状況の分析 | |||
## 4. データ保護 | |||
以下のセキュリティ対策を実施しています: | |||
- **暗号化**:すべてのデータは転送中および保存中に暗号化されます | |||
- **セキュアサーバー**:業界標準のセキュリティで保護されています | |||
- **定期的な評価**:継続的なセキュリティ監視 | |||
- **アクセス制御**:厳格なアクセス管理 | |||
## 5. お客様の権利 | |||
以下の権利があります: | |||
- 個人データへのアクセス | |||
- 不正確なデータの修正 | |||
- データ削除のリクエスト | |||
- データ処理への異議申し立て | |||
- データポータビリティ | |||
## 6. クッキー | |||
クッキーを以下の目的で使用します: | |||
- ウェブサイト機能の向上 | |||
- お客様の設定の記憶 | |||
- ウェブサイトトラフィックの分析 | |||
- ユーザー体験の向上 | |||
## 7. 第三者サービス | |||
情報を収集する第三者サービスを使用する場合があります。これらのサービスには独自のプライバシーポリシーがあります。 | |||
## 8. 子どものプライバシー | |||
当サービスは13歳未満の子ども向けではありません。子どもから故意に個人情報を収集することはありません。 | |||
## 9. ポリシーの変更 | |||
このプライバシーポリシーは定期的に更新される場合があります。変更があった場合は、このページに新しいポリシーを掲載してお知らせします。 | |||
## 10. お問い合わせ | |||
このプライバシーポリシーについてご質問がある場合は、以下までご連絡ください: | |||
**メール:** [hanye@hanye.cn] |
@@ -0,0 +1,66 @@ | |||
--- | |||
title: サポート | |||
--- | |||
# カスタマーサポート | |||
> 最終更新日: 2025年5月12日 | |||
## 1. サポートについて | |||
Hanyeのカスタマーサポートページへようこそ。このページでは、当社の製品やサービスに関するサポート情報を提供しています。 | |||
## 2. よくある質問 | |||
### 2.1 注文と配送 | |||
- **注文の追跡方法**: 確認メールに記載されたリンクから注文状況を追跡できます | |||
- **配送にかかる時間**: 通常3〜5営業日以内に発送されます | |||
- **国際配送**: 現在、世界中の多くの国へ配送しています | |||
- **返品ポリシー**: 商品到着後30日以内であれば返品可能です | |||
### 2.2 製品について | |||
- **製品の保証**: すべての製品は1年間の保証が付いています | |||
- **製品の詳細**: 各製品ページで詳細な仕様を確認できます | |||
- **互換性**: 互換性に関する情報は製品詳細ページに記載されています | |||
## 3. お問い合わせ方法 | |||
以下の方法でサポートチームにお問い合わせいただけます: | |||
### 3.1 メール | |||
support@hanye.cn 宛にメールをお送りください。24時間以内に返信いたします。 | |||
### 3.2 電話 | |||
営業時間内(月曜〜金曜、9:00〜18:00 JST)に以下の番号にお電話ください: | |||
+81-XX-XXXX-XXXX | |||
### 3.3 ライブチャット | |||
当ウェブサイトの右下にあるチャットアイコンをクリックすると、営業時間内であればサポートスタッフとリアルタイムでチャットができます。 | |||
## 4. トラブルシューティング | |||
一般的な問題の解決方法: | |||
### 4.1 製品が正常に動作しない場合 | |||
- 電源が正しく接続されているか確認してください | |||
- 最新のファームウェアにアップデートされているか確認してください | |||
- 製品を再起動してみてください | |||
### 4.2 アカウントの問題 | |||
- パスワードのリセットが必要な場合は、ログインページの「パスワードを忘れた場合」をクリックしてください | |||
- アカウント情報の更新は、ログイン後に「マイアカウント」セクションから行えます | |||
## 5. サポートリソース | |||
### 5.1 マニュアルとガイド | |||
製品マニュアルやユーザーガイドは製品ページからダウンロードできます。 | |||
### 5.2 ビデオチュートリアル | |||
当社のYouTubeチャンネルでは、製品の使い方に関する詳細なビデオチュートリアルを公開しています。 | |||
### 5.3 コミュニティフォーラム | |||
ユーザー同士で情報交換ができるコミュニティフォーラムもご利用いただけます。 | |||
## 6. フィードバック | |||
お客様からのフィードバックは、当社のサービス向上に非常に重要です。製品やサービスに関するご意見・ご提案は feedback@hanye.cn までお送りください。 |
@@ -0,0 +1,91 @@ | |||
--- | |||
title: 利用規約 | |||
--- | |||
# 利用規約 | |||
> 最終更新日: 2025年5月12日 | |||
## 1. はじめに | |||
Hanyeのウェブサイト(以下「当サイト」)をご利用いただき、ありがとうございます。本利用規約は、当サイトの利用に関する条件を定めたものです。当サイトをご利用いただくことにより、お客様は本規約に同意したものとみなされます。 | |||
## 2. サービスの利用 | |||
### 2.1 利用資格 | |||
当サイトのサービスをご利用いただくには、以下の条件を満たす必要があります: | |||
- 満13歳以上であること | |||
- 有効なメールアドレスを持っていること | |||
- 本規約に同意していること | |||
### 2.2 アカウント | |||
一部のサービスでは、アカウントの作成が必要となる場合があります。アカウントを作成する場合: | |||
- 正確かつ最新の情報を提供すること | |||
- パスワードの機密性を保持すること | |||
- アカウントでの全活動に責任を持つこと | |||
## 3. 知的財産権 | |||
### 3.1 所有権 | |||
当サイトおよびそのコンテンツ(テキスト、画像、ロゴ、デザインなど)はHanyeまたはそのライセンサーに帰属し、著作権法および国際条約によって保護されています。 | |||
### 3.2 利用制限 | |||
当サイトのコンテンツは、個人的、非商業的な目的でのみ利用することができます。以下の行為は禁止されています: | |||
- コンテンツの複製、改変、配布 | |||
- コンテンツの商業的利用 | |||
- 当社の書面による許可なしにコンテンツをダウンロードまたは保存すること | |||
## 4. 禁止事項 | |||
当サイトを利用する際、以下の行為は禁止されています: | |||
- 違法または有害な活動への参加 | |||
- 他のユーザーへの嫌がらせや脅迫 | |||
- 虚偽または誤解を招く情報の投稿 | |||
- サイトのセキュリティを侵害する試み | |||
- ウイルスやマルウェアの配布 | |||
- 当サイトの正常な機能を妨げるような行為 | |||
## 5. 購入と支払い | |||
### 5.1 商品情報 | |||
当サイトで表示される商品情報は可能な限り正確なものを提供しておりますが、誤りがある場合があります。価格や在庫状況は予告なく変更される場合があります。 | |||
### 5.2 注文と確認 | |||
お客様が注文を行った後、確認メールが送信されます。ただし、当社は以下の場合に注文をキャンセルする権利を有します: | |||
- 商品が在庫切れの場合 | |||
- お客様の支払い情報を確認できない場合 | |||
- 不正行為の疑いがある場合 | |||
### 5.3 支払い | |||
支払いは、当サイトで指定された方法で行う必要があります。お客様は、提供する支払い情報が正確であることを保証するものとします。 | |||
## 6. 責任の制限 | |||
当サイトおよびそのコンテンツは「現状有姿」で提供されており、当社は以下について明示的または黙示的保証を行いません: | |||
- サービスの中断がないこと | |||
- エラーがないこと | |||
- 特定目的への適合性 | |||
- セキュリティの完全性 | |||
法律で許容される最大限の範囲で、当社は以下に起因する損害について責任を負いません: | |||
- 当サイトの利用または利用不能 | |||
- 第三者による不正アクセス | |||
- データの損失または破損 | |||
## 7. 紛争解決 | |||
### 7.1 準拠法 | |||
本規約は日本法に準拠し、解釈されるものとします。 | |||
### 7.2 紛争解決 | |||
本規約に関連するすべての紛争は、東京地方裁判所を第一審の専属的合意管轄裁判所とします。 | |||
## 8. 規約の変更 | |||
当社は、いつでも本規約を変更する権利を有します。変更があった場合は、当サイトに新しい規約を掲載することで通知します。変更後も当サイトを継続して利用することにより、お客様は変更後の規約に同意したものとみなされます。 | |||
## 9. お問い合わせ | |||
本規約に関するご質問がある場合は、以下の連絡先までお問い合わせください: | |||
**メール:** [terms@hanye.cn] |
@@ -0,0 +1,79 @@ | |||
--- | |||
title: 隐私政策 | |||
--- | |||
# 隐私政策 | |||
> 最后更新日期:2025-05-12 | |||
## 1. 引言 | |||
欢迎查阅 Hanye 的隐私政策。本文档说明我们如何收集、使用和保护您在使用我们网站时的个人信息。 | |||
## 2. 我们收集的信息 | |||
### 2.1 个人信息 | |||
我们收集以下个人信息: | |||
- **姓名**:您的全名 | |||
- **电子邮箱**:您的联系邮箱 | |||
- **联系方式**:电话号码和地址 | |||
- **使用数据**:您与网站的互动情况 | |||
### 2.2 非个人信息 | |||
我们还收集以下非个人信息: | |||
- **浏览器类型**:您使用的网页浏览器 | |||
- **设备信息**:您的设备类型和操作系统 | |||
- **IP地址**:您的互联网协议地址 | |||
- **Cookie**:存储在您设备上的小型数据文件 | |||
## 3. 我们如何使用您的信息 | |||
我们使用您的信息来: | |||
- 提供和维护我们的服务 | |||
- 改善用户体验 | |||
- 发送重要更新 | |||
- 回应您的询问 | |||
- 分析网站使用情况 | |||
## 4. 数据保护 | |||
我们实施以下安全措施: | |||
- **加密**:所有数据在传输和存储时都经过加密 | |||
- **安全服务器**:采用行业标准安全保护 | |||
- **定期评估**:持续安全监控 | |||
- **访问控制**:严格的访问管理 | |||
## 5. 您的权利 | |||
您拥有以下权利: | |||
- 访问您的个人数据 | |||
- 更正不准确的数据 | |||
- 请求删除数据 | |||
- 反对数据处理 | |||
- 数据可携带性 | |||
## 6. Cookie使用 | |||
我们使用Cookie来: | |||
- 改善网站功能 | |||
- 记住您的偏好 | |||
- 分析网站流量 | |||
- 增强用户体验 | |||
## 7. 第三方服务 | |||
我们可能使用收集信息的第三方服务。这些服务有其自己的隐私政策。 | |||
## 8. 儿童隐私 | |||
我们的服务不面向13岁以下儿童。我们不会故意收集儿童的个人信息。 | |||
## 9. 政策变更 | |||
我们可能会定期更新此隐私政策。我们将在本页面上发布新政策来通知您任何变更。 | |||
## 10. 联系我们 | |||
如果您对此隐私政策有任何疑问,请通过以下方式联系我们: | |||
**电子邮件:** [hanye@hanye.cn] |
@@ -0,0 +1,66 @@ | |||
--- | |||
title: 客户支持 | |||
--- | |||
# 客户支持 | |||
> 最后更新: 2025年5月12日 | |||
## 1. 关于支持 | |||
欢迎来到Hanye的客户支持页面。本页面提供关于我们产品和服务的支持信息。 | |||
## 2. 常见问题 | |||
### 2.1 订单和配送 | |||
- **如何跟踪订单**: 您可以通过确认邮件中的链接跟踪订单状态 | |||
- **配送时间**: 商品通常在3-5个工作日内发货 | |||
- **国际配送**: 我们目前向全球多个国家提供配送服务 | |||
- **退货政策**: 收到商品后30天内可以退货 | |||
### 2.2 关于产品 | |||
- **产品保修**: 所有产品都提供1年保修 | |||
- **产品详情**: 详细规格可在各产品页面查看 | |||
- **兼容性**: 兼容性信息列在产品详情页中 | |||
## 3. 联系方式 | |||
您可以通过以下方式联系我们的支持团队: | |||
### 3.1 电子邮件 | |||
发送邮件至support@hanye.cn,我们将在24小时内回复。 | |||
### 3.2 电话 | |||
在营业时间内(周一至周五,9:00-18:00 JST)拨打以下电话: | |||
+81-XX-XXXX-XXXX | |||
### 3.3 在线聊天 | |||
在营业时间内,点击我们网站右下角的聊天图标,即可与支持代表进行实时交流。 | |||
## 4. 故障排除 | |||
常见问题的解决方案: | |||
### 4.1 产品无法正常工作 | |||
- 检查电源是否正确连接 | |||
- 确认固件已更新至最新版本 | |||
- 尝试重启产品 | |||
### 4.2 账户问题 | |||
- 如需重置密码,请点击登录页面的"忘记密码" | |||
- 要更新账户信息,请登录后进入"我的账户"部分 | |||
## 5. 支持资源 | |||
### 5.1 手册和指南 | |||
产品手册和用户指南可从产品页面下载。 | |||
### 5.2 视频教程 | |||
我们的YouTube频道提供详细的产品使用视频教程。 | |||
### 5.3 社区论坛 | |||
您还可以使用我们的社区论坛,用户可以在那里交流信息。 | |||
## 6. 反馈 | |||
您的反馈对改进我们的服务极为重要。请将您对我们产品和服务的意见和建议发送至feedback@hanye.cn。 |
@@ -0,0 +1,91 @@ | |||
--- | |||
title: 服务条款 | |||
--- | |||
# 服务条款 | |||
> 最后更新: 2025年5月12日 | |||
## 1. 简介 | |||
感谢使用Hanye网站(以下简称"本网站")。本服务条款确立了使用本网站的条件。使用本网站即表示您同意遵守这些条款。 | |||
## 2. 服务使用 | |||
### 2.1 使用资格 | |||
要使用本网站的服务,您必须满足以下条件: | |||
- 年满13周岁 | |||
- 拥有有效的电子邮件地址 | |||
- 同意本服务条款 | |||
### 2.2 账户 | |||
某些服务可能需要创建账户。创建账户时: | |||
- 提供准确且最新的信息 | |||
- 保持密码的保密性 | |||
- 对您账户上的所有活动负责 | |||
## 3. 知识产权 | |||
### 3.1 所有权 | |||
本网站及其内容(文本、图片、徽标、设计等)属于Hanye或其许可方,受版权法和国际条约保护。 | |||
### 3.2 使用限制 | |||
本网站的内容仅可用于个人、非商业目的。以下行为是禁止的: | |||
- 复制、修改或分发内容 | |||
- 将内容用于商业用途 | |||
- 未经我们书面许可下载或保存内容 | |||
## 4. 禁止行为 | |||
使用本网站时,禁止以下行为: | |||
- 参与非法或有害活动 | |||
- 骚扰或威胁其他用户 | |||
- 发布虚假或误导性信息 | |||
- 试图破坏网站安全 | |||
- 传播病毒或恶意软件 | |||
- 从事干扰网站正常运行的活动 | |||
## 5. 购买与支付 | |||
### 5.1 产品信息 | |||
本网站上显示的产品信息尽可能准确,但可能存在错误。价格和可用性可能会变更,恕不另行通知。 | |||
### 5.2 订单与确认 | |||
下订单后,将发送确认邮件。但在以下情况下,我们保留取消订单的权利: | |||
- 产品缺货 | |||
- 无法验证您的支付信息 | |||
- 怀疑存在欺诈活动 | |||
### 5.3 支付 | |||
必须使用本网站指定的方式付款。您保证所提供的支付信息准确无误。 | |||
## 6. 责任限制 | |||
本网站及其内容按"现状"提供,我们不对以下方面做出明示或暗示的保证: | |||
- 服务不中断 | |||
- 没有错误 | |||
- 适合特定目的 | |||
- 安全完整性 | |||
在法律允许的最大范围内,我们不对以下原因造成的损害承担责任: | |||
- 使用或无法使用本网站 | |||
- 第三方未经授权的访问 | |||
- 数据丢失或损坏 | |||
## 7. 争议解决 | |||
### 7.1 适用法律 | |||
本服务条款受日本法律管辖并据其解释。 | |||
### 7.2 争议解决 | |||
与本服务条款相关的所有争议应以东京地方法院为第一审专属管辖法院。 | |||
## 8. 条款变更 | |||
我们保留随时变更本服务条款的权利。如有变更,我们将通过在本网站上发布新条款来通知您。变更后继续使用本网站即表示您接受新条款。 | |||
## 9. 联系我们 | |||
如果您对本服务条款有任何疑问,请联系我们: | |||
**电子邮件:** [terms@hanye.cn] |
@@ -14,6 +14,23 @@ export default { | |||
clear: "Clear", | |||
noResults: "No results found", | |||
searching: "Searching...", | |||
breadcrumb: { | |||
home: "Home", | |||
privacy: "Privacy Policy", | |||
products: "Products", | |||
terms: "Terms of Use", | |||
support: "Support", | |||
}, | |||
privacy: { | |||
title: "Privacy Policy", | |||
description: "Learn how we collect, use, and protect your personal information", | |||
}, | |||
meta: { | |||
privacy: { | |||
title: "Privacy Policy - Hanye", | |||
description: "Learn how Hanye collects, uses, and protects your personal information" | |||
} | |||
}, | |||
footer: { | |||
productsLinks: { | |||
title: "Products", | |||
@@ -26,8 +43,11 @@ export default { | |||
about: "About Us", | |||
contact: "Contact", | |||
}, | |||
quickLinks: { | |||
title: "Quick Links", | |||
legalAndSupport: { | |||
title: "Legal & Support", | |||
support: "Support", | |||
privacy: "Privacy Policy", | |||
terms: "Terms of Use", | |||
}, | |||
}, | |||
}, | |||
@@ -80,6 +100,17 @@ export default { | |||
view_details: "View Details", | |||
product_count: "Products", | |||
loadError: "Failed to load product. Please try again later.", | |||
retry: "Retry", | |||
imageLoadError: "Failed to load image. Please try again later.", | |||
image: "Image {index}", | |||
error: "Error", | |||
productSpecifications: "Product Specifications", | |||
productDescription: "Product Description", | |||
detailedDescription: "Detailed Description", | |||
categoryTitle: "Category", | |||
usageTitle: "Usage", | |||
capacitiesTitle: "Capacities", | |||
relatedProducts: "Related Products", | |||
}, | |||
faq: { | |||
title: "Frequently Asked Questions", |
@@ -14,6 +14,23 @@ export default { | |||
clear: "クリア", | |||
noResults: "関連する結果はありません", | |||
searching: "検索中...", | |||
breadcrumb: { | |||
home: "ホーム", | |||
privacy: "プライバシーポリシー", | |||
products: "製品", | |||
terms: "利用規約", | |||
support: "サポート", | |||
}, | |||
privacy: { | |||
title: "プライバシーポリシー", | |||
description: "当社が収集、使用、保護する個人情報について学ぶ", | |||
}, | |||
meta: { | |||
privacy: { | |||
title: "プライバシーポリシー - Hanye", | |||
description: "Hanyeが収集、使用、保護する個人情報について学ぶ", | |||
}, | |||
}, | |||
footer: { | |||
productsLinks: { | |||
title: "製品", | |||
@@ -26,8 +43,11 @@ export default { | |||
about: "会社概要", | |||
contact: "お問い合わせ", | |||
}, | |||
quickLinks: { | |||
title: "クイックリンク", | |||
legalAndSupport: { | |||
title: "法令遵守とサポート", | |||
support: "サポート", | |||
privacy: "プライバシーポリシー", | |||
terms: "利用規約", | |||
}, | |||
}, | |||
}, | |||
@@ -75,6 +95,17 @@ export default { | |||
view_details: "詳細を見る", | |||
product_count: "製品", | |||
loadError: "製品の読み込みに失敗しました。後でもう一度お試しください。", | |||
retry: "再試行", | |||
imageLoadError: "画像の読み込みに失敗しました。後でもう一度お試しください。", | |||
image: "画像 {index}", | |||
error: "エラー", | |||
productSpecifications: "製品仕様", | |||
productDescription: "製品説明", | |||
detailedDescription: "詳細説明", | |||
categoryTitle: "カテゴリー", | |||
usageTitle: "用途", | |||
capacitiesTitle: "容量", | |||
relatedProducts: "関連製品", | |||
}, | |||
faq: { | |||
title: "よくある質問", |
@@ -26,10 +26,30 @@ export default { | |||
about: "关于我们", | |||
contact: "联系我们", | |||
}, | |||
quickLinks: { | |||
title: "快捷链接", | |||
legalAndSupport: { | |||
title: "法律与支持", | |||
support: "技术支持", | |||
privacy: "隐私政策", | |||
terms: "使用条款", | |||
}, | |||
}, | |||
breadcrumb: { | |||
home: "首页", | |||
privacy: "隐私政策", | |||
products: "产品", | |||
terms: "使用条款", | |||
support: "技术支持", | |||
}, | |||
privacy: { | |||
title: "隐私政策", | |||
description: "了解我们如何收集、使用和保护您的个人信息", | |||
}, | |||
meta: { | |||
privacy: { | |||
title: "隐私政策 - Hanye", | |||
description: "了解 Hanye 如何收集、使用和保护您的个人信息" | |||
} | |||
}, | |||
}, | |||
home: { | |||
title: "Hanye 官网", | |||
@@ -75,6 +95,17 @@ export default { | |||
view_details: "查看详情", | |||
product_count: "款产品", | |||
loadError: "加载产品失败,请稍后重试", | |||
retry: "重试", | |||
imageLoadError: "图片加载失败,请稍后重试", | |||
image: "图片 {index}", | |||
error: "错误", | |||
productSpecifications: "产品规格", | |||
productDescription: "产品描述", | |||
detailedDescription: "详细描述", | |||
categoryTitle: "分类", | |||
usageTitle: "用途", | |||
capacitiesTitle: "容量", | |||
relatedProducts: "相关产品", | |||
}, | |||
faq: { | |||
title: "常见问题", |
@@ -52,11 +52,12 @@ export default defineNuxtConfig({ | |||
// 配置 Markdown | |||
build: { | |||
markdown: { | |||
// 配置高亮 | |||
highlight: { | |||
theme: "github-light", | |||
}, | |||
}, | |||
}, | |||
theme: 'github-light' | |||
} | |||
} | |||
} | |||
}, | |||
// i18n 配置 (从外部文件加载) |
@@ -14,7 +14,7 @@ | |||
<div class="max-w-full mb-6 xl:px-2 lg:px-2 md:px-4 px-4 mt-6"> | |||
<div class="max-w-screen-2xl mx-auto"> | |||
<nuxt-link | |||
to="/" | |||
:to="`${homepagePath}/`" | |||
class="justify-start text-white/60 text-base font-normal hover:text-white transition-colors duration-300" | |||
>{{ t("common.home") }}</nuxt-link | |||
> | |||
@@ -205,6 +205,10 @@ interface CompanyInfo { | |||
fax: string; | |||
} | |||
const homepagePath = computed(() => { | |||
return locale.value === "zh" ? "" : `/${locale.value}`; | |||
}); | |||
const { error, isLoading, wrapAsync } = useErrorHandler(); | |||
const companyInfo = ref<CompanyInfo>({ | |||
email: "hanye@hanye.cn", |
@@ -12,12 +12,12 @@ | |||
<div class="max-w-full xl:px-2 lg:px-2 md:px-4 px-4 mt-6 mb-20"> | |||
<div class="max-w-screen-2xl mx-auto"> | |||
<nuxt-link | |||
to="/" | |||
:to="`${homepagePath}/`" | |||
class="justify-start text-white/60 text-base font-normal" | |||
>{{ 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 :to="`${homepagePath}/contact`" class="text-white text-base font-normal">{{ | |||
t("contact.title") | |||
}}</nuxt-link> | |||
</div> | |||
@@ -246,6 +246,10 @@ import { useCaptcha } from "~/composables/useCaptcha"; | |||
import emailjs from '@emailjs/browser'; | |||
const { t, locale } = useI18n(); | |||
const homepagePath = computed(() => { | |||
return locale.value === "zh" ? "" : `/${locale.value}`; | |||
}); | |||
// EmailJS 配置 | |||
const EMAILJS_SERVICE_ID = 'YOUR_SERVICE_ID'; // 替换为你的 Service ID | |||
const EMAILJS_TEMPLATE_ID = 'YOUR_TEMPLATE_ID'; // 替换为你的 Template ID |
@@ -12,12 +12,12 @@ | |||
<div class="max-w-full xl:px-2 lg:px-2 md:px-4 px-4 mt-6 mb-20"> | |||
<div class="max-w-screen-2xl mx-auto"> | |||
<nuxt-link | |||
to="/" | |||
:to="`${homepagePath}/`" | |||
class="justify-start text-white/60 text-base font-normal" | |||
>{{ 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 :to="`${homepagePath}/faq`" class="text-white text-base font-normal">{{ | |||
t("faq.title") | |||
}}</nuxt-link> | |||
</div> | |||
@@ -156,6 +156,12 @@ interface FAQ { | |||
id?: string; | |||
} | |||
const homepagePath = computed(() => { | |||
return locale.value === "zh" ? "" : `/${locale.value}`; | |||
}); | |||
// 从content目录读取FAQ数据 | |||
const faqs = ref<FAQ[]>([]); | |||
const categoriesList = ref<string[]>([]); |
@@ -605,6 +605,10 @@ const productsDataUrl = computed(() => { | |||
return `/data/products-${locale.value}.json`; | |||
}); | |||
const homepagePath = computed(() => { | |||
return locale.value === "zh" ? "" : `/${locale.value}`; | |||
}); | |||
// 获取按用途产品数据 | |||
const usageList = ref<Usage[]>([]); | |||
const isLoadingUsage = ref(true); | |||
@@ -660,7 +664,7 @@ const loadUsageData = async () => { | |||
id: product.id, | |||
title: product.title, | |||
image: product.image, | |||
link: `/products/${product.id}`, | |||
link: `${homepagePath.value}/products/${product.id}`, | |||
description: product.summary, | |||
}); | |||
}); | |||
@@ -670,9 +674,9 @@ const loadUsageData = async () => { | |||
usageProducts.push({ | |||
id: `placeholder-${usage.id}`, | |||
title: usage.title || "未命名用途", | |||
title: usage.title, | |||
image: ``, | |||
link: `/products?usage=${encodeURIComponent(usage.title)}`, | |||
link: `${homepagePath.value}/products?usage=${encodeURIComponent(usage.title)}`, | |||
description: "", | |||
}); | |||
} | |||
@@ -720,7 +724,7 @@ const loadCategoryData = async () => { | |||
title: category.title || "", | |||
description: category.description || "", | |||
image: category.image || "", | |||
link: `/products?category=${encodeURIComponent(category.title)}`, | |||
link: `${homepagePath.value}/products?category=${encodeURIComponent(category.title)}`, | |||
capacities: category.capacities || [], | |||
summary: category.summary || "", | |||
sort: category.sort || 0, |
@@ -0,0 +1,65 @@ | |||
<template> | |||
<div class="min-h-screen bg-stone-950"> | |||
<div class="w-full h-[45px] sm:h-[55px] md:h-[65px] lg:h-[72px]"></div> | |||
<!-- 面包屑导航 --> | |||
<div class="max-w-screen-2xl mx-auto px-4 pt-8"> | |||
<nav class="flex items-center space-x-2 text-sm text-zinc-400"> | |||
<NuxtLink :to="homepagePath" class="hover:text-white transition">{{ | |||
t("common.breadcrumb.home") | |||
}}</NuxtLink> | |||
<span class="text-zinc-600">/</span> | |||
<span class="text-white">{{ t("common.breadcrumb.privacy") }}</span> | |||
</nav> | |||
</div> | |||
<!-- 主要内容 --> | |||
<div class="max-w-screen-2xl mx-auto px-4 py-8"> | |||
<!-- 页面标题 --> | |||
<div class="mb-8"> | |||
<h1 class="text-4xl font-bold text-white mb-4"> | |||
{{ t("common.privacy.title") }} | |||
</h1> | |||
<p class="text-zinc-400">{{ t("common.privacy.description") }}</p> | |||
</div> | |||
<!-- 内容区域 --> | |||
<div class="bg-stone-900 rounded-lg p-8 shadow-lg"> | |||
<div class="prose prose-invert max-w-none"> | |||
<ContentRenderer v-if="page" :value="page" /> | |||
</div> | |||
</div> | |||
</div> | |||
</div> | |||
</template> | |||
<script setup lang="ts"> | |||
/** | |||
* 隐私政策页面 | |||
* 展示网站的隐私政策内容 | |||
*/ | |||
const { t, locale } = useI18n(); | |||
const homepagePath = computed(() => { | |||
return locale.value === "zh" ? "/" : `/${locale.value}`; | |||
}); | |||
// 计算内容路径 | |||
const contentPath = computed(() => { | |||
return `/${locale.value}/privacy`; | |||
}); | |||
const { data: page } = await useAsyncData(contentPath.value, () => { | |||
return queryCollection("content").path(contentPath.value).first(); | |||
}); | |||
// 设置页面元数据 | |||
useHead({ | |||
title: t("common.meta.privacy.title"), | |||
meta: [ | |||
{ | |||
name: "description", | |||
content: t("common.meta.privacy.description"), | |||
}, | |||
], | |||
}); | |||
</script> |
@@ -14,20 +14,20 @@ | |||
<div class="max-w-full mb-6 xl:px-2 lg:px-2 md:px-4 px-4 mt-6"> | |||
<div class="max-w-screen-2xl mx-auto"> | |||
<nuxt-link | |||
to="/" | |||
:to="`${homepagePath}/`" | |||
class="justify-start text-white/60 text-base font-normal" | |||
>ホーム</nuxt-link | |||
>{{ t("common.breadcrumb.home") }}</nuxt-link | |||
> | |||
<span class="text-white/60 text-base font-normal px-2"> / </span> | |||
<nuxt-link | |||
to="/products" | |||
:to="`${homepagePath}/products`" | |||
class="text-white/60 text-base font-normal" | |||
>製品一覧</nuxt-link | |||
>{{ t("common.breadcrumb.products") }}</nuxt-link | |||
> | |||
<span class="text-white/60 text-base font-normal px-2"> / </span> | |||
<nuxt-link | |||
v-if="product?.category" | |||
:to="`/products?category=${encodeURIComponent(product.category)}`" | |||
:to="`${homepagePath}/products?category=${encodeURIComponent(product.category)}`" | |||
class="text-white/60 text-base font-normal" | |||
>{{ product.category }}</nuxt-link | |||
> | |||
@@ -46,138 +46,229 @@ | |||
<div class="max-w-screen-2xl mx-auto"> | |||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-8 lg:gap-16"> | |||
<!-- 左侧产品图片 --> | |||
<div class="flex flex-col gap-6 lg:sticky lg:top-24 self-start"> | |||
<div | |||
class="flex flex-col gap-6 lg:sticky lg:top-24 self-start select-none" | |||
> | |||
<!-- 主图展示 --> | |||
<div | |||
class="bg-zinc-900 rounded-lg p-8 relative overflow-hidden group aspect-square" | |||
> | |||
<!-- 加载状态 --> | |||
<div | |||
v-if="isImageLoading" | |||
class="absolute inset-0 flex items-center justify-center bg-zinc-800/50 z-10" | |||
<!-- 主图容器 - Swiper --> | |||
<Swiper | |||
:modules="[Navigation, Pagination, EffectFade, Thumbs]" | |||
:slides-per-view="1" | |||
:pagination="{ | |||
clickable: true, | |||
dynamicBullets: true, | |||
dynamicMainBullets: 3, | |||
}" | |||
:navigation="true" | |||
:effect="'fade'" | |||
:fade-effect="{ crossFade: true }" | |||
:thumbs="{ swiper: thumbsSwiper }" | |||
class="w-full h-full product-swiper" | |||
@swiper="swiperInstance = $event" | |||
@slideChange="currentSlideIndex = $event.activeIndex" | |||
> | |||
<div | |||
class="animate-spin h-8 w-8 border-4 border-blue-500 rounded-full border-t-transparent" | |||
></div> | |||
</div> | |||
<SwiperSlide | |||
v-for="(image, slideIndex) in [ | |||
product.image, | |||
...(product.gallery || []), | |||
]" | |||
:key="slideIndex" | |||
> | |||
<div class="relative w-full h-full"> | |||
<!-- 当前图片 --> | |||
<img | |||
:src="image" | |||
:alt="`${product.name} - ${t('products.image', { | |||
index: slideIndex + 1, | |||
})}`" | |||
class="w-full h-full object-contain rounded-lg transition-all duration-500" | |||
:class="{ | |||
'opacity-0': isSlideThumbnailLoading[slideIndex], | |||
'opacity-100': | |||
!isSlideThumbnailLoading[slideIndex] && | |||
!slideThumbnailErrors[slideIndex], | |||
}" | |||
@load="handleSlideImageLoad(slideIndex)" | |||
@error="handleSlideImageError(slideIndex)" | |||
/> | |||
<!-- 加载状态 --> | |||
<div | |||
v-if="isSlideThumbnailLoading[slideIndex]" | |||
class="absolute inset-0 flex items-center justify-center bg-zinc-800/50 z-10" | |||
> | |||
<div | |||
class="animate-spin h-8 w-8 border-4 border-blue-500 rounded-full border-t-transparent" | |||
></div> | |||
</div> | |||
<!-- 主图容器 --> | |||
<div class="relative w-full h-full"> | |||
<!-- 当前图片 --> | |||
<img | |||
:src="currentImage" | |||
:alt="product.name" | |||
class="absolute inset-0 w-full h-full object-contain rounded-lg transition-all duration-500" | |||
:class="{ | |||
'opacity-0': isImageLoading, | |||
'opacity-100': !isImageLoading, | |||
}" | |||
@load="handleImageLoad" | |||
@error="handleImageError" | |||
/> | |||
<!-- 预加载图片 --> | |||
<img | |||
v-if="preloadImage" | |||
:src="preloadImage" | |||
class="absolute inset-0 w-full h-full object-contain rounded-lg opacity-0" | |||
@load="handlePreloadComplete" | |||
/> | |||
</div> | |||
<!-- 错误提示 --> | |||
<div | |||
v-if="slideThumbnailErrors[slideIndex]" | |||
class="absolute inset-0 flex items-center justify-center bg-red-900/50 z-20" | |||
> | |||
<div class="flex flex-col items-center gap-2"> | |||
<span class="text-white">{{ | |||
t("products.imageLoadError") | |||
}}</span> | |||
<button | |||
@click.stop="retryLoadSlideImage(slideIndex)" | |||
class="px-4 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600 transition-colors duration-300" | |||
> | |||
{{ t("products.retry") }} | |||
</button> | |||
</div> | |||
</div> | |||
</div> | |||
</SwiperSlide> | |||
</Swiper> | |||
<!-- 错误提示 --> | |||
<!-- 全局加载状态 - 仅在所有图片都未加载完成时显示 --> | |||
<div | |||
v-if="imageError" | |||
class="absolute inset-0 flex items-center justify-center bg-red-900/50 z-20" | |||
v-if="isInitialLoading" | |||
class="absolute inset-0 flex items-center justify-center bg-zinc-900/80 z-30" | |||
> | |||
<div class="flex flex-col items-center gap-2"> | |||
<span class="text-white" | |||
>画像の読み込みに失敗しました</span | |||
> | |||
<button | |||
@click.stop="retryLoadImage" | |||
class="px-4 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600 transition-colors duration-300" | |||
> | |||
再試行 | |||
</button> | |||
</div> | |||
<div | |||
class="animate-spin h-12 w-12 border-4 border-blue-500 rounded-full border-t-transparent" | |||
></div> | |||
</div> | |||
</div> | |||
<!-- 缩略图列表 --> | |||
<div class="flex gap-4 overflow-x-auto pb-2 scrollbar-hide"> | |||
<div | |||
v-for="(image, index) in [ | |||
product.image, | |||
...(product.gallery || []), | |||
]" | |||
:key="index" | |||
@click="changeImage(image)" | |||
class="flex-shrink-0 w-20 h-20 cursor-pointer rounded-lg transition-all duration-300 relative group aspect-square p-0.5" | |||
:class="{ | |||
'bg-gradient-to-r from-blue-500 to-blue-600': | |||
currentImage === image, | |||
'hover:bg-gradient-to-r hover:from-blue-500/50 hover:to-blue-600/50': | |||
currentImage !== image, | |||
'opacity-50': | |||
isThumbnailLoading[index] || thumbnailErrors[index], | |||
<!-- 缩略图列表 - Swiper --> | |||
<div | |||
class="relative bg-zinc-900 rounded-lg py-6 px-8 overflow-hidden product-thumbnail-container" | |||
> | |||
<Swiper | |||
:modules="[Navigation, Thumbs, FreeMode]" | |||
:slides-per-view="'auto'" | |||
:space-between="12" | |||
:free-mode="true" | |||
:watch-slides-progress="true" | |||
:navigation="{ | |||
nextEl: '.swiper-thumb-next', | |||
prevEl: '.swiper-thumb-prev', | |||
}" | |||
class="thumbs-swiper" | |||
@swiper="thumbsSwiper = $event" | |||
> | |||
<!-- 缩略图加载状态 --> | |||
<div | |||
v-if="isThumbnailLoading[index]" | |||
class="absolute inset-0 flex items-center justify-center bg-zinc-800/50 rounded-lg z-10" | |||
<SwiperSlide | |||
v-for="(image, index) in [ | |||
product.image, | |||
...(product.gallery || []), | |||
]" | |||
:key="index" | |||
class="!w-16 !h-16 md:!w-20 md:!h-20 !flex-shrink-0 cursor-pointer transition-all duration-300 relative" | |||
> | |||
<div | |||
class="animate-spin h-4 w-4 border-2 border-blue-500 rounded-full border-t-transparent" | |||
></div> | |||
</div> | |||
class="w-full h-full rounded-lg overflow-hidden thumbnail-fixed-size" | |||
:class="{ | |||
'ring-2 ring-blue-500 ring-offset-2 ring-offset-zinc-900': | |||
currentSlideIndex === index, | |||
'hover:ring-1 hover:ring-blue-500/50 hover:ring-offset-1 hover:ring-offset-zinc-900': | |||
currentSlideIndex !== index, | |||
'opacity-50': | |||
isSlideThumbnailLoading[index] || | |||
slideThumbnailErrors[index], | |||
}" | |||
> | |||
<!-- 缩略图加载状态 --> | |||
<div | |||
v-if="isSlideThumbnailLoading[index]" | |||
class="absolute inset-0 flex items-center justify-center bg-zinc-800/50 rounded-lg z-10" | |||
> | |||
<div | |||
class="animate-spin h-4 w-4 border-2 border-blue-500 rounded-full border-t-transparent" | |||
></div> | |||
</div> | |||
<!-- 缩略图遮罩 --> | |||
<div | |||
class="absolute inset-0 bg-black/0 transition-all duration-300 rounded-lg" | |||
:class="{ | |||
'bg-black/30': currentImage === image, | |||
'group-hover:bg-black/20': currentImage !== image, | |||
}" | |||
></div> | |||
<!-- 缩略图遮罩 --> | |||
<div | |||
class="absolute inset-0 transition-all duration-300 rounded-lg" | |||
:class="{ | |||
'bg-black/30': currentSlideIndex === index, | |||
'bg-black/0 hover:bg-black/20': | |||
currentSlideIndex !== index, | |||
}" | |||
></div> | |||
<img | |||
:src="image" | |||
:alt="`${product.name} - ${t('products.image', { | |||
index: index + 1, | |||
})}`" | |||
class="w-full h-full object-cover transition-all duration-300 rounded-lg" | |||
:class="{ | |||
'opacity-0': isSlideThumbnailLoading[index], | |||
'opacity-100': | |||
!isSlideThumbnailLoading[index] && | |||
!slideThumbnailErrors[index], | |||
'hover:scale-110': currentSlideIndex !== index, | |||
}" | |||
@load="handleSlideImageLoad(index)" | |||
@error="handleSlideImageError(index)" | |||
/> | |||
<!-- 缩略图错误提示 --> | |||
<div | |||
v-if="slideThumbnailErrors[index]" | |||
class="absolute inset-0 flex items-center justify-center bg-red-900/50 rounded-lg" | |||
> | |||
<div class="flex flex-col items-center gap-1"> | |||
<span class="text-white text-xs">{{ | |||
t("products.error") | |||
}}</span> | |||
<button | |||
@click.stop="retryLoadSlideImage(index)" | |||
class="px-2 py-1 bg-blue-500 text-white text-xs rounded hover:bg-blue-600 transition-colors duration-300" | |||
> | |||
{{ t("products.retry") }} | |||
</button> | |||
</div> | |||
</div> | |||
</div> | |||
</SwiperSlide> | |||
</Swiper> | |||
<img | |||
:src="image" | |||
:alt="`${product.name} - 画像 ${index + 1}`" | |||
class="w-full h-full object-cover transition-all duration-300 rounded-lg" | |||
:class="{ | |||
'opacity-0': isThumbnailLoading[index], | |||
'opacity-100': !isThumbnailLoading[index] && !thumbnailErrors[index], | |||
'group-hover:scale-110': currentImage !== image, | |||
}" | |||
@load="handleThumbnailLoad(index)" | |||
@error="handleThumbnailError(index)" | |||
/> | |||
<!-- 选中标记 --> | |||
<div | |||
v-if="currentImage === image" | |||
class="absolute top-1 right-1 w-4 h-4 bg-blue-500 rounded-full flex items-center justify-center" | |||
<!-- 缩略图导航按钮 --> | |||
<button | |||
class="swiper-thumb-prev absolute top-1/2 left-2 z-10 w-8 h-8 flex items-center justify-center bg-black/50 hover:bg-blue-500 rounded-full transform -translate-y-1/2 transition-all duration-300" | |||
> | |||
<svg | |||
xmlns="http://www.w3.org/2000/svg" | |||
class="h-5 w-5 text-white" | |||
fill="none" | |||
viewBox="0 0 24 24" | |||
stroke="currentColor" | |||
> | |||
<div class="w-2 h-2 bg-white rounded-full"></div> | |||
</div> | |||
<!-- 缩略图错误提示 --> | |||
<div | |||
v-if="thumbnailErrors[index]" | |||
class="absolute inset-0 flex items-center justify-center bg-red-900/50 rounded-lg" | |||
<path | |||
stroke-linecap="round" | |||
stroke-linejoin="round" | |||
stroke-width="2" | |||
d="M15 19l-7-7 7-7" | |||
/> | |||
</svg> | |||
</button> | |||
<button | |||
class="swiper-thumb-next absolute top-1/2 right-2 z-10 w-8 h-8 flex items-center justify-center bg-black/50 hover:bg-blue-500 rounded-full transform -translate-y-1/2 transition-all duration-300" | |||
> | |||
<svg | |||
xmlns="http://www.w3.org/2000/svg" | |||
class="h-5 w-5 text-white" | |||
fill="none" | |||
viewBox="0 0 24 24" | |||
stroke="currentColor" | |||
> | |||
<div class="flex flex-col items-center gap-1"> | |||
<span class="text-white text-xs">エラー</span> | |||
<button | |||
@click.stop="retryLoadThumbnail(index)" | |||
class="px-2 py-1 bg-blue-500 text-white text-xs rounded hover:bg-blue-600 transition-colors duration-300" | |||
> | |||
再試行 | |||
</button> | |||
</div> | |||
</div> | |||
</div> | |||
<path | |||
stroke-linecap="round" | |||
stroke-linejoin="round" | |||
stroke-width="2" | |||
d="M9 5l7 7-7 7" | |||
/> | |||
</svg> | |||
</button> | |||
</div> | |||
</div> | |||
@@ -195,12 +286,16 @@ | |||
<!-- 产品参数 --> | |||
<div class="bg-zinc-900 rounded-lg p-6"> | |||
<h2 class="text-white text-xl font-medium mb-6">製品仕様</h2> | |||
<h2 class="text-white text-xl font-medium mb-6"> | |||
{{ t("products.productSpecifications") }} | |||
</h2> | |||
<div class="grid grid-cols-1 gap-4"> | |||
<div | |||
class="flex justify-between items-center py-2 border-b border-zinc-800" | |||
> | |||
<span class="text-stone-400">カテゴリー</span> | |||
<span class="text-stone-400">{{ | |||
t("products.categoryTitle") | |||
}}</span> | |||
<span class="text-white font-medium">{{ | |||
product.category | |||
}}</span> | |||
@@ -208,13 +303,20 @@ | |||
<div | |||
class="flex justify-between items-center py-2 border-b border-zinc-800" | |||
> | |||
<span class="text-stone-400">用途</span> | |||
<span class="text-stone-400">{{ | |||
t("products.usageTitle") | |||
}}</span> | |||
<span class="text-white font-medium">{{ | |||
product.usage?.join(", ") | |||
}}</span> | |||
</div> | |||
<div class="flex justify-between items-center py-2"> | |||
<span class="text-stone-400">容量</span> | |||
<div | |||
v-if="product.capacities && product.capacities.length > 0" | |||
class="flex justify-between items-center py-2" | |||
> | |||
<span class="text-stone-400">{{ | |||
t("products.capacitiesTitle") | |||
}}</span> | |||
<span class="text-white font-medium">{{ | |||
product.capacities?.join(" / ") | |||
}}</span> | |||
@@ -223,8 +325,13 @@ | |||
</div> | |||
<!-- 产品描述 --> | |||
<div class="bg-zinc-900 rounded-lg p-6"> | |||
<h2 class="text-white text-xl font-medium mb-6">产品描述</h2> | |||
<div | |||
v-if="product.description" | |||
class="bg-zinc-900 rounded-lg p-6" | |||
> | |||
<h2 class="text-white text-xl font-medium mb-6"> | |||
{{ t("products.productDescription") }} | |||
</h2> | |||
<div | |||
class="text-stone-400 leading-relaxed space-y-4 prose prose-invert max-w-none" | |||
> | |||
@@ -233,7 +340,9 @@ | |||
</div> | |||
<div class="bg-zinc-900 rounded-lg p-6"> | |||
<h2 class="text-white text-xl font-medium mb-6">详细描述</h2> | |||
<h2 class="text-white text-xl font-medium mb-6"> | |||
{{ t("products.detailedDescription") }} | |||
</h2> | |||
<div | |||
class="text-stone-400 leading-relaxed space-y-4 prose prose-invert max-w-none" | |||
> | |||
@@ -247,11 +356,7 @@ | |||
class="bg-zinc-900 rounded-lg p-6" | |||
> | |||
<h2 class="text-white text-xl font-medium mb-6"> | |||
{{ | |||
product.meta?.series && product.meta.series.length > 0 | |||
? "同シリーズ製品" | |||
: "関連製品" | |||
}} | |||
{{ t("products.relatedProducts") }} | |||
</h2> | |||
<div | |||
class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4" | |||
@@ -259,7 +364,7 @@ | |||
<nuxt-link | |||
v-for="relatedProduct in relatedProducts" | |||
:key="relatedProduct.id" | |||
:to="`/products/${relatedProduct.id}`" | |||
:to="`${homepagePath}/products/${relatedProduct.id}`" | |||
class="group" | |||
> | |||
<div | |||
@@ -304,24 +409,41 @@ import { useErrorHandler } from "~/composables/useErrorHandler"; | |||
import { useRoute, useI18n, useAsyncData } from "#imports"; | |||
import { queryCollection } from "#imports"; | |||
import { ContentRenderer } from "#components"; | |||
import { Swiper, SwiperSlide } from "swiper/vue"; | |||
import { | |||
Navigation, | |||
Pagination, | |||
EffectFade, | |||
Thumbs, | |||
FreeMode, | |||
} from "swiper/modules"; | |||
import type { Swiper as SwiperType } from "swiper"; | |||
import "swiper/css"; | |||
import "swiper/css/navigation"; | |||
import "swiper/css/pagination"; | |||
import "swiper/css/effect-fade"; | |||
const { error, isLoading } = useErrorHandler(); | |||
const route = useRoute(); | |||
const { locale, t } = useI18n(); | |||
const id = route.params.id as string; | |||
const swiperInstance = ref<SwiperType | null>(null); | |||
const thumbsSwiper = ref<SwiperType | null>(null); | |||
// 图片状态 | |||
const currentImage = ref<string>(""); | |||
const isImageLoading = ref(true); | |||
const isThumbnailLoading = ref<boolean[]>([]); | |||
const imageError = ref(false); | |||
const thumbnailErrors = ref<boolean[]>([]); | |||
const preloadImage = ref<string | null>(null); | |||
const currentSlideIndex = ref(0); | |||
const isInitialLoading = ref(true); // 初始加载状态 | |||
const isSlideThumbnailLoading = ref<boolean[]>([]); | |||
const slideThumbnailErrors = ref<boolean[]>([]); | |||
// 滚动跟随相关 | |||
const scrollContainer = ref<HTMLElement | null>(null); | |||
const isSticky = ref(false); | |||
const homepagePath = computed(() => { | |||
return locale.value === "zh" ? "" : `/${locale.value}`; | |||
}); | |||
interface Product { | |||
id: string; | |||
name: string; | |||
@@ -450,17 +572,42 @@ const { data: relatedProductsContent } = await useAsyncData( | |||
const relatedProducts = computed(() => { | |||
if (!relatedProductsContent.value || !product.value) return []; | |||
// 获取当前产品的分类和系列 | |||
const currentCategory = product.value.categoryId; | |||
const currentSeries = product.value.meta?.series || []; | |||
const currentProductId = id; | |||
return relatedProductsContent.value | |||
.filter((item: any) => item._path !== `/products/${locale.value}/${id}`) | |||
.filter((item: any) => { | |||
// 排除当前产品 - 多重检查确保排除 | |||
if (item._path === `/products/${locale.value}/${id}`) return false; | |||
const meta = item.meta || {}; | |||
if (meta.name === currentProductId) return false; | |||
const itemCategoryId = meta.categoryId || ""; | |||
const itemSeries = Array.isArray(meta.series) ? meta.series : []; | |||
// 判断是否同类别或同系列 | |||
const isSameCategory = | |||
currentCategory && itemCategoryId === currentCategory; | |||
const hasSameSeries = | |||
currentSeries.length > 0 && | |||
itemSeries.some((series: string) => currentSeries.includes(series)); | |||
// 返回同类别或同系列的产品 | |||
return isSameCategory || hasSameSeries; | |||
}) | |||
.map((item: any) => { | |||
const meta = item.meta || {}; | |||
console.log(meta); | |||
return { | |||
id: meta.name || "", | |||
name: meta.name || item.title || "", | |||
title: item.title || meta.name || "", | |||
image: meta.image || "", | |||
summary: meta.summary || "", | |||
category: meta.categoryId || "", | |||
series: Array.isArray(meta.series) ? meta.series : [], | |||
}; | |||
}) | |||
.slice(0, 6); // 最多显示6个相关产品 | |||
@@ -470,194 +617,126 @@ const relatedProducts = computed(() => { | |||
* 预加载下一张图片 | |||
*/ | |||
function preloadNextImage(image: string) { | |||
preloadImage.value = image; | |||
} | |||
/** | |||
* 处理预加载完成 | |||
*/ | |||
function handlePreloadComplete() { | |||
preloadImage.value = null; | |||
currentSlideIndex.value = | |||
(currentSlideIndex.value + 1) % (product.value?.gallery?.length || 1); | |||
} | |||
/** | |||
* 处理图片加载完成 | |||
*/ | |||
function handleImageLoad() { | |||
isImageLoading.value = false; | |||
imageError.value = false; | |||
function handleSlideImageLoad(index: number) { | |||
isSlideThumbnailLoading.value[index] = false; | |||
slideThumbnailErrors.value[index] = false; | |||
// 检查是否所有图片都已加载 | |||
const allImagesLoaded = isSlideThumbnailLoading.value.every( | |||
(status) => !status | |||
); | |||
if (allImagesLoaded) { | |||
isInitialLoading.value = false; | |||
} | |||
} | |||
/** | |||
* 处理图片加载错误 | |||
*/ | |||
function handleImageError() { | |||
isImageLoading.value = false; | |||
imageError.value = true; | |||
function handleSlideImageError(index: number) { | |||
isSlideThumbnailLoading.value[index] = false; | |||
slideThumbnailErrors.value[index] = true; | |||
} | |||
/** | |||
* 重试加载图片 | |||
*/ | |||
function retryLoadImage() { | |||
isImageLoading.value = true; | |||
imageError.value = false; | |||
// 强制重新加载图片 | |||
const img = new Image(); | |||
img.src = currentImage.value; | |||
img.onload = () => { | |||
handleImageLoad(); | |||
}; | |||
img.onerror = () => { | |||
handleImageError(); | |||
}; | |||
} | |||
/** | |||
* 重试加载缩略图 | |||
*/ | |||
function retryLoadThumbnail(index: number) { | |||
// 确保index在有效范围内 | |||
if (index < 0) { | |||
console.error('Invalid thumbnail index:', index); | |||
return; | |||
} | |||
function retryLoadSlideImage(index: number) { | |||
isSlideThumbnailLoading.value[index] = true; | |||
slideThumbnailErrors.value[index] = false; | |||
// 确定正确的图片URL | |||
const images = [product.value?.image, ...(product.value?.gallery || [])]; | |||
const imageUrl = images[index]; | |||
// 检查图片URL是否有效 | |||
if (!imageUrl) { | |||
console.error('Invalid image URL for thumbnail:', index); | |||
thumbnailErrors.value[index] = true; | |||
isThumbnailLoading.value[index] = false; | |||
console.error("Invalid image URL:", index); | |||
slideThumbnailErrors.value[index] = true; | |||
isSlideThumbnailLoading.value[index] = false; | |||
return; | |||
} | |||
console.log('Retrying thumbnail load:', { index, imageUrl }); | |||
isThumbnailLoading.value[index] = true; | |||
thumbnailErrors.value[index] = false; | |||
// 创建新的图片对象并设置超时 | |||
const img = new Image(); | |||
const timeoutId = setTimeout(() => { | |||
console.error('Thumbnail load timeout:', index); | |||
handleThumbnailError(index); | |||
console.error("Image load timeout:", index); | |||
handleSlideImageError(index); | |||
}, 10000); // 10秒超时 | |||
img.onload = () => { | |||
clearTimeout(timeoutId); | |||
console.log('Thumbnail loaded successfully:', index); | |||
handleThumbnailLoad(index); | |||
console.log("Image loaded successfully:", index); | |||
handleSlideImageLoad(index); | |||
}; | |||
img.onerror = (error) => { | |||
clearTimeout(timeoutId); | |||
console.error('Thumbnail load error:', { index, error }); | |||
handleThumbnailError(index); | |||
console.error("Image load error:", { index, error }); | |||
handleSlideImageError(index); | |||
}; | |||
// 设置跨域属性 | |||
img.crossOrigin = 'anonymous'; | |||
img.crossOrigin = "anonymous"; | |||
// 最后设置src以开始加载 | |||
img.src = imageUrl; | |||
} | |||
/** | |||
* 处理缩略图加载完成 | |||
*/ | |||
function handleThumbnailLoad(index: number) { | |||
console.log('Thumbnail load handler called:', index); | |||
// 确保数组索引存在 | |||
if (typeof isThumbnailLoading.value[index] === 'undefined') { | |||
console.warn('Thumbnail index out of bounds:', index); | |||
return; | |||
} | |||
// 直接修改对应索引的状态 | |||
isThumbnailLoading.value[index] = false; | |||
thumbnailErrors.value[index] = false; | |||
} | |||
/** | |||
* 处理缩略图加载错误 | |||
*/ | |||
function handleThumbnailError(index: number) { | |||
console.log('Thumbnail error handler called:', index); | |||
// 确保数组索引存在 | |||
if (typeof isThumbnailLoading.value[index] === 'undefined') { | |||
console.warn('Thumbnail index out of bounds:', index); | |||
return; | |||
} | |||
// 直接修改对应索引的状态 | |||
isThumbnailLoading.value[index] = false; | |||
thumbnailErrors.value[index] = true; | |||
} | |||
/** | |||
* 切换图片 | |||
*/ | |||
function changeImage(image: string | undefined) { | |||
if (image && image !== currentImage.value) { | |||
isImageLoading.value = true; | |||
imageError.value = false; | |||
preloadNextImage(image); | |||
currentImage.value = image; | |||
} | |||
} | |||
// 页面加载时初始化状态 | |||
onMounted(() => { | |||
// 设置当前图片 | |||
if (product.value?.image) { | |||
currentImage.value = product.value.image; | |||
} | |||
// 初始化缩略图加载状态数组 | |||
const galleryLength = (product.value?.gallery?.length || 0) + 1; | |||
isThumbnailLoading.value = new Array(galleryLength).fill(true); | |||
thumbnailErrors.value = new Array(galleryLength).fill(false); | |||
isSlideThumbnailLoading.value = new Array(galleryLength).fill(true); | |||
slideThumbnailErrors.value = new Array(galleryLength).fill(false); | |||
// 设置初始加载状态 | |||
isInitialLoading.value = true; | |||
// 预加载所有缩略图 | |||
const images = [product.value?.image, ...(product.value?.gallery || [])]; | |||
images.forEach((image, index) => { | |||
if (image) { | |||
const img = new Image(); | |||
img.onload = () => handleThumbnailLoad(index); | |||
img.onerror = () => handleThumbnailError(index); | |||
img.onload = () => handleSlideImageLoad(index); | |||
img.onerror = () => handleSlideImageError(index); | |||
img.src = image; | |||
} | |||
}); | |||
console.log('Initialized thumbnail states:', { | |||
loading: isThumbnailLoading.value, | |||
errors: thumbnailErrors.value | |||
console.log("Initialized thumbnail states:", { | |||
loading: isSlideThumbnailLoading.value, | |||
errors: slideThumbnailErrors.value, | |||
}); | |||
// 添加滚动监听 | |||
scrollContainer.value = document.querySelector('.max-w-screen-2xl'); | |||
scrollContainer.value = document.querySelector(".max-w-screen-2xl"); | |||
if (scrollContainer.value) { | |||
window.addEventListener('scroll', handleScroll, { passive: true }); | |||
window.addEventListener("scroll", handleScroll, { passive: true }); | |||
} | |||
}); | |||
// 清理滚动监听 | |||
onUnmounted(() => { | |||
if (scrollContainer.value) { | |||
window.removeEventListener('scroll', handleScroll); | |||
window.removeEventListener("scroll", handleScroll); | |||
} | |||
}); | |||
// 处理滚动事件 | |||
function handleScroll() { | |||
if (!scrollContainer.value) return; | |||
const containerRect = scrollContainer.value.getBoundingClientRect(); | |||
const scrollTop = window.scrollY || document.documentElement.scrollTop; | |||
// 当容器顶部距离视窗顶部小于100px时,启用sticky | |||
isSticky.value = containerRect.top < 100; | |||
} | |||
@@ -733,4 +812,131 @@ useHead(() => ({ | |||
max-height: none; | |||
} | |||
} | |||
/* Swiper 样式定制 */ | |||
.product-swiper { | |||
:deep(.swiper-pagination-bullet) { | |||
background-color: white; | |||
opacity: 0.5; | |||
} | |||
:deep(.swiper-pagination-bullet-active) { | |||
opacity: 1; | |||
background-color: theme("colors.blue.500"); | |||
} | |||
:deep(.swiper-button-next), | |||
:deep(.swiper-button-prev) { | |||
color: theme("colors.blue.500"); | |||
background-color: rgba(0, 0, 0, 0.3); | |||
width: 36px; | |||
height: 36px; | |||
border-radius: 50%; | |||
&:after { | |||
font-size: 16px; | |||
font-weight: bold; | |||
} | |||
&:hover { | |||
background-color: theme("colors.blue.500"); | |||
color: white; | |||
} | |||
} | |||
:deep(.swiper-button-disabled) { | |||
opacity: 0.35; | |||
cursor: auto; | |||
pointer-events: none; | |||
} | |||
:deep(.swiper-slide) { | |||
display: flex; | |||
align-items: center; | |||
justify-content: center; | |||
} | |||
} | |||
/* 缩略图Swiper样式 */ | |||
.thumbs-swiper { | |||
overflow: visible; | |||
padding: 0.25rem; | |||
min-height: 6rem; | |||
:deep(.swiper-wrapper) { | |||
align-items: center; | |||
display: flex; | |||
min-height: 6rem; | |||
} | |||
:deep(.swiper-slide) { | |||
width: auto; | |||
height: auto; | |||
opacity: 0.7; | |||
transition: all 0.3s ease; | |||
display: flex; | |||
align-items: center; | |||
justify-content: center; | |||
} | |||
:deep(.swiper-slide-thumb-active) { | |||
opacity: 1; | |||
transform: scale(1.05); | |||
z-index: 1; | |||
} | |||
} | |||
/* 自定义缩略图导航按钮 */ | |||
.swiper-thumb-next, | |||
.swiper-thumb-prev { | |||
&:focus { | |||
outline: none; | |||
} | |||
&.swiper-button-disabled { | |||
opacity: 0.3; | |||
cursor: default; | |||
background-color: rgba(0, 0, 0, 0.2); | |||
&:hover { | |||
background-color: rgba(0, 0, 0, 0.2); | |||
} | |||
} | |||
} | |||
/* 响应式调整 */ | |||
@media (max-width: 768px) { | |||
.thumbs-swiper { | |||
padding: 0; | |||
:deep(.swiper-slide) { | |||
margin-right: 8px; | |||
} | |||
} | |||
/* 确保缩略图容器在移动设备上有足够空间 */ | |||
.product-thumbnail-container { | |||
padding: 1rem; | |||
.swiper-thumb-prev { | |||
left: 0.5rem; | |||
width: 2rem; | |||
height: 2rem; | |||
} | |||
.swiper-thumb-next { | |||
right: 0.5rem; | |||
width: 2rem; | |||
height: 2rem; | |||
} | |||
} | |||
} | |||
/* 解决不同分辨率下缩略图大小问题 */ | |||
.thumbnail-fixed-size { | |||
width: 100%; | |||
height: 100%; | |||
aspect-ratio: 1 / 1; | |||
position: relative; | |||
} | |||
</style> |
@@ -33,16 +33,20 @@ | |||
class="w-full object-cover h-40 sm:h-48 md:h-56 lg:h-64 xl:h-80" | |||
/> | |||
</div> | |||
<div class="max-w-full mb-4 md:mb-6 lg:mb-8 xl:px-8 lg:px-6 md:px-4 px-4"> | |||
<div | |||
class="max-w-full mb-4 md:mb-6 lg:mb-8 xl:px-8 lg:px-6 md:px-4 px-4" | |||
> | |||
<div class="max-w-screen-2xl mx-auto"> | |||
<nuxt-link | |||
to="/" | |||
:to="`${homepagePath}/`" | |||
class="text-white/60 text-sm md:text-base font-normal hover:text-white transition-colors duration-300" | |||
>{{ t("common.home") }}</nuxt-link | |||
> | |||
<span class="text-white/60 text-sm md:text-base font-normal px-2"> / </span> | |||
<span class="text-white/60 text-sm md:text-base font-normal px-2"> | |||
/ | |||
</span> | |||
<nuxt-link | |||
to="/products" | |||
:to="`${homepagePath}/products`" | |||
class="text-white text-sm md:text-base font-normal" | |||
>{{ t("products.product_list") }}</nuxt-link | |||
> | |||
@@ -52,13 +56,15 @@ | |||
class="max-w-full mb-8 md:mb-12 lg:mb-16 xl:mb-20 xl:px-8 lg:px-6 md:px-4 px-4" | |||
> | |||
<div class="max-w-screen-2xl mx-auto"> | |||
<div class="w-full grid grid-cols-1 md:grid-cols-10 gap-4 md:gap-6 lg:gap-8"> | |||
<div | |||
class="col-span-1 md:col-span-2 flex flex-col gap-4 sm:gap-6 md:gap-8 lg:gap-10 xl:gap-12 2xl:gap-16 mb-4 sm:mb-6 md:mb-8 lg:mb-10 xl:mb-12 2xl:mb-16" | |||
> | |||
<div class="flex flex-col gap-2 sm:gap-3 md:gap-4 lg:gap-5 xl:gap-6 "> | |||
<div class="w-full grid grid-cols-1 md:grid-cols-12 gap-4"> | |||
<div class="sticky top-24 col-span-1 md:col-span-3 flex flex-col gap-4 sm:gap-6 md:gap-8 lg:gap-10 xl:gap-12 2xl:gap-16 mb-4 sm:mb-6 md:mb-8 lg:mb-10 xl:mb-12 2xl:mb-16 pr-4"> | |||
<div | |||
class="flex flex-col gap-2 sm:gap-3 md:gap-4 lg:gap-5 xl:gap-6" | |||
> | |||
<div class="flex justify-between items-center"> | |||
<div class="text-white text-base sm:text-lg md:text-xl lg:text-2xl xl:text-3xl font-medium"> | |||
<div | |||
class="text-white text-base sm:text-lg md:text-xl lg:text-2xl xl:text-3xl font-medium" | |||
> | |||
{{ t("products.product_categories_title") }} | |||
</div> | |||
<button | |||
@@ -80,7 +86,9 @@ | |||
</svg> | |||
</button> | |||
</div> | |||
<div class="flex flex-row md:flex-col gap-1.5 sm:gap-2 md:gap-2.5 lg:gap-3 xl:gap-4 w-full md:w-fit overflow-x-auto md:overflow-x-visible pb-2 md:pb-0 "> | |||
<div | |||
class="flex flex-row md:flex-col gap-1.5 sm:gap-2 md:gap-2.5 lg:gap-3 xl:gap-4 w-full md:w-fit overflow-x-auto md:overflow-x-visible pb-2 md:pb-0 whitespace-nowrap md:whitespace-normal" | |||
> | |||
<div | |||
v-for="category in categories" | |||
:key="category" | |||
@@ -97,9 +105,13 @@ | |||
</div> | |||
</div> | |||
<div class="flex flex-col gap-2 sm:gap-3 md:gap-4 lg:gap-5 xl:gap-6"> | |||
<div | |||
class="flex-col gap-2 sm:gap-3 md:gap-4 lg:gap-5 xl:gap-6 hidden" | |||
> | |||
<div class="flex justify-between items-center"> | |||
<div class="text-white text-base sm:text-lg md:text-xl lg:text-2xl xl:text-3xl font-medium"> | |||
<div | |||
class="text-white text-base sm:text-lg md:text-xl lg:text-2xl xl:text-3xl font-medium" | |||
> | |||
{{ t("products.product_categories_usage") }} | |||
</div> | |||
<button | |||
@@ -121,12 +133,14 @@ | |||
</svg> | |||
</button> | |||
</div> | |||
<div class="flex flex-row md:flex-col gap-1.5 sm:gap-2 md:gap-2.5 lg:gap-3 xl:gap-4 w-full md:w-fit overflow-x-auto md:overflow-x-visible pb-2 md:pb-0"> | |||
<div | |||
class="flex flex-row md:flex-col gap-1.5 sm:gap-2 md:gap-2.5 lg:gap-3 xl:gap-4 w-full md:w-fit overflow-x-auto md:overflow-x-visible pb-2 md:pb-0 whitespace-nowrap md:whitespace-normal" | |||
> | |||
<div | |||
v-for="usage in usages" | |||
:key="usage" | |||
@click="handleUsageFilter(usage)" | |||
class="opacity-80 select-none text-white text-xs sm:text-sm md:text-base lg:text-lg font-normal cursor-pointer hover:opacity-100 transition-all duration-300 px-2 py-1 sm:px-2.5 sm:py-1.5 md:px-3 md:py-2 lg:px-4 lg:py-2.5 rounded-lg inline-block active:scale-95 whitespace-nowrap" | |||
class="opacity-80 select-none text-white text-xs sm:text-sm md:text-base lg:text-lg font-normal cursor-pointer hover:opacity-100 transition-all duration-300 px-2 py-1 sm:px-2.5 sm:py-1.5 md:px-3 md:py-2 lg:px-4 lg:py-2.5 rounded-lg inline-block active:scale-95" | |||
:class="{ | |||
'opacity-100 font-bold bg-gradient-to-r from-blue-700 to-blue-400 text-white border-0 shadow-lg scale-105 transition-all duration-300': | |||
selectedUsage === usage, | |||
@@ -137,8 +151,9 @@ | |||
</div> | |||
</div> | |||
</div> | |||
</div> | |||
<div class="col-span-1 md:col-span-8"> | |||
<div class="col-span-1 md:col-span-9"> | |||
<div class="flex flex-col gap-8 md:gap-12 lg:gap-16"> | |||
<template v-for="category in categories" :key="category"> | |||
<div | |||
@@ -152,55 +167,89 @@ | |||
" | |||
class="flex flex-col gap-4 md:gap-6" | |||
> | |||
<div class="w-full text-white text-2xl md:text-3xl lg:text-4xl font-normal mb-4 md:mb-6"> | |||
<div | |||
class="w-full text-white text-2xl md:text-3xl lg:text-4xl font-normal mb-4 md:mb-6" | |||
> | |||
{{ category }} | |||
</div> | |||
<!-- 添加系列分组 --> | |||
<template v-for="[seriesName, seriesProducts] in Array.from(productsBySeries.entries())" :key="seriesName"> | |||
<div v-if="seriesProducts.some(p => { | |||
const categoryObj = allCategories.find(c => c.title === category); | |||
return categoryObj && p.categoryId === categoryObj.id; | |||
})" class="mb-8 md:mb-12"> | |||
<template | |||
v-for="[seriesName, seriesProducts] in Array.from( | |||
productsBySeries.entries() | |||
)" | |||
:key="seriesName" | |||
> | |||
<div | |||
v-if=" | |||
seriesProducts.some((p) => { | |||
const categoryObj = allCategories.find( | |||
(c) => c.title === category | |||
); | |||
return ( | |||
categoryObj && p.categoryId === categoryObj.id | |||
); | |||
}) | |||
" | |||
class="mb-8 md:mb-12" | |||
> | |||
<div class="relative"> | |||
<!-- 系列标题背景 --> | |||
<div class="absolute inset-0 bg-gradient-to-r from-blue-900/20 to-blue-500/20 rounded-lg transform -skew-x-6"></div> | |||
<div | |||
class="absolute inset-0 bg-gradient-to-r from-blue-900/20 to-blue-500/20 rounded-lg transform -skew-x-6" | |||
></div> | |||
<!-- 系列标题内容 --> | |||
<div class="relative flex items-center gap-3 md:gap-4 py-3 md:py-4 px-4 md:px-6"> | |||
<div | |||
class="relative flex items-center gap-3 md:gap-4 py-3 md:py-4 px-4 md:px-6" | |||
> | |||
<!-- 装饰线 --> | |||
<div class="w-1 h-6 md:h-8 bg-gradient-to-b from-blue-500 to-blue-300 rounded-full"></div> | |||
<div | |||
class="w-1 h-6 md:h-8 bg-gradient-to-b from-blue-500 to-blue-300 rounded-full" | |||
></div> | |||
<!-- 系列名称 --> | |||
<h3 class="text-white text-lg md:text-xl lg:text-2xl font-medium tracking-wide"> | |||
<h3 | |||
class="text-white text-lg md:text-xl lg:text-2xl font-medium tracking-wide" | |||
> | |||
{{ seriesName }} | |||
</h3> | |||
<!-- 产品数量标签 --> | |||
<div class="ml-auto px-2 py-0.5 md:px-3 md:py-1 bg-blue-500/20 rounded-full text-blue-300 text-xs md:text-sm"> | |||
{{ seriesProducts.length }} {{ t("products.product_count") }} | |||
<div | |||
class="ml-auto px-2 py-0.5 md:px-3 md:py-1 bg-blue-500/20 rounded-full text-blue-300 text-xs md:text-sm" | |||
> | |||
{{ seriesProducts.length }} | |||
{{ t("products.product_count") }} | |||
</div> | |||
</div> | |||
</div> | |||
<!-- 产品网格 --> | |||
<div class="mt-4 md:mt-6"> | |||
<transition-group | |||
name="fade" | |||
tag="div" | |||
class="w-full grid grid-cols-2 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-4 gap-3 md:gap-4" | |||
class="w-full grid grid-cols-2 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-3 xl:grid-cols-4 gap-3 md:gap-4" | |||
> | |||
<nuxt-link | |||
v-for="product in seriesProducts.filter(p => { | |||
const categoryObj = allCategories.find(c => c.title === category); | |||
return categoryObj && p.categoryId === categoryObj.id; | |||
v-for="product in seriesProducts.filter((p) => { | |||
const categoryObj = allCategories.find( | |||
(c) => c.title === category | |||
); | |||
return ( | |||
categoryObj && | |||
p.categoryId === categoryObj.id | |||
); | |||
})" | |||
:key="product.id" | |||
:to="`/products/${product.name}`" | |||
:to="`${homepagePath}/products/${product.name}`" | |||
class="group bg-zinc-900 rounded-lg transition-all duration-300 hover:bg-zinc-800 hover:scale-105 hover:shadow-lg hover:ring-2 hover:ring-blue-400 relative overflow-hidden" | |||
> | |||
<div class="w-full p-4 md:p-6 lg:p-8"> | |||
<div class="relative w-full aspect-square mb-3 md:mb-4"> | |||
<div | |||
class="relative w-full aspect-square mb-3 md:mb-4" | |||
> | |||
<img | |||
v-if="!isImageError(product.id)" | |||
:src="product.image" | |||
@@ -242,17 +291,25 @@ | |||
</svg> | |||
</div> | |||
</div> | |||
<div class="text-center text-white text-base md:text-lg lg:text-xl font-normal mb-2"> | |||
<div | |||
class="text-center text-white text-base md:text-lg lg:text-xl font-normal mb-2" | |||
> | |||
{{ product.name }} | |||
</div> | |||
<div class="text-center text-stone-400 text-sm md:text-base font-normal leading-normal"> | |||
<div | |||
class="text-center text-stone-400 text-sm md:text-base font-normal leading-normal" | |||
> | |||
{{ product.capacities.join(" / ") }} | |||
</div> | |||
<!-- Summary 悬浮层 --> | |||
<div class="absolute inset-0 bg-gradient-to-t from-black/90 via-black/50 to-transparent opacity-0 group-hover:opacity-100 transition-all duration-300 flex items-end"> | |||
<div | |||
class="absolute inset-0 bg-gradient-to-t from-black/90 via-black/50 to-transparent opacity-0 group-hover:opacity-100 transition-all duration-300 flex items-end" | |||
> | |||
<div class="p-4 md:p-6 w-full"> | |||
<div class="text-white text-sm md:text-base line-clamp-3"> | |||
<div | |||
class="text-white text-sm md:text-base line-clamp-3" | |||
> | |||
{{ product.summary }} | |||
</div> | |||
</div> | |||
@@ -263,28 +320,44 @@ | |||
</div> | |||
</div> | |||
</template> | |||
<!-- 展示没有系列的产品 --> | |||
<transition-group | |||
v-if="filteredProducts.filter(p => { | |||
const categoryObj = allCategories.find(c => c.title === category); | |||
return categoryObj && p.categoryId === categoryObj.id && (!p.series || p.series.length === 0); | |||
}).length > 0" | |||
v-if=" | |||
filteredProducts.filter((p) => { | |||
const categoryObj = allCategories.find( | |||
(c) => c.title === category | |||
); | |||
return ( | |||
categoryObj && | |||
p.categoryId === categoryObj.id && | |||
(!p.series || p.series.length === 0) | |||
); | |||
}).length > 0 | |||
" | |||
name="fade" | |||
tag="div" | |||
class="w-full grid grid-cols-2 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-4 gap-3 md:gap-4" | |||
class="w-full grid grid-cols-2 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-3 xl:grid-cols-4 gap-3 md:gap-4" | |||
> | |||
<nuxt-link | |||
v-for="product in filteredProducts.filter(p => { | |||
const categoryObj = allCategories.find(c => c.title === category); | |||
return categoryObj && p.categoryId === categoryObj.id && (!p.series || p.series.length === 0); | |||
v-for="product in filteredProducts.filter((p) => { | |||
const categoryObj = allCategories.find( | |||
(c) => c.title === category | |||
); | |||
return ( | |||
categoryObj && | |||
p.categoryId === categoryObj.id && | |||
(!p.series || p.series.length === 0) | |||
); | |||
})" | |||
:key="product.id" | |||
:to="`/products/${product.name}`" | |||
:to="`${homepagePath}/products/${product.name}`" | |||
class="group bg-zinc-900 rounded-lg transition-all duration-300 hover:bg-zinc-800 hover:scale-105 hover:shadow-lg hover:ring-2 hover:ring-blue-400 relative overflow-hidden" | |||
> | |||
<div class="w-full p-4 md:p-6 lg:p-8"> | |||
<div class="relative w-full aspect-square mb-3 md:mb-4"> | |||
<div | |||
class="relative w-full aspect-square mb-3 md:mb-4" | |||
> | |||
<img | |||
v-if="!isImageError(product.id)" | |||
:src="product.image" | |||
@@ -326,17 +399,25 @@ | |||
</svg> | |||
</div> | |||
</div> | |||
<div class="text-center text-white text-base md:text-lg lg:text-xl font-normal mb-2"> | |||
<div | |||
class="text-center text-white text-base md:text-lg lg:text-xl font-normal mb-2" | |||
> | |||
{{ product.name }} | |||
</div> | |||
<div class="text-center text-stone-400 text-sm md:text-base font-normal leading-normal"> | |||
<div | |||
class="text-center text-stone-400 text-sm md:text-base font-normal leading-normal" | |||
> | |||
{{ product.capacities.join(" / ") }} | |||
</div> | |||
<!-- Summary 悬浮层 --> | |||
<div class="absolute inset-0 bg-gradient-to-t from-black/90 via-black/50 to-transparent opacity-0 group-hover:opacity-100 transition-all duration-300 flex items-end"> | |||
<div | |||
class="absolute inset-0 bg-gradient-to-t from-black/90 via-black/50 to-transparent opacity-0 group-hover:opacity-100 transition-all duration-300 flex items-end" | |||
> | |||
<div class="p-4 md:p-6 w-full"> | |||
<div class="text-white text-sm md:text-base line-clamp-3"> | |||
<div | |||
class="text-white text-sm md:text-base line-clamp-3" | |||
> | |||
{{ product.summary }} | |||
</div> | |||
</div> | |||
@@ -372,6 +453,10 @@ const { t, locale } = useI18n(); | |||
const route = useRoute(); | |||
const router = useRouter(); | |||
const homepagePath = computed(() => { | |||
return locale.value === "zh" ? "" : `/${locale.value}`; | |||
}); | |||
// 产品接口定义 | |||
interface Product { | |||
id: number; | |||
@@ -535,7 +620,7 @@ const categoryTitles = computed(() => | |||
// 使用计算属性优化系列分组 | |||
const productsBySeries = computed(() => { | |||
const seriesMap = new Map<string, Product[]>(); | |||
filteredProducts.value.forEach((product: Product) => { | |||
if (product.series && product.series.length > 0) { | |||
const series = product.series[0]; // 取第一个系列作为分组依据 | |||
@@ -545,7 +630,7 @@ const productsBySeries = computed(() => { | |||
seriesMap.get(series)?.push(product); | |||
} | |||
}); | |||
return seriesMap; | |||
}); | |||
@@ -892,7 +977,8 @@ button { | |||
button:hover { | |||
transform: translateY(-2px); | |||
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06); | |||
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), | |||
0 2px 4px -1px rgba(0, 0, 0, 0.06); | |||
} | |||
button:active { | |||
@@ -904,7 +990,7 @@ button:active { | |||
.bg-zinc-900\/50 { | |||
padding: 1rem; | |||
} | |||
.group:hover { | |||
transform: translateX(2px); | |||
} |
@@ -0,0 +1,68 @@ | |||
<template> | |||
<div class="min-h-screen bg-stone-950"> | |||
<div class="w-full h-[45px] sm:h-[55px] md:h-[65px] lg:h-[72px]"></div> | |||
<!-- 面包屑导航 --> | |||
<div class="max-w-screen-2xl mx-auto px-4 pt-8"> | |||
<nav class="flex items-center space-x-2 text-sm text-zinc-400"> | |||
<NuxtLink :to="homepagePath" class="hover:text-white transition">{{ | |||
t("common.breadcrumb.home") | |||
}}</NuxtLink> | |||
<span class="text-zinc-600">/</span> | |||
<span class="text-white">{{ t("common.breadcrumb.support") }}</span> | |||
</nav> | |||
</div> | |||
<!-- 主要内容 --> | |||
<div class="max-w-screen-2xl mx-auto px-4 py-8"> | |||
<!-- 页面标题 --> | |||
<div class="mb-8"> | |||
<h1 class="text-4xl font-bold text-white mb-4"> | |||
{{ t("common.support.title") }} | |||
</h1> | |||
<p class="text-zinc-400">{{ t("common.support.description") }}</p> | |||
</div> | |||
<!-- 内容区域 --> | |||
<div class="bg-stone-900 rounded-lg p-8 shadow-lg"> | |||
<div class="prose prose-invert max-w-none"> | |||
<ContentRenderer v-if="page" :value="page" /> | |||
</div> | |||
</div> | |||
</div> | |||
</div> | |||
</template> | |||
<script setup lang="ts"> | |||
/** | |||
* サポートページ | |||
* ウェブサイトのサポート情報を表示 | |||
*/ | |||
import { useI18n } from 'vue-i18n' | |||
import { computed } from 'vue' | |||
const { t, locale } = useI18n() | |||
const homepagePath = computed(() => { | |||
return locale.value === "zh" ? "/" : `/${locale.value}` | |||
}) | |||
// 计算内容路径 | |||
const contentPath = computed(() => { | |||
return `/${locale.value}/support` | |||
}) | |||
const { data: page } = await useAsyncData(contentPath.value, () => { | |||
return queryCollection("content").path(contentPath.value).first() | |||
}) | |||
// 设置页面元数据 | |||
useHead({ | |||
title: t("common.meta.support.title"), | |||
meta: [ | |||
{ | |||
name: "description", | |||
content: t("common.meta.support.description"), | |||
}, | |||
], | |||
}) | |||
</script> |
@@ -0,0 +1,68 @@ | |||
<template> | |||
<div class="min-h-screen bg-stone-950"> | |||
<div class="w-full h-[45px] sm:h-[55px] md:h-[65px] lg:h-[72px]"></div> | |||
<!-- 面包屑导航 --> | |||
<div class="max-w-screen-2xl mx-auto px-4 pt-8"> | |||
<nav class="flex items-center space-x-2 text-sm text-zinc-400"> | |||
<NuxtLink :to="homepagePath" class="hover:text-white transition">{{ | |||
t("common.breadcrumb.home") | |||
}}</NuxtLink> | |||
<span class="text-zinc-600">/</span> | |||
<span class="text-white">{{ t("common.breadcrumb.terms") }}</span> | |||
</nav> | |||
</div> | |||
<!-- 主要内容 --> | |||
<div class="max-w-screen-2xl mx-auto px-4 py-8"> | |||
<!-- 页面标题 --> | |||
<div class="mb-8"> | |||
<h1 class="text-4xl font-bold text-white mb-4"> | |||
{{ t("common.terms.title") }} | |||
</h1> | |||
<p class="text-zinc-400">{{ t("common.terms.description") }}</p> | |||
</div> | |||
<!-- 内容区域 --> | |||
<div class="bg-stone-900 rounded-lg p-8 shadow-lg"> | |||
<div class="prose prose-invert max-w-none"> | |||
<ContentRenderer v-if="page" :value="page" /> | |||
</div> | |||
</div> | |||
</div> | |||
</div> | |||
</template> | |||
<script setup lang="ts"> | |||
/** | |||
* 利用規約ページ | |||
* ウェブサイトの利用規約を表示 | |||
*/ | |||
import { useI18n } from 'vue-i18n' | |||
import { computed } from 'vue' | |||
const { t, locale } = useI18n() | |||
const homepagePath = computed(() => { | |||
return locale.value === "zh" ? "/" : `/${locale.value}` | |||
}) | |||
// 计算内容路径 | |||
const contentPath = computed(() => { | |||
return `/${locale.value}/terms` | |||
}) | |||
const { data: page } = await useAsyncData(contentPath.value, () => { | |||
return queryCollection("content").path(contentPath.value).first() | |||
}) | |||
// 设置页面元数据 | |||
useHead({ | |||
title: t("common.meta.terms.title"), | |||
meta: [ | |||
{ | |||
name: "description", | |||
content: t("common.meta.terms.description"), | |||
}, | |||
], | |||
}) | |||
</script> |
@@ -105,9 +105,6 @@ | |||
"/en/products/HSE-M20GRC01", | |||
"/en/products/HSE-M40GRC01", | |||
"/en/products/HSE-M4IN1GRC01", | |||
"/en/products/HSE-M4IN1GRC02", | |||
"/en/products/HSE-M4IN1GRC03", | |||
"/en/products/HSE-M4IN1GRC04", | |||
"/en/products/HSE-M6IN1GRC01", | |||
"/en/products/M200-SC256G", | |||
"/en/products/M200-SC512G", | |||
@@ -139,6 +136,9 @@ | |||
"/en/products/SD5-16GB-5600-1R8", | |||
"/en/products/UD-032G01A1", | |||
"/en/products/UD-064G01A1", | |||
"/en/products/UD-128G01A1", | |||
"/en/products/UD-256G01A1", | |||
"/en/products/UD-512G01A1", | |||
"/en/products/UD4-08GB-2666-1R8", | |||
"/en/products/UD4-08GB-3200-1R8", | |||
"/en/products/UD4-16GB-2666-2R8", | |||
@@ -179,9 +179,6 @@ | |||
"/products/HSE-M20GRC01", | |||
"/products/HSE-M40GRC01", | |||
"/products/HSE-M4IN1GRC01", | |||
"/products/HSE-M4IN1GRC02", | |||
"/products/HSE-M4IN1GRC03", | |||
"/products/HSE-M4IN1GRC04", | |||
"/products/HSE-M6IN1GRC01", | |||
"/products/M200-SC256G", | |||
"/products/M200-SC512G", |
@@ -370,56 +370,6 @@ | |||
], | |||
"capacities": [] | |||
}, | |||
{ | |||
"id": "HSE-M4IN1GRC02", | |||
"title": "HSE-M4IN1GRC02", | |||
"name": "HSE-M4IN1GRC02", | |||
"usage": [ | |||
"移动数据传输/备份" | |||
], | |||
"category": 6, | |||
"image": "/images/products/M.2 SSD Enclosure/HSE-M4IN1GRC02/hse-m4in1grc02.webp", | |||
"description": "M.2 SSD外置盒,支持NVMe/SATA协议", | |||
"summary": "支持M.2 NVMe/SATA SSD,最大支持4TB容量,支持SSD尺寸为2230/2242/2260/2280。", | |||
"series": [ | |||
"M.2 SSD Enclosure" | |||
], | |||
"gallery": [ | |||
"/images/products/M.2 SSD Enclosure/HSE-M4IN1GRC02/hse-m4in1grc02-1.webp" | |||
], | |||
"capacities": [] | |||
}, | |||
{ | |||
"id": "HSE-M4IN1GRC03", | |||
"title": "HSE-M4IN1GRC03", | |||
"name": "HSE-M4IN1GRC03", | |||
"usage": [], | |||
"category": "", | |||
"description": "", | |||
"summary": "", | |||
"series": [], | |||
"gallery": [], | |||
"capacities": [] | |||
}, | |||
{ | |||
"id": "HSE-M4IN1GRC04", | |||
"title": "HSE-M4IN1GRC04", | |||
"name": "HSE-M4IN1GRC04", | |||
"usage": [ | |||
"移动数据传输/备份" | |||
], | |||
"category": 6, | |||
"image": "/images/products/M.2 SSD Enclosure/HSE-M4IN1GRC04/hse-m4in1grc04.webp", | |||
"description": "M.2 SSD外置盒,支持NVMe/SATA协议", | |||
"summary": "支持M.2 NVMe/SATA SSD,最大支持4TB容量,支持SSD尺寸为2230/2242/2260/2280。", | |||
"series": [ | |||
"M.2 SSD Enclosure" | |||
], | |||
"gallery": [ | |||
"/images/products/M.2 SSD Enclosure/HSE-M4IN1GRC04/hse-m4in1grc04-1.webp" | |||
], | |||
"capacities": [] | |||
}, | |||
{ | |||
"id": "HSE-M6IN1GRC01", | |||
"title": "HSE-M6IN1GRC01", |