Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions docs/user-stories.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

> **本文档以中文撰写**(用户故事主体、验收标准、流程说明均为中文;关键术语保留中英对照)。
>
> **v1.13(2026-08-17)· UI 演化约束:尽量少用弹窗 / modal.** 新增「UI 约定」章节(§1.1):优先行内(inline)交互(行内展开表单 / 可折叠面板 / 就地编辑),弹窗仅限真正需要聚焦 / 阻塞的场景(如删除确认),**禁止弹窗嵌套**;存量 modal(充值 topup-modal、申请加额 raise-modal、模型消费 chat-modal)标记改造优先级,后续迭代分批改为行内形态。部门添加/编辑已由弹层改为**行内展开表单**(`#dept-form-card`)。
> **v1.12(2026-08-17)· 移除「组织设置 Organization settings」表单.** 组织管理页只保留部门列表(部门 CRUD + 每月点数分配),删除底部「组织设置」表单(组织名称 / 默认成员配额 / 开关)。「关闭外部注册」属**部署配置项,不在 UI 中设置**(US-16 标注);「成员自助申请加额需管理员审批」开关移除后,申请加额流程**默认需审批**(US-20 说明)。
> **v1.11(2026-08-17)· 管理视图移除独立「Key 池」管理,统一走「上架 key」.** 管理员与普通用户一样通过**共享管理页「上架新 key」**(选厂商 → Plan → 模型、填 key、声明额度、可用时间段)配置上游 key——企业场景下管理员上架、员工消费,本质上就是 key 池;不再有独立的管理员 Key 池界面。移除 Admin「Key 池」tab(成员管理 / 用量报表 / 组织管理 / 平台运营保留)。「Key 池」保留为**数据层概念**(平台持有的上游 key 集合,见 docs/architecture.md 4.2)。更新 US-12 / 企业流程 / 对齐表。
> **v1.10(2026-08-17)· 上架表单"可用时间段"结构化.** 上架 key 的可用时间段改为**正式字段**(星期多选 + 起止时间,可多选;留空 = 全天不限),后端可解析生效;**备注只作纯文本**,不承载任何逻辑。共享列表展示可用时间段(如「周一~周五 09:00-18:00」),未设置显示「全天」。更新 US-8 / US-9 AC。
Expand All @@ -28,6 +29,19 @@

功能集合**完全相同**;差异只在*谁有动力分享*(公共版人人可分享;企业版只有管理员)。**角色是权限差异,不是产品差异**——管理员额外拥有管理视图(key 池 / 成员 / 用量报表 / 组织),普通用户没有。

### 1.1 UI 约定:尽量少用弹窗(UI Principles)

> **v1.13 定案**(宿主 2026-08-17 指示,rant `2026-08-17T14:34:19`):整个系统**尽量少用弹窗 / modal**。

- **优先行内(inline)交互**:表单、编辑、确认等操作优先在当前页面 / 区域内展开(行内展开表单、可折叠面板、就地编辑),而非弹窗;
- **弹窗仅限真正需要聚焦 / 阻塞的场景**:如必须确认的重要操作(删除)、需要专注输入的少量场景;**禁止弹窗嵌套**(弹窗内不得再开弹窗);
- **替代形态**:表单 / 编辑 → 行内展开、抽屉(drawer / 侧滑)、或就地编辑;确认 → 行内二次确认(按钮变「确认?」态)或轻量 toast + 撤销;选择 / 搜索 → 行内下拉或独立页面;
- **存量 modal 改造优先级**(后续迭代分批处理):
- **P0 高频**:上架表单(已行内卡片 ✓)、部门添加/编辑(已行内 ✓)
- **P1**:充值(topup-modal → 行内 / 独立页)、申请加额(raise-modal → 行内)
- **P2**:模型消费聊天(chat-modal → 独立页或行内面板)
- 该原则约束后续所有 UI 演化;与 UI 原型(`ui/`)对齐。

### 核心机制(Core mechanism)

| 术语 Term | 定义 Definition |
Expand Down
13 changes: 12 additions & 1 deletion ui/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,17 @@
- 差异只在**谁会主动分享**(公共版人人可分享;企业版只有管理员有动力采购/分享 key);
- **角色是权限差异,不是产品差异**:管理员额外拥有"管理视图"(成员点数 / 用量报表 / 组织管理 / 平台运营),普通用户没有;员工就是普通用户,用同一套界面。

## UI 演化约束:尽量少用弹窗(v1.13 定案)

- **优先行内(inline)交互**:表单 / 编辑 / 确认优先行内展开(行内展开表单、可折叠面板、就地编辑),不用弹窗;
- 弹窗仅限真正需要聚焦 / 阻塞的场景(如删除确认);**禁止弹窗嵌套**;
- 替代形态:行内二次确认(按钮变「确认?」)、toast + 撤销、行内下拉 / 独立页;
- **存量 modal 改造优先级**:
- **P0 已行内**:上架表单(`#share-form-card` 行内卡片)、部门添加/编辑(`#dept-form-card` 行内卡片,v1.13 由弹层改造)
- **P1**:充值(topup-modal)、申请加额(raise-modal)→ 行内 / 独立页
- **P2**:模型消费聊天(chat-modal)→ 独立页 / 行内面板
- 本原则与 docs/user-stories.md §1.1 对齐。

本原型据此实现:单一登录入口(角色由账号决定),一套导航(仪表盘 / 模型市场 / 共享管理 / 钱包 / 交易记录 / 设置 + 管理员角色视图)。

## 如何浏览
Expand Down Expand Up @@ -43,7 +54,7 @@ python3 -m http.server 8000 --directory ui
4. 钱包 Wallet — 点数余额、**本月点数变化(近 1 月按类型汇总收支 + 净变化,与仪表盘一致)**、**充值入口(US-4:模拟流程——输入点数 → 余额增加 → topup 交易记录;文案注明"演示,真实支付后续接入";提现仍 disabled)**、**申请加额(US-20:企业成员余额低时申请更多点数 → 提交后等待管理员审批,默认需审批)**;收支明细已去重,统一到【交易记录】(页内提供跳转提示)
5. 交易记录 Transactions — 消费/收益/充值/提现/赠送(gift)唯一明细入口,Tab 筛选 + MRT 风格表格(列排序/列筛选/分页,与 Tab 叠加生效)
6. 设置 Settings — 账户、**API Key 管理(独占整行全宽,表格字段不挤压;完整增删改查:生成带名字、改名、删除需确认「删除后该 key 立即失效」、按名字搜索、一键复制完整 key——列表脱敏展示 atk_live_****xxxx,复制为完整值;file:// 下 clipboard API 受限时降级为选中+Ctrl/Cmd+C)**、通知、偏好
7. 管理视图 Admin(管理员 / 运营者角色专属)— 成员管理(改部门:下拉可选部门或"未分配";任意金额充值,正整数校验;新注册成员默认未分配部门;**加额申请审批(US-20:待审批列表 → 批准=成员余额+申请点数 / 驳回;申请默认需审批)**)、用量报表(按模型/成员)、组织管理(部门列表 + 部门增删改查 + 每月点数分配,含部门汇总统计,未分配成员不计入任何部门;**添加/编辑部门用表单弹层 modal(名称+月分配,替代 window.prompt)**,删除有确认;**无「组织设置」表单**——组织名称 / 默认成员配额 / 开关均移除,"关闭外部注册"为部署配置项不在 UI 中)、平台运营(运营者 = 宿主本人,职责最小化:① 运行概览——运行状态 / 用户数 / 共享 key 数 / 交易量 / 点数流入流出;② 用户充值——按用户名 / 邮箱搜索定位,输入点数金额(可为小数)确认后余额增加永久有效点数并产生一条交易记录)。**无独立 Key 池管理**——管理员与普通用户一样通过共享管理页「上架 key」配置上游 key(共享列表即 key 池视图,可暂停/删除)
7. 管理视图 Admin(管理员 / 运营者角色专属)— 成员管理(改部门:下拉可选部门或"未分配";任意金额充值,正整数校验;新注册成员默认未分配部门;**加额申请审批(US-20:待审批列表 → 批准=成员余额+申请点数 / 驳回;申请默认需审批)**)、用量报表(按模型/成员)、组织管理(部门列表 + 部门增删改查 + 每月点数分配,含部门汇总统计,未分配成员不计入任何部门;**添加/编辑部门用行内展开表单(`#dept-form-card`,名称+月分配,替代 window.prompt)**,删除有确认;**无「组织设置」表单**——组织名称 / 默认成员配额 / 开关均移除,"关闭外部注册"为部署配置项不在 UI 中)、平台运营(运营者 = 宿主本人,职责最小化:① 运行概览——运行状态 / 用户数 / 共享 key 数 / 交易量 / 点数流入流出;② 用户充值——按用户名 / 邮箱搜索定位,输入点数金额(可为小数)确认后余额增加永久有效点数并产生一条交易记录)。**无独立 Key 池管理**——管理员与普通用户一样通过共享管理页「上架 key」配置上游 key(共享列表即 key 池视图,可暂停/删除)

## 文件结构

Expand Down
40 changes: 18 additions & 22 deletions ui/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,24 @@ <h3>部门列表 <span class="en">Departments</span></h3>
</table>
</div>
</div>
<!-- 部门添加/编辑:行内展开表单(UI 原则:少用弹窗,优先行内交互) -->
<div class="card" id="dept-form-card" hidden>
<h3 id="dept-form-title">添加部门 <span class="en">Add department</span></h3>
<div class="form">
<div class="form-row">
<label>部门名称
<input id="dept-form-name" class="input" placeholder="如:研发">
</label>
<label>每月点数分配(正整数)
<input id="dept-form-quota" type="number" min="1" step="1" class="input" placeholder="如:20000">
</label>
</div>
<div class="form-row">
<button type="button" class="btn btn-primary" id="dept-confirm">确认 <span class="en">(Confirm)</span></button>
<button type="button" class="btn btn-ghost" id="dept-cancel">取消 <span class="en">(Cancel)</span></button>
</div>
</div>
</div>
</div>

<div class="admin-pane hidden" data-admin-pane="ops">
Expand Down Expand Up @@ -433,28 +451,6 @@ <h3>申请加额 <span class="en">Request more points</span></h3>
</div>
</div>

<!-- 部门添加/编辑(组织管理:表单弹层替代 window.prompt,确保点击可靠响应) -->
<div id="dept-modal" class="modal-overlay hidden">
<div class="modal">
<div class="modal-head">
<h3 id="dept-modal-title">添加部门 <span class="en">Add department</span></h3>
<button type="button" class="btn btn-ghost" data-modal-close="dept-modal">✕</button>
</div>
<div class="form">
<label>部门名称
<input id="dept-modal-name" class="input" placeholder="如:研发">
</label>
<label>每月点数分配(正整数)
<input id="dept-modal-quota" type="number" min="1" step="1" class="input" placeholder="如:20000">
</label>
</div>
<div class="modal-actions">
<button type="button" class="btn btn-primary" id="dept-confirm">确认 <span class="en">(Confirm)</span></button>
<button type="button" class="btn btn-ghost" data-modal-close="dept-modal">取消 <span class="en">(Cancel)</span></button>
</div>
</div>
</div>

<script src="js/data.js"></script>
<script src="js/app.js"></script>
</body>
Expand Down
29 changes: 15 additions & 14 deletions ui/js/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -830,25 +830,25 @@
}).join("") : '<tr><td colspan="7" class="muted">没有匹配的部门</td></tr>';
}

/* --- 部门添加/编辑:表单弹层 modal(rant 10:59:47:替代 window.prompt,确保点击可靠响应) --- */
/* --- 部门添加/编辑:行内展开表单(UI 原则:少用弹窗,优先行内交互;继承 rant 10:59:47 的可靠响应) --- */

let deptEditIndex = null; // null = 添加,数字 = 编辑的部门索引

function openDeptModal(i) {
function openDeptForm(i) {
deptEditIndex = (i == null ? null : i);
const d = (i == null ? null : D.DEPARTMENTS[i]);
$("#dept-modal-title").innerHTML = d
$("#dept-form-title").innerHTML = d
? "编辑部门 <span class='en'>Edit department</span>"
: "添加部门 <span class='en'>Add department</span>";
$("#dept-modal-name").value = d ? d.name : "";
$("#dept-modal-quota").value = d ? String(d.quota) : "";
$("#dept-modal").classList.remove("hidden");
$("#dept-modal-name").focus();
$("#dept-form-name").value = d ? d.name : "";
$("#dept-form-quota").value = d ? String(d.quota) : "";
$("#dept-form-card").hidden = false;
$("#dept-form-name").focus();
}

function confirmDept() {
const name = String($("#dept-modal-name").value).trim();
const rawQ = String($("#dept-modal-quota").value).trim();
const name = String($("#dept-form-name").value).trim();
const rawQ = String($("#dept-form-quota").value).trim();
const quota = Number(rawQ);
if (!name) { toast("请输入部门名称(未生效)"); return; }
if (!rawQ || !Number.isInteger(quota) || quota <= 0) { toast("请输入正整数月分配点数(未生效)"); return; }
Expand All @@ -867,7 +867,7 @@
toast("已更新部门「" + name + "」(月分配 " + D.fmt(quota) + " 点)");
}
renderAdmin();
$("#dept-modal").classList.add("hidden");
$("#dept-form-card").hidden = true;
}

function deleteDept(i) {
Expand Down Expand Up @@ -1131,7 +1131,7 @@
if (m) m.classList.add("hidden");
})
);
["topup-modal", "raise-modal", "dept-modal"].forEach((id) => {
["topup-modal", "raise-modal"].forEach((id) => {
document.getElementById(id).addEventListener("click", (e) => { if (e.target === document.getElementById(id)) document.getElementById(id).classList.add("hidden"); });
});
// 钱包页提示 → 跳转交易记录(明细统一入口)
Expand Down Expand Up @@ -1198,15 +1198,16 @@
if (rj) rejectRaise(Number(rj.dataset.raiseReject));
});

// 组织管理:部门搜索 / 添加 / 编辑 / 删除(事件委托;添加/编辑用表单弹层 modal)
// 组织管理:部门搜索 / 添加 / 编辑 / 删除(事件委托;添加/编辑用行内展开表单)
$("#od-search").addEventListener("input", renderAdmin);

$("#add-dept-btn").addEventListener("click", () => openDeptModal(null));
$("#add-dept-btn").addEventListener("click", () => openDeptForm(null));
$("#dept-confirm").addEventListener("click", confirmDept);
$("#dept-cancel").addEventListener("click", () => { $("#dept-form-card").hidden = true; });

$("#dept-body").addEventListener("click", (e) => {
const ed = e.target.closest("[data-dept-edit]");
if (ed) { openDeptModal(Number(ed.dataset.deptEdit)); return; }
if (ed) { openDeptForm(Number(ed.dataset.deptEdit)); return; }
const dl = e.target.closest("[data-dept-del]");
if (dl) deleteDept(Number(dl.dataset.deptDel));
});
Expand Down
Loading