Files
shanxia2.0/docs/fresh-mint-view-wide-dialog-demos.html
T
2026-06-21 09:19:07 +08:00

1861 lines
65 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Fresh Mint · 宽弹窗多列布局 Demo</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=Inter:wght@400;500;600;700&display=swap" rel="stylesheet" />
<style>
:root {
--primary: #10b981;
--primary-dark: #059669;
--primary-bg: #ecfdf5;
--text: #1e293b;
--text-muted: #64748b;
--border: #d9e8e2;
--surface: #ffffff;
--table-head: #f8fafc;
--shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
--shadow-lg: 0 8px 24px rgba(15, 23, 42, 0.08);
--radius: 10px;
--danger: #dc2626;
}
* { box-sizing: border-box; }
body {
margin: 0;
min-height: 100vh;
font-family: Inter, -apple-system, sans-serif;
font-size: 14px;
line-height: 1.5;
color: var(--text);
background: #f8fafc;
}
.page { max-width: 1320px; margin: 0 auto; padding: 32px 24px 80px; }
.page-head { margin-bottom: 24px; }
.page-head h1 { margin: 0 0 8px; font-size: 24px; font-weight: 700; }
.page-head h1 span { color: var(--primary); }
.page-head p { margin: 0 0 12px; color: var(--text-muted); max-width: 780px; line-height: 1.6; }
.page-head a { color: var(--primary-dark); }
.width-bar {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-bottom: 24px;
}
.width-pill {
font-size: 12px;
font-weight: 600;
padding: 4px 10px;
border-radius: 6px;
background: var(--surface);
border: 1px solid var(--border);
color: var(--text-muted);
}
.width-pill b { color: var(--text); }
.compare-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 20px;
}
@media (max-width: 1000px) { .compare-grid { grid-template-columns: 1fr; } }
.layout-card {
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius);
overflow: hidden;
box-shadow: var(--shadow);
display: flex;
flex-direction: column;
}
.layout-card.rec { border-color: rgba(16, 185, 129, 0.4); }
.layout-card-head {
padding: 14px 16px;
border-bottom: 1px solid var(--border);
display: flex;
justify-content: space-between;
gap: 12px;
}
.layout-card-head h2 { margin: 0 0 4px; font-size: 15px; font-weight: 600; }
.layout-card-head p { margin: 0; font-size: 12px; color: var(--text-muted); line-height: 1.45; }
.badge {
flex-shrink: 0;
font-size: 11px;
font-weight: 700;
padding: 3px 8px;
border-radius: 6px;
background: var(--primary-bg);
color: var(--primary-dark);
white-space: nowrap;
}
.badge.blue { background: #eff6ff; color: #2563eb; }
.badge.amber { background: #fffbeb; color: #b45309; }
.layout-preview {
flex: 1;
padding: 12px;
background: #f1f5f9;
min-height: 260px;
overflow: auto;
}
.mini-wrap {
transform: scale(0.92);
transform-origin: top center;
}
.mini-dialog {
background: #fff;
border-radius: 8px;
border: 1px solid var(--border);
overflow: hidden;
font-size: 11px;
margin: 0 auto;
}
.mini-dialog.w920 { max-width: 420px; }
.mini-dialog.w960 { max-width: 440px; }
.mini-dialog.w1040 { max-width: 480px; }
.mini-dialog.w1120 { max-width: 520px; }
.layout-card-foot {
padding: 12px 16px;
border-top: 1px solid var(--border);
display: flex;
justify-content: space-between;
align-items: center;
background: var(--table-head);
}
.layout-card-foot .meta { font-size: 11px; color: var(--text-muted); }
.btn {
height: 32px;
padding: 0 12px;
border-radius: 8px;
font-size: 12px;
font-weight: 600;
font-family: inherit;
cursor: pointer;
border: 1px solid var(--primary);
background: var(--primary);
color: #fff;
}
/* ── 弹窗通用 ── */
.backdrop {
position: fixed;
inset: 0;
background: rgba(15, 23, 42, 0.45);
display: none;
align-items: center;
justify-content: center;
padding: 20px;
z-index: 1000;
backdrop-filter: none;
}
.backdrop.open { display: flex; }
.dialog {
background: #fff;
border-radius: var(--radius);
box-shadow: var(--shadow-lg);
width: 100%;
max-height: calc(100vh - 40px);
display: flex;
flex-direction: column;
overflow: hidden;
animation: pop 0.2s ease;
}
@keyframes pop {
from { opacity: 0; transform: scale(0.97); }
to { opacity: 1; transform: none; }
}
.dialog.w920 { max-width: 920px; }
.dialog.w720 { max-width: 720px; }
.dialog.w960 { max-width: 960px; }
.dialog.w1040 { max-width: 1040px; }
.dialog.w1120 { max-width: 1120px; }
.dialog-hero {
padding: 16px 22px;
background: linear-gradient(135deg, var(--primary-bg), #fff);
border-bottom: 1px solid var(--border);
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 16px;
}
.dialog-hero h2 { margin: 0 0 4px; font-size: 18px; font-weight: 700; }
.dialog-hero p { margin: 0; font-size: 13px; color: var(--text-muted); }
.width-tag {
flex-shrink: 0;
font-size: 11px;
font-weight: 700;
padding: 4px 10px;
border-radius: 6px;
background: #eff6ff;
color: #2563eb;
border: 1px solid rgba(37, 99, 235, 0.2);
}
.dialog-scroll { overflow-y: auto; flex: 1; }
.dialog-foot {
padding: 12px 22px;
border-top: 1px solid var(--border);
display: flex;
justify-content: flex-end;
background: var(--table-head);
}
.dialog-foot .btn-ghost {
height: 36px;
padding: 0 14px;
border-radius: 8px;
font-size: 13px;
font-weight: 600;
font-family: inherit;
cursor: pointer;
border: 1px solid var(--border);
background: #fff;
color: var(--text);
}
.view-section {
padding: 18px 22px;
border-bottom: 1px solid #ebeef5;
}
.view-section:last-child { border-bottom: none; }
.view-section h3 {
margin: 0 0 14px;
padding-left: 10px;
font-size: 14px;
font-weight: 600;
border-left: 3px solid var(--primary);
line-height: 1;
}
/* KV 上下堆叠 + 多列 grid */
.kv-grid {
display: grid;
gap: 12px 18px;
}
.kv-grid.cols-2 { grid-template-columns: repeat(2, 1fr); }
.kv-grid.cols-3 { grid-template-columns: repeat(3, 1fr); }
.kv-grid.cols-4 { grid-template-columns: repeat(4, 1fr); }
.kv-item { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
.kv-item dt { margin: 0; font-size: 11px; color: var(--text-muted); }
.kv-item dd { margin: 0; font-size: 13px; font-weight: 600; word-break: break-word; line-height: 1.45; }
.kv-item.span-2 { grid-column: span 2; }
.kv-item.span-3 { grid-column: span 3; }
.kv-item.span-4 { grid-column: span 4; }
.kv-item.span-all { grid-column: 1 / -1; }
.tag {
display: inline-block;
padding: 1px 8px;
border-radius: 5px;
font-size: 12px;
font-weight: 600;
background: var(--primary-bg);
color: var(--primary-dark);
border: 1px solid rgba(16, 185, 129, 0.25);
}
/* 双区块并排 */
.section-cols-2 {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 0;
}
.section-cols-2 .view-section {
border-bottom: none;
border-right: 1px solid #ebeef5;
}
.section-cols-2 .view-section:last-child { border-right: none; }
@media (max-width: 800px) {
.section-cols-2 { grid-template-columns: 1fr; }
.section-cols-2 .view-section { border-right: none; border-bottom: 1px solid #ebeef5; }
}
/* 左右分栏 */
.view-split {
display: flex;
min-height: 400px;
}
.view-split-left {
flex: 0 0 340px;
border-right: 1px solid var(--border);
overflow-y: auto;
}
.view-split-right {
flex: 1;
overflow-y: auto;
background: var(--table-head);
}
.view-split-left .view-section,
.view-split-right .view-section { border-bottom: none; }
@media (max-width: 860px) {
.view-split { flex-direction: column; }
.view-split-left { flex: none; border-right: none; border-bottom: 1px solid var(--border); }
}
/* 2×2 卡片宫格 */
.card-grid-2x2 {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 14px;
}
@media (max-width: 720px) { .card-grid-2x2 { grid-template-columns: 1fr; } }
.info-card {
border: 1px solid var(--border);
border-radius: var(--radius);
padding: 14px 16px;
background: #fff;
}
.info-card h4 {
margin: 0 0 12px;
font-size: 13px;
font-weight: 600;
color: var(--primary-dark);
display: flex;
align-items: center;
gap: 6px;
}
.info-card h4::before {
content: "";
width: 6px;
height: 6px;
border-radius: 50%;
background: var(--primary);
}
.info-card .kv-grid { gap: 10px 12px; }
/* 宽表双组(一行两组 label-value) */
.kv-wide-table {
width: 100%;
border-collapse: collapse;
font-size: 13px;
}
.kv-wide-table td {
padding: 9px 12px;
border-bottom: 1px solid #f1f5f9;
vertical-align: top;
}
.kv-wide-table .lbl {
width: 11%;
color: var(--text-muted);
font-size: 11px;
font-weight: 500;
white-space: nowrap;
}
.kv-wide-table .val {
width: 39%;
font-weight: 600;
word-break: break-word;
padding-right: 20px;
}
.kv-wide-table tr:last-child td { border-bottom: none; }
/* ── KV 表格模式(左右 label | value)── */
.demo-section { margin-top: 32px; }
.demo-section h2 {
margin: 0 0 8px;
font-size: 18px;
font-weight: 700;
}
.demo-section > p {
margin: 0 0 16px;
color: var(--text-muted);
max-width: 820px;
line-height: 1.6;
font-size: 13px;
}
.kv-table-wrap {
border: 1px solid var(--border);
border-radius: 8px;
overflow: hidden;
background: #fff;
}
.kv-table {
width: 100%;
border-collapse: collapse;
font-size: 13px;
table-layout: fixed;
}
.kv-table th {
padding: 10px 12px;
background: var(--table-head);
color: var(--text-muted);
font-size: 11px;
font-weight: 500;
text-align: left;
vertical-align: top;
border-bottom: 1px solid #eef2f6;
border-right: 1px solid #eef2f6;
white-space: nowrap;
}
.kv-table td {
padding: 10px 14px;
font-weight: 600;
color: var(--text);
border-bottom: 1px solid #eef2f6;
vertical-align: top;
word-break: break-word;
line-height: 1.45;
}
.kv-table tr:last-child th,
.kv-table tr:last-child td { border-bottom: none; }
.kv-table .money-val { color: var(--danger); }
.kv-table .muted-val { color: var(--text-muted); font-weight: 600; }
/* 每行一组 label | value */
.kv-table--pair th { width: 128px; }
.kv-table--pair tr.row-long th { width: 128px; }
/* 每行两组 label | value | label | value */
.kv-table--dual th { width: 12%; }
.kv-table--dual td { width: 38%; }
.kv-table--dual tr.row-long th { width: 12%; }
.kv-table--dual tr.row-long td { width: auto; }
/* 斑马纹:无竖线、行间浅底 */
.kv-table--zebra { table-layout: auto; }
.kv-table--zebra th {
border-right: none;
background: transparent;
color: var(--text-muted);
width: 110px;
padding-left: 14px;
}
.kv-table--zebra td {
border-bottom: 1px solid #f1f5f9;
padding-right: 14px;
}
.kv-table--zebra tbody tr:nth-child(even) { background: #f8fafc; }
.kv-table--zebra tr.row-long th { vertical-align: top; padding-top: 12px; }
.kv-table--zebra.kv-table--dual th { width: 11%; }
.kv-table--zebra.kv-table--dual td { width: 39%; }
/* 紧凑:更小行高 */
.kv-table--compact th,
.kv-table--compact td { padding: 8px 10px; }
.kv-table--compact th { font-size: 10px; }
.kv-table--compact td { font-size: 12px; }
.mini-dialog .kv-table th { font-size: 8px; padding: 4px 5px; }
.mini-dialog .kv-table td { font-size: 9px; padding: 4px 6px; }
.mini-dialog .kv-table .tag { font-size: 8px; padding: 0 4px; }
/* ── 其他展示模式(非 KV 栅格 / 非 KV 表)── */
.stat-row {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 10px;
}
@media (max-width: 720px) { .stat-row { grid-template-columns: repeat(2, 1fr); } }
.stat-cell {
padding: 12px 14px;
border: 1px solid var(--border);
border-radius: 8px;
background: #fff;
box-shadow: var(--shadow);
}
.stat-cell .lbl { font-size: 11px; color: var(--text-muted); margin-bottom: 4px; }
.stat-cell .val { font-size: 15px; font-weight: 700; line-height: 1.3; font-variant-numeric: tabular-nums; }
.stat-cell .val.money { color: var(--danger); }
.mini-dialog .stat-row { grid-template-columns: repeat(2, 1fr); gap: 6px; }
.mini-dialog .stat-cell { padding: 6px 8px; }
.mini-dialog .stat-cell .val { font-size: 10px; }
.prop-list {
border: 1px solid var(--border);
border-radius: 8px;
overflow: hidden;
background: #fff;
}
.prop-row {
display: flex;
align-items: flex-start;
gap: 12px;
padding: 10px 14px;
border-bottom: 1px solid #f1f5f9;
font-size: 13px;
}
.prop-row:last-child { border-bottom: none; }
.prop-row .lbl {
flex: 0 0 108px;
font-size: 11px;
color: var(--text-muted);
font-weight: 500;
line-height: 1.45;
}
.prop-row .val { flex: 1; font-weight: 600; word-break: break-word; line-height: 1.45; }
.prop-row .val.money { color: var(--danger); }
.mini-dialog .prop-row { padding: 5px 8px; }
.mini-dialog .prop-row .lbl { flex-basis: 56px; font-size: 8px; }
.mini-dialog .prop-row .val { font-size: 9px; }
.view-tabs {
display: flex;
flex-wrap: wrap;
gap: 8px;
padding: 0 0 14px;
margin-bottom: 4px;
}
.tab-chip {
height: 32px;
padding: 0 14px;
border-radius: 8px;
font-size: 12px;
font-weight: 600;
font-family: inherit;
cursor: pointer;
border: 1px solid var(--border);
background: #fff;
color: var(--text-muted);
transition: background 0.15s, color 0.15s, border-color 0.15s;
}
.tab-chip.is-active {
background: var(--primary);
border-color: var(--primary);
color: #fff;
}
.tab-panel { display: none; }
.tab-panel.is-active { display: block; }
.mini-dialog .tab-chip { height: 20px; padding: 0 8px; font-size: 8px; }
.view-timeline {
padding-left: 18px;
border-left: 2px solid var(--border);
margin-left: 8px;
}
.view-tl-item {
position: relative;
padding: 0 0 16px 14px;
font-size: 13px;
}
.view-tl-item:last-child { padding-bottom: 0; }
.view-tl-item::before {
content: "";
position: absolute;
left: -25px;
top: 4px;
width: 10px;
height: 10px;
border-radius: 50%;
background: var(--primary);
border: 2px solid #fff;
box-shadow: 0 0 0 1px var(--border);
}
.view-tl-item.muted::before { background: #cbd5e1; }
.view-tl-item.future::before { background: #fff; border-color: var(--primary); }
.view-tl-time { font-size: 11px; color: var(--text-muted); margin-bottom: 2px; }
.view-tl-title { font-weight: 600; margin-bottom: 2px; }
.view-tl-desc { font-size: 12px; color: var(--text-muted); line-height: 1.45; }
.mini-dialog .view-tl-item { padding-bottom: 10px; font-size: 9px; }
.mini-dialog .view-tl-time { font-size: 8px; }
.acc-list { display: flex; flex-direction: column; gap: 8px; }
.acc-item {
border: 1px solid var(--border);
border-radius: 8px;
overflow: hidden;
background: #fff;
}
.acc-hd {
width: 100%;
padding: 11px 14px;
border: none;
background: var(--table-head);
font-size: 13px;
font-weight: 600;
font-family: inherit;
color: var(--text);
cursor: pointer;
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
text-align: left;
}
.acc-hd::after {
content: "▾";
font-size: 12px;
color: var(--text-muted);
transition: transform 0.15s;
}
.acc-item.is-open .acc-hd::after { transform: rotate(180deg); }
.acc-bd { display: none; padding: 12px 14px 14px; border-top: 1px solid #f1f5f9; }
.acc-item.is-open .acc-bd { display: block; }
.mini-dialog .acc-hd { padding: 6px 8px; font-size: 9px; }
.mini-dialog .acc-bd { padding: 6px 8px; }
.meta-chips {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
.meta-chip {
font-size: 12px;
padding: 6px 10px;
border-radius: 6px;
background: var(--table-head);
border: 1px solid var(--border);
color: var(--text);
font-weight: 600;
}
.meta-chip b {
font-weight: 500;
color: var(--text-muted);
margin-right: 4px;
}
.prose-block {
padding: 14px 16px;
background: var(--table-head);
border: 1px solid var(--border);
border-radius: 8px;
font-size: 13px;
line-height: 1.65;
color: var(--text);
word-break: break-word;
}
.prose-block h4 {
margin: 0 0 8px;
font-size: 12px;
font-weight: 600;
color: var(--text-muted);
}
.mini-dialog .meta-chip { font-size: 8px; padding: 3px 6px; }
.mini-dialog .prose-block { font-size: 8px; padding: 6px 8px; }
.view-route-bar {
display: flex;
align-items: center;
min-height: 88px;
padding: 16px 20px;
border-radius: var(--radius);
border: 1px solid var(--border);
background: linear-gradient(90deg, #ecfdf5 0%, #d1fae5 40%, #a7f3d0 100%);
}
.route-node {
flex: 0 0 auto;
text-align: center;
min-width: 64px;
}
.route-node .dot {
width: 12px;
height: 12px;
border-radius: 50%;
background: #fff;
border: 2px solid var(--primary);
margin: 0 auto 6px;
opacity: 0.55;
}
.route-node.is-done .dot,
.route-node.is-active .dot {
background: var(--primary);
opacity: 1;
box-shadow: 0 0 0 3px rgba(16, 185, 129, 0.2);
}
.route-node .lbl { font-size: 11px; font-weight: 600; color: var(--text); }
.route-node .sub { font-size: 10px; color: var(--text-muted); margin-top: 2px; }
.view-route-line {
flex: 1;
height: 3px;
background: var(--primary);
opacity: 0.35;
border-radius: 2px;
margin: 0 8px 18px;
min-width: 12px;
}
.view-route-line.is-done { opacity: 0.75; }
.mini-dialog .view-route-bar { min-height: 48px; padding: 8px 10px; }
.mini-dialog .route-node .lbl { font-size: 7px; }
.mini-dialog .route-node .dot { width: 8px; height: 8px; }
.view-bento {
display: grid;
grid-template-columns: 1.2fr 0.8fr;
gap: 12px;
}
@media (max-width: 720px) { .view-bento { grid-template-columns: 1fr; } }
.view-card {
border: 1px solid var(--border);
border-radius: var(--radius);
padding: 14px 16px;
background: #fff;
}
.view-card h4 {
margin: 0 0 10px;
font-size: 12px;
font-weight: 700;
color: var(--text-muted);
letter-spacing: 0.04em;
}
.view-card.span-full { grid-column: 1 / -1; }
.mini-dialog .view-bento { grid-template-columns: 1fr 1fr; gap: 6px; }
.mini-dialog .view-card { padding: 6px 8px; }
.mini-dialog .view-card h4 { font-size: 8px; margin-bottom: 4px; }
.note-bar {
margin-top: 24px;
padding: 14px 16px;
background: var(--primary-bg);
border: 1px solid rgba(16, 185, 129, 0.25);
border-radius: var(--radius);
font-size: 13px;
color: var(--primary-dark);
line-height: 1.6;
}
/* mini 预览专用缩小 */
.mini-dialog .kv-grid.cols-3 { grid-template-columns: repeat(3, 1fr); gap: 6px 8px; }
.mini-dialog .kv-grid.cols-4 { grid-template-columns: repeat(4, 1fr); gap: 5px 6px; }
.mini-dialog .kv-item dt { font-size: 9px; }
.mini-dialog .kv-item dd { font-size: 10px; font-weight: 600; }
.mini-dialog .view-section { padding: 10px 12px; }
.mini-dialog .view-section h3 { font-size: 11px; margin-bottom: 8px; }
.mini-dialog .section-cols-2 { grid-template-columns: 1fr 1fr; }
.mini-dialog .card-grid-2x2 { gap: 8px; }
.mini-dialog .info-card { padding: 8px 10px; }
.mini-dialog .info-card h4 { font-size: 10px; margin-bottom: 6px; }
/* ── 少字段稀疏场景 ── */
.sparse-section { margin-top: 32px; }
.kpi-strip {
display: flex;
flex-wrap: wrap;
gap: 12px;
padding: 14px 22px;
background: var(--table-head);
border-bottom: 1px solid var(--border);
}
.kpi-chip {
flex: 1;
min-width: 140px;
padding: 12px 14px;
background: #fff;
border: 1px solid var(--border);
border-radius: 8px;
box-shadow: var(--shadow);
}
.kpi-chip .lbl { font-size: 11px; color: var(--text-muted); margin-bottom: 4px; }
.kpi-chip .val { font-size: 16px; font-weight: 700; color: var(--text); line-height: 1.3; }
.kpi-chip .val.money { color: var(--danger); }
.kpi-chip .val.muted { font-size: 14px; font-weight: 600; color: var(--text-muted); }
.hero-chips {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-top: 10px;
}
.hero-chip {
font-size: 12px;
padding: 4px 10px;
border-radius: 6px;
background: rgba(255, 255, 255, 0.75);
border: 1px solid rgba(16, 185, 129, 0.2);
color: var(--text);
font-weight: 500;
}
.hero-chip b { font-weight: 600; color: var(--text-muted); margin-right: 4px; }
.kv-panel-centered {
max-width: 680px;
margin: 0 auto;
padding: 20px 22px 24px;
}
.kv-panel-centered .kv-grid { gap: 14px 20px; }
.bento-single {
max-width: 760px;
margin: 0 auto;
padding: 18px 22px 22px;
}
.bento-single .info-card {
background: linear-gradient(180deg, #fff 0%, var(--primary-bg) 100%);
border-color: rgba(16, 185, 129, 0.25);
padding: 18px 20px;
}
.sparse-empty-hint {
margin-top: 14px;
padding: 12px 14px;
border-radius: 8px;
background: var(--table-head);
border: 1px dashed var(--border);
font-size: 12px;
color: var(--text-muted);
text-align: center;
line-height: 1.5;
}
.dialog-compact.w720 { max-width: 720px; }
.dialog-compact .view-section { padding: 16px 20px; }
</style>
</head>
<body>
<div class="page">
<header class="page-head">
<h1><span>宽弹窗</span> · 多列 KV 布局 Demo</h1>
<p>
弹窗宽度 ≥920px 时,如何在<strong>不牺牲长值可读性</strong>的前提下提高信息密度。
以下 7 种方案使用同一组 18 个字段(含路径、地址、备注等长值),默认 KV 为<strong>上下堆叠</strong>。
若偏好<strong>左右表格</strong>(label | value),见「KV 表格模式」;
指标卡、Tab、时间轴、手风琴等<strong>其他展示形态</strong>见「其他信息展示模式」。
相关:<a href="fresh-mint-view-kv-layout-demos.html">窄弹窗 KV 对比</a> ·
<a href="fresh-mint-view-dialog-demos.html">弹窗风格 Demo</a> ·
<a href="fresh-mint-view-sparse-kv-demos.html">少字段稀疏场景</a>
</p>
<div class="width-bar">
<span class="width-pill"><b>720px</b> 双列(基准,不在此页)</span>
<span class="width-pill"><b>920px</b> 三列起点</span>
<span class="width-pill"><b>960px</b> 分栏 / 双区块</span>
<span class="width-pill"><b>1040px</b> 四列 / 2×2 卡片</span>
<span class="width-pill"><b>1120px</b> 宽表双组</span>
</div>
</header>
<div class="compare-grid" id="grid"></div>
<section class="demo-section" id="table-section">
<h2>KV 表格模式(左右 label | value)</h2>
<p>
与上下堆叠栅格相对:每行(或每半行)为<strong>左 label、右 value</strong> 的表格行,类似 <code>el-descriptions</code> 但样式对齐 Fresh Mint。
短字段可<strong>一行两组</strong>(四列);长字段<strong>独占整行</strong>。少字段时表格更饱满,不易显空。
</p>
<div class="compare-grid" id="table-grid"></div>
</section>
<section class="demo-section" id="alt-section">
<h2>其他信息展示模式</h2>
<p>
除<strong>上下 KV 栅格</strong>与<strong>左右 KV 表格</strong>外,查看弹窗还可采用指标卡、属性行、Tab 分段、时间轴、手风琴、芯片摘要、路线进度、Bento 卡片等形态。
样式对齐规范 <code>§9.6</code>(<code>view-timeline</code> / <code>view-route-bar</code> / <code>view-bento</code> 等)。Tab 与手风琴在全屏预览中可点击交互。
</p>
<div class="compare-grid" id="alt-grid"></div>
</section>
<div class="note-bar">
<strong>宽弹窗选型建议:</strong>
短字段(编码/状态/日期)≥9 个 → 可用 <strong>3~4 列</strong>;
含长路径/备注 → 长字段必须 <strong>span 跨整行</strong>(方案 C / G);
字段可自然分组 → 优先 <strong>双区块并排</strong>(D)或 <strong>2×2 卡片</strong>(F),比单纯加列更易扫读;
需「摘要 + 明细/轨迹」→ 用 <strong>左右分栏</strong>(E),左侧 1 列、右侧 2~3 列。
偏好<strong>左右对齐、表格感</strong> → 见 <a href="#table-section" style="color:inherit;font-weight:600">KV 表格模式</a>(M~R,推荐 N 双组四列)。
需<strong>流程/进度/折叠/分段</strong> → 见 <a href="#alt-section" style="color:inherit;font-weight:600">其他展示模式</a>(S~Y)。
<strong>字段 ≤8 个</strong> → 见下方「少字段稀疏场景」或 <a href="fresh-mint-view-sparse-kv-demos.html" style="color:inherit;font-weight:600">独立 Demo 页</a>;表格方案见 Q / R。
</div>
<section class="demo-section sparse-section" id="sparse-section">
<h2>少字段稀疏场景(≤8 个 KV)</h2>
<p>
1040px 四列在字段少时容易「空荡荡」。常用手段:<strong>① 自适应降列</strong> ② <strong>Hero 下沉 KPI</strong>
③ <strong>Hero 芯片摘要</strong> ④ <strong>单卡片 Bento</strong> ⑤ <strong>窄弹窗 720px</strong>。
实现上可在 <code>SystemViewDialog</code> 按 <code>fields.length</code> 动态设 <code>gridColumns</code> 与 <code>width</code>。
</p>
<div class="compare-grid" id="sparse-grid"></div>
</section>
</div>
<div class="backdrop" id="backdrop">
<div class="dialog w920" id="dialog"></div>
</div>
<script>
var FIELDS = {
short: [
{ k: "合同编号", v: "HT-2026-SX-00428" },
{ k: "合同类型", v: "年度框架" },
{ k: "签订日期", v: "2026-06-01" },
{ k: "生效日期", v: "2026-06-01" },
{ k: "到期日期", v: "2027-05-31" },
{ k: "合同状态", v: "已生效", tag: true },
{ k: "付款方式", v: "月结 30 天" },
{ k: "交货方式", v: "卖方送货" },
{ k: "销售负责人", v: "陈经理" },
{ k: "归属部门", v: "华东销售一部" },
{ k: "币种", v: "CNY" },
{ k: "税率", v: "13%" }
],
long: [
{ k: "甲方", v: "山峡化工有限公司" },
{ k: "乙方", v: "华东精细化工贸易公司" },
{ k: "交货地址", v: "浙江省杭州市滨江区长河街道江南大道 3588 号 · 山峡化工华东仓储中心(北门)" },
{ k: "质量标准", v: "GB/T 标准 · 每批次附第三方质检报告 · 不合格品 24 小时内退换" },
{ k: "合同金额", v: "¥ 2,580,000.00", money: true },
{ k: "备注", v: "本合同为年度框架,具体订单以 PO 为准;价格锁定至 2026-12-31,逾期重新询价。紧急联系人:刘总 139****8821。" }
]
};
var ALL_SHORT = FIELDS.short;
var ALL_LONG = FIELDS.long;
/** 备用金申请等少字段场景(7 个) */
var SPARSE_FIELDS = [
{ k: "申请人", v: "张三" },
{ k: "申请原因", v: "项目现场零星采购备用" },
{ k: "申请金额(元)", v: "¥ 5,000.00", money: true },
{ k: "实际发放(元)", v: "—", muted: true },
{ k: "当前节点", v: "部门审批", tag: true },
{ k: "申请时间", v: "2026-06-18 14:32" },
{ k: "冲抵总额(元)", v: "¥ 0.00", money: true }
];
function suggestCols(n) {
if (n <= 4) return 2;
if (n <= 8) return 3;
return 4;
}
function suggestWidth(n) {
if (n <= 6) return "w720";
if (n <= 10) return "w920";
return "w1040";
}
function esc(s) {
var d = document.createElement("div");
d.textContent = s;
return d.innerHTML;
}
function kvItem(f, spanClass) {
var val = f.tag ? '<span class="tag">' + esc(f.v) + "</span>" : esc(f.v);
if (f.money) val = '<span style="color:var(--danger)">' + val + "</span>";
if (f.muted) val = '<span style="color:var(--text-muted);font-weight:600">' + val + "</span>";
var span = spanClass ? " " + spanClass : "";
return '<div class="kv-item' + span + '"><dt>' + esc(f.k) + "</dt><dd>" + val + "</dd></div>";
}
function kvGrid(items, cols, spanFn) {
var html = '<dl class="kv-grid cols-' + cols + '">';
items.forEach(function (f) {
html += kvItem(f, spanFn ? spanFn(f) : "");
});
return html + "</dl>";
}
function section(title, body) {
return '<div class="view-section"><h3>' + esc(title) + "</h3>" + body + "</div>";
}
var LONG_LABEL_RE = /备注|地址|描述|说明|条款|原因|摘要|内容|标准|路径|意向/;
function isLongField(f) {
var text = f.v == null ? "" : String(f.v);
if (text.length >= 17) return true;
if (LONG_LABEL_RE.test(f.k)) return true;
return false;
}
function formatFieldVal(f) {
if (f.tag) return '<span class="tag">' + esc(f.v) + "</span>";
if (f.money) return '<span class="money-val">' + esc(f.v) + "</span>";
if (f.muted) return '<span class="muted-val">' + esc(f.v) + "</span>";
return esc(f.v);
}
function kvTableWrap(inner) {
return '<div class="kv-table-wrap">' + inner + "</div>";
}
/** 每行一组:label | value */
function kvTablePair(items) {
var html = '<table class="kv-table kv-table--pair"><tbody>';
items.forEach(function (f) {
html +=
'<tr' +
(isLongField(f) ? ' class="row-long"' : "") +
"><th>" +
esc(f.k) +
"</th><td>" +
formatFieldVal(f) +
"</td></tr>";
});
return kvTableWrap(html + "</tbody></table>");
}
/** 每行两组短字段;长字段独占一行(value 跨 3 列) */
function kvTableDual(items) {
var html = '<table class="kv-table kv-table--dual"><tbody>';
var i = 0;
while (i < items.length) {
var f = items[i];
if (isLongField(f)) {
html +=
'<tr class="row-long"><th>' +
esc(f.k) +
'</th><td colspan="3">' +
formatFieldVal(f) +
"</td></tr>";
i += 1;
continue;
}
html += "<tr><th>" + esc(f.k) + "</th><td>" + formatFieldVal(f) + "</td>";
i += 1;
if (i < items.length && !isLongField(items[i])) {
html += "<th>" + esc(items[i].k) + "</th><td>" + formatFieldVal(items[i]) + "</td>";
i += 1;
} else {
html += "<th></th><td></td>";
}
html += "</tr>";
}
return kvTableWrap(html + "</tbody></table>");
}
/** 斑马纹:无竖线,偶数行浅底(两列) */
function kvTableZebraPair(items) {
var html = '<table class="kv-table kv-table--zebra kv-table--pair"><tbody>';
items.forEach(function (f) {
html +=
'<tr' +
(isLongField(f) ? ' class="row-long"' : "") +
"><th>" +
esc(f.k) +
"</th><td>" +
formatFieldVal(f) +
"</td></tr>";
});
return kvTableWrap(html + "</tbody></table>");
}
/** 斑马纹 + 双组四列 */
function kvTableZebraDual(items) {
var html = '<table class="kv-table kv-table--zebra kv-table--dual"><tbody>';
var i = 0;
while (i < items.length) {
var f = items[i];
if (isLongField(f)) {
html +=
'<tr class="row-long"><th>' +
esc(f.k) +
'</th><td colspan="3">' +
formatFieldVal(f) +
"</td></tr>";
i += 1;
continue;
}
html += "<tr><th>" + esc(f.k) + "</th><td>" + formatFieldVal(f) + "</td>";
i += 1;
if (i < items.length && !isLongField(items[i])) {
html += "<th>" + esc(items[i].k) + "</th><td>" + formatFieldVal(items[i]) + "</td>";
i += 1;
} else {
html += "<th></th><td></td>";
}
html += "</tr>";
}
return kvTableWrap(html + "</tbody></table>");
}
var CONTRACT_TIMELINE = [
{ time: "2026-05-20 10:00", title: "合同起草", desc: "陈经理创建年度框架合同草稿" },
{ time: "2026-05-28 16:30", title: "审批通过", desc: "法务、财务会签完成" },
{ time: "2026-06-01 09:00", title: "签订生效", desc: "双方盖章,合同状态变更为已生效", active: true },
{ time: "2027-05-31", title: "合同到期", desc: "到期前 30 天触发续签提醒", future: true }
];
function renderStatRow(items) {
var html = '<div class="stat-row">';
items.forEach(function (f) {
html +=
'<div class="stat-cell"><div class="lbl">' +
esc(f.k) +
'</div><div class="val' +
(f.money ? " money" : "") +
'">' +
(f.tag ? '<span class="tag">' + esc(f.v) + "</span>" : esc(f.v)) +
"</div></div>";
});
return html + "</div>";
}
function renderPropList(items) {
var html = '<div class="prop-list">';
items.forEach(function (f) {
html +=
'<div class="prop-row"><span class="lbl">' +
esc(f.k) +
'</span><span class="val' +
(f.money ? " money" : "") +
'">' +
formatFieldVal(f) +
"</span></div>";
});
return html + "</div>";
}
function renderTabs(panels, groupId) {
var gid = groupId || "tabs";
var html = '<div class="view-tabs" role="tablist">';
panels.forEach(function (p, idx) {
html +=
'<button type="button" class="tab-chip' +
(idx === 0 ? " is-active" : "") +
'" role="tab" data-tab-group="' +
gid +
'" data-tab="' +
idx +
'">' +
esc(p.title) +
"</button>";
});
html += "</div>";
panels.forEach(function (p, idx) {
html +=
'<div class="tab-panel' +
(idx === 0 ? " is-active" : "") +
'" role="tabpanel" data-tab-group="' +
gid +
'" data-tab-panel="' +
idx +
'">' +
p.body +
"</div>";
});
return html;
}
function renderTimeline(items) {
var html = '<div class="view-timeline">';
items.forEach(function (it) {
var cls = it.future ? " future" : it.muted ? " muted" : it.active ? " active" : "";
html +=
'<div class="view-tl-item' +
cls +
'"><div class="view-tl-time">' +
esc(it.time) +
'</div><div class="view-tl-title">' +
esc(it.title) +
'</div><div class="view-tl-desc">' +
esc(it.desc) +
"</div></div>";
});
return html + "</div>";
}
function renderAccordion(sections) {
var html = '<div class="acc-list">';
sections.forEach(function (sec, idx) {
html +=
'<div class="acc-item' +
(idx === 0 ? " is-open" : "") +
'"><button type="button" class="acc-hd" data-acc-toggle>' +
esc(sec.title) +
'</button><div class="acc-bd">' +
sec.body +
"</div></div>";
});
return html + "</div>";
}
function renderMetaChips(items) {
var html = '<div class="meta-chips">';
items.forEach(function (f) {
html += '<span class="meta-chip"><b>' + esc(f.k) + "</b>" + formatFieldVal(f) + "</span>";
});
return html + "</div>";
}
function renderProseBlock(title, text) {
return '<div class="prose-block"><h4>' + esc(title) + "</h4>" + esc(text) + "</div>";
}
function renderRouteBar(nodes) {
var html = '<div class="view-route-bar">';
nodes.forEach(function (node, idx) {
if (idx > 0) {
html += '<div class="view-route-line' + (node.done || node.active ? " is-done" : "") + '"></div>';
}
html +=
'<div class="route-node' +
(node.done ? " is-done" : "") +
(node.active ? " is-active" : "") +
'"><div class="dot"></div><div class="lbl">' +
esc(node.label) +
'</div><div class="sub">' +
esc(node.sub || "") +
"</div></div>";
});
return html + "</div>";
}
function renderBento(cards) {
var html = '<div class="view-bento">';
cards.forEach(function (c) {
html +=
'<div class="view-card' +
(c.spanFull ? " span-full" : "") +
'"><h4>' +
esc(c.title) +
"</h4>" +
c.body +
"</div>";
});
return html + "</div>";
}
var LAYOUTS = [
{
id: "cols3",
name: "A · 三列上下 KV",
width: "w920",
widthLabel: "920px",
badge: "推荐",
desc: "宽弹窗最常用。18 个短字段 3 列 × 6 行;长字段另开区块 span 整行。",
meta: "字段 12~24 · 920px",
previewClass: "w920",
render: function () {
var s = kvGrid(ALL_SHORT, 3);
var l = kvGrid(ALL_LONG, 3, function () { return "span-all"; });
return section("合同要素", s) + section("主体与条款(长字段整行)", l);
}
},
{
id: "cols4",
name: "B · 四列上下 KV",
width: "w1040",
widthLabel: "1040px",
badge: "高密度",
desc: "仅当字段几乎全是短值(编码/日期/枚举)时使用。1040px 下每格 ~240px。",
meta: "短字段 ≥16 · 1040px",
previewClass: "w1040",
render: function () {
var mix = ALL_SHORT.concat(ALL_LONG.filter(function (f) { return f.k !== "备注"; }));
var html = kvGrid(mix, 4);
var remark = ALL_LONG.find(function (f) { return f.k === "备注"; });
html = html.replace("</dl>", kvItem(remark, "span-all") + "</dl>");
return section("合同全量字段", html);
}
},
{
id: "cols3-span2",
name: "C · 三列 + 长值跨 2 列",
width: "w920",
widthLabel: "920px",
badge: "折中",
desc: "中等长度(甲方/乙方/地址)占 2 列宽;超长备注仍整行。比全 span 省高度。",
meta: "长短混合 · 920px",
previewClass: "w920",
render: function () {
var html = '<dl class="kv-grid cols-3">';
ALL_SHORT.forEach(function (f) { html += kvItem(f); });
ALL_LONG.forEach(function (f) {
var span = f.k === "备注" ? "span-all" : "span-2";
html += kvItem(f, span);
});
html += "</dl>";
return section("合同信息", html);
}
},
{
id: "section-2col",
name: "D · 双区块并排(各 2 列 KV)",
width: "w960",
widthLabel: "960px",
badge: "推荐",
desc: "按业务语义拆成左右两栏(如「基本信息|商务条款」),每栏内部 2 列 KV。比 4 列更易分组扫读。",
meta: "可自然分组 · 960px",
previewClass: "w960",
render: function () {
var left = ALL_SHORT.slice(0, 6).concat(ALL_LONG.filter(function (f) { return f.k === "甲方" || f.k === "乙方"; }));
var right = ALL_SHORT.slice(6).concat(ALL_LONG.filter(function (f) { return f.k !== "甲方" && f.k !== "乙方"; }));
return (
'<div class="section-cols-2">' +
section("基本信息", kvGrid(left, 2, function (f) { return f.k === "备注" || f.k === "交货地址" || f.k === "质量标准" ? "span-2" : ""; })) +
section("商务与条款", kvGrid(right, 2, function (f) { return f.k === "备注" || f.k === "交货地址" || f.k === "质量标准" ? "span-2" : ""; })) +
"</div>"
);
}
},
{
id: "split",
name: "E · 左摘要 + 右三列",
width: "w960",
widthLabel: "960px",
badge: "分栏",
desc: "左 340px 放核心摘要(1 列 KV),右栏 3 列放其余。对应 SystemViewDialog split 布局。",
meta: "摘要+明细 · 960px",
previewClass: "w960",
render: function () {
var leftFields = [
{ k: "合同编号", v: "HT-2026-SX-00428" },
{ k: "合同状态", v: "已生效", tag: true },
{ k: "合同金额", v: "¥ 2,580,000.00", money: true },
{ k: "签订日期", v: "2026-06-01" },
{ k: "销售负责人", v: "陈经理" },
{ k: "甲方", v: "山峡化工有限公司" },
{ k: "乙方", v: "华东精细化工贸易公司" }
];
var rightFields = ALL_SHORT.slice(1).filter(function (f) { return f.k !== "合同状态"; })
.concat(ALL_LONG.filter(function (f) { return f.k !== "甲方" && f.k !== "乙方" && f.k !== "合同金额"; }));
return (
'<div class="view-split">' +
'<div class="view-split-left">' + section("核心摘要", kvGrid(leftFields, 1)) + "</div>" +
'<div class="view-split-right">' + section("完整要素", kvGrid(rightFields, 3, function (f) {
return f.k === "备注" || f.k === "交货地址" ? "span-all" : f.k === "质量标准" ? "span-2" : "";
})) + "</div>" +
"</div>"
);
}
},
{
id: "card-2x2",
name: "F · 2×2 卡片宫格",
width: "w1040",
widthLabel: "1040px",
badge: "分组",
desc: "4 张主题卡片(身份/时间/商务/条款),卡内 2 列 KV。适合 CRM 客户/合同等多维度档案。",
meta: "多维度 · 1040px",
previewClass: "w1040",
render: function () {
var groups = [
{ title: "身份标识", items: [ALL_SHORT[0], ALL_SHORT[1], ALL_SHORT[5], ALL_LONG[0], ALL_LONG[1]] },
{ title: "时间周期", items: ALL_SHORT.slice(2, 5) },
{ title: "商务条款", items: [ALL_SHORT[6], ALL_SHORT[7], ALL_SHORT[10], ALL_SHORT[11], ALL_LONG[4]] },
{ title: "交付与备注", items: [ALL_SHORT[8], ALL_SHORT[9], ALL_LONG[2], ALL_LONG[3], ALL_LONG[5]] }
];
var html = '<div class="view-section"><div class="card-grid-2x2">';
groups.forEach(function (g) {
html += '<div class="info-card"><h4>' + esc(g.title) + "</h4>" +
kvGrid(g.items, 2, function (f) {
return f.k === "备注" || f.k === "交货地址" ? "span-2" : "";
}) + "</div>";
});
return html + "</div></div>";
}
},
{
id: "wide-table",
name: "G · 宽表双组(一行 4 格)",
width: "w1120",
widthLabel: "1120px",
badge: "表格感",
desc: "1120px 下一行展示两组 label-value(共 4 列),label 窄列对齐。同质字段极多时的替代方案。",
meta: "18+ 同质字段 · 1120px",
previewClass: "w1120",
render: function () {
var all = ALL_SHORT.concat(ALL_LONG);
var html = '<div class="view-section"><table class="kv-wide-table"><tbody>';
for (var i = 0; i < all.length; i += 2) {
html += "<tr>";
html += '<td class="lbl">' + esc(all[i].k) + '</td><td class="val">' + (all[i].tag ? '<span class="tag">' + esc(all[i].v) + "</span>" : esc(all[i].v)) + "</td>";
if (all[i + 1]) {
html += '<td class="lbl">' + esc(all[i + 1].k) + '</td><td class="val">' + (all[i + 1].tag ? '<span class="tag">' + esc(all[i + 1].v) + "</span>" : esc(all[i + 1].v)) + "</td>";
} else {
html += '<td class="lbl"></td><td class="val"></td>';
}
html += "</tr>";
}
return html + "</tbody></table></div>";
}
}
];
var TABLE_LAYOUTS = [
{
id: "table-pair",
name: "M · 标准两列表格",
width: "w920",
widthLabel: "920px",
badge: "表格",
desc: "每行一组:左 label(灰底固定宽)| 右 value。长字段仍独占一行,适合字段不多或需逐行阅读。",
meta: "18 字段 · 1 组/行",
previewClass: "w920",
tableMode: true,
render: function () {
return section("合同信息", kvTablePair(ALL_SHORT.concat(ALL_LONG)));
}
},
{
id: "table-dual",
name: "N · 双组四列表格",
width: "w1040",
widthLabel: "1040px",
badge: "推荐",
desc: "一行展示两组 label|value(共四列);备注/地址等长字段自动独占整行。信息密度高且对齐清晰。",
meta: "18 字段 · 2 组/行",
previewClass: "w1040",
tableMode: true,
render: function () {
return section("合同全量字段", kvTableDual(ALL_SHORT.concat(ALL_LONG)));
}
},
{
id: "table-split-section",
name: "O · 分区 + 双组表",
width: "w1040",
widthLabel: "1040px",
badge: "分组",
desc: "「合同要素」「主体与条款」各一张表,双组四列。比单表更易扫读分组。",
meta: "分组表格 · 1040px",
previewClass: "w1040",
tableMode: true,
render: function () {
return (
section("合同要素", kvTableDual(ALL_SHORT)) +
section("主体与条款", kvTableDual(ALL_LONG))
);
}
},
{
id: "table-zebra-dual",
name: "P · 斑马纹双组表",
width: "w1040",
widthLabel: "1040px",
badge: "轻量",
desc: "无双竖线,偶数行浅灰底;仍为一行两组。视觉比全边框表更轻。",
meta: "斑马纹 · 1040px",
previewClass: "w1040",
tableMode: true,
render: function () {
return section("合同全量字段", kvTableZebraDual(ALL_SHORT.concat(ALL_LONG)));
}
},
{
id: "table-pair-sparse",
name: "Q · 少字段两列表格",
width: "w720",
widthLabel: "720px",
badge: "推荐",
desc: "7 个字段用标准两列表格 + 720px 弹窗,行数适中、不空荡,报销/审批简详情首选表格方案。",
meta: "7 字段 · 720px",
previewClass: "w920",
sparse: true,
tableMode: true,
render: function () {
return section("申请信息", kvTablePair(SPARSE_FIELDS));
}
},
{
id: "table-dual-sparse",
name: "R · 少字段双组表",
width: "w920",
widthLabel: "920px",
badge: "表格",
desc: "少字段时双组四列(4 行左右),比四列上下 KV 更饱满。",
meta: "7 字段 · 双组",
previewClass: "w920",
sparse: true,
tableMode: true,
render: function () {
return section("申请信息", kvTableDual(SPARSE_FIELDS));
}
}
];
var ALT_LAYOUTS = [
{
id: "alt-stat-props",
name: "S · 指标卡 + 属性行",
width: "w1040",
widthLabel: "1040px",
badge: "推荐",
desc: "顶部 4 格核心指标(金额/状态/日期),下方属性行列表展示其余字段。适合「先抓重点、再扫明细」。",
meta: "指标 + 列表 · 1040px",
previewClass: "w1040",
altMode: true,
render: function () {
var stats = [
ALL_LONG.find(function (f) { return f.k === "合同金额"; }),
ALL_SHORT.find(function (f) { return f.k === "合同状态"; }),
ALL_SHORT.find(function (f) { return f.k === "签订日期"; }),
ALL_SHORT.find(function (f) { return f.k === "到期日期"; })
];
var rest = ALL_SHORT.filter(function (f) {
return f.k !== "合同状态" && f.k !== "签订日期" && f.k !== "到期日期";
}).concat(ALL_LONG.filter(function (f) { return f.k !== "合同金额"; }));
return section("核心指标", renderStatRow(stats)) + section("合同明细", renderPropList(rest));
}
},
{
id: "alt-tabs",
name: "T · Tab 分段切换",
width: "w1040",
widthLabel: "1040px",
badge: "分段",
desc: "字段按业务语义拆成 Tab(基本信息 / 商务条款 / 主体备注),单屏信息量可控,全屏可点击切换。",
meta: "3 Tab · 1040px",
previewClass: "w1040",
altMode: true,
interactive: true,
render: function () {
return section(
"合同信息",
renderTabs(
[
{ title: "基本信息", body: kvGrid(ALL_SHORT.slice(0, 6), 2) },
{ title: "商务条款", body: kvGrid(ALL_SHORT.slice(6).concat(ALL_LONG.filter(function (f) { return f.k === "合同金额" || f.k === "质量标准"; })), 2, function (f) { return f.k === "质量标准" ? "span-2" : ""; }) },
{ title: "主体备注", body: renderPropList(ALL_LONG.filter(function (f) { return f.k !== "合同金额" && f.k !== "质量标准"; })) }
],
"contract"
)
);
}
},
{
id: "alt-timeline",
name: "U · 时间轴 + 精简属性",
width: "w920",
widthLabel: "920px",
badge: "流程",
desc: "左侧时间轴展示合同生命周期节点,右侧或下方属性行补充当前态字段。适合审批/履约类详情。",
meta: "时间轴 · 920px",
previewClass: "w920",
altMode: true,
render: function () {
var brief = [
ALL_SHORT[0],
ALL_SHORT[5],
ALL_LONG[0],
ALL_LONG[1]
];
return (
section("履约进度", renderTimeline(CONTRACT_TIMELINE)) +
section("当前摘要", renderPropList(brief))
);
}
},
{
id: "alt-accordion",
name: "V · 手风琴折叠",
width: "w1040",
widthLabel: "1040px",
badge: "折叠",
desc: "按分组折叠次要信息,默认展开第一组。字段多、分组清晰时减少首屏高度,全屏可展开/收起。",
meta: "3 组折叠 · 1040px",
previewClass: "w1040",
altMode: true,
interactive: true,
render: function () {
return section(
"合同信息",
renderAccordion([
{ title: "身份与时间(6 项)", body: kvGrid(ALL_SHORT.slice(0, 6), 2) },
{ title: "商务与交付(6 项)", body: kvGrid(ALL_SHORT.slice(6).concat(ALL_LONG.filter(function (f) { return f.k === "交货地址"; })), 2, function (f) { return f.k === "交货地址" ? "span-2" : ""; }) },
{ title: "主体、金额与备注(6 项)", body: renderPropList(ALL_LONG.filter(function (f) { return f.k !== "交货地址"; })) }
])
);
}
},
{
id: "alt-chips-prose",
name: "W · 芯片摘要 + 正文块",
width: "w920",
widthLabel: "920px",
badge: "轻量",
desc: "短字段收成芯片行(类 Hero 下沉),长文本(备注/条款)用独立正文块展示,阅读感更接近文档。",
meta: "芯片 + 正文 · 920px",
previewClass: "w920",
altMode: true,
render: function () {
var chips = ALL_SHORT.filter(function (f) {
return f.k !== "合同状态";
}).slice(0, 8);
return (
section("关键要素", renderMetaChips(chips.concat([ALL_SHORT[5]]))) +
section("质量标准", renderProseBlock("质量标准", ALL_LONG.find(function (f) { return f.k === "质量标准"; }).v)) +
section("备注说明", renderProseBlock("备注", ALL_LONG.find(function (f) { return f.k === "备注"; }).v))
);
}
},
{
id: "alt-route",
name: "X · 路线进度条 + 属性行",
width: "w1040",
widthLabel: "1040px",
badge: "进度",
desc: "顶部路线条展示合同阶段(起草→审批→签订→履行→到期),下方属性行列关键字段。对齐 view-route-bar。",
meta: "路线条 · 1040px",
previewClass: "w1040",
altMode: true,
render: function () {
var route = [
{ label: "起草", sub: "05-20", done: true },
{ label: "审批", sub: "05-28", done: true },
{ label: "签订", sub: "06-01", done: true, active: true },
{ label: "履行", sub: "进行中" },
{ label: "到期", sub: "2027-05" }
];
var props = [
ALL_SHORT[0],
ALL_SHORT[5],
ALL_LONG[4],
ALL_SHORT[2],
ALL_SHORT[4],
ALL_LONG[0],
ALL_LONG[1]
];
return section("合同阶段", renderRouteBar(route)) + section("合同要素", renderPropList(props));
}
},
{
id: "alt-bento",
name: "Y · Bento 不均等卡片",
width: "w1040",
widthLabel: "1040px",
badge: "卡片",
desc: "左大右小 Bento 布局:左侧主体信息占宽列,右侧堆叠时间/金额小卡;底部长备注横跨整行。",
meta: "Bento · 1040px",
previewClass: "w1040",
altMode: true,
render: function () {
return section(
"合同档案",
renderBento([
{
title: "主体信息",
body: renderPropList(ALL_LONG.filter(function (f) { return f.k === "甲方" || f.k === "乙方" || f.k === "交货地址"; }))
},
{
title: "金额与周期",
body:
renderStatRow([
ALL_LONG.find(function (f) { return f.k === "合同金额"; }),
ALL_SHORT.find(function (f) { return f.k === "合同状态"; }),
ALL_SHORT.find(function (f) { return f.k === "签订日期"; }),
ALL_SHORT.find(function (f) { return f.k === "到期日期"; })
])
},
{
title: "备注",
spanFull: true,
body: renderProseBlock("备注", ALL_LONG.find(function (f) { return f.k === "备注"; }).v)
}
])
);
}
},
{
id: "alt-stat-sparse",
name: "Z · 少字段指标 + 属性行",
width: "w720",
widthLabel: "720px",
badge: "推荐",
desc: "备用金等少字段场景:顶部 3 指标 + 下方属性行,比四列 KV 更饱满,比表格更轻。",
meta: "7 字段 · 720px",
previewClass: "w920",
sparse: true,
altMode: true,
render: function () {
var stats = SPARSE_FIELDS.filter(function (f) {
return f.k === "申请金额(元)" || f.k === "当前节点" || f.k === "冲抵总额(元)";
});
var rest = SPARSE_FIELDS.filter(function (f) {
return stats.indexOf(f) === -1;
});
return section("申请概览", renderStatRow(stats)) + section("申请明细", renderPropList(rest));
}
}
];
var SPARSE_LAYOUTS = [
{
id: "sparse-auto-cols",
name: "H · 自适应降列 + 居中窄区",
width: "w1040",
widthLabel: "1040 壳 / 2列 KV",
badge: "推荐",
desc: "弹窗仍 1040,KV 区 max-width 680 居中,字段 ≤8 时自动 2 列(非 4 列)。视觉不「铺满空格子」。",
meta: "7 字段 · 2 列",
previewClass: "w920",
sparse: true,
render: function () {
var cols = suggestCols(SPARSE_FIELDS.length);
var html = kvGrid(SPARSE_FIELDS, cols, function (f) {
return f.k === "申请原因" ? "span-all" : "";
});
return '<div class="kv-panel-centered">' + section("申请信息", html) + "</div>";
}
},
{
id: "sparse-kpi",
name: "I · Hero KPI 条 + 精简 KV",
width: "w1040",
widthLabel: "1040px",
badge: "推荐",
desc: "金额/状态/节点提到 Hero 下 KPI 三格;正文只留 3~4 个补充字段,2 列 KV。",
meta: "7 字段 · KPI 下沉",
previewClass: "w920",
sparse: true,
render: function () {
var kpi =
'<div class="kpi-strip">' +
'<div class="kpi-chip"><div class="lbl">申请金额(元)</div><div class="val money">¥ 5,000.00</div></div>' +
'<div class="kpi-chip"><div class="lbl">当前节点</div><div class="val"><span class="tag">部门审批</span></div></div>' +
'<div class="kpi-chip"><div class="lbl">冲抵总额(元)</div><div class="val money">¥ 0.00</div></div>' +
"</div>";
var rest = SPARSE_FIELDS.filter(function (f) {
return f.k !== "申请金额(元)" && f.k !== "当前节点" && f.k !== "冲抵总额(元)";
});
return kpi + section("其他信息", kvGrid(rest, 2, function (f) {
return f.k === "申请原因" ? "span-all" : "";
}));
}
},
{
id: "sparse-chips",
name: "J · Hero 芯片摘要",
width: "w1040",
widthLabel: "1040px",
badge: "轻量",
desc: "次要短字段(时间/节点/冲抵)收成 Hero 下 chip 行;正文仅保留人、原因等 2~3 项。",
meta: "7 字段 · chip",
previewClass: "w920",
sparse: true,
renderHeroExtra: function () {
return (
'<div class="hero-chips">' +
'<span class="hero-chip"><b>节点</b>部门审批</span>' +
'<span class="hero-chip"><b>时间</b>2026-06-18 14:32</span>' +
'<span class="hero-chip"><b>冲抵</b>¥ 0.00</span>' +
"</div>"
);
},
render: function () {
var core = SPARSE_FIELDS.filter(function (f) {
return f.k === "申请人" || f.k === "申请原因" || f.k === "实际发放(元)";
});
return section("申请信息", kvGrid(core, 2, function (f) {
return f.k === "申请原因" ? "span-all" : "";
}));
}
},
{
id: "sparse-bento",
name: "K · 单卡片 Bento",
width: "w920",
widthLabel: "920px",
badge: "卡片",
desc: "少字段时一张主题卡片包裹全部 KV(2 列),比大面积空白 grid 更有「档案感」。",
meta: "7 字段 · 单卡",
previewClass: "w920",
sparse: true,
render: function () {
return (
'<div class="bento-single">' +
'<div class="info-card"><h4>备用金申请</h4>' +
kvGrid(SPARSE_FIELDS, 2, function (f) { return f.k === "申请原因" ? "span-all" : ""; }) +
"</div></div>"
);
}
},
{
id: "sparse-compact",
name: "L · 窄弹窗 720px",
width: "w720",
widthLabel: "720px",
badge: "紧凑",
desc: "字段 ≤6 时直接缩弹窗宽度 + 2 列 KV,不强行 1040。报销/审批类简详情最常用。",
meta: "7 字段 · 720px",
previewClass: "w920",
sparse: true,
dialogClass: "dialog-compact",
render: function () {
return section("申请信息", kvGrid(SPARSE_FIELDS, 2, function (f) {
return f.k === "申请原因" ? "span-all" : "";
}));
}
}
];
var ALL_LAYOUTS = LAYOUTS.concat(TABLE_LAYOUTS).concat(ALT_LAYOUTS).concat(SPARSE_LAYOUTS);
function renderPreview(layout) {
return '<div class="mini-wrap"><div class="mini-dialog ' + layout.previewClass + '">' + layout.render() + "</div></div>";
}
function renderFull(layout) {
var title = layout.sparse ? "备用金申请 · BYJ-2026-0618" : "产品销售合同 · 查看";
var sub = layout.sparse ? "张三 · 申请 ¥5,000.00" : "HT-2026-SX-00428 · 华东精细化工贸易公司";
var heroExtra = layout.renderHeroExtra ? layout.renderHeroExtra() : "";
return (
'<div class="dialog-hero">' +
"<div><h2>" + title + "</h2><p>" + sub + "</p>" + heroExtra + "</div>" +
'<span class="width-tag">' + esc(layout.widthLabel) + "</span>" +
"</div>" +
'<div class="dialog-scroll">' + layout.render() + "</div>" +
'<div class="dialog-foot"><button type="button" class="btn-ghost" data-close>关闭</button></div>'
);
}
function mountLayoutCards(targetId, layouts, recBadge) {
var target = document.getElementById(targetId);
if (!target) return;
layouts.forEach(function (layout) {
var card = document.createElement("article");
var isRec = layout.badge === recBadge || layout.badge === "推荐";
card.className = "layout-card" + (isRec ? " rec" : "");
card.innerHTML =
'<div class="layout-card-head">' +
"<div><h2>" + esc(layout.name) + "</h2><p>" + esc(layout.desc) + "</p></div>" +
'<span class="badge' +
(layout.badge === "表格感" ? " blue" : layout.altMode ? " amber" : "") +
'">' +
esc(layout.widthLabel) +
"</span>" +
"</div>" +
'<div class="layout-preview">' + renderPreview(layout) + "</div>" +
'<div class="layout-card-foot">' +
'<span class="meta">' + esc(layout.meta) + "</span>" +
'<button type="button" class="btn" data-open="' + layout.id + '">全屏预览</button>' +
"</div>";
target.appendChild(card);
});
}
function initDialogInteractions(layout) {
if (!layout || !layout.interactive) return;
dialog.querySelectorAll("[data-tab]").forEach(function (btn) {
btn.addEventListener("click", function () {
var group = btn.getAttribute("data-tab-group");
var idx = btn.getAttribute("data-tab");
dialog.querySelectorAll('[data-tab-group="' + group + '"][data-tab]').forEach(function (b) {
b.classList.toggle("is-active", b === btn);
});
dialog.querySelectorAll('[data-tab-group="' + group + '"][data-tab-panel]').forEach(function (p) {
p.classList.toggle("is-active", p.getAttribute("data-tab-panel") === idx);
});
});
});
dialog.querySelectorAll("[data-acc-toggle]").forEach(function (btn) {
btn.addEventListener("click", function () {
var item = btn.closest(".acc-item");
if (item) item.classList.toggle("is-open");
});
});
}
mountLayoutCards("grid", LAYOUTS, "推荐");
mountLayoutCards("table-grid", TABLE_LAYOUTS, "推荐");
mountLayoutCards("alt-grid", ALT_LAYOUTS, "推荐");
mountLayoutCards("sparse-grid", SPARSE_LAYOUTS, "推荐");
var backdrop = document.getElementById("backdrop");
var dialog = document.getElementById("dialog");
document.addEventListener("click", function (e) {
var btn = e.target.closest("[data-open]");
if (!btn) return;
var layout = ALL_LAYOUTS.find(function (l) { return l.id === btn.getAttribute("data-open"); });
if (!layout) return;
dialog.className = "dialog " + layout.width + (layout.dialogClass ? " " + layout.dialogClass : "");
dialog.innerHTML = renderFull(layout);
initDialogInteractions(layout);
backdrop.classList.add("open");
});
backdrop.addEventListener("click", function (e) {
if (e.target === backdrop || e.target.closest("[data-close]")) backdrop.classList.remove("open");
});
document.addEventListener("keydown", function (e) {
if (e.key === "Escape") backdrop.classList.remove("open");
});
</script>
</body>
</html>