569 lines
17 KiB
TypeScript
569 lines
17 KiB
TypeScript
|
|
'use client';
|
|||
|
|
|
|||
|
|
import { useEffect, useState } from 'react';
|
|||
|
|
import useSWR from 'swr';
|
|||
|
|
import {
|
|||
|
|
Plus,
|
|||
|
|
Pencil,
|
|||
|
|
Trash2,
|
|||
|
|
KeyRound,
|
|||
|
|
Loader2,
|
|||
|
|
ShieldCheck,
|
|||
|
|
Crown,
|
|||
|
|
} from 'lucide-react';
|
|||
|
|
import { AdminHeader } from '@/components/admin/AdminHeader';
|
|||
|
|
import { PaginationTable, type Column } from '@/components/admin/PaginationTable';
|
|||
|
|
import { TableToolbar } from '@/components/admin/TableToolbar';
|
|||
|
|
import { Avatar } from '@/components/admin/Avatar';
|
|||
|
|
import { ImageUpload } from '@/components/admin/ImageUpload';
|
|||
|
|
import { Button } from '@/components/ui/button';
|
|||
|
|
import { Input } from '@/components/ui/input';
|
|||
|
|
import { Label } from '@/components/ui/label';
|
|||
|
|
import { Badge } from '@/components/ui/badge';
|
|||
|
|
import {
|
|||
|
|
Dialog,
|
|||
|
|
DialogContent,
|
|||
|
|
DialogFooter,
|
|||
|
|
DialogHeader,
|
|||
|
|
DialogTitle,
|
|||
|
|
} from '@/components/ui/dialog';
|
|||
|
|
import { adminApi } from '@/lib/admin-services';
|
|||
|
|
import { useAdminStore } from '@/store/adminStore';
|
|||
|
|
import { formatDate } from '@/lib/utils';
|
|||
|
|
import type { AdminRole, AdminUser } from '@/lib/types';
|
|||
|
|
|
|||
|
|
interface CreateForm {
|
|||
|
|
username: string;
|
|||
|
|
password: string;
|
|||
|
|
nickname: string;
|
|||
|
|
avatar: string;
|
|||
|
|
role: AdminRole;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
interface EditForm {
|
|||
|
|
nickname: string;
|
|||
|
|
avatar: string;
|
|||
|
|
role: AdminRole;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const CREATE_DEFAULT: CreateForm = {
|
|||
|
|
username: '',
|
|||
|
|
password: '',
|
|||
|
|
nickname: '',
|
|||
|
|
avatar: '',
|
|||
|
|
role: 'normal',
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const ROLE_LABEL: Record<AdminRole, string> = {
|
|||
|
|
super_admin: '超级管理员',
|
|||
|
|
normal: '普通管理员',
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
export default function AdminUserPage() {
|
|||
|
|
const [hydrated, setHydrated] = useState(false);
|
|||
|
|
const [page, setPage] = useState(1);
|
|||
|
|
const [keyword, setKeyword] = useState('');
|
|||
|
|
const [searchKw, setSearchKw] = useState('');
|
|||
|
|
|
|||
|
|
// 创建/编辑
|
|||
|
|
const [open, setOpen] = useState(false);
|
|||
|
|
const [editId, setEditId] = useState<number | null>(null);
|
|||
|
|
const [createForm, setCreateForm] = useState<CreateForm>(CREATE_DEFAULT);
|
|||
|
|
const [editForm, setEditForm] = useState<EditForm>({
|
|||
|
|
nickname: '',
|
|||
|
|
avatar: '',
|
|||
|
|
role: 'normal',
|
|||
|
|
});
|
|||
|
|
const [saving, setSaving] = useState(false);
|
|||
|
|
|
|||
|
|
// 重置密码
|
|||
|
|
const [pwdOpen, setPwdOpen] = useState(false);
|
|||
|
|
const [pwdId, setPwdId] = useState<number | null>(null);
|
|||
|
|
const [newPassword, setNewPassword] = useState('');
|
|||
|
|
const [pwdConfirm, setPwdConfirm] = useState('');
|
|||
|
|
const [pwdSaving, setPwdSaving] = useState(false);
|
|||
|
|
|
|||
|
|
const currentAdminId = useAdminStore((s) => s.admin?.id);
|
|||
|
|
const currentAdminRole = useAdminStore((s) => s.admin?.role);
|
|||
|
|
const isSuperAdmin = currentAdminRole === 'super_admin';
|
|||
|
|
|
|||
|
|
useEffect(() => setHydrated(true), []);
|
|||
|
|
|
|||
|
|
const { data, isLoading, mutate } = useSWR(
|
|||
|
|
hydrated ? ['/admin/admin-user', page, searchKw] : null,
|
|||
|
|
() =>
|
|||
|
|
adminApi.adminUserPaginate({
|
|||
|
|
page,
|
|||
|
|
pageSize: 10,
|
|||
|
|
keyword: searchKw || undefined,
|
|||
|
|
}),
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
const openCreate = () => {
|
|||
|
|
setEditId(null);
|
|||
|
|
setCreateForm(CREATE_DEFAULT);
|
|||
|
|
setOpen(true);
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const openEdit = async (id: number) => {
|
|||
|
|
const detail = await adminApi.adminUserDetail(id);
|
|||
|
|
setEditId(id);
|
|||
|
|
setEditForm({
|
|||
|
|
nickname: detail.nickname,
|
|||
|
|
avatar: detail.avatar ?? '',
|
|||
|
|
role: detail.role ?? 'normal',
|
|||
|
|
});
|
|||
|
|
setOpen(true);
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const onSave = async () => {
|
|||
|
|
if (editId) {
|
|||
|
|
if (!editForm.nickname.trim()) {
|
|||
|
|
alert('请填写名称');
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
setSaving(true);
|
|||
|
|
try {
|
|||
|
|
await adminApi.adminUserUpdate(editId, {
|
|||
|
|
nickname: editForm.nickname.trim(),
|
|||
|
|
avatar: editForm.avatar,
|
|||
|
|
role: editForm.role,
|
|||
|
|
});
|
|||
|
|
setOpen(false);
|
|||
|
|
await mutate();
|
|||
|
|
} catch (e) {
|
|||
|
|
alert((e as Error).message);
|
|||
|
|
} finally {
|
|||
|
|
setSaving(false);
|
|||
|
|
}
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
// create
|
|||
|
|
if (!createForm.username.trim() || !createForm.password || !createForm.nickname.trim()) {
|
|||
|
|
alert('请填写登录账号、初始密码和名称');
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
setSaving(true);
|
|||
|
|
try {
|
|||
|
|
await adminApi.adminUserCreate({
|
|||
|
|
username: createForm.username.trim(),
|
|||
|
|
password: createForm.password,
|
|||
|
|
nickname: createForm.nickname.trim(),
|
|||
|
|
avatar: createForm.avatar,
|
|||
|
|
role: createForm.role,
|
|||
|
|
});
|
|||
|
|
setOpen(false);
|
|||
|
|
await mutate();
|
|||
|
|
} catch (e) {
|
|||
|
|
alert((e as Error).message);
|
|||
|
|
} finally {
|
|||
|
|
setSaving(false);
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const onDelete = async (id: number) => {
|
|||
|
|
if (!confirm('确认删除该管理员账号?此操作不可恢复。')) return;
|
|||
|
|
try {
|
|||
|
|
await adminApi.adminUserDelete(id);
|
|||
|
|
await mutate();
|
|||
|
|
} catch (e) {
|
|||
|
|
alert((e as Error).message);
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const openResetPassword = (id: number) => {
|
|||
|
|
setPwdId(id);
|
|||
|
|
setNewPassword('');
|
|||
|
|
setPwdConfirm('');
|
|||
|
|
setPwdOpen(true);
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const onResetPassword = async () => {
|
|||
|
|
if (!pwdId) return;
|
|||
|
|
if (newPassword.length < 6) {
|
|||
|
|
alert('新密码至少 6 位');
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
if (newPassword !== pwdConfirm) {
|
|||
|
|
alert('两次输入的密码不一致');
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
setPwdSaving(true);
|
|||
|
|
try {
|
|||
|
|
await adminApi.adminUserResetPassword(pwdId, newPassword);
|
|||
|
|
setPwdOpen(false);
|
|||
|
|
} catch (e) {
|
|||
|
|
alert((e as Error).message);
|
|||
|
|
} finally {
|
|||
|
|
setPwdSaving(false);
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const columns: Column<AdminUser>[] = [
|
|||
|
|
{ key: 'id', title: 'ID', width: 60 },
|
|||
|
|
{
|
|||
|
|
key: 'avatar',
|
|||
|
|
title: '头像',
|
|||
|
|
width: 80,
|
|||
|
|
render: (r) => <Avatar src={r.avatar} name={r.nickname} size={36} />,
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
key: 'username',
|
|||
|
|
title: '登录账号',
|
|||
|
|
width: 200,
|
|||
|
|
render: (r) => (
|
|||
|
|
<div className="flex items-center gap-2">
|
|||
|
|
<span className="font-medium text-gray-900">{r.username}</span>
|
|||
|
|
{r.id === currentAdminId && (
|
|||
|
|
<Badge variant="success">
|
|||
|
|
<ShieldCheck className="h-3 w-3" /> 当前
|
|||
|
|
</Badge>
|
|||
|
|
)}
|
|||
|
|
</div>
|
|||
|
|
),
|
|||
|
|
},
|
|||
|
|
{ key: 'nickname', title: '名称', width: 140 },
|
|||
|
|
{
|
|||
|
|
key: 'role',
|
|||
|
|
title: '角色',
|
|||
|
|
width: 130,
|
|||
|
|
render: (r) =>
|
|||
|
|
r.role === 'super_admin' ? (
|
|||
|
|
<Badge variant="warning">
|
|||
|
|
<Crown className="h-3 w-3" /> {ROLE_LABEL[r.role]}
|
|||
|
|
</Badge>
|
|||
|
|
) : (
|
|||
|
|
<Badge variant="secondary">{ROLE_LABEL[r.role]}</Badge>
|
|||
|
|
),
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
key: 'createdAt',
|
|||
|
|
title: '创建时间',
|
|||
|
|
width: 160,
|
|||
|
|
render: (r) => formatDate(r.createdAt, 'YYYY-MM-DD HH:mm'),
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
key: '_op',
|
|||
|
|
title: '操作',
|
|||
|
|
width: 240,
|
|||
|
|
render: (r) =>
|
|||
|
|
isSuperAdmin ? (
|
|||
|
|
<div className="flex items-center gap-1.5 whitespace-nowrap">
|
|||
|
|
<Button size="sm" variant="outline" onClick={() => openEdit(r.id)}>
|
|||
|
|
<Pencil className="h-3 w-3" /> 编辑
|
|||
|
|
</Button>
|
|||
|
|
<Button
|
|||
|
|
size="sm"
|
|||
|
|
variant="outline"
|
|||
|
|
onClick={() => openResetPassword(r.id)}
|
|||
|
|
>
|
|||
|
|
<KeyRound className="h-3 w-3" /> 重置密码
|
|||
|
|
</Button>
|
|||
|
|
{r.id !== currentAdminId && (
|
|||
|
|
<Button
|
|||
|
|
size="sm"
|
|||
|
|
variant="destructive"
|
|||
|
|
onClick={() => onDelete(r.id)}
|
|||
|
|
>
|
|||
|
|
<Trash2 className="h-3 w-3" /> 删除
|
|||
|
|
</Button>
|
|||
|
|
)}
|
|||
|
|
</div>
|
|||
|
|
) : (
|
|||
|
|
<span className="text-xs text-gray-400">无操作权限</span>
|
|||
|
|
),
|
|||
|
|
},
|
|||
|
|
];
|
|||
|
|
|
|||
|
|
return (
|
|||
|
|
<div>
|
|||
|
|
<AdminHeader
|
|||
|
|
title="管理员账号"
|
|||
|
|
description="维护可登录后台的员工账号(支持手机号/邮箱/自定义账号)"
|
|||
|
|
/>
|
|||
|
|
|
|||
|
|
<TableToolbar
|
|||
|
|
left={
|
|||
|
|
<>
|
|||
|
|
<Input
|
|||
|
|
placeholder="搜索 登录账号/名称"
|
|||
|
|
value={keyword}
|
|||
|
|
onChange={(e) => setKeyword(e.target.value)}
|
|||
|
|
onKeyDown={(e) => {
|
|||
|
|
if (e.key === 'Enter') {
|
|||
|
|
setSearchKw(keyword);
|
|||
|
|
setPage(1);
|
|||
|
|
}
|
|||
|
|
}}
|
|||
|
|
className="max-w-xs"
|
|||
|
|
/>
|
|||
|
|
<Button
|
|||
|
|
variant="outline"
|
|||
|
|
onClick={() => {
|
|||
|
|
setSearchKw(keyword);
|
|||
|
|
setPage(1);
|
|||
|
|
}}
|
|||
|
|
>
|
|||
|
|
搜索
|
|||
|
|
</Button>
|
|||
|
|
</>
|
|||
|
|
}
|
|||
|
|
right={
|
|||
|
|
isSuperAdmin ? (
|
|||
|
|
<Button onClick={openCreate}>
|
|||
|
|
<Plus className="h-4 w-4" /> 新增账号
|
|||
|
|
</Button>
|
|||
|
|
) : null
|
|||
|
|
}
|
|||
|
|
/>
|
|||
|
|
|
|||
|
|
<PaginationTable<AdminUser>
|
|||
|
|
columns={columns}
|
|||
|
|
rows={data?.list ?? []}
|
|||
|
|
total={data?.total ?? 0}
|
|||
|
|
page={page}
|
|||
|
|
pageSize={10}
|
|||
|
|
loading={isLoading}
|
|||
|
|
onPageChange={setPage}
|
|||
|
|
rowKey={(r) => String(r.id)}
|
|||
|
|
/>
|
|||
|
|
|
|||
|
|
{/* 创建 / 编辑弹窗 */}
|
|||
|
|
<Dialog open={open} onOpenChange={setOpen}>
|
|||
|
|
<DialogContent className="max-w-lg">
|
|||
|
|
<DialogHeader>
|
|||
|
|
<DialogTitle>
|
|||
|
|
{editId ? '编辑管理员账号' : '新增管理员账号'}
|
|||
|
|
</DialogTitle>
|
|||
|
|
</DialogHeader>
|
|||
|
|
<div className="space-y-4">
|
|||
|
|
{editId ? (
|
|||
|
|
<>
|
|||
|
|
<div className="space-y-1.5">
|
|||
|
|
<Label>登录账号(不可修改)</Label>
|
|||
|
|
<Input
|
|||
|
|
value={
|
|||
|
|
data?.list.find((x) => x.id === editId)?.username ?? ''
|
|||
|
|
}
|
|||
|
|
disabled
|
|||
|
|
/>
|
|||
|
|
<p className="text-xs text-gray-500">
|
|||
|
|
登录账号创建后不可修改,如需修改请删除后重建
|
|||
|
|
</p>
|
|||
|
|
</div>
|
|||
|
|
<div className="space-y-1.5">
|
|||
|
|
<Label>名称 *</Label>
|
|||
|
|
<Input
|
|||
|
|
value={editForm.nickname}
|
|||
|
|
onChange={(e) =>
|
|||
|
|
setEditForm({ ...editForm, nickname: e.target.value })
|
|||
|
|
}
|
|||
|
|
/>
|
|||
|
|
</div>
|
|||
|
|
<div className="space-y-1.5">
|
|||
|
|
<Label>头像</Label>
|
|||
|
|
<ImageUpload
|
|||
|
|
value={editForm.avatar}
|
|||
|
|
onChange={(url) =>
|
|||
|
|
setEditForm({ ...editForm, avatar: url })
|
|||
|
|
}
|
|||
|
|
hint="建议正方形头像,单图 ≤ 2M"
|
|||
|
|
/>
|
|||
|
|
</div>
|
|||
|
|
<div className="space-y-1.5">
|
|||
|
|
<Label>角色 *</Label>
|
|||
|
|
<div className="flex items-center gap-4">
|
|||
|
|
<label className="flex items-center gap-2 text-sm">
|
|||
|
|
<input
|
|||
|
|
type="radio"
|
|||
|
|
name="edit-role"
|
|||
|
|
value="normal"
|
|||
|
|
checked={editForm.role === 'normal'}
|
|||
|
|
onChange={() =>
|
|||
|
|
setEditForm({ ...editForm, role: 'normal' })
|
|||
|
|
}
|
|||
|
|
/>
|
|||
|
|
普通管理员
|
|||
|
|
</label>
|
|||
|
|
<label className="flex items-center gap-2 text-sm">
|
|||
|
|
<input
|
|||
|
|
type="radio"
|
|||
|
|
name="edit-role"
|
|||
|
|
value="super_admin"
|
|||
|
|
checked={editForm.role === 'super_admin'}
|
|||
|
|
onChange={() =>
|
|||
|
|
setEditForm({ ...editForm, role: 'super_admin' })
|
|||
|
|
}
|
|||
|
|
/>
|
|||
|
|
超级管理员
|
|||
|
|
</label>
|
|||
|
|
</div>
|
|||
|
|
{editId === currentAdminId && (
|
|||
|
|
<p className="text-xs text-amber-600">
|
|||
|
|
不能将自己降级为普通管理员
|
|||
|
|
</p>
|
|||
|
|
)}
|
|||
|
|
</div>
|
|||
|
|
</>
|
|||
|
|
) : (
|
|||
|
|
<>
|
|||
|
|
<div className="space-y-1.5">
|
|||
|
|
<Label>登录账号 *</Label>
|
|||
|
|
<Input
|
|||
|
|
placeholder="可使用手机号、邮箱或自定义账号"
|
|||
|
|
value={createForm.username}
|
|||
|
|
maxLength={50}
|
|||
|
|
onChange={(e) =>
|
|||
|
|
setCreateForm({
|
|||
|
|
...createForm,
|
|||
|
|
username: e.target.value,
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
/>
|
|||
|
|
<p className="text-xs text-gray-500">
|
|||
|
|
2-50 个字符,创建后不可修改
|
|||
|
|
</p>
|
|||
|
|
</div>
|
|||
|
|
<div className="space-y-1.5">
|
|||
|
|
<Label>初始密码 *</Label>
|
|||
|
|
<Input
|
|||
|
|
type="password"
|
|||
|
|
placeholder="至少 6 位"
|
|||
|
|
value={createForm.password}
|
|||
|
|
onChange={(e) =>
|
|||
|
|
setCreateForm({
|
|||
|
|
...createForm,
|
|||
|
|
password: e.target.value,
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
/>
|
|||
|
|
<p className="text-xs text-gray-500">
|
|||
|
|
创建后请通过安全渠道告知该员工
|
|||
|
|
</p>
|
|||
|
|
</div>
|
|||
|
|
<div className="space-y-1.5">
|
|||
|
|
<Label>名称 *</Label>
|
|||
|
|
<Input
|
|||
|
|
placeholder="员工姓名或别名"
|
|||
|
|
value={createForm.nickname}
|
|||
|
|
maxLength={50}
|
|||
|
|
onChange={(e) =>
|
|||
|
|
setCreateForm({
|
|||
|
|
...createForm,
|
|||
|
|
nickname: e.target.value,
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
/>
|
|||
|
|
</div>
|
|||
|
|
<div className="space-y-1.5">
|
|||
|
|
<Label>头像</Label>
|
|||
|
|
<ImageUpload
|
|||
|
|
value={createForm.avatar}
|
|||
|
|
onChange={(url) =>
|
|||
|
|
setCreateForm({ ...createForm, avatar: url })
|
|||
|
|
}
|
|||
|
|
hint="可选,建议正方形头像,单图 ≤ 2M"
|
|||
|
|
/>
|
|||
|
|
</div>
|
|||
|
|
<div className="space-y-1.5">
|
|||
|
|
<Label>角色 *</Label>
|
|||
|
|
<div className="flex items-center gap-4">
|
|||
|
|
<label className="flex items-center gap-2 text-sm">
|
|||
|
|
<input
|
|||
|
|
type="radio"
|
|||
|
|
name="create-role"
|
|||
|
|
value="normal"
|
|||
|
|
checked={createForm.role === 'normal'}
|
|||
|
|
onChange={() =>
|
|||
|
|
setCreateForm({ ...createForm, role: 'normal' })
|
|||
|
|
}
|
|||
|
|
/>
|
|||
|
|
普通管理员
|
|||
|
|
</label>
|
|||
|
|
<label className="flex items-center gap-2 text-sm">
|
|||
|
|
<input
|
|||
|
|
type="radio"
|
|||
|
|
name="create-role"
|
|||
|
|
value="super_admin"
|
|||
|
|
checked={createForm.role === 'super_admin'}
|
|||
|
|
onChange={() =>
|
|||
|
|
setCreateForm({ ...createForm, role: 'super_admin' })
|
|||
|
|
}
|
|||
|
|
/>
|
|||
|
|
超级管理员
|
|||
|
|
</label>
|
|||
|
|
</div>
|
|||
|
|
<p className="text-xs text-gray-500">
|
|||
|
|
超级管理员可管理所有账号及删除内容
|
|||
|
|
</p>
|
|||
|
|
</div>
|
|||
|
|
</>
|
|||
|
|
)}
|
|||
|
|
</div>
|
|||
|
|
<DialogFooter>
|
|||
|
|
<Button variant="outline" onClick={() => setOpen(false)}>
|
|||
|
|
取消
|
|||
|
|
</Button>
|
|||
|
|
<Button onClick={onSave} disabled={saving}>
|
|||
|
|
{saving ? (
|
|||
|
|
<>
|
|||
|
|
<Loader2 className="h-4 w-4 animate-spin" />
|
|||
|
|
保存中…
|
|||
|
|
</>
|
|||
|
|
) : (
|
|||
|
|
'保存'
|
|||
|
|
)}
|
|||
|
|
</Button>
|
|||
|
|
</DialogFooter>
|
|||
|
|
</DialogContent>
|
|||
|
|
</Dialog>
|
|||
|
|
|
|||
|
|
{/* 重置密码弹窗 */}
|
|||
|
|
<Dialog open={pwdOpen} onOpenChange={setPwdOpen}>
|
|||
|
|
<DialogContent className="max-w-md">
|
|||
|
|
<DialogHeader>
|
|||
|
|
<DialogTitle>重置密码</DialogTitle>
|
|||
|
|
</DialogHeader>
|
|||
|
|
<div className="space-y-4">
|
|||
|
|
<div className="space-y-1.5">
|
|||
|
|
<Label>新密码 *</Label>
|
|||
|
|
<Input
|
|||
|
|
type="password"
|
|||
|
|
placeholder="至少 6 位"
|
|||
|
|
value={newPassword}
|
|||
|
|
onChange={(e) => setNewPassword(e.target.value)}
|
|||
|
|
/>
|
|||
|
|
</div>
|
|||
|
|
<div className="space-y-1.5">
|
|||
|
|
<Label>确认新密码 *</Label>
|
|||
|
|
<Input
|
|||
|
|
type="password"
|
|||
|
|
placeholder="再次输入新密码"
|
|||
|
|
value={pwdConfirm}
|
|||
|
|
onChange={(e) => setPwdConfirm(e.target.value)}
|
|||
|
|
/>
|
|||
|
|
</div>
|
|||
|
|
<p className="text-xs text-gray-500">
|
|||
|
|
重置后请通过安全渠道告知该员工
|
|||
|
|
</p>
|
|||
|
|
</div>
|
|||
|
|
<DialogFooter>
|
|||
|
|
<Button variant="outline" onClick={() => setPwdOpen(false)}>
|
|||
|
|
取消
|
|||
|
|
</Button>
|
|||
|
|
<Button onClick={onResetPassword} disabled={pwdSaving}>
|
|||
|
|
{pwdSaving ? (
|
|||
|
|
<>
|
|||
|
|
<Loader2 className="h-4 w-4 animate-spin" /> 重置中…
|
|||
|
|
</>
|
|||
|
|
) : (
|
|||
|
|
'确认重置'
|
|||
|
|
)}
|
|||
|
|
</Button>
|
|||
|
|
</DialogFooter>
|
|||
|
|
</DialogContent>
|
|||
|
|
</Dialog>
|
|||
|
|
</div>
|
|||
|
|
);
|
|||
|
|
}
|