AirShelf/v2/product-create-v2.html
UI 设计 e293aa43be
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 6s
feat(v2): 添加 V2.1 设计稿目录 · 团队/设置页 · pipeline 多项 mock 优化
2026-05-21 16:18:28 +08:00

414 lines
15 KiB
HTML

<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<title>新建商品 · 流·Studio</title>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
<link rel="stylesheet" href="assets/restraint.css">
<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/shell.js"></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="2" 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>