feat(header, sidebar):增强导航链接并添加页面类型支持

- 更新了 Header 和 SidebarNav 组件中的导航链接处理,以使用 href、target 和 rel 属性的实用函数。
- 在导航渲染中添加了对“页面”类型的支持,从而可以更好地组织和显示页面链接。
- 修改了 Cate 类型定义,将“页面”包含为有效类型。
- 从 cateNav 导出新的实用函数以改进链接管理。
This commit is contained in:
刘宇阳
2026-06-16 10:09:32 +08:00
parent ddc0721526
commit a3ffdbb795
5 changed files with 81 additions and 10 deletions

View File

@@ -1,5 +1,6 @@
import Show from '@/components/Show';
import { Cate } from '@/types/app/cate';
import { getCateNavHref, getCateNavRel, getCateNavTarget } from '@/utils/cateNav';
import Link from 'next/link';
import { IoIosArrowDown } from 'react-icons/io';
import { motion, AnimatePresence } from 'framer-motion';
@@ -22,7 +23,7 @@ export default ({ list, open, onClose }: Props) => {
<div key={one.id}>
{one.type === 'cate' && (
<li className="group/one relative hover:bg-[#e0e6ec] dark:hover:bg-[#495362] rounded-md ">
<Link href={`/cate/${one.id}?name=${one.name}`} className={`flex justify-between items-center p-3 px-5 text-[15px] group-hover/one:!text-primary text-[#333] dark:text-white whitespace-nowrap`} onClick={onClose}>
<Link href={getCateNavHref(one)} target={getCateNavTarget(one.type)} rel={getCateNavRel(one.type)} className={`flex justify-between items-center p-3 px-5 text-[15px] group-hover/one:!text-primary text-[#333] dark:text-white whitespace-nowrap`} onClick={onClose}>
{one.icon} {one.name}
<Show is={!!one.children.length}>
<IoIosArrowDown className="ml-2" />
@@ -33,7 +34,7 @@ export default ({ list, open, onClose }: Props) => {
<ul className="overflow-hidden top-[50px] w-full rounded-md">
{one.children?.map((two) => (
<li key={two.id} className="group/two">
<Link href={`/cate/${two.id}?name=${two.name}`} className="inline-block w-full p-2.5 pl-10 text-[15px] box-border text-[#666] dark:text-[#8c9ab1] hover:!text-primary" onClick={onClose}>
<Link href={getCateNavHref(two)} target={getCateNavTarget(two.type)} rel={getCateNavRel(two.type)} className="inline-block w-full p-2.5 pl-10 text-[15px] box-border text-[#666] dark:text-[#8c9ab1] hover:!text-primary" onClick={onClose}>
{two.name}
</Link>
</li>
@@ -43,9 +44,32 @@ export default ({ list, open, onClose }: Props) => {
</li>
)}
{one.type === 'page' && (
<li className="group/one relative hover:bg-[#e0e6ec] dark:hover:bg-[#495362] rounded-md ">
<Link href={getCateNavHref(one)} target={getCateNavTarget(one.type)} rel={getCateNavRel(one.type)} className={`flex justify-between items-center p-3 px-5 text-[15px] group-hover/one:!text-primary text-[#333] dark:text-white whitespace-nowrap`} onClick={onClose}>
{one.icon} {one.name}
<Show is={!!one.children.length}>
<IoIosArrowDown className="ml-2" />
</Show>
</Link>
<Show is={!!one.children.length}>
<ul className="overflow-hidden top-[50px] w-full rounded-md">
{one.children?.map((two) => (
<li key={two.id} className="group/two">
<Link href={getCateNavHref(two)} target={getCateNavTarget(two.type)} rel={getCateNavRel(two.type)} className="inline-block w-full p-2.5 pl-10 text-[15px] box-border text-[#666] dark:text-[#8c9ab1] hover:!text-primary" onClick={onClose}>
{two.icon} {two.name}
</Link>
</li>
))}
</ul>
</Show>
</li>
)}
{one.type === 'nav' && (
<li className="group/one relative hover:bg-[#e0e6ec] dark:hover:bg-[#495362] rounded-md ">
<Link href={one.url} target={`${one.url.startsWith('http') ? '_blank' : '_self'}`} className={`flex justify-between items-center p-3 px-5 text-[15px] group-hover/one:!text-primary text-[#333] dark:text-white whitespace-nowrap`} onClick={onClose}>
<Link href={getCateNavHref(one)} target={getCateNavTarget(one.type)} rel={getCateNavRel(one.type)} className={`flex justify-between items-center p-3 px-5 text-[15px] group-hover/one:!text-primary text-[#333] dark:text-white whitespace-nowrap`} onClick={onClose}>
{one.icon} {one.name}
<Show is={!!one.children.length}>
<IoIosArrowDown className="ml-2" />
@@ -56,7 +80,7 @@ export default ({ list, open, onClose }: Props) => {
<ul className="overflow-hidden top-[50px] w-full rounded-md">
{one.children?.map((two) => (
<li key={two.id} className="group/two">
<Link href={two.url} target={`${two.url.startsWith('http') ? '_blank' : '_self'}`} className="inline-block w-full p-2.5 pl-10 text-[15px] box-border text-[#666] dark:text-[#8c9ab1] hover:!text-primary" onClick={onClose}>
<Link href={getCateNavHref(two)} target={getCateNavTarget(two.type)} rel={getCateNavRel(two.type)} className="inline-block w-full p-2.5 pl-10 text-[15px] box-border text-[#666] dark:text-[#8c9ab1] hover:!text-primary" onClick={onClose}>
{two.icon} {two.name}
</Link>
</li>

View File

@@ -14,6 +14,7 @@ import { BsFillMoonStarsFill, BsTextIndentLeft } from 'react-icons/bs';
import { Cate } from '@/types/app/cate';
import { getCateListAPI } from '@/api/cate';
import { getCateNavHref, getCateNavRel, getCateNavTarget } from '@/utils/cateNav';
import { useConfigStore } from '@/stores';
@@ -107,7 +108,7 @@ export default () => {
{/* 渲染分类 */}
{one.type === 'cate' && (
<li className="group/one relative">
<Link href={`/cate/${one.id}?name=${one.name}`} target={`${one.url.startsWith('http') ? '_blank' : '_self'}`} className={`flex items-center p-5 text-[15px] whitespace-nowrap group-hover/one:!text-primary ${isPathSty || isScrolled ? 'text-[#333] dark:text-white' : 'text-white'}`}>
<Link href={getCateNavHref(one)} target={getCateNavTarget(one.type)} rel={getCateNavRel(one.type)} className={`flex items-center p-5 text-[15px] whitespace-nowrap group-hover/one:!text-primary ${isPathSty || isScrolled ? 'text-[#333] dark:text-white' : 'text-white'}`}>
{one.icon} {one.name}
<Show is={!!one.children.length}>
<IoIosArrowDown className="ml-2" />
@@ -122,7 +123,7 @@ export default () => {
className="opacity-0 -translate-x-2 group-hover/one:opacity-100 group-hover/one:translate-x-0 transition-all duration-300 ease-out"
style={{ transitionDelay: `${index * 45 + 60}ms` }}
>
<Link href={`/cate/${two.id}?name=${two.name}`} target={`${two.url.startsWith('http') ? '_blank' : '_self'}`} title={two.name} className={`${submenuItemClass} truncate`}>
<Link href={getCateNavHref(two)} target={getCateNavTarget(two.type)} rel={getCateNavRel(two.type)} title={two.name} className={`${submenuItemClass} truncate`}>
{two.name}
</Link>
</li>
@@ -132,10 +133,39 @@ export default () => {
</li>
)}
{/* 渲染页面 */}
{one.type === 'page' && (
<li className="group/one relative">
<Link href={getCateNavHref(one)} target={getCateNavTarget(one.type)} rel={getCateNavRel(one.type)} className={`flex items-center p-5 px-10 text-[15px] whitespace-nowrap group-hover/one:!text-primary ${isPathSty || isScrolled ? 'text-[#333] dark:text-white' : 'text-white'}`}>
{one.icon} {one.name}
<Show is={!!one.children?.length}>
<IoIosArrowDown className="ml-2" />
</Show>
</Link>
<Show is={!!one.children?.length}>
<ul className={`${submenuPanelClass} bg-[rgba(255,255,255,0.95)] dark:bg-[rgba(44,51,62,0.95)]`} style={{ boxShadow: '0 12px 32px rgba(0, 0, 0, 0.1), 0 2px 6px rgba(0, 0, 0, 0.08)' }}>
{one.children?.map((two, index) => (
<li
key={two.id}
className="opacity-0 -translate-x-2 group-hover/one:opacity-100 group-hover/one:translate-x-0 transition-all duration-300 ease-out"
style={{ transitionDelay: `${index * 45 + 60}ms` }}
>
<Link href={getCateNavHref(two)} target={getCateNavTarget(two.type)} rel={getCateNavRel(two.type)} title={two.name} className={`${submenuItemClass} gap-1.5`}>
<span className="shrink-0 transition-transform duration-300 ease-[cubic-bezier(0.34,1.56,0.64,1)] group-hover/item:scale-125 group-hover/item:-rotate-6">{two.icon}</span>
<span className="truncate">{two.name}</span>
</Link>
</li>
))}
</ul>
</Show>
</li>
)}
{/* 渲染导航 */}
{one.type === 'nav' && (
<li className="group/one relative">
<Link href={one.url} className={`flex items-center p-5 px-10 text-[15px] whitespace-nowrap group-hover/one:!text-primary ${isPathSty || isScrolled ? 'text-[#333] dark:text-white' : 'text-white'}`}>
<Link href={getCateNavHref(one)} target={getCateNavTarget(one.type)} rel={getCateNavRel(one.type)} className={`flex items-center p-5 px-10 text-[15px] whitespace-nowrap group-hover/one:!text-primary ${isPathSty || isScrolled ? 'text-[#333] dark:text-white' : 'text-white'}`}>
{one.icon} {one.name}
{/* 如果有子分类就显示下拉三角 */}
<Show is={!!one.children?.length}>
@@ -151,7 +181,7 @@ export default () => {
className="opacity-0 -translate-x-2 group-hover/one:opacity-100 group-hover/one:translate-x-0 transition-all duration-300 ease-out"
style={{ transitionDelay: `${index * 45 + 60}ms` }}
>
<Link href={two.url} title={two.name} className={`${submenuItemClass} gap-1.5`}>
<Link href={getCateNavHref(two)} target={getCateNavTarget(two.type)} rel={getCateNavRel(two.type)} title={two.name} className={`${submenuItemClass} gap-1.5`}>
<span className="shrink-0 transition-transform duration-300 ease-[cubic-bezier(0.34,1.56,0.64,1)] group-hover/item:scale-125 group-hover/item:-rotate-6">{two.icon}</span>
<span className="truncate">{two.name}</span>
</Link>

View File

@@ -5,7 +5,7 @@ export interface Cate {
url: string,
icon: string,
level: number,
type: 'cate' | 'nav',
type: 'cate' | 'page' | 'nav',
count: number,
is_hide: boolean,
order: number,

16
src/utils/cateNav.ts Normal file
View File

@@ -0,0 +1,16 @@
import { Cate } from '@/types/app/cate';
export function getCateNavHref(item: Cate): string {
if (item.type === 'cate') {
return `/cate/${item.id}?name=${item.name}`;
}
return item.url || '/';
}
export function getCateNavTarget(type: Cate['type']): '_self' | '_blank' {
return type === 'nav' ? '_blank' : '_self';
}
export function getCateNavRel(type: Cate['type']): 'noopener noreferrer' | undefined {
return type === 'nav' ? 'noopener noreferrer' : undefined;
}

View File

@@ -3,4 +3,5 @@ export * from './await-io';
export * from './htmlParser';
export * from './common';
export * from './url';
export * from './request';
export * from './request';
export * from './cateNav';