fix: 优化预测页面
This commit is contained in:
parent
07776bee0a
commit
326689155f
139
src/pages/Forecast/index.less
Normal file
139
src/pages/Forecast/index.less
Normal file
@ -0,0 +1,139 @@
|
||||
.container {
|
||||
:global {
|
||||
.ant-pro-page-container-children-content {
|
||||
margin: 24px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.mainCard {
|
||||
min-height: 80vh;
|
||||
background: linear-gradient(to bottom, #ffffff, #f0f2f5);
|
||||
}
|
||||
|
||||
.analysisTypeSelector {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
flex-wrap: wrap;
|
||||
padding: 16px 0;
|
||||
|
||||
.analysisTag {
|
||||
cursor: pointer;
|
||||
padding: 8px 16px;
|
||||
font-size: 14px;
|
||||
border-radius: 16px;
|
||||
transition: all 0.3s;
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.uploadCard {
|
||||
background: #fafafa;
|
||||
border-radius: 8px;
|
||||
|
||||
:global {
|
||||
.ant-upload-drag {
|
||||
border: 2px dashed #d9d9d9;
|
||||
border-radius: 8px;
|
||||
padding: 24px;
|
||||
transition: all 0.3s;
|
||||
|
||||
&:hover {
|
||||
border-color: #1890ff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.uploadIcon {
|
||||
font-size: 48px;
|
||||
color: #1890ff;
|
||||
}
|
||||
}
|
||||
|
||||
.chatContainer {
|
||||
height: 500px;
|
||||
overflow-y: auto;
|
||||
padding: 16px;
|
||||
background: #fff;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||
|
||||
.messageList {
|
||||
.userMessage {
|
||||
justify-content: flex-end;
|
||||
|
||||
.messageCard {
|
||||
background: #e6f7ff;
|
||||
}
|
||||
}
|
||||
|
||||
.assistantMessage {
|
||||
justify-content: flex-start;
|
||||
|
||||
.messageCard {
|
||||
background: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.messageCard {
|
||||
max-width: 80%;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||
|
||||
.avatar {
|
||||
background: #1890ff;
|
||||
}
|
||||
|
||||
.chartContainer {
|
||||
margin-top: 16px;
|
||||
padding: 16px;
|
||||
background: #fff;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
}
|
||||
|
||||
.inputContainer {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
margin-top: 16px;
|
||||
|
||||
.input {
|
||||
border-radius: 8px;
|
||||
resize: none;
|
||||
|
||||
&:focus {
|
||||
box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2);
|
||||
}
|
||||
}
|
||||
|
||||
.sendButton {
|
||||
align-self: flex-end;
|
||||
height: 40px;
|
||||
border-radius: 8px;
|
||||
padding: 0 24px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 768px) {
|
||||
.analysisTypeSelector {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.messageCard {
|
||||
max-width: 90%;
|
||||
}
|
||||
|
||||
.inputContainer {
|
||||
flex-direction: column;
|
||||
|
||||
.sendButton {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
@ -12,10 +12,22 @@ import {
|
||||
message,
|
||||
Tooltip,
|
||||
Space,
|
||||
Tag,
|
||||
Divider,
|
||||
} from 'antd';
|
||||
import { SendOutlined, RobotOutlined, UserOutlined, InboxOutlined } from '@ant-design/icons';
|
||||
import {
|
||||
SendOutlined,
|
||||
RobotOutlined,
|
||||
UserOutlined,
|
||||
InboxOutlined,
|
||||
LineChartOutlined,
|
||||
BarChartOutlined,
|
||||
PieChartOutlined,
|
||||
AreaChartOutlined,
|
||||
} from '@ant-design/icons';
|
||||
import type { UploadFile } from 'antd/es/upload/interface';
|
||||
import ReactECharts from 'echarts-for-react';
|
||||
import styles from './index.less';
|
||||
|
||||
const { Option } = Select;
|
||||
const { Dragger } = Upload;
|
||||
@ -37,12 +49,59 @@ const AnalysisCenter: React.FC = () => {
|
||||
const messagesEndRef = useRef<null | HTMLDivElement>(null);
|
||||
|
||||
const analysisOptions = [
|
||||
{ value: 'predictive', label: '预测性分析' },
|
||||
{ value: 'descriptive', label: '描述性统计' },
|
||||
{ value: 'anomaly', label: '异常检测' },
|
||||
{ value: 'quality', label: '数据质量分析' },
|
||||
{ value: 'predictive', label: '预测性分析', icon: <LineChartOutlined />, color: '#1890ff' },
|
||||
{ value: 'descriptive', label: '描述性统计', icon: <BarChartOutlined />, color: '#52c41a' },
|
||||
{ value: 'anomaly', label: '异常检测', icon: <PieChartOutlined />, color: '#faad14' },
|
||||
{ value: 'quality', label: '数据质量分析', icon: <AreaChartOutlined />, color: '#722ed1' },
|
||||
];
|
||||
|
||||
const generateMockChart = (type: string) => {
|
||||
switch (type) {
|
||||
case 'predictive':
|
||||
return {
|
||||
title: { text: '销售趋势预测', left: 'center' },
|
||||
tooltip: { trigger: 'axis' },
|
||||
legend: { data: ['历史数据', '预测数据'], bottom: 10 },
|
||||
grid: { top: 50, right: 20, bottom: 60, left: 40 },
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: ['1月', '2月', '3月', '4月', '5月', '6月'],
|
||||
axisLabel: { interval: 0 }
|
||||
},
|
||||
yAxis: { type: 'value', name: '销售额' },
|
||||
series: [
|
||||
{
|
||||
name: '历史数据',
|
||||
type: 'line',
|
||||
data: [150, 230, 224, 218, 135, 147],
|
||||
smooth: true,
|
||||
},
|
||||
{
|
||||
name: '预测数据',
|
||||
type: 'line',
|
||||
data: [null, null, null, 225, 238, 251],
|
||||
smooth: true,
|
||||
lineStyle: { type: 'dashed' },
|
||||
}
|
||||
]
|
||||
};
|
||||
case 'descriptive':
|
||||
return {
|
||||
title: { text: '数据分布情况', left: 'center' },
|
||||
tooltip: { trigger: 'axis' },
|
||||
grid: { top: 50, right: 20, bottom: 60, left: 40 },
|
||||
xAxis: { type: 'category', data: ['极小值', '下四分位', '中位数', '上四分位', '极大值'] },
|
||||
yAxis: { type: 'value' },
|
||||
series: [{
|
||||
type: 'boxplot',
|
||||
data: [[10, 25, 35, 50, 70]],
|
||||
itemStyle: { color: '#52c41a' }
|
||||
}]
|
||||
};
|
||||
}
|
||||
return baseOption;
|
||||
};
|
||||
|
||||
const scrollToBottom = () => {
|
||||
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
|
||||
};
|
||||
@ -92,72 +151,72 @@ const AnalysisCenter: React.FC = () => {
|
||||
return responses[type] || '分析完成';
|
||||
};
|
||||
|
||||
const generateMockChart = (type: string) => {
|
||||
const baseOption = {
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: ['1月', '2月', '3月', '4月', '5月', '6月'],
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
},
|
||||
series: [{
|
||||
data: [150, 230, 224, 218, 135, 147],
|
||||
type: 'line',
|
||||
}],
|
||||
};
|
||||
return baseOption;
|
||||
};
|
||||
|
||||
return (
|
||||
<PageContainer>
|
||||
<Card>
|
||||
<Space direction="vertical" style={{ width: '100%' }}>
|
||||
<Select
|
||||
style={{ width: '100%' }}
|
||||
value={analysisType}
|
||||
onChange={setAnalysisType}
|
||||
placeholder="请选择分析类型"
|
||||
>
|
||||
<PageContainer
|
||||
className={styles.container}
|
||||
title="智能预测分析"
|
||||
subTitle="上传数据,获取专业的数据分析见解"
|
||||
>
|
||||
<Card bordered={false} className={styles.mainCard}>
|
||||
<Space direction="vertical" style={{ width: '100%' }} size="large">
|
||||
<div className={styles.analysisTypeSelector}>
|
||||
{analysisOptions.map(option => (
|
||||
<Option key={option.value} value={option.value}>
|
||||
{option.label}
|
||||
</Option>
|
||||
<Tooltip key={option.value} title={option.label}>
|
||||
<Tag
|
||||
className={styles.analysisTag}
|
||||
color={analysisType === option.value ? option.color : 'default'}
|
||||
icon={option.icon}
|
||||
onClick={() => setAnalysisType(option.value)}
|
||||
>
|
||||
{option.label}
|
||||
</Tag>
|
||||
</Tooltip>
|
||||
))}
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<Dragger
|
||||
fileList={fileList}
|
||||
onChange={({ fileList }) => setFileList(fileList)}
|
||||
beforeUpload={(file) => {
|
||||
setFileList([file]);
|
||||
return false;
|
||||
}}
|
||||
>
|
||||
<p className="ant-upload-drag-icon">
|
||||
<InboxOutlined />
|
||||
</p>
|
||||
<p className="ant-upload-text">点击或拖拽文件上传</p>
|
||||
<p className="ant-upload-hint">支持 CSV、Excel 等数据文件格式</p>
|
||||
</Dragger>
|
||||
<Card className={styles.uploadCard}>
|
||||
<Dragger
|
||||
fileList={fileList}
|
||||
onChange={({ fileList }) => setFileList(fileList)}
|
||||
beforeUpload={(file) => {
|
||||
const isExcelOrCsv = /\.(xlsx|xls|csv)$/.test(file.name.toLowerCase());
|
||||
if (!isExcelOrCsv) {
|
||||
message.error('只支持 Excel 或 CSV 文件!');
|
||||
return false;
|
||||
}
|
||||
setFileList([file]);
|
||||
return false;
|
||||
}}
|
||||
>
|
||||
<p className="ant-upload-drag-icon">
|
||||
<InboxOutlined className={styles.uploadIcon} />
|
||||
</p>
|
||||
<p className="ant-upload-text">点击或拖拽文件上传</p>
|
||||
<p className="ant-upload-hint">支持 Excel (.xlsx, .xls) 或 CSV 文件格式</p>
|
||||
</Dragger>
|
||||
</Card>
|
||||
|
||||
<div style={{ height: '400px', overflowY: 'auto', marginBottom: '16px' }}>
|
||||
<div className={styles.chatContainer}>
|
||||
<List
|
||||
className={styles.messageList}
|
||||
itemLayout="horizontal"
|
||||
dataSource={messages}
|
||||
renderItem={item => (
|
||||
<List.Item style={{ justifyContent: item.type === 'user' ? 'flex-end' : 'flex-start' }}>
|
||||
<Card style={{ maxWidth: '80%' }}>
|
||||
<List.Item className={item.type === 'user' ? styles.userMessage : styles.assistantMessage}>
|
||||
<Card className={styles.messageCard}>
|
||||
<List.Item.Meta
|
||||
avatar={
|
||||
<Avatar icon={item.type === 'user' ? <UserOutlined /> : <RobotOutlined />} />
|
||||
<Avatar
|
||||
icon={item.type === 'user' ? <UserOutlined /> : <RobotOutlined />}
|
||||
className={styles.avatar}
|
||||
/>
|
||||
}
|
||||
title={item.type === 'user' ? '你' : 'AI 助手'}
|
||||
description={item.content}
|
||||
/>
|
||||
{item.charts && (
|
||||
<div style={{ marginTop: '16px' }}>
|
||||
<ReactECharts option={item.charts} style={{ height: '300px' }} />
|
||||
<div className={styles.chartContainer}>
|
||||
<ReactECharts option={item.charts} style={{ height: 300 }} />
|
||||
</div>
|
||||
)}
|
||||
</Card>
|
||||
@ -167,12 +226,13 @@ const AnalysisCenter: React.FC = () => {
|
||||
<div ref={messagesEndRef} />
|
||||
</div>
|
||||
|
||||
<div style={{ display: 'flex', gap: '8px' }}>
|
||||
<div className={styles.inputContainer}>
|
||||
<TextArea
|
||||
value={inputValue}
|
||||
onChange={(e) => setInputValue(e.target.value)}
|
||||
placeholder="请输入你的分析需求..."
|
||||
placeholder="请描述您的分析需求..."
|
||||
autoSize={{ minRows: 2, maxRows: 6 }}
|
||||
className={styles.input}
|
||||
onPressEnter={(e) => {
|
||||
if (!e.shiftKey) {
|
||||
e.preventDefault();
|
||||
@ -185,9 +245,9 @@ const AnalysisCenter: React.FC = () => {
|
||||
icon={<SendOutlined />}
|
||||
onClick={handleSend}
|
||||
loading={loading}
|
||||
style={{ alignSelf: 'flex-end' }}
|
||||
className={styles.sendButton}
|
||||
>
|
||||
发送
|
||||
分析
|
||||
</Button>
|
||||
</div>
|
||||
</Space>
|
||||
|
93
src/pages/Report/index.less
Normal file
93
src/pages/Report/index.less
Normal file
@ -0,0 +1,93 @@
|
||||
.container {
|
||||
min-height: 600px;
|
||||
|
||||
.steps {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
.uploadStep {
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.previewCard {
|
||||
margin-top: 24px;
|
||||
|
||||
.previewTable {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
|
||||
th,
|
||||
td {
|
||||
padding: 12px;
|
||||
border: 1px solid #f0f0f0;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
th {
|
||||
background: #fafafa;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
tr:hover {
|
||||
background: #fafafa;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.goalStep {
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
text-align: left;
|
||||
|
||||
.goalInput {
|
||||
margin: 24px 0;
|
||||
}
|
||||
|
||||
.generateButton {
|
||||
min-width: 200px;
|
||||
}
|
||||
|
||||
.progressContainer {
|
||||
margin-top: 24px;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
.reportStep {
|
||||
.reportPreview {
|
||||
margin-top: 24px;
|
||||
padding: 24px;
|
||||
background: #fafafa;
|
||||
border-radius: 8px;
|
||||
|
||||
.chartCard {
|
||||
margin-top: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
:global {
|
||||
.ant-upload-drag {
|
||||
border: 2px dashed #d9d9d9;
|
||||
border-radius: 8px;
|
||||
transition: all 0.3s;
|
||||
|
||||
&:hover {
|
||||
border-color: #1890ff;
|
||||
}
|
||||
}
|
||||
|
||||
.ant-steps-item-icon {
|
||||
background: #fff !important;
|
||||
|
||||
.anticon {
|
||||
color: #1890ff;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,13 +1,14 @@
|
||||
import React, { useState } from 'react';
|
||||
import { PageContainer } from '@ant-design/pro-components';
|
||||
import { InboxOutlined } from '@ant-design/icons';
|
||||
import { Upload, Card, Button, Steps, message, Input, Spin,Result } from 'antd';
|
||||
import { InboxOutlined, FileExcelOutlined, BarChartOutlined, FileWordOutlined } from '@ant-design/icons';
|
||||
import { Upload, Card, Button, Steps, message, Input, Spin, Result, Space, Progress, Alert, Typography } from 'antd';
|
||||
import type { UploadFile } from 'antd/es/upload/interface';
|
||||
import ReactECharts from 'echarts-for-react';
|
||||
import { DownloadOutlined } from '@ant-design/icons';
|
||||
import styles from './index.less';
|
||||
|
||||
const { Dragger } = Upload;
|
||||
const { TextArea } = Input;
|
||||
const { Title, Paragraph } = Typography;
|
||||
|
||||
const ReportPage: React.FC = () => {
|
||||
const [fileList, setFileList] = useState<UploadFile[]>([]);
|
||||
@ -16,108 +17,236 @@ const ReportPage: React.FC = () => {
|
||||
const [goal, setGoal] = useState('');
|
||||
const [reportData, setReportData] = useState<any>(null);
|
||||
const [wordUrl, setWordUrl] = useState<string>('');
|
||||
const [progress, setProgress] = useState(0);
|
||||
const [previewData, setPreviewData] = useState<any>(null);
|
||||
|
||||
const handleUpload = async (file: File) => {
|
||||
setLoading(true);
|
||||
// 这里应该调用后端API上传文件
|
||||
try {
|
||||
const formData = new FormData();
|
||||
formData.append('file', file);
|
||||
// const response = await uploadFile(formData);
|
||||
message.success('文件上传成功');
|
||||
setCurrentStep(1);
|
||||
|
||||
// 模拟上传进度
|
||||
const timer = setInterval(() => {
|
||||
setProgress(prev => {
|
||||
if (prev >= 99) {
|
||||
clearInterval(timer);
|
||||
return 99;
|
||||
}
|
||||
return prev + 10;
|
||||
});
|
||||
}, 200);
|
||||
|
||||
// 模拟预览数据
|
||||
setTimeout(() => {
|
||||
setPreviewData({
|
||||
columns: ['日期', '销售额', '利润'],
|
||||
data: [
|
||||
['2023-01', 1000, 200],
|
||||
['2023-02', 1500, 300],
|
||||
['2023-03', 1200, 250],
|
||||
]
|
||||
});
|
||||
setProgress(100);
|
||||
message.success('文件上传成功');
|
||||
setCurrentStep(1);
|
||||
setLoading(false);
|
||||
clearInterval(timer);
|
||||
}, 2000);
|
||||
} catch (error) {
|
||||
message.error('文件上传失败');
|
||||
setLoading(false);
|
||||
}
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
const generateReport = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
// 这里应该调用后端API生成报告
|
||||
// const response = await generateWordReport({ fileId: fileList[0].uid, goal });
|
||||
// setWordUrl(response.data.wordUrl);
|
||||
|
||||
// 模拟数据
|
||||
let progress = 0;
|
||||
const timer = setInterval(() => {
|
||||
progress += 20;
|
||||
if (progress <= 100) {
|
||||
setProgress(progress);
|
||||
}
|
||||
}, 1000);
|
||||
|
||||
setTimeout(() => {
|
||||
clearInterval(timer);
|
||||
setProgress(100);
|
||||
setWordUrl('https://example.com/report.docx');
|
||||
setReportData({
|
||||
title: '数据分析报告',
|
||||
summary: '根据您提供的数据,我们生成了详细的分析报告...',
|
||||
charts: [
|
||||
{
|
||||
title: '销售趋势',
|
||||
type: 'line',
|
||||
data: {
|
||||
title: {
|
||||
text: '销售趋势分析'
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'axis'
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: ['1月', '2月', '3月']
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value'
|
||||
},
|
||||
series: [{
|
||||
name: '销售额',
|
||||
type: 'line',
|
||||
data: [1000, 1500, 1200],
|
||||
smooth: true
|
||||
}]
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
message.success('报告生成成功');
|
||||
setCurrentStep(2);
|
||||
}, 1500);
|
||||
setLoading(false);
|
||||
}, 5000);
|
||||
} catch (error) {
|
||||
message.error('报告生成失败');
|
||||
setLoading(false);
|
||||
}
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
const renderPreview = () => {
|
||||
if (!previewData) return null;
|
||||
return (
|
||||
<Card className={styles.previewCard} title="数据预览">
|
||||
<table className={styles.previewTable}>
|
||||
<thead>
|
||||
<tr>
|
||||
{previewData.columns.map((col: string) => (
|
||||
<th key={col}>{col}</th>
|
||||
))}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{previewData.data.map((row: any[], index: number) => (
|
||||
<tr key={index}>
|
||||
{row.map((cell, cellIndex) => (
|
||||
<td key={cellIndex}>{cell}</td>
|
||||
))}
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
const steps = [
|
||||
{
|
||||
title: '上传文件',
|
||||
icon: <FileExcelOutlined />,
|
||||
content: (
|
||||
<Dragger
|
||||
fileList={fileList}
|
||||
beforeUpload={(file) => {
|
||||
const isExcelOrCsv =
|
||||
file.type === 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' ||
|
||||
file.type === 'application/vnd.ms-excel' ||
|
||||
file.type === 'text/csv';
|
||||
if (!isExcelOrCsv) {
|
||||
message.error('只支持上传 Excel 或 CSV 文件!');
|
||||
<div className={styles.uploadStep}>
|
||||
<Alert
|
||||
message="支持的文件格式"
|
||||
description="Excel文件 (.xlsx, .xls) 或 CSV文件 (.csv)"
|
||||
type="info"
|
||||
showIcon
|
||||
style={{ marginBottom: 24 }}
|
||||
/>
|
||||
<Dragger
|
||||
fileList={fileList}
|
||||
beforeUpload={(file) => {
|
||||
const isExcelOrCsv =
|
||||
file.type === 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' ||
|
||||
file.type === 'application/vnd.ms-excel' ||
|
||||
file.type === 'text/csv';
|
||||
if (!isExcelOrCsv) {
|
||||
message.error('只支持上传 Excel 或 CSV 文件!');
|
||||
return false;
|
||||
}
|
||||
setFileList([file]);
|
||||
handleUpload(file);
|
||||
return false;
|
||||
}
|
||||
setFileList([file]);
|
||||
handleUpload(file);
|
||||
return false;
|
||||
}}
|
||||
onRemove={() => {
|
||||
setFileList([]);
|
||||
setCurrentStep(0);
|
||||
return true;
|
||||
}}
|
||||
>
|
||||
<p className="ant-upload-drag-icon">
|
||||
<InboxOutlined />
|
||||
</p>
|
||||
<p className="ant-upload-text">点击或拖拽文件到此区域上传</p>
|
||||
<p className="ant-upload-hint">支持 Excel 和 CSV 文件格式</p>
|
||||
</Dragger>
|
||||
}}
|
||||
onRemove={() => {
|
||||
setFileList([]);
|
||||
setCurrentStep(0);
|
||||
setPreviewData(null);
|
||||
return true;
|
||||
}}
|
||||
>
|
||||
<p className="ant-upload-drag-icon">
|
||||
<InboxOutlined />
|
||||
</p>
|
||||
<p className="ant-upload-text">点击或拖拽文件到此区域上传</p>
|
||||
<p className="ant-upload-hint">支持 Excel 和 CSV 文件格式</p>
|
||||
</Dragger>
|
||||
{loading && (
|
||||
<Progress percent={progress} status="active" style={{ marginTop: 24 }} />
|
||||
)}
|
||||
{renderPreview()}
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '设置目标',
|
||||
icon: <BarChartOutlined />,
|
||||
content: (
|
||||
<div style={{ textAlign: 'center' }}>
|
||||
<div className={styles.goalStep}>
|
||||
<Title level={4}>分析目标设置</Title>
|
||||
<Paragraph type="secondary">
|
||||
请详细描述您的分析需求,例如:
|
||||
<ul>
|
||||
<li>分析销售趋势和影响因素</li>
|
||||
<li>识别客户购买行为模式</li>
|
||||
<li>预测未来销售情况</li>
|
||||
</ul>
|
||||
</Paragraph>
|
||||
<TextArea
|
||||
placeholder="请输入你想要分析的目标,例如:分析销售趋势、客户分布等"
|
||||
rows={4}
|
||||
value={goal}
|
||||
onChange={(e) => setGoal(e.target.value)}
|
||||
style={{ marginBottom: 16 }}
|
||||
className={styles.goalInput}
|
||||
/>
|
||||
<Button type="primary" onClick={generateReport} disabled={!goal}>
|
||||
生成报告
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={generateReport}
|
||||
disabled={!goal}
|
||||
size="large"
|
||||
className={styles.generateButton}
|
||||
>
|
||||
开始生成报告
|
||||
</Button>
|
||||
{loading && (
|
||||
<div className={styles.progressContainer}>
|
||||
<Progress percent={progress} status="active" />
|
||||
<Paragraph type="secondary">正在生成分析报告,请稍候...</Paragraph>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '查看报告',
|
||||
icon: <FileWordOutlined />,
|
||||
content: wordUrl && (
|
||||
<div style={{ textAlign: 'center' }}>
|
||||
<div className={styles.reportStep}>
|
||||
<Card>
|
||||
<Result
|
||||
status="success"
|
||||
title="报告生成成功"
|
||||
subTitle="您可以下载生成的Word报告文档"
|
||||
subTitle="您可以查看报告预览或下载完整报告"
|
||||
extra={[
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<DownloadOutlined />}
|
||||
icon={<FileWordOutlined />}
|
||||
onClick={() => window.open(wordUrl)}
|
||||
key="download"
|
||||
size="large"
|
||||
>
|
||||
下载报告
|
||||
下载完整报告
|
||||
</Button>,
|
||||
<Button
|
||||
onClick={() => {
|
||||
@ -125,13 +254,27 @@ const ReportPage: React.FC = () => {
|
||||
setFileList([]);
|
||||
setGoal('');
|
||||
setWordUrl('');
|
||||
setPreviewData(null);
|
||||
setReportData(null);
|
||||
}}
|
||||
key="again"
|
||||
size="large"
|
||||
>
|
||||
重新生成
|
||||
</Button>,
|
||||
]}
|
||||
/>
|
||||
{reportData && (
|
||||
<div className={styles.reportPreview}>
|
||||
<Title level={4}>{reportData.title}</Title>
|
||||
<Paragraph>{reportData.summary}</Paragraph>
|
||||
{reportData.charts.map((chart: any, index: number) => (
|
||||
<Card key={index} title={chart.title} className={styles.chartCard}>
|
||||
<ReactECharts option={chart.data} style={{ height: 300 }} />
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</Card>
|
||||
</div>
|
||||
),
|
||||
@ -139,10 +282,19 @@ const ReportPage: React.FC = () => {
|
||||
];
|
||||
|
||||
return (
|
||||
<PageContainer>
|
||||
<Card>
|
||||
<Steps current={currentStep} items={steps} style={{ marginBottom: 24 }} />
|
||||
<Spin spinning={loading}>{steps[currentStep].content}</Spin>
|
||||
<PageContainer
|
||||
title="智能报告生成"
|
||||
subTitle="上传数据文件,快速生成专业分析报告"
|
||||
>
|
||||
<Card className={styles.container}>
|
||||
<Steps
|
||||
current={currentStep}
|
||||
items={steps}
|
||||
className={styles.steps}
|
||||
/>
|
||||
<div className={styles.content}>
|
||||
<Spin spinning={loading}>{steps[currentStep].content}</Spin>
|
||||
</div>
|
||||
</Card>
|
||||
</PageContainer>
|
||||
);
|
||||
|
Loading…
x
Reference in New Issue
Block a user