import { useRef, useState, useEffect } from 'react';
import {
Typography,
Alert,
Table,
Pagination,
Form,
Radio,
message,
Row,
Col,
DatePicker,
Input,
Tabs,
Button,
Drawer,
FloatButton,
Tooltip,
Spin,
Tag,
} from 'antd';
import { UploadOutlined, FilterOutlined } from '@ant-design/icons';
import FileUploadPanel from './components/FileUploadPanel';
import EventListModal from './components/EventListModal';
import DiffListModal from './components/DiffListModal';
import SummaryAnalysis from './components/SummaryAnalysis';
import useJsonProcessor from './hooks/useJsonProcessor'; // 替换为 useJsonProcessor
import { playOptions, booleanOption, userOption } from './data';
import './index.css';
const { RangePicker } = DatePicker;
const { Paragraph } = Typography;
const { TabPane } = Tabs;
const UnifiedJsonMerger = () => {
const fileInputRef = useRef(null);
const resultRef = useRef(null);
const [form] = Form.useForm();
const [filters, setFilters] = useState({});
const [drawerOpen, setDrawerOpen] = useState(false); // 文件管理抽屉
const [filterDrawerOpen, setFilterDrawerOpen] = useState(false); // 过滤抽屉(左)
const [lastParseCompleted, setLastParseCompleted] = useState(false);
const [activeTab, setActiveTab] = useState('raw-data');
const [eventModalVisible, setEventModalVisible] = useState(false);
const [currentEvents, setCurrentEvents] = useState([]);
const [diffModalVisible, setDiffModalVisible] = useState(false);
const [currentDiffs, setCurrentDiffs] = useState([]);
// 打开事件模态框
const showEventModal = events => {
setCurrentEvents(events);
setEventModalVisible(true);
};
// 打开耗时详情模态框
const showDiffModal = playEpList => {
setCurrentDiffs(playEpList);
setDiffModalVisible(true);
};
const {
processedData,
selectedFiles,
addFiles,
removeFile,
isParsing,
error,
totalRows,
parseAllFiles: originalParseAllFiles,
pagination,
handleChangePage,
handleChangeRowsPerPage,
getTableData,
} = useJsonProcessor();
// 包装解析函数:防止重复提交
const parseAllFiles = async () => {
if (isParsing) return;
setLastParseCompleted(false);
await originalParseAllFiles();
};
// 解析完成后关闭抽屉并提示
useEffect(() => {
if (totalRows > 0 && !lastParseCompleted) {
setDrawerOpen(false);
setLastParseCompleted(true);
message.success(`解析完成,共加载 ${totalRows} 行原始数据`);
}
}, [totalRows, lastParseCompleted]);
const initialFilterValues = {
playType: 'all',
isPreConnect: 'all',
isValid: 'all',
isPerceived: 'all',
isError: 'all',
dateRange: undefined,
targetPlayId: '',
targetDevId: '',
targetKeyword: '',
};
// 设置表单初始值
useEffect(() => {
form.setFieldsValue(initialFilterValues);
}, []);
// 监听表单变化并更新 filters
const allFormValues = Form.useWatch([], form);
useEffect(() => {
if (allFormValues) {
const newFilters = Object.fromEntries(
Object.entries(allFormValues).filter(([_, value]) => {
if (value === null || value === undefined) return false;
if (typeof value === 'string' && value.trim() === '') return false;
if (Array.isArray(value) && value.length === 0) return false;
return true;
})
);
setFilters(prev => {
if (JSON.stringify(prev) === JSON.stringify(newFilters)) {
return prev;
}
return newFilters;
});
handleChangePage(1); // 筛选变更回到第一页
}
}, [allFormValues, handleChangePage]);
// 获取过滤后数据
const currentData = getTableData(filters);
const totalCount = currentData.length;
// 动态生成列
const getColumns = data => {
if (data.length === 0) return [];
const sample = data.find(item => typeof item === 'object' && item !== null);
if (!sample) return [];
return Object.keys(sample).map(key => ({
title: key,
dataIndex: key,
key,
render: text => {
// epList 列
if (key === 'epList' && Array.isArray(text)) {
return (
<Button
type="link"
size="small"
onClick={e => {
e.stopPropagation();
showEventModal(text);
}}
>
{text.length} 个埋点事件
</Button>
);
}
// playEpList 列
if (key === 'playEpList' && Array.isArray(text)) {
return (
<Button
type="link"
size="small"
onClick={e => {
e.stopPropagation();
showDiffModal(text);
}}
>
{text.length} 条起播记录
</Button>
);
}
// playId 和 devId 截断显示 + tooltip
if ((key === 'playId' || key === 'devId') && typeof text === 'string') {
const displayText = text.length <= 6 ? text : `...${text.slice(-6)}`;
return (
<Tooltip title={text}>
<span style={{ fontFamily: 'monospace', cursor: 'help' }}>
{displayText}
</span>
</Tooltip>
);
}
// 布尔字段美化
if (
[
'validStatus',
'isUserPerceivedSuccess',
'isUserPerceivedFailure',
].includes(key)
) {
if (typeof text === 'boolean') {
return text ? (
<Tag color="green">是</Tag>
) : (
<Tag color="red">否</Tag>
);
}
return '-';
}
// 数组渲染(非对象数组用逗号分隔,对象数组用 JSON 格式展示)
if (Array.isArray(text)) {
if (text.length === 0) return <span className="array-empty">-</span>;
const isObjectArray = text.some(
item => typeof item === 'object' && item !== null
);
if (isObjectArray) {
const items = text.map(item => JSON.stringify(item, null, 2));
return (
<div
style={{
whiteSpace: 'pre-wrap',
fontSize: '12px',
fontFamily: 'monospace',
}}
>
{items.join(',\n')}
</div>
);
} else {
return String(text.join(', '));
}
}
// null/undefined 显示 -
if (text == null) return '-';
const str = String(text).trim();
return str ? str : '-';
},
}));
};
const columns = getColumns(currentData);
// 分页数据
const previewData = currentData.slice(
(pagination.current - 1) * pagination.pageSize,
pagination.current * pagination.pageSize
);
return (
<>
{/* 浮动上传按钮 */}
<FloatButton
icon={<UploadOutlined />}
tooltip="管理 JSON 文件"
onClick={() => setDrawerOpen(true)}
style={{ right: 40, bottom: 40 }}
/>
{/* 右侧抽屉:文件上传与管理 */}
<Drawer
title="管理 JSON 文件"
placement="right"
width={480}
onClose={() => setDrawerOpen(false)}
open={drawerOpen}
destroyOnClose={false}
extra={
<Button
type="primary"
onClick={parseAllFiles}
disabled={isParsing || selectedFiles.length === 0}
>
{isParsing ? '解析中...' : '开始解析'}
</Button>
}
>
<FileUploadPanel
fileInputRef={fileInputRef}
selectedFiles={selectedFiles}
isParsing={isParsing}
onAddFiles={() => {
const files = fileInputRef.current?.files;
if (files?.length) {
const jsonFiles = Array.from(files).filter(f =>
f.name.endsWith('.json')
);
if (jsonFiles.length > 0) {
addFiles(jsonFiles);
message.success(`已添加 ${jsonFiles.length} 个 JSON 文件`);
} else {
message.warning('请选择 .json 文件');
}
if (fileInputRef.current) fileInputRef.current.value = '';
}
}}
onRemoveFile={removeFile}
onParseAllFiles={parseAllFiles}
/>
</Drawer>
{/* 错误提示 */}
{error && (
<Alert
message="解析出错"
description={error}
type="error"
showIcon
style={{ margin: '24px' }}
/>
)}
{/* 引导页:尚未上传任何数据 */}
{totalRows === 0 && !isParsing && (
<div
style={{
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
height: '70vh',
textAlign: 'center',
color: '#666',
padding: '24px',
}}
>
<UploadOutlined
style={{ fontSize: '48px', color: '#1890ff', marginBottom: '16px' }}
/>
<Typography.Title level={3} style={{ color: '#1890ff' }}>
欢迎使用 JSON 数据分析工具
</Typography.Title>
<Paragraph style={{ fontSize: '16px', maxWidth: '500px' }}>
请上传您的 JSON 文件并点击“开始解析”,我们将为您加载和分析数据。
</Paragraph>
<Button
type="primary"
icon={<UploadOutlined />}
size="large"
onClick={() => setDrawerOpen(true)}
>
上传 JSON 文件
</Button>
</div>
)}
{/* 主界面:正在解析或已有数据 */}
<Spin spinning={isParsing} tip="解析中,请稍候..." size="large">
{(totalRows > 0 || isParsing) && (
<div style={{ padding: '0 24px' }}>
<Tabs activeKey={activeTab} onChange={setActiveTab}>
{/* Tab 1: 数据列表 */}
<TabPane tab="数据列表" key="raw-data">
<div ref={resultRef}>
<EventListModal
visible={eventModalVisible}
events={currentEvents}
onClose={() => setEventModalVisible(false)}
/>
<DiffListModal
visible={diffModalVisible}
playEpList={currentDiffs}
onClose={() => setDiffModalVisible(false)}
/>
{/* 筛选按钮 */}
<div style={{ marginBottom: '16px' }}>
<Button
type="primary"
icon={<FilterOutlined />}
onClick={() => setFilterDrawerOpen(true)}
disabled={isParsing}
>
筛选条件
</Button>
</div>
{/* 表格区域 */}
<div style={{ position: 'relative', minHeight: 200 }}>
{!isParsing && (
<>
{columns.length > 0 ? (
<>
<Table
columns={columns}
dataSource={previewData.map((item, idx) => ({
...item,
key: idx,
}))}
pagination={false}
scroll={{ x: true }}
size="small"
bordered
style={{ marginBottom: '16px' }}
/>
<div
style={{
display: 'flex',
justifyContent: 'flex-end',
marginBottom: '24px',
}}
>
<Pagination
current={pagination.current}
pageSize={pagination.pageSize}
total={totalCount}
pageSizeOptions={['100', '200', '500']}
showSizeChanger
onChange={(page, pageSize) => {
handleChangePage(page);
if (pageSize !== pagination.pageSize) {
handleChangeRowsPerPage(pageSize);
}
}}
showTotal={(total, range) =>
`${range[0]}-${range[1]} 条,共 ${total} 条`
}
/>
</div>
</>
) : (
<div
style={{
textAlign: 'center',
padding: '32px',
color: '#999',
backgroundColor: '#fafafa',
borderRadius: '4px',
}}
>
暂无符合条件的数据
</div>
)}
</>
)}
</div>
</div>
</TabPane>
{/* Tab 2: 总结分析 */}
<TabPane tab="总结分析" key="summary">
<SummaryAnalysis data={processedData} />
</TabPane>
</Tabs>
{/* 左侧抽屉:过滤表单 */}
<Drawer
title="数据过滤"
placement="left"
width={400}
onClose={() => setFilterDrawerOpen(false)}
open={filterDrawerOpen}
extra={
<Button
size="small"
onClick={() => {
form.resetFields();
message.info('已重置筛选条件');
}}
>
重置
</Button>
}
>
<Form
form={form}
layout="horizontal"
className="compact-form"
size="middle"
>
<Row gutter={[16, 16]}>
<Col span={24}>
<Form.Item
name="playType"
label="播放类型"
initialValue="all"
>
<Radio.Group options={playOptions} buttonStyle="solid" />
</Form.Item>
</Col>
<Col span={24}>
<Form.Item
name="isPreConnect"
label="命中预连接"
initialValue="all"
>
<Radio.Group
options={booleanOption}
buttonStyle="solid"
/>
</Form.Item>
</Col>
<Col span={24}>
<Form.Item
name="isValid"
label="是否有效"
initialValue="all"
>
<Radio.Group
options={booleanOption}
buttonStyle="solid"
/>
</Form.Item>
</Col>
<Col span={24}>
<Form.Item
name="isPerceived"
label="用户"
initialValue="all"
>
<Radio.Group options={userOption} buttonStyle="solid" />
</Form.Item>
</Col>
<Col span={24}>
<Form.Item
name="isError"
label="是否存在报错"
initialValue="all"
>
<Radio.Group
options={booleanOption}
buttonStyle="solid"
/>
</Form.Item>
</Col>
<Col span={24}>
<Form.Item name="dateRange" label="日期范围">
<RangePicker
format="YYYY-MM-DD"
allowClear
style={{ width: '100%' }}
/>
</Form.Item>
</Col>
<Col span={24}>
<Form.Item name="targetPlayId" label="目标playId">
<Input placeholder="请输入 playId" allowClear />
</Form.Item>
</Col>
<Col span={24}>
<Form.Item name="targetDevId" label="目标devId">
<Input placeholder="请输入 devId" allowClear />
</Form.Item>
</Col>
<Col span={24}>
<Form.Item name="targetKeyword" label="关键字模糊查询">
<Input placeholder="请输入关键字" allowClear />
</Form.Item>
</Col>
</Row>
</Form>
</Drawer>
</div>
)}
</Spin>
</>
);
};
export default UnifiedJsonMerger;
这个怎么改
最新发布