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) }),
        })),
 
posted @ 2026-10-09 15:05  小兔子09  阅读(3)  评论(0)    收藏  举报