Files
senmesh-zc/src/routes/admin/org-account/index.tsx

351 lines
10 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useState } from "react";
import { createFileRoute } from "@tanstack/react-router";
import { RotateCcw, Search } from "lucide-react";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Badge } from "@/components/ui/badge";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { DataTable } from "@/components/data-table";
import { Pagination } from "@/components/pagination";
import { useFetchList } from "./-hooks/useFetchList";
import { useUserStatusActions } from "@/hooks/use-user-status-actions";
import { UserActionConfirmDialog } from "./-components/UserActionConfirmDialog";
import { ResetUserPasswordDialog } from "@/components/reset-user-password-dialog";
import { useUserStore } from "@/stores/user";
import { DateRangePicker } from "@/components/date-range-picker";
export const Route = createFileRoute("/admin/org-account/")({
component: RouteComponent,
});
const initialFilters = {
orgNameLike: "",
usernameLike: "",
realNameLike: "",
phoneLike: "",
enabled: "",
status: "",
createTimeStart: "",
createTimeEnd: "",
};
export function useColumns({
handleEnable,
handleDisable,
handleCancelUser,
handleResetPassword,
}: any) {
const currentUserId = useUserStore((state: any) => state.user?.id);
return [
{
accessorKey: "orgName",
header: "机构名称",
cell: ({ row }: any) => (
<div className="flex items-center gap-1">
<div>{row.getValue("orgName")}</div>
{row.original.administrator && (
<Badge
variant="outline"
className="text-primary border-primary shrink-0"
>
管理员
</Badge>
)}
</div>
),
},
{
accessorKey: "username",
header: "账号",
cell: ({ row }: any) => <div>{row.getValue("username")}</div>,
},
{
accessorKey: "realName",
header: "姓名",
cell: ({ row }: any) => (
<div>{row.getValue("realName") || row.original.nickname || "-"}</div>
),
},
{
accessorKey: "phone",
header: "手机号",
cell: ({ row }: any) => <div>{row.getValue("phone") || "-"}</div>,
},
{
accessorKey: "userType",
header: "用户类型",
cell: ({ row }: any) => (
<div>{row.getValue("userType") === 2 ? "个人用户" : "企业用户"}</div>
),
},
{
accessorKey: "enabled",
header: "启用状态",
cell: ({ row }: any) =>
row.getValue("enabled") ? (
<Badge className="bg-green-500/10 text-green-600">启用</Badge>
) : (
<Badge className="bg-red-500/10 text-red-500">禁用</Badge>
),
},
{
accessorKey: "status",
header: "状态",
cell: ({ row }: any) => {
const status = Number(row.getValue("status"));
return (
<div className="text-xs">
{status === 1 ? (
<Badge variant="secondary">待激活</Badge>
) : status === 2 ? (
<Badge variant="secondary" className="text-green-600">
已激活
</Badge>
) : (
<Badge variant="secondary" className="text-red-600">
已注销
</Badge>
)}
</div>
);
},
},
{
accessorKey: "createTime",
header: "创建时间",
cell: ({ row }: any) => <div>{row.getValue("createTime")}</div>,
},
{
id: "actions",
enableHiding: false,
header: "操作",
cell: ({ row }: any) => {
const record = row.original;
// 已注销或当前登录人自己,不展示操作
if (Number(record.status) === 3 || record.userId === currentUserId) {
return null;
}
return (
<div className="space-x-2">
<Button
size="sm"
variant="outline"
onClick={() => handleResetPassword(record)}
>
重置密码
</Button>
{record.enabled ? (
<Button
size="sm"
variant="outline"
onClick={() => handleDisable(record)}
>
禁用
</Button>
) : (
<Button
size="sm"
variant="outline"
onClick={() => handleEnable(record)}
>
启用
</Button>
)}
<Button
size="sm"
variant="destructive"
onClick={() => handleCancelUser(record)}
>
注销
</Button>
</div>
);
},
},
];
}
function RouteComponent() {
const [currentEntity, setCurrentEntity] = useState<any>(null);
// 启用(直接操作)
const { enable } = useUserStatusActions({
onSuccess: () => query?.refetch(),
});
const handleEnable = (record: any) => {
enable(record.userId);
};
// 禁用/注销(二次确认)
const [confirmAction, setConfirmAction] = useState<
"disable" | "adminCancel"
>("disable");
const [openConfirmDialog, setOpenConfirmDialog] = useState(false);
const handleDisable = (record: any) => {
setCurrentEntity(record);
setConfirmAction("disable");
setOpenConfirmDialog(true);
};
const handleCancelUser = (record: any) => {
setCurrentEntity(record);
setConfirmAction("adminCancel");
setOpenConfirmDialog(true);
};
// 重置密码
const [openResetPasswordDialog, setOpenResetPasswordDialog] =
useState(false);
const handleResetPassword = (record: any) => {
setCurrentEntity(record);
setOpenResetPasswordDialog(true);
};
const { table, query, search } = useFetchList(() =>
useColumns({
handleEnable,
handleDisable,
handleCancelUser,
handleResetPassword,
}),
);
// 筛选条件(点击查询后生效)
const [filters, setFilters] = useState<any>(initialFilters);
const setFilter = (key: string, value: string) => {
setFilters((prev: any) => ({ ...prev, [key]: value }));
};
const handleSearch = () => {
search({
...filters,
createTimeStart: filters.createTimeStart
? `${filters.createTimeStart} 00:00:00`
: "",
createTimeEnd: filters.createTimeEnd
? `${filters.createTimeEnd} 23:59:59`
: "",
});
};
const handleReset = () => {
setFilters(initialFilters);
search({});
};
return (
<>
<UserActionConfirmDialog
open={openConfirmDialog}
setOpen={setOpenConfirmDialog}
action={confirmAction}
data={currentEntity}
refetch={query?.refetch}
/>
<ResetUserPasswordDialog
open={openResetPasswordDialog}
setOpen={setOpenResetPasswordDialog}
data={currentEntity}
/>
<div className="p-4 box-border min-h-full space-y-4">
<div>
<div className="text-lg">机构账号</div>
<div className="text-xs text-black/50">
查看各机构下的账号信息,启用/禁用/注销账号
</div>
</div>
<div className="p-4 rounded-lg border bg-white space-y-2">
<div className="flex flex-wrap items-center gap-2">
<Input
placeholder="机构名称"
className="w-[160px]"
value={filters.orgNameLike}
onChange={(e: any) => setFilter("orgNameLike", e.target.value)}
/>
<Input
placeholder="账号"
className="w-[140px]"
value={filters.usernameLike}
onChange={(e: any) => setFilter("usernameLike", e.target.value)}
/>
<Input
placeholder="真实姓名"
className="w-[120px]"
value={filters.realNameLike}
onChange={(e: any) => setFilter("realNameLike", e.target.value)}
/>
<Input
placeholder="手机号"
className="w-[140px]"
value={filters.phoneLike}
onChange={(e: any) => setFilter("phoneLike", e.target.value)}
/>
<Select
value={filters.enabled || "all"}
onValueChange={(value: string) =>
setFilter("enabled", value === "all" ? "" : value)
}
>
<SelectTrigger className="w-[110px]">
<SelectValue placeholder="启用状态" />
</SelectTrigger>
<SelectContent>
<SelectItem value="all">启用状态</SelectItem>
<SelectItem value="true">启用</SelectItem>
<SelectItem value="false">禁用</SelectItem>
</SelectContent>
</Select>
<Select
value={filters.status || "all"}
onValueChange={(value: string) =>
setFilter("status", value === "all" ? "" : value)
}
>
<SelectTrigger className="w-[110px]">
<SelectValue placeholder="用户状态" />
</SelectTrigger>
<SelectContent>
<SelectItem value="all">用户状态</SelectItem>
<SelectItem value="1">待激活</SelectItem>
<SelectItem value="2">已激活</SelectItem>
<SelectItem value="3">已注销</SelectItem>
</SelectContent>
</Select>
<div className="flex items-center gap-1 text-sm text-black/60">
创建时间
<DateRangePicker
from={filters.createTimeStart}
to={filters.createTimeEnd}
onChange={(range) => {
setFilter("createTimeStart", range.from ?? "");
setFilter("createTimeEnd", range.to ?? "");
}}
/>
</div>
<Button onClick={handleSearch}>
<Search />
查询
</Button>
<Button variant="outline" onClick={handleReset}>
<RotateCcw />
重置
</Button>
</div>
<div className="relative overflow-hidden">
<DataTable table={table} query={query} />
<Pagination
table={table}
className="justify-end p-2 sticky bottom-0 left-0 right-0 z-10 bg-white"
/>
</div>
</div>
</div>
</>
);
}