Files
workspace/code/fms/fms-vue/public/module-management-layout-demo.html
T
2026-08-23 21:02:41 +08:00

314 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">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>模块管理布局演示</title>
<style>
:root {
--bg: #f4f6f8;
--surface: #ffffff;
--surface-subtle: #f8fafb;
--line: #e5e9ee;
--line-strong: #d4dbe3;
--text: #182230;
--muted: #657384;
--muted-2: #8b98a8;
--brand: #1677ff;
--brand-soft: #eaf3ff;
--success: #18864b;
--warning: #b86b00;
--shadow: 0 10px 28px rgba(29, 44, 63, 0.08);
--radius: 8px;
font-family: Inter, "Noto Sans SC", "Microsoft YaHei", sans-serif;
color: var(--text);
background: var(--bg);
}
* { box-sizing: border-box; }
body { margin: 0; min-width: 1120px; background: var(--bg); }
button, input, select { font: inherit; }
button { cursor: pointer; }
.app { min-height: 100vh; display: flex; flex-direction: column; }
.topbar {
height: 60px; display: flex; align-items: center; gap: 26px; padding: 0 24px;
background: var(--surface); border-bottom: 1px solid var(--line);
}
.brand { display: flex; align-items: center; gap: 10px; font-weight: 700; letter-spacing: 0; }
.brand-mark { width: 28px; height: 28px; display: grid; place-items: center; border-radius: 7px; color: white; background: #1f6feb; font-size: 14px; }
.brand-title { font-size: 16px; }
.zone-nav { display: flex; align-self: stretch; gap: 4px; }
.zone-tab { position: relative; border: 0; padding: 0 14px; color: var(--muted); background: transparent; font-size: 13px; }
.zone-tab::after { content: ""; position: absolute; left: 12px; right: 12px; bottom: -1px; height: 2px; background: transparent; }
.zone-tab.active { color: var(--brand); font-weight: 600; }
.zone-tab.active::after { background: var(--brand); }
.topbar-spacer { flex: 1; }
.top-status { display: flex; align-items: center; gap: 8px; color: var(--muted); font-size: 12px; }
.dot { width: 7px; height: 7px; border-radius: 50%; background: var(--success); }
.top-actions { display: flex; align-items: center; gap: 8px; }
.icon-btn, .plain-btn, .primary-btn {
height: 32px; display: inline-flex; align-items: center; justify-content: center; gap: 7px;
border-radius: 6px; border: 1px solid var(--line-strong); padding: 0 11px; background: var(--surface); color: var(--text); font-size: 12px;
}
.icon-btn { width: 32px; padding: 0; color: var(--muted); }
.plain-btn:hover, .icon-btn:hover { background: var(--surface-subtle); border-color: #b8c3cf; }
.primary-btn { border-color: var(--brand); color: white; background: var(--brand); }
.primary-btn:hover { background: #0f66df; }
.toolbar-label { color: var(--muted); font-size: 12px; }
.workspace { flex: 1; min-height: 0; display: flex; flex-direction: column; padding: 14px 18px 18px; gap: 12px; }
.page-head { display: flex; align-items: center; justify-content: space-between; min-height: 48px; }
.breadcrumb { display: flex; align-items: center; gap: 8px; color: var(--muted); font-size: 12px; }
.breadcrumb strong { color: var(--text); font-size: 15px; }
.head-actions { display: flex; align-items: center; gap: 8px; }
.unsaved { display: inline-flex; align-items: center; gap: 6px; padding: 5px 9px; border: 1px solid #f4d5a4; border-radius: 999px; color: var(--warning); background: #fff9ef; font-size: 11px; }
.workbench { flex: 1; min-height: 660px; display: grid; grid-template-columns: 238px 154px minmax(0, 1fr); overflow: hidden; border: 1px solid var(--line); border-radius: var(--radius); background: var(--surface); box-shadow: var(--shadow); }
.module-pane, .section-pane { min-width: 0; border-right: 1px solid var(--line); background: var(--surface); }
.pane-head { height: 48px; display: flex; align-items: center; justify-content: space-between; padding: 0 12px; border-bottom: 1px solid var(--line); }
.pane-title { font-size: 12px; font-weight: 700; }
.pane-tools { display: flex; gap: 3px; }
.mini-btn { width: 26px; height: 26px; display: grid; place-items: center; border: 0; border-radius: 5px; color: var(--muted); background: transparent; }
.mini-btn:hover { background: var(--brand-soft); color: var(--brand); }
.search { margin: 10px 10px 8px; height: 30px; display: flex; align-items: center; gap: 7px; padding: 0 9px; border: 1px solid var(--line); border-radius: 6px; color: var(--muted-2); background: var(--surface-subtle); }
.search input { width: 100%; border: 0; outline: 0; color: var(--text); background: transparent; font-size: 12px; }
.tree { padding: 2px 8px 12px; }
.tree-group { margin-top: 4px; }
.tree-row { min-height: 32px; display: flex; align-items: center; gap: 6px; padding: 0 7px; border-radius: 6px; color: var(--muted); font-size: 12px; }
.tree-row:hover { background: var(--surface-subtle); }
.tree-row.active { color: var(--brand); background: var(--brand-soft); font-weight: 600; }
.tree-indent { width: 14px; flex: none; }
.tree-name { min-width: 0; flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.tree-tag { padding: 2px 5px; border-radius: 4px; color: var(--muted); background: #eef1f4; font-size: 10px; font-weight: 500; }
.tree-tag.blue { color: #1554a0; background: #e8f1ff; }
.tree-tag.green { color: #146c41; background: #e7f6ed; }
.tree-tag.amber { color: #8b5b00; background: #fff4d9; }
.section-pane { padding: 10px 8px; background: #fbfcfd; }
.section-label { padding: 9px 9px 5px; color: var(--muted-2); font-size: 10px; font-weight: 700; letter-spacing: .06em; }
.section-item { width: 100%; display: flex; align-items: center; gap: 8px; min-height: 34px; padding: 0 9px; border: 0; border-radius: 6px; text-align: left; color: var(--muted); background: transparent; font-size: 12px; }
.section-item:hover { background: #f0f4f8; color: var(--text); }
.section-item.active { color: var(--brand); background: var(--brand-soft); font-weight: 600; }
.section-item .count { margin-left: auto; min-width: 18px; padding: 1px 5px; border-radius: 999px; color: var(--muted); background: #e9eef3; text-align: center; font-size: 10px; }
.section-separator { height: 1px; margin: 10px 9px; background: var(--line); }
.tip { margin: 22px 8px 0; padding: 10px; border: 1px solid #d9e6f5; border-radius: 6px; color: #42617f; background: #f4f9ff; font-size: 11px; line-height: 1.6; }
.designer { min-width: 0; display: flex; flex-direction: column; background: #f5f7f9; }
.designer-head { min-height: 56px; display: flex; align-items: center; justify-content: space-between; padding: 0 16px; border-bottom: 1px solid var(--line); background: var(--surface); }
.designer-title { display: flex; align-items: center; gap: 8px; }
.designer-title strong { font-size: 14px; }
.designer-title span { color: var(--muted); font-size: 11px; }
.view-switch { display: inline-flex; padding: 3px; border: 1px solid var(--line); border-radius: 6px; background: var(--surface-subtle); }
.view-switch button { height: 26px; padding: 0 10px; border: 0; border-radius: 4px; color: var(--muted); background: transparent; font-size: 11px; }
.view-switch button.active { color: var(--text); background: var(--surface); box-shadow: 0 1px 3px rgba(30, 45, 64, .12); font-weight: 600; }
.device-switch { display: inline-flex; gap: 4px; margin-left: 9px; }
.device-switch button { width: 28px; height: 28px; display: grid; place-items: center; border: 1px solid var(--line); border-radius: 5px; color: var(--muted); background: var(--surface); }
.device-switch button.active { border-color: #a7c8f7; color: var(--brand); background: var(--brand-soft); }
.designer-body { flex: 1; min-height: 0; display: grid; grid-template-columns: 188px minmax(430px, 1fr) 218px; gap: 12px; padding: 14px; overflow: hidden; }
.library, .inspector { min-width: 0; padding: 10px; border: 1px solid var(--line); border-radius: 7px; background: var(--surface); }
.subhead { display: flex; align-items: center; justify-content: space-between; margin-bottom: 10px; }
.subhead strong { font-size: 12px; }
.subhead span { color: var(--muted-2); font-size: 10px; }
.library-search { height: 28px; display: flex; align-items: center; gap: 6px; padding: 0 8px; border: 1px solid var(--line); border-radius: 5px; color: var(--muted-2); background: var(--surface-subtle); font-size: 11px; }
.library-group { margin-top: 14px; }
.library-group-title { margin-bottom: 6px; color: var(--muted-2); font-size: 10px; font-weight: 700; }
.library-item { min-height: 34px; display: flex; align-items: center; gap: 8px; margin-bottom: 5px; padding: 0 8px; border: 1px solid var(--line); border-radius: 5px; color: var(--muted); background: #fcfdfe; font-size: 11px; }
.library-item:hover { border-color: #a7c8f7; color: var(--brand); background: var(--brand-soft); }
.drag-grip { color: #9eabb8; font-size: 12px; letter-spacing: -2px; }
.field-mark { width: 20px; height: 20px; display: grid; place-items: center; border-radius: 4px; color: #2f6ca8; background: #e8f2ff; font-size: 10px; font-weight: 700; }
.canvas-wrap { min-width: 0; overflow: auto; }
.canvas { min-height: 100%; padding: 14px; border: 1px dashed #c8d2dc; border-radius: 7px; background: #e9edf1; }
.canvas-note { display: flex; align-items: center; justify-content: space-between; margin-bottom: 10px; color: var(--muted); font-size: 10px; }
.form-sheet { max-width: 690px; margin: 0 auto; padding: 18px 20px 22px; border: 1px solid var(--line); border-radius: 7px; background: var(--surface); box-shadow: 0 3px 14px rgba(39, 54, 70, .06); }
.form-sheet-head { display: flex; align-items: flex-start; justify-content: space-between; margin-bottom: 18px; padding-bottom: 14px; border-bottom: 1px solid var(--line); }
.form-sheet-head strong { font-size: 15px; }
.form-sheet-head span { display: block; margin-top: 4px; color: var(--muted); font-size: 11px; }
.section-block { margin-top: 13px; }
.section-block-title { display: flex; align-items: center; gap: 7px; margin-bottom: 8px; color: var(--text); font-size: 11px; font-weight: 700; }
.section-block-title::before { content: ""; width: 3px; height: 13px; border-radius: 2px; background: var(--brand); }
.form-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 8px; }
.field-card { position: relative; display: flex; align-items: center; gap: 9px; min-height: 52px; padding: 8px 10px; border: 1px solid var(--line); border-radius: 6px; background: #fff; }
.field-card:hover { border-color: #a7c8f7; }
.field-card.selected { border: 1px solid var(--brand); box-shadow: 0 0 0 2px rgba(22, 119, 255, .11); }
.field-card.span-2 { grid-column: span 2; }
.field-card .drag-handle { color: #9ba9b6; font-size: 14px; }
.field-copy { min-width: 0; flex: 1; }
.field-copy strong { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 11px; }
.field-copy span { display: block; margin-top: 3px; color: var(--muted-2); font-size: 10px; }
.required { color: #d95757; }
.drop-zone { display: flex; align-items: center; justify-content: center; min-height: 38px; margin-top: 10px; border: 1px dashed #9ebce0; border-radius: 5px; color: #547ba9; background: #f5f9ff; font-size: 11px; }
.inspector .subhead { padding-bottom: 10px; border-bottom: 1px solid var(--line); }
.inspector-empty { padding: 28px 12px; color: var(--muted-2); text-align: center; font-size: 11px; line-height: 1.7; }
.property { margin-top: 13px; }
.property label { display: block; margin-bottom: 5px; color: var(--muted); font-size: 10px; }
.property input, .property select { width: 100%; height: 30px; padding: 0 8px; border: 1px solid var(--line); border-radius: 5px; color: var(--text); background: var(--surface-subtle); outline: 0; font-size: 11px; }
.property input:focus, .property select:focus { border-color: var(--brand); box-shadow: 0 0 0 2px rgba(22, 119, 255, .12); }
.property-row { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
.toggle-line { display: flex; align-items: center; justify-content: space-between; min-height: 30px; color: var(--muted); font-size: 11px; }
.toggle { width: 30px; height: 18px; padding: 2px; border: 0; border-radius: 999px; background: #bdc7d2; }
.toggle::after { content: ""; display: block; width: 14px; height: 14px; border-radius: 50%; background: white; transition: transform .16s ease; }
.toggle.on { background: var(--brand); }
.toggle.on::after { transform: translateX(12px); }
.inspector-foot { margin-top: 20px; padding-top: 12px; border-top: 1px solid var(--line); color: var(--muted-2); font-size: 10px; line-height: 1.6; }
.data-view { display: none; flex: 1; padding: 16px; overflow: auto; background: #f5f7f9; }
.data-toolbar { display: flex; align-items: center; justify-content: space-between; margin-bottom: 12px; }
.data-toolbar strong { font-size: 12px; }
table { width: 100%; border-collapse: collapse; border: 1px solid var(--line); border-radius: 7px; overflow: hidden; background: var(--surface); font-size: 11px; }
th, td { height: 40px; padding: 0 10px; border-bottom: 1px solid var(--line); text-align: left; white-space: nowrap; }
th { color: var(--muted); background: var(--surface-subtle); font-weight: 600; }
td { color: var(--text); }
tr:last-child td { border-bottom: 0; }
.check { color: var(--success); font-weight: 700; }
@media (max-width: 1280px) {
body { min-width: 0; }
.workbench { grid-template-columns: 214px 142px minmax(0, 1fr); }
.designer-body { grid-template-columns: 164px minmax(380px, 1fr) 190px; }
.topbar { padding: 0 16px; gap: 16px; }
.workspace { padding-left: 12px; padding-right: 12px; }
}
</style>
</head>
<body>
<div class="app">
<header class="topbar">
<div class="brand"><span class="brand-mark">F</span><span class="brand-title">FMS 管理台</span></div>
<nav class="zone-nav" aria-label="配置区域">
<button class="zone-tab active" type="button">模块配置</button>
<button class="zone-tab" type="button">菜单配置</button>
</nav>
<div class="topbar-spacer"></div>
<div class="top-status"><span class="dot"></span>演示模式</div>
<div class="top-actions">
<button class="plain-btn" type="button">预览</button>
<button class="primary-btn" type="button">保存</button>
</div>
</header>
<main class="workspace">
<div class="page-head">
<div class="breadcrumb"><span>系统</span><span>/</span><strong>订单模块</strong><span class="tree-tag blue">数据模块</span><span class="unsaved">● 未保存</span></div>
<div class="head-actions"><button class="plain-btn" type="button">重载</button><span class="toolbar-label">Ctrl + S 保存</span></div>
</div>
<section class="workbench" aria-label="模块配置工作台">
<aside class="module-pane">
<div class="pane-head"><span class="pane-title">模块</span><div class="pane-tools"><button class="mini-btn" title="新建">+</button><button class="mini-btn" title="刷新">↻</button></div></div>
<label class="search"><span>⌕</span><input placeholder="搜索模块" /></label>
<div class="tree">
<div class="tree-group">
<div class="tree-row"><span>⌄</span><span>▣</span><span class="tree-name">系统</span><span class="tree-tag">分类</span></div>
<div class="tree-row active"><span class="tree-indent"></span><span>◈</span><span class="tree-name">订单模块</span><span class="tree-tag blue">数据</span></div>
<div class="tree-row"><span class="tree-indent"></span><span>⌘</span><span class="tree-name">订单查询</span><span class="tree-tag amber">查询</span></div>
</div>
<div class="tree-group">
<div class="tree-row"><span>›</span><span>▣</span><span class="tree-name">基础资料</span><span class="tree-tag">分类</span></div>
<div class="tree-row"><span>›</span><span>▣</span><span class="tree-name">报表中心</span><span class="tree-tag">分类</span></div>
</div>
</div>
</aside>
<aside class="section-pane">
<div class="section-label">概览</div>
<button class="section-item" type="button">▤ <span>基本信息</span></button>
<div class="section-label">数据结构</div>
<button class="section-item" type="button">⌘ <span>字段定义</span><span class="count">18</span></button>
<button class="section-item" type="button">▤ <span>分组管理</span><span class="count">3</span></button>
<div class="section-label">界面配置</div>
<button class="section-item active" type="button">✣ <span>表单设计</span></button>
<button class="section-item" type="button">▦ <span>列表配置</span></button>
<button class="section-item" type="button">⌕ <span>查询配置</span></button>
<div class="section-label">业务能力</div>
<button class="section-item" type="button">⚙ <span>自动编码</span></button>
<button class="section-item" type="button">♢ <span>权限</span></button>
<div class="section-label">高级设置</div>
<button class="section-item" type="button">文 <span>多语言</span></button>
<div class="section-separator"></div>
<div class="tip">设计视图用于调整布局,数据视图适合批量修改字段属性。两种视图共享同一份草稿。</div>
</aside>
<section class="designer">
<div class="designer-head">
<div class="designer-title"><strong>表单设计</strong><span>订单模块 · 编辑表单</span></div>
<div>
<div class="view-switch" role="tablist"><button class="active" data-view="design" type="button">设计视图</button><button data-view="data" type="button">数据视图</button></div>
<span class="device-switch"><button class="active" type="button" title="桌面预览">▣</button><button type="button" title="移动预览">▯</button></span>
</div>
</div>
<div class="designer-body" id="design-view">
<aside class="library">
<div class="subhead"><strong>字段库</strong><span>拖到画布</span></div>
<div class="library-search">⌕&nbsp; 筛选字段</div>
<div class="library-group"><div class="library-group-title">订单字段</div>
<div class="library-item"><span class="drag-grip">⋮⋮</span><span class="field-mark">文</span><span>订单编号</span></div>
<div class="library-item"><span class="drag-grip">⋮⋮</span><span class="field-mark">文</span><span>客户名称</span></div>
<div class="library-item"><span class="drag-grip">⋮⋮</span><span class="field-mark">数</span><span>订单金额</span></div>
<div class="library-item"><span class="drag-grip">⋮⋮</span><span class="field-mark">日</span><span>下单日期</span></div>
<div class="library-item"><span class="drag-grip">⋮⋮</span><span class="field-mark">选</span><span>订单状态</span></div>
</div>
<div class="library-group"><div class="library-group-title">布局组件</div>
<div class="library-item"><span class="drag-grip">⋮⋮</span><span class="field-mark">分</span><span>分组容器</span></div>
<div class="library-item"><span class="drag-grip">⋮⋮</span><span class="field-mark">文</span><span>说明文字</span></div>
</div>
</aside>
<div class="canvas-wrap"><div class="canvas"><div class="canvas-note"><span>画布 · 桌面端 12 栅格</span><span>可拖拽调整顺序与宽度</span></div>
<div class="form-sheet">
<div class="form-sheet-head"><div><strong>订单信息</strong><span>维护订单基本资料和业务状态</span></div><span class="tree-tag green">编辑态</span></div>
<div class="section-block"><div class="section-block-title">基础信息</div><div class="form-grid">
<button class="field-card selected" type="button" data-field="订单编号"><span class="drag-handle">⋮⋮</span><span class="field-mark">文</span><span class="field-copy"><strong>订单编号 <i class="required">*</i></strong><span>文本框 · 6 栅格</span></span></button>
<button class="field-card" type="button" data-field="客户名称"><span class="drag-handle">⋮⋮</span><span class="field-mark">文</span><span class="field-copy"><strong>客户名称 <i class="required">*</i></strong><span>文本框 · 6 栅格</span></span></button>
<button class="field-card" type="button" data-field="订单金额"><span class="drag-handle">⋮⋮</span><span class="field-mark">数</span><span class="field-copy"><strong>订单金额</strong><span>数字框 · 6 栅格</span></span></button>
<button class="field-card" type="button" data-field="下单日期"><span class="drag-handle">⋮⋮</span><span class="field-mark">日</span><span class="field-copy"><strong>下单日期</strong><span>日期框 · 6 栅格</span></span></button>
</div></div>
<div class="section-block"><div class="section-block-title">业务状态</div><div class="form-grid">
<button class="field-card span-2" type="button" data-field="订单状态"><span class="drag-handle">⋮⋮</span><span class="field-mark">选</span><span class="field-copy"><strong>订单状态</strong><span>下拉选择 · 12 栅格</span></span></button>
</div><div class="drop-zone">拖拽字段到这里,插入新的表单项</div></div>
</div>
</div></div>
<aside class="inspector"><div class="subhead"><strong>属性</strong><span id="selected-label">订单编号</span></div>
<div id="inspector-content"><div class="property"><label>字段标题</label><input value="订单编号" /></div><div class="property"><label>控件类型</label><select><option>文本框</option><option>数字框</option><option>日期框</option></select></div><div class="property-row"><div class="property"><label>栅格宽度</label><select><option>6 / 12</option><option>12 / 12</option><option>4 / 12</option></select></div><div class="property"><label>排序号</label><input value="10" /></div></div><div class="property"><div class="toggle-line"><span>必填</span><button class="toggle on" type="button"></button></div><div class="toggle-line"><span>只读</span><button class="toggle" type="button"></button></div><div class="toggle-line"><span>禁用</span><button class="toggle" type="button"></button></div></div><div class="inspector-foot">拖动字段卡片可以调整顺序。后续可在这里扩展显示条件、默认值和校验规则。</div></div>
</aside>
</div>
<div class="data-view" id="data-view"><div class="data-toolbar"><strong>编辑字段属性</strong><button class="plain-btn" type="button">批量同步到列表</button></div><table><thead><tr><th>拖动</th><th>字段</th><th>显示</th><th>必填</th><th>只读</th><th>分组</th><th>排序号</th></tr></thead><tbody><tr><td>⋮⋮</td><td>订单编号</td><td class="check">✓</td><td class="check">✓</td><td>—</td><td>基础信息</td><td>10</td></tr><tr><td>⋮⋮</td><td>客户名称</td><td class="check">✓</td><td class="check">✓</td><td>—</td><td>基础信息</td><td>20</td></tr><tr><td>⋮⋮</td><td>订单金额</td><td class="check">✓</td><td>—</td><td>—</td><td>基础信息</td><td>30</td></tr><tr><td>⋮⋮</td><td>下单日期</td><td class="check">✓</td><td>—</td><td>—</td><td>基础信息</td><td>40</td></tr><tr><td>⋮⋮</td><td>订单状态</td><td class="check">✓</td><td>—</td><td>—</td><td>业务状态</td><td>50</td></tr></tbody></table></div>
</section>
</section>
</main>
</div>
<script>
const viewButtons = document.querySelectorAll('[data-view]')
const designView = document.getElementById('design-view')
const dataView = document.getElementById('data-view')
viewButtons.forEach((button) => button.addEventListener('click', () => {
viewButtons.forEach((item) => item.classList.toggle('active', item === button))
const isDesign = button.dataset.view === 'design'
designView.style.display = isDesign ? 'grid' : 'none'
dataView.style.display = isDesign ? 'none' : 'block'
}))
const fieldNames = ['订单编号', '客户名称', '订单金额', '下单日期', '订单状态']
document.querySelectorAll('.field-card').forEach((card) => card.addEventListener('click', () => {
document.querySelectorAll('.field-card').forEach((item) => item.classList.remove('selected'))
card.classList.add('selected')
const name = card.dataset.field
document.getElementById('selected-label').textContent = name
const input = document.querySelector('#inspector-content input')
if (input && fieldNames.includes(name)) input.value = name
}))
document.querySelectorAll('.toggle').forEach((toggle) => toggle.addEventListener('click', () => toggle.classList.toggle('on')))
document.querySelectorAll('.device-switch button').forEach((button) => button.addEventListener('click', () => {
document.querySelectorAll('.device-switch button').forEach((item) => item.classList.toggle('active', item === button))
document.querySelector('.form-sheet').style.maxWidth = button.textContent.trim() === '▯' ? '420px' : '690px'
}))
</script>
</body>
</html>