579 lines
23 KiB
JavaScript
579 lines
23 KiB
JavaScript
/* ============================================================
|
||
新建商品 · 共享 Drawer 模块
|
||
----------------------------------------------------------
|
||
在任意页面只需 <script src="assets/new-product-drawer.js"> 引入,
|
||
然后调用 NewProductDrawer.open({ onSave: fn }) 即可在当前页之上
|
||
弹出右侧 Drawer。点击遮罩 / X / 取消 / ESC 关闭后,用户停在原页面。
|
||
|
||
提供:
|
||
window.NewProductDrawer.open(opts?)
|
||
window.NewProductDrawer.close()
|
||
opts 字段:
|
||
onSave(product) — 保存时回调,product = { id, name, cat, target,
|
||
points: string[], images: { id, dataUrl, name }[] }
|
||
============================================================ */
|
||
(function () {
|
||
'use strict';
|
||
if (window.NewProductDrawer) return; // idempotent
|
||
|
||
const DRAWER_ID = 'npd-drawer';
|
||
const DRAWER_BG_ID = 'npd-drawer-bg';
|
||
|
||
/* ---------- 注入样式(独立 namespace 以免与 products.html 冲突) ---------- */
|
||
|
||
const CSS = `
|
||
/* drawer base (相同尺寸/动画) */
|
||
#${DRAWER_BG_ID} {
|
||
position: fixed; inset: 0;
|
||
background: rgba(21, 20, 15, .32);
|
||
display: none; z-index: 1100;
|
||
}
|
||
#${DRAWER_BG_ID}.show { display: block; }
|
||
#${DRAWER_ID} {
|
||
position: fixed; right: 0; top: 0; bottom: 0;
|
||
width: 820px; max-width: 100vw;
|
||
background: var(--surface);
|
||
border-left: 1px solid var(--border-faint);
|
||
z-index: 1101;
|
||
transform: translateX(100%);
|
||
transition: transform .25s cubic-bezier(.32, .72, 0, 1);
|
||
display: flex; flex-direction: column;
|
||
box-shadow: -4px 0 24px rgba(21, 20, 15, .04);
|
||
}
|
||
#${DRAWER_ID}.show { transform: translateX(0); }
|
||
#${DRAWER_ID} .drawer-h {
|
||
padding: 20px 24px;
|
||
border-bottom: 1px solid var(--border-faint);
|
||
display: flex; align-items: center;
|
||
}
|
||
#${DRAWER_ID} .drawer-h h3 { font-size: 16px; font-weight: 600; color: var(--accent-black); }
|
||
#${DRAWER_ID} .drawer-h .x {
|
||
margin-left: auto; width: 32px; height: 32px;
|
||
border-radius: var(--r-md);
|
||
display: grid; place-items: center;
|
||
color: var(--black-alpha-56); cursor: pointer;
|
||
background: transparent; border: 0;
|
||
transition: background var(--t-base);
|
||
}
|
||
#${DRAWER_ID} .drawer-h .x:hover { background: var(--black-alpha-4); color: var(--accent-black); }
|
||
#${DRAWER_ID} .drawer-b { padding: 24px 28px; overflow-y: auto; flex: 1; overscroll-behavior: contain; }
|
||
#${DRAWER_ID} .drawer-f {
|
||
padding: 14px 24px;
|
||
border-top: 1px solid var(--border-faint);
|
||
display: flex; gap: 10px; align-items: center;
|
||
background: var(--surface);
|
||
}
|
||
#${DRAWER_ID} .drawer-f .btn-guide {
|
||
margin-right: auto;
|
||
display: inline-flex; align-items: center; gap: 6px;
|
||
font-size: 13px; color: var(--black-alpha-56);
|
||
background: transparent; border: 0; cursor: pointer;
|
||
padding: 8px 10px; border-radius: var(--r-md);
|
||
font-family: inherit;
|
||
transition: background var(--t-base), color var(--t-base);
|
||
}
|
||
#${DRAWER_ID} .drawer-f .btn-guide:hover { color: var(--accent-black); background: var(--black-alpha-4); }
|
||
#${DRAWER_ID} .drawer-f .btn-guide svg { width: 14px; height: 14px; }
|
||
|
||
/* form-card */
|
||
#${DRAWER_ID} .form-h {
|
||
font-size: 15px; font-weight: 600; color: var(--accent-black);
|
||
margin-bottom: 18px; padding-bottom: 12px;
|
||
border-bottom: 1px solid var(--border-faint);
|
||
}
|
||
#${DRAWER_ID} .field { margin-bottom: 16px; }
|
||
#${DRAWER_ID} .field:last-child { margin-bottom: 0; }
|
||
#${DRAWER_ID} .field-row { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; margin-bottom: 16px; }
|
||
#${DRAWER_ID} .field-label {
|
||
display: block; font-size: 13px; font-weight: 500;
|
||
color: var(--accent-black); margin-bottom: 6px;
|
||
}
|
||
#${DRAWER_ID} .field-label .req { color: var(--heat); margin-left: 2px; }
|
||
#${DRAWER_ID} .field-label .opt {
|
||
color: var(--black-alpha-48); font-weight: 400; font-size: 12px; margin-left: 6px;
|
||
}
|
||
#${DRAWER_ID} .input,
|
||
#${DRAWER_ID} .select {
|
||
width: 100%; height: 38px;
|
||
background: var(--background-lighter);
|
||
border: 1px solid var(--black-alpha-12);
|
||
border-radius: var(--r-md);
|
||
padding: 0 14px;
|
||
font-size: 13.5px; color: var(--accent-black);
|
||
outline: none; font-family: inherit;
|
||
transition: border-color var(--t-base);
|
||
}
|
||
#${DRAWER_ID} .input:focus,
|
||
#${DRAWER_ID} .select:focus {
|
||
border-color: var(--heat-40);
|
||
box-shadow: inset 0 0 0 1px var(--heat-40);
|
||
}
|
||
|
||
/* upload */
|
||
#${DRAWER_ID} .pf-upload-row {
|
||
display: grid; grid-template-columns: minmax(0, 1.4fr) minmax(0, 1fr);
|
||
gap: 16px; align-items: stretch;
|
||
}
|
||
#${DRAWER_ID} .pf-upload-zone {
|
||
border: 1.5px dashed var(--black-alpha-24);
|
||
border-radius: var(--r-md);
|
||
padding: 28px 20px;
|
||
background: var(--background-lighter);
|
||
cursor: pointer; text-align: center;
|
||
transition: border-color var(--t-base), background var(--t-base);
|
||
display: flex; flex-direction: column; align-items: center; justify-content: center;
|
||
min-height: 180px;
|
||
}
|
||
#${DRAWER_ID} .pf-upload-zone:hover { border-color: var(--heat); background: var(--heat-8); }
|
||
#${DRAWER_ID} .pf-upload-zone .uz-ic {
|
||
width: 44px; height: 44px;
|
||
margin: 0 auto 10px;
|
||
background: var(--surface);
|
||
border: 1px solid var(--heat-20);
|
||
border-radius: var(--r-md);
|
||
color: var(--heat);
|
||
display: grid; place-items: center;
|
||
}
|
||
#${DRAWER_ID} .pf-upload-zone .uz-ic svg { width: 20px; height: 20px; }
|
||
#${DRAWER_ID} .pf-upload-zone .uz-t { font-size: 14px; color: var(--accent-black); font-weight: 500; }
|
||
#${DRAWER_ID} .pf-upload-zone .uz-t strong { color: var(--heat); font-weight: 600; }
|
||
#${DRAWER_ID} .pf-upload-zone .uz-d {
|
||
margin-top: 8px;
|
||
font-family: var(--font-mono); font-size: 11.5px;
|
||
color: var(--black-alpha-48); letter-spacing: .02em;
|
||
}
|
||
#${DRAWER_ID} .pf-example {
|
||
background: var(--background-lighter);
|
||
border: 1px solid var(--border-faint);
|
||
border-radius: var(--r-md);
|
||
padding: 16px;
|
||
display: flex; flex-direction: column; gap: 10px;
|
||
}
|
||
#${DRAWER_ID} .pf-example .ex-h { font-size: 13px; font-weight: 600; color: var(--accent-black); }
|
||
#${DRAWER_ID} .pf-example .ex-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; }
|
||
#${DRAWER_ID} .pf-example .ex-grid .ex-thumb {
|
||
aspect-ratio: 1;
|
||
background: var(--surface);
|
||
border: 1px solid var(--border-faint);
|
||
border-radius: var(--r-sm);
|
||
overflow: hidden; position: relative;
|
||
display: grid; place-items: center;
|
||
color: var(--black-alpha-32);
|
||
}
|
||
#${DRAWER_ID} .pf-example .ex-grid .ex-thumb svg { width: 22px; height: 22px; }
|
||
#${DRAWER_ID} .pf-example .ex-grid .ex-thumb::after {
|
||
content: ''; position: absolute; inset: 0;
|
||
background: repeating-linear-gradient(135deg, transparent 0 6px, rgba(0,0,0,.03) 6px 7px);
|
||
pointer-events: none;
|
||
}
|
||
#${DRAWER_ID} .pf-example .ex-d { font-size: 12px; color: var(--black-alpha-56); line-height: 1.5; }
|
||
#${DRAWER_ID} .pf-grid {
|
||
display: grid; grid-template-columns: repeat(5, 1fr);
|
||
gap: 8px; margin-top: 12px;
|
||
}
|
||
#${DRAWER_ID} .pf-grid:empty { display: none; }
|
||
#${DRAWER_ID} .pf-thumb {
|
||
aspect-ratio: 1;
|
||
background: var(--background-lighter);
|
||
border: 1px solid var(--border-faint);
|
||
border-radius: var(--r-md);
|
||
position: relative; overflow: hidden; cursor: pointer;
|
||
}
|
||
#${DRAWER_ID} .pf-thumb img { width: 100%; height: 100%; object-fit: cover; }
|
||
#${DRAWER_ID} .pf-thumb .pf-x {
|
||
position: absolute; top: 4px; right: 4px;
|
||
width: 22px; height: 22px;
|
||
background: rgba(0,0,0,.7); color: var(--accent-white);
|
||
border: 0; border-radius: 50%; cursor: pointer;
|
||
display: grid; place-items: center;
|
||
opacity: 0; transition: opacity var(--t-base);
|
||
}
|
||
#${DRAWER_ID} .pf-thumb:hover .pf-x { opacity: 1; }
|
||
#${DRAWER_ID} .pf-thumb .pf-x svg { width: 11px; height: 11px; }
|
||
|
||
/* bullet-list */
|
||
#${DRAWER_ID} .bullet-list { list-style: none; padding: 0; margin: 0; }
|
||
#${DRAWER_ID} .bullet-list .bl-item,
|
||
#${DRAWER_ID} .bullet-list .bl-add {
|
||
display: flex; align-items: center; gap: 10px;
|
||
padding: 8px 12px;
|
||
background: var(--background-lighter);
|
||
border: 1px solid var(--border-faint);
|
||
border-radius: var(--r-md);
|
||
margin-bottom: 6px;
|
||
font-size: 13.5px;
|
||
}
|
||
#${DRAWER_ID} .bullet-list .bl-add { background: transparent; border-style: dashed; }
|
||
#${DRAWER_ID} .bullet-list .num {
|
||
width: 22px; height: 22px;
|
||
background: var(--surface);
|
||
border: 1px solid var(--border-faint);
|
||
border-radius: var(--r-sm);
|
||
font-family: var(--font-mono);
|
||
font-size: 11px; color: var(--heat); font-weight: 700;
|
||
display: grid; place-items: center; flex-shrink: 0;
|
||
}
|
||
#${DRAWER_ID} .bullet-list .bl-text { flex: 1; color: var(--accent-black); }
|
||
#${DRAWER_ID} .bullet-list .bl-input {
|
||
flex: 1; background: transparent; border: 0; outline: none;
|
||
font-size: 13.5px; color: var(--accent-black); font-family: inherit;
|
||
}
|
||
#${DRAWER_ID} .bullet-list .bl-x {
|
||
width: 22px; height: 22px;
|
||
color: var(--black-alpha-48);
|
||
cursor: pointer; display: grid; place-items: center;
|
||
border-radius: var(--r-sm);
|
||
transition: color var(--t-base), background var(--t-base);
|
||
}
|
||
#${DRAWER_ID} .bullet-list .bl-x:hover { color: var(--accent-crimson); background: var(--crimson-bg); }
|
||
#${DRAWER_ID} .bullet-list .bl-x svg { width: 11px; height: 11px; }
|
||
|
||
@media (max-width: 900px) {
|
||
#${DRAWER_ID} .pf-upload-row { grid-template-columns: 1fr; }
|
||
}
|
||
`;
|
||
|
||
const HTML = `
|
||
<div id="${DRAWER_BG_ID}"></div>
|
||
<aside id="${DRAWER_ID}" role="dialog" aria-label="新建商品" aria-hidden="true">
|
||
<div class="drawer-h">
|
||
<h3>新建商品</h3>
|
||
<button class="x" type="button" data-act="close" aria-label="关闭">
|
||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M6 6l12 12M6 18L18 6"/></svg>
|
||
</button>
|
||
</div>
|
||
<div class="drawer-b">
|
||
<div class="form-card">
|
||
<div class="form-h">基础信息</div>
|
||
|
||
<div class="field">
|
||
<label class="field-label">商品名称<span class="req">*</span></label>
|
||
<input class="input" data-f="name" placeholder="请输入商品名称(必填)" maxlength="100">
|
||
</div>
|
||
|
||
<div class="field-row">
|
||
<div>
|
||
<label class="field-label">品类<span class="req">*</span></label>
|
||
<select class="select" data-f="cat">
|
||
<option>美妆个护</option>
|
||
<option>服饰内衣</option>
|
||
<option>食品饮料</option>
|
||
<option>家居家电</option>
|
||
<option>数码 3C</option>
|
||
<option>个护清洁</option>
|
||
<option>运动户外</option>
|
||
<option>母婴亲子</option>
|
||
</select>
|
||
</div>
|
||
<div>
|
||
<label class="field-label">目标人群<span class="opt">(选填)</span></label>
|
||
<input class="input" data-f="target" placeholder="例: 22-32 岁女性、敏感肌、办公室通勤">
|
||
</div>
|
||
</div>
|
||
|
||
<div class="field">
|
||
<label class="field-label">商品主图<span class="req">*</span></label>
|
||
<input type="file" data-f="file" accept="image/*" multiple hidden>
|
||
<div class="pf-upload-row">
|
||
<div class="pf-upload-zone" data-act="upload-zone">
|
||
<div class="uz-ic">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4M17 8l-5-5-5 5M12 3v12"/></svg>
|
||
</div>
|
||
<div class="uz-t">点击上传或<strong>拖拽图片</strong>到此处</div>
|
||
<div class="uz-d">// 支持 JPG、PNG 格式,建议尺寸 800×800 以上,大小不超过 10MB</div>
|
||
</div>
|
||
<div class="pf-example">
|
||
<div class="ex-h">示例图</div>
|
||
<div class="ex-grid">
|
||
<div class="ex-thumb"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"><path d="M7 4h10l1 4v12H6V8l1-4z"/><path d="M9 4v3M15 4v3M9 11h6M9 14h6"/></svg></div>
|
||
<div class="ex-thumb"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"><rect x="6" y="5" width="12" height="15" rx="2"/><path d="M9 9h6M9 12h6M9 15h4"/></svg></div>
|
||
<div class="ex-thumb"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"><path d="M8 3h8l1 5v12H7V8l1-5z"/><circle cx="12" cy="13" r="2.5"/></svg></div>
|
||
</div>
|
||
<div class="ex-d">优质的商品图有助于生成更好的素材效果</div>
|
||
</div>
|
||
</div>
|
||
<div class="pf-grid" data-f="grid"></div>
|
||
</div>
|
||
|
||
<div class="field" style="margin-bottom: 0;">
|
||
<label class="field-label">核心卖点<span class="req">*</span></label>
|
||
<ul class="bullet-list" data-f="bullets">
|
||
<li class="bl-add"><span class="num">+</span><input class="bl-input" placeholder="添加新卖点 · 回车确认"></li>
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="drawer-f">
|
||
<button class="btn-guide" type="button" data-act="guide">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="9"/><path d="M9.5 9a2.5 2.5 0 015 0c0 1.5-2.5 2-2.5 4M12 17h.01"/></svg>
|
||
使用指南
|
||
</button>
|
||
<button class="btn" type="button" data-act="cancel">取消</button>
|
||
<button class="btn btn-primary" type="button" data-act="save">
|
||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M4 12l5 5L20 6"/></svg>
|
||
创建商品
|
||
</button>
|
||
</div>
|
||
</aside>
|
||
`;
|
||
|
||
/* ---------- DOM refs (populated by ensureInjected) ---------- */
|
||
let injected = false;
|
||
let bg, drawer, $f, $grid, $bullets, $blInput;
|
||
let currentOpts = {};
|
||
const PF_MAX = 5;
|
||
let pfFiles = []; // { id, dataUrl, name }
|
||
const blXSvg = '<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"><path d="M4 4l8 8M12 4l-8 8"/></svg>';
|
||
|
||
function esc(s) { return String(s == null ? '' : s).replace(/[<>&"]/g, c => ({ '<':'<','>':'>','&':'&','"':'"' })[c]); }
|
||
function uid() { return Date.now().toString(36) + Math.random().toString(36).slice(2, 5); }
|
||
function toast(msg, sub) {
|
||
if (typeof Shell !== 'undefined' && Shell && Shell.toast) Shell.toast(msg, sub);
|
||
}
|
||
|
||
function ensureInjected() {
|
||
if (injected) return;
|
||
// style
|
||
const styleEl = document.createElement('style');
|
||
styleEl.textContent = CSS;
|
||
document.head.appendChild(styleEl);
|
||
// html
|
||
const wrap = document.createElement('div');
|
||
wrap.innerHTML = HTML;
|
||
while (wrap.firstChild) document.body.appendChild(wrap.firstChild);
|
||
|
||
bg = document.getElementById(DRAWER_BG_ID);
|
||
drawer = document.getElementById(DRAWER_ID);
|
||
$f = {
|
||
name: drawer.querySelector('[data-f="name"]'),
|
||
cat: drawer.querySelector('[data-f="cat"]'),
|
||
target: drawer.querySelector('[data-f="target"]'),
|
||
file: drawer.querySelector('[data-f="file"]'),
|
||
};
|
||
$grid = drawer.querySelector('[data-f="grid"]');
|
||
$bullets = drawer.querySelector('[data-f="bullets"]');
|
||
$blInput = $bullets.querySelector('.bl-add .bl-input');
|
||
|
||
bindEvents();
|
||
injected = true;
|
||
}
|
||
|
||
function bindEvents() {
|
||
// 关闭交互
|
||
bg.addEventListener('click', close);
|
||
drawer.addEventListener('click', e => {
|
||
const a = e.target.closest('[data-act]');
|
||
if (!a) return;
|
||
const act = a.dataset.act;
|
||
if (act === 'close') return close();
|
||
if (act === 'cancel') return close();
|
||
if (act === 'guide') return toast('使用指南', '点击查看完整填写指南');
|
||
if (act === 'save') return save();
|
||
if (act === 'upload-zone') return openFilePicker();
|
||
});
|
||
document.addEventListener('keydown', e => {
|
||
if (e.key === 'Escape' && drawer.classList.contains('show')) close();
|
||
});
|
||
|
||
// 上传
|
||
$f.file.addEventListener('change', e => { addFiles(e.target.files); e.target.value = ''; });
|
||
const zone = drawer.querySelector('[data-act="upload-zone"]');
|
||
zone.addEventListener('dragover', e => { e.preventDefault(); zone.style.borderColor = 'var(--heat)'; });
|
||
zone.addEventListener('dragleave', () => { zone.style.borderColor = ''; });
|
||
zone.addEventListener('drop', e => {
|
||
e.preventDefault(); zone.style.borderColor = '';
|
||
if (e.dataTransfer && e.dataTransfer.files && e.dataTransfer.files.length) addFiles(e.dataTransfer.files);
|
||
});
|
||
|
||
// 卖点 bullet-list
|
||
$blInput.addEventListener('keydown', e => {
|
||
if (e.key === 'Enter') { e.preventDefault(); blAdd($blInput.value); $blInput.value = ''; }
|
||
});
|
||
}
|
||
|
||
function openFilePicker() { if (pfFiles.length < PF_MAX) $f.file.click(); }
|
||
|
||
function pfRender() {
|
||
$grid.innerHTML = pfFiles.map(u => `
|
||
<div class="pf-thumb" data-id="${u.id}">
|
||
<img src="${u.dataUrl}" alt="${esc(u.name)}">
|
||
<button class="pf-x" type="button" title="删除">
|
||
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M4 4l8 8M12 4l-8 8"/></svg>
|
||
</button>
|
||
</div>
|
||
`).join('');
|
||
$grid.querySelectorAll('.pf-thumb .pf-x').forEach(b => {
|
||
b.onclick = e => {
|
||
e.stopPropagation();
|
||
const id = b.closest('.pf-thumb').dataset.id;
|
||
const i = pfFiles.findIndex(f => f.id === id);
|
||
if (i >= 0) { pfFiles.splice(i, 1); pfRender(); }
|
||
};
|
||
});
|
||
}
|
||
|
||
function addFiles(fileList) {
|
||
const room = PF_MAX - pfFiles.length;
|
||
if (room <= 0) { toast('已达上限', PF_MAX + ' / ' + PF_MAX + ' 张'); return; }
|
||
const incoming = [...fileList].filter(f => f.type.startsWith('image/')).slice(0, room);
|
||
let done = 0;
|
||
incoming.forEach(f => {
|
||
const r = new FileReader();
|
||
r.onload = e => {
|
||
pfFiles.push({ id: uid(), dataUrl: e.target.result, name: f.name });
|
||
if (++done === incoming.length) {
|
||
pfRender();
|
||
toast('已上传', '+ ' + done + ' 张 · 共 ' + pfFiles.length + ' / ' + PF_MAX);
|
||
}
|
||
};
|
||
r.readAsDataURL(f);
|
||
});
|
||
}
|
||
|
||
function blRenumber() {
|
||
[...$bullets.querySelectorAll('.bl-item')].forEach((li, i) => {
|
||
li.querySelector('.num').textContent = i + 1;
|
||
});
|
||
}
|
||
function blAdd(text) {
|
||
const t = (text || '').trim();
|
||
if (!t) return;
|
||
const li = document.createElement('li');
|
||
li.className = 'bl-item';
|
||
li.innerHTML = '<span class="num">0</span><span class="bl-text">' + esc(t) + '</span><span class="bl-x" title="删除">' + blXSvg + '</span>';
|
||
$bullets.querySelector('.bl-add').before(li);
|
||
li.querySelector('.bl-x').addEventListener('click', () => {
|
||
li.style.transition = 'opacity .15s, transform .15s';
|
||
li.style.opacity = 0;
|
||
li.style.transform = 'translateX(-8px)';
|
||
setTimeout(() => { li.remove(); blRenumber(); }, 150);
|
||
});
|
||
blRenumber();
|
||
}
|
||
function getBullets() {
|
||
return [...$bullets.querySelectorAll('.bl-item .bl-text')].map(t => t.textContent.trim()).filter(Boolean);
|
||
}
|
||
|
||
/* ---------- API ---------- */
|
||
|
||
function resetForm() {
|
||
$f.name.value = '';
|
||
$f.cat.value = $f.cat.options[0].value;
|
||
$f.target.value = '';
|
||
pfFiles = [];
|
||
pfRender();
|
||
[...$bullets.querySelectorAll('.bl-item')].forEach(li => li.remove());
|
||
$blInput.value = '';
|
||
}
|
||
|
||
function lockBody() {
|
||
// 优先用 Shell 的引用计数实现(避免多 overlay 互相解锁)
|
||
if (typeof Shell !== 'undefined' && Shell && typeof Shell.lockScroll === 'function') {
|
||
Shell.lockScroll();
|
||
return;
|
||
}
|
||
// 兜底: Shell 未加载时本地锁
|
||
const docEl = document.documentElement;
|
||
const sbw = window.innerWidth - docEl.clientWidth;
|
||
drawer._lockSnap = {
|
||
bodyOverflow: document.body.style.overflow,
|
||
bodyPaddingRight: document.body.style.paddingRight,
|
||
htmlOverflow: docEl.style.overflow,
|
||
};
|
||
document.body.style.overflow = 'hidden';
|
||
docEl.style.overflow = 'hidden';
|
||
if (sbw > 0) document.body.style.paddingRight = sbw + 'px';
|
||
}
|
||
function unlockBody() {
|
||
if (typeof Shell !== 'undefined' && Shell && typeof Shell.unlockScroll === 'function') {
|
||
Shell.unlockScroll();
|
||
return;
|
||
}
|
||
const s = drawer._lockSnap;
|
||
if (s) {
|
||
document.body.style.overflow = s.bodyOverflow;
|
||
document.body.style.paddingRight = s.bodyPaddingRight;
|
||
document.documentElement.style.overflow = s.htmlOverflow;
|
||
drawer._lockSnap = null;
|
||
}
|
||
}
|
||
|
||
function open(opts) {
|
||
ensureInjected();
|
||
if (drawer.classList.contains('show')) return; // 已开则不重复锁
|
||
currentOpts = opts || {};
|
||
resetForm();
|
||
bg.classList.add('show');
|
||
drawer.classList.add('show');
|
||
drawer.setAttribute('aria-hidden', 'false');
|
||
lockBody();
|
||
setTimeout(() => $f.name.focus(), 280);
|
||
}
|
||
|
||
function close() {
|
||
if (!injected) return;
|
||
if (!drawer.classList.contains('show')) return; // 已关则不重复解锁
|
||
bg.classList.remove('show');
|
||
drawer.classList.remove('show');
|
||
drawer.setAttribute('aria-hidden', 'true');
|
||
unlockBody();
|
||
if (typeof currentOpts.onClose === 'function') currentOpts.onClose();
|
||
}
|
||
|
||
function save() {
|
||
const name = ($f.name.value || '').trim();
|
||
const cat = $f.cat.value;
|
||
const target = ($f.target.value || '').trim();
|
||
const points = getBullets();
|
||
const images = pfFiles.slice();
|
||
|
||
if (!name) {
|
||
toast('请填写商品名称');
|
||
$f.name.focus();
|
||
return;
|
||
}
|
||
if (images.length === 0) {
|
||
toast('请上传商品主图', '至少 1 张');
|
||
return;
|
||
}
|
||
if (points.length === 0) {
|
||
toast('请添加核心卖点', '至少 1 条');
|
||
$blInput.focus();
|
||
return;
|
||
}
|
||
|
||
const product = {
|
||
id: 'np-' + uid(),
|
||
name, cat, target,
|
||
points,
|
||
images,
|
||
imgs: images.length,
|
||
};
|
||
toast('商品已创建', '+ ' + name);
|
||
if (typeof currentOpts.onSave === 'function') currentOpts.onSave(product);
|
||
close();
|
||
}
|
||
|
||
window.NewProductDrawer = { open, close };
|
||
|
||
/* ---------- sessionStorage 自动打开钩子 ---------- */
|
||
// 任何页面只要在跳转前 sessionStorage.setItem('npd-auto-open','1') 即可,
|
||
// 落地页加载完模块后,会自动 open() 一次并清掉 flag。
|
||
// 用于:product-create.html 重定向后让落地页弹出 drawer,而不是用户重新点击。
|
||
function checkAutoOpen() {
|
||
try {
|
||
if (sessionStorage.getItem('npd-auto-open') === '1') {
|
||
sessionStorage.removeItem('npd-auto-open');
|
||
// 延后一拍,确保宿主页面自己的 init 已经跑完
|
||
setTimeout(() => open(), 50);
|
||
}
|
||
} catch (e) { /* sessionStorage 不可用就静默放弃 */ }
|
||
}
|
||
if (document.readyState === 'loading') {
|
||
document.addEventListener('DOMContentLoaded', checkAutoOpen);
|
||
} else {
|
||
checkAutoOpen();
|
||
}
|
||
})();
|