NAC_Blockchain/gnacs-service/static/index.html

592 lines
29 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>NAC GNACS 资产分类系统 - 管理控制台</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: 'Segoe UI', sans-serif; background: #0a0e1a; color: #e0e6f0; min-height: 100vh; }
.header { background: linear-gradient(135deg, #1a2744 0%, #0d1b3e 100%); padding: 20px 40px; border-bottom: 1px solid #2a3a6e; display: flex; align-items: center; gap: 20px; }
.header h1 { font-size: 22px; color: #4fc3f7; font-weight: 700; }
.header .badge { background: #1e3a5f; color: #64b5f6; padding: 4px 12px; border-radius: 12px; font-size: 12px; border: 1px solid #2a5a9e; }
.nav { background: #0f1829; padding: 0 40px; border-bottom: 1px solid #1e2d4e; display: flex; gap: 0; }
.nav-btn { padding: 14px 24px; cursor: pointer; color: #8899bb; font-size: 14px; border-bottom: 3px solid transparent; transition: all 0.2s; background: none; border-top: none; border-left: none; border-right: none; }
.nav-btn:hover { color: #4fc3f7; }
.nav-btn.active { color: #4fc3f7; border-bottom-color: #4fc3f7; }
.container { padding: 30px 40px; max-width: 1400px; }
.panel { display: none; }
.panel.active { display: block; }
.card { background: #111827; border: 1px solid #1e2d4e; border-radius: 12px; padding: 24px; margin-bottom: 20px; }
.card h2 { color: #4fc3f7; font-size: 16px; margin-bottom: 16px; display: flex; align-items: center; gap: 8px; }
.form-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 16px; margin-bottom: 16px; }
.form-group { display: flex; flex-direction: column; gap: 6px; }
.form-group label { font-size: 12px; color: #8899bb; text-transform: uppercase; letter-spacing: 0.5px; }
.form-group input, .form-group select { background: #0a0e1a; border: 1px solid #2a3a6e; color: #e0e6f0; padding: 10px 14px; border-radius: 8px; font-size: 14px; }
.form-group input:focus, .form-group select:focus { outline: none; border-color: #4fc3f7; }
.btn { padding: 10px 24px; border-radius: 8px; cursor: pointer; font-size: 14px; font-weight: 600; border: none; transition: all 0.2s; }
.btn-primary { background: linear-gradient(135deg, #1565c0, #0d47a1); color: #fff; }
.btn-primary:hover { background: linear-gradient(135deg, #1976d2, #1565c0); }
.btn-success { background: linear-gradient(135deg, #2e7d32, #1b5e20); color: #fff; }
.btn-info { background: linear-gradient(135deg, #0277bd, #01579b); color: #fff; }
.result-box { background: #0a0e1a; border: 1px solid #2a3a6e; border-radius: 8px; padding: 16px; margin-top: 16px; font-family: 'Courier New', monospace; font-size: 13px; max-height: 500px; overflow-y: auto; white-space: pre-wrap; word-break: break-all; color: #a5d6a7; }
.gnacs-display { background: #0a0e1a; border: 1px solid #4fc3f7; border-radius: 8px; padding: 20px; margin-top: 16px; text-align: center; }
.gnacs-code { font-family: 'Courier New', monospace; font-size: 28px; color: #4fc3f7; letter-spacing: 4px; font-weight: 700; word-break: break-all; }
.gnacs-segments { display: grid; grid-template-columns: repeat(6, 1fr); gap: 8px; margin-top: 16px; }
.segment { background: #111827; border: 1px solid #2a3a6e; border-radius: 6px; padding: 8px; text-align: center; }
.segment .val { font-size: 18px; font-weight: 700; color: #4fc3f7; font-family: monospace; }
.segment .lbl { font-size: 10px; color: #8899bb; margin-top: 4px; }
.tree-item { padding: 10px 16px; border: 1px solid #1e2d4e; border-radius: 8px; margin-bottom: 8px; cursor: pointer; transition: all 0.2s; }
.tree-item:hover { border-color: #4fc3f7; background: #111827; }
.tree-item .code { color: #4fc3f7; font-weight: 700; font-size: 13px; }
.tree-item .name { color: #e0e6f0; font-size: 14px; }
.tree-item .meta { color: #8899bb; font-size: 12px; margin-top: 4px; }
.sub-items { margin-left: 24px; margin-top: 8px; display: none; }
.sub-item { padding: 8px 12px; border-left: 2px solid #2a3a6e; margin-bottom: 4px; font-size: 13px; color: #8899bb; }
.badge-acc { padding: 2px 8px; border-radius: 10px; font-size: 11px; font-weight: 600; }
.acc-20 { background: #1a3a5e; color: #4fc3f7; }
.acc-721 { background: #3a1a5e; color: #ce93d8; }
.acc-1155 { background: #1a3a2e; color: #a5d6a7; }
.acc-1400 { background: #3a2a1a; color: #ffcc80; }
.jurisdiction-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 12px; }
.j-card { background: #0a0e1a; border: 1px solid #1e2d4e; border-radius: 8px; padding: 14px; }
.j-card .j-code { font-size: 20px; font-weight: 700; color: #4fc3f7; }
.j-card .j-name { font-size: 13px; color: #e0e6f0; margin-top: 4px; }
.j-card .j-meta { font-size: 11px; color: #8899bb; margin-top: 6px; }
.tier-1 { border-left: 3px solid #4caf50; }
.tier-2 { border-left: 3px solid #ff9800; }
.tier-3 { border-left: 3px solid #f44336; }
.status-ok { color: #4caf50; }
.status-err { color: #f44336; }
.cross-border-alert { background: #1a2a1a; border: 1px solid #4caf50; border-radius: 8px; padding: 16px; margin-top: 12px; }
.cross-border-alert.warning { background: #2a1a0a; border-color: #ff9800; }
.tabs { display: flex; gap: 8px; margin-bottom: 16px; }
.tab { padding: 8px 16px; border-radius: 6px; cursor: pointer; font-size: 13px; background: #0a0e1a; border: 1px solid #2a3a6e; color: #8899bb; }
.tab.active { background: #1565c0; border-color: #1976d2; color: #fff; }
.health-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 16px; }
.health-card { background: #0a0e1a; border: 1px solid #1e2d4e; border-radius: 8px; padding: 16px; text-align: center; }
.health-card .val { font-size: 32px; font-weight: 700; color: #4fc3f7; }
.health-card .lbl { font-size: 12px; color: #8899bb; margin-top: 4px; }
</style>
</head>
<body>
<div class="header">
<h1>🔷 NAC GNACS 资产分类系统</h1>
<span class="badge">v1.0.0</span>
<span class="badge">独立微服务</span>
<span class="badge" id="health-badge" style="margin-left:auto">检查中...</span>
</div>
<div class="nav">
<button class="nav-btn active" onclick="showPanel('dashboard')">仪表盘</button>
<button class="nav-btn" onclick="showPanel('classify')">资产分类树</button>
<button class="nav-btn" onclick="showPanel('encode')">GNACS编码生成</button>
<button class="nav-btn" onclick="showPanel('decode')">编码解析</button>
<button class="nav-btn" onclick="showPanel('jurisdiction')">司法辖区</button>
<button class="nav-btn" onclick="showPanel('compliance')">合规规则查询</button>
</div>
<div class="container">
<!-- 仪表盘 -->
<div id="panel-dashboard" class="panel active">
<div class="card">
<h2>📊 系统状态</h2>
<div class="health-grid">
<div class="health-card"><div class="val" id="stat-classes">-</div><div class="lbl">资产大类</div></div>
<div class="health-card"><div class="val" id="stat-jurisdictions">-</div><div class="lbl">司法辖区</div></div>
<div class="health-card"><div class="val" id="stat-rules">-</div><div class="lbl">合规规则</div></div>
<div class="health-card"><div class="val" id="stat-codes">-</div><div class="lbl">已注册编码</div></div>
</div>
</div>
<div class="card">
<h2>🔗 API端点</h2>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:12px">
<div style="background:#0a0e1a;border:1px solid #2a3a6e;border-radius:8px;padding:12px">
<div style="color:#4fc3f7;font-size:12px;margin-bottom:8px">GET /api/gnacs/classify/tree</div>
<div style="color:#8899bb;font-size:12px">获取完整20大类资产分类树</div>
</div>
<div style="background:#0a0e1a;border:1px solid #2a3a6e;border-radius:8px;padding:12px">
<div style="color:#4fc3f7;font-size:12px;margin-bottom:8px">POST /api/gnacs/encode/generate</div>
<div style="color:#8899bb;font-size:12px">生成48位GNACS编码</div>
</div>
<div style="background:#0a0e1a;border:1px solid #2a3a6e;border-radius:8px;padding:12px">
<div style="color:#4fc3f7;font-size:12px;margin-bottom:8px">GET /api/gnacs/encode/decode/{code}</div>
<div style="color:#8899bb;font-size:12px">解析48位GNACS编码语义</div>
</div>
<div style="background:#0a0e1a;border:1px solid #2a3a6e;border-radius:8px;padding:12px">
<div style="color:#4fc3f7;font-size:12px;margin-bottom:8px">GET /api/gnacs/compliance/query</div>
<div style="color:#8899bb;font-size:12px">查询合规规则(同辖区/跨境)</div>
</div>
<div style="background:#0a0e1a;border:1px solid #2a3a6e;border-radius:8px;padding:12px">
<div style="color:#4fc3f7;font-size:12px;margin-bottom:8px">GET /api/gnacs/jurisdiction/list</div>
<div style="color:#8899bb;font-size:12px">列出60+司法辖区</div>
</div>
<div style="background:#0a0e1a;border:1px solid #2a3a6e;border-radius:8px;padding:12px">
<div style="color:#4fc3f7;font-size:12px;margin-bottom:8px">GET /api/gnacs/jurisdiction/check-cross-border</div>
<div style="color:#8899bb;font-size:12px">判断同辖区/跨境交易</div>
</div>
</div>
<div style="margin-top:16px">
<a href="/api/docs" target="_blank" class="btn btn-info" style="text-decoration:none;display:inline-block">📖 查看完整API文档</a>
</div>
</div>
</div>
<!-- 资产分类树 -->
<div id="panel-classify" class="panel">
<div class="card">
<h2>🌳 20大类资产分类树</h2>
<button class="btn btn-primary" onclick="loadClassTree()">加载分类树</button>
<div id="class-tree" style="margin-top:16px"></div>
</div>
</div>
<!-- GNACS编码生成 -->
<div id="panel-encode" class="panel">
<div class="card">
<h2>⚙️ GNACS 48位编码生成器</h2>
<div class="form-row">
<div class="form-group">
<label>资产大类</label>
<select id="enc-class">
<option value="RE">不动产 (RE)</option>
<option value="FS">金融证券 (FS)</option>
<option value="CM">大宗商品 (CM)</option>
<option value="AT">艺术品与收藏品 (AT)</option>
<option value="IP">知识产权 (IP)</option>
<option value="DA">数字资产 (DA)</option>
<option value="IF">基础设施 (IF)</option>
<option value="NR">自然资源 (NR)</option>
<option value="ER">环境权益 (ER)</option>
<option value="CE">企业权益 (CE)</option>
<option value="DE">债权资产 (DE)</option>
<option value="IA">保险资产 (IA)</option>
<option value="AG">农业资产 (AG)</option>
<option value="TR">交通运输资产 (TR)</option>
<option value="EM">设备与机械 (EM)</option>
<option value="DTA">数据资产 (DTA)</option>
<option value="IB">无形商业资产 (IB)</option>
<option value="SP">体育资产 (SP)</option>
<option value="CE2">文化娱乐资产 (CE2)</option>
<option value="CU">自定义资产 (CU)</option>
</select>
</div>
<div class="form-group">
<label>资产所在辖区</label>
<select id="enc-jurisdiction">
<option value="CN">中国大陆 (CN)</option>
<option value="HK">香港 (HK)</option>
<option value="SG">新加坡 (SG)</option>
<option value="US">美国 (US)</option>
<option value="GB">英国 (GB)</option>
<option value="AE">阿联酋 (AE)</option>
<option value="JP">日本 (JP)</option>
<option value="AU">澳大利亚 (AU)</option>
<option value="EU">欧盟 (EU)</option>
<option value="CA">加拿大 (CA)</option>
<option value="CH">瑞士 (CH)</option>
<option value="KR">韩国 (KR)</option>
<option value="IN">印度 (IN)</option>
<option value="BR">巴西 (BR)</option>
<option value="SA">沙特 (SA)</option>
</select>
</div>
<div class="form-group">
<label>投资者辖区(跨境时填写)</label>
<select id="enc-investor-jurisdiction">
<option value="">同辖区(不填)</option>
<option value="CN">中国大陆 (CN)</option>
<option value="HK">香港 (HK)</option>
<option value="SG">新加坡 (SG)</option>
<option value="US">美国 (US)</option>
<option value="GB">英国 (GB)</option>
<option value="AE">阿联酋 (AE)</option>
<option value="JP">日本 (JP)</option>
<option value="AU">澳大利亚 (AU)</option>
<option value="EU">欧盟 (EU)</option>
<option value="CA">加拿大 (CA)</option>
</select>
</div>
</div>
<div class="form-row">
<div class="form-group">
<label>资产ID</label>
<input type="text" id="enc-asset-id" value="ASSET-TEST-001" />
</div>
<div class="form-group">
<label>资产名称</label>
<input type="text" id="enc-asset-name" value="测试资产" />
</div>
<div class="form-group">
<label>资产估值 (USD)</label>
<input type="number" id="enc-value" value="5000000" />
</div>
<div class="form-group">
<label>流动性等级</label>
<select id="enc-liquidity">
<option value="H">高流动性 (H)</option>
<option value="M" selected>中流动性 (M)</option>
<option value="L">低流动性 (L)</option>
</select>
</div>
<div class="form-group">
<label>托管类型</label>
<select id="enc-custody">
<option value="CUST">CUST传统托管</option>
<option value="NANO">NANO纳米托管</option>
<option value="DIGI">DIGI数字托管</option>
<option value="C001">C001C001协议</option>
</select>
</div>
</div>
<button class="btn btn-success" onclick="generateGNACS()">🔷 生成GNACS编码</button>
<div id="gnacs-result" style="display:none">
<div class="gnacs-display">
<div style="color:#8899bb;font-size:12px;margin-bottom:8px">48位GNACS编码</div>
<div class="gnacs-code" id="gnacs-code-display"></div>
<div style="color:#8899bb;font-size:12px;margin-top:8px" id="gnacs-formatted"></div>
</div>
<div class="gnacs-segments" id="gnacs-segments"></div>
<div class="result-box" id="gnacs-json"></div>
</div>
</div>
</div>
<!-- 编码解析 -->
<div id="panel-decode" class="panel">
<div class="card">
<h2>🔍 GNACS编码解析器</h2>
<div class="form-row">
<div class="form-group" style="grid-column: span 3">
<label>输入48位GNACS编码可带连字符</label>
<input type="text" id="decode-input" placeholder="例940000010210200104110002010100000000..." />
</div>
</div>
<button class="btn btn-info" onclick="decodeGNACS()">🔍 解析编码</button>
<div id="decode-result" class="result-box" style="display:none"></div>
</div>
</div>
<!-- 司法辖区 -->
<div id="panel-jurisdiction" class="panel">
<div class="card">
<h2>🌍 司法辖区注册表</h2>
<div class="form-row">
<div class="form-group">
<label>按监管等级筛选</label>
<select id="j-tier">
<option value="">全部</option>
<option value="1">Tier 1 - 高度成熟</option>
<option value="2">Tier 2 - 中等成熟</option>
<option value="3">Tier 3 - 新兴市场</option>
</select>
</div>
<div class="form-group">
<label>按区域联盟筛选</label>
<select id="j-region">
<option value="">全部</option>
<option value="ASEAN">ASEAN</option>
<option value="GCC">GCC</option>
<option value="EU">EU</option>
<option value="G20">G20</option>
</select>
</div>
</div>
<button class="btn btn-primary" onclick="loadJurisdictions()">加载辖区列表</button>
<div style="margin-top:16px">
<h3 style="color:#8899bb;font-size:14px;margin-bottom:12px">跨境交易检查</h3>
<div class="form-row">
<div class="form-group">
<label>投资者辖区</label>
<input type="text" id="cb-investor" placeholder="如: CN" value="CN" />
</div>
<div class="form-group">
<label>资产辖区</label>
<input type="text" id="cb-asset" placeholder="如: US" value="US" />
</div>
</div>
<button class="btn btn-info" onclick="checkCrossBorder()">检查跨境状态</button>
<div id="cb-result" style="display:none;margin-top:12px"></div>
</div>
<div id="jurisdiction-list" style="margin-top:16px"></div>
</div>
</div>
<!-- 合规规则查询 -->
<div id="panel-compliance" class="panel">
<div class="card">
<h2>⚖️ 合规规则查询(同辖区/跨境双轨)</h2>
<div class="form-row">
<div class="form-group">
<label>资产所在辖区</label>
<input type="text" id="comp-asset-j" value="US" placeholder="如: US" />
</div>
<div class="form-group">
<label>资产大类</label>
<select id="comp-class">
<option value="RE">不动产 (RE)</option>
<option value="FS">金融证券 (FS)</option>
<option value="CM">大宗商品 (CM)</option>
<option value="AT">艺术品 (AT)</option>
<option value="IP">知识产权 (IP)</option>
<option value="DA">数字资产 (DA)</option>
<option value="ER">环境权益 (ER)</option>
<option value="CE">企业权益 (CE)</option>
<option value="DE">债权资产 (DE)</option>
</select>
</div>
<div class="form-group">
<label>投资者辖区(跨境时填写)</label>
<input type="text" id="comp-investor-j" value="CN" placeholder="留空=同辖区" />
</div>
</div>
<button class="btn btn-primary" onclick="queryCompliance()">查询合规规则</button>
<div id="compliance-result" style="display:none;margin-top:16px"></div>
</div>
</div>
</div>
<script>
const API = '';
function showPanel(name) {
document.querySelectorAll('.panel').forEach(p => p.classList.remove('active'));
document.querySelectorAll('.nav-btn').forEach(b => b.classList.remove('active'));
document.getElementById('panel-' + name).classList.add('active');
event.target.classList.add('active');
if (name === 'classify') loadClassTree();
if (name === 'jurisdiction') loadJurisdictions();
}
async function checkHealth() {
try {
const r = await fetch(API + '/api/health');
const d = await r.json();
const badge = document.getElementById('health-badge');
badge.textContent = d.mongo === 'connected' ? '✅ 服务正常' : '⚠️ MongoDB断开';
badge.style.background = d.mongo === 'connected' ? '#1a3a2e' : '#3a1a1a';
badge.style.color = d.mongo === 'connected' ? '#4caf50' : '#f44336';
loadStats();
} catch(e) {
document.getElementById('health-badge').textContent = '❌ 服务离线';
}
}
async function loadStats() {
try {
const [classes, jurisdictions] = await Promise.all([
fetch(API + '/api/gnacs/classify/list').then(r=>r.json()),
fetch(API + '/api/gnacs/jurisdiction/list').then(r=>r.json())
]);
document.getElementById('stat-classes').textContent = classes.total || '-';
document.getElementById('stat-jurisdictions').textContent = jurisdictions.total || '-';
document.getElementById('stat-rules').textContent = '~' + ((classes.total||0) * 5);
document.getElementById('stat-codes').textContent = '0';
} catch(e) {}
}
async function loadClassTree() {
const container = document.getElementById('class-tree');
container.innerHTML = '<div style="color:#8899bb">加载中...</div>';
try {
const r = await fetch(API + '/api/gnacs/classify/tree');
const d = await r.json();
let html = '';
for (const cls of (d.data || [])) {
const accClass = 'acc-' + (cls.acc_standard||'').replace('ACC-','').toLowerCase();
html += `<div class="tree-item" onclick="toggleSubs('${cls.class_code}')">
<div style="display:flex;align-items:center;gap:12px">
<span class="code">${cls.class_code}</span>
<span class="name">${cls.name_cn} / ${cls.name_en}</span>
<span class="badge-acc ${accClass}" style="margin-left:auto">${cls.acc_standard}</span>
</div>
<div class="meta">风险权重: ${cls.risk_weight} | 最低KYC: ${cls.min_kyc_level} | XTZH质押比: ${(cls.xtzh_ratio*100).toFixed(0)}% | ${cls.description}</div>
<div class="sub-items" id="subs-${cls.class_code}">`;
for (const sub of (cls.sub_classes || [])) {
html += `<div class="sub-item">📌 ${sub.sub_code} - ${sub.name_cn} / ${sub.name_en} (KYC≥${sub.min_kyc_level})</div>`;
}
html += `</div></div>`;
}
container.innerHTML = html || '<div style="color:#f44336">无数据,请先初始化数据库</div>';
} catch(e) {
container.innerHTML = `<div style="color:#f44336">加载失败: ${e.message}</div>`;
}
}
function toggleSubs(code) {
const el = document.getElementById('subs-' + code);
if (el) el.style.display = el.style.display === 'block' ? 'none' : 'block';
}
async function generateGNACS() {
const body = {
asset_class: document.getElementById('enc-class').value,
jurisdiction: document.getElementById('enc-jurisdiction').value,
investor_jurisdiction: document.getElementById('enc-investor-jurisdiction').value || null,
asset_id: document.getElementById('enc-asset-id').value,
asset_name: document.getElementById('enc-asset-name').value,
asset_value: parseFloat(document.getElementById('enc-value').value),
liquidity_grade: document.getElementById('enc-liquidity').value,
custody_type: document.getElementById('enc-custody').value
};
try {
const r = await fetch(API + '/api/gnacs/encode/generate', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify(body)
});
const d = await r.json();
if (d.success) {
const code = d.data.gnacs_code;
document.getElementById('gnacs-code-display').textContent = code;
document.getElementById('gnacs-formatted').textContent = d.data.formatted;
document.getElementById('gnacs-json').textContent = JSON.stringify(d.data, null, 2);
// 渲染分段
const segs = d.data.segments;
let segHtml = '';
for (const [k, v] of Object.entries(segs)) {
const parts = k.split('_');
const label = parts.slice(1).join('_');
segHtml += `<div class="segment"><div class="val">${v}</div><div class="lbl">${label}</div></div>`;
}
document.getElementById('gnacs-segments').innerHTML = segHtml;
document.getElementById('gnacs-result').style.display = 'block';
} else {
alert('生成失败: ' + JSON.stringify(d));
}
} catch(e) {
alert('请求失败: ' + e.message);
}
}
async function decodeGNACS() {
const code = document.getElementById('decode-input').value.trim();
if (!code) { alert('请输入GNACS编码'); return; }
try {
const r = await fetch(API + '/api/gnacs/encode/decode/' + encodeURIComponent(code));
const d = await r.json();
const el = document.getElementById('decode-result');
el.style.display = 'block';
if (d.success) {
let html = `<div style="color:#4fc3f7;font-size:14px;margin-bottom:12px">编码: ${d.data.formatted}</div>`;
for (const [k, v] of Object.entries(d.data.decoded)) {
html += `<div style="display:flex;gap:16px;padding:4px 0;border-bottom:1px solid #1e2d4e">
<span style="color:#8899bb;min-width:150px">${k}</span>
<span style="color:#e0e6f0">${v}</span>
</div>`;
}
el.innerHTML = html;
} else {
el.textContent = JSON.stringify(d, null, 2);
}
} catch(e) {
document.getElementById('decode-result').textContent = '请求失败: ' + e.message;
document.getElementById('decode-result').style.display = 'block';
}
}
async function loadJurisdictions() {
const tier = document.getElementById('j-tier').value;
const region = document.getElementById('j-region').value;
let url = API + '/api/gnacs/jurisdiction/list';
const params = [];
if (tier) params.push('tier=' + tier);
if (region) params.push('region=' + region);
if (params.length) url += '?' + params.join('&');
try {
const r = await fetch(url);
const d = await r.json();
let html = `<div style="color:#8899bb;font-size:12px;margin-bottom:12px">共 ${d.total} 个辖区</div><div class="jurisdiction-grid">`;
for (const j of (d.data || [])) {
const tierClass = 'tier-' + (j.tier || 3);
html += `<div class="j-card ${tierClass}">
<div style="display:flex;align-items:center;gap:8px">
<span class="j-code">${j.code}</span>
<span style="font-size:10px;padding:2px 8px;border-radius:10px;background:#1a2a3a;color:#4fc3f7">Tier ${j.tier}</span>
</div>
<div class="j-name">${j.name_cn} / ${j.name_en}</div>
<div class="j-meta">监管机构: ${j.regulator}</div>
<div class="j-meta">货币: ${j.currency} | AML: ${j.aml_level}</div>
<div class="j-meta">FATCA: ${j.fatca?'✅':'❌'} | CRS: ${j.crs?'✅':'❌'} | 外汇管制: ${j.forex_control?'⚠️是':'否'}</div>
${j.regional_alliances && j.regional_alliances.length ? `<div class="j-meta">联盟: ${j.regional_alliances.join(', ')}</div>` : ''}
</div>`;
}
html += '</div>';
document.getElementById('jurisdiction-list').innerHTML = html;
} catch(e) {
document.getElementById('jurisdiction-list').innerHTML = `<div style="color:#f44336">加载失败: ${e.message}</div>`;
}
}
async function checkCrossBorder() {
const investor = document.getElementById('cb-investor').value.trim().toUpperCase();
const asset = document.getElementById('cb-asset').value.trim().toUpperCase();
try {
const r = await fetch(`${API}/api/gnacs/jurisdiction/check-cross-border?investor_jurisdiction=${investor}&asset_jurisdiction=${asset}`);
const d = await r.json();
const el = document.getElementById('cb-result');
el.style.display = 'block';
if (d.success) {
const data = d.data;
const isCross = data.is_cross_border;
el.innerHTML = `<div class="cross-border-alert ${isCross ? 'warning' : ''}">
<div style="font-size:16px;font-weight:700;color:${isCross?'#ff9800':'#4caf50'};margin-bottom:8px">
${isCross ? '⚠️ 跨境交易' : '✅ 同辖区交易'}
${data.regional_alliance ? ` (${data.regional_alliance}区域联盟内)` : ''}
</div>
<div style="color:#e0e6f0">${data.description}</div>
${data.tax_treaty ? `<div style="margin-top:8px;color:#8899bb;font-size:12px">
双边税收协定: ${data.tax_treaty.treaty_code} | 预提税率: ${(data.tax_treaty.reduced_withholding_rate*100).toFixed(0)}%
| 资本利得豁免: ${data.tax_treaty.capital_gains_exempt?'是':'否'}
</div>` : (isCross ? '<div style="margin-top:8px;color:#f44336;font-size:12px">⚠️ 无双边税收协定,可能面临双重征税风险</div>' : '')}
</div>`;
}
} catch(e) {
document.getElementById('cb-result').innerHTML = `<div style="color:#f44336">请求失败: ${e.message}</div>`;
document.getElementById('cb-result').style.display = 'block';
}
}
async function queryCompliance() {
const assetJ = document.getElementById('comp-asset-j').value.trim().toUpperCase();
const cls = document.getElementById('comp-class').value;
const investorJ = document.getElementById('comp-investor-j').value.trim().toUpperCase();
let url = `${API}/api/gnacs/compliance/query?asset_jurisdiction=${assetJ}&asset_class=${cls}`;
if (investorJ && investorJ !== assetJ) url += `&investor_jurisdiction=${investorJ}`;
try {
const r = await fetch(url);
const d = await r.json();
const el = document.getElementById('compliance-result');
el.style.display = 'block';
if (d.success) {
const isCross = d.transaction_type === 'CROSS_BORDER';
let html = `<div style="background:${isCross?'#2a1a0a':'#0a1a0a'};border:1px solid ${isCross?'#ff9800':'#4caf50'};border-radius:8px;padding:16px;margin-bottom:12px">
<div style="font-size:14px;font-weight:700;color:${isCross?'#ff9800':'#4caf50'};margin-bottom:8px">
${isCross ? '⚠️ 跨境交易 - 双重合规规则' : '✅ 同辖区交易 - 单套合规规则'}
</div>`;
const rules = d.data.combined_rules || d.data.rules || {};
html += `<div style="display:grid;grid-template-columns:1fr 1fr;gap:8px">`;
for (const [k, v] of Object.entries(rules)) {
html += `<div style="display:flex;justify-content:space-between;padding:6px 0;border-bottom:1px solid #1e2d4e">
<span style="color:#8899bb;font-size:12px">${k}</span>
<span style="color:#e0e6f0;font-size:12px;font-weight:600">${JSON.stringify(v)}</span>
</div>`;
}
html += `</div></div>`;
html += `<div class="result-box">${JSON.stringify(d.data, null, 2)}</div>`;
el.innerHTML = html;
} else {
el.innerHTML = `<div class="result-box">${JSON.stringify(d, null, 2)}</div>`;
}
} catch(e) {
document.getElementById('compliance-result').innerHTML = `<div style="color:#f44336">请求失败: ${e.message}</div>`;
document.getElementById('compliance-result').style.display = 'block';
}
}
// 初始化
checkHealth();
</script>
</body>
</html>