diff --git a/ui/README.md b/ui/README.md index d6cc675..59aa7d6 100644 --- a/ui/README.md +++ b/ui/README.md @@ -43,7 +43,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:待审批列表 → 批准=成员余额+申请点数 / 驳回;申请默认需审批)**)、用量报表(按模型/成员)、组织管理(部门列表 + 部门增删改查 + 每月点数分配,含部门汇总统计,未分配成员不计入任何部门;**无「组织设置」表单**——组织名称 / 默认成员配额 / 开关均移除,"关闭外部注册"为部署配置项不在 UI 中)、平台运营(运营者 = 宿主本人,职责最小化:① 运行概览——运行状态 / 用户数 / 共享 key 数 / 交易量 / 点数流入流出;② 用户充值——按用户名 / 邮箱搜索定位,输入点数金额(可为小数)确认后余额增加永久有效点数并产生一条交易记录)。**无独立 Key 池管理**——管理员与普通用户一样通过共享管理页「上架 key」配置上游 key(共享列表即 key 池视图,可暂停/删除) +7. 管理视图 Admin(管理员 / 运营者角色专属)— 成员管理(改部门:下拉可选部门或"未分配";任意金额充值,正整数校验;新注册成员默认未分配部门;**加额申请审批(US-20:待审批列表 → 批准=成员余额+申请点数 / 驳回;申请默认需审批)**)、用量报表(按模型/成员)、组织管理(部门列表 + 部门增删改查 + 每月点数分配,含部门汇总统计,未分配成员不计入任何部门;**添加/编辑部门用表单弹层 modal(名称+月分配,替代 window.prompt)**,删除有确认;**无「组织设置」表单**——组织名称 / 默认成员配额 / 开关均移除,"关闭外部注册"为部署配置项不在 UI 中)、平台运营(运营者 = 宿主本人,职责最小化:① 运行概览——运行状态 / 用户数 / 共享 key 数 / 交易量 / 点数流入流出;② 用户充值——按用户名 / 邮箱搜索定位,输入点数金额(可为小数)确认后余额增加永久有效点数并产生一条交易记录)。**无独立 Key 池管理**——管理员与普通用户一样通过共享管理页「上架 key」配置上游 key(共享列表即 key 池视图,可暂停/删除) ## 文件结构 diff --git a/ui/index.html b/ui/index.html index 84357b7..6f7ed5a 100644 --- a/ui/index.html +++ b/ui/index.html @@ -433,6 +433,28 @@

申请加额 Request more points

+ + + diff --git a/ui/js/app.js b/ui/js/app.js index 3acb48d..d76aff3 100644 --- a/ui/js/app.js +++ b/ui/js/app.js @@ -830,45 +830,44 @@ }).join("") : '没有匹配的部门'; } - // 月分配点数必须为正整数 - function promptPositiveInt(promptText, fallback) { - const raw = window.prompt(promptText, fallback); - if (raw == null) return null; // 取消 - const s = String(raw).trim(); - const n = Number(s); - if (!s || !Number.isInteger(n) || n <= 0) { toast("请输入正整数点数(未生效)"); return null; } - return n; + /* --- 部门添加/编辑:表单弹层 modal(rant 10:59:47:替代 window.prompt,确保点击可靠响应) --- */ + + let deptEditIndex = null; // null = 添加,数字 = 编辑的部门索引 + + function openDeptModal(i) { + deptEditIndex = (i == null ? null : i); + const d = (i == null ? null : D.DEPARTMENTS[i]); + $("#dept-modal-title").innerHTML = d + ? "编辑部门 Edit department" + : "添加部门 Add department"; + $("#dept-modal-name").value = d ? d.name : ""; + $("#dept-modal-quota").value = d ? String(d.quota) : ""; + $("#dept-modal").classList.remove("hidden"); + $("#dept-modal-name").focus(); } - function addDept() { - const name = window.prompt("新部门名称:", ""); - if (name == null) return; - const n = String(name).trim(); - if (!n) { toast("请输入部门名称(未生效)"); return; } - if (D.DEPARTMENTS.some((d) => d.name === n)) { toast("部门「" + n + "」已存在"); return; } - const quota = promptPositiveInt("每月点数分配(正整数):", "10000"); - if (quota == null) return; - D.DEPARTMENTS.push({ id: Date.now(), name: n, quota }); - renderAdmin(); - toast("已添加部门「" + n + "」(月分配 " + D.fmt(quota) + " 点)"); - } - - function editDept(i) { - const d = D.DEPARTMENTS[i]; - if (!d) return; - const name = window.prompt("修改部门名称:", d.name); - if (name == null) return; - const n = String(name).trim(); - if (!n) { toast("部门名称不能为空(未生效)"); return; } - if (n !== d.name && D.DEPARTMENTS.some((x) => x.name === n)) { toast("部门「" + n + "」已存在"); return; } - const quota = promptPositiveInt("每月点数分配(正整数):", String(d.quota)); - if (quota == null) return; - const old = d.name; - d.name = n; - d.quota = quota; - if (n !== old) D.EMPLOYEES.forEach((e) => { if (e.dept === old) e.dept = n; }); // 成员部门联动改名 + function confirmDept() { + const name = String($("#dept-modal-name").value).trim(); + const rawQ = String($("#dept-modal-quota").value).trim(); + const quota = Number(rawQ); + if (!name) { toast("请输入部门名称(未生效)"); return; } + if (!rawQ || !Number.isInteger(quota) || quota <= 0) { toast("请输入正整数月分配点数(未生效)"); return; } + if (deptEditIndex == null) { + if (D.DEPARTMENTS.some((d) => d.name === name)) { toast("部门「" + name + "」已存在"); return; } + D.DEPARTMENTS.push({ id: Date.now(), name, quota }); + toast("已添加部门「" + name + "」(月分配 " + D.fmt(quota) + " 点)"); + } else { + const d = D.DEPARTMENTS[deptEditIndex]; + if (!d) return; + if (name !== d.name && D.DEPARTMENTS.some((x) => x.name === name)) { toast("部门「" + name + "」已存在"); return; } + const old = d.name; + d.name = name; + d.quota = quota; + if (name !== old) D.EMPLOYEES.forEach((e) => { if (e.dept === old) e.dept = name; }); // 成员部门联动改名 + toast("已更新部门「" + name + "」(月分配 " + D.fmt(quota) + " 点)"); + } renderAdmin(); - toast("已更新部门「" + n + "」(月分配 " + D.fmt(quota) + " 点)"); + $("#dept-modal").classList.add("hidden"); } function deleteDept(i) { @@ -1132,7 +1131,7 @@ if (m) m.classList.add("hidden"); }) ); - ["topup-modal", "raise-modal"].forEach((id) => { + ["topup-modal", "raise-modal", "dept-modal"].forEach((id) => { document.getElementById(id).addEventListener("click", (e) => { if (e.target === document.getElementById(id)) document.getElementById(id).classList.add("hidden"); }); }); // 钱包页提示 → 跳转交易记录(明细统一入口) @@ -1199,14 +1198,15 @@ if (rj) rejectRaise(Number(rj.dataset.raiseReject)); }); - // 组织管理:部门搜索 / 添加 / 编辑 / 删除(事件委托) + // 组织管理:部门搜索 / 添加 / 编辑 / 删除(事件委托;添加/编辑用表单弹层 modal) $("#od-search").addEventListener("input", renderAdmin); - $("#add-dept-btn").addEventListener("click", addDept); + $("#add-dept-btn").addEventListener("click", () => openDeptModal(null)); + $("#dept-confirm").addEventListener("click", confirmDept); $("#dept-body").addEventListener("click", (e) => { const ed = e.target.closest("[data-dept-edit]"); - if (ed) { editDept(Number(ed.dataset.deptEdit)); return; } + if (ed) { openDeptModal(Number(ed.dataset.deptEdit)); return; } const dl = e.target.closest("[data-dept-del]"); if (dl) deleteDept(Number(dl.dataset.deptDel)); });