更新电商AI平台原型交互
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 7s

This commit is contained in:
iye 2026-05-25 19:12:56 +08:00
parent 04335f3269
commit 553014cc79
23 changed files with 6661 additions and 860 deletions

View File

@ -249,7 +249,7 @@
<div class="export-wrap" style="flex:1; position:relative;">
<button class="btn btn-ghost btn-lg" id="export-trigger" type="button" style="width:100%;">
导出账单
<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="margin-left:6px; vertical-align:-1px;"><polyline points="6 9 12 15 18 9"/></svg>
<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" style="margin-left:6px; vertical-align:-1px;"><polyline points="6 9 12 15 18 9"/></svg>
</button>
<div class="export-menu" id="export-menu" hidden>
<button type="button" data-fmt="csv"><span class="ic"></span><span class="t">CSV<span class="d">// 全部明细 · Excel 兼容</span></span></button>
@ -479,7 +479,7 @@
<span class="corner-tr" aria-hidden></span><span class="corner-bl" aria-hidden></span>
<div class="modal-h">
<div class="ic-m">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="9"/><path d="M12 7v10M9 10h4a1.5 1.5 0 0 1 0 3h-3a1.5 1.5 0 0 0 0 3h4"/></svg>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="9"/><path d="M12 7v10M9 10h4a1.5 1.5 0 0 1 0 3h-3a1.5 1.5 0 0 0 0 3h4"/></svg>
</div>
<div class="ti">扫码支付<span id="topup-channel-label">// 微信支付</span></div>
</div>

View File

@ -167,6 +167,7 @@ img, svg, video { display: block; max-width: 100%; }
.muted-2 { color: var(--black-alpha-48); }
.spacer { flex: 1; }
.hstack { display: flex; align-items: center; gap: 8px; }
.hstack[hidden], .vstack[hidden] { display: none; }
.vstack { display: flex; flex-direction: column; gap: 8px; }
.divider { height: 1px; background: var(--border-faint); margin: 16px 0; }
@ -374,6 +375,7 @@ main { position: relative; background: var(--background-base); min-width: 0; }
transition: background var(--t-base), border-color var(--t-base);
}
.queue-chip:hover { background: var(--black-alpha-4); border-color: var(--black-alpha-24); }
.queue-chip[hidden] { display: none; }
.queue-chip svg { width: 14px; height: 14px; color: var(--black-alpha-56); }
.queue-chip .count {
display: inline-flex; align-items: center; justify-content: center;
@ -481,6 +483,7 @@ main { position: relative; background: var(--background-base); min-width: 0; }
border-color: var(--black-alpha-12);
}
.btn svg { width: 13px; height: 13px; }
.btn[hidden] { display: none; }
.btn-primary {
background: var(--heat);

View File

@ -93,12 +93,12 @@ window.Shell = {
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><circle cx="12" cy="12" r="9"/><path d="M12 7v10M9 10h4a1.5 1.5 0 0 1 0 3h-3a1.5 1.5 0 0 0 0 3h4"/></svg>
余额 <strong>${balance}</strong>
</span>
<button class="queue-chip" onclick="Shell.toast('任务队列', '3 个进行中')">
<button class="queue-chip" onclick="Shell.toast('任务队列', '3 个进行中')" hidden>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M3 6h18M3 12h18M3 18h18"/></svg>
任务队列
<span class="count">3</span>
</button>
<button class="icon-btn" onclick="Shell.toast('通知中心', '12 条未读')">
<button class="icon-btn" onclick="location.href='messages.html'" title="消息中心">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M6 8a6 6 0 0 1 12 0c0 7 3 9 3 9H3s3-2 3-9M10 21a2 2 0 0 0 4 0"/></svg>
<span class="count-noti">12</span>
</button>

File diff suppressed because it is too large Load Diff

View File

@ -542,10 +542,436 @@ Disabled: 底 `--black-alpha-5` + 边 `--black-alpha-12` + 半透明。
**颜色:** 默认 `--black-alpha-56` / hover `--accent-black` / active `--heat` / disabled `--black-alpha-12` / 在主 CTA 内 `#fff`
### §4.13 AI 对话面板(编辑器型页面专用)
> **使用范围:** AI 图片创作 / 流水线 AI 助手 / 商品 AI 优化 / 镜头脚本编辑。3 个编辑器页(pipeline / model-photo / image-optimize)定义一致 — 已成事实组件,该抽到 restraint.css。
#### HTML 结构
```html
<div class="pane chat-pane">
<div class="pane-h">
<span class="ic-box"><svg>...</svg></span>
<span class="ti">AI 助手</span>
<span class="mono" style="margin-left:auto">// chat /v2</span>
</div>
<div class="chat-body">
<div class="msg ai">
<div class="bubble">你好,我来帮你优化这张图...</div>
<div class="mono ts">// 14:32</div>
</div>
<div class="msg user">
<div class="bubble">请把背景换成咖啡厅</div>
</div>
<div class="msg ai">
<div class="ai-thinking"><span class="dots"><span></span><span></span><span></span></span></div>
</div>
</div>
<div class="chat-input">
<div class="chat-input-card">
<textarea placeholder="@AI 帮我..."></textarea>
<div class="chat-input-actions">
<button class="btn btn-sm btn-ghost">附件</button>
<button class="btn btn-sm btn-primary">发送 →</button>
</div>
</div>
</div>
</div>
```
#### 关键 CSS
| 类 | 关键属性 |
| -- | -------- |
| `.chat-pane` | `flex column / 高度填充父容器` |
| `.chat-body` | `flex 1 / overflow-y auto / padding 16 18 / gap 14 / flex column / max-height 460(可选)` |
| `.chat-input` | `padding 14 18 18 / border-top 1px var(--border-faint)` |
| `.chat-input-card` | `bg --background-base / border 1px --border-faint / radius 8 / padding 12 14 10` · `:focus-within` 边变 `--accent-black` + ring `0 0 0 3px rgba(0,0,0,.04)` |
#### 消息气泡(`.msg.ai` / `.msg.user`)
| 类型 | 对齐 | 气泡背景 | 气泡边框 | 字色 |
| ---- | ---- | -------- | -------- | ---- |
| `.msg.ai` | 左对齐(默认) | `--surface` 白 | `--border-faint` | `--accent-black` |
| `.msg.user` | 右对齐(`align-items: flex-end`) | `--heat-12` 浅橙 | `--heat-20` | `--accent-black` |
所有气泡共享:`max-width 90% / padding 10 14 / font-size 13 / line-height 1.6 / radius 8`
时间戳放气泡下:mono 11 px / `--black-alpha-48` / 格式 `// 14:32`
#### 打字指示器(`.ai-thinking`)
3 个 6×6 圆点 · 1.2 s 交替闪烁:
```css
.ai-thinking .dots { display: inline-flex; gap: 3px; }
.ai-thinking .dots span {
width: 6px; height: 6px; background: var(--black-alpha-32);
border-radius: 50%;
animation: thinking 1.2s ease-in-out infinite;
}
.ai-thinking .dots span:nth-child(2) { animation-delay: .15s; }
.ai-thinking .dots span:nth-child(3) { animation-delay: .30s; }
@keyframes thinking {
0%, 80%, 100% { opacity: .25; }
40% { opacity: 1; }
}
```
---
### §4.14 商品 / 资产卡片体系(3 种形态)
> **核心原则:** 商品 + 资产 + 人物 + 场景共用**一套**卡片体系,按"使用场景"选形态。**不要为每种内容类型做新类。**
#### 三种形态总览
| 形态 | 类名(建议统一) | 缩图比 | 缩图宽度 | 使用场景 |
| ---- | -------------- | ------ | -------- | -------- |
| **网格主卡** | `.product-card` | 1.4:1(商品)· 1:1(资产/场景)· 3:4(人物) | 自适应宽 | 商品库 / 资产库 / 团队页主网格 |
| **行卡(列表)** | `.prod-row``.mp-prod-item` | 1:1 | 2836 px | 表单内已选行 / 侧栏列表 |
| **选择器卡** | `.pl-card` / `.opt-card` / `.model-card` | 1:1 或 3:4 | 100160 px | Modal 内多选 picker |
#### 4.14.1 网格主卡(`.product-card`)
```html
<div class="product-card">
<div class="product-thumb placeholder">9:16</div>
<div class="product-body">
<div class="product-name">夏季新款蕾丝连衣裙</div>
<div class="product-cat mono">// 女装 · 蓝色</div>
</div>
<div class="product-footer">
<span class="stat">素材 <b>124</b></span>
<span class="sep">·</span>
<span class="stat">视频 <b>36</b></span>
</div>
</div>
```
关键 CSS:
- 容器:`bg --surface / border 1px --border-faint / radius 8 / display flex column / overflow hidden / cursor pointer`
- hover:`bg --background-lighter / border --black-alpha-48`
- 缩图:`aspect-ratio` 按内容类型选 — 商品 `1.4/1` · 资产 `1/1` · 人物 `3/4`
- body:`padding 14 14 12 / flex 1`
- name:`14 / 600 / --accent-black / 单行省略`
- cat(分类):mono 11 / `--black-alpha-48`
- footer:`grid 1fr auto 1fr / padding 10 12 / border-top 1px --border-faint / bg --background-base / font 11.5 / 字 --black-alpha-56`
**变体:**
- `.product-card.with-action` — footer 右侧加一个小按钮(用于流水线 stage 2 资产库的"AI 生成"入口)
- `.product-card` 默认支持 selectable 模式:加 `.card-check` 22×22 圆形 checkbox(左上 hover 显示)+ 选中态 border 加深
#### 4.14.2 行卡(`.prod-row` / `.mp-prod-item`)
```html
<div class="prod-row">
<div class="thumb placeholder">9:16</div>
<div class="nm">夏季新款蕾丝连衣裙</div>
<button class="row-swap"><svg>swap</svg></button>
<button class="row-x"><svg>×</svg></button>
</div>
```
紧凑行卡:`flex align-center / gap 10 / padding 8 10 / bg --background-lighter / border 1px --border-faint / radius 6 / 28-36 px 缩图 + 单行文本`
**用法:**
- 表单里"已选商品"列表(可删可换)
- 侧栏商品/演员列表(`.mp-prod-item` · 36×36 缩图)
- 流水线 stage 选中态
#### 4.14.3 选择器卡(`.pl-card`)
```html
<div class="pl-card selected">
<div class="pl-thumb placeholder">3:4</div>
<div class="pl-name">夏季新款蕾丝连衣裙</div>
<div class="pl-check"><svg></svg></div>
</div>
```
Modal 内多选 picker:`bg --background-lighter / padding 10 / display flex column / gap 6 / cursor pointer`
3 种状态:
| 状态 | 视觉 |
| ---- | ---- |
| 默认 | `bg --background-lighter / border 1px --border-faint` |
| hover | `bg --surface`(变白) |
| **selected ★** | `border var(--heat) / bg var(--heat-12)` + 右上角橙色勾 SVG |
---
### §4.15 人物 / 演员 / 模特卡片(`.model-card`)
> **本质:** 选择器卡的"人物 3:4 竖图"特例。**复用 `.pl-card` 的选中态逻辑**,只换缩图比例。
```html
<div class="model-card selected">
<div class="m-thumb placeholder">3:4</div>
<div class="m-name">林夏 · 25 岁</div>
<div class="m-meta mono">// 165cm · 都市风</div>
</div>
```
| 元素 | 样式 |
| ---- | ---- |
| 缩图 | `aspect-ratio: 3/4`(人物竖图标准) |
| name | 12.5 / 500 / `--accent-black` |
| meta | mono 11 / `--black-alpha-48` |
| 选中态 | 同 `.pl-card`(橙边 + 橙底 + 右上勾) |
**与 `.pl-card` 的关系:** 同一基类,仅缩图比例不同 — 商品 1:1 / 人物 3:4。
**建议实现:** `.model-card` 继承 `.pl-card` 基类 + 覆盖 `.pl-thumb``aspect-ratio`
---
### §4.16 场景卡片(复用 `.asset-card-2`)
> 场景卡 = 资产卡的"1:1 通用"形态。**不要做新类**,只语义命名。
```html
<div class="asset-card-2 scene-card">
<div class="thumb-2 placeholder">1:1</div>
<div class="body-2">
<div class="ti">咖啡厅 · 暖光</div>
<div class="sub mono">// indoor · warm</div>
</div>
</div>
```
`.asset-card-2`(通用资产卡基础):
- `bg --surface / border 1px --border-faint / radius 8 / overflow hidden / display flex column`
- hover:`border --heat-40 / box-shadow 0 1 3 rgba(0,0,0,.04)`
- 缩图:`aspect-ratio 1` 正方形 · 通常宽 240 px(flex shrink 0)
- body:`padding 12 14`
- ti:14 / 500 / `--accent-black` · sub:mono 11 / `--black-alpha-48`
**派生类只做语义命名:** `.scene-card` / `.person-card` / `.background-card` — 样式继承 `.asset-card-2`,**不要重新写规则**。
---
### §4.17 其他高频复用组件
以下组件已在 2-5 个页面重复定义,需要尽快抽到 restraint.css。规范在此先记录。
#### 4.17.1 流程步骤条(`.stepper` / `.stage-step`)
```html
<div class="stepper">
<div class="stage-step done"><span class="num">1</span><span class="lbl">商品</span></div>
<div class="stage-step active"><span class="num">2</span><span class="lbl">故事板</span></div>
<div class="stage-step"><span class="num">3</span><span class="lbl">镜头</span></div>
<div class="stage-step"><span class="num">4</span><span class="lbl">生成</span></div>
<div class="stage-step"><span class="num">5</span><span class="lbl">投放</span></div>
</div>
```
| 元素 | 样式 |
| ---- | ---- |
| 容器 | `flex align-center / padding 14 18 / bg --surface / border 1px --border-faint / radius 8` |
| `.num` 编号 | 26×26 / mono 12 / 600 / radius 4 / 默认 `bg --surface + border 1px --border-faint + 字 --black-alpha-48` |
| `.done .num` | `bg --accent-black + 字 --accent-white`(已完成 · 黑) |
| **`.active .num`** | `bg --heat + 字 --accent-white`(进行中 · 橙) |
| 步间连接线 | 1 px / `--border-faint` / 高 1 px / 长度自适应 |
**与 `.prog` 区别:** `.prog` 是 5 段进度条徽标(横放在卡片角落),`.stepper` 是大尺寸主流程步骤条(放在 page-head 之下)。
#### 4.17.2 分页(`.pagination`)
```html
<div class="pagination">
<span class="total"><b>12</b></span>
<button class="page-size">12 条/页</button>
<span class="pages">
<button>1</button>
<button class="active">2</button>
<button>3</button>
</span>
<span class="jump">跳至 <input type="number"></span>
</div>
```
| 元素 | 样式 |
| ---- | ---- |
| 容器 | sticky bottom / `flex gap 16 / padding 14 0 / border-top 1px --border-faint / bg --background-base / font 12.5 / 字 --black-alpha-56` |
| 页码按钮 | 30×30 / radius 4 · 4 px 不是 8(密集场景小圆角不破节奏) |
| 默认页 | `bg --surface / border 1px --border-faint / 字 --black-alpha-72` |
| **激活页 ★** | `bg --heat / 字 --accent-white / 600 字重` |
#### 4.17.3 视图切换(`.view-toggle`)
```html
<div class="view-toggle">
<button class="active"><svg>grid</svg></button>
<button><svg>list</svg></button>
</div>
```
| 元素 | 样式 |
| ---- | ---- |
| 容器 | `inline-flex / bg --surface / border 1px --border-faint / radius 4 / padding 2`(外壳 4,因子按钮 4) |
| 按钮 | 30×28 / radius 4 / transparent / 字 `--black-alpha-48` |
| hover | `bg --black-alpha-4 / 字 --accent-black` |
| **激活 ★** | `bg --accent-black / 字 --accent-white`(**注意:不是橙**) |
> **为何用黑不用橙:** 视图切换是"显示模式开关",不是 CTA。黑表达"激活态"又不抢主橙的舞台。**同理:任何"模式切换"按钮组都用黑激活,不用橙。**
#### 4.17.4 操作型胶囊(`.pill-tip`)
```html
<button class="pill-tip">→ 去商品库</button>
```
- 高 28 / padding 0 14 / radius 999 / font 12 / weight 500
- 默认:`bg --heat-12 / border 1px --heat-20 / 字 --heat`
- hover:`bg --heat / 字 --accent-white / box-shadow var(--shadow-cta)`
**与 `.pill.info` 区别:** `.pill.info` 是状态徽标(静态展示),`.pill-tip` 是可点击操作(动态橙底反馈 · hover 升级到主 CTA 视觉)。
#### 4.17.5 卖点 / 编辑式列表(`.bullet-list`)
```html
<ul class="bullet-list">
<li class="bl-item">
<span class="num">1</span>
<span class="bl-text">100% 棉麻 · 透气吸汗</span>
<button class="bl-x">×</button>
</li>
<li class="bl-item">
<span class="num">2</span>
<span class="bl-text">独家设计 · 限量 200 件</span>
<button class="bl-x">×</button>
</li>
<li class="bl-add">
<span class="num">+</span>
<input class="bl-input" placeholder="添加卖点(Enter 确认)">
</li>
</ul>
```
| 元素 | 样式 |
| ---- | ---- |
| 行 `.bl-item` | `flex gap 10 / padding 8 12 / bg --background-lighter / border 1px --border-faint / radius 8 / mb 6` |
| 序号 `.num` | 22×22 / mono 11 / 700 / 字 `--heat` / `bg --surface / border 1px --border-faint / radius 4` |
| `.bl-text` | 13.5 / `--accent-black` / `flex 1` |
| `.bl-x` 删除 | 22×22 / 圆 · hover 时显示(默认 `opacity 0`)· hover 变红 |
| **`.bl-add` 添加行 ★** | 同 `.bl-item``border-style: dashed / bg transparent` |
| `.bl-add .num` | `bg transparent / border 1px dashed --heat-40 / 字 --heat`(显示 `+`) |
| `.bl-input` | `height 24 / border 0 / bg transparent / outline none / 字 13` |
---
### §4.18 AI 生成结果卡(`.gen-card`)
> **来源:** AI 图片创作 / 视频创作 / 任何"输入提示词 → 多候选输出"场景。
> **三个可变接口:** ① 图片比例 ② 悬浮按钮的功能 ③ 底部按钮的功能。**其他全部复用。**
#### 组件骨架
```html
<div class="gen-card">
<!-- ① Prompt 提示词行 -->
<div class="gen-prompt">
<svg class="quote"><!-- 16px " 引号 --></svg>
<span class="text">一只穿着宇航服的橘猫,漂浮在霓虹色星云中,赛博朋克风</span>
</div>
<!-- ② Meta chip 行(竖线分隔) -->
<div class="gen-meta">
<span class="m-item">流·Studio v2</span>
<span class="m-sep">|</span>
<span class="m-item">1:1</span>
<span class="m-sep">|</span>
<span class="m-item">1K · 2 天前</span>
</div>
<!-- ③ 图片网格(N 张候选 · 比例由 --ratio 控制) -->
<div class="gen-images" style="--cols:4; --ratio: 1/1">
<div class="gen-image">
<div class="placeholder"><span class="ph-frame">1:1 · #1</span></div>
<!-- hover 浮层 · 右上按钮组 -->
<div class="gen-image-actions">
<button class="gen-img-btn" data-act="regen"><svg><!----></svg></button>
<button class="gen-img-btn" data-act="dl"><svg><!----></svg></button>
<button class="gen-img-btn" data-act="more"><svg><!-- ··· --></svg></button>
</div>
<!-- 点击反馈 · 中央 toast(默认 opacity 0) -->
<div class="gen-image-feedback">
<svg><!----></svg>
<span>已添加到剪贴板</span>
</div>
</div>
<!-- × N -->
</div>
<!-- ④ 底部操作按钮行 -->
<div class="gen-card-actions">
<button class="btn btn-sm"><svg></svg>重新编辑</button>
<button class="btn btn-sm"><svg></svg>再次生成</button>
<button class="btn btn-sm btn-ghost"><svg></svg></button>
</div>
</div>
```
#### 关键 CSS
| 区域 | 样式 |
| ---- | ---- |
| 容器 `.gen-card` | `bg --surface / border 1px --border-faint / radius 8 / padding 16 / display flex column / gap 14` |
| 选中态 `.gen-card.selected` | `border-color --heat`(可选 modifier) |
| Prompt `.gen-prompt` | `flex align-start / gap 10 / padding 0 4` |
| Prompt 引号 `svg.quote` | 16×16 / `--black-alpha-32` / margin-top 2 |
| Prompt 文本 `.text` | 14 / 500 / `--accent-black` |
| Meta 行 `.gen-meta` | `flex align-center / gap 8 / padding 0 4 / font 11.5 mono / 字 --black-alpha-48 / letter-spacing .04em` |
| Meta 分隔 `.m-sep` | `--black-alpha-24`(直接用 `\|` 字符) |
| 图片网格 `.gen-images` | `display grid / grid-template-columns repeat(var(--cols), 1fr) / gap 10` |
| 单张图 `.gen-image` | `position relative / aspect-ratio var(--ratio) / radius 8 / overflow hidden / cursor pointer` |
| 浮层按钮组 `.gen-image-actions` | `absolute / top 8 right 8 / flex gap 2 / bg --surface / border 1px --border-faint / radius 8 / padding 2 / opacity 0 / z-index 2 / shadow 0 2 8 rgba(0,0,0,.08)` · hover `.gen-image``opacity 1` |
| 浮层按钮 `.gen-img-btn` | `28×28 / radius 6 / bg transparent / 字 --black-alpha-56 / border 0 / cursor pointer` · hover `bg --black-alpha-4 / 字 --accent-black` |
| 中央反馈 `.gen-image-feedback` | `absolute / inset 0 / flex column center / gap 8 / bg rgba(38,38,38,.88) / 字 --accent-white / radius 8 / opacity 0 / transition opacity .2s / pointer-events none` |
| 反馈触发态 `.gen-image.show-feedback .gen-image-feedback` | `opacity 1`(1.5 s 后 JS 移除) |
| 反馈 icon | 20×20 / 白 SVG ✓ |
| 反馈文字 | 12.5 / 500 / 白 |
| 底部按钮行 `.gen-card-actions` | `flex gap 8 / padding-top 4` |
#### 三个可变接口
| 维度 | 怎么变 |
| ---- | ------ |
| **① 图片比例** | 改 `style="--ratio: 1/1"``9/16` `16/9` `4/5` `3/4` 等 · 同时可改 `--cols` 控制列数(4 张方图 4 列 / 2 张竖图 2 列) |
| **② 悬浮按钮功能** | 改 `<button data-act="X">` 的 act 名 + icon SVG · 数量 14 个(超过 4 个合并到 ··· 弹出菜单) |
| **③ 底部按钮功能** | `.gen-card-actions` 内放 `.btn.btn-sm` · 通常 1-2 个二级 + 1 个三级 ghost ··· · **不放主橙**(避免抢 prompt 视觉) |
#### 反馈 toast 触发 JS
```js
img.addEventListener('click', () => {
img.classList.add('show-feedback');
setTimeout(() => img.classList.remove('show-feedback'), 1500);
});
```
#### 适用场景
- AI 图片创作(2-8 张候选图 · 1:1 或 9:16)
- AI 视频创作(1-4 段候选视频 · 16:9 或 9:16 · 缩图用 video poster)
- 模板生成结果(批量预览选择)
- 任何"prompt 输入 → 多候选输出"流程
#### Don't
- ❌ **给每个图片单独写卡样式** — 用 `.gen-image` 通用
- ❌ **悬浮按钮组放图片底部** — 永远右上(避免遮挡图片主体)
- ❌ **用全局 toast 通知"已复制"** — 用本组件内的 `.gen-image-feedback`(就地反馈,用户不必转头看屏幕角落)
- ❌ **把 prompt 文字截断省略** — 完整显示(它就是用户的输入 · 要看清)
- ❌ **底部按钮做成主橙 primary** — 抢了 prompt 的视觉重心,用二级 `.btn` 即可
---
## §5 · Mono 装饰元素(品牌签名 · 不能丢)
> 方括号标签 / 双斜杠注释 / 中点连接 —— 这些是流·Studio 独有的"调试视图感",Firecrawl 没有,绝对保留。
| 用法 | 示例 |
| ---- | ---- |
| 方括号标签 | `[ 200 OK ]` `[ /v2 ]` `[ .MP4 · 9:16 ]` `[ STUDIO ]` `[ ALL · 12 ] →` |

View File

@ -678,16 +678,16 @@
<aside class="io-side">
<div class="io-side-h">
<button class="back-pill" type="button" onclick="history.length > 1 ? history.back() : location.href='asset-factory.html'" title="返回">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M15 18l-6-6 6-6"/></svg>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M15 18l-6-6 6-6"/></svg>
<span>返回</span>
</button>
<button class="fold" type="button" title="折叠侧栏">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2"/><path d="M9 3v18"/></svg>
<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="18" height="18" rx="2"/><path d="M9 3v18"/></svg>
</button>
</div>
<button class="io-new-conv" type="button" id="io-new-conv">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><path d="M12 20h9"/><path d="M16.5 3.5a2.121 2.121 0 113 3L7 19l-4 1 1-4z"/></svg>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M12 20h9"/><path d="M16.5 3.5a2.121 2.121 0 113 3L7 19l-4 1 1-4z"/></svg>
新对话
</button>
@ -695,7 +695,7 @@
<div class="io-conv-list" style="flex: 0 0 auto;">
<div class="io-conv-item default active" data-default>
<div class="thumb">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2"/><path d="M3 16l5-5 4 4 3-3 6 6"/></svg>
<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="18" height="18" rx="2"/><path d="M3 16l5-5 4 4 3-3 6 6"/></svg>
</div>
<span class="nm">默认创作</span>
</div>
@ -713,7 +713,7 @@
<div class="io-toolbar">
<span class="spacer"></span>
<button class="search-btn" type="button" title="搜索">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7"><circle cx="11" cy="11" r="7"/><path d="m21 21-4.3-4.3"/></svg>
<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>
</button>
<button class="tb-chip" type="button">
时间
@ -737,7 +737,7 @@
<button class="io-jump-bottom" type="button" id="io-jump-bottom">
回到底部
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><path d="M4 6l4 4 4-4"/></svg>
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M4 6l4 4 4-4"/></svg>
</button>
<!-- 底部输入栏 -->
@ -747,7 +747,7 @@
<div class="io-input-top">
<div class="io-input-refs" id="io-input-refs"></div>
<button class="add-btn" type="button" id="io-add-btn" title="上传参考图">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><path d="M12 5v14M5 12h14"/></svg>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M12 5v14M5 12h14"/></svg>
</button>
<input type="file" id="io-file-input" accept="image/*" multiple>
</div>
@ -803,7 +803,7 @@
<div class="io-dup-modal">
<div class="dh">
<div class="ic">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="9"/><path d="M12 8v4M12 16h.01"/></svg>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="9"/><path d="M12 8v4M12 16h.01"/></svg>
</div>
<div class="ti">
<strong id="io-dup-title">图片已在资产库</strong>
@ -1021,11 +1021,11 @@ Shell.render({
convList.innerHTML = state.convs.map(c => `
<div class="io-conv-item${state.activeConvId === c.id ? ' active' : ''}" data-id="${c.id}">
<div class="thumb"${c.thumb ? ` style="background-image:url(${c.thumb})"` : ''}>
${c.thumb ? '' : '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6"><rect x="3" y="3" width="18" height="18" rx="2"/><circle cx="8.5" cy="8.5" r="1.5"/><path d="M21 15l-5-5-9 9"/></svg>'}
${c.thumb ? '' : '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="3" y="3" width="18" height="18" rx="2"/><circle cx="8.5" cy="8.5" r="1.5"/><path d="M21 15l-5-5-9 9"/></svg>'}
</div>
<span class="nm">${esc(c.title)}</span>
<button class="del" type="button" data-del="${c.id}" 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>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" 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>
`).join('');
@ -1078,7 +1078,7 @@ Shell.render({
streamInner.innerHTML = `
<div class="io-empty">
<div class="ic">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.4" 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"/><path d="M19 16l.85 2.3L22 19l-2.15.7L19 22l-.85-2.3L16 19l2.15-.7L19 16z"/></svg>
<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"/><path d="M19 16l.85 2.3L22 19l-2.15.7L19 22l-.85-2.3L16 19l2.15-.7L19 16z"/></svg>
</div>
<div class="badge">// IMAGE STUDIO</div>
<h2>开始你的创作</h2>
@ -1113,18 +1113,18 @@ Shell.render({
${r.status === 'ok' ? `
<div class="cell-ops">
<button type="button" data-act="cell-rerun" title="再次生成">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12a9 9 0 11-3-6.7L21 8M21 3v5h-5"/></svg>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12a9 9 0 11-3-6.7L21 8M21 3v5h-5"/></svg>
</button>
<button type="button" data-act="dl" title="下载">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4M7 10l5 5 5-5M12 15V3"/></svg>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4M7 10l5 5 5-5M12 15V3"/></svg>
</button>
<div class="cell-more-wrap">
<button type="button" data-act="more" title="更多">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="5" cy="12" r="1.2"/><circle cx="12" cy="12" r="1.2"/><circle cx="19" cy="12" r="1.2"/></svg>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><circle cx="5" cy="12" r="1.2"/><circle cx="12" cy="12" r="1.2"/><circle cx="19" cy="12" r="1.2"/></svg>
</button>
<div class="cell-more-menu">
<button type="button" data-act="save-lib"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><path d="M19 21l-7-5-7 5V5a2 2 0 012-2h10a2 2 0 012 2z"/></svg>加入资产库</button>
<button type="button" class="danger" data-act="cell-del"><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>
<button type="button" data-act="save-lib"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M19 21l-7-5-7 5V5a2 2 0 012-2h10a2 2 0 012 2z"/></svg>加入资产库</button>
<button type="button" class="danger" data-act="cell-del"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" 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>
</div>
</div>
@ -1156,24 +1156,24 @@ Shell.render({
<div class="io-msg-grid">${cellsHTML}</div>
<div class="io-msg-ops">
<button type="button" data-act="edit" data-id="${m.id}">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><path d="M12 20h9"/><path d="M16.5 3.5a2.121 2.121 0 113 3L7 19l-4 1 1-4z"/></svg>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M12 20h9"/><path d="M16.5 3.5a2.121 2.121 0 113 3L7 19l-4 1 1-4z"/></svg>
重新编辑
</button>
<button type="button" data-act="rerun" data-id="${m.id}">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12a9 9 0 11-3-6.7L21 8M21 3v5h-5"/></svg>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12a9 9 0 11-3-6.7L21 8M21 3v5h-5"/></svg>
再次生成
</button>
<div class="msg-more-wrap">
<button type="button" class="icon" data-act="msg-more" data-id="${m.id}" title="更多">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7"><circle cx="5" cy="12" r="1.2"/><circle cx="12" cy="12" r="1.2"/><circle cx="19" cy="12" r="1.2"/></svg>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><circle cx="5" cy="12" r="1.2"/><circle cx="12" cy="12" r="1.2"/><circle cx="19" cy="12" r="1.2"/></svg>
</button>
<div class="msg-more-menu" role="menu">
<button type="button" data-act="msg-save-all" data-id="${m.id}">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><path d="M19 21H5a2 2 0 01-2-2V5a2 2 0 012-2h11l5 5v11a2 2 0 01-2 2z"/><polyline points="17 21 17 13 7 13 7 21"/><polyline points="7 3 7 8 15 8"/></svg>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M19 21H5a2 2 0 01-2-2V5a2 2 0 012-2h11l5 5v11a2 2 0 01-2 2z"/><polyline points="17 21 17 13 7 13 7 21"/><polyline points="7 3 7 8 15 8"/></svg>
全部加入资产库
</button>
<button type="button" class="danger" data-act="msg-del" data-id="${m.id}">
<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>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" 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>
@ -1500,7 +1500,7 @@ Shell.render({
inputRefs.innerHTML = state.refImages.map(r => `
<div class="io-input-ref" data-id="${r.id}">
<img src="${r.dataUrl}" alt="">
<button class="x" type="button"><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>
<button class="x" type="button"><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>
</div>
`).join('');
inputRefs.querySelectorAll('.x').forEach(b => {

View File

@ -398,7 +398,7 @@
<div class="asset-grid" data-tab="people" id="grid-people">
<div class="asset-card" data-name="林夕" data-gender="女" data-age="青年" data-role="都市白领" data-source="AI 生成" data-used="4" data-added="20260513" onclick="Shell.toast('查看资产', '林夕')">
<span class="card-check" aria-hidden="true"><svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="3 8 7 12 13 4"/></svg></span>
<button class="card-del-btn" type="button" title="删除资产" onclick="event.stopPropagation();" data-action="delete-asset"><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>
<button class="card-del-btn" type="button" title="删除资产" onclick="event.stopPropagation();" data-action="delete-asset"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" 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 asset-thumb"><span class="ph-frame">林夕 · 都市白领</span></div>
<div class="asset-body">
<div class="asset-name">林夕</div>
@ -407,7 +407,7 @@
</div>
<div class="asset-card" data-name="阿楠" data-gender="女" data-age="青年" data-role="都市白领" data-source="AI 生成" data-used="2" data-added="20260507" onclick="Shell.toast('查看资产', '阿楠')">
<span class="card-check" aria-hidden="true"><svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="3 8 7 12 13 4"/></svg></span>
<button class="card-del-btn" type="button" title="删除资产" onclick="event.stopPropagation();" data-action="delete-asset"><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>
<button class="card-del-btn" type="button" title="删除资产" onclick="event.stopPropagation();" data-action="delete-asset"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" 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 asset-thumb"><span class="ph-frame">阿楠 · 同事女</span></div>
<div class="asset-body">
<div class="asset-name">阿楠</div>
@ -416,7 +416,7 @@
</div>
<div class="asset-card" data-name="小七" data-gender="女" data-age="青年" data-role="学生" data-source="AI 生成" data-used="3" data-added="20260512" onclick="Shell.toast('查看资产', '小七')">
<span class="card-check" aria-hidden="true"><svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="3 8 7 12 13 4"/></svg></span>
<button class="card-del-btn" type="button" title="删除资产" onclick="event.stopPropagation();" data-action="delete-asset"><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>
<button class="card-del-btn" type="button" title="删除资产" onclick="event.stopPropagation();" data-action="delete-asset"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" 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 asset-thumb"><span class="ph-frame">小七 · 学生女</span></div>
<div class="asset-body">
<div class="asset-name">小七</div>
@ -425,7 +425,7 @@
</div>
<div class="asset-card" data-name="阿杰" data-gender="男" data-age="青年" data-role="都市白领" data-source="AI 生成" data-used="2" data-added="20260428" onclick="Shell.toast('查看资产', '阿杰')">
<span class="card-check" aria-hidden="true"><svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="3 8 7 12 13 4"/></svg></span>
<button class="card-del-btn" type="button" title="删除资产" onclick="event.stopPropagation();" data-action="delete-asset"><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>
<button class="card-del-btn" type="button" title="删除资产" onclick="event.stopPropagation();" data-action="delete-asset"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" 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 asset-thumb"><span class="ph-frame">阿杰 · 通勤男</span></div>
<div class="asset-body">
<div class="asset-name">阿杰</div>
@ -434,14 +434,14 @@
</div>
<div class="asset-card" data-name="妈妈 · 王姐" data-gender="女" data-age="中年" data-role="居家" data-source="手动上传" data-triview="0" data-used="1" data-added="20260415" onclick="Shell.toast('查看资产', '王姐')">
<span class="card-check" aria-hidden="true"><svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="3 8 7 12 13 4"/></svg></span>
<button class="card-del-btn" type="button" title="删除资产" onclick="event.stopPropagation();" data-action="delete-asset"><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>
<button class="card-del-btn" type="button" title="删除资产" onclick="event.stopPropagation();" data-action="delete-asset"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" 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 asset-thumb">
<span class="tri-missing-badge" tabindex="0" role="button" aria-label="缺三视图,查看说明">
<span class="ico" aria-hidden="true"></span>
<span class="lbl-mono">缺三视图</span>
<span class="tri-missing-pop" role="tooltip">
<span class="pop-h">
<svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><path d="M12 9v4M12 17h.01"/><path d="M10.3 3.86 1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/></svg>
<svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M12 9v4M12 17h.01"/><path d="M10.3 3.86 1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/></svg>
MISSING TRI-VIEW
</span>
<span class="pop-body">手动上传的人物未生成 <b>正 / 侧 / 背</b> 三视图。直接进入图片或视频生成,人脸/服饰一致性可能下降。</span>
@ -457,7 +457,7 @@
</div>
<div class="asset-card" data-name="阿强" data-gender="男" data-age="青年" data-role="健身" data-source="AI 生成" data-used="2" data-added="20260508" onclick="Shell.toast('查看资产', '阿强')">
<span class="card-check" aria-hidden="true"><svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="3 8 7 12 13 4"/></svg></span>
<button class="card-del-btn" type="button" title="删除资产" onclick="event.stopPropagation();" data-action="delete-asset"><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>
<button class="card-del-btn" type="button" title="删除资产" onclick="event.stopPropagation();" data-action="delete-asset"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" 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 asset-thumb"><span class="ph-frame">阿强 · 健身男</span></div>
<div class="asset-body">
<div class="asset-name">阿强</div>
@ -466,7 +466,7 @@
</div>
<div class="asset-card" data-name="小苏" data-gender="女" data-age="青年" data-role="文艺" data-source="AI 生成" data-used="1" data-added="20260420" onclick="Shell.toast('查看资产', '小苏')">
<span class="card-check" aria-hidden="true"><svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="3 8 7 12 13 4"/></svg></span>
<button class="card-del-btn" type="button" title="删除资产" onclick="event.stopPropagation();" data-action="delete-asset"><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>
<button class="card-del-btn" type="button" title="删除资产" onclick="event.stopPropagation();" data-action="delete-asset"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" 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 asset-thumb"><span class="ph-frame">小苏 · 文艺女</span></div>
<div class="asset-body">
<div class="asset-name">小苏</div>
@ -475,7 +475,7 @@
</div>
<div class="asset-card" data-name="闺蜜组合" data-gender="女" data-age="青年" data-role="都市白领" data-source="AI 生成" data-used="1" data-added="20260511" onclick="Shell.toast('查看资产', '闺蜜组合')">
<span class="card-check" aria-hidden="true"><svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="3 8 7 12 13 4"/></svg></span>
<button class="card-del-btn" type="button" title="删除资产" onclick="event.stopPropagation();" data-action="delete-asset"><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>
<button class="card-del-btn" type="button" title="删除资产" onclick="event.stopPropagation();" data-action="delete-asset"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" 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 asset-thumb"><span class="ph-frame">闺蜜组合 · 双人</span></div>
<div class="asset-body">
<div class="asset-name">闺蜜组合</div>
@ -484,7 +484,7 @@
</div>
<div class="asset-card" data-name="豆豆" data-gender="女" data-age="幼年" data-role="居家" data-source="AI 生成" data-used="2" data-added="20260509" onclick="Shell.toast('查看资产', '豆豆')">
<span class="card-check" aria-hidden="true"><svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="3 8 7 12 13 4"/></svg></span>
<button class="card-del-btn" type="button" title="删除资产" onclick="event.stopPropagation();" data-action="delete-asset"><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>
<button class="card-del-btn" type="button" title="删除资产" onclick="event.stopPropagation();" data-action="delete-asset"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" 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 asset-thumb"><span class="ph-frame">豆豆 · 幼儿</span></div>
<div class="asset-body">
<div class="asset-name">豆豆</div>
@ -493,7 +493,7 @@
</div>
<div class="asset-card" data-name="小宇" data-gender="男" data-age="少年" data-role="学生" data-source="AI 生成" data-used="1" data-added="20260502" onclick="Shell.toast('查看资产', '小宇')">
<span class="card-check" aria-hidden="true"><svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="3 8 7 12 13 4"/></svg></span>
<button class="card-del-btn" type="button" title="删除资产" onclick="event.stopPropagation();" data-action="delete-asset"><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>
<button class="card-del-btn" type="button" title="删除资产" onclick="event.stopPropagation();" data-action="delete-asset"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" 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 asset-thumb"><span class="ph-frame">小宇 · 中学生</span></div>
<div class="asset-body">
<div class="asset-name">小宇</div>
@ -502,14 +502,14 @@
</div>
<div class="asset-card" data-name="李爷爷" data-gender="男" data-age="老年" data-role="居家" data-source="手动上传" data-triview="0" data-used="1" data-added="20260418" onclick="Shell.toast('查看资产', '李爷爷')">
<span class="card-check" aria-hidden="true"><svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="3 8 7 12 13 4"/></svg></span>
<button class="card-del-btn" type="button" title="删除资产" onclick="event.stopPropagation();" data-action="delete-asset"><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>
<button class="card-del-btn" type="button" title="删除资产" onclick="event.stopPropagation();" data-action="delete-asset"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" 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 asset-thumb">
<span class="tri-missing-badge" tabindex="0" role="button" aria-label="缺三视图,查看说明">
<span class="ico" aria-hidden="true"></span>
<span class="lbl-mono">缺三视图</span>
<span class="tri-missing-pop" role="tooltip">
<span class="pop-h">
<svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><path d="M12 9v4M12 17h.01"/><path d="M10.3 3.86 1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/></svg>
<svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M12 9v4M12 17h.01"/><path d="M10.3 3.86 1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/></svg>
MISSING TRI-VIEW
</span>
<span class="pop-body">手动上传的人物未生成 <b>正 / 侧 / 背</b> 三视图。直接进入图片或视频生成,人脸/服饰一致性可能下降。</span>
@ -529,73 +529,73 @@
<div class="asset-grid" data-tab="scenes" id="grid-scenes" hidden>
<div class="asset-card" data-name="卧室·暖光" data-scene-type="卧室" data-source="AI 生成" data-used="6" data-added="20260513" onclick="Shell.toast('查看资产', '卧室·暖光')">
<span class="card-check" aria-hidden="true"><svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="3 8 7 12 13 4"/></svg></span>
<button class="card-del-btn" type="button" title="删除资产" onclick="event.stopPropagation();" data-action="delete-asset"><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>
<button class="card-del-btn" type="button" title="删除资产" onclick="event.stopPropagation();" data-action="delete-asset"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" 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 asset-thumb"><span class="ph-frame">卧室 · 暖光</span></div>
<div class="asset-body"><div class="asset-name">卧室·暖光</div><div class="asset-meta">卧室 · AI 生成 · 用过 6 次</div></div>
</div>
<div class="asset-card" data-name="卧室·冷调" data-scene-type="卧室" data-source="AI 生成" data-used="3" data-added="20260507" onclick="Shell.toast('查看资产', '卧室·冷调')">
<span class="card-check" aria-hidden="true"><svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="3 8 7 12 13 4"/></svg></span>
<button class="card-del-btn" type="button" title="删除资产" onclick="event.stopPropagation();" data-action="delete-asset"><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>
<button class="card-del-btn" type="button" title="删除资产" onclick="event.stopPropagation();" data-action="delete-asset"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" 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 asset-thumb"><span class="ph-frame">卧室 · 冷调</span></div>
<div class="asset-body"><div class="asset-name">卧室·冷调</div><div class="asset-meta">卧室 · AI 生成 · 用过 3 次</div></div>
</div>
<div class="asset-card" data-name="浴室·梳妆台" data-scene-type="浴室" data-source="AI 生成" data-used="4" data-added="20260510" onclick="Shell.toast('查看资产', '浴室·梳妆台')">
<span class="card-check" aria-hidden="true"><svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="3 8 7 12 13 4"/></svg></span>
<button class="card-del-btn" type="button" title="删除资产" onclick="event.stopPropagation();" data-action="delete-asset"><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>
<button class="card-del-btn" type="button" title="删除资产" onclick="event.stopPropagation();" data-action="delete-asset"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" 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 asset-thumb"><span class="ph-frame">浴室 · 梳妆台</span></div>
<div class="asset-body"><div class="asset-name">浴室·梳妆台</div><div class="asset-meta">浴室 · AI 生成 · 用过 4 次</div></div>
</div>
<div class="asset-card" data-name="客厅·北欧" data-scene-type="客厅" data-source="AI 生成" data-used="5" data-added="20260512" onclick="Shell.toast('查看资产', '客厅·北欧')">
<span class="card-check" aria-hidden="true"><svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="3 8 7 12 13 4"/></svg></span>
<button class="card-del-btn" type="button" title="删除资产" onclick="event.stopPropagation();" data-action="delete-asset"><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>
<button class="card-del-btn" type="button" title="删除资产" onclick="event.stopPropagation();" data-action="delete-asset"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" 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 asset-thumb"><span class="ph-frame">客厅 · 北欧</span></div>
<div class="asset-body"><div class="asset-name">客厅·北欧</div><div class="asset-meta">客厅 · AI 生成 · 用过 5 次</div></div>
</div>
<div class="asset-card" data-name="客厅·中古" data-scene-type="客厅" data-source="手动上传" data-used="1" data-added="20260418" onclick="Shell.toast('查看资产', '客厅·中古')">
<span class="card-check" aria-hidden="true"><svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="3 8 7 12 13 4"/></svg></span>
<button class="card-del-btn" type="button" title="删除资产" onclick="event.stopPropagation();" data-action="delete-asset"><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>
<button class="card-del-btn" type="button" title="删除资产" onclick="event.stopPropagation();" data-action="delete-asset"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" 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 asset-thumb"><span class="ph-frame">客厅 · 中古</span></div>
<div class="asset-body"><div class="asset-name">客厅·中古</div><div class="asset-meta">客厅 · 手动上传 · 用过 1 次</div></div>
</div>
<div class="asset-card" data-name="厨房·中岛" data-scene-type="厨房" data-source="AI 生成" data-used="3" data-added="20260509" onclick="Shell.toast('查看资产', '厨房·中岛')">
<span class="card-check" aria-hidden="true"><svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="3 8 7 12 13 4"/></svg></span>
<button class="card-del-btn" type="button" title="删除资产" onclick="event.stopPropagation();" data-action="delete-asset"><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>
<button class="card-del-btn" type="button" title="删除资产" onclick="event.stopPropagation();" data-action="delete-asset"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" 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 asset-thumb"><span class="ph-frame">厨房 · 中岛</span></div>
<div class="asset-body"><div class="asset-name">厨房·中岛</div><div class="asset-meta">厨房 · AI 生成 · 用过 3 次</div></div>
</div>
<div class="asset-card" data-name="办公室·开放" data-scene-type="办公室" data-source="AI 生成" data-used="2" data-added="20260506" onclick="Shell.toast('查看资产', '办公室·开放')">
<span class="card-check" aria-hidden="true"><svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="3 8 7 12 13 4"/></svg></span>
<button class="card-del-btn" type="button" title="删除资产" onclick="event.stopPropagation();" data-action="delete-asset"><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>
<button class="card-del-btn" type="button" title="删除资产" onclick="event.stopPropagation();" data-action="delete-asset"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" 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 asset-thumb"><span class="ph-frame">办公室 · 开放</span></div>
<div class="asset-body"><div class="asset-name">办公室·开放</div><div class="asset-meta">办公室 · AI 生成 · 用过 2 次</div></div>
</div>
<div class="asset-card" data-name="办公室·会议室" data-scene-type="办公室" data-source="AI 生成" data-used="1" data-added="20260425" onclick="Shell.toast('查看资产', '办公室·会议室')">
<span class="card-check" aria-hidden="true"><svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="3 8 7 12 13 4"/></svg></span>
<button class="card-del-btn" type="button" title="删除资产" onclick="event.stopPropagation();" data-action="delete-asset"><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>
<button class="card-del-btn" type="button" title="删除资产" onclick="event.stopPropagation();" data-action="delete-asset"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" 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 asset-thumb"><span class="ph-frame">办公室 · 会议室</span></div>
<div class="asset-body"><div class="asset-name">办公室·会议室</div><div class="asset-meta">办公室 · AI 生成 · 用过 1 次</div></div>
</div>
<div class="asset-card" data-name="咖啡店·窗边" data-scene-type="咖啡店" data-source="AI 生成" data-used="4" data-added="20260511" onclick="Shell.toast('查看资产', '咖啡店·窗边')">
<span class="card-check" aria-hidden="true"><svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="3 8 7 12 13 4"/></svg></span>
<button class="card-del-btn" type="button" title="删除资产" onclick="event.stopPropagation();" data-action="delete-asset"><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>
<button class="card-del-btn" type="button" title="删除资产" onclick="event.stopPropagation();" data-action="delete-asset"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" 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 asset-thumb"><span class="ph-frame">咖啡店 · 窗边</span></div>
<div class="asset-body"><div class="asset-name">咖啡店·窗边</div><div class="asset-meta">咖啡店 · AI 生成 · 用过 4 次</div></div>
</div>
<div class="asset-card" data-name="街景·夜" data-scene-type="街景" data-source="AI 生成" data-used="2" data-added="20260430" onclick="Shell.toast('查看资产', '街景·夜')">
<span class="card-check" aria-hidden="true"><svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="3 8 7 12 13 4"/></svg></span>
<button class="card-del-btn" type="button" title="删除资产" onclick="event.stopPropagation();" data-action="delete-asset"><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>
<button class="card-del-btn" type="button" title="删除资产" onclick="event.stopPropagation();" data-action="delete-asset"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" 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 asset-thumb"><span class="ph-frame">街景 · 夜</span></div>
<div class="asset-body"><div class="asset-name">街景·夜</div><div class="asset-meta">街景 · AI 生成 · 用过 2 次</div></div>
</div>
<div class="asset-card" data-name="健身房·器械" data-scene-type="健身房" data-source="AI 生成" data-used="3" data-added="20260508" onclick="Shell.toast('查看资产', '健身房·器械')">
<span class="card-check" aria-hidden="true"><svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="3 8 7 12 13 4"/></svg></span>
<button class="card-del-btn" type="button" title="删除资产" onclick="event.stopPropagation();" data-action="delete-asset"><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>
<button class="card-del-btn" type="button" title="删除资产" onclick="event.stopPropagation();" data-action="delete-asset"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" 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 asset-thumb"><span class="ph-frame">健身房 · 器械</span></div>
<div class="asset-body"><div class="asset-name">健身房·器械</div><div class="asset-meta">健身房 · AI 生成 · 用过 3 次</div></div>
</div>
<div class="asset-card" data-name="街景·日" data-scene-type="街景" data-source="手动上传" data-used="1" data-added="20260422" onclick="Shell.toast('查看资产', '街景·日')">
<span class="card-check" aria-hidden="true"><svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="3 8 7 12 13 4"/></svg></span>
<button class="card-del-btn" type="button" title="删除资产" onclick="event.stopPropagation();" data-action="delete-asset"><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>
<button class="card-del-btn" type="button" title="删除资产" onclick="event.stopPropagation();" data-action="delete-asset"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" 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 asset-thumb"><span class="ph-frame">街景 · 日</span></div>
<div class="asset-body"><div class="asset-name">街景·日</div><div class="asset-meta">街景 · 手动上传 · 用过 1 次</div></div>
</div>
@ -605,73 +605,73 @@
<div class="asset-grid" data-tab="products" id="grid-products" hidden>
<div class="asset-card" data-name="补水面膜 · 主图" data-product="透真补水面膜" data-source="商品库引用" data-used="5" data-added="20260513" onclick="Shell.toast('查看资产', '补水面膜 主图')">
<span class="card-check" aria-hidden="true"><svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="3 8 7 12 13 4"/></svg></span>
<button class="card-del-btn" type="button" title="删除资产" onclick="event.stopPropagation();" data-action="delete-asset"><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>
<button class="card-del-btn" type="button" title="删除资产" onclick="event.stopPropagation();" data-action="delete-asset"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" 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 asset-thumb"><span class="ph-frame">补水面膜 · 主图</span></div>
<div class="asset-body"><div class="asset-name">补水面膜 · 主图</div><div class="asset-meta">透真补水面膜 · 库引用 · 用过 5 次</div></div>
</div>
<div class="asset-card" data-name="补水面膜 · AI 优化" data-product="透真补水面膜" data-source="AI 优化" data-used="3" data-added="20260513" onclick="Shell.toast('查看资产', '补水面膜 AI 优化')">
<span class="card-check" aria-hidden="true"><svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="3 8 7 12 13 4"/></svg></span>
<button class="card-del-btn" type="button" title="删除资产" onclick="event.stopPropagation();" data-action="delete-asset"><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>
<button class="card-del-btn" type="button" title="删除资产" onclick="event.stopPropagation();" data-action="delete-asset"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" 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 asset-thumb"><span class="ph-frame">补水面膜 · AI 优化</span></div>
<div class="asset-body"><div class="asset-name">补水面膜 · AI 优化</div><div class="asset-meta">透真补水面膜 · AI 优化 · 用过 3 次</div></div>
</div>
<div class="asset-card" data-name="蓝牙耳机 · 主图" data-product="南卡 Lite Pro" data-source="商品库引用" data-used="4" data-added="20260507" onclick="Shell.toast('查看资产', '蓝牙耳机 主图')">
<span class="card-check" aria-hidden="true"><svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="3 8 7 12 13 4"/></svg></span>
<button class="card-del-btn" type="button" title="删除资产" onclick="event.stopPropagation();" data-action="delete-asset"><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>
<button class="card-del-btn" type="button" title="删除资产" onclick="event.stopPropagation();" data-action="delete-asset"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" 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 asset-thumb"><span class="ph-frame">蓝牙耳机 · 主图</span></div>
<div class="asset-body"><div class="asset-name">蓝牙耳机 · 主图</div><div class="asset-meta">南卡 Lite Pro · 库引用 · 用过 4 次</div></div>
</div>
<div class="asset-card" data-name="蓝牙耳机 · 场景图" data-product="南卡 Lite Pro" data-source="手动上传" data-used="1" data-added="20260507" onclick="Shell.toast('查看资产', '蓝牙耳机 场景')">
<span class="card-check" aria-hidden="true"><svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="3 8 7 12 13 4"/></svg></span>
<button class="card-del-btn" type="button" title="删除资产" onclick="event.stopPropagation();" data-action="delete-asset"><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>
<button class="card-del-btn" type="button" title="删除资产" onclick="event.stopPropagation();" data-action="delete-asset"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" 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 asset-thumb"><span class="ph-frame">蓝牙耳机 · 场景</span></div>
<div class="asset-body"><div class="asset-name">蓝牙耳机 · 场景图</div><div class="asset-meta">南卡 Lite Pro · 手动上传 · 用过 1 次</div></div>
</div>
<div class="asset-card" data-name="速食面 · 主图" data-product="滋啦速食" data-source="商品库引用" data-used="3" data-added="20260512" onclick="Shell.toast('查看资产', '速食面 主图')">
<span class="card-check" aria-hidden="true"><svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="3 8 7 12 13 4"/></svg></span>
<button class="card-del-btn" type="button" title="删除资产" onclick="event.stopPropagation();" data-action="delete-asset"><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>
<button class="card-del-btn" type="button" title="删除资产" onclick="event.stopPropagation();" data-action="delete-asset"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" 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 asset-thumb"><span class="ph-frame">速食面 · 主图</span></div>
<div class="asset-body"><div class="asset-name">速食面 · 主图</div><div class="asset-meta">滋啦速食 · 库引用 · 用过 3 次</div></div>
</div>
<div class="asset-card" data-name="速食面 · 加汤" data-product="滋啦速食" data-source="AI 优化" data-used="2" data-added="20260512" onclick="Shell.toast('查看资产', '速食面 加汤')">
<span class="card-check" aria-hidden="true"><svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="3 8 7 12 13 4"/></svg></span>
<button class="card-del-btn" type="button" title="删除资产" onclick="event.stopPropagation();" data-action="delete-asset"><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>
<button class="card-del-btn" type="button" title="删除资产" onclick="event.stopPropagation();" data-action="delete-asset"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" 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 asset-thumb"><span class="ph-frame">速食面 · 加汤</span></div>
<div class="asset-body"><div class="asset-name">速食面 · 加汤</div><div class="asset-meta">滋啦速食 · AI 优化 · 用过 2 次</div></div>
</div>
<div class="asset-card" data-name="防晒霜 · 主图" data-product="透真防晒霜" data-source="商品库引用" data-used="4" data-added="20260510" onclick="Shell.toast('查看资产', '防晒霜 主图')">
<span class="card-check" aria-hidden="true"><svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="3 8 7 12 13 4"/></svg></span>
<button class="card-del-btn" type="button" title="删除资产" onclick="event.stopPropagation();" data-action="delete-asset"><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>
<button class="card-del-btn" type="button" title="删除资产" onclick="event.stopPropagation();" data-action="delete-asset"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" 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 asset-thumb"><span class="ph-frame">防晒霜 · 主图</span></div>
<div class="asset-body"><div class="asset-name">防晒霜 · 主图</div><div class="asset-meta">透真防晒霜 · 库引用 · 用过 4 次</div></div>
</div>
<div class="asset-card" data-name="防晒霜 · AI 优化" data-product="透真防晒霜" data-source="AI 优化" data-used="3" data-added="20260510" onclick="Shell.toast('查看资产', '防晒霜 优化')">
<span class="card-check" aria-hidden="true"><svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="3 8 7 12 13 4"/></svg></span>
<button class="card-del-btn" type="button" title="删除资产" onclick="event.stopPropagation();" data-action="delete-asset"><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>
<button class="card-del-btn" type="button" title="删除资产" onclick="event.stopPropagation();" data-action="delete-asset"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" 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 asset-thumb"><span class="ph-frame">防晒霜 · AI 优化</span></div>
<div class="asset-body"><div class="asset-name">防晒霜 · AI 优化</div><div class="asset-meta">透真防晒霜 · AI 优化 · 用过 3 次</div></div>
</div>
<div class="asset-card" data-name="咖啡冻干 · 主图" data-product="三顿半同款" data-source="商品库引用" data-used="3" data-added="20260509" onclick="Shell.toast('查看资产', '咖啡冻干 主图')">
<span class="card-check" aria-hidden="true"><svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="3 8 7 12 13 4"/></svg></span>
<button class="card-del-btn" type="button" title="删除资产" onclick="event.stopPropagation();" data-action="delete-asset"><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>
<button class="card-del-btn" type="button" title="删除资产" onclick="event.stopPropagation();" data-action="delete-asset"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" 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 asset-thumb"><span class="ph-frame">咖啡冻干 · 主图</span></div>
<div class="asset-body"><div class="asset-name">咖啡冻干 · 主图</div><div class="asset-meta">三顿半同款 · 库引用 · 用过 3 次</div></div>
</div>
<div class="asset-card" data-name="咖啡冻干 · 24 颗" data-product="三顿半同款" data-source="商品库引用" data-used="2" data-added="20260509" onclick="Shell.toast('查看资产', '咖啡冻干 24 颗')">
<span class="card-check" aria-hidden="true"><svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="3 8 7 12 13 4"/></svg></span>
<button class="card-del-btn" type="button" title="删除资产" onclick="event.stopPropagation();" data-action="delete-asset"><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>
<button class="card-del-btn" type="button" title="删除资产" onclick="event.stopPropagation();" data-action="delete-asset"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" 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 asset-thumb"><span class="ph-frame">咖啡冻干 · 24 颗</span></div>
<div class="asset-body"><div class="asset-name">咖啡冻干 · 24 颗</div><div class="asset-meta">三顿半同款 · 库引用 · 用过 2 次</div></div>
</div>
<div class="asset-card" data-name="空气炸锅 · 主图" data-product="小熊 4L 空气炸锅" data-source="商品库引用" data-used="2" data-added="20260504" onclick="Shell.toast('查看资产', '空气炸锅 主图')">
<span class="card-check" aria-hidden="true"><svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="3 8 7 12 13 4"/></svg></span>
<button class="card-del-btn" type="button" title="删除资产" onclick="event.stopPropagation();" data-action="delete-asset"><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>
<button class="card-del-btn" type="button" title="删除资产" onclick="event.stopPropagation();" data-action="delete-asset"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" 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 asset-thumb"><span class="ph-frame">空气炸锅 · 主图</span></div>
<div class="asset-body"><div class="asset-name">空气炸锅 · 主图</div><div class="asset-meta">小熊 4L · 库引用 · 用过 2 次</div></div>
</div>
<div class="asset-card" data-name="瑜伽裤 · 模特图" data-product="露露同款瑜伽裤" data-source="手动上传" data-used="3" data-added="20260506" onclick="Shell.toast('查看资产', '瑜伽裤 模特')">
<span class="card-check" aria-hidden="true"><svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="3 8 7 12 13 4"/></svg></span>
<button class="card-del-btn" type="button" title="删除资产" onclick="event.stopPropagation();" data-action="delete-asset"><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>
<button class="card-del-btn" type="button" title="删除资产" onclick="event.stopPropagation();" data-action="delete-asset"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" 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 asset-thumb"><span class="ph-frame">瑜伽裤 · 模特</span></div>
<div class="asset-body"><div class="asset-name">瑜伽裤 · 模特图</div><div class="asset-meta">露露同款瑜伽裤 · 手动上传 · 用过 3 次</div></div>
</div>
@ -681,49 +681,49 @@
<div class="asset-grid video-grid" data-tab="finals" id="grid-finals" hidden>
<div class="asset-card video" data-name="蓝牙耳机 · 开箱测评" data-project="蓝牙耳机 · 开箱测评" data-duration="60s" data-used="3" data-added="20260507" onclick="Shell.toast('打开成片', '蓝牙耳机 · 开箱测评')">
<span class="card-check" aria-hidden="true"><svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="3 8 7 12 13 4"/></svg></span>
<button class="card-del-btn" type="button" title="删除资产" onclick="event.stopPropagation();" data-action="delete-asset"><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>
<button class="card-del-btn" type="button" title="删除资产" onclick="event.stopPropagation();" data-action="delete-asset"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" 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 asset-thumb"><span class="ph-frame">9:16 · 60s</span></div>
<div class="asset-body"><div class="asset-name">蓝牙耳机 · 开箱测评</div><div class="asset-meta">南卡 Lite Pro · 60s · 5 月 7 日</div></div>
</div>
<div class="asset-card video" data-name="瑜伽裤 · 通勤穿搭" data-project="瑜伽裤 · 通勤穿搭" data-duration="45s" data-used="2" data-added="20260506" onclick="Shell.toast('打开成片', '瑜伽裤 · 通勤穿搭')">
<span class="card-check" aria-hidden="true"><svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="3 8 7 12 13 4"/></svg></span>
<button class="card-del-btn" type="button" title="删除资产" onclick="event.stopPropagation();" data-action="delete-asset"><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>
<button class="card-del-btn" type="button" title="删除资产" onclick="event.stopPropagation();" data-action="delete-asset"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" 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 asset-thumb"><span class="ph-frame">9:16 · 45s</span></div>
<div class="asset-body"><div class="asset-name">瑜伽裤 · 通勤穿搭</div><div class="asset-meta">露露同款 · 45s · 5 月 6 日</div></div>
</div>
<div class="asset-card video" data-name="空气炸锅 · 小户型" data-project="空气炸锅 · 小户型" data-duration="30s" data-used="2" data-added="20260504" onclick="Shell.toast('打开成片', '空气炸锅 · 小户型')">
<span class="card-check" aria-hidden="true"><svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="3 8 7 12 13 4"/></svg></span>
<button class="card-del-btn" type="button" title="删除资产" onclick="event.stopPropagation();" data-action="delete-asset"><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>
<button class="card-del-btn" type="button" title="删除资产" onclick="event.stopPropagation();" data-action="delete-asset"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" 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 asset-thumb"><span class="ph-frame">9:16 · 30s</span></div>
<div class="asset-body"><div class="asset-name">空气炸锅 · 小户型</div><div class="asset-meta">小熊 4L · 30s · 5 月 4 日</div></div>
</div>
<div class="asset-card video" data-name="补水面膜 · 痛点种草 v1" data-project="补水面膜 · 痛点种草 v1" data-duration="60s" data-used="2" data-added="20260428" onclick="Shell.toast('打开成片', '补水面膜 v1')">
<span class="card-check" aria-hidden="true"><svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="3 8 7 12 13 4"/></svg></span>
<button class="card-del-btn" type="button" title="删除资产" onclick="event.stopPropagation();" data-action="delete-asset"><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>
<button class="card-del-btn" type="button" title="删除资产" onclick="event.stopPropagation();" data-action="delete-asset"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" 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 asset-thumb"><span class="ph-frame">9:16 · 60s</span></div>
<div class="asset-body"><div class="asset-name">补水面膜 · 痛点种草 v1</div><div class="asset-meta">透真补水面膜 · 60s · 4 月 28 日</div></div>
</div>
<div class="asset-card video" data-name="防晒霜 · 通勤对比" data-project="防晒霜 · 通勤对比" data-duration="60s" data-used="1" data-added="20260425" onclick="Shell.toast('打开成片', '防晒霜 · 通勤对比')">
<span class="card-check" aria-hidden="true"><svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="3 8 7 12 13 4"/></svg></span>
<button class="card-del-btn" type="button" title="删除资产" onclick="event.stopPropagation();" data-action="delete-asset"><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>
<button class="card-del-btn" type="button" title="删除资产" onclick="event.stopPropagation();" data-action="delete-asset"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" 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 asset-thumb"><span class="ph-frame">9:16 · 60s</span></div>
<div class="asset-body"><div class="asset-name">防晒霜 · 通勤对比</div><div class="asset-meta">透真防晒霜 · 60s · 4 月 25 日</div></div>
</div>
<div class="asset-card video" data-name="速食面 · 加班治愈" data-project="速食面 · 加班治愈" data-duration="30s" data-used="1" data-added="20260420" onclick="Shell.toast('打开成片', '速食面 · 加班治愈')">
<span class="card-check" aria-hidden="true"><svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="3 8 7 12 13 4"/></svg></span>
<button class="card-del-btn" type="button" title="删除资产" onclick="event.stopPropagation();" data-action="delete-asset"><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>
<button class="card-del-btn" type="button" title="删除资产" onclick="event.stopPropagation();" data-action="delete-asset"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" 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 asset-thumb"><span class="ph-frame">9:16 · 30s</span></div>
<div class="asset-body"><div class="asset-name">速食面 · 加班治愈</div><div class="asset-meta">滋啦速食 · 30s · 4 月 20 日</div></div>
</div>
<div class="asset-card video" data-name="咖啡 · 早八剧情" data-project="咖啡 · 早八剧情" data-duration="45s" data-used="2" data-added="20260418" onclick="Shell.toast('打开成片', '咖啡 · 早八剧情')">
<span class="card-check" aria-hidden="true"><svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="3 8 7 12 13 4"/></svg></span>
<button class="card-del-btn" type="button" title="删除资产" onclick="event.stopPropagation();" data-action="delete-asset"><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>
<button class="card-del-btn" type="button" title="删除资产" onclick="event.stopPropagation();" data-action="delete-asset"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" 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 asset-thumb"><span class="ph-frame">9:16 · 45s</span></div>
<div class="asset-body"><div class="asset-name">咖啡 · 早八剧情</div><div class="asset-meta">三顿半同款 · 45s · 4 月 18 日</div></div>
</div>
<div class="asset-card video" data-name="收纳 · 北欧" data-project="收纳 · 北欧" data-duration="15s" data-used="1" data-added="20260410" onclick="Shell.toast('打开成片', '收纳 · 北欧')">
<span class="card-check" aria-hidden="true"><svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="3 8 7 12 13 4"/></svg></span>
<button class="card-del-btn" type="button" title="删除资产" onclick="event.stopPropagation();" data-action="delete-asset"><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>
<button class="card-del-btn" type="button" title="删除资产" onclick="event.stopPropagation();" data-action="delete-asset"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" 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 asset-thumb"><span class="ph-frame">9:16 · 15s</span></div>
<div class="asset-body"><div class="asset-name">收纳 · 北欧</div><div class="asset-meta">家居好物 · 15s · 4 月 10 日</div></div>
</div>
@ -733,19 +733,19 @@
<div class="asset-grid" data-tab="uploads" id="grid-uploads" hidden>
<div class="asset-card" data-name="林夕 · 主播照" data-kind="人物" data-source="手动上传" data-used="4" data-added="20260513" onclick="Shell.toast('查看资产', '林夕 · 主播照')">
<span class="card-check" aria-hidden="true"><svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="3 8 7 12 13 4"/></svg></span>
<button class="card-del-btn" type="button" title="删除资产" onclick="event.stopPropagation();" data-action="delete-asset"><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>
<button class="card-del-btn" type="button" title="删除资产" onclick="event.stopPropagation();" data-action="delete-asset"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" 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 asset-thumb"><span class="ph-frame">林夕 · 主播照</span></div>
<div class="asset-body"><div class="asset-name">林夕 · 主播照</div><div class="asset-meta">人物 · 手动上传 · 用过 4 次</div></div>
</div>
<div class="asset-card" data-name="卧室 · 实拍" data-kind="场景" data-source="手动上传" data-used="2" data-added="20260510" onclick="Shell.toast('查看资产', '卧室 · 实拍')">
<span class="card-check" aria-hidden="true"><svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="3 8 7 12 13 4"/></svg></span>
<button class="card-del-btn" type="button" title="删除资产" onclick="event.stopPropagation();" data-action="delete-asset"><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>
<button class="card-del-btn" type="button" title="删除资产" onclick="event.stopPropagation();" data-action="delete-asset"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" 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 asset-thumb"><span class="ph-frame">卧室 · 实拍</span></div>
<div class="asset-body"><div class="asset-name">卧室 · 实拍</div><div class="asset-meta">场景 · 手动上传 · 用过 2 次</div></div>
</div>
<div class="asset-card" data-name="防晒霜 · 官方图" data-kind="商品" data-source="手动上传" data-used="3" data-added="20260507" onclick="Shell.toast('查看资产', '防晒霜 · 官方图')">
<span class="card-check" aria-hidden="true"><svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="3 8 7 12 13 4"/></svg></span>
<button class="card-del-btn" type="button" title="删除资产" onclick="event.stopPropagation();" data-action="delete-asset"><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>
<button class="card-del-btn" type="button" title="删除资产" onclick="event.stopPropagation();" data-action="delete-asset"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" 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 asset-thumb"><span class="ph-frame">防晒霜 · 官方图</span></div>
<div class="asset-body"><div class="asset-name">防晒霜 · 官方图</div><div class="asset-meta">商品 · 手动上传 · 用过 3 次</div></div>
</div>
@ -814,7 +814,7 @@
<img id="upload-preview-img" alt="预览">
<video id="upload-preview-video" controls hidden></video>
<button class="preview-x" type="button" id="upload-preview-x" 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>
<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>
</div>
</div>
@ -904,7 +904,7 @@
<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>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" 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>
@ -912,7 +912,7 @@
<div class="modal-f" id="del-confirm-foot">
<button class="btn" type="button" id="del-confirm-cancel">取消</button>
<button class="btn" type="button" id="del-confirm-ok" style="background:var(--accent-crimson,#c43d3d);color:var(--accent-white);border-color:var(--accent-crimson,#c43d3d)">
<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>
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M3 6h18"/></svg>
确认删除
</button>
</div>
@ -925,14 +925,14 @@
<button class="clear-sel" type="button" id="bulk-clear">清空</button>
<span class="sep"></span>
<button class="danger" type="button" id="bulk-del">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" 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>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" 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="move-wrap">
<button type="button" id="bulk-move">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M9 18l6-6-6-6"/><path d="M3 12h12"/></svg>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M9 18l6-6-6-6"/><path d="M3 12h12"/></svg>
移动到
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" style="margin-left:2px"><path d="M4 10l4-4 4 4"/></svg>
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" style="margin-left:2px"><path d="M4 10l4-4 4 4"/></svg>
</button>
<div class="move-menu" id="bulk-move-menu"></div>
</div>
@ -945,7 +945,7 @@ Shell.render({ active: 'library', crumbs: [{ label: '工作台', href: 'index.ht
/* ─── 给所有资产卡注入下载按钮 · PRD §6.5 所有中间产物可下载 ─── */
(function injectDownloadBtns() {
const dlSvg = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg>';
const dlSvg = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg>';
document.querySelectorAll('.asset-card').forEach(card => {
if (card.querySelector('.card-dl-btn')) return;
const btn = document.createElement('button');
@ -995,7 +995,7 @@ const TAB_KEYS = ['people', 'scenes', 'products', 'finals', 'uploads', 'unclassi
card.dataset.libId = it.id || '';
card.innerHTML = `
<span class="card-check" aria-hidden="true"><svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="3 8 7 12 13 4"/></svg></span>
<button class="card-del-btn" type="button" title="删除资产" onclick="event.stopPropagation();" data-action="delete-asset"><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>
<button class="card-del-btn" type="button" title="删除资产" onclick="event.stopPropagation();" data-action="delete-asset"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" 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 asset-thumb"><span class="ph-frame">${esc(it.name || '未命名')}</span></div>
<div class="asset-body">
<div class="asset-name">${esc(it.name || '未命名')}</div>
@ -1262,14 +1262,14 @@ function renderPagination(totalVisible, totalPages) {
const list = document.getElementById('page-list');
const items = pageNumberList(state.page, totalPages);
let html = `<button type="button" data-page="prev" ${state.page <= 1 ? 'disabled' : ''} aria-label="上一页">
<svg width="11" height="11" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M10 12L6 8l4-4"/></svg>
<svg width="11" height="11" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M10 12L6 8l4-4"/></svg>
</button>`;
items.forEach(p => {
if (p === '…') html += `<span class="ellipsis"></span>`;
else html += `<button type="button" data-page="${p}" ${p === state.page ? 'class="active"' : ''}>${p}</button>`;
});
html += `<button type="button" data-page="next" ${state.page >= totalPages ? 'disabled' : ''} aria-label="下一页">
<svg width="11" height="11" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M6 4l4 4-4 4"/></svg>
<svg width="11" height="11" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M6 4l4 4-4 4"/></svg>
</button>`;
list.innerHTML = html;
}
@ -1894,7 +1894,7 @@ function renderMoveMenu() {
bulkMoveMenu.innerHTML = TAB_KEYS
.filter(t => t !== cur)
.map(t => `<button class="mv-item" type="button" data-target="${t}">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><polyline points="9 18 15 12 9 6"/></svg>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="9 18 15 12 9 6"/></svg>
移到「${TAB_NAMES[t]}」
</button>`).join('');
bulkMoveMenu.querySelectorAll('.mv-item').forEach(btn => {
@ -1988,9 +1988,13 @@ document.querySelectorAll('.asset-card').forEach(card => {
.asset-detail-lead { display: flex; flex-direction: column; gap: 10px; }
.asset-detail-lead .ad-lead-wrap { position: relative; }
.asset-detail-lead .placeholder.ad-lead-img { aspect-ratio: 3/4; border-radius: var(--r-md); }
.asset-detail-lead .ad-zoom-btn { position: absolute; right: 10px; bottom: 10px; height: 28px; padding: 0 12px; background: rgba(21,20,15,.7); color: #fff; border: 0; border-radius: var(--r-pill); display: inline-flex; align-items: center; gap: 4px; font-size: 11.5px; font-family: inherit; cursor: pointer; }
.asset-detail-lead .ad-zoom-btn:hover { background: rgba(21,20,15,.9); }
.asset-detail-lead .ad-zoom-btn svg { width: 12px; height: 12px; }
/* 查看大图 icon · 悬浮容器才显示 · 32×32 icon-only */
.ad-zoom-btn { position: absolute; right: 8px; bottom: 8px; width: 32px; height: 32px; padding: 0; background: rgba(21,20,15,.7); color: #fff; border: 0; border-radius: var(--r-sm); display: grid; place-items: center; cursor: pointer; backdrop-filter: blur(4px); opacity: 0; transition: opacity var(--t-base), background var(--t-base); z-index: 3; }
.ad-zoom-btn:hover { background: rgba(21,20,15,.92); }
.ad-zoom-btn svg { width: 14px; height: 14px; }
.asset-detail-lead .ad-lead-wrap:hover .ad-zoom-btn,
.asset-detail-tri-row .placeholder:hover .ad-zoom-btn { opacity: 1; }
.asset-detail-tri-row .placeholder { position: relative; }
.asset-detail-lead .ad-thumbs { display: flex; gap: 8px; }
.asset-detail-lead .ad-thumbs .thumb { flex: 0 0 64px; aspect-ratio: 3/4; border-radius: var(--r-sm); border: 1px solid var(--border-faint); cursor: pointer; overflow: hidden; }
.asset-detail-lead .ad-thumbs .thumb:hover { border-color: var(--heat-40); }
@ -2019,13 +2023,38 @@ document.querySelectorAll('.asset-card').forEach(card => {
.ad-props .ad-prop .v { color: var(--accent-black); font-weight: 500; word-break: break-all; }
.asset-detail-tip { margin-top: 10px; padding: 10px 12px; background: var(--heat-12); border: 1px solid var(--heat-20); border-radius: var(--r-sm); font-size: 12px; color: var(--accent-black); display: flex; align-items: center; gap: 8px; line-height: 1.5; }
.asset-detail-tip svg { width: 14px; height: 14px; color: var(--heat); flex-shrink: 0; }
.asset-detail-tip .ai-gen-btn { margin-left: auto; height: 26px; padding: 0 10px; background: var(--heat); color: #fff; border: 1px solid var(--heat); border-radius: var(--r-sm); font-size: 11.5px; cursor: pointer; font-family: inherit; flex-shrink: 0; }
.asset-detail-tip .ai-gen-btn { margin-left: auto; height: 26px; padding: 0 10px; background: var(--heat); color: #fff; border: 1px solid var(--heat); border-radius: var(--r-sm); font-size: 11.5px; cursor: pointer; font-family: inherit; flex-shrink: 0; display: inline-flex; align-items: center; }
.asset-detail-tip .ai-gen-btn:disabled { opacity: .55; cursor: not-allowed; }
.asset-detail-tip.is-loading svg { animation: ad-spin 1s linear infinite; }
@keyframes ad-spin { to { transform: rotate(360deg); } }
.asset-detail-history { margin-top: 10px; padding: 10px 12px; background: var(--background-lighter); border: 1px solid var(--border-faint); border-radius: var(--r-sm); }
.asset-detail-history .adh-h { font-family: var(--font-mono); font-size: 10.5px; color: var(--black-alpha-56); margin-bottom: 8px; letter-spacing: .02em; }
.asset-detail-history .adh-h .adh-cur { color: var(--heat); }
.asset-detail-history .adh-row { display: flex; gap: 8px; flex-wrap: wrap; }
.asset-detail-history .adh-thumb { width: 64px; height: 36px; border-radius: var(--r-sm); background: var(--background-base); border: 1px solid var(--border-faint); display: grid; place-items: center; font-family: var(--font-mono); font-size: 11px; color: var(--black-alpha-72); cursor: pointer; position: relative; transition: border-color var(--t-base), color var(--t-base); }
.asset-detail-history .adh-thumb:hover { border-color: var(--heat-40); color: var(--heat); }
.asset-detail-history .adh-thumb.active { border-color: var(--heat); color: var(--heat); background: var(--heat-12); }
.asset-detail-history .adh-thumb.active::after { content: ""; position: absolute; top: -3px; right: -3px; width: 8px; height: 8px; background: var(--heat); border: 2px solid var(--surface); border-radius: 50%; }
.asset-modal-f { padding: 14px 20px; border-top: 1px solid var(--border-faint); display: flex; align-items: center; gap: 8px; }
.asset-modal-f .ad-foot-stats { display: flex; gap: 6px; margin-right: auto; }
.asset-modal-f .ad-stat-btn { height: 32px; padding: 0 12px; display: inline-flex; align-items: center; gap: 6px; background: transparent; border: 1px solid var(--border-faint); border-radius: var(--r-sm); color: var(--black-alpha-72); font-size: 12.5px; font-family: inherit; cursor: pointer; }
.asset-modal-f .ad-stat-btn:hover { background: var(--background-lighter); color: var(--accent-black); border-color: var(--black-alpha-24); }
.asset-modal-f .ad-stat-btn svg { width: 13px; height: 13px; }
.asset-modal-f .ad-stat-btn b { color: var(--accent-black); font-weight: 600; }
/* ── 缺保存 · 二次确认弹窗(模仿 model-photo .mc-leave) ── */
.lib-confirm-bg { position: fixed; inset: 0; z-index: 1300; background: rgba(21,20,15,.42); display: none; align-items: center; justify-content: center; padding: 40px; }
.lib-confirm-bg.show { display: flex; }
.lib-confirm { background: var(--surface); border: 1px solid var(--border-faint); border-radius: var(--r-md); width: 440px; max-width: 92vw; box-shadow: 0 24px 64px rgba(0,0,0,.16); overflow: hidden; }
.lib-confirm .lc-h { display: flex; align-items: center; gap: 10px; padding: 14px 20px 10px; }
.lib-confirm .lc-h .ic { width: 28px; height: 28px; display: grid; place-items: center; background: var(--heat-12); color: var(--heat); border-radius: var(--r-sm); flex-shrink: 0; }
.lib-confirm .lc-h .ic svg { width: 16px; height: 16px; }
.lib-confirm .lc-h h3 { font-size: 15px; font-weight: 600; color: var(--accent-black); margin: 0; }
.lib-confirm .lc-h .mono { margin-left: auto; font-family: var(--font-mono); font-size: 10.5px; color: var(--black-alpha-48); letter-spacing: .04em; }
.lib-confirm .lc-b { padding: 4px 20px 18px; font-size: 13px; line-height: 1.65; color: var(--black-alpha-72); }
.lib-confirm .lc-b b { color: var(--accent-black); font-weight: 600; }
.lib-confirm .lc-f { display: flex; align-items: center; gap: 8px; padding: 12px 20px; border-top: 1px solid var(--border-faint); background: var(--background-lighter); }
.lib-confirm .lc-f .spacer { flex: 1; }
.lib-confirm .lc-f .btn { height: 34px; padding: 0 14px; font-size: 13px; }
`;
const style = document.createElement('style');
style.textContent = css;
@ -2044,9 +2073,8 @@ document.querySelectorAll('.asset-card').forEach(card => {
<div class="asset-detail-lead">
<div class="ad-lead-wrap">
<div class="placeholder ad-lead-img" id="lib-detail-lead-img"><span class="ph-frame">立绘 / 主图</span></div>
<button class="ad-zoom-btn" type="button">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 8V3h5M16 3h5v5M21 16v5h-5M8 21H3v-5"/></svg>
查看大图
<button class="ad-zoom-btn" type="button" id="lib-detail-lead-zoom" aria-label="查看大图" title="查看大图">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M3 8V3h5M16 3h5v5M21 16v5h-5M8 21H3v-5"/></svg>
</button>
</div>
<div class="ad-thumbs" id="lib-detail-thumbs"></div>
@ -2054,23 +2082,30 @@ document.querySelectorAll('.asset-card').forEach(card => {
<div class="asset-detail-right">
<div class="ad-section" id="lib-detail-tri-section">
<div class="asset-detail-section-h">
<span class="ic"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="7" height="7"/><rect x="14" y="3" width="7" height="7"/><rect x="3" y="14" width="7" height="7"/></svg></span>
<span class="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="7" height="7"/><rect x="14" y="3" width="7" height="7"/><rect x="3" y="14" width="7" height="7"/></svg></span>
<span class="t">三视图</span>
<span class="ad-ratio-chip" id="lib-detail-ratio">16:9</span>
<button class="ad-icon-btn" type="button" title="下载"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4M7 10l5 5 5-5M12 15V3"/></svg></button>
<button class="ad-icon-btn" type="button" title="下载"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4M7 10l5 5 5-5M12 15V3"/></svg></button>
</div>
<div class="asset-detail-tri-row" id="lib-detail-tri">
<div class="placeholder"><span class="ph-frame">正 / 侧 / 背 · 三视图</span></div>
</div>
<div class="asset-detail-tip" id="lib-detail-tip" style="display:none;">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="9"/><path d="M12 8v4M12 16h.01"/></svg>
<span>暂无三视图,建议用 AI 生成以保证多角度一致性</span>
<button class="ai-gen-btn" type="button" id="lib-detail-aigen">AI 生成三视图</button>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="9"/><path d="M12 8v4M12 16h.01"/></svg>
<span id="lib-detail-tip-text">暂无三视图,建议用 AI 生成以保证多角度一致性</span>
<button class="ai-gen-btn" type="button" id="lib-detail-aigen">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" style="width:11px;height:11px;display:inline-block;vertical-align:-1px;margin-right:3px;"><path d="M12 3l1.8 4.2L18 9l-4.2 1.8L12 15l-1.8-4.2L6 9l4.2-1.8L12 3z"/></svg>
<span id="lib-detail-aigen-label">AI 生成三视图</span>
</button>
</div>
<div class="asset-detail-history" id="lib-detail-history" style="display:none;">
<div class="adh-h">// 三视图版本 · <span class="adh-ct" id="lib-detail-history-count">0</span> 版 · <span class="adh-cur" id="lib-detail-history-cur">v1</span></div>
<div class="adh-row" id="lib-detail-history-row"></div>
</div>
</div>
<div class="ad-section">
<div class="asset-detail-section-h">
<span class="ic"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M4 6h16M4 12h16M4 18h10"/></svg></span>
<span class="ic"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M4 6h16M4 12h16M4 18h10"/></svg></span>
<span class="t">简介</span>
</div>
<p class="ad-intro" id="lib-detail-intro"></p>
@ -2083,11 +2118,30 @@ document.querySelectorAll('.asset-card').forEach(card => {
<div class="asset-modal-f">
<div class="ad-foot-stats">
<button class="ad-stat-btn" type="button">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4M7 10l5 5 5-5M12 15V3"/></svg>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4M7 10l5 5 5-5M12 15V3"/></svg>
下载
</button>
</div>
<button class="btn btn-primary" type="button" id="lib-detail-apply">使用该资产</button>
<button class="btn btn-primary" type="button" id="lib-detail-apply">保存</button>
</div>
</div>
</div>
<div class="lib-confirm-bg" id="lib-confirm-bg" aria-hidden="true">
<div class="lib-confirm" role="dialog" aria-modal="true" aria-labelledby="lib-confirm-title">
<div class="lc-h">
<span class="ic">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M12 9v4M12 17h.01M10.3 3.86l-8.18 14.18A2 2 0 0 0 3.84 21h16.32a2 2 0 0 0 1.72-2.96L13.7 3.86a2 2 0 0 0-3.4 0z"/></svg>
</span>
<h3 id="lib-confirm-title">三视图尚未保存</h3>
<span class="mono">// UNSAVED</span>
</div>
<div class="lc-b" id="lib-confirm-body">
已生成 <b id="lib-confirm-count">1</b> 版三视图但<b>尚未保存</b>。直接退出会丢失这些版本,且当前资产仍标记为「<b>缺三视图</b>」。
</div>
<div class="lc-f">
<span class="spacer"></span>
<button class="btn" type="button" id="lib-confirm-discard">退出</button>
<button class="btn btn-primary" type="button" id="lib-confirm-save">保存并退出</button>
</div>
</div>
</div>`;
@ -2105,6 +2159,86 @@ document.querySelectorAll('.asset-card').forEach(card => {
const tagsEl = document.getElementById('lib-detail-tags');
const propsEl = document.getElementById('lib-detail-props');
const tipEl = document.getElementById('lib-detail-tip');
const tipTextEl = document.getElementById('lib-detail-tip-text');
const aigenBtn = document.getElementById('lib-detail-aigen');
const aigenLabel = document.getElementById('lib-detail-aigen-label');
const historyEl = document.getElementById('lib-detail-history');
const historyRowEl = document.getElementById('lib-detail-history-row');
const historyCountEl = document.getElementById('lib-detail-history-count');
const historyCurEl = document.getElementById('lib-detail-history-cur');
const applyBtn = document.getElementById('lib-detail-apply');
// 当前打开资产的状态(仅 isActor + missing tri 时启用)
let _curCard = null;
let _curName = '';
let _versions = []; // [{ ts, label }]
let _curIdx = -1;
let _dirty = false; // 已生成但未保存
let _generating = false;
let _allowGen = false; // 是否启用生成入口(missing tri-view 才启用)
function _renderHistory() {
if (!_versions.length) { historyEl.style.display = 'none'; return; }
historyEl.style.display = 'block';
historyCountEl.textContent = _versions.length;
historyCurEl.textContent = _versions[_curIdx]?.label || '';
historyRowEl.innerHTML = _versions.map((v, i) =>
'<div class="adh-thumb' + (i === _curIdx ? ' active' : '') + '" data-idx="' + i + '" title="' + v.label + ' · ' + v.ts + '">' + v.label + '</div>'
).join('');
historyRowEl.querySelectorAll('.adh-thumb').forEach(el => {
el.addEventListener('click', () => {
const i = Number(el.dataset.idx);
if (i === _curIdx) return;
_curIdx = i;
_renderTriPreview();
_renderHistory();
});
});
}
function _renderTriPreview() {
if (_curIdx < 0) return;
const ver = _versions[_curIdx];
triEl.innerHTML = '<div class="placeholder"><span class="ph-frame">' + _curName + ' · 三视图(正/侧/背)· ' + ver.label + '</span><button class="ad-zoom-btn" type="button" data-zoom-tri aria-label="查看大图" title="查看大图"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M3 8V3h5M16 3h5v5M21 16v5h-5M8 21H3v-5"/></svg></button></div>';
triEl.querySelector('[data-zoom-tri]')?.addEventListener('click', e => {
e.stopPropagation();
if (window.Shell?._openLightbox) Shell._openLightbox('', _curName + ' · 三视图 · ' + ver.label);
});
}
function _renderTriLoading() {
triEl.innerHTML = '<div class="placeholder" style="display:grid;place-items:center;gap:6px;"><div class="spinner" style="width:22px;height:22px;border:2px solid var(--border-faint);border-top-color:var(--heat);border-radius:50%;animation:ad-spin 1s linear infinite;"></div><span class="ph-frame" style="font-size:10.5px;">生成中 · 约 12s</span></div>';
}
function _setAigenLabel(text, loading) {
aigenLabel.textContent = text;
aigenBtn.disabled = !!loading;
tipEl.classList.toggle('is-loading', !!loading);
}
function _startGenerate() {
if (!_allowGen || _generating) return;
_generating = true;
_setAigenLabel(_versions.length ? '生成中…' : '生成中…', true);
_renderTriLoading();
setTimeout(() => {
_generating = false;
const now = new Date();
const ts = String(now.getHours()).padStart(2,'0') + ':' + String(now.getMinutes()).padStart(2,'0');
_versions.push({ ts, label: 'v' + (_versions.length + 1) });
_curIdx = _versions.length - 1;
_dirty = true;
_renderTriPreview();
_renderHistory();
// 第一次生成后,按钮文案 → 再次生成;并隐藏 tip 文案,只留按钮在右侧
tipEl.style.display = 'flex';
tipTextEl.textContent = '已生成 ' + _versions.length + ' 版三视图 · 不满意可重跑,保存后写入资产';
_setAigenLabel('再次生成', false);
if (window.Shell?.toast) {
Shell.toast('三视图已生成', _curName + ' · ' + _versions[_curIdx].label + ' · 满意请点「保存」');
}
}, 1500);
}
function _hash(s) { let h = 0; for (let i = 0; i < s.length; i++) h = ((h << 5) - h + s.charCodeAt(i)) | 0; return Math.abs(h); }
function _fmtAssetId(name, k) { return 'ASSET-20240520-' + (k === 'person' ? 'M' : k === 'scene' ? 'S' : 'P') + String(_hash(name) % 1000).padStart(3, '0'); }
@ -2113,7 +2247,19 @@ document.querySelectorAll('.asset-card').forEach(card => {
function _fmtDl(name) { const n = 200 + _hash(name) % 1800; return n >= 1000 ? (n / 1000).toFixed(1) + 'k' : String(n); }
function open(card) {
// 重置本次打开的状态
_curCard = card;
_versions = [];
_curIdx = -1;
_dirty = false;
_generating = false;
_allowGen = false;
historyEl.style.display = 'none';
tipEl.classList.remove('is-loading');
_setAigenLabel('AI 生成三视图', false);
const name = card.dataset.name || '资产';
_curName = name;
const used = card.dataset.used || '0';
const source = card.dataset.source || '平台预设';
let tagText = 'AI 素材', intro = '', tags = [], props = [], hasTri = false, isActor = false;
@ -2163,51 +2309,142 @@ document.querySelectorAll('.asset-card').forEach(card => {
titleEl.textContent = name;
kindEl.textContent = '/ ' + tagText;
leadImg.innerHTML = '<span class="ph-frame">' + name + '</span>';
// 立绘 zoom 按钮(单次绑定 · 通过 name 闭包始终读最新 _curName)
const _leadZoomBtn = document.getElementById('lib-detail-lead-zoom');
if (_leadZoomBtn && !_leadZoomBtn.dataset.bound) {
_leadZoomBtn.dataset.bound = '1';
_leadZoomBtn.addEventListener('click', e => {
e.stopPropagation();
if (window.Shell?._openLightbox) Shell._openLightbox('', _curName || titleEl.textContent);
});
}
thumbsEl.innerHTML = ['v1','v2','v3'].map((t, i) => `<div class="thumb placeholder${i === 0 ? ' active' : ''}"><span class="ph-frame">${t}</span></div>`).join('');
thumbsEl.querySelectorAll('.thumb').forEach(t => t.addEventListener('click', () => {
thumbsEl.querySelectorAll('.thumb').forEach(x => x.classList.remove('active'));
t.classList.add('active');
}));
// 平台预设资产 · 仅 1 张缩略图(用户上传多张的场景在 pipeline 工作台详情中处理)
const _thumbLabel = card.dataset.sceneType ? '场景' : (isActor ? '立绘' : '主图');
thumbsEl.innerHTML = `<div class="thumb placeholder active"><span class="ph-frame">${_thumbLabel}</span></div>`;
// 卡片是否标注「缺三视图」(data-triview="0")
const cardMissingTri = card.dataset.triview === '0';
if (card.dataset.sceneType) {
triSection.style.display = 'none';
} else if (isActor) {
triSection.style.display = '';
triEl.classList.remove('actor');
triEl.innerHTML = '<div class="placeholder"><span class="ph-frame">' + name + ' · 三视图 (正/侧/背)</span></div>';
ratioChip.textContent = '16:9';
tipEl.style.display = 'none';
if (cardMissingTri) {
// 人物 · 缺三视图 → 显示生成入口
_allowGen = true;
triEl.innerHTML = '<div class="placeholder missing" data-tri="0"><svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="9"/><path d="M12 8v4M12 16h.01"/></svg><span>暂未生成三视图 · 点击右侧按钮 AI 生成</span></div>';
tipTextEl.textContent = '暂无三视图,建议用 AI 生成以保证多角度一致性';
_setAigenLabel('AI 生成三视图', false);
tipEl.style.display = 'flex';
} else {
triEl.innerHTML = '<div class="placeholder"><span class="ph-frame">' + name + ' · 三视图 (正/侧/背)</span><button class="ad-zoom-btn" type="button" data-zoom-tri aria-label="查看大图" title="查看大图"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M3 8V3h5M16 3h5v5M21 16v5h-5M8 21H3v-5"/></svg></button></div>';
tipEl.style.display = 'none';
}
} else {
triSection.style.display = '';
triEl.classList.remove('actor');
ratioChip.textContent = '16:9';
if (hasTri) {
triEl.innerHTML = '<div class="placeholder"><span class="ph-frame">' + name + ' · 三视图</span></div>';
if (hasTri && !cardMissingTri) {
triEl.innerHTML = '<div class="placeholder"><span class="ph-frame">' + name + ' · 三视图</span><button class="ad-zoom-btn" type="button" data-zoom-tri aria-label="查看大图" title="查看大图"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M3 8V3h5M16 3h5v5M21 16v5h-5M8 21H3v-5"/></svg></button></div>';
tipEl.style.display = 'none';
} else {
// 商品/其他 · 缺三视图 → 同样启用生成入口
_allowGen = true;
triEl.innerHTML = '<div class="placeholder missing" data-tri="0"><svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="9"/><path d="M12 8v4M12 16h.01"/></svg><span>暂未生成三视图(16:9 单图)</span></div>';
tipTextEl.textContent = '暂无三视图,建议用 AI 生成以保证多角度一致性';
_setAigenLabel('AI 生成三视图', false);
tipEl.style.display = 'flex';
}
}
// 三视图 zoom 按钮 click
triEl.querySelector('[data-zoom-tri]')?.addEventListener('click', e => {
e.stopPropagation();
if (window.Shell?._openLightbox) Shell._openLightbox('', name + ' · 三视图');
});
introEl.textContent = intro || '暂无简介';
tagsEl.innerHTML = tags.map(t => '<span class="ad-tag-chip">' + t + '</span>').join('') +
'<button class="ad-tag-add" type="button" title="添加标签"><svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M8 3v10M3 8h10"/></svg></button>';
'<button class="ad-tag-add" type="button" title="添加标签"><svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"><path d="M8 3v10M3 8h10"/></svg></button>';
propsEl.innerHTML = props.map(([k, v]) => '<div class="ad-prop"><span class="k">' + k + '</span><span class="v">' + v + '</span></div>').join('');
bg.classList.add('show');
}
function close() { bg.classList.remove('show'); }
// ── 二次确认弹窗 ──
const confirmBg = document.getElementById('lib-confirm-bg');
const confirmCountEl = document.getElementById('lib-confirm-count');
const confirmSaveBtn = document.getElementById('lib-confirm-save');
const confirmDiscardBtn = document.getElementById('lib-confirm-discard');
function _openConfirm() {
confirmCountEl.textContent = _versions.length;
confirmBg.classList.add('show');
confirmBg.setAttribute('aria-hidden', 'false');
}
function _closeConfirm() {
confirmBg.classList.remove('show');
confirmBg.setAttribute('aria-hidden', 'true');
}
confirmBg.addEventListener('click', e => { if (e.target === confirmBg) _closeConfirm(); });
function _doSave() {
if (_curCard) {
const badge = _curCard.querySelector('.tri-missing-badge');
if (badge) badge.remove();
_curCard.dataset.triview = '1';
}
_dirty = false;
Shell.toast('已保存', _curName + ' · 三视图(' + _versions[_curIdx].label + ')已写入资产');
}
function close(force) {
if (!force && _dirty) {
_openConfirm();
return;
}
bg.classList.remove('show');
_dirty = false;
}
bg.addEventListener('click', e => { if (e.target === bg) close(); });
document.getElementById('lib-detail-x').addEventListener('click', close);
document.getElementById('lib-detail-apply').addEventListener('click', () => {
Shell.toast('已应用「' + titleEl.textContent + '」', '已加入当前项目');
close();
document.getElementById('lib-detail-x').addEventListener('click', () => close());
applyBtn.addEventListener('click', () => {
if (_allowGen && _versions.length) {
_doSave();
close(true);
return;
}
if (_allowGen && !_versions.length) {
Shell.toast('请先生成三视图', '点击「AI 生成三视图」开始');
return;
}
Shell.toast('已保存', _curName);
close(true);
});
document.getElementById('lib-detail-aigen').addEventListener('click', () => {
Shell.toast('AI 生成三视图中', '约 12s · POST /assets/tri-view');
// 弹窗按钮 · 主按钮「保存并退出」/ 次按钮「退出」
confirmSaveBtn.addEventListener('click', () => {
if (_versions.length) _doSave();
_closeConfirm();
close(true);
});
confirmDiscardBtn.addEventListener('click', () => {
_dirty = false;
_closeConfirm();
close(true);
});
aigenBtn.addEventListener('click', _startGenerate);
// Esc · 若确认弹窗开着,先关确认;否则尝试关详情(经过 dirty 检查)
document.addEventListener('keydown', e => {
if (e.key !== 'Escape') return;
if (confirmBg.classList.contains('show')) { _closeConfirm(); return; }
if (!bg.classList.contains('show')) return;
close();
});
// 把所有 .asset-card 的旧 onclick="Shell.toast(...)" 清掉,改成 open(card)

View File

@ -107,7 +107,7 @@
<!-- 左:品牌 -->
<aside class="auth-brand">
<div class="logo">
<span class="ic"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 2 L19 7 V17 L12 22 L5 17 V7 Z"/><path d="M12 2 V22"/><path d="M5 7 L19 17"/></svg></span>
<span class="ic"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M12 2 L19 7 V17 L12 22 L5 17 V7 Z"/><path d="M12 2 V22"/><path d="M5 7 L19 17"/></svg></span>
<span>流·Studio</span>
</div>
<div class="tag">// SHORT-VIDEO COMMERCE PLATFORM</div>
@ -145,7 +145,7 @@
<div class="field">
<label class="field-label" for="auth-email">邮箱 <span class="req">*</span></label>
<div class="field-input-wrap">
<svg class="ic-l" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M4 4h16a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2z"/><path d="m22 6-10 7L2 6"/></svg>
<svg class="ic-l" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M4 4h16a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2z"/><path d="m22 6-10 7L2 6"/></svg>
<input type="email" id="auth-email" placeholder="name@company.com" value="li@shop.com" required>
</div>
</div>
@ -153,17 +153,17 @@
<div class="field">
<label class="field-label" for="auth-pwd">密码 <span class="req">*</span></label>
<div class="field-input-wrap">
<svg class="ic-l" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="11" width="18" height="11" rx="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></svg>
<svg class="ic-l" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="11" width="18" height="11" rx="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></svg>
<input type="password" id="auth-pwd" placeholder="••••••••" value="demo-1234" required>
<button type="button" class="toggle-pwd" aria-label="切换密码可见" onclick="togglePwd()">
<svg id="pwd-eye" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M2 12s4-7 10-7 10 7 10 7-4 7-10 7-10-7-10-7z"/><circle cx="12" cy="12" r="3"/></svg>
<svg id="pwd-eye" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M2 12s4-7 10-7 10 7 10 7-4 7-10 7-10-7-10-7z"/><circle cx="12" cy="12" r="3"/></svg>
</button>
</div>
</div>
<div class="row-between">
<label><input type="checkbox" checked> 记住我 7 天</label>
<a href="#" onclick="event.preventDefault();alert('演示稿 · 真实流程为重置密码邮件');">忘记密码?</a>
<a href="#" onclick="event.preventDefault();_loginToast('已发送重置邮件','请到 li@shop.com 收件箱查看 · 链接 30 分钟有效');">忘记密码?</a>
</div>
<button class="btn-cta" type="submit">
@ -174,11 +174,11 @@
<div class="divider"><span class="line"></span><span class="txt">OR</span><span class="line"></span></div>
<div class="sso-row">
<button type="button" class="sso-btn" onclick="alert('演示稿 · 微信扫码登录占位');">
<button type="button" class="sso-btn" onclick="_loginToast('微信扫码','请在 60s 内用微信扫一扫完成授权 · 内测中,以邮箱登录为准');">
<svg viewBox="0 0 24 24" fill="currentColor"><path d="M9 4C5 4 2 6.5 2 9.5c0 1.7 1 3.2 2.5 4.2L4 16l2.2-1.1c.7.2 1.5.3 2.3.3-.3-.5-.5-1.1-.5-1.7 0-2.8 2.9-5 6.5-5h.5C14.6 6 12 4 9 4zm-2 3.5c.6 0 1 .4 1 1s-.4 1-1 1-1-.4-1-1 .4-1 1-1zm4 0c.6 0 1 .4 1 1s-.4 1-1 1-1-.4-1-1 .4-1 1-1zM15 9c-3.3 0-6 2.2-6 5s2.7 5 6 5c.7 0 1.4-.1 2-.3L19 20l-.4-1.7c1.4-.9 2.4-2.3 2.4-3.8 0-2.8-2.7-5-6-5zm-2 2.5c.6 0 1 .4 1 1s-.4 1-1 1-1-.4-1-1 .4-1 1-1zm4 0c.6 0 1 .4 1 1s-.4 1-1 1-1-.4-1-1 .4-1 1-1z"/></svg>
微信扫码
</button>
<button type="button" class="sso-btn" onclick="alert('演示稿 · 飞书 SSO 占位');">
<button type="button" class="sso-btn" onclick="_loginToast('飞书 SSO','即将打开企业飞书登录授权页 · 内测中,以邮箱登录为准');">
<svg viewBox="0 0 24 24" fill="currentColor"><rect x="3" y="3" width="18" height="18" rx="3"/></svg>
飞书 SSO
</button>
@ -202,6 +202,20 @@
btn.disabled = true;
setTimeout(() => { location.href = 'index.html'; }, 700);
}
// 轻量 toast(不依赖 shell.js,免登录页拉重)
function _loginToast(t, sub) {
let el = document.getElementById('__login-toast');
if (!el) {
el = document.createElement('div');
el.id = '__login-toast';
el.style.cssText = 'position:fixed;left:50%;bottom:36px;transform:translateX(-50%) translateY(20px);background:#fff;border:1px solid #e0e0e0;border-radius:8px;padding:12px 18px;box-shadow:0 8px 24px rgba(0,0,0,.12);display:flex;flex-direction:column;gap:2px;opacity:0;transition:opacity .2s,transform .2s;z-index:9999;font-family:inherit;max-width:360px;';
document.body.appendChild(el);
}
el.innerHTML = '<div style="font-size:13.5px;font-weight:600;color:#262626;">' + t + '</div>' + (sub ? '<div style="font-size:11.5px;color:rgba(0,0,0,.56);font-family:\'JetBrains Mono\',monospace;letter-spacing:.02em;">// ' + sub + '</div>' : '');
requestAnimationFrame(() => { el.style.opacity = '1'; el.style.transform = 'translateX(-50%) translateY(0)'; });
clearTimeout(el._t);
el._t = setTimeout(() => { el.style.opacity = '0'; el.style.transform = 'translateX(-50%) translateY(20px)'; }, 2800);
}
</script>
</body>
</html>

View File

@ -0,0 +1,769 @@
<!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?v=202605211643">
<style>
#page-content { flex: 1; min-height: 0; display: flex; flex-direction: column; }
#page { display: flex; flex-direction: column; height: 100%; min-height: 0; }
/* ─── 页头 ─── */
.page-head { margin-bottom: 18px; }
.page-head h1 .ct { font-family: var(--font-mono); font-size: 12px; color: var(--black-alpha-48); margin-left: 8px; font-weight: 400; letter-spacing: .04em; }
/* ─── 顶部工具栏:tabs + 搜索 + 操作 ─── */
.ms-toolbar {
display: flex; align-items: center; gap: 14px;
padding: 10px 14px;
background: var(--surface); border: 1px solid var(--border-faint);
border-radius: var(--r-md);
margin-bottom: 14px; flex-wrap: wrap;
}
.ms-tabs { display: inline-flex; gap: 4px; }
.ms-tab {
height: 30px; padding: 0 14px;
display: inline-flex; align-items: center; gap: 6px;
background: transparent; border: 1px solid transparent;
border-radius: var(--r-pill);
font-family: inherit; font-size: 12.5px; color: var(--black-alpha-72);
cursor: pointer;
transition: background var(--t-base), color var(--t-base), border-color var(--t-base);
}
.ms-tab:hover { color: var(--accent-black); background: var(--background-lighter); }
.ms-tab.active { background: var(--heat-12); color: var(--heat); border-color: var(--heat-20); font-weight: 600; }
.ms-tab .ct {
font-family: var(--font-mono); font-size: 10.5px;
background: var(--black-alpha-7); color: var(--black-alpha-72);
padding: 1px 6px; border-radius: var(--r-pill);
min-width: 18px; text-align: center;
}
.ms-tab.active .ct { background: var(--heat); color: var(--accent-white); }
.ms-search {
flex: 1; min-width: 200px; max-width: 320px;
position: relative; display: inline-flex; align-items: center;
}
.ms-search svg {
position: absolute; left: 10px; width: 13px; height: 13px;
color: var(--black-alpha-40); pointer-events: none;
}
.ms-search input {
width: 100%; height: 32px; padding: 0 12px 0 30px;
background: var(--background-lighter); border: 1px solid var(--border-faint);
border-radius: var(--r-sm); font-family: inherit; font-size: 12.5px;
color: var(--accent-black); outline: none;
}
.ms-search input:focus { border-color: var(--heat-40); background: var(--surface); }
.ms-toggle {
height: 30px; padding: 0 12px;
display: inline-flex; align-items: center; gap: 6px;
background: var(--background-lighter); border: 1px solid var(--border-faint);
border-radius: var(--r-pill);
font-family: inherit; font-size: 12px; color: var(--black-alpha-72);
cursor: pointer; transition: background var(--t-base), color var(--t-base), border-color var(--t-base);
}
.ms-toggle:hover { color: var(--accent-black); border-color: var(--black-alpha-24); }
.ms-toggle.on { background: var(--heat-12); border-color: var(--heat-20); color: var(--heat); font-weight: 600; }
.ms-toggle .dot { width: 6px; height: 6px; border-radius: 50%; background: currentColor; }
.ms-actions { margin-left: auto; display: inline-flex; gap: 8px; }
.ms-actions .btn { height: 30px; padding: 0 12px; font-size: 12px; }
/* ─── 主体:左侧列表 + 右侧详情 ─── */
.ms-body {
flex: 1; min-height: 0;
display: grid;
grid-template-columns: minmax(340px, 420px) 1fr;
gap: 14px;
}
@media (max-width: 980px) { .ms-body { grid-template-columns: 1fr; } }
/* 左:列表卡片 */
.ms-list-pane {
background: var(--surface); border: 1px solid var(--border-faint);
border-radius: var(--r-md);
display: flex; flex-direction: column;
overflow: hidden; min-height: 0;
}
.ms-list-h {
padding: 12px 16px; border-bottom: 1px solid var(--border-faint);
display: flex; align-items: center; gap: 10px;
flex-shrink: 0;
}
.ms-list-h .mono { font-family: var(--font-mono); font-size: 11px; color: var(--black-alpha-48); letter-spacing: .04em; }
.ms-list-h .group-toggle {
margin-left: auto;
background: transparent; border: 0; cursor: pointer;
font-family: var(--font-mono); font-size: 10.5px; color: var(--black-alpha-56);
letter-spacing: .04em; padding: 2px 6px; border-radius: var(--r-sm);
}
.ms-list-h .group-toggle:hover { background: var(--background-lighter); color: var(--accent-black); }
.ms-list { flex: 1; min-height: 0; overflow-y: auto; }
.ms-group-h {
padding: 8px 16px 6px;
font-family: var(--font-mono); font-size: 10.5px;
color: var(--black-alpha-48); letter-spacing: .06em; text-transform: uppercase;
background: var(--background-base);
position: sticky; top: 0; z-index: 1;
}
.ms-item {
position: relative;
display: grid; grid-template-columns: 28px 1fr; gap: 10px;
padding: 14px 16px;
border-bottom: 1px solid var(--border-faint);
cursor: pointer;
transition: background var(--t-base);
}
.ms-item:hover { background: var(--background-lighter); }
.ms-item.active { background: var(--heat-12); }
.ms-item.active::before {
content: ''; position: absolute; left: 0; top: 0; bottom: 0;
width: 3px; background: var(--heat);
}
.ms-item.read .ms-item-title { color: var(--black-alpha-56); font-weight: 400; }
.ms-item.read .ms-item-icon { opacity: .55; }
.ms-item-icon {
width: 28px; height: 28px;
display: grid; place-items: center;
border-radius: var(--r-sm);
background: var(--background-lighter); border: 1px solid var(--border-faint);
color: var(--black-alpha-72);
flex-shrink: 0;
}
.ms-item-icon svg { width: 14px; height: 14px; }
.ms-item-icon.task { background: var(--heat-12); border-color: var(--heat-20); color: var(--heat); }
.ms-item-icon.team { background: rgba(76,134,255,.10); border-color: rgba(76,134,255,.24); color: #3a6dd1; }
.ms-item-icon.system { background: var(--black-alpha-7); border-color: var(--border-faint); color: var(--black-alpha-72); }
.ms-item-icon.billing { background: rgba(241,176,29,.12); border-color: rgba(241,176,29,.28); color: #b5860d; }
.ms-item-icon.success { background: var(--forest-bg); border-color: var(--forest-bd); color: var(--accent-forest); }
.ms-item-icon.error { background: var(--crimson-bg); border-color: var(--crimson-bd); color: var(--accent-crimson); }
.ms-item-main { min-width: 0; }
.ms-item-row1 { display: flex; align-items: center; gap: 8px; }
.ms-item-title { font-size: 13px; font-weight: 600; color: var(--accent-black); flex: 1; min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.ms-item-ts { font-family: var(--font-mono); font-size: 10.5px; color: var(--black-alpha-48); letter-spacing: .02em; flex-shrink: 0; }
.ms-item-unread {
width: 7px; height: 7px; border-radius: 50%;
background: var(--heat); flex-shrink: 0;
}
.ms-item.read .ms-item-unread { display: none; }
.ms-item-body {
font-size: 12px; color: var(--black-alpha-56);
line-height: 1.5; margin-top: 3px;
display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
overflow: hidden;
}
.ms-item-tag {
display: inline-block; margin-top: 6px;
padding: 1px 6px;
font-family: var(--font-mono); font-size: 10px; letter-spacing: .02em;
background: var(--background-lighter); border: 1px solid var(--border-faint);
border-radius: var(--r-sm); color: var(--black-alpha-56);
}
.ms-item-tag.crit { background: var(--crimson-bg); border-color: var(--crimson-bd); color: var(--accent-crimson); }
.ms-item-tag.warn { background: rgba(241,176,29,.14); border-color: rgba(241,176,29,.32); color: #b5860d; }
.ms-item-tag.ok { background: var(--forest-bg); border-color: var(--forest-bd); color: var(--accent-forest); }
.ms-item-tag.info { background: var(--heat-12); border-color: var(--heat-20); color: var(--heat); }
.ms-list-empty {
padding: 60px 24px;
display: flex; flex-direction: column; align-items: center; gap: 10px;
color: var(--black-alpha-48); font-size: 13px;
}
.ms-list-empty .ic {
width: 44px; height: 44px;
display: grid; place-items: center;
background: var(--background-lighter); border: 1px solid var(--border-faint);
border-radius: 50%; color: var(--black-alpha-40);
}
.ms-list-empty .mono { font-family: var(--font-mono); font-size: 10.5px; letter-spacing: .04em; }
/* 右:详情 pane */
.ms-detail-pane {
background: var(--surface); border: 1px solid var(--border-faint);
border-radius: var(--r-md);
display: flex; flex-direction: column;
overflow: hidden; min-height: 0;
}
.ms-detail-empty {
flex: 1; display: grid; place-items: center;
color: var(--black-alpha-40); font-size: 13px;
flex-direction: column; gap: 8px;
}
.ms-detail-empty .ic {
width: 48px; height: 48px;
display: grid; place-items: center;
background: var(--background-lighter); border: 1px solid var(--border-faint);
border-radius: var(--r-md); color: var(--black-alpha-40);
}
.ms-detail-empty .ic svg { width: 22px; height: 22px; }
.ms-detail-empty .mono { font-family: var(--font-mono); font-size: 10.5px; color: var(--black-alpha-48); letter-spacing: .04em; }
.ms-detail-h {
padding: 18px 24px 14px;
border-bottom: 1px solid var(--border-faint);
flex-shrink: 0;
}
.ms-detail-h .row1 { display: flex; align-items: center; gap: 10px; }
.ms-detail-h .kind-tag {
font-family: var(--font-mono); font-size: 10.5px;
padding: 2px 8px; border-radius: var(--r-sm);
background: var(--background-lighter); border: 1px solid var(--border-faint);
color: var(--black-alpha-56); letter-spacing: .04em; text-transform: uppercase;
}
.ms-detail-h .kind-tag.task { background: var(--heat-12); color: var(--heat); border-color: var(--heat-20); }
.ms-detail-h .kind-tag.team { background: rgba(76,134,255,.10); color: #3a6dd1; border-color: rgba(76,134,255,.24); }
.ms-detail-h .kind-tag.system { background: var(--black-alpha-7); color: var(--black-alpha-72); }
.ms-detail-h .kind-tag.billing { background: rgba(241,176,29,.12); color: #b5860d; border-color: rgba(241,176,29,.28); }
.ms-detail-h .x {
margin-left: auto; width: 28px; height: 28px;
background: transparent; border: 0; border-radius: var(--r-sm);
color: var(--black-alpha-56); cursor: pointer;
display: grid; place-items: center;
}
.ms-detail-h .x:hover { background: var(--background-lighter); color: var(--accent-black); }
.ms-detail-h .x svg { width: 14px; height: 14px; }
.ms-detail-h h2 { font-size: 18px; font-weight: 600; letter-spacing: -.012em; color: var(--accent-black); margin: 12px 0 6px; }
.ms-detail-h .meta { display: flex; align-items: center; gap: 14px; font-family: var(--font-mono); font-size: 11px; color: var(--black-alpha-48); letter-spacing: .02em; }
.ms-detail-h .meta .from { color: var(--accent-black); font-weight: 600; }
.ms-detail-h .meta .dot { width: 3px; height: 3px; border-radius: 50%; background: var(--black-alpha-24); }
.ms-detail-b {
flex: 1; min-height: 0; overflow-y: auto;
padding: 18px 24px 20px;
}
.ms-detail-b p { font-size: 13.5px; line-height: 1.7; color: var(--accent-black); margin: 0 0 12px; }
.ms-detail-b .quote {
border-left: 3px solid var(--heat);
padding: 8px 14px;
background: var(--heat-12);
border-radius: 0 var(--r-sm) var(--r-sm) 0;
font-size: 13px; line-height: 1.65; color: var(--accent-black);
margin: 12px 0;
}
/* 详情属性表 */
.ms-props {
display: grid; grid-template-columns: 110px 1fr;
row-gap: 10px; column-gap: 16px;
margin: 14px 0 6px;
padding: 12px 16px;
background: var(--background-lighter); border: 1px solid var(--border-faint);
border-radius: var(--r-sm);
}
.ms-props .k {
font-family: var(--font-mono); font-size: 11px;
color: var(--black-alpha-48); letter-spacing: .04em; text-transform: uppercase;
}
.ms-props .v { font-size: 12.5px; color: var(--accent-black); word-break: break-all; }
.ms-props .v.mono { font-family: var(--font-mono); }
.ms-props .v a { color: var(--heat); }
.ms-props .v a:hover { text-decoration: underline; }
.ms-detail-f {
padding: 12px 20px;
border-top: 1px solid var(--border-faint);
background: var(--background-lighter);
display: flex; align-items: center; gap: 8px;
flex-shrink: 0; flex-wrap: wrap;
}
.ms-detail-f .spacer { flex: 1; }
.ms-detail-f .btn { height: 32px; padding: 0 14px; font-size: 12.5px; }
/* 设置入口卡(底部) */
.ms-foot-hint {
margin-top: 14px;
padding: 10px 14px;
background: var(--background-lighter); border: 1px solid var(--border-faint);
border-radius: var(--r-md);
display: flex; align-items: center; gap: 10px;
font-size: 12px; color: var(--black-alpha-56);
}
.ms-foot-hint svg { width: 14px; height: 14px; color: var(--heat); flex-shrink: 0; }
.ms-foot-hint a { color: var(--heat); margin-left: auto; font-weight: 600; }
.ms-foot-hint a:hover { text-decoration: underline; }
</style>
</head>
<body>
<div id="page">
<div class="page-head">
<div>
<h1>消息中心<span class="ct" id="ms-head-ct">// 0 条未读 · 0 条总计</span></h1>
<div class="sub"><span class="mono">// 任务 · 团队 · 系统 · 计费 四类事件流</span></div>
</div>
</div>
<!-- 顶部工具栏 -->
<div class="ms-toolbar">
<div class="ms-tabs" id="ms-tabs">
<button class="ms-tab active" type="button" data-tab="all">全部 <span class="ct" data-ct="all">0</span></button>
<button class="ms-tab" type="button" data-tab="task">任务 <span class="ct" data-ct="task">0</span></button>
<button class="ms-tab" type="button" data-tab="team">团队 <span class="ct" data-ct="team">0</span></button>
<button class="ms-tab" type="button" data-tab="system">系统 <span class="ct" data-ct="system">0</span></button>
<button class="ms-tab" type="button" data-tab="billing">计费 <span class="ct" data-ct="billing">0</span></button>
</div>
<button class="ms-toggle" type="button" id="ms-only-unread"><span class="dot"></span>仅看未读</button>
<div class="ms-search">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><circle cx="11" cy="11" r="7"/><path d="m21 21-4.3-4.3"/></svg>
<input type="text" id="ms-search-input" placeholder="搜索消息内容 / 来源 / 项目…">
</div>
<div class="ms-actions">
<button class="btn" type="button" id="ms-mark-all">全部标已读</button>
<button class="btn btn-ghost" type="button" id="ms-settings">通知设置</button>
</div>
</div>
<!-- 列表 + 详情 -->
<div class="ms-body">
<!-- 左 · 列表 -->
<div class="ms-list-pane">
<div class="ms-list-h">
<span class="mono" id="ms-list-ct">// 显示 0 条</span>
<button class="group-toggle" type="button" id="ms-group-toggle">按时间分组 ▾</button>
</div>
<div class="ms-list" id="ms-list"></div>
</div>
<!-- 右 · 详情 -->
<div class="ms-detail-pane" id="ms-detail-pane">
<div class="ms-detail-empty" id="ms-detail-empty">
<div class="ic">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M6 8a6 6 0 0 1 12 0c0 7 3 9 3 9H3s3-2 3-9M10 21a2 2 0 0 0 4 0"/></svg>
</div>
<div>从左侧选择一条消息查看详情</div>
<div class="mono">// 点击列表项 · 自动标为已读</div>
</div>
</div>
</div>
<div class="ms-foot-hint">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="9"/><path d="M12 8v4M12 16h.01"/></svg>
消息默认保留 90 天,任务类消息可在「通知设置」中按类别静音或转发到团队邮箱。
<a href="settings.html">前往设置 →</a>
</div>
</div>
<script src="assets/shell.js?v=202605211643"></script>
<script>
Shell.render({ active: '', crumbs: [{ label: '工作台', href: 'index.html' }, { label: '消息中心' }] });
/* ─── Mock 数据 · 12 条,贴合 PRD 与项目当前内容 ─── */
const NOW = new Date('2026-05-25T15:18:00');
function _ago(min) {
const d = new Date(NOW.getTime() - min * 60000);
return d;
}
function _fmt(d) {
const today = new Date(NOW.getFullYear(), NOW.getMonth(), NOW.getDate());
const dDay = new Date(d.getFullYear(), d.getMonth(), d.getDate());
const diff = (today - dDay) / (24 * 3600 * 1000);
const hh = String(d.getHours()).padStart(2, '0'), mm = String(d.getMinutes()).padStart(2, '0');
if (diff === 0) return '今天 ' + hh + ':' + mm;
if (diff === 1) return '昨天 ' + hh + ':' + mm;
if (diff < 7) return diff + ' 天前';
return (d.getMonth() + 1) + '.' + d.getDate() + ' ' + hh + ':' + mm;
}
function _fmtFull(d) {
return d.getFullYear() + '-' + String(d.getMonth() + 1).padStart(2, '0') + '-' + String(d.getDate()).padStart(2, '0')
+ ' ' + String(d.getHours()).padStart(2, '0') + ':' + String(d.getMinutes()).padStart(2, '0');
}
const ICONS = {
taskOk: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M22 11.1V12a10 10 0 1 1-5.93-9.14"/><path d="m9 11 3 3L22 4"/></svg>',
taskFail: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="9"/><path d="M15 9l-6 6M9 9l6 6"/></svg>',
taskRun: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="9"/><path d="M12 7v5l3 2"/></svg>',
team: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><circle cx="9" cy="8" r="3.5"/><path d="M3 20c0-3 2.7-5 6-5s6 2 6 5"/><circle cx="17" cy="9" r="2.5"/><path d="M14 20c.5-2.4 2.4-4 5-4"/></svg>',
comment: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M21 11.5a8.4 8.4 0 0 1-8.5 8.5 8.5 8.5 0 0 1-4.5-1.3L3 20l1.3-5a8.5 8.5 0 0 1-1.3-4.5A8.4 8.4 0 0 1 11.5 2 8.5 8.5 0 0 1 21 11.5z"/></svg>',
system: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="9"/><path d="M12 8v4M12 16h.01"/></svg>',
billing: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="6" width="18" height="13" rx="2"/><path d="M3 10h18M16 14h2"/></svg>',
shield: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/><path d="m9 12 2 2 4-4"/></svg>',
pipeline: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="4" width="18" height="16"/><path d="M7 4v16M16 4v16M3 9h18M3 15h18"/></svg>',
};
const MESSAGES = [
// ─ 任务类(5) ─
{
id: 'm-001', type: 'task', icon: 'taskOk', severity: 'ok',
title: '视频已完成 · 补水面膜 · 痛点种草 v3',
body: '7 镜 · 40 秒成片渲染完成,余额已扣除 ¥18.40。点 [✓ 通过] 进入投放阶段,或 [↻ 重跑] 调整脚本。',
ts: _ago(8), from: '系统', tag: '通过', tagKind: 'ok',
project: '补水面膜', stage: 'Stage 5 · 投放',
target: { type: 'project', href: 'pipeline.html?product=' + encodeURIComponent('补水面膜'), name: '补水面膜 · 痛点种草' },
actions: [
{ label: '进入项目', primary: true, action: 'goto' },
{ label: '查看成片', action: 'preview' },
{ label: '再次生成', action: 'rerun' },
],
unread: true,
},
{
id: 'm-002', type: 'task', icon: 'taskOk', severity: 'ok',
title: '4 张模特上身图已生成 · 祛痘精华',
body: '阿楠 · 通勤白领 · 室内自然光 · 3:4。预估 ¥3.20,可在「资产库 / 我的上传」直接调用或加入新项目。',
ts: _ago(42), from: '系统', tag: '通过', tagKind: 'ok',
project: '祛痘精华', stage: '图片生成 · 模特上身图',
target: { type: 'asset-factory', href: 'asset-factory.html', name: '图片生成 · 模特上身图' },
actions: [
{ label: '查看素材', primary: true, action: 'goto' },
{ label: '加入项目', action: 'attach' },
],
unread: true,
},
{
id: 'm-003', type: 'task', icon: 'taskOk', severity: 'ok',
title: '演员「林夕」三视图已就绪',
body: '正/侧/背 三视图生成完成,共 ¥0.30。该演员现已具备多角度一致性,可放心进入视频生成。',
ts: _ago(95), from: '系统', tag: '资产更新', tagKind: 'info',
project: '资产库', stage: '演员库 · 我的上传',
target: { type: 'library', href: 'library.html#person', name: '资产库 / 人物 / 林夕' },
actions: [
{ label: '查看演员', primary: true, action: 'goto' },
{ label: '在项目中使用', action: 'attach' },
],
unread: true,
},
{
id: 'm-004', type: 'task', icon: 'taskFail', severity: 'err',
title: '脚本生成失败 · 618 大促',
body: '上下文超长(prompt > 8k tokens),AI 脚本服务返回 413。系统已自动截断 30% 内容并重试,本次失败不扣费。',
ts: _ago(180), from: '系统', tag: '失败 · 免费重试', tagKind: 'crit',
project: '618 大促', stage: 'Stage 1 · 脚本',
target: { type: 'project', href: 'pipeline.html?product=' + encodeURIComponent('618 大促'), name: '618 大促 · Stage 1' },
actions: [
{ label: '重试生成', primary: true, action: 'rerun' },
{ label: '查看错误日志', action: 'log' },
],
unread: true,
},
{
id: 'm-005', type: 'task', icon: 'taskRun', severity: 'warn',
title: '图片优化任务已超时,自动重跑',
body: '原任务 12:45 提交,> 10 分钟未完成。已按 PRD §10.3 自动重跑,如再次失败将退还本次预估额度。',
ts: _ago(220), from: '系统', tag: '超时 · 已重跑', tagKind: 'warn',
project: '资产库', stage: '图片优化 · 队列',
target: { type: 'queue', href: 'index.html', name: '工作台 · 任务队列' },
actions: [
{ label: '查看队列', primary: true, action: 'goto' },
],
unread: true,
},
// ─ 团队类(4) ─
{
id: 'm-006', type: 'team', icon: 'team',
title: '@王芳 已加入团队 · 角色 运营',
body: '由超管 李 邀请。月限额 ¥800 · 资产可读可写。如需调整权限请前往团队设置。',
ts: _ago(265), from: '@李(超管)', tag: '团队', tagKind: 'info',
project: '团队', stage: '成员管理',
target: { type: 'team', href: 'team.html', name: '团队 / 成员' },
actions: [
{ label: '查看团队', primary: true, action: 'goto' },
{ label: '调整权限', action: 'edit' },
],
unread: true,
},
{
id: 'm-007', type: 'team', icon: 'team',
title: '@张磊 由「运营」升级为「主管」',
body: '权限范围:可管理本人 + 下属团员的项目、资产、额度。变更生效于今天 13:22。',
ts: _ago(310), from: '@李(超管)', tag: '权限变更', tagKind: 'info',
project: '团队', stage: '权限管理',
target: { type: 'team', href: 'team.html', name: '团队 / 角色' },
actions: [
{ label: '查看团队', primary: true, action: 'goto' },
],
unread: false,
},
{
id: 'm-008', type: 'team', icon: 'team',
title: '@李 创建了模特「小七 · 学生女」',
body: '团队资产已同步 · 你可立即在「资产库 / 人物」中调用,免重复生成。',
ts: _ago(420), from: '@李', tag: '资产共用', tagKind: 'ok',
project: '资产库', stage: '人物 · 我的上传',
target: { type: 'library', href: 'library.html#person', name: '资产库 / 人物 / 小七' },
actions: [
{ label: '查看资产', primary: true, action: 'goto' },
],
unread: false,
},
{
id: 'm-009', type: 'team', icon: 'comment',
title: '@刘 在「补水面膜」镜头 3 留下了评论',
body: '"开头节奏可以更紧凑,前 2 秒建议直接进痛点。可以让 AI 把镜头 1 缩到 3 秒吗?"',
ts: _ago(540), from: '@刘(运营)', tag: '评论', tagKind: 'info',
project: '补水面膜', stage: 'Stage 3 · 镜头',
target: { type: 'project', href: 'pipeline.html?product=' + encodeURIComponent('补水面膜') + '#sh3', name: '补水面膜 / 镜头 3' },
actions: [
{ label: '查看镜头', primary: true, action: 'goto' },
{ label: '回复', action: 'reply' },
],
unread: true,
},
// ─ 系统/计费类(3) ─
{
id: 'm-010', type: 'billing', icon: 'billing', severity: 'warn',
title: '团队余额低于 ¥100',
body: '当前余额 ¥87.20,按本周消耗速度估算可支撑 4-5 个视频项目。建议尽早充值以免任务中断。',
ts: _ago(660), from: '系统', tag: '余额预警', tagKind: 'warn',
project: '计费', stage: '余额监控',
target: { type: 'account', href: 'account.html', name: '消费 / 充值' },
actions: [
{ label: '前往充值', primary: true, action: 'goto' },
{ label: '设置预警阈值', action: 'edit' },
],
unread: false,
},
{
id: 'm-011', type: 'system', icon: 'system',
title: '5/25 23:00 - 5/26 01:00 · 视频生成服务例行维护',
body: '本次维护涉及视频生成 + 三视图生成两项服务,期间提交的任务会自动延迟到维护结束后开始处理,已生成任务不受影响。',
ts: _ago(720), from: '流·Studio 运维', tag: '公告', tagKind: 'info',
project: '系统', stage: '维护公告',
target: null,
actions: [
{ label: '我已了解', primary: true, action: 'ack' },
],
unread: false,
},
{
id: 'm-012', type: 'system', icon: 'shield',
title: '「祛痘精华」首版投放素材已通过平台审核',
body: '审核耗时 1h 12min,通过 4 张图 / 2 条视频,可直接进入「投放」阶段。',
ts: _ago(1380), from: '审核中台', tag: '审核通过', tagKind: 'ok',
project: '祛痘精华', stage: 'Stage 5 · 投放',
target: { type: 'project', href: 'pipeline.html?product=' + encodeURIComponent('祛痘精华'), name: '祛痘精华 · 投放' },
actions: [
{ label: '进入投放', primary: true, action: 'goto' },
],
unread: false,
},
];
/* ─── 状态 + 渲染 ─── */
const state = { tab: 'all', onlyUnread: false, q: '', selectedId: null };
function _filter() {
return MESSAGES.filter(m => {
if (state.tab !== 'all' && m.type !== state.tab) return false;
if (state.onlyUnread && !m.unread) return false;
if (state.q) {
const q = state.q.toLowerCase();
const hay = (m.title + ' ' + m.body + ' ' + (m.project || '') + ' ' + (m.from || '')).toLowerCase();
if (!hay.includes(q)) return false;
}
return true;
});
}
function _groupByTs(items) {
const groups = { '今天': [], '昨天': [], '更早': [] };
const today = new Date(NOW.getFullYear(), NOW.getMonth(), NOW.getDate());
items.forEach(m => {
const dDay = new Date(m.ts.getFullYear(), m.ts.getMonth(), m.ts.getDate());
const diff = (today - dDay) / (24 * 3600 * 1000);
if (diff === 0) groups['今天'].push(m);
else if (diff === 1) groups['昨天'].push(m);
else groups['更早'].push(m);
});
return groups;
}
function _renderTabs() {
const counts = { all: 0, task: 0, team: 0, system: 0, billing: 0 };
MESSAGES.forEach(m => {
if (m.unread) {
counts.all++;
if (counts[m.type] !== undefined) counts[m.type]++;
}
});
// 显示总数(不只是未读) — 与 tabs 自身含义一致
const tot = { all: MESSAGES.length, task: 0, team: 0, system: 0, billing: 0 };
MESSAGES.forEach(m => { if (tot[m.type] !== undefined) tot[m.type]++; });
document.querySelectorAll('#ms-tabs .ms-tab').forEach(btn => {
const k = btn.dataset.tab;
const ct = btn.querySelector('[data-ct]');
ct.textContent = tot[k];
btn.classList.toggle('active', k === state.tab);
});
const totalUnread = counts.all;
document.getElementById('ms-head-ct').textContent = '// ' + totalUnread + ' 条未读 · ' + MESSAGES.length + ' 条总计';
}
function _renderList() {
const list = _filter();
const listEl = document.getElementById('ms-list');
document.getElementById('ms-list-ct').textContent = '// 显示 ' + list.length + ' 条';
if (!list.length) {
listEl.innerHTML = `
<div class="ms-list-empty">
<div class="ic"><svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M22 11.1V12a10 10 0 1 1-5.93-9.14"/><path d="m9 11 3 3L22 4"/></svg></div>
<div>没有符合条件的消息</div>
<div class="mono">// 试试切换分类 / 关闭「仅看未读」</div>
</div>`;
return;
}
const groups = _groupByTs(list);
let html = '';
['今天', '昨天', '更早'].forEach(g => {
if (!groups[g].length) return;
html += `<div class="ms-group-h">${g} · ${groups[g].length}</div>`;
html += groups[g].map(m => _itemHtml(m)).join('');
});
listEl.innerHTML = html;
listEl.querySelectorAll('.ms-item').forEach(el => {
el.addEventListener('click', () => _select(el.dataset.id));
});
}
function _itemHtml(m) {
const iconClass = m.severity === 'err' ? 'error' : (m.severity === 'ok' ? 'success' : m.type);
return `
<div class="ms-item ${m.unread ? '' : 'read'} ${state.selectedId === m.id ? 'active' : ''}" data-id="${m.id}">
<div class="ms-item-icon ${iconClass}">${ICONS[m.icon] || ICONS.system}</div>
<div class="ms-item-main">
<div class="ms-item-row1">
<span class="ms-item-unread"></span>
<span class="ms-item-title">${m.title}</span>
<span class="ms-item-ts">${_fmt(m.ts)}</span>
</div>
<div class="ms-item-body">${m.body}</div>
${m.tag ? `<span class="ms-item-tag ${m.tagKind || ''}">${m.tag}</span>` : ''}
</div>
</div>`;
}
function _select(id) {
const m = MESSAGES.find(x => x.id === id);
if (!m) return;
state.selectedId = id;
if (m.unread) {
m.unread = false;
_renderTabs();
_renderList();
} else {
document.querySelectorAll('#ms-list .ms-item').forEach(el => {
el.classList.toggle('active', el.dataset.id === id);
});
}
_renderDetail(m);
}
function _renderDetail(m) {
const pane = document.getElementById('ms-detail-pane');
const propsHtml = [
['来源', m.from || '系统'],
['类别', { task: '任务', team: '团队', system: '系统', billing: '计费' }[m.type] || '系统'],
['项目', m.project || '-'],
['阶段', m.stage || '-'],
['时间', _fmtFull(m.ts)],
...(m.target ? [['关联资源', `<a href="${m.target.href}">${m.target.name} →</a>`]] : []),
].map(([k, v]) => `<div class="k">${k}</div><div class="v">${v}</div>`).join('');
const actionsHtml = (m.actions || []).map(a => {
const cls = a.primary ? 'btn btn-primary' : 'btn';
return `<button class="${cls}" type="button" data-act="${a.action}">${a.label}</button>`;
}).join('');
pane.innerHTML = `
<div class="ms-detail-h">
<div class="row1">
<span class="kind-tag ${m.type}">${ { task: '任务', team: '团队', system: '系统', billing: '计费' }[m.type] || '系统' }</span>
${m.tag ? `<span class="ms-item-tag ${m.tagKind || ''}" style="margin: 0;">${m.tag}</span>` : ''}
<button class="x" type="button" id="ms-detail-close" title="关闭"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"><path d="M6 6l12 12M6 18L18 6"/></svg></button>
</div>
<h2>${m.title}</h2>
<div class="meta">
<span class="from">${m.from || '系统'}</span>
<span class="dot"></span>
<span>${_fmtFull(m.ts)}</span>
</div>
</div>
<div class="ms-detail-b">
<p>${m.body}</p>
${m.quote ? `<div class="quote">${m.quote}</div>` : ''}
<div class="ms-props">${propsHtml}</div>
</div>
<div class="ms-detail-f">
<button class="btn btn-ghost" type="button" id="ms-detail-del">删除</button>
<button class="btn btn-ghost" type="button" id="ms-detail-mute">不再接收同类</button>
<span class="spacer"></span>
${actionsHtml}
</div>`;
document.getElementById('ms-detail-close').addEventListener('click', () => {
state.selectedId = null;
pane.innerHTML = `
<div class="ms-detail-empty">
<div class="ic"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M6 8a6 6 0 0 1 12 0c0 7 3 9 3 9H3s3-2 3-9M10 21a2 2 0 0 0 4 0"/></svg></div>
<div>从左侧选择一条消息查看详情</div>
<div class="mono">// 点击列表项 · 自动标为已读</div>
</div>`;
_renderList();
});
document.getElementById('ms-detail-del').addEventListener('click', () => {
const i = MESSAGES.findIndex(x => x.id === m.id);
if (i >= 0) MESSAGES.splice(i, 1);
state.selectedId = null;
Shell.toast('已删除', m.title);
_renderTabs(); _renderList();
document.getElementById('ms-detail-close').click();
});
document.getElementById('ms-detail-mute').addEventListener('click', () => {
Shell.toast('已静音同类消息', `不再接收「${ { task: '任务', team: '团队', system: '系统', billing: '计费' }[m.type] }」类提醒 · 可在通知设置中恢复`);
});
pane.querySelectorAll('.ms-detail-f .btn-primary, .ms-detail-f [data-act]').forEach(btn => {
btn.addEventListener('click', e => {
const act = btn.dataset.act;
if (act === 'goto' && m.target?.href) { location.href = m.target.href; return; }
if (act === 'rerun') { Shell.toast('已发起重试', m.project + ' · 任务已重新排队'); return; }
if (act === 'preview') { Shell.toast('打开预览', m.project + ' · 视频成片'); return; }
if (act === 'attach') { Shell.toast('已加入项目', '请到工作台选择目标项目'); return; }
if (act === 'log') { Shell.toast('错误日志', 'prompt=tokens 8124 / max=8000 · type=413'); return; }
if (act === 'reply') { Shell.toast('打开评论', '可在镜头详情页直接回复'); return; }
if (act === 'ack') { Shell.toast('已确认'); return; }
if (act === 'edit') { Shell.toast('打开设置', '权限 / 阈值 · 可在设置中调整'); return; }
});
});
}
/* ─── 事件绑定 ─── */
document.querySelectorAll('#ms-tabs .ms-tab').forEach(btn => {
btn.addEventListener('click', () => {
state.tab = btn.dataset.tab;
_renderTabs(); _renderList();
});
});
const ouBtn = document.getElementById('ms-only-unread');
ouBtn.addEventListener('click', () => {
state.onlyUnread = !state.onlyUnread;
ouBtn.classList.toggle('on', state.onlyUnread);
_renderList();
});
document.getElementById('ms-search-input').addEventListener('input', e => {
state.q = e.target.value.trim();
_renderList();
});
document.getElementById('ms-mark-all').addEventListener('click', () => {
MESSAGES.forEach(m => { m.unread = false; });
Shell.toast('已全部标为已读', MESSAGES.length + ' 条');
_renderTabs(); _renderList();
});
document.getElementById('ms-settings').addEventListener('click', () => {
location.href = 'settings.html';
});
document.getElementById('ms-group-toggle').addEventListener('click', () => {
Shell.toast('排序', '当前按时间分组 · 后续支持按项目分组');
});
/* 首次渲染 */
_renderTabs();
_renderList();
</script>
</body>
</html>

View File

@ -386,12 +386,12 @@
<div class="ti-row">
<span class="ti">商品空间</span>
<button class="add" type="button" title="新建商品">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 5v14M5 12h14"/></svg>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M12 5v14M5 12h14"/></svg>
</button>
</div>
<!-- Q1-A · 搜索框 -->
<div class="dma-search">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6"><circle cx="11" cy="11" r="7"/><path d="m21 21-4.3-4.3"/></svg>
<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 type="text" placeholder="搜索商品 / 分类">
</div>
</div>
@ -444,10 +444,10 @@
<!-- Q1-B · 全部商品入口(贴底) -->
<button class="dma-all" type="button">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="4" width="18" height="16" rx="2"/><path d="M3 9h18M9 4v16"/></svg>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="4" width="18" height="16" rx="2"/><path d="M3 9h18M9 4v16"/></svg>
全部商品
<span class="ct">24 个</span>
<svg style="margin-left:4px" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14M12 5l7 7-7 7"/></svg>
<svg style="margin-left:4px" 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>
</button>
</aside>
@ -538,7 +538,7 @@
<span>余额 ¥327.40</span>
</div>
<button class="dma-gen" type="button">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" 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>
<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>
立即生成 · 透真补水面膜 × Ava
</button>
</div>
@ -560,9 +560,9 @@
<div class="info">透真补水面膜 <span class="sep">·</span> 3:4 <span class="sep">·</span> 3 分钟前 <span class="sep">·</span> ¥1.20</div>
</div>
<div class="ops">
<button 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="M21 12a9 9 0 11-3-6.7L21 8M21 3v5h-5"/></svg></button>
<button 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="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4M7 10l5 5 5-5M12 15V3"/></svg></button>
<button 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="M19 21l-7-5-7 5V5a2 2 0 012-2h10a2 2 0 012 2z"/></svg></button>
<button type="button" title="全部重跑"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12a9 9 0 11-3-6.7L21 8M21 3v5h-5"/></svg></button>
<button type="button" title="全部下载"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4M7 10l5 5 5-5M12 15V3"/></svg></button>
<button type="button" title="加入资产库"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M19 21l-7-5-7 5V5a2 2 0 012-2h10a2 2 0 012 2z"/></svg></button>
</div>
</div>
<div class="dma-batch-grid">
@ -582,9 +582,9 @@
<div class="info">透真补水面膜 <span class="sep">·</span> 3:4 <span class="sep">·</span> 12 分钟前 <span class="sep">·</span> ¥1.20</div>
</div>
<div class="ops">
<button 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="M21 12a9 9 0 11-3-6.7L21 8M21 3v5h-5"/></svg></button>
<button 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="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4M7 10l5 5 5-5M12 15V3"/></svg></button>
<button 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="M19 21l-7-5-7 5V5a2 2 0 012-2h10a2 2 0 012 2z"/></svg></button>
<button type="button" title="全部重跑"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12a9 9 0 11-3-6.7L21 8M21 3v5h-5"/></svg></button>
<button type="button" title="全部下载"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4M7 10l5 5 5-5M12 15V3"/></svg></button>
<button type="button" title="加入资产库"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M19 21l-7-5-7 5V5a2 2 0 012-2h10a2 2 0 012 2z"/></svg></button>
</div>
</div>
<div class="dma-batch-grid">
@ -604,7 +604,7 @@
<div class="info">透真补水面膜 <span class="sep">·</span> 3:4 <span class="sep">·</span> 刚刚 <span class="sep">·</span> 生成中</div>
</div>
<div class="ops">
<button 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="M6 6l12 12M6 18L18 6"/></svg></button>
<button type="button" title="取消"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M6 6l12 12M6 18L18 6"/></svg></button>
</div>
</div>
<div class="dma-batch-grid">

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@ -890,28 +890,51 @@
color: var(--accent-crimson, #c43d3d);
background: rgba(196, 61, 61, .05);
}
/* 右上 hover 操作组 */
/* 右上 hover 操作组 · 同 spec §4.18 .gen-image-actions */
.pv-platform-section .ps-grid .mp-result .cell-ops {
position: absolute; top: 6px; right: 6px;
display: flex; gap: 4px;
position: absolute; top: 8px; right: 8px;
display: flex; gap: 2px;
padding: 2px;
background: var(--surface);
border: 1px solid var(--border-faint);
border-radius: var(--r-md);
box-shadow: 0 2px 8px rgba(0,0,0,.08);
opacity: 0;
transition: opacity var(--t-base);
z-index: 2;
}
.pv-platform-section .ps-grid .mp-result:hover .cell-ops { opacity: 1; }
.pv-platform-section .ps-grid .mp-result .cell-ops button {
width: 26px; height: 26px;
background: rgba(255, 255, 255, .92);
border: 1px solid var(--border-faint);
border-radius: var(--r-sm);
color: var(--accent-black);
width: 28px; height: 28px;
background: transparent;
border: 0;
border-radius: 6px;
color: var(--black-alpha-56);
cursor: pointer;
display: grid; place-items: center;
backdrop-filter: blur(4px);
transition: border-color var(--t-base), color var(--t-base);
transition: background var(--t-base), color var(--t-base);
}
.pv-platform-section .ps-grid .mp-result .cell-ops button:hover { border-color: var(--heat); color: var(--heat); }
.pv-platform-section .ps-grid .mp-result .cell-ops button svg { width: 12px; height: 12px; }
.pv-platform-section .ps-grid .mp-result .cell-ops button:hover {
background: var(--black-alpha-4);
color: var(--accent-black);
}
.pv-platform-section .ps-grid .mp-result .cell-ops button svg { width: 14px; height: 14px; }
.pv-platform-section .ps-grid .mp-result.adopted .cell-ops .r-check { display: none; }
/* 中央反馈 toast · 同 spec §4.18 .gen-image-feedback */
.pv-platform-section .ps-grid .mp-result .cell-feedback {
position: absolute; inset: 0;
display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 8px;
background: rgba(38, 38, 38, .88);
color: var(--accent-white);
border-radius: var(--r-md);
opacity: 0;
pointer-events: none;
transition: opacity .2s var(--t-base, ease);
z-index: 3;
}
.pv-platform-section .ps-grid .mp-result.show-feedback .cell-feedback { opacity: 1; }
.pv-platform-section .ps-grid .mp-result .cell-feedback svg { width: 20px; height: 20px; }
.pv-platform-section .ps-grid .mp-result .cell-feedback span { font-size: 12.5px; font-weight: 500; letter-spacing: .02em; }
.pv-platform-section .ps-grid .mp-result .cell-more-wrap { position: relative; }
.pv-platform-section .ps-grid .mp-result .cell-more-menu {
position: absolute; top: calc(100% + 4px); right: 0;
@ -1008,25 +1031,24 @@
color: var(--black-alpha-48); letter-spacing: .02em;
}
.pc-pv-batch .summary b { color: var(--heat); font-weight: 700; }
/* 底栏按钮 · 同 spec §4.18 + image-optimize .io-msg-ops button */
.pc-pv-batch .pill-btn {
height: 34px;
padding: 0 18px;
height: 30px;
padding: 0 12px;
display: inline-flex; align-items: center; gap: 6px;
background: var(--surface);
border: 1px solid var(--black-alpha-12);
border-radius: var(--r-pill);
border: 1px solid var(--border-faint);
border-radius: var(--r-md);
color: var(--accent-black);
font-family: inherit; font-size: 12.5px;
cursor: pointer;
transition: background var(--t-base), border-color var(--t-base), color var(--t-base);
}
.pc-pv-batch .pill-btn:hover { background: var(--background-lighter); border-color: var(--black-alpha-24); }
.pc-pv-batch .pill-btn.primary {
background: var(--heat); color: #fff; border-color: var(--heat);
.pc-pv-batch .pill-btn:hover {
border-color: var(--heat-20); color: var(--heat); background: var(--heat-12);
}
.pc-pv-batch .pill-btn.primary:hover { filter: brightness(.94); }
.pc-pv-batch .pill-btn svg { width: 13px; height: 13px; }
.pc-pv-batch .pill-btn.icon { width: 34px; padding: 0; justify-content: center; }
.pc-pv-batch .pill-btn.icon { width: 30px; padding: 0; justify-content: center; }
/* 批次更多气泡 */
.pc-pv-batch .batch-more-wrap { position: relative; display: inline-flex; }
.pc-pv-batch .batch-more-menu {
@ -1102,16 +1124,16 @@
<!-- 顶部 · 返回 + 折叠 (跟图片创作风格一致) -->
<div class="pc-side-top">
<button class="back-pill" type="button" onclick="history.length > 1 ? history.back() : location.href='asset-factory.html'" title="返回">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M15 18l-6-6 6-6"/></svg>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M15 18l-6-6 6-6"/></svg>
<span>返回</span>
</button>
<button class="fold" type="button" title="折叠侧栏" style="margin-left:auto">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2"/><path d="M9 3v18"/></svg>
<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="18" height="18" rx="2"/><path d="M9 3v18"/></svg>
</button>
</div>
<div class="pc-ps-h">
<div class="pc-ps-search">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6"><circle cx="11" cy="11" r="7"/><path d="m21 21-4.3-4.3"/></svg>
<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 type="text" id="ps-search-input" placeholder="搜索商品 / 分类">
</div>
</div>
@ -1125,10 +1147,10 @@
</div>
<div class="pc-ps-list" id="ps-list"></div>
<button class="pc-ps-all" type="button" id="ps-all-btn">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="4" width="18" height="16" rx="2"/><path d="M3 9h18M9 4v16"/></svg>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="4" width="18" height="16" rx="2"/><path d="M3 9h18M9 4v16"/></svg>
<span>全部商品</span>
<span class="ct" id="ps-all-ct">0 个</span>
<svg style="margin-left:0" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14M12 5l7 7-7 7"/></svg>
<svg style="margin-left:0" 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>
</button>
</aside>
@ -1144,7 +1166,7 @@
<span class="spacer"></span>
<div class="tb-search-wrap" id="pc-search-wrap">
<button class="search-btn" type="button" title="搜索批次/平台" id="pc-search-toggle">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7"><circle cx="11" cy="11" r="7"/><path d="m21 21-4.3-4.3"/></svg>
<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>
</button>
<input type="text" class="tb-search-input" id="pc-search-input" placeholder="搜索批次/平台…" autocomplete="off">
</div>
@ -1373,11 +1395,11 @@
<div class="pl-main">
<div class="pl-toolbar">
<div class="search">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6"><circle cx="11" cy="11" r="7"/><path d="m21 21-4.3-4.3"/></svg>
<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 type="text" id="pl-search-input" placeholder="搜索商品名">
</div>
<button class="btn-new" type="button" id="pl-new-btn">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><path d="M12 5v14M5 12h14"/></svg>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M12 5v14M5 12h14"/></svg>
新建商品
</button>
</div>
@ -1518,10 +1540,10 @@ function renderProdLib() {
<div class="pl-card${_plDraft === p.id ? ' selected' : ''}" data-id="${p.id}">
<div class="pl-card-actions">
<button class="pl-act" type="button" data-edit="${p.id}" title="编辑商品" aria-label="编辑">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><path d="M12 20h9"/><path d="M16.5 3.5a2.121 2.121 0 013 3L7 19l-4 1 1-4 12.5-12.5z"/></svg>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M12 20h9"/><path d="M16.5 3.5a2.121 2.121 0 013 3L7 19l-4 1 1-4 12.5-12.5z"/></svg>
</button>
<button class="pl-act danger" type="button" data-del="${p.id}" title="删除商品" aria-label="删除">
<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>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" 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>
<div class="pl-check"><svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="3 8 7 12 13 4"/></svg></div>
@ -1584,9 +1606,9 @@ function renderProdImgs(n) {
if (ct) ct.textContent = n;
let html = '';
for (let i = 0; i < n; i++) {
html += `<div class="thumb"><span class="ph-frame">1:1</span><button class="rm" type="button" title="删除" data-idx="${i}"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M6 6l12 12M6 18L18 6"/></svg></button></div>`;
html += `<div class="thumb"><span class="ph-frame">1:1</span><button class="rm" type="button" title="删除" data-idx="${i}"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"><path d="M6 6l12 12M6 18L18 6"/></svg></button></div>`;
}
html += `<div class="img-upload" id="pcf-img-add" title="上传图片"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M12 5v14M5 12h14"/></svg></div>`;
html += `<div class="img-upload" id="pcf-img-add" title="上传图片"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M12 5v14M5 12h14"/></svg></div>`;
grid.innerHTML = html;
grid.querySelectorAll('.thumb .rm').forEach(btn => {
btn.addEventListener('click', () => {
@ -1621,7 +1643,7 @@ function openEditProductDrawer(id) {
li.innerHTML = `
<span class="num">${i + 1}</span>
<input value="${b.replace(/"/g, '&quot;')}">
<button class="rm" type="button" aria-label="删除"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M6 6l12 12M6 18L18 6"/></svg></button>
<button class="rm" type="button" aria-label="删除"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M6 6l12 12M6 18L18 6"/></svg></button>
`;
ul.insertBefore(li, addLi);
li.querySelector('.rm').addEventListener('click', () => { li.remove(); renumberBullets(); });
@ -1654,7 +1676,7 @@ document.getElementById('pcf-add-input').addEventListener('keydown', e => {
li.innerHTML = `
<span class="num">0</span>
<input value="${v.replace(/"/g, '&quot;')}">
<button class="rm" type="button" aria-label="删除"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M6 6l12 12M6 18L18 6"/></svg></button>
<button class="rm" type="button" aria-label="删除"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M6 6l12 12M6 18L18 6"/></svg></button>
`;
ul.insertBefore(li, addLi);
li.querySelector('.rm').addEventListener('click', () => { li.remove(); renumberBullets(); });
@ -1855,13 +1877,14 @@ function renderPreviewSections() {
}
// 渲染生成结果 (点立即生成时调,带 hover overlay + 批量 bar)
const RERUN_SVG = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><polyline points="23 4 23 10 17 10"/><polyline points="1 20 1 14 7 14"/><path d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15"/></svg>';
const RERUN_SVG = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="23 4 23 10 17 10"/><polyline points="1 20 1 14 7 14"/><path d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15"/></svg>';
const ADOPT_SVG = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>';
const CELL_RERUN_SVG = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12a9 9 0 11-3-6.7L21 8M21 3v5h-5"/></svg>';
const CELL_DL_SVG = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4M7 10l5 5 5-5M12 15V3"/></svg>';
const CELL_MORE_SVG = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="5" cy="12" r="1.2"/><circle cx="12" cy="12" r="1.2"/><circle cx="19" cy="12" r="1.2"/></svg>';
const CELL_ADOPT_SVG = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><path d="M19 21l-7-5-7 5V5a2 2 0 012-2h10a2 2 0 012 2z"/></svg>';
const CELL_DEL_SVG = '<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>';
const CELL_RERUN_SVG = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12a9 9 0 11-3-6.7L21 8M21 3v5h-5"/></svg>';
const CELL_DL_SVG = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4M7 10l5 5 5-5M12 15V3"/></svg>';
const CELL_MORE_SVG = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><circle cx="5" cy="12" r="1.2"/><circle cx="12" cy="12" r="1.2"/><circle cx="19" cy="12" r="1.2"/></svg>';
const CELL_ADOPT_SVG = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M19 21l-7-5-7 5V5a2 2 0 012-2h10a2 2 0 012 2z"/></svg>';
const CELL_DEL_SVG = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" 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>';
const CELL_EDIT_SVG = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M12 20h9"/><path d="M16.5 3.5a2.121 2.121 0 113 3L7 19l-4 1 1-4z"/></svg>';
let _batchSeq = 0;
function appendBatch(n, kind) {
@ -1893,6 +1916,10 @@ function appendBatch(n, kind) {
<div class="mp-result gen" data-idx="${i}">
<div class="mp-r-thumb"><span class="ph-frame">${c.dataset.name}</span></div>
<span class="adopt-badge">已采用</span>
<div class="cell-feedback" aria-hidden="true">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>
<span>已采用</span>
</div>
<div class="cell-ops">
<button class="r-rerun" type="button" title="再次生成" aria-label="再次生成">${CELL_RERUN_SVG}</button>
<button class="r-dl" type="button" title="下载" aria-label="下载">${CELL_DL_SVG}</button>
@ -1908,14 +1935,14 @@ function appendBatch(n, kind) {
`).join('')}
</div>
<div class="pc-pv-batch batch-foot">
<button class="pill-btn edit-batch" type="button" title="重新编辑">
${CELL_EDIT_SVG}
<span>重新编辑</span>
</button>
<button class="pill-btn rerun-batch" type="button" title="再次生成这一批">
${CELL_RERUN_SVG}
<span>再次生成</span>
</button>
<button class="pill-btn primary adopt-batch" type="button" title="采用这一批">
${ADOPT_SVG}
<span>全部采用 · <span class="adopted">0</span>/<span class="total">${n}</span></span>
</button>
<div class="batch-more-wrap">
<button class="pill-btn icon batch-more" type="button" title="更多" aria-label="更多">${CELL_MORE_SVG}</button>
<div class="batch-more-menu" role="menu">
@ -1962,17 +1989,22 @@ function appendBatch(n, kind) {
else updateBatchSummary();
Shell.toast('已删除');
}));
section.querySelector('.edit-batch').addEventListener('click', () => {
const form = document.querySelector('.pc-form');
if (form) form.scrollIntoView({ behavior: 'smooth', block: 'start' });
Shell.toast('重新编辑', '请在左侧调整平台 / 张数后再生成');
});
section.querySelector('.rerun-batch').addEventListener('click', () => {
appendBatch(n, 'rerun-all');
Shell.toast('再次生成', n + ' 张图重新生成中 · 新批次已追加');
});
section.querySelector('.adopt-batch').addEventListener('click', () => {
const _adoptAll = () => {
const cards = section.querySelectorAll('.mp-result:not(.adopted)');
if (!cards.length) { Shell.toast('该批次已全部采用'); return; }
cards.forEach(c => { c.classList.remove('gen'); c.classList.add('adopted'); });
updateBatchSummary();
Shell.toast('已全部采用', cards.length + ' 张图入对应商品的 AI 素材 · 扣 ¥' + (cards.length * UNIT_PRICE).toFixed(2));
});
Shell.toast('已全部加入资产库', cards.length + ' 张图入对应商品的 AI 素材 · 扣 ¥' + (cards.length * UNIT_PRICE).toFixed(2));
};
// 批次「更多」按钮 → 开/合 menu
const _bMoreBtn = section.querySelector('.batch-more');
const _bMoreWrap = section.querySelector('.batch-more-wrap');
@ -1987,7 +2019,7 @@ function appendBatch(n, kind) {
section.querySelector('.batch-save-all').addEventListener('click', e => {
e.stopPropagation();
if (_bMoreWrap) _bMoreWrap.classList.remove('open');
section.querySelector('.adopt-batch').click();
_adoptAll();
});
section.querySelector('.batch-del').addEventListener('click', e => {
e.stopPropagation();
@ -2016,7 +2048,9 @@ function adoptOne(card) {
if (!card || card.classList.contains('adopted')) return;
card.classList.remove('gen');
card.classList.add('adopted');
Shell.toast('已加入资产库', '入对应商品的 AI 素材 · 扣 ¥' + UNIT_PRICE.toFixed(2));
// spec §4.18 · 就地中央反馈
card.classList.add('show-feedback');
setTimeout(() => card.classList.remove('show-feedback'), 1500);
updateBatchSummary();
}
// 点击页面其它位置 → 关闭单图/批次 more menu

View File

@ -306,7 +306,7 @@ function renderPhotos() {
<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>
<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) {

View File

@ -442,9 +442,13 @@
}
.ov-images-sub .thumb img { width: 100%; height: 100%; object-fit: cover; }
/* 快速操作 · 2 段:图片生成(3 等比 CTA)+ 视频生成(1 CTA) */
/* 快速操作 · 2 段:图片生成(3 等比 CTA)+ 视频生成(1 CTA) · 两段等高填充容器 */
.ov-actions { display: flex; flex-direction: column; }
.ov-actions .qa-section { margin-bottom: 14px; }
.ov-actions .qa-section {
margin-bottom: 14px;
display: flex; flex-direction: column;
flex: 1 1 0; min-height: 0;
}
.ov-actions .qa-section:last-child { margin-bottom: 0; }
.ov-actions .qa-section-h {
font-family: var(--font-mono);
@ -458,10 +462,16 @@
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 8px;
flex: 1 1 0; min-height: 0;
}
.ov-actions .qa-row-1 {
display: flex;
flex: 1 1 0; min-height: 0;
}
.ov-actions .qa-row-1 .qa-item { width: 100%; }
.qa-item {
display: flex; flex-direction: column; align-items: center; gap: 8px;
display: flex; flex-direction: column;
align-items: center; justify-content: center; gap: 8px;
padding: 14px 10px;
background: var(--background-lighter);
border: 1px solid var(--border-faint);
@ -925,7 +935,7 @@
<!-- AI 生成三视图 · 按钮 + 弹出 panel(view 模式可见) -->
<div class="ov-tri-wrap">
<button class="ov-edit ov-tri-trigger" type="button" id="ov-tri-btn" title="AI 生成商品三视图" aria-haspopup="dialog" aria-expanded="false">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><path d="M12 3l1.8 4.2L18 9l-4.2 1.8L12 15l-1.8-4.2L6 9l4.2-1.8L12 3z"/><path d="M19 14l.9 2.1L22 17l-2.1.9L19 20l-.9-2.1L16 17l2.1-.9L19 14z"/></svg>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M12 3l1.8 4.2L18 9l-4.2 1.8L12 15l-1.8-4.2L6 9l4.2-1.8L12 3z"/><path d="M19 14l.9 2.1L22 17l-2.1.9L19 20l-.9-2.1L16 17l2.1-.9L19 14z"/></svg>
AI 生成三视图
</button>
<div class="ov-tri-pop" id="ov-tri-pop" role="dialog" aria-label="AI 生成三视图">
@ -950,13 +960,13 @@
</div>
<!-- view 模式: 单个 [编辑信息] -->
<button class="ov-edit ov-edit-single" type="button" id="ov-edit-btn" title="编辑商品信息">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><path d="M12 20h9"/><path d="M16.5 3.5a2.12 2.12 0 013 3L7 19l-4 1 1-4L16.5 3.5z"/></svg>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M12 20h9"/><path d="M16.5 3.5a2.12 2.12 0 013 3L7 19l-4 1 1-4L16.5 3.5z"/></svg>
编辑信息
</button>
<!-- edit 模式: [重置] [取消] [保存] -->
<div class="ov-edit-group">
<button class="ov-edit" type="button" id="ov-reset-btn" title="重置为修改前">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><path d="M3 12a9 9 0 1 0 3-6.7"/><path d="M3 4v5h5"/></svg>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M3 12a9 9 0 1 0 3-6.7"/><path d="M3 4v5h5"/></svg>
重置
</button>
<button class="ov-edit" type="button" id="ov-cancel-btn">取消</button>
@ -1043,15 +1053,15 @@
<div class="qa-section-h">// 图片生成</div>
<div class="qa-row-3">
<div class="qa-item" data-go="model-photo" role="button" tabindex="0">
<span class="ic"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="8" r="4"/><path d="M4 21v-2a4 4 0 014-4h8a4 4 0 014 4v2"/></svg></span>
<span class="ic"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="8" r="4"/><path d="M4 21v-2a4 4 0 014-4h8a4 4 0 014 4v2"/></svg></span>
模特上身图
</div>
<div class="qa-item" data-go="platform-cover" role="button" tabindex="0">
<span class="ic"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2"/><path d="M3 9h18M9 3v18"/></svg></span>
<span class="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="18" height="18" rx="2"/><path d="M3 9h18M9 3v18"/></svg></span>
平台套图
</div>
<div class="qa-item" data-go="image-optimize" role="button" tabindex="0">
<span class="ic"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M12 3v18M3 12h18M5 5l14 14M5 19l14-14"/></svg></span>
<span class="ic"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M12 3v18M3 12h18M5 5l14 14M5 19l14-14"/></svg></span>
图片创作
</div>
</div>
@ -1060,7 +1070,7 @@
<div class="qa-section-h">// 视频生成</div>
<div class="qa-row-1">
<div class="qa-item primary" data-go="projects-new" role="button" tabindex="0">
<span class="ic"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="6" width="14" height="12" rx="2"/><path d="M16 10l6-3v10l-6-3z"/></svg></span>
<span class="ic"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="6" width="14" height="12" rx="2"/><path d="M16 10l6-3v10l-6-3z"/></svg></span>
生成视频
</div>
</div>
@ -1092,10 +1102,10 @@
<div class="right">
<div class="view-tog">
<button type="button" class="active" title="网格视图">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6"><rect x="3" y="3" width="7" height="7"/><rect x="14" y="3" width="7" height="7"/><rect x="3" y="14" width="7" height="7"/><rect x="14" y="14" width="7" height="7"/></svg>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="3" y="3" width="7" height="7"/><rect x="14" y="3" width="7" height="7"/><rect x="3" y="14" width="7" height="7"/><rect x="14" y="14" width="7" height="7"/></svg>
</button>
<button type="button" title="列表视图">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6"><path d="M3 6h18M3 12h18M3 18h18"/></svg>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M3 6h18M3 12h18M3 18h18"/></svg>
</button>
</div>
<button class="filter" type="button" data-key="sort">
@ -1944,7 +1954,7 @@
: `${ver.label} · 预览中(未采用)`;
foot.innerHTML = `
<button class="ov-edit" type="button" id="ov-tri-rerun" style="height:28px;">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><path d="M3 12a9 9 0 1 0 3-6.7"/><path d="M3 4v5h5"/></svg>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M3 12a9 9 0 1 0 3-6.7"/><path d="M3 4v5h5"/></svg>
重跑
</button>
<button class="ov-edit ${isAdopted ? '' : 'primary'}" type="button" id="ov-tri-adopt" style="height:28px;" ${isAdopted ? 'disabled title="此版本已采用"' : 'title="将此版本设为唯一通过版本,其他版本变为不通过"'}>

View File

@ -520,7 +520,7 @@
<div class="name">透真玻尿酸补水面膜</div>
<div class="meta">[ 美妆个护 ] · ¥39.9 · 22-32 岁女性</div>
</div>
<button class="edit" onclick="alert('行内编辑商品信息 · demo')">
<button class="edit" onclick="Shell.toast('打开编辑面板','跳转至 product-detail.html · 行内编辑商品信息');setTimeout(()=>location.href='product-detail.html',600);">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/></svg>
编辑
</button>
@ -549,7 +549,7 @@
</div>
<div class="ov-photo" style="background-image: linear-gradient(135deg,#dceafe 0%, #c3e0fe 100%);" title="包装"></div>
<div class="ov-photo" style="background-image: linear-gradient(135deg,#e7e3d5 0%, #d6d1c0 100%);" title="质地"></div>
<div class="ov-photo add" onclick="alert('上传新图 · demo')" title="添加">
<div class="ov-photo add" onclick="document.getElementById('__ov-photo-input').click()" title="添加"><input type="file" id="__ov-photo-input" accept="image/*" multiple hidden onchange="if(this.files.length)Shell.toast('已上传 '+this.files.length+' 张','补充图已加入商品图册')">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M12 5v14M5 12h14"/></svg>
</div>
</div>
@ -863,7 +863,7 @@ function renderAssets() {
</div>
${a.skeleton ? '' : `
<div class="a-actions">
<button title="预览" onclick="event.stopPropagation();alert('预览 · demo')">
<button title="预览" onclick="event.stopPropagation();(window.Shell&&Shell._openLightbox?Shell._openLightbox('','${a.id} · 预览'):Shell.toast('打开预览','${a.id}'))">
<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>
</button>
<button title="重跑" onclick="event.stopPropagation();regen('${a.id}')">

View File

@ -664,10 +664,10 @@
<span>// 显示 <span class="count">7</span> / 7 个商品</span>
<div class="view-tog" style="margin-left:auto" id="view-tog">
<button type="button" class="active" data-view="grid" title="网格视图">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7"><rect x="3" y="3" width="7" height="7"/><rect x="14" y="3" width="7" height="7"/><rect x="3" y="14" width="7" height="7"/><rect x="14" y="14" width="7" height="7"/></svg>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="3" y="3" width="7" height="7"/><rect x="14" y="3" width="7" height="7"/><rect x="3" y="14" width="7" height="7"/><rect x="14" y="14" width="7" height="7"/></svg>
</button>
<button type="button" data-view="list" title="列表视图">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7"><path d="M3 6h18M3 12h18M3 18h18"/></svg>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M3 6h18M3 12h18M3 18h18"/></svg>
</button>
</div>
</div>
@ -677,7 +677,7 @@
<div class="product-grid" id="product-grid">
<div class="product-card" data-cat="美妆个护" data-name="透真玻尿酸补水面膜" data-tags="熬夜党 敏感肌" data-added="1" data-assets="124" data-videos="36" data-date="2026-05-15" onclick="location.href='product-detail.html?t='+Date.now()">
<span class="card-check" aria-hidden="true"><svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="3 8 7 12 13 4"/></svg></span>
<button class="card-del-btn" type="button" title="删除商品" onclick="event.stopPropagation();" data-action="delete-product"><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>
<button class="card-del-btn" type="button" title="删除商品" onclick="event.stopPropagation();" data-action="delete-product"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" 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 product-thumb"><span class="ph-frame">补水面膜 · 1200×800</span></div>
<div class="product-body">
<div class="product-name">透真玻尿酸补水面膜</div>
@ -686,12 +686,12 @@
</div>
<div class="product-footer">
<span class="stat">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2"/><circle cx="9" cy="10" r="2"/><path d="M21 17l-5-5-9 9"/></svg>
<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="18" height="18" rx="2"/><circle cx="9" cy="10" r="2"/><path d="M21 17l-5-5-9 9"/></svg>
素材 <b>124</b>
</span>
<span class="sep">·</span>
<span class="stat">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="6" width="14" height="12" rx="2"/><path d="M16 10l6-3v10l-6-3z"/></svg>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="6" width="14" height="12" rx="2"/><path d="M16 10l6-3v10l-6-3z"/></svg>
视频 <b>36</b>
</span>
@ -700,7 +700,7 @@
<div class="product-card" data-cat="数码 3C" data-name="南卡 Lite Pro 蓝牙耳机" data-tags="通勤 运动" data-added="2" data-assets="96" data-videos="28" data-date="2026-05-12" onclick="location.href='product-detail.html?t='+Date.now()">
<span class="card-check" aria-hidden="true"><svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="3 8 7 12 13 4"/></svg></span>
<button class="card-del-btn" type="button" title="删除商品" onclick="event.stopPropagation();" data-action="delete-product"><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>
<button class="card-del-btn" type="button" title="删除商品" onclick="event.stopPropagation();" data-action="delete-product"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" 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 product-thumb"><span class="ph-frame">蓝牙耳机 · 1200×800</span></div>
<div class="product-body">
<div class="product-name">南卡 Lite Pro 蓝牙耳机</div>
@ -709,12 +709,12 @@
</div>
<div class="product-footer">
<span class="stat">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2"/><circle cx="9" cy="10" r="2"/><path d="M21 17l-5-5-9 9"/></svg>
<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="18" height="18" rx="2"/><circle cx="9" cy="10" r="2"/><path d="M21 17l-5-5-9 9"/></svg>
素材 <b>96</b>
</span>
<span class="sep">·</span>
<span class="stat">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="6" width="14" height="12" rx="2"/><path d="M16 10l6-3v10l-6-3z"/></svg>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="6" width="14" height="12" rx="2"/><path d="M16 10l6-3v10l-6-3z"/></svg>
视频 <b>28</b>
</span>
@ -723,7 +723,7 @@
<div class="product-card" data-cat="食品饮料" data-name="滋啦速食牛肉面 6 桶装" data-tags="加班 独居" data-added="3" data-assets="96" data-videos="24" data-date="2026-05-10" onclick="location.href='product-detail.html?t='+Date.now()">
<span class="card-check" aria-hidden="true"><svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="3 8 7 12 13 4"/></svg></span>
<button class="card-del-btn" type="button" title="删除商品" onclick="event.stopPropagation();" data-action="delete-product"><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>
<button class="card-del-btn" type="button" title="删除商品" onclick="event.stopPropagation();" data-action="delete-product"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" 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 product-thumb"><span class="ph-frame">速食牛肉面 · 1200×800</span></div>
<div class="product-body">
<div class="product-name">滋啦速食牛肉面 · 6 桶装</div>
@ -732,12 +732,12 @@
</div>
<div class="product-footer">
<span class="stat">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2"/><circle cx="9" cy="10" r="2"/><path d="M21 17l-5-5-9 9"/></svg>
<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="18" height="18" rx="2"/><circle cx="9" cy="10" r="2"/><path d="M21 17l-5-5-9 9"/></svg>
素材 <b>96</b>
</span>
<span class="sep">·</span>
<span class="stat">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="6" width="14" height="12" rx="2"/><path d="M16 10l6-3v10l-6-3z"/></svg>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="6" width="14" height="12" rx="2"/><path d="M16 10l6-3v10l-6-3z"/></svg>
视频 <b>24</b>
</span>
@ -746,7 +746,7 @@
<div class="product-card" data-cat="美妆个护" data-name="透真清透物理防晒霜" data-tags="SPF50 通勤" data-added="4" data-assets="76" data-videos="18" data-date="2026-05-08" onclick="location.href='product-detail.html?t='+Date.now()">
<span class="card-check" aria-hidden="true"><svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="3 8 7 12 13 4"/></svg></span>
<button class="card-del-btn" type="button" title="删除商品" onclick="event.stopPropagation();" data-action="delete-product"><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>
<button class="card-del-btn" type="button" title="删除商品" onclick="event.stopPropagation();" data-action="delete-product"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" 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 product-thumb"><span class="ph-frame">防晒霜 · 1200×800</span></div>
<div class="product-body">
<div class="product-name">透真清透物理防晒霜</div>
@ -755,12 +755,12 @@
</div>
<div class="product-footer">
<span class="stat">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2"/><circle cx="9" cy="10" r="2"/><path d="M21 17l-5-5-9 9"/></svg>
<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="18" height="18" rx="2"/><circle cx="9" cy="10" r="2"/><path d="M21 17l-5-5-9 9"/></svg>
素材 <b>76</b>
</span>
<span class="sep">·</span>
<span class="stat">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="6" width="14" height="12" rx="2"/><path d="M16 10l6-3v10l-6-3z"/></svg>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="6" width="14" height="12" rx="2"/><path d="M16 10l6-3v10l-6-3z"/></svg>
视频 <b>18</b>
</span>
@ -769,7 +769,7 @@
<div class="product-card" data-cat="食品饮料" data-name="三顿半同款冻干咖啡粉" data-tags="提神 早八" data-added="5" data-assets="68" data-videos="21" data-date="2026-05-05" onclick="location.href='product-detail.html?t='+Date.now()">
<span class="card-check" aria-hidden="true"><svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="3 8 7 12 13 4"/></svg></span>
<button class="card-del-btn" type="button" title="删除商品" onclick="event.stopPropagation();" data-action="delete-product"><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>
<button class="card-del-btn" type="button" title="删除商品" onclick="event.stopPropagation();" data-action="delete-product"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" 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 product-thumb"><span class="ph-frame">咖啡冻干粉 · 1200×800</span></div>
<div class="product-body">
<div class="product-name">三顿半同款冻干咖啡粉</div>
@ -778,12 +778,12 @@
</div>
<div class="product-footer">
<span class="stat">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2"/><circle cx="9" cy="10" r="2"/><path d="M21 17l-5-5-9 9"/></svg>
<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="18" height="18" rx="2"/><circle cx="9" cy="10" r="2"/><path d="M21 17l-5-5-9 9"/></svg>
素材 <b>68</b>
</span>
<span class="sep">·</span>
<span class="stat">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="6" width="14" height="12" rx="2"/><path d="M16 10l6-3v10l-6-3z"/></svg>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="6" width="14" height="12" rx="2"/><path d="M16 10l6-3v10l-6-3z"/></svg>
视频 <b>21</b>
</span>
@ -792,7 +792,7 @@
<div class="product-card" data-cat="家居家电" data-name="小熊 4L 可视空气炸锅" data-tags="小户型 健康" data-added="6" data-assets="54" data-videos="16" data-date="2026-05-03" onclick="location.href='product-detail.html?t='+Date.now()">
<span class="card-check" aria-hidden="true"><svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="3 8 7 12 13 4"/></svg></span>
<button class="card-del-btn" type="button" title="删除商品" onclick="event.stopPropagation();" data-action="delete-product"><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>
<button class="card-del-btn" type="button" title="删除商品" onclick="event.stopPropagation();" data-action="delete-product"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" 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 product-thumb"><span class="ph-frame">空气炸锅 · 1200×800</span></div>
<div class="product-body">
<div class="product-name">小熊 4L 可视空气炸锅</div>
@ -801,12 +801,12 @@
</div>
<div class="product-footer">
<span class="stat">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2"/><circle cx="9" cy="10" r="2"/><path d="M21 17l-5-5-9 9"/></svg>
<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="18" height="18" rx="2"/><circle cx="9" cy="10" r="2"/><path d="M21 17l-5-5-9 9"/></svg>
素材 <b>54</b>
</span>
<span class="sep">·</span>
<span class="stat">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="6" width="14" height="12" rx="2"/><path d="M16 10l6-3v10l-6-3z"/></svg>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="6" width="14" height="12" rx="2"/><path d="M16 10l6-3v10l-6-3z"/></svg>
视频 <b>16</b>
</span>
@ -815,7 +815,7 @@
<div class="product-card" data-cat="运动户外" data-name="露露同款裸感瑜伽裤" data-tags="健身房 通勤" data-added="7" data-assets="42" data-videos="12" data-date="2026-04-30" onclick="location.href='product-detail.html?t='+Date.now()">
<span class="card-check" aria-hidden="true"><svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="3 8 7 12 13 4"/></svg></span>
<button class="card-del-btn" type="button" title="删除商品" onclick="event.stopPropagation();" data-action="delete-product"><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>
<button class="card-del-btn" type="button" title="删除商品" onclick="event.stopPropagation();" data-action="delete-product"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" 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 product-thumb"><span class="ph-frame">瑜伽裤 · 1200×800</span></div>
<div class="product-body">
<div class="product-name">露露同款裸感瑜伽裤</div>
@ -824,12 +824,12 @@
</div>
<div class="product-footer">
<span class="stat">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2"/><circle cx="9" cy="10" r="2"/><path d="M21 17l-5-5-9 9"/></svg>
<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="18" height="18" rx="2"/><circle cx="9" cy="10" r="2"/><path d="M21 17l-5-5-9 9"/></svg>
素材 <b>42</b>
</span>
<span class="sep">·</span>
<span class="stat">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="6" width="14" height="12" rx="2"/><path d="M16 10l6-3v10l-6-3z"/></svg>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="6" width="14" height="12" rx="2"/><path d="M16 10l6-3v10l-6-3z"/></svg>
视频 <b>12</b>
</span>
@ -914,9 +914,9 @@
<div class="pf-example">
<div class="ex-h">示例图</div>
<div class="ex-grid">
<div class="ex-thumb"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"><path d="M7 4h10l1 4v12H6V8l1-4z"/><path d="M9 4v3M15 4v3M9 11h6M9 14h6"/></svg></div>
<div class="ex-thumb"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"><rect x="6" y="5" width="12" height="15" rx="2"/><path d="M9 9h6M9 12h6M9 15h4"/></svg></div>
<div class="ex-thumb"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"><path d="M8 3h8l1 5v12H7V8l1-5z"/><circle cx="12" cy="13" r="2.5"/></svg></div>
<div class="ex-thumb"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M7 4h10l1 4v12H6V8l1-4z"/><path d="M9 4v3M15 4v3M9 11h6M9 14h6"/></svg></div>
<div class="ex-thumb"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><rect x="6" y="5" width="12" height="15" rx="2"/><path d="M9 9h6M9 12h6M9 15h4"/></svg></div>
<div class="ex-thumb"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M8 3h8l1 5v12H7V8l1-5z"/><circle cx="12" cy="13" r="2.5"/></svg></div>
</div>
<div class="ex-d">优质的商品图有助于生成更好的素材效果</div>
</div>
@ -935,7 +935,7 @@
<div class="drawer-f">
<button class="btn-guide" type="button" onclick="Shell.toast('使用指南', '点击查看完整填写指南')">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="9"/><path d="M9.5 9a2.5 2.5 0 015 0c0 1.5-2.5 2-2.5 4M12 17h.01"/></svg>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="9"/><path d="M9.5 9a2.5 2.5 0 015 0c0 1.5-2.5 2-2.5 4M12 17h.01"/></svg>
使用指南
</button>
<button class="btn" type="button" id="pc-cancel-btn">取消</button>
@ -953,7 +953,7 @@
<span class="corner-bl" aria-hidden></span>
<div class="modal-h">
<div class="ic-m" style="background:var(--heat-12);color:var(--heat)">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" 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>
<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>
</div>
<div class="ti" id="gen-choice-ti"><span id="gen-choice-target"></span><span>// PICK ONE</span></div>
</div>
@ -988,7 +988,7 @@
<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>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" 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" id="del-confirm-ti">确认删除商品<span>// CONFIRM DELETE</span></div>
</div>
@ -996,7 +996,7 @@
<div class="modal-f" id="del-confirm-foot">
<button class="btn" type="button" id="del-confirm-cancel">取消</button>
<button class="btn" type="button" id="del-confirm-ok" style="background:var(--accent-crimson,#c43d3d);color:var(--accent-white);border-color:var(--accent-crimson,#c43d3d)">
<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"/><path d="M8 6V4a2 2 0 012-2h4a2 2 0 012 2v2"/></svg>
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M3 6h18"/><path d="M8 6V4a2 2 0 012-2h4a2 2 0 012 2v2"/></svg>
确认删除
</button>
</div>
@ -1009,7 +1009,7 @@
<button class="clear-sel" type="button" id="bulk-clear">清空</button>
<span class="sep"></span>
<button class="danger" type="button" id="bulk-del">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" 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>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" 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>
<button type="button" id="bulk-exit">完成</button>
@ -1049,14 +1049,14 @@ Shell.render({ active: 'products', crumbs: [{ label: '工作台', href: 'index.h
card.dataset.triview = '0';
card.innerHTML = `
<span class="card-check" aria-hidden="true"><svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="3 8 7 12 13 4"/></svg></span>
<button class="card-del-btn" type="button" title="删除商品" onclick="event.stopPropagation();" data-action="delete-product"><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>
<button class="card-del-btn" type="button" title="删除商品" onclick="event.stopPropagation();" data-action="delete-product"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" 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 product-thumb">
<span class="tri-missing-badge" tabindex="0" role="button" aria-label="缺三视图,查看说明">
<span class="ico" aria-hidden="true"></span>
<span class="lbl-mono">缺三视图</span>
<span class="tri-missing-pop" role="tooltip">
<span class="pop-h">
<svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><path d="M12 9v4M12 17h.01"/><path d="M10.3 3.86 1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/></svg>
<svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M12 9v4M12 17h.01"/><path d="M10.3 3.86 1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/></svg>
MISSING TRI-VIEW
</span>
<span class="pop-body">该商品还未生成 <b>正 / 侧 / 背</b> 三视图。直接生成图片或视频,模型缺少多角度参考,角色一致性、姿态稳定性可能下降。</span>
@ -1072,12 +1072,12 @@ Shell.render({ active: 'products', crumbs: [{ label: '工作台', href: 'index.h
</div>
<div class="product-footer">
<span class="stat">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2"/><circle cx="9" cy="10" r="2"/><path d="M21 17l-5-5-9 9"/></svg>
<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="18" height="18" rx="2"/><circle cx="9" cy="10" r="2"/><path d="M21 17l-5-5-9 9"/></svg>
素材 <b>${p.assets || 0}</b>
</span>
<span class="sep">·</span>
<span class="stat">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="6" width="14" height="12" rx="2"/><path d="M16 10l6-3v10l-6-3z"/></svg>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="6" width="14" height="12" rx="2"/><path d="M16 10l6-3v10l-6-3z"/></svg>
视频 <b>${p.videos || 0}</b>
</span>
</div>
@ -1537,14 +1537,14 @@ function renderPagination(totalVisible, totalPages) {
const list = document.getElementById('page-list');
const items = pageNumberList(state.page, totalPages);
let html = `<button type="button" data-page="prev" ${state.page <= 1 ? 'disabled' : ''} aria-label="上一页">
<svg width="11" height="11" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M10 12L6 8l4-4"/></svg>
<svg width="11" height="11" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M10 12L6 8l4-4"/></svg>
</button>`;
items.forEach(p => {
if (p === '…') html += `<span class="ellipsis"></span>`;
else html += `<button type="button" data-page="${p}" ${p === state.page ? 'class="active"' : ''}>${p}</button>`;
});
html += `<button type="button" data-page="next" ${state.page >= totalPages ? 'disabled' : ''} aria-label="下一页">
<svg width="11" height="11" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M6 4l4 4-4 4"/></svg>
<svg width="11" height="11" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M6 4l4 4-4 4"/></svg>
</button>`;
list.innerHTML = html;
}
@ -1667,7 +1667,7 @@ function pfRender() {
<div class="pf-thumb" data-id="${u.id}">
<img src="${u.dataUrl}" alt="${pfEsc(u.name)}">
<button class="pf-x" type="button" title="删除">
<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>
<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>
</div>
`).join('');

View File

@ -585,11 +585,11 @@
<div class="pl-main">
<div class="pl-toolbar">
<div class="search">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6"><circle cx="11" cy="11" r="7"/><path d="m21 21-4.3-4.3"/></svg>
<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 type="text" id="pl-search-input" placeholder="搜索商品名">
</div>
<button class="btn-new" type="button" id="pl-new-btn">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><path d="M12 5v14M5 12h14"/></svg>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M12 5v14M5 12h14"/></svg>
新建商品
</button>
</div>
@ -769,12 +769,12 @@
const ICONS = {
check: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><path d="M4 12l5 5L20 6"/></svg>',
search: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="7"/><path d="m21 21-4.3-4.3"/></svg>',
plus: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M12 5v14M5 12h14"/></svg>',
search: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="7"/><path d="m21 21-4.3-4.3"/></svg>',
plus: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M12 5v14M5 12h14"/></svg>',
x: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M5 5l14 14M19 5L5 19"/></svg>',
bulb: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M9 18h6"/><path d="M10 22h4"/><path d="M15.09 14c.18-.98.65-1.74 1.41-2.5A4.65 4.65 0 0 0 18 8 6 6 0 0 0 6 8c0 1 .23 2.23 1.5 3.5A4.61 4.61 0 0 1 8.91 14"/></svg>',
arrow: '<svg 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>',
wallet: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="6" width="18" height="13" rx="2"/><path d="M3 10h18M16 14h2"/></svg>',
wallet: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="6" width="18" height="13" rx="2"/><path d="M3 10h18M16 14h2"/></svg>',
};
/* ---------- actions ---------- */
@ -1115,7 +1115,7 @@
const cards = pageList.map(productCardHTML).join('');
const createCard = `<div class="pp-create-card" onclick="_wiz.openNewProduct()">
<div class="pc-plus"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 5v14M5 12h14"/></svg></div>
<div class="pc-plus"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M12 5v14M5 12h14"/></svg></div>
<div class="pc-t">创建新商品</div>
<div class="pc-d">// 在此添加一个新商品</div>
</div>`;
@ -1154,7 +1154,7 @@
${total > pageSize ? pickerPagerHTML(total) : ''}
<div class="pp-bottom-tip">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="9"/><path d="M12 8v5M12 16h.01"/></svg>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="9"/><path d="M12 8v5M12 16h.01"/></svg>
<span>找不到想要的商品?可<a onclick="_wiz.openNewProduct()">创建新商品</a>,或前往 <a href="products.html">商品库 · 管理商品</a></span>
</div>
</div>`;

View File

@ -536,6 +536,40 @@
<p>// 试试切换 tab 或修改搜索词</p>
</div>
<!-- ===== 删除确认 modal (必须在 <script> 之前 · 否则 getElementById 取 null 导致脚本崩) ===== -->
<div class="modal-bg" id="del-confirm-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="del-confirm-body">即将删除项目。</div>
<div class="modal-f">
<button class="btn" type="button" id="del-confirm-cancel">取消</button>
<button class="btn" type="button" id="del-confirm-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>
<!-- ===== bulk-bar (必须在 <script> 之前) ===== -->
<div class="bulk-bar" id="bulk-bar">
<span class="ct">已选 <b id="bulk-count">0</b></span>
<button class="clear-sel" type="button" id="bulk-clear">清空</button>
<span class="sep"></span>
<button class="danger" type="button" id="bulk-del">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" 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>
<button type="button" id="bulk-exit">完成</button>
</div>
</div>
<script src="assets/shell.js?v=202605211643"></script>
<script>
@ -1042,39 +1076,5 @@ document.querySelectorAll('#list-tbody tr').forEach(tr => {
}, true);
});
</script>
<!-- ===== 删除确认 modal ===== -->
<div class="modal-bg" id="del-confirm-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="del-confirm-body">即将删除项目。</div>
<div class="modal-f">
<button class="btn" type="button" id="del-confirm-cancel">取消</button>
<button class="btn" type="button" id="del-confirm-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>
<!-- ===== bulk-bar ===== -->
<div class="bulk-bar" id="bulk-bar">
<span class="ct">已选 <b id="bulk-count">0</b></span>
<button class="clear-sel" type="button" id="bulk-clear">清空</button>
<span class="sep"></span>
<button class="danger" type="button" id="bulk-del">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" 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>
<button type="button" id="bulk-exit">完成</button>
</div>
</body>
</html>

View File

@ -101,7 +101,7 @@
<aside class="auth-brand">
<div class="logo">
<span class="ic"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 2 L19 7 V17 L12 22 L5 17 V7 Z"/><path d="M12 2 V22"/><path d="M5 7 L19 17"/></svg></span>
<span class="ic"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M12 2 L19 7 V17 L12 22 L5 17 V7 Z"/><path d="M12 2 V22"/><path d="M5 7 L19 17"/></svg></span>
<span>流·Studio</span>
</div>
<div class="tag">// SHORT-VIDEO COMMERCE PLATFORM</div>
@ -149,7 +149,7 @@
<div class="field">
<label class="field-label" for="reg-team">团队名 <span class="req">*</span></label>
<div class="field-input-wrap">
<svg class="ic-l" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/><path d="M9 22V12h6v10"/></svg>
<svg class="ic-l" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/><path d="M9 22V12h6v10"/></svg>
<input type="text" id="reg-team" placeholder="例: 小李的店 / XX 文化传媒" required>
</div>
</div>
@ -157,7 +157,7 @@
<div class="field">
<label class="field-label" for="reg-email">超管邮箱 <span class="req">*</span><span class="hint">用于成员邀请 + 找回密码</span></label>
<div class="field-input-wrap">
<svg class="ic-l" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M4 4h16a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2z"/><path d="m22 6-10 7L2 6"/></svg>
<svg class="ic-l" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M4 4h16a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2z"/><path d="m22 6-10 7L2 6"/></svg>
<input type="email" id="reg-email" placeholder="name@company.com" required>
</div>
</div>
@ -166,20 +166,20 @@
<div class="field">
<label class="field-label" for="reg-pwd">密码 <span class="req">*</span></label>
<div class="field-input-wrap">
<svg class="ic-l" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="11" width="18" height="11" rx="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></svg>
<svg class="ic-l" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="11" width="18" height="11" rx="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></svg>
<input type="password" id="reg-pwd" placeholder="至少 8 位" required>
<button type="button" class="toggle-pwd" onclick="togglePwd('reg-pwd')">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M2 12s4-7 10-7 10 7 10 7-4 7-10 7-10-7-10-7z"/><circle cx="12" cy="12" r="3"/></svg>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M2 12s4-7 10-7 10 7 10 7-4 7-10 7-10-7-10-7z"/><circle cx="12" cy="12" r="3"/></svg>
</button>
</div>
</div>
<div class="field">
<label class="field-label" for="reg-pwd2">确认密码 <span class="req">*</span></label>
<div class="field-input-wrap">
<svg class="ic-l" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="11" width="18" height="11" rx="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></svg>
<svg class="ic-l" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="11" width="18" height="11" rx="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></svg>
<input type="password" id="reg-pwd2" placeholder="再输一次" required>
<button type="button" class="toggle-pwd" onclick="togglePwd('reg-pwd2')">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M2 12s4-7 10-7 10 7 10 7-4 7-10 7-10-7-10-7z"/><circle cx="12" cy="12" r="3"/></svg>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M2 12s4-7 10-7 10 7 10 7-4 7-10 7-10-7-10-7z"/><circle cx="12" cy="12" r="3"/></svg>
</button>
</div>
</div>
@ -188,14 +188,14 @@
<div class="field">
<label class="field-label" for="reg-invite">邀请码 <span class="hint">可选 · 团队邀请才需要</span></label>
<div class="field-input-wrap">
<svg class="ic-l" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"/></svg>
<svg class="ic-l" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"/></svg>
<input type="text" id="reg-invite" placeholder="例: TEAM-XXXX-XXXX">
</div>
</div>
<label class="agree">
<input type="checkbox" id="reg-agree" checked>
<span>我已阅读并同意 <a href="#" onclick="event.preventDefault()">用户协议</a><a href="#" onclick="event.preventDefault()">隐私政策</a>,知悉「失败不扣费 · 确认后扣」的扣费规则。</span>
<span>我已阅读并同意 <a href="#" onclick="event.preventDefault();_regToast('用户协议','含数据处理 / 内容生成版权 / 计费规则三章 · 完整文本将在正式版接入');">用户协议</a><a href="#" onclick="event.preventDefault();_regToast('隐私政策','遵循《个人信息保护法》· 团队数据存中国境内 · 默认不用于模型训练');">隐私政策</a>,知悉「失败不扣费 · 确认后扣」的扣费规则。</span>
</label>
<button class="btn-cta" type="submit" id="reg-submit">
@ -230,6 +230,20 @@
btn.disabled = true;
setTimeout(() => { location.href = 'index.html'; }, 900);
}
// 轻量 toast(不依赖 shell.js)
function _regToast(t, sub) {
let el = document.getElementById('__reg-toast');
if (!el) {
el = document.createElement('div');
el.id = '__reg-toast';
el.style.cssText = 'position:fixed;left:50%;bottom:36px;transform:translateX(-50%) translateY(20px);background:#fff;border:1px solid #e0e0e0;border-radius:8px;padding:12px 18px;box-shadow:0 8px 24px rgba(0,0,0,.12);display:flex;flex-direction:column;gap:2px;opacity:0;transition:opacity .2s,transform .2s;z-index:9999;font-family:inherit;max-width:380px;';
document.body.appendChild(el);
}
el.innerHTML = '<div style="font-size:13.5px;font-weight:600;color:#262626;">' + t + '</div>' + (sub ? '<div style="font-size:11.5px;color:rgba(0,0,0,.56);font-family:\'JetBrains Mono\',monospace;letter-spacing:.02em;">// ' + sub + '</div>' : '');
requestAnimationFrame(() => { el.style.opacity = '1'; el.style.transform = 'translateX(-50%) translateY(0)'; });
clearTimeout(el._t);
el._t = setTimeout(() => { el.style.opacity = '0'; el.style.transform = 'translateX(-50%) translateY(20px)'; }, 3000);
}
</script>
</body>
</html>

View File

@ -421,7 +421,7 @@
<div class="lbl">退出登录</div>
<div class="val">
<span class="static mono" style="font-size: 11.5px; color: var(--black-alpha-56);">// 仅退出当前设备,数据保留</span>
<button class="btn btn-sm" style="margin-left: auto; color: var(--accent-crimson); border-color: var(--accent-crimson);" onclick="if(confirm('确定退出登录?')) Shell.toast('已退出', '正在跳转登录页')">退出登录</button>
<button class="btn btn-sm" style="margin-left: auto; color: var(--accent-crimson); border-color: var(--accent-crimson);" onclick="if(confirm('确定退出登录?')){Shell.toast('已退出','正在跳转登录页');setTimeout(()=>location.href='login.html',600);}">退出登录</button>
</div>
</div>
<div class="form-row">

View File

@ -489,7 +489,7 @@
<span>// MCN · 老张</span>
</div>
<button class="ws-new-btn" id="ws-new-btn">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><path d="M12 5v14M5 12h14"/></svg>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M12 5v14M5 12h14"/></svg>
新建商品
</button>
<div class="ws-search">
@ -912,7 +912,7 @@ function renderPhotos(p, isEdit) {
if (ph) {
slots += `<div class="ov-photo" style="background:${ph.bg};background-size:cover;background-position:center;">
${i===0?'<span class="pmain">MAIN</span>':''}
<button class="photo-x" data-i="${i}" type="button"><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>
<button class="photo-x" data-i="${i}" type="button"><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>
</div>`;
} else if (i === p.photos.length) {
slots += `<div class="ov-photo empty-slot add-active" data-add-photo>+ 上传</div>`;

View File

@ -667,7 +667,7 @@
<span>// MCN · 老张的店</span>
</div>
<button class="ws-new-btn" id="ws-new-btn">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><path d="M12 5v14M5 12h14"/></svg>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M12 5v14M5 12h14"/></svg>
新建商品
</button>
<div class="ws-search">
@ -1246,7 +1246,7 @@ function renderPhotosBlock(p) {
if (ph) {
slots += `<div class="ov-photo" style="background:${ph.bg};background-size:cover;background-position:center;">
${i===0?'<span class="pmain">MAIN</span>':''}
<button class="photo-x" data-i="${i}" type="button"><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>
<button class="photo-x" data-i="${i}" type="button"><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>
</div>`;
} else if (i === p.photos.length) {
slots += `<div class="ov-photo empty-slot add-active" data-add-photo>+ 上传</div>`;