feat(workbench): 三工序图片区视觉对齐 + 任务中心聚合 + 工具台头部筛选
- model-photo / platform-cover · 头部 toolbar 落地: 时间 / 模特(平台) chip 下拉 + 折叠搜索 - model-photo / platform-cover · 图片卡片样式同步图片创作 (.io-cell): bg / hover / .gen 脉冲 / .err 红框 - model-photo / platform-cover · 单图 hover overlay: 再次生成 + 下载 + 更多(加入资产库/删除) - model-photo / platform-cover · 批次底栏: 再次生成图标统一 + 更多 menu(全部加入资产库/删除该批) - model-photo · 修 TDZ bug: renderModelMini 调用挪到 MODELS 声明后, 解决整页崩溃 - model-photo · 去掉冗余 pv-summary, 商品自动选最近编辑, task 写入 name 字段 - image-optimize · 单图右上加再次生成图标, 加入 fs-image-tasks-image 与任务中心打通 - image-optimize · 输入区拆 3 行: + 在顶 / textarea 满宽 / 发送在底栏右; 参考图缩略与加号同 64×64 - asset-factory · 任务中心加时间 chip + image 类型 + 跳转表; 删冗余类型列 - pipeline · stage2 商品卡换商品库风格 + AI 生成三视图主 CTA + .tri-missing-badge[hidden] CSS 修复
This commit is contained in:
+85
-29
@@ -8,7 +8,7 @@
|
||||
<style>
|
||||
#page-content { padding: 24px 28px 60px; }
|
||||
|
||||
/* ─── 三 Hero 卡片网格(模特上身图 / 平台套图 / 图片优化 · 等比)─── */
|
||||
/* ─── 三 Hero 卡片网格(模特上身图 / 平台套图 / 图片创作 · 等比)─── */
|
||||
.factory-hero {
|
||||
display: grid; grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
gap: 16px; margin-bottom: 56px;
|
||||
@@ -118,9 +118,6 @@
|
||||
.model-visual .stack .placeholder { aspect-ratio: 3 / 4; }
|
||||
.kit-visual { grid-template-columns: repeat(4, 1fr); }
|
||||
.kit-visual .placeholder { aspect-ratio: 1 / 1; }
|
||||
.tri-visual { display: block; }
|
||||
.tri-visual .placeholder { aspect-ratio: 16 / 9; }
|
||||
.tri-visual .placeholder + .placeholder { display: none; }
|
||||
|
||||
/* ─── 任务中心 · section header ─── */
|
||||
.section-h { display: flex; align-items: center; gap: 12px; margin-top: 24px; margin-bottom: 14px; }
|
||||
@@ -365,14 +362,6 @@
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div class="platform-row">
|
||||
<div class="platform-chip"><span class="code">DY</span><span class="nm">抖音</span></div>
|
||||
<div class="platform-chip"><span class="code">TB</span><span class="nm">淘宝</span></div>
|
||||
<div class="platform-chip"><span class="code">XHS</span><span class="nm">小红书</span></div>
|
||||
<div class="platform-chip"><span class="code">PDD</span><span class="nm">拼多多</span></div>
|
||||
<div class="platform-chip"><span class="code">AMZ</span><span class="nm">亚马逊</span></div>
|
||||
</div>
|
||||
|
||||
<div class="factory-cta">
|
||||
<a class="btn btn-primary btn-lg" href="platform-cover.html">
|
||||
开始生成
|
||||
@@ -391,16 +380,16 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 卡片 C · 图片优化(生成人物 / 商品三视图)-->
|
||||
<!-- 卡片 C · 图片创作(自由创作 AI 图片工作台)-->
|
||||
<div class="factory-card with-corners">
|
||||
<span class="corner-tr" aria-hidden></span>
|
||||
<span class="corner-bl" aria-hidden></span>
|
||||
|
||||
<div class="factory-body">
|
||||
<div class="factory-text">
|
||||
<span class="factory-tag">[ TRI-VIEW · OPTIMIZE ]</span>
|
||||
<div class="factory-title">图片优化</div>
|
||||
<div class="factory-desc">为人物 / 商品生成三视图,保证多镜头一致性</div>
|
||||
<span class="factory-tag">[ IMAGE · STUDIO ]</span>
|
||||
<div class="factory-title">图片创作</div>
|
||||
<div class="factory-desc">自由创作 AI 图片,适用于详情图 / 海报 / 灵感速写</div>
|
||||
|
||||
<ul class="factory-features">
|
||||
<li>
|
||||
@@ -424,7 +413,7 @@
|
||||
</ul>
|
||||
|
||||
<div class="factory-cta">
|
||||
<a class="btn btn-primary btn-lg" href="model-photo.html?mode=tri">
|
||||
<a class="btn btn-primary btn-lg" href="image-optimize.html">
|
||||
开始生成
|
||||
<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="M5 12h14M12 5l7 7-7 7"/></svg>
|
||||
</a>
|
||||
@@ -460,6 +449,16 @@
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><circle cx="11" cy="11" r="7"/><path d="m21 21-4.3-4.3"/></svg>
|
||||
<input class="input" id="tc-search" placeholder="搜索任务名">
|
||||
</div>
|
||||
<div class="chip-wrap" data-key="time">
|
||||
<button class="chip" type="button"><span class="chip-label">时间</span> <svg class="caret" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M4 6l4 4 4-4"/></svg></button>
|
||||
<div class="chip-menu">
|
||||
<div class="mi selected" data-value="all"><svg class="mi-check" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="3 8 7 12 13 4"/></svg><span>全部时间</span></div>
|
||||
<div class="mi-sep"></div>
|
||||
<div class="mi" data-value="today"><svg class="mi-check" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="3 8 7 12 13 4"/></svg><span>今天</span></div>
|
||||
<div class="mi" data-value="1h"><svg class="mi-check" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="3 8 7 12 13 4"/></svg><span>1 小时内</span></div>
|
||||
<div class="mi" data-value="10min"><svg class="mi-check" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="3 8 7 12 13 4"/></svg><span>10 分钟内</span></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="chip-wrap" data-key="type">
|
||||
<button class="chip" type="button"><span class="chip-label">任务类型</span> <svg class="caret" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M4 6l4 4 4-4"/></svg></button>
|
||||
<div class="chip-menu"></div>
|
||||
@@ -488,9 +487,8 @@
|
||||
<table class="t">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width:32%">任务</th>
|
||||
<th>类型</th>
|
||||
<th style="width:140px">进度</th>
|
||||
<th style="width:42%">任务</th>
|
||||
<th style="width:160px">进度</th>
|
||||
<th>状态</th>
|
||||
<th style="width:120px">创建于</th>
|
||||
<th style="width:48px"></th>
|
||||
@@ -547,10 +545,19 @@ Shell.render({
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
const TYPE_LABEL = { model: '模特上身图', platform: '平台套图' };
|
||||
const TYPE_LABEL = { model: '模特上身图', platform: '平台套图', image: '图片创作' };
|
||||
const STATUS_LABEL = { ok: '已完成', gen: '生成中', err: '失败' };
|
||||
const STATUS_PILL = { ok: 'ok', gen: 'info', err: 'err' };
|
||||
const KEY_BY_TYPE = { model: 'fs-image-tasks-model', platform: 'fs-image-tasks-platform' };
|
||||
const KEY_BY_TYPE = {
|
||||
model: 'fs-image-tasks-model',
|
||||
platform: 'fs-image-tasks-platform',
|
||||
image: 'fs-image-tasks-image',
|
||||
};
|
||||
const URL_BY_TYPE = {
|
||||
model: 'model-photo.html',
|
||||
platform: 'platform-cover.html',
|
||||
image: 'image-optimize.html',
|
||||
};
|
||||
|
||||
const taskGrid = document.getElementById('task-grid');
|
||||
const listTbody = document.getElementById('task-list-tbody');
|
||||
@@ -559,7 +566,21 @@ Shell.render({
|
||||
|
||||
let cards = []; // 动态生成的 .task-card 元素数组(顺序与任务时间倒序一致)
|
||||
|
||||
const state = { filter: 'all', type: 'all', search: '', view: 'list' };
|
||||
const state = { filter: 'all', type: 'all', time: 'all', search: '', view: 'list' };
|
||||
|
||||
function _timeMatch(createdAt, key) {
|
||||
if (key === 'all' || !createdAt) return true;
|
||||
const now = Date.now();
|
||||
const diff = now - Number(createdAt);
|
||||
if (key === '10min') return diff <= 10 * 60 * 1000;
|
||||
if (key === '1h') return diff <= 60 * 60 * 1000;
|
||||
if (key === 'today') {
|
||||
const a = new Date(now); const b = new Date(Number(createdAt));
|
||||
return a.toDateString() === b.toDateString();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
const TIME_LABEL = { all: '时间', today: '今天', '1h': '1 小时内', '10min': '10 分钟内' };
|
||||
|
||||
function esc(s) { return String(s == null ? '' : s).replace(/[&<>"']/g, c => ({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c])); }
|
||||
|
||||
@@ -593,9 +614,8 @@ Shell.render({
|
||||
|
||||
/* ---------- 点击行 / 卡片 → 跳转工作台(携带 taskId) ---------- */
|
||||
function goToWorkbench(t) {
|
||||
const url = (t.type === 'model' ? 'model-photo.html' : 'platform-cover.html')
|
||||
+ '?taskId=' + encodeURIComponent(t.id);
|
||||
location.href = url;
|
||||
const base = URL_BY_TYPE[t.type] || URL_BY_TYPE.model;
|
||||
location.href = base + '?taskId=' + encodeURIComponent(t.id);
|
||||
}
|
||||
|
||||
/* ---------- 1. 从 task 数据生成卡片 + list 行 ---------- */
|
||||
@@ -606,6 +626,7 @@ Shell.render({
|
||||
card.dataset.type = t.type;
|
||||
card.dataset.name = t.name;
|
||||
card.dataset.taskId = t.id;
|
||||
card.dataset.createdAt = String(t.createdAt || 0);
|
||||
card.style.cursor = 'pointer';
|
||||
card.innerHTML = `
|
||||
<button class="card-del-btn" type="button" title="删除任务">
|
||||
@@ -642,7 +663,6 @@ Shell.render({
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td><span class="muted">${esc(TYPE_LABEL[t.type] || t.type)}</span></td>
|
||||
<td>${t.status === 'gen'
|
||||
? `<div class="task-list-prog"><div class="bar"><span style="width:60%"></span></div><span class="pct">60%</span></div>`
|
||||
: (t.status === 'ok' ? '<span class="muted-2 mono" style="font-size:11px;">已完成</span>' : '<span class="muted-2 mono" style="font-size:11px;">—</span>')}</td>
|
||||
@@ -719,6 +739,21 @@ Shell.render({
|
||||
wrap.querySelectorAll('.mi').forEach(mi => mi.classList.toggle('selected', mi.dataset.value === state.type));
|
||||
}
|
||||
|
||||
function syncTimeChip() {
|
||||
const wrap = document.querySelector('.chip-wrap[data-key="time"]');
|
||||
if (!wrap) return;
|
||||
const label = wrap.querySelector('.chip-label');
|
||||
const chip = wrap.querySelector('.chip');
|
||||
if (state.time === 'all') {
|
||||
label.textContent = '时间';
|
||||
chip.classList.remove('active');
|
||||
} else {
|
||||
label.textContent = TIME_LABEL[state.time] || state.time;
|
||||
chip.classList.add('active');
|
||||
}
|
||||
wrap.querySelectorAll('.mi').forEach(mi => mi.classList.toggle('selected', mi.dataset.value === state.time));
|
||||
}
|
||||
|
||||
/* ---------- 3. applyFilter ---------- */
|
||||
function applyFilter() {
|
||||
const q = state.search.toLowerCase();
|
||||
@@ -726,8 +761,9 @@ Shell.render({
|
||||
cards.forEach(card => {
|
||||
const okStatus = state.filter === 'all' || card.dataset.status === state.filter;
|
||||
const okType = state.type === 'all' || card.dataset.type === state.type;
|
||||
const okTime = _timeMatch(card.dataset.createdAt, state.time);
|
||||
const okSearch = !q || (card.dataset.name || '').toLowerCase().includes(q);
|
||||
const show = okStatus && okType && okSearch;
|
||||
const show = okStatus && okType && okTime && okSearch;
|
||||
card.style.display = show ? '' : 'none';
|
||||
if (card._listRow) card._listRow.style.display = show ? '' : 'none';
|
||||
if (show) visible++;
|
||||
@@ -746,7 +782,7 @@ Shell.render({
|
||||
document.getElementById('tc-sub-err').textContent = counts.err;
|
||||
|
||||
document.getElementById('tc-result-meta').innerHTML = `// 显示 <span class="count">${visible}</span> / ${cards.length} 个任务`;
|
||||
document.getElementById('tc-clear').hidden = !(state.search || state.type !== 'all');
|
||||
document.getElementById('tc-clear').hidden = !(state.search || state.type !== 'all' || state.time !== 'all');
|
||||
|
||||
// 空态
|
||||
const emptyEl = document.getElementById('tc-empty');
|
||||
@@ -799,6 +835,24 @@ Shell.render({
|
||||
syncTypeChip();
|
||||
applyFilter();
|
||||
});
|
||||
// time chip
|
||||
const timeWrap = document.querySelector('.chip-wrap[data-key="time"]');
|
||||
const timeMenu = timeWrap.querySelector('.chip-menu');
|
||||
timeWrap.querySelector('.chip').addEventListener('click', e => {
|
||||
e.stopPropagation();
|
||||
const isOpen = timeWrap.classList.contains('open');
|
||||
document.querySelectorAll('.chip-wrap.open').forEach(w => w.classList.remove('open'));
|
||||
if (!isOpen) timeWrap.classList.add('open');
|
||||
});
|
||||
timeMenu.addEventListener('click', e => {
|
||||
const mi = e.target.closest('.mi');
|
||||
if (!mi) return;
|
||||
e.stopPropagation();
|
||||
state.time = mi.dataset.value;
|
||||
timeWrap.classList.remove('open');
|
||||
syncTimeChip();
|
||||
applyFilter();
|
||||
});
|
||||
document.addEventListener('click', () => {
|
||||
document.querySelectorAll('.chip-wrap.open').forEach(w => w.classList.remove('open'));
|
||||
});
|
||||
@@ -806,8 +860,10 @@ Shell.render({
|
||||
document.getElementById('tc-clear').addEventListener('click', () => {
|
||||
state.search = '';
|
||||
state.type = 'all';
|
||||
state.time = 'all';
|
||||
document.getElementById('tc-search').value = '';
|
||||
syncTypeChip();
|
||||
syncTimeChip();
|
||||
applyFilter();
|
||||
Shell.toast('已清空筛选');
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user