Download templates/industry_analysis.html from gitdeem/stock: direct link, hf CLI and curl.
- Browser
- Download file 45.8 kB
-
https://huggingface.co/spaces/gitdeem/stock/resolve/main/templates/industry_analysis.html
- Command line
-
hf download hf://spaces/gitdeem/stock/templates/industry_analysis.html
-
curl -L -o industry_analysis.html https://huggingface.co/spaces/gitdeem/stock/resolve/main/templates/industry_analysis.html
45.8 kB
| {% extends "layout.html" %} | |
| {% block title %}行业分析 - 智能分析系统{% endblock %} | |
| {% block content %} | |
| <div class="container-fluid py-3"> | |
| <div id="alerts-container"></div> | |
| <div class="row mb-3"> | |
| <div class="col-12"> | |
| <div class="card"> | |
| <div class="card-header py-2"> | |
| <h5 class="mb-0">行业资金流向分析</h5> | |
| </div> | |
| <div class="card-body py-2"> | |
| <form id="industry-form" class="row g-2"> | |
| <div class="col-md-3"> | |
| <div class="input-group input-group-sm"> | |
| <span class="input-group-text">周期</span> | |
| <select class="form-select" id="fund-flow-period"> | |
| <option value="即时" selected>即时</option> | |
| <option value="3日排行">3日排行</option> | |
| <option value="5日排行">5日排行</option> | |
| <option value="10日排行">10日排行</option> | |
| <option value="20日排行">20日排行</option> | |
| </select> | |
| </div> | |
| </div> | |
| <div class="col-md-3"> | |
| <div class="input-group input-group-sm"> | |
| <span class="input-group-text">行业</span> | |
| <select class="form-select" id="industry-selector"> | |
| <option value="">-- 选择行业 --</option> | |
| <!-- 行业选项将通过JS动态填充 --> | |
| </select> | |
| </div> | |
| </div> | |
| <div class="col-md-2"> | |
| <button type="submit" class="btn btn-primary btn-sm w-100"> | |
| <i class="fas fa-search"></i> 分析 | |
| </button> | |
| </div> | |
| </form> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <div id="loading-panel" class="text-center py-5" style="display: none;"> | |
| <div class="spinner-border text-primary" role="status"> | |
| <span class="visually-hidden">Loading...</span> | |
| </div> | |
| <p class="mt-3 mb-0">正在获取行业数据...</p> | |
| </div> | |
| <!-- 行业资金流向概览 --> | |
| <div id="industry-overview" class="row g-3 mb-3" style="display: none;"> | |
| <div class="col-12"> | |
| <div class="card"> | |
| <div class="card-header py-2 d-flex justify-content-between align-items-center"> | |
| <h5 class="mb-0">行业资金流向概览</h5> | |
| <div class="d-flex"> | |
| <span id="period-badge" class="badge bg-primary ms-2">即时</span> | |
| <button class="btn btn-sm btn-outline-primary ms-2" id="export-btn"> | |
| <i class="fas fa-download"></i> 导出数据 | |
| </button> | |
| </div> | |
| </div> | |
| <div class="card-body p-0"> | |
| <div class="table-responsive"> | |
| <table class="table table-sm table-striped table-hover mb-0"> | |
| <thead> | |
| <tr> | |
| <th>序号</th> | |
| <th>行业</th> | |
| <th>行业指数</th> | |
| <th>涨跌幅</th> | |
| <th>流入资金(亿)</th> | |
| <th>流出资金(亿)</th> | |
| <th>净额(亿)</th> | |
| <th>公司家数</th> | |
| <th>领涨股</th> | |
| <th>操作</th> | |
| </tr> | |
| </thead> | |
| <tbody id="industry-table"> | |
| <!-- 行业资金流向数据将在JS中动态填充 --> | |
| </tbody> | |
| </table> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- 行业详细分析 --> | |
| <div id="industry-detail" class="row g-3 mb-3" style="display: none;"> | |
| <div class="col-md-6"> | |
| <div class="card h-100"> | |
| <div class="card-header py-2"> | |
| <h5 id="industry-name" class="mb-0">行业详情</h5> | |
| </div> | |
| <div class="card-body"> | |
| <div class="row"> | |
| <div class="col-md-6"> | |
| <h6>行业概况</h6> | |
| <p><span class="text-muted">行业指数:</span> <span id="industry-index" class="fw-bold"></span></p> | |
| <p><span class="text-muted">涨跌幅:</span> <span id="industry-change" class="fw-bold"></span></p> | |
| <p><span class="text-muted">公司家数:</span> <span id="industry-company-count" class="fw-bold"></span></p> | |
| </div> | |
| <div class="col-md-6"> | |
| <h6>资金流向</h6> | |
| <p><span class="text-muted">流入资金:</span> <span id="industry-inflow" class="fw-bold"></span></p> | |
| <p><span class="text-muted">流出资金:</span> <span id="industry-outflow" class="fw-bold"></span></p> | |
| <p><span class="text-muted">净额:</span> <span id="industry-net-flow" class="fw-bold"></span></p> | |
| </div> | |
| </div> | |
| <div class="mt-3"> | |
| <div id="industry-flow-chart" style="height: 200px;"></div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="col-md-6"> | |
| <div class="card h-100"> | |
| <div class="card-header py-2"> | |
| <h5 class="mb-0">行业评分</h5> | |
| </div> | |
| <div class="card-body"> | |
| <div class="row"> | |
| <div class="col-md-5"> | |
| <div id="industry-score-chart" style="height: 150px;"></div> | |
| <h4 id="industry-score" class="text-center mt-2">--</h4> | |
| <p class="text-muted text-center">综合评分</p> | |
| </div> | |
| <div class="col-md-7"> | |
| <div class="mb-3"> | |
| <div class="d-flex justify-content-between mb-1"> | |
| <span>技术面</span> | |
| <span id="technical-score">--/40</span> | |
| </div> | |
| <div class="progress"> | |
| <div id="technical-progress" class="progress-bar bg-info" role="progressbar" style="width: 0%"></div> | |
| </div> | |
| </div> | |
| <div class="mb-3"> | |
| <div class="d-flex justify-content-between mb-1"> | |
| <span>基本面</span> | |
| <span id="fundamental-score">--/40</span> | |
| </div> | |
| <div class="progress"> | |
| <div id="fundamental-progress" class="progress-bar bg-success" role="progressbar" style="width: 0%"></div> | |
| </div> | |
| </div> | |
| <div class="mb-3"> | |
| <div class="d-flex justify-content-between mb-1"> | |
| <span>资金面</span> | |
| <span id="capital-flow-score">--/20</span> | |
| </div> | |
| <div class="progress"> | |
| <div id="capital-flow-progress" class="progress-bar bg-warning" role="progressbar" style="width: 0%"></div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="mt-3"> | |
| <h6>投资建议</h6> | |
| <p id="industry-recommendation" class="mb-0"></p> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- 行业成分股表现 --> | |
| <div id="industry-stocks" class="row g-3 mb-3" style="display: none;"> | |
| <div class="col-12"> | |
| <div class="card"> | |
| <div class="card-header py-2"> | |
| <h5 class="mb-0">行业成分股表现</h5> | |
| </div> | |
| <div class="card-body p-0"> | |
| <div class="table-responsive"> | |
| <table class="table table-sm table-striped table-hover mb-0"> | |
| <thead> | |
| <tr> | |
| <th>代码</th> | |
| <th>名称</th> | |
| <th>最新价</th> | |
| <th>涨跌幅</th> | |
| <th>成交量</th> | |
| <th>成交额(万)</th> | |
| <th>换手率</th> | |
| <th>评分</th> | |
| <th>操作</th> | |
| </tr> | |
| </thead> | |
| <tbody id="industry-stocks-table"> | |
| <!-- 行业成分股数据将在JS中动态填充 --> | |
| </tbody> | |
| </table> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- 行业对比分析 --> | |
| <div class="row g-3 mb-3"> | |
| <div class="col-12"> | |
| <div class="card"> | |
| <div class="card-header py-2"> | |
| <h5 class="mb-0">行业对比分析</h5> | |
| </div> | |
| <div class="card-body"> | |
| <ul class="nav nav-tabs" id="industry-compare-tabs" role="tablist"> | |
| <li class="nav-item" role="presentation"> | |
| <button class="nav-link active" id="fund-flow-tab" data-bs-toggle="tab" data-bs-target="#fund-flow" type="button" role="tab" aria-controls="fund-flow" aria-selected="true">资金流向</button> | |
| </li> | |
| <li class="nav-item" role="presentation"> | |
| <button class="nav-link" id="performance-tab" data-bs-toggle="tab" data-bs-target="#performance" type="button" role="tab" aria-controls="performance" aria-selected="false">行业涨跌幅</button> | |
| </li> | |
| </ul> | |
| <div class="tab-content mt-3" id="industry-compare-tabs-content"> | |
| <div class="tab-pane fade show active" id="fund-flow" role="tabpanel" aria-labelledby="fund-flow-tab"> | |
| <div class="row"> | |
| <div class="col-md-6"> | |
| <h6>资金净流入前10行业</h6> | |
| <div id="top-inflow-chart" style="height: 300px;"></div> | |
| </div> | |
| <div class="col-md-6"> | |
| <h6>资金净流出前10行业</h6> | |
| <div id="top-outflow-chart" style="height: 300px;"></div> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="tab-pane fade" id="performance" role="tabpanel" aria-labelledby="performance-tab"> | |
| <div class="row"> | |
| <div class="col-md-6"> | |
| <h6>涨幅前10行业</h6> | |
| <div id="top-gainers-chart" style="height: 300px;"></div> | |
| </div> | |
| <div class="col-md-6"> | |
| <h6>跌幅前10行业</h6> | |
| <div id="top-losers-chart" style="height: 300px;"></div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| {% endblock %} | |
| {% block scripts %} | |
| <script> | |
| $(document).ready(function() { | |
| // 加载行业资金流向数据 | |
| loadIndustryFundFlow(); | |
| $('#industry-form').submit(function(e) { | |
| e.preventDefault(); | |
| // 获取选择的行业 | |
| const industry = $('#industry-selector').val(); | |
| if (!industry) { | |
| showError('请选择行业名称'); | |
| return; | |
| } | |
| // 分析行业 | |
| loadIndustryDetail(industry); | |
| }); | |
| // 资金流向周期切换 | |
| $('#fund-flow-period').change(function() { | |
| const period = $(this).val(); | |
| loadIndustryFundFlow(period); | |
| }); | |
| // 导出按钮点击事件 | |
| $('#export-btn').click(function() { | |
| exportToCSV(); | |
| }); | |
| }); | |
| function analyzeIndustry(industry) { | |
| $('#loading-panel').show(); | |
| $('#industry-result').hide(); | |
| // 1. 获取行业详情 | |
| $.ajax({ | |
| url: `/api/industry_detail?industry=${encodeURIComponent(industry)}`, | |
| type: 'GET', | |
| success: function(industryDetail) { | |
| console.log("Industry detail loaded successfully:", industryDetail); | |
| // 2. 获取行业成分股 | |
| $.ajax({ | |
| url: `/api/industry_stocks?industry=${encodeURIComponent(industry)}`, | |
| type: 'GET', | |
| success: function(stocksResponse) { | |
| console.log("Industry stocks loaded successfully:", stocksResponse); | |
| $('#loading-panel').hide(); | |
| // 渲染行业详情和成分股 | |
| renderIndustryDetail(industryDetail); | |
| renderIndustryStocks(stocksResponse); | |
| $('#industry-detail').show(); | |
| $('#industry-stocks').show(); | |
| }, | |
| error: function(xhr, status, error) { | |
| $('#loading-panel').hide(); | |
| console.error("Error loading industry stocks:", error); | |
| showError('获取行业成分股失败: ' + (xhr.responseJSON?.error || error)); | |
| } | |
| }); | |
| }, | |
| error: function(xhr, status, error) { | |
| $('#loading-panel').hide(); | |
| console.error("Error loading industry detail:", error); | |
| showError('获取行业详情失败: ' + (xhr.responseJSON?.error || error)); | |
| } | |
| }); | |
| } | |
| // 加载行业资金流向数据 | |
| function loadIndustryFundFlow(period = '即时') { | |
| $('#loading-panel').show(); | |
| $('#industry-overview').hide(); | |
| $('#industry-detail').hide(); | |
| $('#industry-stocks').hide(); | |
| $.ajax({ | |
| url: `/api/industry_fund_flow?symbol=${encodeURIComponent(period)}`, | |
| type: 'GET', | |
| dataType: 'json', | |
| success: function(response) { | |
| if (Array.isArray(response) && response.length > 0) { | |
| renderIndustryFundFlow(response, period); | |
| populateIndustrySelector(response); | |
| // 加载行业对比数据 | |
| loadIndustryCompare(); | |
| $('#loading-panel').hide(); | |
| $('#industry-overview').show(); | |
| } else { | |
| showError('获取行业资金流向数据失败:返回数据为空'); | |
| $('#loading-panel').hide(); | |
| } | |
| }, | |
| error: function(xhr, status, error) { | |
| $('#loading-panel').hide(); | |
| let errorMsg = '获取行业资金流向数据失败'; | |
| if (xhr.responseJSON && xhr.responseJSON.error) { | |
| errorMsg += ': ' + xhr.responseJSON.error; | |
| } else if (error) { | |
| errorMsg += ': ' + error; | |
| } | |
| showError(errorMsg); | |
| } | |
| }); | |
| } | |
| // 统一的行业详情加载函数 | |
| function loadIndustryDetail(industry) { | |
| console.log(`Loading industry detail for: ${industry}`); | |
| $('#loading-panel').show(); | |
| $('#industry-overview').hide(); | |
| $('#industry-detail').hide(); | |
| $('#industry-stocks').hide(); | |
| // 并行加载行业详情和行业成分股 | |
| $.when( | |
| // 获取行业详情 | |
| $.ajax({ | |
| url: `/api/industry_detail?industry=${encodeURIComponent(industry)}`, | |
| type: 'GET', | |
| dataType: 'json' | |
| }), | |
| // 获取行业成分股 | |
| $.ajax({ | |
| url: `/api/industry_stocks?industry=${encodeURIComponent(industry)}`, | |
| type: 'GET', | |
| dataType: 'json' | |
| }) | |
| ).done(function(detailResponse, stocksResponse) { | |
| // 处理行业详情数据 | |
| const industryData = detailResponse[0]; | |
| // 处理行业成分股数据 | |
| const stocksData = stocksResponse[0]; | |
| console.log("Industry detail loaded:", industryData); | |
| console.log("Industry stocks loaded:", stocksData); | |
| renderIndustryDetail(industryData); | |
| renderIndustryStocks(stocksData); | |
| $('#loading-panel').hide(); | |
| $('#industry-detail').show(); | |
| $('#industry-stocks').show(); | |
| }).fail(function(jqXHR, textStatus, errorThrown) { | |
| $('#loading-panel').hide(); | |
| console.error("Error loading industry data:", textStatus, errorThrown); | |
| let errorMsg = '获取行业数据失败'; | |
| try { | |
| if (jqXHR.responseJSON && jqXHR.responseJSON.error) { | |
| errorMsg += ': ' + jqXHR.responseJSON.error; | |
| } else if (errorThrown) { | |
| errorMsg += ': ' + errorThrown; | |
| } | |
| } catch (e) { | |
| console.error("Error parsing error response:", e); | |
| } | |
| showError(errorMsg); | |
| }); | |
| } | |
| // 加载行业对比数据 | |
| function loadIndustryCompare() { | |
| $.ajax({ | |
| url: '/api/industry_compare', | |
| type: 'GET', | |
| dataType: 'json', | |
| success: function(response) { | |
| try { | |
| if (response && response.results) { | |
| // 按资金净流入排序 | |
| const sortedByNetFlow = [...response.results] | |
| .filter(item => item.netFlow !== undefined) | |
| .sort((a, b) => parseFloat(b.netFlow || 0) - parseFloat(a.netFlow || 0)); | |
| // 按涨跌幅排序 | |
| const sortedByChange = [...response.results] | |
| .filter(item => item.change !== undefined) | |
| .sort((a, b) => parseFloat(b.change || 0) - parseFloat(a.change || 0)); | |
| // 资金净流入前10行业 | |
| const topInflow = sortedByNetFlow.slice(0, 10); | |
| renderBarChart('top-inflow-chart', | |
| topInflow.map(item => item.industry), | |
| topInflow.map(item => parseFloat(item.netFlow || 0)), | |
| '资金净流入(亿)', | |
| '#00E396'); | |
| // 资金净流出前10行业 | |
| const bottomInflow = [...sortedByNetFlow].reverse().slice(0, 10); | |
| renderBarChart('top-outflow-chart', | |
| bottomInflow.map(item => item.industry), | |
| bottomInflow.map(item => Math.abs(parseFloat(item.netFlow || 0))), | |
| '资金净流出(亿)', | |
| '#FF4560'); | |
| // 涨幅前10行业 | |
| const topGainers = sortedByChange.slice(0, 10); | |
| renderBarChart('top-gainers-chart', | |
| topGainers.map(item => item.industry), | |
| topGainers.map(item => parseFloat(item.change || 0)), | |
| '涨幅(%)', | |
| '#00E396'); | |
| // 跌幅前10行业 | |
| const topLosers = [...sortedByChange].reverse().slice(0, 10); | |
| renderBarChart('top-losers-chart', | |
| topLosers.map(item => item.industry), | |
| topLosers.map(item => Math.abs(parseFloat(item.change || 0))), | |
| '跌幅(%)', | |
| '#FF4560'); | |
| } | |
| } catch (e) { | |
| console.error("Error processing industry comparison data:", e); | |
| } | |
| }, | |
| error: function(xhr, status, error) { | |
| console.error('获取行业对比数据失败:', error); | |
| } | |
| }); | |
| } | |
| // 渲染行业资金流向表格 | |
| function renderIndustryFundFlow(data, period) { | |
| $('#period-badge').text(period); | |
| let html = ''; | |
| if (data.length === 0) { | |
| html = '<tr><td colspan="10" class="text-center">暂无数据</td></tr>'; | |
| } else { | |
| data.forEach((item, index) => { | |
| const changeClass = parseFloat(item.change) >= 0 ? 'trend-up' : 'trend-down'; | |
| const changeIcon = parseFloat(item.change) >= 0 ? '<i class="fas fa-caret-up"></i>' : '<i class="fas fa-caret-down"></i>'; | |
| const netFlowClass = parseFloat(item.netFlow) >= 0 ? 'trend-up' : 'trend-down'; | |
| const netFlowIcon = parseFloat(item.netFlow) >= 0 ? '<i class="fas fa-caret-up"></i>' : '<i class="fas fa-caret-down"></i>'; | |
| html += ` | |
| <tr> | |
| <td>${item.rank}</td> | |
| <td> | |
| <a href="javascript:void(0)" onclick="loadIndustryDetail('${item.industry}')" class="industry-link"> | |
| ${item.industry} | |
| </a> | |
| </td> | |
| <td>${formatNumber(item.index, 2)}</td> | |
| <td class="${changeClass}">${changeIcon} ${item.change}%</td> | |
| <td>${formatNumber(item.inflow, 2)}</td> | |
| <td>${formatNumber(item.outflow, 2)}</td> | |
| <td class="${netFlowClass}">${netFlowIcon} ${formatNumber(item.netFlow, 2)}</td> | |
| <td>${item.companyCount}</td> | |
| <td>${item.leadingStock || '-'}</td> | |
| <td> | |
| <button class="btn btn-sm btn-outline-primary" onclick="loadIndustryDetail('${item.industry}')"> | |
| <i class="fas fa-search"></i> | |
| </button> | |
| </td> | |
| </tr> | |
| `; | |
| }); | |
| } | |
| $('#industry-table').html(html); | |
| } | |
| // 渲染行业详情 | |
| function renderIndustryDetail(data) { | |
| if (!data) { | |
| console.error("renderIndustryDetail: No data provided"); | |
| return; | |
| } | |
| console.log("Rendering industry detail:", data); | |
| // 设置基本信息 | |
| $('#industry-name').text(data.industry); | |
| // 设置行业评分 | |
| const scoreClass = getScoreColorClass(data.score); | |
| $('#industry-score').text(data.score).removeClass().addClass(scoreClass); | |
| // 设置技术面、基本面、资金面分数 (模拟分数) | |
| const technicalScore = Math.round(data.score * 0.4); | |
| const fundamentalScore = Math.round(data.score * 0.4); | |
| const capitalFlowScore = Math.round(data.score * 0.2); | |
| $('#technical-score').text(`${technicalScore}/40`); | |
| $('#fundamental-score').text(`${fundamentalScore}/40`); | |
| $('#capital-flow-score').text(`${capitalFlowScore}/20`); | |
| $('#technical-progress').css('width', `${technicalScore / 40 * 100}%`); | |
| $('#fundamental-progress').css('width', `${fundamentalScore / 40 * 100}%`); | |
| $('#capital-flow-progress').css('width', `${capitalFlowScore / 20 * 100}%`); | |
| // 设置行业基本信息 | |
| $('#industry-index').text(formatNumber(data.index, 2)); | |
| $('#industry-company-count').text(data.companyCount); | |
| // 设置涨跌幅 | |
| const changeClass = parseFloat(data.change) >= 0 ? 'trend-up' : 'trend-down'; | |
| const changeIcon = parseFloat(data.change) >= 0 ? '<i class="fas fa-caret-up"></i>' : '<i class="fas fa-caret-down"></i>'; | |
| $('#industry-change').html(`<span class="${changeClass}">${changeIcon} ${data.change}%</span>`); | |
| // 设置资金流向 | |
| $('#industry-inflow').text(formatNumber(data.inflow, 2) + ' 亿'); | |
| $('#industry-outflow').text(formatNumber(data.outflow, 2) + ' 亿'); | |
| const netFlowClass = parseFloat(data.netFlow) >= 0 ? 'trend-up' : 'trend-down'; | |
| const netFlowIcon = parseFloat(data.netFlow) >= 0 ? '<i class="fas fa-arrow-up"></i>' : '<i class="fas fa-arrow-down"></i>'; | |
| $('#industry-net-flow').html(`<span class="${netFlowClass}">${netFlowIcon} ${formatNumber(data.netFlow, 2)} 亿</span>`); | |
| // 设置投资建议 | |
| $('#industry-recommendation').text(data.recommendation); | |
| // 绘制行业评分图表 | |
| renderIndustryScoreChart(data.score); | |
| // 绘制资金流向图表 | |
| renderIndustryFlowChart(data.flowHistory); | |
| } | |
| // 渲染行业成分股表格 | |
| function renderIndustryStocks(data) { | |
| if (!data) { | |
| console.error("renderIndustryStocks: No data provided"); | |
| return; | |
| } | |
| console.log("Rendering industry stocks:", data); | |
| let html = ''; | |
| if (!Array.isArray(data) || data.length === 0) { | |
| html = '<tr><td colspan="9" class="text-center">暂无成分股数据</td></tr>'; | |
| } else { | |
| data.forEach(stock => { | |
| const changeClass = parseFloat(stock.change) >= 0 ? 'trend-up' : 'trend-down'; | |
| const changeIcon = parseFloat(stock.change) >= 0 ? '<i class="fas fa-caret-up"></i>' : '<i class="fas fa-caret-down"></i>'; | |
| html += ` | |
| <tr> | |
| <td>${stock.code}</td> | |
| <td>${stock.name}</td> | |
| <td>${formatNumber(stock.price, 2)}</td> | |
| <td class="${changeClass}">${changeIcon} ${formatNumber(stock.change, 2)}%</td> | |
| <td>${formatNumber(stock.volume, 0)}</td> | |
| <td>${formatMoney(stock.turnover)}</td> | |
| <td>${formatNumber(stock.turnover_rate || stock.turnoverRate, 2)}%</td> | |
| <td>${stock.score ? formatNumber(stock.score, 0) : '-'}</td> | |
| <td> | |
| <a href="/stock_detail/${stock.code}" class="btn btn-sm btn-outline-primary"> | |
| <i class="fas fa-chart-line"></i> | |
| </a> | |
| </td> | |
| </tr> | |
| `; | |
| }); | |
| } | |
| $('#industry-stocks-table').html(html); | |
| } | |
| function renderCapitalFlowChart(flowHistory) { | |
| // 添加数据检查 | |
| if (!flowHistory || !Array.isArray(flowHistory) || flowHistory.length === 0) { | |
| // 如果没有历史数据,显示提示信息 | |
| document.querySelector("#industry-flow-chart").innerHTML = | |
| '<div class="text-center text-muted py-5">暂无资金流向历史数据</div>'; | |
| return; | |
| } | |
| const dates = flowHistory.map(item => item.date); | |
| const netFlows = flowHistory.map(item => parseFloat(item.netFlow)); | |
| const changes = flowHistory.map(item => parseFloat(item.change)); | |
| // 确保所有数组都有值 | |
| if (dates.length === 0 || netFlows.length === 0 || changes.length === 0) { | |
| document.querySelector("#industry-flow-chart").innerHTML = | |
| '<div class="text-center text-muted py-5">资金流向数据格式不正确</div>'; | |
| return; | |
| } | |
| const options = { | |
| series: [ | |
| { | |
| name: '净流入(亿)', | |
| type: 'column', | |
| data: netFlows | |
| }, | |
| { | |
| name: '涨跌幅(%)', | |
| type: 'line', | |
| data: changes | |
| } | |
| ], | |
| chart: { | |
| height: 265, | |
| type: 'line', | |
| toolbar: { | |
| show: false | |
| } | |
| }, | |
| plotOptions: { | |
| bar: { | |
| borderRadius: 2, | |
| dataLabels: { | |
| position: 'top' | |
| } | |
| } | |
| }, | |
| dataLabels: { | |
| enabled: false | |
| }, | |
| stroke: { | |
| width: [0, 3] | |
| }, | |
| colors: ['#0d6efd', '#dc3545'], | |
| xaxis: { | |
| categories: dates, | |
| labels: { | |
| formatter: function(value) { | |
| return value.slice(5); // 只显示月-日 | |
| } | |
| } | |
| }, | |
| yaxis: [ | |
| { | |
| title: { | |
| text: '净流入(亿)', | |
| style: { | |
| fontSize: '12px' | |
| } | |
| }, | |
| labels: { | |
| formatter: function(val) { | |
| return val.toFixed(2); | |
| } | |
| } | |
| }, | |
| { | |
| opposite: true, | |
| title: { | |
| text: '涨跌幅(%)', | |
| style: { | |
| fontSize: '12px' | |
| } | |
| }, | |
| labels: { | |
| formatter: function(val) { | |
| return val.toFixed(2); | |
| } | |
| } | |
| } | |
| ], | |
| tooltip: { | |
| shared: true, | |
| intersect: false, | |
| y: { | |
| formatter: function(value, { seriesIndex }) { | |
| if (seriesIndex === 0) { | |
| return value.toFixed(2) + ' 亿'; | |
| } | |
| return value.toFixed(2) + '%'; | |
| } | |
| } | |
| }, | |
| legend: { | |
| position: 'top' | |
| } | |
| }; | |
| // 清除任何现有图表 | |
| document.querySelector("#industry-flow-chart").innerHTML = ''; | |
| const chart = new ApexCharts(document.querySelector("#industry-flow-chart"), options); | |
| chart.render(); | |
| } | |
| // 填充行业选择器 | |
| function populateIndustrySelector(data) { | |
| let options = '<option value="">-- 选择行业 --</option>'; | |
| const industries = data.map(item => item.industry); | |
| industries.forEach(industry => { | |
| options += `<option value="${industry}">${industry}</option>`; | |
| }); | |
| $('#industry-selector').html(options); | |
| } | |
| // 评分颜色类 | |
| function getScoreColorClass(score) { | |
| if (score >= 80) return 'badge rounded-pill bg-success'; | |
| if (score >= 60) return 'badge rounded-pill bg-primary'; | |
| if (score >= 40) return 'badge rounded-pill bg-warning text-dark'; | |
| return 'badge rounded-pill bg-danger'; | |
| } | |
| // 获取评分颜色 | |
| function getScoreColor(score) { | |
| if (score >= 80) return '#28a745'; // 绿色 | |
| if (score >= 60) return '#007bff'; // 蓝色 | |
| if (score >= 40) return '#ffc107'; // 黄色 | |
| return '#dc3545'; // 红色 | |
| } | |
| // 格式化金额(单位:万元) | |
| function formatMoney(value) { | |
| if (value === undefined || value === null) { | |
| return '--'; | |
| } | |
| value = parseFloat(value); | |
| if (isNaN(value)) { | |
| return '--'; | |
| } | |
| if (value >= 100000000) { | |
| return (value / 100000000).toFixed(2) + ' 亿'; | |
| } else if (value >= 10000) { | |
| return (value / 10000).toFixed(2) + ' 万'; | |
| } else { | |
| return value.toFixed(2); | |
| } | |
| } | |
| // 渲染行业资金流向图表 | |
| function renderIndustryFlowChart(data) { | |
| const options = { | |
| series: [ | |
| { | |
| name: '流入资金', | |
| data: data.flowHistory.map(item => item.inflow) | |
| }, | |
| { | |
| name: '流出资金', | |
| data: data.flowHistory.map(item => item.outflow) | |
| }, | |
| { | |
| name: '净流入', | |
| data: data.flowHistory.map(item => item.netFlow) | |
| } | |
| ], | |
| chart: { | |
| type: 'bar', | |
| height: 200, | |
| toolbar: { | |
| show: false | |
| } | |
| }, | |
| plotOptions: { | |
| bar: { | |
| horizontal: false, | |
| columnWidth: '55%', | |
| endingShape: 'rounded' | |
| }, | |
| }, | |
| dataLabels: { | |
| enabled: false | |
| }, | |
| stroke: { | |
| show: true, | |
| width: 2, | |
| colors: ['transparent'] | |
| }, | |
| xaxis: { | |
| categories: data.flowHistory.map(item => item.date) | |
| }, | |
| yaxis: { | |
| title: { | |
| text: '亿元' | |
| } | |
| }, | |
| fill: { | |
| opacity: 1 | |
| }, | |
| tooltip: { | |
| y: { | |
| formatter: function(val) { | |
| return val + " 亿元"; | |
| } | |
| } | |
| }, | |
| colors: ['#00E396', '#FF4560', '#008FFB'] | |
| }; | |
| const chart = new ApexCharts(document.querySelector("#industry-flow-chart"), options); | |
| chart.render(); | |
| } | |
| // 绘制行业评分图表 | |
| function renderIndustryScoreChart(score) { | |
| const options = { | |
| series: [score], | |
| chart: { | |
| height: 150, | |
| type: 'radialBar', | |
| }, | |
| plotOptions: { | |
| radialBar: { | |
| hollow: { | |
| size: '70%', | |
| }, | |
| dataLabels: { | |
| show: false | |
| } | |
| } | |
| }, | |
| colors: [getScoreColor(score)], | |
| stroke: { | |
| lineCap: 'round' | |
| } | |
| }; | |
| // 清除旧图表并创建新图表 | |
| $('#industry-score-chart').empty(); | |
| const chart = new ApexCharts(document.querySelector("#industry-score-chart"), options); | |
| chart.render(); | |
| } | |
| // 绘制行业资金流向图表 | |
| function renderIndustryFlowChart(flowHistory) { | |
| if (!flowHistory || !Array.isArray(flowHistory) || flowHistory.length === 0) { | |
| console.error("renderIndustryFlowChart: Invalid flow history data"); | |
| return; | |
| } | |
| console.log("Rendering flow chart with data:", flowHistory); | |
| const dates = flowHistory.map(item => item.date); | |
| const netFlows = flowHistory.map(item => parseFloat(item.netFlow)); | |
| const changes = flowHistory.map(item => parseFloat(item.change)); | |
| const options = { | |
| series: [ | |
| { | |
| name: '净流入(亿)', | |
| type: 'column', | |
| data: netFlows | |
| }, | |
| { | |
| name: '涨跌幅(%)', | |
| type: 'line', | |
| data: changes | |
| } | |
| ], | |
| chart: { | |
| height: 200, | |
| type: 'line', | |
| toolbar: { | |
| show: false | |
| } | |
| }, | |
| plotOptions: { | |
| bar: { | |
| borderRadius: 2, | |
| dataLabels: { | |
| position: 'top' | |
| } | |
| } | |
| }, | |
| dataLabels: { | |
| enabled: false | |
| }, | |
| stroke: { | |
| width: [0, 3] | |
| }, | |
| colors: ['#0d6efd', '#dc3545'], | |
| xaxis: { | |
| categories: dates, | |
| labels: { | |
| formatter: function(value) { | |
| // Only show month-day if it's a date string | |
| if (typeof value === 'string' && value.includes('-')) { | |
| return value.slice(5); // 只显示月-日 | |
| } | |
| return value; | |
| } | |
| } | |
| }, | |
| yaxis: [ | |
| { | |
| title: { | |
| text: '净流入(亿)', | |
| style: { | |
| fontSize: '12px' | |
| } | |
| }, | |
| labels: { | |
| formatter: function(val) { | |
| return val.toFixed(2); | |
| } | |
| } | |
| }, | |
| { | |
| opposite: true, | |
| title: { | |
| text: '涨跌幅(%)', | |
| style: { | |
| fontSize: '12px' | |
| } | |
| }, | |
| labels: { | |
| formatter: function(val) { | |
| return val.toFixed(2); | |
| } | |
| } | |
| } | |
| ], | |
| tooltip: { | |
| shared: true, | |
| intersect: false, | |
| y: { | |
| formatter: function(value, { seriesIndex }) { | |
| if (seriesIndex === 0) { | |
| return value.toFixed(2) + ' 亿'; | |
| } | |
| return value.toFixed(2) + '%'; | |
| } | |
| } | |
| }, | |
| legend: { | |
| position: 'top' | |
| } | |
| }; | |
| // 清除旧图表并创建新图表 | |
| $('#industry-flow-chart').empty(); | |
| try { | |
| const chart = new ApexCharts(document.querySelector("#industry-flow-chart"), options); | |
| chart.render(); | |
| } catch (e) { | |
| console.error("Error rendering flow chart:", e); | |
| } | |
| } | |
| // 渲染行业对比图表 | |
| function renderIndustryCompareCharts(data) { | |
| // 按资金净流入排序 | |
| const sortedByNetFlow = [...data].sort((a, b) => b.netFlow - a.netFlow); | |
| // 资金净流入前10 | |
| const topInflow = sortedByNetFlow.slice(0, 10); | |
| renderBarChart('top-inflow-chart', topInflow.map(item => item.industry), topInflow.map(item => item.netFlow), '资金净流入(亿元)', '#00E396'); | |
| // 资金净流出前10 | |
| const bottomInflow = [...sortedByNetFlow].reverse().slice(0, 10); | |
| renderBarChart('top-outflow-chart', bottomInflow.map(item => item.industry), bottomInflow.map(item => Math.abs(item.netFlow)), '资金净流出(亿元)', '#FF4560'); | |
| // 按涨跌幅排序 | |
| const sortedByChange = [...data].sort((a, b) => parseFloat(b.change) - parseFloat(a.change)); | |
| // 涨幅前10 | |
| const topGainers = sortedByChange.slice(0, 10); | |
| renderBarChart('top-gainers-chart', topGainers.map(item => item.industry), topGainers.map(item => parseFloat(item.change)), '涨幅(%)', '#00E396'); | |
| // 跌幅前10 | |
| const topLosers = [...sortedByChange].reverse().slice(0, 10); | |
| renderBarChart('top-losers-chart', topLosers.map(item => item.industry), topLosers.map(item => Math.abs(parseFloat(item.change))), '跌幅(%)', '#FF4560'); | |
| } | |
| // 通用水平条形图 | |
| function renderBarChart(elementId, categories, data, title, color) { | |
| if (!categories || !data || categories.length === 0 || data.length === 0) { | |
| console.error(`renderBarChart: Invalid data for ${elementId}`); | |
| return; | |
| } | |
| const options = { | |
| series: [{ | |
| name: title, | |
| data: data | |
| }], | |
| chart: { | |
| type: 'bar', | |
| height: 300, | |
| toolbar: { | |
| show: false | |
| } | |
| }, | |
| plotOptions: { | |
| bar: { | |
| horizontal: true, | |
| dataLabels: { | |
| position: 'top', | |
| }, | |
| } | |
| }, | |
| dataLabels: { | |
| enabled: true, | |
| offsetX: -6, | |
| style: { | |
| fontSize: '12px', | |
| colors: ['#fff'] | |
| }, | |
| formatter: function(val) { | |
| return val.toFixed(2); | |
| } | |
| }, | |
| stroke: { | |
| show: true, | |
| width: 1, | |
| colors: ['#fff'] | |
| }, | |
| xaxis: { | |
| categories: categories | |
| }, | |
| yaxis: { | |
| title: { | |
| text: title | |
| } | |
| }, | |
| fill: { | |
| opacity: 1 | |
| }, | |
| colors: [color] | |
| }; | |
| // 清除旧图表并创建新图表 | |
| $(`#${elementId}`).empty(); | |
| try { | |
| const chart = new ApexCharts(document.querySelector(`#${elementId}`), options); | |
| chart.render(); | |
| } catch (e) { | |
| console.error(`Error rendering chart ${elementId}:`, e); | |
| } | |
| } | |
| // 导出CSV | |
| function exportToCSV() { | |
| // 获取表格数据 | |
| const table = document.querySelector('#industry-overview table'); | |
| let csv = []; | |
| let rows = table.querySelectorAll('tr'); | |
| for (let i = 0; i < rows.length; i++) { | |
| let row = [], cols = rows[i].querySelectorAll('td, th'); | |
| for (let j = 0; j < cols.length - 1; j++) { // 跳过最后一列(操作列) | |
| // 获取单元格的文本内容,去除HTML标签 | |
| let text = cols[j].innerText.replace(/(\r\n|\n|\r)/gm, '').replace(/,/g, ','); | |
| row.push(text); | |
| } | |
| csv.push(row.join(',')); | |
| } | |
| // 下载CSV文件 | |
| const period = $('#period-badge').text(); | |
| const csvString = csv.join('\n'); | |
| const filename = `行业资金流向_${period}_${new Date().toISOString().slice(0, 10)}.csv`; | |
| const blob = new Blob(['\uFEFF' + csvString], { type: 'text/csv;charset=utf-8;' }); | |
| const link = document.createElement('a'); | |
| link.href = URL.createObjectURL(blob); | |
| link.download = filename; | |
| link.style.display = 'none'; | |
| document.body.appendChild(link); | |
| link.click(); | |
| document.body.removeChild(link); | |
| } | |
| </script> | |
| {% endblock %} |