|
{% 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>
|
|
|
|
</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">
|
|
|
|
</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">
|
|
|
|
</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();
|
|
|
|
|
|
$.ajax({
|
|
url: `/api/industry_detail?industry=${encodeURIComponent(industry)}`,
|
|
type: 'GET',
|
|
success: function(industryDetail) {
|
|
console.log("Industry detail loaded successfully:", industryDetail);
|
|
|
|
|
|
$.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));
|
|
|
|
|
|
const topInflow = sortedByNetFlow.slice(0, 10);
|
|
renderBarChart('top-inflow-chart',
|
|
topInflow.map(item => item.industry),
|
|
topInflow.map(item => parseFloat(item.netFlow || 0)),
|
|
'资金净流入(亿)',
|
|
'#00E396');
|
|
|
|
|
|
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');
|
|
|
|
|
|
const topGainers = sortedByChange.slice(0, 10);
|
|
renderBarChart('top-gainers-chart',
|
|
topGainers.map(item => item.industry),
|
|
topGainers.map(item => parseFloat(item.change || 0)),
|
|
'涨幅(%)',
|
|
'#00E396');
|
|
|
|
|
|
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) {
|
|
|
|
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);
|
|
|
|
|
|
const topInflow = sortedByNetFlow.slice(0, 10);
|
|
renderBarChart('top-inflow-chart', topInflow.map(item => item.industry), topInflow.map(item => item.netFlow), '资金净流入(亿元)', '#00E396');
|
|
|
|
|
|
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));
|
|
|
|
|
|
const topGainers = sortedByChange.slice(0, 10);
|
|
renderBarChart('top-gainers-chart', topGainers.map(item => item.industry), topGainers.map(item => parseFloat(item.change)), '涨幅(%)', '#00E396');
|
|
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
|
|
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++) {
|
|
|
|
let text = cols[j].innerText.replace(/(\r\n|\n|\r)/gm, '').replace(/,/g, ',');
|
|
row.push(text);
|
|
}
|
|
|
|
csv.push(row.join(','));
|
|
}
|
|
|
|
|
|
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 %} |