refactor: restructure directories to improve module organization Foxel.Models.Request.Picture - Foxel.Models.Request.Tag - Foxel.Models.Request.Auth - Foxel.Models.Request.Picture

This commit is contained in:
ShiYu
2025-05-23 15:07:37 +08:00
parent a03e245d67
commit 0691f1c87d
91 changed files with 30 additions and 30 deletions

View File

@@ -0,0 +1,34 @@
import React from 'react';
import { Layout } from 'antd';
import { GithubOutlined } from '@ant-design/icons';
const { Footer: AntFooter } = Layout;
interface FooterProps {
isMobile?: boolean;
}
const Footer: React.FC<FooterProps> = ({ isMobile = false }) => {
return (
<AntFooter style={{
background: 'white',
padding: isMobile ? '10px' : '10px',
fontSize: isMobile ? '12px' : '12px',
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center'
}}>
<div>Foxel ©{new Date().getFullYear()}</div>
<a
href="https://github.com/DrizzleTime/Foxel"
target="_blank"
rel="noopener noreferrer"
style={{ fontSize: isMobile ? '16px' : '18px', color: '#333' }}
>
<GithubOutlined />
</a>
</AntFooter>
);
};
export default Footer;

View File

@@ -0,0 +1,193 @@
import { Layout, Button, Dropdown, Breadcrumb, Input } from 'antd';
import {
MenuFoldOutlined,
MenuUnfoldOutlined,
UserOutlined,
LogoutOutlined,
SettingOutlined
} from '@ant-design/icons';
import { useNavigate, Link } from 'react-router';
import routes, { type RouteConfig } from '../../config/routeConfig';
import UserAvatar from '../../components/UserAvatar';
import { useAuth } from '../../api/AuthContext';
import { useState } from 'react';
import SearchDialog from '../../components/search/SearchDialog';
const { Header: AntHeader } = Layout;
const { Search } = Input;
interface HeaderProps {
collapsed: boolean;
toggleCollapsed: () => void;
onLogout: () => void;
currentRouteData?: {
routeInfo: RouteConfig | undefined;
params: Record<string, string>;
title?: string; // 动态标题,用于显示如"相册名称"等动态数据
};
isMobile?: boolean;
}
const Header = ({
collapsed,
toggleCollapsed,
onLogout,
currentRouteData,
isMobile = false
}: HeaderProps) => {
const navigate = useNavigate();
const { user } = useAuth();
const [searchDialogVisible, setSearchDialogVisible] = useState(false);
const [searchText, setSearchText] = useState('');
const userMenuItems = [
{
key: 'profile',
icon: <UserOutlined/>,
label: '个人资料',
onClick: () => navigate('/settings')
},
{
key: 'settings',
icon: <SettingOutlined/>,
label: '设置',
onClick: () => navigate('/settings')
},
{
key: 'logout',
icon: <LogoutOutlined/>,
label: '退出登录',
onClick: onLogout
}
];
// 生成面包屑项
const generateBreadcrumbItems = () => {
const breadcrumbItems = [];
// 添加首页
breadcrumbItems.push({
key: 'home',
title: <Link to="/"></Link>,
});
// 确保routeInfo和breadcrumb都存在
if (currentRouteData?.routeInfo && currentRouteData.routeInfo.breadcrumb) {
const { routeInfo, title } = currentRouteData;
const breadcrumb = routeInfo.breadcrumb;
// 如果有父级路由,先添加父级路由的面包屑
if (breadcrumb && breadcrumb.parent) {
const parentRoute = routes.find(r => r.key === breadcrumb.parent);
if (parentRoute && parentRoute.breadcrumb) {
breadcrumbItems.push({
key: parentRoute.key,
title: <Link to={`/${parentRoute.path}`}>{parentRoute.breadcrumb.title}</Link>,
});
}
}
// 添加当前路由的面包屑
breadcrumbItems.push({
key: routeInfo.key,
title: title || breadcrumb?.title,
});
}
return breadcrumbItems;
};
// 处理搜索框输入
const handleSearchInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setSearchText(e.target.value);
};
// 处理搜索操作,仅当点击搜索按钮或按回车时执行
const handleSearch = (value: string) => {
if (value.trim() || !value) { // 允许空搜索打开高级搜索
setSearchDialogVisible(true);
}
};
return (
<>
<AntHeader style={{
padding: isMobile ? '0 10px' : '0 40px',
background: '#ffffff',
borderBottom: '1px solid #f0f0f0',
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
height: isMobile ? 56 : 64,
position: 'sticky',
top: 0,
zIndex: 10,
width: '100%',
backdropFilter: 'blur(10px)'
}}>
<div style={{ display: 'flex', alignItems: 'center', gap: 16 }}>
<Button
type="text"
icon={collapsed ? <MenuUnfoldOutlined/> : <MenuFoldOutlined/>}
onClick={toggleCollapsed}
style={{
fontSize: 18,
width: 46,
height: 46,
borderRadius: 12,
display: 'flex',
alignItems: 'center',
justifyContent: 'center'
}}
/>
{/* 面包屑导航 */}
{!isMobile && (
<Breadcrumb
items={generateBreadcrumbItems()}
style={{ marginLeft: 16 }}
/>
)}
</div>
<div style={{ display: 'flex', alignItems: 'center', gap: 25 }}>
{/* 搜索框 - 修复交互问题 */}
{!isMobile && (
<Search
placeholder="搜索图片..."
allowClear
value={searchText}
onChange={handleSearchInputChange}
onSearch={handleSearch}
style={{
width: 300,
borderRadius: 100
}}
size="middle"
/>
)}
<Dropdown menu={{ items: userMenuItems }} placement="bottomRight">
<UserAvatar
size={46}
email={user?.email}
text={user?.userName}
/>
</Dropdown>
</div>
</AntHeader>
{/* 搜索对话框 - 传递搜索文本 */}
<SearchDialog
visible={searchDialogVisible}
initialSearchText={searchText}
onClose={() => {
setSearchDialogVisible(false);
// 可选:关闭对话框后清空搜索框
// setSearchText('');
}}
/>
</>
);
};
export default Header;

View File

@@ -0,0 +1,173 @@
import React from 'react';
import { Layout, Menu, type MenuProps } from 'antd';
import { useLocation, useNavigate } from 'react-router';
import routes from '../../config/routeConfig';
import logo from '/logo.png';
const { Sider } = Layout;
interface SidebarProps {
collapsed: boolean;
isMobile?: boolean;
onClose?: () => void;
}
// 定义菜单项类型
type MenuItem = Required<MenuProps>['items'][number];
const Sidebar: React.FC<SidebarProps> = ({ collapsed, isMobile = false, onClose }) => {
const location = useLocation();
const navigate = useNavigate();
// 菜单项样式
const menuItemStyle = { fontSize: 15 };
const iconStyle = { fontSize: 18 };
// 分组标题样式
const groupTitleStyle = {
fontSize: 12,
color: '#8c8c8c',
fontWeight: 500,
marginLeft: collapsed ? 0 : 16,
padding: collapsed ? '8px 0' : '8px 0'
};
// 从路由配置生成菜单项
const generateMenuItems = (): MenuItem[] => {
const items: MenuItem[] = [];
let lastGroup = '';
routes.forEach(route => {
if (route.hideInMenu) return;
// 如果有分组标签且与上一个不同,添加分组
if (route.groupLabel && route.groupLabel !== lastGroup && !collapsed) {
items.push({
type: 'group',
label: <div style={groupTitleStyle}>{route.groupLabel}</div>,
children: []
} as MenuItem);
lastGroup = route.groupLabel;
}
// 如果需要添加分隔符
if (route.divider) {
items.push({
type: 'divider'
});
}
// 添加菜单项
items.push({
key: route.path,
icon: route.icon && React.isValidElement(route.icon)
? React.cloneElement(route.icon as React.ReactElement<any>, { style: iconStyle })
: route.icon,
label: <span style={menuItemStyle}>{route.label}</span>
});
});
return items;
};
// 获取当前选中的菜单项
const getSelectedKey = () => {
const pathname = location.pathname;
const matchedRoute = routes.find(route => {
if (route.path.includes(':')) {
const basePath = route.path.split(':')[0].replace(/\/$/, '');
return pathname.startsWith('/' + basePath);
}
if (route.path === '/' && pathname === '/') {
return true;
}
return pathname === '/' + route.path;
});
return matchedRoute ? (matchedRoute.path === '/' ? '/' : matchedRoute.path) : '/';
};
const handleMenuClick = ({ key }: { key: string }) => {
navigate(key);
};
return (
<>
{/* 遮罩层 - 仅在手机模式且侧边栏展开时显示 */}
{isMobile && !collapsed && (
<div
onClick={onClose}
style={{
position: 'fixed',
top: 0,
left: 0,
right: 0,
bottom: 0,
backgroundColor: 'rgba(0,0,0,0.45)',
zIndex: 999, // 确保在Sider(1000)之下
}}
/>
)}
<Sider
trigger={null}
collapsible
collapsed={collapsed}
width={isMobile ? 180 : 250}
collapsedWidth={isMobile ? 0 : 80}
style={{
overflow: 'auto',
height: '100vh',
position: isMobile ? 'absolute' : 'relative',
left: 0,
top: 0,
bottom: 0,
zIndex: isMobile ? 1000 : 1,
boxShadow: isMobile && !collapsed ? '0 0 10px rgba(0,0,0,0.2)' : 'none',
backgroundColor: 'white',
display: 'flex',
flexDirection: 'column',
}}
>
{/* Logo区域 */}
<div style={{
height: isMobile ? '56px' : '64px',
display: 'flex',
alignItems: 'center',
justifyContent: collapsed ? 'center' : 'flex-start',
padding: collapsed ? '0' : '0 20px',
color: '#001529',
fontWeight: 'bold',
fontSize: '18px',
overflow: 'hidden',
backgroundColor: 'white',
borderBottom: '1px solid #f0f0f0'
}}>
<img
src={logo}
alt="Foxel Logo"
style={{
height: collapsed ? '30px' : '32px',
marginRight: collapsed ? '0' : '12px',
transition: 'all 0.2s'
}}
/>
{!collapsed && <span>Foxel</span>}
</div>
{/* 侧边栏菜单 */}
<Menu
theme="light"
mode="inline"
defaultSelectedKeys={[getSelectedKey()]}
items={generateMenuItems()}
onClick={handleMenuClick}
style={{
borderRight: 'none',
flex: 1
}}
/>
</Sider>
</>
);
};
export default Sidebar;