解决了上证指数无法实时显示的问题

This commit is contained in:
Sebastian
2026-02-02 10:50:18 +08:00
parent 2fe01cba3c
commit 80827135b8
16 changed files with 202 additions and 96 deletions

View File

@@ -84,6 +84,16 @@ def get_a_volume_7days():
return jsonify(service.get_a_volume_7days())
@fund_master_bp.route('/indices/intraday', methods=['GET'])
def get_indices_intraday():
"""
获取多指数分时数据上证、深证、沪深300
GET /api/market/indices/intraday
"""
service = get_fund_master_service()
return jsonify(service.get_indices_intraday())
@fund_master_bp.route('/sse', methods=['GET'])
def get_sse_30min():
"""

View File

@@ -552,75 +552,108 @@ class FundMasterService:
except Exception as e:
return {"success": False, "error": str(e), "data": []}
# ==================== 近30分钟上证指数 ====================
def get_sse_30min(self) -> dict:
# ==================== 市场指数分时数据 ====================
def _get_eastmoney_intraday(self, secid: str, name: str) -> list:
"""
获取近30分钟上证指数分时数据
数据源:百度股市通
获取东方财富分时数据(内部通用方法)
"""
try:
url = "http://push2.eastmoney.com/api/qt/stock/trends2/get"
params = {
"fields1": "f1,f2,f3,f4,f5,f6,f7,f8,f9,f10,f11,f12,f13",
"fields2": "f51,f52,f53,f54,f55,f56,f57,f58",
"secid": secid,
"ndays": "1",
"iscr": "0",
"iscca": "0"
}
response = requests.get(url, params=params, timeout=10)
data = response.json()
if data and data.get("data") and data["data"].get("trends"):
trends = data["data"]["trends"]
pre_close = data["data"].get("prePrice", 0)
result = []
for point in trends:
# 格式: time, open, close, high, low, volume, amount, avg
parts = point.split(",")
if len(parts) >= 3:
time_str = parts[0].split(" ")[1] # 取 HH:MM
price = float(parts[2])
# 计算涨跌
change = 0
change_pct = "0.00%"
if pre_close:
change = round(price - pre_close, 2)
pct = (change / pre_close) * 100
change_pct = f"{round(pct, 2)}%"
# 成交量处理
volume = parts[5]
try:
vol_num = float(volume)
if vol_num > 10000:
volume = f"{round(vol_num / 10000, 2)}"
except:
pass
result.append({
"time": time_str,
"price": str(price),
"change": f"{'+' if change > 0 else ''}{change}",
"change_pct": change_pct,
"volume": volume
})
return result
return []
except Exception as e:
print(f"Error fetching intraday for {secid}: {e}")
return []
def get_indices_intraday(self) -> dict:
"""
获取多指数分时数据上证、深证、沪深300
Returns:
dict: {'success': bool, 'data': list, 'update_time': str}
dict: {'sh': [], 'sz': [], 'hs300': [], 'update_time': str}
"""
cache_key = 'sse_30min'
cache_key = 'indices_intraday'
cached = self._get_cache(cache_key)
if cached:
return cached
sh_data = self._get_eastmoney_intraday("1.000001", "上证指数")
sz_data = self._get_eastmoney_intraday("0.399001", "深证成指")
hs300_data = self._get_eastmoney_intraday("1.000300", "沪深300")
try:
url = "https://finance.pae.baidu.com/vapi/v1/getquotation"
params = {
"srcid": "5353",
"all": "1",
"pointType": "string",
"group": "quotation_index_minute",
"query": "000001",
"code": "000001",
"market_type": "ab",
"newFormat": "1",
"name": "上证指数",
"finClientType": "pc"
data = {
"success": True,
"data": {
"sh": sh_data,
"sz": sz_data,
"hs300": hs300_data
},
"update_time": datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
}
self._set_cache(cache_key, data, 'sse_30min') # 复用 sse_30min 的 TTL (1分钟)
return data
def get_sse_30min(self) -> dict:
"""
获取上证指数分时数据(兼容旧接口,但提供全天数据)
"""
# 直接复用新的分时数据获取逻辑,但只返回上证数据
full_data = self.get_indices_intraday()
if full_data["success"]:
return {
"success": True,
"data": full_data["data"]["sh"],
"update_time": full_data["update_time"]
}
response = self.baidu_session.get(url, params=params, timeout=10, verify=False)
if str(response.json().get("ResultCode")) == "0":
market_data = response.json()["Result"]["newMarketData"]["marketData"][0]["p"]
points = market_data.split(";")[-30:] # 最近30分钟
result = []
for point in points:
parts = point.split(",")
if len(parts) >= 6:
# 成交量、成交额单位转换
volume = parts[4]
amount = parts[5]
try:
volume = f"{round(float(volume) / 10000, 2)}万手"
amount = f"{round(float(amount) / 10000 / 10000, 2)}亿"
except:
pass
result.append({
"time": parts[0],
"price": parts[1],
"change": parts[2],
"change_pct": f"{parts[3]}%",
"volume": volume,
"amount": amount
})
data = {
"success": True,
"data": result,
"update_time": datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
}
self._set_cache(cache_key, data, 'sse_30min')
return data
return {"success": False, "error": "获取上证指数数据失败", "data": []}
except Exception as e:
return {"success": False, "error": str(e), "data": []}
return {"success": False, "error": "获取上证指数数据失败", "data": []}
# ==================== 汇总数据接口 ====================
def get_market_overview(self) -> dict:

View File

@@ -247,8 +247,8 @@ export default {
<style>
:root {
--primary-color: #667eea;
--primary-gradient: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
--primary-color: #7B8D9E;
--primary-gradient: linear-gradient(135deg, #9CADBD 0%, #7B8D9E 100%);
--success-color: #10b981;
--danger-color: #ef4444;
--warning-color: #f59e0b;

View File

@@ -324,7 +324,7 @@ defineExpose({
}
.analyze-btn {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
background: linear-gradient(135deg, #9CADBD 0%, #7B8D9E 100%);
color: white;
border: none;
padding: 6px 12px;

View File

@@ -105,17 +105,17 @@ export default {
},
splitArea: {
areaStyle: {
color: ['rgba(102, 126, 234, 0.05)', 'rgba(102, 126, 234, 0.1)']
color: ['rgba(123, 141, 158, 0.05)', 'rgba(123, 141, 158, 0.1)']
}
},
axisLine: {
lineStyle: {
color: 'rgba(102, 126, 234, 0.3)'
color: 'rgba(123, 141, 158, 0.3)'
}
},
splitLine: {
lineStyle: {
color: 'rgba(102, 126, 234, 0.3)'
color: 'rgba(123, 141, 158, 0.3)'
}
}
},
@@ -125,14 +125,14 @@ export default {
value: data,
name: '能力评分',
areaStyle: {
color: 'rgba(102, 126, 234, 0.3)'
color: 'rgba(123, 141, 158, 0.3)'
},
lineStyle: {
color: '#667eea',
color: '#7B8D9E',
width: 2
},
itemStyle: {
color: '#667eea'
color: '#7B8D9E'
}
}]
}]
@@ -174,7 +174,7 @@ export default {
}
.card-header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
background: linear-gradient(135deg, #9CADBD 0%, #7B8D9E 100%);
padding: 10px 14px;
flex-shrink: 0;
display: flex;

View File

@@ -189,7 +189,7 @@ export default {
}
.card-header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
background: linear-gradient(135deg, #9CADBD 0%, #7B8D9E 100%);
color: white;
padding: 12px 16px;
flex-shrink: 0;

View File

@@ -288,7 +288,7 @@ export default {
<style scoped>
.fund-basic-info {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
background: linear-gradient(135deg, #9CADBD 0%, #7B8D9E 100%);
padding: 24px;
border-radius: 12px;
box-shadow: 0 4px 12px rgba(0,0,0,0.15);

View File

@@ -189,7 +189,7 @@ export default {
}
.card-header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
background: linear-gradient(135deg, #9CADBD 0%, #7B8D9E 100%);
padding: 12px 16px;
flex-shrink: 0;
}

View File

@@ -262,7 +262,7 @@ export default {
}
.card-header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
background: linear-gradient(135deg, #9CADBD 0%, #7B8D9E 100%);
padding: 12px 16px;
flex-shrink: 0;
}

View File

@@ -94,7 +94,7 @@ export default {
}
.card-header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
background: linear-gradient(135deg, #9CADBD 0%, #7B8D9E 100%);
padding: 10px 14px;
display: flex;
justify-content: space-between;

View File

@@ -281,7 +281,7 @@ export default {
}
.card-header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
background: linear-gradient(135deg, #9CADBD 0%, #7B8D9E 100%);
color: white;
padding: 10px 16px;
flex-shrink: 0;
@@ -320,7 +320,7 @@ export default {
.range-btn.active {
background: white;
color: #667eea;
color: #7B8D9E;
font-weight: 600;
}

View File

@@ -145,12 +145,12 @@ export default {
}
.period-tab:hover {
border-color: #667eea;
color: #667eea;
border-color: #7B8D9E;
color: #7B8D9E;
}
.period-tab.active {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
background: linear-gradient(135deg, #9CADBD 0%, #7B8D9E 100%);
color: white;
border-color: transparent;
}

View File

@@ -158,7 +158,7 @@ export default {
}
.card-header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
background: linear-gradient(135deg, #9CADBD 0%, #7B8D9E 100%);
color: white;
padding: 12px 16px;
flex-shrink: 0;

View File

@@ -206,7 +206,7 @@ export default {
}
.card-header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
background: linear-gradient(135deg, #9CADBD 0%, #7B8D9E 100%);
padding: 10px 16px;
flex-shrink: 0;
}

View File

@@ -1,14 +1,24 @@
<template>
<div class="market-overview-container">
<!-- 1. 近30分钟上证指数 (置顶 & 折线图) -->
<!-- 1. 市场指数实时走势 (置顶 & 折线图) -->
<div class="market-section" v-if="showSSE30Min">
<div class="section-header">
<h3>📉 上证指数实时走势 (近30分)</h3>
<h3>📉 市场指数实时走势</h3>
<div class="tab-group">
<span
v-for="tab in tabs"
:key="tab.key"
:class="{ active: activeTab === tab.key }"
@click="activeTab = tab.key"
>
{{ tab.name }}
</span>
</div>
<span class="update-tag" v-if="updateTime">{{ updateTime.split(' ')[1] }} 更新</span>
</div>
<div class="chart-container sse-chart-container">
<v-chart class="chart" :option="sseOption" autoresize v-if="sse30Min.length" />
<div v-else class="empty-state">A股未开盘</div>
<v-chart class="chart" :option="currentChartOption" autoresize v-if="hasCurrentData" />
<div v-else class="empty-state">暂无数据 ({{ activeTabName }})</div>
</div>
</div>
@@ -154,11 +164,22 @@ export default {
const goldRealtime = ref([])
const goldHistory = ref([])
const aVolume = ref([])
const sse30Min = ref([])
const updateTime = ref('')
const goldHistoryExpanded = ref(true)
let refreshTimer = null
// 指数分时数据
const indicesIntraday = ref({ sh: [], sz: [], hs300: [] })
const activeTab = ref('sh')
const tabs = [
{ key: 'sh', name: '上证指数' },
{ key: 'sz', name: '深证成指' },
{ key: 'hs300', name: '沪深300' }
]
const activeTabName = computed(() => tabs.find(t => t.key === activeTab.value)?.name || '')
const hasCurrentData = computed(() => indicesIntraday.value[activeTab.value]?.length > 0)
// 指数分组
const indices = computed(() => {
const all = marketIndex.value
@@ -169,12 +190,13 @@ export default {
}
})
// 上证指数图表配置
const sseOption = computed(() => {
if (!sse30Min.value.length) return {}
// 当前选中的指数图表配置
const currentChartOption = computed(() => {
const data = indicesIntraday.value[activeTab.value]
if (!data || !data.length) return {}
const times = sse30Min.value.map(i => i.time.split(' ')[1] || i.time)
const prices = sse30Min.value.map(i => parseFloat(i.price))
const times = data.map(i => i.time)
const prices = data.map(i => parseFloat(i.price))
// 计算涨跌色:基于第一笔数据
const basePrice = prices[0]
const isUp = prices[prices.length - 1] >= basePrice
@@ -187,7 +209,7 @@ export default {
formatter: (params) => {
const p = params[0]
if (!p) return ''
const item = sse30Min.value[p.dataIndex]
const item = data[p.dataIndex]
return `
<div>${item.time}</div>
<div style="font-weight:bold;color:${color}">${item.price}</div>
@@ -297,9 +319,15 @@ export default {
if (data.market_index?.success) marketIndex.value = data.market_index.data
if (data.gold_realtime?.success) goldRealtime.value = data.gold_realtime.data
if (data.a_volume_7days?.success) aVolume.value = data.a_volume_7days.data.reverse() // 按时间正序
if (data.sse_30min?.success) sse30Min.value = data.sse_30min.data
updateTime.value = data.update_time
}
// 获取多指数分时数据
const intradayRes = await marketAPI.getIndicesIntraday()
if (intradayRes.data.success) {
indicesIntraday.value = intradayRes.data.data
}
if (props.showGoldHistory) {
const historyRes = await marketAPI.getGoldHistory(10)
if (historyRes.data.success) goldHistory.value = historyRes.data.data
@@ -344,9 +372,11 @@ export default {
loading, fetchAll,
marketIndex, indices,
goldRealtime, goldHistory, goldHistoryExpanded,
aVolume, updateTime, sse30Min,
aVolume, updateTime,
formatDate, getChangeClass, getUpDnClass,
sseOption, volumeOption
volumeOption,
// New returns
tabs, activeTab, activeTabName, hasCurrentData, currentChartOption
}
}
}
@@ -381,6 +411,34 @@ export default {
color: #333;
}
.tab-group {
display: flex;
gap: 8px;
margin-left: 16px;
flex: 1;
}
.tab-group span {
font-size: 0.85em;
color: #666;
cursor: pointer;
padding: 2px 8px;
border-radius: 4px;
transition: all 0.2s;
user-select: none;
}
.tab-group span:hover {
color: #1890ff;
background: #e6f7ff;
}
.tab-group span.active {
color: #1890ff;
font-weight: bold;
background: #e6f7ff;
}
/* 上证指数 */
.sse-chart-container {
height: 200px;

View File

@@ -239,6 +239,11 @@ export const marketAPI = {
// 获取近30分钟上证指数
getSSE30min() {
return api.get('/market/sse')
},
// 获取多指数分时数据
getIndicesIntraday() {
return api.get('/market/indices/intraday')
}
}