Merge pull request #34 from sijinhui/users

添加用户管理页面
This commit is contained in:
sijinhui 2024-03-26 00:35:37 +08:00 committed by GitHub
commit 7c9c7f511c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 225 additions and 38 deletions

View File

@ -1,17 +1,17 @@
import UsageByModel from "./usage-by-model-chart";
import UserByMap from "./user-by-map";
import { getSession } from "@/lib/auth";
import { isName, ADMIN_LIST } from "@/lib/auth_list";
import { redirect } from "next/navigation";
// import { getSession } from "@/lib/auth";
// import { isName, ADMIN_LIST } from "@/lib/auth_list";
// import { redirect } from "next/navigation";
import { Flex } from "antd";
export default async function AdminPage() {
const session = await getSession();
const name = session?.user?.email || session?.user?.name;
if (!(name && ADMIN_LIST.includes(name))) {
// Replace '/dashboard' with the desired redirect path
redirect("/");
}
// const session = await getSession();
// const name = session?.user?.email || session?.user?.name;
// if (!(name && ADMIN_LIST.includes(name))) {
// // Replace '/dashboard' with the desired redirect path
// redirect("/");
// }
return (
<>

View File

@ -1,6 +1,9 @@
"use client";
import React, { ReactNode, useState } from "react";
import { useSession } from "next-auth/react";
import { redirect } from "next/navigation";
import { ADMIN_LIST } from "@/lib/auth_list";
import React, { ReactNode, useEffect, useState } from "react";
import {
MenuFoldOutlined,
MenuUnfoldOutlined,
@ -15,12 +18,23 @@ const { Header, Sider, Content } = Layout;
function MainLayout({ children }: { children: ReactNode }) {
// const [theme, setTheme] = useState<ThemeConfig>('dark');
const { data, status } = useSession();
const name = data?.user?.email || data?.user?.name;
// console.log('name', name, data, status)
const [collapsed, setCollapsed] = useState(false);
const {
token: { colorBgContainer, borderRadiusLG, colorBgLayout },
} = theme.useToken();
// 客户端才执行
useEffect(() => {
// 用户已登录,且没设置密码
// if (status === "loading") return;
if (status === "authenticated" && !(name && ADMIN_LIST.includes(name))) {
redirect("/");
}
// 状态变化时,重新判断
}, [name, status]);
return (
<ConfigProvider
theme={{

View File

@ -0,0 +1,22 @@
import { Flex } from "antd";
import prisma from "@/lib/prisma";
import { User } from "@prisma/client";
import UsersTable from "../../components/users-table";
async function getData() {
const users = await prisma.user.findMany();
return users;
}
export default async function UsersPage() {
const users: User[] = await getData();
// console.log("data", data);
return (
<>
<Flex gap="middle" vertical>
<UsersTable users={users} />
</Flex>
</>
);
}

View File

@ -68,33 +68,15 @@ const SideBar: React.FC = () => {
setCurrent(e.key);
router.push(e.key);
};
useEffect(() => {
// 如果按钮和路径不相等,那其实应该跳转到按钮的网址
if (current != pathname) {
router.push(current);
}
}, [current, pathname, router]);
// useEffect(() => {
// const handleStart = () => setLoading(true)
// const handleComplete = () => setLoading(false);
// router.events.on('routeChangeStart', handleStart);
// router.events.on('routeChangeComplete', handleStop);
// router.events.on('routeChangeError', handleStop);
//
// return () => {
// router.
// }
//
// }, [router]);
// // 如果按钮和路径不相等,那其实应该跳转到按钮的网址
// if (current != pathname) {
// router.push(current);
// }
// }, [current, pathname, router]);
return (
<>
{/*<Switch*/}
{/* // checked={theme === 'dark'}*/}
{/* // onChange={changeTheme}*/}
{/* checkedChildren="Dark"*/}
{/* unCheckedChildren="Light"*/}
{/*/>*/}
<br />
<br />
<Menu

View File

@ -0,0 +1,164 @@
"use client";
import React, { useRef, useState } from "react";
import { User } from "@prisma/client";
import { Space, Table, Tag, Input, Button } from "antd";
import { SearchOutlined } from "@ant-design/icons";
import type { FilterDropdownProps } from "antd/es/table/interface";
import type { GetRef, TableColumnsType, TableColumnType } from "antd";
import Highlighter from "react-highlight-words";
// 后期考虑删除该依赖
import { getCurrentTime } from "@/app/utils/custom";
interface UserInterface {
users: User[];
}
type DataIndex = keyof User;
type InputRef = GetRef<typeof Input>;
function UsersTable({ users }: UserInterface) {
// const data = {}
// console.log('[data]', users)
const [searchText, setSearchText] = useState("");
const [searchedColumn, setSearchedColumn] = useState("");
const searchInput = useRef<InputRef>(null);
const handleSearch = (
selectedKeys: string[],
confirm: FilterDropdownProps["confirm"],
dataIndex: DataIndex,
) => {
confirm();
setSearchText(selectedKeys[0]);
setSearchedColumn(dataIndex);
};
const handleReset = (clearFilters: () => void) => {
clearFilters();
setSearchText("");
};
const getColumnSearchProps = (
dataIndex: DataIndex,
): TableColumnType<User> => ({
filterDropdown: ({
setSelectedKeys,
selectedKeys,
confirm,
clearFilters,
close,
}) => (
<div style={{ padding: 8 }} onKeyDown={(e) => e.stopPropagation()}>
<Input
ref={searchInput}
placeholder={`Search ${dataIndex}`}
value={selectedKeys[0]}
onChange={(e) =>
setSelectedKeys(e.target.value ? [e.target.value] : [])
}
onPressEnter={() =>
handleSearch(selectedKeys as string[], confirm, dataIndex)
}
style={{ marginBottom: 8, display: "block" }}
/>
<Space>
<Button
type="primary"
onClick={() =>
handleSearch(selectedKeys as string[], confirm, dataIndex)
}
icon={<SearchOutlined />}
size="small"
style={{ width: 90 }}
>
Search
</Button>
<Button
onClick={() => clearFilters && handleReset(clearFilters)}
size="small"
style={{ width: 90 }}
>
Reset
</Button>
<Button
type="link"
size="small"
onClick={() => {
confirm({ closeDropdown: false });
setSearchText((selectedKeys as string[])[0]);
setSearchedColumn(dataIndex);
}}
>
Filter
</Button>
<Button
type="link"
size="small"
onClick={() => {
close();
}}
>
close
</Button>
</Space>
</div>
),
filterIcon: (filtered: boolean) => (
<SearchOutlined style={{ color: filtered ? "#1677ff" : undefined }} />
),
onFilter: (value, record: User) => {
let result = record?.[dataIndex];
if (result) {
return result
.toString()
.toLowerCase()
.includes((value as string).toLowerCase());
}
return false;
},
onFilterDropdownOpenChange: (visible) => {
if (visible) {
setTimeout(() => searchInput.current?.select(), 100);
}
},
render: (text) =>
searchedColumn === dataIndex ? (
<Highlighter
highlightStyle={{ backgroundColor: "#ffc069", padding: 0 }}
searchWords={[searchText]}
autoEscape
textToHighlight={text ? text.toString() : ""}
/>
) : (
text
),
});
const columns: TableColumnsType<User> = [
{ title: "Name", dataIndex: "name", ...getColumnSearchProps("name") },
{
title: "UserName",
dataIndex: "username",
...getColumnSearchProps("username"),
},
{ title: "Email", dataIndex: "email", ...getColumnSearchProps("email") },
{
title: "createdAt",
dataIndex: "createdAt",
render: (value) => getCurrentTime(value),
sorter: (a, b) => {
if (a.createdAt < b.createdAt) return 1;
return -1;
},
},
{
title: "updatedAt",
dataIndex: "updatedAt",
render: (value) => getCurrentTime(value),
},
];
return <Table dataSource={users} rowKey="id" columns={columns} />;
}
export default UsersTable;

View File

@ -1,5 +1,7 @@
export function getCurrentTime(): string {
const now = new Date();
export function getCurrentTime(now?: Date): string {
if (!now) {
const now = new Date();
}
const formatter = new Intl.DateTimeFormat("zh-CN", {
timeZone: "Asia/Shanghai", // 设置为中国标准时间
year: "numeric",

View File

@ -1,6 +1,6 @@
export const DENY_LIST: string[] = [
"suibian", "某某", "张三", "李四", "啊实打实", "官方回复电话", "笑死", "观化听风"
"suibian", "某某", "张三", "李四", "啊实打实", "官方回复电话", "笑死", "观化听风", "null", "undefined",
]
export const ADMIN_LIST: string[] = [
"司金辉", "sijinhui", "sijinhui@qq.com",

View File

@ -27,6 +27,7 @@
"@vercel/speed-insights": "^1.0.9",
"antd": "^5.15.1",
"bcryptjs": "^2.4.3",
"cron": "^3.1.6",
"echarts": "^5.4.3",
"emoji-picker-react": "^4.7.10",
"fuse.js": "^7.0.0",
@ -39,6 +40,7 @@
"nodemailer": "^6.9.13",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-highlight-words": "^0.20.0",
"react-markdown": "^9.0.1",
"react-router-dom": "^6.21.3",
"rehype-highlight": "^7.0.0",
@ -60,6 +62,7 @@
"@types/node": "^20.11.10",
"@types/react": "^18.2.48",
"@types/react-dom": "^18.2.7",
"@types/react-highlight-words": "^0.16.7",
"@types/react-katex": "^3.0.0",
"@types/spark-md5": "^3.0.4",
"autoprefixer": "^10.4.17",
@ -74,7 +77,7 @@
"postcss": "^8.4.33",
"prettier": "^3.2.4",
"prettier-plugin-tailwindcss": "^0.5.11",
"prisma": "^5.9.0",
"prisma": "^5.11.0",
"tailwindcss": "^3.4.1",
"tailwindcss-animate": "^1.0.7",
"typescript": "^5.3.3",