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 { Flex } from "antd";
|
||||||
import prisma from "@/lib/prisma";
|
import prisma from "@/lib/prisma";
|
||||||
import { Space, Table, Tag } from "antd";
|
import { User } from "@prisma/client";
|
||||||
|
import UsersTable from "../../components/users-table";
|
||||||
const { Column, ColumnGroup } = Table;
|
|
||||||
|
|
||||||
async function getData() {
|
async function getData() {
|
||||||
const users = await prisma.user.findMany();
|
const users = await prisma.user.findMany();
|
||||||
return users;
|
return users;
|
||||||
}
|
}
|
||||||
export default async function UsersPage() {
|
export default async function UsersPage() {
|
||||||
const data = await getData();
|
const users: User[] = await getData();
|
||||||
console.log("data", data);
|
|
||||||
|
// console.log("data", data);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Flex gap="middle" vertical>
|
<Flex gap="middle" vertical>
|
||||||
<Table dataSource={data}>
|
<UsersTable users={users} />
|
||||||
{/*<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>
|
|
||||||
</Flex>
|
</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 {
|
||||||
const now = new Date();
|
if (!now) {
|
||||||
|
const now = new Date();
|
||||||
|
}
|
||||||
const formatter = new Intl.DateTimeFormat("zh-CN", {
|
const formatter = new Intl.DateTimeFormat("zh-CN", {
|
||||||
timeZone: "Asia/Shanghai", // 设置为中国标准时间
|
timeZone: "Asia/Shanghai", // 设置为中国标准时间
|
||||||
year: "numeric",
|
year: "numeric",
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
|
|
||||||
export const DENY_LIST: string[] = [
|
export const DENY_LIST: string[] = [
|
||||||
"suibian", "某某", "张三", "李四", "啊实打实", "官方回复电话", "笑死", "观化听风"
|
"suibian", "某某", "张三", "李四", "啊实打实", "官方回复电话", "笑死", "观化听风", "null", "undefined",
|
||||||
]
|
]
|
||||||
export const ADMIN_LIST: string[] = [
|
export const ADMIN_LIST: string[] = [
|
||||||
"司金辉", "sijinhui", "sijinhui@qq.com",
|
"司金辉", "sijinhui", "sijinhui@qq.com",
|
||||||
|
@ -40,6 +40,7 @@
|
|||||||
"nodemailer": "^6.9.13",
|
"nodemailer": "^6.9.13",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
|
"react-highlight-words": "^0.20.0",
|
||||||
"react-markdown": "^9.0.1",
|
"react-markdown": "^9.0.1",
|
||||||
"react-router-dom": "^6.21.3",
|
"react-router-dom": "^6.21.3",
|
||||||
"rehype-highlight": "^7.0.0",
|
"rehype-highlight": "^7.0.0",
|
||||||
|
Loading…
Reference in New Issue
Block a user