Files
hyapi-server/resources/qiye.html
2026-06-12 15:54:40 +08:00

4235 lines
180 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

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

<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>企业综合报告</title>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@400;500;600;700&family=Noto+Serif+SC:wght@600;700&display=swap"
rel="stylesheet"
/>
<style>
:root {
/* QYGL7HBN 企业综合报告主题(与 bdrp QYGLJ1U9 组件视觉对齐) */
--qy-accent: #005be3;
--qy-accent-soft: #3bd8ff;
--qy-label-bg: #ebf2fd;
--qy-label-color: #005be3;
--qy-label-border: #a5c5f6;
--qy-value-border: #d1d5db;
--qy-gradient: linear-gradient(90deg, #005be3 0%, #3bd8ff 100%);
--blue-950: #0f2d6e;
--blue-900: #1e3a8a;
--blue-800: #005be3;
--blue-700: #005be3;
--blue-600: #2563eb;
--blue-500: #3b82f6;
--blue-200: #bfdbfe;
--blue-100: #dbeafe;
--blue-50: #eff6ff;
--red-700: #b91c1c;
--red-600: #dc2626;
--ink: #1f2937;
--ink-secondary: #374151;
--muted: #6b7280;
--line: #dce4ef;
--line-soft: #eef2f7;
--surface: #ffffff;
--surface-raised: #f8fafc;
--brand: var(--qy-accent);
--success: #0d9488;
--success-soft: rgba(13, 148, 136, 0.1);
--warn: #d97706;
--warn-soft: rgba(217, 119, 6, 0.12);
--danger: var(--red-600);
--danger-soft: rgba(220, 38, 38, 0.1);
--radius: 8px;
--radius-sm: 6px;
--shadow-sm: 0 2px 6px rgba(0, 91, 227, 0.08);
--shadow-md: none;
--table-border: var(--qy-label-border);
--table-line: var(--line-soft);
--table-head: var(--qy-label-bg);
--font-sans:
"Noto Sans SC", "PingFang SC", "Microsoft YaHei", sans-serif;
--primary: var(--qy-accent);
--text: var(--ink);
--text-muted: var(--muted);
--border: var(--line);
--bg-card: var(--surface);
--bg-page: #ffffff;
}
*,
*::before,
*::after {
box-sizing: border-box;
}
body {
margin: 0;
font-family: var(--font-sans);
font-size: 15px;
line-height: 1.65;
color: var(--ink);
background: var(--bg-page);
min-height: 100vh;
}
.page {
position: relative;
max-width: 1040px;
margin: 0 auto;
padding: 0 8px 56px;
background: var(--surface);
border: none;
border-radius: 0;
box-shadow: none;
overflow: hidden;
scroll-behavior: smooth;
--qy-accent: #005be3;
--qy-accent-soft: #3bd8ff;
--qy-label-bg: #ebf2fd;
--qy-label-color: #005be3;
--qy-label-border: #a5c5f6;
--qy-value-border: #d1d5db;
--qy-border: #dce4ef;
--qy-row-line: #eef2f7;
--qy-muted: #6b7280;
--qy-text: #1f2937;
--qy-surface: #fff;
--qy-module-gap: 10px;
--qy-cell-py: 6px;
--qy-cell-px: 12px;
--qy-fs-table: 13px;
--qy-fs-body: 14px;
--qy-fs-subhead: 14px;
--qy-gradient: linear-gradient(90deg, #005be3 0%, #3bd8ff 100%);
--qy-success: #0d9488;
--qy-danger: #dc2626;
font-size: var(--qy-fs-body);
color: var(--qy-text);
line-height: 1.55;
}
.page::before {
display: none;
}
.page-inner {
padding: 0 8px 8px;
}
@media (max-width: 768px) {
.page {
margin: 0;
padding: 0 4px 48px;
}
.page-inner {
padding: 0 6px 8px;
}
}
.top-nav {
display: none;
}
.top-nav-title {
font-weight: 600;
color: var(--ink);
}
.top-nav-links {
display: flex;
flex-wrap: wrap;
gap: 6px;
}
.top-nav-links a {
padding: 0;
border-radius: 0;
background: transparent;
color: var(--blue-700);
text-decoration: none;
border: none;
cursor: default;
pointer-events: none;
}
.header {
position: relative;
margin: 0 0 20px;
padding: 28px 20px 24px;
border-radius: var(--radius);
min-height: clamp(168px, 28vw, 220px);
background-color: #f0f7ff;
border: none;
box-shadow: none;
color: var(--ink);
overflow: hidden;
}
.header-poster {
position: absolute;
inset: 0;
z-index: 0;
display: block;
width: 100%;
height: 100%;
object-fit: cover;
object-position: center right;
pointer-events: none;
user-select: none;
-webkit-print-color-adjust: exact;
print-color-adjust: exact;
}
.header::before {
content: "";
position: absolute;
inset: 0;
z-index: 1;
pointer-events: none;
background: linear-gradient(
90deg,
rgba(255, 255, 255, 0.94) 0%,
rgba(255, 255, 255, 0.78) 42%,
rgba(255, 255, 255, 0.25) 68%,
transparent 100%
);
}
.header-inner {
position: relative;
z-index: 2;
display: flex;
flex-direction: column;
align-items: flex-start;
max-width: 78%;
padding: 0;
margin-top: 0;
background: transparent;
text-align: left;
}
.header-hero-badges {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 8px 10px;
}
@media (max-width: 768px) {
.header {
padding: 20px 14px 18px;
min-height: 200px;
}
.header::before {
background: linear-gradient(
180deg,
rgba(255, 255, 255, 0.92) 0%,
rgba(255, 255, 255, 0.82) 55%,
rgba(255, 255, 255, 0.45) 100%
);
}
.header-poster {
object-position: 75% center;
}
.header-inner {
max-width: 100%;
}
}
.header-main {
padding: 0;
min-width: 0;
width: 100%;
text-align: left;
}
.header-hero-kicker {
margin-bottom: 14px;
}
.header-score {
margin: 16px 0 0;
padding: 0;
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 8px 10px;
}
.header-score-inner {
display: inline-flex;
flex-wrap: wrap;
align-items: center;
gap: 8px 10px;
padding: 0;
background: transparent;
border: none;
box-shadow: none;
max-width: none;
text-align: left;
}
.header-main h1.report-title {
margin: 0 0 6px;
font-size: clamp(1.55rem, 4.8vw, 2.15rem);
font-weight: 800;
letter-spacing: 0.02em;
color: #6b7280;
line-height: 1.3;
word-break: break-word;
}
.report-title-en {
margin: 0 0 14px;
font-size: clamp(0.95rem, 2.4vw, 1.55rem);
letter-spacing: 0.03em;
font-weight: 1000;
line-height: 1.25;
text-transform: uppercase;
color: transparent;
-webkit-text-stroke: 0.75px #9ca3af;
}
.header-main .subtitle.ent-name {
margin: 0 0 16px;
font-size: clamp(1.45rem, 4.5vw, 2rem);
font-weight: 800;
line-height: 1.35;
color: var(--qy-accent);
word-break: break-word;
}
.header-main .subtitle {
font-size: clamp(14px, 3.6vw, 17px);
font-weight: 400;
color: var(--ink-secondary);
word-break: break-word;
line-height: 1.75;
}
.header-main .subtitle + .subtitle {
margin-top: 0;
}
.header-meta-line {
margin-top: 8px;
font-size: clamp(13px, 3.2vw, 15px);
font-weight: 400;
color: var(--muted);
}
.header-tags {
margin-top: 12px;
display: flex;
flex-wrap: wrap;
gap: 8px 10px;
}
.tag {
display: inline-flex;
align-items: center;
padding: 6px 12px;
border-radius: 999px;
font-size: clamp(12px, 3vw, 14px);
font-weight: 600;
line-height: 1.2;
border: 1px solid #93c5fd;
background: #eff6ff;
color: #1d4ed8;
}
.header .tag {
background: #eff6ff;
border-color: #93c5fd;
color: #1d4ed8;
}
.header-score .score {
display: inline-flex;
padding: 8px 16px;
border-radius: 7px;
font-size: clamp(14px, 3.4vw, 17px);
font-weight: 700;
line-height: 1.25;
color: #fff;
background: var(--qy-gradient);
box-shadow: var(--shadow-sm);
}
.header-score .label {
margin: 0;
font-size: inherit;
font-weight: 700;
letter-spacing: normal;
color: inherit;
display: inline-flex;
flex-direction: row;
align-items: center;
gap: 8px;
}
.header-score .pill {
border-width: 0;
margin: 0;
display: inline-flex;
padding: 8px 16px;
border-radius: 7px;
font-size: clamp(14px, 3.4vw, 17px);
font-weight: 700;
color: #fff;
background: var(--qy-gradient);
box-shadow: var(--shadow-sm);
}
.header-score .pill.low {
background: linear-gradient(90deg, #0d9488 0%, #14b8a6 100%);
}
.header-score .pill.mid {
background: linear-gradient(90deg, #d97706 0%, #f59e0b 100%);
}
.header-score .pill.high {
background: linear-gradient(90deg, #dc2626 0%, #f87171 100%);
}
.print-btn {
display: none;
}
.pdf-actions-fixed {
position: fixed;
right: 16px;
bottom: 80px;
z-index: 1000;
}
.pdf-fixed-btn {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 10px 18px;
border-radius: 999px;
border: none;
background: var(--qy-accent);
color: #fff;
font-family: var(--font-sans);
font-size: 14px;
font-weight: 600;
cursor: pointer;
box-shadow: 0 4px 14px rgba(0, 91, 227, 0.28);
}
.pdf-fixed-btn:hover {
opacity: 0.92;
}
.pdf-fixed-btn:disabled {
opacity: 0.55;
cursor: not-allowed;
transform: none;
}
.pdf-loading-overlay {
position: fixed;
inset: 0;
background: rgba(255, 255, 255, 0.72);
backdrop-filter: blur(6px);
z-index: 2000;
display: flex;
align-items: center;
justify-content: center;
}
.pdf-loading-overlay[aria-hidden="true"] {
display: none;
}
.pdf-loading-content {
background: var(--surface);
padding: 36px 44px;
border-radius: var(--radius);
text-align: center;
border: 1px solid var(--line);
box-shadow: var(--shadow-md);
}
.pdf-loading-spinner {
width: 44px;
height: 44px;
border: 2px solid var(--line);
border-top-color: var(--blue-800);
border-radius: 50%;
margin: 0 auto 18px;
animation: pdf-spin 0.8s linear infinite;
}
.pdf-loading-content p {
margin: 0;
font-size: 15px;
font-weight: 500;
color: var(--ink-secondary);
}
@keyframes pdf-spin {
to {
transform: rotate(360deg);
}
}
.report-sections {
margin-top: 8px;
display: flex;
flex-direction: column;
gap: 28px;
}
.section-card {
scroll-margin-top: 24px;
padding: 0 6px 8px;
background: transparent;
border: none;
border-radius: 0;
box-shadow: none;
overflow: visible;
}
.section-card h2 {
display: flex;
flex-wrap: wrap;
align-items: baseline;
gap: 10px 12px;
margin: 0 0 18px;
padding: 0 6px 0 2px;
font-weight: 700;
letter-spacing: normal;
color: var(--qy-accent);
background: transparent;
border: none;
}
.section-card h2 .section-no {
flex-shrink: 0;
display: inline-block;
font-size: clamp(2rem, 6vw, 2.85rem);
font-weight: 800;
font-style: italic;
line-height: 1.15;
padding: 0 0.2em 0 0.06em;
background: var(--qy-gradient);
-webkit-background-clip: text;
background-clip: text;
-webkit-text-fill-color: transparent;
}
.section-card h2 .section-title-cn {
margin: 0;
font-size: clamp(1.05rem, 3vw, 1.28rem);
font-weight: 700;
color: var(--qy-accent);
line-height: 1.35;
min-width: 0;
}
.section-card .section-body {
padding: 0 2px;
}
.section-body h3,
.section-card .section-body h3 {
margin: var(--qy-module-gap) 0 0;
padding: var(--qy-cell-py) var(--qy-cell-px);
font-size: var(--qy-fs-subhead);
font-weight: 700;
text-align: center;
color: var(--qy-label-color);
background: var(--qy-label-bg);
border: 1px solid var(--qy-label-border);
}
.section-body h3:first-child,
.section-card .section-body h3:first-of-type {
margin-top: 0;
}
.report-subheading,
.qy-sub-heading {
margin: var(--qy-module-gap) 0 0 !important;
padding: var(--qy-cell-py) var(--qy-cell-px) !important;
font-size: var(--qy-fs-subhead) !important;
font-weight: 700 !important;
letter-spacing: normal !important;
text-align: center !important;
color: var(--qy-label-color) !important;
background: var(--qy-label-bg) !important;
border: 1px solid var(--qy-label-border) !important;
border-radius: 0 !important;
}
.report-subheading:first-child,
.qy-sub-heading:first-child {
margin-top: 0 !important;
}
.report-annual-card-title {
margin: 0 0 0 !important;
padding: var(--qy-cell-py) var(--qy-cell-px) !important;
font-size: var(--qy-fs-subhead) !important;
font-weight: 700 !important;
text-align: center !important;
color: var(--qy-label-color) !important;
background: var(--qy-label-bg) !important;
border: 1px solid var(--qy-label-border) !important;
border-bottom: 1px solid var(--qy-label-border) !important;
}
.report-annual-shell {
margin-bottom: var(--qy-module-gap);
padding: 0;
background: var(--qy-surface);
border: 1px solid var(--qy-label-border);
border-radius: 0;
box-shadow: none;
}
.report-annual-shell:last-child {
margin-bottom: 0;
}
.kv-table-wrap {
width: 100%;
max-width: 100%;
margin-bottom: 0;
overflow-x: auto;
-webkit-overflow-scrolling: touch;
}
.kv-table {
width: 100%;
border-collapse: collapse;
border-spacing: 0;
font-size: var(--qy-fs-table);
line-height: 1.55;
border: 1px solid var(--qy-label-border);
border-radius: 0;
table-layout: fixed;
}
.kv-table th,
.kv-table td {
padding: var(--qy-cell-py) var(--qy-cell-px);
vertical-align: middle;
word-break: break-word;
border: 1px solid var(--qy-label-border);
}
.kv-table tbody tr:nth-child(even),
.kv-table tbody tr:nth-child(odd) {
background: var(--qy-surface);
}
.kv-table th {
width: 28%;
font-weight: 600;
font-size: var(--qy-fs-table);
color: var(--qy-label-color);
background: var(--qy-label-bg);
text-align: left;
}
.kv-table td {
color: var(--qy-text);
font-weight: 400;
background: var(--qy-surface);
border-left: none;
}
@media (max-width: 640px) {
.kv-table th {
width: 36%;
}
}
.qy-info-grid {
width: 100%;
border-collapse: collapse;
border-spacing: 0;
font-size: var(--qy-fs-table);
table-layout: fixed;
border: 1px solid var(--qy-label-border);
margin-bottom: 0;
}
.qy-info-grid th {
width: 18%;
padding: var(--qy-cell-py) var(--qy-cell-px);
font-weight: 600;
color: var(--qy-label-color);
background: var(--qy-label-bg);
border: 1px solid var(--qy-label-border);
vertical-align: middle;
word-break: break-word;
}
.qy-info-grid td {
width: 32%;
padding: var(--qy-cell-py) var(--qy-cell-px);
color: var(--qy-text);
background: var(--qy-surface);
border: 1px solid var(--qy-value-border);
border-left: none;
vertical-align: middle;
word-break: break-word;
}
@media (max-width: 640px) {
.qy-info-grid {
display: block;
}
.qy-info-grid tbody,
.qy-info-grid tr {
display: block;
width: 100%;
}
.qy-info-grid th,
.qy-info-grid td {
display: block;
width: 100% !important;
box-sizing: border-box;
}
.qy-info-grid td {
border-top: none;
border-left: 1px solid var(--qy-value-border);
}
.qy-info-grid td + th {
border-top: 1px solid var(--qy-label-border);
}
}
.report-brand-footer {
position: relative;
margin: 32px 0 16px;
padding: 36px 16px;
text-align: center;
border-radius: 4px;
min-height: clamp(100px, 22vw, 140px);
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
background-color: #f4f8fc;
}
.footer-poster {
position: absolute;
inset: 0;
z-index: 0;
display: block;
width: 100%;
height: 100%;
object-fit: cover;
object-position: center;
pointer-events: none;
user-select: none;
-webkit-print-color-adjust: exact;
print-color-adjust: exact;
}
.report-brand-footer p {
position: relative;
z-index: 1;
margin: 0;
padding: 0 16px;
font-size: clamp(1rem, 3vw, 1.25rem);
font-weight: 700;
color: var(--qy-accent);
text-shadow: 0 1px 8px rgba(255, 255, 255, 0.85);
}
.kv-grid {
display: none;
}
.item-list {
list-style: none;
padding: 0;
margin: var(--qy-module-gap) 0 0;
border: 1px solid var(--qy-label-border);
border-radius: 0;
overflow: hidden;
}
.item-list:first-child {
margin-top: 0;
}
.item-list li {
padding: var(--qy-cell-py) var(--qy-cell-px);
border-bottom: 1px solid var(--qy-label-border);
font-size: var(--qy-fs-table);
line-height: 1.55;
background: var(--qy-surface);
}
.item-list li:last-child {
border-bottom: none;
}
.item-list li .item-title {
font-weight: 600;
color: var(--qy-text);
margin-bottom: 4px;
font-size: var(--qy-fs-subhead);
}
.item-list li .item-meta {
color: var(--qy-muted);
font-size: var(--qy-fs-table);
line-height: 1.55;
}
.item-block,
.record-card {
margin-top: var(--qy-module-gap);
padding: var(--qy-cell-py) var(--qy-cell-px);
background: transparent;
border: 1px solid var(--qy-label-border);
border-radius: 0;
font-size: var(--qy-fs-table);
line-height: 1.55;
color: var(--qy-text);
}
.item-block:first-child,
.record-card:first-child {
margin-top: 0;
}
.report-annual-shell {
padding: 0;
}
.report-annual-shell .report-annual-card-title + .kv-table-wrap,
.report-annual-shell .report-annual-card-title + table,
.report-annual-shell .report-annual-card-title + .qy-info-grid {
margin: 0;
}
.item-block .item-kv span.k {
color: var(--qy-label-color);
margin-right: 6px;
font-weight: 600;
font-size: var(--qy-fs-table);
}
.empty-hint {
margin: 0;
padding: var(--qy-cell-py) var(--qy-cell-px);
font-size: var(--qy-fs-table);
text-align: center;
color: var(--qy-muted);
background: var(--qy-surface);
border: 1px solid var(--qy-label-border);
border-radius: 0;
}
.count-hint {
margin: 0;
padding: var(--qy-cell-py) var(--qy-cell-px);
font-size: var(--qy-fs-table);
font-weight: 600;
color: var(--qy-label-color);
background: var(--qy-label-bg);
border: 1px solid var(--qy-label-border);
border-bottom: none;
}
.count-hint + .item-list,
.count-hint + .kv-table-wrap,
.count-hint + .qy-info-grid {
margin-top: 0;
}
.cell-object .cell-kv-line {
margin: 6px 0;
line-height: 1.55;
}
.cell-object .cell-kv-line:first-child {
margin-top: 0;
}
.cell-object .cell-k {
display: inline-block;
min-width: 11em;
color: var(--qy-label-color);
font-size: var(--qy-fs-table);
font-weight: 600;
vertical-align: top;
}
.cell-object .cell-k::after {
content: "";
}
.cell-object .cell-v {
display: inline-block;
color: var(--ink);
font-size: 13px;
max-width: 100%;
word-break: break-word;
}
.cell-object .cell-object {
margin-top: 6px;
margin-left: 4px;
padding-left: 10px;
border-left: 2px solid var(--table-line);
}
.pill {
display: inline-flex;
align-items: center;
padding: 3px 8px;
border-radius: 4px;
font-size: 11px;
font-weight: 600;
letter-spacing: 0.02em;
margin-left: 8px;
vertical-align: middle;
}
.pill.low {
background: var(--success-soft);
color: var(--success);
border: 1px solid rgba(13, 148, 136, 0.28);
}
.pill.mid {
background: var(--warn-soft);
color: var(--warn);
border: 1px solid rgba(202, 138, 4, 0.3);
}
.pill.high {
background: var(--danger-soft);
color: var(--red-700);
border: 1px solid rgba(220, 38, 38, 0.35);
}
/* 页眉综合评分区:避免被正文 .pill 浅色样式覆盖 */
.header-score .pill.low,
.header-score .pill.mid,
.header-score .pill.high {
color: #fff;
border: none;
}
.header-score .pill.low {
background: linear-gradient(90deg, #0d9488 0%, #14b8a6 100%);
}
.header-score .pill.mid {
background: linear-gradient(90deg, #d97706 0%, #f59e0b 100%);
}
.header-score .pill.high {
background: linear-gradient(90deg, #dc2626 0%, #f87171 100%);
}
.risk-row {
display: flex;
justify-content: space-between;
align-items: center;
padding: var(--qy-cell-py) 0;
font-size: var(--qy-fs-body);
border-bottom: 1px solid var(--qy-row-line);
}
.item-list li.risk-row {
padding: var(--qy-cell-py) var(--qy-cell-px);
border-bottom: 1px solid var(--qy-row-line);
}
.risk-row:last-child {
border-bottom: none;
}
.risk-row .ok {
color: var(--qy-success);
font-weight: 500;
}
.risk-row .bad {
color: var(--qy-danger);
font-weight: 500;
}
.risk-points-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(min(100%, 140px), 1fr));
gap: 8px;
margin-bottom: 12px;
}
.risk-point {
font-size: var(--qy-fs-table);
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
padding: var(--qy-cell-py) var(--qy-cell-px);
background: var(--qy-label-bg);
color: var(--qy-text);
border: 1px solid var(--qy-label-border);
border-radius: 0;
}
.risk-point-name {
color: var(--qy-text);
font-weight: 600;
}
.risk-status-ok {
color: var(--qy-success);
font-weight: 500;
font-size: var(--qy-fs-table);
}
.risk-status-bad {
color: var(--qy-danger);
font-weight: 500;
font-size: var(--qy-fs-table);
}
.raw-section summary {
cursor: pointer;
padding: 12px 16px;
font-weight: 600;
font-size: 13px;
color: var(--ink-secondary);
background: var(--table-head);
border: 1px solid var(--table-border);
border-radius: var(--radius-sm);
}
.raw-section pre {
font-size: 11px;
line-height: 1.6;
font-family:
ui-monospace, "Cascadia Code", "SF Mono", Consolas,
monospace;
background: #1e293b;
color: #e2e8f0;
padding: 16px;
border-radius: 0 0 var(--radius-sm) var(--radius-sm);
overflow: auto;
max-height: 320px;
margin: 0;
border: 1px solid var(--table-border);
border-top: none;
}
@media (max-width: 768px) {
.section-card .section-body {
padding: 18px 16px 20px;
}
}
@page {
size: A4 portrait;
margin: 10mm 12mm;
}
/* 全页斜向透明红字水印position:fixed 在 Chrome 打印/PDF 时每页重复 */
.report-watermark {
position: fixed;
left: 50%;
top: 50%;
width: max-content;
max-width: none;
transform: translate(-50%, -50%) rotate(-45deg);
margin: 0;
padding: 0;
font-family: var(--font-sans);
font-size: 56px;
font-weight: 600;
letter-spacing: 0.14em;
line-height: 1;
white-space: nowrap;
color: rgba(220, 38, 38, 0.14);
pointer-events: none;
user-select: none;
z-index: 500;
-webkit-print-color-adjust: exact;
print-color-adjust: exact;
}
/* Headless Chrome 导出 PDF 时使用(不切换 print 媒体,避免海报/布局异常) */
body.pdf-export {
-webkit-print-color-adjust: exact;
print-color-adjust: exact;
}
body.pdf-export .pdf-actions-fixed,
body.pdf-export .pdf-loading-overlay,
body.pdf-export .top-nav {
display: none !important;
}
body.pdf-export .page {
max-width: none;
width: 100%;
margin: 0;
padding: 0 0 24px;
overflow: visible;
}
body.pdf-export .page-inner {
padding: 0 !important;
}
body.pdf-export .report-sections,
body.pdf-export #reportSections {
padding: 0 12px;
}
body.pdf-export .section-card h2 {
background: transparent !important;
border: none !important;
box-shadow: none !important;
}
body.pdf-export .header {
margin: 0 0 16px !important;
border: none !important;
border-radius: 0 !important;
box-shadow: none !important;
padding: 28px 20px 24px !important;
min-height: clamp(168px, 22vw, 200px) !important;
overflow: hidden !important;
}
body.pdf-export .header-poster {
position: absolute !important;
inset: 0 !important;
width: 100% !important;
height: 100% !important;
object-fit: cover !important;
object-position: center right !important;
}
body.pdf-export .header::before {
display: block !important;
}
body.pdf-export .header-inner {
max-width: 78% !important;
text-align: left !important;
align-items: flex-start !important;
}
body.pdf-export .header-main {
text-align: left !important;
width: 100% !important;
}
body.pdf-export .header-score {
justify-content: flex-start !important;
width: 100% !important;
margin-top: 12px !important;
}
body.pdf-export .header-score-inner {
display: inline-flex !important;
flex-direction: row !important;
flex-wrap: nowrap !important;
align-items: center !important;
justify-content: flex-start !important;
gap: 10px !important;
max-width: none !important;
margin: 0 !important;
padding: 0 !important;
background: transparent !important;
border: none !important;
box-shadow: none !important;
}
body.pdf-export .header-score .label {
display: inline-flex !important;
margin: 0 !important;
background: transparent !important;
}
body.pdf-export .header-score .score,
body.pdf-export .header-score .pill {
flex-shrink: 0;
}
body.pdf-export .header-poster,
body.pdf-export .footer-poster {
display: block !important;
visibility: visible !important;
opacity: 1 !important;
-webkit-print-color-adjust: exact !important;
print-color-adjust: exact !important;
}
@media print {
html,
body {
background: #fff !important;
margin: 0;
width: 100%;
min-height: auto;
-webkit-print-color-adjust: exact;
print-color-adjust: exact;
}
.page {
max-width: none;
width: 100%;
margin: 0;
padding: 0;
border: none;
box-shadow: none;
border-radius: 0;
overflow: visible;
}
.page::before {
display: none;
}
.page-inner {
padding: 0 !important;
}
.top-nav {
display: none !important;
}
.pdf-actions-fixed,
.pdf-loading-overlay {
display: none !important;
}
.header {
background-color: #f0f7ff !important;
color: var(--ink) !important;
box-shadow: none !important;
border: none !important;
border-radius: 0;
margin-top: 0;
padding: 28px 20px 24px !important;
min-height: 180px !important;
overflow: hidden !important;
page-break-inside: avoid;
break-inside: avoid-page;
}
.header-poster,
.footer-poster {
position: absolute !important;
inset: 0 !important;
width: 100% !important;
height: 100% !important;
max-width: none !important;
object-fit: cover !important;
-webkit-print-color-adjust: exact !important;
print-color-adjust: exact !important;
}
.header-poster {
object-position: center right !important;
}
.footer-poster {
object-position: center !important;
}
.header-inner {
padding: 0 !important;
margin-top: 0 !important;
max-width: 78% !important;
gap: 0;
background: transparent !important;
text-align: left !important;
}
.header::before {
display: block !important;
}
.header-main {
padding: 0;
text-align: left !important;
width: 100% !important;
}
.header-score-inner {
display: inline-flex !important;
flex-direction: row !important;
flex-wrap: nowrap !important;
align-items: center !important;
justify-content: flex-start !important;
gap: 10px !important;
background: transparent !important;
border: none !important;
box-shadow: none;
max-width: none !important;
margin: 0 !important;
padding: 0 !important;
}
.header-score .label {
display: inline-flex !important;
background: transparent !important;
}
.header-main h1.report-title {
color: var(--blue-950) !important;
}
.header-main .subtitle,
.header-main .subtitle + .subtitle {
color: var(--ink-secondary) !important;
}
.header-meta-line {
color: var(--muted) !important;
}
.tag {
background: var(--surface-raised);
color: var(--ink-secondary);
border-color: var(--line);
}
.header .tag {
background: #fff !important;
color: var(--blue-900) !important;
border-color: rgba(37, 99, 235, 0.25) !important;
}
.header-score {
background: transparent !important;
border: none !important;
margin: 12px 0 0 !important;
justify-content: flex-start !important;
width: 100% !important;
}
.header-score .score {
color: #fff !important;
background: var(--qy-gradient) !important;
}
.section-card {
box-shadow: none;
border-radius: 0;
border: none;
border-bottom: 1px solid var(--line);
page-break-inside: auto;
break-inside: auto;
padding: 0 !important;
margin-top: 6px !important;
background: #fff !important;
}
.section-card h2 {
background: transparent !important;
border: none !important;
box-shadow: none !important;
color: var(--qy-accent);
padding: 0 6px 0 2px !important;
page-break-after: avoid;
break-after: avoid-page;
}
.section-card .section-body {
padding: 12px 0 16px !important;
}
.kv-table thead {
display: table-header-group;
}
.kv-table tr,
.qy-info-grid tr {
page-break-inside: avoid;
break-inside: avoid-page;
}
.record-card,
.item-block,
.report-brand-footer {
page-break-inside: avoid;
break-inside: avoid-page;
}
.report-brand-footer {
min-height: 100px !important;
padding: 36px 16px !important;
margin: 24px 0 0;
background-color: #f4f8fc !important;
}
.item-list li {
page-break-inside: auto;
break-inside: auto;
}
.raw-section pre {
max-height: none;
page-break-inside: avoid;
break-inside: avoid;
}
a {
color: var(--ink);
text-decoration: none;
}
.report-watermark {
display: block !important;
font-size: 42pt;
color: rgba(220, 38, 38, 0.16) !important;
}
}
</style>
</head>
<body>
<div class="report-watermark" aria-hidden="true">海宇数据</div>
<div class="page">
<div class="page-inner">
<!-- 隐藏的导航容器,仅用于脚本生成目录,页面不展示 -->
<nav class="top-nav">
<div id="navLinks"></div>
</nav>
<header class="header">
<img
class="header-poster"
src="{{ .BannerPosterURL }}"
alt=""
aria-hidden="true"
/>
<div class="header-inner">
<div class="header-main">
<div class="header-hero-kicker">
<h1 class="report-title">企业综合报告</h1>
<p class="report-title-en">ENTERPRISE COMPREHENSIVE REPORT</p>
</div>
<div class="subtitle ent-name" id="entName">企业名称</div>
<div class="subtitle">
<span id="creditCodeLabel"
>统一社会信用代码:-</span
>
·
<span id="entStatusLabel">经营状态:-</span>
·
<span id="entTypeLabel">企业类型:-</span>
</div>
<div class="header-meta-line" id="reportTimeLabel">
报告生成时间:-
</div>
<div class="header-tags" id="headerTags"></div>
</div>
<aside class="header-score" aria-label="综合评分">
<div class="header-score-inner header-hero-badges">
<div class="score" id="overallScore">--</div>
<span id="riskLevelPill" class="pill low">风险:-</span>
</div>
</aside>
</div>
</header>
<main id="reportSections" class="report-sections"></main>
<footer class="report-brand-footer">
<img
class="footer-poster"
src="{{ .FooterPosterURL }}"
alt=""
aria-hidden="true"
/>
<p>更新更全信息欢迎使用海宇数据</p>
</footer>
</div>
</div>
<!-- 固定悬浮的保存 PDF 按钮(在 .page 外,不会被截进 PDF -->
<div class="pdf-actions-fixed">
<button id="btnSavePdf" class="pdf-fixed-btn" type="button">
保存为 PDF
</button>
</div>
<!-- 生成 PDF 时的加载浮层 -->
<div
id="pdfLoadingOverlay"
class="pdf-loading-overlay"
aria-hidden="true"
>
<div class="pdf-loading-content">
<div class="pdf-loading-spinner" aria-hidden="true"></div>
<p id="pdfLoadingMessage">正在准备 PDF请稍候...</p>
</div>
</div>
<!-- 前端截图并生成 PDF 所需依赖 -->
<script src="https://cdn.jsdelivr.net/npm/html2canvas@1.4.1/dist/html2canvas.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/jspdf@2.5.1/dist/jspdf.umd.min.js"></script>
<script>
(function () {
// 后端通过模板引擎注入的企业报告数据QYGL7HBN 聚合结果)
// ReportJSON 为合法的 JSON 对象字面量
var reportData = {{ .ReportJSON }};
var sectionOrder = [
"basic",
"branches",
"shareholding",
"controller",
"beneficiaries",
"investments",
"guarantees",
"management",
"assets",
"licenses",
"activities",
"inspections",
"risks",
"timeline",
"listed",
"annualReports",
"taxViolations",
"ownTaxNotices",
"customsCredit",
];
var sectionTitles = {
basic: "一、主体概览(企业基础信息)",
branches: "二、分支机构",
shareholding: "三、股权与控制(持股结构)",
controller: "四、实际控制人",
beneficiaries: "五、最终受益人",
investments: "六、对外投资",
guarantees: "七、对外担保(披露摘要)",
management: "八、人员与组织(高管与任职)",
assets: "九、资产与经营(财务摘要)",
licenses: "十、行政许可与资质",
activities: "十一、经营活动(招投标)",
inspections: "十二、抽查检查",
risks: "十三、风险与合规",
timeline: "十四、发展时间线",
listed: "十五、上市信息",
annualReports: "十六、企业年报(公示)",
taxViolations: "十七、税收违法",
ownTaxNotices: "十八、欠税公告",
customsCredit: "十九、进出口信用(海关)",
};
// 保持所有板块单列纵向排布
var keyLabels = {
entName: "企业名称",
creditCode: "统一社会信用代码",
regNo: "注册号",
orgCode: "组织机构代码",
entType: "企业类型",
entTypeCode: "企业类型编码",
entityTypeCode: "实体类型编码",
establishDate: "成立日期",
registeredCapital: "注册资本",
regCap: "注册资本",
regCapCurrency: "注册资本币种",
regCapCurrencyCode: "注册资本币种代码",
regOrg: "登记机关",
regOrgCode: "注册地址行政编号",
regProvince: "所在省份",
provinceCode: "所在省份编码",
regCity: "所在城市",
regCityCode: "所在城市编码",
regDistrict: "所在区/县",
districtCode: "所在区/县编码",
address: "住址",
postalCode: "邮编",
legalRepresentative: "法定代表人",
compositionForm: "组成形式",
approvedBusinessItem: "许可经营项目",
status: "经营状态",
statusCode: "经营状态编码",
operationPeriodFrom: "经营期限自",
operationPeriodTo: "经营期限至",
approveDate: "核准日期",
cancelDate: "注销日期",
revokeDate: "吊销日期",
cancelReason: "注销原因",
revokeReason: "吊销原因",
oldNames: "曾用名",
businessScope: "经营业务范围",
lastAnnuReportYear: "最后年检年度",
name: "名称",
type: "类型",
percent: "比例",
reason: "判定依据",
source: "来源",
id: "标识",
path: "控制路径",
topHolderName: "第一大股东",
topHolderPercent: "第一大股东持股",
top5TotalPercent: "前五大合计",
shareholderCount: "股东总数",
currency: "币种",
hasEquityPledges: "存在股权出质",
riskLevel: "风险等级",
riskScore: "风险得分",
dishonestDebtorCount: "失信被执行人数量",
limitHighDebtorCount: "限制高消费被执行人数量",
total: "记录总数",
hasAbnormal: "是否存在异常",
hasPunish: "是否存在处罚",
isCancelled: "是否注销",
customsRegisterCode: "海关注册编码",
customsRegisterDate: "注册日期",
creditLevel: "信用等级",
customsLevel: "海关等级",
customsBusinessType: "经营类别",
validDate: "有效期",
abnormalInfo: "异常信息",
punishInfoText: "处罚信息",
totalCount: "对外投资数量",
totalAmount: "投资总额(万)",
employeeCount: "从业人数",
femaleEmployeeCount: "女性从业人数",
yearReportId: "年报标识",
investor: "股东/投资人",
changeDate: "变更日期",
detailBefore: "变更前",
detailAfter: "变更后",
changeType: "变更类型",
updateDate: "变更日期",
updateContent: "变更内容",
date: "日期",
title: "标题",
overallScore: "总体评分",
tags: "标签",
isListed: "是否上市",
company: "上市公司信息",
stock: "股票信息",
// 上市公司信息字段
bizScope: "经营范围",
regAddr: "注册地址",
regCapital: "注册资本",
cur: "币种",
curName: "币种名称",
// 欠税公告QYGL7D9A
taxIdNumber: "纳税人识别号",
taxpayerName: "纳税人名称",
taxCategory: "欠税税种",
ownTaxBalance: "欠税余额",
ownTaxAmount: "欠税金额",
newOwnTaxBalance: "当前新发生欠税余额",
taxType: "税务类型",
publishDate: "发布时间",
department: "税务机关",
legalPersonName: "法人或负责人名称",
personIdNumber: "证件号码",
personIdName: "法人证件名称",
taxpayerType: "纳税人类型",
regType: "注册类型",
// 税收违法QYGL8848
entityName: "企业名称",
entityCategory: "企业类别",
taxpayerCode: "纳税人识别号",
agencyPersonInfo: "直接责任中介机构信息",
illegalStartDate: "违法事实开始时间",
illegalEndDate: "违法事实结束时间",
illegalTime: "违法行为发生时间",
publishDepartment: "信息公示税务机关",
checkDepartment: "案件检查机关",
belongDepartment: "涉案企业所属税务机关",
punishBasis: "处罚法律依据及具体内容",
illegalFact: "具体违法事实描述",
caseType: "案件性质",
police: "案件移送公安机关情况",
// 企业年报QYGLDJ12
registerCode: "注册号",
organizationCode: "组织机构代码",
creditCode: "统一社会信用代码",
entityStatus: "企业经营状态",
telephone: "企业联系电话",
postCode: "邮政编码",
entityBusinessScope: "主营业务范围",
employeeCnt: "从业人数",
entityPractitionerFemaleAmount: "其中女性从业人数",
actualEmployeeCnt: "实际员工数量",
mainBusinessIncome: "主营业务收入",
netProfit: "净利润",
totalSalesAmount: "销售总额",
totalNetProfit: "利润总额",
totalAssets: "资产总额",
totalLiabilities: "负债总额",
totalOwnerEquity: "所有者权益合计",
totalTaxAmount: "纳税总额",
reportYear: "年报年份",
reportName: "年报名称",
reportDate: "年报提交日期",
isWebsite: "是否有网站或网店",
isInvest: "是否有对外投资信息或购买股权",
isExternalGuarantee: "是否提供对外担保",
isEquityTransfer: "是否发生股东股权转让",
entityHoldingInfo: "企业控股情况信息",
guaranteeInfo: "对外担保信息摘要",
investInfo: "对外投资信息摘要",
actualRegistCapitalInfo: "注册资本实缴情况",
reportChangeInfo: "年报变更记录",
reportBranch: "分支机构信息",
reportContributeInfo: "出资情况信息",
reportRecordInfo: "备案信息",
websiteType: "网址类型",
websiteName: "网址名称",
websiteUrl: "网址",
stockName: "股东名称",
identifyType: "证件类型",
stockPercent: "持股比例",
realCapiItems: "实缴出资详情",
shouldCapiItems: "应缴出资详情",
totalRealCapi: "实缴出资总额",
totalShouldCapi: "应缴出资总额",
investName: "被投资企业名称",
investCapi: "投资金额",
investPercent: "投资比例",
entityId: "主体标识",
investRegNo: "注册号/统一社会信用代码",
seqNo: "序号",
socialSecurity: "社会保险(分项参保人数)",
endowmentInsuranceEmployeeCnt: "城镇职工基本养老保险参保人数",
unemploymentInsuranceEmployeeCnt: "失业保险参保人数",
medicalInsuranceEmployeeCnt: "职工基本医疗保险参保人数",
injuryInsuranceEmployeeCnt: "工伤保险参保人数",
maternityInsuranceEmployeeCnt: "生育保险参保人数",
creditor: "债权人",
debtor: "债务人",
amount: "主债权数额",
period: "履行债务的期限",
guaranteeTime: "保证期间",
guaranteeType: "保证方式",
guaranteeScope: "保证范围",
beforePercent: "变更前股权比例",
afterPercent: "变更后股权比例",
changeDate: "变更日期",
updateItem: "修改事项",
beforeUpdate: "修改前内容",
afterUpdate: "修改后内容",
investType: "出资方式",
realCapi: "实缴额",
realCapiDate: "实缴日期",
shoudCapi: "应缴额",
shouldCapiDate: "应缴日期",
};
function label(key) {
return keyLabels[key] || key;
}
function recordGet(obj) {
var keys = Array.prototype.slice.call(arguments, 1);
for (var i = 0; i < keys.length; i++) {
var k = keys[i];
if (
obj &&
obj[k] != null &&
obj[k] !== ""
) {
return obj[k];
}
}
return undefined;
}
function normalizeEntStatus(value) {
if (value == null || value === "") return undefined;
if (value === 1 || value === "1") return "存续";
if (value === 2 || value === "2") return "注销";
if (value === 3 || value === "3") return "吊销";
if (
typeof value === "string" &&
Number.isNaN(Number(value))
) {
return value;
}
return String(value);
}
function normalizeBasicListRecord(raw) {
if (!raw || typeof raw !== "object") return {};
var statusRaw = recordGet(
raw,
"ENTSTATUS",
"status",
"statusCode",
"ENTSTATUSCODE",
);
return {
entName: recordGet(raw, "ENTNAME", "entName"),
creditCode: recordGet(
raw,
"CREDITNO",
"SHXYDM",
"creditCode",
),
regNo: recordGet(raw, "REGNO", "regNo"),
orgCode: recordGet(raw, "ORGCODES", "orgCode"),
entType: recordGet(raw, "ENTTYPE", "entType"),
establishDate: recordGet(raw, "ESDATE", "establishDate"),
registeredCapital: recordGet(
raw,
"REGCAP",
"registeredCapital",
"regCap",
),
legalRepresentative: recordGet(
raw,
"FRNAME",
"legalRepresentative",
),
status: normalizeEntStatus(statusRaw),
approveDate: recordGet(raw, "APPRDATE", "approveDate"),
regProvince: recordGet(
raw,
"REGORGPROVINCE",
"regProvince",
),
};
}
var basicListGridKeys = [
"entName",
"creditCode",
"regNo",
"orgCode",
"entType",
"establishDate",
"registeredCapital",
"legalRepresentative",
"status",
"approveDate",
"regProvince",
];
var basicListLabelOverrides = {
creditCode: "信用代码",
legalRepresentative: "法定代表人",
registeredCapital: "注册资本",
establishDate: "成立日期",
approveDate: "核准日期",
regProvince: "所在省份",
orgCode: "组织机构代码",
};
function formatInReason(value) {
if (value == null || value === "") return "-";
var code = String(value).trim();
var map = {
"04": "未按规定公示年度报告",
"01": "未依照《企业信息公示暂行条例》第八条规定的期限公示年度报告",
};
return map[code] || code;
}
function normalizeRiskTag(tag) {
if (tag == null || tag === "") return "";
if (typeof tag === "string") return tag.trim();
if (typeof tag === "object" && tag.name) {
return String(tag.name).trim();
}
return String(tag).trim();
}
function formatPunishInfo(raw) {
if (!raw || raw === "[]" || raw === "无") return "—";
try {
var arr = JSON.parse(raw);
if (Array.isArray(arr)) {
return arr
.map(function (p) {
return (
"案件性质:" +
(p.case_type || "—") +
";当事人:" +
(p.case_litigant || "—") +
";编号:" +
(p.case_no || "—")
);
})
.join("\n");
}
} catch (e) {
/* ignore */
}
return String(raw);
}
function normalizeCustomsItem(it) {
if (!it || typeof it !== "object") return {};
return Object.assign({}, it, {
punishInfoText: formatPunishInfo(it.punishInfo),
});
}
var customsDetailKeys = [
"entityName",
"customsRegisterCode",
"customsRegisterDate",
"creditLevel",
"customsLevel",
"customsBusinessType",
"validDate",
"abnormalInfo",
];
function customsDetailKeysFor(it) {
var keys = customsDetailKeys.slice();
if (
it &&
it.punishInfo &&
formatPunishInfo(it.punishInfo) !== "—"
) {
keys.push("punishInfoText");
}
return keys.filter(function (k) {
return label(k) && label(k) !== k;
});
}
function esc(s) {
if (s == null || s === "") return "-";
return String(s);
}
// 职务字段格式化:部分数据源为英文/代码,如 EXEC、EXEC&&LR、HEXEC
function formatPosition(position) {
if (!position) return "";
var p = String(position).trim();
if (!p) return "";
// 将可能存在的转义形式统一为普通字符
p = p.replace(/\\u0026/gi, "&");
// 已知常见职务代码映射
var dict = {
EXEC: "高管",
LR: "法定代表人",
HEXEC: "执行合伙人",
};
// 拆分出可能的代码片段(用非字母字符作为分隔)
var parts = p
.split(/[^A-Z]/i)
.map(function (s) {
return s.trim();
})
.filter(Boolean);
if (parts.length === 0) {
return p;
}
var mapped = parts.map(function (code) {
return dict[code] || code;
});
// 多个角色用顿号连接,例如 "高管、法定代表人"
return mapped.join("、");
}
function renderScalarForCell(x) {
if (x == null || x === "") return "-";
if (typeof x === "boolean") return x ? "是" : "否";
if (typeof x === "number") return String(x);
if (typeof x === "string") return esc(x);
return esc(String(x));
}
function renderObjectAsLines(obj, depth) {
depth = depth || 0;
if (depth > 5) {
try {
return esc(JSON.stringify(obj));
} catch (e) {
return "-";
}
}
if (obj == null) return "-";
if (typeof obj !== "object")
return renderScalarForCell(obj);
if (Array.isArray(obj)) {
if (!obj.length) return "-";
var allScalar = obj.every(function (x) {
return (
x == null ||
typeof x === "string" ||
typeof x === "number" ||
typeof x === "boolean"
);
});
if (allScalar) {
return obj.map(renderScalarForCell).join("、");
}
return obj.length ? "共 " + obj.length + " 条" : "-";
}
var keys = Object.keys(obj);
if (!keys.length) return "-";
return (
"<div class='cell-object'>" +
keys
.map(function (k) {
var val = obj[k];
var inner;
if (
val != null &&
typeof val === "object" &&
!Array.isArray(val) &&
Object.keys(val).length > 0
) {
inner = renderObjectAsLines(val, depth + 1);
} else {
inner = renderCell(val);
}
return (
"<div class='cell-kv-line'><span class='cell-k'>" +
esc(label(k)) +
"</span><span class='cell-v'>" +
inner +
"</span></div>"
);
})
.join("") +
"</div>"
);
}
function renderCell(v) {
if (v == null || v === "") return "-";
if (typeof v === "boolean") return v ? "是" : "否";
if (typeof v === "number") return String(v);
if (typeof v === "object") {
if (Array.isArray(v)) {
if (!v.length) return "-";
var allScalar = v.every(function (x) {
return (
x == null ||
typeof x === "string" ||
typeof x === "number" ||
typeof x === "boolean"
);
});
if (allScalar) {
return v.map(renderScalarForCell).join("、");
}
return "共 " + v.length + " 条";
}
return renderObjectAsLines(v, 0);
}
return esc(v);
}
// 传入 keys 时:渲染所有键,无值显示 -;未传 keys 时:按对象自身键渲染
function kvTable(obj, keys) {
if (!obj || typeof obj !== "object")
return "<p class='empty-hint'>暂无数据</p>";
var keyList = keys && keys.length ? keys : Object.keys(obj);
var rows = keyList.map(function (k) {
return (
"<tr><th>" +
esc(label(k)) +
"</th><td>" +
renderCell(obj[k]) +
"</td></tr>"
);
});
return rows.length
? "<div class='kv-table-wrap'><table class='kv-table kv-table-vertical qy-kv-table'><tbody>" +
rows.join("") +
"</tbody></table></div>"
: "<p class='empty-hint'>暂无数据</p>";
}
// 多列键值表(与 Vue KvGrid 一致:每行最多 2 组 label/value
function kvGrid(obj, keys, cols, labelOverrides) {
if (!obj || typeof obj !== "object")
return "<p class='empty-hint'>暂无数据</p>";
var keyList = keys && keys.length ? keys : Object.keys(obj);
var overrides = labelOverrides || {};
var pairs = keyList.map(function (k) {
return {
k: k,
label: overrides[k] || label(k),
val: renderCell(obj[k]),
};
});
if (!pairs.length) return "<p class='empty-hint'>暂无数据</p>";
var pairsPerRow = Math.max(1, Math.min(2, cols || 2));
var html =
"<table class='qy-info-grid qy-kv-table'><tbody>";
for (var i = 0; i < pairs.length; i += pairsPerRow) {
html += "<tr>";
for (var j = 0; j < pairsPerRow; j += 1) {
var p = pairs[i + j];
if (p) {
html +=
"<th>" +
esc(p.label) +
"</th><td>" +
p.val +
"</td>";
} else {
html +=
"<th class='qy-info-grid-filler'></th><td class='qy-info-grid-filler'></td>";
}
}
html += "</tr>";
}
html += "</tbody></table>";
return html;
}
function itemList(arr, fn) {
if (!Array.isArray(arr) || !arr.length)
return "<p class='empty-hint'>暂无数据</p>";
var html =
"<p class='count-hint'>共 " +
arr.length +
" 条</p><ul class='item-list'>";
arr.forEach(function (item, i) {
html +=
"<li>" +
(fn
? fn(item, i)
: typeof item === "object"
? JSON.stringify(item)
: esc(item)) +
"</li>";
});
return html + "</ul>";
}
function renderBasic(v) {
if (!v) return "<p class='empty-hint'>暂无基础信息</p>";
// 与《企业全量信息核验V2_返回字段说明》BASIC 字段顺序对应
var keys = [
"entName",
"creditCode",
"regNo",
"orgCode",
"entType",
"entTypeCode",
"entityTypeCode",
"establishDate",
"registeredCapital",
"regCap",
"regCapCurrency",
"regCapCurrencyCode",
"regOrg",
"regOrgCode",
"regProvince",
"provinceCode",
"regCity",
"regCityCode",
"regDistrict",
"districtCode",
"address",
"postalCode",
"legalRepresentative",
"compositionForm",
"approvedBusinessItem",
"status",
"statusCode",
"operationPeriodFrom",
"operationPeriodTo",
"approveDate",
"cancelDate",
"revokeDate",
"cancelReason",
"revokeReason",
"oldNames",
"lastAnnuReportYear",
];
var o = {};
for (var k in v) o[k] = v[k];
// 曾用名:统一为字符串展示,无数据时显示 -(保证「曾用名」行始终出现)
o.oldNames =
v.oldNames &&
Array.isArray(v.oldNames) &&
v.oldNames.length
? v.oldNames.join("")
: v.oldNames || "-";
var html = kvGrid(o, keys, 2);
// 经营业务范围:单独占一整行,避免挤在多列网格里
html +=
"<h3>经营业务范围</h3>" +
"<div class='item-block'>" +
esc(v.businessScope || "-") +
"</div>";
var bl = reportData && reportData.basicList;
if (Array.isArray(bl)) {
if (bl.length) {
html +=
"<h3 class='report-subheading qy-sub-heading'>关联主体列表</h3>";
if (bl.length > 1) {
html +=
"<p class='count-hint'>共 " +
bl.length +
" 个关联主体</p>";
}
bl.forEach(function (b, i) {
html += "<div class='record-card'>";
if (bl.length > 1) {
html +=
"<p class='count-hint'>主体 " +
(i + 1) +
"</p>";
}
html += kvGrid(
normalizeBasicListRecord(b),
basicListGridKeys,
2,
basicListLabelOverrides,
);
html += "</div>";
});
} else {
html +=
"<p class='empty-hint'>暂无关联主体信息</p>";
}
}
return html;
}
function renderBranches(v) {
return itemList(v, function (b) {
return (
"<div class='item-title'>" +
esc(
recordGet(
b,
"name",
"entName",
"ENTNAME",
"branchName",
),
) +
"</div>" +
"<div class='item-meta'>注册号:" +
esc(recordGet(b, "regNo", "REGNO")) +
";信用代码:" +
esc(
recordGet(
b,
"creditCode",
"CREDITNO",
"SHXYDM",
),
) +
";登记机关:" +
esc(recordGet(b, "regOrg", "REGORG")) +
"</div>"
);
});
}
function renderShareholding(v) {
if (!v) return "<p class='empty-hint'>暂无股权信息</p>";
var arDj12 =
reportData &&
Array.isArray(reportData.annualReports) &&
reportData.annualReports.length > 0;
var html = "";
var summaryKeys = [
"shareholderCount",
"registeredCapital",
"currency",
"topHolderName",
"topHolderPercent",
"top5TotalPercent",
"hasEquityPledges",
];
var hasAny = summaryKeys.some(function (k) {
return (
v[k] !== undefined && v[k] !== null && v[k] !== ""
);
});
if (hasAny) {
var rows = [];
// 股东总数
if (v.shareholderCount != null) {
rows.push({
k: "股东总数",
v: esc(v.shareholderCount),
});
}
// 注册资本(加单位元)
if (v.registeredCapital != null) {
rows.push({
k: "注册资本",
v: esc(v.registeredCapital) + " 元",
});
}
// 币种
if (v.currency) {
rows.push({
k: "币种",
v: esc(v.currency),
});
}
// 第一大股东及持股
if (v.topHolderName || v.topHolderPercent != null) {
rows.push({
k: "第一大股东",
v: esc(v.topHolderName || "-"),
});
if (v.topHolderPercent != null) {
rows.push({
k: "第一大股东持股",
v:
(v.topHolderPercent * 100).toFixed(1) +
"%",
});
}
}
// 前五大合计
if (v.top5TotalPercent != null) {
rows.push({
k: "前五大合计",
v: (v.top5TotalPercent * 100).toFixed(1) + "%",
});
}
// 是否存在股权出质
if (v.hasEquityPledges != null) {
rows.push({
k: "存在股权出质",
v: v.hasEquityPledges ? "是" : "否",
});
}
if (rows.length) {
var t =
"<div class='kv-table-wrap'><table class='kv-table kv-table-vertical qy-kv-table'><tbody>" +
rows
.map(function (r) {
return (
"<tr><th>" +
esc(r.k) +
"</th><td>" +
esc(r.v) +
"</td></tr>"
);
})
.join("") +
"</tbody></table></div>";
html += "<h3>股权汇总</h3>" + t;
}
}
// 股东明细:优先展示来自股权全景/全量信息的有效字段,避免大段空白
html +=
"<h3>股东明细</h3>" +
itemList(v.shareholders, function (sh) {
var pct =
(sh.ownershipPercent != null
? sh.ownershipPercent
: sh.sharePercent) || 0;
var title =
"<div class='item-title'>" +
esc(sh.name) +
"" +
(pct * 100).toFixed(1) +
"%</div>";
var parts = [];
if (sh.type) {
parts.push("类型:" + esc(sh.type));
}
if (
sh.subscribedAmount != null &&
sh.subscribedAmount !== ""
) {
var sub = "认缴:" + esc(sh.subscribedAmount);
if (
sh.subscribedCurrency ||
sh.subscribedCurrencyCode
) {
sub +=
" " +
esc(sh.subscribedCurrency || "") +
(sh.subscribedCurrencyCode
? "" +
esc(sh.subscribedCurrencyCode) +
""
: "");
}
parts.push(sub);
}
if (sh.subscribedDate) {
parts.push("认缴日:" + esc(sh.subscribedDate));
}
if (
sh.subscribedMethod ||
sh.subscribedMethodCode
) {
var subm =
"认缴方式:" +
esc(sh.subscribedMethod || "");
if (sh.subscribedMethodCode) {
subm +=
"" +
esc(sh.subscribedMethodCode) +
"";
}
parts.push(subm);
}
if (sh.paidAmount != null && sh.paidAmount !== "") {
parts.push("实缴:" + esc(sh.paidAmount));
}
if (sh.paidDate) {
parts.push("实缴日:" + esc(sh.paidDate));
}
if (sh.paidMethod) {
parts.push("实缴方式:" + esc(sh.paidMethod));
}
if (sh.creditCode) {
parts.push(
"股东信用代码:" + esc(sh.creditCode),
);
}
if (sh.regNo) {
parts.push("股东注册号:" + esc(sh.regNo));
}
if (sh.isHistory != null) {
parts.push(
"是否历史:" + (sh.isHistory ? "是" : "否"),
);
}
var meta =
parts.length > 0
? "<div class='item-meta'>" +
parts.join("") +
"</div>"
: "";
return title + meta;
});
html +=
"<h3>股权变更记录</h3>" +
itemList(v.equityChanges, function (e) {
var name = e.shareholderName;
// 如果是纯数字可能是内部ID在标题中不展示
var isId =
typeof name === "string" &&
/^[0-9]+$/.test(name);
return (
"<div class='item-title'>" +
esc(e.changeDate) +
(isId || !name
? ""
: " " + esc(e.shareholderName)) +
"</div>" +
"<div class='item-meta'>变更前:" +
esc(e.percentBefore) +
" → 变更后:" +
esc(e.percentAfter) +
"</div>"
);
});
html +=
"<h3>股权出质</h3>" +
itemList(v.equityPledges, function (e) {
return (
"<div class='item-title'>" +
esc(e.regNo) +
" " +
esc(e.pledgor) +
" → " +
esc(e.pledgee) +
"</div>" +
"<div class='item-meta'>出质数额:" +
esc(e.pledgedAmount) +
" 元" +
";登记日:" +
esc(e.regDate) +
";状态:" +
esc(e.status) +
"</div>"
);
});
html += "<h3>实缴出资明细</h3>";
if (
arDj12 &&
(!Array.isArray(v.paidInDetails) ||
!v.paidInDetails.length)
) {
html +=
"<p class='empty-hint'>暂无实缴/应缴明细。</p>";
} else {
html += itemList(v.paidInDetails, function (e) {
return (
"<div class='item-title'>" +
esc(e.investor) +
"</div>" +
"<div class='item-meta'>日期:" +
esc(e.paidDate) +
";方式:" +
esc(e.paidMethod) +
";累计实缴:" +
esc(e.accumulatedPaid) +
" 万元" +
"</div>"
);
});
}
html += "<h3>认缴出资明细</h3>";
if (
arDj12 &&
(!Array.isArray(v.subscribedCapitalDetails) ||
!v.subscribedCapitalDetails.length)
) {
html +=
"<p class='empty-hint'>暂无认缴出资明细。</p>";
} else {
html += itemList(
v.subscribedCapitalDetails,
function (e) {
return (
"<div class='item-title'>" +
esc(e.investor) +
"</div>" +
"<div class='item-meta'>认缴日:" +
esc(e.subscribedDate) +
";方式:" +
esc(e.subscribedMethod) +
";累计认缴:" +
esc(e.accumulatedSubscribed) +
" 元" +
"</div>"
);
},
);
}
return html;
}
function renderController(v) {
if (!v) return "<p class='empty-hint'>暂无实控人信息</p>";
var html = kvTable(v, [
"id",
"name",
"type",
"percent",
"reason",
"source",
]);
if (v.path && typeof v.path === "object") {
html += "<h3>控制路径</h3>";
if (v.path.nodes && v.path.nodes.length) {
html +=
"<p class='count-hint'>节点 " +
v.path.nodes.length +
" 个</p>";
v.path.nodes.forEach(function (n) {
var id =
n.entityId != null ? n.entityId : n.uid;
html +=
"<div class='item-block'><span class='k'>" +
esc(n.label) +
"</span>" +
esc(n.name) +
(id != null
? "id: " + esc(id) + ""
: "") +
"</div>";
});
}
if (v.path.links && v.path.links.length) {
html +=
"<p class='count-hint'>链接 " +
v.path.links.length +
" 条</p>";
}
}
return html;
}
function renderBeneficiaries(v) {
return itemList(v, function (b) {
var pct = "-";
if (b.percent != null && b.percent !== 0) {
pct = (b.percent * 100).toFixed(1) + "%";
}
var title =
"<div class='item-title'>" +
esc(b.name) +
(pct !== "-" ? "" + pct + "" : "") +
"</div>";
var parts = [];
if (b.type) {
parts.push("类型:" + esc(b.type));
}
if (b.reason) {
parts.push("原因:" + esc(b.reason));
}
var meta =
parts.length > 0
? "<div class='item-meta'>" +
parts.join("") +
"</div>"
: "";
return title + meta;
});
}
function renderInvestments(v) {
if (!v) return "<p class='empty-hint'>暂无投资信息</p>";
var html =
"<h3>汇总</h3>" +
kvTable(v, ["totalCount", "totalAmount"]);
html +=
"<h3>对外投资列表</h3>" +
itemList(v.list || v.entities, function (inv) {
var pct =
inv.investPercent != null
? (inv.investPercent * 100).toFixed(1)
: "-";
var title =
"<div class='item-title'>" +
esc(inv.entName) +
"" +
pct +
"%</div>";
var parts = [];
if (inv.creditCode) {
parts.push("信用代码:" + esc(inv.creditCode));
}
if (
inv.investAmount != null &&
inv.investAmount !== ""
) {
var amt =
"投资额:" + esc(inv.investAmount) + " 万";
if (inv.investCurrency) {
amt +=
"(单位:" +
esc(inv.investCurrency) +
"";
}
parts.push(amt);
}
if (inv.entStatus) {
parts.push("状态:" + esc(inv.entStatus));
}
var meta =
parts.length > 0
? "<div class='item-meta'>" +
parts.join("") +
"</div>"
: "";
return title + meta;
});
html +=
"<h3>法定代表人个人对外投资</h3>" +
itemList(
v.legalRepresentativeInvestments ||
v.legalPersonInvestments,
function (inv) {
var pct = "-";
if (
inv.investPercent != null &&
Number(inv.investPercent) !== 0
) {
pct =
(inv.investPercent * 100).toFixed(1) +
"%";
}
var title =
"<div class='item-title'>" +
esc(inv.entName) +
(pct !== "-" ? "" + pct + "" : "") +
"</div>";
var parts = [];
if (inv.creditCode) {
parts.push(
"信用代码:" + esc(inv.creditCode),
);
}
// 优先展示投资额,其次在无投资额但有注册资本时展示注册资本
if (
inv.investAmount != null &&
inv.investAmount !== "" &&
Number(inv.investAmount) !== 0
) {
parts.push(
"投资额:" +
esc(inv.investAmount) +
" 万",
);
} else if (
inv.regCap != null &&
inv.regCap !== "" &&
Number(inv.regCap) !== 0
) {
parts.push(
"注册资本:" +
esc(inv.regCap) +
" 万(企业注册资本)",
);
}
if (inv.entStatus) {
parts.push("状态:" + esc(inv.entStatus));
}
var meta =
parts.length > 0
? "<div class='item-meta'>" +
parts.join("") +
"</div>"
: "";
return title + meta;
},
);
return html;
}
function renderGuarantees(v) {
if (!Array.isArray(v) || !v.length) {
return "<p class='empty-hint'>暂无对外担保信息</p>";
}
// 只展示有关键字段的记录,避免一堆空行
var coreKeys = [
"mortgagor",
"creditor",
"principalAmount",
"principalKind",
"guaranteeType",
"periodFrom",
"periodTo",
];
var list = v.filter(function (g) {
return coreKeys.some(function (k) {
return g[k] != null && g[k] !== "";
});
});
if (!list.length) {
return (
"<p class='empty-hint'>存在 " +
v.length +
" 条对外担保记录,但债务人、债权人等关键字段为空,暂无法展示详细明细。</p>"
);
}
return (
"<p class='count-hint'>共 " +
list.length +
" 条</p>" +
itemList(list, function (g) {
return (
"<div class='item-title'>" +
esc(g.mortgagor || "-") +
" → " +
esc(g.creditor || "-") +
"</div>" +
"<div class='item-meta'>主债权数额:" +
esc(g.principalAmount || "-") +
";种类:" +
esc(g.principalKind || "-") +
";保证方式:" +
esc(g.guaranteeType || "-") +
";期限:" +
esc(g.periodFrom || "-") +
" 至 " +
esc(g.periodTo || "-") +
"</div>"
);
})
);
}
function renderManagement(v) {
if (!v) return "<p class='empty-hint'>暂无人员信息</p>";
var html =
"<h3>高管列表</h3>" +
itemList(v.executives, function (e) {
return (
"<div class='item-title'>" +
esc(e.name) +
"</div><div class='item-meta'>" +
esc(formatPosition(e.position)) +
"</div>"
);
});
html += "<h3>从业与社保</h3>";
html += kvTable(v, [
"employeeCount",
"femaleEmployeeCount",
"socialSecurity",
]);
html += "<h3>法定代表人其他任职</h3>";
var others =
v.legalRepresentativeOtherPositions ||
v.legalPersonOtherPositions;
html += itemList(others, function (f) {
var title =
"<div class='item-title'>" +
esc(f.entName) +
"</div>";
var parts = [];
if (f.position) {
parts.push(
"职务:" + esc(formatPosition(f.position)),
);
}
if (f.name) {
parts.push("姓名:" + esc(f.name));
}
if (f.entStatus) {
parts.push("企业状态:" + esc(f.entStatus));
}
if (f.creditCode) {
parts.push("信用代码:" + esc(f.creditCode));
}
if (f.regNo) {
parts.push("注册号:" + esc(f.regNo));
}
var meta =
parts.length > 0
? "<div class='item-meta'>" +
parts.join("") +
"</div>"
: "";
return title + meta;
});
return html;
}
function renderAssets(v) {
if (!v) {
return "<p class='empty-hint'>暂无资产数据</p>";
}
var years = Array.isArray(v.years) ? v.years : [];
if (!years.length) {
return "<p class='empty-hint'>暂无资产数据</p>";
}
// 过滤掉所有核心字段都为空的年度记录,避免一堆空行
var coreKeys = [
"assetTotal",
"revenueTotal",
"netProfit",
"liabilityTotal",
"equityTotal",
"profitTotal",
];
var list = years.filter(function (y) {
return coreKeys.some(function (k) {
return y[k] != null && y[k] !== "";
});
});
if (!list.length) {
return "<p class='empty-hint'>存在资产经营记录,但资产总额、营收、净利润等核心字段为空,暂无法展示详细明细。</p>";
}
return (
"<h3>按年度资产经营数据</h3>" +
itemList(list, function (y) {
return (
"<div class='item-title'>" +
esc(y.year) +
" " +
esc(y.reportDate || "") +
"</div>" +
"<div class='item-meta'>资产总额:" +
esc(y.assetTotal || "-") +
";营收:" +
esc(y.revenueTotal || "-") +
";净利润:" +
esc(y.netProfit || "-") +
";负债:" +
esc(y.liabilityTotal || "-") +
"</div>"
);
})
);
}
function renderLicenses(v) {
if (!v) return "<p class='empty-hint'>暂无许可信息</p>";
var html =
"<h3>行政许可列表</h3>" +
itemList(v.permits, function (p) {
return (
"<div class='item-title'>" +
esc(p.name) +
"</div>" +
"<div class='item-meta'>有效期:" +
esc(p.valFrom) +
" ~ " +
esc(p.valTo) +
";许可机关:" +
esc(p.licAnth) +
"" +
esc(p.licItem) +
"</div>"
);
});
html +=
"<h3>许可变更记录</h3>" +
itemList(v.permitChanges, function (p) {
return (
"<div class='item-title'>" +
esc(p.changeDate) +
" " +
esc(p.changeType) +
"</div>" +
"<div class='item-meta'>变更前:" +
(p.detailBefore || "-").substring(0, 80) +
"…;变更后:" +
(p.detailAfter || "-").substring(0, 80) +
"…</div>"
);
});
html +=
"<h3>知识产权出质</h3>" +
itemList(v.ipPledges, function (i) {
return (
"<div class='item-block'>" +
(typeof i === "object"
? JSON.stringify(i)
: esc(i)) +
"</div>"
);
});
return html;
}
function renderActivities(v) {
if (!v) return "<p class='empty-hint'>暂无经营活动数据</p>";
var arDj12 =
reportData &&
Array.isArray(reportData.annualReports) &&
reportData.annualReports.length > 0;
// 招投标:对角色、类型做友好映射
var bidRoleMap = {
10: "招标人/采购人",
20: "中标人/供应商",
30: "代理机构",
};
var announceTypeMap = {
10: "招标公告",
20: "中标结果公告",
};
var html =
"<h3>招投标</h3>" +
itemList(v.bids, function (b) {
var t =
b.announcetitle ||
b.ANNOUNCETITLE ||
b.title ||
"-";
var roleCode = b.bidrole || b.BIDROLE;
var roleText =
bidRoleMap[Number(roleCode)] ||
(roleCode != null && roleCode !== ""
? "角色代码:" + roleCode
: "-");
var typeCode =
b.announceType ||
b.annoucetype ||
b.ANNOUNCETYPE;
var typeText =
announceTypeMap[Number(typeCode)] ||
(typeCode != null && typeCode !== ""
? "类型代码:" + typeCode
: "-");
return (
"<div class='item-title'>" +
esc(t) +
"</div>" +
"<div class='item-meta'>角色:" +
esc(roleText) +
";类型:" +
esc(typeText) +
"</div>"
);
});
html += "<h3>网站/网店</h3>";
if (
arDj12 &&
(!Array.isArray(v.websites) || !v.websites.length)
) {
html +=
"<p class='empty-hint'>暂无网站或网店信息。</p>";
} else {
html += itemList(v.websites, function (w) {
return (
"<div class='item-title'>" +
esc(w.websitname || w.WEBSITNAME || w.name) +
"</div>" +
"<div class='item-meta'>类型:" +
esc(w.webtype || w.WEBTYPE) +
";域名:" +
esc(w.domain || w.DOMAIN) +
"</div>"
);
});
}
return html;
}
function renderInspections(v) {
return itemList(v, function (i) {
return (
"<div class='item-title'>" +
esc(i.inspectDate) +
" " +
esc(i.result) +
"</div>" +
"<div class='item-meta'>数据类型:" +
esc(i.dataType) +
";检查机关:" +
esc(i.regOrg) +
"</div>"
);
});
}
function renderRisks(v) {
if (!v) return "<p class='empty-hint'>暂无风险数据</p>";
var html = kvGrid(
v,
[
"riskLevel",
"riskScore",
"dishonestDebtorCount",
"limitHighDebtorCount",
],
2,
);
var hasKeys = [
"hasCourtJudgments",
"hasJudicialAssists",
"hasDishonestDebtors",
"hasLimitHighDebtors",
"hasAdminPenalty",
"hasException",
"hasSeriousIllegal",
"hasTaxOwing",
"hasSeriousTaxIllegal",
"hasMortgage",
"hasEquityPledges",
"hasQuickCancel",
];
var hasLabels = [
"裁判文书",
"司法协助",
"失信被执行人",
"限高被执行人",
"行政处罚",
"经营异常",
"严重违法",
"欠税",
"重大税收违法",
"动产抵押",
"股权出质",
"简易注销",
];
html += "<h3>风险项</h3><ul class='item-list'>";
hasKeys.forEach(function (k, idx) {
var val = v[k];
if (val === undefined)
val =
v[
k
.replace("Judgments", "JudicialCase")
.replace("Assists", "JudicialAid")
.replace("EquityPledges", "StockPawn")
];
html +=
"<li class='risk-row'><span>" +
hasLabels[idx] +
"</span><span class='" +
(val ? "bad" : "ok") +
"'>" +
(val ? "存在" : "未发现") +
"</span></li>";
});
html += "</ul>";
html +=
"<h3>裁判文书</h3>" +
itemList(
v.courtJudgments || v.judicialCases,
function (c) {
return (
"<div class='item-title'>" +
esc(
c.caseNumber ||
c.CASENUMBER ||
c.judicialDocumentId,
) +
"</div>" +
"<div class='item-meta'>案由:" +
esc(c.caseCause || c.CASECAUSE) +
";结果:" +
esc(c.judgeResult || c.JUDGERESULT) +
"</div>"
);
},
);
html +=
"<h3>司法协助</h3>" +
itemList(
v.judicialAssists || v.judicialAids,
function (a) {
return (
"<div class='item-title'>" +
esc(a.iname || a.INAME || a.marketName) +
"</div>" +
"<div class='item-meta'>法院:" +
esc(a.courtName || a.COURTNAME) +
";股权数额:" +
esc(a.shaream || a.SHAREAM) +
" 元</div>"
);
},
);
html +=
"<h3>失信被执行人</h3>" +
itemList(v.dishonestDebtors, function (d) {
return (
"<div class='item-title'>案号 " +
esc(d.caseNo) +
"</div>" +
"<div class='item-meta'>执行法院:" +
esc(d.execCourt) +
";履行情况:" +
esc(d.performanceStatus) +
";发布时间:" +
esc(d.publishDate) +
"</div>"
);
});
html +=
"<h3>限高被执行人</h3>" +
itemList(v.limitHighDebtors, function (x) {
return (
"<div class='item-title'>" +
esc(x.ah || x.caseNo) +
"</div>" +
"<div class='item-meta'>执行法院:" +
esc(x.zxfy || x.execCourt) +
"" +
esc(x.fbrq || x.publishDate) +
"</div>"
);
});
// 按案件类型展示涉诉信息(来自企业司法认证 entout
if (v.litigation && v.litigation.totalCases) {
var lit = v.litigation;
var typeLabels = {
administrative: "行政案件",
implement: "执行案件",
preservation: "非诉保全审查",
civil: "民事案件",
criminal: "刑事案件",
bankrupt: "强制清算与破产案件",
jurisdict: "管辖案件",
compensate: "赔偿案件",
};
html += "<h3>涉诉案件汇总</h3>";
var rows = [];
Object.keys(typeLabels).forEach(function (k) {
var sec = lit[k];
if (sec && sec.count) {
rows.push({
k: typeLabels[k],
v: sec.count + " 件",
});
}
});
if (rows.length) {
html +=
"<div class='kv-table-wrap'><table class='kv-table kv-table-vertical qy-kv-table'><tbody>" +
rows
.map(function (r) {
return (
"<tr><th>" +
esc(r.k) +
"</th><td>" +
esc(r.v) +
"</td></tr>"
);
})
.join("") +
"</tbody></table></div>";
}
Object.keys(typeLabels).forEach(function (k) {
var sec = lit[k];
if (!sec || !sec.cases || !sec.cases.length) return;
html +=
"<h3>" +
typeLabels[k] +
"</h3>" +
itemList(sec.cases, function (c) {
return (
"<div class='item-title'>" +
esc(c.caseNo) +
"" +
esc(c.court) +
"</div>" +
"<div class='item-meta'>" +
"地区:" +
esc(c.region) +
";审级:" +
esc(c.trialLevel) +
";案件类型:" +
esc(c.caseType) +
";案由:" +
esc(c.cause) +
";标的金额:" +
esc(c.amount) +
";立案日期:" +
esc(c.filingDate) +
";裁判日期:" +
esc(c.judgmentDate) +
";胜败结果:" +
esc(c.victoryResult) +
"</div>"
);
});
});
}
html +=
"<h3>行政处罚</h3>" +
itemList(v.adminPenalties, function (p) {
return (
"<div class='item-title'>" +
esc(p.pendecno || p.PENDECNO) +
"</div>" +
"<div class='item-meta'>违法类型:" +
esc(p.illegacttype || p.ILLEGACTTYPE) +
";机关:" +
esc(p.penauth || p.PENAUTH) +
";日期:" +
esc(p.pendecissdate || p.PENDECISSDATE) +
"</div>"
);
});
html +=
"<h3>行政处罚变更记录</h3>" +
itemList(v.adminPenaltyUpdates, function (p) {
return (
"<div class='item-title'>" +
esc(p.updateDate) +
"</div><div class='item-meta'>" +
esc(p.updateContent) +
"</div>"
);
});
html +=
"<h3>经营异常</h3>" +
itemList(v.exceptions, function (e) {
var inR = recordGet(e, "inreason", "INREASON");
return (
"<div class='item-title'>" +
esc(
recordGet(e, "entName", "ENTNAME") ||
recordGet(e, "indate", "INDATE"),
) +
"</div>" +
"<div class='item-meta'>列入日期:" +
esc(recordGet(e, "indate", "INDATE")) +
";原因:" +
esc(formatInReason(inR)) +
";移出:" +
esc(recordGet(e, "outdate", "OUTDATE")) +
";列入机关:" +
esc(recordGet(e, "YR_REGORG", "yrRegOrg")) +
"</div>"
);
});
html +=
"<h3>严重违法</h3>" +
itemList(v.seriousIllegals, function (s) {
return (
"<div class='item-block'>" +
(typeof s === "object"
? JSON.stringify(s)
: esc(s)) +
"</div>"
);
});
html +=
"<h3>动产抵押</h3>" +
itemList(v.mortgages, function (m) {
return (
"<div class='item-title'>" +
esc(m.regNo) +
" " +
esc(m.guaranteedAmount) +
"</div>" +
"<div class='item-meta'>登记日:" +
esc(m.regDate) +
";机关:" +
esc(m.regOrg) +
";状态:" +
esc(m.status) +
"</div>"
);
});
html += "<h3>简易注销</h3>";
if (v.quickCancel) {
html += kvTable(v.quickCancel, [
"entName",
"creditCode",
"regNo",
"regOrg",
"noticeFromDate",
"noticeToDate",
"cancelResult",
]);
if (
v.quickCancel.dissents &&
v.quickCancel.dissents.length
) {
html +=
"<p class='count-hint'>异议 " +
v.quickCancel.dissents.length +
" 条</p>";
v.quickCancel.dissents.forEach(function (d) {
html +=
"<div class='item-block'>" +
esc(d.dissentOrg) +
"" +
esc(d.dissentDes) +
" " +
esc(d.dissentDate) +
"</div>";
});
}
} else {
html += "<p class='empty-hint'>无</p>";
}
html += "<h3>清算信息</h3>";
if (v.liquidation) {
html += kvTable(v.liquidation, [
"principal",
"members",
]);
} else {
html += "<p class='empty-hint'>无</p>";
}
html += "<h3>纳税与欠税</h3>";
var tax = v.taxRecords;
if (tax) {
html +=
"<p class='count-hint'>纳税A级年度" +
(tax.taxLevelAYears && tax.taxLevelAYears.length
? tax.taxLevelAYears.length + " 条"
: "无") +
";欠税:" +
(tax.taxOwings && tax.taxOwings.length
? tax.taxOwings.length + " 条"
: "无") +
"</p>";
html += itemList(tax.taxOwings, function (t) {
return (
"<div class='item-title'>" +
esc(t.taxOwedType || t.taxowedtype) +
"</div>" +
"<div class='item-meta'>合计:" +
esc(t.totalOwedAmount || t.totalowedamount) +
";公示日:" +
esc(t.publishDate || t.publishdate) +
"</div>"
);
});
} else {
html += "<p class='empty-hint'>无</p>";
}
return html;
}
function renderRiskOverview(v) {
if (!v) return "<p class='empty-hint'>暂无风险分析</p>";
var html = "";
if (
v.riskScore != null ||
(v.riskLevel != null && v.riskLevel !== "")
) {
html +=
"<h3 class='report-subheading qy-sub-heading'>综合风险</h3>" +
kvGrid(v, ["riskScore", "riskLevel"], 2);
}
if (v.tags && v.tags.length) {
html += "<h3>标签</h3><p>";
v.tags.forEach(function (t) {
var s = normalizeRiskTag(t);
if (s) {
html +=
"<span class='tag'>" + esc(s) + "</span> ";
}
});
html += "</p>";
}
// 风险点清单:命中 / 未命中,多列展示
if (Array.isArray(v.items) && v.items.length) {
html += "<h3>风险点一览</h3>";
html += "<div class='risk-points-grid'>";
v.items.forEach(function (it) {
var statusText = it.hit ? "命中" : "未命中";
var statusClass = it.hit
? "risk-status-bad"
: "risk-status-ok";
html +=
"<div class='risk-point'><span class='risk-point-name'>" +
esc(it.name) +
"</span><span class='" +
statusClass +
"'>" +
statusText +
"</span></div>";
});
html += "</div>";
}
if (!html) {
return "<p class='empty-hint'>暂无风险分析明细</p>";
}
return html;
}
function renderTimeline(v) {
if (!v || !v.length)
return "<p class='empty-hint'>暂无发展时间线</p>";
return itemList(v, function (item) {
var title =
"<div class='item-title'>" +
esc(item.date) +
" " +
esc(item.type) +
"</div>";
var body = "<div>" + esc(item.title || "") + "</div>";
var meta = "";
if (item.detailBefore || item.detailAfter) {
// 对长文本拆成更易读的多行段落
var before = (item.detailBefore || "")
.replace(//g, "<br/>")
.replace(/+/g, "");
var after = (item.detailAfter || "")
.replace(//g, "<br/>")
.replace(/+/g, "");
meta =
"<div class='item-meta'>" +
"<div><strong>变更前:</strong><br/>" +
before +
"</div>" +
"<div style='margin-top:4px'><strong>变更后:</strong><br/>" +
after +
"</div>" +
"</div>";
}
return title + body + meta;
});
}
function renderListed(v) {
if (!v)
return "<p class='empty-hint'>非上市或暂无上市信息</p>";
var html =
"<h3>是否上市</h3><p>" +
(v.isListed ? "是" : "否") +
"</p>";
if (v.company) {
html +=
"<h3>上市公司信息</h3>" +
kvTable(v.company, [
"bizScope",
"creditCode",
"regAddr",
"regCapital",
"orgCode",
"cur",
"curName",
]);
}
html += "<h3>股票信息</h3>";
if (v.stock)
html +=
"<div class='item-block'>" +
(typeof v.stock === "object"
? JSON.stringify(v.stock)
: esc(v.stock)) +
"</div>";
else html += "<p class='empty-hint'>无</p>";
html +=
"<h3>十大股东</h3>" +
itemList(v.topShareholders, function (s) {
return (
"<div class='item-title'>" +
esc(s.name || s.shaname) +
"</div>" +
"<div class='item-meta'>" +
esc(s.percent || s.fundedratio) +
"</div>"
);
});
html +=
"<h3>上市高管</h3>" +
itemList(v.listedManagers, function (m) {
return (
"<div class='item-title'>" +
esc(m.name || m.perName) +
"</div>" +
"<div class='item-meta'>" +
esc(m.position) +
"</div>"
);
});
return html;
}
function renderScores(v) {
if (!v) return "<p class='empty-hint'>暂无评分</p>";
var html = kvTable(v, [
"overallScore",
"stabilityScore",
"equityTransparencyScore",
"complianceScore",
"activityScore",
]);
if (v.tags && v.tags.length) {
html += "<h3>标签</h3><p>";
v.tags.forEach(function (t) {
html += "<span class='tag'>" + esc(t) + "</span> ";
});
html += "</p>";
}
return html;
}
function renderBasicList(v) {
return itemList(v, function (b) {
return (
"<div class='item-title'>" +
esc(b.entName || b.entname) +
"</div>" +
"<div class='item-meta'>信用代码:" +
esc(b.creditCode || b.creditNo || b.creditno) +
";注册号:" +
esc(b.regNo) +
"</div>"
);
});
}
function renderRaw(v) {
if (!v) return "<p class='empty-hint'>无原始数据</p>";
return (
"<details class='raw-section'><summary>展开 jiguangFull / judicialCertFull</summary>" +
"<pre>" +
esc(JSON.stringify(v, null, 2)).substring(0, 15000) +
(JSON.stringify(v).length > 15000
? "\n…已截断"
: "") +
"</pre></details>"
);
}
function renderCapiItemList(arr) {
if (!Array.isArray(arr) || !arr.length) return "";
return itemList(arr, function (r) {
var parts = [];
if (r.investType) parts.push("方式:" + esc(r.investType));
if (r.realCapi != null && r.realCapi !== "")
parts.push("实缴:" + esc(r.realCapi));
if (r.shoudCapi != null && r.shoudCapi !== "")
parts.push("应缴:" + esc(r.shoudCapi));
if (r.shouldCapi != null && r.shouldCapi !== "")
parts.push("应缴:" + esc(r.shouldCapi));
if (r.realCapiDate)
parts.push("实缴日:" + esc(r.realCapiDate));
if (r.shouldCapiDate)
parts.push("应缴日:" + esc(r.shouldCapiDate));
return (
"<div class='item-meta'>" +
(parts.length
? parts.join("")
: esc(JSON.stringify(r))) +
"</div>"
);
});
}
function renderAnnualShareholders(list) {
if (!Array.isArray(list) || !list.length) return "";
var html = "";
list.forEach(function (sh) {
html +=
"<div class='item-block' style='margin-bottom:8px'>";
html +=
"<div class='item-title'>" +
esc(sh.stockName || "-") +
"</div>";
var meta = [];
if (sh.identifyType)
meta.push("证件类型:" + esc(sh.identifyType));
if (sh.stockPercent)
meta.push("持股比例:" + esc(sh.stockPercent));
if (
sh.totalRealCapi != null &&
sh.totalRealCapi !== ""
)
meta.push("实缴总额:" + esc(sh.totalRealCapi));
if (
sh.totalShouldCapi != null &&
sh.totalShouldCapi !== ""
)
meta.push("应缴总额:" + esc(sh.totalShouldCapi));
if (meta.length)
html +=
"<div class='item-meta'>" +
meta.join("") +
"</div>";
if (sh.realCapiItems && sh.realCapiItems.length) {
html +=
"<h4 class='report-subheading qy-sub-heading'>实缴明细</h4>";
html += renderCapiItemList(sh.realCapiItems);
}
if (sh.shouldCapiItems && sh.shouldCapiItems.length) {
html +=
"<h4 class='report-subheading qy-sub-heading'>应缴明细</h4>";
html += renderCapiItemList(sh.shouldCapiItems);
}
html += "</div>";
});
return html;
}
function renderAnnualReports(arr) {
if (!Array.isArray(arr) || !arr.length)
return "<p class='empty-hint'>暂无企业年报数据</p>";
var sorted = arr.slice().sort(function (a, b) {
var ya = parseInt(a.reportYear, 10) || 0;
var yb = parseInt(b.reportYear, 10) || 0;
return yb - ya;
});
var html =
"<p class='count-hint'>共 " + sorted.length + " 份</p>";
sorted.forEach(function (y) {
html += "<div class='report-annual-shell'>";
html +=
"<h3 class='report-annual-card-title'>" +
esc(
y.reportName ||
(y.reportYear
? y.reportYear + " 年报"
: "年报"),
) +
"</h3>";
var sumKeys = [
"reportYear",
"reportDate",
"entityName",
"creditCode",
"registerCode",
"organizationCode",
"entityStatus",
"address",
"telephone",
"postCode",
"entityBusinessScope",
"employeeCnt",
"entityPractitionerFemaleAmount",
"actualEmployeeCnt",
"mainBusinessIncome",
"netProfit",
"totalSalesAmount",
"totalNetProfit",
"totalAssets",
"totalLiabilities",
"totalOwnerEquity",
"totalTaxAmount",
"isWebsite",
"isInvest",
"isExternalGuarantee",
"isEquityTransfer",
"entityHoldingInfo",
"guaranteeInfo",
"actualRegistCapitalInfo",
"reportBranch",
"reportContributeInfo",
"reportRecordInfo",
];
html += kvGrid(y, sumKeys, 3);
if (y.reportWebsiteInfo && y.reportWebsiteInfo.length) {
html +=
"<h4 class='report-subheading qy-sub-heading'>网站或网店</h4>";
html += itemList(y.reportWebsiteInfo, function (w) {
return (
"<div class='item-meta'>" +
esc(w.websiteName) +
"" +
esc(w.websiteType) +
" " +
esc(w.websiteUrl) +
"</div>"
);
});
}
if (
y.reportShareholderInfo &&
y.reportShareholderInfo.length
) {
html +=
"<h4 class='report-subheading qy-sub-heading'>股东及出资</h4>";
html += renderAnnualShareholders(
y.reportShareholderInfo,
);
}
if (y.reportInvestInfo && y.reportInvestInfo.length) {
html +=
"<h4 class='report-subheading qy-sub-heading'>年报对外投资</h4>";
html += itemList(y.reportInvestInfo, function (inv) {
return (
"<div class='item-title'>" +
esc(inv.investName) +
"</div><div class='item-meta'>投资金额:" +
esc(inv.investCapi) +
";比例:" +
esc(inv.investPercent) +
"</div>"
);
});
}
if (
y.investInfo &&
Array.isArray(y.investInfo) &&
y.investInfo.length
) {
html +=
"<h4 class='report-subheading qy-sub-heading'>对外投资</h4>";
html += itemList(y.investInfo, function (inv) {
var parts = [];
if (
inv.investCapi != null &&
inv.investCapi !== ""
) {
parts.push(
"投资金额:" + esc(inv.investCapi),
);
}
if (
inv.investPercent != null &&
inv.investPercent !== ""
) {
parts.push(
"投资比例:" + esc(inv.investPercent),
);
}
if (
inv.investRegNo != null &&
inv.investRegNo !== ""
) {
parts.push(
"注册号/统一码:" +
esc(inv.investRegNo),
);
}
if (inv.entityId != null && inv.entityId !== "") {
parts.push(
"主体标识:" + esc(inv.entityId),
);
}
if (inv.seqNo != null && inv.seqNo !== "") {
parts.push("序号:" + esc(inv.seqNo));
}
var meta =
parts.length > 0
? parts.join("")
: "—";
return (
"<div class='item-title'>" +
esc(inv.investName || "—") +
"</div><div class='item-meta'>" +
meta +
"</div>"
);
});
}
if (
y.reportSocialSecurityInfo &&
typeof y.reportSocialSecurityInfo === "object" &&
!Array.isArray(y.reportSocialSecurityInfo)
) {
html +=
"<h4 class='report-subheading qy-sub-heading'>社会保险信息</h4>";
html += kvTable(
y.reportSocialSecurityInfo,
Object.keys(
y.reportSocialSecurityInfo,
).sort(),
);
}
if (
y.reportGuaranteeInfo &&
y.reportGuaranteeInfo.length
) {
html +=
"<h4 class='report-subheading qy-sub-heading'>对外担保</h4>";
html += itemList(y.reportGuaranteeInfo, function (g) {
return kvTable(g, Object.keys(g));
});
}
if (
y.reportEquityChangeInfo &&
y.reportEquityChangeInfo.length
) {
html +=
"<h4 class='report-subheading qy-sub-heading'>股权变更</h4>";
html += itemList(
y.reportEquityChangeInfo,
function (e) {
return (
"<div class='item-title'>" +
esc(e.name) +
"</div><div class='item-meta'>" +
esc(e.beforePercent) +
" → " +
esc(e.afterPercent) +
"" +
esc(e.changeDate) +
"</div>"
);
},
);
}
var ch =
y.reportChangeInfo ||
y.rpportChangeInfo;
if (ch) {
if (typeof ch === "string" && ch.trim()) {
html +=
"<h4 class='report-subheading qy-sub-heading'>年报变更摘要</h4><div class='item-block'>" +
esc(ch) +
"</div>";
} else if (Array.isArray(ch) && ch.length) {
html +=
"<h4 class='report-subheading qy-sub-heading'>年报修改记录</h4>";
html += itemList(ch, function (c) {
return (
"<div class='item-title'>" +
esc(c.updateItem) +
"</div><div class='item-meta'>" +
esc(c.updateDate) +
"</div><div class='item-block' style='margin-top:4px'><strong>修改前:</strong>" +
esc(c.beforeUpdate) +
"<br/><strong>修改后:</strong>" +
esc(c.afterUpdate) +
"</div>"
);
});
}
}
html += "</div>";
});
return html;
}
function renderTaxViolationsSection(v) {
if (!v || !Array.isArray(v.items) || !v.items.length)
return "<p class='empty-hint'>暂无税收违法记录</p>";
return itemList(v.items, function (it) {
return (
"<div class='item-title'>" +
esc(it.entityName) +
"</div>" +
"<div class='item-meta'>纳税人识别号:" +
esc(it.taxpayerCode) +
";案件性质:" +
esc(it.caseType) +
";企业类别:" +
esc(it.entityCategory) +
"</div>" +
"<div class='item-block' style='margin-top:6px'><div><strong>违法事实:</strong>" +
esc(it.illegalFact) +
"</div><div style='margin-top:4px'><strong>处罚依据:</strong>" +
esc(it.punishBasis) +
"</div></div>" +
"<div class='item-meta'>违法期间:" +
esc(it.illegalStartDate) +
" ~ " +
esc(it.illegalEndDate) +
";公示时间:" +
esc(it.illegalTime) +
"</div>" +
"<div class='item-meta'>公示机关:" +
esc(it.publishDepartment) +
";检查机关:" +
esc(it.checkDepartment) +
";所属税务机关:" +
esc(it.belongDepartment) +
"</div>" +
"<div class='item-meta'>移送公安:" +
esc(it.police || "—") +
";中介机构:" +
esc(it.agencyPersonInfo || "—") +
"</div>"
);
});
}
function renderOwnTaxNotices(v) {
if (!v || !Array.isArray(v.items) || !v.items.length)
return "<p class='empty-hint'>暂无欠税公告</p>";
return itemList(v.items, function (it) {
return (
"<div class='item-title'>" +
esc(it.taxCategory || it.taxpayerName) +
"</div>" +
"<div class='item-meta'>纳税人:" +
esc(it.taxpayerName) +
";识别号:" +
esc(it.taxIdNumber) +
";税务类型:" +
esc(it.taxType) +
"</div>" +
"<div class='item-meta'>欠税余额:" +
esc(it.ownTaxBalance) +
";欠税金额:" +
esc(it.ownTaxAmount) +
";新发欠税余额:" +
esc(it.newOwnTaxBalance) +
"</div>" +
"<div class='item-meta'>发布日期:" +
esc(it.publishDate) +
";主管税务机关:" +
esc(it.department) +
"</div>" +
"<div class='item-meta'>法定代表人:" +
esc(it.legalPersonName) +
"" +
esc(it.personIdName) +
"" +
esc(it.personIdNumber) +
"</div>"
);
});
}
function renderCustomsCredit(v) {
if (!v) return "<p class='empty-hint'>暂无海关信用信息</p>";
var html =
"<h3 class='report-subheading qy-sub-heading'>进出口信用概览</h3>" +
kvGrid(
v,
[
"total",
"hasAbnormal",
"hasPunish",
"isCancelled",
],
2,
);
if (Array.isArray(v.items) && v.items.length) {
html +=
"<h3 class='report-subheading qy-sub-heading'>海关信用明细</h3>";
v.items.forEach(function (it, i) {
html += "<div class='qy-kv-record'>";
if (v.items.length > 1) {
html +=
"<p class='count-hint'>" +
esc(
it.entityName ||
"明细 " + (i + 1),
) +
"</p>";
}
html += kvTable(
normalizeCustomsItem(it),
customsDetailKeysFor(it),
);
html += "</div>";
});
} else {
html += "<p class='empty-hint'>暂无海关信用明细</p>";
}
return html;
}
var sectionRenderers = {
basic: renderBasic,
branches: renderBranches,
shareholding: renderShareholding,
controller: renderController,
beneficiaries: renderBeneficiaries,
investments: renderInvestments,
guarantees: renderGuarantees,
management: renderManagement,
assets: renderAssets,
licenses: renderLicenses,
activities: renderActivities,
inspections: renderInspections,
risks: renderRisks,
timeline: renderTimeline,
listed: renderListed,
annualReports: renderAnnualReports,
taxViolations: renderTaxViolationsSection,
ownTaxNotices: renderOwnTaxNotices,
customsCredit: renderCustomsCredit,
};
function renderSectionContent(key, value) {
var fn = sectionRenderers[key];
if (fn) return fn(value);
if (value == null)
return "<p class='empty-hint'>暂无数据</p>";
if (Array.isArray(value))
return itemList(value, function (item) {
return typeof item === "object"
? "<div class='item-block'>" +
kvTable(item, Object.keys(item)) +
"</div>"
: esc(item);
});
if (typeof value === "object")
return kvTable(value, Object.keys(value));
return "<p>" + esc(value) + "</p>";
}
function formatSectionHeading(title) {
if (!title) return "";
return String(title)
.replace(/^[一二三四五六七八九十]+、\s*/, "")
.replace(/([^]+)/g, "—$1");
}
function render() {
var d = reportData;
document.getElementById("entName").textContent =
d.entName || "企业名称";
document.getElementById("creditCodeLabel").textContent =
"统一社会信用代码:" + (d.creditCode || "-");
document.getElementById("entStatusLabel").textContent =
"经营状态:" +
((d.basic && (d.basic.status || d.basic.entStatus)) ||
"-");
document.getElementById("entTypeLabel").textContent =
"企业类型:" + ((d.basic && d.basic.entType) || "-");
document.getElementById("reportTimeLabel").textContent =
"报告生成时间:" + (d.reportTime || "-");
// 使用 riskOverview 中的风险得分和等级替换原综合评分
var ro = d.riskOverview || {};
document.getElementById("overallScore").textContent =
ro.riskScore != null ? "综合评分:" + ro.riskScore : "-";
var level = ro.riskLevel || "-";
var pill = document.getElementById("riskLevelPill");
pill.textContent = "风险评估:" + level;
var levelStr = String(level ?? "").trim();
pill.className = "pill";
if (levelStr.includes("高")) pill.classList.add("high");
else if (levelStr.includes("中")) pill.classList.add("mid");
else pill.classList.add("low");
var tagsBox = document.getElementById("headerTags");
tagsBox.innerHTML = "";
(ro.tags || []).forEach(function (t) {
var s = normalizeRiskTag(t);
if (!s) return;
var span = document.createElement("span");
span.className = "tag";
span.textContent = s;
tagsBox.appendChild(span);
});
var nav = document.getElementById("navLinks");
nav.innerHTML = "";
sectionOrder.forEach(function (key) {
if (key === "raw") return;
var a = document.createElement("a");
a.href = "#section-" + key;
a.textContent = sectionTitles[key] || key;
nav.appendChild(a);
});
var container = document.getElementById("reportSections");
container.innerHTML = "";
var sectionIndex = 0;
sectionOrder.forEach(function (key) {
var value = d[key];
var card = document.createElement("div");
card.className = "section-card";
card.id = "section-" + key;
var title = document.createElement("h2");
var rawTitle = sectionTitles[key] || key;
var no = String(sectionIndex + 1).padStart(2, "0");
sectionIndex += 1;
title.innerHTML =
"<span class='section-no'>" +
no +
"</span><span class='section-title-cn'>" +
esc(formatSectionHeading(rawTitle)) +
"</span>";
card.appendChild(title);
var body = document.createElement("div");
body.className = "section-body";
body.innerHTML = renderSectionContent(key, value);
card.appendChild(body);
container.appendChild(card);
});
document.body.dataset.qyglReportReady = "1";
}
function loadReport() {
try {
// 使用后端注入的 reportData 直接渲染
if (!reportData || typeof reportData !== "object") {
reportData = {
entName: "未加载到数据",
basic: {},
risks: {},
riskOverview: {},
};
}
render();
} catch (e) {
console.error("渲染企业报告失败", e);
}
}
loadReport();
document.body.dataset.qyglReportReady = document.body.dataset.qyglReportReady || "1";
// 绑定「保存为 PDF」先轮询预生成状态再 GET /pdf服务端优先读缓存必要时现场生成
var saveBtn = document.getElementById("btnSavePdf");
var loadingOverlay = document.getElementById("pdfLoadingOverlay");
var pdfLoadingMessage = document.getElementById("pdfLoadingMessage");
function setPdfLoadingText(t) {
if (pdfLoadingMessage) pdfLoadingMessage.textContent = t;
}
function pollPdfStatusUntilReady(reportId, maxMs, intervalMs) {
return new Promise(function (resolve) {
var start = Date.now();
function tick() {
fetch(
"/reports/qygl/" +
encodeURIComponent(reportId) +
"/pdf/status",
)
.then(function (r) {
if (!r.ok) return { status: "error" };
return r.json();
})
.then(function (j) {
if (!j || !j.status) {
setPdfLoadingText("正在准备 PDF…");
if (Date.now() - start >= maxMs) {
resolve("timeout");
return;
}
setTimeout(tick, intervalMs);
return;
}
if (j.status === "ready") {
setPdfLoadingText("正在下载…");
resolve("ready");
return;
}
if (j.status === "none") {
setPdfLoadingText(
j.message ||
"正在生成 PDF请稍候…",
);
resolve("none");
return;
}
if (j.status === "failed") {
setPdfLoadingText(
j.message ||
"预生成未完成,正在现场生成…",
);
resolve("failed");
return;
}
if (j.status === "pending") {
setPdfLoadingText(
j.message || "排队生成 PDF…",
);
} else if (j.status === "generating") {
setPdfLoadingText(
j.message || "正在生成 PDF…",
);
} else {
setPdfLoadingText("正在准备 PDF…");
}
if (Date.now() - start >= maxMs) {
resolve("timeout");
return;
}
setTimeout(tick, intervalMs);
})
.catch(function () {
resolve("error");
});
}
tick();
});
}
if (saveBtn) {
saveBtn.addEventListener("click", function () {
var btnText = saveBtn.textContent;
saveBtn.disabled = true;
saveBtn.textContent = "生成中...";
if (loadingOverlay) {
loadingOverlay.setAttribute("aria-hidden", "false");
}
setPdfLoadingText("正在准备 PDF…");
function restoreBtn() {
saveBtn.disabled = false;
saveBtn.textContent = btnText;
if (loadingOverlay) {
loadingOverlay.setAttribute("aria-hidden", "true");
}
}
try {
var path = window.location.pathname || "";
var segments = path.split("/");
var reportId = null;
for (var i = 0; i < segments.length; i++) {
if (
segments[i] === "qygl" &&
i + 1 < segments.length &&
segments[i + 1]
) {
reportId = segments[i + 1];
break;
}
}
if (
!reportId &&
reportData &&
reportData.reportId &&
typeof reportData.reportId === "string"
) {
reportId = reportData.reportId;
}
if (!reportId) {
console.error(
"无法从当前 URL 解析报告编号,路径为",
path,
);
restoreBtn();
return;
}
var pdfUrl =
"/reports/qygl/" +
encodeURIComponent(reportId) +
"/pdf";
pollPdfStatusUntilReady(
reportId,
120000,
400,
).then(function () {
return fetch(pdfUrl, { method: "GET" });
})
.then(function (resp) {
if (!resp.ok) {
throw new Error(
"生成 PDF 接口返回错误状态:" +
resp.status,
);
}
var contentType =
resp.headers.get("Content-Type") || "";
if (
contentType.indexOf(
"application/pdf",
) === -1 &&
contentType.indexOf(
"application/octet-stream",
) === -1
) {
return resp.text().then(function (txt) {
throw new Error(
"生成 PDF 失败:" + txt,
);
});
}
return resp.blob();
})
.then(function (blob) {
var fileName = "企业全景报告.pdf";
if (
reportData &&
reportData.entName &&
typeof reportData.entName === "string"
) {
fileName =
reportData.entName +
"_企业全景报告.pdf";
}
var url = window.URL.createObjectURL(blob);
var a = document.createElement("a");
a.href = url;
a.download = fileName;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
window.URL.revokeObjectURL(url);
restoreBtn();
})
.catch(function (e) {
console.error("生成 PDF 失败(后端接口)", e);
restoreBtn();
alert("生成 PDF 失败,请稍后重试");
});
} catch (e) {
console.error("触发生成 PDF 失败", e);
restoreBtn();
}
});
}
})();
</script>
</body>
</html>