328 lines
9.8 KiB
TypeScript
328 lines
9.8 KiB
TypeScript
|
|
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 { 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,
|
|||
|
|
}: 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">
|
|||
|
|
{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 { table, query, search } = useFetchList(() =>
|
|||
|
|
useColumns({
|
|||
|
|
handleEnable,
|
|||
|
|
handleDisable,
|
|||
|
|
handleCancelUser,
|
|||
|
|
}),
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
// 筛选条件(点击查询后生效)
|
|||
|
|
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}
|
|||
|
|
/>
|
|||
|
|
<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>
|
|||
|
|
</>
|
|||
|
|
);
|
|||
|
|
}
|