496 lines
21 KiB
JavaScript
496 lines
21 KiB
JavaScript
/* 可视化页面编辑器(可复用):三栏布局(图标栏 | 编辑画布 | 标签页右栏)
|
||
* 右栏:预览(只读实时模拟)/ 属性 / 素材(可删除)
|
||
* 支持元素类型:text 文字, image 图片, button 链接按钮, buy 产品购买, price 产品价格, specs 产品规格
|
||
* 对齐:左/中/右 + 竖(文字=竖排 writing-mode,图片=垂直居中)
|
||
* 依赖全局(由后台 partial 注入):
|
||
* __PB_INIT__ 初始布局数组
|
||
* __PB_MODULE__ page|product|news|case|category
|
||
* __PB_MEDIA__ 素材列表接口
|
||
* __PB_UPLOAD__ 上传接口
|
||
* __PB_DELETE__ 删除接口前缀(拼接文件名)
|
||
* __PB_CSRF__ CSRF 令牌
|
||
*/
|
||
(function () {
|
||
'use strict';
|
||
var STAGE_W = 720;
|
||
var stage = document.getElementById('pbStage');
|
||
if (!stage) return;
|
||
var MODULE = (window.__PB_MODULE__ || 'page');
|
||
|
||
var elements = Array.isArray(window.__PB_INIT__) ? window.__PB_INIT__.slice() : [];
|
||
var selectedId = null;
|
||
var zCounter = elements.reduce(function (m, e) { return Math.max(m, e.z || 0); }, 0);
|
||
|
||
function uid() { return 'el_' + Date.now().toString(36) + Math.floor(Math.random() * 1e4).toString(36); }
|
||
function nextZ() { return ++zCounter; }
|
||
function findEl(id) { for (var i = 0; i < elements.length; i++) if (elements[i].id === id) return elements[i]; return null; }
|
||
function nodeOf(id) { return stage.querySelector('[data-id="' + id + '"]'); }
|
||
function ptOf(ev) { return ev.touches && ev.touches[0] ? ev.touches[0] : ev; }
|
||
function on(id, evt, fn) { var el = document.getElementById(id); if (el) el.addEventListener(evt, fn); }
|
||
|
||
/* ---------- 对齐样式(文字:text-align + 竖排;图片:flex 水平 + 垂直居中) ---------- */
|
||
function styleAlign(el, node) {
|
||
node.style.textAlign = '';
|
||
node.style.writingMode = '';
|
||
node.style.display = '';
|
||
node.style.flexDirection = '';
|
||
node.style.alignItems = '';
|
||
node.style.justifyContent = '';
|
||
if (el.type === 'image') {
|
||
node.style.display = 'flex';
|
||
node.style.flexDirection = 'column';
|
||
node.style.alignItems = (el.align === 'center') ? 'center' : (el.align === 'right') ? 'flex-end' : 'flex-start';
|
||
node.style.justifyContent = el.v ? 'center' : 'flex-start';
|
||
} else if (el.type === 'text') {
|
||
node.style.textAlign = el.align || 'left';
|
||
if (el.v) node.style.writingMode = 'vertical-rl';
|
||
}
|
||
}
|
||
|
||
/* ---------- 通用指针拖拽(鼠标 + 触摸) ---------- */
|
||
function onPointer(ev, handlers) {
|
||
ev.preventDefault();
|
||
var move = function (e) { if (handlers.move) handlers.move(ptOf(e)); };
|
||
var up = function () {
|
||
document.removeEventListener('mousemove', move);
|
||
document.removeEventListener('mouseup', up);
|
||
document.removeEventListener('touchmove', move);
|
||
document.removeEventListener('touchend', up);
|
||
if (handlers.end) handlers.end();
|
||
};
|
||
document.addEventListener('mousemove', move);
|
||
document.addEventListener('mouseup', up);
|
||
document.addEventListener('touchmove', move, { passive: false });
|
||
document.addEventListener('touchend', up);
|
||
if (handlers.start) handlers.start(ptOf(ev));
|
||
}
|
||
|
||
/* ---------- 构建元素内部内容 ---------- */
|
||
function buildInner(el) {
|
||
if (el.type === 'image') {
|
||
var im = document.createElement('img');
|
||
im.className = 'pb-img'; im.src = el.src || ''; im.alt = ''; im.draggable = false;
|
||
im.addEventListener('load', updateStageSize);
|
||
return im;
|
||
}
|
||
if (el.type === 'button' || el.type === 'buy') {
|
||
var a = document.createElement('a');
|
||
a.className = 'pb-static pb-btn-prev' + (el.type === 'buy' ? ' pb-buy' : '');
|
||
a.textContent = el.type === 'buy' ? '立即购买' : (el.text || '按钮');
|
||
if (el.type === 'button') {
|
||
if (el.bg) a.style.background = el.bg;
|
||
if (el.tc) a.style.color = el.tc;
|
||
a.style.fontSize = (el.size === 'lg' ? '18px' : el.size === 'sm' ? '13px' : '15px');
|
||
}
|
||
return a;
|
||
}
|
||
if (el.type === 'price') {
|
||
var d = document.createElement('div');
|
||
d.className = 'pb-static pb-price-prev';
|
||
d.textContent = '¥ — 起/套';
|
||
if (el.color) d.style.color = el.color;
|
||
return d;
|
||
}
|
||
if (el.type === 'specs') {
|
||
var t = document.createElement('div');
|
||
t.className = 'pb-static pb-specs-prev';
|
||
t.innerHTML = '<div class="sp-row"><span>参数 1</span><span>值 1</span></div><div class="sp-row"><span>参数 2</span><span>值 2</span></div>';
|
||
return t;
|
||
}
|
||
var tx = document.createElement('div');
|
||
tx.className = 'pb-text';
|
||
tx.textContent = el.text != null ? el.text : '';
|
||
tx.style.fontSize = (el.fontSize || 18) + 'px';
|
||
tx.style.color = el.color || '#0f172a';
|
||
tx.style.fontWeight = el.bold ? '700' : '400';
|
||
tx.style.textAlign = el.align || 'left';
|
||
tx.style.lineHeight = '1.4';
|
||
return tx;
|
||
}
|
||
|
||
/* ---------- 渲染单个元素 ---------- */
|
||
function renderEl(el) {
|
||
var wrap = document.createElement('div');
|
||
wrap.className = 'pb-el';
|
||
wrap.setAttribute('data-id', el.id);
|
||
wrap.style.left = (el.x || 0) + 'px';
|
||
wrap.style.top = (el.y || 0) + 'px';
|
||
wrap.style.width = (el.w || 200) + 'px';
|
||
wrap.style.zIndex = el.z || 1;
|
||
wrap.appendChild(buildInner(el));
|
||
styleAlign(el, wrap);
|
||
|
||
var h = document.createElement('span');
|
||
h.className = 'pb-handle';
|
||
h.addEventListener('mousedown', function (e) { startResize(e, el); });
|
||
h.addEventListener('touchstart', function (e) { startResize(e, el); }, { passive: false });
|
||
wrap.appendChild(h);
|
||
|
||
wrap.addEventListener('mousedown', function (e) { startDrag(e, el); });
|
||
wrap.addEventListener('touchstart', function (e) { startDrag(e, el); }, { passive: false });
|
||
wrap.addEventListener('click', function (e) { e.stopPropagation(); selectEl(el.id); });
|
||
if (el.type === 'text') {
|
||
wrap.addEventListener('dblclick', function (e) { e.stopPropagation(); startEdit(el, wrap.firstChild); });
|
||
}
|
||
return wrap;
|
||
}
|
||
|
||
function renderAll() {
|
||
stage.innerHTML = '';
|
||
elements.forEach(function (el) { stage.appendChild(renderEl(el)); });
|
||
if (selectedId && nodeOf(selectedId)) nodeOf(selectedId).classList.add('sel');
|
||
updateStageSize();
|
||
renderPreview();
|
||
}
|
||
|
||
/* 画布高度自适应:容纳所有元素底部 */
|
||
function updateStageSize() {
|
||
var maxB = 480;
|
||
elements.forEach(function (el) {
|
||
var n = nodeOf(el.id);
|
||
if (!n) return;
|
||
var b = (el.y || 0) + n.offsetHeight;
|
||
if (b > maxB) maxB = b;
|
||
});
|
||
stage.style.minHeight = maxB + 'px';
|
||
}
|
||
|
||
function applyEl(el) {
|
||
var n = nodeOf(el.id);
|
||
if (!n) return;
|
||
n.style.width = (el.w || 200) + 'px';
|
||
styleAlign(el, n);
|
||
var inner = n.firstChild;
|
||
if (el.type === 'image') {
|
||
inner.src = el.src || '';
|
||
} else if (el.type === 'text') {
|
||
inner.textContent = el.text != null ? el.text : '';
|
||
inner.style.fontSize = (el.fontSize || 18) + 'px';
|
||
inner.style.color = el.color || '#0f172a';
|
||
inner.style.fontWeight = el.bold ? '700' : '400';
|
||
inner.style.textAlign = el.align || 'left';
|
||
inner.style.writingMode = el.v ? 'vertical-rl' : '';
|
||
} else if (el.type === 'button') {
|
||
inner.textContent = el.text || '按钮';
|
||
if (el.bg) inner.style.background = el.bg;
|
||
if (el.tc) inner.style.color = el.tc;
|
||
inner.style.fontSize = (el.size === 'lg' ? '18px' : el.size === 'sm' ? '13px' : '15px');
|
||
} else if (el.type === 'price') {
|
||
if (el.color) inner.style.color = el.color;
|
||
}
|
||
updateStageSize();
|
||
renderPreview();
|
||
}
|
||
|
||
/* ---------- 拖拽移动 ---------- */
|
||
function startDrag(e, el) {
|
||
if (e.target && e.target.classList.contains('pb-handle')) return;
|
||
e.stopPropagation();
|
||
selectEl(el.id);
|
||
var p = ptOf(e);
|
||
var sx = p.clientX, sy = p.clientY, ox = el.x || 0, oy = el.y || 0;
|
||
onPointer(e, {
|
||
move: function (p2) {
|
||
el.x = Math.max(0, ox + (p2.clientX - sx));
|
||
el.y = Math.max(0, oy + (p2.clientY - sy));
|
||
var n = nodeOf(el.id);
|
||
if (n) { n.style.left = el.x + 'px'; n.style.top = el.y + 'px'; }
|
||
}
|
||
});
|
||
}
|
||
|
||
/* ---------- 缩放(右下角) ---------- */
|
||
function startResize(e, el) {
|
||
e.stopPropagation(); e.preventDefault();
|
||
selectEl(el.id);
|
||
var p = ptOf(e);
|
||
var sx = p.clientX, sw = el.w || 200;
|
||
onPointer(e, {
|
||
move: function (p2) {
|
||
el.w = Math.max(40, sw + (p2.clientX - sx));
|
||
var n = nodeOf(el.id);
|
||
if (n) n.style.width = el.w + 'px';
|
||
}
|
||
});
|
||
}
|
||
|
||
/* ---------- 双击编辑文字 ---------- */
|
||
function startEdit(el, node) {
|
||
node.setAttribute('contenteditable', 'true');
|
||
node.focus();
|
||
try { document.getSelection().selectAllChildren(node); } catch (x) {}
|
||
function done() {
|
||
node.removeAttribute('contenteditable');
|
||
el.text = node.textContent;
|
||
node.removeEventListener('blur', done);
|
||
if (selectedId === el.id) { syncProps(); renderPreview(); }
|
||
}
|
||
node.addEventListener('blur', done);
|
||
}
|
||
|
||
/* ---------- 选中 / 属性面板 ---------- */
|
||
function selectEl(id) {
|
||
selectedId = id;
|
||
Array.prototype.forEach.call(stage.querySelectorAll('.pb-el'), function (n) {
|
||
n.classList.toggle('sel', n.getAttribute('data-id') === id);
|
||
});
|
||
renderProps(findEl(id));
|
||
if (id) switchTab('props');
|
||
}
|
||
|
||
function alignGroup(el, types) {
|
||
var html = '<label>对齐</label><div class="pb-align">';
|
||
html += '<button type="button" class="pb-ab" data-a="left">左</button>';
|
||
html += '<button type="button" class="pb-ab" data-a="center">中</button>';
|
||
html += '<button type="button" class="pb-ab" data-a="right">右</button>';
|
||
html += '<button type="button" class="pb-ab" data-a="v">竖</button>';
|
||
html += '</div>';
|
||
return html;
|
||
}
|
||
function wireAlign(el) {
|
||
document.querySelectorAll('#pbPropsBody .pb-ab').forEach(function (b) {
|
||
var a = b.getAttribute('data-a');
|
||
var on = (a === 'v') ? !!el.v : ((el.align || 'left') === a);
|
||
b.classList.toggle('active', on);
|
||
b.onclick = function () {
|
||
if (a === 'v') el.v = !el.v; else el.align = a;
|
||
wireAlign(el); applyEl(el); renderPreview();
|
||
};
|
||
});
|
||
}
|
||
|
||
function renderProps(el) {
|
||
var body = document.getElementById('pbPropsBody');
|
||
if (!el) { body.className = 'pb-empty'; body.textContent = '在画布中选择一个元素以编辑属性'; return; }
|
||
body.className = '';
|
||
if (el.type === 'text') {
|
||
body.innerHTML =
|
||
'<label>文字内容</label><textarea id="pp_text" rows="3"></textarea>' +
|
||
'<label>字号 (px)</label><input type="number" id="pp_size" min="10" max="160">' +
|
||
'<label>颜色</label><input type="color" id="pp_color">' +
|
||
'<label style="display:flex;align-items:center;gap:6px"><input type="checkbox" id="pp_bold"> 加粗</label>' +
|
||
alignGroup(el) +
|
||
'<button class="pb-del" id="pp_del" type="button">删除该元素</button>';
|
||
document.getElementById('pp_text').value = el.text != null ? el.text : '';
|
||
document.getElementById('pp_size').value = el.fontSize || 18;
|
||
document.getElementById('pp_color').value = el.color || '#0f172a';
|
||
document.getElementById('pp_bold').checked = !!el.bold;
|
||
bind('pp_text', 'input', function (v) { el.text = v; applyEl(el); });
|
||
bind('pp_size', 'input', function (v) { el.fontSize = parseInt(v, 10) || 18; applyEl(el); });
|
||
bind('pp_color', 'input', function (v) { el.color = v; applyEl(el); });
|
||
bind('pp_bold', 'change', function (v) { el.bold = !!v; applyEl(el); });
|
||
wireAlign(el);
|
||
} else if (el.type === 'image') {
|
||
body.innerHTML =
|
||
'<label>图片宽度 (px)</label><input type="number" id="pp_w" min="40" max="3000">' +
|
||
alignGroup(el) +
|
||
'<label>替换图片(点击素材)</label><div class="pb-lib" id="pp_lib"></div>' +
|
||
'<button class="pb-del" id="pp_del" type="button">删除该元素</button>';
|
||
document.getElementById('pp_w').value = el.w || 200;
|
||
bind('pp_w', 'input', function (v) { el.w = Math.max(40, parseInt(v, 10) || 200); applyEl(el); });
|
||
fillLib(document.getElementById('pp_lib'), function (url) { el.src = url; applyEl(el); });
|
||
wireAlign(el);
|
||
} else if (el.type === 'button') {
|
||
body.innerHTML =
|
||
'<label>按钮文字</label><input type="text" id="pp_text">' +
|
||
'<label>链接地址</label><input type="text" id="pp_href" placeholder="如 /contact 或 https://...">' +
|
||
'<label>背景色</label><input type="color" id="pp_bg">' +
|
||
'<label>文字颜色</label><input type="color" id="pp_tc">' +
|
||
'<label>尺寸</label><select id="pp_size"><option value="sm">小</option><option value="md">中</option><option value="lg">大</option></select>' +
|
||
'<button class="pb-del" id="pp_del" type="button">删除该元素</button>';
|
||
document.getElementById('pp_text').value = el.text || '按钮';
|
||
document.getElementById('pp_href').value = el.href || '';
|
||
document.getElementById('pp_bg').value = el.bg || '#0ea5e9';
|
||
document.getElementById('pp_tc').value = el.tc || '#ffffff';
|
||
document.getElementById('pp_size').value = el.size || 'md';
|
||
bind('pp_text', 'input', function (v) { el.text = v; applyEl(el); });
|
||
bind('pp_href', 'input', function (v) { el.href = v; });
|
||
bind('pp_bg', 'input', function (v) { el.bg = v; applyEl(el); });
|
||
bind('pp_tc', 'input', function (v) { el.tc = v; applyEl(el); });
|
||
bind('pp_size', 'change', function (v) { el.size = v; applyEl(el); });
|
||
} else if (el.type === 'price') {
|
||
body.innerHTML =
|
||
'<p class="pb-empty" style="margin-bottom:10px">自动显示本产品「价格」,无需填写。</p>' +
|
||
'<label>价格文字颜色</label><input type="color" id="pp_color">' +
|
||
'<button class="pb-del" id="pp_del" type="button">删除该元素</button>';
|
||
document.getElementById('pp_color').value = el.color || '#0f172a';
|
||
bind('pp_color', 'input', function (v) { el.color = v; applyEl(el); });
|
||
} else if (el.type === 'buy') {
|
||
body.innerHTML = '<p class="pb-empty">自动链接到本产品的「立即购买」下单页。</p><button class="pb-del" id="pp_del" type="button">删除该元素</button>';
|
||
} else if (el.type === 'specs') {
|
||
body.innerHTML = '<p class="pb-empty">自动显示本产品的「规格参数」。</p><button class="pb-del" id="pp_del" type="button">删除该元素</button>';
|
||
}
|
||
var del = document.getElementById('pp_del');
|
||
if (del) del.addEventListener('click', function () { removeEl(el.id); });
|
||
}
|
||
|
||
function bind(id, evt, fn) {
|
||
var el = document.getElementById(id);
|
||
if (!el) return;
|
||
el.addEventListener(evt, function () { fn(el.value !== undefined ? el.value : el.checked); });
|
||
}
|
||
function syncProps() { var el = findEl(selectedId); if (el) renderProps(el); }
|
||
|
||
function removeEl(id) {
|
||
elements = elements.filter(function (e) { return e.id !== id; });
|
||
if (selectedId === id) selectedId = null;
|
||
renderAll();
|
||
switchTab('preview');
|
||
if (!selectedId) renderProps(null);
|
||
}
|
||
|
||
/* ---------- 标签页切换 ---------- */
|
||
function switchTab(name) {
|
||
document.querySelectorAll('.pb-tab').forEach(function (t) {
|
||
t.classList.toggle('active', t.getAttribute('data-tab') === name);
|
||
});
|
||
var panes = { preview: 'panePreview', props: 'paneProps', material: 'paneMaterial' };
|
||
Object.keys(panes).forEach(function (k) {
|
||
document.getElementById(panes[k]).classList.toggle('hidden', k !== name);
|
||
});
|
||
if (name === 'preview') fitPreview();
|
||
}
|
||
document.querySelectorAll('.pb-tab').forEach(function (t) {
|
||
t.addEventListener('click', function () { switchTab(t.getAttribute('data-tab')); });
|
||
});
|
||
|
||
/* ---------- 实时预览(克隆只读 + 自适应缩放) ---------- */
|
||
function renderPreview() {
|
||
var pv = document.getElementById('pbPreview');
|
||
if (!pv) return;
|
||
pv.innerHTML = stage.innerHTML;
|
||
pv.querySelectorAll('.pb-handle').forEach(function (h) { h.remove(); });
|
||
pv.querySelectorAll('.pb-el').forEach(function (n) { n.classList.remove('sel'); n.style.outline = ''; });
|
||
pv.querySelectorAll('[contenteditable]').forEach(function (n) { n.removeAttribute('contenteditable'); });
|
||
fitPreview();
|
||
}
|
||
function fitPreview() {
|
||
var wrap = document.getElementById('pbPreviewWrap');
|
||
var pv = document.getElementById('pbPreview');
|
||
if (!wrap || !pv) return;
|
||
var avail = wrap.clientWidth || STAGE_W;
|
||
var scale = Math.min(1, avail / STAGE_W);
|
||
var m = 420;
|
||
pv.querySelectorAll('.pb-el').forEach(function (n) {
|
||
var y = parseInt(n.style.top, 10) || 0;
|
||
var b = y + n.offsetHeight;
|
||
if (b > m) m = b;
|
||
});
|
||
pv.style.transform = 'scale(' + scale + ')';
|
||
pv.style.transformOrigin = 'top left';
|
||
pv.style.height = m + 'px';
|
||
wrap.style.height = (m * scale) + 'px';
|
||
}
|
||
|
||
/* ---------- 素材库(含删除) ---------- */
|
||
function fillLib(container, onPick) {
|
||
if (!container) return;
|
||
container.innerHTML = '<span style="font-size:12px;color:#94a3b8">加载中…</span>';
|
||
fetch(window.__PB_MEDIA__)
|
||
.then(function (r) { return r.json(); })
|
||
.then(function (j) {
|
||
container.innerHTML = '';
|
||
var items = (j && j.items) || [];
|
||
if (!items.length) { container.innerHTML = '<span style="font-size:12px;color:#94a3b8">暂无素材,先上传</span>'; return; }
|
||
items.forEach(function (it) {
|
||
var cell = document.createElement('div');
|
||
cell.className = 'pb-lib-item';
|
||
var img = document.createElement('img');
|
||
img.src = it.url; img.title = it.name;
|
||
img.addEventListener('click', function () { onPick(it.url); });
|
||
var del = document.createElement('button');
|
||
del.className = 'pb-lib-del'; del.type = 'button'; del.textContent = '×'; del.title = '删除素材';
|
||
del.addEventListener('click', function (e) { e.stopPropagation(); deleteMedia(it.name); });
|
||
cell.appendChild(img); cell.appendChild(del);
|
||
container.appendChild(cell);
|
||
});
|
||
})
|
||
.catch(function () { container.innerHTML = '<span style="font-size:12px;color:#dc2626">加载失败</span>'; });
|
||
}
|
||
|
||
function deleteMedia(name) {
|
||
if (!confirm('确定删除素材「' + name + '」?此操作不可撤销。')) return;
|
||
fetch(window.__PB_DELETE__ + encodeURIComponent(name), {
|
||
method: 'POST',
|
||
headers: { 'X-CSRF-TOKEN': window.__PB_CSRF__ }
|
||
})
|
||
.then(function (r) { return r.json(); })
|
||
.then(function (j) {
|
||
if (j && j.ok) {
|
||
loadLib();
|
||
var pp = document.getElementById('pp_lib');
|
||
if (pp) fillLib(pp, function (url) { var el = findEl(selectedId); if (el) { el.src = url; applyEl(el); } });
|
||
} else { alert((j && j.msg) || '删除失败'); }
|
||
})
|
||
.catch(function () { alert('删除失败,请重试'); });
|
||
}
|
||
|
||
function loadLib() { fillLib(document.getElementById('pbLib'), function (url) { addImage(url); }); }
|
||
|
||
/* ---------- 添加元素 ---------- */
|
||
function addText() {
|
||
var el = { id: uid(), type: 'text', x: 40, y: 40, w: 320, text: '双击编辑文字', fontSize: 20, color: '#0f172a', bold: false, align: 'left', v: false, z: nextZ() };
|
||
elements.push(el); renderAll(); selectEl(el.id);
|
||
}
|
||
function addImage(url) {
|
||
var el = { id: uid(), type: 'image', x: 40, y: 40, w: 280, src: url, align: 'left', v: false, z: nextZ() };
|
||
elements.push(el); renderAll(); selectEl(el.id);
|
||
}
|
||
function addButton() {
|
||
var el = { id: uid(), type: 'button', x: 40, y: 40, w: 200, text: '按钮', href: '#', bg: '#0ea5e9', tc: '#ffffff', size: 'md', z: nextZ() };
|
||
elements.push(el); renderAll(); selectEl(el.id);
|
||
}
|
||
function addBuy() {
|
||
var el = { id: uid(), type: 'buy', x: 40, y: 40, w: 200, z: nextZ() };
|
||
elements.push(el); renderAll(); selectEl(el.id);
|
||
}
|
||
function addPrice() {
|
||
var el = { id: uid(), type: 'price', x: 40, y: 40, w: 240, color: '#0f172a', z: nextZ() };
|
||
elements.push(el); renderAll(); selectEl(el.id);
|
||
}
|
||
function addSpecs() {
|
||
var el = { id: uid(), type: 'specs', x: 40, y: 40, w: 360, z: nextZ() };
|
||
elements.push(el); renderAll(); selectEl(el.id);
|
||
}
|
||
|
||
/* 图标栏动作 */
|
||
document.querySelectorAll('.pb-rail-btn').forEach(function (b) {
|
||
b.addEventListener('click', function () {
|
||
var act = b.getAttribute('data-act');
|
||
if (act === 'material') { switchTab('material'); loadLib(); }
|
||
else if (act === 'image') { switchTab('material'); }
|
||
else if (act === 'text') { addText(); }
|
||
else if (act === 'button') { addButton(); }
|
||
else if (act === 'buy') { addBuy(); }
|
||
else if (act === 'price') { addPrice(); }
|
||
else if (act === 'specs') { addSpecs(); }
|
||
});
|
||
});
|
||
|
||
stage.addEventListener('click', function () { selectEl(null); });
|
||
|
||
/* ---------- 上传素材 ---------- */
|
||
on('pbFile', 'change', function () {
|
||
var file = this.files && this.files[0];
|
||
if (!file) return;
|
||
var fd = new FormData();
|
||
fd.append('file', file);
|
||
fetch(window.__PB_UPLOAD__, { method: 'POST', body: fd, headers: { 'X-CSRF-TOKEN': window.__PB_CSRF__ } })
|
||
.then(function (r) { return r.json(); })
|
||
.then(function (j) { if (j && j.ok) { addImage(j.url); loadLib(); } else alert((j && j.msg) || '上传失败'); })
|
||
.catch(function () { alert('上传失败,请重试'); });
|
||
this.value = '';
|
||
});
|
||
|
||
/* ---------- 保存:序列化到隐藏字段 ---------- */
|
||
on('pbForm', 'submit', function () {
|
||
var f = document.getElementById('pbLayout');
|
||
if (f) f.value = JSON.stringify(elements);
|
||
});
|
||
|
||
/* ---------- 启动 ---------- */
|
||
renderAll();
|
||
loadLib();
|
||
window.addEventListener('resize', fitPreview);
|
||
})();
|