Files
shanxia2.0/docs/demos/consumable-merge-plan-b-demo.html
T
2026-05-27 16:52:46 +08:00

954 lines
29 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.0" />
<title>方案 B · 耗材管理融合页 UI 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;
--bg-page: #f8fafc;
--surface: #ffffff;
--table-head: #f8fafc;
--row-alt: #fafafa;
--row-hover: #f1f5f9;
--shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
--radius: 10px;
--sidebar-w: 220px;
--header-h: 56px;
--danger: #ef4444;
--danger-bg: #fef2f2;
--warning: #d97706;
--warning-bg: #fffbeb;
}
* {
box-sizing: border-box;
}
body {
margin: 0;
font-family: Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
font-size: 14px;
line-height: 1.5;
color: var(--text);
background: var(--bg-page);
}
.layout {
display: flex;
min-height: 100vh;
}
.sidebar {
width: var(--sidebar-w);
flex-shrink: 0;
background: var(--surface);
border-right: 1px solid var(--border);
padding: 20px 14px;
}
.logo {
font-size: 15px;
font-weight: 700;
padding: 0 8px 16px;
margin-bottom: 8px;
border-bottom: 1px solid var(--border);
}
.logo span {
color: var(--primary);
}
.nav-item {
display: block;
padding: 8px 12px;
border-radius: 8px;
color: var(--text-muted);
font-size: 13px;
margin-bottom: 2px;
}
.nav-item.is-active {
background: var(--primary-bg);
color: var(--primary-dark);
font-weight: 600;
}
.nav-group {
font-size: 11px;
font-weight: 700;
color: var(--text-muted);
padding: 12px 12px 6px;
letter-spacing: 0.02em;
}
.main {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
}
.topbar {
height: var(--header-h);
background: var(--surface);
border-bottom: 1px solid var(--border);
display: flex;
align-items: center;
padding: 0 24px;
font-size: 13px;
color: var(--text-muted);
}
.topbar strong {
color: var(--text);
font-weight: 600;
}
.content {
flex: 1;
padding: 24px;
overflow: auto;
}
.demo-banner {
background: linear-gradient(90deg, #ecfdf5, #f0fdf4);
border: 1px solid rgba(16, 185, 129, 0.25);
border-radius: var(--radius);
padding: 12px 16px;
margin-bottom: 16px;
font-size: 13px;
color: var(--text);
}
.demo-banner strong {
color: var(--primary-dark);
}
.demo-banner code {
background: rgba(255, 255, 255, 0.7);
padding: 1px 6px;
border-radius: 4px;
font-size: 12px;
}
.panel {
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius);
box-shadow: var(--shadow);
}
.panel-top {
padding: 10px 16px;
border-bottom: 1px solid var(--border);
display: flex;
align-items: center;
justify-content: space-between;
}
.panel-top h2 {
margin: 0;
font-size: 14px;
font-weight: 600;
display: flex;
align-items: center;
gap: 8px;
}
.panel-top h2::before {
content: '';
width: 4px;
height: 14px;
border-radius: 999px;
background: var(--primary);
}
.panel-top .badge-demo {
font-size: 11px;
font-weight: 600;
color: var(--primary-dark);
background: var(--primary-bg);
border: 1px solid rgba(16, 185, 129, 0.2);
padding: 2px 8px;
border-radius: 6px;
}
/* 一级 Tab:实体类型 */
.tabs-l1 {
display: flex;
gap: 0;
padding: 0 16px;
border-bottom: 1px solid var(--border);
background: var(--surface);
}
.tabs-l1 button {
appearance: none;
border: none;
background: transparent;
font-family: inherit;
font-size: 14px;
font-weight: 500;
color: var(--text-muted);
padding: 12px 20px;
cursor: pointer;
position: relative;
margin-bottom: -1px;
}
.tabs-l1 button:hover {
color: var(--primary-dark);
}
.tabs-l1 button.is-active {
color: var(--primary-dark);
font-weight: 600;
}
.tabs-l1 button.is-active::after {
content: '';
position: absolute;
left: 0;
right: 0;
bottom: 0;
height: 2px;
background: var(--primary);
border-radius: 2px 2px 0 0;
}
/* 二级 Tab:仅「耗材」下 */
.sub-tabs-wrap {
padding: 12px 16px 0;
background: var(--surface);
border-bottom: 1px solid var(--border);
}
.sub-tabs-wrap[hidden] {
display: none !important;
}
.sub-tabs-label {
font-size: 11px;
color: var(--text-muted);
margin-bottom: 8px;
}
.tabs-l2 {
display: inline-flex;
gap: 4px;
padding: 3px;
background: var(--table-head);
border: 1px solid var(--border);
border-radius: 8px;
margin-bottom: 12px;
}
.tabs-l2 button {
appearance: none;
border: none;
background: transparent;
font-family: inherit;
font-size: 13px;
font-weight: 500;
color: var(--text-muted);
padding: 6px 14px;
border-radius: 6px;
cursor: pointer;
}
.tabs-l2 button:hover {
color: var(--text);
}
.tabs-l2 button.is-active {
background: var(--surface);
color: var(--primary-dark);
font-weight: 600;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.06);
}
.tab-panel {
display: none;
}
.tab-panel.is-active {
display: block;
}
.filter-block {
padding: 12px 16px 10px;
border-bottom: 1px solid var(--border);
}
.filter-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 10px 14px;
margin-bottom: 10px;
}
@media (max-width: 1100px) {
.filter-grid {
grid-template-columns: repeat(2, 1fr);
}
}
.filter-field label {
display: block;
font-size: 11px;
font-weight: 500;
color: var(--text-muted);
margin-bottom: 4px;
}
.input,
.select {
width: 100%;
height: 36px;
border: 1px solid var(--border);
border-radius: 8px;
padding: 0 10px;
font-size: 14px;
font-family: inherit;
color: var(--text);
background: var(--surface);
}
.input:focus,
.select:focus {
outline: none;
border-color: var(--primary);
box-shadow: 0 0 0 2px rgba(16, 185, 129, 0.2);
}
.filter-actions-row {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
flex-wrap: wrap;
}
.filter-hint {
font-size: 12px;
color: var(--text-muted);
}
.filter-actions-end {
display: flex;
gap: 8px;
}
.btn {
display: inline-flex;
align-items: center;
gap: 6px;
height: 36px;
padding: 0 14px;
border-radius: 8px;
font-size: 14px;
font-weight: 500;
font-family: inherit;
cursor: pointer;
border: 1px solid var(--border);
background: var(--surface);
color: var(--text);
}
.btn:hover {
background: var(--row-alt);
}
.btn-primary {
background: var(--primary);
border-color: var(--primary);
color: #fff;
}
.btn-primary:hover {
background: var(--primary-dark);
border-color: var(--primary-dark);
}
.btn-danger {
color: var(--danger);
border-color: #fecaca;
}
.btn-danger:hover {
background: var(--danger-bg);
}
.btn:disabled {
opacity: 0.45;
cursor: not-allowed;
}
.list-toolbar {
padding: 10px 16px;
background: var(--table-head);
border-bottom: 1px solid var(--border);
}
.list-toolbar-row {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 8px;
}
.list-toolbar-selected {
font-size: 13px;
color: var(--text-muted);
margin-right: 4px;
}
.sel-num {
color: var(--primary-dark);
font-weight: 700;
}
.list-body {
padding: 0;
}
.table-wrap {
overflow-x: auto;
}
table.list-table {
width: 100%;
border-collapse: collapse;
font-size: 13px;
}
table.list-table th,
table.list-table td {
padding: 10px 12px;
text-align: center;
border-bottom: 1px solid var(--border);
white-space: nowrap;
}
table.list-table th {
background: var(--table-head);
color: var(--text-muted);
font-weight: 600;
font-size: 12px;
}
table.list-table tbody tr:nth-child(even) {
background: var(--row-alt);
}
table.list-table tbody tr:hover {
background: var(--row-hover);
}
.tag {
display: inline-block;
padding: 2px 8px;
border-radius: 4px;
font-size: 12px;
font-weight: 500;
}
.tag.success {
background: var(--primary-bg);
color: var(--primary-dark);
}
.tag.danger {
background: var(--danger-bg);
color: var(--danger);
}
.tag.warning {
background: var(--warning-bg);
color: var(--warning);
}
.tag.info {
background: #f1f5f9;
color: #64748b;
}
.tag.primary {
background: #eff6ff;
color: #2563eb;
}
.btn-op {
height: 28px;
padding: 0 8px;
font-size: 12px;
border-radius: 6px;
border: 1px solid var(--border);
background: var(--surface);
cursor: pointer;
color: var(--text);
}
.btn-op:hover {
border-color: var(--primary);
color: var(--primary-dark);
}
.pagination {
display: flex;
align-items: center;
justify-content: flex-end;
gap: 12px;
padding: 12px 16px;
border-top: 1px solid var(--border);
font-size: 13px;
color: var(--text-muted);
}
.pager-btns {
display: flex;
gap: 4px;
}
.pager-btns button {
min-width: 32px;
height: 32px;
border: 1px solid var(--border);
background: var(--surface);
border-radius: 6px;
cursor: pointer;
font-size: 13px;
}
.pager-btns button.is-active {
background: var(--primary);
border-color: var(--primary);
color: #fff;
}
.structure-hint {
margin: 16px;
padding: 12px 14px;
background: #f8fafc;
border: 1px dashed var(--border);
border-radius: 8px;
font-size: 12px;
color: var(--text-muted);
line-height: 1.6;
}
.structure-hint b {
color: var(--text);
}
.ic {
width: 14px;
height: 14px;
display: inline-block;
vertical-align: -2px;
}
</style>
</head>
<body>
<div class="layout">
<aside class="sidebar">
<div class="logo">中科兴皖 <span>管理端</span></div>
<div class="nav-group">生产管理</div>
<div class="nav-item">工艺管理</div>
<div class="nav-item is-active">耗材管理</div>
<div class="nav-item" style="opacity: 0.45; text-decoration: line-through">耗材包管理</div>
<div class="nav-item">试剂管理</div>
<div class="nav-item">生产工单</div>
</aside>
<div class="main">
<header class="topbar">
生产管理 / <strong>耗材管理</strong>
<span style="margin-left: auto; font-size: 12px">方案 B 静态 Demo · 非真实数据</span>
</header>
<main class="content">
<div class="demo-banner">
<strong>方案 B:两级 Tab</strong> — 一级区分「耗材 / 耗材包」;仅在「耗材」下出现二级 Tab「耗材列表 /
变动记录」。侧栏合并后仅保留一个菜单「耗材管理」(灰线划掉的为将移除的菜单项示意)。
用浏览器直接打开本文件即可预览,路径:<code>docs/demos/consumable-merge-plan-b-demo.html</code>
</div>
<section class="panel" id="main-panel">
<div class="panel-top">
<h2>耗材管理</h2>
<span class="badge-demo">融合页 · 方案 B</span>
</div>
<!-- 一级 Tab -->
<div class="tabs-l1" role="tablist" aria-label="实体类型">
<button type="button" class="is-active" data-l1="consumable" role="tab" aria-selected="true">
耗材
</button>
<button type="button" data-l1="pack" role="tab" aria-selected="false">耗材包</button>
</div>
<!-- 二级 Tab(仅耗材) -->
<div class="sub-tabs-wrap" id="sub-tabs-wrap">
<div class="sub-tabs-label">耗材视图</div>
<div class="tabs-l2" role="tablist" aria-label="耗材子视图">
<button type="button" class="is-active" data-l2="list" role="tab">耗材列表</button>
<button type="button" data-l2="logs" role="tab">变动记录</button>
</div>
</div>
<div class="structure-hint" id="structure-hint">
当前结构:<b>一级「耗材」</b> → <b>二级「耗材列表」</b>(筛选 + 工具栏 + 表格 + 分页)
</div>
<!-- ========== 耗材 · 列表 ========== -->
<div class="tab-panel is-active" id="panel-consumable-list">
<div class="filter-block">
<div class="filter-grid">
<div class="filter-field">
<label>关键词</label>
<input class="input" placeholder="耗材编码/名称" />
</div>
<div class="filter-field">
<label>状态</label>
<select class="select">
<option>全部</option>
<option>启用</option>
<option>停用</option>
</select>
</div>
</div>
<div class="filter-actions-row">
<span class="filter-hint">按编码、名称模糊查询,可按状态筛选。</span>
<div class="filter-actions-end">
<button type="button" class="btn">重置</button>
<button type="button" class="btn btn-primary">搜索</button>
</div>
</div>
</div>
<div class="list-toolbar">
<div class="list-toolbar-row">
<span class="list-toolbar-selected">已选 <span class="sel-num">0</span> 项</span>
<button type="button" class="btn btn-primary">新增耗材</button>
<button type="button" class="btn">批量入库</button>
<button type="button" class="btn">批量出库</button>
<button type="button" class="btn btn-danger" disabled>批量删除</button>
</div>
</div>
<div class="list-body">
<div class="table-wrap">
<table class="list-table">
<thead>
<tr>
<th><input type="checkbox" /></th>
<th>序号</th>
<th>耗材编码</th>
<th>耗材名称</th>
<th>规格型号</th>
<th>当前库存</th>
<th>库存状态</th>
<th>状态</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr>
<td><input type="checkbox" /></td>
<td>1</td>
<td>HC-2024-001</td>
<td>细胞培养瓶 25cm²</td>
<td>25cm²</td>
<td>128</td>
<td><span class="tag success">正常</span></td>
<td><span class="tag success">启用</span></td>
<td>
<button class="btn-op">入库</button>
<button class="btn-op">出库</button>
<button class="btn-op">更多</button>
</td>
</tr>
<tr>
<td><input type="checkbox" /></td>
<td>2</td>
<td>HC-2024-018</td>
<td>无菌离心管 15mL</td>
<td>15mL</td>
<td>12</td>
<td><span class="tag danger">库存预警</span></td>
<td><span class="tag success">启用</span></td>
<td>
<button class="btn-op">入库</button>
<button class="btn-op">出库</button>
<button class="btn-op">更多</button>
</td>
</tr>
<tr>
<td><input type="checkbox" /></td>
<td>3</td>
<td>HC-2023-092</td>
<td>胰酶-EDTA 0.25%</td>
<td>100mL</td>
<td>45</td>
<td><span class="tag success">正常</span></td>
<td><span class="tag info">停用</span></td>
<td>
<button class="btn-op">入库</button>
<button class="btn-op">出库</button>
<button class="btn-op">更多</button>
</td>
</tr>
</tbody>
</table>
</div>
<div class="pagination">
<span>共 86 条</span>
<div class="pager-btns">
<button type="button">‹</button>
<button type="button" class="is-active">1</button>
<button type="button">2</button>
<button type="button">›</button>
</div>
</div>
</div>
</div>
<!-- ========== 耗材 · 变动记录 ========== -->
<div class="tab-panel" id="panel-consumable-logs">
<div class="filter-block">
<div class="filter-grid">
<div class="filter-field">
<label>关键词</label>
<input class="input" placeholder="耗材编码/名称" />
</div>
<div class="filter-field">
<label>变动类型</label>
<select class="select">
<option>全部</option>
<option>入库</option>
<option>出库</option>
</select>
</div>
<div class="filter-field">
<label>时间范围</label>
<input class="input" placeholder="开始 — 结束" />
</div>
</div>
<div class="filter-actions-row">
<span class="filter-hint">按耗材编码/名称、变动类型和时间范围查询变动记录。</span>
<div class="filter-actions-end">
<button type="button" class="btn">重置</button>
<button type="button" class="btn btn-primary">搜索</button>
</div>
</div>
</div>
<div class="list-body">
<div class="table-wrap">
<table class="list-table">
<thead>
<tr>
<th>序号</th>
<th>耗材编码</th>
<th>耗材名称</th>
<th>变动类型</th>
<th>变动数量</th>
<th>变动后库存</th>
<th>操作人</th>
<th>变动时间</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>HC-2024-001</td>
<td>细胞培养瓶 25cm²</td>
<td><span class="tag success">入库</span></td>
<td>+50</td>
<td>128</td>
<td>张三</td>
<td>2026-05-26 14:32</td>
</tr>
<tr>
<td>2</td>
<td>HC-2024-018</td>
<td>无菌离心管 15mL</td>
<td><span class="tag warning">出库</span></td>
<td>-20</td>
<td>12</td>
<td>李四</td>
<td>2026-05-25 09:15</td>
</tr>
</tbody>
</table>
</div>
<div class="pagination">
<span>共 312 条</span>
<div class="pager-btns">
<button type="button">‹</button>
<button type="button" class="is-active">1</button>
<button type="button">2</button>
<button type="button">›</button>
</div>
</div>
</div>
</div>
<!-- ========== 耗材包(无二级 Tab) ========== -->
<div class="tab-panel" id="panel-pack-list">
<div class="filter-block">
<div class="filter-grid">
<div class="filter-field">
<label>关键词</label>
<input class="input" placeholder="编号/名称" />
</div>
</div>
<div class="filter-actions-row">
<span class="filter-hint">按编号、名称模糊查询。</span>
<div class="filter-actions-end">
<button type="button" class="btn">重置</button>
<button type="button" class="btn btn-primary">搜索</button>
</div>
</div>
</div>
<div class="list-toolbar">
<div class="list-toolbar-row">
<span class="list-toolbar-selected">已选 <span class="sel-num">0</span> 项</span>
<button type="button" class="btn btn-primary">新增耗材包</button>
<button type="button" class="btn btn-danger" disabled>批量删除</button>
</div>
</div>
<div class="list-body">
<div class="table-wrap">
<table class="list-table">
<thead>
<tr>
<th><input type="checkbox" /></th>
<th>序号</th>
<th>编号</th>
<th>名称</th>
<th>使用场景</th>
<th>类型</th>
<th>状态</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr>
<td><input type="checkbox" /></td>
<td>1</td>
<td>PK-STD-01</td>
<td>标准细胞制备包</td>
<td>间充质干细胞制备</td>
<td><span class="tag primary">标准耗材包</span></td>
<td><span class="tag success">启用</span></td>
<td>
<button class="btn-op">查看</button>
<button class="btn-op">编辑</button>
<button class="btn-op">删除</button>
</td>
</tr>
<tr>
<td><input type="checkbox" /></td>
<td>2</td>
<td>PK-CUS-12</td>
<td>质检取样专用包</td>
<td>质检取样</td>
<td><span class="tag warning">自定义耗材包</span></td>
<td><span class="tag success">启用</span></td>
<td>
<button class="btn-op">查看</button>
<button class="btn-op">编辑</button>
<button class="btn-op">删除</button>
</td>
</tr>
</tbody>
</table>
</div>
<div class="pagination">
<span>共 24 条</span>
<div class="pager-btns">
<button type="button">‹</button>
<button type="button" class="is-active">1</button>
<button type="button">›</button>
</div>
</div>
</div>
</div>
</section>
</main>
</div>
</div>
<script>
;(function () {
const l1Buttons = document.querySelectorAll('.tabs-l1 button')
const l2Buttons = document.querySelectorAll('.tabs-l2 button')
const subTabsWrap = document.getElementById('sub-tabs-wrap')
const structureHint = document.getElementById('structure-hint')
const panels = {
'consumable-list': document.getElementById('panel-consumable-list'),
'consumable-logs': document.getElementById('panel-consumable-logs'),
'pack-list': document.getElementById('panel-pack-list')
}
let l1 = 'consumable'
let l2 = 'list'
function updatePanels() {
Object.values(panels).forEach((p) => p.classList.remove('is-active'))
if (l1 === 'consumable') {
subTabsWrap.hidden = false
const key = l2 === 'logs' ? 'consumable-logs' : 'consumable-list'
panels[key].classList.add('is-active')
structureHint.innerHTML =
l2 === 'logs'
? '当前结构:<b>一级「耗材」</b> → <b>二级「变动记录」</b>(无批量入库/出库,仅查询日志)'
: '当前结构:<b>一级「耗材」</b> → <b>二级「耗材列表」</b>(筛选 + 工具栏 + 表格 + 分页)'
} else {
subTabsWrap.hidden = true
panels['pack-list'].classList.add('is-active')
structureHint.innerHTML =
'当前结构:<b>一级「耗材包」</b>(无二级 Tab,直接进入包列表 CRUD)'
}
}
l1Buttons.forEach((btn) => {
btn.addEventListener('click', () => {
l1 = btn.dataset.l1
l1Buttons.forEach((b) => {
b.classList.toggle('is-active', b === btn)
b.setAttribute('aria-selected', b === btn ? 'true' : 'false')
})
updatePanels()
})
})
l2Buttons.forEach((btn) => {
btn.addEventListener('click', () => {
l2 = btn.dataset.l2
l2Buttons.forEach((b) => {
b.classList.toggle('is-active', b === btn)
})
updatePanels()
})
})
updatePanels()
})()
</script>
</body>
</html>