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
@@ -0,0 +1,84 @@
-- 增量迁移:添加 WorkProcess、EnterpriseTemplate、AttendancePublish 三张表
-- 不影响现有数据,仅 CREATE TABLE IF NOT EXISTS
-- 执行方式: PGPASSWORD=turbohr2026 psql -U turbohr -d turbohr -f migration_add_three_tables.sql
-- 1. WorkProcess 用工办理工作流
CREATE TABLE IF NOT EXISTS "WorkProcess" (
"id" TEXT NOT NULL,
"orgId" TEXT NOT NULL,
"type" TEXT NOT NULL,
"title" TEXT NOT NULL,
"employeeId" TEXT,
"status" TEXT NOT NULL DEFAULT 'DRAFT',
"formData" JSONB NOT NULL,
"documents" JSONB,
"approverId" TEXT,
"approvedAt" TIMESTAMP(3),
"remark" TEXT,
"createdBy" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "WorkProcess_pkey" PRIMARY KEY ("id")
);
-- 外键:orgId -> Organization
ALTER TABLE "WorkProcess"
ADD CONSTRAINT "WorkProcess_orgId_fkey"
FOREIGN KEY ("orgId") REFERENCES "Organization"("id") ON DELETE CASCADE;
-- 外键:employeeId -> Employee (ON DELETE SET NULL)
ALTER TABLE "WorkProcess"
ADD CONSTRAINT "WorkProcess_employeeId_fkey"
FOREIGN KEY ("employeeId") REFERENCES "Employee"("id") ON DELETE SET NULL;
-- 索引
CREATE INDEX IF NOT EXISTS "WorkProcess_orgId_status_idx" ON "WorkProcess"("orgId", "status");
CREATE INDEX IF NOT EXISTS "WorkProcess_orgId_type_idx" ON "WorkProcess"("orgId", "type");
CREATE INDEX IF NOT EXISTS "WorkProcess_orgId_createdBy_idx" ON "WorkProcess"("orgId", "createdBy");
-- 2. EnterpriseTemplate 企业自建文本模板
CREATE TABLE IF NOT EXISTS "EnterpriseTemplate" (
"id" TEXT NOT NULL,
"orgId" TEXT NOT NULL,
"name" TEXT NOT NULL,
"category" TEXT NOT NULL,
"description" TEXT,
"content" TEXT NOT NULL,
"variables" TEXT[] NOT NULL DEFAULT '{}',
"status" TEXT NOT NULL DEFAULT 'ACTIVE',
"createdBy" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "EnterpriseTemplate_pkey" PRIMARY KEY ("id")
);
ALTER TABLE "EnterpriseTemplate"
ADD CONSTRAINT "EnterpriseTemplate_orgId_fkey"
FOREIGN KEY ("orgId") REFERENCES "Organization"("id") ON DELETE CASCADE;
CREATE INDEX IF NOT EXISTS "EnterpriseTemplate_orgId_category_idx" ON "EnterpriseTemplate"("orgId", "category");
-- 3. AttendancePublish 考勤发布记录
CREATE TABLE IF NOT EXISTS "AttendancePublish" (
"id" TEXT NOT NULL,
"orgId" TEXT NOT NULL,
"month" TEXT NOT NULL,
"title" TEXT NOT NULL,
"status" TEXT NOT NULL DEFAULT 'PUBLISHED',
"publishDate" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"createdBy" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "AttendancePublish_pkey" PRIMARY KEY ("id")
);
ALTER TABLE "AttendancePublish"
ADD CONSTRAINT "AttendancePublish_orgId_fkey"
FOREIGN KEY ("orgId") REFERENCES "Organization"("id") ON DELETE CASCADE;
-- 唯一约束:同一组织同月只能有一条发布记录
CREATE UNIQUE INDEX IF NOT EXISTS "AttendancePublish_orgId_month_key" ON "AttendancePublish"("orgId", "month");
CREATE INDEX IF NOT EXISTS "AttendancePublish_orgId_month_idx" ON "AttendancePublish"("orgId", "month");
@@ -15,14 +15,21 @@ function extractVariables(content: string): string[] {
router.get('/', authMiddleware, async (req: AuthRequest, res: Response, next: NextFunction) => { router.get('/', authMiddleware, async (req: AuthRequest, res: Response, next: NextFunction) => {
try { try {
const { category, search } = req.query const { category, search } = req.query
const page = parseInt(req.query.page as string) || 1
const pageSize = Math.min(parseInt(req.query.pageSize as string) || 20, 200)
const where: any = { orgId: req.user!.orgId, status: 'ACTIVE' } const where: any = { orgId: req.user!.orgId, status: 'ACTIVE' }
if (category) where.category = category if (category) where.category = category
if (search) where.name = { contains: String(search) } if (search) where.name = { contains: String(search) }
const items = await (prisma as any).enterpriseTemplate.findMany({ const [items, total] = await Promise.all([
where, (prisma as any).enterpriseTemplate.findMany({
orderBy: { createdAt: 'desc' }, where,
}) orderBy: { createdAt: 'desc' },
res.json({ success: true, data: items }) skip: (page - 1) * pageSize,
take: pageSize,
}),
(prisma as any).enterpriseTemplate.count({ where }),
])
res.json({ success: true, data: { items, total, page, pageSize, totalPages: Math.ceil(total / pageSize) } })
} catch (err) { } catch (err) {
next(err) next(err)
} }
+4 -2
View File
@@ -13,8 +13,10 @@ const router = Router()
router.get('/', authMiddleware, async (req: AuthRequest, res: Response, next: NextFunction) => { router.get('/', authMiddleware, async (req: AuthRequest, res: Response, next: NextFunction) => {
try { try {
const status = req.query.status as string | undefined const status = req.query.status as string | undefined
const policies = await getPolicies(req.user!.orgId, status) const page = parseInt(req.query.page as string) || 1
res.json({ success: true, data: policies }) const pageSize = Math.min(parseInt(req.query.pageSize as string) || 20, 200)
const result = await getPolicies(req.user!.orgId, status, page, pageSize)
res.json({ success: true, data: result })
} catch (err) { } catch (err) {
next(err) next(err)
} }
+3 -1
View File
@@ -162,7 +162,9 @@ router.get('/drafts', authMiddleware, async (req: AuthRequest, res, next) => {
const status = req.query.status as string | undefined const status = req.query.status as string | undefined
const search = req.query.search as string | undefined const search = req.query.search as string | undefined
const department = req.query.department as string | undefined const department = req.query.department as string | undefined
const result = await getDrafts(req.user!.orgId, status, search, department) const page = parseInt(req.query.page as string) || 1
const pageSize = Math.min(parseInt(req.query.pageSize as string) || 20, 200)
const result = await getDrafts(req.user!.orgId, status, search, department, page, pageSize)
res.json({ success: true, data: result }) res.json({ success: true, data: result })
} catch (err) { } catch (err) {
next(err) next(err)
+10 -3
View File
@@ -73,24 +73,31 @@ export async function createPolicy(orgId: string, userId: string, data: { title:
/** /**
* 获取制度列表(含阅读签收统计) * 获取制度列表(含阅读签收统计)
*/ */
export async function getPolicies(orgId: string, status?: string) { export async function getPolicies(orgId: string, status?: string, page?: number, pageSize?: number) {
const where: any = { orgId } const where: any = { orgId }
if (status) where.status = status if (status) where.status = status
const [policies, totalEmployees] = await Promise.all([ const hasPagination = page && pageSize
const [policies, totalEmployees, total] = await Promise.all([
prisma.policyDocument.findMany({ prisma.policyDocument.findMany({
where, where,
orderBy: { updatedAt: 'desc' }, orderBy: { updatedAt: 'desc' },
include: { include: {
_count: { select: { readRecords: true } }, _count: { select: { readRecords: true } },
}, },
...(hasPagination ? { skip: (page! - 1) * pageSize!, take: pageSize! } : {}),
}), }),
prisma.employee.count({ where: { orgId, status: 'ACTIVE' } }), prisma.employee.count({ where: { orgId, status: 'ACTIVE' } }),
hasPagination ? prisma.policyDocument.count({ where }) : Promise.resolve(0),
]) ])
return policies.map(p => ({ const items = policies.map(p => ({
...p, ...p,
readCount: p._count?.readRecords || 0, readCount: p._count?.readRecords || 0,
totalEmployees, totalEmployees,
})) }))
if (hasPagination) {
return { items, total, page, pageSize, totalPages: Math.ceil(total / pageSize!) }
}
return items
} }
/** /**
+17 -7
View File
@@ -738,7 +738,7 @@ export async function cancelTermination(orgId: string, recordId: string, userId:
} }
/** 获取草稿列表 */ /** 获取草稿列表 */
export async function getDrafts(orgId: string, status?: string, search?: string, department?: string) { export async function getDrafts(orgId: string, status?: string, search?: string, department?: string, page?: number, pageSize?: number) {
const where: any = { orgId } const where: any = { orgId }
if (status) { if (status) {
where.status = status where.status = status
@@ -758,13 +758,18 @@ export async function getDrafts(orgId: string, status?: string, search?: string,
} }
} }
const records = await prisma.terminationRecord.findMany({ const hasPagination = page && pageSize
where, const [records, total] = await Promise.all([
include: { employee: true }, prisma.terminationRecord.findMany({
orderBy: { updatedAt: 'desc' }, where,
}) include: { employee: true },
orderBy: { updatedAt: 'desc' },
...(hasPagination ? { skip: (page! - 1) * pageSize!, take: pageSize! } : {}),
}),
hasPagination ? prisma.terminationRecord.count({ where }) : Promise.resolve(0),
])
return records.map((r) => ({ const items = records.map((r) => ({
id: r.id, id: r.id,
employeeId: r.employeeId, employeeId: r.employeeId,
employeeName: r.employee.name, employeeName: r.employee.name,
@@ -780,6 +785,11 @@ export async function getDrafts(orgId: string, status?: string, search?: string,
createdAt: r.createdAt.toISOString().slice(0, 10), createdAt: r.createdAt.toISOString().slice(0, 10),
updatedAt: r.updatedAt.toISOString().slice(0, 10), updatedAt: r.updatedAt.toISOString().slice(0, 10),
})) }))
if (hasPagination) {
return { items, total, page, pageSize, totalPages: Math.ceil(total / pageSize!) }
}
return items
} }
/** 获取单条记录详情(含所有流程字段) */ /** 获取单条记录详情(含所有流程字段) */
+1 -8
View File
@@ -69,17 +69,10 @@ export async function executeWorkProcess(processId: string, type: string, formDa
case 'CONFIRM': { case 'CONFIRM': {
const { employeeId, confirmDate, regularSalary } = formData const { employeeId, confirmDate, regularSalary } = formData
if (employeeId) { if (employeeId) {
const updateData: any = {}
if (regularSalary) { if (regularSalary) {
updateData.monthlySalary = encrypt(String(regularSalary))
}
if (confirmDate) {
updateData.probationEndDate = new Date(confirmDate)
}
if (Object.keys(updateData).length > 0) {
await prisma.employee.update({ await prisma.employee.update({
where: { id: employeeId }, where: { id: employeeId },
data: updateData, data: { monthlySalary: encrypt(String(regularSalary)) },
}) })
await runRiskDetection(orgId) await runRiskDetection(orgId)
} }
+11 -19
View File
@@ -8,6 +8,7 @@ import Button from '../components/ui/Button'
import { Input, Label, Select } from '../components/ui/Input' import { Input, Label, Select } from '../components/ui/Input'
import Modal from '../components/ui/Modal' import Modal from '../components/ui/Modal'
import EmptyState from '../components/ui/EmptyState' import EmptyState from '../components/ui/EmptyState'
import Pagination from '../components/ui/Pagination'
interface EmployeeItem { interface EmployeeItem {
id: string id: string
@@ -37,13 +38,14 @@ export default function Contracts() {
const [filterDepartment, setFilterDepartment] = useState('') const [filterDepartment, setFilterDepartment] = useState('')
const [filterContractStatus, setFilterContractStatus] = useState('') const [filterContractStatus, setFilterContractStatus] = useState('')
const [page, setPage] = useState(1) const [page, setPage] = useState(1)
const [pageSize, setPageSize] = useState(20)
const [showAddModal, setShowAddModal] = useState(false) const [showAddModal, setShowAddModal] = useState(false)
const [selectedEmpId, setSelectedEmpId] = useState<string | null>(null) const [selectedEmpId, setSelectedEmpId] = useState<string | null>(null)
const { data, isLoading } = useQuery<EmployeeListResponse>({ const { data, isLoading } = useQuery<EmployeeListResponse>({
queryKey: ['employees', search, filterDepartment, filterContractStatus, page], queryKey: ['employees', search, filterDepartment, filterContractStatus, page, pageSize],
queryFn: async () => { queryFn: async () => {
const params: any = { search, page, pageSize: 20 } const params: any = { search, page, pageSize }
if (filterDepartment) params.department = filterDepartment if (filterDepartment) params.department = filterDepartment
if (filterContractStatus) params.contractStatus = filterContractStatus if (filterContractStatus) params.contractStatus = filterContractStatus
const res = await api.get('/roster', { params }) as any const res = await api.get('/roster', { params }) as any
@@ -181,23 +183,13 @@ export default function Contracts() {
</div> </div>
{/* 分页 */} {/* 分页 */}
{data.totalPages > 1 && ( <Pagination
<div className="flex items-center justify-center gap-2 mt-4"> page={page}
<Button pageSize={pageSize}
variant="secondary" total={data.total}
size="sm" onPageChange={setPage}
disabled={page === 1} onPageSizeChange={(s) => { setPageSize(s); setPage(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>
)}
</> </>
)} )}
</Card> </Card>
+19 -6
View File
@@ -4,21 +4,27 @@ import { ShieldCheck, FileText, AlertCircle, CheckCircle, XCircle } from 'lucide
import api from '../lib/api' import api from '../lib/api'
import Card from '../components/ui/Card' import Card from '../components/ui/Card'
import EmptyState from '../components/ui/EmptyState' import EmptyState from '../components/ui/EmptyState'
import Pagination from '../components/ui/Pagination'
/** /**
* 证据链管理页面 * 证据链管理页面
*/ */
export default function Evidence() { export default function Evidence() {
const [refType, setRefType] = useState<string>('') const [refType, setRefType] = useState<string>('')
const [page, setPage] = useState(1)
const [pageSize, setPageSize] = useState(20)
const { data: list, isLoading } = useQuery<any>({ const { data: listData, isLoading } = useQuery<any>({
queryKey: ['evidence', refType], queryKey: ['evidence', refType, page, pageSize],
queryFn: async () => { queryFn: async () => {
const params = refType ? `?category=${refType}` : '?category=ALL' const params: any = { page, pageSize }
const res = await api.get(`/evidence${params}`) as any params.category = refType || 'ALL'
return res.data?.records || [] 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>({ const { data: verifyResult, refetch: verifyAll } = useQuery<any>({
queryKey: ['evidence-verify-all'], queryKey: ['evidence-verify-all'],
@@ -68,7 +74,7 @@ export default function Evidence() {
{['', 'ONBOARD', 'CONTRACT_SIGN', 'PAYSLIP_CONFIRM', 'DISCIPLINARY', 'ATTENDANCE', 'TERMINATION'].map((t) => ( {['', 'ONBOARD', 'CONTRACT_SIGN', 'PAYSLIP_CONFIRM', 'DISCIPLINARY', 'ATTENDANCE', 'TERMINATION'].map((t) => (
<button <button
key={t} 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'}`} 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' ? '入职' : '考勤'} {t === '' ? '全部' : t === 'DISCIPLINARY' ? '违纪' : t === 'TERMINATION' ? '解聘' : t === 'CONTRACT_SIGN' ? '合同' : t === 'PAYSLIP_CONFIRM' ? '薪酬' : t === 'ONBOARD' ? '入职' : '考勤'}
@@ -112,6 +118,13 @@ export default function Evidence() {
))} ))}
</div> </div>
)} )}
<Pagination
page={page}
pageSize={pageSize}
total={total}
onPageChange={setPage}
onPageSizeChange={(s) => { setPageSize(s); setPage(1) }}
/>
</div> </div>
) )
} }
+15 -3
View File
@@ -6,6 +6,7 @@ import api from '../lib/api'
import Card from '../components/ui/Card' import Card from '../components/ui/Card'
import Button from '../components/ui/Button' import Button from '../components/ui/Button'
import EmptyState from '../components/ui/EmptyState' import EmptyState from '../components/ui/EmptyState'
import Pagination from '../components/ui/Pagination'
const STEP_LABELS: Record<string, string> = { const STEP_LABELS: Record<string, string> = {
DRAFTING: '起草', DRAFTING: '起草',
@@ -23,14 +24,18 @@ export default function Policies() {
const queryClient = useQueryClient() const queryClient = useQueryClient()
const [showCreate, setShowCreate] = useState(false) const [showCreate, setShowCreate] = useState(false)
const [selectedPolicy, setSelectedPolicy] = useState<any>(null) const [selectedPolicy, setSelectedPolicy] = useState<any>(null)
const [page, setPage] = useState(1)
const [pageSize, setPageSize] = useState(20)
const { data: list, isLoading } = useQuery<any>({ const { data: listData, isLoading } = useQuery<any>({
queryKey: ['policies'], queryKey: ['policies', page, pageSize],
queryFn: async () => { queryFn: async () => {
const res = await api.get('/policies') as any const res = await api.get('/policies', { params: { page, pageSize } }) as any
return res.data return res.data
}, },
}) })
const list = listData?.items || []
const total = listData?.total || 0
const advanceMutation = useMutation({ const advanceMutation = useMutation({
mutationFn: ({ id, step, note }: { id: string; step: number; note?: string }) => api.post(`/policies/${id}/advance-step`, { step, note }), 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> </div>
)} )}
<Pagination
page={page}
pageSize={pageSize}
total={total}
onPageChange={setPage}
onPageSizeChange={(s) => { setPageSize(s); setPage(1) }}
/>
{/* 详情弹窗 */} {/* 详情弹窗 */}
{selectedPolicy && ( {selectedPolicy && (
+17 -4
View File
@@ -9,6 +9,7 @@ import Button from '../components/ui/Button'
import { Input, Label, Select } from '../components/ui/Input' import { Input, Label, Select } from '../components/ui/Input'
import Modal from '../components/ui/Modal' import Modal from '../components/ui/Modal'
import EmptyState from '../components/ui/EmptyState' import EmptyState from '../components/ui/EmptyState'
import Pagination from '../components/ui/Pagination'
const CATEGORY_LABELS: Record<string, string> = { const CATEGORY_LABELS: Record<string, string> = {
CONTRACT: '合同', CONTRACT: '合同',
@@ -303,6 +304,8 @@ function SystemTemplates() {
function EnterpriseTemplates() { function EnterpriseTemplates() {
const queryClient = useQueryClient() const queryClient = useQueryClient()
const [category, setCategory] = useState<string>('') const [category, setCategory] = useState<string>('')
const [page, setPage] = useState(1)
const [pageSize, setPageSize] = useState(20)
const [showEdit, setShowEdit] = useState(false) const [showEdit, setShowEdit] = useState(false)
const [editItem, setEditItem] = useState<any>(null) const [editItem, setEditItem] = useState<any>(null)
const [form, setForm] = useState({ name: '', category: 'CONTRACT', description: '', content: '' }) const [form, setForm] = useState({ name: '', category: 'CONTRACT', description: '', content: '' })
@@ -310,14 +313,17 @@ function EnterpriseTemplates() {
const [rendered, setRendered] = useState('') const [rendered, setRendered] = useState('')
const [variables, setVariables] = useState<Record<string, string>>({}) const [variables, setVariables] = useState<Record<string, string>>({})
const { data: list, isLoading } = useQuery<any>({ const { data: listData, isLoading } = useQuery<any>({
queryKey: ['enterprise-templates', category], queryKey: ['enterprise-templates', category, page, pageSize],
queryFn: async () => { queryFn: async () => {
const params = category ? `?category=${category}` : '' const params: any = { page, pageSize }
const res = await api.get(`/enterprise-templates${params}`) as any if (category) params.category = category
const res = await api.get('/enterprise-templates', { params }) as any
return res.data return res.data
}, },
}) })
const list = listData?.items || []
const total = listData?.total || 0
const { data: detail } = useQuery<any>({ const { data: detail } = useQuery<any>({
queryKey: ['enterprise-template-detail', selected?.id], queryKey: ['enterprise-template-detail', selected?.id],
@@ -459,6 +465,13 @@ function EnterpriseTemplates() {
))} ))}
</div> </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"> <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 Button from '../components/ui/Button'
import { Input, Label, Select } from '../components/ui/Input' import { Input, Label, Select } from '../components/ui/Input'
import EmptyState from '../components/ui/EmptyState' 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 }) 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 [filterStatus, setFilterStatus] = useState('')
const [filterDepartment, setFilterDepartment] = useState('') const [filterDepartment, setFilterDepartment] = useState('')
const [searchTerm, setSearchTerm] = useState('') const [searchTerm, setSearchTerm] = useState('')
const [draftPage, setDraftPage] = useState(1)
const [draftPageSize, setDraftPageSize] = useState(20)
const { data: employees } = useQuery<RosterEmployee[]>({ const { data: employees } = useQuery<RosterEmployee[]>({
queryKey: ['roster-for-termination'], queryKey: ['roster-for-termination'],
@@ -250,10 +253,10 @@ export default function Termination() {
}) })
// 草稿列表 // 草稿列表
const { data: drafts, refetch: refetchDrafts } = useQuery({ const { data: draftsData, refetch: refetchDrafts } = useQuery({
queryKey: ['termination-drafts', filterStatus, filterDepartment, searchTerm], queryKey: ['termination-drafts', filterStatus, filterDepartment, searchTerm, draftPage, draftPageSize],
queryFn: async () => { queryFn: async () => {
const params: any = {} const params: any = { page: draftPage, pageSize: draftPageSize }
if (filterStatus) params.status = filterStatus if (filterStatus) params.status = filterStatus
if (filterDepartment) params.department = filterDepartment if (filterDepartment) params.department = filterDepartment
if (searchTerm) params.search = searchTerm if (searchTerm) params.search = searchTerm
@@ -262,6 +265,8 @@ export default function Termination() {
}, },
enabled: view === 'list', enabled: view === 'list',
}) })
const drafts = draftsData?.items || []
const draftsTotal = draftsData?.total || 0
// 草稿详情 // 草稿详情
const { data: draftDetail } = useQuery({ const { data: draftDetail } = useQuery({
@@ -823,6 +828,13 @@ export default function Termination() {
</table> </table>
</div> </div>
)} )}
<Pagination
page={draftPage}
pageSize={draftPageSize}
total={draftsTotal}
onPageChange={setDraftPage}
onPageSizeChange={(s) => { setDraftPageSize(s); setDraftPage(1) }}
/>
</Card> </Card>
</> </>
)} )}
+13 -2
View File
@@ -11,6 +11,7 @@ import Card from '../components/ui/Card'
import Button from '../components/ui/Button' import Button from '../components/ui/Button'
import { Input, Label, Select } from '../components/ui/Input' import { Input, Label, Select } from '../components/ui/Input'
import Modal from '../components/ui/Modal' import Modal from '../components/ui/Modal'
import Pagination from '../components/ui/Pagination'
const PROCESS_ICONS: Record<string, any> = { const PROCESS_ICONS: Record<string, any> = {
HIRE: UserPlus, ONBOARD: LogIn, CUSTOM_CONTRACT: FileSignature, HIRE: UserPlus, ONBOARD: LogIn, CUSTOM_CONTRACT: FileSignature,
@@ -145,11 +146,13 @@ export default function WorkProcess() {
const [filterStatus, setFilterStatus] = useState('') const [filterStatus, setFilterStatus] = useState('')
const [detailId, setDetailId] = useState<string | null>(null) const [detailId, setDetailId] = useState<string | null>(null)
const [previewContent, setPreviewContent] = 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({ const { data: listData, isLoading } = useQuery({
queryKey: ['work-processes', filterType, filterStatus], queryKey: ['work-processes', filterType, filterStatus, page, pageSize],
queryFn: async () => { queryFn: async () => {
const params: any = {} const params: any = { page, pageSize }
if (filterType) params.type = filterType if (filterType) params.type = filterType
if (filterStatus) params.status = filterStatus if (filterStatus) params.status = filterStatus
const res = await api.get('/work-processes', { params }) as any const res = await api.get('/work-processes', { params }) as any
@@ -238,6 +241,7 @@ export default function WorkProcess() {
} }
const items = listData?.items || [] const items = listData?.items || []
const total = listData?.total || 0
return ( return (
<div className="space-y-4"> <div className="space-y-4">
@@ -325,6 +329,13 @@ export default function WorkProcess() {
})} })}
</div> </div>
)} )}
<Pagination
page={page}
pageSize={pageSize}
total={total}
onPageChange={setPage}
onPageSizeChange={(s) => { setPageSize(s); setPage(1) }}
/>
</Card> </Card>
{/* 创建/编辑弹窗 */} {/* 创建/编辑弹窗 */}