chore: add frontend/src/components/layout/BottomTab.tsx
This commit is contained in:
@@ -0,0 +1,119 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { NavLink, useLocation } from 'react-router-dom';
|
||||||
|
|
||||||
|
const BottomTab: React.FC = () => {
|
||||||
|
const location = useLocation();
|
||||||
|
|
||||||
|
const navItems = [
|
||||||
|
{ path: '/', label: '首页', icon: HomeIcon },
|
||||||
|
{ path: '/record', label: '记账', icon: RecordIcon },
|
||||||
|
{ path: '/budget', label: '预算', icon: BudgetIcon },
|
||||||
|
{ path: '/statistics', label: '统计', icon: StatisticsIcon },
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<nav className="bottom-tab-bar" role="navigation" aria-label="底部导航">
|
||||||
|
{navItems.map((item) => (
|
||||||
|
<NavLink
|
||||||
|
key={item.path}
|
||||||
|
to={item.path}
|
||||||
|
className={({ isActive }) =>
|
||||||
|
`tab-item ${isActive ? 'active' : ''}`
|
||||||
|
}
|
||||||
|
aria-current={location.pathname === item.path ? 'page' : undefined}
|
||||||
|
>
|
||||||
|
<item.icon />
|
||||||
|
<span>{item.label}</span>
|
||||||
|
</NavLink>
|
||||||
|
))}
|
||||||
|
|
||||||
|
<style>{`
|
||||||
|
:root {
|
||||||
|
--primary: #0052ff;
|
||||||
|
--surface: #ffffff;
|
||||||
|
--border: #e5e5e5;
|
||||||
|
--text-secondary: #737373;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bottom-tab-bar {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
height: 64px;
|
||||||
|
background: var(--surface);
|
||||||
|
border-top: 1px solid var(--border);
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-around;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0;
|
||||||
|
z-index: 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-item {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 2px;
|
||||||
|
flex: 1;
|
||||||
|
height: 100%;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
text-decoration: none;
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 500;
|
||||||
|
transition: color 0.15s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-item.active {
|
||||||
|
color: var(--primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
.bottom-tab-bar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`}</style>
|
||||||
|
</nav>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
function HomeIcon() {
|
||||||
|
return (
|
||||||
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" width="24" height="24">
|
||||||
|
<path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/>
|
||||||
|
<polyline points="9 22 9 12 15 12 15 22"/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function RecordIcon() {
|
||||||
|
return (
|
||||||
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" width="24" height="24">
|
||||||
|
<path d="M12 2v20M17 5H9.5a3.5 3.5 0 0 0 0 7h5a3.5 3.5 0 0 1 0 7H6"/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function BudgetIcon() {
|
||||||
|
return (
|
||||||
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" width="24" height="24">
|
||||||
|
<rect x="2" y="3" width="20" height="14" rx="2" ry="2"/>
|
||||||
|
<line x1="8" y1="21" x2="16" y2="21"/>
|
||||||
|
<line x1="12" y1="17" x2="12" y2="21"/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function StatisticsIcon() {
|
||||||
|
return (
|
||||||
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" width="24" height="24">
|
||||||
|
<line x1="18" y1="20" x2="18" y2="10"/>
|
||||||
|
<line x1="12" y1="20" x2="12" y2="4"/>
|
||||||
|
<line x1="6" y1="20" x2="6" y2="14"/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default BottomTab;
|
||||||
Reference in New Issue
Block a user