feat: Sprint 1 — 设计Token系统 + 新导航5分组 + AppShell/PageHeader/FilterBar/DataTable组件 + 花名册导出扩充19字段 + 社保/公积金不缴纳选项 + 个税申报表导出 + 入离职/绩效统计看板
This commit is contained in:
@@ -0,0 +1,54 @@
|
||||
/**
|
||||
* 应用外壳组件 — 统一页面布局结构
|
||||
* 包含 PageHeader 区域 + 内容区域,配合 SidebarNav 和 TopNav 使用
|
||||
*/
|
||||
import { ReactNode } from 'react'
|
||||
import clsx from 'clsx'
|
||||
|
||||
interface AppShellProps {
|
||||
children: ReactNode
|
||||
className?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* 应用外壳 — 限制内容最大宽度,统一内边距
|
||||
*/
|
||||
export function AppShell({ children, className }: AppShellProps) {
|
||||
return (
|
||||
<div className={clsx('w-full max-w-content mx-auto px-4 md:px-6', className)}>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
interface PageHeaderProps {
|
||||
title: string
|
||||
description?: string
|
||||
actions?: ReactNode
|
||||
status?: ReactNode
|
||||
className?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* 页面头部 — 统一标题、说明、状态和操作按钮区域
|
||||
*/
|
||||
export function PageHeader({ title, description, actions, status, className }: PageHeaderProps) {
|
||||
return (
|
||||
<div className={clsx('flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between mb-4', className)}>
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="flex items-center gap-2">
|
||||
<h1 className="text-xl font-semibold text-ink-900 truncate">{title}</h1>
|
||||
{status}
|
||||
</div>
|
||||
{description && (
|
||||
<p className="text-sm text-ink-600 mt-0.5">{description}</p>
|
||||
)}
|
||||
</div>
|
||||
{actions && (
|
||||
<div className="flex items-center gap-2 shrink-0">
|
||||
{actions}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,14 +1,13 @@
|
||||
import { Link, useLocation } from 'react-router-dom'
|
||||
import { Home, Users, Calculator, UserX, Bot, Shield } from 'lucide-react'
|
||||
import { Home, Users, Calculator, CalendarCheck, Shield } from 'lucide-react'
|
||||
import clsx from 'clsx'
|
||||
|
||||
const tabs = [
|
||||
{ path: '/', label: '总览', icon: Home },
|
||||
{ path: '/roster', label: '员工', icon: Users },
|
||||
{ path: '/money', label: '薪税', icon: Calculator },
|
||||
{ path: '/social', label: '社保', icon: Shield },
|
||||
{ path: '/termination', label: '解聘', icon: UserX },
|
||||
{ path: '/ai-assistant', label: 'AI', icon: Bot },
|
||||
{ path: '/', label: '首页', icon: Home },
|
||||
{ path: '/roster', label: '团队', icon: Users },
|
||||
{ path: '/money', label: '薪酬', icon: Calculator },
|
||||
{ path: '/attendance', label: '时间', icon: CalendarCheck },
|
||||
{ path: '/evidence', label: '合规', icon: Shield },
|
||||
]
|
||||
|
||||
export default function MobileTabBar() {
|
||||
|
||||
@@ -30,31 +30,36 @@ interface NavGroup {
|
||||
|
||||
const navGroups: NavGroup[] = [
|
||||
{
|
||||
title: '工作台',
|
||||
title: '首页',
|
||||
items: [
|
||||
{ path: '/', label: '总览', icon: LayoutDashboard },
|
||||
{ path: '/', label: '工作台', icon: LayoutDashboard },
|
||||
{ path: '/calendar', label: '工作日历', icon: CalendarDays },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '员工管理',
|
||||
title: '团队',
|
||||
items: [
|
||||
{ path: '/roster', label: '花名册', icon: Users },
|
||||
{ path: '/work-process', label: '用工办理', icon: ClipboardList },
|
||||
{ path: '/attendance', label: '考勤确认', icon: CalendarCheck },
|
||||
{ path: '/termination', label: '解聘补偿', icon: UserX },
|
||||
{ path: '/termination', label: '离职管理', icon: UserX },
|
||||
{ path: '/special-status', label: '特殊状态', icon: Heart },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '薪税社保',
|
||||
title: '薪酬',
|
||||
items: [
|
||||
{ path: '/money', label: '薪税管理', icon: Calculator },
|
||||
{ path: '/social', label: '社保公积金', icon: Shield },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '合规风控',
|
||||
title: '时间',
|
||||
items: [
|
||||
{ path: '/attendance', label: '考勤排班', icon: CalendarCheck },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '合规',
|
||||
items: [
|
||||
{ path: '/evidence', label: '证据链', icon: FileSearch },
|
||||
{ path: '/policies', label: '规章制度', icon: FileText },
|
||||
@@ -64,15 +69,10 @@ const navGroups: NavGroup[] = [
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'AI 辅助',
|
||||
title: '更多',
|
||||
items: [
|
||||
{ path: '/ai-assistant', label: 'AI 顾问', icon: Bot },
|
||||
{ path: '/templates', label: '文本模板', icon: BookMarked },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '系统',
|
||||
items: [
|
||||
{ path: '/notifications', label: '通知管理', icon: Bell },
|
||||
{ path: '/audit', label: '操作日志', icon: ScrollText },
|
||||
{ path: '/settings', label: '设置', icon: Settings },
|
||||
@@ -85,7 +85,14 @@ const navGroups: NavGroup[] = [
|
||||
*/
|
||||
export default function SidebarNav({ mobileOpen, onClose }: { mobileOpen: boolean; onClose: () => void }) {
|
||||
const location = useLocation()
|
||||
const [expandedGroups, setExpandedGroups] = useState<Set<string>>(new Set(navGroups.map(g => g.title)))
|
||||
const isActive = (path: string) => {
|
||||
if (path === '/') return location.pathname === '/'
|
||||
return location.pathname.startsWith(path)
|
||||
}
|
||||
const activeGroup = navGroups.find(g => g.items.some(item => isActive(item.path)))
|
||||
const [expandedGroups, setExpandedGroups] = useState<Set<string>>(
|
||||
new Set(activeGroup ? [activeGroup.title] : ['首页'])
|
||||
)
|
||||
|
||||
const toggleGroup = (title: string) => {
|
||||
setExpandedGroups(prev => {
|
||||
@@ -96,11 +103,6 @@ export default function SidebarNav({ mobileOpen, onClose }: { mobileOpen: boolea
|
||||
})
|
||||
}
|
||||
|
||||
const isActive = (path: string) => {
|
||||
if (path === '/') return location.pathname === '/'
|
||||
return location.pathname.startsWith(path)
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* 移动端遮罩 */}
|
||||
|
||||
Reference in New Issue
Block a user