415 lines
15 KiB
HTML
415 lines
15 KiB
HTML
<!doctype html>
|
|
<html lang="zh-CN">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<title>新建商品 · Airshelf</title>
|
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
|
<link rel="stylesheet" href="assets/restraint.css?v=2026052607">
|
|
<style>
|
|
/* ─── 主表单 ─── */
|
|
.form-grid {
|
|
display: grid; grid-template-columns: 1.05fr 1fr; gap: 24px;
|
|
margin-bottom: 24px;
|
|
}
|
|
.form-card {
|
|
background: var(--surface);
|
|
border: 1px solid var(--border-faint);
|
|
border-radius: var(--r-md);
|
|
padding: 24px;
|
|
}
|
|
.form-card .card-h {
|
|
display: flex; align-items: center; gap: 10px;
|
|
margin-bottom: 16px;
|
|
}
|
|
.form-card .card-h h3 {
|
|
font-size: 14px; font-weight: 600; color: var(--accent-black);
|
|
}
|
|
.form-card .card-h .req-tag {
|
|
font-family: var(--font-mono); font-size: 10px;
|
|
padding: 2px 7px;
|
|
background: var(--crimson-bg); color: var(--accent-crimson);
|
|
border-radius: var(--r-sm); letter-spacing: .04em;
|
|
border: 1px solid var(--red-bd);
|
|
}
|
|
.form-card .card-h .opt-tag {
|
|
font-family: var(--font-mono); font-size: 10px;
|
|
padding: 2px 7px;
|
|
background: var(--background-lighter); color: var(--black-alpha-56);
|
|
border-radius: var(--r-sm); letter-spacing: .04em;
|
|
border: 1px solid var(--border-faint);
|
|
}
|
|
.form-card .card-sub {
|
|
font-family: var(--font-mono); font-size: 11.5px;
|
|
color: var(--black-alpha-48); margin: -10px 0 14px; letter-spacing: .02em;
|
|
}
|
|
|
|
/* 原图槽位 */
|
|
.photo-grid {
|
|
display: grid; grid-template-columns: repeat(5, 1fr); gap: 8px;
|
|
margin-top: 8px;
|
|
}
|
|
.photo-slot {
|
|
aspect-ratio: 1;
|
|
border-radius: var(--r-md);
|
|
border: 1px dashed var(--border-faint);
|
|
background: var(--background-lighter);
|
|
display: grid; place-items: center;
|
|
color: var(--black-alpha-32);
|
|
cursor: pointer;
|
|
overflow: hidden;
|
|
position: relative;
|
|
font-size: 10px; font-family: var(--font-mono); letter-spacing: .04em;
|
|
transition: all var(--t-base);
|
|
}
|
|
.photo-slot:hover { border-color: var(--heat); color: var(--heat); background: var(--heat-8); }
|
|
.photo-slot.filled {
|
|
border-style: solid;
|
|
background-size: cover; background-position: center;
|
|
cursor: default; color: transparent;
|
|
}
|
|
.photo-slot.filled:hover { border-color: var(--heat-40); }
|
|
.photo-slot .slot-label {
|
|
position: absolute; top: 5px; left: 5px;
|
|
font-family: var(--font-mono); font-size: 9.5px; font-weight: 600;
|
|
padding: 2px 6px;
|
|
background: rgba(255,255,255,.92); color: var(--black-alpha-72);
|
|
border-radius: var(--r-sm); letter-spacing: .04em;
|
|
}
|
|
.photo-slot .slot-main {
|
|
position: absolute; top: 5px; right: 5px;
|
|
font-family: var(--font-mono); font-size: 9.5px; font-weight: 600;
|
|
padding: 2px 6px; background: var(--heat); color: #fff;
|
|
border-radius: var(--r-sm); letter-spacing: .04em;
|
|
}
|
|
.photo-slot .slot-x {
|
|
position: absolute; top: 4px; right: 4px;
|
|
width: 18px; height: 18px;
|
|
border-radius: 999px;
|
|
background: rgba(21,20,15,.7); color: #fff;
|
|
display: none; place-items: center; cursor: pointer; border: 0;
|
|
}
|
|
.photo-slot.filled:hover .slot-x { display: grid; }
|
|
.photo-slot.filled:hover .slot-main { display: none; }
|
|
.photo-slot .slot-x svg { width: 9px; height: 9px; }
|
|
.photo-slot .plus { width: 22px; height: 22px; border: 1px solid currentColor; border-radius: var(--r-sm); display: grid; place-items: center; margin-bottom: 4px; }
|
|
.photo-slot .plus svg { width: 12px; height: 12px; }
|
|
|
|
.upload-tip {
|
|
display: flex; align-items: center; gap: 8px;
|
|
margin-top: 14px; padding: 10px 12px;
|
|
background: var(--heat-8); border: 1px dashed var(--heat-40);
|
|
border-radius: var(--r-md);
|
|
font-size: 12px; color: var(--accent-black); line-height: 1.5;
|
|
}
|
|
.upload-tip svg { width: 14px; height: 14px; color: var(--heat); flex-shrink: 0; }
|
|
.upload-tip strong { color: var(--heat); font-weight: 600; }
|
|
|
|
/* AI 提示 banner(选填字段说明) */
|
|
.ai-tip {
|
|
margin-top: -6px; margin-bottom: 16px;
|
|
padding: 10px 12px;
|
|
background: var(--background-lighter);
|
|
border-radius: var(--r-md);
|
|
border: 1px dashed var(--border-faint);
|
|
display: flex; align-items: flex-start; gap: 8px;
|
|
font-size: 12px; color: var(--black-alpha-72); line-height: 1.55;
|
|
}
|
|
.ai-tip svg { width: 13px; height: 13px; color: var(--heat); flex-shrink: 0; margin-top: 2px; }
|
|
.ai-tip strong { color: var(--accent-black); font-weight: 600; }
|
|
|
|
/* 卖点 bullet 输入 */
|
|
.sell-list { list-style: none; margin: 0; padding: 0; }
|
|
.sell-list li {
|
|
display: flex; align-items: center; gap: 8px;
|
|
padding: 8px 10px;
|
|
background: var(--background-lighter);
|
|
border: 1px solid var(--border-faint);
|
|
border-radius: var(--r-md);
|
|
margin-bottom: 6px;
|
|
font-size: 13px;
|
|
}
|
|
.sell-list li.add { background: var(--surface); border-style: dashed; }
|
|
.sell-list .num {
|
|
width: 18px; height: 18px;
|
|
background: var(--surface);
|
|
border: 1px solid var(--border-faint);
|
|
border-radius: var(--r-sm);
|
|
font-size: 10.5px; font-family: var(--font-mono);
|
|
color: var(--black-alpha-56);
|
|
display: grid; place-items: center; flex-shrink: 0;
|
|
}
|
|
.sell-list li.add .num { background: transparent; color: var(--heat); border-color: var(--heat-40); }
|
|
.sell-list .txt { flex: 1; min-width: 0; }
|
|
.sell-list .bl-input {
|
|
flex: 1; border: 0; background: transparent;
|
|
font-size: 13px; color: var(--accent-black); padding: 0;
|
|
font-family: inherit;
|
|
}
|
|
.sell-list .bl-input::placeholder { color: var(--black-alpha-48); }
|
|
.sell-list .bl-x {
|
|
width: 20px; height: 20px;
|
|
display: grid; place-items: center;
|
|
color: var(--black-alpha-48); cursor: pointer;
|
|
background: transparent; border: 0; opacity: 0;
|
|
transition: opacity var(--t-base);
|
|
}
|
|
.sell-list li:hover .bl-x { opacity: 1; }
|
|
.sell-list .bl-x:hover { color: var(--accent-crimson); }
|
|
.sell-list .bl-x svg { width: 11px; height: 11px; }
|
|
|
|
/* 底部操作行 */
|
|
.form-foot {
|
|
position: sticky; bottom: 0;
|
|
background: var(--surface);
|
|
border: 1px solid var(--border-faint);
|
|
border-radius: var(--r-md);
|
|
padding: 14px 22px;
|
|
display: flex; align-items: center; gap: 14px;
|
|
margin-top: 8px;
|
|
}
|
|
.form-foot .req-info {
|
|
font-family: var(--font-mono); font-size: 11.5px;
|
|
color: var(--black-alpha-48); letter-spacing: .02em;
|
|
}
|
|
.form-foot .req-info .ok { color: var(--accent-forest); }
|
|
.form-foot .req-info .miss { color: var(--accent-crimson); }
|
|
.form-foot .actions {
|
|
margin-left: auto;
|
|
display: flex; gap: 10px;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div id="page">
|
|
|
|
<div class="page-head">
|
|
<div>
|
|
<h1>新建商品</h1>
|
|
<div class="sub"><span class="mono">// 上传原图 + 填写基本信息</span> · 保存后可在工作台逐步丰富素材</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ============ 表单 ============ -->
|
|
<div class="form-grid">
|
|
|
|
<!-- 左:原图 -->
|
|
<div class="form-card">
|
|
<div class="card-h">
|
|
<h3>商品原图</h3>
|
|
<span class="req-tag">必填</span>
|
|
</div>
|
|
<div class="card-sub">// 1-5 张 · 这是后续所有 AI 生成的源材料</div>
|
|
<input type="file" id="photo-input" accept="image/*" multiple hidden>
|
|
<div class="photo-grid" id="photo-grid"></div>
|
|
<div class="upload-tip">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><circle cx="12" cy="12" r="9"/><path d="M12 8v4M12 16h.01"/></svg>
|
|
<span>建议上传 <strong>正面 / 侧面 / 细节 / 包装</strong> 4 张,后续在工作台生成的<strong>白底三视图</strong>更准确。</span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 右:基本信息 -->
|
|
<div class="form-card">
|
|
<div class="card-h">
|
|
<h3>基本信息</h3>
|
|
<span class="req-tag">必填</span>
|
|
</div>
|
|
<div class="field">
|
|
<label class="field-label">商品名称<span class="req">*</span></label>
|
|
<input class="input" id="p-name" placeholder="例: 透真玻尿酸补水面膜">
|
|
</div>
|
|
<div class="field">
|
|
<label class="field-label">品类<span class="req">*</span></label>
|
|
<select class="select" id="p-cat">
|
|
<option value="">— 选择品类 —</option>
|
|
<option>美妆个护</option>
|
|
<option>服饰内衣</option>
|
|
<option>食品饮料</option>
|
|
<option>家居家电</option>
|
|
<option>数码 3C</option>
|
|
<option>个护清洁</option>
|
|
<option>运动户外</option>
|
|
<option>母婴亲子</option>
|
|
</select>
|
|
</div>
|
|
<div class="field" style="margin-bottom:0;">
|
|
<label class="field-label">价格(元)</label>
|
|
<input class="input" id="p-price" type="number" placeholder="选填 · 仅用于素材生成参考">
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div class="form-card" style="margin-bottom: 24px;">
|
|
<div class="card-h">
|
|
<h3>卖点 & 人群</h3>
|
|
<span class="opt-tag">选填 · 推荐</span>
|
|
</div>
|
|
<div class="ai-tip">
|
|
<svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 2l1.5 4.5L18 8l-4.5 1.5L12 14l-1.5-4.5L6 8l4.5-1.5L12 2z"/></svg>
|
|
<span>填上这两项,后续 AI 生脚本(<strong>痛点种草 / 剧情带货</strong> 等模板)质量明显更高 —— 系统会用卖点构造钩子,用人群定语气。现在不填也可以,做视频项目时仍可补。</span>
|
|
</div>
|
|
|
|
<div class="field">
|
|
<label class="field-label">核心卖点</label>
|
|
<div class="field-hint" style="margin: 4px 0 8px;">3-5 条要点,回车添加</div>
|
|
<ul class="sell-list" id="sell-list">
|
|
<li class="add"><span class="num">+</span><input class="bl-input" id="sell-input" placeholder="例: 玻尿酸双效保湿,4 小时持久水润"></li>
|
|
</ul>
|
|
</div>
|
|
<div class="field" style="margin-bottom:0;">
|
|
<label class="field-label">目标人群</label>
|
|
<input class="input" id="p-target" placeholder="例: 22-32 岁女性、敏感肌、办公室通勤">
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ============ 底部操作 ============ -->
|
|
<div class="form-foot">
|
|
<span class="req-info" id="req-info">// 必填检查:<span class="miss">商品名 / 品类 / ≥1 张图</span> 未完成</span>
|
|
<div class="actions">
|
|
<a class="btn" href="products.html">取消</a>
|
|
<button class="btn btn-primary" id="save-btn" disabled>
|
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M4 12l5 5L20 6"/></svg>
|
|
保存并进入工作台
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
<script src="assets/icons.js?v=2026052608"></script>
|
|
<script src="assets/shell.js?v=2026052607"></script>
|
|
<script>
|
|
Shell.render({
|
|
active: 'products',
|
|
crumbs: [
|
|
{ label: '工作台', href: 'index.html' },
|
|
{ label: '商品库', href: 'products.html' },
|
|
{ label: '新建' }
|
|
]
|
|
});
|
|
|
|
const MAX = 5;
|
|
const photos = []; // { id, dataUrl }
|
|
const SLOT_LABELS = ['主图', '细节 02', '细节 03', '细节 04', '细节 05'];
|
|
|
|
const $ = id => document.getElementById(id);
|
|
|
|
// 渲染槽位
|
|
function renderPhotos() {
|
|
const grid = $('photo-grid');
|
|
grid.innerHTML = '';
|
|
for (let i = 0; i < MAX; i++) {
|
|
const slot = document.createElement('div');
|
|
const p = photos[i];
|
|
if (p) {
|
|
slot.className = 'photo-slot filled';
|
|
slot.style.backgroundImage = `url("${p.dataUrl}")`;
|
|
slot.innerHTML = `
|
|
<span class="slot-label">${SLOT_LABELS[i]}</span>
|
|
${i === 0 ? '<span class="slot-main">MAIN</span>' : ''}
|
|
<button class="slot-x" type="button" data-i="${i}" aria-label="移除">
|
|
<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>
|
|
</button>
|
|
`;
|
|
} else if (i === photos.length) {
|
|
slot.className = 'photo-slot empty';
|
|
slot.innerHTML = `<div class="plus"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M12 5v14M5 12h14"/></svg></div><span>添加</span>`;
|
|
slot.addEventListener('click', () => $('photo-input').click());
|
|
} else {
|
|
slot.className = 'photo-slot';
|
|
slot.style.opacity = '.6';
|
|
}
|
|
grid.appendChild(slot);
|
|
}
|
|
// 绑定删除
|
|
grid.querySelectorAll('.slot-x').forEach(b => {
|
|
b.addEventListener('click', e => {
|
|
e.stopPropagation();
|
|
const i = +b.dataset.i;
|
|
photos.splice(i, 1);
|
|
renderPhotos();
|
|
syncSave();
|
|
});
|
|
});
|
|
}
|
|
|
|
// 文件上传
|
|
$('photo-input').addEventListener('change', e => {
|
|
const files = [...e.target.files].filter(f => f.type.startsWith('image/'));
|
|
const remain = MAX - photos.length;
|
|
files.slice(0, remain).forEach(f => {
|
|
const reader = new FileReader();
|
|
reader.onload = ev => {
|
|
photos.push({
|
|
id: Date.now().toString(36) + Math.random().toString(36).slice(2, 5),
|
|
dataUrl: ev.target.result,
|
|
});
|
|
renderPhotos();
|
|
syncSave();
|
|
};
|
|
reader.readAsDataURL(f);
|
|
});
|
|
e.target.value = '';
|
|
});
|
|
|
|
// 卖点
|
|
const sellList = $('sell-list');
|
|
const sellInput = $('sell-input');
|
|
sellInput.addEventListener('keydown', e => {
|
|
if (e.key !== 'Enter') return;
|
|
e.preventDefault();
|
|
const t = sellInput.value.trim();
|
|
if (!t) return;
|
|
const li = document.createElement('li');
|
|
li.innerHTML = `<span class="num"></span><span class="txt"></span><button class="bl-x" type="button" aria-label="删除"><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></button>`;
|
|
li.querySelector('.txt').textContent = t;
|
|
sellList.querySelector('.add').before(li);
|
|
sellInput.value = '';
|
|
renumberSell();
|
|
li.querySelector('.bl-x').addEventListener('click', () => {
|
|
li.remove();
|
|
renumberSell();
|
|
});
|
|
});
|
|
function renumberSell() {
|
|
sellList.querySelectorAll('li:not(.add) .num').forEach((n, i) => n.textContent = i + 1);
|
|
}
|
|
|
|
// 必填检查
|
|
function syncSave() {
|
|
const hasName = $('p-name').value.trim().length > 0;
|
|
const hasCat = $('p-cat').value.length > 0;
|
|
const hasPhoto = photos.length > 0;
|
|
const ok = hasName && hasCat && hasPhoto;
|
|
$('save-btn').disabled = !ok;
|
|
|
|
const missing = [];
|
|
if (!hasName) missing.push('商品名');
|
|
if (!hasCat) missing.push('品类');
|
|
if (!hasPhoto) missing.push('≥1 张图');
|
|
|
|
const info = $('req-info');
|
|
if (ok) {
|
|
info.innerHTML = '// 必填检查:<span class="ok">已全部完成 ✓</span> · 可进入工作台';
|
|
} else {
|
|
info.innerHTML = `// 必填检查:<span class="miss">${missing.join(' / ')}</span> 未完成`;
|
|
}
|
|
}
|
|
$('p-name').addEventListener('input', syncSave);
|
|
$('p-cat').addEventListener('change', syncSave);
|
|
|
|
// 保存 → 跳工作台
|
|
$('save-btn').addEventListener('click', () => {
|
|
if ($('save-btn').disabled) return;
|
|
Shell.toast('商品已建档', `进入工作台`);
|
|
setTimeout(() => {
|
|
// 真实场景下会带商品 ID;demo 直接跳
|
|
location.href = 'product-studio.html?from=new&onboard=1';
|
|
}, 500);
|
|
});
|
|
|
|
renderPhotos();
|
|
syncSave();
|
|
</script>
|
|
</body>
|
|
</html>
|