- 使用 useReducer 整合 7 个折叠状态为统一的 sectionState - 提取自选股逻辑到 useWatchlist Hook,移除 70 行重复代码 - 扩展 useWatchlist 添加 handleAddToWatchlist、isInWatchlist 方法 - 清理未使用的导入(HStack、useColorModeValue) - 移除调试 console.log 日志 - RelatedStocksSection 改用 isInWatchlist 函数替代 watchlistSet 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
142 lines
5.2 KiB
JavaScript
142 lines
5.2 KiB
JavaScript
// src/hooks/useWatchlist.js
|
|
// 自选股管理自定义 Hook
|
|
|
|
import { useState, useCallback } from 'react';
|
|
import { useToast } from '@chakra-ui/react';
|
|
import { logger } from '../utils/logger';
|
|
import { getApiBase } from '../utils/apiConfig';
|
|
|
|
const WATCHLIST_PAGE_SIZE = 10;
|
|
|
|
/**
|
|
* 自选股管理 Hook
|
|
* 提供自选股加载、分页、添加、移除等功能
|
|
*
|
|
* @returns {{
|
|
* watchlistQuotes: Array,
|
|
* watchlistLoading: boolean,
|
|
* watchlistPage: number,
|
|
* setWatchlistPage: Function,
|
|
* WATCHLIST_PAGE_SIZE: number,
|
|
* loadWatchlistQuotes: Function,
|
|
* handleAddToWatchlist: Function,
|
|
* handleRemoveFromWatchlist: Function,
|
|
* isInWatchlist: Function
|
|
* }}
|
|
*/
|
|
export const useWatchlist = () => {
|
|
const toast = useToast();
|
|
const [watchlistQuotes, setWatchlistQuotes] = useState([]);
|
|
const [watchlistLoading, setWatchlistLoading] = useState(false);
|
|
const [watchlistPage, setWatchlistPage] = useState(1);
|
|
|
|
// 加载自选股实时行情
|
|
const loadWatchlistQuotes = useCallback(async () => {
|
|
try {
|
|
setWatchlistLoading(true);
|
|
const base = getApiBase();
|
|
const resp = await fetch(base + '/api/account/watchlist/realtime', {
|
|
credentials: 'include',
|
|
cache: 'no-store',
|
|
headers: { 'Cache-Control': 'no-cache' }
|
|
});
|
|
if (resp.ok) {
|
|
const data = await resp.json();
|
|
if (data && data.success && Array.isArray(data.data)) {
|
|
setWatchlistQuotes(data.data);
|
|
} else {
|
|
setWatchlistQuotes([]);
|
|
}
|
|
} else {
|
|
setWatchlistQuotes([]);
|
|
}
|
|
} catch (e) {
|
|
logger.warn('useWatchlist', '加载自选股实时行情失败', {
|
|
error: e.message
|
|
});
|
|
setWatchlistQuotes([]);
|
|
} finally {
|
|
setWatchlistLoading(false);
|
|
}
|
|
}, []);
|
|
|
|
// 添加到自选股
|
|
const handleAddToWatchlist = useCallback(async (stockCode, stockName) => {
|
|
try {
|
|
const base = getApiBase();
|
|
const resp = await fetch(base + '/api/account/watchlist/add', {
|
|
method: 'POST',
|
|
credentials: 'include',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ stock_code: stockCode, stock_name: stockName })
|
|
});
|
|
const data = await resp.json().catch(() => ({}));
|
|
if (resp.ok && data.success) {
|
|
// 刷新自选股列表
|
|
loadWatchlistQuotes();
|
|
toast({ title: '已添加至自选股', status: 'success', duration: 1500 });
|
|
return true;
|
|
} else {
|
|
toast({ title: '添加失败', status: 'error', duration: 2000 });
|
|
return false;
|
|
}
|
|
} catch (e) {
|
|
toast({ title: '网络错误,添加失败', status: 'error', duration: 2000 });
|
|
return false;
|
|
}
|
|
}, [toast, loadWatchlistQuotes]);
|
|
|
|
// 从自选股移除
|
|
const handleRemoveFromWatchlist = useCallback(async (stockCode) => {
|
|
try {
|
|
const base = getApiBase();
|
|
const resp = await fetch(base + `/api/account/watchlist/${stockCode}`, {
|
|
method: 'DELETE',
|
|
credentials: 'include'
|
|
});
|
|
const data = await resp.json().catch(() => ({}));
|
|
if (resp.ok && data && data.success !== false) {
|
|
setWatchlistQuotes((prev) => {
|
|
const normalize6 = (code) => {
|
|
const m = String(code || '').match(/(\d{6})/);
|
|
return m ? m[1] : String(code || '');
|
|
};
|
|
const target = normalize6(stockCode);
|
|
const updated = (prev || []).filter((x) => normalize6(x.stock_code) !== target);
|
|
const newMaxPage = Math.max(1, Math.ceil((updated.length || 0) / WATCHLIST_PAGE_SIZE));
|
|
setWatchlistPage((p) => Math.min(p, newMaxPage));
|
|
return updated;
|
|
});
|
|
toast({ title: '已从自选股移除', status: 'info', duration: 1500 });
|
|
} else {
|
|
toast({ title: '移除失败', status: 'error', duration: 2000 });
|
|
}
|
|
} catch (e) {
|
|
toast({ title: '网络错误,移除失败', status: 'error', duration: 2000 });
|
|
return false;
|
|
}
|
|
}, [toast]);
|
|
|
|
// 判断股票是否在自选股中
|
|
const isInWatchlist = useCallback((stockCode) => {
|
|
const normalize6 = (code) => {
|
|
const m = String(code || '').match(/(\d{6})/);
|
|
return m ? m[1] : String(code || '');
|
|
};
|
|
const target = normalize6(stockCode);
|
|
return watchlistQuotes.some(item => normalize6(item.stock_code) === target);
|
|
}, [watchlistQuotes]);
|
|
|
|
return {
|
|
watchlistQuotes,
|
|
watchlistLoading,
|
|
watchlistPage,
|
|
setWatchlistPage,
|
|
WATCHLIST_PAGE_SIZE,
|
|
loadWatchlistQuotes,
|
|
handleAddToWatchlist,
|
|
handleRemoveFromWatchlist,
|
|
isInWatchlist
|
|
};
|
|
};
|