diff --git a/src/views/Company/components/ForecastReport/components/DetailTable.tsx b/src/views/Company/components/ForecastReport/components/DetailTable.tsx index 975a8144..5dbab05d 100644 --- a/src/views/Company/components/ForecastReport/components/DetailTable.tsx +++ b/src/views/Company/components/ForecastReport/components/DetailTable.tsx @@ -1,5 +1,6 @@ /** * 详细数据表格 - 黑金主题 + * 优化:斑马纹、等宽字体、首列高亮、重要行强调、预测列区分 */ import React, { useMemo } from 'react'; @@ -8,6 +9,12 @@ import { Table, ConfigProvider, Tag, theme as antTheme } from 'antd'; import type { ColumnsType } from 'antd/es/table'; import type { DetailTableProps, DetailTableRow } from '../types'; +// 判断是否为预测年份 +const isForecastYear = (year: string) => year.includes('E'); + +// 重要指标(需要高亮的行) +const IMPORTANT_METRICS = ['归母净利润', 'ROE', 'EPS', '营业总收入']; + // Ant Design 黑金主题配置 const BLACK_GOLD_THEME = { algorithm: antTheme.darkAlgorithm, @@ -23,43 +30,103 @@ const BLACK_GOLD_THEME = { }, components: { Table: { - headerBg: 'rgba(212, 175, 55, 0.1)', + headerBg: 'rgba(212, 175, 55, 0.12)', headerColor: '#D4AF37', - rowHoverBg: 'rgba(212, 175, 55, 0.05)', + rowHoverBg: 'rgba(212, 175, 55, 0.08)', borderColor: 'rgba(212, 175, 55, 0.2)', - cellPaddingBlock: 8, - cellPaddingInline: 12, + cellPaddingBlock: 12, // 增加行高 + cellPaddingInline: 14, }, }, }; -// 表格样式 +// 表格样式 - 斑马纹、等宽字体、预测列区分 const tableStyles = ` + /* 固定列背景 */ .forecast-detail-table .ant-table-cell-fix-left, .forecast-detail-table .ant-table-cell-fix-right { background: #1A202C !important; } .forecast-detail-table .ant-table-thead .ant-table-cell-fix-left, .forecast-detail-table .ant-table-thead .ant-table-cell-fix-right { - background: rgba(26, 32, 44, 0.95) !important; + background: rgba(26, 32, 44, 0.98) !important; } .forecast-detail-table .ant-table-tbody > tr:hover > td.ant-table-cell-fix-left { background: #242d3d !important; } + + /* 指标标签样式 */ .forecast-detail-table .metric-tag { background: rgba(212, 175, 55, 0.15); border-color: rgba(212, 175, 55, 0.3); color: #D4AF37; + font-weight: 500; + } + + /* 重要指标行高亮 */ + .forecast-detail-table .important-row { + background: rgba(212, 175, 55, 0.06) !important; + } + .forecast-detail-table .important-row .metric-tag { + background: rgba(212, 175, 55, 0.25); + color: #FFD700; + font-weight: 600; + } + + /* 斑马纹 - 奇数行 */ + .forecast-detail-table .ant-table-tbody > tr:nth-child(odd) > td { + background: rgba(255, 255, 255, 0.02); + } + .forecast-detail-table .ant-table-tbody > tr:nth-child(odd):hover > td { + background: rgba(212, 175, 55, 0.08) !important; + } + + /* 等宽字体 - 数值列 */ + .forecast-detail-table .data-cell { + font-family: 'SF Mono', 'Monaco', 'Menlo', 'Consolas', monospace; + font-variant-numeric: tabular-nums; + letter-spacing: -0.02em; + } + + /* 预测列样式 */ + .forecast-detail-table .forecast-col { + background: rgba(212, 175, 55, 0.04) !important; + font-style: italic; + } + .forecast-detail-table .ant-table-thead .forecast-col { + color: #FFD700 !important; + font-weight: 600; + } + + /* 负数红色显示 */ + .forecast-detail-table .negative-value { + color: #FC8181; + } + + /* 正增长绿色 */ + .forecast-detail-table .positive-growth { + color: #68D391; + } + + /* 表头预测/历史分隔线 */ + .forecast-detail-table .forecast-divider { + border-left: 2px solid rgba(212, 175, 55, 0.5) !important; } `; interface TableRowData extends DetailTableRow { key: string; + isImportant?: boolean; } const DetailTable: React.FC = ({ data }) => { const { years, rows } = data; + // 找出预测年份起始索引 + const forecastStartIndex = useMemo(() => { + return years.findIndex(isForecastYear); + }, [years]); + // 构建列配置 const columns: ColumnsType = useMemo(() => { const cols: ColumnsType = [ @@ -69,35 +136,65 @@ const DetailTable: React.FC = ({ data }) => { key: '指标', fixed: 'left', width: 160, - render: (value: string) => ( - {value} + render: (value: string, record: TableRowData) => ( + + {value} + ), }, ]; // 添加年份列 - years.forEach((year) => { + years.forEach((year, idx) => { + const isForecast = isForecastYear(year); + const isFirstForecast = idx === forecastStartIndex; + cols.push({ - title: year, + title: isForecast ? `${year}` : year, dataIndex: year, key: year, align: 'right', - width: 100, - render: (value: string | number | null) => value ?? '-', + width: 110, + className: `${isForecast ? 'forecast-col' : ''} ${isFirstForecast ? 'forecast-divider' : ''}`, + render: (value: string | number | null, record: TableRowData) => { + if (value === null || value === undefined) return '-'; + + // 格式化数值 + const numValue = typeof value === 'number' ? value : parseFloat(value); + const isNegative = !isNaN(numValue) && numValue < 0; + const isGrowthMetric = record['指标']?.includes('增长') || record['指标']?.includes('率'); + const isPositiveGrowth = isGrowthMetric && !isNaN(numValue) && numValue > 0; + + // 数值类添加样式类名 + const className = `data-cell ${isNegative ? 'negative-value' : ''} ${isPositiveGrowth ? 'positive-growth' : ''}`; + + return {value}; + }, }); }); return cols; - }, [years]); + }, [years, forecastStartIndex]); // 构建数据源 const dataSource: TableRowData[] = useMemo(() => { - return rows.map((row, idx) => ({ - ...row, - key: `row-${idx}`, - })); + return rows.map((row, idx) => { + const metric = row['指标'] as string; + const isImportant = IMPORTANT_METRICS.some(m => metric?.includes(m)); + + return { + ...row, + key: `row-${idx}`, + isImportant, + }; + }); }, [rows]); + // 行类名 + const rowClassName = (record: TableRowData) => { + return record.isImportant ? 'important-row' : ''; + }; + return ( @@ -109,9 +206,10 @@ const DetailTable: React.FC = ({ data }) => { columns={columns} dataSource={dataSource} pagination={false} - size="small" + size="middle" scroll={{ x: 'max-content' }} bordered + rowClassName={rowClassName} />