2025-12-01 19:11:53 +08:00
|
|
|
'use client'
|
2025-12-15 11:48:40 +08:00
|
|
|
import {useState, useMemo, useCallback} from 'react'
|
2025-12-12 14:17:24 +08:00
|
|
|
import Link from 'next/link'
|
2025-12-15 11:48:40 +08:00
|
|
|
import {useParams, usePathname} from 'next/navigation'
|
2025-12-12 14:17:24 +08:00
|
|
|
import {ChevronRight} from 'lucide-react'
|
2025-12-01 19:11:53 +08:00
|
|
|
|
|
|
|
|
type Props = {
|
|
|
|
|
collapsed?: boolean
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-12 14:17:24 +08:00
|
|
|
// 菜单结构
|
|
|
|
|
const MENU_CONFIG = {
|
|
|
|
|
tutorials: [
|
|
|
|
|
{
|
|
|
|
|
title: '官网教程',
|
|
|
|
|
sectionKey: 'official-tutorial',
|
|
|
|
|
items: [
|
|
|
|
|
{key: 'browser-proxy', label: '浏览器设置代理教程'},
|
|
|
|
|
{key: 'package-operations', label: '套餐续费、合并、修改时效、补重操作'},
|
|
|
|
|
{key: 'fixed-package', label: '长效固定套餐操作手册'},
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
title: '客户端教程',
|
|
|
|
|
sectionKey: 'client-tutorial',
|
|
|
|
|
items: [
|
|
|
|
|
{key: 'ios-proxy', label: 'iOS设置代理教程'},
|
|
|
|
|
{key: 'windows10-proxy', label: 'Windows10电脑设置代理教程'},
|
|
|
|
|
{key: 'android-proxy', label: '安卓手机设置代理教程'},
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
title: '操作指南',
|
|
|
|
|
sectionKey: 'operation-guide',
|
|
|
|
|
items: [
|
|
|
|
|
{key: 'windows7-proxy', label: 'Windows7电脑设置代理教程'},
|
|
|
|
|
{key: 'mac-proxy', label: 'MAC设置代理教程'},
|
|
|
|
|
{key: 'firefox-proxy', label: '火狐浏览器设置代理'},
|
|
|
|
|
{key: 'socks5-usage', label: 'Socks5代理使用教程'},
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
features: [
|
|
|
|
|
{
|
|
|
|
|
title: '产品介绍',
|
|
|
|
|
sectionKey: 'product-intro',
|
|
|
|
|
items: [
|
|
|
|
|
{key: 'product-overview', label: '产品概述'},
|
|
|
|
|
{key: 'product-features', label: '产品功能'},
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
title: '常见问题',
|
|
|
|
|
sectionKey: 'faq',
|
|
|
|
|
items: [
|
|
|
|
|
{key: 'faq-general', label: '常见问题总览'},
|
|
|
|
|
{key: 'faq-billing', label: '计费与套餐问题'},
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
title: '新闻资讯',
|
|
|
|
|
sectionKey: 'news',
|
|
|
|
|
items: [
|
2025-12-15 11:48:40 +08:00
|
|
|
{key: 'news-latest', label: '了解代理服务器的工作原理'},
|
2025-12-12 14:17:24 +08:00
|
|
|
{key: 'news-announce', label: '公告'},
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export default function Sidebar({collapsed = false}: Props) {
|
|
|
|
|
const params = useParams()
|
|
|
|
|
const pathname = usePathname()
|
|
|
|
|
|
|
|
|
|
// 判断当前所处的 help 子模块
|
|
|
|
|
const getCategory = useCallback(() => {
|
|
|
|
|
if (!pathname) return 'tutorials'
|
|
|
|
|
if (pathname.includes('/help/features')) return 'features'
|
|
|
|
|
if (pathname.includes('/help/tutorials')) return 'tutorials'
|
|
|
|
|
return 'tutorials'
|
|
|
|
|
}, [pathname])
|
|
|
|
|
|
|
|
|
|
const category = getCategory()
|
|
|
|
|
const MENU = category === 'features' ? MENU_CONFIG.features : MENU_CONFIG.tutorials
|
|
|
|
|
|
|
|
|
|
// 获取当前 sectionKey 和 itemKey
|
|
|
|
|
const getCurrentKeys = useCallback(() => {
|
|
|
|
|
const pathParts = pathname?.split('/') || []
|
|
|
|
|
let sectionKey = ''
|
|
|
|
|
let itemKey = ''
|
|
|
|
|
|
|
|
|
|
if (pathParts.length >= 4) {
|
|
|
|
|
sectionKey = pathParts[3]
|
|
|
|
|
}
|
|
|
|
|
if (pathParts.length >= 5) {
|
|
|
|
|
itemKey = pathParts[4]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 如果从 params 获取
|
|
|
|
|
if (!sectionKey && params?.section) {
|
|
|
|
|
sectionKey = String(params.section)
|
|
|
|
|
}
|
|
|
|
|
if (!itemKey && params?.key) {
|
|
|
|
|
itemKey = String(params.key)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return {sectionKey, itemKey}
|
|
|
|
|
}, [pathname, params])
|
|
|
|
|
|
|
|
|
|
const {sectionKey: currentSectionKey, itemKey: currentItemKey} = getCurrentKeys()
|
|
|
|
|
|
|
|
|
|
const expandedSections = useMemo(() => {
|
|
|
|
|
const newExpanded: Record<string, boolean> = {}
|
|
|
|
|
const hasActiveSection = MENU.some(s => s.sectionKey === currentSectionKey)
|
|
|
|
|
|
|
|
|
|
MENU.forEach((section, index) => {
|
|
|
|
|
if (section.sectionKey === currentSectionKey) {
|
|
|
|
|
newExpanded[section.title] = true
|
|
|
|
|
}
|
|
|
|
|
else if (!hasActiveSection && index === 0) {
|
|
|
|
|
newExpanded[section.title] = true
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
newExpanded[section.title] = false
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
return newExpanded
|
|
|
|
|
}, [MENU, currentSectionKey])
|
|
|
|
|
|
|
|
|
|
// 使用 state 来跟踪用户的手动切换
|
|
|
|
|
const [userToggles, setUserToggles] = useState<Record<string, boolean>>({})
|
|
|
|
|
|
|
|
|
|
// 合并自动展开和用户手动切换的状态
|
|
|
|
|
const finalExpandedSections = useMemo(() => {
|
|
|
|
|
const result = {...expandedSections}
|
|
|
|
|
|
|
|
|
|
Object.keys(userToggles).forEach((title) => {
|
|
|
|
|
const section = MENU.find(s => s.title === title)
|
|
|
|
|
if (section && section.sectionKey !== currentSectionKey) {
|
|
|
|
|
result[title] = userToggles[title]
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
return result
|
|
|
|
|
}, [expandedSections, userToggles, MENU, currentSectionKey])
|
2025-12-01 19:11:53 +08:00
|
|
|
|
|
|
|
|
const toggleSection = (title: string) => {
|
2025-12-12 14:17:24 +08:00
|
|
|
const section = MENU.find(s => s.title === title)
|
|
|
|
|
if (!section) return
|
|
|
|
|
if (section.sectionKey === currentSectionKey) {
|
|
|
|
|
setUserToggles(prev => ({
|
|
|
|
|
...prev,
|
|
|
|
|
[title]: !finalExpandedSections[title],
|
|
|
|
|
}))
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
setUserToggles(prev => ({
|
|
|
|
|
...prev,
|
|
|
|
|
[title]: !prev[title],
|
|
|
|
|
}))
|
|
|
|
|
}
|
2025-12-01 19:11:53 +08:00
|
|
|
}
|
|
|
|
|
|
2025-12-12 14:17:24 +08:00
|
|
|
// 构建链接地址
|
|
|
|
|
const getItemHref = useCallback((sectionKey: string, itemKey: string) => {
|
|
|
|
|
return category === 'features'
|
|
|
|
|
? `/help/features/${sectionKey}/${itemKey}`
|
|
|
|
|
: `/help/tutorials/${sectionKey}/${itemKey}`
|
|
|
|
|
}, [category])
|
|
|
|
|
|
2025-12-01 19:11:53 +08:00
|
|
|
return (
|
2025-12-15 11:48:40 +08:00
|
|
|
<aside className={`bg-white rounded-lg p-3 transition-all duration-200 shrink-0 ${collapsed ? 'w-20' : 'w-72'}`}>
|
2025-12-01 19:11:53 +08:00
|
|
|
<nav className="space-y-2">
|
|
|
|
|
{MENU.map(section => (
|
|
|
|
|
<div key={section.title}>
|
|
|
|
|
<div
|
|
|
|
|
onClick={() => toggleSection(section.title)}
|
2025-12-12 14:17:24 +08:00
|
|
|
className={`flex items-center gap-2 cursor-pointer px-3 py-2 rounded-sm transition-colors ${finalExpandedSections[section.title] && !collapsed ? 'bg-blue-50' : 'hover:bg-slate-50'}`}
|
2025-12-01 19:11:53 +08:00
|
|
|
>
|
2025-12-12 14:17:24 +08:00
|
|
|
<div className={`w-4 flex items-center justify-center text-sm text-slate-400 transform transition-transform ${finalExpandedSections[section.title] ? 'rotate-90' : ''}`}>
|
|
|
|
|
<ChevronRight size={16}/>
|
2025-12-01 19:11:53 +08:00
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{!collapsed && (
|
|
|
|
|
<div className="text-lg font-semibold text-slate-900">
|
|
|
|
|
{section.title}
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
|
2025-12-12 14:17:24 +08:00
|
|
|
{finalExpandedSections[section.title] && (
|
2025-12-01 19:11:53 +08:00
|
|
|
<ul className={`mt-1 text-base ${collapsed ? 'hidden' : 'block'}`}>
|
|
|
|
|
{section.items.map((item) => {
|
2025-12-12 14:17:24 +08:00
|
|
|
const isActive = currentItemKey === item.key
|
|
|
|
|
const href = getItemHref(section.sectionKey, item.key)
|
|
|
|
|
|
2025-12-01 19:11:53 +08:00
|
|
|
return (
|
2025-12-12 14:17:24 +08:00
|
|
|
<li key={item.key}>
|
|
|
|
|
<Link
|
|
|
|
|
href={href}
|
|
|
|
|
className={`block pl-8 py-2 text-base cursor-pointer transition-colors ${
|
|
|
|
|
isActive ? 'bg-blue-50 font-semibold' : 'text-slate-700 hover:text-slate-900 hover:bg-slate-50'
|
|
|
|
|
}`}
|
|
|
|
|
>
|
|
|
|
|
{item.label}
|
|
|
|
|
</Link>
|
2025-12-01 19:11:53 +08:00
|
|
|
</li>
|
|
|
|
|
)
|
|
|
|
|
})}
|
|
|
|
|
</ul>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
))}
|
|
|
|
|
</nav>
|
|
|
|
|
</aside>
|
|
|
|
|
)
|
|
|
|
|
}
|