From 16f22e6622f1a9e8af010088c55f890ed89de307 Mon Sep 17 00:00:00 2001 From: selfrelease Date: Sat, 1 Aug 2026 16:13:19 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20=E5=85=A8=E9=87=8F=E8=BF=81?= =?UTF-8?q?=E7=A7=BB=E5=89=8D=E7=AB=AF=20API=20=E8=B0=83=E7=94=A8=E5=88=B0?= =?UTF-8?q?=E7=BB=9F=E4=B8=80=20api-services=20=E6=9C=8D=E5=8A=A1=E5=B1=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新建 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 成功 --- frontend/src/components/HelpModal.tsx | 6 +- frontend/src/components/PortalQRModal.tsx | 7 +- frontend/src/components/SurveyModal.tsx | 4 +- frontend/src/components/layout/TopNav.tsx | 7 +- frontend/src/components/ui/AIContextEntry.tsx | 4 +- frontend/src/components/ui/CommandPalette.tsx | 6 +- frontend/src/lib/api-services-raw.ts | 11 + frontend/src/lib/api-services.ts | 776 +++++++++++++++++- frontend/src/pages/AIAssistant.tsx | 75 +- frontend/src/pages/Attendance.tsx | 59 +- frontend/src/pages/AuditLog.tsx | 18 +- frontend/src/pages/Calendar.tsx | 16 +- frontend/src/pages/CompanyFiles.tsx | 9 +- frontend/src/pages/Compensation.tsx | 3 +- frontend/src/pages/Contracts.tsx | 17 +- frontend/src/pages/Dashboard.tsx | 30 +- frontend/src/pages/Evidence.tsx | 8 +- frontend/src/pages/Money.tsx | 85 +- frontend/src/pages/Notifications.tsx | 13 +- frontend/src/pages/Policies.tsx | 14 +- frontend/src/pages/Roster.tsx | 29 +- frontend/src/pages/SalaryDashboard.tsx | 5 +- frontend/src/pages/Settings.tsx | 41 +- frontend/src/pages/SocialInsurance.tsx | 100 +-- frontend/src/pages/SpecialStatus.tsx | 22 +- frontend/src/pages/Templates.tsx | 31 +- frontend/src/pages/Termination.tsx | 37 +- frontend/src/pages/WorkProcess.tsx | 22 +- frontend/src/pages/auth/ForgotPassword.tsx | 8 +- frontend/src/pages/auth/Login.tsx | 6 +- frontend/src/pages/auth/Register.tsx | 6 +- frontend/src/pages/compliance/RiskCenter.tsx | 5 +- .../src/pages/dashboard/PerformanceStats.tsx | 5 +- frontend/src/pages/dashboard/TaskCenter.tsx | 5 +- .../src/pages/dashboard/TurnoverStats.tsx | 5 +- .../src/pages/platform/PlatformDashboard.tsx | 4 +- frontend/src/pages/platform/PlatformLogin.tsx | 10 +- frontend/src/pages/platform/PlatformOrgs.tsx | 20 +- frontend/src/pages/platform/PlatformUsers.tsx | 14 +- frontend/src/pages/portal/AutoLogin.tsx | 5 +- frontend/src/pages/portal/ContractConfirm.tsx | 12 +- frontend/src/pages/portal/EmployeeHome.tsx | 13 +- frontend/src/pages/portal/MyAttendance.tsx | 5 +- frontend/src/pages/portal/MyContract.tsx | 16 +- frontend/src/pages/portal/MyPolicies.tsx | 17 +- frontend/src/pages/portal/Onboarding.tsx | 14 +- .../src/pages/portal/OnboardingProgress.tsx | 13 +- frontend/src/pages/portal/Payslip.tsx | 18 +- frontend/src/pages/portal/PortalLogin.tsx | 18 +- .../src/pages/portal/ResignationApply.tsx | 19 +- frontend/src/pages/roster/AttachmentInfo.tsx | 6 +- .../pages/roster/AttendanceOvertimeInfo.tsx | 10 +- frontend/src/pages/roster/BasicInfo.tsx | 8 +- .../src/pages/roster/ChangeHistoryTab.tsx | 1 - frontend/src/pages/roster/ContractInfo.tsx | 6 +- .../src/pages/roster/DisciplinaryInfo.tsx | 6 +- frontend/src/pages/roster/EmployeeProfile.tsx | 5 +- frontend/src/pages/roster/EvidenceChain.tsx | 5 +- .../src/pages/roster/PayslipSocialInfo.tsx | 9 +- frontend/src/pages/roster/PerformanceInfo.tsx | 6 +- frontend/src/pages/roster/TerminationInfo.tsx | 5 +- frontend/src/pages/roster/modals.tsx | 12 +- .../src/pages/tools/AnnualValueReport.tsx | 11 +- frontend/src/pages/tools/HealthCheck.tsx | 11 +- 64 files changed, 1197 insertions(+), 597 deletions(-) create mode 100644 frontend/src/lib/api-services-raw.ts diff --git a/frontend/src/components/HelpModal.tsx b/frontend/src/components/HelpModal.tsx index 713d9a1..68229ae 100644 --- a/frontend/src/components/HelpModal.tsx +++ b/frontend/src/components/HelpModal.tsx @@ -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([]) diff --git a/frontend/src/components/PortalQRModal.tsx b/frontend/src/components/PortalQRModal.tsx index eea1d11..ab7314d 100644 --- a/frontend/src/components/PortalQRModal.tsx +++ b/frontend/src/components/PortalQRModal.tsx @@ -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}`) } diff --git a/frontend/src/components/SurveyModal.tsx b/frontend/src/components/SurveyModal.tsx index cae9df2..a5f483f 100644 --- a/frontend/src/components/SurveyModal.tsx +++ b/frontend/src/components/SurveyModal.tsx @@ -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) diff --git a/frontend/src/components/layout/TopNav.tsx b/frontend/src/components/layout/TopNav.tsx index cd19d9b..d14191c 100644 --- a/frontend/src/components/layout/TopNav.tsx +++ b/frontend/src/components/layout/TopNav.tsx @@ -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({ 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 diff --git a/frontend/src/components/ui/AIContextEntry.tsx b/frontend/src/components/ui/AIContextEntry.tsx index 313491b..d50cc4b 100644 --- a/frontend/src/components/ui/AIContextEntry.tsx +++ b/frontend/src/components/ui/AIContextEntry.tsx @@ -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, diff --git a/frontend/src/components/ui/CommandPalette.tsx b/frontend/src/components/ui/CommandPalette.tsx index 6378925..4f77b84 100644 --- a/frontend/src/components/ui/CommandPalette.tsx +++ b/frontend/src/components/ui/CommandPalette.tsx @@ -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, diff --git a/frontend/src/lib/api-services-raw.ts b/frontend/src/lib/api-services-raw.ts new file mode 100644 index 0000000..6823150 --- /dev/null +++ b/frontend/src/lib/api-services-raw.ts @@ -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 diff --git a/frontend/src/lib/api-services.ts b/frontend/src/lib/api-services.ts index a5883b2..f8849c1 100644 --- a/frontend/src/lib/api-services.ts +++ b/frontend/src/lib/api-services.ts @@ -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(): (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()), + /** 注册 */ + register: (data: any) => + post('/auth/register', data).then(unwrap()), + /** 平台登录 */ + platformLogin: (data: any) => + post('/auth/platform-login', data).then(unwrap()), + /** 发送忘记密码验证码 */ + forgotPasswordSendCode: (phone: string) => + post('/auth/forgot-password/send-code', { phone }).then(unwrap()), + /** 验证忘记密码 */ + 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()), + /** 员工分页列表 */ + paged: (params: { page?: number; pageSize?: number; search?: string; department?: string }) => + get('/employees', { params }).then(unwrap()), /** 员工详情 */ detail: (id: string) => get(`/employees/${id}`).then(unwrap()), - /** 员工档案(花名册) */ - profile: (id: string) => - get(`/roster/${id}/profile`).then(unwrap()), + /** 创建员工 */ + 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()), + /** 员工档案 */ + profile: (employeeId: string) => + get(`/roster/${employeeId}/profile`).then(unwrap()), + /** 调薪 */ + 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()), + /** 删除附件 */ + remove: (id: string) => + del(`/attachments/${id}`), } // ========== 仪表盘相关 ========== @@ -92,6 +186,9 @@ export const dashboardApi = { /** 体检诊断历史 */ healthCheckHistory: () => get('/dashboard/health-check/history').then(unwrap()), + /** 体检诊断保存 */ + healthCheckSave: () => + post('/dashboard/health-check/save').then(unwrap()), /** 风险中心(与工作台同口径) */ risks: () => get('/dashboard/risks').then(unwrap()), @@ -119,6 +216,9 @@ export const dashboardApi = { /** 年度价值报告历史 */ annualValueHistory: () => get('/dashboard/annual-value/history').then(unwrap()), + /** 年度价值报告保存 */ + annualValueSave: (year: number) => + post('/dashboard/annual-value/save', { year }).then(unwrap()), } // ========== 考勤相关 ========== @@ -140,4 +240,674 @@ export const attendanceApi = { get('/attendance/leaves').then(unwrap()), publishRecords: () => get('/attendance/publish-records').then(unwrap()), + /** 发布考勤 */ + publish: (month: string) => + post('/attendance/publish', { month }).then(unwrap()), + /** 取消发布 */ + cancelPublish: (id: string) => + post(`/attendance/publish/${id}/cancel`).then(unwrap()), + /** 批量确认 */ + batchConfirm: (data: { month: string; all?: boolean; ids?: string[] }) => + post('/attendance/batch-confirm', data).then(unwrap()), + /** 单条确认 */ + confirm: (data: { employeeId: string; month: string }) => + post('/attendance/confirm', data).then(unwrap()), + /** 创建/更新班次 */ + 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()), + /** 对话详情 */ + conversation: (id: string) => + get(`/ai/conversations/${id}`).then(unwrap()), + /** 创建对话 */ + createConversation: (data: any) => + post('/ai/conversations', data).then(unwrap()), + /** 更新对话 */ + 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()), + /** AI 上下文问答 */ + contextAsk: (data: any) => + post('/ai/context-ask', data).then(unwrap()), + /** 合同审查上传 */ + reviewUpload: (formData: FormData) => + post('/ai/review/upload', formData, { headers: { 'Content-Type': 'multipart/form-data' } }).then(unwrap()), + /** 合同审查 */ + review: (contractText: string) => + post('/ai/review', { contractText }).then(unwrap()), + /** 保存审查结果 */ + reviewSave: (data: any) => + post('/ai/review/save', data), + /** 案例匹配 */ + matchCase: (scenario: string) => + post('/ai/match-case', { scenario }).then(unwrap()), + /** 案例转待办 */ + caseToTodo: (data: any) => + post('/ai/case-to-todo', data), + /** RAG 知识库列表 */ + ragList: () => + get('/ai/rag/list').then(unwrap()), + /** 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()), + /** RAG 知识库种子数据 */ + ragSeed: () => + post('/ai/rag/seed'), +} + +// ========== 薪酬相关 ========== + +export const payrollApi = { + /** 薪酬模版列表 */ + template: () => + get('/payroll2/template').then(unwrap()), + /** 创建模版项 */ + 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()), + /** 批次列表 */ + batches: (params?: any) => + get('/payroll2/batches', { params }).then(unwrap()), + /** 归档批次列表 */ + archivedBatches: () => + get('/payroll2/batches/archived/list').then(unwrap()), + /** 批次详情 */ + batchDetail: (id: string) => + get(`/payroll2/batches/${id}`).then(unwrap()), + /** 创建批次 */ + 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()), + /** 生成工资条 */ + generatePayslips: (month: string) => + post('/payroll2/payslips/generate', { month }).then(unwrap()), + /** 加班费记录列表 */ + overtimeRecords: (params: { month?: string; employeeId?: string }) => + get('/payroll/overtime', { params }).then(unwrap()), + /** 保存加班费记录 */ + saveOvertime: (data: any) => + post('/payroll/overtime', data).then(unwrap()), + /** 更新加班费记录 */ + updateOvertime: (id: string, data: any) => + put(`/payroll/overtime/${id}`, data).then(unwrap()), + /** 批量导入加班工时 */ + batchImportOvertime: (data: any[]) => + post('/payroll/overtime/batch', data), + /** 导入加班费到批次 */ + importOvertimeToBatch: (batchId: string) => + post(`/payroll/overtime/import-to-batch/${batchId}`).then(unwrap()), + /** 加班费配置 */ + overtimeConfig: () => + get('/payroll/overtime/config').then(unwrap()), + /** 保存加班费配置 */ + saveOvertimeConfig: (data: any) => + post('/payroll/overtime/config', data), + /** 工资条列表 */ + payslips: (params: { month?: string; employeeId?: string }) => + get('/payroll/payslip', { params }).then(unwrap()), + /** 创建/更新工资条 */ + savePayslip: (data: any) => + post('/payroll/payslip', data).then(unwrap()), + /** 删除工资条 */ + removePayslip: (id: string) => + del(`/payroll/payslip/${id}`), + /** 个税试算 */ + taxPreview: (data: any) => + post('/payroll/tax-preview', data).then(unwrap()), + /** 薪资汇总表 */ + batchSummary: (id: string) => + get(`/payroll/batch/${id}/summary`).then(unwrap()), + /** 薪资明细表(旧接口) */ + batchDetailExport: (id: string) => + get(`/payroll/batch/${id}/detail`).then(unwrap()), +} + +// ========== 薪资仪表盘 ========== + +export const salaryDashboardApi = { + data: (year: number) => + get('/salary/dashboard', { params: { year } }).then(unwrap()), +} + +// ========== 社保公积金相关 ========== + +export const socialInsuranceApi = { + /** 城市列表 */ + cities: () => + get('/social/config/cities').then(unwrap()), + /** 社保配置 */ + config: (city: string) => + get('/social/config', { params: { city } }).then(unwrap()), + /** 公积金配置 */ + housingConfig: (city: string) => + get('/social/housing-config', { params: { city } }).then(unwrap()), + /** 社保配置版本列表 */ + configVersions: (city: string) => + get('/social/config/versions', { params: { city } }).then(unwrap()), + /** 公积金配置版本列表 */ + housingConfigVersions: (city?: string) => + get('/social/housing-config/versions', { params: city ? { city } : {} }).then(unwrap()), + /** 创建社保配置版本 */ + 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()), + /** 公积金计算 */ + housingCalculate: (base: number, city: string) => + post('/social/housing-calculate', { base, city }).then(unwrap()), + /** AI 建议 */ + aiSuggest: (data: { city: string; effectiveFrom: string; type: 'social' | 'housing' }) => + post('/social/ai-suggest', data).then(unwrap()), + /** 调整预览 */ + adjustPreview: (configId: string) => + get(`/social/config/${configId}/adjust-preview`).then(unwrap()), + /** 公积金调整预览 */ + housingAdjustPreview: (configId: string) => + get(`/social/housing-config/${configId}/adjust-preview`).then(unwrap()), + /** 应用调整 */ + 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()), + /** 月度办理状态 */ + monthlyProcessStatus: (month: string) => + get('/social/monthly-process/status', { params: { month } }).then(unwrap()), + /** 完成月度办理 */ + completeMonthlyProcess: (data: any) => + post('/social/monthly-process/complete', data), + /** 专项附加扣除批量 */ + specialDeductionBatch: (month: string) => + get('/social/special-deduction/batch', { params: { month } }).then(unwrap()), + /** 保存专项附加扣除 */ + saveSpecialDeduction: (data: any) => + post('/social/special-deduction', data), + /** 活跃申报员工 */ + activeDeclaration: (month: string) => + get('/social/active-declaration', { params: { month } }).then(unwrap()), + /** 社保/公积金记录更正 */ + correctRecord: (type: 'social' | 'housing', id: string, data: any) => + put(`/social/records/${type}/${id}/correct`, data).then(unwrap()), +} + +// ========== 商业保险 ========== + +export const commercialInsuranceApi = { + /** 方案列表 */ + plans: () => + get('/commercial-insurance/plans').then(unwrap()), + /** 方案详情(含参保人员) */ + enrollments: (planId: string) => + get(`/commercial-insurance/plans/${planId}/enrollments`).then(unwrap()), + /** 创建/更新方案 */ + 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()), + /** 草稿详情 */ + detail: (draftId: string) => + get(`/termination/detail/${draftId}`).then(unwrap()), + /** 提交审批 */ + 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()), + /** 风险评估 */ + assess: (employeeId: string, reason: string) => + get(`/termination/assess/${employeeId}`, { params: { reason } }).then(unwrap()), + /** 批量预览 */ + batchPreview: (items: any[]) => + post('/termination/batch/preview', { items }), +} + +// ========== 制度相关 ========== + +export const policiesApi = { + /** 制度列表 */ + list: (params: { page?: number; pageSize?: number; status?: string }) => + get('/policies', { params }).then(unwrap()), + /** 制度详情 */ + detail: (id: string) => + get(`/policies/${id}`).then(unwrap()), + /** 创建制度 */ + 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()), +} + +// ========== 证据链相关 ========== + +export const evidenceApi = { + /** 证据链列表 */ + list: (params: { page?: number; pageSize?: number; category?: string }) => + get('/evidence', { params }).then(unwrap()), + /** 全量验证 */ + verifyAll: () => + get('/evidence/verify-all').then(unwrap()), +} + +// ========== 审计日志 ========== + +export const auditApi = { + /** 审计日志列表 */ + list: (params: { page?: number; pageSize?: number; entity?: string; dateFrom?: string; dateTo?: string }) => + get('/audit', { params }).then(unwrap()), + /** 审计统计 */ + stats: () => + get('/audit/stats').then(unwrap()), +} + +// ========== 日历相关 ========== + +export const calendarApi = { + /** 自定义事件列表 */ + events: (month: string) => + get(`/calendar?month=${month}`).then(unwrap()), + /** 创建事件 */ + 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()), + /** 添加文件 */ + 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()), + /** 通知设置 */ + settings: () => + get('/notifications/settings').then(unwrap()), + /** 更新通知设置 */ + updateSettings: (data: any) => + put('/notifications/settings', data), +} + +// ========== 系统设置 ========== + +export const settingsApi = { + /** 组织设置 */ + org: () => + get('/settings/org').then(unwrap()), + /** 更新组织设置 */ + updateOrg: (data: any) => + put('/settings/org', data), + /** 用户列表 */ + users: () => + get('/settings/users').then(unwrap()), + /** 添加用户 */ + 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()), + /** 更新套餐 */ + updatePlan: (plan: string) => + put('/settings/plan', { plan }), + /** 退休政策 */ + retirementPolicy: () => + get('/settings/retirement-policy').then(unwrap()), +} + +// ========== 模板相关 ========== + +export const templatesApi = { + /** 系统模板列表 */ + list: (category?: string) => + get('/templates', { params: category ? { category } : {} }).then(unwrap()), + /** 系统模板详情 */ + detail: (id: string) => + get(`/templates/${id}`).then(unwrap()), + /** 渲染模板 */ + render: (id: string, variables: any) => + post(`/templates/${id}/render`, { variables }).then(unwrap()), + /** 企业模板列表 */ + enterpriseList: (params: { page?: number; pageSize?: number; category?: string }) => + get('/enterprise-templates', { params }).then(unwrap()), + /** 企业模板详情 */ + enterpriseDetail: (id: string) => + get(`/enterprise-templates/${id}`).then(unwrap()), + /** 创建/更新企业模板 */ + 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()), +} + +// ========== 工作流程 ========== + +export const workProcessApi = { + /** 列表 */ + list: (params: { page?: number; pageSize?: number; type?: string; status?: string }) => + get('/work-processes', { params }).then(unwrap()), + /** 详情 */ + detail: (id: string) => + get(`/work-processes/${id}`).then(unwrap()), + /** 创建 */ + create: (data: any) => + post('/work-processes', data).then(unwrap()), + /** 提交 */ + submit: (id: string) => + post(`/work-processes/${id}/submit`).then(unwrap()), + /** 取消 */ + cancel: (id: string) => + post(`/work-processes/${id}/cancel`).then(unwrap()), + /** 删除 */ + remove: (id: string) => + del(`/work-processes/${id}`), + /** 预览 */ + preview: (id: string) => + get(`/work-processes/${id}/preview`).then(unwrap()), +} + +// ========== 特殊状态台账 ========== + +export const specialStatusApi = { + /** 列表 */ + list: (params: { page?: number; pageSize?: number; search?: string; type?: string; status?: string }) => + get('/special-statuses', { params }).then(unwrap()), + /** 统计概览 */ + stats: () => + get('/special-statuses/stats/overview').then(unwrap()), + /** 创建 */ + 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()), +} + +// ========== 问卷 ========== + +export const surveyApi = { + /** 提交问卷 */ + submit: (items: any[]) => + post('/survey/submit', { items }), +} + +// ========== 平台管理 ========== + +export const platformApi = { + /** 平台仪表盘 */ + dashboard: () => + get('/platform/dashboard').then(unwrap()), + /** 组织列表 */ + orgs: (params: { page?: number; pageSize?: number; search?: string; plan?: string }) => + get('/platform/orgs', { params }).then(unwrap()), + /** 组织详情 */ + orgDetail: (id: string) => + get(`/platform/orgs/${id}`).then(unwrap()), + /** 创建组织 */ + 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()), + /** 切换用户状态 */ + 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()), + /** 发送验证码 */ + sendCode: (phone: string) => + portalPost('/send-code', { phone }).then(unwrap()), + /** 验证码登录 */ + verifyCode: (phone: string, code: string) => + portalPost('/verify-code', { phone, code }).then(unwrap()), + /** 自动登录 */ + autoLogin: (token: string) => + portalGet('/auto-login', { params: { token } }).then(unwrap()), + /** 生成自动登录令牌(管理端) */ + generateAutoLoginToken: (employeeId: string) => + post('/portal/auto-login-token', { employeeId }).then(unwrap()), + /** 首页概览 */ + homeOverview: () => + portalGet('/home/overview').then(unwrap()), + /** 工资条 */ + payslip: (month: string) => + portalGet('/payslip', { params: { month } }).then(unwrap()), + /** 工资条历史 */ + payslipHistory: () => + portalGet('/payslip/history').then(unwrap()), + /** 确认工资条 */ + confirmPayslip: (id: string) => + portalPost(`/payslip/${id}/confirm`), + /** 考勤 */ + attendance: (month: string) => + portalGet('/attendance', { params: { month } }).then(unwrap()), + /** 合同 */ + contract: () => + portalGet('/contract').then(unwrap()), + /** 合同信息 */ + contractInfo: (token: string) => + portalGet(`/contract-confirm/${token}`).then(unwrap()), + /** 重发合同确认 */ + resendContractConfirm: (contractId: string) => + portalPost('/contract-confirm/resend', { contractId }).then(unwrap()), + /** 合同确认-发送验证码 */ + contractConfirmSendCode: (token: string) => + portalPost('/contract-confirm/send-code', { token }).then(unwrap()), + /** 合同确认 */ + contractConfirm: (token: string, verifyCode: string) => + portalPost('/contract-confirm', { token, agreed: true, verifyCode }), + /** 入职链接信息 */ + onboardingInfo: (token: string) => + portalGet(`/onboarding/${token}`).then(unwrap()), + /** 入职提交 */ + onboardingSubmit: (data: any) => + portalPost('/onboarding', data), + /** 入职上传文件 */ + onboardingUpload: (token: string, formData: FormData) => + portalPost(`/onboarding/${token}/upload`, formData, { headers: { 'Content-Type': 'multipart/form-data' } }).then(unwrap()), + /** 入职进度 */ + onboardingProgress: () => + portalGet('/onboarding/progress').then(unwrap()), + /** 制度列表 */ + policies: () => + portalGet('/policies').then(unwrap()), + /** 制度详情 */ + policyDetail: (id: string) => + portalGet(`/policies/${id}`).then(unwrap()), + /** 制度阅读确认 */ + policyRead: (id: string) => + portalPost(`/policies/${id}/read`), + /** 离职状态 */ + resignationStatus: () => + portalGet('/resignation/status').then(unwrap()), + /** 提交离职申请 */ + resignationSubmit: (data: any) => + portalPost('/resignation/submit', data).then(unwrap()), + /** 撤回离职申请 */ + resignationWithdraw: (id: string) => + portalPost(`/resignation/${id}/withdraw`).then(unwrap()), } diff --git a/frontend/src/pages/AIAssistant.tsx b/frontend/src/pages/AIAssistant.tsx index 8776bbb..6fd45fa 100644 --- a/frontend/src/pages/AIAssistant.tsx +++ b/frontend/src/pages/AIAssistant.tsx @@ -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({ 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({ 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 = { LATE: '迟到', ABSENT: '旷工', INSUBORDINATION: '不服从管理', MISCONDUCT: '违纪', VIOLATE_POLICY: '违反制度', OTHER: '其他' } const actionMap: Record = { 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({ 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'] }), }) diff --git a/frontend/src/pages/Attendance.tsx b/frontend/src/pages/Attendance.tsx index f0e352b..49ebccf 100644 --- a/frontend/src/pages/Attendance.tsx +++ b/frontend/src/pages/Attendance.tsx @@ -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({ 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({ 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({ 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({ 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({ 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({ 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({ 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({ 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({ 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'] }), }) diff --git a/frontend/src/pages/AuditLog.tsx b/frontend/src/pages/AuditLog.tsx index 7e219c1..c5b7d1e 100644 --- a/frontend/src/pages/AuditLog.tsx +++ b/frontend/src/pages/AuditLog.tsx @@ -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({ 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({ queryKey: ['audit-stats'], queryFn: async () => { - const res = await api.get('/audit/stats') as any - return res.data + return await auditApi.stats() }, }) diff --git a/frontend/src/pages/Calendar.tsx b/frontend/src/pages/Calendar.tsx index 0f03f19..6dde954 100644 --- a/frontend/src/pages/Calendar.tsx +++ b/frontend/src/pages/Calendar.tsx @@ -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({ 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({ 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'] }) diff --git a/frontend/src/pages/CompanyFiles.tsx b/frontend/src/pages/CompanyFiles.tsx index 6560cae..a35ab85 100644 --- a/frontend/src/pages/CompanyFiles.tsx +++ b/frontend/src/pages/CompanyFiles.tsx @@ -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({ 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('已删除') diff --git a/frontend/src/pages/Compensation.tsx b/frontend/src/pages/Compensation.tsx index 259ff11..4fe452c 100644 --- a/frontend/src/pages/Compensation.tsx +++ b/frontend/src/pages/Compensation.tsx @@ -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({ 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 }, }) diff --git a/frontend/src/pages/Contracts.tsx b/frontend/src/pages/Contracts.tsx index c0e5bf0..c960d67 100644 --- a/frontend/src/pages/Contracts.tsx +++ b/frontend/src/pages/Contracts.tsx @@ -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({ 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({ 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] }), }) diff --git a/frontend/src/pages/Dashboard.tsx b/frontend/src/pages/Dashboard.tsx index a1e2bf8..338315d 100644 --- a/frontend/src/pages/Dashboard.tsx +++ b/frontend/src/pages/Dashboard.tsx @@ -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({ queryKey: ['dashboard'], - queryFn: async () => { - const res = await api.get('/dashboard') as any - return res.data - }, + queryFn: () => dashboardApi.data(), }) const { data: expiringContracts } = useQuery({ @@ -65,10 +62,7 @@ export default function Dashboard() { const { data: costAnalysis } = useQuery({ 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({ @@ -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() {