Files
superlink/static/js/dashboard.js
T
nanguaboss 71c6e8d9c1 v1.0.10
2026-08-03 00:07:01 +08:00

253 lines
12 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* dashboard.js - 首页逻辑
* 8个指标卡 + 区域分布(国内=中国地图按省份 / 全球=世界地图按国家,各含企业/人员Tab)
* + 行业分布(企业/人员)+ 需求关键词词云(占位)
*/
$(function () {
renderShell('综合概览', '首页 / 数据看板');
initPage('dashboard', function (user) {
loadStats();
});
// 图表状态(Tab 选中态与数据维度)
var pageData = null;
var regionState = { china: { dim: 'company' }, world: { dim: 'company' } };
var industryState = { dim: 'company' };
/** 数值安全转换:NaN / 空值一律转 0 */
function toNum(v) {
var n = parseInt(v, 10);
return isNaN(n) ? 0 : n;
}
function loadStats() {
httpGet('dashboard/stats.php').then(function (data) {
renderMetricCards(data.metrics || {});
pageData = data;
renderRegionChart('china');
renderRegionChart('world');
renderIndustryChart();
renderWordCloud(data.wordcloud || []);
});
}
/* ============ 8 个指标卡 + 图表容器 ============ */
function renderMetricCards(m) {
var cards = [
{ label: '累计企业', num: toNum(m.company_total), cls: 'c-blue' },
{ label: '累计人员', num: toNum(m.person_total), cls: 'c-green' },
{ label: '昨日新增数据(企业/人员)', num: toNum(m.company_new) + '/' + toNum(m.person_new), cls: 'c-orange' },
{ label: '待处理数据碎片', num: toNum(m.preliminary), cls: 'c-red' },
{ label: '覆盖行业', num: toNum(m.industry_count), cls: 'c-purple' },
{ label: '生产型企业', num: toNum(m.manufacturer), cls: 'c-blue' },
{ label: '集成商/代理商', num: toNum(m.integrator), cls: 'c-cyan' },
{ label: '行业媒体资源', num: toNum(m.media_resource), cls: 'c-teal' }
];
var html = '';
cards.forEach(function (c) {
html += '<div class="stat-card ' + c.cls + '">' +
'<div class="num">' + (c.num === undefined ? '-' : c.num) + '</div>' +
'<div class="label">' + c.label + '</div></div>';
});
$('#page-content').html(
'<div class="stat-cards">' + html + '</div>' +
'<div class="chart-grid">' +
' <div class="chart-box"><div class="chart-title">区域分布(国内)' +
' <span class="tabs" data-group="region-china">' +
' <a class="tab' + (regionState.china.dim === 'company' ? ' active' : '') + '" data-val="company" onclick="switchRegionDim(\'china\',\'company\')">企业</a>' +
' <a class="tab' + (regionState.china.dim === 'person' ? ' active' : '') + '" data-val="person" onclick="switchRegionDim(\'china\',\'person\')">人员</a>' +
' </span></div><div class="chart" id="chart-region-china"></div></div>' +
' <div class="chart-box"><div class="chart-title">区域分布(全球)' +
' <span class="tabs" data-group="region-world">' +
' <a class="tab' + (regionState.world.dim === 'company' ? ' active' : '') + '" data-val="company" onclick="switchRegionDim(\'world\',\'company\')">企业</a>' +
' <a class="tab' + (regionState.world.dim === 'person' ? ' active' : '') + '" data-val="person" onclick="switchRegionDim(\'world\',\'person\')">人员</a>' +
' </span></div><div class="chart" id="chart-region-world"></div></div>' +
' <div class="chart-box"><div class="chart-title">行业分布' +
' <span class="tabs" data-group="industry">' +
' <a class="tab' + (industryState.dim === 'company' ? ' active' : '') + '" data-val="company" onclick="switchIndustryDim(\'company\')">企业</a>' +
' <a class="tab' + (industryState.dim === 'person' ? ' active' : '') + '" data-val="person" onclick="switchIndustryDim(\'person\')">人员</a>' +
' </span></div><div class="chart" id="chart-industry"></div></div>' +
' <div class="chart-box"><div class="chart-title">需求关键词(词云,简化提取,后续接入分词服务)</div><div class="chart" id="chart-wordcloud"></div></div>' +
'</div>'
);
}
/* ============ Tab 切换(内联 onclick,数据属性精确匹配选中态) ============ */
window.switchRegionDim = function (box, dim) {
regionState[box].dim = dim;
syncTabs();
renderRegionChart(box);
};
window.switchIndustryDim = function (dim) {
industryState.dim = dim;
syncTabs();
renderIndustryChart();
};
/** 同步所有 Tab 的选中色块 */
function syncTabs() {
$('.chart-title .tabs').each(function () {
var $tabs = $(this);
var group = $tabs.data('group');
$tabs.find('.tab').each(function () {
var $t = $(this);
var val = $t.data('val');
var active = false;
if (group === 'region-china') active = (regionState.china.dim === val);
else if (group === 'region-world') active = (regionState.world.dim === val);
else if (group === 'industry') active = (industryState.dim === val);
$t.toggleClass('active', active);
});
});
}
/* ============ 区域分布(china=中国地图按省份 / world=世界地图按国家) ============ */
function renderRegionChart(box) {
var el = document.getElementById('chart-region-' + box);
if (!el || !window.echarts) return;
try {
var dim = regionState[box].dim;
var rows = ((pageData.region || {})[dim] || {})[box] || [];
rows = rows.map(function (r) {
return { name: r.name, value: toNum(r.value) };
});
var chart = echarts.getInstanceByDom(el) || echarts.init(el);
var mapName = box === 'china' ? 'china' : 'world';
var hasMap = !!(echarts.getMap && echarts.getMap(mapName));
// 世界地图:ECharts5 不支持 series.nameMap,需把中文国家名转英文(地图用英文名匹配);中文名保留给 tooltip
if (box === 'world') {
rows = rows.map(function (r) {
var en = CN_TO_EN[r.name] || r.name;
return { name: en, value: r.value, displayName: r.name };
});
}
if (!hasMap || !rows.length) {
// 地图未加载或无数据时降级为柱状图
if (!rows.length) {
chart.setOption({
title: { text: '暂无' + (dim === 'company' ? '企业' : '人员') + '数据', left: 'center', top: 'middle', textStyle: { color: '#b6bfcc', fontSize: 14 } }
});
} else {
chart.setOption({
tooltip: { trigger: 'axis' },
grid: { left: 60, right: 20, top: 30, bottom: 80 },
xAxis: { type: 'category', data: rows.map(function (r) { return r.name; }), axisLabel: { rotate: 45 } },
yAxis: { type: 'value' },
series: [{ type: 'bar', data: rows.map(function (r) { return r.value; }), itemStyle: { color: '#16a085' }, barMaxWidth: 40 }]
});
}
return;
}
var values = rows.map(function (r) { return r.value; });
var maxVal = Math.max.apply(null, values);
var option = {
tooltip: {
trigger: 'item',
formatter: function (p) {
// 无数据区域 p.value 为 NaN/undefined,统一显示 0
var v = p.value;
if (v === undefined || v === null || isNaN(v)) v = 0;
return ((p.data && p.data.displayName) || p.name) + ':' + v;
}
},
// 颜色深浅按数据量(数值越大颜色越深),不显示左下角图例
visualMap: {
show: false,
min: 0,
max: maxVal,
inRange: { color: ['#e8f1ff', '#1e3c72'] }
},
series: [{
type: 'map',
map: mapName,
roam: true,
label: { show: false },
emphasis: {
label: { show: true, formatter: function (p) { return ((p.data && p.data.displayName) || p.name); } },
itemStyle: { areaColor: '#f39c12' }
},
data: rows
}]
};
chart.setOption(option);
window.addEventListener('resize', function () { chart.resize(); });
} catch (e) {
console && console.error('region chart error', e);
}
}
/* ============ 行业分布(柱状图,企业/人员) ============ */
function renderIndustryChart() {
var el = document.getElementById('chart-industry');
if (!el || !window.echarts) return;
try {
var dim = industryState.dim;
var rows = ((pageData.industry || {})[dim] || []).map(function (r) {
return { name: r.name, value: toNum(r.value) };
});
var chart = echarts.getInstanceByDom(el) || echarts.init(el);
if (!rows.length) {
chart.setOption({
title: { text: '暂无' + (dim === 'company' ? '企业' : '人员') + '行业数据', left: 'center', top: 'middle', textStyle: { color: '#b6bfcc', fontSize: 14 } }
});
return;
}
chart.setOption({
tooltip: { trigger: 'axis' },
grid: { left: 70, right: 20, top: 30, bottom: 50 },
xAxis: { type: 'category', data: rows.map(function (r) { return r.name; }), axisLabel: { rotate: 30 } },
yAxis: { type: 'value' },
series: [{
type: 'bar',
data: rows.map(function (r) { return r.value; }),
itemStyle: { color: '#2a5298', borderRadius: [3, 3, 0, 0] },
barMaxWidth: 40
}]
});
window.addEventListener('resize', function () { chart.resize(); });
} catch (e) {
console && console.error('industry chart error', e);
}
}
/* ============ 需求关键词词云(占位) ============ */
function renderWordCloud(words) {
var el = document.getElementById('chart-wordcloud');
if (!el || !window.echarts) return;
try {
var chart = echarts.getInstanceByDom(el) || echarts.init(el);
if (!words.length) {
chart.setOption({
title: { text: '暂无关键词数据(占位,后续接入分词服务)', left: 'center', top: 'middle', textStyle: { color: '#b6bfcc', fontSize: 14 } }
});
return;
}
var data = words.map(function (w) {
return {
name: w.name,
value: w.value,
symbolSize: Math.min(60, 14 + w.value * 2),
itemStyle: { color: 'hsl(' + (w.name.length * 37 % 360) + ',65%,55%)' }
};
});
chart.setOption({
tooltip: { formatter: function (p) { return p.name + ':' + toNum(p.value) + '次'; } },
series: [{
type: 'scatter',
data: data,
label: { show: true, formatter: function (p) { return p.name; }, fontSize: 12, color: '#333' },
symbol: 'circle'
}]
});
window.addEventListener('resize', function () { chart.resize(); });
} catch (e) {
console && console.error('wordcloud error', e);
}
}
});