update pay ui
This commit is contained in:
@@ -34,6 +34,8 @@ import type {
|
||||
|
||||
/**
|
||||
* 处理上交所消息
|
||||
* 注意:上交所返回的 code 不带后缀,但通过 msg.type 区分 'stock' 和 'index'
|
||||
* 存储时使用带后缀的完整代码作为 key(如 000001.SH)
|
||||
*/
|
||||
const handleSSEMessage = (
|
||||
msg: SSEMessage,
|
||||
@@ -47,12 +49,16 @@ const handleSSEMessage = (
|
||||
const data = msg.data || {};
|
||||
const updated: QuotesMap = { ...prevQuotes };
|
||||
let hasUpdate = false;
|
||||
const isIndex = msg.type === 'index';
|
||||
|
||||
Object.entries(data).forEach(([code, quote]: [string, SSEQuoteItem]) => {
|
||||
if (subscribedCodes.has(code)) {
|
||||
// 生成带后缀的完整代码(上交所统一用 .SH)
|
||||
const fullCode = code.includes('.') ? code : `${code}.SH`;
|
||||
|
||||
if (subscribedCodes.has(code) || subscribedCodes.has(fullCode)) {
|
||||
hasUpdate = true;
|
||||
updated[code] = {
|
||||
code: quote.security_id,
|
||||
updated[fullCode] = {
|
||||
code: fullCode,
|
||||
name: quote.security_name,
|
||||
price: quote.last_price,
|
||||
prevClose: quote.prev_close,
|
||||
@@ -78,6 +84,8 @@ const handleSSEMessage = (
|
||||
|
||||
/**
|
||||
* 处理深交所实时消息
|
||||
* 注意:深交所返回的 security_id 可能带后缀也可能不带
|
||||
* 存储时统一使用带后缀的完整代码作为 key(如 000001.SZ)
|
||||
*/
|
||||
const handleSZSERealtimeMessage = (
|
||||
msg: SZSERealtimeMessage,
|
||||
@@ -85,9 +93,11 @@ const handleSZSERealtimeMessage = (
|
||||
prevQuotes: QuotesMap
|
||||
): QuotesMap | null => {
|
||||
const { category, data, timestamp } = msg;
|
||||
const code = data.security_id;
|
||||
const rawCode = data.security_id;
|
||||
// 生成带后缀的完整代码(深交所统一用 .SZ)
|
||||
const fullCode = rawCode.includes('.') ? rawCode : `${rawCode}.SZ`;
|
||||
|
||||
if (!subscribedCodes.has(code)) {
|
||||
if (!subscribedCodes.has(rawCode) && !subscribedCodes.has(fullCode)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -99,9 +109,9 @@ const handleSZSERealtimeMessage = (
|
||||
const { prices: bidPrices, volumes: bidVolumes } = extractOrderBook(stockData.bids);
|
||||
const { prices: askPrices, volumes: askVolumes } = extractOrderBook(stockData.asks);
|
||||
|
||||
updated[code] = {
|
||||
code,
|
||||
name: prevQuotes[code]?.name || '',
|
||||
updated[fullCode] = {
|
||||
code: fullCode,
|
||||
name: prevQuotes[fullCode]?.name || '',
|
||||
price: stockData.last_px,
|
||||
prevClose: stockData.prev_close,
|
||||
open: stockData.open_px,
|
||||
@@ -127,9 +137,9 @@ const handleSZSERealtimeMessage = (
|
||||
|
||||
case 'index': {
|
||||
const indexData = data as SZSEIndexData;
|
||||
updated[code] = {
|
||||
code,
|
||||
name: prevQuotes[code]?.name || '',
|
||||
updated[fullCode] = {
|
||||
code: fullCode,
|
||||
name: prevQuotes[fullCode]?.name || '',
|
||||
price: indexData.current_index,
|
||||
prevClose: indexData.prev_close,
|
||||
open: indexData.open_index,
|
||||
@@ -154,9 +164,9 @@ const handleSZSERealtimeMessage = (
|
||||
|
||||
case 'bond': {
|
||||
const bondData = data as SZSEBondData;
|
||||
updated[code] = {
|
||||
code,
|
||||
name: prevQuotes[code]?.name || '',
|
||||
updated[fullCode] = {
|
||||
code: fullCode,
|
||||
name: prevQuotes[fullCode]?.name || '',
|
||||
price: bondData.last_px,
|
||||
prevClose: bondData.prev_close,
|
||||
open: bondData.open_px,
|
||||
@@ -185,9 +195,9 @@ const handleSZSERealtimeMessage = (
|
||||
const { prices: bidPrices, volumes: bidVolumes } = extractOrderBook(hkData.bids);
|
||||
const { prices: askPrices, volumes: askVolumes } = extractOrderBook(hkData.asks);
|
||||
|
||||
updated[code] = {
|
||||
code,
|
||||
name: prevQuotes[code]?.name || '',
|
||||
updated[fullCode] = {
|
||||
code: fullCode,
|
||||
name: prevQuotes[fullCode]?.name || '',
|
||||
price: hkData.last_px,
|
||||
prevClose: hkData.prev_close,
|
||||
open: hkData.open_px,
|
||||
@@ -215,9 +225,9 @@ const handleSZSERealtimeMessage = (
|
||||
case 'afterhours_block':
|
||||
case 'afterhours_trading': {
|
||||
const afterhoursData = data as SZSEAfterhoursData;
|
||||
const existing = prevQuotes[code];
|
||||
const existing = prevQuotes[fullCode];
|
||||
if (existing) {
|
||||
updated[code] = {
|
||||
updated[fullCode] = {
|
||||
...existing,
|
||||
afterhours: {
|
||||
bidPx: afterhoursData.bid_px,
|
||||
@@ -243,6 +253,7 @@ const handleSZSERealtimeMessage = (
|
||||
|
||||
/**
|
||||
* 处理深交所快照消息
|
||||
* 存储时统一使用带后缀的完整代码作为 key
|
||||
*/
|
||||
const handleSZSESnapshotMessage = (
|
||||
msg: SZSESnapshotMessage,
|
||||
@@ -254,13 +265,16 @@ const handleSZSESnapshotMessage = (
|
||||
let hasUpdate = false;
|
||||
|
||||
stocks.forEach((s: SZSEStockData) => {
|
||||
if (subscribedCodes.has(s.security_id)) {
|
||||
const rawCode = s.security_id;
|
||||
const fullCode = rawCode.includes('.') ? rawCode : `${rawCode}.SZ`;
|
||||
|
||||
if (subscribedCodes.has(rawCode) || subscribedCodes.has(fullCode)) {
|
||||
hasUpdate = true;
|
||||
const { prices: bidPrices, volumes: bidVolumes } = extractOrderBook(s.bids);
|
||||
const { prices: askPrices, volumes: askVolumes } = extractOrderBook(s.asks);
|
||||
|
||||
updated[s.security_id] = {
|
||||
code: s.security_id,
|
||||
updated[fullCode] = {
|
||||
code: fullCode,
|
||||
name: '',
|
||||
price: s.last_px,
|
||||
prevClose: s.prev_close,
|
||||
@@ -284,10 +298,13 @@ const handleSZSESnapshotMessage = (
|
||||
});
|
||||
|
||||
indexes.forEach((i: SZSEIndexData) => {
|
||||
if (subscribedCodes.has(i.security_id)) {
|
||||
const rawCode = i.security_id;
|
||||
const fullCode = rawCode.includes('.') ? rawCode : `${rawCode}.SZ`;
|
||||
|
||||
if (subscribedCodes.has(rawCode) || subscribedCodes.has(fullCode)) {
|
||||
hasUpdate = true;
|
||||
updated[i.security_id] = {
|
||||
code: i.security_id,
|
||||
updated[fullCode] = {
|
||||
code: fullCode,
|
||||
name: '',
|
||||
price: i.current_index,
|
||||
prevClose: i.prev_close,
|
||||
@@ -309,10 +326,13 @@ const handleSZSESnapshotMessage = (
|
||||
});
|
||||
|
||||
bonds.forEach((b: SZSEBondData) => {
|
||||
if (subscribedCodes.has(b.security_id)) {
|
||||
const rawCode = b.security_id;
|
||||
const fullCode = rawCode.includes('.') ? rawCode : `${rawCode}.SZ`;
|
||||
|
||||
if (subscribedCodes.has(rawCode) || subscribedCodes.has(fullCode)) {
|
||||
hasUpdate = true;
|
||||
updated[b.security_id] = {
|
||||
code: b.security_id,
|
||||
updated[fullCode] = {
|
||||
code: fullCode,
|
||||
name: '',
|
||||
price: b.last_px,
|
||||
prevClose: b.prev_close,
|
||||
@@ -433,12 +453,14 @@ export const useRealtimeQuote = (codes: string[] = []): UseRealtimeQuoteReturn =
|
||||
setConnected(prev => ({ ...prev, [exchange]: true }));
|
||||
|
||||
if (exchange === 'SSE') {
|
||||
const codes = Array.from(subscribedCodes.current.SSE);
|
||||
if (codes.length > 0) {
|
||||
// subscribedCodes 存的是带后缀的完整代码,发送给 WS 需要去掉后缀
|
||||
const fullCodes = Array.from(subscribedCodes.current.SSE);
|
||||
const baseCodes = fullCodes.map(c => normalizeCode(c));
|
||||
if (baseCodes.length > 0) {
|
||||
ws.send(JSON.stringify({
|
||||
action: 'subscribe',
|
||||
channels: ['stock', 'index'],
|
||||
codes,
|
||||
codes: baseCodes,
|
||||
}));
|
||||
}
|
||||
}
|
||||
@@ -481,17 +503,19 @@ export const useRealtimeQuote = (codes: string[] = []): UseRealtimeQuoteReturn =
|
||||
}, [startHeartbeat, stopHeartbeat, handleMessage]);
|
||||
|
||||
const subscribe = useCallback((code: string) => {
|
||||
const baseCode = normalizeCode(code);
|
||||
const exchange = getExchange(code);
|
||||
// 确保使用带后缀的完整代码
|
||||
const fullCode = code.includes('.') ? code : `${code}.${exchange === 'SSE' ? 'SH' : 'SZ'}`;
|
||||
const baseCode = normalizeCode(code);
|
||||
|
||||
subscribedCodes.current[exchange].add(baseCode);
|
||||
subscribedCodes.current[exchange].add(fullCode);
|
||||
|
||||
const ws = wsRefs.current[exchange];
|
||||
if (exchange === 'SSE' && ws && ws.readyState === WebSocket.OPEN) {
|
||||
ws.send(JSON.stringify({
|
||||
action: 'subscribe',
|
||||
channels: ['stock', 'index'],
|
||||
codes: [baseCode],
|
||||
codes: [baseCode], // 发送给 WS 用不带后缀的代码
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -501,14 +525,15 @@ export const useRealtimeQuote = (codes: string[] = []): UseRealtimeQuoteReturn =
|
||||
}, [createConnection]);
|
||||
|
||||
const unsubscribe = useCallback((code: string) => {
|
||||
const baseCode = normalizeCode(code);
|
||||
const exchange = getExchange(code);
|
||||
// 确保使用带后缀的完整代码
|
||||
const fullCode = code.includes('.') ? code : `${code}.${exchange === 'SSE' ? 'SH' : 'SZ'}`;
|
||||
|
||||
subscribedCodes.current[exchange].delete(baseCode);
|
||||
subscribedCodes.current[exchange].delete(fullCode);
|
||||
|
||||
setQuotes(prev => {
|
||||
const updated = { ...prev };
|
||||
delete updated[baseCode];
|
||||
delete updated[fullCode]; // 删除时也用带后缀的 key
|
||||
return updated;
|
||||
});
|
||||
|
||||
@@ -522,35 +547,40 @@ export const useRealtimeQuote = (codes: string[] = []): UseRealtimeQuoteReturn =
|
||||
}, []);
|
||||
|
||||
// 初始化和 codes 变化处理
|
||||
// 注意:codes 现在是带后缀的完整代码(如 000001.SH)
|
||||
useEffect(() => {
|
||||
if (!codes || codes.length === 0) return;
|
||||
|
||||
// 使用带后缀的完整代码作为内部 key
|
||||
const newSseCodes = new Set<string>();
|
||||
const newSzseCodes = new Set<string>();
|
||||
|
||||
codes.forEach(code => {
|
||||
const baseCode = normalizeCode(code);
|
||||
const exchange = getExchange(code);
|
||||
// 确保代码带后缀
|
||||
const fullCode = code.includes('.') ? code : `${code}.${exchange === 'SSE' ? 'SH' : 'SZ'}`;
|
||||
if (exchange === 'SSE') {
|
||||
newSseCodes.add(baseCode);
|
||||
newSseCodes.add(fullCode);
|
||||
} else {
|
||||
newSzseCodes.add(baseCode);
|
||||
newSzseCodes.add(fullCode);
|
||||
}
|
||||
});
|
||||
|
||||
// 更新上交所订阅
|
||||
const oldSseCodes = subscribedCodes.current.SSE;
|
||||
const sseToAdd = [...newSseCodes].filter(c => !oldSseCodes.has(c));
|
||||
// 发送给 WebSocket 的代码需要去掉后缀
|
||||
const sseToAddBase = sseToAdd.map(c => normalizeCode(c));
|
||||
|
||||
if (sseToAdd.length > 0 || newSseCodes.size !== oldSseCodes.size) {
|
||||
subscribedCodes.current.SSE = newSseCodes;
|
||||
const ws = wsRefs.current.SSE;
|
||||
|
||||
if (ws && ws.readyState === WebSocket.OPEN && sseToAdd.length > 0) {
|
||||
if (ws && ws.readyState === WebSocket.OPEN && sseToAddBase.length > 0) {
|
||||
ws.send(JSON.stringify({
|
||||
action: 'subscribe',
|
||||
channels: ['stock', 'index'],
|
||||
codes: sseToAdd,
|
||||
codes: sseToAddBase,
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -582,7 +612,7 @@ export const useRealtimeQuote = (codes: string[] = []): UseRealtimeQuoteReturn =
|
||||
}
|
||||
}
|
||||
|
||||
// 清理已取消订阅的 quotes
|
||||
// 清理已取消订阅的 quotes(使用带后缀的完整代码)
|
||||
const allNewCodes = new Set([...newSseCodes, ...newSzseCodes]);
|
||||
setQuotes(prev => {
|
||||
const updated: QuotesMap = {};
|
||||
|
||||
Reference in New Issue
Block a user