Files
zkxw/mobile-ui/zkxw-mobile-control-style-demos.html
T
2026-08-12 15:48:15 +08:00

842 lines
23 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>mobile-h5 · 控件样式 Demo(输入 / 下拉 / Tag / Tab)</title>
<style>
:root {
--ink: #1f2937;
--muted: #6b7280;
--line: #e5e7eb;
--page: #f3f4f6;
--brand: #22c55e;
--brand-ink: #166534;
}
* { box-sizing: border-box; }
html, body { margin: 0; min-height: 100%; }
body {
font-family: "PingFang SC", "Hiragino Sans GB", "Noto Sans SC", -apple-system, system-ui, sans-serif;
color: var(--ink);
background: linear-gradient(180deg, #f8fafc 0%, #eef2f7 100%);
line-height: 1.45;
}
.wrap {
max-width: 1240px;
margin: 0 auto;
padding: 28px 16px 80px;
}
h1 { margin: 0 0 8px; font-size: 24px; letter-spacing: -0.02em; }
.sub { margin: 0; color: var(--muted); font-size: 14px; max-width: 760px; }
.note {
margin: 14px 0 22px;
padding: 10px 12px;
border-radius: 10px;
background: #fff;
border: 1px solid var(--line);
font-size: 13px;
color: var(--muted);
}
.note strong { color: var(--brand-ink); }
.scheme-tabs {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-bottom: 18px;
}
.scheme-tab {
appearance: none;
border: 1px solid var(--line);
background: #fff;
border-radius: 999px;
padding: 9px 14px;
font-size: 13px;
font-weight: 650;
color: #475569;
cursor: pointer;
}
.scheme-tab.is-on {
border-color: #94a3b8;
background: #0f172a;
color: #fff;
}
.scheme-tab .rec {
margin-left: 6px;
font-size: 10px;
padding: 1px 6px;
border-radius: 999px;
background: #ecfdf5;
color: #166534;
}
.scheme-tab.is-on .rec {
background: rgba(255,255,255,.18);
color: #fff;
}
.grid {
display: grid;
grid-template-columns: 1fr 390px;
gap: 18px;
align-items: start;
}
@media (max-width: 960px) {
.grid { grid-template-columns: 1fr; }
}
.panel {
background: #fff;
border: 1px solid var(--line);
border-radius: 16px;
padding: 16px;
box-shadow: 0 8px 24px rgba(15, 23, 42, 0.04);
}
.panel h2 {
margin: 0 0 4px;
font-size: 15px;
}
.panel .desc {
margin: 0 0 14px;
font-size: 12px;
color: var(--muted);
}
.block { margin-bottom: 22px; }
.block:last-child { margin-bottom: 0; }
.block-label {
margin: 0 0 10px;
font-size: 11px;
font-weight: 700;
letter-spacing: 0.06em;
text-transform: uppercase;
color: #94a3b8;
}
/* ========== 手机预览壳 ========== */
.phone {
width: 100%;
max-width: 390px;
margin: 0 auto;
background: #111827;
border-radius: 36px;
padding: 10px;
box-shadow: 0 24px 50px rgba(15, 23, 42, 0.18);
}
.phone-screen {
border-radius: 28px;
overflow: hidden;
background: #fff;
min-height: 720px;
}
.phone-nav {
height: 48px;
display: flex;
align-items: center;
justify-content: center;
font-size: 15px;
font-weight: 650;
border-bottom: 1px solid #f1f5f9;
background: #fff;
}
/* ========== 方案公共预览区 ========== */
.preview-pad { padding: 12px 14px 20px; background: #fff; }
.preview-stack { display: grid; gap: 16px; }
/* —— 控件原子(由 scheme class 覆盖) —— */
.ui-search {
display: flex;
align-items: center;
gap: 8px;
height: 40px;
padding: 0 12px;
width: 100%;
}
.ui-search input {
flex: 1;
border: 0;
outline: none;
background: transparent;
font-size: 14px;
color: var(--ink);
min-width: 0;
font-family: inherit;
}
.ui-search input::placeholder { color: #b0b7c3; }
.ui-ico { color: #9ca3af; font-size: 14px; flex-shrink: 0; }
.ui-select {
position: relative;
width: 100%;
}
.ui-select-trigger {
width: 100%;
height: 40px;
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
padding: 0 12px;
font-size: 14px;
cursor: pointer;
background: #fff;
font-family: inherit;
color: var(--ink);
}
.ui-select-trigger .ph { color: #b0b7c3; }
.ui-select-trigger .chev {
color: #9ca3af;
font-size: 12px;
transition: transform .15s;
}
.ui-select.open .ui-select-trigger .chev { transform: rotate(180deg); }
.ui-dropdown {
display: none;
position: absolute;
left: 0; right: 0;
top: calc(100% + 6px);
z-index: 20;
background: #fff;
overflow: hidden;
max-height: 260px;
}
.ui-select.open .ui-dropdown { display: block; }
.ui-dropdown .dd-search {
display: flex;
align-items: center;
gap: 8px;
margin: 10px;
height: 34px;
padding: 0 10px;
}
.ui-dropdown .dd-search input {
flex: 1;
border: 0;
outline: none;
background: transparent;
font-size: 13px;
font-family: inherit;
}
.ui-dropdown .dd-list {
max-height: 180px;
overflow: auto;
padding: 4px 0 8px;
}
.ui-dropdown .dd-item {
padding: 10px 14px;
font-size: 13px;
cursor: pointer;
color: var(--ink);
}
.ui-dropdown .dd-item:hover { background: #f8fafc; }
.ui-dropdown .dd-item.is-on { font-weight: 650; }
.ui-tags {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
.ui-tag {
display: inline-flex;
align-items: center;
height: 28px;
padding: 0 12px;
font-size: 12px;
cursor: pointer;
user-select: none;
border: 0;
font-family: inherit;
}
.ui-tag.is-on { font-weight: 650; }
.ui-tabs {
display: flex;
align-items: stretch;
gap: 0;
overflow-x: auto;
-webkit-overflow-scrolling: touch;
}
.ui-tab {
position: relative;
flex-shrink: 0;
height: 40px;
padding: 0 14px;
display: inline-flex;
align-items: center;
justify-content: center;
font-size: 14px;
color: #6b7280;
cursor: pointer;
background: transparent;
border: 0;
font-family: inherit;
}
.ui-tab.is-on {
color: var(--ink);
font-weight: 650;
}
/* ========== A 极简线框 ========== */
.scheme-a .ui-search {
border: 1px solid #e5e7eb;
border-radius: 10px;
background: #fff;
}
.scheme-a .ui-select-trigger {
border: 1px solid #e5e7eb;
border-radius: 10px;
}
.scheme-a .ui-dropdown {
border: 1px solid #e5e7eb;
border-radius: 12px;
box-shadow: 0 10px 28px rgba(15,23,42,.08);
}
.scheme-a .ui-dropdown .dd-search {
border: 1px solid #e5e7eb;
border-radius: 8px;
background: #fff;
}
.scheme-a .ui-dropdown .dd-item.is-on { color: #111827; background: #f3f4f6; }
.scheme-a .ui-tag {
border: 1px solid #e5e7eb;
border-radius: 8px;
background: #fff;
color: #4b5563;
}
.scheme-a .ui-tag.is-on {
border-color: #111827;
color: #111827;
background: #fff;
}
.scheme-a .ui-tabs {
border-bottom: 1px solid #eee;
}
.scheme-a .ui-tab.is-on::after {
content: "";
position: absolute;
left: 14px; right: 14px; bottom: 0;
height: 2px;
background: #111827;
border-radius: 2px 2px 0 0;
}
/* ========== B 浅灰填充 ========== */
.scheme-b .ui-search {
border: 0;
border-radius: 12px;
background: #f3f4f6;
}
.scheme-b .ui-select-trigger {
border: 0;
border-radius: 12px;
background: #f3f4f6;
}
.scheme-b .ui-dropdown {
border: 0;
border-radius: 14px;
box-shadow: 0 12px 32px rgba(15,23,42,.12);
}
.scheme-b .ui-dropdown .dd-search {
border: 0;
border-radius: 10px;
background: #f3f4f6;
}
.scheme-b .ui-dropdown .dd-item.is-on { background: #eef2ff; color: #3730a3; }
.scheme-b .ui-tag {
border-radius: 999px;
background: #f3f4f6;
color: #6b7280;
}
.scheme-b .ui-tag.is-on {
background: #e5e7eb;
color: #111827;
}
.scheme-b .ui-tabs {
gap: 8px;
padding: 2px 0;
}
.scheme-b .ui-tab {
height: 32px;
border-radius: 999px;
background: #f3f4f6;
padding: 0 14px;
font-size: 13px;
}
.scheme-b .ui-tab.is-on {
background: #e5e7eb;
color: #111827;
}
/* ========== C 分段控件 ========== */
.scheme-c .ui-search {
border: 0;
border-radius: 999px;
background: #f5f5f7;
padding: 0 14px;
}
.scheme-c .ui-select-trigger {
border: 0;
border-radius: 12px;
background: #f5f5f7;
}
.scheme-c .ui-dropdown {
border-radius: 16px;
box-shadow: 0 16px 40px rgba(15,23,42,.14);
border: 1px solid rgba(15,23,42,.06);
}
.scheme-c .ui-dropdown .dd-search {
border: 0;
border-radius: 999px;
background: #f5f5f7;
}
.scheme-c .ui-dropdown .dd-item.is-on { color: #007aff; background: #f0f7ff; }
.scheme-c .ui-tag {
border-radius: 8px;
background: #f5f5f7;
color: #6b7280;
}
.scheme-c .ui-tag.is-on {
background: #111827;
color: #fff;
}
.scheme-c .ui-tabs {
background: #f5f5f7;
border-radius: 10px;
padding: 3px;
gap: 2px;
}
.scheme-c .ui-tab {
flex: 1;
height: 34px;
border-radius: 8px;
font-size: 13px;
justify-content: center;
}
.scheme-c .ui-tab.is-on {
background: #fff;
color: #111827;
box-shadow: 0 1px 3px rgba(15,23,42,.08);
}
/* ========== D 清爽描边 + 轻主色(推荐) ========== */
.scheme-d .ui-search {
border: 1px solid #e8eaed;
border-radius: 10px;
background: #fafafa;
}
.scheme-d .ui-search:focus-within {
border-color: #cbd5e1;
background: #fff;
box-shadow: 0 0 0 3px rgba(148, 163, 184, 0.18);
}
.scheme-d .ui-select-trigger {
border: 1px solid #e8eaed;
border-radius: 10px;
background: #fafafa;
}
.scheme-d .ui-select.open .ui-select-trigger {
border-color: #cbd5e1;
background: #fff;
}
.scheme-d .ui-dropdown {
border: 1px solid #e8eaed;
border-radius: 12px;
box-shadow: 0 12px 28px rgba(15,23,42,.1);
}
.scheme-d .ui-dropdown .dd-search {
border: 1px solid #e8eaed;
border-radius: 8px;
background: #fafafa;
}
.scheme-d .ui-dropdown .dd-item.is-on {
color: var(--brand-ink);
background: #f0fdf4;
}
.scheme-d .ui-tag {
border: 1px solid #e8eaed;
border-radius: 999px;
background: #fff;
color: #64748b;
}
.scheme-d .ui-tag.is-on {
border-color: #bbf7d0;
background: #f0fdf4;
color: var(--brand-ink);
}
.scheme-d .ui-tabs {
border-bottom: 1px solid #f1f5f9;
}
.scheme-d .ui-tab.is-on {
color: var(--brand-ink);
}
.scheme-d .ui-tab.is-on::after {
content: "";
position: absolute;
left: 14px; right: 14px; bottom: 0;
height: 2px;
background: var(--brand);
border-radius: 2px 2px 0 0;
}
.phone-list {
padding: 12px 14px 20px;
}
.phone-card {
display: flex;
align-items: center;
gap: 10px;
padding: 12px;
border: 1px solid #e8eaed;
border-radius: 12px;
margin-bottom: 10px;
background: #fff;
}
.phone-card .ico {
width: 36px; height: 36px;
border-radius: 8px;
background: #f1f5f9;
display: grid; place-items: center;
font-size: 12px; font-weight: 700; color: #64748b;
}
.phone-card .name { font-size: 14px; font-weight: 650; }
.phone-card .meta { font-size: 12px; color: #94a3b8; margin-top: 2px; }
.phone-card .go {
margin-left: auto;
font-size: 12px;
font-weight: 650;
color: #fff;
background: var(--brand);
border-radius: 8px;
padding: 6px 10px;
}
.token-list {
display: grid;
gap: 6px;
margin-top: 10px;
font-size: 12px;
color: #64748b;
}
.token-list code {
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
background: #f8fafc;
border: 1px solid #eef2f7;
border-radius: 6px;
padding: 1px 5px;
color: #0f766e;
}
.compare {
margin-top: 22px;
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 12px;
}
@media (max-width: 1100px) {
.compare { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 640px) {
.compare { grid-template-columns: 1fr; }
}
.compare .mini {
background: #fff;
border: 1px solid var(--line);
border-radius: 14px;
padding: 12px;
cursor: pointer;
transition: border-color .15s, box-shadow .15s;
}
.compare .mini:hover,
.compare .mini.is-on {
border-color: #94a3b8;
box-shadow: 0 0 0 3px rgba(148,163,184,.2);
}
.compare .mini h3 {
margin: 0 0 8px;
font-size: 13px;
}
</style>
</head>
<body>
<div class="wrap">
<h1>控件样式 Demo · 输入 / 下拉 / Tag / Tab</h1>
<p class="sub">
针对「发起审批」顶部检索与分类,以及表单里常见的下拉选择。以下 4 套均为中性清爽风格,主色只作轻量点缀(或完全不用)。
点选方案后,右侧手机预览会同步切换。
</p>
<div class="note">
文件路径:<strong>mobile-ui/zkxw-mobile-control-style-demos.html</strong>
· 本地直接打开即可。选定后告诉我方案代号(A/B/C/D),再落到工程。
</div>
<div class="scheme-tabs" id="schemeTabs"></div>
<div class="grid">
<aside class="panel" id="specPanel">
<h2 id="schemeTitle">—</h2>
<p class="desc" id="schemeDesc">—</p>
<div class="block">
<div class="block-label">1. 搜索输入框</div>
<div id="demoSearch"></div>
</div>
<div class="block">
<div class="block-label">2. 下拉框</div>
<div id="demoSelect"></div>
</div>
<div class="block">
<div class="block-label">3. 下拉框(内嵌检索)</div>
<div id="demoSelectSearch"></div>
</div>
<div class="block">
<div class="block-label">4. Tag 标签</div>
<div id="demoTags"></div>
</div>
<div class="block">
<div class="block-label">5. Tab 栏</div>
<div id="demoTabs"></div>
</div>
<div class="token-list" id="tokenList"></div>
</aside>
<section>
<div class="phone">
<div class="phone-screen" id="phoneRoot">
<div class="phone-nav">发起审批</div>
<div class="preview-pad" id="phoneToolbar"></div>
<div class="phone-list">
<div class="phone-card">
<div class="ico">假</div>
<div>
<div class="name">请假申请</div>
<div class="meta">人事 · 通用</div>
</div>
<div class="go">发起</div>
</div>
<div class="phone-card">
<div class="ico">差</div>
<div>
<div class="name">出差申请</div>
<div class="meta">行政</div>
</div>
<div class="go">发起</div>
</div>
<div class="phone-card">
<div class="ico">报</div>
<div>
<div class="name">费用报销</div>
<div class="meta">财务</div>
</div>
<div class="go">发起</div>
</div>
</div>
</div>
</div>
</section>
</div>
<div class="compare" id="compare"></div>
</div>
<script>
const SCHEMES = {
a: {
id: 'a',
name: 'A · 极简线框',
desc: '白底细线框,Tab 用下划线。最克制,适合信息密度高的列表页。完全不用主题绿。',
tokens: [
'搜索:白底 + #e5e7eb 描边,圆角 10',
'下拉:同搜索线框;展开层白底阴影',
'Tag:线框方圆角;选中加深描边',
'Tab:文字 + 黑色下划线'
]
},
b: {
id: 'b',
name: 'B · 浅灰填充',
desc: '控件用浅灰底、无描边。Tag / Tab 都是胶囊。柔和,但选中对比偏弱。',
tokens: [
'搜索 / 下拉:#f3f4f6 填充,圆角 12',
'内嵌检索:同浅灰底',
'Tag / Tab:灰色胶囊,选中更深灰'
]
},
c: {
id: 'c',
name: 'C · 分段控件',
desc: '搜索做成胶囊;分类用 iOS 分段控件。适合分类少(≤5)的场景。',
tokens: [
'搜索:圆角胶囊 #f5f5f7',
'Tag:选中黑底白字',
'Tab:整条灰底,选中白块浮起'
]
},
d: {
id: 'd',
name: 'D · 清爽描边',
badge: '推荐',
desc: '浅灰底 + 细描边,聚焦时微阴影。Tag/Tab 选中才用极浅绿点缀,主色不抢戏。最适合当前「明亮但不花」目标。',
tokens: [
'搜索:#fafafa + #e8eaed 边,聚焦提亮',
'下拉检索:同风格;选中项浅绿底',
'Tag:白底描边胶囊;选中浅绿',
'Tab:下划线用主色,文字深绿'
]
}
}
const GROUPS = ['全部', '人事', '行政', '财务', '其他']
const OPTIONS = ['请假申请', '出差申请', '加班申请', '费用报销', '用章申请', '采购申请']
let active = 'd'
let tabOn = 0
let tagOn = 0
let selectVal = ''
let selectSearchVal = ''
let openSelect = '' // 'plain' | 'search' | ''
function el(html) {
const t = document.createElement('template')
t.innerHTML = html.trim()
return t.content.firstElementChild
}
function renderSchemeTabs() {
const root = document.getElementById('schemeTabs')
root.innerHTML = ''
Object.values(SCHEMES).forEach((s) => {
const b = el(`<button class="scheme-tab ${s.id === active ? 'is-on' : ''}" type="button">
${s.name}${s.badge ? `<span class="rec">${s.badge}</span>` : ''}
</button>`)
b.onclick = () => { active = s.id; openSelect = ''; renderAll() }
root.appendChild(b)
})
}
function renderCompare() {
const root = document.getElementById('compare')
root.innerHTML = ''
Object.values(SCHEMES).forEach((s) => {
const card = el(`<div class="mini ${s.id === active ? 'is-on' : ''} scheme-${s.id}" data-choice="${s.id}">
<h3>${s.name}</h3>
<div class="ui-search" style="margin-bottom:8px"><span class="ui-ico">⌕</span><input placeholder="搜索审批单" readonly /></div>
<div class="ui-tags" style="margin-bottom:8px">
<button class="ui-tag is-on" type="button">全部</button>
<button class="ui-tag" type="button">人事</button>
</div>
<div class="ui-tabs">
<button class="ui-tab is-on" type="button">全部</button>
<button class="ui-tab" type="button">人事</button>
<button class="ui-tab" type="button">行政</button>
</div>
</div>`)
card.onclick = () => { active = s.id; openSelect = ''; renderAll() }
root.appendChild(card)
})
}
function searchHtml() {
return `<div class="ui-search"><span class="ui-ico">⌕</span><input placeholder="搜索审批单" /><span class="ui-ico">×</span></div>`
}
function selectHtml(kind) {
const isOpen = openSelect === kind
const val = kind === 'plain' ? selectVal : selectSearchVal
const label = val || '<span class="ph">请选择审批类型</span>'
const items = OPTIONS.map((o) =>
`<div class="dd-item ${o === val ? 'is-on' : ''}" data-v="${o}">${o}</div>`
).join('')
const searchBar = kind === 'search'
? `<div class="dd-search"><span class="ui-ico">⌕</span><input placeholder="输入关键词筛选" /></div>`
: ''
return `<div class="ui-select ${isOpen ? 'open' : ''}" data-kind="${kind}">
<button type="button" class="ui-select-trigger"><span>${label}</span><span class="chev">▾</span></button>
<div class="ui-dropdown">${searchBar}<div class="dd-list">${items}</div></div>
</div>`
}
function tagsHtml() {
return `<div class="ui-tags">${GROUPS.map((g, i) =>
`<button type="button" class="ui-tag ${i === tagOn ? 'is-on' : ''}" data-i="${i}">${g}</button>`
).join('')}</div>`
}
function tabsHtml() {
return `<div class="ui-tabs">${GROUPS.map((g, i) =>
`<button type="button" class="ui-tab ${i === tabOn ? 'is-on' : ''}" data-i="${i}">${g}</button>`
).join('')}</div>`
}
function bindSelect(root) {
root.querySelectorAll('.ui-select').forEach((box) => {
const kind = box.dataset.kind
box.querySelector('.ui-select-trigger').onclick = (e) => {
e.stopPropagation()
openSelect = openSelect === kind ? '' : kind
renderAll()
}
box.querySelectorAll('.dd-item').forEach((item) => {
item.onclick = (e) => {
e.stopPropagation()
if (kind === 'plain') selectVal = item.dataset.v
else selectSearchVal = item.dataset.v
openSelect = ''
renderAll()
}
})
})
}
function bindTagsTabs(root) {
root.querySelectorAll('.ui-tag').forEach((t) => {
t.onclick = () => { tagOn = Number(t.dataset.i); renderAll() }
})
root.querySelectorAll('.ui-tab').forEach((t) => {
t.onclick = () => { tabOn = Number(t.dataset.i); renderAll() }
})
}
function renderAll() {
const s = SCHEMES[active]
document.getElementById('schemeTitle').textContent = s.name
document.getElementById('schemeDesc').textContent = s.desc
document.getElementById('tokenList').innerHTML = s.tokens.map((t) => `<div>· ${t}</div>`).join('')
const panel = document.getElementById('specPanel')
panel.className = `panel scheme-${active}`
document.getElementById('demoSearch').innerHTML = searchHtml()
document.getElementById('demoSelect').innerHTML = selectHtml('plain')
document.getElementById('demoSelectSearch').innerHTML = selectHtml('search')
document.getElementById('demoTags').innerHTML = tagsHtml()
document.getElementById('demoTabs').innerHTML = tabsHtml()
bindSelect(panel)
bindTagsTabs(panel)
const phone = document.getElementById('phoneRoot')
phone.className = `phone-screen scheme-${active}`
document.getElementById('phoneToolbar').innerHTML =
`<div class="preview-stack">${searchHtml()}${tabsHtml()}</div>`
bindTagsTabs(document.getElementById('phoneToolbar'))
renderSchemeTabs()
renderCompare()
}
document.addEventListener('click', () => {
if (!openSelect) return
openSelect = ''
renderAll()
})
renderAll()
</script>
</body>
</html>