优化: 大文件拆分+代码分割+按需加载+console清理+any类型替换
- Money.tsx (2260行→54行): 拆分为 money/ 子目录4个组件, React.lazy二级分割 - AIAssistant.tsx (2038行→63行): 拆分为 ai-assistant/ 子目录6个组件, React.lazy二级分割 - xlsx改为动态导入, OvertimeTab从345KB降至12.7KB - api-services.ts: 请求参数 any→Record<string,unknown> - 移除前端3处console.log残留 - 后端console替换为pino logger - 前后端未使用import/变量清理 - Zod schema验证: termination/platform/special-status/work-process - 新增 leave.routes.ts, acceptance-test.routes.ts - UI组件: PageGuide, QueryError, Stepper
This commit is contained in:
@@ -44,9 +44,9 @@ export default function AcceptanceTestModal({ open, onClose }: { open: boolean;
|
||||
<X className="w-4 h-4" />
|
||||
</button>
|
||||
</div>
|
||||
{/* iframe 加载验收测试页面 */}
|
||||
{/* iframe 加载验收测试页面 — 带版本参数防止缓存 */}
|
||||
<iframe
|
||||
src="/acceptance-test.html"
|
||||
src={`/acceptance-test.html?v=${__APP_VERSION__}`}
|
||||
className="w-full"
|
||||
style={{ height: 'calc(95vh - 42px)', border: 'none' }}
|
||||
title="验收测试清单"
|
||||
|
||||
@@ -1,59 +0,0 @@
|
||||
import { Component, ErrorInfo, ReactNode } from 'react'
|
||||
|
||||
interface Props {
|
||||
children: ReactNode
|
||||
}
|
||||
|
||||
interface State {
|
||||
hasError: boolean
|
||||
error: Error | null
|
||||
}
|
||||
|
||||
export default class ErrorBoundary extends Component<Props, State> {
|
||||
constructor(props: Props) {
|
||||
super(props)
|
||||
this.state = { hasError: false, error: null }
|
||||
}
|
||||
|
||||
static getDerivedStateFromError(error: Error): State {
|
||||
return { hasError: true, error }
|
||||
}
|
||||
|
||||
componentDidCatch(error: Error, errorInfo: ErrorInfo) {
|
||||
console.error('ErrorBoundary caught:', error, errorInfo)
|
||||
}
|
||||
|
||||
handleReset = () => {
|
||||
this.setState({ hasError: false, error: null })
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.state.hasError) {
|
||||
return (
|
||||
<div className="min-h-[60vh] flex flex-col items-center justify-center gap-4 px-4">
|
||||
<div className="text-6xl">😵</div>
|
||||
<h2 className="text-xl font-semibold text-gray-800">页面出错了</h2>
|
||||
<p className="text-sm text-gray-500 text-center max-w-md">
|
||||
{this.state.error?.message || '发生了未知错误,请刷新页面重试'}
|
||||
</p>
|
||||
<div className="flex gap-3">
|
||||
<button
|
||||
onClick={this.handleReset}
|
||||
className="px-4 py-2 text-sm font-medium text-gray-600 bg-gray-100 rounded-lg hover:bg-gray-200 transition-colors"
|
||||
>
|
||||
重试
|
||||
</button>
|
||||
<button
|
||||
onClick={() => window.location.reload()}
|
||||
className="px-4 py-2 text-sm font-medium text-white bg-blue-600 rounded-lg hover:bg-blue-700 transition-colors"
|
||||
>
|
||||
刷新页面
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return this.props.children
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useState, useEffect, useRef } from 'react'
|
||||
import { HelpCircle, Search, ChevronDown, ChevronRight, Sparkles,
|
||||
Home, Users, FileText, Calculator, Shield, UserX, Bot,
|
||||
Bell, Settings, Lightbulb, AlertTriangle, CheckCircle, Phone } from 'lucide-react'
|
||||
Home, Users, FileText, Calculator, Bot,
|
||||
Settings, Lightbulb, AlertTriangle, CheckCircle, Phone } from 'lucide-react'
|
||||
import Modal from './ui/Modal'
|
||||
import { aiApi } from '../lib/api-services'
|
||||
import clsx from 'clsx'
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useState, useMemo, useEffect, useCallback } from 'react'
|
||||
import { Star, ChevronDown, ChevronRight, Check, Search, ClipboardList, Send, Save, RotateCcw } from 'lucide-react'
|
||||
import { Star, ChevronDown, ChevronRight, Search, ClipboardList, Send, Save, RotateCcw } from 'lucide-react'
|
||||
import Modal from './ui/Modal'
|
||||
import { toast } from 'sonner'
|
||||
import { surveyApi } from '../lib/api-services'
|
||||
|
||||
@@ -17,6 +17,7 @@ const ROUTE_MAP: Record<string, BreadcrumbItem> = {
|
||||
'/roster': { group: '员工管理', label: '花名册' },
|
||||
'/work-process': { group: '员工管理', label: '用工办理' },
|
||||
'/attendance': { group: '员工管理', label: '考勤确认' },
|
||||
'/leave-approval': { group: '员工管理', label: '休假审批' },
|
||||
'/termination': { group: '员工管理', label: '解聘补偿' },
|
||||
'/special-status': { group: '员工管理', label: '特殊状态' },
|
||||
'/money': { group: '薪税社保', label: '薪税管理' },
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*/
|
||||
|
||||
import { Link, useLocation, useNavigate } from 'react-router-dom'
|
||||
import { DollarSign, FileText, ScrollText, LogOut, CalendarCheck, Home, UserX, ClipboardList } from 'lucide-react'
|
||||
import { DollarSign, FileText, ScrollText, LogOut, CalendarCheck, Home } from 'lucide-react'
|
||||
import Logo from '../../components/ui/Logo'
|
||||
|
||||
const tabItems = [
|
||||
|
||||
@@ -13,7 +13,7 @@ import {
|
||||
Bot, BookMarked,
|
||||
Bell, ScrollText, Settings,
|
||||
ChevronDown, ChevronRight,
|
||||
Building2, CalendarDays, ClipboardList, Heart,
|
||||
Building2, CalendarDays, ClipboardList, Heart, CalendarClock,
|
||||
} from 'lucide-react'
|
||||
import Logo from '../ui/Logo'
|
||||
|
||||
@@ -33,7 +33,7 @@ const navGroups: NavGroup[] = [
|
||||
title: '首页',
|
||||
items: [
|
||||
{ path: '/', label: '工作台', icon: LayoutDashboard },
|
||||
{ path: '/calendar', label: '工作日历', icon: CalendarDays },
|
||||
{ path: '/calendar', label: '日历', icon: CalendarDays },
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -57,6 +57,7 @@ const navGroups: NavGroup[] = [
|
||||
title: '时间',
|
||||
items: [
|
||||
{ path: '/attendance', label: '考勤排班', icon: CalendarCheck },
|
||||
{ path: '/leave-approval', label: '休假审批', icon: CalendarClock },
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -66,14 +67,14 @@ const navGroups: NavGroup[] = [
|
||||
{ path: '/evidence', label: '证据链', icon: FileSearch },
|
||||
{ path: '/policies', label: '规章制度', icon: FileText },
|
||||
{ path: '/tools/health-check', label: '用工体检', icon: Stethoscope },
|
||||
{ path: '/tools/medical-period', label: '医疗期计算', icon: HeartPulse },
|
||||
{ path: '/tools/medical-period', label: '医疗期', icon: HeartPulse },
|
||||
{ path: '/tools/annual-value', label: '年度价值', icon: Award },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '更多',
|
||||
items: [
|
||||
{ path: '/ai-assistant', label: 'AI 顾问', icon: Bot },
|
||||
{ path: '/ai-assistant', label: 'AI顾问', icon: Bot },
|
||||
{ path: '/templates', label: '文本模板', icon: BookMarked },
|
||||
{ path: '/notifications', label: '通知管理', icon: Bell },
|
||||
{ path: '/audit', label: '操作日志', icon: ScrollText },
|
||||
|
||||
@@ -24,6 +24,7 @@ const QUICK_PAGES: SearchResult[] = [
|
||||
{ type: 'page', id: 'social', title: '社保公积金', link: '/social', icon: 'shield' },
|
||||
{ type: 'page', id: 'termination', title: '离职管理', link: '/termination', icon: 'userX' },
|
||||
{ type: 'page', id: 'attendance', title: '考勤排班', link: '/attendance', icon: 'calendar' },
|
||||
{ type: 'page', id: 'leave-approval', title: '休假审批', link: '/leave-approval', icon: 'calendar' },
|
||||
{ type: 'page', id: 'risk-center', title: '风险中心', link: '/risk-center', icon: 'alert' },
|
||||
{ type: 'page', id: 'salary-dashboard', title: '薪酬分析', link: '/salary-dashboard', icon: 'chart' },
|
||||
{ type: 'page', id: 'policies', title: '规章制度', link: '/policies', icon: 'file' },
|
||||
|
||||
@@ -23,8 +23,8 @@ export default class ErrorBoundary extends Component<Props, State> {
|
||||
return { hasError: true, error }
|
||||
}
|
||||
|
||||
componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
|
||||
console.error('ErrorBoundary caught:', error, errorInfo)
|
||||
componentDidCatch(_error: Error, _errorInfo: React.ErrorInfo) {
|
||||
// 错误已通过 getDerivedStateFromError 捕获并展示降级 UI
|
||||
}
|
||||
|
||||
handleReset = () => {
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
/**
|
||||
* PageGuide 页面操作指导组件 — 折叠式,默认收起
|
||||
* 统一用于各页面/Tab的操作说明,点击展开查看
|
||||
*/
|
||||
import { useState, ReactNode } from 'react'
|
||||
import { ChevronRight, Lightbulb } from 'lucide-react'
|
||||
|
||||
interface PageGuideProps {
|
||||
/** 指导标题,默认"操作说明" */
|
||||
title?: string
|
||||
/** 指导内容,支持字符串或自定义JSX */
|
||||
children: ReactNode
|
||||
/** 额外类名 */
|
||||
className?: string
|
||||
}
|
||||
|
||||
export default function PageGuide({ title = '操作说明', children, className = '' }: PageGuideProps) {
|
||||
const [open, setOpen] = useState(false)
|
||||
|
||||
return (
|
||||
<div className={`rounded-md border border-blue-100 bg-blue-50/50 ${className}`}>
|
||||
<button
|
||||
onClick={() => setOpen(!open)}
|
||||
className="flex items-center gap-1.5 w-full px-3 py-1.5 text-xs font-medium text-blue-600 hover:text-blue-700 transition-colors"
|
||||
>
|
||||
<ChevronRight className={`w-3 h-3 transition-transform ${open ? 'rotate-90' : ''}`} />
|
||||
<Lightbulb className="w-3.5 h-3.5" />
|
||||
{title}
|
||||
</button>
|
||||
{open && (
|
||||
<div className="px-3 pb-2.5 text-xs text-blue-600/80 leading-relaxed">
|
||||
{children}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
import { AlertCircle, RefreshCw } from 'lucide-react'
|
||||
|
||||
interface QueryErrorProps {
|
||||
error?: unknown
|
||||
onRetry?: () => void
|
||||
message?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询错误状态组件
|
||||
* 在 useQuery 的 isError 状态下显示错误提示和重试按钮
|
||||
*/
|
||||
export default function QueryError({ error, onRetry, message }: QueryErrorProps) {
|
||||
const errMsg = message
|
||||
|| (error as any)?.response?.data?.error?.message
|
||||
|| (error as any)?.message
|
||||
|| '数据加载失败,请稍后重试'
|
||||
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center py-12 px-4 text-center">
|
||||
<div className="w-12 h-12 rounded-full bg-red-50 flex items-center justify-center mb-3">
|
||||
<AlertCircle className="w-6 h-6 text-red-500" />
|
||||
</div>
|
||||
<p className="text-sm text-gray-600 mb-3 max-w-md">{errMsg}</p>
|
||||
{onRetry && (
|
||||
<button
|
||||
onClick={onRetry}
|
||||
className="inline-flex items-center gap-1.5 px-3 py-1.5 text-sm font-medium text-primary bg-primary/5 rounded-lg hover:bg-primary/10 transition-colors"
|
||||
>
|
||||
<RefreshCw className="w-3.5 h-3.5" />
|
||||
重试
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -2,7 +2,6 @@
|
||||
* Stepper 步骤条组件 — 用于多步骤工作流引导
|
||||
* 支持横向/纵向布局、可点击步骤导航、完成/当前/待办状态
|
||||
*/
|
||||
import { ReactNode } from 'react'
|
||||
import clsx from 'clsx'
|
||||
import { Check } from 'lucide-react'
|
||||
|
||||
@@ -24,7 +23,6 @@ interface StepperProps {
|
||||
* Stepper 步骤条 — 展示工作流进度和步骤导航
|
||||
*/
|
||||
export function Stepper({ steps, orientation = 'horizontal', onStepClick, className }: StepperProps) {
|
||||
const currentIndex = steps.findIndex(s => s.status === 'current')
|
||||
|
||||
if (orientation === 'vertical') {
|
||||
return (
|
||||
|
||||
Reference in New Issue
Block a user