update pay ui

This commit is contained in:
2025-12-17 17:29:08 +08:00
parent 8def7f355b
commit 697c366e88
7 changed files with 850 additions and 473 deletions

View File

@@ -0,0 +1,173 @@
/**
* Company 页面数据加载 Hook
* - 使用 axios 请求
* - 懒加载策略
* - 自动取消请求
*/
import { useState, useEffect, useCallback, useRef } from 'react';
import { useToast } from '@chakra-ui/react';
import axios from '@utils/axiosConfig';
import { logger } from '@utils/logger';
import { useAuth } from '@contexts/AuthContext';
import type {
StockInfo,
WatchlistItem,
UseCompanyDataReturn,
ApiResponse,
} from '../types';
interface UseCompanyDataOptions {
stockCode: string;
/** 是否自动加载股票信息 */
autoLoadStockInfo?: boolean;
/** 是否自动加载自选股状态 */
autoLoadWatchlist?: boolean;
}
/**
* Company 页面数据管理 Hook
*/
export const useCompanyData = ({
stockCode,
autoLoadStockInfo = true,
autoLoadWatchlist = true,
}: UseCompanyDataOptions): UseCompanyDataReturn => {
// 状态
const [stockInfo, setStockInfo] = useState<StockInfo | null>(null);
const [stockInfoLoading, setStockInfoLoading] = useState(false);
const [isInWatchlist, setIsInWatchlist] = useState(false);
const [watchlistLoading, setWatchlistLoading] = useState(false);
// Hooks
const toast = useToast();
const { isAuthenticated } = useAuth();
// AbortController 用于取消请求
const abortControllerRef = useRef<AbortController | null>(null);
/**
* 加载股票基本信息
*/
const loadStockInfo = useCallback(async () => {
if (!stockCode || stockCode.length !== 6) return;
// 取消之前的请求
abortControllerRef.current?.abort();
abortControllerRef.current = new AbortController();
setStockInfoLoading(true);
try {
const { data } = await axios.get<ApiResponse<StockInfo>>(
`/api/financial/stock-info/${stockCode}`,
{ signal: abortControllerRef.current.signal }
);
if (data.success && data.data) {
setStockInfo(data.data);
}
} catch (error: any) {
if (error.name === 'CanceledError') return;
logger.error('useCompanyData', 'loadStockInfo', error, { stockCode });
} finally {
setStockInfoLoading(false);
}
}, [stockCode]);
/**
* 加载自选股状态
*/
const loadWatchlistStatus = useCallback(async () => {
if (!isAuthenticated) {
setIsInWatchlist(false);
return;
}
try {
const { data } = await axios.get<ApiResponse<WatchlistItem[]>>(
'/api/account/watchlist'
);
if (data.success && Array.isArray(data.data)) {
const codes = new Set(data.data.map((item) => item.stock_code));
setIsInWatchlist(codes.has(stockCode));
}
} catch (error: any) {
logger.error('useCompanyData', 'loadWatchlistStatus', error);
setIsInWatchlist(false);
}
}, [stockCode, isAuthenticated]);
/**
* 切换自选股状态
*/
const toggleWatchlist = useCallback(async () => {
if (!stockCode) {
toast({ title: '无效的股票代码', status: 'error', duration: 2000 });
return;
}
if (!isAuthenticated) {
toast({ title: '请先登录后再加入自选', status: 'warning', duration: 2000 });
return;
}
setWatchlistLoading(true);
try {
if (isInWatchlist) {
// 移除自选
await axios.delete(`/api/account/watchlist/${stockCode}`);
setIsInWatchlist(false);
toast({ title: '已从自选移除', status: 'info', duration: 1500 });
} else {
// 添加自选
await axios.post('/api/account/watchlist', { stock_code: stockCode });
setIsInWatchlist(true);
toast({ title: '已加入自选', status: 'success', duration: 1500 });
}
} catch (error: any) {
logger.error('useCompanyData', 'toggleWatchlist', error, { stockCode });
toast({ title: '操作失败,请稍后重试', status: 'error', duration: 2000 });
} finally {
setWatchlistLoading(false);
}
}, [stockCode, isAuthenticated, isInWatchlist, toast]);
/**
* 刷新股票信息
*/
const refreshStockInfo = useCallback(async () => {
await loadStockInfo();
}, [loadStockInfo]);
// 自动加载股票信息
useEffect(() => {
if (autoLoadStockInfo) {
loadStockInfo();
}
return () => {
abortControllerRef.current?.abort();
};
}, [autoLoadStockInfo, loadStockInfo]);
// 自动加载自选股状态
useEffect(() => {
if (autoLoadWatchlist) {
loadWatchlistStatus();
}
}, [autoLoadWatchlist, loadWatchlistStatus]);
return {
stockInfo,
stockInfoLoading,
isInWatchlist,
watchlistLoading,
toggleWatchlist,
refreshStockInfo,
};
};
export default useCompanyData;