feat: 完成23项系统优化 - 花名册社保状态列/身份证复制/附件类型扩展, 用工办理姓名检索/直接提交/文书查看/批量证明, 风险中心跳转筛选+批量处理, 日历7/15/35天分组+逾期统计, 考勤单条编辑+按人导出, 工资条查看状态+工资流水导出, 辞职申请附件上传, 交接清单PDF下载, 操作完成下一步引导, 休假审批入口, 人效成本分部门

This commit is contained in:
freedakgmail
2026-08-04 22:55:03 +08:00
parent 338af5eee9
commit 5604d02de9
41 changed files with 2104 additions and 482 deletions
+36 -51
View File
@@ -1,9 +1,10 @@
import { useState, useEffect } from 'react'
import { toast } from 'sonner'
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
import { Building2, Users, CreditCard, Plus, Bell, Download, Upload, FileSpreadsheet, Clock, CheckCircle, AlertCircle, ClipboardList } from 'lucide-react'
import { Building2, Users, CreditCard, Plus, Bell, Download, Upload, FileSpreadsheet, Clock, CheckCircle, AlertCircle, ClipboardList, LayoutGrid } from 'lucide-react'
import { settingsApi, notificationsApi } from '../lib/api-services'
import { useAuthStore } from '../store/authStore'
import { getPageSize, setPageSize as setGlobalPageSize } from '../lib/pageSize'
import Card from '../components/ui/Card'
import Button from '../components/ui/Button'
import { Input, Label, Select } from '../components/ui/Input'
@@ -13,7 +14,7 @@ import { useConfirm } from '../hooks/useConfirm'
export default function Settings() {
const queryClient = useQueryClient()
const [activeSection, setActiveSection] = useState<'org' | 'users' | 'plan' | 'notifications' | 'import' | 'export'>('org')
const [activeSection, setActiveSection] = useState<'org' | 'users' | 'plan' | 'notifications' | 'import' | 'export' | 'display'>('org')
const { data: orgData } = useQuery<any>({
queryKey: ['org-settings'],
@@ -41,6 +42,7 @@ export default function Settings() {
{ key: 'notifications' as const, label: '通知设置', icon: Bell },
{ key: 'import' as const, label: '数据导入', icon: FileSpreadsheet },
{ key: 'export' as const, label: '数据导出', icon: Download },
{ key: 'display' as const, label: '显示设置', icon: LayoutGrid },
]
return (
@@ -78,12 +80,8 @@ export default function Settings() {
{activeSection === 'plan' && <PlanSettings orgData={orgData} />}
{activeSection === 'notifications' && <NotificationSettings />}
{activeSection === 'import' && <ImportSettings />}
{activeSection === 'export' && (
<Card>
<h2 className="text-sm font-medium mb-4"></h2>
<ExportSettings />
</Card>
)}
{activeSection === 'export' && <ExportSettings />}
{activeSection === 'display' && <DisplaySettings />}
</div>
)
}
@@ -705,7 +703,6 @@ function PlanSettings({ orgData }: { orgData: any }) {
function NotificationSettings() {
const queryClient = useQueryClient()
const [form, setForm] = useState<any>({})
const [checkResult, setCheckResult] = useState<string>('')
const { data: setting } = useQuery<any>({
queryKey: ['notification-settings'],
@@ -714,13 +711,6 @@ function NotificationSettings() {
},
})
const { data: logsData } = useQuery<any>({
queryKey: ['notification-logs'],
queryFn: async () => {
return await notificationsApi.logs({ pageSize: 10 })
},
})
useEffect(() => {
if (setting) setForm(setting)
}, [setting])
@@ -730,14 +720,6 @@ function NotificationSettings() {
onSuccess: () => queryClient.invalidateQueries({ queryKey: ['notification-settings'] }),
})
const checkMutation = useMutation({
mutationFn: () => notificationsApi.checkContracts() as any,
onSuccess: (res: any) => {
setCheckResult(`检查完成:发现 ${res.data.checked} 个即将到期的合同,已发送 ${res.data.notified} 条通知`)
queryClient.invalidateQueries({ queryKey: ['notification-logs'] })
},
})
const testWechatMutation = useMutation({
mutationFn: () => notificationsApi.test('wechat') as any,
onSuccess: (res: any) => {
@@ -752,8 +734,6 @@ function NotificationSettings() {
},
})
const logs = logsData?.items || []
return (
<div className="space-y-3">
<Card>
@@ -830,31 +810,6 @@ function NotificationSettings() {
</Button>
</div>
</Card>
<Card>
<div className="flex items-center justify-between mb-4">
<h2 className="text-sm font-medium"></h2>
<Button size="sm" onClick={() => checkMutation.mutate()} disabled={checkMutation.isPending}>
{checkMutation.isPending ? '检查中...' : '立即检查'}
</Button>
</div>
{checkResult && (
<div className="px-3 py-2 rounded-md bg-blue-50 text-blue-700 text-xs mb-3">{checkResult}</div>
)}
{logs.length > 0 ? (
<div className="space-y-2">
{logs.map((log: any) => (
<div key={log.id} className="text-xs border-b last:border-0 py-2">
<div className="font-medium">{log.title}</div>
<div className="text-gray-500 text-xs mt-0.5">{log.content}</div>
<div className="text-gray-500 text-xs mt-0.5">{new Date(log.createdAt).toLocaleString('zh-CN')}</div>
</div>
))}
</div>
) : (
<div className="text-gray-500 text-xs text-center py-4"></div>
)}
</Card>
</div>
)
}
@@ -1335,3 +1290,33 @@ function MonthlyImport() {
</div>
)
}
function DisplaySettings() {
const [size, setSize] = useState(getPageSize())
const handleSave = () => {
setGlobalPageSize(size)
toast.success(`分页大小已设置为 ${size} 条/页`)
}
return (
<Card>
<h2 className="text-sm font-medium mb-4"></h2>
<div className="space-y-4">
<div>
<Label></Label>
<p className="text-xs text-gray-500 mt-1 mb-2"></p>
<div className="flex items-center gap-3">
<Select value={String(size)} onChange={(e) => setSize(parseInt(e.target.value, 10))} className="!w-32">
<option value="10">10 /</option>
<option value="20">20 /</option>
<option value="50">50 /</option>
<option value="100">100 /</option>
</Select>
<Button size="sm" onClick={handleSave}></Button>
</div>
</div>
</div>
</Card>
)
}