Bundle pending backend/web-admin feature updates, data-migration scripts, and related docs so the V1.5 branch on the Tencent Cloud Gogs matches the current workspace (excluding local secrets). Co-authored-by: Cursor <cursoragent@cursor.com>
1007 lines
40 KiB
HTML
1007 lines
40 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;
|
||
--bg-page: #f8fafc;
|
||
--shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
|
||
--shadow-lg: 0 8px 24px rgba(15, 23, 42, 0.1);
|
||
--radius: 10px;
|
||
--danger: #dc2626;
|
||
}
|
||
|
||
* { box-sizing: border-box; }
|
||
body {
|
||
margin: 0;
|
||
min-height: 100vh;
|
||
font-family: Inter, -apple-system, BlinkMacSystemFont, sans-serif;
|
||
font-size: 14px;
|
||
line-height: 1.5;
|
||
color: var(--text);
|
||
background: var(--bg-page);
|
||
}
|
||
|
||
.demo-page { max-width: 1100px; margin: 0 auto; padding: 32px 24px 80px; }
|
||
.demo-head { margin-bottom: 28px; }
|
||
.demo-head h1 {
|
||
margin: 0 0 8px;
|
||
font-size: 24px;
|
||
font-weight: 700;
|
||
letter-spacing: -0.02em;
|
||
}
|
||
.demo-head h1 span { color: var(--primary); }
|
||
.demo-head p { margin: 0; color: var(--text-muted); max-width: 720px; }
|
||
|
||
.toc {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 8px;
|
||
margin: 20px 0 0;
|
||
}
|
||
.toc a {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
height: 32px;
|
||
padding: 0 12px;
|
||
border-radius: 8px;
|
||
border: 1px solid var(--border);
|
||
background: var(--surface);
|
||
color: var(--text);
|
||
text-decoration: none;
|
||
font-size: 13px;
|
||
font-weight: 500;
|
||
}
|
||
.toc a:hover { border-color: var(--primary); color: var(--primary-dark); background: var(--primary-bg); }
|
||
.toc a.rec {
|
||
border-color: var(--primary);
|
||
background: var(--primary-bg);
|
||
color: var(--primary-dark);
|
||
}
|
||
.toc a.probe {
|
||
border-color: #93c5fd;
|
||
background: #eff6ff;
|
||
color: #1d4ed8;
|
||
}
|
||
|
||
.section { margin-top: 36px; }
|
||
.section-head {
|
||
display: flex;
|
||
align-items: flex-start;
|
||
justify-content: space-between;
|
||
gap: 16px;
|
||
margin-bottom: 12px;
|
||
}
|
||
.section-head h2 {
|
||
margin: 0 0 4px;
|
||
font-size: 18px;
|
||
font-weight: 700;
|
||
}
|
||
.section-head p { margin: 0; color: var(--text-muted); font-size: 13px; max-width: 640px; }
|
||
.badge {
|
||
flex-shrink: 0;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
height: 26px;
|
||
padding: 0 10px;
|
||
border-radius: 999px;
|
||
font-size: 12px;
|
||
font-weight: 600;
|
||
}
|
||
.badge-bad { background: #fef2f2; color: var(--danger); }
|
||
.badge-ok { background: var(--primary-bg); color: var(--primary-dark); }
|
||
.badge-alt { background: #fff7ed; color: #c2410c; }
|
||
.badge-probe { background: #eff6ff; color: #1d4ed8; }
|
||
|
||
/* —— 文字按钮 —— */
|
||
.btn-text {
|
||
height: 36px;
|
||
padding: 0 8px;
|
||
border: none;
|
||
background: transparent;
|
||
color: var(--text);
|
||
font-weight: 600;
|
||
box-shadow: none;
|
||
}
|
||
.btn-text:hover {
|
||
background: var(--primary-bg);
|
||
color: var(--primary-dark);
|
||
border: none;
|
||
}
|
||
.btn-text.btn-main {
|
||
background: transparent;
|
||
border: none;
|
||
color: var(--primary-dark);
|
||
}
|
||
.btn-text.btn-main:hover {
|
||
background: var(--primary-bg);
|
||
color: var(--primary-dark);
|
||
}
|
||
.btn-text.danger { color: var(--danger); }
|
||
.btn-text.danger:hover { background: #fef2f2; color: var(--danger); }
|
||
.btn-text-only { padding: 0 6px; gap: 0; }
|
||
|
||
.measure-row {
|
||
display: grid;
|
||
grid-template-columns: 1fr 1fr;
|
||
gap: 12px;
|
||
margin-bottom: 12px;
|
||
}
|
||
@media (max-width: 860px) {
|
||
.measure-row { grid-template-columns: 1fr; }
|
||
}
|
||
.measure-card {
|
||
border: 1px solid var(--border);
|
||
border-radius: 10px;
|
||
background: var(--surface);
|
||
overflow: hidden;
|
||
}
|
||
.measure-card-head {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
gap: 8px;
|
||
padding: 10px 12px;
|
||
background: var(--table-head);
|
||
border-bottom: 1px solid var(--border);
|
||
font-size: 12px;
|
||
font-weight: 600;
|
||
color: var(--text-muted);
|
||
}
|
||
.measure-card-body {
|
||
padding: 10px 12px;
|
||
overflow-x: auto;
|
||
}
|
||
.measure-actions {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 4px;
|
||
white-space: nowrap;
|
||
}
|
||
.measure-actions.ghost-gap { gap: 8px; }
|
||
.px-tag {
|
||
font-variant-numeric: tabular-nums;
|
||
color: var(--primary-dark);
|
||
font-weight: 700;
|
||
}
|
||
|
||
.panel {
|
||
background: var(--surface);
|
||
border: 1px solid var(--border);
|
||
border-radius: var(--radius);
|
||
box-shadow: var(--shadow);
|
||
overflow: hidden;
|
||
}
|
||
.panel-top {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
padding: 14px 16px;
|
||
border-bottom: 1px solid var(--border);
|
||
}
|
||
.panel-top h3 {
|
||
margin: 0;
|
||
font-size: 15px;
|
||
font-weight: 700;
|
||
position: relative;
|
||
padding-left: 10px;
|
||
}
|
||
.panel-top h3::before {
|
||
content: "";
|
||
position: absolute;
|
||
left: 0;
|
||
top: 2px;
|
||
bottom: 2px;
|
||
width: 3px;
|
||
border-radius: 2px;
|
||
background: var(--primary);
|
||
}
|
||
|
||
/* —— 工具栏基线 —— */
|
||
.list-toolbar { background: var(--table-head); }
|
||
.list-toolbar-row {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
align-items: center;
|
||
justify-content: flex-start;
|
||
gap: 8px;
|
||
padding: 8px 16px;
|
||
}
|
||
.list-toolbar-selected {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
min-height: 36px;
|
||
padding: 0 10px 0 0;
|
||
margin-right: 4px;
|
||
border-right: 1px solid var(--border);
|
||
font-size: 12px;
|
||
font-weight: 600;
|
||
color: var(--text-muted);
|
||
white-space: nowrap;
|
||
}
|
||
.sel-num { color: var(--primary-dark); font-weight: 700; margin: 0 2px; }
|
||
|
||
.btn {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: 6px;
|
||
height: 36px;
|
||
padding: 0 14px;
|
||
border-radius: 8px;
|
||
border: 1px solid var(--border);
|
||
background: var(--surface);
|
||
color: var(--text);
|
||
font-size: 14px;
|
||
font-weight: 600;
|
||
font-family: inherit;
|
||
cursor: pointer;
|
||
white-space: nowrap;
|
||
transition: background .15s, border-color .15s, color .15s;
|
||
}
|
||
.btn:hover { border-color: #b7d5cb; background: #f8fafc; }
|
||
.btn:disabled { opacity: .45; cursor: not-allowed; }
|
||
.btn-main {
|
||
background: var(--primary);
|
||
border-color: var(--primary);
|
||
color: #fff;
|
||
}
|
||
.btn-main:hover { background: var(--primary-dark); border-color: var(--primary-dark); color: #fff; }
|
||
.btn-icon {
|
||
width: 36px;
|
||
min-width: 36px;
|
||
padding: 0;
|
||
border: none;
|
||
background: transparent;
|
||
color: var(--text-muted);
|
||
}
|
||
.btn-icon:hover { background: var(--primary-bg); color: var(--primary-dark); }
|
||
.btn svg { width: 16px; height: 16px; flex-shrink: 0; }
|
||
|
||
.chip-group {
|
||
display: inline-flex;
|
||
flex-wrap: wrap;
|
||
gap: 6px;
|
||
padding: 2px;
|
||
}
|
||
.chip {
|
||
height: 28px;
|
||
padding: 0 10px;
|
||
border-radius: 6px;
|
||
border: 1px solid var(--border);
|
||
background: var(--surface);
|
||
color: var(--text-muted);
|
||
font-size: 12px;
|
||
font-weight: 600;
|
||
font-family: inherit;
|
||
cursor: pointer;
|
||
}
|
||
.chip.is-active {
|
||
background: var(--primary);
|
||
border-color: var(--primary);
|
||
color: #fff;
|
||
}
|
||
|
||
.quick-search {
|
||
width: 220px;
|
||
height: 36px;
|
||
padding: 0 12px;
|
||
border: 1px solid var(--border);
|
||
border-radius: 8px;
|
||
font-size: 13px;
|
||
font-family: inherit;
|
||
color: var(--text);
|
||
background: var(--surface);
|
||
}
|
||
.quick-search::placeholder { color: #94a3b8; }
|
||
.quick-search:focus {
|
||
outline: none;
|
||
border-color: var(--primary);
|
||
box-shadow: 0 0 0 3px rgba(16, 185, 129, .15);
|
||
}
|
||
|
||
.list-toolbar-end {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 4px;
|
||
margin-left: auto;
|
||
}
|
||
|
||
.list-body {
|
||
border-top: 1px solid var(--border);
|
||
padding: 0;
|
||
}
|
||
.fake-table {
|
||
width: 100%;
|
||
border-collapse: collapse;
|
||
font-size: 13px;
|
||
}
|
||
.fake-table th,
|
||
.fake-table td {
|
||
padding: 10px 16px;
|
||
text-align: left;
|
||
border-bottom: 1px solid var(--border);
|
||
}
|
||
.fake-table th {
|
||
background: var(--table-head);
|
||
color: var(--text-muted);
|
||
font-size: 12px;
|
||
font-weight: 600;
|
||
}
|
||
.fake-table tbody tr:nth-child(even) td { background: #fafafa; }
|
||
|
||
.note {
|
||
margin-top: 10px;
|
||
padding: 10px 12px;
|
||
border-radius: 8px;
|
||
background: #f1f5f9;
|
||
color: var(--text-muted);
|
||
font-size: 12px;
|
||
}
|
||
.note strong { color: var(--text); }
|
||
|
||
/* —— 方案 A:更多下拉 —— */
|
||
.toolbar-actions {
|
||
display: inline-flex;
|
||
flex-wrap: nowrap;
|
||
align-items: center;
|
||
gap: 8px;
|
||
min-width: 0;
|
||
}
|
||
.more-wrap { position: relative; }
|
||
.more-menu {
|
||
display: none;
|
||
position: absolute;
|
||
top: calc(100% + 6px);
|
||
right: 0;
|
||
z-index: 20;
|
||
min-width: 180px;
|
||
padding: 6px;
|
||
border: 1px solid var(--border);
|
||
border-radius: 10px;
|
||
background: var(--surface);
|
||
box-shadow: var(--shadow-lg);
|
||
}
|
||
.more-wrap.is-open .more-menu { display: block; }
|
||
.more-item {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
width: 100%;
|
||
height: 36px;
|
||
padding: 0 10px;
|
||
border: none;
|
||
border-radius: 8px;
|
||
background: transparent;
|
||
color: var(--text);
|
||
font-size: 13px;
|
||
font-weight: 500;
|
||
font-family: inherit;
|
||
cursor: pointer;
|
||
text-align: left;
|
||
}
|
||
.more-item:hover { background: var(--primary-bg); color: var(--primary-dark); }
|
||
.more-item:disabled { opacity: .45; cursor: not-allowed; }
|
||
.more-item.danger { color: var(--danger); }
|
||
.more-item.danger:hover { background: #fef2f2; color: var(--danger); }
|
||
.more-divider {
|
||
height: 1px;
|
||
margin: 4px 6px;
|
||
background: var(--border);
|
||
}
|
||
|
||
/* —— 方案 B:横向滚动 —— */
|
||
.toolbar-scroll {
|
||
display: flex;
|
||
flex-wrap: nowrap;
|
||
align-items: center;
|
||
gap: 8px;
|
||
min-width: 0;
|
||
flex: 1;
|
||
overflow-x: auto;
|
||
-webkit-overflow-scrolling: touch;
|
||
padding-bottom: 2px;
|
||
scrollbar-width: thin;
|
||
}
|
||
.toolbar-scroll::-webkit-scrollbar { height: 4px; }
|
||
.toolbar-scroll::-webkit-scrollbar-thumb {
|
||
background: #cbd5e1;
|
||
border-radius: 4px;
|
||
}
|
||
.list-toolbar-row.nowrap-row {
|
||
flex-wrap: nowrap;
|
||
}
|
||
|
||
/* —— 方案 C:分区 —— */
|
||
.toolbar-primary {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
flex-wrap: nowrap;
|
||
}
|
||
.toolbar-meta {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
align-items: center;
|
||
gap: 8px;
|
||
padding: 6px 16px 10px;
|
||
border-top: 1px dashed #e2e8f0;
|
||
background: #fcfdfd;
|
||
}
|
||
.meta-label {
|
||
font-size: 11px;
|
||
font-weight: 600;
|
||
color: var(--text-muted);
|
||
letter-spacing: .02em;
|
||
}
|
||
|
||
.compare {
|
||
margin-top: 28px;
|
||
overflow: auto;
|
||
border: 1px solid var(--border);
|
||
border-radius: var(--radius);
|
||
background: var(--surface);
|
||
}
|
||
.compare table { width: 100%; border-collapse: collapse; font-size: 13px; }
|
||
.compare th, .compare td {
|
||
padding: 12px 14px;
|
||
text-align: left;
|
||
border-bottom: 1px solid var(--border);
|
||
vertical-align: top;
|
||
}
|
||
.compare th {
|
||
background: var(--table-head);
|
||
font-size: 12px;
|
||
color: var(--text-muted);
|
||
font-weight: 600;
|
||
}
|
||
.compare tr:last-child td { border-bottom: none; }
|
||
.rec-mark { color: var(--primary-dark); font-weight: 700; }
|
||
|
||
.width-hint {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 10px;
|
||
margin-bottom: 10px;
|
||
font-size: 12px;
|
||
color: var(--text-muted);
|
||
}
|
||
.width-hint input[type="range"] { width: 180px; accent-color: var(--primary); }
|
||
.viewport-frame {
|
||
border: 1px dashed #cbd5e1;
|
||
border-radius: 8px;
|
||
overflow: hidden;
|
||
resize: horizontal;
|
||
max-width: 100%;
|
||
min-width: 480px;
|
||
width: 100%;
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="demo-page">
|
||
<header class="demo-head">
|
||
<h1>列表工具栏溢出优化 · <span>Fresh Mint</span></h1>
|
||
<p>
|
||
当「已选计数 + 日期快捷 + 简易检索 + 业务按钮 + 刷新/列设置」挤在同一行时,
|
||
<code>flex-wrap</code> 会折成两行。当前选定方向:
|
||
<strong>列表工具栏带文案的操作按钮去掉前置图标</strong>(保留描边/主色实心形态),用更小的水平占位换单行概率。
|
||
右侧刷新/列设置仍为纯图标按钮,不受影响。
|
||
</p>
|
||
<nav class="toc">
|
||
<a href="#before">现状(有图标)</a>
|
||
<a class="rec" href="#d">选定 · 去掉前置图标</a>
|
||
<a href="#a">备选 · 更多下拉</a>
|
||
<a href="#b">备选 · 横向滚动</a>
|
||
<a href="#c">备选 · 元信息下沉</a>
|
||
<a href="#compare">对比表</a>
|
||
</nav>
|
||
</header>
|
||
|
||
<!-- ========== 现状 ========== -->
|
||
<section class="section" id="before">
|
||
<div class="section-head">
|
||
<div>
|
||
<h2>0. 现状:全部平铺 → 折行</h2>
|
||
<p>模拟合同列表工具栏:日期快捷 + 检索 + 8 个操作按钮,窄屏或中等宽度必折两行。</p>
|
||
</div>
|
||
<span class="badge badge-bad">需优化</span>
|
||
</div>
|
||
<div class="width-hint">
|
||
拖拽右侧边缘缩小容器宽度,观察折行:
|
||
</div>
|
||
<div class="viewport-frame" style="width: 920px;">
|
||
<div class="panel">
|
||
<div class="panel-top"><h3>合同列表</h3></div>
|
||
<div class="list-toolbar">
|
||
<div class="list-toolbar-row">
|
||
<div class="list-toolbar-selected">已选 <span class="sel-num">2</span> 项</div>
|
||
<div class="chip-group">
|
||
<button type="button" class="chip is-active">全部</button>
|
||
<button type="button" class="chip">昨日</button>
|
||
<button type="button" class="chip">今日</button>
|
||
<button type="button" class="chip">本周</button>
|
||
</div>
|
||
<input class="quick-search" placeholder="合同编号/名称/客户" />
|
||
<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>
|
||
<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>
|
||
<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>
|
||
<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>
|
||
<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>
|
||
<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>
|
||
<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>
|
||
<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>
|
||
<div class="list-toolbar-end">
|
||
<button type="button" class="btn btn-icon" title="刷新" aria-label="刷新">
|
||
<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>
|
||
</button>
|
||
<button type="button" class="btn btn-icon" title="列设置" aria-label="列设置">
|
||
<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>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="list-body">
|
||
<table class="fake-table">
|
||
<thead><tr><th>合同编号</th><th>合同名称</th><th>状态</th></tr></thead>
|
||
<tbody>
|
||
<tr><td>HT-2026-001</td><td>某某化工原料采购合同</td><td>草稿</td></tr>
|
||
<tr><td>HT-2026-002</td><td>年度框架协议</td><td>审批中</td></tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<p class="note"><strong>问题:</strong>折行后占用纵向空间,已选与主操作扫描节奏被打断;窄屏时第二行按钮不易被发现。</p>
|
||
</section>
|
||
|
||
<!-- ========== 选定:去掉前置图标 ========== -->
|
||
<section class="section" id="d">
|
||
<div class="section-head">
|
||
<div>
|
||
<h2>选定方案 · 工具栏文案按钮去掉前置图标</h2>
|
||
<p>保留现有描边 / 主色实心按钮形态,只去掉文案前的图标与图标间距,降低每个按钮约 16px + gap 的占位。</p>
|
||
</div>
|
||
<span class="badge badge-ok">已选定</span>
|
||
</div>
|
||
|
||
<div class="measure-row">
|
||
<div class="measure-card">
|
||
<div class="measure-card-head">
|
||
<span>有前置图标(现状)</span>
|
||
<span>动作区宽约 <span class="px-tag" id="wGhost">—</span> px</span>
|
||
</div>
|
||
<div class="measure-card-body">
|
||
<div class="measure-actions ghost-gap" id="measureGhost">
|
||
<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>
|
||
<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>
|
||
<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>
|
||
<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>
|
||
<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>
|
||
<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>
|
||
<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>
|
||
<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>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="measure-card">
|
||
<div class="measure-card-head">
|
||
<span>无前置图标(选定)</span>
|
||
<span>动作区宽约 <span class="px-tag" id="wTextOnly">—</span> px</span>
|
||
</div>
|
||
<div class="measure-card-body">
|
||
<div class="measure-actions ghost-gap" id="measureTextOnly">
|
||
<button type="button" class="btn btn-main">新增</button>
|
||
<button type="button" class="btn">编辑</button>
|
||
<button type="button" class="btn">删除</button>
|
||
<button type="button" class="btn">提交审批</button>
|
||
<button type="button" class="btn">审批通过</button>
|
||
<button type="button" class="btn">审批驳回</button>
|
||
<button type="button" class="btn">上传扫描件</button>
|
||
<button type="button" class="btn">导出</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="measure-card">
|
||
<div class="measure-card-head">
|
||
<span>对照:文字按钮仍带图标</span>
|
||
<span>动作区宽约 <span class="px-tag" id="wText">—</span> px</span>
|
||
</div>
|
||
<div class="measure-card-body">
|
||
<div class="measure-actions" id="measureText">
|
||
<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>
|
||
<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>
|
||
<button type="button" class="btn btn-text">删除</button>
|
||
<button type="button" class="btn btn-text">提交审批</button>
|
||
<button type="button" class="btn btn-text">审批通过</button>
|
||
<button type="button" class="btn btn-text">审批驳回</button>
|
||
<button type="button" class="btn btn-text">上传扫描件</button>
|
||
<button type="button" class="btn btn-text">导出</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="measure-card">
|
||
<div class="measure-card-head">
|
||
<span>节省幅度(相对「有图标描边」)</span>
|
||
<span id="saveSummary" class="px-tag">—</span>
|
||
</div>
|
||
<div class="measure-card-body" style="color: var(--text-muted); font-size: 13px; line-height: 1.7;">
|
||
<div>去掉前置图标:约省 <strong id="saveTextOnly">—</strong></div>
|
||
<div>仅改文字按钮但仍留图标:约省 <strong id="saveText">—</strong></div>
|
||
<div style="margin-top: 6px;">结论:在保留按钮形态的前提下,<strong>去图标比改文字链更划算、也更贴合现有视觉</strong>。</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="width-hint">整行模拟(去图标后),拖窄观察折行是否缓解:</div>
|
||
<div class="viewport-frame" style="width: 920px;">
|
||
<div class="panel">
|
||
<div class="panel-top"><h3>合同列表 · 无前置图标</h3></div>
|
||
<div class="list-toolbar">
|
||
<div class="list-toolbar-row">
|
||
<div class="list-toolbar-selected">已选 <span class="sel-num">2</span> 项</div>
|
||
<div class="chip-group">
|
||
<button type="button" class="chip is-active">全部</button>
|
||
<button type="button" class="chip">昨日</button>
|
||
<button type="button" class="chip">今日</button>
|
||
<button type="button" class="chip">本周</button>
|
||
</div>
|
||
<input class="quick-search" placeholder="合同编号/名称/客户" />
|
||
<button type="button" class="btn btn-main">新增</button>
|
||
<button type="button" class="btn">编辑</button>
|
||
<button type="button" class="btn">删除</button>
|
||
<button type="button" class="btn">提交审批</button>
|
||
<button type="button" class="btn">审批通过</button>
|
||
<button type="button" class="btn">审批驳回</button>
|
||
<button type="button" class="btn">上传扫描件</button>
|
||
<button type="button" class="btn">导出</button>
|
||
<div class="list-toolbar-end">
|
||
<button type="button" class="btn btn-icon" title="刷新" aria-label="刷新">
|
||
<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>
|
||
</button>
|
||
<button type="button" class="btn btn-icon" title="列设置" aria-label="列设置">
|
||
<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>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="list-body">
|
||
<table class="fake-table">
|
||
<thead><tr><th>合同编号</th><th>合同名称</th><th>状态</th></tr></thead>
|
||
<tbody>
|
||
<tr><td>HT-2026-001</td><td>某某化工原料采购合同</td><td>草稿</td></tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<p class="note">
|
||
<strong>落地范围建议:</strong>仅列表工具栏 <code>.list-toolbar</code> 内「有文案的 el-button」去掉前置图标;
|
||
弹窗底栏、表格操作列、筛选区搜索/重置等可暂不改。
|
||
注意:现有 OpenSpec「按钮须携带图标」需同步放宽为「工具栏文案按钮可不带图标」。
|
||
</p>
|
||
</section>
|
||
|
||
<!-- ========== 方案 A ========== -->
|
||
<section class="section" id="a">
|
||
<div class="section-head">
|
||
<div>
|
||
<h2>方案 A · 主操作外露 +「更多」下拉</h2>
|
||
<p>高频主路径始终可见;低频/场景化动作收入「更多」。保持单行、左起扫描、仅首按钮主色。</p>
|
||
</div>
|
||
<span class="badge badge-alt">备选</span>
|
||
</div>
|
||
<div class="viewport-frame" style="width: 920px;">
|
||
<div class="panel">
|
||
<div class="panel-top"><h3>合同列表</h3></div>
|
||
<div class="list-toolbar">
|
||
<div class="list-toolbar-row nowrap-row">
|
||
<div class="list-toolbar-selected">已选 <span class="sel-num">2</span> 项</div>
|
||
<div class="toolbar-actions">
|
||
<button type="button" class="btn btn-main">新增</button>
|
||
<button type="button" class="btn">编辑</button>
|
||
<button type="button" class="btn">删除</button>
|
||
<button type="button" class="btn">提交审批</button>
|
||
<div class="more-wrap" id="moreA">
|
||
<button type="button" class="btn" id="moreABtn" aria-expanded="false" aria-haspopup="true">
|
||
更多
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M6 9l6 6 6-6"/></svg>
|
||
</button>
|
||
<div class="more-menu" role="menu">
|
||
<button type="button" class="more-item" role="menuitem">上传扫描件</button>
|
||
<button type="button" class="more-item" role="menuitem">导出</button>
|
||
<div class="more-divider"></div>
|
||
<button type="button" class="more-item" role="menuitem">审批通过</button>
|
||
<button type="button" class="more-item" role="menuitem">审批驳回</button>
|
||
<div class="more-divider"></div>
|
||
<button type="button" class="more-item" role="menuitem">下载导入模板</button>
|
||
<button type="button" class="more-item" role="menuitem">批量导入</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="list-toolbar-end">
|
||
<button type="button" class="btn btn-icon" title="刷新" aria-label="刷新">
|
||
<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>
|
||
</button>
|
||
<button type="button" class="btn btn-icon" title="列设置" aria-label="列设置">
|
||
<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>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="list-body">
|
||
<table class="fake-table">
|
||
<thead><tr><th>合同编号</th><th>合同名称</th><th>状态</th></tr></thead>
|
||
<tbody>
|
||
<tr><td>HT-2026-001</td><td>某某化工原料采购合同</td><td>草稿</td></tr>
|
||
<tr><td>HT-2026-002</td><td>年度框架协议</td><td>审批中</td></tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<p class="note">
|
||
<strong>收纳原则:</strong>
|
||
① 主创建/主编辑类外露;② 依赖选中行的低频动作(上传、导出、导入)进「更多」;
|
||
③ 场景切换型动作(审批通过/驳回)可按当前 Tab 动态外露或进「更多」;
|
||
④ 日期快捷与简易检索建议迁出工具栏(见方案 C),避免与操作按钮抢宽。
|
||
落地可用 Element Plus <code>el-dropdown</code>。
|
||
</p>
|
||
</section>
|
||
|
||
<!-- ========== 方案 B ========== -->
|
||
<section class="section" id="b">
|
||
<div class="section-head">
|
||
<div>
|
||
<h2>方案 B · 单行不换行 + 横向滚动</h2>
|
||
<p>工具栏 <code>flex-wrap: nowrap</code>,中间操作区 <code>overflow-x: auto</code>。实现成本低,但可发现性差。</p>
|
||
</div>
|
||
<span class="badge badge-alt">备选</span>
|
||
</div>
|
||
<div class="viewport-frame" style="width: 640px;">
|
||
<div class="panel">
|
||
<div class="panel-top"><h3>合同列表</h3></div>
|
||
<div class="list-toolbar">
|
||
<div class="list-toolbar-row nowrap-row">
|
||
<div class="list-toolbar-selected">已选 <span class="sel-num">2</span> 项</div>
|
||
<div class="toolbar-scroll">
|
||
<button type="button" class="btn btn-main">新增</button>
|
||
<button type="button" class="btn">编辑</button>
|
||
<button type="button" class="btn">删除</button>
|
||
<button type="button" class="btn">提交审批</button>
|
||
<button type="button" class="btn">审批通过</button>
|
||
<button type="button" class="btn">审批驳回</button>
|
||
<button type="button" class="btn">上传扫描件</button>
|
||
<button type="button" class="btn">导出</button>
|
||
</div>
|
||
<div class="list-toolbar-end">
|
||
<button type="button" class="btn btn-icon" title="刷新" aria-label="刷新">
|
||
<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>
|
||
</button>
|
||
<button type="button" class="btn btn-icon" title="列设置" aria-label="列设置">
|
||
<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>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="list-body">
|
||
<table class="fake-table">
|
||
<thead><tr><th>合同编号</th><th>合同名称</th><th>状态</th></tr></thead>
|
||
<tbody>
|
||
<tr><td>HT-2026-001</td><td>某某化工原料采购合同</td><td>草稿</td></tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<p class="note"><strong>适用:</strong>临时兜底或按钮数量偶发超标的页。不推荐作为全站默认——右侧按钮容易被「藏」在滚动条外。</p>
|
||
</section>
|
||
|
||
<!-- ========== 方案 C ========== -->
|
||
<section class="section" id="c">
|
||
<div class="section-head">
|
||
<div>
|
||
<h2>方案 C · 元信息下沉 + 主操作区精简</h2>
|
||
<p>把「日期快捷 / 简易检索」从操作行拆出(独立轻量条或并回筛选区),操作行只保留 CRUD + 更多。</p>
|
||
</div>
|
||
<span class="badge badge-ok">可与 A 组合</span>
|
||
</div>
|
||
<div class="viewport-frame" style="width: 920px;">
|
||
<div class="panel">
|
||
<div class="panel-top"><h3>合同列表</h3></div>
|
||
<div class="list-toolbar">
|
||
<div class="list-toolbar-row nowrap-row">
|
||
<div class="list-toolbar-selected">已选 <span class="sel-num">2</span> 项</div>
|
||
<div class="toolbar-primary">
|
||
<button type="button" class="btn btn-main">新增</button>
|
||
<button type="button" class="btn">编辑</button>
|
||
<button type="button" class="btn">删除</button>
|
||
<button type="button" class="btn">提交审批</button>
|
||
<div class="more-wrap" id="moreC">
|
||
<button type="button" class="btn" id="moreCBtn">
|
||
更多
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M6 9l6 6 6-6"/></svg>
|
||
</button>
|
||
<div class="more-menu" role="menu">
|
||
<button type="button" class="more-item">上传扫描件</button>
|
||
<button type="button" class="more-item">导出</button>
|
||
<div class="more-divider"></div>
|
||
<button type="button" class="more-item">审批通过</button>
|
||
<button type="button" class="more-item">审批驳回</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="list-toolbar-end">
|
||
<button type="button" class="btn btn-icon" title="刷新" aria-label="刷新">
|
||
<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>
|
||
</button>
|
||
<button type="button" class="btn btn-icon" title="列设置" aria-label="列设置">
|
||
<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>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
<div class="toolbar-meta">
|
||
<span class="meta-label">快捷筛选</span>
|
||
<div class="chip-group">
|
||
<button type="button" class="chip is-active">全部</button>
|
||
<button type="button" class="chip">昨日</button>
|
||
<button type="button" class="chip">今日</button>
|
||
<button type="button" class="chip">本周</button>
|
||
</div>
|
||
<input class="quick-search" placeholder="合同编号/名称/客户" style="margin-left: auto;" />
|
||
</div>
|
||
</div>
|
||
<div class="list-body">
|
||
<table class="fake-table">
|
||
<thead><tr><th>合同编号</th><th>合同名称</th><th>状态</th></tr></thead>
|
||
<tbody>
|
||
<tr><td>HT-2026-001</td><td>某某化工原料采购合同</td><td>草稿</td></tr>
|
||
<tr><td>HT-2026-002</td><td>年度框架协议</td><td>审批中</td></tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<p class="note">
|
||
<strong>说明:</strong>这里用虚线次级条演示「元信息下沉」。产品上更干净的做法是:日期快捷/检索回到上方
|
||
<code>filter-block</code>,工具栏只做「已选 → 动作 → 刷新/列设置」。
|
||
</p>
|
||
</section>
|
||
|
||
<!-- ========== 对比 ========== -->
|
||
<section class="section" id="compare">
|
||
<div class="section-head">
|
||
<div>
|
||
<h2>对比与落地建议</h2>
|
||
<p>全站默认建议 A;复杂业务页(合同/价格函)用 A + 把日期/检索移出操作行。</p>
|
||
</div>
|
||
</div>
|
||
<div class="compare">
|
||
<table>
|
||
<thead>
|
||
<tr>
|
||
<th>方案</th>
|
||
<th>单行稳定</th>
|
||
<th>可发现性</th>
|
||
<th>与规范兼容</th>
|
||
<th>实现成本</th>
|
||
<th>建议</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr>
|
||
<td>去掉前置图标</td>
|
||
<td>中高</td>
|
||
<td>高</td>
|
||
<td>需放宽「按钮必带图标」</td>
|
||
<td>低</td>
|
||
<td class="rec-mark">已选定</td>
|
||
</tr>
|
||
<tr>
|
||
<td>A 更多下拉</td>
|
||
<td>高</td>
|
||
<td>中高</td>
|
||
<td>高</td>
|
||
<td>中</td>
|
||
<td>备选</td>
|
||
</tr>
|
||
<tr>
|
||
<td>B 横向滚动</td>
|
||
<td>高</td>
|
||
<td>低</td>
|
||
<td>中</td>
|
||
<td>低</td>
|
||
<td>备选</td>
|
||
</tr>
|
||
<tr>
|
||
<td>C 元信息下沉</td>
|
||
<td>高(组合后)</td>
|
||
<td>高</td>
|
||
<td>高</td>
|
||
<td>低~中</td>
|
||
<td>备选</td>
|
||
</tr>
|
||
<tr>
|
||
<td>全部平铺 + 有图标 + wrap</td>
|
||
<td>低</td>
|
||
<td>高但占高</td>
|
||
<td>符合旧规范</td>
|
||
<td>—</td>
|
||
<td>弃用</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
<p class="note" style="margin-top: 16px;">
|
||
<strong>落地约定(草案):</strong>
|
||
<code>.list-toolbar</code> 内带可见文案的操作按钮不再放前置 <code>el-icon</code>;
|
||
「刷新 / 列设置」等无文案图标钮保留;弹窗 footer、筛选区、表格操作列暂保持有图标。
|
||
</p>
|
||
</section>
|
||
</div>
|
||
|
||
<script>
|
||
function bindMore(wrapId, btnId) {
|
||
const wrap = document.getElementById(wrapId)
|
||
const btn = document.getElementById(btnId)
|
||
if (!wrap || !btn) return
|
||
btn.addEventListener('click', (e) => {
|
||
e.stopPropagation()
|
||
const open = wrap.classList.toggle('is-open')
|
||
btn.setAttribute('aria-expanded', open ? 'true' : 'false')
|
||
document.querySelectorAll('.more-wrap.is-open').forEach((el) => {
|
||
if (el !== wrap) {
|
||
el.classList.remove('is-open')
|
||
const b = el.querySelector('button')
|
||
if (b) b.setAttribute('aria-expanded', 'false')
|
||
}
|
||
})
|
||
})
|
||
}
|
||
bindMore('moreA', 'moreABtn')
|
||
bindMore('moreC', 'moreCBtn')
|
||
document.addEventListener('click', () => {
|
||
document.querySelectorAll('.more-wrap.is-open').forEach((el) => {
|
||
el.classList.remove('is-open')
|
||
const b = el.querySelector('button')
|
||
if (b) b.setAttribute('aria-expanded', 'false')
|
||
})
|
||
})
|
||
|
||
function measureToolbarWidths() {
|
||
const ghost = document.getElementById('measureGhost')
|
||
const text = document.getElementById('measureText')
|
||
const textOnly = document.getElementById('measureTextOnly')
|
||
if (!ghost || !text || !textOnly) return
|
||
const wG = Math.round(ghost.scrollWidth)
|
||
const wT = Math.round(text.scrollWidth)
|
||
const wO = Math.round(textOnly.scrollWidth)
|
||
const set = (id, v) => { const el = document.getElementById(id); if (el) el.textContent = String(v) }
|
||
set('wGhost', wG)
|
||
set('wText', wT)
|
||
set('wTextOnly', wO)
|
||
const pct = (from, to) => ((from - to) / from * 100).toFixed(0) + '%(约 ' + (from - to) + 'px)'
|
||
set('saveText', pct(wG, wT))
|
||
set('saveTextOnly', pct(wG, wO))
|
||
set('saveSummary', '最多约省 ' + pct(wG, wO))
|
||
}
|
||
measureToolbarWidths()
|
||
window.addEventListener('resize', measureToolbarWidths)
|
||
</script>
|
||
</body>
|
||
</html>
|