201 lines
5.2 KiB
TypeScript
201 lines
5.2 KiB
TypeScript
/**
|
||
* 关联描述组件
|
||
*
|
||
* 用于显示股票与事件的关联描述信息
|
||
* 固定标题为"关联描述:"
|
||
* 自动处理多种数据格式(字符串、对象数组)
|
||
* 支持悬停显示来源信息
|
||
*
|
||
* @example
|
||
* ```tsx
|
||
* // 基础使用 - 传入原始 relation_desc 数据
|
||
* <RelationDescription relationDesc={stock.relation_desc} />
|
||
*
|
||
* // 自定义样式
|
||
* <RelationDescription
|
||
* relationDesc={stock.relation_desc}
|
||
* fontSize="md"
|
||
* titleColor="blue.700"
|
||
* />
|
||
* ```
|
||
*/
|
||
|
||
import React, { useMemo } from 'react';
|
||
import { Box, Text, BoxProps, Tooltip } from '@chakra-ui/react';
|
||
|
||
/**
|
||
* 关联描述数据项类型
|
||
*/
|
||
export interface RelationDescItem {
|
||
query_part?: string;
|
||
sentences?: string;
|
||
organization?: string;
|
||
report_title?: string;
|
||
declare_date?: string;
|
||
author?: string;
|
||
match_score?: string;
|
||
}
|
||
|
||
/**
|
||
* 关联描述数据类型
|
||
* - 字符串格式:直接的描述文本
|
||
* - 对象格式:包含多个句子的数组
|
||
*/
|
||
export type RelationDescType =
|
||
| string
|
||
| {
|
||
data: Array<RelationDescItem>;
|
||
}
|
||
| null
|
||
| undefined;
|
||
|
||
export interface RelationDescriptionProps {
|
||
/** 原始关联描述数据(支持字符串或对象格式) */
|
||
relationDesc: RelationDescType;
|
||
|
||
/** 字体大小,默认 'sm' */
|
||
fontSize?: string;
|
||
|
||
/** 标题颜色,默认 'gray.700' */
|
||
titleColor?: string;
|
||
|
||
/** 文本颜色,默认 'gray.600' */
|
||
textColor?: string;
|
||
|
||
/** 行高,默认 '1.7' */
|
||
lineHeight?: string;
|
||
|
||
/** 容器额外属性 */
|
||
containerProps?: BoxProps;
|
||
}
|
||
|
||
export const RelationDescription: React.FC<RelationDescriptionProps> = ({
|
||
relationDesc,
|
||
fontSize = 'sm',
|
||
titleColor = 'gray.700',
|
||
textColor = 'gray.600',
|
||
lineHeight = '1.7',
|
||
containerProps = {}
|
||
}) => {
|
||
// 判断是否为对象格式(带来源信息)
|
||
const isObjectFormat = useMemo(() => {
|
||
return typeof relationDesc === 'object' && relationDesc?.data && Array.isArray(relationDesc.data);
|
||
}, [relationDesc]);
|
||
|
||
// 处理关联描述数据
|
||
const descData = useMemo(() => {
|
||
if (!relationDesc) return null;
|
||
|
||
// 字符串格式:直接返回
|
||
if (typeof relationDesc === 'string') {
|
||
return { type: 'string' as const, content: relationDesc };
|
||
}
|
||
|
||
// 对象格式:返回数据数组
|
||
if (isObjectFormat && relationDesc && typeof relationDesc === 'object') {
|
||
const items = relationDesc.data.filter((item) => item.query_part);
|
||
if (items.length === 0) return null;
|
||
return { type: 'array' as const, items };
|
||
}
|
||
|
||
return null;
|
||
}, [relationDesc, isObjectFormat]);
|
||
|
||
// 如果没有有效的描述内容,不渲染组件
|
||
if (!descData) {
|
||
return null;
|
||
}
|
||
|
||
// 格式化日期
|
||
const formatDate = (dateStr?: string) => {
|
||
if (!dateStr) return '';
|
||
try {
|
||
return new Date(dateStr).toLocaleDateString('zh-CN');
|
||
} catch {
|
||
return dateStr;
|
||
}
|
||
};
|
||
|
||
return (
|
||
<Box
|
||
p={4}
|
||
borderTop="1px solid"
|
||
borderTopColor="gray.200"
|
||
{...containerProps}
|
||
>
|
||
<Text
|
||
fontSize={fontSize}
|
||
fontWeight="bold"
|
||
mb={2}
|
||
color={titleColor}
|
||
>
|
||
关联描述:
|
||
</Text>
|
||
{descData.type === 'string' ? (
|
||
<Text
|
||
fontSize={fontSize}
|
||
color={textColor}
|
||
lineHeight={lineHeight}
|
||
whiteSpace="pre-wrap"
|
||
>
|
||
{descData.content}
|
||
</Text>
|
||
) : (
|
||
<Text
|
||
fontSize={fontSize}
|
||
color={textColor}
|
||
lineHeight={lineHeight}
|
||
>
|
||
{descData.items.map((item, index, arr) => (
|
||
<React.Fragment key={index}>
|
||
<Tooltip
|
||
label={
|
||
<Box maxW="400px" p={2}>
|
||
{item.sentences && (
|
||
<Text fontSize="xs" mb={2} whiteSpace="pre-wrap">
|
||
{item.sentences}
|
||
</Text>
|
||
)}
|
||
<Text fontSize="xs" color="gray.300" mt={1}>
|
||
来源:{item.organization || '未知'}
|
||
</Text>
|
||
{item.report_title && (
|
||
<Text fontSize="xs" color="gray.300" noOfLines={2}>
|
||
{item.report_title}
|
||
</Text>
|
||
)}
|
||
{item.declare_date && (
|
||
<Text fontSize="xs" color="gray.400">
|
||
{formatDate(item.declare_date)}
|
||
</Text>
|
||
)}
|
||
</Box>
|
||
}
|
||
placement="top"
|
||
hasArrow
|
||
bg="rgba(20, 20, 20, 0.95)"
|
||
maxW="420px"
|
||
>
|
||
<Text
|
||
as="span"
|
||
cursor="help"
|
||
borderBottom="1px dashed"
|
||
borderBottomColor="gray.400"
|
||
_hover={{
|
||
color: 'blue.500',
|
||
borderBottomColor: 'blue.500',
|
||
}}
|
||
transition="all 0.2s"
|
||
>
|
||
{item.query_part}
|
||
</Text>
|
||
</Tooltip>
|
||
{index < arr.length - 1 && ';'}
|
||
</React.Fragment>
|
||
))}
|
||
</Text>
|
||
)}
|
||
</Box>
|
||
);
|
||
};
|