This commit is contained in:
ditiqi 2025-01-26 09:28:38 +08:00
parent daae6a9018
commit a0447eab2f
11 changed files with 552 additions and 294 deletions

View File

@ -38,7 +38,7 @@ export class BaseService<
protected prisma: PrismaClient, protected prisma: PrismaClient,
protected objectType: string, protected objectType: string,
protected enableOrder: boolean = false, protected enableOrder: boolean = false,
) { } ) {}
/** /**
* Retrieves the name of the model dynamically. * Retrieves the name of the model dynamically.
@ -451,7 +451,11 @@ export class BaseService<
pageSize?: number; pageSize?: number;
where?: WhereArgs<A['findMany']>; where?: WhereArgs<A['findMany']>;
select?: SelectArgs<A['findMany']>; select?: SelectArgs<A['findMany']>;
}): Promise<{ items: R['findMany']; totalPages: number, totalCount: number }> { }): Promise<{
items: R['findMany'];
totalPages: number;
totalCount: number;
}> {
const { page = 1, pageSize = 10, where, select } = args; const { page = 1, pageSize = 10, where, select } = args;
try { try {
@ -470,7 +474,7 @@ export class BaseService<
return { return {
items, items,
totalPages, totalPages,
totalCount: total totalCount: total,
}; };
} catch (error) { } catch (error) {
this.handleError(error, 'read'); this.handleError(error, 'read');

View File

@ -0,0 +1,74 @@
export function Header() {
return (
<header className="bg-gradient-to-r from-primary to-primary-400 p-6 rounded-t-xl">
<div className="flex flex-col space-y-6">
{/* 主标题区域 */}
<div>
<h1 className="text-3xl font-bold tracking-wider text-white">
</h1>
<p className="mt-2 text-blue-100 text-lg">
</p>
</div>
{/* 服务特点说明 */}
<div className="flex flex-wrap gap-6 text-sm text-white">
<div className="flex items-center gap-2">
<svg
className="w-5 h-5"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24">
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M20 13V6a2 2 0 00-2-2H6a2 2 0 00-2 2v7m16 0v5a2 2 0 01-2 2H6a2 2 0 01-2-2v-5m16 0h-2.586a1 1 0 00-.707.293l-2.414 2.414a1 1 0 01-.707.293h-3.172a1 1 0 01-.707-.293l-2.414-2.414A1 1 0 006.586 13H4"
/>
</svg>
<span></span>
</div>
<div className="flex items-center gap-2">
<svg
className="w-5 h-5"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24">
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M12 6v6m0 0v6m0-6h6m-6 0H6"
/>
</svg>
<span></span>
</div>
<div className="flex items-center gap-2">
<svg
className="w-5 h-5"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24">
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M8 10h.01M12 10h.01M16 10h.01M9 16H5a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v8a2 2 0 01-2 2h-5l-5 5v-5z"
/>
</svg>
<span></span>
</div>
</div>
{/* 服务宗旨说明 */}
<div className="text-sm text-blue-100 border-t border-blue-400/30 pt-4">
<p className="leading-relaxed">
</p>
</div>
</div>
</header>
);
}

View File

@ -0,0 +1,24 @@
import LetterList from "@web/src/components/models/post/list/LetterList";
import { Header } from "./Header";
import { useAuth } from "@web/src/providers/auth-provider";
export default function InboxPage() {
const { user } = useAuth();
return (
// 添加 flex flex-col 使其成为弹性布局容器
<div className="min-h-screen shadow-elegant border-2 border-white rounded-xl overflow-hidden bg-gradient-to-b from-slate-100 to-slate-50 flex flex-col">
<Header />
{/* 添加 flex-grow 使内容区域自动填充剩余空间 */}
<LetterList
params={{
where: {
receivers: {
some: {
id: user?.id,
},
},
},
}}></LetterList>
</div>
);
}

View File

@ -0,0 +1,11 @@
import { useAuth } from "@web/src/providers/auth-provider";
import InboxPage from "../inbox/page";
import LetterListPage from "../list/page";
export default function IndexPage() {
const { user } = useAuth();
if (user) {
return <InboxPage></InboxPage>;
}
return <LetterListPage></LetterListPage>;
}

View File

@ -0,0 +1,74 @@
export function Header() {
return (
<header className="bg-gradient-to-r from-primary to-primary-400 p-6 rounded-t-xl">
<div className="flex flex-col space-y-6">
{/* 主标题区域 */}
<div>
<h1 className="text-3xl font-bold tracking-wider text-white">
</h1>
<p className="mt-2 text-blue-100 text-lg">
</p>
</div>
{/* 服务特点说明 */}
<div className="flex flex-wrap gap-6 text-sm text-white">
<div className="flex items-center gap-2">
<svg
className="w-5 h-5"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24">
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M12 19l9 2-9-18-9 18 9-2zm0 0v-8"
/>
</svg>
<span></span>
</div>
<div className="flex items-center gap-2">
<svg
className="w-5 h-5"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24">
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"
/>
</svg>
<span></span>
</div>
<div className="flex items-center gap-2">
<svg
className="w-5 h-5"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24">
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M8 10h.01M12 10h.01M16 10h.01M9 16H5a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v8a2 2 0 01-2 2h-5l-5 5v-5z"
/>
</svg>
<span></span>
</div>
</div>
{/* 服务宗旨说明 */}
<div className="text-sm text-blue-100 border-t border-blue-400/30 pt-4">
<p className="leading-relaxed">
</p>
</div>
</div>
</header>
);
}

View File

@ -0,0 +1,20 @@
import LetterList from "@web/src/components/models/post/list/LetterList";
import { Header } from "./Header";
import { useAuth } from "@web/src/providers/auth-provider";
export default function OutboxPage() {
const { user } = useAuth();
return (
// 添加 flex flex-col 使其成为弹性布局容器
<div className="min-h-screen shadow-elegant border-2 border-white rounded-xl overflow-hidden bg-gradient-to-b from-slate-100 to-slate-50 flex flex-col">
<Header />
{/* 添加 flex-grow 使内容区域自动填充剩余空间 */}
<LetterList
params={{
where: {
authorId: user?.id,
},
}}></LetterList>
</div>
);
}

View File

@ -22,6 +22,7 @@ export default function WriteLetterPage() {
const { data, isLoading, error } = api.staff.findManyWithPagination.useQuery({ const { data, isLoading, error } = api.staff.findManyWithPagination.useQuery({
page: currentPage, page: currentPage,
pageSize, pageSize,
where: { where: {
deptId: selectedDept, deptId: selectedDept,
OR: [{ OR: [{

View File

@ -4,194 +4,196 @@ import { motion, AnimatePresence } from "framer-motion";
import { useState, useRef, useCallback, useMemo } from "react"; import { useState, useRef, useCallback, useMemo } from "react";
import { Avatar } from "../../common/element/Avatar"; import { Avatar } from "../../common/element/Avatar";
import { import {
UserOutlined, UserOutlined,
SettingOutlined, SettingOutlined,
QuestionCircleOutlined, QuestionCircleOutlined,
LogoutOutlined LogoutOutlined,
} from "@ant-design/icons"; } from "@ant-design/icons";
import { Spin } from "antd"; import { Spin } from "antd";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import { MenuItemType } from "./types"; import { MenuItemType } from "./types";
const menuVariants = { const menuVariants = {
hidden: { opacity: 0, scale: 0.95, y: -10 }, hidden: { opacity: 0, scale: 0.95, y: -10 },
visible: { visible: {
opacity: 1, opacity: 1,
scale: 1, scale: 1,
y: 0, y: 0,
transition: { transition: {
type: "spring", type: "spring",
stiffness: 300, stiffness: 300,
damping: 30 damping: 30,
} },
}, },
exit: { exit: {
opacity: 0, opacity: 0,
scale: 0.95, scale: 0.95,
y: -10, y: -10,
transition: { transition: {
duration: 0.2 duration: 0.2,
} },
} },
}; };
export function UserMenu() { export function UserMenu() {
const [showMenu, setShowMenu] = useState(false); const [showMenu, setShowMenu] = useState(false);
const menuRef = useRef<HTMLDivElement>(null); const menuRef = useRef<HTMLDivElement>(null);
const { user, logout, isLoading } = useAuth(); const { user, logout, isLoading } = useAuth();
const navigate = useNavigate() const navigate = useNavigate();
useClickOutside(menuRef, () => setShowMenu(false)); useClickOutside(menuRef, () => setShowMenu(false));
const toggleMenu = useCallback(() => { const toggleMenu = useCallback(() => {
setShowMenu(prev => !prev); setShowMenu((prev) => !prev);
}, []); }, []);
const menuItems: MenuItemType[] = useMemo(() => [ const menuItems: MenuItemType[] = useMemo(
{ () => [
icon: <UserOutlined className="text-lg" />, {
label: '个人信息', icon: <UserOutlined className="text-lg" />,
action: () => { }, label: "个人信息",
}, action: () => {},
{ },
icon: <SettingOutlined className="text-lg" />, {
label: '设置', icon: <SettingOutlined className="text-lg" />,
action: () => { label: "设置",
navigate('/admin/staff') action: () => {
}, navigate("/admin/staff");
}, },
{ },
icon: <QuestionCircleOutlined className="text-lg" />, // {
label: '帮助', // icon: <QuestionCircleOutlined className="text-lg" />,
action: () => { }, // label: '帮助',
}, // action: () => { },
{ // },
icon: <LogoutOutlined className="text-lg" />, {
label: '注销', icon: <LogoutOutlined className="text-lg" />,
action: () => logout(), label: "注销",
}, action: () => logout(),
], [logout]); },
],
[logout]
);
const handleMenuItemClick = useCallback((action: () => void) => { const handleMenuItemClick = useCallback((action: () => void) => {
action(); action();
setShowMenu(false); setShowMenu(false);
}, []); }, []);
if (isLoading) { if (isLoading) {
return ( return (
<div className="flex items-center justify-center w-10 h-10"> <div className="flex items-center justify-center w-10 h-10">
<Spin size="small" /> <Spin size="small" />
</div> </div>
); );
} }
return ( return (
<div ref={menuRef} className="relative"> <div ref={menuRef} className="relative">
<motion.button <motion.button
aria-label="用户菜单" aria-label="用户菜单"
aria-haspopup="true" aria-haspopup="true"
aria-expanded={showMenu} aria-expanded={showMenu}
aria-controls="user-menu" aria-controls="user-menu"
whileHover={{ scale: 1.02 }} whileHover={{ scale: 1.02 }}
whileTap={{ scale: 0.98 }} whileTap={{ scale: 0.98 }}
onClick={toggleMenu} onClick={toggleMenu}
className="relative rounded-full focus:outline-none className="relative rounded-full focus:outline-none
focus:ring-2 focus:ring-[#00538E]/80 focus:ring-offset-2 focus:ring-2 focus:ring-[#00538E]/80 focus:ring-offset-2
focus:ring-offset-white transition-all duration-200 ease-in-out" focus:ring-offset-white transition-all duration-200 ease-in-out">
> <Avatar
<Avatar src={user?.avatar}
src={user?.avatar} name={user?.showname || user?.username}
name={user?.showname || user?.username} size={40}
size={40} className="ring-2 ring-white hover:ring-[#00538E]/90
className="ring-2 ring-white hover:ring-[#00538E]/90
transition-all duration-200 ease-in-out shadow-md transition-all duration-200 ease-in-out shadow-md
hover:shadow-lg" hover:shadow-lg"
/> />
<span <span
className="absolute bottom-0 right-0 h-3 w-3 className="absolute bottom-0 right-0 h-3 w-3
rounded-full bg-emerald-500 ring-2 ring-white rounded-full bg-emerald-500 ring-2 ring-white
shadow-sm transition-transform duration-200 shadow-sm transition-transform duration-200
ease-in-out hover:scale-110" ease-in-out hover:scale-110"
aria-hidden="true" aria-hidden="true"
/> />
</motion.button> </motion.button>
<AnimatePresence> <AnimatePresence>
{showMenu && ( {showMenu && (
<motion.div <motion.div
initial="hidden" initial="hidden"
animate="visible" animate="visible"
exit="exit" exit="exit"
variants={menuVariants} variants={menuVariants}
role="menu" role="menu"
id="user-menu" id="user-menu"
aria-orientation="vertical" aria-orientation="vertical"
aria-labelledby="user-menu-button" aria-labelledby="user-menu-button"
style={{ zIndex: 100 }} style={{ zIndex: 100 }}
className="absolute right-0 mt-3 w-64 origin-top-right className="absolute right-0 mt-3 w-64 origin-top-right
bg-white rounded-xl overflow-hidden shadow-lg bg-white rounded-xl overflow-hidden shadow-lg
border border-[#E5EDF5]" border border-[#E5EDF5]">
> {/* User Profile Section */}
{/* User Profile Section */} <div
<div className="px-4 py-4 bg-gradient-to-b from-[#F6F9FC] to-white
className="px-4 py-4 bg-gradient-to-b from-[#F6F9FC] to-white border-b border-[#E5EDF5] ">
border-b border-[#E5EDF5] " <div className="flex items-center space-x-4">
<Avatar
src={user?.avatar}
name={user?.showname || user?.username}
size={40}
className="ring-2 ring-white shadow-sm"
/>
<div className="flex flex-col space-y-0.5">
<span className="text-sm font-semibold text-[#00538E]">
{user?.showname || user?.username}
</span>
<span className="text-xs text-[#718096] flex items-center gap-1.5">
<span className="w-1.5 h-1.5 rounded-full bg-emerald-500 animate-pulse"></span>
线
</span>
</div>
</div>
</div>
> {/* Menu Items */}
<div className="flex items-center space-x-4"> <div className="p-2">
<Avatar {menuItems.map((item, index) => (
src={user?.avatar} <button
name={user?.showname || user?.username} key={index}
size={40} role="menuitem"
className="ring-2 ring-white shadow-sm" tabIndex={showMenu ? 0 : -1}
/> onClick={(e) => {
<div className="flex flex-col space-y-0.5"> e.stopPropagation();
<span className="text-sm font-semibold text-[#00538E]"> handleMenuItemClick(item.action);
{user?.showname || user?.username} }}
</span> className={`flex items-center gap-3 w-full px-4 py-3
<span className="text-xs text-[#718096] flex items-center gap-1.5">
<span className="w-1.5 h-1.5 rounded-full bg-emerald-500 animate-pulse"></span>
线
</span>
</div>
</div>
</div>
{/* Menu Items */}
<div className="p-2">
{menuItems.map((item, index) => (
<button
key={index}
role="menuitem"
tabIndex={showMenu ? 0 : -1}
onClick={(e) => {
e.stopPropagation();
handleMenuItemClick(item.action);
}}
className={`flex items-center gap-3 w-full px-4 py-3
text-sm font-medium rounded-lg transition-all text-sm font-medium rounded-lg transition-all
focus:outline-none focus:outline-none
focus:ring-2 focus:ring-[#00538E]/20 focus:ring-2 focus:ring-[#00538E]/20
group relative overflow-hidden group relative overflow-hidden
active:scale-[0.99] active:scale-[0.99]
${item.label === '注销' ${
? 'text-[#B22234] hover:bg-red-50/80 hover:text-red-700' item.label === "注销"
: 'text-[#00538E] hover:bg-[#E6EEF5] hover:text-[#003F6A]' ? "text-[#B22234] hover:bg-red-50/80 hover:text-red-700"
}`} : "text-[#00538E] hover:bg-[#E6EEF5] hover:text-[#003F6A]"
> }`}>
<span className={`w-5 h-5 flex items-center justify-center <span
className={`w-5 h-5 flex items-center justify-center
transition-all duration-200 ease-in-out transition-all duration-200 ease-in-out
group-hover:scale-110 group-hover:rotate-6 group-hover:scale-110 group-hover:rotate-6
group-hover:translate-x-0.5 ${item.label === '注销' group-hover:translate-x-0.5 ${
? 'group-hover:text-red-600' item.label === "注销"
: 'group-hover:text-[#003F6A]'}`}> ? "group-hover:text-red-600"
{item.icon} : "group-hover:text-[#003F6A]"
</span> }`}>
<span>{item.label}</span> {item.icon}
</button> </span>
))} <span>{item.label}</span>
</div> </button>
</motion.div> ))}
)} </div>
</AnimatePresence> </motion.div>
</div> )}
); </AnimatePresence>
</div>
);
} }

View File

@ -1,96 +1,108 @@
import { NavLink, useLocation } from "react-router-dom"; import { NavLink, useLocation } from "react-router-dom";
import { useNavItem } from "./useNavItem"; import { useNavItem } from "./useNavItem";
import { twMerge } from "tailwind-merge"; import { twMerge } from "tailwind-merge";
import React from "react";
interface NavItem { interface NavItem {
to: string; to: string;
label: string; label: string;
icon?: React.ReactNode; icon?: React.ReactNode;
} }
interface NavigationProps { interface NavigationProps {
className?: string; className?: string;
} }
export default function Navigation({ className }: NavigationProps) { export default function Navigation({ className }: NavigationProps) {
const { navItems } = useNavItem(); const { navItems } = useNavItem();
const location = useLocation(); const location = useLocation();
const isActive = (to: string) => { const isActive = (to: string) => {
const [pathname, search] = to.split('?'); const [pathname, search] = to.split("?");
return location.pathname === pathname && return (
(!search ? !location.search : location.search === `?${search}`); location.pathname === pathname &&
}; (!search ? !location.search : location.search === `?${search}`)
);
};
return ( return (
<nav className={twMerge( <nav
"mt-4 rounded-xl bg-gradient-to-r from-primary-500 to-primary-600 shadow-lg", className={twMerge(
className "mt-4 rounded-xl bg-gradient-to-r from-primary-500 to-primary-600 shadow-lg",
)}> className
<div className="flex flex-col md:flex-row items-stretch"> )}>
{/* Desktop Navigation */} <div className="flex flex-col md:flex-row items-stretch">
<div className="hidden md:flex items-center px-6 py-2 w-full overflow-x-auto"> {/* Desktop Navigation */}
<div className="flex space-x-6 min-w-max"> <div className="hidden md:flex items-center px-6 py-2 w-full overflow-x-auto">
{navItems.map((item) => ( <div className="flex space-x-6 min-w-max">
<NavLink {navItems.map((item) => (
key={item.to} <NavLink
to={item.to} key={item.to}
className={({ isActive: active }) => twMerge( to={item.to}
"relative px-4 py-2.5 text-sm font-medium", className={({ isActive: active }) =>
"text-gray-300 hover:text-white", twMerge(
"transition-all duration-200 ease-out group", "relative px-4 py-2.5 text-sm font-medium",
active && "text-white" "text-gray-300 hover:text-white",
)} "transition-all duration-200 ease-out group",
> active && "text-white"
<span className="relative z-10 flex items-center gap-2 transition-transform group-hover:translate-y-[-1px]"> )
{item.icon} }>
<span className="tracking-wide">{item.label}</span> <span className="relative z-10 flex items-center gap-2 transition-transform group-hover:translate-y-[-1px]">
</span> {item.icon}
<span className="tracking-wide">
{item.label}
</span>
</span>
{/* Active Indicator */} {/* Active Indicator */}
<span className={twMerge( <span
"absolute bottom-0 left-1/2 h-[2px] bg-blue-400", className={twMerge(
"transition-all duration-300 ease-out", "absolute bottom-0 left-1/2 h-[2px] bg-blue-400",
"transform -translate-x-1/2", "transition-all duration-300 ease-out",
isActive(item.to) "transform -translate-x-1/2",
? "w-full opacity-100" isActive(item.to)
: "w-0 opacity-0 group-hover:w-1/2 group-hover:opacity-40" ? "w-full opacity-100"
)} /> : "w-0 opacity-0 group-hover:w-1/2 group-hover:opacity-40"
)}
/>
{/* Hover Glow Effect */} {/* Hover Glow Effect */}
<span className={twMerge( <span
"absolute inset-0 rounded-lg bg-blue-400/0", className={twMerge(
"transition-all duration-300", "absolute inset-0 rounded-lg bg-blue-400/0",
"group-hover:bg-blue-400/5" "transition-all duration-300",
)} /> "group-hover:bg-blue-400/5"
</NavLink> )}
))} />
</div> </NavLink>
</div> ))}
</div>
</div>
{/* Mobile Navigation */} {/* Mobile Navigation */}
<div className="md:hidden flex overflow-x-auto scrollbar-none px-4 py-2"> <div className="md:hidden flex overflow-x-auto scrollbar-none px-4 py-2">
<div className="flex space-x-4 min-w-max"> <div className="flex space-x-4 min-w-max">
{navItems.map((item) => ( {navItems.map((item) => (
<NavLink <NavLink
key={item.to} key={item.to}
to={item.to} to={item.to}
className={({ isActive: active }) => twMerge( className={({ isActive: active }) =>
"px-3 py-1.5 text-sm font-medium rounded-full", twMerge(
"transition-colors duration-200", "px-3 py-1.5 text-sm font-medium rounded-full",
"text-gray-300 hover:text-white", "transition-colors duration-200",
active && "bg-blue-500/20 text-white" "text-gray-300 hover:text-white",
)} active && "bg-blue-500/20 text-white"
> )
<span className="flex items-center gap-1.5"> }>
{item.icon} <span className="flex items-center gap-1.5">
<span>{item.label}</span> {item.icon}
</span> <span>{item.label}</span>
</NavLink> </span>
))} </NavLink>
</div> ))}
</div> </div>
</div> </div>
</nav> </div>
); </nav>
);
} }

View File

@ -1,66 +1,87 @@
import { api } from "@nice/client"; import { api } from "@nice/client";
import { TaxonomySlug } from "@nice/common"; import { TaxonomySlug } from "@nice/common";
import { useMemo } from "react"; import React, { useMemo } from "react";
import { MailOutlined, SendOutlined } from "@ant-design/icons";
import { import {
FileTextOutlined, FileTextOutlined,
ScheduleOutlined, ScheduleOutlined,
QuestionCircleOutlined, QuestionCircleOutlined,
FolderOutlined, FolderOutlined,
TagsOutlined TagsOutlined,
} from "@ant-design/icons"; } from "@ant-design/icons";
import { useAuth } from "@web/src/providers/auth-provider";
export interface NavItem { export interface NavItem {
to: string; to: string;
label: string; label: string;
icon?: React.ReactNode; icon?: React.ReactNode;
} }
export function useNavItem() { export function useNavItem() {
const { data } = api.term.findMany.useQuery({ const { user } = useAuth();
where: { const { data } = api.term.findMany.useQuery({
taxonomy: { slug: TaxonomySlug.CATEGORY } where: {
} taxonomy: { slug: TaxonomySlug.CATEGORY },
}); },
});
const navItems = useMemo(() => { const navItems = useMemo(() => {
// 定义固定的导航项 // 定义固定的导航项
const staticItems = { const staticItems = {
letterList: { inbox: {
to: "/", to: user ? "/" : "/inbox",
label: "公开信件", label: "我收到的",
icon: <FileTextOutlined className="text-base" /> icon: <MailOutlined className="text-base" />,
}, },
letterProgress: { outbox: {
to: "/letter-progress", to: "/outbox",
label: "进度查询", label: "我发出的",
icon: <ScheduleOutlined className="text-base" /> icon: <SendOutlined className="text-base" />,
}, },
help: { letterList: {
to: "/help", to: !user ? "/" : "/letter-list",
label: "使用帮助", label: "公开信件",
icon: <QuestionCircleOutlined className="text-base" /> icon: <FileTextOutlined className="text-base" />,
} },
}; letterProgress: {
to: "/letter-progress",
label: "进度查询",
icon: <ScheduleOutlined className="text-base" />,
},
// help: {
// to: "/help",
// label: "使用帮助",
// icon: <QuestionCircleOutlined className="text-base" />
// }
};
if (!data) { if (!data) {
return [staticItems.letterList, staticItems.letterProgress, staticItems.help]; return [
} user && staticItems.inbox,
user && staticItems.outbox,
staticItems.letterList,
staticItems.letterProgress,
// staticItems.help,
].filter(Boolean);
}
// 构建分类导航项 // 构建分类导航项
const categoryItems = data.map(term => ({ const categoryItems = data.map((term) => ({
to: `/write-letter?termId=${term.id}`, to: `/write-letter?termId=${term.id}`,
label: term.name, label: term.name,
icon: <TagsOutlined className="text-base"></TagsOutlined> icon: <TagsOutlined className="text-base"></TagsOutlined>,
})); }));
// 按照指定顺序返回导航项 // 按照指定顺序返回导航项
return [ return [
staticItems.letterList, user && staticItems.inbox,
staticItems.letterProgress, user && staticItems.outbox,
...categoryItems, staticItems.letterList,
staticItems.help staticItems.letterProgress,
]; ...categoryItems,
}, [data]); // staticItems.help,
].filter(Boolean);
}, [data]);
return { navItems }; return { navItems };
} }

View File

@ -23,6 +23,9 @@ import LetterDetailPage from "../app/main/letter/detail/page";
import AdminLayout from "../components/layout/admin/AdminLayout"; import AdminLayout from "../components/layout/admin/AdminLayout";
import { CustomRouteObject } from "./types"; import { CustomRouteObject } from "./types";
import { adminRoute } from "./admin-route"; import { adminRoute } from "./admin-route";
import InboxPage from "../app/main/letter/inbox/page";
import OutboxPage from "../app/main/letter/outbox/page";
import IndexPage from "../app/main/letter/index/page";
export const routes: CustomRouteObject[] = [ export const routes: CustomRouteObject[] = [
{ {
path: "/", path: "/",
@ -36,8 +39,20 @@ export const routes: CustomRouteObject[] = [
{ {
element: <MainLayout></MainLayout>, element: <MainLayout></MainLayout>,
children: [ children: [
{
path: "inbox",
element: <InboxPage></InboxPage>,
},
{
path: "outbox",
element: <OutboxPage></OutboxPage>,
},
{ {
index: true, index: true,
element: <IndexPage></IndexPage>,
},
{
path: "letter-list",
element: <LetterListPage></LetterListPage>, element: <LetterListPage></LetterListPage>,
}, },
{ {