refactor: 全量迁移前端 API 调用到统一 api-services 服务层

- 新建 api-services-raw.ts 导出原始 axios 方法供特殊端点使用
- 完成 api-services.ts 全领域覆盖(auth/employee/roster/dashboard/attendance/payroll/socialInsurance/commercialInsurance/termination/policies/evidence/audit/calendar/companyFiles/notifications/settings/ai/platform/portal/survey/search)
- 迁移所有 47+ 页面文件:pages/、pages/roster/、pages/portal/、pages/platform/、pages/auth/、pages/dashboard/、pages/compliance/
- 移除所有直接 import api from '../../lib/api' 引用
- 修复 Termination.tsx / WorkProcess.tsx 中 string|null 类型错误
- 修复 SocialInsurance.tsx 中 api-services-raw delete 导入名
- 修复 PlatformLogin.tsx 变量遮蔽问题
- tsc --noEmit 零错误,vite build 成功
This commit is contained in:
selfrelease
2026-08-01 16:13:19 +08:00
parent fb924dea98
commit 16f22e6622
64 changed files with 1197 additions and 597 deletions
+3 -3
View File
@@ -3,7 +3,7 @@ import { HelpCircle, Search, ChevronDown, ChevronRight, Sparkles,
Home, Users, FileText, Calculator, Shield, UserX, Bot,
Bell, Settings, Lightbulb, AlertTriangle, CheckCircle, Phone } from 'lucide-react'
import Modal from './ui/Modal'
import api from '../lib/api'
import { aiApi } from '../lib/api-services'
import clsx from 'clsx'
interface HelpCategory {
@@ -267,8 +267,8 @@ export default function HelpModal({ open, onClose }: { open: boolean; onClose: (
setRagLoading(true)
setRagError(false)
try {
const res = await api.post('/ai/rag/help-search', { query: searchQuery, topK: 8 }) as any
setRagResults(res.data?.results || [])
const res = await aiApi.ragHelpSearch(searchQuery, 8) as any
setRagResults(res?.results || [])
} catch {
setRagError(true)
setRagResults([])
+3 -4
View File
@@ -6,8 +6,7 @@ import { useState, useEffect } from 'react'
import { QRCodeSVG } from 'qrcode.react'
import { Smartphone, Copy, Check, Search, Loader2, User, ChevronRight } from 'lucide-react'
import Modal from './ui/Modal'
import api from '../lib/api'
import { employeeApi } from '../lib/api-services'
import { employeeApi, portalApi } from '../lib/api-services'
interface Employee {
id: string
@@ -47,8 +46,8 @@ export default function PortalQRModal({ open, onClose }: { open: boolean; onClos
setGenerating(true)
setAutoLoginUrl('')
try {
const res = await api.post('/portal/auto-login-token', { employeeId: emp.id }) as any
const token = res.data?.token
const res = await portalApi.generateAutoLoginToken(emp.id) as any
const token = res?.token
if (token) {
setAutoLoginUrl(`${window.location.origin}/portal/auto-login?token=${token}`)
}
+2 -2
View File
@@ -2,7 +2,7 @@ import { useState, useMemo, useEffect, useCallback } from 'react'
import { Star, ChevronDown, ChevronRight, Check, Search, ClipboardList, Send, Save, RotateCcw } from 'lucide-react'
import Modal from './ui/Modal'
import { toast } from 'sonner'
import api from '../lib/api'
import { surveyApi } from '../lib/api-services'
import { surveyPages, totalFeatures } from '../data/surveyData'
interface FeatureScore {
@@ -104,7 +104,7 @@ export default function SurveyModal({ open, onClose }: { open: boolean; onClose:
}
setSubmitting(true)
try {
await api.post('/survey/submit', { items })
await surveyApi.submit(items)
toast.success(`已提交 ${items.length} 项评分,感谢您的反馈!`)
localStorage.removeItem(STORAGE_KEY)
setSavedAt(null)
+2 -5
View File
@@ -3,7 +3,7 @@ import { ChevronDown, Settings as SettingsIcon, Bell, Menu, HelpCircle, Smartpho
import { useState } from 'react'
import { useQuery } from '@tanstack/react-query'
import { useAuthStore } from '../../store/authStore'
import api from '../../lib/api'
import { dashboardApi } from '../../lib/api-services'
import Breadcrumb from './Breadcrumb'
import HelpModal from '../HelpModal'
import PortalQRModal from '../PortalQRModal'
@@ -19,10 +19,7 @@ export default function TopNav({ onMenuClick }: { onMenuClick?: () => void }) {
const { data: dashboardData } = useQuery<any>({
queryKey: ['dashboard'],
queryFn: async () => {
const res = await api.get('/dashboard') as any
return res.data
},
queryFn: () => dashboardApi.data(),
refetchInterval: 60000,
})
const riskCount = dashboardData?.riskSummary?.pending || 0
@@ -5,7 +5,7 @@
import { useState, useRef, useEffect } from 'react'
import { useMutation } from '@tanstack/react-query'
import { Bot, Send, X, Sparkles } from 'lucide-react'
import api from '../../lib/api'
import { aiApi } from '../../lib/api-services'
interface AIContextEntryProps {
/** 当前页面上下文标识 */
@@ -39,7 +39,7 @@ export function AIContextEntry({ context, contextData, pageTitle }: AIContextEnt
/** AI 问答 */
const askMutation = useMutation({
mutationFn: async (question: string) => {
const res = await api.post('/ai/context-ask', {
const res = await aiApi.contextAsk({
context,
contextData,
pageTitle,
@@ -4,7 +4,7 @@
import { useState, useEffect, useRef, useMemo } from 'react'
import { useNavigate } from 'react-router-dom'
import { Search, ArrowRight, Clock } from 'lucide-react'
import api from '../../lib/api'
import { searchApi } from '../../lib/api-services'
/** 搜索结果类型 */
interface SearchResult {
@@ -74,8 +74,8 @@ export function CommandPalette({ open, onClose }: CommandPaletteProps) {
// 远程搜索员工
const timer = setTimeout(async () => {
try {
const res = await api.get('/search', { params: { q } }) as any
const remoteResults: SearchResult[] = (res.data?.employees || []).map((e: any) => ({
const res = await searchApi.search(q) as any
const remoteResults: SearchResult[] = (res?.employees || []).map((e: any) => ({
type: 'employee' as const,
id: e.id,
title: e.name,
+11
View File
@@ -0,0 +1,11 @@
/**
* 原始 HTTP 方法导出 — 供少量未纳入服务层的特殊端点使用
* 优先使用 api-services.ts 中的领域服务
*/
import api from './api'
export const get = ((url: string, config?: any) => api.get(url, config)) as any
export const post = ((url: string, data?: any, config?: any) => api.post(url, data, config)) as any
export const put = ((url: string, data?: any, config?: any) => api.put(url, data, config)) as any
export const patch = ((url: string, data?: any, config?: any) => api.patch(url, data, config)) as any
export const del = ((url: string, config?: any) => api.delete(url, config)) as any
+773 -3
View File
@@ -4,17 +4,40 @@
*/
import api from './api'
import axios from 'axios'
/** axios 拦截器已返回 response.data,但 TS 类型仍是 AxiosResponse,用 as any 绕过 */
const get = ((url: string, config?: any) => api.get(url, config)) as any
const post = ((url: string, data?: any, config?: any) => api.post(url, data, config)) as any
const put = ((url: string, data?: any, config?: any) => api.put(url, data, config)) as any
const patch = ((url: string, data?: any, config?: any) => api.patch(url, data, config)) as any
const del = ((url: string, config?: any) => api.delete(url, config)) as any
/** 统一响应解析:返回一个回调函数,用于 .then() 中取 data 字段 */
function unwrap<T>(): (res: any) => T {
return (res: any) => (res?.data ?? res) as T
}
// ========== 认证相关 ==========
export const authApi = {
/** 登录 */
login: (data: { phone: string; password: string }) =>
post('/auth/login', data).then(unwrap<any>()),
/** 注册 */
register: (data: any) =>
post('/auth/register', data).then(unwrap<any>()),
/** 平台登录 */
platformLogin: (data: any) =>
post('/auth/platform-login', data).then(unwrap<any>()),
/** 发送忘记密码验证码 */
forgotPasswordSendCode: (phone: string) =>
post('/auth/forgot-password/send-code', { phone }).then(unwrap<any>()),
/** 验证忘记密码 */
forgotPasswordVerify: (data: { phone: string; code: string; newPassword: string }) =>
post('/auth/forgot-password/verify', data),
}
// ========== 员工相关 ==========
export interface EmployeeOption {
@@ -34,12 +57,36 @@ export const employeeApi = {
/** 轻量级全量员工列表(含 RESIGNED) */
allLite: (params?: { status?: string; department?: string }) =>
get('/employees/all-lite', { params }).then(unwrap<EmployeeOption[]>()),
/** 员工分页列表 */
paged: (params: { page?: number; pageSize?: number; search?: string; department?: string }) =>
get('/employees', { params }).then(unwrap<any>()),
/** 员工详情 */
detail: (id: string) =>
get(`/employees/${id}`).then(unwrap<any>()),
/** 员工档案(花名册) */
profile: (id: string) =>
get(`/roster/${id}/profile`).then(unwrap<any>()),
/** 创建员工 */
create: (data: any) =>
post('/employees', data),
/** 更新员工 */
update: (id: string, data: any) =>
put(`/employees/${id}`, data),
/** 删除员工 */
remove: (id: string) =>
del(`/employees/${id}`),
/** 重新入职 */
rehire: (id: string, data: any) =>
post(`/employees/${id}/rehire`, data),
/** 添加合同 */
addContract: (data: any) =>
post('/employees/contracts', data),
/** 删除合同 */
removeContract: (contractId: string) =>
del(`/employees/contracts/${contractId}`),
/** 批量续签预检 */
previewRenew: (contractIds: string[]) =>
post('/employees/contracts/preview-renew', { contractIds }),
/** 批量续签 */
batchRenew: (data: { contractIds: string[]; years: number }) =>
post('/employees/contracts/batch-renew', data),
}
// ========== 花名册相关 ==========
@@ -78,6 +125,53 @@ export const rosterApi = {
/** 证据链 */
evidenceChain: (employeeId: string) =>
get(`/roster/${employeeId}/evidence-chain`).then(unwrap<any>()),
/** 员工档案 */
profile: (employeeId: string) =>
get(`/roster/${employeeId}/profile`).then(unwrap<any>()),
/** 调薪 */
salaryChange: (employeeId: string, data: any) =>
post(`/roster/${employeeId}/salary-change`, data),
/** 调岗 */
departmentChange: (employeeId: string, data: any) =>
post(`/roster/${employeeId}/department-change`, data),
/** 考勤记录 */
attendance: (employeeId: string, data: any) =>
post(`/roster/${employeeId}/attendance`, data),
/** 删除考勤记录 */
removeAttendance: (employeeId: string, id: string) =>
del(`/roster/${employeeId}/attendance/${id}`),
/** 培训记录 */
training: (employeeId: string, data: any) =>
post(`/roster/${employeeId}/training`, data),
/** 删除培训记录 */
removeTraining: (employeeId: string, id: string) =>
del(`/roster/${employeeId}/training/${id}`),
/** 绩效记录 */
performance: (employeeId: string, data: any) =>
post(`/roster/${employeeId}/performance`, data),
/** 删除绩效记录 */
removePerformance: (employeeId: string, id: string) =>
del(`/roster/${employeeId}/performance/${id}`),
/** 违纪记录-创建 */
createDisciplinary: (employeeId: string, data: any) =>
post(`/roster/${employeeId}/disciplinary`, data),
/** 删除违纪记录 */
removeDisciplinary: (employeeId: string, id: string) =>
del(`/roster/${employeeId}/disciplinary/${id}`),
}
// ========== 附件相关 ==========
export const attachmentApi = {
/** 添加附件 */
add: (data: any) =>
post('/attachments', data),
/** 获取附件列表 */
list: (employeeId: string) =>
get(`/attachments/${employeeId}`).then(unwrap<any[]>()),
/** 删除附件 */
remove: (id: string) =>
del(`/attachments/${id}`),
}
// ========== 仪表盘相关 ==========
@@ -92,6 +186,9 @@ export const dashboardApi = {
/** 体检诊断历史 */
healthCheckHistory: () =>
get('/dashboard/health-check/history').then(unwrap<any[]>()),
/** 体检诊断保存 */
healthCheckSave: () =>
post('/dashboard/health-check/save').then(unwrap<any>()),
/** 风险中心(与工作台同口径) */
risks: () =>
get('/dashboard/risks').then(unwrap<any[]>()),
@@ -119,6 +216,9 @@ export const dashboardApi = {
/** 年度价值报告历史 */
annualValueHistory: () =>
get('/dashboard/annual-value/history').then(unwrap<any[]>()),
/** 年度价值报告保存 */
annualValueSave: (year: number) =>
post('/dashboard/annual-value/save', { year }).then(unwrap<any>()),
}
// ========== 考勤相关 ==========
@@ -140,4 +240,674 @@ export const attendanceApi = {
get('/attendance/leaves').then(unwrap<any[]>()),
publishRecords: () =>
get('/attendance/publish-records').then(unwrap<any[]>()),
/** 发布考勤 */
publish: (month: string) =>
post('/attendance/publish', { month }).then(unwrap<any>()),
/** 取消发布 */
cancelPublish: (id: string) =>
post(`/attendance/publish/${id}/cancel`).then(unwrap<any>()),
/** 批量确认 */
batchConfirm: (data: { month: string; all?: boolean; ids?: string[] }) =>
post('/attendance/batch-confirm', data).then(unwrap<any>()),
/** 单条确认 */
confirm: (data: { employeeId: string; month: string }) =>
post('/attendance/confirm', data).then(unwrap<any>()),
/** 创建/更新班次 */
saveShift: (data: any, editId?: string) =>
editId ? put(`/attendance/shifts/${editId}`, data) : post('/attendance/shifts', data),
/** 删除班次 */
removeShift: (id: string) =>
del(`/attendance/shifts/${id}`),
/** 批量排班 */
batchAssign: (items: any[]) =>
post('/attendance/shift-assignments/batch', { items }),
/** 删除排班 */
removeAssignment: (id: string) =>
del(`/attendance/shift-assignments/${id}`),
/** 创建请假记录 */
createLeave: (data: any) =>
post('/attendance/leaves', data),
/** 删除请假记录 */
removeLeave: (id: string) =>
del(`/attendance/leaves/${id}`),
}
// ========== AI 相关 ==========
export const aiApi = {
/** 对话历史列表 */
conversations: (type: string) =>
get(`/ai/conversations?type=${type}`).then(unwrap<any[]>()),
/** 对话详情 */
conversation: (id: string) =>
get(`/ai/conversations/${id}`).then(unwrap<any>()),
/** 创建对话 */
createConversation: (data: any) =>
post('/ai/conversations', data).then(unwrap<any>()),
/** 更新对话 */
updateConversation: (id: string, data: any) =>
put(`/ai/conversations/${id}`, data),
/** 删除对话 */
removeConversation: (id: string) =>
del(`/ai/conversations/${id}`),
/** AI 咨询 */
consult: (data: any) =>
post('/ai/consultation', data).then(unwrap<any>()),
/** AI 上下文问答 */
contextAsk: (data: any) =>
post('/ai/context-ask', data).then(unwrap<any>()),
/** 合同审查上传 */
reviewUpload: (formData: FormData) =>
post('/ai/review/upload', formData, { headers: { 'Content-Type': 'multipart/form-data' } }).then(unwrap<any>()),
/** 合同审查 */
review: (contractText: string) =>
post('/ai/review', { contractText }).then(unwrap<any>()),
/** 保存审查结果 */
reviewSave: (data: any) =>
post('/ai/review/save', data),
/** 案例匹配 */
matchCase: (scenario: string) =>
post('/ai/match-case', { scenario }).then(unwrap<any>()),
/** 案例转待办 */
caseToTodo: (data: any) =>
post('/ai/case-to-todo', data),
/** RAG 知识库列表 */
ragList: () =>
get('/ai/rag/list').then(unwrap<any[]>()),
/** RAG 添加知识 */
ragAdd: (data: any) =>
post('/ai/rag/add', data),
/** RAG 删除知识 */
ragRemove: (id: string) =>
del(`/ai/rag/${id}`),
/** RAG 帮助搜索 */
ragHelpSearch: (query: string, topK = 8) =>
post('/ai/rag/help-search', { query, topK }).then(unwrap<any>()),
/** RAG 知识库种子数据 */
ragSeed: () =>
post('/ai/rag/seed'),
}
// ========== 薪酬相关 ==========
export const payrollApi = {
/** 薪酬模版列表 */
template: () =>
get('/payroll2/template').then(unwrap<any[]>()),
/** 创建模版项 */
createTemplateItem: (data: any) =>
post('/payroll2/template', data),
/** 更新模版项 */
updateTemplateItem: (id: string, data: any) =>
put(`/payroll2/template/${id}`, data),
/** 删除模版项 */
removeTemplateItem: (id: string) =>
del(`/payroll2/template/${id}`),
/** 检查本月是否已发薪 */
batchCheck: (month: string) =>
get('/payroll2/batches/check', { params: { month } }).then(unwrap<any>()),
/** 批次列表 */
batches: (params?: any) =>
get('/payroll2/batches', { params }).then(unwrap<any[]>()),
/** 归档批次列表 */
archivedBatches: () =>
get('/payroll2/batches/archived/list').then(unwrap<any[]>()),
/** 批次详情 */
batchDetail: (id: string) =>
get(`/payroll2/batches/${id}`).then(unwrap<any>()),
/** 创建批次 */
createBatch: (data: any) =>
post('/payroll2/batches', data),
/** 重命名批次 */
renameBatch: (batchId: string, name: string) =>
put(`/payroll2/batches/${batchId}/name`, { name }),
/** 删除批次 */
removeBatch: (batchId: string) =>
del(`/payroll2/batches/${batchId}`),
/** 归档批次 */
archiveBatch: (batchId: string) =>
post(`/payroll2/batches/${batchId}/archive`),
/** 取消归档 */
unarchiveBatch: (batchId: string) =>
post(`/payroll2/batches/${batchId}/unarchive`),
/** 发布工资条 */
publishPayslip: (batchId: string) =>
post(`/payroll2/batches/${batchId}/publish`),
/** 定时发送工资条 */
schedulePayslip: (batchId: string, scheduledAt: string) =>
post(`/payroll2/batches/${batchId}/schedule`, { scheduledAt }),
/** 批次增加人员 */
addBatchEmployees: (batchId: string, employeeIds: string[]) =>
post(`/payroll2/batches/${batchId}/employees`, { employeeIds }),
/** 批次移除人员 */
removeBatchEmployee: (batchId: string, employeeId: string) =>
del(`/payroll2/batches/${batchId}/employees/${employeeId}`),
/** 更新批次条目 */
updateBatchEntry: (batchId: string, employeeId: string, data: any) =>
put(`/payroll2/batches/${batchId}/entries/${employeeId}`, data),
/** 算薪前 AI 校验 */
preCheck: (batchId: string) =>
get(`/payroll2/batches/${batchId}/pre-check`).then(unwrap<any>()),
/** 生成工资条 */
generatePayslips: (month: string) =>
post('/payroll2/payslips/generate', { month }).then(unwrap<any>()),
/** 加班费记录列表 */
overtimeRecords: (params: { month?: string; employeeId?: string }) =>
get('/payroll/overtime', { params }).then(unwrap<any[]>()),
/** 保存加班费记录 */
saveOvertime: (data: any) =>
post('/payroll/overtime', data).then(unwrap<any>()),
/** 更新加班费记录 */
updateOvertime: (id: string, data: any) =>
put(`/payroll/overtime/${id}`, data).then(unwrap<any>()),
/** 批量导入加班工时 */
batchImportOvertime: (data: any[]) =>
post('/payroll/overtime/batch', data),
/** 导入加班费到批次 */
importOvertimeToBatch: (batchId: string) =>
post(`/payroll/overtime/import-to-batch/${batchId}`).then(unwrap<any>()),
/** 加班费配置 */
overtimeConfig: () =>
get('/payroll/overtime/config').then(unwrap<any>()),
/** 保存加班费配置 */
saveOvertimeConfig: (data: any) =>
post('/payroll/overtime/config', data),
/** 工资条列表 */
payslips: (params: { month?: string; employeeId?: string }) =>
get('/payroll/payslip', { params }).then(unwrap<any[]>()),
/** 创建/更新工资条 */
savePayslip: (data: any) =>
post('/payroll/payslip', data).then(unwrap<any>()),
/** 删除工资条 */
removePayslip: (id: string) =>
del(`/payroll/payslip/${id}`),
/** 个税试算 */
taxPreview: (data: any) =>
post('/payroll/tax-preview', data).then(unwrap<any>()),
/** 薪资汇总表 */
batchSummary: (id: string) =>
get(`/payroll/batch/${id}/summary`).then(unwrap<any>()),
/** 薪资明细表(旧接口) */
batchDetailExport: (id: string) =>
get(`/payroll/batch/${id}/detail`).then(unwrap<any>()),
}
// ========== 薪资仪表盘 ==========
export const salaryDashboardApi = {
data: (year: number) =>
get('/salary/dashboard', { params: { year } }).then(unwrap<any>()),
}
// ========== 社保公积金相关 ==========
export const socialInsuranceApi = {
/** 城市列表 */
cities: () =>
get('/social/config/cities').then(unwrap<string[]>()),
/** 社保配置 */
config: (city: string) =>
get('/social/config', { params: { city } }).then(unwrap<any>()),
/** 公积金配置 */
housingConfig: (city: string) =>
get('/social/housing-config', { params: { city } }).then(unwrap<any>()),
/** 社保配置版本列表 */
configVersions: (city: string) =>
get('/social/config/versions', { params: { city } }).then(unwrap<any[]>()),
/** 公积金配置版本列表 */
housingConfigVersions: (city?: string) =>
get('/social/housing-config/versions', { params: city ? { city } : {} }).then(unwrap<any[]>()),
/** 创建社保配置版本 */
createConfigVersion: (data: any) =>
post('/social/config/versions', data),
/** 创建公积金配置版本 */
createHousingConfigVersion: (data: any) =>
post('/social/housing-config/versions', data),
/** 社保计算 */
calculate: (base: number, city: string) =>
post('/social/calculate', { base, city }).then(unwrap<any>()),
/** 公积金计算 */
housingCalculate: (base: number, city: string) =>
post('/social/housing-calculate', { base, city }).then(unwrap<any>()),
/** AI 建议 */
aiSuggest: (data: { city: string; effectiveFrom: string; type: 'social' | 'housing' }) =>
post('/social/ai-suggest', data).then(unwrap<any>()),
/** 调整预览 */
adjustPreview: (configId: string) =>
get(`/social/config/${configId}/adjust-preview`).then(unwrap<any>()),
/** 公积金调整预览 */
housingAdjustPreview: (configId: string) =>
get(`/social/housing-config/${configId}/adjust-preview`).then(unwrap<any>()),
/** 应用调整 */
applyAdjust: (configId: string, data: any) =>
post(`/social/config/${configId}/adjust-apply`, data),
/** 应用公积金调整 */
applyHousingAdjust: (configId: string, data: any) =>
post(`/social/housing-config/${configId}/adjust-apply`, data),
/** 重置调整 */
resetAdjust: (configId: string, city: string) =>
post(`/social/config/${configId}/reset-adjustment`, { city }),
/** 重置公积金调整 */
resetHousingAdjust: (configId: string, city: string) =>
post(`/social/housing-config/${configId}/reset-adjustment`, { city }),
/** 月度办理列表 */
monthlyProcessList: () =>
get('/social/monthly-process/list').then(unwrap<any[]>()),
/** 月度办理状态 */
monthlyProcessStatus: (month: string) =>
get('/social/monthly-process/status', { params: { month } }).then(unwrap<any>()),
/** 完成月度办理 */
completeMonthlyProcess: (data: any) =>
post('/social/monthly-process/complete', data),
/** 专项附加扣除批量 */
specialDeductionBatch: (month: string) =>
get('/social/special-deduction/batch', { params: { month } }).then(unwrap<any[]>()),
/** 保存专项附加扣除 */
saveSpecialDeduction: (data: any) =>
post('/social/special-deduction', data),
/** 活跃申报员工 */
activeDeclaration: (month: string) =>
get('/social/active-declaration', { params: { month } }).then(unwrap<any>()),
/** 社保/公积金记录更正 */
correctRecord: (type: 'social' | 'housing', id: string, data: any) =>
put(`/social/records/${type}/${id}/correct`, data).then(unwrap<any>()),
}
// ========== 商业保险 ==========
export const commercialInsuranceApi = {
/** 方案列表 */
plans: () =>
get('/commercial-insurance/plans').then(unwrap<any[]>()),
/** 方案详情(含参保人员) */
enrollments: (planId: string) =>
get(`/commercial-insurance/plans/${planId}/enrollments`).then(unwrap<any[]>()),
/** 创建/更新方案 */
savePlan: (data: any, editId?: string) =>
editId ? put(`/commercial-insurance/plans/${editId}`, data) : post('/commercial-insurance/plans', data),
/** 删除方案 */
removePlan: (id: string) =>
del(`/commercial-insurance/plans/${id}`),
}
// ========== 离职相关 ==========
export const terminationApi = {
/** 创建离职 */
create: (data: any) =>
post('/termination', data),
/** 创建草稿 */
createDraft: (data: any) =>
post('/termination/draft', data),
/** 更新草稿 */
updateDraft: (draftId: string, data: any) =>
put(`/termination/draft/${draftId}`, data),
/** 草稿列表 */
drafts: (params: any) =>
get('/termination/drafts', { params }).then(unwrap<any>()),
/** 草稿详情 */
detail: (draftId: string) =>
get(`/termination/detail/${draftId}`).then(unwrap<any>()),
/** 提交审批 */
submit: (draftId: string) =>
post(`/termination/draft/${draftId}/submit`),
/** 审批通过 */
approve: (draftId: string, comment: string) =>
post(`/termination/draft/${draftId}/approve`, { comment }),
/** 审批驳回 */
reject: (draftId: string, comment: string) =>
post(`/termination/draft/${draftId}/reject`, { comment }),
/** 执行解聘 */
execute: (draftId: string) =>
post(`/termination/draft/${draftId}/execute`),
/** 撤销 */
cancel: (draftId: string) =>
post(`/termination/draft/${draftId}/cancel`),
/** 撤回离职记录 */
revoke: (recordId: string) =>
del(`/termination/${recordId}/revoke`),
/** 离职清单 */
checklist: (reason: string, employeeId: string) =>
get(`/termination/checklist/${reason}`, { params: { employeeId } }).then(unwrap<any>()),
/** 风险评估 */
assess: (employeeId: string, reason: string) =>
get(`/termination/assess/${employeeId}`, { params: { reason } }).then(unwrap<any>()),
/** 批量预览 */
batchPreview: (items: any[]) =>
post('/termination/batch/preview', { items }),
}
// ========== 制度相关 ==========
export const policiesApi = {
/** 制度列表 */
list: (params: { page?: number; pageSize?: number; status?: string }) =>
get('/policies', { params }).then(unwrap<any>()),
/** 制度详情 */
detail: (id: string) =>
get(`/policies/${id}`).then(unwrap<any>()),
/** 创建制度 */
create: (data: any) =>
post('/policies', data),
/** 更新制度 */
update: (id: string, data: any) =>
put(`/policies/${id}`, data),
/** 推进民主程序 */
advanceStep: (id: string, step: number, note?: string) =>
post(`/policies/${id}/advance-step`, { step, note }),
/** 删除制度 */
remove: (id: string) =>
del(`/policies/${id}`),
/** 阅读签收统计 */
readStats: (id: string) =>
get(`/policies/${id}/read-stats`).then(unwrap<any>()),
}
// ========== 证据链相关 ==========
export const evidenceApi = {
/** 证据链列表 */
list: (params: { page?: number; pageSize?: number; category?: string }) =>
get('/evidence', { params }).then(unwrap<any>()),
/** 全量验证 */
verifyAll: () =>
get('/evidence/verify-all').then(unwrap<any>()),
}
// ========== 审计日志 ==========
export const auditApi = {
/** 审计日志列表 */
list: (params: { page?: number; pageSize?: number; entity?: string; dateFrom?: string; dateTo?: string }) =>
get('/audit', { params }).then(unwrap<any>()),
/** 审计统计 */
stats: () =>
get('/audit/stats').then(unwrap<any>()),
}
// ========== 日历相关 ==========
export const calendarApi = {
/** 自定义事件列表 */
events: (month: string) =>
get(`/calendar?month=${month}`).then(unwrap<any[]>()),
/** 创建事件 */
createEvent: (data: any) =>
post('/calendar', data),
/** 删除事件 */
removeEvent: (id: string) =>
del(`/calendar/${id}`),
}
// ========== 公司文件 ==========
export const companyFilesApi = {
/** 文件列表 */
list: (params?: { fileType?: string }) =>
get('/company-files', { params }).then(unwrap<any[]>()),
/** 添加文件 */
add: (data: any) =>
post('/company-files', data),
/** 删除文件 */
remove: (id: string) =>
del(`/company-files/${id}`),
}
// ========== 通知相关 ==========
export const notificationsApi = {
/** 通知日志 */
logs: (params?: { page?: number; pageSize?: number }) =>
get('/notifications/logs', { params }).then(unwrap<any>()),
/** 通知设置 */
settings: () =>
get('/notifications/settings').then(unwrap<any>()),
/** 更新通知设置 */
updateSettings: (data: any) =>
put('/notifications/settings', data),
}
// ========== 系统设置 ==========
export const settingsApi = {
/** 组织设置 */
org: () =>
get('/settings/org').then(unwrap<any>()),
/** 更新组织设置 */
updateOrg: (data: any) =>
put('/settings/org', data),
/** 用户列表 */
users: () =>
get('/settings/users').then(unwrap<any>()),
/** 添加用户 */
addUser: (data: any) =>
post('/settings/users', data),
/** 更新用户 */
updateUser: (id: string, data: any) =>
put(`/settings/users/${id}`, data),
/** 切换用户禁用状态 */
toggleDisable: (id: string) =>
patch(`/settings/users/${id}/toggle-disable`),
/** 用量统计 */
usage: () =>
get('/settings/usage').then(unwrap<any>()),
/** 更新套餐 */
updatePlan: (plan: string) =>
put('/settings/plan', { plan }),
/** 退休政策 */
retirementPolicy: () =>
get('/settings/retirement-policy').then(unwrap<any>()),
}
// ========== 模板相关 ==========
export const templatesApi = {
/** 系统模板列表 */
list: (category?: string) =>
get('/templates', { params: category ? { category } : {} }).then(unwrap<any[]>()),
/** 系统模板详情 */
detail: (id: string) =>
get(`/templates/${id}`).then(unwrap<any>()),
/** 渲染模板 */
render: (id: string, variables: any) =>
post(`/templates/${id}/render`, { variables }).then(unwrap<any>()),
/** 企业模板列表 */
enterpriseList: (params: { page?: number; pageSize?: number; category?: string }) =>
get('/enterprise-templates', { params }).then(unwrap<any>()),
/** 企业模板详情 */
enterpriseDetail: (id: string) =>
get(`/enterprise-templates/${id}`).then(unwrap<any>()),
/** 创建/更新企业模板 */
saveEnterprise: (data: any, editId?: string) =>
editId ? put(`/enterprise-templates/${editId}`, data) : post('/enterprise-templates', data),
/** 删除企业模板 */
removeEnterprise: (id: string) =>
del(`/enterprise-templates/${id}`),
/** 渲染企业模板 */
renderEnterprise: (id: string, variables: any) =>
post(`/enterprise-templates/${id}/render`, { variables }).then(unwrap<any>()),
}
// ========== 工作流程 ==========
export const workProcessApi = {
/** 列表 */
list: (params: { page?: number; pageSize?: number; type?: string; status?: string }) =>
get('/work-processes', { params }).then(unwrap<any>()),
/** 详情 */
detail: (id: string) =>
get(`/work-processes/${id}`).then(unwrap<any>()),
/** 创建 */
create: (data: any) =>
post('/work-processes', data).then(unwrap<any>()),
/** 提交 */
submit: (id: string) =>
post(`/work-processes/${id}/submit`).then(unwrap<any>()),
/** 取消 */
cancel: (id: string) =>
post(`/work-processes/${id}/cancel`).then(unwrap<any>()),
/** 删除 */
remove: (id: string) =>
del(`/work-processes/${id}`),
/** 预览 */
preview: (id: string) =>
get(`/work-processes/${id}/preview`).then(unwrap<any>()),
}
// ========== 特殊状态台账 ==========
export const specialStatusApi = {
/** 列表 */
list: (params: { page?: number; pageSize?: number; search?: string; type?: string; status?: string }) =>
get('/special-statuses', { params }).then(unwrap<any>()),
/** 统计概览 */
stats: () =>
get('/special-statuses/stats/overview').then(unwrap<any>()),
/** 创建 */
create: (data: any) =>
post('/special-statuses', data),
/** 更新 */
update: (id: string, data: any) =>
put(`/special-statuses/${id}`, data),
/** 删除 */
remove: (id: string) =>
del(`/special-statuses/${id}`),
}
// ========== 搜索 ==========
export const searchApi = {
/** 全局搜索 */
search: (q: string) =>
get('/search', { params: { q } }).then(unwrap<any>()),
}
// ========== 问卷 ==========
export const surveyApi = {
/** 提交问卷 */
submit: (items: any[]) =>
post('/survey/submit', { items }),
}
// ========== 平台管理 ==========
export const platformApi = {
/** 平台仪表盘 */
dashboard: () =>
get('/platform/dashboard').then(unwrap<any>()),
/** 组织列表 */
orgs: (params: { page?: number; pageSize?: number; search?: string; plan?: string }) =>
get('/platform/orgs', { params }).then(unwrap<any>()),
/** 组织详情 */
orgDetail: (id: string) =>
get(`/platform/orgs/${id}`).then(unwrap<any>()),
/** 创建组织 */
createOrg: (data: any) =>
post('/platform/orgs', data),
/** 更新组织 */
updateOrg: (id: string, data: any) =>
put(`/platform/orgs/${id}`, data),
/** 更新组织管理员 */
updateOrgAdmin: (id: string, data: any) =>
put(`/platform/orgs/${id}/admin`, data),
/** 删除组织 */
removeOrg: (id: string) =>
del(`/platform/orgs/${id}`),
/** 用户列表 */
users: (params: { page?: number; pageSize?: number; search?: string; orgId?: string }) =>
get('/platform/users', { params }).then(unwrap<any>()),
/** 切换用户状态 */
toggleUser: (id: string) =>
put(`/platform/users/${id}/toggle`),
}
// ========== 员工端 Portal ==========
/** 员工端 API 实例(携带 portalToken */
const portalAxios = axios.create({ baseURL: '/api/v1/portal' })
portalAxios.interceptors.request.use((config: any) => {
const token = localStorage.getItem('portalToken')
if (token) config.headers.Authorization = `Bearer ${token}`
return config
})
const portalGet = ((url: string, config?: any) => portalAxios.get(url, config)) as any
const portalPost = ((url: string, data?: any, config?: any) => portalAxios.post(url, data, config)) as any
export const portalApi = {
/** 登录 */
login: (phone: string, password: string) =>
portalPost('/login', { phone, password }).then(unwrap<any>()),
/** 发送验证码 */
sendCode: (phone: string) =>
portalPost('/send-code', { phone }).then(unwrap<any>()),
/** 验证码登录 */
verifyCode: (phone: string, code: string) =>
portalPost('/verify-code', { phone, code }).then(unwrap<any>()),
/** 自动登录 */
autoLogin: (token: string) =>
portalGet('/auto-login', { params: { token } }).then(unwrap<any>()),
/** 生成自动登录令牌(管理端) */
generateAutoLoginToken: (employeeId: string) =>
post('/portal/auto-login-token', { employeeId }).then(unwrap<any>()),
/** 首页概览 */
homeOverview: () =>
portalGet('/home/overview').then(unwrap<any>()),
/** 工资条 */
payslip: (month: string) =>
portalGet('/payslip', { params: { month } }).then(unwrap<any>()),
/** 工资条历史 */
payslipHistory: () =>
portalGet('/payslip/history').then(unwrap<any[]>()),
/** 确认工资条 */
confirmPayslip: (id: string) =>
portalPost(`/payslip/${id}/confirm`),
/** 考勤 */
attendance: (month: string) =>
portalGet('/attendance', { params: { month } }).then(unwrap<any>()),
/** 合同 */
contract: () =>
portalGet('/contract').then(unwrap<any>()),
/** 合同信息 */
contractInfo: (token: string) =>
portalGet(`/contract-confirm/${token}`).then(unwrap<any>()),
/** 重发合同确认 */
resendContractConfirm: (contractId: string) =>
portalPost('/contract-confirm/resend', { contractId }).then(unwrap<any>()),
/** 合同确认-发送验证码 */
contractConfirmSendCode: (token: string) =>
portalPost('/contract-confirm/send-code', { token }).then(unwrap<any>()),
/** 合同确认 */
contractConfirm: (token: string, verifyCode: string) =>
portalPost('/contract-confirm', { token, agreed: true, verifyCode }),
/** 入职链接信息 */
onboardingInfo: (token: string) =>
portalGet(`/onboarding/${token}`).then(unwrap<any>()),
/** 入职提交 */
onboardingSubmit: (data: any) =>
portalPost('/onboarding', data),
/** 入职上传文件 */
onboardingUpload: (token: string, formData: FormData) =>
portalPost(`/onboarding/${token}/upload`, formData, { headers: { 'Content-Type': 'multipart/form-data' } }).then(unwrap<any>()),
/** 入职进度 */
onboardingProgress: () =>
portalGet('/onboarding/progress').then(unwrap<any>()),
/** 制度列表 */
policies: () =>
portalGet('/policies').then(unwrap<any[]>()),
/** 制度详情 */
policyDetail: (id: string) =>
portalGet(`/policies/${id}`).then(unwrap<any>()),
/** 制度阅读确认 */
policyRead: (id: string) =>
portalPost(`/policies/${id}/read`),
/** 离职状态 */
resignationStatus: () =>
portalGet('/resignation/status').then(unwrap<any[]>()),
/** 提交离职申请 */
resignationSubmit: (data: any) =>
portalPost('/resignation/submit', data).then(unwrap<any>()),
/** 撤回离职申请 */
resignationWithdraw: (id: string) =>
portalPost(`/resignation/${id}/withdraw`).then(unwrap<any>()),
}
+34 -41
View File
@@ -7,8 +7,8 @@ import remarkGfm from 'remark-gfm'
import rehypeRaw from 'rehype-raw'
import { Document, Packer, Paragraph, HeadingLevel, TextRun, Table, TableRow, TableCell, WidthType, BorderStyle, AlignmentType } from 'docx'
import { saveAs } from 'file-saver'
import api from '../lib/api'
import { employeeApi } from '../lib/api-services'
import { aiApi, rosterApi, employeeApi } from '../lib/api-services'
import { post as apiPost } from '../lib/api-services-raw'
import { useAuthStore } from '../store/authStore'
import Card from '../components/ui/Card'
import Button from '../components/ui/Button'
@@ -107,30 +107,28 @@ function useAIHistory(type: 'predict' | 'review' | 'case') {
const { data: history } = useQuery<any[]>({
queryKey,
queryFn: async () => {
const res = await api.get(`/ai/conversations?type=${type}`) as any
return res.data
return await aiApi.conversations(type)
},
})
const saveMutation = useMutation({
mutationFn: async ({ title, input, result }: { title: string; input: string; result: string }) => {
const res = await api.post('/ai/conversations', {
const res = await aiApi.createConversation({
title: `${type}:${title}`,
messages: [{ role: 'user', content: input }, { role: 'assistant', content: result }],
}) as any
return res.data
return res
},
onSuccess: () => queryClient.invalidateQueries({ queryKey }),
})
const deleteMutation = useMutation({
mutationFn: (id: string) => api.delete(`/ai/conversations/${id}`),
mutationFn: (id: string) => aiApi.removeConversation(id),
onSuccess: () => queryClient.invalidateQueries({ queryKey }),
})
const loadHistory = useCallback(async (id: string) => {
const res = await api.get(`/ai/conversations/${id}`) as any
return res.data
return await aiApi.conversation(id)
}, [])
return { history, saveMutation, deleteMutation, loadHistory }
@@ -238,20 +236,18 @@ function ChatTab() {
const { data: conversations } = useQuery<any[]>({
queryKey: ['ai-conversations'],
queryFn: async () => {
const res = await api.get('/ai/conversations?type=chat') as any
return res.data
return await aiApi.conversations('chat')
},
})
const deleteConvMutation = useMutation({
mutationFn: (id: string) => api.delete(`/ai/conversations/${id}`),
mutationFn: (id: string) => aiApi.removeConversation(id),
onSuccess: () => queryClient.invalidateQueries({ queryKey: ['ai-conversations'] }),
})
const consultMutation = useMutation({
mutationFn: async (data: typeof consultForm) => {
const res = await api.post('/ai/consultation', data) as any
return res.data
return await aiApi.consult(data)
},
onSuccess: () => {
toast.success('已提交咨询请求,专业律师将尽快与您联系')
@@ -274,11 +270,11 @@ function ChatTab() {
saveTimerRef.current = setTimeout(async () => {
const title = `chat:${messages.find(m => m.role === 'user')?.content.slice(0, 30) || '新对话'}`
if (currentConvId) {
await api.put(`/ai/conversations/${currentConvId}`, { messages }).catch(() => {})
await aiApi.updateConversation(currentConvId, { messages }).catch(() => {})
} else {
const res = await api.post('/ai/conversations', { title, messages }) as any
if (res.data?.id) {
setCurrentConvId(res.data.id)
const res = await aiApi.createConversation({ title, messages }) as any
if (res?.id) {
setCurrentConvId(res.id)
queryClient.invalidateQueries({ queryKey: ['ai-conversations'] })
}
}
@@ -288,9 +284,9 @@ function ChatTab() {
const loadConversation = async (id: string) => {
try {
const res = await api.get(`/ai/conversations/${id}`) as any
if (res.data?.messages) {
setMessages(res.data.messages)
const res = await aiApi.conversation(id) as any
if (res?.messages) {
setMessages(res.messages)
setCurrentConvId(id)
setShowHistory(false)
}
@@ -679,8 +675,8 @@ function PredictTab() {
// 4. 获取违纪记录(列表接口未含明细,需调用专用接口)
try {
const res = await api.get(`/roster/${emp.id}/disciplinary`) as any
const records = res?.data || res || []
const res = await rosterApi.disciplinary(emp.id) as any
const records = res || []
if (Array.isArray(records) && records.length > 0) {
const typeMap: Record<string, string> = { LATE: '迟到', ABSENT: '旷工', INSUBORDINATION: '不服从管理', MISCONDUCT: '违纪', VIOLATE_POLICY: '违反制度', OTHER: '其他' }
const actionMap: Record<string, string> = { ORAL_WARNING: '口头警告', WRITTEN_WARNING: '书面警告', DEDUCTION: '扣款', DEMOTION: '降职', TERMINATION: '辞退' }
@@ -1365,10 +1361,8 @@ function ReviewTab() {
try {
const formData = new FormData()
formData.append('file', file)
const res = await api.post('/ai/review/upload', formData, {
headers: { 'Content-Type': 'multipart/form-data' },
}) as any
if (res.data?.text) {
const res = await aiApi.reviewUpload(formData) as any
if (res?.text) {
setContractText(res.data.text)
setFileName(file.name)
toast.success(`已提取文件内容(${res.data.text.length} 字)`)
@@ -1391,10 +1385,10 @@ function ReviewTab() {
setLoading(true)
setResult(null)
try {
const res = await api.post('/ai/review', { contractText }) as any
setResult(res.data)
const res = await aiApi.review(contractText) as any
setResult(res)
// 自动保存到历史
if (res.data && !res.data.error) {
if (res && !res.error) {
const title = contractText.slice(0, 30).replace(/\n/g, ' ')
saveMutation.mutate({ title, input: contractText, result: res.data.text || JSON.stringify(res.data) })
}
@@ -1421,7 +1415,7 @@ function ReviewTab() {
const handleSave = async () => {
if (!saveEmployeeId || !result) return
try {
await api.post('/ai/review/save', { employeeId: saveEmployeeId, type: 'REVIEW', input: contractText, result: result.text || JSON.stringify(result) })
await aiApi.reviewSave({ employeeId: saveEmployeeId, type: 'REVIEW', input: contractText, result: result.text || JSON.stringify(result) })
setShowSaveModal(false)
setSaveEmployeeId('')
toast.success('已保存到员工档案')
@@ -1584,10 +1578,10 @@ function CaseTab() {
setLoading(true)
setResult('')
try {
const res = await api.post('/ai/match-case', { scenario }) as any
setResult(res.data.result)
const res = await aiApi.matchCase(scenario) as any
setResult(res.result)
// 自动保存到历史
if (res.data?.result && !res.data.result.startsWith('出错了')) {
if (res?.result && !res.result.startsWith('出错了')) {
const title = scenario.slice(0, 30).replace(/\n/g, ' ')
saveMutation.mutate({ title, input: scenario, result: res.data.result })
}
@@ -1612,7 +1606,7 @@ function CaseTab() {
const handleSave = async () => {
if (!saveEmployeeId || !result) return
try {
await api.post('/ai/review/save', { employeeId: saveEmployeeId, type: 'CASE', input: scenario, result })
await aiApi.reviewSave({ employeeId: saveEmployeeId, type: 'CASE', input: scenario, result })
setShowSaveModal(false)
setSaveEmployeeId('')
toast.success('已保存到员工档案')
@@ -1625,7 +1619,7 @@ function CaseTab() {
if (!todoEmployeeId || !todoTitle) return
setCreatingTodo(true)
try {
await api.post('/ai/case-to-todo', {
await aiApi.caseToTodo({
employeeId: todoEmployeeId,
title: todoTitle,
description: result.slice(0, 500),
@@ -1761,14 +1755,13 @@ function KnowledgeTab() {
const { data: knowledgeList, isLoading } = useQuery<any[]>({
queryKey: ['rag-knowledge'],
queryFn: async () => {
const res = await api.get('/ai/rag/list') as any
return res.data
return await aiApi.ragList()
},
})
const addMutation = useMutation({
mutationFn: async (data: typeof newItem) => {
return await api.post('/ai/rag/add', data)
return await aiApi.ragAdd(data)
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['rag-knowledge'] })
@@ -1778,12 +1771,12 @@ function KnowledgeTab() {
})
const deleteMutation = useMutation({
mutationFn: (id: string) => api.delete(`/ai/rag/${id}`),
mutationFn: (id: string) => aiApi.ragRemove(id),
onSuccess: () => queryClient.invalidateQueries({ queryKey: ['rag-knowledge'] }),
})
const seedMutation = useMutation({
mutationFn: () => api.post('/ai/rag/seed'),
mutationFn: () => aiApi.ragSeed(),
onSuccess: () => queryClient.invalidateQueries({ queryKey: ['rag-knowledge'] }),
})
+22 -37
View File
@@ -2,8 +2,7 @@ import { useState, useRef } from 'react'
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
import { toast } from 'sonner'
import { CalendarCheck, CheckCircle, Clock, AlertCircle, Plus, Trash2, Calendar, Users, BarChart3, Plane, Upload, Download, X, Send, Loader2, CheckCheck } from 'lucide-react'
import api from '../lib/api'
import { employeeApi, rosterApi } from '../lib/api-services'
import { attendanceApi, employeeApi, rosterApi } from '../lib/api-services'
import { useAuthStore } from '../store/authStore'
import Card from '../components/ui/Card'
import Button from '../components/ui/Button'
@@ -110,16 +109,14 @@ function ConfirmTab() {
const params: any = { month }
if (filterDepartment) params.department = filterDepartment
if (filterStatus) params.status = filterStatus
const res = await api.get('/attendance', { params }) as any
return res.data
return await attendanceApi.list({ month, department: filterDepartment || undefined, status: filterStatus || undefined })
},
})
const { data: stats } = useQuery<any>({
queryKey: ['attendance-stats', month],
queryFn: async () => {
const res = await api.get(`/attendance/stats?month=${month}`) as any
return res.data
return await attendanceApi.stats(month)
},
})
@@ -131,15 +128,13 @@ function ConfirmTab() {
const { data: publishRecords } = useQuery<any[]>({
queryKey: ['attendance-publish-records'],
queryFn: async () => {
const res = await api.get('/attendance/publish-records') as any
return res.data
return await attendanceApi.publishRecords()
},
})
const publishMutation = useMutation({
mutationFn: async () => {
const res = await api.post('/attendance/publish', { month }) as any
return res.data
return await attendanceApi.publish(month)
},
onSuccess: () => {
toast.success(`${month}月考勤表已发布`)
@@ -150,8 +145,7 @@ function ConfirmTab() {
const cancelPublishMutation = useMutation({
mutationFn: async (id: string) => {
const res = await api.post(`/attendance/publish/${id}/cancel`) as any
return res.data
return await attendanceApi.cancelPublish(id)
},
onSuccess: () => {
toast.success('已取消发布')
@@ -162,8 +156,7 @@ function ConfirmTab() {
const batchConfirmMutation = useMutation({
mutationFn: async (params: { all?: boolean; ids?: string[] }) => {
const res = await api.post('/attendance/batch-confirm', { month, ...params }) as any
return res.data
return await attendanceApi.batchConfirm({ month, ...params })
},
onSuccess: (data: any) => {
toast.success(`已批量确认 ${data.count} 条考勤记录`)
@@ -176,8 +169,7 @@ function ConfirmTab() {
const singleConfirmMutation = useMutation({
mutationFn: async (id: string) => {
const res = await api.post('/attendance/confirm', { employeeId: list.find((i: any) => i.id === id)?.employeeId, month }) as any
return res.data
return await attendanceApi.confirm({ employeeId: list.find((i: any) => i.id === id)?.employeeId, month })
},
onSuccess: () => {
toast.success('已确认')
@@ -520,17 +512,16 @@ function ShiftsTab() {
const { data: shifts, isLoading } = useQuery<any>({
queryKey: ['shifts'],
queryFn: async () => {
const res = await api.get('/attendance/shifts') as any
return res.data
return await attendanceApi.shifts()
},
})
const saveMutation = useMutation({
mutationFn: async (data: any) => {
if (editShift) {
return api.put(`/attendance/shifts/${editShift.id}`, data)
return attendanceApi.saveShift(data, editShift.id)
}
return api.post('/attendance/shifts', data)
return attendanceApi.saveShift(data)
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['shifts'] })
@@ -541,7 +532,7 @@ function ShiftsTab() {
})
const deleteMutation = useMutation({
mutationFn: (id: string) => api.delete(`/attendance/shifts/${id}`),
mutationFn: (id: string) => attendanceApi.removeShift(id),
onSuccess: () => queryClient.invalidateQueries({ queryKey: ['shifts'] }),
})
@@ -637,29 +628,26 @@ function ScheduleTab() {
const { data: shifts } = useQuery<any>({
queryKey: ['shifts'],
queryFn: async () => {
const res = await api.get('/attendance/shifts') as any
return res.data
return await attendanceApi.shifts()
},
})
const { data: assignments, isLoading } = useQuery<any>({
queryKey: ['shift-assignments', date],
queryFn: async () => {
const res = await api.get(`/attendance/shift-assignments?date=${date}`) as any
return res.data
return await attendanceApi.shiftAssignments(date)
},
})
const { data: dailyData } = useQuery<any>({
queryKey: ['daily-attendance', date],
queryFn: async () => {
const res = await api.get(`/attendance/daily?date=${date}`) as any
return res.data
return await attendanceApi.daily(date)
},
})
const batchAssignMutation = useMutation({
mutationFn: (items: any[]) => api.post('/attendance/shift-assignments/batch', { items }),
mutationFn: (items: any[]) => attendanceApi.batchAssign(items),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['shift-assignments'] })
queryClient.invalidateQueries({ queryKey: ['daily-attendance'] })
@@ -671,7 +659,7 @@ function ScheduleTab() {
})
const deleteAssignmentMutation = useMutation({
mutationFn: (id: string) => api.delete(`/attendance/shift-assignments/${id}`),
mutationFn: (id: string) => attendanceApi.removeAssignment(id),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['shift-assignments'] })
queryClient.invalidateQueries({ queryKey: ['daily-attendance'] })
@@ -805,8 +793,7 @@ function DailyTab() {
const { data, isLoading } = useQuery<any>({
queryKey: ['daily-attendance', date],
queryFn: async () => {
const res = await api.get(`/attendance/daily?date=${date}`) as any
return res.data
return await attendanceApi.daily(date)
},
})
@@ -880,8 +867,7 @@ function MonthlyTab() {
const { data, isLoading } = useQuery<any>({
queryKey: ['monthly-report', month],
queryFn: async () => {
const res = await api.get(`/attendance/monthly-report?month=${month}`) as any
return res.data
return await attendanceApi.monthlyReport(month)
},
})
@@ -974,8 +960,7 @@ function LeavesTab() {
const { data: leaves, isLoading } = useQuery<any>({
queryKey: ['leave-records'],
queryFn: async () => {
const res = await api.get('/attendance/leaves') as any
return res.data
return await attendanceApi.leaves()
},
})
@@ -985,7 +970,7 @@ function LeavesTab() {
})
const createMutation = useMutation({
mutationFn: (data: any) => api.post('/attendance/leaves', data),
mutationFn: (data: any) => attendanceApi.createLeave(data),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['leave-records'] })
setShowAdd(false)
@@ -995,7 +980,7 @@ function LeavesTab() {
})
const deleteMutation = useMutation({
mutationFn: (id: string) => api.delete(`/attendance/leaves/${id}`),
mutationFn: (id: string) => attendanceApi.removeLeave(id),
onSuccess: () => queryClient.invalidateQueries({ queryKey: ['leave-records'] }),
})
+8 -10
View File
@@ -1,7 +1,7 @@
import { useState } from 'react'
import { useQuery } from '@tanstack/react-query'
import { ScrollText, Search, Filter } from 'lucide-react'
import api from '../lib/api'
import { auditApi } from '../lib/api-services'
import Card from '../components/ui/Card'
import Button from '../components/ui/Button'
import EmptyState from '../components/ui/EmptyState'
@@ -128,21 +128,19 @@ export default function AuditLog() {
const { data, isLoading } = useQuery<any>({
queryKey: ['audit-logs', page, pageSize, action, entity, dateFrom, dateTo],
queryFn: async () => {
const params = new URLSearchParams({ page: String(page), pageSize: String(pageSize) })
if (action) params.set('action', action)
if (entity) params.set('entity', entity)
if (dateFrom) params.set('dateFrom', dateFrom)
if (dateTo) params.set('dateTo', dateTo)
const res = await api.get(`/audit?${params}`) as any
return res.data
const p: any = { page, pageSize }
if (action) p.action = action
if (entity) p.entity = entity
if (dateFrom) p.dateFrom = dateFrom
if (dateTo) p.dateTo = dateTo
return await auditApi.list(p)
},
})
const { data: stats } = useQuery<any>({
queryKey: ['audit-stats'],
queryFn: async () => {
const res = await api.get('/audit/stats') as any
return res.data
return await auditApi.stats()
},
})
+5 -11
View File
@@ -3,7 +3,7 @@ import { toast } from 'sonner'
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
import { Link } from 'react-router-dom'
import { CalendarDays, Plus, Trash2, ChevronLeft, ChevronRight, X, MapPin, User } from 'lucide-react'
import api from '../lib/api'
import { dashboardApi, calendarApi } from '../lib/api-services'
import Card from '../components/ui/Card'
import Button from '../components/ui/Button'
import { Input, Label } from '../components/ui/Input'
@@ -66,22 +66,16 @@ export default function Calendar() {
const { data: calendarData } = useQuery<any>({
queryKey: ['calendar', calendarMonth],
queryFn: async () => {
const res = await api.get(`/dashboard/calendar?month=${calendarMonth}`) as any
return res.data
},
queryFn: () => dashboardApi.calendar(calendarMonth),
})
const { data: customEvents } = useQuery<any[]>({
queryKey: ['custom-events', calendarMonth],
queryFn: async () => {
const res = await api.get(`/calendar?month=${calendarMonth}`) as any
return res.data
},
queryFn: () => calendarApi.events(calendarMonth),
})
const createEventMutation = useMutation({
mutationFn: (data: any) => api.post('/calendar', data),
mutationFn: (data: any) => calendarApi.createEvent(data),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['custom-events'] })
queryClient.invalidateQueries({ queryKey: ['calendar'] })
@@ -93,7 +87,7 @@ export default function Calendar() {
})
const deleteEventMutation = useMutation({
mutationFn: (id: string) => api.delete(`/calendar/${id}`),
mutationFn: (id: string) => calendarApi.removeEvent(id),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['custom-events'] })
queryClient.invalidateQueries({ queryKey: ['calendar'] })
+4 -5
View File
@@ -2,7 +2,7 @@ import { useState, useRef } from 'react'
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
import { toast } from 'sonner'
import { Building2, Upload, Trash2, FileText, AlertCircle, Calendar } from 'lucide-react'
import api from '../lib/api'
import { companyFilesApi } from '../lib/api-services'
import Card from '../components/ui/Card'
import Button from '../components/ui/Button'
import { Input, Label, Select } from '../components/ui/Input'
@@ -29,13 +29,12 @@ export default function CompanyFiles() {
const { data: files, isLoading } = useQuery<any[]>({
queryKey: ['company-files', filterType],
queryFn: async () => {
const res = await api.get('/company-files', { params: filterType ? { fileType: filterType } : {} }) as any
return res.data || []
return await companyFilesApi.list(filterType ? { fileType: filterType } : {})
},
})
const addMutation = useMutation({
mutationFn: (data: any) => api.post('/company-files', data),
mutationFn: (data: any) => companyFilesApi.add(data),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['company-files'] })
toast.success('文件上传成功')
@@ -46,7 +45,7 @@ export default function CompanyFiles() {
})
const deleteMutation = useMutation({
mutationFn: (id: string) => api.delete(`/company-files/${id}`),
mutationFn: (id: string) => companyFilesApi.remove(id),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['company-files'] })
toast.success('已删除')
+1 -2
View File
@@ -1,7 +1,6 @@
import { useState, useMemo } from 'react'
import { useQuery } from '@tanstack/react-query'
import { Calculator, Info, AlertCircle } from 'lucide-react'
import api from '../lib/api'
import { rosterApi } from '../lib/api-services'
import Card from '../components/ui/Card'
import Button from '../components/ui/Button'
@@ -24,7 +23,7 @@ function useEmployees() {
return useQuery<EmployeeOption[]>({
queryKey: ['roster-for-compensation'],
queryFn: async () => {
const res = await api.get('/roster') as any
const res = await rosterApi.list({} as any) as any
return res.data
},
})
+7 -10
View File
@@ -2,8 +2,7 @@ import { useState, useRef } from 'react'
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
import { Plus, Search, Paperclip, Trash2, X, FileText, Download, Eye } from 'lucide-react'
import { toast } from 'sonner'
import api from '../lib/api'
import { rosterApi } from '../lib/api-services'
import { rosterApi, employeeApi, attachmentApi } from '../lib/api-services'
import Card from '../components/ui/Card'
import Button from '../components/ui/Button'
import { Input, Label, Select } from '../components/ui/Input'
@@ -49,13 +48,13 @@ export default function Contracts() {
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
const res = await rosterApi.list({ search, page, pageSize, department: filterDepartment || undefined, contractStatus: filterContractStatus || undefined } as any) as any
return res
},
})
const addMutation = useMutation({
mutationFn: (data: any) => api.post('/employees', data),
mutationFn: (data: any) => employeeApi.create(data),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['employees'] })
queryClient.invalidateQueries({ queryKey: ['roster'] })
@@ -384,26 +383,24 @@ function EmployeeDetailDrawer({ employeeId, onClose }: { employeeId: string; onC
const { data: employee } = useQuery<any>({
queryKey: ['employee-detail', employeeId],
queryFn: async () => {
const res = await api.get(`/employees/${employeeId}`) as any
return res.data
return await employeeApi.detail(employeeId)
},
})
const { data: attachments } = useQuery<any[]>({
queryKey: ['employee-attachments', employeeId],
queryFn: async () => {
const res = await api.get(`/attachments/${employeeId}`) as any
return res.data
return await attachmentApi.list(employeeId)
},
})
const addAttachmentMutation = useMutation({
mutationFn: (data: any) => api.post('/attachments', data),
mutationFn: (data: any) => attachmentApi.add(data),
onSuccess: () => queryClient.invalidateQueries({ queryKey: ['employee-attachments', employeeId] }),
})
const deleteAttachmentMutation = useMutation({
mutationFn: (id: string) => api.delete(`/attachments/${id}`),
mutationFn: (id: string) => attachmentApi.remove(id),
onSuccess: () => queryClient.invalidateQueries({ queryKey: ['employee-attachments', employeeId] }),
})
+12 -18
View File
@@ -4,8 +4,8 @@ import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
import { Link } from 'react-router-dom'
import { PieChart, Pie, Cell, ResponsiveContainer, Tooltip, RadialBarChart, RadialBar, PolarAngleAxis } from 'recharts'
import { Users, AlertTriangle, CheckSquare, DollarSign, ArrowRight, RefreshCw, FileText, Calendar, TrendingUp, Briefcase, Calculator, Wallet, Building2, Receipt, Check, X, Clock, LayoutDashboard, ListTodo, ShieldAlert, UserPlus, AlertCircle, Download, ChevronRight, TrendingDown, ShieldCheck, Lightbulb, BookOpen, Sparkles, Repeat, XCircle, Loader2 } from 'lucide-react'
import api from '../lib/api'
import { dashboardApi, rosterApi } from '../lib/api-services'
import { dashboardApi, rosterApi, workProcessApi } from '../lib/api-services'
import { post as apiPost, patch as apiPatch } from '../lib/api-services-raw'
import { useAuthStore } from '../store/authStore'
import Card from '../components/ui/Card'
import Button from '../components/ui/Button'
@@ -50,10 +50,7 @@ export default function Dashboard() {
const [dismissedExpiring, setDismissedExpiring] = useState(false)
const { data, isLoading, refetch, isFetching } = useQuery<DashboardData>({
queryKey: ['dashboard'],
queryFn: async () => {
const res = await api.get('/dashboard') as any
return res.data
},
queryFn: () => dashboardApi.data(),
})
const { data: expiringContracts } = useQuery<any>({
@@ -65,10 +62,7 @@ export default function Dashboard() {
const { data: costAnalysis } = useQuery<any>({
queryKey: ['cost-analysis', currentMonth],
queryFn: async () => {
const res = await api.get(`/dashboard/cost-analysis?month=${currentMonth}`) as any
return res.data
},
queryFn: () => dashboardApi.costAnalysis(currentMonth),
})
const { data: complianceScore } = useQuery<any>({
@@ -82,17 +76,17 @@ export default function Dashboard() {
})
const resolveMutation = useMutation({
mutationFn: (id: string) => api.patch(`/dashboard/todos/${id}/resolve`),
mutationFn: (id: string) => apiPatch(`/dashboard/todos/${id}/resolve`),
onSuccess: () => queryClient.invalidateQueries({ queryKey: ['dashboard'] }),
})
const ignoreMutation = useMutation({
mutationFn: (id: string) => api.patch(`/dashboard/todos/${id}/ignore`),
mutationFn: (id: string) => apiPatch(`/dashboard/todos/${id}/ignore`),
onSuccess: () => queryClient.invalidateQueries({ queryKey: ['dashboard'] }),
})
const batchResolveMutation = useMutation({
mutationFn: (ids: string[]) => api.patch('/dashboard/todos/batch-resolve', { ids }),
mutationFn: (ids: string[]) => apiPatch('/dashboard/todos/batch-resolve', { ids }),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['dashboard'] })
setSelectedIds(new Set())
@@ -100,7 +94,7 @@ export default function Dashboard() {
})
const batchIgnoreMutation = useMutation({
mutationFn: (ids: string[]) => api.patch('/dashboard/todos/batch-ignore', { ids }),
mutationFn: (ids: string[]) => apiPatch('/dashboard/todos/batch-ignore', { ids }),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['dashboard'] })
setSelectedIds(new Set())
@@ -1066,7 +1060,7 @@ export default function Dashboard() {
<div className="flex items-center gap-1">
<button
onClick={() => {
api.post('/work-processes', {
workProcessApi.create({
type: 'RENEW',
title: `合同续签-${c.employeeName}`,
employeeId: c.employeeId,
@@ -1075,7 +1069,7 @@ export default function Dashboard() {
}).then(() => {
toast.success(`已创建 ${c.employeeName} 的续签流程`)
setShowExpiringModal(false)
}).catch((err) => {
}).catch((err: any) => {
toast.error(err?.response?.data?.error?.message || '创建失败')
})
}}
@@ -1085,7 +1079,7 @@ export default function Dashboard() {
</button>
<button
onClick={() => {
api.post('/work-processes', {
workProcessApi.create({
type: 'TERMINATE',
title: `合同终止-${c.employeeName}`,
employeeId: c.employeeId,
@@ -1094,7 +1088,7 @@ export default function Dashboard() {
}).then(() => {
toast.success(`已创建 ${c.employeeName} 的终止流程`)
setShowExpiringModal(false)
}).catch((err) => {
}).catch((err: any) => {
toast.error(err?.response?.data?.error?.message || '创建失败')
})
}}
+3 -5
View File
@@ -1,7 +1,7 @@
import { useState } from 'react'
import { useQuery } from '@tanstack/react-query'
import { ShieldCheck, FileText, AlertCircle, CheckCircle, XCircle } from 'lucide-react'
import api from '../lib/api'
import { evidenceApi } from '../lib/api-services'
import Card from '../components/ui/Card'
import EmptyState from '../components/ui/EmptyState'
import Pagination from '../components/ui/Pagination'
@@ -19,8 +19,7 @@ export default function Evidence() {
queryFn: async () => {
const params: any = { page, pageSize }
params.category = refType || 'ALL'
const res = await api.get('/evidence', { params }) as any
return res.data
return await evidenceApi.list(params)
},
})
const list = listData?.records || []
@@ -29,8 +28,7 @@ export default function Evidence() {
const { data: verifyResult, refetch: verifyAll } = useQuery<any>({
queryKey: ['evidence-verify-all'],
queryFn: async () => {
const res = await api.get('/evidence/verify-all') as any
return res.data
return await evidenceApi.verifyAll()
},
enabled: false,
})
+37 -48
View File
@@ -6,8 +6,7 @@ import * as XLSX from 'xlsx'
import { Calculator, AlertCircle, Info, Check, Upload, Layers, Settings as SettingsIcon, Archive, Plus, Trash2, AlertTriangle, Download, FileText, X, ChevronLeft, Wallet, LayoutTemplate, Clock, Receipt, Users, TrendingDown, TrendingUp, BadgeCheck } from 'lucide-react'
import { Stepper, type Step } from '../components/ui/Stepper'
import { InlineAlert } from '../components/ui/InlineAlert'
import api from '../lib/api'
import { rosterApi } from '../lib/api-services'
import { payrollApi, rosterApi, employeeApi } from '../lib/api-services'
import { useAuthStore } from '../store/authStore'
import Card from '../components/ui/Card'
import Button from '../components/ui/Button'
@@ -162,8 +161,7 @@ function BatchManager() {
const { data: checkResult } = useQuery<any>({
queryKey: ['batch-check', month],
queryFn: async () => {
const res = await api.get('/payroll2/batches/check', { params: { month } }) as any
return res.data
return await payrollApi.batchCheck(month)
},
})
@@ -176,22 +174,20 @@ function BatchManager() {
if (monthTo) params.monthTo = monthTo
if (filterStatus) params.status = filterStatus
if (filterType) params.type = filterType
const res = await api.get('/payroll2/batches', { params }) as any
return res.data
return await payrollApi.batches(params)
},
})
const { data: archivedBatches } = useQuery<any[]>({
queryKey: ['archived-batches'],
queryFn: async () => {
const res = await api.get('/payroll2/batches/archived/list') as any
return res.data
return await payrollApi.archivedBatches()
},
enabled: createMode === 'copy_batch',
})
const createMutation = useMutation({
mutationFn: (data: any) => api.post('/payroll2/batches', data),
mutationFn: (data: any) => payrollApi.createBatch(data),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['batches'] })
queryClient.invalidateQueries({ queryKey: ['batch-check'] })
@@ -202,7 +198,7 @@ function BatchManager() {
})
const deleteBatchMutation = useMutation({
mutationFn: (batchId: string) => api.delete(`/payroll2/batches/${batchId}`),
mutationFn: (batchId: string) => payrollApi.removeBatch(batchId),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['batches'] })
queryClient.invalidateQueries({ queryKey: ['batch-check'] })
@@ -213,7 +209,7 @@ function BatchManager() {
const renameBatchMutation = useMutation({
mutationFn: ({ batchId, name }: { batchId: string; name: string }) =>
api.put(`/payroll2/batches/${batchId}/name`, { name }),
payrollApi.renameBatch(batchId, name),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['batches'] })
toast.success('批次名称已更新')
@@ -222,7 +218,7 @@ function BatchManager() {
})
const unarchiveBatchMutation = useMutation({
mutationFn: (batchId: string) => api.post(`/payroll2/batches/${batchId}/unarchive`),
mutationFn: (batchId: string) => payrollApi.unarchiveBatch(batchId),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['batches'] })
queryClient.invalidateQueries({ queryKey: ['batch-check'] })
@@ -503,14 +499,13 @@ function BatchDetail({ batchId, onBack }: { batchId: string; onBack: () => void
const { data: batch, isLoading } = useQuery<any>({
queryKey: ['batch-detail', batchId],
queryFn: async () => {
const res = await api.get(`/payroll2/batches/${batchId}`) as any
return res.data
return await payrollApi.batchDetail(batchId)
},
})
const updateEntryMutation = useMutation({
mutationFn: ({ employeeId, data }: { employeeId: string; data: any }) =>
api.put(`/payroll2/batches/${batchId}/entries/${employeeId}`, data),
payrollApi.updateBatchEntry(batchId, employeeId, data),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['batch-detail'] })
queryClient.invalidateQueries({ queryKey: ['batches'] })
@@ -518,7 +513,7 @@ function BatchDetail({ batchId, onBack }: { batchId: string; onBack: () => void
})
const removeEmployeeMutation = useMutation({
mutationFn: (employeeId: string) => api.delete(`/payroll2/batches/${batchId}/employees/${employeeId}`),
mutationFn: (employeeId: string) => payrollApi.removeBatchEmployee(batchId, employeeId),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['batch-detail'] })
queryClient.invalidateQueries({ queryKey: ['batches'] })
@@ -526,7 +521,7 @@ function BatchDetail({ batchId, onBack }: { batchId: string; onBack: () => void
})
const archiveMutation = useMutation({
mutationFn: () => api.post(`/payroll2/batches/${batchId}/archive`),
mutationFn: () => payrollApi.archiveBatch(batchId),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['batch-detail'] })
queryClient.invalidateQueries({ queryKey: ['batches'] })
@@ -537,7 +532,7 @@ function BatchDetail({ batchId, onBack }: { batchId: string; onBack: () => void
})
const unarchiveMutation = useMutation({
mutationFn: () => api.post(`/payroll2/batches/${batchId}/unarchive`),
mutationFn: () => payrollApi.unarchiveBatch(batchId),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['batch-detail'] })
queryClient.invalidateQueries({ queryKey: ['batches'] })
@@ -549,7 +544,7 @@ function BatchDetail({ batchId, onBack }: { batchId: string; onBack: () => void
})
const publishPayslipMutation = useMutation({
mutationFn: () => api.post(`/payroll2/batches/${batchId}/publish`),
mutationFn: () => payrollApi.publishPayslip(batchId),
onSuccess: (res: any) => {
toast.success(`已发布 ${res.data?.published || 0} 条工资条`)
queryClient.invalidateQueries({ queryKey: ['batch-detail'] })
@@ -560,7 +555,7 @@ function BatchDetail({ batchId, onBack }: { batchId: string; onBack: () => void
const [showScheduleModal, setShowScheduleModal] = useState(false)
const [scheduleDate, setScheduleDate] = useState('')
const schedulePayslipMutation = useMutation({
mutationFn: () => api.post(`/payroll2/batches/${batchId}/schedule`, { scheduledAt: scheduleDate }),
mutationFn: () => payrollApi.schedulePayslip(batchId, scheduleDate),
onSuccess: (res: any) => {
toast.success(`已设定定时发送 ${res.data?.scheduled || 0} 条工资条`)
setShowScheduleModal(false)
@@ -570,7 +565,7 @@ function BatchDetail({ batchId, onBack }: { batchId: string; onBack: () => void
})
const importOvertimeMutation = useMutation({
mutationFn: () => api.post(`/payroll/overtime/import-to-batch/${batchId}`),
mutationFn: () => payrollApi.importOvertimeToBatch(batchId),
onSuccess: (res: any) => {
queryClient.invalidateQueries({ queryKey: ['batch-detail'] })
queryClient.invalidateQueries({ queryKey: ['batches'] })
@@ -615,7 +610,7 @@ function BatchDetail({ batchId, onBack }: { batchId: string; onBack: () => void
})
const deleteBatchMutation = useMutation({
mutationFn: () => api.delete(`/payroll2/batches/${batchId}`),
mutationFn: () => payrollApi.removeBatch(batchId),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['batches'] })
queryClient.invalidateQueries({ queryKey: ['batch-check'] })
@@ -845,8 +840,8 @@ function BatchDetail({ batchId, onBack }: { batchId: string; onBack: () => void
size="sm"
onClick={async () => {
try {
const res = await api.get(`/payroll/batch/${batchId}/summary`) as any
const { departments, grandTotal } = res.data
const res = await payrollApi.batchSummary(batchId) as any
const { departments, grandTotal } = res
const headers = ['部门', '人数', '应发合计', '实发合计', '个人社保', '单位社保', '个人公积金', '单位公积金', '个税合计']
const rows = departments.map((d: any) => [
d.department, d.headcount, d.totalPay.toFixed(2), d.totalNetPay.toFixed(2),
@@ -874,8 +869,8 @@ function BatchDetail({ batchId, onBack }: { batchId: string; onBack: () => void
size="sm"
onClick={async () => {
try {
const res = await api.get(`/payroll/batch/${batchId}/detail`) as any
const { details } = res.data
const res = await payrollApi.batchDetailExport(batchId) as any
const { details } = res
const headers = ['姓名', '部门', '基本工资', '岗位工资', '绩效工资', '工龄工资', '加班费', '交通补贴', '餐补', '住房补贴', '通讯补贴', '其他津贴', '奖金', '扣款', '其他扣款', '个人社保', '个人公积金', '个税', '应发合计', '实发工资']
const rows = details.map((d: any) => [
d.name, d.department,
@@ -1161,13 +1156,12 @@ function AddEmployeeToBatch({ batchId, onClose }: { batchId: string; onClose: ()
const { data: employees } = useQuery<any>({
queryKey: ['employees-for-batch'],
queryFn: async () => {
const res = await api.get('/employees', { params: { pageSize: 100 } }) as any
return res.data
return await employeeApi.paged({ pageSize: 100 })
},
})
const addMutation = useMutation({
mutationFn: (employeeIds: string[]) => api.post(`/payroll2/batches/${batchId}/employees`, { employeeIds }),
mutationFn: (employeeIds: string[]) => payrollApi.addBatchEmployees(batchId, employeeIds),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['batch-detail'] })
queryClient.invalidateQueries({ queryKey: ['batches'] })
@@ -1225,13 +1219,12 @@ function TemplateManager() {
const { data: items, isLoading } = useQuery<any[]>({
queryKey: ['payslip-template'],
queryFn: async () => {
const res = await api.get('/payroll2/template') as any
return res.data
return await payrollApi.template()
},
})
const deleteMutation = useMutation({
mutationFn: (id: string) => api.delete(`/payroll2/template/${id}`),
mutationFn: (id: string) => payrollApi.removeTemplateItem(id),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['payslip-template'] })
toast.success('已删除')
@@ -1240,7 +1233,7 @@ function TemplateManager() {
})
const createMutation = useMutation({
mutationFn: (data: any) => api.post('/payroll2/template', data),
mutationFn: (data: any) => payrollApi.createTemplateItem(data),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['payslip-template'] })
setShowForm(false)
@@ -1250,7 +1243,7 @@ function TemplateManager() {
})
const updateMutation = useMutation({
mutationFn: ({ id, data }: { id: string; data: any }) => api.put(`/payroll2/template/${id}`, data),
mutationFn: ({ id, data }: { id: string; data: any }) => payrollApi.updateTemplateItem(id, data),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['payslip-template'] })
setShowForm(false)
@@ -1491,13 +1484,12 @@ function OvertimeCalculator() {
const { data: config, isLoading: configLoading } = useQuery<any>({
queryKey: ['overtime-config'],
queryFn: async () => {
const res = await api.get('/payroll/overtime/config') as any
return res.data
return await payrollApi.overtimeConfig()
},
})
const saveConfigMutation = useMutation({
mutationFn: (data: any) => api.post('/payroll/overtime/config', data),
mutationFn: (data: any) => payrollApi.saveOvertimeConfig(data),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['overtime-config'] })
},
@@ -1507,8 +1499,7 @@ function OvertimeCalculator() {
const { data: employees } = useQuery<{ items: { id: string; name: string; department: string }[] }>({
queryKey: ['employees-for-overtime'],
queryFn: async () => {
const res = await api.get('/employees', { params: { pageSize: 100 } }) as any
return res.data
return await employeeApi.paged({ pageSize: 100 })
},
})
@@ -1516,14 +1507,13 @@ function OvertimeCalculator() {
const { data: overtimeRecords, refetch } = useQuery<any[]>({
queryKey: ['overtime-records', month],
queryFn: async () => {
const res = await api.get('/payroll/overtime', { params: { month } }) as any
return res.data
return await payrollApi.overtimeRecords({ month })
},
enabled: step === 3,
})
const batchImportMutation = useMutation({
mutationFn: (data: any[]) => api.post('/payroll/overtime/batch', data),
mutationFn: (data: any[]) => payrollApi.batchImportOvertime(data),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['overtime-records'] })
setPreviewData([])
@@ -1534,7 +1524,7 @@ function OvertimeCalculator() {
// 更新单条加班记录
const updateOvertimeMutation = useMutation({
mutationFn: ({ id, data }: { id: string; data: any }) =>
api.put(`/payroll/overtime/${id}`, data),
payrollApi.updateOvertime(id, data),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['overtime-records'] })
setEditingId(null)
@@ -1939,18 +1929,17 @@ function PayslipManager() {
const { data: payslips, isLoading } = useQuery<any[]>({
queryKey: ['payslips', month],
queryFn: async () => {
const res = await api.get('/payroll/payslip', { params: { month } }) as any
return res.data
return await payrollApi.payslips({ month })
},
})
const deleteMutation = useMutation({
mutationFn: (id: string) => api.delete(`/payroll/payslip/${id}`),
mutationFn: (id: string) => payrollApi.removePayslip(id),
onSuccess: () => queryClient.invalidateQueries({ queryKey: ['payslips'] }),
})
const generateFromBatchMutation = useMutation({
mutationFn: (data: any) => api.post('/payroll2/payslips/generate', data),
mutationFn: (data: any) => payrollApi.generatePayslips(data?.month || data),
onSuccess: (res: any) => {
queryClient.invalidateQueries({ queryKey: ['payslips'] })
queryClient.invalidateQueries({ queryKey: ['dashboard'] })
@@ -1960,7 +1949,7 @@ function PayslipManager() {
})
const taxPreviewMutation = useMutation({
mutationFn: (data: any) => api.post('/payroll/tax-preview', data),
mutationFn: (data: any) => payrollApi.taxPreview(data),
onSuccess: (res: any) => {
setTaxResult(res.data)
setShowTaxPreview(true)
+6 -7
View File
@@ -2,7 +2,8 @@ import { useState } from 'react'
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
import { toast } from 'sonner'
import { Bell, CheckCircle, AlertCircle, Send, Settings as SettingsIcon, X } from 'lucide-react'
import api from '../lib/api'
import { notificationsApi } from '../lib/api-services'
import { post as apiPost } from '../lib/api-services-raw'
import Card from '../components/ui/Card'
import Button from '../components/ui/Button'
import EmptyState from '../components/ui/EmptyState'
@@ -39,21 +40,19 @@ export default function Notifications() {
const { data, isLoading } = useQuery<any>({
queryKey: ['notification-logs', page, pageSize],
queryFn: async () => {
const res = await api.get(`/notifications/logs?page=${page}&pageSize=${pageSize}`) as any
return res.data
return await notificationsApi.logs({ page, pageSize })
},
})
const { data: settings } = useQuery<any>({
queryKey: ['notification-settings'],
queryFn: async () => {
const res = await api.get('/notifications/settings') as any
return res.data
return await notificationsApi.settings()
},
})
const checkContractsMutation = useMutation({
mutationFn: () => api.post('/notifications/check-contracts'),
mutationFn: () => apiPost('/notifications/check-contracts'),
onSuccess: () => {
toast.success('合同到期检查已触发')
queryClient.invalidateQueries({ queryKey: ['notification-logs'] })
@@ -174,7 +173,7 @@ function SettingsModal({ settings, onClose, onSuccess }: { settings: any; onClos
})
const saveMutation = useMutation({
mutationFn: () => api.put('/notifications/settings', form),
mutationFn: () => notificationsApi.updateSettings(form),
onSuccess: () => { toast.success('通知设置已保存'); onSuccess() },
onError: () => toast.error('保存失败'),
})
+6 -8
View File
@@ -2,7 +2,7 @@ import { useState } from 'react'
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
import { toast } from 'sonner'
import { FileText, Plus, ChevronRight, CheckCircle, Clock, X } from 'lucide-react'
import api from '../lib/api'
import { policiesApi } from '../lib/api-services'
import Card from '../components/ui/Card'
import Button from '../components/ui/Button'
import EmptyState from '../components/ui/EmptyState'
@@ -30,21 +30,20 @@ export default function Policies() {
const { data: listData, isLoading } = useQuery<any>({
queryKey: ['policies', page, pageSize],
queryFn: async () => {
const res = await api.get('/policies', { params: { page, pageSize } }) as any
return res.data
return await policiesApi.list({ page, pageSize })
},
})
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 }),
mutationFn: ({ id, step, note }: { id: string; step: number; note?: string }) => policiesApi.advanceStep(id, step, note),
onSuccess: () => {
toast.success('流程步骤已推进')
queryClient.invalidateQueries({ queryKey: ['policies'] })
// 刷新选中制度详情
if (selectedPolicy) {
api.get(`/policies/${selectedPolicy.id}`).then((res: any) => setSelectedPolicy(res.data))
policiesApi.detail(selectedPolicy.id).then((res: any) => setSelectedPolicy(res))
}
},
onError: (err: any) => toast.error(err?.response?.data?.error?.message || '操作失败'),
@@ -196,7 +195,7 @@ function CreatePolicyModal({ onClose, onSuccess }: { onClose: () => void; onSucc
const [type, setType] = useState('RULES')
const createMutation = useMutation({
mutationFn: () => api.post('/policies', { title, content, type }),
mutationFn: () => policiesApi.create({ title, content, type }),
onSuccess: () => { toast.success('制度已创建'); onSuccess() },
onError: (err: any) => toast.error(err?.response?.data?.error?.message || '创建失败'),
})
@@ -243,8 +242,7 @@ function ReadStats({ policyId }: { policyId: string }) {
const { data, isLoading } = useQuery<any>({
queryKey: ['policy-read-stats', policyId],
queryFn: async () => {
const res = await api.get(`/policies/${policyId}/read-stats`) as any
return res.data
return await policiesApi.readStats(policyId)
},
})
+14 -15
View File
@@ -4,7 +4,7 @@ import { toast } from 'sonner'
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
import { useConfirm } from '../hooks/useConfirm'
import { Users, FileText, AlertTriangle, Calendar, TrendingUp, Scale, X, Plus, Check, UserX, UserPlus, DollarSign, Building2, RotateCcw, History, Upload, Wallet, Download } from 'lucide-react'
import api from '../lib/api'
import { rosterApi, employeeApi, terminationApi } from '../lib/api-services'
import { useAuthStore } from '../store/authStore'
import { useDebouncedValue } from '../hooks/useDebouncedValue'
import Card from '../components/ui/Card'
@@ -58,7 +58,7 @@ export default function Roster() {
if (filterStatus === 'PROBATION') params.status = 'ACTIVE'
if (filterContractStatus) params.contractStatus = filterContractStatus
if (filterDepartment) params.department = filterDepartment
const res = await api.get('/roster', { params }) as any
const res = await rosterApi.list({ search: debouncedSearch, page, pageSize, status: filterStatus === 'PROBATION' ? 'ACTIVE' : filterStatus || undefined, contractStatus: filterContractStatus || undefined, department: filterDepartment || undefined } as any) as any
return res
},
})
@@ -83,7 +83,7 @@ export default function Roster() {
}, [employeeParam, debouncedSearch, isLoading, employees, selectedId, setSearchParams])
const addMutation = useMutation({
mutationFn: (data: any) => api.post('/employees', data),
mutationFn: (data: any) => employeeApi.create(data),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['roster'] })
queryClient.invalidateQueries({ queryKey: ['dashboard'] })
@@ -93,7 +93,7 @@ export default function Roster() {
})
const resignMutation = useMutation({
mutationFn: (data: any) => api.post('/termination/draft', {
mutationFn: (data: any) => terminationApi.createDraft({
employeeId: data.employeeId,
type: 'RESIGNATION',
reason: 'RESIGNATION',
@@ -113,7 +113,7 @@ export default function Roster() {
})
const revokeMutation = useMutation({
mutationFn: (recordId: string) => api.delete(`/termination/${recordId}/revoke`),
mutationFn: (recordId: string) => terminationApi.revoke(recordId),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['roster'] })
queryClient.invalidateQueries({ queryKey: ['dashboard'] })
@@ -122,7 +122,7 @@ export default function Roster() {
})
const rehireMutation = useMutation({
mutationFn: (data: any) => api.post(`/employees/${rehireEmployee?.id}/rehire`, data),
mutationFn: (data: any) => employeeApi.rehire(rehireEmployee?.id, data),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['roster'] })
queryClient.invalidateQueries({ queryKey: ['dashboard'] })
@@ -133,7 +133,7 @@ export default function Roster() {
})
const salaryChangeMutation = useMutation({
mutationFn: (data: any) => api.post(`/roster/${salaryEmployee?.id}/salary-change`, data),
mutationFn: (data: any) => rosterApi.salaryChange(salaryEmployee?.id, data),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['roster'] })
queryClient.invalidateQueries({ queryKey: ['dashboard'] })
@@ -144,7 +144,7 @@ export default function Roster() {
})
const deptChangeMutation = useMutation({
mutationFn: (data: any) => api.post(`/roster/${deptEmployee?.id}/department-change`, data),
mutationFn: (data: any) => rosterApi.departmentChange(deptEmployee?.id, data),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['roster'] })
queryClient.invalidateQueries({ queryKey: ['dashboard'] })
@@ -155,7 +155,7 @@ export default function Roster() {
})
const batchRenewMutation = useMutation({
mutationFn: (data: { contractIds: string[]; years: number }) => api.post('/employees/contracts/batch-renew', data),
mutationFn: (data: { contractIds: string[]; years: number }) => employeeApi.batchRenew(data),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['roster'] })
queryClient.invalidateQueries({ queryKey: ['dashboard'] })
@@ -167,7 +167,7 @@ export default function Roster() {
})
const previewRenewMutation = useMutation({
mutationFn: (contractIds: string[]) => api.post('/employees/contracts/preview-renew', { contractIds }),
mutationFn: (contractIds: string[]) => employeeApi.previewRenew(contractIds),
onSuccess: (data: any) => {
setPreviewData(data.data)
},
@@ -175,7 +175,7 @@ export default function Roster() {
const previewTerminateMutation = useMutation({
mutationFn: (items: Array<{ employeeId: string; reason: string; terminationDate: string }>) =>
api.post('/termination/batch/preview', { items }),
terminationApi.batchPreview(items),
onSuccess: (data: any) => {
setTerminatePreviewData(data.data)
},
@@ -184,12 +184,12 @@ export default function Roster() {
const batchTerminateMutation = useMutation({
mutationFn: async (items: Array<{ employeeId: string; reason: string; terminationDate: string }>) => {
const results = await Promise.all(
items.map(item => api.post('/termination/draft', {
items.map(item => terminationApi.createDraft({
employeeId: item.employeeId,
type: 'TERMINATION',
reason: item.reason,
terminationDate: item.terminationDate,
}).catch(err => ({ error: err, employeeId: item.employeeId })))
}).catch((err: any) => ({ error: err, employeeId: item.employeeId })))
)
return results
},
@@ -258,8 +258,7 @@ export default function Roster() {
const { data: departmentList } = useQuery<string[]>({
queryKey: ['roster-departments'],
queryFn: async () => {
const res = await api.get('/roster/departments') as any
return res.data || []
return await rosterApi.departments()
},
})
+2 -3
View File
@@ -9,7 +9,7 @@ import {
import Card from '../components/ui/Card'
import { Select } from '../components/ui/Input'
import { InlineAlert } from '../components/ui/InlineAlert'
import api from '../lib/api'
import { salaryDashboardApi } from '../lib/api-services'
/** 金额格式化 */
const fmt = (n: number) => (n || 0).toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })
@@ -21,8 +21,7 @@ export default function SalaryDashboard() {
const { data, isLoading } = useQuery<any>({
queryKey: ['salary-dashboard', year],
queryFn: async () => {
const res = await api.get('/salary/dashboard', { params: { year } }) as any
return res.data
return await salaryDashboardApi.data(Number(year))
},
})
+18 -23
View File
@@ -2,7 +2,8 @@ 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 api from '../lib/api'
import { settingsApi, notificationsApi } from '../lib/api-services'
import { post as apiPost, patch as apiPatch } from '../lib/api-services-raw'
import { useAuthStore } from '../store/authStore'
import Card from '../components/ui/Card'
import Button from '../components/ui/Button'
@@ -17,21 +18,19 @@ export default function Settings() {
const { data: orgData } = useQuery<any>({
queryKey: ['org-settings'],
queryFn: async () => {
const res = await api.get('/settings/org') as any
return res.data
return await settingsApi.org()
},
})
const { data: usersData } = useQuery<any>({
queryKey: ['users'],
queryFn: async () => {
const res = await api.get('/settings/users') as any
return res.data
return await settingsApi.users()
},
})
const updateOrgMutation = useMutation({
mutationFn: (data: any) => api.put('/settings/org', data),
mutationFn: (data: any) => settingsApi.updateOrg(data),
onSuccess: () => queryClient.invalidateQueries({ queryKey: ['org-settings'] }),
})
@@ -153,14 +152,13 @@ function RetirementSection({ enabled, onToggle }: { enabled: boolean; onToggle:
const { data: policyData, isLoading } = useQuery<any>({
queryKey: ['retirement-policy'],
queryFn: async () => {
const res = await api.get('/settings/retirement-policy') as any
return res.data
return await settingsApi.retirementPolicy()
},
enabled,
})
const confirmMutation = useMutation({
mutationFn: (id: string) => api.post(`/settings/retirement-policy/${id}/confirm`),
mutationFn: (id: string) => apiPost(`/settings/retirement-policy/${id}/confirm`),
onSuccess: () => {
toast.success('退休政策已确认生效')
setConfirming(false)
@@ -272,12 +270,12 @@ function UserSettings({ usersData }: { usersData: any }) {
const users = usersData || []
const updateMutation = useMutation({
mutationFn: ({ id, data }: { id: string; data: any }) => api.put(`/settings/users/${id}`, data),
mutationFn: ({ id, data }: { id: string; data: any }) => settingsApi.updateUser(id, data),
onSuccess: () => queryClient.invalidateQueries({ queryKey: ['users'] }),
})
const toggleDisableMutation = useMutation({
mutationFn: (id: string) => api.patch(`/settings/users/${id}/toggle-disable`),
mutationFn: (id: string) => settingsApi.toggleDisable(id),
onSuccess: () => queryClient.invalidateQueries({ queryKey: ['users'] }),
})
@@ -407,7 +405,7 @@ function AddUserModal({ open, onClose }: { open: boolean; onClose: () => void })
setLoading(true)
setError('')
try {
await api.post('/settings/users', form)
await settingsApi.addUser(form)
onClose()
} catch (err: any) {
setError(err.response?.data?.error?.message || '添加失败')
@@ -658,13 +656,12 @@ function PlanSettings({ orgData }: { orgData: any }) {
const { data: usageData } = useQuery<any>({
queryKey: ['usage'],
queryFn: async () => {
const res = await api.get('/settings/usage') as any
return res.data
return await settingsApi.usage()
},
})
const planMutation = useMutation({
mutationFn: (newPlan: string) => api.put('/settings/plan', { plan: newPlan }),
mutationFn: (newPlan: string) => settingsApi.updatePlan(newPlan),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['org'] })
queryClient.invalidateQueries({ queryKey: ['usage'] })
@@ -744,16 +741,14 @@ function NotificationSettings() {
const { data: setting } = useQuery<any>({
queryKey: ['notification-settings'],
queryFn: async () => {
const res = await api.get('/notifications/settings') as any
return res.data
return await notificationsApi.settings()
},
})
const { data: logsData } = useQuery<any>({
queryKey: ['notification-logs'],
queryFn: async () => {
const res = await api.get('/notifications/logs', { params: { pageSize: 10 } }) as any
return res.data
return await notificationsApi.logs({ pageSize: 10 })
},
})
@@ -762,12 +757,12 @@ function NotificationSettings() {
}, [setting])
const updateMutation = useMutation({
mutationFn: (data: any) => api.put('/notifications/settings', data),
mutationFn: (data: any) => notificationsApi.updateSettings(data),
onSuccess: () => queryClient.invalidateQueries({ queryKey: ['notification-settings'] }),
})
const checkMutation = useMutation({
mutationFn: () => api.post('/notifications/check-contracts') as any,
mutationFn: () => apiPost('/notifications/check-contracts') as any,
onSuccess: (res: any) => {
setCheckResult(`检查完成:发现 ${res.data.checked} 个即将到期的合同,已发送 ${res.data.notified} 条通知`)
queryClient.invalidateQueries({ queryKey: ['notification-logs'] })
@@ -775,14 +770,14 @@ function NotificationSettings() {
})
const testWechatMutation = useMutation({
mutationFn: () => api.post('/notifications/test', { channel: 'wechat' }) as any,
mutationFn: () => apiPost('/notifications/test', { channel: 'wechat' }) as any,
onSuccess: (res: any) => {
toast.success(res.success ? res.data.message : (res.error?.message || '测试失败'))
},
})
const testEmailMutation = useMutation({
mutationFn: () => api.post('/notifications/test', { channel: 'email' }) as any,
mutationFn: () => apiPost('/notifications/test', { channel: 'email' }) as any,
onSuccess: (res: any) => {
toast.success(res.success ? res.data.message : (res.error?.message || '测试失败'))
},
+43 -57
View File
@@ -4,7 +4,8 @@ import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
import { useConfirm } from '../hooks/useConfirm'
import { Calculator, Info, Check, Settings as SettingsIcon, Plus, History, Download, AlertCircle, Clock, MapPin, Sparkles, Upload, X, Shield } from 'lucide-react'
import { InlineAlert } from '../components/ui/InlineAlert'
import api from '../lib/api'
import { socialInsuranceApi, commercialInsuranceApi } from '../lib/api-services'
import { get as apiGet, post as apiPost, put as apiPut, del as apiDel } from '../lib/api-services-raw'
import { useAuthStore } from '../store/authStore'
import Card from '../components/ui/Card'
import Button from '../components/ui/Button'
@@ -54,31 +55,28 @@ export default function SocialInsurance() {
const { data: cities = [] } = useQuery<string[]>({
queryKey: ['social-config-cities'],
queryFn: async () => {
const res = await api.get('/social/config/cities') as any
return res.data
return await socialInsuranceApi.cities()
},
})
const { data: config, isLoading: configLoading } = useQuery<any>({
queryKey: ['social-config', city],
queryFn: async () => {
const res = await api.get('/social/config', { params: { city } }) as any
return res.data
return await socialInsuranceApi.config(city)
},
})
const { data: housingConfig, isLoading: housingLoading } = useQuery<any>({
queryKey: ['housing-config', city],
queryFn: async () => {
const res = await api.get('/social/housing-config', { params: { city } }) as any
return res.data
return await socialInsuranceApi.housingConfig(city)
},
})
const { data: housingAllAccounts } = useQuery<any[]>({
queryKey: ['housing-config-all-accounts', city],
queryFn: async () => {
const res = await api.get('/social/housing-config/versions', { params: { city } }) as any
const current = (res.data || []).filter((v: any) => v.isCurrent)
const res = await socialInsuranceApi.housingConfigVersions(city) as any
const current = (res || []).filter((v: any) => v.isCurrent)
return current
},
})
@@ -86,8 +84,7 @@ export default function SocialInsurance() {
const { data: versions } = useQuery<any[]>({
queryKey: ['social-config-versions', city],
queryFn: async () => {
const res = await api.get('/social/config/versions', { params: { city } }) as any
return res.data
return await socialInsuranceApi.configVersions(city)
},
enabled: showVersions && tab === 'social',
})
@@ -95,8 +92,7 @@ export default function SocialInsurance() {
const { data: housingVersions } = useQuery<any[]>({
queryKey: ['housing-config-versions'],
queryFn: async () => {
const res = await api.get('/social/housing-config/versions') as any
return res.data
return await socialInsuranceApi.housingConfigVersions()
},
enabled: showVersions && tab === 'housing',
})
@@ -105,8 +101,7 @@ export default function SocialInsurance() {
const { data: processedList, refetch: refetchProcessedList } = useQuery<any[]>({
queryKey: ['monthly-process-list'],
queryFn: async () => {
const res = await api.get('/social/monthly-process/list') as any
return res.data
return await socialInsuranceApi.monthlyProcessList()
},
enabled: tab === 'monthly',
})
@@ -114,8 +109,8 @@ export default function SocialInsurance() {
// 进入月度办理Tab时自动查询当前月状态
useEffect(() => {
if (tab === 'monthly') {
api.get('/social/monthly-process/status', { params: { month: monthlyMonth } }).then((res: any) => {
setProcessStatus(res.data)
socialInsuranceApi.monthlyProcessStatus(monthlyMonth).then((res: any) => {
setProcessStatus(res)
}).catch(() => {})
refetchProcessedList()
}
@@ -124,10 +119,10 @@ export default function SocialInsurance() {
const { mutateAsync: fetchMonthlyChanges, isPending: monthlyLoading, data: monthlyChanges } = useMutation<any>({
mutationFn: async () => {
const [socialRes, housingRes, socialActiveRes, housingActiveRes] = await Promise.all([
api.get('/social/monthly-changes', { params: { month: monthlyMonth } }) as any,
api.get('/social/housing/monthly-changes', { params: { month: monthlyMonth } }) as any,
api.get('/social/active-declaration', { params: { month: monthlyMonth } }) as any,
api.get('/social/housing/active-declaration', { params: { month: monthlyMonth } }) as any,
apiGet('/social/monthly-changes', { params: { month: monthlyMonth } }) as any,
apiGet('/social/housing/monthly-changes', { params: { month: monthlyMonth } }) as any,
apiGet('/social/active-declaration', { params: { month: monthlyMonth } }) as any,
apiGet('/social/housing/active-declaration', { params: { month: monthlyMonth } }) as any,
])
return {
social: socialRes.data,
@@ -143,8 +138,8 @@ export default function SocialInsurance() {
await fetchMonthlyChanges()
setMonthlyProcessed(true)
// 查询该月办理状态
const statusRes = await api.get('/social/monthly-process/status', { params: { month: monthlyMonth } }) as any
setProcessStatus(statusRes.data)
const statusRes = await socialInsuranceApi.monthlyProcessStatus(monthlyMonth) as any
setProcessStatus(statusRes)
} catch {
toast.error('获取月度办理数据失败')
}
@@ -154,7 +149,7 @@ export default function SocialInsurance() {
mutationFn: async (type: 'SOCIAL' | 'HOUSING') => {
const snapshot = type === 'SOCIAL' ? monthlyChanges.social : monthlyChanges.housing
const activeSnapshot = type === 'SOCIAL' ? monthlyChanges.socialActive : monthlyChanges.housingActive
const res = await api.post('/social/monthly-process/complete', {
const res = await socialInsuranceApi.completeMonthlyProcess({
month: monthlyMonth,
type,
snapshot: { changes: snapshot, active: activeSnapshot },
@@ -174,20 +169,18 @@ export default function SocialInsurance() {
const { data: result, mutate: calcMutate, isPending } = useMutation<any>({
mutationFn: async () => {
const res = await api.post('/social/calculate', { base, city }) as any
return res.data
return await socialInsuranceApi.calculate(base, city)
},
})
const { data: housingResult, mutate: calcHousingMutate, isPending: housingCalcPending } = useMutation<any>({
mutationFn: async () => {
const res = await api.post('/social/housing-calculate', { base, city }) as any
return res.data
return await socialInsuranceApi.housingCalculate(base, city)
},
})
const createVersionMutation = useMutation({
mutationFn: (data: any) => api.post('/social/config/versions', data),
mutationFn: (data: any) => socialInsuranceApi.createConfigVersion(data),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['social-config'] })
queryClient.invalidateQueries({ queryKey: ['social-config-versions'] })
@@ -197,7 +190,7 @@ export default function SocialInsurance() {
})
const createHousingVersionMutation = useMutation({
mutationFn: (data: any) => api.post('/social/housing-config/versions', data),
mutationFn: (data: any) => socialInsuranceApi.createHousingConfigVersion(data),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['housing-config'] })
queryClient.invalidateQueries({ queryKey: ['housing-config-versions'] })
@@ -208,8 +201,7 @@ export default function SocialInsurance() {
const aiSuggestMut = useMutation<any, any, { city: string; effectiveFrom: string; type: 'social' | 'housing' }>({
mutationFn: async (vars: { city: string; effectiveFrom: string; type: 'social' | 'housing' }) => {
const res = await api.post('/social/ai-suggest', vars) as any
return res.data
return await socialInsuranceApi.aiSuggest(vars)
},
onSuccess: (data) => {
if (isHousing) {
@@ -247,8 +239,7 @@ export default function SocialInsurance() {
const previewAdjustMutation = useMutation({
mutationFn: async () => {
const res = await api.get(`/social/config/${config?.id}/adjust-preview`) as any
return res.data
return await socialInsuranceApi.adjustPreview(config?.id)
},
onSuccess: (data) => {
setAdjustData(data)
@@ -258,8 +249,7 @@ export default function SocialInsurance() {
const previewHousingAdjustMutation = useMutation({
mutationFn: async () => {
const res = await api.get(`/social/housing-config/${housingConfig?.id}/adjust-preview`) as any
return res.data
return await socialInsuranceApi.housingAdjustPreview(housingConfig?.id)
},
onSuccess: (data) => {
setAdjustData(data)
@@ -269,7 +259,7 @@ export default function SocialInsurance() {
const applyAdjustMutation = useMutation({
mutationFn: (data: { items: { employeeId: string; newBase: number }[] }) =>
api.post(`/social/config/${config?.id}/adjust-apply`, data),
socialInsuranceApi.applyAdjust(config?.id, data),
onSuccess: (res: any) => {
queryClient.invalidateQueries({ queryKey: ['social-config'] })
queryClient.invalidateQueries({ queryKey: ['social-config-versions'] })
@@ -285,7 +275,7 @@ export default function SocialInsurance() {
const applyHousingAdjustMutation = useMutation({
mutationFn: (data: { items: { employeeId: string; newBase: number }[] }) =>
api.post(`/social/housing-config/${housingConfig?.id}/adjust-apply`, data),
socialInsuranceApi.applyHousingAdjust(housingConfig?.id, data),
onSuccess: (res: any) => {
queryClient.invalidateQueries({ queryKey: ['housing-config'] })
queryClient.invalidateQueries({ queryKey: ['housing-config-versions'] })
@@ -300,7 +290,7 @@ export default function SocialInsurance() {
})
const resetAdjustMutation = useMutation({
mutationFn: () => api.post(`/social/config/${config?.id}/reset-adjustment`, { city }),
mutationFn: () => socialInsuranceApi.resetAdjust(config?.id, city),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['social-config', city] })
queryClient.invalidateQueries({ queryKey: ['social-config-versions', city] })
@@ -309,7 +299,7 @@ export default function SocialInsurance() {
})
const resetHousingAdjustMutation = useMutation({
mutationFn: () => api.post(`/social/housing-config/${housingConfig?.id}/reset-adjustment`, { city }),
mutationFn: () => socialInsuranceApi.resetHousingAdjust(housingConfig?.id, city),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['housing-config', city] })
queryClient.invalidateQueries({ queryKey: ['housing-config-versions', city] })
@@ -1162,7 +1152,7 @@ function MonthlyRow({ item: i, type, onCorrected }: { item: any; type: 'add' | '
const d = i.detail
const correctMutation = useMutation({
mutationFn: (data: { base: number }) => api.put(`/social/records/social/${i.recordId}/correct`, data),
mutationFn: (data: { base: number }) => socialInsuranceApi.correctRecord('social', i.recordId, data),
onSuccess: () => {
setEditing(false)
toast.success('基数已修改')
@@ -1250,7 +1240,7 @@ function MonthlyHousingRow({ item: i, type, onCorrected }: { item: any; type: 'a
const d = i.detail
const correctMutation = useMutation({
mutationFn: (data: { base: number }) => api.put(`/social/records/housing/${i.recordId}/correct`, data),
mutationFn: (data: { base: number }) => socialInsuranceApi.correctRecord('housing', i.recordId, data),
onSuccess: () => {
setEditing(false)
toast.success('基数已修改')
@@ -1314,8 +1304,7 @@ function SpecialDeductionTab({ month, setMonth }: { month: string; setMonth: (m:
const { data: records = [], isLoading } = useQuery<any[]>({
queryKey: ['special-deduction', month],
queryFn: async () => {
const res = await api.get('/social/special-deduction/batch', { params: { month } }) as any
return res.data
return await socialInsuranceApi.specialDeductionBatch(month)
},
})
@@ -1323,8 +1312,8 @@ function SpecialDeductionTab({ month, setMonth }: { month: string; setMonth: (m:
const { data: employees = [] } = useQuery<any[]>({
queryKey: ['active-social-employees', month],
queryFn: async () => {
const res = await api.get('/social/active-declaration', { params: { month } }) as any
return (res.data?.items || []).map((item: any) => ({
const res = await socialInsuranceApi.activeDeclaration(month) as any
return (res?.items || []).map((item: any) => ({
id: item.employeeId,
name: item.name,
department: item.department,
@@ -1341,14 +1330,13 @@ function SpecialDeductionTab({ month, setMonth }: { month: string; setMonth: (m:
const { data: prevRecords = [] } = useQuery<any[]>({
queryKey: ['special-deduction', prevMonth],
queryFn: async () => {
const res = await api.get('/social/special-deduction/batch', { params: { month: prevMonth } }) as any
return res.data || []
return await socialInsuranceApi.specialDeductionBatch(prevMonth)
},
})
const prevRecordMap = new Map(prevRecords.map((r: any) => [r.employeeId, r]))
const saveMutation = useMutation({
mutationFn: (data: any) => api.post('/social/special-deduction', { ...data, month }),
mutationFn: (data: any) => socialInsuranceApi.saveSpecialDeduction({ ...data, month }),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['special-deduction'] })
setEditing(null)
@@ -1376,7 +1364,7 @@ function SpecialDeductionTab({ month, setMonth }: { month: string; setMonth: (m:
mutationFn: async () => {
let copied = 0
for (const prev of prevRecords) {
await api.post('/social/special-deduction', {
await socialInsuranceApi.saveSpecialDeduction({
employeeId: prev.employeeId,
month,
children: prev.children || 0,
@@ -1661,8 +1649,7 @@ function CommercialInsuranceTab() {
const { data: plans = [], isLoading } = useQuery<any[]>({
queryKey: ['commercial-insurance-plans'],
queryFn: async () => {
const res = await api.get('/commercial-insurance/plans') as any
return res.data || []
return await commercialInsuranceApi.plans()
},
})
@@ -1671,8 +1658,7 @@ function CommercialInsuranceTab() {
queryKey: ['commercial-insurance-enrollments', selectedPlanId],
queryFn: async () => {
if (!selectedPlanId) return []
const res = await api.get(`/commercial-insurance/plans/${selectedPlanId}/enrollments`) as any
return res.data || []
return await commercialInsuranceApi.enrollments(selectedPlanId)
},
enabled: !!selectedPlanId,
})
@@ -1681,9 +1667,9 @@ function CommercialInsuranceTab() {
const savePlanMutation = useMutation({
mutationFn: async (data: any) => {
if (editingPlan) {
return api.put(`/commercial-insurance/plans/${editingPlan.id}`, data) as any
return commercialInsuranceApi.savePlan(data, editingPlan.id) as any
}
return api.post('/commercial-insurance/plans', data) as any
return commercialInsuranceApi.savePlan(data) as any
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['commercial-insurance-plans'] })
@@ -1697,7 +1683,7 @@ function CommercialInsuranceTab() {
/** 删除商险方案 */
const deletePlanMutation = useMutation({
mutationFn: (id: string) => api.delete(`/commercial-insurance/plans/${id}`),
mutationFn: (id: string) => commercialInsuranceApi.removePlan(id),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['commercial-insurance-plans'] })
setSelectedPlanId(null)
+11 -11
View File
@@ -4,7 +4,7 @@
*/
import { useEffect, useState } from 'react'
import { Search, Plus, Edit2, Trash2, AlertTriangle, Clock, Baby, HeartPulse, Activity, X } from 'lucide-react'
import api from '../lib/api'
import { specialStatusApi, employeeApi } from '../lib/api-services'
import { Input, Select, Label } from '../components/ui/Input'
import Button from '../components/ui/Button'
@@ -140,9 +140,9 @@ export default function SpecialStatus() {
if (search) params.search = search
if (typeFilter) params.type = typeFilter
if (statusFilter) params.status = statusFilter
const res = await api.get('/special-statuses', { params }) as any
setList(res.data.list)
setTotal(res.data.total)
const res = await specialStatusApi.list({ page, pageSize, search: search || undefined, type: typeFilter || undefined, status: statusFilter || undefined } as any) as any
setList(res.list)
setTotal(res.total)
} finally {
setLoading(false)
}
@@ -150,8 +150,8 @@ export default function SpecialStatus() {
const fetchStats = async () => {
try {
const res = await api.get('/special-statuses/stats/overview') as any
setStats(res.data)
const res = await specialStatusApi.stats() as any
setStats(res)
} catch {
// 忽略
}
@@ -159,8 +159,8 @@ export default function SpecialStatus() {
const fetchEmployees = async () => {
try {
const res = await api.get('/employees/all-lite') as any
setEmployees(res.data || [])
const res = await employeeApi.allLite() as any
setEmployees(res || [])
} catch {
// 忽略
}
@@ -212,9 +212,9 @@ export default function SpecialStatus() {
if (data.medicalMonths) data.medicalMonths = parseInt(data.medicalMonths)
if (editing) {
await api.put(`/special-statuses/${editing.id}`, data)
await specialStatusApi.update(editing.id, data)
} else {
await api.post('/special-statuses', data)
await specialStatusApi.create(data)
}
setEditOpen(false)
fetchList()
@@ -227,7 +227,7 @@ export default function SpecialStatus() {
const handleDelete = async () => {
if (!deleteTarget) return
try {
await api.delete(`/special-statuses/${deleteTarget.id}`)
await specialStatusApi.remove(deleteTarget.id)
setDeleteTarget(null)
fetchList()
fetchStats()
+12 -19
View File
@@ -2,7 +2,7 @@ import { useState } from 'react'
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
import { FileText, Copy, X, ChevronRight, Download, BookOpen, HelpCircle, Plus, Edit, Trash2, Building2 } from 'lucide-react'
import { toast } from 'sonner'
import api from '../lib/api'
import { templatesApi } from '../lib/api-services'
import { useAuthStore } from '../store/authStore'
import Card from '../components/ui/Card'
import Button from '../components/ui/Button'
@@ -110,17 +110,14 @@ function SystemTemplates() {
const { data: list, isLoading } = useQuery<any>({
queryKey: ['templates', category],
queryFn: async () => {
const params = category ? `?category=${category}` : ''
const res = await api.get(`/templates${params}`) as any
return res.data
return await templatesApi.list(category || undefined)
},
})
const { data: detail } = useQuery<any>({
queryKey: ['template-detail', selected?.id],
queryFn: async () => {
const res = await api.get(`/templates/${selected.id}`) as any
return res.data
return await templatesApi.detail(selected.id)
},
enabled: !!selected,
})
@@ -128,8 +125,8 @@ function SystemTemplates() {
const handleRender = async () => {
if (!selected) return
try {
const res = await api.post(`/templates/${selected.id}/render`, { variables }) as any
setRendered(res.data.content)
const res = await templatesApi.render(selected.id, variables) as any
setRendered(res.content)
} catch (err: any) {
toast.error('渲染失败')
}
@@ -318,8 +315,7 @@ function EnterpriseTemplates() {
queryFn: async () => {
const params: any = { page, pageSize }
if (category) params.category = category
const res = await api.get('/enterprise-templates', { params }) as any
return res.data
return await templatesApi.enterpriseList({ page, pageSize, category: category || undefined } as any)
},
})
const list = listData?.items || []
@@ -328,8 +324,7 @@ function EnterpriseTemplates() {
const { data: detail } = useQuery<any>({
queryKey: ['enterprise-template-detail', selected?.id],
queryFn: async () => {
const res = await api.get(`/enterprise-templates/${selected.id}`) as any
return res.data
return await templatesApi.enterpriseDetail(selected.id)
},
enabled: !!selected,
})
@@ -337,11 +332,9 @@ function EnterpriseTemplates() {
const saveMutation = useMutation({
mutationFn: async (data: any) => {
if (editItem) {
const res = await api.put(`/enterprise-templates/${editItem.id}`, data) as any
return res.data
return await templatesApi.saveEnterprise(data, editItem.id)
} else {
const res = await api.post('/enterprise-templates', data) as any
return res.data
return await templatesApi.saveEnterprise(data)
}
},
onSuccess: () => {
@@ -356,7 +349,7 @@ function EnterpriseTemplates() {
const deleteMutation = useMutation({
mutationFn: async (id: string) => {
await api.delete(`/enterprise-templates/${id}`)
await templatesApi.removeEnterprise(id)
},
onSuccess: () => {
toast.success('已删除')
@@ -368,8 +361,8 @@ function EnterpriseTemplates() {
const handleRender = async () => {
if (!selected) return
try {
const res = await api.post(`/enterprise-templates/${selected.id}/render`, { variables }) as any
setRendered(res.data.content)
const res = await templatesApi.renderEnterprise(selected.id, variables) as any
setRendered(res.content)
} catch {
toast.error('渲染失败')
}
+15 -22
View File
@@ -5,8 +5,7 @@ import { AlertTriangle, Check, ChevronRight, ChevronLeft, Shield, Info, Calculat
import { Stepper } from '../components/ui/Stepper'
import { InlineAlert } from '../components/ui/InlineAlert'
import jsPDF from 'jspdf'
import api from '../lib/api'
import { rosterApi } from '../lib/api-services'
import { rosterApi, terminationApi } from '../lib/api-services'
import { useAuthStore } from '../store/authStore'
import Card from '../components/ui/Card'
import Button from '../components/ui/Button'
@@ -236,8 +235,7 @@ export default function Termination() {
const { data: profile } = useQuery<EmployeeProfile>({
queryKey: ['employee-profile', employeeId],
queryFn: async () => {
const res = await api.get(`/roster/${employeeId}/profile`) as any
return res.data
return await rosterApi.profile(employeeId)
},
enabled: !!employeeId,
})
@@ -305,8 +303,7 @@ export default function Termination() {
}[]>({
queryKey: ['checklist', reason, employeeId],
queryFn: async () => {
const res = await api.get(`/termination/checklist/${reason}`, { params: { employeeId } }) as any
return res.data
return await terminationApi.checklist(reason, employeeId)
},
enabled: !!reason && !!employeeId && step >= 2,
})
@@ -326,14 +323,13 @@ export default function Termination() {
const { data: riskAssessment } = useQuery<{ level: string; warnings: string[] }>({
queryKey: ['assess', employeeId, reason],
queryFn: async () => {
const res = await api.get(`/termination/assess/${employeeId}`, { params: { reason } }) as any
return res.data
return await terminationApi.assess(employeeId, reason)
},
enabled: !!employeeId && !!reason && step >= 1,
})
const saveMutation = useMutation({
mutationFn: (data: any) => api.post('/termination', data),
mutationFn: (data: any) => terminationApi.create(data),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['dashboard'] })
queryClient.invalidateQueries({ queryKey: ['employees'] })
@@ -352,8 +348,7 @@ export default function Termination() {
if (filterStatus) params.status = filterStatus
if (filterDepartment) params.department = filterDepartment
if (searchTerm) params.search = searchTerm
const res = await api.get('/termination/drafts', { params }) as any
return res.data
return await terminationApi.drafts(params)
},
enabled: view === 'list',
})
@@ -364,8 +359,7 @@ export default function Termination() {
const { data: draftDetail } = useQuery({
queryKey: ['termination-detail', draftId],
queryFn: async () => {
const res = await api.get(`/termination/detail/${draftId}`) as any
return res.data
return await terminationApi.detail(draftId!)
},
enabled: !!draftId && view === 'detail',
})
@@ -373,8 +367,8 @@ export default function Termination() {
// 保存草稿
const saveDraftMutation = useMutation({
mutationFn: (data: any) => draftId
? api.put(`/termination/draft/${draftId}`, data)
: api.post('/termination/draft', data),
? terminationApi.updateDraft(draftId!, data)
: terminationApi.createDraft(data),
onSuccess: (res: any) => {
const newId = draftId || res?.data?.id
setDraftId(newId)
@@ -386,7 +380,7 @@ export default function Termination() {
// 提交审批
const submitMutation = useMutation({
mutationFn: () => api.post(`/termination/draft/${draftId}/submit`),
mutationFn: () => terminationApi.submit(draftId!),
onSuccess: () => {
toast.success('已提交审批')
queryClient.invalidateQueries({ queryKey: ['termination-drafts'] })
@@ -398,7 +392,7 @@ export default function Termination() {
// 审批通过
const approveMutation = useMutation({
mutationFn: (comment: string) => api.post(`/termination/draft/${draftId}/approve`, { comment }),
mutationFn: (comment: string) => terminationApi.approve(draftId!, comment),
onSuccess: () => {
toast.success('审批通过')
queryClient.invalidateQueries({ queryKey: ['termination-drafts'] })
@@ -409,7 +403,7 @@ export default function Termination() {
// 审批驳回
const rejectMutation = useMutation({
mutationFn: (comment: string) => api.post(`/termination/draft/${draftId}/reject`, { comment }),
mutationFn: (comment: string) => terminationApi.reject(draftId!, comment),
onSuccess: () => {
toast.success('已驳回')
queryClient.invalidateQueries({ queryKey: ['termination-drafts'] })
@@ -420,7 +414,7 @@ export default function Termination() {
// 执行解聘
const executeMutation = useMutation({
mutationFn: () => api.post(`/termination/draft/${draftId}/execute`),
mutationFn: () => terminationApi.execute(draftId!),
onSuccess: () => {
toast.success('解聘已执行')
queryClient.invalidateQueries({ queryKey: ['termination-drafts'] })
@@ -436,7 +430,7 @@ export default function Termination() {
// 撤销
const cancelMutation = useMutation({
mutationFn: () => api.post(`/termination/draft/${draftId}/cancel`),
mutationFn: () => terminationApi.cancel(draftId!),
onSuccess: () => {
toast.success('已撤销')
queryClient.invalidateQueries({ queryKey: ['termination-drafts'] })
@@ -451,8 +445,7 @@ export default function Termination() {
const { data: evidenceChain } = useQuery({
queryKey: ['evidence-chain', employeeId],
queryFn: async () => {
const res = await api.get(`/roster/${employeeId}/evidence-chain`) as any
return res.data
return await rosterApi.evidenceChain(employeeId)
},
enabled: !!employeeId && step === 5 && saveMutation.isSuccess,
})
+8 -14
View File
@@ -6,7 +6,7 @@ import {
Repeat, Pause, FileText, XCircle, UserX, FileMinus, Briefcase,
Loader2, ChevronRight, Trash2, Send, X, Eye,
} from 'lucide-react'
import api from '../lib/api'
import { workProcessApi } from '../lib/api-services'
import Card from '../components/ui/Card'
import Button from '../components/ui/Button'
import { Input, Label, Select } from '../components/ui/Input'
@@ -155,15 +155,13 @@ export default function WorkProcess() {
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
return res.data
return await workProcessApi.list({ page, pageSize, type: filterType || undefined, status: filterStatus || undefined } as any)
},
})
const createMutation = useMutation({
mutationFn: async (data: any) => {
const res = await api.post('/work-processes', data) as any
return res.data
return await workProcessApi.create(data)
},
onSuccess: () => {
toast.success('已创建草稿')
@@ -177,8 +175,7 @@ export default function WorkProcess() {
const submitMutation = useMutation({
mutationFn: async (id: string) => {
const res = await api.post(`/work-processes/${id}/submit`) as any
return res.data
return await workProcessApi.submit(id)
},
onSuccess: () => {
toast.success('已提交并执行')
@@ -190,8 +187,7 @@ export default function WorkProcess() {
const cancelMutation = useMutation({
mutationFn: async (id: string) => {
const res = await api.post(`/work-processes/${id}/cancel`) as any
return res.data
return await workProcessApi.cancel(id)
},
onSuccess: () => {
toast.success('已撤销')
@@ -203,7 +199,7 @@ export default function WorkProcess() {
const deleteMutation = useMutation({
mutationFn: async (id: string) => {
await api.delete(`/work-processes/${id}`)
await workProcessApi.remove(id)
},
onSuccess: () => {
toast.success('已删除')
@@ -214,8 +210,7 @@ export default function WorkProcess() {
const previewMutation = useMutation({
mutationFn: async (id: string) => {
const res = await api.get(`/work-processes/${id}/preview`) as any
return res.data
return await workProcessApi.preview(id)
},
onSuccess: (data) => {
setPreviewContent(data.content)
@@ -426,8 +421,7 @@ function DetailContent({ id, previewContent, onPreview, onSubmit, onCancel, onDe
const { data, isLoading } = useQuery({
queryKey: ['work-process', id],
queryFn: async () => {
const res = await api.get(`/work-processes/${id}`) as any
return res.data
return await workProcessApi.detail(id!)
},
enabled: !!id,
})
+4 -4
View File
@@ -2,7 +2,7 @@ import { useState } from 'react'
import { Link } from 'react-router-dom'
import { Eye, EyeOff } from 'lucide-react'
import Logo from '../../components/ui/Logo'
import api from '../../lib/api'
import { authApi } from '../../lib/api-services'
import { Input, Label } from '../../components/ui/Input'
import Button from '../../components/ui/Button'
@@ -25,8 +25,8 @@ export default function ForgotPassword() {
}
setLoading(true)
try {
const res = await api.post('/auth/forgot-password/send-code', { phone }) as any
setSentCode(res.data?.code || '')
const res = await authApi.forgotPasswordSendCode(phone) as any
setSentCode(res?.code || '')
setStep(2)
} catch (err: any) {
setError(err.response?.data?.error?.message || '发送失败,请稍后重试')
@@ -47,7 +47,7 @@ export default function ForgotPassword() {
}
setLoading(true)
try {
await api.post('/auth/forgot-password/verify', { phone, code, newPassword })
await authApi.forgotPasswordVerify({ phone, code, newPassword })
setSuccess(true)
} catch (err: any) {
setError(err.response?.data?.error?.message || '重置失败,请稍后重试')
+3 -3
View File
@@ -6,7 +6,7 @@ import { useForm } from 'react-hook-form'
import { zodResolver } from '@hookform/resolvers/zod'
import { z } from 'zod'
import { useAuthStore } from '../../store/authStore'
import api from '../../lib/api'
import { authApi } from '../../lib/api-services'
import { Input, Label } from '../../components/ui/Input'
import Button from '../../components/ui/Button'
@@ -52,13 +52,13 @@ export default function Login() {
setError('')
setLoading(true)
try {
const res = await api.post('/auth/login', data) as any
const res = await authApi.login(data) as any
if (remember) {
localStorage.setItem('admin-login-remember', JSON.stringify({ phone: data.phone, password: data.password }))
} else {
localStorage.removeItem('admin-login-remember')
}
setAuth(res.data.user, res.data.accessToken, res.data.refreshToken)
setAuth(res.user, res.accessToken, res.refreshToken)
navigate('/')
} catch (err: any) {
setError(err.response?.data?.error?.message || '登录失败,请稍后重试')
+3 -3
View File
@@ -6,7 +6,7 @@ import { useForm } from 'react-hook-form'
import { zodResolver } from '@hookform/resolvers/zod'
import { z } from 'zod'
import { useAuthStore } from '../../store/authStore'
import api from '../../lib/api'
import { authApi } from '../../lib/api-services'
import { Input, Label } from '../../components/ui/Input'
import Button from '../../components/ui/Button'
@@ -37,8 +37,8 @@ export default function Register() {
setError('')
setLoading(true)
try {
const res = await api.post('/auth/register', data) as any
setAuth(res.data.user, res.data.accessToken, res.data.refreshToken)
const res = await authApi.register(data) as any
setAuth(res.user, res.accessToken, res.refreshToken)
navigate('/')
} catch (err: any) {
setError(err.response?.data?.error?.message || '注册失败,请稍后重试')
+2 -3
View File
@@ -12,7 +12,7 @@ import {
import Card from '../../components/ui/Card'
import Button from '../../components/ui/Button'
import { InlineAlert } from '../../components/ui/InlineAlert'
import api from '../../lib/api'
import { dashboardApi } from '../../lib/api-services'
/** 风险等级配置 */
const RISK_LEVELS: Record<string, { label: string; color: string; bg: string }> = {
@@ -39,8 +39,7 @@ export default function RiskCenter() {
const { data: risks = [], isLoading } = useQuery<any[]>({
queryKey: ['risk-center'],
queryFn: async () => {
const res = await api.get('/dashboard/risks') as any
return res.data || []
return await dashboardApi.risks()
},
})
@@ -5,7 +5,7 @@ import { useState } from 'react'
import { useQuery } from '@tanstack/react-query'
import { PieChart, Pie, Cell, ResponsiveContainer, Tooltip, BarChart, Bar, XAxis, YAxis, CartesianGrid, Legend } from 'recharts'
import { Award, TrendingUp } from 'lucide-react'
import api from '../../lib/api'
import { dashboardApi } from '../../lib/api-services'
const GRADE_COLORS: Record<string, string> = {
'A': '#237A57',
@@ -22,8 +22,7 @@ export default function PerformanceStats() {
const { data, isLoading } = useQuery({
queryKey: ['performance-stats', period],
queryFn: async () => {
const res = await api.get(`/dashboard/performance-stats?period=${period}`)
return res.data.data
return await dashboardApi.performanceStats(period)
},
})
+2 -3
View File
@@ -5,7 +5,7 @@
import { useQuery } from '@tanstack/react-query'
import { Link } from 'react-router-dom'
import { AlertCircle, Layers, FileText, Heart, ArrowRight, ListTodo } from 'lucide-react'
import api from '../../lib/api'
import { dashboardApi } from '../../lib/api-services'
import { InlineAlert } from '../../components/ui/InlineAlert'
interface NextAction {
@@ -44,8 +44,7 @@ export function TaskCenter() {
const { data, isLoading } = useQuery<{ actions: ActionGroup[]; totalCount: number }>({
queryKey: ['next-actions'],
queryFn: async () => {
const res = await api.get('/dashboard/workspace/next-actions') as any
return res.data
return await dashboardApi.nextActions()
},
staleTime: 60_000,
})
@@ -4,14 +4,13 @@
import { useQuery } from '@tanstack/react-query'
import { BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, Legend } from 'recharts'
import { UserPlus, UserMinus, Users, TrendingDown } from 'lucide-react'
import api from '../../lib/api'
import { dashboardApi } from '../../lib/api-services'
export default function TurnoverStats() {
const { data, isLoading } = useQuery({
queryKey: ['turnover-stats'],
queryFn: async () => {
const res = await api.get('/dashboard/turnover-stats?months=12')
return res.data.data
return await dashboardApi.turnoverStats(12)
},
})
@@ -4,7 +4,7 @@
import { useEffect, useState } from 'react'
import { Link } from 'react-router-dom'
import { Building2, Users, FileText, Calculator, TrendingUp } from 'lucide-react'
import api from '../../lib/api'
import { platformApi } from '../../lib/api-services'
interface DashboardData {
totalOrgs: number
@@ -29,7 +29,7 @@ export default function PlatformDashboard() {
const [loading, setLoading] = useState(true)
useEffect(() => {
api.get('/platform/dashboard').then((res: any) => setData(res.data)).finally(() => setLoading(false))
platformApi.dashboard().then((data: any) => setData(data)).finally(() => setLoading(false))
}, [])
if (loading) return <div className="text-center py-12 text-gray-400">...</div>
@@ -10,7 +10,7 @@ import { useForm } from 'react-hook-form'
import { zodResolver } from '@hookform/resolvers/zod'
import { z } from 'zod'
import { useAuthStore } from '../../store/authStore'
import api from '../../lib/api'
import { authApi } from '../../lib/api-services'
import { Input, Label } from '../../components/ui/Input'
import Button from '../../components/ui/Button'
@@ -54,18 +54,18 @@ export default function PlatformLogin() {
}
}, [setValue])
const onSubmit = async (data: FormData) => {
const onSubmit = async (formData: FormData) => {
setError('')
setLoading(true)
try {
const res = await api.post('/auth/platform-login', data) as any
const res = await authApi.platformLogin(formData) as any
// 登录成功后保存/清除记住的账号密码
if (remember) {
localStorage.setItem(STORAGE_KEY, JSON.stringify({ phone: data.phone, password: data.password }))
localStorage.setItem(STORAGE_KEY, JSON.stringify({ phone: formData.phone, password: formData.password }))
} else {
localStorage.removeItem(STORAGE_KEY)
}
setAuth(res.data.user, res.data.accessToken, res.data.refreshToken)
setAuth(res.user, res.accessToken, res.refreshToken)
navigate('/platform/dashboard')
} catch (err: any) {
setError(err.response?.data?.error?.message || '登录失败,请稍后重试')
+10 -10
View File
@@ -3,7 +3,7 @@
*/
import { useEffect, useState } from 'react'
import { Search, Building2, Eye, Trash2, Edit2, Plus } from 'lucide-react'
import api from '../../lib/api'
import { platformApi } from '../../lib/api-services'
import { Input, Select, Label } from '../../components/ui/Input'
import Button from '../../components/ui/Button'
@@ -44,9 +44,9 @@ export default function PlatformOrgs() {
const params: any = { page, pageSize }
if (search) params.search = search
if (planFilter) params.plan = planFilter
const res = await api.get('/platform/orgs', { params }) as any
setOrgs(res.data.list)
setTotal(res.data.total)
const res = await platformApi.orgs(params) as any
setOrgs(res.list)
setTotal(res.total)
} finally {
setLoading(false)
}
@@ -62,8 +62,8 @@ export default function PlatformOrgs() {
setEditAdmin({ name: '', phone: '', password: '' })
// 加载企业管理员信息
try {
const res = await api.get(`/platform/orgs/${org.id}`) as any
const admin = res.data.users?.find((u: any) => u.role === 'ADMIN')
const res = await platformApi.orgDetail(org.id) as any
const admin = res.users?.find((u: any) => u.role === 'ADMIN')
if (admin) {
setEditAdmin({ name: admin.name || '', phone: admin.phone || '', password: '' })
}
@@ -75,7 +75,7 @@ export default function PlatformOrgs() {
const handleSaveEdit = async () => {
if (!editOrg) return
try {
await api.put(`/platform/orgs/${editOrg.id}`, {
await platformApi.updateOrg(editOrg.id, {
name: editOrg.name,
plan: editOrg.plan,
maxEmployees: editOrg.maxEmployees,
@@ -85,7 +85,7 @@ export default function PlatformOrgs() {
})
// 如果管理员信息有改动,同步保存
if (editAdmin.name || editAdmin.phone || editAdmin.password) {
await api.put(`/platform/orgs/${editOrg.id}/admin`, {
await platformApi.updateOrgAdmin(editOrg.id, {
adminName: editAdmin.name || undefined,
adminPhone: editAdmin.phone || undefined,
adminPassword: editAdmin.password || undefined,
@@ -105,7 +105,7 @@ export default function PlatformOrgs() {
}
setCreating(true)
try {
await api.post('/platform/orgs', createForm)
await platformApi.createOrg(createForm)
setCreateOpen(false)
setCreateForm({
name: '', plan: 'FREE', maxEmployees: 20, city: '',
@@ -123,7 +123,7 @@ export default function PlatformOrgs() {
const handleDelete = async () => {
if (!deleteOrg) return
try {
await api.delete(`/platform/orgs/${deleteOrg.id}`)
await platformApi.removeOrg(deleteOrg.id)
setDeleteOrg(null)
fetchOrgs()
} catch (err: any) {
@@ -3,7 +3,7 @@
*/
import { useEffect, useState } from 'react'
import { Search, Ban, CheckCircle } from 'lucide-react'
import api from '../../lib/api'
import { platformApi } from '../../lib/api-services'
import { Input, Select } from '../../components/ui/Input'
import Button from '../../components/ui/Button'
@@ -37,17 +37,17 @@ export default function PlatformUsers() {
const params: any = { page, pageSize }
if (search) params.search = search
if (orgFilter) params.orgId = orgFilter
const res = await api.get('/platform/users', { params }) as any
setUsers(res.data.list)
setTotal(res.data.total)
const res = await platformApi.users(params) as any
setUsers(res.list)
setTotal(res.total)
} finally {
setLoading(false)
}
}
useEffect(() => {
api.get('/platform/orgs', { params: { pageSize: 200 } }).then((res: any) => {
setOrgs(res.data.list.map((o: any) => ({ id: o.id, name: o.name })))
platformApi.orgs({ pageSize: 200 }).then((res: any) => {
setOrgs(res.list.map((o: any) => ({ id: o.id, name: o.name })))
})
}, [])
@@ -56,7 +56,7 @@ export default function PlatformUsers() {
const handleToggle = async (user: UserItem) => {
try {
await api.put(`/platform/users/${user.id}/toggle`)
await platformApi.toggleUser(user.id)
fetchUsers()
} catch (err: any) {
alert(err.response?.data?.error?.message || '操作失败')
+2 -3
View File
@@ -5,7 +5,7 @@
import { useEffect, useState } from 'react'
import { useNavigate, useSearchParams } from 'react-router-dom'
import { Loader2, CheckCircle, AlertCircle } from 'lucide-react'
import api from '../../lib/api'
import { portalApi } from '../../lib/api-services'
import Logo from '../../components/ui/Logo'
export default function AutoLogin() {
@@ -21,8 +21,7 @@ export default function AutoLogin() {
setErrorMsg('缺少登录凭证')
return
}
api.get('/portal/auto-login', { params: { token } }).then((res: any) => {
const data = res.data?.data || res.data
portalApi.autoLogin(token).then((data: any) => {
if (data?.token) {
localStorage.setItem('portalToken', data.token)
localStorage.setItem('portalEmployee', JSON.stringify(data.employee))
@@ -1,7 +1,7 @@
import { useState, useEffect } from 'react'
import { useSearchParams } from 'react-router-dom'
import { PenTool, Check, AlertCircle } from 'lucide-react'
import api from '../../lib/api'
import { portalApi } from '../../lib/api-services'
import Card from '../../components/ui/Card'
import Button from '../../components/ui/Button'
@@ -24,8 +24,8 @@ export default function ContractConfirm() {
useEffect(() => {
if (token) {
api.get(`/portal/contract-confirm/${token}`).then((res: any) => {
setData(res.data)
portalApi.contractInfo(token).then((data: any) => {
setData(data)
}).catch((err: any) => {
setError(err.response?.data?.error?.message || '链接无效或已过期')
}).finally(() => setLoading(false))
@@ -39,9 +39,9 @@ export default function ContractConfirm() {
setSendingCode(true)
setError('')
try {
const res = await api.post('/portal/contract-confirm/send-code', { token }) as any
const data = await portalApi.contractConfirmSendCode(token) as any
setCodeSent(true)
setDevCode(res.data?.data?.code || '')
setDevCode(data?.code || '')
} catch (err: any) {
setError(err.response?.data?.error?.message || '验证码发送失败')
} finally {
@@ -52,7 +52,7 @@ export default function ContractConfirm() {
const handleConfirm = async () => {
setSubmitting(true)
try {
await api.post('/portal/contract-confirm', { token, agreed: true, verifyCode })
await portalApi.contractConfirm(token, verifyCode)
setConfirmed(true)
} catch (err: any) {
setError(err.response?.data?.error?.message || '确认失败')
+2 -11
View File
@@ -9,17 +9,9 @@ import {
DollarSign, FileText, CalendarCheck, ScrollText,
TrendingUp, Clock, AlertCircle, ChevronRight,
} from 'lucide-react'
import { portalApi } from '../../lib/api-services'
import Card from '../../components/ui/Card'
import { InlineAlert } from '../../components/ui/InlineAlert'
import axios from 'axios'
/** 员工端 API 实例(自动携带 portalToken */
const portalApi = axios.create({ baseURL: '/api/v1/portal' })
portalApi.interceptors.request.use((config: any) => {
const token = localStorage.getItem('portalToken')
if (token) config.headers.Authorization = `Bearer ${token}`
return config
})
/** 金额格式化 */
const fmt = (n: number) => (n || 0).toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })
@@ -41,8 +33,7 @@ export default function EmployeeHome() {
const { data: overview, isLoading } = useQuery<any>({
queryKey: ['portal-home-overview'],
queryFn: async () => {
const res = await portalApi.get('/home/overview') as any
return res.data
return await portalApi.homeOverview()
},
})
+2 -3
View File
@@ -1,7 +1,7 @@
import { useState } from 'react'
import { useQuery } from '@tanstack/react-query'
import { Loader2, CalendarCheck } from 'lucide-react'
import api from '../../lib/api'
import { portalApi } from '../../lib/api-services'
export default function MyAttendance() {
const [month, setMonth] = useState(new Date().toISOString().slice(0, 7))
@@ -9,8 +9,7 @@ export default function MyAttendance() {
const { data, isLoading } = useQuery({
queryKey: ['portal-attendance', month],
queryFn: async () => {
const res = await api.get('/portal/attendance', { params: { month } }) as any
return res.data
return await portalApi.attendance(month)
},
})
+4 -12
View File
@@ -1,7 +1,7 @@
import { useState } from 'react'
import { useQuery } from '@tanstack/react-query'
import { FileText, AlertCircle, Check, RefreshCw, Calendar, Briefcase, Clock } from 'lucide-react'
import api from '../../lib/api'
import { portalApi } from '../../lib/api-services'
import Card from '../../components/ui/Card'
import Button from '../../components/ui/Button'
import EmptyState from '../../components/ui/EmptyState'
@@ -9,13 +9,6 @@ import EmptyState from '../../components/ui/EmptyState'
/** 金额格式化:保留两位小数 + 千分位 */
const fmt = (n: number) => (n || 0).toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })
const portalApi = api.create({ baseURL: '/api/v1/portal' })
portalApi.interceptors.request.use((config: any) => {
const token = localStorage.getItem('portalToken')
if (token) config.headers.Authorization = `Bearer ${token}`
return config
})
export default function MyContract() {
const [resending, setResending] = useState(false)
const [resendMsg, setResendMsg] = useState('')
@@ -23,8 +16,7 @@ export default function MyContract() {
const { data, isLoading } = useQuery<any>({
queryKey: ['my-contract'],
queryFn: async () => {
const res = await portalApi.get('/contract') as any
return res.data?.data ?? null
return await portalApi.contract()
},
})
@@ -40,8 +32,8 @@ export default function MyContract() {
setResending(true)
setResendMsg('')
try {
const res = await portalApi.post('/contract-confirm/resend', { contractId: contract?.id }) as any
setResendMsg(res.data?.data?.message || '重发成功')
const data = await portalApi.resendContractConfirm(contract?.id) as any
setResendMsg(data?.message || '重发成功')
} catch (err: any) {
setResendMsg(err.response?.data?.error?.message || '重发失败')
} finally {
+4 -13
View File
@@ -6,18 +6,11 @@
import { useState } from 'react'
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
import { FileText, CheckCircle, Clock, ArrowLeft, ChevronRight, ScrollText } from 'lucide-react'
import api from '../../lib/api'
import { portalApi } from '../../lib/api-services'
import Card from '../../components/ui/Card'
import Button from '../../components/ui/Button'
import EmptyState from '../../components/ui/EmptyState'
const portalApi = api.create({ baseURL: '/api/v1/portal' })
portalApi.interceptors.request.use((config: any) => {
const token = localStorage.getItem('portalToken')
if (token) config.headers.Authorization = `Bearer ${token}`
return config
})
export default function MyPolicies() {
const queryClient = useQueryClient()
const [selectedId, setSelectedId] = useState<string | null>(null)
@@ -26,8 +19,7 @@ export default function MyPolicies() {
const { data: list, isLoading } = useQuery<any>({
queryKey: ['portal-policies'],
queryFn: async () => {
const res = await portalApi.get('/policies') as any
return res.data?.data ?? res.data ?? []
return await portalApi.policies()
},
})
@@ -36,15 +28,14 @@ export default function MyPolicies() {
queryKey: ['portal-policy', selectedId],
queryFn: async () => {
if (!selectedId) return null
const res = await portalApi.get(`/policies/${selectedId}`) as any
return res.data?.data ?? res.data ?? null
return await portalApi.policyDetail(selectedId)
},
enabled: !!selectedId,
})
/** 阅读确认 */
const readMutation = useMutation({
mutationFn: (id: string) => portalApi.post(`/policies/${id}/read`),
mutationFn: (id: string) => portalApi.policyRead(id),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['portal-policies'] })
queryClient.invalidateQueries({ queryKey: ['portal-policy', selectedId] })
+6 -8
View File
@@ -1,7 +1,7 @@
import { useState, useRef } from 'react'
import { useSearchParams } from 'react-router-dom'
import { ClipboardList, Check, FileText, X } from 'lucide-react'
import api from '../../lib/api'
import { portalApi } from '../../lib/api-services'
import Card from '../../components/ui/Card'
import Button from '../../components/ui/Button'
import { Input, Label } from '../../components/ui/Input'
@@ -46,8 +46,8 @@ export default function Onboarding() {
// 获取链接信息
useState(() => {
if (token) {
api.get(`/portal/onboarding/${token}`).then((res: any) => {
setOrgName(res.data.orgName)
portalApi.onboardingInfo(token).then((data: any) => {
setOrgName(data.orgName)
}).catch((err: any) => {
setError(err.response?.data?.error?.message || '链接无效')
})
@@ -63,10 +63,8 @@ export default function Onboarding() {
const formData = new FormData()
formData.append('file', file)
formData.append('fileType', currentFileType)
const res = await api.post(`/portal/onboarding/${token}/upload`, formData, {
headers: { 'Content-Type': 'multipart/form-data' },
}) as any
setUploadedFiles([...uploadedFiles, res.data.data])
const data = await portalApi.onboardingUpload(token, formData) as any
setUploadedFiles([...uploadedFiles, data])
} catch (err: any) {
setError(err.response?.data?.error?.message || '文件上传失败')
} finally {
@@ -83,7 +81,7 @@ export default function Onboarding() {
setError('')
setLoading(true)
try {
await api.post('/portal/onboarding', { ...form, token, attachments: uploadedFiles })
await portalApi.onboardingSubmit({ ...form, token, attachments: uploadedFiles })
setSubmitted(true)
} catch (err: any) {
setError(err.response?.data?.error?.message || '提交失败')
@@ -4,18 +4,10 @@
*/
import { useQuery } from '@tanstack/react-query'
import { Check, Clock, AlertCircle, FileText, Upload, User, Phone, Banknote } from 'lucide-react'
import { portalApi } from '../../lib/api-services'
import Card from '../../components/ui/Card'
import { InlineAlert } from '../../components/ui/InlineAlert'
import { Stepper } from '../../components/ui/Stepper'
import axios from 'axios'
/** 员工端 API 实例 */
const portalApi = axios.create({ baseURL: '/api/v1/portal' })
portalApi.interceptors.request.use((config: any) => {
const token = localStorage.getItem('portalToken')
if (token) config.headers.Authorization = `Bearer ${token}`
return config
})
/** 入职步骤定义 */
const ONBOARDING_STEPS = [
@@ -31,8 +23,7 @@ export default function OnboardingProgress() {
const { data: progress, isLoading } = useQuery<any>({
queryKey: ['portal-onboarding-progress'],
queryFn: async () => {
const res = await portalApi.get('/onboarding/progress') as any
return res.data
return await portalApi.onboardingProgress()
},
})
+4 -14
View File
@@ -1,7 +1,7 @@
import { useState } from 'react'
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
import { Check, TrendingUp, Download, Wallet, ChevronLeft, ChevronRight } from 'lucide-react'
import api from '../../lib/api'
import { portalApi } from '../../lib/api-services'
import Card from '../../components/ui/Card'
import Button from '../../components/ui/Button'
import EmptyState from '../../components/ui/EmptyState'
@@ -9,14 +9,6 @@ import EmptyState from '../../components/ui/EmptyState'
/** 金额格式化:保留两位小数 + 千分位 */
const fmt = (n: number) => (n || 0).toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })
/** 员工端 API 实例(自动携带 portalToken */
const portalApi = api.create({ baseURL: '/api/v1/portal' })
portalApi.interceptors.request.use((config: any) => {
const token = localStorage.getItem('portalToken')
if (token) config.headers.Authorization = `Bearer ${token}`
return config
})
export default function Payslip() {
const queryClient = useQueryClient()
const [month, setMonth] = useState(new Date().toISOString().slice(0, 7))
@@ -25,21 +17,19 @@ export default function Payslip() {
const { data, isLoading } = useQuery<any>({
queryKey: ['payslip', month],
queryFn: async () => {
const res = await portalApi.get('/payslip', { params: { month } }) as any
return res.data?.data ?? null
return await portalApi.payslip(month)
},
})
const { data: history } = useQuery<any[]>({
queryKey: ['payslip-history'],
queryFn: async () => {
const res = await portalApi.get('/payslip/history') as any
return res.data?.data ?? []
return await portalApi.payslipHistory()
},
})
const confirmMutation = useMutation({
mutationFn: (id: string) => portalApi.post(`/payslip/${id}/confirm`),
mutationFn: (id: string) => portalApi.confirmPayslip(id),
onSuccess: () => queryClient.invalidateQueries({ queryKey: ['payslip'] }),
})
+9 -9
View File
@@ -1,7 +1,7 @@
import { useState } from 'react'
import { useNavigate } from 'react-router-dom'
import Logo from '../../components/ui/Logo'
import api from '../../lib/api'
import { portalApi } from '../../lib/api-services'
import { Input, Label } from '../../components/ui/Input'
import Button from '../../components/ui/Button'
@@ -22,9 +22,9 @@ export default function PortalLogin() {
setError('')
setLoading(true)
try {
const res = await api.post('/portal/login', { phone, password }) as any
localStorage.setItem('portalToken', res.data.token)
localStorage.setItem('portalEmployee', JSON.stringify(res.data.employee))
const data = await portalApi.login(phone, password) as any
localStorage.setItem('portalToken', data.token)
localStorage.setItem('portalEmployee', JSON.stringify(data.employee))
navigate('/portal/payslip')
} catch (err: any) {
setError(err.response?.data?.error?.message || '登录失败')
@@ -36,9 +36,9 @@ export default function PortalLogin() {
const handleSendCode = async () => {
setError('')
try {
const res = await api.post('/portal/send-code', { phone }) as any
const data = await portalApi.sendCode(phone) as any
setCodeSent(true)
setDisplayedCode(res.data.code)
setDisplayedCode(data?.code || '')
} catch (err: any) {
setError(err.response?.data?.error?.message || '发送失败')
}
@@ -48,9 +48,9 @@ export default function PortalLogin() {
setError('')
setLoading(true)
try {
const res = await api.post('/portal/verify-code', { phone, code }) as any
localStorage.setItem('portalToken', res.data.token)
localStorage.setItem('portalEmployee', JSON.stringify(res.data.employee))
const data = await portalApi.verifyCode(phone, code) as any
localStorage.setItem('portalToken', data.token)
localStorage.setItem('portalEmployee', JSON.stringify(data.employee))
navigate('/portal/payslip')
} catch (err: any) {
setError(err.response?.data?.error?.message || '登录失败')
+4 -15
View File
@@ -5,19 +5,11 @@ import { useState } from 'react'
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
import { toast } from 'sonner'
import { UserX, Clock, Check, X, FileText } from 'lucide-react'
import { portalApi } from '../../lib/api-services'
import Card from '../../components/ui/Card'
import Button from '../../components/ui/Button'
import { Input, Label, Select } from '../../components/ui/Input'
import { InlineAlert } from '../../components/ui/InlineAlert'
import axios from 'axios'
/** 员工端 API 实例 */
const portalApi = axios.create({ baseURL: '/api/v1/portal' })
portalApi.interceptors.request.use((config: any) => {
const token = localStorage.getItem('portalToken')
if (token) config.headers.Authorization = `Bearer ${token}`
return config
})
/** 离职原因选项 */
const RESIGN_REASONS = [
@@ -51,16 +43,14 @@ export default function ResignationApply() {
const { data: records = [], isLoading } = useQuery<any[]>({
queryKey: ['portal-resignation-status'],
queryFn: async () => {
const res = await portalApi.get('/resignation/status') as any
return Array.isArray(res?.data) ? res.data : []
return await portalApi.resignationStatus()
},
})
/** 提交离职申请 */
const submitMutation = useMutation({
mutationFn: async (data: { reason: string; expectedDate: string; remark: string }) => {
const res = await portalApi.post('/resignation/submit', data) as any
return res.data
return await portalApi.resignationSubmit(data)
},
onSuccess: () => {
toast.success('离职申请已提交,请等待HR审批')
@@ -75,8 +65,7 @@ export default function ResignationApply() {
/** 撤回离职申请 */
const withdrawMutation = useMutation({
mutationFn: async (id: string) => {
const res = await portalApi.post(`/resignation/${id}/withdraw`) as any
return res.data
return await portalApi.resignationWithdraw(id)
},
onSuccess: () => {
toast.success('离职申请已撤回')
+3 -3
View File
@@ -1,7 +1,7 @@
import { useState, useRef } from "react"
import { toast } from "sonner"
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"
import api from "../../lib/api"
import { rosterApi, attachmentApi } from '../../lib/api-services'
import Card from "../../components/ui/Card"
import Button from "../../components/ui/Button"
import { Input, Label, Select } from "../../components/ui/Input"
@@ -15,12 +15,12 @@ export default function AttachmentInfo({ employeeId, attachments }: { employeeId
const [fileType, setFileType] = useState<'ID_CARD' | 'BANK_CARD' | 'EDUCATION' | 'OTHER'>('ID_CARD')
const addAttachmentMutation = useMutation({
mutationFn: (data: any) => api.post('/attachments', data),
mutationFn: (data: any) => attachmentApi.add(data),
onSuccess: () => queryClient.invalidateQueries({ queryKey: ['roster-profile', employeeId] }),
})
const deleteAttachmentMutation = useMutation({
mutationFn: (id: string) => api.delete(`/attachments/${id}`),
mutationFn: (id: string) => attachmentApi.remove(id),
onSuccess: () => queryClient.invalidateQueries({ queryKey: ['roster-profile', employeeId] }),
})
@@ -1,7 +1,7 @@
import { useState, useRef } from "react"
import { toast } from "sonner"
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"
import api from "../../lib/api"
import { rosterApi, employeeApi } from '../../lib/api-services'
import Card from "../../components/ui/Card"
import Button from "../../components/ui/Button"
import { Input, Label, Select } from "../../components/ui/Input"
@@ -18,22 +18,22 @@ export default function AttendanceOvertimeInfo({ employeeId, attendanceRecords,
const [trainingForm, setTrainingForm] = useState({ trainingDate: '', topic: '', content: '', trainer: '', duration: 1, ackStatus: 'PENDING', ackDate: '', remark: '' })
const createMutation = useMutation({
mutationFn: (data: any) => api.post(`/roster/${employeeId}/attendance`, data),
mutationFn: (data: any) => rosterApi.attendance(employeeId, data),
onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['roster-profile'] }); setShowForm(false) },
})
const deleteMutation = useMutation({
mutationFn: (id: string) => api.delete(`/roster/${employeeId}/attendance/${id}`),
mutationFn: (id: string) => rosterApi.removeAttendance(employeeId, id),
onSuccess: () => queryClient.invalidateQueries({ queryKey: ['roster-profile'] }),
})
const createTrainingMutation = useMutation({
mutationFn: (data: any) => api.post(`/roster/${employeeId}/training`, data),
mutationFn: (data: any) => rosterApi.training(employeeId, data),
onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['roster-profile'] }); setShowForm(false) },
})
const deleteTrainingMutation = useMutation({
mutationFn: (id: string) => api.delete(`/roster/${employeeId}/training/${id}`),
mutationFn: (id: string) => rosterApi.removeTraining(employeeId, id),
onSuccess: () => queryClient.invalidateQueries({ queryKey: ['roster-profile'] }),
})
+4 -4
View File
@@ -3,7 +3,7 @@ import { useUnsavedChanges } from "../../hooks/useUnsavedChanges"
import { useState, useRef } from "react"
import { toast } from "sonner"
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"
import api from "../../lib/api"
import { rosterApi, attachmentApi, employeeApi } from '../../lib/api-services'
import Card from "../../components/ui/Card"
import Button from "../../components/ui/Button"
import { Input, Label, Select } from "../../components/ui/Input"
@@ -18,12 +18,12 @@ export default function BasicInfo({ profile, employeeId, attachments }: { profil
const [fileType, setFileType] = useState<'ID_CARD' | 'BANK_CARD' | 'EDUCATION' | 'OTHER'>('ID_CARD')
const addAttachmentMutation = useMutation({
mutationFn: (data: any) => api.post('/attachments', data),
mutationFn: (data: any) => attachmentApi.add(data),
onSuccess: () => queryClient.invalidateQueries({ queryKey: ['roster-profile', employeeId] }),
})
const deleteAttachmentMutation = useMutation({
mutationFn: (id: string) => api.delete(`/attachments/${id}`),
mutationFn: (id: string) => attachmentApi.remove(id),
onSuccess: () => queryClient.invalidateQueries({ queryKey: ['roster-profile', employeeId] }),
})
@@ -81,7 +81,7 @@ export default function BasicInfo({ profile, employeeId, attachments }: { profil
})
const updateMutation = useMutation({
mutationFn: (data: any) => api.put(`/employees/${profile.id}`, data),
mutationFn: (data: any) => employeeApi.update(profile.id, data),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['roster-profile'] })
queryClient.invalidateQueries({ queryKey: ['roster'] })
@@ -1,7 +1,6 @@
import { useState, useRef } from "react"
import { toast } from "sonner"
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"
import api from "../../lib/api"
import Card from "../../components/ui/Card"
import Button from "../../components/ui/Button"
import { Input, Label, Select } from "../../components/ui/Input"
+3 -3
View File
@@ -1,7 +1,7 @@
import { useState, useRef } from "react"
import { toast } from "sonner"
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"
import api from "../../lib/api"
import { employeeApi } from '../../lib/api-services'
import Card from "../../components/ui/Card"
import Button from "../../components/ui/Button"
import { Input, Label, Select } from "../../components/ui/Input"
@@ -17,12 +17,12 @@ export default function ContractInfo({ employeeId, contracts, hireDate }: { empl
const [previewUrl, setPreviewUrl] = useState<string | null>(null)
const addContractMutation = useMutation({
mutationFn: (data: any) => api.post('/employees/contracts', { ...data, employeeId }),
mutationFn: (data: any) => employeeApi.addContract({ ...data, employeeId }),
onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['roster-profile'] }); setShowForm(false) },
})
const deleteContractMutation = useMutation({
mutationFn: (contractId: string) => api.delete(`/employees/contracts/${contractId}`),
mutationFn: (contractId: string) => employeeApi.removeContract(contractId),
onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['roster-profile'] }); toast.success('合同已删除') },
})
@@ -1,7 +1,7 @@
import { useState, useRef } from "react"
import { toast } from "sonner"
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"
import api from "../../lib/api"
import { rosterApi } from '../../lib/api-services'
import Card from "../../components/ui/Card"
import Button from "../../components/ui/Button"
import { Input, Label, Select } from "../../components/ui/Input"
@@ -17,12 +17,12 @@ export default function DisciplinaryInfo({ employeeId, records }: { employeeId:
const [form, setForm] = useState({ violationDate: '', violationType: 'LATE', description: '', severity: 'WARNING', action: 'ORAL_WARNING', actionDetail: '', employeeAck: false, ackDate: '', ackMethod: 'SIGN', witness: '' })
const createMutation = useMutation({
mutationFn: (data: any) => api.post(`/roster/${employeeId}/disciplinary`, data),
mutationFn: (data: any) => rosterApi.createDisciplinary(employeeId, data),
onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['roster-profile'] }); setShowForm(false) },
})
const deleteMutation = useMutation({
mutationFn: (id: string) => api.delete(`/roster/${employeeId}/disciplinary/${id}`),
mutationFn: (id: string) => rosterApi.removeDisciplinary(employeeId, id),
onSuccess: () => queryClient.invalidateQueries({ queryKey: ['roster-profile'] }),
})
@@ -1,7 +1,7 @@
/** EmployeeProfile 组件 - 员工详情页 */
import { useState } from 'react'
import { useQuery } from '@tanstack/react-query'
import api from '../../lib/api'
import { rosterApi } from '../../lib/api-services'
import { DetailTab, TAB_COUNT_KEYS } from './shared'
import EmployeeProfileShell from './EmployeeProfileShell'
import BasicInfo from './BasicInfo'
@@ -23,8 +23,7 @@ export default function EmployeeProfile({ employeeId, onBack }: { employeeId: st
const { data: profile, isLoading } = useQuery<any>({
queryKey: ['roster-profile', employeeId],
queryFn: async () => {
const res = await api.get(`/roster/${employeeId}/profile`) as any
return res.data
return await rosterApi.profile(employeeId)
},
})
+2 -3
View File
@@ -1,7 +1,7 @@
import { useState, useRef } from "react"
import { toast } from "sonner"
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"
import api from "../../lib/api"
import { rosterApi } from '../../lib/api-services'
import { useAuthStore } from "../../store/authStore"
import Card from "../../components/ui/Card"
import Button from "../../components/ui/Button"
@@ -16,8 +16,7 @@ export default function EvidenceChain({ employeeId }: { employeeId: string }) {
const { data, isLoading } = useQuery<any>({
queryKey: ['evidence-chain', employeeId],
queryFn: async () => {
const res = await api.get(`/roster/${employeeId}/evidence-chain`) as any
return res.data
return await rosterApi.evidenceChain(employeeId)
},
})
@@ -1,7 +1,7 @@
import { useState, useRef } from "react"
import { toast } from "sonner"
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"
import api from "../../lib/api"
import { socialInsuranceApi } from '../../lib/api-services'
import Card from "../../components/ui/Card"
import Button from "../../components/ui/Button"
import { Input, Label, Select } from "../../components/ui/Input"
@@ -148,11 +148,8 @@ export function CityHistoryTab({ socialInsRecords, housingFundRecords, changeTyp
const correctMutation = useMutation({
mutationFn: async (data: any) => {
const cat = editing.cat
const url = cat === '社保'
? `/social/records/social/${editing.id}/correct`
: `/social/records/housing/${editing.id}/correct`
const res = await api.put(url, data) as any
return res.data
const type = cat === '社保' ? 'social' : 'housing'
return await socialInsuranceApi.correctRecord(type, editing.id, data)
},
onSuccess: () => {
toast.success('记录已修正')
@@ -1,7 +1,7 @@
import { useState, useRef } from "react"
import { toast } from "sonner"
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"
import api from "../../lib/api"
import { rosterApi } from '../../lib/api-services'
import Card from "../../components/ui/Card"
import Button from "../../components/ui/Button"
import { Input, Label, Select } from "../../components/ui/Input"
@@ -17,12 +17,12 @@ export default function PerformanceInfo({ employeeId, records }: { employeeId: s
const [form, setForm] = useState({ period: '', score: 80, grade: 'B', result: 'QUALIFIED', summary: '', improvementPlan: '', employeeAck: false, ackDate: '', reviewer: '' })
const createMutation = useMutation({
mutationFn: (data: any) => api.post(`/roster/${employeeId}/performance`, data),
mutationFn: (data: any) => rosterApi.performance(employeeId, data),
onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['roster-profile'] }); setShowForm(false) },
})
const deleteMutation = useMutation({
mutationFn: (id: string) => api.delete(`/roster/${employeeId}/performance/${id}`),
mutationFn: (id: string) => rosterApi.removePerformance(employeeId, id),
onSuccess: () => queryClient.invalidateQueries({ queryKey: ['roster-profile'] }),
})
@@ -1,7 +1,7 @@
import { useState, useRef } from "react"
import { toast } from "sonner"
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"
import api from "../../lib/api"
import { rosterApi } from '../../lib/api-services'
import Card from "../../components/ui/Card"
import Button from "../../components/ui/Button"
import { Input, Label, Select } from "../../components/ui/Input"
@@ -28,8 +28,7 @@ export default function TerminationInfo({ employeeId, profile, records }: { empl
const { data: evidenceChain, isLoading: evidenceLoading } = useQuery<any>({
queryKey: ['evidence-chain', employeeId],
queryFn: async () => {
const res = await api.get(`/roster/${employeeId}/evidence-chain`) as any
return res.data
return await rosterApi.evidenceChain(employeeId)
},
enabled: !!printRecord || showEvidence,
})
+5 -7
View File
@@ -1,7 +1,7 @@
import { useState, useRef } from "react"
import { toast } from "sonner"
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"
import api from "../../lib/api"
import { rosterApi, socialInsuranceApi } from '../../lib/api-services'
import Card from "../../components/ui/Card"
import Button from "../../components/ui/Button"
import { Input, Label, Select } from "../../components/ui/Input"
@@ -245,8 +245,7 @@ export function RehireModal({ employee, onClose, onSubmit, loading, error }: {
const { data: contractTypes = [] } = useQuery<Array<{ value: string; label: string; hasEndDate: boolean }>>({
queryKey: ['contract-types'],
queryFn: async () => {
const res = await api.get('/roster/contract-types') as any
return res.data || []
return await rosterApi.contractTypes()
},
staleTime: Infinity,
})
@@ -496,15 +495,14 @@ export function AddEmployeeModal({ onClose, onSubmit, loading, error }: {
const { data: cities = ['北京'] } = useQuery<string[]>({
queryKey: ['social-config-cities'],
queryFn: async () => {
const res = await api.get('/social/config/cities') as any
return res.data?.length ? res.data : ['北京']
const res = await socialInsuranceApi.cities() as any
return res?.length ? res : ['北京']
},
})
const { data: contractTypes = [] } = useQuery<Array<{ value: string; label: string; hasEndDate: boolean }>>({
queryKey: ['contract-types'],
queryFn: async () => {
const res = await api.get('/roster/contract-types') as any
return res.data || []
return await rosterApi.contractTypes()
},
staleTime: Infinity,
})
@@ -8,7 +8,7 @@ import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
import { toast } from 'sonner'
import { TrendingUp, Save, History, Download, ShieldCheck, Clock, DollarSign, Sparkles, Users, FileText, Calculator, Award } from 'lucide-react'
import { BarChart, Bar, XAxis, YAxis, Tooltip, ResponsiveContainer, Cell } from 'recharts'
import api from '../../lib/api'
import { dashboardApi } from '../../lib/api-services'
import Card from '../../components/ui/Card'
import Button from '../../components/ui/Button'
@@ -29,24 +29,21 @@ export default function AnnualValueReport() {
const { data: report, isLoading } = useQuery<any>({
queryKey: ['annual-value', year],
queryFn: async () => {
const res = await api.get(`/dashboard/annual-value?year=${year}`) as any
return res.data
return await dashboardApi.annualValue(year)
},
})
const { data: history } = useQuery<any>({
queryKey: ['annual-value-history'],
queryFn: async () => {
const res = await api.get('/dashboard/annual-value/history') as any
return res.data
return await dashboardApi.annualValueHistory()
},
enabled: showHistory,
})
const saveMutation = useMutation({
mutationFn: async () => {
const res = await api.post('/dashboard/annual-value/save', { year }) as any
return res.data
return await dashboardApi.annualValueSave(year)
},
onSuccess: () => {
toast.success('报告已保存')
+4 -7
View File
@@ -8,7 +8,7 @@ import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
import { toast } from 'sonner'
import { Stethoscope, Save, History, CheckCircle2, AlertCircle, AlertTriangle, ChevronDown, ChevronUp } from 'lucide-react'
import { RadialBarChart, RadialBar, PolarAngleAxis, ResponsiveContainer } from 'recharts'
import api from '../../lib/api'
import { dashboardApi } from '../../lib/api-services'
import Card from '../../components/ui/Card'
import Button from '../../components/ui/Button'
@@ -23,24 +23,21 @@ export default function HealthCheck() {
const { data: healthCheck, isLoading } = useQuery<any>({
queryKey: ['health-check'],
queryFn: async () => {
const res = await api.get('/dashboard/health-check') as any
return res.data
return await dashboardApi.healthCheck()
},
})
const { data: history } = useQuery<any>({
queryKey: ['health-check-history'],
queryFn: async () => {
const res = await api.get('/dashboard/health-check/history') as any
return res.data
return await dashboardApi.healthCheckHistory()
},
enabled: showHistory,
})
const saveMutation = useMutation({
mutationFn: async () => {
const res = await api.post('/dashboard/health-check/save') as any
return res.data
return await dashboardApi.healthCheckSave()
},
onSuccess: () => {
toast.success('诊断报告已保存')