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

2723 lines
92 KiB
HTML

This file contains ambiguous Unicode characters!

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

<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>企业列表查询 · 接口测试台</title>
<style>
:root {
--paper: #f3efe5;
--card: #fffdf6;
--ink: #1f2a3d;
--ink-soft: #5f6572;
--ink-faint: #9a948a;
--line: #ddd5c2;
--line-strong: #c8bda2;
--seal: #b02e26;
--seal-dark: #8c221c;
--seal-bg: #faeee9;
--green: #2e7d4f;
--green-bg: #e9f3ec;
--amber: #b0741a;
--screen-bg: #111a26;
--screen-line: #233247;
--serif:
"Noto Serif SC", "Source Han Serif SC", "SimSun", Georgia, serif;
--sans: "PingFang SC", "Microsoft YaHei", "Helvetica Neue", sans-serif;
--mono:
"JetBrains Mono", "Cascadia Code", Consolas, "Courier New", monospace;
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
html,
body {
background: var(--paper);
color: var(--ink);
font-family: var(--sans);
font-size: 14px;
line-height: 1.6;
}
body {
background-image:
linear-gradient(rgba(31, 42, 61, 0.035) 1px, transparent 1px),
linear-gradient(90deg, rgba(31, 42, 61, 0.035) 1px, transparent 1px);
background-size: 28px 28px;
min-height: 100vh;
}
.wrap {
max-width: 1240px;
margin: 0 auto;
padding: 28px 24px 48px;
}
.masthead {
text-align: center;
padding: 18px 0 14px;
animation: reveal 0.5s ease both;
}
.mast-top {
display: flex;
justify-content: space-between;
font-family: var(--mono);
font-size: 11px;
letter-spacing: 0.18em;
color: var(--ink-faint);
border-bottom: 1px solid var(--line);
padding-bottom: 8px;
margin-bottom: 22px;
}
.masthead h1 {
font-family: var(--serif);
font-size: 32px;
font-weight: 700;
color: var(--seal);
letter-spacing: 0.14em;
}
.masthead h1 em {
font-style: normal;
font-size: 18px;
color: var(--ink);
letter-spacing: 0.28em;
margin-left: 14px;
font-weight: 400;
}
.endpoint {
margin-top: 14px;
display: inline-flex;
align-items: center;
gap: 10px;
background: var(--card);
border: 1px solid var(--line);
padding: 7px 14px;
font-family: var(--mono);
font-size: 12px;
color: var(--ink-soft);
max-width: 100%;
}
.endpoint .method {
background: var(--seal);
color: #fff;
padding: 1px 8px;
font-weight: 700;
letter-spacing: 0.08em;
}
.endpoint code {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.red-rule {
margin-top: 20px;
border-top: 3px solid var(--seal);
position: relative;
}
.red-rule::after {
content: "";
display: block;
border-top: 1px solid var(--seal);
margin-top: 3px;
}
.grid {
display: grid;
grid-template-columns: 420px 1fr;
gap: 22px;
margin-top: 26px;
align-items: start;
}
@media (max-width: 960px) {
.grid {
grid-template-columns: 1fr;
}
}
.panel {
background: var(--card);
border: 1px solid var(--line);
box-shadow:
0 1px 0 rgba(31, 42, 61, 0.06),
0 10px 26px -18px rgba(31, 42, 61, 0.35);
animation: reveal 0.55s ease both;
margin-bottom: 22px;
}
.panel-hd {
display: flex;
align-items: baseline;
justify-content: space-between;
padding: 13px 18px 11px;
border-bottom: 1px solid var(--line);
flex-wrap: wrap;
gap: 6px;
}
.panel-hd h2 {
font-family: var(--serif);
font-size: 16px;
letter-spacing: 0.22em;
font-weight: 700;
}
.panel-hd h2::before {
content: "▣ ";
color: var(--seal);
font-size: 13px;
}
.panel-hd .tag {
font-family: var(--mono);
font-size: 10px;
letter-spacing: 0.14em;
color: var(--ink-faint);
}
.panel-hd .tag.ok {
color: var(--green);
}
.panel-hd .tag.bad {
color: var(--seal);
}
.panel-bd {
padding: 16px 18px 18px;
}
.field {
margin-bottom: 14px;
}
.field label {
display: flex;
justify-content: space-between;
align-items: baseline;
font-size: 12px;
font-weight: 600;
color: var(--ink);
letter-spacing: 0.06em;
margin-bottom: 5px;
}
.field label .hint {
font-family: var(--mono);
font-size: 10px;
color: var(--ink-faint);
font-weight: 400;
letter-spacing: 0.04em;
}
input[type="text"],
input[type="number"],
input[type="password"],
textarea,
select {
width: 100%;
border: 1px solid var(--line-strong);
background: #fffef9;
color: var(--ink);
font-family: var(--mono);
font-size: 12.5px;
padding: 8px 10px;
outline: none;
transition:
border-color 0.15s,
box-shadow 0.15s;
}
select {
font-family: var(--sans);
}
textarea {
resize: vertical;
min-height: 64px;
word-break: break-all;
line-height: 1.5;
}
input:focus,
textarea:focus,
select:focus {
border-color: var(--seal);
box-shadow: 0 0 0 3px rgba(176, 46, 38, 0.12);
}
.row2 {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 12px;
}
.url-quick {
display: flex;
gap: 8px;
margin-top: 6px;
flex-wrap: wrap;
}
.actions {
display: flex;
gap: 10px;
margin-top: 16px;
flex-wrap: wrap;
}
button {
cursor: pointer;
font-family: var(--sans);
letter-spacing: 0.1em;
transition:
transform 0.12s ease,
box-shadow 0.15s ease,
background 0.15s;
border: none;
}
button:active {
transform: translateY(1px);
}
.btn-primary {
flex: 1 1 auto;
background: var(--seal);
color: #fff;
font-size: 15px;
font-weight: 700;
padding: 11px 18px;
box-shadow: 0 3px 0 var(--seal-dark);
}
.btn-primary:hover {
background: #c1352c;
}
.btn-primary:disabled {
background: #c9a09c;
box-shadow: 0 3px 0 #a98884;
cursor: wait;
}
.btn-ghost {
background: transparent;
border: 1px solid var(--line-strong);
color: var(--ink-soft);
font-size: 12px;
padding: 8px 12px;
}
.btn-ghost:hover {
border-color: var(--seal);
color: var(--seal);
}
.btn-mini {
font-size: 11px;
padding: 3px 10px;
border: 1px solid var(--line-strong);
background: transparent;
color: var(--ink-faint);
letter-spacing: 0.06em;
}
.btn-mini:hover {
border-color: var(--seal);
color: var(--seal);
}
.btn-mini.on {
border-color: var(--seal);
color: #fff;
background: var(--seal);
}
.switch {
display: inline-flex;
align-items: center;
gap: 6px;
font-size: 12px;
color: var(--ink-soft);
cursor: pointer;
user-select: none;
margin-top: 12px;
margin-right: 14px;
}
.switch input {
accent-color: var(--seal);
width: 14px;
height: 14px;
}
.preview {
margin-top: 16px;
border-top: 1px dashed var(--line-strong);
padding-top: 12px;
}
.preview .cap {
font-family: var(--mono);
font-size: 10px;
letter-spacing: 0.16em;
color: var(--ink-faint);
margin-bottom: 6px;
}
.preview pre {
background: var(--screen-bg);
color: #9fb6a8;
font-family: var(--mono);
font-size: 11.5px;
line-height: 1.55;
padding: 10px 12px;
overflow: auto;
max-height: 170px;
border: 1px solid var(--screen-line);
word-break: break-all;
white-space: pre-wrap;
}
.j-key {
color: #8ab8e8;
}
.j-str {
color: #c9d8a8;
}
.j-num {
color: #e8c47c;
}
.note {
margin-top: 14px;
font-size: 11.5px;
color: var(--ink-faint);
line-height: 1.7;
border-left: 3px solid var(--line-strong);
padding-left: 10px;
}
.note b {
color: var(--ink-soft);
font-weight: 600;
}
.statusbar {
display: flex;
gap: 8px;
flex-wrap: wrap;
align-items: center;
padding: 12px 18px;
border-bottom: 1px solid var(--line);
background: linear-gradient(rgba(31, 42, 61, 0.02), transparent);
}
.chip {
font-family: var(--mono);
font-size: 11px;
letter-spacing: 0.06em;
border: 1px solid var(--line-strong);
padding: 3px 9px;
color: var(--ink-soft);
background: #fffef9;
display: inline-flex;
gap: 6px;
align-items: center;
}
.chip b {
font-weight: 700;
color: var(--ink);
}
.chip.ok {
border-color: var(--green);
color: var(--green);
background: var(--green-bg);
}
.chip.bad {
border-color: var(--seal);
color: var(--seal);
background: var(--seal-bg);
}
.chip.mock {
border-color: var(--amber);
color: var(--amber);
background: #faf3e4;
}
.tabs {
display: flex;
gap: 0;
border-bottom: 1px solid var(--line);
padding: 0 18px;
}
.tab {
background: transparent;
font-size: 13px;
letter-spacing: 0.14em;
color: var(--ink-faint);
padding: 10px 14px 9px;
border-bottom: 2px solid transparent;
margin-bottom: -1px;
}
.tab.active {
color: var(--seal);
border-bottom-color: var(--seal);
font-weight: 700;
}
.tab:hover {
color: var(--ink);
}
.viewbar {
margin-left: auto;
display: flex;
gap: 8px;
padding-bottom: 6px;
}
.meta-line {
padding: 10px 18px 0;
font-size: 12px;
color: var(--ink-soft);
}
.meta-line code {
font-family: var(--mono);
font-size: 11px;
background: rgba(31, 42, 61, 0.05);
padding: 1px 6px;
}
.table-wrap {
overflow-x: auto;
padding: 12px 18px 18px;
}
table.ledger {
border-collapse: collapse;
width: 100%;
min-width: 860px;
}
table.ledger th {
font-size: 12px;
font-weight: 700;
color: var(--ink);
text-align: left;
letter-spacing: 0.05em;
border-top: 2px solid var(--ink);
border-bottom: 1px solid var(--ink);
padding: 8px 10px;
background: rgba(31, 42, 61, 0.045);
white-space: nowrap;
vertical-align: top;
}
table.ledger th .code {
display: block;
font-family: var(--mono);
font-size: 9.5px;
color: var(--ink-faint);
font-weight: 400;
letter-spacing: 0.03em;
margin-top: 2px;
}
table.ledger td {
border-bottom: 1px solid var(--line);
padding: 9px 10px;
vertical-align: top;
font-size: 12.5px;
}
table.ledger tbody tr {
animation: rowin 0.3s ease both;
}
table.ledger tbody tr:nth-child(1) {
animation-delay: 0.03s;
}
table.ledger tbody tr:nth-child(2) {
animation-delay: 0.09s;
}
table.ledger tbody tr:nth-child(3) {
animation-delay: 0.15s;
}
table.ledger tbody tr:hover {
background: rgba(176, 46, 38, 0.035);
}
td.mono,
.mono {
font-family: var(--mono);
font-size: 12px;
word-break: break-all;
}
.copyable {
cursor: copy;
border-bottom: 1px dotted var(--line-strong);
}
.copyable:hover {
color: var(--seal);
border-bottom-color: var(--seal);
}
.nsrmc-cell b {
font-size: 13.5px;
letter-spacing: 0.02em;
}
.badge {
display: inline-block;
font-size: 10.5px;
font-family: var(--mono);
padding: 1px 7px;
margin-left: 8px;
vertical-align: 1px;
letter-spacing: 0.05em;
}
.badge.normal {
color: var(--green);
background: var(--green-bg);
border: 1px solid var(--green);
}
.badge.other {
color: var(--amber);
background: #faf3e4;
border: 1px solid var(--amber);
}
.sub {
display: block;
font-family: var(--mono);
font-size: 10.5px;
color: var(--ink-faint);
margin-top: 3px;
}
.map-hint {
color: var(--ink-soft);
font-size: 11px;
}
.empty-row {
text-align: center;
color: var(--ink-faint);
padding: 34px 0;
}
.screen-wrap {
padding: 12px 18px 18px;
}
pre.screen {
background: var(--screen-bg);
border: 1px solid var(--screen-line);
color: #c8d5c5;
font-family: var(--mono);
font-size: 12px;
line-height: 1.65;
padding: 14px 16px;
overflow: auto;
max-height: 480px;
box-shadow: inset 0 0 40px rgba(0, 0, 0, 0.45);
}
.j-key {
color: #8ab8e8;
}
.j-str {
color: #c9d8a8;
}
.j-num {
color: #e8c47c;
}
.j-bool {
color: #d98fd1;
}
.j-null {
color: #6f7d90;
}
.stage {
min-height: 320px;
display: flex;
align-items: center;
justify-content: center;
position: relative;
overflow: hidden;
}
.stamp {
width: 168px;
height: 168px;
border: 3px solid var(--seal);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
gap: 6px;
color: var(--seal);
font-family: var(--serif);
letter-spacing: 0.3em;
text-indent: 0.3em;
font-size: 17px;
font-weight: 700;
transform: rotate(-13deg);
opacity: 0.5;
position: relative;
}
.stamp::before {
content: "";
position: absolute;
inset: 7px;
border: 1px dashed var(--seal);
border-radius: 50%;
}
.stamp small {
font-size: 9px;
font-family: var(--mono);
letter-spacing: 0.2em;
text-indent: 0.2em;
font-weight: 400;
}
.errbox {
margin: 16px 18px 18px;
border: 1px solid var(--seal);
background: var(--seal-bg);
padding: 14px 16px;
animation: reveal 0.3s ease both;
}
.errbox h3 {
font-family: var(--serif);
color: var(--seal);
font-size: 14px;
letter-spacing: 0.14em;
margin-bottom: 6px;
}
.errbox pre {
font-family: var(--mono);
font-size: 11.5px;
color: #7a2a24;
white-space: pre-wrap;
word-break: break-all;
margin-bottom: 10px;
}
.errbox ol {
margin-left: 18px;
font-size: 12.5px;
color: var(--ink-soft);
}
.errbox ol li {
margin: 3px 0;
}
.errbox code {
font-family: var(--mono);
font-size: 11px;
background: #fff;
color: var(--seal-dark);
padding: 1px 6px;
border: 1px solid #e5c8c2;
}
.aes-panel .tag {
max-width: 60%;
text-align: right;
}
.aes-grid {
display: grid;
grid-template-columns: 1fr 1fr 300px;
gap: 20px;
}
.aes-actions {
display: flex;
gap: 8px;
flex-wrap: wrap;
}
textarea[readonly] {
background: #f5f1e6;
color: var(--ink-soft);
}
textarea.err,
input.err {
border-color: var(--seal);
color: var(--seal);
}
@media (max-width: 1100px) {
.aes-grid {
grid-template-columns: 1fr;
}
}
.history .panel-bd {
display: flex;
gap: 10px;
flex-wrap: wrap;
}
.h-item {
border: 1px solid var(--line-strong);
background: #fffef9;
padding: 6px 12px;
font-family: var(--mono);
font-size: 11px;
color: var(--ink-soft);
cursor: pointer;
display: flex;
gap: 8px;
align-items: center;
}
.h-item:hover {
border-color: var(--seal);
color: var(--ink);
}
.h-item .dot {
width: 7px;
height: 7px;
border-radius: 50%;
background: var(--green);
}
.h-item.err .dot {
background: var(--seal);
}
footer {
margin-top: 34px;
border-top: 1px solid var(--line);
padding-top: 14px;
display: flex;
justify-content: space-between;
gap: 16px;
font-size: 11px;
color: var(--ink-faint);
font-family: var(--mono);
letter-spacing: 0.08em;
flex-wrap: wrap;
}
@keyframes reveal {
from {
opacity: 0;
transform: translateY(10px);
}
to {
opacity: 1;
transform: none;
}
}
@keyframes rowin {
from {
opacity: 0;
transform: translateY(6px);
}
to {
opacity: 1;
transform: none;
}
}
.hidden {
display: none !important;
}
/* ---------- 企业体检信息面板 ---------- */
.risk-card {
border: 1px solid var(--line-strong);
background: #fffef9;
padding: 14px 16px;
margin: 12px 18px 0;
}
.risk-title {
font-family: var(--serif);
font-size: 18px;
font-weight: 700;
color: var(--ink);
letter-spacing: 0.04em;
display: flex;
align-items: center;
gap: 10px;
}
.risk-sub {
font-family: var(--mono);
font-size: 11px;
color: var(--ink-soft);
margin-top: 4px;
}
.risk-dl {
display: grid;
grid-template-columns: 130px 1fr;
gap: 6px 12px;
margin-top: 12px;
font-size: 12.5px;
}
.risk-dl dt {
color: var(--ink-faint);
font-family: var(--mono);
font-size: 11px;
}
.risk-dl dd {
color: var(--ink);
word-break: break-all;
}
.risk-dl dd .sub {
display: block;
font-family: var(--mono);
font-size: 10.5px;
color: var(--ink-faint);
margin-top: 2px;
}
.risk-metrics {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 10px;
margin: 14px 18px 4px;
}
.metric {
border: 1px solid var(--line-strong);
background: #fffef9;
padding: 10px 12px;
text-align: center;
}
.metric .k {
font-size: 11px;
color: var(--ink-faint);
letter-spacing: 0.04em;
}
.metric .v {
font-family: var(--mono);
font-size: 22px;
font-weight: 700;
margin-top: 4px;
}
.metric.has .v {
color: var(--seal);
}
.metric.zero .v {
color: var(--green);
}
.risk-empty {
text-align: center;
color: var(--ink-faint);
padding: 30px 0;
font-size: 13px;
}
.risk-section-h {
font-family: var(--serif);
font-size: 13px;
letter-spacing: 0.14em;
color: var(--seal);
margin: 0 18px;
padding: 14px 0 6px;
border-top: 1px dashed var(--line-strong);
}
.risk-list {
margin: 0 18px 16px;
font-size: 12px;
color: var(--ink-soft);
list-style: none;
padding: 0;
}
.risk-list li {
padding: 6px 0;
border-bottom: 1px solid var(--line);
word-break: break-all;
}
.enc-row {
display: flex;
gap: 8px;
margin-top: 8px;
}
.enc-row input {
flex: 1;
min-width: 0;
}
.enc-row .btn-mini {
white-space: nowrap;
flex-shrink: 0;
}
</style>
</head>
<body>
<div class="wrap">
<header class="masthead">
<div class="mast-top">
<span>DINGTAX · TAX-APPOINT · COMPANY PAGE</span
><span>接口联调 · 调试台 NO.DSB-0081</span>
</div>
<h1>企业列表查询<em>接 口 测 试</em></h1>
<div class="endpoint">
<span class="method">POST</span
><code id="endpointEcho"
>https://apit-dsb.dingtax.cn/dsb/tax-appoint/api/dx/third/company/info/page</code
>
</div>
<div class="red-rule"></div>
</header>
<div class="grid">
<div>
<!-- ============ 主接口:企业列表 POST ============ -->
<section class="panel">
<div class="panel-hd">
<h2>企业列表 · 分页查询</h2>
<span class="tag ok"
>POST /dsb/tax-appoint/.../company/info/page</span
>
</div>
<div class="panel-bd">
<div class="field">
<label>接口地址 <span class="hint">api url</span></label>
<input
type="text"
id="apiUrl"
spellcheck="false"
value="https://apit-dsb.dingtax.cn/dsb/tax-appoint/api/dx/third/company/info/page"
/>
<div class="url-quick">
<button type="button" class="btn-mini on" id="btnApp">
公网 apit-dsb.dingtax.cn
</button>
<button type="button" class="btn-mini" id="btnLocal">
本机 :8081
</button>
</div>
</div>
<div class="field">
<label
>访问令牌
<span class="hint">header: token · 公网必填</span></label
>
<input
type="text"
id="authToken"
spellcheck="false"
placeholder="钉钉/企微内页面 sessionStorage 的 getToken 值"
/>
<div class="note" style="margin-top: 8px">
下方「模拟登录」获取的
<b>JWT</b> 会自动填入此处;发送时同时携带 <b>token</b>
<b>Authorization: Bearer</b> 两种头(兼容两种校验)。
联调路径:本机 8081 后端启动后 → 地址切「本机」→ 登录 →
发送。token 只保存在本机浏览器。
</div>
</div>
<div
class="field"
style="
border-top: 1px dashed var(--line-strong);
padding-top: 12px;
"
>
<label
>模拟登录换取 Token
<span class="hint">POST /login · 完整信封</span></label
>
<input
type="text"
id="loginUrl"
spellcheck="false"
value="/pub/api/tax-appoint/dx/third/request/public"
/>
<div class="url-quick">
<button type="button" class="btn-mini on" id="btnLoginProxy">
经代理 /pub/(公网 CORS 403走代理
</button>
<button type="button" class="btn-mini" id="btnLoginDirect">
直连 apit-dsb
</button>
</div>
<div class="row2" style="margin-top: 8px">
<input
type="text"
id="loginUser"
spellcheck="false"
value="admin"
placeholder="username"
/>
<input
type="password"
id="loginPass"
spellcheck="false"
value="admin"
placeholder="password"
/>
</div>
<div class="row2" style="margin-top: 8px">
<input
type="text"
id="loginTag"
spellcheck="false"
value="pad.auth"
placeholder="tag"
/>
<input
type="text"
id="loginPath"
spellcheck="false"
value="/login"
placeholder="map.path"
/>
</div>
<div class="actions" style="margin-top: 10px">
<button class="btn-ghost" id="btnLogin" style="flex: 1">
登录获取 Token
</button>
<button class="btn-ghost" id="btnLoginBody">
查看请求体
</button>
</div>
<div class="signline" id="loginResult">
POST 完整信封 { tag:"pad.auth", map:{ head:{method:"POST"},
path:"/login", query:{}, body:{username, password} } } →
自动提取 data.access_token
填入「访问令牌」。已实测:真实登录成功。
</div>
</div>
<div class="field">
<label
>证件号密文zjhm
<span class="hint">aes encrypted id</span></label
>
<textarea id="zjhm" spellcheck="false">
tdbNRl7N9UQlYNLZWE+aP4nULyPYKfqXFwdjXuwV20g=</textarea
>
</div>
<div class="row2">
<div class="field">
<label>页码 <span class="hint">pageNum</span></label>
<input
type="number"
id="pageNum"
value="1"
min="1"
step="1"
/>
</div>
<div class="field">
<label>每页条数 <span class="hint">pageSize</span></label>
<input
type="number"
id="pageSize"
value="10"
min="1"
max="100"
step="1"
/>
</div>
</div>
<div class="actions">
<button class="btn-primary" id="btnSend">发 送 请 求</button>
<button class="btn-ghost" id="btnSample">文档示例参数</button>
<button class="btn-ghost" id="btnCopyCurl">
复制 curl 命令
</button>
</div>
<label class="switch"
><input type="checkbox" id="maskToggle" checked />
证件号脱敏显示</label
>
<label class="switch"
><input type="checkbox" id="mockToggle" />
离线示例模式(不发真实请求)</label
>
<div class="preview">
<div class="cap">请求报文预览 · REQUEST BODY</div>
<pre id="payloadPreview"></pre>
</div>
</div>
</section>
</div>
<!-- ============ 响应区 ============ -->
<section class="panel">
<div class="panel-hd">
<h2>响应结果</h2>
<span class="tag">RESPONSE</span>
</div>
<div class="statusbar" id="statusBar">
<span class="chip" id="chipHttp">HTTP <b></b></span>
<span class="chip" id="chipTime">耗时 <b></b></span>
<span class="chip" id="chipCode">code <b></b></span>
<span class="chip" id="chipCount">count <b></b></span>
</div>
<div class="tabs">
<button class="tab active" data-tab="ledger">台账视图</button>
<button class="tab" data-tab="json">JSON 原文</button>
<div class="viewbar">
<button class="btn-ghost" id="btnCopyJson">复制 JSON</button>
</div>
</div>
<div class="stage" id="stageEmpty">
<div class="stamp">尚未请求<small>READY · STANDBY</small></div>
</div>
<div id="stageError" class="hidden"></div>
<div id="stageLedger" class="hidden">
<div class="meta-line" id="metaLine"></div>
<div class="table-wrap">
<table class="ledger">
<thead id="ledgerHead"></thead>
<tbody id="ledgerBody"></tbody>
</table>
</div>
</div>
<div id="stageJson" class="screen-wrap hidden">
<pre class="screen" id="jsonScreen"></pre>
</div>
</section>
</div>
<!-- ============ 企业信息 · 体检信息接口 ============ -->
<section class="panel" id="riskPanel">
<div class="panel-hd">
<h2>企业信息 · 体检信息</h2>
<span class="tag ok"
>POST /dsb/tax-appoint/.../company/risk/info</span
>
</div>
<div class="panel-bd">
<div
style="
display: grid;
grid-template-columns: 420px 1fr;
gap: 22px;
align-items: start;
"
>
<!-- 左:参数 -->
<div>
<div class="field">
<label>接口地址 <span class="hint">api url</span></label>
<input
type="text"
id="riskApiUrl"
spellcheck="false"
value="/dsb/tax-appoint/api/dx/third/company/risk/info"
/>
<div class="url-quick">
<button type="button" class="btn-mini on" id="btnRiskLocal">
经代理 :8081推荐
</button>
<button type="button" class="btn-mini" id="btnRiskApp">
公网 apit-dsb
</button>
</div>
</div>
<div class="field">
<label
>访问令牌
<span class="hint">header: token · 选填</span></label
>
<input
type="text"
id="riskToken"
spellcheck="false"
placeholder="留空则自动复用上方「企业列表」令牌"
/>
</div>
<div class="field">
<label
>证件号密文zjhm
<span class="hint">aes encrypted id</span></label
>
<textarea id="riskZjhm" spellcheck="false">
tdbNRl7N9UQlYNLZWE+aP4nULyPYKfqXFwdjXuwV20g=</textarea
>
<div class="enc-row">
<input
type="text"
id="riskZjhmPlain"
spellcheck="false"
value="330724198205310320"
placeholder="明文身份证号,一键加密填入 ↑"
/>
<button type="button" class="btn-mini" id="btnRiskEncZjhm">
加密填入 ↑
</button>
</div>
<div class="actions" style="margin-top: 8px">
<button class="btn-ghost" id="btnRiskFromAes">
从 AES 工具密文填入
</button>
<button class="btn-ghost" id="btnRiskCopyCurl">
复制 curl
</button>
</div>
</div>
<div class="field" style="margin-top: 4px">
<label
>登记序号密文djxh
<span class="hint">aes encrypted djxh</span></label
>
<input
type="text"
id="riskDjxh"
spellcheck="false"
value="5PUZa0cMhGtmVcxGiSTj9jL+vKLQHec0ZI1qleZ6Mus="
/>
<div class="enc-row">
<input
type="text"
id="riskDjxhPlain"
spellcheck="false"
placeholder="明文登记序号,如 1013307000000593860"
/>
<button type="button" class="btn-mini" id="btnRiskEncDjxh">
加密填入 ↑
</button>
</div>
<div class="note" style="margin-top: 8px">
zjhm 与 djxh
<b>均为 AES 密文</b>AES/ECB/PKCS5Padding密钥同下方 AES
工具)。填明文点「加密填入」即可;响应里返回的 djxh
是明文,可直接复制到这里重新加密查询。
</div>
</div>
<div class="actions">
<button class="btn-primary" id="btnRiskSend">
发 送 请 求
</button>
</div>
<div class="preview">
<div class="cap">请求报文预览 · REQUEST BODY</div>
<pre id="riskPayloadPreview"></pre>
</div>
<div class="note">
在浏览器测试台联调请使用「经代理」地址(相对路径
<code>/dsb/...</code>),由调试代理转发至本机
<code>127.0.0.1:8081</code
>,避免浏览器跨域拦截;本机后端需先启动。
</div>
</div>
<!-- 右:响应 -->
<div>
<div class="statusbar" id="riskStatusBar">
<span class="chip" id="riskChipHttp">HTTP <b></b></span>
<span class="chip" id="riskChipTime">耗时 <b></b></span>
<span class="chip" id="riskChipCode">code <b></b></span>
</div>
<div class="tabs">
<button class="tab active" data-risk-tab="detail">
体检详情
</button>
<button class="tab" data-risk-tab="json">JSON 原文</button>
<div class="viewbar">
<button class="btn-ghost" id="btnRiskCopyJson">
复制 JSON
</button>
</div>
</div>
<div class="stage" id="riskStageEmpty">
<div class="stamp">尚未请求<small>READY · STANDBY</small></div>
</div>
<div id="riskStageError" class="hidden"></div>
<div id="riskStageDetail" class="hidden">
<div id="riskDetailBody"></div>
</div>
<div id="riskStageJson" class="screen-wrap hidden">
<pre class="screen" id="riskJsonScreen"></pre>
</div>
</div>
</div>
</div>
</section>
<!-- ============ AES 工具 ============ -->
<section class="panel aes-panel">
<div class="panel-hd">
<h2>AES 加解密工具</h2>
<span class="tag"
>AES/ECB/PKCS5Padding · 与后端 AESUtil 一致 · Base64</span
>
</div>
<div class="panel-bd aes-grid">
<div class="aes-col">
<div class="field">
<label>明文(身份证号) <span class="hint">plain</span></label>
<textarea
id="aesPlain"
spellcheck="false"
style="min-height: 48px"
>330724198205310320</textarea>
</div>
<div class="aes-actions">
<button class="btn-ghost" id="btnAesEnc">加 密 →</button>
<button class="btn-ghost" id="btnFillZjhm">密文填入 zjhm</button>
</div>
<div class="field" style="margin-top: 12px; margin-bottom: 0">
<label
>密文结果
<span class="hint">base64 · 可作接口 zjhm</span></label
>
<textarea
id="aesCipherOut"
readonly
style="min-height: 48px"
></textarea>
</div>
</div>
<div class="aes-col">
<div class="field">
<label>密文zjhm <span class="hint">base64</span></label>
<textarea
id="aesCipherIn"
spellcheck="false"
style="min-height: 48px"
></textarea>
</div>
<div class="aes-actions">
<button class="btn-ghost" id="btnAesDec">解 密 →</button
><button class="btn-ghost" id="btnVerifyZjhm">
公网校验解密
</button>
</div>
<div class="field" style="margin-top: 12px; margin-bottom: 0">
<label>明文结果 <span class="hint">utf-8</span></label>
<textarea
id="aesPlainOut"
readonly
style="min-height: 48px"
></textarea>
</div>
</div>
<div class="aes-side">
<div class="field">
<label>密钥16 位) <span class="hint">key</span></label>
<input
type="text"
id="aesKey"
spellcheck="false"
value="Gi4swf3llyafmsuK"
/>
</div>
<div class="note" style="margin-top: 0">
与后端
<b>AESUtil.encrypt / decrypt</b>
一致:<b>AES/ECB/PKCS5Padding</b>,输出 Base64。 本接口入参
<b>zjhm</b>
即为身份证号密文(已用后端解密接口与文档示例双重验证)。
「公网校验解密」调用 api-cx 的 risk/info/sign/zjhm
token回显明文以核对。
</div>
</div>
</div>
</section>
<!-- ============ 历史 ============ -->
<section class="panel history">
<div class="panel-hd">
<h2>最近请求</h2>
<span class="tag">HISTORY · 点击回填参数</span>
</div>
<div class="panel-bd" id="historyBar">
<span style="font-size: 12px; color: var(--ink-faint)">暂无记录</span>
</div>
</section>
<footer>
<span>企业列表查询 · 接口调试台</span>
<span>数据含敏感字段,仅限联调测试使用,请勿外传</span>
</footer>
</div>
<script>
(function () {
"use strict";
/* ============ AES-128-ECB已对照 FIPS-197 向量与后端 AESUtil 输出验证) ============ */
var AESTool = (function () {
var sbox = new Uint8Array([
0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67,
0x2b, 0xfe, 0xd7, 0xab, 0x76, 0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59,
0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0, 0xb7,
0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1,
0x71, 0xd8, 0x31, 0x15, 0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05,
0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75, 0x09, 0x83,
0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29,
0xe3, 0x2f, 0x84, 0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b,
0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf, 0xd0, 0xef, 0xaa,
0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c,
0x9f, 0xa8, 0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc,
0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2, 0xcd, 0x0c, 0x13, 0xec,
0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19,
0x73, 0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee,
0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb, 0xe0, 0x32, 0x3a, 0x0a, 0x49,
0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79,
0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4,
0xea, 0x65, 0x7a, 0xae, 0x08, 0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6,
0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a, 0x70,
0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9,
0x86, 0xc1, 0x1d, 0x9e, 0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e,
0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf, 0x8c, 0xa1,
0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0,
0x54, 0xbb, 0x16,
]);
var invSbox = new Uint8Array(256);
(function () {
for (var i = 0; i < 256; i++) invSbox[sbox[i]] = i;
})();
var RCON = [1, 2, 4, 8, 16, 32, 64, 128, 27, 54];
function expandKey(keyBytes) {
var rk = new Uint8Array(176),
i = 16,
rconIdx = 0,
j,
t;
rk.set(keyBytes);
while (i < 176) {
t = [rk[i - 4], rk[i - 3], rk[i - 2], rk[i - 1]];
if (i % 16 === 0)
t = [
sbox[t[1]] ^ RCON[rconIdx++],
sbox[t[2]],
sbox[t[3]],
sbox[t[0]],
];
for (j = 0; j < 4; j++) {
rk[i] = rk[i - 16] ^ t[j];
i++;
}
}
return rk;
}
function xtime(a) {
return ((a << 1) ^ (a & 128 ? 27 : 0)) & 255;
}
function mul(a, b) {
var r = 0;
while (b) {
if (b & 1) r ^= a;
a = xtime(a);
b >>= 1;
}
return r & 255;
}
function encryptBlock(b, rk) {
var i, c, r, round;
for (i = 0; i < 16; i++) b[i] ^= rk[i];
for (round = 1; round <= 10; round++) {
for (i = 0; i < 16; i++) b[i] = sbox[b[i]];
for (r = 1; r < 4; r++) {
var row = [b[r], b[r + 4], b[r + 8], b[r + 12]];
for (c = 0; c < 4; c++) b[r + 4 * c] = row[(c + r) % 4];
}
if (round < 10) {
for (c = 0; c < 4; c++) {
var o = 4 * c,
a0 = b[o],
a1 = b[o + 1],
a2 = b[o + 2],
a3 = b[o + 3];
b[o] = mul(a0, 2) ^ mul(a1, 3) ^ a2 ^ a3;
b[o + 1] = a0 ^ mul(a1, 2) ^ mul(a2, 3) ^ a3;
b[o + 2] = a0 ^ a1 ^ mul(a2, 2) ^ mul(a3, 3);
b[o + 3] = mul(a0, 3) ^ a1 ^ a2 ^ mul(a3, 2);
}
}
for (i = 0; i < 16; i++) b[i] ^= rk[16 * round + i];
}
return b;
}
function decryptBlock(b, rk) {
var i, c, r, round;
for (i = 0; i < 16; i++) b[i] ^= rk[160 + i];
for (round = 9; round >= 0; round--) {
if (round < 9) {
for (c = 0; c < 4; c++) {
var o = 4 * c,
a0 = b[o],
a1 = b[o + 1],
a2 = b[o + 2],
a3 = b[o + 3];
b[o] = mul(a0, 14) ^ mul(a1, 11) ^ mul(a2, 13) ^ mul(a3, 9);
b[o + 1] =
mul(a0, 9) ^ mul(a1, 14) ^ mul(a2, 11) ^ mul(a3, 13);
b[o + 2] =
mul(a0, 13) ^ mul(a1, 9) ^ mul(a2, 14) ^ mul(a3, 11);
b[o + 3] =
mul(a0, 11) ^ mul(a1, 13) ^ mul(a2, 9) ^ mul(a3, 14);
}
}
for (r = 1; r < 4; r++) {
var row = [b[r], b[r + 4], b[r + 8], b[r + 12]];
for (c = 0; c < 4; c++) b[r + 4 * c] = row[(c - r + 4) % 4];
}
for (i = 0; i < 16; i++) b[i] = invSbox[b[i]];
for (i = 0; i < 16; i++) b[i] ^= rk[16 * round + i];
}
return b;
}
function b2a(bytes) {
var s = "";
for (var i = 0; i < bytes.length; i++)
s += String.fromCharCode(bytes[i]);
return s;
}
function a2b(str) {
var u = new Uint8Array(str.length);
for (var i = 0; i < str.length; i++) u[i] = str.charCodeAt(i) & 255;
return u;
}
function pkcs7(bytes) {
var pad = 16 - (bytes.length % 16),
out = new Uint8Array(bytes.length + pad);
out.set(bytes);
out.fill(pad, bytes.length);
return out;
}
function encrypt(plainText, keyStr) {
if (keyStr.length !== 16) throw new Error("密钥长度必须为 16 位");
var data = pkcs7(new TextEncoder().encode(plainText));
var rk = expandKey(new TextEncoder().encode(keyStr));
var out = "";
for (var off = 0; off < data.length; off += 16) {
var b = data.slice(off, off + 16);
encryptBlock(b, rk);
out += b2a(b);
}
return btoa(out);
}
function decrypt(b64Text, keyStr) {
if (keyStr.length !== 16) throw new Error("密钥长度必须为 16 位");
var bin = atob(b64Text.trim());
if (bin.length === 0 || bin.length % 16 !== 0)
throw new Error("密文长度不合法(须为 16 字节的整数倍)");
var rk = expandKey(new TextEncoder().encode(keyStr));
var bytes = a2b(bin),
out = [];
for (var off = 0; off < bytes.length; off += 16) {
var b = bytes.slice(off, off + 16);
decryptBlock(b, rk);
out.push(b2a(b));
}
var u8 = a2b(out.join(""));
var pad = u8[u8.length - 1];
if (pad < 1 || pad > 16)
throw new Error("解密失败:填充不合法(密钥错误或密文损坏)");
for (var i = u8.length - pad; i < u8.length; i++)
if (u8[i] !== pad)
throw new Error("解密失败:填充不合法(密钥错误或密文损坏)");
return new TextDecoder().decode(u8.slice(0, u8.length - pad));
}
return { encrypt: encrypt, decrypt: decrypt };
})();
/* ---------- 文档示例响应 ---------- */
var SAMPLE = {
code: "0",
count: 2,
data: [
{
djxh: "10213307000000180035",
nsrsbh: "92330782MA2A57BR7E",
nsrmc: "义乌市万竹贤口腔诊所",
nsrztDm: "03",
zgswjDm: "13307820000",
gdslxDm: "1",
ssdabh: "92330782MA2A57BR7E",
kzztdjlxDm: "1120",
djzclxDm: "411",
fddbrxm: "万竹贤",
fddbrsfzjlxDm: "201",
fddbrsfzjhm: "330725198202084328",
hyDm: "8090",
zfjglxDm: null,
zgswskfjDm: "13307829300",
},
{
djxh: "10213307000000587528",
nsrsbh: "91330782MA7FJX5F12",
nsrmc: "义乌品众口腔门诊部有限公司",
nsrztDm: "03",
zgswjDm: "13307820000",
gdslxDm: "1",
ssdabh: "10213307000000190418",
kzztdjlxDm: "1110",
djzclxDm: "173",
fddbrxm: "万竹贤",
fddbrsfzjlxDm: "201",
fddbrsfzjhm: "330725198202084328",
hyDm: "8425",
zfjglxDm: "0",
zgswskfjDm: "13307829300",
},
],
};
var MAP = {
nsrztDm: { "03": "正常" },
fddbrsfzjlxDm: { 201: "居民身份证" },
djzclxDm: { 411: "个体工商户", 173: "私营有限责任公司" },
};
var CX_BASE = "https://api-cx.dingtax.cn";
var VERIFY_PATH =
"/cx/api-cloudtax/abnormal/report/risk/info/sign/zjhm?zjhm=";
var $ = function (id) {
return document.getElementById(id);
};
var elApiUrl = $("apiUrl"),
elToken = $("authToken"),
elZjhm = $("zjhm"),
elPageNum = $("pageNum"),
elPageSize = $("pageSize");
var elMask = $("maskToggle"),
elMock = $("mockToggle");
var currentResponse = null,
currentMeta = null,
lastPayload = null,
lastLoginBody = null;
var historyList = [];
/* ---------- 通用工具 ---------- */
function esc(s) {
return String(s == null ? "" : s)
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;");
}
function maskId(v) {
v = String(v == null ? "" : v);
if (v.length <= 10) return v;
return v.slice(0, 6) + "********" + v.slice(-4);
}
function highlight(obj) {
var json = JSON.stringify(obj, null, 2)
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;");
return json.replace(
/("(\\u[a-fA-F0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(true|false)\b|\bnull\b|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?)/g,
function (m) {
var cls = "j-num";
if (/^"/.test(m)) cls = /:\s*$/.test(m) ? "j-key" : "j-str";
else if (/^(true|false)$/.test(m)) cls = "j-bool";
else if (m === "null") cls = "j-null";
return '<span class="' + cls + '">' + m + "</span>";
},
);
}
function copyText(text) {
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(text).catch(function () {
legacyCopy(text);
});
} else {
legacyCopy(text);
}
}
function legacyCopy(text) {
var ta = document.createElement("textarea");
ta.value = text;
ta.style.position = "fixed";
ta.style.opacity = "0";
document.body.appendChild(ta);
ta.select();
try {
document.execCommand("copy");
} catch (e) {}
document.body.removeChild(ta);
}
/* ---------- 视图 / 状态 ---------- */
function setChips(meta) {
$("chipHttp").innerHTML =
"HTTP <b>" + esc(meta.http == null ? "—" : meta.http) + "</b>";
$("chipHttp").className =
"chip " +
(typeof meta.http === "number"
? meta.http >= 200 && meta.http < 300
? "ok"
: "bad"
: meta.mock
? "mock"
: "");
$("chipTime").innerHTML =
"耗时 <b>" +
(meta.elapsed == null ? "—" : meta.elapsed + "ms") +
"</b>";
var isGateway =
currentResponse &&
currentResponse.respCode !== undefined &&
currentResponse.code === undefined;
var codeKey = isGateway ? "respCode" : "code";
var code =
currentResponse && currentResponse[codeKey] != null
? currentResponse[codeKey]
: "—";
var ok = code === "0" || code === 0;
$("chipCode").innerHTML = codeKey + " <b>" + esc(code) + "</b>";
$("chipCode").className =
"chip " + (meta.mock ? "mock" : ok ? "ok" : "bad");
var cnt =
currentResponse && currentResponse.count != null
? currentResponse.count
: "—";
$("chipCount").innerHTML = "count <b>" + esc(cnt) + "</b>";
$("chipCount").className = "chip " + (meta.mock ? "mock" : "");
}
function showView(view) {
["stageEmpty", "stageError", "stageLedger", "stageJson"].forEach(
function (id) {
$(id).classList.toggle("hidden", id !== view);
},
);
}
function switchTab(name) {
document.querySelectorAll(".tab").forEach(function (t) {
t.classList.toggle("active", t.dataset.tab === name);
});
showView(name === "ledger" ? "stageLedger" : "stageJson");
}
/* ---------- 请求参数 / 渲染 ---------- */
function buildPayload() {
var pn = parseInt(elPageNum.value, 10);
if (!(pn >= 1)) pn = 1;
var ps = parseInt(elPageSize.value, 10);
if (!(ps >= 1)) ps = 10;
if (ps > 100) ps = 100;
return { zjhm: elZjhm.value.trim(), pageNum: pn, pageSize: ps };
}
function updatePreview() {
$("payloadPreview").innerHTML = highlight(buildPayload());
}
function renderResponse() {
if (!currentResponse) return;
setChips(currentMeta);
$("jsonScreen").innerHTML = highlight(currentResponse);
renderLedger();
switchTab(Array.isArray(currentResponse.data) ? "ledger" : "json");
}
function renderLedger() {
var list =
currentResponse && Array.isArray(currentResponse.data)
? currentResponse.data
: [];
var head = $("ledgerHead"),
body = $("ledgerBody");
var p = lastPayload || buildPayload();
if (currentMeta && currentMeta.mock) {
$("metaLine").innerHTML =
'<span style="color:var(--amber)">◈ 离线示例模式:展示接口文档中的示例响应,未发起网络请求。</span>';
} else {
$("metaLine").innerHTML =
"第 <code>" +
esc(p.pageNum) +
"</code> 页 · 每页 <code>" +
esc(p.pageSize) +
"</code> 条 · 共 <code>" +
esc(currentResponse.count == null ? "?" : currentResponse.count) +
"</code> 条 · 本页返回 <code>" +
list.length +
"</code> 行";
}
if (!list.length) {
head.innerHTML = "";
body.innerHTML =
'<tr><td class="empty-row">查询成功但未返回企业数据data 为空)</td></tr>';
return;
}
head.innerHTML =
"<tr>" +
"<th>纳税人名称<span class='code'>nsrmc</span></th><th>纳税人识别号<span class='code'>nsrsbh</span></th>" +
"<th>登记序号<span class='code'>djxh</span></th><th>法定代表人<span class='code'>fddbrxm / fddbrsfzjhm</span></th>" +
"<th>行业代码<span class='code'>hyDm</span></th><th>登记注册类型<span class='code'>djzclxDm</span></th>" +
"<th>证照登记类型<span class='code'>kzztdjlxDm</span></th><th>主管税务机关 / 科分局<span class='code'>zgswjDm · zgswskfjDm</span></th>" +
"<th>涉税档案编号<span class='code'>ssdabh</span></th><th>其他代码<span class='code'>gdslxDm · zfjglxDm</span></th></tr>";
body.innerHTML = list
.map(function (row) {
var zt = row.nsrztDm,
ztMap = MAP.nsrztDm[zt];
var ztBadge =
'<span class="badge ' +
(zt === "03" ? "normal" : "other") +
'" title="nsrztDm=' +
esc(zt) +
(ztMap ? " " + ztMap : "") +
'">' +
esc(ztMap || zt || "—") +
"</span>";
var idNo = elMask.checked
? maskId(row.fddbrsfzjhm)
: String(row.fddbrsfzjhm == null ? "—" : row.fddbrsfzjhm);
var cert = MAP.fddbrsfzjlxDm[row.fddbrsfzjlxDm];
var zclx = MAP.djzclxDm[row.djzclxDm];
return (
"<tr>" +
'<td class="nsrmc-cell"><b>' +
esc(row.nsrmc || "—") +
"</b>" +
ztBadge +
"</td>" +
'<td class="mono copyable" title="点击复制" data-copy="' +
esc(row.nsrsbh) +
'">' +
esc(row.nsrsbh || "—") +
"</td>" +
'<td class="mono">' +
esc(row.djxh || "—") +
"</td>" +
"<td>" +
esc(row.fddbrxm || "—") +
'<span class="sub" title="' +
esc(row.fddbrsfzjhm || "") +
'">' +
esc(idNo) +
(cert
? ' <span class="map-hint">(' + esc(cert) + ")</span>"
: "") +
"</span></td>" +
'<td class="mono">' +
esc(row.hyDm == null ? "—" : row.hyDm) +
"</td>" +
'<td class="mono">' +
esc(row.djzclxDm == null ? "—" : row.djzclxDm) +
(zclx
? '<span class="sub map-hint">' + esc(zclx) + "</span>"
: "") +
"</td>" +
'<td class="mono">' +
esc(row.kzztdjlxDm == null ? "—" : row.kzztdjlxDm) +
"</td>" +
'<td class="mono">' +
esc(row.zgswjDm == null ? "—" : row.zgswjDm) +
'<span class="sub">' +
esc(row.zgswskfjDm == null ? "—" : row.zgswskfjDm) +
"</span></td>" +
'<td class="mono">' +
esc(row.ssdabh == null ? "—" : row.ssdabh) +
"</td>" +
'<td class="mono">gdslx ' +
esc(row.gdslxDm == null ? "—" : row.gdslxDm) +
'<span class="sub">zfjg ' +
esc(row.zfjglxDm == null ? "null" : row.zfjglxDm) +
"</span></td>" +
"</tr>"
);
})
.join("");
}
function renderFailure(o) {
currentResponse = null;
if (currentMeta) setChips(currentMeta);
var hints = (o.hints || [])
.map(function (h) {
return "<li>" + h + "</li>";
})
.join("");
$("stageError").innerHTML =
'<div class="errbox"><h3>✖ ' +
esc(o.title) +
"</h3><pre>" +
esc(o.detail || "") +
"</pre>" +
(hints ? "<ol>" + hints + "</ol>" : "") +
'<div style="margin-top:10px"><button class="btn-ghost" id="btnViewSample">查看文档示例数据</button></div></div>';
showView("stageError");
$("btnViewSample").addEventListener("click", loadSample);
}
function loadSample() {
currentResponse = JSON.parse(JSON.stringify(SAMPLE));
currentMeta = { http: "—", elapsed: null, mock: true };
renderResponse();
}
function sendRequest() {
if (elMock.checked) {
loadSample();
pushHistory(false, "mock", 0);
return;
}
var url = elApiUrl.value.trim();
if (!url) {
renderFailure({
title: "参数错误",
detail: "接口地址不能为空",
hints: [],
});
return;
}
var payload = buildPayload();
lastPayload = payload;
var btn = $("btnSend");
btn.disabled = true;
btn.textContent = "请求中 …";
var headers = { "Content-Type": "application/json" };
var token = elToken.value.trim();
if (token) {
headers["token"] = token;
headers["Authorization"] = "Bearer " + token;
}
var t0 = performance.now();
var ctrl = "AbortController" in window ? new AbortController() : null;
var timer = ctrl
? setTimeout(function () {
ctrl.abort();
}, 20000)
: null;
fetch(url, {
method: "POST",
headers: headers,
body: JSON.stringify(payload),
signal: ctrl ? ctrl.signal : undefined,
})
.then(function (resp) {
if (timer) clearTimeout(timer);
var elapsed = Math.round(performance.now() - t0);
return resp.text().then(function (text) {
var body;
try {
body = JSON.parse(text);
} catch (e) {
body = { raw: text };
}
if (!resp.ok && body.raw !== undefined) {
currentMeta = { http: resp.status, elapsed: elapsed };
renderFailure({
title:
"HTTP " + resp.status + " · " + (resp.statusText || ""),
detail: text.slice(0, 300) || "(空响应体)",
hints: [
resp.status === 405
? "该路径不接受 POST通常是此域名下该路径为静态资源/未路由到接口,或服务未启动。"
: "后端返回了非 JSON 内容(可能是网关/存储拦截页)。",
],
});
pushHistory(true, resp.status, elapsed);
return;
}
currentResponse = body;
currentMeta = { http: resp.status, elapsed: elapsed };
if (
body &&
body.respCode !== undefined &&
body.respCode !== 0
) {
renderResponse();
pushHistory(true, resp.status, elapsed);
renderFailure({
title: "业务错误 respCode=" + body.respCode,
detail: body.respMsg || "",
hints: [
/没有token/.test(body.respMsg || "")
? "公网网关要求 header 携带 <b>token</b>:在上方「访问令牌」填入钉钉/企微内 sessionStorage 的 getToken 值;或改用「本机 :8081」联调。"
: "",
/token失效|重新登陆/.test(body.respMsg || "")
? "token 已失效:到钉钉/企微内业务页面重新获取 <code>sessionStorage.getToken</code> 后粘贴。"
: "",
resp.status >= 500 &&
/Failed to handle/.test(body.respMsg || "")
? "网关已把请求路由到 /dsb/tax-appoint 服务,只差鉴权——说明地址正确。"
: "",
].filter(Boolean),
});
return;
}
renderResponse();
pushHistory(
!(
resp.ok ||
(body && (body.code === "0" || body.code === 0))
),
resp.status,
elapsed,
);
});
})
.catch(function (err) {
if (timer) clearTimeout(timer);
var elapsed = Math.round(performance.now() - t0);
currentMeta = { http: "ERR", elapsed: elapsed };
var msg =
err && err.name === "AbortError"
? "请求超时20s后端无响应。"
: err && err.message
? err.message
: String(err);
renderFailure({
title: "请求失败",
detail: msg,
hints: /failed to fetch|networkerror|load failed|abort/i.test(
msg,
)
? [
"可能原因目标域名不提供此接口、服务未启动或浏览器跨域限制curl/Postman 不受跨域影响,可用来排除浏览器因素)。",
]
: [],
});
pushHistory(true, "ERR", elapsed);
})
.finally(function () {
btn.disabled = false;
btn.textContent = "发 送 请 求";
});
}
/* ---------- 历史 ---------- */
function pushHistory(isErr, http, elapsed) {
var p = buildPayload();
historyList.unshift({
time: new Date().toLocaleTimeString("zh-CN", { hour12: false }),
err: isErr,
http: http,
elapsed: elapsed,
p: p.pageNum,
ps: p.pageSize,
zjhm: p.zjhm.slice(0, 8) + "…",
});
if (historyList.length > 8) historyList.pop();
var bar = $("historyBar");
bar.innerHTML = historyList
.map(function (h, i) {
return (
'<button class="h-item' +
(h.err ? " err" : "") +
'" data-idx="' +
i +
'">' +
'<span class="dot"></span>' +
esc(h.time) +
"<span>" +
esc(String(h.http)) +
(h.err ? "" : " · " + esc(String(h.elapsed)) + "ms") +
"</span>" +
"<span>p" +
esc(h.p) +
"/n" +
esc(h.ps) +
"</span><span>" +
esc(h.zjhm) +
"</span></button>"
);
})
.join("");
}
/* ---------- 事件 ---------- */
$("btnSend").addEventListener("click", sendRequest);
$("btnSample").addEventListener("click", function () {
elZjhm.value = "tdbNRl7N9UQlYNLZWE+aP4nULyPYKfqXFwdjXuwV20g=";
elPageNum.value = 1;
elPageSize.value = 10;
updatePreview();
});
$("btnCopyCurl").addEventListener("click", function () {
var p = buildPayload();
var parts = [
'curl.exe -X POST "' + elApiUrl.value.trim() + '"',
'-H "Content-Type: application/json"',
];
var token = elToken.value.trim();
if (token) {
parts.push('-H "token: ' + token + '"');
parts.push('-H "Authorization: Bearer ' + token + '"');
}
parts.push("-d '" + JSON.stringify(p) + "'");
copyText(parts.join(" "));
var btn = $("btnCopyCurl"),
old = btn.textContent;
btn.textContent = "已复制 ✓";
setTimeout(function () {
btn.textContent = "复制 curl 命令";
}, 1200);
});
function markQuick(id) {
["btnApp", "btnLocal"].forEach(function (b) {
$(b).classList.toggle("on", b === id);
});
}
$("btnApp").addEventListener("click", function () {
elApiUrl.value =
"https://apit-dsb.dingtax.cn/dsb/tax-appoint/api/dx/third/company/info/page";
markQuick("btnApp");
$("endpointEcho").textContent = elApiUrl.value;
});
$("btnLocal").addEventListener("click", function () {
elApiUrl.value =
"http://127.0.0.1:8081/dsb/tax-appoint/api/dx/third/company/info/page";
markQuick("btnLocal");
$("endpointEcho").textContent = elApiUrl.value;
});
$("btnCopyJson").addEventListener("click", function () {
if (currentResponse)
copyText(JSON.stringify(currentResponse, null, 2));
});
document.querySelectorAll(".tab").forEach(function (t) {
t.addEventListener("click", function () {
if (!currentResponse) return;
switchTab(t.dataset.tab);
});
});
elMask.addEventListener("change", function () {
if (currentResponse) renderLedger();
});
[elZjhm, elPageNum, elPageSize].forEach(function (el) {
el.addEventListener("input", updatePreview);
});
document.addEventListener("keydown", function (e) {
if ((e.ctrlKey || e.metaKey) && e.key === "Enter") {
e.preventDefault();
sendRequest();
}
});
$("ledgerBody").addEventListener("click", function (e) {
var td = e.target.closest("td.copyable");
if (td && td.dataset.copy) {
copyText(td.dataset.copy);
td.title = "已复制 ✓";
setTimeout(function () {
td.title = "点击复制";
}, 900);
}
});
/* token 持久化 */
try {
elToken.value = localStorage.getItem("cxDebugToken") || "";
} catch (e) {}
elToken.addEventListener("change", function () {
try {
localStorage.setItem("cxDebugToken", elToken.value.trim());
} catch (e) {}
});
/* ---------- 模拟登录换取 Token ---------- */
function deepFindToken(obj) {
var ok = {
token: 1,
access_token: 1,
accesstoken: 1,
tokenvalue: 1,
gettoken: 1,
auth_token: 1,
authtoken: 1,
};
var queue = [obj];
while (queue.length) {
var cur = queue.shift();
if (!cur || typeof cur !== "object") continue;
for (var k in cur) {
if (!Object.prototype.hasOwnProperty.call(cur, k)) continue;
var v = cur[k];
if (typeof v === "string" && v && ok[k.toLowerCase()]) return v;
if (v && typeof v === "object") queue.push(v);
}
}
return null;
}
function buildLoginBody() {
return {
tag: $("loginTag").value.trim(),
map: {
head: { method: "POST" },
path: $("loginPath").value.trim() || "/login",
query: {},
body: {
username: $("loginUser").value.trim(),
password: $("loginPass").value,
},
},
};
}
$("btnLoginBody").addEventListener("click", function () {
lastLoginBody = buildLoginBody();
currentResponse = lastLoginBody;
currentMeta = { http: "—", elapsed: null, mock: true };
setChips(currentMeta);
$("jsonScreen").innerHTML = highlight(lastLoginBody);
$("metaLine").innerHTML =
"登录请求体预览(完整信封)· 复制后可直接用于 Postman 等工具调试";
switchTab("json");
});
$("btnLoginProxy").addEventListener("click", function () {
$("loginUrl").value = "/pub/api/tax-appoint/dx/third/request/public";
$("btnLoginProxy").classList.add("on");
$("btnLoginDirect").classList.remove("on");
});
$("btnLoginDirect").addEventListener("click", function () {
$("loginUrl").value =
"https://apit-dsb.dingtax.cn/dsb/api/tax-appoint/dx/third/request/public";
$("btnLoginDirect").classList.add("on");
$("btnLoginProxy").classList.remove("on");
});
var LOGIN_PROXY_PATH = "/pub/api/tax-appoint/dx/third/request/public";
var LOGIN_PROXY_ABS = "http://localhost:9527" + LOGIN_PROXY_PATH;
function isFailedFetch(err) {
return (
err &&
(err.name === "TypeError" ||
/failed to fetch|networkerror|load failed/i.test(
String(err.message || err)
))
);
}
function useLoginProxy(url) {
$("loginUrl").value = url;
$("btnLoginProxy").classList.add("on");
$("btnLoginDirect").classList.remove("on");
}
$("btnLogin").addEventListener("click", function () {
var btn = $("btnLogin"),
res = $("loginResult");
var url = $("loginUrl").value.trim();
if (!url) {
res.innerHTML =
"<span style='color:var(--seal)'>请先填入登录接口地址(等对方发来后粘贴到上方输入框)</span>";
return;
}
var body = buildLoginBody();
lastLoginBody = body;
btn.disabled = true;
res.textContent = "登录中 …";
var t0 = performance.now();
function loginPost(target) {
return fetch(target, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(body),
});
}
loginPost(url)
.catch(function (e) {
if (!isFailedFetch(e)) throw e;
if (url === LOGIN_PROXY_PATH || url.indexOf(LOGIN_PROXY_PATH) !== -1) {
throw e;
}
useLoginProxy(LOGIN_PROXY_PATH);
res.textContent = "直连失败,改走代理重试 …";
return loginPost(LOGIN_PROXY_PATH);
})
.catch(function (e) {
if (!isFailedFetch(e)) throw e;
if (url === LOGIN_PROXY_ABS) throw e;
useLoginProxy(LOGIN_PROXY_PATH);
res.textContent = "相对路径失败,改走 http://localhost:9527 代理重试 …";
return loginPost(LOGIN_PROXY_ABS);
})
.then(function (r) {
return r.text().then(function (text) {
var elapsed = Math.round(performance.now() - t0);
var parsed;
try {
parsed = JSON.parse(text);
} catch (e) {
parsed = { raw: text };
}
currentResponse = parsed;
currentMeta = { http: r.status, elapsed: elapsed };
setChips(currentMeta);
$("jsonScreen").innerHTML = highlight(parsed);
var tk = deepFindToken(parsed);
if (tk) {
elToken.value = tk;
try {
localStorage.setItem("cxDebugToken", tk);
} catch (e) {}
res.innerHTML =
"✓ " +
elapsed +
"ms · 已提取 token 并填入「访问令牌」:<b>" +
esc(tk.slice(0, 16)) +
"…</b>,现在可直接发送企业列表请求。";
switchTab("json");
} else {
res.innerHTML =
"<span style='color:var(--seal)'>✖ 登录响应中未找到 token 字段HTTP " +
esc(r.status) +
",原文见右侧 JSON 原文)</span>";
switchTab("json");
}
});
})
.catch(function (e) {
res.innerHTML =
"<span style='color:var(--seal)'>✖ 登录请求失败:" +
esc(e && e.message ? e.message : e) +
"。请先在 docs/company-api-test 执行 <b>node proxy.js</b>,再用浏览器打开 <b>http://localhost:9527</b> 后登录。</span>";
})
.finally(function () {
btn.disabled = false;
});
});
/* ---------- AES 工具 ---------- */
function aesSetOut(el, text, isErr) {
el.value = text;
el.classList.toggle("err", !!isErr);
}
$("btnAesEnc").addEventListener("click", function () {
var out = $("aesCipherOut");
var plain = $("aesPlain").value;
if (!plain.trim()) {
aesSetOut(out, "请先输入明文", true);
return;
}
try {
aesSetOut(out, AESTool.encrypt(plain, $("aesKey").value), false);
aesSetOut($("aesPlainOut"), "", false);
} catch (e) {
aesSetOut(out, "错误:" + e.message, true);
}
});
$("btnAesDec").addEventListener("click", function () {
var out = $("aesPlainOut");
var cipher = $("aesCipherIn").value;
if (!cipher.trim()) {
aesSetOut(out, "请先输入密文", true);
return;
}
try {
aesSetOut(out, AESTool.decrypt(cipher, $("aesKey").value), false);
aesSetOut($("aesCipherOut"), "", false);
} catch (e) {
aesSetOut(out, "错误:" + e.message, true);
}
});
$("btnVerifyZjhm").addEventListener("click", function () {
var out = $("aesPlainOut");
var cipher = $("aesCipherIn").value.trim();
if (!cipher) {
aesSetOut(out, "请先输入密文", true);
return;
}
aesSetOut(out, "公网校验中 …", false);
fetch(CX_BASE + VERIFY_PATH + encodeURIComponent(cipher))
.then(function (r) {
return r.json();
})
.then(function (body) {
if (body && body.respCode === 0 && body.datas != null) {
aesSetOut(out, String(body.datas), false);
} else {
aesSetOut(
out,
"校验失败:" + ((body && body.respMsg) || "未知响应"),
true,
);
}
})
.catch(function (e) {
aesSetOut(out, "校验请求失败:" + e.message, true);
});
});
$("btnFillZjhm").addEventListener("click", function () {
var v = $("aesCipherOut").value.trim();
if (!v || $("aesCipherOut").classList.contains("err")) return;
elZjhm.value = v;
updatePreview();
});
$("endpointEcho").textContent = elApiUrl.value;
elApiUrl.addEventListener("change", function () {
$("endpointEcho").textContent = elApiUrl.value;
});
/* ============ 企业体检信息接口 ============ */
var currentRiskResponse = null,
currentRiskMeta = null;
function riskBuildPayload() {
return {
zjhm: $("riskZjhm").value.trim(),
djxh: $("riskDjxh").value.trim(),
};
}
function riskUpdatePreview() {
$("riskPayloadPreview").innerHTML = highlight(riskBuildPayload());
}
function riskShowView(v) {
[
"riskStageEmpty",
"riskStageError",
"riskStageDetail",
"riskStageJson",
].forEach(function (id) {
$(id).classList.toggle("hidden", id !== v);
});
}
function riskSwitchTab(name) {
document.querySelectorAll("[data-risk-tab]").forEach(function (t) {
t.classList.toggle("active", t.dataset.riskTab === name);
});
riskShowView(name === "detail" ? "riskStageDetail" : "riskStageJson");
}
function riskSetChips() {
var ok =
currentRiskResponse &&
(currentRiskResponse.code === "0" ||
currentRiskResponse.code === 0);
$("riskChipHttp").innerHTML =
"HTTP <b>" +
esc(currentRiskMeta.http == null ? "—" : currentRiskMeta.http) +
"</b>";
$("riskChipHttp").className =
"chip " +
(typeof currentRiskMeta.http === "number"
? currentRiskMeta.http >= 200 && currentRiskMeta.http < 300
? "ok"
: "bad"
: "");
$("riskChipTime").innerHTML =
"耗时 <b>" +
(currentRiskMeta.elapsed == null
? "—"
: currentRiskMeta.elapsed + "ms") +
"</b>";
$("riskChipCode").innerHTML =
"code <b>" +
esc(
currentRiskResponse && currentRiskResponse.code != null
? currentRiskResponse.code
: "—",
) +
"</b>";
$("riskChipCode").className = "chip " + (ok ? "ok" : "bad");
}
function riskItemText(x) {
if (typeof x === "string") return x;
if (!x || typeof x !== "object") return String(x);
var parts = [];
Object.keys(x).forEach(function (k) {
var v = x[k];
if (v == null) return;
if (typeof v === "string" || typeof v === "number") {
if (String(v).trim() !== "") parts.push(k + ": " + v);
} else if (Array.isArray(v)) {
parts.push(k + ": " + v.length + " 项");
}
});
return parts.join(" · ") || JSON.stringify(x);
}
function renderRiskDetail() {
var d = currentRiskResponse && currentRiskResponse.data;
if (!d) {
$("riskDetailBody").innerHTML =
'<div class="risk-empty">未返回 datacode=' +
esc(currentRiskResponse ? currentRiskResponse.code : "—") +
"</div>";
return;
}
var nsr = d.nsrxx || {};
var zt = nsr.nsrztmc || "未知",
ztDm = nsr.nsrztDm;
var ztBadge =
'<span class="badge ' +
(ztDm === "03" ? "normal" : "other") +
'">' +
esc(zt) +
"</span>";
function row(k, v, sub) {
return (
"<dt>" +
esc(k) +
"</dt><dd>" +
(v == null || v === "" ? "—" : esc(v)) +
(sub ? '<span class="sub">' + esc(sub) + "</span>" : "") +
"</dd>"
);
}
var dl =
'<div class="risk-card">' +
'<div class="risk-title">' +
esc(nsr.nsrmc || "—") +
ztBadge +
"</div>" +
'<div class="risk-sub">纳税人识别号 / 社会信用代码 ' +
esc(nsr.shxydm || nsr.nsrsbh || "—") +
"</div>" +
'<dl class="risk-dl">' +
row("登记序号 djxh", nsr.djxh) +
row("法定代表人", nsr.fddbrxm) +
row("登记日期", nsr.djrq) +
row("登记注册类型", nsr.djzclxmc) +
row("纳税人类型", nsr.nsrlx) +
row("纳税信用等级", nsr.nsxydj) +
row("生产经营地址", nsr.scjydz) +
row(
"主管税务机关",
nsr.zgswjDm,
"科分局 " + esc(nsr.zgswskfjDm || "—"),
) +
"</dl></div>";
var lists = [
["wfwzxxList", "违法违章信息"],
["sfzwsbList", "税费未申报"],
["sfqjxxList", "欠税信息"],
["cwbbwsbList", "财务报表未申报"],
];
var metrics = '<div class="risk-metrics">';
lists.forEach(function (it) {
var arr = d[it[0]];
var n = Array.isArray(arr) ? arr.length : arr == null ? 0 : 1;
metrics +=
'<div class="metric ' +
(n > 0 ? "has" : "zero") +
'"><div class="k">' +
esc(it[1]) +
'</div><div class="v">' +
n +
"</div></div>";
});
metrics += "</div>";
var detail = "";
lists.forEach(function (it) {
var arr = d[it[0]];
if (Array.isArray(arr) && arr.length) {
detail +=
'<div class="risk-section-h">' +
esc(it[1]) +
"" +
arr.length +
'</div><ul class="risk-list">';
arr.forEach(function (x) {
detail += "<li>" + esc(riskItemText(x)) + "</li>";
});
detail += "</ul>";
}
});
$("riskDetailBody").innerHTML = dl + metrics + detail;
}
function riskRenderFailure(title, detail, hints) {
var hs = (hints || [])
.map(function (h) {
return "<li>" + h + "</li>";
})
.join("");
$("riskStageError").innerHTML =
'<div class="errbox"><h3>✖ ' +
esc(title) +
"</h3><pre>" +
esc(detail) +
"</pre>" +
(hs ? "<ol>" + hs + "</ol>" : "") +
"</div>";
riskShowView("riskStageError");
}
function riskSend() {
var url = $("riskApiUrl").value.trim();
if (!url) {
riskRenderFailure("参数错误", "接口地址不能为空", []);
return;
}
var payload = riskBuildPayload();
if (!payload.zjhm || !payload.djxh) {
riskRenderFailure("参数错误", "zjhm 与 djxh 均不能为空", []);
return;
}
var btn = $("btnRiskSend");
btn.disabled = true;
btn.textContent = "请求中 …";
var headers = { "Content-Type": "application/json" };
var token = $("riskToken").value.trim() || elToken.value.trim();
if (token) {
headers.token = token;
headers["Authorization"] = "Bearer " + token;
}
var t0 = performance.now();
var ctrl = "AbortController" in window ? new AbortController() : null;
var timer = ctrl
? setTimeout(function () {
ctrl.abort();
}, 20000)
: null;
fetch(url, {
method: "POST",
headers: headers,
body: JSON.stringify(payload),
signal: ctrl ? ctrl.signal : undefined,
})
.then(function (resp) {
if (timer) clearTimeout(timer);
var elapsed = Math.round(performance.now() - t0);
return resp.text().then(function (text) {
var body;
try {
body = JSON.parse(text);
} catch (e) {
body = { raw: text };
}
if (!resp.ok && body.raw !== undefined) {
currentRiskResponse = null;
currentRiskMeta = { http: resp.status, elapsed: elapsed };
riskSetChips();
riskRenderFailure(
"HTTP " + resp.status + " · " + (resp.statusText || ""),
text.slice(0, 300) || "(空响应体)",
[
resp.status === 405
? "该路径不接受 POST服务未启动公网直连被跨域拦截——改用「经代理」地址。"
: "后端返回了非 JSON 内容(网关/拦截页)。",
],
);
return;
}
currentRiskResponse = body;
currentRiskMeta = { http: resp.status, elapsed: elapsed };
riskSetChips();
$("riskJsonScreen").innerHTML = highlight(body);
renderRiskDetail();
riskSwitchTab("detail");
});
})
.catch(function (err) {
if (timer) clearTimeout(timer);
var elapsed = Math.round(performance.now() - t0);
currentRiskResponse = null;
currentRiskMeta = { http: "ERR", elapsed: elapsed };
riskSetChips();
var msg =
err && err.name === "AbortError"
? "请求超时20s后端无响应。"
: (err && err.message) || String(err);
riskRenderFailure(
"请求失败",
msg,
/failed to fetch|networkerror|load failed|abort/i.test(msg)
? [
"目标不可达、服务未启动或浏览器跨域限制(用「经代理」地址重试)。",
]
: [],
);
})
.finally(function () {
btn.disabled = false;
btn.textContent = "发 送 请 求";
});
}
/* 事件 */
$("btnRiskSend").addEventListener("click", riskSend);
function riskEncFill(plainId, cipherId, btnId) {
var plain = $(plainId).value.trim();
if (!plain) {
$(plainId).focus();
return;
}
var btn = $(btnId),
old = btn.textContent;
try {
$(cipherId).value = AESTool.encrypt(plain, $("aesKey").value);
riskUpdatePreview();
btn.textContent = "已填入 ✓";
setTimeout(function () {
btn.textContent = old;
}, 1200);
} catch (e) {
btn.textContent = "✖ " + e.message;
setTimeout(function () {
btn.textContent = old;
}, 2200);
}
}
$("btnRiskEncZjhm").addEventListener("click", function () {
riskEncFill("riskZjhmPlain", "riskZjhm", "btnRiskEncZjhm");
});
$("btnRiskEncDjxh").addEventListener("click", function () {
riskEncFill("riskDjxhPlain", "riskDjxh", "btnRiskEncDjxh");
});
$("btnRiskFromAes").addEventListener("click", function () {
var v = $("aesCipherOut").value.trim();
if (v && !$("aesCipherOut").classList.contains("err")) {
$("riskZjhm").value = v;
riskUpdatePreview();
}
});
$("btnRiskCopyCurl").addEventListener("click", function () {
var p = riskBuildPayload();
var parts = [
'curl.exe -X POST "' + $("riskApiUrl").value.trim() + '"',
'-H "Content-Type: application/json"',
];
var token = $("riskToken").value.trim() || elToken.value.trim();
if (token) {
parts.push('-H "token: ' + token + '"');
parts.push('-H "Authorization: Bearer ' + token + '"');
}
parts.push("-d '" + JSON.stringify(p) + "'");
copyText(parts.join(" "));
var b = $("btnRiskCopyCurl"),
o = b.textContent;
b.textContent = "已复制 ✓";
setTimeout(function () {
b.textContent = "复制 curl";
}, 1200);
});
$("btnRiskCopyJson").addEventListener("click", function () {
if (currentRiskResponse)
copyText(JSON.stringify(currentRiskResponse, null, 2));
});
$("btnRiskLocal").addEventListener("click", function () {
$("riskApiUrl").value =
"/dsb/tax-appoint/api/dx/third/company/risk/info";
$("btnRiskLocal").classList.add("on");
$("btnRiskApp").classList.remove("on");
});
$("btnRiskApp").addEventListener("click", function () {
$("riskApiUrl").value =
"https://apit-dsb.dingtax.cn/dsb/tax-appoint/api/dx/third/company/risk/info";
$("btnRiskApp").classList.add("on");
$("btnRiskLocal").classList.remove("on");
});
document.querySelectorAll("[data-risk-tab]").forEach(function (t) {
t.addEventListener("click", function () {
riskSwitchTab(t.dataset.riskTab);
});
});
["riskZjhm", "riskDjxh"].forEach(function (id) {
$(id).addEventListener("input", riskUpdatePreview);
});
riskUpdatePreview();
updatePreview();
})();
</script>
</body>
</html>