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

623 lines
22 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 · 查看弹窗 KV 布局对比 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;
--warn: #d97706;
--warn-bg: #fffbeb;
}
* { 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: 1280px; margin: 0 auto; padding: 32px 24px 80px; }
.page-head { margin-bottom: 28px; }
.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; color: var(--text-muted); max-width: 720px; line-height: 1.6; }
.page-head a { color: var(--primary-dark); }
.legend {
display: flex;
flex-wrap: wrap;
gap: 10px;
margin-bottom: 28px;
padding: 14px 16px;
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius);
font-size: 13px;
}
.legend-item { display: flex; align-items: center; gap: 6px; }
.legend-dot { width: 10px; height: 10px; border-radius: 50%; flex-shrink: 0; }
.legend-dot.rec { background: var(--primary); }
.legend-dot.ok { background: #2563eb; }
.legend-dot.avoid { background: var(--danger); }
.compare-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 20px;
}
@media (max-width: 960px) { .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.45); box-shadow: 0 0 0 1px rgba(16, 185, 129, 0.12); }
.layout-card.avoid { border-color: rgba(220, 38, 38, 0.25); }
.layout-card-head {
padding: 14px 16px;
border-bottom: 1px solid var(--border);
display: flex;
align-items: flex-start;
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;
white-space: nowrap;
}
.badge.rec { background: var(--primary-bg); color: var(--primary-dark); }
.badge.ok { background: #eff6ff; color: #2563eb; }
.badge.avoid { background: #fef2f2; color: var(--danger); }
/* 内嵌预览区(缩小版弹窗体) */
.layout-preview {
flex: 1;
padding: 12px;
background: #f1f5f9;
min-height: 320px;
overflow: auto;
}
.mini-dialog {
background: #fff;
border-radius: 8px;
border: 1px solid var(--border);
overflow: hidden;
font-size: 12px;
}
.mini-section {
padding: 12px 14px;
border-bottom: 1px solid #ebeef5;
}
.mini-section:last-child { border-bottom: none; }
.mini-h3 {
margin: 0 0 10px;
padding-left: 8px;
font-size: 12px;
font-weight: 600;
border-left: 3px solid var(--primary);
line-height: 1;
}
.mini-section .mini-h3:not(:first-child) { margin-top: 14px; }
.layout-card-foot {
padding: 12px 16px;
border-top: 1px solid var(--border);
display: flex;
justify-content: space-between;
align-items: center;
gap: 8px;
background: var(--table-head);
}
.layout-card-foot .hint { font-size: 11px; color: var(--text-muted); }
.btn {
display: inline-flex;
align-items: center;
gap: 5px;
height: 32px;
padding: 0 12px;
border-radius: 8px;
font-size: 12px;
font-weight: 600;
font-family: inherit;
cursor: pointer;
border: 1px solid var(--border);
background: var(--surface);
color: var(--text);
}
.btn-primary { background: var(--primary); border-color: var(--primary); color: #fff; }
.btn:hover { filter: brightness(0.97); }
/* ═══ 布局 A:上下 + 双列网格(推荐默认)═══ */
.kv-stacked-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 10px 14px;
}
.kv-stacked { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.kv-stacked dt { margin: 0; font-size: 10px; color: var(--text-muted); font-weight: 400; }
.kv-stacked dd { margin: 0; font-size: 12px; font-weight: 600; word-break: break-word; line-height: 1.45; }
.kv-stacked.span-2 { grid-column: 1 / -1; }
/* ═══ 布局 B:左右 + 双列网格(不推荐)═══ */
.kv-inline-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 8px 12px;
}
.kv-inline-cell {
display: grid;
grid-template-columns: 52px 1fr;
gap: 4px 8px;
align-items: start;
font-size: 12px;
min-width: 0;
}
.kv-inline-cell dt { margin: 0; color: var(--text-muted); font-weight: 500; font-size: 10px; }
.kv-inline-cell dd { margin: 0; font-weight: 600; word-break: break-word; line-height: 1.4; }
.kv-inline-cell.span-2 { grid-column: 1 / -1; grid-template-columns: 52px 1fr; }
/* ═══ 布局 C:左右单行紧凑清单 ═══ */
.kv-rows .kv-row {
display: flex;
align-items: baseline;
justify-content: space-between;
gap: 12px;
padding: 7px 0;
border-bottom: 1px solid #f1f5f9;
font-size: 12px;
}
.kv-rows .kv-row:last-child { border-bottom: none; }
.kv-rows .kv-row dt { margin: 0; color: var(--text-muted); flex-shrink: 0; }
.kv-rows .kv-row dd { margin: 0; font-weight: 600; text-align: right; word-break: break-word; }
/* ═══ 布局 D:表格式 ═══ */
.kv-table { width: 100%; border-collapse: collapse; font-size: 12px; }
.kv-table td {
padding: 7px 0;
border-bottom: 1px dotted #e2e8f0;
vertical-align: top;
}
.kv-table td:first-child { width: 88px; color: var(--text-muted); font-weight: 500; padding-right: 10px; }
.kv-table td:last-child { font-weight: 600; word-break: break-word; }
.kv-table tr:last-child td { border-bottom: none; }
/* ═══ 布局 E:指标卡 + 上下双列 ═══ */
.stat-row {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 8px;
margin-bottom: 12px;
}
.stat-chip {
border: 1px solid var(--border);
border-radius: 8px;
padding: 8px 10px;
text-align: center;
background: #fff;
}
.stat-chip .lbl { font-size: 10px; color: var(--text-muted); }
.stat-chip .val { font-size: 14px; font-weight: 700; color: var(--primary-dark); margin-top: 2px; }
/* ═══ 布局 F:上下 + 三列网格 ═══ */
.kv-stacked-grid.cols-3 { grid-template-columns: repeat(3, 1fr); }
.tag {
display: inline-block;
padding: 1px 7px;
border-radius: 5px;
font-size: 11px;
font-weight: 600;
background: var(--primary-bg);
color: var(--primary-dark);
border: 1px solid rgba(16,185,129,0.25);
}
.long-val { color: var(--danger); }
.long-val-note { font-size: 10px; color: var(--danger); font-weight: 400; display: block; margin-top: 2px; }
/* ── 全屏弹窗 ── */
.backdrop {
position: fixed;
inset: 0;
background: rgba(15, 23, 42, 0.45);
display: none;
align-items: center;
justify-content: center;
padding: 24px;
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 - 48px);
display: flex;
flex-direction: column;
overflow: hidden;
animation: fadeIn 0.2s ease;
}
@keyframes fadeIn {
from { opacity: 0; transform: scale(0.97); }
to { opacity: 1; transform: none; }
}
.dialog.w720 { max-width: 720px; }
.dialog.w560 { max-width: 560px; }
.dialog.w920 { max-width: 920px; }
.dialog-scroll { overflow-y: auto; flex: 1; }
.dialog-foot {
padding: 12px 20px;
border-top: 1px solid var(--border);
display: flex;
justify-content: flex-end;
background: var(--table-head);
}
.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;
}
/* 全尺寸样式(弹窗内用 13px) */
.dialog .kv-stacked dt { font-size: 11px; }
.dialog .kv-stacked dd { font-size: 13px; }
.dialog .kv-inline-cell { grid-template-columns: 96px 1fr; gap: 8px 12px; font-size: 13px; }
.dialog .kv-inline-cell dt { font-size: 13px; }
.dialog .kv-stacked-grid { gap: 12px 20px; }
.dialog .kv-inline-grid { gap: 12px 20px; }
.dialog .kv-rows .kv-row { padding: 11px 0; font-size: 13px; }
.dialog .kv-table { font-size: 13px; }
.dialog .kv-table td { padding: 8px 0; }
.dialog .kv-table td:first-child { width: 120px; }
.dialog .stat-row { gap: 12px; margin-bottom: 16px; }
.dialog .stat-chip { padding: 12px 14px; }
.dialog .stat-chip .lbl { font-size: 11px; }
.dialog .stat-chip .val { font-size: 20px; }
.dialog-hero {
padding: 16px 22px;
background: linear-gradient(135deg, var(--primary-bg), #fff);
border-bottom: 1px solid var(--border);
}
.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); }
.note-bar {
margin: 20px 0 0;
padding: 12px 16px;
background: var(--warn-bg);
border: 1px solid #fde68a;
border-radius: var(--radius);
font-size: 13px;
color: #92400e;
line-height: 1.5;
}
</style>
</head>
<body>
<div class="page">
<header class="page-head">
<h1><span>KV 布局</span> · 同一组数据 · 六种排法对比</h1>
<p>
以下均使用<strong>相同的 12 个字段</strong>(含长路径、地址、备注),在 720px 宽弹窗场景下对比。
重点观察<strong>长值字段</strong>(所属区域、备注)在不同布局下的可读性。
另见 <a href="fresh-mint-view-dialog-demos.html">查看弹窗风格 Demo</a> ·
<a href="fresh-mint-view-wide-dialog-demos.html">宽弹窗多列 Demo</a>。
</p>
</header>
<div class="legend">
<div class="legend-item"><span class="legend-dot rec"></span> 推荐默认</div>
<div class="legend-item"><span class="legend-dot ok"></span> 特定场景适用</div>
<div class="legend-item"><span class="legend-dot avoid"></span> 双列 grid 内慎用</div>
</div>
<div class="compare-grid" id="compareGrid"></div>
<div class="note-bar">
<strong>选型口诀:</strong>双列网格里用<strong>上下</strong>;单列窄条用<strong>左右行</strong>;字段 &gt;12 条同质属性用<strong>表格</strong>;核心数字抽<strong>指标卡</strong>;字段 &gt;15 个用 Tab 分区(不在此页展开)。
</div>
</div>
<!-- 全屏弹窗容器 -->
<div class="backdrop" id="backdrop" role="dialog" aria-modal="true">
<div class="dialog w720" id="dialogInner"></div>
</div>
<script>
/* ── 样本数据:12 字段,刻意含长短值 ── */
var SAMPLE = {
title: "张三 · 用户详情",
subtitle: "工号 SX-2024-0086 · 调度主管",
sections: [
{
title: "基本信息",
fields: [
{ label: "用户名", value: "zhangsan", long: false },
{ label: "姓名", value: "张三", long: false },
{ label: "手机号", value: "138****6688", long: false },
{ label: "账号状态", value: "正常", tag: true, long: false },
{ label: "所属角色", value: "调度主管、区域管理员、报表查看", long: true },
{ label: "所属部门", value: "运营中心 / 华东调度组", long: true },
{ label: "所属区域", value: "华东 / 浙江省 / 杭州市 / 滨江区 / 长河街道配送站", long: true },
{ label: "最后登录", value: "2026-06-18 09:32:15", long: false },
{ label: "创建时间", value: "2024-03-12 14:20:00", long: false },
{ label: "创建人", value: "系统管理员", long: false },
{ label: "排序", value: "10", long: false },
{ label: "备注", value: "2024 年 Q1 从宁波区域调岗至华东;负责城配线路排班与司机考勤对接,紧急联系人:李经理 139****1234。", long: true }
]
}
],
stats: [
{ label: "本月登录", value: "28 次" },
{ label: "待办任务", value: "5" },
{ label: "管辖站点", value: "12" }
]
};
var LAYOUTS = [
{
id: "stacked-2col",
name: "A · 上下 + 双列网格",
badge: "rec",
badgeText: "推荐默认",
cardClass: "rec",
desc: "SystemViewDialog 应对齐此方案。720px 弹窗内每格 ~330px,值可换行。",
hint: "字段 6~20 个",
width: "w720",
render: renderStacked2Col
},
{
id: "inline-2col",
name: "B · 左右 + 双列网格",
badge: "avoid",
badgeText: "不推荐",
cardClass: "avoid",
desc: "规范 §9.5 旧写法。格内再分左右,长值被压到 ~180px,路径/备注难读。",
hint: "双列 grid 内避免",
width: "w720",
render: renderInline2Col
},
{
id: "inline-rows",
name: "C · 左右单行(紧凑清单)",
badge: "ok",
badgeText: "轻量实体",
cardClass: "",
desc: "单列全宽,每行 label 左 value 右。适合单位/字典等 ≤8 字段,弹窗 560px。",
hint: "字段 ≤8 · 560px",
width: "w560",
render: renderInlineRows
},
{
id: "table",
name: "D · 表格式 KV",
badge: "ok",
badgeText: "字段多",
cardClass: "",
desc: "12+ 条同质属性,label 列对齐,逐行扫读。适合档案/票据/审计类。",
hint: "字段 12~20",
width: "w720",
render: renderTable
},
{
id: "stats-stacked",
name: "E · 指标卡 + 上下双列",
badge: "rec",
badgeText: "业务详情",
cardClass: "rec",
desc: "3~4 个关键数字提到 Bento 卡,其余走上下双列。订单/合同/客户常用。",
hint: "含金额/数量",
width: "w920",
render: renderStatsStacked
},
{
id: "stacked-3col",
name: "F · 上下 + 三列网格",
badge: "ok",
badgeText: "短字段多",
cardClass: "",
desc: "920px 宽弹窗,字段全是短值(编码/状态/日期)时可提高密度。",
hint: "920px · 短值为主",
width: "w920",
render: renderStacked3Col
}
];
function esc(s) {
var d = document.createElement("div");
d.textContent = s;
return d.innerHTML;
}
function fieldValue(f) {
if (f.tag) return '<span class="tag">' + esc(f.value) + "</span>";
var html = esc(f.value);
if (f.long) html += '<span class="long-val-note">← 长值</span>';
return html;
}
function allFields() {
return SAMPLE.sections[0].fields;
}
function previewTitle(title) {
return '<h3 class="mini-h3">' + esc(title) + "</h3>";
}
function section(title, content) {
return '<div class="view-section"><h3>' + esc(title) + "</h3>" + content + "</div>";
}
function stackedGrid(fields, opts) {
opts = opts || {};
var cols = opts.cols ? " kv-stacked-grid cols-3" : " kv-stacked-grid";
var html = "<dl class=\"" + cols.trim() + '">';
fields.forEach(function (f) {
var span = opts.spanLong && f.long ? " span-2" : "";
html += '<div class="kv-stacked' + span + '"><dt>' + esc(f.label) + "</dt><dd>" + fieldValue(f) + "</dd></div>";
});
return html + "</dl>";
}
/* ── 六种布局 ── */
function renderStacked2Col(mode) {
var fields = allFields();
var body = stackedGrid(fields, { spanLong: mode === "full" });
if (mode === "preview") return previewTitle("基本信息") + body;
return section("基本信息", body);
}
function renderInline2Col(mode) {
var fields = allFields();
var html = '<dl class="kv-inline-grid">';
fields.forEach(function (f) {
var span = f.long && mode === "full" ? " span-2" : "";
html += '<div class="kv-inline-cell' + span + '"><dt>' + esc(f.label) + "</dt><dd>" + fieldValue(f) + "</dd></div>";
});
html += "</dl>";
if (mode === "preview") return previewTitle("基本信息") + html;
return section("基本信息", html);
}
function renderInlineRows(mode) {
var fields = allFields();
var html = '<dl class="kv-rows">';
fields.forEach(function (f) {
html += '<div class="kv-row"><dt>' + esc(f.label) + "</dt><dd>" + fieldValue(f) + "</dd></div>";
});
html += "</dl>";
if (mode === "preview") return previewTitle("基本信息") + html;
return section("基本信息", html);
}
function renderTable(mode) {
var fields = allFields();
var html = "<table class=\"kv-table\"><tbody>";
fields.forEach(function (f) {
html += "<tr><td>" + esc(f.label) + "</td><td>" + fieldValue(f) + "</td></tr>";
});
html += "</tbody></table>";
if (mode === "preview") return previewTitle("基本信息") + html;
return section("基本信息", html);
}
function renderStatsStacked(mode) {
var statsHtml = '<div class="stat-row">';
SAMPLE.stats.forEach(function (s) {
statsHtml += '<div class="stat-chip"><div class="lbl">' + esc(s.label) + '</div><div class="val">' + esc(s.value) + "</div></div>";
});
statsHtml += "</div>";
var kvHtml = stackedGrid(allFields(), { spanLong: mode === "full" });
if (mode === "preview") {
return previewTitle("业务概览") + statsHtml + previewTitle("基本信息") + kvHtml;
}
return section("业务概览", statsHtml) + section("基本信息", kvHtml);
}
function renderStacked3Col(mode) {
var shortFields = allFields().filter(function (f) { return !f.long; });
var longFields = allFields().filter(function (f) { return f.long; });
var shortHtml = stackedGrid(shortFields, { cols: true });
var longHtml = stackedGrid(longFields, { spanLong: true });
if (mode === "preview") {
return previewTitle("短字段 · 三列") + shortHtml + previewTitle("长字段 · 整行") + longHtml;
}
return section("基本属性(短字段 · 三列)", shortHtml) + section("补充信息(长字段 · 整行)", longHtml);
}
function renderPreview(layout) {
return '<div class="mini-section">' + layout.render("preview") + "</div>";
}
function renderFullDialog(layout) {
return (
'<div class="dialog-hero"><h2>' + esc(SAMPLE.title) + "</h2><p>" + esc(SAMPLE.subtitle) + '</p></div>' +
'<div class="dialog-scroll">' + layout.render("full") + '</div>' +
'<div class="dialog-foot"><button type="button" class="btn" data-close>关闭</button></div>'
);
}
/* ── 构建对比卡片 ── */
var grid = document.getElementById("compareGrid");
LAYOUTS.forEach(function (layout) {
var card = document.createElement("article");
card.className = "layout-card" + (layout.cardClass ? " " + layout.cardClass : "");
card.innerHTML =
'<div class="layout-card-head">' +
"<div><h2>" + esc(layout.name) + '</h2><p>' + esc(layout.desc) + "</p></div>" +
'<span class="badge ' + layout.badge + '">' + esc(layout.badgeText) + "</span>" +
"</div>" +
'<div class="layout-preview"><div class="mini-dialog">' + renderPreview(layout) + "</div></div>" +
'<div class="layout-card-foot">' +
'<span class="hint">' + esc(layout.hint) + "</span>" +
'<button type="button" class="btn btn-primary" data-layout="' + layout.id + '">全屏预览</button>' +
"</div>";
grid.appendChild(card);
});
/* ── 全屏弹窗 ── */
var backdrop = document.getElementById("backdrop");
var dialogInner = document.getElementById("dialogInner");
document.querySelectorAll("[data-layout]").forEach(function (btn) {
btn.addEventListener("click", function () {
var id = btn.getAttribute("data-layout");
var layout = LAYOUTS.find(function (l) { return l.id === id; });
if (!layout) return;
dialogInner.className = "dialog " + layout.width;
dialogInner.innerHTML = renderFullDialog(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>