完成五人团队正式内用最小收口(07-18-small-team-internal-readiness)
这个提交包含在:
@@ -25,26 +25,50 @@ import { Pagination } from "../components/common/Pagination";
|
||||
import { CreateUserDialog, type CreateUserFormData } from "./user-management/CreateUserDialog";
|
||||
import { UserImportDialog } from "./user-management/UserImportDialog";
|
||||
import { UserRoleDialog } from "./user-management/UserRoleDialog";
|
||||
import { useAuth } from "../auth/AuthContext";
|
||||
|
||||
const PAGE_SIZE = 20;
|
||||
|
||||
const ROLE_OPTIONS: Array<{ code: string; label: string }> = [
|
||||
{ code: "employee", label: "employee" },
|
||||
{ code: "manager", label: "manager" },
|
||||
{ code: "ceo", label: "ceo" },
|
||||
{ code: "finance", label: "finance" },
|
||||
{ code: "finance_mgr", label: "finance_mgr" },
|
||||
{ code: "administration", label: "administration" },
|
||||
{ code: "hr", label: "hr" },
|
||||
{ code: "admin", label: "admin" },
|
||||
];
|
||||
const ROLE_LABELS: Record<string, string> = {
|
||||
employee: "员工",
|
||||
manager: "直属经理",
|
||||
ceo: "公司负责人",
|
||||
finance: "财务",
|
||||
system_admin: "OA 系统管理员",
|
||||
finance_mgr: "财务负责人(兼容)",
|
||||
administration: "行政(兼容)",
|
||||
hr: "人事(兼容)",
|
||||
admin: "应急超级管理员",
|
||||
};
|
||||
|
||||
const ORDINARY_ROLE_OPTIONS = [
|
||||
"employee",
|
||||
"manager",
|
||||
"ceo",
|
||||
"finance",
|
||||
"system_admin",
|
||||
"finance_mgr",
|
||||
"administration",
|
||||
"hr",
|
||||
] as const;
|
||||
|
||||
function formatRoleCodes(roleCodes: string[]) {
|
||||
return roleCodes.length ? roleCodes.join(", ") : "-";
|
||||
return roleCodes.length ? roleCodes.map((roleCode) => ROLE_LABELS[roleCode] ?? roleCode).join("、") : "-";
|
||||
}
|
||||
|
||||
function isProtectedUserAccount(user: UserSummary) {
|
||||
return user.roleCodes.some((roleCode) => roleCode === "admin" || roleCode === "system_admin");
|
||||
}
|
||||
|
||||
export function UserManagementPage() {
|
||||
const { me } = useAuth();
|
||||
const queryClient = useQueryClient();
|
||||
const isEmergencyAdmin = (me?.roleCodes ?? []).includes("admin");
|
||||
const roleOptions = ORDINARY_ROLE_OPTIONS.map((code) => ({
|
||||
code,
|
||||
label: ROLE_LABELS[code],
|
||||
disabled: code === "system_admin" && !isEmergencyAdmin,
|
||||
}));
|
||||
const [status, setStatus] = useState<UserStatus | "">("");
|
||||
const [q, setQ] = useState("");
|
||||
const [page, setPage] = useState(1);
|
||||
@@ -190,7 +214,7 @@ export function UserManagementPage() {
|
||||
employeeNo: item.employeeNo,
|
||||
})),
|
||||
}}
|
||||
roleOptions={ROLE_OPTIONS}
|
||||
roleOptions={roleOptions}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
@@ -280,8 +304,16 @@ export function UserManagementPage() {
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{users.length ? (
|
||||
users.map((item) => (
|
||||
<TableRow data-testid="users-row" key={item.id}>
|
||||
users.map((item) => {
|
||||
const protectedAccount = isProtectedUserAccount(item);
|
||||
const canManageAccount = isEmergencyAdmin || !protectedAccount;
|
||||
const isCurrentAccount = item.id === me?.id;
|
||||
const canToggleAccount = canManageAccount && !isCurrentAccount;
|
||||
const canResetPassword = canManageAccount;
|
||||
const canEditRoles = canManageAccount && !isCurrentAccount;
|
||||
|
||||
return (
|
||||
<TableRow data-testid="users-row" key={item.id}>
|
||||
<TableCell>
|
||||
<div className="font-medium text-zinc-900">
|
||||
{item.employeeNo} / {item.name}
|
||||
@@ -296,42 +328,58 @@ export function UserManagementPage() {
|
||||
<TableCell className="text-xs">{formatRoleCodes(item.roleCodes)}</TableCell>
|
||||
<TableCell>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<Button
|
||||
data-testid="users-toggle"
|
||||
size="sm"
|
||||
variant="secondary"
|
||||
disabled={toggleMutation.isPending}
|
||||
onClick={() => toggleMutation.mutate(item)}
|
||||
type="button"
|
||||
>
|
||||
{item.status === "active" ? "禁用" : "启用"}
|
||||
</Button>
|
||||
{canToggleAccount ? (
|
||||
<Button
|
||||
data-testid="users-toggle"
|
||||
size="sm"
|
||||
variant="secondary"
|
||||
disabled={toggleMutation.isPending}
|
||||
onClick={() => toggleMutation.mutate(item)}
|
||||
type="button"
|
||||
>
|
||||
{item.status === "active" ? "禁用" : "启用"}
|
||||
</Button>
|
||||
) : null}
|
||||
|
||||
<Button
|
||||
data-testid="users-reset-password"
|
||||
size="sm"
|
||||
variant="secondary"
|
||||
disabled={resetMutation.isPending}
|
||||
onClick={() => resetMutation.mutate(item)}
|
||||
type="button"
|
||||
>
|
||||
重置密码
|
||||
</Button>
|
||||
{canResetPassword ? (
|
||||
<Button
|
||||
data-testid="users-reset-password"
|
||||
size="sm"
|
||||
variant="secondary"
|
||||
disabled={resetMutation.isPending}
|
||||
onClick={() => resetMutation.mutate(item)}
|
||||
type="button"
|
||||
>
|
||||
重置密码
|
||||
</Button>
|
||||
) : null}
|
||||
|
||||
<Button
|
||||
data-testid="users-edit-roles"
|
||||
size="sm"
|
||||
variant="secondary"
|
||||
disabled={rolesMutation.isPending}
|
||||
onClick={() => setRoleDialogUser(item)}
|
||||
type="button"
|
||||
>
|
||||
角色
|
||||
</Button>
|
||||
{canEditRoles ? (
|
||||
<Button
|
||||
data-testid="users-edit-roles"
|
||||
size="sm"
|
||||
variant="secondary"
|
||||
disabled={rolesMutation.isPending}
|
||||
onClick={() => setRoleDialogUser(item)}
|
||||
type="button"
|
||||
>
|
||||
{item.roleCodes.includes("admin") ? "应急账号" : "角色"}
|
||||
</Button>
|
||||
) : null}
|
||||
|
||||
{protectedAccount && !isEmergencyAdmin ? (
|
||||
<span
|
||||
className="inline-flex items-center text-xs text-zinc-500"
|
||||
data-testid="users-protected-account"
|
||||
>
|
||||
受保护账号
|
||||
</span>
|
||||
) : null}
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))
|
||||
</TableRow>
|
||||
);
|
||||
})
|
||||
) : (
|
||||
<TableRow>
|
||||
<TableCell colSpan={5} className="py-3 text-zinc-500">
|
||||
@@ -359,7 +407,7 @@ export function UserManagementPage() {
|
||||
}
|
||||
}}
|
||||
user={roleDialogUser}
|
||||
roleOptions={ROLE_OPTIONS}
|
||||
roleOptions={roleOptions}
|
||||
onSubmit={(roles) => {
|
||||
if (roleDialogUser) {
|
||||
rolesMutation.mutate({ userId: roleDialogUser.id, roles });
|
||||
|
||||
在新工单中引用
屏蔽一个用户