style(MainBusinessAnalysis): 优化历史对比表格布局

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
zdl
2025-12-16 20:15:57 +08:00
parent ab7164681a
commit 83b24b6d54

View File

@@ -4,18 +4,9 @@
import React, { useMemo } from 'react'; import React, { useMemo } from 'react';
import { import {
VStack, Flex,
Grid,
GridItem,
Box, Box,
Heading, Heading,
Table,
Thead,
Tbody,
Tr,
Th,
Td,
TableContainer,
Alert, Alert,
AlertIcon, AlertIcon,
} from '@chakra-ui/react'; } from '@chakra-ui/react';
@@ -45,7 +36,7 @@ const BLACK_GOLD_THEME = {
algorithm: antTheme.darkAlgorithm, algorithm: antTheme.darkAlgorithm,
token: { token: {
colorPrimary: '#D4AF37', colorPrimary: '#D4AF37',
colorBgContainer: 'transparent', colorBgContainer: '#1A202C',
colorBgElevated: '#1a1a2e', colorBgElevated: '#1a1a2e',
colorBorder: 'rgba(212, 175, 55, 0.3)', colorBorder: 'rgba(212, 175, 55, 0.3)',
colorText: '#e0e0e0', colorText: '#e0e0e0',
@@ -65,41 +56,79 @@ const BLACK_GOLD_THEME = {
}, },
}; };
// 历史对比表格数据行类型 // 固定列背景样式(防止滚动时内容重叠)
const fixedColumnStyles = `
.main-business-table .ant-table-cell-fix-left,
.main-business-table .ant-table-cell-fix-right {
background: #1A202C !important;
}
.main-business-table .ant-table-thead .ant-table-cell-fix-left,
.main-business-table .ant-table-thead .ant-table-cell-fix-right {
background: rgba(26, 32, 44, 0.95) !important;
}
.main-business-table .ant-table-tbody > tr:hover .ant-table-cell-fix-left,
.main-business-table .ant-table-tbody > tr:hover .ant-table-cell-fix-right {
background: rgba(212, 175, 55, 0.08) !important;
}
`;
// 历史对比表格数据行类型(包含业务明细)
interface HistoricalRowData { interface HistoricalRowData {
key: string; key: string;
business: string; business: string;
grossMargin?: number;
profit?: number;
[period: string]: string | number | undefined; [period: string]: string | number | undefined;
} }
// 历史对比表格组件 // 历史对比表格组件(整合业务明细)
interface HistoricalComparisonTableProps { interface HistoricalComparisonTableProps {
historicalData: (ProductClassification | IndustryClassification)[]; historicalData: (ProductClassification | IndustryClassification)[];
businessItems: BusinessItem[]; businessItems: BusinessItem[];
hasProductData: boolean; hasProductData: boolean;
latestReportType: string;
} }
const HistoricalComparisonTable: React.FC<HistoricalComparisonTableProps> = ({ const HistoricalComparisonTable: React.FC<HistoricalComparisonTableProps> = ({
historicalData, historicalData,
businessItems, businessItems,
hasProductData, hasProductData,
latestReportType,
}) => { }) => {
// 动态生成列配置 // 动态生成列配置
const columns: ColumnsType<HistoricalRowData> = useMemo(() => { const columns: ColumnsType<HistoricalRowData> = useMemo(() => {
const cols: ColumnsType<HistoricalRowData> = [ const cols: ColumnsType<HistoricalRowData> = [
{ {
title: '业务/期间', title: '业务',
dataIndex: 'business', dataIndex: 'business',
key: 'business', key: 'business',
fixed: 'left', fixed: 'left',
width: 150, width: 150,
}, },
{
title: `毛利率(${latestReportType})`,
dataIndex: 'grossMargin',
key: 'grossMargin',
align: 'right',
width: 120,
render: (value: number | undefined) =>
value !== undefined ? formatUtils.formatPercent(value) : '-',
},
{
title: `利润(${latestReportType})`,
dataIndex: 'profit',
key: 'profit',
align: 'right',
width: 100,
render: (value: number | undefined) =>
value !== undefined ? formatUtils.formatLargeNumber(value) : '-',
},
]; ];
// 添加各期间列 // 添加各期间营收
historicalData.slice(0, 4).forEach((period) => { historicalData.slice(0, 4).forEach((period) => {
cols.push({ cols.push({
title: period.report_type, title: `营收(${period.report_type})`,
dataIndex: period.period, dataIndex: period.period,
key: period.period, key: period.period,
align: 'right', align: 'right',
@@ -112,9 +141,9 @@ const HistoricalComparisonTable: React.FC<HistoricalComparisonTableProps> = ({
}); });
return cols; return cols;
}, [historicalData]); }, [historicalData, latestReportType]);
// 生成表格数据 // 生成表格数据(包含业务明细)
const dataSource: HistoricalRowData[] = useMemo(() => { const dataSource: HistoricalRowData[] = useMemo(() => {
return businessItems return businessItems
.filter((item: BusinessItem) => item.content !== '合计') .filter((item: BusinessItem) => item.content !== '合计')
@@ -122,8 +151,11 @@ const HistoricalComparisonTable: React.FC<HistoricalComparisonTableProps> = ({
const row: HistoricalRowData = { const row: HistoricalRowData = {
key: `${idx}`, key: `${idx}`,
business: item.content, business: item.content,
grossMargin: item.gross_margin || item.profit_margin,
profit: item.profit,
}; };
// 添加各期间营收数据
historicalData.slice(0, 4).forEach((period) => { historicalData.slice(0, 4).forEach((period) => {
const periodItems: BusinessItem[] = hasProductData const periodItems: BusinessItem[] = hasProductData
? (period as ProductClassification).products ? (period as ProductClassification).products
@@ -145,13 +177,16 @@ const HistoricalComparisonTable: React.FC<HistoricalComparisonTableProps> = ({
borderColor={THEME.border} borderColor={THEME.border}
borderRadius="md" borderRadius="md"
overflow="hidden" overflow="hidden"
h="100%"
className="main-business-table"
> >
<style>{fixedColumnStyles}</style>
<Box px={4} py={3} borderBottom="1px solid" borderColor={THEME.border}> <Box px={4} py={3} borderBottom="1px solid" borderColor={THEME.border}>
<Heading size="sm" color={THEME.headingColor}> <Heading size="sm" color={THEME.headingColor}>
</Heading> </Heading>
</Box> </Box>
<Box p={4}> <Box p={4} overflowX="auto">
<ConfigProvider theme={BLACK_GOLD_THEME}> <ConfigProvider theme={BLACK_GOLD_THEME}>
<AntTable<HistoricalRowData> <AntTable<HistoricalRowData>
columns={columns} columns={columns}
@@ -218,77 +253,35 @@ export const MainBusinessAnalysis: React.FC<MainBusinessAnalysisProps> = ({
: (mainBusiness!.industry_classification! as IndustryClassification[]); : (mainBusiness!.industry_classification! as IndustryClassification[]);
return ( return (
<VStack spacing={4} align="stretch"> <Flex
<Grid templateColumns="repeat(2, 1fr)" gap={4}> direction={{ base: 'column', lg: 'row' }}
<GridItem> gap={4}
<Box >
bg={THEME.cardBg} {/* 左侧:饼图 */}
border="1px solid" <Box
borderColor={THEME.border} flexShrink={0}
borderRadius="md" w={{ base: '100%', lg: '340px' }}
p={4} bg={THEME.cardBg}
> border="1px solid"
<ReactECharts option={pieOption} style={{ height: '300px' }} /> borderColor={THEME.border}
</Box> borderRadius="md"
</GridItem> p={4}
<GridItem> >
<Box <ReactECharts option={pieOption} style={{ height: '280px' }} />
bg={THEME.cardBg} </Box>
border="1px solid"
borderColor={THEME.border}
borderRadius="md"
overflow="hidden"
>
<Box px={4} py={3} borderBottom="1px solid" borderColor={THEME.border}>
<Heading size="sm" color={THEME.headingColor}>
- {latestPeriod.report_type}
</Heading>
</Box>
<Box p={4}>
<TableContainer>
<Table size="sm">
<Thead>
<Tr>
<Th color={THEME.thColor} borderColor={THEME.border}></Th>
<Th isNumeric color={THEME.thColor} borderColor={THEME.border}></Th>
<Th isNumeric color={THEME.thColor} borderColor={THEME.border}>(%)</Th>
<Th isNumeric color={THEME.thColor} borderColor={THEME.border}></Th>
</Tr>
</Thead>
<Tbody>
{businessItems
.filter((item: BusinessItem) => item.content !== '合计')
.map((item: BusinessItem, idx: number) => (
<Tr key={idx}>
<Td color={THEME.textColor} borderColor={THEME.border}>{item.content}</Td>
<Td isNumeric color={THEME.textColor} borderColor={THEME.border}>
{formatUtils.formatLargeNumber(item.revenue)}
</Td>
<Td isNumeric color={THEME.textColor} borderColor={THEME.border}>
{formatUtils.formatPercent(item.gross_margin || item.profit_margin)}
</Td>
<Td isNumeric color={THEME.textColor} borderColor={THEME.border}>
{formatUtils.formatLargeNumber(item.profit)}
</Td>
</Tr>
))}
</Tbody>
</Table>
</TableContainer>
</Box>
</Box>
</GridItem>
</Grid>
{/* 历史对比 - Ant Design Table 黑金主题 */} {/* 右侧:业务明细与历史对比表格 */}
{historicalData.length > 1 && ( <Box flex={1} minW={0} overflow="hidden">
<HistoricalComparisonTable {historicalData.length > 0 && (
historicalData={historicalData} <HistoricalComparisonTable
businessItems={businessItems} historicalData={historicalData}
hasProductData={hasProductData} businessItems={businessItems}
/> hasProductData={hasProductData}
)} latestReportType={latestPeriod.report_type}
</VStack> />
)}
</Box>
</Flex>
); );
}; };