diff --git a/.env.mock b/.env.mock deleted file mode 100644 index 9f23c071..00000000 --- a/.env.mock +++ /dev/null @@ -1,41 +0,0 @@ -# ======================================== -# Mock 测试环境配置 -# ======================================== -# 使用方式: npm run start:mock -# -# 工作原理: -# 1. 通过 env-cmd 加载此配置文件 -# 2. REACT_APP_ENABLE_MOCK=true 会在 src/index.js 中启动 MSW (Mock Service Worker) -# 3. MSW 在浏览器层面拦截所有 HTTP 请求 -# 4. 根据 src/mocks/handlers/* 中定义的规则返回 mock 数据 -# 5. 未定义 mock 的接口会继续请求真实后端 -# -# 适用场景: -# - 前端独立开发,无需后端支持 -# - 测试特定接口的 UI 表现 -# - 后端接口未就绪时的快速原型开发 -# ======================================== - -# React 构建优化配置 -GENERATE_SOURCEMAP=false -SKIP_PREFLIGHT_CHECK=true -DISABLE_ESLINT_PLUGIN=true -TSC_COMPILE_ON_ERROR=true -IMAGE_INLINE_SIZE_LIMIT=10000 -NODE_OPTIONS=--max_old_space_size=4096 - -# API 配置 -# Mock 模式下使用空字符串,让请求使用相对路径 -# MSW 会在浏览器层拦截这些请求,不需要真实的后端地址 -REACT_APP_API_URL= - -# Socket.IO 连接地址(Mock 模式下连接生产环境) -# 注意:WebSocket 不被 MSW 拦截,可以独立配置 -REACT_APP_SOCKET_URL=https://valuefrontier.cn - -# 启用 Mock 数据(核心配置) -# 此配置会触发 src/index.js 中的 MSW 初始化 -REACT_APP_ENABLE_MOCK=true - -# Mock 环境标识 -REACT_APP_ENV=mock diff --git a/package.json b/package.json index 43029d68..1a2ceb1d 100755 --- a/package.json +++ b/package.json @@ -65,7 +65,7 @@ }, "scripts": { "prestart": "kill-port 3000", - "start": "NODE_OPTIONS='--openssl-legacy-provider --max_old_space_size=4096' env-cmd -f .env.mock craco start", + "start": "NODE_OPTIONS='--openssl-legacy-provider --max_old_space_size=4096' env-cmd -f .env.development craco start", "prestart:real": "kill-port 3000", "start:real": "NODE_OPTIONS='--openssl-legacy-provider --max_old_space_size=4096' env-cmd -f .env.local craco start", "prestart:dev": "kill-port 3000", @@ -113,7 +113,6 @@ "kill-port": "^2.0.1", "less": "^4.4.2", "less-loader": "^12.3.0", - "msw": "^2.11.5", "prettier": "2.2.1", "react-error-overlay": "6.0.9", "sharp": "^0.34.4", @@ -134,11 +133,6 @@ "not op_mini all" ] }, - "msw": { - "workerDirectory": [ - "public" - ] - }, "optionalDependencies": { "fsevents": "^2.3.3" } diff --git a/public/mockServiceWorker.js b/public/mockServiceWorker.js deleted file mode 100644 index f5cddde0..00000000 --- a/public/mockServiceWorker.js +++ /dev/null @@ -1,349 +0,0 @@ -/* eslint-disable */ -/* tslint:disable */ - -/** - * Mock Service Worker. - * @see https://github.com/mswjs/msw - * - Please do NOT modify this file. - */ - -const PACKAGE_VERSION = '2.12.2' -const INTEGRITY_CHECKSUM = '4db4a41e972cec1b64cc569c66952d82' -const IS_MOCKED_RESPONSE = Symbol('isMockedResponse') -const activeClientIds = new Set() - -addEventListener('install', function () { - self.skipWaiting() -}) - -addEventListener('activate', function (event) { - event.waitUntil(self.clients.claim()) -}) - -addEventListener('message', async function (event) { - const clientId = Reflect.get(event.source || {}, 'id') - - if (!clientId || !self.clients) { - return - } - - const client = await self.clients.get(clientId) - - if (!client) { - return - } - - const allClients = await self.clients.matchAll({ - type: 'window', - }) - - switch (event.data) { - case 'KEEPALIVE_REQUEST': { - sendToClient(client, { - type: 'KEEPALIVE_RESPONSE', - }) - break - } - - case 'INTEGRITY_CHECK_REQUEST': { - sendToClient(client, { - type: 'INTEGRITY_CHECK_RESPONSE', - payload: { - packageVersion: PACKAGE_VERSION, - checksum: INTEGRITY_CHECKSUM, - }, - }) - break - } - - case 'MOCK_ACTIVATE': { - activeClientIds.add(clientId) - - sendToClient(client, { - type: 'MOCKING_ENABLED', - payload: { - client: { - id: client.id, - frameType: client.frameType, - }, - }, - }) - break - } - - case 'CLIENT_CLOSED': { - activeClientIds.delete(clientId) - - const remainingClients = allClients.filter((client) => { - return client.id !== clientId - }) - - // Unregister itself when there are no more clients - if (remainingClients.length === 0) { - self.registration.unregister() - } - - break - } - } -}) - -addEventListener('fetch', function (event) { - const requestInterceptedAt = Date.now() - - // Bypass navigation requests. - if (event.request.mode === 'navigate') { - return - } - - // Opening the DevTools triggers the "only-if-cached" request - // that cannot be handled by the worker. Bypass such requests. - if ( - event.request.cache === 'only-if-cached' && - event.request.mode !== 'same-origin' - ) { - return - } - - // Bypass all requests when there are no active clients. - // Prevents the self-unregistered worked from handling requests - // after it's been terminated (still remains active until the next reload). - if (activeClientIds.size === 0) { - return - } - - const requestId = crypto.randomUUID() - event.respondWith(handleRequest(event, requestId, requestInterceptedAt)) -}) - -/** - * @param {FetchEvent} event - * @param {string} requestId - * @param {number} requestInterceptedAt - */ -async function handleRequest(event, requestId, requestInterceptedAt) { - const client = await resolveMainClient(event) - const requestCloneForEvents = event.request.clone() - const response = await getResponse( - event, - client, - requestId, - requestInterceptedAt, - ) - - // Send back the response clone for the "response:*" life-cycle events. - // Ensure MSW is active and ready to handle the message, otherwise - // this message will pend indefinitely. - if (client && activeClientIds.has(client.id)) { - const serializedRequest = await serializeRequest(requestCloneForEvents) - - // Clone the response so both the client and the library could consume it. - const responseClone = response.clone() - - sendToClient( - client, - { - type: 'RESPONSE', - payload: { - isMockedResponse: IS_MOCKED_RESPONSE in response, - request: { - id: requestId, - ...serializedRequest, - }, - response: { - type: responseClone.type, - status: responseClone.status, - statusText: responseClone.statusText, - headers: Object.fromEntries(responseClone.headers.entries()), - body: responseClone.body, - }, - }, - }, - responseClone.body ? [serializedRequest.body, responseClone.body] : [], - ) - } - - return response -} - -/** - * Resolve the main client for the given event. - * Client that issues a request doesn't necessarily equal the client - * that registered the worker. It's with the latter the worker should - * communicate with during the response resolving phase. - * @param {FetchEvent} event - * @returns {Promise} - */ -async function resolveMainClient(event) { - const client = await self.clients.get(event.clientId) - - if (activeClientIds.has(event.clientId)) { - return client - } - - if (client?.frameType === 'top-level') { - return client - } - - const allClients = await self.clients.matchAll({ - type: 'window', - }) - - return allClients - .filter((client) => { - // Get only those clients that are currently visible. - return client.visibilityState === 'visible' - }) - .find((client) => { - // Find the client ID that's recorded in the - // set of clients that have registered the worker. - return activeClientIds.has(client.id) - }) -} - -/** - * @param {FetchEvent} event - * @param {Client | undefined} client - * @param {string} requestId - * @param {number} requestInterceptedAt - * @returns {Promise} - */ -async function getResponse(event, client, requestId, requestInterceptedAt) { - // Clone the request because it might've been already used - // (i.e. its body has been read and sent to the client). - const requestClone = event.request.clone() - - function passthrough() { - // Cast the request headers to a new Headers instance - // so the headers can be manipulated with. - const headers = new Headers(requestClone.headers) - - // Remove the "accept" header value that marked this request as passthrough. - // This prevents request alteration and also keeps it compliant with the - // user-defined CORS policies. - const acceptHeader = headers.get('accept') - if (acceptHeader) { - const values = acceptHeader.split(',').map((value) => value.trim()) - const filteredValues = values.filter( - (value) => value !== 'msw/passthrough', - ) - - if (filteredValues.length > 0) { - headers.set('accept', filteredValues.join(', ')) - } else { - headers.delete('accept') - } - } - - return fetch(requestClone, { headers }) - } - - // Bypass mocking when the client is not active. - if (!client) { - return passthrough() - } - - // Bypass initial page load requests (i.e. static assets). - // The absence of the immediate/parent client in the map of the active clients - // means that MSW hasn't dispatched the "MOCK_ACTIVATE" event yet - // and is not ready to handle requests. - if (!activeClientIds.has(client.id)) { - return passthrough() - } - - // Notify the client that a request has been intercepted. - const serializedRequest = await serializeRequest(event.request) - const clientMessage = await sendToClient( - client, - { - type: 'REQUEST', - payload: { - id: requestId, - interceptedAt: requestInterceptedAt, - ...serializedRequest, - }, - }, - [serializedRequest.body], - ) - - switch (clientMessage.type) { - case 'MOCK_RESPONSE': { - return respondWithMock(clientMessage.data) - } - - case 'PASSTHROUGH': { - return passthrough() - } - } - - return passthrough() -} - -/** - * @param {Client} client - * @param {any} message - * @param {Array} transferrables - * @returns {Promise} - */ -function sendToClient(client, message, transferrables = []) { - return new Promise((resolve, reject) => { - const channel = new MessageChannel() - - channel.port1.onmessage = (event) => { - if (event.data && event.data.error) { - return reject(event.data.error) - } - - resolve(event.data) - } - - client.postMessage(message, [ - channel.port2, - ...transferrables.filter(Boolean), - ]) - }) -} - -/** - * @param {Response} response - * @returns {Response} - */ -function respondWithMock(response) { - // Setting response status code to 0 is a no-op. - // However, when responding with a "Response.error()", the produced Response - // instance will have status code set to 0. Since it's not possible to create - // a Response instance with status code 0, handle that use-case separately. - if (response.status === 0) { - return Response.error() - } - - const mockedResponse = new Response(response.body, response) - - Reflect.defineProperty(mockedResponse, IS_MOCKED_RESPONSE, { - value: true, - enumerable: true, - }) - - return mockedResponse -} - -/** - * @param {Request} request - */ -async function serializeRequest(request) { - return { - url: request.url, - mode: request.mode, - method: request.method, - headers: Object.fromEntries(request.headers.entries()), - cache: request.cache, - credentials: request.credentials, - destination: request.destination, - integrity: request.integrity, - redirect: request.redirect, - referrer: request.referrer, - referrerPolicy: request.referrerPolicy, - body: await request.arrayBuffer(), - keepalive: request.keepalive, - } -} diff --git a/src/components/Auth/WechatRegister.js b/src/components/Auth/WechatRegister.js index 223852d0..11e58b14 100644 --- a/src/components/Auth/WechatRegister.js +++ b/src/components/Auth/WechatRegister.js @@ -103,23 +103,6 @@ const styles = { fontSize: '14px', fontWeight: 500, }, - mockSection: { - marginTop: '12px', - paddingTop: '12px', - borderTop: '1px solid rgba(212, 175, 55, 0.1)', - }, - mockBtn: { - background: 'transparent', - color: '#9370DB', - border: '1px solid rgba(147, 112, 219, 0.5)', - }, - mockHint: { - display: 'block', - textAlign: 'center', - color: 'rgba(255, 255, 255, 0.4)', - fontSize: '12px', - marginTop: '4px', - }, iframeLoading: { position: 'absolute', top: 0, @@ -414,27 +397,6 @@ const WechatRegister = forwardRef(function WechatRegister({ subtitle }, ref) { {getStatusText(wechatStatus)} )} - - {process.env.REACT_APP_ENABLE_MOCK === 'true' && wechatStatus === WECHAT_STATUS.WAITING && wechatSessionId && ( -
- - 开发模式 | 自动登录: 5秒 -
- )} ); }); diff --git a/src/contexts/NotificationContext.js b/src/contexts/NotificationContext.js index 0518fd0d..95c84373 100644 --- a/src/contexts/NotificationContext.js +++ b/src/contexts/NotificationContext.js @@ -661,12 +661,6 @@ export const NotificationProvider = ({ children }) => { // ========== 连接到 Socket 服务(⚡ 异步初始化,不阻塞首屏) ========== useEffect(() => { - // ⚡ Mock 模式下跳过 Socket 连接(避免连接生产服务器失败的错误) - if (process.env.REACT_APP_ENABLE_MOCK === 'true') { - logger.debug('NotificationContext', 'Mock 模式,跳过 Socket 连接'); - return; - } - // ⚡ 防止 React Strict Mode 导致的重复初始化 if (socketInitialized) { logger.debug('NotificationContext', 'Socket 已初始化,跳过重复执行(Strict Mode 保护)'); diff --git a/src/index.js b/src/index.js index dda7fd09..f5bd509f 100755 --- a/src/index.js +++ b/src/index.js @@ -52,11 +52,6 @@ if (process.env.REACT_APP_ENABLE_DEBUG === 'true') { // 注册 Service Worker(用于支持浏览器通知) function registerServiceWorker() { - // ⚠️ Mock 模式下跳过 Service Worker 注册(避免与 MSW 冲突) - if (process.env.REACT_APP_ENABLE_MOCK === 'true') { - return; - } - // 仅在支持 Service Worker 的浏览器中注册 if ('serviceWorker' in navigator) { window.addEventListener('load', () => { @@ -87,26 +82,9 @@ function renderApp() { ); - // 注册 Service Worker(非 Mock 模式) + // 注册 Service Worker registerServiceWorker(); } // 启动应用 -async function startApp() { - // ✅ 开发环境 Mock 模式:先启动 MSW,再渲染应用 - // 确保所有 API 请求(包括 AuthContext.checkSession)都被正确拦截 - if (process.env.NODE_ENV === 'development' && process.env.REACT_APP_ENABLE_MOCK === 'true') { - try { - const { startMockServiceWorker } = await import('./mocks/browser'); - await startMockServiceWorker(); - } catch (error) { - console.error('[MSW] 启动失败:', error); - } - } - - // 渲染应用 - renderApp(); -} - -// 启动应用 -startApp(); \ No newline at end of file +renderApp(); \ No newline at end of file diff --git a/src/mocks/browser.js b/src/mocks/browser.js deleted file mode 100644 index b454e1b8..00000000 --- a/src/mocks/browser.js +++ /dev/null @@ -1,75 +0,0 @@ -// src/mocks/browser.js -// 浏览器环境的 MSW Worker - -import { setupWorker } from 'msw/browser'; -import { handlers } from './handlers'; - -// 创建 Service Worker 实例 -export const worker = setupWorker(...handlers); - -// 启动状态管理(防止重复启动) -let isStarting = false; -let isStarted = false; - -// 启动 Mock Service Worker -export async function startMockServiceWorker() { - // 防止重复启动 - if (isStarting || isStarted) { - console.log('[MSW] Mock Service Worker 已启动或正在启动中,跳过重复调用'); - return; - } - - // 只在开发环境且 REACT_APP_ENABLE_MOCK=true 时启动 - const shouldEnableMock = process.env.REACT_APP_ENABLE_MOCK === 'true'; - - if (!shouldEnableMock) { - console.log('[MSW] Mock 已禁用 (REACT_APP_ENABLE_MOCK=false)'); - return; - } - - isStarting = true; - - try { - await worker.start({ - // 🎯 警告模式(关键配置) - // 'bypass': 未定义 Mock 的请求自动转发到真实后端 - // 'warn': 未定义的请求会显示警告(调试用)✅ 当前使用(允许 passthrough) - // 'error': 未定义的请求会抛出错误(严格模式,不允许 passthrough) - onUnhandledRequest: 'warn', - - // 自定义 Service Worker URL(如果需要) - serviceWorker: { - url: '/mockServiceWorker.js', - }, - - // 是否在控制台显示启动日志和拦截日志 静默模式(不在控制台打印启动消息) - quiet: false, - }); - - isStarted = true; - // 精简日志:只保留一行启动提示 - console.log('%c[MSW] Mock 已启用 🎭', 'color: #4CAF50; font-weight: bold;'); - } catch (error) { - console.error('[MSW] 启动失败:', error); - } finally { - isStarting = false; - } -} - -// 停止 Mock Service Worker -export function stopMockServiceWorker() { - if (!isStarted) { - console.log('[MSW] Mock Service Worker 未启动,无需停止'); - return; - } - - worker.stop(); - isStarted = false; - console.log('[MSW] Mock Service Worker 已停止'); -} - -// 重置所有 Handlers -export function resetMockHandlers() { - worker.resetHandlers(); - console.log('[MSW] Handlers 已重置'); -} diff --git a/src/mocks/data/account.js b/src/mocks/data/account.js deleted file mode 100644 index 9b687d25..00000000 --- a/src/mocks/data/account.js +++ /dev/null @@ -1,1551 +0,0 @@ -// src/mocks/data/account.js -// 个人中心相关的 Mock 数据 - -// ==================== 自选股数据 ==================== - -export const mockWatchlist = [ - { - id: 1, - user_id: 1, - stock_code: '600519.SH', - stock_name: '贵州茅台', - industry: '白酒', - current_price: 1650.50, - change_percent: 2.5, - added_at: '2025-01-10T10:30:00Z' - }, - { - id: 2, - user_id: 1, - stock_code: '000001.SZ', - stock_name: '平安银行', - industry: '银行', - current_price: 12.34, - change_percent: 4.76, - added_at: '2025-01-15T14:20:00Z' - }, - { - id: 3, - user_id: 1, - stock_code: '000858.SZ', - stock_name: '五粮液', - industry: '白酒', - current_price: 156.78, - change_percent: 1.52, - added_at: '2025-01-08T09:15:00Z' - }, - { - id: 4, - user_id: 1, - stock_code: '300750.SZ', - stock_name: '宁德时代', - industry: '新能源', - current_price: 168.90, - change_percent: -1.23, - added_at: '2025-01-12T16:45:00Z' - }, - { - id: 5, - user_id: 1, - stock_code: '002594.SZ', - stock_name: 'BYD比亚迪', - industry: '新能源汽车', - current_price: 256.88, - change_percent: 3.45, - added_at: '2025-01-05T11:20:00Z' - } -]; - -// ==================== 实时行情数据 ==================== - -export const mockRealtimeQuotes = [ - { - stock_code: '600519.SH', - current_price: 1650.50, - change_percent: 2.5, - change: 40.25, - volume: 2345678, - turnover: 3945678901.23, - high: 1665.00, - low: 1645.00, - open: 1648.80, - prev_close: 1610.25, - update_time: '15:00:00' - }, - { - stock_code: '000001.SZ', - current_price: 12.34, - change_percent: 4.76, - change: 0.56, - volume: 123456789, - turnover: 1523456789.12, - high: 12.50, - low: 11.80, - open: 11.90, - prev_close: 11.78, - update_time: '15:00:00' - }, - { - stock_code: '000858.SZ', - current_price: 156.78, - change_percent: 1.52, - change: 2.34, - volume: 45678901, - turnover: 7123456789.45, - high: 158.00, - low: 154.50, - open: 155.00, - prev_close: 154.44, - update_time: '15:00:00' - }, - { - stock_code: '300750.SZ', - current_price: 168.90, - change_percent: -1.23, - change: -2.10, - volume: 98765432, - turnover: 16678945612.34, - high: 172.30, - low: 167.50, - open: 171.00, - prev_close: 171.00, - update_time: '15:00:00' - }, - { - stock_code: '002594.SZ', - current_price: 256.88, - change_percent: 3.45, - change: 8.56, - volume: 56789012, - turnover: 14567890123.45, - high: 260.00, - low: 252.00, - open: 253.50, - prev_close: 248.32, - update_time: '15:00:00' - } -]; - -// ==================== 关注事件数据 ==================== - -// 事件关注内存存储(Set 存储已关注的事件 ID) -export const followedEventsSet = new Set(); - -// 关注事件完整数据存储(Map: eventId -> eventData) -export const followedEventsMap = new Map(); - -// 初始关注事件列表(用于初始化) -export const mockFollowingEvents = [ - { - id: 101, - title: '央行宣布降准0.5个百分点,释放长期资金约1.2万亿元', - tags: ['货币政策', '央行', '降准', '银行'], - view_count: 12340, - comment_count: 156, - upvote_count: 489, - heat_score: 95, - exceed_expectation_score: 85, - related_avg_chg: 1.25, - related_max_chg: 3.15, - related_week_chg: 2.80, - creator: { - id: 1001, - username: '财经分析师', - avatar_url: 'https://i.pravatar.cc/150?img=11' - }, - created_at: '2025-01-15T09:00:00Z', - followed_at: '2025-01-15T10:30:00Z' - }, - { - id: 102, - title: 'ChatGPT-5 即将发布,AI 算力需求将迎来爆发式增长', - tags: ['人工智能', 'ChatGPT', '算力', '科技'], - view_count: 8950, - comment_count: 234, - upvote_count: 567, - heat_score: 88, - exceed_expectation_score: 78, - related_avg_chg: 5.60, - related_max_chg: 12.50, - related_week_chg: 8.90, - creator: { - id: 1002, - username: '科技观察者', - avatar_url: 'https://i.pravatar.cc/150?img=12' - }, - created_at: '2025-01-14T14:20:00Z', - followed_at: '2025-01-14T15:00:00Z' - }, - { - id: 103, - title: '新能源汽车补贴政策延续至2026年,行业持续受益', - tags: ['新能源', '汽车', '补贴政策', '产业政策'], - view_count: 6780, - comment_count: 98, - upvote_count: 345, - heat_score: 72, - exceed_expectation_score: 68, - related_avg_chg: 2.35, - related_max_chg: 6.80, - related_week_chg: 4.20, - creator: { - id: 1003, - username: '产业研究员', - avatar_url: 'https://i.pravatar.cc/150?img=13' - }, - created_at: '2025-01-13T11:15:00Z', - followed_at: '2025-01-13T12:00:00Z' - }, - { - id: 104, - title: '芯片法案正式实施,国产半导体迎来黄金发展期', - tags: ['半导体', '芯片', '国产替代', '政策'], - view_count: 9540, - comment_count: 178, - upvote_count: 432, - heat_score: 80, - exceed_expectation_score: 72, - related_avg_chg: 3.80, - related_max_chg: 9.20, - related_week_chg: 6.50, - creator: { - id: 1004, - username: '半导体观察', - avatar_url: 'https://i.pravatar.cc/150?img=14' - }, - created_at: '2025-01-12T16:30:00Z', - followed_at: '2025-01-12T17:00:00Z' - }, - { - id: 105, - title: '医保目录调整,创新药企业有望获得更多市场份额', - tags: ['医药', '医保', '创新药', '政策'], - view_count: 5430, - comment_count: 87, - upvote_count: 234, - heat_score: 65, - exceed_expectation_score: null, - related_avg_chg: -0.80, - related_max_chg: 2.50, - related_week_chg: 1.20, - creator: { - id: 1005, - username: '医药行业专家', - avatar_url: 'https://i.pravatar.cc/150?img=15' - }, - created_at: '2025-01-11T10:00:00Z', - followed_at: '2025-01-11T11:30:00Z' - } -]; - -// 初始化:将 mockFollowingEvents 的数据加入内存存储 -mockFollowingEvents.forEach(event => { - followedEventsSet.add(event.id); - followedEventsMap.set(event.id, event); -}); - -/** - * 切换事件关注状态 - * @param {number} eventId - 事件 ID - * @param {Object} eventData - 事件数据(关注时需要) - * @returns {{ isFollowing: boolean, followerCount: number }} - */ -export function toggleEventFollowStatus(eventId, eventData = null) { - const wasFollowing = followedEventsSet.has(eventId); - - if (wasFollowing) { - // 取消关注 - followedEventsSet.delete(eventId); - followedEventsMap.delete(eventId); - } else { - // 添加关注 - followedEventsSet.add(eventId); - if (eventData) { - followedEventsMap.set(eventId, { - ...eventData, - followed_at: new Date().toISOString() - }); - } else { - // 如果没有提供事件数据,创建基础数据 - followedEventsMap.set(eventId, { - id: eventId, - title: `事件 ${eventId}`, - tags: [], - followed_at: new Date().toISOString() - }); - } - } - - const isFollowing = !wasFollowing; - const followerCount = isFollowing ? Math.floor(Math.random() * 500) + 100 : Math.floor(Math.random() * 500) + 50; - - console.log('[Mock Data] 切换事件关注状态:', { - eventId, - wasFollowing, - isFollowing, - followedEventsCount: followedEventsSet.size - }); - - return { isFollowing, followerCount }; -} - -/** - * 检查事件是否已关注 - * @param {number} eventId - 事件 ID - * @returns {boolean} - */ -export function isEventFollowed(eventId) { - return followedEventsSet.has(eventId); -} - -/** - * 获取所有已关注的事件列表 - * @returns {Array} - */ -export function getFollowedEvents() { - return Array.from(followedEventsMap.values()); -} - -// ==================== 评论数据 ==================== - -export const mockEventComments = [ - { - id: 201, - user_id: 1, - event_id: 101, - event_title: '央行宣布降准0.5个百分点,释放长期资金约1.2万亿元', - content: '这次降准对银行股是重大利好!预计四大行和股份制银行都会受益,特别是净息差承压的中小银行。建议重点关注招商银行、兴业银行等优质标的。', - created_at: '2025-01-15T11:20:00Z', - likes: 45, - replies: 12 - }, - { - id: 202, - user_id: 1, - event_id: 102, - event_title: 'ChatGPT-5 即将发布,AI 算力需求将迎来爆发式增长', - content: 'AI 板块又要起飞了!重点关注算力基础设施概念股,如服务器、芯片、数据中心等。另外,AI 应用端也值得关注,特别是已经有成熟产品的公司。', - created_at: '2025-01-14T16:45:00Z', - likes: 38, - replies: 8 - }, - { - id: 203, - user_id: 1, - event_id: 103, - event_title: '新能源汽车补贴政策延续至2026年,行业持续受益', - content: '政策延续对整个产业链都是好消息。上游的锂电池、下游的整车厂都会受益。比亚迪和宁德时代可以继续持有,长期看好新能源汽车的渗透率提升。', - created_at: '2025-01-13T14:30:00Z', - likes: 56, - replies: 15 - }, - { - id: 204, - user_id: 1, - event_id: 104, - event_title: '芯片法案正式实施,国产半导体迎来黄金发展期', - content: '国产替代是大趋势!设备材料、设计封测、制造都有机会。关注那些有核心技术、已经打入国内大厂供应链的公司。半导体是长期主线,波动中坚定持有。', - created_at: '2025-01-12T18:00:00Z', - likes: 67, - replies: 20 - }, - { - id: 205, - user_id: 1, - event_id: 105, - event_title: '医保目录调整,创新药企业有望获得更多市场份额', - content: '医保谈判结果出来了,创新药企业普遍受益。重点关注有多个重磅品种的药企,以及 CXO 产业链。医药板块经过调整后,估值已经比较合理,可以逐步配置。', - created_at: '2025-01-11T13:15:00Z', - likes: 42, - replies: 10 - } -]; - -// ==================== 投资计划与复盘数据 ==================== - -export const mockInvestmentPlans = [ - // ==================== 计划数据(符合计划模板) ==================== - { - id: 301, - user_id: 1, - type: 'plan', - title: '2025年Q1 新能源板块布局计划', - content: `【目标】 -在Q1末实现新能源板块仓位15%,预计收益率20%,重点捕捉新能源政策利好和销量数据催化。 - -【策略】 -1. 宁德时代:占比6%,等待回调至160元附近分批买入,技术面看好底部放量信号 -2. 比亚迪:占比6%,当前价位可以开始建仓,采用金字塔式加仓 -3. 隆基绿能:占比3%,观察光伏行业景气度再决定,等待基本面拐点确认 - -【风险控制】 -- 单只个股止损-8% -- 板块整体止损-10% -- 遇到系统性风险事件,果断减仓50% -- 避免在重大财报日前重仓 - -【时间规划】 -- 1月中旬:完成第一批建仓(5%仓位) -- 2月春节后:根据市场情况加仓(5%仓位) -- 3月中旬:完成最终布局(5%仓位) -- 季度末:复盘调整,决定是否持有到Q2`, - target_date: '2025-03-31', - status: 'in_progress', - created_at: '2025-01-10T10:00:00Z', - updated_at: '2025-01-15T14:30:00Z', - tags: ['新能源', '布局计划', 'Q1计划'], - stocks: ['300750.SZ', '002594.SZ', '601012.SH'] - }, - { - id: 303, - user_id: 1, - type: 'plan', - title: 'AI 算力板块波段交易计划', - content: `【目标】 -捕捉ChatGPT-5发布带来的AI算力板块短期行情,目标收益15-20%,控制最大回撤在8%以内。 - -【策略】 -- 寒武纪:AI芯片龙头,弹性最大,首选标的 -- 中科曙光:服务器厂商,业绩支撑更扎实 -- 浪潮信息:算力基础设施,流动性好 -- 采用金字塔式买入,第一笔3%,后续根据走势加仓 -- 快进快出,涨幅20%分批止盈 - -【风险控制】 -- 仓位控制在10%以内(高风险高弹性) -- 单只个股止损-5% -- 破位及时止损,不恋战 -- 避免追高,只在回调时介入 - -【时间规划】 -- 本周:观察消息面发酵情况,确定进场时机 -- 发布前1周:逐步建仓 -- 发布后:根据市场反应决定持有还是止盈 -- 2月底前:完成此轮操作`, - target_date: '2025-02-28', - status: 'pending', - created_at: '2025-01-14T16:00:00Z', - updated_at: '2025-01-14T16:00:00Z', - tags: ['AI', '算力', '波段交易'], - stocks: ['688256.SH', '603019.SH', '000977.SZ'] - }, - { - id: 305, - user_id: 1, - type: 'plan', - title: '银行股防守配置计划', - content: `【目标】 -构建15%仓位的银行股防守配置,获取稳定分红收益(股息率5%+),同时等待估值修复带来的资本利得。 - -【策略】 -1. 招商银行:零售银行龙头,ROE持续优秀,配置8% -2. 兴业银行:同业业务优势明显,配置4% -3. 成都银行:城商行中成长性最好,配置3% -选股逻辑:优先选择ROE高、资产质量好、分红稳定的标的 - -【风险控制】 -- 银行股整体波动较小,但需关注宏观经济风险 -- 如遇利率大幅下行或地产风险暴露,需重新评估持仓 -- 单只银行股止损-15%(较宽松,适合长线持有) -- 定期关注季报中的不良贷款率和拨备覆盖率 - -【时间规划】 -- 春节前:完成建仓 -- 全年持有:享受分红收益 -- 年中复盘:根据半年报调整配置比例 -- 年底:评估是否继续持有到下一年`, - target_date: '2025-06-30', - status: 'active', - created_at: '2025-01-08T11:00:00Z', - updated_at: '2025-01-08T11:00:00Z', - tags: ['银行', '防守配置', '高股息'], - stocks: ['600036.SH', '601166.SH', '601838.SH'] - }, - { - id: 306, - user_id: 1, - type: 'plan', - title: '医药创新药中长线布局', - content: `【目标】 -布局医药创新药板块,目标3-6个月内获得25%收益,享受创新药产品上市带来的业绩爆发。 - -【策略】 -1. 恒瑞医药:创新药管线最丰富,PD-1放量进行中 -2. 药明康德:CRO龙头,受益于全球创新药研发外包 -3. 百济神州:海外收入占比高,泽布替尼持续放量 -采用分批建仓策略,避免一次性重仓 - -【风险控制】 -- 总仓位不超过12% -- 单只个股止损-10% -- 关注集采政策风险,如有利空政策出台立即减仓 -- 关注核心产品的销售数据和临床进展 - -【时间规划】 -- 第1个月:建立6%底仓 -- 第2-3个月:根据业绩催化加仓至12% -- 第4-6个月:达到目标收益后分批止盈 -- 每月关注:产品获批进展、销售数据、研报观点`, - target_date: '2025-06-30', - status: 'active', - created_at: '2025-01-05T14:00:00Z', - updated_at: '2025-01-12T09:30:00Z', - tags: ['医药', '创新药', '中长线'], - stocks: ['600276.SH', '603259.SH', '688235.SH'] - }, - { - id: 307, - user_id: 1, - type: 'plan', - title: '消费复苏主题布局计划', - content: `【目标】 -捕捉春节消费旺季和全年消费复苏趋势,目标收益20%,重点布局白酒和免税龙头。 - -【策略】 -1. 贵州茅台:高端白酒龙头,提价预期+渠道优化 -2. 五粮液:次高端领军,估值修复空间大 -3. 中国中免:免税龙头,海南自贸港政策利好 -分散配置,每只占比3-5% - -【风险控制】 -- 总仓位控制在15%以内 -- 单只个股止损-8% -- 关注消费数据变化,如不及预期及时调整 -- 警惕宏观经济下行风险对消费的冲击 - -【时间规划】 -- 春节前2周:完成建仓 -- 春节后:观察销售数据和股价反应 -- Q1末:根据一季度消费数据决定是否加仓 -- 全年跟踪:月度社零数据、旅游数据`, - target_date: '2025-04-30', - status: 'pending', - created_at: '2025-01-03T10:30:00Z', - updated_at: '2025-01-03T10:30:00Z', - tags: ['消费', '白酒', '免税'], - stocks: ['600519.SH', '000858.SZ', '601888.SH'] - }, - - // ==================== 复盘数据(符合复盘模板) ==================== - { - id: 302, - user_id: 1, - type: 'review', - title: '2024年12月投资复盘 - 白酒板块大涨', - content: `【操作回顾】 -1. 11月底在1550元加仓茅台0.5%仓位,持有至今 -2. 五粮液持仓未动,从11月初一直持有 -3. 错过了洋河股份的反弹行情 -4. 月中短线做了一次泸州老窖,小赚2%出局 - -【盈亏分析】 -- 贵州茅台:获利6.5%,贡献账户收益约0.65% -- 五粮液:获利4.2%,贡献账户收益约0.42% -- 泸州老窖:短线获利2%,贡献约0.06% -- 月度总收益:约1.13% -- 同期沪深300涨幅:0.8%,跑赢指数0.33% - -【经验总结】 -- 消费板块在年底有明显的估值修复行情,这个规律可以记住 -- 龙头白马股在市场震荡时更具韧性,应该坚定持有 -- 应该更大胆一些,茅台仓位可以再提高2-3个点 -- 洋河的机会没把握住,主要是对二线白酒信心不足 - -【后续调整】 -- 继续持有茅台、五粮液,不轻易卖出 -- 关注春节前的消费旺季催化 -- 如果有回调,考虑加仓茅台至5%总仓位 -- 下月开始关注春节消费数据`, - target_date: '2024-12-31', - status: 'completed', - created_at: '2025-01-02T09:00:00Z', - updated_at: '2025-01-02T09:00:00Z', - tags: ['月度复盘', '白酒', '2024年12月'], - stocks: ['600519.SH', '000858.SZ', '000568.SZ'] - }, - { - id: 304, - user_id: 1, - type: 'review', - title: '2024年全年投资总结 - 收益率25.6%', - content: `【操作回顾】 -1. 全年共进行交易52次,其中胜率62% -2. 主要盈利来源:新能源(+35%)、白酒(+18%) -3. 主要亏损来源:年初追高的概念股(-8%) -4. 最成功操作:5月底抄底宁德时代,持有3个月获利45% -5. 最失败操作:3月追高机器人概念,亏损12%割肉 - -【盈亏分析】 -- 全年总收益率:25.6% -- 沪深300涨幅:13.6% -- 超额收益:12个百分点 -- 最大回撤:-8.5%(3月份) -- 夏普比率:约1.8 -- 各板块贡献: - - 新能源:+12.6% - - 白酒:+7.2% - - 半导体:+3.2% - - 其他:+2.6% - -【经验总结】 -1. 年初追高热门概念股是最大教训,后续回调损失较大 -2. 止损执行不够坚决,有两次错过最佳止损时机 -3. 仓位管理有待提高,牛市时仓位偏低(最高才70%) -4. 成功的操作都是逆向买入+耐心持有 -5. 频繁交易并没有带来更好收益 - -【后续调整】 -2025年目标: -- 收益率目标:30% -- 优化仓位管理,提高资金使用效率至80%+ -- 严格执行止损纪律,设置自动止损提醒 -- 加强行业研究,提前布局而非追高 -- 减少交易频率,提高单次交易质量`, - target_date: '2024-12-31', - status: 'completed', - created_at: '2025-01-01T10:00:00Z', - updated_at: '2025-01-01T10:00:00Z', - tags: ['年度复盘', '2024年', '总结'], - stocks: [] - }, - { - id: 308, - user_id: 1, - type: 'review', - title: '宁德时代波段操作复盘', - content: `【操作回顾】 -- 5月25日:在160元附近建仓3%,理由是估值回到历史低位+储能业务放量预期 -- 6月10日:加仓2%,价格172元,技术面突破关键阻力 -- 7月20日:再加仓2%,价格195元,财报预告超预期 -- 8月15日:开始分批止盈,卖出3%仓位,均价235元 -- 8月28日:清仓剩余4%仓位,均价228元 - -【盈亏分析】 -- 第一笔:160元买入,平均卖出231.5元,收益率44.7% -- 第二笔:172元买入,平均卖出231.5元,收益率34.6% -- 第三笔:195元买入,平均卖出231.5元,收益率18.7% -- 加权平均收益率:约35% -- 持仓时间:约3个月 -- 年化收益率:约140% - -【经验总结】 -1. 在估值底部+催化剂出现时建仓是正确的选择 -2. 金字塔式加仓策略有效控制了成本 -3. 分批止盈策略让我吃到了大部分涨幅 -4. 但最后一笔加仓(195元)价格偏高,拉低了整体收益 -5. 应该在涨幅达到30%时就开始止盈,而非等到40%+ - -【后续调整】 -- 下次操作宁德时代,设置150-170元为合理买入区间 -- 涨幅达到25%开始分批止盈 -- 储能业务是长期逻辑,可以保留2%底仓长期持有 -- 关注Q4业绩和2025年指引`, - target_date: '2024-08-31', - status: 'completed', - created_at: '2024-09-01T10:00:00Z', - updated_at: '2024-09-01T10:00:00Z', - tags: ['个股复盘', '宁德时代', '波段'], - stocks: ['300750.SZ'] - }, - { - id: 309, - user_id: 1, - type: 'review', - title: '11月第三周交易复盘', - content: `【操作回顾】 -周一:观望,未操作 -周二:买入比亚迪2%仓位,价格248元 -周三:加仓比亚迪1%,价格252元;卖出中芯国际1%仓位 -周四:买入恒瑞医药1.5%仓位,价格42元 -周五:观望,持仓未动 - -【盈亏分析】 -- 本周账户收益:+0.8% -- 比亚迪:浮盈1.2% -- 恒瑞医药:浮亏0.5% -- 中芯国际:卖出盈利3% -- 同期沪深300:+0.5% -- 超额收益:+0.3% - -【经验总结】 -1. 比亚迪买入时机较好,趁回调建仓 -2. 恒瑞医药买得稍早,本周没有继续下跌但也没涨 -3. 中芯国际止盈时机把握得不错,避免了后续调整 -4. 交易频率偏高,手续费成本需要注意 - -【后续调整】 -- 下周继续持有比亚迪和恒瑞,等待催化 -- 如果恒瑞跌破40元,考虑加仓 -- 比亚迪如果突破260元,可以继续加仓 -- 下周计划观察AI板块是否有机会`, - target_date: '2024-11-24', - status: 'completed', - created_at: '2024-11-25T18:00:00Z', - updated_at: '2024-11-25T18:00:00Z', - tags: ['周度复盘', '11月第三周'], - stocks: ['002594.SZ', '600276.SH', '688981.SH'] - }, - { - id: 310, - user_id: 1, - type: 'review', - title: '机器人概念追高教训复盘', - content: `【操作回顾】 -- 3月5日:看到机器人概念连续大涨,FOMO心态买入机器人ETF 5%仓位 -- 3月8日:继续上涨,追加3%仓位 -- 3月12日:见顶回落,犹豫不决 -- 3月18日:跌破成本价,仍抱有侥幸心理 -- 3月25日:止损出局,平均亏损12% - -【盈亏分析】 -- 买入成本:约1.05元(均价) -- 卖出价格:约0.92元 -- 亏损金额:约8%仓位 × 12% = 0.96%账户净值 -- 这是本年度最大单笔亏损 -- 教训成本:约5000元 - -【经验总结】 -1. 追高是最大的错误,概念炒作往往来去匆匆 -2. FOMO心态害死人,看到别人赚钱就想追 -3. 止损不坚决,跌破成本价时就应该走 -4. 对机器人行业基本面了解不够,纯粹是跟风 -5. 仓位太重,首次买入就5%,完全不符合试仓原则 - -【后续调整】 -- 概念炒作坚决不追高,只在调整时考虑 -- 任何新建仓位首次买入不超过2% -- 设置硬性止损-5%,坚决执行 -- 不熟悉的领域少碰或只做小仓位 -- 记住这次教训,下次遇到类似情况要克制`, - target_date: '2024-03-31', - status: 'completed', - created_at: '2024-04-01T09:00:00Z', - updated_at: '2024-04-01T09:00:00Z', - tags: ['教训复盘', '追高', '机器人'], - stocks: [] - }, - { - id: 311, - user_id: 1, - type: 'review', - title: '半导体板块Q3操作复盘', - content: `【操作回顾】 -7月份: -- 买入中芯国际3%仓位,价格45元 -- 买入北方华创2%仓位,价格180元 - -8月份: -- 中芯国际加仓1%,价格48元 -- 北方华创持仓不动 - -9月份: -- 中芯国际在55元分批止盈2% -- 北方华创在195元全部止盈 -- 保留中芯国际2%底仓 - -【盈亏分析】 -- 中芯国际: - - 已止盈部分:收益率约20% - - 剩余持仓:浮盈约15% -- 北方华创: - - 全部止盈,收益率约8% -- Q3半导体板块总收益:约+3.2%账户净值 -- 板块贡献排名:第三(仅次于新能源和白酒) - -【经验总结】 -1. 半导体板块波动大,不适合重仓长持 -2. 北方华创止盈过早,后来又涨了10% -3. 中芯国际的分批止盈策略比较成功 -4. 应该更多关注设备和材料,而非制造环节 -5. 华为产业链相关标的值得持续关注 - -【后续调整】 -- Q4继续持有中芯国际底仓 -- 关注北方华创回调机会 -- 新增关注标的:长电科技、华虹半导体 -- 仓位目标:半导体板块不超过10%`, - target_date: '2024-09-30', - status: 'completed', - created_at: '2024-10-08T10:00:00Z', - updated_at: '2024-10-08T10:00:00Z', - tags: ['季度复盘', '半导体', 'Q3'], - stocks: ['688981.SH', '002371.SZ'] - }, - - // ==================== 今日数据(用于日历视图展示) ==================== - // 测试同日期多事件显示:计划 x3, 复盘 x3, 系统 x3 - { - id: 320, - user_id: 1, - type: 'plan', - title: '今日交易计划 - 年末布局', - content: `【今日目标】 -重点关注年末资金流向,寻找低位优质标的布局机会。 - -【操作计划】 -1. 白酒板块:观察茅台、五粮液走势,若出现回调可适当加仓 -2. 新能源:宁德时代逢低补仓,目标价位160元附近 -3. AI算力:关注寒武纪的突破信号 - -【资金安排】 -- 当前仓位:65% -- 可动用资金:35% -- 计划使用资金:15%(分3笔建仓) - -【风险控制】 -- 单笔止损:-3% -- 日内最大亏损:-5% -- 不追涨,只接回调`, - target_date: '2025-12-23', - status: 'active', - created_at: '2025-12-23T08:30:00Z', - updated_at: '2025-12-23T08:30:00Z', - tags: ['日计划', '年末布局'], - stocks: ['600519.SH', '300750.SZ', '688256.SH'] - }, - { - id: 321, - user_id: 1, - type: 'review', - title: '今日交易复盘 - 市场震荡', - content: `【操作回顾】 -1. 上午10:30 在茅台1580元位置加仓0.5% -2. 下午14:00 宁德时代触及160元支撑位,建仓1% -3. AI算力板块异动,寒武纪涨幅超5%,观望未操作 - -【盈亏分析】 -- 茅台加仓部分:浮盈+0.8% -- 宁德时代:浮亏-0.3%(正常波动范围内) -- 当日账户变动:+0.15% - -【经验总结】 -- 茅台买点把握较好,符合预期的回调位置 -- 宁德时代略显急躁,可以再等一等 -- AI算力虽然错过涨幅,但不追高的纪律执行到位 - -【明日计划】 -- 继续持有今日新增仓位 -- 如茅台继续上涨至1620,可考虑获利了结一半 -- 关注周五PMI数据公布对市场影响`, - target_date: '2025-12-23', - status: 'completed', - created_at: '2025-12-23T15:30:00Z', - updated_at: '2025-12-23T16:00:00Z', - tags: ['日复盘', '年末交易'], - stocks: ['600519.SH', '300750.SZ'] - }, - // 额外计划2:测试同日期多计划显示 - { - id: 322, - user_id: 1, - type: 'plan', - title: 'AI算力板块布局', - content: `【目标】捕捉AI算力板块机会 - -【策略】 -- 寒武纪:关注突破信号 -- 中科曙光:服务器龙头 -- 浪潮信息:算力基础设施`, - target_date: '2025-12-23', - status: 'pending', - created_at: '2025-12-23T09:00:00Z', - updated_at: '2025-12-23T09:00:00Z', - tags: ['AI', '算力'], - stocks: ['688256.SH', '603019.SH', '000977.SZ'] - }, - // 额外计划3:测试同日期多计划显示 - { - id: 323, - user_id: 1, - type: 'plan', - title: '医药板块观察计划', - content: `【目标】关注创新药投资机会 - -【策略】 -- 恒瑞医药:创新药龙头 -- 药明康德:CRO龙头`, - target_date: '2025-12-23', - status: 'pending', - created_at: '2025-12-23T10:00:00Z', - updated_at: '2025-12-23T10:00:00Z', - tags: ['医药', '创新药'], - stocks: ['600276.SH', '603259.SH'] - }, - // 额外复盘2:测试同日期多复盘显示 - { - id: 324, - user_id: 1, - type: 'review', - title: '半导体操作复盘', - content: `【操作回顾】 -- 中芯国际:持仓未动 -- 北方华创:观望 - -【经验总结】 -半导体板块整体震荡,等待突破信号`, - target_date: '2025-12-23', - status: 'completed', - created_at: '2025-12-23T16:00:00Z', - updated_at: '2025-12-23T16:30:00Z', - tags: ['半导体复盘'], - stocks: ['688981.SH', '002371.SZ'] - }, - // 额外复盘3:测试同日期多复盘显示 - { - id: 325, - user_id: 1, - type: 'review', - title: '本周白酒持仓复盘', - content: `【操作回顾】 -- 茅台:本周加仓0.5% -- 五粮液:持仓未动 - -【盈亏分析】 -白酒板块本周表现平稳,继续持有`, - target_date: '2025-12-23', - status: 'completed', - created_at: '2025-12-23T17:00:00Z', - updated_at: '2025-12-23T17:30:00Z', - tags: ['白酒复盘', '周复盘'], - stocks: ['600519.SH', '000858.SZ'] - } -]; - -// ==================== 投资日历事件数据 ==================== - -// ==================== 未来事件数据(用于投资日历) ==================== - -export const mockFutureEvents = [ - { - id: 501, - data_id: 501, - title: '美联储FOMC会议', - calendar_time: '2025-10-20T14:00:00Z', - type: 'event', - star: 5, - former: { - data: [ - { - author: '美联储官网', - sentences: '本次会议将重点讨论通胀控制和利率调整策略,美联储将评估当前经济形势,包括就业市场、物价水平和金融稳定性等关键指标,以决定是否调整联邦基金利率目标区间', - query_part: '本次会议将重点讨论通胀控制和利率调整策略', - report_title: 'FOMC会议议程公告', - declare_date: '2025-10-15T00:00:00', - match_score: '好' - }, - { - author: '彭博社', - sentences: '市场普遍预期美联储将维持当前利率水平,根据对50位经济学家的调查,超过80%的受访者认为美联储将在本次会议上保持利率不变,等待更多经济数据以评估政策效果', - query_part: '市场普遍预期美联储将维持当前利率水平', - report_title: '美联储利率决议前瞻:经济学家调查报告', - declare_date: '2025-10-18T00:00:00', - match_score: '好' - }, - { - author: '路透社', - sentences: '鲍威尔的讲话将释放未来货币政策方向的重要信号,市场将密切关注其对经济前景的评估,特别是关于通胀回落速度、就业市场韧性以及未来降息时点的表述', - query_part: '鲍威尔的讲话将释放未来货币政策方向的重要信号', - report_title: '鲍威尔讲话要点预测', - declare_date: '2025-10-19T00:00:00', - match_score: '好' - } - ] - }, - forecast: '预计维持利率不变,关注鲍威尔讲话基调', - fact: null, - related_stocks: [ - [ - '600036', - '招商银行', - { - data: [ - { - author: '中信证券', - sentences: '作为国内领先的商业银行,招商银行对利率变化敏感度高,美联储货币政策调整将通过汇率、资本流动等渠道影响国内货币政策,进而影响银行净息差和资产质量', - query_part: '美联储政策通过汇率和资本流动影响国内银行业', - report_title: '美联储政策对中国银行业影响分析', - declare_date: '2025-10-18T00:00:00', - match_score: '好' - }, - { - author: '中信证券', - sentences: '作为国内领先的商业银行,招商银行对利率变化敏感度高,美联储货币政策调整将通过汇率、资本流动等渠道影响国内货币政策,进而影响银行净息差和资产质量', - query_part: '美联储政策通过汇率和资本流动影响国内银行业', - report_title: '美联储政策对中国银行业影响分析', - declare_date: '2025-10-18T00:00:00', - match_score: '好' - } - ] - }, - 0.85 - ], - [ - '601398', - '工商银行', - { - data: [ - { - author: '招商证券', - sentences: '工商银行作为国有大行,其经营业绩与宏观经济和货币政策高度相关,美联储利率决策将影响全球流动性和人民币汇率,对大型商业银行的跨境业务和外汇敞口产生直接影响', - query_part: '美联储决策影响全球流动性和大行跨境业务', - report_title: '货币政策对银行业影响专题研究', - declare_date: '2025-10-17T00:00:00', - match_score: '好' - } - ] - }, - 0.80 - ] - ], - concepts: ['货币政策', '利率', '美联储'], - is_following: false - }, - { - id: 502, - data_id: 502, - title: '央行货币政策委员会例会', - calendar_time: '2025-10-20T09:00:00Z', - type: 'event', - star: 4, - former: '本次例会将总结前期货币政策执行情况,研究部署下一阶段工作。重点关注经济增长、通胀水平和金融稳定等方面的形势变化。\n\n(AI合成)', - forecast: '可能释放适度宽松信号', - fact: null, - related_stocks: [], - concepts: ['货币政策', '央行', '宏观经济'], - is_following: true - }, - { - id: 503, - data_id: 503, - title: '宁德时代业绩快报', - calendar_time: '2025-10-20T16:00:00Z', - type: 'data', - star: 5, - former: { - data: [ - { - author: 'SNE Research', - sentences: '公司Q3动力电池装机量持续保持全球第一,市场份额达到37.8%,较去年同期提升2.3个百分点,在全球动力电池市场继续保持领先地位,主要得益于国内新能源汽车市场的强劲增长以及海外客户订单的持续放量', - query_part: '公司Q3动力电池装机量持续保持全球第一', - report_title: '全球动力电池市场装机量统计报告', - declare_date: '2025-10-10T00:00:00', - match_score: '好' - }, - { - author: '宁德时代', - sentences: '储能业务订单饱满,预计全年营收同比增长超过60%,公司储能产品已应用于全球多个大型储能项目,在用户侧储能、电网侧储能等领域均实现突破,随着全球能源转型加速,储能市场需求持续旺盛', - query_part: '储能业务订单饱满,预计全年营收同比增长超过60%', - report_title: '宁德时代2024年业绩预告', - declare_date: '2025-09-30T00:00:00', - match_score: '好' - } - ] - }, - forecast: '预计营收和净利润双增长', - fact: null, - related_stocks: [ - [ - '300750', - '宁德时代', - { - data: [ - { - author: '宁德时代公告', - sentences: '公司Q3动力电池装机量持续保持全球第一,市场份额达到37.8%,较去年同期提升2.3个百分点,在全球动力电池市场继续保持领先地位,主要得益于国内新能源汽车市场的强劲增长以及海外客户订单的持续放量', - query_part: '动力电池装机量全球第一,市场份额37.8%', - report_title: '宁德时代2024年Q3业绩快报', - declare_date: '2025-10-15T00:00:00', - match_score: '优' - }, - { - author: '国泰君安证券', - sentences: '储能业务订单饱满,预计全年营收同比增长超过60%,公司储能产品已应用于全球多个大型储能项目,在用户侧储能、电网侧储能等领域均实现突破', - query_part: '储能业务营收同比增长超60%', - report_title: '宁德时代储能业务深度报告', - declare_date: '2025-10-12T00:00:00', - match_score: '优' - } - ] - }, - 0.95 - ], - [ - '002466', - '天齐锂业', - { - data: [ - { - author: '天风证券', - sentences: '天齐锂业作为宁德时代的核心供应商,将直接受益于下游动力电池需求的增长,公司锂资源储量丰富,随着宁德时代产能扩张,锂盐需求持续旺盛,公司业绩增长确定性强', - query_part: '核心锂供应商直接受益于下游需求增长', - report_title: '天齐锂业:受益动力电池产业链景气', - declare_date: '2025-10-14T00:00:00', - match_score: '好' - }, - { - author: '天风证券', - sentences: '天齐锂业作为宁德时代的核心供应商,将直接受益于下游动力电池需求的增长,公司锂资源储量丰富,随着宁德时代产能扩张,锂盐需求持续旺盛,公司业绩增长确定性强', - query_part: '核心锂供应商直接受益于下游需求增长', - report_title: '天齐锂业:受益动力电池产业链景气', - declare_date: '2025-10-14T00:00:00', - match_score: '好' - }, - { - author: '天风证券', - sentences: '天齐锂业作为宁德时代的核心供应商,将直接受益于下游动力电池需求的增长,公司锂资源储量丰富,随着宁德时代产能扩张,锂盐需求持续旺盛,公司业绩增长确定性强', - query_part: '核心锂供应商直接受益于下游需求增长', - report_title: '天齐锂业:受益动力电池产业链景气', - declare_date: '2025-10-14T00:00:00', - match_score: '好' - } - ] - }, - 0.82 - ] - ], - concepts: ['新能源', '动力电池', '储能'], - is_following: false - }, - // ==================== 2026年1月事件数据 ==================== - { - id: 601, - data_id: 601, - title: '特斯拉Q4财报发布', - calendar_time: '2026-01-15T21:00:00Z', - type: 'data', - star: 5, - former: { - data: [ - { - author: '特斯拉投资者关系', - sentences: '特斯拉将发布2025年第四季度及全年财务报告,市场关注其全球交付量、毛利率表现以及2026年产能扩张计划', - query_part: '特斯拉将发布2025年第四季度财报', - report_title: 'Q4 2025 Earnings Release', - declare_date: '2026-01-10T00:00:00', - match_score: '好' - } - ] - }, - forecast: '预计营收超过260亿美元,全年交付量超200万辆', - fact: null, - related_stocks: [ - ['002594', '比亚迪', { data: [{ author: '中信证券', sentences: '作为特斯拉主要竞争对手,比亚迪股价与特斯拉财报关联度高', query_part: '比亚迪与特斯拉竞争格局', report_title: '新能源汽车竞争分析', declare_date: '2026-01-12T00:00:00', match_score: '好' }] }, 0.85], - ['300750', '宁德时代', { data: [{ author: '招商证券', sentences: '作为特斯拉动力电池供应商,宁德时代业绩与特斯拉销量高度相关', query_part: '宁德时代与特斯拉供应链关系', report_title: '动力电池产业链研究', declare_date: '2026-01-11T00:00:00', match_score: '好' }] }, 0.80] - ], - concepts: ['新能源汽车', '特斯拉', '电动车'], - is_following: false - }, - { - id: 602, - data_id: 602, - title: '央行MLF操作', - calendar_time: '2026-01-15T09:30:00Z', - type: 'event', - star: 4, - former: '央行将开展中期借贷便利(MLF)操作,市场关注操作规模和利率变动。本次MLF到期规模约5000亿元,预计央行将等量或超量续作以维护流动性合理充裕。\n\n(AI合成)', - forecast: '预计MLF利率维持2.5%不变', - fact: null, - related_stocks: [ - ['601398', '工商银行', { data: [{ author: '国泰君安', sentences: 'MLF利率影响银行负债成本和净息差表现', query_part: 'MLF对银行净息差影响', report_title: '货币政策对银行业影响', declare_date: '2026-01-14T00:00:00', match_score: '好' }] }, 0.75] - ], - concepts: ['货币政策', 'MLF', '央行'], - is_following: true - }, - { - id: 603, - data_id: 603, - title: '英伟达CES 2026新品发布', - calendar_time: '2026-01-14T10:00:00Z', - type: 'event', - star: 5, - former: { - data: [ - { - author: '英伟达官方', - sentences: '英伟达CEO黄仁勋将在CES 2026发表主题演讲,预计发布新一代RTX 50系列显卡和AI芯片产品线更新', - query_part: '英伟达CES 2026发布会', - report_title: 'CES 2026 Keynote Preview', - declare_date: '2026-01-08T00:00:00', - match_score: '好' - } - ] - }, - forecast: '新一代AI芯片性能预计提升2-3倍', - fact: null, - related_stocks: [ - ['603986', '兆易创新', { data: [{ author: '华泰证券', sentences: '英伟达新品发布带动国产芯片概念关注度提升', query_part: '国产芯片替代机遇', report_title: '半导体产业链研究', declare_date: '2026-01-10T00:00:00', match_score: '好' }] }, 0.78], - ['002049', '紫光国微', { data: [{ author: '中金公司', sentences: '作为国产芯片龙头,紫光国微受益于AI芯片需求增长', query_part: '国产芯片龙头分析', report_title: 'AI芯片产业链报告', declare_date: '2026-01-09T00:00:00', match_score: '好' }] }, 0.75] - ], - concepts: ['人工智能', 'AI芯片', '半导体'], - is_following: false - }, - { - id: 604, - data_id: 604, - title: '12月CPI/PPI数据公布', - calendar_time: '2026-01-13T09:30:00Z', - type: 'data', - star: 4, - former: '国家统计局将公布2025年12月居民消费价格指数(CPI)和工业生产者出厂价格指数(PPI)数据,市场预期CPI同比上涨0.3%,PPI同比下降2.5%。\n\n【详细背景分析】\n\n一、CPI数据展望\n\n1. 食品价格方面:受季节性因素影响,12月食品价格预计环比上涨。猪肉价格在供给偏紧的情况下维持高位运行,鲜菜价格受寒潮天气影响有所上涨,鲜果价格保持稳定。预计食品价格环比上涨0.8%左右。\n\n2. 非食品价格方面:能源价格受国际油价波动影响,12月成品油价格有所调整。服务价格保持平稳,教育文化娱乐、医疗保健等服务价格环比持平。预计非食品价格环比微降0.1%。\n\n3. 核心CPI方面:剔除食品和能源价格的核心CPI预计同比上涨0.6%,反映出内需恢复仍在进行中,但力度有待加强。\n\n二、PPI数据展望\n\n1. 生产资料价格:受全球大宗商品价格波动影响,12月生产资料价格预计环比下降。其中,采掘工业、原材料工业价格降幅收窄,加工工业价格基本持平。\n\n2. 生活资料价格:食品类价格小幅上涨,衣着类价格保持稳定,一般日用品类价格持平,耐用消费品类价格略有下降。\n\n3. 主要行业分析:\n - 石油和天然气开采业:受国际油价影响,价格环比下降约2%\n - 黑色金属冶炼:钢材价格震荡运行,环比微降0.5%\n - 有色金属冶炼:铜、铝等价格受供需影响有所波动\n - 化学原料制造:基础化工品价格整体平稳\n\n三、对市场的影响分析\n\n1. 货币政策方面:当前通胀压力不大,为货币政策提供了较大的操作空间。预计央行将继续保持流动性合理充裕,适时运用降准、降息等工具支持实体经济发展。\n\n2. 股市方面:\n - 消费板块:CPI温和上涨利好食品饮料、农业等消费板块\n - 周期板块:PPI降幅收窄显示工业品需求有所改善,利好有色、钢铁等周期板块\n - 金融板块:货币政策宽松预期利好银行、保险等金融板块\n\n3. 债市方面:通胀压力可控,经济复苏温和,利率债配置价值凸显。\n\n四、历史数据回顾\n\n2025年全年CPI走势:\n- 1月:0.8%(春节因素)\n- 2月:0.1%\n- 3月:0.2%\n- 4月:0.3%\n- 5月:0.3%\n- 6月:0.2%\n- 7月:0.1%\n- 8月:0.2%\n- 9月:0.3%\n- 10月:0.2%\n- 11月:0.3%\n\n2025年全年PPI走势:\n- 1月:-2.8%\n- 2月:-2.6%\n- 3月:-2.5%\n- 4月:-2.7%\n- 5月:-2.9%\n- 6月:-3.0%\n- 7月:-2.8%\n- 8月:-2.7%\n- 9月:-2.6%\n- 10月:-2.5%\n- 11月:-2.4%\n\n五、投资策略建议\n\n1. 短期策略:关注数据公布后的市场反应,若数据好于预期,可适当增配周期板块;若数据不及预期,可关注防御性板块。\n\n2. 中期策略:在通胀温和、流动性充裕的环境下,建议均衡配置成长股和价值股,重点关注科技创新、消费升级、绿色转型等方向。\n\n3. 风险提示:需关注全球经济形势变化、地缘政治风险、国内政策调整等因素对市场的影响。\n\n(AI合成)', - forecast: 'CPI温和上涨,PPI降幅收窄', - fact: null, - related_stocks: [], - concepts: ['宏观经济', 'CPI', 'PPI'], - is_following: false - }, - { - id: 605, - data_id: 605, - title: '苹果Vision Pro 2发布会', - calendar_time: '2026-01-16T02:00:00Z', - type: 'event', - star: 5, - former: { - data: [ - { - author: '苹果公司', - sentences: '苹果将举办特别活动,预计发布第二代Vision Pro头显设备,新产品将在性能、重量和价格方面实现重大突破', - query_part: '苹果Vision Pro 2发布', - report_title: 'Apple Special Event', - declare_date: '2026-01-12T00:00:00', - match_score: '好' - } - ] - }, - forecast: '售价预计下调30%,重量减轻40%', - fact: null, - related_stocks: [ - ['002475', '立讯精密', { data: [{ author: '天风证券', sentences: '立讯精密是苹果Vision Pro核心代工厂,新品发布将带动订单增长', query_part: '立讯精密Vision Pro代工', report_title: '消费电子产业链研究', declare_date: '2026-01-13T00:00:00', match_score: '好' }] }, 0.88], - ['002241', '歌尔股份', { data: [{ author: '国盛证券', sentences: '歌尔股份在VR/AR光学模组领域具有领先地位', query_part: '歌尔股份VR光学布局', report_title: 'XR产业链深度报告', declare_date: '2026-01-12T00:00:00', match_score: '好' }] }, 0.82] - ], - concepts: ['苹果', 'VR/AR', '消费电子'], - is_following: true - } -]; - -export const mockCalendarEvents = [ - { - id: 408, - user_id: 1, - title: '2025中医药高质量发展大会将于12月5日至7日举办', - date: '2025-12-05', - event_date: '2025-12-05', - type: 'policy', - category: 'industry_event', - description: `基于提供的路演记录、新闻动态以及上市公司公告,以下是与"2025中医药高质量发展大会将于12月5日至7日举办"相关的信息整理: - -事件背景: -"2025中医药高质量发展大会"将于12月5日至7日在北京召开,由国家中医药管理局主办,旨在总结十四五期间中医药发展成果,部署下一阶段重点任务。大会主题为"守正创新、传承发展",将邀请国内外中医药领域专家学者、企业代表共商中医药现代化发展路径。 - -政策支持: -1. 国务院办公厅印发《中医药振兴发展重大工程实施方案》,明确到2025年中医药服务体系更加完善 -2. 国家医保局持续推进中成药集采,优质中药企业有望受益于市场集中度提升 -3. 各地出台中医药产业发展支持政策,加大对中药创新药研发的资金支持 - -行业展望: -中医药行业正处于政策红利期,创新中药、配方颗粒、中药材种植等细分领域景气度较高。预计大会将释放更多利好政策信号,推动行业高质量发展。`, - importance: 5, - source: 'future', - stocks: ['002424.SZ', '002873.SZ', '600518.SH', '002907.SZ', '600129.SH', '300519.SZ', '300878.SZ', '002275.SZ', '600222.SH'], - created_at: '2025-12-01T10:00:00Z' - }, - { - id: 401, - user_id: 1, - title: '贵州茅台年报披露', - date: '2025-12-20', - event_date: '2025-12-20', - type: 'earnings', - category: 'financial_report', - description: '关注营收和净利润增速,以及渠道库存情况', - stock_code: '600519.SH', - stock_name: '贵州茅台', - importance: 5, - source: 'future', - stocks: ['600519'], - created_at: '2025-01-10T10:00:00Z' - }, - { - id: 402, - user_id: 1, - title: '宁德时代业绩快报', - date: '2025-11-28', - event_date: '2025-11-28', - type: 'earnings', - category: 'financial_report', - description: '重点关注出货量和单位盈利情况', - stock_code: '300750.SZ', - stock_name: '宁德时代', - importance: 5, - source: 'future', - stocks: ['300750'], - created_at: '2025-01-12T14:00:00Z' - }, - { - id: 403, - user_id: 1, - title: '央行货币政策委员会例会', - date: '2025-10-25', - event_date: '2025-10-25', - type: 'policy', - category: 'macro_policy', - description: '关注货币政策基调和利率调整信号', - importance: 4, - source: 'future', - stocks: [], - created_at: '2025-01-08T09:00:00Z' - }, - { - id: 404, - user_id: 1, - title: '春节假期后首个交易日', - date: '2025-11-15', - event_date: '2025-11-15', - type: 'reminder', - category: 'trading', - description: '节后第一天,关注资金面和市场情绪', - importance: 3, - source: 'future', - stocks: [], - created_at: '2025-01-05T16:00:00Z' - }, - { - id: 405, - user_id: 1, - title: '定投日 - 沪深300ETF', - date: '2025-10-20', - event_date: '2025-10-20', - type: 'reminder', - category: 'investment', - description: '每月20日定投3000元', - importance: 2, - source: 'user', - stocks: [], - is_recurring: true, - recurrence_rule: 'monthly', - created_at: '2024-12-15T10:00:00Z' - }, - { - id: 406, - user_id: 1, - title: '美联储FOMC会议', - date: '2025-11-07', - event_date: '2025-11-07', - type: 'policy', - category: 'macro_policy', - description: '关注美联储利率决议和鲍威尔讲话', - importance: 5, - source: 'future', - stocks: [], - created_at: '2025-01-07T11:00:00Z' - }, - { - id: 407, - user_id: 1, - title: '持仓股票复盘日', - date: '2025-10-26', - event_date: '2025-10-26', - type: 'reminder', - category: 'review', - description: '每周六进行持仓复盘和下周计划', - importance: 3, - source: 'user', - stocks: [], - is_recurring: true, - recurrence_rule: 'weekly', - created_at: '2025-01-01T10:00:00Z' - }, - - // ==================== 今日事件(2025-12-23) ==================== - { - id: 409, - user_id: 1, - title: '比亚迪全球发布会', - date: '2025-12-23', - event_date: '2025-12-23', - type: 'earnings', - category: 'company_event', - description: `比亚迪将于今日14:00召开全球发布会,预计发布新一代刀片电池技术和2026年新车规划。 - -重点关注: -1. 刀片电池2.0技术参数:能量密度提升预期 -2. 2026年新车型规划:高端品牌仰望系列 -3. 海外市场扩张计划:欧洲建厂进度 -4. 年度交付量预告 - -投资建议: -- 关注发布会后股价走势 -- 若技术突破超预期,可考虑加仓 -- 设置止损位:当前价-5%`, - stock_code: '002594.SZ', - stock_name: '比亚迪', - importance: 5, - source: 'future', - stocks: ['002594.SZ', '300750.SZ', '601238.SH'], - created_at: '2025-12-20T10:00:00Z' - }, - { - id: 410, - user_id: 1, - title: '12月LPR报价公布', - date: '2025-12-23', - event_date: '2025-12-23', - type: 'policy', - category: 'macro_policy', - description: `中国人民银行将于今日9:30公布12月贷款市场报价利率(LPR)。 - -市场预期: -- 1年期LPR:3.10%(维持不变) -- 5年期以上LPR:3.60%(维持不变) - -影响板块: -1. 银行板块:利差压力关注 -2. 房地产:按揭成本影响 -3. 基建:融资成本变化 - -投资策略: -- 若降息,利好成长股,可加仓科技板块 -- 若维持,银行股防守价值凸显`, - importance: 5, - source: 'future', - stocks: ['601398.SH', '600036.SH', '000001.SZ'], - created_at: '2025-12-20T08:00:00Z' - }, - { - id: 411, - user_id: 1, - title: 'A股年末交易策略会议', - date: '2025-12-23', - event_date: '2025-12-23', - type: 'reminder', - category: 'personal', - description: `个人备忘:年末交易策略规划 - -待办事项: -1. 回顾2025年度投资收益 -2. 分析持仓股票基本面变化 -3. 制定2026年Q1布局计划 -4. 检查止盈止损纪律执行情况 - -重点关注: -- 白酒板块持仓是否需要调整 -- 新能源板块估值是否合理 -- 是否需要增加防守性配置`, - importance: 3, - source: 'user', - stocks: [], - created_at: '2025-12-22T20:00:00Z' - } -]; - -// ==================== 订阅信息数据 ==================== - -export const mockSubscriptionCurrent = { - type: 'pro', - status: 'active', - is_active: true, - days_left: 90, - end_date: '2025-04-15T23:59:59Z', - plan_name: 'Pro版', - features: [ - '无限事件查看', - '实时行情推送', - '专业分析报告', - '优先客服支持', - '关联股票分析', - '历史事件对比' - ], - price: 0.01, - currency: 'CNY', - billing_cycle: 'monthly', - auto_renew: true, - next_billing_date: '2025-02-15T00:00:00Z' -}; - -// ==================== 辅助函数 ==================== - -// 根据用户ID获取自选股 -export function getWatchlistByUserId(userId) { - return mockWatchlist.filter(item => item.user_id === userId); -} - -// 根据用户ID获取关注事件 -export function getFollowingEventsByUserId(userId) { - return mockFollowingEvents; -} - -// 根据用户ID获取评论 -export function getCommentsByUserId(userId) { - return mockEventComments.filter(comment => comment.user_id === userId); -} - -// 根据用户ID获取投资计划 -export function getInvestmentPlansByUserId(userId) { - return mockInvestmentPlans.filter(plan => plan.user_id === userId); -} - -// 根据用户ID获取日历事件 -export function getCalendarEventsByUserId(userId) { - return mockCalendarEvents.filter(event => event.user_id === userId); -} - -// 获取指定日期范围的日历事件 -export function getCalendarEventsByDateRange(userId, startDate, endDate) { - const start = new Date(startDate); - const end = new Date(endDate); - - return mockCalendarEvents.filter(event => { - if (event.user_id !== userId) return false; - const eventDate = new Date(event.date); - return eventDate >= start && eventDate <= end; - }); -} - -// ==================== 未来事件(投资日历)辅助函数 ==================== - -/** - * 获取指定日期的未来事件列表 - * @param {string} dateStr - 日期字符串 'YYYY-MM-DD' - * @param {string} type - 事件类型 'event' | 'data' | 'all' - * @returns {Array} 事件列表 - */ -export function getMockFutureEvents(dateStr, type = 'all') { - const targetDate = new Date(dateStr); - - return mockFutureEvents.filter(event => { - const eventDate = new Date(event.calendar_time); - const isSameDate = - eventDate.getFullYear() === targetDate.getFullYear() && - eventDate.getMonth() === targetDate.getMonth() && - eventDate.getDate() === targetDate.getDate(); - - if (!isSameDate) return false; - - if (type === 'all') return true; - return event.type === type; - }); -} - -/** - * 获取指定月份的事件统计 - * @param {number} year - 年份 - * @param {number} month - 月份 (1-12) - * @returns {Array} 事件统计数组 - */ -export function getMockEventCountsForMonth(year, month) { - const counts = {}; - - mockFutureEvents.forEach(event => { - const eventDate = new Date(event.calendar_time); - if (eventDate.getFullYear() === year && eventDate.getMonth() + 1 === month) { - const dateStr = eventDate.toISOString().split('T')[0]; - counts[dateStr] = (counts[dateStr] || 0) + 1; - } - }); - - return Object.entries(counts).map(([date, count]) => ({ - date, - count, - className: count >= 3 ? 'high-activity' : count >= 2 ? 'medium-activity' : 'low-activity' - })); -} diff --git a/src/mocks/data/company.js b/src/mocks/data/company.js deleted file mode 100644 index 9289b790..00000000 --- a/src/mocks/data/company.js +++ /dev/null @@ -1,1638 +0,0 @@ -// src/mocks/data/company.js -// 公司相关的 Mock 数据 -// 字段名与后端 API 返回格式保持一致 - -// 平安银行 (000001) 的完整数据 -export const PINGAN_BANK_DATA = { - stockCode: '000001', - stockName: '平安银行', - - // 基本信息 - 字段名与后端 API 保持一致 - basicInfo: { - SECCODE: '000001', - SECNAME: '平安银行', - ORGNAME: '平安银行股份有限公司', - english_name: 'Ping An Bank Co., Ltd.', - reg_capital: 1940642.3, // 万元 - legal_representative: '谢永林', - chairman: '谢永林', - general_manager: '冀光恒', - secretary: '周强', - reg_address: '深圳市罗湖区深南东路5047号', - office_address: '深圳市福田区益田路5023号平安金融中心', - zipcode: '518001', - tel: '0755-82080387', - fax: '0755-82080386', - email: 'ir@pingan.com.cn', - website: 'http://bank.pingan.com', - sw_industry_l1: '金融', - sw_industry_l2: '银行', - sw_industry_l3: '股份制银行', - establish_date: '1987-12-22', - list_date: '1991-04-03', - province: '广东省', - city: '深圳市', - credit_code: '914403001000010008', - company_size: '大型企业(员工超3万人)', - accounting_firm: '普华永道中天会计师事务所(特殊普通合伙)', - law_firm: '北京市金杜律师事务所', - company_intro: '平安银行股份有限公司是中国平安保险(集团)股份有限公司控股的一家跨区域经营的股份制商业银行,为中国大陆12家全国性股份制商业银行之一。总部位于深圳,在全国设有超过90家分行、近1000家营业网点。平安银行致力于成为中国最卓越、全球领先的智能化零售银行,以科技引领业务发展,持续推进零售转型战略。', - main_business: '吸收公众存款、发放贷款、办理结算、票据贴现、资金拆借、银行卡业务、代理收付款项、外汇业务等商业银行业务', - business_scope: '吸收公众存款;发放短期、中期和长期贷款;办理国内外结算;办理票据承兑与贴现;发行金融债券;代理发行、代理兑付、承销政府债券;买卖政府债券、金融债券;从事同业拆借;买卖、代理买卖外汇;从事银行卡业务;提供信用证服务及担保;代理收付款项及代理保险业务;提供保管箱服务;经有关监管机构批准的其他业务。', - employees: 42099, - }, - - // 市场概览数据 - StockSummaryCard 使用 - marketSummary: { - stock_code: '000001', - stock_name: '平安银行', - latest_trade: { - close: 11.28, - change_percent: 2.35, - volume: 58623400, - amount: 659800000, - turnover_rate: 0.30, - pe_ratio: 4.92 - }, - latest_funding: { - financing_balance: 5823000000, - securities_balance: 125600000 - }, - latest_pledge: { - pledge_ratio: 8.25 - } - }, - - // 当日分钟K线数据 - MinuteKLineChart 使用 - minuteData: { - code: '000001', - name: '平安银行', - trade_date: '2024-12-12', - type: '1min', - data: [ - // 上午交易时段 9:30 - 11:30 - { time: '09:30', open: 11.02, close: 11.05, high: 11.06, low: 11.01, volume: 1856000, amount: 20458000 }, - { time: '09:31', open: 11.05, close: 11.08, high: 11.09, low: 11.04, volume: 1423000, amount: 15782000 }, - { time: '09:32', open: 11.08, close: 11.06, high: 11.10, low: 11.05, volume: 1125000, amount: 12468000 }, - { time: '09:33', open: 11.06, close: 11.10, high: 11.11, low: 11.05, volume: 1678000, amount: 18623000 }, - { time: '09:34', open: 11.10, close: 11.12, high: 11.14, low: 11.09, volume: 2134000, amount: 23725000 }, - { time: '09:35', open: 11.12, close: 11.15, high: 11.16, low: 11.11, volume: 1892000, amount: 21082000 }, - { time: '09:40', open: 11.15, close: 11.18, high: 11.20, low: 11.14, volume: 1567000, amount: 17523000 }, - { time: '09:45', open: 11.18, close: 11.16, high: 11.19, low: 11.15, volume: 1234000, amount: 13782000 }, - { time: '09:50', open: 11.16, close: 11.20, high: 11.21, low: 11.15, volume: 1456000, amount: 16298000 }, - { time: '09:55', open: 11.20, close: 11.22, high: 11.24, low: 11.19, volume: 1789000, amount: 20068000 }, - { time: '10:00', open: 11.22, close: 11.25, high: 11.26, low: 11.21, volume: 2012000, amount: 22635000 }, - { time: '10:10', open: 11.25, close: 11.23, high: 11.26, low: 11.22, volume: 1345000, amount: 15123000 }, - { time: '10:20', open: 11.23, close: 11.26, high: 11.28, low: 11.22, volume: 1678000, amount: 18912000 }, - { time: '10:30', open: 11.26, close: 11.24, high: 11.27, low: 11.23, volume: 1123000, amount: 12645000 }, - { time: '10:40', open: 11.24, close: 11.27, high: 11.28, low: 11.23, volume: 1456000, amount: 16412000 }, - { time: '10:50', open: 11.27, close: 11.25, high: 11.28, low: 11.24, volume: 1234000, amount: 13902000 }, - { time: '11:00', open: 11.25, close: 11.28, high: 11.30, low: 11.24, volume: 1567000, amount: 17689000 }, - { time: '11:10', open: 11.28, close: 11.26, high: 11.29, low: 11.25, volume: 1089000, amount: 12278000 }, - { time: '11:20', open: 11.26, close: 11.28, high: 11.29, low: 11.25, volume: 1234000, amount: 13912000 }, - { time: '11:30', open: 11.28, close: 11.27, high: 11.29, low: 11.26, volume: 987000, amount: 11134000 }, - // 下午交易时段 13:00 - 15:00 - { time: '13:00', open: 11.27, close: 11.30, high: 11.31, low: 11.26, volume: 1456000, amount: 16456000 }, - { time: '13:10', open: 11.30, close: 11.28, high: 11.31, low: 11.27, volume: 1123000, amount: 12689000 }, - { time: '13:20', open: 11.28, close: 11.32, high: 11.33, low: 11.27, volume: 1789000, amount: 20245000 }, - { time: '13:30', open: 11.32, close: 11.30, high: 11.33, low: 11.29, volume: 1345000, amount: 15212000 }, - { time: '13:40', open: 11.30, close: 11.33, high: 11.35, low: 11.29, volume: 1678000, amount: 18978000 }, - { time: '13:50', open: 11.33, close: 11.31, high: 11.34, low: 11.30, volume: 1234000, amount: 13956000 }, - { time: '14:00', open: 11.31, close: 11.34, high: 11.36, low: 11.30, volume: 1567000, amount: 17789000 }, - { time: '14:10', open: 11.34, close: 11.32, high: 11.35, low: 11.31, volume: 1123000, amount: 12712000 }, - { time: '14:20', open: 11.32, close: 11.30, high: 11.33, low: 11.29, volume: 1456000, amount: 16478000 }, - { time: '14:30', open: 11.30, close: 11.28, high: 11.31, low: 11.27, volume: 1678000, amount: 18956000 }, - { time: '14:40', open: 11.28, close: 11.26, high: 11.29, low: 11.25, volume: 1345000, amount: 15167000 }, - { time: '14:50', open: 11.26, close: 11.28, high: 11.30, low: 11.25, volume: 1892000, amount: 21345000 }, - { time: '15:00', open: 11.28, close: 11.28, high: 11.29, low: 11.27, volume: 2345000, amount: 26478000 } - ] - }, - - // 实际控制人信息(数组格式) - actualControl: [ - { - actual_controller_name: '中国平安保险(集团)股份有限公司', - controller_name: '中国平安保险(集团)股份有限公司', - control_type: '企业法人', - controller_type: '企业', - holding_ratio: 52.38, - holding_shares: 10168542300, - end_date: '2024-09-30', - control_chain: '中国平安保险(集团)股份有限公司 -> 平安银行股份有限公司', - is_listed: true, - remark: '中国平安通过直接和间接方式控股平安银行', - } - ], - - // 股权集中度(数组格式,按统计项分组) - concentration: [ - { stat_item: '前1大股东', holding_ratio: 52.38, ratio_change: 0.00, end_date: '2024-09-30' }, - { stat_item: '前3大股东', holding_ratio: 58.42, ratio_change: 0.15, end_date: '2024-09-30' }, - { stat_item: '前5大股东', holding_ratio: 60.15, ratio_change: 0.22, end_date: '2024-09-30' }, - { stat_item: '前10大股东', holding_ratio: 63.28, ratio_change: 0.35, end_date: '2024-09-30' }, - { stat_item: '前1大股东', holding_ratio: 52.38, ratio_change: -0.12, end_date: '2024-06-30' }, - { stat_item: '前3大股东', holding_ratio: 58.27, ratio_change: -0.08, end_date: '2024-06-30' }, - { stat_item: '前5大股东', holding_ratio: 59.93, ratio_change: -0.15, end_date: '2024-06-30' }, - { stat_item: '前10大股东', holding_ratio: 62.93, ratio_change: -0.22, end_date: '2024-06-30' }, - ], - - // 高管信息(包含高管、董事、监事、其他) - management: [ - // === 高管 === - { - name: '谢永林', - position_name: '董事长', - position_category: '高管', - gender: '男', - birth_year: '1968', - education: '硕士', - nationality: '中国', - start_date: '2019-01-01', - status: 'active' - }, - { - name: '冀光恒', - position_name: '行长', - position_category: '高管', - gender: '男', - birth_year: '1972', - education: '博士', - nationality: '中国', - start_date: '2023-08-01', - status: 'active' - }, - { - name: '周强', - position_name: '副行长、董事会秘书', - position_category: '高管', - gender: '男', - birth_year: '1970', - education: '硕士', - nationality: '中国', - start_date: '2016-06-01', - status: 'active' - }, - { - name: '郭世邦', - position_name: '副行长、首席财务官', - position_category: '高管', - gender: '男', - birth_year: '1972', - education: '博士', - nationality: '中国', - start_date: '2018-03-01', - status: 'active' - }, - { - name: '项有志', - position_name: '副行长、首席信息官', - position_category: '高管', - gender: '男', - birth_year: '1975', - education: '硕士', - nationality: '中国', - start_date: '2019-09-01', - status: 'active' - }, - { - name: '张小璐', - position_name: '副行长、首席风险官', - position_category: '高管', - gender: '女', - birth_year: '1973', - education: '硕士', - nationality: '中国', - start_date: '2020-03-15', - status: 'active' - }, - // === 董事 === - { - name: '马明哲', - position_name: '非执行董事', - position_category: '董事', - gender: '男', - birth_year: '1955', - education: '博士', - nationality: '中国', - start_date: '2012-06-15', - status: 'active' - }, - { - name: '孙建一', - position_name: '非执行董事', - position_category: '董事', - gender: '男', - birth_year: '1960', - education: '硕士', - nationality: '中国', - start_date: '2016-08-20', - status: 'active' - }, - { - name: '陈心颖', - position_name: '非执行董事', - position_category: '董事', - gender: '女', - birth_year: '1977', - education: '硕士', - nationality: '新加坡', - start_date: '2018-06-01', - status: 'active' - }, - { - name: '黄宝新', - position_name: '独立非执行董事', - position_category: '董事', - gender: '男', - birth_year: '1962', - education: '博士', - nationality: '中国', - start_date: '2019-06-20', - status: 'active' - }, - { - name: '王志良', - position_name: '独立非执行董事', - position_category: '董事', - gender: '男', - birth_year: '1958', - education: '博士', - nationality: '美国', - start_date: '2020-06-18', - status: 'active' - }, - { - name: '李曙光', - position_name: '独立非执行董事', - position_category: '董事', - gender: '男', - birth_year: '1963', - education: '博士', - nationality: '中国', - start_date: '2021-06-25', - status: 'active' - }, - // === 监事 === - { - name: '王选庆', - position_name: '监事会主席', - position_category: '监事', - gender: '男', - birth_year: '1965', - education: '硕士', - nationality: '中国', - start_date: '2017-06-15', - status: 'active' - }, - { - name: '杨峻', - position_name: '职工监事', - position_category: '监事', - gender: '男', - birth_year: '1970', - education: '本科', - nationality: '中国', - start_date: '2019-06-20', - status: 'active' - }, - { - name: '刘春华', - position_name: '外部监事', - position_category: '监事', - gender: '女', - birth_year: '1968', - education: '硕士', - nationality: '中国', - start_date: '2020-06-18', - status: 'active' - }, - { - name: '张伟民', - position_name: '外部监事', - position_category: '监事', - gender: '男', - birth_year: '1966', - education: '博士', - nationality: '中国', - start_date: '2021-06-25', - status: 'active' - }, - // === 其他 === - { - name: '陈敏', - position_name: '合规总监', - position_category: '其他', - gender: '女', - birth_year: '1975', - education: '硕士', - nationality: '中国', - start_date: '2018-09-01', - status: 'active' - }, - { - name: '李明', - position_name: '审计部总经理', - position_category: '其他', - gender: '男', - birth_year: '1978', - education: '硕士', - nationality: '中国', - start_date: '2019-03-15', - status: 'active' - }, - { - name: '王建国', - position_name: '法务部总经理', - position_category: '其他', - gender: '男', - birth_year: '1972', - education: '博士', - nationality: '中国', - start_date: '2017-06-01', - status: 'active' - } - ], - - // 十大流通股东(字段名与组件期望格式匹配) - topCirculationShareholders: [ - { shareholder_rank: 1, shareholder_name: '中国平安保险(集团)股份有限公司', holding_shares: 10168542300, circulation_share_ratio: 52.38, shareholder_type: '法人', end_date: '2024-09-30' }, - { shareholder_rank: 2, shareholder_name: '香港中央结算有限公司(陆股通)', holding_shares: 542138600, circulation_share_ratio: 2.79, shareholder_type: 'QFII', end_date: '2024-09-30' }, - { shareholder_rank: 3, shareholder_name: '中国平安人寿保险股份有限公司-传统-普通保险产品', holding_shares: 382456100, circulation_share_ratio: 1.97, shareholder_type: '保险', end_date: '2024-09-30' }, - { shareholder_rank: 4, shareholder_name: '中国证券金融股份有限公司', holding_shares: 298654200, circulation_share_ratio: 1.54, shareholder_type: '券商', end_date: '2024-09-30' }, - { shareholder_rank: 5, shareholder_name: '中央汇金资产管理有限责任公司', holding_shares: 267842100, circulation_share_ratio: 1.38, shareholder_type: '法人', end_date: '2024-09-30' }, - { shareholder_rank: 6, shareholder_name: '全国社保基金一零三组合', holding_shares: 156234500, circulation_share_ratio: 0.80, shareholder_type: '社保', end_date: '2024-09-30' }, - { shareholder_rank: 7, shareholder_name: '华夏上证50交易型开放式指数证券投资基金', holding_shares: 142356700, circulation_share_ratio: 0.73, shareholder_type: '资产管理公司资产管理计划', end_date: '2024-09-30' }, - { shareholder_rank: 8, shareholder_name: '中国人寿保险股份有限公司-分红-个人分红-005L-FH002深', holding_shares: 128945600, circulation_share_ratio: 0.66, shareholder_type: '保险资产管理产品', end_date: '2024-09-30' }, - { shareholder_rank: 9, shareholder_name: '易方达沪深300交易型开放式指数发起式证券投资基金', holding_shares: 98765400, circulation_share_ratio: 0.51, shareholder_type: '基金', end_date: '2024-09-30' }, - { shareholder_rank: 10, shareholder_name: '嘉实沪深300交易型开放式指数证券投资基金', holding_shares: 87654300, circulation_share_ratio: 0.45, shareholder_type: '基金', end_date: '2024-09-30' } - ], - - // 十大股东(字段名与组件期望格式匹配) - topShareholders: [ - { shareholder_rank: 1, shareholder_name: '中国平安保险(集团)股份有限公司', holding_shares: 10168542300, total_share_ratio: 52.38, shareholder_type: '法人', share_nature: '流通A股', end_date: '2024-09-30' }, - { shareholder_rank: 2, shareholder_name: '香港中央结算有限公司(陆股通)', holding_shares: 542138600, total_share_ratio: 2.79, shareholder_type: 'QFII', share_nature: '流通A股', end_date: '2024-09-30' }, - { shareholder_rank: 3, shareholder_name: '中国平安人寿保险股份有限公司-传统-普通保险产品', holding_shares: 382456100, total_share_ratio: 1.97, shareholder_type: '保险', share_nature: '流通A股', end_date: '2024-09-30' }, - { shareholder_rank: 4, shareholder_name: '中国证券金融股份有限公司', holding_shares: 298654200, total_share_ratio: 1.54, shareholder_type: '券商', share_nature: '流通A股', end_date: '2024-09-30' }, - { shareholder_rank: 5, shareholder_name: '中央汇金资产管理有限责任公司', holding_shares: 267842100, total_share_ratio: 1.38, shareholder_type: '法人', share_nature: '流通A股', end_date: '2024-09-30' }, - { shareholder_rank: 6, shareholder_name: '全国社保基金一零三组合', holding_shares: 156234500, total_share_ratio: 0.80, shareholder_type: '社保', share_nature: '流通A股', end_date: '2024-09-30' }, - { shareholder_rank: 7, shareholder_name: '华夏上证50交易型开放式指数证券投资基金', holding_shares: 142356700, total_share_ratio: 0.73, shareholder_type: '资产管理公司资产管理计划', share_nature: '限售流通A股', end_date: '2024-09-30' }, - { shareholder_rank: 8, shareholder_name: '中国人寿保险股份有限公司-分红-个人分红-005L-FH002深', holding_shares: 128945600, total_share_ratio: 0.66, shareholder_type: '保险资产管理产品', share_nature: '限售流通A股、质押股份', end_date: '2024-09-30' }, - { shareholder_rank: 9, shareholder_name: '易方达沪深300交易型开放式指数发起式证券投资基金', holding_shares: 98765400, total_share_ratio: 0.51, shareholder_type: '基金', share_nature: '流通A股', end_date: '2024-09-30' }, - { shareholder_rank: 10, shareholder_name: '嘉实沪深300交易型开放式指数证券投资基金', holding_shares: 87654300, total_share_ratio: 0.45, shareholder_type: '基金', share_nature: '流通A股', end_date: '2024-09-30' } - ], - - // 分支机构(字段与 BranchesPanel 组件匹配) - branches: [ - { branch_name: '平安银行股份有限公司北京分行', business_status: '存续', register_capital: '20亿元', legal_person: '张伟', register_date: '2007-03-15', related_company_count: 156 }, - { branch_name: '平安银行股份有限公司上海分行', business_status: '存续', register_capital: '25亿元', legal_person: '李明', register_date: '2007-05-20', related_company_count: 203 }, - { branch_name: '平安银行股份有限公司广州分行', business_status: '存续', register_capital: '18亿元', legal_person: '王芳', register_date: '2007-06-10', related_company_count: 142 }, - { branch_name: '平安银行股份有限公司深圳分行', business_status: '存续', register_capital: '30亿元', legal_person: '陈强', register_date: '1995-01-01', related_company_count: 287 }, - { branch_name: '平安银行股份有限公司杭州分行', business_status: '存续', register_capital: '15亿元', legal_person: '刘洋', register_date: '2008-09-12', related_company_count: 98 }, - { branch_name: '平安银行股份有限公司成都分行', business_status: '存续', register_capital: '12亿元', legal_person: '赵静', register_date: '2009-04-25', related_company_count: 76 }, - { branch_name: '平安银行股份有限公司南京分行', business_status: '存续', register_capital: '14亿元', legal_person: '周涛', register_date: '2010-06-30', related_company_count: 89 }, - { branch_name: '平安银行股份有限公司武汉分行', business_status: '存续', register_capital: '10亿元', legal_person: '吴磊', register_date: '2011-08-15', related_company_count: 65 }, - { branch_name: '平安银行股份有限公司西安分行', business_status: '存续', register_capital: '8亿元', legal_person: '郑华', register_date: '2012-10-20', related_company_count: 52 }, - { branch_name: '平安银行股份有限公司天津分行', business_status: '存续', register_capital: '10亿元', legal_person: '孙丽', register_date: '2013-03-18', related_company_count: 71 }, - { branch_name: '平安银行股份有限公司重庆分行', business_status: '存续', register_capital: '9亿元', legal_person: '钱峰', register_date: '2014-05-08', related_company_count: 58 }, - { branch_name: '平安银行股份有限公司苏州分行', business_status: '存续', register_capital: '6亿元', legal_person: '冯雪', register_date: '2015-07-22', related_company_count: 45 }, - ], - - // 公告列表 - announcements: [ - { - title: '平安银行股份有限公司2024年第三季度报告', - announce_date: '2024-10-28', - info_type: '定期报告', - format: 'PDF', - file_size: 2580, - url: '/announcement/detail/ann_20241028_001' - }, - { - title: '关于召开2024年第一次临时股东大会的通知', - announce_date: '2024-10-15', - info_type: '临时公告', - format: 'PDF', - file_size: 156, - url: '/announcement/detail/ann_20241015_001' - }, - { - title: '平安银行股份有限公司关于完成注册资本变更登记的公告', - announce_date: '2024-09-20', - info_type: '临时公告', - format: 'PDF', - file_size: 89, - url: '/announcement/detail/ann_20240920_001' - }, - { - title: '平安银行股份有限公司2024年半年度报告', - announce_date: '2024-08-28', - info_type: '定期报告', - format: 'PDF', - file_size: 3420, - url: '/announcement/detail/ann_20240828_001' - }, - { - title: '关于2024年上半年利润分配预案的公告', - announce_date: '2024-08-20', - info_type: '分配方案', - format: 'PDF', - file_size: 245, - url: '/announcement/detail/ann_20240820_001' - } - ], - - // 披露时间表 - disclosureSchedule: [ - { report_name: '2024年年度报告', is_disclosed: false, actual_date: null, latest_scheduled_date: '2025-04-30' }, - { report_name: '2024年第四季度报告', is_disclosed: false, actual_date: null, latest_scheduled_date: '2025-01-31' }, - { report_name: '2024年第三季度报告', is_disclosed: true, actual_date: '2024-10-28', latest_scheduled_date: '2024-10-31' }, - { report_name: '2024年半年度报告', is_disclosed: true, actual_date: '2024-08-28', latest_scheduled_date: '2024-08-31' }, - { report_name: '2024年第一季度报告', is_disclosed: true, actual_date: '2024-04-28', latest_scheduled_date: '2024-04-30' } - ], - - // 综合分析 - 结构与组件期望格式匹配 - comprehensiveAnalysis: { - qualitative_analysis: { - core_positioning: { - one_line_intro: '中国领先的股份制商业银行,平安集团综合金融战略的核心载体', - // 核心特性(显示在核心定位区域下方的两个卡片) - features: [ - { - icon: 'bank', - title: '零售业务', - description: '收入占比超50%,个人客户突破1.2亿户,零售AUM 4.2万亿' - }, - { - icon: 'fire', - title: '综合金融', - description: '交叉销售和客户资源共享带来持续增长,成本趋近于零' - } - ], - // 结构化投资亮点 - investment_highlights: [ - { - icon: 'users', - title: '综合金融优势', - description: '背靠平安集团,客户资源共享和交叉销售带来持续增长动力' - }, - { - icon: 'trending-up', - title: '零售转型成效', - description: '零售业务收入占比超50%,个人客户突破1.2亿户' - }, - { - icon: 'cpu', - title: '金融科技领先', - description: 'AI、大数据、区块链等技术深化应用,运营效率持续提升' - }, - { - icon: 'shield-check', - title: '风险管理体系', - description: '不良贷款率控制在较低水平,拨备覆盖率保持充足' - } - ], - // 结构化商业模式 - business_model_sections: [ - { - title: '零售银行核心驱动', - description: '以零售银行业务为核心驱动,依托平安集团综合金融平台,构建智能化、移动化、综合化三位一体发展模式。' - }, - { - title: '科技赋能转型', - description: '通过科技赋能实现业务流程数字化,降本增效的同时提升客户体验。', - tags: ['AI应用深化', '大数据分析'] - }, - { - title: '对公业务聚焦', - description: '聚焦供应链金融和产业互联网,服务实体经济高质量发展。' - } - ], - // 兼容旧数据格式 - investment_highlights_text: '1. 零售AUM 4.2万亿、抵押贷占比63%,低不良+高拨备形成稀缺安全垫\n2. 背靠平安集团,保险-银行-投资生态协同,交叉销售成本趋近于零\n3. 战略收缩高风险消费贷、发力科技/绿色/普惠"五篇大文章",资产重构带来息差与估值双升期权', - business_model_desc: '以零售金融为压舱石,通过按揭、私行财富、信用卡获取低成本负债;对公金融做精行业赛道,输出供应链金融与跨境金融解决方案;同业金融做专投资交易,赚取做市与波段收益。' - }, - strategy: { - strategy_description: '以"零售做强、对公做精、同业做专"为主线,通过压降高风险资产、深耕科技绿色普惠、强化集团协同,实现轻资本、弱周期、高股息的高质量增长。', - strategic_initiatives: '2025年AI 138个项目落地,构建智能风控、智能投顾与智能运营,目标3年降低单位成本10%以上;发行800亿元资本债,用于置换存量高成本次级债并支持科技绿色贷款扩张,目标2026年科技绿色贷款占比提升至15%' - } - }, - competitive_position: { - ranking: { - industry_rank: 6, - total_companies: 42 - }, - analysis: { - main_competitors: '招商银行、兴业银行、中信银行、浦发银行、民生银行', - competitive_advantages: '1. 综合金融优势:依托平安集团综合金融平台,实现银行、保险、投资等业务协同\n2. 科技创新领先:金融科技投入占营收比重行业领先,AI、大数据应用成熟\n3. 零售客户基础雄厚:个人客户1.2亿+,财富管理AUM持续增长\n4. 品牌认知度高:平安品牌具有较强的公众认知度和信任度', - competitive_disadvantages: '1. 网点覆盖不如国有大行,在县域地区布局相对薄弱\n2. 对公业务规模与头部股份制银行存在差距\n3. 存款成本相对较高,息差空间受到一定压制' - }, - scores: { - market_position: 82, - technology: 90, - brand: 85, - operation: 83, - finance: 86, - innovation: 92, - risk: 84, - growth: 80 - } - }, - business_structure: [ - { - business_name: '舒泰清(复方聚乙二醇电解质散IV)', - business_level: 1, - revenue: 17900, - revenue_unit: '万元', - financial_metrics: { - revenue_ratio: 55.16, - gross_margin: 78.21 - }, - growth_metrics: { - revenue_growth: -8.20 - }, - report_period: '2024年报' - }, - { - business_name: '苏肽生(注射用鼠神经生长因子)', - business_level: 1, - revenue: 13400, - revenue_unit: '万元', - financial_metrics: { - revenue_ratio: 41.21, - gross_margin: 89.11 - }, - growth_metrics: { - revenue_growth: -17.30 - }, - report_period: '2024年报' - }, - { - business_name: '舒斯通(复方聚乙二醇(3350)电解质散)', - business_level: 1, - revenue: 771, - revenue_unit: '万元', - financial_metrics: { - revenue_ratio: 2.37 - }, - report_period: '2024年报' - }, - { - business_name: '阿司匹林肠溶片', - business_level: 1, - revenue: 396, - revenue_unit: '万元', - financial_metrics: { - revenue_ratio: 1.22 - }, - report_period: '2024年报' - }, - { - business_name: '研发业务', - business_level: 1, - report_period: '2024年报' - } - ], - business_segments: [ - { - segment_name: '已上市药品营销', - segment_description: '舒泰神已上市药品营销业务主要包括舒泰清(复方聚乙二醇电解质散IV)和苏肽生(注射用鼠神经生长因子)两大核心产品。2024年实现营业收入3.25亿元,其中舒泰清贡献1.79亿元(55.16%),苏肽生贡献1.34亿元(41.21%)。尽管面临市场竞争压力,产品毛利率保持高位,综合毛利率达80.83%,其中苏肽生毛利率高达89.11%。', - competitive_position: '舒泰清为《中国消化内镜诊疗肠道准备指南》和《慢性便秘诊治指南》一线用药,苏肽生是国内首个国药准字鼠神经生长因子产品。公司医保目录产品舒斯通已落地,并布局舒亦清、舒常轻等系列产品形成梯队,构建了一定市场竞争优势。然而,2024年集采中同类(III型)产品中选,对舒泰清(IV型)形成潜在价格压力。', - future_potential: '公司正在构建系列化产品线应对市场变化,研发投入保持高强度(1.62亿元,占营收49.97%)。在研管线中,STSP-0601血友病药物获FDA孤儿药资格,BDB-001被纳入突破性治疗品种,创新药研发持续推进。国家政策支持创新药发展,行业环境向好,同时国际化布局已有初步进展,未来3-5年有望通过新产品上市实现业绩突破。' - } - ] - }, - - // 价值链分析 - 结构与组件期望格式匹配 - valueChainAnalysis: { - value_chain_flows: [ - // 上游第2级 → 上游第1级 - { - source: { node_name: '中国人民银行', node_level: -2 }, - target: { node_name: '同业市场', node_level: -1 }, - flow_metrics: { flow_ratio: 35 } - }, - { - source: { node_name: '银保监会', node_level: -2 }, - target: { node_name: '同业市场', node_level: -1 }, - flow_metrics: { flow_ratio: 25 } - }, - { - source: { node_name: '中国人民银行', node_level: -2 }, - target: { node_name: '债券市场', node_level: -1 }, - flow_metrics: { flow_ratio: 30 } - }, - // 上游第1级 → 核心企业 - { - source: { node_name: '同业市场', node_level: -1 }, - target: { node_name: '平安银行', node_level: 0 }, - flow_metrics: { flow_ratio: 40 } - }, - { - source: { node_name: '债券市场', node_level: -1 }, - target: { node_name: '平安银行', node_level: 0 }, - flow_metrics: { flow_ratio: 25 } - }, - { - source: { node_name: '平安集团', node_level: -1 }, - target: { node_name: '平安银行', node_level: 0 }, - flow_metrics: { flow_ratio: 20 } - }, - { - source: { node_name: '金融科技供应商', node_level: -1 }, - target: { node_name: '平安银行', node_level: 0 }, - flow_metrics: { flow_ratio: 15 } - }, - // 核心企业 → 下游第1级 - { - source: { node_name: '平安银行', node_level: 0 }, - target: { node_name: '个人客户', node_level: 1 }, - flow_metrics: { flow_ratio: 50 } - }, - { - source: { node_name: '平安银行', node_level: 0 }, - target: { node_name: '企业客户', node_level: 1 }, - flow_metrics: { flow_ratio: 35 } - }, - { - source: { node_name: '平安银行', node_level: 0 }, - target: { node_name: '政府机构', node_level: 1 }, - flow_metrics: { flow_ratio: 10 } - }, - { - source: { node_name: '平安银行', node_level: 0 }, - target: { node_name: '金融同业', node_level: 1 }, - flow_metrics: { flow_ratio: 5 } - }, - // 下游第1级 → 下游第2级 - { - source: { node_name: '个人客户', node_level: 1 }, - target: { node_name: '消费场景', node_level: 2 }, - flow_metrics: { flow_ratio: 60 } - }, - { - source: { node_name: '企业客户', node_level: 1 }, - target: { node_name: '产业链', node_level: 2 }, - flow_metrics: { flow_ratio: 70 } - }, - { - source: { node_name: '政府机构', node_level: 1 }, - target: { node_name: '公共服务', node_level: 2 }, - flow_metrics: { flow_ratio: 80 } - }, - { - source: { node_name: '个人客户', node_level: 1 }, - target: { node_name: '产业链', node_level: 2 }, - flow_metrics: { flow_ratio: 20 } - }, - { - source: { node_name: '企业客户', node_level: 1 }, - target: { node_name: '公共服务', node_level: 2 }, - flow_metrics: { flow_ratio: 15 } - } - ], - value_chain_structure: { - nodes_by_level: { - 'level_-2': [ - { - node_name: '中国人民银行', - node_type: 'regulator', - node_description: '制定货币政策,维护金融稳定,是银行业的最高监管机构', - node_level: -2, - importance_score: 95, - market_share: null, - dependency_degree: 100 - }, - { - node_name: '银保监会', - node_type: 'regulator', - node_description: '负责银行业和保险业的监督管理,制定行业规范', - node_level: -2, - importance_score: 90, - market_share: null, - dependency_degree: 95 - } - ], - 'level_-1': [ - { - node_name: '同业市场', - node_type: 'supplier', - node_description: '银行间资金拆借市场,提供短期流动性支持', - node_level: -1, - importance_score: 85, - market_share: 12.5, - dependency_degree: 75 - }, - { - node_name: '债券市场', - node_type: 'supplier', - node_description: '债券发行与交易市场,银行重要融资渠道', - node_level: -1, - importance_score: 80, - market_share: 8.2, - dependency_degree: 60 - }, - { - node_name: '平安集团', - node_type: 'supplier', - node_description: '控股股东,提供综合金融平台支撑和客户资源共享', - node_level: -1, - importance_score: 92, - market_share: 100, - dependency_degree: 85 - }, - { - node_name: '金融科技供应商', - node_type: 'supplier', - node_description: '提供核心系统、云服务、AI等技术支持', - node_level: -1, - importance_score: 75, - market_share: 15.0, - dependency_degree: 55 - } - ], - 'level_0': [ - { - node_name: '平安银行', - node_type: 'company', - node_description: '全国性股份制商业银行,零售银行转型标杆,科技驱动战略引领者', - node_level: 0, - importance_score: 100, - market_share: 2.8, - dependency_degree: 0, - is_core: true - } - ], - 'level_1': [ - { - node_name: '个人客户', - node_type: 'customer', - node_description: '零售银行服务对象,超1.2亿户,涵盖储蓄、信用卡、消费贷等业务', - node_level: 1, - importance_score: 88, - market_share: 3.5, - dependency_degree: 45 - }, - { - node_name: '企业客户', - node_type: 'customer', - node_description: '对公金融服务对象,超90万户,包括大型企业、中小微企业', - node_level: 1, - importance_score: 82, - market_share: 2.1, - dependency_degree: 40 - }, - { - node_name: '政府机构', - node_type: 'customer', - node_description: '政务金融服务对象,提供财政资金管理、政务支付等服务', - node_level: 1, - importance_score: 70, - market_share: 1.8, - dependency_degree: 25 - }, - { - node_name: '金融同业', - node_type: 'customer', - node_description: '同业金融服务对象,包括其他银行、保险、基金等金融机构', - node_level: 1, - importance_score: 65, - market_share: 2.5, - dependency_degree: 20 - } - ], - 'level_2': [ - { - node_name: '消费场景', - node_type: 'end_user', - node_description: '个人消费支付场景,包括电商、餐饮、出行、娱乐等日常消费', - node_level: 2, - importance_score: 72, - market_share: 4.2, - dependency_degree: 30 - }, - { - node_name: '产业链', - node_type: 'end_user', - node_description: '企业生产经营场景,覆盖采购、生产、销售全链条金融服务', - node_level: 2, - importance_score: 78, - market_share: 2.8, - dependency_degree: 35 - }, - { - node_name: '公共服务', - node_type: 'end_user', - node_description: '政务公共服务场景,包括社保、医疗、教育等民生领域', - node_level: 2, - importance_score: 68, - market_share: 1.5, - dependency_degree: 20 - } - ] - } - }, - analysis_summary: { - upstream_nodes: 6, - company_nodes: 1, - downstream_nodes: 7, - total_nodes: 14, - key_insights: '平安银行处于金融产业链核心位置,上游依托央行政策和集团资源,下游服务广泛的个人和企业客户群体' - } - }, - - // 关键因素时间线 - 结构与组件期望格式匹配 - keyFactorsTimeline: { - key_factors: { - total_factors: 8, - categories: [ - { - category_name: '财务指标', - factors: [ - { - factor_name: '净息差', - factor_value: 2.45, - factor_unit: '%', - factor_desc: '利率市场化推进下,净息差较上年同期收窄12bp,但仍处于行业中上水平', - impact_direction: 'negative', - impact_weight: 85, - year_on_year: -4.7, - report_period: '2024Q3' - }, - { - factor_name: '不良贷款率', - factor_value: 1.05, - factor_unit: '%', - factor_desc: '资产质量稳中向好,不良贷款率环比下降3bp,拨备覆盖率保持在280%以上', - impact_direction: 'positive', - impact_weight: 90, - year_on_year: -2.8, - report_period: '2024Q3' - }, - { - factor_name: '零售AUM', - factor_value: 4.2, - factor_unit: '万亿', - factor_desc: '零售客户资产管理规模持续增长,同比增长15.3%,财富管理转型成效显著', - impact_direction: 'positive', - impact_weight: 88, - year_on_year: 15.3, - report_period: '2024Q3' - } - ] - }, - { - category_name: '业务发展', - factors: [ - { - factor_name: '零售营收占比', - factor_value: 58.3, - factor_unit: '%', - factor_desc: '零售银行转型深化,零售业务收入占比持续提升,成为最主要的盈利来源', - impact_direction: 'positive', - impact_weight: 92, - year_on_year: 3.2, - report_period: '2024Q3' - }, - { - factor_name: '对公贷款增速', - factor_value: 8.5, - factor_unit: '%', - factor_desc: '对公业务稳步发展,聚焦制造业、绿色金融等重点领域', - impact_direction: 'neutral', - impact_weight: 70, - year_on_year: -1.2, - report_period: '2024Q3' - } - ] - }, - { - category_name: '风险因素', - factors: [ - { - factor_name: '房地产敞口', - factor_value: 3200, - factor_unit: '亿元', - factor_desc: '房地产相关贷款余额约3200亿,占比8.5%,需持续关注行业风险演变', - impact_direction: 'negative', - impact_weight: 75, - year_on_year: -5.2, - report_period: '2024Q3' - }, - { - factor_name: '信用成本', - factor_value: 1.32, - factor_unit: '%', - factor_desc: '信用成本率保持相对稳定,风险抵补能力充足', - impact_direction: 'neutral', - impact_weight: 72, - year_on_year: 0.05, - report_period: '2024Q3' - } - ] - }, - { - category_name: '战略布局', - factors: [ - { - factor_name: '科技投入', - factor_value: 120, - factor_unit: '亿元', - factor_desc: '持续加大金融科技投入,AI、大数据应用深化,数字化转型领先同业', - impact_direction: 'positive', - impact_weight: 85, - year_on_year: 18.5, - report_period: '2024' - } - ] - } - ] - }, - development_timeline: { - statistics: { - positive_events: 10, - negative_events: 3, - neutral_events: 2 - }, - events: [ - { - event_date: '2024-11-15', - event_title: '获评"最佳零售银行"称号', - event_type: '荣誉奖项', - event_desc: '在《亚洲银行家》评选中荣获"中国最佳零售银行"称号,零售转型战略获行业高度认可', - importance: 'high', - impact_metrics: { - impact_score: 75, - is_positive: true - }, - related_info: { - financial_impact: '品牌价值提升,预计带动零售业务增长2-3%' - } - }, - { - event_date: '2024-10-28', - event_title: '发布2024年三季报', - event_type: '业绩公告', - event_desc: '前三季度实现净利润412.8亿元,同比增长12.5%,超市场预期。零售业务收入占比提升至52.3%', - importance: 'high', - impact_metrics: { - impact_score: 88, - is_positive: true - }, - related_info: { - financial_impact: '股价当日上涨5.2%,市值增加约150亿元' - } - }, - { - event_date: '2024-09-20', - event_title: '房地产贷款风险暴露', - event_type: '风险事件', - event_desc: '部分房地产开发贷款出现逾期,计提减值准备约25亿元,不良贷款率小幅上升', - importance: 'high', - impact_metrics: { - impact_score: 65, - is_positive: false - }, - related_info: { - financial_impact: '影响当期利润约18亿元,股价下跌2.8%' - } - }, - { - event_date: '2024-09-15', - event_title: '推出AI智能客服系统3.0', - event_type: '科技创新', - event_desc: '新一代AI客服系统上线,集成大语言模型技术,客服效率提升40%,客户满意度达95%', - importance: 'medium', - impact_metrics: { - impact_score: 72, - is_positive: true - }, - related_info: { - financial_impact: '预计年化降低运营成本约3亿元' - } - }, - { - event_date: '2024-08-28', - event_title: '发布2024年中报', - event_type: '业绩公告', - event_desc: '上半年净利润增长11.2%,达275.6亿元。资产质量保持稳定,不良贷款率1.05%', - importance: 'high', - impact_metrics: { - impact_score: 82, - is_positive: true - }, - related_info: { - financial_impact: '股价累计上涨3.8%' - } - }, - { - event_date: '2024-07-20', - event_title: '平安理财获批新产品资质', - event_type: '业务拓展', - event_desc: '旗下平安理财获批养老理财产品试点资格,成为首批获批的股份制银行理财子公司', - importance: 'high', - impact_metrics: { - impact_score: 78, - is_positive: true - }, - related_info: { - financial_impact: '预计为AUM贡献500-800亿元增量' - } - }, - { - event_date: '2024-06-25', - event_title: '监管处罚通知', - event_type: '合规事件', - event_desc: '因贷款业务违规被银保监会罚款1200万元,涉及信贷资金违规流入房地产领域', - importance: 'medium', - impact_metrics: { - impact_score: 45, - is_positive: false - }, - related_info: { - financial_impact: '罚款金额对业绩影响有限,但需加强合规管理' - } - }, - { - event_date: '2024-06-10', - event_title: '完成300亿元二级资本债发行', - event_type: '融资事件', - event_desc: '成功发行300亿元二级资本债券,票面利率3.15%,认购倍数达2.8倍,市场反响良好', - importance: 'medium', - impact_metrics: { - impact_score: 68, - is_positive: true - }, - related_info: { - financial_impact: '资本充足率提升0.35个百分点至13.2%' - } - }, - { - event_date: '2024-05-18', - event_title: '与腾讯云达成战略合作', - event_type: '战略合作', - event_desc: '与腾讯云签署战略合作协议,在云计算、大数据、人工智能等领域开展深度合作', - importance: 'medium', - impact_metrics: { - impact_score: 70, - is_positive: true - }, - related_info: { - financial_impact: '预计3年内科技投入效率提升20%' - } - }, - { - event_date: '2024-04-30', - event_title: '发布2024年一季报', - event_type: '业绩公告', - event_desc: '一季度净利润增长10.8%,达138.2亿元。信用卡业务表现亮眼,交易额同比增长18%', - importance: 'high', - impact_metrics: { - impact_score: 80, - is_positive: true - }, - related_info: { - financial_impact: '开门红业绩推动股价上涨4.2%' - } - }, - { - event_date: '2024-03-28', - event_title: '高管层人事变动', - event_type: '人事变动', - event_desc: '副行长郭世邦因个人原因辞职,对公业务主管暂由行长冀光恒兼任', - importance: 'medium', - impact_metrics: { - impact_score: 52, - is_positive: false - }, - related_info: { - financial_impact: '短期内对公业务战略执行或受影响' - } - }, - { - event_date: '2024-03-15', - event_title: '零售客户突破1.2亿户', - event_type: '业务里程碑', - event_desc: '零售客户数量突破1.2亿户大关,较年初净增800万户,私行客户AUM突破1.5万亿', - importance: 'high', - impact_metrics: { - impact_score: 85, - is_positive: true - }, - related_info: { - financial_impact: '零售转型成效显著,客户基础进一步夯实' - } - }, - { - event_date: '2024-02-05', - event_title: '发布2023年年报', - event_type: '业绩公告', - event_desc: '2023年全年净利润464.5亿元,同比增长2.1%。拨备覆盖率277%,资产质量稳健', - importance: 'high', - impact_metrics: { - impact_score: 75, - is_positive: true - }, - related_info: { - financial_impact: '分红方案:每股派息0.28元,股息率约4.2%' - } - }, - { - event_date: '2024-01-10', - event_title: '供应链金融平台升级', - event_type: '产品创新', - event_desc: '供应链金融平台完成4.0版本升级,新增区块链存证、智能风控等功能,服务企业超3.5万家', - importance: 'medium', - impact_metrics: { - impact_score: 72, - is_positive: true - }, - related_info: { - financial_impact: '供应链金融余额预计增长25%' - } - }, - { - event_date: '2023-12-20', - event_title: '获批设立香港分行', - event_type: '业务拓展', - event_desc: '获中国银保监会批准设立香港分行,标志着国际化战略迈出重要一步', - importance: 'high', - impact_metrics: { - impact_score: 78, - is_positive: true - }, - related_info: { - financial_impact: '预计为跨境业务带来新增长点' - } - } - ] - } - }, - - // 盈利预测报告 - forecastReport: { - income_profit_trend: { - years: ['2020', '2021', '2022', '2023', '2024E', '2025E', '2026E'], - income: [116524, 134632, 148956, 162350, 175280, 189450, 204120], - profit: [34562, 39845, 43218, 52860, 58420, 64680, 71250] - }, - growth_bars: { - years: ['2021', '2022', '2023', '2024E', '2025E', '2026E'], - revenue_growth_pct: [15.5, 10.6, 8.9, 8.0, 8.1, 7.7] - }, - eps_trend: { - years: ['2020', '2021', '2022', '2023', '2024E', '2025E', '2026E'], - eps: [1.78, 2.05, 2.23, 2.72, 3.01, 3.33, 3.67] - }, - pe_peg_axes: { - years: ['2020', '2021', '2022', '2023', '2024E', '2025E', '2026E'], - pe: [7.4, 6.9, 7.2, 4.9, 4.4, 4.0, 3.6], - peg: [0.48, 0.65, 0.81, 0.55, 0.55, 0.49, 0.47] - }, - detail_table: { - years: ['2020', '2021', '2022', '2023', '2024E', '2025E', '2026E'], - rows: [ - { '指标': '营业总收入(百万元)', '2020': 116524, '2021': 134632, '2022': 148956, '2023': 162350, '2024E': 175280, '2025E': 189450, '2026E': 204120 }, - { '指标': '营收增长率(%)', '2020': '-', '2021': 15.5, '2022': 10.6, '2023': 8.9, '2024E': 8.0, '2025E': 8.1, '2026E': 7.7 }, - { '指标': '归母净利润(百万元)', '2020': 34562, '2021': 39845, '2022': 43218, '2023': 52860, '2024E': 58420, '2025E': 64680, '2026E': 71250 }, - { '指标': '净利润增长率(%)', '2020': '-', '2021': 15.3, '2022': 8.5, '2023': 22.3, '2024E': 10.5, '2025E': 10.7, '2026E': 10.2 }, - { '指标': 'EPS(稀释,元)', '2020': 1.78, '2021': 2.05, '2022': 2.23, '2023': 2.72, '2024E': 3.01, '2025E': 3.33, '2026E': 3.67 }, - { '指标': 'ROE(%)', '2020': 14.2, '2021': 15.8, '2022': 15.5, '2023': 16.2, '2024E': 16.5, '2025E': 16.8, '2026E': 17.0 }, - { '指标': '总资产(百万元)', '2020': 4512360, '2021': 4856230, '2022': 4923150, '2023': 5024560, '2024E': 5230480, '2025E': 5445200, '2026E': 5668340 }, - { '指标': '净资产(百万元)', '2020': 293540, '2021': 312680, '2022': 318920, '2023': 325680, '2024E': 338560, '2025E': 352480, '2026E': 367820 }, - { '指标': '资产负债率(%)', '2020': 93.5, '2021': 93.6, '2022': 93.5, '2023': 93.5, '2024E': 93.5, '2025E': 93.5, '2026E': 93.5 }, - { '指标': 'PE(倍)', '2020': 7.4, '2021': 6.9, '2022': 7.2, '2023': 4.9, '2024E': 4.4, '2025E': 4.0, '2026E': 3.6 }, - { '指标': 'PB(倍)', '2020': 1.05, '2021': 1.09, '2022': 1.12, '2023': 0.79, '2024E': 0.72, '2025E': 0.67, '2026E': 0.61 } - ] - } - } -}; - -// 生成通用公司数据的工具函数 -export const generateCompanyData = (stockCode, stockName = '示例公司') => { - // 如果是平安银行,直接返回详细数据 - if (stockCode === '000001') { - return PINGAN_BANK_DATA; - } - - // 随机生成一些基础数值 - const baseRevenue = Math.floor(Math.random() * 50000) + 10000; - const baseProfit = Math.floor(Math.random() * 5000) + 1000; - const employeeCount = Math.floor(Math.random() * 20000) + 1000; - - // 生成通用数据,结构与组件期望格式匹配 - return { - stockCode, - stockName, - basicInfo: { - SECCODE: stockCode, - SECNAME: stockName, - ORGNAME: `${stockName}股份有限公司`, - english_name: `${stockName} Co., Ltd.`, - reg_capital: Math.floor(Math.random() * 500000) + 10000, - legal_representative: '张三', - chairman: '张三', - general_manager: '李四', - secretary: '王五', - reg_address: '中国某省某市某区某路123号', - office_address: '中国某省某市某区某路456号', - zipcode: '100000', - tel: '010-12345678', - fax: '010-12345679', - email: 'ir@company.com', - website: 'http://www.company.com', - sw_industry_l1: '制造业', - sw_industry_l2: '电子设备', - sw_industry_l3: '消费电子', - establish_date: '2005-01-01', - list_date: '2010-06-15', - province: '广东省', - city: '深圳市', - credit_code: '91440300XXXXXXXXXX', - company_size: '中型企业', - accounting_firm: '安永华明会计师事务所', - law_firm: '北京市君合律师事务所', - company_intro: `${stockName}股份有限公司是一家专注于XX领域的高科技企业,致力于为客户提供优质的产品和服务。公司拥有完善的研发体系和生产能力,在行业内具有较强的竞争力。`, - main_business: '电子产品的研发、生产和销售', - business_scope: '电子产品、通信设备、计算机软硬件的研发、生产、销售;技术咨询、技术服务;货物进出口、技术进出口。', - employees: employeeCount, - }, - actualControl: [ - { - actual_controller_name: '某控股集团有限公司', - controller_name: '某控股集团有限公司', - control_type: '企业法人', - controller_type: '企业', - holding_ratio: 35.5, - holding_shares: 1560000000, - end_date: '2024-09-30', - control_chain: `某控股集团有限公司 -> ${stockName}股份有限公司`, - is_listed: false, - } - ], - concentration: [ - { stat_item: '前1大股东', holding_ratio: 35.5, ratio_change: 0.12, end_date: '2024-09-30' }, - { stat_item: '前3大股东', holding_ratio: 52.3, ratio_change: 0.25, end_date: '2024-09-30' }, - { stat_item: '前5大股东', holding_ratio: 61.8, ratio_change: 0.18, end_date: '2024-09-30' }, - { stat_item: '前10大股东', holding_ratio: 72.5, ratio_change: 0.32, end_date: '2024-09-30' }, - { stat_item: '前1大股东', holding_ratio: 35.38, ratio_change: -0.08, end_date: '2024-06-30' }, - { stat_item: '前3大股东', holding_ratio: 52.05, ratio_change: -0.15, end_date: '2024-06-30' }, - { stat_item: '前5大股东', holding_ratio: 61.62, ratio_change: -0.10, end_date: '2024-06-30' }, - { stat_item: '前10大股东', holding_ratio: 72.18, ratio_change: -0.20, end_date: '2024-06-30' }, - ], - management: [ - // 高管 - { name: '张三', position_name: '董事长', position_category: '高管', gender: '男', birth_year: '1969', education: '硕士', nationality: '中国', start_date: '2018-06-01', status: 'active' }, - { name: '李四', position_name: '总经理', position_category: '高管', gender: '男', birth_year: '1974', education: '硕士', nationality: '中国', start_date: '2019-03-15', status: 'active' }, - { name: '王五', position_name: '董事会秘书', position_category: '高管', gender: '女', birth_year: '1979', education: '本科', nationality: '中国', start_date: '2020-01-10', status: 'active' }, - { name: '赵六', position_name: '财务总监', position_category: '高管', gender: '男', birth_year: '1976', education: '硕士', nationality: '中国', start_date: '2017-09-01', status: 'active' }, - { name: '钱七', position_name: '技术总监', position_category: '高管', gender: '男', birth_year: '1982', education: '博士', nationality: '中国', start_date: '2021-06-01', status: 'active' }, - // 董事 - { name: '孙八', position_name: '非执行董事', position_category: '董事', gender: '男', birth_year: '1965', education: '博士', nationality: '中国', start_date: '2016-06-15', status: 'active' }, - { name: '周九', position_name: '非执行董事', position_category: '董事', gender: '男', birth_year: '1968', education: '硕士', nationality: '中国', start_date: '2018-06-20', status: 'active' }, - { name: '吴十', position_name: '独立董事', position_category: '董事', gender: '女', birth_year: '1972', education: '博士', nationality: '美国', start_date: '2019-06-18', status: 'active' }, - { name: '郑十一', position_name: '独立董事', position_category: '董事', gender: '男', birth_year: '1970', education: '博士', nationality: '中国', start_date: '2020-06-25', status: 'active' }, - // 监事 - { name: '冯十二', position_name: '监事会主席', position_category: '监事', gender: '男', birth_year: '1967', education: '硕士', nationality: '中国', start_date: '2017-06-15', status: 'active' }, - { name: '陈十三', position_name: '职工监事', position_category: '监事', gender: '女', birth_year: '1975', education: '本科', nationality: '中国', start_date: '2019-06-20', status: 'active' }, - { name: '楚十四', position_name: '外部监事', position_category: '监事', gender: '男', birth_year: '1971', education: '硕士', nationality: '中国', start_date: '2020-06-18', status: 'active' }, - // 其他 - { name: '卫十五', position_name: '合规负责人', position_category: '其他', gender: '男', birth_year: '1978', education: '硕士', nationality: '中国', start_date: '2018-09-01', status: 'active' }, - { name: '蒋十六', position_name: '内审部负责人', position_category: '其他', gender: '女', birth_year: '1980', education: '硕士', nationality: '中国', start_date: '2019-03-15', status: 'active' }, - ], - topCirculationShareholders: [ - { shareholder_rank: 1, shareholder_name: '某控股集团有限公司', holding_shares: 560000000, circulation_share_ratio: 35.50, shareholder_type: '法人', end_date: '2024-09-30' }, - { shareholder_rank: 2, shareholder_name: '香港中央结算有限公司(陆股通)', holding_shares: 156000000, circulation_share_ratio: 9.88, shareholder_type: 'QFII', end_date: '2024-09-30' }, - { shareholder_rank: 3, shareholder_name: '中国平安人寿保险股份有限公司-传统-普通保险产品', holding_shares: 89000000, circulation_share_ratio: 5.64, shareholder_type: '保险', end_date: '2024-09-30' }, - { shareholder_rank: 4, shareholder_name: '中国证券金融股份有限公司', holding_shares: 67000000, circulation_share_ratio: 4.24, shareholder_type: '券商', end_date: '2024-09-30' }, - { shareholder_rank: 5, shareholder_name: '中央汇金资产管理有限责任公司', holding_shares: 45000000, circulation_share_ratio: 2.85, shareholder_type: '法人', end_date: '2024-09-30' }, - { shareholder_rank: 6, shareholder_name: '全国社保基金一零三组合', holding_shares: 34000000, circulation_share_ratio: 2.15, shareholder_type: '社保', end_date: '2024-09-30' }, - { shareholder_rank: 7, shareholder_name: '华夏上证50交易型开放式指数证券投资基金', holding_shares: 28000000, circulation_share_ratio: 1.77, shareholder_type: '基金', end_date: '2024-09-30' }, - { shareholder_rank: 8, shareholder_name: '中国人寿保险股份有限公司-分红-个人分红-005L-FH002深', holding_shares: 23000000, circulation_share_ratio: 1.46, shareholder_type: '保险', end_date: '2024-09-30' }, - { shareholder_rank: 9, shareholder_name: '易方达沪深300交易型开放式指数发起式证券投资基金', holding_shares: 19000000, circulation_share_ratio: 1.20, shareholder_type: '基金', end_date: '2024-09-30' }, - { shareholder_rank: 10, shareholder_name: '嘉实沪深300交易型开放式指数证券投资基金', holding_shares: 15000000, circulation_share_ratio: 0.95, shareholder_type: '基金', end_date: '2024-09-30' } - ], - topShareholders: [ - { shareholder_rank: 1, shareholder_name: '某控股集团有限公司', holding_shares: 560000000, total_share_ratio: 35.50, shareholder_type: '法人', share_nature: '流通A股', end_date: '2024-09-30' }, - { shareholder_rank: 2, shareholder_name: '香港中央结算有限公司(陆股通)', holding_shares: 156000000, total_share_ratio: 9.88, shareholder_type: 'QFII', share_nature: '流通A股', end_date: '2024-09-30' }, - { shareholder_rank: 3, shareholder_name: '中国平安人寿保险股份有限公司-传统-普通保险产品', holding_shares: 89000000, total_share_ratio: 5.64, shareholder_type: '保险', share_nature: '流通A股', end_date: '2024-09-30' }, - { shareholder_rank: 4, shareholder_name: '中国证券金融股份有限公司', holding_shares: 67000000, total_share_ratio: 4.24, shareholder_type: '券商', share_nature: '流通A股', end_date: '2024-09-30' }, - { shareholder_rank: 5, shareholder_name: '中央汇金资产管理有限责任公司', holding_shares: 45000000, total_share_ratio: 2.85, shareholder_type: '法人', share_nature: '流通A股', end_date: '2024-09-30' }, - { shareholder_rank: 6, shareholder_name: '全国社保基金一零三组合', holding_shares: 34000000, total_share_ratio: 2.15, shareholder_type: '社保', share_nature: '流通A股', end_date: '2024-09-30' }, - { shareholder_rank: 7, shareholder_name: '华夏上证50交易型开放式指数证券投资基金', holding_shares: 28000000, total_share_ratio: 1.77, shareholder_type: '基金', share_nature: '流通A股', end_date: '2024-09-30' }, - { shareholder_rank: 8, shareholder_name: '中国人寿保险股份有限公司-分红-个人分红-005L-FH002深', holding_shares: 23000000, total_share_ratio: 1.46, shareholder_type: '保险', share_nature: '流通A股', end_date: '2024-09-30' }, - { shareholder_rank: 9, shareholder_name: '易方达沪深300交易型开放式指数发起式证券投资基金', holding_shares: 19000000, total_share_ratio: 1.20, shareholder_type: '基金', share_nature: '流通A股', end_date: '2024-09-30' }, - { shareholder_rank: 10, shareholder_name: '嘉实沪深300交易型开放式指数证券投资基金', holding_shares: 15000000, total_share_ratio: 0.95, shareholder_type: '基金', share_nature: '流通A股', end_date: '2024-09-30' } - ], - branches: [ - { branch_name: `${stockName}北京分公司`, business_status: '存续', register_capital: '5000万元', legal_person: '张伟', register_date: '2012-05-01', related_company_count: 23 }, - { branch_name: `${stockName}上海分公司`, business_status: '存续', register_capital: '8000万元', legal_person: '李明', register_date: '2013-08-15', related_company_count: 35 }, - { branch_name: `${stockName}广州分公司`, business_status: '存续', register_capital: '3000万元', legal_person: '王芳', register_date: '2014-03-20', related_company_count: 18 }, - { branch_name: `${stockName}深圳分公司`, business_status: '存续', register_capital: '6000万元', legal_person: '陈强', register_date: '2015-06-10', related_company_count: 28 }, - { branch_name: `${stockName}成都分公司`, business_status: '存续', register_capital: '2000万元', legal_person: '刘洋', register_date: '2018-09-25', related_company_count: 12 }, - { branch_name: `${stockName}武汉子公司`, business_status: '注销', register_capital: '1000万元', legal_person: '赵静', register_date: '2016-04-18', related_company_count: 5 }, - ], - announcements: [ - { title: `${stockName}2024年第三季度报告`, announce_date: '2024-10-28', info_type: '定期报告', format: 'PDF', file_size: 1850, url: '#' }, - { title: `${stockName}2024年半年度报告`, announce_date: '2024-08-28', info_type: '定期报告', format: 'PDF', file_size: 2340, url: '#' }, - { title: `关于重大合同签订的公告`, announce_date: '2024-07-15', info_type: '临时公告', format: 'PDF', file_size: 128, url: '#' }, - ], - disclosureSchedule: [ - { report_name: '2024年年度报告', is_disclosed: false, actual_date: null, latest_scheduled_date: '2025-04-30' }, - { report_name: '2024年第三季度报告', is_disclosed: true, actual_date: '2024-10-28', latest_scheduled_date: '2024-10-31' }, - { report_name: '2024年半年度报告', is_disclosed: true, actual_date: '2024-08-28', latest_scheduled_date: '2024-08-31' }, - ], - comprehensiveAnalysis: { - qualitative_analysis: { - core_positioning: { - one_line_intro: `${stockName}是XX行业的领先企业,专注于为客户提供创新解决方案`, - investment_highlights: '1. 行业龙头地位,市场份额领先\n2. 技术研发实力强,专利储备丰富\n3. 客户资源优质,大客户粘性高\n4. 管理团队经验丰富,执行力强', - business_model_desc: `${stockName}采用"研发+生产+销售"一体化经营模式,通过持续的技术创新和产品迭代,为客户提供高性价比的产品和服务。` - }, - strategy: '坚持技术创新驱动发展,深耕核心业务领域,积极拓展新兴市场,持续提升企业核心竞争力。' - }, - competitive_position: { - ranking: { - industry_rank: Math.floor(Math.random() * 20) + 1, - total_companies: 150 - }, - analysis: { - main_competitors: '竞争对手A、竞争对手B、竞争对手C', - competitive_advantages: '技术领先、品牌优势、客户资源丰富、管理团队优秀', - competitive_disadvantages: '规模相对较小、区域布局有待完善' - }, - scores: { - market_position: Math.floor(Math.random() * 20) + 70, - technology: Math.floor(Math.random() * 20) + 70, - brand: Math.floor(Math.random() * 20) + 65, - operation: Math.floor(Math.random() * 20) + 70, - finance: Math.floor(Math.random() * 20) + 70, - innovation: Math.floor(Math.random() * 20) + 70, - risk: Math.floor(Math.random() * 20) + 70, - growth: Math.floor(Math.random() * 20) + 70 - } - }, - business_structure: [ - { - business_name: '舒泰清(复方聚乙二醇电解质散IV)', - business_level: 1, - revenue: 17900, - revenue_unit: '万元', - financial_metrics: { - revenue_ratio: 55.16, - gross_margin: 78.21 - }, - growth_metrics: { - revenue_growth: -8.20 - }, - report_period: '2024年报' - }, - { - business_name: '苏肽生(注射用鼠神经生长因子)', - business_level: 1, - revenue: 13400, - revenue_unit: '万元', - financial_metrics: { - revenue_ratio: 41.21, - gross_margin: 89.11 - }, - growth_metrics: { - revenue_growth: -17.30 - }, - report_period: '2024年报' - }, - { - business_name: '舒斯通(复方聚乙二醇(3350)电解质散)', - business_level: 1, - revenue: 771, - revenue_unit: '万元', - financial_metrics: { - revenue_ratio: 2.37 - }, - report_period: '2024年报' - }, - { - business_name: '阿司匹林肠溶片', - business_level: 1, - revenue: 396, - revenue_unit: '万元', - financial_metrics: { - revenue_ratio: 1.22 - }, - report_period: '2024年报' - }, - { - business_name: '研发业务', - business_level: 1, - report_period: '2024年报' - } - ], - business_segments: [ - { - segment_name: '已上市药品营销', - segment_description: '舒泰神已上市药品营销业务主要包括舒泰清(复方聚乙二醇电解质散IV)和苏肽生(注射用鼠神经生长因子)两大核心产品。2024年实现营业收入3.25亿元,其中舒泰清贡献1.79亿元(55.16%),苏肽生贡献1.34亿元(41.21%)。尽管面临市场竞争压力,产品毛利率保持高位,综合毛利率达80.83%,其中苏肽生毛利率高达89.11%。', - competitive_position: '舒泰清为《中国消化内镜诊疗肠道准备指南》和《慢性便秘诊治指南》一线用药,苏肽生是国内首个国药准字鼠神经生长因子产品。公司医保目录产品舒斯通已落地,并布局舒亦清、舒常轻等系列产品形成梯队,构建了一定市场竞争优势。然而,2024年集采中同类(III型)产品中选,对舒泰清(IV型)形成潜在价格压力。', - future_potential: '公司正在构建系列化产品线应对市场变化,研发投入保持高强度(1.62亿元,占营收49.97%)。在研管线中,STSP-0601血友病药物获FDA孤儿药资格,BDB-001被纳入突破性治疗品种,创新药研发持续推进。国家政策支持创新药发展,行业环境向好,同时国际化布局已有初步进展,未来3-5年有望通过新产品上市实现业绩突破。' - } - ] - }, - valueChainAnalysis: { - value_chain_flows: [ - { from: '原材料供应商', to: stockName, type: 'supply', label: '原材料采购' }, - { from: '设备供应商', to: stockName, type: 'supply', label: '设备采购' }, - { from: stockName, to: '直销客户', type: 'sales', label: '直销' }, - { from: stockName, to: '经销商', type: 'sales', label: '分销' }, - { from: '经销商', to: '终端用户', type: 'distribution', label: '零售' } - ], - value_chain_structure: { - nodes_by_level: { - 'level_-2': [ - { node_name: '原材料供应商', node_type: 'supplier', description: '提供生产所需原材料' } - ], - 'level_-1': [ - { node_name: '设备供应商', node_type: 'supplier', description: '提供生产设备' }, - { node_name: '技术服务商', node_type: 'supplier', description: '提供技术支持' } - ], - 'level_0': [ - { node_name: stockName, node_type: 'company', description: '核心企业', is_core: true } - ], - 'level_1': [ - { node_name: '直销客户', node_type: 'customer', description: '大客户直销' }, - { node_name: '经销商', node_type: 'customer', description: '渠道分销' } - ], - 'level_2': [ - { node_name: '终端用户', node_type: 'end_user', description: '最终消费者' } - ] - } - }, - analysis_summary: { - upstream_nodes: 3, - company_nodes: 1, - downstream_nodes: 3, - total_nodes: 7, - key_insights: `${stockName}在产业链中处于核心位置,上下游关系稳定` - } - }, - keyFactorsTimeline: { - key_factors: { - total_factors: 6, - categories: [ - { - category_name: '财务指标', - factors: [ - { - factor_name: '营收增速', - factor_value: 12.5, - factor_unit: '%', - factor_desc: '营业收入保持稳定增长,主营业务发展良好', - impact_direction: 'positive', - impact_weight: 85, - year_on_year: 2.3, - report_period: '2024Q3' - }, - { - factor_name: '毛利率', - factor_value: 35.2, - factor_unit: '%', - factor_desc: '毛利率水平稳定,成本控制能力较强', - impact_direction: 'neutral', - impact_weight: 75, - year_on_year: -0.8, - report_period: '2024Q3' - } - ] - }, - { - category_name: '业务发展', - factors: [ - { - factor_name: '市场份额', - factor_value: 15.8, - factor_unit: '%', - factor_desc: '市场份额稳步提升,竞争优势逐步巩固', - impact_direction: 'positive', - impact_weight: 82, - year_on_year: 1.5, - report_period: '2024Q3' - }, - { - factor_name: '新产品贡献', - factor_value: 22.3, - factor_unit: '%', - factor_desc: '新产品收入占比提升,创新驱动成效明显', - impact_direction: 'positive', - impact_weight: 78, - year_on_year: 5.2, - report_period: '2024Q3' - } - ] - }, - { - category_name: '风险因素', - factors: [ - { - factor_name: '原材料成本', - factor_value: 28.5, - factor_unit: '%', - factor_desc: '原材料成本占比上升,需关注价格波动影响', - impact_direction: 'negative', - impact_weight: 70, - year_on_year: 3.2, - report_period: '2024Q3' - }, - { - factor_name: '应收账款周转', - factor_value: 85, - factor_unit: '天', - factor_desc: '应收账款周转天数有所增加,需加强回款管理', - impact_direction: 'negative', - impact_weight: 65, - year_on_year: 8, - report_period: '2024Q3' - } - ] - } - ] - }, - development_timeline: { - statistics: { - positive_events: 4, - negative_events: 1, - neutral_events: 1 - }, - events: [ - { - event_date: '2024-10-28', - event_title: '发布2024年三季报', - event_type: '业绩公告', - event_desc: '前三季度业绩稳健增长,营收和净利润均实现双位数增长', - impact_metrics: { impact_score: 82, is_positive: true }, - related_info: { financial_impact: '股价当日上涨3.5%' } - }, - { - event_date: '2024-08-28', - event_title: '发布2024年中报', - event_type: '业绩公告', - event_desc: '上半年业绩稳定增长,各项经营指标符合预期', - impact_metrics: { impact_score: 75, is_positive: true }, - related_info: { financial_impact: '股价累计上涨2.8%' } - }, - { - event_date: '2024-06-15', - event_title: '新产品发布会', - event_type: '产品发布', - event_desc: '发布新一代产品系列,技术升级明显,市场反响良好', - impact_metrics: { impact_score: 70, is_positive: true }, - related_info: { financial_impact: '预计贡献增量收入10-15%' } - }, - { - event_date: '2024-05-20', - event_title: '原材料价格上涨', - event_type: '成本压力', - event_desc: '主要原材料价格上涨约8%,对毛利率形成一定压力', - impact_metrics: { impact_score: 55, is_positive: false }, - related_info: { financial_impact: '预计影响毛利率0.5-1个百分点' } - }, - { - event_date: '2024-04-28', - event_title: '发布2024年一季报', - event_type: '业绩公告', - event_desc: '一季度业绩实现开门红,主要指标好于市场预期', - impact_metrics: { impact_score: 78, is_positive: true }, - related_info: { financial_impact: '股价上涨2.2%' } - } - ] - } - }, - forecastReport: { - income_profit_trend: { - years: ['2020', '2021', '2022', '2023', '2024E', '2025E', '2026E'], - income: [baseRevenue * 0.6, baseRevenue * 0.7, baseRevenue * 0.8, baseRevenue * 0.9, baseRevenue, baseRevenue * 1.1, baseRevenue * 1.2], - profit: [baseProfit * 0.6, baseProfit * 0.7, baseProfit * 0.8, baseProfit * 0.9, baseProfit, baseProfit * 1.1, baseProfit * 1.2] - }, - growth_bars: { - years: ['2021', '2022', '2023', '2024E', '2025E', '2026E'], - revenue_growth_pct: [16.7, 14.3, 12.5, 11.1, 10.0, 9.1] - }, - eps_trend: { - years: ['2020', '2021', '2022', '2023', '2024E', '2025E', '2026E'], - eps: [0.45, 0.52, 0.60, 0.68, 0.76, 0.84, 0.92] - }, - pe_peg_axes: { - years: ['2020', '2021', '2022', '2023', '2024E', '2025E', '2026E'], - pe: [22.2, 19.2, 16.7, 14.7, 13.2, 11.9, 10.9], - peg: [1.33, 1.34, 1.34, 1.32, 1.32, 1.31, 1.20] - }, - detail_table: { - years: ['2020', '2021', '2022', '2023', '2024E', '2025E', '2026E'], - rows: [ - { '指标': '营业总收入(百万元)', '2020': baseRevenue * 0.6, '2021': baseRevenue * 0.7, '2022': baseRevenue * 0.8, '2023': baseRevenue * 0.9, '2024E': baseRevenue, '2025E': baseRevenue * 1.1, '2026E': baseRevenue * 1.2 }, - { '指标': '营收增长率(%)', '2020': '-', '2021': 16.7, '2022': 14.3, '2023': 12.5, '2024E': 11.1, '2025E': 10.0, '2026E': 9.1 }, - { '指标': '归母净利润(百万元)', '2020': baseProfit * 0.6, '2021': baseProfit * 0.7, '2022': baseProfit * 0.8, '2023': baseProfit * 0.9, '2024E': baseProfit, '2025E': baseProfit * 1.1, '2026E': baseProfit * 1.2 }, - { '指标': 'EPS(稀释,元)', '2020': 0.45, '2021': 0.52, '2022': 0.60, '2023': 0.68, '2024E': 0.76, '2025E': 0.84, '2026E': 0.92 }, - { '指标': 'ROE(%)', '2020': 12.5, '2021': 13.2, '2022': 13.8, '2023': 14.2, '2024E': 14.5, '2025E': 14.8, '2026E': 15.0 }, - { '指标': 'PE(倍)', '2020': 22.2, '2021': 19.2, '2022': 16.7, '2023': 14.7, '2024E': 13.2, '2025E': 11.9, '2026E': 10.9 } - ] - } - } - }; -}; diff --git a/src/mocks/data/events.js b/src/mocks/data/events.js deleted file mode 100644 index 6ad46e77..00000000 --- a/src/mocks/data/events.js +++ /dev/null @@ -1,1429 +0,0 @@ -// Mock 事件相关数据 - -// Mock 股票池 - 常见的A股股票 -const stockPool = [ - { stock_code: '600000.SH', stock_name: '浦发银行', industry: '银行' }, - { stock_code: '600519.SH', stock_name: '贵州茅台', industry: '白酒' }, - { stock_code: '600036.SH', stock_name: '招商银行', industry: '银行' }, - { stock_code: '601318.SH', stock_name: '中国平安', industry: '保险' }, - { stock_code: '600016.SH', stock_name: '民生银行', industry: '银行' }, - { stock_code: '601398.SH', stock_name: '工商银行', industry: '银行' }, - { stock_code: '601288.SH', stock_name: '农业银行', industry: '银行' }, - { stock_code: '601166.SH', stock_name: '兴业银行', industry: '银行' }, - { stock_code: '000001.SZ', stock_name: '平安银行', industry: '银行' }, - { stock_code: '000002.SZ', stock_name: '万科A', industry: '房地产' }, - { stock_code: '000858.SZ', stock_name: '五粮液', industry: '白酒' }, - { stock_code: '000333.SZ', stock_name: '美的集团', industry: '家电' }, - { stock_code: '002415.SZ', stock_name: '海康威视', industry: '安防' }, - { stock_code: '002594.SZ', stock_name: 'BYD比亚迪', industry: '新能源汽车' }, - { stock_code: '300750.SZ', stock_name: '宁德时代', industry: '新能源' }, - { stock_code: '300059.SZ', stock_name: '东方财富', industry: '证券' }, - { stock_code: '601888.SH', stock_name: '中国中免', industry: '免税' }, - { stock_code: '600276.SH', stock_name: '恒瑞医药', industry: '医药' }, - { stock_code: '600887.SH', stock_name: '伊利股份', industry: '乳制品' }, - { stock_code: '601012.SH', stock_name: '隆基绿能', industry: '光伏' }, - { stock_code: '688981.SH', stock_name: '中芯国际', industry: '半导体' }, - { stock_code: '600309.SH', stock_name: '万华化学', industry: '化工' }, - { stock_code: '603259.SH', stock_name: '药明康德', industry: '医药研发' }, - { stock_code: '002475.SZ', stock_name: '立讯精密', industry: '电子' }, - { stock_code: '000063.SZ', stock_name: '中兴通讯', industry: '通信设备' }, -]; - -// 关联描述模板 - 更详细和专业的描述 -const relationDescTemplates = [ - '主营业务直接相关,预计受事件影响较大。公司在该领域拥有领先的市场地位,事件催化有望带动业绩增长。', - '产业链上游核心供应商,间接受益明显。随着下游需求提升,公司产品销量和价格有望双升。', - '产业链下游龙头企业,需求端直接受益。事件将推动行业景气度提升,公司订单量预计大幅增长。', - '同行业竞争对手,市场份额有望提升。行业整体向好背景下,公司凭借技术优势可能获得更多市场机会。', - '参股该领域优质企业,投资收益可期。被投企业在事件催化下估值提升,将为公司带来可观的投资回报。', - '业务板块深度布局相关领域,多项产品受益。公司提前布局的战略眼光将在此次事件中得到验证。', - '控股子公司主营相关业务,贡献利润占比较高。子公司业绩改善将直接提升上市公司整体盈利能力。', - '近期公告加大投资布局该领域,潜在受益标的。公司前瞻性布局正逢政策东风,项目落地进度有望加快。', - '行业绝对龙头,市场关注度极高。事件影响下,资金有望持续流入,股价弹性较大。', - '技术储备充足且研发投入领先,有望抢占市场先机。公司多项核心技术处于行业领先地位。', - '已有成熟产品线和完善销售渠道,短期内可实现业绩兑现。公司产能充足,可快速响应市场需求。', - '战略转型方向高度契合事件主题,转型进程有望提速。管理层明确表态将加大相关业务投入力度。', - '拥有稀缺资源或独家技术,竞争壁垒显著。事件催化下,公司核心竞争优势将进一步凸显。', - '区域市场领导者,地方政策支持力度大。公司深耕区域市场多年,具备先发优势和政府资源。', - '新增产能即将释放,业绩拐点临近。事件催化恰逢产能爬坡期,盈利能力有望超预期。', - '与行业巨头建立战略合作,订单保障充足。大客户资源优势明显,业务增长确定性强。', -]; - -// 模拟作者列表 -const authorPool = [ - "张明", "李华", "王芳", "陈强", "赵磊", "孙杰", "周磊", "吴洋", - "刘畅", "林芳", "郑华", "钱敏", "张敏", "赵强", "张华", "李明" -]; - -// 生成随机关联股票数据 -export function generateRelatedStocks(eventId, count = 5) { - // 使用事件ID作为随机种子,确保相同事件ID返回相同的股票列表 - const seed = parseInt(eventId) || 1; - const selectedStocks = []; - - // 伪随机选择股票(基于事件ID) - for (let i = 0; i < Math.min(count, stockPool.length); i++) { - const index = (seed * 17 + i * 13) % stockPool.length; - const stock = stockPool[index]; - const descIndex = (seed * 7 + i * 11) % relationDescTemplates.length; - const authorIndex1 = (seed * 3 + i * 5) % authorPool.length; - const authorIndex2 = (seed * 5 + i * 7) % authorPool.length; - - // 获取模板文本 - const templateText = relationDescTemplates[descIndex]; - - // 将模板文本分成两部分作为query_part - const sentences = templateText.split('。'); - const queryPart1 = sentences[0] || templateText.substring(0, 30); - const queryPart2 = sentences[1] || templateText.substring(30, 60); - - // 生成随机日期(基于seed) - const baseDate = new Date('2025-08-01'); - const daysOffset1 = (seed * i * 3) % 30; - const daysOffset2 = (seed * i * 5) % 30; - const date1 = new Date(baseDate); - date1.setDate(date1.getDate() + daysOffset1); - const date2 = new Date(baseDate); - date2.setDate(date2.getDate() + daysOffset2); - - selectedStocks.push({ - stock_code: stock.stock_code, - stock_name: stock.stock_name, - relation_desc: { - data: [ - { - author: authorPool[authorIndex1], - sentences: sentences[0] + '。' + (sentences[1] || ''), - query_part: queryPart1, - match_score: i < 2 ? "优" : "良", - declare_date: date1.toISOString(), - report_title: `${stock.stock_name}:行业分析与投资价值研究-深度报告` - }, - { - author: authorPool[authorIndex2], - sentences: sentences.slice(2).join('。') || templateText, - query_part: queryPart2 || '政策催化,市场关注度提升', - match_score: i < 3 ? "优" : "良", - declare_date: date2.toISOString(), - report_title: `${stock.industry}行业:事件驱动下的投资机会分析` - } - ] - }, - industry: stock.industry, - // 可选字段 - 用于前端显示更多信息 - relevance_score: Math.max(60, 100 - i * 8), // 相关性评分,递减 - impact_level: i < 2 ? 'high' : i < 4 ? 'medium' : 'low', // 影响程度 - }); - } - - return selectedStocks; -} - -// Mock 事件相关股票数据映射 -// 这里可以为特定事件ID预设特定的股票列表 -export const mockEventStocks = { - // 示例:事件ID为1的预设股票(消费刺激政策) - '1': [ - { - stock_code: '600519.SH', - stock_name: '贵州茅台', - relation_desc: { - data: [ - { - author: "张明", - sentences: "贵州茅台作为白酒行业绝对龙头,品牌溢价能力强,提价预期明确。2024年产能持续释放,叠加渠道库存处于低位,业绩增长确定性高。", - query_part: "白酒行业绝对龙头,高端消费代表性标的", - match_score: "优", - declare_date: "2025-03-15T00:00:00", - report_title: "贵州茅台:高端白酒龙头,消费复苏核心受益标的-深度报告" - }, - { - author: "李华", - sentences: "消费刺激政策将直接提振高端白酒需求,茅台作为高端消费代表性品牌,需求弹性大,定价权强。机构持仓集中度高,资金关注度极高。", - query_part: "消费刺激政策直接受益,品牌溢价能力行业领先", - match_score: "优", - declare_date: "2025-03-20T00:00:00", - report_title: "白酒行业:消费政策催化,高端白酒迎来配置良机" - } - ] - }, - industry: '白酒', - relevance_score: 95, - impact_level: 'high', - }, - { - stock_code: '000858.SZ', - stock_name: '五粮液', - relation_desc: { - data: [ - { - author: "王芳", - sentences: "五粮液作为白酒行业第二梯队领军企业,产品矩阵完善,中高端产品结构优化进程加快。管理层改革成效显著,渠道改革红利持续释放。", - query_part: "白酒行业第二梯队领军企业,产品矩阵完善", - match_score: "良", - declare_date: "2025-03-18T00:00:00", - report_title: "五粮液:改革红利释放,次高端市场份额稳步提升" - }, - { - author: "陈强", - sentences: "消费复苏背景下,五粮液次高端市场份额稳步提升。估值修复空间较大,股价弹性优于行业平均水平。", - query_part: "消费复苏受益明显,估值修复空间大", - match_score: "良", - declare_date: "2025-03-22T00:00:00", - report_title: "白酒行业复盘:次高端品牌估值修复进行时" - } - ] - }, - industry: '白酒', - relevance_score: 90, - impact_level: 'high', - }, - { - stock_code: '600887.SH', - stock_name: '伊利股份', - relation_desc: '乳制品行业龙头,市占率稳居第一。消费品类中必选消费属性强,受政策刺激需求提升明显。公司全国化布局完善,冷链物流体系成熟,新品推出节奏加快。常温、低温双轮驱动,盈利能力持续改善。分红率稳定,股息收益率具有吸引力。', - industry: '乳制品', - relevance_score: 82, - impact_level: 'medium', - }, - { - stock_code: '000333.SZ', - stock_name: '美的集团', - relation_desc: '家电行业龙头,以旧换新政策核心受益标的。公司产品线覆盖全品类,渠道布局线上线下协同优势明显。智能家居战略推进顺利,高端化、国际化双线并进。成本控制能力行业领先,ROE水平稳定在20%以上。', - industry: '家电', - relevance_score: 78, - impact_level: 'medium', - }, - ], - - // 事件ID为2的预设股票(AI人工智能发展政策) - '2': [ - { - stock_code: '002415.SZ', - stock_name: '海康威视', - relation_desc: { - data: [ - { - author: "赵敏", - sentences: "海康威视作为AI+安防龙头企业,智能视觉技术全球领先。公司AI芯片、算法、云平台业务增长迅速,研发投入占比保持10%以上。", - query_part: "AI+安防龙头企业,智能视觉技术全球领先", - match_score: "优", - declare_date: "2025-04-10T00:00:00", - report_title: "海康威视:AI赋能安防,技术护城河持续加深-深度报告" - }, - { - author: "孙杰", - sentences: "人工智能政策支持下,智慧城市、智能交通等政府项目订单充足。海外市场拓展提速,国际化战略成效显著。", - query_part: "人工智能政策支持,政府项目订单充足", - match_score: "优", - declare_date: "2025-04-12T00:00:00", - report_title: "AI产业链深度:政策催化下的投资机遇分析" - } - ] - }, - industry: '安防', - relevance_score: 92, - impact_level: 'high', - }, - { - stock_code: '000063.SZ', - stock_name: '中兴通讯', - relation_desc: { - data: [ - { - author: "周磊", - sentences: "中兴通讯作为5G通信设备商,是算力网络建设核心受益者。AI大模型训练和推理需要海量算力支撑,公司服务器、交换机等产品需求激增。", - query_part: "5G通信设备商,算力网络建设核心受益者", - match_score: "优", - declare_date: "2025-04-08T00:00:00", - report_title: "中兴通讯:算力基础设施建设加速,订单饱满-点评报告" - }, - { - author: "吴洋", - sentences: "运营商资本开支回暖,5G-A、算力网络投资加速。国产替代进程加快,中兴通讯市场份额持续提升,盈利能力改善明显。", - query_part: "国产替代加速,市场份额持续提升", - match_score: "良", - declare_date: "2025-04-15T00:00:00", - report_title: "通信设备行业:运营商资本开支拐点已现" - }, - { - author: "刘畅", - sentences: "AI产业链中,算力基础设施投资是重中之重。中兴通讯在数据中心交换机、服务器等领域布局完善,技术实力强劲。", - query_part: "AI算力基础设施投资核心标的", - match_score: "优", - declare_date: "2025-04-18T00:00:00", - report_title: "AI算力产业链投资机会深度解析" - } - ] - }, - industry: '通信设备', - relevance_score: 88, - impact_level: 'high', - }, - { - stock_code: '688981.SH', - stock_name: '中芯国际', - relation_desc: '国内半导体制造龙头,AI芯片代工核心标的。人工智能发展带动高端芯片需求爆发,公司先进制程产能利用率高位运行。政策支持力度空前,产业基金持续注资,扩产进度超预期。国产替代空间巨大,长期成长确定性强。', - industry: '半导体', - relevance_score: 85, - impact_level: 'high', - }, - { - stock_code: '002475.SZ', - stock_name: '立讯精密', - relation_desc: '精密制造龙头,AI终端设备供应链核心企业。AI眼镜、AI手机等新型终端设备放量,公司作为苹果、Meta等巨头供应商直接受益。自动化生产水平行业领先,成本优势明显。新业务拓展顺利,汽车电子、服务器连接器增长快速。', - industry: '电子', - relevance_score: 80, - impact_level: 'medium', - }, - ], - - // 事件ID为3的预设股票(新能源汽车补贴延续) - '3': [ - { - stock_code: '300750.SZ', - stock_name: '宁德时代', - relation_desc: { - data: [ - { - author: "张华", - sentences: "宁德时代作为全球动力电池绝对龙头,市占率超35%,技术路线覆盖三元、磷酸铁锂、钠电池等全品类。客户资源优质,特斯拉、比亚迪等头部车企深度绑定。", - query_part: "动力电池绝对龙头,全球市占率超35%", - match_score: "优", - declare_date: "2025-05-10T00:00:00", - report_title: "宁德时代:全球动力电池龙头,新能源汽车核心受益标的" - }, - { - author: "李明", - sentences: "新能源汽车补贴延续政策出台,将直接刺激终端需求,宁德时代作为产业链核心环节,电池出货量有望大幅提升。储能业务高速增长,打开第二增长曲线。", - query_part: "补贴政策核心受益,储能业务打开第二曲线", - match_score: "优", - declare_date: "2025-05-12T00:00:00", - report_title: "新能源汽车产业链:补贴延续下的投资机遇" - } - ] - }, - industry: '新能源', - relevance_score: 98, - impact_level: 'high', - }, - { - stock_code: '002594.SZ', - stock_name: 'BYD比亚迪', - relation_desc: { - data: [ - { - author: "王芳", - sentences: "比亚迪月销量持续突破30万辆,市占率稳步提升。王朝、海洋、腾势三大品牌矩阵完善,价格带覆盖10-50万元,产品竞争力强。", - query_part: "新能源汽车销量冠军,月销超30万辆", - match_score: "优", - declare_date: "2025-05-08T00:00:00", - report_title: "比亚迪:新能源汽车销量王者,产业链垂直整合优势显著" - }, - { - author: "陈强", - sentences: "电池、电机、电控自主可控,成本优势明显。出海战略推进顺利,欧洲、东南亚市场表现亮眼,国际化进程加速。", - query_part: "垂直整合成本优势,出海战略成效显著", - match_score: "良", - declare_date: "2025-05-15T00:00:00", - report_title: "比亚迪国际化战略深度解析" - } - ] - }, - industry: '新能源汽车', - relevance_score: 95, - impact_level: 'high', - }, - { - stock_code: '601012.SH', - stock_name: '隆基绿能', - relation_desc: { - data: [ - { - author: "赵磊", - sentences: "隆基绿能作为光伏组件龙头,单晶硅片市占率第一。BC电池技术领先,产品溢价能力强,一体化产能布局完善。", - query_part: "光伏组件龙头,BC电池技术领先", - match_score: "良", - declare_date: "2025-05-05T00:00:00", - report_title: "隆基绿能:光伏技术引领者,一体化优势突出" - }, - { - author: "孙杰", - sentences: "新能源汽车补贴延续,带动绿色能源需求增长。隆基海外收入占比超50%,全球化布局分散风险,盈利稳定性强。", - query_part: "绿色能源需求增长,全球化布局优势", - match_score: "良", - declare_date: "2025-05-18T00:00:00", - report_title: "光伏行业:新能源政策催化下的配置机会" - } - ] - }, - industry: '光伏', - relevance_score: 85, - impact_level: 'medium', - }, - { - stock_code: '688187.SH', - stock_name: '天齐锂业', - relation_desc: { - data: [ - { - author: "刘畅", - sentences: "天齐锂业拥有优质锂矿资源,锂资源自给率高,成本优势显著。澳洲、智利矿山产能稳定,国内锂盐产能持续扩张。", - query_part: "锂资源龙头,优质矿山资源储备充足", - match_score: "优", - declare_date: "2025-05-20T00:00:00", - report_title: "天齐锂业:锂资源龙头,成本优势突出" - }, - { - author: "吴洋", - sentences: "新能源汽车、储能需求增长带动锂盐价格中枢上移。锂价回暖周期开启,天齐锂业业绩弹性巨大,是锂价上行核心受益标的。", - query_part: "锂价回暖周期受益,业绩弹性大", - match_score: "优", - declare_date: "2025-05-22T00:00:00", - report_title: "锂行业:供需格局改善,价格拐点已现" - } - ] - }, - industry: '有色金属', - relevance_score: 82, - impact_level: 'high', - }, - ], - - // 事件ID为4的预设股票(医药创新支持政策) - '4': [ - { - stock_code: '600276.SH', - stock_name: '恒瑞医药', - relation_desc: { - data: [ - { - author: "周磊", - sentences: "恒瑞医药作为创新药龙头,研发管线最丰富,涵盖肿瘤、麻醉、造影等多领域。PD-1、PARP抑制剂等重磅产品进入收获期,放量迅速。", - query_part: "创新药龙头,研发管线最丰富", - match_score: "优", - declare_date: "2025-06-10T00:00:00", - report_title: "恒瑞医药:创新药进入收获期,业绩拐点显现" - }, - { - author: "钱敏", - sentences: "创新药政策支持力度加大,集采影响逐步消化。恒瑞研发投入占比超20%,海外授权合作频繁,国际化进程加速。", - query_part: "政策支持加码,国际化进程加速", - match_score: "优", - declare_date: "2025-06-12T00:00:00", - report_title: "医药创新政策解读:龙头企业核心受益" - } - ] - }, - industry: '医药', - relevance_score: 93, - impact_level: 'high', - }, - { - stock_code: '603259.SH', - stock_name: '药明康德', - relation_desc: { - data: [ - { - author: "孙杰", - sentences: "药明康德作为CRO/CDMO龙头,全球制药产业链核心服务商。客户覆盖全球TOP20药企,粘性强,订单饱满。一体化平台优势明显。", - query_part: "CRO/CDMO龙头,全球制药核心服务商", - match_score: "优", - declare_date: "2025-06-08T00:00:00", - report_title: "药明康德:CRO龙头地位稳固,订单饱满" - }, - { - author: "林芳", - sentences: "创新药研发投入增加,外包渗透率提升。药明从研发到商业化全流程服务能力强,海外收入占比高,人民币贬值受益。", - query_part: "外包渗透率提升,全流程服务优势", - match_score: "良", - declare_date: "2025-06-15T00:00:00", - report_title: "CRO行业:创新药外包需求持续增长" - } - ] - }, - industry: '医药研发', - relevance_score: 90, - impact_level: 'high', - }, - { - stock_code: '300760.SZ', - stock_name: '迈瑞医疗', - relation_desc: { - data: [ - { - author: "赵强", - sentences: "迈瑞医疗产品线覆盖生命信息与支持、体外诊断、医学影像三大领域。高端医疗设备国产替代加速,市占率持续提升。", - query_part: "医疗器械龙头,国产替代加速", - match_score: "良", - declare_date: "2025-06-05T00:00:00", - report_title: "迈瑞医疗:医疗器械龙头,国产化进程提速" - }, - { - author: "吴洋", - sentences: "海外市场突破进展顺利,进入更多顶级医院。研发能力强,新品推出节奏加快,盈利能力稳定,现金流充沛。", - query_part: "海外突破顺利,研发能力强劲", - match_score: "良", - declare_date: "2025-06-18T00:00:00", - report_title: "医疗器械行业:国产品牌全球化加速" - } - ] - }, - industry: '医疗器械', - relevance_score: 87, - impact_level: 'medium', - }, - ], - - // 事件ID为5的预设股票(数字经济发展规划) - '5': [ - { - stock_code: '300059.SZ', - stock_name: '东方财富', - relation_desc: { - data: [ - { - author: "郑华", - sentences: "东方财富作为互联网金融龙头,流量优势显著,APP月活超亿。券商、基金代销、数据服务多业务协同,形成完整生态闭环。", - query_part: "互联网金融龙头,流量优势显著", - match_score: "优", - declare_date: "2025-07-10T00:00:00", - report_title: "东方财富:互联网金融龙头,生态优势突出" - }, - { - author: "刘明", - sentences: "数字经济发展推动线上理财渗透率提升。市场交易活跃度提升,佣金收入和利息收入双增长,低成本负债优势明显,ROE水平行业领先。", - query_part: "数字经济受益,线上理财渗透率提升", - match_score: "优", - declare_date: "2025-07-12T00:00:00", - report_title: "数字经济政策解读:互联网金融核心受益" - } - ] - }, - industry: '证券', - relevance_score: 88, - impact_level: 'high', - }, - { - stock_code: '002410.SZ', - stock_name: '广联达', - relation_desc: { - data: [ - { - author: "张敏", - sentences: "广联达作为建筑信息化龙头,工程造价软件市占率第一。云转型进入收获期,订阅模式收入占比提升,现金流改善明显。", - query_part: "建筑信息化龙头,云转型收获期", - match_score: "良", - declare_date: "2025-07-08T00:00:00", - report_title: "广联达:建筑信息化龙头,云转型成效显著" - }, - { - author: "李芳", - sentences: "数字化转型加速,建筑行业信息化需求旺盛。施工、设计等新业务拓展顺利,成长空间广阔,政策支持力度大,行业壁垒高。", - query_part: "数字化转型加速,新业务拓展顺利", - match_score: "良", - declare_date: "2025-07-15T00:00:00", - report_title: "建筑信息化:数字经济下的产业升级机遇" - } - ] - }, - industry: '软件', - relevance_score: 85, - impact_level: 'medium', - }, - ], -}; - -// 获取事件相关股票 -export function getEventRelatedStocks(eventId) { - // 优先返回预设的股票列表 - if (mockEventStocks[eventId]) { - return mockEventStocks[eventId]; - } - - // 否则生成随机股票列表(3-6只股票) - const count = 3 + (parseInt(eventId) % 4); - return generateRelatedStocks(eventId, count); -} - -// ==================== Mock 事件列表数据 ==================== - -// 事件类型池 -const eventTypes = ['政策发布', '行业动向', '公司公告', '市场研判', '技术突破', '财报发布', '投融资', '高管变动']; - -// 行业池 -const industries = ['半导体', '新能源', '人工智能', '医药', '消费', '金融', '房地产', '通信', '互联网', '军工', '化工', '机械']; - -// 事件标题模板 -const eventTitleTemplates = [ - '{industry}行业迎来重大政策利好中国物流与采购联合会发布《国有企业采购业务监督指南》团体标准', - '{company}发布{quarter}财报,业绩超预期', - '{industry}板块集体大涨,{company}涨停', - '央行宣布{policy},影响{industry}行业', - '{company}与{partner}达成战略合作', - '{industry}技术取得重大突破', - '{company}拟投资{amount}亿元布局{industry}', - '国家发改委:支持{industry}产业发展', - '{industry}龙头{company}涨价{percent}%', - '{company}回购股份,彰显信心', -]; - -// 生成随机公司名 -function generateCompanyName(industry) { - const prefixes = ['华为', '中兴', '阿里', '腾讯', '比亚迪', '宁德时代', '隆基', '恒瑞', '茅台', '五粮液', '海康', '中芯']; - const suffixes = ['科技', '集团', '股份', '控股', '实业', '']; - const prefix = prefixes[Math.floor(Math.random() * prefixes.length)]; - const suffix = suffixes[Math.floor(Math.random() * suffixes.length)]; - return `${prefix}${suffix}`; -} - -// 生成事件标题 -function generateEventTitle(industry, seed) { - const template = eventTitleTemplates[seed % eventTitleTemplates.length]; - return template - .replace('{industry}', industry) - .replace('{company}', generateCompanyName(industry)) - .replace('{partner}', generateCompanyName(industry)) - .replace('{quarter}', ['一季度', '半年度', '三季度', '年度'][seed % 4]) - .replace('{policy}', ['降准0.5%', '降息25BP', 'MLF下调', '提高赤字率'][seed % 4]) - .replace('{amount}', [50, 100, 200, 500][seed % 4]) - .replace('{percent}', [5, 10, 15, 20][seed % 4]); -} - -// 生成事件描述 -function generateEventDescription(industry, importance, seed) { - const impacts = { - S: '重大利好,预计将对行业格局产生深远影响,相关概念股有望持续受益。机构预计该事件将带动行业整体估值提升15-20%,龙头企业市值增长空间广阔。', - A: '重要利好,市场情绪积极,短期内资金流入明显。分析师普遍认为该事件将推动行业景气度上行,相关公司业绩有望超预期增长。', - B: '中性偏好,对部分细分领域有一定促进作用。虽然不是行业性机会,但优质标的仍有结构性行情,建议关注业绩确定性强的公司。', - C: '影响有限,市场反应平淡,但长期来看仍有积极意义。事件对行业发展方向有指引作用,关注后续政策跟进和落地情况。', - }; - - const details = [ - `根据最新消息,${industry}领域将获得新一轮政策支持,产业链相关企业订单饱满。`, - `${industry}板块近期表现活跃,多只个股创出年内新高,资金持续流入。`, - `行业专家指出,${industry}产业正处于高速发展期,市场空间广阔,龙头企业优势明显。`, - `券商研报显示,${industry}行业估值处于历史低位,当前具备较高配置价值。`, - ]; - - return impacts[importance] + details[seed % details.length]; -} - -// 概念到层级结构的映射(模拟真实 API 的 concept_hierarchy) -const conceptHierarchyMap = { - // 人工智能主线 - '大模型': { lv1: '人工智能', lv1_id: 'AI', lv2: 'AI大模型与应用', lv2_id: 'AI_MODEL_APP', lv3: 'AI大模型', lv3_id: 'AI_LLM' }, - 'AI应用': { lv1: '人工智能', lv1_id: 'AI', lv2: 'AI大模型与应用', lv2_id: 'AI_MODEL_APP', lv3: 'AI应用场景', lv3_id: 'AI_APP' }, - '算力': { lv1: '人工智能', lv1_id: 'AI', lv2: 'AI算力与基础设施', lv2_id: 'AI_INFRA', lv3: 'AI芯片与硬件', lv3_id: 'AI_CHIP' }, - '数据': { lv1: '人工智能', lv1_id: 'AI', lv2: 'AI大模型与应用', lv2_id: 'AI_MODEL_APP', lv3: '数据要素', lv3_id: 'DATA' }, - '机器学习': { lv1: '人工智能', lv1_id: 'AI', lv2: 'AI大模型与应用', lv2_id: 'AI_MODEL_APP', lv3: 'AI算法', lv3_id: 'AI_ALGO' }, - 'AI芯片': { lv1: '人工智能', lv1_id: 'AI', lv2: 'AI算力与基础设施', lv2_id: 'AI_INFRA', lv3: 'AI芯片与硬件', lv3_id: 'AI_CHIP' }, - // 半导体主线 - '芯片': { lv1: '半导体', lv1_id: 'SEMI', lv2: '芯片设计', lv2_id: 'CHIP_DESIGN', lv3: '芯片设计', lv3_id: 'CHIP' }, - '晶圆': { lv1: '半导体', lv1_id: 'SEMI', lv2: '芯片制造', lv2_id: 'CHIP_MFG', lv3: '晶圆代工', lv3_id: 'WAFER' }, - '封测': { lv1: '半导体', lv1_id: 'SEMI', lv2: '封装测试', lv2_id: 'PKG_TEST', lv3: '封装测试', lv3_id: 'PKG' }, - '国产替代': { lv1: '半导体', lv1_id: 'SEMI', lv2: '国产替代', lv2_id: 'DOMESTIC', lv3: '自主可控', lv3_id: 'SELF_CTRL' }, - // 新能源主线 - '电池': { lv1: '新能源', lv1_id: 'ENERGY', lv2: '新能源汽车', lv2_id: 'EV', lv3: '动力电池', lv3_id: 'BATTERY' }, - '光伏': { lv1: '新能源', lv1_id: 'ENERGY', lv2: '光伏产业', lv2_id: 'SOLAR', lv3: '光伏组件', lv3_id: 'PV_MODULE' }, - '储能': { lv1: '新能源', lv1_id: 'ENERGY', lv2: '储能产业', lv2_id: 'ESS', lv3: '电化学储能', lv3_id: 'ESS_CHEM' }, - '新能源车': { lv1: '新能源', lv1_id: 'ENERGY', lv2: '新能源汽车', lv2_id: 'EV', lv3: '整车制造', lv3_id: 'EV_OEM' }, - '锂电': { lv1: '新能源', lv1_id: 'ENERGY', lv2: '新能源汽车', lv2_id: 'EV', lv3: '锂电池材料', lv3_id: 'LI_MATERIAL' }, - // 医药主线 - '创新药': { lv1: '医药生物', lv1_id: 'PHARMA', lv2: '创新药', lv2_id: 'INNOV_DRUG', lv3: '创新药研发', lv3_id: 'DRUG_RD' }, - 'CRO': { lv1: '医药生物', lv1_id: 'PHARMA', lv2: '医药服务', lv2_id: 'PHARMA_SVC', lv3: 'CRO/CDMO', lv3_id: 'CRO' }, - '医疗器械': { lv1: '医药生物', lv1_id: 'PHARMA', lv2: '医疗器械', lv2_id: 'MED_DEVICE', lv3: '高端器械', lv3_id: 'HI_DEVICE' }, - '生物制药': { lv1: '医药生物', lv1_id: 'PHARMA', lv2: '生物制药', lv2_id: 'BIO_PHARMA', lv3: '生物药', lv3_id: 'BIO_DRUG' }, - '仿制药': { lv1: '医药生物', lv1_id: 'PHARMA', lv2: '仿制药', lv2_id: 'GENERIC', lv3: '仿制药', lv3_id: 'GEN_DRUG' }, - // 消费主线 - '白酒': { lv1: '消费', lv1_id: 'CONSUMER', lv2: '食品饮料', lv2_id: 'FOOD_BEV', lv3: '白酒', lv3_id: 'BAIJIU' }, - '食品': { lv1: '消费', lv1_id: 'CONSUMER', lv2: '食品饮料', lv2_id: 'FOOD_BEV', lv3: '食品加工', lv3_id: 'FOOD' }, - '家电': { lv1: '消费', lv1_id: 'CONSUMER', lv2: '家电', lv2_id: 'HOME_APPL', lv3: '白色家电', lv3_id: 'WHITE_APPL' }, - '零售': { lv1: '消费', lv1_id: 'CONSUMER', lv2: '商贸零售', lv2_id: 'RETAIL', lv3: '零售连锁', lv3_id: 'CHAIN' }, - '免税': { lv1: '消费', lv1_id: 'CONSUMER', lv2: '商贸零售', lv2_id: 'RETAIL', lv3: '免税', lv3_id: 'DUTY_FREE' }, - // 通用概念(分配到多个主线) - '政策': { lv1: '宏观政策', lv1_id: 'MACRO', lv2: '产业政策', lv2_id: 'POLICY', lv3: null, lv3_id: null }, - '利好': { lv1: '市场情绪', lv1_id: 'SENTIMENT', lv2: '利好因素', lv2_id: 'POSITIVE', lv3: null, lv3_id: null }, - '业绩': { lv1: '基本面', lv1_id: 'FUNDAMENTAL', lv2: '业绩增长', lv2_id: 'EARNINGS', lv3: null, lv3_id: null }, - '涨停': { lv1: '市场情绪', lv1_id: 'SENTIMENT', lv2: '涨停板', lv2_id: 'LIMIT_UP', lv3: null, lv3_id: null }, - '龙头': { lv1: '投资策略', lv1_id: 'STRATEGY', lv2: '龙头股', lv2_id: 'LEADER', lv3: null, lv3_id: null }, - '突破': { lv1: '技术面', lv1_id: 'TECHNICAL', lv2: '技术突破', lv2_id: 'BREAKOUT', lv3: null, lv3_id: null }, - '合作': { lv1: '公司动态', lv1_id: 'CORP_ACTION', lv2: '战略合作', lv2_id: 'PARTNERSHIP', lv3: null, lv3_id: null }, - '投资': { lv1: '公司动态', lv1_id: 'CORP_ACTION', lv2: '投资并购', lv2_id: 'MA', lv3: null, lv3_id: null }, -}; - -// 生成关键词(对象数组格式,包含完整信息) -function generateKeywords(industry, seed) { - const commonKeywords = ['政策', '利好', '业绩', '涨停', '龙头', '突破', '合作', '投资']; - const industryKeywords = { - '半导体': ['芯片', '晶圆', '封测', 'AI芯片', '国产替代'], - '新能源': ['电池', '光伏', '储能', '新能源车', '锂电'], - '人工智能': ['大模型', 'AI应用', '算力', '数据', '机器学习'], - '医药': ['创新药', 'CRO', '医疗器械', '生物制药', '仿制药'], - '消费': ['白酒', '食品', '家电', '零售', '免税'], - }; - - // 概念描述模板 - const descriptionTemplates = { - '政策': '政策性利好消息对相关行业和板块产生积极影响,带动市场情绪和资金流向。', - '利好': '市场积极因素推动相关板块上涨,投资者情绪乐观,资金持续流入。', - '业绩': '公司业绩超预期增长,盈利能力提升,市场给予更高估值预期。', - '涨停': '强势涨停板显示市场热度,短期资金追捧,板块效应明显。', - '龙头': '行业龙头企业具备竞争优势,市场地位稳固,带动板块走势。', - '突破': '技术面或基本面出现重大突破,打开上涨空间,吸引资金关注。', - '合作': '战略合作为公司带来新的增长点,业务协同效应显著。', - '投资': '重大投资项目落地,长期发展空间广阔,市场预期良好。', - '芯片': '国产芯片替代加速,自主可控需求强烈,政策和资金支持力度大。', - '晶圆': '晶圆产能紧张,供需关系改善,相关企业盈利能力提升。', - '封测': '封测环节景气度上行,订单饱满,产能利用率提高。', - 'AI芯片': '人工智能快速发展带动AI芯片需求爆发,市场空间巨大。', - '国产替代': '国产替代进程加速,政策扶持力度大,进口依赖度降低。', - '电池': '新能源汽车渗透率提升,动力电池需求旺盛,技术迭代加快。', - '光伏': '光伏装机量快速增长,成本持续下降,行业景气度维持高位。', - '储能': '储能市场爆发式增长,政策支持力度大,应用场景不断拓展。', - '新能源车': '新能源汽车销量高增长,渗透率持续提升,产业链受益明显。', - '锂电': '锂电池技术进步,成本优势扩大,下游应用领域持续扩张。', - '大模型': '大语言模型技术突破,商业化进程加速,应用场景广阔。', - 'AI应用': '人工智能应用落地加速,垂直领域渗透率提升,市场空间巨大。', - '算力': '算力需求持续增长,数据中心建设加速,相关产业链受益。', - '数据': '数据要素市场化改革推进,数据价值释放,相关企业盈利模式清晰。', - '机器学习': '机器学习技术成熟,应用场景丰富,商业价值逐步显现。', - '创新药': '创新药研发管线丰富,商业化进程顺利,市场给予高估值。', - 'CRO': 'CRO行业高景气,订单充足,盈利能力稳定增长。', - '医疗器械': '医疗器械国产化率提升,技术创新加快,市场份额扩大。', - '生物制药': '生物制药技术突破,产品管线丰富,商业化前景广阔。', - '仿制药': '仿制药集采常态化,质量优势企业市场份额提升。', - '白酒': '白酒消费升级,高端产品量价齐升,龙头企业护城河深厚。', - '食品': '食品饮料需求稳定,品牌力强的企业市场份额持续提升。', - '家电': '家电消费需求回暖,智能化升级带动产品结构优化。', - '零售': '零售行业数字化转型,线上线下融合,运营效率提升。', - '免税': '免税政策优化,消费回流加速,行业景气度上行。' - }; - - const keywordNames = [ - ...commonKeywords.slice(seed % 3, seed % 3 + 3), - ...(industryKeywords[industry] || []).slice(0, 2) - ].slice(0, 5); - - const matchTypes = ['hybrid_knn', 'keyword', 'semantic']; - - // 生成历史触发时间(3-5个历史日期) - const generateHappenedTimes = (baseSeed) => { - const times = []; - const count = 3 + (baseSeed % 3); // 3-5个时间点 - for (let i = 0; i < count; i++) { - const daysAgo = 30 + (baseSeed * 7 + i * 11) % 330; // 30-360天前 - const date = new Date(); - date.setDate(date.getDate() - daysAgo); - times.push(date.toISOString().split('T')[0]); - } - return times.sort().reverse(); // 降序排列 - }; - - // 生成核心相关股票(4-6只) - const generateRelatedStocks = (conceptName, baseSeed) => { - const stockCount = 4 + (baseSeed % 3); // 4-6只股票 - const selectedStocks = []; - - for (let i = 0; i < stockCount && i < stockPool.length; i++) { - const stockIndex = (baseSeed + i * 7) % stockPool.length; - const stock = stockPool[stockIndex]; - selectedStocks.push({ - stock_name: stock.stock_name, - stock_code: stock.stock_code, - reason: relationDescTemplates[(baseSeed + i) % relationDescTemplates.length], - change_pct: (Math.random() * 15 - 5).toFixed(2) // -5% ~ +10% - }); - } - - return selectedStocks; - }; - - // 将字符串数组转换为对象数组,匹配真实API数据结构 - return keywordNames.map((name, index) => { - const score = (70 + Math.floor((seed * 7 + index * 11) % 30)) / 100; // 0.70-0.99的分数 - const avgChangePct = (Math.random() * 15 - 5).toFixed(2); // -5% ~ +10% 的涨跌幅 - - // 获取概念的层级信息 - const hierarchy = conceptHierarchyMap[name] || { - lv1: industry || '其他', - lv1_id: 'OTHER', - lv2: '未分类', - lv2_id: 'UNCATEGORIZED', - lv3: null, - lv3_id: null - }; - - return { - concept: name, // 使用 concept 字段而不是 name - stock_count: 10 + Math.floor((seed + index) % 30), // 10-39个相关股票 - score: parseFloat(score.toFixed(2)), // 0-1之间的分数,而不是0-100 - description: descriptionTemplates[name] || `${name}相关概念,市场关注度较高,具有一定的投资价值。`, - price_info: { // 将 avg_change_pct 嵌套在 price_info 对象中 - avg_change_pct: parseFloat(avgChangePct) - }, - match_type: matchTypes[(seed + index) % 3], // 随机匹配类型 - happened_times: generateHappenedTimes(seed + index), // 历史触发时间 - stocks: generateRelatedStocks(name, seed + index), // 核心相关股票 - hierarchy: hierarchy // 层级信息(用于按主线分组) - }; - }); -} - -/** - * 生成 Mock 事件列表 - * @param {Object} params - 查询参数 - * @returns {Object} - {events: [], pagination: {}} - */ -export function generateMockEvents(params = {}) { - const { - page = 1, - per_page = 10, - sort = 'new', - importance = 'all', - date_range = '', - q = '', - industry_code = '', - stock_code = '', - } = params; - - // 生成200个事件用于测试(足够测试分页功能) - const totalEvents = 200; - const allEvents = []; - - const importanceLevels = ['S', 'A', 'B', 'C']; - const baseDate = new Date(); // 使用当前日期作为基准 - - for (let i = 0; i < totalEvents; i++) { - const industry = industries[i % industries.length]; - const imp = importanceLevels[i % importanceLevels.length]; - const eventType = eventTypes[i % eventTypes.length]; - - // 生成随机日期(最近30天内) - const createdAt = new Date(baseDate); - createdAt.setDate(createdAt.getDate() - (i % 30)); - - // 生成随机热度和收益率 - const hotScore = Math.max(50, 100 - i); - const relatedAvgChg = (Math.random() * 20 - 5).toFixed(2); // -5% 到 15% - const relatedMaxChg = (Math.random() * 30).toFixed(2); // 0% 到 30% - const relatedWeekChg = (Math.random() * 30 - 10).toFixed(2); // -10% 到 20% - - // 生成价格走势数据(前一天、当天、后一天) - const generatePriceTrend = (seed) => { - const basePrice = 10 + (seed % 90); // 基础价格 10-100 - const trend = []; - - // 前一天(5个数据点) - let price = basePrice; - for (let i = 0; i < 5; i++) { - price = price + (Math.random() - 0.5) * 0.5; - trend.push(parseFloat(price.toFixed(2))); - } - - // 当天(5个数据点) - for (let i = 0; i < 5; i++) { - price = price + (Math.random() - 0.4) * 0.8; // 轻微上涨趋势 - trend.push(parseFloat(price.toFixed(2))); - } - - // 后一天(5个数据点) - for (let i = 0; i < 5; i++) { - price = price + (Math.random() - 0.45) * 1.0; - trend.push(parseFloat(price.toFixed(2))); - } - - return trend; - }; - - // 为每个事件随机选择2-5个相关股票 - const relatedStockCount = 2 + (i % 4); // 2-5个股票 - const relatedStocks = []; - const industryStocks = stockPool.filter(s => s.industry === industry); - const addedStockCodes = new Set(); // 用于去重 - - // 优先选择同行业股票 - if (industryStocks.length > 0) { - for (let j = 0; j < Math.min(relatedStockCount, industryStocks.length); j++) { - const stock = industryStocks[j % industryStocks.length]; - if (!addedStockCodes.has(stock.stock_code)) { - const dailyChange = (Math.random() * 6 - 2).toFixed(2); // -2% ~ +4% - const weekChange = (Math.random() * 10 - 3).toFixed(2); // -3% ~ +7% - - relatedStocks.push({ - stock_name: stock.stock_name, - stock_code: stock.stock_code, - relation_desc: relationDescTemplates[(i + j) % relationDescTemplates.length], - daily_change: dailyChange, - week_change: weekChange, - price_trend: generatePriceTrend(i * 100 + j) - }); - addedStockCodes.add(stock.stock_code); - } - } - } - - // 如果同行业股票不够,从整个 stockPool 中补充 - let poolIndex = 0; - while (relatedStocks.length < relatedStockCount && poolIndex < stockPool.length) { - const randomStock = stockPool[poolIndex % stockPool.length]; - if (!addedStockCodes.has(randomStock.stock_code)) { - const dailyChange = (Math.random() * 6 - 2).toFixed(2); // -2% ~ +4% - const weekChange = (Math.random() * 10 - 3).toFixed(2); // -3% ~ +7% - - relatedStocks.push({ - stock_name: randomStock.stock_name, - stock_code: randomStock.stock_code, - relation_desc: relationDescTemplates[(i + poolIndex) % relationDescTemplates.length], - daily_change: dailyChange, - week_change: weekChange, - price_trend: generatePriceTrend(i * 100 + poolIndex) - }); - addedStockCodes.add(randomStock.stock_code); - } - poolIndex++; - } - - // 计算交易日期(模拟下一交易日,这里简单地加1天) - const tradingDate = new Date(createdAt); - tradingDate.setDate(tradingDate.getDate() + 1); - - // 生成投票数据 - const bullishCount = Math.floor(Math.random() * 100) + 5; - const bearishCount = Math.floor(Math.random() * 50) + 3; - - allEvents.push({ - id: i + 1, - title: generateEventTitle(industry, i), - description: generateEventDescription(industry, imp, i), - content: generateEventDescription(industry, imp, i), - event_type: eventType, - importance: imp, - status: 'published', - created_at: createdAt.toISOString(), - updated_at: createdAt.toISOString(), - trading_date: tradingDate.toISOString().split('T')[0], // YYYY-MM-DD 格式 - hot_score: hotScore, - view_count: Math.floor(Math.random() * 10000), - follower_count: Math.floor(Math.random() * 500) + 10, - bullish_count: bullishCount, - bearish_count: bearishCount, - user_vote: null, // 默认未投票 - related_avg_chg: parseFloat(relatedAvgChg), - related_max_chg: parseFloat(relatedMaxChg), - related_week_chg: parseFloat(relatedWeekChg), - expectation_score: Math.floor(Math.random() * 60) + 30, // 30-90 超预期得分 - keywords: generateKeywords(industry, i), - is_ai_generated: i % 4 === 0, // 25% 的事件是AI生成 - industry: industry, - related_stocks: relatedStocks, // 添加相关股票列表 - historical_events: generateHistoricalEvents(industry, i), // 添加历史事件 - transmission_chain: generateTransmissionChain(industry, i), // 添加传导链数据 - }); - } - - // 筛选 - let filteredEvents = allEvents; - - // 重要性筛选 - if (importance && importance !== 'all') { - filteredEvents = filteredEvents.filter(e => e.importance === importance); - } - - // 关键词搜索 - if (q) { - const query = q.toLowerCase(); - filteredEvents = filteredEvents.filter(e => - e.title.toLowerCase().includes(query) || - e.description.toLowerCase().includes(query) || - // keywords 是对象数组 { concept, score, ... },需要访问 concept 属性 - e.keywords.some(k => k.concept && k.concept.toLowerCase().includes(query)) || - // 搜索 related_stocks 中的股票名称和代码 - (e.related_stocks && e.related_stocks.some(stock => - (stock.stock_name && stock.stock_name.toLowerCase().includes(query)) || - (stock.stock_code && stock.stock_code.toLowerCase().includes(query)) - )) || - // 搜索行业 - (e.industry && e.industry.toLowerCase().includes(query)) - ); - - // 如果搜索结果为空,返回所有事件(宽松模式) - if (filteredEvents.length === 0) { - filteredEvents = allEvents; - } - } - - // 行业筛选 - if (industry_code) { - filteredEvents = filteredEvents.filter(e => - e.industry.includes(industry_code) || - // keywords 是对象数组 { concept, ... },需要检查 concept 属性 - e.keywords.some(k => k.concept && k.concept.includes(industry_code)) - ); - } - - // 股票代码筛选 - if (stock_code) { - // 移除可能的后缀 (.SH, .SZ) - const cleanStockCode = stock_code.replace(/\.(SH|SZ)$/, ''); - filteredEvents = filteredEvents.filter(e => { - if (!e.related_stocks || e.related_stocks.length === 0) { - return false; - } - // 检查事件的 related_stocks 中是否包含该股票代码 - // related_stocks 是对象数组 { stock_code, stock_name, ... } - return e.related_stocks.some(stock => { - const stockCodeStr = stock.stock_code || ''; - const cleanCode = stockCodeStr.replace(/\.(SH|SZ)$/, ''); - return cleanCode === cleanStockCode || stockCodeStr === stock_code; - }); - }); - } - - // 日期范围筛选 - if (date_range) { - const [startStr, endStr] = date_range.split(' 至 '); - if (startStr && endStr) { - const start = new Date(startStr); - const end = new Date(endStr); - filteredEvents = filteredEvents.filter(e => { - const eventDate = new Date(e.created_at); - return eventDate >= start && eventDate <= end; - }); - } - } - - // 排序 - if (sort === 'hot') { - filteredEvents.sort((a, b) => b.hot_score - a.hot_score); - } else if (sort === 'returns') { - filteredEvents.sort((a, b) => b.related_avg_chg - a.related_avg_chg); - } else { - // 默认按时间排序 (new) - filteredEvents.sort((a, b) => new Date(b.created_at) - new Date(a.created_at)); - } - - // 分页 - const start = (page - 1) * per_page; - const end = start + per_page; - const paginatedEvents = filteredEvents.slice(start, end); - - const totalPages = Math.ceil(filteredEvents.length / per_page); - return { - events: paginatedEvents, - pagination: { - page: page, - per_page: per_page, - total: filteredEvents.length, - pages: totalPages, // ← 对齐后端字段名 (was: total_pages) - has_prev: page > 1, // ← 对齐后端:是否有上一页 - has_next: page < totalPages // ← 对齐后端:是否有下一页 - }, - }; -} - -/** - * 生成热点事件 - * @param {number} limit - 返回数量 - * @returns {Array} - 热点事件列表 - */ -export function generateHotEvents(limit = 5) { - const { events } = generateMockEvents({ sort: 'hot', per_page: limit }); - return events; -} - -/** - * 生成热门关键词 - * @param {number} limit - 返回数量 - * @returns {Array} - 热门关键词列表 - */ -export function generatePopularKeywords(limit = 20) { - const allKeywords = [ - '人工智能', '芯片', '新能源', '锂电池', '光伏', '储能', - '消费', '白酒', '医药', 'CRO', '半导体', '国产替代', - '军工', '航空', '5G', '通信', '互联网', '云计算', - '大数据', '区块链', '元宇宙', '新基建', '数字经济', - ]; - - return allKeywords.slice(0, limit).map((keyword, index) => ({ - keyword, - count: Math.max(10, 100 - index * 3), - trend: index % 3 === 0 ? 'up' : index % 3 === 1 ? 'down' : 'stable', - })); -} - -/** - * 生成历史事件对比数据 - * @param {string} industry - 行业 - * @param {number} index - 索引 - * @returns {Array} - 历史事件列表 - */ -function generateHistoricalEvents(industry, index) { - const historicalCount = 3 + (index % 3); // 3-5个历史事件 - const historical = []; - const baseDate = new Date(); - - for (let i = 0; i < historicalCount; i++) { - // 生成过去1-6个月的随机时间 - const monthsAgo = 1 + Math.floor(Math.random() * 6); - const eventDate = new Date(baseDate); - eventDate.setMonth(eventDate.getMonth() - monthsAgo); - - const similarityScore = 0.6 + Math.random() * 0.35; // 60%-95%相似度 - - historical.push({ - id: `hist_${industry}_${index}_${i}`, - title: generateEventTitle(industry, i + index * 10), - created_at: eventDate.toISOString(), - related_avg_chg: parseFloat((Math.random() * 15 - 3).toFixed(2)), - related_max_chg: parseFloat((Math.random() * 25).toFixed(2)), - similarity_score: parseFloat(similarityScore.toFixed(2)), - view_count: Math.floor(Math.random() * 3000) + 500, - }); - } - - // 按相似度排序 - historical.sort((a, b) => b.similarity_score - a.similarity_score); - return historical; -} - -/** - * 生成传导链数据 - * @param {string} industry - 行业 - * @param {number} index - 索引 - * @returns {Object} - 传导链数据 { nodes, edges } - */ -function generateTransmissionChain(industry, index) { - const nodeTypes = ['event', 'industry', 'company', 'policy', 'technology', 'market']; - const impactTypes = ['positive', 'negative', 'neutral', 'mixed']; - const strengthLevels = ['strong', 'medium', 'weak']; - - const nodes = []; - const edges = []; - - // 主事件节点 - nodes.push({ - id: 1, - name: '主事件', - type: 'event', - extra: { is_main_event: true, description: `${industry}重要事件` } - }); - - // 生成5-8个相关节点 - const nodeCount = 5 + (index % 4); - for (let i = 2; i <= nodeCount; i++) { - const nodeType = nodeTypes[i % nodeTypes.length]; - const industryStock = stockPool.find(s => s.industry === industry); - - let nodeName; - if (nodeType === 'company' && industryStock) { - nodeName = industryStock.stock_name; - } else if (nodeType === 'industry') { - nodeName = `${industry}产业`; - } else if (nodeType === 'policy') { - nodeName = '相关政策'; - } else if (nodeType === 'technology') { - nodeName = '技术创新'; - } else if (nodeType === 'market') { - nodeName = '市场需求'; - } else { - nodeName = `节点${i}`; - } - - nodes.push({ - id: i, - name: nodeName, - type: nodeType, - extra: { description: `${nodeName}相关信息` } - }); - - // 创建与主事件或其他节点的连接 - const targetId = i === 2 ? 1 : Math.max(1, Math.floor(Math.random() * (i - 1)) + 1); - edges.push({ - source: targetId, - target: i, - impact: impactTypes[i % impactTypes.length], - strength: strengthLevels[i % strengthLevels.length], - description: `传导路径${i}` - }); - } - - return { nodes, edges }; -} - -// 主线事件标题模板 - 确保生成的事件能够匹配主线定义的关键词 -// 每个主线定义至少有 2-3 个事件模板,确保数据充足 -const mainlineEventTemplates = [ - // ==================== TMT (科技/媒体/通信) ==================== - - // AI基础设施 - 算力/芯片 (L3_AI_CHIP) - { title: '英伟达发布新一代GPU,AI算力大幅提升', keywords: ['算力', 'AI芯片', 'GPU', '英伟达'], industry: '人工智能' }, - { title: '华为昇腾芯片出货量创新高,国产AI算力加速', keywords: ['华为昇腾', 'AI芯片', '算力'], industry: '人工智能' }, - { title: '寒武纪发布新一代AI训练芯片,性能提升50%', keywords: ['寒武纪', 'AI芯片', '算力'], industry: '人工智能' }, - { title: 'AI芯片需求激增,GPU供不应求价格上涨', keywords: ['AI芯片', 'GPU', '算力'], industry: '人工智能' }, - - // AI基础设施 - 服务器与数据中心 (L3_AI_SERVER) - { title: '智算中心建设加速,多地启动算力基础设施项目', keywords: ['智算中心', '算力', '数据中心'], industry: '人工智能' }, - { title: '液冷技术成数据中心标配,服务器散热升级', keywords: ['液冷', '数据中心', '服务器'], industry: '人工智能' }, - { title: 'AI服务器订单暴增,数据中心扩容需求旺盛', keywords: ['服务器', '数据中心', '智算中心'], industry: '人工智能' }, - - // AI基础设施 - 光通信 (L3_OPTICAL) - { title: 'CPO技术迎来突破,光模块成本大幅下降', keywords: ['CPO', '光模块', '光通信'], industry: '通信' }, - { title: '800G光模块量产加速,AI训练网络升级', keywords: ['光模块', '光通信', '光芯片'], industry: '通信' }, - { title: '光芯片技术突破,CPO方案渗透率提升', keywords: ['光芯片', 'CPO', '光通信', '光模块'], industry: '通信' }, - - // AI基础设施 - PCB与封装 (L3_PCB) - { title: 'AI PCB需求激增,高多层板产能紧张', keywords: ['PCB', 'AI PCB', '封装'], industry: '电子' }, - { title: '先进封装技术突破,PCB产业链升级', keywords: ['封装', 'PCB'], industry: '电子' }, - - // AI应用与大模型 (L3_AI_APP) - { title: 'DeepSeek发布最新大模型,推理能力超越GPT-4', keywords: ['DeepSeek', '大模型', 'AI', '人工智能'], industry: '人工智能' }, - { title: 'KIMI月活突破1亿,国产大模型竞争白热化', keywords: ['KIMI', '大模型', 'AI', '人工智能'], industry: '人工智能' }, - { title: 'ChatGPT新版本发布,AI Agent智能体能力升级', keywords: ['ChatGPT', '大模型', '智能体', 'AI'], industry: '人工智能' }, - { title: '人工智能+医疗深度融合,AI辅助诊断准确率超90%', keywords: ['人工智能', 'AI', '医疗'], industry: '人工智能' }, - { title: '多模态大模型技术突破,AI应用场景扩展', keywords: ['大模型', 'AI', '人工智能'], industry: '人工智能' }, - - // 半导体 - 芯片设计 (L3_CHIP_DESIGN) - { title: '芯片设计企业扩产,IC设计产能大幅提升', keywords: ['芯片设计', 'IC设计', '半导体'], industry: '半导体' }, - { title: '国产芯片设计工具取得突破,EDA软件自主可控', keywords: ['芯片设计', 'IC设计', '半导体'], industry: '半导体' }, - - // 半导体 - 芯片制造 (L3_CHIP_MFG) - { title: '中芯国际N+1工艺量产,芯片制造技术再突破', keywords: ['中芯国际', '芯片制造', '晶圆'], industry: '半导体' }, - { title: '国产光刻机实现技术突破,半导体设备自主可控', keywords: ['光刻', '半导体', '芯片制造'], industry: '半导体' }, - { title: '晶圆产能利用率回升,半导体行业景气度上行', keywords: ['晶圆', '半导体', '芯片制造'], industry: '半导体' }, - - // 机器人 - 人形机器人 (L3_HUMANOID) - { title: '特斯拉人形机器人Optimus量产提速,成本降至2万美元', keywords: ['人形机器人', '特斯拉机器人', '具身智能'], industry: '人工智能' }, - { title: '具身智能迎来突破,人形机器人商业化加速', keywords: ['具身智能', '人形机器人', '机器人'], industry: '人工智能' }, - { title: '人形机器人产业链爆发,核心零部件需求激增', keywords: ['人形机器人', '具身智能'], industry: '人工智能' }, - - // 机器人 - 工业机器人 (L3_INDUSTRIAL_ROBOT) - { title: '工业机器人出货量同比增长30%,自动化渗透率提升', keywords: ['工业机器人', '自动化', '机器人'], industry: '机械' }, - { title: '智能制造升级,机器人自动化需求持续增长', keywords: ['机器人', '自动化', '工业机器人'], industry: '机械' }, - - // 消费电子 - 智能手机 (L3_MOBILE) - { title: '华为Mate系列销量火爆,折叠屏手机市场爆发', keywords: ['华为', '手机', '折叠屏'], industry: '消费电子' }, - { title: '小米可穿戴设备出货量全球第一,智能手表市场扩张', keywords: ['小米', '可穿戴', '手机'], industry: '消费电子' }, - { title: '手机市场复苏,5G手机换机潮来临', keywords: ['手机', '华为', '小米'], industry: '消费电子' }, - - // 消费电子 - XR与可穿戴 (L3_XR) - { title: '苹果Vision Pro销量不及预期,XR设备面临挑战', keywords: ['苹果', 'Vision Pro', 'XR', 'MR'], industry: '消费电子' }, - { title: 'AR眼镜成新风口,VR/AR设备渗透率提升', keywords: ['AR', 'VR', 'XR'], industry: '消费电子' }, - { title: 'Meta发布新一代VR头显,XR市场竞争加剧', keywords: ['VR', 'XR', 'MR', '可穿戴'], industry: '消费电子' }, - - // 通信 - 5G/6G通信 (L3_5G) - { title: '5G基站建设加速,运营商资本开支超预期', keywords: ['5G', '基站', '通信'], industry: '通信' }, - { title: '6G技术标准制定启动,下一代通信网络布局', keywords: ['6G', '5G', '通信'], industry: '通信' }, - - // 通信 - 云计算与软件 (L3_CLOUD) - { title: '云计算市场规模突破万亿,SaaS企业业绩增长', keywords: ['云计算', 'SaaS', '软件', '互联网'], industry: '互联网' }, - { title: '数字化转型加速,企业级软件需求旺盛', keywords: ['数字化', '软件', '互联网'], industry: '互联网' }, - { title: '国产软件替代加速,信创产业迎来发展机遇', keywords: ['软件', '数字化', '互联网'], industry: '互联网' }, - - // ==================== 新能源与智能汽车 ==================== - - // 新能源 - 光伏 (L3_PV) - { title: '光伏装机量创新高,太阳能发电成本持续下降', keywords: ['光伏', '太阳能', '硅片', '组件'], industry: '新能源' }, - { title: '光伏组件价格企稳,行业出清接近尾声', keywords: ['光伏', '组件', '硅片'], industry: '新能源' }, - { title: '分布式光伏爆发,太阳能产业链受益', keywords: ['光伏', '太阳能'], industry: '新能源' }, - - // 新能源 - 储能与电池 (L3_STORAGE) - { title: '储能市场爆发式增长,电池需求大幅提升', keywords: ['储能', '电池', '锂电', '新能源'], industry: '新能源' }, - { title: '固态电池技术突破,新能源汽车续航大幅提升', keywords: ['固态电池', '电池', '锂电', '新能源'], industry: '新能源' }, - { title: '钠电池产业化加速,储能成本有望大幅下降', keywords: ['电池', '储能', '新能源'], industry: '新能源' }, - - // 智能汽车 - 新能源整车 (L3_EV_OEM) - { title: '比亚迪月销量突破50万辆,新能源汽车市占率第一', keywords: ['比亚迪', '新能源汽车', '电动车'], industry: '新能源' }, - { title: '新能源整车出口创新高,中国汽车品牌走向全球', keywords: ['新能源汽车', '整车', '电动车'], industry: '新能源' }, - { title: '电动车价格战持续,新能源汽车渗透率突破50%', keywords: ['电动车', '新能源汽车', '整车'], industry: '新能源' }, - - // 智能汽车 - 智能驾驶 (L3_AUTO_DRIVE) - { title: '特斯拉FSD自动驾驶进入中国市场,智能驾驶加速', keywords: ['特斯拉', '自动驾驶', '智能驾驶', '智能网联'], industry: '新能源' }, - { title: '车路协同试点扩大,智能网联汽车基建提速', keywords: ['车路协同', '智能网联', '智能驾驶'], industry: '新能源' }, - { title: 'L3级自动驾驶获批,智能驾驶产业化加速', keywords: ['自动驾驶', '智能驾驶', '智能网联'], industry: '新能源' }, - - // ==================== 先进制造 ==================== - - // 低空经济 - 无人机 (L3_DRONE) - { title: '低空经济政策密集出台,无人机产业迎来风口', keywords: ['低空', '无人机', '空域'], industry: '航空' }, - { title: '无人机应用场景拓展,低空经济市场规模扩大', keywords: ['无人机', '低空', '空域'], industry: '航空' }, - - // 低空经济 - eVTOL (L3_EVTOL) - { title: 'eVTOL飞行汽车完成首飞,空中出租车商业化在即', keywords: ['eVTOL', '飞行汽车', '空中出租车'], industry: '航空' }, - { title: '飞行汽车获适航认证,eVTOL商业运营启动', keywords: ['飞行汽车', 'eVTOL', '空中出租车'], industry: '航空' }, - - // 军工 - 航空航天 (L3_AEROSPACE) - { title: '商业航天发射成功,卫星互联网建设加速', keywords: ['航天', '卫星', '火箭'], industry: '军工' }, - { title: '航空发动机国产化取得突破,航空产业链升级', keywords: ['航空', '军工'], industry: '军工' }, - { title: '卫星通信需求爆发,航天发射频次创新高', keywords: ['卫星', '航天', '火箭'], industry: '军工' }, - - // 军工 - 国防军工 (L3_DEFENSE) - { title: '军工订单饱满,国防装备现代化提速', keywords: ['军工', '国防', '军工装备'], industry: '军工' }, - { title: '国防预算增长,军工装备需求持续提升', keywords: ['国防', '军工', '军工装备', '导弹'], industry: '军工' }, - - // ==================== 医药健康 ==================== - - // 医药 - 创新药 (L3_DRUG) - { title: '创新药获批上市,医药板块迎来业绩兑现', keywords: ['创新药', '医药', '生物'], industry: '医药' }, - { title: 'CXO订单持续增长,医药研发外包景气度高', keywords: ['CXO', '医药', '生物'], industry: '医药' }, - { title: '生物医药融资回暖,创新药研发管线丰富', keywords: ['医药', '生物', '创新药'], industry: '医药' }, - - // 医药 - 医疗器械 (L3_DEVICE) - { title: '医疗器械集采扩面,国产替代加速', keywords: ['医疗器械', '医疗', '器械'], industry: '医药' }, - { title: '高端医疗设备国产化突破,医疗器械出口增长', keywords: ['医疗器械', '医疗', '器械'], industry: '医药' }, - - // ==================== 金融 ==================== - - // 金融 - 银行 (L3_BANK) - { title: '银行净息差企稳,金融板块估值修复', keywords: ['银行', '金融'], industry: '金融' }, - { title: '银行业绩超预期,金融股迎来价值重估', keywords: ['银行', '金融'], industry: '金融' }, - - // 金融 - 券商 (L3_BROKER) - { title: '券商业绩大幅增长,证券板块活跃', keywords: ['券商', '证券', '金融'], industry: '金融' }, - { title: 'A股成交量放大,证券行业业绩弹性显现', keywords: ['证券', '券商', '金融'], industry: '金融' }, -]; - -/** - * 生成动态新闻事件(实时要闻·动态追踪专用) - * @param {Object} timeRange - 时间范围 { startTime, endTime } - * @param {number} count - 生成事件数量,默认30条 - * @returns {Array} - 事件列表 - */ -export function generateDynamicNewsEvents(timeRange = null, count = 30) { - const events = []; - const importanceLevels = ['S', 'A', 'B', 'C']; - - // 如果没有提供时间范围,默认生成最近24小时的事件 - let startTime, endTime; - if (timeRange) { - startTime = new Date(timeRange.startTime); - endTime = new Date(timeRange.endTime); - } else { - endTime = new Date(); - startTime = new Date(endTime.getTime() - 24 * 60 * 60 * 1000); // 24小时前 - } - - // 计算时间跨度(毫秒) - const timeSpan = endTime.getTime() - startTime.getTime(); - - for (let i = 0; i < count; i++) { - // 使用主线事件模板生成事件,确保能匹配主线关键词 - const templateIndex = i % mainlineEventTemplates.length; - const template = mainlineEventTemplates[templateIndex]; - const industry = template.industry; - const imp = importanceLevels[i % importanceLevels.length]; - const eventType = eventTypes[i % eventTypes.length]; - - // 在时间范围内随机生成事件时间 - const randomOffset = Math.random() * timeSpan; - const createdAt = new Date(startTime.getTime() + randomOffset); - - // 生成随机热度和收益率 - const hotScore = Math.max(60, 100 - i * 1.2); // 动态新闻热度更高 - const relatedAvgChg = (Math.random() * 15 - 3).toFixed(2); // -3% 到 12% - const relatedMaxChg = (Math.random() * 25).toFixed(2); // 0% 到 25% - const relatedWeekChg = (Math.random() * 30 - 10).toFixed(2); // -10% 到 20% - - // 为每个事件随机选择2-5个相关股票(完整对象) - const relatedStockCount = 2 + (i % 4); - const relatedStocks = []; - const industryStocks = stockPool.filter(s => s.industry === industry); - const relationDescriptions = [ - '直接受益标的', - '产业链上游企业', - '产业链下游企业', - '行业龙头企业', - '潜在受益标的', - '概念相关个股' - ]; - - // 优先选择同行业股票 - if (industryStocks.length > 0) { - for (let j = 0; j < Math.min(relatedStockCount, industryStocks.length); j++) { - const stock = industryStocks[j % industryStocks.length]; - relatedStocks.push({ - stock_code: stock.stock_code, - stock_name: stock.stock_name, - relation_desc: relationDescriptions[j % relationDescriptions.length] - }); - } - } - - // 如果同行业股票不够,从整个 stockPool 中补充 - while (relatedStocks.length < relatedStockCount && relatedStocks.length < stockPool.length) { - const randomStock = stockPool[relatedStocks.length % stockPool.length]; - if (!relatedStocks.some(s => s.stock_code === randomStock.stock_code)) { - relatedStocks.push({ - stock_code: randomStock.stock_code, - stock_name: randomStock.stock_name, - relation_desc: relationDescriptions[relatedStocks.length % relationDescriptions.length] - }); - } - } - - // 使用模板标题,并生成包含模板关键词的 keywords 数组 - const eventTitle = template.title; - const eventDescription = generateEventDescription(industry, imp, i); - - // 生成关键词对象数组,包含模板中的关键词 - const templateKeywords = template.keywords.map((kw, idx) => ({ - concept: kw, - stock_count: 10 + Math.floor(Math.random() * 20), - score: parseFloat((0.7 + Math.random() * 0.25).toFixed(2)), - description: `${kw}相关概念,市场关注度较高`, - price_info: { - avg_change_pct: parseFloat((Math.random() * 15 - 5).toFixed(2)) - }, - match_type: ['hybrid_knn', 'keyword', 'semantic'][idx % 3] - })); - - // 合并模板关键词和行业关键词 - const mergedKeywords = [ - ...templateKeywords, - ...generateKeywords(industry, i).slice(0, 2) - ]; - - // 生成投票数据 - const bullishCount = Math.floor(Math.random() * 80) + 10; - const bearishCount = Math.floor(Math.random() * 40) + 5; - - events.push({ - id: `dynamic_${i + 1}`, - title: eventTitle, - description: eventDescription, - content: eventDescription, - event_type: eventType, - importance: imp, - status: 'published', - created_at: createdAt.toISOString(), - updated_at: createdAt.toISOString(), - hot_score: hotScore, - view_count: Math.floor(Math.random() * 5000) + 1000, // 1000-6000 浏览量 - follower_count: Math.floor(Math.random() * 500) + 50, // 50-550 关注数 - bullish_count: bullishCount, // 看多数 - bearish_count: bearishCount, // 看空数 - user_vote: null, // 默认未投票 - post_count: Math.floor(Math.random() * 100) + 10, // 10-110 帖子数 - related_avg_chg: parseFloat(relatedAvgChg), - related_max_chg: parseFloat(relatedMaxChg), - related_week_chg: parseFloat(relatedWeekChg), - expectation_surprise_score: Math.floor(Math.random() * 60) + 30, // 30-90 超预期得分 - keywords: mergedKeywords, - related_concepts: mergedKeywords, // 添加 related_concepts 字段,兼容主线匹配逻辑 - is_ai_generated: i % 3 === 0, // 33% 的事件是AI生成 - industry: industry, - related_stocks: relatedStocks, - historical_events: generateHistoricalEvents(industry, i), - transmission_chain: generateTransmissionChain(industry, i), - creator: { - username: authorPool[i % authorPool.length], - avatar_url: null - } - }); - } - - // 按时间倒序排序(最新的在前) - events.sort((a, b) => new Date(b.created_at) - new Date(a.created_at)); - - return events; -} diff --git a/src/mocks/data/financial.js b/src/mocks/data/financial.js deleted file mode 100644 index 6fdb1019..00000000 --- a/src/mocks/data/financial.js +++ /dev/null @@ -1,429 +0,0 @@ -// src/mocks/data/financial.js -// 财务数据相关的 Mock 数据 - -// 生成财务数据 -export const generateFinancialData = (stockCode) => { - // 12 期数据 - 用于财务指标表格(7个指标Tab) - const metricsPeriods = [ - '2024-09-30', '2024-06-30', '2024-03-31', '2023-12-31', - '2023-09-30', '2023-06-30', '2023-03-31', '2022-12-31', - '2022-09-30', '2022-06-30', '2022-03-31', '2021-12-31', - ]; - - // 8 期数据 - 用于财务报表(3个报表Tab) - const statementPeriods = [ - '2024-09-30', '2024-06-30', '2024-03-31', '2023-12-31', - '2023-09-30', '2023-06-30', '2023-03-31', '2022-12-31', - ]; - - // 兼容旧代码 - const periods = statementPeriods.slice(0, 4); - - return { - stockCode, - - // 股票基本信息 - stockInfo: { - stock_code: stockCode, - stock_name: stockCode === '000001' ? '平安银行' : '示例公司', - industry: stockCode === '000001' ? '银行' : '制造业', - list_date: '1991-04-03', - market: 'SZ', - // 关键指标 - key_metrics: { - eps: 2.72, - roe: 16.23, - gross_margin: 71.92, - net_margin: 32.56, - roa: 1.05 - }, - // 增长率 - growth_rates: { - revenue_growth: 8.2, - profit_growth: 12.5, - asset_growth: 5.6, - equity_growth: 6.8 - }, - // 财务概要 - financial_summary: { - revenue: 162350, - net_profit: 52860, - total_assets: 5024560, - total_liabilities: 4698880 - }, - // 最新业绩预告 - latest_forecast: { - forecast_type: '预增', - content: '预计全年净利润同比增长10%-17%' - } - }, - - // 资产负债表 - 嵌套结构(8期数据) - balanceSheet: statementPeriods.map((period, i) => ({ - period, - assets: { - current_assets: { - cash: 856780 - i * 10000, - trading_financial_assets: 234560 - i * 5000, - notes_receivable: 12340 - i * 200, - accounts_receivable: 45670 - i * 1000, - prepayments: 8900 - i * 100, - other_receivables: 23450 - i * 500, - inventory: 156780 - i * 3000, - contract_assets: 34560 - i * 800, - other_current_assets: 67890 - i * 1500, - total: 2512300 - i * 25000 - }, - non_current_assets: { - long_term_equity_investments: 234560 - i * 5000, - investment_property: 45670 - i * 1000, - fixed_assets: 678900 - i * 15000, - construction_in_progress: 123450 - i * 3000, - right_of_use_assets: 34560 - i * 800, - intangible_assets: 89012 - i * 2000, - goodwill: 45670 - i * 1000, - deferred_tax_assets: 12340 - i * 300, - other_non_current_assets: 67890 - i * 1500, - total: 2512260 - i * 25000 - }, - total: 5024560 - i * 50000 - }, - liabilities: { - current_liabilities: { - short_term_borrowings: 456780 - i * 10000, - notes_payable: 23450 - i * 500, - accounts_payable: 234560 - i * 5000, - advance_receipts: 12340 - i * 300, - contract_liabilities: 34560 - i * 800, - employee_compensation_payable: 45670 - i * 1000, - taxes_payable: 23450 - i * 500, - other_payables: 78900 - i * 1500, - non_current_liabilities_due_within_one_year: 89012 - i * 2000, - total: 3456780 - i * 35000 - }, - non_current_liabilities: { - long_term_borrowings: 678900 - i * 15000, - bonds_payable: 234560 - i * 5000, - lease_liabilities: 45670 - i * 1000, - deferred_tax_liabilities: 12340 - i * 300, - other_non_current_liabilities: 89012 - i * 2000, - total: 1242100 - i * 13000 - }, - total: 4698880 - i * 48000 - }, - equity: { - share_capital: 19405, - capital_reserve: 89012 - i * 2000, - surplus_reserve: 45670 - i * 1000, - undistributed_profit: 156780 - i * 3000, - treasury_stock: 0, - other_comprehensive_income: 12340 - i * 300, - parent_company_equity: 315680 - i * 1800, - minority_interests: 10000 - i * 200, - total: 325680 - i * 2000 - } - })), - - // 利润表 - 嵌套结构(8期数据) - incomeStatement: statementPeriods.map((period, i) => ({ - period, - revenue: { - total_operating_revenue: 162350 - i * 4000, - operating_revenue: 158900 - i * 3900, - other_income: 3450 - i * 100 - }, - costs: { - total_operating_cost: 93900 - i * 2500, - operating_cost: 45620 - i * 1200, - taxes_and_surcharges: 4560 - i * 100, - selling_expenses: 12340 - i * 300, - admin_expenses: 15670 - i * 400, - rd_expenses: 8900 - i * 200, - financial_expenses: 6810 - i * 300, - interest_expense: 8900 - i * 200, - interest_income: 2090 - i * 50, - three_expenses_total: 34820 - i * 1000, - four_expenses_total: 43720 - i * 1200, - asset_impairment_loss: 1200 - i * 50, - credit_impairment_loss: 2340 - i * 100 - }, - other_gains: { - fair_value_change: 1230 - i * 50, - investment_income: 3450 - i * 100, - investment_income_from_associates: 890 - i * 20, - exchange_income: 560 - i * 10, - asset_disposal_income: 340 - i * 10 - }, - profit: { - operating_profit: 68450 - i * 1500, - total_profit: 69500 - i * 1500, - income_tax_expense: 16640 - i * 300, - net_profit: 52860 - i * 1200, - parent_net_profit: 51200 - i * 1150, - minority_profit: 1660 - i * 50, - continuing_operations_net_profit: 52860 - i * 1200, - discontinued_operations_net_profit: 0 - }, - non_operating: { - non_operating_income: 1050 - i * 20, - non_operating_expenses: 450 - i * 10 - }, - per_share: { - basic_eps: 2.72 - i * 0.06, - diluted_eps: 2.70 - i * 0.06 - }, - comprehensive_income: { - other_comprehensive_income: 890 - i * 20, - total_comprehensive_income: 53750 - i * 1220, - parent_comprehensive_income: 52050 - i * 1170, - minority_comprehensive_income: 1700 - i * 50 - } - })), - - // 现金流量表 - 嵌套结构(8期数据) - cashflow: statementPeriods.map((period, i) => ({ - period, - operating_activities: { - inflow: { - cash_from_sales: 178500 - i * 4500 - }, - outflow: { - cash_for_goods: 52900 - i * 1500 - }, - net_flow: 125600 - i * 3000 - }, - investment_activities: { - net_flow: -45300 - i * 1000 - }, - financing_activities: { - net_flow: -38200 + i * 500 - }, - cash_changes: { - net_increase: 42100 - i * 1500, - ending_balance: 456780 - i * 10000 - }, - key_metrics: { - free_cash_flow: 80300 - i * 2000 - } - })), - - // 财务指标 - 嵌套结构(12期数据) - financialMetrics: metricsPeriods.map((period, i) => ({ - period, - profitability: { - roe: 16.23 - i * 0.3, - roe_deducted: 15.89 - i * 0.3, - roe_weighted: 16.45 - i * 0.3, - roa: 1.05 - i * 0.02, - gross_margin: 71.92 - i * 0.5, - net_profit_margin: 32.56 - i * 0.3, - operating_profit_margin: 42.16 - i * 0.4, - cost_profit_ratio: 115.8 - i * 1.2, - ebit: 86140 - i * 1800 - }, - per_share_metrics: { - eps: 2.72 - i * 0.06, - basic_eps: 2.72 - i * 0.06, - diluted_eps: 2.70 - i * 0.06, - deducted_eps: 2.65 - i * 0.06, - bvps: 16.78 - i * 0.1, - operating_cash_flow_ps: 6.47 - i * 0.15, - capital_reserve_ps: 4.59 - i * 0.1, - undistributed_profit_ps: 8.08 - i * 0.15 - }, - growth: { - revenue_growth: 8.2 - i * 0.5, - net_profit_growth: 12.5 - i * 0.8, - deducted_profit_growth: 11.8 - i * 0.7, - parent_profit_growth: 12.3 - i * 0.75, - operating_cash_flow_growth: 15.6 - i * 1.0, - total_asset_growth: 5.6 - i * 0.3, - equity_growth: 6.8 - i * 0.4, - fixed_asset_growth: 4.2 - i * 0.2 - }, - operational_efficiency: { - total_asset_turnover: 0.41 - i * 0.01, - fixed_asset_turnover: 2.35 - i * 0.05, - current_asset_turnover: 0.82 - i * 0.02, - receivable_turnover: 12.5 - i * 0.3, - receivable_days: 29.2 + i * 0.7, - inventory_turnover: 0, // 银行无库存 - inventory_days: 0, - working_capital_turnover: 1.68 - i * 0.04 - }, - solvency: { - current_ratio: 0.73 + i * 0.01, - quick_ratio: 0.71 + i * 0.01, - cash_ratio: 0.25 + i * 0.005, - conservative_quick_ratio: 0.68 + i * 0.01, - asset_liability_ratio: 93.52 + i * 0.05, - interest_coverage: 8.56 - i * 0.2, - cash_to_maturity_debt_ratio: 0.45 - i * 0.01, - tangible_asset_debt_ratio: 94.12 + i * 0.05 - }, - expense_ratios: { - selling_expense_ratio: 7.60 + i * 0.1, - admin_expense_ratio: 9.65 + i * 0.1, - financial_expense_ratio: 4.19 + i * 0.1, - rd_expense_ratio: 5.48 + i * 0.1, - three_expense_ratio: 21.44 + i * 0.3, - four_expense_ratio: 26.92 + i * 0.4, - cost_ratio: 28.10 + i * 0.2 - } - })), - - // 主营业务 - 按产品/业务分类 - mainBusiness: { - product_classification: [ - { - period: '2024-09-30', - report_type: '2024年三季报', - products: [ - { content: '零售金融业务', revenue: 81320000000, gross_margin: 68.5, profit_margin: 42.3, profit: 34398160000 }, - { content: '对公金融业务', revenue: 68540000000, gross_margin: 62.8, profit_margin: 38.6, profit: 26456440000 }, - { content: '金融市场业务', revenue: 12490000000, gross_margin: 75.2, profit_margin: 52.1, profit: 6507290000 }, - { content: '合计', revenue: 162350000000, gross_margin: 67.5, profit_margin: 41.2, profit: 66883200000 }, - ] - }, - { - period: '2024-06-30', - report_type: '2024年中报', - products: [ - { content: '零售金融业务', revenue: 78650000000, gross_margin: 67.8, profit_margin: 41.5, profit: 32639750000 }, - { content: '对公金融业务', revenue: 66280000000, gross_margin: 61.9, profit_margin: 37.8, profit: 25053840000 }, - { content: '金融市场业务', revenue: 11870000000, gross_margin: 74.5, profit_margin: 51.2, profit: 6077440000 }, - { content: '合计', revenue: 156800000000, gross_margin: 66.8, profit_margin: 40.5, profit: 63504000000 }, - ] - }, - { - period: '2024-03-31', - report_type: '2024年一季报', - products: [ - { content: '零售金融业务', revenue: 38920000000, gross_margin: 67.2, profit_margin: 40.8, profit: 15879360000 }, - { content: '对公金融业务', revenue: 32650000000, gross_margin: 61.2, profit_margin: 37.1, profit: 12113150000 }, - { content: '金融市场业务', revenue: 5830000000, gross_margin: 73.8, profit_margin: 50.5, profit: 2944150000 }, - { content: '合计', revenue: 77400000000, gross_margin: 66.1, profit_margin: 39.8, profit: 30805200000 }, - ] - }, - { - period: '2023-12-31', - report_type: '2023年年报', - products: [ - { content: '零售金融业务', revenue: 152680000000, gross_margin: 66.5, profit_margin: 40.2, profit: 61377360000 }, - { content: '对公金融业务', revenue: 128450000000, gross_margin: 60.5, profit_margin: 36.5, profit: 46884250000 }, - { content: '金融市场业务', revenue: 22870000000, gross_margin: 73.2, profit_margin: 49.8, profit: 11389260000 }, - { content: '合计', revenue: 304000000000, gross_margin: 65.2, profit_margin: 39.2, profit: 119168000000 }, - ] - }, - ], - industry_classification: [ - { - period: '2024-09-30', - report_type: '2024年三季报', - industries: [ - { content: '华南地区', revenue: 56817500000, gross_margin: 69.2, profit_margin: 43.5, profit: 24715612500 }, - { content: '华东地区', revenue: 48705000000, gross_margin: 67.8, profit_margin: 41.2, profit: 20066460000 }, - { content: '华北地区', revenue: 32470000000, gross_margin: 65.5, profit_margin: 38.8, profit: 12598360000 }, - { content: '西南地区', revenue: 16235000000, gross_margin: 64.2, profit_margin: 37.5, profit: 6088125000 }, - { content: '其他地区', revenue: 8122500000, gross_margin: 62.8, profit_margin: 35.2, profit: 2859120000 }, - { content: '合计', revenue: 162350000000, gross_margin: 67.5, profit_margin: 41.2, profit: 66883200000 }, - ] - }, - { - period: '2024-06-30', - report_type: '2024年中报', - industries: [ - { content: '华南地区', revenue: 54880000000, gross_margin: 68.5, profit_margin: 42.8, profit: 23488640000 }, - { content: '华东地区', revenue: 47040000000, gross_margin: 67.1, profit_margin: 40.5, profit: 19051200000 }, - { content: '华北地区', revenue: 31360000000, gross_margin: 64.8, profit_margin: 38.1, profit: 11948160000 }, - { content: '西南地区', revenue: 15680000000, gross_margin: 63.5, profit_margin: 36.8, profit: 5770240000 }, - { content: '其他地区', revenue: 7840000000, gross_margin: 62.1, profit_margin: 34.5, profit: 2704800000 }, - { content: '合计', revenue: 156800000000, gross_margin: 66.8, profit_margin: 40.5, profit: 63504000000 }, - ] - }, - ] - }, - - // 业绩预告 - forecast: { - period: '2024', - forecast_net_profit_min: 580000, // 百万元 - forecast_net_profit_max: 620000, - yoy_growth_min: 10.0, // % - yoy_growth_max: 17.0, - forecast_type: '预增', - reason: '受益于零售业务快速增长及资产质量改善,预计全年业绩保持稳定增长', - publish_date: '2024-10-15' - }, - - // 行业排名(数组格式,符合 IndustryRankingView 组件要求) - industryRank: [ - { - period: '2024-09-30', - report_type: '三季报', - rankings: [ - { - industry_name: stockCode === '000001' ? '银行' : '制造业', - level_description: '一级行业', - metrics: { - eps: { value: 2.72, rank: 8, industry_avg: 1.85 }, - bvps: { value: 15.23, rank: 12, industry_avg: 12.50 }, - roe: { value: 16.23, rank: 10, industry_avg: 12.00 }, - revenue_growth: { value: 8.2, rank: 15, industry_avg: 5.50 }, - profit_growth: { value: 12.5, rank: 9, industry_avg: 8.00 }, - operating_margin: { value: 32.56, rank: 6, industry_avg: 25.00 }, - debt_ratio: { value: 92.5, rank: 35, industry_avg: 88.00 }, - receivable_turnover: { value: 5.2, rank: 18, industry_avg: 4.80 } - } - } - ] - } - ], - - // 期间对比 - 营收与利润趋势数据 - periodComparison: [ - { - period: '2024-09-30', - performance: { - revenue: 41500000000, // 415亿 - net_profit: 13420000000 // 134.2亿 - } - }, - { - period: '2024-06-30', - performance: { - revenue: 40800000000, // 408亿 - net_profit: 13180000000 // 131.8亿 - } - }, - { - period: '2024-03-31', - performance: { - revenue: 40200000000, // 402亿 - net_profit: 13050000000 // 130.5亿 - } - }, - { - period: '2023-12-31', - performance: { - revenue: 40850000000, // 408.5亿 - net_profit: 13210000000 // 132.1亿 - } - }, - { - period: '2023-09-30', - performance: { - revenue: 38500000000, // 385亿 - net_profit: 11920000000 // 119.2亿 - } - }, - { - period: '2023-06-30', - performance: { - revenue: 37800000000, // 378亿 - net_profit: 11850000000 // 118.5亿 - } - } - ] - }; -}; diff --git a/src/mocks/data/forum.js b/src/mocks/data/forum.js deleted file mode 100644 index 12aff576..00000000 --- a/src/mocks/data/forum.js +++ /dev/null @@ -1,394 +0,0 @@ -/** - * 价值论坛帖子 Mock 数据 - */ - -// 模拟用户 -export const mockForumUsers = [ - { - id: "user_1", - nickname: "价值投资者", - username: "value_investor", - avatar_url: "https://api.dicebear.com/7.x/avataaars/svg?seed=investor1", - }, - { - id: "user_2", - nickname: "趋势猎手", - username: "trend_hunter", - avatar_url: "https://api.dicebear.com/7.x/avataaars/svg?seed=hunter2", - }, - { - id: "user_3", - nickname: "量化先锋", - username: "quant_pioneer", - avatar_url: "https://api.dicebear.com/7.x/avataaars/svg?seed=quant3", - }, - { - id: "user_4", - nickname: "股市老兵", - username: "stock_veteran", - avatar_url: "https://api.dicebear.com/7.x/avataaars/svg?seed=veteran4", - }, - { - id: "user_5", - nickname: "新手小白", - username: "newbie", - avatar_url: "https://api.dicebear.com/7.x/avataaars/svg?seed=newbie5", - }, -]; - -// 帖子标签 -export const mockTags = [ - "A股", - "美股", - "港股", - "新能源", - "半导体", - "AI", - "消费", - "医药", - "金融", - "地产", - "白酒", - "锂电池", - "光伏", - "汽车", - "军工", -]; - -// 帖子分类 -export const mockCategories = [ - "analysis", // 个股分析 - "strategy", // 投资策略 - "news", // 市场资讯 - "discussion", // 讨论交流 - "experience", // 经验分享 -]; - -// 模拟帖子列表 -export const mockPosts = [ - { - id: "post_001", - author_id: "user_1", - author_name: "价值投资者", - author_avatar: "https://api.dicebear.com/7.x/avataaars/svg?seed=investor1", - title: "深度解析:宁德时代2024年业绩展望", - content: - "宁德时代作为全球动力电池龙头,2024年面临几个关键变量:\n\n1. **产能扩张**:公司在欧洲、北美的产能布局持续推进\n2. **技术迭代**:麒麟电池、钠离子电池等新技术商业化进度\n3. **竞争格局**:比亚迪、中创新航等竞争对手的市场份额变化\n\n从估值角度看,当前PE约25倍,处于历史中位数附近...", - images: [ - "https://images.unsplash.com/photo-1611974789855-9c2a0a7236a3?w=800", - "https://images.unsplash.com/photo-1590283603385-17ffb3a7f29f?w=800", - ], - tags: ["新能源", "锂电池", "A股"], - category: "analysis", - likes_count: 156, - comments_count: 42, - views_count: 2345, - created_at: "2024-12-20T10:30:00Z", - updated_at: "2024-12-20T10:30:00Z", - is_pinned: true, - status: "active", - }, - { - id: "post_002", - author_id: "user_2", - author_name: "趋势猎手", - author_avatar: "https://api.dicebear.com/7.x/avataaars/svg?seed=hunter2", - title: "技术面分析:上证指数短期走势预判", - content: - "从技术形态来看,上证指数近期呈现以下特征:\n\n- MACD金叉形成,多头趋势确立\n- 5日均线上穿10日均线\n- 成交量温和放大\n\n综合来看,短期内大盘有望向3200点发起冲击,支撑位在3050点附近。操作上建议逢低布局优质赛道龙头。", - images: [ - "https://images.unsplash.com/photo-1611974789855-9c2a0a7236a3?w=800", - ], - tags: ["A股", "技术分析"], - category: "strategy", - likes_count: 89, - comments_count: 28, - views_count: 1567, - created_at: "2024-12-19T15:20:00Z", - updated_at: "2024-12-19T15:20:00Z", - is_pinned: false, - status: "active", - }, - { - id: "post_003", - author_id: "user_3", - author_name: "量化先锋", - author_avatar: "https://api.dicebear.com/7.x/avataaars/svg?seed=quant3", - title: "AI芯片赛道深度研究:英伟达vs AMD", - content: - "随着ChatGPT引爆AI浪潮,算力需求爆发式增长。本文对比分析两大芯片巨头:\n\n**英伟达 (NVDA)**\n- GPU市场份额超80%\n- CUDA生态护城河深厚\n- 数据中心业务高速增长\n\n**AMD**\n- MI300系列强势追赶\n- 性价比优势明显\n- 客户多元化策略\n\n投资建议:长期看好英伟达,但AMD估值更具吸引力...", - images: [ - "https://images.unsplash.com/photo-1518770660439-4636190af475?w=800", - "https://images.unsplash.com/photo-1555255707-c07966088b7b?w=800", - ], - tags: ["美股", "AI", "半导体"], - category: "analysis", - likes_count: 234, - comments_count: 67, - views_count: 4521, - created_at: "2024-12-18T09:00:00Z", - updated_at: "2024-12-18T09:00:00Z", - is_pinned: true, - status: "active", - }, - { - id: "post_004", - author_id: "user_4", - author_name: "股市老兵", - author_avatar: "https://api.dicebear.com/7.x/avataaars/svg?seed=veteran4", - title: "白酒板块投资逻辑:消费复苏下的机会", - content: - "白酒作为A股核心资产,具有以下投资逻辑:\n\n1. 消费升级持续,高端白酒需求稳定\n2. 品牌壁垒高,提价能力强\n3. 现金流充沛,分红率提升空间大\n\n重点关注标的:\n- 贵州茅台:行业龙头,确定性最强\n- 五粮液:次高端领军,性价比突出\n- 山西汾酒:清香型龙头,成长性好", - images: [], - tags: ["白酒", "消费", "A股"], - category: "analysis", - likes_count: 112, - comments_count: 35, - views_count: 1890, - created_at: "2024-12-17T14:30:00Z", - updated_at: "2024-12-17T14:30:00Z", - is_pinned: false, - status: "active", - }, - { - id: "post_005", - author_id: "user_5", - author_name: "新手小白", - author_avatar: "https://api.dicebear.com/7.x/avataaars/svg?seed=newbie5", - title: "请教:如何判断一只股票是否值得长期持有?", - content: - "刚入市不久,想请教各位大佬几个问题:\n\n1. 判断一家公司是否值得长期投资,最重要的指标是什么?\n2. PE、PB这些估值指标应该怎么用?\n3. 行业龙头和细分赛道龙头怎么选?\n\n希望大家不吝赐教,感谢!", - images: [], - tags: ["投资入门", "讨论"], - category: "discussion", - likes_count: 45, - comments_count: 52, - views_count: 876, - created_at: "2024-12-16T11:00:00Z", - updated_at: "2024-12-16T11:00:00Z", - is_pinned: false, - status: "active", - }, - { - id: "post_006", - author_id: "user_1", - author_name: "价值投资者", - author_avatar: "https://api.dicebear.com/7.x/avataaars/svg?seed=investor1", - title: "医药板块复盘:集采影响逐步消化", - content: - "经过近两年的调整,医药板块估值已回到合理区间。从政策面看:\n\n- 集采常态化,边际影响减弱\n- 创新药审批加速\n- 医保谈判规则趋于稳定\n\n建议关注方向:\n1. 创新药龙头(恒瑞医药、百济神州)\n2. CXO赛道(药明康德、康龙化成)\n3. 医疗器械(迈瑞医疗、联影医疗)", - images: [ - "https://images.unsplash.com/photo-1584308666744-24d5c474f2ae?w=800", - ], - tags: ["医药", "A股"], - category: "analysis", - likes_count: 78, - comments_count: 23, - views_count: 1234, - created_at: "2024-12-15T16:45:00Z", - updated_at: "2024-12-15T16:45:00Z", - is_pinned: false, - status: "active", - }, - { - id: "post_007", - author_id: "user_2", - author_name: "趋势猎手", - author_avatar: "https://api.dicebear.com/7.x/avataaars/svg?seed=hunter2", - title: "港股科技股投资机会分析", - content: - "港股科技股经历大幅调整后,估值已具吸引力:\n\n**腾讯控股**\n- 游戏业务恢复增长\n- 视频号商业化提速\n- 回购力度加大\n\n**阿里巴巴**\n- 云业务盈利改善\n- 电商份额企稳\n- 分拆上市预期\n\n**美团**\n- 外卖业务利润率提升\n- 即时零售空间广阔", - images: [], - tags: ["港股", "科技", "互联网"], - category: "analysis", - likes_count: 145, - comments_count: 41, - views_count: 2156, - created_at: "2024-12-14T10:15:00Z", - updated_at: "2024-12-14T10:15:00Z", - is_pinned: false, - status: "active", - }, - { - id: "post_008", - author_id: "user_3", - author_name: "量化先锋", - author_avatar: "https://api.dicebear.com/7.x/avataaars/svg?seed=quant3", - title: "光伏行业2024年展望:产能出清进行时", - content: - "光伏行业正经历产能过剩带来的调整期:\n\n**现状分析**\n- 硅料价格跌至成本线附近\n- 组件企业盈利承压\n- 落后产能加速出清\n\n**未来展望**\n- N型技术渗透率提升\n- 海外需求持续高增\n- 龙头市占率提升\n\n投资建议:等待产业链利润重新分配,关注技术领先的龙头企业。", - images: [ - "https://images.unsplash.com/photo-1509391366360-2e959784a276?w=800", - "https://images.unsplash.com/photo-1558449028-b53a39d100fc?w=800", - ], - tags: ["光伏", "新能源", "A股"], - category: "analysis", - likes_count: 167, - comments_count: 48, - views_count: 2890, - created_at: "2024-12-13T08:30:00Z", - updated_at: "2024-12-13T08:30:00Z", - is_pinned: false, - status: "active", - }, - { - id: "post_009", - author_id: "user_4", - author_name: "股市老兵", - author_avatar: "https://api.dicebear.com/7.x/avataaars/svg?seed=veteran4", - title: "分享我的投资框架:价值与成长的平衡", - content: - "投资十五年,总结出一套适合自己的投资框架:\n\n**选股标准**\n1. ROE连续5年>15%\n2. 资产负债率<50%\n3. 经营性现金流为正\n4. 行业地位前三\n\n**估值方法**\n- PE历史分位数\n- PEG估值法\n- DCF现金流折现\n\n**仓位管理**\n- 核心仓位60%(优质蓝筹)\n- 卫星仓位30%(成长股)\n- 现金储备10%", - images: [], - tags: ["投资策略", "经验分享"], - category: "experience", - likes_count: 289, - comments_count: 76, - views_count: 4567, - created_at: "2024-12-12T13:00:00Z", - updated_at: "2024-12-12T13:00:00Z", - is_pinned: true, - status: "active", - }, - { - id: "post_010", - author_id: "user_1", - author_name: "价值投资者", - author_avatar: "https://api.dicebear.com/7.x/avataaars/svg?seed=investor1", - title: "军工板块:国防建设加速下的投资机会", - content: - "在复杂国际形势下,国防建设持续加码:\n\n**行业驱动力**\n- 国防预算稳定增长\n- 装备更新换代周期\n- 军民融合深化\n\n**重点方向**\n1. 航空发动机(航发动力)\n2. 导弹武器(航天电器)\n3. 信息化装备(中航电子)\n4. 新材料(光威复材)\n\n估值方面,板块PE约50倍,中长期看仍有配置价值。", - images: [ - "https://images.unsplash.com/photo-1540575467063-178a50c2df87?w=800", - ], - tags: ["军工", "A股"], - category: "analysis", - likes_count: 98, - comments_count: 31, - views_count: 1678, - created_at: "2024-12-11T09:45:00Z", - updated_at: "2024-12-11T09:45:00Z", - is_pinned: false, - status: "active", - }, -]; - -// 帖子评论 -export const mockPostComments = { - post_001: [ - { - id: "comment_001_1", - post_id: "post_001", - author_id: "user_2", - author_name: "趋势猎手", - author_avatar: "https://api.dicebear.com/7.x/avataaars/svg?seed=hunter2", - content: "分析得很透彻!宁德时代确实是新能源赛道的核心资产,长期看好。", - parent_id: null, - likes_count: 23, - created_at: "2024-12-20T11:30:00Z", - status: "active", - }, - { - id: "comment_001_2", - post_id: "post_001", - author_id: "user_3", - author_name: "量化先锋", - author_avatar: "https://api.dicebear.com/7.x/avataaars/svg?seed=quant3", - content: - "补充一点:钠离子电池的商业化进度值得关注,可能会打开新的增长空间。", - parent_id: null, - likes_count: 18, - created_at: "2024-12-20T12:15:00Z", - status: "active", - }, - { - id: "comment_001_3", - post_id: "post_001", - author_id: "user_4", - author_name: "股市老兵", - author_avatar: "https://api.dicebear.com/7.x/avataaars/svg?seed=veteran4", - content: "目前估值还是偏贵,等回调到20倍PE再考虑加仓。", - parent_id: null, - likes_count: 12, - created_at: "2024-12-20T14:00:00Z", - status: "active", - }, - ], - post_003: [ - { - id: "comment_003_1", - post_id: "post_003", - author_id: "user_1", - author_name: "价值投资者", - author_avatar: - "https://api.dicebear.com/7.x/avataaars/svg?seed=investor1", - content: "英伟达的护城河确实深,但估值也确实贵。我选择定投,分批建仓。", - parent_id: null, - likes_count: 34, - created_at: "2024-12-18T10:30:00Z", - status: "active", - }, - { - id: "comment_003_2", - post_id: "post_003", - author_id: "user_4", - author_name: "股市老兵", - author_avatar: "https://api.dicebear.com/7.x/avataaars/svg?seed=veteran4", - content: "AMD的MI300确实有竞争力,但CUDA生态不是一朝一夕能追上的。", - parent_id: null, - likes_count: 28, - created_at: "2024-12-18T11:45:00Z", - status: "active", - }, - ], - post_005: [ - { - id: "comment_005_1", - post_id: "post_005", - author_id: "user_4", - author_name: "股市老兵", - author_avatar: "https://api.dicebear.com/7.x/avataaars/svg?seed=veteran4", - content: - "最重要的是理解公司的商业模式和竞争优势。PE只是参考,不同行业估值中枢不同。", - parent_id: null, - likes_count: 45, - created_at: "2024-12-16T12:00:00Z", - status: "active", - }, - { - id: "comment_005_2", - post_id: "post_005", - author_id: "user_1", - author_name: "价值投资者", - author_avatar: - "https://api.dicebear.com/7.x/avataaars/svg?seed=investor1", - content: - "建议多看看公司年报,特别是管理层讨论分析部分。另外ROE是个很重要的指标。", - parent_id: null, - likes_count: 38, - created_at: "2024-12-16T13:30:00Z", - status: "active", - }, - { - id: "comment_005_3", - post_id: "post_005", - author_id: "user_3", - author_name: "量化先锋", - author_avatar: "https://api.dicebear.com/7.x/avataaars/svg?seed=quant3", - content: - "可以用PEG来判断成长股估值是否合理,PEG=PE/盈利增速,小于1通常说明被低估。", - parent_id: null, - likes_count: 32, - created_at: "2024-12-16T15:00:00Z", - status: "active", - }, - ], -}; - -export default { - mockForumUsers, - mockTags, - mockCategories, - mockPosts, - mockPostComments, -}; diff --git a/src/mocks/data/industries.js b/src/mocks/data/industries.js deleted file mode 100644 index 8966be6c..00000000 --- a/src/mocks/data/industries.js +++ /dev/null @@ -1,554 +0,0 @@ -// src/mocks/data/industries.js -// 行业分类完整树形数据 Mock - -/** - * 完整的行业分类树形结构 - * 包含 5 个分类体系,层级深度 2-4 层不等 - */ -export const industryTreeData = [ - { - value: "新财富行业分类", - label: "新财富行业分类", - children: [ - { - value: "XCF001", - label: "传播与文化", - children: [ - { - value: "XCF001001", - label: "互联网传媒", - children: [ - { value: "XCF001001001", label: "数字媒体" }, - { value: "XCF001001002", label: "社交平台" }, - { value: "XCF001001003", label: "短视频平台" } - ] - }, - { - value: "XCF001002", - label: "影视娱乐", - children: [ - { value: "XCF001002001", label: "电影制作" }, - { value: "XCF001002002", label: "网络视频" } - ] - }, - { - value: "XCF001003", - label: "出版发行" - } - ] - }, - { - value: "XCF002", - label: "交通运输仓储", - children: [ - { - value: "XCF002001", - label: "航空运输", - children: [ - { value: "XCF002001001", label: "航空客运" }, - { value: "XCF002001002", label: "航空货运" } - ] - }, - { - value: "XCF002002", - label: "铁路运输" - }, - { - value: "XCF002003", - label: "公路运输", - children: [ - { value: "XCF002003001", label: "公路客运" }, - { value: "XCF002003002", label: "公路货运" }, - { value: "XCF002003003", label: "快递物流" } - ] - } - ] - }, - { - value: "XCF003", - label: "农林牧渔", - children: [ - { value: "XCF003001", label: "种植业" }, - { value: "XCF003002", label: "林业" }, - { value: "XCF003003", label: "畜牧业" }, - { value: "XCF003004", label: "渔业" } - ] - }, - { - value: "XCF004", - label: "医药生物", - children: [ - { - value: "XCF004001", - label: "化学制药", - children: [ - { value: "XCF004001001", label: "化学原料药" }, - { value: "XCF004001002", label: "化学制剂" } - ] - }, - { - value: "XCF004002", - label: "生物制品", - children: [ - { value: "XCF004002001", label: "疫苗" }, - { value: "XCF004002002", label: "血液制品" }, - { value: "XCF004002003", label: "诊断试剂" } - ] - }, - { value: "XCF004003", label: "中药" }, - { value: "XCF004004", label: "医疗器械" } - ] - }, - { - value: "XCF005", - label: "基础化工", - children: [ - { value: "XCF005001", label: "化学原料" }, - { value: "XCF005002", label: "化学制品" }, - { value: "XCF005003", label: "塑料" }, - { value: "XCF005004", label: "橡胶" } - ] - }, - { - value: "XCF006", - label: "家电", - children: [ - { value: "XCF006001", label: "白色家电" }, - { value: "XCF006002", label: "黑色家电" }, - { value: "XCF006003", label: "小家电" } - ] - }, - { - value: "XCF007", - label: "电子", - children: [ - { - value: "XCF007001", - label: "半导体", - children: [ - { value: "XCF007001001", label: "芯片设计" }, - { value: "XCF007001002", label: "芯片制造" }, - { value: "XCF007001003", label: "封装测试" } - ] - }, - { value: "XCF007002", label: "元件" }, - { value: "XCF007003", label: "光学光电子" }, - { value: "XCF007004", label: "消费电子" } - ] - }, - { - value: "XCF008", - label: "计算机", - children: [ - { - value: "XCF008001", - label: "计算机设备", - children: [ - { value: "XCF008001001", label: "PC" }, - { value: "XCF008001002", label: "服务器" } - ] - }, - { - value: "XCF008002", - label: "软件开发", - children: [ - { value: "XCF008002001", label: "应用软件" }, - { value: "XCF008002002", label: "系统软件" } - ] - }, - { value: "XCF008003", label: "IT服务" } - ] - } - ] - }, - { - value: "申银万国行业分类", - label: "申银万国行业分类", - children: [ - { - value: "SW001", - label: "电子", - children: [ - { - value: "SW001001", - label: "半导体", - children: [ - { value: "SW001001001", label: "半导体材料" }, - { value: "SW001001002", label: "半导体设备" }, - { value: "SW001001003", label: "集成电路" } - ] - }, - { - value: "SW001002", - label: "电子制造", - children: [ - { value: "SW001002001", label: "PCB" }, - { value: "SW001002002", label: "被动元件" } - ] - }, - { value: "SW001003", label: "光学光电子" } - ] - }, - { - value: "SW002", - label: "计算机", - children: [ - { value: "SW002001", label: "计算机设备" }, - { value: "SW002002", label: "计算机应用" }, - { value: "SW002003", label: "通信设备" } - ] - }, - { - value: "SW003", - label: "传媒", - children: [ - { value: "SW003001", label: "互联网传媒" }, - { value: "SW003002", label: "营销传播" }, - { value: "SW003003", label: "文化传媒" } - ] - }, - { - value: "SW004", - label: "医药生物", - children: [ - { value: "SW004001", label: "化学制药" }, - { value: "SW004002", label: "中药" }, - { value: "SW004003", label: "生物制品" }, - { value: "SW004004", label: "医疗器械" }, - { value: "SW004005", label: "医药商业" } - ] - }, - { - value: "SW005", - label: "汽车", - children: [ - { - value: "SW005001", - label: "乘用车", - children: [ - { value: "SW005001001", label: "燃油车" }, - { value: "SW005001002", label: "新能源车" } - ] - }, - { value: "SW005002", label: "商用车" }, - { value: "SW005003", label: "汽车零部件" } - ] - }, - { - value: "SW006", - label: "机械设备", - children: [ - { value: "SW006001", label: "通用设备" }, - { value: "SW006002", label: "专用设备" }, - { value: "SW006003", label: "仪器仪表" } - ] - }, - { - value: "SW007", - label: "食品饮料", - children: [ - { value: "SW007001", label: "白酒" }, - { value: "SW007002", label: "啤酒" }, - { value: "SW007003", label: "软饮料" }, - { value: "SW007004", label: "食品加工" } - ] - }, - { - value: "SW008", - label: "银行", - children: [ - { value: "SW008001", label: "国有银行" }, - { value: "SW008002", label: "股份制银行" }, - { value: "SW008003", label: "城商行" } - ] - }, - { - value: "SW009", - label: "非银金融", - children: [ - { value: "SW009001", label: "证券" }, - { value: "SW009002", label: "保险" }, - { value: "SW009003", label: "多元金融" } - ] - }, - { - value: "SW010", - label: "房地产", - children: [ - { value: "SW010001", label: "房地产开发" }, - { value: "SW010002", label: "房地产服务" } - ] - } - ] - }, - { - value: "证监会行业分类(2001)", - label: "证监会行业分类(2001)", - children: [ - { - value: "CSRC_A", - label: "A 农、林、牧、渔业", - children: [ - { value: "CSRC_A01", label: "A01 农业" }, - { value: "CSRC_A02", label: "A02 林业" }, - { value: "CSRC_A03", label: "A03 畜牧业" }, - { value: "CSRC_A04", label: "A04 渔业" } - ] - }, - { - value: "CSRC_B", - label: "B 采矿业", - children: [ - { value: "CSRC_B06", label: "B06 煤炭开采和洗选业" }, - { value: "CSRC_B07", label: "B07 石油和天然气开采业" }, - { value: "CSRC_B08", label: "B08 黑色金属矿采选业" }, - { value: "CSRC_B09", label: "B09 有色金属矿采选业" } - ] - }, - { - value: "CSRC_C", - label: "C 制造业", - children: [ - { - value: "CSRC_C13", - label: "C13 农副食品加工业", - children: [ - { value: "CSRC_C1310", label: "C1310 肉制品加工" }, - { value: "CSRC_C1320", label: "C1320 水产品加工" } - ] - }, - { - value: "CSRC_C27", - label: "C27 医药制造业", - children: [ - { value: "CSRC_C2710", label: "C2710 化学药品原料药制造" }, - { value: "CSRC_C2720", label: "C2720 化学药品制剂制造" }, - { value: "CSRC_C2730", label: "C2730 中药饮片加工" } - ] - }, - { value: "CSRC_C35", label: "C35 专用设备制造业" }, - { value: "CSRC_C39", label: "C39 计算机、通信和其他电子设备制造业" } - ] - }, - { - value: "CSRC_I", - label: "I 信息传输、软件和信息技术服务业", - children: [ - { value: "CSRC_I63", label: "I63 电信、广播电视和卫星传输服务" }, - { value: "CSRC_I64", label: "I64 互联网和相关服务" }, - { value: "CSRC_I65", label: "I65 软件和信息技术服务业" } - ] - }, - { - value: "CSRC_J", - label: "J 金融业", - children: [ - { value: "CSRC_J66", label: "J66 货币金融服务" }, - { value: "CSRC_J67", label: "J67 资本市场服务" }, - { value: "CSRC_J68", label: "J68 保险业" } - ] - }, - { - value: "CSRC_K", - label: "K 房地产业", - children: [ - { value: "CSRC_K70", label: "K70 房地产业" } - ] - } - ] - }, - { - value: "中银国际行业分类", - label: "中银国际行业分类", - children: [ - { - value: "BOC001", - label: "能源", - children: [ - { value: "BOC001001", label: "石油天然气" }, - { value: "BOC001002", label: "煤炭" }, - { value: "BOC001003", label: "新能源" } - ] - }, - { - value: "BOC002", - label: "原材料", - children: [ - { value: "BOC002001", label: "化工" }, - { value: "BOC002002", label: "钢铁" }, - { value: "BOC002003", label: "有色金属" }, - { value: "BOC002004", label: "建材" } - ] - }, - { - value: "BOC003", - label: "工业", - children: [ - { value: "BOC003001", label: "机械" }, - { value: "BOC003002", label: "电气设备" }, - { value: "BOC003003", label: "国防军工" } - ] - }, - { - value: "BOC004", - label: "消费", - children: [ - { - value: "BOC004001", - label: "可选消费", - children: [ - { value: "BOC004001001", label: "汽车" }, - { value: "BOC004001002", label: "家电" }, - { value: "BOC004001003", label: "纺织服装" } - ] - }, - { - value: "BOC004002", - label: "必需消费", - children: [ - { value: "BOC004002001", label: "食品饮料" }, - { value: "BOC004002002", label: "农林牧渔" } - ] - } - ] - }, - { - value: "BOC005", - label: "医疗保健", - children: [ - { value: "BOC005001", label: "医药" }, - { value: "BOC005002", label: "医疗器械" }, - { value: "BOC005003", label: "医疗服务" } - ] - }, - { - value: "BOC006", - label: "金融", - children: [ - { value: "BOC006001", label: "银行" }, - { value: "BOC006002", label: "非银金融" } - ] - }, - { - value: "BOC007", - label: "科技", - children: [ - { - value: "BOC007001", - label: "信息技术", - children: [ - { value: "BOC007001001", label: "半导体" }, - { value: "BOC007001002", label: "电子" }, - { value: "BOC007001003", label: "计算机" }, - { value: "BOC007001004", label: "通信" } - ] - }, - { value: "BOC007002", label: "传媒" } - ] - } - ] - }, - { - value: "巨潮行业分类", - label: "巨潮行业分类", - children: [ - { - value: "JC01", - label: "制造业", - children: [ - { - value: "JC0101", - label: "电气机械及器材制造业", - children: [ - { value: "JC010101", label: "电机制造" }, - { value: "JC010102", label: "输配电及控制设备制造" }, - { value: "JC010103", label: "电池制造" } - ] - }, - { - value: "JC0102", - label: "医药制造业", - children: [ - { value: "JC010201", label: "化学药品原药制造" }, - { value: "JC010202", label: "化学药品制剂制造" }, - { value: "JC010203", label: "中成药制造" }, - { value: "JC010204", label: "生物、生化制品制造" } - ] - }, - { value: "JC0103", label: "食品制造业" }, - { value: "JC0104", label: "纺织业" } - ] - }, - { - value: "JC02", - label: "信息传输、软件和信息技术服务业", - children: [ - { value: "JC0201", label: "互联网和相关服务" }, - { value: "JC0202", label: "软件和信息技术服务业" } - ] - }, - { - value: "JC03", - label: "批发和零售业", - children: [ - { value: "JC0301", label: "批发业" }, - { value: "JC0302", label: "零售业" } - ] - }, - { - value: "JC04", - label: "房地产业", - children: [ - { value: "JC0401", label: "房地产开发经营" }, - { value: "JC0402", label: "物业管理" } - ] - }, - { - value: "JC05", - label: "金融业", - children: [ - { value: "JC0501", label: "货币金融服务" }, - { value: "JC0502", label: "资本市场服务" }, - { value: "JC0503", label: "保险业" } - ] - }, - { - value: "JC06", - label: "交通运输、仓储和邮政业", - children: [ - { value: "JC0601", label: "道路运输业" }, - { value: "JC0602", label: "航空运输业" }, - { value: "JC0603", label: "水上运输业" } - ] - }, - { - value: "JC07", - label: "采矿业", - children: [ - { value: "JC0701", label: "煤炭开采和洗选业" }, - { value: "JC0702", label: "石油和天然气开采业" }, - { value: "JC0703", label: "有色金属矿采选业" } - ] - }, - { - value: "JC08", - label: "农、林、牧、渔业", - children: [ - { value: "JC0801", label: "农业" }, - { value: "JC0802", label: "林业" }, - { value: "JC0803", label: "畜牧业" }, - { value: "JC0804", label: "渔业" } - ] - }, - { - value: "JC09", - label: "建筑业", - children: [ - { value: "JC0901", label: "房屋建筑业" }, - { value: "JC0902", label: "土木工程建筑业" }, - { value: "JC0903", label: "建筑装饰和其他建筑业" } - ] - } - ] - } -]; diff --git a/src/mocks/data/kline.js b/src/mocks/data/kline.js deleted file mode 100644 index 4951ce0f..00000000 --- a/src/mocks/data/kline.js +++ /dev/null @@ -1,164 +0,0 @@ -// src/mocks/data/kline.js -// K线数据生成函数 - -/** - * 生成分时数据 (timeline) - * 用于展示当日分钟级别的价格走势 - */ -export const generateTimelineData = (indexCode) => { - const data = []; - const basePrice = getBasePrice(indexCode); - const today = new Date(); - - // 生成早盘数据 (09:30 - 11:30) - const morningStart = new Date(today.setHours(9, 30, 0, 0)); - const morningEnd = new Date(today.setHours(11, 30, 0, 0)); - generateTimeRange(data, morningStart, morningEnd, basePrice, 'morning'); - - // 生成午盘数据 (13:00 - 15:00) - const afternoonStart = new Date(today.setHours(13, 0, 0, 0)); - const afternoonEnd = new Date(today.setHours(15, 0, 0, 0)); - generateTimeRange(data, afternoonStart, afternoonEnd, basePrice, 'afternoon'); - - return data; -}; - -/** - * 生成日线数据 (daily) - * 用于获取历史收盘价等数据 - * 默认生成 400 天的数据,覆盖足够的历史范围 - */ -export const generateDailyData = (indexCode, days = 400) => { - const data = []; - const basePrice = getBasePrice(indexCode); - const today = new Date(); - - // 使用固定种子生成一致的随机数,确保同一天的涨跌幅一致 - const seededRandom = (seed) => { - const x = Math.sin(seed) * 10000; - return x - Math.floor(x); - }; - - for (let i = days - 1; i >= 0; i--) { - const date = new Date(today); - date.setDate(date.getDate() - i); - - // 跳过周末 - const dayOfWeek = date.getDay(); - if (dayOfWeek === 0 || dayOfWeek === 6) continue; - - // 使用日期作为种子,确保同一天生成相同的数据 - const dateSeed = date.getFullYear() * 10000 + (date.getMonth() + 1) * 100 + date.getDate(); - const rand1 = seededRandom(dateSeed); - const rand2 = seededRandom(dateSeed + 1); - const rand3 = seededRandom(dateSeed + 2); - - const open = basePrice * (1 + (rand1 * 0.04 - 0.02)); - const close = open * (1 + (rand2 * 0.03 - 0.015)); - const high = Math.max(open, close) * (1 + rand3 * 0.015); - const low = Math.min(open, close) * (1 - seededRandom(dateSeed + 3) * 0.015); - const volume = Math.floor(seededRandom(dateSeed + 4) * 50000000000 + 10000000000); - - data.push({ - date: formatDate(date), - time: formatDate(date), - open: parseFloat(open.toFixed(2)), - close: parseFloat(close.toFixed(2)), - high: parseFloat(high.toFixed(2)), - low: parseFloat(low.toFixed(2)), - volume: volume, - prev_close: data.length === 0 ? parseFloat((basePrice * 0.99).toFixed(2)) : data[data.length - 1]?.close - }); - } - - return data; -}; - -/** - * 计算简单移动均价(用于分时图均价线) - * @param {Array} data - 已有数据 - * @param {number} currentPrice - 当前价格 - * @param {number} period - 均线周期(默认5) - * @returns {number} 均价 - */ -function calculateAvgPrice(data, currentPrice, period = 5) { - const recentPrices = data.slice(-period).map(d => d.price || d.close); - recentPrices.push(currentPrice); - const sum = recentPrices.reduce((acc, p) => acc + p, 0); - return parseFloat((sum / recentPrices.length).toFixed(2)); -} - -/** - * 生成时间范围内的数据 - */ -function generateTimeRange(data, startTime, endTime, basePrice, session) { - const current = new Date(startTime); - let price = basePrice; - - // 波动趋势(早盘和午盘可能有不同的走势) - const trend = session === 'morning' ? Math.random() * 0.02 - 0.01 : Math.random() * 0.015 - 0.005; - - while (current <= endTime) { - // 添加随机波动 - const volatility = (Math.random() - 0.5) * 0.005; - price = price * (1 + trend / 120 + volatility); // 每分钟微小变化 - - const volume = Math.floor(Math.random() * 500000000 + 100000000); - - // ✅ 修复:为分时图添加完整的 OHLC 字段 - const closePrice = parseFloat(price.toFixed(2)); - - // 计算均价和涨跌幅 - const avgPrice = calculateAvgPrice(data, closePrice); - const changePercent = parseFloat(((closePrice - basePrice) / basePrice * 100).toFixed(2)); - - data.push({ - time: formatTime(current), - timestamp: current.getTime(), // ✅ 新增:毫秒时间戳 - open: parseFloat((price * 0.9999).toFixed(2)), // ✅ 新增:开盘价(略低于收盘) - high: parseFloat((price * 1.0002).toFixed(2)), // ✅ 新增:最高价(略高于收盘) - low: parseFloat((price * 0.9997).toFixed(2)), // ✅ 新增:最低价(略低于收盘) - close: closePrice, // ✅ 保留:收盘价 - price: closePrice, // ✅ 保留:兼容字段(供 MiniTimelineChart 使用) - avg_price: avgPrice, // ✅ 新增:均价(供 TimelineChartModal 使用) - change_percent: changePercent, // ✅ 新增:涨跌幅(供 TimelineChartModal 使用) - volume: volume, - prev_close: basePrice - }); - - // 增加1分钟 - current.setMinutes(current.getMinutes() + 1); - } -} - -/** - * 获取不同指数的基准价格 - */ -function getBasePrice(indexCode) { - const basePrices = { - '000001.SH': 3200, // 上证指数 - '399001.SZ': 10500, // 深证成指 - '399006.SZ': 2100 // 创业板指 - }; - - return basePrices[indexCode] || 3000; -} - -/** - * 格式化时间为 HH:mm - */ -function formatTime(date) { - const hours = String(date.getHours()).padStart(2, '0'); - const minutes = String(date.getMinutes()).padStart(2, '0'); - return `${hours}:${minutes}`; -} - -/** - * 格式化日期为 YYYY-MM-DD - */ -function formatDate(date) { - const year = date.getFullYear(); - const month = String(date.getMonth() + 1).padStart(2, '0'); - const day = String(date.getDate()).padStart(2, '0'); - return `${year}-${month}-${day}`; -} diff --git a/src/mocks/data/market.js b/src/mocks/data/market.js deleted file mode 100644 index 266754f5..00000000 --- a/src/mocks/data/market.js +++ /dev/null @@ -1,308 +0,0 @@ -// src/mocks/data/market.js -// 市场行情相关的 Mock 数据 - -// 股票名称映射 -const STOCK_NAME_MAP = { - '000001': { name: '平安银行', basePrice: 13.50 }, - '600000': { name: '浦发银行', basePrice: 8.20 }, - '600519': { name: '贵州茅台', basePrice: 1650.00 }, - '000858': { name: '五粮液', basePrice: 165.00 }, - '601318': { name: '中国平安', basePrice: 45.00 }, - '600036': { name: '招商银行', basePrice: 32.00 }, - '300750': { name: '宁德时代', basePrice: 180.00 }, - '002594': { name: '比亚迪', basePrice: 260.00 }, -}; - -// 生成市场数据 -export const generateMarketData = (stockCode) => { - const stockInfo = STOCK_NAME_MAP[stockCode] || { name: `股票${stockCode}`, basePrice: 20.00 }; - const basePrice = stockInfo.basePrice; - - return { - stockCode, - - // 成交数据 - 必须包含K线所需的字段 - tradeData: { - success: true, - data: Array(30).fill(null).map((_, i) => { - const open = basePrice + (Math.random() - 0.5) * 0.5; - const close = basePrice + (Math.random() - 0.5) * 0.5; - const high = Math.max(open, close) + Math.random() * 0.3; - const low = Math.min(open, close) - Math.random() * 0.3; - return { - date: new Date(Date.now() - (29 - i) * 24 * 60 * 60 * 1000).toISOString().split('T')[0], - open: parseFloat(open.toFixed(2)), - close: parseFloat(close.toFixed(2)), - high: parseFloat(high.toFixed(2)), - low: parseFloat(low.toFixed(2)), - volume: Math.floor(Math.random() * 500000000) + 100000000, // 1-6亿股 - amount: Math.floor(Math.random() * 7000000000) + 1300000000, // 13-80亿元 - turnover_rate: parseFloat((Math.random() * 2 + 0.5).toFixed(2)), // 0.5-2.5% - change_percent: parseFloat((Math.random() * 6 - 3).toFixed(2)), // -3% to +3% - pe_ratio: parseFloat((Math.random() * 3 + 4).toFixed(2)) // 4-7 - }; - }) - }, - - // 资金流向 - 融资融券数据数组 - fundingData: { - success: true, - data: Array(30).fill(null).map((_, i) => ({ - date: new Date(Date.now() - (29 - i) * 24 * 60 * 60 * 1000).toISOString().split('T')[0], - financing: { - balance: Math.floor(Math.random() * 5000000000) + 10000000000, // 融资余额 - buy: Math.floor(Math.random() * 500000000) + 100000000, // 融资买入 - repay: Math.floor(Math.random() * 500000000) + 80000000 // 融资偿还 - }, - securities: { - balance: Math.floor(Math.random() * 100000000) + 50000000, // 融券余额(股数) - balance_amount: Math.floor(Math.random() * 2000000000) + 1000000000, // 融券余额(金额) - sell: Math.floor(Math.random() * 10000000) + 5000000, // 融券卖出 - repay: Math.floor(Math.random() * 10000000) + 3000000 // 融券偿还 - } - })) - }, - - // 大宗交易 - 包含 daily_stats 数组,符合 BigDealDayStats 类型 - bigDealData: { - success: true, - data: [], - daily_stats: Array(10).fill(null).map((_, i) => { - const count = Math.floor(Math.random() * 5) + 1; // 1-5 笔交易 - const avgPrice = parseFloat((basePrice * (0.95 + Math.random() * 0.1)).toFixed(2)); // 折价/溢价 -5%~+5% - const deals = Array(count).fill(null).map(() => { - const volume = parseFloat((Math.random() * 500 + 100).toFixed(2)); // 100-600 万股 - const price = parseFloat((avgPrice * (0.98 + Math.random() * 0.04)).toFixed(2)); - return { - buyer_dept: ['中信证券北京总部', '国泰君安上海分公司', '华泰证券深圳营业部', '招商证券广州分公司'][Math.floor(Math.random() * 4)], - seller_dept: ['中金公司北京营业部', '海通证券上海分公司', '广发证券深圳营业部', '平安证券广州分公司'][Math.floor(Math.random() * 4)], - price, - volume, - amount: parseFloat((price * volume).toFixed(2)) - }; - }); - const totalVolume = deals.reduce((sum, d) => sum + d.volume, 0); - const totalAmount = deals.reduce((sum, d) => sum + d.amount, 0); - return { - date: new Date(Date.now() - (9 - i) * 24 * 60 * 60 * 1000).toISOString().split('T')[0], - count, - total_volume: parseFloat(totalVolume.toFixed(2)), - total_amount: parseFloat(totalAmount.toFixed(2)), - avg_price: avgPrice, - deals - }; - }) - }, - - // 龙虎榜数据 - 包含 grouped_data 数组,符合 UnusualDayData 类型 - unusualData: { - success: true, - data: [], - grouped_data: Array(5).fill(null).map((_, i) => { - const buyerDepts = ['中信证券北京总部', '国泰君安上海分公司', '华泰证券深圳营业部', '招商证券广州分公司', '中金公司北京营业部']; - const sellerDepts = ['海通证券上海分公司', '广发证券深圳营业部', '平安证券广州分公司', '东方证券上海营业部', '兴业证券福州营业部']; - const infoTypes = ['日涨幅偏离值达7%', '日振幅达15%', '连续三日涨幅偏离20%', '换手率达20%']; - - const buyers = buyerDepts.map(dept => ({ - dept_name: dept, - buy_amount: Math.floor(Math.random() * 50000000) + 10000000 // 1000万-6000万 - })).sort((a, b) => b.buy_amount - a.buy_amount); - - const sellers = sellerDepts.map(dept => ({ - dept_name: dept, - sell_amount: Math.floor(Math.random() * 40000000) + 8000000 // 800万-4800万 - })).sort((a, b) => b.sell_amount - a.sell_amount); - - const totalBuy = buyers.reduce((sum, b) => sum + b.buy_amount, 0); - const totalSell = sellers.reduce((sum, s) => sum + s.sell_amount, 0); - - return { - date: new Date(Date.now() - (4 - i) * 24 * 60 * 60 * 1000).toISOString().split('T')[0], - total_buy: totalBuy, - total_sell: totalSell, - net_amount: totalBuy - totalSell, - buyers, - sellers, - info_types: infoTypes.slice(0, Math.floor(Math.random() * 3) + 1) // 随机选1-3个类型 - }; - }) - }, - - // 股权质押 - 匹配 PledgeData[] 类型 - pledgeData: { - success: true, - data: Array(12).fill(null).map((_, i) => { - const date = new Date(); - date.setMonth(date.getMonth() - (11 - i)); - return { - end_date: date.toISOString().split('T')[0].slice(0, 7) + '-01', - unrestricted_pledge: Math.floor(Math.random() * 1000000000) + 500000000, - restricted_pledge: Math.floor(Math.random() * 200000000) + 50000000, - total_pledge: Math.floor(Math.random() * 1200000000) + 550000000, - total_shares: 19405918198, - pledge_ratio: parseFloat((Math.random() * 3 + 6).toFixed(2)), // 6-9% - pledge_count: Math.floor(Math.random() * 50) + 100 // 100-150 - }; - }) - }, - - // 市场摘要 - 匹配 MarketSummary 类型 - summaryData: { - success: true, - data: { - stock_code: stockCode, - stock_name: stockInfo.name, - latest_trade: { - close: basePrice, - change_percent: 1.89, - volume: 345678900, - amount: 4678900000, - turnover_rate: 1.78, - pe_ratio: 4.96 - }, - latest_funding: { - financing_balance: 5823000000, - securities_balance: 125600000 - }, - latest_pledge: { - pledge_ratio: 8.25 - } - } - }, - - // 涨幅分析 - 匹配 RiseAnalysis 类型,每个交易日一条记录 - riseAnalysisData: { - success: true, - data: Array(30).fill(null).map((_, i) => { - const tradeDate = new Date(Date.now() - (29 - i) * 24 * 60 * 60 * 1000).toISOString().split('T')[0]; - const riseRate = parseFloat(((Math.random() - 0.5) * 10).toFixed(2)); // -5% ~ +5% - const closePrice = parseFloat((basePrice * (1 + riseRate / 100)).toFixed(2)); - const volume = Math.floor(Math.random() * 500000000) + 100000000; - const amount = Math.floor(volume * closePrice); - - // 涨幅分析详情模板 - const riseReasons = [ - { - brief: '业绩超预期', - detail: `## 业绩驱动\n\n${stockInfo.name}发布业绩公告,主要经营指标超出市场预期:\n\n- **营业收入**:同比增长15.3%,环比增长5.2%\n- **净利润**:同比增长18.7%,创历史新高\n- **毛利率**:提升2.1个百分点至35.8%\n\n### 核心亮点\n\n1. 主营业务增长强劲,市场份额持续提升\n2. 成本管控效果显著,盈利能力改善\n3. 新产品放量,贡献增量收入`, - announcements: `**重要公告**\n\n1. [${stockInfo.name}:关于2024年度业绩预告的公告](javascript:void(0))\n2. [${stockInfo.name}:关于获得政府补助的公告](javascript:void(0))` - }, - { - brief: '政策利好', - detail: `## 政策催化\n\n近期行业政策密集出台,对${stockInfo.name}形成重大利好:\n\n### 政策要点\n\n- **行业支持政策**:国家出台支持措施,加大对行业的扶持力度\n- **税收优惠**:符合条件的企业可享受税收减免\n- **融资支持**:拓宽企业融资渠道,降低融资成本\n\n### 受益分析\n\n公司作为行业龙头,有望充分受益于政策红利,预计:\n\n1. 订单量将显著增长\n2. 毛利率有望提升\n3. 市场份额进一步扩大`, - announcements: `**相关公告**\n\n1. [${stockInfo.name}:关于行业政策影响的说明公告](javascript:void(0))` - }, - { - brief: '资金流入', - detail: `## 资金面分析\n\n今日${stockInfo.name}获得主力资金大幅流入:\n\n### 资金流向\n\n| 指标 | 数值 | 变化 |\n|------|------|------|\n| 主力净流入 | 3.2亿 | +156% |\n| 超大单净流入 | 1.8亿 | +89% |\n| 大单净流入 | 1.4亿 | +67% |\n\n### 分析结论\n\n1. 机构资金持续加仓,看好公司长期价值\n2. 北向资金连续3日净买入\n3. 融资余额创近期新高`, - announcements: '' - }, - { - brief: '技术突破', - detail: `## 技术面分析\n\n${stockInfo.name}今日实现技术突破:\n\n### 技术信号\n\n- **突破关键阻力位**:成功站上${(closePrice * 0.95).toFixed(2)}元重要阻力\n- **量价配合良好**:成交量较昨日放大1.5倍\n- **均线多头排列**:5日、10日、20日均线呈多头排列\n\n### 后市展望\n\n技术面看,股价有望继续向上挑战${(closePrice * 1.05).toFixed(2)}元目标位。建议关注:\n\n1. 能否持续放量\n2. 均线支撑情况\n3. MACD金叉确认`, - announcements: '' - } - ]; - - const reasonIndex = i % riseReasons.length; - const reason = riseReasons[reasonIndex]; - - // 研报数据 - const publishers = ['中信证券', '华泰证券', '国泰君安', '招商证券', '中金公司', '海通证券']; - const authors = ['张三', '李四', '王五', '赵六', '钱七', '孙八']; - const matchScores = ['好', '中', '差']; - - return { - stock_code: stockCode, - stock_name: stockInfo.name, - trade_date: tradeDate, - rise_rate: riseRate, - close_price: closePrice, - volume: volume, - amount: amount, - main_business: stockInfo.business || '金融服务、零售银行、对公业务、资产管理等', - rise_reason_brief: reason.brief, - rise_reason_detail: reason.detail, - announcements: reason.announcements || '', - verification_reports: [ - { - publisher: publishers[i % publishers.length], - match_score: matchScores[Math.floor(Math.random() * 3)], - match_ratio: parseFloat((Math.random() * 0.5 + 0.5).toFixed(2)), - declare_date: tradeDate, - report_title: `${stockInfo.name}深度研究:${reason.brief}带来投资机会`, - author: authors[i % authors.length], - verification_item: `${reason.brief}对公司业绩的影响分析`, - content: `我们认为${stockInfo.name}在${reason.brief}的背景下,有望实现业绩的持续增长。维持"买入"评级,目标价${(closePrice * 1.2).toFixed(2)}元。` - }, - { - publisher: publishers[(i + 1) % publishers.length], - match_score: matchScores[Math.floor(Math.random() * 3)], - match_ratio: parseFloat((Math.random() * 0.4 + 0.3).toFixed(2)), - declare_date: tradeDate, - report_title: `${stockInfo.name}跟踪报告:关注${reason.brief}`, - author: authors[(i + 1) % authors.length], - verification_item: '估值分析与投资建议', - content: `当前估值处于历史中低位,安全边际充足。建议投资者积极关注。` - } - ], - update_time: new Date().toISOString().split('T')[0] + ' 18:30:00', - create_time: tradeDate + ' 15:30:00' - }; - }) - }, - - // 最新分时数据 - 匹配 MinuteData 类型 - latestMinuteData: { - success: true, - data: (() => { - const minuteData = []; - // 上午 9:30-11:30 (120分钟) - for (let i = 0; i < 120; i++) { - const hour = 9 + Math.floor((30 + i) / 60); - const min = (30 + i) % 60; - const time = `${hour.toString().padStart(2, '0')}:${min.toString().padStart(2, '0')}`; - const randomChange = (Math.random() - 0.5) * 0.1; - const open = parseFloat((basePrice + randomChange).toFixed(2)); - const close = parseFloat((basePrice + randomChange + (Math.random() - 0.5) * 0.05).toFixed(2)); - const high = parseFloat(Math.max(open, close, open + Math.random() * 0.05).toFixed(2)); - const low = parseFloat(Math.min(open, close, close - Math.random() * 0.05).toFixed(2)); - minuteData.push({ - time, - open, - close, - high, - low, - volume: Math.floor(Math.random() * 2000000) + 500000, - amount: Math.floor(Math.random() * 30000000) + 5000000 - }); - } - // 下午 13:00-15:00 (120分钟) - for (let i = 0; i < 120; i++) { - const hour = 13 + Math.floor(i / 60); - const min = i % 60; - const time = `${hour.toString().padStart(2, '0')}:${min.toString().padStart(2, '0')}`; - const randomChange = (Math.random() - 0.5) * 0.1; - const open = parseFloat((basePrice + randomChange).toFixed(2)); - const close = parseFloat((basePrice + randomChange + (Math.random() - 0.5) * 0.05).toFixed(2)); - const high = parseFloat(Math.max(open, close, open + Math.random() * 0.05).toFixed(2)); - const low = parseFloat(Math.min(open, close, close - Math.random() * 0.05).toFixed(2)); - minuteData.push({ - time, - open, - close, - high, - low, - volume: Math.floor(Math.random() * 1500000) + 400000, - amount: Math.floor(Math.random() * 25000000) + 4000000 - }); - } - return minuteData; - })(), - code: stockCode, - name: stockInfo.name, - trade_date: new Date().toISOString().split('T')[0], - type: '1min' - } - }; -}; diff --git a/src/mocks/data/prediction.js b/src/mocks/data/prediction.js deleted file mode 100644 index bf874697..00000000 --- a/src/mocks/data/prediction.js +++ /dev/null @@ -1,342 +0,0 @@ -/** - * 预测市场 Mock 数据 - */ - -// 模拟用户 -export const mockUsers = [ - { - id: 1, - nickname: "价值投资者", - username: "value_investor", - avatar_url: "https://api.dicebear.com/7.x/avataaars/svg?seed=1", - }, - { - id: 2, - nickname: "趋势猎手", - username: "trend_hunter", - avatar_url: "https://api.dicebear.com/7.x/avataaars/svg?seed=2", - }, - { - id: 3, - nickname: "量化先锋", - username: "quant_pioneer", - avatar_url: "https://api.dicebear.com/7.x/avataaars/svg?seed=3", - }, - { - id: 4, - nickname: "股市老兵", - username: "stock_veteran", - avatar_url: "https://api.dicebear.com/7.x/avataaars/svg?seed=4", - }, - { - id: 5, - nickname: "新手小白", - username: "newbie", - avatar_url: "https://api.dicebear.com/7.x/avataaars/svg?seed=5", - }, -]; - -// 预测话题列表 -export const mockTopics = [ - { - id: 1, - title: "2024年A股能否突破3500点?", - description: - "预测2024年内上证指数是否能够突破3500点大关。以2024年12月31日收盘价为准。", - category: "stock", - tags: ["A股", "大盘", "指数"], - author_id: 1, - author_name: "价值投资者", - author_avatar: "https://api.dicebear.com/7.x/avataaars/svg?seed=1", - created_at: "2024-01-15T10:00:00Z", - deadline: "2024-12-31T15:00:00Z", - status: "active", - total_pool: 15000, - yes_total_shares: 120, - no_total_shares: 80, - yes_price: 600, - no_price: 400, - yes_lord_id: 2, - no_lord_id: 3, - yes_lord_name: "趋势猎手", - no_lord_name: "量化先锋", - participants_count: 25, - comments_count: 18, - }, - { - id: 2, - title: "英伟达股价年底能否突破800美元?", - description: "预测英伟达(NVDA)股价在2024年底前是否能突破800美元。", - category: "stock", - tags: ["美股", "AI", "英伟达"], - author_id: 2, - author_name: "趋势猎手", - author_avatar: "https://api.dicebear.com/7.x/avataaars/svg?seed=2", - created_at: "2024-02-01T14:30:00Z", - deadline: "2024-12-31T23:59:00Z", - status: "active", - total_pool: 28000, - yes_total_shares: 200, - no_total_shares: 100, - yes_price: 667, - no_price: 333, - yes_lord_id: 1, - no_lord_id: 4, - yes_lord_name: "价值投资者", - no_lord_name: "股市老兵", - participants_count: 42, - comments_count: 35, - }, - { - id: 3, - title: "比特币2024年能否创历史新高?", - description: "预测比特币在2024年是否能够突破历史最高价69000美元。", - category: "crypto", - tags: ["比特币", "加密货币", "BTC"], - author_id: 3, - author_name: "量化先锋", - author_avatar: "https://api.dicebear.com/7.x/avataaars/svg?seed=3", - created_at: "2024-01-20T09:00:00Z", - deadline: "2024-12-31T23:59:00Z", - status: "active", - total_pool: 50000, - yes_total_shares: 300, - no_total_shares: 200, - yes_price: 600, - no_price: 400, - yes_lord_id: 2, - no_lord_id: 1, - yes_lord_name: "趋势猎手", - no_lord_name: "价值投资者", - participants_count: 68, - comments_count: 52, - }, - { - id: 4, - title: "茅台股价能否重返2000元?", - description: "预测贵州茅台股价在2024年内是否能够重返2000元以上。", - category: "stock", - tags: ["白酒", "茅台", "A股"], - author_id: 4, - author_name: "股市老兵", - author_avatar: "https://api.dicebear.com/7.x/avataaars/svg?seed=4", - created_at: "2024-02-10T11:00:00Z", - deadline: "2024-12-31T15:00:00Z", - status: "active", - total_pool: 12000, - yes_total_shares: 60, - no_total_shares: 140, - yes_price: 300, - no_price: 700, - yes_lord_id: 5, - no_lord_id: 3, - yes_lord_name: "新手小白", - no_lord_name: "量化先锋", - participants_count: 30, - comments_count: 22, - }, - { - id: 5, - title: "美联储2024年会降息几次?(3次以上)", - description: "预测美联储在2024年是否会降息3次或以上。", - category: "general", - tags: ["美联储", "降息", "宏观"], - author_id: 1, - author_name: "价值投资者", - author_avatar: "https://api.dicebear.com/7.x/avataaars/svg?seed=1", - created_at: "2024-01-25T16:00:00Z", - deadline: "2024-12-31T23:59:00Z", - status: "active", - total_pool: 20000, - yes_total_shares: 150, - no_total_shares: 150, - yes_price: 500, - no_price: 500, - yes_lord_id: 4, - no_lord_id: 2, - yes_lord_name: "股市老兵", - no_lord_name: "趋势猎手", - participants_count: 55, - comments_count: 40, - }, -]; - -// 用户账户数据 -export const mockUserAccount = { - user_id: 1, - balance: 8500, - frozen: 1500, - total: 10000, - total_earned: 12000, - total_spent: 3500, - total_profit: 2000, - last_daily_bonus: null, // 可领取 - stats: { - total_topics: 3, - win_count: 5, - loss_count: 2, - win_rate: 0.714, - best_profit: 1200, - total_trades: 15, - }, -}; - -// 用户持仓 -export const mockPositions = [ - { - id: 1, - topic_id: 1, - topic_title: "2024年A股能否突破3500点?", - direction: "yes", - shares: 50, - avg_cost: 550, - current_price: 600, - current_value: 30000, - unrealized_pnl: 2500, - acquired_at: "2024-01-20T10:30:00Z", - }, - { - id: 2, - topic_id: 2, - topic_title: "英伟达股价年底能否突破800美元?", - direction: "yes", - shares: 30, - avg_cost: 600, - current_price: 667, - current_value: 20010, - unrealized_pnl: 2010, - acquired_at: "2024-02-05T14:00:00Z", - }, - { - id: 3, - topic_id: 4, - topic_title: "茅台股价能否重返2000元?", - direction: "no", - shares: 20, - avg_cost: 650, - current_price: 700, - current_value: 14000, - unrealized_pnl: 1000, - acquired_at: "2024-02-12T09:30:00Z", - }, -]; - -// 评论数据 -export const mockComments = { - 1: [ - // topic_id: 1 的评论 - { - id: 101, - topic_id: 1, - user: mockUsers[1], - content: - "看好A股,政策面利好不断,预计下半年会有一波行情。技术面已经筑底完成,可以积极布局。", - parent_id: null, - likes_count: 42, - is_liked: false, - is_lord: true, - total_investment: 2500, - investment_shares: 25, - verification_status: null, - created_at: "2024-01-16T10:30:00Z", - }, - { - id: 102, - topic_id: 1, - user: mockUsers[2], - content: - "持谨慎态度,虽然政策有支持,但经济基本面还需要时间恢复。建议观望为主。", - parent_id: null, - likes_count: 28, - is_liked: true, - is_lord: true, - total_investment: 1800, - investment_shares: 18, - verification_status: null, - created_at: "2024-01-17T14:20:00Z", - }, - { - id: 103, - topic_id: 1, - user: mockUsers[3], - content: "从量化指标来看,当前市场估值处于历史低位,长期来看有投资价值。", - parent_id: null, - likes_count: 35, - is_liked: false, - is_lord: false, - total_investment: 500, - investment_shares: 5, - verification_status: null, - created_at: "2024-01-18T09:15:00Z", - }, - ], - 2: [ - // topic_id: 2 的评论 - { - id: 201, - topic_id: 2, - user: mockUsers[0], - content: - "AI浪潮势不可挡,英伟达作为算力龙头,业绩增长确定性强。800美元只是时间问题。", - parent_id: null, - likes_count: 56, - is_liked: true, - is_lord: true, - total_investment: 3500, - investment_shares: 35, - verification_status: null, - created_at: "2024-02-02T11:00:00Z", - }, - { - id: 202, - topic_id: 2, - user: mockUsers[3], - content: "估值已经很高了,需要警惕回调风险。但长期仍然看好AI赛道。", - parent_id: null, - likes_count: 38, - is_liked: false, - is_lord: true, - total_investment: 2000, - investment_shares: 20, - verification_status: null, - created_at: "2024-02-03T16:30:00Z", - }, - ], -}; - -// 交易记录 -export const mockTrades = [ - { - id: 1, - topic_id: 1, - user_id: 1, - direction: "yes", - type: "buy", - shares: 50, - price: 550, - total_cost: 27500, - tax: 550, - created_at: "2024-01-20T10:30:00Z", - }, - { - id: 2, - topic_id: 2, - user_id: 1, - direction: "yes", - type: "buy", - shares: 30, - price: 600, - total_cost: 18000, - tax: 360, - created_at: "2024-02-05T14:00:00Z", - }, -]; - -export default { - mockUsers, - mockTopics, - mockUserAccount, - mockPositions, - mockComments, - mockTrades, -}; diff --git a/src/mocks/data/users.js b/src/mocks/data/users.js deleted file mode 100644 index b5593172..00000000 --- a/src/mocks/data/users.js +++ /dev/null @@ -1,135 +0,0 @@ -// Mock 用户数据 -export const mockUsers = { - // 免费用户 - 手机号登录 - '13800138000': { - id: 1, - phone: '13800138000', - nickname: '测试用户', - email: 'test@example.com', - avatar_url: 'https://i.pravatar.cc/150?img=1', - has_wechat: false, - created_at: '2024-01-01T00:00:00Z', - // 会员信息 - 免费用户 - subscription_type: 'free', - subscription_status: 'active', - subscription_end_date: null, - is_subscription_active: true, - subscription_days_left: 0 - }, - - // Pro 会员 - 手机号登录 - '13900139000': { - id: 2, - phone: '13900139000', - nickname: 'Pro会员', - email: 'pro@example.com', - avatar_url: 'https://i.pravatar.cc/150?img=2', - has_wechat: true, - created_at: '2024-01-15T00:00:00Z', - // 会员信息 - Pro 会员 - subscription_type: 'pro', - subscription_status: 'active', - subscription_end_date: '2025-12-31T23:59:59Z', - is_subscription_active: true, - subscription_days_left: 90 - }, - - // Max 会员 - 手机号登录 - '13700137000': { - id: 3, - phone: '13700137000', - nickname: 'Max会员', - email: 'max@example.com', - avatar_url: 'https://i.pravatar.cc/150?img=3', - has_wechat: false, - created_at: '2024-02-01T00:00:00Z', - // 会员信息 - Max 会员 - subscription_type: 'max', - subscription_status: 'active', - subscription_end_date: '2026-12-31T23:59:59Z', - is_subscription_active: true, - subscription_days_left: 365 - }, - - // 过期会员 - 测试过期状态 - '13600136000': { - id: 4, - phone: '13600136000', - nickname: '过期会员', - email: 'expired@example.com', - avatar_url: 'https://i.pravatar.cc/150?img=4', - has_wechat: false, - created_at: '2023-01-01T00:00:00Z', - // 会员信息 - 已过期 - subscription_type: 'pro', - subscription_status: 'expired', - subscription_end_date: '2024-01-01T00:00:00Z', - is_subscription_active: false, - subscription_days_left: -300 - } -}; - -// Mock 验证码存储(实际项目中应该在后端验证) -export const mockVerificationCodes = new Map(); - -// 生成随机6位验证码 -export function generateVerificationCode() { - return Math.floor(100000 + Math.random() * 900000).toString(); -} - -// 微信 session 存储 -export const mockWechatSessions = new Map(); - -// 生成微信 session ID -export function generateWechatSessionId() { - return 'wx_' + Math.random().toString(36).substring(2, 15); -} - -// ==================== 当前登录用户状态管理 ==================== -// Mock 模式下使用 localStorage 持久化登录状态 - -// 设置当前登录用户 -export function setCurrentUser(user) { - if (user) { - // 数据兼容处理:确保用户数据包含订阅信息字段 - const normalizedUser = { - ...user, - // 如果缺少订阅信息,添加默认值 - subscription_type: user.subscription_type || 'free', - subscription_status: user.subscription_status || 'active', - subscription_end_date: user.subscription_end_date || null, - is_subscription_active: user.is_subscription_active !== false, - subscription_days_left: user.subscription_days_left || 0 - }; - localStorage.setItem('mock_current_user', JSON.stringify(normalizedUser)); - } -} - -// 获取当前登录用户 -export function getCurrentUser() { - try { - const stored = localStorage.getItem('mock_current_user'); - if (stored) { - const user = JSON.parse(stored); - // console.log('[Mock State] 获取当前登录用户:', { // 已关闭:减少日志 - // id: user.id, - // phone: user.phone, - // nickname: user.nickname, - // subscription_type: user.subscription_type, - // subscription_status: user.subscription_status, - // subscription_days_left: user.subscription_days_left - // }); - return user; - } - } catch (error) { - console.error('[Mock State] 解析用户数据失败:', error); - } - // console.log('[Mock State] 未找到当前登录用户'); // 已关闭:减少日志 - return null; -} - -// 清除当前登录用户 -export function clearCurrentUser() { - localStorage.removeItem('mock_current_user'); - console.log('[Mock State] 清除当前登录用户'); -} diff --git a/src/mocks/handlers/account.js b/src/mocks/handlers/account.js deleted file mode 100644 index 15b1aa86..00000000 --- a/src/mocks/handlers/account.js +++ /dev/null @@ -1,938 +0,0 @@ -// src/mocks/handlers/account.js -import { http, HttpResponse, delay } from 'msw'; -import { getCurrentUser } from '../data/users'; -import { - mockWatchlist, - mockRealtimeQuotes, - mockFollowingEvents, - mockEventComments, - mockInvestmentPlans, - mockCalendarEvents, - mockSubscriptionCurrent, - getCalendarEventsByDateRange, - getFollowedEvents -} from '../data/account'; - -// 模拟网络延迟(毫秒) -const NETWORK_DELAY = 300; - -export const accountHandlers = [ - // ==================== 用户资料管理 ==================== - - // 1. 获取资料完整度 - http.get('/api/account/profile-completeness', async () => { - await delay(NETWORK_DELAY); - - const currentUser = getCurrentUser(); - if (!currentUser) { - return HttpResponse.json({ - success: false, - error: '用户未登录' - }, { status: 401 }); - } - - console.log('[Mock] 获取资料完整度:', currentUser); - - const isWechatUser = currentUser.has_wechat || !!currentUser.wechat_openid; - - const completeness = { - hasPassword: !!currentUser.password_hash || !isWechatUser, - hasPhone: !!currentUser.phone, - hasEmail: !!currentUser.email && currentUser.email.includes('@') && !currentUser.email.endsWith('@valuefrontier.temp'), - isWechatUser: isWechatUser - }; - - const totalItems = 3; - const completedItems = [completeness.hasPassword, completeness.hasPhone, completeness.hasEmail].filter(Boolean).length; - const completenessPercentage = Math.round((completedItems / totalItems) * 100); - - let needsAttention = false; - const missingItems = []; - - if (isWechatUser && completenessPercentage < 100) { - needsAttention = true; - if (!completeness.hasPassword) missingItems.push('登录密码'); - if (!completeness.hasPhone) missingItems.push('手机号'); - if (!completeness.hasEmail) missingItems.push('邮箱'); - } - - const result = { - success: true, - data: { - completeness, - completenessPercentage, - needsAttention, - missingItems, - isComplete: completedItems === totalItems, - showReminder: needsAttention - } - }; - - console.log('[Mock] 资料完整度结果:', result.data); - - return HttpResponse.json(result); - }), - - // 2. 更新用户资料 - http.put('/api/account/profile', async ({ request }) => { - await delay(NETWORK_DELAY); - - const currentUser = getCurrentUser(); - if (!currentUser) { - return HttpResponse.json({ - success: false, - error: '用户未登录' - }, { status: 401 }); - } - - const body = await request.json(); - console.log('[Mock] 更新用户资料:', body); - - Object.assign(currentUser, body); - - return HttpResponse.json({ - success: true, - message: '资料更新成功', - data: currentUser - }); - }), - - // 3. 获取用户资料 - http.get('/api/account/profile', async () => { - await delay(NETWORK_DELAY); - - const currentUser = getCurrentUser(); - if (!currentUser) { - return HttpResponse.json({ - success: false, - error: '用户未登录' - }, { status: 401 }); - } - - console.log('[Mock] 获取用户资料:', currentUser); - - return HttpResponse.json({ - success: true, - data: currentUser - }); - }), - - // ==================== 自选股管理 ==================== - - // 4. 获取自选股列表 - http.get('/api/account/watchlist', async () => { - await delay(NETWORK_DELAY); - - const currentUser = getCurrentUser(); - if (!currentUser) { - return HttpResponse.json( - { success: false, error: '未登录' }, - { status: 401 } - ); - } - - // console.log('[Mock] 获取自选股列表'); // 已关闭:减少日志 - - return HttpResponse.json({ - success: true, - data: mockWatchlist - }); - }), - - // 5. 获取自选股实时行情 - http.get('/api/account/watchlist/realtime', async () => { - await delay(200); - - const currentUser = getCurrentUser(); - if (!currentUser) { - return HttpResponse.json( - { success: false, error: '未登录' }, - { status: 401 } - ); - } - - console.log('[Mock] 获取自选股实时行情'); - - return HttpResponse.json({ - success: true, - data: mockRealtimeQuotes - }); - }), - - // 6. 添加自选股 - http.post('/api/account/watchlist', async ({ request }) => { - await delay(NETWORK_DELAY); - - const currentUser = getCurrentUser(); - if (!currentUser) { - return HttpResponse.json( - { success: false, error: '未登录' }, - { status: 401 } - ); - } - - const body = await request.json(); - const { stock_code, stock_name } = body; - - console.log('[Mock] 添加自选股:', { stock_code, stock_name }); - - const newItem = { - id: mockWatchlist.length + 1, - user_id: currentUser.id, - stock_code, - stock_name, - added_at: new Date().toISOString(), - industry: '未知', - current_price: null, - change_percent: null - }; - - mockWatchlist.push(newItem); - - // 同步添加到 mockRealtimeQuotes(导航栏自选股菜单使用此数组) - mockRealtimeQuotes.push({ - stock_code: stock_code, - stock_name: stock_name, - current_price: null, - change_percent: 0, - change: 0, - volume: 0, - turnover: 0, - high: 0, - low: 0, - open: 0, - prev_close: 0, - update_time: new Date().toTimeString().slice(0, 8) - }); - - return HttpResponse.json({ - success: true, - message: '添加成功', - data: newItem - }); - }), - - // 7. 删除自选股 - http.delete('/api/account/watchlist/:id', async ({ params }) => { - await delay(NETWORK_DELAY); - - const currentUser = getCurrentUser(); - if (!currentUser) { - return HttpResponse.json( - { success: false, error: '未登录' }, - { status: 401 } - ); - } - - const { id } = params; - console.log('[Mock] 删除自选股:', id); - - // 支持按 stock_code 或 id 匹配删除 - const index = mockWatchlist.findIndex(item => - item.stock_code === id || item.id === parseInt(id) - ); - - if (index !== -1) { - const stockCode = mockWatchlist[index].stock_code; - mockWatchlist.splice(index, 1); - - // 同步从 mockRealtimeQuotes 移除 - const quotesIndex = mockRealtimeQuotes.findIndex(item => item.stock_code === stockCode); - if (quotesIndex !== -1) { - mockRealtimeQuotes.splice(quotesIndex, 1); - } - } - - return HttpResponse.json({ - success: true, - message: '删除成功' - }); - }), - - // ==================== 事件关注管理 ==================== - - // 8. 获取关注的事件(使用内存状态动态返回) - http.get('/api/account/events/following', async () => { - await delay(NETWORK_DELAY); - - const currentUser = getCurrentUser(); - if (!currentUser) { - return HttpResponse.json( - { success: false, error: '未登录' }, - { status: 401 } - ); - } - - // 从内存存储获取已关注的事件列表 - const followedEvents = getFollowedEvents(); - - console.log('[Mock] 获取关注的事件, 数量:', followedEvents.length); - - return HttpResponse.json({ - success: true, - data: followedEvents - }); - }), - - // 9. 获取事件评论 - http.get('/api/account/events/comments', async () => { - await delay(NETWORK_DELAY); - - const currentUser = getCurrentUser(); - if (!currentUser) { - return HttpResponse.json( - { success: false, error: '未登录' }, - { status: 401 } - ); - } - - console.log('[Mock] 获取事件评论'); - - return HttpResponse.json({ - success: true, - data: mockEventComments - }); - }), - - // 10. 获取事件帖子(用户发布的评论/帖子) - http.get('/api/account/events/posts', async () => { - await delay(NETWORK_DELAY); - - const currentUser = getCurrentUser(); - if (!currentUser) { - return HttpResponse.json( - { success: false, error: '未登录' }, - { status: 401 } - ); - } - - console.log('[Mock] 获取事件帖子'); - - return HttpResponse.json({ - success: true, - data: mockEventComments // 复用 mockEventComments 数据 - }); - }), - - // ==================== 投资计划与复盘 ==================== - - // 10. 获取投资计划列表 - http.get('/api/account/investment-plans', async () => { - await delay(NETWORK_DELAY); - - const currentUser = getCurrentUser(); - if (!currentUser) { - return HttpResponse.json( - { success: false, error: '未登录' }, - { status: 401 } - ); - } - - console.log('[Mock] 获取投资计划列表'); - - return HttpResponse.json({ - success: true, - data: mockInvestmentPlans - }); - }), - - // 11. 创建投资计划 - http.post('/api/account/investment-plans', async ({ request }) => { - await delay(NETWORK_DELAY); - - const currentUser = getCurrentUser(); - if (!currentUser) { - return HttpResponse.json( - { success: false, error: '未登录' }, - { status: 401 } - ); - } - - const body = await request.json(); - console.log('[Mock] 创建投资计划:', body); - - // 生成唯一 ID(使用时间戳避免冲突) - const newId = Date.now(); - - const newPlan = { - id: newId, - user_id: currentUser.id, - ...body, - // 确保 target_date 字段存在(兼容前端发送的 date 字段) - target_date: body.target_date || body.date, - created_at: new Date().toISOString(), - updated_at: new Date().toISOString() - }; - - mockInvestmentPlans.push(newPlan); - console.log('[Mock] 新增计划/复盘,当前总数:', mockInvestmentPlans.length); - - return HttpResponse.json({ - success: true, - message: '创建成功', - data: newPlan - }); - }), - - // 12. 更新投资计划 - http.put('/api/account/investment-plans/:id', async ({ request, params }) => { - await delay(NETWORK_DELAY); - - const currentUser = getCurrentUser(); - if (!currentUser) { - return HttpResponse.json( - { success: false, error: '未登录' }, - { status: 401 } - ); - } - - const { id } = params; - const body = await request.json(); - - console.log('[Mock] 更新投资计划:', { id, body }); - - const index = mockInvestmentPlans.findIndex(plan => plan.id === parseInt(id)); - if (index !== -1) { - mockInvestmentPlans[index] = { - ...mockInvestmentPlans[index], - ...body, - updated_at: new Date().toISOString() - }; - - return HttpResponse.json({ - success: true, - message: '更新成功', - data: mockInvestmentPlans[index] - }); - } - - return HttpResponse.json({ - success: false, - error: '计划不存在' - }, { status: 404 }); - }), - - // 13. 删除投资计划 - http.delete('/api/account/investment-plans/:id', async ({ params }) => { - await delay(NETWORK_DELAY); - - const currentUser = getCurrentUser(); - if (!currentUser) { - return HttpResponse.json( - { success: false, error: '未登录' }, - { status: 401 } - ); - } - - const { id } = params; - console.log('[Mock] 删除投资计划:', id); - - const index = mockInvestmentPlans.findIndex(plan => plan.id === parseInt(id)); - if (index !== -1) { - mockInvestmentPlans.splice(index, 1); - } - - return HttpResponse.json({ - success: true, - message: '删除成功' - }); - }), - - // ==================== 投资日历 ==================== - - // 14. 获取日历事件(可选日期范围)- 合并投资计划和日历事件 - http.get('/api/account/calendar/events', async ({ request }) => { - await delay(NETWORK_DELAY); - - // Mock 模式下允许无登录访问,使用默认用户 id: 1 - const currentUser = getCurrentUser() || { id: 1 }; - - const url = new URL(request.url); - const startDate = url.searchParams.get('start_date'); - const endDate = url.searchParams.get('end_date'); - - console.log('[Mock] 获取日历事件:', { startDate, endDate }); - - // 1. 获取日历事件 - let calendarEvents = mockCalendarEvents; - if (startDate && endDate) { - calendarEvents = getCalendarEventsByDateRange(currentUser.id, startDate, endDate); - } - - // 2. 获取投资计划和复盘,转换为日历事件格式 - // Mock 模式:不过滤 user_id,显示所有 mock 数据(方便开发测试) - const investmentPlansAsEvents = mockInvestmentPlans - .map(plan => ({ - id: plan.id, - user_id: plan.user_id, - title: plan.title, - date: plan.target_date || plan.date, - event_date: plan.target_date || plan.date, - type: plan.type, // 'plan' or 'review' - category: plan.type === 'plan' ? 'investment_plan' : 'investment_review', - description: plan.content || '', - importance: 3, // 默认重要度 - source: 'user', // 标记为用户创建 - stocks: plan.stocks || [], - tags: plan.tags || [], - status: plan.status, - created_at: plan.created_at, - updated_at: plan.updated_at - })); - - // 3. 合并两个数据源 - const allEvents = [...calendarEvents, ...investmentPlansAsEvents]; - - // 4. 如果提供了日期范围,对合并后的数据进行过滤 - let filteredEvents = allEvents; - if (startDate && endDate) { - const start = new Date(startDate); - const end = new Date(endDate); - filteredEvents = allEvents.filter(event => { - const eventDate = new Date(event.date || event.event_date); - return eventDate >= start && eventDate <= end; - }); - } - - // 5. 按日期倒序排序(最新的在前面) - filteredEvents.sort((a, b) => { - const dateA = new Date(a.date || a.event_date); - const dateB = new Date(b.date || b.event_date); - return dateB - dateA; // 倒序:新日期在前 - }); - - // 打印今天的事件(方便调试) - const today = new Date().toISOString().split('T')[0]; - const todayEvents = filteredEvents.filter(e => - (e.date === today || e.event_date === today) - ); - - console.log('[Mock] 日历事件详情:', { - currentUserId: currentUser.id, - calendarEvents: calendarEvents.length, - investmentPlansAsEvents: investmentPlansAsEvents.length, - total: filteredEvents.length, - plansCount: filteredEvents.filter(e => e.type === 'plan').length, - reviewsCount: filteredEvents.filter(e => e.type === 'review').length, - today, - todayEventsCount: todayEvents.length, - todayEventTitles: todayEvents.map(e => `[${e.type}] ${e.title}`) - }); - - return HttpResponse.json({ - success: true, - data: filteredEvents - }); - }), - - // 15. 创建日历事件 - http.post('/api/account/calendar/events', async ({ request }) => { - await delay(NETWORK_DELAY); - - const currentUser = getCurrentUser(); - if (!currentUser) { - return HttpResponse.json( - { success: false, error: '未登录' }, - { status: 401 } - ); - } - - const body = await request.json(); - console.log('[Mock] 创建日历事件:', body); - - const newEvent = { - id: mockCalendarEvents.length + 401, - user_id: currentUser.id, - ...body, - source: 'user', // 用户创建的事件标记为 'user' - created_at: new Date().toISOString() - }; - - mockCalendarEvents.push(newEvent); - - return HttpResponse.json({ - success: true, - message: '创建成功', - data: newEvent - }); - }), - - // 16. 更新日历事件 - http.put('/api/account/calendar/events/:id', async ({ request, params }) => { - await delay(NETWORK_DELAY); - - const currentUser = getCurrentUser(); - if (!currentUser) { - return HttpResponse.json( - { success: false, error: '未登录' }, - { status: 401 } - ); - } - - const { id } = params; - const body = await request.json(); - - console.log('[Mock] 更新日历事件:', { id, body }); - - const index = mockCalendarEvents.findIndex(event => event.id === parseInt(id)); - if (index !== -1) { - mockCalendarEvents[index] = { - ...mockCalendarEvents[index], - ...body - }; - - return HttpResponse.json({ - success: true, - message: '更新成功', - data: mockCalendarEvents[index] - }); - } - - return HttpResponse.json({ - success: false, - error: '事件不存在' - }, { status: 404 }); - }), - - // 17. 删除日历事件 - http.delete('/api/account/calendar/events/:id', async ({ params }) => { - await delay(NETWORK_DELAY); - - const currentUser = getCurrentUser(); - if (!currentUser) { - return HttpResponse.json( - { success: false, error: '未登录' }, - { status: 401 } - ); - } - - const { id } = params; - console.log('[Mock] 删除日历事件:', id); - - const index = mockCalendarEvents.findIndex(event => event.id === parseInt(id)); - if (index !== -1) { - mockCalendarEvents.splice(index, 1); - } - - return HttpResponse.json({ - success: true, - message: '删除成功' - }); - }), - - // ==================== 订阅信息 ==================== - - // 18. 获取订阅信息 - http.get('/api/subscription/info', async () => { - await delay(NETWORK_DELAY); - - const currentUser = getCurrentUser(); - - if (!currentUser) { - return HttpResponse.json({ - success: true, - data: { - type: 'free', - status: 'active', - is_active: true, - days_left: 0, - end_date: null - } - }); - } - - console.log('[Mock] 获取订阅信息:', currentUser); - - const subscriptionInfo = { - type: currentUser.subscription_type || 'free', - status: currentUser.subscription_status || 'active', - is_active: currentUser.is_subscription_active !== false, - days_left: currentUser.subscription_days_left || 0, - end_date: currentUser.subscription_end_date || null - }; - - console.log('[Mock] 订阅信息结果:', subscriptionInfo); - - return HttpResponse.json({ - success: true, - data: subscriptionInfo - }); - }), - - // 19. 获取当前订阅详情 - http.get('/api/subscription/current', async () => { - await delay(NETWORK_DELAY); - - const currentUser = getCurrentUser(); - if (!currentUser) { - console.warn('[Mock API] 获取订阅详情失败: 用户未登录'); - return HttpResponse.json( - { success: false, error: '未登录' }, - { status: 401 } - ); - } - - // 基于当前用户的订阅类型返回详情 - const userSubscriptionType = (currentUser.subscription_type || 'free').toLowerCase(); - - const subscriptionDetails = { - ...mockSubscriptionCurrent, - type: userSubscriptionType, - status: currentUser.subscription_status || 'active', - is_active: currentUser.is_subscription_active !== false, - days_left: currentUser.subscription_days_left || 0, - end_date: currentUser.subscription_end_date || null - }; - - return HttpResponse.json({ - success: true, - data: subscriptionDetails - }); - }), - - // 20. 获取订阅权限 - http.get('/api/subscription/permissions', async () => { - await delay(NETWORK_DELAY); - - const currentUser = getCurrentUser(); - - if (!currentUser) { - return HttpResponse.json({ - success: true, - data: { - permissions: { - 'related_stocks': false, - 'related_concepts': false, - 'transmission_chain': false, - 'historical_events': 'limited', - 'concept_html_detail': false, - 'concept_stats_panel': false, - 'concept_related_stocks': false, - 'concept_timeline': false, - 'hot_stocks': false - } - } - }); - } - - const subscriptionType = (currentUser.subscription_type || 'free').toLowerCase(); - - let permissions = {}; - - if (subscriptionType === 'free') { - permissions = { - 'related_stocks': false, - 'related_concepts': false, - 'transmission_chain': false, - 'historical_events': 'limited', - 'concept_html_detail': false, - 'concept_stats_panel': false, - 'concept_related_stocks': false, - 'concept_timeline': false, - 'hot_stocks': false - }; - } else if (subscriptionType === 'pro') { - permissions = { - 'related_stocks': true, - 'related_concepts': true, - 'transmission_chain': false, - 'historical_events': 'full', - 'concept_html_detail': true, - 'concept_stats_panel': true, - 'concept_related_stocks': true, - 'concept_timeline': false, - 'hot_stocks': true - }; - } else if (subscriptionType === 'max') { - permissions = { - 'related_stocks': true, - 'related_concepts': true, - 'transmission_chain': true, - 'historical_events': 'full', - 'concept_html_detail': true, - 'concept_stats_panel': true, - 'concept_related_stocks': true, - 'concept_timeline': true, - 'hot_stocks': true - }; - } - - console.log('[Mock] 订阅权限:', { subscriptionType, permissions }); - - return HttpResponse.json({ - success: true, - data: { - subscription_type: subscriptionType, - permissions - } - }); - }), - - // ==================== 账号绑定管理 ==================== - - // 手机号发送验证码 - http.post('/api/account/phone/send-code', async ({ request }) => { - await delay(NETWORK_DELAY); - const body = await request.json(); - console.log('[Mock] 发送手机验证码:', body.phone); - return HttpResponse.json({ - success: true, - message: '验证码已发送' - }); - }), - - // 手机号绑定 - http.post('/api/account/phone/bind', async ({ request }) => { - await delay(NETWORK_DELAY); - const body = await request.json(); - console.log('[Mock] 绑定手机号:', body.phone); - return HttpResponse.json({ - success: true, - message: '手机号绑定成功', - data: { phone: body.phone, phone_confirmed: true } - }); - }), - - // 手机号解绑 - http.post('/api/account/phone/unbind', async () => { - await delay(NETWORK_DELAY); - console.log('[Mock] 解绑手机号'); - return HttpResponse.json({ - success: true, - message: '手机号解绑成功' - }); - }), - - // 邮箱发送验证码 - http.post('/api/account/email/send-bind-code', async ({ request }) => { - await delay(NETWORK_DELAY); - const body = await request.json(); - console.log('[Mock] 发送邮箱验证码:', body.email); - return HttpResponse.json({ - success: true, - message: '验证码已发送' - }); - }), - - // 邮箱绑定 - http.post('/api/account/email/bind', async ({ request }) => { - await delay(NETWORK_DELAY); - const body = await request.json(); - console.log('[Mock] 绑定邮箱:', body.email); - return HttpResponse.json({ - success: true, - message: '邮箱绑定成功', - user: { email: body.email, email_confirmed: true } - }); - }), - - // 微信获取二维码 - http.get('/api/account/wechat/qrcode', async () => { - await delay(NETWORK_DELAY); - console.log('[Mock] 获取微信绑定二维码'); - return HttpResponse.json({ - success: true, - auth_url: 'https://open.weixin.qq.com/connect/qrconnect?mock=true', - session_id: 'mock_session_' + Date.now() - }); - }), - - // 微信绑定检查 - http.post('/api/account/wechat/check', async ({ request }) => { - await delay(NETWORK_DELAY); - const body = await request.json(); - console.log('[Mock] 检查微信绑定状态:', body.session_id); - // 模拟绑定成功 - return HttpResponse.json({ - success: true, - status: 'bind_ready' - }); - }), - - // 微信解绑 - http.post('/api/account/wechat/unbind', async () => { - await delay(NETWORK_DELAY); - console.log('[Mock] 解绑微信'); - return HttpResponse.json({ - success: true, - message: '微信解绑成功' - }); - }), - - // 21. 获取订阅套餐列表 - http.get('/api/subscription/plans', async () => { - await delay(NETWORK_DELAY); - - const plans = [ - { - id: 1, - name: 'pro', - display_name: 'Pro 专业版', - description: '事件关联股票深度分析 | 历史事件智能对比复盘 | 事件概念关联与挖掘 | 概念板块个股追踪 | 概念深度研报与解读 | 个股异动实时预警', - monthly_price: 299, - yearly_price: 2699, - pricing_options: [ - { cycle_key: 'monthly', label: '月付', months: 1, price: 299, original_price: null, discount_percent: 0 }, - { cycle_key: 'quarterly', label: '季付', months: 3, price: 799, original_price: 897, discount_percent: 11 }, - { cycle_key: 'semiannual', label: '半年付', months: 6, price: 1499, original_price: 1794, discount_percent: 16 }, - { cycle_key: 'yearly', label: '年付', months: 12, price: 2699, original_price: 3588, discount_percent: 25 } - ], - features: [ - '新闻信息流', - '历史事件对比', - '事件传导链分析(AI)', - '事件-相关标的分析', - '相关概念展示', - 'AI复盘功能', - '企业概览', - '个股深度分析(AI) - 50家/月', - '高效数据筛选工具', - '概念中心(548大概念)', - '历史时间轴查询 - 100天', - '涨停板块数据分析', - '个股涨停分析' - ], - sort_order: 1 - }, - { - id: 2, - name: 'max', - display_name: 'Max 旗舰版', - description: '包含Pro版全部功能 | 事件传导链路智能分析 | 概念演变时间轴追溯 | 个股全方位深度研究 | 价小前投研助手无限使用 | 新功能优先体验权 | 专属客服一对一服务', - monthly_price: 599, - yearly_price: 5399, - pricing_options: [ - { cycle_key: 'monthly', label: '月付', months: 1, price: 599, original_price: null, discount_percent: 0 }, - { cycle_key: 'quarterly', label: '季付', months: 3, price: 1599, original_price: 1797, discount_percent: 11 }, - { cycle_key: 'semiannual', label: '半年付', months: 6, price: 2999, original_price: 3594, discount_percent: 17 }, - { cycle_key: 'yearly', label: '年付', months: 12, price: 5399, original_price: 7188, discount_percent: 25 } - ], - features: [ - '新闻信息流', - '历史事件对比', - '事件传导链分析(AI)', - '事件-相关标的分析', - '相关概念展示', - '板块深度分析(AI)', - 'AI复盘功能', - '企业概览', - '个股深度分析(AI) - 无限制', - '高效数据筛选工具', - '概念中心(548大概念)', - '历史时间轴查询 - 无限制', - '概念高频更新', - '涨停板块数据分析', - '个股涨停分析' - ], - sort_order: 2 - } - ]; - - console.log('[Mock] 获取订阅套餐列表:', plans.length, '个套餐'); - - return HttpResponse.json({ - success: true, - data: plans - }); - }), -]; diff --git a/src/mocks/handlers/agent.js b/src/mocks/handlers/agent.js deleted file mode 100644 index 0ce9f5d3..00000000 --- a/src/mocks/handlers/agent.js +++ /dev/null @@ -1,606 +0,0 @@ -// src/mocks/handlers/agent.js -// Agent Chat API Mock Handlers - -import { http, HttpResponse, delay } from 'msw'; - -// 模拟会话数据 -const mockSessions = [ - { - session_id: 'session-001', - title: '贵州茅台投资分析', - created_at: new Date(Date.now() - 2 * 24 * 60 * 60 * 1000).toISOString(), - timestamp: new Date(Date.now() - 2 * 24 * 60 * 60 * 1000).toISOString(), - message_count: 15, - }, - { - session_id: 'session-002', - title: '新能源板块研究', - created_at: new Date(Date.now() - 5 * 24 * 60 * 60 * 1000).toISOString(), - timestamp: new Date(Date.now() - 5 * 24 * 60 * 60 * 1000).toISOString(), - message_count: 8, - }, - { - session_id: 'session-003', - title: '半导体行业分析', - created_at: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString(), - timestamp: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString(), - message_count: 12, - }, -]; - -// 模拟历史消息数据 -const mockHistory = { - 'session-001': [ - { - message_type: 'user', - message: '分析一下贵州茅台的投资价值', - timestamp: new Date(Date.now() - 2 * 24 * 60 * 60 * 1000).toISOString(), - plan: null, - steps: null, - }, - { - message_type: 'assistant', - message: - '# 贵州茅台投资价值分析\n\n根据最新数据,贵州茅台(600519.SH)具有以下投资亮点:\n\n## 基本面分析\n- **营收增长**:2024年Q3营收同比增长12.5%\n- **净利润率**:保持在50%以上的高水平\n- **ROE**:连续10年超过20%\n\n## 估值分析\n- **PE(TTM)**:35.6倍,略高于历史中位数\n- **PB**:10.2倍,处于合理区间\n\n## 投资建议\n建议关注价格回调机会,长期配置价值显著。', - timestamp: new Date(Date.now() - 2 * 24 * 60 * 60 * 1000 + 5000).toISOString(), - plan: JSON.stringify({ - goal: '分析贵州茅台的投资价值', - steps: [ - { step: 1, action: '获取贵州茅台最新股价和财务数据', reasoning: '需要基础数据支持分析' }, - { step: 2, action: '分析公司基本面和盈利能力', reasoning: '评估公司质量' }, - { step: 3, action: '对比行业估值水平', reasoning: '判断估值合理性' }, - { step: 4, action: '给出投资建议', reasoning: '综合判断投资价值' }, - ], - }), - steps: JSON.stringify([ - { - tool: 'get_stock_info', - status: 'success', - result: '获取到贵州茅台最新数据:股价1850元,市值2.3万亿', - }, - { - tool: 'analyze_financials', - status: 'success', - result: '财务分析完成:营收增长稳健,利润率行业领先', - }, - ]), - }, - ], -}; - -// 生成模拟的 Agent 响应 -function generateAgentResponse(message, sessionId) { - const responses = { - 包含: { - 贵州茅台: { - plan: { - goal: '分析贵州茅台相关信息', - steps: [ - { step: 1, action: '搜索贵州茅台最新新闻', reasoning: '了解最新动态' }, - { step: 2, action: '获取股票实时行情', reasoning: '查看当前价格走势' }, - { step: 3, action: '分析财务数据', reasoning: '评估基本面' }, - { step: 4, action: '生成投资建议', reasoning: '综合判断' }, - ], - }, - step_results: [ - { - tool: 'search_news', - status: 'success', - result: '找到5条相关新闻:茅台Q3业绩超预期...', - }, - { - tool: 'get_stock_quote', - status: 'success', - result: '当前价格:1850元,涨幅:+2.3%', - }, - { - tool: 'analyze_financials', - status: 'success', - result: 'ROE: 25.6%, 净利润率: 52.3%', - }, - ], - final_summary: - '# 贵州茅台分析报告\n\n## 最新动态\n茅台Q3业绩超预期,营收增长稳健。\n\n## 行情分析\n当前价格1850元,今日上涨2.3%,成交量活跃。\n\n## 财务表现\n- ROE: 25.6%(行业领先)\n- 净利润率: 52.3%(极高水平)\n- 营收增长: 12.5% YoY\n\n## 投资建议\n**推荐关注**:基本面优秀,估值合理,建议逢低布局。', - }, - 新能源: { - plan: { - goal: '分析新能源行业', - steps: [ - { step: 1, action: '搜索新能源行业新闻', reasoning: '了解行业动态' }, - { step: 2, action: '获取新能源概念股', reasoning: '找到相关标的' }, - { step: 3, action: '分析行业趋势', reasoning: '判断投资机会' }, - ], - }, - step_results: [ - { - tool: 'search_news', - status: 'success', - result: '新能源政策利好频出,行业景气度提升', - }, - { - tool: 'get_concept_stocks', - status: 'success', - result: '新能源板块共182只个股,今日平均涨幅3.2%', - }, - ], - final_summary: - '# 新能源行业分析\n\n## 行业动态\n政策利好频出,行业景气度持续提升。\n\n## 板块表现\n新能源板块今日强势上涨,平均涨幅3.2%。\n\n## 投资机会\n建议关注龙头企业和细分赛道领导者。', - }, - }, - 默认: { - plan: { - goal: '回答用户问题', - steps: [ - { step: 1, action: '理解用户意图', reasoning: '准确把握需求' }, - { step: 2, action: '搜索相关信息', reasoning: '获取数据支持' }, - { step: 3, action: '生成回复', reasoning: '提供专业建议' }, - ], - }, - step_results: [ - { - tool: 'search_related_info', - status: 'success', - result: '已找到相关信息', - }, - ], - final_summary: `我已经收到您的问题:"${message}"\n\n作为您的 AI 投研助手,我可以帮您:\n- 📊 分析股票基本面和技术面\n- 🔥 追踪市场热点和板块动态\n- 📈 研究行业趋势和投资机会\n- 📰 汇总最新财经新闻和研报\n\n请告诉我您想了解哪方面的信息?`, - }, - }; - - // 根据关键词匹配响应 - for (const keyword in responses.包含) { - if (message.includes(keyword)) { - return responses.包含[keyword]; - } - } - - return responses.默认; -} - -// Agent Chat API Handlers -export const agentHandlers = [ - // POST /mcp/agent/chat - 发送消息 - http.post('/mcp/agent/chat', async ({ request }) => { - await delay(800); // 模拟网络延迟 - - const body = await request.json(); - const { message, session_id, user_id, subscription_type } = body; - - // 模拟权限检查(仅 max 用户可用) - if (subscription_type !== 'max') { - return HttpResponse.json( - { - success: false, - error: '很抱歉,「价小前投研」功能仅对 Max 订阅用户开放。请升级您的订阅以使用此功能。', - }, - { status: 403 } - ); - } - - // 生成或使用现有 session_id - const responseSessionId = session_id || `session-${Date.now()}`; - - // 根据消息内容生成响应 - const response = generateAgentResponse(message, responseSessionId); - - return HttpResponse.json({ - success: true, - message: '处理成功', - session_id: responseSessionId, - plan: response.plan, - steps: response.step_results, - final_answer: response.final_summary, - metadata: { - model: body.model || 'kimi-k2-thinking', - timestamp: new Date().toISOString(), - }, - }); - }), - - // GET /mcp/agent/sessions - 获取会话列表 - http.get('/mcp/agent/sessions', async ({ request }) => { - await delay(300); - - const url = new URL(request.url); - const userId = url.searchParams.get('user_id'); - const limit = parseInt(url.searchParams.get('limit') || '50'); - - // 返回模拟的会话列表 - const sessions = mockSessions.slice(0, limit); - - return HttpResponse.json({ - success: true, - data: sessions, - count: sessions.length, - }); - }), - - // GET /mcp/agent/history/:session_id - 获取会话历史 - http.get('/mcp/agent/history/:session_id', async ({ params }) => { - await delay(400); - - const { session_id } = params; - - // 返回模拟的历史消息 - const history = mockHistory[session_id] || []; - - return HttpResponse.json({ - success: true, - data: history, - count: history.length, - }); - }), - - // ==================== 投研会议室 API Handlers ==================== - - // GET /mcp/agent/meeting/roles - 获取会议角色配置 - http.get('/mcp/agent/meeting/roles', async () => { - await delay(200); - - return HttpResponse.json({ - success: true, - roles: [ - { - id: 'buffett', - name: '巴菲特', - nickname: '唱多者', - role_type: 'bull', - avatar: '/avatars/buffett.png', - color: '#10B981', - description: '主观多头,善于分析事件的潜在利好和长期价值', - }, - { - id: 'big_short', - name: '大空头', - nickname: '大空头', - role_type: 'bear', - avatar: '/avatars/big_short.png', - color: '#EF4444', - description: '善于分析事件和财报中的风险因素,帮助投资者避雷', - }, - { - id: 'simons', - name: '量化分析员', - nickname: '西蒙斯', - role_type: 'quant', - avatar: '/avatars/simons.png', - color: '#3B82F6', - description: '中性立场,使用量化分析工具分析技术指标', - }, - { - id: 'leek', - name: '韭菜', - nickname: '牢大', - role_type: 'retail', - avatar: '/avatars/leek.png', - color: '#F59E0B', - description: '贪婪又讨厌亏损,热爱追涨杀跌的典型散户', - }, - { - id: 'fund_manager', - name: '基金经理', - nickname: '决策者', - role_type: 'manager', - avatar: '/avatars/fund_manager.png', - color: '#8B5CF6', - description: '总结其他人的发言做出最终决策', - }, - ], - }); - }), - - // POST /mcp/agent/meeting/start - 启动投研会议 - http.post('/mcp/agent/meeting/start', async ({ request }) => { - await delay(2000); // 模拟多角色讨论耗时 - - const body = await request.json(); - const { topic, user_id } = body; - - const sessionId = `meeting-${Date.now()}`; - const timestamp = new Date().toISOString(); - - // 生成模拟的多角色讨论消息 - const messages = [ - { - role_id: 'buffett', - role_name: '巴菲特', - nickname: '唱多者', - avatar: '/avatars/buffett.png', - color: '#10B981', - content: `关于「${topic}」,我认为这里存在显著的投资机会。从价值投资的角度看,我们应该关注以下几点:\n\n1. **长期价值**:该标的具有较强的护城河\n2. **盈利能力**:ROE持续保持在较高水平\n3. **管理层质量**:管理团队稳定且执行力强\n\n我的观点是**看多**,建议逢低布局。`, - timestamp, - round_number: 1, - }, - { - role_id: 'big_short', - role_name: '大空头', - nickname: '大空头', - avatar: '/avatars/big_short.png', - color: '#EF4444', - content: `等等,让我泼点冷水。关于「${topic}」,市场似乎过于乐观了:\n\n⚠️ **风险提示**:\n1. 当前估值处于历史高位,安全边际不足\n2. 行业竞争加剧,利润率面临压力\n3. 宏观环境不确定性增加\n\n建议投资者**保持谨慎**,不要追高。`, - timestamp: new Date(Date.now() + 1000).toISOString(), - round_number: 1, - }, - { - role_id: 'simons', - role_name: '量化分析员', - nickname: '西蒙斯', - avatar: '/avatars/simons.png', - color: '#3B82F6', - content: `从量化角度分析「${topic}」:\n\n📊 **技术指标**:\n- MACD:金叉形态,动能向上\n- RSI:58,处于中性区域\n- 均线:5日>10日>20日,多头排列\n\n📈 **资金面**:\n- 主力资金:近5日净流入2.3亿\n- 北向资金:持续加仓\n\n**结论**:短期技术面偏多,但需关注60日均线支撑。`, - timestamp: new Date(Date.now() + 2000).toISOString(), - round_number: 1, - }, - { - role_id: 'leek', - role_name: '韭菜', - nickname: '牢大', - avatar: '/avatars/leek.png', - color: '#F59E0B', - content: `哇!「${topic}」看起来要涨啊!\n\n🚀 我觉得必须满仓干!隔壁老王都赚翻了!\n\n不过话说回来...万一跌了怎么办?会不会套住?\n\n算了不管了,先冲一把再说!错过这村就没这店了!\n\n(内心OS:希望别当接盘侠...)`, - timestamp: new Date(Date.now() + 3000).toISOString(), - round_number: 1, - }, - { - role_id: 'fund_manager', - role_name: '基金经理', - nickname: '决策者', - avatar: '/avatars/fund_manager.png', - color: '#8B5CF6', - content: `## 投资建议总结\n\n综合各方观点,对于「${topic}」,我的判断如下:\n\n### 综合评估\n多空双方都提出了有价值的观点。技术面短期偏多,但估值确实需要关注。\n\n### 关键观点\n- ✅ 基本面优质,长期价值明确\n- ⚠️ 短期估值偏高,需要耐心等待\n- 📊 技术面处于上升趋势\n\n### 风险提示\n注意仓位控制,避免追高\n\n### 操作建议\n**观望为主**,等待回调至支撑位再考虑建仓\n\n### 信心指数:7/10`, - timestamp: new Date(Date.now() + 4000).toISOString(), - round_number: 1, - is_conclusion: true, - }, - ]; - - return HttpResponse.json({ - success: true, - session_id: sessionId, - messages, - round_number: 1, - is_concluded: true, - conclusion: messages[messages.length - 1], - }); - }), - - // POST /mcp/agent/meeting/continue - 继续会议讨论 - http.post('/mcp/agent/meeting/continue', async ({ request }) => { - await delay(1500); - - const body = await request.json(); - const { topic, user_message, conversation_history } = body; - - const roundNumber = Math.floor(conversation_history.length / 5) + 2; - const timestamp = new Date().toISOString(); - - const messages = []; - - // 如果用户有插话,添加用户消息 - if (user_message) { - messages.push({ - role_id: 'user', - role_name: '用户', - nickname: '你', - avatar: '', - color: '#6366F1', - content: user_message, - timestamp, - round_number: roundNumber, - }); - } - - // 生成新一轮讨论 - messages.push( - { - role_id: 'buffett', - role_name: '巴菲特', - nickname: '唱多者', - avatar: '/avatars/buffett.png', - color: '#10B981', - content: `感谢用户的补充。${user_message ? `关于"${user_message}",` : ''}我依然坚持看多的观点。从更长远的角度看,短期波动不影响长期价值。`, - timestamp: new Date(Date.now() + 1000).toISOString(), - round_number: roundNumber, - }, - { - role_id: 'big_short', - role_name: '大空头', - nickname: '大空头', - avatar: '/avatars/big_short.png', - color: '#EF4444', - content: `用户提出了很好的问题。我要再次强调风险控制的重要性。当前市场情绪过热,建议保持警惕。`, - timestamp: new Date(Date.now() + 2000).toISOString(), - round_number: roundNumber, - }, - { - role_id: 'fund_manager', - role_name: '基金经理', - nickname: '决策者', - avatar: '/avatars/fund_manager.png', - color: '#8B5CF6', - content: `## 第${roundNumber}轮讨论总结\n\n经过进一步讨论,我维持之前的判断:\n\n- 短期观望为主\n- 中长期可以考虑分批建仓\n- 严格控制仓位,设好止损\n\n**信心指数:7.5/10**\n\n会议到此结束,感谢各位的参与!`, - timestamp: new Date(Date.now() + 3000).toISOString(), - round_number: roundNumber, - is_conclusion: true, - } - ); - - return HttpResponse.json({ - success: true, - session_id: body.session_id, - messages, - round_number: roundNumber, - is_concluded: true, - conclusion: messages[messages.length - 1], - }); - }), - - // POST /mcp/agent/meeting/stream - 流式会议接口(V2) - http.post('/mcp/agent/meeting/stream', async ({ request }) => { - const body = await request.json(); - const { topic, user_id } = body; - - const sessionId = `meeting-${Date.now()}`; - - // 定义会议角色和他们的消息 - const roleMessages = [ - { - role_id: 'buffett', - role_name: '巴菲特', - content: `关于「${topic}」,我认为这里存在显著的投资机会。从价值投资的角度看,我们应该关注以下几点:\n\n1. **长期价值**:该标的具有较强的护城河\n2. **盈利能力**:ROE持续保持在较高水平\n3. **管理层质量**:管理团队稳定且执行力强\n\n我的观点是**看多**,建议逢低布局。`, - tools: [ - { name: 'search_china_news', result: { articles: [{ title: '相关新闻1' }, { title: '相关新闻2' }] } }, - { name: 'get_stock_basic_info', result: { pe: 25.6, pb: 3.2, roe: 18.5 } }, - ], - }, - { - role_id: 'big_short', - role_name: '大空头', - content: `等等,让我泼点冷水。关于「${topic}」,市场似乎过于乐观了:\n\n⚠️ **风险提示**:\n1. 当前估值处于历史高位,安全边际不足\n2. 行业竞争加剧,利润率面临压力\n3. 宏观环境不确定性增加\n\n建议投资者**保持谨慎**,不要追高。`, - tools: [ - { name: 'get_stock_financial_index', result: { debt_ratio: 45.2, current_ratio: 1.8 } }, - ], - }, - { - role_id: 'simons', - role_name: '量化分析员', - content: `从量化角度分析「${topic}」:\n\n📊 **技术指标**:\n- MACD:金叉形态,动能向上\n- RSI:58,处于中性区域\n- 均线:5日>10日>20日,多头排列\n\n📈 **资金面**:\n- 主力资金:近5日净流入2.3亿\n- 北向资金:持续加仓\n\n**结论**:短期技术面偏多,但需关注60日均线支撑。`, - tools: [ - { name: 'get_stock_trade_data', result: { volume: 1234567, turnover: 5.2 } }, - { name: 'get_concept_statistics', result: { concepts: ['AI概念', '半导体'], avg_change: 2.3 } }, - ], - }, - { - role_id: 'leek', - role_name: '韭菜', - content: `哇!「${topic}」看起来要涨啊!\n\n🚀 我觉得必须满仓干!隔壁老王都赚翻了!\n\n不过话说回来...万一跌了怎么办?会不会套住?\n\n算了不管了,先冲一把再说!错过这村就没这店了!\n\n(内心OS:希望别当接盘侠...)`, - tools: [], // 韭菜不用工具 - }, - { - role_id: 'fund_manager', - role_name: '基金经理', - content: `## 投资建议总结\n\n综合各方观点,对于「${topic}」,我的判断如下:\n\n### 综合评估\n多空双方都提出了有价值的观点。技术面短期偏多,但估值确实需要关注。\n\n### 关键观点\n- ✅ 基本面优质,长期价值明确\n- ⚠️ 短期估值偏高,需要耐心等待\n- 📊 技术面处于上升趋势\n\n### 风险提示\n注意仓位控制,避免追高\n\n### 操作建议\n**观望为主**,等待回调至支撑位再考虑建仓\n\n### 信心指数:7/10`, - tools: [ - { name: 'search_research_reports', result: { reports: [{ title: '深度研报1' }] } }, - ], - is_conclusion: true, - }, - ]; - - // 创建 SSE 流 - const encoder = new TextEncoder(); - const stream = new ReadableStream({ - async start(controller) { - // 发送 session_start - controller.enqueue(encoder.encode(`data: ${JSON.stringify({ - type: 'session_start', - session_id: sessionId, - })}\n\n`)); - - await delay(300); - - // 发送 order_decided - controller.enqueue(encoder.encode(`data: ${JSON.stringify({ - type: 'order_decided', - order: roleMessages.map(r => r.role_id), - })}\n\n`)); - - await delay(300); - - // 依次发送每个角色的消息 - for (const role of roleMessages) { - // speaking_start - controller.enqueue(encoder.encode(`data: ${JSON.stringify({ - type: 'speaking_start', - role_id: role.role_id, - role_name: role.role_name, - })}\n\n`)); - - await delay(200); - - // 发送工具调用 - const toolCallResults = []; - for (const tool of role.tools) { - const toolCallId = `tc-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`; - const execTime = 0.5 + Math.random() * 0.5; - - // tool_call_start - controller.enqueue(encoder.encode(`data: ${JSON.stringify({ - type: 'tool_call_start', - role_id: role.role_id, - tool_call_id: toolCallId, - tool_name: tool.name, - arguments: {}, - })}\n\n`)); - - await delay(500); - - // tool_call_result - controller.enqueue(encoder.encode(`data: ${JSON.stringify({ - type: 'tool_call_result', - role_id: role.role_id, - tool_call_id: toolCallId, - tool_name: tool.name, - result: { success: true, data: tool.result }, - status: 'success', - execution_time: execTime, - })}\n\n`)); - - toolCallResults.push({ - tool_call_id: toolCallId, - tool_name: tool.name, - result: { success: true, data: tool.result }, - status: 'success', - execution_time: execTime, - }); - - await delay(200); - } - - // 流式发送内容 - const chunks = role.content.match(/.{1,20}/g) || []; - for (const chunk of chunks) { - controller.enqueue(encoder.encode(`data: ${JSON.stringify({ - type: 'content_delta', - role_id: role.role_id, - content: chunk, - })}\n\n`)); - await delay(30); - } - - // message_complete - controller.enqueue(encoder.encode(`data: ${JSON.stringify({ - type: 'message_complete', - role_id: role.role_id, - message: { - role_id: role.role_id, - role_name: role.role_name, - content: role.content, - tool_calls: toolCallResults, - is_conclusion: role.is_conclusion || false, - }, - })}\n\n`)); - - await delay(500); - } - - // round_end - controller.enqueue(encoder.encode(`data: ${JSON.stringify({ - type: 'round_end', - round_number: 1, - is_concluded: false, - })}\n\n`)); - - controller.close(); - }, - }); - - return new Response(stream, { - headers: { - 'Content-Type': 'text/event-stream', - 'Cache-Control': 'no-cache', - 'Connection': 'keep-alive', - }, - }); - }), -]; diff --git a/src/mocks/handlers/auth.js b/src/mocks/handlers/auth.js deleted file mode 100644 index ea30f63c..00000000 --- a/src/mocks/handlers/auth.js +++ /dev/null @@ -1,408 +0,0 @@ -// src/mocks/handlers/auth.js -import { http, HttpResponse, delay } from 'msw'; -import { - mockUsers, - mockVerificationCodes, - generateVerificationCode, - mockWechatSessions, - generateWechatSessionId, - setCurrentUser, - getCurrentUser, - clearCurrentUser -} from '../data/users'; - -// 模拟网络延迟(毫秒) -// ⚡ 开发环境使用较短延迟,加快首屏加载速度 -const NETWORK_DELAY = 50; - -export const authHandlers = [ - // ==================== 手机验证码登录 ==================== - - // 1. 发送验证码 - http.post('/api/auth/send-verification-code', async ({ request }) => { - await delay(NETWORK_DELAY); - - const body = await request.json(); - const { credential, type, purpose } = body; - - // 生成验证码 - const code = generateVerificationCode(); - mockVerificationCodes.set(credential, { - code, - expiresAt: Date.now() + 5 * 60 * 1000 // 5分钟后过期 - }); - - return HttpResponse.json({ - success: true, - message: `验证码已发送到 ${credential}(Mock: ${code})`, - // 开发环境下返回验证码,方便测试 - dev_code: code - }); - }), - - // 1.1 发送手机验证码(前端实际调用的接口) - http.post('/api/auth/send-sms-code', async ({ request }) => { - await delay(NETWORK_DELAY); - - const body = await request.json(); - const { phone } = body; - - console.log('[Mock] 发送手机验证码请求:', { phone }); - - // 生成验证码 - const code = generateVerificationCode(); - mockVerificationCodes.set(phone, { - code, - expiresAt: Date.now() + 5 * 60 * 1000 // 5分钟后过期 - }); - - // 超醒目的验证码提示 - 方便开发调试 - console.log( - `%c\n` + - `╔════════════════════════════════════════════╗\n` + - `║ 📱 手机验证码: ${code.padEnd(19)}║\n` + - `║ 📞 手机号: ${phone.padEnd(23)}║\n` + - `╚════════════════════════════════════════════╝\n`, - 'color: #ffffff; background: #16a34a; font-weight: bold; font-size: 16px; padding: 20px; line-height: 1.8;' - ); - - // 额外的高亮提示 - console.log( - `%c 📱 验证码: ${code} `, - 'color: #ffffff; background: #dc2626; font-weight: bold; font-size: 24px; padding: 15px 30px; border-radius: 8px; margin: 10px 0;' - ); - - return HttpResponse.json({ - success: true, - message: `验证码已发送到 ${phone}(Mock: ${code})`, - // 开发环境下返回验证码,方便测试 - dev_code: code - }); - }), - - // 1.2 发送邮箱验证码(前端实际调用的接口) - http.post('/api/auth/send-email-code', async ({ request }) => { - await delay(NETWORK_DELAY); - - const body = await request.json(); - const { email } = body; - - console.log('[Mock] 发送邮箱验证码请求:', { email }); - - // 生成验证码 - const code = generateVerificationCode(); - mockVerificationCodes.set(email, { - code, - expiresAt: Date.now() + 5 * 60 * 1000 // 5分钟后过期 - }); - - // 超醒目的验证码提示 - 方便开发调试 - console.log( - `%c\n` + - `╔════════════════════════════════════════════╗\n` + - `║ 📧 邮箱验证码: ${code.padEnd(19)}║\n` + - `║ 📮 邮箱: ${email.padEnd(27)}║\n` + - `╚════════════════════════════════════════════╝\n`, - 'color: #ffffff; background: #2563eb; font-weight: bold; font-size: 16px; padding: 20px; line-height: 1.8;' - ); - - // 额外的高亮提示 - console.log( - `%c 📧 验证码: ${code} `, - 'color: #ffffff; background: #dc2626; font-weight: bold; font-size: 24px; padding: 15px 30px; border-radius: 8px; margin: 10px 0;' - ); - - return HttpResponse.json({ - success: true, - message: `验证码已发送到 ${email}(Mock: ${code})`, - // 开发环境下返回验证码,方便测试 - dev_code: code - }); - }), - - // 2. 验证码登录 - http.post('/api/auth/login-with-code', async ({ request }) => { - await delay(NETWORK_DELAY); - - const body = await request.json(); - const { credential, verification_code, login_type } = body; - - // 验证验证码 - const storedCode = mockVerificationCodes.get(credential); - if (!storedCode) { - return HttpResponse.json({ - success: false, - error: '验证码不存在或已过期' - }, { status: 400 }); - } - - if (storedCode.expiresAt < Date.now()) { - mockVerificationCodes.delete(credential); - return HttpResponse.json({ - success: false, - error: '验证码已过期' - }, { status: 400 }); - } - - if (storedCode.code !== verification_code) { - return HttpResponse.json({ - success: false, - error: '验证码错误' - }, { status: 400 }); - } - - // 验证成功,删除验证码 - mockVerificationCodes.delete(credential); - - // 查找或创建用户 - let user = mockUsers[credential]; - let isNewUser = false; - - if (!user) { - // 新用户 - isNewUser = true; - const id = Object.keys(mockUsers).length + 1; - user = { - id, - phone: credential, - nickname: `用户${id}`, - email: null, - avatar_url: `https://i.pravatar.cc/150?img=${id}`, - has_wechat: false, - created_at: new Date().toISOString(), - // 默认订阅信息 - 免费用户 - subscription_type: 'free', - subscription_status: 'active', - subscription_end_date: null, - is_subscription_active: true, - subscription_days_left: 0 - }; - mockUsers[credential] = user; - } - - // 设置当前登录用户 - setCurrentUser(user); - - return HttpResponse.json({ - success: true, - message: isNewUser ? '注册成功' : '登录成功', - isNewUser, - user, - token: `mock_token_${user.id}_${Date.now()}` - }); - }), - - // ==================== 微信登录 ==================== - - // 3. 获取微信 PC 二维码 - http.get('/api/auth/wechat/qrcode', async () => { - await delay(NETWORK_DELAY); - - const sessionId = generateWechatSessionId(); - - // 创建微信 session - mockWechatSessions.set(sessionId, { - status: 'waiting', // waiting, scanned, confirmed, expired - createdAt: Date.now(), - user: null - }); - - // 模拟微信授权 URL(实际是微信的 URL) - // 使用真实的微信 AppID 和真实的授权回调地址(必须与微信开放平台配置的域名一致) - const mockRedirectUri = encodeURIComponent('http://valuefrontier.cn/api/auth/wechat/callback'); - const authUrl = `https://open.weixin.qq.com/connect/qrconnect?appid=wxa8d74c47041b5f87&redirect_uri=${mockRedirectUri}&response_type=code&scope=snsapi_login&state=${sessionId}#wechat_redirect`; - - console.log('[Mock] 生成微信二维码:', { sessionId, authUrl }); - - // 3秒后自动模拟扫码(方便测试,已缩短延迟) - setTimeout(() => { - const session = mockWechatSessions.get(sessionId); - if (session && session.status === 'waiting') { - session.status = 'scanned'; - console.log(`[Mock] 模拟用户扫码: ${sessionId}`); - - // 再过5秒自动确认登录(延长时间让用户看到 scanned 状态) - setTimeout(() => { - const session2 = mockWechatSessions.get(sessionId); - if (session2 && session2.status === 'scanned') { - session2.status = 'authorized'; // ✅ 使用 'authorized' 状态,与后端保持一致 - session2.user = { - id: 999, - nickname: '微信用户', - wechat_openid: 'mock_openid_' + sessionId, - avatar_url: 'https://ui-avatars.com/api/?name=微信用户&size=150&background=4299e1&color=fff', - phone: null, - email: null, - has_wechat: true, - created_at: new Date().toISOString(), - // 添加默认订阅信息 - subscription_type: 'free', - subscription_status: 'active', - subscription_end_date: null, - is_subscription_active: true, - subscription_days_left: 0 - }; - session2.user_info = { user_id: session2.user.id }; // ✅ 添加 user_info 字段 - console.log(`[Mock] 模拟用户确认登录: ${sessionId}`, session2.user); - } - }, 2000); - } - }, 3000); - - return HttpResponse.json({ - code: 0, - message: '成功', - data: { - auth_url: authUrl, - session_id: sessionId - } - }); - }), - - // 4. 检查微信扫码状态 - http.post('/api/auth/wechat/check', async ({ request }) => { - await delay(200); // 轮询请求,延迟短一些 - - const body = await request.json(); - const { session_id } = body; - - const session = mockWechatSessions.get(session_id); - - if (!session) { - return HttpResponse.json({ - code: 404, - message: 'Session 不存在', - data: { status: 'expired' } - }); - } - - // 检查是否过期(5分钟) - if (Date.now() - session.createdAt > 5 * 60 * 1000) { - session.status = 'expired'; - mockWechatSessions.delete(session_id); - } - - console.log('[Mock] 检查微信状态:', { session_id, status: session.status }); - - // ✅ 返回与后端真实 API 一致的扁平化数据结构 - return HttpResponse.json({ - status: session.status, - user_info: session.user_info, - expires_in: Math.floor((session.createdAt + 5 * 60 * 1000 - Date.now()) / 1000) - }); - }), - - // 5. 微信登录确认 - http.post('/api/auth/login/wechat', async ({ request }) => { - await delay(NETWORK_DELAY); - - const body = await request.json(); - const { session_id } = body; - - const session = mockWechatSessions.get(session_id); - - if (!session || session.status !== 'authorized') { // ✅ 使用 'authorized' 状态,与前端保持一致 - return HttpResponse.json({ - success: false, - error: '微信登录未确认或已过期' - }, { status: 400 }); - } - - const user = session.user; - - // 清理 session - mockWechatSessions.delete(session_id); - - console.log('[Mock] 微信登录成功:', user); - - // 设置当前登录用户 - setCurrentUser(user); - - return HttpResponse.json({ - success: true, - message: '微信登录成功', - user, - token: `mock_wechat_token_${user.id}_${Date.now()}` - }); - }), - - // 6. 获取微信 H5 授权 URL(手机浏览器用) - http.post('/api/auth/wechat/h5-auth', async ({ request }) => { - await delay(NETWORK_DELAY); - - const body = await request.json(); - const { redirect_url } = body; - - const state = generateWechatSessionId(); - // Mock 模式下直接返回前端回调地址(模拟授权成功) - const authUrl = `${redirect_url}?wechat_login=success&state=${state}`; - - console.log('[Mock] 生成微信 H5 授权 URL:', authUrl); - - return HttpResponse.json({ - auth_url: authUrl, - state - }); - }), - - // ==================== Session 管理 ==================== - - // 7. 检查 Session(AuthContext 使用的正确端点) - http.get('/api/auth/session', async () => { - await delay(NETWORK_DELAY); // ⚡ 使用统一延迟配置 - - // 获取当前登录用户 - const currentUser = getCurrentUser(); - - if (currentUser) { - return HttpResponse.json({ - success: true, - isAuthenticated: true, - user: currentUser - }); - } else { - return HttpResponse.json({ - success: true, - isAuthenticated: false, - user: null - }); - } - }), - - // 8. 检查 Session(旧端点,保留兼容) - http.get('/api/auth/check-session', async () => { - await delay(NETWORK_DELAY); // ⚡ 使用统一延迟配置 - - // 获取当前登录用户 - const currentUser = getCurrentUser(); - - if (currentUser) { - return HttpResponse.json({ - success: true, - isAuthenticated: true, - user: currentUser - }); - } else { - return HttpResponse.json({ - success: true, - isAuthenticated: false, - user: null - }); - } - }), - - // 9. 退出登录 - http.post('/api/auth/logout', async () => { - await delay(NETWORK_DELAY); - - console.log('[Mock] 退出登录'); - - // 清除当前登录用户 - clearCurrentUser(); - - return HttpResponse.json({ - success: true, - message: '退出成功' - }); - }) -]; diff --git a/src/mocks/handlers/bytedesk.js b/src/mocks/handlers/bytedesk.js deleted file mode 100644 index 1ac3dc1c..00000000 --- a/src/mocks/handlers/bytedesk.js +++ /dev/null @@ -1,34 +0,0 @@ -// src/mocks/handlers/bytedesk.js -/** - * Bytedesk 客服 Widget MSW Handler - * Mock 模式下返回模拟数据 - */ - -import { http, HttpResponse, passthrough } from 'msw'; - -export const bytedeskHandlers = [ - // 未读消息数量 - http.get('/bytedesk/visitor/api/v1/message/unread/count', () => { - return HttpResponse.json({ - code: 200, - message: 'success', - data: { count: 0 }, - }); - }), - - // 其他 Bytedesk API - 返回通用成功响应 - http.all('/bytedesk/*', () => { - return HttpResponse.json({ - code: 200, - message: 'success', - data: null, - }); - }), - - // Bytedesk 外部 CDN/服务请求 - http.all('https://www.weiyuai.cn/*', () => { - return passthrough(); - }), -]; - -export default bytedeskHandlers; diff --git a/src/mocks/handlers/company.js b/src/mocks/handlers/company.js deleted file mode 100644 index bf7eccbd..00000000 --- a/src/mocks/handlers/company.js +++ /dev/null @@ -1,281 +0,0 @@ -// src/mocks/handlers/company.js -// 公司相关的 Mock Handlers - -import { http, HttpResponse } from 'msw'; -import { PINGAN_BANK_DATA, generateCompanyData } from '../data/company'; - -// 模拟延迟 -const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms)); - -// 获取公司数据的辅助函数 -const getCompanyData = (stockCode) => { - return stockCode === '000001' ? PINGAN_BANK_DATA : generateCompanyData(stockCode, '示例公司'); -}; - -export const companyHandlers = [ - // 1. 综合分析 - http.get('/api/company/comprehensive-analysis/:stockCode', async ({ params }) => { - await delay(300); - const { stockCode } = params; - const data = getCompanyData(stockCode); - - return HttpResponse.json({ - success: true, - data: data.comprehensiveAnalysis - }); - }), - - // 2. 价值链分析 - http.get('/api/company/value-chain-analysis/:stockCode', async ({ params }) => { - await delay(250); - const { stockCode } = params; - const data = getCompanyData(stockCode); - - return HttpResponse.json({ - success: true, - data: data.valueChainAnalysis - }); - }), - - // 3. 关键因素时间线 - http.get('/api/company/key-factors-timeline/:stockCode', async ({ params }) => { - await delay(200); - const { stockCode } = params; - const data = getCompanyData(stockCode); - - // 直接返回 keyFactorsTimeline 对象(包含 key_factors 和 development_timeline) - return HttpResponse.json({ - success: true, - data: data.keyFactorsTimeline - }); - }), - - // 4. 基本信息 - http.get('/api/stock/:stockCode/basic-info', async ({ params }) => { - await delay(150); - const { stockCode } = params; - const data = getCompanyData(stockCode); - - return HttpResponse.json({ - success: true, - data: data.basicInfo - }); - }), - - // 5. 实际控制人 - http.get('/api/stock/:stockCode/actual-control', async ({ params }) => { - await delay(150); - const { stockCode } = params; - const data = getCompanyData(stockCode); - const raw = data.actualControl; - - // 数据保持原始百分比格式(如 52.38 表示 52.38%) - const formatted = Array.isArray(raw) ? raw : []; - - return HttpResponse.json({ - success: true, - data: formatted - }); - }), - - // 6. 股权集中度 - http.get('/api/stock/:stockCode/concentration', async ({ params }) => { - await delay(150); - const { stockCode } = params; - const data = getCompanyData(stockCode); - const raw = data.concentration; - - // 数据保持原始百分比格式(如 52.38 表示 52.38%) - const formatted = Array.isArray(raw) ? raw : []; - - return HttpResponse.json({ - success: true, - data: formatted - }); - }), - - // 7. 高管信息 - http.get('/api/stock/:stockCode/management', async ({ params, request }) => { - await delay(200); - const { stockCode } = params; - const data = getCompanyData(stockCode); - - // 解析查询参数 - const url = new URL(request.url); - const activeOnly = url.searchParams.get('active_only') === 'true'; - - let management = data.management || []; - - // 如果需要只返回在职高管(mock 数据中默认都是在职) - if (activeOnly) { - management = management.filter(m => m.status !== 'resigned'); - } - - return HttpResponse.json({ - success: true, - data: management // 直接返回数组 - }); - }), - - // 8. 十大流通股东 - http.get('/api/stock/:stockCode/top-circulation-shareholders', async ({ params, request }) => { - await delay(200); - const { stockCode } = params; - const data = getCompanyData(stockCode); - - // 解析查询参数 - const url = new URL(request.url); - const limit = parseInt(url.searchParams.get('limit') || '10', 10); - - const shareholders = (data.topCirculationShareholders || []).slice(0, limit); - - return HttpResponse.json({ - success: true, - data: shareholders // 直接返回数组 - }); - }), - - // 9. 十大股东 - http.get('/api/stock/:stockCode/top-shareholders', async ({ params, request }) => { - await delay(200); - const { stockCode } = params; - const data = getCompanyData(stockCode); - - // 解析查询参数 - const url = new URL(request.url); - const limit = parseInt(url.searchParams.get('limit') || '10', 10); - - const shareholders = (data.topShareholders || []).slice(0, limit); - - return HttpResponse.json({ - success: true, - data: shareholders // 直接返回数组 - }); - }), - - // 10. 分支机构 - http.get('/api/stock/:stockCode/branches', async ({ params }) => { - await delay(200); - const { stockCode } = params; - const data = getCompanyData(stockCode); - - return HttpResponse.json({ - success: true, - data: data.branches || [] // 直接返回数组 - }); - }), - - // 11. 公告列表 - http.get('/api/stock/:stockCode/announcements', async ({ params, request }) => { - await delay(250); - const { stockCode } = params; - const data = getCompanyData(stockCode); - - // 解析查询参数 - const url = new URL(request.url); - const limit = parseInt(url.searchParams.get('limit') || '20', 10); - const page = parseInt(url.searchParams.get('page') || '1', 10); - const type = url.searchParams.get('type'); - - let announcements = data.announcements || []; - - // 类型筛选 - if (type) { - announcements = announcements.filter(a => a.type === type); - } - - // 分页 - const start = (page - 1) * limit; - const end = start + limit; - const paginatedAnnouncements = announcements.slice(start, end); - - return HttpResponse.json({ - success: true, - data: paginatedAnnouncements // 直接返回数组 - }); - }), - - // 12. 披露时间表 - http.get('/api/stock/:stockCode/disclosure-schedule', async ({ params }) => { - await delay(150); - const { stockCode } = params; - const data = getCompanyData(stockCode); - - return HttpResponse.json({ - success: true, - data: data.disclosureSchedule || [] // 直接返回数组 - }); - }), - - // 13. 盈利预测报告 - http.get('/api/stock/:stockCode/forecast-report', async ({ params }) => { - await delay(300); - const { stockCode } = params; - const data = getCompanyData(stockCode); - - return HttpResponse.json({ - success: true, - data: data.forecastReport || null - }); - }), - - // 14. 价值链关联公司 - http.get('/api/company/value-chain/related-companies', async ({ request }) => { - await delay(300); - - const url = new URL(request.url); - const nodeName = url.searchParams.get('node_name') || ''; - - console.log('[Mock] 获取价值链关联公司:', nodeName); - - // 生成模拟的关联公司数据 - const relatedCompanies = [ - { - stock_code: '601318', - stock_name: '中国平安', - industry: '保险', - relation: '同业竞争', - market_cap: 9200, - change_pct: 1.25 - }, - { - stock_code: '600036', - stock_name: '招商银行', - industry: '银行', - relation: '核心供应商', - market_cap: 8500, - change_pct: 0.85 - }, - { - stock_code: '601166', - stock_name: '兴业银行', - industry: '银行', - relation: '同业竞争', - market_cap: 4200, - change_pct: -0.32 - }, - { - stock_code: '601398', - stock_name: '工商银行', - industry: '银行', - relation: '同业竞争', - market_cap: 15000, - change_pct: 0.15 - }, - { - stock_code: '601288', - stock_name: '农业银行', - industry: '银行', - relation: '同业竞争', - market_cap: 12000, - change_pct: -0.08 - } - ]; - - return HttpResponse.json({ - success: true, - data: relatedCompanies, - node_name: nodeName - }); - }), -]; diff --git a/src/mocks/handlers/concept.js b/src/mocks/handlers/concept.js deleted file mode 100644 index 1454b84f..00000000 --- a/src/mocks/handlers/concept.js +++ /dev/null @@ -1,1701 +0,0 @@ -// src/mocks/handlers/concept.js -// 概念相关的 Mock Handlers - -import { http, HttpResponse } from 'msw'; - -// 模拟延迟 -const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms)); - -// 生成历史触发时间(3-5个历史日期) -const generateHappenedTimes = (seed) => { - const times = []; - const count = 3 + (seed % 3); // 3-5个时间点 - for (let i = 0; i < count; i++) { - const daysAgo = 30 + (seed * 7 + i * 11) % 330; // 30-360天前 - const date = new Date(); - date.setDate(date.getDate() - daysAgo); - times.push(date.toISOString().split('T')[0]); - } - return times.sort().reverse(); // 降序排列 -}; - -// 生成核心相关股票 -const generateStocksForConcept = (seed, count = 4) => { - const stockPool = [ - { name: '贵州茅台', code: '600519' }, - { name: '宁德时代', code: '300750' }, - { name: '中国平安', code: '601318' }, - { name: '比亚迪', code: '002594' }, - { name: '隆基绿能', code: '601012' }, - { name: '阳光电源', code: '300274' }, - { name: '三一重工', code: '600031' }, - { name: '中芯国际', code: '688981' }, - { name: '京东方A', code: '000725' }, - { name: '立讯精密', code: '002475' } - ]; - - const stocks = []; - for (let i = 0; i < count; i++) { - const stockIndex = (seed + i * 7) % stockPool.length; - const stock = stockPool[stockIndex]; - stocks.push({ - stock_name: stock.name, - stock_code: stock.code, - reason: `作为行业龙头企业,${stock.name}在该领域具有核心竞争优势,市场份额领先,技术实力雄厚。`, - change_pct: parseFloat((Math.random() * 15 - 5).toFixed(2)) // -5% ~ +10% - }); - } - return stocks; -}; - -// 生成热门概念数据 -export const generatePopularConcepts = (size = 20) => { - const concepts = [ - '人工智能', '新能源汽车', '半导体', '光伏', '锂电池', - '储能', '氢能源', '风电', '特高压', '工业母机', - '军工', '航空航天', '卫星导航', '量子科技', '数字货币', - '云计算', '大数据', '物联网', '5G', '6G', - '元宇宙', '虚拟现实', 'AIGC', 'ChatGPT', '算力', - '芯片设计', '芯片制造', '半导体设备', '半导体材料', 'EDA', - '新能源', '风光储', '充电桩', '智能电网', '特斯拉', - '比亚迪', '宁德时代', '华为', '苹果产业链', '鸿蒙', - '国产软件', '信创', '网络安全', '数据安全', '量子通信', - '医疗器械', '创新药', '医美', 'CXO', '生物医药', - '疫苗', '中药', '医疗信息化', '智慧医疗', '基因测序' - ]; - - const conceptDescriptions = { - '人工智能': '人工智能是"技术突破+政策扶持"双轮驱动的硬科技主题。随着大模型技术的突破,AI应用场景不断拓展,预计将催化算力、数据、应用三大产业链。', - '新能源汽车': '新能源汽车行业景气度持续向好,渗透率不断提升。政策支持力度大,产业链上下游企业均受益明显。', - '半导体': '国产半导体替代加速,自主可控需求强烈。政策和资金支持力度大,行业迎来黄金发展期。', - '光伏': '光伏装机量快速增长,成本持续下降,行业景气度维持高位。双碳目标下,光伏行业前景广阔。', - '锂电池': '锂电池技术进步,成本优势扩大,下游应用领域持续扩张。新能源汽车和储能需求旺盛。', - '储能': '储能市场爆发式增长,政策支持力度大,应用场景不断拓展。未来市场空间巨大。', - '默认': '该概念市场关注度较高,具有一定的投资价值。相关企业技术实力雄厚,市场前景广阔。' - }; - - const matchTypes = ['hybrid_knn', 'keyword', 'semantic']; - - const results = []; - for (let i = 0; i < Math.min(size, concepts.length); i++) { - const changePct = (Math.random() * 12 - 2).toFixed(2); // -2% 到 +10% - const stockCount = Math.floor(Math.random() * 50) + 10; // 10-60 只股票 - const score = parseFloat((Math.random() * 5 + 3).toFixed(2)); // 3-8 分数范围 - - results.push({ - concept: concepts[i], - concept_id: `CONCEPT_${1000 + i}`, - stock_count: stockCount, - score: score, // 相关度分数 - match_type: matchTypes[i % 3], // 匹配类型 - description: conceptDescriptions[concepts[i]] || conceptDescriptions['默认'], - price_info: { - avg_change_pct: parseFloat(changePct), - avg_price: parseFloat((Math.random() * 100 + 10).toFixed(2)), - total_market_cap: parseFloat((Math.random() * 1000 + 100).toFixed(2)) - }, - happened_times: generateHappenedTimes(i), // 历史触发时间 - stocks: generateStocksForConcept(i, 4), // 核心相关股票 - hot_score: Math.floor(Math.random() * 100) - }); - } - - // 按涨跌幅降序排序 - results.sort((a, b) => b.price_info.avg_change_pct - a.price_info.avg_change_pct); - - return results; -}; - -// 生成完整的概念统计数据(用于 ConceptStatsPanel) -const generateConceptStats = () => { - // 热门概念(涨幅最大的前5) - const hot_concepts = [ - { name: '小米大模型', change_pct: 18.76, stock_count: 12, news_count: 35 }, - { name: '人工智能', change_pct: 15.67, stock_count: 45, news_count: 23 }, - { name: '新能源汽车', change_pct: 12.34, stock_count: 38, news_count: 18 }, - { name: '芯片概念', change_pct: 9.87, stock_count: 52, news_count: 31 }, - { name: '5G通信', change_pct: 8.45, stock_count: 29, news_count: 15 }, - ]; - - // 冷门概念(跌幅最大的前5) - const cold_concepts = [ - { name: '房地产', change_pct: -8.76, stock_count: 33, news_count: 12 }, - { name: '煤炭开采', change_pct: -6.54, stock_count: 25, news_count: 8 }, - { name: '传统零售', change_pct: -5.43, stock_count: 19, news_count: 6 }, - { name: '钢铁冶炼', change_pct: -4.21, stock_count: 28, news_count: 9 }, - { name: '纺织服装', change_pct: -3.98, stock_count: 15, news_count: 4 }, - ]; - - // 活跃概念(新闻+研报最多的前5) - const active_concepts = [ - { name: '人工智能', news_count: 89, report_count: 15, total_mentions: 104 }, - { name: '芯片概念', news_count: 76, report_count: 12, total_mentions: 88 }, - { name: '新能源汽车', news_count: 65, report_count: 18, total_mentions: 83 }, - { name: '生物医药', news_count: 54, report_count: 9, total_mentions: 63 }, - { name: '量子科技', news_count: 41, report_count: 7, total_mentions: 48 }, - ]; - - // 波动最大的概念(前5) - const volatile_concepts = [ - { name: '区块链', volatility: 23.45, avg_change: 3.21, max_change: 12.34 }, - { name: '元宇宙', volatility: 21.87, avg_change: 2.98, max_change: 11.76 }, - { name: '虚拟现实', volatility: 19.65, avg_change: -1.23, max_change: 9.87 }, - { name: '游戏概念', volatility: 18.32, avg_change: 4.56, max_change: 10.45 }, - { name: '在线教育', volatility: 17.89, avg_change: -2.11, max_change: 8.76 }, - ]; - - // 动量概念(连续上涨的前5) - const momentum_concepts = [ - { name: '数字经济', consecutive_days: 5, total_change: 18.76, avg_daily: 3.75 }, - { name: '云计算', consecutive_days: 4, total_change: 14.32, avg_daily: 3.58 }, - { name: '物联网', consecutive_days: 4, total_change: 12.89, avg_daily: 3.22 }, - { name: '大数据', consecutive_days: 3, total_change: 11.45, avg_daily: 3.82 }, - { name: '工业互联网', consecutive_days: 3, total_change: 9.87, avg_daily: 3.29 }, - ]; - - return { - hot_concepts, - cold_concepts, - active_concepts, - volatile_concepts, - momentum_concepts - }; -}; - -// 概念相关的 Handlers -export const conceptHandlers = [ - // 搜索概念(热门概念) - http.post('/concept-api/search', async ({ request }) => { - await delay(300); - - try { - const body = await request.json(); - const { query = '', size = 20, page = 1, sort_by = 'change_pct' } = body; - - console.log('[Mock Concept] 搜索概念:', { query, size, page, sort_by }); - - // 生成数据(不过滤,模拟真实 API 的语义搜索返回热门概念) - let results = generatePopularConcepts(size); - console.log('[Mock Concept] 生成概念数量:', results.length); - - // Mock 环境下不做过滤,直接返回热门概念 - // 真实环境会根据 query 进行语义搜索 - - // 根据排序字段排序 - if (sort_by === 'change_pct') { - results.sort((a, b) => b.price_info.avg_change_pct - a.price_info.avg_change_pct); - } else if (sort_by === 'stock_count') { - results.sort((a, b) => b.stock_count - a.stock_count); - } else if (sort_by === 'hot_score') { - results.sort((a, b) => b.hot_score - a.hot_score); - } - - return HttpResponse.json({ - results, - total: results.length, - page, - size, - message: '搜索成功' - }); - } catch (error) { - console.error('[Mock Concept] 搜索概念失败:', error); - return HttpResponse.json( - { - results: [], - total: 0, - error: '搜索失败' - }, - { status: 500 } - ); - } - }), - - // 获取单个概念详情 - http.get('/concept-api/concepts/:conceptId', async ({ params }) => { - await delay(300); - - const { conceptId } = params; - console.log('[Mock Concept] 获取概念详情:', conceptId); - - const concepts = generatePopularConcepts(50); - const concept = concepts.find(c => c.concept_id === conceptId || c.concept === conceptId); - - if (concept) { - return HttpResponse.json({ - ...concept, - related_stocks: [ - { stock_code: '600519', stock_name: '贵州茅台', change_pct: 2.34 }, - { stock_code: '000858', stock_name: '五粮液', change_pct: 1.89 }, - { stock_code: '000568', stock_name: '泸州老窖', change_pct: 3.12 } - ], - news: [ - { title: `${concept.concept}板块异动`, date: '2024-10-24', source: '财经新闻' } - ] - }); - } else { - return HttpResponse.json( - { error: '概念不存在' }, - { status: 404 } - ); - } - }), - - // 获取概念相关股票(concept-api 路由) - http.get('/concept-api/concepts/:conceptId/stocks', async ({ params, request }) => { - await delay(300); - - const { conceptId } = params; - const url = new URL(request.url); - const limit = parseInt(url.searchParams.get('limit') || '20'); - - console.log('[Mock Concept] 获取概念相关股票:', { conceptId, limit }); - - // 生成模拟股票数据(确保 change_pct 是数字类型) - const stocks = []; - for (let i = 0; i < limit; i++) { - const code = 600000 + i; - stocks.push({ - stock_code: `${code}.SH`, - code: `${code}.SH`, - stock_name: `股票${i + 1}`, - name: `股票${i + 1}`, - change_pct: parseFloat((Math.random() * 10 - 2).toFixed(2)), - price: parseFloat((Math.random() * 100 + 10).toFixed(2)), - market_cap: parseFloat((Math.random() * 1000 + 100).toFixed(2)), - reason: '板块核心成分股' - }); - } - - return HttpResponse.json({ - stocks, - total: stocks.length, - concept_id: conceptId - }); - }), - - // 获取概念相关股票(/api/concept 路由 - 热点概览异动列表使用) - http.get('/api/concept/:conceptId/stocks', async ({ params, request }) => { - await delay(200); - - const { conceptId } = params; - const url = new URL(request.url); - const limit = parseInt(url.searchParams.get('limit') || '15'); - - console.log('[Mock Concept] /api/concept/:conceptId/stocks:', { conceptId, limit }); - - // 股票池 - const stockPool = [ - { code: '600519', name: '贵州茅台' }, - { code: '300750', name: '宁德时代' }, - { code: '601318', name: '中国平安' }, - { code: '002594', name: '比亚迪' }, - { code: '601012', name: '隆基绿能' }, - { code: '300274', name: '阳光电源' }, - { code: '688981', name: '中芯国际' }, - { code: '000725', name: '京东方A' }, - { code: '002230', name: '科大讯飞' }, - { code: '300124', name: '汇川技术' }, - { code: '002049', name: '紫光国微' }, - { code: '688012', name: '中微公司' }, - { code: '603501', name: '韦尔股份' }, - { code: '600036', name: '招商银行' }, - { code: '000858', name: '五粮液' }, - ]; - - // 生成模拟股票数据(确保 change_pct 是数字类型) - const stocks = []; - const count = Math.min(limit, stockPool.length); - for (let i = 0; i < count; i++) { - const stock = stockPool[i]; - // 根据股票代码判断交易所后缀 - let suffix = '.SZ'; - if (stock.code.startsWith('6')) { - suffix = '.SH'; - } else if (stock.code.startsWith('8') || stock.code.startsWith('9') || stock.code.startsWith('4')) { - suffix = '.BJ'; - } - stocks.push({ - stock_code: `${stock.code}${suffix}`, - code: `${stock.code}${suffix}`, - stock_name: stock.name, - name: stock.name, - change_pct: parseFloat((Math.random() * 12 - 3).toFixed(2)), // -3% ~ +9% - price: parseFloat((Math.random() * 100 + 10).toFixed(2)), - market_cap: parseFloat((Math.random() * 1000 + 100).toFixed(2)), - reason: '板块核心标的' - }); - } - - return HttpResponse.json({ - success: true, - data: { - stocks, - total: stocks.length, - concept_id: conceptId - } - }); - }), - - // 获取最新交易日期 - 硬编码 URL(开发环境) - http.get('http://111.198.58.126:16801/price/latest', async () => { - await delay(200); - - const today = new Date(); - // 使用 YYYY-MM-DD 格式,确保 new Date() 可以正确解析 - const dateStr = today.toISOString().split('T')[0]; - - console.log('[Mock Concept] 获取最新交易日期:', dateStr); - - return HttpResponse.json({ - latest_trade_date: dateStr, - timestamp: today.toISOString() - }); - }), - - // 获取最新交易日期 - 相对路径(MSW 环境) - http.get('/concept-api/price/latest', async () => { - await delay(200); - - const today = new Date(); - // 使用 YYYY-MM-DD 格式,确保 new Date() 可以正确解析 - const dateStr = today.toISOString().split('T')[0]; - - console.log('[Mock Concept] 获取最新交易日期 (概念API):', dateStr); - - return HttpResponse.json({ - latest_trade_date: dateStr, - timestamp: today.toISOString() - }); - }), - - // 搜索概念(硬编码 URL) - http.post('http://111.198.58.126:16801/search', async ({ request }) => { - await delay(300); - - try { - const body = await request.json(); - const { query = '', size = 20, page = 1, sort_by = 'change_pct' } = body; - - console.log('[Mock Concept] 搜索概念 (硬编码URL):', { query, size, page, sort_by }); - - let results = generatePopularConcepts(size); - - if (query) { - results = results.filter(item => - item.concept.toLowerCase().includes(query.toLowerCase()) - ); - } - - if (sort_by === 'change_pct') { - results.sort((a, b) => b.price_info.avg_change_pct - a.price_info.avg_change_pct); - } else if (sort_by === 'stock_count') { - results.sort((a, b) => b.stock_count - a.stock_count); - } else if (sort_by === 'hot_score') { - results.sort((a, b) => b.hot_score - a.hot_score); - } - - return HttpResponse.json({ - results, - total: results.length, - page, - size, - message: '搜索成功' - }); - } catch (error) { - console.error('[Mock Concept] 搜索失败:', error); - return HttpResponse.json( - { results: [], total: 0, error: '搜索失败' }, - { status: 500 } - ); - } - }), - - // 搜索概念(MSW 代理路径) - http.post('/concept-api/search', async ({ request }) => { - await delay(300); - - try { - const body = await request.json(); - const { query = '', size = 20, page = 1, sort_by = 'change_pct' } = body; - - console.log('[Mock Concept] 搜索概念 (concept-api):', { query, size, page, sort_by }); - - let results = generatePopularConcepts(size); - - if (query) { - results = results.filter(item => - item.concept.toLowerCase().includes(query.toLowerCase()) - ); - } - - if (sort_by === 'change_pct') { - results.sort((a, b) => b.price_info.avg_change_pct - a.price_info.avg_change_pct); - } else if (sort_by === 'stock_count') { - results.sort((a, b) => b.stock_count - a.stock_count); - } else if (sort_by === 'hot_score') { - results.sort((a, b) => b.hot_score - a.hot_score); - } - - return HttpResponse.json({ - results, - total: results.length, - page, - size, - message: '搜索成功' - }); - } catch (error) { - console.error('[Mock Concept] 搜索失败 (concept-api):', error); - return HttpResponse.json( - { results: [], total: 0, error: '搜索失败' }, - { status: 500 } - ); - } - }), - - // 获取统计数据(直接访问外部 API) - http.get('http://111.198.58.126:16801/statistics', async ({ request }) => { - await delay(300); - - const url = new URL(request.url); - const minStockCount = parseInt(url.searchParams.get('min_stock_count') || '3'); - const days = parseInt(url.searchParams.get('days') || '7'); - const startDate = url.searchParams.get('start_date'); - const endDate = url.searchParams.get('end_date'); - - console.log('[Mock Concept] 获取统计数据 (直接API):', { minStockCount, days, startDate, endDate }); - - // 生成完整的统计数据 - const statsData = generateConceptStats(); - - return HttpResponse.json({ - success: true, - data: statsData, - note: 'Mock 数据', - params: { - min_stock_count: minStockCount, - days: days, - start_date: startDate, - end_date: endDate - }, - updated_at: new Date().toISOString() - }); - }), - - // 获取统计数据(通过 nginx 代理) - http.get('/concept-api/statistics', async ({ request }) => { - await delay(300); - - const url = new URL(request.url); - const minStockCount = parseInt(url.searchParams.get('min_stock_count') || '3'); - const days = parseInt(url.searchParams.get('days') || '7'); - const startDate = url.searchParams.get('start_date'); - const endDate = url.searchParams.get('end_date'); - - console.log('[Mock Concept] 获取统计数据 (nginx代理):', { minStockCount, days, startDate, endDate }); - - // 生成完整的统计数据 - const statsData = generateConceptStats(); - - return HttpResponse.json({ - success: true, - data: statsData, - note: 'Mock 数据(通过 nginx 代理)', - params: { - min_stock_count: minStockCount, - days: days, - start_date: startDate, - end_date: endDate - }, - updated_at: new Date().toISOString() - }); - }), - - // 获取概念价格时间序列 - http.get('http://111.198.58.126:16801/concept/:conceptId/price-timeseries', async ({ params, request }) => { - await delay(300); - - const { conceptId } = params; - const url = new URL(request.url); - const startDate = url.searchParams.get('start_date'); - const endDate = url.searchParams.get('end_date'); - - console.log('[Mock Concept] 获取价格时间序列:', { conceptId, startDate, endDate }); - - // 生成时间序列数据 - const timeseries = []; - const start = new Date(startDate || '2024-01-01'); - const end = new Date(endDate || new Date()); - const daysDiff = Math.ceil((end - start) / (1000 * 60 * 60 * 24)); - - for (let i = 0; i <= daysDiff; i++) { - const date = new Date(start); - date.setDate(date.getDate() + i); - - // 跳过周末 - if (date.getDay() !== 0 && date.getDay() !== 6) { - timeseries.push({ - trade_date: date.toISOString().split('T')[0], // 改为 trade_date - avg_change_pct: parseFloat((Math.random() * 8 - 2).toFixed(2)), // 转为数值 - stock_count: Math.floor(Math.random() * 30) + 10, - volume: Math.floor(Math.random() * 1000000000) - }); - } - } - - return HttpResponse.json({ - concept_id: conceptId, - timeseries: timeseries, - start_date: startDate, - end_date: endDate - }); - }), - - // 获取概念相关新闻 (search_china_news) - http.get('http://111.198.58.126:21891/search_china_news', async ({ request }) => { - await delay(300); - - const url = new URL(request.url); - const query = url.searchParams.get('query'); - const exactMatch = url.searchParams.get('exact_match'); - const startDate = url.searchParams.get('start_date'); - const endDate = url.searchParams.get('end_date'); - const topK = parseInt(url.searchParams.get('top_k') || '100'); - - console.log('[Mock Concept] 搜索中国新闻:', { query, exactMatch, startDate, endDate, topK }); - - // 生成新闻数据 - const news = []; - const newsCount = Math.min(topK, Math.floor(Math.random() * 15) + 5); // 5-20 条新闻 - - for (let i = 0; i < newsCount; i++) { - const daysAgo = Math.floor(Math.random() * 100); // 0-100 天前 - const date = new Date(); - date.setDate(date.getDate() - daysAgo); - - const hour = Math.floor(Math.random() * 24); - const minute = Math.floor(Math.random() * 60); - const publishedTime = `${date.toISOString().split('T')[0]} ${String(hour).padStart(2, '0')}:${String(minute).padStart(2, '0')}:00`; - - news.push({ - id: `news_${i}`, - title: `${query || '概念'}板块动态:${['利好政策发布', '行业景气度提升', '龙头企业业绩超预期', '技术突破进展', '市场需求旺盛'][i % 5]}`, - detail: `${query || '概念'}相关新闻详细内容。近期${query || '概念'}板块表现活跃,市场关注度持续上升。多家券商研报指出,${query || '概念'}行业前景广阔,建议重点关注龙头企业投资机会。`, - description: `${query || '概念'}板块最新动态摘要...`, - source: ['新浪财经', '东方财富网', '财联社', '证券时报', '中国证券报', '上海证券报'][Math.floor(Math.random() * 6)], - published_time: publishedTime, - url: `https://finance.sina.com.cn/stock/news/${date.getFullYear()}${String(date.getMonth() + 1).padStart(2, '0')}${String(date.getDate()).padStart(2, '0')}/news_${i}.html` - }); - } - - // 按时间降序排序 - news.sort((a, b) => new Date(b.published_time) - new Date(a.published_time)); - - // 返回数组(不是对象) - return HttpResponse.json(news); - }), - - // 获取概念相关研报 (search) - http.get('http://111.198.58.126:8811/search', async ({ request }) => { - await delay(300); - - const url = new URL(request.url); - const query = url.searchParams.get('query'); - const mode = url.searchParams.get('mode'); - const exactMatch = url.searchParams.get('exact_match'); - const size = parseInt(url.searchParams.get('size') || '30'); - const startDate = url.searchParams.get('start_date'); - - console.log('[Mock Concept] 搜索研报:', { query, mode, exactMatch, size, startDate }); - - // 生成研报数据 - const reports = []; - const reportCount = Math.min(size, Math.floor(Math.random() * 10) + 3); // 3-12 份研报 - - const publishers = ['中信证券', '国泰君安', '华泰证券', '招商证券', '海通证券', '广发证券', '申万宏源', '兴业证券']; - const authors = ['张明', '李华', '王强', '刘洋', '陈杰', '赵敏']; - const ratings = ['买入', '增持', '中性', '减持', '强烈推荐']; - const securityNames = ['行业研究', '公司研究', '策略研究', '宏观研究', '固收研究']; - - for (let i = 0; i < reportCount; i++) { - const daysAgo = Math.floor(Math.random() * 100); // 0-100 天前 - const date = new Date(); - date.setDate(date.getDate() - daysAgo); - - const declareDate = `${date.toISOString().split('T')[0]} ${String(Math.floor(Math.random() * 24)).padStart(2, '0')}:${String(Math.floor(Math.random() * 60)).padStart(2, '0')}:00`; - - reports.push({ - id: `report_${i}`, - report_title: `${query || '概念'}行业${['深度研究报告', '投资策略分析', '行业景气度跟踪', '估值分析报告', '竞争格局研究'][i % 5]}`, - content: `${query || '概念'}行业研究报告内容摘要。\n\n核心观点:\n1. ${query || '概念'}行业景气度持续向好,市场规模预计将保持高速增长。\n2. 龙头企业凭借技术优势和规模效应,市场份额有望进一步提升。\n3. 政策支持力度加大,为行业发展提供有力保障。\n\n投资建议:建议重点关注行业龙头企业,给予"${ratings[Math.floor(Math.random() * ratings.length)]}"评级。`, - abstract: `本报告深入分析了${query || '概念'}行业的发展趋势、竞争格局和投资机会,认为行业具备良好的成长性...`, - publisher: publishers[Math.floor(Math.random() * publishers.length)], - author: authors[Math.floor(Math.random() * authors.length)], - declare_date: declareDate, - rating: ratings[Math.floor(Math.random() * ratings.length)], - security_name: securityNames[Math.floor(Math.random() * securityNames.length)], - content_url: `https://pdf.dfcfw.com/pdf/H3_${1000000 + i}_1_${date.getFullYear()}${String(date.getMonth() + 1).padStart(2, '0')}${String(date.getDate()).padStart(2, '0')}.pdf` - }); - } - - // 按时间降序排序 - reports.sort((a, b) => new Date(b.declare_date) - new Date(a.declare_date)); - - // 返回符合组件期望的格式 - return HttpResponse.json({ - results: reports, - total: reports.length, - query: query, - mode: mode - }); - }), - - // ============ 层级结构 API ============ - - // 获取完整层级结构(硬编码 URL - 开发环境) - http.get('http://111.198.58.126:16801/hierarchy', async () => { - await delay(300); - - console.log('[Mock Concept] 获取层级结构 (硬编码URL)'); - - // 模拟完整的层级结构数据 - const hierarchy = [ - { - id: 'lv1_1', - name: '人工智能', - concept_count: 98, - children: [ - { - id: 'lv2_1_1', - name: 'AI基础设施', - concept_count: 52, - children: [ - { id: 'lv3_1_1_1', name: 'AI算力硬件', concept_count: 16, concepts: ['AI芯片', 'GPU概念股', '服务器', 'AI一体机', '算力租赁', 'NPU', '智能计算中心', '超算中心'] }, - { id: 'lv3_1_1_2', name: 'AI关键组件', concept_count: 24, concepts: ['HBM', 'PCB', '光通信', '存储芯片', 'CPO', '光模块', '铜连接', '高速背板'] }, - { id: 'lv3_1_1_3', name: 'AI配套设施', concept_count: 12, concepts: ['数据中心', '液冷', '电力设备', 'UPS', 'IDC服务', '边缘计算', 'AI电源'] } - ] - }, - { - id: 'lv2_1_2', - name: 'AI模型与软件', - concept_count: 18, - concepts: ['DeepSeek', 'KIMI', 'SORA概念', '国产大模型', 'ChatGPT', 'Claude', '文心一言', '通义千问', 'Gemini概念', 'AI推理'] - }, - { - id: 'lv2_1_3', - name: 'AI应用', - concept_count: 28, - children: [ - { id: 'lv3_1_3_1', name: '智能体与陪伴', concept_count: 11, concepts: ['AI伴侣', 'AI智能体', 'AI陪伴', 'AI助手', '数字人', 'AI语音', 'AI社交'] }, - { id: 'lv3_1_3_2', name: '行业应用', concept_count: 17, concepts: ['AI编程', '低代码', 'AI教育', 'AI医疗', 'AI金融', 'AI制造', 'AI安防', 'AI营销', 'AI设计'] } - ] - } - ] - }, - { - id: 'lv1_2', - name: '半导体', - concept_count: 65, - children: [ - { id: 'lv2_2_1', name: '半导体设备', concept_count: 18, concepts: ['光刻机', '刻蚀设备', '薄膜沉积', '离子注入', 'CMP设备', '清洗设备'] }, - { id: 'lv2_2_2', name: '半导体材料', concept_count: 15, concepts: ['光刻胶', '半导体材料', '石英砂', '硅片', '电子特气', '靶材', 'CMP抛光液'] }, - { id: 'lv2_2_3', name: '芯片设计与制造', concept_count: 22, concepts: ['CPU', 'GPU', 'FPGA', 'ASIC', 'MCU', 'DSP', 'NPU芯片', '功率半导体', '碳化硅'] }, - { id: 'lv2_2_4', name: '先进封装', concept_count: 10, concepts: ['玻璃基板', '半导体封测', 'Chiplet', 'CoWoS', 'SiP', '3D封装'] } - ] - }, - { - id: 'lv1_3', - name: '机器人', - concept_count: 52, - children: [ - { id: 'lv2_3_1', name: '人形机器人整机', concept_count: 20, concepts: ['特斯拉机器人', '人形机器人', '智元机器人', '优必选', '小米机器人', '华为机器人'] }, - { id: 'lv2_3_2', name: '机器人核心零部件', concept_count: 20, concepts: ['滚柱丝杆', '电子皮肤', '轴向磁通电机', '谐波减速器', '行星减速器', '伺服电机', '六维力传感器', '灵巧手'] }, - { id: 'lv2_3_3', name: '其他类型机器人', concept_count: 12, concepts: ['工业机器人', '机器狗', '外骨骼机器人', '协作机器人', '手术机器人', '服务机器人'] } - ] - }, - { - id: 'lv1_4', - name: '消费电子', - concept_count: 48, - children: [ - { id: 'lv2_4_1', name: '智能终端', concept_count: 12, concepts: ['AI PC', 'AI手机', '折叠屏', '卫星通信手机', '智能手表', '智能耳机'] }, - { id: 'lv2_4_2', name: 'XR与空间计算', concept_count: 18, concepts: ['AR眼镜', 'MR', '智能眼镜', 'VR头显', 'Apple Vision Pro概念', 'Meta Quest概念'] }, - { id: 'lv2_4_3', name: '华为产业链', concept_count: 18, concepts: ['华为Mate70', '鸿蒙', '华为昇腾', '华为汽车', '华为海思', '华为云', '麒麟芯片'] } - ] - }, - { - id: 'lv1_5', - name: '智能驾驶与汽车', - concept_count: 45, - children: [ - { id: 'lv2_5_1', name: '自动驾驶解决方案', concept_count: 18, concepts: ['Robotaxi', '无人驾驶', '特斯拉FSD', 'L4自动驾驶', '萝卜快跑', '百度Apollo'] }, - { id: 'lv2_5_2', name: '智能汽车产业链', concept_count: 18, concepts: ['比亚迪产业链', '小米汽车产业链', '特斯拉产业链', '新能源汽车', '智能座舱', '汽车电子'] }, - { id: 'lv2_5_3', name: '车路协同', concept_count: 9, concepts: ['车路云一体化', '车路协同', 'V2X', '智能交通', '高精地图', '车联网'] } - ] - }, - { - id: 'lv1_6', - name: '新能源与电力', - concept_count: 62, - children: [ - { id: 'lv2_6_1', name: '新型电池技术', concept_count: 22, concepts: ['固态电池', '钠离子电池', '锂电池', '磷酸铁锂', '三元锂电池', '硅基负极', '电解液'] }, - { id: 'lv2_6_2', name: '电力设备与电网', concept_count: 24, concepts: ['电力', '变压器出海', '特高压', '智能电网', '储能', '充电桩', '虚拟电厂'] }, - { id: 'lv2_6_3', name: '清洁能源', concept_count: 16, concepts: ['光伏', '核电', '可控核聚变', '风电', '海上风电', '氢能源', '绿电', '碳中和'] } - ] - }, - { - id: 'lv1_7', - name: '空天经济', - concept_count: 38, - children: [ - { id: 'lv2_7_1', name: '低空经济', concept_count: 20, concepts: ['低空经济', 'eVTOL', '飞行汽车', '无人机', '电动垂直起降', '载人飞行器', '物流无人机'] }, - { id: 'lv2_7_2', name: '商业航天', concept_count: 18, concepts: ['卫星互联网', '商业航天', '北斗导航', '星链概念', '火箭发射', '卫星制造', '遥感卫星'] } - ] - }, - { - id: 'lv1_8', - name: '国防军工', - concept_count: 35, - children: [ - { id: 'lv2_8_1', name: '无人作战与信息化', concept_count: 14, concepts: ['AI军工', '无人机蜂群', '军工信息化', '无人作战', '军用无人机', '电子战'] }, - { id: 'lv2_8_2', name: '海军装备', concept_count: 12, concepts: ['国产航母', '电磁弹射', '舰艇', '潜艇', '海军武器', '舰载机'] }, - { id: 'lv2_8_3', name: '军贸出海', concept_count: 9, concepts: ['军贸', '巴黎航展', '武器出口', '国际军贸', '军工出海'] } - ] - }, - { - id: 'lv1_9', - name: '政策与主题', - concept_count: 42, - children: [ - { id: 'lv2_9_1', name: '国家战略', concept_count: 22, concepts: ['一带一路', '国产替代', '自主可控', '信创', '数字经济', '数据要素', '东数西算', '新基建', '乡村振兴'] }, - { id: 'lv2_9_2', name: '区域发展', concept_count: 20, concepts: ['粤港澳大湾区', '长三角一体化', '京津冀协同', '成渝经济圈', '海南自贸港', '雄安新区'] } - ] - }, - { - id: 'lv1_10', - name: '周期与材料', - concept_count: 35, - children: [ - { id: 'lv2_10_1', name: '有色金属', concept_count: 18, concepts: ['黄金', '白银', '铜', '铝', '稀土', '锂', '钴', '镍', '小金属'] }, - { id: 'lv2_10_2', name: '化工材料', concept_count: 17, concepts: ['氟化工', '磷化工', '钛白粉', '碳纤维', '石墨烯', '复合材料', '新材料'] } - ] - }, - { - id: 'lv1_11', - name: '大消费', - concept_count: 45, - children: [ - { id: 'lv2_11_1', name: '食品饮料', concept_count: 22, concepts: ['白酒', '啤酒', '乳业', '预制菜', '零食', '调味品', '饮料', '食品加工'] }, - { id: 'lv2_11_2', name: '消费服务', concept_count: 23, concepts: ['免税', '旅游', '酒店', '航空', '电商', '直播带货', '新零售', '跨境电商'] } - ] - }, - { - id: 'lv1_12', - name: '数字经济与金融科技', - concept_count: 38, - children: [ - { id: 'lv2_12_1', name: '金融科技', concept_count: 20, concepts: ['数字货币', '数字人民币', '区块链', 'Web3', '金融IT', '征信', '保险科技'] }, - { id: 'lv2_12_2', name: '数字化转型', concept_count: 18, concepts: ['云计算', '大数据', '物联网', '5G', '6G', '数字孪生', '元宇宙', 'SaaS'] } - ] - }, - { - id: 'lv1_13', - name: '医药健康', - concept_count: 55, - children: [ - { id: 'lv2_13_1', name: '创新药', concept_count: 25, concepts: ['创新药', 'CXO', 'ADC', '减肥药', 'GLP-1', 'CAR-T', '细胞治疗', '基因治疗', 'mRNA'] }, - { id: 'lv2_13_2', name: '医疗器械', concept_count: 18, concepts: ['医疗器械', '高值耗材', '医疗影像', '手术机器人', '康复器械', 'IVD'] }, - { id: 'lv2_13_3', name: '中医药', concept_count: 12, concepts: ['中药', '中药创新药', '中医诊疗', '中药配方颗粒'] } - ] - }, - { - id: 'lv1_14', - name: '前沿科技', - concept_count: 28, - children: [ - { id: 'lv2_14_1', name: '量子科技', concept_count: 14, concepts: ['量子计算', '量子通信', '量子芯片', '量子加密', '量子传感'] }, - { id: 'lv2_14_2', name: '脑机接口', concept_count: 14, concepts: ['脑机接口', 'Neuralink概念', '神经科技', '脑科学', '人脑芯片'] } - ] - }, - { - id: 'lv1_15', - name: '全球宏观与贸易', - concept_count: 25, - children: [ - { id: 'lv2_15_1', name: '国际贸易', concept_count: 15, concepts: ['跨境电商', '出口', '贸易摩擦', '人民币国际化', '中美贸易', '中欧贸易'] }, - { id: 'lv2_15_2', name: '宏观主题', concept_count: 10, concepts: ['美联储加息', '美债', '汇率', '通胀', '衰退预期', '地缘政治'] } - ] - }, - { - id: 'lv1_16', - name: '传统能源与资源', - concept_count: 30, - children: [ - { id: 'lv2_16_1', name: '煤炭石油', concept_count: 15, concepts: ['煤炭', '动力煤', '焦煤', '石油', '天然气', '页岩油', '油服', '煤化工'] }, - { id: 'lv2_16_2', name: '钢铁建材', concept_count: 15, concepts: ['钢铁', '特钢', '铁矿石', '水泥', '玻璃', '建材', '基建', '房地产'] } - ] - }, - { - id: 'lv1_17', - name: '公用事业与交运', - concept_count: 25, - children: [ - { id: 'lv2_17_1', name: '公用事业', concept_count: 12, concepts: ['电力', '水务', '燃气', '环保', '垃圾处理', '污水处理'] }, - { id: 'lv2_17_2', name: '交通运输', concept_count: 13, concepts: ['航空', '机场', '港口', '航运', '铁路', '公路', '物流', '快递'] } - ] - } - ]; - - return HttpResponse.json({ - hierarchy, - total_lv1: hierarchy.length, - total_concepts: hierarchy.reduce((acc, h) => acc + h.concept_count, 0) - }); - }), - - // 获取完整层级结构(更丰富的 mock 数据) - http.get('/concept-api/hierarchy', async () => { - await delay(300); - - console.log('[Mock Concept] 获取层级结构'); - - // 模拟完整的层级结构数据 - const hierarchy = [ - { - id: 'lv1_1', - name: '人工智能', - concept_count: 98, - children: [ - { - id: 'lv2_1_1', - name: 'AI基础设施', - concept_count: 52, - children: [ - { id: 'lv3_1_1_1', name: 'AI算力硬件', concept_count: 16, concepts: ['AI芯片', 'GPU概念股', '服务器', 'AI一体机', '算力租赁', 'NPU', '智能计算中心', '超算中心'] }, - { id: 'lv3_1_1_2', name: 'AI关键组件', concept_count: 24, concepts: ['HBM', 'PCB', '光通信', '存储芯片', 'CPO', '光模块', '铜连接', '高速背板'] }, - { id: 'lv3_1_1_3', name: 'AI配套设施', concept_count: 12, concepts: ['数据中心', '液冷', '电力设备', 'UPS', 'IDC服务', '边缘计算', 'AI电源'] } - ] - }, - { - id: 'lv2_1_2', - name: 'AI模型与软件', - concept_count: 18, - concepts: ['DeepSeek', 'KIMI', 'SORA概念', '国产大模型', 'ChatGPT', 'Claude', '文心一言', '通义千问', 'Gemini概念', 'AI推理'] - }, - { - id: 'lv2_1_3', - name: 'AI应用', - concept_count: 28, - children: [ - { id: 'lv3_1_3_1', name: '智能体与陪伴', concept_count: 11, concepts: ['AI伴侣', 'AI智能体', 'AI陪伴', 'AI助手', '数字人', 'AI语音', 'AI社交'] }, - { id: 'lv3_1_3_2', name: '行业应用', concept_count: 17, concepts: ['AI编程', '低代码', 'AI教育', 'AI医疗', 'AI金融', 'AI制造', 'AI安防', 'AI营销', 'AI设计'] } - ] - } - ] - }, - { - id: 'lv1_2', - name: '半导体', - concept_count: 65, - children: [ - { - id: 'lv2_2_1', - name: '半导体设备', - concept_count: 18, - children: [ - { id: 'lv3_2_1_1', name: '前道设备', concept_count: 10, concepts: ['光刻机', '刻蚀设备', '薄膜沉积', '离子注入', 'CMP设备', '清洗设备'] }, - { id: 'lv3_2_1_2', name: '后道设备', concept_count: 8, concepts: ['划片机', '键合设备', '测试设备', '封装设备', 'AOI检测'] } - ] - }, - { - id: 'lv2_2_2', - name: '半导体材料', - concept_count: 15, - concepts: ['光刻胶', '半导体材料', '石英砂', '硅片', '电子特气', '靶材', 'CMP抛光液', '湿电子化学品', '掩模版'] - }, - { - id: 'lv2_2_3', - name: '芯片设计与制造', - concept_count: 22, - children: [ - { id: 'lv3_2_3_1', name: '数字芯片', concept_count: 12, concepts: ['CPU', 'GPU', 'FPGA', 'ASIC', 'MCU', 'DSP', 'NPU芯片'] }, - { id: 'lv3_2_3_2', name: '模拟芯片', concept_count: 10, concepts: ['电源管理芯片', '驱动芯片', '传感器芯片', '射频芯片', '功率半导体', '碳化硅', '氮化镓'] } - ] - }, - { id: 'lv2_2_4', name: '先进封装', concept_count: 10, concepts: ['玻璃基板', '半导体封测', 'Chiplet', 'CoWoS', 'SiP', '3D封装', '扇出封装'] } - ] - }, - { - id: 'lv1_3', - name: '机器人', - concept_count: 52, - children: [ - { - id: 'lv2_3_1', - name: '人形机器人整机', - concept_count: 20, - children: [ - { id: 'lv3_3_1_1', name: '整机厂商', concept_count: 12, concepts: ['特斯拉机器人', '人形机器人', '智元机器人', '优必选', '小米机器人', '华为机器人', '波士顿动力概念'] }, - { id: 'lv3_3_1_2', name: '解决方案', concept_count: 8, concepts: ['具身智能', '机器人操作系统', '多模态AI', '机器人仿真'] } - ] - }, - { - id: 'lv2_3_2', - name: '机器人核心零部件', - concept_count: 20, - concepts: ['滚柱丝杆', '电子皮肤', '轴向磁通电机', '谐波减速器', '行星减速器', '伺服电机', '编码器', '力矩传感器', '六维力传感器', '灵巧手', '机器人关节'] - }, - { id: 'lv2_3_3', name: '其他类型机器人', concept_count: 12, concepts: ['工业机器人', '机器狗', '外骨骼机器人', '协作机器人', '手术机器人', '服务机器人', '物流机器人', '农业机器人'] } - ] - }, - { - id: 'lv1_4', - name: '消费电子', - concept_count: 48, - children: [ - { id: 'lv2_4_1', name: '智能终端', concept_count: 12, concepts: ['AI PC', 'AI手机', '折叠屏', '卫星通信手机', '智能手表', '智能耳机', '智能音箱', '平板电脑'] }, - { - id: 'lv2_4_2', - name: 'XR与空间计算', - concept_count: 18, - children: [ - { id: 'lv3_4_2_1', name: 'XR硬件', concept_count: 10, concepts: ['AR眼镜', 'MR', '智能眼镜', 'VR头显', 'Apple Vision Pro概念', 'Meta Quest概念'] }, - { id: 'lv3_4_2_2', name: 'XR软件与内容', concept_count: 8, concepts: ['XR内容', '空间计算', '3D引擎', 'XR社交', 'XR游戏'] } - ] - }, - { id: 'lv2_4_3', name: '华为产业链', concept_count: 18, concepts: ['华为Mate70', '鸿蒙', '华为昇腾', '华为汽车', '华为海思', '华为云', '华为手机', '华为概念', '麒麟芯片'] } - ] - }, - { - id: 'lv1_5', - name: '智能驾驶与汽车', - concept_count: 45, - children: [ - { - id: 'lv2_5_1', - name: '自动驾驶解决方案', - concept_count: 18, - children: [ - { id: 'lv3_5_1_1', name: '整体方案', concept_count: 10, concepts: ['Robotaxi', '无人驾驶', '特斯拉FSD', 'L4自动驾驶', '萝卜快跑', '百度Apollo'] }, - { id: 'lv3_5_1_2', name: '核心部件', concept_count: 8, concepts: ['自动驾驶芯片', '激光雷达', '毫米波雷达', '车载摄像头', '域控制器'] } - ] - }, - { id: 'lv2_5_2', name: '智能汽车产业链', concept_count: 18, concepts: ['比亚迪产业链', '小米汽车产业链', '特斯拉产业链', '理想产业链', '蔚来产业链', '小鹏产业链', '新能源汽车', '智能座舱', '汽车电子'] }, - { id: 'lv2_5_3', name: '车路协同', concept_count: 9, concepts: ['车路云一体化', '车路协同', 'V2X', '智能交通', '高精地图', '车联网'] } - ] - }, - { - id: 'lv1_6', - name: '新能源与电力', - concept_count: 62, - children: [ - { - id: 'lv2_6_1', - name: '新型电池技术', - concept_count: 22, - children: [ - { id: 'lv3_6_1_1', name: '电池类型', concept_count: 12, concepts: ['固态电池', '钠离子电池', '锂电池', '磷酸铁锂', '三元锂电池', '刀片电池', '4680电池'] }, - { id: 'lv3_6_1_2', name: '电池材料', concept_count: 10, concepts: ['硅基负极', '正极材料', '负极材料', '电解液', '隔膜', '锂矿', '钠矿'] } - ] - }, - { id: 'lv2_6_2', name: '电力设备与电网', concept_count: 24, concepts: ['电力', '变压器出海', '燃料电池', '特高压', '智能电网', '配电网', '虚拟电厂', '储能', '抽水蓄能', '电力物联网', '充电桩'] }, - { id: 'lv2_6_3', name: '清洁能源', concept_count: 16, concepts: ['光伏', '核电', '可控核聚变', '风电', '海上风电', '氢能源', '绿电', '碳中和', 'CCER'] } - ] - }, - { - id: 'lv1_7', - name: '空天经济', - concept_count: 38, - children: [ - { - id: 'lv2_7_1', - name: '低空经济', - concept_count: 20, - children: [ - { id: 'lv3_7_1_1', name: '飞行器', concept_count: 12, concepts: ['低空经济', 'eVTOL', '飞行汽车', '无人机', '电动垂直起降', '载人飞行器', '物流无人机'] }, - { id: 'lv3_7_1_2', name: '配套设施', concept_count: 8, concepts: ['低空空域', '通用航空', '无人机反制', '低空雷达', '飞行管控'] } - ] - }, - { id: 'lv2_7_2', name: '商业航天', concept_count: 18, concepts: ['卫星互联网', '商业航天', '北斗导航', '星链概念', '火箭发射', '卫星制造', '遥感卫星', '通信卫星', '空间站', '太空旅游'] } - ] - }, - { - id: 'lv1_8', - name: '国防军工', - concept_count: 35, - children: [ - { id: 'lv2_8_1', name: '无人作战与信息化', concept_count: 14, concepts: ['AI军工', '无人机蜂群', '军工信息化', '无人作战', '军用无人机', '军用机器人', '电子战', '军用芯片'] }, - { id: 'lv2_8_2', name: '海军装备', concept_count: 12, concepts: ['国产航母', '电磁弹射', '舰艇', '潜艇', '海军武器', '舰载机', '军船', '海洋装备'] }, - { id: 'lv2_8_3', name: '军贸出海', concept_count: 9, concepts: ['军贸', '巴黎航展', '武器出口', '国际军贸', '军工出海', '航展概念'] } - ] - }, - { - id: 'lv1_9', - name: '政策与主题', - concept_count: 42, - children: [ - { id: 'lv2_9_1', name: '国家战略', concept_count: 22, concepts: ['一带一路', '国产替代', '自主可控', '信创', '数字经济', '数据要素', '东数西算', '新基建', '乡村振兴', '共同富裕', '双循环'] }, - { id: 'lv2_9_2', name: '区域发展', concept_count: 20, concepts: ['粤港澳大湾区', '长三角一体化', '京津冀协同', '成渝经济圈', '海南自贸港', '雄安新区', '西部大开发', '中部崛起'] } - ] - }, - { - id: 'lv1_10', - name: '周期与材料', - concept_count: 35, - children: [ - { id: 'lv2_10_1', name: '有色金属', concept_count: 18, concepts: ['黄金', '白银', '铜', '铝', '稀土', '锂', '钴', '镍', '锡', '锌', '小金属'] }, - { id: 'lv2_10_2', name: '化工材料', concept_count: 17, concepts: ['氟化工', '磷化工', '钛白粉', '碳纤维', '石墨烯', '复合材料', '新材料', '高分子材料'] } - ] - }, - { - id: 'lv1_11', - name: '大消费', - concept_count: 45, - children: [ - { id: 'lv2_11_1', name: '食品饮料', concept_count: 22, concepts: ['白酒', '啤酒', '乳业', '预制菜', '零食', '调味品', '饮料', '食品加工', '餐饮', '咖啡茶饮'] }, - { id: 'lv2_11_2', name: '消费服务', concept_count: 23, concepts: ['免税', '旅游', '酒店', '航空', '电商', '直播带货', '新零售', '社区团购', '跨境电商', '本地生活'] } - ] - }, - { - id: 'lv1_12', - name: '数字经济与金融科技', - concept_count: 38, - children: [ - { id: 'lv2_12_1', name: '金融科技', concept_count: 20, concepts: ['数字货币', '数字人民币', '区块链', 'Web3', '金融IT', '征信', '保险科技', '券商金融科技'] }, - { id: 'lv2_12_2', name: '数字化转型', concept_count: 18, concepts: ['云计算', '大数据', '物联网', '5G', '6G', '数字孪生', '元宇宙', '工业互联网', 'SaaS'] } - ] - }, - { - id: 'lv1_13', - name: '医药健康', - concept_count: 55, - children: [ - { id: 'lv2_13_1', name: '创新药', concept_count: 25, concepts: ['创新药', 'CXO', 'ADC', '减肥药', 'GLP-1', 'CAR-T', '细胞治疗', '基因治疗', 'mRNA', 'PROTAC'] }, - { id: 'lv2_13_2', name: '医疗器械', concept_count: 18, concepts: ['医疗器械', '高值耗材', '医疗影像', '手术机器人', '康复器械', 'IVD', '医美设备', '眼科器械'] }, - { id: 'lv2_13_3', name: '中医药', concept_count: 12, concepts: ['中药', '中药创新药', '中医诊疗', '中药配方颗粒', '道地药材'] } - ] - }, - { - id: 'lv1_14', - name: '前沿科技', - concept_count: 28, - children: [ - { id: 'lv2_14_1', name: '量子科技', concept_count: 14, concepts: ['量子计算', '量子通信', '量子芯片', '量子加密', '量子传感', '量子软件'] }, - { id: 'lv2_14_2', name: '脑机接口', concept_count: 14, concepts: ['脑机接口', 'Neuralink概念', '神经科技', '脑科学', '人脑芯片', '神经调控'] } - ] - }, - { - id: 'lv1_15', - name: '全球宏观与贸易', - concept_count: 25, - children: [ - { id: 'lv2_15_1', name: '国际贸易', concept_count: 15, concepts: ['跨境电商', '出口', '贸易摩擦', '人民币国际化', '中美贸易', '中欧贸易', '东盟贸易'] }, - { id: 'lv2_15_2', name: '宏观主题', concept_count: 10, concepts: ['美联储加息', '美债', '汇率', '通胀', '衰退预期', '地缘政治'] } - ] - }, - { - id: 'lv1_16', - name: '传统能源与资源', - concept_count: 30, - children: [ - { id: 'lv2_16_1', name: '煤炭石油', concept_count: 15, concepts: ['煤炭', '动力煤', '焦煤', '石油', '天然气', '页岩油', '油服', '油气开采', '煤化工', '石油化工'] }, - { id: 'lv2_16_2', name: '钢铁建材', concept_count: 15, concepts: ['钢铁', '特钢', '铁矿石', '水泥', '玻璃', '建材', '基建', '房地产', '装配式建筑'] } - ] - }, - { - id: 'lv1_17', - name: '公用事业与交运', - concept_count: 25, - children: [ - { id: 'lv2_17_1', name: '公用事业', concept_count: 12, concepts: ['电力', '水务', '燃气', '环保', '垃圾处理', '污水处理', '园林绿化'] }, - { id: 'lv2_17_2', name: '交通运输', concept_count: 13, concepts: ['航空', '机场', '港口', '航运', '铁路', '公路', '物流', '快递', '冷链物流'] } - ] - } - ]; - - return HttpResponse.json({ - hierarchy, - total_lv1: hierarchy.length, - total_concepts: hierarchy.reduce((acc, h) => acc + h.concept_count, 0) - }); - }), - - // 获取层级统计数据(包含涨跌幅) - http.get('/concept-api/statistics/hierarchy', async () => { - await delay(300); - - console.log('[Mock Concept] 获取层级统计数据'); - - const statistics = [ - { lv1: '人工智能', concept_count: 98, avg_change_pct: 3.56, top_gainer: 'DeepSeek', top_gainer_change: 15.23 }, - { lv1: '半导体', concept_count: 45, avg_change_pct: 2.12, top_gainer: '光刻机', top_gainer_change: 8.76 }, - { lv1: '机器人', concept_count: 42, avg_change_pct: 4.28, top_gainer: '人形机器人', top_gainer_change: 12.45 }, - { lv1: '消费电子', concept_count: 38, avg_change_pct: 1.45, top_gainer: 'AR眼镜', top_gainer_change: 6.78 }, - { lv1: '智能驾驶与汽车', concept_count: 35, avg_change_pct: 2.89, top_gainer: 'Robotaxi', top_gainer_change: 9.32 }, - { lv1: '新能源与电力', concept_count: 52, avg_change_pct: -0.56, top_gainer: '固态电池', top_gainer_change: 5.67 }, - { lv1: '空天经济', concept_count: 28, avg_change_pct: 3.12, top_gainer: '低空经济', top_gainer_change: 11.23 }, - { lv1: '国防军工', concept_count: 25, avg_change_pct: 1.78, top_gainer: 'AI军工', top_gainer_change: 7.89 } - ]; - - return HttpResponse.json({ - statistics, - total_lv1: statistics.length, - total_concepts: statistics.reduce((acc, s) => acc + s.concept_count, 0), - market_avg_change: 2.34, - update_time: new Date().toISOString() - }); - }), - - // 获取层级涨跌幅数据(硬编码 URL - 开发环境) - http.get('http://111.198.58.126:16801/hierarchy/price', async ({ request }) => { - await delay(200); - - const url = new URL(request.url); - const tradeDate = url.searchParams.get('trade_date'); - - console.log('[Mock Concept] 获取层级涨跌幅数据 (硬编码URL):', { tradeDate }); - - // 模拟 lv1 层级涨跌幅数据 - const lv1_concepts = [ - { concept_name: '人工智能', avg_change_pct: 3.56, stock_count: 245, top_stocks: ['寒武纪', '中科曙光', '浪潮信息'] }, - { concept_name: '半导体', avg_change_pct: 2.12, stock_count: 156, top_stocks: ['北方华创', '中微公司', '韦尔股份'] }, - { concept_name: '机器人', avg_change_pct: 4.28, stock_count: 128, top_stocks: ['汇川技术', '绿的谐波', '埃斯顿'] }, - { concept_name: '消费电子', avg_change_pct: 1.45, stock_count: 98, top_stocks: ['立讯精密', '歌尔股份', '蓝思科技'] }, - { concept_name: '智能驾驶与汽车', avg_change_pct: 2.89, stock_count: 112, top_stocks: ['比亚迪', '宁德时代', '德赛西威'] }, - { concept_name: '新能源与电力', avg_change_pct: -0.56, stock_count: 186, top_stocks: ['隆基绿能', '阳光电源', '通威股份'] }, - { concept_name: '空天经济', avg_change_pct: 3.12, stock_count: 76, top_stocks: ['中航沈飞', '航发动力', '中直股份'] }, - { concept_name: '国防军工', avg_change_pct: 1.78, stock_count: 89, top_stocks: ['中航光电', '振华科技', '中航重机'] }, - { concept_name: '政策与主题', avg_change_pct: 1.23, stock_count: 95, top_stocks: ['中国软件', '东方国信', '太极股份'] }, - { concept_name: '周期与材料', avg_change_pct: -0.89, stock_count: 82, top_stocks: ['紫金矿业', '洛阳钼业', '北方稀土'] }, - { concept_name: '大消费', avg_change_pct: 0.56, stock_count: 115, top_stocks: ['贵州茅台', '五粮液', '伊利股份'] }, - { concept_name: '数字经济与金融科技', avg_change_pct: 2.34, stock_count: 88, top_stocks: ['恒生电子', '同花顺', '东方财富'] }, - { concept_name: '医药健康', avg_change_pct: -1.23, stock_count: 142, top_stocks: ['药明康德', '恒瑞医药', '迈瑞医疗'] }, - { concept_name: '前沿科技', avg_change_pct: 4.67, stock_count: 56, top_stocks: ['国盾量子', '科大国创', '三博脑科'] }, - { concept_name: '全球宏观与贸易', avg_change_pct: 0.12, stock_count: 48, top_stocks: ['跨境通', '华贸物流', '海控'] }, - { concept_name: '传统能源与资源', avg_change_pct: -0.34, stock_count: 65, top_stocks: ['中国神华', '中国石油', '海螺水泥'] }, - { concept_name: '公用事业与交运', avg_change_pct: 0.45, stock_count: 58, top_stocks: ['长江电力', '中国国航', '顺丰控股'] } - ]; - - // 模拟 lv2 层级涨跌幅数据 - const lv2_concepts = [ - { concept_name: 'AI基础设施', avg_change_pct: 4.12, stock_count: 85, top_stocks: ['寒武纪', '中科曙光', '浪潮信息'] }, - { concept_name: 'AI模型与软件', avg_change_pct: 5.67, stock_count: 42, top_stocks: ['科大讯飞', '金山办公', '万兴科技'] }, - { concept_name: 'AI应用', avg_change_pct: 2.34, stock_count: 65, top_stocks: ['海康威视', '大华股份', '虹软科技'] }, - { concept_name: '半导体设备', avg_change_pct: 3.21, stock_count: 38, top_stocks: ['北方华创', '中微公司', '拓荆科技'] }, - { concept_name: '半导体材料', avg_change_pct: 1.89, stock_count: 32, top_stocks: ['沪硅产业', '立昂微', '南大光电'] }, - { concept_name: '芯片设计与制造', avg_change_pct: 2.45, stock_count: 56, top_stocks: ['中芯国际', '海光信息', '龙芯中科'] }, - { concept_name: '先进封装', avg_change_pct: 1.23, stock_count: 22, top_stocks: ['长电科技', '通富微电', '华天科技'] }, - { concept_name: '人形机器人整机', avg_change_pct: 5.89, stock_count: 45, top_stocks: ['优必选', '傅利叶', '达闼机器人'] }, - { concept_name: '机器人核心零部件', avg_change_pct: 3.45, stock_count: 52, top_stocks: ['汇川技术', '绿的谐波', '双环传动'] }, - { concept_name: '其他类型机器人', avg_change_pct: 2.12, stock_count: 31, top_stocks: ['埃斯顿', '新时达', '拓斯达'] }, - { concept_name: '智能终端', avg_change_pct: 1.78, stock_count: 28, top_stocks: ['传音控股', '小米集团', '闻泰科技'] }, - { concept_name: 'XR与空间计算', avg_change_pct: 2.56, stock_count: 36, top_stocks: ['歌尔股份', '利亚德', '京东方'] }, - { concept_name: '华为产业链', avg_change_pct: 0.89, stock_count: 48, top_stocks: ['立讯精密', '比亚迪电子', '舜宇光学'] }, - { concept_name: '自动驾驶解决方案', avg_change_pct: 4.23, stock_count: 35, top_stocks: ['德赛西威', '经纬恒润', '华阳集团'] }, - { concept_name: '智能汽车产业链', avg_change_pct: 2.45, stock_count: 52, top_stocks: ['比亚迪', '宁德时代', '汇川技术'] }, - { concept_name: '车路协同', avg_change_pct: 1.56, stock_count: 25, top_stocks: ['千方科技', '万集科技', '金溢科技'] }, - { concept_name: '新型电池技术', avg_change_pct: 0.67, stock_count: 62, top_stocks: ['宁德时代', '亿纬锂能', '国轩高科'] }, - { concept_name: '电力设备与电网', avg_change_pct: -1.23, stock_count: 78, top_stocks: ['特变电工', '许继电气', '国电南瑞'] }, - { concept_name: '清洁能源', avg_change_pct: -0.45, stock_count: 46, top_stocks: ['隆基绿能', '阳光电源', '晶澳科技'] }, - { concept_name: '低空经济', avg_change_pct: 4.56, stock_count: 42, top_stocks: ['亿航智能', '万丰奥威', '中信海直'] }, - { concept_name: '商业航天', avg_change_pct: 1.89, stock_count: 34, top_stocks: ['航天电器', '航天发展', '中国卫星'] }, - { concept_name: '无人作战与信息化', avg_change_pct: 2.34, stock_count: 28, top_stocks: ['航天彩虹', '中无人机', '纵横股份'] }, - { concept_name: '海军装备', avg_change_pct: 1.45, stock_count: 32, top_stocks: ['中国船舶', '中船防务', '中国重工'] }, - { concept_name: '军贸出海', avg_change_pct: 1.12, stock_count: 18, top_stocks: ['中航沈飞', '航发动力', '洪都航空'] }, - // 政策与主题 - { concept_name: '国家战略', avg_change_pct: 1.56, stock_count: 52, top_stocks: ['中国软件', '东方国信', '太极股份'] }, - { concept_name: '区域发展', avg_change_pct: 0.89, stock_count: 43, top_stocks: ['深圳华强', '招商蛇口', '华发股份'] }, - // 周期与材料 - { concept_name: '有色金属', avg_change_pct: -1.23, stock_count: 45, top_stocks: ['紫金矿业', '洛阳钼业', '北方稀土'] }, - { concept_name: '化工材料', avg_change_pct: -0.56, stock_count: 37, top_stocks: ['万华化学', '华鲁恒升', '宝丰能源'] }, - // 大消费 - { concept_name: '食品饮料', avg_change_pct: 0.78, stock_count: 58, top_stocks: ['贵州茅台', '五粮液', '伊利股份'] }, - { concept_name: '消费服务', avg_change_pct: 0.34, stock_count: 57, top_stocks: ['中国中免', '锦江酒店', '宋城演艺'] }, - // 数字经济与金融科技 - { concept_name: '金融科技', avg_change_pct: 2.89, stock_count: 46, top_stocks: ['恒生电子', '同花顺', '东方财富'] }, - { concept_name: '数字化转型', avg_change_pct: 1.78, stock_count: 42, top_stocks: ['用友网络', '金蝶国际', '广联达'] }, - // 医药健康 - { concept_name: '创新药', avg_change_pct: -1.56, stock_count: 65, top_stocks: ['恒瑞医药', '百济神州', '信达生物'] }, - { concept_name: '医疗器械', avg_change_pct: -0.89, stock_count: 48, top_stocks: ['迈瑞医疗', '联影医疗', '微创医疗'] }, - { concept_name: '中医药', avg_change_pct: -1.12, stock_count: 29, top_stocks: ['片仔癀', '云南白药', '同仁堂'] }, - // 前沿科技 - { concept_name: '量子科技', avg_change_pct: 5.23, stock_count: 28, top_stocks: ['国盾量子', '科大国创', '亨通光电'] }, - { concept_name: '脑机接口', avg_change_pct: 4.12, stock_count: 28, top_stocks: ['三博脑科', '创新医疗', '博济医药'] }, - // 全球宏观与贸易 - { concept_name: '国际贸易', avg_change_pct: 0.23, stock_count: 32, top_stocks: ['跨境通', '华贸物流', '海控'] }, - { concept_name: '宏观主题', avg_change_pct: -0.01, stock_count: 16, top_stocks: ['中信证券', '国泰君安', '华泰证券'] }, - // 传统能源与资源 - { concept_name: '煤炭石油', avg_change_pct: -0.45, stock_count: 35, top_stocks: ['中国神华', '中国石油', '中海油'] }, - { concept_name: '钢铁建材', avg_change_pct: -0.23, stock_count: 30, top_stocks: ['宝钢股份', '海螺水泥', '中国建材'] }, - // 公用事业与交运 - { concept_name: '公用事业', avg_change_pct: 0.56, stock_count: 32, top_stocks: ['长江电力', '华能国际', '首创环保'] }, - { concept_name: '交通运输', avg_change_pct: 0.34, stock_count: 26, top_stocks: ['中国国航', '顺丰控股', '招商港口'] } - ]; - - // 模拟 lv3 层级涨跌幅数据 - const lv3_concepts = [ - { concept_name: 'AI算力硬件', avg_change_pct: 5.23, stock_count: 32, top_stocks: ['寒武纪', '海光信息', '景嘉微'] }, - { concept_name: 'AI关键组件', avg_change_pct: 3.89, stock_count: 45, top_stocks: ['中际旭创', '新易盛', '天孚通信'] }, - { concept_name: 'AI配套设施', avg_change_pct: 2.67, stock_count: 28, top_stocks: ['英维克', '佳力图', '申菱环境'] }, - { concept_name: '智能体与陪伴', avg_change_pct: 3.12, stock_count: 24, top_stocks: ['科大讯飞', '云从科技', '商汤科技'] }, - { concept_name: '行业应用', avg_change_pct: 1.56, stock_count: 18, top_stocks: ['海康威视', '大华股份', '萤石网络'] } - ]; - - // 模拟叶子概念涨跌幅数据 - 完整版本 - const leaf_concepts = [ - // 人工智能 - AI基础设施 - AI算力硬件 - { concept_name: 'AI芯片', avg_change_pct: 6.78, stock_count: 12, lv1: '人工智能', lv2: 'AI基础设施', lv3: 'AI算力硬件', top_stocks: ['寒武纪', '海光信息', '景嘉微'] }, - { concept_name: 'GPU概念股', avg_change_pct: 5.45, stock_count: 8, lv1: '人工智能', lv2: 'AI基础设施', lv3: 'AI算力硬件', top_stocks: ['景嘉微', '寒武纪', '海光信息'] }, - { concept_name: '服务器', avg_change_pct: 4.23, stock_count: 15, lv1: '人工智能', lv2: 'AI基础设施', lv3: 'AI算力硬件', top_stocks: ['浪潮信息', '中科曙光', '紫光股份'] }, - { concept_name: 'AI一体机', avg_change_pct: 5.12, stock_count: 6, lv1: '人工智能', lv2: 'AI基础设施', lv3: 'AI算力硬件', top_stocks: ['联想集团', '浪潮信息', '新华三'] }, - { concept_name: '算力租赁', avg_change_pct: 3.89, stock_count: 8, lv1: '人工智能', lv2: 'AI基础设施', lv3: 'AI算力硬件', top_stocks: ['鹏博士', '奥飞数据', '光环新网'] }, - { concept_name: 'NPU', avg_change_pct: 7.23, stock_count: 5, lv1: '人工智能', lv2: 'AI基础设施', lv3: 'AI算力硬件', top_stocks: ['寒武纪', '海光信息', '龙芯中科'] }, - { concept_name: '智能计算中心', avg_change_pct: 4.56, stock_count: 9, lv1: '人工智能', lv2: 'AI基础设施', lv3: 'AI算力硬件', top_stocks: ['浪潮信息', '中科曙光', '神州数码'] }, - { concept_name: '超算中心', avg_change_pct: 3.12, stock_count: 4, lv1: '人工智能', lv2: 'AI基础设施', lv3: 'AI算力硬件', top_stocks: ['中科曙光', '浪潮信息', '天融信'] }, - // AI关键组件 - { concept_name: 'HBM', avg_change_pct: 8.12, stock_count: 10, lv1: '人工智能', lv2: 'AI基础设施', lv3: 'AI关键组件', top_stocks: ['兆易创新', '北京君正', '普冉股份'] }, - { concept_name: 'PCB', avg_change_pct: 2.34, stock_count: 18, lv1: '人工智能', lv2: 'AI基础设施', lv3: 'AI关键组件', top_stocks: ['沪电股份', '深南电路', '鹏鼎控股'] }, - { concept_name: '光通信', avg_change_pct: 4.56, stock_count: 14, lv1: '人工智能', lv2: 'AI基础设施', lv3: 'AI关键组件', top_stocks: ['中际旭创', '新易盛', '光迅科技'] }, - { concept_name: '存储芯片', avg_change_pct: 3.78, stock_count: 12, lv1: '人工智能', lv2: 'AI基础设施', lv3: 'AI关键组件', top_stocks: ['兆易创新', '北京君正', '东芯股份'] }, - { concept_name: 'CPO', avg_change_pct: 9.34, stock_count: 8, lv1: '人工智能', lv2: 'AI基础设施', lv3: 'AI关键组件', top_stocks: ['中际旭创', '新易盛', '天孚通信'] }, - { concept_name: '光模块', avg_change_pct: 5.67, stock_count: 11, lv1: '人工智能', lv2: 'AI基础设施', lv3: 'AI关键组件', top_stocks: ['中际旭创', '新易盛', '光迅科技'] }, - { concept_name: '铜连接', avg_change_pct: 3.45, stock_count: 9, lv1: '人工智能', lv2: 'AI基础设施', lv3: 'AI关键组件', top_stocks: ['沃尔核材', '神宇股份', '精达股份'] }, - { concept_name: '高速背板', avg_change_pct: 2.89, stock_count: 6, lv1: '人工智能', lv2: 'AI基础设施', lv3: 'AI关键组件', top_stocks: ['沪电股份', '深南电路', '胜宏科技'] }, - // AI配套设施 - { concept_name: '数据中心', avg_change_pct: 3.12, stock_count: 16, lv1: '人工智能', lv2: 'AI基础设施', lv3: 'AI配套设施', top_stocks: ['万国数据', '世纪互联', '光环新网'] }, - { concept_name: '液冷', avg_change_pct: 6.78, stock_count: 12, lv1: '人工智能', lv2: 'AI基础设施', lv3: 'AI配套设施', top_stocks: ['英维克', '高澜股份', '申菱环境'] }, - { concept_name: '电力设备', avg_change_pct: 1.23, stock_count: 22, lv1: '人工智能', lv2: 'AI基础设施', lv3: 'AI配套设施', top_stocks: ['特变电工', '许继电气', '国电南瑞'] }, - { concept_name: 'UPS', avg_change_pct: 2.45, stock_count: 8, lv1: '人工智能', lv2: 'AI基础设施', lv3: 'AI配套设施', top_stocks: ['科华数据', '易事特', '科士达'] }, - { concept_name: 'IDC服务', avg_change_pct: 1.89, stock_count: 10, lv1: '人工智能', lv2: 'AI基础设施', lv3: 'AI配套设施', top_stocks: ['光环新网', '万国数据', '数据港'] }, - { concept_name: '边缘计算', avg_change_pct: 2.67, stock_count: 9, lv1: '人工智能', lv2: 'AI基础设施', lv3: 'AI配套设施', top_stocks: ['网宿科技', '浪潮信息', '中科创达'] }, - { concept_name: 'AI电源', avg_change_pct: 4.12, stock_count: 7, lv1: '人工智能', lv2: 'AI基础设施', lv3: 'AI配套设施', top_stocks: ['麦格米特', '英杰电气', '欧陆通'] }, - // AI模型与软件 - { concept_name: 'DeepSeek', avg_change_pct: 12.34, stock_count: 15, lv1: '人工智能', lv2: 'AI模型与软件', top_stocks: ['科大讯飞', '云从科技', '商汤科技'] }, - { concept_name: 'KIMI', avg_change_pct: 8.56, stock_count: 12, lv1: '人工智能', lv2: 'AI模型与软件', top_stocks: ['金山办公', '万兴科技', '福昕软件'] }, - { concept_name: 'SORA概念', avg_change_pct: 6.78, stock_count: 10, lv1: '人工智能', lv2: 'AI模型与软件', top_stocks: ['万兴科技', '当虹科技', '虹软科技'] }, - { concept_name: '国产大模型', avg_change_pct: 5.45, stock_count: 18, lv1: '人工智能', lv2: 'AI模型与软件', top_stocks: ['科大讯飞', '百度', '阿里巴巴'] }, - { concept_name: 'ChatGPT', avg_change_pct: 4.23, stock_count: 14, lv1: '人工智能', lv2: 'AI模型与软件', top_stocks: ['三六零', '昆仑万维', '汉王科技'] }, - { concept_name: 'Claude', avg_change_pct: 3.89, stock_count: 8, lv1: '人工智能', lv2: 'AI模型与软件', top_stocks: ['科大讯飞', '云从科技', '格灵深瞳'] }, - { concept_name: '文心一言', avg_change_pct: 5.12, stock_count: 11, lv1: '人工智能', lv2: 'AI模型与软件', top_stocks: ['百度', '拓尔思', '中科信息'] }, - { concept_name: '通义千问', avg_change_pct: 4.67, stock_count: 9, lv1: '人工智能', lv2: 'AI模型与软件', top_stocks: ['阿里巴巴', '恒生电子', '新华三'] }, - { concept_name: 'Gemini概念', avg_change_pct: 3.45, stock_count: 6, lv1: '人工智能', lv2: 'AI模型与软件', top_stocks: ['谷歌概念', '科大讯飞', '云从科技'] }, - { concept_name: 'AI推理', avg_change_pct: 4.89, stock_count: 13, lv1: '人工智能', lv2: 'AI模型与软件', top_stocks: ['寒武纪', '海光信息', '澜起科技'] }, - // 机器人 - { concept_name: '特斯拉机器人', avg_change_pct: 8.45, stock_count: 18, lv1: '机器人', lv2: '人形机器人整机', top_stocks: ['三花智控', '拓普集团', '鸣志电器'] }, - { concept_name: '人形机器人', avg_change_pct: 7.23, stock_count: 25, lv1: '机器人', lv2: '人形机器人整机', top_stocks: ['汇川技术', '绿的谐波', '埃斯顿'] }, - { concept_name: '滚柱丝杆', avg_change_pct: 4.56, stock_count: 12, lv1: '机器人', lv2: '机器人核心零部件', top_stocks: ['贝斯特', '恒立液压', '五洲新春'] }, - { concept_name: '电子皮肤', avg_change_pct: 5.89, stock_count: 8, lv1: '机器人', lv2: '机器人核心零部件', top_stocks: ['汉威科技', '柯力传感', '敏芯股份'] }, - { concept_name: '谐波减速器', avg_change_pct: 2.89, stock_count: 10, lv1: '机器人', lv2: '机器人核心零部件', top_stocks: ['绿的谐波', '中大力德', '双环传动'] }, - { concept_name: '伺服电机', avg_change_pct: 3.12, stock_count: 14, lv1: '机器人', lv2: '机器人核心零部件', top_stocks: ['汇川技术', '禾川科技', '雷赛智能'] }, - { concept_name: '六维力传感器', avg_change_pct: 4.23, stock_count: 7, lv1: '机器人', lv2: '机器人核心零部件', top_stocks: ['柯力传感', '昊志机电', '奥普特'] }, - { concept_name: '灵巧手', avg_change_pct: 5.12, stock_count: 6, lv1: '机器人', lv2: '机器人核心零部件', top_stocks: ['鸣志电器', '拓普集团', '三花智控'] }, - // 空天经济 - { concept_name: '低空经济', avg_change_pct: 6.78, stock_count: 22, lv1: '空天经济', lv2: '低空经济', top_stocks: ['万丰奥威', '中信海直', '亿航智能'] }, - { concept_name: 'eVTOL', avg_change_pct: 7.89, stock_count: 15, lv1: '空天经济', lv2: '低空经济', top_stocks: ['亿航智能', '万丰奥威', '山河智能'] }, - { concept_name: '飞行汽车', avg_change_pct: 5.45, stock_count: 12, lv1: '空天经济', lv2: '低空经济', top_stocks: ['小鹏汽车', '广汽集团', '吉利汽车'] }, - { concept_name: '无人机', avg_change_pct: 4.23, stock_count: 18, lv1: '空天经济', lv2: '低空经济', top_stocks: ['纵横股份', '航天彩虹', '中无人机'] }, - { concept_name: '卫星互联网', avg_change_pct: 3.45, stock_count: 16, lv1: '空天经济', lv2: '商业航天', top_stocks: ['中国卫星', '航天发展', '航天电器'] }, - { concept_name: '商业航天', avg_change_pct: 2.89, stock_count: 14, lv1: '空天经济', lv2: '商业航天', top_stocks: ['航天科技', '航天电子', '中航光电'] }, - { concept_name: '北斗导航', avg_change_pct: 1.56, stock_count: 20, lv1: '空天经济', lv2: '商业航天', top_stocks: ['北斗星通', '中海达', '华测导航'] }, - // 半导体 - { concept_name: '光刻机', avg_change_pct: 5.67, stock_count: 10, lv1: '半导体', lv2: '半导体设备', top_stocks: ['北方华创', '中微公司', '上海微电子'] }, - { concept_name: '刻蚀设备', avg_change_pct: 4.23, stock_count: 8, lv1: '半导体', lv2: '半导体设备', top_stocks: ['中微公司', '北方华创', '屹唐股份'] }, - { concept_name: '光刻胶', avg_change_pct: 2.34, stock_count: 12, lv1: '半导体', lv2: '半导体材料', top_stocks: ['南大光电', '彤程新材', '上海新阳'] }, - { concept_name: '硅片', avg_change_pct: 1.23, stock_count: 10, lv1: '半导体', lv2: '半导体材料', top_stocks: ['沪硅产业', '立昂微', 'TCL中环'] }, - { concept_name: 'Chiplet', avg_change_pct: 4.56, stock_count: 14, lv1: '半导体', lv2: '先进封装', top_stocks: ['长电科技', '通富微电', '华天科技'] }, - { concept_name: 'CoWoS', avg_change_pct: 5.12, stock_count: 10, lv1: '半导体', lv2: '先进封装', top_stocks: ['长电科技', '通富微电', '甬矽电子'] }, - // 新能源 - { concept_name: '固态电池', avg_change_pct: 3.45, stock_count: 18, lv1: '新能源与电力', lv2: '新型电池技术', top_stocks: ['宁德时代', '赣锋锂业', '亿纬锂能'] }, - { concept_name: '钠离子电池', avg_change_pct: 2.12, stock_count: 14, lv1: '新能源与电力', lv2: '新型电池技术', top_stocks: ['宁德时代', '传艺科技', '华阳股份'] }, - { concept_name: '光伏', avg_change_pct: -1.23, stock_count: 28, lv1: '新能源与电力', lv2: '清洁能源', top_stocks: ['隆基绿能', '通威股份', '阳光电源'] }, - { concept_name: '储能', avg_change_pct: -0.45, stock_count: 22, lv1: '新能源与电力', lv2: '电力设备与电网', top_stocks: ['阳光电源', '派能科技', '鹏辉能源'] }, - // 智能驾驶 - { concept_name: 'Robotaxi', avg_change_pct: 5.67, stock_count: 14, lv1: '智能驾驶与汽车', lv2: '自动驾驶解决方案', top_stocks: ['百度', '小马智行', '文远知行'] }, - { concept_name: '无人驾驶', avg_change_pct: 4.89, stock_count: 18, lv1: '智能驾驶与汽车', lv2: '自动驾驶解决方案', top_stocks: ['德赛西威', '经纬恒润', '中科创达'] }, - { concept_name: '激光雷达', avg_change_pct: 3.45, stock_count: 16, lv1: '智能驾驶与汽车', lv2: '自动驾驶解决方案', top_stocks: ['禾赛科技', '速腾聚创', '图达通'] }, - // 消费电子 - { concept_name: 'AR眼镜', avg_change_pct: 4.56, stock_count: 16, lv1: '消费电子', lv2: 'XR与空间计算', top_stocks: ['歌尔股份', '立讯精密', '舜宇光学'] }, - { concept_name: 'AI PC', avg_change_pct: 2.34, stock_count: 12, lv1: '消费电子', lv2: '智能终端', top_stocks: ['联想集团', '惠普', '戴尔'] }, - { concept_name: '鸿蒙', avg_change_pct: 0.89, stock_count: 18, lv1: '消费电子', lv2: '华为产业链', top_stocks: ['润和软件', '软通动力', '常山北明'] } - ]; - - const today = tradeDate ? new Date(tradeDate) : new Date(); - const tradeDateStr = today.toISOString().split('T')[0]; - - return HttpResponse.json({ - trade_date: tradeDateStr, - lv1_concepts, - lv2_concepts, - lv3_concepts, - leaf_concepts, - update_time: new Date().toISOString() - }); - }), - - // 获取层级涨跌幅数据(实时价格)- 完整版本,包含 leaf_concepts - http.get('/concept-api/hierarchy/price', async ({ request }) => { - await delay(200); - - const url = new URL(request.url); - const tradeDate = url.searchParams.get('trade_date'); - - console.log('[Mock Concept] 获取层级涨跌幅数据:', { tradeDate }); - - // 模拟 lv1 层级涨跌幅数据 - const lv1_concepts = [ - { concept_name: '人工智能', avg_change_pct: 3.56, stock_count: 245 }, - { concept_name: '半导体', avg_change_pct: 2.12, stock_count: 156 }, - { concept_name: '机器人', avg_change_pct: 4.28, stock_count: 128 }, - { concept_name: '消费电子', avg_change_pct: 1.45, stock_count: 98 }, - { concept_name: '智能驾驶与汽车', avg_change_pct: 2.89, stock_count: 112 }, - { concept_name: '新能源与电力', avg_change_pct: -0.56, stock_count: 186 }, - { concept_name: '空天经济', avg_change_pct: 3.12, stock_count: 76 }, - { concept_name: '国防军工', avg_change_pct: 1.78, stock_count: 89 }, - { concept_name: '政策与主题', avg_change_pct: 1.23, stock_count: 95 }, - { concept_name: '周期与材料', avg_change_pct: -0.89, stock_count: 82 }, - { concept_name: '大消费', avg_change_pct: 0.56, stock_count: 115 }, - { concept_name: '数字经济与金融科技', avg_change_pct: 2.34, stock_count: 88 }, - { concept_name: '医药健康', avg_change_pct: -1.23, stock_count: 142 }, - { concept_name: '前沿科技', avg_change_pct: 4.67, stock_count: 56 }, - { concept_name: '全球宏观与贸易', avg_change_pct: 0.12, stock_count: 48 } - ]; - - // 模拟 lv2 层级涨跌幅数据 - const lv2_concepts = [ - // 人工智能下的 lv2 - { concept_name: 'AI基础设施', avg_change_pct: 4.12, stock_count: 85 }, - { concept_name: 'AI模型与软件', avg_change_pct: 5.67, stock_count: 42 }, - { concept_name: 'AI应用', avg_change_pct: 2.34, stock_count: 65 }, - // 半导体下的 lv2 - { concept_name: '半导体设备', avg_change_pct: 3.21, stock_count: 38 }, - { concept_name: '半导体材料', avg_change_pct: 1.89, stock_count: 32 }, - { concept_name: '芯片设计与制造', avg_change_pct: 2.45, stock_count: 56 }, - { concept_name: '先进封装', avg_change_pct: 1.23, stock_count: 22 }, - // 机器人下的 lv2 - { concept_name: '人形机器人整机', avg_change_pct: 5.89, stock_count: 45 }, - { concept_name: '机器人核心零部件', avg_change_pct: 3.45, stock_count: 52 }, - { concept_name: '其他类型机器人', avg_change_pct: 2.12, stock_count: 31 }, - // 消费电子下的 lv2 - { concept_name: '智能终端', avg_change_pct: 1.78, stock_count: 28 }, - { concept_name: 'XR与空间计算', avg_change_pct: 2.56, stock_count: 36 }, - { concept_name: '华为产业链', avg_change_pct: 0.89, stock_count: 48 }, - // 智能驾驶下的 lv2 - { concept_name: '自动驾驶解决方案', avg_change_pct: 4.23, stock_count: 35 }, - { concept_name: '智能汽车产业链', avg_change_pct: 2.45, stock_count: 52 }, - { concept_name: '车路协同', avg_change_pct: 1.56, stock_count: 25 }, - // 新能源下的 lv2 - { concept_name: '新型电池技术', avg_change_pct: 0.67, stock_count: 62 }, - { concept_name: '电力设备与电网', avg_change_pct: -1.23, stock_count: 78 }, - { concept_name: '清洁能源', avg_change_pct: -0.45, stock_count: 46 }, - // 空天经济下的 lv2 - { concept_name: '低空经济', avg_change_pct: 4.56, stock_count: 42 }, - { concept_name: '商业航天', avg_change_pct: 1.89, stock_count: 34 }, - // 国防军工下的 lv2 - { concept_name: '无人作战与信息化', avg_change_pct: 2.34, stock_count: 28 }, - { concept_name: '海军装备', avg_change_pct: 1.45, stock_count: 32 }, - { concept_name: '军贸出海', avg_change_pct: 1.12, stock_count: 18 }, - // 政策与主题下的 lv2 - { concept_name: '国家战略', avg_change_pct: 1.56, stock_count: 52 }, - { concept_name: '区域发展', avg_change_pct: 0.89, stock_count: 43 }, - // 周期与材料下的 lv2 - { concept_name: '有色金属', avg_change_pct: -1.23, stock_count: 45 }, - { concept_name: '化工材料', avg_change_pct: -0.56, stock_count: 37 }, - // 大消费下的 lv2 - { concept_name: '食品饮料', avg_change_pct: 0.78, stock_count: 58 }, - { concept_name: '消费服务', avg_change_pct: 0.34, stock_count: 57 }, - // 数字经济与金融科技下的 lv2 - { concept_name: '金融科技', avg_change_pct: 2.89, stock_count: 46 }, - { concept_name: '数字化转型', avg_change_pct: 1.78, stock_count: 42 }, - // 医药健康下的 lv2 - { concept_name: '创新药', avg_change_pct: -1.56, stock_count: 65 }, - { concept_name: '医疗器械', avg_change_pct: -0.89, stock_count: 48 }, - { concept_name: '中医药', avg_change_pct: -1.12, stock_count: 29 }, - // 前沿科技下的 lv2 - { concept_name: '量子科技', avg_change_pct: 5.23, stock_count: 28 }, - { concept_name: '脑机接口', avg_change_pct: 4.12, stock_count: 28 }, - // 全球宏观与贸易下的 lv2 - { concept_name: '国际贸易', avg_change_pct: 0.23, stock_count: 32 }, - { concept_name: '宏观主题', avg_change_pct: -0.01, stock_count: 16 } - ]; - - // 模拟 lv3 层级涨跌幅数据 - const lv3_concepts = [ - // AI基础设施下的 lv3 - { concept_name: 'AI算力硬件', avg_change_pct: 5.23, stock_count: 32 }, - { concept_name: 'AI关键组件', avg_change_pct: 3.89, stock_count: 45 }, - { concept_name: 'AI配套设施', avg_change_pct: 2.67, stock_count: 28 }, - // AI应用下的 lv3 - { concept_name: '智能体与陪伴', avg_change_pct: 3.12, stock_count: 24 }, - { concept_name: '行业应用', avg_change_pct: 1.56, stock_count: 18 }, - // 半导体设备下的 lv3 - { concept_name: '前道设备', avg_change_pct: 3.89, stock_count: 22 }, - { concept_name: '后道设备', avg_change_pct: 2.45, stock_count: 16 }, - // 芯片设计与制造下的 lv3 - { concept_name: '数字芯片', avg_change_pct: 2.78, stock_count: 32 }, - { concept_name: '模拟芯片', avg_change_pct: 2.12, stock_count: 24 }, - // 人形机器人下的 lv3 - { concept_name: '整机厂商', avg_change_pct: 6.78, stock_count: 25 }, - { concept_name: '解决方案', avg_change_pct: 4.56, stock_count: 20 }, - // XR与空间计算下的 lv3 - { concept_name: 'XR硬件', avg_change_pct: 3.12, stock_count: 22 }, - { concept_name: 'XR软件与内容', avg_change_pct: 1.89, stock_count: 14 }, - // 自动驾驶解决方案下的 lv3 - { concept_name: '整体方案', avg_change_pct: 5.12, stock_count: 18 }, - { concept_name: '核心部件', avg_change_pct: 3.34, stock_count: 17 }, - // 新型电池技术下的 lv3 - { concept_name: '电池类型', avg_change_pct: 1.23, stock_count: 35 }, - { concept_name: '电池材料', avg_change_pct: 0.12, stock_count: 27 }, - // 低空经济下的 lv3 - { concept_name: '飞行器', avg_change_pct: 5.67, stock_count: 26 }, - { concept_name: '配套设施', avg_change_pct: 3.12, stock_count: 16 } - ]; - - // 模拟叶子概念涨跌幅数据 (leaf_concepts) - 这是最细粒度的概念数据 - const leaf_concepts = [ - // 人工智能 - AI基础设施 - AI算力硬件 - { concept_name: 'AI芯片', avg_change_pct: 6.78, stock_count: 12, lv1: '人工智能', lv2: 'AI基础设施', lv3: 'AI算力硬件', top_stocks: ['寒武纪', '海光信息', '景嘉微'] }, - { concept_name: 'GPU概念股', avg_change_pct: 5.45, stock_count: 8, lv1: '人工智能', lv2: 'AI基础设施', lv3: 'AI算力硬件', top_stocks: ['景嘉微', '寒武纪', '海光信息'] }, - { concept_name: '服务器', avg_change_pct: 4.23, stock_count: 15, lv1: '人工智能', lv2: 'AI基础设施', lv3: 'AI算力硬件', top_stocks: ['浪潮信息', '中科曙光', '紫光股份'] }, - { concept_name: 'AI一体机', avg_change_pct: 5.12, stock_count: 6, lv1: '人工智能', lv2: 'AI基础设施', lv3: 'AI算力硬件', top_stocks: ['联想集团', '浪潮信息', '新华三'] }, - { concept_name: '算力租赁', avg_change_pct: 3.89, stock_count: 8, lv1: '人工智能', lv2: 'AI基础设施', lv3: 'AI算力硬件', top_stocks: ['鹏博士', '奥飞数据', '光环新网'] }, - { concept_name: 'NPU', avg_change_pct: 7.23, stock_count: 5, lv1: '人工智能', lv2: 'AI基础设施', lv3: 'AI算力硬件', top_stocks: ['寒武纪', '海光信息', '龙芯中科'] }, - { concept_name: '智能计算中心', avg_change_pct: 4.56, stock_count: 9, lv1: '人工智能', lv2: 'AI基础设施', lv3: 'AI算力硬件', top_stocks: ['浪潮信息', '中科曙光', '神州数码'] }, - { concept_name: '超算中心', avg_change_pct: 3.12, stock_count: 4, lv1: '人工智能', lv2: 'AI基础设施', lv3: 'AI算力硬件', top_stocks: ['中科曙光', '浪潮信息', '天融信'] }, - // AI关键组件 - { concept_name: 'HBM', avg_change_pct: 8.12, stock_count: 10, lv1: '人工智能', lv2: 'AI基础设施', lv3: 'AI关键组件', top_stocks: ['兆易创新', '北京君正', '普冉股份'] }, - { concept_name: 'PCB', avg_change_pct: 2.34, stock_count: 18, lv1: '人工智能', lv2: 'AI基础设施', lv3: 'AI关键组件', top_stocks: ['沪电股份', '深南电路', '鹏鼎控股'] }, - { concept_name: '光通信', avg_change_pct: 4.56, stock_count: 14, lv1: '人工智能', lv2: 'AI基础设施', lv3: 'AI关键组件', top_stocks: ['中际旭创', '新易盛', '光迅科技'] }, - { concept_name: '存储芯片', avg_change_pct: 3.78, stock_count: 12, lv1: '人工智能', lv2: 'AI基础设施', lv3: 'AI关键组件', top_stocks: ['兆易创新', '北京君正', '东芯股份'] }, - { concept_name: 'CPO', avg_change_pct: 9.34, stock_count: 8, lv1: '人工智能', lv2: 'AI基础设施', lv3: 'AI关键组件', top_stocks: ['中际旭创', '新易盛', '天孚通信'] }, - { concept_name: '光模块', avg_change_pct: 5.67, stock_count: 11, lv1: '人工智能', lv2: 'AI基础设施', lv3: 'AI关键组件', top_stocks: ['中际旭创', '新易盛', '光迅科技'] }, - { concept_name: '铜连接', avg_change_pct: 3.45, stock_count: 9, lv1: '人工智能', lv2: 'AI基础设施', lv3: 'AI关键组件', top_stocks: ['沃尔核材', '神宇股份', '精达股份'] }, - { concept_name: '高速背板', avg_change_pct: 2.89, stock_count: 6, lv1: '人工智能', lv2: 'AI基础设施', lv3: 'AI关键组件', top_stocks: ['沪电股份', '深南电路', '胜宏科技'] }, - // AI配套设施 - { concept_name: '数据中心', avg_change_pct: 3.12, stock_count: 16, lv1: '人工智能', lv2: 'AI基础设施', lv3: 'AI配套设施', top_stocks: ['万国数据', '世纪互联', '光环新网'] }, - { concept_name: '液冷', avg_change_pct: 6.78, stock_count: 12, lv1: '人工智能', lv2: 'AI基础设施', lv3: 'AI配套设施', top_stocks: ['英维克', '高澜股份', '申菱环境'] }, - { concept_name: '电力设备', avg_change_pct: 1.23, stock_count: 22, lv1: '人工智能', lv2: 'AI基础设施', lv3: 'AI配套设施', top_stocks: ['特变电工', '许继电气', '国电南瑞'] }, - { concept_name: 'UPS', avg_change_pct: 2.45, stock_count: 8, lv1: '人工智能', lv2: 'AI基础设施', lv3: 'AI配套设施', top_stocks: ['科华数据', '易事特', '科士达'] }, - { concept_name: 'IDC服务', avg_change_pct: 1.89, stock_count: 10, lv1: '人工智能', lv2: 'AI基础设施', lv3: 'AI配套设施', top_stocks: ['光环新网', '万国数据', '数据港'] }, - { concept_name: '边缘计算', avg_change_pct: 2.67, stock_count: 9, lv1: '人工智能', lv2: 'AI基础设施', lv3: 'AI配套设施', top_stocks: ['网宿科技', '浪潮信息', '中科创达'] }, - { concept_name: 'AI电源', avg_change_pct: 4.12, stock_count: 7, lv1: '人工智能', lv2: 'AI基础设施', lv3: 'AI配套设施', top_stocks: ['麦格米特', '英杰电气', '欧陆通'] }, - // AI模型与软件 - { concept_name: 'DeepSeek', avg_change_pct: 12.34, stock_count: 15, lv1: '人工智能', lv2: 'AI模型与软件', top_stocks: ['科大讯飞', '云从科技', '商汤科技'] }, - { concept_name: 'KIMI', avg_change_pct: 8.56, stock_count: 12, lv1: '人工智能', lv2: 'AI模型与软件', top_stocks: ['金山办公', '万兴科技', '福昕软件'] }, - { concept_name: 'SORA概念', avg_change_pct: 6.78, stock_count: 10, lv1: '人工智能', lv2: 'AI模型与软件', top_stocks: ['万兴科技', '当虹科技', '虹软科技'] }, - { concept_name: '国产大模型', avg_change_pct: 5.45, stock_count: 18, lv1: '人工智能', lv2: 'AI模型与软件', top_stocks: ['科大讯飞', '百度', '阿里巴巴'] }, - { concept_name: 'ChatGPT', avg_change_pct: 4.23, stock_count: 14, lv1: '人工智能', lv2: 'AI模型与软件', top_stocks: ['三六零', '昆仑万维', '汉王科技'] }, - { concept_name: 'Claude', avg_change_pct: 3.89, stock_count: 8, lv1: '人工智能', lv2: 'AI模型与软件', top_stocks: ['科大讯飞', '云从科技', '格灵深瞳'] }, - { concept_name: '文心一言', avg_change_pct: 5.12, stock_count: 11, lv1: '人工智能', lv2: 'AI模型与软件', top_stocks: ['百度', '拓尔思', '中科信息'] }, - { concept_name: '通义千问', avg_change_pct: 4.67, stock_count: 9, lv1: '人工智能', lv2: 'AI模型与软件', top_stocks: ['阿里巴巴', '恒生电子', '新华三'] }, - { concept_name: 'Gemini概念', avg_change_pct: 3.45, stock_count: 6, lv1: '人工智能', lv2: 'AI模型与软件', top_stocks: ['谷歌概念', '科大讯飞', '云从科技'] }, - { concept_name: 'AI推理', avg_change_pct: 4.89, stock_count: 13, lv1: '人工智能', lv2: 'AI模型与软件', top_stocks: ['寒武纪', '海光信息', '澜起科技'] }, - // AI应用 - 智能体与陪伴 - { concept_name: 'AI伴侣', avg_change_pct: 4.56, stock_count: 8, lv1: '人工智能', lv2: 'AI应用', lv3: '智能体与陪伴' }, - { concept_name: 'AI智能体', avg_change_pct: 5.78, stock_count: 12, lv1: '人工智能', lv2: 'AI应用', lv3: '智能体与陪伴' }, - { concept_name: 'AI陪伴', avg_change_pct: 3.23, stock_count: 6, lv1: '人工智能', lv2: 'AI应用', lv3: '智能体与陪伴' }, - { concept_name: 'AI助手', avg_change_pct: 2.89, stock_count: 10, lv1: '人工智能', lv2: 'AI应用', lv3: '智能体与陪伴' }, - { concept_name: '数字人', avg_change_pct: 4.12, stock_count: 14, lv1: '人工智能', lv2: 'AI应用', lv3: '智能体与陪伴' }, - { concept_name: 'AI语音', avg_change_pct: 1.67, stock_count: 9, lv1: '人工智能', lv2: 'AI应用', lv3: '智能体与陪伴' }, - { concept_name: 'AI社交', avg_change_pct: 2.34, stock_count: 5, lv1: '人工智能', lv2: 'AI应用', lv3: '智能体与陪伴' }, - // AI应用 - 行业应用 - { concept_name: 'AI编程', avg_change_pct: 3.45, stock_count: 11, lv1: '人工智能', lv2: 'AI应用', lv3: '行业应用' }, - { concept_name: '低代码', avg_change_pct: 1.89, stock_count: 8, lv1: '人工智能', lv2: 'AI应用', lv3: '行业应用' }, - { concept_name: 'AI教育', avg_change_pct: 2.12, stock_count: 12, lv1: '人工智能', lv2: 'AI应用', lv3: '行业应用' }, - { concept_name: 'AI医疗', avg_change_pct: 0.78, stock_count: 15, lv1: '人工智能', lv2: 'AI应用', lv3: '行业应用' }, - { concept_name: 'AI金融', avg_change_pct: 1.56, stock_count: 10, lv1: '人工智能', lv2: 'AI应用', lv3: '行业应用' }, - { concept_name: 'AI制造', avg_change_pct: 1.23, stock_count: 8, lv1: '人工智能', lv2: 'AI应用', lv3: '行业应用' }, - { concept_name: 'AI安防', avg_change_pct: 0.89, stock_count: 11, lv1: '人工智能', lv2: 'AI应用', lv3: '行业应用' }, - { concept_name: 'AI营销', avg_change_pct: 2.34, stock_count: 7, lv1: '人工智能', lv2: 'AI应用', lv3: '行业应用' }, - { concept_name: 'AI设计', avg_change_pct: 1.67, stock_count: 6, lv1: '人工智能', lv2: 'AI应用', lv3: '行业应用' }, - // 机器人相关叶子概念 - { concept_name: '特斯拉机器人', avg_change_pct: 8.45, stock_count: 18, lv1: '机器人', lv2: '人形机器人整机', lv3: '整机厂商' }, - { concept_name: '人形机器人', avg_change_pct: 7.23, stock_count: 25, lv1: '机器人', lv2: '人形机器人整机', lv3: '整机厂商' }, - { concept_name: '智元机器人', avg_change_pct: 6.12, stock_count: 10, lv1: '机器人', lv2: '人形机器人整机', lv3: '整机厂商' }, - { concept_name: '优必选', avg_change_pct: 5.78, stock_count: 8, lv1: '机器人', lv2: '人形机器人整机', lv3: '整机厂商' }, - { concept_name: '滚柱丝杆', avg_change_pct: 4.56, stock_count: 12, lv1: '机器人', lv2: '机器人核心零部件' }, - { concept_name: '电子皮肤', avg_change_pct: 5.89, stock_count: 8, lv1: '机器人', lv2: '机器人核心零部件' }, - { concept_name: '轴向磁通电机', avg_change_pct: 3.78, stock_count: 6, lv1: '机器人', lv2: '机器人核心零部件' }, - { concept_name: '谐波减速器', avg_change_pct: 2.89, stock_count: 10, lv1: '机器人', lv2: '机器人核心零部件' }, - { concept_name: '行星减速器', avg_change_pct: 2.45, stock_count: 9, lv1: '机器人', lv2: '机器人核心零部件' }, - { concept_name: '伺服电机', avg_change_pct: 3.12, stock_count: 14, lv1: '机器人', lv2: '机器人核心零部件' }, - { concept_name: '六维力传感器', avg_change_pct: 4.23, stock_count: 7, lv1: '机器人', lv2: '机器人核心零部件' }, - { concept_name: '灵巧手', avg_change_pct: 5.12, stock_count: 6, lv1: '机器人', lv2: '机器人核心零部件' }, - // 低空经济相关叶子概念 - { concept_name: '低空经济', avg_change_pct: 6.78, stock_count: 22, lv1: '空天经济', lv2: '低空经济', lv3: '飞行器' }, - { concept_name: 'eVTOL', avg_change_pct: 7.89, stock_count: 15, lv1: '空天经济', lv2: '低空经济', lv3: '飞行器' }, - { concept_name: '飞行汽车', avg_change_pct: 5.45, stock_count: 12, lv1: '空天经济', lv2: '低空经济', lv3: '飞行器' }, - { concept_name: '无人机', avg_change_pct: 4.23, stock_count: 18, lv1: '空天经济', lv2: '低空经济', lv3: '飞行器' }, - { concept_name: '电动垂直起降', avg_change_pct: 6.12, stock_count: 10, lv1: '空天经济', lv2: '低空经济', lv3: '飞行器' }, - { concept_name: '卫星互联网', avg_change_pct: 3.45, stock_count: 16, lv1: '空天经济', lv2: '商业航天' }, - { concept_name: '商业航天', avg_change_pct: 2.89, stock_count: 14, lv1: '空天经济', lv2: '商业航天' }, - { concept_name: '北斗导航', avg_change_pct: 1.56, stock_count: 20, lv1: '空天经济', lv2: '商业航天' }, - { concept_name: '星链概念', avg_change_pct: 4.12, stock_count: 8, lv1: '空天经济', lv2: '商业航天' }, - // 半导体相关叶子概念 - { concept_name: '光刻机', avg_change_pct: 5.67, stock_count: 10, lv1: '半导体', lv2: '半导体设备', lv3: '前道设备' }, - { concept_name: '刻蚀设备', avg_change_pct: 4.23, stock_count: 8, lv1: '半导体', lv2: '半导体设备', lv3: '前道设备' }, - { concept_name: '薄膜沉积', avg_change_pct: 3.89, stock_count: 7, lv1: '半导体', lv2: '半导体设备', lv3: '前道设备' }, - { concept_name: '光刻胶', avg_change_pct: 2.34, stock_count: 12, lv1: '半导体', lv2: '半导体材料' }, - { concept_name: '半导体材料', avg_change_pct: 1.89, stock_count: 18, lv1: '半导体', lv2: '半导体材料' }, - { concept_name: '硅片', avg_change_pct: 1.23, stock_count: 10, lv1: '半导体', lv2: '半导体材料' }, - { concept_name: '电子特气', avg_change_pct: 2.56, stock_count: 8, lv1: '半导体', lv2: '半导体材料' }, - { concept_name: 'Chiplet', avg_change_pct: 4.56, stock_count: 14, lv1: '半导体', lv2: '先进封装' }, - { concept_name: 'CoWoS', avg_change_pct: 5.12, stock_count: 10, lv1: '半导体', lv2: '先进封装' }, - { concept_name: '玻璃基板', avg_change_pct: 3.78, stock_count: 8, lv1: '半导体', lv2: '先进封装' }, - // 新能源相关叶子概念 - { concept_name: '固态电池', avg_change_pct: 3.45, stock_count: 18, lv1: '新能源与电力', lv2: '新型电池技术', lv3: '电池类型' }, - { concept_name: '钠离子电池', avg_change_pct: 2.12, stock_count: 14, lv1: '新能源与电力', lv2: '新型电池技术', lv3: '电池类型' }, - { concept_name: '锂电池', avg_change_pct: 0.89, stock_count: 25, lv1: '新能源与电力', lv2: '新型电池技术', lv3: '电池类型' }, - { concept_name: '磷酸铁锂', avg_change_pct: 0.56, stock_count: 16, lv1: '新能源与电力', lv2: '新型电池技术', lv3: '电池类型' }, - { concept_name: '硅基负极', avg_change_pct: 1.78, stock_count: 10, lv1: '新能源与电力', lv2: '新型电池技术', lv3: '电池材料' }, - { concept_name: '电解液', avg_change_pct: -0.34, stock_count: 12, lv1: '新能源与电力', lv2: '新型电池技术', lv3: '电池材料' }, - { concept_name: '光伏', avg_change_pct: -1.23, stock_count: 28, lv1: '新能源与电力', lv2: '清洁能源' }, - { concept_name: '核电', avg_change_pct: 0.56, stock_count: 14, lv1: '新能源与电力', lv2: '清洁能源' }, - { concept_name: '可控核聚变', avg_change_pct: 2.89, stock_count: 8, lv1: '新能源与电力', lv2: '清洁能源' }, - { concept_name: '风电', avg_change_pct: -0.89, stock_count: 20, lv1: '新能源与电力', lv2: '清洁能源' }, - { concept_name: '储能', avg_change_pct: -0.45, stock_count: 22, lv1: '新能源与电力', lv2: '电力设备与电网' }, - { concept_name: '充电桩', avg_change_pct: 1.23, stock_count: 16, lv1: '新能源与电力', lv2: '电力设备与电网' }, - { concept_name: '特高压', avg_change_pct: -1.56, stock_count: 12, lv1: '新能源与电力', lv2: '电力设备与电网' }, - // 前沿科技叶子概念 - { concept_name: '量子计算', avg_change_pct: 6.78, stock_count: 12, lv1: '前沿科技', lv2: '量子科技' }, - { concept_name: '量子通信', avg_change_pct: 4.56, stock_count: 10, lv1: '前沿科技', lv2: '量子科技' }, - { concept_name: '量子芯片', avg_change_pct: 5.23, stock_count: 8, lv1: '前沿科技', lv2: '量子科技' }, - { concept_name: '脑机接口', avg_change_pct: 5.89, stock_count: 14, lv1: '前沿科技', lv2: '脑机接口' }, - { concept_name: 'Neuralink概念', avg_change_pct: 4.12, stock_count: 6, lv1: '前沿科技', lv2: '脑机接口' }, - { concept_name: '神经科技', avg_change_pct: 3.45, stock_count: 8, lv1: '前沿科技', lv2: '脑机接口' }, - // 消费电子叶子概念 - { concept_name: 'AI PC', avg_change_pct: 2.34, stock_count: 12, lv1: '消费电子', lv2: '智能终端' }, - { concept_name: 'AI手机', avg_change_pct: 1.89, stock_count: 14, lv1: '消费电子', lv2: '智能终端' }, - { concept_name: '折叠屏', avg_change_pct: 1.56, stock_count: 10, lv1: '消费电子', lv2: '智能终端' }, - { concept_name: 'AR眼镜', avg_change_pct: 4.56, stock_count: 16, lv1: '消费电子', lv2: 'XR与空间计算', lv3: 'XR硬件' }, - { concept_name: 'MR', avg_change_pct: 3.23, stock_count: 12, lv1: '消费电子', lv2: 'XR与空间计算', lv3: 'XR硬件' }, - { concept_name: '智能眼镜', avg_change_pct: 2.89, stock_count: 10, lv1: '消费电子', lv2: 'XR与空间计算', lv3: 'XR硬件' }, - { concept_name: 'VR头显', avg_change_pct: 1.78, stock_count: 8, lv1: '消费电子', lv2: 'XR与空间计算', lv3: 'XR硬件' }, - { concept_name: '华为Mate70', avg_change_pct: 1.23, stock_count: 12, lv1: '消费电子', lv2: '华为产业链' }, - { concept_name: '鸿蒙', avg_change_pct: 0.89, stock_count: 18, lv1: '消费电子', lv2: '华为产业链' }, - { concept_name: '华为昇腾', avg_change_pct: 2.12, stock_count: 10, lv1: '消费电子', lv2: '华为产业链' }, - { concept_name: '麒麟芯片', avg_change_pct: 1.56, stock_count: 8, lv1: '消费电子', lv2: '华为产业链' }, - // 智能驾驶叶子概念 - { concept_name: 'Robotaxi', avg_change_pct: 5.67, stock_count: 14, lv1: '智能驾驶与汽车', lv2: '自动驾驶解决方案', lv3: '整体方案' }, - { concept_name: '无人驾驶', avg_change_pct: 4.89, stock_count: 18, lv1: '智能驾驶与汽车', lv2: '自动驾驶解决方案', lv3: '整体方案' }, - { concept_name: '特斯拉FSD', avg_change_pct: 6.12, stock_count: 12, lv1: '智能驾驶与汽车', lv2: '自动驾驶解决方案', lv3: '整体方案' }, - { concept_name: '萝卜快跑', avg_change_pct: 7.23, stock_count: 8, lv1: '智能驾驶与汽车', lv2: '自动驾驶解决方案', lv3: '整体方案' }, - { concept_name: '激光雷达', avg_change_pct: 3.45, stock_count: 16, lv1: '智能驾驶与汽车', lv2: '自动驾驶解决方案', lv3: '核心部件' }, - { concept_name: '毫米波雷达', avg_change_pct: 2.78, stock_count: 10, lv1: '智能驾驶与汽车', lv2: '自动驾驶解决方案', lv3: '核心部件' }, - { concept_name: '域控制器', avg_change_pct: 3.12, stock_count: 12, lv1: '智能驾驶与汽车', lv2: '自动驾驶解决方案', lv3: '核心部件' }, - { concept_name: '比亚迪产业链', avg_change_pct: 2.89, stock_count: 20, lv1: '智能驾驶与汽车', lv2: '智能汽车产业链' }, - { concept_name: '特斯拉产业链', avg_change_pct: 3.56, stock_count: 18, lv1: '智能驾驶与汽车', lv2: '智能汽车产业链' }, - { concept_name: '小米汽车产业链', avg_change_pct: 4.12, stock_count: 15, lv1: '智能驾驶与汽车', lv2: '智能汽车产业链' }, - { concept_name: '新能源汽车', avg_change_pct: 1.78, stock_count: 35, lv1: '智能驾驶与汽车', lv2: '智能汽车产业链' }, - // 医药健康叶子概念 - { concept_name: '创新药', avg_change_pct: -1.89, stock_count: 22, lv1: '医药健康', lv2: '创新药' }, - { concept_name: 'CXO', avg_change_pct: -2.34, stock_count: 14, lv1: '医药健康', lv2: '创新药' }, - { concept_name: 'ADC', avg_change_pct: -0.78, stock_count: 10, lv1: '医药健康', lv2: '创新药' }, - { concept_name: '减肥药', avg_change_pct: 1.23, stock_count: 12, lv1: '医药健康', lv2: '创新药' }, - { concept_name: 'GLP-1', avg_change_pct: 0.89, stock_count: 8, lv1: '医药健康', lv2: '创新药' }, - { concept_name: '医疗器械', avg_change_pct: -1.12, stock_count: 18, lv1: '医药健康', lv2: '医疗器械' }, - { concept_name: '手术机器人', avg_change_pct: 0.56, stock_count: 10, lv1: '医药健康', lv2: '医疗器械' }, - { concept_name: '中药', avg_change_pct: -1.45, stock_count: 16, lv1: '医药健康', lv2: '中医药' }, - // 大消费叶子概念 - { concept_name: '白酒', avg_change_pct: 1.23, stock_count: 18, lv1: '大消费', lv2: '食品饮料' }, - { concept_name: '啤酒', avg_change_pct: 0.56, stock_count: 10, lv1: '大消费', lv2: '食品饮料' }, - { concept_name: '预制菜', avg_change_pct: -0.34, stock_count: 12, lv1: '大消费', lv2: '食品饮料' }, - { concept_name: '免税', avg_change_pct: 0.89, stock_count: 8, lv1: '大消费', lv2: '消费服务' }, - { concept_name: '旅游', avg_change_pct: 0.45, stock_count: 14, lv1: '大消费', lv2: '消费服务' }, - { concept_name: '电商', avg_change_pct: 0.12, stock_count: 16, lv1: '大消费', lv2: '消费服务' }, - { concept_name: '直播带货', avg_change_pct: -0.23, stock_count: 10, lv1: '大消费', lv2: '消费服务' }, - // 金融科技叶子概念 - { concept_name: '数字货币', avg_change_pct: 3.45, stock_count: 16, lv1: '数字经济与金融科技', lv2: '金融科技' }, - { concept_name: '区块链', avg_change_pct: 2.89, stock_count: 18, lv1: '数字经济与金融科技', lv2: '金融科技' }, - { concept_name: 'Web3', avg_change_pct: 2.12, stock_count: 10, lv1: '数字经济与金融科技', lv2: '金融科技' }, - { concept_name: '云计算', avg_change_pct: 2.34, stock_count: 22, lv1: '数字经济与金融科技', lv2: '数字化转型' }, - { concept_name: '大数据', avg_change_pct: 1.78, stock_count: 18, lv1: '数字经济与金融科技', lv2: '数字化转型' }, - { concept_name: '物联网', avg_change_pct: 1.45, stock_count: 16, lv1: '数字经济与金融科技', lv2: '数字化转型' }, - { concept_name: '5G', avg_change_pct: 0.89, stock_count: 20, lv1: '数字经济与金融科技', lv2: '数字化转型' }, - { concept_name: '6G', avg_change_pct: 2.56, stock_count: 10, lv1: '数字经济与金融科技', lv2: '数字化转型' }, - { concept_name: '元宇宙', avg_change_pct: 1.23, stock_count: 14, lv1: '数字经济与金融科技', lv2: '数字化转型' }, - // 政策主题叶子概念 - { concept_name: '一带一路', avg_change_pct: 1.89, stock_count: 22, lv1: '政策与主题', lv2: '国家战略' }, - { concept_name: '国产替代', avg_change_pct: 2.34, stock_count: 28, lv1: '政策与主题', lv2: '国家战略' }, - { concept_name: '自主可控', avg_change_pct: 2.12, stock_count: 24, lv1: '政策与主题', lv2: '国家战略' }, - { concept_name: '信创', avg_change_pct: 1.56, stock_count: 18, lv1: '政策与主题', lv2: '国家战略' }, - { concept_name: '数字经济', avg_change_pct: 1.78, stock_count: 20, lv1: '政策与主题', lv2: '国家战略' }, - { concept_name: '粤港澳大湾区', avg_change_pct: 0.89, stock_count: 16, lv1: '政策与主题', lv2: '区域发展' }, - { concept_name: '长三角一体化', avg_change_pct: 0.56, stock_count: 14, lv1: '政策与主题', lv2: '区域发展' }, - { concept_name: '海南自贸港', avg_change_pct: 1.23, stock_count: 10, lv1: '政策与主题', lv2: '区域发展' }, - // 周期材料叶子概念 - { concept_name: '黄金', avg_change_pct: -0.45, stock_count: 14, lv1: '周期与材料', lv2: '有色金属' }, - { concept_name: '白银', avg_change_pct: -0.78, stock_count: 10, lv1: '周期与材料', lv2: '有色金属' }, - { concept_name: '铜', avg_change_pct: -1.23, stock_count: 12, lv1: '周期与材料', lv2: '有色金属' }, - { concept_name: '稀土', avg_change_pct: -1.56, stock_count: 16, lv1: '周期与材料', lv2: '有色金属' }, - { concept_name: '锂', avg_change_pct: -2.34, stock_count: 14, lv1: '周期与材料', lv2: '有色金属' }, - { concept_name: '氟化工', avg_change_pct: -0.34, stock_count: 10, lv1: '周期与材料', lv2: '化工材料' }, - { concept_name: '碳纤维', avg_change_pct: 0.12, stock_count: 8, lv1: '周期与材料', lv2: '化工材料' }, - { concept_name: '石墨烯', avg_change_pct: 0.89, stock_count: 12, lv1: '周期与材料', lv2: '化工材料' }, - // 国防军工叶子概念 - { concept_name: 'AI军工', avg_change_pct: 3.12, stock_count: 10, lv1: '国防军工', lv2: '无人作战与信息化' }, - { concept_name: '无人机蜂群', avg_change_pct: 2.78, stock_count: 8, lv1: '国防军工', lv2: '无人作战与信息化' }, - { concept_name: '军工信息化', avg_change_pct: 1.89, stock_count: 12, lv1: '国防军工', lv2: '无人作战与信息化' }, - { concept_name: '国产航母', avg_change_pct: 1.56, stock_count: 10, lv1: '国防军工', lv2: '海军装备' }, - { concept_name: '电磁弹射', avg_change_pct: 1.23, stock_count: 8, lv1: '国防军工', lv2: '海军装备' }, - { concept_name: '军贸', avg_change_pct: 1.45, stock_count: 10, lv1: '国防军工', lv2: '军贸出海' }, - { concept_name: '航展概念', avg_change_pct: 0.89, stock_count: 8, lv1: '国防军工', lv2: '军贸出海' } - ]; - - // 计算交易日期(如果没有传入则使用今天) - const today = tradeDate ? new Date(tradeDate) : new Date(); - const tradeDateStr = today.toISOString().split('T')[0]; - - return HttpResponse.json({ - trade_date: tradeDateStr, - lv1_concepts, - lv2_concepts, - lv3_concepts, - leaf_concepts, - update_time: new Date().toISOString() - }); - }), - - // 获取指定层级的概念列表 - http.get('/concept-api/hierarchy/:lv1Id', async ({ params, request }) => { - await delay(300); - - const { lv1Id } = params; - const url = new URL(request.url); - const lv2Id = url.searchParams.get('lv2_id'); - - console.log('[Mock Concept] 获取层级概念列表:', { lv1Id, lv2Id }); - - // 返回该层级下的概念列表 - let concepts = generatePopularConcepts(20); - - // 添加层级信息 - concepts = concepts.map(c => ({ - ...c, - hierarchy: { - lv1: '人工智能', - lv1_id: lv1Id, - lv2: lv2Id ? 'AI基础设施' : null, - lv2_id: lv2Id - } - })); - - return HttpResponse.json({ - concepts, - total: concepts.length, - lv1_id: lv1Id, - lv2_id: lv2Id - }); - }), - - // 热门概念静态数据文件(HeroPanel 使用) - http.get('/data/concept/latest.json', async () => { - await delay(200); - console.log('[Mock Concept] 获取热门概念静态数据'); - - const concepts = generatePopularConcepts(30); - - return HttpResponse.json({ - date: new Date().toISOString().split('T')[0], - results: concepts - }); - }) -]; diff --git a/src/mocks/handlers/event.js b/src/mocks/handlers/event.js deleted file mode 100644 index 84fbefeb..00000000 --- a/src/mocks/handlers/event.js +++ /dev/null @@ -1,2590 +0,0 @@ -// src/mocks/handlers/event.js -// 事件相关的 Mock API Handlers - -import { http, HttpResponse } from "msw"; -import { - getEventRelatedStocks, - generateMockEvents, - generateHotEvents, - generatePopularKeywords, - generateDynamicNewsEvents, -} from "../data/events"; -import { - getMockFutureEvents, - getMockEventCountsForMonth, - toggleEventFollowStatus, - isEventFollowed, -} from "../data/account"; -import { generatePopularConcepts } from "./concept"; -import { getCurrentUser } from "../data/users"; - -// 模拟网络延迟 -const delay = (ms = 300) => new Promise((resolve) => setTimeout(resolve, ms)); - -// ==================== 评论内存存储 ==================== -// 用于在 Mock 环境下持久化评论数据(按 eventId 分组) -const commentsStore = new Map(); - -/** - * 初始化某个事件的 mock 评论列表 - * @param {string} eventId - 事件 ID - * @returns {Array} 初始的 15 条 mock 评论 - */ -const initializeMockComments = (eventId) => { - const comments = []; - const users = [ - { username: "张三", avatar: null }, - { username: "李四", avatar: null }, - { username: "王五", avatar: null }, - { username: "赵六", avatar: null }, - { username: "投资达人", avatar: null }, - { username: "价值投资者", avatar: null }, - { username: "技术分析师", avatar: null }, - { username: "基本面研究员", avatar: null }, - { username: "量化交易员", avatar: null }, - { username: "市场观察者", avatar: null }, - { username: "行业分析师", avatar: null }, - { username: "财经评论员", avatar: null }, - { username: "趋势跟踪者", avatar: null }, - { username: "价值发现者", avatar: null }, - { username: "理性投资人", avatar: null }, - ]; - - const commentTemplates = [ - "这个事件对相关板块影响很大,值得关注后续发展", - "相关概念股已经开始异动了,市场反应很快", - "感谢分享,这个事件我之前没注意到", - "从基本面来看,这个事件会带来实质性利好", - "需要观察后续政策落地情况,现在下结论还太早", - "相关产业链的龙头企业值得重点关注", - "这类事件一般都是短期刺激,长期影响有限", - "建议大家理性对待,不要盲目追高", - "这个消息已经在预期之中,股价可能提前反应了", - "关键要看后续的执行力度和落地速度", - "建议关注产业链上下游的投资机会", - "短期可能会有波动,但长期逻辑依然成立", - "市场情绪很高涨,需要警惕追高风险", - "从历史数据来看,类似事件后续表现都不错", - "这是一个结构性机会,需要精选个股", - ]; - - for (let i = 0; i < 15; i++) { - const hoursAgo = Math.floor(Math.random() * 48) + 1; // 1-48 小时前 - const createdAt = new Date(Date.now() - hoursAgo * 60 * 60 * 1000); - const user = users[i % users.length]; - - comments.push({ - id: `comment_${eventId}_${i + 1}`, - content: commentTemplates[i % commentTemplates.length], - content_type: "text", - author: { - id: `user_${i + 1}`, - username: user.username, - avatar: user.avatar, - }, - created_at: createdAt.toISOString(), - likes_count: Math.floor(Math.random() * 20), - is_liked: false, - }); - } - - // 按时间升序排序(最旧的在前) - comments.sort((a, b) => new Date(a.created_at) - new Date(b.created_at)); - - return comments; -}; - -/** - * 获取或初始化评论列表 - * @param {string} eventId - 事件 ID - * @returns {Array} 评论列表 - */ -const getOrInitComments = (eventId) => { - if (!commentsStore.has(eventId)) { - commentsStore.set(eventId, initializeMockComments(eventId)); - } - return commentsStore.get(eventId); -}; - -export const eventHandlers = [ - // ==================== 事件列表相关 ==================== - - // 获取事件列表 - http.get("/api/events", async ({ request }) => { - await delay(500); - - const url = new URL(request.url); - const params = { - page: parseInt(url.searchParams.get("page") || "1"), - per_page: parseInt(url.searchParams.get("per_page") || "10"), - sort: url.searchParams.get("sort") || "new", - importance: url.searchParams.get("importance") || "all", - date_range: url.searchParams.get("date_range") || "", - q: url.searchParams.get("q") || "", - industry_code: url.searchParams.get("industry_code") || "", - industry_classification: - url.searchParams.get("industry_classification") || "", - stock_code: url.searchParams.get("stock_code") || "", - }; - - console.log("[Mock] 获取事件列表:", params); - - try { - const result = generateMockEvents(params); - - // 返回格式兼容 useEventData 期望的结构 - // useEventData 期望: { success, data: { events: [], pagination: {} } } - return HttpResponse.json({ - success: true, - data: { - events: result.events, // 事件数组 - pagination: result.pagination, // 分页信息 - }, - message: "获取成功", - }); - } catch (error) { - console.error("[Mock] 获取事件列表失败:", error); - console.error("[Mock] Error details:", { - message: error.message, - stack: error.stack, - params: params, - }); - return HttpResponse.json( - { - success: false, - error: "获取事件列表失败", - data: [], - pagination: { - page: 1, - per_page: 10, - total: 0, - pages: 0, - has_prev: false, - has_next: false, - }, - }, - { status: 500 } - ); - } - }), - - // 获取热点事件 - http.get("/api/events/hot", async ({ request }) => { - await delay(300); - - const url = new URL(request.url); - const limit = parseInt(url.searchParams.get("limit") || "5"); - - console.log("[Mock] 获取热点事件, limit:", limit); - - try { - const hotEvents = generateHotEvents(limit); - - return HttpResponse.json({ - success: true, - data: hotEvents, - message: "获取成功", - }); - } catch (error) { - console.error("[Mock] 获取热点事件失败:", error); - return HttpResponse.json( - { - success: false, - error: "获取热点事件失败", - data: [], - }, - { status: 500 } - ); - } - }), - - // 获取热门关键词 - http.get("/api/events/keywords/popular", async ({ request }) => { - await delay(300); - - const url = new URL(request.url); - const limit = parseInt(url.searchParams.get("limit") || "20"); - - console.log("[Mock] 获取热门关键词, limit:", limit); - - try { - const keywords = generatePopularKeywords(limit); - - return HttpResponse.json({ - success: true, - data: keywords, - message: "获取成功", - }); - } catch (error) { - console.error("[Mock] 获取热门关键词失败:", error); - return HttpResponse.json( - { - success: false, - error: "获取热门关键词失败", - data: [], - }, - { status: 500 } - ); - } - }), - - // 获取动态新闻(实时要闻·动态追踪专用) - http.get("/api/events/dynamic-news", async ({ request }) => { - await delay(400); - - const url = new URL(request.url); - const count = parseInt(url.searchParams.get("count") || "30"); - const startTime = url.searchParams.get("start_time"); - const endTime = url.searchParams.get("end_time"); - - console.log( - "[Mock] 获取动态新闻, count:", - count, - "startTime:", - startTime, - "endTime:", - endTime - ); - - try { - let timeRange = null; - if (startTime && endTime) { - timeRange = { - startTime: new Date(startTime), - endTime: new Date(endTime), - }; - } - - const events = generateDynamicNewsEvents(timeRange, count); - - return HttpResponse.json({ - success: true, - data: events, - total: events.length, - message: "获取成功", - }); - } catch (error) { - console.error("[Mock] 获取动态新闻失败:", error); - return HttpResponse.json( - { - success: false, - error: "获取动态新闻失败", - data: [], - }, - { status: 500 } - ); - } - }), - - // ==================== 主线模式相关(必须在 :eventId 之前,否则会被通配符匹配)==================== - - // 获取按主线(lv1/lv2/lv3概念)分组的事件列表 - http.get("/api/events/mainline", async ({ request }) => { - await delay(500); - - const url = new URL(request.url); - const recentDays = parseInt(url.searchParams.get("recent_days") || "7", 10); - const importance = url.searchParams.get("importance") || "all"; - const limitPerMainline = parseInt( - url.searchParams.get("limit") || "20", - 10 - ); - const groupBy = url.searchParams.get("group_by") || "lv2"; - - try { - // 生成 mock 事件数据 - 第一个参数是 timeRange(null 表示默认24小时),第二个参数是 count - const allEvents = generateDynamicNewsEvents(null, 100); - - const mainlineDefinitions = [ - { - lv3_id: "L3_AI_CHIP", - lv3_name: "AI芯片与算力", - lv2_id: "L2_AI_INFRA", - lv2_name: "AI基础设施 (算力/CPO/PCB)", - lv1_id: "L1_TMT", - lv1_name: "TMT (科技/媒体/通信)", - keywords: ["算力", "AI芯片", "GPU", "英伟达", "华为昇腾", "寒武纪"], - }, - { - lv3_id: "L3_AI_SERVER", - lv3_name: "服务器与数据中心", - lv2_id: "L2_AI_INFRA", - lv2_name: "AI基础设施 (算力/CPO/PCB)", - lv1_id: "L1_TMT", - lv1_name: "TMT (科技/媒体/通信)", - keywords: ["服务器", "数据中心", "智算中心", "液冷"], - }, - { - lv3_id: "L3_OPTICAL", - lv3_name: "光通信与CPO", - lv2_id: "L2_AI_INFRA", - lv2_name: "AI基础设施 (算力/CPO/PCB)", - lv1_id: "L1_TMT", - lv1_name: "TMT (科技/媒体/通信)", - keywords: ["CPO", "光通信", "光模块", "光芯片"], - }, - { - lv3_id: "L3_PCB", - lv3_name: "PCB与封装", - lv2_id: "L2_AI_INFRA", - lv2_name: "AI基础设施 (算力/CPO/PCB)", - lv1_id: "L1_TMT", - lv1_name: "TMT (科技/媒体/通信)", - keywords: ["PCB", "封装", "AI PCB"], - }, - { - lv3_id: "L3_AI_APP", - lv3_name: "AI应用与大模型", - lv2_id: "L2_AI_INFRA", - lv2_name: "AI基础设施 (算力/CPO/PCB)", - lv1_id: "L1_TMT", - lv1_name: "TMT (科技/媒体/通信)", - keywords: [ - "大模型", - "智能体", - "AI", - "人工智能", - "DeepSeek", - "KIMI", - "ChatGPT", - ], - }, - { - lv3_id: "L3_CHIP_DESIGN", - lv3_name: "芯片设计", - lv2_id: "L2_SEMICONDUCTOR", - lv2_name: "半导体 (设计/制造/封测)", - lv1_id: "L1_TMT", - lv1_name: "TMT (科技/媒体/通信)", - keywords: ["芯片设计", "半导体", "IC设计"], - }, - { - lv3_id: "L3_CHIP_MFG", - lv3_name: "芯片制造", - lv2_id: "L2_SEMICONDUCTOR", - lv2_name: "半导体 (设计/制造/封测)", - lv1_id: "L1_TMT", - lv1_name: "TMT (科技/媒体/通信)", - keywords: ["晶圆", "光刻", "芯片制造", "中芯国际"], - }, - { - lv3_id: "L3_HUMANOID", - lv3_name: "人形机器人", - lv2_id: "L2_ROBOT", - lv2_name: "机器人 (人形机器人/工业机器人)", - lv1_id: "L1_TMT", - lv1_name: "TMT (科技/媒体/通信)", - keywords: ["人形机器人", "具身智能", "特斯拉机器人"], - }, - { - lv3_id: "L3_INDUSTRIAL_ROBOT", - lv3_name: "工业机器人", - lv2_id: "L2_ROBOT", - lv2_name: "机器人 (人形机器人/工业机器人)", - lv1_id: "L1_TMT", - lv1_name: "TMT (科技/媒体/通信)", - keywords: ["工业机器人", "自动化", "机器人"], - }, - { - lv3_id: "L3_MOBILE", - lv3_name: "智能手机", - lv2_id: "L2_CONSUMER_ELEC", - lv2_name: "消费电子 (手机/XR/可穿戴)", - lv1_id: "L1_TMT", - lv1_name: "TMT (科技/媒体/通信)", - keywords: ["手机", "华为", "苹果", "小米", "折叠屏"], - }, - { - lv3_id: "L3_XR", - lv3_name: "XR与可穿戴", - lv2_id: "L2_CONSUMER_ELEC", - lv2_name: "消费电子 (手机/XR/可穿戴)", - lv1_id: "L1_TMT", - lv1_name: "TMT (科技/媒体/通信)", - keywords: ["XR", "VR", "AR", "可穿戴", "MR", "Vision Pro"], - }, - { - lv3_id: "L3_5G", - lv3_name: "5G/6G通信", - lv2_id: "L2_TELECOM", - lv2_name: "通信、互联网与软件", - lv1_id: "L1_TMT", - lv1_name: "TMT (科技/媒体/通信)", - keywords: ["5G", "6G", "通信", "基站"], - }, - { - lv3_id: "L3_CLOUD", - lv3_name: "云计算与软件", - lv2_id: "L2_TELECOM", - lv2_name: "通信、互联网与软件", - lv1_id: "L1_TMT", - lv1_name: "TMT (科技/媒体/通信)", - keywords: ["云计算", "软件", "SaaS", "互联网", "数字化"], - }, - { - lv3_id: "L3_PV", - lv3_name: "光伏", - lv2_id: "L2_NEW_ENERGY", - lv2_name: "新能源 (光伏/储能/电池)", - lv1_id: "L1_NEW_ENERGY_ENV", - lv1_name: "新能源与智能汽车", - keywords: ["光伏", "太阳能", "硅片", "组件"], - }, - { - lv3_id: "L3_STORAGE", - lv3_name: "储能与电池", - lv2_id: "L2_NEW_ENERGY", - lv2_name: "新能源 (光伏/储能/电池)", - lv1_id: "L1_NEW_ENERGY_ENV", - lv1_name: "新能源与智能汽车", - keywords: ["储能", "电池", "锂电", "固态电池", "新能源"], - }, - { - lv3_id: "L3_EV_OEM", - lv3_name: "新能源整车", - lv2_id: "L2_EV", - lv2_name: "智能网联汽车", - lv1_id: "L1_NEW_ENERGY_ENV", - lv1_name: "新能源与智能汽车", - keywords: ["新能源汽车", "电动车", "比亚迪", "特斯拉", "整车"], - }, - { - lv3_id: "L3_AUTO_DRIVE", - lv3_name: "智能驾驶", - lv2_id: "L2_EV", - lv2_name: "智能网联汽车", - lv1_id: "L1_NEW_ENERGY_ENV", - lv1_name: "新能源与智能汽车", - keywords: ["智能驾驶", "自动驾驶", "智能网联", "车路协同"], - }, - { - lv3_id: "L3_DRONE", - lv3_name: "无人机", - lv2_id: "L2_LOW_ALTITUDE", - lv2_name: "低空经济 (无人机/eVTOL)", - lv1_id: "L1_ADVANCED_MFG", - lv1_name: "先进制造", - keywords: ["无人机", "低空", "空域"], - }, - { - lv3_id: "L3_EVTOL", - lv3_name: "eVTOL", - lv2_id: "L2_LOW_ALTITUDE", - lv2_name: "低空经济 (无人机/eVTOL)", - lv1_id: "L1_ADVANCED_MFG", - lv1_name: "先进制造", - keywords: ["eVTOL", "飞行汽车", "空中出租车"], - }, - { - lv3_id: "L3_AEROSPACE", - lv3_name: "航空航天", - lv2_id: "L2_MILITARY", - lv2_name: "军工 (航空航天/国防)", - lv1_id: "L1_ADVANCED_MFG", - lv1_name: "先进制造", - keywords: ["航空", "航天", "卫星", "火箭", "军工"], - }, - { - lv3_id: "L3_DEFENSE", - lv3_name: "国防军工", - lv2_id: "L2_MILITARY", - lv2_name: "军工 (航空航天/国防)", - lv1_id: "L1_ADVANCED_MFG", - lv1_name: "先进制造", - keywords: ["国防", "导弹", "军工装备"], - }, - { - lv3_id: "L3_DRUG", - lv3_name: "创新药", - lv2_id: "L2_PHARMA", - lv2_name: "医药医疗 (创新药/器械)", - lv1_id: "L1_PHARMA", - lv1_name: "医药健康", - keywords: ["创新药", "医药", "生物", "CXO"], - }, - { - lv3_id: "L3_DEVICE", - lv3_name: "医疗器械", - lv2_id: "L2_PHARMA", - lv2_name: "医药医疗 (创新药/器械)", - lv1_id: "L1_PHARMA", - lv1_name: "医药健康", - keywords: ["医疗器械", "医疗", "器械"], - }, - { - lv3_id: "L3_BANK", - lv3_name: "银行", - lv2_id: "L2_FINANCE", - lv2_name: "金融 (银行/券商/保险)", - lv1_id: "L1_FINANCE", - lv1_name: "金融", - keywords: ["银行", "金融"], - }, - { - lv3_id: "L3_BROKER", - lv3_name: "券商", - lv2_id: "L2_FINANCE", - lv2_name: "金融 (银行/券商/保险)", - lv1_id: "L1_FINANCE", - lv1_name: "金融", - keywords: ["券商", "证券"], - }, - ]; - - const hierarchyOptions = { - lv1: [ - ...new Map( - mainlineDefinitions.map((m) => [ - m.lv1_id, - { id: m.lv1_id, name: m.lv1_name }, - ]) - ).values(), - ], - lv2: [ - ...new Map( - mainlineDefinitions.map((m) => [ - m.lv2_id, - { - id: m.lv2_id, - name: m.lv2_name, - lv1_id: m.lv1_id, - lv1_name: m.lv1_name, - }, - ]) - ).values(), - ], - lv3: mainlineDefinitions.map((m) => ({ - id: m.lv3_id, - name: m.lv3_name, - lv2_id: m.lv2_id, - lv2_name: m.lv2_name, - lv1_id: m.lv1_id, - lv1_name: m.lv1_name, - })), - }; - - const mainlineGroups = {}; - const isSpecificId = - groupBy.startsWith("L1_") || - groupBy.startsWith("L2_") || - groupBy.startsWith("L3_"); - - allEvents.forEach((event) => { - const keywords = event.keywords || event.related_concepts || []; - const conceptNames = keywords - .map((k) => { - if (typeof k === "string") return k; - if (typeof k === "object" && k !== null) - return k.concept || k.name || ""; - return ""; - }) - .filter(Boolean) - .join(" "); - const titleAndDesc = `${event.title || ""} ${event.description || ""}`; - const textToMatch = `${conceptNames} ${titleAndDesc}`.toLowerCase(); - - mainlineDefinitions.forEach((mainline) => { - const matched = mainline.keywords.some((kw) => - textToMatch.includes(kw.toLowerCase()) - ); - if (matched) { - let groupKey, groupData; - - if (isSpecificId) { - if (groupBy.startsWith("L1_") && mainline.lv1_id === groupBy) { - groupKey = mainline.lv2_id; - groupData = { - group_id: mainline.lv2_id, - group_name: mainline.lv2_name, - parent_name: mainline.lv1_name, - events: [], - }; - } else if ( - groupBy.startsWith("L2_") && - mainline.lv2_id === groupBy - ) { - groupKey = mainline.lv3_id; - groupData = { - group_id: mainline.lv3_id, - group_name: mainline.lv3_name, - parent_name: mainline.lv2_name, - grandparent_name: mainline.lv1_name, - events: [], - }; - } else if ( - groupBy.startsWith("L3_") && - mainline.lv3_id === groupBy - ) { - groupKey = mainline.lv3_id; - groupData = { - group_id: mainline.lv3_id, - group_name: mainline.lv3_name, - parent_name: mainline.lv2_name, - grandparent_name: mainline.lv1_name, - events: [], - }; - } else { - return; - } - } else if (groupBy === "lv1") { - groupKey = mainline.lv1_id; - groupData = { - group_id: mainline.lv1_id, - group_name: mainline.lv1_name, - events: [], - }; - } else if (groupBy === "lv3") { - groupKey = mainline.lv3_id; - groupData = { - group_id: mainline.lv3_id, - group_name: mainline.lv3_name, - parent_name: mainline.lv2_name, - grandparent_name: mainline.lv1_name, - events: [], - }; - } else { - groupKey = mainline.lv2_id; - groupData = { - group_id: mainline.lv2_id, - group_name: mainline.lv2_name, - parent_name: mainline.lv1_name, - events: [], - }; - } - - if (!mainlineGroups[groupKey]) { - mainlineGroups[groupKey] = groupData; - } - if ( - !mainlineGroups[groupKey].events.find((e) => e.id === event.id) - ) { - mainlineGroups[groupKey].events.push(event); - } - } - }); - }); - - const generatePriceData = () => - parseFloat((Math.random() * 13 - 5).toFixed(2)); - const priceDataMap = { lv1: {}, lv2: {}, lv3: {} }; - - mainlineDefinitions.forEach((def) => { - if (!priceDataMap.lv1[def.lv1_name]) - priceDataMap.lv1[def.lv1_name] = generatePriceData(); - if (!priceDataMap.lv2[def.lv2_name]) - priceDataMap.lv2[def.lv2_name] = generatePriceData(); - if (!priceDataMap.lv3[def.lv3_name]) - priceDataMap.lv3[def.lv3_name] = generatePriceData(); - }); - - const mainlines = Object.values(mainlineGroups) - .map((group) => { - let avgChangePct = null; - if (groupBy === "lv1" || groupBy.startsWith("L1_")) { - avgChangePct = groupBy.startsWith("L1_") - ? priceDataMap.lv2[group.group_name] - : priceDataMap.lv1[group.group_name]; - } else if (groupBy === "lv3" || groupBy.startsWith("L2_")) { - avgChangePct = priceDataMap.lv3[group.group_name]; - } else { - avgChangePct = priceDataMap.lv2[group.group_name]; - } - return { - ...group, - events: group.events.slice(0, limitPerMainline), - event_count: Math.min(group.events.length, limitPerMainline), - avg_change_pct: avgChangePct ?? null, - price_date: new Date().toISOString().split("T")[0], - }; - }) - .filter((group) => group.event_count > 0) - .sort((a, b) => b.event_count - a.event_count); - - const groupedEventIds = new Set(); - mainlines.forEach((m) => - m.events.forEach((e) => groupedEventIds.add(e.id)) - ); - const ungroupedCount = allEvents.filter((e) => !groupedEventIds.has(e.id)) - .length; - - return HttpResponse.json({ - success: true, - data: { - mainlines, - total_events: allEvents.length, - mainline_count: mainlines.length, - ungrouped_count: ungroupedCount, - group_by: groupBy, - hierarchy_options: hierarchyOptions, - }, - }); - } catch (error) { - console.error("[Mock Event] 主线数据获取失败:", error); - return HttpResponse.json( - { success: false, error: error.message || "获取主线数据失败" }, - { status: 500 } - ); - } - }), - - // ==================== 事件详情相关 ==================== - - // 获取事件详情 - http.get("/api/events/:eventId", async ({ params }) => { - await delay(200); - - const { eventId } = params; - const numericEventId = parseInt(eventId, 10); - - console.log("[Mock] 获取事件详情, eventId:", numericEventId); - - try { - // 检查是否已关注 - const isFollowing = isEventFollowed(numericEventId); - - // 返回模拟的事件详情数据 - return HttpResponse.json({ - success: true, - data: { - id: numericEventId, - title: `测试事件 ${eventId} - 重大政策发布`, - description: - "这是一个模拟的事件描述,用于开发测试。该事件涉及重要政策变化,可能对相关板块产生显著影响。建议关注后续发展动态。", - importance: ["S", "A", "B", "C"][Math.floor(Math.random() * 4)], - created_at: new Date().toISOString(), - trading_date: new Date().toISOString().split("T")[0], - event_type: ["政策", "财报", "行业", "宏观"][ - Math.floor(Math.random() * 4) - ], - related_avg_chg: parseFloat((Math.random() * 10 - 5).toFixed(2)), - follower_count: Math.floor(Math.random() * 500) + 50, - view_count: Math.floor(Math.random() * 5000) + 100, - is_following: isFollowing, // 使用内存状态 - post_count: Math.floor(Math.random() * 50), - expectation_surprise_score: parseFloat( - (Math.random() * 100).toFixed(1) - ), - }, - message: "获取成功", - }); - } catch (error) { - console.error("[Mock] 获取事件详情失败:", error); - return HttpResponse.json( - { - success: false, - error: "获取事件详情失败", - data: null, - }, - { status: 500 } - ); - } - }), - - // 获取事件超预期得分 - http.get("/api/events/:eventId/expectation-score", async ({ params }) => { - await delay(200); - - const { eventId } = params; - - console.log("[Mock] 获取事件超预期得分, eventId:", eventId); - - try { - // 生成模拟的超预期得分数据 - const score = parseFloat((Math.random() * 100).toFixed(1)); - const avgChange = parseFloat((Math.random() * 10 - 2).toFixed(2)); - const maxChange = parseFloat((Math.random() * 15).toFixed(2)); - - return HttpResponse.json({ - success: true, - data: { - event_id: parseInt(eventId), - expectation_score: score, - avg_change: avgChange, - max_change: maxChange, - stock_count: Math.floor(Math.random() * 20) + 5, - updated_at: new Date().toISOString(), - }, - message: "获取成功", - }); - } catch (error) { - console.error("[Mock] 获取事件超预期得分失败:", error); - return HttpResponse.json( - { - success: false, - error: "获取事件超预期得分失败", - data: null, - }, - { status: 500 } - ); - } - }), - - // 获取事件相关股票 - http.get("/api/events/:eventId/stocks", async ({ params }) => { - await delay(300); - - const { eventId } = params; - - console.log("[Mock] 获取事件相关股票, eventId:", eventId); - - try { - const relatedStocks = getEventRelatedStocks(eventId); - - return HttpResponse.json({ - success: true, - data: relatedStocks, - message: "获取成功", - }); - } catch (error) { - console.error("[Mock] 获取事件相关股票失败:", error); - return HttpResponse.json( - { - success: false, - error: "获取事件相关股票失败", - data: [], - }, - { status: 500 } - ); - } - }), - - // 获取事件相关概念 - http.get("/api/events/:eventId/concepts", async ({ params }) => { - await delay(300); - - const { eventId } = params; - - console.log("[Mock] 获取事件相关概念, eventId:", eventId); - - try { - // 返回热门概念列表(模拟真实场景下根据事件标题搜索的结果) - const concepts = generatePopularConcepts(5); - - return HttpResponse.json({ - success: true, - data: concepts, - message: "获取成功", - }); - } catch (error) { - console.error("[Mock] 获取事件相关概念失败:", error); - return HttpResponse.json( - { - success: false, - error: "获取事件相关概念失败", - data: [], - }, - { status: 500 } - ); - } - }), - - // 切换事件关注状态(使用内存状态管理) - http.post("/api/events/:eventId/follow", async ({ params, request }) => { - await delay(200); - - const { eventId } = params; - const numericEventId = parseInt(eventId, 10); - - console.log("[Mock] 切换事件关注状态, eventId:", numericEventId); - - try { - // 尝试从请求体获取事件数据(用于新关注时保存完整信息) - let eventData = null; - try { - const body = await request.json(); - if (body && body.title) { - eventData = body; - } - } catch { - // 没有请求体或解析失败,忽略 - } - - // 使用内存状态管理切换关注 - const { isFollowing, followerCount } = toggleEventFollowStatus( - numericEventId, - eventData - ); - - return HttpResponse.json({ - success: true, - data: { - is_following: isFollowing, - follower_count: followerCount, - }, - message: isFollowing ? "关注成功" : "取消关注成功", - }); - } catch (error) { - console.error("[Mock] 切换事件关注状态失败:", error); - return HttpResponse.json( - { - success: false, - error: "切换关注状态失败", - data: null, - }, - { status: 500 } - ); - } - }), - - // 事件情绪投票(看多/看空) - http.post( - "/api/events/:eventId/sentiment-vote", - async ({ params, request }) => { - await delay(200); - - const { eventId } = params; - const numericEventId = parseInt(eventId, 10); - - console.log("[Mock] 事件情绪投票, eventId:", numericEventId); - - try { - const body = await request.json(); - const voteType = body.vote_type; // 'bullish', 'bearish', 或 null - - // 使用内存状态管理投票 - // 简单模拟:根据 eventId 生成基础数据 - const baseBullish = ((numericEventId * 7) % 50) + 10; - const baseBearish = ((numericEventId * 3) % 30) + 5; - - // 根据投票类型调整计数 - let bullishCount = baseBullish; - let bearishCount = baseBearish; - - if (voteType === "bullish") { - bullishCount += 1; - } else if (voteType === "bearish") { - bearishCount += 1; - } - - return HttpResponse.json({ - success: true, - data: { - user_vote: voteType || null, - bullish_count: bullishCount, - bearish_count: bearishCount, - }, - message: voteType ? "投票成功" : "取消投票成功", - }); - } catch (error) { - console.error("[Mock] 事件情绪投票失败:", error); - return HttpResponse.json( - { - success: false, - error: "投票失败", - data: null, - }, - { status: 500 } - ); - } - } - ), - - // 获取事件传导链分析数据 - http.get("/api/events/:eventId/transmission", async ({ params }) => { - await delay(500); - - const { eventId } = params; - - console.log("[Mock] 获取事件传导链分析, eventId:", eventId); - - // Mock数据:事件传导链 - const mockTransmissionData = { - success: true, - data: { - nodes: [ - { - id: "1", - name: "主要事件", - category: "事件", - value: 50, - extra: { - node_type: "event", - description: "这是主要事件节点", - importance_score: 50, - is_main_event: true, - }, - }, - { - id: "2", - name: "半导体行业", - category: "行业", - value: 40, - extra: { - node_type: "industry", - description: "受影响的半导体行业", - importance_score: 40, - is_main_event: false, - }, - }, - { - id: "3", - name: "芯片制造", - category: "行业", - value: 35, - extra: { - node_type: "industry", - description: "芯片制造产业链", - importance_score: 35, - is_main_event: false, - }, - }, - { - id: "4", - name: "A公司", - category: "公司", - value: 30, - extra: { - node_type: "company", - description: "龙头企业A", - importance_score: 30, - stock_code: "600000", - is_main_event: false, - }, - }, - { - id: "5", - name: "B公司", - category: "公司", - value: 25, - extra: { - node_type: "company", - description: "龙头企业B", - importance_score: 25, - stock_code: "600001", - is_main_event: false, - }, - }, - { - id: "6", - name: "相关政策", - category: "政策", - value: 30, - extra: { - node_type: "policy", - description: "国家产业政策支持", - importance_score: 30, - is_main_event: false, - }, - }, - ], - edges: [ - { - source: "1", - target: "2", - value: 0.8, - extra: { - transmission_strength: 0.8, - transmission_type: "直接影响", - description: "主事件对半导体行业的直接影响", - }, - }, - { - source: "2", - target: "3", - value: 0.7, - extra: { - transmission_strength: 0.7, - transmission_type: "产业链传导", - description: "半导体到芯片制造的传导", - }, - }, - { - source: "3", - target: "4", - value: 0.6, - extra: { - transmission_strength: 0.6, - transmission_type: "企业影响", - description: "对龙头企业A的影响", - }, - }, - { - source: "3", - target: "5", - value: 0.5, - extra: { - transmission_strength: 0.5, - transmission_type: "企业影响", - description: "对龙头企业B的影响", - }, - }, - { - source: "6", - target: "1", - value: 0.7, - extra: { - transmission_strength: 0.7, - transmission_type: "政策驱动", - description: "政策对主事件的推动作用", - }, - }, - { - source: "6", - target: "2", - value: 0.6, - extra: { - transmission_strength: 0.6, - transmission_type: "政策支持", - description: "政策对行业的支持", - }, - }, - ], - categories: ["事件", "行业", "公司", "政策", "技术", "市场", "其他"], - }, - message: "获取成功", - }; - - return HttpResponse.json(mockTransmissionData); - }), - - // 获取桑基图数据 - http.get("/api/events/:eventId/sankey-data", async ({ params }) => { - await delay(300); - const { eventId } = params; - console.log("[Mock] 获取桑基图数据, eventId:", eventId); - - const mockSankeyData = { - success: true, - data: { - nodes: [ - { - name: "相关政策", - type: "policy", - level: 0, - color: "#10ac84", - }, - { - name: "主要事件", - type: "event", - level: 0, - color: "#ff4757", - }, - { - name: "半导体行业", - type: "industry", - level: 1, - color: "#00d2d3", - }, - { - name: "芯片制造", - type: "industry", - level: 2, - color: "#00d2d3", - }, - { - name: "A公司", - type: "company", - level: 3, - color: "#54a0ff", - }, - { - name: "B公司", - type: "company", - level: 3, - color: "#54a0ff", - }, - ], - links: [ - { source: 0, target: 1, value: 7 }, // 相关政策 -> 主要事件 - { source: 0, target: 2, value: 6 }, // 相关政策 -> 半导体行业 - { source: 1, target: 2, value: 8 }, // 主要事件 -> 半导体行业 - { source: 2, target: 3, value: 7 }, // 半导体行业 -> 芯片制造 - { source: 3, target: 4, value: 6 }, // 芯片制造 -> A公司 - { source: 3, target: 5, value: 5 }, // 芯片制造 -> B公司 - ], - }, - message: "获取成功", - }; - - return HttpResponse.json(mockSankeyData); - }), - - // 获取传导链节点详情 - http.get("/api/events/:eventId/chain-node/:nodeId", async ({ params }) => { - await delay(300); - - const { eventId, nodeId } = params; - - console.log("[Mock] 获取节点详情, eventId:", eventId, "nodeId:", nodeId); - - // 根据节点ID返回不同的详细信息 - const nodeDetailsMap = { - 1: { - success: true, - data: { - node: { - id: "1", - name: "主要事件", - type: "event", - description: - "这是影响整个产业链的重大事件,涉及政策调整和技术突破,对下游产业产生深远影响。", - importance_score: 50, - total_connections: 2, - incoming_connections: 1, - outgoing_connections: 1, - }, - parents: [ - { - id: "6", - name: "相关政策", - transmission_mechanism: { - data: [ - { - author: "国务院", - sentences: - "为加快实施创新驱动发展战略,推动产业转型升级,国家将对重点领域给予财政补贴支持,单个项目最高补贴金额可达5000万元,同时享受研发费用加计扣除175%的税收优惠政策", - query_part: - "国家财政补贴最高5000万元,研发费用加计扣除175%", - match_score: "好", - declare_date: "2024-01-15T00:00:00", - report_title: "关于促进产业高质量发展的若干政策措施", - }, - { - author: "工信部", - sentences: - "根据《重点产业扶持目录》,对符合条件的企业和项目,将优先纳入政府采购名单,并提供专项资金支持,确保政策红利直接惠及实体经济", - query_part: "政府采购优先支持,专项资金直达企业", - match_score: "好", - declare_date: "2024-01-20T00:00:00", - report_title: "工业和信息化部关于落实产业扶持政策的通知", - }, - ], - }, - direction: "positive", - strength: 70, - is_circular: false, - }, - ], - children: [ - { - id: "2", - name: "半导体行业(正向影响)", - transmission_mechanism: { - data: [ - { - author: "李明", - organization: "中国电子信息产业发展研究院", - sentences: - "在技术突破和应用场景快速扩张的双重驱动下,国内半导体市场呈现爆发式增长态势。据统计,2024年上半年半导体市场规模达到1.2万亿元,同比增长32%,其中新能源汽车和AI算力芯片需求贡献了超过60%的增量", - query_part: "技术突破和需求激增推动半导体市场增长32%", - match_score: "好", - declare_date: "2024-07-10T00:00:00", - report_title: "2024年上半年中国半导体产业发展报告", - }, - ], - }, - direction: "positive", - strength: 80, - is_circular: false, - }, - { - id: "7", - name: "传统制造业(负向影响)", - transmission_mechanism: { - data: [ - { - author: "张华", - organization: "经济观察报", - sentences: - "随着半导体等高科技产业获得大量政策和资金支持,传统制造业面临融资难、用工成本上升等多重压力。部分劳动密集型企业利润率下降15%,行业整体投资意愿降低", - query_part: "资源向高科技倾斜导致传统制造业承压", - match_score: "好", - declare_date: "2024-06-15T00:00:00", - report_title: "传统制造业转型升级调研报告", - }, - ], - }, - direction: "negative", - strength: 60, - is_circular: false, - }, - { - id: "8", - name: "能源行业(中性影响)", - transmission_mechanism: { - data: [ - { - author: "王刚", - organization: "能源研究所", - sentences: - "半导体产业扩张带来电力需求增长约8%,但同时推动节能技术应用,整体能源消费结构趋于优化。新建芯片工厂虽增加用电负荷,但智能电网技术应用使能源利用效率提升12%", - query_part: "半导体产业对能源行业影响相对中性", - match_score: "中", - declare_date: "2024-07-01T00:00:00", - report_title: "高科技产业能源消费分析", - }, - ], - }, - direction: "neutral", - strength: 40, - is_circular: false, - }, - { - id: "9", - name: "教育培训行业(未明确方向)", - transmission_mechanism: { - data: [ - { - author: "赵敏", - organization: "教育部职业教育司", - sentences: - "半导体产业快速发展催生大量专业人才需求,各类培训机构、职业院校纷纷开设相关课程。预计未来三年将新增半导体专业学员超过50万人,带动职业教育市场规模扩大", - query_part: "半导体产业推动职业教育发展", - match_score: "好", - declare_date: "2024-06-20T00:00:00", - report_title: "半导体人才培养白皮书", - }, - ], - }, - strength: 50, - is_circular: false, - }, - ], - }, - }, - 2: { - success: true, - data: { - node: { - id: "2", - name: "半导体行业", - type: "industry", - description: - "半导体行业是现代科技产业的基础,受到主事件和政策的双重推动,迎来新一轮发展机遇。", - importance_score: 40, - total_connections: 3, - incoming_connections: 2, - outgoing_connections: 1, - }, - parents: [ - { - id: "1", - name: "主要事件", - transmission_mechanism: { - data: [ - { - author: "刘洋", - organization: "中国半导体行业协会", - sentences: - "受益于新能源汽车、5G通信等新兴应用领域的爆发式增长,国内半导体市场需求持续旺盛,2024年Q1市场规模同比增长28%,创历史新高", - query_part: "新兴应用推动半导体需求增长28%", - match_score: "好", - declare_date: "2024-04-05T00:00:00", - report_title: "2024年Q1中国半导体行业景气度报告", - }, - { - author: "刘洋", - organization: "中国半导体行业协会", - sentences: - "受益于新能源汽车、5G通信等新兴应用领域的爆发式增长,国内半导体市场需求持续旺盛,2024年Q1市场规模同比增长28%,创历史新高", - query_part: "新兴应用推动半导体需求增长28%", - match_score: "好", - declare_date: "2024-04-05T00:00:00", - report_title: "2024年Q1中国半导体行业景气度报告", - }, - ], - }, - direction: "positive", - strength: 80, - is_circular: false, - }, - { - id: "6", - name: "相关政策", - transmission_mechanism: { - data: [ - { - author: "国家发改委", - sentences: - "《国家集成电路产业发展推进纲要》明确提出,到2025年半导体产业自给率要达到70%以上,国家将设立专项基金规模超过3000亿元,重点支持半导体设备、材料、设计等关键环节", - query_part: "半导体自给率目标70%,专项基金3000亿", - match_score: "好", - declare_date: "2024-02-01T00:00:00", - report_title: "国家集成电路产业发展推进纲要(2024-2030)", - }, - ], - }, - direction: "positive", - strength: 60, - is_circular: false, - }, - ], - children: [ - { - id: "3", - name: "芯片制造", - transmission_mechanism: { - data: [ - { - author: "张明", - organization: "中信证券", - sentences: - "在半导体行业景气度持续提升的背景下,下游芯片制造企业订单饱满,产能利用率达到历史新高,预计2024年产能扩张将达到30%以上,技术工艺也将从28nm向14nm升级", - query_part: "半导体行业繁荣带动芯片制造产能扩张30%", - match_score: "好", - declare_date: "2024-03-15T00:00:00", - report_title: "半导体行业深度报告:产业链景气度传导分析", - }, - { - author: "李华", - organization: "海通证券", - sentences: - "芯片制造环节作为半导体产业链核心,受益于上游材料供应稳定和下游应用需求旺盛,技术迭代速度明显加快,先进制程占比持续提升", - query_part: "技术迭代加快,先进制程占比提升", - match_score: "好", - declare_date: "2024-02-28T00:00:00", - report_title: "芯片制造行业跟踪报告", - }, - ], - }, - direction: "positive", - strength: 70, - is_circular: false, - }, - ], - }, - }, - 3: { - success: true, - data: { - node: { - id: "3", - name: "芯片制造", - type: "industry", - description: - "芯片制造作为半导体产业链的核心环节,在上游需求推动下,产能利用率提升,技术迭代加快。", - importance_score: 35, - total_connections: 3, - incoming_connections: 1, - outgoing_connections: 2, - }, - parents: [ - { - id: "2", - name: "半导体行业", - transmission_mechanism: { - data: [ - { - author: "张明", - sentences: - "在半导体行业景气度持续提升的背景下,下游芯片制造企业订单饱满,产能利用率达到历史新高,预计2024年产能扩张将达到30%以上,技术工艺也将从28nm向14nm升级", - query_part: "半导体行业繁荣带动芯片制造产能扩张30%", - match_score: "好", - declare_date: "2024-03-15T00:00:00", - report_title: "半导体行业深度报告:产业链景气度传导分析", - }, - { - author: "李华", - sentences: - "芯片制造环节作为半导体产业链核心,受益于上游材料供应稳定和下游应用需求旺盛,技术迭代速度明显加快,先进制程占比持续提升", - query_part: "技术迭代加快,先进制程占比提升", - match_score: "好", - declare_date: "2024-02-28T00:00:00", - report_title: "芯片制造行业跟踪报告", - }, - ], - }, - direction: "positive", - strength: 70, - is_circular: false, - }, - ], - children: [ - { - id: "4", - name: "A公司", - transmission_mechanism: { - data: [ - { - author: "王芳", - organization: "国泰君安", - sentences: - "A公司作为国内芯片制造龙头企业,在手订单已排至2024年Q4,预计全年营收增长45%,净利润增长60%以上。公司28nm及以下先进制程产能占比已达到40%,技术实力行业领先", - query_part: "A公司在手订单充足,预计营收增长45%", - match_score: "好", - declare_date: "2024-04-10T00:00:00", - report_title: "A公司深度研究:受益芯片制造景气周期", - }, - ], - }, - direction: "positive", - strength: 60, - is_circular: false, - }, - { - id: "5", - name: "B公司", - transmission_mechanism: { - data: [ - { - author: "赵强", - organization: "华泰证券", - sentences: - "随着芯片制造产能的大规模扩张,上游设备和材料供应商迎来历史性机遇。B公司作为核心配套企业,订单量同比增长55%,产品供不应求,预计2024年营收将突破百亿大关。公司在封装测试领域的市场份额已提升至国内第二位", - query_part: "B公司订单增长55%,营收将破百亿", - match_score: "好", - declare_date: "2024-05-08T00:00:00", - report_title: "B公司跟踪报告:芯片产业链配套龙头崛起", - }, - { - author: "陈彤", - organization: "国信证券", - sentences: - "B公司深度受益于芯片制造产业链的景气度传导。公司凭借先进的封装技术和完善的产能布局,成功绑定多家头部芯片制造企业,形成稳定的供应关系。随着下游客户产能持续扩张,公司业绩增长确定性强", - query_part: "B公司受益产业链景气度,业绩增长确定性强", - match_score: "好", - declare_date: "2024-06-01T00:00:00", - report_title: - "半导体封装测试行业专题:产业链景气度传导分析", - }, - ], - }, - direction: "positive", - strength: 50, - is_circular: false, - }, - ], - }, - }, - 4: { - success: true, - data: { - node: { - id: "4", - name: "A公司", - type: "company", - description: - "A公司是行业龙头企业,拥有先进的芯片制造技术和完整的产业链布局,在本轮产业升级中占据有利位置。", - importance_score: 30, - stock_code: "600000", - total_connections: 1, - incoming_connections: 1, - outgoing_connections: 0, - }, - parents: [ - { - id: "3", - name: "芯片制造", - transmission_mechanism: { - data: [ - { - author: "王芳", - sentences: - "A公司作为国内芯片制造龙头企业,在手订单已排至2024年Q4,预计全年营收增长45%,净利润增长60%以上。公司28nm及以下先进制程产能占比已达到40%,技术实力行业领先", - query_part: "A公司在手订单充足,预计营收增长45%", - match_score: "好", - declare_date: "2024-04-10T00:00:00", - report_title: "A公司深度研究:受益芯片制造景气周期", - }, - ], - }, - direction: "positive", - strength: 60, - is_circular: false, - }, - ], - children: [], - }, - }, - 5: { - success: true, - data: { - node: { - id: "5", - name: "B公司", - type: "company", - description: - "B公司专注于芯片封装测试领域,随着上游制造产能释放,公司订单饱满,业绩稳步增长。", - importance_score: 25, - stock_code: "600001", - total_connections: 1, - incoming_connections: 1, - outgoing_connections: 0, - }, - parents: [ - { - id: "3", - name: "芯片制造", - transmission_mechanism: { - data: [ - { - author: "赵强", - organization: "华泰证券", - sentences: - "随着芯片制造产能的大规模扩张,上游设备和材料供应商迎来历史性机遇。B公司作为核心配套企业,订单量同比增长55%,产品供不应求,预计2024年营收将突破百亿大关", - query_part: "B公司订单增长55%,营收将破百亿", - match_score: "好", - declare_date: "2024-05-08T00:00:00", - report_title: "B公司跟踪报告:芯片产业链配套龙头崛起", - }, - ], - }, - direction: "positive", - strength: 50, - is_circular: false, - }, - ], - children: [], - }, - }, - 6: { - success: true, - data: { - node: { - id: "6", - name: "相关政策", - type: "policy", - description: - "国家出台了一系列产业扶持政策,包括财政补贴、税收减免和研发支持,旨在推动产业自主创新和进口替代。", - importance_score: 30, - total_connections: 2, - incoming_connections: 0, - outgoing_connections: 2, - }, - parents: [], - children: [ - { - id: "1", - name: "主要事件", - transmission_mechanism: { - data: [ - { - author: "国务院", - sentences: - "为加快实施创新驱动发展战略,推动产业转型升级,国家将对重点领域给予财政补贴支持,单个项目最高补贴金额可达5000万元,同时享受研发费用加计扣除175%的税收优惠政策", - query_part: - "国家财政补贴最高5000万元,研发费用加计扣除175%", - match_score: "好", - declare_date: "2024-01-15T00:00:00", - report_title: "关于促进产业高质量发展的若干政策措施", - }, - { - author: "工信部", - sentences: - "将重点支持关键核心技术攻关和产业化应用,建立产业发展专项基金,规模达到1000亿元,引导社会资本共同参与产业发展", - query_part: "设立1000亿元产业发展专项基金", - match_score: "好", - declare_date: "2024-02-01T00:00:00", - report_title: "产业发展专项基金管理办法", - }, - ], - }, - direction: "positive", - strength: 70, - is_circular: false, - }, - { - id: "2", - name: "半导体行业", - transmission_mechanism: { - data: [ - { - author: "国家发改委", - sentences: - "《国家集成电路产业发展推进纲要》明确提出,到2025年半导体产业自给率要达到70%以上,国家将设立专项基金规模超过3000亿元,重点支持半导体设备、材料、设计等关键环节。同时,通过进口替代战略,加快培育本土产业链", - query_part: "半导体自给率目标70%,专项基金3000亿", - match_score: "好", - declare_date: "2024-02-01T00:00:00", - report_title: "国家集成电路产业发展推进纲要(2024-2030)", - }, - { - author: "工信部", - sentences: - "将重点支持关键核心技术攻关和产业化应用,建立产业发展专项基金,规模达到1000亿元,引导社会资本共同参与产业发展。通过税收优惠、研发补贴等政策工具,为半导体行业创造良好的发展环境", - query_part: "设立1000亿元产业发展专项基金", - match_score: "好", - declare_date: "2024-02-01T00:00:00", - report_title: "产业发展专项基金管理办法", - }, - ], - }, - direction: "positive", - strength: 60, - is_circular: false, - }, - ], - }, - }, - }; - - // 返回对应节点的详情,如果不存在则返回默认数据 - const nodeDetail = nodeDetailsMap[nodeId] || { - success: true, - data: { - node: { - id: nodeId, - name: "未知节点", - type: "other", - description: "该节点暂无详细信息", - importance_score: 0, - total_connections: 0, - incoming_connections: 0, - outgoing_connections: 0, - }, - parents: [], - children: [], - }, - }; - - return HttpResponse.json(nodeDetail); - }), - - // ==================== 投资日历相关 ==================== - - // 获取月度事件统计 - http.get("/api/v1/calendar/event-counts", async ({ request }) => { - await delay(300); - - const url = new URL(request.url); - const year = parseInt(url.searchParams.get("year")); - const month = parseInt(url.searchParams.get("month")); - - console.log("[Mock] 获取月度事件统计:", { year, month }); - - const eventCounts = getMockEventCountsForMonth(year, month); - - return HttpResponse.json({ - success: true, - data: eventCounts, - }); - }), - - // 获取指定日期的事件列表 - http.get("/api/v1/calendar/events", async ({ request }) => { - await delay(300); - - const url = new URL(request.url); - const dateStr = url.searchParams.get("date"); - const type = url.searchParams.get("type") || "all"; - - console.log("[Mock] 获取日历事件列表:", { date: dateStr, type }); - - if (!dateStr) { - return HttpResponse.json( - { - success: false, - error: "Date parameter required", - }, - { status: 400 } - ); - } - - const events = getMockFutureEvents(dateStr, type); - - return HttpResponse.json({ - success: true, - data: events, - }); - }), - - // 切换未来事件关注状态 - http.post("/api/v1/calendar/events/:eventId/follow", async ({ params }) => { - await delay(300); - - const { eventId } = params; - - console.log("[Mock] 切换事件关注状态, eventId:", eventId); - - // 简单返回成功,实际状态管理可以后续完善 - return HttpResponse.json({ - success: true, - data: { - is_following: true, - message: "关注成功", - }, - }); - }), - - // ==================== 历史事件对比相关 ==================== - - // 获取历史事件列表 - http.get("/api/events/:eventId/historical", async ({ params }) => { - await delay(400); - - const { eventId } = params; - - console.log("[Mock] 获取历史事件列表, eventId:", eventId); - - // 生成历史事件数据 - const generateHistoricalEvents = (count = 5) => { - const events = []; - const eventTitles = [ - "芯片产业链政策扶持升级", - "新能源汽车销量创历史新高", - "人工智能大模型技术突破", - "半导体设备国产化加速", - "数字经济政策利好发布", - "新能源产业链整合提速", - "医药创新药获批上市", - "5G应用场景扩展", - "智能驾驶技术迭代升级", - "储能行业景气度上行", - ]; - - const importanceLevels = [1, 2, 3, 4, 5]; - - for (let i = 0; i < count; i++) { - const daysAgo = Math.floor(Math.random() * 180) + 30; // 30-210 天前 - const date = new Date(); - date.setDate(date.getDate() - daysAgo); - - const importance = - importanceLevels[Math.floor(Math.random() * importanceLevels.length)]; - const title = eventTitles[i % eventTitles.length]; - - // 带引用来源的研报数据 - const researchReports = [ - { - author: "中信证券", - report_title: `${title}深度研究报告`, - declare_date: new Date( - date.getTime() - - Math.floor(Math.random() * 10) * 24 * 60 * 60 * 1000 - ).toISOString(), - }, - { - author: "国泰君安", - report_title: `行业专题:${title}影响分析`, - declare_date: new Date( - date.getTime() - - Math.floor(Math.random() * 15) * 24 * 60 * 60 * 1000 - ).toISOString(), - }, - { - author: "华泰证券", - report_title: `${title}投资机会深度解析`, - declare_date: new Date( - date.getTime() - - Math.floor(Math.random() * 20) * 24 * 60 * 60 * 1000 - ).toISOString(), - }, - ]; - - // 生成带引用标记的content(data结构) - const contentWithCitations = { - data: [ - { - query_part: `${title}的详细描述。该事件对相关产业链产生重要影响【1】,市场关注度高,相关概念股表现活跃。`, - sentences: `根据券商研报分析,${title}将推动相关产业链快速发展【2】。预计未来${Math.floor( - Math.random() * 2 + 1 - )}年内,相关企业营收增速有望达到${Math.floor( - Math.random() * 30 + 20 - )}%以上【3】。该事件的影响范围广泛,涉及多个细分领域,投资机会显著。`, - match_score: - importance >= 4 ? "好" : importance >= 2 ? "中" : "一般", - author: researchReports[0].author, - declare_date: researchReports[0].declare_date, - report_title: researchReports[0].report_title, - }, - { - query_part: `市场分析师认为,该事件将带动产业链上下游企业协同发展【2】,形成良性循环。`, - sentences: `从产业趋势来看,相关板块估值仍处于合理区间,具备较高的安全边际。机构投资者持续加仓相关标的,显示出对长期发展前景的看好。`, - match_score: importance >= 3 ? "好" : "中", - author: researchReports[1].author, - declare_date: researchReports[1].declare_date, - report_title: researchReports[1].report_title, - }, - { - query_part: `根据行业数据显示,受此事件影响,相关企业订单量同比增长${Math.floor( - Math.random() * 40 + 30 - )}%【3】。`, - sentences: `行业景气度持续提升,龙头企业凭借技术优势和规模效应,市场份额有望进一步扩大。建议关注产业链核心环节的投资机会。`, - match_score: "好", - author: researchReports[2].author, - declare_date: researchReports[2].declare_date, - report_title: researchReports[2].report_title, - }, - ], - }; - - events.push({ - id: `hist_event_${i + 1}`, - title: title, - content: contentWithCitations, // 升级版本:带引用来源的data结构 - description: `${title}的详细描述。该事件对相关产业链产生重要影响,市场关注度高,相关概念股表现活跃。`, // 降级兼容 - date: date.toISOString().split("T")[0], - importance: importance, - similarity: Math.floor(Math.random() * 10) + 1, // 1-10 - impact_sectors: [ - ["半导体", "芯片设计", "EDA"], - ["新能源汽车", "锂电池", "充电桩"], - ["人工智能", "算力", "大模型"], - ["半导体设备", "国产替代", "集成电路"], - ["数字经济", "云计算", "大数据"], - ][i % 5], - affected_stocks_count: Math.floor(Math.random() * 30) + 10, // 10-40 只股票 - avg_change_pct: parseFloat((Math.random() * 10 - 2).toFixed(2)), // -2% to +8% - created_at: date.toISOString(), - }); - } - - // 按日期降序排序 - return events.sort((a, b) => new Date(b.date) - new Date(a.date)); - }; - - try { - const historicalEvents = generateHistoricalEvents(5); - - return HttpResponse.json({ - success: true, - data: historicalEvents, - total: historicalEvents.length, - message: "获取历史事件列表成功", - }); - } catch (error) { - console.error("[Mock] 获取历史事件列表失败:", error); - return HttpResponse.json( - { - success: false, - error: "获取历史事件列表失败", - data: [], - }, - { status: 500 } - ); - } - }), - - // 获取历史事件相关股票 - http.get("/api/historical-events/:eventId/stocks", async ({ params }) => { - await delay(500); - - const { eventId } = params; - - console.log("[Mock] 获取历史事件相关股票, eventId:", eventId); - - // 生成历史事件相关股票数据 - const generateHistoricalEventStocks = (count = 10) => { - const stocks = []; - const sectors = [ - "半导体", - "新能源", - "医药", - "消费电子", - "人工智能", - "5G通信", - ]; - const stockNames = [ - "中芯国际", - "长江存储", - "华为海思", - "紫光国微", - "兆易创新", - "宁德时代", - "比亚迪", - "隆基绿能", - "阳光电源", - "亿纬锂能", - "恒瑞医药", - "迈瑞医疗", - "药明康德", - "泰格医药", - "康龙化成", - "立讯精密", - "歌尔声学", - "京东方A", - "TCL科技", - "海康威视", - "科大讯飞", - "商汤科技", - "寒武纪", - "海光信息", - "中兴通讯", - ]; - - for (let i = 0; i < count; i++) { - const stockCode = `${Math.random() > 0.5 ? "6" : "0"}${String( - Math.floor(Math.random() * 100000) - ).padStart(5, "0")}`; - const changePct = (Math.random() * 15 - 3).toFixed(2); // -3% ~ +12% - const correlation = (Math.random() * 0.4 + 0.6).toFixed(2); // 0.6 ~ 1.0 - - stocks.push({ - id: `stock_${i}`, - stock_code: `${stockCode}.${Math.random() > 0.5 ? "SH" : "SZ"}`, - stock_name: stockNames[i % stockNames.length], - sector: sectors[Math.floor(Math.random() * sectors.length)], - correlation: parseFloat(correlation), - event_day_change_pct: parseFloat(changePct), - relation_desc: { - data: [ - { - query_part: `该公司是${ - sectors[Math.floor(Math.random() * sectors.length)] - }行业龙头,受事件影响显著,市场关注度高,订单量同比增长${Math.floor( - Math.random() * 50 + 20 - )}%`, - sentences: `根据行业研究报告,该公司在${ - sectors[Math.floor(Math.random() * sectors.length)] - }领域具有核心技术优势,产能利用率达到${Math.floor( - Math.random() * 20 + 80 - )}%,随着事件的深入发展,公司业绩有望持续受益。机构预测未来三年复合增长率将达到${Math.floor( - Math.random() * 30 + 15 - )}%以上`, - match_score: - correlation > 0.8 ? "好" : correlation > 0.6 ? "中" : "一般", - author: ["中信证券", "国泰君安", "华泰证券", "招商证券"][ - Math.floor(Math.random() * 4) - ], - declare_date: new Date( - Date.now() - - Math.floor(Math.random() * 90) * 24 * 60 * 60 * 1000 - ).toISOString(), - report_title: `${ - stockNames[i % stockNames.length] - }深度研究报告`, - }, - ], - }, - }); - } - - // 按相关度降序排序 - return stocks.sort((a, b) => b.correlation - a.correlation); - }; - - try { - const stocks = generateHistoricalEventStocks(15); - - return HttpResponse.json({ - success: true, - data: stocks, - message: "获取历史事件相关股票成功", - }); - } catch (error) { - console.error("[Mock] 获取历史事件相关股票失败:", error); - return HttpResponse.json( - { - success: false, - error: "获取历史事件相关股票失败", - data: [], - }, - { status: 500 } - ); - } - }), - - // ==================== 评论相关 ==================== - - // 获取事件评论列表 - http.get("/api/events/:eventId/posts", async ({ params, request }) => { - await delay(300); - - const { eventId } = params; - const url = new URL(request.url); - const sort = url.searchParams.get("sort") || "latest"; - const page = parseInt(url.searchParams.get("page") || "1"); - const perPage = parseInt(url.searchParams.get("per_page") || "20"); - - console.log("[Mock] 获取评论列表, eventId:", eventId, "sort:", sort); - - try { - // 从内存存储获取评论列表 - const allComments = getOrInitComments(eventId); - - // ✅ 创建副本并排序(避免直接修改原数组) - let sortedComments = [...allComments]; - if (sort === "hot") { - sortedComments.sort((a, b) => b.likes_count - a.likes_count); - } else { - // 默认按时间升序(oldest first)- 最旧评论在前,最新在后 - sortedComments.sort( - (a, b) => new Date(a.created_at) - new Date(b.created_at) - ); - } - - // 分页处理(使用排序后的副本) - const startIndex = (page - 1) * perPage; - const endIndex = startIndex + perPage; - const paginatedComments = sortedComments.slice(startIndex, endIndex); - - return HttpResponse.json({ - success: true, - data: paginatedComments, - pagination: { - page: page, - per_page: perPage, - total: allComments.length, - pages: Math.ceil(allComments.length / perPage), - has_prev: page > 1, - has_next: endIndex < allComments.length, - }, - message: "获取评论成功", - }); - } catch (error) { - console.error("[Mock] 获取评论列表失败:", error); - return HttpResponse.json( - { - success: false, - error: "获取评论失败", - data: [], - }, - { status: 500 } - ); - } - }), - - // 发表评论 - http.post("/api/events/:eventId/posts", async ({ params, request }) => { - await delay(500); - - const { eventId } = params; - const body = await request.json(); - - console.log("[Mock] 发表评论, eventId:", eventId, "content:", body.content); - - try { - // 获取当前登录用户信息 - const currentUser = getCurrentUser(); - - // 模拟创建新评论 - const newComment = { - id: `comment_${eventId}_${Date.now()}`, - content: body.content, - content_type: body.content_type || "text", - author: { - id: currentUser?.id || "current_user", - // 与导航区保持一致:优先显示昵称 - username: - currentUser?.nickname || - currentUser?.username || - currentUser?.email || - "当前用户", - avatar: currentUser?.avatar_url || null, - }, - created_at: new Date().toISOString(), - likes_count: 0, - is_liked: false, - }; - - // 将新评论添加到内存存储(插入到列表开头) - const comments = getOrInitComments(eventId); - comments.unshift(newComment); - - console.log( - "[Mock] 评论已添加到内存存储, 当前评论总数:", - comments.length - ); - - return HttpResponse.json({ - success: true, - data: newComment, - message: "评论发布成功", - }); - } catch (error) { - console.error("[Mock] 发表评论失败:", error); - return HttpResponse.json( - { - success: false, - error: "评论发布失败", - message: "系统错误,请稍后重试", - }, - { status: 500 } - ); - } - }), - - // 删除帖子/评论 - http.delete("/api/posts/:postId", async ({ params }) => { - await delay(300); - const { postId } = params; - - console.log("[Mock] 删除帖子, postId:", postId); - - try { - // 从内存存储中删除评论 - let deleted = false; - for (const [eventId, comments] of commentsStore.entries()) { - const index = comments.findIndex( - (c) => String(c.id) === String(postId) - ); - if (index !== -1) { - comments.splice(index, 1); - deleted = true; - console.log("[Mock] 评论已从事件", eventId, "中删除"); - break; - } - } - - if (!deleted) { - console.log("[Mock] 未找到评论,但仍返回成功(可能是乐观更新的评论)"); - } - - return HttpResponse.json({ - success: true, - message: "删除成功", - }); - } catch (error) { - console.error("[Mock] 删除帖子失败:", error); - return HttpResponse.json( - { - success: false, - error: "删除失败", - message: "系统错误,请稍后重试", - }, - { status: 500 } - ); - } - }), - - // ==================== 日历综合数据(一次性获取所有数据)==================== - - // 获取日历综合数据(涨停 + 事件 + 上证涨跌幅) - http.get("/api/v1/calendar/combined-data", async ({ request }) => { - await delay(400); - - const url = new URL(request.url); - const year = - parseInt(url.searchParams.get("year")) || new Date().getFullYear(); - const month = - parseInt(url.searchParams.get("month")) || new Date().getMonth() + 1; - - console.log("[Mock] 获取日历综合数据:", { year, month }); - - try { - const data = []; - const today = new Date(); - const daysInMonth = new Date(year, month, 0).getDate(); - - // 热门概念列表 - const hotConcepts = [ - "人工智能", - "华为鸿蒙", - "机器人", - "芯片", - "算力", - "新能源", - "固态电池", - "量子计算", - "低空经济", - "智能驾驶", - "光伏", - "储能", - ]; - - // 预生成概念连续段(用于测试跨天连接效果) - // 每段 2-4 天使用相同概念 - let currentConcept = hotConcepts[0]; - let conceptDaysLeft = 0; - let conceptIndex = 0; - - for (let day = 1; day <= daysInMonth; day++) { - const date = new Date(year, month - 1, day); - const dayOfWeek = date.getDay(); - - // 跳过周末 - if (dayOfWeek === 0 || dayOfWeek === 6) continue; - - const dateStr = `${year}${String(month).padStart(2, "0")}${String( - day - ).padStart(2, "0")}`; - const isPast = date < today; - const isToday = date.toDateString() === today.toDateString(); - const isFuture = date > today; - - // 使用日期作为种子生成一致的随机数 - const dateSeed = year * 10000 + month * 100 + day; - const seededRandom = (seed) => { - const x = Math.sin(seed) * 10000; - return x - Math.floor(x); - }; - - // 概念连续段逻辑:每段 2-4 天使用相同概念 - if (conceptDaysLeft <= 0) { - conceptIndex = Math.floor( - seededRandom(dateSeed + 100) * hotConcepts.length - ); - currentConcept = hotConcepts[conceptIndex]; - conceptDaysLeft = Math.floor(seededRandom(dateSeed + 101) * 3) + 2; // 2-4 天 - } - conceptDaysLeft--; - - const item = { - date: dateStr, - zt_count: 0, - top_sector: "", - event_count: 0, - index_change: null, - }; - - // 历史数据:涨停 + 上证涨跌幅 - if (isPast || isToday) { - item.zt_count = Math.floor(seededRandom(dateSeed) * 80 + 30); // 30-110 - item.top_sector = currentConcept; // 使用连续概念 - item.index_change = parseFloat( - (seededRandom(dateSeed + 2) * 4 - 2).toFixed(2) - ); // -2% ~ +2% - } - - // 未来数据:事件数量 - if (isFuture) { - const hasEvents = seededRandom(dateSeed + 3) > 0.4; // 60% 概率有事件 - if (hasEvents) { - item.event_count = Math.floor(seededRandom(dateSeed + 4) * 10 + 1); // 1-10 - } - } - - // 今天:同时有涨停和事件 - if (isToday) { - item.event_count = Math.floor(seededRandom(dateSeed + 5) * 8 + 2); // 2-10 - } - - data.push(item); - } - - return HttpResponse.json({ - success: true, - data: data, - year: year, - month: month, - }); - } catch (error) { - console.error("[Mock] 获取日历综合数据失败:", error); - return HttpResponse.json( - { - success: false, - error: "获取日历综合数据失败", - data: [], - }, - { status: 500 } - ); - } - }), - - // ==================== 事件有效性统计 ==================== - - // 获取事件有效性统计数据 - http.get("/api/v1/events/effectiveness-stats", async ({ request }) => { - await delay(300); - - const url = new URL(request.url); - const days = parseInt(url.searchParams.get("days") || "30"); - - console.log("[Mock] 获取事件有效性统计:", { days }); - - try { - // 模拟市场涨跌分布数据 - const totalCount = 5200; - const risingCount = Math.floor(Math.random() * 1500) + 1800; // 1800-3300 - const fallingCount = Math.floor(Math.random() * 1500) + 1200; // 1200-2700 - const flatCount = totalCount - risingCount - fallingCount; - const marketStats = { - totalCount, - risingCount, - fallingCount, - flatCount: Math.max(0, flatCount), - risingRate: parseFloat(((risingCount / totalCount) * 100).toFixed(1)), - }; - - // 模拟统计数据 - const totalEvents = Math.floor(Math.random() * 50) + 30; - const totalStocks = Math.floor(Math.random() * 300) + 200; - const summary = { - totalEvents, - totalStocks, - avgChg: parseFloat((Math.random() * 6 - 2).toFixed(2)), // -2% ~ 4% - maxChg: parseFloat((Math.random() * 10 + 2).toFixed(2)), // 2% ~ 12% - positiveRate: parseFloat((Math.random() * 30 + 50).toFixed(1)), // 50% ~ 80% - }; - - // 模拟表现最佳的事件 TOP10 - const topPerformers = []; - const eventTitles = [ - "重大政策利好:半导体产业扶持政策出台", - "人工智能板块迎来突破性进展", - "新能源汽车销量创历史新高", - "消费复苏数据超预期", - "央行降准释放流动性", - "科技股龙头财报超预期", - "军工板块获重大订单", - "医药创新药获批上市", - "光伏产业链订单大增", - "锂电池技术突破", - "芯片国产化加速", - "机器人概念持续活跃", - ]; - - for (let i = 0; i < 12; i++) { - topPerformers.push({ - id: i + 1, - title: eventTitles[i % eventTitles.length], - importance: ["S", "A", "B"][Math.floor(Math.random() * 3)], - created_at: new Date( - Date.now() - Math.random() * days * 24 * 60 * 60 * 1000 - ).toISOString(), - avgChg: parseFloat((Math.random() * 8 + 1).toFixed(2)), - maxChg: parseFloat((Math.random() * 15 + 5).toFixed(2)), - stockCount: Math.floor(Math.random() * 20) + 5, - }); - } - - // 按 avgChg 降序排列 - topPerformers.sort((a, b) => b.avgChg - a.avgChg); - - // 模拟股票 TOP10 - const topStocks = []; - const stockNames = [ - { code: "300750.SZ", name: "宁德时代" }, - { code: "002594.SZ", name: "比亚迪" }, - { code: "601012.SH", name: "隆基绿能" }, - { code: "300274.SZ", name: "阳光电源" }, - { code: "002475.SZ", name: "立讯精密" }, - { code: "300059.SZ", name: "东方财富" }, - { code: "600519.SH", name: "贵州茅台" }, - { code: "000858.SZ", name: "五粮液" }, - { code: "601318.SH", name: "中国平安" }, - { code: "600036.SH", name: "招商银行" }, - { code: "002371.SZ", name: "北方华创" }, - { code: "688981.SH", name: "中芯国际" }, - ]; - - for (let i = 0; i < 12; i++) { - const stock = stockNames[i]; - topStocks.push({ - stockCode: stock.code, - stockName: stock.name, - maxChg: parseFloat((Math.random() * 12 + 3).toFixed(2)), - eventCount: Math.floor(Math.random() * 5) + 1, - }); - } - - // 按 maxChg 降序排列 - topStocks.sort((a, b) => b.maxChg - a.maxChg); - - return HttpResponse.json({ - code: 200, - data: { - summary, - marketStats, - topPerformers, - topStocks, - }, - message: "获取成功", - }); - } catch (error) { - console.error("[Mock] 获取事件有效性统计失败:", error); - return HttpResponse.json( - { - code: 500, - error: "获取事件有效性统计失败", - data: null, - }, - { status: 500 } - ); - } - }), - - // ==================== 涨停题材散点图数据 ==================== - - // 获取涨停题材散点图数据 - http.get("/api/v1/zt/theme-scatter", async ({ request }) => { - await delay(400); - - const url = new URL(request.url); - const days = parseInt(url.searchParams.get("days") || "5"); - const date = url.searchParams.get("date"); - - console.log("[Mock] 获取涨停题材散点图:", { days, date }); - - try { - // 生成可用日期列表(最近5个交易日) - const availableDates = []; - const today = new Date(); - let tradingDays = 0; - let checkDate = new Date(today); - - while (tradingDays < days) { - const dayOfWeek = checkDate.getDay(); - if (dayOfWeek !== 0 && dayOfWeek !== 6) { - const dateStr = checkDate.toISOString().split("T")[0]; - availableDates.push({ - date: dateStr, - formatted: `${checkDate.getMonth() + 1}月${checkDate.getDate()}日`, - }); - tradingDays++; - } - checkDate.setDate(checkDate.getDate() - 1); - } - - // 使用日期作为随机种子 - const targetDate = date || availableDates[0]?.date; - const dateSeed = targetDate - ? parseInt(targetDate.replace(/-/g, "")) - : Date.now(); - const seededRandom = (offset = 0) => { - const x = Math.sin(dateSeed + offset) * 10000; - return x - Math.floor(x); - }; - - // 题材数据模板 - const themeTemplates = [ - { label: "人工智能", baseX: 5, baseY: 15, status: "rising" }, - { label: "机器人", baseX: 4, baseY: 12, status: "rising" }, - { label: "半导体", baseX: 3, baseY: 10, status: "clustering" }, - { label: "光模块", baseX: 6, baseY: 8, status: "rising" }, - { label: "算力", baseX: 4, baseY: 9, status: "clustering" }, - { label: "新能源汽车", baseX: 2, baseY: 7, status: "lurking" }, - { label: "固态电池", baseX: 3, baseY: 6, status: "lurking" }, - { label: "光伏", baseX: 2, baseY: 5, status: "declining" }, - { label: "储能", baseX: 2, baseY: 4, status: "declining" }, - { label: "医药", baseX: 1, baseY: 3, status: "lurking" }, - ]; - - // 状态颜色映射 - const statusColors = { - rising: "#FF4D4F", - declining: "#52C41A", - lurking: "#1890FF", - clustering: "#722ED1", - }; - - // 生成题材数据 - const themes = themeTemplates.map((template, index) => { - const xVariation = seededRandom(index * 10) * 2 - 1; // -1 ~ 1 - const yVariation = seededRandom(index * 10 + 1) * 4 - 2; // -2 ~ 2 - - return { - label: template.label, - x: Math.max(1, Math.round(template.baseX + xVariation)), // 辨识度(最高板高度) - y: Math.max(1, Math.round(template.baseY + yVariation)), // 热度(涨停家数) - status: template.status, - color: statusColors[template.status], - countTrend: parseFloat( - (seededRandom(index * 10 + 2) * 40 - 20).toFixed(1) - ), // -20% ~ 20% - boardTrend: parseFloat( - (seededRandom(index * 10 + 3) * 2 - 1).toFixed(1) - ), // -1 ~ 1 - history: [], // 历史数据(简化版不填充) - }; - }); - - // 计算统计数据 - const totalLimitUp = themes.reduce((sum, t) => sum + t.y, 0); - const totalEvents = themes.length; - const indexChange = parseFloat((seededRandom(999) * 4 - 2).toFixed(2)); // 上证涨跌 -2% ~ 2% - - return HttpResponse.json({ - success: true, - data: { - themes, - availableDates, - currentDate: targetDate, - totalLimitUp, - totalEvents, - indexChange, - }, - message: "获取成功", - }); - } catch (error) { - console.error("[Mock] 获取涨停题材散点图失败:", error); - return HttpResponse.json( - { - success: false, - error: "获取涨停题材散点图失败", - data: null, - }, - { status: 500 } - ); - } - }), -]; diff --git a/src/mocks/handlers/external.js b/src/mocks/handlers/external.js deleted file mode 100644 index 6b68fd8e..00000000 --- a/src/mocks/handlers/external.js +++ /dev/null @@ -1,25 +0,0 @@ -// src/mocks/handlers/external.js -// 外部服务 Mock Handler(允许通过) - -import { http, passthrough } from 'msw'; - -/** - * 外部服务处理器 - * 对于外部服务(如头像、CDN等),使用 passthrough 让请求正常发送到真实服务器 - */ -export const externalHandlers = [ - // Pravatar 头像服务 - 允许通过到真实服务 - http.get('https://i.pravatar.cc/*', async () => { - return passthrough(); - }), - - // 如果需要 mock 头像,也可以返回一个占位图片 - // http.get('https://i.pravatar.cc/*', async () => { - // return HttpResponse.text( - // 'Avatar', - // { - // headers: { 'Content-Type': 'image/svg+xml' } - // } - // ); - // }), -]; diff --git a/src/mocks/handlers/financial.js b/src/mocks/handlers/financial.js deleted file mode 100644 index 2d2a28f8..00000000 --- a/src/mocks/handlers/financial.js +++ /dev/null @@ -1,121 +0,0 @@ -// src/mocks/handlers/financial.js -// 财务数据相关的 Mock Handlers - -import { http, HttpResponse } from 'msw'; -import { generateFinancialData } from '../data/financial'; - -// 模拟延迟 -const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms)); - -export const financialHandlers = [ - // 1. 股票基本信息 - http.get('/api/financial/stock-info/:stockCode', async ({ params }) => { - await delay(150); - const { stockCode } = params; - const data = generateFinancialData(stockCode); - return HttpResponse.json({ - success: true, - data: data.stockInfo - }); - }), - - // 2. 资产负债表 - http.get('/api/financial/balance-sheet/:stockCode', async ({ params, request }) => { - await delay(250); - const { stockCode } = params; - const url = new URL(request.url); - const limit = parseInt(url.searchParams.get('limit') || '4', 10); - - const data = generateFinancialData(stockCode); - return HttpResponse.json({ - success: true, - data: data.balanceSheet.slice(0, limit) - }); - }), - - // 3. 利润表 - http.get('/api/financial/income-statement/:stockCode', async ({ params, request }) => { - await delay(250); - const { stockCode } = params; - const url = new URL(request.url); - const limit = parseInt(url.searchParams.get('limit') || '4', 10); - - const data = generateFinancialData(stockCode); - return HttpResponse.json({ - success: true, - data: data.incomeStatement.slice(0, limit) - }); - }), - - // 4. 现金流量表 - http.get('/api/financial/cashflow/:stockCode', async ({ params, request }) => { - await delay(250); - const { stockCode } = params; - const url = new URL(request.url); - const limit = parseInt(url.searchParams.get('limit') || '4', 10); - - const data = generateFinancialData(stockCode); - return HttpResponse.json({ - success: true, - data: data.cashflow.slice(0, limit) - }); - }), - - // 5. 财务指标 - http.get('/api/financial/financial-metrics/:stockCode', async ({ params, request }) => { - await delay(250); - const { stockCode } = params; - const url = new URL(request.url); - const limit = parseInt(url.searchParams.get('limit') || '4', 10); - - const data = generateFinancialData(stockCode); - return HttpResponse.json({ - success: true, - data: data.financialMetrics.slice(0, limit) - }); - }), - - // 6. 主营业务 - http.get('/api/financial/main-business/:stockCode', async ({ params }) => { - await delay(200); - const { stockCode } = params; - const data = generateFinancialData(stockCode); - return HttpResponse.json({ - success: true, - data: data.mainBusiness - }); - }), - - // 7. 业绩预告 - http.get('/api/financial/forecast/:stockCode', async ({ params }) => { - await delay(200); - const { stockCode } = params; - const data = generateFinancialData(stockCode); - return HttpResponse.json({ - success: true, - data: data.forecast - }); - }), - - // 8. 行业排名 - http.get('/api/financial/industry-rank/:stockCode', async ({ params }) => { - await delay(250); - const { stockCode } = params; - const data = generateFinancialData(stockCode); - return HttpResponse.json({ - success: true, - data: data.industryRank - }); - }), - - // 9. 期间对比 - http.get('/api/financial/comparison/:stockCode', async ({ params }) => { - await delay(250); - const { stockCode } = params; - const data = generateFinancialData(stockCode); - return HttpResponse.json({ - success: true, - data: data.periodComparison - }); - }), -]; diff --git a/src/mocks/handlers/forum.js b/src/mocks/handlers/forum.js deleted file mode 100644 index 2d8753b6..00000000 --- a/src/mocks/handlers/forum.js +++ /dev/null @@ -1,311 +0,0 @@ -/** - * 价值论坛帖子 Mock Handler - * 模拟 Elasticsearch API 响应 - */ - -import { http, HttpResponse, delay } from "msw"; -import { mockPosts, mockPostComments } from "../data/forum"; - -// 内存中的帖子数据(支持增删改查) -let postsData = [...mockPosts]; -let commentsData = { ...mockPostComments }; - -// ES 基础 URL(开发环境直连) -const ES_BASE_URL = "http://222.128.1.157:19200"; - -export const forumHandlers = [ - // ==================== 帖子搜索 ==================== - // POST /forum_posts/_search - http.post(`${ES_BASE_URL}/forum_posts/_search`, async ({ request }) => { - await delay(200); - - const body = await request.json(); - const { from = 0, size = 20, query, sort } = body; - - let filteredPosts = [...postsData]; - - // 处理查询条件 - if (query?.bool?.must) { - query.bool.must.forEach((condition) => { - // 状态过滤 - if (condition.match?.status) { - filteredPosts = filteredPosts.filter( - (p) => p.status === condition.match.status - ); - } - // 分类过滤 - if (condition.term?.category) { - filteredPosts = filteredPosts.filter( - (p) => p.category === condition.term.category - ); - } - // 标签过滤 - if (condition.terms?.tags) { - filteredPosts = filteredPosts.filter((p) => - p.tags.some((tag) => condition.terms.tags.includes(tag)) - ); - } - // 全文搜索 - if (condition.multi_match) { - const keyword = condition.multi_match.query.toLowerCase(); - filteredPosts = filteredPosts.filter( - (p) => - p.title.toLowerCase().includes(keyword) || - p.content.toLowerCase().includes(keyword) || - p.tags.some((tag) => tag.toLowerCase().includes(keyword)) - ); - } - }); - } - - // 处理排序 - if (sort && sort.length > 0) { - filteredPosts.sort((a, b) => { - for (const sortItem of sort) { - const [field, config] = Object.entries(sortItem)[0]; - const order = config.order || "desc"; - - if (field === "is_pinned") { - if (a.is_pinned !== b.is_pinned) { - return order === "desc" - ? (b.is_pinned ? 1 : 0) - (a.is_pinned ? 1 : 0) - : (a.is_pinned ? 1 : 0) - (b.is_pinned ? 1 : 0); - } - } else if (field === "created_at") { - const dateA = new Date(a.created_at).getTime(); - const dateB = new Date(b.created_at).getTime(); - if (dateA !== dateB) { - return order === "desc" ? dateB - dateA : dateA - dateB; - } - } else if (field === "likes_count" || field === "views_count") { - if (a[field] !== b[field]) { - return order === "desc" - ? b[field] - a[field] - : a[field] - b[field]; - } - } - } - return 0; - }); - } - - // 分页 - const total = filteredPosts.length; - const paginatedPosts = filteredPosts.slice(from, from + size); - - return HttpResponse.json({ - hits: { - total: { value: total }, - hits: paginatedPosts.map((post) => ({ - _id: post.id, - _source: post, - })), - }, - }); - }), - - // ==================== 获取单个帖子 ==================== - // GET /forum_posts/_doc/:postId - http.get(`${ES_BASE_URL}/forum_posts/_doc/:postId`, async ({ params }) => { - await delay(100); - - const { postId } = params; - const post = postsData.find((p) => p.id === postId); - - if (!post) { - return HttpResponse.json({ found: false }, { status: 404 }); - } - - return HttpResponse.json({ - _id: post.id, - _source: post, - found: true, - }); - }), - - // ==================== 创建帖子 ==================== - // POST /forum_posts/_doc/:postId - http.post( - `${ES_BASE_URL}/forum_posts/_doc/:postId`, - async ({ params, request }) => { - await delay(300); - - const { postId } = params; - const postData = await request.json(); - - const newPost = { - ...postData, - id: postId, - }; - - postsData.unshift(newPost); - - return HttpResponse.json({ - _id: postId, - result: "created", - }); - } - ), - - // ==================== 更新帖子(增加浏览量等) ==================== - // POST /forum_posts/_update/:postId - http.post( - `${ES_BASE_URL}/forum_posts/_update/:postId`, - async ({ params, request }) => { - await delay(100); - - const { postId } = params; - const body = await request.json(); - const postIndex = postsData.findIndex((p) => p.id === postId); - - if (postIndex === -1) { - return HttpResponse.json({ error: "Post not found" }, { status: 404 }); - } - - // 处理脚本更新(如增加浏览量) - if (body.script?.source) { - if (body.script.source.includes("views_count")) { - postsData[postIndex].views_count += 1; - } else if (body.script.source.includes("likes_count")) { - postsData[postIndex].likes_count += 1; - } else if (body.script.source.includes("comments_count")) { - postsData[postIndex].comments_count += 1; - } - } - - // 处理文档更新 - if (body.doc) { - postsData[postIndex] = { ...postsData[postIndex], ...body.doc }; - } - - return HttpResponse.json({ - _id: postId, - result: "updated", - }); - } - ), - - // ==================== 评论搜索 ==================== - // POST /forum_comments/_search - http.post(`${ES_BASE_URL}/forum_comments/_search`, async ({ request }) => { - await delay(150); - - const body = await request.json(); - const { from = 0, size = 50, query } = body; - - let postId = null; - - // 提取 post_id - if (query?.bool?.must) { - const postIdCondition = query.bool.must.find((c) => c.term?.post_id); - if (postIdCondition) { - postId = postIdCondition.term.post_id; - } - } - - const comments = postId ? commentsData[postId] || [] : []; - const activeComments = comments.filter((c) => c.status === "active"); - const paginatedComments = activeComments.slice(from, from + size); - - return HttpResponse.json({ - hits: { - total: { value: activeComments.length }, - hits: paginatedComments.map((comment) => ({ - _id: comment.id, - _source: comment, - })), - }, - }); - }), - - // ==================== 创建评论 ==================== - // POST /forum_comments/_doc/:commentId - http.post( - `${ES_BASE_URL}/forum_comments/_doc/:commentId`, - async ({ params, request }) => { - await delay(200); - - const { commentId } = params; - const commentData = await request.json(); - - const newComment = { - ...commentData, - id: commentId, - }; - - const postId = commentData.post_id; - if (!commentsData[postId]) { - commentsData[postId] = []; - } - commentsData[postId].push(newComment); - - return HttpResponse.json({ - _id: commentId, - result: "created", - }); - } - ), - - // ==================== 更新评论(点赞等) ==================== - // POST /forum_comments/_update/:commentId - http.post( - `${ES_BASE_URL}/forum_comments/_update/:commentId`, - async ({ params, request }) => { - await delay(100); - - const { commentId } = params; - const body = await request.json(); - - // 在所有帖子的评论中查找 - for (const postId in commentsData) { - const commentIndex = commentsData[postId].findIndex( - (c) => c.id === commentId - ); - if (commentIndex !== -1) { - if (body.script?.source?.includes("likes_count")) { - commentsData[postId][commentIndex].likes_count += 1; - } - break; - } - } - - return HttpResponse.json({ - _id: commentId, - result: "updated", - }); - } - ), - - // ==================== 事件时间轴搜索 ==================== - // POST /forum_events/_search - http.post(`${ES_BASE_URL}/forum_events/_search`, async () => { - await delay(100); - - // 返回空数组(暂无事件数据) - return HttpResponse.json({ - hits: { - total: { value: 0 }, - hits: [], - }, - }); - }), - - // ==================== 索引创建(初始化时调用) ==================== - // PUT /forum_posts, /forum_comments, /forum_events - http.put(`${ES_BASE_URL}/forum_posts`, async () => { - await delay(100); - return HttpResponse.json({ acknowledged: true }); - }), - - http.put(`${ES_BASE_URL}/forum_comments`, async () => { - await delay(100); - return HttpResponse.json({ acknowledged: true }); - }), - - http.put(`${ES_BASE_URL}/forum_events`, async () => { - await delay(100); - return HttpResponse.json({ acknowledged: true }); - }), -]; - -export default forumHandlers; diff --git a/src/mocks/handlers/index.js b/src/mocks/handlers/index.js deleted file mode 100644 index f075f008..00000000 --- a/src/mocks/handlers/index.js +++ /dev/null @@ -1,48 +0,0 @@ -// src/mocks/handlers/index.js -// 汇总所有 Mock Handlers - -import { authHandlers } from './auth'; -import { accountHandlers } from './account'; -import { simulationHandlers } from './simulation'; -import { eventHandlers } from './event'; -import { paymentHandlers } from './payment'; -import { industryHandlers } from './industry'; -import { conceptHandlers } from './concept'; -import { stockHandlers } from './stock'; -import { companyHandlers } from './company'; -import { marketHandlers } from './market'; -import { financialHandlers } from './financial'; -import { limitAnalyseHandlers } from './limitAnalyse'; -import { posthogHandlers } from './posthog'; -import { externalHandlers } from './external'; -import { agentHandlers } from './agent'; -import { bytedeskHandlers } from './bytedesk'; -import { predictionHandlers } from './prediction'; -import { forumHandlers } from './forum'; -import { invoiceHandlers } from './invoice'; - -// 可以在这里添加更多的 handlers -// import { userHandlers } from './user'; - -export const handlers = [ - ...authHandlers, - ...accountHandlers, - ...simulationHandlers, - ...eventHandlers, - ...paymentHandlers, - ...industryHandlers, - ...conceptHandlers, - ...stockHandlers, - ...companyHandlers, - ...marketHandlers, - ...financialHandlers, - ...limitAnalyseHandlers, - ...posthogHandlers, - ...externalHandlers, - ...agentHandlers, - ...bytedeskHandlers, // ⚡ Bytedesk 客服 Widget passthrough - ...predictionHandlers, // 预测市场 - ...forumHandlers, // 价值论坛帖子 (ES) - ...invoiceHandlers, // 发票管理 - // ...userHandlers, -]; diff --git a/src/mocks/handlers/industry.js b/src/mocks/handlers/industry.js deleted file mode 100644 index 84674ba0..00000000 --- a/src/mocks/handlers/industry.js +++ /dev/null @@ -1,44 +0,0 @@ -// src/mocks/handlers/industry.js -// 行业分类相关的 Mock API Handlers - -import { http, HttpResponse } from 'msw'; -import { industryData } from '../../data/industryData'; - -// 模拟网络延迟 -const delay = (ms = 300) => new Promise(resolve => setTimeout(resolve, ms)); - -export const industryHandlers = [ - // 获取行业分类完整树形结构 - http.get('/api/classifications', async ({ request }) => { - await delay(500); - - const url = new URL(request.url); - const classification = url.searchParams.get('classification'); - - console.log('[Mock] 获取行业分类树形数据(真实数据)', { classification }); - - try { - let data = industryData; - - // 如果指定了分类体系,只返回该体系的数据 - if (classification) { - data = industryData.filter(item => item.value === classification); - } - - return HttpResponse.json({ - success: true, - data: data - }); - } catch (error) { - console.error('[Mock] 获取行业分类失败:', error); - return HttpResponse.json( - { - success: false, - error: '获取行业分类失败', - data: [] - }, - { status: 500 } - ); - } - }) -]; diff --git a/src/mocks/handlers/invoice.js b/src/mocks/handlers/invoice.js deleted file mode 100644 index b633a033..00000000 --- a/src/mocks/handlers/invoice.js +++ /dev/null @@ -1,920 +0,0 @@ -// src/mocks/handlers/invoice.js -import { http, HttpResponse, delay } from 'msw'; -import { getCurrentUser } from '../data/users'; - -// 模拟网络延迟(毫秒) -const NETWORK_DELAY = 500; - -// 模拟发票数据存储 -const mockInvoices = new Map(); -const mockTitleTemplates = new Map(); -let invoiceIdCounter = 1000; -let templateIdCounter = 100; - -// 模拟可开票订单数据 -const mockInvoiceableOrders = [ - { - id: 'ORDER_1001_1703001600000', - orderNo: 'VF20241220001', - planName: 'pro', - billingCycle: 'yearly', - amount: 2699, - paidAt: '2024-12-20T10:00:00Z', - invoiceApplied: false, - }, - { - id: 'ORDER_1002_1703088000000', - orderNo: 'VF20241221001', - planName: 'max', - billingCycle: 'monthly', - amount: 599, - paidAt: '2024-12-21T10:00:00Z', - invoiceApplied: false, - }, -]; - -// 为每个用户生成模拟发票数据 -const initMockInvoices = () => { - // 为用户 ID 1-4 都生成一些发票数据 - const userInvoiceData = [ - // 用户1 (免费用户) - 无发票 - // 用户2 (Pro会员) - 有多张发票 - { - id: 'INV_001', - orderId: 'ORDER_999_1702396800000', - orderNo: 'VF20241213001', - userId: 2, - invoiceType: 'electronic', - titleType: 'company', - title: '北京价值前沿科技有限公司', - taxNumber: '91110108MA01XXXXX', - amount: 2699, - email: 'pro@example.com', - status: 'completed', - invoiceNo: 'E20241213001', - invoiceCode: '011001900111', - invoiceUrl: 'https://example.com/invoices/E20241213001.pdf', - createdAt: '2024-12-13T10:00:00Z', - updatedAt: '2024-12-14T15:30:00Z', - completedAt: '2024-12-14T15:30:00Z', - }, - { - id: 'INV_002', - orderId: 'ORDER_998_1701792000000', - orderNo: 'VF20241206001', - userId: 2, - invoiceType: 'electronic', - titleType: 'personal', - title: '张三', - amount: 599, - email: 'pro@example.com', - status: 'processing', - createdAt: '2024-12-06T10:00:00Z', - updatedAt: '2024-12-06T10:00:00Z', - }, - { - id: 'INV_003', - orderId: 'ORDER_997_1700000000000', - orderNo: 'VF20241115001', - userId: 2, - invoiceType: 'electronic', - titleType: 'personal', - title: '李四', - amount: 299, - email: 'pro@example.com', - status: 'pending', - createdAt: '2024-12-24T10:00:00Z', - updatedAt: '2024-12-24T10:00:00Z', - }, - // 用户3 (Max会员) - 有发票 - { - id: 'INV_004', - orderId: 'ORDER_996_1703000000000', - orderNo: 'VF20241220002', - userId: 3, - invoiceType: 'electronic', - titleType: 'company', - title: '上海科技发展有限公司', - taxNumber: '91310115MA01YYYYY', - amount: 5999, - email: 'max@example.com', - status: 'completed', - invoiceNo: 'E20241220001', - invoiceCode: '011001900222', - invoiceUrl: 'https://example.com/invoices/E20241220001.pdf', - createdAt: '2024-12-20T10:00:00Z', - updatedAt: '2024-12-21T09:00:00Z', - completedAt: '2024-12-21T09:00:00Z', - }, - { - id: 'INV_005', - orderId: 'ORDER_995_1702500000000', - orderNo: 'VF20241214001', - userId: 3, - invoiceType: 'paper', - titleType: 'company', - title: '上海科技发展有限公司', - taxNumber: '91310115MA01YYYYY', - amount: 2699, - email: 'max@example.com', - status: 'processing', - createdAt: '2024-12-14T10:00:00Z', - updatedAt: '2024-12-15T10:00:00Z', - }, - // 用户1 (测试用户) - 也添加一些发票方便测试 - { - id: 'INV_006', - orderId: 'ORDER_994_1703100000000', - orderNo: 'VF20241222001', - userId: 1, - invoiceType: 'electronic', - titleType: 'personal', - title: '测试用户', - amount: 299, - email: 'test@example.com', - status: 'completed', - invoiceNo: 'E20241222001', - invoiceCode: '011001900333', - invoiceUrl: 'https://example.com/invoices/E20241222001.pdf', - createdAt: '2024-12-22T10:00:00Z', - updatedAt: '2024-12-23T10:00:00Z', - completedAt: '2024-12-23T10:00:00Z', - }, - { - id: 'INV_007', - orderId: 'ORDER_993_1703200000000', - orderNo: 'VF20241223001', - userId: 1, - invoiceType: 'electronic', - titleType: 'company', - title: '测试科技有限公司', - taxNumber: '91110108MA01ZZZZZ', - amount: 599, - email: 'test@example.com', - status: 'processing', - createdAt: '2024-12-23T14:00:00Z', - updatedAt: '2024-12-23T14:00:00Z', - }, - { - id: 'INV_008', - orderId: 'ORDER_992_1703250000000', - orderNo: 'VF20241225001', - userId: 1, - invoiceType: 'electronic', - titleType: 'personal', - title: '王五', - amount: 199, - email: 'test@example.com', - status: 'pending', - createdAt: '2024-12-25T10:00:00Z', - updatedAt: '2024-12-25T10:00:00Z', - }, - { - id: 'INV_009', - orderId: 'ORDER_991_1702000000000', - orderNo: 'VF20241208001', - userId: 1, - invoiceType: 'electronic', - titleType: 'personal', - title: '赵六', - amount: 99, - email: 'test@example.com', - status: 'cancelled', - createdAt: '2024-12-08T10:00:00Z', - updatedAt: '2024-12-09T10:00:00Z', - }, - ]; - - userInvoiceData.forEach((invoice) => { - mockInvoices.set(invoice.id, invoice); - }); -}; - -// 初始化模拟抬头模板 - 为每个用户生成 -const initMockTemplates = () => { - const sampleTemplates = [ - // 用户1 (测试用户) 的模板 - { - id: 'TPL_001', - userId: 1, - isDefault: true, - titleType: 'company', - title: '测试科技有限公司', - taxNumber: '91110108MA01ZZZZZ', - companyAddress: '北京市朝阳区建国路1号', - companyPhone: '010-88888888', - bankName: '中国建设银行北京分行', - bankAccount: '1100001234567890123', - createdAt: '2024-01-01T00:00:00Z', - }, - { - id: 'TPL_002', - userId: 1, - isDefault: false, - titleType: 'personal', - title: '测试用户', - createdAt: '2024-06-01T00:00:00Z', - }, - // 用户2 (Pro会员) 的模板 - { - id: 'TPL_003', - userId: 2, - isDefault: true, - titleType: 'company', - title: '北京价值前沿科技有限公司', - taxNumber: '91110108MA01XXXXX', - companyAddress: '北京市海淀区中关村大街1号', - companyPhone: '010-12345678', - bankName: '中国工商银行北京分行', - bankAccount: '0200001234567890123', - createdAt: '2024-01-01T00:00:00Z', - }, - { - id: 'TPL_004', - userId: 2, - isDefault: false, - titleType: 'personal', - title: '张三', - createdAt: '2024-06-01T00:00:00Z', - }, - // 用户3 (Max会员) 的模板 - { - id: 'TPL_005', - userId: 3, - isDefault: true, - titleType: 'company', - title: '上海科技发展有限公司', - taxNumber: '91310115MA01YYYYY', - companyAddress: '上海市浦东新区陆家嘴金融中心', - companyPhone: '021-66666666', - bankName: '中国银行上海分行', - bankAccount: '4400001234567890123', - createdAt: '2024-02-01T00:00:00Z', - }, - ]; - - sampleTemplates.forEach((template) => { - mockTitleTemplates.set(template.id, template); - }); -}; - -// 初始化数据 -initMockInvoices(); -initMockTemplates(); - -export const invoiceHandlers = [ - // ==================== 发票申请管理 ==================== - - // 1. 获取可开票订单列表 - http.get('/api/invoice/available-orders', async () => { - await delay(NETWORK_DELAY); - - const currentUser = getCurrentUser(); - if (!currentUser) { - return HttpResponse.json( - { - code: 401, - message: '未登录', - data: null, - }, - { status: 401 } - ); - } - - // 返回未申请开票的订单 - const availableOrders = mockInvoiceableOrders.filter((order) => !order.invoiceApplied); - - console.log('[Mock] 获取可开票订单:', { count: availableOrders.length }); - - return HttpResponse.json({ - code: 200, - message: 'success', - data: availableOrders, - }); - }), - - // 2. 申请开票 - http.post('/api/invoice/apply', async ({ request }) => { - await delay(NETWORK_DELAY); - - const currentUser = getCurrentUser(); - if (!currentUser) { - return HttpResponse.json( - { - code: 401, - message: '未登录', - data: null, - }, - { status: 401 } - ); - } - - const body = await request.json(); - const { orderId, invoiceType, titleType, title, taxNumber, email, phone, remark } = body; - - console.log('[Mock] 申请开票:', { orderId, invoiceType, titleType, title }); - - // 验证订单 - const order = mockInvoiceableOrders.find((o) => o.id === orderId); - if (!order) { - return HttpResponse.json( - { - code: 404, - message: '订单不存在', - data: null, - }, - { status: 404 } - ); - } - - if (order.invoiceApplied) { - return HttpResponse.json( - { - code: 400, - message: '该订单已申请开票', - data: null, - }, - { status: 400 } - ); - } - - // 企业开票必须有税号 - if (titleType === 'company' && !taxNumber) { - return HttpResponse.json( - { - code: 400, - message: '企业开票必须填写税号', - data: null, - }, - { status: 400 } - ); - } - - // 创建发票申请 - const invoiceId = `INV_${invoiceIdCounter++}`; - const invoice = { - id: invoiceId, - orderId: order.id, - orderNo: order.orderNo, - userId: currentUser.id, - invoiceType, - titleType, - title, - taxNumber: taxNumber || null, - companyAddress: body.companyAddress || null, - companyPhone: body.companyPhone || null, - bankName: body.bankName || null, - bankAccount: body.bankAccount || null, - amount: order.amount, - email, - phone: phone || null, - mailingAddress: body.mailingAddress || null, - recipientName: body.recipientName || null, - recipientPhone: body.recipientPhone || null, - status: 'pending', - remark: remark || null, - createdAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), - }; - - mockInvoices.set(invoiceId, invoice); - order.invoiceApplied = true; - - console.log('[Mock] 发票申请创建成功:', invoice); - - // 模拟3秒后自动变为处理中 - setTimeout(() => { - const existingInvoice = mockInvoices.get(invoiceId); - if (existingInvoice && existingInvoice.status === 'pending') { - existingInvoice.status = 'processing'; - existingInvoice.updatedAt = new Date().toISOString(); - console.log(`[Mock] 发票开始处理: ${invoiceId}`); - } - }, 3000); - - // 模拟10秒后自动开具完成(电子发票) - if (invoiceType === 'electronic') { - setTimeout(() => { - const existingInvoice = mockInvoices.get(invoiceId); - if (existingInvoice && existingInvoice.status === 'processing') { - existingInvoice.status = 'completed'; - existingInvoice.invoiceNo = `E${Date.now()}`; - existingInvoice.invoiceCode = '011001900111'; - existingInvoice.invoiceUrl = `https://example.com/invoices/${existingInvoice.invoiceNo}.pdf`; - existingInvoice.completedAt = new Date().toISOString(); - existingInvoice.updatedAt = new Date().toISOString(); - console.log(`[Mock] 电子发票开具完成: ${invoiceId}`); - } - }, 10000); - } - - return HttpResponse.json({ - code: 200, - message: '开票申请已提交,预计1-3个工作日内处理', - data: invoice, - }); - }), - - // 3. 获取发票列表 - http.get('/api/invoice/list', async ({ request }) => { - await delay(NETWORK_DELAY); - - const currentUser = getCurrentUser(); - if (!currentUser) { - return HttpResponse.json( - { - code: 401, - message: '未登录', - data: null, - }, - { status: 401 } - ); - } - - const url = new URL(request.url); - const page = parseInt(url.searchParams.get('page') || '1', 10); - const pageSize = parseInt(url.searchParams.get('pageSize') || '10', 10); - const statusFilter = url.searchParams.get('status'); - - // 获取用户的发票 - let userInvoices = Array.from(mockInvoices.values()) - .filter((invoice) => invoice.userId === currentUser.id) - .sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt)); - - // 状态筛选 - if (statusFilter) { - userInvoices = userInvoices.filter((invoice) => invoice.status === statusFilter); - } - - // 分页 - const total = userInvoices.length; - const startIndex = (page - 1) * pageSize; - const paginatedInvoices = userInvoices.slice(startIndex, startIndex + pageSize); - - console.log('[Mock] 获取发票列表:', { total, page, pageSize }); - - return HttpResponse.json({ - code: 200, - message: 'success', - data: { - list: paginatedInvoices, - total, - page, - pageSize, - totalPages: Math.ceil(total / pageSize), - }, - }); - }), - - // 4. 获取发票详情 - http.get('/api/invoice/:invoiceId', async ({ params }) => { - await delay(NETWORK_DELAY); - - const currentUser = getCurrentUser(); - if (!currentUser) { - return HttpResponse.json( - { - code: 401, - message: '未登录', - data: null, - }, - { status: 401 } - ); - } - - const { invoiceId } = params; - const invoice = mockInvoices.get(invoiceId); - - if (!invoice) { - return HttpResponse.json( - { - code: 404, - message: '发票不存在', - data: null, - }, - { status: 404 } - ); - } - - if (invoice.userId !== currentUser.id) { - return HttpResponse.json( - { - code: 403, - message: '无权访问此发票', - data: null, - }, - { status: 403 } - ); - } - - console.log('[Mock] 获取发票详情:', { invoiceId }); - - return HttpResponse.json({ - code: 200, - message: 'success', - data: invoice, - }); - }), - - // 5. 取消发票申请 - http.post('/api/invoice/:invoiceId/cancel', async ({ params }) => { - await delay(NETWORK_DELAY); - - const currentUser = getCurrentUser(); - if (!currentUser) { - return HttpResponse.json( - { - code: 401, - message: '未登录', - data: null, - }, - { status: 401 } - ); - } - - const { invoiceId } = params; - const invoice = mockInvoices.get(invoiceId); - - if (!invoice) { - return HttpResponse.json( - { - code: 404, - message: '发票不存在', - data: null, - }, - { status: 404 } - ); - } - - if (invoice.userId !== currentUser.id) { - return HttpResponse.json( - { - code: 403, - message: '无权操作此发票', - data: null, - }, - { status: 403 } - ); - } - - if (invoice.status !== 'pending') { - return HttpResponse.json( - { - code: 400, - message: '只能取消待处理的发票申请', - data: null, - }, - { status: 400 } - ); - } - - invoice.status = 'cancelled'; - invoice.updatedAt = new Date().toISOString(); - - // 恢复订单的开票状态 - const order = mockInvoiceableOrders.find((o) => o.id === invoice.orderId); - if (order) { - order.invoiceApplied = false; - } - - console.log('[Mock] 发票申请已取消:', invoiceId); - - return HttpResponse.json({ - code: 200, - message: '发票申请已取消', - data: null, - }); - }), - - // 6. 下载电子发票 - http.get('/api/invoice/:invoiceId/download', async ({ params }) => { - await delay(NETWORK_DELAY); - - const currentUser = getCurrentUser(); - if (!currentUser) { - return HttpResponse.json( - { - code: 401, - message: '未登录', - data: null, - }, - { status: 401 } - ); - } - - const { invoiceId } = params; - const invoice = mockInvoices.get(invoiceId); - - if (!invoice) { - return HttpResponse.json( - { - code: 404, - message: '发票不存在', - data: null, - }, - { status: 404 } - ); - } - - if (invoice.userId !== currentUser.id) { - return HttpResponse.json( - { - code: 403, - message: '无权下载此发票', - data: null, - }, - { status: 403 } - ); - } - - if (invoice.status !== 'completed') { - return HttpResponse.json( - { - code: 400, - message: '发票尚未开具完成', - data: null, - }, - { status: 400 } - ); - } - - console.log('[Mock] 下载电子发票:', invoiceId); - - // 返回模拟的 PDF 内容 - const pdfContent = `%PDF-1.4 -1 0 obj -<< /Type /Catalog /Pages 2 0 R >> -endobj -2 0 obj -<< /Type /Pages /Kids [3 0 R] /Count 1 >> -endobj -3 0 obj -<< /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792] >> -endobj -trailer -<< /Root 1 0 R >> -%%EOF`; - - return new HttpResponse(pdfContent, { - headers: { - 'Content-Type': 'application/pdf', - 'Content-Disposition': `attachment; filename="invoice_${invoice.invoiceNo}.pdf"`, - }, - }); - }), - - // 7. 获取发票统计 - http.get('/api/invoice/stats', async () => { - await delay(NETWORK_DELAY); - - const currentUser = getCurrentUser(); - if (!currentUser) { - return HttpResponse.json( - { - code: 401, - message: '未登录', - data: null, - }, - { status: 401 } - ); - } - - const userInvoices = Array.from(mockInvoices.values()).filter( - (invoice) => invoice.userId === currentUser.id - ); - - // 计算可开票金额(未申请开票的订单) - const availableOrders = mockInvoiceableOrders.filter((order) => !order.invoiceApplied); - const availableAmount = availableOrders.reduce((sum, order) => sum + order.amount, 0); - - // 计算已开票金额 - const invoicedAmount = userInvoices - .filter((i) => i.status === 'completed') - .reduce((sum, invoice) => sum + invoice.amount, 0); - - // 计算处理中金额 - const processingAmount = userInvoices - .filter((i) => i.status === 'processing' || i.status === 'pending') - .reduce((sum, invoice) => sum + invoice.amount, 0); - - const stats = { - total: userInvoices.length, - pending: userInvoices.filter((i) => i.status === 'pending').length, - processing: userInvoices.filter((i) => i.status === 'processing').length, - completed: userInvoices.filter((i) => i.status === 'completed').length, - cancelled: userInvoices.filter((i) => i.status === 'cancelled').length, - availableAmount, - invoicedAmount, - processingAmount, - }; - - console.log('[Mock] 获取发票统计:', stats); - - return HttpResponse.json({ - code: 200, - message: 'success', - data: stats, - }); - }), - - // ==================== 发票抬头模板管理 ==================== - - // 8. 获取发票抬头模板列表 - http.get('/api/invoice/title-templates', async () => { - await delay(NETWORK_DELAY); - - const currentUser = getCurrentUser(); - if (!currentUser) { - return HttpResponse.json( - { - code: 401, - message: '未登录', - data: null, - }, - { status: 401 } - ); - } - - const userTemplates = Array.from(mockTitleTemplates.values()) - .filter((template) => template.userId === currentUser.id) - .sort((a, b) => { - // 默认的排在前面 - if (a.isDefault !== b.isDefault) { - return b.isDefault ? 1 : -1; - } - return new Date(b.createdAt) - new Date(a.createdAt); - }); - - console.log('[Mock] 获取抬头模板列表:', { count: userTemplates.length }); - - return HttpResponse.json({ - code: 200, - message: 'success', - data: userTemplates, - }); - }), - - // 9. 保存发票抬头模板 - http.post('/api/invoice/title-template', async ({ request }) => { - await delay(NETWORK_DELAY); - - const currentUser = getCurrentUser(); - if (!currentUser) { - return HttpResponse.json( - { - code: 401, - message: '未登录', - data: null, - }, - { status: 401 } - ); - } - - const body = await request.json(); - - const templateId = `TPL_${templateIdCounter++}`; - const template = { - id: templateId, - userId: currentUser.id, - isDefault: body.isDefault || false, - titleType: body.titleType, - title: body.title, - taxNumber: body.taxNumber || null, - companyAddress: body.companyAddress || null, - companyPhone: body.companyPhone || null, - bankName: body.bankName || null, - bankAccount: body.bankAccount || null, - createdAt: new Date().toISOString(), - }; - - // 如果设为默认,取消其他模板的默认状态 - if (template.isDefault) { - mockTitleTemplates.forEach((t) => { - if (t.userId === currentUser.id) { - t.isDefault = false; - } - }); - } - - mockTitleTemplates.set(templateId, template); - - console.log('[Mock] 保存抬头模板:', template); - - return HttpResponse.json({ - code: 200, - message: '保存成功', - data: template, - }); - }), - - // 10. 删除发票抬头模板 - http.delete('/api/invoice/title-template/:templateId', async ({ params }) => { - await delay(NETWORK_DELAY); - - const currentUser = getCurrentUser(); - if (!currentUser) { - return HttpResponse.json( - { - code: 401, - message: '未登录', - data: null, - }, - { status: 401 } - ); - } - - const { templateId } = params; - const template = mockTitleTemplates.get(templateId); - - if (!template) { - return HttpResponse.json( - { - code: 404, - message: '模板不存在', - data: null, - }, - { status: 404 } - ); - } - - if (template.userId !== currentUser.id) { - return HttpResponse.json( - { - code: 403, - message: '无权删除此模板', - data: null, - }, - { status: 403 } - ); - } - - mockTitleTemplates.delete(templateId); - - console.log('[Mock] 删除抬头模板:', templateId); - - return HttpResponse.json({ - code: 200, - message: '删除成功', - data: null, - }); - }), - - // 11. 设置默认发票抬头 - http.post('/api/invoice/title-template/:templateId/default', async ({ params }) => { - await delay(NETWORK_DELAY); - - const currentUser = getCurrentUser(); - if (!currentUser) { - return HttpResponse.json( - { - code: 401, - message: '未登录', - data: null, - }, - { status: 401 } - ); - } - - const { templateId } = params; - const template = mockTitleTemplates.get(templateId); - - if (!template) { - return HttpResponse.json( - { - code: 404, - message: '模板不存在', - data: null, - }, - { status: 404 } - ); - } - - if (template.userId !== currentUser.id) { - return HttpResponse.json( - { - code: 403, - message: '无权操作此模板', - data: null, - }, - { status: 403 } - ); - } - - // 取消其他模板的默认状态 - mockTitleTemplates.forEach((t) => { - if (t.userId === currentUser.id) { - t.isDefault = false; - } - }); - - template.isDefault = true; - - console.log('[Mock] 设置默认抬头:', templateId); - - return HttpResponse.json({ - code: 200, - message: '设置成功', - data: null, - }); - }), -]; diff --git a/src/mocks/handlers/limitAnalyse.js b/src/mocks/handlers/limitAnalyse.js deleted file mode 100644 index 2921c387..00000000 --- a/src/mocks/handlers/limitAnalyse.js +++ /dev/null @@ -1,607 +0,0 @@ -// src/mocks/handlers/limitAnalyse.js -// 涨停分析相关的 Mock Handlers - -import { http, HttpResponse } from 'msw'; - -// 模拟延迟 -const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms)); - -// 生成可用日期列表(最近30个交易日) -const generateAvailableDates = () => { - const dates = []; - const today = new Date(); - let count = 0; - - for (let i = 0; i < 60 && count < 30; i++) { - const date = new Date(today); - date.setDate(date.getDate() - i); - const dayOfWeek = date.getDay(); - - // 跳过周末 - if (dayOfWeek !== 0 && dayOfWeek !== 6) { - const year = date.getFullYear(); - const month = String(date.getMonth() + 1).padStart(2, '0'); - const day = String(date.getDate()).padStart(2, '0'); - const dateStr = `${year}${month}${day}`; - - // 返回包含 date 和 count 字段的对象 - dates.push({ - date: dateStr, - count: Math.floor(Math.random() * 80) + 30 // 30-110 只涨停股票 - }); - count++; - } - } - - return dates; -}; - -// 生成板块数据 -const generateSectors = (count = 8) => { - const sectorNames = [ - '人工智能', 'ChatGPT', '数字经济', - '新能源汽车', '光伏', '锂电池', - '半导体', '芯片', '5G通信', - '医疗器械', '创新药', '中药', - '白酒', '食品饮料', '消费电子', - '军工', '航空航天', '新材料' - ]; - - const sectors = []; - for (let i = 0; i < Math.min(count, sectorNames.length); i++) { - const stockCount = Math.floor(Math.random() * 15) + 5; - const stocks = []; - - for (let j = 0; j < stockCount; j++) { - stocks.push({ - code: `${Math.random() > 0.5 ? '6' : '0'}${String(Math.floor(Math.random() * 100000)).padStart(5, '0')}`, - name: `${sectorNames[i]}股票${j + 1}`, - latest_limit_time: `${Math.floor(Math.random() * 4) + 9}:${String(Math.floor(Math.random() * 60)).padStart(2, '0')}:${String(Math.floor(Math.random() * 60)).padStart(2, '0')}`, - limit_up_count: Math.floor(Math.random() * 3) + 1, - price: (Math.random() * 100 + 10).toFixed(2), - change_pct: (Math.random() * 5 + 5).toFixed(2), - turnover_rate: (Math.random() * 30 + 5).toFixed(2), - volume: Math.floor(Math.random() * 100000000 + 10000000), - amount: (Math.random() * 1000000000 + 100000000).toFixed(2), - limit_type: Math.random() > 0.7 ? '一字板' : (Math.random() > 0.5 ? 'T字板' : '普通涨停'), - 封单金额: (Math.random() * 500000000).toFixed(2), - }); - } - - sectors.push({ - sector_name: sectorNames[i], - stock_count: stockCount, - avg_limit_time: `${Math.floor(Math.random() * 2) + 10}:${String(Math.floor(Math.random() * 60)).padStart(2, '0')}`, - stocks: stocks, - }); - } - - return sectors; -}; - -// 生成高位股数据(用于 HighPositionStocks 组件) -const generateHighPositionStocks = () => { - const stocks = []; - const stockNames = [ - '宁德时代', '比亚迪', '隆基绿能', '东方财富', '中际旭创', - '京东方A', '海康威视', '立讯精密', '三一重工', '恒瑞医药', - '三六零', '东方通信', '贵州茅台', '五粮液', '中国平安' - ]; - const industries = [ - '锂电池', '新能源汽车', '光伏', '金融科技', '通信设备', - '显示器件', '安防设备', '电子元件', '工程机械', '医药制造', - '网络安全', '通信服务', '白酒', '食品饮料', '保险' - ]; - - for (let i = 0; i < stockNames.length; i++) { - const code = `${Math.random() > 0.5 ? '6' : '0'}${String(Math.floor(Math.random() * 100000)).padStart(5, '0')}`; - const continuousDays = Math.floor(Math.random() * 8) + 2; // 2-9连板 - const price = parseFloat((Math.random() * 100 + 20).toFixed(2)); - const increaseRate = parseFloat((Math.random() * 3 + 8).toFixed(2)); // 8%-11% - const turnoverRate = parseFloat((Math.random() * 20 + 5).toFixed(2)); // 5%-25% - - stocks.push({ - stock_code: code, - stock_name: stockNames[i], - price: price, - increase_rate: increaseRate, - continuous_limit_up: continuousDays, - industry: industries[i], - turnover_rate: turnoverRate, - }); - } - - // 按连板天数降序排序 - stocks.sort((a, b) => b.continuous_limit_up - a.continuous_limit_up); - - return stocks; -}; - -// 生成高位股统计数据 -const generateHighPositionStatistics = (stocks) => { - if (!stocks || stocks.length === 0) { - return { - total_count: 0, - avg_continuous_days: 0, - max_continuous_days: 0, - }; - } - - const totalCount = stocks.length; - const sumDays = stocks.reduce((sum, stock) => sum + stock.continuous_limit_up, 0); - const maxDays = Math.max(...stocks.map(s => s.continuous_limit_up)); - - return { - total_count: totalCount, - avg_continuous_days: parseFloat((sumDays / totalCount).toFixed(1)), - max_continuous_days: maxDays, - }; -}; - -// 生成词云数据 -const generateWordCloudData = () => { - const keywords = [ - '人工智能', 'ChatGPT', 'AI芯片', '大模型', '算力', - '新能源', '光伏', '锂电池', '储能', '充电桩', - '半导体', '芯片', 'EDA', '国产替代', '集成电路', - '医疗', '创新药', 'CXO', '医疗器械', '生物医药', - '消费', '白酒', '食品', '零售', '餐饮', - '金融', '券商', '保险', '银行', '金融科技' - ]; - - return keywords.map(keyword => ({ - text: keyword, - value: Math.floor(Math.random() * 50) + 10, - category: ['科技', '新能源', '医疗', '消费', '金融'][Math.floor(Math.random() * 5)], - })); -}; - -// 生成每日分析数据 -const generateDailyAnalysis = (date) => { - const sectorNames = [ - '公告', '人工智能', 'ChatGPT', '数字经济', - '新能源汽车', '光伏', '锂电池', - '半导体', '芯片', '5G通信', - '医疗器械', '创新药', '其他' - ]; - - const stockNameTemplates = [ - '龙头', '科技', '新能源', '智能', '数字', '云计算', '创新', - '生物', '医疗', '通信', '电子', '材料', '能源', '互联' - ]; - - // 生成 sector_data(SectorDetails 组件需要的格式) - const sectorData = {}; - let totalStocks = 0; - - sectorNames.forEach((sectorName, sectorIdx) => { - const stockCount = Math.floor(Math.random() * 12) + 3; // 每个板块 3-15 只股票 - const stocks = []; - - for (let i = 0; i < stockCount; i++) { - const code = `${Math.random() > 0.5 ? '6' : '0'}${String(Math.floor(Math.random() * 100000)).padStart(5, '0')}`; - const continuousDays = Math.floor(Math.random() * 6) + 1; // 1-6连板 - const ztHour = Math.floor(Math.random() * 5) + 9; // 9-13点 - const ztMinute = Math.floor(Math.random() * 60); - const ztSecond = Math.floor(Math.random() * 60); - const ztTime = `2024-10-28 ${String(ztHour).padStart(2, '0')}:${String(ztMinute).padStart(2, '0')}:${String(ztSecond).padStart(2, '0')}`; - - const stockName = `${stockNameTemplates[i % stockNameTemplates.length]}${sectorName === '公告' ? '公告' : ''}股份${i + 1}`; - - stocks.push({ - scode: code, - sname: stockName, - zt_time: ztTime, - formatted_time: `${String(ztHour).padStart(2, '0')}:${String(ztMinute).padStart(2, '0')}`, - continuous_days: continuousDays === 1 ? '首板' : `${continuousDays}连板`, - brief: `${sectorName}板块异动,${stockName}因${sectorName === '公告' ? '重大公告利好' : '板块热点'}涨停。公司是${sectorName}行业龙头企业之一。`, - summary: `${sectorName}概念持续活跃`, - first_time: `2024-10-${String(28 - (continuousDays - 1)).padStart(2, '0')}`, - change_pct: parseFloat((Math.random() * 2 + 9).toFixed(2)), // 9%-11% - core_sectors: [ - sectorName, - sectorNames[Math.floor(Math.random() * sectorNames.length)], - sectorNames[Math.floor(Math.random() * sectorNames.length)] - ].filter((v, i, a) => a.indexOf(v) === i) // 去重 - }); - } - - sectorData[sectorName] = { - count: stockCount, - stocks: stocks.sort((a, b) => a.zt_time.localeCompare(b.zt_time)) // 按涨停时间排序 - }; - - totalStocks += stockCount; - }); - - // 统计数据 - const morningCount = Math.floor(totalStocks * 0.35); // 早盘涨停 - const middayCount = Math.floor(totalStocks * 0.25); // 午盘涨停 - const afternoonCount = totalStocks - morningCount - middayCount; // 尾盘涨停 - const announcementCount = sectorData['公告']?.count || 0; - const topSector = sectorNames.filter(s => s !== '公告' && s !== '其他') - .reduce((max, name) => - (sectorData[name]?.count || 0) > (sectorData[max]?.count || 0) ? name : max - , '人工智能'); - - // 生成 chart_data(板块分布饼图需要) - const sortedSectors = Object.entries(sectorData) - .filter(([name]) => name !== '其他' && name !== '公告') - .sort((a, b) => b[1].count - a[1].count) - .slice(0, 10); - - const chartData = { - labels: sortedSectors.map(([name]) => name), - counts: sortedSectors.map(([, info]) => info.count) - }; - - return { - date: date, - total_stocks: totalStocks, - total_sectors: Object.keys(sectorData).length, - sector_data: sectorData, // 👈 SectorDetails 组件需要的数据 - chart_data: chartData, // 👈 板块分布饼图需要的数据 - summary: { - top_sector: topSector, - top_sector_count: sectorData[topSector]?.count || 0, - announcement_stocks: announcementCount, - zt_time_distribution: { - morning: morningCount, - midday: middayCount, - afternoon: afternoonCount, - } - } - }; -}; - -// ==================== 静态文件 Mock Handlers ==================== -// 这些 handlers 用于拦截 /data/zt/* 静态文件请求 - -// 生成 dates.json 数据 -const generateDatesJson = () => { - const dates = []; - const today = new Date(); - - for (let i = 0; i < 60; i++) { - const date = new Date(today); - date.setDate(date.getDate() - i); - const dayOfWeek = date.getDay(); - - // 跳过周末 - if (dayOfWeek !== 0 && dayOfWeek !== 6) { - const year = date.getFullYear(); - const month = String(date.getMonth() + 1).padStart(2, '0'); - const day = String(date.getDate()).padStart(2, '0'); - - dates.push({ - date: `${year}${month}${day}`, - formatted_date: `${year}-${month}-${day}`, - count: Math.floor(Math.random() * 60) + 40 // 40-100 只涨停股票 - }); - - if (dates.length >= 30) break; - } - } - - return { dates }; -}; - -// 生成每日分析 JSON 数据(用于 /data/zt/daily/${date}.json) -const generateDailyJson = (date) => { - // 板块名称列表 - const sectorNames = [ - '公告', '人工智能', 'ChatGPT', '大模型', '算力', - '光伏', '新能源汽车', '锂电池', '储能', '充电桩', - '半导体', '芯片', '集成电路', '国产替代', - '医药', '创新药', 'CXO', '医疗器械', - '军工', '航空航天', '其他' - ]; - - // 股票名称模板 - const stockPrefixes = [ - '龙头', '科技', '新能', '智能', '数字', '云计', '创新', - '生物', '医疗', '通信', '电子', '材料', '能源', '互联', - '天马', '华鑫', '中科', '东方', '西部', '南方', '北方', - '金龙', '银河', '星辰', '宏达', '盛世', '鹏程', '万里' - ]; - - const stockSuffixes = [ - '股份', '科技', '电子', '信息', '新材', '能源', '医药', - '通讯', '智造', '集团', '实业', '控股', '产业', '发展' - ]; - - // 生成所有股票 - const stocks = []; - const sectorData = {}; - let stockIndex = 0; - - sectorNames.forEach((sectorName, sectorIdx) => { - const stockCount = sectorName === '公告' - ? Math.floor(Math.random() * 5) + 8 // 公告板块 8-12 只 - : sectorName === '其他' - ? Math.floor(Math.random() * 4) + 3 // 其他板块 3-6 只 - : Math.floor(Math.random() * 8) + 3; // 普通板块 3-10 只 - - const sectorStockCodes = []; - - for (let i = 0; i < stockCount; i++) { - const code = `${Math.random() > 0.6 ? '6' : Math.random() > 0.3 ? '0' : '3'}${String(Math.floor(Math.random() * 100000)).padStart(5, '0')}`; - const continuousDays = Math.floor(Math.random() * 6) + 1; - const ztHour = Math.floor(Math.random() * 4) + 9; - const ztMinute = Math.floor(Math.random() * 60); - const ztSecond = Math.floor(Math.random() * 60); - - const prefix = stockPrefixes[Math.floor(Math.random() * stockPrefixes.length)]; - const suffix = stockSuffixes[Math.floor(Math.random() * stockSuffixes.length)]; - const stockName = `${prefix}${suffix}`; - - // 生成关联板块 - const coreSectors = [sectorName]; - if (Math.random() > 0.5) { - const otherSector = sectorNames[Math.floor(Math.random() * (sectorNames.length - 1))]; - if (otherSector !== sectorName && otherSector !== '其他' && otherSector !== '公告') { - coreSectors.push(otherSector); - } - } - - stocks.push({ - scode: code, - sname: stockName, - zt_time: `${date.slice(0,4)}-${date.slice(4,6)}-${date.slice(6,8)} ${String(ztHour).padStart(2,'0')}:${String(ztMinute).padStart(2,'0')}:${String(ztSecond).padStart(2,'0')}`, - formatted_time: `${String(ztHour).padStart(2,'0')}:${String(ztMinute).padStart(2,'0')}`, - continuous_days: continuousDays === 1 ? '首板' : `${continuousDays}连板`, - brief: sectorName === '公告' - ? `${stockName}发布重大公告,公司拟收购资产/重组/增发等利好消息。` - : `${sectorName}板块异动,${stockName}因板块热点涨停。公司是${sectorName}行业核心标的。`, - summary: `${sectorName}概念活跃`, - first_time: `${date.slice(0,4)}-${date.slice(4,6)}-${String(parseInt(date.slice(6,8)) - (continuousDays - 1)).padStart(2,'0')}`, - change_pct: parseFloat((Math.random() * 1.5 + 9.5).toFixed(2)), - core_sectors: coreSectors - }); - - sectorStockCodes.push(code); - stockIndex++; - } - - sectorData[sectorName] = { - count: stockCount, - stock_codes: sectorStockCodes - }; - }); - - // 生成词频数据 - const wordFreqData = [ - { name: '人工智能', value: Math.floor(Math.random() * 30) + 20 }, - { name: 'ChatGPT', value: Math.floor(Math.random() * 25) + 15 }, - { name: '大模型', value: Math.floor(Math.random() * 20) + 12 }, - { name: '算力', value: Math.floor(Math.random() * 18) + 10 }, - { name: '光伏', value: Math.floor(Math.random() * 15) + 10 }, - { name: '新能源', value: Math.floor(Math.random() * 15) + 8 }, - { name: '锂电池', value: Math.floor(Math.random() * 12) + 8 }, - { name: '储能', value: Math.floor(Math.random() * 12) + 6 }, - { name: '半导体', value: Math.floor(Math.random() * 15) + 10 }, - { name: '芯片', value: Math.floor(Math.random() * 15) + 8 }, - { name: '集成电路', value: Math.floor(Math.random() * 10) + 5 }, - { name: '国产替代', value: Math.floor(Math.random() * 10) + 5 }, - { name: '医药', value: Math.floor(Math.random() * 12) + 6 }, - { name: '创新药', value: Math.floor(Math.random() * 10) + 5 }, - { name: '医疗器械', value: Math.floor(Math.random() * 8) + 4 }, - { name: '军工', value: Math.floor(Math.random() * 10) + 5 }, - { name: '航空航天', value: Math.floor(Math.random() * 8) + 4 }, - { name: '数字经济', value: Math.floor(Math.random() * 12) + 6 }, - { name: '工业4.0', value: Math.floor(Math.random() * 8) + 4 }, - { name: '机器人', value: Math.floor(Math.random() * 10) + 5 }, - { name: '自动驾驶', value: Math.floor(Math.random() * 8) + 4 }, - { name: '元宇宙', value: Math.floor(Math.random() * 6) + 3 }, - { name: 'Web3.0', value: Math.floor(Math.random() * 5) + 2 }, - { name: '区块链', value: Math.floor(Math.random() * 5) + 2 }, - ]; - - // 生成 chart_data(板块分布饼图需要) - const sortedSectors = Object.entries(sectorData) - .filter(([name]) => name !== '其他' && name !== '公告') - .sort((a, b) => b[1].count - a[1].count) - .slice(0, 10); - - const chartData = { - labels: sortedSectors.map(([name]) => name), - counts: sortedSectors.map(([, info]) => info.count) - }; - - // 时间分布(早盘、午盘、尾盘) - const morningCount = Math.floor(stocks.length * 0.35); - const middayCount = Math.floor(stocks.length * 0.25); - const afternoonCount = stocks.length - morningCount - middayCount; - - return { - date: date, - total_stocks: stocks.length, - total_sectors: Object.keys(sectorData).length, - stocks: stocks, - sector_data: sectorData, - word_freq_data: wordFreqData, - chart_data: chartData, // 👈 板块分布饼图需要的数据 - summary: { - top_sector: '人工智能', - top_sector_count: sectorData['人工智能']?.count || 0, - announcement_stocks: sectorData['公告']?.count || 0, - zt_time_distribution: { - morning: morningCount, // 早盘 9:30-11:30 - midday: middayCount, // 午盘 11:30-13:00 - afternoon: afternoonCount, // 尾盘 13:00-15:00 - } - } - }; -}; - -// 生成 stocks.jsonl 数据 -const generateStocksJsonl = () => { - const stocks = []; - const today = new Date(); - - // 生成 200 只历史涨停股票记录 - for (let i = 0; i < 200; i++) { - const daysAgo = Math.floor(Math.random() * 30); - const date = new Date(today); - date.setDate(date.getDate() - daysAgo); - - // 跳过周末 - while (date.getDay() === 0 || date.getDay() === 6) { - date.setDate(date.getDate() - 1); - } - - const year = date.getFullYear(); - const month = String(date.getMonth() + 1).padStart(2, '0'); - const day = String(date.getDate()).padStart(2, '0'); - - stocks.push({ - scode: `${Math.random() > 0.6 ? '6' : Math.random() > 0.3 ? '0' : '3'}${String(Math.floor(Math.random() * 100000)).padStart(5, '0')}`, - sname: ['龙头', '科技', '新能', '智能', '数字', '云计'][Math.floor(Math.random() * 6)] + - ['股份', '科技', '电子', '信息', '新材'][Math.floor(Math.random() * 5)], - date: `${year}${month}${day}`, - formatted_date: `${year}-${month}-${day}`, - continuous_days: Math.floor(Math.random() * 5) + 1, - core_sectors: [['人工智能', 'ChatGPT', '光伏', '锂电池', '芯片'][Math.floor(Math.random() * 5)]] - }); - } - - return stocks; -}; - -// Mock Handlers -export const limitAnalyseHandlers = [ - // ==================== 静态文件路径 Handlers ==================== - - // 1. /data/zt/dates.json - 可用日期列表 - http.get('/data/zt/dates.json', async () => { - await delay(200); - console.log('[Mock LimitAnalyse] 获取 dates.json'); - const data = generateDatesJson(); - return HttpResponse.json(data); - }), - - // 2. /data/zt/daily/:date.json - 每日分析数据 - http.get('/data/zt/daily/:date', async ({ params }) => { - await delay(300); - // 移除 .json 后缀和查询参数 - const dateParam = params.date.replace('.json', '').split('?')[0]; - console.log('[Mock LimitAnalyse] 获取每日数据:', dateParam); - const data = generateDailyJson(dateParam); - return HttpResponse.json(data); - }), - - // 3. /data/zt/stocks.jsonl - 股票列表(用于搜索) - http.get('/data/zt/stocks.jsonl', async () => { - await delay(200); - console.log('[Mock LimitAnalyse] 获取 stocks.jsonl'); - const stocks = generateStocksJsonl(); - // JSONL 格式:每行一个 JSON - const jsonl = stocks.map(s => JSON.stringify(s)).join('\n'); - return new HttpResponse(jsonl, { - headers: { 'Content-Type': 'text/plain' } - }); - }), - - // ==================== API 路径 Handlers (兼容旧版本) ==================== - - // 1. 获取可用日期列表 - http.get('http://111.198.58.126:5001/api/v1/dates/available', async () => { - await delay(300); - - const availableDates = generateAvailableDates(); - - return HttpResponse.json({ - success: true, - events: availableDates, - message: '可用日期列表获取成功', - }); - }), - - // 2. 获取每日分析数据 - http.get('http://111.198.58.126:5001/api/v1/analysis/daily/:date', async ({ params }) => { - await delay(500); - - const { date } = params; - const data = generateDailyAnalysis(date); - - return HttpResponse.json({ - success: true, - data: data, - message: `${date} 每日分析数据获取成功`, - }); - }), - - // 3. 获取词云数据 - http.get('http://111.198.58.126:5001/api/v1/analysis/wordcloud/:date', async ({ params }) => { - await delay(300); - - const { date } = params; - const wordCloudData = generateWordCloudData(); - - return HttpResponse.json({ - success: true, - data: wordCloudData, - message: `${date} 词云数据获取成功`, - }); - }), - - // 4. 混合搜索(POST) - http.post('http://111.198.58.126:5001/api/v1/stocks/search/hybrid', async ({ request }) => { - await delay(400); - - const body = await request.json(); - const { query, type = 'all', mode = 'hybrid' } = body; - - // 生成模拟搜索结果 - const results = []; - const count = Math.floor(Math.random() * 10) + 5; - - for (let i = 0; i < count; i++) { - results.push({ - code: `${Math.random() > 0.5 ? '6' : '0'}${String(Math.floor(Math.random() * 100000)).padStart(5, '0')}`, - name: `${query || '搜索'}相关股票${i + 1}`, - sector: ['人工智能', 'ChatGPT', '新能源'][Math.floor(Math.random() * 3)], - limit_date: new Date().toISOString().split('T')[0].replace(/-/g, ''), - limit_time: `${Math.floor(Math.random() * 4) + 9}:${String(Math.floor(Math.random() * 60)).padStart(2, '0')}`, - price: (Math.random() * 100 + 10).toFixed(2), - change_pct: (Math.random() * 10).toFixed(2), - match_score: (Math.random() * 0.5 + 0.5).toFixed(2), - }); - } - - return HttpResponse.json({ - success: true, - data: { - query: query, - type: type, - mode: mode, - results: results, - total: results.length, - }, - message: '搜索完成', - }); - }), - - // 5. 获取高位股列表(涨停股票列表) - http.get('http://111.198.58.126:5001/api/limit-analyse/high-position-stocks', async ({ request }) => { - await delay(400); - - const url = new URL(request.url); - const date = url.searchParams.get('date'); - - console.log('[Mock LimitAnalyse] 获取高位股列表:', { date }); - - const stocks = generateHighPositionStocks(); - const statistics = generateHighPositionStatistics(stocks); - - return HttpResponse.json({ - success: true, - data: { - stocks: stocks, - statistics: statistics, - date: date, - }, - message: '高位股数据获取成功', - }); - }), -]; diff --git a/src/mocks/handlers/limitAnalyse.ts b/src/mocks/handlers/limitAnalyse.ts deleted file mode 100644 index 3a681bce..00000000 --- a/src/mocks/handlers/limitAnalyse.ts +++ /dev/null @@ -1,965 +0,0 @@ -// src/mocks/handlers/limitAnalyse.ts -// 涨停分析相关的 Mock Handlers - -import { http, HttpResponse } from 'msw'; - -// ============ 类型定义 ============ - -/** 日期项 */ -interface MockDateItem { - date: string; - formatted_date?: string; - count: number; - top_sector: string; -} - -/** 板块股票 */ -interface MockSectorStock { - code: string; - name: string; - latest_limit_time: string; - limit_up_count: number; - price: string; - change_pct: string; - turnover_rate: string; - volume: number; - amount: string; - limit_type: string; - 封单金额: string; -} - -/** 板块数据 */ -interface MockSector { - sector_name: string; - stock_count: number; - avg_limit_time: string; - stocks: MockSectorStock[]; -} - -/** 高位股 */ -interface MockHighPositionStock { - stock_code: string; - stock_name: string; - price: number; - increase_rate: number; - continuous_limit_up: number; - industry: string; - turnover_rate: number; -} - -/** 高位股统计 */ -interface MockHighPositionStatistics { - total_count: number; - avg_continuous_days: number; - max_continuous_days: number; -} - -/** 词云项 */ -interface MockWordCloudItem { - text: string; - value: number; - category: string; -} - -/** 股票记录 */ -interface MockStockRecord { - scode: string; - sname: string; - zt_time: string; - formatted_time: string; - continuous_days: string; - brief: string; - summary: string; - first_time: string; - change_pct: number; - core_sectors: string[]; -} - -/** 板块信息 */ -interface MockSectorInfo { - count: number; - stocks?: MockStockRecord[]; - stock_codes?: string[]; - net_inflow?: number; - leading_stock?: string; -} - -/** 板块关联节点 */ -interface MockSectorNode { - id: string; - name: string; - value: number; - category: number; - symbolSize: number; -} - -/** 板块关联边 */ -interface MockSectorLink { - source: string; - target: string; - value: number; -} - -/** 板块关联数据 */ -interface MockSectorRelations { - nodes: MockSectorNode[]; - links: MockSectorLink[]; -} - -/** 每日分析数据 */ -interface MockDailyAnalysis { - date: string; - total_stocks: number; - total_sectors: number; - sector_data: Record; - chart_data: { labels: string[]; counts: number[] }; - summary: { - top_sector: string; - top_sector_count: number; - announcement_stocks: number; - zt_time_distribution: { - morning: number; - midday: number; - afternoon: number; - }; - }; -} - -/** 每日 JSON 数据(扩展版) */ -interface MockDailyJson extends MockDailyAnalysis { - stocks: MockStockRecord[]; - word_freq_data: { name: string; value: number }[]; - sector_relations: MockSectorRelations; -} - -/** 搜索结果项 */ -interface MockSearchResult { - code: string; - name: string; - sector: string; - limit_date: string; - limit_time: string; - price: string; - change_pct: string; - match_score: string; -} - -/** JSONL 股票记录 */ -interface MockJsonlStock { - scode: string; - sname: string; - date: string; - formatted_date: string; - continuous_days: number; - core_sectors: string[]; -} - -// ============ 工具函数 ============ - -/** 模拟延迟 */ -const delay = (ms: number): Promise => new Promise((resolve) => setTimeout(resolve, ms)); - -/** 生成可用日期列表(最近30个交易日) */ -const generateAvailableDates = (): MockDateItem[] => { - const dates: MockDateItem[] = []; - const today = new Date(); - let count = 0; - - for (let i = 0; i < 60 && count < 30; i++) { - const date = new Date(today); - date.setDate(date.getDate() - i); - const dayOfWeek = date.getDay(); - - // 跳过周末 - if (dayOfWeek !== 0 && dayOfWeek !== 6) { - const year = date.getFullYear(); - const month = String(date.getMonth() + 1).padStart(2, '0'); - const day = String(date.getDate()).padStart(2, '0'); - const dateStr = `${year}${month}${day}`; - - // 返回包含 date 和 count 字段的对象 - // 生成 15-110 范围的涨停数,确保各阶段都有数据 - // <40: 偏冷, >=40: 温和, >=60: 高涨, >=80: 超级高涨 - let limitCount: number; - const rand = Math.random(); - if (rand < 0.2) { - limitCount = Math.floor(Math.random() * 25) + 15; // 15-39 偏冷日 - } else if (rand < 0.5) { - limitCount = Math.floor(Math.random() * 20) + 40; // 40-59 温和日 - } else if (rand < 0.8) { - limitCount = Math.floor(Math.random() * 20) + 60; // 60-79 高涨日 - } else { - limitCount = Math.floor(Math.random() * 30) + 80; // 80-109 超级高涨日 - } - - dates.push({ - date: dateStr, - count: limitCount, - top_sector: ['人工智能', 'ChatGPT', '新能源', '半导体', '医药'][Math.floor(Math.random() * 5)], - }); - count++; - } - } - - return dates; -}; - -/** 生成板块数据 */ -const generateSectors = (count = 8): MockSector[] => { - const sectorNames = [ - '人工智能', - 'ChatGPT', - '数字经济', - '新能源汽车', - '光伏', - '锂电池', - '半导体', - '芯片', - '5G通信', - '医疗器械', - '创新药', - '中药', - '白酒', - '食品饮料', - '消费电子', - '军工', - '航空航天', - '新材料', - ]; - - const sectors: MockSector[] = []; - for (let i = 0; i < Math.min(count, sectorNames.length); i++) { - const stockCount = Math.floor(Math.random() * 15) + 5; - const stocks: MockSectorStock[] = []; - - for (let j = 0; j < stockCount; j++) { - stocks.push({ - code: `${Math.random() > 0.5 ? '6' : '0'}${String(Math.floor(Math.random() * 100000)).padStart(5, '0')}`, - name: `${sectorNames[i]}股票${j + 1}`, - latest_limit_time: `${Math.floor(Math.random() * 4) + 9}:${String(Math.floor(Math.random() * 60)).padStart(2, '0')}:${String(Math.floor(Math.random() * 60)).padStart(2, '0')}`, - limit_up_count: Math.floor(Math.random() * 3) + 1, - price: (Math.random() * 100 + 10).toFixed(2), - change_pct: (Math.random() * 5 + 5).toFixed(2), - turnover_rate: (Math.random() * 30 + 5).toFixed(2), - volume: Math.floor(Math.random() * 100000000 + 10000000), - amount: (Math.random() * 1000000000 + 100000000).toFixed(2), - limit_type: Math.random() > 0.7 ? '一字板' : Math.random() > 0.5 ? 'T字板' : '普通涨停', - 封单金额: (Math.random() * 500000000).toFixed(2), - }); - } - - sectors.push({ - sector_name: sectorNames[i], - stock_count: stockCount, - avg_limit_time: `${Math.floor(Math.random() * 2) + 10}:${String(Math.floor(Math.random() * 60)).padStart(2, '0')}`, - stocks: stocks, - }); - } - - return sectors; -}; - -/** 生成高位股数据(用于 HighPositionStocks 组件) */ -const generateHighPositionStocks = (): MockHighPositionStock[] => { - const stocks: MockHighPositionStock[] = []; - const stockNames = [ - '宁德时代', - '比亚迪', - '隆基绿能', - '东方财富', - '中际旭创', - '京东方A', - '海康威视', - '立讯精密', - '三一重工', - '恒瑞医药', - '三六零', - '东方通信', - '贵州茅台', - '五粮液', - '中国平安', - ]; - const industries = [ - '锂电池', - '新能源汽车', - '光伏', - '金融科技', - '通信设备', - '显示器件', - '安防设备', - '电子元件', - '工程机械', - '医药制造', - '网络安全', - '通信服务', - '白酒', - '食品饮料', - '保险', - ]; - - for (let i = 0; i < stockNames.length; i++) { - const code = `${Math.random() > 0.5 ? '6' : '0'}${String(Math.floor(Math.random() * 100000)).padStart(5, '0')}`; - const continuousDays = Math.floor(Math.random() * 8) + 2; // 2-9连板 - const price = parseFloat((Math.random() * 100 + 20).toFixed(2)); - const increaseRate = parseFloat((Math.random() * 3 + 8).toFixed(2)); // 8%-11% - const turnoverRate = parseFloat((Math.random() * 20 + 5).toFixed(2)); // 5%-25% - - stocks.push({ - stock_code: code, - stock_name: stockNames[i], - price: price, - increase_rate: increaseRate, - continuous_limit_up: continuousDays, - industry: industries[i], - turnover_rate: turnoverRate, - }); - } - - // 按连板天数降序排序 - stocks.sort((a, b) => b.continuous_limit_up - a.continuous_limit_up); - - return stocks; -}; - -/** 生成高位股统计数据 */ -const generateHighPositionStatistics = (stocks: MockHighPositionStock[]): MockHighPositionStatistics => { - if (!stocks || stocks.length === 0) { - return { - total_count: 0, - avg_continuous_days: 0, - max_continuous_days: 0, - }; - } - - const totalCount = stocks.length; - const sumDays = stocks.reduce((sum, stock) => sum + stock.continuous_limit_up, 0); - const maxDays = Math.max(...stocks.map((s) => s.continuous_limit_up)); - - return { - total_count: totalCount, - avg_continuous_days: parseFloat((sumDays / totalCount).toFixed(1)), - max_continuous_days: maxDays, - }; -}; - -/** 生成词云数据 */ -const generateWordCloudData = (): MockWordCloudItem[] => { - const keywords = [ - '人工智能', - 'ChatGPT', - 'AI芯片', - '大模型', - '算力', - '新能源', - '光伏', - '锂电池', - '储能', - '充电桩', - '半导体', - '芯片', - 'EDA', - '国产替代', - '集成电路', - '医疗', - '创新药', - 'CXO', - '医疗器械', - '生物医药', - '消费', - '白酒', - '食品', - '零售', - '餐饮', - '金融', - '券商', - '保险', - '银行', - '金融科技', - ]; - - return keywords.map((keyword) => ({ - text: keyword, - value: Math.floor(Math.random() * 50) + 10, - category: ['科技', '新能源', '医疗', '消费', '金融'][Math.floor(Math.random() * 5)], - })); -}; - -/** 生成每日分析数据 */ -const generateDailyAnalysis = (date: string): MockDailyAnalysis => { - const sectorNames = [ - '公告', - '人工智能', - 'ChatGPT', - '数字经济', - '新能源汽车', - '光伏', - '锂电池', - '半导体', - '芯片', - '5G通信', - '医疗器械', - '创新药', - '其他', - ]; - - const stockNameTemplates = ['龙头', '科技', '新能源', '智能', '数字', '云计算', '创新', '生物', '医疗', '通信', '电子', '材料', '能源', '互联']; - - // 生成 sector_data(SectorDetails 组件需要的格式) - const sectorData: Record = {}; - let totalStocks = 0; - - sectorNames.forEach((sectorName) => { - const stockCount = Math.floor(Math.random() * 12) + 3; // 每个板块 3-15 只股票 - const stocks: MockStockRecord[] = []; - - for (let i = 0; i < stockCount; i++) { - const code = `${Math.random() > 0.5 ? '6' : '0'}${String(Math.floor(Math.random() * 100000)).padStart(5, '0')}`; - const continuousDays = Math.floor(Math.random() * 6) + 1; // 1-6连板 - const ztHour = Math.floor(Math.random() * 5) + 9; // 9-13点 - const ztMinute = Math.floor(Math.random() * 60); - const ztSecond = Math.floor(Math.random() * 60); - const ztTime = `2024-10-28 ${String(ztHour).padStart(2, '0')}:${String(ztMinute).padStart(2, '0')}:${String(ztSecond).padStart(2, '0')}`; - - const stockName = `${stockNameTemplates[i % stockNameTemplates.length]}${sectorName === '公告' ? '公告' : ''}股份${i + 1}`; - - stocks.push({ - scode: code, - sname: stockName, - zt_time: ztTime, - formatted_time: `${String(ztHour).padStart(2, '0')}:${String(ztMinute).padStart(2, '0')}`, - continuous_days: continuousDays === 1 ? '首板' : `${continuousDays}连板`, - brief: `${sectorName}板块异动,${stockName}因${sectorName === '公告' ? '重大公告利好' : '板块热点'}涨停。公司是${sectorName}行业龙头企业之一。`, - summary: `${sectorName}概念持续活跃`, - first_time: `2024-10-${String(28 - (continuousDays - 1)).padStart(2, '0')}`, - change_pct: parseFloat((Math.random() * 2 + 9).toFixed(2)), // 9%-11% - core_sectors: [sectorName, sectorNames[Math.floor(Math.random() * sectorNames.length)], sectorNames[Math.floor(Math.random() * sectorNames.length)]].filter( - (v, idx, a) => a.indexOf(v) === idx - ), // 去重 - }); - } - - sectorData[sectorName] = { - count: stockCount, - stocks: stocks.sort((a, b) => a.zt_time.localeCompare(b.zt_time)), // 按涨停时间排序 - }; - - totalStocks += stockCount; - }); - - // 统计数据 - const morningCount = Math.floor(totalStocks * 0.35); // 早盘涨停 - const middayCount = Math.floor(totalStocks * 0.25); // 午盘涨停 - const afternoonCount = totalStocks - morningCount - middayCount; // 尾盘涨停 - const announcementCount = sectorData['公告']?.count || 0; - const topSector = sectorNames - .filter((s) => s !== '公告' && s !== '其他') - .reduce((max, name) => ((sectorData[name]?.count || 0) > (sectorData[max]?.count || 0) ? name : max), '人工智能'); - - // 生成 chart_data(板块分布饼图需要) - const sortedSectors = Object.entries(sectorData) - .filter(([name]) => name !== '其他' && name !== '公告') - .sort((a, b) => (b[1].count || 0) - (a[1].count || 0)) - .slice(0, 10); - - const chartData = { - labels: sortedSectors.map(([name]) => name), - counts: sortedSectors.map(([, info]) => info.count || 0), - }; - - return { - date: date, - total_stocks: totalStocks, - total_sectors: Object.keys(sectorData).length, - sector_data: sectorData, // 👈 SectorDetails 组件需要的数据 - chart_data: chartData, // 👈 板块分布饼图需要的数据 - summary: { - top_sector: topSector, - top_sector_count: sectorData[topSector]?.count || 0, - announcement_stocks: announcementCount, - zt_time_distribution: { - morning: morningCount, - midday: middayCount, - afternoon: afternoonCount, - }, - }, - }; -}; - -// ==================== 静态文件 Mock Handlers ==================== -// 这些 handlers 用于拦截 /data/zt/* 静态文件请求 - -/** 生成 dates.json 数据 */ -const generateDatesJson = (): { dates: MockDateItem[] } => { - const dates: MockDateItem[] = []; - const today = new Date(); - - for (let i = 0; i < 60; i++) { - const date = new Date(today); - date.setDate(date.getDate() - i); - const dayOfWeek = date.getDay(); - - // 跳过周末 - if (dayOfWeek !== 0 && dayOfWeek !== 6) { - const year = date.getFullYear(); - const month = String(date.getMonth() + 1).padStart(2, '0'); - const day = String(date.getDate()).padStart(2, '0'); - - // 生成 15-110 范围的涨停数,确保各阶段都有数据 - let limitCount: number; - const rand = Math.random(); - if (rand < 0.2) { - limitCount = Math.floor(Math.random() * 25) + 15; // 15-39 偏冷日 - } else if (rand < 0.5) { - limitCount = Math.floor(Math.random() * 20) + 40; // 40-59 温和日 - } else if (rand < 0.8) { - limitCount = Math.floor(Math.random() * 20) + 60; // 60-79 高涨日 - } else { - limitCount = Math.floor(Math.random() * 30) + 80; // 80-109 超级高涨日 - } - - dates.push({ - date: `${year}${month}${day}`, - formatted_date: `${year}-${month}-${day}`, - count: limitCount, - top_sector: ['人工智能', 'ChatGPT', '新能源', '半导体', '医药'][Math.floor(Math.random() * 5)], - }); - - if (dates.length >= 30) break; - } - } - - return { dates }; -}; - -/** 生成板块关联数据 */ -const generateSectorRelations = (sectorData: Record): MockSectorRelations => { - const sectors = Object.entries(sectorData) - .filter(([name]) => name !== '其他' && name !== '公告') - .slice(0, 12); - - if (sectors.length === 0) return { nodes: [], links: [] }; - - // 板块分类映射 - const categoryMap: Record = { - 人工智能: 0, - ChatGPT: 0, - 大模型: 0, - 算力: 0, - 光伏: 1, - 新能源汽车: 1, - 锂电池: 1, - 储能: 1, - 充电桩: 1, - 半导体: 2, - 芯片: 2, - 集成电路: 2, - 国产替代: 2, - 医药: 3, - 创新药: 3, - CXO: 3, - 医疗器械: 3, - 军工: 4, - 航空航天: 4, - }; - - const nodes: MockSectorNode[] = sectors.map(([name, info]) => ({ - id: name, - name: name, - value: info.count || 0, - category: categoryMap[name] ?? 5, - symbolSize: Math.max(25, Math.min(60, (info.count || 0) * 3)), - })); - - // 生成关联边(基于分类相似性和随机) - const links: MockSectorLink[] = []; - const sectorNames = sectors.map(([name]) => name); - - for (let i = 0; i < sectorNames.length; i++) { - for (let j = i + 1; j < sectorNames.length; j++) { - const source = sectorNames[i]; - const target = sectorNames[j]; - const sameCategory = categoryMap[source] === categoryMap[target]; - - // 同类板块有更高概率关联 - if (sameCategory || Math.random() > 0.7) { - links.push({ - source, - target, - value: sameCategory ? 3 : 1, - }); - } - } - } - - return { nodes, links }; -}; - -/** 生成每日分析 JSON 数据(用于 /data/zt/daily/${date}.json) */ -const generateDailyJson = (date: string): MockDailyJson => { - // 板块名称列表 - const sectorNames = [ - '公告', - '人工智能', - 'ChatGPT', - '大模型', - '算力', - '光伏', - '新能源汽车', - '锂电池', - '储能', - '充电桩', - '半导体', - '芯片', - '集成电路', - '国产替代', - '医药', - '创新药', - 'CXO', - '医疗器械', - '军工', - '航空航天', - '其他', - ]; - - // 股票名称模板 - const stockPrefixes = [ - '龙头', - '科技', - '新能', - '智能', - '数字', - '云计', - '创新', - '生物', - '医疗', - '通信', - '电子', - '材料', - '能源', - '互联', - '天马', - '华鑫', - '中科', - '东方', - '西部', - '南方', - '北方', - '金龙', - '银河', - '星辰', - '宏达', - '盛世', - '鹏程', - '万里', - ]; - - const stockSuffixes = ['股份', '科技', '电子', '信息', '新材', '能源', '医药', '通讯', '智造', '集团', '实业', '控股', '产业', '发展']; - - // 生成所有股票 - const stocks: MockStockRecord[] = []; - const sectorData: Record = {}; - - sectorNames.forEach((sectorName) => { - const stockCount = - sectorName === '公告' - ? Math.floor(Math.random() * 5) + 8 // 公告板块 8-12 只 - : sectorName === '其他' - ? Math.floor(Math.random() * 4) + 3 // 其他板块 3-6 只 - : Math.floor(Math.random() * 8) + 3; // 普通板块 3-10 只 - - const sectorStockCodes: string[] = []; - - for (let i = 0; i < stockCount; i++) { - const code = `${Math.random() > 0.6 ? '6' : Math.random() > 0.3 ? '0' : '3'}${String(Math.floor(Math.random() * 100000)).padStart(5, '0')}`; - const continuousDays = Math.floor(Math.random() * 6) + 1; - const ztHour = Math.floor(Math.random() * 4) + 9; - const ztMinute = Math.floor(Math.random() * 60); - const ztSecond = Math.floor(Math.random() * 60); - - const prefix = stockPrefixes[Math.floor(Math.random() * stockPrefixes.length)]; - const suffix = stockSuffixes[Math.floor(Math.random() * stockSuffixes.length)]; - const stockName = `${prefix}${suffix}`; - - // 生成关联板块 - const coreSectors = [sectorName]; - if (Math.random() > 0.5) { - const otherSector = sectorNames[Math.floor(Math.random() * (sectorNames.length - 1))]; - if (otherSector !== sectorName && otherSector !== '其他' && otherSector !== '公告') { - coreSectors.push(otherSector); - } - } - - stocks.push({ - scode: code, - sname: stockName, - zt_time: `${date.slice(0, 4)}-${date.slice(4, 6)}-${date.slice(6, 8)} ${String(ztHour).padStart(2, '0')}:${String(ztMinute).padStart(2, '0')}:${String(ztSecond).padStart(2, '0')}`, - formatted_time: `${String(ztHour).padStart(2, '0')}:${String(ztMinute).padStart(2, '0')}`, - continuous_days: continuousDays === 1 ? '首板' : `${continuousDays}连板`, - brief: - sectorName === '公告' - ? `${stockName}发布重大公告,公司拟收购资产/重组/增发等利好消息。` - : `${sectorName}板块异动,${stockName}因板块热点涨停。公司是${sectorName}行业核心标的。`, - summary: `${sectorName}概念活跃`, - first_time: `${date.slice(0, 4)}-${date.slice(4, 6)}-${String(parseInt(date.slice(6, 8)) - (continuousDays - 1)).padStart(2, '0')}`, - change_pct: parseFloat((Math.random() * 1.5 + 9.5).toFixed(2)), - core_sectors: coreSectors, - }); - - sectorStockCodes.push(code); - } - - // 添加净流入和领涨股数据 - const netInflow = parseFloat((Math.random() * 30 - 10).toFixed(2)); - const leadingStock = stocks.find((s) => s.core_sectors?.includes(sectorName))?.sname || stocks[0]?.sname || '-'; - - sectorData[sectorName] = { - count: stockCount, - stock_codes: sectorStockCodes, - net_inflow: netInflow, - leading_stock: leadingStock, - stocks: stocks.filter((s) => s.core_sectors?.includes(sectorName)).slice(0, 15), - }; - }); - - // 生成词频数据 - const wordFreqData = [ - { name: '人工智能', value: Math.floor(Math.random() * 30) + 20 }, - { name: 'ChatGPT', value: Math.floor(Math.random() * 25) + 15 }, - { name: '大模型', value: Math.floor(Math.random() * 20) + 12 }, - { name: '算力', value: Math.floor(Math.random() * 18) + 10 }, - { name: '光伏', value: Math.floor(Math.random() * 15) + 10 }, - { name: '新能源', value: Math.floor(Math.random() * 15) + 8 }, - { name: '锂电池', value: Math.floor(Math.random() * 12) + 8 }, - { name: '储能', value: Math.floor(Math.random() * 12) + 6 }, - { name: '半导体', value: Math.floor(Math.random() * 15) + 10 }, - { name: '芯片', value: Math.floor(Math.random() * 15) + 8 }, - { name: '集成电路', value: Math.floor(Math.random() * 10) + 5 }, - { name: '国产替代', value: Math.floor(Math.random() * 10) + 5 }, - { name: '医药', value: Math.floor(Math.random() * 12) + 6 }, - { name: '创新药', value: Math.floor(Math.random() * 10) + 5 }, - { name: '医疗器械', value: Math.floor(Math.random() * 8) + 4 }, - { name: '军工', value: Math.floor(Math.random() * 10) + 5 }, - { name: '航空航天', value: Math.floor(Math.random() * 8) + 4 }, - { name: '数字经济', value: Math.floor(Math.random() * 12) + 6 }, - { name: '工业4.0', value: Math.floor(Math.random() * 8) + 4 }, - { name: '机器人', value: Math.floor(Math.random() * 10) + 5 }, - { name: '自动驾驶', value: Math.floor(Math.random() * 8) + 4 }, - { name: '元宇宙', value: Math.floor(Math.random() * 6) + 3 }, - { name: 'Web3.0', value: Math.floor(Math.random() * 5) + 2 }, - { name: '区块链', value: Math.floor(Math.random() * 5) + 2 }, - ]; - - // 生成 chart_data(板块分布饼图需要) - const sortedSectors = Object.entries(sectorData) - .filter(([name]) => name !== '其他' && name !== '公告') - .sort((a, b) => (b[1].count || 0) - (a[1].count || 0)) - .slice(0, 10); - - const chartData = { - labels: sortedSectors.map(([name]) => name), - counts: sortedSectors.map(([, info]) => info.count || 0), - }; - - // 时间分布(早盘、午盘、尾盘) - const morningCount = Math.floor(stocks.length * 0.35); - const middayCount = Math.floor(stocks.length * 0.25); - const afternoonCount = stocks.length - morningCount - middayCount; - - // 生成板块关联数据 - const sectorRelations = generateSectorRelations(sectorData); - - return { - date: date, - total_stocks: stocks.length, - total_sectors: Object.keys(sectorData).length, - stocks: stocks, - sector_data: sectorData, - word_freq_data: wordFreqData, - chart_data: chartData, // 👈 板块分布饼图需要的数据 - sector_relations: sectorRelations, // 👈 板块关联网络图数据 - summary: { - top_sector: '人工智能', - top_sector_count: sectorData['人工智能']?.count || 0, - announcement_stocks: sectorData['公告']?.count || 0, - zt_time_distribution: { - morning: morningCount, // 早盘 9:30-11:30 - midday: middayCount, // 午盘 11:30-13:00 - afternoon: afternoonCount, // 尾盘 13:00-15:00 - }, - }, - }; -}; - -/** 生成 stocks.jsonl 数据 */ -const generateStocksJsonl = (): MockJsonlStock[] => { - const stocks: MockJsonlStock[] = []; - const today = new Date(); - - // 生成 200 只历史涨停股票记录 - for (let i = 0; i < 200; i++) { - const daysAgo = Math.floor(Math.random() * 30); - const date = new Date(today); - date.setDate(date.getDate() - daysAgo); - - // 跳过周末 - while (date.getDay() === 0 || date.getDay() === 6) { - date.setDate(date.getDate() - 1); - } - - const year = date.getFullYear(); - const month = String(date.getMonth() + 1).padStart(2, '0'); - const day = String(date.getDate()).padStart(2, '0'); - - stocks.push({ - scode: `${Math.random() > 0.6 ? '6' : Math.random() > 0.3 ? '0' : '3'}${String(Math.floor(Math.random() * 100000)).padStart(5, '0')}`, - sname: - ['龙头', '科技', '新能', '智能', '数字', '云计'][Math.floor(Math.random() * 6)] + - ['股份', '科技', '电子', '信息', '新材'][Math.floor(Math.random() * 5)], - date: `${year}${month}${day}`, - formatted_date: `${year}-${month}-${day}`, - continuous_days: Math.floor(Math.random() * 5) + 1, - core_sectors: [['人工智能', 'ChatGPT', '光伏', '锂电池', '芯片'][Math.floor(Math.random() * 5)]], - }); - } - - return stocks; -}; - -// ============ Mock Handlers ============ - -export const limitAnalyseHandlers = [ - // ==================== 静态文件路径 Handlers ==================== - - // 1. /data/zt/dates.json - 可用日期列表 - http.get('/data/zt/dates.json', async () => { - await delay(200); - console.log('[Mock LimitAnalyse] 获取 dates.json'); - const data = generateDatesJson(); - return HttpResponse.json(data); - }), - - // 2. /data/zt/daily/:date.json - 每日分析数据 - http.get('/data/zt/daily/:date', async ({ params }) => { - await delay(300); - // 移除 .json 后缀和查询参数 - const dateParam = (params.date as string).replace('.json', '').split('?')[0]; - console.log('[Mock LimitAnalyse] 获取每日数据:', dateParam); - const data = generateDailyJson(dateParam); - return HttpResponse.json(data); - }), - - // 3. /data/zt/stocks.jsonl - 股票列表(用于搜索) - http.get('/data/zt/stocks.jsonl', async () => { - await delay(200); - console.log('[Mock LimitAnalyse] 获取 stocks.jsonl'); - const stocks = generateStocksJsonl(); - // JSONL 格式:每行一个 JSON - const jsonl = stocks.map((s) => JSON.stringify(s)).join('\n'); - return new HttpResponse(jsonl, { - headers: { 'Content-Type': 'text/plain' }, - }); - }), - - // ==================== API 路径 Handlers (兼容旧版本) ==================== - - // 1. 获取可用日期列表 - http.get('http://111.198.58.126:5001/api/v1/dates/available', async () => { - await delay(300); - - const availableDates = generateAvailableDates(); - - return HttpResponse.json({ - success: true, - events: availableDates, - message: '可用日期列表获取成功', - }); - }), - - // 2. 获取每日分析数据 - http.get('http://111.198.58.126:5001/api/v1/analysis/daily/:date', async ({ params }) => { - await delay(500); - - const { date } = params; - const data = generateDailyAnalysis(date as string); - - return HttpResponse.json({ - success: true, - data: data, - message: `${date} 每日分析数据获取成功`, - }); - }), - - // 3. 获取词云数据 - http.get('http://111.198.58.126:5001/api/v1/analysis/wordcloud/:date', async ({ params }) => { - await delay(300); - - const { date } = params; - const wordCloudData = generateWordCloudData(); - - return HttpResponse.json({ - success: true, - data: wordCloudData, - message: `${date} 词云数据获取成功`, - }); - }), - - // 4. 混合搜索(POST) - http.post('http://111.198.58.126:5001/api/v1/stocks/search/hybrid', async ({ request }) => { - await delay(400); - - const body = (await request.json()) as { query?: string; type?: string; mode?: string }; - const { query, type = 'all', mode = 'hybrid' } = body; - - // 生成模拟搜索结果 - const results: MockSearchResult[] = []; - const count = Math.floor(Math.random() * 10) + 5; - - for (let i = 0; i < count; i++) { - results.push({ - code: `${Math.random() > 0.5 ? '6' : '0'}${String(Math.floor(Math.random() * 100000)).padStart(5, '0')}`, - name: `${query || '搜索'}相关股票${i + 1}`, - sector: ['人工智能', 'ChatGPT', '新能源'][Math.floor(Math.random() * 3)], - limit_date: new Date().toISOString().split('T')[0].replace(/-/g, ''), - limit_time: `${Math.floor(Math.random() * 4) + 9}:${String(Math.floor(Math.random() * 60)).padStart(2, '0')}`, - price: (Math.random() * 100 + 10).toFixed(2), - change_pct: (Math.random() * 10).toFixed(2), - match_score: (Math.random() * 0.5 + 0.5).toFixed(2), - }); - } - - return HttpResponse.json({ - success: true, - data: { - query: query, - type: type, - mode: mode, - results: results, - total: results.length, - }, - message: '搜索完成', - }); - }), - - // 5. 获取高位股列表(涨停股票列表) - http.get('http://111.198.58.126:5001/api/limit-analyse/high-position-stocks', async ({ request }) => { - await delay(400); - - const url = new URL(request.url); - const date = url.searchParams.get('date'); - - console.log('[Mock LimitAnalyse] 获取高位股列表:', { date }); - - const stocks = generateHighPositionStocks(); - const statistics = generateHighPositionStatistics(stocks); - - return HttpResponse.json({ - success: true, - data: { - stocks: stocks, - statistics: statistics, - date: date, - }, - message: '高位股数据获取成功', - }); - }), -]; diff --git a/src/mocks/handlers/market.js b/src/mocks/handlers/market.js deleted file mode 100644 index ba0fb21a..00000000 --- a/src/mocks/handlers/market.js +++ /dev/null @@ -1,665 +0,0 @@ -// src/mocks/handlers/market.js -// 市场行情相关的 Mock Handlers - -import { http, HttpResponse } from 'msw'; -import { generateMarketData } from '../data/market'; - -// 模拟延迟 -const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms)); - -export const marketHandlers = [ - // 0. 指数实时行情数据 - http.get('/api/index/:indexCode/realtime', async ({ params }) => { - await delay(100); - const { indexCode } = params; - - console.log('[Mock] 获取指数实时行情, indexCode:', indexCode); - - // 指数基础数据 - const indexData = { - '000001': { name: '上证指数', basePrice: 3200, baseVolume: 3500 }, - '399001': { name: '深证成指', basePrice: 10500, baseVolume: 4200 }, - '399006': { name: '创业板指', basePrice: 2100, baseVolume: 1800 }, - '000300': { name: '沪深300', basePrice: 3800, baseVolume: 2800 }, - '000016': { name: '上证50', basePrice: 2600, baseVolume: 1200 }, - '000905': { name: '中证500', basePrice: 5800, baseVolume: 1500 }, - }; - - const baseData = indexData[indexCode] || { name: `指数${indexCode}`, basePrice: 3000, baseVolume: 2000 }; - - // 生成随机波动 - const changePercent = parseFloat((Math.random() * 4 - 2).toFixed(2)); // -2% ~ +2% - const price = parseFloat((baseData.basePrice * (1 + changePercent / 100)).toFixed(2)); - const change = parseFloat((price - baseData.basePrice).toFixed(2)); - const volume = parseFloat((baseData.baseVolume * (0.8 + Math.random() * 0.4)).toFixed(2)); // 80%-120% of base - const amount = parseFloat((volume * price / 10000).toFixed(2)); // 亿元 - - return HttpResponse.json({ - success: true, - data: { - index_code: indexCode, - index_name: baseData.name, - current_price: price, - change: change, - change_percent: changePercent, - open_price: parseFloat((baseData.basePrice * (1 + (Math.random() * 0.01 - 0.005))).toFixed(2)), - high_price: parseFloat((price * (1 + Math.random() * 0.01)).toFixed(2)), - low_price: parseFloat((price * (1 - Math.random() * 0.01)).toFixed(2)), - prev_close: baseData.basePrice, - volume: volume, // 亿手 - amount: amount, // 亿元 - update_time: new Date().toISOString(), - market_status: 'trading', // trading, closed, pre-market, after-hours - }, - message: '获取成功' - }); - }), - - // 1. 成交数据 - http.get('/api/market/trade/:stockCode', async ({ params, request }) => { - await delay(200); - const { stockCode } = params; - const data = generateMarketData(stockCode); - return HttpResponse.json(data.tradeData); - }), - - // 2. 资金流向 - http.get('/api/market/funding/:stockCode', async ({ params }) => { - await delay(200); - const { stockCode } = params; - const data = generateMarketData(stockCode); - return HttpResponse.json(data.fundingData); - }), - - // 3. 大单统计 - http.get('/api/market/bigdeal/:stockCode', async ({ params }) => { - await delay(200); - const { stockCode } = params; - const data = generateMarketData(stockCode); - return HttpResponse.json(data.bigDealData); - }), - - // 4. 异动分析 - http.get('/api/market/unusual/:stockCode', async ({ params }) => { - await delay(200); - const { stockCode } = params; - const data = generateMarketData(stockCode); - return HttpResponse.json(data.unusualData); - }), - - // 5. 股权质押 - http.get('/api/market/pledge/:stockCode', async ({ params }) => { - await delay(200); - const { stockCode } = params; - const data = generateMarketData(stockCode); - return HttpResponse.json(data.pledgeData); - }), - - // 6. 市场摘要 - http.get('/api/market/summary/:stockCode', async ({ params }) => { - await delay(200); - const { stockCode } = params; - const data = generateMarketData(stockCode); - return HttpResponse.json(data.summaryData); - }), - - // 7. 涨停分析 - http.get('/api/market/rise-analysis/:stockCode', async ({ params }) => { - await delay(200); - const { stockCode } = params; - const data = generateMarketData(stockCode); - return HttpResponse.json(data.riseAnalysisData); - }), - - // 8. 最新分时数据 - http.get('/api/stock/:stockCode/latest-minute', async ({ params }) => { - await delay(300); - const { stockCode } = params; - const data = generateMarketData(stockCode); - return HttpResponse.json(data.latestMinuteData); - }), - - // 9. 热门概念数据(个股中心页面使用) - http.get('/api/concepts/daily-top', async ({ request }) => { - await delay(300); - const url = new URL(request.url); - const limit = parseInt(url.searchParams.get('limit') || '6'); - const date = url.searchParams.get('date'); - - // 获取当前日期或指定日期 - const tradeDate = date || new Date().toISOString().split('T')[0]; - - // 热门概念列表 - const conceptPool = [ - { name: '人工智能', desc: '人工智能是"技术突破+政策扶持"双轮驱动的硬科技主题。随着大模型技术的突破,AI应用场景不断拓展。', tags: ['AI大模型', '算力', '政策驱动'] }, - { name: '新能源汽车', desc: '新能源汽车行业景气度持续向好,渗透率不断提升。政策支持力度大,产业链上下游企业均受益。', tags: ['新能源', '汽车制造', '政策扶持'] }, - { name: '半导体', desc: '国产半导体替代加速,自主可控需求强烈。政策和资金支持力度大,行业迎来黄金发展期。', tags: ['芯片', '自主可控', '国产替代'] }, - { name: '光伏', desc: '光伏装机量快速增长,成本持续下降,行业景气度维持高位。双碳目标下前景广阔。', tags: ['清洁能源', '双碳', '装机量'] }, - { name: '锂电池', desc: '锂电池技术进步,成本优势扩大,下游应用领域持续扩张。新能源汽车和储能需求旺盛。', tags: ['储能', '新能源', '技术突破'] }, - { name: '储能', desc: '储能市场爆发式增长,政策支持力度大,应用场景不断拓展。未来市场空间巨大。', tags: ['新型储能', '电力系统', '政策驱动'] }, - { name: '算力', desc: 'AI大模型推动算力需求爆发,数据中心、服务器、芯片等产业链受益明显。', tags: ['数据中心', 'AI算力', '服务器'] }, - { name: '机器人', desc: '人形机器人产业化加速,特斯拉、小米等巨头入局,产业链迎来发展机遇。', tags: ['人形机器人', '产业化', '巨头入局'] }, - ]; - - // 股票池(扩展到足够多的股票) - const stockPool = [ - { stock_code: '600519', stock_name: '贵州茅台' }, - { stock_code: '300750', stock_name: '宁德时代' }, - { stock_code: '601318', stock_name: '中国平安' }, - { stock_code: '002594', stock_name: '比亚迪' }, - { stock_code: '601012', stock_name: '隆基绿能' }, - { stock_code: '300274', stock_name: '阳光电源' }, - { stock_code: '688981', stock_name: '中芯国际' }, - { stock_code: '000725', stock_name: '京东方A' }, - { stock_code: '600036', stock_name: '招商银行' }, - { stock_code: '000858', stock_name: '五粮液' }, - { stock_code: '601166', stock_name: '兴业银行' }, - { stock_code: '600276', stock_name: '恒瑞医药' }, - { stock_code: '000333', stock_name: '美的集团' }, - { stock_code: '600887', stock_name: '伊利股份' }, - { stock_code: '002415', stock_name: '海康威视' }, - { stock_code: '601888', stock_name: '中国中免' }, - { stock_code: '300059', stock_name: '东方财富' }, - { stock_code: '002475', stock_name: '立讯精密' }, - { stock_code: '600900', stock_name: '长江电力' }, - { stock_code: '601398', stock_name: '工商银行' }, - { stock_code: '600030', stock_name: '中信证券' }, - { stock_code: '000568', stock_name: '泸州老窖' }, - { stock_code: '002352', stock_name: '顺丰控股' }, - { stock_code: '600809', stock_name: '山西汾酒' }, - { stock_code: '300015', stock_name: '爱尔眼科' }, - { stock_code: '002142', stock_name: '宁波银行' }, - { stock_code: '601899', stock_name: '紫金矿业' }, - { stock_code: '600309', stock_name: '万华化学' }, - { stock_code: '002304', stock_name: '洋河股份' }, - { stock_code: '600585', stock_name: '海螺水泥' }, - { stock_code: '601288', stock_name: '农业银行' }, - { stock_code: '600050', stock_name: '中国联通' }, - { stock_code: '000001', stock_name: '平安银行' }, - { stock_code: '601668', stock_name: '中国建筑' }, - { stock_code: '600028', stock_name: '中国石化' }, - { stock_code: '601857', stock_name: '中国石油' }, - { stock_code: '600000', stock_name: '浦发银行' }, - { stock_code: '601328', stock_name: '交通银行' }, - { stock_code: '000002', stock_name: '万科A' }, - { stock_code: '600104', stock_name: '上汽集团' }, - { stock_code: '601601', stock_name: '中国太保' }, - { stock_code: '600016', stock_name: '民生银行' }, - { stock_code: '601628', stock_name: '中国人寿' }, - { stock_code: '600031', stock_name: '三一重工' }, - { stock_code: '002230', stock_name: '科大讯飞' }, - { stock_code: '300124', stock_name: '汇川技术' }, - { stock_code: '002049', stock_name: '紫光国微' }, - { stock_code: '688012', stock_name: '中微公司' }, - { stock_code: '688008', stock_name: '澜起科技' }, - { stock_code: '603501', stock_name: '韦尔股份' }, - ]; - - // 生成历史触发时间 - const generateHappenedTimes = (seed) => { - const times = []; - const count = 3 + (seed % 3); // 3-5个时间点 - for (let k = 0; k < count; k++) { - const daysAgo = 30 + (seed * 7 + k * 11) % 330; - const d = new Date(); - d.setDate(d.getDate() - daysAgo); - times.push(d.toISOString().split('T')[0]); - } - return times.sort().reverse(); - }; - - const matchTypes = ['hybrid_knn', 'keyword', 'semantic']; - - // 生成概念数据 - const concepts = []; - for (let i = 0; i < Math.min(limit, conceptPool.length); i++) { - const concept = conceptPool[i]; - const changePercent = parseFloat((Math.random() * 8 - 1).toFixed(2)); // -1% ~ 7% - const stockCount = Math.floor(Math.random() * 20) + 15; // 15-35只股票 - - // 生成与 stockCount 一致的股票列表(包含完整字段) - const relatedStocks = []; - for (let j = 0; j < stockCount; j++) { - const idx = (i * 7 + j) % stockPool.length; - const stock = stockPool[idx]; - relatedStocks.push({ - stock_code: stock.stock_code, - stock_name: stock.stock_name, - reason: `作为行业龙头企业,${stock.stock_name}在该领域具有核心竞争优势,市场份额领先。`, - change_pct: parseFloat((Math.random() * 15 - 5).toFixed(2)) // -5% ~ +10% - }); - } - - concepts.push({ - concept_id: `CONCEPT_${1001 + i}`, - concept: concept.name, // 原始字段名 - concept_name: concept.name, // 兼容字段名 - description: concept.desc, - stock_count: stockCount, - score: parseFloat((Math.random() * 5 + 3).toFixed(2)), // 3-8 分数 - match_type: matchTypes[i % 3], - price_info: { - avg_change_pct: changePercent, - avg_price: parseFloat((Math.random() * 100 + 10).toFixed(2)), - total_market_cap: parseFloat((Math.random() * 1000 + 100).toFixed(2)) - }, - change_percent: changePercent, // 兼容字段 - happened_times: generateHappenedTimes(i), - outbreak_dates: generateHappenedTimes(i).slice(0, 2), // 爆发日期 - tags: concept.tags || [], // 标签 - stocks: relatedStocks, - hot_score: Math.floor(Math.random() * 100) - }); - } - - // 按涨跌幅降序排序 - concepts.sort((a, b) => b.change_percent - a.change_percent); - - console.log('[Mock Market] 获取热门概念:', { limit, date: tradeDate, count: concepts.length }); - - return HttpResponse.json({ - success: true, - data: concepts, - trade_date: tradeDate - }); - }), - - // 10. 市值热力图数据(个股中心页面使用) - http.get('/api/market/heatmap', async ({ request }) => { - await delay(400); - const url = new URL(request.url); - const limit = parseInt(url.searchParams.get('limit') || '500'); - const date = url.searchParams.get('date'); - - const tradeDate = date || new Date().toISOString().split('T')[0]; - - // 行业列表 - const industries = ['食品饮料', '银行', '医药生物', '电子', '计算机', '汽车', '电力设备', '机械设备', '化工', '房地产', '有色金属', '钢铁']; - const provinces = ['北京', '上海', '广东', '浙江', '江苏', '山东', '四川', '湖北', '福建', '安徽']; - - // 常见股票数据 - const majorStocks = [ - { code: '600519', name: '贵州茅台', cap: 1850, industry: '食品饮料', province: '贵州' }, - { code: '601318', name: '中国平安', cap: 920, industry: '保险', province: '广东' }, - { code: '600036', name: '招商银行', cap: 850, industry: '银行', province: '广东' }, - { code: '300750', name: '宁德时代', cap: 780, industry: '电力设备', province: '福建' }, - { code: '601166', name: '兴业银行', cap: 420, industry: '银行', province: '福建' }, - { code: '000858', name: '五粮液', cap: 580, industry: '食品饮料', province: '四川' }, - { code: '002594', name: '比亚迪', cap: 650, industry: '汽车', province: '广东' }, - { code: '601012', name: '隆基绿能', cap: 320, industry: '电力设备', province: '陕西' }, - { code: '688981', name: '中芯国际', cap: 280, industry: '电子', province: '上海' }, - { code: '600900', name: '长江电力', cap: 520, industry: '公用事业', province: '湖北' }, - ]; - - // 生成热力图数据 - const heatmapData = []; - let risingCount = 0; - let fallingCount = 0; - - // 涨停股票名称池(模拟真实市场) - const limitUpNames = ['东方财富', '科大讯飞', '中科曙光', '寒武纪', '金山办公', '同花顺', '三六零', '紫光股份']; - const limitDownNames = ['某ST股A', '某ST股B']; - - // 先添加一些涨停股票(10%) - for (let i = 0; i < 8; i++) { - const changePercent = parseFloat((9.9 + Math.random() * 0.15).toFixed(2)); // 9.9% ~ 10.05%(涨停) - risingCount++; - heatmapData.push({ - stock_code: `00${3000 + i}`, - stock_name: limitUpNames[i] || `涨停股${i}`, - market_cap: parseFloat((Math.random() * 300 + 50).toFixed(2)), - change_percent: changePercent, - amount: parseFloat((Math.random() * 30 + 5).toFixed(2)), - industry: industries[Math.floor(Math.random() * industries.length)], - province: provinces[Math.floor(Math.random() * provinces.length)] - }); - } - - // 添加一些跌停股票(-10%) - for (let i = 0; i < 2; i++) { - const changePercent = parseFloat((-9.9 - Math.random() * 0.15).toFixed(2)); // -10.05% ~ -9.9%(跌停) - fallingCount++; - heatmapData.push({ - stock_code: `00${2000 + i}`, - stock_name: limitDownNames[i] || `跌停股${i}`, - market_cap: parseFloat((Math.random() * 50 + 10).toFixed(2)), - change_percent: changePercent, - amount: parseFloat((Math.random() * 5 + 0.5).toFixed(2)), - industry: industries[Math.floor(Math.random() * industries.length)], - province: provinces[Math.floor(Math.random() * provinces.length)] - }); - } - - // 添加主要股票 - majorStocks.forEach(stock => { - const changePercent = parseFloat((Math.random() * 12 - 4).toFixed(2)); // -4% ~ 8% - const amount = parseFloat((Math.random() * 100 + 10).toFixed(2)); // 10-110亿 - - if (changePercent > 0) risingCount++; - else if (changePercent < 0) fallingCount++; - - heatmapData.push({ - stock_code: stock.code, - stock_name: stock.name, - market_cap: stock.cap, - change_percent: changePercent, - amount: amount, - industry: stock.industry, - province: stock.province - }); - }); - - // 生成更多随机股票数据 - for (let i = majorStocks.length; i < Math.min(limit, 200); i++) { - const changePercent = parseFloat((Math.random() * 14 - 5).toFixed(2)); // -5% ~ 9% - const marketCap = parseFloat((Math.random() * 500 + 20).toFixed(2)); // 20-520亿 - const amount = parseFloat((Math.random() * 50 + 1).toFixed(2)); // 1-51亿 - - if (changePercent > 0) risingCount++; - else if (changePercent < 0) fallingCount++; - - heatmapData.push({ - stock_code: `${600000 + i}`, - stock_name: `股票${i}`, - market_cap: marketCap, - change_percent: changePercent, - amount: amount, - industry: industries[Math.floor(Math.random() * industries.length)], - province: provinces[Math.floor(Math.random() * provinces.length)] - }); - } - - console.log('[Mock Market] 获取热力图数据:', { limit, date: tradeDate, count: heatmapData.length }); - - return HttpResponse.json({ - success: true, - data: heatmapData, - trade_date: tradeDate, - statistics: { - rising_count: risingCount, - falling_count: fallingCount - } - }); - }), - - // 11. 热点概览数据(大盘分时 + 概念异动) - http.get('/api/market/hotspot-overview', async ({ request }) => { - await delay(300); - const url = new URL(request.url); - const date = url.searchParams.get('date'); - - const tradeDate = date || new Date().toISOString().split('T')[0]; - - // 生成分时数据(240个点,9:30-11:30 + 13:00-15:00) - const timeline = []; - const basePrice = 3900 + Math.random() * 100; // 基准价格 3900-4000 - const prevClose = basePrice; - let currentPrice = basePrice; - let cumulativeVolume = 0; - - // 上午时段 9:30-11:30 (120分钟) - for (let i = 0; i < 120; i++) { - const hour = 9 + Math.floor((i + 30) / 60); - const minute = (i + 30) % 60; - const time = `${hour.toString().padStart(2, '0')}:${minute.toString().padStart(2, '0')}`; - - // 模拟价格波动 - const volatility = 0.002; // 0.2%波动 - const drift = (Math.random() - 0.5) * 0.001; // 微小趋势 - currentPrice = currentPrice * (1 + (Math.random() - 0.5) * volatility + drift); - - const volume = Math.floor(Math.random() * 500000 + 100000); // 成交量 - cumulativeVolume += volume; - - timeline.push({ - time, - price: parseFloat(currentPrice.toFixed(2)), - volume: cumulativeVolume, - change_pct: parseFloat(((currentPrice - prevClose) / prevClose * 100).toFixed(2)) - }); - } - - // 下午时段 13:00-15:00 (120分钟) - for (let i = 0; i < 120; i++) { - const hour = 13 + Math.floor(i / 60); - const minute = i % 60; - const time = `${hour.toString().padStart(2, '0')}:${minute.toString().padStart(2, '0')}`; - - // 下午波动略小 - const volatility = 0.0015; - const drift = (Math.random() - 0.5) * 0.0008; - currentPrice = currentPrice * (1 + (Math.random() - 0.5) * volatility + drift); - - const volume = Math.floor(Math.random() * 400000 + 80000); - cumulativeVolume += volume; - - timeline.push({ - time, - price: parseFloat(currentPrice.toFixed(2)), - volume: cumulativeVolume, - change_pct: parseFloat(((currentPrice - prevClose) / prevClose * 100).toFixed(2)) - }); - } - - // 生成概念异动数据 - const conceptNames = [ - '人工智能', 'AI眼镜', '机器人', '核电', '国企', '卫星导航', - '福建自贸区', '两岸融合', 'CRO', '三季报增长', '百货零售', - '人形机器人', '央企', '数据中心', 'CPO', '新能源', '电网设备', - '氢能源', '算力租赁', '厦门国资', '乳业', '低空安防', '创新药', - '商业航天', '控制权变更', '文化传媒', '海峡两岸' - ]; - - const alertTypes = ['surge_up', 'surge_down', 'volume_spike', 'limit_up', 'rank_jump']; - - // 生成 12-18 个异动(减少数量,更符合实际) - const alertCount = Math.floor(Math.random() * 6) + 12; - const alerts = []; - const usedTimeGroups = new Set(); // 记录已使用的10分钟时间段 - - // 获取10分钟时间段 - const getTimeGroup = (timeStr) => { - const [h, m] = timeStr.split(':').map(Number); - const groupStart = Math.floor(m / 10) * 10; - return `${h}:${groupStart < 10 ? '0' : ''}${groupStart}`; - }; - - for (let i = 0; i < alertCount; i++) { - // 随机选择一个时间点,尽量分散到不同10分钟时间段 - let timeIdx; - let attempts = 0; - let timeGroup; - do { - timeIdx = Math.floor(Math.random() * timeline.length); - timeGroup = getTimeGroup(timeline[timeIdx].time); - attempts++; - // 允许同一时间段最多2个异动 - } while (usedTimeGroups.has(timeGroup) && Math.random() > 0.3 && attempts < 50); - - if (attempts >= 50) continue; - - usedTimeGroups.add(timeGroup); - const time = timeline[timeIdx].time; - const conceptName = conceptNames[Math.floor(Math.random() * conceptNames.length)]; - const alertType = alertTypes[Math.floor(Math.random() * alertTypes.length)]; - - // 根据类型生成 alpha - let alpha; - if (alertType === 'surge_up') { - alpha = parseFloat((Math.random() * 3 + 2).toFixed(2)); // +2% ~ +5% - } else if (alertType === 'surge_down') { - alpha = parseFloat((-Math.random() * 3 - 1.5).toFixed(2)); // -1.5% ~ -4.5% - } else { - alpha = parseFloat((Math.random() * 4 - 1).toFixed(2)); // -1% ~ +3% - } - - const finalScore = Math.floor(Math.random() * 40 + 45); // 45-85分 - const ruleScore = Math.floor(Math.random() * 30 + 40); - const mlScore = Math.floor(Math.random() * 30 + 40); - - alerts.push({ - concept_id: `CONCEPT_${1000 + i}`, - concept_name: conceptName, - time, - alert_type: alertType, - alpha, - alpha_delta: parseFloat((Math.random() * 2 - 0.5).toFixed(2)), - amt_ratio: parseFloat((Math.random() * 5 + 1).toFixed(2)), - limit_up_count: alertType === 'limit_up' ? Math.floor(Math.random() * 5 + 1) : 0, - limit_up_ratio: parseFloat((Math.random() * 0.3).toFixed(3)), - final_score: finalScore, - rule_score: ruleScore, - ml_score: mlScore, - trigger_reason: finalScore >= 65 ? '规则强信号' : (mlScore >= 70 ? 'ML强信号' : '融合触发'), - importance_score: parseFloat((finalScore / 100).toFixed(2)), - index_price: timeline[timeIdx].price - }); - } - - // 按时间排序 - alerts.sort((a, b) => a.time.localeCompare(b.time)); - - // 统计异动类型 - const alertSummary = alerts.reduce((acc, alert) => { - acc[alert.alert_type] = (acc[alert.alert_type] || 0) + 1; - return acc; - }, {}); - - // 计算指数统计 - const prices = timeline.map(t => t.price); - const latestPrice = prices[prices.length - 1]; - const highPrice = Math.max(...prices); - const lowPrice = Math.min(...prices); - const changePct = ((latestPrice - prevClose) / prevClose * 100); - - console.log('[Mock Market] 获取热点概览数据:', { - date: tradeDate, - timelinePoints: timeline.length, - alertCount: alerts.length - }); - - return HttpResponse.json({ - success: true, - data: { - index: { - code: '000001.SH', - name: '上证指数', - latest_price: latestPrice, - prev_close: prevClose, - high: highPrice, - low: lowPrice, - change_pct: parseFloat(changePct.toFixed(2)), - timeline - }, - alerts, - alert_summary: alertSummary - }, - trade_date: tradeDate - }); - }), - - // 12. 市场概况数据(投资仪表盘使用)- 上证/深证/总市值/成交额 - http.get('/api/market/summary', async () => { - await delay(150); - - // 生成实时数据(基于当前时间产生小波动) - const now = new Date(); - const seed = now.getHours() * 60 + now.getMinutes(); - - // 上证指数(基准 3400) - const shBasePrice = 3400; - const shChange = parseFloat(((Math.sin(seed / 30) + Math.random() - 0.5) * 2).toFixed(2)); - const shPrice = parseFloat((shBasePrice * (1 + shChange / 100)).toFixed(2)); - const shChangeAmount = parseFloat((shPrice - shBasePrice).toFixed(2)); - - // 深证指数(基准 10800) - const szBasePrice = 10800; - const szChange = parseFloat(((Math.sin(seed / 25) + Math.random() - 0.5) * 2.5).toFixed(2)); - const szPrice = parseFloat((szBasePrice * (1 + szChange / 100)).toFixed(2)); - const szChangeAmount = parseFloat((szPrice - szBasePrice).toFixed(2)); - - // 总市值(约 100-110 万亿波动) - const totalMarketCap = parseFloat((105 + (Math.sin(seed / 60) * 5)).toFixed(1)) * 1000000000000; - - // 成交额(约 0.8-1.5 万亿波动) - const turnover = parseFloat((1.0 + (Math.random() * 0.5 - 0.2)).toFixed(2)) * 1000000000000; - - console.log('[Mock Market] 获取市场概况数据'); - - return HttpResponse.json({ - success: true, - data: { - shanghai: { - value: shPrice, - change: shChange, - changeAmount: shChangeAmount, - }, - shenzhen: { - value: szPrice, - change: szChange, - changeAmount: szChangeAmount, - }, - totalMarketCap, - turnover, - updateTime: now.toISOString(), - }, - }); - }), - - // 13. 市场统计数据(个股中心页面使用) - http.get('/api/market/statistics', async ({ request }) => { - await delay(200); - const url = new URL(request.url); - const date = url.searchParams.get('date'); - - const tradeDate = date || new Date().toISOString().split('T')[0]; - - // 生成最近30个交易日 - const availableDates = []; - const currentDate = new Date(tradeDate); - for (let i = 0; i < 30; i++) { - const d = new Date(currentDate); - d.setDate(d.getDate() - i); - // 跳过周末 - if (d.getDay() !== 0 && d.getDay() !== 6) { - availableDates.push(d.toISOString().split('T')[0]); - } - } - - console.log('[Mock Market] 获取市场统计数据:', { date: tradeDate }); - - // 生成今日数据 - const todayMarketCap = parseFloat((Math.random() * 5000 + 80000).toFixed(2)); - const todayAmount = parseFloat((Math.random() * 3000 + 8000).toFixed(2)); - const todayRising = Math.floor(Math.random() * 1500 + 1500); - const todayFalling = Math.floor(Math.random() * 1500 + 1000); - - // 生成昨日数据(用于对比) - const yesterdayMarketCap = parseFloat((todayMarketCap * (0.98 + Math.random() * 0.04)).toFixed(2)); - const yesterdayAmount = parseFloat((todayAmount * (0.85 + Math.random() * 0.3)).toFixed(2)); - const yesterdayRising = Math.floor(todayRising * (0.7 + Math.random() * 0.6)); - const yesterdayFalling = Math.floor(todayFalling * (0.7 + Math.random() * 0.6)); - - return HttpResponse.json({ - success: true, - summary: { - total_market_cap: todayMarketCap, - total_amount: todayAmount, - avg_pe: parseFloat((Math.random() * 5 + 12).toFixed(2)), - avg_pb: parseFloat((Math.random() * 0.5 + 1.3).toFixed(2)), - rising_stocks: todayRising, - falling_stocks: todayFalling, - unchanged_stocks: Math.floor(Math.random() * 200 + 100) - }, - // 昨日对比数据 - yesterday: { - total_market_cap: yesterdayMarketCap, - total_amount: yesterdayAmount, - rising_stocks: yesterdayRising, - falling_stocks: yesterdayFalling - }, - trade_date: tradeDate, - available_dates: availableDates.slice(0, 20) - }); - }), -]; diff --git a/src/mocks/handlers/payment.js b/src/mocks/handlers/payment.js deleted file mode 100644 index 753e7f53..00000000 --- a/src/mocks/handlers/payment.js +++ /dev/null @@ -1,242 +0,0 @@ -// src/mocks/handlers/payment.js -import { http, HttpResponse, delay } from 'msw'; -import { getCurrentUser } from '../data/users'; - -// 模拟网络延迟(毫秒) -const NETWORK_DELAY = 500; - -// 模拟订单数据存储 -const mockOrders = new Map(); -let orderIdCounter = 1000; - -export const paymentHandlers = [ - // ==================== 支付订单管理 ==================== - - // 1. 创建支付订单 - http.post('/api/payment/create-order', async ({ request }) => { - await delay(NETWORK_DELAY); - - const currentUser = getCurrentUser(); - if (!currentUser) { - return HttpResponse.json({ - success: false, - error: '未登录' - }, { status: 401 }); - } - - const body = await request.json(); - const { plan_name, billing_cycle } = body; - - console.log('[Mock] 创建支付订单:', { plan_name, billing_cycle, user: currentUser.id }); - - if (!plan_name || !billing_cycle) { - return HttpResponse.json({ - success: false, - error: '参数不完整' - }, { status: 400 }); - } - - // 模拟价格 - const prices = { - pro: { monthly: 0.01, yearly: 0.08 }, - max: { monthly: 0.1, yearly: 0.8 } - }; - - const amount = prices[plan_name]?.[billing_cycle] || 0.01; - - // 创建订单 - const orderId = `ORDER_${orderIdCounter++}_${Date.now()}`; - const order = { - id: orderId, - user_id: currentUser.id, - plan_name, - billing_cycle, - amount, - status: 'pending', - qr_code_url: `https://api.qrserver.com/v1/create-qr-code/?size=300x300&data=weixin://wxpay/bizpayurl?pr=mock_${orderId}`, - created_at: new Date().toISOString(), - expires_at: new Date(Date.now() + 30 * 60 * 1000).toISOString() // 30分钟后过期 - }; - - mockOrders.set(orderId, order); - - console.log('[Mock] 订单创建成功:', order); - - // 模拟5秒后自动支付成功(方便测试) - setTimeout(() => { - const existingOrder = mockOrders.get(orderId); - if (existingOrder && existingOrder.status === 'pending') { - existingOrder.status = 'paid'; - existingOrder.paid_at = new Date().toISOString(); - console.log(`[Mock] 订单自动支付成功: ${orderId}`); - } - }, 5000); - - return HttpResponse.json({ - success: true, - data: order - }); - }), - - // 2. 查询订单状态 - http.get('/api/payment/order-status/:orderId', async ({ params }) => { - await delay(300); - - const currentUser = getCurrentUser(); - if (!currentUser) { - return HttpResponse.json({ - success: false, - error: '未登录' - }, { status: 401 }); - } - - const { orderId } = params; - const order = mockOrders.get(orderId); - - console.log('[Mock] 查询订单状态:', { orderId, found: !!order }); - - if (!order) { - return HttpResponse.json({ - success: false, - error: '订单不存在' - }, { status: 404 }); - } - - if (order.user_id !== currentUser.id) { - return HttpResponse.json({ - success: false, - error: '无权访问此订单' - }, { status: 403 }); - } - - return HttpResponse.json({ - success: true, - data: order - }); - }), - - // 3. 获取用户订单列表 - http.get('/api/payment/orders', async () => { - await delay(NETWORK_DELAY); - - const currentUser = getCurrentUser(); - if (!currentUser) { - return HttpResponse.json({ - success: false, - error: '未登录' - }, { status: 401 }); - } - - const userOrders = Array.from(mockOrders.values()) - .filter(order => order.user_id === currentUser.id) - .sort((a, b) => new Date(b.created_at) - new Date(a.created_at)); - - console.log('[Mock] 获取用户订单列表:', { count: userOrders.length }); - - return HttpResponse.json({ - success: true, - data: userOrders - }); - }), - - // 4. 取消订单 - http.post('/api/payment/cancel-order', async ({ request }) => { - await delay(NETWORK_DELAY); - - const currentUser = getCurrentUser(); - if (!currentUser) { - return HttpResponse.json({ - success: false, - error: '未登录' - }, { status: 401 }); - } - - const body = await request.json(); - const { order_id } = body; - - const order = mockOrders.get(order_id); - - if (!order) { - return HttpResponse.json({ - success: false, - error: '订单不存在' - }, { status: 404 }); - } - - if (order.user_id !== currentUser.id) { - return HttpResponse.json({ - success: false, - error: '无权操作此订单' - }, { status: 403 }); - } - - if (order.status !== 'pending') { - return HttpResponse.json({ - success: false, - error: '只能取消待支付的订单' - }, { status: 400 }); - } - - order.status = 'cancelled'; - order.cancelled_at = new Date().toISOString(); - - console.log('[Mock] 订单已取消:', order_id); - - return HttpResponse.json({ - success: true, - message: '订单已取消' - }); - }), - - // ==================== 微信 JSAPI 支付 ==================== - - // 5. 创建 JSAPI 支付订单(小程序 H5 支付) - http.post('/api/payment/wechat/jsapi/create-order', async ({ request }) => { - await delay(NETWORK_DELAY); - - const body = await request.json(); - const { plan_id, user_id, openid } = body; - - console.log('[Mock] 创建 JSAPI 支付订单:', { plan_id, user_id, openid }); - - if (!plan_id || !user_id || !openid) { - return HttpResponse.json({ - success: false, - error: '参数不完整:需要 plan_id, user_id, openid' - }, { status: 400 }); - } - - // 模拟套餐价格 - const planPrices = { - 'pro_monthly': { name: 'Pro 月度会员', amount: 29.9 }, - 'pro_yearly': { name: 'Pro 年度会员', amount: 299 }, - 'max_monthly': { name: 'Max 月度会员', amount: 99 }, - 'max_yearly': { name: 'Max 年度会员', amount: 999 }, - }; - - const plan = planPrices[plan_id] || { name: '测试套餐', amount: 0.01 }; - - // 创建订单 - const orderNo = `JSAPI_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`; - - // 模拟微信支付参数(实际由后端生成) - const paymentParams = { - appId: 'wx0edeaab76d4fa414', - timeStamp: String(Math.floor(Date.now() / 1000)), - nonceStr: Math.random().toString(36).slice(2, 18), - package: `prepay_id=mock_prepay_${orderNo}`, - signType: 'MD5', - paySign: 'MOCK_SIGN_' + Math.random().toString(36).slice(2, 10).toUpperCase(), - }; - - console.log('[Mock] JSAPI 订单创建成功:', { orderNo, plan: plan.name, amount: plan.amount }); - - return HttpResponse.json({ - success: true, - order_no: orderNo, - plan_name: plan.name, - amount: plan.amount, - payment_params: paymentParams, - }); - }) -]; diff --git a/src/mocks/handlers/posthog.js b/src/mocks/handlers/posthog.js deleted file mode 100644 index ac1fc2d2..00000000 --- a/src/mocks/handlers/posthog.js +++ /dev/null @@ -1,132 +0,0 @@ -// src/mocks/handlers/posthog.js -// PostHog 埋点请求 Mock Handler - -import { http, HttpResponse } from 'msw'; - -/** - * PostHog 埋点 Mock Handler - * 拦截所有发往 PostHog 的埋点请求,避免在 Mock 模式下产生 500 错误 - */ -export const posthogHandlers = [ - // PostHog 事件追踪接口 - http.post('https://us.i.posthog.com/e/', async ({ request }) => { - try { - // 读取埋点数据(可选,用于调试) - const body = await request.text(); - - // 开发环境输出埋点日志(可选,方便调试) - if (process.env.NODE_ENV === 'development' && process.env.REACT_APP_LOG_POSTHOG === 'true') { - console.log('[Mock] PostHog 埋点请求:', { - url: request.url, - bodyPreview: body.substring(0, 150) + (body.length > 150 ? '...' : ''), - }); - } - - // 返回成功响应(模拟 PostHog 服务器响应) - return HttpResponse.json( - { status: 1 }, - { status: 200 } - ); - } catch (error) { - console.error('[Mock] PostHog handler error:', error); - return HttpResponse.json( - { status: 0, error: 'Mock handler error' }, - { status: 500 } - ); - } - }), - - // PostHog batch 批量事件追踪接口(可选) - http.post('https://us.i.posthog.com/batch/', async ({ request }) => { - try { - const body = await request.text(); - - if (process.env.NODE_ENV === 'development' && process.env.REACT_APP_LOG_POSTHOG === 'true') { - console.log('[Mock] PostHog 批量埋点请求:', { - url: request.url, - bodyPreview: body.substring(0, 150) + (body.length > 150 ? '...' : ''), - }); - } - - return HttpResponse.json( - { status: 1 }, - { status: 200 } - ); - } catch (error) { - console.error('[Mock] PostHog batch handler error:', error); - return HttpResponse.json( - { status: 0, error: 'Mock handler error' }, - { status: 500 } - ); - } - }), - - // PostHog decide 接口(功能开关、特性标志) - http.post('https://us.i.posthog.com/decide/', async ({ request }) => { - try { - if (process.env.NODE_ENV === 'development' && process.env.REACT_APP_LOG_POSTHOG === 'true') { - const body = await request.json(); - console.log('[Mock] PostHog decide 请求:', body); - } - - // 返回空的特性标志配置 - return HttpResponse.json({ - featureFlags: {}, - sessionRecording: false, - }); - } catch (error) { - console.error('[Mock] PostHog decide handler error:', error); - return HttpResponse.json( - { featureFlags: {}, sessionRecording: false }, - { status: 200 } - ); - } - }), - - // PostHog session recording 接口(会话录制) - http.post('https://us.i.posthog.com/s/', async ({ request }) => { - try { - const body = await request.text(); - - if (process.env.NODE_ENV === 'development' && process.env.REACT_APP_LOG_POSTHOG === 'true') { - console.log('[Mock] PostHog session recording 请求:', { - url: request.url, - bodyPreview: body.substring(0, 100) + (body.length > 100 ? '...' : ''), - }); - } - - // 返回成功响应 - return HttpResponse.json( - { status: 1 }, - { status: 200 } - ); - } catch (error) { - console.error('[Mock] PostHog session recording handler error:', error); - return HttpResponse.json( - { status: 0, error: 'Mock handler error' }, - { status: 500 } - ); - } - }), - - // PostHog feature flags 接口(特性标志查询) - http.post('https://us.i.posthog.com/flags/', async ({ request }) => { - try { - if (process.env.NODE_ENV === 'development' && process.env.REACT_APP_LOG_POSTHOG === 'true') { - console.log('[Mock] PostHog feature flags 请求:', request.url); - } - - // 返回空的特性标志 - return HttpResponse.json({ - featureFlags: {}, - featureFlagPayloads: {}, - }); - } catch (error) { - console.error('[Mock] PostHog flags handler error:', error); - return HttpResponse.json( - { featureFlags: {}, featureFlagPayloads: {} }, - { status: 200 } - ); - } - }), -]; diff --git a/src/mocks/handlers/prediction.js b/src/mocks/handlers/prediction.js deleted file mode 100644 index 64e12b20..00000000 --- a/src/mocks/handlers/prediction.js +++ /dev/null @@ -1,619 +0,0 @@ -/** - * 预测市场 Mock Handlers - */ -import { http, HttpResponse, delay } from "msw"; -import { - mockTopics, - mockUserAccount, - mockPositions, - mockComments, - mockUsers, -} from "../data/prediction"; - -// 内存状态(用于模拟状态变化) -let userAccount = { ...mockUserAccount }; -let topics = [...mockTopics]; -let positions = [...mockPositions]; -let comments = JSON.parse(JSON.stringify(mockComments)); - -// 重置状态 -const resetState = () => { - userAccount = { ...mockUserAccount }; - topics = [...mockTopics]; - positions = [...mockPositions]; - comments = JSON.parse(JSON.stringify(mockComments)); -}; - -export const predictionHandlers = [ - // ==================== 积分系统 ==================== - - // 获取用户积分账户 - http.get(`/api/prediction/credit/account`, async () => { - await delay(300); - return HttpResponse.json({ - success: true, - code: 200, - message: "success", - data: userAccount, - }); - }), - - // 领取每日奖励 - http.post(`/api/prediction/credit/daily-bonus`, async () => { - await delay(500); - - // 检查是否已领取 - const today = new Date().toDateString(); - const lastBonus = userAccount.last_daily_bonus - ? new Date(userAccount.last_daily_bonus).toDateString() - : null; - - if (lastBonus === today) { - return HttpResponse.json( - { - success: false, - code: 400, - message: "今日已领取过奖励", - data: null, - }, - { status: 400 } - ); - } - - // 发放奖励 - const bonusAmount = 100; - userAccount.balance += bonusAmount; - userAccount.total += bonusAmount; - userAccount.total_earned += bonusAmount; - userAccount.last_daily_bonus = new Date().toISOString(); - - return HttpResponse.json({ - success: true, - code: 200, - message: "领取成功", - data: { - bonus_amount: bonusAmount, - new_balance: userAccount.balance, - }, - }); - }), - - // ==================== 预测话题 ==================== - - // 获取话题列表 - http.get(`/api/prediction/topics`, async ({ request }) => { - await delay(400); - - const url = new URL(request.url); - const status = url.searchParams.get("status"); - const category = url.searchParams.get("category"); - const sortBy = url.searchParams.get("sort_by") || "created_at"; - const page = parseInt(url.searchParams.get("page") || "1", 10); - const perPage = parseInt(url.searchParams.get("per_page") || "10", 10); - - let filteredTopics = [...topics]; - - // 过滤状态 - if (status && status !== "all") { - filteredTopics = filteredTopics.filter((t) => t.status === status); - } - - // 过滤分类 - if (category && category !== "all") { - filteredTopics = filteredTopics.filter((t) => t.category === category); - } - - // 排序 - filteredTopics.sort((a, b) => { - if (sortBy === "total_pool") return b.total_pool - a.total_pool; - if (sortBy === "participants_count") - return b.participants_count - a.participants_count; - return new Date(b.created_at) - new Date(a.created_at); - }); - - // 分页 - const total = filteredTopics.length; - const start = (page - 1) * perPage; - const paginatedTopics = filteredTopics.slice(start, start + perPage); - - return HttpResponse.json({ - success: true, - code: 200, - message: "success", - data: { - topics: paginatedTopics, - pagination: { - page, - per_page: perPage, - total, - total_pages: Math.ceil(total / perPage), - }, - }, - }); - }), - - // 获取话题详情 - http.get(`/api/prediction/topics/:topicId`, async ({ params }) => { - await delay(300); - - const topicId = parseInt(params.topicId, 10); - const topic = topics.find((t) => t.id === topicId); - - if (!topic) { - return HttpResponse.json( - { - success: false, - code: 404, - message: "话题不存在", - data: null, - }, - { status: 404 } - ); - } - - // 构建详细的席位信息 - const topicDetail = { - ...topic, - yes_seats: [ - { - user_id: topic.yes_lord_id, - user_name: topic.yes_lord_name, - user_avatar: mockUsers.find((u) => u.id === topic.yes_lord_id) - ?.avatar_url, - shares: Math.floor(topic.yes_total_shares * 0.4), - is_lord: true, - }, - ...Array(Math.min(4, Math.floor(topic.participants_count / 4))) - .fill(null) - .map((_, i) => ({ - user_id: mockUsers[(i + 2) % mockUsers.length].id, - user_name: mockUsers[(i + 2) % mockUsers.length].nickname, - user_avatar: mockUsers[(i + 2) % mockUsers.length].avatar_url, - shares: Math.floor((topic.yes_total_shares * 0.15) / (i + 1)), - is_lord: false, - })), - ], - no_seats: [ - { - user_id: topic.no_lord_id, - user_name: topic.no_lord_name, - user_avatar: mockUsers.find((u) => u.id === topic.no_lord_id) - ?.avatar_url, - shares: Math.floor(topic.no_total_shares * 0.4), - is_lord: true, - }, - ...Array(Math.min(4, Math.floor(topic.participants_count / 5))) - .fill(null) - .map((_, i) => ({ - user_id: mockUsers[(i + 3) % mockUsers.length].id, - user_name: mockUsers[(i + 3) % mockUsers.length].nickname, - user_avatar: mockUsers[(i + 3) % mockUsers.length].avatar_url, - shares: Math.floor((topic.no_total_shares * 0.15) / (i + 1)), - is_lord: false, - })), - ], - }; - - return HttpResponse.json({ - success: true, - code: 200, - message: "success", - data: topicDetail, - }); - }), - - // 创建话题 - http.post(`/api/prediction/topics`, async ({ request }) => { - await delay(600); - - const body = await request.json(); - const { title, description, category, deadline } = body; - - // 扣除创建费用 - const createCost = 100; - if (userAccount.balance < createCost) { - return HttpResponse.json( - { - success: false, - code: 400, - message: "积分不足,创建话题需要100积分", - data: null, - }, - { status: 400 } - ); - } - - userAccount.balance -= createCost; - userAccount.total_spent += createCost; - - const newTopic = { - id: topics.length + 1, - title, - description, - category: category || "general", - tags: [], - author_id: 1, - author_name: "当前用户", - author_avatar: "https://api.dicebear.com/7.x/avataaars/svg?seed=current", - created_at: new Date().toISOString(), - deadline: - deadline || - new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString(), - status: "active", - total_pool: createCost, - yes_total_shares: 0, - no_total_shares: 0, - yes_price: 500, - no_price: 500, - yes_lord_id: null, - no_lord_id: null, - yes_lord_name: null, - no_lord_name: null, - participants_count: 0, - comments_count: 0, - }; - - topics.unshift(newTopic); - - return HttpResponse.json({ - success: true, - code: 200, - message: "创建成功", - data: newTopic, - }); - }), - - // 结算话题 - http.post( - `/api/prediction/topics/:topicId/settle`, - async ({ params, request }) => { - await delay(500); - - const topicId = parseInt(params.topicId, 10); - const body = await request.json(); - const { result } = body; - - const topicIndex = topics.findIndex((t) => t.id === topicId); - if (topicIndex === -1) { - return HttpResponse.json( - { success: false, code: 404, message: "话题不存在", data: null }, - { status: 404 } - ); - } - - topics[topicIndex] = { - ...topics[topicIndex], - status: "settled", - settlement_result: result, - settled_at: new Date().toISOString(), - }; - - return HttpResponse.json({ - success: true, - code: 200, - message: "结算成功", - data: topics[topicIndex], - }); - } - ), - - // ==================== 交易 ==================== - - // 买入份额 - http.post(`/api/prediction/trade/buy`, async ({ request }) => { - await delay(500); - - const body = await request.json(); - const { topic_id, direction, shares } = body; - - const topic = topics.find((t) => t.id === topic_id); - if (!topic) { - return HttpResponse.json( - { success: false, code: 404, message: "话题不存在", data: null }, - { status: 404 } - ); - } - - // 计算成本 - const currentPrice = direction === "yes" ? topic.yes_price : topic.no_price; - const totalCost = Math.floor(currentPrice * shares); - const tax = Math.floor(totalCost * 0.02); - const finalCost = totalCost + tax; - - if (userAccount.balance < finalCost) { - return HttpResponse.json( - { success: false, code: 400, message: "积分不足", data: null }, - { status: 400 } - ); - } - - // 扣除积分 - userAccount.balance -= finalCost; - userAccount.frozen += totalCost; - userAccount.total_spent += finalCost; - - // 更新话题数据 - if (direction === "yes") { - topic.yes_total_shares += shares; - } else { - topic.no_total_shares += shares; - } - - // 重新计算价格 - const totalShares = topic.yes_total_shares + topic.no_total_shares; - topic.yes_price = Math.round((topic.yes_total_shares / totalShares) * 1000); - topic.no_price = Math.round((topic.no_total_shares / totalShares) * 1000); - topic.total_pool += tax; - topic.participants_count += 1; - - // 添加持仓 - const existingPosition = positions.find( - (p) => p.topic_id === topic_id && p.direction === direction - ); - if (existingPosition) { - existingPosition.shares += shares; - existingPosition.current_value = - existingPosition.shares * - (direction === "yes" ? topic.yes_price : topic.no_price); - } else { - positions.push({ - id: positions.length + 1, - topic_id, - topic_title: topic.title, - direction, - shares, - avg_cost: currentPrice, - current_price: direction === "yes" ? topic.yes_price : topic.no_price, - current_value: - shares * (direction === "yes" ? topic.yes_price : topic.no_price), - unrealized_pnl: 0, - acquired_at: new Date().toISOString(), - }); - } - - return HttpResponse.json({ - success: true, - code: 200, - message: "买入成功", - data: { - trade_id: Date.now(), - topic, - shares, - price: currentPrice, - total_cost: totalCost, - tax, - new_balance: userAccount.balance, - }, - }); - }), - - // 获取用户持仓 - http.get(`/api/prediction/positions`, async () => { - await delay(300); - return HttpResponse.json({ - success: true, - code: 200, - message: "success", - data: positions, - }); - }), - - // ==================== 评论 ==================== - - // 获取评论列表 - http.get(`/api/prediction/topics/:topicId/comments`, async ({ params }) => { - await delay(300); - - const topicId = parseInt(params.topicId, 10); - const topicComments = comments[topicId] || []; - - return HttpResponse.json({ - success: true, - code: 200, - message: "success", - data: { - comments: topicComments, - total: topicComments.length, - }, - }); - }), - - // 发表评论 - http.post( - `/api/prediction/topics/:topicId/comments`, - async ({ params, request }) => { - await delay(400); - - const topicId = parseInt(params.topicId, 10); - const body = await request.json(); - const { content, parent_id } = body; - - const newComment = { - id: Date.now(), - topic_id: topicId, - user: { - id: 1, - nickname: "当前用户", - username: "current_user", - avatar_url: "https://api.dicebear.com/7.x/avataaars/svg?seed=current", - }, - content, - parent_id: parent_id || null, - likes_count: 0, - is_liked: false, - is_lord: false, - total_investment: 0, - investment_shares: 0, - verification_status: null, - created_at: new Date().toISOString(), - }; - - if (!comments[topicId]) { - comments[topicId] = []; - } - comments[topicId].unshift(newComment); - - // 更新话题评论数 - const topic = topics.find((t) => t.id === topicId); - if (topic) { - topic.comments_count += 1; - } - - return HttpResponse.json({ - success: true, - code: 200, - message: "评论成功", - data: newComment, - }); - } - ), - - // 点赞评论 - http.post(`/api/prediction/comments/:commentId/like`, async ({ params }) => { - await delay(200); - - const commentId = parseInt(params.commentId, 10); - - // 在所有话题的评论中查找 - for (const topicId of Object.keys(comments)) { - const comment = comments[topicId].find((c) => c.id === commentId); - if (comment) { - comment.is_liked = !comment.is_liked; - comment.likes_count += comment.is_liked ? 1 : -1; - - return HttpResponse.json({ - success: true, - code: 200, - message: comment.is_liked ? "点赞成功" : "取消点赞", - data: { - is_liked: comment.is_liked, - likes_count: comment.likes_count, - }, - }); - } - } - - return HttpResponse.json( - { success: false, code: 404, message: "评论不存在", data: null }, - { status: 404 } - ); - }), - - // ==================== 观点IPO ==================== - - // 投资评论 - http.post( - `/api/prediction/comments/:commentId/invest`, - async ({ params, request }) => { - await delay(400); - - const commentId = parseInt(params.commentId, 10); - const body = await request.json(); - const { shares } = body; - - const investCost = shares * 100; // 每份100积分 - - if (userAccount.balance < investCost) { - return HttpResponse.json( - { success: false, code: 400, message: "积分不足", data: null }, - { status: 400 } - ); - } - - // 扣除积分 - userAccount.balance -= investCost; - userAccount.total_spent += investCost; - - // 更新评论投资数据 - for (const topicId of Object.keys(comments)) { - const comment = comments[topicId].find((c) => c.id === commentId); - if (comment) { - comment.total_investment += investCost; - comment.investment_shares += shares; - - return HttpResponse.json({ - success: true, - code: 200, - message: "投资成功", - data: { - comment, - invested_shares: shares, - invested_amount: investCost, - new_balance: userAccount.balance, - }, - }); - } - } - - return HttpResponse.json( - { success: false, code: 404, message: "评论不存在", data: null }, - { status: 404 } - ); - } - ), - - // 获取评论投资列表 - http.get( - `/api/prediction/comments/:commentId/investments`, - async ({ params }) => { - await delay(300); - - // 返回模拟的投资列表 - return HttpResponse.json({ - success: true, - code: 200, - message: "success", - data: { - investments: [ - { - user: mockUsers[0], - shares: 10, - amount: 1000, - invested_at: "2024-02-01T10:00:00Z", - }, - { - user: mockUsers[1], - shares: 5, - amount: 500, - invested_at: "2024-02-02T14:30:00Z", - }, - ], - total: 2, - }, - }); - } - ), - - // 验证评论 - http.post( - `/api/prediction/comments/:commentId/verify`, - async ({ params, request }) => { - await delay(400); - - const commentId = parseInt(params.commentId, 10); - const body = await request.json(); - const { result } = body; - - for (const topicId of Object.keys(comments)) { - const comment = comments[topicId].find((c) => c.id === commentId); - if (comment) { - comment.verification_status = result; - - return HttpResponse.json({ - success: true, - code: 200, - message: "验证成功", - data: comment, - }); - } - } - - return HttpResponse.json( - { success: false, code: 404, message: "评论不存在", data: null }, - { status: 404 } - ); - } - ), -]; - -export default predictionHandlers; diff --git a/src/mocks/handlers/simulation.js b/src/mocks/handlers/simulation.js deleted file mode 100644 index da33c5e1..00000000 --- a/src/mocks/handlers/simulation.js +++ /dev/null @@ -1,374 +0,0 @@ -// src/mocks/handlers/simulation.js -import { http, HttpResponse, delay } from 'msw'; -import { getCurrentUser } from '../data/users'; - -// 模拟网络延迟(毫秒) -const NETWORK_DELAY = 300; - -// 模拟交易账户数据 -let mockTradingAccount = { - account_id: 'sim_001', - account_name: '模拟交易账户', - initial_capital: 1000000, - available_cash: 850000, - frozen_cash: 0, - position_value: 150000, - total_assets: 1000000, - total_profit: 0, - total_profit_rate: 0, - daily_profit: 0, - daily_profit_rate: 0, - created_at: '2024-01-01T00:00:00Z', - updated_at: new Date().toISOString() -}; - -// 模拟持仓数据 -let mockPositions = [ - { - id: 1, - stock_code: '600036', - stock_name: '招商银行', - position_qty: 1000, - available_qty: 1000, - frozen_qty: 0, - avg_cost: 42.50, - current_price: 42.80, - market_value: 42800, - profit: 300, - profit_rate: 0.71, - today_profit: 100, - today_profit_rate: 0.23, - updated_at: new Date().toISOString() - }, - { - id: 2, - stock_code: '000001', - stock_name: '平安银行', - position_qty: 2000, - available_qty: 2000, - frozen_qty: 0, - avg_cost: 12.30, - current_price: 12.50, - market_value: 25000, - profit: 400, - profit_rate: 1.63, - today_profit: -50, - today_profit_rate: -0.20, - updated_at: new Date().toISOString() - } -]; - -// 模拟交易历史 -let mockOrders = [ - { - id: 1, - order_no: 'ORD20240101001', - stock_code: '600036', - stock_name: '招商银行', - order_type: 'BUY', - price_type: 'MARKET', - order_price: 42.50, - order_qty: 1000, - filled_qty: 1000, - filled_price: 42.50, - filled_amount: 42500, - commission: 12.75, - stamp_tax: 0, - transfer_fee: 0.42, - total_fee: 13.17, - status: 'FILLED', - reject_reason: null, - order_time: '2024-01-15T09:30:00Z', - filled_time: '2024-01-15T09:30:05Z' - }, - { - id: 2, - order_no: 'ORD20240102001', - stock_code: '000001', - stock_name: '平安银行', - order_type: 'BUY', - price_type: 'LIMIT', - order_price: 12.30, - order_qty: 2000, - filled_qty: 2000, - filled_price: 12.30, - filled_amount: 24600, - commission: 7.38, - stamp_tax: 0, - transfer_fee: 0.25, - total_fee: 7.63, - status: 'FILLED', - reject_reason: null, - order_time: '2024-01-16T10:15:00Z', - filled_time: '2024-01-16T10:15:10Z' - } -]; - -export const simulationHandlers = [ - // ==================== 获取模拟账户信息 ==================== - http.get('/api/simulation/account', async () => { - await delay(NETWORK_DELAY); - - const currentUser = getCurrentUser(); - - // 未登录时返回401 - if (!currentUser) { - return HttpResponse.json({ - success: false, - error: '未登录' - }, { status: 401 }); - } - - console.log('[Mock] 获取模拟账户信息:', currentUser); - - return HttpResponse.json({ - success: true, - data: mockTradingAccount - }); - }), - - // ==================== 获取持仓列表 ==================== - http.get('/api/simulation/positions', async () => { - await delay(NETWORK_DELAY); - - const currentUser = getCurrentUser(); - - if (!currentUser) { - return HttpResponse.json({ - success: false, - error: '未登录' - }, { status: 401 }); - } - - console.log('[Mock] 获取持仓列表'); - - return HttpResponse.json({ - success: true, - data: mockPositions - }); - }), - - // ==================== 获取交易订单历史 ==================== - http.get('/api/simulation/orders', async ({ request }) => { - await delay(NETWORK_DELAY); - - const currentUser = getCurrentUser(); - - if (!currentUser) { - return HttpResponse.json({ - success: false, - error: '未登录' - }, { status: 401 }); - } - - const url = new URL(request.url); - const limit = parseInt(url.searchParams.get('limit') || '100'); - - console.log('[Mock] 获取交易订单历史, limit:', limit); - - return HttpResponse.json({ - success: true, - data: mockOrders.slice(0, limit) - }); - }), - - // ==================== 下单(买入/卖出)==================== - http.post('/api/simulation/place-order', async ({ request }) => { - await delay(NETWORK_DELAY); - - const currentUser = getCurrentUser(); - - if (!currentUser) { - return HttpResponse.json({ - success: false, - error: '未登录' - }, { status: 401 }); - } - - const body = await request.json(); - console.log('[Mock] 下单请求:', body); - - const { stock_code, order_type, order_qty, price_type } = body; - - // 生成订单号 - const orderNo = 'ORD' + Date.now(); - - // 创建新订单 - const newOrder = { - id: mockOrders.length + 1, - order_no: orderNo, - stock_code: stock_code, - stock_name: '模拟股票', // 实际应该查询股票名称 - order_type: order_type, - price_type: price_type, - order_price: 0, - order_qty: order_qty, - filled_qty: order_qty, - filled_price: 0, - filled_amount: 0, - commission: 0, - stamp_tax: 0, - transfer_fee: 0, - total_fee: 0, - status: 'FILLED', - reject_reason: null, - order_time: new Date().toISOString(), - filled_time: new Date().toISOString() - }; - - // 添加到订单列表 - mockOrders.unshift(newOrder); - - return HttpResponse.json({ - success: true, - message: '下单成功', - data: { - order_no: orderNo, - order_id: newOrder.id - } - }); - }), - - // ==================== 撤销订单 ==================== - http.post('/api/simulation/cancel-order/:orderId', async ({ params }) => { - await delay(NETWORK_DELAY); - - const currentUser = getCurrentUser(); - - if (!currentUser) { - return HttpResponse.json({ - success: false, - error: '未登录' - }, { status: 401 }); - } - - const { orderId } = params; - console.log('[Mock] 撤销订单:', orderId); - - // 查找并更新订单状态 - const order = mockOrders.find(o => o.id.toString() === orderId || o.order_no === orderId); - if (order) { - order.status = 'CANCELLED'; - } - - return HttpResponse.json({ - success: true, - message: '撤单成功' - }); - }), - - // ==================== 获取资产统计数据 ==================== - http.get('/api/simulation/statistics', async ({ request }) => { - await delay(NETWORK_DELAY); - - const currentUser = getCurrentUser(); - - if (!currentUser) { - return HttpResponse.json({ - success: false, - error: '未登录' - }, { status: 401 }); - } - - const url = new URL(request.url); - const days = parseInt(url.searchParams.get('days') || '30'); - - console.log('[Mock] 获取资产统计, days:', days); - - // 生成模拟的资产历史数据 - const dailyReturns = []; - const baseAssets = 1000000; - - for (let i = 0; i < days; i++) { - const date = new Date(); - date.setDate(date.getDate() - (days - 1 - i)); - - // 生成随机波动 - const randomChange = (Math.random() - 0.5) * 0.02; // ±1% - const assets = baseAssets * (1 + randomChange * i / days); - - dailyReturns.push({ - date: date.toISOString().split('T')[0], - closing_assets: assets, - total_assets: assets, - daily_profit: assets - baseAssets, - daily_profit_rate: ((assets - baseAssets) / baseAssets * 100).toFixed(2) - }); - } - - return HttpResponse.json({ - success: true, - data: { - daily_returns: dailyReturns, - summary: { - total_profit: 0, - total_profit_rate: 0, - win_rate: 50, - max_drawdown: -5.2 - } - } - }); - }), - - // ==================== 获取交易记录 ==================== - http.get('/api/simulation/transactions', async ({ request }) => { - await delay(NETWORK_DELAY); - - const currentUser = getCurrentUser(); - - if (!currentUser) { - return HttpResponse.json({ - success: false, - error: '未登录' - }, { status: 401 }); - } - - const url = new URL(request.url); - const limit = parseInt(url.searchParams.get('limit') || '50'); - - console.log('[Mock] 获取交易记录, limit:', limit); - - // 返回已成交的订单作为交易记录 - const transactions = mockOrders - .filter(order => order.status === 'FILLED') - .slice(0, limit); - - return HttpResponse.json({ - success: true, - data: transactions - }); - }), - - // ==================== 搜索股票 ==================== - http.get('/api/stocks/search', async ({ request }) => { - await delay(200); - - const url = new URL(request.url); - const keyword = url.searchParams.get('q') || ''; - const limit = parseInt(url.searchParams.get('limit') || '10'); - - console.log('[Mock] 搜索股票:', keyword); - - // 模拟股票数据 - const allStocks = [ - { stock_code: '000001', stock_name: '平安银行', current_price: 12.50, pinyin_abbr: 'payh', security_type: 'A股', exchange: '深交所' }, - { stock_code: '000002', stock_name: '万科A', current_price: 8.32, pinyin_abbr: 'wka', security_type: 'A股', exchange: '深交所' }, - { stock_code: '600036', stock_name: '招商银行', current_price: 42.80, pinyin_abbr: 'zsyh', security_type: 'A股', exchange: '上交所' }, - { stock_code: '600519', stock_name: '贵州茅台', current_price: 1680.50, pinyin_abbr: 'gzmt', security_type: 'A股', exchange: '上交所' }, - { stock_code: '601318', stock_name: '中国平安', current_price: 45.20, pinyin_abbr: 'zgpa', security_type: 'A股', exchange: '上交所' }, - { stock_code: '688256', stock_name: '寒武纪', current_price: 1394.94, pinyin_abbr: 'hwj', security_type: 'A股', exchange: '上交所科创板' }, - ]; - - // 过滤股票 - const results = allStocks.filter(stock => - stock.stock_code.includes(keyword) || - stock.stock_name.includes(keyword) || - stock.pinyin_abbr.includes(keyword.toLowerCase()) - ).slice(0, limit); - - return HttpResponse.json({ - success: true, - data: results - }); - }) -]; diff --git a/src/mocks/handlers/stock.js b/src/mocks/handlers/stock.js deleted file mode 100644 index 4a69472b..00000000 --- a/src/mocks/handlers/stock.js +++ /dev/null @@ -1,690 +0,0 @@ -// src/mocks/handlers/stock.js -// 股票相关的 Mock Handlers - -import { http, HttpResponse } from 'msw'; -import { generateTimelineData, generateDailyData } from '../data/kline'; - -// 模拟延迟 -const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms)); - -// 常用字拼音首字母映射(简化版) -const PINYIN_MAP = { - '平': 'p', '安': 'a', '银': 'y', '行': 'h', '浦': 'p', '发': 'f', - '招': 'z', '商': 's', '兴': 'x', '业': 'y', '北': 'b', '京': 'j', - '农': 'n', '交': 'j', '通': 't', '工': 'g', '光': 'g', '大': 'd', - '建': 'j', '设': 's', '中': 'z', '信': 'x', '证': 'z', '券': 'q', - '国': 'g', '金': 'j', '海': 'h', '华': 'h', '泰': 't', '方': 'f', - '正': 'z', '新': 'x', '保': 'b', '险': 'x', '太': 't', '人': 'r', - '寿': 's', '泸': 'l', '州': 'z', '老': 'l', '窖': 'j', '古': 'g', - '井': 'j', '贡': 'g', '酒': 'j', '五': 'w', '粮': 'l', '液': 'y', - '贵': 'g', '茅': 'm', '台': 't', '青': 'q', '岛': 'd', '啤': 'p', - '水': 's', '坊': 'f', '今': 'j', '世': 's', '缘': 'y', '云': 'y', - '南': 'n', '白': 'b', '药': 'y', '长': 'c', '春': 'c', '高': 'g', - '科': 'k', '伦': 'l', '比': 'b', '亚': 'y', '迪': 'd', '恒': 'h', - '瑞': 'r', '医': 'y', '片': 'p', '仔': 'z', '癀': 'h', '明': 'm', - '康': 'k', '德': 'd', '讯': 'x', '东': 'd', '威': 'w', '视': 's', - '立': 'l', '精': 'j', '密': 'm', '电': 'd', '航': 'h', - '动': 'd', '力': 'l', '韦': 'w', '尔': 'e', '股': 'g', '份': 'f', - '万': 'w', '赣': 'g', '锋': 'f', '锂': 'l', '宁': 'n', '时': 's', - '代': 'd', '隆': 'l', '基': 'j', '绿': 'l', '能': 'n', - '筑': 'z', '汽': 'q', '车': 'c', '宇': 'y', '客': 'k', '上': 's', - '集': 'j', '团': 't', '广': 'g', '城': 'c', '侨': 'q', '夏': 'x', - '幸': 'x', '福': 'f', '地': 'd', '控': 'k', '美': 'm', '格': 'g', - '苏': 's', '智': 'z', '家': 'j', '易': 'y', '购': 'g', - '轩': 'x', '财': 'c', '富': 'f', '石': 's', '化': 'h', '学': 'x', - '山': 's', '黄': 'h', '螺': 'l', '泥': 'n', '神': 's', '油': 'y', - '联': 'l', '移': 'y', '伊': 'y', '利': 'l', '紫': 'z', '矿': 'k', - '天': 't', '味': 'w', '港': 'g', '微': 'w', - '技': 'j', '的': 'd', '器': 'q', '泊': 'b', '铁': 't', -}; - -// 生成拼音缩写 -const generatePinyinAbbr = (name) => { - return name.split('').map(char => PINYIN_MAP[char] || '').join(''); -}; - -// 生成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.map(s => ({ - ...s, - pinyin_abbr: generatePinyinAbbr(s.name) - })); -}; - -// 股票相关的 Handlers -export const stockHandlers = [ - // 搜索股票(个股中心页面使用)- 支持模糊搜索 - http.get('/api/stocks/search', async ({ request }) => { - await delay(200); - - const url = new URL(request.url); - const query = (url.searchParams.get('q') || '').toLowerCase().trim(); - const limit = parseInt(url.searchParams.get('limit') || '10'); - - console.log('[Mock Stock] 搜索股票:', { query, limit }); - - const stocks = generateStockList(); - - // 如果没有搜索词,返回空结果 - if (!query) { - return HttpResponse.json({ - success: true, - data: [] - }); - } - - // 模糊搜索:代码 + 名称 + 拼音缩写(不区分大小写) - const results = stocks.filter(s => { - const code = s.code.toLowerCase(); - const name = s.name.toLowerCase(); - const pinyin = (s.pinyin_abbr || '').toLowerCase(); - return code.includes(query) || name.includes(query) || pinyin.includes(query); - }); - - // 按相关性排序:完全匹配 > 开头匹配 > 包含匹配 - results.sort((a, b) => { - const aCode = a.code.toLowerCase(); - const bCode = b.code.toLowerCase(); - const aName = a.name.toLowerCase(); - const bName = b.name.toLowerCase(); - const aPinyin = (a.pinyin_abbr || '').toLowerCase(); - const bPinyin = (b.pinyin_abbr || '').toLowerCase(); - - // 计算匹配分数(包含拼音匹配) - const getScore = (code, name, pinyin) => { - if (code === query || name === query || pinyin === query) return 100; // 完全匹配 - if (code.startsWith(query)) return 80; // 代码开头 - if (pinyin.startsWith(query)) return 70; // 拼音开头 - if (name.startsWith(query)) return 60; // 名称开头 - if (code.includes(query)) return 40; // 代码包含 - if (pinyin.includes(query)) return 30; // 拼音包含 - if (name.includes(query)) return 20; // 名称包含 - return 0; - }; - - return getScore(bCode, bName, bPinyin) - getScore(aCode, aName, aPinyin); - }); - - // 返回格式化数据 - return HttpResponse.json({ - success: true, - data: results.slice(0, limit).map(s => ({ - stock_code: s.code, - stock_name: s.name, - pinyin_abbr: s.pinyin_abbr, - market: s.code.startsWith('6') ? 'SH' : 'SZ', - industry: ['银行', '证券', '保险', '白酒', '医药', '科技', '新能源', '汽车', '地产', '家电'][Math.floor(Math.random() * 10)], - change_pct: parseFloat((Math.random() * 10 - 3).toFixed(2)), - price: parseFloat((Math.random() * 100 + 5).toFixed(2)) - })) - }); - }), - - // 获取所有股票列表 - 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 } - ); - } - }), - - // 获取指数K线数据 - http.get('/api/index/:indexCode/kline', async ({ params, request }) => { - await delay(300); - - const { indexCode } = params; - const url = new URL(request.url); - const type = url.searchParams.get('type') || 'timeline'; - const eventTime = url.searchParams.get('event_time'); - - console.log('[Mock Stock] 获取指数K线数据:', { indexCode, type, eventTime }); - - try { - let data; - - if (type === 'timeline' || type === 'minute') { - // timeline 和 minute 都使用分时数据 - data = generateTimelineData(indexCode); - } else if (type === 'daily') { - data = generateDailyData(indexCode, 30); - } else { - // 其他类型也降级使用 timeline 数据 - console.log('[Mock Stock] 未知类型,降级使用 timeline:', type); - data = generateTimelineData(indexCode); - } - - return HttpResponse.json({ - success: true, - data: data, - index_code: indexCode, - type: type, - message: '获取成功' - }); - } catch (error) { - console.error('[Mock Stock] 获取K线数据失败:', error); - return HttpResponse.json( - { error: '获取K线数据失败' }, - { status: 500 } - ); - } - }), - - // 获取股票K线数据 - http.get('/api/stock/:stockCode/kline', async ({ params, request }) => { - await delay(300); - - const { stockCode } = params; - const url = new URL(request.url); - const type = url.searchParams.get('type') || 'timeline'; - const eventTime = url.searchParams.get('event_time'); - - console.log('[Mock Stock] 获取股票K线数据:', { stockCode, type, eventTime }); - - try { - let data; - - if (type === 'timeline') { - // 股票使用指数的数据生成逻辑,但价格基数不同 - data = generateTimelineData('000001.SH'); // 可以根据股票代码调整 - } else if (type === 'daily') { - data = generateDailyData('000001.SH', 30); - } else { - return HttpResponse.json( - { error: '不支持的类型' }, - { status: 400 } - ); - } - - return HttpResponse.json({ - success: true, - data: data, - stock_code: stockCode, - type: type, - message: '获取成功' - }); - } catch (error) { - console.error('[Mock Stock] 获取股票K线数据失败:', error); - return HttpResponse.json( - { error: '获取K线数据失败' }, - { status: 500 } - ); - } - }), - - // 批量获取股票K线数据 - http.post('/api/stock/batch-kline', async ({ request }) => { - await delay(400); - - try { - const body = await request.json(); - const { codes, type = 'timeline', event_time } = body; - - console.log('[Mock Stock] 批量获取K线数据:', { - stockCount: codes?.length, - type, - eventTime: event_time - }); - - if (!codes || !Array.isArray(codes) || codes.length === 0) { - return HttpResponse.json( - { error: '股票代码列表不能为空' }, - { status: 400 } - ); - } - - // 为每只股票生成数据 - const batchData = {}; - codes.forEach(stockCode => { - let data; - if (type === 'timeline') { - data = generateTimelineData('000001.SH'); - } else if (type === 'daily') { - data = generateDailyData('000001.SH', 60); - } else { - data = []; - } - - batchData[stockCode] = { - success: true, - data: data, - stock_code: stockCode - }; - }); - - return HttpResponse.json({ - success: true, - data: batchData, - type: type, - message: '批量获取成功' - }); - } catch (error) { - console.error('[Mock Stock] 批量获取K线数据失败:', error); - return HttpResponse.json( - { error: '批量获取K线数据失败' }, - { status: 500 } - ); - } - }), - - // 获取股票业绩预告 - http.get('/api/stock/:stockCode/forecast', async ({ params }) => { - await delay(200); - - const { stockCode } = params; - console.log('[Mock Stock] 获取业绩预告:', { stockCode }); - - // 生成股票列表用于查找名称 - const stockList = generateStockList(); - const stockInfo = stockList.find(s => s.code === stockCode.replace(/\.(SH|SZ)$/i, '')); - const stockName = stockInfo?.name || `股票${stockCode}`; - - // 业绩预告类型列表 - const forecastTypes = ['预增', '预减', '略增', '略减', '扭亏', '续亏', '首亏', '续盈']; - - // 生成业绩预告数据 - const forecasts = [ - { - forecast_type: '预增', - report_date: '2024年年报', - content: `${stockName}预计2024年度归属于上市公司股东的净利润为58亿元至62亿元,同比增长10%至17%。`, - reason: '报告期内,公司主营业务收入稳步增长,产品结构持续优化,毛利率提升;同时公司加大研发投入,新产品市场表现良好。', - change_range: { - lower: 10, - upper: 17 - }, - publish_date: '2024-10-15' - }, - { - forecast_type: '略增', - report_date: '2024年三季报', - content: `${stockName}预计2024年1-9月归属于上市公司股东的净利润为42亿元至45亿元,同比增长5%至12%。`, - reason: '公司积极拓展市场渠道,销售规模持续扩大,经营效益稳步提升。', - change_range: { - lower: 5, - upper: 12 - }, - publish_date: '2024-07-12' - }, - { - forecast_type: forecastTypes[Math.floor(Math.random() * forecastTypes.length)], - report_date: '2024年中报', - content: `${stockName}预计2024年上半年归属于上市公司股东的净利润为28亿元至30亿元。`, - reason: '受益于行业景气度回升及公司降本增效措施效果显现,经营业绩同比有所改善。', - change_range: { - lower: 3, - upper: 8 - }, - publish_date: '2024-04-20' - } - ]; - - return HttpResponse.json({ - success: true, - data: { - stock_code: stockCode, - stock_name: stockName, - forecasts: forecasts - } - }); - }), - - // 获取股票报价(批量) - http.post('/api/stock/quotes', async ({ request }) => { - await delay(200); - - try { - const body = await request.json(); - const { codes, event_time } = body; - - console.log('[Mock Stock] 获取股票报价:', { - stockCount: codes?.length, - eventTime: event_time - }); - - if (!codes || !Array.isArray(codes) || codes.length === 0) { - return HttpResponse.json( - { success: false, error: '股票代码列表不能为空' }, - { status: 400 } - ); - } - - // 生成股票列表用于查找名称 - const stockList = generateStockList(); - const stockMap = {}; - stockList.forEach(s => { - stockMap[s.code] = s.name; - }); - - // 行业和指数映射表 - const stockIndustryMap = { - '000001': { industry_l1: '金融', industry: '银行', index_tags: ['沪深300', '上证180'] }, - '600519': { industry_l1: '消费', industry: '白酒', index_tags: ['沪深300', '上证50'] }, - '300750': { industry_l1: '工业', industry: '电池', index_tags: ['创业板50'] }, - '601318': { industry_l1: '金融', industry: '保险', index_tags: ['沪深300', '上证50'] }, - '600036': { industry_l1: '金融', industry: '银行', index_tags: ['沪深300', '上证50'] }, - '000858': { industry_l1: '消费', industry: '白酒', index_tags: ['沪深300'] }, - '002594': { industry_l1: '汽车', industry: '乘用车', index_tags: ['沪深300', '创业板指'] }, - }; - - const defaultIndustries = [ - { industry_l1: '科技', industry: '软件' }, - { industry_l1: '医药', industry: '化学制药' }, - { industry_l1: '消费', industry: '食品' }, - { industry_l1: '金融', industry: '证券' }, - { industry_l1: '工业', industry: '机械' }, - ]; - - // 为每只股票生成报价数据 - const quotesData = {}; - codes.forEach(stockCode => { - // 生成基础价格(10-200之间) - const basePrice = parseFloat((Math.random() * 190 + 10).toFixed(2)); - // 涨跌幅(-10% 到 +10%) - const changePercent = parseFloat((Math.random() * 20 - 10).toFixed(2)); - // 涨跌额 - const change = parseFloat((basePrice * changePercent / 100).toFixed(2)); - // 昨收 - const prevClose = parseFloat((basePrice - change).toFixed(2)); - - // 获取行业和指数信息 - const codeWithoutSuffix = stockCode.replace(/\.(SH|SZ)$/i, ''); - const industryInfo = stockIndustryMap[codeWithoutSuffix] || - defaultIndustries[Math.floor(Math.random() * defaultIndustries.length)]; - - quotesData[stockCode] = { - code: stockCode, - name: stockMap[stockCode] || `股票${stockCode}`, - price: basePrice, - change: change, - change_percent: changePercent, - prev_close: prevClose, - open: parseFloat((prevClose * (1 + (Math.random() * 0.02 - 0.01))).toFixed(2)), - high: parseFloat((basePrice * (1 + Math.random() * 0.05)).toFixed(2)), - low: parseFloat((basePrice * (1 - Math.random() * 0.05)).toFixed(2)), - volume: Math.floor(Math.random() * 100000000), - amount: parseFloat((Math.random() * 10000000000).toFixed(2)), - market: stockCode.startsWith('6') ? 'SH' : 'SZ', - update_time: new Date().toISOString(), - // 行业和指数标签 - industry_l1: industryInfo.industry_l1, - industry: industryInfo.industry, - index_tags: industryInfo.index_tags || [], - // 关键指标 - pe: parseFloat((Math.random() * 50 + 5).toFixed(2)), - eps: parseFloat((Math.random() * 5 + 0.1).toFixed(3)), - pb: parseFloat((Math.random() * 8 + 0.5).toFixed(2)), - market_cap: `${(Math.random() * 5000 + 100).toFixed(0)}亿`, - week52_low: parseFloat((basePrice * 0.7).toFixed(2)), - week52_high: parseFloat((basePrice * 1.3).toFixed(2)), - // 主力动态 - main_net_inflow: parseFloat((Math.random() * 10 - 5).toFixed(2)), - institution_holding: parseFloat((Math.random() * 50 + 10).toFixed(2)), - buy_ratio: parseFloat((Math.random() * 40 + 30).toFixed(2)), - sell_ratio: parseFloat((100 - (Math.random() * 40 + 30)).toFixed(2)) - }; - }); - - return HttpResponse.json({ - success: true, - data: quotesData, - message: '获取成功' - }); - } catch (error) { - console.error('[Mock Stock] 获取股票报价失败:', error); - return HttpResponse.json( - { success: false, error: '获取股票报价失败' }, - { status: 500 } - ); - } - }), - - // 获取股票详细行情(quote-detail) - http.get('/api/stock/:stockCode/quote-detail', async ({ params }) => { - await delay(200); - - const { stockCode } = params; - console.log('[Mock Stock] 获取股票详细行情:', { stockCode }); - - const stocks = generateStockList(); - const codeWithoutSuffix = stockCode.replace(/\.(SH|SZ)$/i, ''); - const stockInfo = stocks.find(s => s.code === codeWithoutSuffix); - const stockName = stockInfo?.name || `股票${stockCode}`; - - // 生成基础价格(10-200之间) - const basePrice = parseFloat((Math.random() * 190 + 10).toFixed(2)); - // 涨跌幅(-10% 到 +10%) - const changePercent = parseFloat((Math.random() * 20 - 10).toFixed(2)); - // 涨跌额 - const change = parseFloat((basePrice * changePercent / 100).toFixed(2)); - // 昨收 - const prevClose = parseFloat((basePrice - change).toFixed(2)); - - return HttpResponse.json({ - success: true, - data: { - stock_code: stockCode, - stock_name: stockName, - price: basePrice, - change: change, - change_percent: changePercent, - prev_close: prevClose, - open: parseFloat((prevClose * (1 + (Math.random() * 0.02 - 0.01))).toFixed(2)), - high: parseFloat((basePrice * (1 + Math.random() * 0.05)).toFixed(2)), - low: parseFloat((basePrice * (1 - Math.random() * 0.05)).toFixed(2)), - volume: Math.floor(Math.random() * 100000000), - amount: parseFloat((Math.random() * 10000000000).toFixed(2)), - turnover_rate: parseFloat((Math.random() * 10).toFixed(2)), - amplitude: parseFloat((Math.random() * 8).toFixed(2)), - market: stockCode.startsWith('6') ? 'SH' : 'SZ', - update_time: new Date().toISOString(), - // 买卖盘口 - bid1: parseFloat((basePrice * 0.998).toFixed(2)), - bid1_volume: Math.floor(Math.random() * 10000), - bid2: parseFloat((basePrice * 0.996).toFixed(2)), - bid2_volume: Math.floor(Math.random() * 10000), - bid3: parseFloat((basePrice * 0.994).toFixed(2)), - bid3_volume: Math.floor(Math.random() * 10000), - bid4: parseFloat((basePrice * 0.992).toFixed(2)), - bid4_volume: Math.floor(Math.random() * 10000), - bid5: parseFloat((basePrice * 0.990).toFixed(2)), - bid5_volume: Math.floor(Math.random() * 10000), - ask1: parseFloat((basePrice * 1.002).toFixed(2)), - ask1_volume: Math.floor(Math.random() * 10000), - ask2: parseFloat((basePrice * 1.004).toFixed(2)), - ask2_volume: Math.floor(Math.random() * 10000), - ask3: parseFloat((basePrice * 1.006).toFixed(2)), - ask3_volume: Math.floor(Math.random() * 10000), - ask4: parseFloat((basePrice * 1.008).toFixed(2)), - ask4_volume: Math.floor(Math.random() * 10000), - ask5: parseFloat((basePrice * 1.010).toFixed(2)), - ask5_volume: Math.floor(Math.random() * 10000), - // 关键指标 - pe: parseFloat((Math.random() * 50 + 5).toFixed(2)), - pb: parseFloat((Math.random() * 8 + 0.5).toFixed(2)), - eps: parseFloat((Math.random() * 5 + 0.1).toFixed(3)), - market_cap: `${(Math.random() * 5000 + 100).toFixed(0)}亿`, - circulating_market_cap: `${(Math.random() * 3000 + 50).toFixed(0)}亿`, - total_shares: `${(Math.random() * 100 + 10).toFixed(2)}亿`, - circulating_shares: `${(Math.random() * 80 + 5).toFixed(2)}亿`, - week52_high: parseFloat((basePrice * 1.3).toFixed(2)), - week52_low: parseFloat((basePrice * 0.7).toFixed(2)) - }, - message: '获取成功' - }); - }), - - // FlexScreen 行情数据 - http.get('/api/flex-screen/quotes', async ({ request }) => { - await delay(200); - - const url = new URL(request.url); - const codes = url.searchParams.get('codes')?.split(',') || []; - - console.log('[Mock Stock] 获取 FlexScreen 行情:', { codes }); - - // 默认主要指数 - const defaultIndices = ['000001', '399001', '399006']; - const targetCodes = codes.length > 0 ? codes : defaultIndices; - - const indexData = { - '000001': { name: '上证指数', basePrice: 3200 }, - '399001': { name: '深证成指', basePrice: 10500 }, - '399006': { name: '创业板指', basePrice: 2100 }, - '000300': { name: '沪深300', basePrice: 3800 }, - '000016': { name: '上证50', basePrice: 2600 }, - '000905': { name: '中证500', basePrice: 5800 }, - }; - - const quotesData = {}; - targetCodes.forEach(code => { - const codeWithoutSuffix = code.replace(/\.(SH|SZ)$/i, ''); - const info = indexData[codeWithoutSuffix] || { name: `指数${code}`, basePrice: 3000 }; - - const changePercent = parseFloat((Math.random() * 4 - 2).toFixed(2)); - const price = parseFloat((info.basePrice * (1 + changePercent / 100)).toFixed(2)); - const change = parseFloat((price - info.basePrice).toFixed(2)); - - quotesData[code] = { - code: code, - name: info.name, - price: price, - change: change, - change_percent: changePercent, - prev_close: info.basePrice, - open: parseFloat((info.basePrice * (1 + (Math.random() * 0.01 - 0.005))).toFixed(2)), - high: parseFloat((price * (1 + Math.random() * 0.01)).toFixed(2)), - low: parseFloat((price * (1 - Math.random() * 0.01)).toFixed(2)), - volume: parseFloat((Math.random() * 5000 + 2000).toFixed(2)), // 亿手 - amount: parseFloat((Math.random() * 8000 + 3000).toFixed(2)), // 亿元 - update_time: new Date().toISOString() - }; - }); - - return HttpResponse.json({ - success: true, - data: quotesData, - message: '获取成功' - }); - }), -]; diff --git a/src/mocks/handlers/stock.ts b/src/mocks/handlers/stock.ts deleted file mode 100644 index af4a35b1..00000000 --- a/src/mocks/handlers/stock.ts +++ /dev/null @@ -1,932 +0,0 @@ -// src/mocks/handlers/stock.ts -// 股票相关的 Mock Handlers - -import { http, HttpResponse } from 'msw'; -import { generateTimelineData, generateDailyData } from '../data/kline'; - -// 调试日志:确认模块加载 -console.log('[Mock] stockHandlers 模块已加载'); - -// ============ 类型定义 ============ - -/** 拼音映射类型 */ -type PinyinMap = Record; - -/** 股票基础信息 */ -interface StockInfo { - code: string; - name: string; - pinyin_abbr?: string; -} - -/** 搜索结果项 */ -interface SearchResultItem { - stock_code: string; - stock_name: string; - pinyin_abbr?: string; - market: 'SH' | 'SZ'; - industry: string; - change_pct: number; - price: number; -} - -/** 股票报价 */ -interface StockQuote { - code: string; - name: string; - price: number; - change: number; - change_percent: number; - prev_close: number; - open: number; - high: number; - low: number; - volume: number; - amount: number; - market: string; - update_time: string; - industry_l1?: string; - industry?: string; - index_tags?: string[]; - pe?: number; - eps?: number; - pb?: number; - market_cap?: string; - week52_low?: number; - week52_high?: number; - main_net_inflow?: number; - institution_holding?: number; - buy_ratio?: number; - sell_ratio?: number; -} - -/** 股票详细行情 */ -interface StockQuoteDetail extends StockQuote { - stock_code: string; - stock_name: string; - turnover_rate: number; - amplitude: number; - bid1: number; - bid1_volume: number; - bid2: number; - bid2_volume: number; - bid3: number; - bid3_volume: number; - bid4: number; - bid4_volume: number; - bid5: number; - bid5_volume: number; - ask1: number; - ask1_volume: number; - ask2: number; - ask2_volume: number; - ask3: number; - ask3_volume: number; - ask4: number; - ask4_volume: number; - ask5: number; - ask5_volume: number; - circulating_market_cap: string; - total_shares: string; - circulating_shares: string; -} - -/** 业绩预告 */ -interface ForecastItem { - forecast_type: string; - report_date: string; - content: string; - reason: string; - change_range: { lower: number; upper: number }; - publish_date: string; -} - -/** K线批量请求体 */ -interface BatchKlineRequest { - codes: string[]; - type?: 'timeline' | 'daily'; - event_time?: string; -} - -/** 股票报价批量请求体 */ -interface QuotesRequest { - codes: string[]; - event_time?: string; -} - -/** 行业信息 */ -interface IndustryInfo { - industry_l1: string; - industry: string; - index_tags?: string[]; -} - -/** 指数基础信息 */ -interface IndexInfo { - name: string; - basePrice: number; -} - -// ============ 常量配置 ============ - -/** 模拟延迟 */ -const delay = (ms: number): Promise => new Promise((resolve) => setTimeout(resolve, ms)); - -/** 常用字拼音首字母映射(简化版) */ -const PINYIN_MAP: Readonly = { - 平: 'p', - 安: 'a', - 银: 'y', - 行: 'h', - 浦: 'p', - 发: 'f', - 招: 'z', - 商: 's', - 兴: 'x', - 业: 'y', - 北: 'b', - 京: 'j', - 农: 'n', - 交: 'j', - 通: 't', - 工: 'g', - 光: 'g', - 大: 'd', - 建: 'j', - 设: 's', - 中: 'z', - 信: 'x', - 证: 'z', - 券: 'q', - 国: 'g', - 金: 'j', - 海: 'h', - 华: 'h', - 泰: 't', - 方: 'f', - 正: 'z', - 新: 'x', - 保: 'b', - 险: 'x', - 太: 't', - 人: 'r', - 寿: 's', - 泸: 'l', - 州: 'z', - 老: 'l', - 窖: 'j', - 古: 'g', - 井: 'j', - 贡: 'g', - 酒: 'j', - 五: 'w', - 粮: 'l', - 液: 'y', - 贵: 'g', - 茅: 'm', - 台: 't', - 青: 'q', - 岛: 'd', - 啤: 'p', - 水: 's', - 坊: 'f', - 今: 'j', - 世: 's', - 缘: 'y', - 云: 'y', - 南: 'n', - 白: 'b', - 药: 'y', - 长: 'c', - 春: 'c', - 高: 'g', - 科: 'k', - 伦: 'l', - 比: 'b', - 亚: 'y', - 迪: 'd', - 恒: 'h', - 瑞: 'r', - 医: 'y', - 片: 'p', - 仔: 'z', - 癀: 'h', - 明: 'm', - 康: 'k', - 德: 'd', - 讯: 'x', - 东: 'd', - 威: 'w', - 视: 's', - 立: 'l', - 精: 'j', - 密: 'm', - 电: 'd', - 航: 'h', - 动: 'd', - 力: 'l', - 韦: 'w', - 尔: 'e', - 股: 'g', - 份: 'f', - 万: 'w', - 赣: 'g', - 锋: 'f', - 锂: 'l', - 宁: 'n', - 时: 's', - 代: 'd', - 隆: 'l', - 基: 'j', - 绿: 'l', - 能: 'n', - 筑: 'z', - 汽: 'q', - 车: 'c', - 宇: 'y', - 客: 'k', - 上: 's', - 集: 'j', - 团: 't', - 广: 'g', - 城: 'c', - 侨: 'q', - 夏: 'x', - 幸: 'x', - 福: 'f', - 地: 'd', - 控: 'k', - 美: 'm', - 格: 'g', - 苏: 's', - 智: 'z', - 家: 'j', - 易: 'y', - 购: 'g', - 轩: 'x', - 财: 'c', - 富: 'f', - 石: 's', - 化: 'h', - 学: 'x', - 山: 's', - 黄: 'h', - 螺: 'l', - 泥: 'n', - 神: 's', - 油: 'y', - 联: 'l', - 移: 'y', - 伊: 'y', - 利: 'l', - 紫: 'z', - 矿: 'k', - 天: 't', - 味: 'w', - 港: 'g', - 微: 'w', - 技: 'j', - 的: 'd', - 器: 'q', - 泊: 'b', - 铁: 't', -} as const; - -// ============ 工具函数 ============ - -/** 生成拼音缩写 */ -const generatePinyinAbbr = (name: string): string => { - return name - .split('') - .map((char) => PINYIN_MAP[char] || '') - .join(''); -}; - -/** 生成A股主要股票数据(包含各大指数成分股) */ -const generateStockList = (): StockInfo[] => { - const stocks: Array<{ code: string; name: string }> = [ - // 银行 - { 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: '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.map((s) => ({ - ...s, - pinyin_abbr: generatePinyinAbbr(s.name), - })); -}; - -// ============ Mock Handlers ============ - -export const stockHandlers = [ - // 搜索股票(个股中心页面使用)- 支持模糊搜索 - http.get('/api/stocks/search', async ({ request }) => { - console.log('[Mock Stock] ✅ 搜索请求已被 MSW 拦截:', request.url); - await delay(200); - - const url = new URL(request.url); - const query = (url.searchParams.get('q') || '').toLowerCase().trim(); - const limit = parseInt(url.searchParams.get('limit') || '10'); - - console.log('[Mock Stock] 搜索股票:', { query, limit }); - - const stocks = generateStockList(); - - // 如果没有搜索词,返回空结果 - if (!query) { - return HttpResponse.json({ - success: true, - data: [], - }); - } - - // 模糊搜索:代码 + 名称 + 拼音缩写(不区分大小写) - const results = stocks.filter((s) => { - const code = s.code.toLowerCase(); - const name = s.name.toLowerCase(); - const pinyin = (s.pinyin_abbr || '').toLowerCase(); - return code.includes(query) || name.includes(query) || pinyin.includes(query); - }); - - // 按相关性排序:完全匹配 > 开头匹配 > 包含匹配 - results.sort((a, b) => { - const aCode = a.code.toLowerCase(); - const bCode = b.code.toLowerCase(); - const aName = a.name.toLowerCase(); - const bName = b.name.toLowerCase(); - const aPinyin = (a.pinyin_abbr || '').toLowerCase(); - const bPinyin = (b.pinyin_abbr || '').toLowerCase(); - - // 计算匹配分数(包含拼音匹配) - const getScore = (code: string, name: string, pinyin: string): number => { - if (code === query || name === query || pinyin === query) return 100; // 完全匹配 - if (code.startsWith(query)) return 80; // 代码开头 - if (pinyin.startsWith(query)) return 70; // 拼音开头 - if (name.startsWith(query)) return 60; // 名称开头 - if (code.includes(query)) return 40; // 代码包含 - if (pinyin.includes(query)) return 30; // 拼音包含 - if (name.includes(query)) return 20; // 名称包含 - return 0; - }; - - return getScore(bCode, bName, bPinyin) - getScore(aCode, aName, aPinyin); - }); - - // 返回格式化数据 - const searchResults: SearchResultItem[] = results.slice(0, limit).map((s) => ({ - stock_code: s.code, - stock_name: s.name, - pinyin_abbr: s.pinyin_abbr, - market: (s.code.startsWith('6') ? 'SH' : 'SZ') as 'SH' | 'SZ', - industry: ['银行', '证券', '保险', '白酒', '医药', '科技', '新能源', '汽车', '地产', '家电'][Math.floor(Math.random() * 10)], - change_pct: parseFloat((Math.random() * 10 - 3).toFixed(2)), - price: parseFloat((Math.random() * 100 + 5).toFixed(2)), - })); - - return HttpResponse.json({ - success: true, - data: searchResults, - }); - }), - - // 获取所有股票列表 - 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 }); - } - }), - - // 获取指数K线数据 - http.get('/api/index/:indexCode/kline', async ({ params, request }) => { - await delay(300); - - const { indexCode } = params; - const url = new URL(request.url); - const type = url.searchParams.get('type') || 'timeline'; - const eventTime = url.searchParams.get('event_time'); - - console.log('[Mock Stock] 获取指数K线数据:', { indexCode, type, eventTime }); - - try { - let data; - - if (type === 'timeline' || type === 'minute') { - // timeline 和 minute 都使用分时数据 - data = generateTimelineData(indexCode as string); - } else if (type === 'daily') { - data = generateDailyData(indexCode as string, 30); - } else { - // 其他类型也降级使用 timeline 数据 - console.log('[Mock Stock] 未知类型,降级使用 timeline:', type); - data = generateTimelineData(indexCode as string); - } - - return HttpResponse.json({ - success: true, - data: data, - index_code: indexCode, - type: type, - message: '获取成功', - }); - } catch (error) { - console.error('[Mock Stock] 获取K线数据失败:', error); - return HttpResponse.json({ error: '获取K线数据失败' }, { status: 500 }); - } - }), - - // 获取股票K线数据 - http.get('/api/stock/:stockCode/kline', async ({ params, request }) => { - await delay(300); - - const { stockCode } = params; - const url = new URL(request.url); - const type = url.searchParams.get('type') || 'timeline'; - const eventTime = url.searchParams.get('event_time'); - - console.log('[Mock Stock] 获取股票K线数据:', { stockCode, type, eventTime }); - - try { - let data; - - if (type === 'timeline' || type === 'minute') { - // minute 和 timeline 使用相同的分时数据 - data = generateTimelineData('000001.SH'); - } else if (type === 'daily') { - data = generateDailyData('000001.SH', 30); - } else { - return HttpResponse.json({ error: '不支持的类型' }, { status: 400 }); - } - - return HttpResponse.json({ - success: true, - data: data, - stock_code: stockCode, - type: type, - message: '获取成功', - }); - } catch (error) { - console.error('[Mock Stock] 获取股票K线数据失败:', error); - return HttpResponse.json({ error: '获取K线数据失败' }, { status: 500 }); - } - }), - - // 批量获取股票K线数据 - http.post('/api/stock/batch-kline', async ({ request }) => { - await delay(400); - - try { - const body = (await request.json()) as BatchKlineRequest; - const { codes, type = 'timeline', event_time } = body; - - console.log('[Mock Stock] 批量获取K线数据:', { - stockCount: codes?.length, - type, - eventTime: event_time, - }); - - if (!codes || !Array.isArray(codes) || codes.length === 0) { - return HttpResponse.json({ error: '股票代码列表不能为空' }, { status: 400 }); - } - - // 为每只股票生成数据 - const batchData: Record = {}; - codes.forEach((stockCode) => { - let data; - if (type === 'timeline') { - data = generateTimelineData('000001.SH'); - } else if (type === 'daily') { - data = generateDailyData('000001.SH', 60); - } else { - data = []; - } - - batchData[stockCode] = { - success: true, - data: data, - stock_code: stockCode, - }; - }); - - return HttpResponse.json({ - success: true, - data: batchData, - type: type, - message: '批量获取成功', - }); - } catch (error) { - console.error('[Mock Stock] 批量获取K线数据失败:', error); - return HttpResponse.json({ error: '批量获取K线数据失败' }, { status: 500 }); - } - }), - - // 获取股票业绩预告 - http.get('/api/stock/:stockCode/forecast', async ({ params }) => { - await delay(200); - - const { stockCode } = params; - console.log('[Mock Stock] 获取业绩预告:', { stockCode }); - - // 生成股票列表用于查找名称 - const stockList = generateStockList(); - const stockInfo = stockList.find((s) => s.code === (stockCode as string).replace(/\.(SH|SZ)$/i, '')); - const stockName = stockInfo?.name || `股票${stockCode}`; - - // 业绩预告类型列表 - const forecastTypes = ['预增', '预减', '略增', '略减', '扭亏', '续亏', '首亏', '续盈']; - - // 生成业绩预告数据 - const forecasts: ForecastItem[] = [ - { - forecast_type: '预增', - report_date: '2024年年报', - content: `${stockName}预计2024年度归属于上市公司股东的净利润为58亿元至62亿元,同比增长10%至17%。`, - reason: '报告期内,公司主营业务收入稳步增长,产品结构持续优化,毛利率提升;同时公司加大研发投入,新产品市场表现良好。', - change_range: { - lower: 10, - upper: 17, - }, - publish_date: '2024-10-15', - }, - { - forecast_type: '略增', - report_date: '2024年三季报', - content: `${stockName}预计2024年1-9月归属于上市公司股东的净利润为42亿元至45亿元,同比增长5%至12%。`, - reason: '公司积极拓展市场渠道,销售规模持续扩大,经营效益稳步提升。', - change_range: { - lower: 5, - upper: 12, - }, - publish_date: '2024-07-12', - }, - { - forecast_type: forecastTypes[Math.floor(Math.random() * forecastTypes.length)], - report_date: '2024年中报', - content: `${stockName}预计2024年上半年归属于上市公司股东的净利润为28亿元至30亿元。`, - reason: '受益于行业景气度回升及公司降本增效措施效果显现,经营业绩同比有所改善。', - change_range: { - lower: 3, - upper: 8, - }, - publish_date: '2024-04-20', - }, - ]; - - return HttpResponse.json({ - success: true, - data: { - stock_code: stockCode, - stock_name: stockName, - forecasts: forecasts, - }, - }); - }), - - // 获取股票报价(批量) - http.post('/api/stock/quotes', async ({ request }) => { - await delay(200); - - try { - const body = (await request.json()) as QuotesRequest; - const { codes, event_time } = body; - - console.log('[Mock Stock] 获取股票报价:', { - stockCount: codes?.length, - eventTime: event_time, - }); - - if (!codes || !Array.isArray(codes) || codes.length === 0) { - return HttpResponse.json({ success: false, error: '股票代码列表不能为空' }, { status: 400 }); - } - - // 生成股票列表用于查找名称 - const stockList = generateStockList(); - const stockMap: Record = {}; - stockList.forEach((s) => { - stockMap[s.code] = s.name; - }); - - // 行业和指数映射表 - const stockIndustryMap: Record = { - '000001': { industry_l1: '金融', industry: '银行', index_tags: ['沪深300', '上证180'] }, - '600519': { industry_l1: '消费', industry: '白酒', index_tags: ['沪深300', '上证50'] }, - '300750': { industry_l1: '工业', industry: '电池', index_tags: ['创业板50'] }, - '601318': { industry_l1: '金融', industry: '保险', index_tags: ['沪深300', '上证50'] }, - '600036': { industry_l1: '金融', industry: '银行', index_tags: ['沪深300', '上证50'] }, - '000858': { industry_l1: '消费', industry: '白酒', index_tags: ['沪深300'] }, - '002594': { industry_l1: '汽车', industry: '乘用车', index_tags: ['沪深300', '创业板指'] }, - }; - - const defaultIndustries: IndustryInfo[] = [ - { industry_l1: '科技', industry: '软件' }, - { industry_l1: '医药', industry: '化学制药' }, - { industry_l1: '消费', industry: '食品' }, - { industry_l1: '金融', industry: '证券' }, - { industry_l1: '工业', industry: '机械' }, - ]; - - // 为每只股票生成报价数据 - const quotesData: Record = {}; - codes.forEach((stockCode) => { - // 生成基础价格(10-200之间) - const basePrice = parseFloat((Math.random() * 190 + 10).toFixed(2)); - // 涨跌幅(-10% 到 +10%) - const changePercent = parseFloat((Math.random() * 20 - 10).toFixed(2)); - // 涨跌额 - const change = parseFloat(((basePrice * changePercent) / 100).toFixed(2)); - // 昨收 - const prevClose = parseFloat((basePrice - change).toFixed(2)); - - // 获取行业和指数信息 - const codeWithoutSuffix = stockCode.replace(/\.(SH|SZ)$/i, ''); - const industryInfo = stockIndustryMap[codeWithoutSuffix] || defaultIndustries[Math.floor(Math.random() * defaultIndustries.length)]; - - quotesData[stockCode] = { - code: stockCode, - name: stockMap[stockCode] || `股票${stockCode}`, - price: basePrice, - change: change, - change_percent: changePercent, - prev_close: prevClose, - open: parseFloat((prevClose * (1 + (Math.random() * 0.02 - 0.01))).toFixed(2)), - high: parseFloat((basePrice * (1 + Math.random() * 0.05)).toFixed(2)), - low: parseFloat((basePrice * (1 - Math.random() * 0.05)).toFixed(2)), - volume: Math.floor(Math.random() * 100000000), - amount: parseFloat((Math.random() * 10000000000).toFixed(2)), - market: stockCode.startsWith('6') ? 'SH' : 'SZ', - update_time: new Date().toISOString(), - // 行业和指数标签 - industry_l1: industryInfo.industry_l1, - industry: industryInfo.industry, - index_tags: industryInfo.index_tags || [], - // 关键指标 - pe: parseFloat((Math.random() * 50 + 5).toFixed(2)), - eps: parseFloat((Math.random() * 5 + 0.1).toFixed(3)), - pb: parseFloat((Math.random() * 8 + 0.5).toFixed(2)), - market_cap: `${(Math.random() * 5000 + 100).toFixed(0)}亿`, - week52_low: parseFloat((basePrice * 0.7).toFixed(2)), - week52_high: parseFloat((basePrice * 1.3).toFixed(2)), - // 主力动态 - main_net_inflow: parseFloat((Math.random() * 10 - 5).toFixed(2)), - institution_holding: parseFloat((Math.random() * 50 + 10).toFixed(2)), - buy_ratio: parseFloat((Math.random() * 40 + 30).toFixed(2)), - sell_ratio: parseFloat((100 - (Math.random() * 40 + 30)).toFixed(2)), - }; - }); - - return HttpResponse.json({ - success: true, - data: quotesData, - message: '获取成功', - }); - } catch (error) { - console.error('[Mock Stock] 获取股票报价失败:', error); - return HttpResponse.json({ success: false, error: '获取股票报价失败' }, { status: 500 }); - } - }), - - // 获取股票详细行情(quote-detail) - http.get('/api/stock/:stockCode/quote-detail', async ({ params }) => { - await delay(200); - - const { stockCode } = params; - console.log('[Mock Stock] 获取股票详细行情:', { stockCode }); - - const stocks = generateStockList(); - const codeWithoutSuffix = (stockCode as string).replace(/\.(SH|SZ)$/i, ''); - const stockInfo = stocks.find((s) => s.code === codeWithoutSuffix); - const stockName = stockInfo?.name || `股票${stockCode}`; - - // 生成基础价格(10-200之间) - const basePrice = parseFloat((Math.random() * 190 + 10).toFixed(2)); - // 涨跌幅(-10% 到 +10%) - const changePercent = parseFloat((Math.random() * 20 - 10).toFixed(2)); - // 涨跌额 - const change = parseFloat(((basePrice * changePercent) / 100).toFixed(2)); - // 昨收 - const prevClose = parseFloat((basePrice - change).toFixed(2)); - - const quoteDetail: StockQuoteDetail = { - stock_code: stockCode as string, - stock_name: stockName, - code: stockCode as string, - name: stockName, - price: basePrice, - change: change, - change_percent: changePercent, - prev_close: prevClose, - open: parseFloat((prevClose * (1 + (Math.random() * 0.02 - 0.01))).toFixed(2)), - high: parseFloat((basePrice * (1 + Math.random() * 0.05)).toFixed(2)), - low: parseFloat((basePrice * (1 - Math.random() * 0.05)).toFixed(2)), - volume: Math.floor(Math.random() * 100000000), - amount: parseFloat((Math.random() * 10000000000).toFixed(2)), - turnover_rate: parseFloat((Math.random() * 10).toFixed(2)), - amplitude: parseFloat((Math.random() * 8).toFixed(2)), - market: (stockCode as string).startsWith('6') ? 'SH' : 'SZ', - update_time: new Date().toISOString(), - // 买卖盘口 - bid1: parseFloat((basePrice * 0.998).toFixed(2)), - bid1_volume: Math.floor(Math.random() * 10000), - bid2: parseFloat((basePrice * 0.996).toFixed(2)), - bid2_volume: Math.floor(Math.random() * 10000), - bid3: parseFloat((basePrice * 0.994).toFixed(2)), - bid3_volume: Math.floor(Math.random() * 10000), - bid4: parseFloat((basePrice * 0.992).toFixed(2)), - bid4_volume: Math.floor(Math.random() * 10000), - bid5: parseFloat((basePrice * 0.99).toFixed(2)), - bid5_volume: Math.floor(Math.random() * 10000), - ask1: parseFloat((basePrice * 1.002).toFixed(2)), - ask1_volume: Math.floor(Math.random() * 10000), - ask2: parseFloat((basePrice * 1.004).toFixed(2)), - ask2_volume: Math.floor(Math.random() * 10000), - ask3: parseFloat((basePrice * 1.006).toFixed(2)), - ask3_volume: Math.floor(Math.random() * 10000), - ask4: parseFloat((basePrice * 1.008).toFixed(2)), - ask4_volume: Math.floor(Math.random() * 10000), - ask5: parseFloat((basePrice * 1.01).toFixed(2)), - ask5_volume: Math.floor(Math.random() * 10000), - // 关键指标 - pe: parseFloat((Math.random() * 50 + 5).toFixed(2)), - pb: parseFloat((Math.random() * 8 + 0.5).toFixed(2)), - eps: parseFloat((Math.random() * 5 + 0.1).toFixed(3)), - market_cap: `${(Math.random() * 5000 + 100).toFixed(0)}亿`, - circulating_market_cap: `${(Math.random() * 3000 + 50).toFixed(0)}亿`, - total_shares: `${(Math.random() * 100 + 10).toFixed(2)}亿`, - circulating_shares: `${(Math.random() * 80 + 5).toFixed(2)}亿`, - week52_high: parseFloat((basePrice * 1.3).toFixed(2)), - week52_low: parseFloat((basePrice * 0.7).toFixed(2)), - }; - - return HttpResponse.json({ - success: true, - data: quoteDetail, - message: '获取成功', - }); - }), - - // FlexScreen 行情数据 - http.get('/api/flex-screen/quotes', async ({ request }) => { - await delay(200); - - const url = new URL(request.url); - const codes = url.searchParams.get('codes')?.split(',') || []; - - console.log('[Mock Stock] 获取 FlexScreen 行情:', { codes }); - - // 默认主要指数 - const defaultIndices = ['000001', '399001', '399006']; - const targetCodes = codes.length > 0 ? codes : defaultIndices; - - const indexData: Record = { - '000001': { name: '上证指数', basePrice: 3200 }, - '399001': { name: '深证成指', basePrice: 10500 }, - '399006': { name: '创业板指', basePrice: 2100 }, - '000300': { name: '沪深300', basePrice: 3800 }, - '000016': { name: '上证50', basePrice: 2600 }, - '000905': { name: '中证500', basePrice: 5800 }, - }; - - const quotesData: Record = {}; - targetCodes.forEach((code) => { - const codeWithoutSuffix = code.replace(/\.(SH|SZ)$/i, ''); - const info = indexData[codeWithoutSuffix] || { name: `指数${code}`, basePrice: 3000 }; - - const changePercent = parseFloat((Math.random() * 4 - 2).toFixed(2)); - const price = parseFloat((info.basePrice * (1 + changePercent / 100)).toFixed(2)); - const change = parseFloat((price - info.basePrice).toFixed(2)); - - quotesData[code] = { - code: code, - name: info.name, - price: price, - change: change, - change_percent: changePercent, - prev_close: info.basePrice, - open: parseFloat((info.basePrice * (1 + (Math.random() * 0.01 - 0.005))).toFixed(2)), - high: parseFloat((price * (1 + Math.random() * 0.01)).toFixed(2)), - low: parseFloat((price * (1 - Math.random() * 0.01)).toFixed(2)), - volume: parseFloat((Math.random() * 5000 + 2000).toFixed(2)), // 亿手 - amount: parseFloat((Math.random() * 8000 + 3000).toFixed(2)), // 亿元 - market: code.startsWith('6') || code.startsWith('000') ? 'SH' : 'SZ', - update_time: new Date().toISOString(), - }; - }); - - return HttpResponse.json({ - success: true, - data: quotesData, - message: '获取成功', - }); - }), -]; diff --git a/src/utils/apiConfig.js b/src/utils/apiConfig.js index 4ab7dcf3..e6e57731 100644 --- a/src/utils/apiConfig.js +++ b/src/utils/apiConfig.js @@ -30,11 +30,12 @@ export const getApiBase = () => { }; /** - * 检查是否处于 Mock 模式 - * @returns {boolean} + * 检查是否处于 Mock 模式(已禁用) + * @returns {boolean} 总是返回 false + * @deprecated Mock 模式已移除,保留此函数仅为兼容性 */ export const isMockMode = () => { - return process.env.REACT_APP_ENABLE_MOCK === 'true'; + return false; }; /** @@ -44,8 +45,7 @@ export const isMockMode = () => { * * @example * const url = getApiUrl('/api/users'); - * // Mock 模式: '/api/users' - * // 开发模式: 'http://49.232.185.254:5001/api/users' + * // 返回: 'http://api.example.com/api/users' */ export const getApiUrl = (path) => { return getApiBase() + path; diff --git a/src/views/StockOverview/components/FlexScreen/hooks/constants.ts b/src/views/StockOverview/components/FlexScreen/hooks/constants.ts index 1de70b9a..a0b1335d 100644 --- a/src/views/StockOverview/components/FlexScreen/hooks/constants.ts +++ b/src/views/StockOverview/components/FlexScreen/hooks/constants.ts @@ -5,24 +5,12 @@ import type { Exchange } from '../types'; import { getApiBase } from '@utils/apiConfig'; -/** 是否为 Mock 模式 */ -export const IS_MOCK_MODE = process.env.REACT_APP_ENABLE_MOCK === 'true'; - /** * 获取 WebSocket 配置 - * - Mock 模式: 返回空字符串,不连接 WebSocket * - 生产环境 (HTTPS): 通过 API 服务器 Nginx 代理使用 wss:// * - 开发环境 (HTTP): 直连 ws:// */ const getWsConfig = (): Record => { - // Mock 模式:不连接 WebSocket - if (IS_MOCK_MODE) { - return { - SSE: '', - SZSE: '', - }; - } - // 服务端渲染或测试环境使用默认配置 if (typeof window === 'undefined') { return { diff --git a/src/views/StockOverview/components/FlexScreen/hooks/useRealtimeQuote.ts b/src/views/StockOverview/components/FlexScreen/hooks/useRealtimeQuote.ts index ab46cd98..dd3e53a7 100644 --- a/src/views/StockOverview/components/FlexScreen/hooks/useRealtimeQuote.ts +++ b/src/views/StockOverview/components/FlexScreen/hooks/useRealtimeQuote.ts @@ -15,7 +15,7 @@ import { useState, useEffect, useRef, useCallback } from 'react'; import { logger } from '@utils/logger'; import { getApiBase } from '@utils/apiConfig'; -import { WS_CONFIG, HEARTBEAT_INTERVAL, RECONNECT_INTERVAL, IS_MOCK_MODE } from './constants'; +import { WS_CONFIG, HEARTBEAT_INTERVAL, RECONNECT_INTERVAL } from './constants'; import { getExchange, normalizeCode, calcChangePct } from './utils'; import type { Exchange, @@ -38,43 +38,6 @@ import type { /** 最大重连次数 */ const MAX_RECONNECT_ATTEMPTS = 5; -/** - * 生成 Mock 行情数据 - */ -const generateMockQuote = (code: string): QuoteData => { - const exchange: Exchange = code.endsWith('.SH') ? 'SSE' : 'SZSE'; - const basePrice = 10 + Math.random() * 90; // 10-100 之间的随机价格 - const prevClose = basePrice * (0.95 + Math.random() * 0.1); // 前收盘价在基准价格附近 - const change = basePrice - prevClose; - const changePct = (change / prevClose) * 100; - - // 生成五档买卖盘 - const bidPrices = Array.from({ length: 5 }, (_, i) => +(basePrice - 0.01 * (i + 1)).toFixed(2)); - const askPrices = Array.from({ length: 5 }, (_, i) => +(basePrice + 0.01 * (i + 1)).toFixed(2)); - const bidVolumes = Array.from({ length: 5 }, () => Math.floor(Math.random() * 10000) * 100); - const askVolumes = Array.from({ length: 5 }, () => Math.floor(Math.random() * 10000) * 100); - - return { - code, - name: `Mock股票${code.slice(0, 4)}`, - price: +basePrice.toFixed(2), - prevClose: +prevClose.toFixed(2), - open: +(prevClose * (0.99 + Math.random() * 0.02)).toFixed(2), - high: +(basePrice * (1 + Math.random() * 0.05)).toFixed(2), - low: +(basePrice * (1 - Math.random() * 0.05)).toFixed(2), - volume: Math.floor(Math.random() * 100000000), - amount: Math.floor(Math.random() * 1000000000), - change: +change.toFixed(2), - changePct: +changePct.toFixed(2), - bidPrices, - bidVolumes, - askPrices, - askVolumes, - updateTime: new Date().toISOString(), - exchange, - } as QuoteData; -}; - /** * 处理上交所消息 */ @@ -601,12 +564,6 @@ export const useRealtimeQuote = (codes: string[] = []): UseRealtimeQuoteReturn = * 创建 WebSocket 连接 */ const createConnection = useCallback((exchange: Exchange) => { - // Mock 模式:跳过 WebSocket 连接 - if (IS_MOCK_MODE) { - logger.info('FlexScreen', `${exchange} Mock 模式,跳过 WebSocket 连接`); - return; - } - const isHttps = typeof window !== 'undefined' && window.location.protocol === 'https:'; const wsUrl = WS_CONFIG[exchange]; @@ -786,41 +743,6 @@ export const useRealtimeQuote = (codes: string[] = []): UseRealtimeQuoteReturn = const allNewCodes = [...newSseCodes, ...newSzseCodes]; - // Mock 模式:生成 Mock 数据并定时更新 - if (IS_MOCK_MODE) { - logger.info('FlexScreen', 'Mock 模式,使用本地 Mock 数据'); - - // 生成初始 Mock 数据 - const mockQuotes: QuotesMap = {}; - allNewCodes.forEach(code => { - mockQuotes[code] = generateMockQuote(code); - }); - setQuotes(mockQuotes); - - // 定时更新 Mock 数据(模拟实时行情) - const mockInterval = setInterval(() => { - setQuotes(prev => { - const updated = { ...prev }; - Object.keys(updated).forEach(code => { - const quote = updated[code]; - // 小幅波动价格 - const priceChange = (Math.random() - 0.5) * 0.1; - const newPrice = +(quote.price + priceChange).toFixed(2); - updated[code] = { - ...quote, - price: newPrice, - change: +(newPrice - quote.prevClose).toFixed(2), - changePct: +((newPrice - quote.prevClose) / quote.prevClose * 100).toFixed(2), - updateTime: new Date().toISOString(), - }; - }); - return updated; - }); - }, 3000); // 每 3 秒更新一次 - - return () => clearInterval(mockInterval); - } - // 检查是否有新增的代码需要加载初始数据 const codesToLoad = allNewCodes.filter(c => !initialLoadedRef.current.has(c));