修复查看器:滚动条+缩放/展开按钮+初始视图

This commit is contained in:
2026-08-06 15:23:48 +08:00
parent 9b6c5ccca6
commit 2cc49e7b94
+117 -82
View File
@@ -6,16 +6,21 @@
<title>金地商置股权架构图</title>
<style>
* { margin:0; padding:0; box-sizing:border-box; }
body { font-family: "PingFang SC","Microsoft YaHei",sans-serif; background:#f5f7fa; }
.header { background:#1e6fd9; color:#fff; padding:12px 16px; display:flex; align-items:center; gap:12px; flex-wrap:wrap; }
.header h1 { font-size:17px; font-weight:600; white-space:nowrap; }
.search-box { flex:1; min-width:200px; max-width:400px; position:relative; }
.search-box input { width:100%; padding:8px 34px 8px 12px; border:none; border-radius:6px; font-size:14px; outline:none; }
.search-box .clear { position:absolute; right:8px; top:50%; transform:translateY(-50%); cursor:pointer; color:#999; display:none; font-size:18px; }
.stats { font-size:12px; opacity:.9; white-space:nowrap; }
.legend { font-size:12px; opacity:.9; white-space:nowrap; }
#chart { width:100%; height:calc(100vh - 56px); }
.tip { position:fixed; bottom:16px; left:50%; transform:translateX(-50%); background:rgba(0,0,0,.75); color:#fff; padding:8px 16px; border-radius:20px; font-size:13px; z-index:99; display:none; }
html,body { height:100%; font-family:"PingFang SC","Microsoft YaHei",sans-serif; background:#f5f7fa; overflow:hidden; }
.header { background:#1e6fd9; color:#fff; padding:10px 14px; display:flex; align-items:center; gap:10px; flex-wrap:wrap; }
.header h1 { font-size:16px; font-weight:600; white-space:nowrap; }
.search-box { flex:1; min-width:160px; max-width:360px; position:relative; }
.search-box input { width:100%; padding:7px 30px 7px 10px; border:none; border-radius:6px; font-size:13px; outline:none; }
.search-box .clear { position:absolute; right:8px; top:50%; transform:translateY(-50%); cursor:pointer; color:#999; display:none; font-size:16px; }
.stats { font-size:12px; opacity:.95; white-space:nowrap; }
.btn-group { display:flex; gap:6px; flex-wrap:wrap; }
.btn { background:rgba(255,255,255,.18); color:#fff; border:1px solid rgba(255,255,255,.35); border-radius:5px;
padding:5px 10px; font-size:12px; cursor:pointer; transition:background .15s; }
.btn:hover { background:rgba(255,255,255,.32); }
#wrap { position:relative; overflow:auto; background:#fff; }
#chart { width:100%; height:100%; }
.tip { position:fixed; bottom:16px; left:50%; transform:translateX(-50%); background:rgba(0,0,0,.78); color:#fff;
padding:8px 16px; border-radius:20px; font-size:13px; z-index:99; display:none; max-width:80%; text-align:center; }
.matched { font-weight:700; color:#e6a700; }
</style>
</head>
@@ -23,13 +28,19 @@
<div class="header">
<h1>🏢 金地商置股权架构图</h1>
<div class="search-box">
<input id="search" type="text" placeholder="🔍 搜索公司名称(如:威新 / 金地 / 置业)">
<input id="search" type="text" placeholder="🔍 搜索公司名称">
<span class="clear" id="clearBtn">×</span>
</div>
<div class="stats" id="stats"></div>
<div class="legend">🟦 根公司 &nbsp; ⬜ 子公司 &nbsp; 🔍 点击节点展开/收起 &nbsp; 滚轮缩放</div>
<div class="btn-group">
<button class="btn" id="btnZoomIn">🔍 放大</button>
<button class="btn" id="btnZoomOut">🔍 缩小</button>
<button class="btn" id="btnReset">🔄 重置</button>
<button class="btn" id="btnExpandAll">📂 全部展开</button>
<button class="btn" id="btnCollapseAll">📁 全部收起</button>
</div>
</div>
<div id="chart"></div>
<div id="wrap"><div id="chart"></div></div>
<div class="tip" id="tip"></div>
<script>
@@ -84,55 +95,103 @@ var treeData = [{"name": "上海信能度尔信息科技有限公司", "org2": "
var relCount = 630;
var companyCount = 668;
// 统计节点数
function countNodes(n) { return 1 + (n.children||[]).reduce((s,c)=>s+countNodes(c),0); }
document.getElementById('stats').textContent = '共 ' + companyCount + ' 家公司 · ' + relCount + ' 条持股关系';
function countNodes(n) { return 1 + (n.children||[]).reduce(function(s,c){return s+countNodes(c);},0); }
var totalNodes = treeData.reduce(function(s,n){return s+countNodes(n);},0);
var depth = 7;
var maxW = 121;
document.getElementById('stats').textContent = companyCount + ' 家公司 · ' + relCount + ' 条关系 · ' + depth + ' 层';
var chart = echarts.init(document.getElementById('chart'));
window.addEventListener('resize', function(){ chart.resize(); });
var wrap = document.getElementById('wrap');
var chartEl = document.getElementById('chart');
var chart = echarts.init(chartEl);
// ===== 画布尺寸 =====
var BASE_W = Math.max(1600, maxW * 50);
var BASE_H = Math.max(800, depth * 140);
var zoom = 1.0;
var MIN_ZOOM = 0.3, MAX_ZOOM = 4.0;
function applySize() {
chartEl.style.width = (BASE_W * zoom) + 'px';
chartEl.style.height = (BASE_H * zoom) + 'px';
chart.resize();
}
// 为每个节点加 children 数量标签
function annotate(n) {
n.value = [n.name, (n.children||[]).length];
n.label = { formatter: '{b}' };
(n.children||[]).forEach(annotate);
return n;
}
treeData.forEach(annotate);
var option = {
tooltip: {
trigger: 'item',
triggerOn: 'mousemove',
formatter: function(p) {
if (p.data && p.data.org2) return '<b>' + p.data.name + '</b><br>所属:' + p.data.org2;
return p.data ? p.data.name : '';
}
},
// 初始: 只展开第一层
function markCollapsed(ns, isRoot) {
ns.forEach(function(n){
if (!isRoot && n.children && n.children.length) n.collapsed = true;
if (n.children) markCollapsed(n.children, false);
});
}
markCollapsed(treeData, true);
var baseOption = {
tooltip: { trigger:'item', triggerOn:'mousemove',
formatter: function(p){ return p.data ? '<b>'+p.data.name+'</b>'+(p.data.org2?'<br>所属:'+p.data.org2:'') : ''; } },
series: [{
type: 'tree',
data: treeData,
top: '8%', bottom: '8%', left: '6%', right: '18%',
symbol: 'circle', symbolSize: 8,
initialTreeDepth: 1, // 默认只展开第一层(分层展开)
expandAndCollapse: true, // 点击展开/收起
roam: true, // 滚轮缩放 + 拖拽
label: { position: 'left', verticalAlign: 'middle', align: 'right', fontSize: 12, color: '#333' },
leaves: { label: { position: 'right', verticalAlign: 'middle', align: 'left' } },
emphasis: { focus: 'descendant' },
lineStyle: { color: '#999', width: 1.2, curveness: 0.2 },
itemStyle: { color: function(p){ return p.data.children && p.data.children.length ? '#1e6fd9' : '#aab'; } }
type:'tree', data: treeData, orient:'TB',
top:'4%', bottom:'4%', left:'2%', right:'2%',
symbol:'circle', symbolSize:7,
initialTreeDepth:1, expandAndCollapse:true, roam:true,
label:{ position:'bottom', fontSize:11, color:'#333', formatter:'{b}', distance:4 },
leaves:{ label:{ position:'bottom', fontSize:11, color:'#333' } },
emphasis:{ focus:'descendant' },
lineStyle:{ color:'#999', width:1.2, curveness:0.15 },
itemStyle:{ color:function(p){ return (p.data.children&&p.data.children.length)?'#1e6fd9':'#aab'; } }
}]
};
chart.setOption(option);
chart.setOption(baseOption);
applySize();
// ===== 搜索功能 =====
window.addEventListener('resize', function(){ chart.resize(); });
// ===== 缩放按钮 =====
function setZoom(z) {
zoom = Math.min(MAX_ZOOM, Math.max(MIN_ZOOM, z));
applySize();
showTip('缩放 ' + Math.round(zoom*100) + '%');
}
document.getElementById('btnZoomIn').onclick = function(){ setZoom(zoom * 1.25); };
document.getElementById('btnZoomOut').onclick = function(){ setZoom(zoom / 1.25); };
document.getElementById('btnReset').onclick = function(){ zoom = 1.0; applySize(); showTip('已重置视图'); };
// ===== 全部展开/收起 =====
function collectParents(ns, arr) {
ns.forEach(function(n){
if (n.children && n.children.length) { arr.push(n.name); collectParents(n.children, arr); }
});
return arr;
}
function expandAll() {
chart.dispatchAction({ type:'treeExpandAndCollapse', seriesIndex:0, name: collectParents(treeData, []) });
showTip('已全部展开');
}
function collapseAll() {
var names = [];
treeData.forEach(function(n){ if(n.children) n.children.forEach(function(c){ names.push(c.name); }); });
chart.dispatchAction({ type:'treeExpandAndCollapse', seriesIndex:0, name: names });
showTip('已全部收起');
}
document.getElementById('btnExpandAll').onclick = expandAll;
document.getElementById('btnCollapseAll').onclick = collapseAll;
// ===== 搜索 =====
var searchInput = document.getElementById('search');
var clearBtn = document.getElementById('clearBtn');
var tip = document.getElementById('tip');
var lastQuery = '';
var tipTimer = null;
function showTip(msg){ tip.textContent=msg; tip.style.display='block'; clearTimeout(tipTimer); tipTimer=setTimeout(function(){tip.style.display='none';},3000); }
function showTip(msg) { tip.textContent = msg; tip.style.display = 'block'; setTimeout(function(){ tip.style.display='none'; }, 3000); }
var allNames = [];
(function collect(ns){ ns.forEach(function(n){ allNames.push(n.name); if(n.children) collect(n.children); }); })(treeData);
function findPath(name, nodes, path) {
for (var i=0;i<nodes.length;i++) {
@@ -145,70 +204,46 @@ function findPath(name, nodes, path) {
}
return null;
}
// 收集所有公司名 → 搜索建议
var allNames = [];
(function collect(ns){ ns.forEach(function(n){ allNames.push(n.name); if(n.children) collect(n.children); }); })(treeData);
// 展开路径(把某节点的所有祖先展开)
function expandPath(path) {
var expanded = [];
for (var i=0;i<path.length-1;i++) {
expanded.push(path[i].name);
}
chart.dispatchAction({
type: 'treeExpandAndCollapse',
seriesIndex: 0,
name: expanded
});
chart.setOption({ series:[{ data: treeData }] });
var names = path.slice(0, -1).map(function(n){ return n.name; });
if (names.length) chart.dispatchAction({ type:'treeExpandAndCollapse', seriesIndex:0, name: names });
}
searchInput.addEventListener('input', function() {
var q = searchInput.value.trim().toLowerCase();
clearBtn.style.display = q ? 'block' : 'none';
if (!q) {
// 清空搜索:重置为初始视图
chart.setOption({ series:[{ data: treeData }] });
chart.setOption({ series:[{ data: treeData, label:{formatter:'{b}', rich:{}} }] });
return;
}
var matches = allNames.filter(function(n){ return n.toLowerCase().indexOf(q) !== -1; });
if (matches.length === 0) { showTip('未找到包含「' + q + '」的公司'); return; }
if (matches.length > 50) { showTip('找到 ' + matches.length + ' 家,仅示前 50,请更精确'); matches = matches.slice(0,50); }
// 高亮匹配项(通过 tooltip 提示 + 展开第一个匹配的路径)
if (matches.length === 0) { showTip('未找到包含「'+q+'」的公司'); return; }
if (matches.length > 50) { showTip('找到 '+matches.length+' 家,仅示前 50,请更精确'); matches = matches.slice(0,50); }
var first = matches[0];
var path = findPath(first, treeData, []);
if (path) {
expandPath(path);
showTip('找到 ' + matches.length + ' 家:' + matches.slice(0,5).join('、') + (matches.length>5?'…':''));
}
// 高亮所有匹配节点
if (path) expandPath(path);
showTip('找到 ' + matches.length + ' 家:' + matches.slice(0,5).join('、') + (matches.length>5?'…':''));
chart.setOption({
series: [{
data: treeData,
label: {
formatter: function(p){
if (p.data && p.data.name.toLowerCase().indexOf(q) !== -1) return '{matched|' + p.data.name + '}';
if (p.data && p.data.name.toLowerCase().indexOf(q) !== -1) return '{matched|'+p.data.name+'}';
return p.data ? p.data.name : '';
},
rich: { matched: { color:'#e6a700', fontWeight:'bold' } }
rich: { matched:{ color:'#e6a700', fontWeight:'bold' } }
}
}]
});
});
clearBtn.onclick = function(){ searchInput.value=''; clearBtn.style.display='none'; chart.setOption({ series:[{ data: treeData, label:{formatter:'{b}', rich:{}} }] }); };
clearBtn.addEventListener('click', function(){
searchInput.value = '';
clearBtn.style.display = 'none';
chart.setOption({ series:[{ data: treeData }] });
});
// 点击空白处提示
chart.on('click', function(p){
if (p.data && p.data.children && p.data.children.length) {
showTip('「' + p.data.name + '」' + (p.data.children.length) + ' 家子公司');
showTip('「'+p.data.name+'」共 ' + p.data.children.length + ' 家子公司');
}
});
setTimeout(function(){ zoom=1.0; applySize(); }, 200);
</script>
</body>
</html>