|
|
<!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, "&")
|
|
|
.replace(/</g, "<")
|
|
|
.replace(/>/g, ">")
|
|
|
.replace(/"/g, """);
|
|
|
}
|
|
|
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, "&")
|
|
|
.replace(/</g, "<")
|
|
|
.replace(/>/g, ">");
|
|
|
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">未返回 data(code=' +
|
|
|
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>
|