增加菜单栏帮助中心和登录页面相关协议的文档1.0版本 & allowedDevOrigins添加IP地址
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
'use client'
|
||||
import Link from 'next/link'
|
||||
import Image, {StaticImageData} from 'next/image'
|
||||
import Wrap from '@/components/wrap'
|
||||
@@ -24,18 +25,18 @@ export default function HelpMenu() {
|
||||
icon={h02}
|
||||
title="使用教程"
|
||||
items={[
|
||||
{lead: '快速入手', href: '/help/tutorials/quick-start'},
|
||||
{lead: '代码下载', href: '#'},
|
||||
{lead: 'API文档', href: '#'},
|
||||
{lead: '官方教程', href: '/help/tutorials/official-tutorial/browser-proxy'},
|
||||
{lead: '客户端教程', href: '/help/tutorials/client-tutorial/ios-proxy'},
|
||||
{lead: '操作指南', href: '/help/tutorials/operation-guide/windows7-proxy'},
|
||||
]}
|
||||
/>
|
||||
<Column
|
||||
icon={h03}
|
||||
title="产品功能"
|
||||
items={[
|
||||
{lead: '常见问题', href: '/prodFeat'},
|
||||
{lead: '产品介绍', href: '#'},
|
||||
{lead: '行业资讯', href: '#'},
|
||||
{lead: '常见问题', href: '/help/features/faq/faq-general'},
|
||||
{lead: '产品介绍', href: '/help/features/product-intro/product-overview'},
|
||||
{lead: '新闻资讯', href: '/help/features/news/news-latest'},
|
||||
]}
|
||||
/>
|
||||
<Image src={banner} alt="banner" className="hidden lg:block"/>
|
||||
@@ -67,8 +68,8 @@ function Column(props: {
|
||||
href={item.href}
|
||||
className="px-4 py-2"
|
||||
onClick={() => {
|
||||
ctx.setMenu(false) // 点击时关闭菜单
|
||||
router.push(item.href) // 跳转页面
|
||||
ctx.setMenu(false)
|
||||
router.push(item.href)
|
||||
}}
|
||||
>
|
||||
{item.lead}
|
||||
|
||||
60
src/app/(home)/help/features/[section]/[key]/page.tsx
Normal file
60
src/app/(home)/help/features/[section]/[key]/page.tsx
Normal file
@@ -0,0 +1,60 @@
|
||||
import Wrap from '@/components/wrap'
|
||||
|
||||
interface Props {
|
||||
params: Promise<{section: string, key: string}>
|
||||
}
|
||||
|
||||
// 根据 key 返回对应的组件
|
||||
async function getContentComponent(key: string) {
|
||||
switch (key) {
|
||||
case 'faq-general':
|
||||
const {default: FaqGeneral} = await import('@/docs/faq-general.mdx')
|
||||
return FaqGeneral
|
||||
case 'product-overview':
|
||||
const {default: ProductOverview} = await import('@/docs/product-overview.mdx')
|
||||
return ProductOverview
|
||||
case 'news-latest':
|
||||
const {default: NewsLatest} = await import('@/docs/news-latest.mdx')
|
||||
return NewsLatest
|
||||
default:
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
// 标题
|
||||
function getTitle(key: string) {
|
||||
const titleMap: Record<string, string> = {
|
||||
'faq-general': '常见问题总览',
|
||||
'faq-billing': '计费与套餐问题',
|
||||
'product-overview': '产品概述',
|
||||
'product-features': '产品功能',
|
||||
'news-latest': '最新动态',
|
||||
'news-announce': '公告',
|
||||
}
|
||||
return titleMap[key] || '功能'
|
||||
}
|
||||
|
||||
export default async function FeatureContentPage({params}: Props) {
|
||||
const {key} = await params
|
||||
|
||||
// 动态获取组件
|
||||
const Content = await getContentComponent(key)
|
||||
const title = getTitle(key)
|
||||
|
||||
return (
|
||||
<main>
|
||||
<Wrap className="flex flex-col">
|
||||
<div className="flex">
|
||||
{Content ? (
|
||||
<Content/>
|
||||
) : (
|
||||
<div>
|
||||
<h2 className="text-2xl font-semibold mb-4">{title}</h2>
|
||||
<p className="text-slate-600">此页面内容开发中...</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Wrap>
|
||||
</main>
|
||||
)
|
||||
}
|
||||
@@ -1,63 +1,185 @@
|
||||
'use client'
|
||||
import React from 'react'
|
||||
|
||||
export type MenuItem = {key: string, label: string, desc?: string, icon?: string}
|
||||
export type Section = {title: string, items: MenuItem[]}
|
||||
|
||||
export const MENU: Section[] = [
|
||||
{
|
||||
title: '官网教程',
|
||||
items: [
|
||||
{key: 'browser-proxy', label: '浏览器设置代理教程'},
|
||||
{key: 'code-download', label: '代码下载'},
|
||||
{key: 'api-docs', label: 'API 文档'},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '客户端教程',
|
||||
items: [
|
||||
{key: 'client-install', label: '客户端安装与配置'},
|
||||
{key: 'client-usage', label: '客户端使用指南'},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '操作指南',
|
||||
items: [
|
||||
{key: 'faq', label: '常见问题', desc: '常见问题与解答'},
|
||||
{key: 'troubleshoot', label: '故障排查', desc: '排查与解决常见故障'},
|
||||
],
|
||||
},
|
||||
]
|
||||
import React, {useState, useMemo, useCallback, useEffect} from 'react'
|
||||
import Link from 'next/link'
|
||||
import {useParams, usePathname, useSearchParams} from 'next/navigation'
|
||||
import {ChevronRight} from 'lucide-react'
|
||||
|
||||
type Props = {
|
||||
collapsed?: boolean
|
||||
selected?: string
|
||||
onSelect?: (key: string) => void
|
||||
onToggle?: () => void
|
||||
}
|
||||
|
||||
export default function Sidebar({collapsed = false, selected, onSelect, onToggle}: Props) {
|
||||
const [expanded, setExpanded] = React.useState<Record<string, boolean>>(() => {
|
||||
const s: Record<string, boolean> = {}
|
||||
MENU.forEach((section, idx) => (s[section.title] = idx === 0))
|
||||
return s
|
||||
})
|
||||
// 菜单结构
|
||||
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: [
|
||||
{key: 'news-latest', label: '最新动态'},
|
||||
{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])
|
||||
|
||||
const toggleSection = (title: string) => {
|
||||
setExpanded(prev => ({...prev, [title]: !prev[title]}))
|
||||
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],
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
// 构建链接地址
|
||||
const getItemHref = useCallback((sectionKey: string, itemKey: string) => {
|
||||
return category === 'features'
|
||||
? `/help/features/${sectionKey}/${itemKey}`
|
||||
: `/help/tutorials/${sectionKey}/${itemKey}`
|
||||
}, [category])
|
||||
|
||||
return (
|
||||
<aside className={`bg-white rounded p-3 transition-all duration-200 flex-shrink-0 ${collapsed ? 'w-20' : 'w-72'}`}>
|
||||
<aside className={`bg-white border rounded p-3 transition-all duration-200 shrink-0 ${collapsed ? 'w-20' : 'w-72'}`}>
|
||||
<nav className="space-y-2">
|
||||
{MENU.map(section => (
|
||||
<div key={section.title}>
|
||||
<div
|
||||
onClick={() => toggleSection(section.title)}
|
||||
className={`flex items-center gap-2 cursor-pointer px-3 py-2 rounded-sm transition-colors ${expanded[section.title] && !collapsed ? 'bg-blue-50' : 'hover:bg-slate-50'}`}
|
||||
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'}`}
|
||||
>
|
||||
<div className={`w-4 flex items-center justify-center text-sm text-slate-400 transform transition-transform ${expanded[section.title] ? 'rotate-90' : ''}`}>
|
||||
▸
|
||||
<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}/>
|
||||
</div>
|
||||
|
||||
{!collapsed && (
|
||||
@@ -67,17 +189,22 @@ export default function Sidebar({collapsed = false, selected, onSelect, onToggle
|
||||
)}
|
||||
</div>
|
||||
|
||||
{expanded[section.title] && (
|
||||
{finalExpandedSections[section.title] && (
|
||||
<ul className={`mt-1 text-base ${collapsed ? 'hidden' : 'block'}`}>
|
||||
{section.items.map((item) => {
|
||||
const active = selected === item.key
|
||||
const isActive = currentItemKey === item.key
|
||||
const href = getItemHref(section.sectionKey, item.key)
|
||||
|
||||
return (
|
||||
<li
|
||||
key={item.key}
|
||||
onClick={() => onSelect?.(item.key)}
|
||||
className={`pl-8 py-2 text-base cursor-pointer transition-colors ${active ? 'text-blue-600 font-semibold' : 'text-slate-700 hover:text-slate-900'}`}
|
||||
>
|
||||
{item.label}
|
||||
<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>
|
||||
</li>
|
||||
)
|
||||
})}
|
||||
|
||||
69
src/app/(home)/help/tutorials/[section]/[key]/page.tsx
Normal file
69
src/app/(home)/help/tutorials/[section]/[key]/page.tsx
Normal file
@@ -0,0 +1,69 @@
|
||||
import Wrap from '@/components/wrap'
|
||||
|
||||
// 只导入真正需要的 MDX 文件(按需导入,避免一次性导入太多)
|
||||
interface Props {
|
||||
params: Promise<{section: string, key: string}>
|
||||
}
|
||||
|
||||
// 根据 key 返回对应的组件
|
||||
async function getContentComponent(key: string) {
|
||||
switch (key) {
|
||||
case 'browser-proxy':
|
||||
const {default: BrowserProxy} = await import('@/docs/browser-proxy.mdx')
|
||||
return BrowserProxy
|
||||
case 'package-operations':
|
||||
const {default: PackageOperations} = await import('@/docs/package-operations.mdx')
|
||||
return PackageOperations
|
||||
case 'ios-proxy':
|
||||
const {default: IosProxy} = await import('@/docs/ios-proxy.mdx')
|
||||
return IosProxy
|
||||
case 'windows7-proxy':
|
||||
const {default: Windows7Proxy} = await import('@/docs/windows7-proxy.mdx')
|
||||
return Windows7Proxy
|
||||
default:
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
// 标题
|
||||
function getTitle(key: string) {
|
||||
const titleMap: Record<string, string> = {
|
||||
'browser-proxy': '浏览器设置代理教程',
|
||||
'package-operations': '套餐续费、合并、修改时效、补重操作',
|
||||
'fixed-package': '长效固定套餐操作手册',
|
||||
'ios-proxy': 'iOS设置代理教程',
|
||||
'windows10-proxy': 'Windows10电脑设置代理教程',
|
||||
'android-proxy': '安卓手机设置代理教程',
|
||||
'windows7-proxy': 'Windows7电脑设置代理教程',
|
||||
'mac-proxy': 'MAC设置代理教程',
|
||||
'firefox-proxy': '火狐浏览器怎么设置HTTP/Socks5代理服务器',
|
||||
'socks5-usage': '怎么使用Socks5代理IP上网',
|
||||
'http-notes': '使用HTTP代理注意的点',
|
||||
}
|
||||
return titleMap[key] || '教程'
|
||||
}
|
||||
|
||||
export default async function TutorialContentPage({params}: Props) {
|
||||
const {key} = await params
|
||||
|
||||
// 动态获取组件
|
||||
const Content = await getContentComponent(key)
|
||||
const title = getTitle(key)
|
||||
|
||||
return (
|
||||
<main>
|
||||
<Wrap className="flex flex-col">
|
||||
<div className="flex">
|
||||
{Content ? (
|
||||
<Content/>
|
||||
) : (
|
||||
<div>
|
||||
<h2 className="text-2xl font-semibold mb-4">{title}</h2>
|
||||
<p className="text-slate-600">此页面内容开发中...</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Wrap>
|
||||
</main>
|
||||
)
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
'use client'
|
||||
import QuickStart from '@/components/docs/quick-start.mdx'
|
||||
|
||||
export default function CollectPage() {
|
||||
return (
|
||||
<QuickStart/>
|
||||
)
|
||||
}
|
||||
8
src/app/privacyPolicy/page.tsx
Normal file
8
src/app/privacyPolicy/page.tsx
Normal file
@@ -0,0 +1,8 @@
|
||||
'use client'
|
||||
import PrivacyPolicy from '@/docs/privacyPolicy.mdx'
|
||||
|
||||
export default function CollectPage() {
|
||||
return (
|
||||
<PrivacyPolicy/>
|
||||
)
|
||||
}
|
||||
8
src/app/userAgreement/page.tsx
Normal file
8
src/app/userAgreement/page.tsx
Normal file
@@ -0,0 +1,8 @@
|
||||
'use client'
|
||||
import UserAgreement from '@/docs/userAgreement.mdx'
|
||||
|
||||
export default function CollectPage() {
|
||||
return (
|
||||
<UserAgreement/>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user