Files
cms-SiteForge/examples/Studio_V8_24_深层嵌套可视化编辑器_交互演示.html
T

55 lines
13 KiB
HTML
Raw Normal View History

<!doctype html><html lang="zh-CN"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>SiteForge Studio V8.24 · 深层画布编辑</title><style>
*{box-sizing:border-box}body{margin:0;background:#f3f5f8;color:#263447;font:14px system-ui,-apple-system,'Microsoft YaHei',sans-serif}button,input,select{font:inherit}button{cursor:pointer}header{height:70px;display:flex;align-items:center;gap:16px;padding:0 22px;background:white;border-bottom:1px solid #e3e8ef}header strong{font-size:19px}header small{color:#8795a6}header .sep{flex:1}header button{border:1px solid #e0e5eb;border-radius:8px;padding:9px 15px;background:white;color:#34485e}header .save{background:#ec772f;border-color:#ec772f;color:#fff;font-weight:750}.step{padding:11px 25px;background:#fff8f2;border-bottom:1px solid #f1d9c4;color:#ad642e;font-weight:700}main{display:grid;grid-template-columns:246px minmax(0,1fr) 290px;height:calc(100vh - 112px);min-height:620px}aside{background:#fff;overflow-y:auto;padding:21px;border-right:1px solid #e7eaf0}aside:last-child{border-right:0;border-left:1px solid #e7eaf0}h3{margin:0 0 20px;font-size:16px}p.hint{color:#8995a6;font-size:12px;line-height:1.7;margin-top:6px}.group{margin:16px 0 9px;color:#8b99a9;font-size:11px;font-weight:800;letter-spacing:1px}select,input{border:1px solid #dce3ed;border-radius:8px;padding:10px;width:100%;background:white;color:#293d55}.navitem,.childlist{border:1px solid #e3e9f0;border-radius:9px;padding:11px;margin-bottom:8px;background:#fbfcfe;cursor:pointer}.navitem.selected,.childlist.selected{border-color:#e98037;background:#fff8f2}.navitem small,.childlist small{display:block;color:#8898a6;margin-top:5px}.add{width:100%;margin-top:13px;background:#e8782f;color:white;padding:12px;border:0;border-radius:9px;font-weight:800}.canvas-shell{display:flex;flex-direction:column;min-width:0}.canvas-head{background:#fff;height:51px;display:flex;align-items:center;justify-content:space-between;padding:0 24px;border-bottom:1px solid #e7ebf0;font-size:12px;color:#77879a}.switch button{background:#f1f4f8;padding:8px 10px;border:0;border-radius:6px;margin:0 2px;color:#63768a}.switch button.on{background:#ed7e37;color:#fff}.stage{flex:1;overflow:auto;padding:28px;background:radial-gradient(#d6dde8 1px,transparent 1px);background-size:20px 20px}.page{margin:auto;max-width:1050px;min-width:0;background:white;box-shadow:0 12px 48px #2437471c;transition:width .2s}.page.phone{width:min(390px,100%)}.page.tablet{width:min(780px,100%)}.sitehead{height:70px;border-bottom:1px solid #e7e7e7;display:flex;align-items:center;justify-content:space-between;padding:0 7%;color:#25364d}.logo{font-size:18px;font-weight:900;color:#e8752f}.sitehead small{font-size:12px;color:#6f7e8d}.hero{padding:40px 7% 22px}.hero h1{font-size:clamp(25px,3vw,36px);margin:0 0 8px}.hero p{color:#8793a1;margin:0}.layout{padding:12px 7% 60px;display:grid;gap:16px;grid-template-columns:var(--cols,1fr 1fr)}.page.phone .layout{grid-template-columns:1fr!important}.page.phone .inner-grid{grid-template-columns:1fr!important}.page.phone .inner-grid[style*="1fr 1fr"]{grid-template-columns:1fr 1fr!important}.page.tablet .layout{grid-template-columns:1fr 1fr}.unit{border:2px dashed transparent;background:#f5f7fa;min-height:125px;border-radius:12px;padding:20px 17px;cursor:pointer;position:relative;overflow:visible}.unit:hover{border-color:#e8b48e}.unit.active{border-color:#e8752f;background:#fffaf6}.unit h2{font-size:18px;margin:12px 0 8px}.unit p{color:#7c8b9b;font-size:13px;line-height:1.65}.unit b{font-size:11px;color:#e27530}.unit .mini{display:none;gap:4px;position:absolute;right:7px;top:7px;z-index:2}.unit:hover .mini,.unit.active .mini{display:flex}.mini button{font-size:11px;border:0;background:#34465a;color:#fff;border-radius:5px;padding:5px 7px}.placeholder{background:linear-gradient(135deg,#f7d2aa,#e9edf5);height:100px;border-radius:6px;margin:6px 0}.ctrl{margin-bottom:17px}.ctrl label{display:block;font-size:12px;color:#78899b;font-weight:750;margin-bottom:7px}.ctrl input{font-size:13px}.buttons{display:flex;gap:6px;flex-wrap:wrap}.buttons button{background:#fff8f1;color:#965522;border:1px solid #edc9aa;border-radius:7px;font-size:11px;padding:8px 10px}.buttons button:hover{background:#ffe9d
</style></head><body>
<header><strong><span style="color:#ea792e">◈</span> SiteForge Studio</strong><small>V8.24 · 深层画布编辑演示</small><span class="sep"></span><span id="status" style="font-size:12px;color:#8392a1">演示草稿</span><button onclick="undo()">↶ 撤销</button><button onclick="save()" class="save">保存草稿</button></header>
<div class="step">① 点击画布中的组件 → ② 直接编辑右侧内容或选择布局 → ③ 保存草稿</div>
<main><aside><h3>页面内容</h3><label style="font-size:12px;color:#70839a">当前页面</label><select><option>首页 /</option></select><div class="group">页面结构</div><div class="navitem">▤ 封面横幅<small>网站主标题</small></div><div class="navitem selected">▥ 双列布局<small>可直接操作内层布局和组件</small></div><div id="tree"></div><button class="add" onclick="add()">+ 在本列新增组件</button><p class="hint">此页面使用本地演示数据。实际系统具备草稿、版本校验与构建接口。</p></aside>
<div class="canvas-shell"><div class="canvas-head"><span>● 可视化预览 · 点击卡片直接编辑</span><span class="switch"><button class="on" onclick="device('desktop',this)">电脑</button><button onclick="device('tablet',this)">平板</button><button onclick="device('phone',this)">手机</button></span></div><div class="stage"><div class="page" id="page"><div class="sitehead"><span class="logo">HESU AUTO</span><small>HOME VEHICLES ABOUT CONTACT ☰</small></div><div class="hero"><h1>China Sourcing. Global Delivery.</h1><p>内外层布局、手机列数与媒体选择均可直接操作。</p></div><div class="layout" id="layout"></div></div></div><div class="meta">画布修改只影响演示数据 · 不会连接、修改或发布正式网站</div></div>
<aside><h3>编辑选中的组件</h3><div id="selectedInfo" class="notice">点击中间任一组件开始编辑。</div><div class="group">内容</div><div class="ctrl"><label>组件标题</label><input id="title" oninput="field('title',this.value)" placeholder="请输入标题"></div><div class="ctrl"><label>说明文字</label><input id="description" oninput="field('description',this.value)" placeholder="请输入简介"></div><div class="group">布局设置</div><div class="buttons"><button onclick="preset('equal')">等宽两列</button><button onclick="preset('left')">左宽右窄</button><button onclick="preset('right')">左窄右宽</button></div><p class="hint">手机布局独立显示为单列,不会覆盖电脑端的列宽设置。</p><div class="group">内层响应式</div><div class="buttons"><button onclick="mobileCols(1)">手机 1 列</button><button onclick="mobileCols(2)">手机 2 列</button></div><p id="mobileStatus" class="hint">手机 1 列</p><div class="group">组件操作</div><div class="buttons"><button onclick="duplicate()">复制组件</button><button onclick="up()">上移</button><button onclick="remove()">删除</button></div></aside></main>
<script>
// This is a deliberately isolated offline demonstration. It never calls a server.
let state={
rows:[{id:1,title:'采购流程',description:'中国供应链与整车采购方案',col:0},{id:2,title:'海外枢纽',description:'覆盖不同地区的本地服务网络',col:1}],
nested:[{id:4,title:'车辆选择',description:'帮助客户选到合适车型',col:0},{id:5,title:'运输方案',description:'灵活选择运输方式',col:1}],
selected:4,preset:'equal',innerPreset:'equal',mobile:1
};
let history=[];
const $=id=>document.getElementById(id);
const esc=x=>String(x??'').replace(/[&<>"']/g,c=>({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c]));
const current=()=>[...state.rows,...state.nested].find(x=>x.id===state.selected);
function snapshot(){history.push(JSON.stringify(state));if(history.length>30)history.shift()}
function changed(){ $('status').textContent='未保存的演示修改';draw() }
function card(r,name){return `<section class="unit ${state.selected===r.id?'active':''}" onclick="selectItem(${r.id})"><div class="mini"><button onclick="event.stopPropagation();selectItem(${r.id})">编辑</button><button onclick="event.stopPropagation();cloneItem(${r.id})">复制</button><button onclick="event.stopPropagation();dropItem(${r.id})">删除</button></div><b>${esc(name)}</b><h2 contenteditable="false" ondblclick="inlineTitle(event,${r.id})">${esc(r.title)}</h2><p>${esc(r.description)}</p></section>`}
function draw(){
const widths=v=>v==='left'?'2fr 1fr':v==='right'?'1fr 2fr':'1fr 1fr';
$('layout').style.setProperty('--cols',widths(state.preset));
const deepcols=[[],[]];state.nested.forEach(r=>deepcols[r.col].push(r));
const inner=`<div class="group" style="padding-top:5px;margin-bottom:10px">▥ 第二层布局 · 可直接点击里面的组件</div><div style="display:grid;grid-template-columns:${widths(state.innerPreset)};gap:10px" class="inner-grid" id="innerGrid">${deepcols.map((items,ci)=>`<div>${items.map(r=>card(r,'内层第 '+(ci+1)+' 列')).join('')}</div>`).join('')}</div>`;
const cols=[[],[]];state.rows.forEach(r=>cols[r.col].push(r));
$('layout').innerHTML=cols.map((items,ci)=>`<div>${items.map(r=>card(r,'外层第 '+(ci+1)+' 列')).join('')}${ci===0?inner:''}</div>`).join('');
if($('page').classList.contains('phone')){
$('innerGrid').style.gridTemplateColumns=state.mobile===1?'1fr':'1fr 1fr';
}
const both=[...state.rows.map(r=>({r,depth:0})),...state.nested.map(r=>({r,depth:1}))];
$('tree').innerHTML=both.map(({r,depth})=>`<div class="childlist ${state.selected===r.id?'selected':''}" onclick="selectItem(${r.id})" style="margin-left:${depth*13}px">${depth?'↳ 第二层':'第1层'} · ${esc(r.title)}<small>第${r.col+1}列 · 点击编辑</small></div>`).join('');
let row=current();$('selectedInfo').textContent=row?(state.nested.includes(row)?'第二层组件:':'外层组件:')+row.title:'点击画布上的组件';
$('title').value=row?.title||'';$('description').value=row?.description||'';
$('mobileStatus').textContent='手机设置:'+state.mobile+' 列,仅影响演示中的手机视图';
}
function selectItem(id){state.selected=id;draw()}
function field(key,value){const row=current();if(!row)return;row[key]=value;changed()}
function preset(value){snapshot();const row=current();if(row && state.nested.includes(row))state.innerPreset=value;else state.preset=value;changed()}
function mobileCols(n){snapshot();state.mobile=n;changed()}
function inlineTitle(event,id){event.stopPropagation();selectItem(id);const el=document.querySelector('.unit.active h2');if(!el)return;el.contentEditable='true';el.focus();const finish=()=>{const row=current();if(row){snapshot();row.title=el.textContent.trim().slice(0,140)}el.contentEditable='false';changed()};el.addEventListener('blur',finish,{once:true});el.addEventListener('keydown',e=>{if(e.key==='Enter'){e.preventDefault();el.blur()}})}
function cloneItem(id){const row=[...state.rows,...state.nested].find(x=>x.id===id);if(!row)return;snapshot();const next={...row,id:Math.max(...[...state.rows,...state.nested].map(x=>x.id))+1};(state.nested.includes(row)?state.nested:state.rows).push(next);state.selected=next.id;changed()}
function duplicate(){cloneItem(state.selected)}
function dropItem(id){snapshot();state.rows=state.rows.filter(x=>x.id!==id);state.nested=state.nested.filter(x=>x.id!==id);state.selected=[...state.nested,...state.rows][0]?.id||null;changed()}
function remove(){dropItem(state.selected)}
function up(){const group=state.nested.some(r=>r.id===state.selected)?state.nested:state.rows;const i=group.findIndex(r=>r.id===state.selected);if(i<1)return;snapshot();[group[i],group[i-1]]=[group[i-1],group[i]];changed()}
function add(){snapshot();const group=current()&&state.nested.includes(current())?state.nested:state.rows;const r={id:Math.max(...[...state.rows,...state.nested].map(x=>x.id))+1,title:'新组件',description:'点击并输入需要展示的内容',col:current()?.col||0};group.push(r);state.selected=r.id;changed()}
function undo(){if(!history.length)return;state=JSON.parse(history.pop());changed()}
function save(){$('status').textContent='演示草稿已保存(未连接服务器)'}
function device(name,button){$('page').className='page '+(name==='desktop'?'':name);document.querySelectorAll('.switch button').forEach(b=>b.classList.toggle('on',b===button));draw()}
draw();
</script></body></html>