|
|
|
|
@ -1,61 +1,196 @@
|
|
|
|
|
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';
|
|
|
|
|
import dataCache from '../utils/cache';
|
|
|
|
|
|
|
|
|
|
// 后端API基础URL
|
|
|
|
|
const API_BASE_URL = 'http://localhost:3007/api';
|
|
|
|
|
|
|
|
|
|
// 缓存有效期配置(毫秒)
|
|
|
|
|
const CACHE_TTL = {
|
|
|
|
|
OVERVIEW: 5 * 60 * 1000, // 市场概览:5分钟
|
|
|
|
|
DETAIL: 10 * 60 * 1000, // 品种详情:10分钟
|
|
|
|
|
RISK_ALERTS: 3 * 60 * 1000, // 风险预警:3分钟
|
|
|
|
|
AI_ANALYSIS: 10 * 60 * 1000, // AI分析:10分钟
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 异步获取期货概览数据
|
|
|
|
|
export const fetchFuturesOverview = createAsyncThunk(
|
|
|
|
|
'futures/fetchOverview',
|
|
|
|
|
async () => {
|
|
|
|
|
async ({ forceRefresh = false } = {}, { rejectWithValue }) => {
|
|
|
|
|
const cacheKey = 'futures_overview';
|
|
|
|
|
|
|
|
|
|
// 检查缓存
|
|
|
|
|
if (!forceRefresh) {
|
|
|
|
|
const cached = dataCache.get(cacheKey);
|
|
|
|
|
if (cached) {
|
|
|
|
|
console.log('[Cache] 使用缓存的市场概览数据');
|
|
|
|
|
return cached;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const response = await fetch(`${API_BASE_URL}/market/overview`);
|
|
|
|
|
if (!response.ok) {
|
|
|
|
|
throw new Error('获取市场概览失败');
|
|
|
|
|
}
|
|
|
|
|
const data = await response.json();
|
|
|
|
|
|
|
|
|
|
// 缓存数据
|
|
|
|
|
dataCache.set(cacheKey, data.data, CACHE_TTL.OVERVIEW);
|
|
|
|
|
console.log('[Cache] 市场概览数据已缓存');
|
|
|
|
|
|
|
|
|
|
return data.data;
|
|
|
|
|
} catch (error) {
|
|
|
|
|
return rejectWithValue(error.message);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// 异步获取单个期货详情
|
|
|
|
|
export const fetchFutureDetail = createAsyncThunk(
|
|
|
|
|
'futures/fetchDetail',
|
|
|
|
|
async ({ code, name }) => {
|
|
|
|
|
async ({ code, forceRefresh = false }, { rejectWithValue }) => {
|
|
|
|
|
const cacheKey = `future_detail_${code}`;
|
|
|
|
|
|
|
|
|
|
// 检查缓存
|
|
|
|
|
if (!forceRefresh) {
|
|
|
|
|
const cached = dataCache.get(cacheKey);
|
|
|
|
|
if (cached) {
|
|
|
|
|
console.log(`[Cache] 使用缓存的品种详情数据: ${code}`);
|
|
|
|
|
return cached;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const response = await fetch(`${API_BASE_URL}/market/detail/${code}`);
|
|
|
|
|
if (!response.ok) {
|
|
|
|
|
throw new Error('获取品种详情失败');
|
|
|
|
|
}
|
|
|
|
|
const data = await response.json();
|
|
|
|
|
|
|
|
|
|
// 缓存数据
|
|
|
|
|
dataCache.set(cacheKey, data.data, CACHE_TTL.DETAIL);
|
|
|
|
|
console.log(`[Cache] 品种详情数据已缓存: ${code}`);
|
|
|
|
|
|
|
|
|
|
return data.data;
|
|
|
|
|
} catch (error) {
|
|
|
|
|
return rejectWithValue(error.message);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// 异步获取风险预警
|
|
|
|
|
export const fetchRiskAlerts = createAsyncThunk(
|
|
|
|
|
'futures/fetchRiskAlerts',
|
|
|
|
|
async () => {
|
|
|
|
|
async ({ forceRefresh = false } = {}, { rejectWithValue }) => {
|
|
|
|
|
const cacheKey = 'risk_alerts';
|
|
|
|
|
|
|
|
|
|
// 检查缓存
|
|
|
|
|
if (!forceRefresh) {
|
|
|
|
|
const cached = dataCache.get(cacheKey);
|
|
|
|
|
if (cached) {
|
|
|
|
|
console.log('[Cache] 使用缓存的风险预警数据');
|
|
|
|
|
return cached;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const response = await fetch(`${API_BASE_URL}/market/alerts`);
|
|
|
|
|
if (!response.ok) {
|
|
|
|
|
throw new Error('获取风险预警失败');
|
|
|
|
|
}
|
|
|
|
|
const data = await response.json();
|
|
|
|
|
|
|
|
|
|
// 缓存数据
|
|
|
|
|
dataCache.set(cacheKey, data.data, CACHE_TTL.RISK_ALERTS);
|
|
|
|
|
console.log('[Cache] 风险预警数据已缓存');
|
|
|
|
|
|
|
|
|
|
return data.data;
|
|
|
|
|
} catch (error) {
|
|
|
|
|
return rejectWithValue(error.message);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// 异步获取AI市场分析
|
|
|
|
|
export const fetchAIMarketAnalysis = createAsyncThunk(
|
|
|
|
|
'futures/fetchAIMarketAnalysis',
|
|
|
|
|
async () => {
|
|
|
|
|
// 由于后端没有专门的市场分析接口,我们使用模拟数据
|
|
|
|
|
return {
|
|
|
|
|
async ({ forceRefresh = false } = {}, { rejectWithValue }) => {
|
|
|
|
|
const cacheKey = 'ai_market_analysis';
|
|
|
|
|
|
|
|
|
|
// 检查缓存
|
|
|
|
|
if (!forceRefresh) {
|
|
|
|
|
const cached = dataCache.get(cacheKey);
|
|
|
|
|
if (cached) {
|
|
|
|
|
console.log('[Cache] 使用缓存的AI市场分析数据');
|
|
|
|
|
return cached;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 由于后端没有专门的市场分析接口,使用模拟数据
|
|
|
|
|
const data = {
|
|
|
|
|
overallTrend: '震荡偏弱',
|
|
|
|
|
keyFactors: ['原油价格波动', '宏观经济数据', '政策面变化'],
|
|
|
|
|
recommendations: ['控制仓位', '关注原油走势', '做好止损'],
|
|
|
|
|
confidence: 75
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 缓存数据
|
|
|
|
|
dataCache.set(cacheKey, data, CACHE_TTL.AI_ANALYSIS);
|
|
|
|
|
console.log('[Cache] AI市场分析数据已缓存');
|
|
|
|
|
|
|
|
|
|
return data;
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// 异步获取K线数据
|
|
|
|
|
export const fetchKlineData = createAsyncThunk(
|
|
|
|
|
'futures/fetchKlineData',
|
|
|
|
|
async ({ code, period = '1D', forceRefresh = false }, { rejectWithValue }) => {
|
|
|
|
|
const cacheKey = `kline_${code}_${period}`;
|
|
|
|
|
|
|
|
|
|
// 检查缓存
|
|
|
|
|
if (!forceRefresh) {
|
|
|
|
|
const cached = dataCache.get(cacheKey);
|
|
|
|
|
if (cached) {
|
|
|
|
|
console.log(`[Cache] 使用缓存的K线数据: ${code} ${period}`);
|
|
|
|
|
return { code, period, data: cached };
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const response = await fetch(`${API_BASE_URL}/market/klines/${code}?period=${period}`);
|
|
|
|
|
if (!response.ok) {
|
|
|
|
|
throw new Error('获取K线数据失败');
|
|
|
|
|
}
|
|
|
|
|
const result = await response.json();
|
|
|
|
|
|
|
|
|
|
// 缓存数据(K线数据缓存时间较长)
|
|
|
|
|
dataCache.set(cacheKey, result.data, 30 * 60 * 1000); // 30分钟
|
|
|
|
|
console.log(`[Cache] K线数据已缓存: ${code} ${period}`);
|
|
|
|
|
|
|
|
|
|
return { code, period, data: result.data };
|
|
|
|
|
} catch (error) {
|
|
|
|
|
return rejectWithValue(error.message);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// 清除指定缓存
|
|
|
|
|
export const clearCache = (key) => {
|
|
|
|
|
if (key) {
|
|
|
|
|
dataCache.delete(key);
|
|
|
|
|
console.log(`[Cache] 已清除缓存: ${key}`);
|
|
|
|
|
} else {
|
|
|
|
|
dataCache.clear();
|
|
|
|
|
console.log('[Cache] 已清除所有缓存');
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 获取缓存统计
|
|
|
|
|
export const getCacheStats = () => {
|
|
|
|
|
return dataCache.getStats();
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const futuresSlice = createSlice({
|
|
|
|
|
name: 'futures',
|
|
|
|
|
initialState: {
|
|
|
|
|
@ -63,9 +198,11 @@ const futuresSlice = createSlice({
|
|
|
|
|
selectedFuture: null,
|
|
|
|
|
riskAlerts: [],
|
|
|
|
|
aiAnalysis: null,
|
|
|
|
|
klineData: {}, // 存储不同品种的K线数据 { [code_period]: data }
|
|
|
|
|
loading: false,
|
|
|
|
|
error: null,
|
|
|
|
|
watchlist: []
|
|
|
|
|
watchlist: [],
|
|
|
|
|
cacheStats: null // 缓存统计
|
|
|
|
|
},
|
|
|
|
|
reducers: {
|
|
|
|
|
selectFuture: (state, action) => {
|
|
|
|
|
@ -91,6 +228,12 @@ const futuresSlice = createSlice({
|
|
|
|
|
if (state.selectedFuture && state.selectedFuture.code === code) {
|
|
|
|
|
state.selectedFuture.isInWatchlist = state.watchlist.includes(code);
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
clearCacheStats: (state) => {
|
|
|
|
|
state.cacheStats = null;
|
|
|
|
|
},
|
|
|
|
|
updateCacheStats: (state) => {
|
|
|
|
|
state.cacheStats = dataCache.getStats();
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
extraReducers: (builder) => {
|
|
|
|
|
@ -159,9 +302,24 @@ const futuresSlice = createSlice({
|
|
|
|
|
.addCase(fetchAIMarketAnalysis.rejected, (state, action) => {
|
|
|
|
|
state.loading = false;
|
|
|
|
|
state.error = action.error.message;
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
// 处理fetchKlineData
|
|
|
|
|
.addCase(fetchKlineData.pending, (state) => {
|
|
|
|
|
state.loading = true;
|
|
|
|
|
state.error = null;
|
|
|
|
|
})
|
|
|
|
|
.addCase(fetchKlineData.fulfilled, (state, action) => {
|
|
|
|
|
state.loading = false;
|
|
|
|
|
const { code, period, data } = action.payload;
|
|
|
|
|
state.klineData[`${code}_${period}`] = data;
|
|
|
|
|
})
|
|
|
|
|
.addCase(fetchKlineData.rejected, (state, action) => {
|
|
|
|
|
state.loading = false;
|
|
|
|
|
state.error = action.error.message;
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
export const { selectFuture, clearSelectedFuture, toggleWatchlist } = futuresSlice.actions;
|
|
|
|
|
export const { selectFuture, clearSelectedFuture, toggleWatchlist, clearCacheStats, updateCacheStats } = futuresSlice.actions;
|
|
|
|
|
export default futuresSlice.reducer;
|
|
|
|
|
|