1861 lines
65 KiB
HTML
1861 lines
65 KiB
HTML
<!doctype html>
|
||
<html lang="zh-CN">
|
||
<head>
|
||
<meta charset="UTF-8" />
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||
<title>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>
|