Files
yidaima/RuoYi-Vue/docs/mockups/page-designer-menu-tree.html

1161 lines
31 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" />
<title>EasyCode 页面菜单式设计器原型</title>
<style>
:root {
--bg: #f4f7fb;
--panel: #ffffff;
--line: #dfe6f0;
--soft: #f8fafc;
--text: #172033;
--muted: #667085;
--faint: #98a2b3;
--primary: #2563eb;
--primary-soft: #eaf2ff;
--green: #0f9f6e;
--amber: #b7791f;
--danger: #dc2626;
font-family: "Inter", "PingFang SC", "Microsoft YaHei", Arial, sans-serif;
}
* { box-sizing: border-box; }
body {
margin: 0;
min-height: 100vh;
background: var(--bg);
color: var(--text);
font-size: 14px;
}
button,
input,
select {
font: inherit;
}
button { cursor: pointer; }
.app {
min-height: 100vh;
display: grid;
grid-template-rows: 64px minmax(0, 1fr);
}
.topbar {
display: flex;
align-items: center;
justify-content: space-between;
gap: 20px;
padding: 0 28px;
border-bottom: 1px solid var(--line);
background: var(--panel);
}
.brand {
display: flex;
align-items: center;
gap: 12px;
min-width: 220px;
}
.brand-mark {
width: 36px;
height: 36px;
display: grid;
place-items: center;
border-radius: 8px;
background: var(--primary);
color: #fff;
font-weight: 800;
}
.brand strong { font-size: 20px; }
.nav {
display: flex;
gap: 26px;
color: #475467;
white-space: nowrap;
}
.nav .active {
color: var(--primary);
font-weight: 700;
}
.userbar {
display: flex;
align-items: center;
gap: 10px;
color: #475467;
white-space: nowrap;
}
.designer-page {
min-height: 0;
display: grid;
grid-template-rows: auto minmax(0, 1fr);
gap: 14px;
padding: 20px 24px 24px;
}
.designer-head {
display: grid;
gap: 12px;
padding: 16px 18px;
border: 1px solid var(--line);
border-radius: 8px;
background: var(--panel);
}
.head-row,
.head-subrow {
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
}
.title {
display: grid;
gap: 4px;
min-width: 0;
}
.crumb {
color: var(--muted);
font-size: 13px;
}
.title h1 {
margin: 0;
color: var(--text);
font-size: 22px;
line-height: 1.2;
}
.title p {
margin: 0;
color: var(--muted);
line-height: 1.45;
}
.actions,
.switch-row,
.canvas-actions {
display: flex;
align-items: center;
gap: 8px;
flex-wrap: wrap;
}
.btn {
min-height: 36px;
padding: 7px 13px;
border: 1px solid var(--line);
border-radius: 6px;
background: #fff;
color: #344054;
}
.btn.primary {
border-color: var(--primary);
background: var(--primary);
color: #fff;
font-weight: 700;
}
.btn.soft { background: var(--soft); }
.btn.icon {
width: 32px;
min-height: 32px;
padding: 0;
display: grid;
place-items: center;
}
.badge {
display: inline-flex;
align-items: center;
min-height: 26px;
padding: 4px 9px;
border-radius: 999px;
background: #fff8e6;
color: var(--amber);
font-size: 12px;
font-weight: 700;
white-space: nowrap;
}
.segmented {
display: flex;
align-items: center;
gap: 6px;
padding: 4px;
border: 1px solid var(--line);
border-radius: 8px;
background: var(--soft);
}
.segmented button {
min-height: 30px;
padding: 5px 12px;
border: 0;
border-radius: 6px;
background: transparent;
color: var(--muted);
}
.segmented button.active {
background: #fff;
color: var(--primary);
font-weight: 700;
box-shadow: 0 1px 4px rgba(16, 24, 40, 0.08);
}
.designer-grid {
min-height: 0;
display: grid;
grid-template-columns: 292px minmax(520px, 1fr) 336px;
gap: 14px;
}
.panel {
min-height: 0;
overflow: hidden;
display: grid;
grid-template-rows: auto minmax(0, 1fr);
border: 1px solid var(--line);
border-radius: 8px;
background: var(--panel);
}
.left-panel {
grid-template-rows: minmax(0, 0.95fr) minmax(0, 1.05fr);
gap: 12px;
border: 0;
background: transparent;
overflow: visible;
}
.left-card {
min-height: 0;
overflow: hidden;
display: grid;
grid-template-rows: auto minmax(0, 1fr);
border: 1px solid var(--line);
border-radius: 8px;
background: var(--panel);
}
.panel-head {
min-height: 50px;
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
padding: 12px 14px;
border-bottom: 1px solid var(--line);
background: #fff;
}
.panel-head strong { font-size: 15px; }
.panel-body {
min-height: 0;
overflow: auto;
padding: 14px;
}
.muted {
color: var(--muted);
font-size: 12px;
}
.page-menu-tree,
.structure-tree {
display: grid;
gap: 8px;
}
.menu-group {
display: grid;
gap: 6px;
}
.menu-parent {
width: 100%;
min-height: 36px;
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
padding: 7px 9px;
border: 1px solid #e8eef7;
border-radius: 7px;
background: var(--soft);
color: #344054;
font-weight: 700;
text-align: left;
}
.menu-children {
display: grid;
gap: 5px;
padding-left: 14px;
}
.menu-page {
min-height: 42px;
display: grid;
grid-template-columns: minmax(0, 1fr) auto;
gap: 8px;
align-items: center;
padding: 8px 9px;
border: 1px solid #eef2f7;
border-radius: 7px;
background: #fff;
text-align: left;
}
.menu-page.active {
border-color: var(--primary);
background: var(--primary-soft);
color: var(--primary);
box-shadow: 0 0 0 2px rgba(37, 99, 235, 0.08);
}
.menu-page strong {
display: block;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-size: 14px;
}
.menu-page span {
display: block;
margin-top: 3px;
overflow: hidden;
color: var(--muted);
font-size: 12px;
text-overflow: ellipsis;
white-space: nowrap;
}
.menu-page.active span { color: #3b6fd9; }
.page-type {
padding: 4px 7px;
border-radius: 999px;
background: #f2f4f7;
color: var(--muted);
font-size: 12px;
white-space: nowrap;
}
.menu-page.active .page-type {
background: #fff;
color: var(--primary);
}
.tree-group {
display: grid;
gap: 6px;
padding: 10px;
border: 1px solid #e8eef7;
border-radius: 8px;
background: var(--soft);
}
.tree-group > button,
.tree-child {
width: 100%;
min-height: 32px;
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
padding: 6px 8px;
border: 1px solid transparent;
border-radius: 6px;
background: transparent;
color: #344054;
text-align: left;
}
.tree-group > button.active,
.tree-child.active {
border-color: var(--primary);
background: var(--primary-soft);
color: var(--primary);
font-weight: 700;
}
.tree-children {
display: grid;
gap: 4px;
padding-left: 14px;
}
.tree-child {
color: var(--muted);
background: #fff;
border-color: #eef2f7;
}
.count {
color: var(--faint);
font-size: 12px;
}
.canvas-panel { background: #f9fbff; }
.canvas-body {
min-height: 0;
overflow: auto;
padding: 22px;
display: grid;
justify-items: center;
}
.page-frame {
width: min(100%, 880px);
display: grid;
gap: 14px;
}
.canvas-band {
padding: 14px;
border: 1px solid #dce6f3;
border-radius: 8px;
background: #fff;
}
.canvas-band.active {
border-color: var(--primary);
box-shadow: 0 0 0 2px rgba(37, 99, 235, 0.08);
}
.band-title {
display: flex;
align-items: center;
justify-content: space-between;
gap: 10px;
margin-bottom: 12px;
}
.band-title strong { font-size: 15px; }
.band-title span {
color: var(--muted);
font-size: 12px;
}
.block-row,
.field-row,
.action-row {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
.chip {
display: inline-flex;
align-items: center;
gap: 6px;
min-height: 32px;
padding: 6px 10px;
border: 1px solid var(--line);
border-radius: 6px;
background: #fff;
color: #344054;
}
.chip.block {
border-color: #cfe0f5;
background: #f5f9ff;
color: #1d4ed8;
font-weight: 700;
}
.chip.action {
border-color: #bbf7d0;
background: #ecfdf5;
color: var(--green);
font-weight: 700;
}
.query-grid {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr)) auto;
gap: 10px;
}
.input-preview {
min-height: 36px;
display: flex;
align-items: center;
padding: 0 10px;
border: 1px solid var(--line);
border-radius: 6px;
background: #fff;
color: var(--muted);
}
.card-grid {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 12px;
}
.book-card {
display: grid;
gap: 10px;
padding: 12px;
border: 1px solid var(--line);
border-radius: 8px;
background: #fff;
}
.cover {
height: 76px;
display: grid;
place-items: center;
border-radius: 6px;
background: #eaf2ff;
color: var(--primary);
font-size: 12px;
}
.book-meta {
display: flex;
flex-wrap: wrap;
gap: 6px;
}
.book-meta span {
padding: 4px 7px;
border-radius: 5px;
background: #f2f4f7;
color: var(--muted);
font-size: 12px;
}
.inspector {
display: grid;
align-content: start;
gap: 14px;
}
.inspector-title {
display: grid;
gap: 4px;
padding-bottom: 12px;
border-bottom: 1px solid var(--line);
}
.inspector-title strong { font-size: 16px; }
.inspector-title span {
color: var(--muted);
font-size: 12px;
}
.inspector-box {
display: grid;
gap: 10px;
padding: 12px;
border: 1px solid var(--line);
border-radius: 8px;
background: var(--soft);
}
.form-grid {
display: grid;
gap: 12px;
}
.field {
display: grid;
gap: 6px;
}
.field label {
color: #344054;
font-size: 13px;
font-weight: 700;
}
.field input,
.field select {
width: 100%;
min-height: 36px;
padding: 0 10px;
border: 1px solid var(--line);
border-radius: 6px;
background: #fff;
color: #344054;
}
.json-line {
display: flex;
align-items: center;
justify-content: space-between;
gap: 10px;
padding-top: 12px;
border-top: 1px solid var(--line);
color: var(--muted);
font-size: 13px;
}
.drawer-mask {
position: fixed;
inset: 0;
display: none;
background: rgba(15, 23, 42, 0.28);
z-index: 10;
}
.drawer-mask.open { display: block; }
.drawer {
position: absolute;
top: 0;
right: 0;
width: min(440px, 100vw);
height: 100%;
display: grid;
grid-template-rows: auto auto minmax(0, 1fr);
background: #fff;
box-shadow: -12px 0 32px rgba(15, 23, 42, 0.18);
}
.drawer-head {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
padding: 16px;
border-bottom: 1px solid var(--line);
}
.drawer-head h2 {
margin: 0;
font-size: 18px;
}
.drawer-tabs {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 8px;
padding: 12px 16px;
border-bottom: 1px solid var(--line);
}
.drawer-tabs button {
min-height: 34px;
border: 1px solid var(--line);
border-radius: 6px;
background: #fff;
color: var(--muted);
}
.drawer-tabs button.active {
border-color: var(--primary);
background: var(--primary-soft);
color: var(--primary);
font-weight: 700;
}
.drawer-body {
min-height: 0;
overflow: auto;
padding: 16px;
display: grid;
align-content: start;
gap: 10px;
}
.material {
display: grid;
gap: 5px;
padding: 12px;
border: 1px solid var(--line);
border-radius: 8px;
background: #fff;
}
.material strong { font-size: 14px; }
.material span {
color: var(--muted);
font-size: 12px;
}
@media (max-width: 1320px) {
.designer-grid {
grid-template-columns: 276px minmax(420px, 1fr);
}
.right-panel {
grid-column: 1 / -1;
}
}
@media (max-width: 900px) {
.topbar,
.head-row,
.head-subrow {
align-items: stretch;
flex-direction: column;
}
.designer-grid,
.query-grid,
.card-grid {
grid-template-columns: 1fr;
}
.actions {
justify-content: flex-start;
}
}
</style>
</head>
<body>
<div class="app">
<header class="topbar">
<div class="brand">
<div class="brand-mark">EC</div>
<strong>EasyCode</strong>
</div>
<nav class="nav">
<span>主页</span>
<span class="active">工作台</span>
<span>源码库</span>
<span>我的项目</span>
</nav>
<div class="userbar">
<span>wangpeng</span>
<button class="btn">退出</button>
</div>
</header>
<main class="designer-page">
<section class="designer-head">
<div class="head-row">
<div class="title">
<span class="crumb">图书借阅系统 / 前台页面设计</span>
<h1 id="pageTitle">图书目录</h1>
<p id="pageMeta">/books · 卡片列表 · 图书信息表。左侧用一级/二级菜单切页面,下面同步当前页面结构。</p>
</div>
<div class="actions">
<span class="badge">有未保存修改</span>
<button class="btn" id="addContentTop">添加内容</button>
<button class="btn">预览页面</button>
<button class="btn primary">保存设计</button>
<button class="btn">返回工作台</button>
</div>
</div>
<div class="head-subrow">
<div class="switch-row">
<div class="segmented">
<button class="active">前台</button>
<button>后台</button>
</div>
<span class="muted" id="currentPagePath">当前页面:图书服务 / 图书目录</span>
</div>
<div class="canvas-actions">
<button class="btn soft">桌面</button>
<button class="btn">手机</button>
<button class="btn">撤销</button>
<button class="btn">重做</button>
</div>
</div>
</section>
<section class="designer-grid">
<aside class="left-panel">
<section class="left-card">
<div class="panel-head">
<strong>页面菜单</strong>
<button class="btn icon" title="新增页面">+</button>
</div>
<div class="panel-body">
<div class="page-menu-tree">
<div class="menu-group">
<button class="menu-parent" type="button">
<span>图书服务</span>
<span class="count">2</span>
</button>
<div class="menu-children">
<button class="menu-page active" type="button">
<span>
<strong>图书目录</strong>
<span>/books</span>
</span>
<em class="page-type">列表</em>
</button>
<button class="menu-page" type="button">
<span>
<strong>图书详情</strong>
<span>/books/:id</span>
</span>
<em class="page-type">详情</em>
</button>
</div>
</div>
<div class="menu-group">
<button class="menu-parent" type="button">
<span>我的中心</span>
<span class="count">2</span>
</button>
<div class="menu-children">
<button class="menu-page" type="button">
<span>
<strong>我的借阅</strong>
<span>/borrowings</span>
</span>
<em class="page-type">我的</em>
</button>
<button class="menu-page" type="button">
<span>
<strong>我的预约</strong>
<span>/reservations</span>
</span>
<em class="page-type">我的</em>
</button>
</div>
</div>
<div class="menu-group">
<button class="menu-parent" type="button">
<span>业务页面</span>
<span class="count">2</span>
</button>
<div class="menu-children">
<button class="menu-page" type="button">
<span>
<strong>购物车</strong>
<span>/cart</span>
</span>
<em class="page-type">业务块</em>
</button>
<button class="menu-page" type="button">
<span>
<strong>订单中心</strong>
<span>/orders</span>
</span>
<em class="page-type">业务块</em>
</button>
</div>
</div>
</div>
</div>
</section>
<section class="left-card">
<div class="panel-head">
<strong>当前页面结构</strong>
<span class="muted">点击配置</span>
</div>
<div class="panel-body">
<div class="structure-tree">
<div class="tree-group">
<button class="active" type="button">
<span>顶部业务块</span>
<span class="count">2</span>
</button>
<div class="tree-children">
<button class="tree-child active" type="button">轮播图</button>
<button class="tree-child" type="button">公告信息</button>
</div>
</div>
<div class="tree-group">
<button type="button">
<span>查询区</span>
<span class="count">3</span>
</button>
<div class="tree-children">
<button class="tree-child" type="button">书名</button>
<button class="tree-child" type="button">作者</button>
<button class="tree-child" type="button">图书分类</button>
</div>
</div>
<div class="tree-group">
<button type="button">
<span>卡片列表</span>
<span class="count">7</span>
</button>
<div class="tree-children">
<button class="tree-child" type="button">封面图片URL</button>
<button class="tree-child" type="button">书名</button>
<button class="tree-child" type="button">作者</button>
<button class="tree-child" type="button">借书按钮</button>
<button class="tree-child" type="button">预约按钮</button>
</div>
</div>
<div class="tree-group">
<button type="button">
<span>底部业务块</span>
<span class="count">0</span>
</button>
</div>
</div>
</div>
</section>
</aside>
<section class="panel canvas-panel">
<div class="panel-head">
<div>
<strong>画布</strong>
<span class="muted" id="selectedNodeText">当前选中:轮播图</span>
</div>
<button class="btn" id="addContentCanvas">添加内容</button>
</div>
<div class="canvas-body">
<div class="page-frame">
<section class="canvas-band active">
<div class="band-title">
<strong>顶部业务块</strong>
<span>2 个业务块</span>
</div>
<div class="block-row">
<span class="chip block">轮播图</span>
<span class="chip block">公告信息</span>
</div>
</section>
<section class="canvas-band">
<div class="band-title">
<strong>查询区</strong>
<span>3 个条件</span>
</div>
<div class="query-grid">
<div class="input-preview">书名</div>
<div class="input-preview">作者</div>
<div class="input-preview">图书分类</div>
<button class="btn primary">搜索</button>
</div>
</section>
<section class="canvas-band">
<div class="band-title">
<strong>卡片列表</strong>
<span>封面 + 标题 + 操作按钮</span>
</div>
<div class="card-grid">
<article class="book-card">
<div class="cover">封面图片URL</div>
<strong>书名</strong>
<div class="book-meta">
<span>作者</span>
<span>出版社</span>
<span>可借副本数</span>
</div>
<div class="action-row">
<span class="chip action">借书</span>
<span class="chip action">预约</span>
</div>
</article>
<article class="book-card">
<div class="cover">封面图片URL</div>
<strong>书名</strong>
<div class="book-meta">
<span>作者</span>
<span>出版社</span>
<span>可借副本数</span>
</div>
<div class="action-row">
<span class="chip action">借书</span>
<span class="chip action">预约</span>
</div>
</article>
<article class="book-card">
<div class="cover">封面图片URL</div>
<strong>书名</strong>
<div class="book-meta">
<span>作者</span>
<span>出版社</span>
<span>可借副本数</span>
</div>
<div class="action-row">
<span class="chip action">借书</span>
<span class="chip action">预约</span>
</div>
</article>
</div>
</section>
<section class="canvas-band">
<div class="band-title">
<strong>底部业务块</strong>
<span>可添加公告、轮播等</span>
</div>
<div class="field-row">
<span class="chip">空区域</span>
</div>
</section>
</div>
</div>
</section>
<aside class="panel right-panel">
<div class="panel-head">
<strong>属性配置</strong>
<button class="btn">高级</button>
</div>
<div class="panel-body inspector">
<div class="inspector-title">
<strong id="inspectorTitle">业务块配置:轮播图</strong>
<span>右侧只显示当前选中对象的配置JSON 默认收起。</span>
</div>
<div class="inspector-box">
<strong>数据映射</strong>
<div class="form-grid">
<div class="field">
<label>轮播图表</label>
<select>
<option>首页轮播表</option>
</select>
</div>
<div class="field">
<label>图片字段</label>
<select>
<option>封面图片URL</option>
</select>
</div>
<div class="field">
<label>标题字段</label>
<select>
<option>书名</option>
</select>
</div>
<div class="field">
<label>跳转字段</label>
<select>
<option>详情页路径</option>
</select>
</div>
</div>
</div>
<div class="json-line">
<span>JSON 高级设置</span>
<button class="btn">展开</button>
</div>
</div>
</aside>
</section>
</main>
</div>
<div id="drawerMask" class="drawer-mask">
<aside class="drawer">
<div class="drawer-head">
<div>
<h2>添加内容</h2>
<span class="muted">字段池、按钮池、业务块池都收进这里,不常驻占空间。</span>
</div>
<button id="closeDrawerBtn" class="btn">关闭</button>
</div>
<div class="drawer-tabs">
<button>结构</button>
<button class="active">字段</button>
<button>按钮</button>
<button>业务块</button>
</div>
<div class="drawer-body">
<div class="material">
<strong>书名</strong>
<span>字段 · 可拖入查询区或卡片列表</span>
</div>
<div class="material">
<strong>作者</strong>
<span>字段 · 可拖入查询区或卡片列表</span>
</div>
<div class="material">
<strong>封面图片URL</strong>
<span>图片字段 · 可拖入卡片封面</span>
</div>
<div class="material">
<strong>借书</strong>
<span>按钮 · 可拖入卡片操作区</span>
</div>
<div class="material">
<strong>公告信息</strong>
<span>业务块 · 可放到顶部或底部业务块区域</span>
</div>
<div class="material">
<strong>轮播图</strong>
<span>业务块 · 可放到顶部或底部业务块区域</span>
</div>
</div>
</aside>
</div>
<script>
const drawerMask = document.getElementById('drawerMask')
const openDrawer = () => drawerMask.classList.add('open')
const closeDrawer = () => drawerMask.classList.remove('open')
document.getElementById('addContentTop').addEventListener('click', openDrawer)
document.getElementById('addContentCanvas').addEventListener('click', openDrawer)
document.getElementById('closeDrawerBtn').addEventListener('click', closeDrawer)
drawerMask.addEventListener('click', (event) => {
if (event.target === drawerMask) closeDrawer()
})
const pageTitle = document.getElementById('pageTitle')
const pageMeta = document.getElementById('pageMeta')
const currentPagePath = document.getElementById('currentPagePath')
const selectedNodeText = document.getElementById('selectedNodeText')
const inspectorTitle = document.getElementById('inspectorTitle')
document.querySelectorAll('.menu-page').forEach((button) => {
button.addEventListener('click', () => {
document.querySelectorAll('.menu-page').forEach((item) => item.classList.remove('active'))
button.classList.add('active')
const group = button.closest('.menu-group').querySelector('.menu-parent span:first-child').textContent.trim()
const name = button.querySelector('strong').textContent.trim()
const route = button.querySelector('span span').textContent.trim()
const type = button.querySelector('.page-type').textContent.trim()
pageTitle.textContent = name
pageMeta.textContent = `${route} · ${type}页面。左侧用一级/二级菜单切页面,下面同步当前页面结构。`
currentPagePath.textContent = `当前页面:${group} / ${name}`
})
})
const getInspectorPrefix = (name) => {
if (name.includes('按钮')) return '按钮配置'
if (name.includes('字段') || name.includes('书名') || name.includes('作者') || name.includes('封面')) return '字段配置'
if (name.includes('轮播') || name.includes('公告') || name.includes('业务块')) return '业务块配置'
if (name.includes('查询') || name.includes('卡片') || name.includes('顶部') || name.includes('底部')) return '布局区域配置'
return '页面配置'
}
document.querySelectorAll('.structure-tree button').forEach((button) => {
button.addEventListener('click', () => {
document.querySelectorAll('.structure-tree button').forEach((item) => item.classList.remove('active'))
button.classList.add('active')
const name = button.querySelector('span') ? button.querySelector('span').textContent.trim() : button.textContent.trim()
selectedNodeText.textContent = `当前选中:${name}`
inspectorTitle.textContent = `${getInspectorPrefix(name)}${name}`
})
})
</script>
</body>
</html>