Compare commits
1 Commits
| Author | SHA1 | Date |
|---|---|---|
|
|
a0cc39f84e | 2 months ago |
@ -1,19 +0,0 @@
|
||||
# Python
|
||||
__pycache__/
|
||||
*.pyc
|
||||
*.pyo
|
||||
|
||||
# Node
|
||||
node_modules/
|
||||
dist/
|
||||
|
||||
# IDE
|
||||
.vscode/
|
||||
.idea/
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# Env
|
||||
.env
|
||||
|
|
|
|
|
|
|
|
|
File diff suppressed because it is too large
Load Diff
@ -1,4 +0,0 @@
|
||||
flask
|
||||
flask-cors
|
||||
neo4j
|
||||
openpyxl
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,73 +0,0 @@
|
||||
/**
|
||||
* 重置管理员密码 — 使用与前端 auth.js 完全一致的参数
|
||||
*/
|
||||
const crypto = require('crypto');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
// 与前端 auth.js 一致的参数
|
||||
const SYSTEM_SALT = new Uint8Array([
|
||||
0xDA, 0x4A, 0x1F, 0x93, 0xB2, 0x7E, 0x05, 0xC1,
|
||||
0x38, 0xD9, 0x6A, 0x2B, 0x44, 0xF0, 0x87, 0x3E
|
||||
]);
|
||||
const PBKDF2_ITERATIONS = 100000;
|
||||
const KEY_LENGTH = 16; // AES-128
|
||||
const DIGEST = 'sha256';
|
||||
const PLAINTEXT = 'DARPA_AUTH';
|
||||
|
||||
function decrypt(b64, password) {
|
||||
try {
|
||||
const combined = Buffer.from(b64, 'base64');
|
||||
const iv = combined.subarray(0, 12);
|
||||
const authTag = combined.subarray(combined.length - 16);
|
||||
const ciphertext = combined.subarray(12, combined.length - 16);
|
||||
const key = crypto.pbkdf2Sync(password, SYSTEM_SALT, PBKDF2_ITERATIONS, KEY_LENGTH, DIGEST);
|
||||
const decipher = crypto.createDecipheriv('aes-128-gcm', key, iv);
|
||||
decipher.setAuthTag(authTag);
|
||||
return Buffer.concat([decipher.update(ciphertext), decipher.final()]).toString('utf8');
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function encrypt(password, plaintext) {
|
||||
const key = crypto.pbkdf2Sync(password, SYSTEM_SALT, PBKDF2_ITERATIONS, KEY_LENGTH, DIGEST);
|
||||
const iv = crypto.randomBytes(12);
|
||||
const cipher = crypto.createCipheriv('aes-128-gcm', key, iv);
|
||||
const encrypted = Buffer.concat([cipher.update(plaintext, 'utf8'), cipher.final()]);
|
||||
const authTag = cipher.getAuthTag();
|
||||
const combined = Buffer.concat([iv, encrypted, authTag]);
|
||||
return combined.toString('base64');
|
||||
}
|
||||
|
||||
// ── 读取 users.json ──
|
||||
const usersPath = path.join(__dirname, '..', 'users.json');
|
||||
const users = JSON.parse(fs.readFileSync(usersPath, 'utf8'));
|
||||
|
||||
const storedCT = users['woker'].ciphertext;
|
||||
console.log('=== 当前状态 ===');
|
||||
console.log('woker 的密文:', storedCT);
|
||||
|
||||
// 测试可能的旧密码
|
||||
const testPwds = ['Worker@2026', 'Admin@2025', 'admin123', 'woker', 'DARPA2025', 'Work@2026'];
|
||||
for (const pwd of testPwds) {
|
||||
const r = decrypt(storedCT, pwd);
|
||||
console.log(' 尝试密码 [' + pwd + ']:', r === PLAINTEXT ? 'OK' : (r || '解密失败'));
|
||||
}
|
||||
|
||||
// ── 重置为 Worker@2026 ──
|
||||
const NEW_PASSWORD = 'Worker@2026';
|
||||
console.log('\n=== 重置 ===');
|
||||
const newCT = encrypt(NEW_PASSWORD, PLAINTEXT);
|
||||
console.log('新密码:', NEW_PASSWORD);
|
||||
console.log('新密文:', newCT);
|
||||
|
||||
// 验证新密文可解密
|
||||
const verify = decrypt(newCT, NEW_PASSWORD);
|
||||
console.log('验证解密:', verify === PLAINTEXT ? '通过' : '失败!');
|
||||
|
||||
// 更新并写回
|
||||
users['woker'].ciphertext = newCT;
|
||||
users['woker'].name = '系统管理员';
|
||||
fs.writeFileSync(usersPath, JSON.stringify(users, null, 2) + '\n', 'utf8');
|
||||
console.log('\nusers.json 已更新!');
|
||||
@ -1,70 +0,0 @@
|
||||
/**
|
||||
* 验证加密/解密是否与前端 auth.js 一致
|
||||
*/
|
||||
const crypto = require('crypto');
|
||||
const path = require('path');
|
||||
|
||||
const SYSTEM_SALT = new Uint8Array([
|
||||
0xDA, 0x4A, 0x1F, 0x93, 0xB2, 0x7E, 0x05, 0xC1,
|
||||
0x38, 0xD9, 0x6A, 0x2B, 0x44, 0xF0, 0x87, 0x3E
|
||||
]);
|
||||
|
||||
function encrypt(password, plaintext) {
|
||||
const key = crypto.pbkdf2Sync(password, SYSTEM_SALT, 100000, 16, 'sha256');
|
||||
const iv = crypto.randomBytes(12);
|
||||
const cipher = crypto.createCipheriv('aes-128-gcm', key, iv);
|
||||
const encrypted = Buffer.concat([
|
||||
cipher.update(plaintext, 'utf8'),
|
||||
cipher.final()
|
||||
]);
|
||||
const authTag = cipher.getAuthTag();
|
||||
const combined = Buffer.concat([iv, encrypted, authTag]);
|
||||
return combined.toString('base64');
|
||||
}
|
||||
|
||||
function decrypt(b64, password) {
|
||||
try {
|
||||
const combined = Buffer.from(b64, 'base64');
|
||||
const iv = combined.subarray(0, 12);
|
||||
const authTag = combined.subarray(combined.length - 16);
|
||||
const ciphertext = combined.subarray(12, combined.length - 16);
|
||||
const key = crypto.pbkdf2Sync(password, SYSTEM_SALT, 100000, 16, 'sha256');
|
||||
const decipher = crypto.createDecipheriv('aes-128-gcm', key, iv);
|
||||
decipher.setAuthTag(authTag);
|
||||
const decrypted = Buffer.concat([
|
||||
decipher.update(ciphertext),
|
||||
decipher.final()
|
||||
]);
|
||||
return decrypted.toString('utf8');
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// ── 测试 ──────────────────────────────────────────────────────────
|
||||
const testPassword = 'Admin@2025';
|
||||
const plaintext = 'DARPA_AUTH';
|
||||
|
||||
// 测试 1: 加密后解密
|
||||
const ciphertext = encrypt(testPassword, plaintext);
|
||||
console.log('密文:', ciphertext);
|
||||
const decrypted = decrypt(ciphertext, testPassword);
|
||||
console.log('解密结果:', decrypted);
|
||||
console.log('测试1 (加密→解密):', decrypted === plaintext ? '通过' : '失败');
|
||||
|
||||
// 测试 2: 验证 users.json 中的密文能否解密
|
||||
const fs = require('fs');
|
||||
const users = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'users.json'), 'utf8'));
|
||||
const storedCiphertext = users['woker'].ciphertext;
|
||||
console.log('\nusers.json 中 woker 的密文:', storedCiphertext);
|
||||
const result = decrypt(storedCiphertext, testPassword);
|
||||
console.log('用', testPassword, '解密结果:', result);
|
||||
console.log('测试2 (文件密文解密):', result === plaintext ? '通过' : '失败');
|
||||
|
||||
// 测试 3: 额外测试 base64 编解码一致性
|
||||
console.log('\nBase64 往返测试:');
|
||||
const testBytes = Buffer.from([0x00, 0xFF, 0x80, 0x7F, 0xAB, 0xCD]);
|
||||
const b64 = testBytes.toString('base64');
|
||||
console.log('原始字节:', testBytes.toString('hex'));
|
||||
console.log('Base64:', b64);
|
||||
console.log('解码回:', Buffer.from(b64, 'base64').toString('hex'));
|
||||
File diff suppressed because one or more lines are too long
@ -1,12 +0,0 @@
|
||||
{
|
||||
"names": [
|
||||
"刘安柯",
|
||||
"刘帝威",
|
||||
"张三",
|
||||
"张洺恺",
|
||||
"徐航",
|
||||
"李四",
|
||||
"王锦博",
|
||||
"系统管理员"
|
||||
]
|
||||
}
|
||||
@ -1,24 +0,0 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
.DS_Store
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,17 +0,0 @@
|
||||
{
|
||||
"name": "darpa-frontend",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"devDependencies": {
|
||||
"vite": "^8.0.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"vite-plugin-html": "^3.2.2"
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 9.3 KiB |
|
Before Width: | Height: | Size: 4.9 KiB |
@ -1,7 +0,0 @@
|
||||
/* =============================================================================
|
||||
* [CONFIG] 前端全局配置 — 修改此处即可适配不同环境
|
||||
* ============================================================================= */
|
||||
window.APP_CONFIG = {
|
||||
// 后端 API 地址(部署到其他机器时改这里)
|
||||
API_BASE: 'http://localhost:5001/api',
|
||||
};
|
||||
@ -1,216 +0,0 @@
|
||||
/* =============================================================================
|
||||
* [MODULE: detail] 项目详情模块 — 动态加载
|
||||
* ============================================================================= */
|
||||
|
||||
const API_BASE = window.APP_CONFIG.API_BASE;
|
||||
let detailProjectId = null;
|
||||
|
||||
// ── 初始化 ──────────────────────────────────────────────────
|
||||
function initDetail() {
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
const pid = params.get('id');
|
||||
if (pid) {
|
||||
detailProjectId = pid;
|
||||
loadProjectDetail(pid);
|
||||
} else {
|
||||
// 无 ID 时显示默认占位
|
||||
showDetailPlaceholder();
|
||||
}
|
||||
}
|
||||
|
||||
// ── 加载项目详情 ────────────────────────────────────────────
|
||||
async function loadProjectDetail(pid) {
|
||||
showDetailSkeleton();
|
||||
try {
|
||||
const resp = await fetch(`${API_BASE}/project/${encodeURIComponent(pid)}/detail`);
|
||||
if (!resp.ok) {
|
||||
showDetailError('项目不存在或加载失败');
|
||||
return;
|
||||
}
|
||||
const data = await resp.json();
|
||||
renderDetail(data);
|
||||
} catch (e) {
|
||||
console.error('Detail load error:', e);
|
||||
showDetailError('数据加载失败,请检查后端服务');
|
||||
}
|
||||
}
|
||||
|
||||
// ── 骨架屏 ──────────────────────────────────────────────────
|
||||
function showDetailSkeleton() {
|
||||
const titleEl = document.getElementById('detailTitle');
|
||||
if (titleEl) titleEl.innerHTML = '<div class="h-8 bg-slate-100 rounded w-3/4 animate-pulse"></div>';
|
||||
const abstractEl = document.getElementById('detailAbstractContent');
|
||||
if (abstractEl) abstractEl.innerHTML = '<div class="space-y-2 animate-pulse"><div class="h-3 bg-slate-50 rounded w-full"></div><div class="h-3 bg-slate-50 rounded w-5/6"></div><div class="h-3 bg-slate-50 rounded w-4/6"></div></div>';
|
||||
document.querySelectorAll('.detail-stat-val').forEach(el => { el.textContent = '...'; el.classList.add('skeleton-text'); });
|
||||
document.querySelectorAll('.detail-tag-area').forEach(el => el.innerHTML = '<span class="px-3 py-1 bg-slate-50 rounded-full text-xs skeleton-text-sm">---</span>');
|
||||
}
|
||||
|
||||
function showDetailError(msg) {
|
||||
const titleEl = document.getElementById('detailTitle');
|
||||
if (titleEl) titleEl.innerHTML = `<span class="text-red-400">${msg}</span>`;
|
||||
}
|
||||
|
||||
function showDetailPlaceholder() {
|
||||
const titleEl = document.getElementById('detailTitle');
|
||||
if (titleEl) titleEl.innerHTML = '<span class="text-slate-400">请从知识图谱中选择项目查看详情</span>';
|
||||
}
|
||||
|
||||
// ── 渲染详情 ────────────────────────────────────────────────
|
||||
function renderDetail(data) {
|
||||
// Title
|
||||
const titleEl = document.getElementById('detailTitle');
|
||||
if (titleEl) {
|
||||
titleEl.innerHTML = `<span id="detailNameCn">${data.nameCn || data.projectId}</span>`;
|
||||
titleEl.classList.remove('skeleton-text');
|
||||
}
|
||||
|
||||
// Subtitle / English name
|
||||
const enEl = document.getElementById('detailNameEn');
|
||||
if (enEl) { enEl.textContent = data.nameEn || ''; enEl.classList.remove('skeleton-text'); }
|
||||
|
||||
// Status badge
|
||||
const statusEl = document.getElementById('detailStatus');
|
||||
if (statusEl) {
|
||||
const st = data.status || '';
|
||||
const colorMap = { '结题': 'bg-slate-100 text-slate-600', '研发中': 'bg-emerald-100 text-emerald-700', '进行中': 'bg-blue-100 text-blue-700', '已完成': 'bg-green-100 text-green-700' };
|
||||
statusEl.textContent = st || '未知';
|
||||
statusEl.className = `px-2 py-0.5 text-[10px] font-bold uppercase tracking-wider rounded-full ${colorMap[st] || 'bg-slate-100 text-slate-500'}`;
|
||||
}
|
||||
|
||||
// Project ID
|
||||
const idEl = document.getElementById('detailProjectId');
|
||||
if (idEl) { idEl.textContent = `ID: ${data.projectId}`; idEl.classList.remove('skeleton-text'); }
|
||||
|
||||
// Basic info grid
|
||||
setDetailStat('detailOffice', data.office ? data.office.name : '-');
|
||||
setDetailStat('detailDomain', data.domain || '-');
|
||||
setDetailStat('detailPeriod', data.startYear && data.endYear ? `${data.startYear} — ${data.endYear}` : '-');
|
||||
setDetailStat('detailBudget', data.budget || '-');
|
||||
setDetailStat('detailManager', data.manager ? data.manager.name : '-');
|
||||
|
||||
// Abstract
|
||||
const abstractEl = document.getElementById('detailAbstractContent');
|
||||
if (abstractEl) {
|
||||
abstractEl.innerHTML = (data.description || '暂无项目摘要').replace(/\n/g, '<br>');
|
||||
abstractEl.classList.remove('line-clamp-4');
|
||||
// Hide expand overlay if not too long
|
||||
const overlay = document.getElementById('detailAbstractOverlay');
|
||||
if (overlay && (data.description || '').length < 300) overlay.style.display = 'none';
|
||||
}
|
||||
|
||||
// Keywords
|
||||
const kwEl = document.getElementById('detailKeywords');
|
||||
if (kwEl) {
|
||||
const kws = (data.keywords || '').split(/[,,、]/).filter(Boolean);
|
||||
if (kws.length) {
|
||||
kwEl.innerHTML = kws.map(k => `<span class="px-3 py-1 bg-blue-50 text-blue-600 text-xs font-semibold rounded-full">${k.trim()}</span>`).join('');
|
||||
} else {
|
||||
kwEl.innerHTML = '<span class="text-slate-300 text-xs">暂无关键词</span>';
|
||||
}
|
||||
}
|
||||
|
||||
// Technology tags
|
||||
const techEl = document.getElementById('detailTechnologies');
|
||||
if (techEl) {
|
||||
if (data.technologies && data.technologies.length) {
|
||||
techEl.innerHTML = data.technologies.map(t => `<span class="px-3 py-1 bg-purple-50 text-purple-600 text-xs font-semibold rounded-full cursor-pointer hover:bg-purple-100" title="${t.description || ''}">${t.name}</span>`).join('');
|
||||
} else {
|
||||
techEl.innerHTML = '<span class="text-slate-300 text-xs">暂无</span>';
|
||||
}
|
||||
}
|
||||
|
||||
// Wikipedia link
|
||||
const wikiEl = document.getElementById('detailWikipedia');
|
||||
if (wikiEl) {
|
||||
if (data.wikipediaUrl) {
|
||||
wikiEl.innerHTML = `<a href="${data.wikipediaUrl}" target="_blank" class="inline-flex items-center gap-1 text-xs text-blue-500 hover:text-blue-600"><i data-lucide="external-link" class="w-3 h-3"></i> Wikipedia</a>`;
|
||||
wikiEl.classList.remove('hidden');
|
||||
} else {
|
||||
wikiEl.classList.add('hidden');
|
||||
}
|
||||
}
|
||||
|
||||
// Institutions
|
||||
const instEl = document.getElementById('detailInstitutions');
|
||||
if (instEl) {
|
||||
if (data.institutions && data.institutions.length) {
|
||||
instEl.innerHTML = data.institutions.map(i => `<div class="flex items-center gap-2 py-1.5">
|
||||
<i data-lucide="building-2" class="w-3.5 h-3.5 text-slate-400"></i>
|
||||
<span class="text-xs text-slate-700">${i.name}</span></div>`).join('');
|
||||
} else {
|
||||
instEl.innerHTML = '<span class="text-slate-300 text-xs">暂无机构数据</span>';
|
||||
}
|
||||
}
|
||||
|
||||
// Manager info
|
||||
const mgrDetailEl = document.getElementById('detailManagerInfo');
|
||||
if (mgrDetailEl && data.manager) {
|
||||
const m = data.manager;
|
||||
mgrDetailEl.innerHTML = `<div class="text-xs font-bold text-slate-800">${m.name}</div>
|
||||
${m.bio ? `<p class="text-[10px] text-slate-500 mt-1">${m.bio.substring(0, 150)}</p>` : ''}
|
||||
${m.expertise ? `<p class="text-[10px] text-slate-400 mt-1">专长:${m.expertise}</p>` : ''}
|
||||
${m.affiliation ? `<p class="text-[10px] text-slate-400">机构:${m.affiliation}</p>` : ''}`;
|
||||
}
|
||||
|
||||
// Milestones timeline
|
||||
const msEl = document.getElementById('detailMilestones');
|
||||
if (msEl) {
|
||||
if (data.milestones && data.milestones.length) {
|
||||
const eraColors = { '1950s': '#ef4444','1960s': '#f97316','1970s': '#f59e0b','1980s': '#10b981','1990s': '#06b6d4','2000s': '#3B82F6','2010s': '#8b5cf6','2020s': '#ec4899' };
|
||||
msEl.innerHTML = data.milestones.map((ms, i) => {
|
||||
const color = eraColors[ms.era] || '#64748b';
|
||||
return `<div class="relative pl-6 detail-timeline-dot pb-5 ${i === data.milestones.length - 1 ? '' : ''}">
|
||||
<div class="absolute -left-[5px] top-1 w-2.5 h-2.5 rounded-full border-2 border-white" style="background:${color}"></div>
|
||||
<p class="text-[10px] font-bold uppercase mb-1" style="color:${color}">${ms.era || ''}</p>
|
||||
<p class="text-xs font-bold text-slate-800">${ms.title}</p>
|
||||
${ms.historicalSignificance ? `<p class="text-[10px] text-slate-500 mt-1 line-clamp-2">${ms.historicalSignificance}</p>` : ''}
|
||||
${ms.technicalDetail ? `<p class="text-[10px] text-slate-400 mt-0.5 line-clamp-2">${ms.technicalDetail}</p>` : ''}
|
||||
</div>`;
|
||||
}).join('');
|
||||
} else {
|
||||
msEl.innerHTML = '<p class="text-xs text-slate-300">暂无里程碑数据</p>';
|
||||
}
|
||||
}
|
||||
|
||||
lucide.createIcons();
|
||||
}
|
||||
|
||||
function setDetailStat(id, val) {
|
||||
const el = document.getElementById(id);
|
||||
if (el) { el.textContent = val; el.classList.remove('skeleton-text'); }
|
||||
}
|
||||
|
||||
// ── 导航到详情页 ───────────────────────────────────────────
|
||||
function navigateToDetail(projectId) {
|
||||
const newUrl = `${window.location.pathname}?id=${encodeURIComponent(projectId)}`;
|
||||
window.history.pushState({ id: projectId }, '', newUrl);
|
||||
detailProjectId = projectId;
|
||||
showPage('detailPage');
|
||||
setTimeout(() => loadProjectDetail(projectId), 100);
|
||||
}
|
||||
|
||||
// ── 展开/收起摘要 ──────────────────────────────────────────
|
||||
let detailAbstractExpanded = false;
|
||||
function toggleDetailAbstract() {
|
||||
const content = document.getElementById('detailAbstractContent');
|
||||
const overlay = document.getElementById('detailAbstractOverlay');
|
||||
const btn = overlay ? overlay.querySelector('button') : null;
|
||||
if (!detailAbstractExpanded) {
|
||||
content.classList.remove('line-clamp-4');
|
||||
if (overlay) { overlay.classList.remove('detail-abstract-fade'); overlay.style.background = 'none'; overlay.style.position = 'relative'; }
|
||||
if (btn) btn.innerHTML = '收起全文 <i data-lucide="chevron-up" class="w-3 h-3"></i>';
|
||||
detailAbstractExpanded = true;
|
||||
} else {
|
||||
content.classList.add('line-clamp-4');
|
||||
if (overlay) { overlay.classList.add('detail-abstract-fade'); overlay.style.background = ''; overlay.style.position = 'absolute'; }
|
||||
if (btn) btn.innerHTML = '展开全文 <i data-lucide="chevron-down" class="w-3 h-3"></i>';
|
||||
detailAbstractExpanded = false;
|
||||
}
|
||||
lucide.createIcons();
|
||||
}
|
||||
|
||||
// ── 挂载到 window ──────────────────────────────────────────
|
||||
window.initDetail = initDetail;
|
||||
window.navigateToDetail = navigateToDetail;
|
||||
window.toggleDetailAbstract = toggleDetailAbstract;
|
||||
window.loadProjectDetail = loadProjectDetail;
|
||||
@ -1,572 +0,0 @@
|
||||
/* =============================================================================
|
||||
* [MODULE: graph] 可视化分析模块 (API 驱动 — 连接 graph_backend)
|
||||
* ============================================================================= */
|
||||
|
||||
const API_BASE = window.APP_CONFIG.API_BASE;
|
||||
|
||||
// ── 颜色/符号映射 ──────────────────────────────────────────────
|
||||
const STATUS_COLOR = {
|
||||
'结题': '#0EA5E9', '研发中': '#3B82F6', '已完成': '#22C55E', '进行中': '#F97316'
|
||||
};
|
||||
const TYPE_COLOR = {
|
||||
'Project': '#1d4ed8',
|
||||
'Technology': '#6d28d9',
|
||||
'Institution': '#F59E0B',
|
||||
'Manager': '#e67e22',
|
||||
'Office': '#e74c3c',
|
||||
};
|
||||
const REL_COLOR = {
|
||||
'主导方': '#ef5350',
|
||||
'承担方': '#42a5f5',
|
||||
'合作方': '#546e7a',
|
||||
'USES_TECHNOLOGY': '#9c27b0',
|
||||
'BELONGS_TO': '#ffa726',
|
||||
'MANAGED_BY': '#ffd600',
|
||||
'PARTICIPATED_BY': '#78909c'
|
||||
};
|
||||
const TYPE_SYMBOL = {
|
||||
'Project': 'roundRect', 'Technology': 'diamond',
|
||||
'Institution': 'circle', 'Manager': 'triangle', 'Office': 'rect'
|
||||
};
|
||||
const TYPE_ICON = {
|
||||
'Project': 'rocket', 'Technology': 'cpu',
|
||||
'Institution': 'building-2', 'Manager': 'user', 'Office': 'landmark'
|
||||
};
|
||||
const TYPE_LABEL_CN = {
|
||||
'Project': '科研项目', 'Technology': '关键技术',
|
||||
'Institution': '研究机构', 'Manager': '项目经理', 'Office': '办公室'
|
||||
};
|
||||
|
||||
// ── 状态变量 ──────────────────────────────────────────────────
|
||||
let graphChart = null;
|
||||
let graphResizeTimer = null;
|
||||
let graphData = { nodes: [], edges: [] };
|
||||
let pathHighlight = new Set();
|
||||
let breadcrumbTrail = []; // [{id, label, type, nodeData}, ...]
|
||||
let filteredNodeSet = null; // Set of visible node IDs (null = show all)
|
||||
let selectedNode = null; // Currently selected node for detail panel
|
||||
|
||||
// ── 工具函数 ──────────────────────────────────────────────────
|
||||
function domainColor(domain) {
|
||||
const colors = ['#42a5f5','#ab47bc','#26a69a','#ef5350','#ff7043',
|
||||
'#66bb6a','#ffa726','#8d6e63','#5c6bc0','#29b6f6',
|
||||
'#ec407a','#9ccc65','#78909c','#ffca28','#26c6da'];
|
||||
let hash = 0;
|
||||
for (let i = 0; i < (domain||'').length; i++) hash = ((hash<<5)-hash)+domain.charCodeAt(i);
|
||||
return colors[Math.abs(hash) % colors.length];
|
||||
}
|
||||
|
||||
// ── 数据加载 ──────────────────────────────────────────────────
|
||||
async function reloadGraph() {
|
||||
if (!graphChart) return;
|
||||
// 清除两级过滤状态
|
||||
breadcrumbTrail = [];
|
||||
filteredNodeSet = null;
|
||||
pathHighlight = new Set();
|
||||
renderBreadcrumb();
|
||||
|
||||
const hideDarpa = document.getElementById('hideDarpa')?.checked ?? true;
|
||||
const types = [];
|
||||
if (document.getElementById('chkProj')?.checked) types.push('Project');
|
||||
if (document.getElementById('chkTech')?.checked) types.push('Technology');
|
||||
if (document.getElementById('chkInst')?.checked) types.push('Institution');
|
||||
if (!types.length) { graphChart.clear(); return; }
|
||||
|
||||
try {
|
||||
const resp = await fetch(
|
||||
`${API_BASE}/graph?hide_darpa=${hideDarpa}&node_types=${types.join(',')}`
|
||||
);
|
||||
const data = await resp.json();
|
||||
graphData = data;
|
||||
renderGraph(data);
|
||||
renderStatBar(data);
|
||||
} catch (e) {
|
||||
console.error('无法连接后端 API:', e);
|
||||
}
|
||||
}
|
||||
|
||||
// ── 图谱渲染 ──────────────────────────────────────────────────
|
||||
function renderGraph(data) {
|
||||
const ecNodes = data.nodes.map(n => {
|
||||
const hl = pathHighlight.has(n.id);
|
||||
const typeColor = (n.type === 'Project' ? (STATUS_COLOR[n.status] || '#42a5f5') : TYPE_COLOR[n.type]);
|
||||
const color = hl ? '#ffd600' : (typeColor || '#42a5f5');
|
||||
const symbol = TYPE_SYMBOL[n.type] || 'circle';
|
||||
const size = n.size ? n.size * 1.5 : 28;
|
||||
|
||||
return {
|
||||
id: n.id, name: n.label, _raw: n,
|
||||
symbol, symbolSize: hl ? size * 1.8 : size,
|
||||
itemStyle: {
|
||||
color,
|
||||
opacity: pathHighlight.size > 0 && !hl ? 0.18 : 1,
|
||||
borderColor: hl ? '#fff' : 'transparent',
|
||||
borderWidth: hl ? 3 : 0,
|
||||
shadowBlur: hl ? 18 : 0,
|
||||
shadowColor: hl ? '#ffd60088' : 'transparent'
|
||||
},
|
||||
emphasis: { scale: true, itemStyle: { borderColor: '#4fc3f7', borderWidth: 2 } },
|
||||
label: {
|
||||
show: true,
|
||||
position: 'bottom',
|
||||
color: hl ? '#f59e0b' : '#000000',
|
||||
fontSize: hl ? 12 : 10,
|
||||
fontWeight: hl ? 'bold' : 'normal',
|
||||
distance: 4
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
const ecEdges = data.edges.map(e => {
|
||||
const rt = e.relationType || e.relType || '';
|
||||
const hl = pathHighlight.has(e.source) && pathHighlight.has(e.target);
|
||||
const col = REL_COLOR[rt] || REL_COLOR[e.relType] || '#475569';
|
||||
return {
|
||||
source: e.source, target: e.target,
|
||||
lineStyle: {
|
||||
color: hl ? '#ffd600' : col,
|
||||
width: hl ? 4 : 1,
|
||||
opacity: pathHighlight.size > 0 ? (hl ? 1 : 0.08) : 0.55,
|
||||
curveness: 0.08,
|
||||
type: e.relType === 'USES_TECHNOLOGY' ? 'dashed' : 'solid',
|
||||
shadowBlur: hl ? 8 : 0,
|
||||
shadowColor: hl ? '#ffd60066' : 'transparent'
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
graphChart.setOption({
|
||||
backgroundColor: 'transparent',
|
||||
tooltip: {
|
||||
trigger: 'item',
|
||||
backgroundColor: 'rgba(255,255,255,0.96)',
|
||||
borderColor: 'rgba(59,130,246,0.25)',
|
||||
textStyle: { color: '#1e293b', fontSize: 11 },
|
||||
formatter: p => {
|
||||
if (p.dataType !== 'node') return '';
|
||||
const n = p.data._raw; if (!n) return p.data.name;
|
||||
if (n.type === 'Project') {
|
||||
const kws = n.keywords || '';
|
||||
return `<div style="font-weight:bold;font-size:13px;margin-bottom:2px">${n.nameCn||n.label}</div>` +
|
||||
(n.nameEn ? `<div style="color:#475569;font-size:10px">${n.nameEn}</div>` : '') +
|
||||
`<div style="margin-top:4px;font-size:10px">领域:${n.domain||'-'} 状态:<span style="color:${STATUS_COLOR[n.status]||'#aaa'}">${n.status||'-'}</span></div>` +
|
||||
`<div style="font-size:10px">预算:${n.budget||'-'} ${n.startYear||'?'}—${n.endYear||'?'}</div>` +
|
||||
(kws ? `<div style="margin-top:2px;font-size:9px;color:#94a3b8">关键词:${kws}</div>` : '') +
|
||||
`<div style="margin-top:3px;font-size:9px;color:#3B82F6">点击查看详情 →</div>`;
|
||||
}
|
||||
if (n.type === 'Technology') {
|
||||
const desc = n.description || '';
|
||||
const descShort = desc.length > 100 ? desc.substring(0, 100) + '...' : desc;
|
||||
return `<div style="font-weight:bold">${n.label}</div><div style="font-size:10px;color:#94a3b8">关联项目:${n.projectCount||0} 个</div>` +
|
||||
(descShort ? `<div style="margin-top:3px;font-size:10px;color:#64748b;line-height:1.4;max-width:220px">${descShort}</div>` : '');
|
||||
}
|
||||
if (n.type === 'Institution') {
|
||||
const desc = n.description || '';
|
||||
const descShort = desc.length > 100 ? desc.substring(0, 100) + '...' : desc;
|
||||
return `<div style="font-weight:bold">${n.label}</div><div style="font-size:10px;color:#94a3b8">参与项目:${n.projectCount||0} 个</div>` +
|
||||
(descShort ? `<div style="margin-top:3px;font-size:10px;color:#64748b;line-height:1.4;max-width:220px">${descShort}</div>` : '');
|
||||
}
|
||||
if (n.type === 'Office') {
|
||||
const desc = n.description || '';
|
||||
const descShort = desc.length > 100 ? desc.substring(0, 100) + '...' : desc;
|
||||
const director = n.director || '';
|
||||
return `<div style="font-weight:bold">${n.label}</div><div style="font-size:10px;color:#94a3b8">项目:${n.projectCount||0} 个</div>` +
|
||||
(director ? `<div style="font-size:10px;color:#64748b">主任:${director}</div>` : '') +
|
||||
(descShort ? `<div style="margin-top:2px;font-size:10px;color:#64748b;line-height:1.4;max-width:220px">${descShort}</div>` : '');
|
||||
}
|
||||
return `<div style="font-weight:bold">${n.label}</div>`;
|
||||
}
|
||||
},
|
||||
series: [{
|
||||
type: 'graph', layout: 'force', roam: true, draggable: true,
|
||||
data: ecNodes, edges: ecEdges,
|
||||
force: { repulsion: 500, edgeLength: [90, 300], gravity: 0.06, friction: 0.6 },
|
||||
emphasis: { lineStyle: { opacity: 0.9 } }
|
||||
}]
|
||||
}, true);
|
||||
}
|
||||
|
||||
// ── 统计栏 ────────────────────────────────────────────────────
|
||||
function renderStatBar(data) {
|
||||
const c = t => data.nodes.filter(n => n.type === t).length;
|
||||
const nc = c('Project') + c('Technology') + c('Institution') + c('Manager') + c('Office');
|
||||
const ec = data.edges.length;
|
||||
|
||||
// Legend overlay
|
||||
const gn = document.getElementById('graphNodeCount');
|
||||
const ge = document.getElementById('graphEdgeCount');
|
||||
if (gn) gn.textContent = nc;
|
||||
if (ge) ge.textContent = ec;
|
||||
|
||||
// Right panel global stats
|
||||
const tn = document.getElementById('gspTotalNodes');
|
||||
const te = document.getElementById('gspTotalEdges');
|
||||
const pc = document.getElementById('gspProjCount');
|
||||
const tc = document.getElementById('gspTechInstCount');
|
||||
if (tn) tn.textContent = nc;
|
||||
if (te) te.textContent = ec;
|
||||
if (pc) pc.textContent = c('Project');
|
||||
if (tc) tc.textContent = `${c('Technology')} / ${c('Institution')}`;
|
||||
}
|
||||
|
||||
// ── 节点详情面板 ──────────────────────────────────────────────
|
||||
function renderGraphDetail(n) {
|
||||
if (!n) return;
|
||||
const cat = TYPE_LABEL_CN[n.type] || n.type || '';
|
||||
const color = n.type === 'Project' ? (STATUS_COLOR[n.status] || '#1d4ed8') : (TYPE_COLOR[n.type] || '#fff');
|
||||
const icon = TYPE_ICON[n.type] || 'info';
|
||||
|
||||
const wrap = document.getElementById('gsp-icon-wrap');
|
||||
if (wrap) { wrap.style.background = `${color}22`; wrap.style.borderColor = `${color}55`; }
|
||||
|
||||
const iconEl = document.getElementById('gsp-icon');
|
||||
if (iconEl) { iconEl.setAttribute('data-lucide', icon); iconEl.style.color = color; }
|
||||
|
||||
const catEl = document.getElementById('gsp-cat');
|
||||
if (catEl) { catEl.textContent = cat; catEl.style.color = color; catEl.style.background = `${color}18`; }
|
||||
|
||||
const titleEl = document.getElementById('gsp-title');
|
||||
if (titleEl) titleEl.textContent = n.nameCn || n.label || '';
|
||||
|
||||
const orgEl = document.getElementById('gsp-org');
|
||||
if (orgEl) {
|
||||
if (n.type === 'Project') orgEl.textContent = n.nameEn || '';
|
||||
else if (n.type === 'Technology') orgEl.textContent = `关联项目: ${n.projectCount || 0} 个`;
|
||||
else if (n.type === 'Institution') orgEl.textContent = `参与项目: ${n.projectCount || 0} 个`;
|
||||
else orgEl.textContent = '';
|
||||
}
|
||||
|
||||
const centEl = document.getElementById('gsp-centrality');
|
||||
if (centEl) {
|
||||
centEl.textContent = n.size ? n.size.toFixed(1) : '--';
|
||||
centEl.style.color = color;
|
||||
}
|
||||
|
||||
const degEl = document.getElementById('gsp-degree');
|
||||
if (degEl) degEl.textContent = n.projectCount || '--';
|
||||
|
||||
const summaryEl = document.getElementById('gsp-summary');
|
||||
if (summaryEl) {
|
||||
if (n.type === 'Project') {
|
||||
const desc = n.description || '';
|
||||
const descShort = desc.length > 150 ? desc.substring(0, 150) + '...' : desc;
|
||||
summaryEl.innerHTML = (descShort ? `<span class="text-xs text-slate-500">${descShort}</span><br>` : '') +
|
||||
`<span class="text-[10px] text-slate-400">领域:${n.domain||'-'} | 状态:${n.status||'-'} | 周期:${n.startYear||'?'}—${n.endYear||'?'}</span>`;
|
||||
} else {
|
||||
summaryEl.textContent = `该${cat}节点在图谱中共关联 ${n.projectCount||0} 个项目`;
|
||||
}
|
||||
}
|
||||
|
||||
// Detail page link for Projects
|
||||
const detailBtn = document.getElementById('gsp-detail-btn');
|
||||
if (detailBtn) {
|
||||
if (n.type === 'Project') {
|
||||
detailBtn.classList.remove('hidden');
|
||||
detailBtn.onclick = () => navigateToDetail(n.id);
|
||||
} else {
|
||||
detailBtn.classList.add('hidden');
|
||||
}
|
||||
}
|
||||
|
||||
lucide.createIcons();
|
||||
}
|
||||
|
||||
// ══════════════════════════════════════════════════════════════
|
||||
// 两级关系过滤 + 面包屑导航
|
||||
// ══════════════════════════════════════════════════════════════
|
||||
|
||||
function computeTwoHopNeighbors(startNodeId) {
|
||||
const adj = new Map();
|
||||
for (const e of graphData.edges) {
|
||||
if (!adj.has(e.source)) adj.set(e.source, new Set());
|
||||
if (!adj.has(e.target)) adj.set(e.target, new Set());
|
||||
adj.get(e.source).add(e.target);
|
||||
adj.get(e.target).add(e.source);
|
||||
}
|
||||
|
||||
const visited = new Set([startNodeId]);
|
||||
const queue = [{ nodeId: startNodeId, depth: 0 }];
|
||||
while (queue.length) {
|
||||
const { nodeId, depth } = queue.shift();
|
||||
if (depth >= 2) continue;
|
||||
const neighbors = adj.get(nodeId);
|
||||
if (!neighbors) continue;
|
||||
for (const nb of neighbors) {
|
||||
if (!visited.has(nb)) {
|
||||
visited.add(nb);
|
||||
queue.push({ nodeId: nb, depth: depth + 1 });
|
||||
}
|
||||
}
|
||||
}
|
||||
return visited;
|
||||
}
|
||||
|
||||
function renderFilteredGraph() {
|
||||
if (filteredNodeSet === null) {
|
||||
renderGraph(graphData);
|
||||
return;
|
||||
}
|
||||
const filtered = {
|
||||
nodes: graphData.nodes.filter(n => filteredNodeSet.has(n.id)),
|
||||
edges: graphData.edges.filter(e => filteredNodeSet.has(e.source) && filteredNodeSet.has(e.target))
|
||||
};
|
||||
renderGraph(filtered);
|
||||
}
|
||||
|
||||
function renderBreadcrumb() {
|
||||
const bar = document.getElementById('graphBreadcrumb');
|
||||
const path = document.getElementById('breadcrumbPath');
|
||||
if (!bar || !path) return;
|
||||
|
||||
if (breadcrumbTrail.length === 0) {
|
||||
bar.style.display = 'none';
|
||||
return;
|
||||
}
|
||||
|
||||
bar.style.display = 'flex';
|
||||
|
||||
// "全部" 起始段
|
||||
let html = `<button onclick="breadcrumbReset()" class="text-blue-500 hover:text-blue-600 font-medium transition-colors px-1" title="显示全图">全部</button>`;
|
||||
|
||||
for (let i = 0; i < breadcrumbTrail.length; i++) {
|
||||
const item = breadcrumbTrail[i];
|
||||
const isLast = i === breadcrumbTrail.length - 1;
|
||||
const color = TYPE_COLOR[item.type] || '#94a3b8';
|
||||
html += `<span class="text-slate-600">/</span>`;
|
||||
if (isLast) {
|
||||
html += `<span class="font-bold text-slate-800 px-1" style="font-size:11px">${item.label}</span>`;
|
||||
} else {
|
||||
html += `<button onclick="breadcrumbGoTo(${i})" class="text-slate-500 hover:text-slate-800 transition-colors px-1" style="font-size:10px;max-width:120px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap" title="${item.label}">${item.label}</button>`;
|
||||
}
|
||||
}
|
||||
|
||||
path.innerHTML = html;
|
||||
setTimeout(() => lucide.createIcons(), 50);
|
||||
}
|
||||
|
||||
function focusNode(nodeData) {
|
||||
const nodeId = nodeData.id;
|
||||
const nodeLabel = nodeData.nameCn || nodeData.label || nodeId;
|
||||
const shortLabel = nodeLabel.length > 10 ? nodeLabel.slice(0, 10) + '…' : nodeLabel;
|
||||
|
||||
// 追加到面包屑
|
||||
breadcrumbTrail.push({ id: nodeId, label: shortLabel, type: nodeData.type, nodeData });
|
||||
|
||||
// 计算 2-hop 邻居
|
||||
filteredNodeSet = computeTwoHopNeighbors(nodeId);
|
||||
|
||||
renderFilteredGraph();
|
||||
renderBreadcrumb();
|
||||
}
|
||||
|
||||
function breadcrumbBack() {
|
||||
breadcrumbTrail.pop();
|
||||
|
||||
if (breadcrumbTrail.length === 0) {
|
||||
// 恢复全量视图
|
||||
filteredNodeSet = null;
|
||||
renderGraph(graphData);
|
||||
renderBreadcrumb();
|
||||
// 重置路径高亮
|
||||
pathHighlight = new Set();
|
||||
return;
|
||||
}
|
||||
|
||||
// 对上一个节点重新过滤
|
||||
const prev = breadcrumbTrail[breadcrumbTrail.length - 1];
|
||||
filteredNodeSet = computeTwoHopNeighbors(prev.id);
|
||||
renderFilteredGraph();
|
||||
renderBreadcrumb();
|
||||
}
|
||||
|
||||
function breadcrumbReset() {
|
||||
breadcrumbTrail = [];
|
||||
filteredNodeSet = null;
|
||||
pathHighlight = new Set();
|
||||
renderGraph(graphData);
|
||||
renderBreadcrumb();
|
||||
}
|
||||
|
||||
function focusSelectedNode() {
|
||||
if (!selectedNode) return;
|
||||
focusNode(selectedNode);
|
||||
renderGraphDetail(selectedNode);
|
||||
}
|
||||
|
||||
function breadcrumbGoTo(index) {
|
||||
// 裁剪到指定位置
|
||||
breadcrumbTrail = breadcrumbTrail.slice(0, index + 1);
|
||||
const target = breadcrumbTrail[index];
|
||||
filteredNodeSet = computeTwoHopNeighbors(target.id);
|
||||
renderFilteredGraph();
|
||||
renderBreadcrumb();
|
||||
}
|
||||
|
||||
// ── 初始化 ────────────────────────────────────────────────────
|
||||
function initGraph() {
|
||||
const dom = document.getElementById('mainGraph');
|
||||
if (!dom) return;
|
||||
if (graphChart) { graphChart.dispose(); graphChart = null; }
|
||||
graphChart = echarts.init(dom);
|
||||
pathHighlight = new Set();
|
||||
|
||||
// 节点单击 → 右侧详情面板(不改变图谱过滤状态)
|
||||
graphChart.on('click', p => {
|
||||
if (p.dataType === 'node' && p.data._raw) {
|
||||
selectedNode = p.data._raw;
|
||||
// 确保右侧面板可见
|
||||
const panel = document.getElementById('graphSidePanel');
|
||||
if (panel && panel.style.width === '0px') {
|
||||
panel.style.width = '380px';
|
||||
panel.style.overflow = '';
|
||||
setTimeout(() => { if (graphChart) graphChart.resize(); }, 520);
|
||||
}
|
||||
renderGraphDetail(selectedNode);
|
||||
// 更新底部按钮跳转目标
|
||||
const archiveBtn = document.getElementById('gsp-archive-btn');
|
||||
if (archiveBtn && selectedNode.type === 'Project') {
|
||||
archiveBtn.onclick = () => { if (selectedNode) navigateToDetail(selectedNode.id); };
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 节点双击 → 两级关系过滤 + 面包屑
|
||||
graphChart.on('dblclick', p => {
|
||||
if (p.dataType === 'node' && p.data._raw) {
|
||||
focusNode(p.data._raw);
|
||||
}
|
||||
});
|
||||
|
||||
reloadGraph();
|
||||
initNodeList();
|
||||
}
|
||||
|
||||
// ── 视图操作 ──────────────────────────────────────────────────
|
||||
function resetGraphView() {
|
||||
if (graphChart) graphChart.dispatchAction({ type: 'restore' });
|
||||
}
|
||||
|
||||
function changeLayout(val) {
|
||||
if (!graphChart) return;
|
||||
graphChart.setOption({ series: [{ layout: val }] });
|
||||
}
|
||||
|
||||
function switchGraphTab(tab) {
|
||||
['proj','tech','org'].forEach(t => {
|
||||
const btn = document.getElementById(`gtab-${t}`);
|
||||
if (!btn) return;
|
||||
if (t === tab) { btn.classList.add('bg-blue-600','text-white'); btn.classList.remove('text-slate-600'); }
|
||||
else { btn.classList.remove('bg-blue-600','text-white'); btn.classList.add('text-slate-600'); }
|
||||
});
|
||||
}
|
||||
|
||||
function toggleGraphSidebar() {
|
||||
const panel = document.getElementById('graphSidePanel');
|
||||
if (!panel) return;
|
||||
const isOpen = panel.style.width !== '0px';
|
||||
panel.style.width = isOpen ? '0px' : '380px';
|
||||
panel.style.overflow = isOpen ? 'hidden' : '';
|
||||
setTimeout(() => { if (graphChart) graphChart.resize(); }, 520);
|
||||
}
|
||||
|
||||
// ── 最短路径查询 ──────────────────────────────────────────────
|
||||
let nodeListCache = [];
|
||||
|
||||
async function initNodeList() {
|
||||
try {
|
||||
const resp = await fetch(`${API_BASE}/node_list`);
|
||||
nodeListCache = await resp.json();
|
||||
populateSelect(document.getElementById('pathFrom'), nodeListCache);
|
||||
populateSelect(document.getElementById('pathTo'), nodeListCache);
|
||||
} catch (e) { console.error('无法加载节点列表:', e); }
|
||||
}
|
||||
|
||||
function populateSelect(sel, nodes) {
|
||||
if (!sel) return;
|
||||
sel.innerHTML = '<option value="">── 选择节点 ──</option>';
|
||||
let currentType = '';
|
||||
nodes.forEach(n => {
|
||||
if (n.type !== currentType) {
|
||||
currentType = n.type;
|
||||
const opt = document.createElement('option');
|
||||
opt.disabled = true;
|
||||
opt.textContent = `── ${TYPE_LABEL_CN[currentType] || currentType} ──`;
|
||||
opt.style.color = '#64748b';
|
||||
sel.appendChild(opt);
|
||||
}
|
||||
const opt = document.createElement('option');
|
||||
opt.value = n.id;
|
||||
opt.textContent = n.label;
|
||||
sel.appendChild(opt);
|
||||
});
|
||||
}
|
||||
|
||||
async function queryPath() {
|
||||
const fromId = document.getElementById('pathFrom')?.value;
|
||||
const toId = document.getElementById('pathTo')?.value;
|
||||
const ignoreDarpa = document.getElementById('pathIgnoreDarpa')?.checked ?? true;
|
||||
const resultBox = document.getElementById('pathResult');
|
||||
|
||||
if (!fromId || !toId) {
|
||||
if (resultBox) resultBox.textContent = '请先选择起点和终点';
|
||||
return;
|
||||
}
|
||||
if (resultBox) resultBox.textContent = '查询中...';
|
||||
|
||||
try {
|
||||
const resp = await fetch(
|
||||
`${API_BASE}/shortest_path?from_id=${encodeURIComponent(fromId)}&to_id=${encodeURIComponent(toId)}&ignore_darpa=${ignoreDarpa}`
|
||||
);
|
||||
const data = await resp.json();
|
||||
|
||||
pathHighlight = new Set();
|
||||
if (data.pathNodes) {
|
||||
data.pathNodes.forEach(n => pathHighlight.add(n.id));
|
||||
const names = data.pathNodes.map(n => n.label).join(' → ');
|
||||
if (resultBox) resultBox.innerHTML = `<span style="color:#52b788">路径长度: ${data.pathLen} 跳</span><br>${names}`;
|
||||
} else if (data.error) {
|
||||
if (resultBox) resultBox.textContent = data.error;
|
||||
} else {
|
||||
if (resultBox) resultBox.textContent = '未找到路径';
|
||||
}
|
||||
renderGraph(graphData);
|
||||
} catch (e) {
|
||||
if (resultBox) resultBox.textContent = '查询失败';
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
function clearPath() {
|
||||
pathHighlight = new Set();
|
||||
const resultBox = document.getElementById('pathResult');
|
||||
if (resultBox) resultBox.textContent = '';
|
||||
renderGraph(graphData);
|
||||
}
|
||||
|
||||
// ── 导出 ──────────────────────────────────────────────────────
|
||||
function exportGraphImage() {
|
||||
if (!graphChart) return;
|
||||
const url = graphChart.getDataURL({ type: 'png', pixelRatio: 2, backgroundColor: '#ffffff' });
|
||||
const a = document.createElement('a'); a.href = url; a.download = 'DARPA知识图谱.png'; a.click();
|
||||
}
|
||||
|
||||
// ── resize ────────────────────────────────────────────────────
|
||||
window.addEventListener('resize', () => {
|
||||
clearTimeout(graphResizeTimer);
|
||||
graphResizeTimer = setTimeout(() => { if (graphChart) graphChart.resize(); }, 200);
|
||||
});
|
||||
|
||||
// ── 挂载到 window ────────────────────────────────────────────
|
||||
window.initGraph = initGraph;
|
||||
window.reloadGraph = reloadGraph;
|
||||
window.resetGraphView = resetGraphView;
|
||||
window.changeLayout = changeLayout;
|
||||
window.switchGraphTab = switchGraphTab;
|
||||
window.toggleGraphSidebar = toggleGraphSidebar;
|
||||
window.queryPath = queryPath;
|
||||
window.clearPath = clearPath;
|
||||
window.exportGraphImage = exportGraphImage;
|
||||
window.breadcrumbBack = breadcrumbBack;
|
||||
window.breadcrumbReset = breadcrumbReset;
|
||||
window.breadcrumbGoTo = breadcrumbGoTo;
|
||||
window.focusSelectedNode = focusSelectedNode;
|
||||
@ -1,323 +0,0 @@
|
||||
<div id="dashboardPage" class="page">
|
||||
<div class="w-full h-screen flex">
|
||||
<!-- Sidebar -->
|
||||
<aside class="w-64 bg-sidebar text-slate-400 flex flex-col">
|
||||
<div class="p-6 flex items-center gap-3">
|
||||
<div class="w-8 h-8 bg-primary rounded flex items-center justify-center text-white">
|
||||
<i data-lucide="shield-half" class="w-5 h-5"></i>
|
||||
</div>
|
||||
<span class="text-white font-bold tracking-tight text-lg">DARPA Intelligence</span>
|
||||
</div>
|
||||
<nav class="flex-1 px-4 py-4 space-y-1 overflow-y-auto custom-scrollbar">
|
||||
<div class="text-[10px] font-bold text-slate-500 uppercase tracking-widest px-2 mb-2">主导航</div>
|
||||
<a data-nav-key="dashboard" onclick="showPage('dashboardPage')" class="flex items-center gap-3 px-3 py-2.5 rounded-lg menu-active text-sm cursor-pointer">
|
||||
<i data-lucide="layout-dashboard" class="w-4 h-4"></i> 工作台
|
||||
</a>
|
||||
<a data-nav-key="search" onclick="showPage('searchPage')" class="flex items-center gap-3 px-3 py-2.5 rounded-lg hover:bg-white/5 hover:text-white transition-all text-sm cursor-pointer">
|
||||
<i data-lucide="search" class="w-4 h-4"></i> 智能检索
|
||||
</a>
|
||||
<a data-nav-key="graph" onclick="showPage('graphPage')" class="flex items-center gap-3 px-3 py-2.5 rounded-lg hover:bg-white/5 hover:text-white transition-all text-sm cursor-pointer">
|
||||
<i data-lucide="share-2" class="w-4 h-4"></i> 可视化分析
|
||||
</a>
|
||||
<a data-nav-key="trend" onclick="showPage('trendPage')" class="flex items-center gap-3 px-3 py-2.5 rounded-lg hover:bg-white/5 hover:text-white transition-all text-sm cursor-pointer">
|
||||
<i data-lucide="trending-up" class="w-4 h-4"></i> 趋势分析
|
||||
</a>
|
||||
<a data-nav-key="wiki" onclick="showPage('wikiPage')" class="flex items-center gap-3 px-3 py-2.5 rounded-lg hover:bg-white/5 hover:text-white transition-all text-sm cursor-pointer">
|
||||
<i data-lucide="book-open" class="w-4 h-4"></i> 情报百科
|
||||
</a>
|
||||
<div class="text-[10px] font-bold text-slate-500 uppercase tracking-widest px-2 mt-6 mb-2">数据管理</div>
|
||||
<a data-nav-key="governance" onclick="showPage('governancePage')" class="flex items-center gap-3 px-3 py-2.5 rounded-lg hover:bg-white/5 hover:text-white transition-all text-sm cursor-pointer">
|
||||
<i data-lucide="database" class="w-4 h-4"></i> 数据治理
|
||||
</a>
|
||||
<div class="text-[10px] font-bold text-slate-500 uppercase tracking-widest px-2 mt-6 mb-2">系统</div>
|
||||
<a onclick="logout()" class="flex items-center gap-3 px-3 py-2.5 rounded-lg hover:bg-white/5 hover:text-white transition-all text-sm cursor-pointer">
|
||||
<i data-lucide="log-out" class="w-4 h-4"></i> 退出登录
|
||||
</a>
|
||||
</nav>
|
||||
<div class="p-4 border-t border-white/5 flex items-center gap-3">
|
||||
<div class="sidebar-user-avatar w-8 h-8 rounded-full bg-slate-700 flex items-center justify-center text-slate-300 shrink-0">
|
||||
<i data-lucide="user" class="w-4 h-4"></i>
|
||||
</div>
|
||||
<div class="flex-1 min-w-0">
|
||||
<p class="sidebar-user-name text-xs font-bold text-white truncate">--</p>
|
||||
<p class="sidebar-user-role text-[10px] text-slate-500 truncate">--</p>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<!-- Main Content -->
|
||||
<main class="flex-1 flex flex-col">
|
||||
<header class="h-16 glass-nav border-b border-gray-200 px-6 flex items-center justify-between">
|
||||
<div>
|
||||
<h1 class="text-lg font-bold text-slate-800">DARPA 情报工作台</h1>
|
||||
<p class="text-[11px] text-slate-400">数据来源: Neo4j 图数据库 · 实时分析</p>
|
||||
</div>
|
||||
<div class="flex items-center gap-4">
|
||||
<button class="relative p-2 hover:bg-slate-100 rounded-lg transition-colors" title="通知">
|
||||
<i data-lucide="bell" class="w-5 h-5 text-slate-500"></i>
|
||||
<span class="absolute top-1.5 right-1.5 w-2 h-2 rounded-full bg-red-500"></span>
|
||||
</button>
|
||||
<div onclick="openProfileModal()" class="header-user-avatar w-8 h-8 rounded-full bg-slate-200 flex items-center justify-center cursor-pointer">
|
||||
<i data-lucide="user" class="w-4 h-4 text-slate-500"></i>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="p-6 space-y-6 overflow-y-auto custom-scrollbar flex-1 bg-slate-50">
|
||||
<!-- 统计卡片 (可点击) -->
|
||||
<div class="grid grid-cols-4 gap-4" id="dashStatCards">
|
||||
<div onclick="dashShowTable('projects')" class="bg-white rounded-xl shadow-sm border border-slate-100 hover:shadow-md hover:border-primary/30 transition-all overflow-hidden cursor-pointer group">
|
||||
<div class="h-[3px] bg-gradient-to-r from-blue-500 to-blue-400"></div>
|
||||
<div class="p-5">
|
||||
<div class="flex justify-between items-start">
|
||||
<span class="text-xs font-medium text-slate-500 group-hover:text-primary transition-colors">项目总数</span>
|
||||
<div class="p-1.5 bg-blue-50 rounded-lg text-primary"><i data-lucide="layers" class="w-4 h-4"></i></div>
|
||||
</div>
|
||||
<div class="mt-3">
|
||||
<span id="dashTotalProj" class="text-3xl font-din font-bold text-slate-900 skeleton-text">--</span>
|
||||
<div class="flex items-center gap-1 mt-1">
|
||||
<span id="dashTotalProjTrend" class="text-[10px] font-medium skeleton-text-sm">--</span>
|
||||
<span class="text-[10px] text-slate-400">环比</span>
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-[10px] text-slate-400 mt-2 opacity-0 group-hover:opacity-100 transition-opacity">点击查看完整列表 →</p>
|
||||
</div>
|
||||
</div>
|
||||
<div onclick="dashShowTable('domains')" class="bg-white rounded-xl shadow-sm border border-slate-100 hover:shadow-md hover:border-purple-500/30 transition-all overflow-hidden cursor-pointer group">
|
||||
<div class="h-[3px] bg-gradient-to-r from-purple-500 to-purple-400"></div>
|
||||
<div class="p-5">
|
||||
<div class="flex justify-between items-start">
|
||||
<span class="text-xs font-medium text-slate-500 group-hover:text-purple-600 transition-colors">技术领域</span>
|
||||
<div class="p-1.5 bg-purple-50 rounded-lg text-purple-500"><i data-lucide="cpu" class="w-4 h-4"></i></div>
|
||||
</div>
|
||||
<div class="mt-3">
|
||||
<span id="dashTechCount" class="text-3xl font-din font-bold text-slate-900 skeleton-text">--</span>
|
||||
<div class="flex items-center gap-1 mt-1">
|
||||
<span id="dashTechCountTrend" class="text-[10px] font-medium skeleton-text-sm">--</span>
|
||||
<span class="text-[10px] text-slate-400">个领域</span>
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-[10px] text-slate-400 mt-2 opacity-0 group-hover:opacity-100 transition-opacity">点击查看领域分布 →</p>
|
||||
</div>
|
||||
</div>
|
||||
<div onclick="dashShowTable('institutions')" class="bg-white rounded-xl shadow-sm border border-slate-100 hover:shadow-md hover:border-amber-500/30 transition-all overflow-hidden cursor-pointer group">
|
||||
<div class="h-[3px] bg-gradient-to-r from-amber-500 to-amber-400"></div>
|
||||
<div class="p-5">
|
||||
<div class="flex justify-between items-start">
|
||||
<span class="text-xs font-medium text-slate-500 group-hover:text-amber-600 transition-colors">合作机构</span>
|
||||
<div class="p-1.5 bg-amber-50 rounded-lg text-amber-500"><i data-lucide="building-2" class="w-4 h-4"></i></div>
|
||||
</div>
|
||||
<div class="mt-3">
|
||||
<span id="dashInstCount" class="text-3xl font-din font-bold text-slate-900 skeleton-text">--</span>
|
||||
<div class="flex items-center gap-1 mt-1">
|
||||
<span id="dashInstCountTrend" class="text-[10px] font-medium skeleton-text-sm">--</span>
|
||||
<span class="text-[10px] text-slate-400">家机构</span>
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-[10px] text-slate-400 mt-2 opacity-0 group-hover:opacity-100 transition-opacity">点击查看机构列表 →</p>
|
||||
</div>
|
||||
</div>
|
||||
<div onclick="dashShowTable('projects')" class="bg-white rounded-xl shadow-sm border border-slate-100 hover:shadow-md hover:border-emerald-500/30 transition-all overflow-hidden cursor-pointer group">
|
||||
<div class="h-[3px] bg-gradient-to-r from-emerald-500 to-emerald-400"></div>
|
||||
<div class="p-5">
|
||||
<div class="flex justify-between items-start">
|
||||
<span class="text-xs font-medium text-slate-500 group-hover:text-emerald-600 transition-colors">预算总额</span>
|
||||
<div class="p-1.5 bg-emerald-50 rounded-lg text-emerald-500"><i data-lucide="dollar-sign" class="w-4 h-4"></i></div>
|
||||
</div>
|
||||
<div class="mt-3">
|
||||
<span id="dashTotalBudget" class="text-3xl font-din font-bold text-slate-900 skeleton-text">--</span>
|
||||
<div class="flex items-center gap-1 mt-1">
|
||||
<span id="dashTotalBudgetTrend" class="text-[10px] font-medium skeleton-text-sm">--</span>
|
||||
<span class="text-[10px] text-slate-400">环比</span>
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-[10px] text-slate-400 mt-2 opacity-0 group-hover:opacity-100 transition-opacity">点击查看项目明细 →</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 数据表格 (默认隐藏) -->
|
||||
<div id="dashDataTable" class="bg-white rounded-xl shadow-sm border border-slate-100 p-6 hidden">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<h3 class="font-bold text-slate-800 flex items-center gap-2" id="dashTableTitle">
|
||||
<div class="w-1 h-5 bg-primary rounded-full"></div>
|
||||
项目列表
|
||||
</h3>
|
||||
<div class="flex items-center gap-2">
|
||||
<button onclick="dashAddRow()" class="flex items-center gap-1 px-3 py-1.5 bg-primary text-white rounded-lg text-xs font-medium hover:bg-blue-600 transition-colors">
|
||||
<i data-lucide="plus" class="w-3.5 h-3.5"></i> 新增
|
||||
</button>
|
||||
<button onclick="document.getElementById('dashDataTable').classList.add('hidden')" class="p-1.5 hover:bg-slate-100 rounded-lg transition-colors">
|
||||
<i data-lucide="x" class="w-4 h-4 text-slate-400"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="overflow-x-auto max-h-96 overflow-y-auto custom-scrollbar">
|
||||
<table class="w-full text-xs">
|
||||
<thead class="sticky top-0 bg-slate-50 z-10">
|
||||
<tr class="text-left text-slate-500">
|
||||
<th class="py-2 px-3 font-medium">项目编号</th>
|
||||
<th class="py-2 px-3 font-medium">项目名称</th>
|
||||
<th class="py-2 px-3 font-medium">领域</th>
|
||||
<th class="py-2 px-3 font-medium">状态</th>
|
||||
<th class="py-2 px-3 font-medium">预算(万美元)</th>
|
||||
<th class="py-2 px-3 font-medium">开始</th>
|
||||
<th class="py-2 px-3 font-medium">结束</th>
|
||||
<th class="py-2 px-3 font-medium text-right">操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="dashTableBody" class="divide-y divide-slate-50">
|
||||
<tr><td colspan="8" class="py-8 text-center text-slate-400">加载中...</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<p id="dashTableCount" class="text-[10px] text-slate-400 mt-2"></p>
|
||||
</div>
|
||||
|
||||
<!-- 主图表行: 年代趋势 + Top5 -->
|
||||
<div class="grid grid-cols-5 gap-6">
|
||||
<div class="col-span-3 bg-white rounded-xl shadow-sm border border-slate-100 p-6">
|
||||
<h3 class="font-bold text-slate-800 mb-3 flex items-center gap-2">
|
||||
<div class="w-1 h-5 bg-primary rounded-full"></div>
|
||||
项目数量年代分布
|
||||
</h3>
|
||||
<div id="dashTimelineChart" style="width:100%;height:300px">
|
||||
<p class="text-sm text-slate-400 text-center pt-28">加载中...</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-span-2 bg-white rounded-xl shadow-sm border border-slate-100 p-6 flex flex-col">
|
||||
<h3 class="font-bold text-slate-800 mb-3 flex items-center gap-2">
|
||||
<div class="w-1 h-5 bg-emerald-500 rounded-full"></div>
|
||||
领域预算 Top5
|
||||
</h3>
|
||||
<div id="dashDomainMiniBars" class="space-y-2.5 flex-1">
|
||||
<p class="text-xs text-slate-400">加载中...</p>
|
||||
</div>
|
||||
<div class="mt-3 pt-4 border-t border-slate-100">
|
||||
<h4 class="text-[10px] font-bold text-slate-400 uppercase tracking-wider mb-2.5">快捷入口</h4>
|
||||
<div class="grid grid-cols-2 gap-2">
|
||||
<button onclick="showPage('graphPage')" class="flex items-center gap-2 p-2 rounded-lg bg-blue-50 hover:bg-blue-100 text-blue-700 text-xs font-medium transition-colors">
|
||||
<i data-lucide="share-2" class="w-3.5 h-3.5"></i> 知识图谱
|
||||
</button>
|
||||
<button onclick="showPage('trendPage')" class="flex items-center gap-2 p-2 rounded-lg bg-purple-50 hover:bg-purple-100 text-purple-700 text-xs font-medium transition-colors">
|
||||
<i data-lucide="trending-up" class="w-3.5 h-3.5"></i> 趋势分析
|
||||
</button>
|
||||
<button onclick="showPage('wikiPage')" class="flex items-center gap-2 p-2 rounded-lg bg-amber-50 hover:bg-amber-100 text-amber-700 text-xs font-medium transition-colors">
|
||||
<i data-lucide="book-open" class="w-3.5 h-3.5"></i> 情报百科
|
||||
</button>
|
||||
<button onclick="showPage('searchPage')" class="flex items-center gap-2 p-2 rounded-lg bg-emerald-50 hover:bg-emerald-100 text-emerald-700 text-xs font-medium transition-colors">
|
||||
<i data-lucide="search" class="w-3.5 h-3.5"></i> 智能检索
|
||||
</button>
|
||||
<button onclick="showPage('governancePage')" class="flex items-center gap-2 p-2 rounded-lg bg-slate-100 hover:bg-slate-200 text-slate-700 text-xs font-medium transition-colors">
|
||||
<i data-lucide="database" class="w-3.5 h-3.5"></i> 数据治理
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- AI 情报研判摘要 -->
|
||||
<div id="dashAiSummaryCard" class="bg-white rounded-xl shadow-sm border p-6" style="border-left:4px solid #eab308;background:#fefce8">
|
||||
<div class="flex items-start gap-3">
|
||||
<div class="w-9 h-9 bg-white rounded-full flex items-center justify-center shadow-sm border border-yellow-200 shrink-0 mt-0.5">
|
||||
<i data-lucide="sparkles" class="w-4 h-4 text-yellow-500"></i>
|
||||
</div>
|
||||
<div class="flex-1 min-w-0">
|
||||
<div class="flex items-center justify-between mb-2">
|
||||
<h4 class="font-bold text-slate-800 text-sm">AI 情报研判摘要</h4>
|
||||
<span class="text-[10px] text-slate-400">基于图谱实时生成</span>
|
||||
</div>
|
||||
<div id="dashAiSummary" class="text-sm text-slate-600 leading-relaxed line-clamp-4">
|
||||
<p class="text-slate-400 italic">数据加载中...</p>
|
||||
</div>
|
||||
<a onclick="showPage('trendPage')" class="inline-block mt-2 text-xs text-primary hover:text-blue-700 font-medium cursor-pointer">
|
||||
查看完整分析 <i data-lucide="arrow-right" class="w-3 h-3 inline"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="dashAiSummaryError" class="bg-white rounded-xl shadow-sm border border-slate-100 p-6 hidden">
|
||||
<div class="flex items-start gap-3">
|
||||
<div class="w-9 h-9 bg-red-50 rounded-full flex items-center justify-center shrink-0 mt-0.5">
|
||||
<i data-lucide="alert-circle" class="w-4 h-4 text-red-400"></i>
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="font-bold text-slate-800 text-sm">数据加载失败</h4>
|
||||
<p class="text-sm text-slate-500 mt-1">AI 研判数据获取失败,请检查后端服务。</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 重点预算项目 -->
|
||||
<div class="bg-white rounded-xl shadow-sm border border-slate-100 p-6">
|
||||
<h3 class="font-bold text-slate-800 mb-3 flex items-center gap-2">
|
||||
<div class="w-1 h-5 bg-orange-500 rounded-full"></div>
|
||||
重大预算投入项目 (Top 5)
|
||||
</h3>
|
||||
<div id="dashKeyProjects" class="divide-y divide-slate-50">
|
||||
<p class="text-sm text-slate-400">加载中...</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<!-- 编辑/新增弹窗 -->
|
||||
<div id="dashEditModal" class="fixed inset-0 bg-black/40 z-50 hidden flex items-center justify-center" onclick="if(event.target===this)this.classList.add('hidden')">
|
||||
<div class="bg-white rounded-xl shadow-2xl p-6 w-full max-w-lg mx-4 max-h-[85vh] overflow-y-auto">
|
||||
<h3 class="font-bold text-slate-800 text-lg mb-4" id="dashModalTitle">编辑项目</h3>
|
||||
<div class="space-y-3">
|
||||
<div>
|
||||
<label class="text-[11px] text-slate-500 font-medium">项目编号</label>
|
||||
<input id="dashF_projectId" class="w-full border border-slate-200 rounded-lg px-3 py-2 text-sm focus:outline-none focus:border-primary" placeholder="DARPA-2026-XX-001">
|
||||
</div>
|
||||
<div class="grid grid-cols-2 gap-3">
|
||||
<div>
|
||||
<label class="text-[11px] text-slate-500 font-medium">中文名称</label>
|
||||
<input id="dashF_nameCn" class="w-full border border-slate-200 rounded-lg px-3 py-2 text-sm focus:outline-none focus:border-primary">
|
||||
</div>
|
||||
<div>
|
||||
<label class="text-[11px] text-slate-500 font-medium">英文名称</label>
|
||||
<input id="dashF_nameEn" class="w-full border border-slate-200 rounded-lg px-3 py-2 text-sm focus:outline-none focus:border-primary">
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 gap-3">
|
||||
<div>
|
||||
<label class="text-[11px] text-slate-500 font-medium">技术领域</label>
|
||||
<input id="dashF_domain" class="w-full border border-slate-200 rounded-lg px-3 py-2 text-sm focus:outline-none focus:border-primary">
|
||||
</div>
|
||||
<div>
|
||||
<label class="text-[11px] text-slate-500 font-medium">状态</label>
|
||||
<select id="dashF_status" class="w-full border border-slate-200 rounded-lg px-3 py-2 text-sm focus:outline-none focus:border-primary">
|
||||
<option value="研发中">研发中</option>
|
||||
<option value="结题">结题</option>
|
||||
<option value="已完成">已完成</option>
|
||||
<option value="进行中">进行中</option>
|
||||
<option value="立项中">立项中</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid grid-cols-3 gap-3">
|
||||
<div>
|
||||
<label class="text-[11px] text-slate-500 font-medium">预算(万美元)</label>
|
||||
<input id="dashF_budgetVal" type="number" class="w-full border border-slate-200 rounded-lg px-3 py-2 text-sm focus:outline-none focus:border-primary">
|
||||
</div>
|
||||
<div>
|
||||
<label class="text-[11px] text-slate-500 font-medium">开始年份</label>
|
||||
<input id="dashF_startYear" type="number" class="w-full border border-slate-200 rounded-lg px-3 py-2 text-sm focus:outline-none focus:border-primary">
|
||||
</div>
|
||||
<div>
|
||||
<label class="text-[11px] text-slate-500 font-medium">结束年份</label>
|
||||
<input id="dashF_endYear" type="number" class="w-full border border-slate-200 rounded-lg px-3 py-2 text-sm focus:outline-none focus:border-primary">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex justify-end gap-2 mt-5">
|
||||
<button onclick="document.getElementById('dashEditModal').classList.add('hidden')" class="px-4 py-2 text-sm text-slate-600 hover:bg-slate-100 rounded-lg transition-colors">取消</button>
|
||||
<button onclick="dashSaveRow()" class="px-4 py-2 text-sm bg-primary text-white rounded-lg hover:bg-blue-600 transition-colors font-medium">保存</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -1,336 +0,0 @@
|
||||
<div id="governancePage" class="page">
|
||||
<div class="w-full h-screen flex">
|
||||
|
||||
<!-- Sidebar -->
|
||||
<aside class="w-64 bg-sidebar text-slate-400 flex flex-col shrink-0">
|
||||
<div class="p-6 flex items-center gap-3">
|
||||
<div class="w-8 h-8 bg-primary rounded flex items-center justify-center text-white">
|
||||
<i data-lucide="shield-half" class="w-5 h-5"></i>
|
||||
</div>
|
||||
<span class="text-white font-bold tracking-tight text-lg">DARPA Intelligence</span>
|
||||
</div>
|
||||
<nav class="flex-1 px-4 py-4 space-y-1 overflow-y-auto custom-scrollbar">
|
||||
<div class="text-[10px] font-bold text-slate-500 uppercase tracking-widest px-2 mb-2">主导航</div>
|
||||
<a data-nav-key="dashboard" onclick="showPage('dashboardPage')" class="flex items-center gap-3 px-3 py-2.5 rounded-lg hover:bg-white/5 hover:text-white transition-all text-sm cursor-pointer">
|
||||
<i data-lucide="layout-dashboard" class="w-4 h-4"></i> 工作台
|
||||
</a>
|
||||
<a data-nav-key="search" onclick="showPage('searchPage')" class="flex items-center gap-3 px-3 py-2.5 rounded-lg hover:bg-white/5 hover:text-white transition-all text-sm cursor-pointer">
|
||||
<i data-lucide="search" class="w-4 h-4"></i> 智能检索
|
||||
</a>
|
||||
<a data-nav-key="graph" onclick="showPage('graphPage')" class="flex items-center gap-3 px-3 py-2.5 rounded-lg hover:bg-white/5 hover:text-white transition-all text-sm cursor-pointer">
|
||||
<i data-lucide="share-2" class="w-4 h-4"></i> 可视化分析
|
||||
</a>
|
||||
<a data-nav-key="trend" onclick="showPage('trendPage')" class="flex items-center gap-3 px-3 py-2.5 rounded-lg hover:bg-white/5 hover:text-white transition-all text-sm cursor-pointer">
|
||||
<i data-lucide="trending-up" class="w-4 h-4"></i> 趋势分析
|
||||
</a>
|
||||
<a data-nav-key="wiki" onclick="showPage('wikiPage')" class="flex items-center gap-3 px-3 py-2.5 rounded-lg hover:bg-white/5 hover:text-white transition-all text-sm cursor-pointer">
|
||||
<i data-lucide="book-open" class="w-4 h-4"></i> 情报百科
|
||||
</a>
|
||||
<div class="text-[10px] font-bold text-slate-500 uppercase tracking-widest px-2 mt-6 mb-2">数据管理</div>
|
||||
<a data-nav-key="governance" onclick="showPage('governancePage')" class="flex items-center gap-3 px-3 py-2.5 rounded-lg menu-active text-sm cursor-pointer">
|
||||
<i data-lucide="database" class="w-4 h-4"></i> 数据治理
|
||||
</a>
|
||||
<div class="text-[10px] font-bold text-slate-500 uppercase tracking-widest px-2 mt-6 mb-2">系统</div>
|
||||
<a onclick="logout()" class="flex items-center gap-3 px-3 py-2.5 rounded-lg hover:bg-white/5 hover:text-white transition-all text-sm cursor-pointer">
|
||||
<i data-lucide="log-out" class="w-4 h-4"></i> 退出登录
|
||||
</a>
|
||||
</nav>
|
||||
<div class="p-4 border-t border-white/5 flex items-center gap-3">
|
||||
<div class="sidebar-user-avatar w-8 h-8 rounded-full bg-slate-700 flex items-center justify-center text-slate-300 shrink-0">
|
||||
<i data-lucide="user" class="w-4 h-4"></i>
|
||||
</div>
|
||||
<div class="flex-1 min-w-0">
|
||||
<p class="sidebar-user-name text-xs font-bold text-white truncate">--</p>
|
||||
<p class="sidebar-user-role text-[10px] text-slate-500 truncate">--</p>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<!-- Right content -->
|
||||
<div class="flex-1 flex flex-col bg-slate-100 overflow-hidden">
|
||||
<!-- Action Bar (原来的顶部 nav 精简为操作栏,去掉 logo 和返回按钮) -->
|
||||
<div class="h-14 px-6 border-b border-slate-200 bg-white flex items-center justify-between shrink-0">
|
||||
<div class="flex items-center gap-3">
|
||||
<span class="font-bold text-slate-900 text-sm">Governance <span class="text-primary">Console</span></span>
|
||||
<div class="h-4 w-px bg-slate-200"></div>
|
||||
<div class="flex gap-5 text-sm font-medium text-slate-500">
|
||||
<a id="gtab-governance" onclick="switchGovTab('governance')" class="text-primary border-b-2 border-primary pb-[13px] pt-[13px] cursor-pointer">数据治理</a>
|
||||
<a id="gtab-crawler" onclick="switchGovTab('crawler')" class="hover:text-slate-900 transition-colors py-[13px] cursor-pointer">采集任务</a>
|
||||
<a id="gtab-backup" onclick="switchGovTab('backup')" class="hover:text-slate-900 transition-colors py-[13px] cursor-pointer">数据备份</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="relative">
|
||||
<i data-lucide="search" class="w-4 h-4 absolute left-3 top-1/2 -translate-y-1/2 text-slate-400"></i>
|
||||
<input type="text" placeholder="搜索项目或机构..." class="pl-10 pr-4 py-1.5 bg-slate-100 border-transparent focus:bg-white focus:border-primary rounded-lg text-sm w-52 outline-none transition-all border">
|
||||
</div>
|
||||
<div onclick="openProfileModal()" class="header-user-avatar w-8 h-8 rounded-full bg-slate-200 flex items-center justify-center cursor-pointer">
|
||||
<i data-lucide="user" class="w-4 h-4 text-slate-600"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Action Bar -->
|
||||
<div class="px-6 py-3 border-b border-slate-200 bg-white flex items-center justify-between shrink-0">
|
||||
<div class="flex items-center gap-3">
|
||||
<button onclick="toggleGovModal()" class="bg-primary hover:bg-blue-600 text-white px-4 py-2 rounded-lg text-sm font-semibold flex items-center gap-2 shadow-sm transition-all">
|
||||
<i data-lucide="upload-cloud" class="w-4 h-4"></i> 数据导入
|
||||
</button>
|
||||
<button class="bg-white border border-slate-200 px-4 py-2 rounded-lg text-sm font-medium flex items-center gap-2 hover:bg-slate-50">
|
||||
批量操作 <i data-lucide="chevron-down" class="w-4 h-4"></i>
|
||||
</button>
|
||||
<button class="p-2 text-slate-500 hover:bg-slate-200 rounded-lg transition-colors"><i data-lucide="rotate-cw" class="w-4 h-4"></i></button>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="text-xs text-slate-400 font-medium mr-2">筛选状态:</span>
|
||||
<select class="text-xs border-slate-200 rounded-md py-1.5 px-2 outline-none border">
|
||||
<option>全部状态</option><option>已入库</option><option>清洗中</option><option>校验失败</option><option>待采集</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Content: Table + Sidebar -->
|
||||
<div id="gpanel-governance" class="flex flex-1 overflow-hidden relative">
|
||||
<!-- Floating Batch Bar -->
|
||||
<div id="govBatchBar" class="hidden absolute top-4 left-1/2 -translate-x-1/2 bg-slate-900 text-white px-6 py-3 rounded-full shadow-2xl flex items-center gap-6 z-30">
|
||||
<span class="text-sm font-medium">已选中 <span id="govSelectedCount" class="text-blue-400">0</span> 项数据</span>
|
||||
<div class="h-4 w-px bg-slate-700"></div>
|
||||
<div class="flex items-center gap-4">
|
||||
<button class="text-sm hover:text-blue-400 flex items-center gap-1"><i data-lucide="download" class="w-4 h-4"></i> 导出</button>
|
||||
<button class="text-sm hover:text-blue-400 flex items-center gap-1"><i data-lucide="tag" class="w-4 h-4"></i> 标记</button>
|
||||
<button class="text-sm hover:text-red-400 flex items-center gap-1"><i data-lucide="trash-2" class="w-4 h-4"></i> 删除</button>
|
||||
</div>
|
||||
<button onclick="clearGovSelection()" class="ml-4 text-slate-400 hover:text-white"><i data-lucide="x" class="w-4 h-4"></i></button>
|
||||
</div>
|
||||
|
||||
<!-- Table -->
|
||||
<div class="flex-1 overflow-auto p-6">
|
||||
<table class="w-full text-left border-collapse text-sm">
|
||||
<thead>
|
||||
<tr class="text-slate-400 text-xs font-bold uppercase tracking-wider border-b border-slate-200">
|
||||
<th class="pb-3 w-10"><input type="checkbox" id="govSelectAll" class="rounded border-slate-300"></th>
|
||||
<th class="pb-3">项目编号</th>
|
||||
<th class="pb-3">项目名称</th>
|
||||
<th class="pb-3">承担机构</th>
|
||||
<th class="pb-3">采集时间</th>
|
||||
<th class="pb-3">清洗状态</th>
|
||||
<th class="pb-3 text-right">操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-slate-100">
|
||||
<tr class="hover:bg-white transition-colors group">
|
||||
<td class="py-3"><input type="checkbox" class="gov-row-cb rounded border-slate-300"></td>
|
||||
<td class="py-3 font-mono text-xs text-slate-500">D2024-QX-089</td>
|
||||
<td class="py-3 font-semibold text-slate-800 cursor-pointer hover:text-primary" onclick="showPage('detailPage')">下一代空战系统算法验证</td>
|
||||
<td class="py-3 text-slate-600">洛克希德·马丁</td>
|
||||
<td class="py-3 text-slate-500 text-xs">2024-10-24 09:15</td>
|
||||
<td class="py-3"><span class="inline-flex items-center gap-1 px-2 py-0.5 rounded text-xs font-semibold bg-emerald-50 text-emerald-700 border border-emerald-100"><i data-lucide="check-circle-2" class="w-3 h-3"></i> 已入库</span></td>
|
||||
<td class="py-3 text-right"><button class="text-primary hover:underline font-medium text-xs" onclick="showPage('detailPage')">详情</button></td>
|
||||
</tr>
|
||||
<tr class="hover:bg-white transition-colors">
|
||||
<td class="py-3"><input type="checkbox" class="gov-row-cb rounded border-slate-300"></td>
|
||||
<td class="py-3 font-mono text-xs text-slate-500">D2024-QX-102</td>
|
||||
<td class="py-3 font-semibold text-slate-800">量子保密通信协议分析</td>
|
||||
<td class="py-3 text-slate-600">哈佛大学研究院</td>
|
||||
<td class="py-3 text-slate-500 text-xs">2024-10-24 10:30</td>
|
||||
<td class="py-3"><span class="inline-flex items-center gap-1 px-2 py-0.5 rounded text-xs font-semibold bg-amber-50 text-amber-700 border border-amber-100"><i data-lucide="loader-2" class="w-3 h-3 animate-spin"></i> 清洗中</span></td>
|
||||
<td class="py-3 text-right"><button class="text-slate-400 cursor-not-allowed text-xs">停止</button></td>
|
||||
</tr>
|
||||
<tr class="hover:bg-white transition-colors">
|
||||
<td class="py-3"><input type="checkbox" class="gov-row-cb rounded border-slate-300"></td>
|
||||
<td class="py-3 font-mono text-xs text-slate-500">D2024-QX-115</td>
|
||||
<td class="py-3 font-semibold text-slate-800">深海自主潜航器感知集群</td>
|
||||
<td class="py-3 text-slate-600">波音防御系统</td>
|
||||
<td class="py-3 text-slate-500 text-xs">2024-10-23 16:45</td>
|
||||
<td class="py-3"><span class="inline-flex items-center gap-1 px-2 py-0.5 rounded text-xs font-semibold bg-rose-50 text-rose-700 border border-rose-100"><i data-lucide="alert-circle" class="w-3 h-3"></i> 校验失败</span></td>
|
||||
<td class="py-3 text-right"><button class="text-primary hover:underline font-medium text-xs">重试</button></td>
|
||||
</tr>
|
||||
<tr class="hover:bg-white transition-colors">
|
||||
<td class="py-3"><input type="checkbox" class="gov-row-cb rounded border-slate-300"></td>
|
||||
<td class="py-3 font-mono text-xs text-slate-500">D2024-QX-121</td>
|
||||
<td class="py-3 font-semibold text-slate-800">高超音速材料热防护测试</td>
|
||||
<td class="py-3 text-slate-600">桑迪亚国家实验室</td>
|
||||
<td class="py-3 text-slate-500 text-xs">2024-10-24 11:20</td>
|
||||
<td class="py-3"><span class="inline-flex items-center gap-1 px-2 py-0.5 rounded text-xs font-semibold bg-slate-100 text-slate-500 border border-slate-200"><i data-lucide="clock" class="w-3 h-3"></i> 待采集</span></td>
|
||||
<td class="py-3 text-right"><button class="text-primary hover:underline font-medium text-xs">开始</button></td>
|
||||
</tr>
|
||||
<tr class="hover:bg-white transition-colors">
|
||||
<td class="py-3"><input type="checkbox" class="gov-row-cb rounded border-slate-300"></td>
|
||||
<td class="py-3 font-mono text-xs text-slate-500">D2024-QX-138</td>
|
||||
<td class="py-3 font-semibold text-slate-800">自主无人机编队控制算法</td>
|
||||
<td class="py-3 text-slate-600">MIT林肯实验室</td>
|
||||
<td class="py-3 text-slate-500 text-xs">2024-10-24 13:05</td>
|
||||
<td class="py-3"><span class="inline-flex items-center gap-1 px-2 py-0.5 rounded text-xs font-semibold bg-emerald-50 text-emerald-700 border border-emerald-100"><i data-lucide="check-circle-2" class="w-3 h-3"></i> 已入库</span></td>
|
||||
<td class="py-3 text-right"><button class="text-primary hover:underline font-medium text-xs" onclick="showPage('detailPage')">详情</button></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<!-- Pagination -->
|
||||
<div class="mt-6 flex items-center justify-between">
|
||||
<span class="text-xs text-slate-500">显示 1 到 5 条,共 1,240 条记录</span>
|
||||
<div class="flex items-center gap-2">
|
||||
<button class="p-2 border border-slate-200 rounded hover:bg-slate-50"><i data-lucide="chevron-left" class="w-4 h-4"></i></button>
|
||||
<button class="w-8 h-8 bg-primary text-white rounded text-sm font-bold">1</button>
|
||||
<button class="w-8 h-8 hover:bg-slate-100 rounded text-sm">2</button>
|
||||
<button class="w-8 h-8 hover:bg-slate-100 rounded text-sm">3</button>
|
||||
<button class="p-2 border border-slate-200 rounded hover:bg-slate-50"><i data-lucide="chevron-right" class="w-4 h-4"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Right Sidebar: Task Monitor -->
|
||||
<aside class="w-72 border-l border-slate-200 bg-white flex flex-col shrink-0">
|
||||
<div class="p-4 border-b border-slate-100 flex items-center justify-between">
|
||||
<h3 class="font-bold text-sm flex items-center gap-2"><i data-lucide="activity" class="w-4 h-4 text-primary"></i> 任务实时监控</h3>
|
||||
</div>
|
||||
<div class="flex-1 overflow-auto p-4 space-y-5">
|
||||
<!-- Progress -->
|
||||
<div>
|
||||
<div class="flex justify-between items-end mb-2">
|
||||
<span class="text-xs font-bold text-slate-500 uppercase">数据清洗进度</span>
|
||||
<span class="text-xs font-mono text-primary font-bold">78.4%</span>
|
||||
</div>
|
||||
<div class="h-1.5 w-full bg-slate-200 rounded-full overflow-hidden">
|
||||
<div class="h-full bg-primary transition-all duration-500" style="width:78.4%"></div>
|
||||
</div>
|
||||
<p class="text-[10px] text-slate-400 mt-1.5">正在处理: D2024-QX-102 (哈佛大学研究院)</p>
|
||||
</div>
|
||||
<!-- Crawler Task -->
|
||||
<div class="bg-slate-50 p-4 rounded-xl border border-slate-200 flex items-center gap-3">
|
||||
<div class="relative w-12 h-12 flex items-center justify-center shrink-0">
|
||||
<svg class="w-full h-full transform -rotate-90">
|
||||
<circle cx="24" cy="24" r="20" stroke="currentColor" stroke-width="4" fill="transparent" class="text-slate-200"/>
|
||||
<circle cx="24" cy="24" r="20" stroke="currentColor" stroke-width="4" fill="transparent" stroke-dasharray="125.6" stroke-dashoffset="30" class="text-emerald-500"/>
|
||||
</svg>
|
||||
<span class="absolute text-[10px] font-bold text-slate-700">爬虫</span>
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="text-xs font-bold text-slate-800">分布式爬虫任务 B-09</h4>
|
||||
<p class="text-[10px] text-emerald-600 font-medium mt-0.5">运行中 · 耗时 12m</p>
|
||||
</div>
|
||||
</div>
|
||||
<!-- History -->
|
||||
<div>
|
||||
<h4 class="text-xs font-bold text-slate-400 uppercase mb-3 flex items-center gap-2">
|
||||
<i data-lucide="history" class="w-3 h-3"></i> 最近完成任务
|
||||
</h4>
|
||||
<div class="space-y-2">
|
||||
<div class="text-[11px] p-2 hover:bg-slate-50 rounded border-l-2 border-emerald-400">
|
||||
<div class="flex justify-between font-bold text-slate-700"><span>全量备份</span><span class="text-emerald-600">完成</span></div>
|
||||
<div class="flex justify-between text-slate-400 mt-1"><span>2024-10-24 08:00</span><span>4.2GB</span></div>
|
||||
</div>
|
||||
<div class="text-[11px] p-2 hover:bg-slate-50 rounded border-l-2 border-primary">
|
||||
<div class="flex justify-between font-bold text-slate-700"><span>字段解析 (Batch)</span><span class="text-primary">完成</span></div>
|
||||
<div class="flex justify-between text-slate-400 mt-1"><span>2024-10-23 18:20</span><span>1.2s</span></div>
|
||||
</div>
|
||||
<div class="text-[11px] p-2 hover:bg-slate-50 rounded border-l-2 border-slate-300">
|
||||
<div class="flex justify-between font-bold text-slate-700"><span>实体识别清洗</span><span class="text-slate-500">完成</span></div>
|
||||
<div class="flex justify-between text-slate-400 mt-1"><span>2024-10-23 10:40</span><span>892条</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button class="m-4 py-2 border border-slate-200 rounded-lg text-xs font-bold text-slate-600 hover:bg-slate-50 transition-colors">查看全部任务</button>
|
||||
</aside>
|
||||
</div>
|
||||
|
||||
<!-- Footer Stats -->
|
||||
<footer class="h-12 border-t border-slate-200 bg-white px-6 flex items-center justify-between shrink-0">
|
||||
<div class="flex items-center gap-8">
|
||||
<div class="flex items-baseline gap-2">
|
||||
<span class="text-[10px] font-bold text-slate-400 uppercase">数据总量</span>
|
||||
<span class="text-sm font-bold text-slate-900">1,420,892</span>
|
||||
</div>
|
||||
<div class="flex items-baseline gap-2">
|
||||
<span class="text-[10px] font-bold text-slate-400 uppercase">今日新增</span>
|
||||
<span class="text-sm font-bold text-emerald-600">+1,240</span>
|
||||
</div>
|
||||
<div class="flex items-baseline gap-2">
|
||||
<span class="text-[10px] font-bold text-slate-400 uppercase">异常警告</span>
|
||||
<span class="text-sm font-bold text-rose-500">12</span>
|
||||
</div>
|
||||
</div>
|
||||
<button class="text-xs font-bold text-primary flex items-center gap-2 hover:bg-blue-50 px-3 py-1.5 rounded-md transition-colors">
|
||||
<i data-lucide="shield-check" class="w-4 h-4"></i> 立即执行系统备份
|
||||
</button>
|
||||
</footer>
|
||||
|
||||
<!-- 采集任务 Tab Panel -->
|
||||
<div id="gpanel-crawler" class="hidden flex-1 flex items-center justify-center bg-slate-50">
|
||||
<div class="text-center">
|
||||
<div class="w-16 h-16 bg-slate-100 rounded-2xl flex items-center justify-center mx-auto mb-4">
|
||||
<i data-lucide="rss" class="w-8 h-8 text-slate-400"></i>
|
||||
</div>
|
||||
<h3 class="text-sm font-bold text-slate-700 mb-1">采集任务管理</h3>
|
||||
<p class="text-xs text-slate-400">分布式爬虫任务配置与监控</p>
|
||||
<button class="mt-4 px-4 py-2 bg-primary text-white rounded-lg text-xs font-bold hover:bg-blue-600 transition-colors">
|
||||
新建采集任务
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 数据备份 Tab Panel -->
|
||||
<div id="gpanel-backup" class="hidden flex-1 flex items-center justify-center bg-slate-50">
|
||||
<div class="text-center">
|
||||
<div class="w-16 h-16 bg-slate-100 rounded-2xl flex items-center justify-center mx-auto mb-4">
|
||||
<i data-lucide="hard-drive" class="w-8 h-8 text-slate-400"></i>
|
||||
</div>
|
||||
<h3 class="text-sm font-bold text-slate-700 mb-1">数据备份中心</h3>
|
||||
<p class="text-xs text-slate-400">全量/增量备份策略管理与历史记录</p>
|
||||
<button class="mt-4 px-4 py-2 bg-primary text-white rounded-lg text-xs font-bold hover:bg-blue-600 transition-colors">
|
||||
立即执行备份
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Import Modal -->
|
||||
<div id="govImportModal" class="hidden fixed inset-0 z-50 flex items-center justify-center bg-slate-900/60 backdrop-blur-sm">
|
||||
<div class="bg-white w-[720px] rounded-2xl shadow-2xl overflow-hidden">
|
||||
<div class="p-6 border-b border-slate-100 flex items-center justify-between">
|
||||
<div>
|
||||
<h2 class="text-lg font-bold text-slate-900">数据导入向导</h2>
|
||||
<p class="text-xs text-slate-400 mt-1">支持 Excel/CSV 格式,单次最大 10,000 条记录</p>
|
||||
</div>
|
||||
<button onclick="toggleGovModal()" class="text-slate-400 hover:text-slate-600"><i data-lucide="x" class="w-6 h-6"></i></button>
|
||||
</div>
|
||||
<!-- Steps -->
|
||||
<div class="px-12 py-6 bg-slate-50 flex justify-between relative">
|
||||
<div class="absolute top-[52px] left-24 right-24 h-0.5 bg-slate-200"></div>
|
||||
<div class="relative flex flex-col items-center gap-2 z-10">
|
||||
<div class="w-8 h-8 rounded-full bg-primary text-white flex items-center justify-center text-xs font-bold">1</div>
|
||||
<span class="text-[10px] font-bold text-primary uppercase">文件上传</span>
|
||||
</div>
|
||||
<div class="relative flex flex-col items-center gap-2 z-10">
|
||||
<div class="w-8 h-8 rounded-full bg-white border-2 border-slate-200 text-slate-400 flex items-center justify-center text-xs font-bold">2</div>
|
||||
<span class="text-[10px] font-bold text-slate-400 uppercase">智能解析</span>
|
||||
</div>
|
||||
<div class="relative flex flex-col items-center gap-2 z-10">
|
||||
<div class="w-8 h-8 rounded-full bg-white border-2 border-slate-200 text-slate-400 flex items-center justify-center text-xs font-bold">3</div>
|
||||
<span class="text-[10px] font-bold text-slate-400 uppercase">字段映射</span>
|
||||
</div>
|
||||
<div class="relative flex flex-col items-center gap-2 z-10">
|
||||
<div class="w-8 h-8 rounded-full bg-white border-2 border-slate-200 text-slate-400 flex items-center justify-center text-xs font-bold">4</div>
|
||||
<span class="text-[10px] font-bold text-slate-400 uppercase">结果预览</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Upload Zone -->
|
||||
<div class="p-10">
|
||||
<div class="border-2 border-dashed border-slate-200 rounded-2xl p-12 flex flex-col items-center justify-center bg-slate-50 hover:border-primary hover:bg-blue-50 transition-all cursor-pointer">
|
||||
<div class="w-16 h-16 bg-white rounded-2xl shadow-sm flex items-center justify-center mb-4">
|
||||
<i data-lucide="file-up" class="w-8 h-8 text-primary"></i>
|
||||
</div>
|
||||
<p class="text-sm font-bold text-slate-700">点击或将文件拖拽到此处</p>
|
||||
<p class="text-xs text-slate-400 mt-2">支持 .xlsx, .xls, .csv 格式 (最大 20MB)</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="p-6 border-t border-slate-100 flex justify-end gap-3 bg-slate-50">
|
||||
<button onclick="toggleGovModal()" class="px-6 py-2 text-sm font-medium text-slate-600 hover:bg-slate-200 rounded-lg">取消</button>
|
||||
<button class="px-6 py-2 text-sm font-bold bg-primary text-white rounded-lg hover:bg-blue-600 shadow-lg transition-all">下一步</button>
|
||||
</div>
|
||||
</div>
|
||||
</div><!-- end govImportModal -->
|
||||
|
||||
</div><!-- end right wrapper -->
|
||||
</div><!-- end h-screen flex -->
|
||||
</div><!-- end governancePage -->
|
||||
@ -1,257 +0,0 @@
|
||||
<div id="graphPage" class="page">
|
||||
<div class="w-full h-screen flex">
|
||||
|
||||
<!-- Sidebar (dark theme to match graph bg) -->
|
||||
<aside class="w-64 bg-sidebar text-slate-400 flex flex-col shrink-0">
|
||||
<div class="p-6 flex items-center gap-3">
|
||||
<div class="w-8 h-8 bg-blue-600 rounded flex items-center justify-center text-white">
|
||||
<i data-lucide="share-2" class="w-5 h-5"></i>
|
||||
</div>
|
||||
<span class="text-white font-bold tracking-tight text-lg">DARPA <span class="text-blue-400 font-light">Graph</span></span>
|
||||
</div>
|
||||
<nav class="flex-1 px-4 py-4 space-y-1 overflow-y-auto custom-scrollbar">
|
||||
<div class="text-[10px] font-bold text-slate-500 uppercase tracking-widest px-2 mb-2">主导航</div>
|
||||
<a data-nav-key="dashboard" onclick="showPage('dashboardPage')" class="flex items-center gap-3 px-3 py-2.5 rounded-lg hover:bg-white/5 hover:text-white transition-all text-sm cursor-pointer">
|
||||
<i data-lucide="layout-dashboard" class="w-4 h-4"></i> 工作台
|
||||
</a>
|
||||
<a data-nav-key="search" onclick="showPage('searchPage')" class="flex items-center gap-3 px-3 py-2.5 rounded-lg hover:bg-white/5 hover:text-white transition-all text-sm cursor-pointer">
|
||||
<i data-lucide="search" class="w-4 h-4"></i> 智能检索
|
||||
</a>
|
||||
<a data-nav-key="graph" onclick="showPage('graphPage')" class="flex items-center gap-3 px-3 py-2.5 rounded-lg menu-active text-sm cursor-pointer">
|
||||
<i data-lucide="share-2" class="w-4 h-4"></i> 可视化分析
|
||||
</a>
|
||||
<a data-nav-key="trend" onclick="showPage('trendPage')" class="flex items-center gap-3 px-3 py-2.5 rounded-lg hover:bg-white/5 hover:text-white transition-all text-sm cursor-pointer">
|
||||
<i data-lucide="trending-up" class="w-4 h-4"></i> 趋势分析
|
||||
</a>
|
||||
<div class="text-[10px] font-bold text-slate-500 uppercase tracking-widest px-2 mt-6 mb-2">图谱控制</div>
|
||||
<div class="px-2 space-y-2">
|
||||
<label class="flex items-center gap-2 text-xs text-slate-300 cursor-pointer">
|
||||
<input type="checkbox" id="chkProj" checked onchange="reloadGraph()" class="accent-blue-500 rounded"> 项目节点
|
||||
</label>
|
||||
<label class="flex items-center gap-2 text-xs text-slate-300 cursor-pointer">
|
||||
<input type="checkbox" id="chkTech" checked onchange="reloadGraph()" class="accent-purple-500 rounded"> 技术节点
|
||||
</label>
|
||||
<label class="flex items-center gap-2 text-xs text-slate-300 cursor-pointer">
|
||||
<input type="checkbox" id="chkInst" checked onchange="reloadGraph()" class="accent-amber-600 rounded"> 机构节点
|
||||
</label>
|
||||
<label class="flex items-center justify-between text-xs text-slate-300 cursor-pointer">
|
||||
<span>隐藏 DARPA</span>
|
||||
<input type="checkbox" id="hideDarpa" checked onchange="reloadGraph()" class="accent-blue-500 rounded">
|
||||
</label>
|
||||
</div>
|
||||
<div class="text-[10px] font-bold text-slate-500 uppercase tracking-widest px-2 mt-4 mb-2">最短路径</div>
|
||||
<div class="px-2 space-y-2">
|
||||
<select id="pathFrom" class="w-full bg-slate-800 border border-slate-700 rounded px-2 py-1 text-xs text-slate-200 outline-none focus:border-blue-500"><option value="">── 选择起点 ──</option></select>
|
||||
<select id="pathTo" class="w-full bg-slate-800 border border-slate-700 rounded px-2 py-1 text-xs text-slate-200 outline-none focus:border-blue-500"><option value="">── 选择终点 ──</option></select>
|
||||
<label class="flex items-center justify-between text-xs text-slate-300 cursor-pointer">
|
||||
<span>排除 DARPA 中转</span>
|
||||
<input type="checkbox" id="pathIgnoreDarpa" checked class="accent-blue-500 rounded">
|
||||
</label>
|
||||
<div class="flex gap-1">
|
||||
<button onclick="queryPath()" class="flex-1 px-2 py-1 bg-blue-700 hover:bg-blue-600 rounded text-xs font-medium transition-all">查询路径</button>
|
||||
<button onclick="clearPath()" class="px-2 py-1 bg-slate-700 hover:bg-slate-600 rounded text-xs font-medium transition-all">清除</button>
|
||||
</div>
|
||||
<div id="pathResult" class="text-[10px] text-emerald-400 leading-relaxed min-h-[20px] break-all"></div>
|
||||
</div>
|
||||
<a data-nav-key="wiki" onclick="showPage('wikiPage')" class="flex items-center gap-3 px-3 py-2.5 rounded-lg hover:bg-white/5 hover:text-white transition-all text-sm cursor-pointer">
|
||||
<i data-lucide="book-open" class="w-4 h-4"></i> 情报百科
|
||||
</a>
|
||||
<div class="text-[10px] font-bold text-slate-500 uppercase tracking-widest px-2 mt-6 mb-2">数据管理</div>
|
||||
<a data-nav-key="governance" onclick="showPage('governancePage')" class="flex items-center gap-3 px-3 py-2.5 rounded-lg hover:bg-white/5 hover:text-white transition-all text-sm cursor-pointer">
|
||||
<i data-lucide="database" class="w-4 h-4"></i> 数据治理
|
||||
</a>
|
||||
<div class="text-[10px] font-bold text-slate-500 uppercase tracking-widest px-2 mt-6 mb-2">系统</div>
|
||||
<a onclick="logout()" class="flex items-center gap-3 px-3 py-2.5 rounded-lg hover:bg-white/5 hover:text-white transition-all text-sm cursor-pointer">
|
||||
<i data-lucide="log-out" class="w-4 h-4"></i> 退出登录
|
||||
</a>
|
||||
</nav>
|
||||
<div class="p-4 border-t border-white/5 flex items-center gap-3">
|
||||
<div class="sidebar-user-avatar w-8 h-8 rounded-full bg-slate-700 flex items-center justify-center text-slate-300 shrink-0">
|
||||
<i data-lucide="user" class="w-4 h-4"></i>
|
||||
</div>
|
||||
<div class="flex-1 min-w-0">
|
||||
<p class="sidebar-user-name text-xs font-bold text-white truncate">--</p>
|
||||
<p class="sidebar-user-role text-[10px] text-slate-500 truncate">--</p>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<!-- Right content -->
|
||||
<div class="flex-1 flex flex-col bg-slate-50 text-slate-800 overflow-hidden">
|
||||
<!-- Top Control Bar (工具栏,去掉了原来的 logo 和返回按钮) -->
|
||||
<header class="h-14 px-6 flex items-center justify-between shrink-0 z-50" style="background:rgba(255,255,255,0.88);backdrop-filter:blur(12px);border-bottom:1px solid rgba(0,0,0,0.06)">
|
||||
<div class="flex items-center gap-6">
|
||||
<!-- Graph Type Tabs -->
|
||||
<div class="flex bg-slate-200/60 p-1 rounded-lg">
|
||||
<button id="gtab-proj" onclick="switchGraphTab('proj')" class="px-3 py-1 text-xs font-medium rounded-md bg-blue-600 text-white transition-all">项目关联</button>
|
||||
<button id="gtab-tech" onclick="switchGraphTab('tech')" class="px-3 py-1 text-xs font-medium text-slate-600 hover:text-slate-800 transition-all">技术演进</button>
|
||||
<button id="gtab-org" onclick="switchGraphTab('org')" class="px-3 py-1 text-xs font-medium text-slate-600 hover:text-slate-800 transition-all">机构合作</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center gap-4">
|
||||
<div onclick="openProfileModal()" class="header-user-avatar w-8 h-8 rounded-full bg-slate-200 flex items-center justify-center cursor-pointer shrink-0">
|
||||
<i data-lucide="user" class="w-4 h-4 text-slate-500"></i>
|
||||
</div>
|
||||
<!-- Algorithm Select -->
|
||||
<div class="flex items-center gap-2 text-xs text-slate-500">
|
||||
<span>布局算法:</span>
|
||||
<select id="graphLayout" onchange="changeLayout(this.value)" class="bg-white border border-slate-300 rounded px-2 py-1 text-slate-600 outline-none focus:border-blue-500">
|
||||
<option value="force">力导向 (Force)</option>
|
||||
<option value="circular">环形布局 (Circular)</option>
|
||||
<option value="none">无布局 (None)</option>
|
||||
</select>
|
||||
</div>
|
||||
<!-- Threshold Slider -->
|
||||
<div class="flex items-center gap-2 text-xs text-slate-500">
|
||||
<span>关联阈值:</span>
|
||||
<input type="range" class="w-24 accent-blue-500" min="0" max="100" value="30">
|
||||
</div>
|
||||
<div class="h-6 w-px bg-slate-300"></div>
|
||||
<div class="flex items-center gap-2">
|
||||
<button onclick="resetGraphView()" class="p-2 text-slate-500 hover:text-slate-700 hover:bg-slate-100 rounded-lg transition-all" title="重置视图">
|
||||
<i data-lucide="refresh-cw" class="w-4 h-4"></i>
|
||||
</button>
|
||||
<button onclick="exportGraphImage()" class="flex items-center gap-2 px-3 py-1.5 bg-white hover:bg-slate-100 border border-slate-300 rounded-lg text-xs font-medium transition-all">
|
||||
<i data-lucide="camera" class="w-4 h-4 text-blue-500"></i> 截图导出
|
||||
</button>
|
||||
<button onclick="reloadGraph()" class="flex items-center gap-2 px-3 py-1.5 bg-blue-600 hover:bg-blue-500 rounded-lg text-xs font-medium transition-all">
|
||||
<i data-lucide="refresh-cw" class="w-4 h-4"></i> 刷新数据
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- Main Area -->
|
||||
<main class="flex-1 flex overflow-hidden relative">
|
||||
<!-- Left Legend (dark glass) -->
|
||||
<div class="absolute left-6 top-6 z-10 p-4 rounded-xl shadow-lg glass-panel" style="min-width:160px">
|
||||
<h4 class="text-[10px] font-bold text-slate-500 uppercase tracking-widest mb-3">节点图例</h4>
|
||||
<div class="space-y-2">
|
||||
<div class="flex items-center gap-2 text-[10px] text-slate-600">
|
||||
<span class="w-2.5 h-2.5 rounded inline-block" style="background:#1d4ed8"></span> 项目 (圆角矩形)
|
||||
</div>
|
||||
<div class="flex items-center gap-2 text-[10px] text-slate-600">
|
||||
<span class="w-2.5 h-2.5 inline-block" style="background:#6d28d9;clip-path:polygon(50% 0,100% 100%,0 100%)"></span> 技术 (菱形)
|
||||
</div>
|
||||
<div class="flex items-center gap-2 text-[10px] text-slate-600">
|
||||
<span class="w-2.5 h-2.5 rounded-full inline-block" style="background:#b45309"></span> 机构 (圆)
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-3 pt-3 border-t border-slate-200 space-y-1">
|
||||
<div class="flex justify-between text-[10px]">
|
||||
<span class="text-slate-500">节点总数</span><span id="graphNodeCount" class="font-mono text-blue-600">--</span>
|
||||
</div>
|
||||
<div class="flex justify-between text-[10px]">
|
||||
<span class="text-slate-500">关系边数</span><span id="graphEdgeCount" class="font-mono text-blue-600">--</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Breadcrumb Navigation Bar -->
|
||||
<div id="graphBreadcrumb" class="absolute top-4 left-1/2 -translate-x-1/2 z-20 flex items-center gap-2 px-4 py-2 rounded-full shadow-2xl transition-all" style="display:none;background:rgba(255,255,255,0.92);backdrop-filter:blur(12px);border:1px solid rgba(0,0,0,0.08)">
|
||||
<button onclick="breadcrumbBack()" class="p-1 text-slate-500 hover:text-slate-800 transition-colors" title="返回上一级">
|
||||
<i data-lucide="arrow-left" class="w-3.5 h-3.5"></i>
|
||||
</button>
|
||||
<div id="breadcrumbPath" class="flex items-center gap-1 text-xs"></div>
|
||||
<button onclick="breadcrumbReset()" class="p-1 text-slate-500 hover:text-slate-800 transition-colors" title="重置视图">
|
||||
<i data-lucide="x" class="w-3 h-3"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Graph Canvas -->
|
||||
<div id="mainGraph" class="flex-1 cursor-grab active:cursor-grabbing graph-canvas-bg" style="background-color:#ffffff"></div>
|
||||
|
||||
<!-- Right Side Panel -->
|
||||
<aside id="graphSidePanel" class="w-[380px] flex flex-col shrink-0 transition-all duration-500" style="background:rgba(255,255,255,0.92);backdrop-filter:blur(12px);border-left:1px solid rgba(0,0,0,0.06)">
|
||||
<div class="p-5 flex items-center justify-between shrink-0" style="border-bottom:1px solid rgba(0,0,0,0.06)">
|
||||
<h3 class="font-bold flex items-center gap-2 text-sm text-slate-800">
|
||||
<i data-lucide="info" class="w-4 h-4 text-blue-500"></i> 节点情报详情
|
||||
</h3>
|
||||
<button onclick="toggleGraphSidebar()" class="text-slate-400 hover:text-slate-600 transition-colors">
|
||||
<i data-lucide="x" class="w-5 h-5"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="flex-1 overflow-y-auto p-5 space-y-6 custom-scrollbar">
|
||||
<!-- Node Header -->
|
||||
<div class="flex items-start gap-4">
|
||||
<div id="gsp-icon-wrap" class="w-14 h-14 rounded-2xl flex items-center justify-center shrink-0" style="background:rgba(59,130,246,0.08);border:1px solid rgba(59,130,246,0.2)">
|
||||
<i id="gsp-icon" data-lucide="rocket" class="w-7 h-7" style="color:#3b82f6"></i>
|
||||
</div>
|
||||
<div>
|
||||
<span id="gsp-cat" class="text-[10px] font-bold uppercase tracking-widest px-2 py-0.5 rounded" style="color:#3b82f6;background:rgba(59,130,246,0.08)">科研项目</span>
|
||||
<h2 id="gsp-title" class="text-base font-bold mt-1 text-slate-800 leading-snug">ACE — 空战演进计划</h2>
|
||||
<p id="gsp-org" class="text-[11px] text-slate-500 mt-0.5">DARPA / STO</p>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Stats Grid -->
|
||||
<div class="grid grid-cols-2 gap-3">
|
||||
<div class="p-3 rounded-xl" style="background:rgba(248,250,252,0.9);border:1px solid rgba(0,0,0,0.06)">
|
||||
<p class="text-[10px] text-slate-500 uppercase font-bold">中心度得分</p>
|
||||
<p id="gsp-centrality" class="text-xl font-mono font-bold mt-1" style="color:#3b82f6">0.862</p>
|
||||
</div>
|
||||
<div class="p-3 rounded-xl" style="background:rgba(248,250,252,0.9);border:1px solid rgba(0,0,0,0.06)">
|
||||
<p class="text-[10px] text-slate-500 uppercase font-bold">关联节点数</p>
|
||||
<p id="gsp-degree" class="text-xl font-mono text-emerald-600 font-bold mt-1">8</p>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Summary -->
|
||||
<div>
|
||||
<h4 class="text-xs font-bold text-slate-500 mb-2 uppercase tracking-wider">情报摘要</h4>
|
||||
<p id="gsp-summary" class="text-sm text-slate-600 leading-relaxed">通过空中格斗环境验证受信任人工智能算法,探索在复杂对抗决策场景下人机协同的极限边界。重点评估自主系统在高度不确定环境中的适应性与安全性。</p>
|
||||
<button id="gsp-detail-btn" class="hidden mt-3 w-full py-2 text-xs font-bold text-white bg-primary rounded-lg hover:bg-blue-600 transition-colors flex items-center justify-center gap-1.5">
|
||||
<i data-lucide="file-text" class="w-3.5 h-3.5"></i> 查看项目详情
|
||||
</button>
|
||||
</div>
|
||||
<!-- Graph Stats -->
|
||||
<div class="pt-5" style="border-top:1px solid rgba(0,0,0,0.06)">
|
||||
<h4 class="text-xs font-bold text-slate-500 mb-4 uppercase tracking-wider">全局拓扑统计</h4>
|
||||
<div class="space-y-3">
|
||||
<div class="flex justify-between items-center text-xs">
|
||||
<span class="text-slate-500">全图节点总数</span><span id="gspTotalNodes" class="text-slate-800 font-mono">--</span>
|
||||
</div>
|
||||
<div class="flex justify-between items-center text-xs">
|
||||
<span class="text-slate-500">关系边总数</span><span id="gspTotalEdges" class="text-slate-800 font-mono">--</span>
|
||||
</div>
|
||||
<div class="flex justify-between items-center text-xs">
|
||||
<span class="text-slate-500">项目节点数</span><span id="gspProjCount" class="text-slate-800 font-mono">--</span>
|
||||
</div>
|
||||
<div class="flex justify-between items-center text-xs">
|
||||
<span class="text-slate-500">技术/机构</span><span id="gspTechInstCount" class="text-slate-800 font-mono">--</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Hint -->
|
||||
<div class="rounded-lg px-3 py-2 text-[11px] text-slate-500 flex items-center gap-2" style="background:rgba(59,130,246,0.04);border:1px solid rgba(59,130,246,0.1)">
|
||||
<i data-lucide="mouse-pointer-click" class="w-3.5 h-3.5 shrink-0" style="color:#3b82f6"></i>
|
||||
单击节点查看详情,双击聚焦子图
|
||||
</div>
|
||||
</div>
|
||||
<!-- Footer Actions -->
|
||||
<div class="p-5 grid grid-cols-2 gap-3 shrink-0" style="border-top:1px solid rgba(0,0,0,0.06)">
|
||||
<button id="gsp-archive-btn" onclick="showPage('detailPage')" class="px-4 py-2 rounded-lg text-xs font-bold text-slate-600 transition-all flex items-center justify-center gap-2" style="background:rgba(248,250,252,0.9);border:1px solid rgba(0,0,0,0.08)">
|
||||
<i data-lucide="external-link" class="w-4 h-4"></i> 查看档案
|
||||
</button>
|
||||
<button id="gsp-focus-btn" onclick="focusSelectedNode()" class="px-4 py-2 bg-blue-600 hover:bg-blue-500 text-white rounded-lg text-xs font-bold transition-all flex items-center justify-center gap-2">
|
||||
<i data-lucide="crosshair" class="w-4 h-4"></i> 聚焦子图
|
||||
</button>
|
||||
</div>
|
||||
</aside>
|
||||
</main>
|
||||
|
||||
<!-- Bottom Hint Bar -->
|
||||
<div class="absolute bottom-6 left-1/2 -translate-x-1/2 flex items-center gap-4 px-4 py-2 rounded-full shadow-xl z-20" style="background:rgba(255,255,255,0.92);backdrop-filter:blur(12px);border:1px solid rgba(0,0,0,0.08)">
|
||||
<div class="flex items-center gap-2 text-[10px] text-slate-500">
|
||||
<kbd class="px-1.5 py-0.5 rounded border text-[10px]" style="background:rgba(248,250,252,0.9);border-color:rgba(0,0,0,0.1)">滚轮</kbd> 缩放
|
||||
</div>
|
||||
<div class="w-px h-3 bg-slate-300"></div>
|
||||
<div class="flex items-center gap-2 text-[10px] text-slate-500">
|
||||
<kbd class="px-1.5 py-0.5 rounded border text-[10px]" style="background:rgba(248,250,252,0.9);border-color:rgba(0,0,0,0.1)">拖拽</kbd> 移动
|
||||
</div>
|
||||
<div class="w-px h-3 bg-slate-300"></div>
|
||||
<div class="flex items-center gap-2 text-[10px] text-slate-500">
|
||||
<kbd class="px-1.5 py-0.5 rounded border text-[10px]" style="background:rgba(248,250,252,0.9);border-color:rgba(0,0,0,0.1)">双击</kbd> 聚焦子图
|
||||
</div>
|
||||
</div>
|
||||
</div><!-- end right wrapper -->
|
||||
</div><!-- end h-screen flex -->
|
||||
</div><!-- end graphPage -->
|
||||
@ -1,215 +0,0 @@
|
||||
<div id="searchPage" class="page">
|
||||
<div class="w-full h-screen flex">
|
||||
|
||||
<!-- Sidebar -->
|
||||
<aside class="w-64 bg-sidebar text-slate-400 flex flex-col shrink-0">
|
||||
<div class="p-6 flex items-center gap-3">
|
||||
<div class="w-8 h-8 bg-primary rounded flex items-center justify-center text-white">
|
||||
<i data-lucide="shield-half" class="w-5 h-5"></i>
|
||||
</div>
|
||||
<span class="text-white font-bold tracking-tight text-lg">DARPA Intelligence</span>
|
||||
</div>
|
||||
<nav class="flex-1 px-4 py-4 space-y-1 overflow-y-auto custom-scrollbar">
|
||||
<div class="text-[10px] font-bold text-slate-500 uppercase tracking-widest px-2 mb-2">主导航</div>
|
||||
<a data-nav-key="dashboard" onclick="showPage('dashboardPage')" class="flex items-center gap-3 px-3 py-2.5 rounded-lg hover:bg-white/5 hover:text-white transition-all text-sm cursor-pointer">
|
||||
<i data-lucide="layout-dashboard" class="w-4 h-4"></i> 工作台
|
||||
</a>
|
||||
<a data-nav-key="search" onclick="showPage('searchPage')" class="flex items-center gap-3 px-3 py-2.5 rounded-lg menu-active text-sm cursor-pointer">
|
||||
<i data-lucide="search" class="w-4 h-4"></i> 智能检索
|
||||
</a>
|
||||
<a data-nav-key="graph" onclick="showPage('graphPage')" class="flex items-center gap-3 px-3 py-2.5 rounded-lg hover:bg-white/5 hover:text-white transition-all text-sm cursor-pointer">
|
||||
<i data-lucide="share-2" class="w-4 h-4"></i> 可视化分析
|
||||
</a>
|
||||
<a data-nav-key="trend" onclick="showPage('trendPage')" class="flex items-center gap-3 px-3 py-2.5 rounded-lg hover:bg-white/5 hover:text-white transition-all text-sm cursor-pointer">
|
||||
<i data-lucide="trending-up" class="w-4 h-4"></i> 趋势分析
|
||||
</a>
|
||||
<a data-nav-key="wiki" onclick="showPage('wikiPage')" class="flex items-center gap-3 px-3 py-2.5 rounded-lg hover:bg-white/5 hover:text-white transition-all text-sm cursor-pointer">
|
||||
<i data-lucide="book-open" class="w-4 h-4"></i> 情报百科
|
||||
</a>
|
||||
<div class="text-[10px] font-bold text-slate-500 uppercase tracking-widest px-2 mt-6 mb-2">数据管理</div>
|
||||
<a data-nav-key="governance" onclick="showPage('governancePage')" class="flex items-center gap-3 px-3 py-2.5 rounded-lg hover:bg-white/5 hover:text-white transition-all text-sm cursor-pointer">
|
||||
<i data-lucide="database" class="w-4 h-4"></i> 数据治理
|
||||
</a>
|
||||
<div class="text-[10px] font-bold text-slate-500 uppercase tracking-widest px-2 mt-6 mb-2">系统</div>
|
||||
<a onclick="logout()" class="flex items-center gap-3 px-3 py-2.5 rounded-lg hover:bg-white/5 hover:text-white transition-all text-sm cursor-pointer">
|
||||
<i data-lucide="log-out" class="w-4 h-4"></i> 退出登录
|
||||
</a>
|
||||
</nav>
|
||||
<div class="p-4 border-t border-white/5 flex items-center gap-3">
|
||||
<div class="sidebar-user-avatar w-8 h-8 rounded-full bg-slate-700 flex items-center justify-center text-slate-300 shrink-0">
|
||||
<i data-lucide="user" class="w-4 h-4"></i>
|
||||
</div>
|
||||
<div class="flex-1 min-w-0">
|
||||
<p class="sidebar-user-name text-xs font-bold text-white truncate">--</p>
|
||||
<p class="sidebar-user-role text-[10px] text-slate-500 truncate">--</p>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<!-- Right content -->
|
||||
<div class="flex-1 flex flex-col overflow-hidden bg-slate-50">
|
||||
|
||||
<!-- Search Header -->
|
||||
<section class="px-8 py-6 border-b bg-gradient-to-r from-slate-50 to-blue-50 shrink-0">
|
||||
<div class="max-w-5xl mx-auto">
|
||||
<!-- Mode Tabs -->
|
||||
<div class="flex items-center justify-between mb-5">
|
||||
<div class="flex bg-slate-200/60 p-1 rounded-xl w-fit">
|
||||
<button onclick="switchSearchTab('fuzzy')" id="stab-fuzzy" class="px-6 py-2 rounded-lg text-sm font-semibold transition-all bg-white text-primary shadow-sm">模糊检索</button>
|
||||
<button onclick="switchSearchTab('precise')" id="stab-precise" class="px-6 py-2 rounded-lg text-sm font-semibold transition-all text-slate-600 hover:text-primary">精准检索</button>
|
||||
<button onclick="switchSearchTab('advanced')" id="stab-advanced" class="px-6 py-2 rounded-lg text-sm font-semibold transition-all text-slate-600 hover:text-primary">高级检索</button>
|
||||
</div>
|
||||
<div onclick="openProfileModal()" class="header-user-avatar w-8 h-8 rounded-full bg-slate-200 flex items-center justify-center cursor-pointer shrink-0">
|
||||
<i data-lucide="user" class="w-4 h-4 text-slate-500"></i>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Fuzzy Panel -->
|
||||
<div id="spanel-fuzzy">
|
||||
<div class="flex items-center bg-white rounded-2xl shadow-xl border border-blue-100 p-2 pl-6 focus-within:ring-4 ring-blue-100 transition-all">
|
||||
<i data-lucide="search" class="text-slate-400 w-6 h-6 shrink-0"></i>
|
||||
<input type="text" id="fuzzyInput" placeholder="输入项目名称、机构或技术关键字..." class="flex-1 px-4 py-3 text-lg outline-none placeholder:text-slate-300">
|
||||
<button id="fuzzySearchBtn" class="bg-primary text-white px-8 py-3 rounded-xl font-bold hover:bg-blue-600 transition-colors flex items-center gap-2">
|
||||
<i data-lucide="search" class="w-4 h-4"></i><span>搜索</span>
|
||||
</button>
|
||||
</div>
|
||||
<!-- Smart Suggestions -->
|
||||
<div id="searchSuggestions" class="mt-2 bg-white rounded-xl shadow-2xl border border-slate-100 p-4 hidden">
|
||||
<p class="text-xs font-bold text-slate-400 uppercase mb-2 tracking-wider">智能推荐</p>
|
||||
<div class="grid grid-cols-2 gap-2" id="searchSuggestionsList">
|
||||
<div class="col-span-2 text-xs text-slate-400">加载中...</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Precise Panel -->
|
||||
<div id="spanel-precise" class="hidden">
|
||||
<div class="bg-white p-6 rounded-2xl shadow-lg border border-slate-100 grid grid-cols-4 gap-4">
|
||||
<div class="space-y-1">
|
||||
<label class="text-xs font-bold text-slate-500 ml-1">检索字段</label>
|
||||
<select id="preciseField" class="w-full border rounded-lg p-2 text-sm outline-none focus:border-primary">
|
||||
<option value="nameCn">项目名称</option>
|
||||
<option value="institution">执行机构</option>
|
||||
<option value="domain">技术领域</option>
|
||||
<option value="description">项目摘要</option>
|
||||
<option value="office">管理办公室</option>
|
||||
<option value="technology">关键技术</option>
|
||||
<option value="keywords">关键词</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-span-2 space-y-1">
|
||||
<label class="text-xs font-bold text-slate-500 ml-1">检索值</label>
|
||||
<input type="text" id="preciseValue" class="w-full border rounded-lg p-2 text-sm outline-none focus:border-primary" placeholder="请输入...">
|
||||
</div>
|
||||
<div class="flex items-end">
|
||||
<button id="preciseSearchBtn" class="w-full bg-primary text-white px-4 py-2 rounded-lg font-bold hover:bg-blue-600 text-sm flex items-center justify-center gap-1">
|
||||
<i data-lucide="search" class="w-4 h-4"></i> 搜索
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Advanced Panel -->
|
||||
<div id="spanel-advanced" class="hidden">
|
||||
<div class="bg-slate-800 p-6 rounded-2xl shadow-xl text-white">
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<h4 class="text-sm font-bold flex items-center gap-2"><i data-lucide="cpu" class="w-4 h-4"></i> 逻辑表达式编辑器</h4>
|
||||
<span class="text-xs text-slate-400">支持 SQL 风格逻辑组合</span>
|
||||
</div>
|
||||
<div class="space-y-3" id="advancedExprList">
|
||||
<p class="text-xs text-slate-500 text-center py-4">开发中,敬请期待...</p>
|
||||
</div>
|
||||
<div class="mt-4 pt-4 border-t border-slate-700 flex justify-between">
|
||||
<button class="text-sm text-slate-400 hover:text-white flex items-center gap-1">
|
||||
<i data-lucide="help-circle" class="w-4 h-4"></i> 语法说明
|
||||
</button>
|
||||
<button class="bg-primary px-6 py-2 rounded-lg text-sm font-bold hover:bg-blue-600">执行组合检索</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Results Area -->
|
||||
<div class="flex-1 flex overflow-hidden">
|
||||
<!-- Results List -->
|
||||
<div class="flex-1 p-8 overflow-y-auto custom-scrollbar">
|
||||
<!-- Stats & Sort Bar -->
|
||||
<div class="flex items-center justify-between mb-6">
|
||||
<div id="searchStats" class="text-sm text-slate-500">
|
||||
输入关键词开始检索
|
||||
</div>
|
||||
<div class="flex items-center gap-4">
|
||||
<div class="flex items-center gap-2 bg-white border rounded-lg px-3 py-1.5 shadow-sm">
|
||||
<span class="text-xs font-bold text-slate-400">排序:</span>
|
||||
<select id="searchSort" class="text-xs font-semibold outline-none bg-transparent">
|
||||
<option value="relevance">相关性优先</option>
|
||||
<option value="newest">最新发布</option>
|
||||
<option value="budget">预算从高到低</option>
|
||||
<option value="name">名称排序</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Result Items (dynamic) -->
|
||||
<div id="searchResults" class="space-y-4">
|
||||
<div class="text-center py-16">
|
||||
<i data-lucide="search" class="w-12 h-12 text-slate-300 mx-auto mb-4"></i>
|
||||
<p class="text-slate-500 font-medium">输入关键词或选择筛选条件开始检索</p>
|
||||
<p class="text-xs text-slate-400 mt-1">共 162 个项目可供探索</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Pagination (dynamic) -->
|
||||
<div id="searchPagination" class="mt-10 flex items-center justify-center"></div>
|
||||
</div>
|
||||
|
||||
<!-- Right Filter Panel -->
|
||||
<aside class="w-72 bg-white border-l p-6 overflow-y-auto custom-scrollbar shrink-0">
|
||||
<div class="flex items-center justify-between mb-6">
|
||||
<h3 class="font-bold text-slate-900 flex items-center gap-2"><i data-lucide="filter" class="w-4 h-4"></i> 筛选过滤</h3>
|
||||
<button onclick="resetAllFilters()" class="text-xs text-primary font-bold hover:underline">一键重置</button>
|
||||
</div>
|
||||
<!-- Active Tags -->
|
||||
<div id="activeFilterTags" class="mb-6 flex flex-wrap gap-2">
|
||||
<span class="text-[10px] text-slate-400">无筛选条件</span>
|
||||
</div>
|
||||
<!-- Filter Groups -->
|
||||
<div class="space-y-6">
|
||||
<!-- Domain -->
|
||||
<div class="space-y-3">
|
||||
<div class="flex justify-between items-center cursor-pointer" onclick="toggleSearchFilter('sorg-list', 'sorg-icon')">
|
||||
<span class="text-sm font-bold text-slate-700">技术领域</span>
|
||||
<i data-lucide="chevron-down" id="sorg-icon" class="w-4 h-4 text-slate-400 transition-transform"></i>
|
||||
</div>
|
||||
<div id="sorg-list" class="space-y-2">
|
||||
<p class="text-xs text-slate-400">加载中...</p>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Status -->
|
||||
<div class="space-y-3">
|
||||
<div class="flex justify-between items-center cursor-pointer" onclick="toggleSearchFilter('sstatus-list', 'sstatus-icon')">
|
||||
<span class="text-sm font-bold text-slate-700">项目状态</span>
|
||||
<i data-lucide="chevron-down" id="sstatus-icon" class="w-4 h-4 text-slate-400 transition-transform"></i>
|
||||
</div>
|
||||
<div id="sstatus-list" class="space-y-2">
|
||||
<p class="text-xs text-slate-400">加载中...</p>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Date Range -->
|
||||
<div class="space-y-3">
|
||||
<span class="text-sm font-bold text-slate-700">时间跨度</span>
|
||||
<div class="flex gap-2">
|
||||
<input type="number" id="searchDateFrom" placeholder="起始年" min="1950" max="2026" class="w-20 border rounded px-2 py-1 text-xs outline-none focus:border-primary">
|
||||
<span class="text-xs text-slate-400 self-center">—</span>
|
||||
<input type="number" id="searchDateTo" placeholder="结束年" min="1950" max="2026" class="w-20 border rounded px-2 py-1 text-xs outline-none focus:border-primary">
|
||||
</div>
|
||||
<button onclick="applyDateFilter()" class="w-full py-1.5 text-xs font-bold text-primary border border-primary/30 rounded-lg hover:bg-blue-50 transition-colors">应用时间范围</button>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
</div>
|
||||
</div><!-- end right wrapper -->
|
||||
</div><!-- end h-screen flex -->
|
||||
</div><!-- end searchPage -->
|
||||
@ -1,177 +0,0 @@
|
||||
<div id="trendPage" class="page">
|
||||
<div class="w-full h-screen flex">
|
||||
|
||||
<!-- Sidebar -->
|
||||
<aside class="w-64 bg-sidebar text-slate-400 flex flex-col shrink-0">
|
||||
<div class="p-6 flex items-center gap-3">
|
||||
<div class="w-8 h-8 bg-primary rounded flex items-center justify-center text-white">
|
||||
<i data-lucide="shield-half" class="w-5 h-5"></i>
|
||||
</div>
|
||||
<span class="text-white font-bold tracking-tight text-lg">DARPA Intelligence</span>
|
||||
</div>
|
||||
<nav class="flex-1 px-4 py-4 space-y-1 overflow-y-auto custom-scrollbar">
|
||||
<div class="text-[10px] font-bold text-slate-500 uppercase tracking-widest px-2 mb-2">主导航</div>
|
||||
<a data-nav-key="dashboard" onclick="showPage('dashboardPage')" class="flex items-center gap-3 px-3 py-2.5 rounded-lg hover:bg-white/5 hover:text-white transition-all text-sm cursor-pointer">
|
||||
<i data-lucide="layout-dashboard" class="w-4 h-4"></i> 工作台
|
||||
</a>
|
||||
<a data-nav-key="search" onclick="showPage('searchPage')" class="flex items-center gap-3 px-3 py-2.5 rounded-lg hover:bg-white/5 hover:text-white transition-all text-sm cursor-pointer">
|
||||
<i data-lucide="search" class="w-4 h-4"></i> 智能检索
|
||||
</a>
|
||||
<a data-nav-key="graph" onclick="showPage('graphPage')" class="flex items-center gap-3 px-3 py-2.5 rounded-lg hover:bg-white/5 hover:text-white transition-all text-sm cursor-pointer">
|
||||
<i data-lucide="share-2" class="w-4 h-4"></i> 可视化分析
|
||||
</a>
|
||||
<a data-nav-key="trend" onclick="showPage('trendPage')" class="flex items-center gap-3 px-3 py-2.5 rounded-lg menu-active text-sm cursor-pointer">
|
||||
<i data-lucide="trending-up" class="w-4 h-4"></i> 趋势分析
|
||||
</a>
|
||||
<a data-nav-key="wiki" onclick="showPage('wikiPage')" class="flex items-center gap-3 px-3 py-2.5 rounded-lg hover:bg-white/5 hover:text-white transition-all text-sm cursor-pointer">
|
||||
<i data-lucide="book-open" class="w-4 h-4"></i> 情报百科
|
||||
</a>
|
||||
<div class="text-[10px] font-bold text-slate-500 uppercase tracking-widest px-2 mt-6 mb-2">数据管理</div>
|
||||
<a data-nav-key="governance" onclick="showPage('governancePage')" class="flex items-center gap-3 px-3 py-2.5 rounded-lg hover:bg-white/5 hover:text-white transition-all text-sm cursor-pointer">
|
||||
<i data-lucide="database" class="w-4 h-4"></i> 数据治理
|
||||
</a>
|
||||
<div class="text-[10px] font-bold text-slate-500 uppercase tracking-widest px-2 mt-6 mb-2">系统</div>
|
||||
<a onclick="logout()" class="flex items-center gap-3 px-3 py-2.5 rounded-lg hover:bg-white/5 hover:text-white transition-all text-sm cursor-pointer">
|
||||
<i data-lucide="log-out" class="w-4 h-4"></i> 退出登录
|
||||
</a>
|
||||
</nav>
|
||||
<div class="p-4 border-t border-white/5 flex items-center gap-3">
|
||||
<div class="sidebar-user-avatar w-8 h-8 rounded-full bg-slate-700 flex items-center justify-center text-slate-300 shrink-0">
|
||||
<i data-lucide="user" class="w-4 h-4"></i>
|
||||
</div>
|
||||
<div class="flex-1 min-w-0">
|
||||
<p class="sidebar-user-name text-xs font-bold text-white truncate">--</p>
|
||||
<p class="sidebar-user-role text-[10px] text-slate-500 truncate">--</p>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<!-- Right content -->
|
||||
<div class="flex-1 flex flex-col bg-slate-50 overflow-y-auto custom-scrollbar">
|
||||
<!-- Header -->
|
||||
<div class="px-8 pt-8 pb-0 shrink-0">
|
||||
<div class="flex items-center justify-between mb-6">
|
||||
<div>
|
||||
<h1 class="text-2xl font-bold text-slate-900 tracking-tight">技术演进与趋势分析</h1>
|
||||
<p class="text-sm text-slate-500 mt-1">
|
||||
数据范围: <span id="intelYearRange" class="font-mono text-slate-700">--</span>
|
||||
| 项目总数: <span id="intelTotalProj" class="font-mono text-slate-700 font-bold">--</span>
|
||||
| 预算总额: <span id="intelTotalBudget" class="font-mono text-slate-700 font-bold">--</span>
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex items-center gap-3">
|
||||
<button onclick="trendInitFallback()" class="flex items-center gap-2 px-4 py-2 bg-slate-100 hover:bg-slate-200 text-slate-600 rounded-lg text-sm font-medium transition-all" title="重新加载数据">
|
||||
<i data-lucide="refresh-cw" class="w-4 h-4"></i> 刷新数据
|
||||
</button>
|
||||
<div onclick="openProfileModal()" class="header-user-avatar w-8 h-8 rounded-full bg-slate-200 flex items-center justify-center cursor-pointer shrink-0 ml-2">
|
||||
<i data-lucide="user" class="w-4 h-4 text-slate-500"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Charts Grid -->
|
||||
<div class="px-8 pb-8">
|
||||
<!-- Row 1 -->
|
||||
<div class="grid grid-cols-2 gap-6 mb-6">
|
||||
<!-- 1. 项目增长趋势 -->
|
||||
<div class="bg-white rounded-2xl p-6 border border-slate-100 shadow-sm hover:shadow-md transition-shadow">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="w-1.5 h-6 bg-primary rounded-full"></div>
|
||||
<h3 class="font-bold text-slate-800">各年代技术领域项目分布</h3>
|
||||
</div>
|
||||
<div class="flex gap-1">
|
||||
<button onclick="changeTrendType('bar')" class="p-1.5 hover:bg-slate-100 rounded text-slate-400 hover:text-primary" title="柱状图"><i data-lucide="bar-chart-3" class="w-4 h-4"></i></button>
|
||||
<button onclick="changeTrendType('line')" class="p-1.5 hover:bg-slate-100 rounded text-slate-400 hover:text-primary" title="折线图"><i data-lucide="line-chart" class="w-4 h-4"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="trendChartMain" style="width:100%;height:300px"></div>
|
||||
</div>
|
||||
|
||||
<!-- 2. 技术领域分布 -->
|
||||
<div class="bg-white rounded-2xl p-6 border border-slate-100 shadow-sm hover:shadow-md transition-shadow">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="w-1.5 h-6 bg-emerald-500 rounded-full"></div>
|
||||
<h3 class="font-bold text-slate-800">关键技术领域分布</h3>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex gap-0">
|
||||
<div id="distChartMain" style="width:55%;height:260px"></div>
|
||||
<div id="distBudgetChart" style="width:45%;height:260px"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 3. 机构合作网络 -->
|
||||
<div class="bg-white rounded-2xl p-6 border border-slate-100 shadow-sm hover:shadow-md transition-shadow">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="w-1.5 h-6 bg-purple-500 rounded-full"></div>
|
||||
<h3 class="font-bold text-slate-800">核心机构合作网络</h3>
|
||||
</div>
|
||||
<span class="text-[10px] text-slate-400">节点大小 ∝ 参与项目数</span>
|
||||
</div>
|
||||
<div id="networkChartMain" style="width:100%;height:300px"></div>
|
||||
</div>
|
||||
|
||||
<!-- 4. 技术关联度排行 -->
|
||||
<div class="bg-white rounded-2xl p-6 border border-slate-100 shadow-sm hover:shadow-md transition-shadow">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="w-1.5 h-6 bg-orange-500 rounded-full"></div>
|
||||
<h3 class="font-bold text-slate-800">技术关联度排行 (Top 15)</h3>
|
||||
</div>
|
||||
<span class="text-[10px] text-slate-400">按关联项目数降序</span>
|
||||
</div>
|
||||
<div id="cloudChartMain" style="width:100%;height:300px"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Row 2: 办公室雷达图 (全宽) -->
|
||||
<div class="mb-6">
|
||||
<div class="bg-white rounded-2xl p-6 border border-slate-100 shadow-sm hover:shadow-md transition-shadow">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="w-1.5 h-6 bg-cyan-500 rounded-full"></div>
|
||||
<h3 class="font-bold text-slate-800">DARPA 六大办公室多维度对比</h3>
|
||||
</div>
|
||||
<span class="text-[10px] text-slate-400">维度已归一化至 0-100 | 悬停查看绝对值</span>
|
||||
</div>
|
||||
<div id="officeRadarChart" style="width:100%;height:400px"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- AI 研判摘要 (动态) -->
|
||||
<div class="bg-white rounded-2xl p-6 border border-slate-100 shadow-sm" style="border-left:4px solid #eab308;background:#fefce8">
|
||||
<div class="flex items-start gap-4">
|
||||
<div class="w-10 h-10 bg-white rounded-full flex items-center justify-center shadow-sm border border-yellow-200 shrink-0">
|
||||
<i data-lucide="sparkles" class="w-5 h-5 text-yellow-500"></i>
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<div class="flex items-center justify-between mb-3">
|
||||
<h4 class="font-bold text-slate-800">AI 智能研判摘要</h4>
|
||||
<span class="text-[10px] text-slate-400">基于 Neo4j 图谱实时生成</span>
|
||||
</div>
|
||||
<div id="aiIntelContent" class="text-sm text-slate-600 leading-relaxed space-y-2">
|
||||
<p class="text-slate-400 italic">数据加载中...</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div><!-- end right wrapper -->
|
||||
</div><!-- end h-screen flex -->
|
||||
</div><!-- end trendPage -->
|
||||
|
||||
<!-- fallback: 如果模块加载失败,提供一个重试手段 -->
|
||||
<script>
|
||||
window.trendInitFallback = function() {
|
||||
if (window.initTrendCharts) {
|
||||
Object.values(window.trendCharts||{}).forEach(c => c && c.dispose && c.dispose());
|
||||
window.trendCharts = {};
|
||||
window.trendInited = false;
|
||||
window.initTrendCharts();
|
||||
}
|
||||
};
|
||||
</script>
|
||||
@ -1,148 +0,0 @@
|
||||
<div id="wikiPage" class="page bg-[#F8FAFC]">
|
||||
<div class="w-full h-screen flex">
|
||||
|
||||
<!-- Sidebar (与 dashboard 一致) -->
|
||||
<aside class="w-64 bg-sidebar text-slate-400 flex flex-col shrink-0">
|
||||
<div class="p-6 flex items-center gap-3">
|
||||
<div class="w-8 h-8 bg-primary rounded flex items-center justify-center text-white">
|
||||
<i data-lucide="shield-half" class="w-5 h-5"></i>
|
||||
</div>
|
||||
<span class="text-white font-bold tracking-tight text-lg">DARPA Intelligence</span>
|
||||
</div>
|
||||
<nav class="flex-1 px-4 py-4 space-y-1 overflow-y-auto custom-scrollbar">
|
||||
<div class="text-[10px] font-bold text-slate-500 uppercase tracking-widest px-2 mb-2">主导航</div>
|
||||
<a data-nav-key="dashboard" onclick="showPage('dashboardPage')" class="flex items-center gap-3 px-3 py-2.5 rounded-lg hover:bg-white/5 hover:text-white transition-all text-sm cursor-pointer">
|
||||
<i data-lucide="layout-dashboard" class="w-4 h-4"></i> 工作台
|
||||
</a>
|
||||
<a data-nav-key="search" onclick="showPage('searchPage')" class="flex items-center gap-3 px-3 py-2.5 rounded-lg hover:bg-white/5 hover:text-white transition-all text-sm cursor-pointer">
|
||||
<i data-lucide="search" class="w-4 h-4"></i> 智能检索
|
||||
</a>
|
||||
<a data-nav-key="graph" onclick="showPage('graphPage')" class="flex items-center gap-3 px-3 py-2.5 rounded-lg hover:bg-white/5 hover:text-white transition-all text-sm cursor-pointer">
|
||||
<i data-lucide="share-2" class="w-4 h-4"></i> 可视化分析
|
||||
</a>
|
||||
<a data-nav-key="trend" onclick="showPage('trendPage')" class="flex items-center gap-3 px-3 py-2.5 rounded-lg hover:bg-white/5 hover:text-white transition-all text-sm cursor-pointer">
|
||||
<i data-lucide="trending-up" class="w-4 h-4"></i> 趋势分析
|
||||
</a>
|
||||
<a data-nav-key="wiki" onclick="showPage('wikiPage')" class="flex items-center gap-3 px-3 py-2.5 rounded-lg menu-active text-sm cursor-pointer">
|
||||
<i data-lucide="book-open" class="w-4 h-4"></i> 情报百科
|
||||
</a>
|
||||
<div class="text-[10px] font-bold text-slate-500 uppercase tracking-widest px-2 mt-6 mb-2">数据管理</div>
|
||||
<a data-nav-key="governance" onclick="showPage('governancePage')" class="flex items-center gap-3 px-3 py-2.5 rounded-lg hover:bg-white/5 hover:text-white transition-all text-sm cursor-pointer">
|
||||
<i data-lucide="database" class="w-4 h-4"></i> 数据治理
|
||||
</a>
|
||||
<a data-nav-key="detail" onclick="showPage('detailPage')" class="flex items-center gap-3 px-3 py-2.5 rounded-lg hover:bg-white/5 hover:text-white transition-all text-sm cursor-pointer">
|
||||
<i data-lucide="file-text" class="w-4 h-4"></i> 项目详情
|
||||
</a>
|
||||
<div class="text-[10px] font-bold text-slate-500 uppercase tracking-widest px-2 mt-6 mb-2">系统</div>
|
||||
<a onclick="logout()" class="flex items-center gap-3 px-3 py-2.5 rounded-lg hover:bg-white/5 hover:text-white transition-all text-sm cursor-pointer">
|
||||
<i data-lucide="log-out" class="w-4 h-4"></i> 退出登录
|
||||
</a>
|
||||
</nav>
|
||||
<div class="p-4 border-t border-white/5 flex items-center gap-3">
|
||||
<div class="sidebar-user-avatar w-8 h-8 rounded-full bg-slate-700 flex items-center justify-center text-slate-300 shrink-0">
|
||||
<i data-lucide="user" class="w-4 h-4"></i>
|
||||
</div>
|
||||
<div class="flex-1 min-w-0">
|
||||
<p class="sidebar-user-name text-xs font-bold text-white truncate">--</p>
|
||||
<p class="sidebar-user-role text-[10px] text-slate-500 truncate">--</p>
|
||||
</div>
|
||||
<button onclick="logout()" class="text-slate-500 hover:text-slate-300 transition-colors" title="退出">
|
||||
<i data-lucide="log-out" class="w-4 h-4"></i>
|
||||
</button>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<!-- Right: header + main -->
|
||||
<div class="flex-1 flex flex-col overflow-y-auto custom-scrollbar bg-[#F8FAFC]">
|
||||
|
||||
<header class="sticky top-0 z-50 px-8 py-4 flex items-center justify-between shrink-0 bg-white/80 backdrop-blur-md border-b border-slate-100">
|
||||
<div class="flex items-center gap-4">
|
||||
<i data-lucide="book-open" class="w-5 h-5 text-primary"></i>
|
||||
<h1 class="text-lg font-bold text-slate-900">情报百科</h1>
|
||||
<span class="text-[10px] text-slate-400 bg-slate-100 px-2 py-0.5 rounded-full">DARPA Intelligence Wiki</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-3">
|
||||
<div onclick="openProfileModal()" class="header-user-avatar w-8 h-8 rounded-full bg-slate-200 flex items-center justify-center cursor-pointer shrink-0">
|
||||
<i data-lucide="user" class="w-4 h-4 text-slate-500"></i>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- Tab Navigation -->
|
||||
<div class="px-8 pt-6 pb-0">
|
||||
<div class="flex gap-1 bg-slate-100 rounded-xl p-1 w-fit" id="wikiTabs">
|
||||
<button data-wiki-tab="tech" onclick="switchWikiTab('tech')" class="px-5 py-2.5 text-sm font-medium rounded-lg transition-all duration-200 bg-white text-slate-900 shadow-sm">
|
||||
<i data-lucide="cpu" class="w-4 h-4 inline mr-1.5"></i>技术图谱
|
||||
</button>
|
||||
<button data-wiki-tab="milestone" onclick="switchWikiTab('milestone')" class="px-5 py-2.5 text-sm font-medium rounded-lg transition-all duration-200 text-slate-500 hover:text-slate-700">
|
||||
<i data-lucide="clock" class="w-4 h-4 inline mr-1.5"></i>里程碑时间线
|
||||
</button>
|
||||
<button data-wiki-tab="office" onclick="switchWikiTab('office')" class="px-5 py-2.5 text-sm font-medium rounded-lg transition-all duration-200 text-slate-500 hover:text-slate-700">
|
||||
<i data-lucide="landmark" class="w-4 h-4 inline mr-1.5"></i>DARPA 办公室
|
||||
</button>
|
||||
<button data-wiki-tab="manager" onclick="switchWikiTab('manager')" class="px-5 py-2.5 text-sm font-medium rounded-lg transition-all duration-200 text-slate-500 hover:text-slate-700">
|
||||
<i data-lucide="users" class="w-4 h-4 inline mr-1.5"></i>关键人物
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Tab Content -->
|
||||
<main class="flex-1 px-8 py-6">
|
||||
<!-- 技术图谱 Tab -->
|
||||
<div id="wikiTabTech" class="wiki-tab-content">
|
||||
<!-- Skeleton -->
|
||||
<div id="wikiTechSkeleton" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-5">
|
||||
<div class="bg-white rounded-xl p-6 border border-slate-100 animate-pulse space-y-3" repeat="9">
|
||||
<div class="h-4 bg-slate-100 rounded w-3/4"></div>
|
||||
<div class="h-3 bg-slate-50 rounded w-full"></div>
|
||||
<div class="h-3 bg-slate-50 rounded w-5/6"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="wikiTechContent" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-5 hidden"></div>
|
||||
<div id="wikiTechError" class="text-center py-16 text-red-400 hidden">数据加载失败,请检查后端服务</div>
|
||||
</div>
|
||||
|
||||
<!-- 里程碑时间线 Tab -->
|
||||
<div id="wikiTabMilestone" class="wiki-tab-content hidden">
|
||||
<div id="wikiMsSkeleton" class="space-y-4">
|
||||
<div class="bg-white rounded-xl p-6 border border-slate-100 animate-pulse space-y-3" repeat="6">
|
||||
<div class="h-4 bg-slate-100 rounded w-1/4"></div>
|
||||
<div class="h-3 bg-slate-50 rounded w-3/4"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="wikiMsContent" class="hidden">
|
||||
<div class="flex flex-wrap gap-2 mb-6" id="wikiMsEraFilter"></div>
|
||||
<div id="wikiMsTimeline" class="space-y-0"></div>
|
||||
</div>
|
||||
<div id="wikiMsError" class="text-center py-16 text-red-400 hidden">数据加载失败</div>
|
||||
</div>
|
||||
|
||||
<!-- DARPA 办公室 Tab -->
|
||||
<div id="wikiTabOffice" class="wiki-tab-content hidden">
|
||||
<div id="wikiOfficeSkeleton" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-5">
|
||||
<div class="bg-white rounded-xl p-6 border border-slate-100 animate-pulse space-y-3" repeat="6">
|
||||
<div class="h-4 bg-slate-100 rounded w-3/4"></div>
|
||||
<div class="h-3 bg-slate-50 rounded w-full"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="wikiOfficeContent" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-5 hidden"></div>
|
||||
<div id="wikiOfficeError" class="text-center py-16 text-red-400 hidden">数据加载失败</div>
|
||||
</div>
|
||||
|
||||
<!-- 关键人物 Tab -->
|
||||
<div id="wikiTabManager" class="wiki-tab-content hidden">
|
||||
<div id="wikiMgrSkeleton" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-5">
|
||||
<div class="bg-white rounded-xl p-6 border border-slate-100 animate-pulse space-y-3" repeat="12">
|
||||
<div class="h-10 w-10 bg-slate-100 rounded-full mx-auto"></div>
|
||||
<div class="h-4 bg-slate-100 rounded w-1/2 mx-auto"></div>
|
||||
<div class="h-3 bg-slate-50 rounded w-3/4 mx-auto"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="wikiMgrContent" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-5 hidden"></div>
|
||||
<div id="wikiMgrError" class="text-center py-16 text-red-400 hidden">数据加载失败</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
</div><!-- end right -->
|
||||
</div><!-- end h-screen flex -->
|
||||
</div><!-- end wikiPage -->
|
||||
@ -1,126 +0,0 @@
|
||||
/* =====================================================================
|
||||
* [GLOBAL] 全局基础样式 — 页面切换骨架
|
||||
* ===================================================================== */
|
||||
body { background-color: #F0F2F5; overflow: hidden; height: 100vh; }
|
||||
.page { display: none; height: 100vh; }
|
||||
.page.active { display: block; }
|
||||
#loginPage.active { display: flex; flex-direction: column; overflow-y: auto; }
|
||||
#dashboardPage.active { display: flex; }
|
||||
#searchPage.active { display: flex; flex-direction: column; }
|
||||
#graphPage.active { display: flex; flex-direction: column; }
|
||||
#trendPage.active { display: flex; flex-direction: column; }
|
||||
#governancePage.active { display: flex; flex-direction: column; }
|
||||
#detailPage.active { display: flex; flex-direction: column; }
|
||||
|
||||
/* [GLOBAL] 公共组件 */
|
||||
.custom-scrollbar::-webkit-scrollbar { width: 4px; }
|
||||
.custom-scrollbar::-webkit-scrollbar-thumb { background: #d1d5db; border-radius: 10px; }
|
||||
.glass-nav { background: rgba(255, 255, 255, 0.8); backdrop-filter: blur(10px); }
|
||||
.menu-active { background: #1890FF !important; color: white !important; }
|
||||
|
||||
/* =====================================================================
|
||||
* [MODULE: login] 登录 / 注册页专属样式
|
||||
* ===================================================================== */
|
||||
.login-page { background: radial-gradient(circle at 50% 50%, #002766 0%, #001529 100%); position: relative; }
|
||||
.login-page::before { content: ""; position: absolute; inset: 0; background-image: url('https://www.transparenttextures.com/patterns/carbon-fibre.png'); opacity: 0.1; pointer-events: none; }
|
||||
.glass-card { background: rgba(0, 21, 41, 0.7); backdrop-filter: blur(20px); border: 1px solid rgba(24, 144, 255, 0.2); box-shadow: 0 25px 50px -12px rgba(0,0,0,0.5); }
|
||||
.input-focus-glow:focus { box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2); border-color: #1890FF; }
|
||||
.error-shake { animation: shake 0.5s cubic-bezier(.36,.07,.19,.97) both; border-color: #ff4d4f !important; }
|
||||
@keyframes shake { 10%,90%{transform:translate3d(-1px,0,0)} 20%,80%{transform:translate3d(2px,0,0)} 30%,50%,70%{transform:translate3d(-4px,0,0)} 40%,60%{transform:translate3d(4px,0,0)} }
|
||||
|
||||
/* =====================================================================
|
||||
* [MODULE: graph] 可视化分析页专属样式
|
||||
* ===================================================================== */
|
||||
.graph-canvas-bg { background-image: radial-gradient(circle at 1px 1px, rgba(0,0,0,0.05) 1px, transparent 0); background-size: 40px 40px; }
|
||||
.glass-panel { background: rgba(255, 255, 255, 0.88); backdrop-filter: blur(12px); border: 1px solid rgba(0, 0, 0, 0.08); }
|
||||
|
||||
/* =====================================================================
|
||||
* [MODULE: detail] 项目详情页专属样式
|
||||
* ===================================================================== */
|
||||
.detail-glass-header { background: rgba(255, 255, 255, 0.85); backdrop-filter: blur(12px); border-bottom: 1px solid rgba(226, 232, 240, 0.8); }
|
||||
.detail-card { background: #fff; border-radius: 16px; border: 1px solid #e2e8f0; box-shadow: 0 1px 3px rgba(0,0,0,0.02), 0 1px 2px rgba(0,0,0,0.04); }
|
||||
.status-tag-in { background: #F6FFED; border: 1px solid #B7EB8F; color: #52C41A; }
|
||||
.detail-timeline-dot::before { content: ''; position: absolute; left: -21px; top: 6px; width: 10px; height: 10px; border-radius: 50%; background: #1890FF; border: 2px solid #F8FAFC; box-shadow: 0 0 0 3px rgba(24,144,255,0.15); }
|
||||
.detail-abstract-fade { background: linear-gradient(to top, rgba(255,255,255,1) 0%, rgba(255,255,255,0) 100%); }
|
||||
.line-clamp-4 { display: -webkit-box; -webkit-line-clamp: 4; -webkit-box-orient: vertical; overflow: hidden; }
|
||||
|
||||
/* =====================================================================
|
||||
* [GLOBAL] 骨架屏加载动画
|
||||
* ===================================================================== */
|
||||
@keyframes skeleton-pulse {
|
||||
0%, 100% { opacity: 1; }
|
||||
50% { opacity: 0.4; }
|
||||
}
|
||||
.skeleton-text {
|
||||
display: inline-block;
|
||||
min-width: 40px;
|
||||
height: 1em;
|
||||
border-radius: 4px;
|
||||
background: #e2e8f0;
|
||||
animation: skeleton-pulse 1.5s ease-in-out infinite;
|
||||
}
|
||||
.skeleton-text-sm {
|
||||
display: inline-block;
|
||||
min-width: 30px;
|
||||
height: 0.75em;
|
||||
border-radius: 3px;
|
||||
background: #e2e8f0;
|
||||
animation: skeleton-pulse 1.5s ease-in-out infinite;
|
||||
}
|
||||
|
||||
/* =====================================================================
|
||||
* [MODULE: profile] 资料弹窗动画 + 头像 hover
|
||||
* ===================================================================== */
|
||||
@keyframes modal-fade-in {
|
||||
from { opacity: 0; }
|
||||
to { opacity: 1; }
|
||||
}
|
||||
@keyframes modal-scale-in {
|
||||
from { opacity: 0; transform: scale(0.92) translateY(8px); }
|
||||
to { opacity: 1; transform: scale(1) translateY(0); }
|
||||
}
|
||||
#profileModal.active {
|
||||
display: flex !important;
|
||||
animation: modal-fade-in 0.2s ease-out;
|
||||
}
|
||||
#profileModal.active .profile-modal-inner {
|
||||
animation: modal-scale-in 0.25s cubic-bezier(0.16, 1, 0.3, 1);
|
||||
}
|
||||
.header-user-avatar {
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
.header-user-avatar:hover {
|
||||
background: #e2e8f0 !important;
|
||||
box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.3);
|
||||
transform: scale(1.05);
|
||||
}
|
||||
.sidebar-user-avatar {
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
.sidebar-user-avatar:hover {
|
||||
cursor: pointer;
|
||||
box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2);
|
||||
}
|
||||
.whitelist-tag {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 2px;
|
||||
padding: 2px 8px;
|
||||
font-size: 11px;
|
||||
font-weight: 500;
|
||||
background: #e0f2fe;
|
||||
color: #0369a1;
|
||||
border-radius: 9999px;
|
||||
transition: all 0.15s;
|
||||
}
|
||||
.whitelist-tag:hover {
|
||||
background: #bae6fd;
|
||||
}
|
||||
.whitelist-tag button {
|
||||
margin-left: 2px;
|
||||
color: #0284c7;
|
||||
cursor: pointer;
|
||||
}
|
||||
.whitelist-tag button:hover {
|
||||
color: #dc2626;
|
||||
}
|
||||
Binary file not shown.
Loading…
Reference in new issue