Polish static UI flows

This commit is contained in:
iye
2026-05-28 12:29:12 +08:00
parent 5edfa05369
commit bbe29622c2
45 changed files with 2187 additions and 813 deletions
@@ -0,0 +1,16 @@
# Deprecated Static Pages Archive · 2026-05-28
These pages were moved out of the active static app because the current
navigation and production prototype use `products.html`, `product-detail.html`,
`pipeline.html`, `asset-factory.html`, `model-photo.html`, and
`platform-cover.html` instead.
Archived pages:
- `product-create.legacy.html`
- `product-create-v2.html`
- `product-studio.html`
- `studio.html`
- `studio-v2.html`
Keep them only as reference material. They should not be linked from the active
Airshelf static flow.
@@ -0,0 +1,414 @@
<!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>
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff