Files
workspace/code/fms/demos/layout-demo-1-vertical-nav.html
T
2026-08-11 16:51:11 +08:00

183 lines
8.9 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>
<!--
Demo 1 —— 方案 A:GitHub / Stripe 式「左侧竖向分区导航」
结构:模块树(far-left) | 竖向分区导航(mid-left) | 内容(right)
特点:分区再多也不裁切、不换行;树被拖宽后仍能保住表单横向空间。
-->
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>模块管理布局 Demo 1 · 左侧竖向分区导航</title>
<style>
:root {
--background: oklch(1 0 0);
--foreground: oklch(0.145 0 0);
--muted: oklch(0.97 0 0);
--muted-foreground: oklch(0.556 0 0);
--border: oklch(0.922 0 0);
--primary: oklch(0.205 0 0);
--primary-foreground: oklch(0.985 0 0);
--accent: oklch(0.97 0 0);
--ring: oklch(0.708 0 0);
--radius: 8px;
}
* { box-sizing: border-box; }
html, body { height: 100%; margin: 0; }
body {
font-family: Inter, system-ui, -apple-system, "Segoe UI", "PingFang SC", "Microsoft YaHei", sans-serif;
background: var(--background);
color: var(--foreground);
font-size: 13px;
}
.app { display: flex; height: 100vh; min-width: 0; }
/* 左侧模块树 */
.tree {
width: 240px; flex: none; display: flex; flex-direction: column;
border-right: 1px solid var(--border);
}
.tree .search { padding: 12px 8px 8px; }
.tree .search input {
width: 100%; height: 32px; padding: 0 10px; font-size: 13px;
border: 1px solid var(--border); border-radius: 6px; outline: none;
background: var(--background); color: var(--foreground);
}
.tree .tree-body { flex: 1; min-height: 0; overflow: auto; padding: 0 4px 8px; }
.node { display: flex; align-items: center; gap: 6px; height: 30px; padding: 0 8px; border-radius: 6px; color: var(--foreground); cursor: pointer; }
.node:hover { background: var(--accent); }
.node.group { color: var(--muted-foreground); font-weight: 500; }
.node.active { background: var(--accent); font-weight: 600; }
.node .tw { width: 14px; text-align: center; color: var(--muted-foreground); }
.indent { padding-left: 16px; }
/* 右侧主区 */
.main { flex: 1; min-width: 0; display: flex; flex-direction: column; }
.header {
flex: none; display: flex; align-items: center; justify-content: space-between;
padding: 10px 20px; border-bottom: 1px solid var(--border);
}
.header .title { font-size: 14px; font-weight: 600; }
.header .badge { margin-left: 8px; font-size: 10px; padding: 1px 6px; border-radius: 4px; background: var(--muted); color: var(--muted-foreground); letter-spacing: .05em; }
.header .actions { display: flex; align-items: center; gap: 14px; }
.switch { display: flex; align-items: center; gap: 6px; color: var(--muted-foreground); cursor: pointer; }
.switch .track { width: 30px; height: 18px; border-radius: 999px; background: var(--border); position: relative; }
.switch .track::after { content: ""; position: absolute; top: 2px; left: 2px; width: 14px; height: 14px; border-radius: 50%; background: #fff; transition: left .15s; }
.switch.on .track { background: var(--primary); }
.switch.on .track::after { left: 14px; }
.btn { height: 28px; padding: 0 14px; font-size: 12px; border-radius: 6px; border: 1px solid transparent; background: var(--primary); color: var(--primary-foreground); cursor: pointer; }
.btn:hover { opacity: .9; }
/* 内容区:竖向分区导航 + 面板 */
.body { flex: 1; min-height: 0; display: flex; }
.sections {
width: 168px; flex: none; border-right: 1px solid var(--border);
padding: 10px 8px; overflow: auto;
}
.sec { display: flex; align-items: center; gap: 8px; height: 34px; padding: 0 10px; border-radius: 6px; color: var(--muted-foreground); cursor: pointer; }
.sec:hover { background: var(--accent); color: var(--foreground); }
.sec.active { background: var(--accent); color: var(--foreground); font-weight: 600; }
.sec .ico { width: 15px; text-align: center; opacity: .8; }
.panel-wrap { flex: 1; min-width: 0; overflow: auto; padding: 20px; }
.panel { display: none; max-width: 920px; margin: 0 auto; }
.panel.active { display: block; }
.panel h3 { font-size: 13px; font-weight: 600; margin: 0 0 12px; }
.field { display: grid; grid-template-columns: 110px 1fr; align-items: center; gap: 12px; margin-bottom: 12px; }
.field label { color: var(--muted-foreground); }
.field input { height: 32px; padding: 0 10px; border: 1px solid var(--border); border-radius: 6px; background: var(--background); color: var(--foreground); }
.card { border: 1px solid var(--border); border-radius: 8px; padding: 16px; color: var(--muted-foreground); }
table.tbl { width: 100%; border-collapse: collapse; font-size: 13px; }
table.tbl th, table.tbl td { text-align: left; padding: 8px 10px; border-bottom: 1px solid var(--border); }
table.tbl th { color: var(--muted-foreground); font-weight: 500; background: var(--muted); }
.empty { color: var(--muted-foreground); padding: 24px; text-align: center; border: 1px dashed var(--border); border-radius: 8px; }
</style>
</head>
<body>
<div class="app">
<!-- 左:模块树 -->
<aside class="tree">
<div class="search"><input placeholder="搜索模块…" /></div>
<div class="tree-body">
<div class="node group"><span class="tw">▾</span> 系统管理</div>
<div class="node indent">用户管理</div>
<div class="node indent">角色管理</div>
<div class="node indent">部门管理</div>
<div class="node group"><span class="tw">▾</span> 业务模块</div>
<div class="node indent active">订单</div>
<div class="node indent">商品</div>
<div class="node indent">库存</div>
</div>
</aside>
<!-- 右:主区 -->
<main class="main">
<div class="header">
<div><span class="title">订单</span><span class="badge">数据模块</span></div>
<div class="actions">
<label class="switch on"><span class="track"></span>启用</label>
<button class="btn">保存</button>
</div>
</div>
<div class="body">
<!-- 中:竖向分区导航 -->
<nav class="sections" id="sections">
<div class="sec active" data-key="basic"><span class="ico">●</span> 基础信息</div>
<div class="sec" data-key="fields"><span class="ico">●</span> 字段定义</div>
<div class="sec" data-key="list"><span class="ico">●</span> 列表配置</div>
<div class="sec" data-key="edit"><span class="ico">●</span> 编辑配置</div>
<div class="sec" data-key="query"><span class="ico">●</span> 查询配置</div>
<div class="sec" data-key="auto"><span class="ico">●</span> 自动编码</div>
<div class="sec" data-key="i18n"><span class="ico">●</span> 多语言</div>
</nav>
<!-- 右:面板 -->
<div class="panel-wrap">
<section class="panel active" data-key="basic">
<h3>基础信息</h3>
<div class="field"><label>模块名称</label><input value="订单" /></div>
<div class="field"><label>模块编码</label><input value="m_order" /></div>
<div class="field"><label>模块类型</label><input value="数据模块" /></div>
</section>
<section class="panel" data-key="fields">
<h3>字段定义</h3>
<table class="tbl">
<thead><tr><th>字段</th><th>名称</th><th>类型</th><th>必填</th></tr></thead>
<tbody>
<tr><td>b_id</td><td>主键</td><td>bigint</td><td>是</td></tr>
<tr><td>b_name</td><td>名称</td><td>nvarchar</td><td>是</td></tr>
<tr><td>b_status</td><td>状态</td><td>int</td><td>否</td></tr>
</tbody>
</table>
</section>
<section class="panel" data-key="list"><h3>列表配置</h3><div class="card">列表列 / 分页 / 排序配置区</div></section>
<section class="panel" data-key="edit"><h3>编辑配置</h3><div class="card">编辑表单字段布局配置区</div></section>
<section class="panel" data-key="query"><h3>查询配置</h3><div class="card">查询条件配置区</div></section>
<section class="panel" data-key="auto"><h3>自动编码</h3><div class="card">编码规则配置区</div></section>
<section class="panel" data-key="i18n"><h3>多语言</h3><div class="card">翻译键 / 多语言文案配置区</div></section>
</div>
</div>
</main>
</div>
<script>
const sections = document.getElementById('sections');
const secs = [...sections.querySelectorAll('.sec')];
const panels = [...document.querySelectorAll('.panel')];
function activate(key) {
secs.forEach(s => s.classList.toggle('active', s.dataset.key === key));
panels.forEach(p => p.classList.toggle('active', p.dataset.key === key));
}
secs.forEach(s => s.addEventListener('click', () => activate(s.dataset.key)));
// 模拟启用开关
document.querySelectorAll('.switch').forEach(sw => sw.addEventListener('click', () => sw.classList.toggle('on')));
</script>
</body>
</html>