This commit is contained in:
2026-01-13 15:10:13 +08:00
parent 38f0885a85
commit 45d5debead
134 changed files with 16980 additions and 1 deletions

View File

@@ -0,0 +1,304 @@
/**
* 涨停数据服务
* 获取涨停板块、个股、统计等数据
*/
import { apiRequest, API_BASE } from './api';
export const ztService = {
/**
* 获取指定日期的涨停数据
* @param {string} dateStr - 日期 YYYYMMDD 格式
* @returns {Promise<object>} 涨停数据
*/
getDailyZt: async (dateStr) => {
try {
// 尝试从静态数据获取
const response = await fetch(`${API_BASE}/data/zt/daily/${dateStr}.json`);
if (response.ok) {
const data = await response.json();
return { success: true, data };
}
return { success: false, message: '数据不存在' };
} catch (error) {
console.error('获取涨停数据失败:', error);
return { success: false, message: error.message };
}
},
/**
* 获取最新的涨停数据(今日或最近交易日)
* @returns {Promise<object>} 最新涨停数据
*/
getLatestZt: async () => {
try {
const response = await fetch(`${API_BASE}/data/zt/latest.json`);
if (response.ok) {
const data = await response.json();
return { success: true, data };
}
// 如果没有 latest.json尝试获取今天的数据
const today = new Date();
const dateStr = today.toISOString().slice(0, 10).replace(/-/g, '');
return ztService.getDailyZt(dateStr);
} catch (error) {
console.error('获取最新涨停数据失败:', error);
return { success: false, message: error.message };
}
},
/**
* 获取可用的涨停数据日期列表
* @returns {Promise<object>} 日期列表
*/
getAvailableDates: async () => {
try {
const response = await fetch(`${API_BASE}/data/zt/dates.json`);
if (response.ok) {
const data = await response.json();
return { success: true, data };
}
return { success: false, data: [] };
} catch (error) {
return { success: false, data: [] };
}
},
/**
* 获取板块详情(包含该板块的所有涨停股票)
* @param {string} dateStr - 日期
* @param {string} sectorName - 板块名称
* @returns {Promise<object>} 板块详情
*/
getSectorDetail: async (dateStr, sectorName) => {
const result = await ztService.getDailyZt(dateStr);
if (!result.success) return result;
const sectorData = result.data.sector_data?.[sectorName];
if (!sectorData) {
return { success: false, message: '板块不存在' };
}
// 获取该板块的股票详情
const stocks = result.data.stocks?.filter(s =>
sectorData.stock_codes?.includes(s.scode)
) || [];
return {
success: true,
data: {
name: sectorName,
count: sectorData.count,
stocks,
related_events: sectorData.related_events || [],
}
};
},
/**
* 获取连板统计
* @param {object} ztData - 涨停原始数据
* @returns {object} 统计结果
*/
calculateStats: (ztData) => {
if (!ztData) return null;
const stocks = ztData.stocks || ztData.stock_infos || [];
// 连板统计
const continuousStats = {};
// 时间统计
const timeStats = { '秒板': 0, '早盘': 0, '盘中': 0, '尾盘': 0 };
// 公告驱动统计
let announcementCount = 0;
stocks.forEach(stock => {
// 连板统计
const continuous = stock.continuous_days || '首板';
continuousStats[continuous] = (continuousStats[continuous] || 0) + 1;
// 时间统计
const time = stock.formatted_time || stock.zt_time;
if (time) {
const hour = parseInt(time.split(':')[0]);
const minute = parseInt(time.split(':')[1]);
const totalMinutes = hour * 60 + minute;
if (totalMinutes <= 9 * 60 + 31) {
timeStats['秒板']++;
} else if (totalMinutes <= 10 * 60 + 30) {
timeStats['早盘']++;
} else if (totalMinutes <= 14 * 60) {
timeStats['盘中']++;
} else {
timeStats['尾盘']++;
}
}
// 公告驱动
if (stock.is_announcement) {
announcementCount++;
}
});
return {
total: ztData.total_stocks || stocks.length,
continuousStats,
timeStats,
announcementCount,
announcementRatio: stocks.length > 0
? ((announcementCount / stocks.length) * 100).toFixed(1)
: 0,
};
},
/**
* 获取热门板块排序
* @param {object} ztData - 涨停原始数据
* @param {number} limit - 返回数量
* @returns {Array} 排序后的板块列表
*/
getHotSectors: (ztData, limit = 10) => {
if (!ztData?.sector_data) return [];
// 需要过滤掉的板块名称
const excludedSectors = ['其他', '公告'];
const sectors = Object.entries(ztData.sector_data)
.filter(([name]) => !excludedSectors.includes(name)) // 过滤掉"其他"和"公告"
.map(([name, data]) => ({
name,
count: data.count || 0,
stock_codes: data.stock_codes || [],
related_events: data.related_events || [],
// 计算热度分数:涨停数 * 权重 + 关联事件数 * 权重
hotScore: (data.count || 0) * 10 + (data.related_events?.length || 0) * 5,
}))
.sort((a, b) => b.hotScore - a.hotScore)
.slice(0, limit);
return sectors;
},
/**
* 获取连板龙头股
* @param {object} ztData - 涨停原始数据
* @param {number} minDays - 最小连板天数
* @returns {Array} 连板股列表
*/
getContinuousLeaders: (ztData, minDays = 2) => {
const stocks = ztData?.stocks || ztData?.stock_infos || [];
// 解析连板天数
const parseDay = (str) => {
if (!str || str === '首板') return 1;
const match = str.match(/(\d+)/);
return match ? parseInt(match[1]) : 1;
};
return stocks
.filter(s => parseDay(s.continuous_days) >= minDays)
.sort((a, b) => parseDay(b.continuous_days) - parseDay(a.continuous_days));
},
/**
* 获取热门关键词
* @param {object} ztData - 涨停原始数据
* @param {number} limit - 返回数量
* @returns {Array} 关键词列表
*/
getHotKeywords: (ztData, limit = 12) => {
return (ztData?.word_freq_data || []).slice(0, limit);
},
/**
* 获取日历综合数据(涨停数 + 事件数 + 涨跌幅 + 热门概念)
* @param {number} year - 年份
* @param {number} month - 月份 (1-12)
* @returns {Promise<object>} 日历数据
*/
getCalendarData: async (year, month) => {
try {
// 获取日期列表
const datesResult = await ztService.getAvailableDates();
if (!datesResult.success) {
return { success: false, data: [] };
}
const dates = datesResult.data.dates || [];
const calendarData = [];
// 过滤当月数据
const monthStr = String(month).padStart(2, '0');
const monthPrefix = `${year}${monthStr}`;
for (const dateInfo of dates) {
const dateStr = dateInfo.date;
if (!dateStr.startsWith(monthPrefix)) continue;
// 获取每日详细数据
const dailyResult = await ztService.getDailyZt(dateStr);
let topSector = '';
let indexChange = null;
if (dailyResult.success && dailyResult.data) {
// 获取最热板块
const hotSectors = ztService.getHotSectors(dailyResult.data, 1);
topSector = hotSectors[0]?.name || '';
// 获取指数涨跌幅(如果数据中有)
indexChange = dailyResult.data.index_change || null;
}
calendarData.push({
date: dateStr,
ztCount: dateInfo.count || 0,
topSector,
eventCount: dateInfo.event_count || 0,
indexChange,
});
}
return { success: true, data: calendarData };
} catch (error) {
console.error('获取日历数据失败:', error);
return { success: false, data: [] };
}
},
/**
* 快速获取日历数据(只从 dates.json 获取基础信息)
* @param {number} year - 年份
* @param {number} month - 月份 (1-12)
* @returns {Promise<object>} 日历数据
*/
getCalendarDataFast: async (year, month) => {
try {
const datesResult = await ztService.getAvailableDates();
if (!datesResult.success) {
return { success: false, data: [] };
}
const dates = datesResult.data.dates || [];
const monthStr = String(month).padStart(2, '0');
const monthPrefix = `${year}${monthStr}`;
const calendarData = dates
.filter(d => d.date.startsWith(monthPrefix))
.map(d => ({
date: d.date,
ztCount: d.count || 0,
formattedDate: d.formatted_date,
topSector: d.top_sector || '',
eventCount: d.event_count || 0,
indexChange: d.index_change || null,
}));
return { success: true, data: calendarData };
} catch (error) {
console.error('获取日历数据失败:', error);
return { success: false, data: [] };
}
},
};
export default ztService;