fresh-mint-list-toolbar-overflow-demo.html 40 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006
  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 · 列表工具栏溢出优化 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. --bg-page: #f8fafc;
  21. --shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
  22. --shadow-lg: 0 8px 24px rgba(15, 23, 42, 0.1);
  23. --radius: 10px;
  24. --danger: #dc2626;
  25. }
  26. * { box-sizing: border-box; }
  27. body {
  28. margin: 0;
  29. min-height: 100vh;
  30. font-family: Inter, -apple-system, BlinkMacSystemFont, sans-serif;
  31. font-size: 14px;
  32. line-height: 1.5;
  33. color: var(--text);
  34. background: var(--bg-page);
  35. }
  36. .demo-page { max-width: 1100px; margin: 0 auto; padding: 32px 24px 80px; }
  37. .demo-head { margin-bottom: 28px; }
  38. .demo-head h1 {
  39. margin: 0 0 8px;
  40. font-size: 24px;
  41. font-weight: 700;
  42. letter-spacing: -0.02em;
  43. }
  44. .demo-head h1 span { color: var(--primary); }
  45. .demo-head p { margin: 0; color: var(--text-muted); max-width: 720px; }
  46. .toc {
  47. display: flex;
  48. flex-wrap: wrap;
  49. gap: 8px;
  50. margin: 20px 0 0;
  51. }
  52. .toc a {
  53. display: inline-flex;
  54. align-items: center;
  55. height: 32px;
  56. padding: 0 12px;
  57. border-radius: 8px;
  58. border: 1px solid var(--border);
  59. background: var(--surface);
  60. color: var(--text);
  61. text-decoration: none;
  62. font-size: 13px;
  63. font-weight: 500;
  64. }
  65. .toc a:hover { border-color: var(--primary); color: var(--primary-dark); background: var(--primary-bg); }
  66. .toc a.rec {
  67. border-color: var(--primary);
  68. background: var(--primary-bg);
  69. color: var(--primary-dark);
  70. }
  71. .toc a.probe {
  72. border-color: #93c5fd;
  73. background: #eff6ff;
  74. color: #1d4ed8;
  75. }
  76. .section { margin-top: 36px; }
  77. .section-head {
  78. display: flex;
  79. align-items: flex-start;
  80. justify-content: space-between;
  81. gap: 16px;
  82. margin-bottom: 12px;
  83. }
  84. .section-head h2 {
  85. margin: 0 0 4px;
  86. font-size: 18px;
  87. font-weight: 700;
  88. }
  89. .section-head p { margin: 0; color: var(--text-muted); font-size: 13px; max-width: 640px; }
  90. .badge {
  91. flex-shrink: 0;
  92. display: inline-flex;
  93. align-items: center;
  94. height: 26px;
  95. padding: 0 10px;
  96. border-radius: 999px;
  97. font-size: 12px;
  98. font-weight: 600;
  99. }
  100. .badge-bad { background: #fef2f2; color: var(--danger); }
  101. .badge-ok { background: var(--primary-bg); color: var(--primary-dark); }
  102. .badge-alt { background: #fff7ed; color: #c2410c; }
  103. .badge-probe { background: #eff6ff; color: #1d4ed8; }
  104. /* —— 文字按钮 —— */
  105. .btn-text {
  106. height: 36px;
  107. padding: 0 8px;
  108. border: none;
  109. background: transparent;
  110. color: var(--text);
  111. font-weight: 600;
  112. box-shadow: none;
  113. }
  114. .btn-text:hover {
  115. background: var(--primary-bg);
  116. color: var(--primary-dark);
  117. border: none;
  118. }
  119. .btn-text.btn-main {
  120. background: transparent;
  121. border: none;
  122. color: var(--primary-dark);
  123. }
  124. .btn-text.btn-main:hover {
  125. background: var(--primary-bg);
  126. color: var(--primary-dark);
  127. }
  128. .btn-text.danger { color: var(--danger); }
  129. .btn-text.danger:hover { background: #fef2f2; color: var(--danger); }
  130. .btn-text-only { padding: 0 6px; gap: 0; }
  131. .measure-row {
  132. display: grid;
  133. grid-template-columns: 1fr 1fr;
  134. gap: 12px;
  135. margin-bottom: 12px;
  136. }
  137. @media (max-width: 860px) {
  138. .measure-row { grid-template-columns: 1fr; }
  139. }
  140. .measure-card {
  141. border: 1px solid var(--border);
  142. border-radius: 10px;
  143. background: var(--surface);
  144. overflow: hidden;
  145. }
  146. .measure-card-head {
  147. display: flex;
  148. align-items: center;
  149. justify-content: space-between;
  150. gap: 8px;
  151. padding: 10px 12px;
  152. background: var(--table-head);
  153. border-bottom: 1px solid var(--border);
  154. font-size: 12px;
  155. font-weight: 600;
  156. color: var(--text-muted);
  157. }
  158. .measure-card-body {
  159. padding: 10px 12px;
  160. overflow-x: auto;
  161. }
  162. .measure-actions {
  163. display: inline-flex;
  164. align-items: center;
  165. gap: 4px;
  166. white-space: nowrap;
  167. }
  168. .measure-actions.ghost-gap { gap: 8px; }
  169. .px-tag {
  170. font-variant-numeric: tabular-nums;
  171. color: var(--primary-dark);
  172. font-weight: 700;
  173. }
  174. .panel {
  175. background: var(--surface);
  176. border: 1px solid var(--border);
  177. border-radius: var(--radius);
  178. box-shadow: var(--shadow);
  179. overflow: hidden;
  180. }
  181. .panel-top {
  182. display: flex;
  183. align-items: center;
  184. gap: 8px;
  185. padding: 14px 16px;
  186. border-bottom: 1px solid var(--border);
  187. }
  188. .panel-top h3 {
  189. margin: 0;
  190. font-size: 15px;
  191. font-weight: 700;
  192. position: relative;
  193. padding-left: 10px;
  194. }
  195. .panel-top h3::before {
  196. content: "";
  197. position: absolute;
  198. left: 0;
  199. top: 2px;
  200. bottom: 2px;
  201. width: 3px;
  202. border-radius: 2px;
  203. background: var(--primary);
  204. }
  205. /* —— 工具栏基线 —— */
  206. .list-toolbar { background: var(--table-head); }
  207. .list-toolbar-row {
  208. display: flex;
  209. flex-wrap: wrap;
  210. align-items: center;
  211. justify-content: flex-start;
  212. gap: 8px;
  213. padding: 8px 16px;
  214. }
  215. .list-toolbar-selected {
  216. display: inline-flex;
  217. align-items: center;
  218. min-height: 36px;
  219. padding: 0 10px 0 0;
  220. margin-right: 4px;
  221. border-right: 1px solid var(--border);
  222. font-size: 12px;
  223. font-weight: 600;
  224. color: var(--text-muted);
  225. white-space: nowrap;
  226. }
  227. .sel-num { color: var(--primary-dark); font-weight: 700; margin: 0 2px; }
  228. .btn {
  229. display: inline-flex;
  230. align-items: center;
  231. justify-content: center;
  232. gap: 6px;
  233. height: 36px;
  234. padding: 0 14px;
  235. border-radius: 8px;
  236. border: 1px solid var(--border);
  237. background: var(--surface);
  238. color: var(--text);
  239. font-size: 14px;
  240. font-weight: 600;
  241. font-family: inherit;
  242. cursor: pointer;
  243. white-space: nowrap;
  244. transition: background .15s, border-color .15s, color .15s;
  245. }
  246. .btn:hover { border-color: #b7d5cb; background: #f8fafc; }
  247. .btn:disabled { opacity: .45; cursor: not-allowed; }
  248. .btn-main {
  249. background: var(--primary);
  250. border-color: var(--primary);
  251. color: #fff;
  252. }
  253. .btn-main:hover { background: var(--primary-dark); border-color: var(--primary-dark); color: #fff; }
  254. .btn-icon {
  255. width: 36px;
  256. min-width: 36px;
  257. padding: 0;
  258. border: none;
  259. background: transparent;
  260. color: var(--text-muted);
  261. }
  262. .btn-icon:hover { background: var(--primary-bg); color: var(--primary-dark); }
  263. .btn svg { width: 16px; height: 16px; flex-shrink: 0; }
  264. .chip-group {
  265. display: inline-flex;
  266. flex-wrap: wrap;
  267. gap: 6px;
  268. padding: 2px;
  269. }
  270. .chip {
  271. height: 28px;
  272. padding: 0 10px;
  273. border-radius: 6px;
  274. border: 1px solid var(--border);
  275. background: var(--surface);
  276. color: var(--text-muted);
  277. font-size: 12px;
  278. font-weight: 600;
  279. font-family: inherit;
  280. cursor: pointer;
  281. }
  282. .chip.is-active {
  283. background: var(--primary);
  284. border-color: var(--primary);
  285. color: #fff;
  286. }
  287. .quick-search {
  288. width: 220px;
  289. height: 36px;
  290. padding: 0 12px;
  291. border: 1px solid var(--border);
  292. border-radius: 8px;
  293. font-size: 13px;
  294. font-family: inherit;
  295. color: var(--text);
  296. background: var(--surface);
  297. }
  298. .quick-search::placeholder { color: #94a3b8; }
  299. .quick-search:focus {
  300. outline: none;
  301. border-color: var(--primary);
  302. box-shadow: 0 0 0 3px rgba(16, 185, 129, .15);
  303. }
  304. .list-toolbar-end {
  305. display: inline-flex;
  306. align-items: center;
  307. gap: 4px;
  308. margin-left: auto;
  309. }
  310. .list-body {
  311. border-top: 1px solid var(--border);
  312. padding: 0;
  313. }
  314. .fake-table {
  315. width: 100%;
  316. border-collapse: collapse;
  317. font-size: 13px;
  318. }
  319. .fake-table th,
  320. .fake-table td {
  321. padding: 10px 16px;
  322. text-align: left;
  323. border-bottom: 1px solid var(--border);
  324. }
  325. .fake-table th {
  326. background: var(--table-head);
  327. color: var(--text-muted);
  328. font-size: 12px;
  329. font-weight: 600;
  330. }
  331. .fake-table tbody tr:nth-child(even) td { background: #fafafa; }
  332. .note {
  333. margin-top: 10px;
  334. padding: 10px 12px;
  335. border-radius: 8px;
  336. background: #f1f5f9;
  337. color: var(--text-muted);
  338. font-size: 12px;
  339. }
  340. .note strong { color: var(--text); }
  341. /* —— 方案 A:更多下拉 —— */
  342. .toolbar-actions {
  343. display: inline-flex;
  344. flex-wrap: nowrap;
  345. align-items: center;
  346. gap: 8px;
  347. min-width: 0;
  348. }
  349. .more-wrap { position: relative; }
  350. .more-menu {
  351. display: none;
  352. position: absolute;
  353. top: calc(100% + 6px);
  354. right: 0;
  355. z-index: 20;
  356. min-width: 180px;
  357. padding: 6px;
  358. border: 1px solid var(--border);
  359. border-radius: 10px;
  360. background: var(--surface);
  361. box-shadow: var(--shadow-lg);
  362. }
  363. .more-wrap.is-open .more-menu { display: block; }
  364. .more-item {
  365. display: flex;
  366. align-items: center;
  367. gap: 8px;
  368. width: 100%;
  369. height: 36px;
  370. padding: 0 10px;
  371. border: none;
  372. border-radius: 8px;
  373. background: transparent;
  374. color: var(--text);
  375. font-size: 13px;
  376. font-weight: 500;
  377. font-family: inherit;
  378. cursor: pointer;
  379. text-align: left;
  380. }
  381. .more-item:hover { background: var(--primary-bg); color: var(--primary-dark); }
  382. .more-item:disabled { opacity: .45; cursor: not-allowed; }
  383. .more-item.danger { color: var(--danger); }
  384. .more-item.danger:hover { background: #fef2f2; color: var(--danger); }
  385. .more-divider {
  386. height: 1px;
  387. margin: 4px 6px;
  388. background: var(--border);
  389. }
  390. /* —— 方案 B:横向滚动 —— */
  391. .toolbar-scroll {
  392. display: flex;
  393. flex-wrap: nowrap;
  394. align-items: center;
  395. gap: 8px;
  396. min-width: 0;
  397. flex: 1;
  398. overflow-x: auto;
  399. -webkit-overflow-scrolling: touch;
  400. padding-bottom: 2px;
  401. scrollbar-width: thin;
  402. }
  403. .toolbar-scroll::-webkit-scrollbar { height: 4px; }
  404. .toolbar-scroll::-webkit-scrollbar-thumb {
  405. background: #cbd5e1;
  406. border-radius: 4px;
  407. }
  408. .list-toolbar-row.nowrap-row {
  409. flex-wrap: nowrap;
  410. }
  411. /* —— 方案 C:分区 —— */
  412. .toolbar-primary {
  413. display: inline-flex;
  414. align-items: center;
  415. gap: 8px;
  416. flex-wrap: nowrap;
  417. }
  418. .toolbar-meta {
  419. display: flex;
  420. flex-wrap: wrap;
  421. align-items: center;
  422. gap: 8px;
  423. padding: 6px 16px 10px;
  424. border-top: 1px dashed #e2e8f0;
  425. background: #fcfdfd;
  426. }
  427. .meta-label {
  428. font-size: 11px;
  429. font-weight: 600;
  430. color: var(--text-muted);
  431. letter-spacing: .02em;
  432. }
  433. .compare {
  434. margin-top: 28px;
  435. overflow: auto;
  436. border: 1px solid var(--border);
  437. border-radius: var(--radius);
  438. background: var(--surface);
  439. }
  440. .compare table { width: 100%; border-collapse: collapse; font-size: 13px; }
  441. .compare th, .compare td {
  442. padding: 12px 14px;
  443. text-align: left;
  444. border-bottom: 1px solid var(--border);
  445. vertical-align: top;
  446. }
  447. .compare th {
  448. background: var(--table-head);
  449. font-size: 12px;
  450. color: var(--text-muted);
  451. font-weight: 600;
  452. }
  453. .compare tr:last-child td { border-bottom: none; }
  454. .rec-mark { color: var(--primary-dark); font-weight: 700; }
  455. .width-hint {
  456. display: flex;
  457. align-items: center;
  458. gap: 10px;
  459. margin-bottom: 10px;
  460. font-size: 12px;
  461. color: var(--text-muted);
  462. }
  463. .width-hint input[type="range"] { width: 180px; accent-color: var(--primary); }
  464. .viewport-frame {
  465. border: 1px dashed #cbd5e1;
  466. border-radius: 8px;
  467. overflow: hidden;
  468. resize: horizontal;
  469. max-width: 100%;
  470. min-width: 480px;
  471. width: 100%;
  472. }
  473. </style>
  474. </head>
  475. <body>
  476. <div class="demo-page">
  477. <header class="demo-head">
  478. <h1>列表工具栏溢出优化 · <span>Fresh Mint</span></h1>
  479. <p>
  480. 当「已选计数 + 日期快捷 + 简易检索 + 业务按钮 + 刷新/列设置」挤在同一行时,
  481. <code>flex-wrap</code> 会折成两行。当前选定方向:
  482. <strong>列表工具栏带文案的操作按钮去掉前置图标</strong>(保留描边/主色实心形态),用更小的水平占位换单行概率。
  483. 右侧刷新/列设置仍为纯图标按钮,不受影响。
  484. </p>
  485. <nav class="toc">
  486. <a href="#before">现状(有图标)</a>
  487. <a class="rec" href="#d">选定 · 去掉前置图标</a>
  488. <a href="#a">备选 · 更多下拉</a>
  489. <a href="#b">备选 · 横向滚动</a>
  490. <a href="#c">备选 · 元信息下沉</a>
  491. <a href="#compare">对比表</a>
  492. </nav>
  493. </header>
  494. <!-- ========== 现状 ========== -->
  495. <section class="section" id="before">
  496. <div class="section-head">
  497. <div>
  498. <h2>0. 现状:全部平铺 → 折行</h2>
  499. <p>模拟合同列表工具栏:日期快捷 + 检索 + 8 个操作按钮,窄屏或中等宽度必折两行。</p>
  500. </div>
  501. <span class="badge badge-bad">需优化</span>
  502. </div>
  503. <div class="width-hint">
  504. 拖拽右侧边缘缩小容器宽度,观察折行:
  505. </div>
  506. <div class="viewport-frame" style="width: 920px;">
  507. <div class="panel">
  508. <div class="panel-top"><h3>合同列表</h3></div>
  509. <div class="list-toolbar">
  510. <div class="list-toolbar-row">
  511. <div class="list-toolbar-selected">已选 <span class="sel-num">2</span> 项</div>
  512. <div class="chip-group">
  513. <button type="button" class="chip is-active">全部</button>
  514. <button type="button" class="chip">昨日</button>
  515. <button type="button" class="chip">今日</button>
  516. <button type="button" class="chip">本周</button>
  517. </div>
  518. <input class="quick-search" placeholder="合同编号/名称/客户" />
  519. <button type="button" class="btn btn-main"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 5v14M5 12h14"/></svg>新增</button>
  520. <button type="button" class="btn"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 20h9M16.5 3.5a2.1 2.1 0 0 1 3 3L7 19l-4 1 1-4Z"/></svg>编辑</button>
  521. <button type="button" class="btn"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 6h18M8 6V4h8v2M19 6l-1 14H6L5 6"/></svg>删除</button>
  522. <button type="button" class="btn"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 2H6a2 2 0 0 0-2 2v16l4-2 4 2 4-2 4 2V8z"/></svg>提交审批</button>
  523. <button type="button" class="btn"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M20 6 9 17l-5-5"/></svg>审批通过</button>
  524. <button type="button" class="btn"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M18 6 6 18M6 6l12 12"/></svg>审批驳回</button>
  525. <button type="button" class="btn"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4M17 8l-5-5-5 5M12 3v12"/></svg>上传扫描件</button>
  526. <button type="button" class="btn"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4M7 10l5 5 5-5M12 15V3"/></svg>导出</button>
  527. <div class="list-toolbar-end">
  528. <button type="button" class="btn btn-icon" title="刷新" aria-label="刷新">
  529. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 12a9 9 0 1 1-2.6-6.3"/><path d="M21 3v6h-6"/></svg>
  530. </button>
  531. <button type="button" class="btn btn-icon" title="列设置" aria-label="列设置">
  532. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="3"/><path d="M12 2v2M12 20v2M4.9 4.9l1.4 1.4M17.7 17.7l1.4 1.4M2 12h2M20 12h2M4.9 19.1l1.4-1.4M17.7 6.3l1.4-1.4"/></svg>
  533. </button>
  534. </div>
  535. </div>
  536. </div>
  537. <div class="list-body">
  538. <table class="fake-table">
  539. <thead><tr><th>合同编号</th><th>合同名称</th><th>状态</th></tr></thead>
  540. <tbody>
  541. <tr><td>HT-2026-001</td><td>某某化工原料采购合同</td><td>草稿</td></tr>
  542. <tr><td>HT-2026-002</td><td>年度框架协议</td><td>审批中</td></tr>
  543. </tbody>
  544. </table>
  545. </div>
  546. </div>
  547. </div>
  548. <p class="note"><strong>问题:</strong>折行后占用纵向空间,已选与主操作扫描节奏被打断;窄屏时第二行按钮不易被发现。</p>
  549. </section>
  550. <!-- ========== 选定:去掉前置图标 ========== -->
  551. <section class="section" id="d">
  552. <div class="section-head">
  553. <div>
  554. <h2>选定方案 · 工具栏文案按钮去掉前置图标</h2>
  555. <p>保留现有描边 / 主色实心按钮形态,只去掉文案前的图标与图标间距,降低每个按钮约 16px + gap 的占位。</p>
  556. </div>
  557. <span class="badge badge-ok">已选定</span>
  558. </div>
  559. <div class="measure-row">
  560. <div class="measure-card">
  561. <div class="measure-card-head">
  562. <span>有前置图标(现状)</span>
  563. <span>动作区宽约 <span class="px-tag" id="wGhost">—</span> px</span>
  564. </div>
  565. <div class="measure-card-body">
  566. <div class="measure-actions ghost-gap" id="measureGhost">
  567. <button type="button" class="btn btn-main"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 5v14M5 12h14"/></svg>新增</button>
  568. <button type="button" class="btn"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 20h9M16.5 3.5a2.1 2.1 0 0 1 3 3L7 19l-4 1 1-4Z"/></svg>编辑</button>
  569. <button type="button" class="btn"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 6h18M8 6V4h8v2M19 6l-1 14H6L5 6"/></svg>删除</button>
  570. <button type="button" class="btn"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 2H6a2 2 0 0 0-2 2v16l4-2 4 2 4-2 4 2V8z"/></svg>提交审批</button>
  571. <button type="button" class="btn"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M20 6 9 17l-5-5"/></svg>审批通过</button>
  572. <button type="button" class="btn"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M18 6 6 18M6 6l12 12"/></svg>审批驳回</button>
  573. <button type="button" class="btn"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4M17 8l-5-5-5 5M12 3v12"/></svg>上传扫描件</button>
  574. <button type="button" class="btn"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4M7 10l5 5 5-5M12 15V3"/></svg>导出</button>
  575. </div>
  576. </div>
  577. </div>
  578. <div class="measure-card">
  579. <div class="measure-card-head">
  580. <span>无前置图标(选定)</span>
  581. <span>动作区宽约 <span class="px-tag" id="wTextOnly">—</span> px</span>
  582. </div>
  583. <div class="measure-card-body">
  584. <div class="measure-actions ghost-gap" id="measureTextOnly">
  585. <button type="button" class="btn btn-main">新增</button>
  586. <button type="button" class="btn">编辑</button>
  587. <button type="button" class="btn">删除</button>
  588. <button type="button" class="btn">提交审批</button>
  589. <button type="button" class="btn">审批通过</button>
  590. <button type="button" class="btn">审批驳回</button>
  591. <button type="button" class="btn">上传扫描件</button>
  592. <button type="button" class="btn">导出</button>
  593. </div>
  594. </div>
  595. </div>
  596. <div class="measure-card">
  597. <div class="measure-card-head">
  598. <span>对照:文字按钮仍带图标</span>
  599. <span>动作区宽约 <span class="px-tag" id="wText">—</span> px</span>
  600. </div>
  601. <div class="measure-card-body">
  602. <div class="measure-actions" id="measureText">
  603. <button type="button" class="btn btn-text btn-main"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 5v14M5 12h14"/></svg>新增</button>
  604. <button type="button" class="btn btn-text"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 20h9M16.5 3.5a2.1 2.1 0 0 1 3 3L7 19l-4 1 1-4Z"/></svg>编辑</button>
  605. <button type="button" class="btn btn-text">删除</button>
  606. <button type="button" class="btn btn-text">提交审批</button>
  607. <button type="button" class="btn btn-text">审批通过</button>
  608. <button type="button" class="btn btn-text">审批驳回</button>
  609. <button type="button" class="btn btn-text">上传扫描件</button>
  610. <button type="button" class="btn btn-text">导出</button>
  611. </div>
  612. </div>
  613. </div>
  614. <div class="measure-card">
  615. <div class="measure-card-head">
  616. <span>节省幅度(相对「有图标描边」)</span>
  617. <span id="saveSummary" class="px-tag">—</span>
  618. </div>
  619. <div class="measure-card-body" style="color: var(--text-muted); font-size: 13px; line-height: 1.7;">
  620. <div>去掉前置图标:约省 <strong id="saveTextOnly">—</strong></div>
  621. <div>仅改文字按钮但仍留图标:约省 <strong id="saveText">—</strong></div>
  622. <div style="margin-top: 6px;">结论:在保留按钮形态的前提下,<strong>去图标比改文字链更划算、也更贴合现有视觉</strong>。</div>
  623. </div>
  624. </div>
  625. </div>
  626. <div class="width-hint">整行模拟(去图标后),拖窄观察折行是否缓解:</div>
  627. <div class="viewport-frame" style="width: 920px;">
  628. <div class="panel">
  629. <div class="panel-top"><h3>合同列表 · 无前置图标</h3></div>
  630. <div class="list-toolbar">
  631. <div class="list-toolbar-row">
  632. <div class="list-toolbar-selected">已选 <span class="sel-num">2</span> 项</div>
  633. <div class="chip-group">
  634. <button type="button" class="chip is-active">全部</button>
  635. <button type="button" class="chip">昨日</button>
  636. <button type="button" class="chip">今日</button>
  637. <button type="button" class="chip">本周</button>
  638. </div>
  639. <input class="quick-search" placeholder="合同编号/名称/客户" />
  640. <button type="button" class="btn btn-main">新增</button>
  641. <button type="button" class="btn">编辑</button>
  642. <button type="button" class="btn">删除</button>
  643. <button type="button" class="btn">提交审批</button>
  644. <button type="button" class="btn">审批通过</button>
  645. <button type="button" class="btn">审批驳回</button>
  646. <button type="button" class="btn">上传扫描件</button>
  647. <button type="button" class="btn">导出</button>
  648. <div class="list-toolbar-end">
  649. <button type="button" class="btn btn-icon" title="刷新" aria-label="刷新">
  650. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 12a9 9 0 1 1-2.6-6.3"/><path d="M21 3v6h-6"/></svg>
  651. </button>
  652. <button type="button" class="btn btn-icon" title="列设置" aria-label="列设置">
  653. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="3"/><path d="M12 2v2M12 20v2M4.9 4.9l1.4 1.4M17.7 17.7l1.4 1.4M2 12h2M20 12h2M4.9 19.1l1.4-1.4M17.7 6.3l1.4-1.4"/></svg>
  654. </button>
  655. </div>
  656. </div>
  657. </div>
  658. <div class="list-body">
  659. <table class="fake-table">
  660. <thead><tr><th>合同编号</th><th>合同名称</th><th>状态</th></tr></thead>
  661. <tbody>
  662. <tr><td>HT-2026-001</td><td>某某化工原料采购合同</td><td>草稿</td></tr>
  663. </tbody>
  664. </table>
  665. </div>
  666. </div>
  667. </div>
  668. <p class="note">
  669. <strong>落地范围建议:</strong>仅列表工具栏 <code>.list-toolbar</code> 内「有文案的 el-button」去掉前置图标;
  670. 弹窗底栏、表格操作列、筛选区搜索/重置等可暂不改。
  671. 注意:现有 OpenSpec「按钮须携带图标」需同步放宽为「工具栏文案按钮可不带图标」。
  672. </p>
  673. </section>
  674. <!-- ========== 方案 A ========== -->
  675. <section class="section" id="a">
  676. <div class="section-head">
  677. <div>
  678. <h2>方案 A · 主操作外露 +「更多」下拉</h2>
  679. <p>高频主路径始终可见;低频/场景化动作收入「更多」。保持单行、左起扫描、仅首按钮主色。</p>
  680. </div>
  681. <span class="badge badge-alt">备选</span>
  682. </div>
  683. <div class="viewport-frame" style="width: 920px;">
  684. <div class="panel">
  685. <div class="panel-top"><h3>合同列表</h3></div>
  686. <div class="list-toolbar">
  687. <div class="list-toolbar-row nowrap-row">
  688. <div class="list-toolbar-selected">已选 <span class="sel-num">2</span> 项</div>
  689. <div class="toolbar-actions">
  690. <button type="button" class="btn btn-main">新增</button>
  691. <button type="button" class="btn">编辑</button>
  692. <button type="button" class="btn">删除</button>
  693. <button type="button" class="btn">提交审批</button>
  694. <div class="more-wrap" id="moreA">
  695. <button type="button" class="btn" id="moreABtn" aria-expanded="false" aria-haspopup="true">
  696. 更多
  697. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M6 9l6 6 6-6"/></svg>
  698. </button>
  699. <div class="more-menu" role="menu">
  700. <button type="button" class="more-item" role="menuitem">上传扫描件</button>
  701. <button type="button" class="more-item" role="menuitem">导出</button>
  702. <div class="more-divider"></div>
  703. <button type="button" class="more-item" role="menuitem">审批通过</button>
  704. <button type="button" class="more-item" role="menuitem">审批驳回</button>
  705. <div class="more-divider"></div>
  706. <button type="button" class="more-item" role="menuitem">下载导入模板</button>
  707. <button type="button" class="more-item" role="menuitem">批量导入</button>
  708. </div>
  709. </div>
  710. </div>
  711. <div class="list-toolbar-end">
  712. <button type="button" class="btn btn-icon" title="刷新" aria-label="刷新">
  713. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 12a9 9 0 1 1-2.6-6.3"/><path d="M21 3v6h-6"/></svg>
  714. </button>
  715. <button type="button" class="btn btn-icon" title="列设置" aria-label="列设置">
  716. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="3"/><path d="M12 2v2M12 20v2M4.9 4.9l1.4 1.4M17.7 17.7l1.4 1.4M2 12h2M20 12h2M4.9 19.1l1.4-1.4M17.7 6.3l1.4-1.4"/></svg>
  717. </button>
  718. </div>
  719. </div>
  720. </div>
  721. <div class="list-body">
  722. <table class="fake-table">
  723. <thead><tr><th>合同编号</th><th>合同名称</th><th>状态</th></tr></thead>
  724. <tbody>
  725. <tr><td>HT-2026-001</td><td>某某化工原料采购合同</td><td>草稿</td></tr>
  726. <tr><td>HT-2026-002</td><td>年度框架协议</td><td>审批中</td></tr>
  727. </tbody>
  728. </table>
  729. </div>
  730. </div>
  731. </div>
  732. <p class="note">
  733. <strong>收纳原则:</strong>
  734. ① 主创建/主编辑类外露;② 依赖选中行的低频动作(上传、导出、导入)进「更多」;
  735. ③ 场景切换型动作(审批通过/驳回)可按当前 Tab 动态外露或进「更多」;
  736. ④ 日期快捷与简易检索建议迁出工具栏(见方案 C),避免与操作按钮抢宽。
  737. 落地可用 Element Plus <code>el-dropdown</code>。
  738. </p>
  739. </section>
  740. <!-- ========== 方案 B ========== -->
  741. <section class="section" id="b">
  742. <div class="section-head">
  743. <div>
  744. <h2>方案 B · 单行不换行 + 横向滚动</h2>
  745. <p>工具栏 <code>flex-wrap: nowrap</code>,中间操作区 <code>overflow-x: auto</code>。实现成本低,但可发现性差。</p>
  746. </div>
  747. <span class="badge badge-alt">备选</span>
  748. </div>
  749. <div class="viewport-frame" style="width: 640px;">
  750. <div class="panel">
  751. <div class="panel-top"><h3>合同列表</h3></div>
  752. <div class="list-toolbar">
  753. <div class="list-toolbar-row nowrap-row">
  754. <div class="list-toolbar-selected">已选 <span class="sel-num">2</span> 项</div>
  755. <div class="toolbar-scroll">
  756. <button type="button" class="btn btn-main">新增</button>
  757. <button type="button" class="btn">编辑</button>
  758. <button type="button" class="btn">删除</button>
  759. <button type="button" class="btn">提交审批</button>
  760. <button type="button" class="btn">审批通过</button>
  761. <button type="button" class="btn">审批驳回</button>
  762. <button type="button" class="btn">上传扫描件</button>
  763. <button type="button" class="btn">导出</button>
  764. </div>
  765. <div class="list-toolbar-end">
  766. <button type="button" class="btn btn-icon" title="刷新" aria-label="刷新">
  767. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 12a9 9 0 1 1-2.6-6.3"/><path d="M21 3v6h-6"/></svg>
  768. </button>
  769. <button type="button" class="btn btn-icon" title="列设置" aria-label="列设置">
  770. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="3"/><path d="M12 2v2M12 20v2M4.9 4.9l1.4 1.4M17.7 17.7l1.4 1.4M2 12h2M20 12h2M4.9 19.1l1.4-1.4M17.7 6.3l1.4-1.4"/></svg>
  771. </button>
  772. </div>
  773. </div>
  774. </div>
  775. <div class="list-body">
  776. <table class="fake-table">
  777. <thead><tr><th>合同编号</th><th>合同名称</th><th>状态</th></tr></thead>
  778. <tbody>
  779. <tr><td>HT-2026-001</td><td>某某化工原料采购合同</td><td>草稿</td></tr>
  780. </tbody>
  781. </table>
  782. </div>
  783. </div>
  784. </div>
  785. <p class="note"><strong>适用:</strong>临时兜底或按钮数量偶发超标的页。不推荐作为全站默认——右侧按钮容易被「藏」在滚动条外。</p>
  786. </section>
  787. <!-- ========== 方案 C ========== -->
  788. <section class="section" id="c">
  789. <div class="section-head">
  790. <div>
  791. <h2>方案 C · 元信息下沉 + 主操作区精简</h2>
  792. <p>把「日期快捷 / 简易检索」从操作行拆出(独立轻量条或并回筛选区),操作行只保留 CRUD + 更多。</p>
  793. </div>
  794. <span class="badge badge-ok">可与 A 组合</span>
  795. </div>
  796. <div class="viewport-frame" style="width: 920px;">
  797. <div class="panel">
  798. <div class="panel-top"><h3>合同列表</h3></div>
  799. <div class="list-toolbar">
  800. <div class="list-toolbar-row nowrap-row">
  801. <div class="list-toolbar-selected">已选 <span class="sel-num">2</span> 项</div>
  802. <div class="toolbar-primary">
  803. <button type="button" class="btn btn-main">新增</button>
  804. <button type="button" class="btn">编辑</button>
  805. <button type="button" class="btn">删除</button>
  806. <button type="button" class="btn">提交审批</button>
  807. <div class="more-wrap" id="moreC">
  808. <button type="button" class="btn" id="moreCBtn">
  809. 更多
  810. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M6 9l6 6 6-6"/></svg>
  811. </button>
  812. <div class="more-menu" role="menu">
  813. <button type="button" class="more-item">上传扫描件</button>
  814. <button type="button" class="more-item">导出</button>
  815. <div class="more-divider"></div>
  816. <button type="button" class="more-item">审批通过</button>
  817. <button type="button" class="more-item">审批驳回</button>
  818. </div>
  819. </div>
  820. </div>
  821. <div class="list-toolbar-end">
  822. <button type="button" class="btn btn-icon" title="刷新" aria-label="刷新">
  823. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 12a9 9 0 1 1-2.6-6.3"/><path d="M21 3v6h-6"/></svg>
  824. </button>
  825. <button type="button" class="btn btn-icon" title="列设置" aria-label="列设置">
  826. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="3"/><path d="M12 2v2M12 20v2M4.9 4.9l1.4 1.4M17.7 17.7l1.4 1.4M2 12h2M20 12h2M4.9 19.1l1.4-1.4M17.7 6.3l1.4-1.4"/></svg>
  827. </button>
  828. </div>
  829. </div>
  830. <div class="toolbar-meta">
  831. <span class="meta-label">快捷筛选</span>
  832. <div class="chip-group">
  833. <button type="button" class="chip is-active">全部</button>
  834. <button type="button" class="chip">昨日</button>
  835. <button type="button" class="chip">今日</button>
  836. <button type="button" class="chip">本周</button>
  837. </div>
  838. <input class="quick-search" placeholder="合同编号/名称/客户" style="margin-left: auto;" />
  839. </div>
  840. </div>
  841. <div class="list-body">
  842. <table class="fake-table">
  843. <thead><tr><th>合同编号</th><th>合同名称</th><th>状态</th></tr></thead>
  844. <tbody>
  845. <tr><td>HT-2026-001</td><td>某某化工原料采购合同</td><td>草稿</td></tr>
  846. <tr><td>HT-2026-002</td><td>年度框架协议</td><td>审批中</td></tr>
  847. </tbody>
  848. </table>
  849. </div>
  850. </div>
  851. </div>
  852. <p class="note">
  853. <strong>说明:</strong>这里用虚线次级条演示「元信息下沉」。产品上更干净的做法是:日期快捷/检索回到上方
  854. <code>filter-block</code>,工具栏只做「已选 → 动作 → 刷新/列设置」。
  855. </p>
  856. </section>
  857. <!-- ========== 对比 ========== -->
  858. <section class="section" id="compare">
  859. <div class="section-head">
  860. <div>
  861. <h2>对比与落地建议</h2>
  862. <p>全站默认建议 A;复杂业务页(合同/价格函)用 A + 把日期/检索移出操作行。</p>
  863. </div>
  864. </div>
  865. <div class="compare">
  866. <table>
  867. <thead>
  868. <tr>
  869. <th>方案</th>
  870. <th>单行稳定</th>
  871. <th>可发现性</th>
  872. <th>与规范兼容</th>
  873. <th>实现成本</th>
  874. <th>建议</th>
  875. </tr>
  876. </thead>
  877. <tbody>
  878. <tr>
  879. <td>去掉前置图标</td>
  880. <td>中高</td>
  881. <td>高</td>
  882. <td>需放宽「按钮必带图标」</td>
  883. <td>低</td>
  884. <td class="rec-mark">已选定</td>
  885. </tr>
  886. <tr>
  887. <td>A 更多下拉</td>
  888. <td>高</td>
  889. <td>中高</td>
  890. <td>高</td>
  891. <td>中</td>
  892. <td>备选</td>
  893. </tr>
  894. <tr>
  895. <td>B 横向滚动</td>
  896. <td>高</td>
  897. <td>低</td>
  898. <td>中</td>
  899. <td>低</td>
  900. <td>备选</td>
  901. </tr>
  902. <tr>
  903. <td>C 元信息下沉</td>
  904. <td>高(组合后)</td>
  905. <td>高</td>
  906. <td>高</td>
  907. <td>低~中</td>
  908. <td>备选</td>
  909. </tr>
  910. <tr>
  911. <td>全部平铺 + 有图标 + wrap</td>
  912. <td>低</td>
  913. <td>高但占高</td>
  914. <td>符合旧规范</td>
  915. <td>—</td>
  916. <td>弃用</td>
  917. </tr>
  918. </tbody>
  919. </table>
  920. </div>
  921. <p class="note" style="margin-top: 16px;">
  922. <strong>落地约定(草案):</strong>
  923. <code>.list-toolbar</code> 内带可见文案的操作按钮不再放前置 <code>el-icon</code>;
  924. 「刷新 / 列设置」等无文案图标钮保留;弹窗 footer、筛选区、表格操作列暂保持有图标。
  925. </p>
  926. </section>
  927. </div>
  928. <script>
  929. function bindMore(wrapId, btnId) {
  930. const wrap = document.getElementById(wrapId)
  931. const btn = document.getElementById(btnId)
  932. if (!wrap || !btn) return
  933. btn.addEventListener('click', (e) => {
  934. e.stopPropagation()
  935. const open = wrap.classList.toggle('is-open')
  936. btn.setAttribute('aria-expanded', open ? 'true' : 'false')
  937. document.querySelectorAll('.more-wrap.is-open').forEach((el) => {
  938. if (el !== wrap) {
  939. el.classList.remove('is-open')
  940. const b = el.querySelector('button')
  941. if (b) b.setAttribute('aria-expanded', 'false')
  942. }
  943. })
  944. })
  945. }
  946. bindMore('moreA', 'moreABtn')
  947. bindMore('moreC', 'moreCBtn')
  948. document.addEventListener('click', () => {
  949. document.querySelectorAll('.more-wrap.is-open').forEach((el) => {
  950. el.classList.remove('is-open')
  951. const b = el.querySelector('button')
  952. if (b) b.setAttribute('aria-expanded', 'false')
  953. })
  954. })
  955. function measureToolbarWidths() {
  956. const ghost = document.getElementById('measureGhost')
  957. const text = document.getElementById('measureText')
  958. const textOnly = document.getElementById('measureTextOnly')
  959. if (!ghost || !text || !textOnly) return
  960. const wG = Math.round(ghost.scrollWidth)
  961. const wT = Math.round(text.scrollWidth)
  962. const wO = Math.round(textOnly.scrollWidth)
  963. const set = (id, v) => { const el = document.getElementById(id); if (el) el.textContent = String(v) }
  964. set('wGhost', wG)
  965. set('wText', wT)
  966. set('wTextOnly', wO)
  967. const pct = (from, to) => ((from - to) / from * 100).toFixed(0) + '%(约 ' + (from - to) + 'px)'
  968. set('saveText', pct(wG, wT))
  969. set('saveTextOnly', pct(wG, wO))
  970. set('saveSummary', '最多约省 ' + pct(wG, wO))
  971. }
  972. measureToolbarWidths()
  973. window.addEventListener('resize', measureToolbarWidths)
  974. </script>
  975. </body>
  976. </html>