feat: 添加股票mock数据
This commit is contained in:
@@ -8,6 +8,7 @@ import { eventHandlers } from './event';
|
|||||||
import { paymentHandlers } from './payment';
|
import { paymentHandlers } from './payment';
|
||||||
import { industryHandlers } from './industry';
|
import { industryHandlers } from './industry';
|
||||||
import { conceptHandlers } from './concept';
|
import { conceptHandlers } from './concept';
|
||||||
|
import { stockHandlers } from './stock';
|
||||||
|
|
||||||
// 可以在这里添加更多的 handlers
|
// 可以在这里添加更多的 handlers
|
||||||
// import { userHandlers } from './user';
|
// import { userHandlers } from './user';
|
||||||
@@ -20,5 +21,6 @@ export const handlers = [
|
|||||||
...paymentHandlers,
|
...paymentHandlers,
|
||||||
...industryHandlers,
|
...industryHandlers,
|
||||||
...conceptHandlers,
|
...conceptHandlers,
|
||||||
|
...stockHandlers,
|
||||||
// ...userHandlers,
|
// ...userHandlers,
|
||||||
];
|
];
|
||||||
|
|||||||
143
src/mocks/handlers/stock.js
Normal file
143
src/mocks/handlers/stock.js
Normal file
@@ -0,0 +1,143 @@
|
|||||||
|
// src/mocks/handlers/stock.js
|
||||||
|
// 股票相关的 Mock Handlers
|
||||||
|
|
||||||
|
import { http, HttpResponse } from 'msw';
|
||||||
|
|
||||||
|
// 模拟延迟
|
||||||
|
const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));
|
||||||
|
|
||||||
|
// 生成A股主要股票数据(包含各大指数成分股)
|
||||||
|
const generateStockList = () => {
|
||||||
|
const stocks = [
|
||||||
|
// 银行
|
||||||
|
{ code: '000001', name: '平安银行' },
|
||||||
|
{ code: '600000', name: '浦发银行' },
|
||||||
|
{ code: '600036', name: '招商银行' },
|
||||||
|
{ code: '601166', name: '兴业银行' },
|
||||||
|
{ code: '601169', name: '北京银行' },
|
||||||
|
{ code: '601288', name: '农业银行' },
|
||||||
|
{ code: '601328', name: '交通银行' },
|
||||||
|
{ code: '601398', name: '工商银行' },
|
||||||
|
{ code: '601818', name: '光大银行' },
|
||||||
|
{ code: '601939', name: '建设银行' },
|
||||||
|
{ code: '601998', name: '中信银行' },
|
||||||
|
|
||||||
|
// 证券
|
||||||
|
{ code: '600030', name: '中信证券' },
|
||||||
|
{ code: '600109', name: '国金证券' },
|
||||||
|
{ code: '600837', name: '海通证券' },
|
||||||
|
{ code: '600999', name: '招商证券' },
|
||||||
|
{ code: '601688', name: '华泰证券' },
|
||||||
|
{ code: '601901', name: '方正证券' },
|
||||||
|
|
||||||
|
// 保险
|
||||||
|
{ code: '601318', name: '中国平安' },
|
||||||
|
{ code: '601336', name: '新华保险' },
|
||||||
|
{ code: '601601', name: '中国太保' },
|
||||||
|
{ code: '601628', name: '中国人寿' },
|
||||||
|
|
||||||
|
// 白酒/食品饮料
|
||||||
|
{ code: '000568', name: '泸州老窖' },
|
||||||
|
{ code: '000596', name: '古井贡酒' },
|
||||||
|
{ code: '000858', name: '五粮液' },
|
||||||
|
{ code: '600519', name: '贵州茅台' },
|
||||||
|
{ code: '600600', name: '青岛啤酒' },
|
||||||
|
{ code: '600779', name: '水井坊' },
|
||||||
|
{ code: '603369', name: '今世缘' },
|
||||||
|
|
||||||
|
// 医药
|
||||||
|
{ code: '000538', name: '云南白药' },
|
||||||
|
{ code: '000661', name: '长春高新' },
|
||||||
|
{ code: '002422', name: '科伦药业' },
|
||||||
|
{ code: '002594', name: '比亚迪' },
|
||||||
|
{ code: '600276', name: '恒瑞医药' },
|
||||||
|
{ code: '600436', name: '片仔癀' },
|
||||||
|
{ code: '603259', name: '药明康德' },
|
||||||
|
|
||||||
|
// 科技/半导体
|
||||||
|
{ code: '000063', name: '中兴通讯' },
|
||||||
|
{ code: '000725', name: '京东方A' },
|
||||||
|
{ code: '002049', name: '紫光国微' },
|
||||||
|
{ code: '002415', name: '海康威视' },
|
||||||
|
{ code: '002475', name: '立讯精密' },
|
||||||
|
{ code: '600584', name: '长电科技' },
|
||||||
|
{ code: '600893', name: '航发动力' },
|
||||||
|
{ code: '603501', name: '韦尔股份' },
|
||||||
|
|
||||||
|
// 新能源/电力
|
||||||
|
{ code: '000002', name: '万科A' },
|
||||||
|
{ code: '002460', name: '赣锋锂业' },
|
||||||
|
{ code: '300750', name: '宁德时代' },
|
||||||
|
{ code: '600438', name: '通威股份' },
|
||||||
|
{ code: '601012', name: '隆基绿能' },
|
||||||
|
{ code: '601668', name: '中国建筑' },
|
||||||
|
|
||||||
|
// 汽车
|
||||||
|
{ code: '000625', name: '长安汽车' },
|
||||||
|
{ code: '600066', name: '宇通客车' },
|
||||||
|
{ code: '600104', name: '上汽集团' },
|
||||||
|
{ code: '601238', name: '广汽集团' },
|
||||||
|
{ code: '601633', name: '长城汽车' },
|
||||||
|
|
||||||
|
// 地产
|
||||||
|
{ code: '000002', name: '万科A' },
|
||||||
|
{ code: '000069', name: '华侨城A' },
|
||||||
|
{ code: '600340', name: '华夏幸福' },
|
||||||
|
{ code: '600606', name: '绿地控股' },
|
||||||
|
|
||||||
|
// 家电
|
||||||
|
{ code: '000333', name: '美的集团' },
|
||||||
|
{ code: '000651', name: '格力电器' },
|
||||||
|
{ code: '002032', name: '苏泊尔' },
|
||||||
|
{ code: '600690', name: '海尔智家' },
|
||||||
|
|
||||||
|
// 互联网/电商
|
||||||
|
{ code: '002024', name: '苏宁易购' },
|
||||||
|
{ code: '002074', name: '国轩高科' },
|
||||||
|
{ code: '300059', name: '东方财富' },
|
||||||
|
|
||||||
|
// 能源/化工
|
||||||
|
{ code: '600028', name: '中国石化' },
|
||||||
|
{ code: '600309', name: '万华化学' },
|
||||||
|
{ code: '600547', name: '山东黄金' },
|
||||||
|
{ code: '600585', name: '海螺水泥' },
|
||||||
|
{ code: '601088', name: '中国神华' },
|
||||||
|
{ code: '601857', name: '中国石油' },
|
||||||
|
|
||||||
|
// 电信/运营商
|
||||||
|
{ code: '600050', name: '中国联通' },
|
||||||
|
{ code: '600941', name: '中国移动' },
|
||||||
|
{ code: '601728', name: '中国电信' },
|
||||||
|
|
||||||
|
// 其他蓝筹
|
||||||
|
{ code: '600887', name: '伊利股份' },
|
||||||
|
{ code: '601111', name: '中国国航' },
|
||||||
|
{ code: '601390', name: '中国中铁' },
|
||||||
|
{ code: '601899', name: '紫金矿业' },
|
||||||
|
{ code: '603288', name: '海天味业' },
|
||||||
|
];
|
||||||
|
|
||||||
|
return stocks;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 股票相关的 Handlers
|
||||||
|
export const stockHandlers = [
|
||||||
|
// 获取所有股票列表
|
||||||
|
http.get('/api/stocklist', async () => {
|
||||||
|
await delay(200);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const stocks = generateStockList();
|
||||||
|
|
||||||
|
console.log('[Mock Stock] 获取股票列表成功:', { count: stocks.length });
|
||||||
|
|
||||||
|
return HttpResponse.json(stocks);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[Mock Stock] 获取股票列表失败:', error);
|
||||||
|
return HttpResponse.json(
|
||||||
|
{ error: '获取股票列表失败' },
|
||||||
|
{ status: 500 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
];
|
||||||
@@ -1,183 +0,0 @@
|
|||||||
// src/views/Community/components/EventFilters.js
|
|
||||||
import React, { useState, useEffect } from 'react';
|
|
||||||
import { Card, Row, Col, DatePicker, Button, Select, Form, Cascader } from 'antd';
|
|
||||||
import { FilterOutlined } from '@ant-design/icons';
|
|
||||||
import moment from 'moment';
|
|
||||||
import locale from 'antd/es/date-picker/locale/zh_CN';
|
|
||||||
import { useIndustry } from '../../../contexts/IndustryContext';
|
|
||||||
import { logger } from '../../../utils/logger';
|
|
||||||
|
|
||||||
const { RangePicker } = DatePicker;
|
|
||||||
const { Option } = Select;
|
|
||||||
|
|
||||||
const EventFilters = ({ filters, onFilterChange, loading }) => {
|
|
||||||
const [form] = Form.useForm();
|
|
||||||
|
|
||||||
// 使用全局行业数据
|
|
||||||
const { industryData, loading: industryLoading, loadIndustryData } = useIndustry();
|
|
||||||
|
|
||||||
// 初始化表单值
|
|
||||||
useEffect(() => {
|
|
||||||
const initialValues = {
|
|
||||||
date_range: filters.date_range ? filters.date_range.split(' 至 ').map(d => moment(d)) : null,
|
|
||||||
sort: filters.sort,
|
|
||||||
importance: filters.importance,
|
|
||||||
industry_code: filters.industry_code ? filters.industry_code.split(',') : []
|
|
||||||
};
|
|
||||||
form.setFieldsValue(initialValues);
|
|
||||||
}, [filters, form]);
|
|
||||||
|
|
||||||
// Cascader 获得焦点时确保数据已加载
|
|
||||||
const handleCascaderFocus = async () => {
|
|
||||||
if (!industryData || industryData.length === 0) {
|
|
||||||
logger.debug('EventFilters', 'Cascader 获得焦点,触发数据加载');
|
|
||||||
await loadIndustryData();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleDateRangeChange = (dates) => {
|
|
||||||
if (dates && dates.length === 2) {
|
|
||||||
const dateRange = `${dates[0].format('YYYY-MM-DD')} 至 ${dates[1].format('YYYY-MM-DD')}`;
|
|
||||||
onFilterChange('date_range', dateRange);
|
|
||||||
} else {
|
|
||||||
onFilterChange('date_range', '');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSortChange = (value) => {
|
|
||||||
onFilterChange('sort', value);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleImportanceChange = (value) => {
|
|
||||||
onFilterChange('importance', value);
|
|
||||||
};
|
|
||||||
|
|
||||||
// 收集所有叶子节点的 value(递归)
|
|
||||||
const collectLeafValues = (node) => {
|
|
||||||
// 如果没有子节点,说明是叶子节点
|
|
||||||
if (!node.children || node.children.length === 0) {
|
|
||||||
return [node.value];
|
|
||||||
}
|
|
||||||
|
|
||||||
// 有子节点,递归收集所有子节点的叶子节点
|
|
||||||
let leafValues = [];
|
|
||||||
node.children.forEach(child => {
|
|
||||||
leafValues = leafValues.concat(collectLeafValues(child));
|
|
||||||
});
|
|
||||||
return leafValues;
|
|
||||||
};
|
|
||||||
|
|
||||||
// 根据级联路径找到对应的节点
|
|
||||||
const findNodeByPath = (options, path) => {
|
|
||||||
let current = options;
|
|
||||||
let node = null;
|
|
||||||
|
|
||||||
for (let i = 0; i < path.length; i++) {
|
|
||||||
node = current.find(item => item.value === path[i]);
|
|
||||||
if (!node) return null;
|
|
||||||
if (i < path.length - 1) {
|
|
||||||
current = node.children || [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return node;
|
|
||||||
};
|
|
||||||
|
|
||||||
// 行业级联选择变化
|
|
||||||
const handleIndustryChange = (value) => {
|
|
||||||
if (!value || value.length === 0) {
|
|
||||||
onFilterChange('industry_code', '');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取选中的节点
|
|
||||||
const selectedNode = findNodeByPath(industryData || [], value);
|
|
||||||
|
|
||||||
if (!selectedNode) {
|
|
||||||
// 如果找不到节点,使用最后一个值
|
|
||||||
onFilterChange('industry_code', value[value.length - 1]);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果选中的节点有子节点,收集所有叶子节点的 value
|
|
||||||
// 这样可以匹配该级别下的所有事件
|
|
||||||
if (selectedNode.children && selectedNode.children.length > 0) {
|
|
||||||
const leafValues = collectLeafValues(selectedNode);
|
|
||||||
onFilterChange('industry_code', leafValues.join(','));
|
|
||||||
} else {
|
|
||||||
// 叶子节点,直接使用该 value
|
|
||||||
onFilterChange('industry_code', selectedNode.value);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Card className="event-filters" title="事件筛选" style={{ marginBottom: 16 }}>
|
|
||||||
<Form form={form} layout="vertical">
|
|
||||||
<Row gutter={16}>
|
|
||||||
<Col span={12}>
|
|
||||||
<Form.Item label="日期范围" name="date_range">
|
|
||||||
<RangePicker
|
|
||||||
style={{ width: '100%' }}
|
|
||||||
locale={locale}
|
|
||||||
placeholder={['开始日期', '结束日期']}
|
|
||||||
onChange={handleDateRangeChange}
|
|
||||||
disabled={loading}
|
|
||||||
allowEmpty={[true, true]}
|
|
||||||
/>
|
|
||||||
</Form.Item>
|
|
||||||
</Col>
|
|
||||||
<Col span={12}>
|
|
||||||
<Row gutter={8}>
|
|
||||||
<Col span={12}>
|
|
||||||
<Form.Item label="排序方式" name="sort">
|
|
||||||
<Select onChange={handleSortChange} disabled={loading}>
|
|
||||||
<Option value="new">最新</Option>
|
|
||||||
<Option value="hot">热门</Option>
|
|
||||||
<Option value="returns">收益率</Option>
|
|
||||||
</Select>
|
|
||||||
</Form.Item>
|
|
||||||
</Col>
|
|
||||||
<Col span={12}>
|
|
||||||
<Form.Item label="重要性" name="importance">
|
|
||||||
<Select onChange={handleImportanceChange} disabled={loading}>
|
|
||||||
<Option value="all">全部</Option>
|
|
||||||
<Option value="S">S级</Option>
|
|
||||||
<Option value="A">A级</Option>
|
|
||||||
<Option value="B">B级</Option>
|
|
||||||
<Option value="C">C级</Option>
|
|
||||||
</Select>
|
|
||||||
</Form.Item>
|
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
|
|
||||||
{/* 行业分类级联选择器 - 替换原来的 5 个独立 Select */}
|
|
||||||
<Row gutter={16}>
|
|
||||||
<Col span={24}>
|
|
||||||
<Form.Item label="行业" name="industry_cascade">
|
|
||||||
<Cascader
|
|
||||||
options={industryData || []}
|
|
||||||
onChange={handleIndustryChange}
|
|
||||||
onFocus={handleCascaderFocus}
|
|
||||||
placeholder={industryLoading ? "加载中..." : "请选择行业(支持选择任意级别)"}
|
|
||||||
changeOnSelect
|
|
||||||
expandTrigger="hover"
|
|
||||||
showSearch={{
|
|
||||||
filter: (inputValue, path) =>
|
|
||||||
path.some(option => option.label.toLowerCase().indexOf(inputValue.toLowerCase()) > -1)
|
|
||||||
}}
|
|
||||||
disabled={loading || industryLoading}
|
|
||||||
loading={industryLoading}
|
|
||||||
allowClear
|
|
||||||
style={{ width: '100%' }}
|
|
||||||
displayRender={(labels) => labels.join(' / ')}
|
|
||||||
/>
|
|
||||||
</Form.Item>
|
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
</Form>
|
|
||||||
</Card>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default EventFilters;
|
|
||||||
Reference in New Issue
Block a user