"use client"; import { useState, useEffect, useCallback } from "react"; import { useSearchParams } from "next/navigation"; import { motion } from "motion/react"; import { Card, CardContent, CardHeader, CardTitle, } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Badge } from "@/components/ui/badge"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "@/components/ui/table"; import { TrendingUp, TrendingDown, Users, Wallet, Download, Loader2, Sparkles, MessageSquare, Send, PiggyBank, Building2, } from "lucide-react"; import { BarChart, Bar, PieChart, Pie, Cell, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, Legend, } from "recharts"; import { api } from "@/lib/api/client"; interface CostSummary { total_cost: number; salary_cost: number; social_security_cost: number; fund_cost: number; employee_count: number; } interface DepartmentCost { department: string; employee_count: number; salary_cost: number; social_security_cost: number; fund_cost: number; total_cost: number; } interface ExpenseBreakdown { expense_type: string; amount: number; } interface FullAnalysis { summary: CostSummary; departments: DepartmentCost[]; expenses: ExpenseBreakdown[]; changes?: Record; ai_summary?: string; } interface SuggestedQuestions { questions: string[]; } interface QAResponse { answer: string; data_points: string[]; } const COLORS = ["#6366f1", "#10b981", "#f59e0b", "#ef4444", "#8b5cf6", "#06b6d4"]; const container = { hidden: { opacity: 0 }, show: { opacity: 1, transition: { staggerChildren: 0.08 }, }, }; const item = { hidden: { opacity: 0, y: 12 }, show: { opacity: 1, y: 0, transition: { duration: 0.4, ease: [0.16, 1, 0.3, 1] as const }, }, }; function formatCurrency(value: number): string { return new Intl.NumberFormat("zh-CN", { style: "currency", currency: "CNY", minimumFractionDigits: 2, }).format(value); } function formatNumber(value: number): string { return new Intl.NumberFormat("zh-CN").format(value); } export default function AnalysisPage() { const searchParams = useSearchParams(); const [analysis, setAnalysis] = useState(null); const [loading, setLoading] = useState(true); const [taskId, setTaskId] = useState(searchParams.get("task_id") || ""); const [prevTaskId, setPrevTaskId] = useState(searchParams.get("prev_task_id") || ""); const [questions, setQuestions] = useState([]); const [answer, setAnswer] = useState(""); const [askLoading, setAskLoading] = useState(false); const [customQuestion, setCustomQuestion] = useState(""); const loadAnalysis = useCallback(async () => { if (!taskId) { setLoading(false); return; } setLoading(true); try { const params: Record = {}; if (prevTaskId) params.prev_task_id = prevTaskId; const res = await api.get( `/api/analysis/labor-cost/${taskId}`, { params } ); setAnalysis(res.data); } catch (error) { console.error("加载成本分析失败:", error); } finally { setLoading(false); } }, [taskId, prevTaskId]); const loadQuestions = useCallback(async () => { if (!taskId) return; try { const res = await api.get( `/api/qa/suggested-questions`, { params: { task_id: taskId, context: "cost_analysis" } } ); setQuestions(res.data.questions || []); } catch (error) { console.error("加载建议问题失败:", error); } }, [taskId]); useEffect(() => { loadAnalysis(); loadQuestions(); }, [loadAnalysis, loadQuestions]); async function askQuestion(question: string) { if (!question || !taskId) return; setAskLoading(true); setAnswer(""); try { const res = await api.post(`/api/qa/ask`, { task_id: Number(taskId), question, context: "cost_analysis", }); setAnswer(res.data.answer); } catch (error) { console.error("提问失败:", error); setAnswer("暂时无法回答该问题,请稍后重试。"); } finally { setAskLoading(false); } } function handleExport() { if (!taskId) return; const token = localStorage.getItem("auth_token"); const companyId = localStorage.getItem("company_id"); const url = `${process.env.NEXT_PUBLIC_API_URL}/api/analysis/labor-cost/${taskId}/export`; fetch(url, { headers: { Authorization: `Bearer ${token}`, "X-Company-ID": companyId || "", }, }) .then((res) => res.blob()) .then((blob) => { const a = document.createElement("a"); a.href = URL.createObjectURL(blob); a.download = `cost_analysis_${taskId}.xlsx`; a.click(); URL.revokeObjectURL(a.href); }); } const deptChartData = analysis?.departments.map((d) => ({ name: d.department, 工资: d.salary_cost, 社保: d.social_security_cost, 公积金: d.fund_cost, })) || []; const expenseChartData = analysis?.expenses.map((e) => ({ name: e.expense_type, value: e.amount, })) || []; return (

人工成本分析

基于对账数据的多维度成本分析

{analysis && ( )}
setTaskId(e.target.value)} className="h-9" />
setPrevTaskId(e.target.value)} className="h-9" />
{loading ? (
) : !analysis ? (

请输入任务ID开始成本分析

) : ( <>

人工成本总额

{formatCurrency(analysis.summary.total_cost)}

工资成本

{formatCurrency(analysis.summary.salary_cost)}

占比 {((analysis.summary.salary_cost / analysis.summary.total_cost) * 100).toFixed(1)}%

社保+公积金

{formatCurrency(analysis.summary.social_security_cost + analysis.summary.fund_cost)}

占比 {(((analysis.summary.social_security_cost + analysis.summary.fund_cost) / analysis.summary.total_cost) * 100).toFixed(1)}%

员工人数

{formatNumber(analysis.summary.employee_count)}

人均 {formatCurrency(analysis.summary.total_cost / (analysis.summary.employee_count || 1))}

{analysis.ai_summary && (

AI 成本分析摘要

{analysis.ai_summary}

)} {analysis.changes && ( 环比变化
{[ { label: "总成本", data: analysis.changes.total_cost }, { label: "工资成本", data: analysis.changes.salary_cost }, { label: "社保成本", data: analysis.changes.social_security_cost }, { label: "公积金成本", data: analysis.changes.fund_cost }, ].map((item) => { const change = item.data?.amount_change || 0; const ratio = item.data?.ratio_change || 0; const isUp = change > 0; return (

{item.label}

{formatCurrency(item.data?.current || 0)}

{isUp ? ( ) : ( )} {isUp ? "+" : ""}{formatCurrency(change)} ({ratio}%)
); })}
)}
部门成本拆分 {deptChartData.length > 0 ? ( formatCurrency(Number(value))} contentStyle={{ borderRadius: "8px", border: "1px solid hsl(var(--border))" }} /> ) : (

暂无部门数据

)}
费用科目拆分 {expenseChartData.length > 0 ? ( `${entry.name}: ${formatCurrency(entry.value)}`} > {expenseChartData.map((_, index) => ( ))} formatCurrency(Number(value))} /> ) : (

暂无费用科目数据

)}
部门明细表 部门 人数 工资成本 社保成本 公积金成本 合计 {analysis.departments.map((dept) => ( {dept.department} {dept.employee_count} {formatCurrency(dept.salary_cost)} {formatCurrency(dept.social_security_cost)} {formatCurrency(dept.fund_cost)} {formatCurrency(dept.total_cost)} ))}
AI 问答
{questions.length > 0 && (
{questions.map((q, i) => ( ))}
)}
setCustomQuestion(e.target.value)} onKeyDown={(e) => { if (e.key === "Enter" && customQuestion) { askQuestion(customQuestion); setCustomQuestion(""); } }} />
{askLoading && (
正在思考...
)} {answer && !askLoading && (

{answer}

)}
)}
); }