592 lines
16 KiB
HTML
592 lines
16 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="zh-CN">
|
||
<head>
|
||
<meta charset="UTF-8" />
|
||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||
<title>表单设计 · 三栏布局 Demo(Fresh Mint)</title>
|
||
<style>
|
||
:root {
|
||
--primary: #10b981;
|
||
--primary-dark: #059669;
|
||
--primary-bg: #ecfdf5;
|
||
--text: #0f172a;
|
||
--text-2: #334155;
|
||
--muted: #94a3b8;
|
||
--border: #e2e8f0;
|
||
--border-strong: #cbd5e1;
|
||
--bg: #f1f5f9;
|
||
--left-w: 280px;
|
||
--right-w: 300px;
|
||
--menu-w: 44px;
|
||
--top-h: 48px;
|
||
--radius: 8px;
|
||
}
|
||
|
||
* { box-sizing: border-box; }
|
||
html, body {
|
||
height: 100%;
|
||
margin: 0;
|
||
font: 14px/1.5 Inter, system-ui, -apple-system, "PingFang SC", "Microsoft YaHei", sans-serif;
|
||
color: var(--text);
|
||
background: var(--bg);
|
||
}
|
||
|
||
.page {
|
||
display: flex;
|
||
flex-direction: column;
|
||
height: 100%;
|
||
min-height: 560px;
|
||
padding: 12px 16px 16px;
|
||
}
|
||
|
||
.banner {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 8px 16px;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
margin-bottom: 10px;
|
||
padding: 10px 14px;
|
||
background: #fff;
|
||
border: 1px solid var(--border);
|
||
border-radius: var(--radius);
|
||
font-size: 13px;
|
||
}
|
||
.banner b { color: var(--primary-dark); }
|
||
.banner span { color: var(--muted); }
|
||
.tag {
|
||
padding: 2px 8px;
|
||
border-radius: 999px;
|
||
background: var(--primary-bg);
|
||
color: var(--primary-dark);
|
||
font-size: 12px;
|
||
font-weight: 600;
|
||
}
|
||
|
||
/* 顶部:返回 + Tab(示意,与设计器一致) */
|
||
.toolbar {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
margin-bottom: 8px;
|
||
}
|
||
.btn {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
height: 34px;
|
||
padding: 0 12px;
|
||
border: 1px solid var(--border);
|
||
border-radius: 8px;
|
||
background: #fff;
|
||
color: var(--text-2);
|
||
font-size: 13px;
|
||
cursor: pointer;
|
||
}
|
||
.btn.primary {
|
||
border-color: var(--primary);
|
||
background: var(--primary);
|
||
color: #fff;
|
||
}
|
||
.tabs {
|
||
display: flex;
|
||
justify-content: center;
|
||
gap: 8px;
|
||
margin: 4px 0 12px;
|
||
border-bottom: 1px solid var(--border);
|
||
}
|
||
.tab {
|
||
position: relative;
|
||
padding: 10px 18px;
|
||
border: 0;
|
||
background: none;
|
||
color: var(--text-2);
|
||
font-size: 15px;
|
||
font-weight: 600;
|
||
cursor: pointer;
|
||
}
|
||
.tab.active { color: var(--text); }
|
||
.tab.active::after {
|
||
content: "";
|
||
position: absolute;
|
||
right: 12px;
|
||
bottom: -1px;
|
||
left: 12px;
|
||
height: 3px;
|
||
background: var(--text);
|
||
border-radius: 2px;
|
||
}
|
||
|
||
.hint {
|
||
margin: 0 0 10px;
|
||
color: var(--muted);
|
||
font-size: 13px;
|
||
}
|
||
|
||
/* ===== 核心:左 / 中 / 右 三栏 ===== */
|
||
.designer {
|
||
display: flex;
|
||
flex: 1;
|
||
flex-direction: row;
|
||
flex-wrap: nowrap;
|
||
align-items: stretch;
|
||
min-height: 0;
|
||
overflow: hidden;
|
||
background: #fff;
|
||
border: 1px solid var(--border-strong);
|
||
border-radius: 10px;
|
||
box-shadow: 0 2px 8px rgb(15 23 42 / 8%);
|
||
}
|
||
|
||
/* 左侧极窄图标栏 */
|
||
.side-menu {
|
||
display: flex;
|
||
flex: 0 0 var(--menu-w);
|
||
flex-direction: column;
|
||
align-items: center;
|
||
gap: 4px;
|
||
padding-top: 8px;
|
||
background: #fff;
|
||
border-right: 1px solid var(--border);
|
||
}
|
||
.side-menu button {
|
||
display: grid;
|
||
place-items: center;
|
||
width: 36px;
|
||
height: 36px;
|
||
border: 0;
|
||
border-radius: 8px;
|
||
background: transparent;
|
||
color: #64748b;
|
||
font-size: 16px;
|
||
cursor: pointer;
|
||
}
|
||
.side-menu button:hover { background: #f1f5f9; }
|
||
.side-menu button.active {
|
||
color: var(--primary-dark);
|
||
background: var(--primary-bg);
|
||
}
|
||
|
||
/* 左:控件库 */
|
||
.pane-left {
|
||
display: flex;
|
||
flex: 0 0 var(--left-w);
|
||
flex-direction: column;
|
||
min-width: 0;
|
||
border-right: 1px solid var(--border);
|
||
background: #fff;
|
||
}
|
||
.pane-head {
|
||
display: flex;
|
||
flex-shrink: 0;
|
||
height: 44px;
|
||
border-bottom: 1px solid var(--border);
|
||
}
|
||
.pane-head button {
|
||
flex: 1;
|
||
border: 0;
|
||
background: none;
|
||
color: var(--muted);
|
||
font-size: 14px;
|
||
font-weight: 600;
|
||
cursor: pointer;
|
||
}
|
||
.pane-head button.active {
|
||
color: var(--text);
|
||
box-shadow: inset 0 -2px 0 var(--primary);
|
||
}
|
||
.pane-scroll {
|
||
flex: 1;
|
||
min-height: 0;
|
||
padding: 12px;
|
||
overflow: auto;
|
||
}
|
||
.group-title {
|
||
margin: 12px 0 8px;
|
||
color: var(--muted);
|
||
font-size: 12px;
|
||
font-weight: 600;
|
||
}
|
||
.group-title:first-child { margin-top: 0; }
|
||
.ctrl-grid {
|
||
display: grid;
|
||
grid-template-columns: 1fr 1fr;
|
||
gap: 8px;
|
||
}
|
||
.ctrl {
|
||
display: flex;
|
||
gap: 8px;
|
||
align-items: center;
|
||
padding: 10px 10px;
|
||
cursor: grab;
|
||
user-select: none;
|
||
background: #f8fafc;
|
||
border: 1px solid var(--border);
|
||
border-radius: 8px;
|
||
font-size: 13px;
|
||
color: var(--text-2);
|
||
transition: border-color 0.15s, background 0.15s;
|
||
}
|
||
.ctrl:hover {
|
||
background: var(--primary-bg);
|
||
border-color: #a7f3d0;
|
||
color: var(--primary-dark);
|
||
}
|
||
.ctrl-ico {
|
||
display: grid;
|
||
place-items: center;
|
||
width: 28px;
|
||
height: 28px;
|
||
border-radius: 6px;
|
||
background: #fff;
|
||
border: 1px solid var(--border);
|
||
font-size: 14px;
|
||
}
|
||
|
||
/* 中:画布 */
|
||
.pane-mid {
|
||
display: flex;
|
||
flex: 1 1 auto;
|
||
flex-direction: column;
|
||
min-width: 0;
|
||
background: #f8fafc;
|
||
}
|
||
.mid-tools {
|
||
display: flex;
|
||
flex-shrink: 0;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
height: var(--top-h);
|
||
padding: 0 12px;
|
||
background: #fff;
|
||
border-bottom: 1px solid var(--border);
|
||
}
|
||
.mid-tools-l {
|
||
display: flex;
|
||
gap: 4px;
|
||
align-items: center;
|
||
}
|
||
.tool-ico {
|
||
display: grid;
|
||
place-items: center;
|
||
width: 32px;
|
||
height: 32px;
|
||
border: 0;
|
||
border-radius: 6px;
|
||
background: transparent;
|
||
color: #64748b;
|
||
cursor: pointer;
|
||
font-size: 15px;
|
||
}
|
||
.tool-ico:hover { background: #f1f5f9; }
|
||
.tool-ico.active {
|
||
color: var(--primary-dark);
|
||
background: var(--primary-bg);
|
||
}
|
||
.tool-line {
|
||
width: 1px;
|
||
height: 18px;
|
||
margin: 0 6px;
|
||
background: var(--border);
|
||
}
|
||
|
||
.canvas-area {
|
||
display: flex;
|
||
flex: 1;
|
||
justify-content: center;
|
||
align-items: flex-start;
|
||
min-height: 0;
|
||
padding: 20px;
|
||
overflow: auto;
|
||
}
|
||
.phone {
|
||
width: min(420px, 100%);
|
||
min-height: 520px;
|
||
padding: 16px;
|
||
background: #fff;
|
||
border: 1px solid var(--border-strong);
|
||
border-radius: 12px;
|
||
box-shadow: 0 4px 16px rgb(15 23 42 / 6%);
|
||
}
|
||
.phone.wide { width: min(720px, 100%); }
|
||
.field {
|
||
margin-bottom: 14px;
|
||
padding: 10px 12px;
|
||
border: 1px solid transparent;
|
||
border-radius: 8px;
|
||
cursor: pointer;
|
||
}
|
||
.field:hover { background: #f8fafc; }
|
||
.field.active {
|
||
background: var(--primary-bg);
|
||
border-color: #a7f3d0;
|
||
box-shadow: 0 0 0 2px rgb(16 185 129 / 12%);
|
||
}
|
||
.field label {
|
||
display: block;
|
||
margin-bottom: 6px;
|
||
color: var(--text-2);
|
||
font-size: 13px;
|
||
font-weight: 500;
|
||
}
|
||
.field .fake-input {
|
||
height: 36px;
|
||
padding: 0 12px;
|
||
color: var(--muted);
|
||
font-size: 13px;
|
||
line-height: 36px;
|
||
background: #fff;
|
||
border: 1px solid var(--border);
|
||
border-radius: 8px;
|
||
}
|
||
.drop-zone {
|
||
margin-top: 8px;
|
||
padding: 28px 16px;
|
||
color: var(--muted);
|
||
font-size: 13px;
|
||
text-align: center;
|
||
border: 1px dashed #a7f3d0;
|
||
border-radius: 8px;
|
||
background: #fafffe;
|
||
}
|
||
|
||
/* 右:属性设置 */
|
||
.pane-right {
|
||
display: flex;
|
||
flex: 0 0 var(--right-w);
|
||
flex-direction: column;
|
||
min-width: 0;
|
||
background: #fff;
|
||
border-left: 1px solid var(--border);
|
||
}
|
||
.prop-body {
|
||
flex: 1;
|
||
min-height: 0;
|
||
padding: 14px 16px;
|
||
overflow: auto;
|
||
}
|
||
.prop-body h4 {
|
||
margin: 0 0 14px;
|
||
font-size: 14px;
|
||
font-weight: 600;
|
||
}
|
||
.prop-item { margin-bottom: 14px; }
|
||
.prop-item label {
|
||
display: block;
|
||
margin-bottom: 6px;
|
||
color: var(--muted);
|
||
font-size: 12px;
|
||
}
|
||
.prop-item input,
|
||
.prop-item select,
|
||
.prop-item textarea {
|
||
width: 100%;
|
||
height: 36px;
|
||
padding: 0 10px;
|
||
border: 1px solid var(--border);
|
||
border-radius: 8px;
|
||
font: inherit;
|
||
color: var(--text);
|
||
background: #fff;
|
||
}
|
||
.prop-item textarea {
|
||
height: 72px;
|
||
padding: 8px 10px;
|
||
resize: vertical;
|
||
}
|
||
.prop-item .row {
|
||
display: flex;
|
||
gap: 8px;
|
||
align-items: center;
|
||
}
|
||
.switch {
|
||
width: 40px;
|
||
height: 22px;
|
||
border-radius: 999px;
|
||
background: var(--primary);
|
||
position: relative;
|
||
cursor: pointer;
|
||
flex-shrink: 0;
|
||
}
|
||
.switch::after {
|
||
content: "";
|
||
position: absolute;
|
||
top: 2px;
|
||
left: 20px;
|
||
width: 18px;
|
||
height: 18px;
|
||
background: #fff;
|
||
border-radius: 50%;
|
||
}
|
||
|
||
@media (max-width: 960px) {
|
||
:root {
|
||
--left-w: 220px;
|
||
--right-w: 240px;
|
||
}
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="page">
|
||
<div class="banner">
|
||
<div>
|
||
<b>表单设计 · 三栏布局 Demo</b>
|
||
<span>左:控件库 · 中:画布 · 右:选中控件属性(对照钉钉 / FormCreate)</span>
|
||
</div>
|
||
<span class="tag">Fresh Mint</span>
|
||
</div>
|
||
|
||
<div class="toolbar">
|
||
<button type="button" class="btn">← 返回</button>
|
||
<div style="display:flex;gap:8px">
|
||
<button type="button" class="btn">保存草稿</button>
|
||
<button type="button" class="btn primary">发布</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="tabs">
|
||
<button type="button" class="tab">1 基础设置</button>
|
||
<button type="button" class="tab active">2 表单设计</button>
|
||
<button type="button" class="tab">3 流程设计</button>
|
||
<button type="button" class="tab">4 高级设置</button>
|
||
</div>
|
||
|
||
<p class="hint">拖拽左侧控件到中间画布;顶部可切换 PC / 手机预览。选中控件后在右侧配置属性。</p>
|
||
|
||
<div class="designer" id="designer">
|
||
<!-- 极窄模块图标 -->
|
||
<aside class="side-menu" aria-label="模块">
|
||
<button type="button" class="active" title="组件">▦</button>
|
||
<button type="button" title="多语言">🌐</button>
|
||
<button type="button" title="JSON"></></button>
|
||
<button type="button" title="AI" style="color:var(--primary-dark);font-weight:700;font-size:12px">AI</button>
|
||
</aside>
|
||
|
||
<!-- 左:控件 -->
|
||
<aside class="pane-left">
|
||
<div class="pane-head">
|
||
<button type="button" class="active">组件</button>
|
||
<button type="button">大纲</button>
|
||
</div>
|
||
<div class="pane-scroll">
|
||
<div class="group-title">布局</div>
|
||
<div class="ctrl-grid">
|
||
<div class="ctrl"><span class="ctrl-ico">▤</span>分栏</div>
|
||
<div class="ctrl"><span class="ctrl-ico">▭</span>分组</div>
|
||
</div>
|
||
<div class="group-title">基础</div>
|
||
<div class="ctrl-grid">
|
||
<div class="ctrl"><span class="ctrl-ico">Aa</span>单行</div>
|
||
<div class="ctrl"><span class="ctrl-ico">¶</span>多行</div>
|
||
<div class="ctrl"><span class="ctrl-ico">123</span>数字</div>
|
||
<div class="ctrl"><span class="ctrl-ico">◉</span>单选</div>
|
||
<div class="ctrl"><span class="ctrl-ico">☑</span>多选</div>
|
||
<div class="ctrl"><span class="ctrl-ico">📅</span>日期</div>
|
||
<div class="ctrl"><span class="ctrl-ico">☎</span>电话</div>
|
||
<div class="ctrl"><span class="ctrl-ico">¥</span>金额</div>
|
||
</div>
|
||
<div class="group-title">增强</div>
|
||
<div class="ctrl-grid">
|
||
<div class="ctrl"><span class="ctrl-ico">📎</span>附件</div>
|
||
<div class="ctrl"><span class="ctrl-ico">🏢</span>部门</div>
|
||
<div class="ctrl"><span class="ctrl-ico">📍</span>省市区</div>
|
||
<div class="ctrl"><span class="ctrl-ico">ƒ</span>公式</div>
|
||
</div>
|
||
</div>
|
||
</aside>
|
||
|
||
<!-- 中:画布 -->
|
||
<main class="pane-mid">
|
||
<div class="mid-tools">
|
||
<div class="mid-tools-l">
|
||
<button type="button" class="tool-ico" title="PC" data-device="pc">🖥</button>
|
||
<button type="button" class="tool-ico active" title="手机" data-device="mobile">📱</button>
|
||
<button type="button" class="tool-ico" title="平板" data-device="pad">▣</button>
|
||
<span class="tool-line"></span>
|
||
<button type="button" class="tool-ico" title="撤销">↶</button>
|
||
<button type="button" class="tool-ico" title="重做">↷</button>
|
||
</div>
|
||
<button type="button" class="btn">👁 预览</button>
|
||
</div>
|
||
<div class="canvas-area">
|
||
<div class="phone" id="phone">
|
||
<div class="field" data-name="请假类型">
|
||
<label>请假类型</label>
|
||
<div class="fake-input">请选择 ▾</div>
|
||
</div>
|
||
<div class="field active" data-name="开始日期">
|
||
<label>开始日期</label>
|
||
<div class="fake-input">2026-07-20</div>
|
||
</div>
|
||
<div class="field" data-name="事由">
|
||
<label>事由</label>
|
||
<div class="fake-input">请输入</div>
|
||
</div>
|
||
<div class="drop-zone">拖拽左侧控件到此处</div>
|
||
</div>
|
||
</div>
|
||
</main>
|
||
|
||
<!-- 右:属性 -->
|
||
<aside class="pane-right">
|
||
<div class="pane-head">
|
||
<button type="button" class="active">控件</button>
|
||
<button type="button">表单</button>
|
||
</div>
|
||
<div class="prop-body">
|
||
<h4 id="propTitle">开始日期</h4>
|
||
<div class="prop-item">
|
||
<label>标题</label>
|
||
<input id="propLabel" value="开始日期" />
|
||
</div>
|
||
<div class="prop-item">
|
||
<label>字段名</label>
|
||
<input value="startDate" />
|
||
</div>
|
||
<div class="prop-item">
|
||
<label>占位提示</label>
|
||
<input value="请选择日期" />
|
||
</div>
|
||
<div class="prop-item">
|
||
<label>必填</label>
|
||
<div class="row">
|
||
<span class="switch" title="开"></span>
|
||
<span style="color:var(--text-2);font-size:13px">是</span>
|
||
</div>
|
||
</div>
|
||
<div class="prop-item">
|
||
<label>说明</label>
|
||
<textarea>选中画布中的字段后,在此编辑属性(Demo)</textarea>
|
||
</div>
|
||
</div>
|
||
</aside>
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
const phone = document.getElementById('phone')
|
||
const propTitle = document.getElementById('propTitle')
|
||
const propLabel = document.getElementById('propLabel')
|
||
|
||
document.querySelectorAll('.field').forEach((el) => {
|
||
el.addEventListener('click', () => {
|
||
document.querySelectorAll('.field').forEach((f) => f.classList.remove('active'))
|
||
el.classList.add('active')
|
||
const name = el.dataset.name || '控件'
|
||
propTitle.textContent = name
|
||
propLabel.value = name
|
||
})
|
||
})
|
||
|
||
document.querySelectorAll('.tool-ico[data-device]').forEach((btn) => {
|
||
btn.addEventListener('click', () => {
|
||
document.querySelectorAll('.tool-ico[data-device]').forEach((b) => b.classList.remove('active'))
|
||
btn.classList.add('active')
|
||
phone.classList.toggle('wide', btn.dataset.device === 'pc')
|
||
})
|
||
})
|
||
</script>
|
||
</body>
|
||
</html>
|