| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051 |
- <!DOCTYPE html>
- <html lang="zh-CN">
- <head>
- <meta charset="UTF-8" />
- <meta name="viewport" content="width=device-width, initial-scale=1" />
- <title>发起审批 · 下拉选择弹窗列表样式 Demo</title>
- <style>
- :root {
- --ink: #0f172a;
- --muted: #64748b;
- --line: #e2e8f0;
- --brand: #22c55e;
- --brand-dark: #16a34a;
- --brand-deep: #15803d;
- --brand-soft: #ecfdf5;
- --text: #0f291e;
- --text-muted: #5f7a6d;
- --border-mint: #d8f3e5;
- }
- * { 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:
- radial-gradient(900px 420px at 8% -8%, rgba(52, 211, 153, 0.16), transparent 55%),
- radial-gradient(700px 380px at 92% 0%, rgba(148, 163, 184, 0.14), transparent 50%),
- linear-gradient(180deg, #f8fffc 0%, #f4f7fb 50%, #f7f8fa 100%);
- line-height: 1.45;
- }
- .wrap {
- max-width: 1220px;
- margin: 0 auto;
- padding: 28px 18px 96px;
- }
- h1 {
- margin: 0 0 8px;
- font-size: 24px;
- letter-spacing: -0.02em;
- font-weight: 720;
- }
- .sub {
- margin: 0;
- max-width: 760px;
- color: var(--muted);
- font-size: 14px;
- }
- .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-deep); }
- .toolbar {
- display: flex;
- flex-wrap: wrap;
- gap: 8px;
- margin-bottom: 16px;
- }
- .chip {
- appearance: none;
- border: 1px solid var(--line);
- background: #fff;
- border-radius: 999px;
- padding: 8px 14px;
- font-size: 13px;
- font-weight: 650;
- color: #475569;
- cursor: pointer;
- font-family: inherit;
- }
- .chip.is-on {
- border-color: var(--brand);
- background: var(--brand-soft);
- color: var(--brand-deep);
- box-shadow: 0 0 0 3px rgba(34, 197, 94, 0.12);
- }
- .layout {
- display: grid;
- grid-template-columns: 1fr minmax(320px, 380px);
- gap: 22px;
- align-items: start;
- }
- @media (max-width: 960px) {
- .layout { grid-template-columns: 1fr; }
- }
- .schemes { display: grid; gap: 10px; }
- .scheme {
- appearance: none;
- width: 100%;
- text-align: left;
- border: 1px solid var(--line);
- background: #fff;
- border-radius: 16px;
- padding: 14px 16px;
- cursor: pointer;
- transition: border-color .15s, box-shadow .15s, background .15s;
- font-family: inherit;
- color: inherit;
- }
- .scheme:hover {
- border-color: #cbd5e1;
- box-shadow: 0 8px 24px rgba(15, 23, 42, 0.05);
- }
- .scheme.is-on {
- border-color: var(--brand);
- background: linear-gradient(180deg, #f0fdf4, #fff);
- box-shadow: 0 0 0 3px rgba(34, 197, 94, 0.18);
- }
- .scheme-top {
- display: flex;
- align-items: center;
- gap: 10px;
- margin-bottom: 4px;
- }
- .letter {
- width: 28px; height: 28px;
- border-radius: 8px;
- display: grid; place-items: center;
- font-size: 13px; font-weight: 750;
- background: #f1f5f9;
- color: #334155;
- flex-shrink: 0;
- }
- .scheme.is-on .letter {
- background: var(--brand-soft);
- color: var(--brand-deep);
- }
- .scheme h2 {
- margin: 0;
- font-size: 15px;
- font-weight: 680;
- }
- .scheme p {
- margin: 0;
- font-size: 12.5px;
- color: var(--muted);
- padding-left: 38px;
- }
- .tag {
- margin-left: auto;
- font-size: 10px;
- font-weight: 700;
- padding: 2px 8px;
- border-radius: 999px;
- background: #f1f5f9;
- color: #64748b;
- white-space: nowrap;
- }
- .tag.rec {
- background: var(--brand-soft);
- color: var(--brand-deep);
- }
- .tag.base {
- background: #fef3c7;
- color: #92400e;
- }
- /* —— 右侧手机 —— */
- .stage { position: sticky; top: 16px; }
- .phone {
- 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: #f1f5f9;
- height: 680px;
- display: flex;
- flex-direction: column;
- position: relative;
- }
- .phone-nav {
- height: 48px;
- display: flex;
- align-items: center;
- justify-content: center;
- background: #fff;
- border-bottom: 1px solid #f1f5f9;
- font-size: 15px;
- font-weight: 650;
- color: var(--text);
- position: relative;
- flex-shrink: 0;
- }
- .phone-nav .back {
- position: absolute;
- left: 14px;
- color: var(--text-muted);
- font-size: 13px;
- font-weight: 500;
- }
- .form-mock {
- flex: 1;
- padding: 12px;
- display: flex;
- flex-direction: column;
- gap: 10px;
- overflow: auto;
- }
- .field {
- background: #fff;
- border-radius: 12px;
- border: 1px solid #eef2f6;
- padding: 12px 14px;
- cursor: pointer;
- }
- .field:active { opacity: 0.92; }
- .field .lab {
- font-size: 12px;
- color: #94a3b8;
- margin-bottom: 6px;
- }
- .field .val {
- display: flex;
- align-items: center;
- justify-content: space-between;
- font-size: 14px;
- color: #334155;
- }
- .field .val.ph { color: #cbd5e1; }
- .field .chev { color: #cbd5e1; font-size: 12px; }
- /* 遮罩 + 底部弹窗 */
- .mask {
- position: absolute;
- inset: 0;
- background: rgba(15, 23, 42, 0.42);
- display: flex;
- align-items: flex-end;
- z-index: 5;
- }
- .sheet {
- width: 100%;
- height: 72%;
- background: #fff;
- border-radius: 18px 18px 0 0;
- display: flex;
- flex-direction: column;
- overflow: hidden;
- animation: sheetUp .22s ease-out;
- }
- @keyframes sheetUp {
- from { transform: translateY(18%); opacity: .6; }
- to { transform: translateY(0); opacity: 1; }
- }
- .sheet-hd {
- display: flex;
- align-items: center;
- justify-content: space-between;
- padding: 14px 16px;
- border-bottom: 1px solid var(--border-mint);
- flex-shrink: 0;
- }
- .sheet-hd .cancel {
- min-width: 48px;
- color: var(--text-muted);
- font-size: 14px;
- cursor: pointer;
- }
- .sheet-hd strong {
- font-size: 16px;
- font-weight: 700;
- color: var(--text);
- }
- .sheet-hd .ok {
- min-width: 48px;
- color: var(--brand);
- font-size: 14px;
- font-weight: 600;
- text-align: right;
- cursor: pointer;
- }
- .search {
- padding: 10px 12px;
- background: #fff;
- flex-shrink: 0;
- }
- .search-inner {
- display: flex;
- align-items: center;
- gap: 8px;
- height: 36px;
- padding: 0 12px;
- border-radius: 999px;
- background: #f3f4f6;
- color: #94a3b8;
- font-size: 13px;
- }
- .search-inner svg { width: 14px; height: 14px; flex-shrink: 0; }
- .pick-list {
- flex: 1;
- overflow: auto;
- -webkit-overflow-scrolling: touch;
- }
- .pick-item {
- display: flex;
- align-items: center;
- gap: 10px;
- cursor: pointer;
- -webkit-tap-highlight-color: transparent;
- user-select: none;
- }
- .pick-item:active { opacity: 0.88; }
- .pick-main { flex: 1; min-width: 0; }
- .pick-title {
- font-size: 15px;
- font-weight: 600;
- color: var(--text);
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
- }
- .pick-sub {
- margin-top: 2px;
- font-size: 12px;
- color: var(--text-muted);
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
- }
- .check {
- width: 20px;
- height: 20px;
- border-radius: 50%;
- border: 1.5px solid #cbd5e1;
- flex-shrink: 0;
- display: grid;
- place-items: center;
- background: #fff;
- transition: all .15s;
- }
- .check.sq { border-radius: 5px; }
- .pick-item.is-on .check {
- border-color: var(--brand);
- background: var(--brand);
- color: #fff;
- }
- .check svg { width: 12px; height: 12px; opacity: 0; }
- .pick-item.is-on .check svg { opacity: 1; }
- .avatar {
- width: 36px;
- height: 36px;
- border-radius: 50%;
- display: grid;
- place-items: center;
- font-size: 14px;
- font-weight: 700;
- color: var(--brand-deep);
- background: var(--brand-soft);
- flex-shrink: 0;
- }
- .badge {
- font-size: 10px;
- font-weight: 700;
- padding: 2px 7px;
- border-radius: 999px;
- background: #f1f5f9;
- color: #64748b;
- flex-shrink: 0;
- }
- .badge.ok {
- background: var(--brand-soft);
- color: var(--brand-deep);
- }
- /* ========== 方案 A:现状基线 ========== */
- .s-a .pick-list {
- padding: 12px;
- background: transparent;
- }
- .s-a .pick-item {
- margin-bottom: 10px;
- padding: 12px 14px;
- border-radius: 10px;
- background: transparent;
- }
- .s-a .pick-item:last-child { margin-bottom: 0; }
- /* ========== 方案 B:柔和卡片 ========== */
- .s-b .pick-list {
- padding: 12px;
- background: #f8fafc;
- }
- .s-b .pick-item {
- margin-bottom: 8px;
- padding: 12px 14px;
- border-radius: 12px;
- background: #fff;
- border: 1px solid #eef2f6;
- box-shadow: 0 1px 2px rgba(15, 23, 42, 0.03);
- }
- .s-b .pick-item:last-child { margin-bottom: 0; }
- .s-b .pick-item.is-on {
- border-color: #86efac;
- background: linear-gradient(180deg, #f0fdf4, #fff);
- box-shadow: 0 0 0 3px rgba(34, 197, 94, 0.12);
- }
- /* ========== 方案 C:分割线行 ========== */
- .s-c .pick-list {
- padding: 0;
- background: #fff;
- }
- .s-c .pick-item {
- padding: 14px 16px;
- border-bottom: 1px solid #f1f5f9;
- background: #fff;
- }
- .s-c .pick-item:last-child { border-bottom: none; }
- .s-c .pick-item.is-on {
- background: var(--brand-soft);
- }
- .s-c .pick-item.is-on .pick-title { color: var(--brand-deep); }
- /* ========== 方案 D:头像 + 右侧勾选 ========== */
- .s-d .pick-list {
- padding: 8px 12px 16px;
- background: #f8fafc;
- }
- .s-d .pick-item {
- margin-bottom: 8px;
- padding: 12px 14px;
- border-radius: 14px;
- background: #fff;
- border: 1px solid #eef2f6;
- flex-direction: row;
- }
- .s-d .pick-item .check { order: 3; margin-left: 4px; }
- .s-d .pick-item.is-on {
- border-color: #bbf7d0;
- background: #fff;
- }
- .s-d .pick-item.is-on .avatar {
- background: var(--brand);
- color: #fff;
- }
- /* ========== 方案 E:左侧色条 ========== */
- .s-e .pick-list {
- padding: 12px;
- background: #fff;
- }
- .s-e .pick-item {
- position: relative;
- margin-bottom: 8px;
- padding: 13px 14px 13px 16px;
- border-radius: 10px;
- background: #f8fafc;
- overflow: hidden;
- }
- .s-e .pick-item::before {
- content: "";
- position: absolute;
- left: 0; top: 10px; bottom: 10px;
- width: 3px;
- border-radius: 0 3px 3px 0;
- background: transparent;
- transition: background .15s;
- }
- .s-e .pick-item.is-on {
- background: var(--brand-soft);
- }
- .s-e .pick-item.is-on::before {
- background: var(--brand);
- }
- /* ========== 方案 F:紧凑密排 ========== */
- .s-f .pick-list {
- padding: 4px 0;
- background: #fff;
- }
- .s-f .pick-item {
- padding: 10px 16px;
- gap: 8px;
- border-bottom: 1px solid #f8fafc;
- }
- .s-f .pick-title { font-size: 14px; font-weight: 550; }
- .s-f .pick-sub { font-size: 11px; }
- .s-f .check { width: 18px; height: 18px; }
- .s-f .pick-item.is-on { background: #f0fdf4; }
- /* ========== 方案 G:整行高亮 + 右勾 ========== */
- .s-g .pick-list {
- padding: 12px;
- background: #f1f5f9;
- }
- .s-g .pick-item {
- margin-bottom: 8px;
- padding: 14px 14px;
- border-radius: 12px;
- background: #fff;
- border: 1.5px solid transparent;
- }
- .s-g .pick-item .check {
- order: 3;
- margin-left: auto;
- border: none;
- background: #e2e8f0;
- color: transparent;
- }
- .s-g .pick-item.is-on {
- border-color: var(--brand);
- background: #fff;
- box-shadow: 0 4px 14px rgba(22, 163, 74, 0.12);
- }
- .s-g .pick-item.is-on .check {
- background: var(--brand);
- color: #fff;
- }
- .s-g .pick-item.is-on .check svg { opacity: 1; }
- .s-g .pick-item:not(.is-on) .check svg { opacity: 0; }
- /* ========== 方案 H:双行元信息卡 ========== */
- .s-h .pick-list {
- padding: 12px;
- background: linear-gradient(180deg, #f0fdf4 0%, #f8fafc 40%);
- }
- .s-h .pick-item {
- margin-bottom: 10px;
- padding: 14px;
- border-radius: 14px;
- background: rgba(255,255,255,0.95);
- border: 1px solid #e2e8f0;
- align-items: flex-start;
- }
- .s-h .pick-item.is-on {
- border-color: #86efac;
- box-shadow: 0 8px 20px rgba(22, 163, 74, 0.1);
- }
- .s-h .meta-row {
- display: flex;
- align-items: center;
- gap: 6px;
- margin-top: 8px;
- flex-wrap: wrap;
- }
- .s-h .meta-pill {
- font-size: 10px;
- font-weight: 650;
- padding: 2px 8px;
- border-radius: 999px;
- background: #f1f5f9;
- color: #64748b;
- }
- .s-h .pick-item.is-on .meta-pill.pri {
- background: var(--brand-soft);
- color: var(--brand-deep);
- }
- /* ========== 方案 I:单选点选(无复选框感) ========== */
- .s-i .pick-list {
- padding: 8px 12px 16px;
- background: #fff;
- }
- .s-i .pick-item {
- margin-bottom: 6px;
- padding: 13px 14px;
- border-radius: 12px;
- background: #f8fafc;
- border: 1px solid transparent;
- }
- .s-i .pick-item .check {
- width: 18px;
- height: 18px;
- border-width: 2px;
- }
- .s-i .pick-item.is-on {
- background: var(--brand-soft);
- border-color: #86efac;
- }
- .s-i .pick-item.is-on .pick-title { color: var(--brand-deep); }
- .s-i .pick-item.is-on .check {
- box-shadow: 0 0 0 3px rgba(34, 197, 94, 0.18);
- }
- /* ========== 方案 J:分组标题 ========== */
- .s-j .pick-list {
- padding: 0 0 12px;
- background: #f8fafc;
- }
- .s-j .group-title {
- padding: 10px 16px 6px;
- font-size: 12px;
- font-weight: 650;
- color: #94a3b8;
- letter-spacing: 0.02em;
- }
- .s-j .group-block {
- margin: 0 12px 10px;
- background: #fff;
- border-radius: 12px;
- border: 1px solid #eef2f6;
- overflow: hidden;
- }
- .s-j .pick-item {
- padding: 12px 14px;
- border-bottom: 1px solid #f8fafc;
- }
- .s-j .pick-item:last-child { border-bottom: none; }
- .s-j .pick-item.is-on { background: #f0fdf4; }
- .stage-meta {
- margin-top: 14px;
- padding: 12px 14px;
- background: rgba(255,255,255,.92);
- border: 1px solid var(--line);
- border-radius: 14px;
- font-size: 13px;
- color: var(--muted);
- }
- .stage-meta strong { color: var(--brand-deep); }
- .stage-meta .why {
- margin-top: 6px;
- font-size: 12.5px;
- line-height: 1.55;
- }
- .hint {
- margin-top: 8px;
- font-size: 12px;
- color: #94a3b8;
- }
- </style>
- </head>
- <body>
- <div class="wrap">
- <h1>发起审批 · 下拉选择弹窗列表样式</h1>
- <p class="sub">
- 针对 OA 发起页中人员 / 部门 / 岗位 / 收款账户 / 关联审批等底部弹窗列表,提供多套视觉方案。
- 左侧切换方案,右侧手机预览可点选体验;上方可切换「单选 / 多选」与「数据场景」。
- </p>
- <div class="note">
- Token 对齐 mobile-h5「薄荷微风」:主色 <strong>#22c55e</strong>、软底 <strong>#ecfdf5</strong>、正文 <strong>#0f291e</strong>。
- 当前线上基线为方案 A(透明 cell + 间距,选中态弱)。
- </div>
- <div class="toolbar">
- <button type="button" class="chip is-on" data-mode="multi">多选</button>
- <button type="button" class="chip" data-mode="single">单选</button>
- <span style="width:8px"></span>
- <button type="button" class="chip is-on" data-scene="person">人员</button>
- <button type="button" class="chip" data-scene="dept">部门</button>
- <button type="button" class="chip" data-scene="option">简单选项</button>
- <button type="button" class="chip" data-scene="related">关联审批</button>
- </div>
- <div class="layout">
- <div class="schemes" id="schemes"></div>
- <div class="stage">
- <div class="phone">
- <div class="phone-screen" id="phone">
- <div class="phone-nav">
- <span class="back">‹</span>
- <span>发起审批</span>
- </div>
- <div class="form-mock">
- <div class="field" id="openField">
- <div class="lab" id="fieldLab">审批人</div>
- <div class="val ph" id="fieldVal">
- <span>请选择</span>
- <span class="chev">›</span>
- </div>
- </div>
- <div class="field">
- <div class="lab">事由</div>
- <div class="val ph"><span>请输入</span></div>
- </div>
- <p class="hint">点击上方字段打开选择弹窗。切换方案时弹窗保持打开,便于对比。</p>
- </div>
- <div class="mask" id="mask" hidden>
- <div class="sheet" id="sheet">
- <div class="sheet-hd">
- <span class="cancel" id="btnCancel">取消</span>
- <strong id="sheetTitle">选择人员</strong>
- <span class="ok" id="btnOk">确定</span>
- </div>
- <div class="search">
- <div class="search-inner">
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <circle cx="11" cy="11" r="7"/><path d="M20 20l-3-3"/>
- </svg>
- <span id="searchPh">搜索姓名或工号</span>
- </div>
- </div>
- <div class="pick-list" id="pickList"></div>
- </div>
- </div>
- </div>
- </div>
- <div class="stage-meta">
- 当前方案:<strong id="metaName">A · 现状基线</strong>
- <div class="why" id="metaWhy"></div>
- </div>
- </div>
- </div>
- </div>
- <script>
- const SCHEMES = [
- {
- id: 'a',
- letter: 'A',
- name: '现状基线',
- tag: '当前',
- tagClass: 'base',
- desc: '透明 cell + 10px 间距,与现网 oa-pick__list 接近;选中态几乎不可见。',
- why: '层级弱、选中反馈不足,长列表时条目边界不清晰。适合作为改造前对照。'
- },
- {
- id: 'b',
- letter: 'B',
- name: '柔和白卡片',
- tag: '',
- tagClass: '',
- desc: '灰底上白卡片 + 浅描边;选中后薄荷绿描边与外环光。',
- why: '卡片边界清晰、选中反馈强,适配人员/部门/岗位等通用列表,落地成本低。'
- },
- {
- id: 'c',
- letter: 'C',
- name: '分割线行表',
- tag: '已采用',
- tagClass: 'rec',
- desc: '白底连续行 + 发丝分割线;选中整行浅绿底。',
- why: '信息密度高、偏系统原生感;适合简单选项、枚举类单选。当前已落地到 OA 选择弹窗。'
- },
- {
- id: 'd',
- letter: 'D',
- name: '头像 + 右勾选',
- tag: '人员',
- tagClass: 'rec',
- desc: '左侧头像字、中部姓名/副标题、右侧圆形勾选;选中头像变主色。',
- why: '专为人员/抄送场景;身份识别强。部门/简单选项场景可隐藏头像。'
- },
- {
- id: 'e',
- letter: 'E',
- name: '左侧色条',
- tag: '',
- tagClass: '',
- desc: '灰底圆角行,选中出现左侧品牌色竖条 + 软绿底。',
- why: '选中态含蓄但不丢识别度,适合希望「少边框、少阴影」的清爽风。'
- },
- {
- id: 'f',
- letter: 'F',
- name: '紧凑密排',
- tag: '长列表',
- tagClass: '',
- desc: '更小行高与字号,弱分割线;一屏展示更多选项。',
- why: '人员很多、或枚举项较长时优先;牺牲一点呼吸感换效率。'
- },
- {
- id: 'g',
- letter: 'G',
- name: '描边高亮 + 右勾',
- tag: '',
- tagClass: '',
- desc: '灰底面板内白卡片;选中品牌描边 + 阴影,勾选移到右侧。',
- why: '选中态最醒目,适合「必须看清已选」的多选场景(审批人、抄送人)。'
- },
- {
- id: 'h',
- letter: 'H',
- name: '双行元信息卡',
- tag: '关联单',
- tagClass: 'rec',
- desc: '标题 + 副标题 + 底部 pill 元数据(单号/状态/时间)。',
- why: '专为关联审批、收款账户等「需要多字段」的选项;普通短列表略重。'
- },
- {
- id: 'i',
- letter: 'I',
- name: '轻量点选',
- tag: '单选友好',
- tagClass: '',
- desc: '浅灰圆角块,选中软绿底 + 描边;勾选圈带外环光。',
- why: '适合部门、岗位、简单下拉等单选为主的字段;多选也可用。'
- },
- {
- id: 'j',
- letter: 'J',
- name: '分组块列表',
- tag: '',
- tagClass: '',
- desc: '按字母/部门分组,组内白卡片连续行。',
- why: '选项可按组织/首字母归类时体验最好;需后端或前端分组能力。'
- }
- ]
- const SCENES = {
- person: {
- title: '选择人员',
- lab: '审批人',
- search: '搜索姓名或工号',
- groups: [
- {
- title: '研发中心',
- items: [
- { id: '1', title: '张晓明', sub: '工号 · 10086', avatar: '张', meta: ['研发', '在职'] },
- { id: '2', title: '李思远', sub: '工号 · 10087', avatar: '李', meta: ['研发', '在职'] },
- { id: '3', title: '王芳', sub: '工号 · 10091', avatar: '王', meta: ['产品', '在职'] }
- ]
- },
- {
- title: '职能支持',
- items: [
- { id: '4', title: '陈静', sub: '工号 · 20012', avatar: '陈', meta: ['人事', '在职'] },
- { id: '5', title: '赵磊', sub: '工号 · 20018', avatar: '赵', meta: ['财务', '在职'] }
- ]
- }
- ]
- },
- dept: {
- title: '选择部门',
- lab: '所属部门',
- search: '搜索部门名称',
- groups: [
- {
- title: '组织架构',
- items: [
- { id: 'd1', title: '中科兴皖 / 研发中心', sub: '一级部门', avatar: '研', meta: ['12 人'] },
- { id: 'd2', title: '中科兴皖 / 产品中心', sub: '一级部门', avatar: '产', meta: ['8 人'] },
- { id: 'd3', title: '中科兴皖 / 运营中心', sub: '一级部门', avatar: '运', meta: ['15 人'] },
- { id: 'd4', title: '中科兴皖 / 财务部', sub: '一级部门', avatar: '财', meta: ['5 人'] }
- ]
- }
- ]
- },
- option: {
- title: '选择类型',
- lab: '出差类型',
- search: '搜索选项',
- groups: [
- {
- title: '常用',
- items: [
- { id: 'o1', title: '市内出差', sub: '当日往返', avatar: '市', meta: [] },
- { id: 'o2', title: '省内出差', sub: '需填写行程', avatar: '省', meta: [] },
- { id: 'o3', title: '省外出差', sub: '需关联预算', avatar: '外', meta: [] },
- { id: 'o4', title: '海外出差', sub: '需提前审批', avatar: '海', meta: [] }
- ]
- }
- ]
- },
- related: {
- title: '关联审批',
- lab: '关联单据',
- search: '搜索单号或标题',
- groups: [
- {
- title: '最近',
- items: [
- { id: 'r1', title: '出差申请 · 上海客户拜访', sub: 'OA-20260320-018', avatar: '差', meta: ['已通过', '3/18'] },
- { id: 'r2', title: '费用报销 · 差旅费', sub: 'OA-20260318-042', avatar: '报', meta: ['审批中', '3/18'] },
- { id: 'r3', title: '用章申请 · 合同用印', sub: 'OA-20260315-009', avatar: '章', meta: ['已通过', '3/15'] }
- ]
- }
- ]
- }
- }
- const state = {
- scheme: 'c',
- mode: 'multi',
- scene: 'person',
- selected: new Set(['1', '2']),
- open: true
- }
- const checkSvg = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3"><path d="M5 12l5 5L20 7"/></svg>`
- function flatItems() {
- return SCENES[state.scene].groups.flatMap((g) => g.items)
- }
- function renderSchemes() {
- const box = document.getElementById('schemes')
- box.innerHTML = SCHEMES.map((s) => `
- <button type="button" class="scheme ${state.scheme === s.id ? 'is-on' : ''}" data-id="${s.id}">
- <div class="scheme-top">
- <span class="letter">${s.letter}</span>
- <h2>${s.name}</h2>
- ${s.tag ? `<span class="tag ${s.tagClass}">${s.tag}</span>` : ''}
- </div>
- <p>${s.desc}</p>
- </button>
- `).join('')
- box.querySelectorAll('.scheme').forEach((el) => {
- el.addEventListener('click', () => {
- state.scheme = el.dataset.id
- apply()
- })
- })
- }
- function renderList() {
- const list = document.getElementById('pickList')
- const scene = SCENES[state.scene]
- const sq = state.mode === 'multi' ? 'sq' : ''
- const showAvatar = state.scheme === 'd' || state.scene === 'person' && state.scheme === 'h'
- const useGroups = state.scheme === 'j'
- if (useGroups) {
- list.innerHTML = scene.groups.map((g) => `
- <div class="group-title">${g.title}</div>
- <div class="group-block">
- ${g.items.map((it) => itemHtml(it, sq, true)).join('')}
- </div>
- `).join('')
- } else if (state.scheme === 'h') {
- list.innerHTML = flatItems().map((it) => `
- <div class="pick-item ${state.selected.has(it.id) ? 'is-on' : ''}" data-id="${it.id}">
- <span class="check ${sq}">${checkSvg}</span>
- <div class="pick-main">
- <div class="pick-title">${it.title}</div>
- <div class="pick-sub">${it.sub}</div>
- ${it.meta?.length ? `<div class="meta-row">${it.meta.map((m, i) => `<span class="meta-pill ${i === 0 ? 'pri' : ''}">${m}</span>`).join('')}</div>` : ''}
- </div>
- </div>
- `).join('')
- } else {
- list.innerHTML = flatItems().map((it) => itemHtml(it, sq, state.scheme === 'd')).join('')
- }
- list.querySelectorAll('.pick-item').forEach((el) => {
- el.addEventListener('click', () => toggle(el.dataset.id))
- })
- }
- function itemHtml(it, sq, withAvatar) {
- return `
- <div class="pick-item ${state.selected.has(it.id) ? 'is-on' : ''}" data-id="${it.id}">
- <span class="check ${sq}">${checkSvg}</span>
- ${withAvatar ? `<span class="avatar">${it.avatar || it.title.slice(0, 1)}</span>` : ''}
- <div class="pick-main">
- <div class="pick-title">${it.title}</div>
- ${it.sub ? `<div class="pick-sub">${it.sub}</div>` : ''}
- </div>
- </div>
- `
- }
- function toggle(id) {
- if (state.mode === 'single') {
- state.selected = new Set([id])
- } else {
- if (state.selected.has(id)) state.selected.delete(id)
- else state.selected.add(id)
- }
- updateFieldVal()
- renderList()
- }
- function updateFieldVal() {
- const el = document.getElementById('fieldVal')
- const map = new Map(flatItems().map((x) => [x.id, x.title]))
- const names = [...state.selected].map((id) => map.get(id)).filter(Boolean)
- if (!names.length) {
- el.className = 'val ph'
- el.innerHTML = `<span>请选择</span><span class="chev">›</span>`
- } else {
- el.className = 'val'
- el.innerHTML = `<span>${names.join('、')}</span><span class="chev">›</span>`
- }
- }
- function apply() {
- const phone = document.getElementById('phone')
- const sheet = document.getElementById('sheet')
- const scene = SCENES[state.scene]
- const scheme = SCHEMES.find((s) => s.id === state.scheme)
- phone.className = 'phone-screen s-' + state.scheme
- sheet.className = 'sheet s-' + state.scheme
- document.getElementById('sheetTitle').textContent = scene.title
- document.getElementById('fieldLab').textContent = scene.lab
- document.getElementById('searchPh').textContent = scene.search
- document.getElementById('metaName').textContent = `${scheme.letter} · ${scheme.name}`
- document.getElementById('metaWhy').textContent = scheme.why
- document.getElementById('mask').hidden = !state.open
- document.getElementById('btnOk').style.visibility = state.mode === 'single' ? 'hidden' : 'visible'
- renderSchemes()
- renderList()
- updateFieldVal()
- }
- document.getElementById('openField').addEventListener('click', () => {
- state.open = true
- apply()
- })
- document.getElementById('btnCancel').addEventListener('click', () => {
- state.open = false
- apply()
- })
- document.getElementById('btnOk').addEventListener('click', () => {
- state.open = false
- apply()
- })
- document.getElementById('mask').addEventListener('click', (e) => {
- if (e.target.id === 'mask') {
- state.open = false
- apply()
- }
- })
- document.querySelectorAll('.chip[data-mode]').forEach((el) => {
- el.addEventListener('click', () => {
- document.querySelectorAll('.chip[data-mode]').forEach((c) => c.classList.remove('is-on'))
- el.classList.add('is-on')
- state.mode = el.dataset.mode
- if (state.mode === 'single' && state.selected.size > 1) {
- const first = [...state.selected][0]
- state.selected = new Set(first ? [first] : [])
- }
- apply()
- })
- })
- document.querySelectorAll('.chip[data-scene]').forEach((el) => {
- el.addEventListener('click', () => {
- document.querySelectorAll('.chip[data-scene]').forEach((c) => c.classList.remove('is-on'))
- el.classList.add('is-on')
- state.scene = el.dataset.scene
- const ids = flatItems().map((x) => x.id)
- state.selected = new Set(ids.slice(0, state.mode === 'multi' ? 2 : 1))
- state.open = true
- apply()
- })
- })
- apply()
- </script>
- </body>
- </html>
|