diff --git a/src/pages/Forecast/index.less b/src/pages/Forecast/index.less new file mode 100644 index 0000000..090d541 --- /dev/null +++ b/src/pages/Forecast/index.less @@ -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%; + } + } +} \ No newline at end of file diff --git a/src/pages/Forecast/index.tsx b/src/pages/Forecast/index.tsx index 9c075bf..4fda0c7 100644 --- a/src/pages/Forecast/index.tsx +++ b/src/pages/Forecast/index.tsx @@ -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); const analysisOptions = [ - { value: 'predictive', label: '预测性分析' }, - { value: 'descriptive', label: '描述性统计' }, - { value: 'anomaly', label: '异常检测' }, - { value: 'quality', label: '数据质量分析' }, + { value: 'predictive', label: '预测性分析', icon: , color: '#1890ff' }, + { value: 'descriptive', label: '描述性统计', icon: , color: '#52c41a' }, + { value: 'anomaly', label: '异常检测', icon: , color: '#faad14' }, + { value: 'quality', label: '数据质量分析', icon: , 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 ( - - - - + - setFileList(fileList)} - beforeUpload={(file) => { - setFileList([file]); - return false; - }} - > -

- -

-

点击或拖拽文件上传

-

支持 CSV、Excel 等数据文件格式

-
+ + 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; + }} + > +

+ +

+

点击或拖拽文件上传

+

支持 Excel (.xlsx, .xls) 或 CSV 文件格式

+
+
-
+
( - - + + : } /> + : } + className={styles.avatar} + /> } title={item.type === 'user' ? '你' : 'AI 助手'} description={item.content} /> {item.charts && ( -
- +
+
)} @@ -167,12 +226,13 @@ const AnalysisCenter: React.FC = () => {
-
+