159 lines
4.9 KiB
TypeScript
159 lines
4.9 KiB
TypeScript
'use client'
|
|
import {useState, useMemo, useCallback} from 'react'
|
|
import Link from 'next/link'
|
|
import {usePathname} from 'next/navigation'
|
|
import {ChevronRight} from 'lucide-react'
|
|
import {merge} from '@/lib/utils'
|
|
|
|
// 菜单配置
|
|
const MENU_ITEMS = [
|
|
{
|
|
group: '产品文档',
|
|
items: [
|
|
{key: 'product-overview', label: '产品介绍'},
|
|
{key: 'choose-product', label: '如何选择产品'},
|
|
{key: 'why-verify', label: '为什么需要实名认证'},
|
|
{key: 'city-lines', label: '有哪些城市线路'},
|
|
{key: 'api-docs', label: 'ip提取接口文档'},
|
|
// 服务条款
|
|
],
|
|
},
|
|
{
|
|
group: '操作指南',
|
|
items: [
|
|
{key: 'profile-settings', label: '修改个人信息和重置密码'},
|
|
{key: 'whitelist-guide', label: '如何添加白名单'},
|
|
{key: 'verify-guide', label: '如何进行实名认证'},
|
|
{key: 'extract-link', label: '如何生成提取链接'},
|
|
{key: 'payment-records', label: '查看支付和使用记录'},
|
|
],
|
|
},
|
|
{
|
|
group: '客户端教程',
|
|
items: [
|
|
{key: 'browser-proxy', label: '浏览器设置代理教程'},
|
|
{key: 'ios-proxy', label: 'iOS设置代理教程'},
|
|
{key: 'android-proxy', label: '安卓手机设置代理教程'},
|
|
{key: 'windows10-proxy', label: 'Windows10设置代理教程'},
|
|
],
|
|
},
|
|
{
|
|
group: '常见问题',
|
|
items: [
|
|
{key: 'faq-general', label: '常见问题总览'},
|
|
{key: 'faq-billing', label: '计费与套餐问题'},
|
|
// 业务场景集成方案
|
|
// 故障排查
|
|
],
|
|
},
|
|
{
|
|
group: '新闻资讯',
|
|
items: [
|
|
{key: 'news-latest', label: '了解代理服务器的工作原理'},
|
|
{key: 'news-announce', label: '网站公告'},
|
|
],
|
|
},
|
|
]
|
|
|
|
type Props = {
|
|
className?: string
|
|
onClose?: () => void
|
|
}
|
|
|
|
export default function Sidebar({className, onClose}: Props) {
|
|
const pathname = usePathname()
|
|
|
|
// 获取当前文档 key
|
|
const getCurrentKey = useCallback(() => {
|
|
const parts = pathname?.split('/') || []
|
|
return parts[2] || ''
|
|
}, [pathname])
|
|
|
|
const currentKey = getCurrentKey()
|
|
|
|
// 展开/收起状态
|
|
const [expandedGroups, setExpandedGroups] = useState<Record<string, boolean>>({})
|
|
|
|
// 初始化:自动展开包含当前活跃项的分组
|
|
const initialExpandedGroups = useMemo(() => {
|
|
const result: Record<string, boolean> = {}
|
|
MENU_ITEMS.forEach((section, index) => {
|
|
const hasActive = section.items.some(item => item.key === currentKey)
|
|
if (hasActive || index === 0) {
|
|
result[section.group] = true
|
|
}
|
|
})
|
|
return result
|
|
}, [currentKey])
|
|
|
|
// 合并自动展开和用户手动切换
|
|
const finalExpandedGroups = useMemo(() => {
|
|
return {...initialExpandedGroups, ...expandedGroups}
|
|
}, [initialExpandedGroups, expandedGroups])
|
|
|
|
const toggleGroup = (group: string) => {
|
|
setExpandedGroups(prev => ({
|
|
...prev,
|
|
[group]: !finalExpandedGroups[group],
|
|
}))
|
|
}
|
|
|
|
const getItemHref = (key: string) => `/docs/${key}`
|
|
|
|
return (
|
|
<aside
|
|
className={merge(`bg-white rounded-lg p-3 transition-all duration-200 shrink-0`, className)}
|
|
>
|
|
<nav className="space-y-2">
|
|
{MENU_ITEMS.map(section => (
|
|
<div key={section.group}>
|
|
<div
|
|
onClick={() => toggleGroup(section.group)}
|
|
className={`flex items-center gap-2 cursor-pointer px-3 py-2 rounded-sm transition-colors ${
|
|
finalExpandedGroups[section.group] && 'bg-blue-50'
|
|
}`}
|
|
>
|
|
<div
|
|
className={`w-4 flex items-center justify-center text-sm text-slate-400 transform transition-transform ${
|
|
finalExpandedGroups[section.group] ? 'rotate-90' : ''
|
|
}`}
|
|
>
|
|
<ChevronRight size={16}/>
|
|
</div>
|
|
|
|
<div className="text-lg font-semibold text-slate-900">
|
|
{section.group}
|
|
</div>
|
|
</div>
|
|
|
|
{finalExpandedGroups[section.group] && (
|
|
<ul className="mt-1 text-base">
|
|
{section.items.map((item) => {
|
|
const isActive = currentKey === item.key
|
|
const href = getItemHref(item.key)
|
|
|
|
return (
|
|
<li key={item.key}>
|
|
<Link
|
|
href={href}
|
|
onClick={() => onClose?.()}
|
|
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>
|
|
</li>
|
|
)
|
|
})}
|
|
</ul>
|
|
)}
|
|
</div>
|
|
))}
|
|
</nav>
|
|
</aside>
|
|
)
|
|
}
|