- 添加支持页面,提供技术支持服务入口,包含FAQ和联系信息。 - 新增FAQ内容组件,支持分类和搜索功能,提升用户体验。 - 更新国际化文件,增加支持页面相关内容的多语言支持。 - 删除旧的FAQ页面,整合至新的支持页面中,优化代码结构。master
@@ -0,0 +1,472 @@ | |||
<template> | |||
<div class="faq-content"> | |||
<div v-if="isLoading" class="flex justify-center py-12"> | |||
<div class="animate-spin h-8 w-8 border-2 border-blue-400 border-t-transparent rounded-full"></div> | |||
</div> | |||
<div v-else class="p-8"> | |||
<!-- 分类和搜索区域 --> | |||
<div class="mb-8 space-y-6"> | |||
<!-- 分类选择 --> | |||
<div class="flex flex-wrap gap-2"> | |||
<button | |||
v-for="category in categoriesList" | |||
:key="category" | |||
@click="handleCategoryFilter(category)" | |||
class="px-4 py-2 rounded-md text-sm font-medium transition-colors duration-200 border" | |||
:class="{ | |||
'bg-blue-600 text-white border-blue-600': selectedCategory === category, | |||
'bg-gray-700 text-gray-300 border-gray-600 hover:bg-gray-600': selectedCategory !== category, | |||
}" | |||
> | |||
{{ category }} | |||
</button> | |||
</div> | |||
<!-- 搜索框 --> | |||
<div class="relative max-w-md"> | |||
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none"> | |||
<svg class="h-5 w-5 text-gray-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"> | |||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path> | |||
</svg> | |||
</div> | |||
<input | |||
v-model="searchTerm" | |||
type="search" | |||
:placeholder="t('faq.searchPlaceholder')" | |||
class="w-full pl-10 pr-10 py-3 border border-gray-600 rounded-md text-white bg-gray-700 placeholder-gray-400 focus:border-blue-500 focus:ring-1 focus:ring-blue-500 focus:outline-none" | |||
/> | |||
<button | |||
v-if="searchTerm" | |||
@click="clearSearch" | |||
class="absolute inset-y-0 right-0 pr-3 flex items-center text-gray-500 hover:text-gray-300" | |||
> | |||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"> | |||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path> | |||
</svg> | |||
</button> | |||
</div> | |||
</div> | |||
<!-- FAQ列表 --> | |||
<div class="space-y-3"> | |||
<div | |||
v-for="faq in filteredFaqs" | |||
:key="generateFaqKey(faq)" | |||
class="border border-gray-600 rounded-lg overflow-hidden" | |||
> | |||
<div | |||
class="flex items-center justify-between p-6 cursor-pointer hover:bg-gray-700 transition-colors duration-200" | |||
@click="toggleFaq(faq)" | |||
> | |||
<div class="text-white font-medium flex-1 text-left"> | |||
<template | |||
v-for="(part, i) in highlightKeyword(faq.title)" | |||
:key="i" | |||
> | |||
<span v-if="typeof part === 'string'">{{ part }}</span> | |||
<component v-else :is="part"></component> | |||
</template> | |||
</div> | |||
<div | |||
class="text-gray-400 transition-transform duration-200 ml-4 flex-shrink-0" | |||
:class="{ 'rotate-180': isFaqExpanded(faq) }" | |||
> | |||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"> | |||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path> | |||
</svg> | |||
</div> | |||
</div> | |||
<div | |||
v-if="isFaqExpanded(faq)" | |||
class="px-6 pb-6 border-t border-gray-600 bg-gray-750" | |||
> | |||
<div class="pt-6"> | |||
<ContentRenderer | |||
class="prose prose-invert prose-sm max-w-none" | |||
:value="{ body: faq.content }" | |||
v-highlight="searchTerm.trim()" | |||
/> | |||
</div> | |||
</div> | |||
</div> | |||
<div | |||
v-if="filteredFaqs.length === 0" | |||
class="text-center text-gray-400 py-16" | |||
> | |||
<div class="mb-4"> | |||
<svg class="mx-auto h-12 w-12 text-gray-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"> | |||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path> | |||
</svg> | |||
</div> | |||
<p class="text-lg font-medium text-white mb-2">{{ t("faq.noResults") }}</p> | |||
<p class="text-sm text-gray-500">尝试调整搜索条件或选择不同的分类</p> | |||
</div> | |||
</div> | |||
</div> | |||
</div> | |||
</template> | |||
<script setup lang="ts"> | |||
/** | |||
* FAQ内容组件 | |||
* 专业的常见问题解答展示组件 | |||
*/ | |||
import { useErrorHandler } from "~/composables/useErrorHandler"; | |||
import { queryCollection } from "#imports"; | |||
const { error, isLoading, wrapAsync } = useErrorHandler(); | |||
const { t, locale } = useI18n(); | |||
// FAQ数据接口 | |||
interface FAQ { | |||
category: string; | |||
title: string; | |||
content: any; | |||
sort: number; | |||
id?: string; | |||
} | |||
// 响应式数据 | |||
const faqs = ref<FAQ[]>([]); | |||
const categoriesList = ref<string[]>([]); | |||
const selectedCategory = ref(""); | |||
const expandedFaqKeys = ref<Set<string>>(new Set()); | |||
const searchTerm = ref(""); | |||
// 使用 queryCollection 加载FAQ数据 | |||
const { data: faqData } = await useAsyncData( | |||
"faqs", | |||
async () => { | |||
try { | |||
const content = await queryCollection("content") | |||
.where("path", "LIKE", `/faq/${locale.value}/%`) | |||
.all(); | |||
if (!content || !Array.isArray(content)) { | |||
console.error("No FAQ content found or invalid format:", content); | |||
return []; | |||
} | |||
const faqItems = content.map((item: any) => { | |||
return { | |||
category: item.meta?.category || "", | |||
title: item.title || "", | |||
content: item.body || "", | |||
sort: item.meta?.sort || 0, | |||
}; | |||
}); | |||
return faqItems.sort((a, b) => a.sort - b.sort); | |||
} catch (error) { | |||
console.error("Error loading FAQ content:", error); | |||
return []; | |||
} | |||
}, | |||
{ | |||
server: true, | |||
lazy: false, | |||
immediate: true, | |||
watch: [locale], | |||
} | |||
); | |||
// 处理FAQ数据变化 | |||
watchEffect(() => { | |||
if (faqData.value) { | |||
isLoading.value = true; | |||
try { | |||
const allOption: string = | |||
locale.value === "en" | |||
? "All" | |||
: locale.value === "zh" | |||
? "全部" | |||
: "すべて"; | |||
const uniqueCategories = [ | |||
...new Set(faqData.value.map((faq: FAQ) => faq.category)), | |||
] | |||
.filter((category) => category) | |||
.sort(); | |||
categoriesList.value = [allOption, ...uniqueCategories]; | |||
selectedCategory.value = categoriesList.value[0]; | |||
} catch (err) { | |||
console.error("Error processing FAQ data:", err); | |||
error.value = new Error(t("faq.processError")); | |||
} finally { | |||
isLoading.value = false; | |||
} | |||
} | |||
}); | |||
// 过滤后的FAQ列表 | |||
const filteredFaqs = computed(() => { | |||
if (!faqData.value) { | |||
return []; | |||
} | |||
let filtered = [...faqData.value]; | |||
// 按分类过滤 | |||
const allOption: string = | |||
locale.value === "en" | |||
? "All" | |||
: locale.value === "zh" | |||
? "全部" | |||
: "すべて"; | |||
if (selectedCategory.value && selectedCategory.value !== allOption) { | |||
filtered = filtered.filter((faq) => faq.category === selectedCategory.value); | |||
} | |||
// 按搜索词过滤 | |||
if (searchTerm.value.trim()) { | |||
const search = searchTerm.value.trim().toLowerCase(); | |||
filtered = filtered.filter((faq) => | |||
faq.title.toLowerCase().includes(search) | |||
); | |||
} | |||
return filtered; | |||
}); | |||
/** | |||
* 生成FAQ的唯一标识 | |||
*/ | |||
const generateFaqKey = (faq: FAQ): string => { | |||
return `${faq.category}-${faq.title}-${faq.sort}`; | |||
}; | |||
/** | |||
* 检查FAQ是否展开 | |||
*/ | |||
const isFaqExpanded = (faq: FAQ): boolean => { | |||
return expandedFaqKeys.value.has(generateFaqKey(faq)); | |||
}; | |||
/** | |||
* 切换FAQ展开状态 | |||
*/ | |||
const toggleFaq = (faq: FAQ): void => { | |||
const key = generateFaqKey(faq); | |||
if (expandedFaqKeys.value.has(key)) { | |||
expandedFaqKeys.value.delete(key); | |||
} else { | |||
expandedFaqKeys.value.add(key); | |||
} | |||
}; | |||
/** | |||
* 处理分类过滤 | |||
*/ | |||
const handleCategoryFilter = (category: string): void => { | |||
selectedCategory.value = category; | |||
// 清空展开状态 | |||
expandedFaqKeys.value.clear(); | |||
}; | |||
/** | |||
* 清除搜索 | |||
*/ | |||
const clearSearch = (): void => { | |||
searchTerm.value = ""; | |||
}; | |||
/** | |||
* 高亮关键词 | |||
*/ | |||
const highlightKeyword = (text: string) => { | |||
if (!searchTerm.value.trim()) { | |||
return [text]; | |||
} | |||
const keyword = searchTerm.value.trim(); | |||
const regex = new RegExp(`(${keyword})`, "gi"); | |||
const parts = text.split(regex); | |||
return parts.map((part) => { | |||
if (part.toLowerCase() === keyword.toLowerCase()) { | |||
return h("mark", { class: "bg-yellow-500/30 text-yellow-200 px-1 rounded" }, part); | |||
} | |||
return part; | |||
}); | |||
}; | |||
// 自定义指令:高亮搜索结果 | |||
const vHighlight = { | |||
mounted(el: HTMLElement, binding: { value: string }) { | |||
highlightText(el, binding.value); | |||
}, | |||
updated(el: HTMLElement, binding: { value: string }) { | |||
highlightText(el, binding.value); | |||
}, | |||
}; | |||
/** | |||
* 高亮文本中的关键词 | |||
*/ | |||
const highlightText = (el: HTMLElement, keyword: string): void => { | |||
if (!keyword || !keyword.trim()) { | |||
return; | |||
} | |||
const walker = document.createTreeWalker( | |||
el, | |||
NodeFilter.SHOW_TEXT, | |||
null | |||
); | |||
const nodes: Text[] = []; | |||
let node: Text | null; | |||
while ((node = walker.nextNode() as Text)) { | |||
nodes.push(node); | |||
} | |||
nodes.forEach((textNode) => { | |||
const parent = textNode.parentNode; | |||
if (!parent || parent.nodeName === "MARK") return; | |||
const text = textNode.textContent || ""; | |||
const regex = new RegExp(`(${keyword.trim()})`, "gi"); | |||
if (regex.test(text)) { | |||
const fragment = document.createDocumentFragment(); | |||
const parts = text.split(regex); | |||
parts.forEach((part) => { | |||
if (part.toLowerCase() === keyword.trim().toLowerCase()) { | |||
const mark = document.createElement("mark"); | |||
mark.className = "bg-yellow-500/30 text-yellow-200 px-1 rounded"; | |||
mark.textContent = part; | |||
fragment.appendChild(mark); | |||
} else if (part) { | |||
fragment.appendChild(document.createTextNode(part)); | |||
} | |||
}); | |||
parent.replaceChild(fragment, textNode); | |||
} | |||
}); | |||
}; | |||
</script> | |||
<style scoped> | |||
/* 过渡效果 */ | |||
.transition-colors { | |||
transition-property: color, background-color, border-color; | |||
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); | |||
} | |||
.transition-transform { | |||
transition-property: transform; | |||
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); | |||
} | |||
.duration-200 { | |||
transition-duration: 200ms; | |||
} | |||
/* 悬停效果 */ | |||
.hover\:bg-gray-700:hover { | |||
background-color: #374151; | |||
} | |||
.hover\:bg-gray-600:hover { | |||
background-color: #4b5563; | |||
} | |||
.hover\:text-gray-300:hover { | |||
color: #d1d5db; | |||
} | |||
/* 焦点样式 */ | |||
input:focus { | |||
box-shadow: 0 0 0 1px #3b82f6; | |||
} | |||
/* 高亮样式 */ | |||
mark { | |||
background-color: rgba(234, 179, 8, 0.3); | |||
color: #fef3c7; | |||
padding: 2px 4px; | |||
border-radius: 4px; | |||
} | |||
/* Prose样式覆盖 - 深色主题 */ | |||
:deep(.prose) { | |||
color: #d1d5db; | |||
max-width: none; | |||
} | |||
:deep(.prose h1), | |||
:deep(.prose h2), | |||
:deep(.prose h3), | |||
:deep(.prose h4), | |||
:deep(.prose h5), | |||
:deep(.prose h6) { | |||
color: #f9fafb; | |||
font-weight: 600; | |||
} | |||
:deep(.prose p) { | |||
margin-top: 1rem; | |||
margin-bottom: 1rem; | |||
line-height: 1.625; | |||
color: #d1d5db; | |||
} | |||
:deep(.prose ul), | |||
:deep(.prose ol) { | |||
margin-top: 1rem; | |||
margin-bottom: 1rem; | |||
} | |||
:deep(.prose li) { | |||
margin-top: 0.5rem; | |||
margin-bottom: 0.5rem; | |||
color: #d1d5db; | |||
} | |||
:deep(.prose strong) { | |||
color: #f9fafb; | |||
font-weight: 600; | |||
} | |||
:deep(.prose code) { | |||
color: #f9fafb; | |||
background-color: #374151; | |||
padding: 0.125rem 0.25rem; | |||
border-radius: 0.25rem; | |||
font-size: 0.875em; | |||
} | |||
:deep(.prose a) { | |||
color: #60a5fa; | |||
text-decoration: underline; | |||
} | |||
:deep(.prose a:hover) { | |||
color: #93c5fd; | |||
} | |||
/* 自定义gray-750背景色 */ | |||
.bg-gray-750 { | |||
background-color: #2d3748; | |||
} | |||
/* 响应式优化 */ | |||
@media (max-width: 768px) { | |||
.flex-wrap { | |||
gap: 0.5rem; | |||
} | |||
.px-6 { | |||
padding-left: 1rem; | |||
padding-right: 1rem; | |||
} | |||
} | |||
</style> |
@@ -196,7 +196,7 @@ const menuWebsiteItems = computed(() => [ | |||
}, | |||
{ | |||
label: "common.footer.websiteLinks.faq", | |||
path: locale.value === defaultLocale ? "/faq" : `/${locale.value}/faq`, | |||
path: locale.value === defaultLocale ? "/support" : `/${locale.value}/support`, | |||
}, | |||
{ | |||
label: "common.footer.websiteLinks.about", |
@@ -41,7 +41,11 @@ | |||
<div | |||
@mouseenter="handleMouseEnter(item.label)" | |||
class="justify-start text-white text-sm opacity-80 hover:opacity-100 transition-opacity cursor-pointer flex items-center gap-1 py-2 px-3 rounded-md" | |||
:class="[route.path.startsWith(item.pathPrefix) ? 'font-bold opacity-100 bg-white/15' : '']" | |||
:class="[ | |||
route.path.startsWith(item.pathPrefix) | |||
? 'font-bold opacity-100 bg-white/15' | |||
: '', | |||
]" | |||
> | |||
<span>{{ t(item.label) }}</span> | |||
</div> | |||
@@ -54,18 +58,24 @@ | |||
class="fixed left-0 top-[70px] w-screen bg-slate-900/95 backdrop-blur-[50px] border-t border-b border-slate-700/20 shadow-2xl z-10" | |||
> | |||
<!-- 内容居中容器 --> | |||
<div class="max-w-screen-2xl mx-auto px-4 sm:px-6 lg:px-10 py-12"> | |||
<div | |||
class="max-w-screen-2xl mx-auto px-4 sm:px-6 lg:px-10 py-12" | |||
> | |||
<!-- 主内容网格 --> | |||
<div> | |||
<!-- 产品分类列 (原有的分类逻辑和数据) --> | |||
<div> | |||
<h3 class="text-xs font-semibold text-gray-400 uppercase tracking-wide mb-4"> | |||
<h3 | |||
class="text-xs font-semibold text-gray-400 uppercase tracking-wide mb-4" | |||
> | |||
{{ t("common.productCategories") }} | |||
</h3> | |||
<div class="flex gap-x-32 gap-y-8"> | |||
<div class="flex gap-x-40 gap-y-8"> | |||
<!-- 企业用户产品组 --> | |||
<div> | |||
<h4 class="text-base font-semibold text-white mb-3"> | |||
<h4 | |||
class="text-lg font-semibold text-white mb-6" | |||
> | |||
<nuxt-link | |||
:to="`${homePath}products?audiences=1`" | |||
class="flex items-center gap-2 hover:text-cyan-400 transition-colors" | |||
@@ -76,7 +86,9 @@ | |||
</h4> | |||
<ul class="space-y-2"> | |||
<li | |||
v-for="link in getGroupedItems(item.children[0].items).enterprise" | |||
v-for="link in getGroupedItems( | |||
item.children[0].items | |||
).enterprise" | |||
:key="link.path" | |||
> | |||
<nuxt-link | |||
@@ -86,8 +98,15 @@ | |||
> | |||
{{ t(link.label) }} | |||
</nuxt-link> | |||
<ul v-if="link.tags && link.tags.length" class="mt-1"> | |||
<li v-for="tag in link.tags" :key="tag"> | |||
<ul | |||
v-if="link.tags && link.tags.length" | |||
class="mt-2" | |||
> | |||
<li | |||
v-for="tag in link.tags" | |||
:key="tag" | |||
class="mt-4 mb-4" | |||
> | |||
<nuxt-link | |||
:to="`${link.path}&tag=${tag}`" | |||
@click.stop="handleMouseLeave" | |||
@@ -103,7 +122,9 @@ | |||
<!-- 个人用户产品组 --> | |||
<div class="flex-1"> | |||
<h4 class="text-base font-semibold text-white mb-3"> | |||
<h4 | |||
class="text-lg font-semibold text-white mb-6" | |||
> | |||
<nuxt-link | |||
:to="`${homePath}products?audiences=0`" | |||
class="flex items-center gap-2 hover:text-cyan-400 transition-colors" | |||
@@ -112,9 +133,11 @@ | |||
<span>{{ t("home.personal.title") }}</span> | |||
</nuxt-link> | |||
</h4> | |||
<ul class="flex gap-12"> | |||
<ul class="flex gap-24"> | |||
<li | |||
v-for="link in getGroupedItems(item.children[0].items).personal" | |||
v-for="link in getGroupedItems( | |||
item.children[0].items | |||
).personal" | |||
:key="link.path" | |||
> | |||
<nuxt-link | |||
@@ -124,8 +147,15 @@ | |||
> | |||
{{ t(link.label) }} | |||
</nuxt-link> | |||
<ul v-if="link.tags && link.tags.length" class="mt-1"> | |||
<li v-for="tag in link.tags" :key="tag"> | |||
<ul | |||
v-if="link.tags && link.tags.length" | |||
class="mt-2" | |||
> | |||
<li | |||
v-for="tag in link.tags" | |||
:key="tag" | |||
class="mt-4 mb-4" | |||
> | |||
<nuxt-link | |||
:to="`${link.path}&tag=${tag}`" | |||
@click.stop="handleMouseLeave" | |||
@@ -556,7 +586,7 @@ const menuItems = computed(() => { | |||
}, | |||
], | |||
}, | |||
{ label: "common.faq", path: `${prefix}/faq` }, | |||
{ label: "support.title", path: `${prefix}/support` }, | |||
{ label: "common.about", path: `${prefix}/about` }, | |||
{ label: "common.contact", path: `${prefix}/contact` }, | |||
]; | |||
@@ -863,7 +893,11 @@ header { | |||
/* 添加下拉菜单背景渐变 */ | |||
.bg-slate-900\/95 { | |||
background: linear-gradient(to bottom, rgba(15, 23, 42, 0.95), rgba(15, 23, 42, 0.98)); | |||
background: linear-gradient( | |||
to bottom, | |||
rgba(15, 23, 42, 0.95), | |||
rgba(15, 23, 42, 0.98) | |||
); | |||
} | |||
/* 响应式布局 */ | |||
@@ -911,4 +945,3 @@ header { | |||
} | |||
} | |||
</style> | |||
@@ -185,6 +185,44 @@ export default { | |||
category: "Category", | |||
noResults: "No results found", | |||
clearSearch: "Clear search", | |||
processError: "Error processing FAQ data", | |||
}, | |||
support: { | |||
title: "Support", | |||
subtitle: "We provide comprehensive technical support services to help you solve various product-related issues", | |||
description: "Get professional technical support services, including FAQ, product manuals, driver downloads, and more", | |||
keywords: "Hanye, technical support, customer service, FAQ, product manuals, driver downloads", | |||
viewMore: "View More", | |||
contactUs: "Contact Us", | |||
viewDocs: "View Documentation", | |||
download: "Download", | |||
startChat: "Start Chat", | |||
comingSoon: "Coming Soon", | |||
faq: { | |||
title: "FAQ", | |||
description: "Browse the most common product questions and answers" | |||
}, | |||
contact: { | |||
title: "Contact Us", | |||
description: "Get in touch with our technical support team directly" | |||
}, | |||
docs: { | |||
title: "Technical Documentation", | |||
description: "Access detailed technical documents and user guides" | |||
}, | |||
manuals: { | |||
title: "Product Manuals", | |||
description: "Download product manuals and installation guides" | |||
}, | |||
drivers: { | |||
title: "Driver Downloads", | |||
description: "Get the latest product drivers and software" | |||
}, | |||
chat: { | |||
title: "Live Chat", | |||
description: "Chat with our technical experts in real-time" | |||
}, | |||
backToSupport: "Back to Support" | |||
}, | |||
about: { | |||
title: "About Us", | |||
@@ -283,7 +321,7 @@ export default { | |||
title: "Technical Support & Quality Management", | |||
description: "We aim to build long-term relationships of trust through professional technical support, consulting, and comprehensive after-sales service.", | |||
support: { | |||
title: "Technical Support", | |||
title: "Support", | |||
item1: "Professional technical consultation", | |||
item2: "Optimal solution proposals", | |||
item3: "Technical training", |
@@ -177,6 +177,44 @@ export default { | |||
category: "カテゴリー", | |||
noResults: "該当する質問はありません", | |||
clearSearch: "検索をクリア", | |||
processError: "FAQ データの処理中にエラーが発生しました", | |||
}, | |||
support: { | |||
title: "技術サポート", | |||
subtitle: "お客様の製品利用に関する様々な問題を解決するため、包括的な技術サポートサービスを提供しています", | |||
description: "よくある質問、製品マニュアル、ドライバーダウンロードなど、専門的な技術サポートサービスをご利用ください", | |||
keywords: "Hanye, 技術サポート, カスタマーサービス, FAQ, 製品マニュアル, ドライバーダウンロード", | |||
viewMore: "詳細を見る", | |||
contactUs: "お問い合わせ", | |||
viewDocs: "ドキュメントを見る", | |||
download: "ダウンロード", | |||
startChat: "チャットを開始", | |||
comingSoon: "近日公開", | |||
faq: { | |||
title: "FAQ", | |||
description: "最も一般的な製品の質問と回答をご覧ください" | |||
}, | |||
contact: { | |||
title: "お問い合わせ", | |||
description: "技術サポートチームに直接お問い合わせください" | |||
}, | |||
docs: { | |||
title: "技術ドキュメント", | |||
description: "詳細な技術ドキュメントとユーザーガイドをご覧ください" | |||
}, | |||
manuals: { | |||
title: "製品マニュアル", | |||
description: "製品取扱説明書とインストールガイドをダウンロード" | |||
}, | |||
drivers: { | |||
title: "ドライバーダウンロード", | |||
description: "最新の製品ドライバーとソフトウェアを取得" | |||
}, | |||
chat: { | |||
title: "ライブチャット", | |||
description: "技術専門家とリアルタイムでチャット" | |||
}, | |||
backToSupport: "技術サポートに戻る" | |||
}, | |||
about: { | |||
title: "当社について - Hanye", |
@@ -174,6 +174,44 @@ export default { | |||
category: "分类", | |||
clearSearch: "清除搜索", | |||
noResults: "没有找到相关问题", | |||
processError: "处理FAQ数据时出错", | |||
}, | |||
support: { | |||
title: "技术支持", | |||
subtitle: "我们提供全方位的技术支持服务,帮助您解决产品使用中的各种问题", | |||
description: "获取专业的技术支持服务,包括常见问题、产品手册、驱动下载等", | |||
keywords: "Hanye, 技术支持, 客服, FAQ, 产品手册, 驱动下载", | |||
viewMore: "查看更多", | |||
contactUs: "联系我们", | |||
viewDocs: "查看文档", | |||
download: "下载", | |||
startChat: "开始对话", | |||
comingSoon: "即将上线", | |||
faq: { | |||
title: "常见问题", | |||
description: "查看最常见的产品问题和解答" | |||
}, | |||
contact: { | |||
title: "联系我们", | |||
description: "直接联系我们的技术支持团队" | |||
}, | |||
docs: { | |||
title: "技术文档", | |||
description: "查看详细的技术文档和使用指南" | |||
}, | |||
manuals: { | |||
title: "产品手册", | |||
description: "下载产品说明书和安装指南" | |||
}, | |||
drivers: { | |||
title: "驱动下载", | |||
description: "获取最新的产品驱动程序" | |||
}, | |||
chat: { | |||
title: "在线客服", | |||
description: "与我们的技术专家实时对话" | |||
}, | |||
backToSupport: "返回技术支持" | |||
}, | |||
about: { | |||
title: "关于我们", |
@@ -80,10 +80,13 @@ | |||
<!-- 关于我们 (公司简介) --> | |||
<div | |||
id="company-profile" | |||
class="section-block w-full bg-gradient-to-b from-zinc-900 via-zinc-800/80 to-zinc-900 py-40" | |||
class="section-block w-full bg-gradient-to-b from-zinc-900 via-zinc-800/80 to-zinc-900 py-40 relative overflow-hidden" | |||
ref="companyProfileRef" | |||
> | |||
<div class="max-w-screen-2xl mx-auto px-4 sm:px-6 lg:px-8"> | |||
<!-- 商务风格背景图 --> | |||
<div class="absolute inset-0 bg-business-overlay"></div> | |||
<div class="absolute inset-0 bg-gradient-to-b from-black/70 via-black/50 to-black/70"></div> | |||
<div class="max-w-screen-2xl mx-auto px-4 sm:px-6 lg:px-8 relative z-10"> | |||
<div class="text-center mb-32 animate-fade-in"> | |||
<h2 | |||
class="section-title text-[#35F1FF] text-sm font-bold mb-4 tracking-[0.2em] animate-slide-in-left" | |||
@@ -637,13 +640,13 @@ onMounted(() => { | |||
// 使用 Intersection Observer 优化性能和准确性 | |||
const observerOptions = { | |||
root: null, // 视口 | |||
rootMargin: '0px', | |||
threshold: 0.4 // 元素 40% 可见时触发 | |||
rootMargin: '-20% 0px -20% 0px', // 提前触发动画 | |||
threshold: [0, 0.1, 0.2, 0.3] // 多个阈值,更灵敏 | |||
}; | |||
const observerCallback = (entries: IntersectionObserverEntry[]) => { | |||
entries.forEach(entry => { | |||
if (entry.isIntersecting) { | |||
if (entry.isIntersecting && entry.intersectionRatio > 0.1) { | |||
currentSection.value = entry.target.id; | |||
entry.target.classList.add('animate-in'); | |||
} | |||
@@ -662,7 +665,16 @@ onMounted(() => { | |||
contactRef.value | |||
]; | |||
sectionElements.forEach(el => { | |||
if (el) observer.observe(el); | |||
if (el) { | |||
observer.observe(el); | |||
// 检查是否已经在视口中,如果是则立即显示 | |||
const rect = el.getBoundingClientRect(); | |||
const windowHeight = window.innerHeight; | |||
if (rect.top < windowHeight * 0.8 && rect.bottom > 0) { | |||
el.classList.add('animate-in'); | |||
} | |||
} | |||
}); | |||
}); | |||
@@ -674,9 +686,30 @@ onMounted(() => { | |||
}); | |||
}); | |||
// 处理滚动 (保留用于横幅视差效果) | |||
// 处理滚动 (保留用于横幅视差效果和备用动画触发) | |||
const handleLegacyScroll = () => { | |||
scrollY.value = window.scrollY; | |||
// 备用动画触发机制 | |||
const sections = [ | |||
companyProfileRef.value, | |||
businessContentRef.value, | |||
philosophyRef.value, | |||
companyInfoRef.value, | |||
contactRef.value | |||
]; | |||
sections.forEach(section => { | |||
if (section && !section.classList.contains('animate-in')) { | |||
const rect = section.getBoundingClientRect(); | |||
const windowHeight = window.innerHeight; | |||
// 当元素进入视口的80%时触发动画 | |||
if (rect.top < windowHeight * 0.8 && rect.bottom > 0) { | |||
section.classList.add('animate-in'); | |||
} | |||
} | |||
}); | |||
}; | |||
// SEO优化 | |||
@@ -696,6 +729,34 @@ useHead({ | |||
</script> | |||
<style scoped> | |||
/* 商务风格背景图样式 */ | |||
.bg-business-overlay { | |||
background-image: url('/assets/images/about.webp'); | |||
background-size: cover; | |||
background-position: center; | |||
background-repeat: no-repeat; | |||
background-attachment: fixed; | |||
opacity: 0.3; | |||
transition: all 0.5s ease; | |||
} | |||
@media (max-width: 768px) { | |||
.bg-business-overlay { | |||
background-attachment: scroll; /* 移动端不支持 fixed */ | |||
opacity: 0.2; | |||
} | |||
} | |||
/* 悬停效果 */ | |||
#company-profile:hover .bg-business-overlay { | |||
opacity: 0.4; | |||
transform: scale(1.02); | |||
} | |||
#company-profile:hover { | |||
background: linear-gradient(to bottom, rgba(53, 241, 255, 0.08), rgba(53, 241, 255, 0.04)); | |||
} | |||
/* 基础动画类 */ | |||
.section-block { | |||
opacity: 0; |
@@ -1,622 +0,0 @@ | |||
<template> | |||
<div> | |||
<div class="w-full h-[55px] sm:h-[72px]"></div> | |||
<ErrorBoundary :error="error"> | |||
<div v-if="isLoading" class="flex justify-center py-12"> | |||
<div | |||
class="animate-spin h-8 w-8 border-4 border-blue-500 rounded-full border-t-transparent" | |||
></div> | |||
</div> | |||
<div v-else> | |||
<div class="max-w-full xl:px-2 lg:px-2 md:px-4 px-4 mt-6 mb-12"> | |||
<div class="max-w-screen-2xl mx-auto"> | |||
<nuxt-link | |||
:to="`${homepagePath}/`" | |||
class="justify-start text-white/60 text-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> | |||
<nuxt-link | |||
:to="`${homepagePath}/faq`" | |||
class="text-white text-sm md:text-base font-normal" | |||
>{{ t("faq.title") }}</nuxt-link | |||
> | |||
</div> | |||
</div> | |||
<div | |||
class="max-w-full mb-12 md:mb-20 lg:mb-32 xl: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-12 gap-8 md:gap-4"> | |||
<!-- 左侧分类导航 --> | |||
<div | |||
class="col-span-1 md:col-span-3 flex flex-col gap-4 sm:gap-6 md:gap-8 lg:gap-10 xl:gap-12 2xl:gap-16 mb-4 sm:mb-6 md:mb-8 lg:mb-10 xl:mb-12 2xl:mb-16 pr-4" | |||
> | |||
<div | |||
class="flex flex-col gap-2 sm:gap-3 md:gap-4 lg:gap-5 xl:gap-6" | |||
> | |||
<div class="flex justify-between items-center"> | |||
<div | |||
class="text-white text-base sm:text-lg md:text-xl lg:text-2xl xl:text-3xl font-medium" | |||
> | |||
{{ t("faq.category") }} | |||
</div> | |||
</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 whitespace-nowrap md:whitespace-normal" | |||
> | |||
<div | |||
v-for="category in categoriesList" | |||
:key="category" | |||
@click="handleCategoryFilter(category)" | |||
class="select-none text-white text-xs sm:text-sm md:text-base lg:text-lg font-normal cursor-pointer hover:opacity-100 transition-all duration-300 px-2 py-1 sm:px-2.5 sm:py-1.5 md:px-3 md:py-2 lg:px-4 lg:py-2.5 rounded-lg inline-block active:scale-95 whitespace-nowrap" | |||
:class="{ | |||
'font-bold bg-cyan-400 text-zinc-900 border-0 shadow-lg scale-105 transition-all duration-300': | |||
selectedCategory === category, | |||
'hover:bg-zinc-800/50': selectedCategory !== category, | |||
}" | |||
> | |||
{{ category }} | |||
</div> | |||
</div> | |||
</div> | |||
</div> | |||
<!-- 右侧FAQ列表 --> | |||
<div class="col-span-1 md:col-span-9"> | |||
<!-- 搜索框 --> | |||
<div class="mb-8 relative"> | |||
<input | |||
v-model="searchTerm" | |||
ref="searchInputRef" | |||
type="search" | |||
: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="t('faq.clearSearch')" | |||
tabindex="0" | |||
type="button" | |||
> | |||
<svg | |||
xmlns="http://www.w3.org/2000/svg" | |||
class="h-5 w-5" | |||
fill="none" | |||
viewBox="0 0 24 24" | |||
stroke="currentColor" | |||
stroke-width="2" | |||
> | |||
<path | |||
stroke-linecap="round" | |||
stroke-linejoin="round" | |||
d="M6 18L18 6M6 6l12 12" | |||
/> | |||
</svg> | |||
</button> | |||
</div> | |||
<div class="flex flex-col gap-8"> | |||
<div | |||
v-for="faq in filteredFaqs" | |||
:key="generateFaqKey(faq)" | |||
class="bg-zinc-900 rounded-lg p-8 transition-all duration-300 hover:bg-zinc-800 hover:shadow-lg" | |||
> | |||
<div | |||
class="flex items-center justify-between cursor-pointer" | |||
@click="toggleFaq(faq)" | |||
> | |||
<div class="text-white text-xl font-medium"> | |||
<template | |||
v-for="(part, i) in highlightKeyword(faq.title)" | |||
:key="i" | |||
> | |||
<span v-if="typeof part === 'string'">{{ | |||
part | |||
}}</span> | |||
<component v-else :is="part"></component> | |||
</template> | |||
</div> | |||
<div | |||
class="text-white text-2xl transition-transform duration-300" | |||
:class="{ 'rotate-180': isFaqExpanded(faq) }" | |||
> | |||
▼ | |||
</div> | |||
</div> | |||
<div v-if="isFaqExpanded(faq)" class="mt-4"> | |||
<ContentRenderer | |||
class="prose prose-invert w-full max-w-none faq-content" | |||
:value="{ body: faq.content }" | |||
v-highlight="searchTerm.trim()" | |||
/> | |||
</div> | |||
</div> | |||
<div | |||
v-if="filteredFaqs.length === 0" | |||
class="text-center text-gray-400 py-8" | |||
> | |||
{{ t("faq.noResults") }} | |||
</div> | |||
</div> | |||
</div> | |||
</div> | |||
</div> | |||
</div> | |||
</div> | |||
</ErrorBoundary> | |||
</div> | |||
</template> | |||
<script setup lang="ts"> | |||
/** | |||
* FAQ页面 | |||
* 展示常见问题及其答案 | |||
*/ | |||
import { useErrorHandler } from "~/composables/useErrorHandler"; | |||
import { queryCollection } from "#imports"; | |||
const { error, isLoading, wrapAsync } = useErrorHandler(); | |||
const { t, locale } = useI18n(); | |||
// FAQ数据 | |||
interface FAQ { | |||
category: string; | |||
title: string; | |||
content: any; // 修改为 any 类型,因为可能是对象或字符串 | |||
sort: number; | |||
id?: string; | |||
} | |||
const homepagePath = computed(() => { | |||
return locale.value === "zh" ? "" : `/${locale.value}`; | |||
}); | |||
// 从content目录读取FAQ数据 | |||
const faqs = ref<FAQ[]>([]); | |||
const categoriesList = ref<string[]>([]); | |||
// 选中的分类 | |||
const selectedCategory = ref(""); | |||
// 使用 queryCollection 加载FAQ数据 | |||
const { data: faqData } = await useAsyncData( | |||
"faqs", | |||
async () => { | |||
try { | |||
// 使用 queryCollection 加载 FAQ 数据 | |||
const content = await queryCollection("content") | |||
.where("path", "LIKE", `/faq/${locale.value}/%`) | |||
.all(); | |||
if (!content || !Array.isArray(content)) { | |||
console.error("No FAQ content found or invalid format:", content); | |||
return []; | |||
} | |||
// 转换数据格式 | |||
const faqItems = content.map((item: any) => { | |||
return { | |||
category: item.meta?.category || "", | |||
title: item.title || "", | |||
content: item.body || "", | |||
sort: item.meta?.sort || 0, | |||
}; | |||
}); | |||
return faqItems.sort((a, b) => a.sort - b.sort); | |||
} catch (error) { | |||
console.error("Error loading FAQ content:", error); | |||
return []; | |||
} | |||
}, | |||
{ | |||
server: true, | |||
lazy: false, | |||
immediate: true, | |||
watch: [locale], | |||
} | |||
); | |||
// 处理FAQ数据变化 | |||
watchEffect(() => { | |||
if (faqData.value) { | |||
isLoading.value = true; | |||
try { | |||
// 设置分类列表和默认选中的分类 | |||
const allOption: string = | |||
locale.value === "en" | |||
? "All" | |||
: locale.value === "zh" | |||
? "全部" | |||
: "すべて"; | |||
// 从FAQ数据中提取所有不同的分类 | |||
const uniqueCategories = [ | |||
...new Set(faqData.value.map((faq: FAQ) => faq.category)), | |||
] | |||
.filter((category) => category) | |||
.sort(); // 过滤掉空分类并排序 | |||
// 设置分类列表和默认选中的分类 | |||
categoriesList.value = [allOption, ...uniqueCategories]; | |||
selectedCategory.value = categoriesList.value[0]; | |||
} catch (err) { | |||
console.error("Error processing FAQ data:", err); | |||
error.value = new Error(t("faq.processError")); | |||
} finally { | |||
isLoading.value = false; | |||
} | |||
} | |||
}); | |||
// 展开的FAQ标识列表 | |||
const expandedFaqKeys = ref<Set<string>>(new Set()); | |||
// 搜索关键词 | |||
const searchTerm = ref(""); | |||
const searchInputRef = ref<HTMLInputElement | null>(null); | |||
// 过滤后的FAQ列表 | |||
const filteredFaqs = computed(() => { | |||
if (!faqData.value) { | |||
return []; | |||
} | |||
let result = faqData.value; | |||
// 分类过滤 | |||
if (selectedCategory.value !== categoriesList.value[0]) { | |||
result = result.filter( | |||
(faq: FAQ) => faq.category === selectedCategory.value | |||
); | |||
} | |||
// 搜索过滤 - 同时搜索标题和内容 | |||
if (searchTerm.value.trim()) { | |||
const keyword = searchTerm.value.trim().toLowerCase(); | |||
result = result.filter((faq: FAQ) => { | |||
const title = String(faq.title || "").toLowerCase(); | |||
// 处理内容可能是对象或字符串的情况 | |||
let contentText = ""; | |||
if (faq.content) { | |||
if (typeof faq.content === "string") { | |||
contentText = faq.content.toLowerCase(); | |||
} else if (typeof faq.content === "object") { | |||
// 如果是对象,尝试提取内容 | |||
const contentObj = faq.content; | |||
if (contentObj.children && Array.isArray(contentObj.children)) { | |||
// 如果有children数组,遍历提取文本 | |||
contentText = JSON.stringify(contentObj); | |||
} else { | |||
// 其他情况,尝试转换整个对象为字符串 | |||
contentText = JSON.stringify(contentObj).toLowerCase(); | |||
} | |||
} | |||
} | |||
return title.includes(keyword) || contentText.includes(keyword); | |||
}); | |||
} | |||
return result; | |||
}); | |||
/** | |||
* 高亮显示匹配的关键字 | |||
* @param text 原始文本 | |||
* @returns 高亮后的VNode数组 | |||
*/ | |||
function highlightKeyword(text: any): (string | any)[] { | |||
const keyword = searchTerm.value.trim(); | |||
if (!keyword) return [text]; | |||
// 确保 text 是字符串 | |||
const textStr = typeof text === "string" ? text : String(text?.body || ""); | |||
// 构建正则,忽略大小写,转义特殊字符 | |||
const escaped = keyword.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); | |||
const reg = new RegExp(escaped, "gi"); | |||
const parts = textStr.split(reg); | |||
const matches = textStr.match(reg); | |||
if (!matches) return [textStr]; | |||
// 组装高亮 | |||
const result: (string | any)[] = []; | |||
parts.forEach((part, i) => { | |||
result.push(part); | |||
if (i < matches.length) { | |||
result.push( | |||
h( | |||
"span", | |||
{ class: "text-blue-400 bg-blue-400/10 font-bold" }, | |||
matches[i] | |||
) | |||
); | |||
} | |||
}); | |||
return result; | |||
} | |||
/** | |||
* 生成FAQ的唯一标识 | |||
* @param faq FAQ对象 | |||
* @returns string 唯一标识 | |||
*/ | |||
function generateFaqKey(faq: FAQ): string { | |||
return `${faq.category}-${faq.title}`; | |||
} | |||
/** | |||
* 切换FAQ展开状态 | |||
* @param faq FAQ对象 | |||
* @returns void | |||
*/ | |||
function toggleFaq(faq: FAQ): void { | |||
if (!faq) return; | |||
const faqKey = generateFaqKey(faq); | |||
// 如果当前FAQ已展开,则关闭它 | |||
if (expandedFaqKeys.value.has(faqKey)) { | |||
expandedFaqKeys.value.delete(faqKey); | |||
} else { | |||
// 否则展开它(不会关闭其他FAQ) | |||
expandedFaqKeys.value.add(faqKey); | |||
} | |||
} | |||
/** | |||
* 检查FAQ是否处于展开状态 | |||
* @param faq FAQ对象 | |||
* @returns boolean | |||
*/ | |||
function isFaqExpanded(faq: FAQ): boolean { | |||
if (!faq) return false; | |||
return expandedFaqKeys.value.has(generateFaqKey(faq)); | |||
} | |||
/** | |||
* 处理分类筛选 | |||
*/ | |||
function handleCategoryFilter(category: string) { | |||
selectedCategory.value = category; | |||
} | |||
// 自动展开匹配项 | |||
watch( | |||
[filteredFaqs, searchTerm], | |||
([faqs, keyword]: [FAQ[], string]) => { | |||
if (!faqs?.length) return; | |||
if (keyword.trim()) { | |||
// 搜索时展开所有匹配项 | |||
expandedFaqKeys.value = new Set(faqs.map((faq) => generateFaqKey(faq))); | |||
} else { | |||
// 清除搜索时关闭所有展开项 | |||
expandedFaqKeys.value.clear(); | |||
} | |||
}, | |||
{ deep: true } | |||
); | |||
function clearSearch() { | |||
searchTerm.value = ""; | |||
// 让输入框重新聚焦 | |||
searchInputRef.value?.focus(); | |||
} | |||
// SEO优化 | |||
useHead({ | |||
title: t("faq.title") + " - Hanye", | |||
meta: [ | |||
{ | |||
name: "description", | |||
content: t("faq.description"), | |||
}, | |||
{ | |||
name: "keywords", | |||
content: t("faq.keywords"), | |||
}, | |||
], | |||
}); | |||
// 添加自定义指令,用于在不破坏HTML结构的情况下高亮内容 | |||
const vHighlight = { | |||
mounted(el: HTMLElement, binding: { value: string }) { | |||
highlight(el, binding.value); | |||
}, | |||
updated(el: HTMLElement, binding: { value: string }) { | |||
highlight(el, binding.value); | |||
}, | |||
}; | |||
// 注册指令 | |||
const app = useNuxtApp(); | |||
app.vueApp.directive("highlight", vHighlight); | |||
// 高亮处理函数 | |||
function highlight(el: HTMLElement, keyword: string) { | |||
if (!keyword || typeof keyword !== "string" || !keyword.trim()) { | |||
return; | |||
} | |||
// 递归处理节点 | |||
function highlightNode(node: Node): boolean { | |||
if (node.nodeType === Node.TEXT_NODE) { | |||
// 文本节点,可以高亮 | |||
const text = node.textContent || ""; | |||
const lowerText = text.toLowerCase(); | |||
const lowerKeyword = keyword.toLowerCase(); | |||
if (lowerText.includes(lowerKeyword)) { | |||
const fragment = document.createDocumentFragment(); | |||
let lastIndex = 0; | |||
// 查找所有匹配项 | |||
const regex = new RegExp(escapeRegExp(keyword), "gi"); | |||
let match; | |||
while ((match = regex.exec(text)) !== null) { | |||
// 添加匹配前的文本 | |||
if (match.index > lastIndex) { | |||
fragment.appendChild( | |||
document.createTextNode(text.substring(lastIndex, match.index)) | |||
); | |||
} | |||
// 创建高亮的span | |||
const highlightSpan = document.createElement("span"); | |||
highlightSpan.className = "text-blue-400 bg-blue-400/10 font-bold"; | |||
highlightSpan.textContent = match[0]; | |||
fragment.appendChild(highlightSpan); | |||
lastIndex = regex.lastIndex; | |||
} | |||
// 添加匹配后的剩余文本 | |||
if (lastIndex < text.length) { | |||
fragment.appendChild( | |||
document.createTextNode(text.substring(lastIndex)) | |||
); | |||
} | |||
// 替换原节点 | |||
if (node.parentNode) { | |||
node.parentNode.replaceChild(fragment, node); | |||
} | |||
return true; | |||
} | |||
} else if (node.nodeType === Node.ELEMENT_NODE) { | |||
// 元素节点,递归处理子节点 | |||
// 避免处理这些标签内的内容 | |||
const element = node as HTMLElement; | |||
if ( | |||
["SCRIPT", "STYLE", "TEXTAREA", "INPUT", "SELECT", "OPTION"].includes( | |||
element.tagName | |||
) | |||
) { | |||
return false; | |||
} | |||
// 创建节点的副本避免在迭代过程中修改节点列表 | |||
const childNodes = Array.from(node.childNodes); | |||
childNodes.forEach((child) => highlightNode(child)); | |||
} | |||
return false; | |||
} | |||
highlightNode(el); | |||
} | |||
// 转义正则表达式中的特殊字符 | |||
function escapeRegExp(string: string): string { | |||
return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$0"); | |||
} | |||
</script> | |||
<style scoped> | |||
/* 添加过渡动画 */ | |||
.fade-enter-active, | |||
.fade-leave-active { | |||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); | |||
} | |||
.fade-enter-from, | |||
.fade-leave-to { | |||
opacity: 0; | |||
transform: translateY(20px); | |||
} | |||
/* 分类悬停效果 */ | |||
.active\:scale-95:active { | |||
transform: scale(0.95); | |||
} | |||
/* 优化横向滚动 */ | |||
.overflow-x-auto { | |||
-webkit-overflow-scrolling: touch; | |||
scrollbar-width: none; /* Firefox */ | |||
-ms-overflow-style: none; /* IE and Edge */ | |||
} | |||
.overflow-x-auto::-webkit-scrollbar { | |||
display: none; /* Chrome, Safari, Opera */ | |||
} | |||
/* 清除按钮动画 */ | |||
button { | |||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); | |||
} | |||
button:hover { | |||
transform: translateY(-2px); | |||
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), | |||
0 2px 4px -1px rgba(0, 0, 0, 0.06); | |||
} | |||
button:active { | |||
transform: translateY(0); | |||
} | |||
/* FAQ项目悬停效果 */ | |||
.bg-zinc-900 { | |||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); | |||
} | |||
.bg-zinc-900:hover { | |||
transform: translateY(-4px); | |||
} | |||
/* 响应式调整 */ | |||
@media (max-width: 768px) { | |||
button:hover { | |||
transform: translateY(-1px); | |||
} | |||
.bg-zinc-900:hover { | |||
transform: translateY(-2px); | |||
} | |||
} | |||
@media (min-width: 1024px) { | |||
.bg-zinc-900:hover { | |||
transform: translateY(-4px); | |||
} | |||
} | |||
/* 高亮内容样式 */ | |||
.faq-content { | |||
white-space: pre-line; | |||
line-height: 1.6; | |||
color: #e2e8f0; | |||
} | |||
.faq-content :deep(ul), | |||
.faq-content :deep(ol) { | |||
padding-left: 1.5rem; | |||
margin: 1rem 0; | |||
} | |||
.faq-content :deep(li) { | |||
margin: 0.5rem 0; | |||
} | |||
.faq-content :deep(p) { | |||
margin: 0.75rem 0; | |||
} | |||
.faq-content :deep(h1), | |||
.faq-content :deep(h2), | |||
.faq-content :deep(h3), | |||
.faq-content :deep(h4), | |||
.faq-content :deep(h5), | |||
.faq-content :deep(h6) { | |||
margin: 1.5rem 0 0.75rem; | |||
font-weight: 600; | |||
color: #f8fafc; | |||
} | |||
</style> |
@@ -245,7 +245,7 @@ | |||
<nuxt-link | |||
v-for="(recommend, index) in recommendList" | |||
:key="index" | |||
:to="`${homepagePath}products/${recommend.name}`" | |||
:to="`${homepagePath}/products/${recommend.name}`" | |||
class="w-full h-full" | |||
> | |||
<div |
@@ -1,68 +0,0 @@ | |||
<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,170 @@ | |||
<template> | |||
<div class="min-h-screen bg-gray-900"> | |||
<div class="w-full h-[55px] sm:h-[72px]"></div> | |||
<ErrorBoundary :error="error"> | |||
<div v-if="isLoading" class="flex justify-center py-16"> | |||
<div class="animate-spin h-8 w-8 border-2 border-blue-400 border-t-transparent rounded-full"></div> | |||
</div> | |||
<div v-else> | |||
<!-- 面包屑导航 --> | |||
<div class="bg-gray-800 border-b border-gray-700"> | |||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-4"> | |||
<nav class="flex items-center space-x-2 text-sm text-gray-400"> | |||
<nuxt-link | |||
:to="`${homepagePath}/`" | |||
class="hover:text-blue-400 transition-colors duration-200" | |||
> | |||
{{ t("common.home") }} | |||
</nuxt-link> | |||
<svg class="w-4 h-4 text-gray-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"> | |||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path> | |||
</svg> | |||
<nuxt-link | |||
:to="`${homepagePath}/support`" | |||
class="hover:text-blue-400 transition-colors duration-200" | |||
> | |||
{{ t("support.title") }} | |||
</nuxt-link> | |||
<svg class="w-4 h-4 text-gray-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"> | |||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path> | |||
</svg> | |||
<span class="text-gray-200 font-medium">{{ t("support.faq.title") }}</span> | |||
</nav> | |||
</div> | |||
</div> | |||
<!-- 页面头部 --> | |||
<div class="bg-gray-800"> | |||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-16"> | |||
<div class="text-center"> | |||
<div class="flex items-center justify-center mb-6"> | |||
<div class="w-12 h-12 bg-blue-500/20 text-blue-400 rounded-lg flex items-center justify-center mr-4"> | |||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"> | |||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8.228 9c.549-1.165 2.03-2 3.772-2 2.21 0 4 1.343 4 3 0 1.4-1.278 2.575-3.006 2.907-.542.104-.994.54-.994 1.093m0 3h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path> | |||
</svg> | |||
</div> | |||
<h1 class="text-3xl md:text-4xl font-bold text-white"> | |||
{{ t("support.faq.title") }} | |||
</h1> | |||
</div> | |||
<p class="text-xl text-gray-300 max-w-3xl mx-auto"> | |||
{{ t("support.faq.description") }} | |||
</p> | |||
</div> | |||
</div> | |||
</div> | |||
<!-- FAQ内容区域 --> | |||
<div class="bg-gray-900"> | |||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-16"> | |||
<div class="bg-gray-800 rounded-lg shadow-lg border border-gray-700 overflow-hidden"> | |||
<FaqContent /> | |||
</div> | |||
</div> | |||
</div> | |||
<!-- 底部操作区域 --> | |||
<div class="bg-gray-800 border-t border-gray-700"> | |||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8"> | |||
<div class="flex flex-col sm:flex-row items-center justify-between"> | |||
<div class="mb-4 sm:mb-0"> | |||
<p class="text-gray-400 text-sm"> | |||
没有找到您需要的答案?我们的技术支持团队随时为您提供帮助。 | |||
</p> | |||
</div> | |||
<div class="flex flex-col sm:flex-row gap-3"> | |||
<nuxt-link | |||
:to="`${homepagePath}/support`" | |||
class="inline-flex items-center px-4 py-2 border border-gray-600 text-sm font-medium rounded-md text-gray-300 bg-gray-700 hover:bg-gray-600 transition-colors duration-200" | |||
> | |||
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24"> | |||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 19l-7-7m0 0l7-7m-7 7h18"></path> | |||
</svg> | |||
{{ t("support.backToSupport") }} | |||
</nuxt-link> | |||
<nuxt-link | |||
:to="`${homepagePath}/contact`" | |||
class="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700 transition-colors duration-200" | |||
> | |||
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24"> | |||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 8l7.89 4.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"></path> | |||
</svg> | |||
联系技术支持 | |||
</nuxt-link> | |||
</div> | |||
</div> | |||
</div> | |||
</div> | |||
</div> | |||
</ErrorBoundary> | |||
</div> | |||
</template> | |||
<script setup lang="ts"> | |||
/** | |||
* FAQ详情页面 | |||
* 专业的常见问题解答页面 | |||
*/ | |||
import { useErrorHandler } from "~/composables/useErrorHandler"; | |||
const { error, isLoading } = useErrorHandler(); | |||
const { t, locale } = useI18n(); | |||
// 计算首页路径 | |||
const homepagePath = computed(() => { | |||
return locale.value === "zh" ? "" : `/${locale.value}`; | |||
}); | |||
// SEO优化 | |||
useHead({ | |||
title: t("support.faq.title") + " - " + t("support.title") + " - Hanye", | |||
meta: [ | |||
{ | |||
name: "description", | |||
content: t("faq.description"), | |||
}, | |||
{ | |||
name: "keywords", | |||
content: t("faq.keywords"), | |||
}, | |||
], | |||
}); | |||
</script> | |||
<style scoped> | |||
/* 过渡效果 */ | |||
.transition-colors { | |||
transition-property: color, background-color, border-color, text-decoration-color, fill, stroke; | |||
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); | |||
} | |||
.duration-200 { | |||
transition-duration: 200ms; | |||
} | |||
/* 悬停效果 */ | |||
.hover\:bg-blue-700:hover { | |||
background-color: #1d4ed8; | |||
} | |||
.hover\:bg-gray-600:hover { | |||
background-color: #4b5563; | |||
} | |||
.hover\:text-blue-400:hover { | |||
color: #60a5fa; | |||
} | |||
/* 响应式优化 */ | |||
@media (max-width: 768px) { | |||
.flex-col { | |||
align-items: stretch; | |||
} | |||
.flex-col > * { | |||
width: 100%; | |||
justify-content: center; | |||
} | |||
} | |||
</style> |
@@ -0,0 +1,456 @@ | |||
<template> | |||
<div class="min-h-screen bg-gray-900"> | |||
<div class="w-full h-[55px] sm:h-[72px]"></div> | |||
<ErrorBoundary :error="error"> | |||
<div v-if="isLoading" class="flex justify-center py-16"> | |||
<div class="animate-spin h-8 w-8 border-2 border-blue-400 border-t-transparent rounded-full"></div> | |||
</div> | |||
<div v-else> | |||
<!-- 面包屑导航 --> | |||
<div class="bg-gray-800 border-b border-gray-700"> | |||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-4"> | |||
<nav class="flex items-center space-x-2 text-sm text-gray-400"> | |||
<nuxt-link | |||
:to="`${homepagePath}/`" | |||
class="hover:text-blue-400 transition-colors duration-200" | |||
> | |||
{{ t("common.home") }} | |||
</nuxt-link> | |||
<svg class="w-4 h-4 text-gray-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"> | |||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path> | |||
</svg> | |||
<span class="text-gray-200 font-medium">{{ t("support.title") }}</span> | |||
</nav> | |||
</div> | |||
</div> | |||
<!-- 页面头部 --> | |||
<div class="bg-gray-800"> | |||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-16"> | |||
<div class="text-center"> | |||
<h1 class="text-4xl font-bold text-white mb-4"> | |||
{{ t("support.title") }} | |||
</h1> | |||
<p class="text-xl text-gray-300 max-w-3xl mx-auto"> | |||
{{ t("support.subtitle") }} | |||
</p> | |||
</div> | |||
</div> | |||
</div> | |||
<!-- 主要服务区域 --> | |||
<div class="bg-gray-900"> | |||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-16"> | |||
<!-- 主要服务 --> | |||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-8 mb-16"> | |||
<!-- FAQ卡片 --> | |||
<nuxt-link | |||
:to="`${homepagePath}/support/faq`" | |||
class="group bg-gray-800 rounded-lg shadow-lg border border-gray-700 p-8 hover:shadow-xl hover:border-blue-500/50 transition-all duration-200" | |||
> | |||
<div class="flex items-start"> | |||
<div class="flex-shrink-0"> | |||
<div class="w-12 h-12 bg-blue-500/20 text-blue-400 rounded-lg flex items-center justify-center"> | |||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"> | |||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8.228 9c.549-1.165 2.03-2 3.772-2 2.21 0 4 1.343 4 3 0 1.4-1.278 2.575-3.006 2.907-.542.104-.994.54-.994 1.093m0 3h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path> | |||
</svg> | |||
</div> | |||
</div> | |||
<div class="ml-6 flex-1"> | |||
<h3 class="text-xl font-semibold text-white mb-3 group-hover:text-blue-400 transition-colors"> | |||
{{ t("support.faq.title") }} | |||
</h3> | |||
<p class="text-gray-400 mb-4 leading-relaxed"> | |||
{{ t("support.faq.description") }} | |||
</p> | |||
<div class="flex items-center text-blue-400 font-medium text-sm"> | |||
{{ t("support.viewMore") }} | |||
<svg class="w-4 h-4 ml-2 group-hover:translate-x-1 transition-transform duration-200" fill="none" stroke="currentColor"> | |||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path> | |||
</svg> | |||
</div> | |||
</div> | |||
</div> | |||
</nuxt-link> | |||
<!-- 联系我们卡片 --> | |||
<nuxt-link | |||
:to="`${homepagePath}/contact`" | |||
class="group bg-gray-800 rounded-lg shadow-lg border border-gray-700 p-8 hover:shadow-xl hover:border-green-500/50 transition-all duration-200" | |||
> | |||
<div class="flex items-start"> | |||
<div class="flex-shrink-0"> | |||
<div class="w-12 h-12 bg-green-500/20 text-green-400 rounded-lg flex items-center justify-center"> | |||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"> | |||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 8l7.89 4.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"></path> | |||
</svg> | |||
</div> | |||
</div> | |||
<div class="ml-6 flex-1"> | |||
<h3 class="text-xl font-semibold text-white mb-3 group-hover:text-green-400 transition-colors"> | |||
{{ t("support.contact.title") }} | |||
</h3> | |||
<p class="text-gray-400 mb-4 leading-relaxed"> | |||
{{ t("support.contact.description") }} | |||
</p> | |||
<div class="flex items-center text-green-400 font-medium text-sm"> | |||
{{ t("support.contactUs") }} | |||
<svg class="w-4 h-4 ml-2 group-hover:translate-x-1 transition-transform duration-200" fill="none" stroke="currentColor"> | |||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path> | |||
</svg> | |||
</div> | |||
</div> | |||
</div> | |||
</nuxt-link> | |||
</div> | |||
<!-- 其他服务 --> | |||
<div class="border-t border-gray-700 pt-16"> | |||
<h2 class="text-2xl font-bold text-white text-center mb-12">其他支持服务</h2> | |||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6"> | |||
<!-- 技术文档 --> | |||
<div | |||
class="bg-gray-800 rounded-lg shadow-lg border border-gray-700 p-6 text-center cursor-pointer hover:shadow-xl hover:border-purple-500/50 transition-all duration-200" | |||
@click="showDocsModal = true" | |||
> | |||
<div class="w-12 h-12 bg-purple-500/20 text-purple-400 rounded-lg flex items-center justify-center mx-auto mb-4"> | |||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"> | |||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.246 18 16.5 18c-1.746 0-3.332.477-4.5 1.253"></path> | |||
</svg> | |||
</div> | |||
<h3 class="text-lg font-semibold text-white mb-2"> | |||
{{ t("support.docs.title") }} | |||
</h3> | |||
<p class="text-gray-400 text-sm mb-4"> | |||
{{ t("support.docs.description") }} | |||
</p> | |||
<span class="inline-flex items-center px-3 py-1 rounded-full text-xs font-medium bg-gray-700 text-gray-300"> | |||
{{ t("support.comingSoon") }} | |||
</span> | |||
</div> | |||
<!-- 产品手册 --> | |||
<div | |||
class="bg-gray-800 rounded-lg shadow-lg border border-gray-700 p-6 text-center cursor-pointer hover:shadow-xl hover:border-orange-500/50 transition-all duration-200" | |||
@click="showManualsModal = true" | |||
> | |||
<div class="w-12 h-12 bg-orange-500/20 text-orange-400 rounded-lg flex items-center justify-center mx-auto mb-4"> | |||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"> | |||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path> | |||
</svg> | |||
</div> | |||
<h3 class="text-lg font-semibold text-white mb-2"> | |||
{{ t("support.manuals.title") }} | |||
</h3> | |||
<p class="text-gray-400 text-sm mb-4"> | |||
{{ t("support.manuals.description") }} | |||
</p> | |||
<span class="inline-flex items-center px-3 py-1 rounded-full text-xs font-medium bg-gray-700 text-gray-300"> | |||
{{ t("support.comingSoon") }} | |||
</span> | |||
</div> | |||
<!-- 驱动下载 --> | |||
<div | |||
class="bg-gray-800 rounded-lg shadow-lg border border-gray-700 p-6 text-center cursor-pointer hover:shadow-xl hover:border-cyan-500/50 transition-all duration-200" | |||
@click="showDriversModal = true" | |||
> | |||
<div class="w-12 h-12 bg-cyan-500/20 text-cyan-400 rounded-lg flex items-center justify-center mx-auto mb-4"> | |||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"> | |||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M9 19l3 3m0 0l3-3m-3 3V10"></path> | |||
</svg> | |||
</div> | |||
<h3 class="text-lg font-semibold text-white mb-2"> | |||
{{ t("support.drivers.title") }} | |||
</h3> | |||
<p class="text-gray-400 text-sm mb-4"> | |||
{{ t("support.drivers.description") }} | |||
</p> | |||
<span class="inline-flex items-center px-3 py-1 rounded-full text-xs font-medium bg-gray-700 text-gray-300"> | |||
{{ t("support.comingSoon") }} | |||
</span> | |||
</div> | |||
<!-- 在线客服 --> | |||
<div | |||
class="bg-gray-800 rounded-lg shadow-lg border border-gray-700 p-6 text-center cursor-pointer hover:shadow-xl hover:border-red-500/50 transition-all duration-200" | |||
@click="showChatModal = true" | |||
> | |||
<div class="w-12 h-12 bg-red-500/20 text-red-400 rounded-lg flex items-center justify-center mx-auto mb-4"> | |||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"> | |||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z"></path> | |||
</svg> | |||
</div> | |||
<h3 class="text-lg font-semibold text-white mb-2"> | |||
{{ t("support.chat.title") }} | |||
</h3> | |||
<p class="text-gray-400 text-sm mb-4"> | |||
{{ t("support.chat.description") }} | |||
</p> | |||
<span class="inline-flex items-center px-3 py-1 rounded-full text-xs font-medium bg-gray-700 text-gray-300"> | |||
{{ t("support.comingSoon") }} | |||
</span> | |||
</div> | |||
</div> | |||
</div> | |||
</div> | |||
</div> | |||
<!-- 联系信息区域 --> | |||
<div class="bg-gray-800 border-t border-gray-700"> | |||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-16"> | |||
<div class="text-center"> | |||
<h2 class="text-2xl font-bold text-white mb-4">需要进一步帮助?</h2> | |||
<p class="text-gray-300 mb-8 max-w-2xl mx-auto"> | |||
我们的专业技术支持团队随时为您提供帮助,确保您获得最佳的产品体验。 | |||
</p> | |||
<div class="flex flex-col sm:flex-row gap-4 justify-center"> | |||
<nuxt-link | |||
:to="`${homepagePath}/contact`" | |||
class="inline-flex items-center px-6 py-3 border border-transparent text-base font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700 transition-colors duration-200" | |||
> | |||
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24"> | |||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 8l7.89 4.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"></path> | |||
</svg> | |||
联系技术支持 | |||
</nuxt-link> | |||
<nuxt-link | |||
:to="`${homepagePath}/support/faq`" | |||
class="inline-flex items-center px-6 py-3 border border-gray-600 text-base font-medium rounded-md text-gray-300 bg-gray-700 hover:bg-gray-600 transition-colors duration-200" | |||
> | |||
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24"> | |||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8.228 9c.549-1.165 2.03-2 3.772-2 2.21 0 4 1.343 4 3 0 1.4-1.278 2.575-3.006 2.907-.542.104-.994.54-.994 1.093m0 3h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path> | |||
</svg> | |||
浏览常见问题 | |||
</nuxt-link> | |||
</div> | |||
</div> | |||
</div> | |||
</div> | |||
<!-- 模态框 --> | |||
<div | |||
v-if="showDocsModal" | |||
class="fixed inset-0 bg-black bg-opacity-75 z-50 flex items-center justify-center p-4" | |||
@click="showDocsModal = false" | |||
> | |||
<div | |||
class="bg-gray-800 rounded-lg max-w-md w-full shadow-2xl border border-gray-700" | |||
@click.stop | |||
> | |||
<div class="flex items-center justify-between p-6 border-b border-gray-700"> | |||
<h2 class="text-xl font-semibold text-white">{{ t("support.docs.title") }}</h2> | |||
<button | |||
@click="showDocsModal = false" | |||
class="text-gray-400 hover:text-gray-200 transition-colors" | |||
> | |||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"> | |||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path> | |||
</svg> | |||
</button> | |||
</div> | |||
<div class="p-6 text-center"> | |||
<div class="w-16 h-16 bg-purple-500/20 text-purple-400 rounded-full flex items-center justify-center mx-auto mb-4"> | |||
<svg class="w-8 h-8" fill="none" stroke="currentColor" viewBox="0 0 24 24"> | |||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"></path> | |||
</svg> | |||
</div> | |||
<h3 class="text-lg font-semibold text-white mb-2">{{ t("support.comingSoon") }}</h3> | |||
<p class="text-gray-400 text-sm">我们正在为您准备详细的技术文档,敬请期待</p> | |||
</div> | |||
</div> | |||
</div> | |||
<div | |||
v-if="showManualsModal" | |||
class="fixed inset-0 bg-black bg-opacity-75 z-50 flex items-center justify-center p-4" | |||
@click="showManualsModal = false" | |||
> | |||
<div | |||
class="bg-gray-800 rounded-lg max-w-md w-full shadow-2xl border border-gray-700" | |||
@click.stop | |||
> | |||
<div class="flex items-center justify-between p-6 border-b border-gray-700"> | |||
<h2 class="text-xl font-semibold text-white">{{ t("support.manuals.title") }}</h2> | |||
<button | |||
@click="showManualsModal = false" | |||
class="text-gray-400 hover:text-gray-200 transition-colors" | |||
> | |||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"> | |||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path> | |||
</svg> | |||
</button> | |||
</div> | |||
<div class="p-6 text-center"> | |||
<div class="w-16 h-16 bg-orange-500/20 text-orange-400 rounded-full flex items-center justify-center mx-auto mb-4"> | |||
<svg class="w-8 h-8" fill="none" stroke="currentColor" viewBox="0 0 24 24"> | |||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"></path> | |||
</svg> | |||
</div> | |||
<h3 class="text-lg font-semibold text-white mb-2">{{ t("support.comingSoon") }}</h3> | |||
<p class="text-gray-400 text-sm">产品手册下载功能即将上线,敬请期待</p> | |||
</div> | |||
</div> | |||
</div> | |||
<div | |||
v-if="showDriversModal" | |||
class="fixed inset-0 bg-black bg-opacity-75 z-50 flex items-center justify-center p-4" | |||
@click="showDriversModal = false" | |||
> | |||
<div | |||
class="bg-gray-800 rounded-lg max-w-md w-full shadow-2xl border border-gray-700" | |||
@click.stop | |||
> | |||
<div class="flex items-center justify-between p-6 border-b border-gray-700"> | |||
<h2 class="text-xl font-semibold text-white">{{ t("support.drivers.title") }}</h2> | |||
<button | |||
@click="showDriversModal = false" | |||
class="text-gray-400 hover:text-gray-200 transition-colors" | |||
> | |||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"> | |||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path> | |||
</svg> | |||
</button> | |||
</div> | |||
<div class="p-6 text-center"> | |||
<div class="w-16 h-16 bg-cyan-500/20 text-cyan-400 rounded-full flex items-center justify-center mx-auto mb-4"> | |||
<svg class="w-8 h-8" fill="none" stroke="currentColor" viewBox="0 0 24 24"> | |||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"></path> | |||
</svg> | |||
</div> | |||
<h3 class="text-lg font-semibold text-white mb-2">{{ t("support.comingSoon") }}</h3> | |||
<p class="text-gray-400 text-sm">驱动程序下载区域正在准备中,敬请期待</p> | |||
</div> | |||
</div> | |||
</div> | |||
<div | |||
v-if="showChatModal" | |||
class="fixed inset-0 bg-black bg-opacity-75 z-50 flex items-center justify-center p-4" | |||
@click="showChatModal = false" | |||
> | |||
<div | |||
class="bg-gray-800 rounded-lg max-w-md w-full shadow-2xl border border-gray-700" | |||
@click.stop | |||
> | |||
<div class="flex items-center justify-between p-6 border-b border-gray-700"> | |||
<h2 class="text-xl font-semibold text-white">{{ t("support.chat.title") }}</h2> | |||
<button | |||
@click="showChatModal = false" | |||
class="text-gray-400 hover:text-gray-200 transition-colors" | |||
> | |||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"> | |||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path> | |||
</svg> | |||
</button> | |||
</div> | |||
<div class="p-6 text-center"> | |||
<div class="w-16 h-16 bg-red-500/20 text-red-400 rounded-full flex items-center justify-center mx-auto mb-4"> | |||
<svg class="w-8 h-8" fill="none" stroke="currentColor" viewBox="0 0 24 24"> | |||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"></path> | |||
</svg> | |||
</div> | |||
<h3 class="text-lg font-semibold text-white mb-2">{{ t("support.comingSoon") }}</h3> | |||
<p class="text-gray-400 text-sm">在线客服功能即将为您开通,敬请期待</p> | |||
</div> | |||
</div> | |||
</div> | |||
</div> | |||
</ErrorBoundary> | |||
</div> | |||
</template> | |||
<script setup lang="ts"> | |||
/** | |||
* 技术支持页面 | |||
* 提供专业的技术支持服务入口 | |||
*/ | |||
import { useErrorHandler } from "~/composables/useErrorHandler"; | |||
const { error, isLoading } = useErrorHandler(); | |||
const { t, locale } = useI18n(); | |||
// 计算首页路径 | |||
const homepagePath = computed(() => { | |||
return locale.value === "zh" ? "" : `/${locale.value}`; | |||
}); | |||
// 模态框控制 | |||
const showDocsModal = ref(false); | |||
const showManualsModal = ref(false); | |||
const showDriversModal = ref(false); | |||
const showChatModal = ref(false); | |||
// SEO优化 | |||
useHead({ | |||
title: t("support.title") + " - Hanye", | |||
meta: [ | |||
{ | |||
name: "description", | |||
content: t("support.description"), | |||
}, | |||
{ | |||
name: "keywords", | |||
content: t("support.keywords"), | |||
}, | |||
], | |||
}); | |||
</script> | |||
<style scoped> | |||
/* 简洁的悬停效果 */ | |||
.group:hover { | |||
transform: translateY(-2px); | |||
} | |||
/* 按钮悬停效果 */ | |||
.hover\:bg-blue-700:hover { | |||
background-color: #1d4ed8; | |||
} | |||
.hover\:bg-gray-600:hover { | |||
background-color: #4b5563; | |||
} | |||
/* 过渡效果 */ | |||
.transition-all { | |||
transition-property: all; | |||
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); | |||
} | |||
.transition-colors { | |||
transition-property: color, background-color, border-color, text-decoration-color, fill, stroke; | |||
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); | |||
} | |||
.transition-transform { | |||
transition-property: transform; | |||
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); | |||
} | |||
.duration-200 { | |||
transition-duration: 200ms; | |||
} | |||
/* 专业的阴影效果 */ | |||
.shadow-lg { | |||
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05); | |||
} | |||
.shadow-xl { | |||
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04); | |||
} | |||
.shadow-2xl { | |||
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25); | |||
} | |||
/* 响应式优化 */ | |||
@media (max-width: 768px) { | |||
.group:hover { | |||
transform: none; | |||
} | |||
} | |||
</style> |
@@ -0,0 +1 @@ | |||