You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

217 lines
7.2 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>知识图谱可视化(接口调用修复版)</title>
<script src="https://cdn.jsdelivr.net/npm/echarts@5.4.3/dist/echarts.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/echarts@5.4.3/dist/extension/graph.min.js"></script>
<style>
.container { width: 1200px; margin: 20px auto; }
#param-form {
margin-bottom: 20px;
padding: 15px;
border: 1px solid #eee;
border-radius: 4px;
}
.form-group {
margin-bottom: 12px;
display: flex;
align-items: center;
gap: 10px;
}
.form-group label {
width: 120px;
font-weight: bold;
}
.form-group input {
flex: 1;
padding: 6px 8px;
border: 1px solid #ddd;
border-radius: 3px;
}
.form-group input[type="number"] {
width: 100px;
}
#note-ids-input {
display: flex;
gap: 5px;
align-items: center;
}
#note-ids-input input {
flex: 1;
}
button {
padding: 8px 16px;
background: #4285f4;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
button:hover {
background: #3367d6;
}
#graph-container {
width: 100%;
height: 700px;
border: 1px solid #eee;
}
#node-info {
margin-top: 10px;
padding: 10px;
border: 1px solid #eee;
border-radius: 4px;
}
.required-mark {
color: red;
}
.error-message {
color: #dc3545;
}
.loading-message {
color: #6c757d;
}
</style>
</head>
<body>
<div class="container">
<form id="param-form">
<div class="form-group">
<label>标签<label class="required-mark">*</label></label>
<input type="text" id="label" placeholder="输入标签(支持通配符*" required value="*">
</div>
<div class="form-group">
<label>最大深度:</label>
<input type="number" id="max_depth" value="3" min="1">
</div>
<div class="form-group">
<label>最大节点数:</label>
<input type="number" id="max_nodes" value="100" min="1">
</div>
<div class="form-group">
<label>文档ID<label class="required-mark">*</label></label>
<input type="text" id="note_ids" placeholder="多个用逗号分隔(如:马克思主义原理.txt" required value="马克思主义原理.txt">
</div>
<button type="submit">加载知识图谱</button>
</form>
<div id="graph-container"></div>
<div id="node-info">请点击按钮调用接口</div>
</div>
<script>
document.getElementById('param-form').addEventListener('submit', function(e) {
e.preventDefault();
const nodeInfo = document.getElementById('node-info');
nodeInfo.className = 'loading-message';
nodeInfo.innerHTML = '正在加载...';
// 1. 收集参数(不手动编码)
const params = {
label: document.getElementById('label').value.trim(),
max_depth: document.getElementById('max_depth').value.trim() || 3,
max_nodes: document.getElementById('max_nodes').value.trim() || 100,
note_ids: document.getElementById('note_ids').value.trim().split(',').map(id => id.trim()).filter(Boolean)
};
if (!params.label || params.note_ids.length === 0) {
nodeInfo.className = 'error-message';
nodeInfo.innerHTML = '请填写必填参数';
return;
}
// 2. 构建URL关键不手动编码让URLSearchParams自动处理
const baseUrl = 'http://localhost:9621/graphs/note';
const urlParams = new URLSearchParams();
// 直接传原始值URLSearchParams会自动编码一次正确格式
urlParams.append('label', params.label);
urlParams.append('max_depth', params.max_depth);
urlParams.append('max_nodes', params.max_nodes);
params.note_ids.forEach(id => urlParams.append('note_ids', id));
const fullUrl = `${baseUrl}?${urlParams.toString()}`;
console.log('调用接口:', fullUrl); // 此时日志中的note_ids应为正确的一次编码格式
// 3. 发起请求
fetch(fullUrl, {
method: 'GET',
mode: 'cors',
headers: { 'Accept': 'application/json' }
})
.then(response => {
if (!response.ok) throw new Error(`HTTP ${response.status}`);
return response.json();
})
.then(kgData => {
if (!kgData.nodes || !kgData.edges) throw new Error('数据格式错误');
// 4. 处理节点连接数
const nodeLinkCount = {};
kgData.nodes.forEach(node => nodeLinkCount[node.id] = 0);
kgData.edges.forEach(edge => {
nodeLinkCount[edge.source]++;
nodeLinkCount[edge.target]++;
});
// 5. 转换为ECharts格式
const echartsNodes = kgData.nodes.map(node => ({
id: node.id,
name: node.labels[0],
category: node.properties.entity_type,
properties: node.properties,
symbolSize: 20 + (nodeLinkCount[node.id] * 3)
}));
const echartsLinks = kgData.edges.map(edge => ({
source: edge.source,
target: edge.target,
properties: edge.properties
}));
const categories = Array.from(new Set(echartsNodes.map(n => n.category))).map(type => ({ name: type }));
// 6. 渲染图表
const chart = echarts.init(document.getElementById('graph-container'));
chart.setOption({
tooltip: { formatter: '{b}' },
legend: { data: categories.map(c => c.name), bottom: 10 },
series: [{
type: 'graph',
layout: 'force',
roam: true,
draggable: true,
force: { repulsion: 300, edgeLength: 100, gravity: 0.1, iterations: 50 },
label: { show: true, fontSize: 12 },
categories: categories,
data: echartsNodes,
links: echartsLinks,
emphasis: { focus: 'adjacency', lineStyle: { width: 5 } }
}]
});
// 7. 点击事件
chart.on('click', (params) => {
// 保持原有点击逻辑不变
if (params.dataType === 'node') {
const p = params.data.properties;
nodeInfo.innerHTML = `<strong>${params.name}</strong> | 类型:${p.entity_type} | 连接数:${nodeLinkCount[params.data.id]}<br>描述:${p.description?.slice(0, 500)}...`;
} else if (params.dataType === 'edge') {
const p = params.data.properties;
const sourceNode = echartsNodes.find(n => n.id === params.data.source);
const targetNode = echartsNodes.find(n => n.id === params.data.target);
nodeInfo.innerHTML = `<strong>边信息</strong> | ${sourceNode?.name}${targetNode?.name}<br>描述:${p.description}`;
}
});
window.addEventListener('resize', () => chart.resize());
nodeInfo.className = '';
nodeInfo.innerHTML = '加载完成 | 点击节点/边查看详情';
})
.catch(error => {
console.error('错误:', error);
nodeInfo.className = 'error-message';
nodeInfo.innerHTML = `加载失败:${error.message}`;
});
});
</script>
</body>
</html>