feat: 新增大盘指数

This commit is contained in:
hzm
2026-03-15 00:03:21 +08:00
parent c85e0021cd
commit bc5ed496aa
6 changed files with 1065 additions and 6 deletions

View File

@@ -513,6 +513,91 @@ export const fetchShanghaiIndexDate = async () => {
});
};
/** 大盘指数项name, code, price, change, changePercent
* 同时用于:
* - qt.gtimg.cn 实时快照code 用于 q= 参数varKey 为全局变量名)
* - 分时 mini 图code 传给 minute/query当不支持分时时会自动回退占位折线
*
* 参照产品图:覆盖主要 A 股宽基 + 创业/科创 + 部分海外与港股指数。
*/
const MARKET_INDEX_KEYS = [
// 行 1上证 / 深证
{ code: 'sh000001', varKey: 'v_sh000001', name: '上证指数' },
{ code: 'sh000016', varKey: 'v_sh000016', name: '上证50' },
{ code: 'sz399001', varKey: 'v_sz399001', name: '深证成指' },
{ code: 'sz399330', varKey: 'v_sz399330', name: '深证100' },
// 行 2北证 / 沪深300 / 创业板
{ code: 'bj899050', varKey: 'v_bj899050', name: '北证50' },
{ code: 'sh000300', varKey: 'v_sh000300', name: '沪深300' },
{ code: 'sz399006', varKey: 'v_sz399006', name: '创业板指' },
{ code: 'sz399102', varKey: 'v_sz399102', name: '创业板综' },
// 行 3创业板 50 / 科创
{ code: 'sz399673', varKey: 'v_sz399673', name: '创业板50' },
{ code: 'sh000688', varKey: 'v_sh000688', name: '科创50' },
{ code: 'sz399005', varKey: 'v_sz399005', name: '中小100' },
// 行 4中证系列
{ code: 'sh000905', varKey: 'v_sh000905', name: '中证500' },
{ code: 'sh000906', varKey: 'v_sh000906', name: '中证800' },
{ code: 'sh000852', varKey: 'v_sh000852', name: '中证1000' },
{ code: 'sh000903', varKey: 'v_sh000903', name: '中证A100' },
// 行 5等权 / 国证 / 纳指
{ code: 'sh000932', varKey: 'v_sh000932', name: '500等权' },
{ code: 'sz399303', varKey: 'v_sz399303', name: '国证2000' },
{ code: 'usIXIC', varKey: 'v_usIXIC', name: '纳斯达克' },
{ code: 'usNDX', varKey: 'v_usNDX', name: '纳斯达克100' },
// 行 6美股三大 + 恒生
{ code: 'usINX', varKey: 'v_usINX', name: '标普500' },
{ code: 'usDJI', varKey: 'v_usDJI', name: '道琼斯' },
{ code: 'hkHSI', varKey: 'v_hkHSI', name: '恒生指数' },
{ code: 'hkHSTECH', varKey: 'v_hkHSTECH', name: '恒生科技指数' },
];
function parseIndexRaw(data) {
if (!data || typeof data !== 'string') return null;
const parts = data.split('~');
if (parts.length < 33) return null;
const name = parts[1] || '';
const price = parseFloat(parts[3], 10);
const change = parseFloat(parts[31], 10);
const changePercent = parseFloat(parts[32], 10);
if (Number.isNaN(price)) return null;
return {
name,
price: Number.isFinite(price) ? price : 0,
change: Number.isFinite(change) ? change : 0,
changePercent: Number.isFinite(changePercent) ? changePercent : 0,
};
}
export const fetchMarketIndices = async () => {
if (typeof window === 'undefined' || typeof document === 'undefined') return [];
return new Promise((resolve, reject) => {
const script = document.createElement('script');
const codes = MARKET_INDEX_KEYS.map((item) => item.code).join(',');
script.src = `https://qt.gtimg.cn/q=${codes}&_t=${Date.now()}`;
script.onload = () => {
const list = MARKET_INDEX_KEYS.map(({ name: defaultName, varKey }) => {
const raw = window[varKey];
const parsed = parseIndexRaw(raw);
if (!parsed) return { name: defaultName, code: '', price: 0, change: 0, changePercent: 0 };
return { ...parsed, code: varKey.replace('v_', '') };
});
if (document.body.contains(script)) document.body.removeChild(script);
resolve(list);
};
script.onerror = () => {
if (document.body.contains(script)) document.body.removeChild(script);
reject(new Error('指数数据加载失败'));
};
document.body.appendChild(script);
});
};
export const fetchLatestRelease = async () => {
const url = process.env.NEXT_PUBLIC_GITHUB_LATEST_RELEASE_URL;
if (!url) return null;