feat: 全部列表页统一分页 + WorkProcess集成现有服务

- WorkProcess/Termination/Evidence/Contracts/Policies/Templates 添加 Pagination 组件
- 后端 getDrafts/getPolicies/enterprise-template 路由添加分页支持(可选参数,向后兼容)
- work-process.service CONFIRM case 修正:移除不存在的 probationEndDate 字段
- 新增 migration_add_three_tables.sql 增量迁移文件
This commit is contained in:
freedakgmail
2026-07-30 10:39:21 +08:00
parent cea7eb07c4
commit 5a5ce7186b
13 changed files with 221 additions and 63 deletions
+11 -19
View File
@@ -8,6 +8,7 @@ import Button from '../components/ui/Button'
import { Input, Label, Select } from '../components/ui/Input'
import Modal from '../components/ui/Modal'
import EmptyState from '../components/ui/EmptyState'
import Pagination from '../components/ui/Pagination'
interface EmployeeItem {
id: string
@@ -37,13 +38,14 @@ export default function Contracts() {
const [filterDepartment, setFilterDepartment] = useState('')
const [filterContractStatus, setFilterContractStatus] = useState('')
const [page, setPage] = useState(1)
const [pageSize, setPageSize] = useState(20)
const [showAddModal, setShowAddModal] = useState(false)
const [selectedEmpId, setSelectedEmpId] = useState<string | null>(null)
const { data, isLoading } = useQuery<EmployeeListResponse>({
queryKey: ['employees', search, filterDepartment, filterContractStatus, page],
queryKey: ['employees', search, filterDepartment, filterContractStatus, page, pageSize],
queryFn: async () => {
const params: any = { search, page, pageSize: 20 }
const params: any = { search, page, pageSize }
if (filterDepartment) params.department = filterDepartment
if (filterContractStatus) params.contractStatus = filterContractStatus
const res = await api.get('/roster', { params }) as any
@@ -181,23 +183,13 @@ export default function Contracts() {
</div>
{/* 分页 */}
{data.totalPages > 1 && (
<div className="flex items-center justify-center gap-2 mt-4">
<Button
variant="secondary"
size="sm"
disabled={page === 1}
onClick={() => setPage(p => p - 1)}
></Button>
<span className="text-xs text-gray-500">{page} / {data.totalPages}</span>
<Button
variant="secondary"
size="sm"
disabled={page === data.totalPages}
onClick={() => setPage(p => p + 1)}
></Button>
</div>
)}
<Pagination
page={page}
pageSize={pageSize}
total={data.total}
onPageChange={setPage}
onPageSizeChange={(s) => { setPageSize(s); setPage(1) }}
/>
</>
)}
</Card>
+19 -6
View File
@@ -4,21 +4,27 @@ import { ShieldCheck, FileText, AlertCircle, CheckCircle, XCircle } from 'lucide
import api from '../lib/api'
import Card from '../components/ui/Card'
import EmptyState from '../components/ui/EmptyState'
import Pagination from '../components/ui/Pagination'
/**
* 证据链管理页面
*/
export default function Evidence() {
const [refType, setRefType] = useState<string>('')
const [page, setPage] = useState(1)
const [pageSize, setPageSize] = useState(20)
const { data: list, isLoading } = useQuery<any>({
queryKey: ['evidence', refType],
const { data: listData, isLoading } = useQuery<any>({
queryKey: ['evidence', refType, page, pageSize],
queryFn: async () => {
const params = refType ? `?category=${refType}` : '?category=ALL'
const res = await api.get(`/evidence${params}`) as any
return res.data?.records || []
const params: any = { page, pageSize }
params.category = refType || 'ALL'
const res = await api.get('/evidence', { params }) as any
return res.data
},
})
const list = listData?.records || []
const total = listData?.total || 0
const { data: verifyResult, refetch: verifyAll } = useQuery<any>({
queryKey: ['evidence-verify-all'],
@@ -68,7 +74,7 @@ export default function Evidence() {
{['', 'ONBOARD', 'CONTRACT_SIGN', 'PAYSLIP_CONFIRM', 'DISCIPLINARY', 'ATTENDANCE', 'TERMINATION'].map((t) => (
<button
key={t}
onClick={() => setRefType(t)}
onClick={() => { setRefType(t); setPage(1) }}
className={`px-3 py-1 text-xs rounded-lg ${refType === t ? 'bg-primary text-white' : 'bg-gray-100 text-gray-600 hover:bg-gray-200'}`}
>
{t === '' ? '全部' : t === 'DISCIPLINARY' ? '违纪' : t === 'TERMINATION' ? '解聘' : t === 'CONTRACT_SIGN' ? '合同' : t === 'PAYSLIP_CONFIRM' ? '薪酬' : t === 'ONBOARD' ? '入职' : '考勤'}
@@ -112,6 +118,13 @@ export default function Evidence() {
))}
</div>
)}
<Pagination
page={page}
pageSize={pageSize}
total={total}
onPageChange={setPage}
onPageSizeChange={(s) => { setPageSize(s); setPage(1) }}
/>
</div>
)
}
+15 -3
View File
@@ -6,6 +6,7 @@ import api from '../lib/api'
import Card from '../components/ui/Card'
import Button from '../components/ui/Button'
import EmptyState from '../components/ui/EmptyState'
import Pagination from '../components/ui/Pagination'
const STEP_LABELS: Record<string, string> = {
DRAFTING: '起草',
@@ -23,14 +24,18 @@ export default function Policies() {
const queryClient = useQueryClient()
const [showCreate, setShowCreate] = useState(false)
const [selectedPolicy, setSelectedPolicy] = useState<any>(null)
const [page, setPage] = useState(1)
const [pageSize, setPageSize] = useState(20)
const { data: list, isLoading } = useQuery<any>({
queryKey: ['policies'],
const { data: listData, isLoading } = useQuery<any>({
queryKey: ['policies', page, pageSize],
queryFn: async () => {
const res = await api.get('/policies') as any
const res = await api.get('/policies', { params: { page, pageSize } }) as any
return res.data
},
})
const list = listData?.items || []
const total = listData?.total || 0
const advanceMutation = useMutation({
mutationFn: ({ id, step, note }: { id: string; step: number; note?: string }) => api.post(`/policies/${id}/advance-step`, { step, note }),
@@ -114,6 +119,13 @@ export default function Policies() {
))}
</div>
)}
<Pagination
page={page}
pageSize={pageSize}
total={total}
onPageChange={setPage}
onPageSizeChange={(s) => { setPageSize(s); setPage(1) }}
/>
{/* 详情弹窗 */}
{selectedPolicy && (
+17 -4
View File
@@ -9,6 +9,7 @@ import Button from '../components/ui/Button'
import { Input, Label, Select } from '../components/ui/Input'
import Modal from '../components/ui/Modal'
import EmptyState from '../components/ui/EmptyState'
import Pagination from '../components/ui/Pagination'
const CATEGORY_LABELS: Record<string, string> = {
CONTRACT: '合同',
@@ -303,6 +304,8 @@ function SystemTemplates() {
function EnterpriseTemplates() {
const queryClient = useQueryClient()
const [category, setCategory] = useState<string>('')
const [page, setPage] = useState(1)
const [pageSize, setPageSize] = useState(20)
const [showEdit, setShowEdit] = useState(false)
const [editItem, setEditItem] = useState<any>(null)
const [form, setForm] = useState({ name: '', category: 'CONTRACT', description: '', content: '' })
@@ -310,14 +313,17 @@ function EnterpriseTemplates() {
const [rendered, setRendered] = useState('')
const [variables, setVariables] = useState<Record<string, string>>({})
const { data: list, isLoading } = useQuery<any>({
queryKey: ['enterprise-templates', category],
const { data: listData, isLoading } = useQuery<any>({
queryKey: ['enterprise-templates', category, page, pageSize],
queryFn: async () => {
const params = category ? `?category=${category}` : ''
const res = await api.get(`/enterprise-templates${params}`) as any
const params: any = { page, pageSize }
if (category) params.category = category
const res = await api.get('/enterprise-templates', { params }) as any
return res.data
},
})
const list = listData?.items || []
const total = listData?.total || 0
const { data: detail } = useQuery<any>({
queryKey: ['enterprise-template-detail', selected?.id],
@@ -459,6 +465,13 @@ function EnterpriseTemplates() {
))}
</div>
)}
<Pagination
page={page}
pageSize={pageSize}
total={total}
onPageChange={setPage}
onPageSizeChange={(s) => { setPageSize(s); setPage(1) }}
/>
{/* 编辑弹窗 */}
<Modal open={showEdit} onClose={() => setShowEdit(false)} title={editItem ? '编辑模板' : '新建模板'} size="lg">
+15 -3
View File
@@ -9,6 +9,7 @@ import Card from '../components/ui/Card'
import Button from '../components/ui/Button'
import { Input, Label, Select } from '../components/ui/Input'
import EmptyState from '../components/ui/EmptyState'
import Pagination from '../components/ui/Pagination'
// 金额格式化:保留两位小数 + 千分位
const fmt = (n: number) => (n || 0).toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })
@@ -119,6 +120,8 @@ export default function Termination() {
const [filterStatus, setFilterStatus] = useState('')
const [filterDepartment, setFilterDepartment] = useState('')
const [searchTerm, setSearchTerm] = useState('')
const [draftPage, setDraftPage] = useState(1)
const [draftPageSize, setDraftPageSize] = useState(20)
const { data: employees } = useQuery<RosterEmployee[]>({
queryKey: ['roster-for-termination'],
@@ -250,10 +253,10 @@ export default function Termination() {
})
// 草稿列表
const { data: drafts, refetch: refetchDrafts } = useQuery({
queryKey: ['termination-drafts', filterStatus, filterDepartment, searchTerm],
const { data: draftsData, refetch: refetchDrafts } = useQuery({
queryKey: ['termination-drafts', filterStatus, filterDepartment, searchTerm, draftPage, draftPageSize],
queryFn: async () => {
const params: any = {}
const params: any = { page: draftPage, pageSize: draftPageSize }
if (filterStatus) params.status = filterStatus
if (filterDepartment) params.department = filterDepartment
if (searchTerm) params.search = searchTerm
@@ -262,6 +265,8 @@ export default function Termination() {
},
enabled: view === 'list',
})
const drafts = draftsData?.items || []
const draftsTotal = draftsData?.total || 0
// 草稿详情
const { data: draftDetail } = useQuery({
@@ -823,6 +828,13 @@ export default function Termination() {
</table>
</div>
)}
<Pagination
page={draftPage}
pageSize={draftPageSize}
total={draftsTotal}
onPageChange={setDraftPage}
onPageSizeChange={(s) => { setDraftPageSize(s); setDraftPage(1) }}
/>
</Card>
</>
)}
+13 -2
View File
@@ -11,6 +11,7 @@ import Card from '../components/ui/Card'
import Button from '../components/ui/Button'
import { Input, Label, Select } from '../components/ui/Input'
import Modal from '../components/ui/Modal'
import Pagination from '../components/ui/Pagination'
const PROCESS_ICONS: Record<string, any> = {
HIRE: UserPlus, ONBOARD: LogIn, CUSTOM_CONTRACT: FileSignature,
@@ -145,11 +146,13 @@ export default function WorkProcess() {
const [filterStatus, setFilterStatus] = useState('')
const [detailId, setDetailId] = useState<string | null>(null)
const [previewContent, setPreviewContent] = useState<string | null>(null)
const [page, setPage] = useState(1)
const [pageSize, setPageSize] = useState(20)
const { data: listData, isLoading } = useQuery({
queryKey: ['work-processes', filterType, filterStatus],
queryKey: ['work-processes', filterType, filterStatus, page, pageSize],
queryFn: async () => {
const params: any = {}
const params: any = { page, pageSize }
if (filterType) params.type = filterType
if (filterStatus) params.status = filterStatus
const res = await api.get('/work-processes', { params }) as any
@@ -238,6 +241,7 @@ export default function WorkProcess() {
}
const items = listData?.items || []
const total = listData?.total || 0
return (
<div className="space-y-4">
@@ -325,6 +329,13 @@ export default function WorkProcess() {
})}
</div>
)}
<Pagination
page={page}
pageSize={pageSize}
total={total}
onPageChange={setPage}
onPageSizeChange={(s) => { setPageSize(s); setPage(1) }}
/>
</Card>
{/* 创建/编辑弹窗 */}