diff --git a/src/app.py b/src/app.py
index d10e65a..7690daa 100644
--- a/src/app.py
+++ b/src/app.py
@@ -2999,7 +2999,7 @@ def run_batch_stock_price_collection():
@app.route('/scheduler/batch_hk_stock_price/collection', methods=['GET'])
def run_batch_hk_stock_price_collection():
- """批量采集A股行情并保存到数据库"""
+ """批量采集港股行情并保存到数据库"""
try:
fetch_and_store_hk_stock_data()
return jsonify({"status": "success", "message": "批量采集A股行情并保存到数据库成功"})
@@ -3039,24 +3039,39 @@ def get_portfolio_industry_allocation():
def get_notice_list():
"""获取重要提醒列表"""
try:
- # 模拟数据 - 实际项目中应该从数据库或外部API获取
- mock_notices = [
- "上证指数突破3200点,市场情绪回暖",
- "北向资金今日净流入85.6亿元",
- "科技板块PE估值处于历史低位",
- "新能源概念股集体上涨,涨幅超3%",
- "医药板块回调,建议关注低吸机会",
- "融资融券余额连续三日增长",
- "消费板块资金流入明显",
- "市场恐贪指数回升至65",
- "机器人概念板块技术面突破",
- "先进封装概念获政策支持"
- ]
+ # 导入提醒服务
+ from src.valuation_analysis.notice_service import NoticeService
- return jsonify({
- "status": "success",
- "data": mock_notices
- })
+ # 创建提醒服务实例
+ notice_service = NoticeService()
+
+ # 获取动态提醒数据
+ result = notice_service.get_dynamic_notices()
+
+ if result.get("success"):
+ return jsonify({
+ "status": "success",
+ "data": result["data"]
+ })
+ else:
+ # 如果动态提醒失败,返回默认提醒
+ logger.warning(f"动态提醒获取失败: {result.get('message')},使用默认提醒")
+ default_notices = [
+ "📈 上证指数突破3200点,市场情绪回暖",
+ "💰 北向资金今日净流入85.6亿元",
+ "📊 科技板块PE估值处于历史低位",
+ "🔥 新能源概念股集体上涨,涨幅超3%",
+ "⚠️ 医药板块回调,建议关注低吸机会",
+ "📈 融资融券余额连续三日增长",
+ "💰 消费板块资金流入明显",
+ "📊 市场恐贪指数回升至65",
+ "🤖 机器人概念板块技术面突破",
+ "📦 先进封装概念获政策支持"
+ ]
+ return jsonify({
+ "status": "success",
+ "data": default_notices
+ })
except Exception as e:
logger.error(f"获取提醒列表失败: {str(e)}")
return jsonify({'status': 'error', 'message': str(e)})
diff --git a/src/fundamentals_llm/fundamental_analysis.py b/src/fundamentals_llm/fundamental_analysis.py
index c020b66..00fde66 100644
--- a/src/fundamentals_llm/fundamental_analysis.py
+++ b/src/fundamentals_llm/fundamental_analysis.py
@@ -99,7 +99,7 @@ class FundamentalAnalyzer:
self.chat_bot = ChatBot(model_type="online_bot")
# 使用离线模型进行其他分析
self.offline_bot = OfflineChatBot(platform="volc", model_type="offline_model")
- # 千问打杂
+ # GLM打杂
# self.offline_bot_tl_qw = OfflineChatBot(platform="tl_qw_private", model_type="qwq")
self.offline_bot_tl_qw = OfflineChatBot(platform="tl_qw_private", model_type="GLM")
diff --git a/src/scripts/config.py b/src/scripts/config.py
index aaff9b9..74562f7 100644
--- a/src/scripts/config.py
+++ b/src/scripts/config.py
@@ -11,7 +11,7 @@ XUEQIU_HEADERS = {
'Accept-Encoding': 'gzip, deflate, br, zstd',
'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
'Client-Version': 'v2.44.75',
- 'Cookie': 'cookiesu=811743062689927; device_id=33fa3c7fca4a65f8f4354e10ed6b7470; smidV2=20250327160437f244626e8b47ca2a7992f30f389e4e790074ae48656a22f10; HMACCOUNT=8B64A2E3C307C8C0; s=c611ttmqlj; xq_is_login=1; u=8493411634; bid=4065a77ca57a69c83405d6e591ab5449_m8r2nhs8; __utma=1.434320573.1747189698.1747189698.1747189698.1; __utmc=1; __utmz=1.1747189698.1.1.utmcsr=(direct)|utmccn=(direct)|utmcmd=(none); snbim_minify=true; _c_WBKFRo=dsWgHR8i8KGPbIyhFlN51PHOzVuuNytvUAFppfkD; _nb_ioWEgULi=; Hm_lvt_1db88642e346389874251b5a1eded6e3=1751936369; xq_a_token=ada154d4707b8d3f8aa521ff0c960aa7f81cbf9e; xqat=ada154d4707b8d3f8aa521ff0c960aa7f81cbf9e; xq_id_token=eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJ1aWQiOjg0OTM0MTE2MzQsImlzcyI6InVjIiwiZXhwIjoxNzU2MDAyNjgyLCJjdG0iOjE3NTM0MTA2ODI0MTQsImNpZCI6ImQ5ZDBuNEFadXAifQ.AlnzQSY7oGKGABfaQcFLg0lAJsDdvBMiwUbgpCMCBlbx6VZPKhzERxWiylQb4dFIyyECvRRJ73SbO9cD46fAqgzOgTxArNHtTKD4lQapTnyb11diDADnpb_nzzaRr4k_BYQRKXWtcJxdUMzde2WLy-eAkSf76QkXmKrwS3kvRm5gfqhdye44whw5XMEGoZ_lXHzGLWGz_PludHZp6W3v-wwZc_0wLU6cTb_KdrwWUWT_8jw5JHXnJEmuZmQI8QWf60DtiHIYCYXarxv8XtyHK7lLKhIAa3C2QmGWw5wv2HGz4I5DPqm2uMPKumgkQxycfAk56-RWviLZ8LAPF-XcbA; xq_r_token=92527e51353f90ba14d5fd16581e5a7a2780baa2; acw_tc=0a27aa0f17542694317833912e006564153fcd1bb89f49a865e382d9953601; is_overseas=0; Hm_lpvt_1db88642e346389874251b5a1eded6e3=1754269439; .thumbcache_f24b8bbe5a5934237bbc0eda20c1b6e7=HS+RscPvXRUz1ypZekks1pgGkAHHlHsHVuftTbDQCbUUaFqtm9BV4h7ghR2d5Nh+YD29otSyz2svRiKWvOJqgQ%3D%3D; ssxmod_itna=1-eqGxBDnGKYuxcD4kDRgxYq7ueYKS8DBP01Dp2xQyP08D60DB40Q0P6Dw1PtDCuq4wQWiYMrK4N4hGRtDl=YoDZDGFdDqx0Ei6Fi7HKzYhtBoqzWKjw_wv5YlCZMPO8//1P9PQCNzkOQ4hviDB3DbqDy/dePxYYjDBYD74G_DDeDixdDj4GmDGYtOeDFfCuNq6R5dxDwDB=DmMIbfeDEDG3D0fbeCLRYwDDBDGUFxtaDG4Gf0mDD0wDAo0jooDGWfnu4s6mkeFKN57G3x0tWDBL5QvG3x/lnoGWNVtlfkS2FkPGuDG6Ogl0kDqQO3i2AfP4KGGIm0iBPKY_5leOQDqQe4YwQGDpl0xliO7Gm0DOGDz0G4ixqYw1n0aSpwhixgPXieD1NZcX3ZXDK4rm0IlvYRGImxqnmmlG4eK40w4Am1BqGYeeGn5ixXWa3m2b/DDgi3YD; ssxmod_itna2=1-eqGxBDnGKYuxcD4kDRgxYq7ueYKS8DBP01Dp2xQyP08D60DB40Q0P6Dw1PtDCuq4wQWiYMrK4N4hGbYDiPbY44h7ie03dz7=3xDlouSdLRKl=Q_2YStYQ7OzOy_RBQ1oeziI2pkPsD8RSfPnSw5L7G4xcSPKKMxxoCD6zTiVCud28rNOm2tL7qASSMTjB2GcYPxzSRi94n0Kgjd6C6jKOMh5rMtOfkR2l8TGOPL277=81u9MRkBgIwRxDwx6iYEE4omE9FE1lonhzib3BUC6PD',
+ 'Cookie': 'cookiesu=811743062689927; device_id=33fa3c7fca4a65f8f4354e10ed6b7470; smidV2=20250327160437f244626e8b47ca2a7992f30f389e4e790074ae48656a22f10; HMACCOUNT=8B64A2E3C307C8C0; s=c611ttmqlj; xq_is_login=1; u=8493411634; bid=4065a77ca57a69c83405d6e591ab5449_m8r2nhs8; __utma=1.434320573.1747189698.1747189698.1747189698.1; __utmc=1; __utmz=1.1747189698.1.1.utmcsr=(direct)|utmccn=(direct)|utmcmd=(none); snbim_minify=true; _c_WBKFRo=dsWgHR8i8KGPbIyhFlN51PHOzVuuNytvUAFppfkD; _nb_ioWEgULi=; xq_a_token=ada154d4707b8d3f8aa521ff0c960aa7f81cbf9e; xqat=ada154d4707b8d3f8aa521ff0c960aa7f81cbf9e; xq_id_token=eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJ1aWQiOjg0OTM0MTE2MzQsImlzcyI6InVjIiwiZXhwIjoxNzU2MDAyNjgyLCJjdG0iOjE3NTM0MTA2ODI0MTQsImNpZCI6ImQ5ZDBuNEFadXAifQ.AlnzQSY7oGKGABfaQcFLg0lAJsDdvBMiwUbgpCMCBlbx6VZPKhzERxWiylQb4dFIyyECvRRJ73SbO9cD46fAqgzOgTxArNHtTKD4lQapTnyb11diDADnpb_nzzaRr4k_BYQRKXWtcJxdUMzde2WLy-eAkSf76QkXmKrwS3kvRm5gfqhdye44whw5XMEGoZ_lXHzGLWGz_PludHZp6W3v-wwZc_0wLU6cTb_KdrwWUWT_8jw5JHXnJEmuZmQI8QWf60DtiHIYCYXarxv8XtyHK7lLKhIAa3C2QmGWw5wv2HGz4I5DPqm2uMPKumgkQxycfAk56-RWviLZ8LAPF-XcbA; xq_r_token=92527e51353f90ba14d5fd16581e5a7a2780baa2; acw_tc=1a0c655917546366986673411e68d25d3c69c1719d6d1d6283c7271cc1529f; is_overseas=0; Hm_lvt_1db88642e346389874251b5a1eded6e3=1754636834; Hm_lpvt_1db88642e346389874251b5a1eded6e3=1754636837; .thumbcache_f24b8bbe5a5934237bbc0eda20c1b6e7=Hvg6Ac+qmPnDgzOvFuCePWwm7reK8TPoE9ayL8cyLnFg+Jhg1RJO2WnkeH2T8Q18+iV9bDh+UAq222GxdelHBg%3D%3D; ssxmod_itna=1-eqGxBDnGKYuxcD4kDRgxYq7ueYKS8DBP01Dp2xQyP08D60DB40Q0P6Dw1PtDCuqbKOOQYMxPsMKjqDsqze4GzDiLPGhDBWAFdYjdqN4NCtAoqzWWF2ruqe8bOZqKKFS96SM6sXUGQKhexGLDY=DCuXiieGGU4GwDGoD34DiDDpLD03Db4D_nWrD7ORQMluokjeDQ4GyDiUk3ObDm4DfDDLorA6osQ4DGqDSFcyTxD3DfRb4DDN4CIDu_mDDbObt5jcbUx7OBCGxIeDMixGXzGC4InyRNvDrgjMXvzEKH1aDtqD9_au4XxKdr3NEAEP4KGGpC0inpge_5neOQDqix1oeee4eQvxQ5O7Gv0DOGDz0G4ix_jwP_RUWjiihW9PeGAShXZ=E/ZND6q3mi40weUmXjmvYIzSQzWDW9wsemhYedCrwihQYbKYvWRD3YD; ssxmod_itna2=1-eqGxBDnGKYuxcD4kDRgxYq7ueYKS8DBP01Dp2xQyP08D60DB40Q0P6Dw1PtDCuqbKOOQYMxPsMKe4DWhzmxhTKRDjR_xWs_DDs6KmhfHjRKnZkBxNA3TIO4Arip5wU2kO0SwUfkEzryfSk6Rzud3ARD49fiKFd344obYvCv1lxYhY3qdzQe3vWD',
'Referer': 'https://weibo.com/u/7735765253',
'Sec-Ch-Ua': '"Chromium";v="122", "Not(A:Brand";v="24", "Google Chrome";v="122"',
'Sec-Ch-Ua-Mobile': '?0',
diff --git a/src/static/js/bigscreen_v2.js b/src/static/js/bigscreen_v2.js
new file mode 100644
index 0000000..2f98dd8
--- /dev/null
+++ b/src/static/js/bigscreen_v2.js
@@ -0,0 +1,1195 @@
+$(function() {
+ // 1. 行业持仓占比
+ $.get('/api/portfolio/industry_allocation', function(res) {
+ if(res.status === 'success') {
+ renderPortfolioChart(res.data);
+ } else {
+ $('#portfolioChart').html('
暂无持仓数据
');
+ }
+ }).fail(function() {
+ $('#portfolioChart').html('暂无持仓数据
');
+ });
+
+ // 2. 重要提醒数据
+ $.get('/api/notice/list', function(res) {
+ if(res.status === 'success' && res.data && res.data.length > 0) {
+ updateNoticeBox(res.data);
+ } else {
+ // 如果没有数据,显示暂无提醒
+ updateNoticeBox([]);
+ }
+ }).fail(function() {
+ // 请求失败时,显示暂无提醒
+ updateNoticeBox([]);
+ });
+
+ // 3. 融资融券/恐贪指数
+ $.get('/api/rzrq/chart_data?days=90', function(res) {
+ if(res.status === 'success') renderRzrqChart(res.data);
+ });
+ $.get('/api/fear_greed/data?limit=180', function(res) {
+ if(res.status === 'success') renderFearGreedChart(res.data);
+ });
+
+ // 4. 行业PE和拥挤度(基于持仓行业)
+ function loadIndustryAnalysis() {
+ // 首先获取持仓数据
+ $.get('/api/portfolio/industry_allocation', function(res) {
+ if(res.status === 'success') {
+ const portfolioIndustries = res.data.industries.map(item => item.industry);
+
+ // 定义补齐的行业列表
+ const defaultIndustries = ['半导体', '消费电子', '自动化设备', '光学光电'];
+
+ // 合并持仓行业和默认行业,去重
+ let allIndustries = [...new Set([...portfolioIndustries, ...defaultIndustries])];
+
+ // 取前四个行业
+ const selectedIndustries = allIndustries.slice(0, 4);
+
+ // 定义图表ID映射
+ const chartIds = [
+ {peId: "peChart_xjfz", crowdId: "crowdChart_xjfz", holdingsId: "holdings_xjfz"},
+ {peId: "peChart_xp", crowdId: "crowdChart_xp", holdingsId: "holdings_xp"},
+ {peId: "peChart_xfdz", crowdId: "crowdChart_xfdz", holdingsId: "holdings_xfdz"},
+ {peId: "peChart_jqr", crowdId: "crowdChart_jqr", holdingsId: "holdings_jqr"}
+ ];
+
+ // 更新HTML标题
+ updateIndustryTitles(selectedIndustries);
+
+ // 加载持仓摘要数据
+ $.get('/api/portfolio/summary', function(summaryRes) {
+ if(summaryRes.status === 'success') {
+ const projectDetails = summaryRes.data.project_details;
+
+ // 加载每个行业的分析数据
+ selectedIndustries.forEach((industry, index) => {
+ const chartId = chartIds[index];
+
+ // 加载PE和拥挤度数据
+ $.get(`/api/industry/analysis?industry_name=${encodeURIComponent(industry)}&metric=pe`, function(res) {
+ if(res.status === 'success') {
+ renderPEChart(chartId.peId, res.data);
+ renderCrowdChart(chartId.crowdId, res.data.crowding);
+ }
+ });
+
+ // 加载持仓标的数据
+ loadIndustryHoldings(industry, chartId.holdingsId, projectDetails);
+ });
+ }
+ });
+ }
+ });
+ }
+
+ function loadIndustryHoldings(industry, holdingsId, projectDetails) {
+ // 筛选该行业的持仓标的
+ const industryHoldings = projectDetails.filter(item => item.industry === industry);
+
+ // 按持仓金额排序,取前三名
+ const topHoldings = industryHoldings
+ .sort((a, b) => b.margin_amount - a.margin_amount)
+ .slice(0, 3);
+
+ // 渲染持仓标的
+ renderHoldings(holdingsId, topHoldings, industry);
+ }
+
+ function renderHoldings(holdingsId, holdings, industry) {
+ const container = document.getElementById(holdingsId);
+ if (!container) return;
+
+ if (holdings.length === 0) {
+ container.innerHTML = '暂无持仓
';
+ return;
+ }
+
+ let html = '';
+ holdings.forEach(holding => {
+ const amountInWan = Math.round(holding.margin_amount / 10000);
+ html += `
+
+
${holding.project_name}
+
${amountInWan}万
+
+ `;
+ });
+
+ container.innerHTML = html;
+ }
+
+ function updateIndustryTitles(industries) {
+ const titles = [
+ {peTitle: "peChart_xjfz", crowdTitle: "crowdChart_xjfz", holdingsTitle: "holdings_xjfz"},
+ {peTitle: "peChart_xp", crowdTitle: "crowdChart_xp", holdingsTitle: "holdings_xp"},
+ {peTitle: "peChart_xfdz", crowdTitle: "crowdChart_xfdz", holdingsTitle: "holdings_xfdz"},
+ {peTitle: "peChart_jqr", crowdTitle: "crowdChart_jqr", holdingsTitle: "holdings_jqr"}
+ ];
+
+ industries.forEach((industry, index) => {
+ const title = titles[index];
+ if (title) {
+ // 更新PE分析标题
+ const peTitleElement = document.querySelector(`#${title.peTitle}`).parentElement.querySelector('.chart-title');
+ if (peTitleElement) {
+ peTitleElement.textContent = `${industry}-历史PE分析`;
+ }
+
+ // 更新拥挤度标题 - 使用更精确的选择器
+ const crowdContainer = document.querySelector(`#${title.crowdTitle}`).parentElement;
+ const allTitles = crowdContainer.querySelectorAll('.chart-title');
+ if (allTitles.length >= 2) {
+ // 第二个.chart-title是拥挤度的标题
+ allTitles[1].textContent = `${industry}-拥挤度`;
+ }
+
+ // 更新持仓标的标题
+ const holdingsContainer = document.querySelector(`#${title.holdingsTitle}`).parentElement;
+ const holdingsTitles = holdingsContainer.querySelectorAll('.chart-title');
+ if (holdingsTitles.length >= 3) {
+ // 第三个.chart-title是持仓标的的标题
+ holdingsTitles[2].textContent = `${industry}-持仓标的`;
+ }
+ }
+ });
+ }
+
+ // 调用行业分析加载函数
+ loadIndustryAnalysis();
+
+ // 绑定重置按钮事件
+ $('#resetViewBtn').on('click', function() {
+ resetToDefaultView();
+ });
+
+ // 重置到默认视图
+ function resetToDefaultView() {
+ // 清除全局变量
+ window.currentSelectedIndustry = null;
+
+ // 隐藏重置按钮
+ $('#resetViewBtn').hide();
+
+ // 恢复第二行的原始布局
+ const rowContainer = document.querySelector('.row.d-flex2');
+ rowContainer.innerHTML = `
+
+
+
行业1-历史PE分析
+
+
行业1-拥挤度
+
+
+
+
+
+
+
行业2-历史PE分析
+
+
行业2-拥挤度
+
+
+
+
+
+
+
行业3-历史PE分析
+
+
行业3-拥挤度
+
+
+
+
+
+
+
行业4-历史PE分析
+
+
行业4-拥挤度
+
+
+
+
+ `;
+
+ // 重新加载默认的四个行业分析
+ loadIndustryAnalysis();
+
+ // 移除动态创建的容器
+ ['stockDetailContainer', 'factorContainer', 'holdingContainer', 'correctionContainer'].forEach(id => {
+ const element = document.getElementById(id);
+ if (element) {
+ element.remove();
+ }
+ });
+ }
+
+ // 加载行业详情函数
+ function loadIndustryDetail(industryName) {
+ // 设置全局变量,记录当前选中的行业
+ window.currentSelectedIndustry = industryName;
+
+ // 显示重置按钮
+ $('#resetViewBtn').show();
+
+ // 直接更新持仓详情,因为updateSecondContainer会重新构建整个第二行
+ updateHoldingsDetails(industryName);
+ }
+
+ // 这个函数已经不再使用,删除
+
+ function updateHoldingsDetails(industryName) {
+ // 获取行业持仓数据
+ $.get(`/api/portfolio/industry_holdings?industry_name=${encodeURIComponent(industryName)}`, function(res) {
+ if(res.status === 'success') {
+ const holdings = res.data.holdings;
+ const stockCodes = [...new Set(holdings.map(item => item.stock_code))];
+
+ // 更新其他三个容器为合并的详细信息
+ updateSecondContainer(stockCodes, industryName);
+ updateThirdContainer(stockCodes);
+ updateFourthContainer(stockCodes);
+ }
+ });
+ }
+
+ function updateSecondContainer(stockCodes, industryName) {
+ // 获取第二行的容器
+ const rowContainer = document.querySelector('.row.d-flex2');
+
+ // 清空第二行,只保留第一个容器
+ rowContainer.innerHTML = `
+
+
+
${industryName}-历史PE分析
+
+
${industryName}-拥挤度
+
+
+
+
+ `;
+
+ // 重新加载第一个容器的PE和拥挤度数据
+ $.get(`/api/industry/analysis?industry_name=${encodeURIComponent(industryName)}&metric=pe`, function(res) {
+ if(res.status === 'success') {
+ renderPEChart('peChart_xjfz', res.data);
+ renderCrowdChart('crowdChart_xjfz', res.data.crowding);
+ }
+ });
+
+ // 加载所有股票详情
+ const detailContainer = document.getElementById('stockDetailContainer');
+ loadStockDetails(stockCodes, detailContainer);
+ }
+
+ function updateThirdContainer(stockCodes) {
+ // 这个函数不再使用,但保留以避免错误
+ }
+
+ function updateFourthContainer(stockCodes) {
+ // 这个函数不再使用,但保留以避免错误
+ }
+
+ function loadStockDetails(stockCodes, container) {
+ if (stockCodes.length === 0) {
+ container.innerHTML = '暂无持仓数据
';
+ return;
+ }
+
+ // 显示加载状态
+ container.innerHTML = '加载中...
';
+
+ // 并行获取三种数据
+ let factorData = null;
+ let holdingData = null;
+ let correctionData = {};
+ let loadedCount = 0;
+ const totalRequests = 2 + stockCodes.length; // 因子数据 + 持仓数据 + 每个股票的修正数据
+
+ // 获取因子数据
+ $.get('https://spb.bmbs.tech/api/dify/getStocksTriggerFactor?groupId=default_system_pool', function(res) {
+ if(res.status === 'success') {
+ factorData = res.factorMessages || [];
+ }
+ loadedCount++;
+ if (loadedCount === totalRequests) {
+ renderStockDetails(container, stockCodes, factorData, holdingData, correctionData);
+ }
+ }).fail(function() {
+ loadedCount++;
+ if (loadedCount === totalRequests) {
+ renderStockDetails(container, stockCodes, factorData, holdingData, correctionData);
+ }
+ });
+
+ // 获取持仓数据
+ const currentIndustry = window.currentSelectedIndustry || '';
+ $.get(`/api/portfolio/industry_holdings?industry_name=${encodeURIComponent(currentIndustry)}`, function(res) {
+ if(res.status === 'success') {
+ holdingData = res.data.holdings || [];
+ }
+ loadedCount++;
+ if (loadedCount === totalRequests) {
+ renderStockDetails(container, stockCodes, factorData, holdingData, correctionData);
+ }
+ }).fail(function() {
+ loadedCount++;
+ if (loadedCount === totalRequests) {
+ renderStockDetails(container, stockCodes, factorData, holdingData, correctionData);
+ }
+ });
+
+ // 获取每个股票的修正数据
+ stockCodes.forEach(stockCode => {
+ const convertedCode = convertStockCode(stockCode);
+ $.get(`http://192.168.16.71:8000/corrections/history/${convertedCode}`, function(res) {
+ correctionData[stockCode] = res || [];
+ loadedCount++;
+ if (loadedCount === totalRequests) {
+ renderStockDetails(container, stockCodes, factorData, holdingData, correctionData);
+ }
+ }).fail(function() {
+ correctionData[stockCode] = [];
+ loadedCount++;
+ if (loadedCount === totalRequests) {
+ renderStockDetails(container, stockCodes, factorData, holdingData, correctionData);
+ }
+ });
+ });
+ }
+
+ function renderStockDetails(container, stockCodes, factorData, holdingData, correctionData) {
+ if (stockCodes.length === 0) {
+ container.innerHTML = '暂无持仓数据
';
+ return;
+ }
+
+ let html = '';
+
+ stockCodes.forEach(stockCode => {
+ // 转换股票代码格式用于匹配因子数据 (600584.SH -> SH600584)
+ const factorCode = convertStockCodeForFactor(stockCode);
+ // 获取该股票的因子数据
+ const factorInfo = factorData ? factorData.find(item => item.code === factorCode) : null;
+
+ // 获取该股票的持仓数据
+ const stockHoldings = holdingData ? holdingData.filter(item => item.stock_code === stockCode) : [];
+
+ // 获取该股票的修正数据
+ const stockCorrections = correctionData[stockCode] || [];
+
+ // 从持仓数据中获取股票名称,如果没有则使用因子数据中的名称
+ let stockName = '未知';
+ if (stockHoldings.length > 0 && stockHoldings[0].project_name) {
+ stockName = stockHoldings[0].project_name;
+ } else if (factorInfo && factorInfo.stock) {
+ stockName = factorInfo.stock;
+ }
+
+ html += `
+
+
${stockName} (${stockCode})
+
+
+
+
+
+
持仓情况
+
+ `;
+
+ if (stockHoldings.length > 0) {
+ stockHoldings.forEach((holding, index) => {
+ const notionalWan = Math.round(holding.notional_principal / 10000);
+ const marginWan = Math.round(holding.margin_amount / 10000);
+ const createTime = holding.create_time ? holding.create_time.split(' ')[0] : '未知';
+
+ html += `
+
+
交易${index + 1}: 名义本金${notionalWan}万, 保证金${marginWan}万, 建仓时间${createTime}
+
+ `;
+ });
+ } else {
+ html += '
暂无持仓详情
';
+ }
+
+ html += `
+
+
+
+
+
+
因子情况
+
+ `;
+
+ if (factorInfo && factorInfo.details) {
+ Object.entries(factorInfo.details).forEach(([factor, value]) => {
+ html += `
${factor}: ${value}
`;
+ });
+ } else {
+ html += '
暂无因子数据
';
+ }
+
+ html += `
+
+
+
+
+
+
+
修正事件
+
+ `;
+
+ if (stockCorrections.length > 0) {
+ stockCorrections.forEach((correction, index) => {
+ const createTime = correction.created_at ? correction.created_at.split('T')[0] : '未知';
+
+ // 根据target_field判断显示类型
+ let fieldType = '';
+ if (correction.target_field === 'prediction_peak') {
+ fieldType = '预测峰值';
+ } else if (correction.target_field === 'prediction_trough') {
+ fieldType = '预测谷值';
+ } else {
+ fieldType = correction.target_field; // 如果是其他字段,直接显示
+ }
+
+ html += `
+
+
${fieldType}: 修正数值为${correction.correction_value},修正理由${correction.reason},修正人${correction.corrected_by},修正时间${createTime}。
+
+ `;
+ });
+ } else {
+ html += '
暂无修正记录
';
+ }
+
+ html += `
+
+
+
+ `;
+ });
+
+ html += '
';
+ container.innerHTML = html;
+ }
+
+ // 这个函数已经不再使用,删除
+
+ // 这个函数已经不再使用,删除
+
+ function convertStockCode(stockCode) {
+ // 将 600584.SH 格式转换为 SH600584 格式
+ const parts = stockCode.split('.');
+ if (parts.length === 2) {
+ return `${parts[1]}${parts[0]}`;
+ }
+ return stockCode;
+ }
+
+ function convertStockCodeForFactor(stockCode) {
+ // 将 600584.SH 格式转换为 SH600584 格式 (用于匹配因子数据)
+ const parts = stockCode.split('.');
+ if (parts.length === 2) {
+ return `${parts[1]}${parts[0]}`;
+ }
+ return stockCode;
+ }
+
+ // 行业持仓详情弹窗相关函数
+ function showIndustryHoldingsModal(industry) {
+ const modal = document.getElementById('industryHoldingsModal');
+ const modalTitle = document.getElementById('industryHoldingsTitle');
+ const modalContent = document.getElementById('industryHoldingsContent');
+
+ // 更新标题
+ modalTitle.textContent = `${industry}持仓详情`;
+
+ // 显示加载状态
+ modalContent.innerHTML = '加载中...
';
+
+ // 显示弹窗
+ modal.style.display = 'flex';
+
+ // 阻止背景滚动
+ document.body.style.overflow = 'hidden';
+
+ // 获取行业持仓详情
+ $.get(`/api/portfolio/industry_holdings?industry_name=${encodeURIComponent(industry)}`, function(res) {
+ if(res.status === 'success') {
+ renderIndustryHoldingsDetail(modalContent, res.data);
+ } else {
+ modalContent.innerHTML = `${res.message || '获取数据失败'}
`;
+ }
+ }).fail(function() {
+ modalContent.innerHTML = '网络错误,请稍后重试
';
+ });
+ }
+
+ function closeIndustryHoldingsModal() {
+ const modal = document.getElementById('industryHoldingsModal');
+ modal.style.display = 'none';
+
+ // 恢复背景滚动
+ document.body.style.overflow = 'auto';
+ }
+
+ function renderIndustryHoldingsDetail(container, data) {
+ const { industry_name, total_count, total_margin_amount, holdings } = data;
+
+ let html = `
+
+
${industry_name}持仓概览
+
持仓标的数量: ${total_count}个
+
总保证金金额: ${Math.round(total_margin_amount/10000)}万元
+
+ `;
+
+ if (holdings.length > 0) {
+ // 按股票代码聚合数据
+ const stockGroups = {};
+ holdings.forEach(holding => {
+ const stockCode = holding.stock_code;
+ if (!stockGroups[stockCode]) {
+ stockGroups[stockCode] = {
+ project_name: holding.project_name,
+ stock_code: stockCode,
+ notional_principal: 0,
+ margin_amount: 0,
+ details: [],
+ last_create_time: ''
+ };
+ }
+ stockGroups[stockCode].notional_principal += holding.notional_principal;
+ stockGroups[stockCode].margin_amount += holding.margin_amount;
+ stockGroups[stockCode].details.push(holding);
+
+ // 更新最后建仓时间
+ const createTime = holding.create_time || '';
+ if (createTime > stockGroups[stockCode].last_create_time) {
+ stockGroups[stockCode].last_create_time = createTime;
+ }
+ });
+
+ // 转换为数组并排序
+ const stockList = Object.values(stockGroups).sort((a, b) => b.margin_amount - a.margin_amount);
+
+ html += `
+
+
+
+ 个股名称 |
+ 股票代码 |
+ 名义本金(万元) |
+ 保证金率(%) |
+ 保证金金额(万元) |
+ 最后建仓时间 |
+
+
+
+ `;
+
+ stockList.forEach((stock, index) => {
+ const notionalWan = Math.round(stock.notional_principal / 10000);
+ const marginWan = Math.round(stock.margin_amount / 10000);
+ const lastCreateTime = stock.last_create_time ? stock.last_create_time.split(' ')[0] : '未知';
+
+ html += `
+
+ ▶${stock.project_name} |
+ ${stock.stock_code} |
+ ${notionalWan} |
+ - |
+ ${marginWan} |
+ ${lastCreateTime} |
+
+
+
+
+ ${stock.project_name} (${stock.stock_code}) 交易详情
+
+
+
+ 交易序号 |
+ 名义本金(万元) |
+ 保证金率(%) |
+ 保证金金额(万元) |
+ 建仓时间 |
+
+
+
+ `;
+
+ // 按建仓时间倒序排列详细交易记录
+ const sortedDetails = stock.details.sort((a, b) => {
+ const timeA = a.create_time || '';
+ const timeB = b.create_time || '';
+ return timeB.localeCompare(timeA); // 倒序排列
+ });
+
+ sortedDetails.forEach((detail, detailIndex) => {
+ const detailNotionalWan = Math.round(detail.notional_principal / 10000);
+ const detailMarginWan = Math.round(detail.margin_amount / 10000);
+ const createTime = detail.create_time ? detail.create_time.split(' ')[0] : '未知';
+
+ html += `
+
+ 交易${detailIndex + 1} |
+ ${detailNotionalWan} |
+ ${detail.margin_rate} |
+ ${detailMarginWan} |
+ ${createTime} |
+
+ `;
+ });
+
+ html += `
+
+
+
+ |
+
+ `;
+ });
+
+ html += `
+
+
+ `;
+ } else {
+ html += '暂无持仓数据
';
+ }
+
+ container.innerHTML = html;
+
+ // 绑定展开/收起事件
+ bindExpandEvents();
+ }
+
+ function bindExpandEvents() {
+ const stockRows = document.querySelectorAll('.stock-row');
+ stockRows.forEach(row => {
+ row.addEventListener('click', function() {
+ const stockCode = this.getAttribute('data-stock');
+ const detailRow = document.getElementById(`detail-${stockCode}`);
+ const expandIcon = this.querySelector('.expand-icon');
+
+ if (detailRow.classList.contains('show')) {
+ // 收起
+ detailRow.classList.remove('show');
+ expandIcon.classList.remove('expanded');
+ } else {
+ // 展开
+ detailRow.classList.add('show');
+ expandIcon.classList.add('expanded');
+ }
+ });
+ });
+ }
+
+ // 绑定行业持仓点击事件
+ function bindIndustryHoldingsClick() {
+ // 为每个持仓容器添加点击事件
+ const holdingsContainers = ['holdings_xjfz', 'holdings_xp', 'holdings_xfdz', 'holdings_jqr'];
+ const industries = ['半导体', '自动化设备', '军工电子', '消费电子']; // 默认行业,实际会根据持仓动态更新
+
+ holdingsContainers.forEach((containerId, index) => {
+ const container = document.getElementById(containerId);
+ if (container) {
+ container.style.cursor = 'pointer';
+ container.addEventListener('click', function() {
+ // 获取当前显示的行业名称
+ const industryTitle = this.parentElement.querySelector('.chart-title');
+ if (industryTitle) {
+ const industryName = industryTitle.textContent.split('-')[0];
+ showIndustryHoldingsModal(industryName);
+ }
+ });
+ }
+ });
+ }
+
+ // 在页面加载完成后绑定点击事件
+ setTimeout(bindIndustryHoldingsClick, 2000); // 延迟绑定,确保数据加载完成
+
+ // --- 渲染函数 ---
+ const chartInstances = {};
+
+ function getDefaultNotices() {
+ return [
+ "上证指数突破3200点,市场情绪回暖",
+ "北向资金今日净流入85.6亿元",
+ "科技板块PE估值处于历史低位",
+ "新能源概念股集体上涨,涨幅超3%",
+ "医药板块回调,建议关注低吸机会",
+ "融资融券余额连续三日增长",
+ "消费板块资金流入明显",
+ "市场恐贪指数回升至65",
+ "机器人概念板块技术面突破",
+ "先进封装概念获政策支持"
+ ];
+ }
+
+ function updateNoticeBox(notices) {
+ const noticeContent = document.querySelector('.notice-content');
+ if (!noticeContent) return;
+
+ let html = '';
+
+ // 检查是否有提醒数据
+ if (!notices || notices.length === 0) {
+ html = '暂无重要提醒
';
+ } else {
+ // 只显示一次内容,不再重复
+ notices.forEach(notice => {
+ html += `${notice}
`;
+ });
+ }
+
+ noticeContent.innerHTML = html;
+
+ // 添加鼠标滚轮事件监听
+ const noticeContainer = document.getElementById('noticeBox');
+ if (noticeContainer) {
+ noticeContainer.addEventListener('wheel', function(e) {
+ e.preventDefault(); // 阻止默认滚动行为
+
+ const scrollAmount = e.deltaY > 0 ? 30 : -30; // 向下滚动30px,向上滚动-30px
+ noticeContainer.scrollTop += scrollAmount;
+ });
+ }
+ }
+
+ function renderPortfolioChart(data) {
+ if(!data || !data.industries || !data.total_amount) return;
+ const dom = document.getElementById('portfolioChart');
+ const chart = echarts.init(dom);
+ chartInstances['portfolioChart'] = chart;
+
+ // 更新HTML标题
+ const titleElement = dom.parentElement.querySelector('.chart-title');
+ if (titleElement) {
+ titleElement.textContent = `行业持仓占比 (总持仓: ${Math.round(data.total_amount/10000)}万元)`;
+ }
+
+ // 准备饼图数据
+ const pieData = data.industries.map(item => ({
+ name: item.industry,
+ value: item.amount,
+ itemStyle: {
+ color: item.color
+ }
+ }));
+
+ chart.setOption({
+ title: {
+ show: false
+ },
+ tooltip: {
+ trigger: 'item',
+ formatter: function(params) {
+ const percentage = ((params.value / data.total_amount) * 100).toFixed(1);
+ return `${params.name}
持仓金额: ${Math.round(params.value/10000)}万元
占比: ${percentage}%`;
+ }
+ },
+ legend: {
+ show: false
+ },
+ series: [{
+ name: '行业持仓',
+ type: 'pie',
+ radius: '60%',
+ center: ['50%', '55%'],
+ data: pieData,
+ emphasis: {
+ itemStyle: {
+ shadowBlur: 10,
+ shadowOffsetX: 0,
+ shadowColor: 'rgba(0, 0, 0, 0.5)'
+ }
+ },
+ label: {
+ show: true,
+ position: 'outside',
+ formatter: function(params) {
+ const percentage = ((params.value / data.total_amount) * 100).toFixed(1);
+ return `${params.name}\n${percentage}%`;
+ },
+ fontSize: 12,
+ color: '#333'
+ },
+ labelLine: {
+ show: true,
+ length: 15,
+ length2: 10,
+ smooth: true
+ }
+ }]
+ });
+
+ // 添加饼图点击事件
+ chart.on('click', function(params) {
+ if (params.componentType === 'series') {
+ const clickedIndustry = params.name;
+ loadIndustryDetail(clickedIndustry);
+
+ // 静默调用通知接口
+ $.get(`https://spb.bmbs.tech/api/dify/webSelectStockIndustry?industry=${encodeURIComponent(clickedIndustry)}`)
+ .fail(function(xhr, status, error) {
+ // 静默处理错误,不显示给用户
+ console.log('通知接口调用失败:', error);
+ });
+ }
+ });
+
+ dom.onclick = function() {
+ for(const k in chartInstances) {
+ if(k !== 'portfolioChart' && chartInstances[k]) {
+ chartInstances[k].dispatchAction({ type: 'hideTip' });
+ }
+ }
+ };
+ }
+ function renderRzrqChart(data) {
+ if(!data || !data.dates || !data.series) return;
+ const dom = document.getElementById('rzrqChart');
+ const chart = echarts.init(dom);
+ chartInstances['rzrqChart'] = chart;
+ const s = data.series[0];
+ let min = Math.min(...s.data.filter(v => v !== null && v !== undefined));
+ min = Math.floor(min * 0.98);
+
+ // 计算当前值在历史数据中的百分位
+ const validData = s.data.filter(v => v !== null && v !== undefined);
+ const currentValue = s.data[s.data.length - 1];
+ const sortedData = validData.sort((a, b) => a - b);
+ const currentIndex = sortedData.findIndex(v => v >= currentValue);
+ const percentile = (currentIndex / sortedData.length) * 100;
+
+ // 准备色带数据
+ let markArea = [];
+ if (percentile >= 80) {
+ // 当前值在80%以上,显示红色色带标记80%-100%
+ const maxValue = Math.max(...validData);
+ const threshold80 = sortedData[Math.floor(sortedData.length * 0.8)];
+ markArea.push([{
+ yAxis: threshold80,
+ itemStyle: {
+ color: 'rgba(255, 0, 0, 0.2)'
+ }
+ }, {
+ yAxis: maxValue,
+ itemStyle: {
+ color: 'rgba(255, 0, 0, 0.2)'
+ }
+ }]);
+ } else if (percentile <= 20) {
+ // 当前值在20%以下,显示绿色色带标记0%-20%
+ const minValue = Math.min(...validData);
+ const threshold20 = sortedData[Math.floor(sortedData.length * 0.2)];
+ markArea.push([{
+ yAxis: minValue,
+ itemStyle: {
+ color: 'rgba(0, 255, 0, 0.2)'
+ }
+ }, {
+ yAxis: threshold20,
+ itemStyle: {
+ color: 'rgba(0, 255, 0, 0.2)'
+ }
+ }]);
+ }
+
+ chart.setOption({
+ title: {text: '', show: false},
+ tooltip: {trigger: 'axis'},
+ legend: {data: [s.name], top: 5, textStyle: {color:'#333'}},
+ grid: {left: '5%', right: '5%', top: 30, bottom: 20, containLabel: true},
+ xAxis: {type: 'category', data: data.dates, axisLabel: {rotate: 0, color:'#666', interval: 'auto'}},
+ yAxis: {type: 'value', name: s.unit, axisLabel: {color:'#666'}, min: min},
+ series: [{
+ name: s.name,
+ type: 'line',
+ data: s.data,
+ symbol: 'none',
+ lineStyle:{width:2},
+ markArea: markArea.length > 0 ? {
+ data: markArea
+ } : undefined
+ }]
+ });
+ dom.onclick = function() {
+ for(const k in chartInstances) {
+ if(k !== 'rzrqChart' && chartInstances[k]) {
+ chartInstances[k].dispatchAction({ type: 'hideTip' });
+ }
+ }
+ };
+ }
+ function renderFearGreedChart(data) {
+ if(!data || !data.dates || !data.values) return;
+ const dom = document.getElementById('fearGreedChart');
+ const chart = echarts.init(dom);
+ chartInstances['fearGreedChart'] = chart;
+ let min = Math.min(...data.values.filter(v => v !== null && v !== undefined));
+ min = Math.floor(min * 0.98);
+
+ // 获取当前值
+ const currentValue = data.values[data.values.length - 1];
+
+ // 准备色带数据 - 直接使用固定的0-100范围
+ let markArea = [];
+ if (currentValue >= 80) {
+ // 当前值在80以上,显示红色色带标记80-100
+ markArea.push([{
+ yAxis: 80,
+ itemStyle: {
+ color: 'rgba(255, 0, 0, 0.2)'
+ }
+ }, {
+ yAxis: 100,
+ itemStyle: {
+ color: 'rgba(255, 0, 0, 0.2)'
+ }
+ }]);
+ } else if (currentValue <= 20) {
+ // 当前值在20以下,显示绿色色带标记0-20
+ markArea.push([{
+ yAxis: 0,
+ itemStyle: {
+ color: 'rgba(0, 255, 0, 0.2)'
+ }
+ }, {
+ yAxis: 20,
+ itemStyle: {
+ color: 'rgba(0, 255, 0, 0.2)'
+ }
+ }]);
+ }
+
+ chart.setOption({
+ title: {text: '', show: false},
+ tooltip: {trigger: 'axis'},
+ legend: {data: ['恐贪指数'], top: 5, textStyle: {color:'#333'}},
+ grid: {left: '5%', right: '5%', top: 30, bottom: 20, containLabel: true},
+ xAxis: {type: 'category', data: data.dates, axisLabel: {rotate: 0, color:'#666', interval: 'auto'}},
+ yAxis: {type: 'value', min: min, max: 100, axisLabel: {color:'#666'}},
+ series: [{
+ name: '恐贪指数',
+ type: 'line',
+ data: data.values,
+ symbol: 'none',
+ lineStyle:{width:2, color:'#f0ad4e'},
+ markArea: markArea.length > 0 ? {
+ data: markArea
+ } : undefined
+ }]
+ });
+ dom.onclick = function() {
+ for(const k in chartInstances) {
+ if(k !== 'fearGreedChart' && chartInstances[k]) {
+ chartInstances[k].dispatchAction({ type: 'hideTip' });
+ }
+ }
+ };
+ }
+ function renderPEChart(domId, data) {
+ if(!data || !data.series || !data.xAxis || !data.xAxis[0] || !data.xAxis[0].data) return;
+ const dom = document.getElementById(domId);
+ const chart = echarts.init(dom);
+ chartInstances[domId] = chart;
+ const mainSeries = data.series.filter(s => s.name.indexOf('平均PE') !== -1 || s.name.indexOf('PE') !== -1);
+ mainSeries.forEach(s => { s.symbol = 'none'; });
+ let allValues = [];
+ mainSeries.forEach(s => allValues = allValues.concat(s.data.filter(v => v !== null && v !== undefined)));
+ let min = Math.min(...allValues);
+ min = Math.floor(min * 0.98);
+ chart.setOption({
+ title: {text: '', show: false},
+ tooltip: {trigger: 'axis'},
+ legend: {show: false},
+ grid: {left: '5%', right: '5%', top: 30, bottom: 20, containLabel: true},
+ xAxis: {type: 'category', data: data.xAxis[0].data, axisLabel: {rotate: 0, color:'#666', interval: 'auto'}},
+ yAxis: {type: 'value', name: data.yAxis[0].name, axisLabel: {color:'#666'}, min: min},
+ series: mainSeries,
+ dataZoom: [
+ {type: 'inside', start: 0, end: 100, zoomOnTouch: true, moveOnMouseWheel: true}
+ ]
+ });
+ dom.onclick = function() {
+ for(const k in chartInstances) {
+ if(k !== domId && chartInstances[k]) {
+ chartInstances[k].dispatchAction({ type: 'hideTip' });
+ }
+ }
+ };
+ }
+ function renderCrowdChart(domId, crowding) {
+ if(!crowding || !crowding.dates || !crowding.percentiles) return;
+ // 展示近一年(240天)数据
+ let dates = crowding.dates;
+ let percentiles = crowding.percentiles;
+ if(dates.length > 240) {
+ dates = dates.slice(-240);
+ percentiles = percentiles.slice(-240);
+ }
+ const dom = document.getElementById(domId);
+ const chart = echarts.init(dom);
+ chartInstances[domId] = chart;
+ let min = Math.min(...percentiles.filter(v => v !== null && v !== undefined));
+ min = Math.floor(min * 0.98);
+ // 检查最后一个点是否需要高亮
+ let markPoint = undefined;
+ const lastVal = percentiles[percentiles.length-1];
+ if(lastVal !== undefined && (lastVal > 80 || lastVal < 20)) {
+ markPoint = {
+ data: [{
+ coord: [dates[dates.length-1], lastVal],
+ symbol: 'circle',
+ symbolSize: 16,
+ itemStyle: {
+ color: lastVal > 80 ? '#ff3333' : '#33cc33',
+ shadowBlur: 20,
+ shadowColor: lastVal > 80 ? '#ff3333' : '#33cc33',
+ opacity: 1
+ },
+ label: {show: false},
+ animation: true,
+ animationDuration: 500,
+ animationEasing: 'bounceOut',
+ animationDurationUpdate: 500,
+ animationEasingUpdate: 'bounceOut',
+ effect: {
+ show: true,
+ period: 1,
+ scaleSize: 2,
+ color: lastVal > 80 ? '#ff3333' : '#33cc33',
+ shadowBlur: 10
+ }
+ }]
+ };
+ }
+ chart.setOption({
+ title: {text: '', show: false},
+ tooltip: {trigger: 'axis'},
+ legend: {data: ['拥挤度历史百分位'], top: 5, textStyle: {color:'#333'}},
+ grid: {left: '5%', right: '5%', top: 30, bottom: 20, containLabel: true},
+ xAxis: {type: 'category', data: dates, axisLabel: {rotate: 0, color:'#666', interval: 'auto'}},
+ yAxis: {type: 'value', min: min, max: 100, name: '百分位(%)', axisLabel: {color:'#666'}},
+ series: [{
+ name: '拥挤度历史百分位',
+ type: 'line',
+ data: percentiles,
+ symbol: 'none',
+ lineStyle:{width:2, color:'#ff7f50'},
+ markPoint: markPoint
+ }],
+ dataZoom: [
+ {type: 'inside', start: 0, end: 100, zoomOnTouch: true, moveOnMouseWheel: true}
+ ]
+ });
+ dom.onclick = function() {
+ for(const k in chartInstances) {
+ if(k !== domId && chartInstances[k]) {
+ chartInstances[k].dispatchAction({ type: 'hideTip' });
+ }
+ }
+ };
+ }
+
+ // 统一resize自适应
+ $(window).on('resize', function() {
+ for(const key in chartInstances) {
+ if(chartInstances[key] && chartInstances[key].resize) {
+ chartInstances[key].resize();
+ }
+ }
+ });
+
+});
+
+// 弹窗相关函数 - 移到全局作用域
+function showNoticeModal() {
+ const modal = document.getElementById('noticeModal');
+ const modalContent = document.getElementById('modalNoticeContent');
+
+ // 获取当前所有提醒数据
+ const notices = getCurrentNotices();
+
+ // 生成弹窗内容
+ let html = '';
+ notices.forEach(notice => {
+ html += `${notice}
`;
+ });
+ modalContent.innerHTML = html;
+
+ // 显示弹窗
+ modal.style.display = 'flex';
+
+ // 阻止背景滚动
+ document.body.style.overflow = 'hidden';
+}
+
+function closeNoticeModal() {
+ const modal = document.getElementById('noticeModal');
+ modal.style.display = 'none';
+
+ // 恢复背景滚动
+ document.body.style.overflow = 'auto';
+}
+
+function closeIndustryHoldingsModal() {
+ const modal = document.getElementById('industryHoldingsModal');
+ modal.style.display = 'none';
+
+ // 恢复背景滚动
+ document.body.style.overflow = 'auto';
+}
+
+function getCurrentNotices() {
+ // 从当前显示的提醒框中获取数据,或者使用默认数据
+ const noticeItems = document.querySelectorAll('.notice-item');
+ if (noticeItems.length > 0) {
+ return Array.from(noticeItems).map(item => item.textContent.trim());
+ }
+ return getDefaultNotices();
+}
+
+// 页面加载完成后绑定事件
+$(function() {
+ // 绑定点击事件
+ document.getElementById('noticeBox').addEventListener('click', showNoticeModal);
+
+ // 点击弹窗背景关闭
+ document.getElementById('noticeModal').addEventListener('click', function(e) {
+ if (e.target === this) {
+ closeNoticeModal();
+ }
+ });
+
+ // 点击行业持仓详情弹窗背景关闭
+ document.getElementById('industryHoldingsModal').addEventListener('click', function(e) {
+ if (e.target === this) {
+ closeIndustryHoldingsModal();
+ }
+ });
+
+ // ESC键关闭弹窗
+ document.addEventListener('keydown', function(e) {
+ if (e.key === 'Escape') {
+ closeNoticeModal();
+ closeIndustryHoldingsModal();
+ }
+ });
+});
diff --git a/src/templates/bigscreen.html b/src/templates/bigscreen.html
index 0cd500a..96c6e8d 100644
--- a/src/templates/bigscreen.html
+++ b/src/templates/bigscreen.html
@@ -216,22 +216,5 @@
-