fresh-mint-view-kv-layout-demos.html 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622
  1. <!doctype html>
  2. <html lang="zh-CN">
  3. <head>
  4. <meta charset="UTF-8" />
  5. <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  6. <title>Fresh Mint · 查看弹窗 KV 布局对比 Demo</title>
  7. <link rel="preconnect" href="https://fonts.googleapis.com" />
  8. <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
  9. <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet" />
  10. <style>
  11. :root {
  12. --primary: #10b981;
  13. --primary-dark: #059669;
  14. --primary-bg: #ecfdf5;
  15. --text: #1e293b;
  16. --text-muted: #64748b;
  17. --border: #d9e8e2;
  18. --surface: #ffffff;
  19. --table-head: #f8fafc;
  20. --shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
  21. --shadow-lg: 0 8px 24px rgba(15, 23, 42, 0.08);
  22. --radius: 10px;
  23. --danger: #dc2626;
  24. --warn: #d97706;
  25. --warn-bg: #fffbeb;
  26. }
  27. * { box-sizing: border-box; }
  28. body {
  29. margin: 0;
  30. min-height: 100vh;
  31. font-family: Inter, -apple-system, sans-serif;
  32. font-size: 14px;
  33. line-height: 1.5;
  34. color: var(--text);
  35. background: #f8fafc;
  36. }
  37. .page { max-width: 1280px; margin: 0 auto; padding: 32px 24px 80px; }
  38. .page-head { margin-bottom: 28px; }
  39. .page-head h1 { margin: 0 0 8px; font-size: 24px; font-weight: 700; }
  40. .page-head h1 span { color: var(--primary); }
  41. .page-head p { margin: 0; color: var(--text-muted); max-width: 720px; line-height: 1.6; }
  42. .page-head a { color: var(--primary-dark); }
  43. .legend {
  44. display: flex;
  45. flex-wrap: wrap;
  46. gap: 10px;
  47. margin-bottom: 28px;
  48. padding: 14px 16px;
  49. background: var(--surface);
  50. border: 1px solid var(--border);
  51. border-radius: var(--radius);
  52. font-size: 13px;
  53. }
  54. .legend-item { display: flex; align-items: center; gap: 6px; }
  55. .legend-dot { width: 10px; height: 10px; border-radius: 50%; flex-shrink: 0; }
  56. .legend-dot.rec { background: var(--primary); }
  57. .legend-dot.ok { background: #2563eb; }
  58. .legend-dot.avoid { background: var(--danger); }
  59. .compare-grid {
  60. display: grid;
  61. grid-template-columns: repeat(2, 1fr);
  62. gap: 20px;
  63. }
  64. @media (max-width: 960px) { .compare-grid { grid-template-columns: 1fr; } }
  65. .layout-card {
  66. background: var(--surface);
  67. border: 1px solid var(--border);
  68. border-radius: var(--radius);
  69. overflow: hidden;
  70. box-shadow: var(--shadow);
  71. display: flex;
  72. flex-direction: column;
  73. }
  74. .layout-card.rec { border-color: rgba(16, 185, 129, 0.45); box-shadow: 0 0 0 1px rgba(16, 185, 129, 0.12); }
  75. .layout-card.avoid { border-color: rgba(220, 38, 38, 0.25); }
  76. .layout-card-head {
  77. padding: 14px 16px;
  78. border-bottom: 1px solid var(--border);
  79. display: flex;
  80. align-items: flex-start;
  81. justify-content: space-between;
  82. gap: 12px;
  83. }
  84. .layout-card-head h2 { margin: 0 0 4px; font-size: 15px; font-weight: 600; }
  85. .layout-card-head p { margin: 0; font-size: 12px; color: var(--text-muted); line-height: 1.45; }
  86. .badge {
  87. flex-shrink: 0;
  88. font-size: 11px;
  89. font-weight: 700;
  90. padding: 3px 8px;
  91. border-radius: 6px;
  92. white-space: nowrap;
  93. }
  94. .badge.rec { background: var(--primary-bg); color: var(--primary-dark); }
  95. .badge.ok { background: #eff6ff; color: #2563eb; }
  96. .badge.avoid { background: #fef2f2; color: var(--danger); }
  97. /* 内嵌预览区(缩小版弹窗体) */
  98. .layout-preview {
  99. flex: 1;
  100. padding: 12px;
  101. background: #f1f5f9;
  102. min-height: 320px;
  103. overflow: auto;
  104. }
  105. .mini-dialog {
  106. background: #fff;
  107. border-radius: 8px;
  108. border: 1px solid var(--border);
  109. overflow: hidden;
  110. font-size: 12px;
  111. }
  112. .mini-section {
  113. padding: 12px 14px;
  114. border-bottom: 1px solid #ebeef5;
  115. }
  116. .mini-section:last-child { border-bottom: none; }
  117. .mini-h3 {
  118. margin: 0 0 10px;
  119. padding-left: 8px;
  120. font-size: 12px;
  121. font-weight: 600;
  122. border-left: 3px solid var(--primary);
  123. line-height: 1;
  124. }
  125. .mini-section .mini-h3:not(:first-child) { margin-top: 14px; }
  126. .layout-card-foot {
  127. padding: 12px 16px;
  128. border-top: 1px solid var(--border);
  129. display: flex;
  130. justify-content: space-between;
  131. align-items: center;
  132. gap: 8px;
  133. background: var(--table-head);
  134. }
  135. .layout-card-foot .hint { font-size: 11px; color: var(--text-muted); }
  136. .btn {
  137. display: inline-flex;
  138. align-items: center;
  139. gap: 5px;
  140. height: 32px;
  141. padding: 0 12px;
  142. border-radius: 8px;
  143. font-size: 12px;
  144. font-weight: 600;
  145. font-family: inherit;
  146. cursor: pointer;
  147. border: 1px solid var(--border);
  148. background: var(--surface);
  149. color: var(--text);
  150. }
  151. .btn-primary { background: var(--primary); border-color: var(--primary); color: #fff; }
  152. .btn:hover { filter: brightness(0.97); }
  153. /* ═══ 布局 A:上下 + 双列网格(推荐默认)═══ */
  154. .kv-stacked-grid {
  155. display: grid;
  156. grid-template-columns: repeat(2, 1fr);
  157. gap: 10px 14px;
  158. }
  159. .kv-stacked { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
  160. .kv-stacked dt { margin: 0; font-size: 10px; color: var(--text-muted); font-weight: 400; }
  161. .kv-stacked dd { margin: 0; font-size: 12px; font-weight: 600; word-break: break-word; line-height: 1.45; }
  162. .kv-stacked.span-2 { grid-column: 1 / -1; }
  163. /* ═══ 布局 B:左右 + 双列网格(不推荐)═══ */
  164. .kv-inline-grid {
  165. display: grid;
  166. grid-template-columns: repeat(2, 1fr);
  167. gap: 8px 12px;
  168. }
  169. .kv-inline-cell {
  170. display: grid;
  171. grid-template-columns: 52px 1fr;
  172. gap: 4px 8px;
  173. align-items: start;
  174. font-size: 12px;
  175. min-width: 0;
  176. }
  177. .kv-inline-cell dt { margin: 0; color: var(--text-muted); font-weight: 500; font-size: 10px; }
  178. .kv-inline-cell dd { margin: 0; font-weight: 600; word-break: break-word; line-height: 1.4; }
  179. .kv-inline-cell.span-2 { grid-column: 1 / -1; grid-template-columns: 52px 1fr; }
  180. /* ═══ 布局 C:左右单行紧凑清单 ═══ */
  181. .kv-rows .kv-row {
  182. display: flex;
  183. align-items: baseline;
  184. justify-content: space-between;
  185. gap: 12px;
  186. padding: 7px 0;
  187. border-bottom: 1px solid #f1f5f9;
  188. font-size: 12px;
  189. }
  190. .kv-rows .kv-row:last-child { border-bottom: none; }
  191. .kv-rows .kv-row dt { margin: 0; color: var(--text-muted); flex-shrink: 0; }
  192. .kv-rows .kv-row dd { margin: 0; font-weight: 600; text-align: right; word-break: break-word; }
  193. /* ═══ 布局 D:表格式 ═══ */
  194. .kv-table { width: 100%; border-collapse: collapse; font-size: 12px; }
  195. .kv-table td {
  196. padding: 7px 0;
  197. border-bottom: 1px dotted #e2e8f0;
  198. vertical-align: top;
  199. }
  200. .kv-table td:first-child { width: 88px; color: var(--text-muted); font-weight: 500; padding-right: 10px; }
  201. .kv-table td:last-child { font-weight: 600; word-break: break-word; }
  202. .kv-table tr:last-child td { border-bottom: none; }
  203. /* ═══ 布局 E:指标卡 + 上下双列 ═══ */
  204. .stat-row {
  205. display: grid;
  206. grid-template-columns: repeat(3, 1fr);
  207. gap: 8px;
  208. margin-bottom: 12px;
  209. }
  210. .stat-chip {
  211. border: 1px solid var(--border);
  212. border-radius: 8px;
  213. padding: 8px 10px;
  214. text-align: center;
  215. background: #fff;
  216. }
  217. .stat-chip .lbl { font-size: 10px; color: var(--text-muted); }
  218. .stat-chip .val { font-size: 14px; font-weight: 700; color: var(--primary-dark); margin-top: 2px; }
  219. /* ═══ 布局 F:上下 + 三列网格 ═══ */
  220. .kv-stacked-grid.cols-3 { grid-template-columns: repeat(3, 1fr); }
  221. .tag {
  222. display: inline-block;
  223. padding: 1px 7px;
  224. border-radius: 5px;
  225. font-size: 11px;
  226. font-weight: 600;
  227. background: var(--primary-bg);
  228. color: var(--primary-dark);
  229. border: 1px solid rgba(16,185,129,0.25);
  230. }
  231. .long-val { color: var(--danger); }
  232. .long-val-note { font-size: 10px; color: var(--danger); font-weight: 400; display: block; margin-top: 2px; }
  233. /* ── 全屏弹窗 ── */
  234. .backdrop {
  235. position: fixed;
  236. inset: 0;
  237. background: rgba(15, 23, 42, 0.45);
  238. display: none;
  239. align-items: center;
  240. justify-content: center;
  241. padding: 24px;
  242. z-index: 1000;
  243. backdrop-filter: none;
  244. }
  245. .backdrop.open { display: flex; }
  246. .dialog {
  247. background: #fff;
  248. border-radius: var(--radius);
  249. box-shadow: var(--shadow-lg);
  250. width: 100%;
  251. max-height: calc(100vh - 48px);
  252. display: flex;
  253. flex-direction: column;
  254. overflow: hidden;
  255. animation: fadeIn 0.2s ease;
  256. }
  257. @keyframes fadeIn {
  258. from { opacity: 0; transform: scale(0.97); }
  259. to { opacity: 1; transform: none; }
  260. }
  261. .dialog.w720 { max-width: 720px; }
  262. .dialog.w560 { max-width: 560px; }
  263. .dialog.w920 { max-width: 920px; }
  264. .dialog-scroll { overflow-y: auto; flex: 1; }
  265. .dialog-foot {
  266. padding: 12px 20px;
  267. border-top: 1px solid var(--border);
  268. display: flex;
  269. justify-content: flex-end;
  270. background: var(--table-head);
  271. }
  272. .view-section { padding: 18px 22px; border-bottom: 1px solid #ebeef5; }
  273. .view-section:last-child { border-bottom: none; }
  274. .view-section h3 {
  275. margin: 0 0 14px;
  276. padding-left: 10px;
  277. font-size: 14px;
  278. font-weight: 600;
  279. border-left: 3px solid var(--primary);
  280. line-height: 1;
  281. }
  282. /* 全尺寸样式(弹窗内用 13px) */
  283. .dialog .kv-stacked dt { font-size: 11px; }
  284. .dialog .kv-stacked dd { font-size: 13px; }
  285. .dialog .kv-inline-cell { grid-template-columns: 96px 1fr; gap: 8px 12px; font-size: 13px; }
  286. .dialog .kv-inline-cell dt { font-size: 13px; }
  287. .dialog .kv-stacked-grid { gap: 12px 20px; }
  288. .dialog .kv-inline-grid { gap: 12px 20px; }
  289. .dialog .kv-rows .kv-row { padding: 11px 0; font-size: 13px; }
  290. .dialog .kv-table { font-size: 13px; }
  291. .dialog .kv-table td { padding: 8px 0; }
  292. .dialog .kv-table td:first-child { width: 120px; }
  293. .dialog .stat-row { gap: 12px; margin-bottom: 16px; }
  294. .dialog .stat-chip { padding: 12px 14px; }
  295. .dialog .stat-chip .lbl { font-size: 11px; }
  296. .dialog .stat-chip .val { font-size: 20px; }
  297. .dialog-hero {
  298. padding: 16px 22px;
  299. background: linear-gradient(135deg, var(--primary-bg), #fff);
  300. border-bottom: 1px solid var(--border);
  301. }
  302. .dialog-hero h2 { margin: 0 0 4px; font-size: 18px; font-weight: 700; }
  303. .dialog-hero p { margin: 0; font-size: 13px; color: var(--text-muted); }
  304. .note-bar {
  305. margin: 20px 0 0;
  306. padding: 12px 16px;
  307. background: var(--warn-bg);
  308. border: 1px solid #fde68a;
  309. border-radius: var(--radius);
  310. font-size: 13px;
  311. color: #92400e;
  312. line-height: 1.5;
  313. }
  314. </style>
  315. </head>
  316. <body>
  317. <div class="page">
  318. <header class="page-head">
  319. <h1><span>KV 布局</span> · 同一组数据 · 六种排法对比</h1>
  320. <p>
  321. 以下均使用<strong>相同的 12 个字段</strong>(含长路径、地址、备注),在 720px 宽弹窗场景下对比。
  322. 重点观察<strong>长值字段</strong>(所属区域、备注)在不同布局下的可读性。
  323. 另见 <a href="fresh-mint-view-dialog-demos.html">查看弹窗风格 Demo</a> ·
  324. <a href="fresh-mint-view-wide-dialog-demos.html">宽弹窗多列 Demo</a>。
  325. </p>
  326. </header>
  327. <div class="legend">
  328. <div class="legend-item"><span class="legend-dot rec"></span> 推荐默认</div>
  329. <div class="legend-item"><span class="legend-dot ok"></span> 特定场景适用</div>
  330. <div class="legend-item"><span class="legend-dot avoid"></span> 双列 grid 内慎用</div>
  331. </div>
  332. <div class="compare-grid" id="compareGrid"></div>
  333. <div class="note-bar">
  334. <strong>选型口诀:</strong>双列网格里用<strong>上下</strong>;单列窄条用<strong>左右行</strong>;字段 &gt;12 条同质属性用<strong>表格</strong>;核心数字抽<strong>指标卡</strong>;字段 &gt;15 个用 Tab 分区(不在此页展开)。
  335. </div>
  336. </div>
  337. <!-- 全屏弹窗容器 -->
  338. <div class="backdrop" id="backdrop" role="dialog" aria-modal="true">
  339. <div class="dialog w720" id="dialogInner"></div>
  340. </div>
  341. <script>
  342. /* ── 样本数据:12 字段,刻意含长短值 ── */
  343. var SAMPLE = {
  344. title: "张三 · 用户详情",
  345. subtitle: "工号 SX-2024-0086 · 调度主管",
  346. sections: [
  347. {
  348. title: "基本信息",
  349. fields: [
  350. { label: "用户名", value: "zhangsan", long: false },
  351. { label: "姓名", value: "张三", long: false },
  352. { label: "手机号", value: "138****6688", long: false },
  353. { label: "账号状态", value: "正常", tag: true, long: false },
  354. { label: "所属角色", value: "调度主管、区域管理员、报表查看", long: true },
  355. { label: "所属部门", value: "运营中心 / 华东调度组", long: true },
  356. { label: "所属区域", value: "华东 / 浙江省 / 杭州市 / 滨江区 / 长河街道配送站", long: true },
  357. { label: "最后登录", value: "2026-06-18 09:32:15", long: false },
  358. { label: "创建时间", value: "2024-03-12 14:20:00", long: false },
  359. { label: "创建人", value: "系统管理员", long: false },
  360. { label: "排序", value: "10", long: false },
  361. { label: "备注", value: "2024 年 Q1 从宁波区域调岗至华东;负责城配线路排班与司机考勤对接,紧急联系人:李经理 139****1234。", long: true }
  362. ]
  363. }
  364. ],
  365. stats: [
  366. { label: "本月登录", value: "28 次" },
  367. { label: "待办任务", value: "5" },
  368. { label: "管辖站点", value: "12" }
  369. ]
  370. };
  371. var LAYOUTS = [
  372. {
  373. id: "stacked-2col",
  374. name: "A · 上下 + 双列网格",
  375. badge: "rec",
  376. badgeText: "推荐默认",
  377. cardClass: "rec",
  378. desc: "SystemViewDialog 应对齐此方案。720px 弹窗内每格 ~330px,值可换行。",
  379. hint: "字段 6~20 个",
  380. width: "w720",
  381. render: renderStacked2Col
  382. },
  383. {
  384. id: "inline-2col",
  385. name: "B · 左右 + 双列网格",
  386. badge: "avoid",
  387. badgeText: "不推荐",
  388. cardClass: "avoid",
  389. desc: "规范 §9.5 旧写法。格内再分左右,长值被压到 ~180px,路径/备注难读。",
  390. hint: "双列 grid 内避免",
  391. width: "w720",
  392. render: renderInline2Col
  393. },
  394. {
  395. id: "inline-rows",
  396. name: "C · 左右单行(紧凑清单)",
  397. badge: "ok",
  398. badgeText: "轻量实体",
  399. cardClass: "",
  400. desc: "单列全宽,每行 label 左 value 右。适合单位/字典等 ≤8 字段,弹窗 560px。",
  401. hint: "字段 ≤8 · 560px",
  402. width: "w560",
  403. render: renderInlineRows
  404. },
  405. {
  406. id: "table",
  407. name: "D · 表格式 KV",
  408. badge: "ok",
  409. badgeText: "字段多",
  410. cardClass: "",
  411. desc: "12+ 条同质属性,label 列对齐,逐行扫读。适合档案/票据/审计类。",
  412. hint: "字段 12~20",
  413. width: "w720",
  414. render: renderTable
  415. },
  416. {
  417. id: "stats-stacked",
  418. name: "E · 指标卡 + 上下双列",
  419. badge: "rec",
  420. badgeText: "业务详情",
  421. cardClass: "rec",
  422. desc: "3~4 个关键数字提到 Bento 卡,其余走上下双列。订单/合同/客户常用。",
  423. hint: "含金额/数量",
  424. width: "w920",
  425. render: renderStatsStacked
  426. },
  427. {
  428. id: "stacked-3col",
  429. name: "F · 上下 + 三列网格",
  430. badge: "ok",
  431. badgeText: "短字段多",
  432. cardClass: "",
  433. desc: "920px 宽弹窗,字段全是短值(编码/状态/日期)时可提高密度。",
  434. hint: "920px · 短值为主",
  435. width: "w920",
  436. render: renderStacked3Col
  437. }
  438. ];
  439. function esc(s) {
  440. var d = document.createElement("div");
  441. d.textContent = s;
  442. return d.innerHTML;
  443. }
  444. function fieldValue(f) {
  445. if (f.tag) return '<span class="tag">' + esc(f.value) + "</span>";
  446. var html = esc(f.value);
  447. if (f.long) html += '<span class="long-val-note">← 长值</span>';
  448. return html;
  449. }
  450. function allFields() {
  451. return SAMPLE.sections[0].fields;
  452. }
  453. function previewTitle(title) {
  454. return '<h3 class="mini-h3">' + esc(title) + "</h3>";
  455. }
  456. function section(title, content) {
  457. return '<div class="view-section"><h3>' + esc(title) + "</h3>" + content + "</div>";
  458. }
  459. function stackedGrid(fields, opts) {
  460. opts = opts || {};
  461. var cols = opts.cols ? " kv-stacked-grid cols-3" : " kv-stacked-grid";
  462. var html = "<dl class=\"" + cols.trim() + '">';
  463. fields.forEach(function (f) {
  464. var span = opts.spanLong && f.long ? " span-2" : "";
  465. html += '<div class="kv-stacked' + span + '"><dt>' + esc(f.label) + "</dt><dd>" + fieldValue(f) + "</dd></div>";
  466. });
  467. return html + "</dl>";
  468. }
  469. /* ── 六种布局 ── */
  470. function renderStacked2Col(mode) {
  471. var fields = allFields();
  472. var body = stackedGrid(fields, { spanLong: mode === "full" });
  473. if (mode === "preview") return previewTitle("基本信息") + body;
  474. return section("基本信息", body);
  475. }
  476. function renderInline2Col(mode) {
  477. var fields = allFields();
  478. var html = '<dl class="kv-inline-grid">';
  479. fields.forEach(function (f) {
  480. var span = f.long && mode === "full" ? " span-2" : "";
  481. html += '<div class="kv-inline-cell' + span + '"><dt>' + esc(f.label) + "</dt><dd>" + fieldValue(f) + "</dd></div>";
  482. });
  483. html += "</dl>";
  484. if (mode === "preview") return previewTitle("基本信息") + html;
  485. return section("基本信息", html);
  486. }
  487. function renderInlineRows(mode) {
  488. var fields = allFields();
  489. var html = '<dl class="kv-rows">';
  490. fields.forEach(function (f) {
  491. html += '<div class="kv-row"><dt>' + esc(f.label) + "</dt><dd>" + fieldValue(f) + "</dd></div>";
  492. });
  493. html += "</dl>";
  494. if (mode === "preview") return previewTitle("基本信息") + html;
  495. return section("基本信息", html);
  496. }
  497. function renderTable(mode) {
  498. var fields = allFields();
  499. var html = "<table class=\"kv-table\"><tbody>";
  500. fields.forEach(function (f) {
  501. html += "<tr><td>" + esc(f.label) + "</td><td>" + fieldValue(f) + "</td></tr>";
  502. });
  503. html += "</tbody></table>";
  504. if (mode === "preview") return previewTitle("基本信息") + html;
  505. return section("基本信息", html);
  506. }
  507. function renderStatsStacked(mode) {
  508. var statsHtml = '<div class="stat-row">';
  509. SAMPLE.stats.forEach(function (s) {
  510. statsHtml += '<div class="stat-chip"><div class="lbl">' + esc(s.label) + '</div><div class="val">' + esc(s.value) + "</div></div>";
  511. });
  512. statsHtml += "</div>";
  513. var kvHtml = stackedGrid(allFields(), { spanLong: mode === "full" });
  514. if (mode === "preview") {
  515. return previewTitle("业务概览") + statsHtml + previewTitle("基本信息") + kvHtml;
  516. }
  517. return section("业务概览", statsHtml) + section("基本信息", kvHtml);
  518. }
  519. function renderStacked3Col(mode) {
  520. var shortFields = allFields().filter(function (f) { return !f.long; });
  521. var longFields = allFields().filter(function (f) { return f.long; });
  522. var shortHtml = stackedGrid(shortFields, { cols: true });
  523. var longHtml = stackedGrid(longFields, { spanLong: true });
  524. if (mode === "preview") {
  525. return previewTitle("短字段 · 三列") + shortHtml + previewTitle("长字段 · 整行") + longHtml;
  526. }
  527. return section("基本属性(短字段 · 三列)", shortHtml) + section("补充信息(长字段 · 整行)", longHtml);
  528. }
  529. function renderPreview(layout) {
  530. return '<div class="mini-section">' + layout.render("preview") + "</div>";
  531. }
  532. function renderFullDialog(layout) {
  533. return (
  534. '<div class="dialog-hero"><h2>' + esc(SAMPLE.title) + "</h2><p>" + esc(SAMPLE.subtitle) + '</p></div>' +
  535. '<div class="dialog-scroll">' + layout.render("full") + '</div>' +
  536. '<div class="dialog-foot"><button type="button" class="btn" data-close>关闭</button></div>'
  537. );
  538. }
  539. /* ── 构建对比卡片 ── */
  540. var grid = document.getElementById("compareGrid");
  541. LAYOUTS.forEach(function (layout) {
  542. var card = document.createElement("article");
  543. card.className = "layout-card" + (layout.cardClass ? " " + layout.cardClass : "");
  544. card.innerHTML =
  545. '<div class="layout-card-head">' +
  546. "<div><h2>" + esc(layout.name) + '</h2><p>' + esc(layout.desc) + "</p></div>" +
  547. '<span class="badge ' + layout.badge + '">' + esc(layout.badgeText) + "</span>" +
  548. "</div>" +
  549. '<div class="layout-preview"><div class="mini-dialog">' + renderPreview(layout) + "</div></div>" +
  550. '<div class="layout-card-foot">' +
  551. '<span class="hint">' + esc(layout.hint) + "</span>" +
  552. '<button type="button" class="btn btn-primary" data-layout="' + layout.id + '">全屏预览</button>' +
  553. "</div>";
  554. grid.appendChild(card);
  555. });
  556. /* ── 全屏弹窗 ── */
  557. var backdrop = document.getElementById("backdrop");
  558. var dialogInner = document.getElementById("dialogInner");
  559. document.querySelectorAll("[data-layout]").forEach(function (btn) {
  560. btn.addEventListener("click", function () {
  561. var id = btn.getAttribute("data-layout");
  562. var layout = LAYOUTS.find(function (l) { return l.id === id; });
  563. if (!layout) return;
  564. dialogInner.className = "dialog " + layout.width;
  565. dialogInner.innerHTML = renderFullDialog(layout);
  566. backdrop.classList.add("open");
  567. });
  568. });
  569. backdrop.addEventListener("click", function (e) {
  570. if (e.target === backdrop || e.target.closest("[data-close]")) {
  571. backdrop.classList.remove("open");
  572. }
  573. });
  574. document.addEventListener("keydown", function (e) {
  575. if (e.key === "Escape") backdrop.classList.remove("open");
  576. });
  577. </script>
  578. </body>
  579. </html>