Polish static UI flows
Some checks failed
Build and Deploy / build-and-deploy (push) Failing after 4m39s
Some checks failed
Build and Deploy / build-and-deploy (push) Failing after 4m39s
This commit is contained in:
parent
5edfa05369
commit
bbe29622c2
99
AGENTS.md
Normal file
99
AGENTS.md
Normal file
@ -0,0 +1,99 @@
|
||||
# Airshelf · 电商 AI 平台 · Codex 工程约定
|
||||
|
||||
> **本文件由 Codex 启动时自动加载。所有 AI 协作必须遵循以下规则。**
|
||||
|
||||
---
|
||||
|
||||
## 项目简介
|
||||
|
||||
**Airshelf** · AI 短视频带货生成平台 · 5 阶段流水线(商品 → 故事板 → 镜头 → 生成 → 投放)
|
||||
|
||||
- **设计代号:** Restraint · V2.1 · Firecrawl-aligned
|
||||
- **主要工作目录:** [电商AI平台/](电商AI平台/)
|
||||
- **Next.js 工程(独立):** [app/](app/)
|
||||
- **V1 历史归档:** [v1/](v1/)
|
||||
- **V2.1 归档(原 v2.1/):** [v2/](v2/)
|
||||
|
||||
---
|
||||
|
||||
## ★ 设计规范铁律(每次涉及页面 / CSS / UI 必读)
|
||||
|
||||
### 触发条件
|
||||
**只要任务涉及以下任一种,必须先 Read [电商AI平台/design.md](电商AI平台/design.md):**
|
||||
- 修改 `.html` 文件
|
||||
- 修改 `assets/restraint.css` 或任何 `.css`
|
||||
- 修改 inline `<style>` 块
|
||||
- 添加新页面 / 新组件
|
||||
- 调整布局 / 间距 / 颜色 / 字号
|
||||
- 用户提到"页面" "样式" "视觉" "组件" "色" "字" "圆角" "间距" 等关键词
|
||||
|
||||
### 必读章节
|
||||
- [design.md §0 AI 协作铁律](电商AI平台/design.md#0--ai-协作铁律每次启动必读) — 必读
|
||||
- [design.md §1 设计哲学](电商AI平台/design.md#1--设计哲学) — 价值观
|
||||
- [design.md §3 组件清单](电商AI平台/design.md#4--组件清单restraintcss-已实现--不要重发明) — 用现成组件
|
||||
- [design.md §8 Don't List](电商AI平台/design.md#8--dont-list绝对禁止--每次自检) — 自检
|
||||
|
||||
### 7 条铁律
|
||||
|
||||
1. **任何页面 / CSS 调整前必须 Read [电商AI平台/design.md](电商AI平台/design.md)** — 不读不动手
|
||||
2. **检查 [电商AI平台/assets/restraint.css](电商AI平台/assets/restraint.css) 已有组件** — `Grep ".btn|.pill|.input"` 等
|
||||
3. **禁止在页面 inline `<style>` 重写共享类**(`.btn` `.pill` `.input` `.modal` `.drawer` `.toast` `.field` `.tabs` `.chip` `.stats` `.list-row` 等)— 要变体回 restraint.css 加
|
||||
4. **禁止创建新色值** — 必须用 design.md §2.1 的 token,不写裸 hex
|
||||
5. **禁止改动基础 token**(`--heat` `--background-base` `--border-faint` 等)— 改了破坏全站
|
||||
6. **完成后对照 [design.md §8 Don't List](电商AI平台/design.md#8--dont-list绝对禁止--每次自检) 逐条自检**
|
||||
7. **不确定就问用户**,不要凭感觉发挥 — 用户原话:"我都希望你能遵循我们的设计规范,而不是乱做"
|
||||
|
||||
---
|
||||
|
||||
## 设计核心速记(详见 design.md)
|
||||
|
||||
- **冷灰底** `#f9f9f9` · 主橙 `#fa5d19` · 主前景 `#262626`
|
||||
- **全场 8 px 圆角**(Pill / dot 999 例外)· `>12 px` 直接判错
|
||||
- **inside-border** 而非真 `border`(hover 不抖动)
|
||||
- **单橙锚点** · 全场只有一个 accent · hover 用 alpha 不用换 hue
|
||||
- **Mono 装饰必有** · `[ 200 OK ]` `// 05.14` `[ /v2 ]`(品牌签名)
|
||||
- **主 CTA 唯一允许阴影** · 4 层橙色发光 · 其他场景禁阴影
|
||||
- **Inter(英/数字/装饰)+ Alibaba PuHuiTi(中)** · 字符级 fallthrough
|
||||
- **字重仅 3 档** · 400 / 500 / 600 · 700 仅给 Ctrl K 徽标
|
||||
|
||||
---
|
||||
|
||||
## Git 工作流
|
||||
|
||||
- **当前开发分支:** `dev`
|
||||
- **主分支:** `main` (生产)
|
||||
- **严禁直推 master/main** — 走 dev 分支 → PR → 合并触发 CI/CD
|
||||
- **严禁 `--no-verify` 跳过 hook**
|
||||
- **Push 规则:** 默认不 push,改完即停 · 用户明确说"push / 推一下"才执行
|
||||
- **commit 前不要 amend** — 创建新 commit,避免破坏历史
|
||||
|
||||
## 文件操作
|
||||
|
||||
- **三视图 = 单张 16:9 图** · 不要拆成 3 张缩略 · 用 `aspect-ratio: 16/9` 单容器
|
||||
- **设计稿优先** · 写代码前必须先读 [电商AI平台/_design_src/](电商AI平台/_design_src/) 设计稿(如果有)
|
||||
- **`.pen` 文件加密** · 只能用 pencil MCP 工具,不能 Read/Grep
|
||||
|
||||
---
|
||||
|
||||
## 用户偏好
|
||||
|
||||
- **角色:** UI 设计师 · 不读代码报错,只看最终视觉结果
|
||||
- **不需要的:** 终端报错截图、深奥的代码解释、过度的实施细节
|
||||
- **需要的:** 简短状态更新、视觉结果对照、清晰的"对/错"反馈
|
||||
|
||||
---
|
||||
|
||||
## 关键路径速查
|
||||
|
||||
| 资产 | 路径 |
|
||||
| ---- | ---- |
|
||||
| **设计规范(SSoT)** | [电商AI平台/design.md](电商AI平台/design.md) |
|
||||
| **共享 CSS** | [电商AI平台/assets/restraint.css](电商AI平台/assets/restraint.css) |
|
||||
| **Shell 注入** | [电商AI平台/assets/shell.js](电商AI平台/assets/shell.js) |
|
||||
| **视觉样板间(归档)** | [电商AI平台/_archive/design-system.html](电商AI平台/_archive/design-system.html) |
|
||||
| **规范理论(归档)** | [电商AI平台/_archive/DESIGN_SPEC_V2.md](电商AI平台/_archive/DESIGN_SPEC_V2.md) |
|
||||
| **设计稿源** | [电商AI平台/_design_src/](电商AI平台/_design_src/) |
|
||||
|
||||
---
|
||||
|
||||
**违反任何规范规则,用户有权要求重做,无需解释。**
|
||||
21
_archive/root-next-20260528/README.md
Normal file
21
_archive/root-next-20260528/README.md
Normal file
@ -0,0 +1,21 @@
|
||||
# Root Next.js Archive · 2026-05-28
|
||||
|
||||
This folder preserves the earlier root-level Next.js scaffold.
|
||||
|
||||
It was archived because the current product surface is the static HTML app in
|
||||
`电商AI平台/*.html`, while this Next.js implementation only contained a partial
|
||||
route set and could confuse future edits.
|
||||
|
||||
Archived paths:
|
||||
- `app/`
|
||||
- `components/`
|
||||
- `k8s/`
|
||||
- `package.json`
|
||||
- `package-lock.json`
|
||||
- `next.config.mjs`
|
||||
- `next-env.d.ts`
|
||||
- `postcss.config.mjs`
|
||||
- `tsconfig.json`
|
||||
- `deployment-guide.md`
|
||||
|
||||
To restore it later, move these files back to the repository root.
|
||||
@ -1,21 +1,27 @@
|
||||
# 待归档/清理的 HTML 文件
|
||||
# 已归档/保留的 HTML 文件说明
|
||||
|
||||
> 2026-05-21 · 设计稿对接前清理清单
|
||||
> 2026-05-28 · 主流程收口记录
|
||||
|
||||
下列文件是早期版本或独立实验页,**不在主流程上**,对接给团队前建议归档/删除。
|
||||
保留它们不会影响主流程渲染(没有 sidebar / 内部 link 指向),但会让 repo 文件目录显得混乱。
|
||||
下列早期版本或独立实验页**不在主流程上**,已归档到
|
||||
[`_archive/deprecated-pages-20260528/`](_archive/deprecated-pages-20260528/)。
|
||||
|
||||
## ✅ 可以直接删除(无任何入口)
|
||||
根目录半成品 Next.js scaffold 已归档到
|
||||
[`../_archive/root-next-20260528/`](../_archive/root-next-20260528/)。
|
||||
|
||||
保留本文件用于说明历史路径,避免后续误改旧页面。
|
||||
|
||||
## ✅ 已归档(无主流程入口)
|
||||
|
||||
| 文件 | 原用途 | 现状 |
|
||||
|------|--------|------|
|
||||
| `product-create.legacy.html` | 旧版「新建商品」全屏页(3883 行 drawer) | 已被 `product-create.html`(stub 重定向)+ drawer 模式替代,无任何链接 |
|
||||
| `product-create-v2.html` | 备选「新建商品」实验版 | 仅 `product-studio.html` 单向关联,product-studio 本身已无入口 |
|
||||
| `studio.html` | 早期工作室页占位 | 已被 `pipeline.html` 替代,sidebar 已切换,无 link |
|
||||
| `studio-v2.html` | 早期工作室 V2 实验 | 同上 |
|
||||
| `product-studio.html` | 商品 + 工作室合并实验 | 主流程已拆为 `product-detail.html` + `pipeline.html` |
|
||||
| `_archive/deprecated-pages-20260528/product-create.legacy.html` | 旧版「新建商品」全屏页(3883 行 drawer) | 已被 `product-create.html` stub + drawer 模式替代 |
|
||||
| `_archive/deprecated-pages-20260528/product-create-v2.html` | 备选「新建商品」实验版 | 仅供参考 |
|
||||
| `_archive/deprecated-pages-20260528/studio.html` | 早期工作室页占位 | 已被 `pipeline.html` 替代 |
|
||||
| `_archive/deprecated-pages-20260528/studio-v2.html` | 早期工作室 V2 实验 | 已被 `pipeline.html` 替代 |
|
||||
| `_archive/deprecated-pages-20260528/product-studio.html` | 商品 + 工作室合并实验 | 主流程已拆为 `product-detail.html` + `pipeline.html` |
|
||||
| `../_archive/root-next-20260528/` | 根目录 Next.js 半成品 | 静态 HTML 才是当前 source of truth |
|
||||
|
||||
## ⚠️ 保留但要清理 sidebar 入口判断
|
||||
## ⚠️ 仍保留在主流程/辅助流程
|
||||
|
||||
| 文件 | 决定 |
|
||||
|------|------|
|
||||
@ -25,17 +31,6 @@
|
||||
| `asset-factory.html` | **保留** · sidebar 入口「图片生成」 |
|
||||
| `design-system.html` | **保留** · 设计系统参考,非用户路径 |
|
||||
|
||||
## 🚀 清理命令(对接前)
|
||||
|
||||
如果决定执行,可在 `电商AI平台/` 目录下:
|
||||
|
||||
```powershell
|
||||
# Windows PowerShell
|
||||
Remove-Item product-create.legacy.html, product-create-v2.html, studio.html, studio-v2.html, product-studio.html
|
||||
```
|
||||
|
||||
清理后总文件数:**21 → 16**(含 design-system),核心 15 个。
|
||||
|
||||
## 📌 sidebar NAV 当前入口对照(assets/shell.js:10-43)
|
||||
|
||||
```
|
||||
|
||||
16
电商AI平台/_archive/deprecated-pages-20260528/README.md
Normal file
16
电商AI平台/_archive/deprecated-pages-20260528/README.md
Normal file
@ -0,0 +1,16 @@
|
||||
# Deprecated Static Pages Archive · 2026-05-28
|
||||
|
||||
These pages were moved out of the active static app because the current
|
||||
navigation and production prototype use `products.html`, `product-detail.html`,
|
||||
`pipeline.html`, `asset-factory.html`, `model-photo.html`, and
|
||||
`platform-cover.html` instead.
|
||||
|
||||
Archived pages:
|
||||
- `product-create.legacy.html`
|
||||
- `product-create-v2.html`
|
||||
- `product-studio.html`
|
||||
- `studio.html`
|
||||
- `studio-v2.html`
|
||||
|
||||
Keep them only as reference material. They should not be linked from the active
|
||||
Airshelf static flow.
|
||||
@ -143,10 +143,9 @@
|
||||
.quota-rules .step .formula { font-family: var(--font-mono); font-size: 11.5px; color: var(--heat); background: var(--heat-12); padding: 0 4px; border-radius: var(--r-sm); }
|
||||
|
||||
/* ─── 表格通用 ─── */
|
||||
.billing-table { width: 100%; border-collapse: separate; border-spacing: 0; background: var(--surface); border: 1px solid var(--border-faint); border-radius: var(--r-md); overflow: hidden; }
|
||||
.billing-table th, .billing-table td { padding: 11px 14px; text-align: left; font-size: 12.5px; border-bottom: 1px solid var(--border-faint); }
|
||||
.billing-table thead th { background: var(--background-lighter); font-family: var(--font-mono); font-size: 10.5px; font-weight: 500; color: var(--black-alpha-48); letter-spacing: .04em; text-transform: uppercase; }
|
||||
.billing-table tbody tr:last-child td { border-bottom: 0; }
|
||||
.billing-table { width: 100%; border-collapse: separate; border-spacing: 0; background: var(--surface); border: 1px solid var(--border-muted); border-radius: var(--r-md); overflow: hidden; }
|
||||
.billing-table th, .billing-table td { padding: 11px 14px; text-align: left; font-size: 12.5px; border-bottom: 0; }
|
||||
.billing-table thead th { background: var(--background-lighter); border-bottom: 1px solid var(--border-muted); font-family: var(--font-mono); font-size: 10.5px; font-weight: 500; color: var(--black-alpha-48); letter-spacing: .04em; text-transform: uppercase; }
|
||||
.billing-table tbody tr:hover { background: var(--background-lighter); }
|
||||
.billing-table .ts { font-family: var(--font-mono); font-size: 11px; color: var(--black-alpha-48); letter-spacing: .02em; }
|
||||
.billing-table .neg { font-variant-numeric: tabular-nums; font-weight: 500; color: var(--accent-black); text-align: right; }
|
||||
|
||||
@ -157,7 +157,25 @@
|
||||
.history-card .placeholder { width: 78px; height: 78px; }
|
||||
|
||||
/* ─── 列表视图 · 表格 ─── */
|
||||
#task-list-view {
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--border-muted);
|
||||
border-radius: var(--r-md);
|
||||
overflow: hidden;
|
||||
}
|
||||
#task-list-view[hidden] { display: none; }
|
||||
#task-list-view table.t {
|
||||
border: 0;
|
||||
border-radius: 0;
|
||||
background: transparent;
|
||||
}
|
||||
#task-list-view table.t thead th {
|
||||
background: var(--background-lighter);
|
||||
border-bottom-color: var(--border-muted);
|
||||
}
|
||||
#task-list-view table.t tbody td {
|
||||
border-bottom: 0;
|
||||
}
|
||||
.task-name-cell { display: flex; align-items: center; gap: 12px; }
|
||||
.task-thumb { width: 40px; height: 40px; flex-shrink: 0; border-radius: var(--r-sm); }
|
||||
.task-name { font-weight: 600; color: var(--accent-black); font-size: 13.5px; }
|
||||
|
||||
@ -1482,9 +1482,9 @@ select.duration-select:focus,
|
||||
|
||||
/* ─── Table ─── */
|
||||
table.t {
|
||||
width: 100%; border-collapse: collapse;
|
||||
width: 100%; border-collapse: separate; border-spacing: 0;
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--border-faint);
|
||||
border: 1px solid var(--border-muted);
|
||||
border-radius: var(--r-md);
|
||||
overflow: hidden;
|
||||
}
|
||||
@ -1495,19 +1495,18 @@ table.t thead th {
|
||||
color: var(--black-alpha-48);
|
||||
padding: 14px 16px;
|
||||
background: var(--black-alpha-3);
|
||||
border-bottom: 1px solid var(--border-faint);
|
||||
border-bottom: 1px solid var(--border-muted);
|
||||
letter-spacing: .04em;
|
||||
text-transform: uppercase;
|
||||
font-family: var(--font-mono);
|
||||
}
|
||||
table.t tbody td {
|
||||
padding: 16px;
|
||||
border-bottom: 1px solid var(--border-faint);
|
||||
border-bottom: 0;
|
||||
font-size: 13px;
|
||||
vertical-align: middle;
|
||||
color: var(--accent-black);
|
||||
}
|
||||
table.t tbody tr:last-child td { border-bottom: 0; }
|
||||
table.t tbody tr { cursor: pointer; transition: background var(--t-base); }
|
||||
table.t tbody tr:hover { background: var(--black-alpha-4); }
|
||||
|
||||
@ -1589,6 +1588,255 @@ table.t tbody tr:hover { background: var(--black-alpha-4); }
|
||||
letter-spacing: .04em; font-weight: 400;
|
||||
}
|
||||
|
||||
/* ─── Global command palette + account menu ─── */
|
||||
.shell-command-bg {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 1800;
|
||||
display: none;
|
||||
align-items: flex-start;
|
||||
justify-content: center;
|
||||
padding: 12vh 16px 24px;
|
||||
background: var(--black-alpha-48);
|
||||
backdrop-filter: blur(8px);
|
||||
-webkit-backdrop-filter: blur(8px);
|
||||
}
|
||||
.shell-command-bg.show { display: flex; }
|
||||
.shell-command {
|
||||
width: min(640px, 100%);
|
||||
max-height: min(680px, 76vh);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: var(--surface-raised);
|
||||
border: 1px solid var(--border-faint);
|
||||
border-radius: var(--r-md);
|
||||
box-shadow: var(--shadow-floating);
|
||||
overflow: hidden;
|
||||
}
|
||||
.shell-command-h {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 14px 16px;
|
||||
border-bottom: 1px solid var(--border-faint);
|
||||
}
|
||||
.shell-command-h .ic {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
background: var(--heat-12);
|
||||
color: var(--heat);
|
||||
border: 1px solid var(--heat-20);
|
||||
border-radius: var(--r-md);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.shell-command-h .ic svg { width: 14px; height: 14px; }
|
||||
.shell-command-h input {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
height: 34px;
|
||||
border: 0;
|
||||
background: transparent;
|
||||
color: var(--accent-black);
|
||||
font-family: inherit;
|
||||
font-size: 15px;
|
||||
outline: none;
|
||||
}
|
||||
.shell-command-h input::placeholder { color: var(--black-alpha-48); }
|
||||
.shell-command-close {
|
||||
height: 26px;
|
||||
padding: 0 8px;
|
||||
border: 1px solid var(--border-faint);
|
||||
border-radius: var(--r-sm);
|
||||
background: var(--background-lighter);
|
||||
color: var(--black-alpha-48);
|
||||
font-family: var(--font-inter);
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
letter-spacing: .02em;
|
||||
cursor: pointer;
|
||||
}
|
||||
.shell-command-close:hover { color: var(--accent-black); border-color: var(--black-alpha-24); }
|
||||
.shell-command-list {
|
||||
padding: 8px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.shell-command-section {
|
||||
padding: 8px 10px 6px;
|
||||
font-family: var(--font-mono);
|
||||
font-size: 10.5px;
|
||||
color: var(--black-alpha-48);
|
||||
letter-spacing: .06em;
|
||||
}
|
||||
.shell-command-item {
|
||||
width: 100%;
|
||||
min-height: 48px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 8px 10px;
|
||||
border: 0;
|
||||
border-radius: var(--r-md);
|
||||
background: transparent;
|
||||
color: var(--accent-black);
|
||||
font-family: inherit;
|
||||
text-align: left;
|
||||
cursor: pointer;
|
||||
}
|
||||
.shell-command-item:hover,
|
||||
.shell-command-item.active {
|
||||
background: var(--heat-12);
|
||||
color: var(--heat);
|
||||
}
|
||||
.shell-command-item .cmd-ic {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
border: 1px solid var(--border-faint);
|
||||
border-radius: var(--r-sm);
|
||||
background: var(--surface);
|
||||
color: currentColor;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.shell-command-item .cmd-ic svg { width: 14px; height: 14px; }
|
||||
.shell-command-item .cmd-main {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
.shell-command-item .cmd-title {
|
||||
display: block;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: currentColor;
|
||||
}
|
||||
.shell-command-item .cmd-sub {
|
||||
display: block;
|
||||
margin-top: 2px;
|
||||
font-size: 12px;
|
||||
line-height: 1.45;
|
||||
color: var(--black-alpha-56);
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.shell-command-item:hover .cmd-sub,
|
||||
.shell-command-item.active .cmd-sub { color: var(--black-alpha-72); }
|
||||
.shell-command-item .cmd-key {
|
||||
min-width: 26px;
|
||||
height: 22px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0 7px;
|
||||
border: 1px solid var(--border-faint);
|
||||
border-radius: var(--r-sm);
|
||||
background: var(--surface);
|
||||
color: var(--black-alpha-48);
|
||||
font-family: var(--font-inter);
|
||||
font-size: 10px;
|
||||
font-weight: 700;
|
||||
letter-spacing: .02em;
|
||||
}
|
||||
.shell-command-empty {
|
||||
padding: 48px 20px;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
gap: 8px;
|
||||
color: var(--black-alpha-48);
|
||||
font-size: 13px;
|
||||
}
|
||||
.shell-command-foot {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 10px 16px;
|
||||
border-top: 1px solid var(--border-faint);
|
||||
color: var(--black-alpha-48);
|
||||
font-family: var(--font-mono);
|
||||
font-size: 10.5px;
|
||||
letter-spacing: .04em;
|
||||
}
|
||||
.shell-command-foot .spacer { flex: 1; }
|
||||
|
||||
.shell-account-menu {
|
||||
position: fixed;
|
||||
z-index: 1700;
|
||||
min-width: 232px;
|
||||
display: none;
|
||||
padding: 8px;
|
||||
background: var(--surface-raised);
|
||||
border: 1px solid var(--border-faint);
|
||||
border-radius: var(--r-md);
|
||||
box-shadow: var(--shadow-floating);
|
||||
}
|
||||
.shell-account-menu.show { display: block; }
|
||||
.shell-account-head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 8px 8px 10px;
|
||||
border-bottom: 1px solid var(--border-faint);
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
.shell-account-head .av {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
border-radius: 6px;
|
||||
background: var(--accent-black);
|
||||
color: var(--accent-white);
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
}
|
||||
.shell-account-head .nm {
|
||||
display: block;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: var(--accent-black);
|
||||
}
|
||||
.shell-account-head .mail {
|
||||
display: block;
|
||||
margin-top: 2px;
|
||||
font-family: var(--font-mono);
|
||||
font-size: 10.5px;
|
||||
color: var(--black-alpha-48);
|
||||
letter-spacing: .02em;
|
||||
}
|
||||
.shell-account-menu button {
|
||||
width: 100%;
|
||||
min-height: 34px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 9px;
|
||||
padding: 0 9px;
|
||||
border: 0;
|
||||
border-radius: var(--r-sm);
|
||||
background: transparent;
|
||||
color: var(--accent-black);
|
||||
font-family: inherit;
|
||||
font-size: 13px;
|
||||
text-align: left;
|
||||
cursor: pointer;
|
||||
}
|
||||
.shell-account-menu button svg {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
color: var(--black-alpha-56);
|
||||
}
|
||||
.shell-account-menu button:hover {
|
||||
background: var(--black-alpha-4);
|
||||
color: var(--heat);
|
||||
}
|
||||
.shell-account-menu button:hover svg { color: var(--heat); }
|
||||
.shell-account-menu .sep {
|
||||
height: 1px;
|
||||
background: var(--border-faint);
|
||||
margin: 6px 4px;
|
||||
}
|
||||
|
||||
/* ─── Modal ─── */
|
||||
.modal-bg {
|
||||
position: fixed; inset: 0;
|
||||
|
||||
@ -44,6 +44,23 @@ const NAV = [
|
||||
}
|
||||
];
|
||||
|
||||
const SHELL_COMMANDS = [
|
||||
{ id: 'dashboard', group: '导航', label: '工作台', sub: '任务队列、今日消耗、项目进度', href: 'index.html', icon: 'dashboard', key: 'D' },
|
||||
{ id: 'products', group: '导航', label: '商品库', sub: '管理 SKU、商品图册、卖点信息', href: 'products.html', icon: 'package', key: 'P' },
|
||||
{ id: 'projects', group: '导航', label: '视频项目', sub: '查看五阶段短视频流水线', href: 'projects.html', icon: 'clapperboard', key: 'V' },
|
||||
{ id: 'asset-factory', group: '导航', label: '图片生成', sub: '模特上身图、平台套图、图片创作', href: 'asset-factory.html', icon: 'sparkles', key: 'I' },
|
||||
{ id: 'library', group: '导航', label: '资产库', sub: '素材、人物、场景、成片统一管理', href: 'library.html', icon: 'folder', key: 'A' },
|
||||
{ id: 'team', group: '导航', label: '团队', sub: '成员、权限、额度、协作记录', href: 'team.html', icon: 'users' },
|
||||
{ id: 'account', group: '导航', label: '消费', sub: '余额、充值、账单流水', href: 'account.html', icon: 'creditCard' },
|
||||
{ id: 'settings', group: '导航', label: '设置', sub: '个人信息、通知、安全、偏好', href: 'settings.html', icon: 'settings' },
|
||||
{ id: 'messages', group: '常用动作', label: '消息中心', sub: '任务提醒、协作评论、系统通知', href: 'messages.html', icon: 'bell', key: 'M' },
|
||||
{ id: 'new-product', group: '常用动作', label: '新建商品', sub: '从商品信息开始生成素材与视频', href: 'product-create.html', icon: 'productPlus' },
|
||||
{ id: 'new-project', group: '常用动作', label: '新建视频项目', sub: '选择商品并进入脚本配置', href: 'projects-new.html', icon: 'plus' },
|
||||
{ id: 'model-photo', group: '常用动作', label: '生成模特上身图', sub: '快速生成 3:4 商品展示素材', href: 'model-photo.html', icon: 'users' },
|
||||
{ id: 'platform-cover', group: '常用动作', label: '生成平台套图', sub: '适配电商平台封面与详情图', href: 'platform-cover.html', icon: 'images' },
|
||||
{ id: 'image-optimize', group: '常用动作', label: '图片创作', sub: '对话式生成、编辑、加入资产库', href: 'image-optimize.html', icon: 'image' },
|
||||
];
|
||||
|
||||
window.Shell = {
|
||||
render({ active = '', crumbs = [], balance = '¥327.40', topActions = '' } = {}) {
|
||||
const navHtml = NAV.map(n => `
|
||||
@ -59,15 +76,15 @@ window.Shell = {
|
||||
<div class="brand">
|
||||
<img class="brand-logo" src="assets/logo.png" alt="Airshelf">
|
||||
</div>
|
||||
<div class="search-box" onclick="document.getElementById('global-search').focus()">
|
||||
<div class="search-box" onclick="Shell.openCommandPalette()">
|
||||
${ShellIcon('search')}
|
||||
<input id="global-search" placeholder="搜索"/>
|
||||
<input id="global-search" placeholder="搜索" readonly aria-label="打开全局搜索"/>
|
||||
<span class="kbd">Ctrl K</span>
|
||||
</div>
|
||||
<div class="nav-section">主要</div>
|
||||
<nav>${navHtml}</nav>
|
||||
<div class="aside-foot">
|
||||
<div class="user" onclick="Shell.toast('账户菜单', 'li@shop.com')">
|
||||
<div class="user" onclick="Shell.toggleAccountMenu(event)">
|
||||
<div class="av">李</div>
|
||||
<div class="em">小李的店</div>
|
||||
</div>
|
||||
@ -103,7 +120,7 @@ window.Shell = {
|
||||
${ShellIcon('bell')}
|
||||
<span class="count-noti">12</span>
|
||||
</button>
|
||||
<div class="topbar-avatar" onclick="Shell.toast('账户菜单', '李 · li@shop.com')" title="账户">
|
||||
<div class="topbar-avatar" onclick="Shell.toggleAccountMenu(event)" title="账户">
|
||||
<span>李</span>
|
||||
</div>
|
||||
${topActions}
|
||||
@ -126,6 +143,43 @@ window.Shell = {
|
||||
</div>
|
||||
`;
|
||||
|
||||
const commandHtml = `
|
||||
<div class="shell-command-bg" id="shell-command-bg" aria-hidden="true">
|
||||
<div class="shell-command" role="dialog" aria-modal="true" aria-labelledby="shell-command-title">
|
||||
<div class="shell-command-h">
|
||||
<span class="ic">${ShellIcon('search')}</span>
|
||||
<input id="shell-command-input" autocomplete="off" placeholder="搜索页面、动作、项目入口" aria-label="搜索命令">
|
||||
<button class="shell-command-close" type="button" id="shell-command-close">ESC</button>
|
||||
</div>
|
||||
<div class="shell-command-list" id="shell-command-list"></div>
|
||||
<div class="shell-command-foot">
|
||||
<span id="shell-command-title">COMMAND</span>
|
||||
<span>// Enter 打开 · Ctrl K 唤起</span>
|
||||
<span class="spacer"></span>
|
||||
<span id="shell-command-count">0 项</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
const accountMenuHtml = `
|
||||
<div class="shell-account-menu" id="shell-account-menu" aria-hidden="true">
|
||||
<div class="shell-account-head">
|
||||
<span class="av">李</span>
|
||||
<span>
|
||||
<span class="nm">小李的店</span>
|
||||
<span class="mail">li@shop.com</span>
|
||||
</span>
|
||||
</div>
|
||||
<button type="button" data-account-act="settings">${ShellIcon('settings')}个人设置</button>
|
||||
<button type="button" data-account-act="messages">${ShellIcon('bell')}消息中心</button>
|
||||
<button type="button" data-account-act="team">${ShellIcon('users')}团队管理</button>
|
||||
<button type="button" data-account-act="account">${ShellIcon('creditCard')}消费与余额</button>
|
||||
<div class="sep"></div>
|
||||
<button type="button" data-account-act="logout">${ShellIcon('arrowRight')}退出登录</button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// ─── 全局 Lightbox · 任意页面可用 Shell._openLightbox(src, name) ──
|
||||
const lightboxHtml = `
|
||||
<div class="np-lightbox" id="np-lightbox" onclick="Shell._closeLightbox()">
|
||||
@ -284,15 +338,203 @@ window.Shell = {
|
||||
}
|
||||
document.body.insertAdjacentHTML('beforeend', toastHtml);
|
||||
document.body.insertAdjacentHTML('beforeend', lightboxHtml);
|
||||
document.body.insertAdjacentHTML('beforeend', commandHtml);
|
||||
document.body.insertAdjacentHTML('beforeend', accountMenuHtml);
|
||||
|
||||
document.addEventListener('keydown', e => {
|
||||
if ((e.metaKey || e.ctrlKey) && e.key === 'k') {
|
||||
e.preventDefault();
|
||||
document.getElementById('global-search')?.focus();
|
||||
Shell.openCommandPalette();
|
||||
}
|
||||
});
|
||||
|
||||
this.enhanceSelects(document);
|
||||
this._bindGlobalChrome();
|
||||
},
|
||||
|
||||
_esc(s) {
|
||||
return String(s ?? '').replace(/[&<>"']/g, c => ({
|
||||
'&': '&', '<': '<', '>': '>', '"': '"', "'": '''
|
||||
})[c]);
|
||||
},
|
||||
|
||||
_bindGlobalChrome() {
|
||||
const input = document.getElementById('global-search');
|
||||
if (input && !input.dataset.shellBound) {
|
||||
input.dataset.shellBound = '1';
|
||||
input.addEventListener('focus', () => {
|
||||
input.blur();
|
||||
Shell.openCommandPalette();
|
||||
});
|
||||
input.addEventListener('keydown', e => {
|
||||
e.preventDefault();
|
||||
Shell.openCommandPalette();
|
||||
});
|
||||
}
|
||||
|
||||
const bg = document.getElementById('shell-command-bg');
|
||||
const closeBtn = document.getElementById('shell-command-close');
|
||||
const cmdInput = document.getElementById('shell-command-input');
|
||||
if (bg && !bg.dataset.shellBound) {
|
||||
bg.dataset.shellBound = '1';
|
||||
bg.addEventListener('click', e => {
|
||||
if (e.target === bg) Shell.closeCommandPalette();
|
||||
});
|
||||
closeBtn?.addEventListener('click', () => Shell.closeCommandPalette());
|
||||
cmdInput?.addEventListener('input', () => Shell.renderCommandList(cmdInput.value));
|
||||
cmdInput?.addEventListener('keydown', e => {
|
||||
if (e.key === 'Escape') {
|
||||
e.preventDefault();
|
||||
Shell.closeCommandPalette();
|
||||
return;
|
||||
}
|
||||
if (e.key !== 'Enter') return;
|
||||
const first = document.querySelector('#shell-command-list .shell-command-item');
|
||||
if (first) {
|
||||
e.preventDefault();
|
||||
Shell.runCommand(first.dataset.command);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const accountMenu = document.getElementById('shell-account-menu');
|
||||
if (accountMenu && !accountMenu.dataset.shellBound) {
|
||||
accountMenu.dataset.shellBound = '1';
|
||||
accountMenu.addEventListener('click', e => {
|
||||
const item = e.target.closest('[data-account-act]');
|
||||
if (!item) return;
|
||||
Shell.closeAccountMenu();
|
||||
const act = item.dataset.accountAct;
|
||||
const hrefs = {
|
||||
settings: 'settings.html',
|
||||
messages: 'messages.html',
|
||||
team: 'team.html',
|
||||
account: 'account.html',
|
||||
logout: 'login.html',
|
||||
};
|
||||
if (act === 'logout') {
|
||||
Shell.toast('已退出登录', '返回登录页');
|
||||
setTimeout(() => { location.href = hrefs.logout; }, 220);
|
||||
return;
|
||||
}
|
||||
if (hrefs[act]) location.href = hrefs[act];
|
||||
});
|
||||
}
|
||||
|
||||
if (!this._globalChromeDocBound) {
|
||||
this._globalChromeDocBound = true;
|
||||
document.addEventListener('click', e => {
|
||||
if (!e.target.closest('.shell-account-menu') && !e.target.closest('.topbar-avatar') && !e.target.closest('.user')) {
|
||||
Shell.closeAccountMenu();
|
||||
}
|
||||
});
|
||||
document.addEventListener('keydown', e => {
|
||||
if (e.key === 'Escape') {
|
||||
Shell.closeCommandPalette();
|
||||
Shell.closeAccountMenu();
|
||||
}
|
||||
});
|
||||
}
|
||||
this.renderCommandList('');
|
||||
},
|
||||
|
||||
openCommandPalette(query = '') {
|
||||
const bg = document.getElementById('shell-command-bg');
|
||||
const input = document.getElementById('shell-command-input');
|
||||
if (!bg || !input) return;
|
||||
this.closeAccountMenu();
|
||||
const wasOpen = bg.classList.contains('show');
|
||||
bg.classList.add('show');
|
||||
bg.setAttribute('aria-hidden', 'false');
|
||||
input.value = query;
|
||||
this.renderCommandList(query);
|
||||
if (!wasOpen) this.lockScroll();
|
||||
requestAnimationFrame(() => input.focus());
|
||||
},
|
||||
|
||||
closeCommandPalette() {
|
||||
const bg = document.getElementById('shell-command-bg');
|
||||
if (!bg || !bg.classList.contains('show')) return;
|
||||
bg.classList.remove('show');
|
||||
bg.setAttribute('aria-hidden', 'true');
|
||||
this.unlockScroll();
|
||||
},
|
||||
|
||||
renderCommandList(query = '') {
|
||||
const list = document.getElementById('shell-command-list');
|
||||
const count = document.getElementById('shell-command-count');
|
||||
if (!list) return;
|
||||
const q = String(query || '').trim().toLowerCase();
|
||||
const items = SHELL_COMMANDS.filter(cmd => {
|
||||
if (!q) return true;
|
||||
return [cmd.label, cmd.sub, cmd.group, cmd.id].join(' ').toLowerCase().includes(q);
|
||||
});
|
||||
if (count) count.textContent = items.length + ' 项';
|
||||
if (!items.length) {
|
||||
list.innerHTML = `<div class="shell-command-empty">${ShellIcon('search')}<span>没有匹配的入口</span><span class="shell-command-section">// 换个关键词试试</span></div>`;
|
||||
return;
|
||||
}
|
||||
let lastGroup = '';
|
||||
list.innerHTML = items.map((cmd, i) => {
|
||||
const section = cmd.group !== lastGroup ? `<div class="shell-command-section">${this._esc(cmd.group)}</div>` : '';
|
||||
lastGroup = cmd.group;
|
||||
return section + `
|
||||
<button class="shell-command-item${i === 0 ? ' active' : ''}" type="button" data-command="${this._esc(cmd.id)}">
|
||||
<span class="cmd-ic">${ShellIcon(cmd.icon)}</span>
|
||||
<span class="cmd-main">
|
||||
<span class="cmd-title">${this._esc(cmd.label)}</span>
|
||||
<span class="cmd-sub">${this._esc(cmd.sub)}</span>
|
||||
</span>
|
||||
${cmd.key ? `<span class="cmd-key">${this._esc(cmd.key)}</span>` : ''}
|
||||
</button>
|
||||
`;
|
||||
}).join('');
|
||||
list.querySelectorAll('.shell-command-item').forEach(btn => {
|
||||
btn.addEventListener('click', () => Shell.runCommand(btn.dataset.command));
|
||||
});
|
||||
},
|
||||
|
||||
runCommand(id) {
|
||||
const cmd = SHELL_COMMANDS.find(item => item.id === id);
|
||||
if (!cmd) return;
|
||||
this.closeCommandPalette();
|
||||
if (cmd.href) {
|
||||
location.href = cmd.href;
|
||||
return;
|
||||
}
|
||||
Shell.toast('已执行', cmd.label);
|
||||
},
|
||||
|
||||
toggleAccountMenu(event) {
|
||||
event?.stopPropagation?.();
|
||||
const menu = document.getElementById('shell-account-menu');
|
||||
const anchor = event?.currentTarget;
|
||||
if (!menu || !anchor) return;
|
||||
const shouldOpen = !menu.classList.contains('show');
|
||||
this.closeCommandPalette();
|
||||
if (!shouldOpen) {
|
||||
this.closeAccountMenu();
|
||||
return;
|
||||
}
|
||||
const rect = anchor.getBoundingClientRect();
|
||||
menu.classList.add('show');
|
||||
menu.setAttribute('aria-hidden', 'false');
|
||||
const width = menu.offsetWidth || 232;
|
||||
const height = menu.offsetHeight || 260;
|
||||
let left = rect.right - width;
|
||||
if (left < 12) left = rect.left;
|
||||
left = Math.min(Math.max(12, left), window.innerWidth - width - 12);
|
||||
let top = rect.bottom + 8;
|
||||
if (top + height > window.innerHeight - 12) top = Math.max(12, rect.top - height - 8);
|
||||
menu.style.left = left + 'px';
|
||||
menu.style.top = top + 'px';
|
||||
},
|
||||
|
||||
closeAccountMenu() {
|
||||
const menu = document.getElementById('shell-account-menu');
|
||||
if (!menu) return;
|
||||
menu.classList.remove('show');
|
||||
menu.setAttribute('aria-hidden', 'true');
|
||||
},
|
||||
|
||||
enhanceSelects(root = document) {
|
||||
|
||||
@ -540,25 +540,25 @@ code.tk.heat { background: var(--heat-12); color: var(--heat); border-color: var
|
||||
|
||||
/* === Audit table (§7 待统一清单) === */
|
||||
.audit-table {
|
||||
width: 100%; border-collapse: collapse;
|
||||
width: 100%; border-collapse: separate; border-spacing: 0;
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--border-faint);
|
||||
border: 1px solid var(--border-muted);
|
||||
border-radius: var(--r-md); overflow: hidden;
|
||||
margin-bottom: 22px;
|
||||
}
|
||||
.audit-table th, .audit-table td {
|
||||
padding: 12px 16px; text-align: left;
|
||||
font-size: 12.5px; color: var(--accent-black);
|
||||
border-bottom: 1px solid var(--border-faint);
|
||||
border-bottom: 0;
|
||||
vertical-align: top;
|
||||
}
|
||||
.audit-table th {
|
||||
border-bottom: 1px solid var(--border-muted);
|
||||
font-family: var(--font-mono); font-size: 11px;
|
||||
font-weight: 500; color: var(--black-alpha-56);
|
||||
letter-spacing: .04em; text-transform: uppercase;
|
||||
background: var(--black-alpha-3);
|
||||
}
|
||||
.audit-table tbody tr:last-child td { border-bottom: 0; }
|
||||
.audit-table td:first-child { font-family: var(--font-mono); font-size: 12px; color: var(--heat); width: 32px; }
|
||||
.audit-table td.problem { color: var(--accent-crimson); font-weight: 500; }
|
||||
.audit-table td.now { font-family: var(--font-mono); font-size: 11.5px; color: var(--black-alpha-64); }
|
||||
@ -2779,7 +2779,7 @@ code.tk.heat { background: var(--heat-12); color: var(--heat); border-color: var
|
||||
<div class="desc">锁视口高度 · 多栏内部滚 · 大量页面级独有样式。<strong style="color:var(--accent-crimson)">⚠️ 这类定制最多 · 改之前先确认 restraint.css 没现成组件</strong>。</div>
|
||||
<pre class="ascii">[ 左栏 · 资产/导航 sticky ] [ 中央 · 画布/参数 ] [ 右栏 · 预览/AI 助手 ]
|
||||
└───────── 整页 viewport 高度锁定 · 不滚 main ────────┘</pre>
|
||||
<div class="pages">代表页:<strong>pipeline.html · model-photo.html · image-optimize.html · studio.html</strong></div>
|
||||
<div class="pages">代表页:<strong>pipeline.html · model-photo.html · platform-cover.html · image-optimize.html</strong></div>
|
||||
</div>
|
||||
|
||||
<div class="layout-card">
|
||||
|
||||
@ -467,6 +467,18 @@ Disabled: 底 `--black-alpha-5` + 边 `--black-alpha-12` + 半透明。
|
||||
- 行间 1 px 分隔(`--border-faint`),最后行去
|
||||
- Hover: `--black-alpha-4` 底色
|
||||
|
||||
#### §4.7.1 表格(`table.t` / 数据表)
|
||||
|
||||
**适用:** 项目列表 / 任务中心 / 成员表 / 消费流水等需要横向扫描的数据表。
|
||||
|
||||
- 外层只保留 1 px `--border-muted` + 8 px 圆角,形成清楚容器边界
|
||||
- 表头保留 1 px `--border-muted` 下分割线,表头底色用 `--background-lighter`
|
||||
- **tbody 行与行之间不画横向分割线**;用 `padding`、留白、hover 底色区分行
|
||||
- 行 hover 只用 `--black-alpha-4`,不加阴影、不换 hue
|
||||
- 表格内状态优先用 `.pill-l2` / `.pill`,行末操作用 `.btn-sm` 或 icon button
|
||||
|
||||
**禁:** 表格 tbody 每行密集 `border-bottom`、双层外框、白色/裸 hex 边框、用强阴影分隔行。
|
||||
|
||||
### §4.8 Tabs(主 / 副)
|
||||
|
||||
**主 Tab(下划线激活):**
|
||||
|
||||
@ -16,8 +16,13 @@
|
||||
flex: 1; min-height: 0;
|
||||
display: grid;
|
||||
grid-template-columns: 240px 1fr;
|
||||
transition: grid-template-columns var(--t-base);
|
||||
}
|
||||
.io-app.side-collapsed { grid-template-columns: 0 1fr; }
|
||||
@media (max-width: 1100px) {
|
||||
.io-app { grid-template-columns: 200px 1fr; }
|
||||
.io-app.side-collapsed { grid-template-columns: 0 1fr; }
|
||||
}
|
||||
@media (max-width: 1100px) { .io-app { grid-template-columns: 200px 1fr; } }
|
||||
|
||||
/* ========== 左 · 会话栏 ========== */
|
||||
.io-side {
|
||||
@ -25,6 +30,12 @@
|
||||
background: var(--surface);
|
||||
display: flex; flex-direction: column;
|
||||
min-height: 0; overflow: hidden;
|
||||
transition: opacity var(--t-base), transform var(--t-base);
|
||||
}
|
||||
.io-app.side-collapsed .io-side {
|
||||
opacity: 0;
|
||||
transform: translateX(-8px);
|
||||
pointer-events: none;
|
||||
}
|
||||
.io-side-h {
|
||||
display: flex; align-items: center; gap: 8px;
|
||||
@ -147,6 +158,74 @@
|
||||
background: var(--surface);
|
||||
}
|
||||
.io-toolbar .spacer { flex: 1; }
|
||||
.io-toolbar .side-restore-btn {
|
||||
height: 32px;
|
||||
padding: 0 10px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--border-faint);
|
||||
border-radius: var(--r-sm);
|
||||
color: var(--black-alpha-72);
|
||||
font-family: inherit;
|
||||
font-size: 12.5px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.io-toolbar .side-restore-btn:hover { border-color: var(--heat-20); color: var(--heat); }
|
||||
.io-toolbar .side-restore-btn[hidden] { display: none; }
|
||||
.io-toolbar .side-restore-btn svg { width: 14px; height: 14px; }
|
||||
.io-toolbar-search {
|
||||
position: relative;
|
||||
width: min(320px, 32vw);
|
||||
min-width: 220px;
|
||||
}
|
||||
.io-toolbar-search[hidden] { display: none; }
|
||||
.io-toolbar-search svg {
|
||||
position: absolute;
|
||||
left: 10px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
width: 13px;
|
||||
height: 13px;
|
||||
color: var(--black-alpha-48);
|
||||
pointer-events: none;
|
||||
}
|
||||
.io-toolbar-search input {
|
||||
width: 100%;
|
||||
height: 32px;
|
||||
padding: 0 32px 0 30px;
|
||||
background: var(--background-lighter);
|
||||
border: 1px solid var(--border-faint);
|
||||
border-radius: var(--r-sm);
|
||||
color: var(--accent-black);
|
||||
font-family: inherit;
|
||||
font-size: 12.5px;
|
||||
outline: none;
|
||||
}
|
||||
.io-toolbar-search input:focus { border-color: var(--heat-40); background: var(--surface); }
|
||||
.io-toolbar-search .clear-search {
|
||||
position: absolute;
|
||||
right: 4px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border: 0;
|
||||
border-radius: var(--r-sm);
|
||||
background: transparent;
|
||||
color: var(--black-alpha-48);
|
||||
cursor: pointer;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
}
|
||||
.io-toolbar-search .clear-search:hover { background: var(--black-alpha-4); color: var(--accent-black); }
|
||||
.io-toolbar-search .clear-search svg {
|
||||
position: static;
|
||||
transform: none;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
}
|
||||
.io-toolbar .search-btn {
|
||||
width: 32px; height: 32px;
|
||||
background: var(--surface);
|
||||
@ -157,7 +236,9 @@
|
||||
display: grid; place-items: center;
|
||||
}
|
||||
.io-toolbar .search-btn:hover { border-color: var(--heat-20); color: var(--heat); }
|
||||
.io-toolbar .search-btn.active { background: var(--heat-12); border-color: var(--heat-20); color: var(--heat); }
|
||||
.io-toolbar .search-btn svg { width: 14px; height: 14px; }
|
||||
.io-tool-filter { position: relative; display: inline-flex; }
|
||||
.io-toolbar .tb-chip {
|
||||
display: inline-flex; align-items: center; gap: 6px;
|
||||
height: 32px; padding: 0 10px;
|
||||
@ -169,7 +250,48 @@
|
||||
transition: border-color var(--t-base), color var(--t-base);
|
||||
}
|
||||
.io-toolbar .tb-chip:hover { border-color: var(--heat-20); color: var(--heat); }
|
||||
.io-toolbar .tb-chip.active { background: var(--heat-12); border-color: var(--heat-20); color: var(--heat); }
|
||||
.io-toolbar .tb-chip svg { width: 10px; height: 10px; opacity: .6; }
|
||||
.io-tool-filter.open .tb-chip svg { transform: rotate(180deg); }
|
||||
.io-tool-menu {
|
||||
position: absolute;
|
||||
top: calc(100% + 4px);
|
||||
right: 0;
|
||||
min-width: 156px;
|
||||
display: none;
|
||||
padding: 4px;
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--border-faint);
|
||||
border-radius: var(--r-md);
|
||||
box-shadow: var(--shadow-floating);
|
||||
z-index: 30;
|
||||
}
|
||||
.io-tool-filter.open .io-tool-menu { display: block; }
|
||||
.io-tool-menu .mi {
|
||||
width: 100%;
|
||||
min-height: 32px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 0 10px;
|
||||
border: 0;
|
||||
border-radius: var(--r-sm);
|
||||
background: transparent;
|
||||
color: var(--accent-black);
|
||||
font-family: inherit;
|
||||
font-size: 12.5px;
|
||||
text-align: left;
|
||||
cursor: pointer;
|
||||
}
|
||||
.io-tool-menu .mi:hover { background: var(--black-alpha-4); }
|
||||
.io-tool-menu .mi.selected { background: var(--heat-12); color: var(--heat); font-weight: 500; }
|
||||
.io-tool-menu .mi svg {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
opacity: 0;
|
||||
color: var(--heat);
|
||||
}
|
||||
.io-tool-menu .mi.selected svg { opacity: 1; }
|
||||
|
||||
/* 对话流主体 */
|
||||
.io-stream {
|
||||
@ -711,22 +833,33 @@
|
||||
<section class="io-main">
|
||||
|
||||
<div class="io-toolbar">
|
||||
<button class="side-restore-btn" type="button" id="io-side-restore" hidden title="展开会话栏">
|
||||
<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>
|
||||
<span class="spacer"></span>
|
||||
<button class="search-btn" type="button" title="搜索">
|
||||
<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">
|
||||
时间
|
||||
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M4 6l4 4 4-4"/></svg>
|
||||
</button>
|
||||
<button class="tb-chip" type="button">
|
||||
生成模式
|
||||
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M4 6l4 4 4-4"/></svg>
|
||||
</button>
|
||||
<button class="tb-chip" type="button">
|
||||
操作类型
|
||||
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M4 6l4 4 4-4"/></svg>
|
||||
</button>
|
||||
<div class="io-toolbar-search" id="io-toolbar-search" hidden>
|
||||
<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 id="io-toolbar-search-input" type="text" placeholder="搜索当前对话结果">
|
||||
<button class="clear-search" type="button" id="io-toolbar-search-clear" title="清空搜索">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"><path d="M18 6 6 18M6 6l12 12"/></svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="io-tool-filter" data-filter="time">
|
||||
<button class="tb-chip" type="button"><span data-filter-label>时间</span><svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M4 6l4 4 4-4"/></svg></button>
|
||||
<div class="io-tool-menu" data-filter-menu></div>
|
||||
</div>
|
||||
<div class="io-tool-filter" data-filter="mode">
|
||||
<button class="tb-chip" type="button"><span data-filter-label>生成模式</span><svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M4 6l4 4 4-4"/></svg></button>
|
||||
<div class="io-tool-menu" data-filter-menu></div>
|
||||
</div>
|
||||
<div class="io-tool-filter" data-filter="action">
|
||||
<button class="tb-chip" type="button"><span data-filter-label>操作类型</span><svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M4 6l4 4 4-4"/></svg></button>
|
||||
<div class="io-tool-menu" data-filter-menu></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="io-stream" id="io-stream">
|
||||
@ -893,6 +1026,13 @@ Shell.render({
|
||||
const convList = $('#io-conv-list');
|
||||
const newConvBtn = $('#io-new-conv');
|
||||
const jumpBtn = $('#io-jump-bottom');
|
||||
const ioApp = $('.io-app');
|
||||
const foldBtn = $('.io-side-h .fold');
|
||||
const sideRestore = $('#io-side-restore');
|
||||
const toolbarSearchBtn = $('.io-toolbar .search-btn');
|
||||
const toolbarSearch = $('#io-toolbar-search');
|
||||
const toolbarSearchInput = $('#io-toolbar-search-input');
|
||||
const toolbarSearchClear = $('#io-toolbar-search-clear');
|
||||
|
||||
function esc(s) { return String(s == null ? '' : s).replace(/[&<>"']/g, c => ({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c])); }
|
||||
function uid() { return 'm-' + Date.now().toString(36) + Math.random().toString(36).slice(2, 5); }
|
||||
@ -913,6 +1053,12 @@ Shell.render({
|
||||
messages: [], // 当前激活会话的对话流(从 convMessages 镜像出来)
|
||||
convMessages: { default: [] }, // 所有会话的 messages 按 convId 持久化
|
||||
refImages: [],
|
||||
toolbar: {
|
||||
q: '',
|
||||
time: 'all',
|
||||
mode: 'all',
|
||||
action: 'all',
|
||||
},
|
||||
param: {
|
||||
model: 'studio-v2',
|
||||
ratio: '1:1',
|
||||
@ -921,6 +1067,27 @@ Shell.render({
|
||||
},
|
||||
};
|
||||
|
||||
const TOOL_FILTERS = {
|
||||
time: [
|
||||
{ id: 'all', label: '时间' },
|
||||
{ id: 'today', label: '今天' },
|
||||
{ id: 'week', label: '近 7 天' },
|
||||
],
|
||||
mode: [
|
||||
{ id: 'all', label: '生成模式' },
|
||||
{ id: 'studio-v2', label: 'Airshelf v2' },
|
||||
{ id: 'studio-v2-pro', label: 'v2 Pro' },
|
||||
{ id: 'realistic', label: '写实增强' },
|
||||
{ id: 'anime', label: '国风动漫' },
|
||||
],
|
||||
action: [
|
||||
{ id: 'all', label: '操作类型' },
|
||||
{ id: 'ok', label: '已完成' },
|
||||
{ id: 'gen', label: '生成中' },
|
||||
{ id: 'err', label: '失败' },
|
||||
],
|
||||
};
|
||||
|
||||
function slimMessages(msgs) {
|
||||
// dataUrl 体积大,持久化时剥离;只保留元数据
|
||||
return (msgs || []).map(m => ({
|
||||
@ -1074,6 +1241,29 @@ Shell.render({
|
||||
}
|
||||
|
||||
/* ---------- 渲染:中央对话流 ---------- */
|
||||
function getVisibleMessages() {
|
||||
const t = state.toolbar;
|
||||
const q = (t.q || '').trim().toLowerCase();
|
||||
const now = Date.now();
|
||||
return state.messages.filter(m => {
|
||||
if (q) {
|
||||
const hay = [
|
||||
m.prompt,
|
||||
m.ratio,
|
||||
modelLabel(m.model),
|
||||
styleLabel(m.style),
|
||||
...(m.results || []).map(r => r.label || r.status),
|
||||
].join(' ').toLowerCase();
|
||||
if (!hay.includes(q)) return false;
|
||||
}
|
||||
if (t.time === 'today' && now - (m.createdAt || now) > 86400000) return false;
|
||||
if (t.time === 'week' && now - (m.createdAt || now) > 86400000 * 7) return false;
|
||||
if (t.mode !== 'all' && m.model !== t.mode) return false;
|
||||
if (t.action !== 'all' && !(m.results || []).some(r => r.status === t.action)) return false;
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
function renderStream() {
|
||||
if (!state.messages.length) {
|
||||
streamInner.innerHTML = `
|
||||
@ -1099,7 +1289,27 @@ Shell.render({
|
||||
});
|
||||
return;
|
||||
}
|
||||
streamInner.innerHTML = state.messages.map(messageHTML).join('');
|
||||
const visible = getVisibleMessages();
|
||||
if (!visible.length) {
|
||||
streamInner.innerHTML = `
|
||||
<div class="io-empty">
|
||||
<div class="ic">
|
||||
<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>
|
||||
</div>
|
||||
<div class="badge">// NO MATCH</div>
|
||||
<h2>没有符合筛选的结果</h2>
|
||||
<p>当前对话里没有匹配的批次,可以清空搜索或切回全部筛选。</p>
|
||||
<div class="examples"><button class="ex" type="button" id="io-clear-toolbar-filters">清空筛选</button></div>
|
||||
</div>`;
|
||||
$('#io-clear-toolbar-filters')?.addEventListener('click', () => {
|
||||
state.toolbar = { q: '', time: 'all', mode: 'all', action: 'all' };
|
||||
if (toolbarSearchInput) toolbarSearchInput.value = '';
|
||||
syncToolbarFilters();
|
||||
renderStream();
|
||||
});
|
||||
return;
|
||||
}
|
||||
streamInner.innerHTML = visible.map(messageHTML).join('');
|
||||
bindMessageEvents();
|
||||
}
|
||||
|
||||
@ -1608,6 +1818,9 @@ Shell.render({
|
||||
if (!e.target.closest('.io-input-bottom .param')) {
|
||||
document.querySelectorAll('.io-input-bottom .param.open').forEach(x => x.classList.remove('open'));
|
||||
}
|
||||
if (!e.target.closest('.io-tool-filter')) {
|
||||
document.querySelectorAll('.io-tool-filter.open').forEach(x => x.classList.remove('open'));
|
||||
}
|
||||
if (!e.target.closest('.cell-more-wrap')) {
|
||||
document.querySelectorAll('.cell-more-wrap.open').forEach(x => x.classList.remove('open'));
|
||||
}
|
||||
@ -1616,6 +1829,73 @@ Shell.render({
|
||||
}
|
||||
});
|
||||
|
||||
/* ---------- 顶部工具栏:折叠 / 搜索 / 筛选 ---------- */
|
||||
function setSideCollapsed(collapsed) {
|
||||
ioApp?.classList.toggle('side-collapsed', collapsed);
|
||||
if (sideRestore) sideRestore.hidden = !collapsed;
|
||||
try { localStorage.setItem('fs-io-side-collapsed', collapsed ? '1' : '0'); } catch (e) {}
|
||||
}
|
||||
foldBtn?.addEventListener('click', () => setSideCollapsed(true));
|
||||
sideRestore?.addEventListener('click', () => setSideCollapsed(false));
|
||||
|
||||
toolbarSearchBtn?.addEventListener('click', () => {
|
||||
const open = toolbarSearch?.hidden;
|
||||
if (toolbarSearch) toolbarSearch.hidden = !open;
|
||||
toolbarSearchBtn.classList.toggle('active', !!open || !!state.toolbar.q);
|
||||
if (open) requestAnimationFrame(() => toolbarSearchInput?.focus());
|
||||
});
|
||||
toolbarSearchInput?.addEventListener('input', e => {
|
||||
state.toolbar.q = e.target.value.trim();
|
||||
toolbarSearchBtn?.classList.toggle('active', !!state.toolbar.q || !toolbarSearch?.hidden);
|
||||
renderStream();
|
||||
});
|
||||
toolbarSearchClear?.addEventListener('click', () => {
|
||||
state.toolbar.q = '';
|
||||
if (toolbarSearchInput) toolbarSearchInput.value = '';
|
||||
toolbarSearchBtn?.classList.toggle('active', !toolbarSearch?.hidden);
|
||||
renderStream();
|
||||
toolbarSearchInput?.focus();
|
||||
});
|
||||
|
||||
function syncToolbarFilters() {
|
||||
document.querySelectorAll('.io-tool-filter').forEach(wrap => {
|
||||
const key = wrap.dataset.filter;
|
||||
const value = state.toolbar[key] || 'all';
|
||||
const items = TOOL_FILTERS[key] || [];
|
||||
const selected = items.find(x => x.id === value) || items[0];
|
||||
wrap.querySelector('[data-filter-label]').textContent = selected?.label || '';
|
||||
wrap.querySelector('.tb-chip')?.classList.toggle('active', value !== 'all');
|
||||
const menu = wrap.querySelector('[data-filter-menu]');
|
||||
if (!menu) return;
|
||||
menu.innerHTML = items.map(it => `
|
||||
<button class="mi${it.id === value ? ' selected' : ''}" type="button" data-val="${esc(it.id)}">
|
||||
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="3 8 7 12 13 4"/></svg>
|
||||
<span>${esc(it.label)}</span>
|
||||
</button>
|
||||
`).join('');
|
||||
});
|
||||
}
|
||||
|
||||
document.querySelectorAll('.io-tool-filter').forEach(wrap => {
|
||||
const btn = wrap.querySelector('.tb-chip');
|
||||
const menu = wrap.querySelector('[data-filter-menu]');
|
||||
btn?.addEventListener('click', e => {
|
||||
e.stopPropagation();
|
||||
const open = wrap.classList.contains('open');
|
||||
document.querySelectorAll('.io-tool-filter.open').forEach(x => x.classList.remove('open'));
|
||||
if (!open) wrap.classList.add('open');
|
||||
});
|
||||
menu?.addEventListener('click', e => {
|
||||
const item = e.target.closest('.mi');
|
||||
if (!item) return;
|
||||
e.stopPropagation();
|
||||
state.toolbar[wrap.dataset.filter] = item.dataset.val;
|
||||
wrap.classList.remove('open');
|
||||
syncToolbarFilters();
|
||||
renderStream();
|
||||
});
|
||||
});
|
||||
|
||||
/* ---------- 新对话 ---------- */
|
||||
newConvBtn.addEventListener('click', () => {
|
||||
// 若 default 上有内容,把它归档到「最近」(新 id + 转存 messages),然后清空 default
|
||||
@ -1671,6 +1951,8 @@ Shell.render({
|
||||
} catch (e) {}
|
||||
buildParamMenus();
|
||||
syncParamLabels();
|
||||
syncToolbarFilters();
|
||||
try { setSideCollapsed(localStorage.getItem('fs-io-side-collapsed') === '1'); } catch (e) {}
|
||||
updateCost();
|
||||
renderRefs();
|
||||
syncSendDisabled();
|
||||
|
||||
@ -2370,6 +2370,17 @@ document.querySelectorAll('.asset-card').forEach(card => {
|
||||
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="1.5" stroke-linecap="round"><path d="M8 3v10M3 8h10"/></svg></button>';
|
||||
const tagAddBtn = tagsEl.querySelector('.ad-tag-add');
|
||||
tagAddBtn?.addEventListener('click', e => {
|
||||
e.stopPropagation();
|
||||
const existing = [...tagsEl.querySelectorAll('.ad-tag-chip')].map(el => el.textContent);
|
||||
const next = ['精选素材', '常用资产', '待复用', '已核准'].find(t => !existing.includes(t)) || '新标签';
|
||||
const chip = document.createElement('span');
|
||||
chip.className = 'ad-tag-chip';
|
||||
chip.textContent = next;
|
||||
tagsEl.insertBefore(chip, tagAddBtn);
|
||||
Shell.toast('已添加标签', next);
|
||||
});
|
||||
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');
|
||||
@ -2426,6 +2437,13 @@ document.querySelectorAll('.asset-card').forEach(card => {
|
||||
close(true);
|
||||
});
|
||||
|
||||
bg.querySelectorAll('.ad-icon-btn, .ad-stat-btn').forEach(btn => {
|
||||
btn.addEventListener('click', e => {
|
||||
e.stopPropagation();
|
||||
Shell.toast('已加入下载', (_curName || '资产') + ' · 原图 / 三视图');
|
||||
});
|
||||
});
|
||||
|
||||
// 弹窗按钮 · 主按钮「保存并退出」/ 次按钮「退出」
|
||||
confirmSaveBtn.addEventListener('click', () => {
|
||||
if (_versions.length) _doSave();
|
||||
|
||||
1391
电商AI平台/messages.html
1391
电商AI平台/messages.html
File diff suppressed because it is too large
Load Diff
@ -15,9 +15,12 @@
|
||||
flex: 1; min-height: 0;
|
||||
display: grid;
|
||||
grid-template-columns: 260px 1fr;
|
||||
transition: grid-template-columns var(--t-base);
|
||||
}
|
||||
.mp-layout.side-collapsed { grid-template-columns: 0 1fr; }
|
||||
@media (max-width: 1280px) {
|
||||
.mp-layout { grid-template-columns: 240px 1fr; }
|
||||
.mp-layout.side-collapsed { grid-template-columns: 0 1fr; }
|
||||
}
|
||||
|
||||
/* ─── 主区: flat 双区 (头部 + body) · 无 card · 用 border 分隔 ─── */
|
||||
@ -55,6 +58,23 @@
|
||||
color: var(--black-alpha-48);
|
||||
}
|
||||
.mp-main-h .spacer { flex: 1; }
|
||||
.mp-main-h .side-restore-btn {
|
||||
height: 32px;
|
||||
padding: 0 10px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--border-faint);
|
||||
border-radius: var(--r-sm);
|
||||
color: var(--black-alpha-72);
|
||||
font-family: inherit;
|
||||
font-size: 12.5px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.mp-main-h .side-restore-btn:hover { border-color: var(--heat-20); color: var(--heat); }
|
||||
.mp-main-h .side-restore-btn[hidden] { display: none; }
|
||||
.mp-main-h .side-restore-btn svg { width: 14px; height: 14px; }
|
||||
.mp-main-h .search-btn {
|
||||
width: 32px; height: 32px;
|
||||
background: var(--surface);
|
||||
@ -177,6 +197,12 @@
|
||||
display: flex; flex-direction: column;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
transition: opacity var(--t-base), transform var(--t-base);
|
||||
}
|
||||
.mp-layout.side-collapsed .mp-prod-space {
|
||||
opacity: 0;
|
||||
transform: translateX(-8px);
|
||||
pointer-events: none;
|
||||
}
|
||||
/* 顶部工具栏: 返回 + 折叠 (跟 image-optimize 视觉一致) */
|
||||
.mp-side-top {
|
||||
@ -2224,6 +2250,10 @@
|
||||
|
||||
<!-- 主区顶部 · toolbar (商品标题 + 搜索 + 筛选 · 跟图片创作一致) -->
|
||||
<div class="mp-main-h">
|
||||
<button class="side-restore-btn" type="button" id="mp-side-restore" hidden title="展开商品空间">
|
||||
<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 class="cur-title">
|
||||
<span class="crumb">// 商品空间</span>
|
||||
<span class="nm placeholder" id="cur-prod-nm">未选择 · 请在左侧商品空间选一个</span>
|
||||
@ -4737,6 +4767,22 @@ document.getElementById('pv-swap').addEventListener('click', () => {
|
||||
tasks = load();
|
||||
})();
|
||||
|
||||
/* ---------- 商品空间折叠 / 展开 ---------- */
|
||||
(function () {
|
||||
const layout = document.querySelector('.mp-layout');
|
||||
const foldBtn = document.querySelector('.mp-side-top .fold');
|
||||
const restoreBtn = document.getElementById('mp-side-restore');
|
||||
function setSideCollapsed(collapsed) {
|
||||
layout?.classList.toggle('side-collapsed', collapsed);
|
||||
if (restoreBtn) restoreBtn.hidden = !collapsed;
|
||||
if (foldBtn) foldBtn.setAttribute('aria-expanded', collapsed ? 'false' : 'true');
|
||||
try { localStorage.setItem('fs-mp-side-collapsed', collapsed ? '1' : '0'); } catch (e) {}
|
||||
}
|
||||
foldBtn?.addEventListener('click', () => setSideCollapsed(true));
|
||||
restoreBtn?.addEventListener('click', () => setSideCollapsed(false));
|
||||
try { setSideCollapsed(localStorage.getItem('fs-mp-side-collapsed') === '1'); } catch (e) {}
|
||||
})();
|
||||
|
||||
// 初始化
|
||||
renderProdSpace();
|
||||
renderSelectedProds();
|
||||
|
||||
@ -14,9 +14,12 @@
|
||||
flex: 1; min-height: 0;
|
||||
display: grid;
|
||||
grid-template-columns: 260px 1fr;
|
||||
transition: grid-template-columns var(--t-base);
|
||||
}
|
||||
.pc-layout.side-collapsed { grid-template-columns: 0 1fr; }
|
||||
@media (max-width: 1280px) {
|
||||
.pc-layout { grid-template-columns: 240px 1fr; }
|
||||
.pc-layout.side-collapsed { grid-template-columns: 0 1fr; }
|
||||
}
|
||||
|
||||
/* ─── 主区: flat 双区 · 用 border 分隔 ─── */
|
||||
@ -53,6 +56,23 @@
|
||||
color: var(--black-alpha-48);
|
||||
}
|
||||
.pc-main-h .spacer { flex: 1; }
|
||||
.pc-main-h .side-restore-btn {
|
||||
height: 32px;
|
||||
padding: 0 10px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--border-faint);
|
||||
border-radius: var(--r-sm);
|
||||
color: var(--black-alpha-72);
|
||||
font-family: inherit;
|
||||
font-size: 12.5px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.pc-main-h .side-restore-btn:hover { border-color: var(--heat-20); color: var(--heat); }
|
||||
.pc-main-h .side-restore-btn[hidden] { display: none; }
|
||||
.pc-main-h .side-restore-btn svg { width: 14px; height: 14px; }
|
||||
.pc-main-h .search-btn {
|
||||
width: 32px; height: 32px;
|
||||
background: var(--surface);
|
||||
@ -170,6 +190,12 @@
|
||||
display: flex; flex-direction: column;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
transition: opacity var(--t-base), transform var(--t-base);
|
||||
}
|
||||
.pc-layout.side-collapsed .pc-prod-space {
|
||||
opacity: 0;
|
||||
transform: translateX(-8px);
|
||||
pointer-events: none;
|
||||
}
|
||||
/* 顶部工具栏: 返回 + 折叠 (跟 image-optimize 视觉一致) */
|
||||
.pc-side-top {
|
||||
@ -1159,6 +1185,10 @@
|
||||
|
||||
<!-- 主区顶部 · toolbar (商品标题 + 搜索 + 筛选 · 跟图片创作一致) -->
|
||||
<div class="pc-main-h">
|
||||
<button class="side-restore-btn" type="button" id="pc-side-restore" hidden title="展开商品空间">
|
||||
<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 class="cur-title">
|
||||
<span class="crumb">// 商品空间</span>
|
||||
<span class="nm placeholder" id="cur-prod-nm">未选择 · 请在左侧商品空间选一个</span>
|
||||
@ -2297,6 +2327,22 @@ document.addEventListener('click', e => {
|
||||
tasks = load();
|
||||
})();
|
||||
|
||||
/* ---------- 商品空间折叠 / 展开 ---------- */
|
||||
(function () {
|
||||
const layout = document.querySelector('.pc-layout');
|
||||
const foldBtn = document.querySelector('.pc-side-top .fold');
|
||||
const restoreBtn = document.getElementById('pc-side-restore');
|
||||
function setSideCollapsed(collapsed) {
|
||||
layout?.classList.toggle('side-collapsed', collapsed);
|
||||
if (restoreBtn) restoreBtn.hidden = !collapsed;
|
||||
if (foldBtn) foldBtn.setAttribute('aria-expanded', collapsed ? 'false' : 'true');
|
||||
try { localStorage.setItem('fs-pc-side-collapsed', collapsed ? '1' : '0'); } catch (e) {}
|
||||
}
|
||||
foldBtn?.addEventListener('click', () => setSideCollapsed(true));
|
||||
restoreBtn?.addEventListener('click', () => setSideCollapsed(false));
|
||||
try { setSideCollapsed(localStorage.getItem('fs-pc-side-collapsed') === '1'); } catch (e) {}
|
||||
})();
|
||||
|
||||
// 初始
|
||||
renderProdSpace();
|
||||
renderSelectedProds();
|
||||
|
||||
@ -24,7 +24,7 @@
|
||||
/* ============================================================
|
||||
新建商品 · 重定向 stub
|
||||
----------------------------------------------------------
|
||||
旧版本是一个 3883 行的全屏 drawer 页(legacy 备份在 product-create.legacy.html)。
|
||||
旧版本是一个 3883 行的全屏 drawer 页(legacy 已归档到 _archive/deprecated-pages-20260528/)。
|
||||
现在「新建商品」改为 drawer 形态在原页面弹出,直接访问此 URL 没有意义。
|
||||
|
||||
行为:
|
||||
|
||||
@ -147,7 +147,6 @@
|
||||
<div class="tab" data-filter="wip">进行中 <span class="count">0</span></div>
|
||||
<div class="tab" data-filter="done">已完成 <span class="count">0</span></div>
|
||||
<div class="tab" data-filter="fail">失败 <span class="count">0</span></div>
|
||||
<div class="tab" data-filter="archived">已归档 <span class="count">0</span></div>
|
||||
</div>
|
||||
|
||||
<div class="toolbar">
|
||||
@ -216,7 +215,7 @@
|
||||
<span class="muted-2 mono" style="font-size:11px;">3/5</span>
|
||||
</div>
|
||||
</td>
|
||||
<td><span class="pill info"><span class="dot"></span>故事板 待确认</span></td>
|
||||
<td><span class="pill info"><span class="dot"></span>故事板生成中</span></td>
|
||||
<td class="muted-2">12 分钟前</td>
|
||||
<td>
|
||||
<div class="row-action">
|
||||
@ -339,25 +338,6 @@
|
||||
<td class="muted-2">5 月 4 日</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr data-status="archived" data-name="补水面膜 痛点种草 v1" onclick="location.href='pipeline.html#stage-5'">
|
||||
<td>
|
||||
<div class="proj-name-cell">
|
||||
<div class="placeholder proj-thumb"><span class="ph-frame">9:16</span></div>
|
||||
<div><div class="proj-name">补水面膜 · 痛点种草 · v1</div><div class="proj-sub">6 镜 · 0-15s</div></div>
|
||||
</div>
|
||||
</td>
|
||||
<td>透真补水面膜</td>
|
||||
<td><span class="muted">AI 全生</span></td>
|
||||
<td>
|
||||
<div class="hstack">
|
||||
<div class="prog"><span class="done"></span><span class="done"></span><span class="done"></span><span class="done"></span><span class="done"></span></div>
|
||||
<span class="muted-2 mono" style="font-size:11px;">5/5</span>
|
||||
</div>
|
||||
</td>
|
||||
<td><span class="pill neutral"><span class="dot"></span>已归档</span></td>
|
||||
<td class="muted-2">4 月 28 日</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@ -379,7 +359,7 @@
|
||||
<span class="muted-2 mono" style="font-size:10.5px;">3/5</span>
|
||||
</div>
|
||||
<div class="card-foot">
|
||||
<span class="pill info"><span class="dot"></span>故事板 待确认</span>
|
||||
<span class="pill info"><span class="dot"></span>故事板生成中</span>
|
||||
<span class="card-time">12 分钟前</span>
|
||||
</div>
|
||||
</div>
|
||||
@ -505,25 +485,6 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="proj-card" data-status="archived" data-name="补水面膜 痛点种草 v1" onclick="location.href='pipeline.html#stage-5'">
|
||||
<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-project"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><path d="M3 6h18"/><path d="M8 6V4a2 2 0 012-2h4a2 2 0 012 2v2"/><path d="M19 6l-1.5 14a2 2 0 01-2 1.8H8.5a2 2 0 01-2-1.8L5 6"/></svg></button>
|
||||
<div class="placeholder card-thumb"><span class="ph-frame">9:16 · 5/5 ✓</span></div>
|
||||
<div class="card-body">
|
||||
<div>
|
||||
<div class="card-name">补水面膜 · 痛点种草 · v1</div>
|
||||
<div class="card-sub" style="margin-top:4px;">透真补水面膜 · 6 镜</div>
|
||||
</div>
|
||||
<div class="hstack">
|
||||
<div class="prog"><span class="done"></span><span class="done"></span><span class="done"></span><span class="done"></span><span class="done"></span></div>
|
||||
<span class="muted-2 mono" style="font-size:10.5px;">5/5</span>
|
||||
</div>
|
||||
<div class="card-foot">
|
||||
<span class="pill neutral"><span class="dot"></span>已归档</span>
|
||||
<span class="card-time">4 月 28 日</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -665,7 +626,7 @@ Shell.render({ active: 'projects', crumbs: [{ label: '工作台', href: 'index.h
|
||||
|
||||
// ============== 从 DOM 实算项目计数,同步副标题 + tab 角标 ==============
|
||||
const allRows = document.querySelectorAll('#list-tbody tr');
|
||||
const counts = { total: allRows.length, wip: 0, done: 0, fail: 0, archived: 0 };
|
||||
const counts = { total: allRows.length, wip: 0, done: 0, fail: 0 };
|
||||
allRows.forEach(r => { const s = r.dataset.status; if (counts[s] !== undefined) counts[s]++; });
|
||||
|
||||
document.getElementById('sub-total').textContent = counts.total;
|
||||
@ -901,13 +862,13 @@ document.getElementById('search-input').addEventListener('input', e => {
|
||||
applyFilter();
|
||||
});
|
||||
|
||||
// 从 URL ?filter=wip|done|fail|archived|all 接收外部跳转 (例如工作台 stat 卡)
|
||||
// 从 URL ?filter=wip|done|fail|all 接收外部跳转 (例如工作台 stat 卡)
|
||||
(function applyUrlFilter() {
|
||||
try {
|
||||
const q = new URLSearchParams(location.search);
|
||||
const f = q.get('filter');
|
||||
if (!f) return;
|
||||
const valid = ['all', 'wip', 'done', 'fail', 'archived'];
|
||||
const valid = ['all', 'wip', 'done', 'fail'];
|
||||
if (!valid.includes(f)) return;
|
||||
state.filter = f;
|
||||
document.querySelectorAll('#status-tabs .tab').forEach(t =>
|
||||
|
||||
@ -111,9 +111,9 @@
|
||||
.members-table tr.pending .nm::after { content: '· 待激活'; font-size: 11px; color: var(--black-alpha-48); margin-left: 6px; font-weight: 400; font-family: var(--font-mono); }
|
||||
|
||||
/* ─── 角色权限矩阵 ─── */
|
||||
.perm-table { width: 100%; border-collapse: collapse; font-size: 12.5px; }
|
||||
.perm-table th, .perm-table td { padding: 8px 4px; border-bottom: 1px solid var(--border-faint); }
|
||||
.perm-table th { font-family: var(--font-mono); font-size: 10.5px; font-weight: 500; color: var(--black-alpha-48); letter-spacing: .04em; text-transform: uppercase; text-align: left; }
|
||||
.perm-table { width: 100%; border-collapse: separate; border-spacing: 0; background: var(--surface); border: 1px solid var(--border-muted); border-radius: var(--r-md); overflow: hidden; font-size: 12.5px; }
|
||||
.perm-table th, .perm-table td { padding: 8px 10px; border-bottom: 0; }
|
||||
.perm-table th { border-bottom: 1px solid var(--border-muted); background: var(--background-lighter); font-family: var(--font-mono); font-size: 10.5px; font-weight: 500; color: var(--black-alpha-48); letter-spacing: .04em; text-transform: uppercase; text-align: left; }
|
||||
.perm-table th:not(:first-child), .perm-table td:not(:first-child) { text-align: center; }
|
||||
.perm-table tbody td:first-child { color: var(--accent-black); }
|
||||
.perm-table .yes { color: var(--accent-forest); font-weight: 600; }
|
||||
|
||||
400
电商AI平台/页面流程定稿.md
Normal file
400
电商AI平台/页面流程定稿.md
Normal file
@ -0,0 +1,400 @@
|
||||
# Airshelf 页面流程定稿记录
|
||||
|
||||
> 本文件用于记录和用户逐页确认后的产品页面流程。
|
||||
> PRD 只作为功能边界参考,页面布局与交互以本文件后续定稿为准。
|
||||
|
||||
维护日期:2026-05-28
|
||||
|
||||
---
|
||||
|
||||
## 0. 总体原则
|
||||
|
||||
- 创作视频是产品主线任务。
|
||||
- 当前版本单条生成视频的时长上限是 15s。
|
||||
- AI 图片创作是附加能力,用于辅助商品图、人物、场景等素材生产,不抢视频主线。
|
||||
- 页面跳转尽量少,能在当前页面完成的操作尽量不跳转。
|
||||
- 操作尽量简单,第一次使用 AI 产品的用户也应该能理解。
|
||||
- 5 个 Stage 是步骤引导,不是强制锁定流程。
|
||||
- 用户可以在脚本、基础资产、故事板、视频、拼接导出之间自由跳转。
|
||||
- 如果某个 Stage 缺少前置内容,页面给提示和快捷补全入口,不做硬锁死。
|
||||
- 暂不做复制项目、归档、审核、待审核、待我确认等复杂项目管理概念。
|
||||
|
||||
---
|
||||
|
||||
## 1. 视频项目页 `/projects`
|
||||
|
||||
### 页面定位
|
||||
|
||||
视频项目页是视频生产任务列表,帮助用户看清每个视频项目做到哪一步,以及下一步能点什么。
|
||||
|
||||
### 不做内容
|
||||
|
||||
- 不做复制项目。
|
||||
- 不做归档。
|
||||
- 不做审核 / 待审核 / 待我确认。
|
||||
- 不做草稿分类。
|
||||
- 不做复杂批量操作。
|
||||
- AI 图片创作不作为顶部主按钮。
|
||||
|
||||
### 状态分类
|
||||
|
||||
只保留:
|
||||
|
||||
- 全部
|
||||
- 进行中
|
||||
- 生成中
|
||||
- 已完成
|
||||
- 失败
|
||||
|
||||
### 页面结构
|
||||
|
||||
1. Page Head
|
||||
- 标题:视频项目
|
||||
- 主按钮:新建视频项目
|
||||
|
||||
2. 状态概览 / Tab
|
||||
- 全部
|
||||
- 进行中
|
||||
- 生成中
|
||||
- 已完成
|
||||
- 失败
|
||||
|
||||
3. 工具栏
|
||||
- 搜索项目 / 商品名
|
||||
- 商品筛选
|
||||
- 状态筛选
|
||||
- 最近更新排序
|
||||
- 可选:列表 / 卡片视图切换
|
||||
|
||||
4. 项目列表
|
||||
- 默认列表行,不默认大卡片。
|
||||
- 每行展示:
|
||||
- 9:16 缩略图
|
||||
- 项目名
|
||||
- 关联商品
|
||||
- 当前阶段
|
||||
- 5 段进度:脚本 / 资产 / 故事板 / 视频 / 导出
|
||||
- 更新时间
|
||||
- 状态
|
||||
- 主操作:继续 / 查看进度 / 查看成片 / 重试
|
||||
|
||||
5. 空状态
|
||||
- 没有商品:引导新建商品。
|
||||
- 有商品但没有项目:引导新建视频项目。
|
||||
|
||||
---
|
||||
|
||||
## 2. 新建视频项目页 `/projects/new`
|
||||
|
||||
### 页面定位
|
||||
|
||||
新建项目页只负责创建项目壳子,并绑定商品。
|
||||
脚本怎么生成、脚本风格、任务设定等全部放到 Stage 1 脚本页。
|
||||
|
||||
### 交互原则
|
||||
|
||||
- 一个页面内完成,不做多步跳转。
|
||||
- 只问用户:为哪个商品创建视频。
|
||||
- 创建后直接进入项目流水线的 Stage 1 脚本页。
|
||||
|
||||
### 字段
|
||||
|
||||
必选:
|
||||
|
||||
- 商品
|
||||
|
||||
可选:
|
||||
|
||||
- 项目名称
|
||||
- 默认自动生成,例如:商品名 + 短视频 + 日期。
|
||||
|
||||
不展示:
|
||||
|
||||
- 创作方式
|
||||
- 脚本风格
|
||||
- 任务设定
|
||||
- 商品卖点选择
|
||||
- 预估消耗
|
||||
|
||||
### 卖点选择归属
|
||||
|
||||
商品卖点不放在新建项目页。
|
||||
商品卖点选择放到 Stage 1 脚本页,作为“本条视频重点”配置项,从商品库自动带出。
|
||||
|
||||
---
|
||||
|
||||
## 3. Stage 1 脚本页
|
||||
|
||||
### 页面定位
|
||||
|
||||
脚本页是 AI 脚本工作台。
|
||||
用户通过 AI 输入和启动卡片完成脚本来源选择、卖点选择、风格设定、任务设定,并在中间区域查看完整读秒流分镜脚本。
|
||||
|
||||
### 核心原则
|
||||
|
||||
- 脚本是用户最关注的内容,必须放在中间最大区域。
|
||||
- 脚本不按 15s 大段展示。
|
||||
- 脚本按完整读秒流分镜展示,适合短视频细看。
|
||||
- AI 聊天不是主内容,而是脚本修改和生成记录。
|
||||
- AI 输入框放页面底部固定,作为当前 Stage 的全局指令入口。
|
||||
|
||||
### 页面布局
|
||||
|
||||
顶部:
|
||||
|
||||
- 项目面包屑
|
||||
- 商品名 / 项目名
|
||||
- 5 Stage 自由切换导航:脚本 / 基础资产 / 故事板 / 视频 / 拼接导出
|
||||
|
||||
主体:
|
||||
|
||||
- 左侧窄栏:商品信息 / 卖点 / 人物 / 场景等辅助信息
|
||||
- 中间主栏:读秒流分镜脚本
|
||||
- 右侧窄栏:AI 对话记录 / 操作历史
|
||||
|
||||
底部固定栏:
|
||||
|
||||
- AI 输入框
|
||||
- 页面操作按钮,例如重新生成全部、进入下一步
|
||||
|
||||
### 空状态引导
|
||||
|
||||
脚本来源不做 Tab。
|
||||
脚本未生成时,中间主区域展示启动卡片:
|
||||
|
||||
- AI 帮我写
|
||||
根据商品信息自动生成完整读秒流分镜。
|
||||
|
||||
- 我有脚本
|
||||
粘贴已有口播稿,AI 帮你整理成分镜结构。
|
||||
|
||||
- 一句话生成
|
||||
输入一句视频方向,AI 生成完整分镜。
|
||||
|
||||
- 复刻爆款
|
||||
暂未开放或后续版本处理。
|
||||
|
||||
用户选择后进入对应模式。生成脚本后,中间区域切换为完整读秒流分镜。
|
||||
|
||||
### 读秒流分镜结构
|
||||
|
||||
每个分镜以卡片 / 行卡展示:
|
||||
|
||||
- 镜头编号
|
||||
- 时间段,例如 0-5s、5-10s
|
||||
- 画面描述
|
||||
- 对白 / 旁白
|
||||
- 可选:镜头类型、场景、人物
|
||||
|
||||
用户可以:
|
||||
|
||||
- 直接编辑某个分镜文字。
|
||||
- 用底部 AI 输入框修改某个分镜。
|
||||
- 重新生成全部。
|
||||
- 调整整体时长、口吻、卖点重点等。
|
||||
|
||||
### 商品卖点
|
||||
|
||||
- 从商品库自动带出。
|
||||
- 在脚本页展示为“本条视频重点”。
|
||||
- 默认选中前 2-3 个,或默认使用全部卖点。
|
||||
- 用户可点选 / 取消。
|
||||
- 用户也可以直接在 AI 输入框里说重点突出哪些卖点。
|
||||
|
||||
---
|
||||
|
||||
## 4. Stage 2 基础资产页
|
||||
|
||||
### 页面定位
|
||||
|
||||
基础资产页用于为当前视频准备视觉资产。
|
||||
它不是资产库,也不是完整 AI 图片创作页。
|
||||
|
||||
### 总体原则
|
||||
|
||||
- 默认 AI 自动生成,用户只在效果不好时调整。
|
||||
- 脚本确定后,AI 自动识别需要的商品、人物、场景,并生成图片提示词。
|
||||
- 页面优先让用户看默认生成效果。
|
||||
- 不满意时再修改提示词、上传参考图、从资产库选择或重新生成。
|
||||
- 商品是最重要的,优先展示商品资产。
|
||||
|
||||
### 页面布局
|
||||
|
||||
- 左侧:资产清单
|
||||
- 中间:当前资产工作台 / 生成结果
|
||||
- 右侧:AI 操作记录 / 生成记录
|
||||
- 底部:AI 输入框
|
||||
|
||||
### 资产顺序
|
||||
|
||||
1. 商品资产
|
||||
2. 人物
|
||||
3. 场景
|
||||
|
||||
### 商品资产
|
||||
|
||||
商品三视图是一张 16:9 图,包含正面、侧面、背面。
|
||||
它不是三张图,也不拆分成三个槽位。
|
||||
|
||||
不提供:
|
||||
|
||||
- 上传三视图
|
||||
- 使用现有商品图
|
||||
|
||||
原因:
|
||||
|
||||
- 用户一般不会有适合生成视频的白底三视图。
|
||||
- 如果不生成三视图,系统本来就只能默认使用商品图继续,不需要多给一个“使用现有商品图”的操作。
|
||||
|
||||
推荐交互:
|
||||
|
||||
- 展示当前商品图。
|
||||
- 展示商品三视图状态:未生成 / 生成中 / 已生成。
|
||||
- 说明:生成 16:9 商品三视图可以提升商品角度稳定性。
|
||||
- 主操作:生成商品三视图。
|
||||
- 不生成三视图也可以继续,只做弱提示。
|
||||
|
||||
生成规则:
|
||||
|
||||
- 商品三视图属于结构转换型。
|
||||
- 默认一次生成 1 张。
|
||||
- 不满意可重新生成或用 AI 输入框调整提示词。
|
||||
- 商品三视图支持保留历史版本,用户可以在历史版本中选择满意的一版采用。
|
||||
|
||||
### 人物资产
|
||||
|
||||
命名为“人物”,不叫“模特”。
|
||||
人物指脚本中需要出现的角色,用户要确定这个角色的形象。
|
||||
|
||||
默认流程:
|
||||
|
||||
1. AI 根据脚本自动识别人物。
|
||||
2. AI 为每个人物自动生成图片提示词。
|
||||
3. 默认生成 4 张人物立绘候选。
|
||||
4. 用户选择 1 张满意的立绘。
|
||||
5. AI 基于选中的立绘生成 1 张 16:9 人物三视图。
|
||||
6. 用户采用此人物资产。
|
||||
|
||||
不满意时,用户可以:
|
||||
|
||||
- 修改描述。
|
||||
- 上传自己的模特 / 参考图。
|
||||
- 从人物库选择。
|
||||
- 重新生成立绘。
|
||||
- 从历史立绘里再选一张生成三视图。
|
||||
- 重新生成人物三视图。
|
||||
- 从人物三视图历史版本中选择满意的一版采用。
|
||||
|
||||
人物调整不放抽屉。
|
||||
点击人物后,中间区域直接切换为人物工作台。
|
||||
|
||||
人物工作台需要一步到位承载:
|
||||
|
||||
- 人物库选择
|
||||
- 上传参考图
|
||||
- 描述生成
|
||||
- 直接使用自己的模特
|
||||
- AI 生成多张立绘
|
||||
- 从立绘生成三视图
|
||||
- 从历史立绘再选再生成三视图
|
||||
|
||||
### 场景资产
|
||||
|
||||
默认流程:
|
||||
|
||||
1. AI 根据脚本识别场景。
|
||||
2. AI 自动生成场景图提示词。
|
||||
3. 默认一次生成 4 张候选。
|
||||
4. 用户选择 1 张。
|
||||
|
||||
不满意时,用户可以:
|
||||
|
||||
- 修改描述。
|
||||
- 重新生成。
|
||||
- 上传替换。
|
||||
|
||||
### 候选生成规则
|
||||
|
||||
创意选择型:一次生成 4 张。
|
||||
|
||||
- 人物立绘
|
||||
- 场景图
|
||||
|
||||
结构转换型:一次生成 1 张。
|
||||
|
||||
- 商品三视图
|
||||
- 人物三视图
|
||||
|
||||
三视图版本规则:
|
||||
|
||||
- 商品三视图和人物三视图都可以重跑。
|
||||
- 每次重跑产生一个历史版本。
|
||||
- 用户可以在历史版本中回选满意的一版。
|
||||
- 采用某一版后,该版本成为当前项目使用版本。
|
||||
|
||||
### 资产复用
|
||||
|
||||
人物默认是项目内资产,不自动进入团队人物库,避免资产库变乱。
|
||||
|
||||
采用人物时可以勾选:
|
||||
|
||||
- 保存到人物库
|
||||
|
||||
保存后:
|
||||
|
||||
- 进入团队共享人物库。
|
||||
- 后续项目可复用。
|
||||
- 记录来源:项目内生成 / AI 生成 / 上传参考图等。
|
||||
|
||||
---
|
||||
|
||||
## 5. 待继续确认页面
|
||||
|
||||
- Stage 4 视频页
|
||||
- Stage 5 拼接导出页
|
||||
- 商品库 / 商品详情
|
||||
- 资产库
|
||||
- AI 图片创作
|
||||
- 人物库
|
||||
- 工作台
|
||||
- 团队
|
||||
- 消费
|
||||
- 设置
|
||||
|
||||
---
|
||||
|
||||
## 5. Stage 3 故事板页
|
||||
|
||||
### 页面定位
|
||||
|
||||
故事板页用于在生成视频前,让用户清晰看到接下来这条 15s 视频的大致内容和画面走向。
|
||||
|
||||
### 核心原则
|
||||
|
||||
- 不做分镜级故事板。
|
||||
- 不按每个镜头单独生成故事板。
|
||||
- 故事板按照“一条视频”的完整时长来展示。
|
||||
- 当前版本单条视频上限是 15s,因此故事板就是这条 15s 视频的整体视觉预览。
|
||||
- 故事板需要让用户一眼明白:等下这条视频会讲什么、画面如何推进、商品如何出现。
|
||||
|
||||
### 与脚本页的关系
|
||||
|
||||
- Stage 1 脚本页仍然可以按读秒流分镜展示,方便用户细看脚本。
|
||||
- Stage 3 故事板页不继承“每个分镜一张图”的结构。
|
||||
- 故事板应总结整条 15s 视频,而不是把脚本分镜逐张拆开。
|
||||
|
||||
### 初步页面方向
|
||||
|
||||
主体重点应放在一张完整故事板 / 故事板预览上。
|
||||
|
||||
页面需要展示:
|
||||
|
||||
- 当前视频脚本摘要
|
||||
- 使用的商品资产、人物资产、场景资产
|
||||
- 故事板整体预览
|
||||
- 生成状态
|
||||
- 重新生成 / 调整提示词 / 采用当前版本
|
||||
- 历史版本回选
|
||||
|
||||
故事板可重跑。
|
||||
每次重跑保留历史版本,用户可以在历史版本中选择满意的一版。
|
||||
Loading…
x
Reference in New Issue
Block a user