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
2 changes: 1 addition & 1 deletion ui/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 池视图,可暂停/删除)

## 文件结构

Expand Down
22 changes: 22 additions & 0 deletions ui/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -433,6 +433,28 @@ <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
80 changes: 40 additions & 40 deletions ui/js/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -830,45 +830,44 @@
}).join("") : '<tr><td colspan="7" class="muted">没有匹配的部门</td></tr>';
}

// 月分配点数必须为正整数
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
? "编辑部门 <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();
}

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) {
Expand Down Expand Up @@ -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"); });
});
// 钱包页提示 → 跳转交易记录(明细统一入口)
Expand Down Expand Up @@ -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));
});
Expand Down
Loading