592 lines
29 KiB
HTML
592 lines
29 KiB
HTML
<!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">C001(C001协议)</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>
|