用户管理表初步成型

This commit is contained in:
sijinhui 2024-03-26 23:23:03 +08:00
parent c8b6c31357
commit 45fa760db1
7 changed files with 325 additions and 183 deletions

View File

@ -0,0 +1,23 @@
import { NextRequest, NextResponse } from "next/server";
import prisma from "@/lib/prisma";
async function handle(
req: NextRequest,
{ params }: { params: { path: string[] } },
) {
// 判断网址和请求方法
// const method = req.method;
// // const url = req.url;
// const { pathname } = new URL(req.url);
//
// console.log('123', method, pathname,)
// const result = await prisma.user.findMany({
// orderBy: {
// createdAt: "desc",
// },
// });
return NextResponse.json({ error: "暂未开发" }, { status: 400 });
}
export const GET = handle;
export const POST = handle;

View File

@ -0,0 +1,58 @@
import { NextRequest, NextResponse } from "next/server";
import prisma from "@/lib/prisma";
async function handle(
req: NextRequest,
{ params }: { params: { path: string[] } },
) {
// 判断网址和请求方法
const method = req.method;
// const url = req.url;
const { pathname, searchParams } = new URL(req.url);
const searchText = searchParams.get("search");
// console.log(req)
if (method === "GET") {
// 是否有查询
const result = searchText
? await prisma.user.findMany({
orderBy: {
createdAt: "desc",
},
where: {
OR: [
{
name: {
contains: searchText,
mode: "insensitive", // 不区分大小写
},
},
{
username: {
contains: searchText,
mode: "insensitive", // 不区分大小写
},
},
{
email: {
contains: searchText,
mode: "insensitive", // 不区分大小写
},
},
],
},
})
: await prisma.user.findMany({
orderBy: {
createdAt: "desc",
},
});
const count = result.length;
return NextResponse.json({ count: count, results: result });
}
return NextResponse.json({ error: "当前方法不支持" }, { status: 400 });
}
export const GET = handle;
export const POST = handle;

View File

@ -1,28 +1,24 @@
import { Flex } from "antd"; import { Flex } from "antd";
import prisma from "@/lib/prisma"; import prisma from "@/lib/prisma";
import { User } from "@prisma/client"; import { User } from "@prisma/client";
import UsersTable from "../../components/users-table"; import UsersTablePart from "../../components/users-table";
import UserTableSearchInput from "../../components/user-table-search";
async function getData() { // async function getData() {
return await prisma.user.findMany({ // return await prisma.user.findMany({
orderBy: { // orderBy: {
createdAt: "desc", // createdAt: "desc",
}, // },
}); // });
} // }
export default async function UsersPage() { export default async function UsersPage() {
const users: User[] = 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>
<div style={{ width: "360px;", fontSize: 0 }}> <UsersTablePart />
<UserTableSearchInput />
</div>
<UsersTable users={users} />
</Flex> </Flex>
</> </>
); );

View File

@ -50,7 +50,7 @@ const items: MenuItem[] = [
const SideBar: React.FC = () => { const SideBar: React.FC = () => {
const [theme, setTheme] = useState<MenuTheme>("dark"); const [theme, setTheme] = useState<MenuTheme>("dark");
const [current, setCurrent] = useState("/admin/ana"); const [current, setCurrent] = useState("");
const router = useRouter(); const router = useRouter();
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const pathname = usePathname(); const pathname = usePathname();
@ -78,7 +78,7 @@ const SideBar: React.FC = () => {
theme={theme} theme={theme}
onClick={onClick} onClick={onClick}
// style={{ width: 256 }} // style={{ width: 256 }}
defaultOpenKeys={["dashboard"]} // defaultOpenKeys={["dashboard"]}
selectedKeys={[current]} selectedKeys={[current]}
mode="inline" mode="inline"
items={items} items={items}

View File

@ -1,31 +0,0 @@
"use client";
import React from "react";
import { AudioOutlined } from "@ant-design/icons";
import { Input, Space } from "antd";
import type { SearchProps } from "antd/es/input/Search";
const { Search } = Input;
const suffix = (
<AudioOutlined
style={{
fontSize: 16,
color: "#1677ff",
}}
/>
);
const onSearch: SearchProps["onSearch"] = (value, _e, info) =>
console.log(info?.source, value);
const UserTableSearchInput: React.FC = () => (
<Search
placeholder="input search text"
onSearch={onSearch}
enterButton
style={{ width: 304 }}
/>
);
export default UserTableSearchInput;

View File

@ -1,6 +1,12 @@
"use client"; "use client";
import React, { useRef, useState } from "react"; import React, {
Dispatch,
SetStateAction,
useEffect,
useRef,
useState,
} from "react";
import { User } from "@prisma/client"; import { User } from "@prisma/client";
import { Space, Table, Tag, Input, Button } from "antd"; import { Space, Table, Tag, Input, Button } from "antd";
import { SearchOutlined } from "@ant-design/icons"; import { SearchOutlined } from "@ant-design/icons";
@ -9,145 +15,194 @@ import type { GetRef, TableColumnsType, TableColumnType } from "antd";
import Highlighter from "react-highlight-words"; import Highlighter from "react-highlight-words";
// 后期考虑删除该依赖 // 后期考虑删除该依赖
import type { SearchProps } from "antd/es/input/Search";
const { Search } = Input;
import { getCurrentTime } from "@/app/utils/custom"; import { getCurrentTime } from "@/app/utils/custom";
interface UserInterface { interface UserInterface {
users: User[]; users: User[];
setUsers: Dispatch<SetStateAction<User[]>>;
loading: Boolean;
setLoading: Dispatch<SetStateAction<boolean>>;
} }
interface SearchTextProps {
searchText: string;
setSearchText: Dispatch<SetStateAction<string>>;
}
type DataIndex = keyof User; type DataIndex = keyof User;
type InputRef = GetRef<typeof Input>; type InputRef = GetRef<typeof Input>;
function UsersTable({ users }: UserInterface) { function UserTableSearchInput({ users, setUsers, setLoading }: UserInterface) {
// const data = {}
// console.log('[data]', users)
const [searchText, setSearchText] = useState(""); const [searchText, setSearchText] = useState("");
const [searchedColumn, setSearchedColumn] = useState(""); // 这里直接搜索,并获取数据不传递搜索的值给表格了。
const searchInput = useRef<InputRef>(null); const onSearch: SearchProps["onSearch"] = (value, _e, info) => {
const handleSearch = ( setSearchText(value);
selectedKeys: string[],
confirm: FilterDropdownProps["confirm"],
dataIndex: DataIndex,
) => {
confirm();
setSearchText(selectedKeys[0]);
setSearchedColumn(dataIndex);
}; };
const handleReset = (clearFilters: () => void) => { useEffect(() => {
clearFilters(); setLoading(true);
setSearchText(""); const fetchUsers = async () => {
try {
const url = new URL("/api/admin/users/", "http://localhost:3000");
url.searchParams.append("search", searchText);
console.log(url, "url");
const response = await fetch(url);
if (response.ok) {
const data = await response.json();
setUsers(data["results"]);
setLoading(false);
}
} catch (e) {
setLoading(false);
console.log("fetch user error: ", e);
}
}; };
const getColumnSearchProps = ( fetchUsers();
dataIndex: DataIndex, console.log(users, "users1");
): TableColumnType<User> => ({ }, [searchText]);
filterDropdown: ({
setSelectedKeys, return (
selectedKeys, <Search
confirm, placeholder="input search text"
clearFilters, onSearch={onSearch}
close, enterButton
}) => ( style={{ width: 304 }}
<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" function UsersTable({ users, setUsers, loading }: UserInterface) {
style={{ width: 90 }} // const [searchText, setSearchText] = useState("");
> // const [searchedColumn, setSearchedColumn] = useState("");
Search // const searchInput = useRef<InputRef>(null);
</Button> // const handleSearch = (
<Button // selectedKeys: string[],
onClick={() => clearFilters && handleReset(clearFilters)} // confirm: FilterDropdownProps["confirm"],
size="small" // dataIndex: DataIndex,
style={{ width: 90 }} // ) => {
> // confirm();
Reset // setSearchText(selectedKeys[0]);
</Button> // setSearchedColumn(dataIndex);
<Button // };
type="link"
size="small" // const handleReset = (clearFilters: () => void) => {
onClick={() => { // clearFilters();
confirm({ closeDropdown: false }); // setSearchText("");
setSearchText((selectedKeys as string[])[0]); // };
setSearchedColumn(dataIndex);
}} // const getColumnSearchProps = (
> // dataIndex: DataIndex,
Filter // ): TableColumnType<User> => ({
</Button> // filterDropdown: ({
<Button // setSelectedKeys,
type="link" // selectedKeys,
size="small" // confirm,
onClick={() => { // clearFilters,
close(); // close,
}} // }) => (
> // <div style={{ padding: 8 }} onKeyDown={(e) => e.stopPropagation()}>
close // <Input
</Button> // ref={searchInput}
</Space> // placeholder={`Search ${dataIndex}`}
</div> // value={selectedKeys[0]}
), // onChange={(e) =>
filterIcon: (filtered: boolean) => ( // setSelectedKeys(e.target.value ? [e.target.value] : [])
<SearchOutlined style={{ color: filtered ? "#1677ff" : undefined }} /> // }
), // onPressEnter={() =>
onFilter: (value, record: User) => { // handleSearch(selectedKeys as string[], confirm, dataIndex)
let result = record?.[dataIndex]; // }
if (result) { // style={{ marginBottom: 8, display: "block" }}
return result // />
.toString() // <Space>
.toLowerCase() // <Button
.includes((value as string).toLowerCase()); // type="primary"
} // onClick={() =>
return false; // handleSearch(selectedKeys as string[], confirm, dataIndex)
}, // }
onFilterDropdownOpenChange: (visible) => { // icon={<SearchOutlined />}
if (visible) { // size="small"
// @ts-ignore // style={{ width: 90 }}
setTimeout(() => searchInput.current?.select(), 100); // >
} // Search
}, // </Button>
render: (text) => // <Button
searchedColumn === dataIndex ? ( // onClick={() => clearFilters && handleReset(clearFilters)}
// @ts-ignore // size="small"
<Highlighter // style={{ width: 90 }}
highlightStyle={{ backgroundColor: "#ffc069", padding: 0 }} // >
searchWords={[searchText]} // Reset
autoEscape // </Button>
textToHighlight={text ? text.toString() : ""} // <Button
/> // type="link"
) : ( // size="small"
text // 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) {
// // @ts-ignore
// setTimeout(() => searchInput.current?.select(), 100);
// }
// },
// render: (text) =>
// searchedColumn === dataIndex ? (
// // @ts-ignore
// <Highlighter
// highlightStyle={{ backgroundColor: "#ffc069", padding: 0 }}
// searchWords={[searchText]}
// autoEscape
// textToHighlight={text ? text.toString() : ""}
// />
// ) : (
// text
// ),
// });
const columns: TableColumnsType<User> = [ const columns: TableColumnsType<User> = [
{ title: "Name", dataIndex: "name", ...getColumnSearchProps("name") }, { title: "Name", dataIndex: "name" },
{ {
title: "UserName", title: "UserName",
dataIndex: "username", dataIndex: "username",
...getColumnSearchProps("username"),
}, },
{ title: "Email", dataIndex: "email", ...getColumnSearchProps("email") }, { title: "Email", dataIndex: "email" },
{ {
title: "createdAt", title: "createdAt",
dataIndex: "createdAt", dataIndex: "createdAt",
render: (value) => getCurrentTime(value), render: (value) => getCurrentTime(new Date(value)),
sorter: (a, b) => { sorter: (a, b) => {
if (a.createdAt < b.createdAt) return 1; if (a.createdAt < b.createdAt) return 1;
return -1; return -1;
@ -156,11 +211,52 @@ function UsersTable({ users }: UserInterface) {
{ {
title: "updatedAt", title: "updatedAt",
dataIndex: "updatedAt", dataIndex: "updatedAt",
render: (value) => getCurrentTime(value), render: (value) => getCurrentTime(new Date(value)),
},
{
title: "Action",
dataIndex: "",
key: "id",
render: () => (
<Space size="middle">
<a></a>
<a></a>
</Space>
),
}, },
]; ];
console.log(users, "users2");
return <Table dataSource={users} rowKey="id" columns={columns} />; return (
<Table
dataSource={users}
rowKey="id"
columns={columns}
loading={loading as boolean}
/>
);
} }
export default UsersTable; function UsersTablePart() {
const [users, setUsers] = useState<User[]>([]);
const [loading, setLoading] = useState(false);
return (
<>
<UserTableSearchInput
users={users}
setUsers={setUsers}
loading={loading}
setLoading={setLoading}
/>
<UsersTable
users={users}
setUsers={setUsers}
loading={loading}
setLoading={setLoading}
/>
</>
);
}
export default UsersTablePart;

View File

@ -219,20 +219,20 @@ input[type="range"]::-ms-thumb:hover {
@include thumbHover(); @include thumbHover();
} }
input[type="number"], //input[type="number"],
input[type="text"], //input[type="text"],
input[type="password"] { //input[type="password"] {
appearance: none; // appearance: none;
border-radius: 10px; // border-radius: 10px;
border: var(--border-in-light); // border: var(--border-in-light);
min-height: 36px; // min-height: 36px;
box-sizing: border-box; // box-sizing: border-box;
background: var(--white); // background: var(--white);
color: var(--black); // color: var(--black);
padding: 0 10px; // padding: 0 10px;
max-width: 50%; // max-width: 50%;
font-family: inherit; // font-family: inherit;
} //}
div.math { div.math {
overflow-x: auto; overflow-x: auto;