mirror of
https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web.git
synced 2025-10-05 01:26:38 +08:00
添加用户管理页
This commit is contained in:
parent
d6b5c86e84
commit
fc351f8734
@ -1,28 +1,21 @@
|
||||
"use client";
|
||||
|
||||
import { Flex } from "antd";
|
||||
import prisma from "@/lib/prisma";
|
||||
import { Space, Table, Tag } from "antd";
|
||||
|
||||
const { Column, ColumnGroup } = Table;
|
||||
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 data = await getData();
|
||||
console.log("data", data);
|
||||
const users: User[] = await getData();
|
||||
|
||||
// console.log("data", data);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Flex gap="middle" vertical>
|
||||
<Table dataSource={data}>
|
||||
{/*<Column title="ID" dataIndex="id" key="id" hidden={true}/>*/}
|
||||
<Column title="Name" key="name" dataIndex="name" />
|
||||
<Column title="UserName" key="username" dataIndex="username" />
|
||||
<Column title="email" key="email" dataIndex="email" />
|
||||
</Table>
|
||||
<UsersTable users={users} />
|
||||
</Flex>
|
||||
</>
|
||||
);
|
||||
|
165
app/app/(admin)/components/users-table.tsx
Normal file
165
app/app/(admin)/components/users-table.tsx
Normal file
@ -0,0 +1,165 @@
|
||||
"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";
|
||||
// 后期考虑删除该依赖
|
||||
|
||||
const { Column, ColumnGroup } = Table;
|
||||
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;
|
@ -1,5 +1,7 @@
|
||||
export function getCurrentTime(): string {
|
||||
export function getCurrentTime(now?: Date): string {
|
||||
if (!now) {
|
||||
const now = new Date();
|
||||
}
|
||||
const formatter = new Intl.DateTimeFormat("zh-CN", {
|
||||
timeZone: "Asia/Shanghai", // 设置为中国标准时间
|
||||
year: "numeric",
|
||||
|
@ -1,6 +1,6 @@
|
||||
|
||||
export const DENY_LIST: string[] = [
|
||||
"suibian", "某某", "张三", "李四", "啊实打实", "官方回复电话", "笑死", "观化听风"
|
||||
"suibian", "某某", "张三", "李四", "啊实打实", "官方回复电话", "笑死", "观化听风", "null", "undefined",
|
||||
]
|
||||
export const ADMIN_LIST: string[] = [
|
||||
"司金辉", "sijinhui", "sijinhui@qq.com",
|
||||
|
@ -40,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",
|
||||
|
Loading…
Reference in New Issue
Block a user