// React not needed for this component import Empty from '@arcblock/ux/lib/Empty'; import { useLocaleContext } from '@arcblock/ux/lib/Locale/context'; import { Box, Chip, Link, Stack, Typography } from '@mui/material'; // 定义本地类型,避免依赖问题 type LocalizedText = { zh: string; en: string; }; // 简单格式适合大部分业务场景 type SimpleSourceData = Record; // 结构化格式适合复杂场景 type StructuredSourceDataField = { key: string; label: string | LocalizedText; value: string; type?: 'text' | 'image' | 'url'; url?: string; group?: string; }; // 混合类型:支持渐进式接入 type SourceData = SimpleSourceData | StructuredSourceDataField[]; interface SourceDataViewerProps { data: SourceData; compact?: boolean; maxItems?: number; locale?: 'en' | 'zh'; showGroups?: boolean; // 是否显示分组 } function SourceDataViewer({ data, compact = false, maxItems = undefined, locale: propLocale = undefined, showGroups = false, }: SourceDataViewerProps) { const { locale: contextLocale, t } = useLocaleContext(); const currentLocale = propLocale || (contextLocale as 'en' | 'zh') || 'en'; if ( !data || (Array.isArray(data) && data.length === 0) || (typeof data === 'object' && Object.keys(data).length === 0) ) { return {t('common.none')}; } const getLocalizedText = (text: string | LocalizedText): string => { if (typeof text === 'string') { return text; } return text[currentLocale] || text.en || ''; }; /** * Type guards */ const isSimpleSourceData = (sourceData: SourceData): sourceData is SimpleSourceData => { return typeof sourceData === 'object' && !Array.isArray(sourceData) && sourceData !== null; }; /** * Auto detect URL pattern */ const isUrlLike = (str: string): boolean => { try { // eslint-disable-next-line no-new new URL(str); return true; } catch { return /^https?:\/\/.+/.test(str) || /^www\..+/.test(str); } }; /** * Convert any format to normalized structured format with auto URL detection */ const normalizeData = (inputData: SourceData): StructuredSourceDataField[] => { if (isSimpleSourceData(inputData)) { return Object.entries(inputData).map(([key, value]) => { const stringValue = String(value); const isUrl = isUrlLike(stringValue); return { key, label: key.replace(/_/g, ' ').replace(/\b\w/g, (l) => l.toUpperCase()), value: stringValue, type: isUrl ? ('url' as const) : ('text' as const), url: isUrl ? stringValue : undefined, }; }); } return inputData; }; /** * Render field value based on type */ const renderFieldValue = (field: StructuredSourceDataField) => { const displayValue = field.value; if (field.type === 'url') { return ( {displayValue} ); } if (field.type === 'image') { return ( {displayValue} ); } return ( {displayValue} ); }; if ( !data || (Array.isArray(data) && data.length === 0) || (typeof data === 'object' && Object.keys(data).length === 0) ) { return null; } const normalizedData = normalizeData(data); const displayItems = maxItems ? normalizedData.slice(0, maxItems) : normalizedData; if (compact) { return ( {displayItems.map((field) => ( ))} {maxItems && normalizedData.length > maxItems && ( )} ); } if (showGroups) { // Group by group field const groupedData = displayItems.reduce( (acc, field) => { const group = field.group || 'default'; if (!acc[group]) acc[group] = []; acc[group].push(field); return acc; }, {} as Record ); return ( {Object.entries(groupedData).map(([group, fields]) => ( {group !== 'default' && ( {group.replace(/_/g, ' ').replace(/\b\w/g, (l) => l.toUpperCase())} )} {fields.map((field) => ( {getLocalizedText(field.label)} {renderFieldValue(field)} ))} ))} ); } return ( {displayItems.map((field) => ( {getLocalizedText(field.label)} {renderFieldValue(field)} ))} ); } export default SourceDataViewer;