aadd
import RecordCountDisplay from '@/components/RecordCountDisplay';
import { useTableScrollY } from '@/hooks/useTableScrollY';
import { Alert, Button, Card, Flex } from 'antd';
import dayjs from 'dayjs';
import { useCallback, useMemo, useState } from 'react';
import ListModeSelector from './components/ListModeSelectorCopy';
import ProposalSearchPanel from './components/ProposalSearchCopy';
import TableForProject from './components/TableForProject';
import { PROPOSAL_LIST } from './constantsCopy';
/* ------------------------------------------------------------------ */
/* 1. 常量:集中管理颜色,避免散落 */
/* ------------------------------------------------------------------ */
const CURRENT_MONTH_BG = '#f0f7ff';
const CURRENT_MONTH_COLOR = '#1677ff';
const CURRENT_MONTH_CELL_BG = '#fafcff';
/* ------------------------------------------------------------------ */
/* 2. 工具函数 */
/* ------------------------------------------------------------------ */
/** 判断某个月份字符串是否为当前月 */
const isCurrentMonth = (monthStr: string) => dayjs(monthStr).isSame(dayjs(), 'month');
/** 把 '2026/09' 或 '2026-09' 统一成 '202609',用于拼 dataIndex 的 key */
const toMonthlyKey = (monthStr: string) => `monthly_${monthStr.replace(/[\/-]/g, '')}`;
/** 当前月统一的 cell / header 样式 */
const currentMonthCellStyle = (monthStr: string) =>
isCurrentMonth(monthStr) ? { backgroundColor: CURRENT_MONTH_CELL_BG } : {};
/** 计算日期区间内的所有月份,返回 ['2026/07', '2026/08', ...] */
const getMonthsInRange = (selectDate?: string[]): string[] => {
const [start, end] = selectDate ?? [];
if (!start) return [dayjs().format('YYYY/MM')];
const startDay = dayjs(start);
// 只有一个日期时,按单月处理
const endDay = end ? dayjs(end) : startDay;
if (!startDay.isValid() || !endDay.isValid()) return [dayjs().format('YYYY/MM')];
const months: string[] = [];
let current = startDay.startOf('month');
const last = endDay.startOf('month');
while (current.isBefore(last) || current.isSame(last, 'month')) {
months.push(current.format('YYYY/MM'));
current = current.add(1, 'month');
}
return months;
};
/* ------------------------------------------------------------------ */
/* 3. 展示组件 */
/* ------------------------------------------------------------------ */
/** 表头月份标签(当月加小 badge) */
const MonthHeaderLabel: React.FC<{ monthStr: string }> = ({ monthStr }) => {
if (!isCurrentMonth(monthStr)) return <>{monthStr}</>;
return (
<span style={{ display: 'inline-flex', alignItems: 'center', gap: 6 }}>
<span style={{ fontWeight: 600 }}>{monthStr}</span>
<span
style={{
fontSize: 10,
lineHeight: '16px',
padding: '0 6px',
color: CURRENT_MONTH_COLOR,
background: CURRENT_MONTH_BG,
borderRadius: 8,
fontWeight: 400,
}}
>
当月
</span>
</span>
);
};
/* ------------------------------------------------------------------ */
/* 4. 数据格式化 */
/* ------------------------------------------------------------------ */
/** 把单行的 dataList 摊平成 monthly_YYYYMM 字段 */
const flattenMonthlyFields = (target: any, dataList: any[]) => {
if (!Array.isArray(dataList)) return;
dataList.forEach((item) => {
if (item?.monthly) {
target[`monthly_${item.monthly}`] = { ...item };
}
});
};
/** 树形数据格式化:注入唯一 key + 摊平月度字段 */
const formatTreeData = (list: any[]): any[] => {
if (!Array.isArray(list)) return [];
return list.map((row, index) => {
const rowId = row.sssAccountID ?? index;
const formattedRow: any = {
...row,
key: `parent_${rowId}`,
};
flattenMonthlyFields(formattedRow, row.dataList);
if (Array.isArray(row.children) && row.children.length > 0) {
formattedRow.children = row.children.map((child: any, cIdx: number) => {
const formattedChild: any = {
...child,
key: `child_${rowId}_${cIdx}`,
};
flattenMonthlyFields(formattedChild, child.dataList);
return formattedChild;
});
} else {
formattedRow.children = undefined;
}
return formattedRow;
});
};
/* ------------------------------------------------------------------ */
/* 5. 列构造:抽出公共部分 */
/* ------------------------------------------------------------------ */
/** 「合計有無」列的可点击展开渲染 */
const renderHasTotal = (
hasTotal: boolean,
record: any,
expandedKeys: React.Key[],
setExpandedKeys: React.Dispatch<React.SetStateAction<React.Key[]>>,
) => {
if (!hasTotal) return <span style={{ color: '#bfbfbf' }}>无</span>;
const parentKey = String(record.key ?? record.sssAccountID);
const isExpanded = expandedKeys.some((k) => String(k) === parentKey);
return (
<span
onClick={(e) => {
e.stopPropagation();
setExpandedKeys((prev) =>
isExpanded ? prev.filter((k) => String(k) !== parentKey) : [...prev, parentKey],
);
}}
style={{
cursor: 'pointer',
display: 'inline-flex',
alignItems: 'center',
gap: 6,
color: CURRENT_MONTH_COLOR,
userSelect: 'none',
}}
>
<span
style={{
fontSize: 12,
transition: 'transform 0.2s',
display: 'inline-block',
transform: isExpanded ? 'rotate(90deg)' : 'none',
}}
>
▶
</span>
<span style={{ fontWeight: 500 }}>有</span>
</span>
);
};
/** 给「合計有無」列挂上 render,其它列原样返回 */
const withHasTotalRender = (
baseCol: any,
itemName: string,
expandedKeys: React.Key[],
setExpandedKeys: React.Dispatch<React.SetStateAction<React.Key[]>>,
) => {
if (itemName === '合計有無') {
baseCol.render = (hasTotal: boolean, record: any) =>
renderHasTotal(hasTotal, record, expandedKeys, setExpandedKeys);
}
return baseCol;
};
/* ------------------------------------------------------------------ */
/* 6. 主组件 */
/* ------------------------------------------------------------------ */
const ProposalList: React.FC = () => {
const { containerRef, tableContainerRef } = useTableScrollY();
const [selectDate, setSelectDate] = useState<string[]>(['2026-09']);
const [loading, setLoading] = useState(false);
const [data, setData] = useState<any[]>([]);
const [currentMode, setCurrentMode] = useState('');
const [layout, setLayout] = useState('project');
const [expandedKeys, setExpandedKeys] = useState<React.Key[]>([]);
/* -------------------- 数据请求 -------------------- */
const fetchData = useCallback(async (nextDate: string[]) => {
setLoading(true);
try {
const params = { selectDate: nextDate };
console.log('fetch params', params);
// TODO: 替换为真实接口
// const res = await getProjectList(params);
const dataResponse: any[] = []; // 假数据见原文件
setData(formatTreeData(dataResponse));
} finally {
setLoading(false);
}
}, []);
const handleSearch = useCallback(
(nextDate: string[]) => {
setSelectDate(nextDate);
return fetchData(nextDate);
},
[fetchData],
);
/* -------------------- 选择器 -------------------- */
const handleModeChange = useCallback((value: string) => setCurrentMode(value), []);
const handleLayoutChange = useCallback((value: string) => setLayout(value), []);
/* -------------------- 列构造 -------------------- */
/** 项目轴:每个 dynamic 项展开成 children,每个月一列 */
const buildColumnsForTableProject = useCallback(
(dates: string[]) => {
const totalDate = getMonthsInRange(dates);
return PROPOSAL_LIST.map((current: any) => {
const hasChildren = current.dynamic && totalDate.length > 0;
const baseCol: any = {
title: current.itemName,
...(hasChildren ? {} : { dataIndex: current.itemVar }),
key: current.itemVar,
width: 150,
...(hasChildren
? {
children: totalDate.map((month) => ({
title: <MonthHeaderLabel monthStr={month} />,
dataIndex: [toMonthlyKey(month), current.itemVar],
align: 'right' as const,
width: 180,
onHeaderCell: () => ({ style: currentMonthCellStyle(month) }),
onCell: () => ({ style: currentMonthCellStyle(month) }),
})),
}
: {}),
};
return withHasTotalRender(baseCol, current.itemName, expandedKeys, setExpandedKeys);
});
},
[expandedKeys],
);
/** 期间轴:非 dynamic 项做固定列,dynamic 项按月份分组 */
const buildColumnsForTableDateRange = useCallback(
(dates: string[]) => {
const totalDate = getMonthsInRange(dates);
const staticItems = PROPOSAL_LIST.filter((item: any) => !item.dynamic);
const dynamicItems = PROPOSAL_LIST.filter((item: any) => item.dynamic);
const staticColumns = staticItems.map((item: any) => {
const baseCol: any = {
title: item.itemName,
dataIndex: item.itemVar,
key: item.itemVar,
width: 120,
};
return withHasTotalRender(baseCol, item.itemName, expandedKeys, setExpandedKeys);
});
const monthGroups = totalDate.map((month) => ({
title: <MonthHeaderLabel monthStr={month} />,
key: `group-${month}`,
align: 'center' as const,
onHeaderCell: () => ({ style: currentMonthCellStyle(month) }),
children: dynamicItems.map((item: any) => ({
title: item.itemName,
key: `${toMonthlyKey(month)}__${item.itemVar}`,
dataIndex: [toMonthlyKey(month), item.itemVar],
align: 'left' as const,
width: 180,
onHeaderCell: () => ({ style: currentMonthCellStyle(month) }),
onCell: () => ({ style: currentMonthCellStyle(month) }),
})),

浙公网安备 33010602011771号