import React, { useMemo, useState, useEffect, FC } from "react"; import { Badge, Card, Button, Select, Table, InputNumber, DatePicker } from "antd"; import { BarChart3 } from "lucide-react"; import type { Dataset } from "@/pages/DataManagement/dataset.model.ts"; const TIME_RANGE_OPTIONS = [ { label: '最近1天', value: 1 }, { label: '最近3天', value: 3 }, { label: '最近7天', value: 7 }, { label: '最近15天', value: 15 }, { label: '最近30天', value: 30 }, ]; interface LabelFilter { label: string; value: string; } interface RatioConfigItem { id: string; name: string; type: "dataset" | "label"; quantity: number; percentage: number; source: string; // dataset id labelFilter?: LabelFilter; dateRange?: [Date | null, Date | null] | null; } interface RatioConfigProps { ratioType: "dataset" | "label"; selectedDatasets: string[]; datasets: Dataset[]; totalTargetCount: number; // distributions now: { datasetId: { labelName: { labelValue: count } } } distributions: Record>>; onChange?: (configs: RatioConfigItem[]) => void; } const genId = (datasetId: string) => `${datasetId}-${Math.random().toString(36).slice(2, 9)}`; const RatioConfig: FC = ({ ratioType, selectedDatasets, datasets, totalTargetCount, distributions, onChange, }) => { const [ratioConfigs, setRatioConfigs] = useState([]); const totalConfigured = useMemo( () => ratioConfigs.reduce((sum, c) => sum + (c.quantity || 0), 0), [ratioConfigs] ); const getDatasetLabels = (datasetId: string): string[] => { const dist = distributions[String(datasetId)] || {}; return Object.keys(dist); }; const getLabelValues = (datasetId: string, label: string): string[] => { return Object.keys(distributions[String(datasetId)]?.[label] || {}); }; const addConfig = (datasetId: string) => { const dataset = datasets.find((d) => String(d.id) === datasetId); const newConfig: RatioConfigItem = { id: genId(datasetId), name: dataset?.name || datasetId, type: ratioType, quantity: 0, percentage: 0, source: datasetId, }; const newConfigs = [...ratioConfigs, newConfig]; setRatioConfigs(newConfigs); onChange?.(newConfigs); }; const removeConfig = (configId: string) => { const newConfigs = ratioConfigs.filter((c) => c.id !== configId); const adjusted = recomputePercentages(newConfigs); setRatioConfigs(adjusted); onChange?.(adjusted); }; const updateConfig = ( configId: string, updates: Partial< Pick > ) => { const newConfigs = ratioConfigs.map((c) => c.id === configId ? { ...c, ...updates } : c ); const adjusted = recomputePercentages(newConfigs); setRatioConfigs(adjusted); onChange?.(adjusted); }; const recomputePercentages = (configs: RatioConfigItem[]) => { return configs.map((c) => ({ ...c, percentage: totalTargetCount > 0 ? Math.round((c.quantity / totalTargetCount) * 100) : 0, })); }; const generateAutoRatio = () => { const selectedCount = selectedDatasets.length; if (selectedCount === 0) return; const baseQuantity = Math.floor(totalTargetCount / selectedCount); const remainder = totalTargetCount % selectedCount; let newConfigs: RatioConfigItem[] = ratioConfigs.filter( (c) => !selectedDatasets.includes(c.source) ); selectedDatasets.forEach((datasetId, index) => { const dataset = datasets.find((d) => String(d.id) === datasetId); const quantity = baseQuantity + (index < remainder ? 1 : 0); const config: RatioConfigItem = { id: genId(datasetId), name: dataset?.name || datasetId, type: ratioType, quantity, percentage: Math.round((quantity / totalTargetCount) * 100), source: datasetId, }; newConfigs.push(config); }); setRatioConfigs(newConfigs); onChange?.(newConfigs); }; useEffect(() => { const keep = ratioConfigs.filter((c) => selectedDatasets.includes(c.source) ); if (keep.length !== ratioConfigs.length) { const adjusted = recomputePercentages(keep); setRatioConfigs(adjusted); onChange?.(adjusted); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [selectedDatasets]); return (
配比配置 (已配置:{totalConfigured}/{totalTargetCount}条)
{selectedDatasets.length === 0 ? (

请先选择数据集

) : (
{ratioConfigs.length > 0 && (
总配比数量: {ratioConfigs .reduce((sum, config) => sum + config.quantity, 0) .toLocaleString()}
目标数量: {totalTargetCount.toLocaleString()}
)}
{selectedDatasets.map((datasetId) => { const dataset = datasets.find((d) => String(d.id) === datasetId); if (!dataset) return null; const datasetConfigs = ratioConfigs.filter( (c) => c.source === datasetId ); const labels = getDatasetLabels(datasetId); // helper: used values per label for this dataset (exclude a given row when needed) const getUsedValuesForLabel = (label: string, excludeId?: string) => { return new Set( datasetConfigs .filter((c) => c.id !== excludeId && c.labelFilter?.label === label) .map((c) => c.labelFilter?.value) .filter(Boolean) as string[] ); }; const columns = [ { title: "标签", dataIndex: "labelFilter", key: "labelFilter", render: (_: any, record: RatioConfigItem) => { const availableLabels = labels .map((l) => ({ label: l, value: l, disabled: getLabelValues(datasetId, l).every((v) => getUsedValuesForLabel(l, record.id).has(v)), })) return ( { if (!selectedLabel) return; updateConfig(record.id, { labelFilter: { label: selectedLabel, value: value || "", }, }); }} /> ); }, }, { title: "标签更新时间", dataIndex: "dateRange", key: "dateRange", render: (_: any, record: RatioConfigItem) => { return ( { updateConfig(record.id, { dateRange: date }); }} placeholder={["开始时间", "结束时间"]} allowClear /> ); }, }, { title: "数量", dataIndex: "quantity", key: "quantity", render: (_: any, record: RatioConfigItem) => ( updateConfig(record.id, { quantity: Number(v || 0) }) } /> ), }, { title: "操作", dataIndex: "actions", key: "actions", render: (_: any, record: RatioConfigItem) => ( ), }, ]; return (
{dataset.name} {dataset.fileCount}条
{datasetConfigs.reduce((s, c) => s + (c.percentage || 0), 0)}%
); })} )} ); }; export default RatioConfig;