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
+58 -4
View File
@@ -1,10 +1,10 @@
/**
* 员工离职申请入口 — 员工端提交离职申请、查看申请状态、撤回申请
*/
import { useState } from 'react'
import { useState, useRef } from 'react'
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
import { toast } from 'sonner'
import { UserX, Clock, FileText } from 'lucide-react'
import { UserX, Clock, FileText, Camera, X, Image as ImageIcon } from 'lucide-react'
import { portalApi } from '../../lib/api-services'
import Card from '../../components/ui/Card'
import Button from '../../components/ui/Button'
@@ -38,6 +38,8 @@ export default function ResignationApply() {
expectedDate: '',
remark: '',
})
const [attachments, setAttachments] = useState<string[]>([])
const fileInputRef = useRef<HTMLInputElement>(null)
/** 查询离职申请状态 */
const { data: records = [], isLoading } = useQuery<any[]>({
@@ -49,13 +51,14 @@ export default function ResignationApply() {
/** 提交离职申请 */
const submitMutation = useMutation({
mutationFn: async (data: { reason: string; expectedDate: string; remark: string }) => {
mutationFn: async (data: { reason: string; expectedDate: string; remark: string; attachments?: string[] }) => {
return await portalApi.resignationSubmit(data)
},
onSuccess: () => {
toast.success('离职申请已提交,请等待HR审批')
queryClient.invalidateQueries({ queryKey: ['portal-resignation-status'] })
setForm({ reason: '', expectedDate: '', remark: '' })
setAttachments([])
},
onError: (err: any) => {
toast.error(err?.response?.data?.error?.message || '提交失败')
@@ -79,7 +82,24 @@ export default function ResignationApply() {
const handleSubmit = () => {
if (!form.reason) { toast.error('请选择离职原因'); return }
if (!form.expectedDate) { toast.error('请选择预计离职日期'); return }
submitMutation.mutate(form)
submitMutation.mutate({ ...form, attachments })
}
const handleFileUpload = (e: React.ChangeEvent<HTMLInputElement>) => {
const files = e.target.files
if (!files) return
Array.from(files).forEach(file => {
if (file.size > 5 * 1024 * 1024) {
toast.error(`${file.name} 超过5MB限制`)
return
}
const reader = new FileReader()
reader.onload = () => {
setAttachments(prev => [...prev, reader.result as string])
}
reader.readAsDataURL(file)
})
if (fileInputRef.current) fileInputRef.current.value = ''
}
const hasPending = records.some((r: any) => r.status === 'DRAFT' || r.status === 'PENDING_APPROVAL')
@@ -125,6 +145,40 @@ export default function ResignationApply() {
placeholder="补充说明(选填)"
/>
</div>
<div>
<Label>5</Label>
<input
ref={fileInputRef}
type="file"
accept="image/*"
multiple
className="hidden"
onChange={handleFileUpload}
/>
<div className="flex items-center gap-2 flex-wrap">
<button
type="button"
onClick={() => fileInputRef.current?.click()}
className="flex items-center gap-1 px-3 py-2 rounded-md border border-dashed border-gray-300 text-sm text-gray-500 hover:border-primary hover:text-primary transition-colors"
>
<Camera className="w-4 h-4" />
</button>
{attachments.map((img, i) => (
<div key={i} className="relative w-16 h-16 rounded-md overflow-hidden border">
<img src={img} alt={`附件${i + 1}`} className="w-full h-full object-cover" />
<button
type="button"
onClick={() => setAttachments(prev => prev.filter((_, idx) => idx !== i))}
className="absolute top-0 right-0 bg-black/50 text-white rounded-bl p-0.5"
>
<X className="w-3 h-3" />
</button>
</div>
))}
</div>
<div className="text-xs text-gray-400 mt-1">HR审批时可查看</div>
</div>
<Button onClick={handleSubmit} disabled={submitMutation.isPending} className="w-full">
{submitMutation.isPending ? '提交中...' : '提交离职申请'}
</Button>