Files
dmt-modelLibrary/web/src/pages/app/modules/users.ts
2026-05-23 09:11:46 +08:00

267 lines
9.2 KiB
TypeScript

import { api } from "../../../services/api";
import { getCurrentUser } from "../../../services/authState";
import type { ManagedUser, UserListResponse, UserRole } from "../../../types";
import { confirmDialog, formDialog, notify, notifyError } from "../../../ui/dialogs";
import { escapeHtml } from "../../../utils/format";
type UserFormMode = "create" | "edit";
let users: ManagedUser[] = [];
let editingUserId: number | null = null;
export async function openUserManager() {
await refreshUsers();
editingUserId = null;
await formDialog<boolean>({
title: "人员权限管理",
width: 980,
height: 560,
body: `
<div class="user-manager">
<header class="user-manager-head">
<div>
<strong>账号列表</strong>
<span>维护人员角色、启停状态和授权到期时间</span>
</div>
<button id="addUserBtn" class="primary-btn" type="button">新增人员</button>
</header>
<div class="user-manager-body">
<div class="user-table">
<div class="user-table-head">
<span>用户</span>
<span>角色</span>
<span>状态</span>
<span>授权到期</span>
<span>更新时间</span>
<span>操作</span>
</div>
<div id="userTableBody" class="user-table-body">
${userRowsHtml()}
</div>
</div>
<aside id="userEditorHost" class="user-editor-panel">
${userEditorEmptyHtml()}
</aside>
</div>
</div>
`,
onOpen: bindUserManagerEvents,
onSubmit: () => true
});
}
async function refreshUsers() {
const result = await api<UserListResponse>("/api/users");
users = result.users;
}
function userRowsHtml() {
return users.map(userRowHtml).join("") || `<div class="user-empty">暂无人员</div>`;
}
function userRowHtml(user: ManagedUser) {
const currentUser = getCurrentUser();
const locked = currentUser?.id === user.id;
const expired = isExpired(user.expires_at);
const statusClass = user.enabled ? (expired ? "is-expired" : "is-enabled") : "is-disabled";
const statusText = user.enabled ? (expired ? "已过期" : "启用") : "禁用";
return `
<div class="user-row" data-id="${user.id}">
<span class="user-name">
<strong>${escapeHtml(user.username)}</strong>
${locked ? `<em>当前账号</em>` : ""}
</span>
<span><i class="role-badge ${user.role === "admin" ? "is-admin" : ""}">${roleText(user.role)}</i></span>
<span><i class="status-badge ${statusClass}">${statusText}</i></span>
<span>${formatDate(user.expires_at) || "长期有效"}</span>
<span>${formatDate(user.updated_at)}</span>
<span class="user-row-actions">
<button type="button" data-action="edit" data-id="${user.id}">编辑</button>
<button class="danger-text-btn" type="button" data-action="delete" data-id="${user.id}" ${locked ? "disabled" : ""}>删除</button>
</span>
</div>
`;
}
function bindUserManagerEvents() {
document.querySelector("#addUserBtn")?.addEventListener("click", () => {
renderUserEditor("create");
});
bindUserRowEvents();
bindUserEditorEvents();
}
function bindUserRowEvents() {
document.querySelectorAll<HTMLButtonElement>(".user-table-body button").forEach((button) => {
button.addEventListener("click", async () => {
try {
const id = Number(button.dataset.id);
if (button.dataset.action === "edit") renderUserEditor("edit", id);
if (button.dataset.action === "delete") await deleteUser(id);
} catch (error) {
notifyError(error);
}
});
});
}
function renderUserEditor(mode: UserFormMode, id?: number) {
editingUserId = mode === "edit" ? id ?? null : null;
const user = id ? users.find((item) => item.id === id) : undefined;
const isCreate = mode === "create";
const host = document.querySelector<HTMLElement>("#userEditorHost");
if (!host) return;
host.innerHTML = `
<div class="user-editor-title">
<strong>${isCreate ? "新增人员" : "编辑权限"}</strong>
<span>${isCreate ? "创建账号并设置初始权限" : "调整角色、状态和授权期限"}</span>
</div>
<form id="userEditorForm" class="popup-form user-editor-form" data-mode="${mode}">
${isCreate ? `
<label>
<span>用户名</span>
<input name="username" autocomplete="off" />
</label>
` : `
<label>
<span>用户名</span>
<input value="${escapeHtml(user?.username ?? "")}" disabled />
</label>
`}
<div class="popup-form-grid">
<label>
<span>角色</span>
<select name="role">
<option value="user" ${user?.role === "user" ? "selected" : ""}>普通用户</option>
<option value="admin" ${user?.role === "admin" ? "selected" : ""}>管理员</option>
</select>
</label>
<label>
<span>状态</span>
<select name="enabled">
<option value="true" ${user?.enabled === false ? "" : "selected"}>启用</option>
<option value="false" ${user?.enabled === false ? "selected" : ""}>禁用</option>
</select>
</label>
</div>
<label>
<span>授权到期</span>
<input name="expiresAt" type="date" value="${dateInputValue(user?.expires_at)}" />
</label>
<label>
<span>${isCreate ? "初始密码" : "重置密码"}</span>
<input name="password" type="password" autocomplete="new-password" placeholder="${isCreate ? "至少 6 位" : "不填写则保持原密码"}" />
</label>
<div class="user-editor-actions">
<button id="cancelUserEditBtn" class="ghost-btn" type="button">取消</button>
<button id="saveUserBtn" class="primary-btn" type="button">${isCreate ? "新增" : "保存"}</button>
</div>
</form>
`;
bindUserEditorEvents();
}
function userEditorEmptyHtml() {
return `
<div class="user-editor-empty">
<strong>选择人员</strong>
<span>点击新增或编辑后,在这里维护账号权限。</span>
</div>
`;
}
function bindUserEditorEvents() {
document.querySelector("#saveUserBtn")?.addEventListener("click", async () => {
try {
await saveUserEditor();
} catch (error) {
notifyError(error);
}
});
document.querySelector("#cancelUserEditBtn")?.addEventListener("click", () => {
editingUserId = null;
const host = document.querySelector<HTMLElement>("#userEditorHost");
if (host) host.innerHTML = userEditorEmptyHtml();
});
}
async function saveUserEditor() {
const formElement = document.querySelector<HTMLFormElement>("#userEditorForm");
if (!formElement) return;
const form = new FormData(formElement);
const isCreate = formElement.dataset.mode === "create";
const password = String(form.get("password") ?? "").trim();
const result = {
username: String(form.get("username") ?? "").trim(),
password,
role: String(form.get("role") ?? "user") as UserRole,
enabled: String(form.get("enabled") ?? "true") === "true",
expiresAt: String(form.get("expiresAt") ?? "") || null
};
if (isCreate && !result.username) throw new Error("用户名不能为空");
if (isCreate && result.password.length < 6) throw new Error("初始密码至少 6 位");
if (!isCreate && result.password && result.password.length < 6) throw new Error("重置密码至少 6 位");
if (isCreate) {
await api("/api/users", {
method: "POST",
body: JSON.stringify(result)
});
notify("新增成功");
} else {
if (!editingUserId) throw new Error("请选择要编辑的用户");
await api(`/api/users/${editingUserId}`, {
method: "PUT",
body: JSON.stringify({
role: result.role,
enabled: result.enabled,
expiresAt: result.expiresAt,
...(result.password ? { password: result.password } : {})
})
});
notify("保存成功");
}
await rerenderUserRows();
if (isCreate) {
const host = document.querySelector<HTMLElement>("#userEditorHost");
if (host) host.innerHTML = userEditorEmptyHtml();
}
}
async function deleteUser(id: number) {
const user = users.find((item) => item.id === id);
if (!user) return;
const confirmed = await confirmDialog(`确认删除用户「${user.username}」?`);
if (!confirmed) return;
await api(`/api/users/${id}`, { method: "DELETE" });
notify("删除成功");
await rerenderUserRows();
}
async function rerenderUserRows() {
await refreshUsers();
const body = document.querySelector<HTMLDivElement>("#userTableBody");
if (!body) return;
body.innerHTML = userRowsHtml();
bindUserRowEvents();
if (editingUserId) {
renderUserEditor("edit", editingUserId);
}
}
function roleText(role: UserRole) {
return role === "admin" ? "管理员" : "普通用户";
}
function isExpired(value: string | null) {
return Boolean(value && new Date(value).getTime() < Date.now());
}
function formatDate(value: string | null) {
if (!value) return "";
return value.slice(0, 10);
}
function dateInputValue(value: string | null | undefined) {
return value ? value.slice(0, 10) : "";
}