All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 7s
929 lines
40 KiB
HTML
929 lines
40 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>
|
||
#page-content { padding: 24px 28px 60px; }
|
||
|
||
/* ─── 三 Hero 卡片网格(模特上身图 / 平台套图 / 图片创作 · 等比)─── */
|
||
.factory-hero {
|
||
display: grid; grid-template-columns: repeat(3, minmax(0, 1fr));
|
||
gap: 16px; margin-bottom: 56px;
|
||
}
|
||
@media (max-width: 1400px) { .factory-hero { grid-template-columns: 1fr 1fr; } }
|
||
@media (max-width: 1000px) { .factory-hero { grid-template-columns: 1fr; } }
|
||
|
||
.factory-card {
|
||
position: relative;
|
||
background: var(--surface);
|
||
border: 1px solid var(--border-faint);
|
||
border-radius: var(--r-md);
|
||
padding: 28px 30px;
|
||
overflow: hidden;
|
||
}
|
||
|
||
/* 卡片内 · 文上图下 单列(3 卡并排时保持视觉一致)*/
|
||
.factory-body {
|
||
display: flex; flex-direction: column;
|
||
gap: 18px;
|
||
height: 100%;
|
||
}
|
||
|
||
.factory-text { display: flex; flex-direction: column; min-width: 0; }
|
||
.factory-tag {
|
||
align-self: flex-start;
|
||
font-family: var(--font-mono); font-size: 10.5px;
|
||
color: var(--black-alpha-48); letter-spacing: .06em;
|
||
padding: 2px 8px;
|
||
border: 1px solid var(--border-faint);
|
||
background: var(--background-lighter);
|
||
border-radius: var(--r-sm);
|
||
margin-bottom: 14px;
|
||
}
|
||
.factory-title {
|
||
font-size: 22px; font-weight: 600;
|
||
letter-spacing: -.018em; line-height: 1.25;
|
||
color: var(--accent-black);
|
||
}
|
||
.factory-desc {
|
||
margin-top: 8px;
|
||
font-size: 13.5px; color: var(--black-alpha-64); line-height: 1.55;
|
||
}
|
||
|
||
/* feature 列表 */
|
||
.factory-features {
|
||
list-style: none; padding: 0;
|
||
margin: 22px 0 0;
|
||
display: flex; flex-direction: column; gap: 11px;
|
||
}
|
||
.factory-features li {
|
||
display: flex; align-items: center; gap: 10px;
|
||
font-size: 13px; color: var(--black-alpha-72); font-weight: 500;
|
||
}
|
||
.factory-features .ff-ic {
|
||
width: 26px; height: 26px;
|
||
display: inline-flex; align-items: center; justify-content: center;
|
||
background: var(--heat-12);
|
||
color: var(--heat);
|
||
border-radius: var(--r-md);
|
||
flex-shrink: 0;
|
||
}
|
||
.factory-features .ff-ic svg { width: 14px; height: 14px; }
|
||
|
||
/* 平台 chip 行 */
|
||
.platform-row {
|
||
display: flex; flex-wrap: wrap; gap: 8px;
|
||
margin-top: 20px;
|
||
}
|
||
.platform-chip {
|
||
display: inline-flex; align-items: center; gap: 7px;
|
||
height: 30px; padding: 0 12px 0 8px;
|
||
border: 1px solid var(--border-faint);
|
||
background: var(--surface);
|
||
border-radius: var(--r-pill);
|
||
transition: background var(--t-base);
|
||
cursor: pointer;
|
||
}
|
||
.platform-chip:hover { background: var(--black-alpha-4); }
|
||
.platform-chip .code {
|
||
display: inline-flex; align-items: center; justify-content: center;
|
||
width: 20px; height: 20px;
|
||
background: var(--accent-black); color: var(--accent-white);
|
||
font-family: var(--font-mono); font-size: 8.5px; font-weight: 600;
|
||
border-radius: var(--r-pill); letter-spacing: .04em;
|
||
}
|
||
.platform-chip .nm {
|
||
font-size: 12px; color: var(--accent-black); font-weight: 500;
|
||
}
|
||
|
||
/* CTA 行:主按钮 + 价格 mono */
|
||
.factory-cta {
|
||
margin-top: auto; padding-top: 24px;
|
||
display: flex; align-items: center; gap: 14px;
|
||
}
|
||
.factory-cta .cost {
|
||
font-family: var(--font-mono); font-size: 10.5px;
|
||
color: var(--black-alpha-48); letter-spacing: .04em;
|
||
}
|
||
|
||
/* 视觉占位 + feature 列表已隐藏 — CTA 卡片只保留标题 + 描述 + 按钮 */
|
||
.factory-visual { display: none; }
|
||
.factory-features { display: none; }
|
||
.model-visual { grid-template-columns: repeat(4, 1fr); }
|
||
.model-visual .main { aspect-ratio: 3 / 4; grid-column: span 1; }
|
||
.model-visual .stack { display: contents; }
|
||
.model-visual .stack .placeholder { aspect-ratio: 3 / 4; }
|
||
.kit-visual { grid-template-columns: repeat(4, 1fr); }
|
||
.kit-visual .placeholder { aspect-ratio: 1 / 1; }
|
||
|
||
/* ─── 任务中心 · section header ─── */
|
||
.section-h { display: flex; align-items: center; gap: 12px; margin-top: 24px; margin-bottom: 14px; }
|
||
.section-h h2 { font-size: 18px; font-weight: 600; letter-spacing: -.01em; }
|
||
.section-h .sub-mono { font-family: var(--font-mono); font-size: 11.5px; color: var(--black-alpha-48); letter-spacing: .02em; }
|
||
|
||
/* ─── 视图切换 (复用 projects.html · 图标 + 文字) ─── */
|
||
.view-toggle { display: inline-flex; border: 1px solid var(--border-faint); border-radius: var(--r-md); overflow: hidden; }
|
||
.view-toggle button { padding: 0 14px; background: var(--surface); color: var(--black-alpha-56); font-size: 13px; border: 0; border-right: 1px solid var(--border-faint); border-radius: 0; height: 36px; cursor: pointer; font-family: inherit; display: flex; align-items: center; gap: 6px; transition: background var(--t-base), color var(--t-base); }
|
||
.view-toggle button:last-child { border-right: 0; }
|
||
.view-toggle button:hover { background: var(--background-lighter); color: var(--accent-black); }
|
||
.view-toggle button.active { background: var(--heat-12); color: var(--heat); font-weight: 600; }
|
||
.view-toggle button svg { width: 13px; height: 13px; }
|
||
|
||
/* ─── 网格视图 · 卡片(原 history-grid) ─── */
|
||
.history-grid {
|
||
display: grid; grid-template-columns: repeat(4, 1fr);
|
||
gap: 16px;
|
||
}
|
||
@media (max-width: 1280px) { .history-grid { grid-template-columns: repeat(3, 1fr); } }
|
||
@media (max-width: 960px) { .history-grid { grid-template-columns: repeat(2, 1fr); } }
|
||
.history-grid[hidden] { display: none; }
|
||
|
||
.history-card {
|
||
background: var(--surface);
|
||
border: 1px solid var(--border-faint);
|
||
border-radius: var(--r-md);
|
||
padding: 12px;
|
||
display: grid; grid-template-columns: 78px 1fr; gap: 14px;
|
||
align-items: center;
|
||
transition: background var(--t-base);
|
||
cursor: pointer;
|
||
position: relative;
|
||
}
|
||
.history-card:hover { background: var(--black-alpha-4); }
|
||
.history-card:hover .card-del-btn { opacity: 1; }
|
||
.history-card .placeholder { width: 78px; height: 78px; }
|
||
|
||
/* ─── 列表视图 · 表格 ─── */
|
||
#task-list-view[hidden] { display: none; }
|
||
.task-name-cell { display: flex; align-items: center; gap: 12px; }
|
||
.task-thumb { width: 40px; height: 40px; flex-shrink: 0; border-radius: var(--r-sm); }
|
||
.task-name { font-weight: 600; color: var(--accent-black); font-size: 13.5px; }
|
||
.task-sub { font-size: 11.5px; color: var(--black-alpha-48); margin-top: 3px; font-family: var(--font-mono); letter-spacing: .02em; }
|
||
table.t .task-list-prog { display: flex; align-items: center; gap: 8px; min-width: 120px; }
|
||
table.t .task-list-prog .bar { flex: 1; height: 4px; background: var(--black-alpha-7); border-radius: 2px; overflow: hidden; }
|
||
table.t .task-list-prog .bar span { display: block; height: 100%; background: var(--heat); border-radius: 2px; animation: hp-pulse 1.4s ease-in-out infinite; }
|
||
table.t .task-list-prog .pct { font-family: var(--font-mono); font-size: 10.5px; color: var(--heat); letter-spacing: .02em; white-space: nowrap; }
|
||
|
||
/* ─── 行末 ⋯ 删除气泡 (复用 projects.html) ─── */
|
||
.row-action { display: flex; gap: 4px; justify-content: flex-end; }
|
||
table.t tbody tr .row-more { opacity: 0; transition: opacity .15s; }
|
||
table.t tbody tr:hover .row-more { opacity: 1; }
|
||
.row-more {
|
||
position: relative; display: inline-flex;
|
||
cursor: pointer; align-items: center;
|
||
color: var(--black-alpha-56);
|
||
padding: 4px;
|
||
}
|
||
.row-more:hover { color: var(--accent-black); }
|
||
.row-more-tip {
|
||
position: absolute; top: calc(100% + 6px); right: 0;
|
||
background: var(--surface);
|
||
border: 1px solid var(--border-faint);
|
||
border-radius: var(--r-md);
|
||
box-shadow: 0 4px 16px rgba(0,0,0,.08);
|
||
padding: 4px; min-width: 110px;
|
||
opacity: 0; pointer-events: none;
|
||
transform: translateY(-2px);
|
||
transition: opacity .15s, transform .15s;
|
||
z-index: 12;
|
||
}
|
||
.row-more-tip::before {
|
||
content: ''; position: absolute;
|
||
top: -8px; left: 0; right: 0; height: 8px;
|
||
}
|
||
.row-more:hover .row-more-tip,
|
||
.row-more-tip:hover { opacity: 1; pointer-events: auto; transform: translateY(0); }
|
||
.row-more-tip .mi {
|
||
display: flex; align-items: center; gap: 6px;
|
||
width: 100%; padding: 6px 10px;
|
||
background: transparent; border: 0;
|
||
border-radius: var(--r-sm); cursor: pointer;
|
||
font-size: 12.5px; color: var(--accent-black);
|
||
font-family: inherit; text-align: left;
|
||
transition: background var(--t-base), color var(--t-base);
|
||
}
|
||
.row-more-tip .mi:hover {
|
||
background: var(--crimson-bg, #fdebea);
|
||
color: var(--accent-crimson, #c43d3d);
|
||
}
|
||
.row-more-tip .mi svg { width: 13px; height: 13px; }
|
||
|
||
/* ─── 删除确认 modal · 复用 ─── */
|
||
.modal-bg.show { display: flex; }
|
||
.mono-acc { font-family: var(--font-mono); color: var(--heat); font-weight: 600; }
|
||
|
||
.history-body { min-width: 0; display: flex; flex-direction: column; gap: 4px; }
|
||
.history-name {
|
||
font-size: 13.5px; font-weight: 600; color: var(--accent-black);
|
||
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
|
||
}
|
||
.history-type {
|
||
font-size: 11.5px; color: var(--black-alpha-48);
|
||
font-family: var(--font-mono); letter-spacing: .02em;
|
||
}
|
||
.history-foot {
|
||
display: flex; align-items: center; justify-content: space-between;
|
||
margin-top: 4px; gap: 6px; min-width: 0;
|
||
}
|
||
.history-foot .mono {
|
||
font-family: var(--font-mono); font-size: 10.5px;
|
||
color: var(--black-alpha-48); letter-spacing: .02em;
|
||
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
|
||
}
|
||
.history-foot .pill { padding: 2px 8px; font-size: 10.5px; }
|
||
.history-foot .pill .dot { width: 5px; height: 5px; }
|
||
|
||
/* 进度条(生成中状态) */
|
||
.history-prog {
|
||
margin-top: 6px;
|
||
display: flex; align-items: center; gap: 8px;
|
||
}
|
||
.history-prog .bar {
|
||
flex: 1; height: 4px;
|
||
background: var(--black-alpha-7);
|
||
border-radius: 2px; overflow: hidden;
|
||
}
|
||
.history-prog .bar span {
|
||
display: block; height: 100%;
|
||
background: var(--heat); border-radius: 2px;
|
||
animation: hp-pulse 1.4s ease-in-out infinite;
|
||
}
|
||
@keyframes hp-pulse {
|
||
0%, 100% { opacity: 1; transform: scaleY(1); }
|
||
50% { opacity: .55; transform: scaleY(.7); }
|
||
}
|
||
.history-prog .pct {
|
||
font-family: var(--font-mono); font-size: 10px;
|
||
color: var(--heat); letter-spacing: .02em; white-space: nowrap;
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div id="page">
|
||
|
||
<div class="page-head">
|
||
<div>
|
||
<h1>图片生成</h1>
|
||
<div class="sub">
|
||
<span class="mono">// 一键生成</span>
|
||
<span>·</span>
|
||
<span>电商视觉素材,提升内容制作效率</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 双 Hero 卡片 -->
|
||
<div class="factory-hero">
|
||
|
||
<!-- 卡片 A · 模特上身图 -->
|
||
<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">[ MODEL · TRY-ON ]</span>
|
||
<div class="factory-title">模特上身图</div>
|
||
<div class="factory-desc">选择模特,AI 生成商品模特上身效果图</div>
|
||
|
||
<ul class="factory-features">
|
||
<li>
|
||
<span class="ff-ic">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><circle cx="9" cy="9" r="3"/><circle cx="17" cy="9" r="2.5"/><path d="M3 19c0-3 2.7-5 6-5s6 2 6 5M14 19c.5-2.4 2.4-4 5-4 .8 0 1.5.2 2 .5"/></svg>
|
||
</span>
|
||
支持多模特选择
|
||
</li>
|
||
<li>
|
||
<span class="ff-ic">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="8" height="8"/><rect x="13" y="3" width="8" height="8"/><rect x="3" y="13" width="8" height="8"/><rect x="13" y="13" width="8" height="8"/></svg>
|
||
</span>
|
||
一次生成 4 张
|
||
</li>
|
||
<li>
|
||
<span class="ff-ic">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="m3 7 9-4 9 4-9 4-9-4z"/><path d="m3 12 9 4 9-4M3 17l9 4 9-4"/></svg>
|
||
</span>
|
||
支持多商品并行
|
||
</li>
|
||
</ul>
|
||
|
||
<div class="factory-cta">
|
||
<a class="btn btn-primary btn-lg" href="model-photo.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>
|
||
<span class="cost">[ ≈ ¥0.30 / 张 ]</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="factory-visual model-visual">
|
||
<div class="placeholder main"><span class="ph-frame">Ava · 9:16</span></div>
|
||
<div class="stack">
|
||
<div class="placeholder"><span class="ph-frame">变体 01</span></div>
|
||
<div class="placeholder"><span class="ph-frame">变体 02</span></div>
|
||
<div class="placeholder"><span class="ph-frame">变体 03</span></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 卡片 B · 平台套图 -->
|
||
<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">[ PLATFORM · KIT ]</span>
|
||
<div class="factory-title">平台套图</div>
|
||
<div class="factory-desc">选择平台模板,AI 生成电商平台套图</div>
|
||
|
||
<ul class="factory-features">
|
||
<li>
|
||
<span class="ff-ic">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M3 21V9M9 21V5M15 21v-8M21 21V11"/></svg>
|
||
</span>
|
||
覆盖主流电商平台
|
||
</li>
|
||
<li>
|
||
<span class="ff-ic">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="8" height="8"/><rect x="13" y="3" width="8" height="8"/><rect x="3" y="13" width="8" height="8"/><rect x="13" y="13" width="8" height="8"/></svg>
|
||
</span>
|
||
一键生成 4 张套图
|
||
</li>
|
||
<li>
|
||
<span class="ff-ic">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M12 3l1.7 4.6L18 9l-4.3 1.4L12 15l-1.7-4.6L6 9l4.3-1.4L12 3z"/></svg>
|
||
</span>
|
||
智能排版设计
|
||
</li>
|
||
</ul>
|
||
|
||
<div class="factory-cta">
|
||
<a class="btn btn-primary btn-lg" href="platform-cover.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>
|
||
<span class="cost">[ ≈ ¥0.50 / 张 ]</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="factory-visual kit-visual">
|
||
<div class="placeholder"><span class="ph-frame">套图 / TB</span></div>
|
||
<div class="placeholder"><span class="ph-frame">套图 / DY</span></div>
|
||
<div class="placeholder"><span class="ph-frame">套图 / XHS</span></div>
|
||
<div class="placeholder"><span class="ph-frame">套图 / PDD</span></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 卡片 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">[ IMAGE · STUDIO ]</span>
|
||
<div class="factory-title">图片创作</div>
|
||
<div class="factory-desc">自由创作 AI 图片,适用于详情图 / 海报 / 灵感速写</div>
|
||
|
||
<ul class="factory-features">
|
||
<li>
|
||
<span class="ff-ic">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><circle cx="9" cy="9" r="3"/><path d="M3 19c0-3 2.7-5 6-5s6 2 6 5"/></svg>
|
||
</span>
|
||
人物 · 商品 全支持
|
||
</li>
|
||
<li>
|
||
<span class="ff-ic">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="6" height="18"/><rect x="9" y="3" width="6" height="18"/><rect x="15" y="3" width="6" height="18"/></svg>
|
||
</span>
|
||
正面 / 侧面 / 背面 一次输出
|
||
</li>
|
||
<li>
|
||
<span class="ff-ic">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M12 2v4M12 18v4M4.93 4.93l2.83 2.83M16.24 16.24l2.83 2.83M2 12h4M18 12h4M4.93 19.07l2.83-2.83M16.24 7.76l2.83-2.83"/></svg>
|
||
</span>
|
||
多镜头一致性保证
|
||
</li>
|
||
</ul>
|
||
|
||
<div class="factory-cta">
|
||
<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>
|
||
<span class="cost">[ ≈ ¥0.40 / 组 ]</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="factory-visual tri-visual">
|
||
<div class="placeholder"><span class="ph-frame">正 / 侧 / 背 · 三视图</span></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
</div>
|
||
|
||
<!-- ============= 任务中心 · 参考 projects.html 布局 ============= -->
|
||
<div class="section-h">
|
||
<h2>任务中心</h2>
|
||
<span class="sub-mono">// <span id="tc-sub-total">0</span> 个 · <span id="tc-sub-gen">0</span> 生成中 · <span id="tc-sub-ok">0</span> 已完成 · <span id="tc-sub-err">0</span> 失败</span>
|
||
</div>
|
||
|
||
<!-- 状态 tabs (复用 .tabs) -->
|
||
<div class="tabs" id="tc-tabs">
|
||
<div class="tab active" data-filter="all">全部 <span class="count">0</span></div>
|
||
<div class="tab" data-filter="gen">生成中 <span class="count">0</span></div>
|
||
<div class="tab" data-filter="ok">已完成 <span class="count">0</span></div>
|
||
<div class="tab" data-filter="err">失败 <span class="count">0</span></div>
|
||
</div>
|
||
|
||
<!-- toolbar: search + 类型 chip + clear + view-toggle -->
|
||
<div class="toolbar">
|
||
<div class="search-inline">
|
||
<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>
|
||
</div>
|
||
<button class="clear-filters" id="tc-clear" type="button" hidden>
|
||
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M4 4l8 8M12 4l-8 8"/></svg>
|
||
清空筛选
|
||
</button>
|
||
<span class="spacer"></span>
|
||
<div class="view-toggle" id="tc-view-toggle">
|
||
<button type="button" data-view="grid">
|
||
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="2" y="2" width="5" height="5"/><rect x="9" y="2" width="5" height="5"/><rect x="2" y="9" width="5" height="5"/><rect x="9" y="9" width="5" height="5"/></svg>
|
||
网格
|
||
</button>
|
||
<button type="button" class="active" data-view="list">
|
||
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M2 4h12M2 8h12M2 12h12"/></svg>
|
||
列表
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="result-meta" id="tc-result-meta">// 显示 <span class="count">0</span> / 0 个任务</div>
|
||
|
||
<!-- ============= LIST VIEW (默认) ============= -->
|
||
<div id="task-list-view">
|
||
<table class="t">
|
||
<thead>
|
||
<tr>
|
||
<th style="width:42%">任务</th>
|
||
<th style="width:160px">进度</th>
|
||
<th>状态</th>
|
||
<th style="width:120px">创建于</th>
|
||
<th style="width:48px"></th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="task-list-tbody"><!-- JS 从卡片同步生成 --></tbody>
|
||
</table>
|
||
</div>
|
||
|
||
<!-- ============= GRID VIEW (JS 从 localStorage 动态渲染) ============= -->
|
||
<div class="history-grid" id="task-grid" hidden></div>
|
||
|
||
<!-- 空态 -->
|
||
<div id="tc-empty" hidden style="padding:60px 20px;text-align:center;color:var(--black-alpha-48);font-size:13px;line-height:1.6">
|
||
<div style="font-family:var(--font-mono);font-size:11px;letter-spacing:.04em;margin-bottom:6px;">// NO TASKS YET</div>
|
||
<div id="tc-empty-text">还没有任务,去上方选一个工序开始生成吧</div>
|
||
</div>
|
||
|
||
</div>
|
||
|
||
<!-- ===== 删除确认 modal (复用 projects.html 风格) ===== -->
|
||
<div class="modal-bg" id="tc-del-bg">
|
||
<div class="modal" role="dialog">
|
||
<span class="corner-tr" aria-hidden></span>
|
||
<span class="corner-bl" aria-hidden></span>
|
||
<div class="modal-h">
|
||
<div class="ic-m" style="background:var(--crimson-bg,#fdebea);color:var(--accent-crimson,#c43d3d)">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><path d="M3 6h18"/><path d="M8 6V4a2 2 0 012-2h4a2 2 0 012 2v2"/><path d="M19 6l-1.5 14a2 2 0 01-2 1.8H8.5a2 2 0 01-2-1.8L5 6"/></svg>
|
||
</div>
|
||
<div class="ti">确认删除任务<span>// CONFIRM DELETE</span></div>
|
||
</div>
|
||
<div class="modal-b" id="tc-del-body">即将删除任务记录。</div>
|
||
<div class="modal-f">
|
||
<button class="btn" type="button" id="tc-del-cancel">取消</button>
|
||
<button class="btn" type="button" id="tc-del-ok" style="background:var(--accent-crimson);color:var(--accent-white);border-color:var(--accent-crimson)">
|
||
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M3 6h18"/></svg>
|
||
确认删除
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<script src="assets/icons.js?v=2026052608"></script>
|
||
<script src="assets/shell.js?v=2026052607"></script>
|
||
<script src="assets/new-product-drawer.js?v=202605211643"></script>
|
||
<script>
|
||
Shell.render({
|
||
active: 'asset-factory',
|
||
crumbs: [{ label: '工作台', href: 'index.html' }, { label: '图片生成' }]
|
||
});
|
||
|
||
/* ============================================================
|
||
任务中心 · 从 localStorage 读取(与 model-photo / platform-cover 共享)
|
||
============================================================ */
|
||
(function () {
|
||
'use strict';
|
||
|
||
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',
|
||
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');
|
||
const gridView = taskGrid;
|
||
const listView = document.getElementById('task-list-view');
|
||
|
||
let cards = []; // 动态生成的 .task-card 元素数组(顺序与任务时间倒序一致)
|
||
|
||
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])); }
|
||
|
||
/* ---------- localStorage 读写 ---------- */
|
||
function loadType(type) {
|
||
try { return JSON.parse(localStorage.getItem(KEY_BY_TYPE[type]) || '[]'); } catch (e) { return []; }
|
||
}
|
||
function saveType(type, arr) {
|
||
try { localStorage.setItem(KEY_BY_TYPE[type], JSON.stringify(arr)); } catch (e) {}
|
||
}
|
||
function loadAllTasks() {
|
||
const all = [];
|
||
Object.keys(KEY_BY_TYPE).forEach(type => {
|
||
loadType(type).forEach(t => all.push({ ...t, type })); // type 兜底
|
||
});
|
||
all.sort((a, b) => (b.createdAt || 0) - (a.createdAt || 0));
|
||
return all;
|
||
}
|
||
|
||
/* ---------- task → 渲染辅助 ---------- */
|
||
function subTextOf(t) {
|
||
const lbl = TYPE_LABEL[t.type] || t.type;
|
||
const count = (t.snap && t.snap.count) || 4;
|
||
return `${lbl} · ${count} 张`;
|
||
}
|
||
function thumbLabelOf(t) {
|
||
// 取「商品 × 模特/平台」中右半作为缩略图占位文字
|
||
const parts = (t.name || '').split(/\s[×x]\s/);
|
||
return (parts[1] || parts[0] || '—').slice(0, 8);
|
||
}
|
||
|
||
/* ---------- 点击行 / 卡片 → 跳转工作台(携带 taskId) ---------- */
|
||
function goToWorkbench(t) {
|
||
const base = URL_BY_TYPE[t.type] || URL_BY_TYPE.model;
|
||
location.href = base + '?taskId=' + encodeURIComponent(t.id);
|
||
}
|
||
|
||
/* ---------- 1. 从 task 数据生成卡片 + list 行 ---------- */
|
||
function cardFor(t) {
|
||
const card = document.createElement('div');
|
||
card.className = 'task-card history-card';
|
||
card.dataset.status = t.status;
|
||
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="删除任务">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><path d="M3 6h18"/><path d="M8 6V4a2 2 0 012-2h4a2 2 0 012 2v2"/><path d="M19 6l-1.5 14a2 2 0 01-2 1.8H8.5a2 2 0 01-2-1.8L5 6"/></svg>
|
||
</button>
|
||
<div class="placeholder"><span class="ph-frame">${esc(thumbLabelOf(t))}</span></div>
|
||
<div class="history-body">
|
||
<div class="history-name">${esc(t.name)}</div>
|
||
<div class="history-type">${esc(subTextOf(t))}</div>
|
||
<div class="history-foot">
|
||
<span class="mono">// ${esc(t.time || '')}</span>
|
||
<span class="pill ${STATUS_PILL[t.status] || 'info'}"><span class="dot"></span>${esc(STATUS_LABEL[t.status] || t.status)}</span>
|
||
</div>
|
||
</div>
|
||
`;
|
||
return card;
|
||
}
|
||
|
||
function rowFor(t) {
|
||
const tr = document.createElement('tr');
|
||
tr.dataset.taskRow = '1';
|
||
tr.dataset.name = t.name;
|
||
tr.dataset.type = t.type;
|
||
tr.dataset.status = t.status;
|
||
tr.dataset.taskId = t.id;
|
||
tr.addEventListener('click', () => goToWorkbench(t));
|
||
tr.innerHTML = `
|
||
<td>
|
||
<div class="task-name-cell">
|
||
<div class="placeholder task-thumb"><span class="ph-frame">${esc(thumbLabelOf(t))}</span></div>
|
||
<div>
|
||
<div class="task-name">${esc(t.name)}</div>
|
||
<div class="task-sub">${esc(subTextOf(t))}</div>
|
||
</div>
|
||
</div>
|
||
</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>
|
||
<td><span class="pill ${STATUS_PILL[t.status] || 'info'}"><span class="dot"></span>${esc(STATUS_LABEL[t.status] || t.status)}</span></td>
|
||
<td class="muted-2">${esc(t.time || '')}</td>
|
||
<td>
|
||
<div class="row-action">
|
||
<span class="row-more"><svg width="14" height="14" viewBox="0 0 16 16"><circle cx="3" cy="8" r="1.2" fill="currentColor"/><circle cx="8" cy="8" r="1.2" fill="currentColor"/><circle cx="13" cy="8" r="1.2" fill="currentColor"/></svg>
|
||
<div class="row-more-tip"><button class="mi mi-del-task" type="button"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><path d="M3 6h18"/><path d="M8 6V4a2 2 0 012-2h4a2 2 0 012 2v2"/><path d="M19 6l-1.5 14a2 2 0 01-2 1.8H8.5a2 2 0 01-2-1.8L5 6"/></svg>删除任务</button></div>
|
||
</span>
|
||
</div>
|
||
</td>
|
||
`;
|
||
tr.querySelector('.row-more').addEventListener('click', e => e.stopPropagation());
|
||
return tr;
|
||
}
|
||
|
||
/* ---------- 2. 全量重渲染(load → render → filter) ---------- */
|
||
function renderAll() {
|
||
// 清空 grid / list
|
||
taskGrid.innerHTML = '';
|
||
listTbody.innerHTML = '';
|
||
cards = [];
|
||
|
||
const tasks = loadAllTasks();
|
||
tasks.forEach(t => {
|
||
const card = cardFor(t);
|
||
const row = rowFor(t);
|
||
taskGrid.appendChild(card);
|
||
listTbody.appendChild(row);
|
||
card._listRow = row;
|
||
card._task = t;
|
||
row._card = card;
|
||
cards.push(card);
|
||
|
||
// 卡片点击 → 跳工作台
|
||
card.addEventListener('click', e => {
|
||
if (e.target.closest('.card-del-btn')) return;
|
||
goToWorkbench(t);
|
||
});
|
||
// 卡片删除按钮
|
||
card.querySelector('.card-del-btn').addEventListener('click', e => {
|
||
e.stopPropagation();
|
||
openDelConfirm(card);
|
||
});
|
||
});
|
||
|
||
// 类型 chip 菜单(基于现有 task 的 type 集合,动态)
|
||
rebuildTypeMenu();
|
||
applyFilter();
|
||
}
|
||
|
||
/* ---------- 3. 构建类型 chip 菜单(基于当前 cards 的 type 集合) ---------- */
|
||
const checkSvg = '<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>';
|
||
const typeMenu = document.querySelector('.chip-wrap[data-key="type"] .chip-menu');
|
||
function rebuildTypeMenu() {
|
||
const typeOptions = [...new Set(cards.map(c => c.dataset.type))];
|
||
typeMenu.innerHTML = `<div class="mi selected" data-value="all">${checkSvg}<span>全部任务类型</span></div><div class="mi-sep"></div>`
|
||
+ typeOptions.map(v => `<div class="mi" data-value="${esc(v)}">${checkSvg}<span>${esc(TYPE_LABEL[v] || v)}</span></div>`).join('');
|
||
syncTypeChip();
|
||
}
|
||
|
||
function syncTypeChip() {
|
||
const wrap = document.querySelector('.chip-wrap[data-key="type"]');
|
||
const label = wrap.querySelector('.chip-label');
|
||
const chip = wrap.querySelector('.chip');
|
||
if (state.type === 'all') {
|
||
label.textContent = '任务类型';
|
||
chip.classList.remove('active');
|
||
} else {
|
||
label.textContent = TYPE_LABEL[state.type] || state.type;
|
||
chip.classList.add('active');
|
||
}
|
||
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();
|
||
let visible = 0;
|
||
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 && okTime && okSearch;
|
||
card.style.display = show ? '' : 'none';
|
||
if (card._listRow) card._listRow.style.display = show ? '' : 'none';
|
||
if (show) visible++;
|
||
});
|
||
|
||
// 计数
|
||
const counts = { all: cards.length, gen: 0, ok: 0, err: 0 };
|
||
cards.forEach(c => { if (counts[c.dataset.status] !== undefined) counts[c.dataset.status]++; });
|
||
document.querySelectorAll('#tc-tabs .tab').forEach(t => {
|
||
const f = t.dataset.filter;
|
||
t.querySelector('.count').textContent = f === 'all' ? counts.all : counts[f];
|
||
});
|
||
document.getElementById('tc-sub-total').textContent = counts.all;
|
||
document.getElementById('tc-sub-gen').textContent = counts.gen;
|
||
document.getElementById('tc-sub-ok').textContent = counts.ok;
|
||
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' || state.time !== 'all');
|
||
|
||
// 空态
|
||
const emptyEl = document.getElementById('tc-empty');
|
||
const emptyText = document.getElementById('tc-empty-text');
|
||
if (cards.length === 0) {
|
||
emptyEl.hidden = false;
|
||
emptyText.textContent = '还没有任务,去上方选一个工序开始生成吧';
|
||
listView.hidden = true; gridView.hidden = true;
|
||
} else if (visible === 0) {
|
||
emptyEl.hidden = false;
|
||
emptyText.textContent = '没有符合筛选条件的任务';
|
||
// 视图本身保留,空表头也保留
|
||
} else {
|
||
emptyEl.hidden = true;
|
||
// 恢复 view 显示(可能在 length===0 分支被隐藏)
|
||
if (state.view === 'list') { listView.hidden = false; gridView.hidden = true; }
|
||
else { listView.hidden = true; gridView.hidden = false; }
|
||
}
|
||
}
|
||
|
||
/* ---------- 4. 事件绑定 ---------- */
|
||
// status tabs
|
||
document.querySelectorAll('#tc-tabs .tab').forEach(t => {
|
||
t.addEventListener('click', () => {
|
||
document.querySelectorAll('#tc-tabs .tab').forEach(x => x.classList.remove('active'));
|
||
t.classList.add('active');
|
||
state.filter = t.dataset.filter;
|
||
applyFilter();
|
||
});
|
||
});
|
||
// search
|
||
document.getElementById('tc-search').addEventListener('input', e => {
|
||
state.search = e.target.value.trim();
|
||
applyFilter();
|
||
});
|
||
// type chip
|
||
const typeWrap = document.querySelector('.chip-wrap[data-key="type"]');
|
||
typeWrap.querySelector('.chip').addEventListener('click', e => {
|
||
e.stopPropagation();
|
||
const isOpen = typeWrap.classList.contains('open');
|
||
document.querySelectorAll('.chip-wrap.open').forEach(w => w.classList.remove('open'));
|
||
if (!isOpen) typeWrap.classList.add('open');
|
||
});
|
||
typeMenu.addEventListener('click', e => {
|
||
const mi = e.target.closest('.mi');
|
||
if (!mi) return;
|
||
e.stopPropagation();
|
||
state.type = mi.dataset.value;
|
||
typeWrap.classList.remove('open');
|
||
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'));
|
||
});
|
||
// clear filters
|
||
document.getElementById('tc-clear').addEventListener('click', () => {
|
||
state.search = '';
|
||
state.type = 'all';
|
||
state.time = 'all';
|
||
document.getElementById('tc-search').value = '';
|
||
syncTypeChip();
|
||
syncTimeChip();
|
||
applyFilter();
|
||
Shell.toast('已清空筛选');
|
||
});
|
||
// view toggle
|
||
document.querySelectorAll('#tc-view-toggle button').forEach(b => {
|
||
b.addEventListener('click', () => {
|
||
document.querySelectorAll('#tc-view-toggle button').forEach(x => x.classList.remove('active'));
|
||
b.classList.add('active');
|
||
state.view = b.dataset.view;
|
||
if (state.view === 'list') { listView.hidden = false; gridView.hidden = true; }
|
||
else { listView.hidden = true; gridView.hidden = false; }
|
||
});
|
||
});
|
||
|
||
/* ---------- 6. 删除 modal + 同步 localStorage ---------- */
|
||
const delBg = document.getElementById('tc-del-bg');
|
||
const delBody = document.getElementById('tc-del-body');
|
||
const delCancel = document.getElementById('tc-del-cancel');
|
||
const delOk = document.getElementById('tc-del-ok');
|
||
let _delTarget = null;
|
||
|
||
function openDelConfirm(target) {
|
||
_delTarget = target;
|
||
const name = target.dataset.name || '该任务';
|
||
delBody.innerHTML = '即将删除任务 <span class="mono-acc">' + esc(name) + '</span>。任务记录将清除,已入库的素材不受影响。';
|
||
delBg.classList.add('show');
|
||
}
|
||
function closeDelConfirm() { delBg.classList.remove('show'); _delTarget = null; }
|
||
delCancel.addEventListener('click', closeDelConfirm);
|
||
delBg.addEventListener('click', e => { if (e.target === delBg) closeDelConfirm(); });
|
||
delOk.addEventListener('click', () => {
|
||
if (!_delTarget) return;
|
||
const card = _delTarget._card || _delTarget; // 可能传 row 或 card
|
||
const taskId = card.dataset.taskId;
|
||
const taskType = card.dataset.type;
|
||
const name = card.dataset.name;
|
||
// 从 localStorage 移除对应任务
|
||
if (taskType && KEY_BY_TYPE[taskType]) {
|
||
const arr = loadType(taskType).filter(t => t.id !== taskId);
|
||
saveType(taskType, arr);
|
||
}
|
||
closeDelConfirm();
|
||
Shell.toast('已删除', name);
|
||
renderAll();
|
||
});
|
||
|
||
// 列表行 删除按钮(事件委托,因为是动态生成)
|
||
listTbody.addEventListener('click', e => {
|
||
const btn = e.target.closest('.mi-del-task');
|
||
if (!btn) return;
|
||
e.stopPropagation();
|
||
const tr = btn.closest('tr');
|
||
if (tr) openDelConfirm(tr);
|
||
});
|
||
|
||
/* ---------- 7. 初始化 ---------- */
|
||
renderAll();
|
||
})();
|
||
</script>
|
||
</body>
|
||
</html>
|