Files
workspace/code/fms/module-management-layout-demo.html
T
2026-08-08 17:28:38 +08:00

348 lines
26 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">
<!--
还有个问题就是 比如模块管理/权限配置 里面的这个table,表格的宽度是100%,但是呢只有几列,还用了固定宽度,不需要自使用宽度是因为如果列少但是列宽自适应的话,难以找到关键信息,这样导致了
-->
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>FMS 模块管理布局 Demo</title>
<style>
:root {
--canvas: #f6f7f9;
--surface: #ffffff;
--surface-muted: #fafbfc;
--line: #e4e7ec;
--line-strong: #d4d9e1;
--text: #1f2937;
--muted: #687386;
--subtle: #96a0af;
--accent: #2563eb;
--accent-soft: #eff4ff;
--success: #138a5b;
--success-soft: #eaf8f1;
--warning: #b56b0a;
--warning-soft: #fff6e7;
--danger: #c43d4b;
--shadow: 0 10px 30px rgba(31, 41, 55, .07);
}
* { box-sizing: border-box; }
html, body { height: 100%; }
body {
margin: 0;
background: var(--canvas);
color: var(--text);
font: 13px/1.5 -apple-system, BlinkMacSystemFont, "Segoe UI", "Microsoft YaHei", sans-serif;
}
button, input { font: inherit; }
button { cursor: pointer; }
.shell { height: 100%; padding: 18px; }
.window {
display: flex;
height: 100%;
min-height: 680px;
overflow: hidden;
border: 1px solid var(--line);
border-radius: 10px;
background: var(--surface);
box-shadow: var(--shadow);
}
.sidebar {
display: flex;
width: 270px;
min-width: 270px;
flex-direction: column;
border-right: 1px solid var(--line);
background: var(--surface-muted);
}
.sidebar-head { padding: 20px 18px 14px; }
.eyebrow { color: var(--subtle); font-size: 11px; letter-spacing: .06em; text-transform: uppercase; }
.sidebar-title { display: flex; align-items: center; justify-content: space-between; margin-top: 3px; font-size: 16px; font-weight: 650; }
.icon-button {
display: inline-grid;
width: 30px;
height: 30px;
place-items: center;
border: 1px solid transparent;
border-radius: 6px;
background: transparent;
color: var(--muted);
font-size: 18px;
}
.icon-button:hover { border-color: var(--line-strong); background: var(--surface); color: var(--text); }
.search { position: relative; padding: 0 18px 16px; }
.search input {
width: 100%;
height: 34px;
padding: 0 11px 0 32px;
border: 1px solid var(--line);
border-radius: 6px;
outline: none;
background: var(--surface);
color: var(--text);
}
.search input:focus { border-color: #9ab7f8; box-shadow: 0 0 0 3px var(--accent-soft); }
.search::before { position: absolute; top: 7px; left: 29px; color: var(--subtle); content: "⌕"; font-size: 18px; }
.tree { overflow: auto; padding: 0 10px 18px; }
.tree-group { margin: 10px 0 4px; padding: 0 8px; color: var(--subtle); font-size: 11px; font-weight: 600; letter-spacing: .04em; text-transform: uppercase; }
.tree-row {
display: flex;
width: 100%;
min-height: 36px;
align-items: center;
gap: 7px;
padding: 0 9px;
border: 0;
border-radius: 6px;
background: transparent;
color: var(--muted);
text-align: left;
}
.tree-row:hover { background: #f0f2f5; color: var(--text); }
.tree-row.active { background: var(--accent-soft); color: #1d4ed8; font-weight: 600; }
.tree-row.child { padding-left: 29px; }
.tree-row .chevron { width: 12px; color: var(--subtle); font-size: 12px; }
.tree-row .node-icon { width: 16px; color: #7f8a9a; text-align: center; font-size: 13px; }
.tree-row.active .node-icon { color: var(--accent); }
.tree-row .count { margin-left: auto; color: var(--subtle); font-size: 11px; }
.main { display: flex; min-width: 0; flex: 1; flex-direction: column; }
.object-head { padding: 20px 28px 0; border-bottom: 1px solid var(--line); background: var(--surface); }
.crumbs { display: flex; align-items: center; gap: 7px; color: var(--subtle); font-size: 12px; }
.crumbs strong { color: var(--muted); font-weight: 500; }
.title-row { display: flex; align-items: flex-start; justify-content: space-between; gap: 24px; padding: 10px 0 18px; }
.title-wrap { min-width: 0; }
h1 { margin: 0; font-size: 24px; font-weight: 650; letter-spacing: 0; }
.subtitle { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin-top: 7px; color: var(--muted); }
.dot { color: #c5cbd4; }
.badge { display: inline-flex; height: 22px; align-items: center; padding: 0 8px; border-radius: 12px; font-size: 11px; font-weight: 600; }
.badge.success { background: var(--success-soft); color: var(--success); }
.badge.draft { background: var(--warning-soft); color: var(--warning); }
.actions { display: flex; align-items: center; gap: 8px; }
.button {
display: inline-flex;
height: 34px;
align-items: center;
gap: 7px;
padding: 0 13px;
border: 1px solid var(--line-strong);
border-radius: 6px;
background: var(--surface);
color: var(--text);
font-weight: 500;
}
.button:hover { border-color: #9aa6b6; background: #fbfcfd; }
.button.primary { border-color: var(--accent); background: var(--accent); color: #fff; }
.button.primary:hover { background: #1d4ed8; }
.button[disabled] { cursor: default; opacity: .5; }
.button .symbol { font-size: 16px; line-height: 1; }
.more { position: relative; }
.menu { position: absolute; z-index: 5; top: 40px; right: 0; display: none; width: 150px; padding: 5px; border: 1px solid var(--line); border-radius: 7px; background: var(--surface); box-shadow: var(--shadow); }
.menu.open { display: block; }
.menu button { display: block; width: 100%; padding: 8px 9px; border: 0; border-radius: 4px; background: transparent; color: var(--text); text-align: left; }
.menu button:hover { background: var(--surface-muted); }
.primary-nav { display: flex; gap: 24px; }
.primary-nav button { position: relative; height: 42px; padding: 0; border: 0; background: transparent; color: var(--muted); font-weight: 550; }
.primary-nav button:hover { color: var(--text); }
.primary-nav button.active { color: var(--accent); }
.primary-nav button.active::after { position: absolute; right: 0; bottom: -1px; left: 0; height: 2px; background: var(--accent); content: ""; }
.content { min-height: 0; flex: 1; overflow: auto; background: var(--surface); }
.view { display: none; max-width: 1080px; padding: 26px 28px 40px; }
.view.active { display: block; }
.section { margin-bottom: 30px; }
.section-head { display: flex; align-items: baseline; justify-content: space-between; gap: 16px; margin-bottom: 14px; }
.section-title { margin: 0; font-size: 15px; font-weight: 650; }
.section-help { color: var(--subtle); font-size: 12px; }
.form-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); column-gap: 28px; row-gap: 18px; max-width: 900px; }
.field label { display: block; margin-bottom: 6px; color: var(--muted); font-size: 12px; }
.field label em { color: var(--danger); font-style: normal; }
.control { width: 100%; height: 35px; padding: 0 10px; border: 1px solid var(--line); border-radius: 5px; outline: none; background: var(--surface); color: var(--text); }
.control:focus { border-color: #9ab7f8; box-shadow: 0 0 0 3px var(--accent-soft); }
textarea.control { height: 74px; padding-top: 8px; resize: vertical; }
.read-only { background: var(--surface-muted); color: var(--muted); }
.switch-line { display: flex; align-items: center; gap: 10px; height: 35px; }
.switch { position: relative; display: inline-block; width: 34px; height: 20px; }
.switch input { width: 0; height: 0; opacity: 0; }
.slider { position: absolute; inset: 0; border-radius: 20px; background: #c7ced8; transition: .15s; }
.slider::before { position: absolute; bottom: 3px; left: 3px; width: 14px; height: 14px; border-radius: 50%; background: #fff; content: ""; transition: .15s; }
.switch input:checked + .slider { background: var(--accent); }
.switch input:checked + .slider::before { transform: translateX(14px); }
.table-wrap { overflow: hidden; border: 1px solid var(--line); border-radius: 7px; }
table { width: 100%; border-collapse: collapse; }
th, td { height: 43px; padding: 0 12px; border-bottom: 1px solid var(--line); text-align: left; white-space: nowrap; }
th { background: var(--surface-muted); color: var(--muted); font-size: 11px; font-weight: 650; }
td { color: var(--text); }
tr:last-child td { border-bottom: 0; }
.table-muted { color: var(--muted); }
.row-actions { display: flex; justify-content: flex-end; gap: 3px; }
.small-button { border: 0; border-radius: 4px; background: transparent; color: var(--muted); padding: 4px 7px; }
.small-button:hover { background: var(--surface-muted); color: var(--text); }
.subnav { display: flex; gap: 4px; margin-bottom: 18px; padding: 3px; border: 1px solid var(--line); border-radius: 7px; background: var(--surface-muted); width: fit-content; }
.subnav button { height: 29px; padding: 0 12px; border: 0; border-radius: 5px; background: transparent; color: var(--muted); }
.subnav button.active { background: var(--surface); color: var(--text); box-shadow: 0 1px 3px rgba(31, 41, 55, .10); font-weight: 600; }
.callout { display: flex; align-items: flex-start; gap: 10px; max-width: 760px; padding: 12px 14px; border: 1px solid #d8e3fb; border-radius: 6px; background: #f7faff; color: #4b638c; }
.callout strong { color: #315b9e; }
.empty-state { display: grid; min-height: 360px; place-items: center; color: var(--subtle); }
@media (max-width: 900px) {
.shell { padding: 0; }
.window { min-height: 100%; border: 0; border-radius: 0; }
.sidebar { width: 230px; min-width: 230px; }
.object-head, .view { padding-left: 20px; padding-right: 20px; }
.title-row { flex-direction: column; gap: 12px; }
.form-grid { grid-template-columns: 1fr; }
}
</style>
</head>
<body>
<div class="shell">
<div class="window">
<aside class="sidebar">
<div class="sidebar-head">
<div class="eyebrow">System configuration</div>
<div class="sidebar-title"><span>模块管理</span><button class="icon-button" id="addButton" title="添加模块">+</button></div>
</div>
<div class="search"><input id="treeSearch" type="search" aria-label="搜索模块"></div>
<nav class="tree" id="tree">
<div class="tree-group">基础数据</div>
<button class="tree-row active" data-module="partners"><span class="chevron">⌄</span><span class="node-icon">▣</span><span>合作伙伴</span></button>
<button class="tree-row child" data-module="contacts"><span class="chevron"></span><span class="node-icon">◦</span><span>联系人</span></button>
<button class="tree-row child" data-module="otherdata"><span class="chevron"></span><span class="node-icon">◦</span><span>其他数据</span></button>
<div class="tree-group">业务管理</div>
<button class="tree-row" data-module="sea"><span class="chevron">⌄</span><span class="node-icon">▣</span><span>海运业务</span></button>
<button class="tree-row child" data-module="shipment"><span class="chevron"></span><span class="node-icon">◦</span><span>出运单</span></button>
<button class="tree-row child" data-module="container"><span class="chevron"></span><span class="node-icon">◦</span><span>集装箱</span></button>
<div class="tree-group">系统管理</div>
<button class="tree-row" data-module="users"><span class="chevron"></span><span class="node-icon">□</span><span>用户管理</span></button>
<button class="tree-row" data-module="i18n"><span class="chevron"></span><span class="node-icon">□</span><span>多语言</span></button>
</nav>
</aside>
<main class="main">
<header class="object-head">
<div class="crumbs"><span>系统管理</span><span>/</span><strong>模块管理</strong></div>
<div class="title-row">
<div class="title-wrap">
<h1 id="moduleTitle">合作伙伴</h1>
<div class="subtitle"><span id="moduleType">数据模块</span><span class="dot">·</span><span id="moduleCode">v_b_othercompany</span><span class="badge success" id="statusBadge">已启用</span></div>
</div>
<div class="actions">
<span class="badge draft" id="dirtyBadge" hidden>有未保存修改</span>
<div class="more"><button class="button" id="moreButton"><span class="symbol">•••</span> 更多</button><div class="menu" id="moreMenu"><button data-action="duplicate">复制模块</button><button data-action="delete">删除模块</button></div></div>
<button class="button primary" id="saveButton"><span class="symbol">✓</span> 保存</button>
</div>
</div>
<nav class="primary-nav" id="primaryNav">
<button class="active" data-view="basic">基本信息</button>
<button data-view="fields">字段设计</button>
<button data-view="ui">界面设计</button>
<button data-view="advanced">高级设置</button>
</nav>
</header>
<section class="content">
<div class="view active" id="view-basic">
<section class="section">
<div class="section-head"><h2 class="section-title">身份信息</h2><span class="section-help">定义模块在系统中的基本身份</span></div>
<div class="form-grid">
<div class="field"><label>模块名称 <em>*</em></label><input class="control" value="合作伙伴"></div>
<div class="field"><label>模块编码 <em>*</em></label><input class="control" value="v_b_othercompany"></div>
<div class="field"><label>模块类型 <em>*</em></label><select class="control"><option selected>数据模块</option><option>页面</option><option>目录</option><option>数据分类</option></select></div>
<div class="field"><label>上级模块</label><input class="control read-only" value="基础数据" readonly></div>
<div class="field"><label>菜单图标</label><select class="control"><option selected>方块</option><option>文件夹</option><option>列表</option></select></div>
<div class="field"><label>排序号</label><input class="control" type="number" value="10"></div>
<div class="field"><label>启用状态</label><div class="switch-line"><label class="switch"><input type="checkbox" checked id="enabledSwitch"><span class="slider"></span></label><span id="enabledText">启用</span></div></div>
<div class="field"><label>显示菜单</label><div class="switch-line"><label class="switch"><input type="checkbox" checked><span class="slider"></span></label><span>在菜单中显示</span></div></div>
</div>
</section>
<section class="section">
<div class="section-head"><h2 class="section-title">数据源</h2><span class="section-help">数据模块从查询视图读取数据</span></div>
<div class="form-grid">
<div class="field"><label>查询视图 <em>*</em></label><input class="control" value="v_b_othercompany"></div>
<div class="field"><label>保存表</label><input class="control" value="b_othercompany"></div>
<div class="field"><label>主键字段</label><input class="control" value="b_id"></div>
<div class="field"><label>默认排序</label><input class="control" value="b_xh ASC, b_id DESC"></div>
</div>
</section>
</div>
<div class="view" id="view-fields">
<section class="section"><div class="section-head"><div><h2 class="section-title">字段设计</h2><span class="section-help">从查询视图同步字段,并调整字段语义</span></div><button class="button" id="syncButton"><span class="symbol">↻</span> 同步字段</button></div>
<div class="table-wrap"><table><thead><tr><th>字段名称</th><th>显示名称</th><th>类型</th><th>启用</th><th></th></tr></thead><tbody>
<tr><td>b_id</td><td>编号</td><td class="table-muted">数字</td><td><span class="badge success">启用</span></td><td class="row-actions"><button class="small-button">编辑</button></td></tr>
<tr><td>b_name</td><td>名称</td><td class="table-muted">文本</td><td><span class="badge success">启用</span></td><td class="row-actions"><button class="small-button">编辑</button></td></tr>
<tr><td>b_shortname</td><td>简称</td><td class="table-muted">文本</td><td><span class="badge success">启用</span></td><td class="row-actions"><button class="small-button">编辑</button></td></tr>
<tr><td>b_type</td><td>合作伙伴类型</td><td class="table-muted">选择</td><td><span class="badge success">启用</span></td><td class="row-actions"><button class="small-button">编辑</button></td></tr>
</tbody></table></div>
</section>
</div>
<div class="view" id="view-ui">
<div class="subnav" id="uiSubnav"><button class="active" data-subview="list">列表</button><button data-subview="edit">编辑</button><button data-subview="query">查询</button></div>
<div id="ui-list" class="subview"><section class="section"><div class="section-head"><div><h2 class="section-title">列表布局</h2><span class="section-help">设置列表显示哪些字段以及字段顺序</span></div><button class="button"><span class="symbol">+</span> 添加字段</button></div><div class="table-wrap"><table><thead><tr><th>顺序</th><th>字段</th><th>标题</th><th>宽度</th><th>可见</th><th></th></tr></thead><tbody><tr><td>1</td><td>b_name</td><td>名称</td><td>180</td><td><span class="badge success">是</span></td><td class="row-actions"><button class="small-button">上移</button><button class="small-button">更多</button></td></tr><tr><td>2</td><td>b_shortname</td><td>简称</td><td>140</td><td><span class="badge success">是</span></td><td class="row-actions"><button class="small-button">上移</button><button class="small-button">更多</button></td></tr><tr><td>3</td><td>b_type</td><td>合作伙伴类型</td><td>120</td><td><span class="badge success">是</span></td><td class="row-actions"><button class="small-button">上移</button><button class="small-button">更多</button></td></tr></tbody></table></div></section></div>
<div id="ui-edit" class="subview" hidden><section class="section"><div class="section-head"><div><h2 class="section-title">编辑表单</h2><span class="section-help">设置字段在新增和编辑时的行为</span></div><button class="button"><span class="symbol">+</span> 添加字段</button></div><div class="table-wrap"><table><thead><tr><th>顺序</th><th>字段</th><th>必填</th><th>只读</th><th>显示</th><th></th></tr></thead><tbody><tr><td>1</td><td>b_name</td><td>是</td><td>否</td><td>是</td><td class="row-actions"><button class="small-button">配置</button></td></tr><tr><td>2</td><td>b_shortname</td><td>否</td><td>否</td><td>是</td><td class="row-actions"><button class="small-button">配置</button></td></tr></tbody></table></div></section></div>
<div id="ui-query" class="subview" hidden><section class="section"><div class="section-head"><div><h2 class="section-title">查询条件</h2><span class="section-help">设置列表顶部可以使用的查询条件</span></div><button class="button"><span class="symbol">+</span> 添加条件</button></div><div class="table-wrap"><table><thead><tr><th>顺序</th><th>字段</th><th>控件</th><th>运算符</th><th>启用</th><th></th></tr></thead><tbody><tr><td>1</td><td>b_name</td><td>文本输入</td><td>包含</td><td><span class="badge success">启用</span></td><td class="row-actions"><button class="small-button">配置</button></td></tr><tr><td>2</td><td>b_type</td><td>下拉选择</td><td>等于</td><td><span class="badge success">启用</span></td><td class="row-actions"><button class="small-button">配置</button></td></tr></tbody></table></div></section></div>
</div>
<div class="view" id="view-advanced">
<section class="section"><div class="section-head"><h2 class="section-title">高级设置</h2><span class="section-help">低频配置集中放置,日常使用无需关注</span></div><div class="callout"><span>ⓘ</span><div><strong>建议先完成基本信息和字段设计。</strong><br>自动编码、多语言和权限配置只影响特定业务场景,可在需要时再设置。</div></div></section>
<section class="section"><div class="section-head"><h2 class="section-title">自动编码</h2><button class="button">配置规则</button></div><div class="table-wrap"><table><thead><tr><th>目标字段</th><th>前缀</th><th>日期格式</th><th>流水位数</th><th>状态</th></tr></thead><tbody><tr><td class="table-muted">未配置</td><td class="table-muted">-</td><td class="table-muted">-</td><td class="table-muted">-</td><td><span class="badge draft">未启用</span></td></tr></tbody></table></div></section>
<section class="section"><div class="section-head"><h2 class="section-title">权限与多语言</h2></div><div class="form-grid"><div class="field"><label>操作权限</label><button class="control" style="text-align:left">配置 4 项操作权限 ›</button></div><div class="field"><label>多语言</label><button class="control" style="text-align:left">管理翻译文本 ›</button></div></div></section>
</div>
</section>
</main>
</div>
</div>
<script>
const modules = {
partners: { title: "合作伙伴", type: "数据模块", code: "v_b_othercompany" },
contacts: { title: "联系人", type: "数据模块", code: "v_b_contact" },
otherdata: { title: "其他数据", type: "数据模块", code: "base_otherdata" },
sea: { title: "海运业务", type: "页面", code: "m_sea" },
shipment: { title: "出运单", type: "数据模块", code: "d_shipment" },
container: { title: "集装箱", type: "数据模块", code: "d_container" },
users: { title: "用户管理", type: "页面", code: "b_user" },
i18n: { title: "多语言", type: "页面", code: "b_i18n" }
};
let dirty = false;
const title = document.getElementById("moduleTitle");
const type = document.getElementById("moduleType");
const code = document.getElementById("moduleCode");
const dirtyBadge = document.getElementById("dirtyBadge");
const statusBadge = document.getElementById("statusBadge");
function setDirty(value) { dirty = value; dirtyBadge.hidden = !value; }
document.querySelectorAll(".tree-row").forEach(row => row.addEventListener("click", () => {
document.querySelectorAll(".tree-row").forEach(item => item.classList.remove("active"));
row.classList.add("active");
const item = modules[row.dataset.module];
if (!item) return;
title.textContent = item.title; type.textContent = item.type; code.textContent = item.code;
setDirty(false);
document.querySelectorAll(".primary-nav button").forEach((button, index) => button.classList.toggle("active", index === 0));
document.querySelectorAll(".view").forEach((view, index) => view.classList.toggle("active", index === 0));
}));
document.querySelectorAll(".primary-nav button").forEach(button => button.addEventListener("click", () => {
document.querySelectorAll(".primary-nav button").forEach(item => item.classList.remove("active")); button.classList.add("active");
document.querySelectorAll(".view").forEach(view => view.classList.remove("active")); document.getElementById("view-" + button.dataset.view).classList.add("active");
}));
document.querySelectorAll(".subnav button").forEach(button => button.addEventListener("click", () => {
document.querySelectorAll(".subnav button").forEach(item => item.classList.remove("active")); button.classList.add("active");
document.querySelectorAll(".subview").forEach(view => view.hidden = true); document.getElementById("ui-" + button.dataset.subview).hidden = false;
}));
document.querySelectorAll("input.control, select.control, textarea.control").forEach(input => input.addEventListener("input", () => setDirty(true)));
document.getElementById("saveButton").addEventListener("click", () => { setDirty(false); statusBadge.textContent = "已启用"; statusBadge.className = "badge success"; });
document.getElementById("enabledSwitch").addEventListener("change", event => { statusBadge.textContent = event.target.checked ? "已启用" : "已停用"; statusBadge.className = event.target.checked ? "badge success" : "badge draft"; setDirty(true); document.getElementById("enabledText").textContent = event.target.checked ? "启用" : "停用"; });
const moreMenu = document.getElementById("moreMenu"); document.getElementById("moreButton").addEventListener("click", () => moreMenu.classList.toggle("open"));
document.addEventListener("click", event => { if (!event.target.closest(".more")) moreMenu.classList.remove("open"); });
document.getElementById("treeSearch").addEventListener("input", event => { const query = event.target.value.trim().toLowerCase(); document.querySelectorAll(".tree-row").forEach(row => row.hidden = query && !row.textContent.toLowerCase().includes(query)); });
document.getElementById("addButton").addEventListener("click", () => alert("这里可以打开“新建模块”菜单,按当前节点提供目录、页面、数据分类和数据模块选项。"));
</script>
</body>
</html>