update:新建用户可选角色、项目
This commit is contained in:
@@ -0,0 +1,62 @@
|
|||||||
|
import type { Workspace } from "../../services/api";
|
||||||
|
import { Checkbox } from "~/components/ui/checkbox";
|
||||||
|
import { formFieldClass } from "../platform/modalUi";
|
||||||
|
|
||||||
|
const CHECKBOX_CLASS =
|
||||||
|
"size-[16px] shrink-0 rounded-[3px] border-[#cdd9e4] bg-white data-checked:border-[#1277d3] data-checked:bg-[#1277d3] data-checked:text-white";
|
||||||
|
|
||||||
|
/** 新建用户时可选加入的项目列表(多选,非必填)。 */
|
||||||
|
export function CreateUserProjectField({
|
||||||
|
projects,
|
||||||
|
loading,
|
||||||
|
selectedIds,
|
||||||
|
onToggle,
|
||||||
|
}: {
|
||||||
|
projects: Workspace[];
|
||||||
|
loading: boolean;
|
||||||
|
selectedIds: string[];
|
||||||
|
onToggle: (workspaceId: string) => void;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<div className={formFieldClass}>
|
||||||
|
<span>
|
||||||
|
加入项目
|
||||||
|
<span className="ml-1 font-normal text-[#8a9aab]">(可选)</span>
|
||||||
|
</span>
|
||||||
|
<div className="max-h-[140px] overflow-auto rounded-md border border-[#d6e0e9] bg-white px-2.5 py-2">
|
||||||
|
{loading ? (
|
||||||
|
<p className="m-0 py-2 text-center text-[12px] text-[#8a9aab]">加载项目中…</p>
|
||||||
|
) : projects.length === 0 ? (
|
||||||
|
<p className="m-0 py-2 text-center text-[12px] text-[#8a9aab]">暂无可用项目</p>
|
||||||
|
) : (
|
||||||
|
<div className="flex flex-col gap-1.5">
|
||||||
|
{projects.map((project) => {
|
||||||
|
const checked = selectedIds.includes(project.workspace_id);
|
||||||
|
return (
|
||||||
|
<label
|
||||||
|
key={project.workspace_id}
|
||||||
|
className="flex cursor-pointer items-center gap-2 rounded px-1 py-1 hover:bg-[#f5f8fb]"
|
||||||
|
>
|
||||||
|
<Checkbox
|
||||||
|
checked={checked}
|
||||||
|
onCheckedChange={() => onToggle(project.workspace_id)}
|
||||||
|
className={CHECKBOX_CLASS}
|
||||||
|
/>
|
||||||
|
<span className="min-w-0 truncate text-[12px] text-[#283a4e]">
|
||||||
|
{project.workspace_name}
|
||||||
|
<span className="ml-1.5 text-[11px] text-[#8a9aab]">
|
||||||
|
{project.workspace_code}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<p className="m-0 text-[11px] leading-snug text-[#8a9aab]">
|
||||||
|
未加入任何项目时,该用户将无法登录。
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,215 @@
|
|||||||
|
import type { Employee, Role, Workspace } from "../../services/api";
|
||||||
|
import {
|
||||||
|
AppFormDialog,
|
||||||
|
dialogPrimaryButtonClass,
|
||||||
|
dialogSecondaryButtonClass,
|
||||||
|
} from "~/components/common/AppFormDialog";
|
||||||
|
import { Button } from "~/components/ui/button";
|
||||||
|
import { Input } from "~/components/ui/input";
|
||||||
|
import { CreateUserProjectField } from "./CreateUserProjectField";
|
||||||
|
import {
|
||||||
|
formFieldClass,
|
||||||
|
formInputClass,
|
||||||
|
modalFormClass,
|
||||||
|
} from "../platform/modalUi";
|
||||||
|
|
||||||
|
export type UserFormState = {
|
||||||
|
username: string;
|
||||||
|
display_name: string;
|
||||||
|
email: string;
|
||||||
|
role_code: "admin" | "developer";
|
||||||
|
password: string;
|
||||||
|
status: "active" | "disabled" | "locked";
|
||||||
|
};
|
||||||
|
|
||||||
|
const FORM_INPUT_CLASS =
|
||||||
|
"h-[39px] w-full rounded-md border border-[#d6e0e9] bg-white px-[11px] text-xs text-[#283a4e] outline-none focus:border-[#5b9ddb] focus:ring-3 focus:ring-brand/10 disabled:cursor-not-allowed disabled:opacity-50";
|
||||||
|
|
||||||
|
export function UserFormDialog({
|
||||||
|
open,
|
||||||
|
editing,
|
||||||
|
form,
|
||||||
|
saving,
|
||||||
|
currentUserId,
|
||||||
|
roles,
|
||||||
|
createRoleOptions,
|
||||||
|
createProjects,
|
||||||
|
createProjectsLoading,
|
||||||
|
selectedWorkspaceIds,
|
||||||
|
onChangeForm,
|
||||||
|
onToggleWorkspace,
|
||||||
|
onSubmit,
|
||||||
|
onClose,
|
||||||
|
}: {
|
||||||
|
open: boolean;
|
||||||
|
editing: Employee | null;
|
||||||
|
form: UserFormState;
|
||||||
|
saving: boolean;
|
||||||
|
currentUserId: string | undefined;
|
||||||
|
roles: Role[];
|
||||||
|
createRoleOptions: Role[];
|
||||||
|
createProjects: Workspace[];
|
||||||
|
createProjectsLoading: boolean;
|
||||||
|
selectedWorkspaceIds: string[];
|
||||||
|
onChangeForm: (next: UserFormState) => void;
|
||||||
|
onToggleWorkspace: (workspaceId: string) => void;
|
||||||
|
onSubmit: (event: React.FormEvent) => void;
|
||||||
|
onClose: () => void;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<AppFormDialog
|
||||||
|
open={open}
|
||||||
|
onOpenChange={(nextOpen) => {
|
||||||
|
if (!nextOpen) onClose();
|
||||||
|
}}
|
||||||
|
eyebrow="EMPLOYEE"
|
||||||
|
title={editing ? "编辑用户" : "添加用户"}
|
||||||
|
>
|
||||||
|
<form className={modalFormClass} onSubmit={onSubmit}>
|
||||||
|
<label className={formFieldClass}>
|
||||||
|
<span>
|
||||||
|
姓名<span className="text-danger">*</span>
|
||||||
|
</span>
|
||||||
|
<Input
|
||||||
|
className={FORM_INPUT_CLASS}
|
||||||
|
autoFocus={!editing}
|
||||||
|
value={form.display_name}
|
||||||
|
onChange={(event) => onChangeForm({ ...form, display_name: event.target.value })}
|
||||||
|
placeholder="请输入姓名"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
<label className={formFieldClass}>
|
||||||
|
<span>
|
||||||
|
登录账号<span className="text-danger">*</span>
|
||||||
|
</span>
|
||||||
|
<Input
|
||||||
|
className={FORM_INPUT_CLASS}
|
||||||
|
disabled={Boolean(editing)}
|
||||||
|
value={form.username}
|
||||||
|
onChange={(event) => onChangeForm({ ...form, username: event.target.value })}
|
||||||
|
placeholder="请输入登录账号"
|
||||||
|
autoComplete="username"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
{!editing && (
|
||||||
|
<label className={formFieldClass}>
|
||||||
|
<span>
|
||||||
|
密码<span className="text-danger">*</span>
|
||||||
|
</span>
|
||||||
|
<Input
|
||||||
|
className={FORM_INPUT_CLASS}
|
||||||
|
type="password"
|
||||||
|
autoComplete="new-password"
|
||||||
|
value={form.password}
|
||||||
|
onChange={(event) => onChangeForm({ ...form, password: event.target.value })}
|
||||||
|
placeholder="请输入密码(8~72 字符)"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
)}
|
||||||
|
<label className={formFieldClass}>
|
||||||
|
<span>邮箱</span>
|
||||||
|
<Input
|
||||||
|
className={FORM_INPUT_CLASS}
|
||||||
|
type="email"
|
||||||
|
autoComplete="email"
|
||||||
|
value={form.email}
|
||||||
|
onChange={(event) => onChangeForm({ ...form, email: event.target.value })}
|
||||||
|
placeholder="请输入邮箱(可选)"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
<label className={formFieldClass}>
|
||||||
|
<span>角色</span>
|
||||||
|
<select
|
||||||
|
className={formInputClass}
|
||||||
|
value={form.role_code}
|
||||||
|
disabled={
|
||||||
|
editing
|
||||||
|
? editing.user_id === currentUserId || roles.length === 0
|
||||||
|
: createRoleOptions.length === 0
|
||||||
|
}
|
||||||
|
onChange={(event) =>
|
||||||
|
onChangeForm({
|
||||||
|
...form,
|
||||||
|
role_code: event.target.value as UserFormState["role_code"],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{editing ? (
|
||||||
|
roles.length === 0 ? (
|
||||||
|
<option value="" disabled>
|
||||||
|
加载中…
|
||||||
|
</option>
|
||||||
|
) : (
|
||||||
|
roles.map((role) => (
|
||||||
|
<option key={role.role_code} value={role.role_code}>
|
||||||
|
{role.role_name}
|
||||||
|
</option>
|
||||||
|
))
|
||||||
|
)
|
||||||
|
) : createRoleOptions.length === 0 ? (
|
||||||
|
<option value="" disabled>
|
||||||
|
加载中…
|
||||||
|
</option>
|
||||||
|
) : (
|
||||||
|
createRoleOptions.map((role) => (
|
||||||
|
<option key={role.role_code} value={role.role_code}>
|
||||||
|
{role.role_name}
|
||||||
|
</option>
|
||||||
|
))
|
||||||
|
)}
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
{!editing && (
|
||||||
|
<CreateUserProjectField
|
||||||
|
projects={createProjects}
|
||||||
|
loading={createProjectsLoading}
|
||||||
|
selectedIds={selectedWorkspaceIds}
|
||||||
|
onToggle={onToggleWorkspace}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{editing && (
|
||||||
|
<label className={formFieldClass}>
|
||||||
|
<span>状态</span>
|
||||||
|
<select
|
||||||
|
className={formInputClass}
|
||||||
|
value={form.status}
|
||||||
|
disabled={
|
||||||
|
editing.user_id === currentUserId || editing.role_code === "admin"
|
||||||
|
}
|
||||||
|
onChange={(event) =>
|
||||||
|
onChangeForm({
|
||||||
|
...form,
|
||||||
|
status: event.target.value as UserFormState["status"],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<option value="active">正常</option>
|
||||||
|
<option value="disabled">停用</option>
|
||||||
|
<option value="locked">锁定</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
)}
|
||||||
|
<div className="mt-[3px] flex shrink-0 justify-end gap-2 border-t border-line bg-white -mx-[22px] px-[22px] py-3.5">
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
type="button"
|
||||||
|
onClick={onClose}
|
||||||
|
className={dialogSecondaryButtonClass}
|
||||||
|
>
|
||||||
|
取消
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="default"
|
||||||
|
size="sm"
|
||||||
|
type="submit"
|
||||||
|
disabled={saving}
|
||||||
|
className={dialogPrimaryButtonClass}
|
||||||
|
>
|
||||||
|
{saving ? "保存中…" : "保存"}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</AppFormDialog>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,16 +1,10 @@
|
|||||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||||
|
|
||||||
import { Plus, Search } from "lucide-react";
|
import { Plus, Search } from "lucide-react";
|
||||||
import { ApiRequestError, type Employee, type Role } from "../../services/api";
|
import { ApiRequestError, type Employee, type Role, type Workspace } from "../../services/api";
|
||||||
import { useApi, useAuth } from "../../context/AuthContext";
|
import { useApi, useAuth } from "../../context/AuthContext";
|
||||||
import {
|
|
||||||
AppFormDialog,
|
|
||||||
dialogPrimaryButtonClass,
|
|
||||||
dialogSecondaryButtonClass,
|
|
||||||
} from "~/components/common/AppFormDialog";
|
|
||||||
import { ConfirmDialog } from "~/components/common/ConfirmDialog";
|
import { ConfirmDialog } from "~/components/common/ConfirmDialog";
|
||||||
import { Button } from "~/components/ui/button";
|
import { Button } from "~/components/ui/button";
|
||||||
import { Input } from "~/components/ui/input";
|
|
||||||
import {
|
import {
|
||||||
Table,
|
Table,
|
||||||
TableBody,
|
TableBody,
|
||||||
@@ -21,12 +15,8 @@ import {
|
|||||||
} from "~/components/ui/table";
|
} from "~/components/ui/table";
|
||||||
import { AdminColgroup, USER_PROJECT_COL_WIDTHS } from "./AdminTable";
|
import { AdminColgroup, USER_PROJECT_COL_WIDTHS } from "./AdminTable";
|
||||||
import { AdminPagination } from "./AdminPagination";
|
import { AdminPagination } from "./AdminPagination";
|
||||||
|
import { UserFormDialog, type UserFormState } from "./UserFormDialog";
|
||||||
import { useCursorPage } from "./useCursorPage";
|
import { useCursorPage } from "./useCursorPage";
|
||||||
import {
|
|
||||||
formFieldClass,
|
|
||||||
formInputClass,
|
|
||||||
modalFormClass,
|
|
||||||
} from "../platform/modalUi";
|
|
||||||
import {
|
import {
|
||||||
adminEmptyClass,
|
adminEmptyClass,
|
||||||
adminPageClass,
|
adminPageClass,
|
||||||
@@ -46,18 +36,15 @@ import {
|
|||||||
import { primaryGradientButtonClass } from "~/components/common/buttonClasses";
|
import { primaryGradientButtonClass } from "~/components/common/buttonClasses";
|
||||||
import { useDebouncedValue } from "./useDebouncedValue";
|
import { useDebouncedValue } from "./useDebouncedValue";
|
||||||
|
|
||||||
const EMPTY_FORM = {
|
const EMPTY_FORM: UserFormState = {
|
||||||
username: "",
|
username: "",
|
||||||
display_name: "",
|
display_name: "",
|
||||||
email: "",
|
email: "",
|
||||||
role_code: "developer" as "admin" | "developer",
|
role_code: "developer",
|
||||||
password: "",
|
password: "",
|
||||||
status: "active" as "active" | "disabled" | "locked",
|
status: "active",
|
||||||
};
|
};
|
||||||
|
|
||||||
const FORM_INPUT_CLASS =
|
|
||||||
"h-[39px] w-full rounded-md border border-[#d6e0e9] bg-white px-[11px] text-xs text-[#283a4e] outline-none focus:border-[#5b9ddb] focus:ring-3 focus:ring-brand/10 disabled:cursor-not-allowed disabled:opacity-50";
|
|
||||||
|
|
||||||
export function UserManagementPage({
|
export function UserManagementPage({
|
||||||
onNotify,
|
onNotify,
|
||||||
onConnectionChange,
|
onConnectionChange,
|
||||||
@@ -71,9 +58,12 @@ export function UserManagementPage({
|
|||||||
const [saving, setSaving] = useState(false);
|
const [saving, setSaving] = useState(false);
|
||||||
const [editing, setEditing] = useState<Employee | null>(null);
|
const [editing, setEditing] = useState<Employee | null>(null);
|
||||||
const [dialogOpen, setDialogOpen] = useState(false);
|
const [dialogOpen, setDialogOpen] = useState(false);
|
||||||
const [form, setForm] = useState(EMPTY_FORM);
|
const [form, setForm] = useState<UserFormState>(EMPTY_FORM);
|
||||||
const [userSearchTerm, setUserSearchTerm] = useState("");
|
const [userSearchTerm, setUserSearchTerm] = useState("");
|
||||||
const [deleteTarget, setDeleteTarget] = useState<Employee | null>(null);
|
const [deleteTarget, setDeleteTarget] = useState<Employee | null>(null);
|
||||||
|
const [createProjects, setCreateProjects] = useState<Workspace[]>([]);
|
||||||
|
const [createProjectsLoading, setCreateProjectsLoading] = useState(false);
|
||||||
|
const [selectedWorkspaceIds, setSelectedWorkspaceIds] = useState<string[]>([]);
|
||||||
const debouncedSearch = useDebouncedValue(userSearchTerm, 300);
|
const debouncedSearch = useDebouncedValue(userSearchTerm, 300);
|
||||||
|
|
||||||
const canManage = user?.role_code === "admin";
|
const canManage = user?.role_code === "admin";
|
||||||
@@ -113,14 +103,34 @@ export function UserManagementPage({
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
void api.listPlatformRoles().then(setRoles).catch(() => {
|
void api.listPlatformRoles().then(setRoles).catch(() => {
|
||||||
/* roles only needed for edit dialog */
|
/* roles only needed for dialog */
|
||||||
});
|
});
|
||||||
}, [api]);
|
}, [api]);
|
||||||
|
|
||||||
|
const createRoleOptions = useMemo(
|
||||||
|
() => roles.filter((role) => role.role_code !== "admin"),
|
||||||
|
[roles],
|
||||||
|
);
|
||||||
|
|
||||||
|
const loadCreateProjects = useCallback(async (): Promise<void> => {
|
||||||
|
setCreateProjectsLoading(true);
|
||||||
|
try {
|
||||||
|
const page = await api.listWorkspaces({ limit: 200, cursor: null, q: "" });
|
||||||
|
setCreateProjects(page.items.filter((item) => item.status === "active"));
|
||||||
|
} catch {
|
||||||
|
setCreateProjects([]);
|
||||||
|
onNotify({ tone: "error", message: "项目列表加载失败" });
|
||||||
|
} finally {
|
||||||
|
setCreateProjectsLoading(false);
|
||||||
|
}
|
||||||
|
}, [api, onNotify]);
|
||||||
|
|
||||||
const openCreate = (): void => {
|
const openCreate = (): void => {
|
||||||
setEditing(null);
|
setEditing(null);
|
||||||
setForm(EMPTY_FORM);
|
setForm(EMPTY_FORM);
|
||||||
|
setSelectedWorkspaceIds([]);
|
||||||
setDialogOpen(true);
|
setDialogOpen(true);
|
||||||
|
void loadCreateProjects();
|
||||||
};
|
};
|
||||||
|
|
||||||
const openEdit = (employee: Employee): void => {
|
const openEdit = (employee: Employee): void => {
|
||||||
@@ -133,9 +143,18 @@ export function UserManagementPage({
|
|||||||
password: "",
|
password: "",
|
||||||
status: employee.status,
|
status: employee.status,
|
||||||
});
|
});
|
||||||
|
setSelectedWorkspaceIds([]);
|
||||||
setDialogOpen(true);
|
setDialogOpen(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const toggleCreateWorkspace = (workspaceId: string): void => {
|
||||||
|
setSelectedWorkspaceIds((current) =>
|
||||||
|
current.includes(workspaceId)
|
||||||
|
? current.filter((id) => id !== workspaceId)
|
||||||
|
: [...current, workspaceId],
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const submit = async (event: React.FormEvent): Promise<void> => {
|
const submit = async (event: React.FormEvent): Promise<void> => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
if (!form.display_name.trim() || (!editing && !form.username.trim())) return;
|
if (!form.display_name.trim() || (!editing && !form.username.trim())) return;
|
||||||
@@ -163,6 +182,10 @@ export function UserManagementPage({
|
|||||||
onNotify({ tone: "error", message: "密码长度必须在 8~72 字符之间" });
|
onNotify({ tone: "error", message: "密码长度必须在 8~72 字符之间" });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (!editing && form.role_code === "admin") {
|
||||||
|
onNotify({ tone: "error", message: "新建用户不能指定管理员角色" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
setSaving(true);
|
setSaving(true);
|
||||||
try {
|
try {
|
||||||
if (editing) {
|
if (editing) {
|
||||||
@@ -177,14 +200,38 @@ export function UserManagementPage({
|
|||||||
);
|
);
|
||||||
onNotify({ tone: "success", message: "用户信息已更新" });
|
onNotify({ tone: "success", message: "用户信息已更新" });
|
||||||
} else {
|
} else {
|
||||||
await api.createPlatformEmployee({
|
const created = await api.createPlatformEmployee({
|
||||||
username: form.username.trim(),
|
username: form.username.trim(),
|
||||||
display_name: form.display_name.trim(),
|
display_name: form.display_name.trim(),
|
||||||
email: form.email.trim() || undefined,
|
email: form.email.trim() || undefined,
|
||||||
password: form.password,
|
password: form.password,
|
||||||
role_code: "developer",
|
role_code: form.role_code,
|
||||||
});
|
});
|
||||||
onNotify({ tone: "success", message: "用户已添加" });
|
if (selectedWorkspaceIds.length > 0) {
|
||||||
|
const results = await Promise.allSettled(
|
||||||
|
selectedWorkspaceIds.map((workspaceId) =>
|
||||||
|
api.addWorkspaceMember(workspaceId, { user_id: created.user_id }),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
const failed = results.filter((item) => item.status === "rejected").length;
|
||||||
|
const joined = results.length - failed;
|
||||||
|
if (failed === 0) {
|
||||||
|
onNotify({
|
||||||
|
tone: "success",
|
||||||
|
message: `用户已添加,并加入 ${joined} 个项目`,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
onNotify({
|
||||||
|
tone: "error",
|
||||||
|
message: `用户已创建,但有 ${failed} 个项目加入失败`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
onNotify({
|
||||||
|
tone: "info",
|
||||||
|
message: "用户已添加",
|
||||||
|
});
|
||||||
|
}
|
||||||
await refreshFromStart();
|
await refreshFromStart();
|
||||||
}
|
}
|
||||||
setDialogOpen(false);
|
setDialogOpen(false);
|
||||||
@@ -317,118 +364,22 @@ export function UserManagementPage({
|
|||||||
onGoToPage={goToPage}
|
onGoToPage={goToPage}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<AppFormDialog
|
<UserFormDialog
|
||||||
open={dialogOpen}
|
open={dialogOpen}
|
||||||
onOpenChange={(nextOpen) => {
|
editing={editing}
|
||||||
if (!nextOpen) setDialogOpen(false);
|
form={form}
|
||||||
}}
|
saving={saving}
|
||||||
eyebrow="EMPLOYEE"
|
currentUserId={user?.user_id}
|
||||||
title={editing ? "编辑用户" : "添加用户"}
|
roles={roles}
|
||||||
>
|
createRoleOptions={createRoleOptions}
|
||||||
<form className={modalFormClass} onSubmit={(event) => void submit(event)}>
|
createProjects={createProjects}
|
||||||
<label className={formFieldClass}>
|
createProjectsLoading={createProjectsLoading}
|
||||||
<span>姓名<span className="text-danger">*</span></span>
|
selectedWorkspaceIds={selectedWorkspaceIds}
|
||||||
<Input
|
onChangeForm={setForm}
|
||||||
className={FORM_INPUT_CLASS}
|
onToggleWorkspace={toggleCreateWorkspace}
|
||||||
autoFocus={!editing}
|
onSubmit={(event) => void submit(event)}
|
||||||
value={form.display_name}
|
onClose={() => setDialogOpen(false)}
|
||||||
onChange={(event) => setForm({ ...form, display_name: event.target.value })}
|
/>
|
||||||
placeholder="请输入姓名"
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
<label className={formFieldClass}>
|
|
||||||
<span>登录账号<span className="text-danger">*</span></span>
|
|
||||||
<Input
|
|
||||||
className={FORM_INPUT_CLASS}
|
|
||||||
disabled={Boolean(editing)}
|
|
||||||
value={form.username}
|
|
||||||
onChange={(event) => setForm({ ...form, username: event.target.value })}
|
|
||||||
placeholder="请输入登录账号"
|
|
||||||
autoComplete="username"
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
{!editing && (
|
|
||||||
<label className={formFieldClass}>
|
|
||||||
<span>密码<span className="text-danger">*</span></span>
|
|
||||||
<Input
|
|
||||||
className={FORM_INPUT_CLASS}
|
|
||||||
type="password"
|
|
||||||
autoComplete="new-password"
|
|
||||||
value={form.password}
|
|
||||||
onChange={(event) => setForm({ ...form, password: event.target.value })}
|
|
||||||
placeholder="请输入密码(8~72 字符)"
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
)}
|
|
||||||
<label className={formFieldClass}>
|
|
||||||
<span>邮箱</span>
|
|
||||||
<Input
|
|
||||||
className={FORM_INPUT_CLASS}
|
|
||||||
type="email"
|
|
||||||
autoComplete="email"
|
|
||||||
value={form.email}
|
|
||||||
onChange={(event) => setForm({ ...form, email: event.target.value })}
|
|
||||||
placeholder="请输入邮箱(可选)"
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
{editing && (
|
|
||||||
<label className={formFieldClass}>
|
|
||||||
<span>角色</span>
|
|
||||||
<select
|
|
||||||
className={formInputClass}
|
|
||||||
value={form.role_code}
|
|
||||||
disabled={editing.user_id === user?.user_id || roles.length === 0}
|
|
||||||
onChange={(event) => setForm({ ...form, role_code: event.target.value as typeof form.role_code })}
|
|
||||||
>
|
|
||||||
{roles.length === 0 ? (
|
|
||||||
<option value="" disabled>加载中…</option>
|
|
||||||
) : (
|
|
||||||
roles.map((role) => (
|
|
||||||
<option key={role.role_code} value={role.role_code}>
|
|
||||||
{role.role_name}
|
|
||||||
</option>
|
|
||||||
))
|
|
||||||
)}
|
|
||||||
</select>
|
|
||||||
</label>
|
|
||||||
)}
|
|
||||||
{editing && (
|
|
||||||
<label className={formFieldClass}>
|
|
||||||
<span>状态</span>
|
|
||||||
<select
|
|
||||||
className={formInputClass}
|
|
||||||
value={form.status}
|
|
||||||
disabled={Boolean(editing) && (editing.user_id === user?.user_id || editing.role_code === "admin")}
|
|
||||||
onChange={(event) => setForm({ ...form, status: event.target.value as typeof form.status })}
|
|
||||||
>
|
|
||||||
<option value="active">正常</option>
|
|
||||||
<option value="disabled">停用</option>
|
|
||||||
<option value="locked">锁定</option>
|
|
||||||
</select>
|
|
||||||
</label>
|
|
||||||
)}
|
|
||||||
<div className="mt-[3px] flex shrink-0 justify-end gap-2 border-t border-line bg-white -mx-[22px] px-[22px] py-3.5">
|
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
size="sm"
|
|
||||||
type="button"
|
|
||||||
onClick={() => setDialogOpen(false)}
|
|
||||||
className={dialogSecondaryButtonClass}
|
|
||||||
>
|
|
||||||
取消
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
variant="default"
|
|
||||||
size="sm"
|
|
||||||
type="submit"
|
|
||||||
disabled={saving}
|
|
||||||
className={dialogPrimaryButtonClass}
|
|
||||||
>
|
|
||||||
{saving ? "保存中…" : "保存"}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</AppFormDialog>
|
|
||||||
|
|
||||||
<ConfirmDialog
|
<ConfirmDialog
|
||||||
open={deleteTarget !== null}
|
open={deleteTarget !== null}
|
||||||
|
|||||||
Reference in New Issue
Block a user